[
  {
    "path": ".clang-format",
    "content": "---\nBasedOnStyle: LLVM\nColumnLimit: 120\nUseTab: Never\nTabWidth: 2\n---\nLanguage: Cpp\nDerivePointerAlignment: false\nPointerAlignment: Left\nAlignAfterOpenBracket: Align\nAlignConsecutiveAssignments: false\nIndentCaseLabels: false\nAllowShortBlocksOnASingleLine: Always\nAlignOperands: true\nAlignTrailingComments: true\nAlwaysBreakBeforeMultilineStrings: true\nAlwaysBreakTemplateDeclarations: Yes\nBreakConstructorInitializersBeforeComma: true\nAlwaysBreakAfterReturnType: None\nAlwaysBreakAfterDefinitionReturnType: None\nAllowShortFunctionsOnASingleLine: All\nCpp11BracedListStyle: true\nNamespaceIndentation: None\nBinPackArguments: true\nBinPackParameters: true\nSortIncludes: false\nAccessModifierOffset: -2\nConstructorInitializerIndentWidth: 0\nConstructorInitializerAllOnOneLineOrOnePerLine: true\n"
  },
  {
    "path": ".clang-tidy",
    "content": "Checks: >\n  *,\n  -altera-*,\n  -cppcoreguidelines-avoid-magic-numbers,\n  -cppcoreguidelines-avoid-non-const-global-variables,\n  -cppcoreguidelines-owning-memory,\n  -cppcoreguidelines-pro-bounds-constant-array-index,\n  -cppcoreguidelines-pro-bounds-pointer-arithmetic,\n  -cppcoreguidelines-pro-type-reinterpret-cast,\n  -cppcoreguidelines-pro-type-static-cast-downcast,\n  -cppcoreguidelines-pro-type-union-access,\n  -cppcoreguidelines-pro-type-vararg,\n  -fuchsia-*,\n  -google-runtime-references,\n  -hicpp-*,\n  -llvm-header-guard,\n  -llvmlibc-*,\n  -misc-unused-parameters,\n  -modernize-use-trailing-return-type,\n  -readability-convert-member-functions-to-static,\n  -readability-function-cognitive-complexity,\n  -readability-magic-numbers,\n  -readability-named-parameter,\n  -readability-uppercase-literal-suffix,\nCheckOptions:\n  - key: misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic\n    value: '1'"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where the package manifests are located.\n# Please see the documentation for all configuration options:\n# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates\n\nversion: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"  \n    schedule:\n      interval: \"weekly\"\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: Build\n\non:\n  push:\n    paths-ignore:\n      - '*.md'\n      - '*LICENSE'\n  pull_request:\n\nenv:\n  SCCACHE_GHA_ENABLED: \"true\"\n  RUSTC_WRAPPER: \"sccache\"\n\njobs:\n  build-linux:\n    name: Build Linux (${{matrix.name}} x86_64)\n    runs-on: ubuntu-latest\n\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - name: GCC\n            preset: gcc\n          - name: Clang\n            preset: clang\n\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n          submodules: recursive\n\n      - name: Install dependencies\n        run: |\n          sudo apt-get update\n          sudo apt-get -y install ninja-build clang lld openssl libcurl4-openssl-dev \\\n            zlib1g-dev libglu1-mesa-dev libdbus-1-dev libvulkan-dev libxi-dev libxrandr-dev libasound2-dev \\\n            libpulse-dev libudev-dev libpng-dev libncurses5-dev libx11-xcb-dev libfreetype-dev \\\n            libxinerama-dev libxcursor-dev python3-markupsafe libgtk-3-dev libssl-dev \\\n            libxss-dev libfuse2\n\n      - name: Setup sccache\n        uses: mozilla-actions/sccache-action@v0.0.9\n\n      - name: Configure CMake\n        run: cmake --preset x-linux-ci-${{matrix.preset}}\n\n      - name: Build\n        run: cmake --build --preset x-linux-ci-${{matrix.preset}}\n\n      - name: Generate AppImage\n        run: ci/build-appimage.sh\n\n      - name: Upload artifacts\n        uses: actions/upload-artifact@v7\n        with:\n          name: metaforce-${{env.METAFORCE_VERSION}}-linux-${{matrix.preset}}-x86_64\n          path: |\n            build/install/Metaforce-*.AppImage\n            build/install/debug.tar.*\n\n  build-macos:\n    name: Build macOS (AppleClang universal)\n    runs-on: macos-latest\n\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n          submodules: recursive\n\n      - name: Install dependencies\n        run: |\n          brew update\n          brew upgrade --formula\n          brew install cmake ninja graphicsmagick imagemagick\n          pip3 install --break-system-packages markupsafe\n\n      - name: Setup sccache\n        uses: mozilla-actions/sccache-action@v0.0.9\n\n      - name: Configure CMake\n        run: cmake --preset x-macos-ci\n\n      - name: Build\n        run: cmake --build --preset x-macos-ci\n        \n        #- name: Import signing certificate\n        # if: 'false' # temporarily disabled\n        #uses: devbotsxyz/xcode-import-certificate@master\n        #with:\n        #  certificate-data: ${{secrets.MACOS_CERTIFICATE_DATA}}\n        #  certificate-passphrase: ${{secrets.MACOS_CERTIFICATE_PASSWORD}}\n        #  keychain-password: ${{secrets.MACOS_KEYCHAIN_PASSWORD}}\n\n      - name: Upload artifacts\n        uses: actions/upload-artifact@v7\n        with:\n          name: metaforce-${{env.METAFORCE_VERSION}}-macos-appleclang-universal\n          path: |\n            build/install/Metaforce.app\n            build/install/debug.tar.*\n\n  build-ios:\n    name: Build iOS (AppleClang arm64)\n    runs-on: macos-latest\n\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n          submodules: recursive\n\n      - name: Install dependencies\n        run: |\n          brew update\n          brew upgrade --formula\n          brew install cmake ninja\n          pip3 install --break-system-packages markupsafe\n          rustup target add aarch64-apple-ios\n\n      - name: Setup sccache\n        uses: mozilla-actions/sccache-action@v0.0.9\n\n      - name: Configure CMake\n        run: cmake --preset x-ios-ci\n\n      - name: Build\n        run: cmake --build --preset x-ios-ci --target install\n\n      - name: Upload artifacts\n        uses: actions/upload-artifact@v7\n        with:\n          name: metaforce-${{env.METAFORCE_VERSION}}-ios-appleclang-arm64\n          path: |\n            build/install/Metaforce.app\n            build/install/debug.tar.*\n\n  build-tvos:\n    name: Build tvOS (AppleClang arm64)\n    runs-on: macos-latest\n\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n          submodules: recursive\n\n      - name: Install dependencies\n        run: |\n          brew update\n          brew upgrade --formula\n          brew install cmake ninja\n          pip3 install --break-system-packages markupsafe\n          rustup toolchain install nightly\n          rustup target add --toolchain nightly aarch64-apple-tvos\n\n      - name: Setup sccache\n        uses: mozilla-actions/sccache-action@v0.0.9\n\n      - name: Configure CMake\n        run: cmake --preset x-tvos-ci\n\n      - name: Build\n        run: cmake --build --preset x-tvos-ci --target install\n\n      - name: Upload artifacts\n        uses: actions/upload-artifact@v7\n        with:\n          name: metaforce-${{env.METAFORCE_VERSION}}-tvos-appleclang-arm64\n          path: |\n            build/install/Metaforce.app\n            build/install/debug.tar.*\n\n  build-windows:\n    name: Build Windows (${{matrix.name}} x86_64)\n    runs-on: windows-latest\n\n    env:\n      BUILD_DIR: C:\\build\n\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - name: MSVC\n            preset: msvc\n          - name: Clang\n            preset: clang\n\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n          submodules: recursive\n\n      - name: Enable Visual Studio environment\n        uses: ilammy/msvc-dev-cmd@v1\n\n      # msvc-dev-cmd sets VCPKG_ROOT, set it back\n      - name: Override VCPKG_ROOT\n        run: echo \"VCPKG_ROOT=C:\\vcpkg\" >> $env:GITHUB_ENV\n\n      - name: Setup sccache\n        uses: mozilla-actions/sccache-action@v0.0.9\n\n      - name: Install dependencies\n        run: |\n          choco install ninja\n          vcpkg install zlib:x64-windows-static bzip2:x64-windows-static zstd:x64-windows-static `\n            liblzma:x64-windows-static freetype:x64-windows-static\n\n      - name: Configure CMake\n        run: cmake --preset x-windows-ci-${{matrix.preset}}\n\n      - name: Build\n        run: cmake --build --preset x-windows-ci-${{matrix.preset}}\n\n      - name: Upload artifacts\n        uses: actions/upload-artifact@v7\n        with:\n          name: metaforce-${{env.METAFORCE_VERSION}}-win32-${{matrix.preset}}-x86_64\n          path: |\n            ${{env.BUILD_DIR}}/install/*.exe\n            ${{env.BUILD_DIR}}/install/debug.7z\n"
  },
  {
    "path": ".gitignore",
    "content": "*.autosave\n*.user\n.buildcache/\n.directory\n.DS_Store\n.idea/\n.vs/\nbuild/\ncmake-build-*/\nCMakeUserPresets.json\ndocs/*\nout/\nRuntime/platforms/win/metaforce.rc\nversion.h\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"extern/nod\"]\n\tpath = extern/nod\n\turl = https://github.com/encounter/nod.git\n\tbranch = main\n[submodule \"extern/kabufuda\"]\n\tpath = extern/kabufuda\n\turl = ../kabufuda.git\n\tbranch = master\n[submodule \"extern/jbus\"]\n\tpath = extern/jbus\n\turl = ../jbus.git\n\tbranch = master\n[submodule \"extern/fixNES\"]\n\tpath = extern/fixNES\n\turl = https://github.com/FIX94/fixNES.git\n\tbranch = master\n[submodule \"extern/libjpeg-turbo\"]\n\tpath = extern/libjpeg-turbo\n\turl = ../libjpeg-turbo.git\n\tbranch = thp\n[submodule \"extern/zeus\"]\n\tpath = extern/zeus\n\turl = ../zeus.git\n\tbranch = master\n[submodule \"extern/nativefiledialog-extended\"]\n\tpath = extern/nativefiledialog-extended\n\turl = https://github.com/btzy/nativefiledialog-extended\n[submodule \"extern/aurora\"]\n\tpath = extern/aurora\n\turl = https://github.com/encounter/aurora.git\n\tbranch = main\n[submodule \"extern/spdlog\"]\n\tpath = extern/spdlog\n\turl = https://github.com/gabime/spdlog.git\n\tbranch = v1.x\n[submodule \"extern/musyx\"]\n\tpath = extern/musyx\n\turl = https://github.com/AxioDL/musyx.git\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.25...4.1)\n\nif (NOT CMAKE_BUILD_TYPE)\n  set(CMAKE_BUILD_TYPE \"RelWithDebInfo\" CACHE STRING\n      \"Build type options: Debug Release RelWithDebInfo MinSizeRel\" FORCE)\nendif ()\n\n# obtain revision info from git\nfind_package(Git)\nif (GIT_FOUND)\n  # make sure version information gets re-run when the current Git HEAD changes\n  execute_process(WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} rev-parse --git-path HEAD\n          OUTPUT_VARIABLE metaforce_git_head_filename\n          OUTPUT_STRIP_TRAILING_WHITESPACE)\n  set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS \"${metaforce_git_head_filename}\")\n\n  execute_process(WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} rev-parse --symbolic-full-name HEAD\n          OUTPUT_VARIABLE metaforce_git_head_symbolic\n          OUTPUT_STRIP_TRAILING_WHITESPACE)\n  execute_process(WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}\n          COMMAND ${GIT_EXECUTABLE} rev-parse --git-path ${metaforce_git_head_symbolic}\n          OUTPUT_VARIABLE metaforce_git_head_symbolic_filename\n          OUTPUT_STRIP_TRAILING_WHITESPACE)\n  set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS \"${metaforce_git_head_symbolic_filename}\")\n\n  # defines METAFORCE_WC_REVISION\n  execute_process(WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} rev-parse HEAD\n          OUTPUT_VARIABLE METAFORCE_WC_REVISION\n          OUTPUT_STRIP_TRAILING_WHITESPACE)\n  # defines METAFORCE_WC_DESCRIBE\n  execute_process(WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} describe --tag --long --dirty\n          OUTPUT_VARIABLE METAFORCE_WC_DESCRIBE\n          OUTPUT_STRIP_TRAILING_WHITESPACE)\n\n  # remove hash (and trailing \"-0\" if needed) from description\n  string(REGEX REPLACE \"(-0)?-[^-]+((-dirty)?)$\" \"\\\\2\" METAFORCE_WC_DESCRIBE \"${METAFORCE_WC_DESCRIBE}\")\n\n  # defines METAFORCE_WC_BRANCH\n  execute_process(WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} rev-parse --abbrev-ref HEAD\n          OUTPUT_VARIABLE METAFORCE_WC_BRANCH\n          OUTPUT_STRIP_TRAILING_WHITESPACE)\n  # defines METAFORCE_WC_DATE\n  execute_process(WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} log -1 --format=%ad\n          OUTPUT_VARIABLE METAFORCE_WC_DATE\n          OUTPUT_STRIP_TRAILING_WHITESPACE)\nelse ()\n  message(STATUS \"Unable to find git, commit information will not be available\")\nendif ()\n\nif (METAFORCE_WC_DESCRIBE)\n  string(REGEX REPLACE \"v([0-9]+)\\.([0-9]+)\\.([0-9]+)\\-([0-9]+).*\" \"\\\\1.\\\\2.\\\\3.\\\\4\" METAFORCE_VERSION_STRING \"${METAFORCE_WC_DESCRIBE}\")\n  string(REGEX REPLACE \"v([0-9]+)\\.([0-9]+)\\.([0-9]+).*\" \"\\\\1.\\\\2.\\\\3\" METAFORCE_SHORT_VERSION_STRING \"${METAFORCE_WC_DESCRIBE}\")\nelse ()\n  set(METAFORCE_WC_DESCRIBE \"UNKNOWN-VERSION\")\n  set(METAFORCE_VERSION_STRING \"0.0.0\")\nendif ()\n\nstring(TIMESTAMP CURRENT_YEAR \"%Y\")\n\n# Add version information to CI environment variables\nif(DEFINED ENV{GITHUB_ENV})\n  file(APPEND \"$ENV{GITHUB_ENV}\" \"METAFORCE_VERSION=${METAFORCE_WC_DESCRIBE}\\n\")\nendif()\nmessage(STATUS \"Metaforce version set to ${METAFORCE_WC_DESCRIBE}\")\nmessage(STATUS \"Build type: ${CMAKE_BUILD_TYPE}\")\nproject(metaforce LANGUAGES C CXX VERSION ${METAFORCE_VERSION_STRING})\nif (APPLE AND NOT TVOS AND CMAKE_SYSTEM_NAME STREQUAL tvOS)\n  # ios.toolchain.cmake hack for SDL\n  set(TVOS ON)\n  set(IOS OFF)\nendif ()\nif (EMSCRIPTEN)\n  set(CMAKE_EXECUTABLE_SUFFIX .html)\nendif ()\n\nset(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/Binaries)\n\nif(APPLE AND NOT CMAKE_OSX_SYSROOT)\n  # If the Xcode SDK is lagging behind system version, CMake needs this done first\n  execute_process(COMMAND xcrun --sdk macosx --show-sdk-path\n                  OUTPUT_VARIABLE CMAKE_OSX_SYSROOT\n                  OUTPUT_STRIP_TRAILING_WHITESPACE)\nendif()\n\nset(CMAKE_CXX_STANDARD 20)\nset(CMAKE_CXX_STANDARD_REQUIRED ON)\n\nset(BUILD_SHARED_LIBS OFF CACHE BOOL \"Force shared libs off\" FORCE)\nset(BUILD_STATIC_LIBS ON CACHE BOOL \"Force static libs on\" FORCE)\n\nif (CMAKE_SYSTEM_PROCESSOR STREQUAL x86_64 OR CMAKE_SYSTEM_PROCESSOR STREQUAL AMD64)\n  set(METAFORCE_VECTOR_ISA \"sse41\" CACHE STRING \"Vector ISA to build for (sse2, sse3, sse41, avx, avx2)\")\nendif ()\n\nif(MSVC)\n  if(${METAFORCE_VECTOR_ISA} STREQUAL \"avx2\")\n    add_compile_options(/arch:AVX2)\n    add_compile_definitions(__SSE4_1__=1)\n    message(STATUS \"Building with AVX2 Vector ISA\")\n  elseif(${METAFORCE_VECTOR_ISA} STREQUAL \"avx\")\n    add_compile_options(/arch:AVX)\n    add_compile_definitions(__SSE4_1__=1)\n    message(STATUS \"Building with AVX Vector ISA\")\n  elseif(${METAFORCE_VECTOR_ISA} STREQUAL \"sse41\")\n    add_compile_definitions(__SSE4_1__=1)\n    # clang-cl 10 requires -msse4.1, may be fixed in newer versions?\n    if(\"${CMAKE_CXX_COMPILER_ID}\" STREQUAL Clang)\n      add_compile_options($<$<OR:$<COMPILE_LANGUAGE:C>,$<COMPILE_LANGUAGE:CXX>>:-msse4.1>)\n    endif()\n    message(STATUS \"Building with SSE4.1 Vector ISA\")\n  else()\n    message(STATUS \"Building with SSE2 Vector ISA\")\n  endif()\n\n  if(${CMAKE_GENERATOR} MATCHES \"Visual Studio*\")\n    set(VS_OPTIONS \"/MP\")\n    set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT metaforce)\n  endif()\n\n  # Shaddup MSVC\n  add_compile_definitions(UNICODE=1 _UNICODE=1 __SSE__=1\n      _CRT_SECURE_NO_WARNINGS=1 D_SCL_SECURE_NO_WARNINGS=1\n      _SCL_SECURE_NO_DEPRECATE=1 _CRT_NONSTDC_NO_WARNINGS=1\n      _ENABLE_EXTENDED_ALIGNED_STORAGE=1 NOMINMAX=1\n      _HAS_EXCEPTIONS=0)\n  add_compile_options(/IGNORE:4221\n          $<$<OR:$<COMPILE_LANGUAGE:C>,$<COMPILE_LANGUAGE:CXX>>:/wd4018>\n          $<$<OR:$<COMPILE_LANGUAGE:C>,$<COMPILE_LANGUAGE:CXX>>:/wd4800>\n          $<$<OR:$<COMPILE_LANGUAGE:C>,$<COMPILE_LANGUAGE:CXX>>:/wd4005>\n          $<$<OR:$<COMPILE_LANGUAGE:C>,$<COMPILE_LANGUAGE:CXX>>:/wd4311>\n          $<$<OR:$<COMPILE_LANGUAGE:C>,$<COMPILE_LANGUAGE:CXX>>:/wd4068>\n          $<$<OR:$<COMPILE_LANGUAGE:C>,$<COMPILE_LANGUAGE:CXX>>:/wd4267>\n          $<$<OR:$<COMPILE_LANGUAGE:C>,$<COMPILE_LANGUAGE:CXX>>:/wd4244>\n          $<$<OR:$<COMPILE_LANGUAGE:C>,$<COMPILE_LANGUAGE:CXX>>:/wd4200>\n          $<$<OR:$<COMPILE_LANGUAGE:C>,$<COMPILE_LANGUAGE:CXX>>:/wd4305>\n          $<$<OR:$<COMPILE_LANGUAGE:C>,$<COMPILE_LANGUAGE:CXX>>:/wd4067>\n          $<$<OR:$<COMPILE_LANGUAGE:C>,$<COMPILE_LANGUAGE:CXX>>:/wd4146>\n          $<$<OR:$<COMPILE_LANGUAGE:C>,$<COMPILE_LANGUAGE:CXX>>:/wd4309>\n          $<$<OR:$<COMPILE_LANGUAGE:C>,$<COMPILE_LANGUAGE:CXX>>:/wd4805>\n          ${VS_OPTIONS})\n\n  string(REPLACE \"/GR \" \"\" CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS}\")\n  string(REPLACE \" /EHsc\" \"\" CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS}\")\n  add_compile_options(\n    # Disable exceptions\n    $<$<COMPILE_LANGUAGE:CXX>:/EHsc->\n\n    # Disable RTTI\n    $<$<COMPILE_LANGUAGE:CXX>:/GR->\n\n    # Enforce various standards compliant behavior.\n    $<$<COMPILE_LANGUAGE:CXX>:/permissive->\n\n    # Enable standard volatile semantics.\n    $<$<COMPILE_LANGUAGE:CXX>:/volatile:iso>\n\n    # Reports the proper value for the __cplusplus preprocessor macro.\n    $<$<COMPILE_LANGUAGE:CXX>:/Zc:__cplusplus>\n  )\n\n  if (\"${CMAKE_CXX_COMPILER_ID}\" STREQUAL \"MSVC\")\n    # Flags for MSVC (not clang-cl)\n    add_compile_options(\n      # Enable standards conforming preprocessor.\n      $<$<COMPILE_LANGUAGE:CXX>:/Zc:preprocessor>\n\n      # Allow constexpr variables to have explicit external linkage.\n      $<$<COMPILE_LANGUAGE:CXX>:/Zc:externConstexpr>\n\n      # Assume that new throws exceptions, allowing better code generation.\n      $<$<COMPILE_LANGUAGE:CXX>:/Zc:throwingNew>\n\n      # Link-time Code Generation for Release builds\n      $<$<CONFIG:Release>:/GL>\n    )\n\n    # Link-time Code Generation for Release builds\n    set(CMAKE_STATIC_LINKER_FLAGS_RELEASE \"/LTCG\")\n    set(CMAKE_EXE_LINKER_FLAGS_RELEASE \"/RELEASE /LTCG /OPT:REF /OPT:ICF /INCREMENTAL:NO\")\n    set(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO \"/DEBUG /RELEASE /OPT:REF /OPT:ICF /INCREMENTAL:NO /DEBUGTYPE:cv,fixup\")\n  endif()\n\nelse()\n  if(${CMAKE_SYSTEM_PROCESSOR} STREQUAL x86_64)\n  if(${METAFORCE_VECTOR_ISA} STREQUAL \"native\")\n    add_compile_options(-march=native)\n    message(STATUS \"Building with native ISA\")\n  elseif(${METAFORCE_VECTOR_ISA} STREQUAL \"avx2\")\n    add_compile_options(-mavx2)\n    message(STATUS \"Building with AVX2 Vector ISA\")\n  elseif(${METAFORCE_VECTOR_ISA} STREQUAL \"avx\")\n    add_compile_options(-mavx)\n    message(STATUS \"Building with AVX Vector ISA\")\n  elseif(${METAFORCE_VECTOR_ISA} STREQUAL \"sse41\")\n    add_compile_options(-msse4.1)\n    message(STATUS \"Building with SSE4.1 Vector ISA\")\n  elseif(${METAFORCE_VECTOR_ISA} STREQUAL \"sse3\")\n    add_compile_options(-msse3)\n    message(STATUS \"Building with SSE3 Vector ISA\")\n  elseif(${METAFORCE_VECTOR_ISA} STREQUAL \"sse2\")\n    add_compile_options(-msse2)\n    message(STATUS \"Building with SSE2 Vector ISA\")\n  else()\n    message(STATUS \"Building with x87 Vector ISA\")\n  endif()\n  endif()\n\n  include(CheckCXXCompilerFlag)\n  check_cxx_compiler_flag(-fno-plt HAS_NO_PLT)\n  if (HAS_NO_PLT)\n    add_compile_options(-fno-plt)\n  endif()\n  check_cxx_compiler_flag(-fno-asynchronous-unwind-tables HAS_NO_ASYNC_UNWIND_TABLES)\n  if (HAS_NO_ASYNC_UNWIND_TABLES AND ${CMAKE_BUILD_TYPE} STREQUAL Release)\n    # Binary size reduction\n    add_compile_options(-fno-asynchronous-unwind-tables)\n  endif()\n\n  if (METAFORCE_ASAN)\n    add_compile_options($<$<COMPILE_LANGUAGE:CXX>:-stdlib=libc++> -fsanitize=address\n        -fsanitize-address-use-after-scope)\n    add_link_options($<$<COMPILE_LANGUAGE:CXX>:-stdlib=libc++> -fsanitize=address\n        -fsanitize-address-use-after-scope)\n  elseif(METAFORCE_MSAN)\n    add_compile_options($<$<COMPILE_LANGUAGE:CXX>:-stdlib=libc++> -fsanitize=memory\n                        -fsanitize-memory-track-origins -fsanitize-recover=all)\n  endif()\n  add_compile_options($<$<COMPILE_LANGUAGE:CXX>:-fno-rtti>\n                      $<$<COMPILE_LANGUAGE:CXX>:-fno-exceptions>\n                      -Wall -Wno-multichar\n                      -Wno-unused-variable -Wno-unused-result -Wno-unused-but-set-variable\n                      -Wno-unused-function -Wno-sign-compare -Wno-unknown-pragmas)\n  # doesn't work with generator expression in add_compile_options?\n  if (\"${CMAKE_CXX_COMPILER_ID}\" STREQUAL \"Clang\" OR \"${CMAKE_CXX_COMPILER_ID}\" STREQUAL \"AppleClang\")\n    add_compile_options(-Wno-unknown-warning-option -Wno-unused-private-field)\n  elseif(\"${CMAKE_CXX_COMPILER_ID}\" STREQUAL \"GNU\")\n    add_compile_options(-Wno-lto-type-mismatch -Wno-maybe-uninitialized)\n  endif()\n\n  if(APPLE)\n    add_compile_options(-Wno-error=deprecated-declarations\n                        $<$<CONFIG:Release>:-flto=thin>)\n  endif()\n\nendif()\n\nif(${CMAKE_SYSTEM_NAME} MATCHES \"FreeBSD\")\n  include_directories(/usr/local/include)\n  link_directories(/usr/local/lib)\nendif()\n\nif(\"${CMAKE_SYSTEM_NAME}\" STREQUAL \"Linux\")\n  if(\"${CMAKE_CXX_COMPILER_ID}\" STREQUAL \"Clang\")\n    if(${CMAKE_BUILD_TYPE} STREQUAL Debug OR ${CMAKE_BUILD_TYPE} STREQUAL RelWithDebInfo)\n      # This is required to summarize std::string\n      add_compile_options(-fno-limit-debug-info -fno-omit-frame-pointer)\n    endif()\n    option(USE_LD_LLD \"Link with LLD\" ON)\n  elseif(\"${CMAKE_CXX_COMPILER_ID}\" STREQUAL \"GNU\")\n    option(USE_LD_GOLD \"Link with GNU Gold\" ON)\n  endif()\n\n  include(CheckIPOSupported)\n  check_ipo_supported(RESULT LTO_SUPPORTED)\n  if(LTO_SUPPORTED AND (\"${CMAKE_BUILD_TYPE}\" STREQUAL \"Release\" OR \"${CMAKE_BUILD_TYPE}\" STREQUAL \"RelWithDebInfo\"))\n    option(USE_LTO \"Enable LTO\" ON)\n  else()\n    option(USE_LTO \"Enable LTO\" OFF)\n  endif()\n  # FIXME GCC 11.1 -flto is completely broken\n  if(CMAKE_COMPILER_IS_GNUCC AND CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 11.1.0)\n    message(NOTICE \"Working around GCC 11.1 bug; disabling LTO\")\n    set(USE_LTO OFF)\n  endif()\nelse()\n  option(USE_LD_LLD \"Link with LLD\" OFF)\n  option(USE_LD_GOLD \"Link with GNU Gold\" OFF)\n  option(USE_LTO \"Enable LTO\" OFF)\nendif()\nif(USE_LD_LLD)\n  execute_process(COMMAND ${CMAKE_C_COMPILER} -fuse-ld=lld -Wl,--version ERROR_QUIET OUTPUT_VARIABLE LD_VERSION)\n  if(\"${LD_VERSION}\" MATCHES \"LLD\")\n    set(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=lld -Wl,--build-id=uuid\")\n    set(CMAKE_SHARED_LINKER_FLAGS \"${CMAKE_SHARED_LINKER_FLAGS} -fuse-ld=lld\")\n    if(USE_LTO)\n      add_compile_options(-flto=thin)\n      add_link_options(-flto=thin)\n      message(STATUS \"LLD linker enabled with LTO.\")\n    else()\n      message(STATUS \"LLD linker enabled.\")\n    endif()\n    set(USE_LD_GOLD OFF)\n  else()\n    message(WARNING \"LLD linker isn't available, using the default system linker.\")\n    set(USE_LD_LLD OFF)\n  endif()\nendif()\nif(USE_LD_GOLD)\n  execute_process(COMMAND ${CMAKE_C_COMPILER} -fuse-ld=gold -Wl,--version ERROR_QUIET OUTPUT_VARIABLE LD_VERSION)\n  if(\"${LD_VERSION}\" MATCHES \"GNU gold\")\n    set(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=gold -Wl,--disable-new-dtags\")\n    set(CMAKE_SHARED_LINKER_FLAGS \"${CMAKE_SHARED_LINKER_FLAGS} -fuse-ld=gold -Wl,--disable-new-dtags\")\n    if (USE_SPLIT_DWARF)\n      add_compile_options(-gsplit-dwarf -Wl,--gdb-index)\n      add_link_options(-gsplit-dwarf -Wl,--gdb-index)\n      message(STATUS \"GNU gold linker enabled with split DWARF.\")\n    elseif (USE_LTO)\n      add_compile_options(-flto)\n      add_link_options(-flto)\n      message(STATUS \"GNU gold linker enabled with LTO.\")\n    else()\n      message(STATUS \"GNU gold linker enabled.\")\n    endif()\n    set(USE_LD_LLD OFF)\n  else()\n    message(WARNING \"GNU gold linker isn't available, using the default system linker.\")\n    set(USE_LD_GOLD OFF)\n  endif()\nendif()\n\nfind_package(ZLIB REQUIRED)\n\ninclude(ExternalProject)\nset(BINTOC_CMAKE_ARGS\n    -DCMAKE_BUILD_TYPE=Release\n    -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>)\n\nif (CMAKE_TOOLCHAIN_FILE AND NOT CMAKE_CROSSCOMPILING)\n  if (IS_ABSOLUTE \"${CMAKE_TOOLCHAIN_FILE}\")\n    set(BINTOC_TOOLCHAIN_FILE \"${CMAKE_TOOLCHAIN_FILE}\")\n  else ()\n    set(BINTOC_TOOLCHAIN_FILE \"${CMAKE_CURRENT_LIST_DIR}/${CMAKE_TOOLCHAIN_FILE}\")\n  endif ()\n  list(APPEND BINTOC_CMAKE_ARGS -DCMAKE_TOOLCHAIN_FILE:PATH=${BINTOC_TOOLCHAIN_FILE})\nendif ()\n\nif (CMAKE_MAKE_PROGRAM)\n  list(APPEND BINTOC_CMAKE_ARGS -DCMAKE_MAKE_PROGRAM:FILEPATH=${CMAKE_MAKE_PROGRAM})\nendif ()\n\nif (DEFINED VCPKG_TARGET_TRIPLET AND NOT \"${VCPKG_TARGET_TRIPLET}\" STREQUAL \"\" AND NOT CMAKE_CROSSCOMPILING)\n  list(APPEND BINTOC_CMAKE_ARGS -DVCPKG_TARGET_TRIPLET:STRING=${VCPKG_TARGET_TRIPLET})\nendif ()\n\nExternalProject_Add(bintoc\n    SOURCE_DIR \"${CMAKE_CURRENT_LIST_DIR}/bintoc\"\n    CMAKE_ARGS ${BINTOC_CMAKE_ARGS}\n    INSTALL_COMMAND ${CMAKE_COMMAND} --build . --config Release --target install)\ninclude(${CMAKE_CURRENT_LIST_DIR}/bintoc/bintocHelpers.cmake)\n\nadd_subdirectory(extern)\nadd_subdirectory(imgui)\nadd_subdirectory(NESEmulator EXCLUDE_FROM_ALL)\nadd_subdirectory(Runtime)\nadd_subdirectory(gbalink EXCLUDE_FROM_ALL)\n\nconfigure_file(${CMAKE_SOURCE_DIR}/version.h.in ${CMAKE_BINARY_DIR}/version.h)\n\n# Packaging logic\nfunction(get_target_output_name target result_var)\n  get_target_property(output_name ${target} OUTPUT_NAME)\n  if (output_name STREQUAL \"output_name-NOTFOUND\")\n    set(${result_var} \"${target}\" PARENT_SCOPE)\n  else ()\n    set(${result_var} \"${output_name}\" PARENT_SCOPE)\n  endif ()\nendfunction()\nfunction(get_target_prefix target result_var)\n  set(${result_var} \"\" PARENT_SCOPE)\n  if (APPLE)\n    # Have to recreate some bundle logic here, since CMake can't tell us\n    get_target_property(is_bundle ${target} MACOSX_BUNDLE)\n    if (is_bundle)\n      get_target_output_name(${target} output_name)\n      if (CMAKE_SYSTEM_NAME STREQUAL Darwin)\n        set(${result_var} \"${output_name}.app/Contents/MacOS/\" PARENT_SCOPE)\n      else ()\n        set(${result_var} \"${output_name}.app/\" PARENT_SCOPE)\n      endif ()\n    endif ()\n  endif ()\nendfunction()\nlist(APPEND BINARY_TARGETS metaforce)\nset(EXTRA_TARGETS \"\")\nif (TARGET crashpad_handler)\n  list(APPEND EXTRA_TARGETS crashpad_handler)\nendif ()\nset(BIN_PREFIX \"${CMAKE_INSTALL_PREFIX}\")\ninstall(TARGETS ${BINARY_TARGETS} ${EXTRA_TARGETS} DESTINATION ${BIN_PREFIX})\nif (CMAKE_BUILD_TYPE STREQUAL Debug OR CMAKE_BUILD_TYPE STREQUAL RelWithDebInfo)\n  set(DEBUG_FILES_LIST \"\")\n  foreach (target IN LISTS BINARY_TARGETS EXTRA_TARGETS)\n    get_target_output_name(${target} output_name)\n    if (WIN32)\n      install(FILES $<TARGET_PDB_FILE:${target}> DESTINATION ${BIN_PREFIX} OPTIONAL)\n    elseif (APPLE)\n      get_target_prefix(${target} target_prefix)\n      install(CODE \"execute_process(WORKING_DIRECTORY \\\"${BIN_PREFIX}\\\" COMMAND rm -fr \\\"$<TARGET_FILE_NAME:${target}>.dSYM\\\")\")\n      install(CODE \"execute_process(WORKING_DIRECTORY \\\"${BIN_PREFIX}\\\" COMMAND dsymutil \\\"${target_prefix}$<TARGET_FILE_NAME:${target}>\\\")\")\n      install(CODE \"execute_process(WORKING_DIRECTORY \\\"${BIN_PREFIX}\\\" COMMAND strip -S \\\"${target_prefix}$<TARGET_FILE_NAME:${target}>\\\")\")\n      if (NOT target_prefix STREQUAL \"\")\n        install(CODE \"execute_process(WORKING_DIRECTORY \\\"${BIN_PREFIX}\\\" COMMAND mv \\\"${target_prefix}$<TARGET_FILE_NAME:${target}>.dSYM\\\" .)\")\n      endif ()\n    elseif (UNIX)\n      get_target_prefix(${target} target_prefix)\n      install(CODE \"execute_process(WORKING_DIRECTORY \\\"${BIN_PREFIX}\\\" COMMAND objcopy --only-keep-debug \\\"${target_prefix}$<TARGET_FILE_NAME:${target}>\\\" \\\"${target_prefix}$<TARGET_FILE_NAME:${target}>.dbg\\\")\")\n      install(CODE \"execute_process(WORKING_DIRECTORY \\\"${BIN_PREFIX}\\\" COMMAND objcopy --strip-debug --add-gnu-debuglink=$<TARGET_FILE_NAME:${target}>.dbg \\\"${target_prefix}$<TARGET_FILE_NAME:${target}>\\\")\")\n    endif ()\n    list(APPEND DEBUG_FILES_LIST \"${output_name}\")\n  endforeach ()\n  if (WIN32)\n    list(TRANSFORM DEBUG_FILES_LIST APPEND \".pdb\")\n    list(JOIN DEBUG_FILES_LIST \" \" DEBUG_FILES)\n    install(CODE \"execute_process(WORKING_DIRECTORY \\\"${BIN_PREFIX}\\\" COMMAND 7z a -t7z \\\"${CMAKE_INSTALL_PREFIX}/debug.7z\\\" ${DEBUG_FILES})\")\n  elseif (APPLE)\n    list(TRANSFORM DEBUG_FILES_LIST APPEND \".dSYM\")\n    list(JOIN DEBUG_FILES_LIST \" \" DEBUG_FILES)\n    install(CODE \"execute_process(WORKING_DIRECTORY \\\"${BIN_PREFIX}\\\" COMMAND tar acfv \\\"${CMAKE_INSTALL_PREFIX}/debug.tar.xz\\\" ${DEBUG_FILES})\")\n  elseif (UNIX)\n    list(TRANSFORM DEBUG_FILES_LIST APPEND \".dbg\")\n    list(JOIN DEBUG_FILES_LIST \" \" DEBUG_FILES)\n    install(CODE \"execute_process(WORKING_DIRECTORY \\\"${BIN_PREFIX}\\\" COMMAND tar -I \\\"xz -9 -T0\\\" -cvf \\\"${CMAKE_INSTALL_PREFIX}/debug.tar.xz\\\" ${DEBUG_FILES})\")\n  endif ()\nendif ()\nforeach (target IN LISTS BINARY_TARGETS)\n  get_target_prefix(${target} target_prefix)\n  foreach (extra_target IN LISTS EXTRA_TARGETS)\n    get_target_prefix(${extra_target} extra_prefix)\n    if (NOT \"${target_prefix}\" STREQUAL \"${extra_prefix}\")\n      # Copy extra target to target prefix\n      install(CODE \"execute_process(WORKING_DIRECTORY \\\"${BIN_PREFIX}\\\" COMMAND cp \\\"${extra_prefix}$<TARGET_FILE_NAME:${extra_target}>\\\" \\\"${target_prefix}$<TARGET_FILE_NAME:${extra_target}>\\\")\")\n    endif ()\n  endforeach ()\nendforeach ()\n"
  },
  {
    "path": "CMakePresets.json",
    "content": "{\n  \"version\": 2,\n  \"cmakeMinimumRequired\": {\n    \"major\": 3,\n    \"minor\": 20,\n    \"patch\": 0\n  },\n  \"configurePresets\": [\n    {\n      \"name\": \"debug\",\n      \"hidden\": true,\n      \"cacheVariables\": {\n        \"CMAKE_BUILD_TYPE\": \"Debug\",\n        \"CMAKE_MSVC_RUNTIME_LIBRARY\": \"MultiThreadedDebugDLL\"\n      }\n    },\n    {\n      \"name\": \"relwithdebinfo\",\n      \"hidden\": true,\n      \"cacheVariables\": {\n        \"CMAKE_BUILD_TYPE\": \"RelWithDebInfo\",\n        \"CMAKE_MSVC_RUNTIME_LIBRARY\": \"MultiThreaded\",\n        \"SENTRY_DSN\": \"$env{SENTRY_DSN}\"\n      }\n    },\n    {\n      \"name\": \"linux-default\",\n      \"displayName\": \"Linux (default)\",\n      \"generator\": \"Ninja\",\n      \"binaryDir\": \"${sourceDir}/build/${presetName}\",\n      \"cacheVariables\": {\n        \"CMAKE_INSTALL_PREFIX\": \"${sourceDir}/build/install\",\n        \"USE_LTO\": {\n          \"type\": \"BOOL\",\n          \"value\": false\n        }\n      },\n      \"vendor\": {\n        \"microsoft.com/VisualStudioSettings/CMake/1.0\": {\n          \"hostOS\": [\n            \"Linux\"\n          ]\n        },\n        \"microsoft.com/VisualStudioRemoteSettings/CMake/1.0\": {\n          \"sourceDir\": \"$env{HOME}/.vs/$ms{projectDirName}\"\n        }\n      }\n    },\n    {\n      \"name\": \"linux-default-debug\",\n      \"displayName\": \"Linux (default) Debug\",\n      \"inherits\": [\n        \"debug\",\n        \"linux-default\"\n      ]\n    },\n    {\n      \"name\": \"linux-default-relwithdebinfo\",\n      \"displayName\": \"Linux (default) RelWithDebInfo\",\n      \"inherits\": [\n        \"relwithdebinfo\",\n        \"linux-default\"\n      ]\n    },\n    {\n      \"name\": \"linux-clang\",\n      \"displayName\": \"Linux (Clang)\",\n      \"inherits\": [\n        \"linux-default\"\n      ],\n      \"cacheVariables\": {\n        \"CMAKE_C_COMPILER\": \"clang\",\n        \"CMAKE_CXX_COMPILER\": \"clang++\"\n      }\n    },\n    {\n      \"name\": \"linux-clang-debug\",\n      \"displayName\": \"Linux (Clang) Debug\",\n      \"inherits\": [\n        \"debug\",\n        \"linux-clang\"\n      ]\n    },\n    {\n      \"name\": \"linux-clang-relwithdebinfo\",\n      \"displayName\": \"Linux (Clang) RelWithDebInfo\",\n      \"inherits\": [\n        \"relwithdebinfo\",\n        \"linux-clang\"\n      ],\n      \"cacheVariables\": {\n        \"USE_LTO\": {\n          \"type\": \"BOOL\",\n          \"value\": true\n        }\n      }\n    },\n    {\n      \"name\": \"linux-clang-debug-asan\",\n      \"displayName\": \"Linux (Clang) Debug w/ ASAN\",\n      \"inherits\": [\n        \"linux-clang-debug\"\n      ],\n      \"cacheVariables\": {\n        \"METAFORCE_ASAN\": {\n          \"type\": \"BOOL\",\n          \"value\": true\n        }\n      }\n    },\n    {\n      \"name\": \"linux-clang-relwithdebinfo-asan\",\n      \"displayName\": \"Linux (Clang) RelWithDebInfo w/ ASAN\",\n      \"inherits\": [\n        \"linux-clang-relwithdebinfo\"\n      ],\n      \"cacheVariables\": {\n        \"METAFORCE_ASAN\": {\n          \"type\": \"BOOL\",\n          \"value\": true\n        }\n      }\n    },\n    {\n      \"name\": \"windows-msvc\",\n      \"displayName\": \"Windows (MSVC)\",\n      \"generator\": \"Ninja\",\n      \"binaryDir\": \"${sourceDir}/out/build/${presetName}\",\n      \"architecture\": {\n        \"value\": \"x64\",\n        \"strategy\": \"external\"\n      },\n      \"cacheVariables\": {\n        \"CMAKE_C_COMPILER\": \"cl\",\n        \"CMAKE_CXX_COMPILER\": \"cl\",\n        \"CMAKE_INSTALL_PREFIX\": \"${sourceDir}/out/install\",\n        \"CMAKE_TOOLCHAIN_FILE\": {\n          \"type\": \"FILEPATH\",\n          \"value\": \"$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake\"\n        },\n        \"VCPKG_TARGET_TRIPLET\": \"x64-windows-static\",\n        \"VCPKG_SETUP_CMAKE_PROGRAM_PATH\": {\n          \"type\": \"BOOL\",\n          \"value\": false\n        }\n      },\n      \"vendor\": {\n        \"microsoft.com/VisualStudioSettings/CMake/1.0\": {\n          \"hostOS\": [\n            \"Windows\"\n          ]\n        }\n      }\n    },\n    {\n      \"name\": \"windows-msvc-debug\",\n      \"displayName\": \"Windows (MSVC) Debug\",\n      \"inherits\": [\n        \"debug\",\n        \"windows-msvc\"\n      ]\n    },\n    {\n      \"name\": \"windows-msvc-relwithdebinfo\",\n      \"displayName\": \"Windows (MSVC) RelWithDebInfo\",\n      \"inherits\": [\n        \"relwithdebinfo\",\n        \"windows-msvc\"\n      ]\n    },\n    {\n      \"name\": \"windows-clang\",\n      \"displayName\": \"Windows (Clang)\",\n      \"inherits\": [\n        \"windows-msvc\"\n      ],\n      \"cacheVariables\": {\n        \"CMAKE_C_COMPILER\": \"clang-cl\",\n        \"CMAKE_CXX_COMPILER\": \"clang-cl\",\n        \"CMAKE_LINKER\": \"lld-link\"\n      },\n      \"vendor\": {\n        \"microsoft.com/VisualStudioSettings/CMake/1.0\": {\n          \"intelliSenseMode\": \"windows-clang-x64\"\n        }\n      }\n    },\n    {\n      \"name\": \"windows-clang-debug\",\n      \"displayName\": \"Windows (Clang) Debug\",\n      \"inherits\": [\n        \"debug\",\n        \"windows-clang\"\n      ]\n    },\n    {\n      \"name\": \"windows-clang-relwithdebinfo\",\n      \"displayName\": \"Windows (Clang) RelWithDebInfo\",\n      \"inherits\": [\n        \"relwithdebinfo\",\n        \"windows-clang\"\n      ]\n    },\n    {\n      \"name\": \"macos-default\",\n      \"displayName\": \"macOS (default)\",\n      \"generator\": \"Ninja\",\n      \"binaryDir\": \"${sourceDir}/build/${presetName}\",\n      \"cacheVariables\": {\n        \"CMAKE_INSTALL_PREFIX\": \"${sourceDir}/build/install\"\n      },\n      \"vendor\": {\n        \"microsoft.com/VisualStudioSettings/CMake/1.0\": {\n          \"hostOS\": [\n            \"macOS\"\n          ]\n        }\n      }\n    },\n    {\n      \"name\": \"macos-default-debug\",\n      \"displayName\": \"macOS (default) Debug\",\n      \"inherits\": [\n        \"debug\",\n        \"macos-default\"\n      ]\n    },\n    {\n      \"name\": \"macos-default-relwithdebinfo\",\n      \"displayName\": \"macOS (default) RelWithDebInfo\",\n      \"inherits\": [\n        \"relwithdebinfo\",\n        \"macos-default\"\n      ]\n    },\n    {\n      \"name\": \"ios-default\",\n      \"displayName\": \"iOS\",\n      \"generator\": \"Ninja\",\n      \"binaryDir\": \"${sourceDir}/build/${presetName}\",\n      \"inherits\": [\n        \"relwithdebinfo\"\n      ],\n      \"cacheVariables\": {\n        \"CMAKE_TOOLCHAIN_FILE\": \"ios.toolchain.cmake\",\n        \"CMAKE_INSTALL_PREFIX\": \"${sourceDir}/build/install\",\n        \"PLATFORM\": \"OS64\",\n        \"DEPLOYMENT_TARGET\": \"14.0\",\n        \"ENABLE_BITCODE\": {\n          \"type\": \"BOOL\",\n          \"value\": false\n        },\n        \"Rust_CARGO_TARGET\": \"aarch64-apple-ios\",\n        \"BUILD_SHARED_LIBS\": {\n          \"type\": \"BOOL\",\n          \"value\": false\n        },\n        \"IMGUI_USE_FREETYPE\": {\n          \"type\": \"BOOL\",\n          \"value\": false\n        },\n        \"CMAKE_DISABLE_FIND_PACKAGE_BZip2\": {\n          \"type\": \"BOOL\",\n          \"value\": true\n        },\n        \"CMAKE_DISABLE_FIND_PACKAGE_LibLZMA\": {\n          \"type\": \"BOOL\",\n          \"value\": true\n        },\n        \"CMAKE_DISABLE_FIND_PACKAGE_zstd\": {\n          \"type\": \"BOOL\",\n          \"value\": true\n        }\n      },\n      \"vendor\": {\n        \"microsoft.com/VisualStudioSettings/CMake/1.0\": {\n          \"hostOS\": [\n            \"macOS\"\n          ]\n        }\n      }\n    },\n    {\n      \"name\": \"tvos-default\",\n      \"displayName\": \"tvOS\",\n      \"generator\": \"Ninja\",\n      \"binaryDir\": \"${sourceDir}/build/${presetName}\",\n      \"inherits\": [\n        \"relwithdebinfo\"\n      ],\n      \"cacheVariables\": {\n        \"CMAKE_TOOLCHAIN_FILE\": \"ios.toolchain.cmake\",\n        \"CMAKE_INSTALL_PREFIX\": \"${sourceDir}/build/install\",\n        \"PLATFORM\": \"TVOS\",\n        \"DEPLOYMENT_TARGET\": \"14.5\",\n        \"ENABLE_BITCODE\": {\n          \"type\": \"BOOL\",\n          \"value\": false\n        },\n        \"Rust_CARGO_TARGET\": \"aarch64-apple-tvos\",\n        \"Rust_TOOLCHAIN\": \"nightly\",\n        \"BUILD_SHARED_LIBS\": {\n          \"type\": \"BOOL\",\n          \"value\": false\n        },\n        \"IMGUI_USE_FREETYPE\": {\n          \"type\": \"BOOL\",\n          \"value\": false\n        },\n        \"CMAKE_DISABLE_FIND_PACKAGE_BZip2\": {\n          \"type\": \"BOOL\",\n          \"value\": true\n        },\n        \"CMAKE_DISABLE_FIND_PACKAGE_LibLZMA\": {\n          \"type\": \"BOOL\",\n          \"value\": true\n        },\n        \"CMAKE_DISABLE_FIND_PACKAGE_zstd\": {\n          \"type\": \"BOOL\",\n          \"value\": true\n        }\n      },\n      \"vendor\": {\n        \"microsoft.com/VisualStudioSettings/CMake/1.0\": {\n          \"hostOS\": [\n            \"macOS\"\n          ]\n        }\n      }\n    },\n    {\n      \"name\": \"android-base\",\n      \"hidden\": true,\n      \"generator\": \"Ninja\",\n      \"binaryDir\": \"${sourceDir}/build/${presetName}\",\n      \"inherits\": [\n        \"relwithdebinfo\"\n      ],\n      \"cacheVariables\": {\n        \"CMAKE_INSTALL_PREFIX\": \"${sourceDir}/build/install\",\n        \"CMAKE_TOOLCHAIN_FILE\": \"$env{ANDROID_HOME}/ndk/$env{ANDROID_NDK_VERSION}/build/cmake/android.toolchain.cmake\",\n        \"ANDROID_PLATFORM\": \"android-24\"\n      }\n    },\n    {\n      \"name\": \"android-arm64\",\n      \"displayName\": \"Android (arm64-v8a)\",\n      \"inherits\": [\n        \"android-base\"\n      ],\n      \"cacheVariables\": {\n        \"ANDROID_ABI\": \"arm64-v8a\"\n      }\n    },\n    {\n      \"name\": \"android-x86_64\",\n      \"displayName\": \"Android (x86_64)\",\n      \"inherits\": [\n        \"android-base\"\n      ],\n      \"cacheVariables\": {\n        \"ANDROID_ABI\": \"x86_64\"\n      }\n    },\n    {\n      \"name\": \"x-linux-ci\",\n      \"hidden\": true,\n      \"inherits\": [\n        \"relwithdebinfo\"\n      ],\n      \"cacheVariables\": {\n        \"CMAKE_C_COMPILER_LAUNCHER\": \"sccache\",\n        \"CMAKE_CXX_COMPILER_LAUNCHER\": \"sccache\"\n      }\n    },\n    {\n      \"name\": \"x-linux-ci-gcc\",\n      \"inherits\": [\n        \"x-linux-ci\",\n        \"linux-default\"\n      ]\n    },\n    {\n      \"name\": \"x-linux-ci-clang\",\n      \"inherits\": [\n        \"x-linux-ci\",\n        \"linux-clang\"\n      ]\n    },\n    {\n      \"name\": \"x-macos-ci\",\n      \"inherits\": [\n        \"macos-default-relwithdebinfo\"\n      ],\n      \"cacheVariables\": {\n        \"CMAKE_C_COMPILER_LAUNCHER\": \"sccache\",\n        \"CMAKE_CXX_COMPILER_LAUNCHER\": \"sccache\"\n      }\n    },\n    {\n      \"name\": \"x-ios-ci\",\n      \"inherits\": [\n        \"ios-default\"\n      ],\n      \"cacheVariables\": {\n        \"CMAKE_C_COMPILER_LAUNCHER\": \"sccache\",\n        \"CMAKE_CXX_COMPILER_LAUNCHER\": \"sccache\"\n      }\n    },\n    {\n      \"name\": \"x-tvos-ci\",\n      \"inherits\": [\n        \"tvos-default\"\n      ],\n      \"cacheVariables\": {\n        \"CMAKE_C_COMPILER_LAUNCHER\": \"sccache\",\n        \"CMAKE_CXX_COMPILER_LAUNCHER\": \"sccache\"\n      }\n    },\n    {\n      \"name\": \"x-windows-ci\",\n      \"hidden\": true,\n      \"inherits\": [\n        \"relwithdebinfo\"\n      ],\n      \"binaryDir\": \"$env{BUILD_DIR}\",\n      \"cacheVariables\": {\n        \"CMAKE_INSTALL_PREFIX\": \"$env{BUILD_DIR}/install\",\n        \"CMAKE_C_COMPILER_LAUNCHER\": \"sccache\",\n        \"CMAKE_CXX_COMPILER_LAUNCHER\": \"sccache\",\n        \"CMAKE_MSVC_DEBUG_INFORMATION_FORMAT\": \"Embedded\"\n      }\n    },\n    {\n      \"name\": \"x-windows-ci-msvc\",\n      \"inherits\": [\n        \"x-windows-ci\",\n        \"windows-msvc\"\n      ]\n    },\n    {\n      \"name\": \"x-windows-ci-clang\",\n      \"inherits\": [\n        \"x-windows-ci\",\n        \"windows-clang\"\n      ]\n    }\n  ],\n  \"buildPresets\": [\n    {\n      \"name\": \"linux-default-debug\",\n      \"configurePreset\": \"linux-default-debug\",\n      \"description\": \"Linux (default) debug build\",\n      \"displayName\": \"Linux (default) Debug\"\n    },\n    {\n      \"name\": \"linux-default-relwithdebinfo\",\n      \"configurePreset\": \"linux-default-relwithdebinfo\",\n      \"description\": \"Linux (default) release build with debug info\",\n      \"displayName\": \"Linux (default) RelWithDebInfo\"\n    },\n    {\n      \"name\": \"linux-clang-debug\",\n      \"configurePreset\": \"linux-clang-debug\",\n      \"description\": \"Linux (Clang) debug build\",\n      \"displayName\": \"Linux (Clang) Debug\"\n    },\n    {\n      \"name\": \"linux-clang-relwithdebinfo\",\n      \"configurePreset\": \"linux-clang-relwithdebinfo\",\n      \"description\": \"Linux (Clang) release build with debug info\",\n      \"displayName\": \"Linux (Clang) RelWithDebInfo\"\n    },\n    {\n      \"name\": \"linux-clang-debug-asan\",\n      \"configurePreset\": \"linux-clang-debug-asan\",\n      \"description\": \"Linux (Clang) debug build w/ ASAN\",\n      \"displayName\": \"Linux (Clang) Debug w/ ASAN\"\n    },\n    {\n      \"name\": \"linux-clang-relwithdebinfo-asan\",\n      \"configurePreset\": \"linux-clang-relwithdebinfo-asan\",\n      \"description\": \"Linux (Clang) release build with debug info w/ ASAN\",\n      \"displayName\": \"Linux (Clang) RelWithDebInfo w/ ASAN\"\n    },\n    {\n      \"name\": \"macos-default-debug\",\n      \"configurePreset\": \"macos-default-debug\",\n      \"description\": \"macOS debug build\",\n      \"displayName\": \"macOS Debug\"\n    },\n    {\n      \"name\": \"macos-default-relwithdebinfo\",\n      \"configurePreset\": \"macos-default-relwithdebinfo\",\n      \"description\": \"macOS release build with debug info\",\n      \"displayName\": \"macOS RelWithDebInfo\"\n    },\n    {\n      \"name\": \"ios-default\",\n      \"configurePreset\": \"ios-default\",\n      \"description\": \"iOS release build with debug info\",\n      \"displayName\": \"iOS RelWithDebInfo\",\n      \"targets\": [\n        \"metaforce\"\n      ]\n    },\n    {\n      \"name\": \"tvos-default\",\n      \"configurePreset\": \"tvos-default\",\n      \"description\": \"tvOS release build with debug info\",\n      \"displayName\": \"tvOS RelWithDebInfo\",\n      \"targets\": [\n        \"metaforce\"\n      ]\n    },\n    {\n      \"name\": \"android-arm64\",\n      \"configurePreset\": \"android-arm64\",\n      \"description\": \"Android arm64-v8a release build with debug info\",\n      \"displayName\": \"Android arm64-v8a RelWithDebInfo\",\n      \"targets\": [\n        \"metaforce\"\n      ]\n    },\n    {\n      \"name\": \"android-x86_64\",\n      \"configurePreset\": \"android-x86_64\",\n      \"description\": \"Android x86_64 release build with debug info\",\n      \"displayName\": \"Android x86_64 RelWithDebInfo\",\n      \"targets\": [\n        \"metaforce\"\n      ]\n    },\n    {\n      \"name\": \"windows-msvc-debug\",\n      \"configurePreset\": \"windows-msvc-debug\",\n      \"description\": \"Windows (MSVC) debug build\",\n      \"displayName\": \"Windows (MSVC) Debug\"\n    },\n    {\n      \"name\": \"windows-msvc-relwithdebinfo\",\n      \"configurePreset\": \"windows-msvc-relwithdebinfo\",\n      \"description\": \"Windows (MSVC) release build with debug info\",\n      \"displayName\": \"Windows (MSVC) RelWithDebInfo\"\n    },\n    {\n      \"name\": \"windows-clang-debug\",\n      \"configurePreset\": \"windows-clang-debug\",\n      \"description\": \"Windows (Clang) debug build\",\n      \"displayName\": \"Windows (Clang) Debug\"\n    },\n    {\n      \"name\": \"windows-clang-relwithdebinfo\",\n      \"configurePreset\": \"windows-clang-relwithdebinfo\",\n      \"description\": \"Windows (Clang) release build with debug info\",\n      \"displayName\": \"Windows (Clang) RelWithDebInfo\"\n    },\n    {\n      \"name\": \"x-linux-ci-gcc\",\n      \"configurePreset\": \"x-linux-ci-gcc\",\n      \"description\": \"(Internal) Linux CI GCC\",\n      \"displayName\": \"(Internal) Linux CI GCC\",\n      \"targets\": [\n        \"install\"\n      ]\n    },\n    {\n      \"name\": \"x-linux-ci-clang\",\n      \"configurePreset\": \"x-linux-ci-clang\",\n      \"description\": \"(Internal) Linux CI Clang\",\n      \"displayName\": \"(Internal) Linux CI Clang\",\n      \"targets\": [\n        \"install\"\n      ]\n    },\n    {\n      \"name\": \"x-macos-ci\",\n      \"configurePreset\": \"x-macos-ci\",\n      \"description\": \"(Internal) macOS CI\",\n      \"displayName\": \"(Internal) macOS CI\",\n      \"targets\": [\n        \"install\"\n      ]\n    },\n    {\n      \"name\": \"x-ios-ci\",\n      \"configurePreset\": \"x-ios-ci\",\n      \"description\": \"(Internal) iOS CI\",\n      \"displayName\": \"(Internal) iOS CI\",\n      \"targets\": [\n        \"install\"\n      ]\n    },\n    {\n      \"name\": \"x-tvos-ci\",\n      \"configurePreset\": \"x-tvos-ci\",\n      \"description\": \"(Internal) tvOS CI\",\n      \"displayName\": \"(Internal) tvOS CI\",\n      \"targets\": [\n        \"install\"\n      ]\n    },\n    {\n      \"name\": \"x-windows-ci-msvc\",\n      \"configurePreset\": \"x-windows-ci-msvc\",\n      \"description\": \"(Internal) Windows CI MSVC\",\n      \"displayName\": \"(Internal) Windows CI MSVC\",\n      \"targets\": [\n        \"install\"\n      ]\n    },\n    {\n      \"name\": \"x-windows-ci-clang\",\n      \"configurePreset\": \"x-windows-ci-clang\",\n      \"description\": \"(Internal) Windows CI Clang\",\n      \"displayName\": \"(Internal) Windows CI Clang\",\n      \"targets\": [\n        \"install\"\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License\n\nCopyright (c) 2015-2021 Metaforce Contributors\nOriginal Authors: Jack Andersen and Phillip \"Antidote\" Stephens\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "NESEmulator/CMakeLists.txt",
    "content": "file(GLOB MAPPER_SRCS ../extern/fixNES/mapper/*.c)\nadd_library(NESEmulator CNESEmulator.hpp CNESEmulator.cpp malloc.h\n        apu.c ../extern/fixNES/audio_fds.c ../extern/fixNES/audio_mmc5.c ../extern/fixNES/audio_vrc6.c\n        ../extern/fixNES/audio_vrc7.c ../extern/fixNES/audio_n163.c ../extern/fixNES/audio_s5b.c\n        ../extern/fixNES/cpu.c ppu.c ../extern/fixNES/mem.c ../extern/fixNES/input.c ../extern/fixNES/mapper.c\n        ../extern/fixNES/mapperList.c ../extern/fixNES/fm2play.c ../extern/fixNES/vrc_irq.c ${MAPPER_SRCS})\ntarget_include_directories(NESEmulator PRIVATE\n        ${CMAKE_SOURCE_DIR}/DataSpec\n        ${CMAKE_SOURCE_DIR}/Runtime\n        ${CMAKE_SOURCE_DIR}/extern\n        PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})\ntarget_compile_definitions(NESEmulator PRIVATE COL_32BIT=1 COL_TEX_BSWAP=1)\ntarget_link_libraries(NESEmulator RuntimeCommon)\nif (NOT MSVC)\n    target_compile_options(NESEmulator PRIVATE -Wno-implicit-fallthrough -Wno-format -Wno-pointer-compare\n            -Wno-memset-elt-size)\nendif ()\n"
  },
  {
    "path": "NESEmulator/CNESEmulator.cpp",
    "content": "#include \"CNESEmulator.hpp\"\n#include \"CGameState.hpp\"\n#include \"Input/CFinalInput.hpp\"\n#include \"Runtime/Logging.hpp\"\n#include <cstring>\n#include <cstdio>\n#include <cstdlib>\n#include <cstring>\n#include \"malloc.h\"\n#include <cinttypes>\n#include <cctype>\n#include <ctime>\n#include <cmath>\n\nstatic metaforce::MP1::CNESEmulator* EmulatorInst = nullptr;\n\nextern \"C\" {\n\n#include \"fixNES/mapper.h\"\n#include \"fixNES/cpu.h\"\n#include \"fixNES/ppu.h\"\n#include \"fixNES/mem.h\"\n#include \"fixNES/input.h\"\n#include \"fixNES/fm2play.h\"\n#include \"fixNES/apu.h\"\n#include \"fixNES/audio_fds.h\"\n#include \"fixNES/audio_vrc7.h\"\n#include \"fixNES/mapper_h/nsf.h\"\n\n/*\n * Portions Copyright (C) 2017 - 2019 FIX94\n *\n * This software may be modified and distributed under the terms\n * of the MIT license.  See the LICENSE file for details.\n */\n\n#define DEBUG_HZ 0\n#define DEBUG_MAIN_CALLS 0\n#define DEBUG_KEY 0\n#define DEBUG_LOAD_INFO 1\n\n#if 0\n#ifndef _WIN32\nstd::chrono::steady_clock::time_point s_tp = std::chrono::steady_clock::now();\nstatic std::chrono::milliseconds::rep GetTickCount() {\n  return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - s_tp).count();\n}\n#endif\n#endif\n\nconst char *VERSION_STRING = \"fixNES Alpha v1.2.7\";\nstatic char window_title[256];\nstatic char window_title_pause[256];\n\nenum {\n  FTYPE_UNK = 0,\n  FTYPE_NES,\n  FTYPE_NSF,\n  FTYPE_FDS,\n  FTYPE_QD,\n#if ZIPSUPPORT\n  FTYPE_ZIP,\n#endif\n};\n\nstatic int emuFileType = FTYPE_UNK;\nstatic char emuFileName[1024];\nuint8_t *emuNesROM = NULL;\nuint32_t emuNesROMsize = 0;\n#ifndef __LIBRETRO__\nstatic char emuSaveName[1024];\n#endif\nuint8_t *emuPrgRAM = NULL;\nuint32_t emuPrgRAMsize = 0;\n//used externally\n#ifdef COL_32BIT\nuint32_t textureImage[0xF000];\n#define TEXIMAGE_LEN VISIBLE_DOTS*VISIBLE_LINES*4\n#ifdef COL_GL_BSWAP\n#define GL_TEX_FMT GL_UNSIGNED_INT_8_8_8_8_REV\n#else //no REVerse\n#define GL_TEX_FMT GL_UNSIGNED_INT_8_8_8_8\n#endif\n#else //COL_16BIT\nuint16_t textureImage[0xF000];\n#define TEXIMAGE_LEN VISIBLE_DOTS*VISIBLE_LINES*2\n#ifdef COL_GL_BSWAP\n#define GL_TEX_FMT GL_UNSIGNED_SHORT_5_6_5_REV\n#else //no REVerse\n#define GL_TEX_FMT GL_UNSIGNED_SHORT_5_6_5\n#endif\n#endif\nbool nesPause = false;\nbool ppuDebugPauseFrame = false;\nbool doOverscan = false;\nbool nesPAL = false;\nbool nesEmuNSFPlayback = false;\nuint8_t emuInitialNT = NT_UNKNOWN;\n\n// static bool inPause = false;\n// static bool inOverscanToggle = false;\n// static bool inResize = false;\n// static bool inDiskSwitch = false;\n// static bool inReset = false;\n\n#if DEBUG_HZ\nstatic int emuFrameStart = 0;\nstatic int emuTimesCalled = 0;\nstatic int emuTotalElapsed = 0;\n#endif\n#if DEBUG_MAIN_CALLS\nstatic int emuMainFrameStart = 0;\nstatic int emuMainTimesCalled = 0;\nstatic int emuMainTimesSkipped = 0;\nstatic int emuMainTotalElapsed = 0;\n#endif\n\n#define DOTS 341\n\n#define VISIBLE_DOTS 256\n#define VISIBLE_LINES 240\n\nstatic uint32_t linesToDraw = VISIBLE_LINES;\nstatic const uint32_t visibleImg = VISIBLE_DOTS * VISIBLE_LINES * 4;\n// static uint8_t scaleFactor = 2;\nstatic bool emuSaveEnabled = false;\nstatic bool emuFdsHasSideB = false;\n\n// static uint16_t ppuCycleTimer;\n//static uint16_t ppuCycleTimer;\nuint32_t cpuCycleTimer;\nuint32_t vrc7CycleTimer;\n//from input.c\nextern uint8_t inValReads[8];\n//from m30.c\nextern bool m30_flashable;\nextern bool m30_singlescreen;\n//from m32.c\nextern bool m32_singlescreen;\n//from p16c8.c\nextern bool m78_m78a;\n//from ppu.c\nextern bool ppuMapper5;\n\nstatic volatile bool emuRenderFrame = false;\nextern uint8_t audioExpansion;\n\n// used externally\nbool emuSkipVsync = false;\nbool emuSkipFrame = false;\n\n// static uint32_t mCycles = 0;\n\nextern bool fdsSwitch;\n\nuint32_t apuGetMaxBufSize();\nvoid apuResetPos();\nuint8_t* ppuGetVRAM();\n\nint audioUpdate() {\n  if (!EmulatorInst)\n    return 0;\n  return EmulatorInst->audioUpdate();\n}\n}\n\nnamespace metaforce::MP1 {\n\nbool CNESEmulator::EmulatorConstructed = false;\n\n#define NESEMUP_ROM_OFFSET 0xa3f8\n\n#define METROID_PAL false\n#define METROID_MAPPER 1\n#define METROID_SAVE_ENABLED false\n#define METROID_TRAINER false\n#define METROID_PRG_SIZE (8 * 0x4000)\n#define METROID_CHR_SIZE (0 * 0x2000)\n#define METROID_PRG_RAM_SIZE 0x2000\n\nCNESEmulator::CNESEmulator() {\n  if (EmulatorConstructed)\n    spdlog::fatal(\"Attempted constructing more than 1 CNESEmulator\");\n  EmulatorConstructed = true;\n\n  CDvdFile NESEmuFile(\"NESemuP.rel\");\n  if (NESEmuFile) {\n    m_nesEmuPBuf.reset(new u8[0x20000]);\n    m_dvdReq = NESEmuFile.AsyncSeekRead(m_nesEmuPBuf.get(), 0x20000, ESeekOrigin::Begin, NESEMUP_ROM_OFFSET);\n  } else {\n    spdlog::fatal(\"Unable to open NESemuP.rel\");\n  }\n}\n\nvoid CNESEmulator::InitializeEmulator() {\n  nesPause = false;\n  ppuDebugPauseFrame = false;\n\n  puts(VERSION_STRING);\n  strcpy(window_title, VERSION_STRING);\n  memset(textureImage, 0, visibleImg);\n  emuFileType = FTYPE_UNK;\n  memset(emuFileName, 0, 1024);\n  memset(emuSaveName, 0, 1024);\n\n  nesPAL = METROID_PAL;\n  uint8_t mapper = METROID_MAPPER;\n  emuSaveEnabled = METROID_SAVE_ENABLED;\n  bool trainer = METROID_TRAINER;\n  uint32_t prgROMsize = METROID_PRG_SIZE;\n  uint32_t chrROMsize = METROID_CHR_SIZE;\n  emuPrgRAMsize = METROID_PRG_RAM_SIZE;\n  emuPrgRAM = (uint8_t*)malloc(emuPrgRAMsize);\n  uint8_t* prgROM = emuNesROM;\n  if (trainer) {\n    memcpy(emuPrgRAM + 0x1000, prgROM, 0x200);\n    prgROM += 512;\n  }\n  uint8_t* chrROM = NULL;\n  if (chrROMsize) {\n    chrROM = emuNesROM + prgROMsize;\n    if (trainer)\n      chrROM += 512;\n  }\n  apuInitBufs();\n  cpuInit();\n  ppuInit();\n  memInit();\n  apuInit();\n  inputInit();\n  ppuSetNameTblVertical();\n#if DEBUG_LOAD_INFO\n  printf(\"Used Mapper: %i\\n\", mapper);\n  printf(\"PRG: 0x%x bytes PRG RAM: 0x%x bytes CHR: 0x%x bytes\\n\", prgROMsize, emuPrgRAMsize, chrROMsize);\n#endif\n  if (!mapperInit(mapper, prgROM, prgROMsize, emuPrgRAM, emuPrgRAMsize, chrROM, chrROMsize)) {\n    printf(\"Mapper init failed!\\n\");\n    return;\n  }\n#if DEBUG_LOAD_INFO\n  printf(\"Trainer: %i Saving: %i VRAM Mode: Vertical\\n\", trainer, emuSaveEnabled);\n#endif\n  sprintf(window_title, \"%s NES - %s\\n\", nesPAL ? \"PAL\" : \"NTSC\", VERSION_STRING);\n\n  sprintf(window_title_pause, \"%s (Pause)\", window_title);\n  sprintf(window_title_pause, \"%s (Pause)\", window_title);\n#if DEBUG_HZ\n  emuFrameStart = GetTickCount();\n#endif\n#if DEBUG_MAIN_CALLS\n  emuMainFrameStart = GetTickCount();\n#endif\n  cpuCycleTimer = nesPAL ? 16 : 12;\n  vrc7CycleTimer = 432 / cpuCycleTimer;\n  // do one scanline per idle loop\n  // ppuCycleTimer = nesPAL ? 5 : 4;\n  // mainLoopRuns = nesPAL ? DOTS*ppuCycleTimer : DOTS*ppuCycleTimer;\n  // mainLoopPos = mainLoopRuns;\n\n//  CGraphics::CommitResources([this](boo::IGraphicsDataFactory::Context& ctx) {\n//    // Nearest-neighbor FTW!\n//    m_texture = ctx.newDynamicTexture(VISIBLE_DOTS, linesToDraw, boo::TextureFormat::RGBA8,\n//                                      boo::TextureClampMode::ClampToEdgeNearest);\n//    if (ctx.platform() == boo::IGraphicsDataFactory::Platform::OpenGL) {\n//      Vert verts[4] = {\n//          {{-1.f, -1.f, 0.f}, {0.f, 1.f}},\n//          {{-1.f, 1.f, 0.f}, {0.f, 0.f}},\n//          {{1.f, -1.f, 0.f}, {1.f, 1.f}},\n//          {{1.f, 1.f, 0.f}, {1.f, 0.f}},\n//      };\n//      m_vbo = ctx.newStaticBuffer(boo::BufferUse::Vertex, verts, sizeof(Vert), 4);\n//    } else {\n//      Vert verts[4] = {\n//          {{-1.f, 1.f, 0.f}, {0.f, 1.f}},\n//          {{-1.f, -1.f, 0.f}, {0.f, 0.f}},\n//          {{1.f, 1.f, 0.f}, {1.f, 1.f}},\n//          {{1.f, -1.f, 0.f}, {1.f, 0.f}},\n//      };\n//      m_vbo = ctx.newStaticBuffer(boo::BufferUse::Vertex, verts, sizeof(Vert), 4);\n//    }\n//    m_uniBuf = ctx.newDynamicBuffer(boo::BufferUse::Uniform, sizeof(Uniform), 1);\n//    m_shadBind = CNESShader::BuildShaderDataBinding(ctx, m_vbo, m_uniBuf, m_texture);\n//    return true;\n//  } BooTrace);\n\n  // double useFreq = 223740;\n  double useFreq = apuGetFrequency();\n  //m_booVoice = CAudioSys::GetVoiceEngine()->allocateNewStereoVoice(useFreq, this);\n  //m_booVoice->start();\n  uint32_t apuBufSz = apuGetMaxBufSize();\n  m_audioBufBlock.reset(new u8[apuBufSz * NUM_AUDIO_BUFFERS]);\n  memset(m_audioBufBlock.get(), 0, apuBufSz * NUM_AUDIO_BUFFERS);\n  for (int i = 0; i < NUM_AUDIO_BUFFERS; ++i)\n    m_audioBufs[i] = m_audioBufBlock.get() + apuBufSz * i;\n\n  EmulatorInst = this;\n}\n\nvoid CNESEmulator::DeinitializeEmulator() {\n  // printf(\"\\n\");\n  emuRenderFrame = false;\n  //m_booVoice->stop();\n  //m_booVoice.reset();\n  apuDeinitBufs();\n  if (emuNesROM != NULL) {\n    if (!nesEmuNSFPlayback && (audioExpansion & EXP_FDS)) {\n      FILE* save = fopen(emuSaveName, \"wb\");\n      if (save) {\n        if (emuFdsHasSideB)\n          fwrite(emuNesROM, 1, 0x20000, save);\n        else\n          fwrite(emuNesROM, 1, 0x10000, save);\n        fclose(save);\n      }\n    }\n  }\n  if (emuPrgRAM != NULL) {\n    if (emuSaveEnabled) {\n      FILE* save = fopen(emuSaveName, \"wb\");\n      if (save) {\n        fwrite(emuPrgRAM, 1, emuPrgRAMsize, save);\n        fclose(save);\n      }\n    }\n    free(emuPrgRAM);\n  }\n  emuPrgRAM = NULL;\n  // printf(\"Bye!\\n\");\n\n  EmulatorInst = nullptr;\n}\n\nCNESEmulator::~CNESEmulator() {\n  if (m_dvdReq)\n    m_dvdReq->PostCancelRequest();\n  if (EmulatorInst)\n    DeinitializeEmulator();\n  if (emuNesROM) {\n    free(emuNesROM);\n    emuNesROM = nullptr;\n  }\n  EmulatorConstructed = false;\n}\n\nint CNESEmulator::audioUpdate() {\n  int origProcBufs = m_procBufs;\n\n//  uint8_t* data = apuGetBuf();\n//  if (data != NULL && m_procBufs) {\n//    uint32_t apuBufSz = apuGetMaxBufSize();\n//    uint32_t remBytes = apuGetBufSize();\n//    while (remBytes != 0) {\n//      size_t thisBytes = std::min(remBytes, apuBufSz - m_posInHeadBuf);\n//      memmove(m_audioBufs[m_headBuf] + m_posInHeadBuf, data, thisBytes);\n//      data += thisBytes;\n//      m_posInHeadBuf += thisBytes;\n//      if (m_posInHeadBuf == apuBufSz) {\n//        m_posInHeadBuf = 0;\n//        --m_procBufs;\n//        ++m_headBuf;\n//        if (m_headBuf == NUM_AUDIO_BUFFERS)\n//          m_headBuf = 0;\n//        // printf(\"PUSH\\n\");\n//      }\n//      remBytes -= thisBytes;\n//    }\n//  }\n//\n//  // if (!origProcBufs)\n//  // printf(\"OVERRUN\\n\");\n//\n  return origProcBufs;\n}\n\nstatic constexpr uint32_t AudioFrameSz = 2 * sizeof(int16_t);\n\n//size_t CNESEmulator::supplyAudio(boo::IAudioVoice& voice, size_t frames, int16_t* data) {\n//  uint32_t remFrames = uint32_t(frames);\n//  while (remFrames) {\n//    if (m_posInTailBuf == apuGetMaxBufSize()) {\n//      ++m_tailBuf;\n//      if (m_tailBuf == NUM_AUDIO_BUFFERS)\n//        m_tailBuf = 0;\n//      m_posInTailBuf = 0;\n//      ++m_procBufs;\n//      // printf(\"POP\\n\");\n//    }\n//\n//    if (m_procBufs == NUM_AUDIO_BUFFERS) {\n//      memset(data, 0, remFrames * AudioFrameSz);\n//      // printf(\"UNDERRUN\\n\");\n//      return frames;\n//    }\n//\n//    size_t copySz = std::min(apuGetMaxBufSize() - m_posInTailBuf, remFrames * AudioFrameSz);\n//    memmove(data, m_audioBufs[m_tailBuf] + m_posInTailBuf, copySz);\n//    data += copySz / sizeof(int16_t);\n//    m_posInTailBuf += copySz;\n//    remFrames -= copySz / AudioFrameSz;\n//  }\n//  return frames;\n//}\n\nvoid CNESEmulator::NesEmuMainLoop(bool forceDraw) {\n  // int start = GetTickCount();\n  int loopCount = 0;\n  do {\n    if (emuRenderFrame || nesPause) {\n#if DEBUG_MAIN_CALLS\n      emuMainTimesSkipped++;\n#endif\n      // printf(\"LC RENDER: %d\\n\", loopCount);\n      // TODO TODO\n//      m_texture->load(textureImage, visibleImg);\n      emuRenderFrame = false;\n      break;\n    }\n    ++loopCount;\n\n    // main CPU clock\n    if (!cpuCycle())\n      exit(EXIT_SUCCESS);\n    // run graphics\n    ppuCycle();\n    // run audio\n    apuCycle();\n    // mapper related irqs\n    mapperCycle();\n    // mCycles++;\n    if (ppuDrawDone()) {\n      // printf(\"%i\\n\",mCycles);\n      // mCycles = 0;\n#ifndef __LIBRETRO__\n      emuRenderFrame = true;\n#if 0\n            if(fm2playRunning())\n                fm2playUpdate();\n#endif\n#if DEBUG_HZ\n      emuTimesCalled++;\n      auto end = GetTickCount();\n      emuTotalElapsed += end - emuFrameStart;\n      if (emuTotalElapsed >= 1000) {\n        printf(\"\\r%iHz   \", emuTimesCalled);\n        emuTimesCalled = 0;\n        emuTotalElapsed = 0;\n      }\n      emuFrameStart = end;\n#endif\n      // update audio before drawing\n      if (!apuUpdate()) {\n        apuResetPos();\n        break;\n      }\n      // glutPostRedisplay();\n#if 0\n            if(ppuDebugPauseFrame)\n            {\n                ppuDebugPauseFrame = false;\n                nesPause = true;\n            }\n#endif\n#endif\n      if (nesEmuNSFPlayback)\n        nsfVsync();\n\n      // keep processing frames if audio buffers are underrunning\n      if (emuSkipFrame)\n        emuRenderFrame = false;\n\n      continue;\n    }\n  } while (true);\n\n#if 0\n    int end = GetTickCount();\n    printf(\"%dms %d %d\\n\", end - start, loopCount, m_procBufs);\n#endif\n\n#if DEBUG_MAIN_CALLS\n  emuMainTimesCalled++;\n  int end = GetTickCount();\n  // printf(\"%dms\\n\", end - start);\n  emuMainTotalElapsed += end - emuMainFrameStart;\n  if (emuMainTotalElapsed >= 1000) {\n    printf(\"\\r%i calls, %i skips   \", emuMainTimesCalled, emuMainTimesSkipped);\n    fflush(stdout);\n    emuMainTimesCalled = 0;\n    emuMainTimesSkipped = 0;\n    emuMainTotalElapsed = 0;\n  }\n  emuMainFrameStart = end;\n#endif\n}\n\n#if 0\nstatic void nesEmuFdsSetup(uint8_t *src, uint8_t *dst)\n{\n    memcpy(dst, src, 0x38);\n    memcpy(dst+0x3A, src+0x38, 2);\n    uint16_t cDiskPos = 0x3E;\n    uint16_t cROMPos = 0x3A;\n    do\n    {\n        if(src[cROMPos] != 0x03)\n            break;\n        memcpy(dst+cDiskPos, src+cROMPos, 0x10);\n        uint16_t copySize = (*(uint16_t*)(src+cROMPos+0xD))+1;\n        cDiskPos+=0x12;\n        cROMPos+=0x10;\n        memcpy(dst+cDiskPos, src+cROMPos, copySize);\n        cDiskPos+=copySize+2;\n        cROMPos+=copySize;\n    } while(cROMPos < 0xFFDC && cDiskPos < 0xFFFF);\n    printf(\"%04x -> %04x\\n\", cROMPos, cDiskPos);\n}\n#endif\n\nstruct BitstreamState {\n  u8* rPos;\n  int position = 0;\n  int tmpBuf = 0;\n  int decBit = 0;\n  BitstreamState(u8* pos) : rPos(pos) {}\n  void resetDecBit() { decBit = 0; }\n  void runDecBit() {\n    if (position == 0) {\n      position = 8;\n      tmpBuf = *rPos++;\n    }\n    decBit <<= 1;\n    if (tmpBuf & 0x80)\n      decBit |= 1;\n    tmpBuf <<= 1;\n    position--;\n  }\n};\n\n// Based on https://gist.github.com/FIX94/7593640c5cee6c37e3b23e7fcf8fe5b7\nvoid CNESEmulator::DecryptMetroid(u8* dataIn, u8* dataOut, u32 decLen, u8 decByte, u32 xorLen, u32 xorVal) {\n  u32 i, j;\n  // simple add obfuscation\n  for (i = 0; i < 0x100; i++) {\n    dataIn[i] += decByte;\n    decByte = dataIn[i];\n  }\n  // flip the first 0x100 bytes around\n  for (i = 0; i < 128; ++i)\n    std::swap(dataIn[255 - i], dataIn[i]);\n  // set up buffer pointers\n  BitstreamState bState(dataIn + 0x100);\n  // unscramble buffer\n  for (i = 0; i < decLen; i++) {\n    bState.resetDecBit();\n    bState.runDecBit();\n    if (bState.decBit) {\n      bState.resetDecBit();\n      for (j = 0; j < 8; j++)\n        bState.runDecBit();\n      dataOut[i] = dataIn[bState.decBit + 0x49];\n    } else {\n      bState.resetDecBit();\n      bState.runDecBit();\n      if (bState.decBit) {\n        bState.resetDecBit();\n        for (j = 0; j < 6; j++)\n          bState.runDecBit();\n        dataOut[i] = dataIn[bState.decBit + 9];\n      } else {\n        bState.resetDecBit();\n        bState.runDecBit();\n        if (bState.decBit) {\n          bState.resetDecBit();\n          for (j = 0; j < 3; j++)\n            bState.runDecBit();\n          dataOut[i] = dataIn[bState.decBit + 1];\n        } else\n          dataOut[i] = dataIn[bState.decBit];\n      }\n    }\n  }\n  // do checksum fixups\n  unsigned int xorTmpVal = 0;\n  for (i = 0; i < xorLen; i++) {\n    xorTmpVal ^= dataOut[i];\n    for (j = 0; j < 8; j++) {\n      if (xorTmpVal & 1) {\n        xorTmpVal >>= 1;\n        xorTmpVal ^= xorVal;\n      } else\n        xorTmpVal >>= 1;\n    }\n  }\n  // write in calculated checksum\n  dataOut[xorLen - 1] = u8((xorTmpVal >> 8) & 0xFF);\n  dataOut[xorLen - 2] = u8(xorTmpVal & 0xFF);\n}\n\nvoid CNESEmulator::ProcessUserInput(const CFinalInput& input, int) {\n  if (input.ControllerIdx() != 0)\n    return;\n\n  if (GetPasswordEntryState() != EPasswordEntryState::NotPasswordScreen) {\n    // Don't swap A/B\n    inValReads[BUTTON_A] = input.DA() || input.DSpecialKey(ESpecialKey::Enter) ||\n                           input.DMouseButton(EMouseButton::Primary);\n    inValReads[BUTTON_B] = input.DB() || input.DSpecialKey(ESpecialKey::Esc);\n  } else {\n    // Prime controls (B jumps, A shoots)\n    inValReads[BUTTON_B] = input.DA() || input.DY() || input.DMouseButton(EMouseButton::Primary);\n    inValReads[BUTTON_A] = input.DB() || input.DX() || input.DKey(' ');\n  }\n\n  inValReads[BUTTON_UP] = input.DDPUp() || input.DLAUp();\n  inValReads[BUTTON_DOWN] = input.DDPDown() || input.DLADown();\n  inValReads[BUTTON_LEFT] = input.DDPLeft() || input.DLALeft();\n  inValReads[BUTTON_RIGHT] = input.DDPRight() || input.DLARight();\n  inValReads[BUTTON_SELECT] = input.DZ() || input.DSpecialKey(ESpecialKey::Tab);\n  inValReads[BUTTON_START] = input.DStart() || input.DSpecialKey(ESpecialKey::Esc);\n}\n\nbool CNESEmulator::CheckForGameOver(const u8* vram, u8* passwordOut) {\n  // \"PASS WORD\"\n  if (memcmp(vram + 0x14B, \"\\x19\\xa\\x1c\\x1c\\xff\\x20\\x18\\x1b\\xd\", 9))\n    return false;\n\n  int chOff = 0;\n  int encOff = 0;\n  u8 pwOut[18];\n  for (int i = 0; i < 24; ++i) {\n    u8 chName = vram[0x1A9 + chOff];\n    ++chOff;\n    if (chOff == 0x6 || chOff == 0x46)\n      ++chOff; // mid-line space\n    else if (chOff == 0xd)\n      chOff = 64; // 2nd line\n\n    if (chName > 0x3f)\n      return false;\n\n    switch (i & 0x3) {\n    case 0:\n      pwOut[encOff] = chName;\n      break;\n    case 1:\n      pwOut[encOff] |= chName << 6;\n      ++encOff;\n      pwOut[encOff] = chName >> 2;\n      break;\n    case 2:\n      pwOut[encOff] |= chName << 4;\n      ++encOff;\n      pwOut[encOff] = chName >> 4;\n      break;\n    case 3:\n      pwOut[encOff] |= chName << 2;\n      ++encOff;\n      break;\n    default:\n      break;\n    }\n  }\n\n  if (passwordOut)\n    memmove(passwordOut, pwOut, 18);\n  return true;\n}\n\nCNESEmulator::EPasswordEntryState CNESEmulator::CheckForPasswordEntryScreen(const u8* vram) {\n  // \"PASS WORD PLEASE\"\n  if (memcmp(vram + 0x88, \"\\x19\\xa\\x1c\\x1c\\xff\\x20\\x18\\x1b\\xd\\xff\\x19\\x15\\xe\\xa\\x1c\\xe\", 16))\n    return EPasswordEntryState::NotPasswordScreen;\n\n  for (int i = 0; i < 13; ++i)\n    if (vram[0x109 + i] < 0x40 || vram[0x149 + i] < 0x40)\n      return EPasswordEntryState::Entered;\n\n  return EPasswordEntryState::NotEntered;\n}\n\nbool CNESEmulator::SetPasswordIntoEntryScreen(u8* vram, u8* wram, const u8* password) {\n  if (CheckForPasswordEntryScreen(vram) != EPasswordEntryState::NotEntered)\n    return false;\n\n  int i;\n  for (i = 0; i < 18; ++i)\n    if (password[i])\n      break;\n  if (i == 18)\n    return false;\n\n  int encOff = 0;\n  int chOff = 0;\n  u32 lastWord = 0;\n  for (i = 0; i < 24; ++i) {\n    switch (i & 0x3) {\n    case 0:\n      lastWord = password[encOff];\n      ++encOff;\n      break;\n    case 1:\n      lastWord = (lastWord >> 6) | (u32(password[encOff]) << 2);\n      ++encOff;\n      break;\n    case 2:\n      lastWord = (lastWord >> 6) | (u32(password[encOff]) << 4);\n      ++encOff;\n      break;\n    case 3:\n      lastWord = (lastWord >> 6);\n      break;\n    default:\n      break;\n    }\n\n    u8 chName = u8(lastWord & 0x3f);\n    wram[0x99a + i] = chName;\n    vram[0x109 + chOff] = chName;\n    ++chOff;\n    if (chOff == 0x6 || chOff == 0x46)\n      ++chOff; // mid-line space\n    else if (chOff == 0xd)\n      chOff = 64; // 2nd line\n  }\n\n  return true;\n}\n\nvoid CNESEmulator::Update() {\n  if (!EmulatorInst) {\n    if (m_dvdReq && m_dvdReq->IsComplete()) {\n      m_dvdReq.reset();\n      emuNesROMsize = 0x20000;\n      emuNesROM = (uint8_t*)malloc(emuNesROMsize);\n      DecryptMetroid(m_nesEmuPBuf.get(), emuNesROM);\n      m_nesEmuPBuf.reset();\n      InitializeEmulator();\n    }\n  } else {\n    if (nesPause) {\n      DeinitializeEmulator();\n      InitializeEmulator();\n      return;\n    }\n\n    bool gameOver = CheckForGameOver(ppuGetVRAM(), x21_passwordFromNES);\n    x34_passwordEntryState = CheckForPasswordEntryScreen(ppuGetVRAM());\n    if (x34_passwordEntryState == EPasswordEntryState::NotEntered && x38_passwordPending) {\n      SetPasswordIntoEntryScreen(ppuGetVRAM(), emuPrgRAM, x39_passwordToNES);\n      x38_passwordPending = false;\n    }\n    if (gameOver && !x20_gameOver)\n      for (int i = 0; i < 3; ++i) // Three draw loops to ensure password display\n        NesEmuMainLoop(true);\n    else\n      NesEmuMainLoop();\n    x20_gameOver = gameOver;\n  }\n}\n\nstatic const float NESAspect = VISIBLE_DOTS / float(VISIBLE_LINES);\n\nvoid CNESEmulator::Draw(const zeus::CColor& mulColor, bool filtering) {\n  if (!EmulatorInst)\n    return;\n\n  float widthFac = NESAspect / CGraphics::GetViewportAspect();\n\n  Uniform uniform = {zeus::CMatrix4f{}, mulColor};\n  uniform.m_matrix[0][0] = widthFac;\n//  m_uniBuf->load(&uniform, sizeof(Uniform));\n//\n//  CGraphics::SetShaderDataBinding(m_shadBind);\n//  CGraphics::DrawArray(0, 4);\n}\n\nvoid CNESEmulator::LoadPassword(const u8* state) {\n  memmove(x39_passwordToNES, state, 18);\n  x38_passwordPending = true;\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "NESEmulator/CNESEmulator.hpp",
    "content": "#pragma once\n\n#include \"RetroTypes.hpp\"\n#include \"zeus/CColor.hpp\"\n//#include \"boo/graphicsdev/IGraphicsDataFactory.hpp\"\n#include \"zeus/CMatrix4f.hpp\"\n#include \"Runtime/Graphics/CGraphics.hpp\"\n\nnamespace metaforce {\nstruct CFinalInput;\nclass IDvdRequest;\n\nnamespace MP1 {\n\n#define NUM_AUDIO_BUFFERS 4\n\nclass CNESEmulator final {\npublic:\n  enum class EPasswordEntryState { NotPasswordScreen, NotEntered, Entered };\n\nprivate:\n  static bool EmulatorConstructed;\n\n  std::unique_ptr<u8[]> m_nesEmuPBuf;\n  std::shared_ptr<IDvdRequest> m_dvdReq;\n\n  struct Vert {\n    zeus::CVector3f m_pos;\n    zeus::CVector2f m_uv;\n  };\n\n  struct Uniform {\n    zeus::CMatrix4f m_matrix;\n    zeus::CColor m_color;\n  };\n\n  TGXTexObj m_texture;\n//  boo::ObjToken<boo::IGraphicsBufferD> m_uniBuf;\n//  boo::ObjToken<boo::IGraphicsBufferS> m_vbo;\n//  boo::ObjToken<boo::IShaderDataBinding> m_shadBind;\n\n  std::unique_ptr<u8[]> m_audioBufBlock;\n  u8* m_audioBufs[NUM_AUDIO_BUFFERS];\n  uint32_t m_headBuf = 0;\n  uint32_t m_tailBuf = 0;\n  uint32_t m_procBufs = NUM_AUDIO_BUFFERS;\n  uint32_t m_posInHeadBuf = 0;\n  uint32_t m_posInTailBuf = 0;\n  //boo::ObjToken<boo::IAudioVoice> m_booVoice;\n\n  // void* x4_loadBuf;\n  // void* x8_rom;\n  // void* xc_state;\n  // OSModuleInfo* x10_module = x4_loadBuf;\n  // void* x14_bss;\n  // void* x18_prgram;\n  // void* x1c_wram;\n  bool x20_gameOver = false;\n  u8 x21_passwordFromNES[18];\n  EPasswordEntryState x34_passwordEntryState = EPasswordEntryState::NotPasswordScreen;\n  bool x38_passwordPending = false;\n  u8 x39_passwordToNES[18];\n  static void DecryptMetroid(u8* dataIn, u8* dataOut, u32 decLen = 0x20000, u8 decByte = 0xe9, u32 xorLen = 0x1FFFC,\n                             u32 xorVal = 0xA663);\n  void InitializeEmulator();\n  void DeinitializeEmulator();\n  void NesEmuMainLoop(bool forceDraw = false);\n  static bool CheckForGameOver(const u8* vram, u8* passwordOut = nullptr);\n  static EPasswordEntryState CheckForPasswordEntryScreen(const uint8_t* vram);\n  static bool SetPasswordIntoEntryScreen(u8* vram, u8* wram, const u8* password);\n\npublic:\n  CNESEmulator();\n  ~CNESEmulator();\n  void ProcessUserInput(const CFinalInput& input, int);\n  void Update();\n  void Draw(const zeus::CColor& mulColor, bool filtering);\n  void LoadPassword(const u8* state);\n  const u8* GetPassword() const { return x21_passwordFromNES; }\n  bool IsGameOver() const { return x20_gameOver; }\n  EPasswordEntryState GetPasswordEntryState() const { return x34_passwordEntryState; }\n\n  int audioUpdate();\n  //void preSupplyAudio(boo::IAudioVoice& voice, double dt) {}\n  //size_t supplyAudio(boo::IAudioVoice& voice, size_t frames, int16_t* data);\n};\n\n} // namespace MP1\n} // namespace metaforce\n"
  },
  {
    "path": "NESEmulator/apu.c",
    "content": "#include \"fixNES/apu.c\"\n\nuint32_t apuGetMaxBufSize()\n{\n    return apu.BufSizeBytes;\n}\n\nvoid apuResetPos()\n{\n    apu.curBufPos = 0;\n}\n"
  },
  {
    "path": "NESEmulator/malloc.h",
    "content": "#ifndef URDE_NESEMULATOR_MALLOC_H\n#define URDE_NESEMULATOR_MALLOC_H\n\n#ifdef __APPLE__\n#include <stdlib.h>\n#elif _WIN32\n#include <../ucrt/malloc.h>\n#else\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wpragmas\"\n#pragma GCC diagnostic ignored \"-Winclude-next-absolute-path\"\n#include_next <malloc.h>\n#pragma GCC diagnostic pop\n#endif\n\n#endif // URDE_NESEMULATOR_MALLOC_H\n"
  },
  {
    "path": "NESEmulator/ppu.c",
    "content": "#include \"fixNES/ppu.c\"\n\nuint8_t* ppuGetVRAM()\n{\n    return ppu.VRAM;\n}\n"
  },
  {
    "path": "README.md",
    "content": "## Metaforce [![Build Status]][actions] [![Discord Badge]][discord]\n\n[Build Status]: https://github.com/AxioDL/metaforce/actions/workflows/build.yml/badge.svg\n[actions]: https://github.com/AxioDL/metaforce/actions\n[Discord Badge]: https://dcbadge.vercel.app/api/server/AMBVFuf?style=flat\n[discord]: https://discord.gg/AMBVFuf\n\nA reverse-engineered, native reimplementation of Metroid Prime.\n\nThis project is currently in **alpha** state.\nBuilds are currently unavailable while the project undergoes large changes.\n\nSeparately, a [matching decompilation](https://github.com/PrimeDecomp/prime) of Metroid Prime is currently underway. Contributions are welcome.\nProgress on the decompilation benefits Metaforce with bug fixes and new implementations.\n\n![Metaforce screenshot](assets/metaforce-screen1.png)\n\n### Platform Support\n* Windows 10+ (64-bit, D3D12 / Vulkan / OpenGL)\n* macOS 10.15+ (Metal)\n* Linux (Vulkan / OpenGL)\n    * Follow [this guide](https://github.com/lutris/docs/blob/master/InstallingDrivers.md) to set up Vulkan & appropriate drivers for your distro.\n\n### Usage\n\nWindows:\n- Open `metaforce.exe`\n\nmacOS:\n- Open `Metaforce.app`\n\nLinux:\n- Ensure AppImage is marked as executable: `chmod +x Metaforce-*.AppImage`\n- Open `Metaforce-*.AppImage`\n\n#### CLI options (non-exhaustive)\n\n* `-l`: Enable console logging\n* `--warp [worldid] [areaid]`: Warp to a specific world/area. Example: `--warp 2 2`\n* `+developer=1`: Enable developer UI\n\n### Build Prerequisites:\n* [CMake 3.25+](https://cmake.org)\n    * Windows: Install `CMake Tools` in Visual Studio\n    * macOS: `brew install cmake`\n* [Python 3+](https://python.org)\n    * Windows: [Microsoft Store](https://go.microsoft.com/fwlink?linkID=2082640)\n        * Verify it's added to `%PATH%` by typing `python` in `cmd`.\n    * macOS: `brew install python@3`\n* **[Windows]** [Visual Studio 2019 Community](https://www.visualstudio.com/en-us/products/visual-studio-community-vs.aspx)\n    * Select `C++ Development` and verify the following packages are included:\n        * `Windows 10 SDK`\n        * `CMake Tools`\n        * `C++ Clang Compiler`\n        * `C++ Clang-cl`\n* **[macOS]** [Xcode 11.5+](https://developer.apple.com/xcode/download/)\n* **[Linux]** Actively tested on Ubuntu 20.04, Arch Linux & derivatives.\n    * Ubuntu 20.04+ packages\n      ```\n      build-essential curl git ninja-build clang lld zlib1g-dev libcurl4-openssl-dev \\\n      libglu1-mesa-dev libdbus-1-dev libvulkan-dev libxi-dev libxrandr-dev libasound2-dev libpulse-dev \\\n      libudev-dev libpng-dev libncurses5-dev cmake libx11-xcb-dev python3 python-is-python3 \\\n      libclang-dev libfreetype-dev libxinerama-dev libxcursor-dev python3-markupsafe libgtk-3-dev\n      ```\n     * Arch Linux packages\n       ```\n       base-devel cmake ninja llvm vulkan-headers python python-markupsafe clang lld alsa-lib libpulse libxrandr freetype2\n       ```\n     * Fedora packages\n       ```\n       cmake vulkan-headers ninja-build clang-devel llvm-devel libpng-devel\n       ```\n         * It's also important that you install the developer tools and libraries\n           ```\n           sudo dnf groupinstall \"Development Tools\" \"Development Libraries\"\n           ```\n### Prep Directions\n\n```sh\ngit clone --recursive https://github.com/AxioDL/metaforce.git\ncd metaforce\n```\n\n### Update Directions\n\n```sh\ncd metaforce\ngit pull\ngit submodule update --recursive\n```\n\n### Build Directions\n\nFor Windows, it's recommended to use Visual Studio. See below.\n\n#### ninja (Windows/macOS/Linux)\n\nBuilds using `RelWithDebInfo` by default.\n\n```sh\ncmake -B out -G Ninja # add extra options here\ncmake --build out --target metaforce hecl visigen\n```\n\n#### CMake configure options\n- Build in debug mode (slower runtime speed, better backtraces): `-DCMAKE_BUILD_TYPE=Debug`\n- Use clang+lld (faster linking): `-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++`\n- Optimize for current CPU (resulting binaries are not portable): `-DMETAFORCE_VECTOR_ISA=native`\n\n#### CLion (Windows/macOS/Linux)\n*(main development / debugging IDE)*\n\nOpen the repository's `CMakeLists.txt`.\n\nOptionally configure CMake options via `File` > `Settings` > `Build, Execution, Deployment` > `CMake`.\n\n#### Qt Creator (Windows/macOS/Linux)\n\nOpen the repository's `CMakeLists.txt` via File > Open File or Project.\n\nConfigure the desired CMake targets to build in the *Projects* area of the IDE.\n\n#### Visual Studio (Windows)\n\nVerify all required VS packages are installed from the above **Build Prerequisites** section.\n\nOpen the `metaforce` directory in Visual Studio (imports CMake configuration).\n\nMSVC and clang-cl configurations should import automatically.\n\n#### Xcode (macOS)\n\n```sh\ncmake -G Xcode ../metaforce\n```\n\nThen open `metaforce.xcodeproj`\n"
  },
  {
    "path": "Runtime/Audio/CAudioGroupSet.cpp",
    "content": "#include \"Runtime/Audio/CAudioGroupSet.hpp\"\n\n#include <cstring>\n\nnamespace metaforce {\n/*\namuse::AudioGroupData CAudioGroupSet::LoadData() {\n  const auto readU32 = [](const u8* ptr) {\n    uint32_t value;\n    std::memcpy(&value, ptr, sizeof(value));\n    return SBig(value);\n  };\n\n  CMemoryInStream r(m_buffer.get(), INT32_MAX, CMemoryInStream::EOwnerShip::NotOwned);\n  x10_baseName = r.Get<std::string>();\n  x20_name = r.Get<std::string>();\n\n  u8* buf = m_buffer.get() + r.GetReadPosition();\n  const uint32_t poolLen = readU32(buf);\n  unsigned char* pool = buf + 4;\n  buf += poolLen + 4;\n  const uint32_t projLen = readU32(buf);\n  unsigned char* proj = buf + 4;\n  buf += projLen + 4;\n  const uint32_t sampLen = readU32(buf);\n  unsigned char* samp = buf + 4;\n  buf += sampLen + 4;\n  const uint32_t sdirLen = readU32(buf);\n  unsigned char* sdir = buf + 4;\n\n  return {proj, projLen, pool, poolLen, sdir, sdirLen, samp, sampLen, amuse::GCNDataTag{}};\n}\n*/\nCAudioGroupSet::CAudioGroupSet(std::unique_ptr<u8[]>&& in) : m_buffer(std::move(in)) {}\n\nCFactoryFnReturn FAudioGroupSetDataFactory(const metaforce::SObjectTag& tag, std::unique_ptr<u8[]>&& in, u32 len,\n                                           const metaforce::CVParamTransfer& vparms, CObjectReference* selfRef) {\n  return TToken<CAudioGroupSet>::GetIObjObjectFor(std::make_unique<CAudioGroupSet>(std::move(in)));\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Audio/CAudioGroupSet.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <string>\n\n#include \"Runtime/CFactoryMgr.hpp\"\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/IObj.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n\n//#include <amuse/AudioGroupData.hpp>\n\nnamespace metaforce {\n\nclass CAudioGroupSet {\n  std::unique_ptr<u8[]> m_buffer;\n  std::string x10_baseName;\n  std::string x20_name;\n//  amuse::AudioGroupData m_data;\n//  amuse::AudioGroupData LoadData();\n\npublic:\n  explicit CAudioGroupSet(std::unique_ptr<u8[]>&& in);\n  //const amuse::AudioGroupData& GetAudioGroupData() const { return m_data; }\n  std::string_view GetName() const { return x20_name; }\n};\n\nCFactoryFnReturn FAudioGroupSetDataFactory(const metaforce::SObjectTag& tag, std::unique_ptr<u8[]>&& in, u32 len,\n                                           const metaforce::CVParamTransfer& vparms, CObjectReference* selfRef);\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Audio/CAudioSys.cpp",
    "content": "#include \"Runtime/Audio/CAudioSys.hpp\"\n\n#include <string>\n#include <unordered_map>\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/Audio/CAudioGroupSet.hpp\"\n\nnamespace metaforce {\nnamespace {\nstd::unordered_map<std::string, TLockedToken<CAudioGroupSet>> mpGroupSetDB;\nstd::unordered_map<CAssetId, std::string> mpGroupSetResNameDB;\nconstexpr std::string_view mpDefaultInvalidString = \"NULL\";\n\nfloat s_MasterVol = 1.f;\nfloat s_SfxVol = 1.f;\n\ns16 s_VolumeScale = 0x7f;\ns16 s_DefaultVolumeScale = 0x7f;\n} // Anonymous namespace\n\nCAudioSys* CAudioSys::g_SharedSys = nullptr;\n\nTLockedToken<CAudioGroupSet> CAudioSys::FindGroupSet(std::string_view name) {\n  // TODO: Heterogeneous lookup when C++20 available\n  auto search = mpGroupSetDB.find(name.data());\n  if (search == mpGroupSetDB.cend())\n    return {};\n  return search->second;\n}\n\nstd::string_view CAudioSys::SysGetGroupSetName(CAssetId id) {\n  auto search = mpGroupSetResNameDB.find(id);\n  if (search == mpGroupSetResNameDB.cend())\n    return mpDefaultInvalidString;\n  return search->second;\n}\n\nbool CAudioSys::SysLoadGroupSet(CSimplePool* pool, CAssetId id) {\n  if (!FindGroupSet(SysGetGroupSetName(id))) {\n    TLockedToken<CAudioGroupSet> set = pool->GetObj(SObjectTag{FOURCC('AGSC'), id});\n    mpGroupSetDB.emplace(set->GetName(), set);\n    mpGroupSetResNameDB.emplace(id, set->GetName());\n    return false;\n  } else {\n    return true;\n  }\n}\n\nbool CAudioSys::SysLoadGroupSet(const TLockedToken<CAudioGroupSet>& set, std::string_view name, CAssetId id) {\n  if (!FindGroupSet(name)) {\n    mpGroupSetDB.emplace(set->GetName(), set);\n    mpGroupSetResNameDB.emplace(id, set->GetName());\n    return false;\n  } else {\n    return true;\n  }\n}\n\nvoid CAudioSys::SysUnloadAudioGroupSet(std::string_view name) {\n  auto set = FindGroupSet(name);\n  if (!set)\n    return;\n\n  mpGroupSetDB.erase(name.data());\n  mpGroupSetResNameDB.erase(set.GetObjectTag()->id);\n}\n\nbool CAudioSys::SysIsGroupSetLoaded(std::string_view name) { return FindGroupSet(name).operator bool(); }\n\nvoid CAudioSys::SysAddGroupIntoAmuse(std::string_view name) {\n}\n\nvoid CAudioSys::SysRemoveGroupFromAmuse(std::string_view name) {\n}\n\nvoid CAudioSys::_UpdateVolume() {  }\n\nvoid CAudioSys::SysSetVolume(u8 volume) {\n  s_MasterVol = volume / 127.f;\n  _UpdateVolume();\n}\n\nvoid CAudioSys::SysSetSfxVolume(u8 volume, u16 time, bool music, bool fx) {\n  s_SfxVol = volume / 127.f;\n  _UpdateVolume();\n}\n\ns16 CAudioSys::GetDefaultVolumeScale() { return s_DefaultVolumeScale; }\n\nvoid CAudioSys::SetDefaultVolumeScale(s16 scale) { s_DefaultVolumeScale = scale; }\n\nvoid CAudioSys::SetVolumeScale(s16 scale) { s_VolumeScale = scale; }\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Audio/CAudioSys.hpp",
    "content": "#pragma once\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n\n//#include <amuse/amuse.hpp>\n//#include <boo/audiodev/IAudioVoiceEngine.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CAudioGroupSet;\nclass CSimplePool;\n\nCFactoryFnReturn FAudioTranslationTableFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& vparms,\n                                               CObjectReference* selfRef);\n\nclass CAudioSys {\npublic:\n  enum class ESurroundModes { Mono, Stereo, Surround };\n\nprivate:\n  static CAudioSys* g_SharedSys;\n  static void _UpdateVolume();\n\npublic:\n  struct C3DEmitterParmData {\n    zeus::CVector3f x0_pos;\n    zeus::CVector3f xc_dir;\n    float x18_maxDist;\n    float x1c_distComp;\n    u32 x20_flags;\n    u16 x24_sfxId;\n    float x26_maxVol;\n    float x27_minVol;\n    bool x28_important; // Can't be allocated over, regardless of priority\n    u8 x29_prio;\n  };\n  CAudioSys(u8, u8, u8, u8, u32) {\n    g_SharedSys = this;\n  }\n  ~CAudioSys() { g_SharedSys = nullptr; }\n\n  static void SetSurroundMode(ESurroundModes mode) {}\n  static TLockedToken<CAudioGroupSet> FindGroupSet(std::string_view name);\n  static std::string_view SysGetGroupSetName(CAssetId id);\n  static bool SysLoadGroupSet(CSimplePool* pool, CAssetId id);\n  static bool SysLoadGroupSet(const TLockedToken<CAudioGroupSet>& set, std::string_view name, CAssetId id);\n  static void SysUnloadAudioGroupSet(std::string_view name);\n  static bool SysIsGroupSetLoaded(std::string_view name);\n  static void SysAddGroupIntoAmuse(std::string_view name);\n  static void SysRemoveGroupFromAmuse(std::string_view name);\n  static void SysSetVolume(u8 volume);\n  static void SysSetSfxVolume(u8 volume, u16 time, bool music, bool fx);\n\n  static s16 GetDefaultVolumeScale();\n  static void SetDefaultVolumeScale(s16 scale);\n  static void SetVolumeScale(s16 scale);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Audio/CMakeLists.txt",
    "content": "set(AUDIO_SOURCES\n        CAudioSys.hpp CAudioSys.cpp\n        CAudioGroupSet.hpp CAudioGroupSet.cpp\n        CSfxManager.hpp CSfxManager.cpp\n        CMidiManager.hpp CMidiManager.cpp\n        CStaticAudioPlayer.hpp CStaticAudioPlayer.cpp\n        CStreamAudioManager.hpp CStreamAudioManager.cpp\n        g721.c g721.h)\n\nruntime_add_list(Audio AUDIO_SOURCES)\n"
  },
  {
    "path": "Runtime/Audio/CMidiManager.cpp",
    "content": "#include \"Runtime/Audio/CMidiManager.hpp\"\n\n#include \"Runtime/Streams/CInputStream.hpp\"\n\nnamespace metaforce {\n\nstd::unordered_set<CMidiHandle> CMidiManager::m_MidiWrappers = {};\n\nvoid CMidiManager::StopAll() {\n  for (auto it = m_MidiWrappers.begin(); it != m_MidiWrappers.end();)\n    it = Stop(it, 0.f);\n}\n\nvoid CMidiManager::Stop(const CMidiHandle& handle, float fadeTime) {\n//  handle->GetAudioSysHandle()->stopSong(fadeTime);\n//  m_MidiWrappers.erase(handle);\n}\n\nstd::unordered_set<CMidiHandle>::iterator CMidiManager::Stop(std::unordered_set<CMidiHandle>::iterator handle,\n                                                             float fadeTime) {\n//  const CMidiHandle& h = *handle;\n//  h->GetAudioSysHandle()->stopSong(fadeTime);\n  return m_MidiWrappers.erase(handle);\n}\n\nCMidiHandle CMidiManager::Play(const CMidiData& data, float fadeTime, bool stopExisting, float volume) {\n  if (stopExisting)\n    for (auto it = m_MidiWrappers.begin(); it != m_MidiWrappers.end();)\n      it = Stop(it, fadeTime);\n\n  CMidiHandle handle = *m_MidiWrappers.insert(std::make_shared<CMidiWrapper>()).first;\n//  handle->SetAudioSysHandle(\n//      CAudioSys::GetAmuseEngine().seqPlay(data.GetGroupId(), data.GetSetupId(), data.GetArrData()));\n//  handle->GetAudioSysHandle()->setVolume(volume, fadeTime);\n//  handle->SetSongId(data.GetSetupId());\n  return handle;\n}\n\nCMidiManager::CMidiData::CMidiData(CInputStream& in) {\n  in.ReadLong();\n  x0_setupId = in.ReadLong();\n  x2_groupId = in.ReadLong();\n  x4_agscId = in.Get<CAssetId>();\n  u32 length = in.ReadLong();\n  x8_arrData.reset(new u8[length]);\n  in.ReadBytes(reinterpret_cast<char*>(x8_arrData.get()), length);\n}\n\nCFactoryFnReturn FMidiDataFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& parms,\n                                  CObjectReference* selfRef) {\n  return TToken<CMidiManager::CMidiData>::GetIObjObjectFor(std::make_unique<CMidiManager::CMidiData>(in));\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Audio/CMidiManager.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/Audio/CSfxManager.hpp\"\n\nnamespace metaforce {\n\nclass CMidiManager {\npublic:\n  class CMidiData {\n    u16 x0_setupId;\n    u16 x2_groupId;\n    CAssetId x4_agscId;\n    std::unique_ptr<u8[]> x8_arrData;\n\n  public:\n    u16 GetSetupId() const { return x0_setupId; }\n    u16 GetGroupId() const { return x2_groupId; }\n    CAssetId GetAGSCAssetId() const { return x4_agscId; }\n    const u8* GetArrData() const { return x8_arrData.get(); }\n    explicit CMidiData(CInputStream& in);\n  };\n\n  class CMidiWrapper {\n    //amuse::ObjToken<amuse::Sequencer> x0_sequencer;\n    // CSfxHandle x4_handle;\n    u16 x8_songId;\n    bool xa_available = true;\n\n  public:\n    //amuse::ObjToken<amuse::Sequencer> GetAudioSysHandle() const { return x0_sequencer; }\n    //void SetAudioSysHandle(amuse::ObjToken<amuse::Sequencer> sequencer) { x0_sequencer = std::move(sequencer); }\n    // const CSfxHandle& GetManagerHandle() const { return x4_handle; }\n    // void SetMidiHandle(const CSfxHandle& handle) { x4_handle = handle; }\n    bool IsAvailable() const { return xa_available; }\n    void SetAvailable(bool available) { xa_available = available; }\n    u16 GetSongId() const { return x8_songId; }\n    void SetSongId(u16 songId) { x8_songId = songId; }\n  };\n  using CMidiHandle = std::shared_ptr<CMidiWrapper>;\n\n  static void StopAll();\n  static void Stop(const CMidiHandle& handle, float fadeTime);\n  static std::unordered_set<CMidiHandle>::iterator Stop(std::unordered_set<CMidiHandle>::iterator handle,\n                                                        float fadeTime);\n  static CMidiHandle Play(const CMidiData& data, float fadeTime, bool stopExisting, float volume);\n\nprivate:\n  static std::unordered_set<CMidiHandle> m_MidiWrappers;\n};\n\nCFactoryFnReturn FMidiDataFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& parms,\n                                  CObjectReference* selfRef);\n\nusing CMidiHandle = CMidiManager::CMidiHandle;\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Audio/CSfxManager.cpp",
    "content": "#include \"Runtime/Audio/CSfxManager.hpp\"\n#include \"Runtime/Streams/CInputStream.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n\nnamespace metaforce {\nstatic TLockedToken<std::vector<u16>> mpSfxTranslationTableTok;\nstd::vector<u16>* CSfxManager::mpSfxTranslationTable = nullptr;\n\n//static amuse::EffectReverbHiInfo s_ReverbHiQueued;\n//static amuse::EffectChorusInfo s_ChorusQueued;\n//static amuse::EffectReverbStdInfo s_ReverbStdQueued;\n//static amuse::EffectDelayInfo s_DelayQueued;\n//\n//static amuse::EffectReverbHi* s_ReverbHiState = nullptr;\n//static amuse::EffectChorus* s_ChorusState = nullptr;\n//static amuse::EffectReverbStd* s_ReverbStdState = nullptr;\n//static amuse::EffectDelay* s_DelayState = nullptr;\n\nCFactoryFnReturn FAudioTranslationTableFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& vparms,\n                                               CObjectReference* selfRef) {\n  std::unique_ptr<std::vector<u16>> obj = std::make_unique<std::vector<u16>>();\n  u32 count = in.ReadLong();\n  obj->reserve(count);\n  for (u32 i = 0; i < count; ++i)\n    obj->push_back(in.ReadShort());\n  return TToken<std::vector<u16>>::GetIObjObjectFor(std::move(obj));\n}\n\nstd::array<CSfxManager::CSfxChannel, 4> CSfxManager::m_channels;\nCSfxManager::ESfxChannels CSfxManager::m_currentChannel = CSfxManager::ESfxChannels::Default;\nbool CSfxManager::m_doUpdate;\nvoid* CSfxManager::m_usedSounds;\nbool CSfxManager::m_muted;\nbool CSfxManager::m_auxProcessingEnabled = false;\nfloat CSfxManager::m_reverbAmount = 1.f;\nCSfxManager::EAuxEffect CSfxManager::m_activeEffect = CSfxManager::EAuxEffect::None;\nCSfxManager::EAuxEffect CSfxManager::m_nextEffect = CSfxManager::EAuxEffect::None;\n//amuse::ObjToken<amuse::Listener> CSfxManager::m_listener;\n\nu16 CSfxManager::kMaxPriority;\nu16 CSfxManager::kMedPriority;\nu16 CSfxManager::kInternalInvalidSfxId;\nu32 CSfxManager::kAllAreas;\n\nbool CSfxManager::LoadTranslationTable(CSimplePool* pool, const SObjectTag* tag) {\n  if (!tag)\n    return false;\n  mpSfxTranslationTable = nullptr;\n  mpSfxTranslationTableTok = pool->GetObj(*tag);\n  if (!mpSfxTranslationTableTok)\n    return false;\n  return true;\n}\n\nbool CSfxManager::CSfxWrapper::IsPlaying() const {\n//  if (CBaseSfxWrapper::IsPlaying() && x1c_voiceHandle)\n//    return x1c_voiceHandle->state() == amuse::VoiceState::Playing;\n  return false;\n}\n\nvoid CSfxManager::CSfxWrapper::Play() {\n//  x1c_voiceHandle = CAudioSys::GetAmuseEngine().fxStart(x18_sfxId, x20_vol, x22_pan);\n//  if (x1c_voiceHandle) {\n//    if (CSfxManager::IsAuxProcessingEnabled() && UseAcoustics())\n//      x1c_voiceHandle->setReverbVol(m_reverbAmount);\n//    SetPlaying(true);\n//  }\n  x24_ready = false;\n}\n\nvoid CSfxManager::CSfxWrapper::Stop() {\n//  if (x1c_voiceHandle) {\n//    x1c_voiceHandle->keyOff();\n//    SetPlaying(false);\n//    x1c_voiceHandle.reset();\n//  }\n}\n\nbool CSfxManager::CSfxWrapper::Ready() {\n  if (IsLooped())\n    return true;\n  return x24_ready;\n}\n\nu16 CSfxManager::CSfxWrapper::GetSfxId() const { return x18_sfxId; }\n\nvoid CSfxManager::CSfxWrapper::UpdateEmitterSilent() {\n//  if (x1c_voiceHandle)\n//    x1c_voiceHandle->setVolume(1.f / 127.f);\n}\n\nvoid CSfxManager::CSfxWrapper::UpdateEmitter() {\n//  if (x1c_voiceHandle)\n//    x1c_voiceHandle->setVolume(x20_vol);\n}\n\nvoid CSfxManager::CSfxWrapper::SetReverb(float rev) {\n//  if (x1c_voiceHandle && IsAuxProcessingEnabled() && UseAcoustics())\n//    x1c_voiceHandle->setReverbVol(rev);\n}\n\nbool CSfxManager::CSfxEmitterWrapper::IsPlaying() const {\n  if (IsLooped())\n    return CBaseSfxWrapper::IsPlaying();\n//  if (CBaseSfxWrapper::IsPlaying() && x50_emitterHandle)\n//    return x50_emitterHandle->getVoice()->state() == amuse::VoiceState::Playing;\n  return false;\n}\n\nvoid CSfxManager::CSfxEmitterWrapper::Play() {\n  if (CSfxManager::IsAuxProcessingEnabled() && UseAcoustics())\n    x1a_reverb = m_reverbAmount;\n  else\n    x1a_reverb = 0.f;\n\n//  zeus::simd_floats pos(x24_parmData.x0_pos.mSimd);\n//  zeus::simd_floats dir(x24_parmData.xc_dir.mSimd);\n//  x50_emitterHandle = CAudioSys::GetAmuseEngine().addEmitter(\n//      pos.data(), dir.data(), x24_parmData.x18_maxDist, x24_parmData.x1c_distComp, x24_parmData.x24_sfxId,\n//      x24_parmData.x27_minVol, x24_parmData.x26_maxVol, (x24_parmData.x20_flags & 0x8) != 0);\n//\n//  if (x50_emitterHandle)\n//    SetPlaying(true);\n  x54_ready = false;\n}\n\nvoid CSfxManager::CSfxEmitterWrapper::Stop() {\n//  if (x50_emitterHandle) {\n//    x50_emitterHandle->getVoice()->keyOff();\n//    SetPlaying(false);\n//    x50_emitterHandle.reset();\n//  }\n}\n\nbool CSfxManager::CSfxEmitterWrapper::Ready() {\n  if (IsLooped())\n    return true;\n  return x54_ready;\n}\n\nCSfxManager::ESfxAudibility CSfxManager::CSfxEmitterWrapper::GetAudible(const zeus::CVector3f& vec) {\n  float magSq = (x24_parmData.x0_pos - vec).magSquared();\n  float maxDist = x24_parmData.x18_maxDist * x24_parmData.x18_maxDist;\n  if (magSq < maxDist * 0.25f)\n    return ESfxAudibility::Aud3;\n  else if (magSq < maxDist * 0.5f)\n    return ESfxAudibility::Aud2;\n  else if (magSq < maxDist)\n    return ESfxAudibility::Aud1;\n  return ESfxAudibility::Aud0;\n}\n\nu16 CSfxManager::CSfxEmitterWrapper::GetSfxId() const { return x24_parmData.x24_sfxId; }\n\nvoid CSfxManager::CSfxEmitterWrapper::UpdateEmitterSilent() {\n//  if (x50_emitterHandle) {\n//    zeus::simd_floats pos(x24_parmData.x0_pos.mSimd);\n//    zeus::simd_floats dir(x24_parmData.xc_dir.mSimd);\n//    x50_emitterHandle->setVectors(pos.data(), dir.data());\n//    x50_emitterHandle->setMaxVol(1.f / 127.f);\n//  }\n  x55_cachedMaxVol = x24_parmData.x26_maxVol;\n}\n\nvoid CSfxManager::CSfxEmitterWrapper::UpdateEmitter() {\n//  if (x50_emitterHandle) {\n//    zeus::simd_floats pos(x24_parmData.x0_pos.mSimd);\n//    zeus::simd_floats dir(x24_parmData.xc_dir.mSimd);\n//    x50_emitterHandle->setVectors(pos.data(), dir.data());\n//    x50_emitterHandle->setMaxVol(x55_cachedMaxVol);\n//  }\n}\n\nvoid CSfxManager::CSfxEmitterWrapper::SetReverb(float rev) {\n  if (IsAuxProcessingEnabled() && UseAcoustics())\n    x1a_reverb = rev;\n}\n\nvoid CSfxManager::SetChannel(ESfxChannels chan) {\n  if (m_currentChannel == chan)\n    return;\n  if (m_currentChannel != ESfxChannels::Invalid)\n    TurnOffChannel(m_currentChannel);\n  TurnOnChannel(chan);\n  m_currentChannel = chan;\n}\n\nvoid CSfxManager::KillAll(ESfxChannels chan) {\n  CSfxChannel& chanObj = m_channels[size_t(chan)];\n  for (auto it = chanObj.x48_handles.begin(); it != chanObj.x48_handles.end();) {\n    const CSfxHandle& handle = *it;\n    handle->Stop();\n    handle->Release();\n    handle->Close();\n    it = chanObj.x48_handles.erase(it);\n  }\n}\n\nvoid CSfxManager::TurnOnChannel(ESfxChannels chan) {\n  CSfxChannel& chanObj = m_channels[size_t(chan)];\n  m_currentChannel = chan;\n  m_doUpdate = true;\n  if (chanObj.x44_listenerActive) {\n    for (const CSfxHandle& handle : chanObj.x48_handles) {\n      handle->UpdateEmitter();\n    }\n  }\n}\n\nvoid CSfxManager::TurnOffChannel(ESfxChannels chan) {\n  CSfxChannel& chanObj = m_channels[size_t(chan)];\n  for (auto it = chanObj.x48_handles.begin(); it != chanObj.x48_handles.end();) {\n    const CSfxHandle& handle = *it;\n    if (handle->IsLooped()) {\n      handle->UpdateEmitterSilent();\n    } else {\n      handle->Stop();\n      handle->Close();\n      it = chanObj.x48_handles.erase(it);\n      continue;\n    }\n    ++it;\n  }\n\n  for (auto it = chanObj.x48_handles.begin(); it != chanObj.x48_handles.end();) {\n    const CSfxHandle& handle = *it;\n    if (!handle->IsLooped()) {\n      handle->Release();\n      handle->Close();\n      it = chanObj.x48_handles.erase(it);\n      continue;\n    }\n    ++it;\n  }\n}\n\nvoid CSfxManager::AddListener(ESfxChannels channel, const zeus::CVector3f& pos, const zeus::CVector3f& dir,\n                              const zeus::CVector3f& heading, const zeus::CVector3f& up, float frontRadius,\n                              float surroundRadius, float soundSpeed, u32 flags /* 0x1 for doppler */, float vol) {\n//  if (m_listener)\n//    CAudioSys::GetAmuseEngine().removeListener(m_listener.get());\n//  zeus::simd_floats p(pos.mSimd);\n//  zeus::simd_floats d(dir.mSimd);\n//  zeus::simd_floats h(heading.mSimd);\n//  zeus::simd_floats u(up.mSimd);\n//  m_listener = CAudioSys::GetAmuseEngine().addListener(p.data(), d.data(), h.data(), u.data(), frontRadius,\n//                                                       surroundRadius, soundSpeed, vol);\n}\n\nvoid CSfxManager::UpdateListener(const zeus::CVector3f& pos, const zeus::CVector3f& dir, const zeus::CVector3f& heading,\n                                 const zeus::CVector3f& up, float vol) {\n//  if (m_listener) {\n//    zeus::simd_floats p(pos.mSimd);\n//    zeus::simd_floats d(dir.mSimd);\n//    zeus::simd_floats h(heading.mSimd);\n//    zeus::simd_floats u(up.mSimd);\n//    m_listener->setVectors(p.data(), d.data(), h.data(), u.data());\n//    m_listener->setVolume(vol);\n//  }\n}\n\ns16 CSfxManager::GetRank(CBaseSfxWrapper* sfx) {\n  const CSfxChannel& chanObj = m_channels[size_t(m_currentChannel)];\n  if (!sfx->IsInArea()) {\n    return 0;\n  }\n\n  s16 rank = sfx->GetPriority() / 4;\n  if (sfx->IsPlaying()) {\n    ++rank;\n  }\n\n  if (sfx->IsLooped()) {\n    rank -= 2;\n  }\n\n  if (sfx->Ready() && !sfx->IsPlaying()) {\n    rank += 3;\n  }\n\n  if (chanObj.x44_listenerActive) {\n    const ESfxAudibility aud = sfx->GetAudible(chanObj.x0_pos);\n    if (aud == ESfxAudibility::Aud0) {\n      return 0;\n    }\n    rank += int(aud) / 2;\n  }\n\n  return rank;\n}\n\nvoid CSfxManager::ApplyReverb() {\n  const CSfxChannel& chanObj = m_channels[size_t(m_currentChannel)];\n  for (const CSfxHandle& handle : chanObj.x48_handles) {\n    handle->SetReverb(m_reverbAmount);\n  }\n}\n\nfloat CSfxManager::GetReverbAmount() { return m_reverbAmount; }\n\nvoid CSfxManager::PitchBend(const CSfxHandle& handle, float pitch) {\n  if (!handle)\n    return;\n  if (!handle->IsPlaying())\n    CSfxManager::Update(0.f);\n//  if (handle->IsPlaying()) {\n//    m_doUpdate = true;\n//    handle->GetVoice()->setPitchWheel(pitch);\n//  }\n}\n\nvoid CSfxManager::SfxVolume(const CSfxHandle& handle, float vol) {\n  if (!handle)\n    return;\n  if (handle->IsEmitter()) {\n    CSfxWrapper& wrapper = static_cast<CSfxWrapper&>(*handle);\n    wrapper.SetVolume(vol);\n  }\n//  if (handle->IsPlaying())\n//    handle->GetVoice()->setVolume(vol);\n}\n\nvoid CSfxManager::SfxSpan(const CSfxHandle& handle, float span) {\n  if (!handle)\n    return;\n//  if (handle->IsPlaying())\n//    handle->GetVoice()->setSurroundPan(span);\n}\n\nu16 CSfxManager::TranslateSFXID(u16 id) {\n  if (mpSfxTranslationTable == nullptr)\n    return 0;\n\n  u16 index = id;\n  if (index >= mpSfxTranslationTable->size())\n    return 0;\n\n  u16 ret = (*mpSfxTranslationTable)[index];\n  if (ret == 0xffff)\n    return 0;\n  return ret;\n}\n\nbool CSfxManager::PlaySound(const CSfxManager::CSfxHandle& handle) { return false; }\n\nvoid CSfxManager::StopSound(const CSfxHandle& handle) {\n  if (!handle)\n    return;\n  m_doUpdate = true;\n  handle->Stop();\n  handle->Release();\n  CSfxChannel& chanObj = m_channels[size_t(m_currentChannel)];\n  handle->Close();\n  chanObj.x48_handles.erase(handle);\n}\n\nvoid CSfxManager::SfxStop(const CSfxHandle& handle) { StopSound(handle); }\n\nCSfxHandle CSfxManager::SfxStart(u16 id, float vol, float pan, bool useAcoustics, s16 prio, bool looped, s32 areaId) {\n  if (m_muted || id == 0xffff)\n    return {};\n\n  m_doUpdate = true;\n  CSfxHandle wrapper = std::make_shared<CSfxWrapper>(looped, prio, id, vol, pan, useAcoustics, areaId);\n  CSfxChannel& chanObj = m_channels[size_t(m_currentChannel)];\n  chanObj.x48_handles.insert(wrapper);\n  return wrapper;\n}\n\nbool CSfxManager::IsPlaying(const CSfxHandle& handle) {\n  if (!handle)\n    return false;\n  return handle->IsPlaying();\n}\n\nvoid CSfxManager::RemoveEmitter(const CSfxHandle& handle) { StopSound(handle); }\n\nvoid CSfxManager::UpdateEmitter(const CSfxHandle& handle, const zeus::CVector3f& pos, const zeus::CVector3f& dir,\n                                float maxVol) {\n  if (!handle || !handle->IsEmitter() || !handle->IsPlaying())\n    return;\n//  m_doUpdate = true;\n//  CSfxEmitterWrapper& emitter = static_cast<CSfxEmitterWrapper&>(*handle);\n//  emitter.GetEmitterData().x0_pos = pos;\n//  emitter.GetEmitterData().xc_dir = dir;\n//  emitter.GetEmitterData().x26_maxVol = maxVol;\n//  amuse::Emitter& h = *emitter.GetHandle();\n//  zeus::simd_floats p(pos.mSimd);\n//  zeus::simd_floats d(dir.mSimd);\n//  h.setVectors(p.data(), d.data());\n//  h.setMaxVol(maxVol);\n}\n\nCSfxHandle CSfxManager::AddEmitter(u16 id, const zeus::CVector3f& pos, const zeus::CVector3f& dir, bool useAcoustics,\n                                   bool looped, s16 prio, s32 areaId) {\n  const CAudioSys::C3DEmitterParmData parmData{\n      .x0_pos = pos,\n      .xc_dir = dir,\n      .x18_maxDist = 150.f,\n      .x1c_distComp = 0.1f,\n      .x20_flags = 1, // Continuous parameter update\n      .x24_sfxId = id,\n      .x26_maxVol = 1.f,\n      .x27_minVol = 0.165f,\n      .x28_important = false,\n      .x29_prio = 0x7f,\n  };\n  return AddEmitter(parmData, useAcoustics, prio, looped, areaId);\n}\n\nCSfxHandle CSfxManager::AddEmitter(u16 id, const zeus::CVector3f& pos, const zeus::CVector3f& dir, float vol,\n                                   bool useAcoustics, bool looped, s16 prio, s32 areaId) {\n  const CAudioSys::C3DEmitterParmData parmData{\n      .x0_pos = pos,\n      .xc_dir = dir,\n      .x18_maxDist = 150.f,\n      .x1c_distComp = 0.1f,\n      .x20_flags = 1, // Continuous parameter update\n      .x24_sfxId = id,\n      .x26_maxVol = std::max(vol, 0.165f),\n      .x27_minVol = 0.165f,\n      .x28_important = false,\n      .x29_prio = 0x7f,\n  };\n  return AddEmitter(parmData, useAcoustics, prio, looped, areaId);\n}\n\nCSfxHandle CSfxManager::AddEmitter(const CAudioSys::C3DEmitterParmData& parmData, bool useAcoustics, s16 prio,\n                                   bool looped, s32 areaId) {\n  if (m_muted || parmData.x24_sfxId == 0xffff)\n    return {};\n\n  CAudioSys::C3DEmitterParmData data = parmData;\n  if (looped)\n    data.x20_flags |= 0x6; // Pausable/restartable when inaudible\n  m_doUpdate = true;\n  CSfxHandle wrapper = std::make_shared<CSfxEmitterWrapper>(looped, prio, data, useAcoustics, areaId);\n  CSfxChannel& chanObj = m_channels[size_t(m_currentChannel)];\n  chanObj.x48_handles.insert(wrapper);\n  return wrapper;\n}\n\nvoid CSfxManager::StopAndRemoveAllEmitters() {\n  for (auto& chanObj : m_channels) {\n    for (auto it = chanObj.x48_handles.begin(); it != chanObj.x48_handles.end();) {\n      const CSfxHandle& handle = *it;\n      handle->Stop();\n      handle->Release();\n      handle->Close();\n      it = chanObj.x48_handles.erase(it);\n    }\n  }\n}\n\nvoid CSfxManager::EnableAuxCallback() {\n  m_reverbAmount = 0.f;\n  ApplyReverb();\n  if (m_activeEffect != EAuxEffect::None)\n    DisableAuxCallback();\n\n//  auto studio = CAudioSys::GetAmuseEngine().getDefaultStudio();\n//  amuse::Submix& smix = studio->getAuxA();\n//\n//  m_activeEffect = m_nextEffect;\n//  switch (m_activeEffect) {\n//  case EAuxEffect::ReverbHi:\n//    s_ReverbHiState = &smix.makeReverbHi(s_ReverbHiQueued);\n//    break;\n//  case EAuxEffect::Chorus:\n//    s_ChorusState = &smix.makeChorus(s_ChorusQueued);\n//    break;\n//  case EAuxEffect::ReverbStd:\n//    s_ReverbStdState = &smix.makeReverbStd(s_ReverbStdQueued);\n//    break;\n//  case EAuxEffect::Delay:\n//    s_DelayState = &smix.makeDelay(s_DelayQueued);\n//    break;\n//  default:\n//    break;\n//  }\n\n  m_auxProcessingEnabled = true;\n}\n\n//void CSfxManager::PrepareDelayCallback(const amuse::EffectDelayInfo& info) {\n//  DisableAuxProcessing();\n//  s_DelayQueued = info;\n//  m_nextEffect = EAuxEffect::Delay;\n//  if (m_reverbAmount == 0.f)\n//    EnableAuxCallback();\n//}\n//\n//void CSfxManager::PrepareReverbStdCallback(const amuse::EffectReverbStdInfo& info) {\n//  DisableAuxProcessing();\n//  s_ReverbStdQueued = info;\n//  m_nextEffect = EAuxEffect::ReverbStd;\n//  if (m_reverbAmount == 0.f)\n//    EnableAuxCallback();\n//}\n//\n//void CSfxManager::PrepareChorusCallback(const amuse::EffectChorusInfo& info) {\n//  DisableAuxProcessing();\n//  s_ChorusQueued = info;\n//  m_nextEffect = EAuxEffect::Chorus;\n//  if (m_reverbAmount == 0.f)\n//    EnableAuxCallback();\n//}\n//\n//void CSfxManager::PrepareReverbHiCallback(const amuse::EffectReverbHiInfo& info) {\n//  DisableAuxProcessing();\n//  s_ReverbHiQueued = info;\n//  m_nextEffect = EAuxEffect::ReverbHi;\n//  if (m_reverbAmount == 0.f)\n//    EnableAuxCallback();\n//}\n\nvoid CSfxManager::DisableAuxCallback() {\n//  auto studio = CAudioSys::GetAmuseEngine().getDefaultStudio();\n//  studio->getAuxA().clearEffects();\n//\n//  switch (m_activeEffect) {\n//  case EAuxEffect::ReverbHi:\n//    s_ReverbHiState = nullptr;\n//    break;\n//  case EAuxEffect::Chorus:\n//    s_ChorusState = nullptr;\n//    break;\n//  case EAuxEffect::ReverbStd:\n//    s_ReverbStdState = nullptr;\n//    break;\n//  case EAuxEffect::Delay:\n//    s_DelayState = nullptr;\n//    break;\n//  default:\n//    break;\n//  }\n//\n//  m_activeEffect = EAuxEffect::None;\n}\n\nvoid CSfxManager::DisableAuxProcessing() {\n  m_nextEffect = EAuxEffect::None;\n  m_auxProcessingEnabled = false;\n}\n\nvoid CSfxManager::SetActiveAreas(const rstl::reserved_vector<TAreaId, 10>& areas) {\n  const CSfxChannel& chanObj = m_channels[size_t(m_currentChannel)];\n\n  for (const CSfxHandle& hnd : chanObj.x48_handles) {\n    const TAreaId sndArea = hnd->GetArea();\n    if (sndArea == kInvalidAreaId) {\n      hnd->SetInArea(true);\n    } else {\n      bool inArea = false;\n      for (const TAreaId id : areas) {\n        if (sndArea == id) {\n          inArea = true;\n          break;\n        }\n      }\n      m_doUpdate = true;\n      hnd->SetInArea(inArea);\n    }\n  }\n}\n\nvoid CSfxManager::Update(float dt) {\n  CSfxChannel& chanObj = m_channels[size_t(m_currentChannel)];\n\n  for (auto it = chanObj.x48_handles.begin(); it != chanObj.x48_handles.end();) {\n    const CSfxHandle& handle = *it;\n    if (!handle->IsLooped()) {\n      float timeRem = handle->GetTimeRemaining();\n      handle->SetTimeRemaining(timeRem - dt);\n      if (timeRem < 0.f) {\n        handle->Stop();\n        m_doUpdate = true;\n        handle->Close();\n        it = chanObj.x48_handles.erase(it);\n        continue;\n      }\n    }\n    ++it;\n  }\n\n  if (m_doUpdate) {\n    std::vector<CSfxHandle> rankedSfx;\n    rankedSfx.reserve(chanObj.x48_handles.size());\n    for (const CSfxHandle& handle : chanObj.x48_handles) {\n      rankedSfx.push_back(handle);\n      handle->SetRank(GetRank(handle.get()));\n    }\n\n    std::sort(rankedSfx.begin(), rankedSfx.end(),\n              [](const CSfxHandle& a, const CSfxHandle& b) -> bool { return a->GetRank() < b->GetRank(); });\n\n    for (size_t i = 48; i < rankedSfx.size(); ++i) {\n      const CSfxHandle& handle = rankedSfx[i];\n      if (handle->IsPlaying()) {\n        handle->Stop();\n        handle->Close();\n        chanObj.x48_handles.erase(handle);\n      }\n    }\n\n    for (const CSfxHandle& handle : rankedSfx) {\n      if (handle->IsPlaying() && !handle->IsInArea()) {\n        handle->Stop();\n        handle->Close();\n        chanObj.x48_handles.erase(handle);\n      }\n    }\n\n#ifndef URDE_MSAN\n    for (const CSfxHandle& handle : chanObj.x48_handles) {\n      if (handle->IsPlaying())\n        continue;\n      if (handle->Ready() && handle->IsInArea())\n        handle->Play();\n    }\n#endif\n\n    m_doUpdate = false;\n  }\n\n  for (auto it = chanObj.x48_handles.begin(); it != chanObj.x48_handles.end();) {\n    const CSfxHandle& handle = *it;\n    if (!handle->IsPlaying() && !handle->IsLooped()) {\n      handle->Stop();\n      handle->Release();\n      m_doUpdate = true;\n      handle->Close();\n      it = chanObj.x48_handles.erase(it);\n      continue;\n    }\n    ++it;\n  }\n\n  if (m_auxProcessingEnabled && m_reverbAmount < 1.f) {\n    m_reverbAmount = std::min(1.f, dt / 0.1f + m_reverbAmount);\n    ApplyReverb();\n  } else if (!m_auxProcessingEnabled && m_reverbAmount > 0.f) {\n    m_reverbAmount = std::max(0.f, m_reverbAmount - dt / (2.f * 0.1f));\n    ApplyReverb();\n    if (m_reverbAmount == 0.f) {\n      DisableAuxCallback();\n      EnableAuxCallback();\n    }\n  }\n\n  if (mpSfxTranslationTableTok.IsLoaded() && !mpSfxTranslationTable)\n    mpSfxTranslationTable = mpSfxTranslationTableTok.GetObj();\n}\n\nvoid CSfxManager::Shutdown() {\n  mpSfxTranslationTable = nullptr;\n  mpSfxTranslationTableTok = TLockedToken<std::vector<u16>>{};\n  StopAndRemoveAllEmitters();\n  DisableAuxCallback();\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Audio/CSfxManager.hpp",
    "content": "#pragma once\n\n#include <array>\n#include <memory>\n#include <unordered_set>\n#include <vector>\n\n#include \"SFX/SFX.h\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Audio/CAudioSys.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\n\nclass CSfxManager {\n  static std::vector<u16>* mpSfxTranslationTable;\n\npublic:\n  enum class ESfxChannels { Invalid = -1, Default = 0, Game, PauseScreen };\n\n  enum class ESfxAudibility { Aud0, Aud1, Aud2, Aud3 };\n\n  enum class EAuxEffect { None = -1, ReverbHi = 0, Chorus, ReverbStd, Delay };\n\n  class CBaseSfxWrapper;\n  using CSfxHandle = std::shared_ptr<CBaseSfxWrapper>;\n\n  /* Original imp, kept for reference\n  class CSfxHandle\n  {\n      static u32 mRefCount;\n      u32 x0_idx;\n  public:\n      CSfxHandle(u32 id)\n          : x0_idx(++mRefCount << 14 | (id & 0xFFFF)) {}\n  };\n  */\n\n  class CSfxChannel {\n    friend class CSfxManager;\n    zeus::CVector3f x0_pos;\n    zeus::CVector3f xc_;\n    zeus::CVector3f x18_;\n    zeus::CVector3f x24_;\n    /*\n    float x30_ = 0.f;\n    float x34_ = 0.f;\n    float x38_ = 0.f;\n    u32 x3c_ = 0;\n    bool x40_ = false;\n    */\n    bool x44_listenerActive = false;\n    std::unordered_set<CSfxHandle> x48_handles;\n  };\n\n  class CBaseSfxWrapper : public std::enable_shared_from_this<CBaseSfxWrapper> {\n    float x4_timeRemaining = 15.f;\n    s16 x8_rank = 0;\n    s16 xa_prio;\n    // CSfxHandle xc_handle;\n    TAreaId x10_area;\n    bool x14_24_isActive : 1 = true;\n    bool x14_25_isPlaying : 1 = false;\n    bool x14_26_looped : 1;\n    bool x14_27_inArea : 1 = true;\n    bool x14_28_isReleased : 1 = false;\n    bool x14_29_useAcoustics : 1;\n\n  protected:\n    bool m_isEmitter : 1 = false;\n    bool m_isClosed : 1 = false;\n\n  public:\n    virtual ~CBaseSfxWrapper() = default;\n    virtual void SetActive(bool v) { x14_24_isActive = v; }\n    virtual void SetPlaying(bool v) { x14_25_isPlaying = v; }\n    virtual void SetRank(short v) { x8_rank = v; }\n    virtual void SetInArea(bool v) { x14_27_inArea = v; }\n    virtual bool IsInArea() const { return x14_27_inArea; }\n    virtual bool IsPlaying() const { return x14_25_isPlaying; }\n    virtual bool UseAcoustics() const { return x14_29_useAcoustics; }\n    virtual bool IsLooped() const { return x14_26_looped; }\n    virtual bool IsActive() const { return x14_24_isActive; }\n    virtual s16 GetRank() const { return x8_rank; }\n    virtual s16 GetPriority() const { return xa_prio; }\n    virtual TAreaId GetArea() const { return x10_area; }\n    virtual CSfxHandle GetSfxHandle() { return shared_from_this(); }\n    virtual void Play() = 0;\n    virtual void Stop() = 0;\n    virtual bool Ready() = 0;\n    virtual ESfxAudibility GetAudible(const zeus::CVector3f&) = 0;\n    //virtual amuse::ObjToken<amuse::Voice> GetVoice() const = 0;\n    virtual u16 GetSfxId() const = 0;\n    virtual void UpdateEmitterSilent() = 0;\n    virtual void UpdateEmitter() = 0;\n    virtual void SetReverb(float rev) = 0;\n    bool IsEmitter() const { return m_isEmitter; }\n\n    void Release() {\n      x14_28_isReleased = true;\n      x4_timeRemaining = 15.f;\n    }\n    bool IsReleased() const { return x14_28_isReleased; }\n\n    void Close() { m_isClosed = true; }\n    bool IsClosed() const { return m_isClosed; }\n\n    float GetTimeRemaining() const { return x4_timeRemaining; }\n    void SetTimeRemaining(float t) { x4_timeRemaining = t; }\n\n    CBaseSfxWrapper(bool looped, s16 prio, /*const CSfxHandle& handle,*/ bool useAcoustics, TAreaId area)\n    : xa_prio(prio), /*xc_handle(handle),*/ x10_area(area), x14_26_looped(looped), x14_29_useAcoustics(useAcoustics) {}\n  };\n\n  class CSfxEmitterWrapper : public CBaseSfxWrapper {\n    float x1a_reverb = 0.0f;\n    CAudioSys::C3DEmitterParmData x24_parmData;\n    //amuse::ObjToken<amuse::Emitter> x50_emitterHandle;\n    bool x54_ready = true;\n    float x55_cachedMaxVol = 0.0f;\n\n  public:\n    bool IsPlaying() const override;\n    void Play() override;\n    void Stop() override;\n    bool Ready() override;\n    ESfxAudibility GetAudible(const zeus::CVector3f&) override;\n    //amuse::ObjToken<amuse::Voice> GetVoice() const override { return x50_emitterHandle->getVoice(); }\n    u16 GetSfxId() const override;\n    void UpdateEmitterSilent() override;\n    void UpdateEmitter() override;\n    void SetReverb(float rev) override;\n    CAudioSys::C3DEmitterParmData& GetEmitterData() { return x24_parmData; }\n\n    //amuse::ObjToken<amuse::Emitter> GetHandle() const { return x50_emitterHandle; }\n\n    CSfxEmitterWrapper(bool looped, s16 prio, const CAudioSys::C3DEmitterParmData& data,\n                       /*const CSfxHandle& handle,*/ bool useAcoustics, TAreaId area)\n    : CBaseSfxWrapper(looped, prio, /*handle,*/ useAcoustics, area), x24_parmData(data) {\n      m_isEmitter = true;\n    }\n  };\n\n  class CSfxWrapper : public CBaseSfxWrapper {\n    u16 x18_sfxId;\n    //amuse::ObjToken<amuse::Voice> x1c_voiceHandle;\n    float x20_vol;\n    float x22_pan;\n    bool x24_ready = true;\n\n  public:\n    bool IsPlaying() const override;\n    void Play() override;\n    void Stop() override;\n    bool Ready() override;\n    ESfxAudibility GetAudible(const zeus::CVector3f&) override { return ESfxAudibility::Aud3; }\n//    amuse::ObjToken<amuse::Voice> GetVoice() const override { return x1c_voiceHandle; }\n    u16 GetSfxId() const override;\n    void UpdateEmitterSilent() override;\n    void UpdateEmitter() override;\n    void SetReverb(float rev) override;\n    void SetVolume(float vol) { x20_vol = vol; }\n\n    CSfxWrapper(bool looped, s16 prio, u16 sfxId, float vol, float pan,\n                /*const CSfxHandle& handle,*/ bool useAcoustics, TAreaId area)\n    : CBaseSfxWrapper(looped, prio, /*handle,*/ useAcoustics, area), x18_sfxId(sfxId), x20_vol(vol), x22_pan(pan) {\n      m_isEmitter = false;\n    }\n  };\n\n  static std::array<CSfxChannel, 4> m_channels;\n  static ESfxChannels m_currentChannel;\n  static bool m_doUpdate;\n  static void* m_usedSounds;\n  static bool m_muted;\n  static bool m_auxProcessingEnabled;\n  static float m_reverbAmount;\n  static EAuxEffect m_activeEffect;\n  static EAuxEffect m_nextEffect;\n  //static amuse::ObjToken<amuse::Listener> m_listener;\n\n  static u16 kMaxPriority;\n  static u16 kMedPriority;\n  static u16 kInternalInvalidSfxId;\n  static u32 kAllAreas;\n\n  static bool LoadTranslationTable(CSimplePool* pool, const SObjectTag* tag);\n  static bool IsAuxProcessingEnabled() { return m_auxProcessingEnabled; }\n  static void SetChannel(ESfxChannels);\n  static void KillAll(ESfxChannels);\n  static void TurnOnChannel(ESfxChannels);\n  static void TurnOffChannel(ESfxChannels);\n  static ESfxChannels GetCurrentChannel() { return m_currentChannel; }\n  static void AddListener(ESfxChannels channel, const zeus::CVector3f& pos, const zeus::CVector3f& dir,\n                          const zeus::CVector3f& heading, const zeus::CVector3f& up, float frontRadius,\n                          float surroundRadius, float soundSpeed, u32 flags /* 0x1 for doppler */, float vol);\n  static void UpdateListener(const zeus::CVector3f& pos, const zeus::CVector3f& dir, const zeus::CVector3f& heading,\n                             const zeus::CVector3f& up, float vol);\n\n  static bool PlaySound(const CSfxHandle& handle);\n  static void StopSound(const CSfxHandle& handle);\n  static s16 GetRank(CBaseSfxWrapper* sfx);\n  static void ApplyReverb();\n  static float GetReverbAmount();\n  static void PitchBend(const CSfxHandle& handle, float pitch);\n  static void SfxVolume(const CSfxHandle& handle, float vol);\n  static void SfxSpan(const CSfxHandle& handle, float span);\n  static u16 TranslateSFXID(u16);\n  static void SfxStop(const CSfxHandle& handle);\n  static CSfxHandle SfxStart(u16 id, float vol, float pan, bool useAcoustics, s16 prio, bool looped, s32 areaId);\n  static bool IsPlaying(const CSfxHandle& handle);\n  static void RemoveEmitter(const CSfxHandle& handle);\n  static void UpdateEmitter(const CSfxHandle& handle, const zeus::CVector3f& pos, const zeus::CVector3f& dir,\n                            float maxVol);\n  static CSfxHandle AddEmitter(u16 id, const zeus::CVector3f& pos, const zeus::CVector3f& dir, bool useAcoustics,\n                               bool looped, s16 prio, s32 areaId);\n  static CSfxHandle AddEmitter(u16 id, const zeus::CVector3f& pos, const zeus::CVector3f& dir, float vol,\n                               bool useAcoustics, bool looped, s16 prio, s32 areaId);\n  static CSfxHandle AddEmitter(const CAudioSys::C3DEmitterParmData& parmData, bool useAcoustics, s16 prio, bool looped,\n                               s32 areaId);\n  static void StopAndRemoveAllEmitters();\n  static void DisableAuxCallback();\n  static void EnableAuxCallback();\n//  static void PrepareDelayCallback(const amuse::EffectDelayInfo& info);\n//  static void PrepareReverbStdCallback(const amuse::EffectReverbStdInfo& info);\n//  static void PrepareChorusCallback(const amuse::EffectChorusInfo& info);\n//  static void PrepareReverbHiCallback(const amuse::EffectReverbHiInfo& info);\n  static void DisableAuxProcessing();\n\n  static void SetActiveAreas(const rstl::reserved_vector<TAreaId, 10>& areas);\n\n  static void Update(float dt);\n  static void Shutdown();\n};\n\nusing CSfxHandle = CSfxManager::CSfxHandle;\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Audio/CStaticAudioPlayer.cpp",
    "content": "#include \"Runtime/Audio/CStaticAudioPlayer.hpp\"\n\n#include \"Runtime/CDvdFile.hpp\"\n#include \"Runtime/CDvdRequest.hpp\"\n\nnamespace metaforce {\n\n#define RSF_BUFFER_SIZE 0x20000\n\n//CStaticAudioPlayer::CStaticAudioPlayer(boo::IAudioVoiceEngine& engine, std::string_view path, int loopStart,\n//                                       int loopEnd)\n//: x0_path(path)\n//, x1c_loopStartSamp(loopStart & 0xfffffffe)\n//, x20_loopEndSamp(loopEnd & 0xfffffffe)\n//, m_voiceCallback(*this)\n//, m_voice(engine.allocateNewStereoVoice(32000, &m_voiceCallback)) {\n//  // These are mixed directly into boo voice engine instead\n//  // x28_dmaLeft.reset(new u8[640]);\n//  // x30_dmaRight.reset(new u8[640]);\n//\n//  CDvdFile file(path);\n//  x10_rsfRem = file.Length();\n//  x14_rsfLength = x10_rsfRem;\n//\n//  u32 bufCount = (x10_rsfRem + RSF_BUFFER_SIZE - 1) / RSF_BUFFER_SIZE;\n//  x48_buffers.reserve(bufCount);\n//  x38_dvdRequests.reserve(bufCount);\n//\n//  for (int remBytes = x10_rsfRem; remBytes > 0; remBytes -= RSF_BUFFER_SIZE) {\n//    u32 thisSz = RSF_BUFFER_SIZE;\n//    if (remBytes < RSF_BUFFER_SIZE)\n//      thisSz = ROUND_UP_32(remBytes);\n//\n//    x48_buffers.emplace_back(new u8[thisSz]);\n//    x38_dvdRequests.push_back(file.AsyncRead(x48_buffers.back().get(), thisSz));\n//  }\n//\n//  g72x_init_state(&x58_leftState);\n//  g72x_init_state(&x8c_rightState);\n//}\n\nbool CStaticAudioPlayer::IsReady() {\n  if (x38_dvdRequests.size())\n    return x38_dvdRequests.back()->IsComplete();\n  return true;\n}\n\nvoid CStaticAudioPlayer::DecodeMonoAndMix(s16* bufOut, u32 numSamples, u32 cur, u32 loopEndCur, u32 loopStartCur,\n                                          int vol, g72x_state& state, std::optional<g72x_state>& loopState) const {\n  for (u32 remBytes = numSamples / 2; remBytes;) {\n    u32 curBuf = cur / RSF_BUFFER_SIZE;\n    u32 thisBytes = (curBuf + 1) * RSF_BUFFER_SIZE - cur;\n    thisBytes = std::min(thisBytes, remBytes);\n    u32 remTillLoop = loopEndCur - cur;\n    remTillLoop = std::min(remTillLoop, thisBytes);\n\n    const std::unique_ptr<u8[]>& buf = x48_buffers[curBuf];\n    const u8* byte = &buf[cur - curBuf * RSF_BUFFER_SIZE];\n\n    for (u32 i = 0; i < remTillLoop; ++i, ++byte) {\n      if (!loopState && cur + i == loopStartCur)\n        loopState.emplace(state);\n\n      *bufOut = SampClamp(((g721_decoder(*byte & 0xf, &state) * vol) >> 15));\n      bufOut += 2;\n\n      *bufOut = SampClamp(((g721_decoder(*byte >> 4 & 0xf, &state) * vol) >> 15));\n      bufOut += 2;\n    }\n\n    cur += remTillLoop;\n    remBytes -= remTillLoop;\n    if (cur == loopEndCur) {\n      cur = loopStartCur;\n      if (loopState)\n        state = *loopState;\n    }\n  }\n}\n\nvoid CStaticAudioPlayer::Decode(s16* bufOut, u32 numSamples) {\n  DecodeMonoAndMix(bufOut, numSamples, x18_curSamp / 2, x20_loopEndSamp / 2, x1c_loopStartSamp / 2, xc0_volume,\n                   x58_leftState, m_leftStateLoop);\n\n  u32 halfway = x14_rsfLength / 2;\n  DecodeMonoAndMix(bufOut + 1, numSamples, x18_curSamp / 2 + halfway, x20_loopEndSamp / 2 + halfway,\n                   x1c_loopStartSamp / 2 + halfway, xc0_volume, x8c_rightState, m_rightStateLoop);\n\n  for (u32 remSamples = numSamples; remSamples;) {\n    u32 remTillLoop = x20_loopEndSamp - x18_curSamp;\n    remTillLoop = std::min(remTillLoop, remSamples);\n\n    x18_curSamp += remTillLoop;\n    remSamples -= remTillLoop;\n\n    if (x18_curSamp == x20_loopEndSamp)\n      x18_curSamp = x1c_loopStartSamp;\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Audio/CStaticAudioPlayer.hpp",
    "content": "#pragma once\n\n#include <cstring>\n#include <memory>\n#include <optional>\n#include <vector>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Audio/CAudioSys.hpp\"\n\n#include \"g721.h\"\n\n//#include <boo/audiodev/IAudioVoice.hpp>\n//#include <boo/audiodev/IAudioVoiceEngine.hpp>\n\nnamespace metaforce {\nclass IDvdRequest;\n\nclass CStaticAudioPlayer {\n  std::string x0_path;\n  u32 x10_rsfRem = -1;\n  u32 x14_rsfLength;\n  u32 x18_curSamp = 0;\n  u32 x1c_loopStartSamp;\n  u32 x20_loopEndSamp;\n  // u32 x24_ = 0;\n  // std::unique_ptr<u8[]> x28_dmaLeft;\n  // std::unique_ptr<u8[]> x30_dmaRight;\n  std::vector<std::shared_ptr<IDvdRequest>> x38_dvdRequests;\n  std::vector<std::unique_ptr<u8[]>> x48_buffers;\n  g72x_state x58_leftState;\n  g72x_state x8c_rightState;\n  std::optional<g72x_state> m_leftStateLoop;\n  std::optional<g72x_state> m_rightStateLoop;\n  u32 xc0_volume = 32768; // Out of 32768\n\n  static int16_t SampClamp(int32_t val) {\n    if (val < -32768)\n      val = -32768;\n    else if (val > 32767)\n      val = 32767;\n    return val;\n  }\n/*\n  struct AudioVoiceCallback  {\n    CStaticAudioPlayer& m_parent;\n    void preSupplyAudio(boo::IAudioVoice&, double) override {}\n    size_t supplyAudio(boo::IAudioVoice& voice, size_t frames, int16_t* data) override {\n      if (m_parent.IsReady()) {\n        m_parent.x38_dvdRequests.clear();\n        m_parent.Decode(data, frames);\n      } else\n        memset(data, 0, 4 * frames);\n      return frames;\n    }\n    explicit AudioVoiceCallback(CStaticAudioPlayer& p) : m_parent(p) {}\n  } m_voiceCallback;\n  boo::ObjToken<boo::IAudioVoice> m_voice;\n*/\npublic:\n//  CStaticAudioPlayer(boo::IAudioVoiceEngine& engine, std::string_view path, int loopStart, int loopEnd);\n//  CStaticAudioPlayer(std::string_view path, int loopStart, int loopEnd)\n//  : CStaticAudioPlayer(*CAudioSys::GetVoiceEngine(), path, loopStart, loopEnd) {}\n\n  bool IsReady();\n  void DecodeMonoAndMix(s16* bufOut, u32 numSamples, u32 cur, u32 loopEndCur, u32 loopStartCur, int vol,\n                        g72x_state& state, std::optional<g72x_state>& loopState) const;\n  void Decode(s16* bufOut, u32 numSamples);\n  void SetVolume(float vol) { xc0_volume = zeus::clamp(0.f, vol, 1.f) * 32768.f; }\n//\n//  void StartMixing() { m_voice->start(); }\n//  void StopMixing() { m_voice->stop(); }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Audio/CStreamAudioManager.cpp",
    "content": "#include \"Runtime/Audio/CStreamAudioManager.hpp\"\n\n#include \"Runtime/Audio/CAudioSys.hpp\"\n#include \"Runtime/CDvdFile.hpp\"\n#include \"Runtime/CDvdRequest.hpp\"\n#include \"Runtime/CStringExtras.hpp\"\n\n#include <algorithm>\n#include <array>\n#include <cstdlib>\n#include <cstring>\n#include <memory>\n\n//#include <amuse/DSPCodec.hpp>\n\nnamespace metaforce {\nclass CDSPStreamManager;\n\nstatic u32 s_HandleCounter = 0;\nstatic u32 s_HandleCounter2 = 0;\n\n/* Standard DSPADPCM header */\nstruct dspadpcm_header {\n  u32 x0_num_samples;\n  u32 x4_num_nibbles;\n  u32 x8_sample_rate;\n  u16 xc_loop_flag;\n  u16 xe_format; /* 0 for ADPCM */\n  u32 x10_loop_start_nibble;\n  u32 x14_loop_end_nibble;\n  u32 x18_ca;\n  s16 x1c_coef[8][2];\n  s16 x3c_gain;\n  s16 x3e_ps;\n  s16 x40_hist1;\n  s16 x42_hist2;\n  s16 x44_loop_ps;\n  s16 x46_loop_hist1;\n  s16 x48_loop_hist2;\n  std::array<u16, 11> x4a_pad;\n};\n\nstruct SDSPStreamInfo {\n  const char* x0_fileName;\n  u32 x4_sampleRate;\n  u32 x8_headerSize = sizeof(dspadpcm_header);\n  u32 xc_adpcmBytes;\n  bool x10_loopFlag;\n  u32 x14_loopStartByte;\n  u32 x18_loopEndByte;\n  s16 x1c_coef[8][2];\n\n  SDSPStreamInfo() = default;\n  explicit SDSPStreamInfo(const CDSPStreamManager& stream);\n};\n\nstruct SDSPStream  {\n  bool x0_active;\n  bool x1_oneshot;\n  s32 x4_ownerId;\n  SDSPStream* x8_stereoLeft;\n  SDSPStream* xc_companionRight;\n  SDSPStreamInfo x10_info;\n  float x4c_vol;\n  float m_leftgain, m_rightgain;\n  // DVDFileInfo x50_dvdHandle1;\n  // DVDFileInfo x8c_dvdHandle2;\n  // u32 xc8_streamId = -1; // MusyX stream handle\n  u32 xcc_fileCur = 0;\n  std::unique_ptr<u8[]> xd4_ringBuffer;\n  u32 xd8_ringBytes = 0x11DC0;   // 73152 4sec in ADPCM bytes\n  u32 xdc_ringSamples = 0x1f410; // 128016 4sec in samples\n  s8 xe0_curBuffer = -1;\n  bool xe8_silent = true;\n  u8 xec_readState = 0; // 0: NoRead 1: Read 2: ReadWrap\n\n  std::optional<CDvdFile> m_file;\n  std::array<std::shared_ptr<IDvdRequest>, 2> m_readReqs;\n\n  void ReadBuffer(int buf) {\n    u32 halfSize = xd8_ringBytes / 2;\n    u8* data = xd4_ringBuffer.get() + (buf ? halfSize : 0);\n\n    if (x10_info.x10_loopFlag) {\n      u32 remFileBytes = x10_info.x18_loopEndByte - xcc_fileCur;\n\n      if (remFileBytes < halfSize) {\n        // printf(\"Buffering %d from %d into %d\\n\", remFileBytes, xcc_fileCur + x10_info.x8_headerSize, buf);\n        m_file->AsyncSeekRead(data, remFileBytes, ESeekOrigin::Begin, xcc_fileCur + x10_info.x8_headerSize);\n        xcc_fileCur = x10_info.x14_loopStartByte;\n        u32 remBytes = halfSize - remFileBytes;\n        // printf(\"Loop Buffering %d from %d into %d\\n\", remBytes, xcc_fileCur + x10_info.x8_headerSize, buf);\n        m_readReqs[buf] = m_file->AsyncSeekRead(data + remFileBytes, remBytes, ESeekOrigin::Begin,\n                                                xcc_fileCur + x10_info.x8_headerSize);\n        xcc_fileCur += remBytes;\n      } else {\n        // printf(\"Buffering %d from %d into %d\\n\", halfSize, xcc_fileCur + x10_info.x8_headerSize, buf);\n        m_readReqs[buf] =\n            m_file->AsyncSeekRead(data, halfSize, ESeekOrigin::Begin, xcc_fileCur + x10_info.x8_headerSize);\n        xcc_fileCur += halfSize;\n      }\n    } else {\n      if (xcc_fileCur == x10_info.xc_adpcmBytes) {\n        memset(data, 0, halfSize);\n        return;\n      }\n\n      u32 remFileBytes = x10_info.xc_adpcmBytes - xcc_fileCur;\n\n      if (remFileBytes < halfSize) {\n        // printf(\"Buffering %d from %d into %d\\n\", remFileBytes, xcc_fileCur + x10_info.x8_headerSize, buf);\n        m_readReqs[buf] =\n            m_file->AsyncSeekRead(data, remFileBytes, ESeekOrigin::Begin, xcc_fileCur + x10_info.x8_headerSize);\n        memset(data + remFileBytes, 0, halfSize - remFileBytes);\n        xcc_fileCur = x10_info.xc_adpcmBytes;\n      } else {\n        // printf(\"Buffering %d from %d into %d\\n\", halfSize, xcc_fileCur + x10_info.x8_headerSize, buf);\n        m_readReqs[buf] =\n            m_file->AsyncSeekRead(data, halfSize, ESeekOrigin::Begin, xcc_fileCur + x10_info.x8_headerSize);\n        xcc_fileCur += halfSize;\n      }\n    }\n  }\n\n  bool BufferStream() {\n    if (xec_readState == 0) {\n      ReadBuffer(0);\n      ReadBuffer(1);\n      xec_readState = 1;\n      return false;\n    } else if (xec_readState == 1) {\n      if (m_readReqs[0]->IsComplete()) {\n        xe0_curBuffer = 0;\n        xec_readState = 2;\n        return true;\n      } else {\n        return false;\n      }\n    } else if (xec_readState == 2) {\n      if (xe0_curBuffer == 1 && m_readReqs[0]->IsComplete()) {\n        xe0_curBuffer = 0;\n        ReadBuffer(1);\n        return true;\n      } else if (xe0_curBuffer == 0 && m_readReqs[1]->IsComplete()) {\n        xe0_curBuffer = 1;\n        ReadBuffer(0);\n        return true;\n      }\n    }\n    return false;\n  }\n\n  unsigned m_curSample = 0;\n  unsigned m_totalSamples = 0;\n  s16 m_prev1 = 0;\n  s16 m_prev2 = 0;\n\n//  void preSupplyAudio(boo::IAudioVoice&, double) override {}\n\n//  unsigned decompressChunk(unsigned readToSample, int16_t*& data) {\n//    unsigned startSamp = m_curSample;\n//\n//    auto sampDiv = std::div(int(m_curSample), int(14));\n//    if (sampDiv.rem) {\n//      unsigned samps = DSPDecompressFrameRanged(data, xd4_ringBuffer.get() + sampDiv.quot * 8, x10_info.x1c_coef,\n//                                                &m_prev1, &m_prev2, unsigned(sampDiv.rem), readToSample - m_curSample);\n//      m_curSample += samps;\n//      data += samps;\n//      ++sampDiv.quot;\n//    }\n//\n//    while (m_curSample < readToSample) {\n//      unsigned samps = DSPDecompressFrame(data, xd4_ringBuffer.get() + sampDiv.quot * 8, x10_info.x1c_coef, &m_prev1,\n//                                          &m_prev2, readToSample - m_curSample);\n//      m_curSample += samps;\n//      data += samps;\n//      ++sampDiv.quot;\n//    }\n//\n//    return m_curSample - startSamp;\n//  }\n\n//  size_t supplyAudio(boo::IAudioVoice&, size_t frames, int16_t* data) override {\n//    if (!x0_active) {\n//      memset(data, 0, frames * 2);\n//      return frames;\n//    }\n//\n//    if (xe8_silent) {\n//      StopStream();\n//      memset(data, 0, frames * 2);\n//      return frames;\n//    }\n//\n//    unsigned halfRingSamples = xdc_ringSamples / 2;\n//\n//    size_t remFrames = frames;\n//    while (remFrames) {\n//      if (xec_readState != 2 || (xe0_curBuffer == 0 && m_curSample >= halfRingSamples)) {\n//        if (!BufferStream()) {\n//          memset(data, 0, remFrames * 2);\n//          return frames;\n//        }\n//      }\n//\n//      unsigned readToSample =\n//          std::min(m_curSample + unsigned(remFrames), (m_curSample / halfRingSamples + 1) * halfRingSamples);\n//\n//      if (!x10_info.x10_loopFlag) {\n//        m_totalSamples += remFrames;\n//        size_t fileSamples = x10_info.xc_adpcmBytes * 14 / 8;\n//        if (m_totalSamples >= fileSamples) {\n//          size_t leftover = m_totalSamples - fileSamples;\n//          readToSample -= leftover;\n//          remFrames -= leftover;\n//          memset(data + remFrames, 0, leftover * 2);\n//          StopStream();\n//        }\n//      }\n//\n//      unsigned leftoverSamples = 0;\n//      if (readToSample > xdc_ringSamples) {\n//        leftoverSamples = readToSample - xdc_ringSamples;\n//        readToSample = xdc_ringSamples;\n//      }\n//\n//      remFrames -= decompressChunk(readToSample, data);\n//\n//      if (leftoverSamples) {\n//        BufferStream();\n//        m_curSample = 0;\n//        remFrames -= decompressChunk(leftoverSamples, data);\n//      }\n//    }\n//\n//    return frames;\n//  }\n//  boo::ObjToken<boo::IAudioVoice> m_booVoice;\n\n  void DoAllocateStream() {\n    xd4_ringBuffer.reset(new u8[0x11DC0]);\n    //m_booVoice = CAudioSys::GetVoiceEngine()->allocateNewMonoVoice(32000.0, this);\n  }\n\n  static void Initialize() {\n    for (size_t i = 0; i < g_Streams.size(); ++i) {\n      SDSPStream& stream = g_Streams[i];\n      stream.x0_active = false;\n      stream.xd4_ringBuffer.reset();\n      stream.xd8_ringBytes = 0x11DC0;\n      stream.xdc_ringSamples = 0x1f410;\n      if (i < 2) {\n        stream.x1_oneshot = false;\n        stream.DoAllocateStream();\n      } else {\n        stream.x1_oneshot = true;\n      }\n    }\n  }\n\n  static void FreeAllStreams() {\n//    for (auto& stream : g_Streams) {\n//      stream.m_booVoice.reset();\n//      stream.x0_active = false;\n//      for (auto& request : stream.m_readReqs) {\n//        if (request) {\n//          request->PostCancelRequest();\n//          request.reset();\n//        }\n//      }\n//      stream.xd4_ringBuffer.reset();\n//      stream.m_file = std::nullopt;\n//    }\n  }\n\n  static s32 PickFreeStream(SDSPStream*& streamOut, bool oneshot) {\n    for (auto& stream : g_Streams) {\n      if (stream.x0_active || stream.x1_oneshot != oneshot) {\n        continue;\n      }\n      stream.x0_active = true;\n      stream.x4_ownerId = ++s_HandleCounter2;\n      if (stream.x4_ownerId == -1) {\n        stream.x4_ownerId = ++s_HandleCounter2;\n      }\n      stream.x8_stereoLeft = nullptr;\n      stream.xc_companionRight = nullptr;\n      streamOut = &stream;\n      return stream.x4_ownerId;\n    }\n    return -1;\n  }\n\n  static s32 FindStreamIdx(s32 id) {\n    for (size_t i = 0; i < g_Streams.size(); ++i) {\n      const SDSPStream& stream = g_Streams[i];\n      if (stream.x4_ownerId == id) {\n        return s32(i);\n      }\n    }\n    return -1;\n  }\n\n  void UpdateStreamVolume(float vol) {\n//    x4c_vol = vol;\n//    if (!x0_active || xe8_silent) {\n//      return;\n//    }\n//    std::array<float, 8> coefs{};\n//    coefs[size_t(boo::AudioChannel::FrontLeft)] = m_leftgain * vol;\n//    coefs[size_t(boo::AudioChannel::FrontRight)] = m_rightgain * vol;\n//    m_booVoice->setMonoChannelLevels(nullptr, coefs.data(), true);\n  }\n\n  static void UpdateVolume(s32 id, float vol) {\n//    s32 idx = FindStreamIdx(id);\n//    if (idx == -1)\n//      return;\n//\n//    SDSPStream& stream = g_Streams[idx];\n//    stream.UpdateStreamVolume(vol);\n//    if (SDSPStream* left = stream.x8_stereoLeft)\n//      left->UpdateStreamVolume(vol);\n//    if (SDSPStream* right = stream.xc_companionRight)\n//      right->UpdateStreamVolume(vol);\n  }\n\n  void SilenceStream() {\n    if (!x0_active || xe8_silent) {\n      return;\n    }\n    constexpr std::array<float, 8> coefs{};\n    //m_booVoice->setMonoChannelLevels(nullptr, coefs.data(), true);\n    xe8_silent = true;\n    x0_active = false;\n  }\n\n  static void Silence(s32 id) {\n    s32 idx = FindStreamIdx(id);\n    if (idx == -1)\n      return;\n\n    SDSPStream& stream = g_Streams[idx];\n    stream.SilenceStream();\n    if (SDSPStream* left = stream.x8_stereoLeft)\n      left->SilenceStream();\n    if (SDSPStream* right = stream.xc_companionRight)\n      right->SilenceStream();\n  }\n\n  void StopStream() {\n    x0_active = false;\n    //m_booVoice->stop();\n    m_file = std::nullopt;\n  }\n\n  static bool IsStreamActive(s32 id) {\n    s32 idx = FindStreamIdx(id);\n    if (idx == -1)\n      return false;\n\n    SDSPStream& stream = g_Streams[idx];\n    return stream.x0_active;\n  }\n\n  static bool IsStreamAvailable(s32 id) {\n    s32 idx = FindStreamIdx(id);\n    if (idx == -1)\n      return false;\n\n    SDSPStream& stream = g_Streams[idx];\n    return !stream.x0_active;\n  }\n\n  static s32 AllocateMono(const SDSPStreamInfo& info, float vol, bool oneshot) {\n    SDSPStream* stream;\n    s32 id = PickFreeStream(stream, oneshot);\n    if (id == -1)\n      return -1;\n\n    /* -3dB pan law for mono */\n    stream->AllocateStream(info, vol, 0.707f, 0.707f);\n    return id;\n  }\n\n  static s32 AllocateStereo(const SDSPStreamInfo& linfo, const SDSPStreamInfo& rinfo, float vol, bool oneshot) {\n    SDSPStream* lstream;\n    s32 lid = PickFreeStream(lstream, oneshot);\n    if (lid == -1)\n      return -1;\n\n    SDSPStream* rstream;\n    if (PickFreeStream(rstream, oneshot) == -1)\n      return -1;\n\n    rstream->x8_stereoLeft = lstream;\n    lstream->xc_companionRight = rstream;\n\n    lstream->AllocateStream(linfo, vol, 1.f, 0.f);\n    rstream->AllocateStream(rinfo, vol, 0.f, 1.f);\n    return lid;\n  }\n\n  void AllocateStream(const SDSPStreamInfo& info, float vol, float left, float right) {\n    x10_info = info;\n    m_file.emplace(x10_info.x0_fileName);\n    if (!xd4_ringBuffer) {\n      DoAllocateStream();\n    }\n    for (auto& request : m_readReqs) {\n      if (request) {\n        request->PostCancelRequest();\n        request.reset();\n      }\n    }\n    x4c_vol = vol;\n    m_leftgain = left;\n    m_rightgain = right;\n    xe8_silent = false;\n    xec_readState = 0;\n    xe0_curBuffer = -1;\n    xd8_ringBytes = 0x11DC0;\n    xdc_ringSamples = 0x1f410;\n    xcc_fileCur = 0;\n    m_curSample = 0;\n    m_totalSamples = 0;\n    m_prev1 = 0;\n    m_prev2 = 0;\n    memset(xd4_ringBuffer.get(), 0, 0x11DC0);\n    //m_booVoice->resetSampleRate(info.x4_sampleRate);\n    //m_booVoice->start();\n    UpdateStreamVolume(vol);\n  }\n\n  static std::array<SDSPStream, 4> g_Streams;\n};\n\nstd::array<SDSPStream, 4> SDSPStream::g_Streams{};\n\nclass CDSPStreamManager {\n  friend struct SDSPStreamInfo;\n\npublic:\n  enum class EState { Looping, Oneshot, Preparing };\n\nprivate:\n  dspadpcm_header x0_header;\n  std::string x60_fileName; // arg1\n  bool x70_24_unclaimed : 1 = true;\n  bool x70_25_headerReadCancelled : 1 = false;\n  u8 x70_26_headerReadState : 2 = 0; // 0: not read 1: reading 2: read\n  s8 x71_companionRight = -1;\n  s8 x72_companionLeft = -1;\n  float x73_volume = 0.f;\n  bool x74_oneshot = false;\n  s32 x78_handleId = -1; // arg2\n  s32 x7c_streamId = -1;\n  std::shared_ptr<IDvdRequest> m_dvdReq;\n  // DVDFileInfo x80_dvdHandle;\n  static std::array<CDSPStreamManager, 4> g_Streams;\n\npublic:\n  CDSPStreamManager() = default;\n\n  CDSPStreamManager(std::string_view fileName, s32 handle, float volume, bool oneshot)\n  : x60_fileName(fileName)\n  , x70_24_unclaimed(!CDvdFile::FileExists(fileName))\n  , x73_volume(volume)\n  , x74_oneshot(oneshot)\n  , x78_handleId(handle) {}\n\n  static s32 FindUnclaimedStreamIdx() {\n    for (size_t i = 0; i < g_Streams.size(); ++i) {\n      const CDSPStreamManager& stream = g_Streams[i];\n      if (stream.x70_24_unclaimed) {\n        return s32(i);\n      }\n    }\n    return -1;\n  }\n\n  static bool FindUnclaimedStereoPair(s32& left, s32& right) {\n    const s32 idx = FindUnclaimedStreamIdx();\n\n    for (size_t i = 0; i < g_Streams.size(); ++i) {\n      CDSPStreamManager& stream = g_Streams[i];\n      if (stream.x70_24_unclaimed && idx != s32(i)) {\n        left = idx;\n        right = s32(i);\n        return true;\n      }\n    }\n\n    return false;\n  }\n\n  static s32 FindClaimedStreamIdx(s32 handle) {\n    for (size_t i = 0; i < g_Streams.size(); ++i) {\n      const CDSPStreamManager& stream = g_Streams[i];\n      if (!stream.x70_24_unclaimed && stream.x78_handleId == handle) {\n        return i;\n      }\n    }\n    return -1;\n  }\n\n  static s32 GetFreeHandleId() {\n    s32 handle;\n    bool good;\n    do {\n      good = true;\n      handle = ++s_HandleCounter;\n      if (handle == -1) {\n        good = false;\n        continue;\n      }\n\n      for (auto& stream : g_Streams) {\n        if (!stream.x70_24_unclaimed && stream.x78_handleId == handle) {\n          good = false;\n          break;\n        }\n      }\n\n    } while (!good);\n\n    return handle;\n  }\n\n  static EState GetStreamState(s32 handle) {\n    s32 idx = FindClaimedStreamIdx(handle);\n    if (idx == -1)\n      return EState::Oneshot;\n\n    CDSPStreamManager& stream = g_Streams[idx];\n    switch (stream.x70_26_headerReadState) {\n    case 0:\n      return EState::Oneshot;\n    case 2:\n      return EState(!stream.x0_header.xc_loop_flag);\n    default:\n      return EState::Preparing;\n    }\n  }\n\n  static bool CanStop(s32 handle) {\n    s32 idx = FindClaimedStreamIdx(handle);\n    if (idx == -1)\n      return true;\n\n    CDSPStreamManager& stream = g_Streams[idx];\n    if (stream.x70_26_headerReadState == 1)\n      return false;\n\n    if (stream.x7c_streamId == -1)\n      return true;\n\n    return !SDSPStream::IsStreamActive(stream.x7c_streamId);\n  }\n\n  static bool IsStreamAvailable(s32 handle) {\n    s32 idx = FindClaimedStreamIdx(handle);\n    if (idx == -1)\n      return false;\n\n    CDSPStreamManager& stream = g_Streams[idx];\n    if (stream.x70_26_headerReadState == 1)\n      return false;\n\n    if (stream.x7c_streamId == -1)\n      return false;\n\n    return SDSPStream::IsStreamAvailable(stream.x7c_streamId);\n  }\n\n  static void AllocateStream(s32 idx) {\n    CDSPStreamManager& stream = g_Streams[idx];\n    SDSPStreamInfo info(stream);\n\n    if (stream.x71_companionRight == -1) {\n      /* Mono */\n      if (!stream.x70_25_headerReadCancelled)\n        stream.x7c_streamId = SDSPStream::AllocateMono(info, stream.x73_volume, stream.x74_oneshot);\n      if (stream.x7c_streamId == -1)\n        stream = CDSPStreamManager();\n    } else {\n      /* Stereo */\n      CDSPStreamManager& rstream = g_Streams[stream.x71_companionRight];\n      SDSPStreamInfo rinfo(rstream);\n\n      if (!stream.x70_25_headerReadCancelled)\n        stream.x7c_streamId = SDSPStream::AllocateStereo(info, rinfo, stream.x73_volume, stream.x74_oneshot);\n      if (stream.x7c_streamId == -1) {\n        stream = CDSPStreamManager();\n        rstream = CDSPStreamManager();\n      }\n    }\n  }\n\n  void HeaderReadComplete() {\n    s32 selfIdx = -1;\n    for (size_t i = 0; i < g_Streams.size(); ++i) {\n      if (this == &g_Streams[i]) {\n        selfIdx = s32(i);\n        break;\n      }\n    }\n\n    if (x70_24_unclaimed || selfIdx == -1) {\n      *this = CDSPStreamManager();\n      return;\n    }\n\n    x70_26_headerReadState = 2;\n\n    s32 companion = -1;\n    if (x72_companionLeft != -1)\n      companion = x72_companionLeft;\n    else if (x71_companionRight != -1)\n      companion = x71_companionRight;\n\n    if (companion != -1) {\n      /* Stereo */\n      CDSPStreamManager& companionStream = g_Streams[companion];\n      if (companionStream.x70_24_unclaimed || companionStream.x70_26_headerReadState == 0 ||\n          (companionStream.x71_companionRight != selfIdx && companionStream.x72_companionLeft != selfIdx)) {\n        /* No consistent companion available */\n        *this = CDSPStreamManager();\n        return;\n      }\n\n      /* Companion is pending; its completion will continue */\n      if (companionStream.x70_26_headerReadState == 1)\n        return;\n\n      /* Use whichever stream is the left channel */\n      if (companionStream.x71_companionRight != -1)\n        AllocateStream(companion);\n      else\n        AllocateStream(selfIdx);\n    } else {\n      /* Mono */\n      AllocateStream(selfIdx);\n    }\n  }\n\n  static void PollHeaderReadCompletions() {\n    for (auto& stream : g_Streams) {\n      if (stream.m_dvdReq && stream.m_dvdReq->IsComplete()) {\n        stream.m_dvdReq.reset();\n        stream.HeaderReadComplete();\n      }\n    }\n  }\n\n  static bool StartMonoHeaderRead(CDSPStreamManager& stream) {\n    if (stream.x70_26_headerReadState != 0 || stream.x70_24_unclaimed)\n      return false;\n\n    CDvdFile file(stream.x60_fileName);\n    if (!file)\n      return false;\n\n    stream.x70_26_headerReadState = 1;\n    stream.m_dvdReq = file.AsyncRead(&stream.x0_header, sizeof(dspadpcm_header));\n    return true;\n  }\n\n  static bool StartStereoHeaderRead(CDSPStreamManager& lstream, CDSPStreamManager& rstream) {\n    if (lstream.x70_26_headerReadState != 0 || lstream.x70_24_unclaimed || rstream.x70_26_headerReadState != 0 ||\n        rstream.x70_24_unclaimed)\n      return false;\n\n    CDvdFile lfile(lstream.x60_fileName);\n    if (!lfile)\n      return false;\n\n    CDvdFile rfile(rstream.x60_fileName);\n    if (!rfile)\n      return false;\n\n    lstream.x70_26_headerReadState = 1;\n    rstream.x70_26_headerReadState = 1;\n\n    lstream.m_dvdReq = lfile.AsyncRead(&lstream.x0_header, sizeof(dspadpcm_header));\n    rstream.m_dvdReq = rfile.AsyncRead(&rstream.x0_header, sizeof(dspadpcm_header));\n    return true;\n  }\n\n  void WaitForReadCompletion() {\n    if (std::shared_ptr<IDvdRequest> req = m_dvdReq)\n      req->WaitUntilComplete();\n    m_dvdReq.reset();\n  }\n\n  static s32 StartStreaming(std::string_view fileName, float volume, bool oneshot) {\n    auto pipePos = fileName.find('|');\n    if (pipePos == std::string::npos) {\n      /* Mono stream */\n      s32 idx = FindUnclaimedStreamIdx();\n      if (idx == -1)\n        return -1;\n\n      s32 handle = GetFreeHandleId();\n      CDSPStreamManager tmpStream(fileName, handle, volume, oneshot);\n      if (tmpStream.x70_24_unclaimed)\n        return -1;\n\n      CDSPStreamManager& stream = g_Streams[idx];\n      stream = tmpStream;\n\n      if (!StartMonoHeaderRead(stream)) {\n        stream.x70_25_headerReadCancelled = true;\n        stream.WaitForReadCompletion();\n        stream = CDSPStreamManager();\n        return -1;\n      }\n\n      return handle;\n    } else {\n      /* Stereo stream */\n      s32 leftIdx = 0;\n      s32 rightIdx = 0;\n      if (!FindUnclaimedStereoPair(leftIdx, rightIdx))\n        return -1;\n\n      std::string leftFile(fileName.begin(), fileName.begin() + pipePos);\n      std::string rightFile(fileName.begin() + pipePos + 1, fileName.end());\n\n      s32 leftHandle = GetFreeHandleId();\n      s32 rightHandle = GetFreeHandleId();\n      CDSPStreamManager tmpLeftStream(leftFile, leftHandle, volume, oneshot);\n      CDSPStreamManager tmpRightStream(rightFile, rightHandle, volume, oneshot);\n      if (tmpLeftStream.x70_24_unclaimed || tmpRightStream.x70_24_unclaimed)\n        return -1;\n\n      tmpLeftStream.x71_companionRight = s8(rightIdx);\n      tmpRightStream.x72_companionLeft = s8(leftIdx);\n\n      CDSPStreamManager& leftStream = g_Streams[leftIdx];\n      CDSPStreamManager& rightStream = g_Streams[rightIdx];\n      leftStream = tmpLeftStream;\n      rightStream = tmpRightStream;\n\n      if (!StartStereoHeaderRead(leftStream, rightStream)) {\n        leftStream.x70_25_headerReadCancelled = true;\n        rightStream.x70_25_headerReadCancelled = true;\n        leftStream.WaitForReadCompletion();\n        leftStream.WaitForReadCompletion();\n        leftStream = CDSPStreamManager();\n        rightStream = CDSPStreamManager();\n        return -1;\n      }\n\n      return leftHandle;\n    }\n  }\n\n  static void StopStreaming(s32 handle) {\n    s32 idx = FindClaimedStreamIdx(handle);\n    if (idx == -1)\n      return;\n\n    CDSPStreamManager& stream = g_Streams[idx];\n    if (stream.x70_24_unclaimed)\n      return;\n\n    if (stream.x70_26_headerReadState == 1) {\n      stream.x70_25_headerReadCancelled = true;\n      return;\n    }\n\n    if (stream.x71_companionRight != -1)\n      g_Streams[stream.x71_companionRight] = CDSPStreamManager();\n\n    SDSPStream::Silence(stream.x7c_streamId);\n\n    stream = CDSPStreamManager();\n  }\n\n  static void UpdateVolume(s32 handle, float volume) {\n    s32 idx = FindClaimedStreamIdx(handle);\n    if (idx == -1)\n      return;\n\n    CDSPStreamManager& stream = g_Streams[idx];\n    stream.x73_volume = volume;\n    if (stream.x7c_streamId == -1)\n      return;\n\n    SDSPStream::UpdateVolume(stream.x7c_streamId, volume);\n  }\n\n  static void Initialize() {\n    SDSPStream::Initialize();\n    for (auto& stream : g_Streams) {\n      stream = CDSPStreamManager();\n    }\n  }\n\n  static void Shutdown() {\n    SDSPStream::FreeAllStreams();\n    for (auto& stream : g_Streams) {\n      stream = CDSPStreamManager();\n    }\n  }\n};\n\nstd::array<CDSPStreamManager, 4> CDSPStreamManager::g_Streams{};\n\nSDSPStreamInfo::SDSPStreamInfo(const CDSPStreamManager& stream) {\n  x0_fileName = stream.x60_fileName.c_str();\n  x4_sampleRate = SBig(stream.x0_header.x8_sample_rate);\n  xc_adpcmBytes = (SBig(stream.x0_header.x4_num_nibbles) / 2) & 0x7FFFFFE0;\n\n  if (stream.x0_header.xc_loop_flag) {\n    u32 loopStartNibble = SBig(stream.x0_header.x10_loop_start_nibble);\n    u32 loopEndNibble = SBig(stream.x0_header.x14_loop_end_nibble);\n    x10_loopFlag = true;\n    x14_loopStartByte = (loopStartNibble / 2) & 0x7FFFFFE0;\n    x18_loopEndByte = std::min((loopEndNibble / 2) & 0x7FFFFFE0, xc_adpcmBytes);\n  } else {\n    x10_loopFlag = false;\n    x14_loopStartByte = 0;\n    x18_loopEndByte = 0;\n  }\n\n  for (int i = 0; i < 8; ++i) {\n    x1c_coef[i][0] = SBig(stream.x0_header.x1c_coef[i][0]);\n    x1c_coef[i][1] = SBig(stream.x0_header.x1c_coef[i][1]);\n  }\n}\n\nenum class EPlayerState { Stopped, FadeIn, Playing, FadeOut, FadeOutNoStop };\n\nstruct SDSPPlayer {\n  std::string x0_fileName;\n  EPlayerState x10_playState = EPlayerState::Stopped;\n  float x14_volume = 0.f;\n  float x18_fadeIn = 0.f;\n  float x1c_fadeOut = 0.f;\n  s32 x20_internalHandle = -1;\n  float x24_fadeFactor = 0.f;\n  bool x28_music = true;\n\n  SDSPPlayer() = default;\n  SDSPPlayer(EPlayerState playing, std::string_view fileName, float volume, float fadeIn, float fadeOut, s32 handle,\n             bool music)\n  : x0_fileName(fileName)\n  , x10_playState(playing)\n  , x14_volume(volume)\n  , x18_fadeIn(fadeIn)\n  , x1c_fadeOut(fadeOut)\n  , x20_internalHandle(handle)\n  , x28_music(music) {}\n};\n\nusing PlayerArray = std::array<SDSPPlayer, 2>;\nstatic PlayerArray s_Players;       // looping, oneshot\nstatic PlayerArray s_QueuedPlayers; // looping, oneshot\n\nfloat CStreamAudioManager::GetTargetDSPVolume(float fileVol, bool music) {\n  if (music)\n    return g_MusicUnmute ? (g_MusicVolume * fileVol / 127.f) : 0.f;\n  else\n    return g_SfxUnmute ? (g_SfxVolume * fileVol / 127.f) : 0.f;\n}\n\nvoid CStreamAudioManager::Start(bool oneshot, std::string_view fileName, float volume, bool music, float fadeIn,\n                                float fadeOut) {\n  SDSPPlayer& p = s_Players[oneshot];\n  SDSPPlayer& qp = s_QueuedPlayers[oneshot];\n\n  if (p.x10_playState != EPlayerState::Stopped && !CStringExtras::CompareCaseInsensitive(fileName, p.x0_fileName)) {\n    /* Enque new stream */\n    qp = SDSPPlayer(EPlayerState::FadeIn, fileName, volume, fadeIn, fadeOut, -1, music);\n    Stop(oneshot, p.x0_fileName);\n  } else if (p.x10_playState != EPlayerState::Stopped) {\n    /* Fade existing stream back in */\n    p.x18_fadeIn = fadeIn;\n    p.x1c_fadeOut = fadeOut;\n    p.x14_volume = volume;\n    if (p.x18_fadeIn <= FLT_EPSILON) {\n      CDSPStreamManager::UpdateVolume(p.x20_internalHandle, GetTargetDSPVolume(p.x14_volume, p.x28_music));\n      p.x24_fadeFactor = 1.f;\n      p.x10_playState = EPlayerState::Playing;\n    } else {\n      p.x10_playState = EPlayerState::FadeIn;\n    }\n  } else {\n    /* Start new stream */\n    EPlayerState state;\n    float vol;\n    if (fadeIn > 0.f) {\n      state = EPlayerState::FadeIn;\n      vol = 0.f;\n    } else {\n      state = EPlayerState::Playing;\n      vol = volume;\n    }\n\n    s32 handle = CDSPStreamManager::StartStreaming(fileName, GetTargetDSPVolume(vol, music), oneshot);\n    if (handle != -1)\n      p = SDSPPlayer(state, fileName, volume, fadeIn, fadeOut, handle, music);\n  }\n}\n\nvoid CStreamAudioManager::Stop(bool oneshot, std::string_view fileName) {\n  SDSPPlayer& p = s_Players[oneshot];\n  SDSPPlayer& qp = s_QueuedPlayers[oneshot];\n\n  if (CStringExtras::CompareCaseInsensitive(fileName, qp.x0_fileName)) {\n    /* Cancel enqueued file */\n    qp = SDSPPlayer();\n  } else if (CStringExtras::CompareCaseInsensitive(fileName, p.x0_fileName) && p.x20_internalHandle != -1 &&\n             p.x10_playState != EPlayerState::Stopped) {\n    /* Fade out or stop */\n    if (p.x1c_fadeOut <= FLT_EPSILON)\n      StopStreaming(oneshot);\n    else\n      p.x10_playState = EPlayerState::FadeOut;\n  }\n}\n\nvoid CStreamAudioManager::FadeBackIn(bool oneshot, float fadeTime) {\n  SDSPPlayer& p = s_Players[oneshot];\n  if (p.x10_playState == EPlayerState::Stopped || p.x10_playState == EPlayerState::Playing)\n    return;\n  p.x18_fadeIn = fadeTime;\n  p.x10_playState = EPlayerState::FadeIn;\n}\n\nvoid CStreamAudioManager::TemporaryFadeOut(bool oneshot, float fadeTime) {\n  SDSPPlayer& p = s_Players[oneshot];\n  if (p.x10_playState == EPlayerState::FadeOut || p.x10_playState == EPlayerState::Stopped)\n    return;\n  p.x1c_fadeOut = fadeTime;\n  p.x10_playState = EPlayerState::FadeOutNoStop;\n}\n\nvoid CStreamAudioManager::StopStreaming(bool oneshot) {\n  SDSPPlayer& p = s_Players[oneshot];\n  p.x10_playState = EPlayerState::Stopped;\n  CDSPStreamManager::StopStreaming(p.x20_internalHandle);\n  p.x24_fadeFactor = 0.f;\n  p.x20_internalHandle = -1;\n}\n\nvoid CStreamAudioManager::UpdateDSP(bool oneshot, float dt) {\n  SDSPPlayer& p = s_Players[oneshot];\n\n  if (p.x10_playState == EPlayerState::Stopped) {\n    SDSPPlayer& qp = s_QueuedPlayers[oneshot];\n    if (qp.x10_playState != EPlayerState::Stopped) {\n      Start(oneshot, qp.x0_fileName, qp.x14_volume, qp.x28_music, qp.x18_fadeIn, qp.x1c_fadeOut);\n      qp = SDSPPlayer();\n    }\n  } else {\n    if (p.x10_playState != EPlayerState::Stopped &&\n        CDSPStreamManager::GetStreamState(p.x20_internalHandle) == CDSPStreamManager::EState::Oneshot &&\n        CDSPStreamManager::CanStop(p.x20_internalHandle)) {\n      StopStreaming(oneshot);\n      return;\n    }\n\n    if ((p.x10_playState != EPlayerState::FadeIn && p.x10_playState != EPlayerState::FadeOut &&\n         p.x10_playState != EPlayerState::FadeOutNoStop)) {\n      if (p.x10_playState == EPlayerState::Playing)\n        CDSPStreamManager::UpdateVolume(p.x20_internalHandle, GetTargetDSPVolume(p.x14_volume, p.x28_music));\n      return;\n    }\n\n    if (p.x10_playState == EPlayerState::FadeIn) {\n      float newFadeFactor = p.x24_fadeFactor + dt / p.x18_fadeIn;\n      if (newFadeFactor >= 1.f) {\n        p.x24_fadeFactor = 1.f;\n        p.x10_playState = EPlayerState::Playing;\n      } else {\n        p.x24_fadeFactor = newFadeFactor;\n      }\n    } else if (p.x10_playState == EPlayerState::FadeOut || p.x10_playState == EPlayerState::FadeOutNoStop) {\n      float newFadeFactor = p.x24_fadeFactor - dt / p.x1c_fadeOut;\n      if (newFadeFactor <= 0.f) {\n        if (p.x10_playState == EPlayerState::FadeOutNoStop) {\n          p.x24_fadeFactor = 0.f;\n        } else {\n          StopStreaming(oneshot);\n          return;\n        }\n      } else {\n        p.x24_fadeFactor = newFadeFactor;\n      }\n    }\n\n    CDSPStreamManager::UpdateVolume(p.x20_internalHandle,\n                                    GetTargetDSPVolume(p.x14_volume * p.x24_fadeFactor, p.x28_music));\n  }\n}\n\nvoid CStreamAudioManager::UpdateDSPStreamers(float dt) {\n  UpdateDSP(false, dt);\n  UpdateDSP(true, dt);\n}\n\nvoid CStreamAudioManager::StopAllStreams() {\n  for (size_t i = 0; i < s_Players.size(); ++i) {\n    StopStreaming(bool(i));\n    SDSPPlayer& p = s_Players[i];\n    SDSPPlayer& qp = s_QueuedPlayers[i];\n    p = SDSPPlayer();\n    qp = SDSPPlayer();\n  }\n}\n\nvoid CStreamAudioManager::Update(float dt) {\n  CDSPStreamManager::PollHeaderReadCompletions();\n  UpdateDSPStreamers(dt);\n}\n\nvoid CStreamAudioManager::StopAll() { StopAllStreams(); }\n\nvoid CStreamAudioManager::SetMusicUnmute(bool unmute) { g_MusicUnmute = unmute; }\n\nvoid CStreamAudioManager::SetSfxVolume(u8 volume) { g_SfxVolume = std::min(volume, u8(127)); }\n\nvoid CStreamAudioManager::SetMusicVolume(u8 volume) { g_MusicVolume = std::min(volume, u8(127)); }\n\nvoid CStreamAudioManager::Initialize() { CDSPStreamManager::Initialize(); }\n\nvoid CStreamAudioManager::StopOneShot() {\n  CStreamAudioManager::StopStreaming(true);\n  SDSPPlayer& p = s_Players[1];\n  p = SDSPPlayer();\n  SDSPPlayer& qp = s_QueuedPlayers[1];\n  qp = SDSPPlayer();\n}\n\nvoid CStreamAudioManager::Shutdown() { CDSPStreamManager::Shutdown(); }\n\nu8 CStreamAudioManager::g_MusicVolume = 0x7f;\nu8 CStreamAudioManager::g_SfxVolume = 0x7f;\nbool CStreamAudioManager::g_MusicUnmute = true;\nbool CStreamAudioManager::g_SfxUnmute = true;\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Audio/CStreamAudioManager.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/GCNTypes.hpp\"\n\nnamespace metaforce {\n\nclass CStreamAudioManager {\n  static u8 g_MusicVolume;\n  static u8 g_SfxVolume;\n  static bool g_MusicUnmute;\n  static bool g_SfxUnmute;\n\n  static float GetTargetDSPVolume(float fileVol, bool music);\n  static void StopStreaming(bool oneshot);\n  static void UpdateDSP(bool oneshot, float dt);\n  static void UpdateDSPStreamers(float dt);\n  static void StopAllStreams();\n\npublic:\n  static void Start(bool oneshot, std::string_view fileName, float volume, bool music, float fadeIn, float fadeOut);\n  static void Stop(bool oneshot, std::string_view fileName);\n  static void FadeBackIn(bool oneshot, float fadeTime);\n  static void TemporaryFadeOut(bool oneshot, float fadeTime);\n  static void Update(float dt);\n  static void StopAll();\n  static void SetMusicUnmute(bool unmute);\n  static void SetSfxVolume(u8 volume);\n  static void SetMusicVolume(u8 volume);\n\n  static void Initialize();\n  static void StopOneShot();\n  static void Shutdown();\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Audio/SFX/Atomic.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: Atomic\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPAtomic 1\n\n#define SFXelu_a_elec_lp_00 42\n#define SFXat2_b_fly_lp_00 43\n#define SFXatm_b_fly_lp_00 44\n#define SFXatm_b_fly_lp_01 45\n#define SFXatm_a_bombdrp_00 46\n#define SFXsfx002F 47\n#define SFXsfx0030 48\n#define SFXsfx0031 49\n#define SFXsfx0032 50\n#define SFXsfx0033 51\n#define SFXsfx0034 52\n#define SFXsfx0035 53\n#define SFXsfx0036 54\n#define SFXsfx0037 55\n#define SFXsfx0038 56\n#define SFXsfx0039 57\n#define SFXsfx003A 58\n#define SFXsfx003B 59\n"
  },
  {
    "path": "Runtime/Audio/SFX/BetaBeetle.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: BetaBeetle\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPBetaBeetle 2\n\n#define SFXsfx003C 60\n#define SFXsfx003D 61\n#define SFXsfx003E 62\n#define SFXsfx003F 63\n#define SFXsfx0040 64\n#define SFXsfx0041 65\n#define SFXsfx0042 66\n#define SFXsfx0043 67\n#define SFXsfx0044 68\n#define SFXsfx0045 69\n#define SFXsfx0046 70\n#define SFXsfx0047 71\n#define SFXsfx0048 72\n#define SFXsfx0049 73\n#define SFXsfx004A 74\n#define SFXsfx004B 75\n#define SFXsfx004C 76\n#define SFXsfx004D 77\n#define SFXsfx004E 78\n#define SFXsfx004F 79\n#define SFXsfx0050 80\n#define SFXsfx0051 81\n#define SFXsfx0052 82\n#define SFXsfx0053 83\n#define SFXsfx0054 84\n#define SFXsfx0055 85\n#define SFXsfx0056 86\n#define SFXsfx0057 87\n#define SFXsfx0058 88\n#define SFXsfx0059 89\n#define SFXsfx005A 90\n#define SFXsfx005B 91\n"
  },
  {
    "path": "Runtime/Audio/SFX/Bird.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: Bird\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPBird 3\n\n#define SFXsfx005C 92\n#define SFXsfx005D 93\n#define SFXsfx005E 94\n#define SFXsfx005F 95\n#define SFXsfx0060 96\n#define SFXsfx0061 97\n#define SFXsfx0062 98\n"
  },
  {
    "path": "Runtime/Audio/SFX/BloodFlower.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: BloodFlower\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPBloodFlower 4\n\n#define SFXblf_a_btmspit_00 99\n#define SFXblf_a_bulb_00 100\n#define SFXsfx0065 101\n#define SFXsfx0066 102\n#define SFXsfx0067 103\n#define SFXblf_b_active_00 104\n#define SFXblf_b_active_01 105\n#define SFXsfx006A 106\n#define SFXblf_b_breathe_00 107\n#define SFXsfx006C 108\n#define SFXsfx006D 109\n#define SFXsfx006E 110\n#define SFXsfx006F 111\n#define SFXblf_r_death_00 112\n#define SFXblf_r_death_01 113\n#define SFXblf_r_impact_00 114\n#define SFXfir_x_crispfire6voice_lp_00 115\n#define SFXsfx0074 116\n#define SFXsfx0075 117\n#define SFXsfx0076 118\n#define SFXsfx0077 119\n"
  },
  {
    "path": "Runtime/Audio/SFX/Burrower.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: Burrower\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPBurrower 5\n\n#define SFXbur_a_attack_00 120\n#define SFXbur_b_burrow_lp_00 121\n#define SFXbur_b_idle_00 122\n#define SFXbur_b_idle_01 123\n#define SFXbur_b_walk_00 124\n#define SFXbur_b_walk_01 125\n#define SFXbur_b_walk_02 126\n#define SFXbur_r_death_00 127\n#define SFXsfx0080 128\n#define SFXsfx0081 129\n#define SFXsfx0082 130\n#define SFXsfx0083 131\n#define SFXsfx0084 132\n#define SFXsfx0085 133\n#define SFXsfx0086 134\n#define SFXsfx0087 135\n#define SFXsfx0088 136\n#define SFXsfx0089 137\n#define SFXsfx008A 138\n#define SFXsfx008B 139\n"
  },
  {
    "path": "Runtime/Audio/SFX/ChozoGhost.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: ChozoGhost\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPChozoGhost 6\n\n#define SFXchg_a_dball_00 140\n#define SFXchg_a_dcharge_00 141\n#define SFXchg_a_dfire_00 142\n#define SFXsfx008F 143\n#define SFXsfx0090 144\n#define SFXsfx0091 145\n#define SFXsfx0092 146\n#define SFXsfx0093 147\n#define SFXsfx0094 148\n#define SFXchg_a_pball_00 149\n#define SFXchg_a_pfire_00 150\n#define SFXsfx0097 151\n#define SFXsfx0098 152\n#define SFXsfx0099 153\n#define SFXsfx009A 154\n#define SFXchg_b_fadein_00 155\n#define SFXsfx009C 156\n#define SFXchg_b_float_00 157\n#define SFXchg_b_growl_00 158\n#define SFXchg_b_jump_00 159\n#define SFXsfx00A0 160\n#define SFXsfx00A1 161\n#define SFXsfx00A2 162\n#define SFXsfx00A3 163\n#define SFXsfx00A4 164\n#define SFXchg_r_death_00 165\n#define SFXchg_r_hit_00 166\n#define SFXchg_a_pcharge_00 167\n#define SFXsfx00A8 168\n#define SFXsfx00A9 169\n#define SFXchg_b_growl_01 170\n#define SFXchg_b_scrape_00 171\n#define SFXchg_r_death_01 172\n#define SFXsfx00AD 173\n#define SFXchg_b_growl_03 174\n#define SFXchg_b_growl_04 175\n#define SFXsfx00B0 176\n#define SFXchg_b_voxalert_00 177\n#define SFXsfx00B2 178\n#define SFXchg_b_warpin_00 179\n#define SFXsfx00B4 180\n#define SFXsfx00B5 181\n#define SFXsfx00B6 182\n#define SFXsfx00B7 183\n#define SFXsfx00B8 184\n#define SFXsfx00B9 185\n#define SFXsfx00BA 186\n#define SFXsfx00BB 187\n#define SFXsfx00BC 188\n#define SFXsfx00BD 189\n#define SFXsfx00BE 190\n#define SFXsfx00BF 191\n"
  },
  {
    "path": "Runtime/Audio/SFX/ChubbWeed.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: ChubbWeed\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPChubbWeed 7\n\n#define SFXchb_r_scream_00 192\n#define SFXchb_r_alert_00_lp 193\n#define SFXsfx00C2 194\n#define SFXsfx00C3 195\n#define SFXsfx00C4 196\n#define SFXsfx00C5 197\n#define SFXsfx00C6 198\n"
  },
  {
    "path": "Runtime/Audio/SFX/CineBoots.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: CineBoots\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPCineBoots 59\n\n#define SFXci7_x_jump_00 2972\n#define SFXsja_c_electric_lp_00 2973\n#define SFXsfx0B9E 2974\n#define SFXsfx0B9F 2975\n#define SFXsfx0BA0 2976\n#define SFXsfx0BA1 2977\n#define SFXsfx0BA2 2978\n#define SFXsfx0BA3 2979\n#define SFXsfx0BA4 2980\n#define SFXsfx0BA5 2981\n#define SFXsfx0BA6 2982\n#define SFXsfx0BA7 2983\n#define SFXsfx0BA8 2984\n#define SFXsfx0BA9 2985\n#define SFXsfx0BAA 2986\n#define SFXsfx0BAB 2987\n#define SFXsfx0BAC 2988\n#define SFXsfx0BAD 2989\n#define SFXsfx0BAE 2990\n#define SFXsfx0BAF 2991\n"
  },
  {
    "path": "Runtime/Audio/SFX/CineGeneral.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: CineGeneral\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPCineGeneral 60\n\n#define SFXsfx0BB0 2992\n#define SFXsfx0BB1 2993\n#define SFXci5_x_mapdown_00 2994\n#define SFXci5_x_mapload_lp_00 2995\n#define SFXci5_x_mapspin_lp_00 2996\n#define SFXci5_x_mapup_00 2997\n#define SFXepr_b_grenup_00 2998\n#define SFXpi2_x_missile_00 2999\n#define SFXpi2_x_healthsm_00 3000\n#define SFXpi2_x_smissile_00 3001\n#define SFXsfx0BBA 3002\n#define SFXsfx0BBB 3003\n#define SFXsfx0BBC 3004\n#define SFXsfx0BBD 3005\n#define SFXci9_x_nrg_lp_00 3006\n#define SFXsfx0BBF 3007\n#define SFXci9_x_insert_00 3008\n#define SFXsfx0BC1 3009\n#define SFXsfx0BC2 3010\n#define SFXsfx0BC3 3011\n#define SFXsfx0BC4 3012\n#define SFXsfx0BC5 3013\n#define SFXsfx0BC6 3014\n#define SFXsfx0BC7 3015\n#define SFXsfx0BC8 3016\n#define SFXsfx0BC9 3017\n#define SFXsfx0BCA 3018\n#define SFXsfx0BCB 3019\n#define SFXsfx0BCC 3020\n#define SFXsfx0BCD 3021\n#define SFXsfx0BCE 3022\n#define SFXsfx0BCF 3023\n#define SFXsfx0BD0 3024\n#define SFXsfx0BD1 3025\n#define SFXsfx0BD2 3026\n#define SFXsfx0BD3 3027\n#define SFXsfx0BD4 3028\n#define SFXsfx0BD5 3029\n#define SFXsfx0BD6 3030\n#define SFXsfx0BD7 3031\n#define SFXsfx0BD8 3032\n"
  },
  {
    "path": "Runtime/Audio/SFX/CineGun.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: CineGun\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPCineGun 62\n\n#define SFXsfx0BE5 3045\n#define SFXci3_x_clank_00 3046\n#define SFXci4_x_clank_00 3047\n#define SFXsfx0BE8 3048\n#define SFXci3_c_ridiclaw_00 3049\n#define SFXsfx0BEA 3050\n#define SFXsfx0BEB 3051\n#define SFXsfx0BEC 3052\n#define SFXsfx0BED 3053\n#define SFXsfx0BEE 3054\n#define SFXsfx0BEF 3055\n#define SFXsfx0BF0 3056\n#define SFXsfx0BF1 3057\n#define SFXsfx0BF2 3058\n"
  },
  {
    "path": "Runtime/Audio/SFX/CineMorphball.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: CineMorphball\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPCineMorphball 63\n\n#define SFXsfx0BF3 3059\n#define SFXsfx0BF4 3060\n#define SFXsfx0BF5 3061\n#define SFXsfx0BF6 3062\n#define SFXsfx0BF7 3063\n#define SFXsfx0BF8 3064\n#define SFXsfx0BF9 3065\n#define SFXsfx0BFA 3066\n#define SFXsfx0BFB 3067\n#define SFXsfx0BFC 3068\n"
  },
  {
    "path": "Runtime/Audio/SFX/CineSuit.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: CineSuit\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPCineSuit 64\n\n#define SFXci2_x_energy_lp_00 3069\n#define SFXci2_x_energy_lp_01 3070\n#define SFXsfx0BFF 3071\n#define SFXsfx0C00 3072\n#define SFXci2_x_jump_00 3073\n#define SFXsfx0C02 3074\n#define SFXci2_x_lights_lp_00 3075\n#define SFXci2_x_pad_lp_00 3076\n#define SFXsfx0C05 3077\n#define SFXsfx0C06 3078\n#define SFXci3_x_energy_02 3079\n#define SFXsfx0C08 3080\n#define SFXsfx0C09 3081\n#define SFXci3_x_whoosh_00 3082\n#define SFXsfx0C0B 3083\n#define SFXsfx0C0C 3084\n#define SFXsfx0C0D 3085\n#define SFXsfx0C0E 3086\n#define SFXsfx0C0F 3087\n#define SFXsfx0C10 3088\n#define SFXsfx0C11 3089\n#define SFXsfx0C12 3090\n#define SFXsfx0C13 3091\n#define SFXsfx0C14 3092\n#define SFXsfx0C15 3093\n"
  },
  {
    "path": "Runtime/Audio/SFX/CineVisor.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: CineVisor\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPCineVisor 61\n\n#define SFXcin_x_visor_00 3033\n#define SFXsfx0BDA 3034\n#define SFXsfx0BDB 3035\n#define SFXsfx0BDC 3036\n#define SFXsfx0BDD 3037\n#define SFXsfx0BDE 3038\n#define SFXsfx0BDF 3039\n#define SFXsfx0BE0 3040\n#define SFXsfx0BE1 3041\n#define SFXsfx0BE2 3042\n#define SFXsfx0BE3 3043\n#define SFXsfx0BE4 3044\n"
  },
  {
    "path": "Runtime/Audio/SFX/Crater.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: Crater\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPCrater 44\n\n#define SFXsfx0764 1892\n#define SFXsfx0765 1893\n#define SFXsfx0766 1894\n#define SFXsfx0767 1895\n#define SFXsfx0768 1896\n#define SFXsfx0769 1897\n#define SFXsfx076A 1898\n#define SFXsfx076B 1899\n#define SFXsfx076C 1900\n#define SFXsfx076D 1901\n#define SFXsfx076E 1902\n#define SFXsfx076F 1903\n#define SFXsfx0770 1904\n#define SFXsfx0771 1905\n#define SFXsfx0772 1906\n#define SFXsfx0773 1907\n#define SFXsfx0774 1908\n#define SFXsfx0775 1909\n#define SFXsfx0776 1910\n#define SFXsfx0777 1911\n#define SFXsfx0778 1912\n#define SFXsfx0779 1913\n#define SFXsfx077A 1914\n#define SFXsfx077B 1915\n#define SFXsfx077C 1916\n#define SFXsfx077D 1917\n#define SFXsfx077E 1918\n#define SFXsfx077F 1919\n#define SFXsfx0780 1920\n#define SFXsfx0781 1921\n"
  },
  {
    "path": "Runtime/Audio/SFX/Crystallite.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: Crystallite\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPCrystallite 8\n\n#define SFXcry_b_idle_00 199\n#define SFXsfx00C8 200\n#define SFXsfx00C9 201\n#define SFXsfx00CA 202\n#define SFXsfx00CB 203\n#define SFXsfx00CC 204\n#define SFXsfx00CD 205\n#define SFXsfx00CE 206\n#define SFXsfx00CF 207\n#define SFXsfx00D0 208\n"
  },
  {
    "path": "Runtime/Audio/SFX/Drones.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: Drones\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPDrones 9\n\n#define SFXepr_a_shockhit_00 209\n#define SFXsfx00D2 210\n#define SFXdrn_b_burst_00 211\n#define SFXsfx00D4 212\n#define SFXsfx00D5 213\n#define SFXsfx00D6 214\n#define SFXsfx00D7 215\n#define SFXdrn_b_patrol_lp_00 216\n#define SFXdrn_b_henshin_00 217\n#define SFXdrn_b_rocket_lp_00 218\n#define SFXdrn_b_rocket_lp_01 219\n#define SFXdrn_b_rocket_lp_02 220\n#define SFXsfx00DD 221\n#define SFXdrn_r_death_00 222\n#define SFXsfx00DF 223\n#define SFXsfx00E0 224\n#define SFXdrn_r_impact_00 225\n#define SFXsfx00E2 226\n#define SFXdrn_a_blast_00 227\n#define SFXsfx00E4 228\n#define SFXdrn_r_death_lp_00 229\n#define SFXsfx00E6 230\n#define SFXdrn_b_alert_00 231\n#define SFXsfx00E8 232\n#define SFXdrn_a_laser_00 233\n#define SFXsfx00EA 234\n#define SFXsfx00EB 235\n#define SFXsfx00EC 236\n#define SFXsfx00ED 237\n#define SFXdrn_r_impact_01 238\n#define SFXsfx00EF 239\n#define SFXdrn_a_blast_01 240\n#define SFXsfx00F1 241\n#define SFXsfx00F2 242\n#define SFXdrn_b_henshin_01 243\n#define SFXdrn_b_talk_00 244\n#define SFXsfx00F5 245\n#define SFXdrn_r_death_lp_01 246\n#define SFXdrn_a_charge_00 247\n#define SFXdrn_b_beep_03 248\n#define SFXsfx00F9 249\n#define SFXdrn_r_empblast_01 250\n#define SFXsfx00FB 251\n#define SFXsfx00FC 252\n#define SFXdrn_b_patrolun_lp_00 253\n#define SFXdrn_a_laserun_00 254\n#define SFXopr_a_shockhit_00 255\n#define SFXsfx0100 256\n#define SFXsfx0101 257\n#define SFXsfx0102 258\n#define SFXsfx0103 259\n#define SFXsfx0104 260\n#define SFXsfx0105 261\n#define SFXsfx0106 262\n#define SFXsfx0107 263\n#define SFXsfx0108 264\n#define SFXsfx0109 265\n#define SFXsfx010A 266\n#define SFXsfx010B 267\n#define SFXsfx010C 268\n"
  },
  {
    "path": "Runtime/Audio/SFX/EliteSpacePirate.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: EliteSpacePirate\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPEliteSpacePirate 10\n\n#define SFXepr_a_grenade_00 269\n#define SFXsfx010E 270\n#define SFXsfx010F 271\n#define SFXepr_b_hitglass_00 272\n#define SFXepr_a_swoosh_00 273\n#define SFXepr_b_run_00 274\n#define SFXepr_b_run_01 275\n#define SFXepr_a_shokwave_00 276\n#define SFXsfx0115 277\n#define SFXepr_a_attack_00 278\n#define SFXepr_a_attack_01 279\n#define SFXepr_b_land_00 280\n#define SFXepr_b_alert_00 281\n#define SFXepr_b_walk_00 282\n#define SFXepr_b_walk_01 283\n#define SFXepr_b_alert_01 284\n#define SFXepr_b_absorb_lp_00 285\n#define SFXepr_b_idle_00 286\n#define SFXepr_b_idle_01 287\n#define SFXepr_a_hitgrnd_00 288\n#define SFXepr_b_walklite_00 289\n#define SFXepr_b_walklite_01 290\n#define SFXepr_r_pissed_00 291\n#define SFXsfx0124 292\n#define SFXsfx0125 293\n#define SFXepr_a_swoosh_01 294\n#define SFXepr_b_taunt_00 295\n#define SFXopr_a_swoosh_00 296\n#define SFXopr_a_swoosh_01 297\n#define SFXepr_b_blokvox_00 298\n#define SFXsfx012B 299\n#define SFXopr_a_shokwave_00 300\n#define SFXsfx012D 301\n#define SFXopr_b_absorb_lp_00 302\n#define SFXsfx012F 303\n#define SFXsfx0130 304\n#define SFXsfx0131 305\n#define SFXsfx0132 306\n#define SFXsfx0133 307\n#define SFXsfx0134 308\n#define SFXsfx0135 309\n#define SFXsfx0136 310\n#define SFXsfx0137 311\n#define SFXsfx0138 312\n#define SFXsfx0139 313\n#define SFXsfx013A 314\n#define SFXsfx013B 315\n#define SFXsfx013C 316\n#define SFXsfx013D 317\n#define SFXsfx013E 318\n#define SFXsfx013F 319\n#define SFXsfx0140 320\n#define SFXsfx0141 321\n#define SFXsfx0142 322\n"
  },
  {
    "path": "Runtime/Audio/SFX/FireFlea.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: FireFlea\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPFireFlea 11\n\n#define SFXfif_b_idle_lp_00 323\n#define SFXsfx0144 324\n#define SFXfif_b_light_00 325\n#define SFXfif_r_death_00 326\n#define SFXfif_r_death_01 327\n#define SFXfif_r_explode_00 328\n#define SFXfif_r_impact_00 329\n#define SFXsfx014A 330\n#define SFXsfx014B 331\n#define SFXsfx014C 332\n#define SFXsfx014D 333\n#define SFXsfx014E 334\n#define SFXsfx014F 335\n"
  },
  {
    "path": "Runtime/Audio/SFX/Flaaghra.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: Flaaghra\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPFlaaghra 52\n\n#define SFXfla_a_hitgrnd_00 2600\n#define SFXfla_a_swoosh_00 2601\n#define SFXfla_a_swoosh_01 2602\n#define SFXfla_a_voxattak_00 2603\n#define SFXfla_a_voxattak_01 2604\n#define SFXfla_b_grow_00 2605\n#define SFXfla_b_idle_00 2606\n#define SFXsfx0A2F 2607\n#define SFXfla_b_voxangry_00 2608\n#define SFXfla_b_voxangry_01 2609\n#define SFXfla_r_death_00 2610\n#define SFXfla_r_death_01 2611\n#define SFXfla_r_death_02 2612\n#define SFXsfx0A35 2613\n#define SFXfla_r_faint_01 2614\n#define SFXfla_r_pain_00 2615\n#define SFXsfx0A38 2616\n#define SFXfla_a_shoot_00 2617\n#define SFXfla_a_spit_00 2618\n#define SFXfla_a_spit_01 2619\n#define SFXsfx0A3C 2620\n#define SFXsfx0A3D 2621\n#define SFXfla_a_charge_00 2622\n#define SFXsfx0A3F 2623\n#define SFXsfx0A40 2624\n#define SFXfla_b_idlesm_00 2625\n#define SFXfla_a_chargevox_00 2626\n#define SFXsfx0A43 2627\n#define SFXsfx0A44 2628\n#define SFXsfx0A45 2629\n#define SFXfla_a_shootvox_00 2630\n#define SFXsfx0A47 2631\n#define SFXsfx0A48 2632\n#define SFXsfx0A49 2633\n#define SFXsfx0A4A 2634\n#define SFXsfx0A4B 2635\n#define SFXfla_a_spitvox_00 2636\n#define SFXfla_a_spitvox_01 2637\n#define SFXsfx0A4E 2638\n#define SFXsfx0A4F 2639\n#define SFXsfx0A50 2640\n#define SFXsfx0A51 2641\n#define SFXsfx0A52 2642\n#define SFXfla_a_sporevox_01 2643\n#define SFXfla_a_hitgrnd_01 2644\n#define SFXsfx0A55 2645\n#define SFXfla_r_landgrnd_00 2646\n#define SFXsfx0A57 2647\n#define SFXsfx0A58 2648\n#define SFXfla_b_grow_01 2649\n#define SFXfla_b_rise_lp_00 2650\n#define SFXsfx0A5B 2651\n#define SFXfla_b_dizzy_00 2652\n#define SFXsfx0A5D 2653\n#define SFXsfx0A5E 2654\n#define SFXfla_r_painsh_00 2655\n#define SFXsfx0A60 2656\n#define SFXfla_b_humor_00 2657\n#define SFXfla_r_painbig_00 2658\n#define SFXfla_b_dizzyout_00 2659\n#define SFXfla_b_faintout_00 2660\n#define SFXsfx0A65 2661\n#define SFXfla_b_dizzy_lp_01 2662\n#define SFXsfx0A67 2663\n#define SFXsfx0A68 2664\n#define SFXsfx0A69 2665\n#define SFXsfx0A6A 2666\n#define SFXfla_b_voxshrnk_00 2667\n#define SFXsfx0A6C 2668\n#define SFXsfx0A6D 2669\n#define SFXfla_b_voxshrnk_03 2670\n#define SFXsfx0A6F 2671\n#define SFXsfx0A70 2672\n#define SFXsfx0A71 2673\n#define SFXsfx0A72 2674\n#define SFXsfx0A73 2675\n#define SFXsfx0A74 2676\n#define SFXsfx0A75 2677\n#define SFXsfx0A76 2678\n#define SFXsfx0A77 2679\n#define SFXsfx0A78 2680\n"
  },
  {
    "path": "Runtime/Audio/SFX/FlickerBat.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: FlickerBat\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPFlickerBat 12\n\n#define SFXflk_b_flicker_00 336\n#define SFXflk_b_talk_00 337\n#define SFXflk_b_talk_01 338\n#define SFXsfx0153 339\n#define SFXsfx0154 340\n#define SFXflk_r_impact_00 341\n#define SFXsfx0156 342\n#define SFXsfx0157 343\n#define SFXsfx0158 344\n#define SFXsfx0159 345\n#define SFXsfx015A 346\n#define SFXsfx015B 347\n"
  },
  {
    "path": "Runtime/Audio/SFX/FlyingPirate.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: FlyingPirate\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPFlyingPirate 13\n\n#define SFXsfx015C 348\n#define SFXfpr_a_chaff_00 349\n#define SFXsfx015E 350\n#define SFXupr_a_mislfire_00 351\n#define SFXsfx0160 352\n#define SFXsfx0161 353\n#define SFXfpr_a_mislfire_00 354\n#define SFXfpr_b_thrust_01 355\n#define SFXsfx0164 356\n#define SFXfpr_b_engine_lp_00 357\n#define SFXfpr_b_engine_lp_01 358\n#define SFXfpr_b_engine_lp_02 359\n#define SFXfpr_b_voxangry_02 360\n#define SFXfpr_b_thrust_00 361\n#define SFXsfx016A 362\n#define SFXfpr_r_die_00 363\n#define SFXfpr_b_intruder_00 364\n#define SFXfpr_b_voxalert_00 365\n#define SFXfpr_a_mislload_00 366\n#define SFXfpr_b_voxangry_00 367\n#define SFXsfx0170 368\n#define SFXfpr_r_impact_00 369\n#define SFXsfx0172 370\n#define SFXsfx0173 371\n#define SFXsfx0174 372\n#define SFXfpr_b_engidle_lp_00 373\n#define SFXsfx0176 374\n#define SFXfpr_b_blastoff_lp_00 375\n#define SFXfpr_b_blastoff_01 376\n#define SFXupr_a_mislload_00 377\n#define SFXupr_b_engidle_lp_00 378\n#define SFXupr_b_engine_lp_00 379\n#define SFXupr_b_engine_lp_01 380\n#define SFXupr_b_engine_lp_02 381\n#define SFXsfx017E 382\n#define SFXsfx017F 383\n#define SFXupr_b_voxalert_00 384\n#define SFXsfx0181 385\n#define SFXupr_b_voxangry_00 386\n#define SFXsfx0183 387\n#define SFXupr_b_voxangry_02 388\n#define SFXupr_r_die_00 389\n#define SFXupr_r_impact_00 390\n#define SFXsfx0187 391\n#define SFXsfx0188 392\n#define SFXsfx0189 393\n#define SFXsfx018A 394\n#define SFXsfx018B 395\n#define SFXsfx018C 396\n#define SFXsfx018D 397\n#define SFXsfx018E 398\n#define SFXsfx018F 399\n#define SFXsfx0190 400\n#define SFXsfx0191 401\n#define SFXsfx0192 402\n#define SFXsfx0193 403\n#define SFXsfx0194 404\n#define SFXsfx0195 405\n#define SFXsfx0196 406\n"
  },
  {
    "path": "Runtime/Audio/SFX/FrontEnd.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: FrontEnd\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPFrontEnd 38\n\n#define SFXfnt_transfore_00L 1090\n#define SFXfnt_advance_R 1091\n#define SFXsfx0444 1092\n#define SFXfnt_selection_change 1093\n#define SFXfnt_back 1094\n#define SFXfnt_enum_change 1095\n#define SFXfnt_advance_L 1096\n#define SFXfnt_transfore_00R 1097\n#define SFXfnt_transfore_01L 1098\n#define SFXfnt_transfore_01R 1099\n#define SFXfnt_transfore_02L 1100\n#define SFXfnt_transfore_02R 1101\n#define SFXfnt_transback_00L 1102\n#define SFXfnt_transback_00R 1103\n#define SFXfnt_transback_01L 1104\n#define SFXfnt_transback_01R 1105\n#define SFXfnt_transback_02L 1106\n#define SFXfnt_transback_02R 1107\n#define SFXfnt_tofusion_L 1108\n#define SFXfnt_tofusion_R 1109\n#define SFXfnt_fromfusion_L 1110\n#define SFXfnt_fromfusion_R 1111\n#define SFXsfx0458 1112\n#define SFXsfx0459 1113\n#define SFXsfx045A 1114\n#define SFXsfx045B 1115\n#define SFXsfx045C 1116\n#define SFXsfx045D 1117\n#define SFXsfx045E 1118\n#define SFXsfx045F 1119\n#define SFXsfx0460 1120\n#define SFXsfx0461 1121\n#define SFXsfx0462 1122\n#define SFXsfx0463 1123\n#define SFXsfx0464 1124\n#define SFXsfx0465 1125\n#define SFXsfx0466 1126\n#define SFXsfx0467 1127\n"
  },
  {
    "path": "Runtime/Audio/SFX/GagantuanBeatle.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: GagantuanBeatle\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPGagantuanBeatle 14\n\n#define SFXgab_r_hitlight_01 407\n#define SFXga2_b_digexplod_00 408\n#define SFXga2_b_digscream_00 409\n#define SFXga2_b_idle_01 410\n#define SFXga2_b_scrapedirt_00 411\n#define SFXgab_b_walkdirt_02 412\n#define SFXgab_b_rundirt_00 413\n#define SFXgab_b_rundirt_01 414\n#define SFXsfx019F 415\n#define SFXgab_a_attack_00 416\n#define SFXgab_a_attack_01 417\n#define SFXgab_b_idle_03 418\n#define SFXgab_b_digexplod_00 419\n#define SFXfla_b_scrapedirt_00 420\n#define SFXgab_b_idle_02 421\n#define SFXgab_b_idle_00 422\n#define SFXgab_b_idle_01 423\n#define SFXgab_b_walkdirt_00 424\n#define SFXgab_b_walkdirt_01 425\n#define SFXgab_r_collide_00 426\n#define SFXsfx01AB 427\n#define SFXgab_r_death_01 428\n#define SFXgab_r_detect_00 429\n#define SFXgab_r_hitlight_00 430\n#define SFXgab_b_digscream_00 431\n#define SFXgab_b_scrapedirt_00 432\n#define SFXga2_b_dig_lp_00 433\n#define SFXga2_b_rundirt_00 434\n#define SFXgab_b_dig_lp_00 435\n#define SFXga2_b_rundirt_01 436\n#define SFXga2_b_rundirt_02 437\n#define SFXga2_b_walkdirt_00 438\n#define SFXga2_b_walkdirt_01 439\n#define SFXga2_b_walkdirt_02 440\n#define SFXga2_r_collide_00 441\n#define SFXga2_a_attack_00 442\n#define SFXsfx01BB 443\n#define SFXsfx01BC 444\n#define SFXsfx01BD 445\n#define SFXsfx01BE 446\n#define SFXsfx01BF 447\n#define SFXsfx01C0 448\n#define SFXsfx01C1 449\n#define SFXsfx01C2 450\n#define SFXsfx01C3 451\n#define SFXsfx01C4 452\n#define SFXsfx01C5 453\n#define SFXsfx01C6 454\n#define SFXsfx01C7 455\n#define SFXsfx01C8 456\n#define SFXsfx01C9 457\n#define SFXsfx01CA 458\n"
  },
  {
    "path": "Runtime/Audio/SFX/Gnats.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: Gnats\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPGnats 15\n\n#define SFXsfx01CB 459\n#define SFXsfx01CC 460\n#define SFXsfx01CD 461\n#define SFXsfx01CE 462\n#define SFXsfx01CF 463\n#define SFXsfx01D0 464\n#define SFXsfx01D1 465\n"
  },
  {
    "path": "Runtime/Audio/SFX/Gryzbee.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: Gryzbee\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPGryzbee 16\n\n#define SFXgrz_b_idle_00 466\n#define SFXsfx01D3 467\n#define SFXsfx01D4 468\n#define SFXsfx01D5 469\n#define SFXsfx01D6 470\n#define SFXsfx01D7 471\n#define SFXsfx01D8 472\n#define SFXsfx01D9 473\n#define SFXsfx01DA 474\n#define SFXsfx01DB 475\n#define SFXsfx01DC 476\n#define SFXsfx01DD 477\n"
  },
  {
    "path": "Runtime/Audio/SFX/IceCrack.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: IceCrack\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPIceCrack 67\n\n#define SFXsfx0C31 3121\n#define SFXsfx0C32 3122\n#define SFXsfx0C33 3123\n#define SFXsfx0C34 3124\n#define SFXsfx0C35 3125\n#define SFXsfx0C36 3126\n#define SFXcrk_break_subsequent 3127\n#define SFXcrk_break_initial 3128\n#define SFXcrk_break_final 3129\n#define SFXsfx0C3A 3130\n#define SFXsfx0C3B 3131\n#define SFXsfx0C3C 3132\n#define SFXsfx0C3D 3133\n#define SFXsfx0C3E 3134\n#define SFXsfx0C3F 3135\n#define SFXsfx0C40 3136\n#define SFXsfx0C41 3137\n#define SFXsfx0C42 3138\n#define SFXsfx0C43 3139\n#define SFXsfx0C44 3140\n"
  },
  {
    "path": "Runtime/Audio/SFX/IceWorld.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: IceWorld\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPIceWorld 45\n\n#define SFXice_x_gateopen_lp_00 1922\n#define SFXice_x_gatestop_00 1923\n#define SFXsfx0784 1924\n#define SFXice_x_towercrk_00 1925\n#define SFXice_ballroll_ice 1926\n#define SFXice_ballroll_snow 1927\n#define SFXsfx0788 1928\n#define SFXice_x_towerlnd_00 1929\n#define SFXsfx078A 1930\n#define SFXice_x_towerlnd_01 1931\n#define SFXice_x_towercrk_01 1932\n#define SFXice_x_towercrk_02 1933\n#define SFXsfx078E 1934\n#define SFXsfx078F 1935\n#define SFXsfx0790 1936\n#define SFXsfx0791 1937\n#define SFXsfx0792 1938\n#define SFXsfx0793 1939\n#define SFXsfx0794 1940\n#define SFXsfx0795 1941\n#define SFXsfx0796 1942\n#define SFXsfx0797 1943\n#define SFXsfx0798 1944\n#define SFXsfx0799 1945\n#define SFXsfx079A 1946\n#define SFXsfx079B 1947\n#define SFXsfx079C 1948\n#define SFXsfx079D 1949\n#define SFXsfx079E 1950\n#define SFXsfx079F 1951\n#define SFXsfx07A0 1952\n#define SFXsfx07A1 1953\n#define SFXsfx07A2 1954\n#define SFXsfx07A3 1955\n#define SFXsfx07A4 1956\n#define SFXsfx07A5 1957\n#define SFXsfx07A6 1958\n#define SFXsfx07A7 1959\n#define SFXsfx07A8 1960\n#define SFXsfx07A9 1961\n#define SFXsfx07AA 1962\n#define SFXsfx07AB 1963\n#define SFXsfx07AC 1964\n#define SFXsfx07AD 1965\n#define SFXsfx07AE 1966\n#define SFXsfx07AF 1967\n#define SFXtha_b_rockup_lp_00 1968\n#define SFXsfx07B1 1969\n#define SFXsfx07B2 1970\n#define SFXice_x_ridflap_00 1971\n#define SFXsfx07B4 1972\n#define SFXsfx07B5 1973\n#define SFXsfx07B6 1974\n#define SFXice_x_pump_00 1975\n#define SFXsfx07B8 1976\n#define SFXsfx07B9 1977\n#define SFXsfx07BA 1978\n#define SFXsfx07BB 1979\n#define SFXsfx07BC 1980\n#define SFXsfx07BD 1981\n#define SFXice_x_piston_00 1982\n#define SFXice_x_piston_lp_00 1983\n#define SFXsfx07C0 1984\n#define SFXsfx07C1 1985\n#define SFXsfx07C2 1986\n#define SFXsfx07C3 1987\n#define SFXsfx07C4 1988\n#define SFXsfx07C5 1989\n#define SFXsfx07C6 1990\n#define SFXsfx07C7 1991\n#define SFXsfx07C8 1992\n#define SFXsfx07C9 1993\n#define SFXtha_b_debris_00 1994\n#define SFXtha_b_debris_01 1995\n#define SFXsfx07CC 1996\n#define SFXsfx07CD 1997\n#define SFXsfx07CE 1998\n#define SFXsfx07CF 1999\n#define SFXsfx07D0 2000\n#define SFXsfx07D1 2001\n#define SFXsfx07D2 2002\n#define SFXsfx07D3 2003\n#define SFXsfx07D4 2004\n#define SFXsfx07D5 2005\n#define SFXsfx07D6 2006\n#define SFXsfx07D7 2007\n#define SFXsfx07D8 2008\n#define SFXsfx07D9 2009\n#define SFXsfx07DA 2010\n#define SFXsfx07DB 2011\n#define SFXsfx07DC 2012\n#define SFXsfx07DD 2013\n#define SFXsfx07DE 2014\n#define SFXsfx07DF 2015\n#define SFXsfx07E0 2016\n#define SFXsfx07E1 2017\n#define SFXsfx07E2 2018\n#define SFXsfx07E3 2019\n#define SFXsfx07E4 2020\n#define SFXsfx07E5 2021\n#define SFXsfx07E6 2022\n#define SFXsfx07E7 2023\n#define SFXsfx07E8 2024\n#define SFXsfx07E9 2025\n#define SFXsfx07EA 2026\n#define SFXsfx07EB 2027\n#define SFXsfx07EC 2028\n#define SFXsfx07ED 2029\n#define SFXsfx07EE 2030\n#define SFXsfx07EF 2031\n#define SFXsfx07F0 2032\n#define SFXsfx07F1 2033\n#define SFXsfx07F2 2034\n#define SFXsfx07F3 2035\n#define SFXsfx07F4 2036\n#define SFXsfx07F5 2037\n#define SFXsfx07F6 2038\n#define SFXsfx07F7 2039\n#define SFXsfx07F8 2040\n#define SFXsfx07F9 2041\n"
  },
  {
    "path": "Runtime/Audio/SFX/InjuredPirates.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: InjuredPirates\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPInjuredPirates 17\n\n#define SFXsfx01DE 478\n#define SFXsfx01DF 479\n#define SFXspr_b_exhale_00 480\n#define SFXsfx01E1 481\n#define SFXspr_b_moan_00 482\n#define SFXsfx01E3 483\n#define SFXsfx01E4 484\n#define SFXsfx01E5 485\n#define SFXsfx01E6 486\n#define SFXsfx01E7 487\n#define SFXsfx01E8 488\n#define SFXsfx01E9 489\n#define SFXsfx01EA 490\n#define SFXsfx01EB 491\n#define SFXsfx01EC 492\n#define SFXsfx01ED 493\n#define SFXsfx01EE 494\n#define SFXsfx01EF 495\n#define SFXsfx01F0 496\n#define SFXsfx01F1 497\n#define SFXsfx01F2 498\n#define SFXsfx01F3 499\n#define SFXsfx01F4 500\n#define SFXsfx01F5 501\n#define SFXsfx01F6 502\n#define SFXsfx01F7 503\n#define SFXsfx01F8 504\n#define SFXsfx01F9 505\n#define SFXsfx01FA 506\n#define SFXsfx01FB 507\n"
  },
  {
    "path": "Runtime/Audio/SFX/IntroBoss.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: IntroBoss\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPIntroBoss 0\n\n#define SFXsfx0000 0\n#define SFXsfx0001 1\n#define SFXsfx0002 2\n#define SFXpaq_a_spit_lp_00 3\n#define SFXpaq_b_squawk_00 4\n#define SFXpaq_b_creak_00 5\n#define SFXpaq_b_creak_01 6\n#define SFXpaq_b_growl_00 7\n#define SFXpaq_b_growl_01 8\n#define SFXpaq_b_land_00 9\n#define SFXpaq_b_roar_00 10\n#define SFXpaq_b_roar_01 11\n#define SFXsfx000C 12\n#define SFXpaq_b_walk_00 13\n#define SFXpaq_b_walk_01 14\n#define SFXpaq_r_impact_00 15\n#define SFXpaq_r_impact_01 16\n#define SFXpaq_r_ldeath_00 17\n#define SFXpaq_r_sdeath_01 18\n#define SFXpaq_b_swish_00 19\n#define SFXsfx0014 20\n#define SFXsfx0015 21\n#define SFXsfx0016 22\n#define SFXpaq_b_land_01 23\n#define SFXpaq_b_run_00 24\n#define SFXpaq_b_run_01 25\n#define SFXsfx001A 26\n#define SFXsfx001B 27\n#define SFXsfx001C 28\n#define SFXsfx001D 29\n#define SFXsfx001E 30\n#define SFXsfx001F 31\n#define SFXsfx0020 32\n#define SFXsfx0021 33\n#define SFXsfx0022 34\n#define SFXsfx0023 35\n#define SFXsfx0024 36\n#define SFXsfx0025 37\n#define SFXsfx0026 38\n#define SFXsfx0027 39\n#define SFXsfx0028 40\n#define SFXsfx0029 41\n"
  },
  {
    "path": "Runtime/Audio/SFX/IntroWorld.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: IntroWorld\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPIntroWorld 46\n\n#define SFXsfx07FA 2042\n#define SFXsfx07FB 2043\n#define SFXsfx07FC 2044\n#define SFXsfx07FD 2045\n#define SFXsfx07FE 2046\n#define SFXint_c_suitsprk_lp_01 2047\n#define SFXsfx0800 2048\n#define SFXsfx0801 2049\n#define SFXsfx0802 2050\n#define SFXsfx0803 2051\n#define SFXsfx0804 2052\n#define SFXsfx0805 2053\n#define SFXsfx0806 2054\n#define SFXsfx0807 2055\n#define SFXsfx0808 2056\n#define SFXsfx0809 2057\n#define SFXsfx080A 2058\n#define SFXsfx080B 2059\n#define SFXsfx080C 2060\n#define SFXsfx080D 2061\n#define SFXsfx080E 2062\n#define SFXsfx080F 2063\n#define SFXsfx0810 2064\n#define SFXsfx0811 2065\n#define SFXsfx0812 2066\n#define SFXsfx0813 2067\n#define SFXsfx0814 2068\n#define SFXsfx0815 2069\n#define SFXsfx0816 2070\n#define SFXsfx0817 2071\n#define SFXsfx0818 2072\n#define SFXsfx0819 2073\n#define SFXsfx081A 2074\n#define SFXsfx081B 2075\n#define SFXsfx081C 2076\n#define SFXsfx081D 2077\n#define SFXsfx081E 2078\n#define SFXsfx081F 2079\n#define SFXsfx0820 2080\n#define SFXsfx0821 2081\n#define SFXsfx0822 2082\n#define SFXsfx0823 2083\n#define SFXsfx0824 2084\n#define SFXsfx0825 2085\n#define SFXsfx0826 2086\n#define SFXsfx0827 2087\n#define SFXsfx0828 2088\n#define SFXsfx0829 2089\n#define SFXsfx082A 2090\n#define SFXsfx082B 2091\n#define SFXsfx082C 2092\n#define SFXsfx082D 2093\n#define SFXsfx082E 2094\n#define SFXsfx082F 2095\n#define SFXsfx0830 2096\n#define SFXsfx0831 2097\n#define SFXsfx0832 2098\n#define SFXsfx0833 2099\n#define SFXsfx0834 2100\n#define SFXsfx0835 2101\n#define SFXsfx0836 2102\n#define SFXsfx0837 2103\n#define SFXsfx0838 2104\n#define SFXsfx0839 2105\n#define SFXsfx083A 2106\n#define SFXsfx083B 2107\n#define SFXsfx083C 2108\n#define SFXint_x_frtdoor_00 2109\n#define SFXint_x_frtdoor_01 2110\n#define SFXsfx083F 2111\n#define SFXsfx0840 2112\n#define SFXsfx0841 2113\n#define SFXsfx0842 2114\n#define SFXsfx0843 2115\n#define SFXsfx0844 2116\n#define SFXsfx0845 2117\n#define SFXsfx0846 2118\n#define SFXsfx0847 2119\n#define SFXsfx0848 2120\n#define SFXsfx0849 2121\n#define SFXsfx084A 2122\n#define SFXsfx084B 2123\n#define SFXsfx084C 2124\n#define SFXsfx084D 2125\n#define SFXint_c_suitbrst_01 2126\n#define SFXsfx084F 2127\n#define SFXsfx0850 2128\n#define SFXsfx0851 2129\n#define SFXint_c_shipthst_00 2130\n#define SFXsfx0853 2131\n#define SFXsfx0854 2132\n#define SFXsfx0855 2133\n#define SFXsfx0856 2134\n#define SFXsfx0857 2135\n#define SFXsfx0858 2136\n#define SFXsfx0859 2137\n#define SFXsfx085A 2138\n#define SFXsfx085B 2139\n#define SFXsfx085C 2140\n#define SFXsfx085D 2141\n#define SFXsfx085E 2142\n#define SFXsfx085F 2143\n#define SFXsfx0860 2144\n#define SFXsfx0861 2145\n#define SFXsfx0862 2146\n#define SFXsfx0863 2147\n#define SFXsfx0864 2148\n#define SFXsfx0865 2149\n#define SFXsfx0866 2150\n#define SFXsfx0867 2151\n#define SFXsfx0868 2152\n#define SFXsfx0869 2153\n#define SFXsfx086A 2154\n#define SFXsfx086B 2155\n#define SFXsfx086C 2156\n#define SFXsfx086D 2157\n#define SFXsfx086E 2158\n#define SFXsfx086F 2159\n#define SFXsfx0870 2160\n#define SFXint_x_clampstp_00 2161\n#define SFXint_x_clamp_00 2162\n#define SFXint_x_clamp_01 2163\n#define SFXsfx0874 2164\n#define SFXsfx0875 2165\n#define SFXsfx0876 2166\n#define SFXsfx0877 2167\n#define SFXsfx0878 2168\n#define SFXsfx0879 2169\n#define SFXsfx087A 2170\n#define SFXsfx087B 2171\n#define SFXsfx087C 2172\n#define SFXsfx087D 2173\n#define SFXsfx087E 2174\n#define SFXsfx087F 2175\n#define SFXsfx0880 2176\n#define SFXsfx0881 2177\n#define SFXsfx0882 2178\n#define SFXsfx0883 2179\n#define SFXsfx0884 2180\n"
  },
  {
    "path": "Runtime/Audio/SFX/JellyZap.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: JellyZap\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPJellyZap 18\n\n#define SFXjzp_a_shock_00 508\n#define SFXjzp_a_suck_lp_00 509\n#define SFXjzp_b_bubbles_00 510\n#define SFXjzp_b_growl_00 511\n#define SFXsfx0200 512\n#define SFXsfx0201 513\n#define SFXsfx0202 514\n#define SFXsfx0203 515\n#define SFXsfx0204 516\n#define SFXsfx0205 517\n"
  },
  {
    "path": "Runtime/Audio/SFX/LavaWorld.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: LavaWorld\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPLavaWorld 47\n\n#define SFXsfx0885 2181\n#define SFXsfx0886 2182\n#define SFXlav_wlklava_00 2183\n#define SFXlav_wlklava_01 2184\n#define SFXsfx0889 2185\n#define SFXlav_ballroll_lava 2186\n#define SFXsfx088B 2187\n#define SFXsfx088C 2188\n#define SFXsfx088D 2189\n#define SFXlav_landlava_00 2190\n#define SFXsfx088F 2191\n#define SFXsfx0890 2192\n#define SFXsfx0891 2193\n#define SFXsfx0892 2194\n#define SFXsfx0893 2195\n#define SFXsfx0894 2196\n#define SFXsfx0895 2197\n#define SFXsfx0896 2198\n#define SFXsfx0897 2199\n#define SFXsfx0898 2200\n#define SFXsfx0899 2201\n#define SFXsfx089A 2202\n#define SFXsfx089B 2203\n#define SFXsfx089C 2204\n#define SFXsfx089D 2205\n#define SFXsfx089E 2206\n#define SFXsfx089F 2207\n#define SFXsfx08A0 2208\n#define SFXsfx08A1 2209\n#define SFXsfx08A2 2210\n#define SFXsfx08A3 2211\n#define SFXsfx08A4 2212\n#define SFXsfx08A5 2213\n#define SFXsfx08A6 2214\n#define SFXsfx08A7 2215\n#define SFXlav_x_piston_lp_00 2216\n#define SFXsfx08A9 2217\n#define SFXlav_x_piststop_00 2218\n#define SFXlav_x_piststop_01 2219\n#define SFXsfx08AC 2220\n#define SFXsfx08AD 2221\n#define SFXswp_x_03bridgestop_00 2222\n#define SFXsfx08AF 2223\n#define SFXsfx08B0 2224\n#define SFXsfx08B1 2225\n#define SFXsfx08B2 2226\n#define SFXsfx08B3 2227\n#define SFXsfx08B4 2228\n#define SFXsfx08B5 2229\n#define SFXsfx08B6 2230\n#define SFXsfx08B7 2231\n#define SFXsfx08B8 2232\n#define SFXsfx08B9 2233\n#define SFXsfx08BA 2234\n#define SFXsfx08BB 2235\n#define SFXsfx08BC 2236\n#define SFXsfx08BD 2237\n#define SFXsfx08BE 2238\n#define SFXmag_b_rise_00 2239\n#define SFXsfx08C0 2240\n#define SFXsfx08C1 2241\n#define SFXsfx08C2 2242\n#define SFXsfx08C3 2243\n#define SFXsfx08C4 2244\n#define SFXsfx08C5 2245\n#define SFXsfx08C6 2246\n#define SFXsfx08C7 2247\n#define SFXsfx08C8 2248\n#define SFXsfx08C9 2249\n#define SFXsfx08CA 2250\n#define SFXsfx08CB 2251\n#define SFXsfx08CC 2252\n#define SFXlav_x_gateup_lp_00 2253\n#define SFXsfx08CE 2254\n#define SFXlav_x_refrig_00 2255\n#define SFXlav_x_gatestop_00 2256\n#define SFXsfx08D1 2257\n#define SFXsfx08D2 2258\n#define SFXsfx08D3 2259\n#define SFXsfx08D4 2260\n#define SFXsfx08D5 2261\n#define SFXsfx08D6 2262\n#define SFXlav_landlava_02 2263\n#define SFXsfx08D8 2264\n#define SFXsfx08D9 2265\n#define SFXsfx08DA 2266\n#define SFXsfx08DB 2267\n#define SFXsfx08DC 2268\n#define SFXsfx08DD 2269\n#define SFXsfx08DE 2270\n#define SFXsfx08DF 2271\n#define SFXsfx08E0 2272\n#define SFXsfx08E1 2273\n#define SFXsfx08E2 2274\n#define SFXsfx08E3 2275\n#define SFXsfx08E4 2276\n#define SFXsfx08E5 2277\n#define SFXsfx08E6 2278\n#define SFXsfx08E7 2279\n#define SFXsfx08E8 2280\n#define SFXsfx08E9 2281\n#define SFXsfx08EA 2282\n#define SFXsfx08EB 2283\n"
  },
  {
    "path": "Runtime/Audio/SFX/Magdolite.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: Magdolite\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPMagdolite 19\n\n#define SFXmag_b_alert_00 518\n#define SFXmag_b_idle_00 519\n#define SFXsfx0208 520\n#define SFXmag_r_pain_00 521\n#define SFXmag_a_bite_00 522\n#define SFXmag_r_death_00 523\n#define SFXmag_a_breath_00 524\n#define SFXmag_a_flame_lp_00 525\n#define SFXmag_r_yelp_00 526\n#define SFXsfx020F 527\n#define SFXsfx0210 528\n#define SFXsfx0211 529\n#define SFXsfx0212 530\n#define SFXsfx0213 531\n#define SFXsfx0214 532\n#define SFXsfx0215 533\n#define SFXsfx0216 534\n#define SFXsfx0217 535\n#define SFXsfx0218 536\n#define SFXsfx0219 537\n#define SFXsfx021A 538\n#define SFXsfx021B 539\n#define SFXsfx021C 540\n#define SFXsfx021D 541\n#define SFXsfx021E 542\n#define SFXsfx021F 543\n#define SFXsfx0220 544\n#define SFXsfx0221 545\n#define SFXsfx0222 546\n#define SFXsfx0223 547\n"
  },
  {
    "path": "Runtime/Audio/SFX/Metaree.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: Metaree\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPMetaree 20\n\n#define SFXmtr_a_scream_00 548\n#define SFXsfx0225 549\n#define SFXmtr_b_spin_lp_06 550\n#define SFXmtr_b_spin_lp_07 551\n#define SFXsfx0228 552\n#define SFXsfx0229 553\n#define SFXsfx022A 554\n#define SFXsfx022B 555\n#define SFXsfx022C 556\n#define SFXsfx022D 557\n#define SFXsfx022E 558\n"
  },
  {
    "path": "Runtime/Audio/SFX/Metroid.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: Metroid\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPMetroid 21\n\n#define SFXsfx022F 559\n#define SFXsfx0230 560\n#define SFXmtd_a_facehug_02 561\n#define SFXmtd_a_swoosh_00 562\n#define SFXmtd_a_swoosh_01 563\n#define SFXmtd_a_thunk_00 564\n#define SFXmtd_b_fadein_00 565\n#define SFXmtd_b_fadeout_00 566\n#define SFXmtd_b_float_lp_00 567\n#define SFXmtd_b_float_lp_01 568\n#define SFXmtd_b_idle_00 569\n#define SFXmtd_b_idle_01 570\n#define SFXsfx023B 571\n#define SFXsfx023C 572\n#define SFXsfx023D 573\n#define SFXsfx023E 574\n#define SFXsfx023F 575\n#define SFXmtd_b_squish_00 576\n#define SFXmtd_b_squish_01 577\n#define SFXsfx0242 578\n#define SFXmtd_b_voxangry_00 579\n#define SFXsfx0244 580\n#define SFXsfx0245 581\n#define SFXmtd_r_impact_00 582\n#define SFXsfx0247 583\n#define SFXsfx0248 584\n#define SFXmt2_a_facehug_02 585\n#define SFXsfx024A 586\n#define SFXsfx024B 587\n#define SFXsfx024C 588\n#define SFXsfx024D 589\n#define SFXmt2_b_float_lp_00 590\n#define SFXmt2_b_float_lp_01 591\n#define SFXsfx0250 592\n#define SFXsfx0251 593\n#define SFXsfx0252 594\n#define SFXmt2_b_idle_02 595\n#define SFXsfx0254 596\n#define SFXsfx0255 597\n#define SFXmt2_b_leech_lp_00 598\n#define SFXsfx0257 599\n#define SFXsfx0258 600\n#define SFXmt2_b_voxangry_00 601\n#define SFXmt2_b_voxangry_01 602\n#define SFXsfx025B 603\n#define SFXmt2_r_impact_00 604\n#define SFXmtd_b_grow_00 605\n#define SFXmtd_b_suck_lp_00 606\n#define SFXmtd_r_death_00 607\n#define SFXmt2_b_float_lp_02 608\n#define SFXsfx0261 609\n#define SFXmtd_b_voxcalm_00 610\n#define SFXsfx0263 611\n#define SFXsfx0264 612\n#define SFXsfx0265 613\n#define SFXsfx0266 614\n#define SFXsfx0267 615\n#define SFXsfx0268 616\n#define SFXsfx0269 617\n#define SFXsfx026A 618\n#define SFXsfx026B 619\n#define SFXsfx026C 620\n#define SFXsfx026D 621\n#define SFXsfx026E 622\n#define SFXsfx026F 623\n"
  },
  {
    "path": "Runtime/Audio/SFX/MetroidPrime.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: MetroidPrime\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPMetroidPrime 58\n\n#define SFXmtb_b_voxtaunt_00 2891\n#define SFXsfx0B4C 2892\n#define SFXmtb_a_claw_00 2893\n#define SFXmtb_a_swoosh 2894\n#define SFXmtb_b_voxattak_00 2895\n#define SFXmtb_b_walk_00 2896\n#define SFXmtb_b_walk_01 2897\n#define SFXmtb_b_walk_02 2898\n#define SFXmtb_b_voxidle_00 2899\n#define SFXsfx0B54 2900\n#define SFXmtb_r_painbig_00 2901\n#define SFXmtb_r_painsm_00 2902\n#define SFXmtb_b_voxattak_01 2903\n#define SFXmtb_a_icewave_lp_00 2904\n#define SFXsfx0B59 2905\n#define SFXsfx0B5A 2906\n#define SFXmtb_b_voxangry_00 2907\n#define SFXmtb_b_voxangry_01 2908\n#define SFXmtb_a_flame_lp_00 2909\n#define SFXmtb_a_mirv_00 2910\n#define SFXsfx0B5F 2911\n#define SFXsfx0B60 2912\n#define SFXmth_b_dash_00 2913\n#define SFXmth_c_painbig_00 2914\n#define SFXmth_b_voxcall_00 2915\n#define SFXmth_b_voxidle_00 2916\n#define SFXmth_b_voxidle_01 2917\n#define SFXmth_b_voxtaunt_00 2918\n#define SFXsfx0B67 2919\n#define SFXmtb_a_hitwall_00 2920\n#define SFXmth_c_painsm_00 2921\n#define SFXsfx0B6A 2922\n#define SFXsfx0B6B 2923\n#define SFXmtb_a_flameup_lp_00 2924\n#define SFXsfx0B6D 2925\n#define SFXsfx0B6E 2926\n#define SFXsfx0B6F 2927\n#define SFXsfx0B70 2928\n#define SFXsfx0B71 2929\n#define SFXmth_b_emerge_00 2930\n#define SFXsfx0B73 2931\n#define SFXmth_b_voxattak_01 2932\n#define SFXsfx0B75 2933\n#define SFXmth_b_float_lp_00 2934\n#define SFXsfx0B77 2935\n#define SFXmth_a_blast_lp_00 2936\n#define SFXsfx0B79 2937\n#define SFXsfx0B7A 2938\n#define SFXsfx0B7B 2939\n#define SFXmth_a_blasthit_00 2940\n#define SFXsfx0B7D 2941\n#define SFXsfx0B7E 2942\n#define SFXmtb_c_cinemove_00 2943\n#define SFXsfx0B80 2944\n#define SFXmtb_c_land_00 2945\n#define SFXmtb_c_wakeup_00 2946\n#define SFXsfx0B83 2947\n#define SFXsfx0B84 2948\n#define SFXsfx0B85 2949\n#define SFXsfx0B86 2950\n#define SFXsfx0B87 2951\n#define SFXmtb_a_tractor_lp_00 2952\n#define SFXmth_a_swing_00 2953\n#define SFXsfx0B8A 2954\n#define SFXsfx0B8B 2955\n#define SFXsfx0B8C 2956\n#define SFXsfx0B8D 2957\n#define SFXsfx0B8E 2958\n#define SFXmtb_b_land_00 2959\n#define SFXmth_c_blur_00 2960\n#define SFXsfx0B91 2961\n#define SFXsfx0B92 2962\n#define SFXsfx0B93 2963\n#define SFXsfx0B94 2964\n#define SFXsfx0B95 2965\n#define SFXsfx0B96 2966\n#define SFXmtb_a_nrgchg_00 2967\n#define SFXsfx0B98 2968\n#define SFXmtb_a_nrgfire_lp_00 2969\n#define SFXsfx0B9A 2970\n#define SFXsfx0B9B 2971\n"
  },
  {
    "path": "Runtime/Audio/SFX/MinesWorld.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: MinesWorld\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPMinesWorld 48\n\n#define SFXsfx08EC 2284\n#define SFXsfx08ED 2285\n#define SFXsfx08EE 2286\n#define SFXsfx08EF 2287\n#define SFXsfx08F0 2288\n#define SFXsfx08F1 2289\n#define SFXmin_x_cranestop_00 2290\n#define SFXsfx08F3 2291\n#define SFXmin_x_piston_00 2292\n#define SFXsfx08F5 2293\n#define SFXsfx08F6 2294\n#define SFXsfx08F7 2295\n#define SFXsfx08F8 2296\n#define SFXmin_x_crane_lp_00 2297\n#define SFXsfx08FA 2298\n#define SFXsfx08FB 2299\n#define SFXsfx08FC 2300\n#define SFXsfx08FD 2301\n#define SFXsfx08FE 2302\n#define SFXsfx08FF 2303\n#define SFXsfx0900 2304\n#define SFXsfx0901 2305\n#define SFXsfx0902 2306\n#define SFXopr_c_land_00 2307\n#define SFXsfx0904 2308\n#define SFXsfx0905 2309\n#define SFXsfx0906 2310\n#define SFXsfx0907 2311\n#define SFXsfx0908 2312\n#define SFXmin_x_gears_lp_01 2313\n#define SFXsfx090A 2314\n#define SFXsfx090B 2315\n#define SFXsfx090C 2316\n#define SFXsfx090D 2317\n#define SFXsfx090E 2318\n#define SFXsfx090F 2319\n#define SFXsfx0910 2320\n#define SFXsfx0911 2321\n#define SFXsfx0912 2322\n#define SFXsfx0913 2323\n#define SFXsfx0914 2324\n#define SFXsfx0915 2325\n#define SFXsfx0916 2326\n#define SFXmin_x_turbine_lp_00 2327\n#define SFXsfx0918 2328\n#define SFXsfx0919 2329\n#define SFXsfx091A 2330\n#define SFXsfx091B 2331\n#define SFXsfx091C 2332\n#define SFXsfx091D 2333\n#define SFXsfx091E 2334\n#define SFXsfx091F 2335\n#define SFXsfx0920 2336\n#define SFXsfx0921 2337\n#define SFXsfx0922 2338\n#define SFXsfx0923 2339\n#define SFXsfx0924 2340\n#define SFXsfx0925 2341\n#define SFXsfx0926 2342\n#define SFXsfx0927 2343\n#define SFXsfx0928 2344\n#define SFXsfx0929 2345\n#define SFXsfx092A 2346\n#define SFXsfx092B 2347\n#define SFXsfx092C 2348\n#define SFXsfx092D 2349\n#define SFXsfx092E 2350\n#define SFXsfx092F 2351\n#define SFXsfx0930 2352\n#define SFXsfx0931 2353\n#define SFXsfx0932 2354\n#define SFXsfx0933 2355\n#define SFXsfx0934 2356\n#define SFXsfx0935 2357\n#define SFXsfx0936 2358\n#define SFXsfx0937 2359\n#define SFXsfx0938 2360\n#define SFXsfx0939 2361\n#define SFXsfx093A 2362\n#define SFXsfx093B 2363\n#define SFXsfx093C 2364\n"
  },
  {
    "path": "Runtime/Audio/SFX/Misc.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: Misc\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPMisc 39\n\n#define SFXdor_x_close_00 1128\n#define SFXdor_x_open_00 1129\n#define SFXsfx046A 1130\n#define SFXpik_x_idle_00 1131\n#define SFXsfx046C 1132\n#define SFXamb_x_rumble_lp_00 1133\n#define SFXsfx046E 1134\n#define SFXpik_x_morphamb_lp_00 1135\n#define SFXpik_x_powerup_00 1136\n#define SFXsfx0471 1137\n#define SFXsfx0472 1138\n#define SFXsfx0473 1139\n#define SFXsfx0474 1140\n#define SFXamb_x_splash_02 1141\n#define SFXsfx0476 1142\n#define SFXsfx0477 1143\n#define SFXsfx0478 1144\n#define SFXsfx0479 1145\n#define SFXsfx047A 1146\n#define SFXpik_x_elevamb_lp_00 1147\n#define SFXsfx047C 1148\n#define SFXeff_x_largeburndeath_lp_00 1149\n#define SFXeff_x_smallburndeath_lp_00 1150\n#define SFXeff_x_fire_lp_00 1151\n#define SFXsfx0480 1152\n#define SFXsfx0481 1153\n#define SFXci2_x_eletric_00 1154\n#define SFXci3_x_electric_lp_00 1155\n#define SFXmac_x_fire_lp_00 1156\n#define SFXsfx0485 1157\n#define SFXci4_x_electric_lp_00 1158\n#define SFXsfx0487 1159\n#define SFXsfx0488 1160\n#define SFXsfx0489 1161\n#define SFXsfx048A 1162\n#define SFXsfx048B 1163\n#define SFXdrn_b_smoke_lp_00 1164\n#define SFXga2_r_explode_00 1165\n#define SFXsfx048E 1166\n#define SFXsfx048F 1167\n#define SFXsfx0490 1168\n#define SFXsfx0491 1169\n#define SFXmag_r_explode_00 1170\n#define SFXsfx0493 1171\n#define SFXsfx0494 1172\n#define SFXsfx0495 1173\n#define SFXsfx0496 1174\n#define SFXsfx0497 1175\n#define SFXeff_x_icebrk_00 1176\n#define SFXeff_x_icebrk_01 1177\n#define SFXsfx049A 1178\n#define SFXsfx049B 1179\n#define SFXsfx049C 1180\n#define SFXsfx049D 1181\n#define SFXsfx049E 1182\n#define SFXsfx049F 1183\n#define SFXsfx04A0 1184\n#define SFXsfx04A1 1185\n#define SFXmac_x_fireup_00 1186\n#define SFXsfx04A3 1187\n#define SFXsfx04A4 1188\n#define SFXsfx04A5 1189\n#define SFXsfx04A6 1190\n#define SFXsfx04A7 1191\n#define SFXsfx04A8 1192\n#define SFXsfx04A9 1193\n#define SFXsfx04AA 1194\n#define SFXsfx04AB 1195\n#define SFXsfx04AC 1196\n#define SFXeff_x_electro_lp_00 1197\n#define SFXeff_x_electro_lp_01 1198\n#define SFXsfx04AF 1199\n#define SFXsfx04B0 1200\n#define SFXsfx04B1 1201\n#define SFXsfx04B2 1202\n#define SFXsfx04B3 1203\n#define SFXsfx04B4 1204\n#define SFXsfx04B5 1205\n#define SFXsfx04B6 1206\n#define SFXsfx04B7 1207\n#define SFXsfx04B8 1208\n#define SFXsfx04B9 1209\n#define SFXsfx04BA 1210\n#define SFXsfx04BB 1211\n#define SFXsfx04BC 1212\n#define SFXsfx04BD 1213\n#define SFXsfx04BE 1214\n#define SFXsfx04BF 1215\n#define SFXsfx04C0 1216\n#define SFXsfx04C1 1217\n#define SFXsfx04C2 1218\n#define SFXocu_b_gas_lp_00 1219\n#define SFXsfx04C4 1220\n#define SFXsfx04C5 1221\n#define SFXsfx04C6 1222\n#define SFXtha_a_electric_00 1223\n#define SFXsfx04C8 1224\n#define SFXdrn_r_empelec_00 1225\n#define SFXeff_x_frozen_00 1226\n#define SFXeff_x_frozen_01 1227\n#define SFXsfx04CC 1228\n#define SFXsfx04CD 1229\n#define SFXsfx04CE 1230\n#define SFXsfx04CF 1231\n#define SFXsfx04D0 1232\n#define SFXsfx04D1 1233\n#define SFXepr_b_elec_lp_00 1234\n#define SFXsfx04D3 1235\n#define SFXsfx04D4 1236\n#define SFXsfx04D5 1237\n#define SFXsfx04D6 1238\n#define SFXsfx04D7 1239\n#define SFXamb_x_gatestop_00 1240\n#define SFXsfx04D9 1241\n#define SFXsfx04DA 1242\n#define SFXsfx04DB 1243\n#define SFXsfx04DC 1244\n#define SFXsfx04DD 1245\n#define SFXsfx04DE 1246\n#define SFXsfx04DF 1247\n#define SFXsfx04E0 1248\n#define SFXsfx04E1 1249\n#define SFXsfx04E2 1250\n#define SFXsfx04E3 1251\n#define SFXsfx04E4 1252\n#define SFXsfx04E5 1253\n#define SFXsfx04E6 1254\n#define SFXsfx04E7 1255\n#define SFXsfx04E8 1256\n#define SFXsfx04E9 1257\n#define SFXsfx04EA 1258\n#define SFXsfx04EB 1259\n#define SFXsfx04EC 1260\n#define SFXsfx04ED 1261\n#define SFXsfx04EE 1262\n#define SFXsfx04EF 1263\n#define SFXsfx04F0 1264\n#define SFXsfx04F1 1265\n#define SFXsfx04F2 1266\n#define SFXsfx04F3 1267\n#define SFXsfx04F4 1268\n#define SFXamb_x_gateup_00 1269\n#define SFXsfx04F6 1270\n#define SFXsfx04F7 1271\n#define SFXsfx04F8 1272\n#define SFXsfx04F9 1273\n#define SFXsfx04FA 1274\n#define SFXrid_r_explode_00 1275\n#define SFXsfx04FC 1276\n#define SFXsfx04FD 1277\n#define SFXsfx04FE 1278\n#define SFXsfx04FF 1279\n#define SFXsfx0500 1280\n#define SFXsfx0501 1281\n#define SFXsfx0502 1282\n#define SFXsfx0503 1283\n#define SFXsfx0504 1284\n#define SFXsfx0505 1285\n#define SFXamb_x_steamsml_lp_00 1286\n#define SFXsfx0507 1287\n#define SFXsfx0508 1288\n#define SFXsfx0509 1289\n#define SFXamb_c_suitlose_lp_00 1290\n#define SFXsfx050B 1291\n#define SFXsfx050C 1292\n#define SFXsfx050D 1293\n#define SFXsfx050E 1294\n#define SFXsfx050F 1295\n#define SFXsfx0510 1296\n#define SFXsfx0511 1297\n#define SFXsfx0512 1298\n#define SFXsfx0513 1299\n#define SFXsfx0514 1300\n#define SFXsfx0515 1301\n#define SFXsfx0516 1302\n#define SFXsfx0517 1303\n#define SFXsfx0518 1304\n#define SFXsfx0519 1305\n#define SFXsfx051A 1306\n#define SFXsfx051B 1307\n#define SFXsfx051C 1308\n#define SFXsfx051D 1309\n#define SFXsfx051E 1310\n#define SFXsfx051F 1311\n#define SFXsfx0520 1312\n#define SFXsfx0521 1313\n#define SFXsfx0522 1314\n#define SFXsfx0523 1315\n#define SFXsfx0524 1316\n#define SFXsfx0525 1317\n#define SFXsfx0526 1318\n#define SFXsfx0527 1319\n#define SFXsfx0528 1320\n#define SFXsfx0529 1321\n#define SFXsfx052A 1322\n#define SFXsfx052B 1323\n#define SFXrid_c_elec_lp_00 1324\n#define SFXsfx052D 1325\n#define SFXsfx052E 1326\n#define SFXsfx052F 1327\n#define SFXsfx0530 1328\n#define SFXsfx0531 1329\n#define SFXsfx0532 1330\n#define SFXsfx0533 1331\n#define SFXsfx0534 1332\n#define SFXsfx0535 1333\n#define SFXsfx0536 1334\n#define SFXsfx0537 1335\n#define SFXsfx0538 1336\n#define SFXsfx0539 1337\n#define SFXsfx053A 1338\n#define SFXsfx053B 1339\n#define SFXsfx053C 1340\n#define SFXsfx053D 1341\n#define SFXsfx053E 1342\n#define SFXsfx053F 1343\n#define SFXsfx0540 1344\n#define SFXsfx0541 1345\n#define SFXsfx0542 1346\n#define SFXsfx0543 1347\n#define SFXsfx0544 1348\n#define SFXsfx0545 1349\n#define SFXsfx0546 1350\n#define SFXsfx0547 1351\n#define SFXsfx0548 1352\n#define SFXsfx0549 1353\n#define SFXsfx054A 1354\n#define SFXsfx054B 1355\n#define SFXsfx054C 1356\n#define SFXsfx054D 1357\n#define SFXsfx054E 1358\n#define SFXsfx054F 1359\n#define SFXsfx0550 1360\n#define SFXsfx0551 1361\n#define SFXsfx0552 1362\n#define SFXsfx0553 1363\n#define SFXsfx0554 1364\n#define SFXsfx0555 1365\n#define SFXsfx0556 1366\n#define SFXsfx0557 1367\n#define SFXsfx0558 1368\n#define SFXsfx0559 1369\n#define SFXsfx055A 1370\n#define SFXsfx055B 1371\n#define SFXsfx055C 1372\n#define SFXsfx055D 1373\n#define SFXsfx055E 1374\n"
  },
  {
    "path": "Runtime/Audio/SFX/MiscSamus.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: MiscSamus\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPMiscSamus 41\n\n#define SFXsam_wlkstone_00 1465\n#define SFXsam_wlkstone_01 1466\n#define SFXsam_suit_damage 1467\n#define SFXsam_ball_jump 1468\n#define SFXsam_b_highland_00 1469\n#define SFXsam_b_jump_00 1470\n#define SFXsam_firstjump 1471\n#define SFXsam_landdirt_00 1472\n#define SFXsfx05C1 1473\n#define SFXsfx05C2 1474\n#define SFXsam_ballland_stone 1475\n#define SFXsam_ball_boost 1476\n#define SFXsam_ball_charge_lp 1477\n#define SFXsam_b_morphin_00 1478\n#define SFXsam_b_morphout_00 1479\n#define SFXsam_ballroll_dirt 1480\n#define SFXsfx05C9 1481\n#define SFXsfx05CA 1482\n#define SFXsfx05CB 1483\n#define SFXsam_wlkwater_00 1484\n#define SFXsam_wlkwater_01 1485\n#define SFXsam_damage_poison_lp 1486\n#define SFXsfx05CF 1487\n#define SFXsfx05D0 1488\n#define SFXsam_vox_damage 1489\n#define SFXsam_landmetl_00 1490\n#define SFXsam_ball_damage 1491\n#define SFXsam_b_movearm_00 1492\n#define SFXsam_wlkgrate_00 1493\n#define SFXsam_wlkgrate_01 1494\n#define SFXsam_wlkmetal_00 1495\n#define SFXsam_wlkmetal_01 1496\n#define SFXsam_wlkdirt_00 1497\n#define SFXsam_ballland_grate 1498\n#define SFXsam_wlkdirt_01 1499\n#define SFXsam_ballroll_grate 1500\n#define SFXsam_ballroll_metal 1501\n#define SFXsam_ballroll_stone 1502\n#define SFXsfx05DF 1503\n#define SFXsam_ballland_metal 1504\n#define SFXsam_b_movearm_01 1505\n#define SFXsfx05E2 1506\n#define SFXsam_landgrate_00 1507\n#define SFXsam_landstone_00 1508\n#define SFXsfx05E5 1509\n#define SFXsfx05E6 1510\n#define SFXsam_vox_damage15 1511\n#define SFXsam_vox_damage30 1512\n#define SFXsam_ball_damage15 1513\n#define SFXsam_ball_damage30 1514\n#define SFXsfx05EB 1515\n#define SFXsam_death 1516\n#define SFXsfx05ED 1517\n#define SFXsfx05EE 1518\n#define SFXsam_b_landmetl_01 1519\n#define SFXsfx05F0 1520\n#define SFXsfx05F1 1521\n#define SFXsfx05F2 1522\n#define SFXsam_spider_lp 1523\n#define SFXsfx05F4 1524\n#define SFXsam_ball_wallhit 1525\n#define SFXsam_grapple_fire 1526\n#define SFXsam_grapple_lp 1527\n#define SFXsam_grapple_swoosh 1528\n#define SFXsam_wlkwood_00 1529\n#define SFXsam_wlkwood_01 1530\n#define SFXsam_landwood_00 1531\n#define SFXsam_b_movefla_00 1532\n#define SFXsam_ballland_wood 1533\n#define SFXsam_ballroll_wood 1534\n#define SFXsfx05FF 1535\n#define SFXsfx0600 1536\n#define SFXsfx0601 1537\n#define SFXsfx0602 1538\n#define SFXsfx0603 1539\n#define SFXsfx0604 1540\n#define SFXsam_b_jumpcine_00 1541\n#define SFXsam_landphazon_00 1542\n#define SFXsfx0607 1543\n#define SFXsfx0608 1544\n#define SFXsam_ballland_phazon 1545\n#define SFXsfx060A 1546\n#define SFXsam_ballroll_phazon 1547\n#define SFXsam_b_voxland_00 1548\n#define SFXsfx060D 1549\n#define SFXsam_voxland_02 1550\n#define SFXsfx060F 1551\n#define SFXsfx0610 1552\n#define SFXsam_wlkphazon_00 1553\n#define SFXsam_wlkphazon_01 1554\n#define SFXpds_b_water_03 1555\n#define SFXsam_b_butpress_00 1556\n#define SFXsam_b_butpress_01 1557\n#define SFXsam_b_panlclos_00 1558\n#define SFXsam_b_panlopen_00 1559\n#define SFXsam_dash 1560\n#define SFXsam_b_move_00 1561\n#define SFXsam_b_move_01 1562\n#define SFXsam_b_voxjump_00 1563\n#define SFXsfx061C 1564\n#define SFXsfx061D 1565\n#define SFXsfx061E 1566\n#define SFXsam_b_wlkmetal_02 1567\n#define SFXsam_b_wlkmetal_03 1568\n#define SFXsam_landgrass_00 1569\n#define SFXsam_b_wlkgrass_00 1570\n#define SFXsam_b_wlkgrass_01 1571\n#define SFXsam_b_spin_lp_00 1572\n#define SFXsam_b_landorg_00 1573\n#define SFXsfx0626 1574\n#define SFXsam_ballland_org 1575\n#define SFXsam_ballroll_org 1576\n#define SFXsam_b_wlkorg_00 1577\n#define SFXsam_b_wlkorg_01 1578\n#define SFXsam_landmud_00 1579\n#define SFXsam_ballland_grass 1580\n#define SFXsam_ballland_mud 1581\n#define SFXsfx062E 1582\n#define SFXsam_ballroll_grass 1583\n#define SFXsam_ballroll_mud 1584\n#define SFXsam_wlkmud_00 1585\n#define SFXsam_wlkmud_01 1586\n#define SFXsam_b_landcine_01 1587\n#define SFXsfx0634 1588\n#define SFXsfx0635 1589\n#define SFXsfx0636 1590\n#define SFXsam_vox_exhausted 1591\n#define SFXsam_landsnow_00 1592\n#define SFXsam_b_landsnow_01 1593\n#define SFXsam_wlksnow_00 1594\n#define SFXsam_wlksnow_01 1595\n#define SFXsam_b_wlksnow_02 1596\n#define SFXsam_b_wlksnow_03 1597\n#define SFXsfx063E 1598\n#define SFXsfx063F 1599\n#define SFXsam_b_landcine_00 1600\n#define SFXgab_b_wlksnow_00 1601\n#define SFXgab_b_wlksnow_01 1602\n#define SFXsfx0643 1603\n#define SFXsam_r_hithelm_00 1604\n#define SFXsfx0645 1605\n#define SFXsfx0646 1606\n#define SFXsam_landgrass_02 1607\n#define SFXsam_landgrate_02 1608\n#define SFXsfx0649 1609\n#define SFXsfx064A 1610\n#define SFXsam_b_landmetl_02 1611\n#define SFXsam_landmud_02 1612\n#define SFXsam_landorg_02 1613\n#define SFXsam_landphazon_02 1614\n#define SFXsam_landdirt_02 1615\n#define SFXsam_landsnow_02 1616\n#define SFXsam_landstone_02 1617\n#define SFXsam_landwood_02 1618\n#define SFXsam_wlkice_00 1619\n#define SFXsam_wlkice_01 1620\n#define SFXsfx0655 1621\n#define SFXsfx0656 1622\n#define SFXsam_b_landgras_01 1623\n#define SFXsam_landice_00 1624\n#define SFXsfx0659 1625\n#define SFXsam_landice_02 1626\n#define SFXsam_ballland_ice 1627\n#define SFXsam_ballland_snow 1628\n#define SFXpar_b_wlksnow_00 1629\n#define SFXpar_b_wlksnow_01 1630\n#define SFXsfx065F 1631\n#define SFXsfx0660 1632\n#define SFXsam_vox_damage_poison 1633\n#define SFXsfx0662 1634\n#define SFXsfx0663 1635\n#define SFXsam_c_suithit_00 1636\n#define SFXsam_c_suithit_01 1637\n#define SFXsam_c_suithitv_00 1638\n#define SFXsam_c_suitmov1_00 1639\n#define SFXsam_r_phazhit_lp_00 1640\n#define SFXsam_c_suitfall_00 1641\n#define SFXsam_c_suitfall_01 1642\n#define SFXsam_c_suitmov2_00 1643\n#define SFXsam_c_suitmov2_01 1644\n#define SFXsfx066D 1645\n#define SFXsfx066E 1646\n#define SFXfpr_b_land_00 1647\n#define SFXfpr_b_land_01 1648\n#define SFXsfx0671 1649\n#define SFXspr_b_land_00 1650\n#define SFXspr_b_land_01 1651\n#define SFXsfx0674 1652\n#define SFXsam_vox_damage_phazon 1653\n#define SFXsfx0676 1654\n#define SFXsfx0677 1655\n#define SFXsam_vox_damage_heat 1656\n#define SFXsfx0679 1657\n#define SFXsfx067A 1658\n#define SFXsfx067B 1659\n#define SFXsam_b_wlkstone_02 1660\n#define SFXsam_b_wlkstone_03 1661\n#define SFXsam_b_wlkdirt_02 1662\n#define SFXsam_b_wlkdirt_03 1663\n#define SFXsam_b_move_02 1664\n#define SFXsam_b_move_03 1665\n#define SFXsfx0682 1666\n#define SFXsam_c_mpwlkorg_00 1667\n#define SFXsam_c_mpwlkorg_01 1668\n#define SFXci7_x_spin_lp_00 1669\n#define SFXsfx0686 1670\n#define SFXsam_c_butpress_00 1671\n#define SFXsam_c_butpress_01 1672\n#define SFXsam_c_intrmove_02 1673\n#define SFXsam_c_intrmove_03 1674\n#define SFXsam_c_intrspin_lp_00 1675\n#define SFXsam_c_iwlkmetal_02 1676\n#define SFXsam_c_iwlkmetal_03 1677\n#define SFXsam_c_movearm_00 1678\n#define SFXsam_c_movearm_01 1679\n#define SFXsam_c_moveend_00 1680\n#define SFXsam_c_moveend_01 1681\n#define SFXsam_c_spinend_lp_00 1682\n#define SFXsfx0693 1683\n#define SFXsam_landlavastone_00 1684\n#define SFXsfx0695 1685\n#define SFXsam_landlavastone_02 1686\n#define SFXsam_ballland_lava 1687\n#define SFXsam_ballroll_lavastone 1688\n#define SFXsam_wlklavastone_00 1689\n#define SFXsam_wlklavastone_01 1690\n#define SFXsfx069B 1691\n#define SFXsfx069C 1692\n#define SFXsfx069D 1693\n#define SFXsfx069E 1694\n#define SFXsfx069F 1695\n#define SFXsfx06A0 1696\n#define SFXsfx06A1 1697\n#define SFXsfx06A2 1698\n#define SFXsfx06A3 1699\n#define SFXsfx06A4 1700\n#define SFXsfx06A5 1701\n#define SFXsfx06A6 1702\n#define SFXsfx06A7 1703\n#define SFXsfx06A8 1704\n#define SFXsfx06A9 1705\n#define SFXsfx06AA 1706\n#define SFXsfx06AB 1707\n#define SFXsfx06AC 1708\n#define SFXsfx06AD 1709\n#define SFXsfx06AE 1710\n#define SFXsfx06AF 1711\n#define SFXsfx06B0 1712\n#define SFXsfx06B1 1713\n#define SFXsfx06B2 1714\n#define SFXsfx06B3 1715\n#define SFXsfx06B4 1716\n#define SFXsfx06B5 1717\n#define SFXsfx06B6 1718\n#define SFXsfx06B7 1719\n#define SFXsfx06B8 1720\n#define SFXsfx06B9 1721\n#define SFXsfx06BA 1722\n#define SFXsfx06BB 1723\n"
  },
  {
    "path": "Runtime/Audio/SFX/OmegaPirate.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: OmegaPirate\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPOmegaPirate 57\n\n#define SFXsfx0B0F 2831\n#define SFXsfx0B10 2832\n#define SFXopr_b_voxcall_00 2833\n#define SFXopr_b_voxcall_01 2834\n#define SFXopr_b_voxlaugh_00 2835\n#define SFXopr_r_moan_00 2836\n#define SFXsfx0B15 2837\n#define SFXsfx0B16 2838\n#define SFXopr_b_run_01 2839\n#define SFXsfx0B18 2840\n#define SFXopr_b_voxalert_00 2841\n#define SFXopr_b_voxalert_01 2842\n#define SFXopr_b_voxattak_00 2843\n#define SFXopr_b_voxattak_01 2844\n#define SFXopr_b_voxblok_00 2845\n#define SFXopr_b_voxidle_00 2846\n#define SFXopr_b_voxidle_01 2847\n#define SFXopr_b_voxpiss_00 2848\n#define SFXopr_b_voxtaunt_00 2849\n#define SFXopr_b_walklite_00 2850\n#define SFXopr_b_walklite_01 2851\n#define SFXopr_b_walk_00 2852\n#define SFXopr_b_walk_01 2853\n#define SFXopr_b_healnrg_lp_00 2854\n#define SFXsfx0B27 2855\n#define SFXsfx0B28 2856\n#define SFXsfx0B29 2857\n#define SFXopr_r_pain_00 2858\n#define SFXopr_r_pain_01 2859\n#define SFXsfx0B2C 2860\n#define SFXopr_b_invis_00 2861\n#define SFXopr_b_voxready_00 2862\n#define SFXsfx0B2F 2863\n#define SFXsfx0B30 2864\n#define SFXopr_r_pain_02 2865\n#define SFXsfx0B32 2866\n#define SFXopr_a_grenchrg_00 2867\n#define SFXsfx0B34 2868\n#define SFXopr_a_grenade_00 2869\n#define SFXsfx0B36 2870\n#define SFXsfx0B37 2871\n#define SFXsfx0B38 2872\n#define SFXsfx0B39 2873\n#define SFXsfx0B3A 2874\n#define SFXsfx0B3B 2875\n#define SFXsfx0B3C 2876\n#define SFXsfx0B3D 2877\n#define SFXopr_r_death_01 2878\n#define SFXsfx0B3F 2879\n#define SFXopr_c_samswoosh_00 2880\n#define SFXsfx0B41 2881\n#define SFXsfx0B42 2882\n#define SFXsfx0B43 2883\n#define SFXsfx0B44 2884\n#define SFXsfx0B45 2885\n#define SFXsfx0B46 2886\n#define SFXsfx0B47 2887\n#define SFXsfx0B48 2888\n#define SFXsfx0B49 2889\n#define SFXsfx0B4A 2890\n"
  },
  {
    "path": "Runtime/Audio/SFX/OverWorld.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: OverWorld\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPOverWorld 51\n\n#define SFXsfx09E0 2528\n#define SFXsfx09E1 2529\n#define SFXsfx09E2 2530\n#define SFXsfx09E3 2531\n#define SFXsfx09E4 2532\n#define SFXsfx09E5 2533\n#define SFXsfx09E6 2534\n#define SFXsfx09E7 2535\n#define SFXsfx09E8 2536\n#define SFXsfx09E9 2537\n#define SFXsfx09EA 2538\n#define SFXsfx09EB 2539\n#define SFXsfx09EC 2540\n#define SFXsfx09ED 2541\n#define SFXsfx09EE 2542\n#define SFXsfx09EF 2543\n#define SFXsfx09F0 2544\n#define SFXsfx09F1 2545\n#define SFXcrb_b_hiss_00 2546\n#define SFXcrb_b_idle_00 2547\n#define SFXsfx09F4 2548\n#define SFXsfx09F5 2549\n#define SFXsfx09F6 2550\n#define SFXsfx09F7 2551\n#define SFXsfx09F8 2552\n#define SFXove_x_spinbars_lp 2553\n#define SFXsfx09FA 2554\n#define SFXsfx09FB 2555\n#define SFXsfx09FC 2556\n#define SFXsfx09FD 2557\n#define SFXsfx09FE 2558\n#define SFXsfx09FF 2559\n#define SFXsfx0A00 2560\n#define SFXsfx0A01 2561\n#define SFXsfx0A02 2562\n#define SFXsfx0A03 2563\n#define SFXsfx0A04 2564\n#define SFXsfx0A05 2565\n#define SFXsfx0A06 2566\n#define SFXsfx0A07 2567\n#define SFXsfx0A08 2568\n#define SFXsfx0A09 2569\n#define SFXsfx0A0A 2570\n#define SFXsfx0A0B 2571\n#define SFXsfx0A0C 2572\n#define SFXsfx0A0D 2573\n#define SFXsfx0A0E 2574\n#define SFXsfx0A0F 2575\n#define SFXsfx0A10 2576\n#define SFXsfx0A11 2577\n#define SFXsfx0A12 2578\n#define SFXsfx0A13 2579\n#define SFXsfx0A14 2580\n#define SFXlbm_c_beam_lp_01 2581\n#define SFXsfx0A16 2582\n#define SFXsfx0A17 2583\n#define SFXsfx0A18 2584\n#define SFXsfx0A19 2585\n#define SFXsfx0A1A 2586\n#define SFXsfx0A1B 2587\n#define SFXsfx0A1C 2588\n#define SFXsfx0A1D 2589\n#define SFXsfx0A1E 2590\n#define SFXsfx0A1F 2591\n#define SFXsfx0A20 2592\n#define SFXsfx0A21 2593\n#define SFXsfx0A22 2594\n#define SFXsfx0A23 2595\n#define SFXsfx0A24 2596\n#define SFXsfx0A25 2597\n#define SFXsfx0A26 2598\n#define SFXsfx0A27 2599\n"
  },
  {
    "path": "Runtime/Audio/SFX/Parasite.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: Parasite\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPParasite 22\n\n#define SFXpar_a_voxangry_00 624\n#define SFXsfx0271 625\n#define SFXsfx0272 626\n#define SFXsfx0273 627\n#define SFXsfx0274 628\n#define SFXpar_b_idle_02 629\n#define SFXpar_b_munch_00 630\n#define SFXsfx0277 631\n#define SFXpar_b_run_00 632\n#define SFXpar_b_run_01 633\n#define SFXsfx027A 634\n#define SFXpar_b_walk_00 635\n#define SFXpar_b_walk_01 636\n#define SFXsfx027D 637\n#define SFXpar_b_idlelone_02 638\n#define SFXpar_r_impact_00 639\n#define SFXsfx0280 640\n#define SFXsfx0281 641\n#define SFXsfx0282 642\n#define SFXsfx0283 643\n#define SFXsfx0284 644\n#define SFXsfx0285 645\n#define SFXsfx0286 646\n#define SFXsfx0287 647\n"
  },
  {
    "path": "Runtime/Audio/SFX/Phazon.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: Phazon\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPPhazon 66\n\n#define SFXphz_damage_lp 3114\n#define SFXsfx0C2B 3115\n#define SFXsfx0C2C 3116\n#define SFXsfx0C2D 3117\n#define SFXsfx0C2E 3118\n#define SFXsfx0C2F 3119\n#define SFXsfx0C30 3120\n"
  },
  {
    "path": "Runtime/Audio/SFX/PhazonGun.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: PhazonGun\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPPhazonGun 68\n\n#define SFXphg_charge_lp 3141\n#define SFXsfx0C46 3142\n"
  },
  {
    "path": "Runtime/Audio/SFX/PuddleSpore.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: PuddleSpore\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPPuddleSpore 23\n\n#define SFXsfx0288 648\n#define SFXpds_a_voxactive_00 649\n#define SFXpds_b_bubbles_00 650\n#define SFXpds_b_open_00 651\n#define SFXpds_b_slam_00 652\n#define SFXpds_b_voxopen_lp_00 653\n#define SFXpds_b_voxslam_00 654\n#define SFXpds_b_water_00 655\n#define SFXpds_b_water_01 656\n#define SFXpds_lava_damage_lp 657\n#define SFXsfx0292 658\n#define SFXpds_r_voxpain_02 659\n#define SFXsfx0294 660\n#define SFXsfx0295 661\n#define SFXsfx0296 662\n#define SFXsfx0297 663\n#define SFXsfx0298 664\n#define SFXsfx0299 665\n#define SFXsfx029A 666\n#define SFXsfx029B 667\n#define SFXsfx029C 668\n#define SFXsfx029D 669\n#define SFXsfx029E 670\n#define SFXsfx029F 671\n#define SFXsfx02A0 672\n#define SFXsfx02A1 673\n#define SFXsfx02A2 674\n"
  },
  {
    "path": "Runtime/Audio/SFX/PuddleToad.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: PuddleToad\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPPuddleToad 24\n\n#define SFXpud_a_suckin_00 675\n#define SFXpud_a_spitout_00 676\n#define SFXsfx02A5 677\n#define SFXpud_b_close_00 678\n#define SFXpud_b_splat_00 679\n#define SFXsfx02A8 680\n#define SFXsfx02A9 681\n#define SFXsfx02AA 682\n#define SFXpud_b_voxclose_00 683\n#define SFXpud_a_suckin_lp_01 684\n#define SFXsfx02AD 685\n#define SFXsfx02AE 686\n#define SFXpud_b_growl_00 687\n#define SFXpud_b_squish_lp_00 688\n#define SFXsfx02B1 689\n#define SFXsfx02B2 690\n#define SFXsfx02B3 691\n#define SFXsfx02B4 692\n#define SFXsfx02B5 693\n#define SFXsfx02B6 694\n#define SFXsfx02B7 695\n#define SFXsfx02B8 696\n#define SFXsfx02B9 697\n#define SFXsfx02BA 698\n#define SFXsfx02BB 699\n#define SFXsfx02BC 700\n#define SFXsfx02BD 701\n#define SFXsfx02BE 702\n#define SFXsfx02BF 703\n"
  },
  {
    "path": "Runtime/Audio/SFX/Puffer.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: Puffer\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPPuffer 25\n\n#define SFXpuf_b_fly_lp_00 704\n#define SFXsfx02C1 705\n#define SFXsfx02C2 706\n#define SFXsfx02C3 707\n#define SFXsfx02C4 708\n#define SFXsfx02C5 709\n#define SFXsfx02C6 710\n"
  },
  {
    "path": "Runtime/Audio/SFX/ReactorDoor.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: ReactorDoor\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPReactorDoor 49\n\n#define SFXdor_x_close_01 2365\n#define SFXdor_x_open_01 2366\n#define SFXsfx093F 2367\n#define SFXint_x_reacdoor_01 2368\n#define SFXint_x_reacdoor_02 2369\n#define SFXint_x_reacdoor_03 2370\n#define SFXint_x_reacdoor_04 2371\n#define SFXint_x_reacdoor_lp_00 2372\n#define SFXsfx0945 2373\n#define SFXsfx0946 2374\n#define SFXsfx0947 2375\n#define SFXsfx0948 2376\n#define SFXsfx0949 2377\n"
  },
  {
    "path": "Runtime/Audio/SFX/Ridley.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: Ridley\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPRidley 56\n\n#define SFXrid_a_flamerake_00 2771\n#define SFXrid_a_flame_lp_00 2772\n#define SFXsfx0AD5 2773\n#define SFXrid_b_flap_00 2774\n#define SFXrid_b_land_00 2775\n#define SFXrid_b_passby_00 2776\n#define SFXrid_b_popup_00 2777\n#define SFXrid_b_voxangry_00 2778\n#define SFXrid_b_voxangry_01 2779\n#define SFXrid_b_voxattack_00 2780\n#define SFXrid_b_voxattack_01 2781\n#define SFXrid_b_voxidle_00 2782\n#define SFXsfx0ADF 2783\n#define SFXrid_b_voxtaunt_00 2784\n#define SFXrid_b_voxtaunt_01 2785\n#define SFXrid_b_walk_00 2786\n#define SFXrid_b_walk_01 2787\n#define SFXsfx0AE4 2788\n#define SFXrid_b_walksm_00 2789\n#define SFXrid_b_walksm_01 2790\n#define SFXrid_r_chestexp_00 2791\n#define SFXrid_r_death_00 2792\n#define SFXrid_r_painbig_00 2793\n#define SFXrid_r_pain_00 2794\n#define SFXsfx0AEB 2795\n#define SFXrid_a_chestglo_00 2796\n#define SFXrid_a_claw_00 2797\n#define SFXsfx0AEE 2798\n#define SFXrid_a_mirv_00 2799\n#define SFXsfx0AF0 2800\n#define SFXrid_a_tail_00 2801\n#define SFXsfx0AF2 2802\n#define SFXsfx0AF3 2803\n#define SFXsfx0AF4 2804\n#define SFXrid_r_pain_lp_00 2805\n#define SFXsfx0AF6 2806\n#define SFXsfx0AF7 2807\n#define SFXsfx0AF8 2808\n#define SFXrid_c_smallexp_00 2809\n#define SFXrid_c_painbig_00 2810\n#define SFXsfx0AFB 2811\n#define SFXsfx0AFC 2812\n#define SFXsfx0AFD 2813\n#define SFXsfx0AFE 2814\n#define SFXsfx0AFF 2815\n#define SFXsfx0B00 2816\n#define SFXsfx0B01 2817\n#define SFXsfx0B02 2818\n#define SFXsfx0B03 2819\n#define SFXsfx0B04 2820\n#define SFXsfx0B05 2821\n#define SFXsfx0B06 2822\n#define SFXsfx0B07 2823\n#define SFXsfx0B08 2824\n#define SFXsfx0B09 2825\n#define SFXsfx0B0A 2826\n#define SFXsfx0B0B 2827\n#define SFXsfx0B0C 2828\n#define SFXsfx0B0D 2829\n#define SFXsfx0B0E 2830\n"
  },
  {
    "path": "Runtime/Audio/SFX/Ripper.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: Ripper\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPRipper 26\n\n#define SFXrip_b_float_lp_00 711\n#define SFXrip_b_scream_00 712\n#define SFXsfx02C9 713\n#define SFXsfx02CA 714\n#define SFXrip_r_impact_00 715\n#define SFXsfx02CC 716\n#define SFXsfx02CD 717\n#define SFXsfx02CE 718\n#define SFXsfx02CF 719\n#define SFXsfx02D0 720\n"
  },
  {
    "path": "Runtime/Audio/SFX/RuinsWorld.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: RuinsWorld\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPRuinsWorld 50\n\n#define SFXsfx094A 2378\n#define SFXsfx094B 2379\n#define SFXsfx094C 2380\n#define SFXsfx094D 2381\n#define SFXeye_b_blink_00 2382\n#define SFXeye_b_impact_00 2383\n#define SFXsfx0950 2384\n#define SFXsfx0951 2385\n#define SFXsfx0952 2386\n#define SFXsfx0953 2387\n#define SFXsfx0954 2388\n#define SFXsfx0955 2389\n#define SFXsfx0956 2390\n#define SFXsfx0957 2391\n#define SFXsfx0958 2392\n#define SFXsfx0959 2393\n#define SFXsfx095A 2394\n#define SFXrui_x_leaves_00 2395\n#define SFXsfx095C 2396\n#define SFXsfx095D 2397\n#define SFXsfx095E 2398\n#define SFXsfx095F 2399\n#define SFXsfx0960 2400\n#define SFXmac_x_stop_00 2401\n#define SFXsfx0962 2402\n#define SFXsfx0963 2403\n#define SFXsfx0964 2404\n#define SFXsfx0965 2405\n#define SFXsfx0966 2406\n#define SFXsfx0967 2407\n#define SFXsfx0968 2408\n#define SFXsfx0969 2409\n#define SFXsfx096A 2410\n#define SFXsfx096B 2411\n#define SFXsfx096C 2412\n#define SFXsfx096D 2413\n#define SFXsfx096E 2414\n#define SFXsfx096F 2415\n#define SFXenc_x_genmove_lp_00 2416\n#define SFXsfx0971 2417\n#define SFXsfx0972 2418\n#define SFXsfx0973 2419\n#define SFXsfx0974 2420\n#define SFXhiv_x_fall_lp_00 2421\n#define SFXsfx0976 2422\n#define SFXsfx0977 2423\n#define SFXhiv_x_open_00 2424\n#define SFXsfx0979 2425\n#define SFXhiv_x_rotate_00 2426\n#define SFXhiv_x_stop_00 2427\n#define SFXsfx097C 2428\n#define SFXsfx097D 2429\n#define SFXrui_x_mapmove_lp_01 2430\n#define SFXsfx097F 2431\n#define SFXsfx0980 2432\n#define SFXsfx0981 2433\n#define SFXsfx0982 2434\n#define SFXsfx0983 2435\n#define SFXhiv_x_closered_lp_00 2436\n#define SFXhiv_x_openred_lp_00 2437\n#define SFXsfx0986 2438\n#define SFXsfx0987 2439\n#define SFXchz_b_balldrop_00 2440\n#define SFXchz_b_balldrop_01 2441\n#define SFXchz_b_release_00 2442\n#define SFXchz_x_down_lp_00 2443\n#define SFXsfx098C 2444\n#define SFXsfx098D 2445\n#define SFXsfx098E 2446\n#define SFXsfx098F 2447\n#define SFXsfx0990 2448\n#define SFXsfx0991 2449\n#define SFXsfx0992 2450\n#define SFXsfx0993 2451\n#define SFXsfx0994 2452\n#define SFXsfx0995 2453\n#define SFXsfx0996 2454\n#define SFXsfx0997 2455\n#define SFXsfx0998 2456\n#define SFXrui_x_mirstop_00 2457\n#define SFXsfx099A 2458\n#define SFXrui_x_slotstop_00 2459\n#define SFXfla_b_bulbopen_00 2460\n#define SFXsfx099D 2461\n#define SFXsfx099E 2462\n#define SFXsfx099F 2463\n#define SFXsfx09A0 2464\n#define SFXsfx09A1 2465\n#define SFXsfx09A2 2466\n#define SFXsfx09A3 2467\n#define SFXsfx09A4 2468\n#define SFXrui_x_flamarmr_00 2469\n#define SFXrui_x_flamarmr_01 2470\n#define SFXrui_x_flamarm_00 2471\n#define SFXrui_x_flamarm_01 2472\n#define SFXrui_x_flamarm_02 2473\n#define SFXrui_x_flamrise_00 2474\n#define SFXrui_x_flamrise_lp_00 2475\n#define SFXrui_x_flamhead_00 2476\n#define SFXrui_x_flamhead_lp_00 2477\n#define SFXrui_x_flamhead_lp_01 2478\n#define SFXsfx09AF 2479\n#define SFXsfx09B0 2480\n#define SFXsfx09B1 2481\n#define SFXsfx09B2 2482\n#define SFXsfx09B3 2483\n#define SFXsfx09B4 2484\n#define SFXsfx09B5 2485\n#define SFXsfx09B6 2486\n#define SFXsfx09B7 2487\n#define SFXsfx09B8 2488\n#define SFXrui_x_mapmove_lp_00 2489\n#define SFXsfx09BA 2490\n#define SFXrui_x_maparm_00 2491\n#define SFXrui_x_mapcover_00 2492\n#define SFXsfx09BD 2493\n#define SFXsfx09BE 2494\n#define SFXsfx09BF 2495\n#define SFXsfx09C0 2496\n#define SFXsfx09C1 2497\n#define SFXsfx09C2 2498\n#define SFXsfx09C3 2499\n#define SFXsfx09C4 2500\n#define SFXmac_x_changed_00 2501\n#define SFXsfx09C6 2502\n#define SFXrui_x_mapstop_00 2503\n#define SFXsfx09C8 2504\n#define SFXrui_x_gatedown_lp_00 2505\n#define SFXsfx09CA 2506\n#define SFXrui_x_gatestop_00 2507\n#define SFXrui_x_gateturn_lp_00 2508\n#define SFXsfx09CD 2509\n#define SFXrui_x_halftrk_00 2510\n#define SFXrui_x_halftrk_lp_00 2511\n#define SFXsfx09D0 2512\n#define SFXsfx09D1 2513\n#define SFXsfx09D2 2514\n#define SFXsfx09D3 2515\n#define SFXdob_x_moveup_lp_00 2516\n#define SFXsfx09D5 2517\n#define SFXsfx09D6 2518\n#define SFXsfx09D7 2519\n#define SFXsfx09D8 2520\n#define SFXsfx09D9 2521\n#define SFXsfx09DA 2522\n#define SFXsfx09DB 2523\n#define SFXsfx09DC 2524\n#define SFXsfx09DD 2525\n#define SFXsfx09DE 2526\n#define SFXsfx09DF 2527\n"
  },
  {
    "path": "Runtime/Audio/SFX/SFX.h",
    "content": "#ifndef DNAMP1_SFX_H\n#define DNAMP1_SFX_H\n\n#include \"Atomic.h\"\n#include \"BetaBeetle.h\"\n#include \"Bird.h\"\n#include \"BloodFlower.h\"\n#include \"Burrower.h\"\n#include \"ChozoGhost.h\"\n#include \"ChubbWeed.h\"\n#include \"CineBoots.h\"\n#include \"CineGeneral.h\"\n#include \"CineGun.h\"\n#include \"CineMorphball.h\"\n#include \"CineSuit.h\"\n#include \"CineVisor.h\"\n#include \"Crater.h\"\n#include \"Crystallite.h\"\n#include \"Drones.h\"\n#include \"EliteSpacePirate.h\"\n#include \"FireFlea.h\"\n#include \"Flaaghra.h\"\n#include \"FlickerBat.h\"\n#include \"FlyingPirate.h\"\n#include \"FrontEnd.h\"\n#include \"GagantuanBeatle.h\"\n#include \"Gnats.h\"\n#include \"Gryzbee.h\"\n#include \"IceCrack.h\"\n#include \"IceWorld.h\"\n#include \"InjuredPirates.h\"\n#include \"IntroBoss.h\"\n#include \"IntroWorld.h\"\n#include \"JellyZap.h\"\n#include \"LavaWorld.h\"\n#include \"Magdolite.h\"\n#include \"Metaree.h\"\n#include \"MetroidPrime.h\"\n#include \"Metroid.h\"\n#include \"MinesWorld.h\"\n#include \"MiscSamus.h\"\n#include \"Misc.h\"\n#include \"OmegaPirate.h\"\n#include \"OverWorld.h\"\n#include \"Parasite.h\"\n#include \"PhazonGun.h\"\n#include \"Phazon.h\"\n#include \"PuddleSpore.h\"\n#include \"PuddleToad.h\"\n#include \"Puffer.h\"\n#include \"ReactorDoor.h\"\n#include \"Ridley.h\"\n#include \"Ripper.h\"\n#include \"RuinsWorld.h\"\n#include \"SFX.h\"\n#include \"SamusShip.h\"\n#include \"Scarab.h\"\n#include \"Seedling.h\"\n#include \"SheeGoth.h\"\n#include \"SnakeWeed.h\"\n#include \"Sova.h\"\n#include \"SpacePirate.h\"\n#include \"SpankWeed.h\"\n#include \"Thardus.h\"\n#include \"TheEnd.h\"\n#include \"Torobyte.h\"\n#include \"Triclops.h\"\n#include \"Turret.h\"\n#include \"UI.h\"\n#include \"WarWasp.h\"\n#include \"Weapons.h\"\n#include \"ZZZ.h\"\n#include \"Zoomer.h\"\n#include \"lumigek.h\"\n#include \"test.h\"\n\n#endif // DNAMP1_SFX_H\n"
  },
  {
    "path": "Runtime/Audio/SFX/SamusShip.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: SamusShip\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPSamusShip 42\n\n#define SFXsas_x_dooropen_00 1724\n#define SFXsas_x_dooropen_01 1725\n#define SFXsas_x_dooropen_02 1726\n#define SFXsas_x_dooropen_03 1727\n#define SFXsas_x_dooropen_04 1728\n#define SFXsas_x_dooropen_05 1729\n#define SFXsfx06C2 1730\n#define SFXsas_x_platrise_lp_01 1731\n#define SFXsas_x_thrusmov_00 1732\n#define SFXsfx06C5 1733\n#define SFXsas_x_thrusmov_02 1734\n#define SFXsas_x_thrusmov_03 1735\n#define SFXsas_x_hover_lp_00 1736\n#define SFXsas_x_thrusfir_lp_01 1737\n#define SFXsas_x_hover_lp_01 1738\n#define SFXsfx06CB 1739\n#define SFXsas_x_thrusfir_lp_04 1740\n#define SFXsfx06CD 1741\n#define SFXsfx06CE 1742\n#define SFXsfx06CF 1743\n#define SFXsfx06D0 1744\n#define SFXsfx06D1 1745\n#define SFXsfx06D2 1746\n#define SFXsfx06D3 1747\n#define SFXsfx06D4 1748\n#define SFXsfx06D5 1749\n#define SFXsfx06D6 1750\n#define SFXsfx06D7 1751\n#define SFXsfx06D8 1752\n#define SFXsfx06D9 1753\n#define SFXsfx06DA 1754\n#define SFXsfx06DB 1755\n"
  },
  {
    "path": "Runtime/Audio/SFX/Scarab.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: Scarab\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPScarab 27\n\n#define SFXsfx02D1 721\n#define SFXsfx02D2 722\n#define SFXsfx02D3 723\n#define SFXsfx02D4 724\n#define SFXsfx02D5 725\n#define SFXsfx02D6 726\n#define SFXsfx02D7 727\n#define SFXsfx02D8 728\n"
  },
  {
    "path": "Runtime/Audio/SFX/Seedling.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: Seedling\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPSeedling 28\n\n#define SFXsed_a_spine_00 729\n#define SFXsed_b_idle_lp_00 730\n#define SFXsfx02DB 731\n#define SFXsfx02DC 732\n#define SFXsed_b_alert_00 733\n#define SFXsfx02DE 734\n#define SFXsfx02DF 735\n#define SFXsfx02E0 736\n#define SFXsfx02E1 737\n#define SFXsfx02E2 738\n#define SFXsfx02E3 739\n#define SFXsfx02E4 740\n#define SFXsfx02E5 741\n#define SFXsfx02E6 742\n#define SFXsfx02E7 743\n#define SFXsfx02E8 744\n#define SFXsfx02E9 745\n#define SFXsfx02EA 746\n#define SFXsfx02EB 747\n#define SFXsfx02EC 748\n"
  },
  {
    "path": "Runtime/Audio/SFX/SheeGoth.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: SheeGoth\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPSheeGoth 29\n\n#define SFXshe_a_fireball_00 749\n#define SFXshe_b_shake_lp_00 750\n#define SFXsfx02EF 751\n#define SFXshe_a_flame_lp_00 752\n#define SFXshe_a_snap_00 753\n#define SFXshe_a_snap_01 754\n#define SFXshe_a_stomp_00 755\n#define SFXshe_a_stomp_01 756\n#define SFXshe_a_voxangry_00 757\n#define SFXshe_a_voxangry_01 758\n#define SFXshe_a_voxangry_03 759\n#define SFXshe_a_voxangry_04 760\n#define SFXsh2_a_voxangry_00 761\n#define SFXsfx02FA 762\n#define SFXsfx02FB 763\n#define SFXshe_b_idle_02 764\n#define SFXsh2_a_voxangry_01 765\n#define SFXshe_b_land_00 766\n#define SFXsh2_a_flame_lp_00 767\n#define SFXshe_b_roar_00 768\n#define SFXsh2_a_snap_00 769\n#define SFXsh2_a_voxangry_03 770\n#define SFXsh2_a_snap_01 771\n#define SFXshe_b_walk_00 772\n#define SFXshe_b_walk_01 773\n#define SFXshe_r_death_00 774\n#define SFXshe_r_death_01 775\n#define SFXshe_r_pain_00 776\n#define SFXsfx0309 777\n#define SFXsh2_a_voxangry_04 778\n#define SFXsfx030B 779\n#define SFXsfx030C 780\n#define SFXsh2_b_idle_02 781\n#define SFXsh2_b_land_00 782\n#define SFXsh2_b_roar_00 783\n#define SFXsh2_b_shake_lp_00 784\n#define SFXsh2_b_walk_00 785\n#define SFXsh2_b_walk_01 786\n#define SFXsh2_r_death_00 787\n#define SFXsh2_r_death_01 788\n#define SFXsh2_r_pain_00 789\n#define SFXsfx0316 790\n#define SFXsfx0317 791\n#define SFXsh2_b_run_00 792\n#define SFXsh2_b_run_01 793\n#define SFXsfx031A 794\n#define SFXsfx031B 795\n#define SFXsfx031C 796\n#define SFXsfx031D 797\n#define SFXsfx031E 798\n#define SFXsfx031F 799\n#define SFXsfx0320 800\n#define SFXsfx0321 801\n#define SFXsfx0322 802\n#define SFXsfx0323 803\n#define SFXsfx0324 804\n#define SFXsfx0325 805\n"
  },
  {
    "path": "Runtime/Audio/SFX/SnakeWeed.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: SnakeWeed\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPSnakeWeed 30\n\n#define SFXsfx0326 806\n#define SFXsnk_b_in_00 807\n#define SFXsnk_b_out_00 808\n#define SFXsfx0329 809\n#define SFXsfx032A 810\n#define SFXsfx032B 811\n"
  },
  {
    "path": "Runtime/Audio/SFX/Sova.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: Sova\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPSova 31\n\n#define SFXfpr_b_walk_00 812\n#define SFXfpr_b_walk_01 813\n#define SFXspr_a_gun_00 814\n#define SFXsfx032F 815\n#define SFXsfx0330 816\n#define SFXspr_b_walk_00 817\n#define SFXspr_b_walk_01 818\n#define SFXspr_b_walk_02 819\n#define SFXspr_b_walk_03 820\n#define SFXsfx0335 821\n#define SFXsfx0336 822\n#define SFXsfx0337 823\n#define SFXsfx0338 824\n#define SFXsfx0339 825\n#define SFXsfx033A 826\n#define SFXsfx033B 827\n#define SFXsfx033C 828\n#define SFXsfx033D 829\n#define SFXsfx033E 830\n"
  },
  {
    "path": "Runtime/Audio/SFX/SpacePirate.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: SpacePirate\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPSpacePirate 32\n\n#define SFXsfx033F 831\n#define SFXepr_b_swordin_00 832\n#define SFXepr_b_swordout_00 833\n#define SFXopr_c_movement_00 834\n#define SFXsfx0343 835\n#define SFXsfx0344 836\n#define SFXsfx0345 837\n#define SFXsfx0346 838\n#define SFXsfx0347 839\n#define SFXspr_b_movement_00 840\n#define SFXspr_b_movement_01 841\n#define SFXsfx034A 842\n#define SFXsfx034B 843\n#define SFXsfx034C 844\n#define SFXsfx034D 845\n#define SFXspr_b_voxalert_01 846\n#define SFXsfx034F 847\n#define SFXsfx0350 848\n#define SFXsfx0351 849\n#define SFXsfx0352 850\n#define SFXsfx0353 851\n#define SFXsfx0354 852\n#define SFXsfx0355 853\n#define SFXsfx0356 854\n#define SFXspr_r_impact_02 855\n#define SFXopr_b_swordin_00 856\n#define SFXsfx0359 857\n#define SFXsfx035A 858\n#define SFXsfx035B 859\n#define SFXspr_b_idle_02 860\n#define SFXspr_b_intruder_00 861\n#define SFXsfx035E 862\n#define SFXsfx035F 863\n#define SFXsfx0360 864\n#define SFXsfx0361 865\n#define SFXopr_b_swordout_00 866\n#define SFXspr_r_himpact_00 867\n#define SFXsfx0364 868\n#define SFXspr_b_jump_00 869\n#define SFXsfx0366 870\n#define SFXsfx0367 871\n#define SFXsfx0368 872\n#define SFXspr_b_voxangry_02 873\n#define SFXsfx036A 874\n#define SFXsfx036B 875\n#define SFXsfx036C 876\n#define SFXsfx036D 877\n#define SFXsfx036E 878\n#define SFXsfx036F 879\n#define SFXsfx0370 880\n#define SFXsfx0371 881\n#define SFXsfx0372 882\n#define SFXsfx0373 883\n#define SFXepr_b_movement_00 884\n#define SFXepr_b_movement_01 885\n#define SFXsfx0376 886\n#define SFXsfx0377 887\n#define SFXsfx0378 888\n#define SFXsfx0379 889\n#define SFXsfx037A 890\n#define SFXsfx037B 891\n#define SFXepr_r_die_00 892\n#define SFXsfx037D 893\n#define SFXepr_r_pain_00 894\n#define SFXsfx037F 895\n#define SFXsfx0380 896\n#define SFXsfx0381 897\n#define SFXsfx0382 898\n#define SFXsfx0383 899\n#define SFXsfx0384 900\n#define SFXsfx0385 901\n#define SFXsfx0386 902\n#define SFXsfx0387 903\n#define SFXsfx0388 904\n#define SFXsfx0389 905\n#define SFXsfx038A 906\n#define SFXsfx038B 907\n#define SFXsfx038C 908\n#define SFXsfx038D 909\n#define SFXsfx038E 910\n#define SFXsfx038F 911\n#define SFXsfx0390 912\n#define SFXsfx0391 913\n#define SFXsfx0392 914\n"
  },
  {
    "path": "Runtime/Audio/SFX/SpankWeed.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: SpankWeed\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPSpankWeed 33\n\n#define SFXspw_a_spank_00 915\n#define SFXsfx0394 916\n#define SFXspw_b_out_00 917\n#define SFXspw_b_swish_02 918\n#define SFXspw_b_swoosh_00 919\n#define SFXsfx0398 920\n#define SFXspw_r_impact_00 921\n#define SFXsfx039A 922\n#define SFXfla_a_tentatak_00 923\n#define SFXsfx039C 924\n#define SFXsfx039D 925\n#define SFXsfx039E 926\n#define SFXsfx039F 927\n#define SFXsfx03A0 928\n#define SFXsfx03A1 929\n#define SFXsfx03A2 930\n#define SFXfla_b_tentmove_01 931\n#define SFXfla_b_tentslid_00 932\n#define SFXfla_b_tentslid_01 933\n#define SFXsfx03A6 934\n#define SFXsfx03A7 935\n#define SFXsfx03A8 936\n#define SFXsfx03A9 937\n#define SFXsfx03AA 938\n#define SFXsfx03AB 939\n#define SFXsfx03AC 940\n#define SFXsfx03AD 941\n#define SFXsfx03AE 942\n#define SFXsfx03AF 943\n#define SFXsfx03B0 944\n#define SFXsfx03B1 945\n#define SFXsfx03B2 946\n#define SFXsfx03B3 947\n#define SFXsfx03B4 948\n#define SFXsfx03B5 949\n#define SFXsfx03B6 950\n#define SFXsfx03B7 951\n#define SFXsfx03B8 952\n#define SFXsfx03B9 953\n#define SFXsfx03BA 954\n#define SFXsfx03BB 955\n#define SFXsfx03BC 956\n#define SFXsfx03BD 957\n#define SFXsfx03BE 958\n#define SFXsfx03BF 959\n"
  },
  {
    "path": "Runtime/Audio/SFX/Thardus.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: Thardus\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPThardus 55\n\n#define SFXtha_b_voxangry_02 2703\n#define SFXtha_b_move_00 2704\n#define SFXsfx0A91 2705\n#define SFXtha_b_rocks_00 2706\n#define SFXtha_a_stoneup_lp_00 2707\n#define SFXtha_a_swoosh_00 2708\n#define SFXsfx0A95 2709\n#define SFXtha_a_voxattak_00 2710\n#define SFXtha_a_voxattak_01 2711\n#define SFXtha_b_henshin_lp_00 2712\n#define SFXtha_b_hitgrnd_00 2713\n#define SFXtha_b_hitgrnd_01 2714\n#define SFXtha_b_hitgrnd_02 2715\n#define SFXtha_b_charge_00 2716\n#define SFXtha_a_thunder_00 2717\n#define SFXtha_b_rocks_lp_00 2718\n#define SFXsfx0A9F 2719\n#define SFXtha_b_roll_lp_00 2720\n#define SFXsfx0AA1 2721\n#define SFXtha_b_voxangry_00 2722\n#define SFXtha_b_voxangry_01 2723\n#define SFXtha_b_walk_00 2724\n#define SFXtha_b_walk_01 2725\n#define SFXsfx0AA6 2726\n#define SFXtha_r_pain_00 2727\n#define SFXtha_b_boulder_00 2728\n#define SFXtha_b_boulder_01 2729\n#define SFXsfx0AAA 2730\n#define SFXtha_b_henshin_00 2731\n#define SFXtha_b_henshin_01 2732\n#define SFXsfx0AAD 2733\n#define SFXtha_a_icewave_lp_00 2734\n#define SFXtha_b_chant_00 2735\n#define SFXtha_b_enraged_00 2736\n#define SFXtha_b_charge_01 2737\n#define SFXtha_b_charge_02 2738\n#define SFXtha_b_walk_02 2739\n#define SFXtha_b_walk_03 2740\n#define SFXtha_a_thunder_01 2741\n#define SFXsfx0AB6 2742\n#define SFXsfx0AB7 2743\n#define SFXsfx0AB8 2744\n#define SFXtha_a_icestorm_lp_02 2745\n#define SFXsfx0ABA 2746\n#define SFXsfx0ABB 2747\n#define SFXsfx0ABC 2748\n#define SFXtha_b_idle_00 2749\n#define SFXsfx0ABE 2750\n#define SFXsfx0ABF 2751\n#define SFXsfx0AC0 2752\n#define SFXtha_b_charge_03 2753\n#define SFXtha_r_smpain_00 2754\n#define SFXsfx0AC3 2755\n#define SFXtha_r_pissed_00 2756\n#define SFXsfx0AC5 2757\n#define SFXsfx0AC6 2758\n#define SFXsfx0AC7 2759\n#define SFXsfx0AC8 2760\n#define SFXsfx0AC9 2761\n#define SFXsfx0ACA 2762\n#define SFXsfx0ACB 2763\n#define SFXsfx0ACC 2764\n#define SFXsfx0ACD 2765\n#define SFXsfx0ACE 2766\n#define SFXsfx0ACF 2767\n#define SFXsfx0AD0 2768\n#define SFXsfx0AD1 2769\n#define SFXsfx0AD2 2770\n"
  },
  {
    "path": "Runtime/Audio/SFX/TheEnd.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: TheEnd\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPTheEnd 70\n\n#define SFXsfx0C4D 3149\n#define SFXsh2_a_fireball_lp_00 3150\n#define SFXshe_a_fireball_lp_00 3151\n#define SFXend_c_shipthst_00 3152\n#define SFXsfx0C51 3153\n#define SFXsfx0C52 3154\n#define SFXsfx0C53 3155\n#define SFXsfx0C54 3156\n#define SFXsfx0C55 3157\n#define SFXsfx0C56 3158\n#define SFXsfx0C57 3159\n#define SFXsfx0C58 3160\n#define SFXsfx0C59 3161\n#define SFXsfx0C5A 3162\n#define SFXsfx0C5B 3163\n#define SFXsfx0C5C 3164\n#define SFXsfx0C5D 3165\n#define SFXsfx0C5E 3166\n"
  },
  {
    "path": "Runtime/Audio/SFX/Torobyte.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: Torobyte\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPToroByte 35\n\n#define SFXocu_b_idle_00 981\n#define SFXsfx03D6 982\n#define SFXocu_b_blink_00 983\n#define SFXsfx03D8 984\n#define SFXbat_r_voxdeath_00 985\n#define SFXsfx03DA 986\n#define SFXsfx03DB 987\n#define SFXsfx03DC 988\n#define SFXsfx03DD 989\n#define SFXsfx03DE 990\n#define SFXsfx03DF 991\n#define SFXsfx03E0 992\n#define SFXsfx03E1 993\n#define SFXsfx03E2 994\n#define SFXsfx03E3 995\n#define SFXsfx03E4 996\n#define SFXsfx03E5 997\n#define SFXsfx03E6 998\n#define SFXsfx03E7 999\n"
  },
  {
    "path": "Runtime/Audio/SFX/Triclops.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: Triclops\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPTriclops 34\n\n#define SFXtri_a_attack_00 960\n#define SFXtri_a_attract_00 961\n#define SFXtri_b_idle_00 962\n#define SFXsfx03C3 963\n#define SFXtri_b_walk_00 964\n#define SFXsfx03C5 965\n#define SFXsfx03C6 966\n#define SFXtri_r_impact_00 967\n#define SFXtri_r_impact_01 968\n#define SFXtri_b_run_00 969\n#define SFXsfx03CA 970\n#define SFXsfx03CB 971\n#define SFXsfx03CC 972\n#define SFXsfx03CD 973\n#define SFXsfx03CE 974\n#define SFXsfx03CF 975\n#define SFXsfx03D0 976\n#define SFXsfx03D1 977\n#define SFXsfx03D2 978\n#define SFXsfx03D3 979\n#define SFXsfx03D4 980\n"
  },
  {
    "path": "Runtime/Audio/SFX/Turret.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: Turret\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPTurret 36\n\n#define SFXsfx03E8 1000\n#define SFXtur_a_laser_00 1001\n#define SFXsfx03EA 1002\n#define SFXsfx03EB 1003\n#define SFXsfx03EC 1004\n#define SFXsfx03ED 1005\n#define SFXsfx03EE 1006\n#define SFXsfx03EF 1007\n#define SFXtur_b_lower_00 1008\n#define SFXsfx03F1 1009\n#define SFXsfx03F2 1010\n#define SFXtur_b_raise_lp_00 1011\n#define SFXtur_b_stop_00 1012\n#define SFXsfx03F5 1013\n#define SFXtur_b_sweep_lp_00 1014\n#define SFXsfx03F7 1015\n#define SFXsfx03F8 1016\n#define SFXsfx03F9 1017\n#define SFXsfx03FA 1018\n#define SFXtur_r_powrdown_lp_00 1019\n#define SFXsfx03FC 1020\n#define SFXsfx03FD 1021\n#define SFXsfx03FE 1022\n#define SFXsfx03FF 1023\n#define SFXsfx0400 1024\n#define SFXsfx0401 1025\n#define SFXsfx0402 1026\n"
  },
  {
    "path": "Runtime/Audio/SFX/UI.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: UI\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPUI 40\n\n#define SFXui_map_rotate 1375\n#define SFXui_map_zoom 1376\n#define SFXui_lockon_poi 1377\n#define SFXui_into_map_screen 1378\n#define SFXsfx0563 1379\n#define SFXui_outof_map_screen 1380\n#define SFXsfx0565 1381\n#define SFXui_outof_visor 1382\n#define SFXui_into_visor 1383\n#define SFXui_visor_xray_lp 1384\n#define SFXui_damage_lp 1385\n#define SFXui_show_local_beacon 1386\n#define SFXui_show_remote_beacon 1387\n#define SFXui_visor_thermal_lp 1388\n#define SFXsfx056D 1389\n#define SFXui_outof_freelook 1390\n#define SFXsfx056F 1391\n#define SFXui_into_freelook 1392\n#define SFXui_lockon_grapple 1393\n#define SFXui_freelook_move_lp 1394\n#define SFXui_select_visor 1395\n#define SFXui_threat_warning 1396\n#define SFXui_missile_warning 1397\n#define SFXui_select_beam 1398\n#define SFXui_threat_damage 1399\n#define SFXui_hud_shutdown 1400\n#define SFXui_hud_reboot 1401\n#define SFXui_static_hi 1402\n#define SFXui_static_lo 1403\n#define SFXui_visor_scan_lp 1404\n#define SFXui_energy_low 1405\n#define SFXui_map_pan 1406\n#define SFXui_scanning_lp 1407\n#define SFXsfx0580 1408\n#define SFXui_outof_scan_window 1409\n#define SFXsfx0582 1410\n#define SFXui_into_scan_window 1411\n#define SFXsfx0584 1412\n#define SFXsfx0585 1413\n#define SFXui_scan_pane_reveal 1414\n#define SFXui_into_hud_message 1415\n#define SFXui_outof_hud_message 1416\n#define SFXui_scan_complete 1417\n#define SFXui_hud_memo_type 1418\n#define SFXsfx058B 1419\n#define SFXsfx058C 1420\n#define SFXui_message_screen_key 1421\n#define SFXui_options_quit_accept 1422\n#define SFXui_options_quit_reject 1423\n#define SFXui_quit_change 1424\n#define SFXui_new_scan_complete 1425\n#define SFXui_map_to_universe 1426\n#define SFXui_map_from_universe 1427\n#define SFXsfx0594 1428\n#define SFXsfx0595 1429\n#define SFXsfx0596 1430\n#define SFXui_table_change_mode 1431\n#define SFXui_advance 1432\n#define SFXui_pause_screen_change 1433\n#define SFXui_pause_screen_exit 1434\n#define SFXui_pause_screen_enter 1435\n#define SFXui_table_selection_change 1436\n#define SFXui_option_enum_change 1437\n#define SFXsfx059E 1438\n#define SFXui_scan_next_page 1439\n#define SFXui_samus_doll_enter 1440\n#define SFXui_samus_doll_exit 1441\n#define SFXui_hud_memo_a_pulse 1442\n#define SFXui_show_hint_memo 1443\n#define SFXui_pause_screen_next_page 1444\n#define SFXsfx05A5 1445\n#define SFXui_map_screen_key2 1446\n#define SFXsfx05A7 1447\n#define SFXsfx05A8 1448\n#define SFXui_hide_hint_memo 1449\n#define SFXsfx05AA 1450\n#define SFXui_options_slider_change_lp 1451\n#define SFXui_map_screen_key1 1452\n#define SFXui_map_screen_key0 1453\n#define SFXsfx05AE 1454\n#define SFXsfx05AF 1455\n#define SFXsfx05B0 1456\n#define SFXsfx05B1 1457\n#define SFXui_frontend_options_slider_change_lp 1458\n#define SFXui_frontend_save_back 1459\n#define SFXui_frontend_save_confirm 1460\n#define SFXui_frontend_save_move 1461\n#define SFXsfx05B6 1462\n#define SFXsfx05B7 1463\n#define SFXsfx05B8 1464\n"
  },
  {
    "path": "Runtime/Audio/SFX/WarWasp.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: WarWasp\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPWarWasp 37\n\n#define SFXwar_b_idle_lp_00 1027\n#define SFXwa2_a_stinger_00 1028\n#define SFXwa2_b_idle_lp_00 1029\n#define SFXwa2_r_diescream_00 1030\n#define SFXwa2_r_hitlight_00 1031\n#define SFXwa2_r_wingbuzz_00 1032\n#define SFXwar_r_diescream_00 1033\n#define SFXwar_r_diescream_01 1034\n#define SFXwar_r_diescream_02 1035\n#define SFXwar_r_diescream_03 1036\n#define SFXwar_r_hitdirt_00 1037\n#define SFXwar_r_hitdirt_01 1038\n#define SFXwar_r_hitlight_00 1039\n#define SFXwar_r_hitlight_01 1040\n#define SFXwar_r_wingbuzz_00 1041\n#define SFXwar_r_wingbuzz_01 1042\n#define SFXwar_r_wingbuzz_02 1043\n#define SFXwar_r_wingbuzz_03 1044\n#define SFXsfx0415 1045\n#define SFXwa2_r_wingbuzz_01 1046\n#define SFXwar_a_stab_00 1047\n#define SFXwar_b_noise_00 1048\n#define SFXwar_b_noise_01 1049\n#define SFXwa3_a_stab_00 1050\n#define SFXwa3_a_stab_01 1051\n#define SFXwa2_b_noise_00 1052\n#define SFXwa2_b_noise_01 1053\n#define SFXwar_a_stab_01 1054\n#define SFXwar_a_stinger_00 1055\n#define SFXwar_r_wingbuzz_04 1056\n#define SFXsfx0421 1057\n#define SFXsfx0422 1058\n#define SFXwa2_b_agitated_lp_00 1059\n#define SFXsfx0424 1060\n#define SFXwa2_b_noise_02 1061\n#define SFXwar_b_agitated_lp_00 1062\n#define SFXwa3_a_voxattak_00 1063\n#define SFXwar_b_noise_02 1064\n#define SFXwa3_b_agitated_lp_00 1065\n#define SFXwa3_b_idle_lp_00 1066\n#define SFXsfx042B 1067\n#define SFXwa3_b_noise_00 1068\n#define SFXsfx042D 1069\n#define SFXwa3_b_noise_02 1070\n#define SFXwa3_r_hitlight_00 1071\n#define SFXwa3_r_wingbuzz_00 1072\n#define SFXglo_b_fly_lp_00 1073\n#define SFXwa3_r_wingbuzz_02 1074\n#define SFXsfx0433 1075\n#define SFXsfx0434 1076\n#define SFXsfx0435 1077\n#define SFXsfx0436 1078\n#define SFXsfx0437 1079\n#define SFXsfx0438 1080\n#define SFXsfx0439 1081\n#define SFXsfx043A 1082\n#define SFXsfx043B 1083\n#define SFXsfx043C 1084\n#define SFXsfx043D 1085\n#define SFXsfx043E 1086\n#define SFXsfx043F 1087\n#define SFXsfx0440 1088\n#define SFXsfx0441 1089\n"
  },
  {
    "path": "Runtime/Audio/SFX/Weapons.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: Weapons\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPWeapons 43\n\n#define SFXwpn_bomb_drop 1756\n#define SFXsfx06DD 1757\n#define SFXwpn_bomb_explo 1758\n#define SFXwpn_chargeup_ice 1759\n#define SFXsfx06E0 1760\n#define SFXsfx06E1 1761\n#define SFXwpn_combo_xfer 1762\n#define SFXwpn_empty_action 1763\n#define SFXsfx06E4 1764\n#define SFXsfx06E5 1765\n#define SFXwpn_chargeup_power 1766\n#define SFXwpn_fire_power_charged 1767\n#define SFXwpn_fire_missile 1768\n#define SFXwpn_reload_missile 1769\n#define SFXwpn_fire_power_normal 1770\n#define SFXsfx06EB 1771\n#define SFXsfx06EC 1772\n#define SFXsfx06ED 1773\n#define SFXwpn_morph_out_wipe 1774\n#define SFXwpn_morph_in_wipe_done 1775\n#define SFXwpn_fire_ice_charged 1776\n#define SFXsfx06F1 1777\n#define SFXsfx06F2 1778\n#define SFXsfx06F3 1779\n#define SFXsfx06F4 1780\n#define SFXwpn_invalid_action 1781\n#define SFXsfx06F6 1782\n#define SFXsfx06F7 1783\n#define SFXsfx06F8 1784\n#define SFXsfx06F9 1785\n#define SFXsfx06FA 1786\n#define SFXsfx06FB 1787\n#define SFXsfx06FC 1788\n#define SFXsfx06FD 1789\n#define SFXsfx06FE 1790\n#define SFXsfx06FF 1791\n#define SFXsfx0700 1792\n#define SFXsfx0701 1793\n#define SFXsfx0702 1794\n#define SFXsfx0703 1795\n#define SFXsfx0704 1796\n#define SFXwpn_fire_ice_normal 1797\n#define SFXsfx0706 1798\n#define SFXsfx0707 1799\n#define SFXsfx0708 1800\n#define SFXwpn_fire_wave_normal 1801\n#define SFXsfx070A 1802\n#define SFXwpn_fire_plasma_normal 1803\n#define SFXsfx070C 1804\n#define SFXwpn_fire_phazon_normal 1805\n#define SFXsfx070E 1806\n#define SFXsfx070F 1807\n#define SFXsfx0710 1808\n#define SFXsfx0711 1809\n#define SFXsfx0712 1810\n#define SFXsfx0713 1811\n#define SFXsfx0714 1812\n#define SFXsfx0715 1813\n#define SFXsfx0716 1814\n#define SFXsfx0717 1815\n#define SFXsfx0718 1816\n#define SFXsfx0719 1817\n#define SFXsfx071A 1818\n#define SFXsfx071B 1819\n#define SFXsfx071C 1820\n#define SFXwpn_into_beam_ice 1821\n#define SFXwpn_from_beam_ice 1822\n#define SFXwpn_to_missile_power 1823\n#define SFXwpn_from_missile_power 1824\n#define SFXwpn_into_beam_plasma 1825\n#define SFXwpn_from_beam_plasma 1826\n#define SFXwpn_into_beam_wave 1827\n#define SFXwpn_from_beam_wave 1828\n#define SFXwpn_to_missile_ice 1829\n#define SFXsfx0726 1830\n#define SFXsfx0727 1831\n#define SFXsfx0728 1832\n#define SFXsfx0729 1833\n#define SFXsfx072A 1834\n#define SFXsfx072B 1835\n#define SFXsfx072C 1836\n#define SFXsfx072D 1837\n#define SFXsfx072E 1838\n#define SFXwpn_chargeup_plasma 1839\n#define SFXwpn_fire_plasma_charged 1840\n#define SFXsfx0731 1841\n#define SFXwpn_combo_flamethrower 1842\n#define SFXsfx0733 1843\n#define SFXwpn_chargeup_wave 1844\n#define SFXwpn_fire_wave_charged 1845\n#define SFXsfx0736 1846\n#define SFXwpn_combo_wavebuster 1847\n#define SFXsfx0738 1848\n#define SFXwpn_from_missile_ice 1849\n#define SFXwpn_to_missile_wave 1850\n#define SFXwpn_from_missile_wave 1851\n#define SFXwpn_to_missile_plasma 1852\n#define SFXwpn_from_missile_plasma 1853\n#define SFXsfx073E 1854\n#define SFXsfx073F 1855\n#define SFXsfx0740 1856\n#define SFXsfx0741 1857\n#define SFXsfx0742 1858\n#define SFXsfx0743 1859\n#define SFXsfx0744 1860\n#define SFXsfx0745 1861\n#define SFXsfx0746 1862\n#define SFXsfx0747 1863\n#define SFXsfx0748 1864\n#define SFXsfx0749 1865\n#define SFXsfx074A 1866\n#define SFXsfx074B 1867\n#define SFXsfx074C 1868\n#define SFXsfx074D 1869\n#define SFXsfx074E 1870\n#define SFXsfx074F 1871\n#define SFXsfx0750 1872\n#define SFXsfx0751 1873\n#define SFXsfx0752 1874\n#define SFXsfx0753 1875\n#define SFXsfx0754 1876\n#define SFXsfx0755 1877\n#define SFXsfx0756 1878\n#define SFXsfx0757 1879\n#define SFXsfx0758 1880\n#define SFXsfx0759 1881\n#define SFXsfx075A 1882\n#define SFXsfx075B 1883\n#define SFXsfx075C 1884\n#define SFXsfx075D 1885\n#define SFXsfx075E 1886\n#define SFXsfx075F 1887\n#define SFXsfx0760 1888\n#define SFXsfx0761 1889\n#define SFXsfx0762 1890\n#define SFXsfx0763 1891\n"
  },
  {
    "path": "Runtime/Audio/SFX/ZZZ.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: ZZZ\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPZZZ 65\n\n#define SFXsfx0C16 3094\n#define SFXsfx0C17 3095\n#define SFXsfx0C18 3096\n#define SFXsfx0C19 3097\n#define SFXsfx0C1A 3098\n#define SFXsfx0C1B 3099\n#define SFXsfx0C1C 3100\n#define SFXsfx0C1D 3101\n#define SFXsfx0C1E 3102\n#define SFXsfx0C1F 3103\n#define SFXsfx0C20 3104\n#define SFXsfx0C21 3105\n#define SFXsfx0C22 3106\n#define SFXsfx0C23 3107\n#define SFXsfx0C24 3108\n#define SFXsfx0C25 3109\n#define SFXsfx0C26 3110\n#define SFXsfx0C27 3111\n#define SFXsfx0C28 3112\n#define SFXsfx0C29 3113\n"
  },
  {
    "path": "Runtime/Audio/SFX/Zoomer.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: Zoomer\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPZoomer 54\n\n#define SFXzom_b_idle_00 2681\n#define SFXsfx0A7A 2682\n#define SFXsfx0A7B 2683\n#define SFXsfx0A7C 2684\n#define SFXsfx0A7D 2685\n#define SFXgem_b_idle_00 2686\n#define SFXsfx0A7F 2687\n#define SFXsfx0A80 2688\n#define SFXsfx0A81 2689\n#define SFXsfx0A82 2690\n#define SFXsfx0A83 2691\n#define SFXsfx0A84 2692\n#define SFXsfx0A85 2693\n#define SFXsfx0A86 2694\n#define SFXsfx0A87 2695\n#define SFXsfx0A88 2696\n#define SFXsfx0A89 2697\n#define SFXsfx0A8A 2698\n#define SFXsfx0A8B 2699\n#define SFXsfx0A8C 2700\n#define SFXsfx0A8D 2701\n#define SFXsfx0A8E 2702\n"
  },
  {
    "path": "Runtime/Audio/SFX/lumigek.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: lumigek\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPLumigek 69\n\n#define SFXlum_b_idle_00 3143\n#define SFXsfx0C48 3144\n#define SFXsfx0C49 3145\n#define SFXsfx0C4A 3146\n#define SFXsfx0C4B 3147\n#define SFXsfx0C4C 3148\n"
  },
  {
    "path": "Runtime/Audio/SFX/test.h",
    "content": "/* Auto-generated Amuse Defines\n *\n * Project: Audio\n * Subproject: test\n * Date: Sat Sep  1 12:32:04 2018\n */\n\n#define GRPtest 53\n\n#define SNGIntro_Cinema 0\n#define SNGMain_Plaza 1\n#define SNGIntro_Exit 2\n#define SNGEndGame 3\n"
  },
  {
    "path": "Runtime/Audio/g721.c",
    "content": "/* G.721 decoder, from Sun's public domain CCITT-ADPCM sources,\n * retrieved from ftp://ftp.cwi.nl/pub/audio/ccitt-adpcm.tar.gz\n *\n * For reference, here's the original license:\n *\n * This source code is a product of Sun Microsystems, Inc. and is provided\n * for unrestricted use.  Users may copy or modify this source code without\n * charge.\n *\n * SUN SOURCE CODE IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING\n * THE WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR\n * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE.\n *\n * Sun source code is provided with no support and without any obligation on\n * the part of Sun Microsystems, Inc. to assist in its use, correction,\n * modification or enhancement.\n *\n * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE\n * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY THIS SOFTWARE\n * OR ANY PART THEREOF.\n *\n * In no event will Sun Microsystems, Inc. be liable for any lost revenue\n * or profits or other special, indirect and consequential damages, even if\n * Sun has been advised of the possibility of such damages.\n *\n * Sun Microsystems, Inc.\n * 2550 Garcia Avenue\n * Mountain View, California  94043\n *\n */\n\n#include <stdlib.h>\n#include \"g721.h\"\n\nstatic short power2[15] = {1, 2, 4, 8, 0x10, 0x20, 0x40, 0x80,\n    0x100, 0x200, 0x400, 0x800, 0x1000, 0x2000, 0x4000};\n\n/*\n * quan()\n *\n * quantizes the input val against the table of size short integers.\n * It returns i if table[i - 1] <= val < table[i].\n *\n * Using linear search for simple coding.\n */\nstatic int\nquan(\n     int     val,\n     short   *table,\n     int     size)\n{\n    int     i;\n    \n    for (i = 0; i < size; i++)\n        if (val < *table++)\n            break;\n    return (i);\n}\n\n/*\n * fmult()\n *\n * returns the integer product of the 14-bit integer \"an\" and\n * \"floating point\" representation (4-bit exponent, 6-bit mantessa) \"srn\".\n */\nstatic int\nfmult(\n      int\t\tan,\n      int\t\tsrn)\n{\n\tshort\t\tanmag, anexp, anmant;\n\tshort\t\twanexp, wanmant;\n\tshort\t\tretval;\n    \n\tanmag = (an > 0) ? an : ((-an) & 0x1FFF);\n\tanexp = quan(anmag, power2, 15) - 6;\n\tanmant = (anmag == 0) ? 32 :\n    (anexp >= 0) ? anmag >> anexp : anmag << -anexp;\n\twanexp = anexp + ((srn >> 6) & 0xF) - 13;\n    \n\twanmant = (anmant * (srn & 077) + 0x30) >> 4;\n\tretval = (wanexp >= 0) ? ((wanmant << wanexp) & 0x7FFF) :\n    (wanmant >> -wanexp);\n    \n\treturn (((an ^ srn) < 0) ? -retval : retval);\n}\n\n/*\n * g72x_init_state()\n *\n * This routine initializes and/or resets the g72x_state structure\n * pointed to by 'state_ptr'.\n * All the initial state values are specified in the CCITT G.721 document.\n */\nvoid\ng72x_init_state(struct g72x_state *state_ptr)\n{\n\tint\t\tcnta;\n    \n\tstate_ptr->yl = 34816;\n\tstate_ptr->yu = 544;\n\tstate_ptr->dms = 0;\n\tstate_ptr->dml = 0;\n\tstate_ptr->ap = 0;\n\tfor (cnta = 0; cnta < 2; cnta++) {\n\t\tstate_ptr->a[cnta] = 0;\n\t\tstate_ptr->pk[cnta] = 0;\n\t\tstate_ptr->sr[cnta] = 32;\n\t}\n\tfor (cnta = 0; cnta < 6; cnta++) {\n\t\tstate_ptr->b[cnta] = 0;\n\t\tstate_ptr->dq[cnta] = 32;\n\t}\n\tstate_ptr->td = 0;\n}\n\n/*\n * predictor_zero()\n *\n * computes the estimated signal from 6-zero predictor.\n *\n */\nstatic int\npredictor_zero(\n               struct g72x_state *state_ptr)\n{\n\tint\t\ti;\n\tint\t\tsezi;\n    \n\tsezi = fmult(state_ptr->b[0] >> 2, state_ptr->dq[0]);\n\tfor (i = 1; i < 6; i++)\t\t\t/* ACCUM */\n\t\tsezi += fmult(state_ptr->b[i] >> 2, state_ptr->dq[i]);\n\treturn (sezi);\n}\n/*\n * predictor_pole()\n *\n * computes the estimated signal from 2-pole predictor.\n *\n */\nstatic int\npredictor_pole(\n               struct g72x_state *state_ptr)\n{\n\treturn (fmult(state_ptr->a[1] >> 2, state_ptr->sr[1]) +\n            fmult(state_ptr->a[0] >> 2, state_ptr->sr[0]));\n}\n/*\n * step_size()\n *\n * computes the quantization step size of the adaptive quantizer.\n *\n */\nstatic long\nstep_size(\n          struct g72x_state *state_ptr)\n{\n\tlong\t\ty;\n\tlong\t\tdif;\n\tlong\t\tal;\n    \n\tif (state_ptr->ap >= 256)\n\t\treturn (state_ptr->yu);\n\telse {\n\t\ty = state_ptr->yl >> 6;\n\t\tdif = state_ptr->yu - y;\n\t\tal = state_ptr->ap >> 2;\n\t\tif (dif > 0)\n\t\t\ty += (dif * al) >> 6;\n\t\telse if (dif < 0)\n\t\t\ty += (dif * al + 0x3F) >> 6;\n\t\treturn (y);\n\t}\n}\n\n/*\n * reconstruct()\n *\n * Returns reconstructed difference signal 'dq' obtained from\n * codeword 'i' and quantization step size scale factor 'y'.\n * Multiplication is performed in log base 2 domain as addition.\n */\nstatic int\nreconstruct(\n            int\t\tsign,\t/* 0 for non-negative value */\n            int\t\tdqln,\t/* G.72x codeword */\n            int\t\ty)\t/* Step size multiplier */\n{\n\tshort\t\tdql;\t/* Log of 'dq' magnitude */\n\tshort\t\tdex;\t/* Integer part of log */\n\tshort\t\tdqt;\n\tshort\t\tdq;\t/* Reconstructed difference signal sample */\n    \n\tdql = dqln + (y >> 2);\t/* ADDA */\n    \n\tif (dql < 0) {\n\t\treturn ((sign) ? -0x8000 : 0);\n\t} else {\t\t/* ANTILOG */\n\t\tdex = (dql >> 7) & 15;\n\t\tdqt = 128 + (dql & 127);\n\t\tdq = (dqt << 7) >> (14 - dex);\n\t\treturn ((sign) ? (dq - 0x8000) : dq);\n\t}\n}\n\n\n/*\n * update()\n *\n * updates the state variables for each output code\n */\nstatic void\nupdate(\n       /*int\t\tcode_size,*/\t/* distinguish 723_40 with others */\n       int\t\ty,\t\t/* quantizer step size */\n       int\t\twi,\t\t/* scale factor multiplier */\n       int\t\tfi,\t\t/* for long/short term energies */\n       int\t\tdq,\t\t/* quantized prediction difference */\n       int\t\tsr,\t\t/* reconstructed signal */\n       int\t\tdqsez,\t\t/* difference from 2-pole predictor */\n       struct g72x_state *state_ptr)\t/* coder state pointer */\n{\n\tint\t\tcnt;\n\tshort\t\tmag, exp;\t/* Adaptive predictor, FLOAT A */\n\tshort\t\ta2p;\t\t/* LIMC */\n\tshort\t\ta1ul;\t\t/* UPA1 */\n\tshort\t\tpks1;\t/* UPA2 */\n\tshort\t\tfa1;\n\tchar\t\ttr;\t\t/* tone/transition detector */\n\tshort\t\tylint, thr2, dqthr;\n\tshort  \t\tylfrac, thr1;\n\tshort\t\tpk0;\n    \n\tpk0 = (dqsez < 0) ? 1 : 0;\t/* needed in updating predictor poles */\n    \n\tmag = dq & 0x7FFF;\t\t/* prediction difference magnitude */\n\t/* TRANS */\n\tylint = state_ptr->yl >> 15;\t/* exponent part of yl */\n\tylfrac = (state_ptr->yl >> 10) & 0x1F;\t/* fractional part of yl */\n\tthr1 = (32 + ylfrac) << ylint;\t\t/* threshold */\n\tthr2 = (ylint > 9) ? 31 << 10 : thr1;\t/* limit thr2 to 31 << 10 */\n\tdqthr = (thr2 + (thr2 >> 1)) >> 1;\t/* dqthr = 0.75 * thr2 */\n\tif (state_ptr->td == 0)\t\t/* signal supposed voice */\n\t\ttr = 0;\n\telse if (mag <= dqthr)\t\t/* supposed data, but small mag */\n\t\ttr = 0;\t\t\t/* treated as voice */\n\telse\t\t\t\t/* signal is data (modem) */\n\t\ttr = 1;\n    \n\t/*\n\t * Quantizer scale factor adaptation.\n\t */\n    \n\t/* FUNCTW & FILTD & DELAY */\n\t/* update non-steady state step size multiplier */\n\tstate_ptr->yu = y + ((wi - y) >> 5);\n    \n\t/* LIMB */\n\tif (state_ptr->yu < 544)\t/* 544 <= yu <= 5120 */\n\t\tstate_ptr->yu = 544;\n\telse if (state_ptr->yu > 5120)\n\t\tstate_ptr->yu = 5120;\n    \n\t/* FILTE & DELAY */\n\t/* update steady state step size multiplier */\n\tstate_ptr->yl += state_ptr->yu + ((-state_ptr->yl) >> 6);\n    \n\t/*\n\t * Adaptive predictor coefficients.\n\t */\n\tif (tr == 1) {\t\t\t/* reset a's and b's for modem signal */\n\t\tstate_ptr->a[0] = 0;\n\t\tstate_ptr->a[1] = 0;\n\t\tstate_ptr->b[0] = 0;\n\t\tstate_ptr->b[1] = 0;\n\t\tstate_ptr->b[2] = 0;\n\t\tstate_ptr->b[3] = 0;\n\t\tstate_ptr->b[4] = 0;\n\t\tstate_ptr->b[5] = 0;\n        a2p=0;          /* won't be used, clear warning */\n\t} else {\t\t\t/* update a's and b's */\n\t\tpks1 = pk0 ^ state_ptr->pk[0];\t\t/* UPA2 */\n        \n\t\t/* update predictor pole a[1] */\n\t\ta2p = state_ptr->a[1] - (state_ptr->a[1] >> 7);\n\t\tif (dqsez != 0) {\n\t\t\tfa1 = (pks1) ? state_ptr->a[0] : -state_ptr->a[0];\n\t\t\tif (fa1 < -8191)\t/* a2p = function of fa1 */\n\t\t\t\ta2p -= 0x100;\n\t\t\telse if (fa1 > 8191)\n\t\t\t\ta2p += 0xFF;\n\t\t\telse\n\t\t\t\ta2p += fa1 >> 5;\n            \n\t\t\tif (pk0 ^ state_ptr->pk[1])\n            /* LIMC */\n\t\t\t\tif (a2p <= -12160)\n\t\t\t\t\ta2p = -12288;\n\t\t\t\telse if (a2p >= 12416)\n\t\t\t\t\ta2p = 12288;\n\t\t\t\telse\n\t\t\t\t\ta2p -= 0x80;\n                else if (a2p <= -12416)\n                    a2p = -12288;\n                else if (a2p >= 12160)\n                    a2p = 12288;\n                else\n                    a2p += 0x80;\n\t\t}\n        \n\t\t/* TRIGB & DELAY */\n\t\tstate_ptr->a[1] = a2p;\n        \n\t\t/* UPA1 */\n\t\t/* update predictor pole a[0] */\n\t\tstate_ptr->a[0] -= state_ptr->a[0] >> 8;\n\t\tif (dqsez != 0) {\n\t\t\tif (pks1 == 0)\n\t\t\t\tstate_ptr->a[0] += 192;\n\t\t\telse\n\t\t\t\tstate_ptr->a[0] -= 192;\n        }\n        \n\t\t/* LIMD */\n\t\ta1ul = 15360 - a2p;\n\t\tif (state_ptr->a[0] < -a1ul)\n\t\t\tstate_ptr->a[0] = -a1ul;\n\t\telse if (state_ptr->a[0] > a1ul)\n\t\t\tstate_ptr->a[0] = a1ul;\n        \n\t\t/* UPB : update predictor zeros b[6] */\n\t\tfor (cnt = 0; cnt < 6; cnt++) {\n\t\t\t/*if (code_size == 5)*/\t\t/* for 40Kbps G.723 */\n\t\t\t/*\tstate_ptr->b[cnt] -= state_ptr->b[cnt] >> 9;*/\n\t\t\t/*else*/\t\t\t/* for G.721 and 24Kbps G.723 */\n            state_ptr->b[cnt] -= state_ptr->b[cnt] >> 8;\n\t\t\tif (dq & 0x7FFF) {\t\t\t/* XOR */\n\t\t\t\tif ((dq ^ state_ptr->dq[cnt]) >= 0)\n\t\t\t\t\tstate_ptr->b[cnt] += 128;\n\t\t\t\telse\n\t\t\t\t\tstate_ptr->b[cnt] -= 128;\n\t\t\t}\n\t\t}\n\t}\n    \n\tfor (cnt = 5; cnt > 0; cnt--)\n\t\tstate_ptr->dq[cnt] = state_ptr->dq[cnt-1];\n\t/* FLOAT A : convert dq[0] to 4-bit exp, 6-bit mantissa f.p. */\n\tif (mag == 0) {\n\t\tstate_ptr->dq[0] = (dq >= 0) ? 0x20 : 0xFC20;\n\t} else {\n\t\texp = quan(mag, power2, 15);\n\t\tstate_ptr->dq[0] = (dq >= 0) ?\n        (exp << 6) + ((mag << 6) >> exp) :\n        (exp << 6) + ((mag << 6) >> exp) - 0x400;\n\t}\n    \n\tstate_ptr->sr[1] = state_ptr->sr[0];\n\t/* FLOAT B : convert sr to 4-bit exp., 6-bit mantissa f.p. */\n\tif (sr == 0) {\n\t\tstate_ptr->sr[0] = 0x20;\n\t} else if (sr > 0) {\n\t\texp = quan(sr, power2, 15);\n\t\tstate_ptr->sr[0] = (exp << 6) + ((sr << 6) >> exp);\n\t} else if (sr > -32768) {\n\t\tmag = -sr;\n\t\texp = quan(mag, power2, 15);\n\t\tstate_ptr->sr[0] =  (exp << 6) + ((mag << 6) >> exp) - 0x400;\n\t} else\n\t\tstate_ptr->sr[0] = 0xFC20;\n    \n\t/* DELAY A */\n\tstate_ptr->pk[1] = state_ptr->pk[0];\n\tstate_ptr->pk[0] = pk0;\n    \n\t/* TONE */\n\tif (tr == 1)\t\t/* this sample has been treated as data */\n\t\tstate_ptr->td = 0;\t/* next one will be treated as voice */\n\telse if (a2p < -11776)\t/* small sample-to-sample correlation */\n\t\tstate_ptr->td = 1;\t/* signal may be data */\n\telse\t\t\t\t/* signal is voice */\n\t\tstate_ptr->td = 0;\n    \n\t/*\n\t * Adaptation speed control.\n\t */\n\tstate_ptr->dms += (fi - state_ptr->dms) >> 5;\t\t/* FILTA */\n\tstate_ptr->dml += (((fi << 2) - state_ptr->dml) >> 7);\t/* FILTB */\n    \n\tif (tr == 1)\n\t\tstate_ptr->ap = 256;\n\telse if (y < 1536)\t\t\t\t\t/* SUBTC */\n\t\tstate_ptr->ap += (0x200 - state_ptr->ap) >> 4;\n\telse if (state_ptr->td == 1)\n\t\tstate_ptr->ap += (0x200 - state_ptr->ap) >> 4;\n\telse if (abs((state_ptr->dms << 2) - state_ptr->dml) >=\n             (state_ptr->dml >> 3))\n\t\tstate_ptr->ap += (0x200 - state_ptr->ap) >> 4;\n\telse\n\t\tstate_ptr->ap += (-state_ptr->ap) >> 4;\n}\n\n/*\n * Maps G.721 code word to reconstructed scale factor normalized log\n * magnitude values.\n */\nstatic short\t_dqlntab[16] = {-2048, 4, 135, 213, 273, 323, 373, 425,\n    425, 373, 323, 273, 213, 135, 4, -2048};\n\n/* Maps G.721 code word to log of scale factor multiplier. */\nstatic short\t_witab[16] = {-12, 18, 41, 64, 112, 198, 355, 1122,\n    1122, 355, 198, 112, 64, 41, 18, -12};\n/*\n * Maps G.721 code words to a set of values whose long and short\n * term averages are computed and then compared to give an indication\n * how stationary (steady state) the signal is.\n */\nstatic short\t_fitab[16] = {0, 0, 0, 0x200, 0x200, 0x200, 0x600, 0xE00,\n    0xE00, 0x600, 0x200, 0x200, 0x200, 0, 0, 0};\n/*\n * g721_decoder()\n *\n * Description:\n *\n * Decodes a 4-bit code of G.721 encoded data of i and\n * returns the resulting linear PCM, A-law or u-law value.\n * return -1 for unknown out_coding value.\n */\nint\ng721_decoder(int\t\ti,\n             struct g72x_state *state_ptr)\n{\n\tshort\t\tsezi, sei, sez, se;\t/* ACCUM */\n\tshort\t\ty;\t\t\t/* MIX */\n\tshort\t\tsr;\t\t\t/* ADDB */\n\tshort\t\tdq;\n\tshort\t\tdqsez;\n    \n\ti &= 0x0f;\t\t\t/* mask to get proper bits */\n\tsezi = predictor_zero(state_ptr);\n\tsez = sezi >> 1;\n\tsei = sezi + predictor_pole(state_ptr);\n\tse = sei >> 1;\t\t\t/* se = estimated signal */\n    \n\ty = step_size(state_ptr);\t/* dynamic quantizer step size */\n    \n\tdq = reconstruct(i & 0x08, _dqlntab[i], y); /* quantized diff. */\n    \n\tsr = (dq < 0) ? (se - (dq & 0x3FFF)) : se + dq;\t/* reconst. signal */\n    \n\tdqsez = sr - se + sez;\t\t\t/* pole prediction diff. */\n    \n\tupdate(y, _witab[i] << 5, _fitab[i], dq, sr, dqsez, state_ptr);\n    \n\treturn (sr << 2);\t/* sr was 14-bit dynamic range */\n}\n\n"
  },
  {
    "path": "Runtime/Audio/g721.h",
    "content": "#ifndef _g721_h\n#define _g721_h\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nstruct g72x_state {\n    long yl;    /* Locked or steady state step size multiplier. */\n    short yu;   /* Unlocked or non-steady state step size multiplier. */\n    short dms;  /* Short term energy estimate. */\n    short dml;  /* Long term energy estimate. */\n    short ap;   /* Linear weighting coefficient of 'yl' and 'yu'. */\n    \n    short a[2]; /* Coefficients of pole portion of prediction filter. */\n    short b[6]; /* Coefficients of zero portion of prediction filter. */\n    short pk[2];    /*\n                     * Signs of previous two samples of a partially\n                     * reconstructed signal.\n                     */\n    short dq[6];    /*\n                     * Previous 6 samples of the quantized difference\n                     * signal represented in an internal floating point\n                     * format.\n                     */\n    short sr[2];    /*\n                     * Previous 2 samples of the quantized difference\n                     * signal represented in an internal floating point\n                     * format.\n                     */\n    char td;    /* delayed tone detect, new in 1988 version */\n};\n\nvoid\ng72x_init_state(struct g72x_state *state_ptr);\n\nint\ng721_decoder(int\t\ti,\n             struct g72x_state *state_ptr);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "Runtime/AutoMapper/CAutoMapper.cpp",
    "content": "#include \"Runtime/AutoMapper/CAutoMapper.hpp\"\n\n#include \"Runtime/CInGameTweakManagerBase.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/AutoMapper/CMapArea.hpp\"\n#include \"Runtime/AutoMapper/CMapUniverse.hpp\"\n#include \"Runtime/Camera/CGameCamera.hpp\"\n#include \"Runtime/GuiSys/CGuiFrame.hpp\"\n#include \"Runtime/GuiSys/CGuiTextPane.hpp\"\n#include \"Runtime/GuiSys/CGuiWidgetDrawParms.hpp\"\n#include \"Runtime/Input/ControlMapper.hpp\"\n#include \"Runtime/MP1/MP1.hpp\"\n#include \"Runtime/Particle/CGenDescription.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n\n#include <fmt/xchar.h>\n#include <zeus/CEulerAngles.hpp>\n\nnamespace metaforce {\n\nvoid CAutoMapper::SAutoMapperRenderState::InterpolateWithClamp(const SAutoMapperRenderState& a,\n                                                               SAutoMapperRenderState& out,\n                                                               const SAutoMapperRenderState& b, float t) {\n  t = zeus::clamp(0.f, t, 1.f);\n  const float easeIn = zeus::clamp(0.f, t * t * t, 1.f);\n  const float omt = 1.f - t;\n  const float easeOut = zeus::clamp(0.f, 1.f - omt * omt * omt, 1.f);\n\n  float easeInOut;\n  if (t >= 0.5f) {\n    easeInOut = zeus::clamp(0.f, 0.5f * std::sqrt(2.f * t - 1.f) + 0.5f, 1.f);\n  } else {\n    easeInOut = zeus::clamp(0.f, 1.f - (0.5f * std::sqrt(2.f * omt - 1.f) + 0.5f), 1.f);\n  }\n\n  const std::array<float, 5> eases{\n      0.0f, t, easeOut, easeIn, easeInOut,\n  };\n\n  if (b.x44_viewportEase != Ease::None) {\n    const float easeB = eases[size_t(b.x44_viewportEase)];\n    const float easeA = 1.f - easeB;\n    const zeus::CVector2i vpA = a.GetViewportSize();\n    const zeus::CVector2i vpB = b.GetViewportSize();\n    out.x0_viewportSize = zeus::CVector2i(vpB.x * easeB + vpA.x * easeA, vpB.y * easeB + vpA.y * easeA);\n  }\n  if (t == 1.f)\n    out.m_getViewportSize = b.m_getViewportSize;\n  else\n    out.m_getViewportSize = nullptr;\n\n  if (b.x48_camEase != Ease::None) {\n    const float easeB = eases[size_t(b.x48_camEase)];\n    const float easeA = 1.f - easeB;\n    out.x8_camOrientation = zeus::CQuaternion::slerp(a.x8_camOrientation, b.x8_camOrientation, easeB);\n    out.x18_camDist = b.x18_camDist * easeB + a.x18_camDist * easeA;\n    out.x1c_camAngle = b.x1c_camAngle * easeB + a.x1c_camAngle * easeA;\n  }\n\n  if (b.x4c_pointEase != Ease::None) {\n    const float easeB = eases[size_t(b.x4c_pointEase)];\n    const float easeA = 1.f - easeB;\n    out.x20_areaPoint = b.x20_areaPoint * easeB + a.x20_areaPoint * easeA;\n  }\n\n  if (b.x50_depth1Ease != Ease::None) {\n    const float easeB = eases[size_t(b.x50_depth1Ease)];\n    const float easeA = 1.f - easeB;\n    out.x2c_drawDepth1 = b.x2c_drawDepth1 * easeB + a.x2c_drawDepth1 * easeA;\n  }\n\n  if (b.x54_depth2Ease != Ease::None) {\n    const float easeB = eases[size_t(b.x54_depth2Ease)];\n    const float easeA = 1.f - easeB;\n    out.x30_drawDepth2 = b.x30_drawDepth2 * easeB + a.x30_drawDepth2 * easeA;\n  }\n\n  if (b.x58_alphaEase != Ease::None) {\n    const float easeB = eases[size_t(b.x58_alphaEase)];\n    const float easeA = 1.f - easeB;\n    out.x34_alphaSurfaceVisited = b.x34_alphaSurfaceVisited * easeB + a.x34_alphaSurfaceVisited * easeA;\n    out.x38_alphaOutlineVisited = b.x38_alphaOutlineVisited * easeB + a.x38_alphaOutlineVisited * easeA;\n    out.x3c_alphaSurfaceUnvisited = b.x3c_alphaSurfaceUnvisited * easeB + a.x3c_alphaSurfaceUnvisited * easeA;\n    out.x40_alphaOutlineUnvisited = b.x40_alphaOutlineUnvisited * easeB + a.x40_alphaOutlineUnvisited * easeA;\n  }\n}\n\nCAutoMapper::CAutoMapper(CStateManager& stateMgr) : x24_world(stateMgr.GetWorld()) {\n  x8_mapu = g_SimplePool->GetObj(\"MAPU_MapUniverse\");\n  x30_miniMapSamus = g_SimplePool->GetObj(\"CMDL_MiniMapSamus\");\n  x3c_hintBeacon = g_SimplePool->GetObj(\"TXTR_HintBeacon\");\n\n  xa0_curAreaId = xa4_otherAreaId = stateMgr.GetWorld()->IGetCurrentAreaId();\n  zeus::CMatrix3f camRot = stateMgr.GetCameraManager()->GetCurrentCamera(stateMgr)->GetTransform().buildMatrix3f();\n  xa8_renderStates[0] = xa8_renderStates[1] = xa8_renderStates[2] =\n      BuildMiniMapWorldRenderState(stateMgr, camRot, xa0_curAreaId);\n\n  x48_mapIcons.emplace_back(g_SimplePool->GetObj(SObjectTag{FOURCC('TXTR'), g_tweakPlayerRes->x4_saveStationIcon}));\n  x48_mapIcons.emplace_back(g_SimplePool->GetObj(SObjectTag{FOURCC('TXTR'), g_tweakPlayerRes->x8_missileStationIcon}));\n  x48_mapIcons.emplace_back(g_SimplePool->GetObj(SObjectTag{FOURCC('TXTR'), g_tweakPlayerRes->xc_elevatorIcon}));\n  x48_mapIcons.emplace_back(\n      g_SimplePool->GetObj(SObjectTag{FOURCC('TXTR'), g_tweakPlayerRes->x10_minesBreakFirstTopIcon}));\n  x48_mapIcons.emplace_back(\n      g_SimplePool->GetObj(SObjectTag{FOURCC('TXTR'), g_tweakPlayerRes->x14_minesBreakFirstBottomIcon}));\n\n  for (u32 i = 0; i < 9; ++i) {\n    x210_lstick.emplace_back(g_SimplePool->GetObj(SObjectTag{FOURCC('TXTR'), g_tweakPlayerRes->x24_lStick[i]}));\n    x25c_cstick.emplace_back(g_SimplePool->GetObj(SObjectTag{FOURCC('TXTR'), g_tweakPlayerRes->x4c_cStick[i]}));\n  }\n\n  for (u32 i = 0; i < 2; ++i) {\n    x2a8_ltrigger.emplace_back(g_SimplePool->GetObj(SObjectTag{FOURCC('TXTR'), g_tweakPlayerRes->x74_lTrigger[i]}));\n    x2bc_rtrigger.emplace_back(g_SimplePool->GetObj(SObjectTag{FOURCC('TXTR'), g_tweakPlayerRes->x80_rTrigger[i]}));\n    x2d0_abutton.emplace_back(g_SimplePool->GetObj(SObjectTag{FOURCC('TXTR'), g_tweakPlayerRes->x98_aButton[i]}));\n  }\n}\n\nbool CAutoMapper::CheckLoadComplete() {\n  switch (x4_loadPhase) {\n  case ELoadPhase::LoadResources:\n    for (TLockedToken<CTexture>& tex : x48_mapIcons)\n      if (!tex.IsLoaded())\n        return false;\n    if (!x30_miniMapSamus.IsLoaded())\n      return false;\n    if (!x3c_hintBeacon.IsLoaded())\n      return false;\n    x4_loadPhase = ELoadPhase::LoadUniverse;\n    [[fallthrough]];\n  case ELoadPhase::LoadUniverse:\n    if (!x8_mapu.IsLoaded())\n      return false;\n    x14_dummyWorlds.resize(x8_mapu->GetNumMapWorldDatas());\n    SetCurWorldAssetId(x24_world->IGetWorldAssetId());\n    x4_loadPhase = ELoadPhase::Done;\n    [[fallthrough]];\n  case ELoadPhase::Done:\n    return true;\n  default:\n    break;\n  }\n  return false;\n}\n\nbool CAutoMapper::NotHintNavigating() const { return x1e0_hintSteps.empty(); }\n\nbool CAutoMapper::CanLeaveMapScreenInternal(const CStateManager& mgr) const {\n  if (!NotHintNavigating())\n    return false;\n  if (IsRenderStateInterpolating())\n    return false;\n  if (IsInMapperState(EAutoMapperState::MapScreenUniverse))\n    return true;\n  if (x24_world != mgr.GetWorld())\n    return false;\n  if (IsInMapperState(EAutoMapperState::MapScreen))\n    return true;\n  return false;\n}\n\nvoid CAutoMapper::LeaveMapScreen(CStateManager& mgr) {\n  if (x1c0_nextState == EAutoMapperState::MapScreenUniverse) {\n    xa8_renderStates[1].x2c_drawDepth1 = GetMapAreaMiniMapDrawDepth();\n    xa8_renderStates[1].x30_drawDepth2 = GetMapAreaMiniMapDrawDepth();\n    xa8_renderStates[0].x2c_drawDepth1 = GetMapAreaMiniMapDrawDepth();\n    xa8_renderStates[0].x30_drawDepth2 = GetMapAreaMiniMapDrawDepth();\n    SetupMiniMapWorld(mgr);\n  } else {\n    x328_ = 2;\n    xa8_renderStates[1] = xa8_renderStates[0];\n    xa8_renderStates[2] = xa8_renderStates[1];\n    xa0_curAreaId = x24_world->IGetCurrentAreaId();\n    xa8_renderStates[1].x20_areaPoint = GetAreaPointOfInterest(mgr, xa0_curAreaId);\n    xa8_renderStates[1].x4c_pointEase = SAutoMapperRenderState::Ease::Linear;\n    xa8_renderStates[1].x2c_drawDepth1 = GetMapAreaMiniMapDrawDepth();\n    xa8_renderStates[1].x30_drawDepth2 = GetMapAreaMiniMapDrawDepth();\n    xa8_renderStates[1].x50_depth1Ease = SAutoMapperRenderState::Ease::Linear;\n    xa8_renderStates[1].x54_depth2Ease = SAutoMapperRenderState::Ease::Linear;\n    ResetInterpolationTimer(0.25f);\n  }\n}\n\nvoid CAutoMapper::SetupMiniMapWorld(CStateManager& mgr) {\n  CWorld& wld = *mgr.GetWorld();\n  wld.GetMapWorld()->SetWhichMapAreasLoaded(wld, wld.GetCurrentAreaId(), 3);\n  x328_ = 3;\n}\n\nbool CAutoMapper::HasCurrentMapUniverseWorld() const {\n  CAssetId mlvlId = x24_world->IGetWorldAssetId();\n  for (const CMapUniverse::CMapWorldData& wld : *x8_mapu)\n    if (wld.GetWorldAssetId() == mlvlId)\n      return true;\n  return false;\n}\n\nbool CAutoMapper::CheckDummyWorldLoad(CStateManager& mgr) {\n  const CMapUniverse::CMapWorldData& mapuWld = x8_mapu->GetMapWorldData(x9c_worldIdx);\n  auto& dummyWorld = x14_dummyWorlds[x9c_worldIdx];\n  if (!dummyWorld) {\n    x32c_loadingDummyWorld = false;\n    return false;\n  }\n\n  if (!dummyWorld->ICheckWorldComplete())\n    return true;\n\n  CWorldState& worldState = g_GameState->StateForWorld(dummyWorld->IGetWorldAssetId());\n  CMapWorldInfo& mwInfo = *worldState.MapWorldInfo();\n  zeus::CVector3f localPoint = mapuWld.GetWorldTransform().inverse() * xa8_renderStates[0].x20_areaPoint;\n  zeus::CMatrix3f camRot = xa8_renderStates[0].x8_camOrientation.toTransform().buildMatrix3f();\n  TAreaId aid = FindClosestVisibleArea(localPoint, zeus::CUnitVector3f(camRot[1]), mgr, *dummyWorld, mwInfo);\n  if (aid == -1) {\n    x32c_loadingDummyWorld = false;\n    return false;\n  }\n  xa0_curAreaId = aid;\n\n  dummyWorld->IGetMapWorld()->RecalculateWorldSphere(mwInfo, *dummyWorld);\n  x24_world = dummyWorld.get();\n  BeginMapperStateTransition(EAutoMapperState::MapScreen, mgr);\n  x32c_loadingDummyWorld = false;\n  return true;\n}\n\nvoid CAutoMapper::UpdateHintNavigation(float dt, CStateManager& mgr) {\n  SAutoMapperHintStep& nextStep = x1e0_hintSteps.front();\n  bool oldProcessing = nextStep.x8_processing;\n  nextStep.x8_processing = true;\n  switch (nextStep.x0_type) {\n  case SAutoMapperHintStep::Type::PanToArea: {\n    if (x24_world->IGetMapWorld()->GetMapArea(nextStep.x4_areaId)) {\n      xa8_renderStates[2] = xa8_renderStates[0];\n      xa8_renderStates[1].x20_areaPoint = GetAreaPointOfInterest(mgr, nextStep.x4_areaId);\n      xa8_renderStates[1].ResetInterpolation();\n      xa8_renderStates[1].x4c_pointEase = SAutoMapperRenderState::Ease::Linear;\n      ResetInterpolationTimer(2.f * g_tweakAutoMapper->GetHintPanTime());\n      x1e0_hintSteps.pop_front();\n    }\n    break;\n  }\n  case SAutoMapperHintStep::Type::PanToWorld: {\n    const CMapUniverse::CMapWorldData& mwData = x8_mapu->GetMapWorldDataByWorldId(nextStep.x4_worldId);\n    xa8_renderStates[2] = xa8_renderStates[0];\n    xa8_renderStates[1].x20_areaPoint = mwData.GetWorldCenterPoint();\n    xa8_renderStates[1].ResetInterpolation();\n    xa8_renderStates[1].x4c_pointEase = SAutoMapperRenderState::Ease::Linear;\n    ResetInterpolationTimer(2.f * g_tweakAutoMapper->GetHintPanTime());\n    x1e0_hintSteps.pop_front();\n    break;\n  }\n  case SAutoMapperHintStep::Type::SwitchToUniverse: {\n    if (HasCurrentMapUniverseWorld()) {\n      BeginMapperStateTransition(EAutoMapperState::MapScreenUniverse, mgr);\n      x1e0_hintSteps.pop_front();\n    } else {\n      x1e0_hintSteps.clear();\n    }\n    break;\n  }\n  case SAutoMapperHintStep::Type::SwitchToWorld: {\n    x1e0_hintSteps.pop_front();\n    x32c_loadingDummyWorld = true;\n    if (CheckDummyWorldLoad(mgr))\n      break;\n    x1e0_hintSteps.clear();\n    break;\n  }\n  case SAutoMapperHintStep::Type::ShowBeacon: {\n    if (!oldProcessing) {\n      if (xa0_curAreaId == mgr.GetNextAreaId() && x24_world == mgr.GetWorld())\n        CSfxManager::SfxStart(SFXui_show_local_beacon, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n      else\n        CSfxManager::SfxStart(SFXui_show_remote_beacon, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n    }\n    nextStep.x4_float = std::max(0.f, nextStep.x4_float - dt);\n    for (SAutoMapperHintLocation& loc : x1f8_hintLocations) {\n      if (x24_world->IGetWorldAssetId() == loc.x8_worldId && xa0_curAreaId == loc.xc_areaId) {\n        loc.x0_showBeacon = 1;\n        loc.x4_beaconAlpha = 1.f - std::min(nextStep.x4_float / 0.5f, 1.f);\n        break;\n      }\n    }\n    if (nextStep.x4_float != 0.f)\n      break;\n    x1e0_hintSteps.pop_front();\n    break;\n  }\n  case SAutoMapperHintStep::Type::ZoomOut: {\n    xa8_renderStates[2] = xa8_renderStates[0];\n    xa8_renderStates[1].x18_camDist = g_tweakAutoMapper->GetMaxCamDist();\n    xa8_renderStates[1].ResetInterpolation();\n    xa8_renderStates[1].x48_camEase = SAutoMapperRenderState::Ease::Linear;\n    ResetInterpolationTimer(0.5f);\n    x1e0_hintSteps.pop_front();\n    break;\n  }\n  case SAutoMapperHintStep::Type::ZoomIn: {\n    xa8_renderStates[2] = xa8_renderStates[0];\n    xa8_renderStates[1].x18_camDist = g_tweakAutoMapper->GetCamDist();\n    xa8_renderStates[1].ResetInterpolation();\n    xa8_renderStates[1].x48_camEase = SAutoMapperRenderState::Ease::Linear;\n    ResetInterpolationTimer(0.5f);\n    x1e0_hintSteps.pop_front();\n    break;\n  }\n  default:\n    break;\n  }\n}\n\nbool CAutoMapper::CanLeaveMapScreen(const CStateManager& mgr) const {\n  return x328_ == 3 && CanLeaveMapScreenInternal(mgr);\n}\n\nvoid CAutoMapper::SetCurWorldAssetId(CAssetId mlvlId) {\n  u32 numWorlds = x8_mapu->GetNumMapWorldDatas();\n  for (u32 i = 0; i < numWorlds; ++i)\n    if (x8_mapu->GetMapWorldData(i).GetWorldAssetId() == mlvlId) {\n      x9c_worldIdx = i;\n      break;\n    }\n}\n\nvoid CAutoMapper::BeginMapperStateTransition(EAutoMapperState state, CStateManager& mgr) {\n  if (state == x1c0_nextState)\n    return;\n  if ((state == EAutoMapperState::MiniMap && x1c0_nextState != EAutoMapperState::MiniMap) ||\n      (state != EAutoMapperState::MiniMap && x1c0_nextState == EAutoMapperState::MiniMap))\n    CSfxManager::KillAll(CSfxManager::ESfxChannels::PauseScreen);\n\n  x1bc_state = x1c0_nextState;\n  x1c0_nextState = state;\n  xa8_renderStates[2] = xa8_renderStates[0];\n  xa8_renderStates[1] = xa8_renderStates[0];\n\n  if (x1bc_state == EAutoMapperState::MiniMap && state == EAutoMapperState::MapScreen) {\n    xa8_renderStates[1] =\n        BuildMapScreenWorldRenderState(mgr, xa8_renderStates[0].x8_camOrientation, xa0_curAreaId, false);\n    ResetInterpolationTimer(g_tweakAutoMapper->GetOpenMapScreenTime());\n  } else if (x1bc_state == EAutoMapperState::MapScreen && state == EAutoMapperState::MiniMap) {\n    xa0_curAreaId = x24_world->IGetCurrentAreaId();\n    xa8_renderStates[1] = BuildMiniMapWorldRenderState(mgr, xa8_renderStates[0].x8_camOrientation, xa0_curAreaId);\n    ResetInterpolationTimer(g_tweakAutoMapper->GetCloseMapScreenTime());\n    x1f8_hintLocations.clear();\n  } else if (x1bc_state == EAutoMapperState::MapScreen && state == EAutoMapperState::MapScreenUniverse) {\n    CSfxManager::SfxStart(SFXui_map_to_universe, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n    xa8_renderStates[1] = BuildMapScreenUniverseRenderState(mgr, xa8_renderStates[0].x8_camOrientation, xa0_curAreaId);\n    TransformRenderStatesWorldToUniverse();\n    ResetInterpolationTimer(g_tweakAutoMapper->GetSwitchToFromUniverseTime());\n  } else if (x1bc_state == EAutoMapperState::MapScreenUniverse && state == EAutoMapperState::MapScreen) {\n    CSfxManager::SfxStart(SFXui_map_from_universe, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n    xa8_renderStates[1] = BuildMapScreenWorldRenderState(mgr, xa8_renderStates[0].x8_camOrientation, xa0_curAreaId,\n                                                         x1e0_hintSteps.size());\n    TransformRenderStateWorldToUniverse(xa8_renderStates[1]);\n    ResetInterpolationTimer(g_tweakAutoMapper->GetSwitchToFromUniverseTime());\n    for (auto& wld : x14_dummyWorlds) {\n      if (wld.get() != x24_world || x24_world == mgr.GetWorld())\n        wld.reset();\n    }\n  } else if (x1bc_state == EAutoMapperState::MapScreenUniverse && state == EAutoMapperState::MiniMap) {\n    x24_world = mgr.GetWorld();\n    xa0_curAreaId = x24_world->IGetCurrentAreaId();\n    xa8_renderStates[1] = BuildMiniMapWorldRenderState(mgr, xa8_renderStates[0].x8_camOrientation, xa0_curAreaId);\n    SetCurWorldAssetId(x24_world->IGetWorldAssetId());\n    TransformRenderStateWorldToUniverse(xa8_renderStates[1]);\n    ResetInterpolationTimer(g_tweakAutoMapper->GetCloseMapScreenTime());\n    x1f8_hintLocations.clear();\n    for (auto& wld : x14_dummyWorlds) {\n      if (wld.get() != x24_world || x24_world == mgr.GetWorld())\n        wld.reset();\n    }\n  }\n}\n\nvoid CAutoMapper::CompleteMapperStateTransition(CStateManager& mgr) {\n  if (x1bc_state == EAutoMapperState::MapScreenUniverse)\n    TransformRenderStatesUniverseToWorld();\n\n  if (x1c0_nextState == EAutoMapperState::MapScreen) {\n    const CMapWorldInfo& mwInfo = *g_GameState->StateForWorld(x24_world->IGetWorldAssetId()).MapWorldInfo();\n    x24_world->IGetMapWorld()->RecalculateWorldSphere(mwInfo, *x24_world);\n    x1d8_flashTimer = 0.f;\n    x1dc_playerFlashPulse = 0.f;\n  }\n\n  if (x1c0_nextState == EAutoMapperState::MiniMap) {\n    x28_frmeMapScreen = TLockedToken<CGuiFrame>();\n    m_frmeInitialized = false;\n    x2fc_textpane_hint = nullptr;\n    x300_textpane_instructions = nullptr;\n    x304_textpane_instructions1 = nullptr;\n    x308_textpane_instructions2 = nullptr;\n    x2f8_textpane_areaname = nullptr;\n    x30c_basewidget_leftPane = nullptr;\n    x310_basewidget_yButtonPane = nullptr;\n    x314_basewidget_bottomPane = nullptr;\n    SetResLockState(x210_lstick, false);\n    SetResLockState(x25c_cstick, false);\n    SetResLockState(x2a8_ltrigger, false);\n    SetResLockState(x2bc_rtrigger, false);\n    SetResLockState(x2d0_abutton, false);\n  }\n\n  if (x1c0_nextState == EAutoMapperState::MapScreenUniverse && x328_ == 1)\n    LeaveMapScreen(mgr);\n\n  x1bc_state = x1c0_nextState;\n}\n\nvoid CAutoMapper::ResetInterpolationTimer(float duration) {\n  x1c4_interpDur = duration;\n  x1c8_interpTime = 0.f;\n}\n\nCAutoMapper::SAutoMapperRenderState CAutoMapper::BuildMiniMapWorldRenderState(const CStateManager& stateMgr,\n                                                                              const zeus::CQuaternion& rot,\n                                                                              TAreaId area) const {\n  zeus::CQuaternion camOrient = GetMiniMapCameraOrientation(stateMgr);\n  zeus::CQuaternion useOrient = (camOrient.dot(rot) >= 0.f) ? camOrient : camOrient.buildEquivalent();\n  SAutoMapperRenderState ret(\n      GetMiniMapViewportSize, useOrient, g_tweakAutoMapper->GetMiniCamDist(), g_tweakAutoMapper->GetMiniCamAngle(),\n      GetAreaPointOfInterest(stateMgr, area), GetMapAreaMiniMapDrawDepth(), GetMapAreaMiniMapDrawDepth(),\n      GetMapAreaMiniMapDrawAlphaSurfaceVisited(stateMgr), GetMapAreaMiniMapDrawAlphaOutlineVisited(stateMgr),\n      GetMapAreaMiniMapDrawAlphaSurfaceUnvisited(stateMgr), GetMapAreaMiniMapDrawAlphaOutlineUnvisited(stateMgr));\n  ret.x44_viewportEase = SAutoMapperRenderState::Ease::Out;\n  ret.x48_camEase = SAutoMapperRenderState::Ease::Out;\n  ret.x4c_pointEase = SAutoMapperRenderState::Ease::Out;\n  ret.x50_depth1Ease = SAutoMapperRenderState::Ease::Linear;\n  ret.x54_depth2Ease = SAutoMapperRenderState::Ease::In;\n  ret.x58_alphaEase = SAutoMapperRenderState::Ease::Linear;\n  return ret;\n}\n\nCAutoMapper::SAutoMapperRenderState CAutoMapper::BuildMapScreenWorldRenderState(const CStateManager& mgr,\n                                                                                const zeus::CQuaternion& rot,\n                                                                                TAreaId area, bool doingHint) const {\n  float camDist = doingHint ? g_tweakAutoMapper->GetMaxCamDist() : g_tweakAutoMapper->GetCamDist();\n  SAutoMapperRenderState ret(GetMapScreenViewportSize, rot, camDist, g_tweakAutoMapper->GetCamAngle(),\n                             GetAreaPointOfInterest(mgr, area), GetMapAreaMaxDrawDepth(mgr, area),\n                             GetMapAreaMaxDrawDepth(mgr, area), g_tweakAutoMapper->GetAlphaSurfaceVisited(),\n                             g_tweakAutoMapper->GetAlphaOutlineVisited(), g_tweakAutoMapper->GetAlphaSurfaceUnvisited(),\n                             g_tweakAutoMapper->GetAlphaOutlineUnvisited());\n  ret.x44_viewportEase = SAutoMapperRenderState::Ease::Out;\n  ret.x48_camEase = SAutoMapperRenderState::Ease::Linear;\n  ret.x4c_pointEase = SAutoMapperRenderState::Ease::Out;\n  ret.x50_depth1Ease = SAutoMapperRenderState::Ease::Linear;\n  ret.x54_depth2Ease = SAutoMapperRenderState::Ease::Out;\n  ret.x58_alphaEase = SAutoMapperRenderState::Ease::Linear;\n  return ret;\n}\n\nCAutoMapper::SAutoMapperRenderState CAutoMapper::BuildMapScreenUniverseRenderState(const CStateManager& mgr,\n                                                                                   const zeus::CQuaternion& rot,\n                                                                                   TAreaId area) const {\n  SAutoMapperRenderState ret(GetMapScreenViewportSize, rot, g_tweakAutoMapper->GetUniverseCamDist(),\n                             g_tweakAutoMapper->GetCamAngle(), GetAreaPointOfInterest(mgr, area),\n                             GetMapAreaMaxDrawDepth(mgr, area), GetMapAreaMaxDrawDepth(mgr, area), 0.f, 0.f, 0.f, 0.f);\n  ret.x44_viewportEase = SAutoMapperRenderState::Ease::Out;\n  ret.x48_camEase = SAutoMapperRenderState::Ease::Linear;\n  ret.x4c_pointEase = SAutoMapperRenderState::Ease::Out;\n  ret.x50_depth1Ease = SAutoMapperRenderState::Ease::Linear;\n  ret.x54_depth2Ease = SAutoMapperRenderState::Ease::Out;\n  ret.x58_alphaEase = SAutoMapperRenderState::Ease::Linear;\n  return ret;\n}\n\nvoid CAutoMapper::LeaveMapScreenState() {\n  SetShouldPanningSoundBePlaying(false);\n  SetShouldZoomingSoundBePlaying(false);\n  SetShouldRotatingSoundBePlaying(false);\n}\n\nfloat CAutoMapper::GetBaseMapScreenCameraMoveSpeed() { return g_tweakAutoMapper->GetBaseMapScreenCameraMoveSpeed(); }\n\nfloat CAutoMapper::GetFinalMapScreenCameraMoveSpeed() const {\n  float ret = GetBaseMapScreenCameraMoveSpeed();\n  if (g_tweakAutoMapper->GetScaleMoveSpeedWithCamDist())\n    ret = ret * xa8_renderStates[0].x18_camDist / g_tweakAutoMapper->GetCamDist();\n  return ret;\n}\n\nvoid CAutoMapper::ProcessMapRotateInput(const CFinalInput& input, const CStateManager& mgr) {\n  const float up = ControlMapper::GetAnalogInput(ControlMapper::ECommands::MapCircleUp, input);\n  const float down = ControlMapper::GetAnalogInput(ControlMapper::ECommands::MapCircleDown, input);\n  const float left = ControlMapper::GetAnalogInput(ControlMapper::ECommands::MapCircleLeft, input);\n  const float right = ControlMapper::GetAnalogInput(ControlMapper::ECommands::MapCircleRight, input);\n\n  std::array<float, 4> dirs{};\n  bool mouseHeld = false;\n  if (const auto& kbm = input.GetKBM()) {\n    if (kbm->m_mouseButtons[size_t(EMouseButton::Primary)]) {\n      mouseHeld = true;\n      if (float(m_mouseDelta.x()) < 0.f)\n        dirs[3] = -m_mouseDelta.x();\n      else if (float(m_mouseDelta.x()) > 0.f)\n        dirs[2] = m_mouseDelta.x();\n      if (float(m_mouseDelta.y()) < 0.f)\n        dirs[0] = -m_mouseDelta.y();\n      else if (float(m_mouseDelta.y()) > 0.f)\n        dirs[1] = m_mouseDelta.y();\n    }\n  }\n\n  float maxMag = up;\n  size_t dirSlot = 0;\n  if (down > up) {\n    maxMag = down;\n    dirSlot = 1;\n  }\n  if (left > maxMag) {\n    maxMag = left;\n    dirSlot = 2;\n  }\n  if (right > maxMag) {\n    maxMag = right;\n    dirSlot = 3;\n  }\n\n  dirs[dirSlot] += maxMag;\n\n  if (dirs[0] > 0.f || dirs[1] > 0.f || dirs[2] > 0.f || dirs[3] > 0.f || mouseHeld) {\n    int flags = 0x0;\n    if (up > 0.f)\n      flags |= 0x2;\n    if (down > 0.f)\n      flags |= 0x1;\n    if (left > 0.f)\n      flags |= 0x4;\n    if (right > 0.f)\n      flags |= 0x8;\n\n    switch (flags) {\n    case 1: // Down\n      x2e4_lStickPos = 1;\n      break;\n    case 2: // Up\n      x2e4_lStickPos = 5;\n      break;\n    case 4: // Left\n      x2e4_lStickPos = 3;\n      break;\n    case 5: // Down-Left\n      x2e4_lStickPos = 2;\n      break;\n    case 6: // Up-Left\n      x2e4_lStickPos = 4;\n      break;\n    case 8: // Right\n      x2e4_lStickPos = 7;\n      break;\n    case 9: // Down-Right\n      x2e4_lStickPos = 8;\n      break;\n    case 10: // Up-Right\n      x2e4_lStickPos = 6;\n      break;\n    default:\n      break;\n    }\n\n    float deltaFrames = input.DeltaTime() * 60.f;\n    SetShouldRotatingSoundBePlaying(dirs[0] > 0.f || dirs[1] > 0.f || dirs[2] > 0.f || dirs[3] > 0.f);\n    zeus::CEulerAngles eulers(xa8_renderStates[0].x8_camOrientation);\n    zeus::CRelAngle angX(eulers.x());\n    angX.makeRel();\n    zeus::CRelAngle angZ(eulers.z());\n    angZ.makeRel();\n\n    float dt = deltaFrames * g_tweakAutoMapper->GetCamRotateDegreesPerFrame();\n\n    angZ -= zeus::degToRad(dt * dirs[2]);\n    angZ.makeRel();\n    angZ += zeus::degToRad(dt * dirs[3]);\n    angZ.makeRel();\n\n    angX -= zeus::degToRad(dt * dirs[0]);\n    angX.makeRel();\n    angX += zeus::degToRad(dt * dirs[1]);\n    angX.makeRel();\n\n    float angXDeg = angX.asDegrees();\n    if (angXDeg > 180.f)\n      angXDeg -= 360.f;\n    angX = zeus::degToRad(\n        zeus::clamp(g_tweakAutoMapper->GetMinCamRotateX(), angXDeg, g_tweakAutoMapper->GetMaxCamRotateX()));\n    angX.makeRel();\n\n    zeus::CQuaternion quat;\n    quat.rotateZ(angZ);\n    quat.rotateX(angX);\n    quat.rotateY(0.f);\n    xa8_renderStates[0].x8_camOrientation = quat;\n  } else {\n    x2e4_lStickPos = 0;\n    SetShouldRotatingSoundBePlaying(false);\n  }\n}\n\nvoid CAutoMapper::ProcessMapZoomInput(const CFinalInput& input, const CStateManager& mgr) {\n  bool in = ControlMapper::GetDigitalInput(ControlMapper::ECommands::MapZoomIn, input);\n  bool out = ControlMapper::GetDigitalInput(ControlMapper::ECommands::MapZoomOut, input);\n\n  float zoomSpeed = 1.f;\n  if (const auto& kbm = input.GetKBM()) {\n    m_mapScroll += kbm->m_accumScroll - m_lastAccumScroll;\n    m_lastAccumScroll = kbm->m_accumScroll;\n    if (m_mapScroll.delta[1] > 0.0) {\n      in = true;\n      zoomSpeed = std::max(1.f, float(m_mapScroll.delta[1]));\n      m_mapScroll.delta[1] = std::max(0.0, m_mapScroll.delta[1] - (15.0 / 60.0));\n    } else if (m_mapScroll.delta[1] < 0.0) {\n      out = true;\n      zoomSpeed = std::max(1.f, float(-m_mapScroll.delta[1]));\n      m_mapScroll.delta[1] = std::min(0.0, m_mapScroll.delta[1] + (15.0 / 60.0));\n    }\n  }\n\n  const EZoomState nextZoomState = [this, in, out] {\n    switch (x324_zoomState) {\n    case EZoomState::None:\n    case EZoomState::In:\n    case EZoomState::Out:\n      if (in) {\n        return EZoomState::In;\n      }\n      if (out) {\n        return EZoomState::Out;\n      }\n      return EZoomState::None;\n\n    default:\n      return EZoomState::None;\n    }\n  }();\n  x324_zoomState = nextZoomState;\n\n  float delta = input.DeltaTime() * 60.f * (x1bc_state == EAutoMapperState::MapScreen ? 1.f : 4.f) *\n                g_tweakAutoMapper->GetCamZoomUnitsPerFrame() * zoomSpeed;\n  float oldDist = xa8_renderStates[0].x18_camDist;\n  if (x324_zoomState == EZoomState::In) {\n    xa8_renderStates[0].x18_camDist = GetClampedMapScreenCameraDistance(xa8_renderStates[0].x18_camDist - delta);\n    x2f0_rTriggerPos = 1;\n    x324_zoomState = EZoomState::In;\n  } else if (x324_zoomState == EZoomState::Out) {\n    xa8_renderStates[0].x18_camDist = GetClampedMapScreenCameraDistance(xa8_renderStates[0].x18_camDist + delta);\n    x2ec_lTriggerPos = 1;\n    x324_zoomState = EZoomState::Out;\n  }\n\n  if (oldDist == xa8_renderStates[0].x18_camDist)\n    m_mapScroll.delta[1] = 0.0;\n  SetShouldZoomingSoundBePlaying(oldDist != xa8_renderStates[0].x18_camDist);\n}\n\nvoid CAutoMapper::ProcessMapPanInput(const CFinalInput& input, const CStateManager& mgr) {\n  float forward = ControlMapper::GetAnalogInput(ControlMapper::ECommands::MapMoveForward, input);\n  float back = ControlMapper::GetAnalogInput(ControlMapper::ECommands::MapMoveBack, input);\n  float left = ControlMapper::GetAnalogInput(ControlMapper::ECommands::MapMoveLeft, input);\n  float right = ControlMapper::GetAnalogInput(ControlMapper::ECommands::MapMoveRight, input);\n\n  bool mouseHeld = false;\n  if (const auto& kbm = input.GetKBM()) {\n    if (kbm->m_mouseButtons[size_t(EMouseButton::Middle)] || kbm->m_mouseButtons[size_t(EMouseButton::Secondary)]) {\n      mouseHeld = true;\n      if (float(m_mouseDelta.x()) < 0.f)\n        right += -m_mouseDelta.x();\n      else if (float(m_mouseDelta.x()) > 0.f)\n        left += m_mouseDelta.x();\n      if (float(m_mouseDelta.y()) < 0.f)\n        forward += -m_mouseDelta.y();\n      else if (float(m_mouseDelta.y()) > 0.f)\n        back += m_mouseDelta.y();\n    }\n  }\n\n  zeus::CTransform camRot = xa8_renderStates[0].x8_camOrientation.toTransform();\n  if (forward > 0.f || back > 0.f || left > 0.f || right > 0.f || mouseHeld) {\n    float deltaFrames = 60.f * input.DeltaTime();\n    float speed = GetFinalMapScreenCameraMoveSpeed();\n    int flags = 0x0;\n    if (forward > 0.f)\n      flags |= 0x1;\n    if (back > 0.f)\n      flags |= 0x2;\n    if (left > 0.f)\n      flags |= 0x4;\n    if (right > 0.f)\n      flags |= 0x8;\n\n    switch (flags) {\n    case 1: // Forward\n      x2e8_rStickPos = 1;\n      break;\n    case 2: // Back\n      x2e8_rStickPos = 5;\n      break;\n    case 4: // Left\n      x2e8_rStickPos = 3;\n      break;\n    case 5: // Forward-Left\n      x2e8_rStickPos = 2;\n      break;\n    case 6: // Back-Left\n      x2e8_rStickPos = 4;\n      break;\n    case 8: // Right\n      x2e8_rStickPos = 7;\n      break;\n    case 9: // Forward-Right\n      x2e8_rStickPos = 8;\n      break;\n    case 10: // Back-Right\n      x2e8_rStickPos = 6;\n      break;\n    default:\n      break;\n    }\n\n    zeus::CVector3f dirVec(right - left, 0.f, forward - back);\n    zeus::CVector3f deltaVec = camRot * (dirVec * deltaFrames * speed);\n    zeus::CVector3f newPoint = xa8_renderStates[0].x20_areaPoint + deltaVec;\n    SetShouldPanningSoundBePlaying(deltaVec.magnitude() > input.DeltaTime());\n\n    if (x1bc_state == EAutoMapperState::MapScreen) {\n      xa8_renderStates[0].x20_areaPoint = x24_world->IGetMapWorld()->ConstrainToWorldVolume(newPoint, camRot.basis[1]);\n    } else {\n      zeus::CVector3f localPoint = newPoint - x8_mapu->GetMapUniverseCenterPoint();\n      if (localPoint.magnitude() > x8_mapu->GetMapUniverseRadius())\n        newPoint = x8_mapu->GetMapUniverseCenterPoint() + localPoint.normalized() * x8_mapu->GetMapUniverseRadius();\n      xa8_renderStates[0].x20_areaPoint = newPoint;\n    }\n  } else {\n    x2e8_rStickPos = 0;\n    SetShouldPanningSoundBePlaying(false);\n    float speed = g_tweakAutoMapper->GetCamPanUnitsPerFrame() * GetBaseMapScreenCameraMoveSpeed();\n    if (x1bc_state == EAutoMapperState::MapScreen) {\n      const CMapArea* area = x24_world->IGetMapWorld()->GetMapArea(xa0_curAreaId);\n      zeus::CVector3f worldPoint = area->GetAreaPostTransform(*x24_world, xa0_curAreaId) * area->GetAreaCenterPoint();\n      zeus::CVector3f viewPoint = worldPoint - xa8_renderStates[0].x20_areaPoint;\n      if (viewPoint.magnitude() < speed)\n        xa8_renderStates[0].x20_areaPoint = worldPoint;\n      else\n        xa8_renderStates[0].x20_areaPoint += viewPoint.normalized() * speed;\n    } else {\n      std::pair<int, int> areas = FindClosestVisibleWorld(xa8_renderStates[0].x20_areaPoint, camRot.basis[1], mgr);\n      const zeus::CTransform& hex = x8_mapu->GetMapWorldData(areas.first).GetMapAreaData(areas.second);\n      zeus::CVector3f areaToHex = hex.origin - xa8_renderStates[0].x20_areaPoint;\n      if (areaToHex.magnitude() < speed)\n        xa8_renderStates[0].x20_areaPoint = hex.origin;\n      else\n        xa8_renderStates[0].x20_areaPoint += areaToHex.normalized() * speed;\n    }\n  }\n}\n\nvoid CAutoMapper::SetShouldPanningSoundBePlaying(bool shouldBePlaying) {\n  if (shouldBePlaying) {\n    if (!x1cc_panningSfx)\n      x1cc_panningSfx = CSfxManager::SfxStart(SFXui_map_pan, 1.f, 0.f, false, 0x7f, true, kInvalidAreaId);\n  } else {\n    CSfxManager::SfxStop(x1cc_panningSfx);\n    x1cc_panningSfx.reset();\n  }\n}\n\nvoid CAutoMapper::SetShouldZoomingSoundBePlaying(bool shouldBePlaying) {\n  if (shouldBePlaying) {\n    if (!x1d4_zoomingSfx)\n      x1d4_zoomingSfx = CSfxManager::SfxStart(SFXui_map_zoom, 1.f, 0.f, false, 0x7f, true, kInvalidAreaId);\n  } else {\n    CSfxManager::SfxStop(x1d4_zoomingSfx);\n    x1d4_zoomingSfx.reset();\n  }\n}\n\nvoid CAutoMapper::SetShouldRotatingSoundBePlaying(bool shouldBePlaying) {\n  if (shouldBePlaying) {\n    if (!x1d0_rotatingSfx)\n      x1d0_rotatingSfx = CSfxManager::SfxStart(SFXui_map_rotate, 1.f, 0.f, false, 0x7f, true, kInvalidAreaId);\n  } else {\n    CSfxManager::SfxStop(x1d0_rotatingSfx);\n    x1d0_rotatingSfx.reset();\n  }\n}\n\nvoid CAutoMapper::ProcessMapScreenInput(const CFinalInput& input, CStateManager& mgr) {\n  zeus::CMatrix3f camRot = xa8_renderStates[0].x8_camOrientation.toTransform().buildMatrix3f();\n  if (x1bc_state == EAutoMapperState::MapScreen) {\n    if ((input.PA() || input.PSpecialKey(ESpecialKey::Enter)) && x328_ == 0 && HasCurrentMapUniverseWorld())\n      BeginMapperStateTransition(EAutoMapperState::MapScreenUniverse, mgr);\n  } else if (x1bc_state == EAutoMapperState::MapScreenUniverse &&\n             (input.PA() || input.PSpecialKey(ESpecialKey::Enter))) {\n    const CMapUniverse::CMapWorldData& mapuWld = x8_mapu->GetMapWorldData(x9c_worldIdx);\n    zeus::CVector3f pointLocal = mapuWld.GetWorldTransform().inverse() * xa8_renderStates[0].x20_areaPoint;\n    if (mapuWld.GetWorldAssetId() != g_GameState->CurrentWorldAssetId()) {\n      x32c_loadingDummyWorld = true;\n      CheckDummyWorldLoad(mgr);\n    } else {\n      x24_world = mgr.GetWorld();\n      CMapWorldInfo& mwInfo = *g_GameState->StateForWorld(x24_world->IGetWorldAssetId()).MapWorldInfo();\n      xa0_curAreaId = FindClosestVisibleArea(pointLocal, zeus::CUnitVector3f(camRot[1]), mgr, *x24_world, mwInfo);\n      BeginMapperStateTransition(EAutoMapperState::MapScreen, mgr);\n    }\n  }\n\n  x2f4_aButtonPos = 0;\n  if (input.PA() || input.PSpecialKey(ESpecialKey::Enter))\n    x2f4_aButtonPos = 1;\n\n  if (IsInPlayerControlState()) {\n    x2ec_lTriggerPos = 0;\n    x2f0_rTriggerPos = 0;\n\n    if (const auto& kbm = input.GetKBM()) {\n      zeus::CVector2f mouseCoord = zeus::CVector2f(kbm->m_mouseCoord.norm[0], kbm->m_mouseCoord.norm[1]);\n      if (!m_lastMouseCoord) {\n        m_lastMouseCoord.emplace(mouseCoord);\n      } else {\n        m_mouseDelta = mouseCoord - *m_lastMouseCoord;\n        m_lastMouseCoord.emplace(mouseCoord);\n        m_mouseDelta.x() *= CGraphics::GetViewportAspect();\n        m_mouseDelta *= 100.f;\n      }\n    }\n\n    ProcessMapRotateInput(input, mgr);\n    ProcessMapZoomInput(input, mgr);\n    ProcessMapPanInput(input, mgr);\n  }\n}\n\nzeus::CQuaternion CAutoMapper::GetMiniMapCameraOrientation(const CStateManager& stateMgr) const {\n  const CGameCamera* cam = stateMgr.GetCameraManager()->GetCurrentCamera(stateMgr);\n  zeus::CEulerAngles camAngles(zeus::CQuaternion(cam->GetTransform().buildMatrix3f()));\n  zeus::CRelAngle angle(camAngles.z());\n  angle.makeRel();\n\n  zeus::CQuaternion ret;\n  ret.rotateZ(angle);\n  ret.rotateX(zeus::degToRad(g_tweakAutoMapper->GetMiniCamXAngle()));\n  return ret;\n}\n\nzeus::CVector3f CAutoMapper::GetAreaPointOfInterest(const CStateManager&, TAreaId aid) const {\n  const CMapArea* mapa = x24_world->IGetMapWorld()->GetMapArea(aid);\n  return mapa->GetAreaPostTransform(*x24_world, aid) * mapa->GetAreaCenterPoint();\n}\n\nTAreaId CAutoMapper::FindClosestVisibleArea(const zeus::CVector3f& point, const zeus::CUnitVector3f& camDir,\n                                            const CStateManager& mgr, const IWorld& wld,\n                                            const CMapWorldInfo& mwInfo) const {\n  float minDist = 9999.f;\n  TAreaId closestArea = xa0_curAreaId;\n  const CMapWorld* mw = wld.IGetMapWorld();\n  std::vector<TAreaId> areas = mw->GetVisibleAreas(wld, mwInfo);\n  for (TAreaId areaId : areas) {\n    const CMapArea* mapa = mw->GetMapArea(areaId);\n    zeus::CVector3f xfPoint = mapa->GetAreaPostTransform(wld, areaId) * mapa->GetAreaCenterPoint();\n    zeus::CVector3f pointToArea = xfPoint - point;\n    pointToArea = pointToArea.canBeNormalized()\n                      ? point + (pointToArea.normalized().dot(camDir) * pointToArea.magnitude()) * camDir\n                      : point;\n    pointToArea -= xfPoint;\n    float dist = pointToArea.magnitude();\n    if (dist < minDist) {\n      minDist = dist;\n      closestArea = areaId;\n    }\n  }\n  return closestArea;\n}\n\nstd::pair<int, int> CAutoMapper::FindClosestVisibleWorld(const zeus::CVector3f& point,\n                                                         const zeus::CUnitVector3f& camDir,\n                                                         const CStateManager& mgr) const {\n  float minDist = 29999.f;\n  std::pair<int, int> closestWorld = {x9c_worldIdx, xa0_curAreaId};\n  for (u32 w = 0; w < x8_mapu->GetNumMapWorldDatas(); ++w) {\n    const CMapUniverse::CMapWorldData& mwData = x8_mapu->GetMapWorldData(w);\n    const CMapWorldInfo& mwInfo = *g_GameState->StateForWorld(mwData.GetWorldAssetId()).MapWorldInfo();\n    if (!mwInfo.IsAnythingSet())\n      continue;\n    for (u32 i = 0; i < mwData.GetNumMapAreaDatas(); ++i) {\n      const zeus::CVector3f& mwOrigin = mwData.GetMapAreaData(i).origin;\n      zeus::CVector3f pointToArea = mwOrigin - point;\n      pointToArea = pointToArea.canBeNormalized()\n                        ? point + (pointToArea.normalized().dot(camDir) * pointToArea.magnitude()) * camDir\n                        : point;\n      pointToArea -= mwOrigin;\n      float dist = pointToArea.magnitude();\n      if (dist < minDist) {\n        minDist = dist;\n        closestWorld.first = w;\n        closestWorld.second = i;\n      }\n    }\n  }\n  return closestWorld;\n}\n\nzeus::CVector2i CAutoMapper::GetMiniMapViewportSize() {\n  float scaleX = CGraphics::GetViewportWidth() / 640.f;\n  float scaleY = CGraphics::GetViewportHeight() / 480.f;\n  return {int(scaleX * g_tweakAutoMapper->GetMiniMapViewportWidth()),\n          int(scaleY * g_tweakAutoMapper->GetMiniMapViewportHeight())};\n}\n\nzeus::CVector2i CAutoMapper::GetMapScreenViewportSize() {\n  return {int(CGraphics::GetViewportWidth()), int(CGraphics::GetViewportHeight())};\n}\n\nfloat CAutoMapper::GetMapAreaMaxDrawDepth(const CStateManager&, TAreaId aid) const {\n  return x24_world->IGetMapWorld()->GetCurrentMapAreaDepth(*x24_world, aid);\n}\n\nfloat CAutoMapper::GetMapAreaMiniMapDrawAlphaSurfaceVisited(const CStateManager& stateMgr) {\n  float mapAlphaInterp = g_tweakGui->GetMapAlphaInterpolant();\n  return g_tweakAutoMapper->GetMiniAlphaSurfaceVisited() * (1.f - mapAlphaInterp) * stateMgr.Player()->GetGunAlpha() +\n         mapAlphaInterp;\n}\n\nfloat CAutoMapper::GetMapAreaMiniMapDrawAlphaOutlineVisited(const CStateManager& stateMgr) {\n  float mapAlphaInterp = g_tweakGui->GetMapAlphaInterpolant();\n  return g_tweakAutoMapper->GetMiniAlphaOutlineVisited() * (1.f - mapAlphaInterp) * stateMgr.Player()->GetGunAlpha() +\n         mapAlphaInterp;\n}\n\nfloat CAutoMapper::GetMapAreaMiniMapDrawAlphaSurfaceUnvisited(const CStateManager& stateMgr) {\n  float mapAlphaInterp = g_tweakGui->GetMapAlphaInterpolant();\n  return g_tweakAutoMapper->GetMiniAlphaSurfaceUnvisited() * (1.f - mapAlphaInterp) * stateMgr.Player()->GetGunAlpha() +\n         mapAlphaInterp;\n}\n\nfloat CAutoMapper::GetMapAreaMiniMapDrawAlphaOutlineUnvisited(const CStateManager& stateMgr) {\n  float mapAlphaInterp = g_tweakGui->GetMapAlphaInterpolant();\n  return g_tweakAutoMapper->GetMiniAlphaOutlineUnvisited() * (1.f - mapAlphaInterp) * stateMgr.Player()->GetGunAlpha() +\n         mapAlphaInterp;\n}\n\nfloat CAutoMapper::GetDesiredMiniMapCameraDistance(const CStateManager& mgr) const {\n  const CMapWorldInfo& mwInfo = *g_GameState->StateForWorld(x24_world->IGetWorldAssetId()).MapWorldInfo();\n  const CMapWorld* mw = x24_world->IGetMapWorld();\n  zeus::CAABox aabb;\n  const IGameArea* area = x24_world->IGetAreaAlways(xa0_curAreaId);\n  const CMapArea* mapa = mw->GetMapArea(xa0_curAreaId);\n  bool oneMiniMapArea = g_tweakAutoMapper->GetShowOneMiniMapArea();\n  for (int i = -1; i < (oneMiniMapArea ? 0 : int(area->IGetNumAttachedAreas())); ++i) {\n    TAreaId aid = i == -1 ? xa0_curAreaId : area->IGetAttachedAreaId(i);\n    const CMapArea* attMapa = mw->GetMapArea(aid);\n    if (attMapa->GetIsVisibleToAutoMapper(mwInfo.IsWorldVisible(aid), mwInfo.IsAreaVisible(aid))) {\n      zeus::CAABox areaAABB =\n          attMapa->GetBoundingBox().getTransformedAABox(attMapa->GetAreaPostTransform(*x24_world, aid));\n      aabb.accumulateBounds(areaAABB.min);\n      aabb.accumulateBounds(areaAABB.max);\n    }\n  }\n\n  zeus::CVector3f xfPoint = mapa->GetAreaPostTransform(*x24_world, xa0_curAreaId) * mapa->GetAreaCenterPoint();\n  zeus::CVector3f maxMargin;\n  maxMargin.x() = std::max(xfPoint.x() - aabb.min.x(), aabb.max.x() - xfPoint.x());\n  maxMargin.y() = std::max(xfPoint.y() - aabb.min.y(), aabb.max.y() - xfPoint.y());\n  maxMargin.z() = std::max(xfPoint.z() - aabb.min.z(), aabb.max.z() - xfPoint.z());\n  zeus::CVector3f extent = mapa->GetBoundingBox().max - mapa->GetBoundingBox().min;\n\n  return (0.5f * (0.5f * extent.magnitude()) + 0.5f * maxMargin.magnitude()) *\n         g_tweakAutoMapper->GetMiniMapCamDistScale() *\n         std::tan(M_PIF / 2.f - 0.5f * 2.f * M_PIF * (xa8_renderStates[0].x1c_camAngle / 360.f));\n}\n\nfloat CAutoMapper::GetClampedMapScreenCameraDistance(float value) const {\n  if (x1bc_state == EAutoMapperState::MapScreenUniverse) {\n    return zeus::clamp(g_tweakAutoMapper->GetMinUniverseCamDist(), value, g_tweakAutoMapper->GetMaxUniverseCamDist());\n  }\n  return zeus::clamp(g_tweakAutoMapper->GetMinCamDist(), value, g_tweakAutoMapper->GetMaxCamDist());\n}\n\nvoid CAutoMapper::MuteAllLoopedSounds() {\n  CSfxManager::SfxVolume(x1cc_panningSfx, 0.f);\n  CSfxManager::SfxVolume(x1d0_rotatingSfx, 0.f);\n  CSfxManager::SfxVolume(x1d4_zoomingSfx, 0.f);\n}\n\nvoid CAutoMapper::UnmuteAllLoopedSounds() {\n  CSfxManager::SfxVolume(x1cc_panningSfx, 1.f);\n  CSfxManager::SfxVolume(x1d0_rotatingSfx, 1.f);\n  CSfxManager::SfxVolume(x1d4_zoomingSfx, 1.f);\n}\n\nvoid CAutoMapper::ProcessControllerInput(const CFinalInput& input, CStateManager& mgr) {\n  if (!IsRenderStateInterpolating()) {\n    if (IsInPlayerControlState()) {\n      if (x32c_loadingDummyWorld)\n        CheckDummyWorldLoad(mgr);\n      else if (x1e0_hintSteps.size())\n        UpdateHintNavigation(input.DeltaTime(), mgr);\n      else if (x328_ == 0)\n        ProcessMapScreenInput(input, mgr);\n    }\n  }\n\n  zeus::CMatrix3f camRot = xa8_renderStates[0].x8_camOrientation.toTransform().buildMatrix3f();\n  if (IsInMapperState(EAutoMapperState::MapScreen)) {\n    CMapWorldInfo& mwInfo = *g_GameState->StateForWorld(x24_world->IGetWorldAssetId()).MapWorldInfo();\n    TAreaId aid = FindClosestVisibleArea(xa8_renderStates[0].x20_areaPoint, camRot[1], mgr, *x24_world, mwInfo);\n    if (aid != xa0_curAreaId) {\n      xa0_curAreaId = aid;\n      xa8_renderStates[0].x2c_drawDepth1 = GetMapAreaMaxDrawDepth(mgr, xa0_curAreaId);\n      xa8_renderStates[0].x30_drawDepth2 = GetMapAreaMaxDrawDepth(mgr, xa0_curAreaId);\n    }\n  } else if (IsInMapperState(EAutoMapperState::MapScreenUniverse)) {\n    u32 oldWldIdx = x9c_worldIdx;\n    if (x1e0_hintSteps.size()) {\n      SAutoMapperHintStep& nextStep = x1e0_hintSteps.front();\n      if (nextStep.x0_type == SAutoMapperHintStep::Type::PanToWorld ||\n          nextStep.x0_type == SAutoMapperHintStep::Type::SwitchToWorld) {\n        SetCurWorldAssetId(nextStep.x4_worldId);\n      } else {\n        std::pair<int, int> wld = FindClosestVisibleWorld(xa8_renderStates[0].x20_areaPoint, camRot[1], mgr);\n        x9c_worldIdx = wld.first;\n      }\n    } else {\n      std::pair<int, int> wld = FindClosestVisibleWorld(xa8_renderStates[0].x20_areaPoint, camRot[1], mgr);\n      x9c_worldIdx = wld.first;\n    }\n\n    if (x9c_worldIdx != oldWldIdx) {\n      CAssetId curMlvl = g_GameState->CurrentWorldAssetId();\n      for (u32 i = 0; i < x14_dummyWorlds.size(); ++i) {\n        auto& wld = x14_dummyWorlds[i];\n        const CMapUniverse::CMapWorldData& mwData = x8_mapu->GetMapWorldData(i);\n        if (i == x9c_worldIdx && curMlvl != mwData.GetWorldAssetId()) {\n          if (g_ResFactory->CanBuild(SObjectTag{FOURCC('MLVL'), mwData.GetWorldAssetId()}))\n            wld = std::make_unique<CDummyWorld>(mwData.GetWorldAssetId(), true);\n        } else {\n          wld.reset();\n        }\n      }\n      x24_world = (curMlvl == x8_mapu->GetMapWorldData(x9c_worldIdx).GetWorldAssetId()) ? mgr.GetWorld() : nullptr;\n    }\n  }\n\n  if (x300_textpane_instructions) {\n    if (x78_areaHintDesc.IsLoaded()) {\n      x2fc_textpane_hint->TextSupport().SetText(x78_areaHintDesc->GetString(0));\n      x304_textpane_instructions1->TextSupport().SetText(u\"\");\n      x300_textpane_instructions->TextSupport().SetText(u\"\");\n      x308_textpane_instructions2->TextSupport().SetText(u\"\");\n    } else {\n      x2fc_textpane_hint->TextSupport().SetText(u\"\");\n      std::u16string str = fmt::format(u\"&image=SI,0.6,1.0,{};\", g_tweakPlayerRes->x24_lStick[x2e4_lStickPos]);\n      str += g_MainStringTable->GetString(46 + (!g_Main->IsUSA() || g_Main->IsTrilogy())); // Rotate\n      x300_textpane_instructions->TextSupport().SetText(str);\n      str = fmt::format(u\"&image=SI,0.6,1.0,{};\", g_tweakPlayerRes->x4c_cStick[x2e8_rStickPos]);\n      str += g_MainStringTable->GetString(47 + (!g_Main->IsUSA() || g_Main->IsTrilogy())); // Move\n      x304_textpane_instructions1->TextSupport().SetText(str);\n      str = fmt::format(u\"&image={};\", g_tweakPlayerRes->x74_lTrigger[x2ec_lTriggerPos]);\n      str += g_MainStringTable->GetString(48 + (!g_Main->IsUSA() || g_Main->IsTrilogy())); // Zoom\n      str += fmt::format(u\"&image={};\", g_tweakPlayerRes->x80_rTrigger[x2f0_rTriggerPos]);\n      x308_textpane_instructions2->TextSupport().SetText(str);\n    }\n  }\n\n  if (input.PY() || input.PKey(' ')) {\n    CPersistentOptions& sysOpts = g_GameState->SystemOptions();\n    switch (sysOpts.GetAutoMapperKeyState()) {\n    case 0:\n      sysOpts.SetAutoMapperKeyState(1);\n      CSfxManager::SfxStart(SFXui_map_screen_key1, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n      break;\n    case 1:\n      sysOpts.SetAutoMapperKeyState(2);\n      CSfxManager::SfxStart(SFXui_map_screen_key2, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n      break;\n    case 2:\n      sysOpts.SetAutoMapperKeyState(0);\n      CSfxManager::SfxStart(SFXui_map_screen_key0, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n      break;\n    default:\n      break;\n    }\n  }\n\n  if (input.PZ() || input.PSpecialKey(ESpecialKey::Tab) || input.PB() || input.PSpecialKey(ESpecialKey::Esc)) {\n    if (x328_ == 0) {\n      if (CanLeaveMapScreenInternal(mgr)) {\n        LeaveMapScreen(mgr);\n      } else if (NotHintNavigating()) {\n        BeginMapperStateTransition(EAutoMapperState::MapScreenUniverse, mgr);\n        x328_ = 1;\n      }\n    }\n  }\n}\n\nvoid CAutoMapper::Update(float dt, CStateManager& mgr) {\n  if (x1bc_state != EAutoMapperState::MiniMap && x1c0_nextState != EAutoMapperState::MiniMap) {\n    x1d8_flashTimer = std::fmod(x1d8_flashTimer + dt, 0.75f);\n    x1dc_playerFlashPulse = x1d8_flashTimer < 0.375f ? x1d8_flashTimer / 0.375f : (0.75f - x1d8_flashTimer) / 0.375f;\n  }\n\n  if (!m_frmeInitialized && x28_frmeMapScreen.IsLoaded()) {\n    x28_frmeMapScreen->SetMaxAspect(1.78f);\n    m_frmeInitialized = true;\n    static_cast<CGuiTextPane*>(x28_frmeMapScreen->FindWidget(\"textpane_left\"))\n        ->TextSupport()\n        .SetText(g_MainStringTable->GetString(42 + (!g_Main->IsUSA() || g_Main->IsTrilogy())));\n    static_cast<CGuiTextPane*>(x28_frmeMapScreen->FindWidget(\"textpane_yicon\"))\n        ->TextSupport()\n        .SetText(g_MainStringTable->GetString(43 + (!g_Main->IsUSA() || g_Main->IsTrilogy())));\n    x2fc_textpane_hint = static_cast<CGuiTextPane*>(x28_frmeMapScreen->FindWidget(\"textpane_hint\"));\n    x300_textpane_instructions = static_cast<CGuiTextPane*>(x28_frmeMapScreen->FindWidget(\"textpane_instructions\"));\n    x304_textpane_instructions1 = static_cast<CGuiTextPane*>(x28_frmeMapScreen->FindWidget(\"textpane_instructions1\"));\n    x308_textpane_instructions2 = static_cast<CGuiTextPane*>(x28_frmeMapScreen->FindWidget(\"textpane_instructions2\"));\n    CGuiTextPane* mapLegend = static_cast<CGuiTextPane*>(x28_frmeMapScreen->FindWidget(\"textpane_mapLegend\"));\n    mapLegend->TextSupport().ClearRenderBuffer();\n    mapLegend->TextSupport().SetImageBaseline(true);\n    mapLegend->TextSupport().SetText(g_MainStringTable->GetString(49 + (!g_Main->IsUSA() || g_Main->IsTrilogy())));\n    x30c_basewidget_leftPane = x28_frmeMapScreen->FindWidget(\"basewidget_leftPane\");\n    x310_basewidget_yButtonPane = x28_frmeMapScreen->FindWidget(\"basewidget_yButtonPane\");\n    x314_basewidget_bottomPane = x28_frmeMapScreen->FindWidget(\"basewidget_bottomPane\");\n    x2f8_textpane_areaname = static_cast<CGuiTextPane*>(x28_frmeMapScreen->FindWidget(\"textpane_areaname\"));\n    x2f8_textpane_areaname->SetDepthTest(false);\n  }\n\n  if (m_frmeInitialized) {\n    x28_frmeMapScreen->Update(dt);\n    CGuiTextPane* right1 = static_cast<CGuiTextPane*>(x28_frmeMapScreen->FindWidget(\"textpane_right1\"));\n    std::u16string string;\n    if (x1bc_state == EAutoMapperState::MapScreenUniverse ||\n        (x1bc_state == EAutoMapperState::MapScreen && HasCurrentMapUniverseWorld()))\n      string = fmt::format(u\"&image={};\", g_tweakPlayerRes->x98_aButton[x2f4_aButtonPos]);\n    right1->TextSupport().SetText(string);\n    CGuiTextPane* right = static_cast<CGuiTextPane*>(x28_frmeMapScreen->FindWidget(\"textpane_right\"));\n    if (x1bc_state == EAutoMapperState::MapScreenUniverse)\n      string = g_MainStringTable->GetString(45);\n    else if (x1bc_state == EAutoMapperState::MapScreen && HasCurrentMapUniverseWorld())\n      string = g_MainStringTable->GetString(44);\n    else\n      string = std::u16string();\n    right->TextSupport().SetText(string);\n  }\n\n  float dt2 = 2.f * dt;\n  switch (g_GameState->SystemOptions().GetAutoMapperKeyState()) {\n  case 0: // All shown\n    x318_leftPanePos -= dt2;\n    x31c_yButtonPanePos -= dt2;\n    x320_bottomPanePos -= dt2;\n    break;\n  case 1: // Left shown\n    x318_leftPanePos += dt2;\n    x31c_yButtonPanePos -= dt2;\n    x320_bottomPanePos -= dt2;\n    break;\n  case 2: // All hidden\n    x318_leftPanePos += dt2;\n    x31c_yButtonPanePos += dt2;\n    x320_bottomPanePos += dt2;\n    break;\n  default:\n    break;\n  }\n\n  x318_leftPanePos = std::max(0.f, std::min(x318_leftPanePos, 1.f));\n  x31c_yButtonPanePos = std::max(0.f, std::min(x31c_yButtonPanePos, 1.f));\n  x320_bottomPanePos = std::max(0.f, std::min(x320_bottomPanePos, 1.f));\n\n  if (x30c_basewidget_leftPane) {\n    float vpAspectRatio = std::max(1.78f, CGraphics::GetViewportAspect());\n    x30c_basewidget_leftPane->SetLocalTransform(\n        zeus::CTransform::Translate(x318_leftPanePos * vpAspectRatio * -9.f, 0.f, 0.f) *\n        x30c_basewidget_leftPane->GetTransform());\n  }\n\n  if (x310_basewidget_yButtonPane) {\n    x310_basewidget_yButtonPane->SetLocalTransform(zeus::CTransform::Translate(0.f, 0.f, x31c_yButtonPanePos * -3.5f) *\n                                                   x310_basewidget_yButtonPane->GetTransform());\n  }\n\n  if (x314_basewidget_bottomPane) {\n    x314_basewidget_bottomPane->SetLocalTransform(zeus::CTransform::Translate(0.f, 0.f, x320_bottomPanePos * -7.f) *\n                                                  x314_basewidget_bottomPane->GetTransform());\n  }\n\n  if (IsInMapperState(EAutoMapperState::MiniMap)) {\n    xa8_renderStates[0].x8_camOrientation = GetMiniMapCameraOrientation(mgr);\n    float desiredDist = GetDesiredMiniMapCameraDistance(mgr);\n    if (std::fabs(xa8_renderStates[0].x18_camDist - desiredDist) < 3.f)\n      xa8_renderStates[0].x18_camDist = desiredDist;\n    else if (xa8_renderStates[0].x18_camDist < desiredDist)\n      xa8_renderStates[0].x18_camDist += 3.f;\n    else\n      xa8_renderStates[0].x18_camDist -= 3.f;\n    TAreaId curAid = x24_world->IGetCurrentAreaId();\n    if (curAid != xa0_curAreaId) {\n      xa8_renderStates[2] = xa8_renderStates[0];\n      xa8_renderStates[1] = xa8_renderStates[0];\n      xa4_otherAreaId = xa0_curAreaId;\n      xa0_curAreaId = curAid;\n      xa8_renderStates[1].x20_areaPoint = GetAreaPointOfInterest(mgr, xa0_curAreaId);\n      xa8_renderStates[1].x44_viewportEase = SAutoMapperRenderState::Ease::None;\n      xa8_renderStates[1].x48_camEase = SAutoMapperRenderState::Ease::None;\n      xa8_renderStates[1].x4c_pointEase = SAutoMapperRenderState::Ease::InOut;\n      xa8_renderStates[1].x50_depth1Ease = SAutoMapperRenderState::Ease::Linear;\n      xa8_renderStates[1].x54_depth2Ease = SAutoMapperRenderState::Ease::Linear;\n      xa8_renderStates[1].x58_alphaEase = SAutoMapperRenderState::Ease::None;\n      xa8_renderStates[1].x2c_drawDepth1 = GetMapAreaMiniMapDrawDepth();\n      xa8_renderStates[1].x30_drawDepth2 = GetMapAreaMiniMapDrawDepth();\n      xa8_renderStates[2].x2c_drawDepth1 = GetMapAreaMiniMapDrawDepth() - 1.f;\n      xa8_renderStates[2].x30_drawDepth2 = GetMapAreaMiniMapDrawDepth() - 1.f;\n      ResetInterpolationTimer(g_tweakAutoMapper->GetHintPanTime());\n    }\n    xa8_renderStates[1].x34_alphaSurfaceVisited = GetMapAreaMiniMapDrawAlphaSurfaceVisited(mgr);\n    xa8_renderStates[1].x38_alphaOutlineVisited = GetMapAreaMiniMapDrawAlphaOutlineVisited(mgr);\n    xa8_renderStates[1].x3c_alphaSurfaceUnvisited = GetMapAreaMiniMapDrawAlphaSurfaceUnvisited(mgr);\n    xa8_renderStates[1].x40_alphaOutlineUnvisited = GetMapAreaMiniMapDrawAlphaOutlineUnvisited(mgr);\n  } else {\n    if (x1c0_nextState == EAutoMapperState::MiniMap) {\n      float desiredDist = GetDesiredMiniMapCameraDistance(mgr);\n      if (std::fabs(xa8_renderStates[1].x18_camDist - desiredDist) < 3.f)\n        xa8_renderStates[0].x18_camDist = desiredDist;\n      else if (xa8_renderStates[1].x18_camDist < desiredDist)\n        xa8_renderStates[1].x18_camDist += 3.f;\n      else\n        xa8_renderStates[1].x18_camDist -= 3.f;\n    } else if (x1bc_state != EAutoMapperState::MiniMap && x1c0_nextState != EAutoMapperState::MiniMap && x24_world) {\n      x24_world->IGetMapWorld()->RecalculateWorldSphere(\n          *g_GameState->StateForWorld(x24_world->IGetWorldAssetId()).MapWorldInfo(), *x24_world);\n    }\n  }\n\n  if (IsRenderStateInterpolating()) {\n    x1c8_interpTime = std::min(x1c8_interpTime + dt, x1c4_interpDur);\n    SAutoMapperRenderState::InterpolateWithClamp(xa8_renderStates[2], xa8_renderStates[0], xa8_renderStates[1],\n                                                 x1c8_interpTime / x1c4_interpDur);\n    if (x1c8_interpTime == x1c4_interpDur && x328_ == 2)\n      SetupMiniMapWorld(mgr);\n  } else if (IsInMapperStateTransition()) {\n    CompleteMapperStateTransition(mgr);\n  }\n\n  CAssetId stringId = x88_mapAreaStringId;\n  if (IsInMapperState(EAutoMapperState::MapScreenUniverse)) {\n    IWorld* wld = x14_dummyWorlds[x9c_worldIdx].get();\n    if (wld && wld->ICheckWorldComplete())\n      stringId = wld->IGetStringTableAssetId();\n    else if (x24_world)\n      stringId = x24_world->IGetStringTableAssetId();\n  } else if (x24_world) {\n    const IGameArea* area = x24_world->IGetAreaAlways(xa0_curAreaId);\n    const CMapWorldInfo& mwInfo = *g_GameState->StateForWorld(x24_world->IGetWorldAssetId()).MapWorldInfo();\n    if (mwInfo.IsMapped(xa0_curAreaId) || mwInfo.IsAreaVisited(xa0_curAreaId))\n      stringId = area->IGetStringTableAssetId();\n    else\n      stringId = {};\n  }\n\n  if (x88_mapAreaStringId != stringId) {\n    x88_mapAreaStringId = stringId;\n    if (x88_mapAreaStringId.IsValid())\n      x8c_mapAreaString = g_SimplePool->GetObj(SObjectTag{FOURCC('STRG'), x88_mapAreaStringId});\n    else\n      x8c_mapAreaString = TLockedToken<CStringTable>();\n  }\n\n  if (x2f8_textpane_areaname) {\n    if (x8c_mapAreaString) {\n      if (x8c_mapAreaString.IsLoaded())\n        x2f8_textpane_areaname->TextSupport().SetText(x8c_mapAreaString->GetString(0));\n    } else {\n      x2f8_textpane_areaname->TextSupport().SetText(u\"\");\n    }\n  }\n\n  if (IsInMapperState(EAutoMapperState::MapScreen)) {\n    CAssetId hintDesc = GetAreaHintDescriptionString(x24_world->IGetAreaAlways(xa0_curAreaId)->IGetAreaAssetId());\n    if (hintDesc != x74_areaHintDescId) {\n      x74_areaHintDescId = hintDesc;\n      if (x74_areaHintDescId.IsValid())\n        x78_areaHintDesc = g_SimplePool->GetObj(SObjectTag{FOURCC('STRG'), x74_areaHintDescId});\n      else\n        x78_areaHintDesc = TLockedToken<CStringTable>();\n    }\n  }\n\n  for (auto& wld : x14_dummyWorlds)\n    if (wld)\n      wld->ICheckWorldComplete();\n}\n\nvoid CAutoMapper::Draw(const CStateManager& mgr, const zeus::CTransform& xf, float alpha) {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CAutoMapper::Draw\", zeus::skPurple);\n  alpha *= g_GameState->GameOptions().GetHUDAlpha() / 255.f;\n  g_Renderer->SetBlendMode_AlphaBlended();\n  CGraphics::SetCullMode(ERglCullMode::Front);\n\n  float alphaInterp;\n  if (x1bc_state != EAutoMapperState::MiniMap && x1c0_nextState != EAutoMapperState::MiniMap) {\n    alphaInterp = 1.f;\n  } else if (IsInMapperState(EAutoMapperState::MiniMap)) {\n    alphaInterp = alpha;\n  } else if (x1c0_nextState == EAutoMapperState::MiniMap) {\n    float t = GetInterp();\n    alphaInterp = alpha * t + (1.f - t);\n  } else if (x1bc_state == EAutoMapperState::MiniMap) {\n    float t = GetInterp();\n    alphaInterp = alpha * (1.f - t) + t;\n  } else {\n    alphaInterp = 1.f;\n  }\n\n  zeus::CVector2i vp = xa8_renderStates[0].GetViewportSize();\n  float aspect = vp.x / float(vp.y);\n  if (aspect > 1.78f)\n    aspect = 1.78f;\n  float yScale = xa8_renderStates[0].x18_camDist /\n                 std::tan(M_PIF / 2.f - 0.5f * 2.f * M_PIF * (xa8_renderStates[0].x1c_camAngle / 360.f));\n  float xScale = yScale * aspect;\n  zeus::CTransform camXf(xa8_renderStates[0].x8_camOrientation, xa8_renderStates[0].x20_areaPoint);\n  zeus::CTransform distScale = zeus::CTransform::Scale(1.f / xScale, 0.001f, 1.f / yScale);\n  zeus::CTransform tweakScale =\n      zeus::CTransform::Scale(g_tweakAutoMapper->GetMapPlaneScaleX(), 0.f, g_tweakAutoMapper->GetMapPlaneScaleZ());\n  zeus::CTransform planeXf = xf * tweakScale * distScale * camXf.inverse();\n\n  float universeInterp = 0.f;\n  if (x1c0_nextState == EAutoMapperState::MapScreenUniverse) {\n    if (x1bc_state == EAutoMapperState::MapScreenUniverse)\n      universeInterp = 1.f;\n    else\n      universeInterp = GetInterp();\n  } else if (x1bc_state == EAutoMapperState::MapScreenUniverse) {\n    universeInterp = 1.f - GetInterp();\n  }\n\n  zeus::CTransform preXf;\n  if (x1bc_state == EAutoMapperState::MapScreenUniverse || x1c0_nextState == EAutoMapperState::MapScreenUniverse)\n    preXf = x8_mapu->GetMapWorldData(x9c_worldIdx).GetWorldTransform();\n\n  float objectScale = xa8_renderStates[0].x18_camDist / g_tweakAutoMapper->GetMinCamDist();\n  float mapAlpha = alphaInterp * (1.f - universeInterp);\n\n  if (x1bc_state != EAutoMapperState::MiniMap && x1c0_nextState != EAutoMapperState::MiniMap) {\n    if (universeInterp < 1.f && x24_world) {\n      const CMapWorldInfo& mwInfo = *g_GameState->StateForWorld(x24_world->IGetWorldAssetId()).MapWorldInfo();\n      CMapWorld* mw = x24_world->IGetMapWorld();\n      float hintFlash = 0.f;\n      if (x1e0_hintSteps.size() && x1e0_hintSteps.front().x0_type == SAutoMapperHintStep::Type::ShowBeacon) {\n        if (xa0_curAreaId == mgr.GetNextAreaId() && x24_world == mgr.GetWorld()) {\n          float pulseTime = std::fmod(x1e0_hintSteps.front().x4_float * 8.f, 1.f);\n          hintFlash = 2.f * (pulseTime < 0.5f ? pulseTime : 1.f - pulseTime);\n        } else {\n          for (const SAutoMapperHintLocation& loc : x1f8_hintLocations) {\n            if (x24_world->IGetWorldAssetId() != loc.x8_worldId)\n              continue;\n            if (xa0_curAreaId != loc.xc_areaId)\n              continue;\n            float pulseTime =\n                std::fmod((1.f - std::max(0.f, (x1e0_hintSteps.front().x4_float - 0.5f) / 0.5f)) * 4.f, 1.f);\n            hintFlash = 2.f * (pulseTime < 0.5f ? pulseTime : 1.f - pulseTime);\n            break;\n          }\n        }\n      }\n      const zeus::CTransform modelXf = planeXf * preXf;\n      const CMapWorld::CMapWorldDrawParms parms(xa8_renderStates[0].x34_alphaSurfaceVisited * alphaInterp,\n                                                xa8_renderStates[0].x38_alphaOutlineVisited * alphaInterp,\n                                                xa8_renderStates[0].x3c_alphaSurfaceUnvisited * alphaInterp,\n                                                xa8_renderStates[0].x40_alphaOutlineUnvisited * alphaInterp, mapAlpha,\n                                                2.f, mgr, modelXf, camXf, *x24_world, mwInfo, x1dc_playerFlashPulse,\n                                                hintFlash, objectScale, true);\n      mw->Draw(parms, xa0_curAreaId, xa0_curAreaId, xa8_renderStates[0].x2c_drawDepth1,\n               xa8_renderStates[0].x30_drawDepth2, true);\n    }\n  } else if (IsInMapperState(EAutoMapperState::MiniMap)) {\n    CMapWorld* mw = x24_world->IGetMapWorld();\n    const CMapWorldInfo& mwInfo = *g_GameState->StateForWorld(x24_world->IGetWorldAssetId()).MapWorldInfo();\n    const CMapWorld::CMapWorldDrawParms parms(xa8_renderStates[0].x34_alphaSurfaceVisited * alphaInterp,\n                                              xa8_renderStates[0].x38_alphaOutlineVisited * alphaInterp,\n                                              xa8_renderStates[0].x3c_alphaSurfaceUnvisited * alphaInterp,\n                                              xa8_renderStates[0].x40_alphaOutlineUnvisited * alphaInterp, mapAlpha,\n                                              1.f, mgr, planeXf, camXf, *x24_world, mwInfo, 0.f, 0.f, objectScale,\n                                              false);\n    mw->Draw(parms, xa0_curAreaId, xa4_otherAreaId, xa8_renderStates[0].x2c_drawDepth1,\n             xa8_renderStates[0].x30_drawDepth2, false);\n  } else {\n    CMapWorld* mw = x24_world->IGetMapWorld();\n    const CMapWorldInfo& mwInfo = *g_GameState->StateForWorld(x24_world->IGetWorldAssetId()).MapWorldInfo();\n    zeus::CTransform modelXf = planeXf * preXf;\n    const CMapWorld::CMapWorldDrawParms parms(xa8_renderStates[0].x34_alphaSurfaceVisited * alphaInterp,\n                                              xa8_renderStates[0].x38_alphaOutlineVisited * alphaInterp,\n                                              xa8_renderStates[0].x3c_alphaSurfaceUnvisited * alphaInterp,\n                                              xa8_renderStates[0].x40_alphaOutlineUnvisited * alphaInterp, mapAlpha,\n                                              2.f, mgr, modelXf, camXf, *x24_world, mwInfo, 0.f, 0.f, objectScale,\n                                              true);\n    mw->Draw(parms, xa0_curAreaId, xa0_curAreaId, xa8_renderStates[0].x2c_drawDepth1,\n             xa8_renderStates[0].x30_drawDepth2, false);\n  }\n\n  if (universeInterp > 0.f) {\n    zeus::CTransform areaXf = mgr.GetWorld()\n                                  ->GetMapWorld()\n                                  ->GetMapArea(mgr.GetNextAreaId())\n                                  ->GetAreaPostTransform(*mgr.GetWorld(), mgr.GetNextAreaId());\n    const CMapUniverse::CMapWorldData& mwData = x8_mapu->GetMapWorldDataByWorldId(g_GameState->CurrentWorldAssetId());\n    zeus::CTransform universeAreaXf = mwData.GetWorldTransform() * areaXf;\n    float minMag = FLT_MAX;\n    int hexIdx = -1;\n    for (u32 i = 0; i < mwData.GetNumMapAreaDatas(); ++i) {\n      float mag = (universeAreaXf.origin - mwData.GetMapAreaData(i).origin).magnitude();\n      if (mag < minMag) {\n        hexIdx = i;\n        minMag = mag;\n      }\n    }\n\n    const CMapUniverse::CMapUniverseDrawParms parms(universeInterp, x9c_worldIdx, g_GameState->CurrentWorldAssetId(),\n                                                    hexIdx, x1dc_playerFlashPulse, mgr, planeXf, camXf);\n    x8_mapu->Draw(parms, zeus::skZero3f, 0.f, 0.f);\n  }\n\n  if (!IsInMapperState(EAutoMapperState::MapScreenUniverse)) {\n    zeus::CTransform mapXf = planeXf * preXf;\n    if (x24_world == mgr.GetWorld()) {\n      float func = zeus::clamp(0.f, 0.5f * (1.f + std::sin(5.f * CGraphics::GetSecondsMod900() - (M_PIF / 2.f))), 1.f);\n      float scale =\n          std::min(0.6f * g_tweakAutoMapper->GetMaxCamDist() / g_tweakAutoMapper->GetMinCamDist(), objectScale);\n      zeus::CEulerAngles eulers(mgr.GetCameraManager()->GetCurrentCameraTransform(mgr));\n      zeus::CRelAngle angle(eulers.z());\n      angle.makeRel();\n      zeus::CTransform playerXf(zeus::CMatrix3f::RotateZ(angle),\n                                CMapArea::GetAreaPostTranslate(*x24_world, mgr.GetNextAreaId()) +\n                                    mgr.GetPlayer().GetTranslation());\n      CGraphics::SetModelMatrix(mapXf * playerXf * zeus::CTransform::Scale(scale * (0.25f * func + 0.75f)));\n      float colorAlpha;\n      if (x1bc_state != EAutoMapperState::MiniMap && x1c0_nextState != EAutoMapperState::MiniMap) {\n        colorAlpha = 1.f;\n      } else {\n        colorAlpha = xa8_renderStates[0].x34_alphaSurfaceVisited;\n      }\n      colorAlpha *= mapAlpha;\n      zeus::CColor modColor = g_tweakAutoMapper->GetMiniMapSamusModColor();\n      modColor.a() *= colorAlpha;\n      CModelFlags flags(5, 0, 8 | 1, modColor); /* Depth GEqual */\n      // flags.m_extendedShader = EExtendedShader::DepthGEqualNoZWrite;\n      x30_miniMapSamus->Draw(flags);\n    }\n    if (IsInMapperState(EAutoMapperState::MapScreen)) {\n      CAssetId wldMlvl = x24_world->IGetWorldAssetId();\n      const CMapWorld* mw = x24_world->IGetMapWorld();\n      auto locIt = x1f8_hintLocations.cbegin();\n      for (; locIt != x1f8_hintLocations.cend(); ++locIt) {\n        const SAutoMapperHintLocation& loc = *locIt;\n        if (loc.x8_worldId != wldMlvl)\n          continue;\n        const CMapArea* mapa = mw->GetMapArea(loc.xc_areaId);\n        if (!mapa)\n          continue;\n        zeus::CTransform camRot(camXf.buildMatrix3f(), zeus::skZero3f);\n        CGraphics::SetModelMatrix(\n            mapXf * zeus::CTransform::Translate(mapa->GetAreaPostTransform(*x24_world, loc.xc_areaId).origin) *\n            zeus::CTransform::Translate(mapa->GetAreaCenterPoint()) * zeus::CTransform::Scale(objectScale) * camRot);\n        float beaconAlpha = 0.f;\n        if (loc.x0_showBeacon == 1) {\n          beaconAlpha = loc.x4_beaconAlpha;\n        }\n        if (beaconAlpha > 0.f) {\n          CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvModulate);\n          x3c_hintBeacon->Load(GX_TEXMAP0, EClampMode::Repeat);\n          g_Renderer->SetBlendMode_AdditiveAlpha();\n          CGraphics::StreamBegin(ERglPrimitive::TriangleStrip);\n          zeus::CColor color = zeus::skWhite;\n          color.a() = beaconAlpha *\n                      ((x1bc_state != EAutoMapperState::MiniMap && x1c0_nextState != EAutoMapperState::MiniMap)\n                           ? 1.f\n                           : xa8_renderStates[0].x34_alphaSurfaceVisited) *\n                      mapAlpha;\n          CGraphics::StreamColor(color);\n          CGraphics::StreamTexcoord(0.f, 1.f);\n          CGraphics::StreamVertex(zeus::CVector3f(-4.f, -8.f, 8.f));\n          CGraphics::StreamTexcoord(0.f, 0.f);\n          CGraphics::StreamVertex(zeus::CVector3f(-4.f, -8.f, 0.f));\n          CGraphics::StreamTexcoord(1.f, 1.f);\n          CGraphics::StreamVertex(zeus::CVector3f(4.f, -8.f, 8.f));\n          CGraphics::StreamTexcoord(1.f, 0.f);\n          CGraphics::StreamVertex(zeus::CVector3f(4.f, -8.f, 0.f));\n          CGraphics::StreamEnd();\n        }\n      }\n    }\n  }\n\n  g_Renderer->SetDepthReadWrite(false, false);\n  g_Renderer->SetAmbientColor(zeus::skWhite);\n  CGraphics::DisableAllLights();\n\n  if (m_frmeInitialized) {\n    float frmeAlpha = 0.f;\n    if (x1bc_state != EAutoMapperState::MiniMap && x1c0_nextState != EAutoMapperState::MiniMap) {\n      frmeAlpha = 1.f;\n    } else {\n      if (x1c0_nextState != EAutoMapperState::MiniMap) {\n        if (x1c4_interpDur > 0.f)\n          frmeAlpha = x1c8_interpTime / x1c4_interpDur;\n      } else {\n        if (x1c4_interpDur > 0.f)\n          frmeAlpha = x1c8_interpTime / x1c4_interpDur;\n        frmeAlpha = 1.f - frmeAlpha;\n      }\n    }\n    CGraphics::SetDepthRange(DEPTH_NEAR, DEPTH_NEAR);\n    CGuiWidgetDrawParms parms(frmeAlpha, zeus::skZero3f);\n    x28_frmeMapScreen->Draw(parms);\n    CGraphics::SetDepthRange(DEPTH_NEAR, DEPTH_HUD);\n  }\n}\n\nvoid CAutoMapper::TransformRenderStatesWorldToUniverse() {\n  const CMapUniverse::CMapWorldData& mapuWld = x8_mapu->GetMapWorldData(x9c_worldIdx);\n  zeus::CQuaternion rot = zeus::CQuaternion(mapuWld.GetWorldTransform().buildMatrix3f());\n  xa8_renderStates[2].x8_camOrientation *= rot;\n  xa8_renderStates[2].x20_areaPoint = mapuWld.GetWorldTransform() * xa8_renderStates[2].x20_areaPoint;\n  xa8_renderStates[0].x8_camOrientation *= rot;\n  xa8_renderStates[0].x20_areaPoint = mapuWld.GetWorldTransform() * xa8_renderStates[0].x20_areaPoint;\n  xa8_renderStates[1].x8_camOrientation *= rot;\n  xa8_renderStates[1].x20_areaPoint = mapuWld.GetWorldTransform() * xa8_renderStates[1].x20_areaPoint;\n}\n\nvoid CAutoMapper::TransformRenderStatesUniverseToWorld() {\n  const CMapUniverse::CMapWorldData& mapuWld = x8_mapu->GetMapWorldData(x9c_worldIdx);\n  zeus::CTransform inv = mapuWld.GetWorldTransform().inverse();\n  zeus::CQuaternion invRot = zeus::CQuaternion(inv.buildMatrix3f());\n  xa8_renderStates[2].x8_camOrientation *= invRot;\n  xa8_renderStates[2].x20_areaPoint = inv * xa8_renderStates[2].x20_areaPoint;\n  xa8_renderStates[0].x8_camOrientation *= invRot;\n  xa8_renderStates[0].x20_areaPoint = inv * xa8_renderStates[0].x20_areaPoint;\n  xa8_renderStates[1].x8_camOrientation *= invRot;\n  xa8_renderStates[1].x20_areaPoint = inv * xa8_renderStates[1].x20_areaPoint;\n}\n\nvoid CAutoMapper::TransformRenderStateWorldToUniverse(SAutoMapperRenderState& state) {\n  state.x20_areaPoint = x8_mapu->GetMapWorldData(x9c_worldIdx).GetWorldTransform() * xa8_renderStates[1].x20_areaPoint;\n}\n\nvoid CAutoMapper::SetupHintNavigation() {\n  if (!g_GameState->GameOptions().GetIsHintSystemEnabled())\n    return;\n  x1e0_hintSteps.clear();\n  x1f8_hintLocations.clear();\n  CHintOptions& hintOpts = g_GameState->HintOptions();\n  const CHintOptions::SHintState* curHint = hintOpts.GetCurrentDisplayedHint();\n  bool navigating = false;\n  if (curHint && curHint->CanContinue()) {\n    navigating = true;\n    x1e0_hintSteps.emplace_back(SAutoMapperHintStep::ShowBeacon{}, 0.75f);\n    const CGameHintInfo::CGameHint& nextHint = g_MemoryCardSys->GetHints()[hintOpts.GetNextHintIdx()];\n    CAssetId curMlvl = x24_world->IGetWorldAssetId();\n    for (const CGameHintInfo::SHintLocation& loc : nextHint.GetLocations()) {\n      if (loc.x0_mlvlId != curMlvl) {\n        x1e0_hintSteps.emplace_back(SAutoMapperHintStep::SwitchToUniverse{});\n        x1e0_hintSteps.emplace_back(SAutoMapperHintStep::PanToWorld{}, loc.x0_mlvlId);\n        x1e0_hintSteps.emplace_back(SAutoMapperHintStep::SwitchToWorld{}, loc.x0_mlvlId);\n      } else {\n        x1e0_hintSteps.emplace_back(SAutoMapperHintStep::ZoomOut{});\n      }\n      x1e0_hintSteps.emplace_back(SAutoMapperHintStep::PanToArea{}, loc.x8_areaId);\n      x1e0_hintSteps.emplace_back(SAutoMapperHintStep::ZoomIn{});\n      x1e0_hintSteps.emplace_back(SAutoMapperHintStep::ShowBeacon{}, 1.f);\n      x1f8_hintLocations.push_back({0, 0.f, loc.x0_mlvlId, loc.x8_areaId});\n    }\n  }\n\n  for (size_t i = 0; i < hintOpts.GetHintStates().size(); ++i) {\n    const CHintOptions::SHintState& state = hintOpts.GetHintStates()[i];\n    if (navigating && hintOpts.GetNextHintIdx() == i)\n      continue;\n    if (state.x0_state != CHintOptions::EHintState::Displaying)\n      continue;\n    const CGameHintInfo::CGameHint& hint = g_MemoryCardSys->GetHints()[i];\n    for (const CGameHintInfo::SHintLocation& loc : hint.GetLocations())\n      x1f8_hintLocations.push_back({1, 1.f, loc.x0_mlvlId, loc.x8_areaId});\n  }\n}\n\nCAssetId CAutoMapper::GetAreaHintDescriptionString(CAssetId mreaId) {\n  const CHintOptions& hintOpts = g_GameState->HintOptions();\n  for (size_t i = 0; i < hintOpts.GetHintStates().size(); ++i) {\n    const CHintOptions::SHintState& state = hintOpts.GetHintStates()[i];\n    if (state.x0_state != CHintOptions::EHintState::Displaying)\n      continue;\n    const CGameHintInfo::CGameHint& memHint = g_MemoryCardSys->GetHints()[i];\n    for (const CGameHintInfo::SHintLocation& loc : memHint.GetLocations()) {\n      if (loc.x4_mreaId != mreaId)\n        continue;\n      for (const SAutoMapperHintLocation& hintLoc : x1f8_hintLocations) {\n        if (hintLoc.xc_areaId != loc.x8_areaId)\n          continue;\n        if (hintLoc.x4_beaconAlpha > 0.f)\n          return loc.xc_stringId;\n      }\n    }\n  }\n  return {};\n}\n\nvoid CAutoMapper::OnNewInGameGuiState(EInGameGuiState state, CStateManager& mgr) {\n  if (state == EInGameGuiState::MapScreen) {\n    MP1::CMain::EnsureWorldPaksReady();\n    CWorld& wld = *mgr.GetWorld();\n    wld.GetMapWorld()->SetWhichMapAreasLoaded(wld, 0, 9999);\n    SetupHintNavigation();\n    BeginMapperStateTransition(EAutoMapperState::MapScreen, mgr);\n    x28_frmeMapScreen = g_SimplePool->GetObj(\"FRME_MapScreen\");\n    SetResLockState(x210_lstick, true);\n    SetResLockState(x25c_cstick, true);\n    SetResLockState(x2a8_ltrigger, true);\n    SetResLockState(x2bc_rtrigger, true);\n    SetResLockState(x2d0_abutton, true);\n  } else {\n    MP1::CMain::EnsureWorldPakReady(g_GameState->CurrentWorldAssetId());\n    if (x1bc_state == EAutoMapperState::MapScreenUniverse || x24_world == mgr.GetWorld()) {\n      BeginMapperStateTransition(EAutoMapperState::MiniMap, mgr);\n      x328_ = 0;\n    }\n    LeaveMapScreenState();\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/AutoMapper/CAutoMapper.hpp",
    "content": "#pragma once\n\n#include <array>\n#include <list>\n#include <memory>\n#include <optional>\n#include <vector>\n\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/AutoMapper/CMapUniverse.hpp\"\n#include \"Runtime/MP1/CInGameGuiManager.hpp\"\n\n#include <zeus/CQuaternion.hpp>\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector2f.hpp>\n#include <zeus/CVector2i.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CMapWorldInfo;\nclass CStateManager;\nclass IWorld;\n\nstruct CFinalInput;\n\nclass CAutoMapper {\npublic:\n  using EInGameGuiState = MP1::EInGameGuiState;\n  enum class ELoadPhase { LoadResources, LoadUniverse, Done };\n  enum class EAutoMapperState { MiniMap, MapScreen, MapScreenUniverse };\n  struct SAutoMapperRenderState {\n    enum class Ease { None, Linear, Out, In, InOut };\n    using FGetViewportSize = zeus::CVector2i (*)();\n\n    FGetViewportSize m_getViewportSize = nullptr;\n    zeus::CVector2i x0_viewportSize;\n    zeus::CQuaternion x8_camOrientation;\n    float x18_camDist = 0.0f;\n    float x1c_camAngle = 0.0f;\n    zeus::CVector3f x20_areaPoint;\n    float x2c_drawDepth1 = 0.0f;\n    float x30_drawDepth2 = 0.0f;\n    float x34_alphaSurfaceVisited = 0.0f;\n    float x38_alphaOutlineVisited = 0.0f;\n    float x3c_alphaSurfaceUnvisited = 0.0f;\n    float x40_alphaOutlineUnvisited = 0.0f;\n    Ease x44_viewportEase = Ease::None;\n    Ease x48_camEase = Ease::None;\n    Ease x4c_pointEase = Ease::None;\n    Ease x50_depth1Ease = Ease::None;\n    Ease x54_depth2Ease = Ease::None;\n    Ease x58_alphaEase = Ease::None;\n    SAutoMapperRenderState() = default;\n    SAutoMapperRenderState(FGetViewportSize getViewportSize, const zeus::CQuaternion& camOrientation, float camDist,\n                           float camAngle, const zeus::CVector3f& areaPoint, float drawDepth1, float drawDepth2,\n                           float alphaSurfaceVisited, float alphaOutlineVisited, float alphaSurfaceUnvisited,\n                           float alphaOutlineUnvisited)\n    : m_getViewportSize(getViewportSize)\n    , x0_viewportSize(getViewportSize())\n    , x8_camOrientation(camOrientation)\n    , x18_camDist(camDist)\n    , x1c_camAngle(camAngle)\n    , x20_areaPoint(areaPoint)\n    , x2c_drawDepth1(drawDepth1)\n    , x30_drawDepth2(drawDepth2)\n    , x34_alphaSurfaceVisited(alphaSurfaceVisited)\n    , x38_alphaOutlineVisited(alphaOutlineVisited)\n    , x3c_alphaSurfaceUnvisited(alphaSurfaceUnvisited)\n    , x40_alphaOutlineUnvisited(alphaOutlineUnvisited) {}\n\n    static void InterpolateWithClamp(const SAutoMapperRenderState& a, SAutoMapperRenderState& out,\n                                     const SAutoMapperRenderState& b, float t);\n    void ResetInterpolation() {\n      x44_viewportEase = Ease::None;\n      x48_camEase = Ease::None;\n      x4c_pointEase = Ease::None;\n      x50_depth1Ease = Ease::None;\n      x54_depth2Ease = Ease::None;\n      x58_alphaEase = Ease::None;\n    }\n\n    zeus::CVector2i GetViewportSize() const {\n      if (m_getViewportSize)\n        return m_getViewportSize();\n      else\n        return x0_viewportSize;\n    }\n  };\n\n  struct SAutoMapperHintStep {\n    enum class Type { PanToArea, PanToWorld, SwitchToUniverse, SwitchToWorld, ShowBeacon, ZoomIn, ZoomOut };\n    struct PanToArea {};\n    struct PanToWorld {};\n    struct SwitchToUniverse {};\n    struct SwitchToWorld {};\n    struct ShowBeacon {};\n    struct ZoomIn {};\n    struct ZoomOut {};\n\n    Type x0_type;\n    union {\n      CAssetId x4_worldId;\n      TAreaId x4_areaId;\n      float x4_float;\n    };\n    bool x8_processing = false;\n\n    SAutoMapperHintStep(PanToArea, TAreaId areaId) : x0_type(Type::PanToArea), x4_areaId(areaId) {}\n    SAutoMapperHintStep(PanToWorld, CAssetId worldId) : x0_type(Type::PanToWorld), x4_worldId(worldId) {}\n    SAutoMapperHintStep(SwitchToUniverse) : x0_type(Type::SwitchToUniverse), x4_worldId(CAssetId()) {}\n    SAutoMapperHintStep(SwitchToWorld, CAssetId worldId) : x0_type(Type::SwitchToWorld), x4_worldId(worldId) {}\n    SAutoMapperHintStep(ShowBeacon, float val) : x0_type(Type::ShowBeacon), x4_float(val) {}\n    SAutoMapperHintStep(ZoomIn) : x0_type(Type::ZoomIn), x4_worldId(CAssetId()) {}\n    SAutoMapperHintStep(ZoomOut) : x0_type(Type::ZoomOut), x4_worldId(CAssetId()) {}\n  };\n\n  struct SAutoMapperHintLocation {\n    u32 x0_showBeacon;\n    float x4_beaconAlpha;\n    CAssetId x8_worldId;\n    TAreaId xc_areaId;\n  };\n\nprivate:\n  enum class EZoomState { None, In, Out };\n\n  ELoadPhase x4_loadPhase = ELoadPhase::LoadResources;\n  TLockedToken<CMapUniverse> x8_mapu;\n  std::vector<std::unique_ptr<IWorld>> x14_dummyWorlds;\n  IWorld* x24_world;\n  TLockedToken<CGuiFrame> x28_frmeMapScreen; // Used to be ptr\n  bool m_frmeInitialized = false;\n  TLockedToken<CModel> x30_miniMapSamus;\n  TLockedToken<CTexture> x3c_hintBeacon;\n  rstl::reserved_vector<TLockedToken<CTexture>, 5> x48_mapIcons;\n  CAssetId x74_areaHintDescId;\n  TLockedToken<CStringTable> x78_areaHintDesc;\n  CAssetId x88_mapAreaStringId;\n  TLockedToken<CStringTable> x8c_mapAreaString; // Used to be optional\n  u32 x9c_worldIdx = 0;\n  TAreaId xa0_curAreaId;\n  TAreaId xa4_otherAreaId;\n  std::array<SAutoMapperRenderState, 3> xa8_renderStates; // xa8, x104, x160; current, next, prev\n  EAutoMapperState x1bc_state = EAutoMapperState::MiniMap;\n  EAutoMapperState x1c0_nextState = EAutoMapperState::MiniMap;\n  float x1c4_interpDur = 0.f;\n  float x1c8_interpTime = 0.f;\n  CSfxHandle x1cc_panningSfx;\n  CSfxHandle x1d0_rotatingSfx;\n  CSfxHandle x1d4_zoomingSfx;\n  float x1d8_flashTimer = 0.f;\n  float x1dc_playerFlashPulse = 0.f;\n  std::list<SAutoMapperHintStep> x1e0_hintSteps;\n  std::list<SAutoMapperHintLocation> x1f8_hintLocations;\n  rstl::reserved_vector<TLockedToken<CTexture>, 9> x210_lstick;\n  rstl::reserved_vector<TLockedToken<CTexture>, 9> x25c_cstick;\n  rstl::reserved_vector<TLockedToken<CTexture>, 2> x2a8_ltrigger;\n  rstl::reserved_vector<TLockedToken<CTexture>, 2> x2bc_rtrigger;\n  rstl::reserved_vector<TLockedToken<CTexture>, 2> x2d0_abutton;\n  u32 x2e4_lStickPos = 0;\n  u32 x2e8_rStickPos = 0;\n  u32 x2ec_lTriggerPos = 0;\n  u32 x2f0_rTriggerPos = 0;\n  u32 x2f4_aButtonPos = 0;\n  CGuiTextPane* x2f8_textpane_areaname = nullptr;\n  CGuiTextPane* x2fc_textpane_hint = nullptr;\n  CGuiTextPane* x300_textpane_instructions = nullptr;\n  CGuiTextPane* x304_textpane_instructions1 = nullptr;\n  CGuiTextPane* x308_textpane_instructions2 = nullptr;\n  CGuiWidget* x30c_basewidget_leftPane = nullptr;\n  CGuiWidget* x310_basewidget_yButtonPane = nullptr;\n  CGuiWidget* x314_basewidget_bottomPane = nullptr;\n  float x318_leftPanePos = 0.f;\n  float x31c_yButtonPanePos = 0.f;\n  float x320_bottomPanePos = 0.f;\n  EZoomState x324_zoomState = EZoomState::None;\n  u32 x328_ = 0;\n  bool x32c_loadingDummyWorld = false;\n\n  std::optional<zeus::CVector2f> m_lastMouseCoord;\n  zeus::CVector2f m_mouseDelta;\n  SScrollDelta m_lastAccumScroll;\n  SScrollDelta m_mapScroll;\n\n  template <class T>\n  static void SetResLockState(T& list, bool lock) {\n    for (auto& res : list)\n      if (lock)\n        res.Lock();\n      else\n        res.Unlock();\n  }\n  bool NotHintNavigating() const;\n  bool CanLeaveMapScreenInternal(const CStateManager& mgr) const;\n  void LeaveMapScreen(CStateManager& mgr);\n  void SetupMiniMapWorld(CStateManager& mgr);\n  bool HasCurrentMapUniverseWorld() const;\n  bool CheckDummyWorldLoad(CStateManager& mgr);\n  void UpdateHintNavigation(float dt, CStateManager& mgr);\n  static zeus::CVector2i GetMiniMapViewportSize();\n  static zeus::CVector2i GetMapScreenViewportSize();\n  static float GetMapAreaMiniMapDrawDepth() { return 2.f; }\n  float GetMapAreaMaxDrawDepth(const CStateManager& mgr, TAreaId aid) const;\n  static float GetMapAreaMiniMapDrawAlphaSurfaceVisited(const CStateManager& mgr);\n  static float GetMapAreaMiniMapDrawAlphaOutlineVisited(const CStateManager& mgr);\n  static float GetMapAreaMiniMapDrawAlphaSurfaceUnvisited(const CStateManager& mgr);\n  static float GetMapAreaMiniMapDrawAlphaOutlineUnvisited(const CStateManager& mgr);\n  float GetDesiredMiniMapCameraDistance(const CStateManager& mgr) const;\n  static float GetBaseMapScreenCameraMoveSpeed();\n  float GetClampedMapScreenCameraDistance(float value) const;\n  float GetFinalMapScreenCameraMoveSpeed() const;\n  void ProcessMapRotateInput(const CFinalInput& input, const CStateManager& mgr);\n  void ProcessMapZoomInput(const CFinalInput& input, const CStateManager& mgr);\n  void ProcessMapPanInput(const CFinalInput& input, const CStateManager& mgr);\n  void SetShouldPanningSoundBePlaying(bool shouldBePlaying);\n  void SetShouldZoomingSoundBePlaying(bool shouldBePlaying);\n  void SetShouldRotatingSoundBePlaying(bool shouldBePlaying);\n  void TransformRenderStatesWorldToUniverse();\n  void TransformRenderStatesUniverseToWorld();\n  void TransformRenderStateWorldToUniverse(SAutoMapperRenderState&);\n  void SetupHintNavigation();\n  CAssetId GetAreaHintDescriptionString(CAssetId mreaId);\n\npublic:\n  explicit CAutoMapper(CStateManager& stateMgr);\n  bool CheckLoadComplete();\n  bool CanLeaveMapScreen(const CStateManager& mgr) const;\n  float GetMapRotationX() const { return xa8_renderStates[0].x1c_camAngle; }\n  float GetMapRotationZ() const { return xa8_renderStates[0].x8_camOrientation.yaw(); }\n  TAreaId GetFocusAreaIndex() const { return xa0_curAreaId; }\n  CAssetId GetCurrWorldAssetId() const { return x24_world->IGetWorldAssetId(); }\n  void SetCurWorldAssetId(CAssetId mlvlId);\n  void MuteAllLoopedSounds();\n  void UnmuteAllLoopedSounds();\n  void ProcessControllerInput(const CFinalInput& input, CStateManager& mgr);\n  bool IsInPlayerControlState() const {\n    return IsInMapperState(EAutoMapperState::MapScreen) || IsInMapperState(EAutoMapperState::MapScreenUniverse);\n  }\n  void Update(float dt, CStateManager& mgr);\n  void Draw(const CStateManager& mgr, const zeus::CTransform& xf, float alpha);\n  float GetTimeIntoInterpolation() const { return x1c8_interpTime; }\n  void BeginMapperStateTransition(EAutoMapperState state, CStateManager& mgr);\n  void CompleteMapperStateTransition(CStateManager& mgr);\n  void ResetInterpolationTimer(float duration);\n  SAutoMapperRenderState BuildMiniMapWorldRenderState(const CStateManager& stateMgr, const zeus::CQuaternion& rot,\n                                                      TAreaId area) const;\n  SAutoMapperRenderState BuildMapScreenWorldRenderState(const CStateManager& mgr, const zeus::CQuaternion& rot,\n                                                        TAreaId area, bool doingHint) const;\n  SAutoMapperRenderState BuildMapScreenUniverseRenderState(const CStateManager& mgr, const zeus::CQuaternion& rot,\n                                                           TAreaId area) const;\n  void LeaveMapScreenState();\n  void ProcessMapScreenInput(const CFinalInput& input, CStateManager& mgr);\n  zeus::CQuaternion GetMiniMapCameraOrientation(const CStateManager& stateMgr) const;\n  zeus::CVector3f GetAreaPointOfInterest(const CStateManager& mgr, TAreaId aid) const;\n  TAreaId FindClosestVisibleArea(const zeus::CVector3f& point, const zeus::CUnitVector3f& camDir,\n                                 const CStateManager& mgr, const IWorld& wld, const CMapWorldInfo& mwInfo) const;\n  std::pair<int, int> FindClosestVisibleWorld(const zeus::CVector3f& point, const zeus::CUnitVector3f& camDir,\n                                              const CStateManager& mgr) const;\n\n  EAutoMapperState GetNextState() const { return x1c0_nextState; }\n  bool IsInMapperState(EAutoMapperState state) const { return state == x1bc_state && state == x1c0_nextState; }\n  bool IsInMapperStateTransition() const { return x1c0_nextState != x1bc_state; }\n  bool IsRenderStateInterpolating() const { return x1c8_interpTime < x1c4_interpDur; }\n  bool IsStateTransitioning() const { return x1bc_state != x1c0_nextState; }\n  bool IsFullyInMiniMapState() const { return IsInMapperState(EAutoMapperState::MiniMap); }\n  bool IsFullyOutOfMiniMapState() const {\n    return x1bc_state != EAutoMapperState::MiniMap && x1c0_nextState != EAutoMapperState::MiniMap;\n  }\n  void OnNewInGameGuiState(EInGameGuiState state, CStateManager& mgr);\n  float GetInterp() const {\n    if (x1c4_interpDur > 0.f)\n      return x1c8_interpTime / x1c4_interpDur;\n    return 0.f;\n  }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/AutoMapper/CMakeLists.txt",
    "content": "set(AUTOMAPPER_SOURCES\n        CMapUniverse.hpp CMapUniverse.cpp\n        CMapWorldInfo.hpp CMapWorldInfo.cpp\n        CMapWorld.hpp CMapWorld.cpp\n        CMapArea.hpp CMapArea.cpp\n        CMappableObject.hpp CMappableObject.cpp\n        CAutoMapper.hpp CAutoMapper.cpp)\n\nruntime_add_list(AutoMapper AUTOMAPPER_SOURCES)\n"
  },
  {
    "path": "Runtime/AutoMapper/CMapArea.cpp",
    "content": "#include \"Runtime/AutoMapper/CMapArea.hpp\"\n\n#include <array>\n#include <cstring>\n\n#include \"Runtime/AutoMapper/CMappableObject.hpp\"\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n#include \"Runtime/CBasics.hpp\"\n#include \"Runtime/Graphics/CGX.hpp\"\n\nnamespace metaforce {\nconstexpr std::array<zeus::CVector3f, 3> MinesPostTransforms{{\n    {0.f, 0.f, 200.f},\n    {0.f, 0.f, 0.f},\n    {0.f, 0.f, -200.f},\n}};\n\nconstexpr std::array<u8, 42> MinesPostTransformIndices{\n    0, // 00 Transport to Tallon Overworld South\n    0, // 01 Quarry Access\n    0, // 02 Main Quarry\n    0, // 03 Waste Disposal\n    0, // 04 Save Station Mines A\n    0, // 05 Security Access A\n    0, // 06 Ore Processing\n    0, // 07 Mine Security Station\n    0, // 08 Research Access\n    0, // 09 Storage Depot B\n    0, // 10 Elevator Access A\n    0, // 11 Security Access B\n    0, // 12 Storage Depot A\n    0, // 13 Elite Research\n    0, // 14 Elevator A\n    1, // 15 Elite Control Access\n    1, // 16 Elite Control\n    1, // 17 Maintenance Tunnel\n    1, // 18 Ventilation Shaft\n    2, // 19 Phazon Processing Center\n    1, // 20 Omega Research\n    2, // 21 Transport Access\n    2, // 22 Processing Center Access\n    1, // 23 Map Station Mines\n    1, // 24 Dynamo Access\n    2, // 25 Transport to Magmoor Caverns South\n    2, // 26 Elite Quarters\n    1, // 27 Central Dynamo\n    2, // 28 Elite Quarters Access\n    1, // 29 Quarantine Access A\n    1, // 30 Save Station Mines B\n    2, // 31 Metroid Quarantine B\n    1, // 32 Metroid Quarantine A\n    2, // 33 Quarantine Access B\n    2, // 34 Save Station Mines C\n    1, // 35 Elevator Access B\n    2, // 36 Fungal Hall B\n    1, // 37 Elevator B\n    2, // 38 Missile Station Mines\n    2, // 39 Phazon Mining Tunnel\n    2, // 40 Fungal Hall Access\n    2, // 41 Fungal Hall A\n};\n\nCMapArea::CMapArea(CInputStream& in, u32 size)\n: x0_magic(in.ReadLong())\n, x4_version(in.ReadLong())\n, x8_(in.ReadLong())\n, xc_visibilityMode(EVisMode(in.ReadLong()))\n, x10_box(in.Get<zeus::CAABox>())\n, x28_mappableObjCount(in.ReadLong())\n, x2c_vertexCount(in.ReadLong())\n, x30_surfaceCount(in.ReadLong())\n, x34_size(size - 52) {\n  x44_buf.reset(new u8[x34_size]);\n  in.ReadBytes(x44_buf.get(), x34_size);\n  PostConstruct();\n}\n\nvoid CMapArea::PostConstruct() {\n//  OPTICK_EVENT();\n  x38_moStart = x44_buf.get();\n  x3c_vertexStart = x38_moStart + (x28_mappableObjCount * 0x50);\n  x40_surfaceStart = x3c_vertexStart + (x2c_vertexCount * 12);\n\n  m_mappableObjects.reserve(x28_mappableObjCount);\n  for (u32 i = 0, j = 0; i < x28_mappableObjCount; ++i, j += 0x50) {\n    m_mappableObjects.emplace_back(x38_moStart + j).PostConstruct(x44_buf.get());\n  }\n\n  u8* tmp = x3c_vertexStart;\n  m_verts.reserve(x2c_vertexCount);\n  for (u32 i = 0; i < x2c_vertexCount; ++i) {\n    float x;\n    std::memcpy(&x, tmp, sizeof(float));\n    float y;\n    std::memcpy(&y, tmp + 4, sizeof(float));\n    float z;\n    std::memcpy(&z, tmp + 8, sizeof(float));\n\n    m_verts.emplace_back(CBasics::SwapBytes(x), CBasics::SwapBytes(y), CBasics::SwapBytes(z));\n    tmp += 12;\n  }\n\n  m_surfaces.reserve(x30_surfaceCount);\n  for (u32 i = 0, j = 0; i < x30_surfaceCount; ++i, j += 32) {\n    m_surfaces.emplace_back(x40_surfaceStart + j).PostConstruct(x44_buf.get());\n  }\n}\n\nbool CMapArea::GetIsVisibleToAutoMapper(bool worldVis, bool areaVis) const {\n  switch (xc_visibilityMode) {\n  case EVisMode::Always:\n    return true;\n  case EVisMode::MapStationOrVisit:\n    return worldVis || areaVis;\n  case EVisMode::Visit:\n    return areaVis;\n  case EVisMode::Never:\n    return false;\n  default:\n    return true;\n  }\n}\n\nzeus::CTransform CMapArea::GetAreaPostTransform(const IWorld& world, TAreaId aid) const {\n  if (world.IGetWorldAssetId() == 0xB1AC4D65) // Phazon Mines\n  {\n    const zeus::CTransform& areaXf = world.IGetAreaAlways(aid)->IGetTM();\n    const zeus::CVector3f& postVec = MinesPostTransforms[MinesPostTransformIndices[aid]];\n    return zeus::CTransform::Translate(postVec) * areaXf;\n  } else {\n    return world.IGetAreaAlways(aid)->IGetTM();\n  }\n}\n\nconst zeus::CVector3f& CMapArea::GetAreaPostTranslate(const IWorld& world, TAreaId aid) {\n  if (world.IGetWorldAssetId() == 0xB1AC4D65) // Phazon Mines\n    return MinesPostTransforms[MinesPostTransformIndices[aid]];\n  else\n    return zeus::skZero3f;\n}\n\nCMapArea::CMapAreaSurface::CMapAreaSurface(const void* surfBuf) {\n  CMemoryInStream r(surfBuf, 32, CMemoryInStream::EOwnerShip::NotOwned);\n  x0_normal = r.Get<zeus::CVector3f>();\n  xc_centroid = r.Get<zeus::CVector3f>();\n  x18_surfOffset = reinterpret_cast<const u32*>(static_cast<uintptr_t>(r.ReadLong()));\n  x1c_outlineOffset = reinterpret_cast<const u32*>(static_cast<uintptr_t>(r.ReadLong()));\n}\n\nvoid CMapArea::CMapAreaSurface::PostConstruct(const void* buf) {\n  x18_surfOffset =\n      reinterpret_cast<const u32*>(static_cast<const u8*>(buf) + reinterpret_cast<uintptr_t>(x18_surfOffset));\n  x1c_outlineOffset =\n      reinterpret_cast<const u32*>(static_cast<const u8*>(buf) + reinterpret_cast<uintptr_t>(x1c_outlineOffset));\n}\n\nvoid CMapArea::CMapAreaSurface::Draw(TConstVectorRef verts, const CColor& surfColor, const CColor& lineColor,\n                                     float lineWidth) const {\n  bool hasSurfAlpha = surfColor.a() > 0.0f;\n  bool hasLineAlpha = lineColor.a() > 0.0f;\n  u32 numSurfaces = CBasics::SwapBytes(*x18_surfOffset);\n  u32 numOutlines = CBasics::SwapBytes(*x1c_outlineOffset);\n  if (!verts.empty()) {\n    CGX::SetArray(GX_VA_POS, verts);\n  }\n  if (hasSurfAlpha) {\n    CGX::SetTevKColor(GX_KCOLOR0, surfColor);\n    const u32* surface = &x18_surfOffset[1];\n    for (u32 i = 0; i < numSurfaces; ++i) {\n      GXPrimitive primType = static_cast<GXPrimitive>(CBasics::SwapBytes(*surface++));\n      u32 numVertices = CBasics::SwapBytes(*surface++);\n      const u8* data = reinterpret_cast<const u8*>(surface);\n      surface += ((numVertices + 3) & ~3) / 4;\n\n      CGX::Begin(primType, GX_VTXFMT0, numVertices);\n      for (u32 v = 0; v < numVertices; ++v) {\n        GXPosition1x8(data[v]);\n      }\n      CGX::End();\n    }\n  }\n  if (hasLineAlpha) {\n    bool thickLine = lineWidth > 1.f;\n    for (u32 j = 0; j < (thickLine ? 1 : 0) + 1; ++j) {\n      const u32* outline = &x1c_outlineOffset[1];\n\n      if (thickLine) {\n        CGraphics::SetLineWidth(lineWidth - j, ERglTexOffset::One);\n      }\n      CColor clr = lineColor;\n      if (thickLine) {\n        clr.a() *= 0.5f;\n      }\n      CGX::SetTevKColor(GX_KCOLOR0, clr);\n\n      for (u32 i = 0; i < numOutlines; ++i) {\n        u32 numVertices = CBasics::SwapBytes(*outline++);\n        const u8* data = reinterpret_cast<const u8*>(outline);\n        outline += ((numVertices + 3) & ~3) / 4;\n\n        CGX::Begin(GX_LINESTRIP, GX_VTXFMT0, numVertices);\n        for (u32 v = 0; v < numVertices; ++v) {\n          GXPosition1x8(data[v]);\n        }\n        CGX::End();\n      }\n    }\n  }\n}\n\nvoid CMapArea::CMapAreaSurface::SetupGXMaterial() {\n  const GXVtxDescList list[2] = {\n      {GX_VA_POS, GX_INDEX8},\n      {GX_VA_NULL, GX_NONE},\n  };\n  CGX::SetVtxDescv(list);\n  CGX::SetNumChans(1);\n  CGX::SetNumTexGens(0);\n  CGX::SetNumTevStages(1);\n  CGX::SetChanCtrl(CGX::EChannelId::Channel0, false, GX_SRC_REG, GX_SRC_VTX, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE);\n  CGX::SetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_KONST);\n  CGX::SetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_KONST);\n  CGX::SetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, true, GX_TEVPREV);\n  CGX::SetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, true, GX_TEVPREV);\n  CGX::SetTevKColorSel(GX_TEVSTAGE0, GX_TEV_KCSEL_K0);\n  CGX::SetTevKAlphaSel(GX_TEVSTAGE0, GX_TEV_KASEL_K0_A);\n}\n\nCFactoryFnReturn FMapAreaFactory(const SObjectTag& objTag, CInputStream& in, const CVParamTransfer&,\n                                 CObjectReference*) {\n  u32 size = g_ResFactory->ResourceSize(objTag);\n  return TToken<CMapArea>::GetIObjObjectFor(std::make_unique<CMapArea>(in, size));\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/AutoMapper/CMapArea.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <vector>\n\n#include \"Runtime/Graphics/CCubeModel.hpp\"\n#include \"Runtime/AutoMapper/CMappableObject.hpp\"\n#include \"Runtime/CResFactory.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nusing CColor = zeus::CColor;\nusing CVector3f = zeus::CVector3f;\n\nclass IWorld;\nclass CMapArea {\npublic:\n  class CMapAreaSurface {\n    friend class CMapArea;\n    CVector3f x0_normal;\n    CVector3f xc_centroid;\n    const u32* x18_surfOffset;\n    const u32* x1c_outlineOffset;\n\n  public:\n    explicit CMapAreaSurface(const void* surfBuf);\n\n    void PostConstruct(const void* buf);\n    void Draw(TConstVectorRef verts, const CColor& surfColor, const CColor& lineColor, float lineWidth) const;\n\n    static void SetupGXMaterial();\n\n    const CVector3f& GetNormal() const { return x0_normal; }\n    const CVector3f& GetCenterPosition() const { return xc_centroid; }\n  };\n  enum class EVisMode { Always, MapStationOrVisit, Visit, Never };\n\nprivate:\n  u32 x0_magic;\n  u32 x4_version;\n  u32 x8_;\n  EVisMode xc_visibilityMode;\n  zeus::CAABox x10_box;\n  u32 x28_mappableObjCount;\n  u32 x2c_vertexCount;\n  u32 x30_surfaceCount;\n  u32 x34_size;\n  u8* x38_moStart;\n  std::vector<CMappableObject> m_mappableObjects;\n  u8* x3c_vertexStart;\n  std::vector<aurora::Vec3<float>> m_verts;\n  u8* x40_surfaceStart;\n  std::vector<CMapAreaSurface> m_surfaces;\n  std::unique_ptr<u8[]> x44_buf;\n\npublic:\n  explicit CMapArea(CInputStream& in, u32 size);\n  void PostConstruct();\n  bool GetIsVisibleToAutoMapper(bool worldVis, bool areaVis) const;\n  zeus::CVector3f GetAreaCenterPoint() const { return x10_box.center(); }\n  const zeus::CAABox& GetBoundingBox() const { return x10_box; }\n  CMappableObject& GetMappableObject(int idx) { return m_mappableObjects[idx]; }\n  const CMappableObject& GetMappableObject(int idx) const { return m_mappableObjects[idx]; }\n  CMapAreaSurface& GetSurface(int idx) { return m_surfaces[idx]; }\n  const CMapAreaSurface& GetSurface(int idx) const { return m_surfaces[idx]; }\n  u32 GetNumMappableObjects() const { return m_mappableObjects.size(); }\n  u32 GetNumSurfaces() const { return m_surfaces.size(); }\n  zeus::CTransform GetAreaPostTransform(const IWorld& world, TAreaId aid) const;\n  static const zeus::CVector3f& GetAreaPostTranslate(const IWorld& world, TAreaId aid);\n  TConstVectorRef GetVertices() const { return m_verts; }\n};\n\nCFactoryFnReturn FMapAreaFactory(const SObjectTag& objTag, CInputStream& in, const CVParamTransfer&, CObjectReference*);\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/AutoMapper/CMapUniverse.cpp",
    "content": "#include \"Runtime/AutoMapper/CMapUniverse.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CGameState.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n\nnamespace metaforce {\n\nCMapUniverse::CMapUniverse(CInputStream& in, u32 version) : x0_hexagonId(in.Get<CAssetId>()) {\n  x4_hexagonToken = g_SimplePool->GetObj({FOURCC('MAPA'), x0_hexagonId});\n  u32 count = in.ReadLong();\n  x10_worldDatas.reserve(count);\n  for (u32 i = 0; i < count; ++i)\n    x10_worldDatas.emplace_back(in, version);\n}\n\nCMapUniverse::CMapWorldData::CMapWorldData(CInputStream& in, u32 version)\n: x0_label(in.Get<std::string>()), x10_worldAssetId(in) {\n  x14_transform = in.Get<zeus::CTransform>();\n  const u32 worldCount = in.ReadLong();\n  x44_hexagonXfs.reserve(worldCount);\n  for (u32 i = 0; i < worldCount; ++i) {\n    x44_hexagonXfs.emplace_back() = in.Get<zeus::CTransform>();\n  }\n\n  if (version != 0)\n    x54_surfColorSelected = in.Get<zeus::CColor>();\n  else\n    x54_surfColorSelected.fromRGBA32(255 | (u32(x10_worldAssetId.Value()) & 0xFFFFFF00));\n\n  x58_outlineColorSelected = zeus::CColor::lerp(zeus::skWhite, x54_surfColorSelected, 0.5f);\n  x5c_surfColorUnselected = zeus::CColor::lerp(zeus::skBlack, x54_surfColorSelected, 0.5f);\n  x60_outlineColorUnselected = zeus::CColor::lerp(zeus::skWhite, x5c_surfColorUnselected, 0.5f);\n\n  for (const zeus::CTransform& xf : x44_hexagonXfs)\n    x64_centerPoint += xf.origin;\n\n  x64_centerPoint *= zeus::CVector3f(1.0f / float(x44_hexagonXfs.size()));\n}\n\nvoid CMapUniverse::Draw(const CMapUniverseDrawParms& parms, const zeus::CVector3f&, float, float) {\n  if (!x4_hexagonToken.IsLoaded()) {\n    return;\n  }\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CMapUniverse::Draw\", zeus::skBlue);\n\n  u32 totalSurfaceCount = 0;\n  for (const CMapWorldData& data : x10_worldDatas)\n    totalSurfaceCount += data.GetNumMapAreaDatas() * x4_hexagonToken->GetNumSurfaces();\n\n  std::vector<CMapObjectSortInfo> sortInfos;\n  sortInfos.reserve(totalSurfaceCount);\n\n  for (size_t w = 0; w < x10_worldDatas.size(); ++w) {\n    const CMapWorldData& data = x10_worldDatas[w];\n    const CMapWorldInfo& mwInfo = *g_GameState->StateForWorld(data.GetWorldAssetId()).MapWorldInfo();\n    if (!mwInfo.IsAnythingSet())\n      continue;\n    zeus::CColor surfColor, outlineColor;\n    if (s32(w) == parms.GetFocusWorldIndex()) {\n      surfColor = data.GetSurfaceColorSelected();\n      surfColor.a() *= parms.GetAlpha();\n      outlineColor = data.GetOutlineColorSelected();\n      outlineColor.a() *= parms.GetAlpha();\n    } else {\n      surfColor = data.GetSurfaceColorUnselected();\n      surfColor.a() *= parms.GetAlpha();\n      outlineColor = data.GetSurfaceColorUnselected();\n      outlineColor.a() *= parms.GetAlpha();\n    }\n\n    for (u32 h = 0; h < data.GetNumMapAreaDatas(); ++h) {\n      zeus::CTransform hexXf = parms.GetCameraTransform().inverse() * data.GetMapAreaData(h);\n      for (u32 s = 0; s < x4_hexagonToken->GetNumSurfaces(); ++s) {\n        const CMapArea::CMapAreaSurface& surf = x4_hexagonToken->GetSurface(s);\n        zeus::CVector3f centerPos = hexXf * surf.GetCenterPosition();\n        sortInfos.emplace_back(centerPos.y(), w, h, s, surfColor, outlineColor);\n      }\n    }\n  }\n\n  if (!sortInfos.empty()) {\n    std::sort(sortInfos.begin(), sortInfos.end(), [](const CMapObjectSortInfo& a, const CMapObjectSortInfo& b) {\n      return a.GetZDistance() > b.GetZDistance();\n    });\n    CMapArea::CMapAreaSurface::SetupGXMaterial();\n\n    int lastWldIdx = -1;\n    int lastHexIdx = -1;\n    for (const CMapObjectSortInfo& info : sortInfos) {\n      const CMapWorldData& mwData = x10_worldDatas[info.GetWorldIndex()];\n      zeus::CColor surfColor = info.GetSurfaceColor();\n      zeus::CColor outlineColor = info.GetOutlineColor();\n      if (parms.GetWorldAssetId() == mwData.GetWorldAssetId() && parms.GetClosestArea() == info.GetAreaIndex()) {\n        surfColor = zeus::CColor::lerp(g_tweakAutoMapper->GetSurfaceSelectVisitedColor(),\n                                       g_tweakAutoMapper->GetAreaFlashPulseColor(), parms.GetFlashPulse());\n        surfColor.a() = info.GetSurfaceColor().a();\n        outlineColor = zeus::CColor::lerp(g_tweakAutoMapper->GetOutlineSelectVisitedColor(),\n                                          g_tweakAutoMapper->GetAreaFlashPulseColor(), parms.GetFlashPulse());\n        outlineColor.a() = info.GetOutlineColor().a();\n      }\n\n      zeus::CTransform hexXf = mwData.GetMapAreaData(info.GetAreaIndex());\n      hexXf.orthonormalize();\n      CMapArea::CMapAreaSurface& surf = x4_hexagonToken->GetSurface(info.GetObjectIndex());\n      zeus::CColor color(std::max(0.f, (-parms.GetCameraTransform().basis[1]).dot(hexXf.rotate(surf.GetNormal()))) *\n                             g_tweakAutoMapper->GetMapSurfaceNormColorLinear() +\n                         g_tweakAutoMapper->GetMapSurfaceNormColorConstant());\n      surfColor *= color;\n\n      if (info.GetAreaIndex() != lastHexIdx || info.GetWorldIndex() != lastWldIdx)\n        CGraphics::SetModelMatrix(parms.GetPaneProjectionTransform() * mwData.GetMapAreaData(info.GetAreaIndex()));\n\n      surf.Draw(x4_hexagonToken->GetVertices(), surfColor, outlineColor, 2.f);\n    }\n  }\n}\n\nCFactoryFnReturn FMapUniverseFactory(const SObjectTag&, CInputStream& in, const CVParamTransfer&, CObjectReference*) {\n  in.ReadLong();\n  u32 version = in.ReadLong();\n\n  return TToken<CMapUniverse>::GetIObjObjectFor(std::make_unique<CMapUniverse>(in, version));\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/AutoMapper/CMapUniverse.hpp",
    "content": "#pragma once\n\n#include <string>\n#include <vector>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/IFactory.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/AutoMapper/CMapArea.hpp\"\n\n#include <zeus/CColor.hpp>\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CStateManager;\nclass CMapUniverse {\npublic:\n  class CMapUniverseDrawParms {\n    float x0_alpha;\n    int x4_wldIdx;\n    CAssetId x8_wldRes;\n    int xc_closestHex;\n    float x10_flashPulse;\n    // const CStateManager& x14_mgr;\n    const zeus::CTransform& x18_model;\n    const zeus::CTransform& x1c_view;\n\n  public:\n    CMapUniverseDrawParms(float alpha, int wldIdx, CAssetId wldRes, int closestHex, float flashPulse,\n                          const CStateManager& mgr, const zeus::CTransform& model, const zeus::CTransform& view)\n    : x0_alpha(alpha)\n    , x4_wldIdx(wldIdx)\n    , x8_wldRes(wldRes)\n    , xc_closestHex(closestHex)\n    , x10_flashPulse(flashPulse)\n    ,\n    // x14_mgr(mgr),\n    x18_model(model)\n    , x1c_view(view) {}\n    int GetFocusWorldIndex() const { return x4_wldIdx; }\n    const zeus::CTransform& GetCameraTransform() const { return x1c_view; }\n    const zeus::CTransform& GetPaneProjectionTransform() const { return x18_model; }\n    float GetAlpha() const { return x0_alpha; }\n    CAssetId GetWorldAssetId() const { return x8_wldRes; }\n    int GetClosestArea() const { return xc_closestHex; }\n    float GetFlashPulse() const { return x10_flashPulse; }\n  };\n\n  class CMapObjectSortInfo {\n    float x0_zDist;\n    int x4_wldIdx;\n    int x8_hexIdx;\n    int xc_surfIdx;\n    zeus::CColor x10_surfColor;\n    zeus::CColor x14_outlineColor;\n\n  public:\n    CMapObjectSortInfo(float zDist, int wldIdx, int hexIdx, int surfIdx, const zeus::CColor& surf,\n                       const zeus::CColor& outline)\n    : x0_zDist(zDist)\n    , x4_wldIdx(wldIdx)\n    , x8_hexIdx(hexIdx)\n    , xc_surfIdx(surfIdx)\n    , x10_surfColor(surf)\n    , x14_outlineColor(outline) {}\n    const zeus::CColor& GetOutlineColor() const { return x14_outlineColor; }\n    const zeus::CColor& GetSurfaceColor() const { return x10_surfColor; }\n    int GetObjectIndex() const { return xc_surfIdx; }\n    int GetAreaIndex() const { return x8_hexIdx; }\n    int GetWorldIndex() const { return x4_wldIdx; }\n    float GetZDistance() const { return x0_zDist; }\n  };\n\n  class CMapWorldData {\n    std::string x0_label;\n    CAssetId x10_worldAssetId;\n    zeus::CTransform x14_transform;\n    std::vector<zeus::CTransform> x44_hexagonXfs;\n    zeus::CColor x54_surfColorSelected;\n    zeus::CColor x58_outlineColorSelected = zeus::CColor(1.0f, 0.0f, 1.0f);\n    zeus::CColor x5c_surfColorUnselected = zeus::CColor(1.0f, 0.0f, 1.0f);\n    zeus::CColor x60_outlineColorUnselected = zeus::CColor(1.0f, 0.0f, 1.0f);\n    zeus::CVector3f x64_centerPoint = zeus::skZero3f;\n\n  public:\n    explicit CMapWorldData(CInputStream& in, u32 version);\n    CAssetId GetWorldAssetId() const { return x10_worldAssetId; }\n    const zeus::CVector3f& GetWorldCenterPoint() const { return x64_centerPoint; }\n    std::string_view GetWorldLabel() const { return x0_label; }\n    const zeus::CTransform& GetWorldTransform() const { return x14_transform; }\n    const zeus::CTransform& GetMapAreaData(s32 idx) const { return x44_hexagonXfs[idx]; }\n    u32 GetNumMapAreaDatas() const { return x44_hexagonXfs.size(); }\n    const zeus::CColor& GetOutlineColorUnselected() const { return x60_outlineColorUnselected; }\n    const zeus::CColor& GetOutlineColorSelected() const { return x58_outlineColorSelected; }\n    const zeus::CColor& GetSurfaceColorUnselected() const { return x5c_surfColorUnselected; }\n    const zeus::CColor& GetSurfaceColorSelected() const { return x54_surfColorSelected; }\n  };\n\nprivate:\n  CAssetId x0_hexagonId;\n  TLockedToken<CMapArea> x4_hexagonToken;\n  std::vector<CMapWorldData> x10_worldDatas;\n  zeus::CVector3f x20_universeCenter = zeus::skZero3f;\n  float x2c_universeRadius = 1600.f;\n\npublic:\n  explicit CMapUniverse(CInputStream&, u32);\n  const CMapWorldData& GetMapWorldData(s32 idx) const { return x10_worldDatas[idx]; }\n  const CMapWorldData& GetMapWorldDataByWorldId(CAssetId id) const {\n    for (const CMapWorldData& data : x10_worldDatas)\n      if (data.GetWorldAssetId() == id)\n        return data;\n    return x10_worldDatas.front();\n  }\n  u32 GetNumMapWorldDatas() const { return x10_worldDatas.size(); }\n  float GetMapUniverseRadius() const { return x2c_universeRadius; }\n  const zeus::CVector3f& GetMapUniverseCenterPoint() const { return x20_universeCenter; }\n  void Draw(const CMapUniverseDrawParms&, const zeus::CVector3f&, float, float);\n  std::vector<CMapWorldData>::const_iterator begin() const { return x10_worldDatas.cbegin(); }\n  std::vector<CMapWorldData>::const_iterator end() const { return x10_worldDatas.cend(); }\n};\n\nCFactoryFnReturn FMapUniverseFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& vparms,\n                                     CObjectReference*);\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/AutoMapper/CMapWorld.cpp",
    "content": "#include \"Runtime/AutoMapper/CMapWorld.hpp\"\n\n#include <algorithm>\n#include <array>\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/AutoMapper/CMapWorldInfo.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n\nnamespace metaforce {\nnamespace {\nstruct Support {\n  int x0_;\n  std::array<int, 3> x4_;\n};\n\nstruct Circle2 {\n  zeus::CVector2f x0_point;\n  float x8_radiusSq;\n};\n\nstruct Circle {\n  zeus::CVector2f x0_point;\n  float x8_radius;\n  Circle(const Circle2& circ2) : x0_point(circ2.x0_point), x8_radius(std::sqrt(circ2.x8_radiusSq)) {}\n};\n\nCircle2 ExactCircle1(const zeus::CVector2f* a) {\n  return {\n      .x0_point = *a,\n      .x8_radiusSq = 0.f,\n  };\n}\n\nCircle2 ExactCircle2(const zeus::CVector2f* a, const zeus::CVector2f* b) {\n  return {\n      .x0_point = 0.5f * (*a + *b),\n      .x8_radiusSq = (*b - *a).magSquared() * 0.25f,\n  };\n}\n\nCircle2 ExactCircle3(const zeus::CVector2f* a, const zeus::CVector2f* b, const zeus::CVector2f* c) {\n  const zeus::CVector2f d1 = *b - *a;\n  const zeus::CVector2f d2 = *c - *a;\n  const float cross = d1.cross(d2);\n  const zeus::CVector2f magVec(d1.magSquared() * 0.5f, d2.magSquared() * 0.5f);\n\n  if (std::fabs(cross) > 0.01f) {\n    const zeus::CVector2f tmp((d2.y() * magVec.x() - d1.y() * magVec.y()) / cross,\n                              (d1.x() * magVec.y() - d2.x() * magVec.x()) / cross);\n\n    return {\n        .x0_point = *a + tmp,\n        .x8_radiusSq = tmp.magSquared(),\n    };\n  } else {\n    return {\n        .x0_point = zeus::skZero2f,\n        .x8_radiusSq = FLT_MAX,\n    };\n  }\n}\n\nbool PointInsideCircle(const zeus::CVector2f& point, const Circle2& circ, float& intersect) {\n  intersect = (point - circ.x0_point).magSquared() - circ.x8_radiusSq;\n  return intersect <= 0.f;\n}\n\nCircle2 UpdateSupport1(int idx, const zeus::CVector2f** list, Support& support) {\n  const Circle2 ret = ExactCircle2(list[support.x4_[0]], list[idx]);\n  support.x0_ = 2;\n  support.x4_[1] = idx;\n  return ret;\n}\n\nCircle2 UpdateSupport2(int idx, const zeus::CVector2f** list, Support& support) {\n  std::array<Circle2, 3> circs{};\n  float intersect;\n  int circIdx = -1;\n  float minRad = FLT_MAX;\n\n  circs[0] = ExactCircle2(list[support.x4_[0]], list[idx]);\n  if (PointInsideCircle(*list[support.x4_[1]], circs[0], intersect)) {\n    minRad = circs[0].x8_radiusSq;\n    circIdx = 0;\n  }\n\n  circs[1] = ExactCircle2(list[support.x4_[1]], list[idx]);\n  if (circs[1].x8_radiusSq < minRad && PointInsideCircle(*list[support.x4_[0]], circs[1], intersect)) {\n    circIdx = 1;\n  }\n\n  Circle2 ret;\n  if (circIdx != -1) {\n    ret = circs[circIdx];\n    support.x4_[1 - circIdx] = idx;\n  } else {\n    ret = ExactCircle3(list[support.x4_[0]], list[support.x4_[1]], list[idx]);\n    support.x0_ = 3;\n    support.x4_[2] = idx;\n  }\n  return ret;\n}\n\nCircle2 UpdateSupport3(int idx, const zeus::CVector2f** list, Support& support) {\n  std::array<Circle2, 6> circs{};\n  float intersect;\n  int circIdxA = -1;\n  int circIdxB = -1;\n  float minRadA = FLT_MAX;\n  float minRadB = FLT_MAX;\n\n  circs[0] = ExactCircle2(list[support.x4_[0]], list[idx]);\n  if (PointInsideCircle(*list[support.x4_[1]], circs[0], intersect)) {\n    if (PointInsideCircle(*list[support.x4_[2]], circs[0], intersect)) {\n      minRadA = circs[0].x8_radiusSq;\n      circIdxA = 0;\n    } else {\n      minRadB = intersect;\n      circIdxB = 0;\n    }\n  } else {\n    minRadB = intersect;\n    circIdxB = 0;\n  }\n\n  circs[1] = ExactCircle2(list[support.x4_[1]], list[idx]);\n  if (circs[1].x8_radiusSq < minRadA) {\n    if (PointInsideCircle(*list[support.x4_[0]], circs[1], intersect)) {\n      if (PointInsideCircle(*list[support.x4_[2]], circs[1], intersect)) {\n        minRadA = circs[1].x8_radiusSq;\n        circIdxA = 1;\n      } else if (intersect < minRadB) {\n        minRadB = intersect;\n        circIdxB = 1;\n      }\n    } else if (intersect < minRadB) {\n      minRadB = intersect;\n      circIdxB = 1;\n    }\n  }\n\n  circs[2] = ExactCircle2(list[support.x4_[2]], list[idx]);\n  if (circs[2].x8_radiusSq < minRadA) {\n    if (PointInsideCircle(*list[support.x4_[0]], circs[2], intersect)) {\n      if (PointInsideCircle(*list[support.x4_[1]], circs[2], intersect)) {\n        minRadA = circs[2].x8_radiusSq;\n        circIdxA = 2;\n      } else if (intersect < minRadB) {\n        minRadB = intersect;\n        circIdxB = 2;\n      }\n    } else if (intersect < minRadB) {\n      minRadB = intersect;\n      circIdxB = 2;\n    }\n  }\n\n  circs[3] = ExactCircle3(list[support.x4_[0]], list[support.x4_[1]], list[idx]);\n  if (circs[3].x8_radiusSq < minRadA) {\n    if (PointInsideCircle(*list[support.x4_[2]], circs[3], intersect)) {\n      minRadA = circs[3].x8_radiusSq;\n      circIdxA = 3;\n    } else if (intersect < minRadB) {\n      minRadB = intersect;\n      circIdxB = 3;\n    }\n  }\n\n  circs[4] = ExactCircle3(list[support.x4_[0]], list[support.x4_[2]], list[idx]);\n  if (circs[4].x8_radiusSq < minRadA) {\n    if (PointInsideCircle(*list[support.x4_[1]], circs[4], intersect)) {\n      minRadA = circs[4].x8_radiusSq;\n      circIdxA = 4;\n    } else if (intersect < minRadB) {\n      minRadB = intersect;\n      circIdxB = 4;\n    }\n  }\n\n  circs[5] = ExactCircle3(list[support.x4_[1]], list[support.x4_[2]], list[idx]);\n  if (circs[5].x8_radiusSq < minRadA) {\n    if (PointInsideCircle(*list[support.x4_[0]], circs[5], intersect)) {\n      circIdxA = 5;\n    } else if (intersect < minRadB) {\n      circIdxB = 5;\n    }\n  }\n\n  if (circIdxA == -1)\n    circIdxA = circIdxB;\n\n  switch (circIdxA) {\n  case 0:\n    support.x0_ = 2;\n    support.x4_[1] = idx;\n    break;\n  case 1:\n    support.x0_ = 2;\n    support.x4_[0] = idx;\n    break;\n  case 2:\n    support.x0_ = 2;\n    support.x4_[0] = support.x4_[2];\n    support.x4_[1] = idx;\n    break;\n  case 3:\n    support.x4_[2] = idx;\n    break;\n  case 4:\n    support.x4_[1] = idx;\n    break;\n  case 5:\n    support.x4_[0] = idx;\n    break;\n  default:\n    break;\n  }\n\n  return circs[circIdxA];\n}\n\nusing FSupport = Circle2 (*)(int idx, const zeus::CVector2f** list, Support& support);\nconstexpr std::array<FSupport, 4> SupportFuncs{\n    nullptr,\n    UpdateSupport1,\n    UpdateSupport2,\n    UpdateSupport3,\n};\n\nCircle MinCircle(const std::vector<zeus::CVector2f>& coords) {\n  Circle2 ret = {};\n  if (coords.size() >= 1) {\n    std::unique_ptr<const zeus::CVector2f*[]> randArr(new const zeus::CVector2f*[coords.size()]);\n    for (size_t i = 0; i < coords.size(); ++i)\n      randArr[i] = &coords[i];\n    for (int i = coords.size() - 1; i >= 0; --i) {\n      int shuf = rand() % (i + 1);\n      if (shuf != i)\n        std::swap(randArr[i], randArr[shuf]);\n    }\n    ret = ExactCircle1(randArr[0]);\n\n    Support support = {};\n    support.x0_ = 1;\n    for (size_t i = 1; i < coords.size();) {\n      bool broke = false;\n      for (int j = 0; j < support.x0_; ++j) {\n        if ((*randArr[i] - *randArr[support.x4_[j]]).magSquared() < 0.01f) {\n          broke = true;\n          break;\n        }\n      }\n      float intersect;\n      if (!broke && !PointInsideCircle(*randArr[i], ret, intersect)) {\n        Circle2 circ = SupportFuncs[support.x0_](i, randArr.get(), support);\n        if (circ.x8_radiusSq > ret.x8_radiusSq) {\n          i = 0;\n          ret = circ;\n          continue;\n        }\n      }\n      ++i;\n    }\n  }\n  return ret;\n}\n} // Anonymous namespace\n\nCMapWorld::CMapAreaData::CMapAreaData(CAssetId areaRes, EMapAreaList list, CMapAreaData* next)\n: x0_area(g_SimplePool->GetObj(SObjectTag{FOURCC('MAPA'), areaRes})), x10_list(list), x14_next(next) {}\n\nCMapWorld::CMapWorld(CInputStream& in) {\n  x10_listHeads.resize(3);\n  in.ReadLong();\n  in.ReadLong();\n  u32 areaCount = in.ReadLong();\n  x0_areas.reserve(areaCount);\n  x20_traversed.resize(areaCount);\n  for (u32 i = 0; i < areaCount; ++i) {\n    CAssetId mapaId = in.Get<CAssetId>();\n    x0_areas.emplace_back(mapaId, EMapAreaList::Unloaded, x0_areas.empty() ? nullptr : &x0_areas.back());\n  }\n  x10_listHeads[2] = &x0_areas.back();\n}\n\nbool CMapWorld::IsMapAreaInBFSInfoVector(const CMapWorld::CMapAreaData* area,\n                                         const std::vector<CMapWorld::CMapAreaBFSInfo>& vec) const {\n  for (const CMapWorld::CMapAreaBFSInfo& bfs : vec) {\n    if (&x0_areas[bfs.GetAreaIndex()] == area)\n      return true;\n  }\n  return false;\n}\n\nvoid CMapWorld::SetWhichMapAreasLoaded(const IWorld& wld, int start, int count) {\n  ClearTraversedFlags();\n\n  std::vector<CMapAreaBFSInfo> bfsInfos;\n  bfsInfos.reserve(x0_areas.size());\n  DoBFS(wld, start, count, 9999.f, 9999.f, false, bfsInfos);\n\n  for (int i = 0; i < 2; ++i) {\n    for (CMapAreaData* data = x10_listHeads[i]; data;) {\n      CMapAreaData* nextData = data->GetNextMapAreaData();\n      if (!IsMapAreaInBFSInfoVector(data, bfsInfos)) {\n        data->Unlock();\n        MoveMapAreaToList(data, EMapAreaList::Unloaded);\n      }\n      data = nextData;\n    }\n  }\n\n  for (CMapAreaBFSInfo& bfs : bfsInfos) {\n    CMapAreaData& data = x0_areas[bfs.GetAreaIndex()];\n    data.Lock();\n    if (data.GetContainingList() == EMapAreaList::Unloaded)\n      MoveMapAreaToList(&data, EMapAreaList::Loading);\n  }\n}\n\nbool CMapWorld::IsMapAreasStreaming() {\n  bool ret = false;\n  CMapAreaData* data = x10_listHeads[1];\n  while (data != nullptr) {\n    if (data->IsLoaded()) {\n      CMapAreaData* next = data->GetNextMapAreaData();\n      MoveMapAreaToList(data, EMapAreaList::Loaded);\n      data = next;\n    } else {\n      data = data->GetNextMapAreaData();\n      ret = true;\n    }\n  }\n  return ret;\n}\n\nvoid CMapWorld::MoveMapAreaToList(CMapWorld::CMapAreaData* data, CMapWorld::EMapAreaList list) {\n  CMapAreaData* last = nullptr;\n  for (CMapAreaData* head = x10_listHeads[int(data->GetContainingList())];;\n       last = head, head = head->GetNextMapAreaData()) {\n    if (head != data)\n      continue;\n    if (!last)\n      x10_listHeads[int(data->GetContainingList())] = head->GetNextMapAreaData();\n    else\n      last->SetNextMapArea(head->GetNextMapAreaData());\n    break;\n  }\n  data->SetNextMapArea(x10_listHeads[int(list)]);\n  data->SetContainingList(list);\n  x10_listHeads[int(list)] = data;\n}\n\ns32 CMapWorld::GetCurrentMapAreaDepth(const IWorld& wld, TAreaId aid) {\n  ClearTraversedFlags();\n  std::vector<CMapAreaBFSInfo> info;\n  info.reserve(x0_areas.size());\n  DoBFS(wld, aid, 9999, 9999.f, 9999.f, false, info);\n  if (info.empty())\n    return 0;\n  return info.back().GetDepth();\n}\n\nstd::vector<int> CMapWorld::GetVisibleAreas(const IWorld& wld, const CMapWorldInfo& mwInfo) const {\n  std::vector<int> ret;\n  ret.reserve(x0_areas.size());\n  for (size_t i = 0; i < x0_areas.size(); ++i) {\n    if (!IsMapAreaValid(wld, i, true))\n      continue;\n    const CMapArea* area = GetMapArea(i);\n    bool areaVis = mwInfo.IsAreaVisible(i);\n    bool worldVis = mwInfo.IsWorldVisible(i);\n    if (area->GetIsVisibleToAutoMapper(worldVis, areaVis))\n      ret.push_back(i);\n  }\n  return ret;\n}\n\nvoid CMapWorld::Draw(const CMapWorldDrawParms& parms, int curArea, int otherArea, float depth1, float depth2,\n                     bool inMapScreen) {\n  if (depth1 == 0.f && depth2 == 0.f)\n    return;\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CMapWorld::Draw\", zeus::skBlue);\n\n  ClearTraversedFlags();\n  int areaDepth = std::ceil(std::max(depth1, depth2));\n\n  std::vector<CMapAreaBFSInfo> bfsInfos;\n  bfsInfos.reserve(x0_areas.size());\n  if (curArea != otherArea) {\n    x20_traversed[otherArea] = true;\n    DoBFS(parms.GetWorld(), curArea, areaDepth, depth1, depth2, true, bfsInfos);\n\n    float lowD1 = std::ceil(depth1 - 1.f);\n    float tmp;\n    if (depth1 == std::floor(depth1))\n      tmp = 0.f;\n    else\n      tmp = 1.f - std::fmod(depth1, 1.f);\n    float newD1 = lowD1 + tmp;\n\n    float lowD2 = std::ceil(depth2 - 1.f);\n    if (depth2 == std::floor(depth2))\n      tmp = 0.f;\n    else\n      tmp = 1.f - std::fmod(depth2, 1.f);\n    float newD2 = lowD2 + tmp;\n\n    int otherDepth = std::ceil(std::max(newD1, newD2));\n    if (parms.GetWorld().IGetAreaAlways(otherArea)->IIsActive()) {\n      x20_traversed[otherArea] = false;\n      DoBFS(parms.GetWorld(), otherArea, otherDepth, newD1, newD2, true, bfsInfos);\n    }\n  } else {\n    DoBFS(parms.GetWorld(), curArea, areaDepth, depth1, depth2, true, bfsInfos);\n  }\n\n  DrawAreas(parms, curArea, bfsInfos, inMapScreen);\n}\n\nvoid CMapWorld::DoBFS(const IWorld& wld, int startArea, int areaCount, float surfDepth, float outlineDepth,\n                      bool checkLoad, std::vector<CMapAreaBFSInfo>& bfsInfos) {\n  if (areaCount <= 0 || !IsMapAreaValid(wld, startArea, checkLoad))\n    return;\n\n  size_t size = bfsInfos.size();\n  bfsInfos.emplace_back(startArea, 1, surfDepth, outlineDepth);\n  x20_traversed[startArea] = true;\n\n  for (; size != bfsInfos.size(); ++size) {\n    CMapAreaBFSInfo& testInfo = bfsInfos[size];\n    if (testInfo.GetDepth() == areaCount)\n      continue;\n\n    surfDepth = testInfo.GetSurfaceDrawDepth() - 1.f;\n    outlineDepth = testInfo.GetOutlineDrawDepth() - 1.f;\n\n    const IGameArea* area = wld.IGetAreaAlways(testInfo.GetAreaIndex());\n    for (u32 i = 0; i < area->IGetNumAttachedAreas(); ++i) {\n      TAreaId attId = area->IGetAttachedAreaId(i);\n      if (IsMapAreaValid(wld, attId, checkLoad) && !x20_traversed[attId]) {\n        bfsInfos.emplace_back(attId, testInfo.GetDepth() + 1, surfDepth, outlineDepth);\n        x20_traversed[attId] = true;\n      }\n    }\n  }\n}\n\nbool CMapWorld::IsMapAreaValid(const IWorld& wld, int areaIdx, bool checkLoad) const {\n  if (!wld.IGetAreaAlways(areaIdx)->IIsActive())\n    return false;\n  const CMapArea* mapa = GetMapArea(areaIdx);\n  if (checkLoad)\n    return mapa != nullptr;\n  return true;\n}\n\nvoid CMapWorld::DrawAreas(const CMapWorldDrawParms& parms, int selArea, const std::vector<CMapAreaBFSInfo>& bfsInfos,\n                          bool inMapScreen) {\n  g_Renderer->SetBlendMode_AlphaBlended();\n  CGraphics::SetLineWidth(1.f, ERglTexOffset::One);\n\n  int surfCount = 0;\n  int objCount = 0;\n  for (const CMapAreaBFSInfo& bfsInfo : bfsInfos) {\n    const CMapArea* mapa = GetMapArea(bfsInfo.GetAreaIndex());\n    surfCount += mapa->GetNumSurfaces();\n    objCount += mapa->GetNumMappableObjects();\n  }\n\n  std::vector<CMapObjectSortInfo> sortInfos;\n  sortInfos.reserve(surfCount + objCount + (parms.GetIsSortDoorSurfaces() ? objCount * 6 : 0));\n\n  int playerArea = parms.GetStateManager().GetNextAreaId();\n  const CMapWorldInfo& mwInfo = parms.GetMapWorldInfo();\n  for (const CMapAreaBFSInfo& bfsInfo : bfsInfos) {\n    int thisArea = bfsInfo.GetAreaIndex();\n    const CMapArea* mapa = GetMapArea(thisArea);\n    if (!mapa->GetIsVisibleToAutoMapper(mwInfo.IsWorldVisible(thisArea), mwInfo.IsAreaVisible(thisArea)))\n      continue;\n\n    float surfDepth = bfsInfo.GetSurfaceDrawDepth();\n    float outlineDepth = bfsInfo.GetOutlineDrawDepth();\n\n    if (surfDepth >= 1.f)\n      surfDepth = 1.f;\n    else if (surfDepth < 0.f)\n      surfDepth = 0.f;\n    else\n      surfDepth -= std::floor(surfDepth);\n\n    if (outlineDepth >= 1.f)\n      outlineDepth = 1.f;\n    else if (outlineDepth < 0.f)\n      outlineDepth = 0.f;\n    else\n      outlineDepth -= std::floor(outlineDepth);\n\n    float alphaSurf;\n    float alphaOutline;\n    const zeus::CColor* surfaceColor;\n    const zeus::CColor* outlineColor;\n    const zeus::CColor* surfacePlayerColor;\n    const zeus::CColor* outlinePlayerColor;\n    if (mwInfo.IsAreaVisited(thisArea)) {\n      alphaSurf = parms.GetAlphaSurfaceVisited();\n      alphaOutline = parms.GetAlphaOutlineVisited();\n      surfaceColor = &g_tweakAutoMapper->GetSurfaceVisitedColor();\n      outlineColor = &g_tweakAutoMapper->GetOutlineVisitedColor();\n      surfacePlayerColor = &g_tweakAutoMapper->GetSurfaceSelectVisitedColor();\n      outlinePlayerColor = &g_tweakAutoMapper->GetOutlineSelectVisitedColor();\n    } else {\n      alphaSurf = parms.GetAlphaSurfaceUnvisited();\n      alphaOutline = parms.GetAlphaOutlineUnvisited();\n      surfaceColor = &g_tweakAutoMapper->GetSurfaceUnvisitedColor();\n      outlineColor = &g_tweakAutoMapper->GetOutlineUnvisitedColor();\n      surfacePlayerColor = &g_tweakAutoMapper->GetSurfaceSelectUnvisitedColor();\n      outlinePlayerColor = &g_tweakAutoMapper->GetOutlineSelectUnvisitedColor();\n    }\n\n    zeus::CColor hintFlashColor =\n        zeus::CColor::lerp(zeus::skClear, zeus::CColor{1.f, 1.f, 1.f, 0.f}, parms.GetHintAreaFlashIntensity());\n\n    zeus::CColor finalSurfColor, finalOutlineColor;\n    if (thisArea == selArea && inMapScreen) {\n      finalSurfColor = *surfacePlayerColor + hintFlashColor;\n      finalOutlineColor = *outlinePlayerColor + hintFlashColor;\n    } else {\n      finalSurfColor = *surfaceColor;\n      finalSurfColor.a() = surfDepth * alphaSurf;\n      finalOutlineColor = *outlineColor;\n      finalOutlineColor.a() = outlineDepth * alphaOutline;\n    }\n\n    if ((selArea != playerArea || parms.GetHintAreaFlashIntensity() == 0.f) && playerArea == thisArea &&\n        this == parms.GetStateManager().GetWorld()->GetMapWorld()) {\n      float pulse = parms.GetPlayerAreaFlashIntensity();\n      const zeus::CColor& flashCol = g_tweakAutoMapper->GetAreaFlashPulseColor();\n      finalSurfColor = zeus::CColor::lerp(finalSurfColor, flashCol, pulse);\n      finalOutlineColor = zeus::CColor::lerp(finalOutlineColor, flashCol, pulse);\n    }\n\n    zeus::CTransform modelView =\n        parms.GetCameraTransform().inverse() * mapa->GetAreaPostTransform(parms.GetWorld(), thisArea);\n    for (u32 i = 0; i < mapa->GetNumSurfaces(); ++i) {\n      const CMapArea::CMapAreaSurface& surf = mapa->GetSurface(i);\n      zeus::CVector3f pos = modelView * surf.GetCenterPosition();\n      sortInfos.emplace_back(pos.y(), thisArea, CMapObjectSortInfo::EObjectCode::Surface, i, finalSurfColor,\n                             finalOutlineColor);\n    }\n\n    u32 i = 0;\n    u32 si = 0;\n    for (; i < mapa->GetNumMappableObjects(); ++i, si += 6) {\n      const CMappableObject& obj = mapa->GetMappableObject(i);\n      if (!obj.GetIsVisibleToAutoMapper(mwInfo.IsWorldVisible(thisArea), mwInfo))\n        continue;\n\n      bool doorType = CMappableObject::IsDoorType(obj.GetType());\n      if (doorType) {\n        if (!mwInfo.IsAreaVisible(thisArea))\n          continue;\n        if (parms.GetIsSortDoorSurfaces()) {\n          for (u32 s = 0; s < 6; ++s) {\n            zeus::CVector3f center = obj.BuildSurfaceCenterPoint(s);\n            zeus::CVector3f pos = modelView * (CMapArea::GetAreaPostTranslate(parms.GetWorld(), thisArea) + center);\n            sortInfos.emplace_back(pos.y(), thisArea, CMapObjectSortInfo::EObjectCode::DoorSurface, si + s,\n                                   zeus::CColor{1.f, 0.f, 1.f, 1.f}, zeus::CColor{1.f, 0.f, 1.f, 1.f});\n          }\n          continue;\n        }\n      }\n\n      zeus::CVector3f pos =\n          modelView * (obj.GetTransform().origin + CMapArea::GetAreaPostTranslate(parms.GetWorld(), thisArea));\n      sortInfos.emplace_back(pos.y(), thisArea,\n                             doorType ? CMapObjectSortInfo::EObjectCode::Door : CMapObjectSortInfo::EObjectCode::Object,\n                             i, zeus::CColor{1.f, 0.f, 1.f, 1.f}, zeus::CColor{1.f, 0.f, 1.f, 1.f});\n    }\n  }\n\n  if (!sortInfos.empty()) {\n    std::sort(sortInfos.begin(), sortInfos.end(), [](const CMapObjectSortInfo& a, const CMapObjectSortInfo& b) {\n      return a.GetZDistance() > b.GetZDistance();\n    });\n    CMapArea::CMapAreaSurface::SetupGXMaterial();\n\n    u32 lastAreaIdx = UINT32_MAX;\n    CMapObjectSortInfo::EObjectCode lastType = CMapObjectSortInfo::EObjectCode::Invalid;\n    for (const CMapObjectSortInfo& info : sortInfos) {\n      CMapArea* mapa = GetMapArea(info.GetAreaIndex());\n      zeus::CTransform areaPostXf = mapa->GetAreaPostTransform(parms.GetWorld(), info.GetAreaIndex());\n      if (info.GetObjectCode() == CMapObjectSortInfo::EObjectCode::Surface) {\n        CMapArea::CMapAreaSurface& surf = mapa->GetSurface(info.GetLocalObjectIndex());\n        zeus::CColor color(\n            std::max(0.f, (-parms.GetCameraTransform().basis[1]).dot(areaPostXf.rotate(surf.GetNormal()))) *\n                g_tweakAutoMapper->GetMapSurfaceNormColorLinear() +\n            g_tweakAutoMapper->GetMapSurfaceNormColorConstant());\n        color *= info.GetSurfaceColor();\n        if (lastAreaIdx != info.GetAreaIndex() || lastType != CMapObjectSortInfo::EObjectCode::Surface) {\n          CGraphics::SetModelMatrix(parms.GetPlaneProjectionTransform() * areaPostXf);\n        }\n        surf.Draw(mapa->GetVertices(), color, info.GetOutlineColor(), parms.GetOutlineWidthScale());\n\n        lastAreaIdx = info.GetAreaIndex();\n        lastType = info.GetObjectCode();\n      }\n    }\n    for (const CMapObjectSortInfo& info : sortInfos) {\n      CMapArea* mapa = GetMapArea(info.GetAreaIndex());\n      if (info.GetObjectCode() == CMapObjectSortInfo::EObjectCode::Door ||\n          info.GetObjectCode() == CMapObjectSortInfo::EObjectCode::Object) {\n        CMappableObject& mapObj = mapa->GetMappableObject(info.GetLocalObjectIndex());\n        const zeus::CTransform objXf =\n            zeus::CTransform::Translate(CMapArea::GetAreaPostTranslate(parms.GetWorld(), info.GetAreaIndex())) *\n            mapObj.GetTransform();\n        if (info.GetObjectCode() == CMapObjectSortInfo::EObjectCode::Door) {\n          CGraphics::SetModelMatrix(parms.GetPlaneProjectionTransform() * objXf);\n        } else {\n          CGraphics::SetModelMatrix(\n              parms.GetPlaneProjectionTransform() * objXf *\n              zeus::CTransform(parms.GetCameraTransform().buildMatrix3f() * zeus::CMatrix3f(parms.GetObjectScale())));\n        }\n        mapObj.Draw(selArea, mwInfo, parms.GetAlpha(), lastType != info.GetObjectCode());\n        lastType = info.GetObjectCode();\n      } else if (info.GetObjectCode() == CMapObjectSortInfo::EObjectCode::DoorSurface) {\n        CMappableObject& mapObj = mapa->GetMappableObject(info.GetLocalObjectIndex() / 6);\n        const zeus::CTransform objXf =\n            parms.GetPlaneProjectionTransform() *\n            zeus::CTransform::Translate(CMapArea::GetAreaPostTranslate(parms.GetWorld(), info.GetAreaIndex())) *\n            mapObj.GetTransform();\n        CGraphics::SetModelMatrix(objXf);\n        mapObj.DrawDoorSurface(selArea, mwInfo, parms.GetAlpha(), info.GetLocalObjectIndex() % 6,\n                               lastType != info.GetObjectCode());\n        lastType = info.GetObjectCode();\n      }\n    }\n  }\n}\n\nvoid CMapWorld::RecalculateWorldSphere(const CMapWorldInfo& mwInfo, const IWorld& wld) {\n  std::vector<zeus::CVector2f> coords;\n  coords.reserve(x0_areas.size() * 8);\n  float zMin = FLT_MAX;\n  float zMax = -FLT_MAX;\n  for (size_t i = 0; i < x0_areas.size(); ++i) {\n    if (IsMapAreaValid(wld, i, true)) {\n      const CMapArea* mapa = GetMapArea(i);\n      if (mapa->GetIsVisibleToAutoMapper(mwInfo.IsWorldVisible(i), mwInfo.IsAreaVisible(i))) {\n        zeus::CAABox aabb = mapa->GetBoundingBox().getTransformedAABox(mapa->GetAreaPostTransform(wld, i));\n        for (int j = 0; j < 8; ++j) {\n          const zeus::CVector3f point = aabb.getPoint(j);\n          coords.push_back(point.toVec2f());\n          zMin = std::min(point.z(), zMin);\n          zMax = std::max(point.z(), zMax);\n        }\n      }\n    }\n  }\n\n  const Circle circle = MinCircle(coords);\n  x3c_worldSphereRadius = circle.x8_radius;\n  x30_worldSpherePoint = zeus::CVector3f(circle.x0_point.x(), circle.x0_point.y(), (zMin + zMax) * 0.5f);\n  x40_worldSphereHalfDepth = (zMax - zMin) * 0.5f;\n}\n\nzeus::CVector3f CMapWorld::ConstrainToWorldVolume(const zeus::CVector3f& point, const zeus::CVector3f& lookVec) const {\n  zeus::CVector3f ret = point;\n  if (std::fabs(lookVec.z()) > FLT_EPSILON) {\n    float f2 = point.z() - (x40_worldSphereHalfDepth + x30_worldSpherePoint.z());\n    float f1 = point.z() - (x30_worldSpherePoint.z() - x40_worldSphereHalfDepth);\n    if (f2 > 0.f)\n      ret = point + lookVec * (-f2 / lookVec.z());\n    else if (f1 < 0.f)\n      ret = point + lookVec * (-f1 / lookVec.z());\n  } else {\n    ret.z() = zeus::clamp(x30_worldSpherePoint.z() - x40_worldSphereHalfDepth, float(ret.z()),\n                          x40_worldSphereHalfDepth + x30_worldSpherePoint.z());\n  }\n\n  zeus::CVector2f tmp = x30_worldSpherePoint.toVec2f();\n  zeus::CVector2f vec2 = point.toVec2f() - tmp;\n  if (vec2.magnitude() > x3c_worldSphereRadius) {\n    tmp += vec2.normalized() * x3c_worldSphereRadius;\n    ret.x() = float(tmp.x());\n    ret.y() = float(tmp.y());\n  }\n\n  return ret;\n}\n\nvoid CMapWorld::ClearTraversedFlags() { std::fill(x20_traversed.begin(), x20_traversed.end(), false); }\n\nCFactoryFnReturn FMapWorldFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& param,\n                                  CObjectReference* selfRef) {\n  return TToken<CMapWorld>::GetIObjObjectFor(std::make_unique<CMapWorld>(in));\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/AutoMapper/CMapWorld.hpp",
    "content": "#pragma once\n\n#include <vector>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/AutoMapper/CMapArea.hpp\"\n\n#include <zeus/CColor.hpp>\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CMapWorldInfo;\nclass CStateManager;\nclass IWorld;\n\nclass CMapWorld {\npublic:\n  /* skDrawProfileItemNames; */\n  enum class EMapAreaList { Loaded, Loading, Unloaded };\n\n  class CMapAreaBFSInfo {\n    int x0_areaIdx;\n    int x4_depth;\n    float x8_surfDrawDepth;\n    float xc_outlineDrawDepth;\n\n  public:\n    CMapAreaBFSInfo(int areaIdx, int depth, float a, float b)\n    : x0_areaIdx(areaIdx), x4_depth(depth), x8_surfDrawDepth(a), xc_outlineDrawDepth(b) {}\n    int GetAreaIndex() const { return x0_areaIdx; }\n    int GetDepth() const { return x4_depth; }\n    float GetOutlineDrawDepth() const { return x8_surfDrawDepth; }\n    float GetSurfaceDrawDepth() const { return xc_outlineDrawDepth; }\n  };\n\n  class CMapObjectSortInfo {\n    float x0_zDist;\n    int x4_areaIdx;\n    int x8_typeAndIdx;\n    zeus::CColor xc_surfColor;\n    zeus::CColor x10_outlineColor;\n\n  public:\n    enum class EObjectCode { Invalid = -1, Object = 1 << 16, DoorSurface = 2 << 16, Door = 3 << 16, Surface = 4 << 16 };\n\n    CMapObjectSortInfo(float zDist, int areaIdx, EObjectCode type, int idx, const zeus::CColor& surfColor,\n                       const zeus::CColor& outlineColor)\n    : x0_zDist(zDist)\n    , x4_areaIdx(areaIdx)\n    , x8_typeAndIdx(int(type) | idx)\n    , xc_surfColor(surfColor)\n    , x10_outlineColor(outlineColor) {}\n    const zeus::CColor& GetOutlineColor() const { return x10_outlineColor; }\n    const zeus::CColor& GetSurfaceColor() const { return xc_surfColor; }\n    u32 GetLocalObjectIndex() const { return x8_typeAndIdx & 0xffff; }\n    EObjectCode GetObjectCode() const { return EObjectCode(x8_typeAndIdx & 0xffff0000); }\n    u32 GetAreaIndex() const { return x4_areaIdx; }\n    float GetZDistance() const { return x0_zDist; }\n  };\n\n  class CMapAreaData {\n    TCachedToken<CMapArea> x0_area;\n    EMapAreaList x10_list;\n    CMapAreaData* x14_next = nullptr;\n\n  public:\n    CMapAreaData(CAssetId areaRes, EMapAreaList list, CMapAreaData* next);\n    void Lock() { x0_area.Lock(); }\n    void Unlock() { x0_area.Unlock(); }\n    bool IsLoaded() const { return x0_area.IsLoaded(); }\n    CMapArea* GetMapArea() { return x0_area.GetObj(); }\n    const CMapArea* GetMapArea() const { return x0_area.GetObj(); }\n    CMapAreaData* GetNextMapAreaData() { return x14_next; }\n    const CMapAreaData* GetNextMapAreaData() const { return x14_next; }\n    EMapAreaList GetContainingList() const { return x10_list; }\n    void SetContainingList(EMapAreaList list) { x10_list = list; }\n    void SetNextMapArea(CMapAreaData* next) { x14_next = next; }\n  };\n\n  class CMapWorldDrawParms {\n    float x0_alphaSurfVisited;\n    float x4_alphaOlVisited;\n    float x8_alphaSurfUnvisited;\n    float xc_alphaOlUnvisited;\n    float x10_alpha;\n    float x14_outlineWidthScale;\n    const CStateManager& x18_mgr;\n    const zeus::CTransform& x1c_modelXf;\n    const zeus::CTransform& x20_viewXf;\n    const IWorld& x24_wld;\n    const CMapWorldInfo& x28_mwInfo;\n    float x2c_playerFlashIntensity;\n    float x30_hintFlashIntensity;\n    float x34_objectScale;\n    bool x38_sortDoorSurfs;\n\n  public:\n    CMapWorldDrawParms(float alphaSurfVisited, float alphaOlVisited, float alphaSurfUnvisited, float alphaOlUnvisited,\n                       float alpha, float outlineWidthScale, const CStateManager& mgr, const zeus::CTransform& modelXf,\n                       const zeus::CTransform& viewXf, const IWorld& wld, const CMapWorldInfo& mwInfo,\n                       float playerFlash, float hintFlash, float objectScale, bool sortDoorSurfs)\n    : x0_alphaSurfVisited(alphaSurfVisited)\n    , x4_alphaOlVisited(alphaOlVisited)\n    , x8_alphaSurfUnvisited(alphaSurfUnvisited)\n    , xc_alphaOlUnvisited(alphaOlUnvisited)\n    , x10_alpha(alpha)\n    , x14_outlineWidthScale(outlineWidthScale)\n    , x18_mgr(mgr)\n    , x1c_modelXf(modelXf)\n    , x20_viewXf(viewXf)\n    , x24_wld(wld)\n    , x28_mwInfo(mwInfo)\n    , x2c_playerFlashIntensity(playerFlash)\n    , x30_hintFlashIntensity(hintFlash)\n    , x34_objectScale(objectScale)\n    , x38_sortDoorSurfs(sortDoorSurfs) {}\n    const IWorld& GetWorld() const { return x24_wld; }\n    float GetOutlineWidthScale() const { return x14_outlineWidthScale; }\n    const zeus::CTransform& GetPlaneProjectionTransform() const { return x1c_modelXf; }\n    float GetHintAreaFlashIntensity() const { return x30_hintFlashIntensity; }\n    float GetPlayerAreaFlashIntensity() const { return x2c_playerFlashIntensity; }\n    const zeus::CTransform& GetCameraTransform() const { return x20_viewXf; }\n    float GetAlphaOutlineUnvisited() const { return xc_alphaOlUnvisited; }\n    float GetAlphaSurfaceUnvisited() const { return x8_alphaSurfUnvisited; }\n    float GetAlphaOutlineVisited() const { return x4_alphaOlVisited; }\n    float GetAlphaSurfaceVisited() const { return x0_alphaSurfVisited; }\n    float GetAlpha() const { return x10_alpha; }\n    const CMapWorldInfo& GetMapWorldInfo() const { return x28_mwInfo; }\n    const CStateManager& GetStateManager() const { return x18_mgr; }\n    bool GetIsSortDoorSurfaces() const { return x38_sortDoorSurfs; }\n    float GetObjectScale() const { return x34_objectScale; }\n  };\n\nprivate:\n  std::vector<CMapAreaData> x0_areas;\n  rstl::reserved_vector<CMapAreaData*, 3> x10_listHeads;\n  std::vector<bool> x20_traversed;\n  zeus::CVector3f x30_worldSpherePoint;\n  float x3c_worldSphereRadius = 0.f;\n  float x40_worldSphereHalfDepth = 0.f;\n\npublic:\n  explicit CMapWorld(CInputStream& in);\n  u32 GetNumAreas() const { return x0_areas.size(); }\n  CMapArea* GetMapArea(int aid) { return x0_areas[aid].GetMapArea(); }\n  const CMapArea* GetMapArea(int aid) const { return x0_areas[aid].GetMapArea(); }\n  bool IsMapAreaInBFSInfoVector(const CMapAreaData* area, const std::vector<CMapAreaBFSInfo>& vec) const;\n  void SetWhichMapAreasLoaded(const IWorld& wld, int start, int count);\n  bool IsMapAreasStreaming();\n  void MoveMapAreaToList(CMapAreaData* data, EMapAreaList list);\n  s32 GetCurrentMapAreaDepth(const IWorld& wld, TAreaId aid);\n  std::vector<int> GetVisibleAreas(const IWorld& wld, const CMapWorldInfo& mwInfo) const;\n  void Draw(const CMapWorldDrawParms& parms, int curArea, int otherArea, float depth1, float depth2, bool inMapScreen);\n  void DoBFS(const IWorld& wld, int startArea, int areaCount, float surfDepth, float outlineDepth, bool checkLoad,\n             std::vector<CMapAreaBFSInfo>& bfsInfos);\n  bool IsMapAreaValid(const IWorld& wld, int areaIdx, bool checkLoad) const;\n  void DrawAreas(const CMapWorldDrawParms& parms, int selArea, const std::vector<CMapAreaBFSInfo>& bfsInfos,\n                 bool inMapScreen);\n  void RecalculateWorldSphere(const CMapWorldInfo& mwInfo, const IWorld& wld);\n  zeus::CVector3f ConstrainToWorldVolume(const zeus::CVector3f& point, const zeus::CVector3f& lookVec) const;\n  void ClearTraversedFlags();\n};\n\nCFactoryFnReturn FMapWorldFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& param,\n                                  CObjectReference* selfRef);\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/AutoMapper/CMapWorldInfo.cpp",
    "content": "#include \"Runtime/AutoMapper/CMapWorldInfo.hpp\"\n\n#include \"Runtime/CMemoryCardSys.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n\nnamespace metaforce {\n\nCMapWorldInfo::CMapWorldInfo(CInputStream& reader, const CWorldSaveGameInfo& savw, CAssetId mlvlId) {\n  const CSaveWorldMemory& worldMem = g_MemoryCardSys->GetSaveWorldMemory(mlvlId);\n\n  x4_visitedAreas.reserve((worldMem.GetAreaCount() + 31) / 32);\n  for (u32 i = 0; i < worldMem.GetAreaCount(); ++i) {\n    const bool visited = reader.ReadBits(1) != 0;\n    SetAreaVisited(i, visited);\n  }\n\n  x18_mappedAreas.reserve((worldMem.GetAreaCount() + 31) / 32);\n  for (u32 i = 0; i < worldMem.GetAreaCount(); ++i) {\n    const bool mapped = reader.ReadBits(1) != 0;\n    SetIsMapped(i, mapped);\n  }\n\n  for (const auto& doorId : savw.GetDoors()) {\n    SetDoorVisited(doorId, reader.ReadBits(1) != 0);\n  }\n\n  x38_mapStationUsed = reader.ReadBits(1) != 0;\n}\n\nvoid CMapWorldInfo::PutTo(COutputStream& writer, const CWorldSaveGameInfo& savw, CAssetId mlvlId) const {\n  const CSaveWorldMemory& worldMem = g_MemoryCardSys->GetSaveWorldMemory(mlvlId);\n\n  for (u32 i = 0; i < worldMem.GetAreaCount(); ++i) {\n    if (i < x0_visitedAreasAllocated) {\n      writer.WriteBits(u32(IsAreaVisited(i)), 1);\n    } else {\n      writer.WriteBits(0, 1);\n    }\n  }\n\n  for (u32 i = 0; i < worldMem.GetAreaCount(); ++i) {\n    if (i < x14_mappedAreasAllocated) {\n      writer.WriteBits(u32(IsMapped(i)), 1);\n    } else {\n      writer.WriteBits(0, 1);\n    }\n  }\n\n  for (const auto& doorId : savw.GetDoors()) {\n    writer.WriteBits(u32(IsDoorVisited(doorId)), 1);\n  }\n\n  writer.WriteBits(u32(x38_mapStationUsed), 1);\n}\n\nvoid CMapWorldInfo::SetDoorVisited(TEditorId eid, bool visited) { x28_visitedDoors[eid] = visited; }\n\nbool CMapWorldInfo::IsDoorVisited(TEditorId eid) const { return x28_visitedDoors.find(eid) != x28_visitedDoors.end(); }\n\nbool CMapWorldInfo::IsAreaVisited(TAreaId aid) const {\n  if (u32(aid) + 1 > x0_visitedAreasAllocated) {\n    x4_visitedAreas.resize((u32(aid) + 32) / 32);\n    x0_visitedAreasAllocated = u32(aid) + 1;\n  }\n  return ((x4_visitedAreas[aid / 32] >> (aid % 32)) & 1) != 0;\n}\n\nvoid CMapWorldInfo::SetAreaVisited(TAreaId aid, bool visited) {\n  if (u32(aid) + 1 > x0_visitedAreasAllocated) {\n    x4_visitedAreas.resize((u32(aid) + 32) / 32);\n    x0_visitedAreasAllocated = u32(aid) + 1;\n  }\n\n  if (visited) {\n    x4_visitedAreas[aid / 32] |= 1U << (aid % 32);\n  } else {\n    x4_visitedAreas[aid / 32] &= ~(1U << (aid % 32));\n  }\n}\n\nbool CMapWorldInfo::IsMapped(TAreaId aid) const {\n  if (u32(aid) + 1 > x14_mappedAreasAllocated) {\n    x18_mappedAreas.resize((u32(aid) + 32) / 32);\n    x14_mappedAreasAllocated = u32(aid) + 1;\n  }\n  return ((x18_mappedAreas[aid / 32] >> (aid % 32)) & 1) != 0;\n}\n\nvoid CMapWorldInfo::SetIsMapped(TAreaId aid, bool mapped) {\n  if (u32(aid) + 1 > x14_mappedAreasAllocated) {\n    x18_mappedAreas.resize((u32(aid) + 32) / 32);\n    x14_mappedAreasAllocated = u32(aid) + 1;\n  }\n\n  if (mapped) {\n    x18_mappedAreas[aid / 32] |= 1U << (aid % 32);\n  } else {\n    x18_mappedAreas[aid / 32] &= ~(1U << (aid % 32));\n  }\n}\n\nbool CMapWorldInfo::IsWorldVisible(TAreaId aid) const { return x38_mapStationUsed || IsMapped(aid); }\n\nbool CMapWorldInfo::IsAreaVisible(TAreaId aid) const { return IsAreaVisited(aid) || IsMapped(aid); }\n\nbool CMapWorldInfo::IsAnythingSet() const {\n  for (u32 i = 0; i < x0_visitedAreasAllocated; ++i) {\n    if ((x4_visitedAreas[i / 32] & (1U << (i % 32))) != 0) {\n      return true;\n    }\n  }\n  for (u32 i = 0; i < x14_mappedAreasAllocated; ++i) {\n    if ((x18_mappedAreas[i / 32] & (1U << (i % 32))) != 0) {\n      return true;\n    }\n  }\n  return x38_mapStationUsed;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/AutoMapper/CMapWorldInfo.hpp",
    "content": "#pragma once\n\n#include <map>\n#include <vector>\n\n#include \"RetroTypes.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n\nnamespace metaforce {\nclass CWorldSaveGameInfo;\n\nclass CMapWorldInfo {\n  mutable u32 x0_visitedAreasAllocated = 0;\n  mutable std::vector<u32> x4_visitedAreas;\n  mutable u32 x14_mappedAreasAllocated = 0;\n  mutable std::vector<u32> x18_mappedAreas;\n  std::map<TEditorId, bool> x28_visitedDoors;\n  bool x38_mapStationUsed = false;\n\npublic:\n  CMapWorldInfo() = default;\n  explicit CMapWorldInfo(CInputStream& reader, const CWorldSaveGameInfo& saveWorld, CAssetId mlvlId);\n  void PutTo(COutputStream& writer, const CWorldSaveGameInfo& savw, CAssetId mlvlId) const;\n  bool IsMapped(TAreaId aid) const;\n  void SetIsMapped(TAreaId aid, bool mapped);\n  void SetDoorVisited(TEditorId eid, bool val);\n  bool IsDoorVisited(TEditorId eid) const;\n  bool IsAreaVisited(TAreaId aid) const;\n  void SetAreaVisited(TAreaId aid, bool visited);\n  bool IsWorldVisible(TAreaId aid) const;\n  bool IsAreaVisible(TAreaId aid) const;\n  bool IsAnythingSet() const;\n  bool GetMapStationUsed() const { return x38_mapStationUsed; }\n  void SetMapStationUsed(bool isUsed) { x38_mapStationUsed = isUsed; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/AutoMapper/CMappableObject.cpp",
    "content": "#include \"Runtime/AutoMapper/CMappableObject.hpp\"\n\n#include \"Runtime/AutoMapper/CMapWorldInfo.hpp\"\n#include \"Runtime/AutoMapper/CMapArea.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CGX.hpp\"\n#include \"Runtime/Graphics/CTexture.hpp\"\n\nnamespace metaforce {\nusing zeus::CColor;\nusing zeus::CVector3f;\nusing uchar = unsigned char;\n\nstruct SDrawData {\n  float x0_x;\n  float x4_y;\n  float x8_z;\n  uchar xc_idxA;\n  uchar xd_idxB;\n  uchar xe_idxC;\n  uchar xf_idxD;\n};\n\nstatic const SDrawData sDrawData[6] = {\n    // clang-format off\n  { 0.f,  0.f, -1.f, 6, 4, 2, 0},\n  { 0.f,  0.f,  1.f, 3, 1, 7, 5},\n  { 0.f, -1.f,  1.f, 1, 0, 5, 4},\n  { 0.f,  1.f,  1.f, 7, 6, 3, 2},\n  {-1.f,  0.f,  0.f, 3, 2, 1, 0},\n  { 1.f,  0.f,  0.f, 5, 4, 7, 6},\n    // clang-format on\n};\n\nstatic std::array<aurora::Vec3<float>, 8> skDoorVerts{};\n\nCMappableObject::CMappableObject(const void* buf) {\n  CMemoryInStream r(buf, 64);\n  x0_type = EMappableObjectType(r.ReadLong());\n  x4_visibilityMode = EVisMode(r.ReadLong());\n  x8_objId = r.ReadLong();\n  xc_ = r.ReadLong();\n  x10_transform = r.Get<zeus::CTransform>();\n}\n\nzeus::CTransform CMappableObject::AdjustTransformForType() const {\n  const float doorCenterX = g_tweakAutoMapper->xa4_doorCenterA;\n  const float doorCenterZ = g_tweakAutoMapper->xac_doorCenterC;\n  if (x0_type == EMappableObjectType::BigDoor1) {\n    zeus::CTransform orientation;\n    orientation.origin = {0.0f, 0.0f, -1.4f * doorCenterX};\n    orientation.rotateLocalZ(zeus::degToRad(90.0f));\n    return (x10_transform * orientation) * zeus::CTransform::Scale(zeus::CVector3f{1.5f});\n  } else if (x0_type == EMappableObjectType::BigDoor2) {\n    zeus::CTransform orientation;\n    orientation.origin = {0.f, -2.0f * doorCenterZ, -1.4f * doorCenterX};\n    orientation.rotateLocalZ(zeus::degToRad(-90.f));\n    return (x10_transform * orientation) * zeus::CTransform::Scale(zeus::CVector3f{1.5f});\n  } else if (x0_type == EMappableObjectType::IceDoorCeiling || x0_type == EMappableObjectType::WaveDoorCeiling ||\n             x0_type == EMappableObjectType::PlasmaDoorCeiling) {\n    zeus::CTransform orientation;\n    orientation.origin = {-1.65f * doorCenterX, 0.f, -1.5f * doorCenterZ};\n    orientation.rotateLocalY(zeus::degToRad(90.f));\n    return x10_transform * orientation;\n  } else if (x0_type == EMappableObjectType::IceDoorFloor || x0_type == EMappableObjectType::WaveDoorFloor ||\n             x0_type == EMappableObjectType::PlasmaDoorFloor) {\n    zeus::CTransform orientation;\n    orientation.origin = {-1.65f * doorCenterX, 0.f, -1.f * doorCenterZ};\n    orientation.rotateLocalY(zeus::degToRad(90.f));\n    return x10_transform * orientation;\n  } else if ((u32(x0_type) - u32(EMappableObjectType::IceDoorFloor2)) <= u32(EMappableObjectType::ShieldDoor) ||\n             x0_type == EMappableObjectType::PlasmaDoorFloor2) {\n    zeus::CTransform orientation;\n    orientation.origin = {-0.49f * doorCenterX, 0.f, -1.f * doorCenterZ};\n    orientation.rotateLocalY(zeus::degToRad(90.f));\n    return x10_transform * orientation;\n  } else if (IsDoorType(x0_type)) {\n    return x10_transform;\n  }\n  return zeus::CTransform::Translate(x10_transform.origin);\n}\n\nstd::pair<CColor, CColor> CMappableObject::GetDoorColors(int curAreaId, const CMapWorldInfo& mwInfo,\n                                                         float alpha) const {\n  CColor color;\n  if (x8_objId.AreaNum() == curAreaId) {\n    if (mwInfo.IsDoorVisited(x8_objId) && x0_type == EMappableObjectType::ShieldDoor) {\n      color = g_tweakAutoMapper->GetDoorColor(0);\n    } else {\n      int colorIdx = 0;\n      switch (x0_type) {\n      case EMappableObjectType::ShieldDoor:\n        colorIdx = 1;\n        break;\n      case EMappableObjectType::IceDoor:\n      case EMappableObjectType::IceDoorCeiling:\n      case EMappableObjectType::IceDoorFloor:\n      case EMappableObjectType::IceDoorFloor2:\n        colorIdx = 2;\n        break;\n      case EMappableObjectType::WaveDoor:\n      case EMappableObjectType::WaveDoorCeiling:\n      case EMappableObjectType::WaveDoorFloor:\n      case EMappableObjectType::WaveDoorFloor2:\n        colorIdx = 3;\n        break;\n      case EMappableObjectType::PlasmaDoor:\n      case EMappableObjectType::PlasmaDoorCeiling:\n      case EMappableObjectType::PlasmaDoorFloor:\n      case EMappableObjectType::PlasmaDoorFloor2:\n        colorIdx = 4;\n        break;\n      default:\n        break;\n      }\n      color = g_tweakAutoMapper->GetDoorColor(colorIdx);\n    }\n  } else if (mwInfo.IsDoorVisited(x8_objId)) {\n    color = g_tweakAutoMapper->GetOpenDoorColor();\n  } else {\n    color = zeus::skClear;\n  }\n\n  color.a() *= alpha;\n  return {color, CColor(std::min(1.4f * color.r(), 1.f), std::min(1.4f * color.g(), 1.f),\n                        std::min(1.4f * color.b(), 1.f), std::min(1.4f * color.a(), 1.f))};\n}\n\nvoid CMappableObject::PostConstruct(const void*) { x10_transform = AdjustTransformForType(); }\n\nvoid CMappableObject::Draw(int curArea, const CMapWorldInfo& mwInfo, float alpha, bool needsVtxLoad) {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CMappableObject::Draw\", zeus::skCyan);\n  if (IsDoorType(x0_type) == true) {\n    std::pair<CColor, CColor> colors = GetDoorColors(curArea, mwInfo, alpha);\n    for (int i = 0; i < 6; ++i) {\n      if (needsVtxLoad) {\n        CGX::SetArray(GX_VA_POS, skDoorVerts);\n      }\n      CGX::SetTevKColor(GX_KCOLOR0, colors.first);\n      CGX::Begin(GX_TRIANGLESTRIP, GX_VTXFMT0, 4);\n      GXPosition1x8(sDrawData[i].xc_idxA);\n      GXPosition1x8(sDrawData[i].xd_idxB);\n      GXPosition1x8(sDrawData[i].xe_idxC);\n      GXPosition1x8(sDrawData[i].xf_idxD);\n      CGX::End();\n\n      CGX::SetTevKColor(GX_KCOLOR0, colors.second);\n      CGX::Begin(GX_LINESTRIP, GX_VTXFMT0, 5);\n      GXPosition1x8(sDrawData[i].xc_idxA);\n      GXPosition1x8(sDrawData[i].xd_idxB);\n      GXPosition1x8(sDrawData[i].xf_idxD);\n      GXPosition1x8(sDrawData[i].xe_idxC);\n      GXPosition1x8(sDrawData[i].xc_idxA);\n      CGX::End();\n    }\n    return;\n  }\n\n  CAssetId iconRes;\n  CColor iconColor = CColor(0xffffffffu);\n  switch (x0_type) {\n  case EMappableObjectType::DownArrowYellow:\n    iconColor = CColor(0xffff96ffu);\n    iconRes = g_tweakPlayerRes->x10_minesBreakFirstTopIcon;\n    break;\n  case EMappableObjectType::UpArrowYellow:\n    iconColor = CColor(0xffff96ffu);\n    iconRes = g_tweakPlayerRes->x14_minesBreakFirstBottomIcon;\n    break;\n  case EMappableObjectType::DownArrowGreen:\n    iconColor = CColor(0x64ff96ffu);\n    iconRes = g_tweakPlayerRes->x10_minesBreakFirstTopIcon;\n    break;\n  case EMappableObjectType::UpArrowGreen:\n    iconColor = CColor(0x64ff96ffu);\n    iconRes = g_tweakPlayerRes->x14_minesBreakFirstBottomIcon;\n    break;\n  case EMappableObjectType::DownArrowRed:\n    iconColor = CColor(0xff6496ffu);\n    iconRes = g_tweakPlayerRes->x10_minesBreakFirstTopIcon;\n    break;\n  case EMappableObjectType::UpArrowRed:\n    iconColor = CColor(0xff6496ffu);\n    iconRes = g_tweakPlayerRes->x14_minesBreakFirstBottomIcon;\n    break;\n  case EMappableObjectType::SaveStation:\n    iconRes = g_tweakPlayerRes->x4_saveStationIcon;\n    break;\n  case EMappableObjectType::MissileStation:\n    iconRes = g_tweakPlayerRes->x8_missileStationIcon;\n    break;\n  default:\n    iconRes = g_tweakPlayerRes->xc_elevatorIcon;\n    break;\n  }\n\n  TLockedToken<CTexture> tex = g_SimplePool->GetObj(SObjectTag('TXTR', iconRes));\n  tex->Load(GX_TEXMAP0, EClampMode::Repeat);\n  CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvModulate);\n  CGraphics::StreamBegin(ERglPrimitive::TriangleStrip);\n  iconColor.a() = alpha;\n  CGraphics::StreamColor(iconColor);\n  CGraphics::StreamTexcoord(0.0f, 1.0f);\n  CGraphics::StreamVertex(-2.6f, 0.0f, 2.6f);\n  CGraphics::StreamTexcoord(0.0f, 0.0f);\n  CGraphics::StreamVertex(-2.6f, 0.0f, -2.6f);\n  CGraphics::StreamTexcoord(1.0f, 1.0f);\n  CGraphics::StreamVertex(2.6f, 0.0f, 2.6f);\n  CGraphics::StreamTexcoord(1.0f, 0.0f);\n  CGraphics::StreamVertex(2.6f, 0.0f, -2.6f);\n  CGraphics::StreamEnd();\n\n  // Metaforce addition: restore GX state\n  CMapArea::CMapAreaSurface::SetupGXMaterial();\n}\n\nvoid CMappableObject::DrawDoorSurface(int curArea, const CMapWorldInfo& mwInfo, float alpha, int surfIdx,\n                                      bool needsVtxLoad) {\n  std::pair<CColor, CColor> colors = GetDoorColors(curArea, mwInfo, alpha);\n  const SDrawData& drawData = sDrawData[surfIdx];\n  if (needsVtxLoad) {\n    CGX::SetArray(GX_VA_POS, skDoorVerts);\n  }\n\n  CGX::SetTevKColor(GX_KCOLOR0, colors.first);\n  CGX::Begin(GX_TRIANGLESTRIP, GX_VTXFMT0, 4);\n  GXPosition1x8(drawData.xc_idxA);\n  GXPosition1x8(drawData.xd_idxB);\n  GXPosition1x8(drawData.xe_idxC);\n  GXPosition1x8(drawData.xf_idxD);\n  CGX::End();\n\n  CGX::SetTevKColor(GX_KCOLOR0, colors.second);\n  CGX::Begin(GX_LINESTRIP, GX_VTXFMT0, 5);\n  GXPosition1x8(drawData.xc_idxA);\n  GXPosition1x8(drawData.xd_idxB);\n  GXPosition1x8(drawData.xf_idxD);\n  GXPosition1x8(drawData.xe_idxC);\n  GXPosition1x8(drawData.xc_idxA);\n  CGX::End();\n}\n\nCVector3f CMappableObject::BuildSurfaceCenterPoint(int surfaceIdx) const {\n  const float x = g_tweakAutoMapper->xac_doorCenterC;\n  const float y = g_tweakAutoMapper->xa8_doorCenterB;\n  const float z = g_tweakAutoMapper->xa4_doorCenterA;\n  switch (surfaceIdx) {\n  case 0:\n    return x10_transform * CVector3f{};\n  case 1:\n    return x10_transform * CVector3f(0.f, 0.f, 2.f * z);\n  case 2:\n    return x10_transform * CVector3f(0.f, -y, 0.f);\n  case 3:\n    return x10_transform * CVector3f(0.f, y, 0.f);\n  case 4:\n    return x10_transform * CVector3f(-x, 0.f, 0.f);\n  case 5:\n    return x10_transform * CVector3f(x, 0.f, 0.f);\n  default:\n    return CVector3f{};\n  }\n}\n\nbool CMappableObject::GetIsVisibleToAutoMapper(bool worldVis, const CMapWorldInfo& mwInfo) const {\n  bool areaVis = mwInfo.IsAreaVisible(x8_objId.AreaNum());\n  switch (x4_visibilityMode) {\n  case EVisMode::Always:\n  default:\n    return true;\n  case EVisMode::MapStationOrVisit:\n  case EVisMode::MapStationOrVisit2:\n    return worldVis || areaVis;\n  case EVisMode::Visit:\n    if (IsDoorType(x0_type)) {\n      return mwInfo.IsDoorVisited(x8_objId);\n    }\n    return areaVis;\n  case EVisMode::Never:\n    return false;\n  }\n}\n\nvoid CMappableObject::ReadAutoMapperTweaks(const ITweakAutoMapper& tweaks) {\n  const float x = tweaks.xac_doorCenterC;\n  const float y = tweaks.xa8_doorCenterB;\n  const float z = tweaks.xa4_doorCenterA;\n  skDoorVerts[0] = aurora::Vec3(-x, -y, 0.f);\n  skDoorVerts[1] = aurora::Vec3(-x, -y, z * 2.f);\n  skDoorVerts[2] = aurora::Vec3(-x, y, 0.f);\n  skDoorVerts[3] = aurora::Vec3(-x, y, z * 2.f);\n  skDoorVerts[4] = aurora::Vec3(-x * .2f, -y, 0.f);\n  skDoorVerts[5] = aurora::Vec3(-x * .2f, -y, z * 2.f);\n  skDoorVerts[6] = aurora::Vec3(-x * .2f, y, 0.f);\n  skDoorVerts[7] = aurora::Vec3(-x * .2f, y, z * 2.f);\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/AutoMapper/CMappableObject.hpp",
    "content": "#pragma once\n\n#include <utility>\n\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n\n#include <zeus/CTransform.hpp>\n\nnamespace metaforce {\nclass CMapWorldInfo;\nclass CStateManager;\n\nclass CMappableObject {\npublic:\n  enum class EMappableObjectType {\n    BlueDoor = 0,\n    ShieldDoor = 1,\n    IceDoor = 2,\n    WaveDoor = 3,\n    PlasmaDoor = 4,\n    BigDoor1 = 5,\n    BigDoor2 = 6,\n    IceDoorCeiling = 7,\n    IceDoorFloor = 8,\n    WaveDoorCeiling = 9,\n    WaveDoorFloor = 10,\n    PlasmaDoorCeiling = 11,\n    PlasmaDoorFloor = 12,\n    IceDoorFloor2 = 13,\n    WaveDoorFloor2 = 14,\n    PlasmaDoorFloor2 = 15,\n    DownArrowYellow = 27, /* Maintenance Tunnel */\n    UpArrowYellow = 28,   /* Phazon Processing Center */\n    DownArrowGreen = 29,  /* Elevator A */\n    UpArrowGreen = 30,    /* Elite Control Access */\n    DownArrowRed = 31,    /* Elevator B */\n    UpArrowRed = 32,      /* Fungal Hall Access */\n    TransportLift = 33,\n    SaveStation = 34,\n    MissileStation = 37\n  };\n\n  enum class EVisMode { Always, MapStationOrVisit, Visit, Never, MapStationOrVisit2 };\n\nprivate:\n  EMappableObjectType x0_type;\n  EVisMode x4_visibilityMode;\n  TEditorId x8_objId;\n  u32 xc_;\n  zeus::CTransform x10_transform;\n\n  zeus::CTransform AdjustTransformForType() const;\n  std::pair<zeus::CColor, zeus::CColor> GetDoorColors(int idx, const CMapWorldInfo& mwInfo, float alpha) const;\n\npublic:\n  explicit CMappableObject(const void* buf);\n  CMappableObject(CMappableObject&&) = default;\n  void PostConstruct(const void*);\n  const zeus::CTransform& GetTransform() const { return x10_transform; }\n  EMappableObjectType GetType() const { return x0_type; }\n  void Draw(int, const CMapWorldInfo&, float, bool);\n  void DrawDoorSurface(int curArea, const CMapWorldInfo& mwInfo, float alpha, int surfIdx, bool needsVtxLoad);\n  zeus::CVector3f BuildSurfaceCenterPoint(int surfIdx) const;\n  bool IsDoorConnectedToArea(int idx, const CStateManager&) const;\n  bool IsDoorConnectedToVisitedArea(const CStateManager&) const;\n  bool GetIsVisibleToAutoMapper(bool worldVis, const CMapWorldInfo& mwInfo) const;\n  bool GetIsSeen() const;\n\n  static void ReadAutoMapperTweaks(const ITweakAutoMapper&);\n  static bool GetTweakIsMapVisibilityCheat();\n  static bool IsDoorType(EMappableObjectType type) {\n    return type >= EMappableObjectType::BlueDoor && type <= EMappableObjectType::PlasmaDoorFloor2;\n  }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CArchitectureMessage.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Input/CFinalInput.hpp\"\n\nnamespace metaforce {\nclass CIOWin;\n\nenum class EArchMsgTarget {\n  IOWinManager = 0,\n  Game = 1,\n};\n\nenum class EArchMsgType {\n  RemoveIOWin = 0,\n  CreateIOWin = 1,\n  ChangeIOWinPriority = 2,\n  RemoveAllIOWins = 3,\n  TimerTick = 4,\n  UserInput = 5,\n  SetGameState = 6,\n  ControllerStatus = 7,\n  QuitGameplay = 8,\n  FrameBegin = 10,\n  FrameEnd = 11,\n};\n\nstruct IArchMsgParm {\n  virtual ~IArchMsgParm() = default;\n};\n\nstruct CArchMsgParmInt32 : IArchMsgParm {\n  u32 x4_parm;\n  CArchMsgParmInt32(u32 parm) : x4_parm(parm) {}\n};\n\nstruct CArchMsgParmVoidPtr : IArchMsgParm {\n  void* x4_parm1;\n  CArchMsgParmVoidPtr(void* parm1) : x4_parm1(parm1) {}\n};\n\nstruct CArchMsgParmInt32Int32VoidPtr : IArchMsgParm {\n  u32 x4_parm1;\n  u32 x8_parm2;\n  void* xc_parm3;\n  CArchMsgParmInt32Int32VoidPtr(u32 parm1, u32 parm2, void* parm3)\n  : x4_parm1(parm1), x8_parm2(parm2), xc_parm3(parm3) {}\n};\n\nstruct CArchMsgParmInt32Int32IOWin : IArchMsgParm {\n  u32 x4_parm1;\n  u32 x8_parm2;\n  std::shared_ptr<CIOWin> xc_parm3;\n  CArchMsgParmInt32Int32IOWin(u32 parm1, u32 parm2, std::shared_ptr<CIOWin>&& parm3)\n  : x4_parm1(parm1), x8_parm2(parm2), xc_parm3(std::move(parm3)) {}\n};\n\nstruct CArchMsgParmNull : IArchMsgParm {};\n\nstruct CArchMsgParmReal32 : IArchMsgParm {\n  float x4_parm;\n  CArchMsgParmReal32(float parm) : x4_parm(parm) {}\n};\n\nstruct CArchMsgParmUserInput : IArchMsgParm {\n  CFinalInput x4_parm;\n  CArchMsgParmUserInput(const CFinalInput& parm) : x4_parm(parm) {}\n};\n\nstruct CArchMsgParmControllerStatus : IArchMsgParm {\n  u16 x4_parm1;\n  bool x6_parm2;\n  CArchMsgParmControllerStatus(u16 a, bool b) : x4_parm1(a), x6_parm2(b) {}\n};\n\nclass CArchitectureMessage {\n  EArchMsgTarget x0_target;\n  EArchMsgType x4_type;\n  std::shared_ptr<IArchMsgParm> x8_parm;\n\npublic:\n  CArchitectureMessage(EArchMsgTarget target, EArchMsgType type, std::shared_ptr<IArchMsgParm>&& parm)\n  : x0_target(target), x4_type(type), x8_parm(std::move(parm)) {}\n\n  EArchMsgTarget GetTarget() const { return x0_target; }\n  EArchMsgType GetType() const { return x4_type; }\n  template <class T>\n  const T* GetParm() const {\n    return static_cast<T*>(x8_parm.get());\n  }\n};\n\nclass MakeMsg {\npublic:\n  static CArchitectureMessage CreateQuitGameplay(EArchMsgTarget target) {\n    return CArchitectureMessage(target, EArchMsgType::QuitGameplay, std::make_shared<CArchMsgParmNull>());\n  }\n  static CArchitectureMessage CreateControllerStatus(EArchMsgTarget target, u16 a, bool b) {\n    return CArchitectureMessage(target, EArchMsgType::ControllerStatus,\n                                std::make_shared<CArchMsgParmControllerStatus>(a, b));\n  }\n  static const CArchMsgParmInt32& GetParmNewGameflowState(const CArchitectureMessage& msg) {\n    return *msg.GetParm<CArchMsgParmInt32>();\n  }\n  static const CArchMsgParmUserInput& GetParmUserInput(const CArchitectureMessage& msg) {\n    return *msg.GetParm<CArchMsgParmUserInput>();\n  }\n  static CArchitectureMessage CreateUserInput(EArchMsgTarget target, const CFinalInput& input) {\n    return CArchitectureMessage(target, EArchMsgType::UserInput, std::make_shared<CArchMsgParmUserInput>(input));\n  }\n  static const CArchMsgParmReal32& GetParmTimerTick(const CArchitectureMessage& msg) {\n    return *msg.GetParm<CArchMsgParmReal32>();\n  }\n  static CArchitectureMessage CreateTimerTick(EArchMsgTarget target, float val) {\n    return CArchitectureMessage(target, EArchMsgType::TimerTick, std::make_shared<CArchMsgParmReal32>(val));\n  }\n  static const CArchMsgParmInt32Int32VoidPtr& GetParmChangeIOWinPriority(const CArchitectureMessage& msg) {\n    return *msg.GetParm<CArchMsgParmInt32Int32VoidPtr>();\n  }\n  static const CArchMsgParmInt32Int32IOWin& GetParmCreateIOWin(const CArchitectureMessage& msg) {\n    return *msg.GetParm<CArchMsgParmInt32Int32IOWin>();\n  }\n  static CArchitectureMessage CreateCreateIOWin(EArchMsgTarget target, int pmin, int pmax,\n                                                std::shared_ptr<CIOWin>&& iowin) {\n    return CArchitectureMessage(target, EArchMsgType::CreateIOWin,\n                                std::make_shared<CArchMsgParmInt32Int32IOWin>(pmin, pmax, std::move(iowin)));\n  }\n  static const CArchMsgParmVoidPtr& GetParmDeleteIOWin(const CArchitectureMessage& msg) {\n    return *msg.GetParm<CArchMsgParmVoidPtr>();\n  }\n  static CArchitectureMessage CreateFrameBegin(EArchMsgTarget target, s32 a) {\n    return CArchitectureMessage(target, EArchMsgType::FrameBegin, std::make_shared<CArchMsgParmInt32>(a));\n  }\n  static CArchitectureMessage CreateFrameEnd(EArchMsgTarget target, s32 a) {\n    return CArchitectureMessage(target, EArchMsgType::FrameEnd, std::make_shared<CArchMsgParmInt32>(a));\n  }\n  /* URDE Messages */\n  static CArchitectureMessage CreateRemoveAllIOWins(EArchMsgTarget target) {\n    return CArchitectureMessage(target, EArchMsgType::RemoveAllIOWins, std::make_shared<CArchMsgParmNull>());\n  }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CArchitectureQueue.hpp",
    "content": "#pragma once\n\n#include <list>\n#include \"Runtime/CArchitectureMessage.hpp\"\n\nnamespace metaforce {\n\nclass CArchitectureQueue {\n  std::list<CArchitectureMessage> m_list;\n\npublic:\n  void Push(CArchitectureMessage&& msg) { m_list.push_back(std::move(msg)); }\n  CArchitectureMessage Pop() {\n    CArchitectureMessage msg = std::move(m_list.front());\n    m_list.pop_front();\n    return msg;\n  }\n  void Clear() { m_list.clear(); }\n  explicit operator bool() const { return !m_list.empty(); }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CBasics.hpp",
    "content": "#pragma once\n\n#include <chrono>\n#include <cstdint>\n#include <cstdlib>\n#include <algorithm>\n#include <string>\n#ifndef _WIN32\n#include <sys/stat.h>\n#else\nstruct _stat64;\n#endif\n\n#include \"Runtime/GCNTypes.hpp\"\n\nnamespace metaforce {\n\nusing OSTime = s64;\n\nstruct OSCalendarTime {\n  int x0_sec;   // seconds after the minute [0, 61]\n  int x4_min;   // minutes after the hour [0, 59]\n  int x8_hour;  // hours since midnight [0, 23]\n  int xc_mday;  // day of the month [1, 31]\n  int x10_mon;  // month since January [0, 11]\n  int x14_year; // years in AD [1, ...]\n  int x18_wday; // days since Sunday [0, 6]\n  int x1c_yday; // days since January 1 [0, 365]\n\n  int x20_msec; // milliseconds after the second [0,999]\n  int x24_usec; // microseconds after the millisecond [0,999]\n};\n\nclass CBasics {\npublic:\n#if _WIN32\n  using Sstat = struct ::_stat64;\n#else\n  using Sstat = struct stat;\n#endif\n\n  static void Initialize();\n\n  static const u64 SECONDS_TO_2000;\n  static const u64 TICKS_PER_SECOND;\n\n  static OSTime ToWiiTime(std::chrono::system_clock::time_point time);\n  static std::chrono::system_clock::time_point FromWiiTime(OSTime wiiTime);\n\n  static OSTime GetTime() { return ToWiiTime(std::chrono::system_clock::now()); }\n  static u64 GetGCTicks();\n  static constexpr u64 GetGCTicksPerSec() { return 486000000ull; }\n\n  static OSCalendarTime ToCalendarTime(OSTime time) { return ToCalendarTime(FromWiiTime(time)); }\n  static OSCalendarTime ToCalendarTime(std::chrono::system_clock::time_point time);\n  static u16 SwapBytes(u16 v);\n  static u32 SwapBytes(u32 v);\n  static u64 SwapBytes(u64 v);\n  static s16 SwapBytes(s16 v);\n  static s32 SwapBytes(s32 v);\n  static s64 SwapBytes(s64 v);\n  static float SwapBytes(float v);\n  static double SwapBytes(double s);\n  static void Swap2Bytes(u8* v);\n  static void Swap4Bytes(u8* v);\n  static void Swap8Bytes(u8* v);\n  static int RecursiveMakeDir(const char* dir);\n  static void MakeDir(const char* dir);\n  static bool IsDir(const char* path);\n  static bool IsFile(const char* path);\n  static int Stat(const char* path, Sstat* statOut);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CBasicsPC.cpp",
    "content": "#ifndef _WIN32\n#include <sys/time.h>\n#include <unistd.h>\n#if __APPLE__\n#include <mach/mach_time.h>\n#endif\n#endif\n\n#include <algorithm>\n#include <cstdarg>\n#include <cstdio>\n#include <cstring>\n#include <ctime>\n#ifdef _WIN32\n#ifndef WIN32_LEAN_AND_MEAN\n#define WIN32_LEAN_AND_MEAN\n#endif\n#ifndef NOMINMAX\n#define NOMINMAX\n#endif\n#include <Windows.h>\n#include <nowide/stackstring.hpp>\n#ifndef _WIN32_IE\n#define _WIN32_IE 0x0400\n#endif\n#include <ShlObj.h>\n#if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG)\n#define S_ISREG(m) (((m)&S_IFMT) == S_IFREG)\n#endif\n#if !defined(S_ISDIR) && defined(S_IFMT) && defined(S_IFDIR)\n#define S_ISDIR(m) (((m)&S_IFMT) == S_IFDIR)\n#endif\n#endif\n\n#include \"Runtime/CBasics.hpp\"\n#include \"Runtime/CStopwatch.hpp\"\n#include \"Runtime/Logging.hpp\"\n\n#if __APPLE__\nstatic u64 MachToDolphinNum;\nstatic u64 MachToDolphinDenom;\n#elif _WIN32\nstatic LARGE_INTEGER PerfFrequency;\n#endif\n\nnamespace metaforce {\nvoid CBasics::Initialize() {\n  CStopwatch::InitGlobalTimer();\n#if __APPLE__\n  mach_timebase_info_data_t timebase;\n  mach_timebase_info(&timebase);\n  MachToDolphinNum = GetGCTicksPerSec() * timebase.numer;\n  MachToDolphinDenom = 1000000000ull * timebase.denom;\n#elif _WIN32\n  QueryPerformanceFrequency(&PerfFrequency);\n#endif\n}\n\nu64 CBasics::GetGCTicks() {\n#if __APPLE__\n  return mach_absolute_time() * MachToDolphinNum / MachToDolphinDenom;\n#elif __linux__ || __FreeBSD__\n  struct timespec tp;\n  clock_gettime(CLOCK_MONOTONIC, &tp);\n\n  return u64((tp.tv_sec * 1000000000ull) + tp.tv_nsec) * GetGCTicksPerSec() / 1000000000ull;\n#elif _WIN32\n  LARGE_INTEGER perf;\n  QueryPerformanceCounter(&perf);\n  perf.QuadPart *= GetGCTicksPerSec();\n  perf.QuadPart /= PerfFrequency.QuadPart;\n  return perf.QuadPart;\n#else\n  return 0;\n#endif\n}\n\nconst u64 CBasics::SECONDS_TO_2000 = 946684800LL;\nconst u64 CBasics::TICKS_PER_SECOND = 60750000LL;\n\nstatic struct tm* localtime_r(const time_t& time, struct tm& timeSt, long& gmtOff) {\n#ifndef _WIN32\n  auto ret = ::localtime_r(&time, &timeSt);\n  if (!ret)\n    return nullptr;\n  gmtOff = ret->tm_gmtoff;\n  return ret;\n#else\n  struct tm _gmSt;\n  auto reta = localtime_s(&timeSt, &time);\n  auto retb = gmtime_s(&_gmSt, &time);\n  if (reta || retb)\n    return nullptr;\n  gmtOff = mktime(&timeSt) - mktime(&_gmSt);\n  return &timeSt;\n#endif\n}\n\nOSTime CBasics::ToWiiTime(std::chrono::system_clock::time_point time) {\n  auto sec = std::chrono::time_point_cast<std::chrono::seconds>(time);\n  auto us = std::chrono::duration_cast<std::chrono::microseconds>((time - sec)).count();\n  time_t sysTime = std::chrono::system_clock::to_time_t(sec);\n\n  struct tm _timeSt;\n  long gmtOff;\n  struct tm* timeSt = localtime_r(sysTime, _timeSt, gmtOff);\n  if (!timeSt)\n    return 0;\n\n  /* Returning local */\n  return OSTime(TICKS_PER_SECOND * ((sysTime + gmtOff) - SECONDS_TO_2000) + us * TICKS_PER_SECOND / 1000000);\n}\n\nstd::chrono::system_clock::time_point CBasics::FromWiiTime(OSTime wiiTime) {\n  auto div = std::lldiv(SECONDS_TO_2000 + wiiTime, TICKS_PER_SECOND);\n  time_t time = time_t(div.quot);\n\n  time_t sysTime = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());\n  struct tm _timeSt;\n  long gmtOff;\n  struct tm* timeSt = localtime_r(sysTime, _timeSt, gmtOff);\n  if (!timeSt)\n    return std::chrono::system_clock::from_time_t(0);\n\n  /* Returning GMT */\n  return std::chrono::system_clock::from_time_t(time - gmtOff) +\n         std::chrono::microseconds(div.rem * 1000000 / TICKS_PER_SECOND);\n}\n\nOSCalendarTime CBasics::ToCalendarTime(std::chrono::system_clock::time_point time) {\n  OSCalendarTime ret;\n\n  auto sec = std::chrono::time_point_cast<std::chrono::seconds>(time);\n  auto us = std::chrono::duration_cast<std::chrono::microseconds>((time - sec)).count();\n  time_t sysTime = std::chrono::system_clock::to_time_t(sec);\n  struct tm _timeSt;\n  long gmtOff;\n  struct tm* timeSt = localtime_r(sysTime, _timeSt, gmtOff);\n  if (!timeSt)\n    return {};\n\n  ret.x0_sec = timeSt->tm_sec;\n  ret.x4_min = timeSt->tm_min;\n  ret.x8_hour = timeSt->tm_hour;\n  ret.xc_mday = timeSt->tm_mday;\n  ret.x10_mon = timeSt->tm_mon;\n  ret.x14_year = timeSt->tm_year + 1900;\n  ret.x18_wday = timeSt->tm_wday;\n  ret.x1c_yday = timeSt->tm_yday;\n\n  auto div = std::ldiv(us, 1000);\n  ret.x20_msec = div.quot;\n  ret.x24_usec = div.rem;\n\n  return ret;\n}\n\nu16 CBasics::SwapBytes(u16 v) {\n  Swap2Bytes(reinterpret_cast<u8*>(&v));\n  return v;\n}\n\nu32 CBasics::SwapBytes(u32 v) {\n  Swap4Bytes(reinterpret_cast<u8*>(&v));\n  return v;\n}\n\nu64 CBasics::SwapBytes(u64 v) {\n  Swap8Bytes(reinterpret_cast<u8*>(&v));\n  return v;\n}\n\ns16 CBasics::SwapBytes(s16 v) {\n  Swap2Bytes(reinterpret_cast<u8*>(&v));\n  return v;\n}\n\ns32 CBasics::SwapBytes(s32 v) {\n  Swap4Bytes(reinterpret_cast<u8*>(&v));\n  return v;\n}\n\ns64 CBasics::SwapBytes(s64 v) {\n  Swap8Bytes(reinterpret_cast<u8*>(&v));\n  return v;\n}\n\nfloat CBasics::SwapBytes(float v) {\n  Swap4Bytes(reinterpret_cast<u8*>(&v));\n  return v;\n}\n\ndouble CBasics::SwapBytes(double v) {\n  Swap8Bytes(reinterpret_cast<u8*>(&v));\n  return v;\n}\n\nvoid CBasics::Swap2Bytes(u8* v) {\n  u16* val = reinterpret_cast<u16*>(v);\n#if __GNUC__\n  *val = __builtin_bswap16(*val);\n#elif _WIN32\n  *val = _byteswap_ushort(*val);\n#else\n  *val = (*val << 8) | ((*val >> 8) & 0xFF);\n#endif\n}\n\nvoid CBasics::Swap4Bytes(u8* v) {\n  u32* val = reinterpret_cast<u32*>(v);\n#if __GNUC__\n  *val = __builtin_bswap32(*val);\n#elif _WIN32\n  *val = _byteswap_ulong(*val);\n#else\n  *val = ((*val & 0x0000FFFF) << 16) | ((*val & 0xFFFF0000) >> 16) | ((*val & 0x00FF00FF) << 8) |\n         ((*val & 0xFF00FF00) >> 8);\n#endif\n}\n\nvoid CBasics::Swap8Bytes(u8* v) {\n  u64* val = reinterpret_cast<u64*>(v);\n#if __GNUC__\n  *val = __builtin_bswap64(*val);\n#elif _WIN32\n  *val = _byteswap_uint64(*val);\n#else\n  *val = ((val & 0xFF00000000000000ULL) >> 56) | ((val & 0x00FF000000000000ULL) >> 40) |\n         ((val & 0x0000FF0000000000ULL) >> 24) | ((val & 0x000000FF00000000ULL) >> 8) |\n         ((val & 0x00000000FF000000ULL) << 8) | ((val & 0x0000000000FF0000ULL) << 24) |\n         ((val & 0x000000000000FF00ULL) << 40) | ((val & 0x00000000000000FFULL) << 56);\n#endif\n}\n\nint CBasics::Stat(const char* path, Sstat* statOut) {\n#if _WIN32\n  size_t pos;\n  const nowide::wstackstring wpath(path);\n  const wchar_t* wpathP = wpath.get();\n  for (pos = 0; pos < 3 && wpathP[pos] != L'\\0'; ++pos) {}\n  if (pos == 2 && wpathP[1] == L':') {\n    wchar_t fixPath[4] = {wpathP[0], L':', L'/', L'\\0'};\n    return _wstat64(fixPath, statOut);\n  }\n  return _wstat64(wpath.get(), statOut);\n#else\n  return stat(path, statOut);\n#endif\n}\n\n/* recursive mkdir */\nint CBasics::RecursiveMakeDir(const char* dir) {\n#if _WIN32\n  char tmp[1024];\n\n  /* copy path */\n  std::strncpy(tmp, dir, std::size(tmp));\n  const size_t len = std::strlen(tmp);\n  if (len >= std::size(tmp)) {\n    return -1;\n  }\n\n  /* remove trailing slash */\n  if (tmp[len - 1] == '/' || tmp[len - 1] == '\\\\') {\n    tmp[len - 1] = 0;\n  }\n\n  /* recursive mkdir */\n  char* p = nullptr;\n  Sstat sb;\n  for (p = tmp + 1; *p; p++) {\n    if (*p == '/' || *p == '\\\\') {\n      *p = 0;\n      /* test path */\n      if (Stat(tmp, &sb) != 0) {\n        /* path does not exist - create directory */\n        const nowide::wstackstring wtmp(tmp);\n        if (!CreateDirectoryW(wtmp.get(), nullptr)) {\n          return -1;\n        }\n      } else if (!S_ISDIR(sb.st_mode)) {\n        /* not a directory */\n        return -1;\n      }\n      *p = '/';\n    }\n  }\n  /* test path */\n  if (Stat(tmp, &sb) != 0) {\n    /* path does not exist - create directory */\n    const nowide::wstackstring wtmp(tmp);\n    if (!CreateDirectoryW(wtmp.get(), nullptr)) {\n      return -1;\n    }\n  } else if (!S_ISDIR(sb.st_mode)) {\n    /* not a directory */\n    return -1;\n  }\n  return 0;\n#else\n  char tmp[1024];\n\n  /* copy path */\n  std::memset(tmp, 0, std::size(tmp));\n  std::strncpy(tmp, dir, std::size(tmp) - 1);\n  const size_t len = std::strlen(tmp);\n  if (len >= std::size(tmp)) {\n    return -1;\n  }\n\n  /* remove trailing slash */\n  if (tmp[len - 1] == '/') {\n    tmp[len - 1] = 0;\n  }\n\n  /* recursive mkdir */\n  char* p = nullptr;\n  Sstat sb;\n  for (p = tmp + 1; *p; p++) {\n    if (*p == '/') {\n      *p = 0;\n      /* test path */\n      if (Stat(tmp, &sb) != 0) {\n        /* path does not exist - create directory */\n        if (mkdir(tmp, 0755) < 0) {\n          return -1;\n        }\n      } else if (!S_ISDIR(sb.st_mode)) {\n        /* not a directory */\n        return -1;\n      }\n      *p = '/';\n    }\n  }\n  /* test path */\n  if (Stat(tmp, &sb) != 0) {\n    /* path does not exist - create directory */\n    if (mkdir(tmp, 0755) < 0) {\n      return -1;\n    }\n  } else if (!S_ISDIR(sb.st_mode)) {\n    /* not a directory */\n    return -1;\n  }\n  return 0;\n#endif\n}\n\nvoid CBasics::MakeDir(const char* dir) {\n#if _WIN32\n  HRESULT err;\n  const nowide::wstackstring wdir(dir);\n  if (!CreateDirectoryW(wdir.get(), NULL))\n    if ((err = GetLastError()) != ERROR_ALREADY_EXISTS)\n      spdlog::fatal(\"MakeDir({})\", dir);\n#else\n  if (mkdir(dir, 0755))\n    if (errno != EEXIST)\n      spdlog::fatal(\"MakeDir({}): {}\", dir, strerror(errno));\n#endif\n}\n\nbool CBasics::IsDir(const char* path) {\n  Sstat  theStat;\n  Stat(path, &theStat);\n\n  return S_ISDIR(theStat.st_mode);\n}\n\nbool CBasics::IsFile(const char* path) {\n  Sstat  theStat;\n  Stat(path, &theStat);\n\n  return S_ISREG(theStat.st_mode);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CCRC32.cpp",
    "content": "#include \"Runtime/CCRC32.hpp\"\n\n#include <array>\n\nnamespace metaforce {\nnamespace {\nconstexpr std::array<uint32_t, 256> crc32Table{\n    0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832,\n    0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2,\n    0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A,\n    0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,\n    0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3,\n    0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423,\n    0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB,\n    0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,\n    0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4,\n    0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950,\n    0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074,\n    0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0,\n    0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525,\n    0x206F85B3, 0xB966D409, 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81,\n    0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615,\n    0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,\n    0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76,\n    0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E,\n    0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6,\n    0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,\n    0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7,\n    0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F,\n    0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7,\n    0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,\n    0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278,\n    0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC,\n    0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330,\n    0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,\n    0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D,\n};\n\nconstexpr uint32_t permute(uint32_t checksum, uint8_t b) { return (checksum >> 8) ^ crc32Table[(checksum & 0xFF) ^ b]; }\n} // Anonymous namespace\n\nuint32_t CCRC32::Calculate(const void* data, uint32_t length) {\n  if (data == nullptr || length == 0) {\n    return 0;\n  }\n\n  uint32_t checksum = 0xFFFFFFFF;\n  const uint8_t* buf = static_cast<const uint8_t*>(data);\n  uint32_t words = length / 4;\n  while ((words--) > 0) {\n    checksum = permute(checksum, *buf++);\n    checksum = permute(checksum, *buf++);\n    checksum = permute(checksum, *buf++);\n    checksum = permute(checksum, *buf++);\n  }\n\n  uint32_t rem = length % 4;\n  while ((rem--) > 0) {\n    checksum = permute(checksum, *buf++);\n  }\n\n  return checksum;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CCRC32.hpp",
    "content": "#pragma once\n\n#include <cstdint>\n\nnamespace metaforce {\n\nclass CCRC32 {\npublic:\n  static uint32_t Calculate(const void* data, uint32_t length);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CDependencyGroup.cpp",
    "content": "#include \"Runtime/CDependencyGroup.hpp\"\n#include \"Runtime/CToken.hpp\"\n\nnamespace metaforce {\nCDependencyGroup::CDependencyGroup(CInputStream& in) { ReadFromStream(in); }\n\nvoid CDependencyGroup::ReadFromStream(CInputStream& in) {\n  u32 depCount = in.ReadLong();\n  x0_objectTags.reserve(depCount);\n  for (u32 i = 0; i < depCount; i++)\n    x0_objectTags.emplace_back(in);\n}\n\nCFactoryFnReturn FDependencyGroupFactory([[maybe_unused]] const SObjectTag& tag, CInputStream& in,\n                                         [[maybe_unused]] const CVParamTransfer& param,\n                                         [[maybe_unused]] CObjectReference* selfRef) {\n  return TToken<CDependencyGroup>::GetIObjObjectFor(std::make_unique<CDependencyGroup>(in));\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CDependencyGroup.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include \"Runtime/CFactoryMgr.hpp\"\n\nnamespace metaforce {\nclass CDependencyGroup {\n  std::vector<SObjectTag> x0_objectTags;\n\npublic:\n  explicit CDependencyGroup(CInputStream& in);\n  void ReadFromStream(CInputStream& in);\n  const std::vector<SObjectTag>& GetObjectTagVector() const { return x0_objectTags; }\n};\n\nCFactoryFnReturn FDependencyGroupFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& param,\n                                         CObjectReference* selfRef);\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CDvdFile.cpp",
    "content": "#include \"Runtime/CDvdFile.hpp\"\n\n// #include <optick.h>\n\n#include <SDL3/SDL_error.h>\n#include <SDL3/SDL_iostream.h>\n\n#include <cstring>\n#include <limits>\n#include <new>\n\n#include \"Runtime/CDvdRequest.hpp\"\n#include \"Runtime/Logging.hpp\"\n#include \"Runtime/CStopwatch.hpp\"\n\nnamespace metaforce {\nnamespace {\n\nstruct SDLDiscStreamCtx {\n  SDL_IOStream* io = nullptr;\n};\n\nint64_t sdlStreamReadAt(void* userData, uint64_t offset, void* out, size_t len) {\n  auto* ctx = static_cast<SDLDiscStreamCtx*>(userData);\n  if (ctx == nullptr || ctx->io == nullptr || offset > uint64_t(std::numeric_limits<int64_t>::max())) {\n    return -1;\n  }\n\n  if (SDL_SeekIO(ctx->io, static_cast<Sint64>(offset), SDL_IO_SEEK_SET) < 0) {\n    return -1;\n  }\n\n  size_t total = 0;\n  auto* dst = static_cast<Uint8*>(out);\n  while (total < len) {\n    const size_t read = SDL_ReadIO(ctx->io, dst + total, len - total);\n    if (read == 0) {\n      break;\n    }\n    total += read;\n  }\n  return static_cast<int64_t>(total);\n}\n\nint64_t sdlStreamLen(void* userData) {\n  auto* ctx = static_cast<SDLDiscStreamCtx*>(userData);\n  if (ctx == nullptr || ctx->io == nullptr) {\n    return -1;\n  }\n  const Sint64 size = SDL_GetIOSize(ctx->io);\n  return size < 0 ? -1 : static_cast<int64_t>(size);\n}\n\nvoid sdlStreamClose(void* userData) {\n  auto* ctx = static_cast<SDLDiscStreamCtx*>(userData);\n  if (ctx == nullptr) {\n    return;\n  }\n  if (ctx->io != nullptr) {\n    SDL_CloseIO(ctx->io);\n  }\n  delete ctx;\n}\n\nu32 nodReadLoop(NodHandle* reader, void* buf, u32 len) {\n  if (reader == nullptr || buf == nullptr || len == 0) {\n    return 0;\n  }\n\n  auto* out = static_cast<uint8_t*>(buf);\n  u32 totalRead = 0;\n  while (totalRead < len) {\n    const u32 remaining = len - totalRead;\n    const int64_t read = nod_read(reader, out + totalRead, remaining);\n    if (read <= 0) {\n      break;\n    }\n    if (read > int64_t(remaining)) {\n      totalRead = len;\n      break;\n    }\n    totalRead += u32(read);\n  }\n\n  return totalRead;\n}\n\n} // namespace\n\nCDvdFile::NodHandleUnique CDvdFile::m_DvdRoot{nullptr, nod_free};\nCDvdFile::NodHandleUnique CDvdFile::m_DataPartition{nullptr, nod_free};\nstd::unordered_map<std::string, CDvdFile::SFileEntry> CDvdFile::m_FileEntries;\n\nclass CFileDvdRequest : public IDvdRequest {\n  std::shared_ptr<NodHandle> m_reader;\n  uint64_t m_begin;\n  uint64_t m_size;\n\n  void* m_buf;\n  u32 m_len;\n  ESeekOrigin m_whence;\n  int m_offset;\n\n  bool m_cancel = false;\n  bool m_complete = false;\n\n  std::function<void(u32)> m_callback;\n\npublic:\n  ~CFileDvdRequest() override { CFileDvdRequest::PostCancelRequest(); }\n\n  void WaitUntilComplete() override {\n\n    if (!m_complete && !m_cancel) {\n      CDvdFile::DoWork();\n    }\n  }\n  bool IsComplete() override {\n\n    if (!m_complete) {\n      CDvdFile::DoWork();\n    }\n    return m_complete;\n  }\n  void PostCancelRequest() override { m_cancel = true; }\n\n  [[nodiscard]] EMediaType GetMediaType() const override { return EMediaType::File; }\n\n  CFileDvdRequest(CDvdFile& file, void* buf, u32 len, ESeekOrigin whence, int off, std::function<void(u32)>&& cb)\n  : m_reader(file.m_reader)\n  , m_begin(file.m_begin)\n  , m_size(file.m_size)\n  , m_buf(buf)\n  , m_len(len)\n  , m_whence(whence)\n  , m_offset(off)\n  , m_callback(std::move(cb)) {}\n\n  void DoRequest() {\n    if (m_cancel) {\n      return;\n    }\n\n    if (!m_reader) {\n\n      m_complete = true;\n      if (m_callback) {\n        m_callback(0);\n      }\n      return;\n    }\n\n    u32 readLen = 0;\n    if (m_whence == ESeekOrigin::Cur && m_offset == 0) {\n      readLen = nodReadLoop(m_reader.get(), m_buf, m_len);\n    } else {\n      int seek = 0;\n      int64_t offset = m_offset;\n      switch (m_whence) {\n      case ESeekOrigin::Begin: {\n        seek = 0;\n        offset += int64_t(m_begin);\n        break;\n      }\n      case ESeekOrigin::End: {\n        seek = 0;\n        offset += int64_t(m_begin) + int64_t(m_size);\n        break;\n      }\n      case ESeekOrigin::Cur: {\n        seek = 1;\n        break;\n      }\n      };\n      if (nod_seek(m_reader.get(), offset, seek) >= 0) {\n        readLen = nodReadLoop(m_reader.get(), m_buf, m_len);\n      }\n    }\n\n    if (m_callback) {\n      m_callback(readLen);\n    }\n    m_complete = true;\n  }\n};\n\nstd::vector<std::shared_ptr<IDvdRequest>> CDvdFile::m_RequestQueue;\nstd::string CDvdFile::m_rootDirectory;\nstd::string CDvdFile::m_lastError;\nstd::unique_ptr<u8[]> CDvdFile::m_dolBuf;\nsize_t CDvdFile::m_dolBufLen = 0;\n\nCDvdFile::CDvdFile(std::string_view path) : x18_path(path) {\n  const SFileEntry* entry = ResolvePath(path);\n  if (entry == nullptr) {\n    return;\n  }\n\n  NodHandle* fileRaw = nullptr;\n  if (nod_partition_open_file(m_DataPartition.get(), entry->fstIndex, &fileRaw) == NOD_RESULT_OK &&\n      fileRaw != nullptr) {\n    m_reader = std::shared_ptr<NodHandle>(fileRaw, nod_free);\n    m_size = entry->size;\n  }\n}\n\n// single-threaded hack\nvoid CDvdFile::DoWork() {\n  for (std::shared_ptr<IDvdRequest>& req : m_RequestQueue) {\n    auto& concreteReq = static_cast<CFileDvdRequest&>(*req);\n    concreteReq.DoRequest();\n  }\n  m_RequestQueue.clear();\n}\n\nstd::shared_ptr<IDvdRequest> CDvdFile::AsyncSeekRead(void* buf, u32 len, ESeekOrigin whence, int off,\n                                                     std::function<void(u32)>&& cb) {\n  std::shared_ptr<IDvdRequest> ret = std::make_shared<CFileDvdRequest>(*this, buf, len, whence, off, std::move(cb));\n  m_RequestQueue.emplace_back(ret);\n  return ret;\n}\n\nu32 CDvdFile::SyncSeekRead(void* buf, u32 len, ESeekOrigin whence, int offset) {\n  if (!m_reader) {\n    return 0;\n  }\n\n  int seek = 0;\n  int64_t seekOffset = offset;\n  switch (whence) {\n  case ESeekOrigin::Begin: {\n    seek = 0;\n    seekOffset += int64_t(m_begin);\n    break;\n  }\n  case ESeekOrigin::End: {\n    seek = 0;\n    seekOffset += int64_t(m_begin) + int64_t(m_size);\n    break;\n  }\n  case ESeekOrigin::Cur: {\n    seek = 1;\n    break;\n  }\n  };\n\n  if (nod_seek(m_reader.get(), seekOffset, seek) < 0) {\n    return 0;\n  }\n  return nodReadLoop(m_reader.get(), buf, len);\n}\n\nu32 CDvdFile::SyncRead(void* buf, u32 len) {\n  if (!m_reader) {\n    return 0;\n  }\n  return nodReadLoop(m_reader.get(), buf, len);\n}\n\nstd::string CDvdFile::NormalizePath(std::string_view path) {\n  std::string out;\n  out.reserve(path.size());\n\n  bool prevSlash = false;\n  for (char c : path) {\n    if (c == '/' || c == '\\\\') {\n      if (!out.empty() && !prevSlash) {\n        out.push_back('/');\n      }\n      prevSlash = true;\n      continue;\n    }\n\n    out.push_back(static_cast<char>(std::tolower(static_cast<unsigned char>(c))));\n    prevSlash = false;\n  }\n\n  if (!out.empty() && out.back() == '/') {\n    out.pop_back();\n  }\n  return out;\n}\n\nconst CDvdFile::SFileEntry* CDvdFile::ResolvePath(std::string_view path) {\n  if (!m_DataPartition) {\n    return nullptr;\n  }\n\n  std::string normalizedPath = NormalizePath(path);\n  if (normalizedPath.empty()) {\n    return nullptr;\n  }\n\n  if (!m_rootDirectory.empty()) {\n    normalizedPath = m_rootDirectory + \"/\" + normalizedPath;\n  }\n\n  const auto search = m_FileEntries.find(normalizedPath);\n  return search != m_FileEntries.end() ? &search->second : nullptr;\n}\n\nbool CDvdFile::BuildFileEntries() {\n  if (!m_DataPartition) {\n    return false;\n  }\n\n  m_FileEntries.clear();\n\n  struct SDirFrame {\n    u32 endIndex = 0;\n    std::string path;\n  };\n\n  struct SFstBuildContext {\n    std::unordered_map<std::string, CDvdFile::SFileEntry>* fileEntries = nullptr;\n    std::vector<SDirFrame> dirStack;\n  } ctx{&m_FileEntries, {}};\n\n  nod_partition_iterate_fst(\n      m_DataPartition.get(),\n      [](u32 index, NodNodeKind kind, const char* name, u32 size, void* userData) -> u32 {\n        auto* ctx = static_cast<SFstBuildContext*>(userData);\n        while (!ctx->dirStack.empty() && index >= ctx->dirStack.back().endIndex) {\n          ctx->dirStack.pop_back();\n        }\n\n        const std::string nodeName =\n            CDvdFile::NormalizePath(name != nullptr ? std::string_view{name} : std::string_view{});\n        if (nodeName.empty()) {\n          return index + 1;\n        }\n\n        std::string fullPath;\n        if (!ctx->dirStack.empty()) {\n          fullPath = ctx->dirStack.back().path;\n          fullPath += '/';\n          fullPath += nodeName;\n        } else {\n          fullPath = nodeName;\n        }\n\n        if (kind == NOD_NODE_KIND_FILE) {\n          ctx->fileEntries->insert_or_assign(fullPath, CDvdFile::SFileEntry{index, size});\n        } else {\n          ctx->dirStack.push_back({size, std::move(fullPath)});\n        }\n        return index + 1;\n      },\n      &ctx);\n\n  return !m_FileEntries.empty();\n}\n\nbool CDvdFile::LoadDolBuf() {\n  if (!m_DataPartition) {\n    return false;\n  }\n\n  NodPartitionMeta meta{};\n  if (nod_partition_meta(m_DataPartition.get(), &meta) != NOD_RESULT_OK || meta.raw_dol.data == nullptr ||\n      meta.raw_dol.size == 0) {\n    return false;\n  }\n\n  auto dolBuf = std::make_unique<u8[]>(meta.raw_dol.size);\n  std::memcpy(dolBuf.get(), meta.raw_dol.data, meta.raw_dol.size);\n  m_dolBuf = std::move(dolBuf);\n  m_dolBufLen = meta.raw_dol.size;\n  return true;\n}\n\nbool CDvdFile::Initialize(const std::string_view& path) {\n  Shutdown();\n  m_lastError.clear();\n\n  std::string pathStr(path);\n  SDL_IOStream* io = SDL_IOFromFile(pathStr.c_str(), \"rb\");\n  if (io == nullptr) {\n    m_lastError = std::string{\"SDL_IOFromFile failed: \"} + SDL_GetError();\n    spdlog::error(\"{} (path: '{}')\", m_lastError, pathStr);\n    return false;\n  }\n\n  auto* streamCtx = new (std::nothrow) SDLDiscStreamCtx{io};\n  if (streamCtx == nullptr) {\n    SDL_CloseIO(io);\n    m_lastError = \"Failed to allocate SDL disc stream context\";\n    spdlog::error(\"{} (path: '{}')\", m_lastError, pathStr);\n    return false;\n  }\n\n  NodHandle* discRaw = nullptr;\n  const NodDiscOptions discOpts{\n      .preloader_threads = 1,\n  };\n  const NodDiscStream stream{\n      .user_data = streamCtx,\n      .read_at = sdlStreamReadAt,\n      .stream_len = sdlStreamLen,\n      .close = sdlStreamClose,\n  };\n  const NodResult discResult = nod_disc_open_stream(&stream, &discOpts, &discRaw);\n  if (discResult != NOD_RESULT_OK || discRaw == nullptr) {\n    const char* nodError = nod_error_message();\n    m_lastError = fmt::format(\"nod_disc_open_stream failed ({}){}\", int(discResult),\n                              nodError != nullptr && nodError[0] != '\\0' ? fmt::format(\": {}\", nodError) : \"\");\n    spdlog::error(\"{} (path: '{}')\", m_lastError, pathStr);\n    // Ownership of streamCtx is transferred to nod_disc_open_stream.\n    // FfiDiscStream drops and invokes close() on failure paths.\n    return false;\n  }\n  m_DvdRoot = NodHandleUnique(discRaw, nod_free);\n\n  NodHandle* partitionRaw = nullptr;\n  const NodResult partitionResult =\n      nod_disc_open_partition_kind(m_DvdRoot.get(), NOD_PARTITION_KIND_DATA, nullptr, &partitionRaw);\n  if (partitionResult != NOD_RESULT_OK || partitionRaw == nullptr) {\n    const char* nodError = nod_error_message();\n    m_lastError = fmt::format(\"nod_disc_open_partition_kind(data) failed ({}){}\", int(partitionResult),\n                              nodError != nullptr && nodError[0] != '\\0' ? fmt::format(\": {}\", nodError) : \"\");\n    spdlog::error(\"{} (path: '{}')\", m_lastError, pathStr);\n    Shutdown();\n    return false;\n  }\n  m_DataPartition = NodHandleUnique(partitionRaw, nod_free);\n\n  if (!BuildFileEntries()) {\n    m_lastError = \"Failed to read disc file-system table\";\n    spdlog::error(\"{} (path: '{}')\", m_lastError, pathStr);\n    Shutdown();\n    return false;\n  }\n  if (!LoadDolBuf()) {\n    m_lastError = \"Failed to load raw DOL data from disc\";\n    spdlog::error(\"{} (path: '{}')\", m_lastError, pathStr);\n    Shutdown();\n    return false;\n  }\n\n  return true;\n}\n\nvoid CDvdFile::Shutdown() {\n  m_RequestQueue.clear();\n  m_FileEntries.clear();\n  m_dolBuf.reset();\n  m_dolBufLen = 0;\n  m_DataPartition.reset();\n  m_DvdRoot.reset();\n}\n\nSDiscInfo CDvdFile::DiscInfo() {\n  SDiscInfo out{};\n  if (!m_DvdRoot) {\n    return out;\n  }\n\n  NodDiscHeader header{};\n  if (nod_disc_header(m_DvdRoot.get(), &header) != NOD_RESULT_OK) {\n    return out;\n  }\n\n  std::memcpy(out.gameId.data(), header.game_id, sizeof(header.game_id));\n  out.version = header.disc_version;\n  const char* titleBegin = header.game_title;\n  const char* titleEnd = std::find(titleBegin, titleBegin + sizeof(header.game_title), '\\0');\n  out.gameTitle.assign(titleBegin, titleEnd);\n  return out;\n}\n\nvoid CDvdFile::SetRootDirectory(const std::string_view& rootDir) { m_rootDirectory = NormalizePath(rootDir); }\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CDvdFile.hpp",
    "content": "#pragma once\n\n#include <atomic>\n#include <array>\n#include <condition_variable>\n#include <functional>\n#include <memory>\n#include <mutex>\n#include <cstddef>\n#include <string>\n#include <string_view>\n#include <thread>\n#include <unordered_map>\n#include <vector>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n\n#include <nod.h>\n\nnamespace metaforce {\n\nenum class ESeekOrigin { Begin = 0, Cur = 1, End = 2 };\n\nstruct DVDFileInfo;\nclass IDvdRequest;\n\nstruct SDiscInfo {\n  std::array<char, 6> gameId;\n  uint8_t version;\n  std::string gameTitle;\n};\n\nclass CDvdFile {\n  friend class CResLoader;\n  friend class CFileDvdRequest;\n  using NodHandleUnique = std::unique_ptr<NodHandle, decltype(&nod_free)>;\n  struct SFileEntry {\n    u32 fstIndex = NOD_FST_STOP;\n    u32 size = 0;\n  };\n\n  static NodHandleUnique m_DvdRoot;\n  static NodHandleUnique m_DataPartition;\n  static std::unordered_map<std::string, SFileEntry> m_FileEntries;\n\n  static std::vector<std::shared_ptr<IDvdRequest>> m_RequestQueue;\n  static std::string m_rootDirectory;\n  static std::string m_lastError;\n  static std::unique_ptr<u8[]> m_dolBuf;\n  static size_t m_dolBufLen;\n\n  std::string x18_path;\n  std::shared_ptr<NodHandle> m_reader;\n  uint64_t m_begin = 0;\n  uint64_t m_size = 0;\n\n  static std::string NormalizePath(std::string_view path);\n  static const SFileEntry* ResolvePath(std::string_view path);\n  static bool BuildFileEntries();\n  static bool LoadDolBuf();\n\npublic:\n  static bool Initialize(const std::string_view& path);\n  static std::string_view GetLastError() { return m_lastError; }\n  static SDiscInfo DiscInfo();\n  static void SetRootDirectory(const std::string_view& rootDir);\n  static void Shutdown();\n  static u8* GetDolBuf() { return m_dolBuf.get(); }\n  static size_t GetDolBufLen() { return m_dolBufLen; }\n  static void DoWork();\n\n  CDvdFile(std::string_view path);\n  operator bool() const { return m_reader.operator bool(); }\n  void UpdateFilePos(int pos) {\n    if (m_reader) {\n      nod_seek(m_reader.get(), pos, 0);\n    }\n  }\n  static bool FileExists(std::string_view path) { return ResolvePath(path) != nullptr; }\n  void CloseFile() { m_reader.reset(); }\n  std::shared_ptr<IDvdRequest> AsyncSeekRead(void* buf, u32 len, ESeekOrigin whence, int off,\n                                             std::function<void(u32)>&& cb = {});\n  u32 SyncSeekRead(void* buf, u32 len, ESeekOrigin whence, int offset);\n  std::shared_ptr<IDvdRequest> AsyncRead(void* buf, u32 len, std::function<void(u32)>&& cb = {}) {\n    return AsyncSeekRead(buf, len, ESeekOrigin::Cur, 0, std::move(cb));\n  }\n  u32 SyncRead(void* buf, u32 len);\n  u64 Length() const { return m_size; }\n  std::string_view GetPath() const { return x18_path; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CDvdRequest.hpp",
    "content": "#pragma once\n\nnamespace metaforce {\n\nclass IDvdRequest {\npublic:\n  virtual ~IDvdRequest() = default;\n\n  virtual void WaitUntilComplete() = 0;\n  virtual bool IsComplete() = 0;\n  virtual void PostCancelRequest() = 0;\n\n  enum class EMediaType { ARAM = 0, Real = 1, File = 2, NOD = 3 };\n  virtual EMediaType GetMediaType() const = 0;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CFactoryMgr.cpp",
    "content": "#include \"Runtime/CFactoryMgr.hpp\"\n\n#include <algorithm>\n#include <array>\n#include <cctype>\n#include <iterator>\n//#include \"optick.h\"\n\n#include \"Runtime/CStopwatch.hpp\"\n#include \"Runtime/IObj.hpp\"\n\nnamespace metaforce {\nconstexpr std::array TypeTable{\n    FOURCC('CLSN'), FOURCC('CMDL'), FOURCC('CSKR'), FOURCC('ANIM'), FOURCC('CINF'), FOURCC('TXTR'), FOURCC('PLTT'),\n    FOURCC('FONT'), FOURCC('ANCS'), FOURCC('EVNT'), FOURCC('MADF'), FOURCC('MLVL'), FOURCC('MREA'), FOURCC('MAPW'),\n    FOURCC('MAPA'), FOURCC('SAVW'), FOURCC('SAVA'), FOURCC('PART'), FOURCC('WPSC'), FOURCC('SWHC'), FOURCC('DPSC'),\n    FOURCC('ELSC'), FOURCC('CRSC'), FOURCC('AFSM'), FOURCC('DCLN'), FOURCC('AGSC'), FOURCC('ATBL'), FOURCC('CSNG'),\n    FOURCC('STRG'), FOURCC('SCAN'), FOURCC('PATH'), FOURCC('DGRP'), FOURCC('HMAP'), FOURCC('CTWK'), FOURCC('FRME'),\n    FOURCC('HINT'), FOURCC('MAPU'), FOURCC('DUMB'), FOURCC('OIDS'),\n};\n\nCFactoryFnReturn CFactoryMgr::MakeObject(const SObjectTag& tag, metaforce::CInputStream& in,\n                                         const CVParamTransfer& paramXfer, CObjectReference* selfRef) {\n  auto search = x10_factories.find(tag.type);\n  if (search == x10_factories.end())\n    return {};\n\n  return search->second(tag, in, paramXfer, selfRef);\n}\n\nbool CFactoryMgr::CanMakeMemory(const metaforce::SObjectTag& tag) const {\n  auto search = x24_memFactories.find(tag.type);\n  return search != x24_memFactories.cend();\n}\n\nCFactoryFnReturn CFactoryMgr::MakeObjectFromMemory(const SObjectTag& tag, std::unique_ptr<u8[]>&& buf, int size,\n                                                   bool compressed, const CVParamTransfer& paramXfer,\n                                                   CObjectReference* selfRef) {\n  //OPTICK_EVENT();\n  std::unique_ptr<u8[]> localBuf = std::move(buf);\n\n  const auto memFactoryIter = x24_memFactories.find(tag.type);\n  if (memFactoryIter != x24_memFactories.cend()) {\n    if (compressed) {\n      std::unique_ptr<CInputStream> compRead =\n          std::make_unique<CMemoryInStream>(localBuf.get(), size, CMemoryInStream::EOwnerShip::NotOwned);\n      const u32 decompLen = compRead->ReadLong();\n      CZipInputStream r(std::move(compRead));\n      std::unique_ptr<u8[]> decompBuf(new u8[decompLen]);\n      r.Get(decompBuf.get(), decompLen);\n      return memFactoryIter->second(tag, std::move(decompBuf), decompLen, paramXfer, selfRef);\n    } else {\n      return memFactoryIter->second(tag, std::move(localBuf), size, paramXfer, selfRef);\n    }\n  } else {\n    const auto factoryIter = x10_factories.find(tag.type);\n    if (factoryIter == x10_factories.end()) {\n      return {};\n    }\n\n    if (compressed) {\n      std::unique_ptr<CInputStream> compRead =\n          std::make_unique<CMemoryInStream>(localBuf.get(), size, CMemoryInStream::EOwnerShip::NotOwned);\n\n      compRead->ReadLong();\n      CZipInputStream r(std::move(compRead));\n      return factoryIter->second(tag, r, paramXfer, selfRef);\n    } else {\n      CMemoryInStream r(localBuf.get(), size, CMemoryInStream::EOwnerShip::NotOwned);\n      return factoryIter->second(tag, r, paramXfer, selfRef);\n    }\n  }\n}\n\nCFactoryMgr::ETypeTable CFactoryMgr::FourCCToTypeIdx(FourCC fcc) {\n  for (size_t i = 0; i < 4; ++i) {\n    fcc.getChars()[i] = char(std::toupper(fcc.getChars()[i]));\n  }\n\n  const auto search =\n      std::find_if(TypeTable.cbegin(), TypeTable.cend(), [fcc](const FourCC& test) { return test == fcc; });\n  if (search == TypeTable.cend()) {\n    return ETypeTable::Invalid;\n  }\n  return ETypeTable(std::distance(TypeTable.cbegin(), search));\n}\n\nFourCC CFactoryMgr::TypeIdxToFourCC(ETypeTable fcc) { return TypeTable[size_t(fcc)]; }\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CFactoryMgr.hpp",
    "content": "#pragma once\n\n#include <unordered_map>\n\n#include \"Runtime/IFactory.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n\nnamespace metaforce {\nstruct SObjectTag;\nclass CVParamTransfer;\nclass IObj;\n\nclass CFactoryMgr {\n  std::unordered_map<FourCC, FFactoryFunc> x10_factories;\n  std::unordered_map<FourCC, FMemFactoryFunc> x24_memFactories;\n\npublic:\n  CFactoryFnReturn MakeObject(const SObjectTag& tag, metaforce::CInputStream& in, const CVParamTransfer& paramXfer,\n                              CObjectReference* selfRef);\n  bool CanMakeMemory(const metaforce::SObjectTag& tag) const;\n  CFactoryFnReturn MakeObjectFromMemory(const SObjectTag& tag, std::unique_ptr<u8[]>&& buf, int size, bool compressed,\n                                        const CVParamTransfer& paramXfer, CObjectReference* selfRef);\n  void AddFactory(FourCC key, FFactoryFunc func) { x10_factories.insert_or_assign(key, std::move(func)); }\n  void AddFactory(FourCC key, FMemFactoryFunc func) { x24_memFactories.insert_or_assign(key, std::move(func)); }\n\n  enum class ETypeTable : u8 {\n    CLSN,\n    CMDL,\n    CSKR,\n    ANIM,\n    CINF,\n    TXTR,\n    PLTT,\n    FONT,\n    ANCS,\n    EVNT,\n    MADF,\n    MLVL,\n    MREA,\n    MAPW,\n    MAPA,\n    SAVW,\n    SAVA,\n    PART,\n    WPSC,\n    SWHC,\n    DPSC,\n    ELSC,\n    CRSC,\n    AFSM,\n    DCLN,\n    AGSC,\n    ATBL,\n    CSNG,\n    STRG,\n    SCAN,\n    PATH,\n    DGRP,\n    HMAP,\n    CTWK,\n    FRME,\n    HINT,\n    MAPU,\n    DUMB,\n    OIDS,\n    Invalid = 127\n  };\n\n  static ETypeTable FourCCToTypeIdx(FourCC fcc);\n  static FourCC TypeIdxToFourCC(ETypeTable fcc);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CGameAllocator.cpp",
    "content": "#include \"Runtime/CGameAllocator.hpp\"\n#include \"Runtime/Logging.hpp\"\n\nnamespace metaforce {\n#pragma GCC diagnostic ignored \"-Wclass-memaccess\"\n\nstd::vector<CGameAllocator::SAllocationDescription> CGameAllocator::m_allocations;\n\nu8* CGameAllocator::Alloc(size_t len) {\n  size_t roundedLen = ROUND_UP_64(len + sizeof(SChunkDescription));\n  for (SAllocationDescription& alloc : m_allocations) {\n    /* We need to supply enough room for allocation information */\n    if (alloc.freeOffset + roundedLen < alloc.allocSize) {\n      u8* ptr = alloc.memptr.get() + alloc.freeOffset;\n      SChunkDescription* chunkInfo = reinterpret_cast<SChunkDescription*>(ptr);\n      *chunkInfo = SChunkDescription();\n      chunkInfo->parent = &alloc;\n      chunkInfo->len = len;\n      alloc.freeOffset += roundedLen;\n      return ptr + sizeof(SChunkDescription);\n    }\n  }\n\n  /* 1MiB minimum allocation to prevent constantly allocating small amounts of memory */\n  size_t allocSz = len;\n  if (allocSz < (1 * 1024 * 1024 * 1024))\n    allocSz = 1 * 1024 * 1024 * 1024;\n\n  /* Pad size to allow for allocation information */\n  allocSz = ROUND_UP_64(allocSz + sizeof(SChunkDescription));\n  auto& alloc = m_allocations.emplace_back();\n  alloc.memptr.reset(new u8[allocSz]);\n  u8* ptr = alloc.memptr.get();\n  alloc.allocSize = allocSz;\n  alloc.freeOffset += roundedLen;\n  SChunkDescription* chunkInfo = reinterpret_cast<SChunkDescription*>(ptr);\n  *chunkInfo = SChunkDescription();\n  chunkInfo->parent = &alloc;\n  chunkInfo->len = len;\n  return ptr + sizeof(SChunkDescription);\n}\n\nvoid CGameAllocator::Free(u8* ptr) {\n  SChunkDescription* info = reinterpret_cast<SChunkDescription*>(ptr - sizeof(SChunkDescription));\n  if (info->magic != 0xE8E8E8E8 || info->sentinal != 0xEFEFEFEF) {\n    spdlog::fatal(\"Invalid chunk description, memory corruption!\");\n  }\n\n  SAllocationDescription& alloc = *info->parent;\n  size_t roundedLen = ROUND_UP_32(info->len + sizeof(SChunkDescription));\n  alloc.freeOffset -= roundedLen;\n  /* Invalidate chunk allocation descriptor */\n  memset(info, 0, ROUND_UP_64(info->len + sizeof(SChunkDescription)));\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CGameAllocator.hpp",
    "content": "#pragma once\n\n#include <cstddef>\n#include <memory>\n#include <vector>\n\n#include \"Runtime/RetroTypes.hpp\"\n\nnamespace metaforce {\nclass CGameAllocator {\n  struct SAllocationDescription {\n    std::unique_ptr<u8[]> memptr;\n    size_t allocSize = 0;\n    ptrdiff_t freeOffset = 0;\n  };\n\n  struct SChunkDescription {\n    u32 magic = 0xE8E8E8E8;\n    SAllocationDescription* parent;\n    size_t len = 0;\n    u32 sentinal = 0xEFEFEFEF;\n  };\n\n  static std::vector<SAllocationDescription> m_allocations;\n\npublic:\n  static u8* Alloc(size_t len);\n  static void Free(u8* ptr);\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CGameDebug.hpp",
    "content": "#pragma once\n\n#include <string>\n\nnamespace metaforce {\nstruct CFinalInput;\n\nconst char* StringForControlOption(int);\n\nenum class EDebugMenu {};\n\nenum class EDebugOptions {};\n\nenum class EDebugMainMenu {};\n\nclass CDebugOption {\npublic:\n  CDebugOption(EDebugMenu, EDebugOptions, const std::string&, bool);\n  CDebugOption(EDebugMenu, EDebugOptions, const std::string&, float, float, float, float);\n};\n\nclass CGameDebug {\npublic:\n  enum class EReturnValue {};\n\n  void DeactivateMenu();\n  void AddDebugOption(EDebugMenu, EDebugOptions, const char*, bool);\n  void AddDebugOption(EDebugMenu, EDebugOptions, const char*, float, float, float, float);\n  void SetCaptureMovieTimeLeft(float);\n  const std::string& GetCaptureMovieName();\n  void SetCaptureMovieName(const std::string&);\n  void AddDebugOptions();\n  void CopyDebugToTweaks();\n  void CopyTweaksToDebug();\n  void ProcessControllerInput(const CFinalInput&);\n  void Update(float);\n  void Draw(void) const;\n  void ActivateMenu(EDebugMainMenu, int);\n  void AddDebugOption(const CDebugOption&);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CGameHintInfo.cpp",
    "content": "#include \"Runtime/CGameHintInfo.hpp\"\n\n#include \"Runtime/CMemoryCardSys.hpp\"\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n\nnamespace metaforce {\n\nCGameHintInfo::CGameHintInfo(CInputStream& in, s32 version) {\n  u32 hintCount = in.ReadLong();\n  x0_hints.reserve(hintCount);\n  for (u32 i = 0; i < hintCount; ++i)\n    x0_hints.emplace_back(in, version);\n}\n\nCGameHintInfo::CGameHint::CGameHint(CInputStream& in, s32 version)\n: x0_name(in.Get<std::string>())\n, x10_immediateTime(in.ReadFloat())\n, x14_normalTime(in.ReadFloat())\n, x18_stringId(in.Get<CAssetId>())\n, x1c_textTime(3.f * float(version <= 0 ? 1 : in.ReadLong())) {\n  u32 locationCount = in.ReadLong();\n  x20_locations.reserve(locationCount);\n  for (u32 i = 0; i < locationCount; ++i)\n    x20_locations.emplace_back(in, version);\n}\n\nCGameHintInfo::SHintLocation::SHintLocation(CInputStream& in, s32)\n: x0_mlvlId(in.Get<CAssetId>())\n, x4_mreaId(in.Get<CAssetId>())\n, x8_areaId(in.ReadLong())\n, xc_stringId(in.Get<CAssetId>()) {}\n\nint CGameHintInfo::FindHintIndex(std::string_view str) {\n  const std::vector<CGameHint>& gameHints = g_MemoryCardSys->GetHints();\n  const auto it =\n      std::find_if(gameHints.cbegin(), gameHints.cend(), [&str](const CGameHint& gh) { return gh.GetName() == str; });\n\n  return it != gameHints.cend() ? it - gameHints.cbegin() : -1;\n}\n\nCFactoryFnReturn FHintFactory(const SObjectTag&, CInputStream& in, const CVParamTransfer&, CObjectReference*) {\n  in.ReadLong();\n  s32 version = in.ReadInt32();\n\n  return TToken<CGameHintInfo>::GetIObjObjectFor(std::make_unique<CGameHintInfo>(in, version));\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CGameHintInfo.hpp",
    "content": "#pragma once\n\n#include <string_view>\n#include <vector>\n\n#include \"Runtime/IFactory.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n\nnamespace metaforce {\nclass CGameHintInfo {\npublic:\n  struct SHintLocation {\n    CAssetId x0_mlvlId;\n    CAssetId x4_mreaId;\n    TAreaId x8_areaId = kInvalidAreaId;\n    CAssetId xc_stringId;\n    SHintLocation(CInputStream&, s32);\n  };\n\n  class CGameHint {\n    std::string x0_name;\n    float x10_immediateTime;\n    float x14_normalTime;\n    CAssetId x18_stringId;\n    float x1c_textTime;\n    std::vector<SHintLocation> x20_locations;\n\n  public:\n    CGameHint(CInputStream&, s32);\n\n    float GetNormalTime() const { return x14_normalTime; }\n    float GetImmediateTime() const { return x10_immediateTime; }\n    float GetTextTime() const { return x1c_textTime; }\n    std::string_view GetName() const { return x0_name; }\n    CAssetId GetStringID() const { return x18_stringId; }\n    const std::vector<SHintLocation>& GetLocations() const { return x20_locations; }\n  };\n\nprivate:\n  std::vector<CGameHint> x0_hints;\n\npublic:\n  CGameHintInfo(CInputStream&, s32);\n  const std::vector<CGameHint>& GetHints() const { return x0_hints; }\n  static int FindHintIndex(std::string_view str);\n};\n\nCFactoryFnReturn FHintFactory(const SObjectTag&, CInputStream&, const CVParamTransfer&, CObjectReference*);\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CGameOptions.cpp",
    "content": "#include \"Runtime/CGameOptions.hpp\"\n\n#include <cstring>\n\n#include \"Runtime/CGameHintInfo.hpp\"\n#include \"Runtime/CGameState.hpp\"\n#include \"Runtime/CMemoryCardSys.hpp\"\n#include \"Runtime/CWorldSaveGameInfo.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Audio/CSfxManager.hpp\"\n#include \"Runtime/Audio/CStreamAudioManager.hpp\"\n#include \"Runtime/Graphics/CMoviePlayer.hpp\"\n#include \"Runtime/Input/CFinalInput.hpp\"\n\n#include \"ConsoleVariables/CVarManager.hpp\"\n\nnamespace metaforce {\n\nconstexpr std::array<SGameOption, 5> VisorOpts{{\n    {EGameOption::VisorOpacity, 21, 0.f, 255.f, 1.f, EOptionType::Float},\n    {EGameOption::HelmetOpacity, 22, 0.f, 255.f, 1.f, EOptionType::Float},\n    {EGameOption::HUDLag, 23, 0.f, 1.f, 1.f, EOptionType::DoubleEnum},\n    {EGameOption::HintSystem, 24, 0.f, 1.f, 1.f, EOptionType::DoubleEnum},\n    {EGameOption::RestoreDefaults, 35, 0.f, 1.f, 1.f, EOptionType::RestoreDefaults},\n}};\n\nconstexpr std::array<SGameOption, 5> DisplayOpts{{\n    //{EGameOption::ScreenBrightness, 25, 0.f, 8.f, 1.f, EOptionType::Float},\n    {EGameOption::ScreenBrightness, 25, -100.f, 100.f, 1.f, EOptionType::Float},\n    {EGameOption::ScreenOffsetX, 26, -30.f, 30.f, 1.f, EOptionType::Float},\n    {EGameOption::ScreenOffsetY, 27, -30.f, 30.f, 1.f, EOptionType::Float},\n    {EGameOption::ScreenStretch, 28, -10.f, 10.f, 1.f, EOptionType::Float},\n    {EGameOption::RestoreDefaults, 35, 0.f, 1.f, 1.f, EOptionType::RestoreDefaults},\n}};\n\nconstexpr std::array<SGameOption, 4> SoundOpts{{\n    {EGameOption::SFXVolume, 29, 0.f, 127.f, 1.f, EOptionType::Float},\n    {EGameOption::MusicVolume, 30, 0.f, 127.f, 1.f, EOptionType::Float},\n    {EGameOption::SoundMode, 31, 0.f, 1.f, 1.f, EOptionType::TripleEnum},\n    {EGameOption::RestoreDefaults, 35, 0.f, 1.f, 1.f, EOptionType::RestoreDefaults},\n}};\n\nconstexpr std::array<SGameOption, 4> ControllerOpts{{\n    {EGameOption::ReverseYAxis, 32, 0.f, 1.f, 1.f, EOptionType::DoubleEnum},\n    {EGameOption::Rumble, 33, 0.f, 1.f, 1.f, EOptionType::DoubleEnum},\n    {EGameOption::SwapBeamControls, 34, 0.f, 1.f, 1.f, EOptionType::DoubleEnum},\n    {EGameOption::RestoreDefaults, 35, 0.f, 1.f, 1.f, EOptionType::RestoreDefaults},\n}};\n\nconstexpr std::array<SGameOption, 5> VisorOptsNew{{\n    {EGameOption::VisorOpacity, 23, 0.f, 255.f, 1.f, EOptionType::Float},\n    {EGameOption::HelmetOpacity, 24, 0.f, 255.f, 1.f, EOptionType::Float},\n    {EGameOption::HUDLag, 25, 0.f, 1.f, 1.f, EOptionType::DoubleEnum},\n    {EGameOption::HintSystem, 26, 0.f, 1.f, 1.f, EOptionType::DoubleEnum},\n    {EGameOption::RestoreDefaults, 38, 0.f, 1.f, 1.f, EOptionType::RestoreDefaults},\n}};\n\nconstexpr std::array<SGameOption, 5> DisplayOptsNew{{\n    //{EGameOption::ScreenBrightness, 25, 0.f, 8.f, 1.f, EOptionType::Float},\n    {EGameOption::ScreenBrightness, 28, -100.f, 100.f, 1.f, EOptionType::Float},\n    {EGameOption::ScreenOffsetX, 29, -30.f, 30.f, 1.f, EOptionType::Float},\n    {EGameOption::ScreenOffsetY, 30, -30.f, 30.f, 1.f, EOptionType::Float},\n    {EGameOption::ScreenStretch, 31, -10.f, 10.f, 1.f, EOptionType::Float},\n    {EGameOption::RestoreDefaults, 38, 0.f, 1.f, 1.f, EOptionType::RestoreDefaults},\n}};\n\nconstexpr std::array<SGameOption, 4> SoundOptsNew{{\n    {EGameOption::SFXVolume, 32, 0.f, 127.f, 1.f, EOptionType::Float},\n    {EGameOption::MusicVolume, 33, 0.f, 127.f, 1.f, EOptionType::Float},\n    {EGameOption::SoundMode, 34, 0.f, 1.f, 1.f, EOptionType::TripleEnum},\n    {EGameOption::RestoreDefaults, 38, 0.f, 1.f, 1.f, EOptionType::RestoreDefaults},\n}};\n\nconstexpr std::array<SGameOption, 4> ControllerOptsNew{{\n    {EGameOption::ReverseYAxis, 35, 0.f, 1.f, 1.f, EOptionType::DoubleEnum},\n    {EGameOption::Rumble, 37, 0.f, 1.f, 1.f, EOptionType::DoubleEnum},\n    {EGameOption::SwapBeamControls, 37, 0.f, 1.f, 1.f, EOptionType::DoubleEnum},\n    {EGameOption::RestoreDefaults, 38, 0.f, 1.f, 1.f, EOptionType::RestoreDefaults},\n}};\n\nconstexpr std::array<std::pair<size_t, const SGameOption*>, 5> GameOptionsRegistry{{\n    {VisorOpts.size(), VisorOpts.data()},\n    {DisplayOpts.size(), DisplayOpts.data()},\n    {SoundOpts.size(), SoundOpts.data()},\n    {ControllerOpts.size(), ControllerOpts.data()},\n    {0, nullptr},\n}};\n\nconstexpr std::array<std::pair<size_t, const SGameOption*>, 5> GameOptionsRegistryNew{{\n    {VisorOptsNew.size(), VisorOptsNew.data()},\n    {DisplayOptsNew.size(), DisplayOptsNew.data()},\n    {SoundOptsNew.size(), SoundOptsNew.data()},\n    {ControllerOptsNew.size(), ControllerOptsNew.data()},\n    {0, nullptr},\n}};\n\nCPersistentOptions::CPersistentOptions(CInputStream& stream) {\n  for (u8& entry : x0_nesState) {\n    entry = stream.ReadBits(8);\n  }\n\n  for (bool& entry : x68_) {\n    entry = stream.ReadBits(8) != 0;\n  }\n\n  xc0_frozenFpsCount = stream.ReadBits(2);\n  xc4_frozenBallCount = stream.ReadBits(2);\n  xc8_powerBombAmmoCount = stream.ReadBits(1);\n  xcc_logScanPercent = stream.ReadBits(7);\n  xd0_24_fusionLinked = stream.ReadBits(1) != 0;\n  xd0_25_normalModeBeat = stream.ReadBits(1) != 0;\n  xd0_26_hardModeBeat = stream.ReadBits(1) != 0;\n  xd0_27_fusionBeat = stream.ReadBits(1) != 0;\n  xd0_28_fusionSuitActive = false;\n  xd0_29_allItemsCollected = stream.ReadBits(1) != 0;\n  xbc_autoMapperKeyState = stream.ReadBits(2);\n\n  const auto& memWorlds = g_MemoryCardSys->GetMemoryWorlds();\n  size_t cinematicCount = 0;\n  for (const auto& world : memWorlds) {\n    TLockedToken<CWorldSaveGameInfo> saveWorld =\n        g_SimplePool->GetObj(SObjectTag{FOURCC('SAVW'), world.second.GetSaveWorldAssetId()});\n    cinematicCount += saveWorld->GetCinematicCount();\n  }\n\n  std::vector<bool> cinematicStates;\n  cinematicStates.reserve(cinematicCount);\n  for (size_t i = 0; i < cinematicCount; ++i) {\n    cinematicStates.push_back(stream.ReadBits(1) != 0);\n  }\n\n  for (const auto& world : memWorlds) {\n    TLockedToken<CWorldSaveGameInfo> saveWorld =\n        g_SimplePool->GetObj(SObjectTag{FOURCC('SAVW'), world.second.GetSaveWorldAssetId()});\n\n    auto stateIt = cinematicStates.cbegin();\n    for (TEditorId cineId : saveWorld->GetCinematics())\n      if (*stateIt++)\n        SetCinematicState(world.first, cineId, true);\n  }\n}\n\nvoid CPersistentOptions::PutTo(COutputStream& w) const {\n  for (const u8 entry : x0_nesState) {\n    w.WriteBits(entry, 8);\n  }\n\n  for (const bool entry : x68_) {\n    w.WriteBits(u32(entry), 8);\n  }\n\n  w.WriteBits(xc0_frozenFpsCount, 2);\n  w.WriteBits(xc4_frozenBallCount, 2);\n  w.WriteBits(xc8_powerBombAmmoCount, 1);\n  w.WriteBits(xcc_logScanPercent, 7);\n  w.WriteBits(xd0_24_fusionLinked, 1);\n  w.WriteBits(xd0_25_normalModeBeat, 1);\n  w.WriteBits(xd0_26_hardModeBeat, 1);\n  w.WriteBits(xd0_27_fusionBeat, 1);\n  w.WriteBits(xd0_29_allItemsCollected, 1);\n  w.WriteBits(xbc_autoMapperKeyState, 2);\n\n  const auto& memWorlds = g_MemoryCardSys->GetMemoryWorlds();\n  for (const auto& world : memWorlds) {\n    const TLockedToken<CWorldSaveGameInfo> saveWorld =\n        g_SimplePool->GetObj(SObjectTag{FOURCC('SAVW'), world.second.GetSaveWorldAssetId()});\n\n    for (const auto& cineId : saveWorld->GetCinematics()) {\n      w.WriteBits(u32(GetCinematicState(world.first, cineId)), 1);\n    }\n  }\n}\n\nbool CPersistentOptions::GetCinematicState(CAssetId mlvlId, TEditorId cineId) const {\n  auto existing = std::find_if(xac_cinematicStates.cbegin(), xac_cinematicStates.cend(),\n                               [&](const std::pair<CAssetId, TEditorId>& pair) -> bool {\n                                 return pair.first == mlvlId && pair.second == cineId;\n                               });\n\n  return existing != xac_cinematicStates.cend();\n}\n\nvoid CPersistentOptions::SetCinematicState(CAssetId mlvlId, TEditorId cineId, bool state) {\n  auto existing = std::find_if(xac_cinematicStates.cbegin(), xac_cinematicStates.cend(),\n                               [&](const std::pair<CAssetId, TEditorId>& pair) -> bool {\n                                 return pair.first == mlvlId && pair.second == cineId;\n                               });\n\n  if (state && existing == xac_cinematicStates.cend())\n    xac_cinematicStates.emplace_back(mlvlId, cineId);\n  else if (!state && existing != xac_cinematicStates.cend())\n    xac_cinematicStates.erase(existing);\n}\n\nCGameOptions::CGameOptions(CInputStream& stream) {\n  for (u8& entry : x0_)\n    entry = stream.ReadBits(8);\n\n  x44_soundMode = CAudioSys::ESurroundModes(stream.ReadBits(2));\n  x48_screenBrightness = stream.ReadBits(4);\n\n  x4c_screenXOffset = stream.ReadBits(6) - 30;\n  x50_screenYOffset = stream.ReadBits(6) - 30;\n  x54_screenStretch = stream.ReadBits(5) - 10;\n  x58_sfxVol = stream.ReadBits(7);\n  x5c_musicVol = stream.ReadBits(7);\n  x60_hudAlpha = stream.ReadBits(8);\n  x64_helmetAlpha = stream.ReadBits(8);\n\n  x68_24_hudLag = stream.ReadBits(1) != 0;\n  x68_28_hintSystem = stream.ReadBits(1) != 0;\n  x68_25_invertY = stream.ReadBits(1) != 0;\n  x68_26_rumble = stream.ReadBits(1) != 0;\n  x68_27_swapBeamsControls = stream.ReadBits(1) != 0;\n}\n\nvoid CGameOptions::ResetToDefaults() {\n  x48_screenBrightness = 4;\n  x4c_screenXOffset = 0;\n  x50_screenYOffset = 0;\n  x54_screenStretch = 0;\n  x58_sfxVol = 0x7f;\n  x5c_musicVol = 0x7f;\n  x44_soundMode = CAudioSys::ESurroundModes::Stereo;\n  x60_hudAlpha = 0xFF;\n  x64_helmetAlpha = 0xFF;\n  x68_24_hudLag = true;\n  x68_25_invertY = false;\n  x68_26_rumble = true;\n  x68_27_swapBeamsControls = false;\n  x68_28_hintSystem = true;\n  InitSoundMode();\n  EnsureSettings();\n}\n\nvoid CGameOptions::PutTo(COutputStream& writer) const {\n  for (const u8 entry : x0_)\n    writer.WriteBits(entry, 8);\n\n  writer.WriteBits(u32(x44_soundMode), 2);\n  writer.WriteBits(x48_screenBrightness, 4);\n\n  writer.WriteBits(x4c_screenXOffset + 30, 6);\n  writer.WriteBits(x50_screenYOffset + 30, 6);\n  writer.WriteBits(x54_screenStretch + 10, 5);\n  writer.WriteBits(x58_sfxVol, 7);\n  writer.WriteBits(x5c_musicVol, 7);\n  writer.WriteBits(x60_hudAlpha, 8);\n  writer.WriteBits(x64_helmetAlpha, 8);\n\n  writer.WriteBits(x68_24_hudLag, 1);\n  writer.WriteBits(x68_28_hintSystem, 1);\n  writer.WriteBits(x68_25_invertY, 1);\n  writer.WriteBits(x68_26_rumble, 1);\n  writer.WriteBits(x68_27_swapBeamsControls, 1);\n}\n\nCGameOptions::CGameOptions()\n: x68_24_hudLag(true)\n, x68_25_invertY(false)\n, x68_26_rumble(true)\n, x68_27_swapBeamsControls(false)\n, x68_28_hintSystem(true) {\n  InitSoundMode();\n}\n\nfloat CGameOptions::TuneScreenBrightness() const { return (0.375f * 1.f) + (float(x48_screenBrightness) * 0.25f); }\n\nvoid CGameOptions::InitSoundMode() { /* If system is mono, force x44 to mono, otherwise honor user preference */\n}\nstatic float BrightnessCopyFilter = 0.f;\nvoid CGameOptions::SetScreenBrightness(s32 value, bool apply) {\n  x48_screenBrightness = zeus::clamp(0, value, 8);\n\n  if (!apply) {\n    return;\n  }\n\n  BrightnessCopyFilter = TuneScreenBrightness();\n}\n\nvoid CGameOptions::ApplyGamma() {\n  float gammaT = -m_gamma / 100.f + 1.f;\n  if (gammaT < 1.f)\n    gammaT = gammaT * 0.5f + 0.5f;\n  if (zeus::close_enough(gammaT, 1.f, 0.05f))\n    gammaT = 1.f;\n//  CGraphics::g_BooFactory->setDisplayGamma(gammaT);\n}\n\nvoid CGameOptions::SetGamma(s32 value, bool apply) {\n  m_gamma = zeus::clamp(-100, value, 100);\n\n  if (!apply) {\n    return;\n  }\n\n  ApplyGamma();\n}\n\nvoid CGameOptions::SetScreenPositionX(s32 position, bool apply) {\n  x4c_screenXOffset = zeus::clamp(-30, position, 30);\n\n  if (apply) {\n    /* TOOD: CGraphics related funcs */\n  }\n}\n\nvoid CGameOptions::SetScreenPositionY(s32 position, bool apply) {\n  x50_screenYOffset = zeus::clamp(-30, position, 30);\n\n  if (apply) {\n    /* TOOD: CGraphics related funcs */\n  }\n}\n\nvoid CGameOptions::SetScreenStretch(s32 stretch, bool apply) {\n  x54_screenStretch = zeus::clamp(-10, stretch, 10);\n\n  if (apply) {\n    /* TOOD: CGraphics related funcs */\n  }\n}\n\nvoid CGameOptions::SetSfxVolume(s32 volume, bool apply) {\n  x58_sfxVol = zeus::clamp(0, volume, 0x7f);\n\n  if (!apply) {\n    return;\n  }\n\n  CAudioSys::SysSetSfxVolume(x58_sfxVol, 1, true, true);\n  CStreamAudioManager::SetSfxVolume(x58_sfxVol);\n  CMoviePlayer::SetSfxVolume(x58_sfxVol);\n}\n\nvoid CGameOptions::SetMusicVolume(s32 volume, bool apply) {\n  x5c_musicVol = zeus::clamp(0, volume, 0x7f);\n\n  if (!apply) {\n    return;\n  }\n\n  CStreamAudioManager::SetMusicVolume(x5c_musicVol);\n}\n\nvoid CGameOptions::SetHUDAlpha(u32 alpha) { x60_hudAlpha = alpha; }\n\nvoid CGameOptions::SetHelmetAlpha(u32 alpha) { x64_helmetAlpha = alpha; }\n\nvoid CGameOptions::SetHUDLag(bool lag) { x68_24_hudLag = lag; }\n\nvoid CGameOptions::SetSurroundMode(int mode, bool apply) {\n  x44_soundMode = CAudioSys::ESurroundModes(zeus::clamp(0, mode, 2));\n  if (apply)\n    CAudioSys::SetSurroundMode(x44_soundMode);\n}\n\nCAudioSys::ESurroundModes CGameOptions::GetSurroundMode() const { return x44_soundMode; }\n\nvoid CGameOptions::SetInvertYAxis(bool invert) { x68_25_invertY = invert; }\n\nvoid CGameOptions::SetIsRumbleEnabled(bool rumble) { x68_26_rumble = rumble; }\n\nvoid CGameOptions::SetSwapBeamControls(bool swap) {\n  x68_27_swapBeamsControls = swap;\n  if (!swap)\n    SetControls(0);\n  else\n    SetControls(1);\n}\n\nvoid CGameOptions::SetIsHintSystemEnabled(bool hints) { x68_28_hintSystem = hints; }\n\nvoid CGameOptions::SetControls(int controls) {\n  if (controls == 0)\n    g_currentPlayerControl = g_tweakPlayerControl;\n  else\n    g_currentPlayerControl = g_tweakPlayerControlAlt;\n\n  ResetControllerAssets(controls);\n}\n\nconstexpr std::array<std::pair<CAssetId, CAssetId>, 5> CStickToDPadRemap{{\n    {0x2A13C23Eu, 0xF13452F8u},\n    {0xA91A7703u, 0xC042EC91u},\n    {0x12A12131u, 0x5F556002u},\n    {0xA9798329u, 0xB306E26Fu},\n    {0xCD7B1ACAu, 0x8ADA8184u},\n}};\n\nconstexpr std::array<std::pair<CAssetId, CAssetId>, 5> CStickOutlineToDPadRemap{{\n    {0x1A29C0E6u, 0xF13452F8u},\n    {0x5D9F9796u, 0xC042EC91u},\n    {0x951546A8u, 0x5F556002u},\n    {0x7946C4C5u, 0xB306E26Fu},\n    {0x409AA72Eu, 0x8ADA8184u},\n}};\n\nvoid CGameOptions::ResetControllerAssets(int controls) {\n  if (controls != 1) {\n    x6c_controlTxtrMap.clear();\n  } else if (x6c_controlTxtrMap.empty()) {\n    x6c_controlTxtrMap.reserve(15);\n\n    for (const auto& entry : CStickToDPadRemap) {\n      const auto& emplaced = x6c_controlTxtrMap.emplace_back(entry);\n      x6c_controlTxtrMap.emplace_back(emplaced.second, emplaced.first);\n    }\n\n    for (const auto& entry : CStickOutlineToDPadRemap)\n      x6c_controlTxtrMap.emplace_back(entry);\n\n    std::sort(x6c_controlTxtrMap.begin(), x6c_controlTxtrMap.end(),\n              [](const std::pair<CAssetId, CAssetId>& a, const std::pair<CAssetId, CAssetId>& b) {\n                return a.first < b.first;\n              });\n  }\n}\n\nvoid CGameOptions::EnsureSettings() {\n  SetScreenBrightness(x48_screenBrightness, true);\n  SetGamma(m_gamma, true);\n  SetScreenPositionX(x4c_screenXOffset, true);\n  SetScreenPositionY(x50_screenYOffset, true);\n  SetScreenStretch(x54_screenStretch, true);\n  SetSfxVolume(x58_sfxVol, true);\n  SetMusicVolume(x5c_musicVol, true);\n  SetSurroundMode(int(x44_soundMode), true);\n  SetHelmetAlpha(x64_helmetAlpha);\n  SetHUDLag(x68_24_hudLag);\n  SetInvertYAxis(x68_25_invertY);\n  SetIsRumbleEnabled(x68_26_rumble);\n  SetIsHintSystemEnabled(x68_28_hintSystem);\n  SetSwapBeamControls(x68_27_swapBeamsControls);\n}\n\nvoid CGameOptions::TryRestoreDefaults(const CFinalInput& input, int category, int option, bool frontend,\n                                      bool forceRestore) {\n  const auto& options = GameOptionsRegistry[category];\n  if (options.first == 0)\n    return;\n\n  if (options.second[option].option != EGameOption::RestoreDefaults)\n    return;\n\n  if (!forceRestore && !input.PA() && !input.PSpecialKey(ESpecialKey::Enter))\n    return;\n\n  if (frontend) {\n    CSfxManager::SfxStart(SFXfnt_advance_L, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n    CSfxManager::SfxStart(SFXfnt_advance_R, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n  } else {\n    CSfxManager::SfxStart(SFXui_advance, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n  }\n\n  CGameOptions& gameOptions = g_GameState->GameOptions();\n  switch (category) {\n  case 0:\n    gameOptions.SetHelmetAlpha(0xff);\n    gameOptions.SetHUDLag(true);\n    gameOptions.SetIsHintSystemEnabled(true);\n    break;\n\n  case 1:\n    gameOptions.SetScreenBrightness(4, true);\n    gameOptions.SetGamma(0, true);\n    gameOptions.SetScreenPositionX(0, true);\n    gameOptions.SetScreenPositionY(0, true);\n    gameOptions.SetScreenStretch(0, true);\n    break;\n\n  case 2:\n    gameOptions.SetSfxVolume(0x7f, true);\n    gameOptions.SetMusicVolume(0x7f, true);\n    gameOptions.SetSurroundMode(1, true);\n    break;\n\n  case 3:\n    gameOptions.SetInvertYAxis(false);\n    gameOptions.SetIsRumbleEnabled(true);\n    gameOptions.SetSwapBeamControls(false);\n    break;\n\n  default:\n    break;\n  }\n}\n\nvoid CGameOptions::SetOption(EGameOption option, int value) {\n  CGameOptions& options = g_GameState->GameOptions();\n\n  switch (option) {\n  case EGameOption::VisorOpacity:\n    options.SetHUDAlpha(value);\n    break;\n  case EGameOption::HelmetOpacity:\n    options.SetHelmetAlpha(value);\n    break;\n  case EGameOption::HUDLag:\n    options.SetHUDLag(value != 0);\n    break;\n  case EGameOption::HintSystem:\n    options.SetIsHintSystemEnabled(value != 0);\n    break;\n  case EGameOption::ScreenBrightness:\n    options.SetGamma(value, true);\n    break;\n  case EGameOption::ScreenOffsetX:\n    options.SetScreenPositionX(value, true);\n    break;\n  case EGameOption::ScreenOffsetY:\n    options.SetScreenPositionY(value, true);\n    break;\n  case EGameOption::ScreenStretch:\n    options.SetScreenStretch(value, true);\n    break;\n  case EGameOption::SFXVolume:\n    options.SetSfxVolume(value, true);\n    break;\n  case EGameOption::MusicVolume:\n    options.SetMusicVolume(value, true);\n    break;\n  case EGameOption::SoundMode:\n    options.SetSurroundMode(value, true);\n    break;\n  case EGameOption::ReverseYAxis:\n    options.SetInvertYAxis(value != 0);\n    break;\n  case EGameOption::Rumble:\n    options.SetIsRumbleEnabled(value != 0);\n    break;\n  case EGameOption::SwapBeamControls:\n    options.SetSwapBeamControls(value != 0);\n    break;\n  default:\n    break;\n  }\n}\n\nint CGameOptions::GetOption(EGameOption option) {\n  const CGameOptions& options = g_GameState->GameOptions();\n\n  switch (option) {\n  case EGameOption::VisorOpacity:\n    return options.GetHUDAlpha();\n  case EGameOption::HelmetOpacity:\n    return options.GetHelmetAlpha();\n  case EGameOption::HUDLag:\n    return int(options.GetHUDLag());\n  case EGameOption::HintSystem:\n    return int(options.GetIsHintSystemEnabled());\n  case EGameOption::ScreenBrightness:\n    return options.GetGamma();\n  case EGameOption::ScreenOffsetX:\n    return options.GetScreenPositionX();\n  case EGameOption::ScreenOffsetY:\n    return options.GetScreenPositionY();\n  case EGameOption::ScreenStretch:\n    return options.GetScreenStretch();\n  case EGameOption::SFXVolume:\n    return options.GetSfxVolume();\n  case EGameOption::MusicVolume:\n    return options.GetMusicVolume();\n  case EGameOption::SoundMode:\n    return int(options.GetSurroundMode());\n  case EGameOption::ReverseYAxis:\n    return int(options.GetInvertYAxis());\n  case EGameOption::Rumble:\n    return int(options.GetIsRumbleEnabled());\n  case EGameOption::SwapBeamControls:\n    return int(options.GetSwapBeamControls());\n  default:\n    break;\n  }\n\n  return 0;\n}\n\nCHintOptions::CHintOptions(CInputStream& stream) {\n  const auto& hints = g_MemoryCardSys->GetHints();\n  x0_hintStates.reserve(hints.size());\n\n  u32 hintIdx = 0;\n  for ([[maybe_unused]] const auto& hint : hints) {\n    const auto state = EHintState(stream.ReadBits(2));\n    const s32 timeBits = stream.ReadBits(32);\n    float time;\n    std::memcpy(&time, &timeBits, sizeof(s32));\n    if (state == EHintState::Zero) {\n      time = 0.f;\n    }\n\n    x0_hintStates.emplace_back(state, time, false);\n\n    if (x10_nextHintIdx == -1 && state == EHintState::Displaying) {\n      x10_nextHintIdx = hintIdx;\n    }\n    ++hintIdx;\n  }\n}\n\nvoid CHintOptions::PutTo(COutputStream& writer) const {\n  for (const SHintState& hint : x0_hintStates) {\n    writer.WriteBits(u32(hint.x0_state), 2);\n\n    u32 timeBits;\n    std::memcpy(&timeBits, &hint.x4_time, sizeof(timeBits));\n\n    writer.WriteBits(timeBits, 32);\n  }\n}\n\nvoid CHintOptions::SetNextHintTime() {\n  if (x10_nextHintIdx == -1)\n    return;\n  x0_hintStates[x10_nextHintIdx].x4_time = g_MemoryCardSys->GetHints()[x10_nextHintIdx].GetTextTime() + 5.f;\n}\n\nvoid CHintOptions::InitializeMemoryState() {\n  const auto& hints = g_MemoryCardSys->GetHints();\n  x0_hintStates.resize(hints.size());\n}\n\nconst CHintOptions::SHintState* CHintOptions::GetCurrentDisplayedHint() const {\n  if (!g_GameState->GameOptions().GetIsHintSystemEnabled())\n    return nullptr;\n\n  if (x10_nextHintIdx == -1)\n    return nullptr;\n\n  const SHintState& hintState = x0_hintStates[x10_nextHintIdx];\n  const CGameHintInfo::CGameHint& hint = g_MemoryCardSys->GetHints()[x10_nextHintIdx];\n  if (hintState.x4_time >= hint.GetTextTime())\n    return nullptr;\n\n  if (hintState.x4_time < 3.f)\n    return &hintState;\n\n  if (!hintState.x8_dismissed)\n    return &hintState;\n\n  return nullptr;\n}\n\nvoid CHintOptions::DelayHint(std::string_view name) {\n  const int idx = CGameHintInfo::FindHintIndex(name);\n  if (idx == -1) {\n    return;\n  }\n\n  if (x10_nextHintIdx == idx) {\n    for (SHintState& state : x0_hintStates) {\n      state.x4_time += 60.f;\n    }\n  }\n\n  x0_hintStates[idx].x0_state = EHintState::Delayed;\n}\n\nvoid CHintOptions::ActivateImmediateHintTimer(std::string_view name) {\n  const int idx = CGameHintInfo::FindHintIndex(name);\n  if (idx == -1) {\n    return;\n  }\n\n  SHintState& hintState = x0_hintStates[idx];\n  const CGameHintInfo::CGameHint& hint = g_MemoryCardSys->GetHints()[idx];\n  if (hintState.x0_state != EHintState::Zero) {\n    return;\n  }\n\n  hintState.x0_state = EHintState::Waiting;\n  hintState.x4_time = hint.GetImmediateTime();\n}\n\nvoid CHintOptions::ActivateContinueDelayHintTimer(std::string_view name) {\n  int idx = x10_nextHintIdx;\n  if (idx != 0) {\n    idx = CGameHintInfo::FindHintIndex(name);\n  }\n  if (idx == -1) {\n    return;\n  }\n\n  SHintState& hintState = x0_hintStates[idx];\n  const CGameHintInfo::CGameHint& hint = g_MemoryCardSys->GetHints()[idx];\n  if (hintState.x0_state != EHintState::Displaying) {\n    return;\n  }\n\n  hintState.x4_time = hint.GetTextTime();\n}\n\nvoid CHintOptions::DismissDisplayedHint() {\n  if (x10_nextHintIdx == -1)\n    return;\n  const CGameHintInfo::CGameHint& hint = g_MemoryCardSys->GetHints()[x10_nextHintIdx];\n  SHintState& hintState = x0_hintStates[x10_nextHintIdx];\n  if (hintState.x4_time >= hint.GetTextTime())\n    return;\n  hintState.x4_time = hint.GetNormalTime();\n  hintState.x8_dismissed = true;\n}\n\nu32 CHintOptions::GetNextHintIdx() const {\n  if (g_GameState->GameOptions().GetIsHintSystemEnabled())\n    return x10_nextHintIdx;\n  return -1;\n}\n\nvoid CHintOptions::Update(float dt, const CStateManager& stateMgr) {\n  x10_nextHintIdx = -1;\n  int idx = 0;\n  auto memIt = g_MemoryCardSys->GetHints().begin();\n  for (SHintState& state : x0_hintStates) {\n    switch (state.x0_state) {\n    case EHintState::Waiting:\n      state.x4_time -= dt;\n      if (state.x4_time <= 0.f) {\n        state.x0_state = EHintState::Displaying;\n        state.x4_time = memIt->GetTextTime();\n      }\n      break;\n    case EHintState::Displaying:\n      if (x10_nextHintIdx == -1)\n        x10_nextHintIdx = idx;\n      break;\n    default:\n      break;\n    }\n    ++memIt;\n    ++idx;\n  }\n\n  if (x10_nextHintIdx == -1)\n    return;\n\n  SHintState& state = x0_hintStates[x10_nextHintIdx];\n  const CGameHintInfo::CGameHint& data = g_MemoryCardSys->GetHints()[x10_nextHintIdx];\n\n  state.x4_time = std::max(0.f, state.x4_time - dt);\n  if (state.x4_time < data.GetTextTime()) {\n    for (const CGameHintInfo::SHintLocation& loc : data.GetLocations()) {\n      if (loc.x0_mlvlId == stateMgr.GetWorld()->IGetWorldAssetId() && loc.x8_areaId == stateMgr.GetNextAreaId()) {\n        state.x4_time = data.GetNormalTime();\n        state.x8_dismissed = true;\n      }\n    }\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CGameOptions.hpp",
    "content": "#pragma once\n\n#include <array>\n#include <string_view>\n#include <vector>\n\n#include \"Runtime/CWorldSaveGameInfo.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Audio/CAudioSys.hpp\"\n\nnamespace metaforce {\nstruct CFinalInput;\nclass CStateManager;\n\n/** Options presented in UI */\nenum class EGameOption {\n  VisorOpacity,\n  HelmetOpacity,\n  HUDLag,\n  HintSystem,\n  ScreenBrightness,\n  ScreenOffsetX,\n  ScreenOffsetY,\n  ScreenStretch,\n  SFXVolume,\n  MusicVolume,\n  SoundMode,\n  ReverseYAxis,\n  Rumble,\n  SwapBeamControls,\n  RestoreDefaults\n};\n\n/** Option UI type */\nenum class EOptionType { Float, DoubleEnum, TripleEnum, RestoreDefaults };\n\n/** Option UI presentation information */\nstruct SGameOption {\n  EGameOption option;\n  u32 stringId;\n  float minVal, maxVal, increment;\n  EOptionType type;\n};\n\n/** Static registry of Option UI presentation information */\nextern const std::array<std::pair<size_t, const SGameOption*>, 5> GameOptionsRegistry;\nextern const std::array<std::pair<size_t, const SGameOption*>, 5> GameOptionsRegistryNew;\n\n/** Options tracked persistently between game sessions */\nclass CPersistentOptions {\n  friend class CGameState;\n  std::array<u8, 98> x0_nesState{};\n  std::array<bool, 64> x68_{};\n  std::vector<std::pair<CAssetId, TEditorId>> xac_cinematicStates; /* (MLVL, Cinematic) */\n  u32 xbc_autoMapperKeyState = 0;\n  u32 xc0_frozenFpsCount = 0;\n  u32 xc4_frozenBallCount = 0;\n  u32 xc8_powerBombAmmoCount = 0;\n  u32 xcc_logScanPercent = 0;\n  bool xd0_24_fusionLinked : 1 = false;\n  bool xd0_25_normalModeBeat : 1 = false;\n  bool xd0_26_hardModeBeat : 1 = false;\n  bool xd0_27_fusionBeat : 1 = false;\n  bool xd0_28_fusionSuitActive : 1 = false;\n  bool xd0_29_allItemsCollected : 1 = false;\n\npublic:\n  CPersistentOptions() = default;\n  explicit CPersistentOptions(CInputStream& stream);\n\n  bool GetCinematicState(CAssetId mlvlId, TEditorId cineId) const;\n  void SetCinematicState(CAssetId mlvlId, TEditorId cineId, bool state);\n  u32 GetAutoMapperKeyState() const { return xbc_autoMapperKeyState; }\n  void SetAutoMapperKeyState(u32 state) { xbc_autoMapperKeyState = state; }\n  bool GetPlayerLinkedFusion() const { return xd0_24_fusionLinked; }\n  void SetPlayerLinkedFusion(bool fusionLinked) { xd0_24_fusionLinked = fusionLinked; }\n  bool GetPlayerBeatNormalMode() const { return xd0_25_normalModeBeat; }\n  void SetPlayerBeatNormalMode(bool normalModeBeat) { xd0_25_normalModeBeat = normalModeBeat; }\n  bool GetPlayerBeatHardMode() const { return xd0_26_hardModeBeat; }\n  void SetPlayerBeatHardMode(bool hardModeBeat) { xd0_26_hardModeBeat = hardModeBeat; }\n  bool GetPlayerBeatFusion() const { return xd0_27_fusionBeat; }\n  void SetPlayerBeatFusion(bool fusionBeat) { xd0_27_fusionBeat = fusionBeat; }\n  bool GetPlayerFusionSuitActive() const { return xd0_28_fusionSuitActive; }\n  void SetPlayerFusionSuitActive(bool fusionSuitActive) { xd0_28_fusionSuitActive = fusionSuitActive; }\n  bool GetAllItemsCollected() const { return xd0_29_allItemsCollected; }\n  void SetAllItemsCollected(bool allItemsCollected) { xd0_29_allItemsCollected = allItemsCollected; }\n  u32 GetLogScanPercent() const { return xcc_logScanPercent; }\n  void SetLogScanPercent(u32 percent) { xcc_logScanPercent = percent; }\n  void IncrementFrozenFpsCount() { xc0_frozenFpsCount = std::min(int(xc0_frozenFpsCount + 1), 3); }\n  bool GetShowFrozenFpsMessage() const { return xc0_frozenFpsCount != 3; }\n  void IncrementFrozenBallCount() { xc4_frozenBallCount = std::min(int(xc4_frozenBallCount + 1), 3); }\n  bool GetShowFrozenBallMessage() const { return xc4_frozenBallCount != 3; }\n  bool GetShowPowerBombAmmoMessage() const { return xc8_powerBombAmmoCount != 1; }\n  void IncrementPowerBombAmmoCount() { xc8_powerBombAmmoCount = std::min<u32>(1, xc8_powerBombAmmoCount + 1); }\n\n  void PutTo(COutputStream& w) const;\n\n  u8* GetNESState() { return x0_nesState.data(); }\n  const u8* GetNESState() const { return x0_nesState.data(); }\n};\n\n/** Options tracked per game session */\nclass CGameOptions {\n  std::array<u8, 64> x0_{};\n  CAudioSys::ESurroundModes x44_soundMode = CAudioSys::ESurroundModes::Stereo;\n  u32 x48_screenBrightness = 4;\n  s32 x4c_screenXOffset = 0;\n  s32 x50_screenYOffset = 0;\n  s32 x54_screenStretch = 0;\n  u32 x58_sfxVol = 0x7f;\n  u32 x5c_musicVol = 0x7f;\n  u32 x60_hudAlpha = 0xff;\n  u32 x64_helmetAlpha = 0xff;\n  bool x68_24_hudLag : 1;\n  bool x68_25_invertY : 1;\n  bool x68_26_rumble : 1;\n  bool x68_27_swapBeamsControls : 1;\n  bool x68_28_hintSystem : 1;\n  std::vector<std::pair<CAssetId, CAssetId>> x6c_controlTxtrMap;\n\n  s32 m_gamma = 0;\n\npublic:\n  CGameOptions();\n  explicit CGameOptions(CInputStream& stream);\n  void ResetToDefaults();\n  void InitSoundMode();\n  void EnsureSettings();\n  void PutTo(COutputStream& writer) const;\n\n  float TuneScreenBrightness() const;\n  void SetScreenBrightness(s32 value, bool apply);\n  s32 GetScreenBrightness() const { return x48_screenBrightness; }\n  void ApplyGamma();\n  void SetGamma(s32 value, bool apply);\n  s32 GetGamma() const { return m_gamma; }\n  void SetScreenPositionX(s32 position, bool apply);\n  s32 GetScreenPositionX() const { return x4c_screenXOffset; }\n  void SetScreenPositionY(s32 position, bool apply);\n  s32 GetScreenPositionY() const { return x50_screenYOffset; }\n  void SetScreenStretch(s32 stretch, bool apply);\n  s32 GetScreenStretch() const { return x54_screenStretch; }\n  void SetSfxVolume(s32 volume, bool apply);\n  s32 GetSfxVolume() const { return x58_sfxVol; }\n  void SetMusicVolume(s32 volume, bool apply);\n  s32 GetMusicVolume() const { return x5c_musicVol; }\n  void SetHUDAlpha(u32 alpha);\n  u32 GetHUDAlpha() const { return x60_hudAlpha; }\n  void SetHelmetAlpha(u32 alpha);\n  u32 GetHelmetAlpha() const { return x64_helmetAlpha; }\n  void SetHUDLag(bool lag);\n  bool GetHUDLag() const { return x68_24_hudLag; }\n  void SetSurroundMode(int mode, bool apply);\n  CAudioSys::ESurroundModes GetSurroundMode() const;\n  void SetInvertYAxis(bool invert);\n  bool GetInvertYAxis() const { return x68_25_invertY; }\n  void SetIsRumbleEnabled(bool rumble);\n  bool GetIsRumbleEnabled() const { return x68_26_rumble; }\n  void SetSwapBeamControls(bool swap);\n  bool GetSwapBeamControls() const { return x68_27_swapBeamsControls; }\n  void SetIsHintSystemEnabled(bool hints);\n  bool GetIsHintSystemEnabled() const { return x68_28_hintSystem; }\n  void SetControls(int controls);\n  void ResetControllerAssets(int controls);\n  const std::vector<std::pair<CAssetId, CAssetId>>& GetControlTXTRMap() const { return x6c_controlTxtrMap; }\n\n  static void TryRestoreDefaults(const CFinalInput& input, int category, int option, bool frontend, bool forceRestore);\n  static void SetOption(EGameOption option, int value);\n  static int GetOption(EGameOption option);\n};\n\nclass CHintOptions {\npublic:\n  enum class EHintState { Zero, Waiting, Displaying, Delayed };\n  struct SHintState {\n    EHintState x0_state = EHintState::Zero;\n    float x4_time = 0.f;\n    bool x8_dismissed = false;\n\n    SHintState() = default;\n    SHintState(EHintState state, float time, bool flag) : x0_state(state), x4_time(time), x8_dismissed(flag) {}\n\n    bool CanContinue() const { return x4_time / 3.f <= 1.f; }\n  };\n\nprivate:\n  std::vector<SHintState> x0_hintStates;\n  u32 x10_nextHintIdx = -1;\n\npublic:\n  CHintOptions() = default;\n  explicit CHintOptions(CInputStream& stream);\n  void PutTo(COutputStream& writer) const;\n  void SetNextHintTime();\n  void InitializeMemoryState();\n  const SHintState* GetCurrentDisplayedHint() const;\n  void DelayHint(std::string_view name);\n  void ActivateImmediateHintTimer(std::string_view name);\n  void ActivateContinueDelayHintTimer(std::string_view name);\n  void DismissDisplayedHint();\n  u32 GetNextHintIdx() const;\n  const std::vector<SHintState>& GetHintStates() const { return x0_hintStates; }\n  void Update(float dt, const CStateManager& stateMgr);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CGameState.cpp",
    "content": "#include \"Runtime/CGameState.hpp\"\n\n#include \"Runtime/CMemoryCardSys.hpp\"\n#include \"Runtime/CWorldSaveGameInfo.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/MP1/MP1.hpp\"\n\n#include <zeus/Math.hpp>\n\nnamespace metaforce {\nunion BitsToDouble {\n  struct {\n#if BYTE_ORDER == __LITTLE_ENDIAN\n    u32 high;\n    u32 low;\n#else\n    u32 low;\n    u32 high;\n#endif\n  };\n  double doub;\n};\n\nCScriptLayerManager::CScriptLayerManager(CInputStream& reader, const CWorldSaveGameInfo& saveWorld) {\n  const u32 bitCount = reader.ReadBits(10);\n  x10_saveLayers.Reserve(bitCount);\n\n  for (u32 i = 0; i < bitCount; ++i) {\n    const bool bit = reader.ReadBits(1) != 0;\n    if (bit) {\n      x10_saveLayers.SetBit(i);\n    } else {\n      x10_saveLayers.UnsetBit(i);\n    }\n  }\n}\n\nvoid CScriptLayerManager::PutTo(COutputStream& writer) const {\n  u32 totalLayerCount = 0;\n  for (size_t i = 0; i < x0_areaLayers.size(); ++i) {\n    totalLayerCount += GetAreaLayerCount(s32(i)) - 1;\n  }\n\n  writer.WriteBits(totalLayerCount, 10);\n\n  for (size_t i = 0; i < x0_areaLayers.size(); ++i) {\n    const u32 count = GetAreaLayerCount(s32(i));\n    for (u32 l = 1; l < count; ++l) {\n      writer.WriteBits(static_cast<u32>(IsLayerActive(s32(i), s32(l))), 1);\n    }\n  }\n}\n\nvoid CScriptLayerManager::InitializeWorldLayers(const std::vector<CWorldLayers::Area>& layers) {\n  if (!x0_areaLayers.empty()) {\n    return;\n  }\n\n  x0_areaLayers = layers;\n  if (x10_saveLayers.GetBitCount() == 0) {\n    return;\n  }\n\n  u32 a = 0;\n  u32 b = 0;\n  for (const CWorldLayers::Area& area : x0_areaLayers) {\n    for (u32 l = 1; l < area.m_layerCount; ++l) {\n      SetLayerActive(a, l, x10_saveLayers.GetBit(b++));\n    }\n    ++a;\n  }\n\n  x10_saveLayers.Clear();\n}\n\nCWorldState::CWorldState(CAssetId id) : x0_mlvlId(id), x4_areaId(0) {\n  x8_mailbox = std::make_shared<CScriptMailbox>();\n  xc_mapWorldInfo = std::make_shared<CMapWorldInfo>();\n  x10_desiredAreaAssetId = {};\n  x14_layerState = std::make_shared<CScriptLayerManager>();\n}\n\nCWorldState::CWorldState(CInputStream& reader, CAssetId mlvlId, const CWorldSaveGameInfo& saveWorld)\n: x0_mlvlId(mlvlId), x4_areaId(TAreaId(reader.ReadBits(32))), x10_desiredAreaAssetId(reader.ReadBits(32)) {\n  x8_mailbox = std::make_shared<CScriptMailbox>(reader, saveWorld);\n  xc_mapWorldInfo = std::make_shared<CMapWorldInfo>(reader, saveWorld, mlvlId);\n  x14_layerState = std::make_shared<CScriptLayerManager>(reader, saveWorld);\n}\n\nvoid CWorldState::PutTo(COutputStream& writer, const CWorldSaveGameInfo& savw) const {\n  writer.WriteBits(x4_areaId, 32);\n  writer.WriteBits(u32(x10_desiredAreaAssetId.Value()), 32);\n  x8_mailbox->PutTo(writer, savw);\n  xc_mapWorldInfo->PutTo(writer, savw, x0_mlvlId);\n  x14_layerState->PutTo(writer);\n}\n\nCGameState::GameFileStateInfo CGameState::LoadGameFileState(const u8* data) {\n  CMemoryInStream stream(data, 4096, CMemoryInStream::EOwnerShip::NotOwned);\n  GameFileStateInfo ret;\n\n  for (u32 i = 0; i < 128; i++) {\n    stream.ReadBits(8);\n  }\n  ret.x14_timestamp = stream.ReadBits(32);\n\n  ret.x20_hardMode = stream.ReadBits(1) != 0;\n  stream.ReadBits(1);\n  const CAssetId origMLVL = u32(stream.ReadBits(32));\n  ret.x8_mlvlId = origMLVL;\n\n  BitsToDouble conv;\n  conv.low = stream.ReadBits(32);\n  conv.high = stream.ReadBits(32);\n  ret.x0_playTime = conv.doub;\n\n  CPlayerState playerState(stream);\n  ret.x10_energyTanks = playerState.GetItemCapacity(CPlayerState::EItemType::EnergyTanks);\n  ret.xc_health = playerState.GetHealthInfo().GetHP();\n\n  u32 itemPercent;\n  if (origMLVL == 0x158EFE17u)\n    itemPercent = 0;\n  else\n    itemPercent = playerState.CalculateItemCollectionRate() * 100 / playerState.GetPickupTotal();\n\n  ret.x18_itemPercent = itemPercent;\n\n  float scanPercent;\n  if (playerState.GetTotalLogScans() == 0)\n    scanPercent = 0.f;\n  else\n    scanPercent = 100.f * playerState.GetLogScans() / float(playerState.GetTotalLogScans());\n  ret.x1c_scanPercent = scanPercent;\n\n  return ret;\n}\n\nCGameState::CGameState() {\n  x98_playerState = std::make_shared<CPlayerState>();\n  x9c_transManager = std::make_shared<CWorldTransManager>();\n\n  if (g_MemoryCardSys != nullptr) {\n    InitializeMemoryStates();\n  }\n}\n\nCGameState::CGameState(CInputStream& stream, u32 saveIdx) : x20c_saveFileIdx(saveIdx) {\n  x9c_transManager = std::make_shared<CWorldTransManager>();\n  x228_24_hardMode = false;\n  x228_25_initPowerupsAtFirstSpawn = true;\n\n  for (bool& value : x0_) {\n    value = stream.ReadBits(8) != 0;\n  }\n  stream.ReadBits(32);\n\n  x228_24_hardMode = stream.ReadBits(1) != 0;\n  x228_25_initPowerupsAtFirstSpawn = stream.ReadBits(1) != 0;\n  x84_mlvlId = u32(stream.ReadBits(32));\n  MP1::CMain::EnsureWorldPakReady(x84_mlvlId);\n\n  BitsToDouble conv;\n  conv.low = stream.ReadBits(32);\n  conv.high = stream.ReadBits(32);\n  xa0_playTime = conv.doub;\n\n  x98_playerState = std::make_shared<CPlayerState>(stream);\n\n  x17c_gameOptions = CGameOptions(stream);\n  x1f8_hintOptions = CHintOptions(stream);\n\n  const auto& memWorlds = g_MemoryCardSys->GetMemoryWorlds();\n  x88_worldStates.reserve(memWorlds.size());\n  for (const auto& memWorld : memWorlds) {\n    TLockedToken<CWorldSaveGameInfo> saveWorld =\n        g_SimplePool->GetObj(SObjectTag{FOURCC('SAVW'), memWorld.second.GetSaveWorldAssetId()});\n    x88_worldStates.emplace_back(stream, memWorld.first, *saveWorld);\n  }\n\n  InitializeMemoryWorlds();\n  WriteBackupBuf();\n}\n\nvoid CGameState::ReadPersistentOptions(CInputStream& r) { xa8_systemOptions = r.Get<CPersistentOptions>(); }\n\nvoid CGameState::ImportPersistentOptions(const CPersistentOptions& opts) {\n  if (opts.xd0_24_fusionLinked)\n    xa8_systemOptions.xd0_24_fusionLinked = true;\n  if (opts.xd0_27_fusionBeat)\n    xa8_systemOptions.xd0_27_fusionBeat = true;\n  if (&opts != &xa8_systemOptions)\n    xa8_systemOptions.x0_nesState = opts.x0_nesState;\n  xa8_systemOptions.SetLogScanPercent(opts.GetLogScanPercent());\n  xa8_systemOptions.SetAllItemsCollected(opts.GetAllItemsCollected());\n  xa8_systemOptions.SetPlayerBeatNormalMode(opts.GetPlayerBeatNormalMode());\n  xa8_systemOptions.SetPlayerBeatHardMode(opts.GetPlayerBeatHardMode());\n}\n\nvoid CGameState::ExportPersistentOptions(CPersistentOptions& opts) const {\n  if (xa8_systemOptions.xd0_24_fusionLinked)\n    opts.xd0_24_fusionLinked = true;\n  if (xa8_systemOptions.xd0_27_fusionBeat)\n    opts.xd0_27_fusionBeat = true;\n  if (&opts != &xa8_systemOptions)\n    opts.x0_nesState = xa8_systemOptions.x0_nesState;\n  opts.SetPlayerFusionSuitActive(xa8_systemOptions.GetPlayerFusionSuitActive());\n}\n\nvoid CGameState::WriteBackupBuf() {\n  x218_backupBuf.resize(940);\n  CMemoryStreamOut w(x218_backupBuf.data(), 940);\n  PutTo(w);\n}\n\nvoid CGameState::PutTo(COutputStream& writer) {\n  for (const bool value : x0_) {\n    writer.WriteBits(u32(value), 8);\n  }\n\n  writer.WriteBits(CBasics::GetTime() / CBasics::TICKS_PER_SECOND, 32);\n  writer.WriteBits(x228_24_hardMode, 1);\n  writer.WriteBits(x228_25_initPowerupsAtFirstSpawn, 1);\n  writer.WriteBits(u32(x84_mlvlId.Value()), 32);\n\n  BitsToDouble conv;\n  conv.doub = xa0_playTime;\n  writer.WriteBits(conv.low, 32);\n  writer.WriteBits(conv.high, 32);\n\n  x98_playerState->PutTo(writer);\n  x17c_gameOptions.PutTo(writer);\n  x1f8_hintOptions.PutTo(writer);\n\n  const auto& memWorlds = g_MemoryCardSys->GetMemoryWorlds();\n  for (const auto& memWorld : memWorlds) {\n    TLockedToken<CWorldSaveGameInfo> saveWorld =\n        g_SimplePool->GetObj(SObjectTag{FOURCC('SAVW'), memWorld.second.GetSaveWorldAssetId()});\n    const CWorldState& wld = StateForWorld(memWorld.first);\n    wld.PutTo(writer, *saveWorld);\n  }\n}\n\nvoid CGameState::SetCurrentWorldId(CAssetId id) {\n  StateForWorld(id);\n  x84_mlvlId = id;\n  MP1::CMain::EnsureWorldPakReady(x84_mlvlId);\n}\n\nvoid CGameState::SetTotalPlayTime(double time) { xa0_playTime = zeus::clamp(0.0, time, 359999.0); }\n\nCWorldState& CGameState::StateForWorld(CAssetId mlvlId) {\n  auto it = x88_worldStates.begin();\n  for (; it != x88_worldStates.end(); ++it) {\n    if (it->GetWorldAssetId() == mlvlId)\n      break;\n  }\n\n  if (it == x88_worldStates.end()) {\n    x88_worldStates.emplace_back(mlvlId);\n    return x88_worldStates.back();\n  }\n  return *it;\n}\n\nfloat CGameState::GetHardModeDamageMultiplier() const { return g_tweakGame->GetHardModeDamageMultiplier(); }\n\nfloat CGameState::GetHardModeWeaponMultiplier() const { return g_tweakGame->GetHardModeWeaponMultiplier(); }\n\nvoid CGameState::InitializeMemoryWorlds() {\n  const auto& memoryWorlds = g_MemoryCardSys->GetMemoryWorlds();\n  for (const auto& wld : memoryWorlds) {\n    const auto& layerState = StateForWorld(wld.first).GetLayerState();\n    layerState->InitializeWorldLayers(wld.second.GetDefaultLayerStates());\n  }\n}\n\nvoid CGameState::InitializeMemoryStates() {\n  x98_playerState->InitializeScanTimes();\n  x1f8_hintOptions.InitializeMemoryState();\n  InitializeMemoryWorlds();\n  WriteBackupBuf();\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CGameState.hpp",
    "content": "#pragma once\n\n#include <array>\n#include <memory>\n#include <vector>\n\n#include \"Runtime/AutoMapper/CMapWorldInfo.hpp\"\n#include \"Runtime/CBasics.hpp\"\n#include \"Runtime/CGameOptions.hpp\"\n#include \"Runtime/CPlayerState.hpp\"\n#include \"Runtime/CScriptMailbox.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n#include \"Runtime/World/CWorldTransManager.hpp\"\n\nnamespace metaforce {\nclass CSaveWorldMemory;\n\nclass WordBitmap {\n  std::vector<u32> x0_words;\n  size_t x10_bitCount = 0;\n\npublic:\n  void Reserve(size_t bitCount) { x0_words.reserve((bitCount + 31) / 32); }\n  [[nodiscard]] size_t GetBitCount() const { return x10_bitCount; }\n  [[nodiscard]] bool GetBit(size_t idx) const {\n    size_t wordIdx = idx / 32;\n    if (wordIdx >= x0_words.size()) {\n      return false;\n    }\n    size_t wordCur = idx % 32;\n    return ((x0_words[wordIdx] >> wordCur) & 0x1) != 0u;\n  }\n  void SetBit(size_t idx) {\n    size_t wordIdx = idx / 32;\n    while (wordIdx >= x0_words.size()) {\n      x0_words.push_back(0);\n    }\n    size_t wordCur = idx % 32;\n    x0_words[wordIdx] |= (1 << wordCur);\n    x10_bitCount = std::max(x10_bitCount, idx + 1);\n  }\n  void UnsetBit(size_t idx) {\n    size_t wordIdx = idx / 32;\n    while (wordIdx >= x0_words.size()) {\n      x0_words.push_back(0);\n    }\n    size_t wordCur = idx % 32;\n    x0_words[wordIdx] &= ~(1 << wordCur);\n    x10_bitCount = std::max(x10_bitCount, idx + 1);\n  }\n  void Clear() {\n    x0_words.clear();\n    x10_bitCount = 0;\n  }\n\n  class Iterator {\n    friend class WordBitmap;\n    const WordBitmap& m_bmp;\n    size_t m_idx = 0;\n    Iterator(const WordBitmap& bmp, size_t idx) : m_bmp(bmp), m_idx(idx) {}\n\n  public:\n    using iterator_category = std::forward_iterator_tag;\n    using value_type = bool;\n    using difference_type = std::ptrdiff_t;\n    using pointer = bool*;\n    using reference = bool&;\n\n    Iterator& operator++() {\n      ++m_idx;\n      return *this;\n    }\n    bool operator*() const { return m_bmp.GetBit(m_idx); }\n    bool operator!=(const Iterator& other) const { return m_idx != other.m_idx; }\n  };\n  [[nodiscard]] Iterator begin() const { return Iterator(*this, 0); }\n  [[nodiscard]] Iterator end() const { return Iterator(*this, x10_bitCount); }\n};\n\nclass CScriptLayerManager {\n  friend class CSaveWorldIntermediate;\n  std::vector<CWorldLayers::Area> x0_areaLayers;\n  WordBitmap x10_saveLayers;\n\npublic:\n  CScriptLayerManager() = default;\n  CScriptLayerManager(CInputStream& reader, const CWorldSaveGameInfo& saveWorld);\n\n  [[nodiscard]] bool IsLayerActive(int areaIdx, int layerIdx) const {\n    return ((x0_areaLayers[areaIdx].m_layerBits >> layerIdx) & 1) != 0u;\n  }\n\n  void SetLayerActive(int areaIdx, int layerIdx, bool active) {\n    if (active) {\n      x0_areaLayers[areaIdx].m_layerBits |= uint64_t(1) << layerIdx;\n    } else {\n      x0_areaLayers[areaIdx].m_layerBits &= ~(uint64_t(1) << layerIdx);\n    }\n  }\n\n  void InitializeWorldLayers(const std::vector<CWorldLayers::Area>& layers);\n\n  [[nodiscard]] u32 GetAreaLayerCount(int areaIdx) const { return x0_areaLayers[areaIdx].m_layerCount; }\n  [[nodiscard]] u32 GetAreaCount() const { return x0_areaLayers.size(); }\n\n  void PutTo(COutputStream& writer) const;\n};\n\nclass CWorldState {\n  CAssetId x0_mlvlId;\n  TAreaId x4_areaId = kInvalidAreaId;\n  std::shared_ptr<CScriptMailbox> x8_mailbox;\n  std::shared_ptr<CMapWorldInfo> xc_mapWorldInfo;\n  CAssetId x10_desiredAreaAssetId;\n  std::shared_ptr<CScriptLayerManager> x14_layerState;\n\npublic:\n  explicit CWorldState(CAssetId id);\n  CWorldState(CInputStream& reader, CAssetId mlvlId, const CWorldSaveGameInfo& saveWorld);\n  CAssetId GetWorldAssetId() const { return x0_mlvlId; }\n  void SetAreaId(TAreaId aid) { x4_areaId = aid; }\n  TAreaId GetCurrentAreaId() const { return x4_areaId; }\n  CAssetId GetDesiredAreaAssetId() const { return x10_desiredAreaAssetId; }\n  void SetDesiredAreaAssetId(CAssetId id) { x10_desiredAreaAssetId = id; }\n  const std::shared_ptr<CScriptMailbox>& Mailbox() const { return x8_mailbox; }\n  const std::shared_ptr<CMapWorldInfo>& MapWorldInfo() const { return xc_mapWorldInfo; }\n  const std::shared_ptr<CScriptLayerManager>& GetLayerState() const { return x14_layerState; }\n  void PutTo(COutputStream& writer, const CWorldSaveGameInfo& savw) const;\n};\n\nclass CGameState {\n  friend class CStateManager;\n\n  std::array<bool, 128> x0_{};\n  u32 x80_ = 0;\n  CAssetId x84_mlvlId;\n  std::vector<CWorldState> x88_worldStates;\n  std::shared_ptr<CPlayerState> x98_playerState;\n  std::shared_ptr<CWorldTransManager> x9c_transManager;\n  double xa0_playTime = 0.0;\n  CPersistentOptions xa8_systemOptions;\n  CGameOptions x17c_gameOptions;\n  CHintOptions x1f8_hintOptions;\n  u32 x20c_saveFileIdx = 0;\n  u64 x210_cardSerial = 0;\n  std::vector<u8> x218_backupBuf;\n  bool x228_24_hardMode : 1 = false;\n  bool x228_25_initPowerupsAtFirstSpawn : 1 = true;\n\npublic:\n  CGameState();\n  CGameState(CInputStream& stream, u32 saveIdx);\n  void SetCurrentWorldId(CAssetId id);\n  std::shared_ptr<CPlayerState> GetPlayerState() const { return x98_playerState; }\n  std::shared_ptr<CWorldTransManager> GetWorldTransitionManager() const { return x9c_transManager; }\n  void SetTotalPlayTime(double time);\n  double GetTotalPlayTime() const { return xa0_playTime; }\n  CPersistentOptions& SystemOptions() { return xa8_systemOptions; }\n  CGameOptions& GameOptions() { return x17c_gameOptions; }\n  CHintOptions& HintOptions() { return x1f8_hintOptions; }\n  CWorldState& StateForWorld(CAssetId mlvlId);\n  CWorldState& CurrentWorldState() { return StateForWorld(x84_mlvlId); }\n  CAssetId CurrentWorldAssetId() const { return x84_mlvlId; }\n  void SetHardMode(bool v) { x228_24_hardMode = v; }\n  bool GetHardMode() const { return x228_24_hardMode; }\n  void ReadPersistentOptions(CInputStream& r);\n  void SetPersistentOptions(const CPersistentOptions& opts) { xa8_systemOptions = opts; }\n  void ImportPersistentOptions(const CPersistentOptions& opts);\n  void ExportPersistentOptions(CPersistentOptions& opts) const;\n  void SetGameOptions(const CGameOptions& opts) { x17c_gameOptions = opts; }\n  void WriteBackupBuf();\n  std::vector<u8>& BackupBuf() { return x218_backupBuf; }\n  u32 GetFileIdx() const { return x20c_saveFileIdx; }\n  void SetFileIdx(u32 idx) { x20c_saveFileIdx = idx; }\n  void SetCardSerial(u64 serial) { x210_cardSerial = serial; }\n  u64 GetCardSerial() const { return x210_cardSerial; }\n  void PutTo(COutputStream& writer);\n  float GetHardModeDamageMultiplier() const;\n  float GetHardModeWeaponMultiplier() const;\n  void InitializeMemoryWorlds();\n  void InitializeMemoryStates();\n\n  struct GameFileStateInfo {\n    double x0_playTime;\n    CAssetId x8_mlvlId;\n    float xc_health;\n    u32 x10_energyTanks;\n    u32 x14_timestamp;\n    u32 x18_itemPercent;\n    float x1c_scanPercent;\n    bool x20_hardMode;\n  };\n  static GameFileStateInfo LoadGameFileState(const u8* data);\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CIOWin.hpp",
    "content": "#pragma once\n\n#include <functional>\n#include <memory>\n#include <string>\n\n#include \"Runtime/RetroTypes.hpp\"\n\nnamespace metaforce {\nclass CArchitectureMessage;\nclass CArchitectureQueue;\n\nclass CIOWin {\n  std::string x4_name;\n  size_t m_nameHash;\n\npublic:\n  enum class EMessageReturn { Normal = 0, Exit = 1, RemoveIOWinAndExit = 2, RemoveIOWin = 3 };\n  explicit CIOWin(std::string_view name) : x4_name(name) { m_nameHash = std::hash<std::string_view>()(name); }\n\n  virtual ~CIOWin() = default;\n  virtual EMessageReturn OnMessage(const CArchitectureMessage&, CArchitectureQueue&) = 0;\n  virtual bool GetIsContinueDraw() const { return true; }\n  virtual void Draw() {}\n  virtual void PreDraw() {}\n\n  std::string_view GetName() const { return x4_name; }\n  size_t GetNameHash() const { return m_nameHash; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CIOWinManager.cpp",
    "content": "#include \"Runtime/CIOWinManager.hpp\"\n\n#include \"Runtime/CArchitectureMessage.hpp\"\n#include \"Runtime/CIOWin.hpp\"\n\nnamespace metaforce {\n\nbool CIOWinManager::OnIOWinMessage(const CArchitectureMessage& msg) {\n  switch (msg.GetType()) {\n  case EArchMsgType::RemoveIOWin: {\n    const CArchMsgParmVoidPtr& parm = MakeMsg::GetParmDeleteIOWin(msg);\n    CIOWin* iow = FindIOWin(*static_cast<const std::string*>(parm.x4_parm1));\n    if (iow)\n      RemoveIOWin(iow);\n    return false;\n  }\n  case EArchMsgType::CreateIOWin: {\n    const CArchMsgParmInt32Int32IOWin& parm = MakeMsg::GetParmCreateIOWin(msg);\n    AddIOWin(parm.xc_parm3, parm.x4_parm1, parm.x8_parm2);\n    return false;\n  }\n  case EArchMsgType::ChangeIOWinPriority: {\n    const CArchMsgParmInt32Int32VoidPtr& parm = MakeMsg::GetParmChangeIOWinPriority(msg);\n    CIOWin* iow = FindIOWin(*static_cast<const std::string*>(parm.xc_parm3));\n    if (iow)\n      ChangeIOWinPriority(iow, parm.x4_parm1, parm.x8_parm2);\n    return false;\n  }\n  case EArchMsgType::RemoveAllIOWins: {\n    RemoveAllIOWins();\n    return true;\n  }\n  default:\n    break;\n  }\n  return false;\n}\n\nvoid CIOWinManager::Draw() const {\n  for (IOWinPQNode* node = x0_drawRoot; node; node = node->x8_next) {\n    CIOWin* iow = node->GetIOWin();\n    iow->PreDraw();\n    if (!iow->GetIsContinueDraw())\n      break;\n  }\n  for (IOWinPQNode* node = x0_drawRoot; node; node = node->x8_next) {\n    CIOWin* iow = node->GetIOWin();\n    iow->Draw();\n    if (!iow->GetIsContinueDraw())\n      break;\n  }\n}\n\nbool CIOWinManager::DistributeOneMessage(const CArchitectureMessage& msg, CArchitectureQueue& queue) {\n  CArchitectureMessage tmpMsg = msg;\n  for (IOWinPQNode* node = x4_pumpRoot; node;) {\n    IOWinPQNode* next = node->x8_next;\n    CIOWin* iow = node->GetIOWin();\n    CIOWin::EMessageReturn mret = iow->OnMessage(tmpMsg, x8_localGatherQueue);\n\n    while (x8_localGatherQueue) {\n      tmpMsg = x8_localGatherQueue.Pop();\n      if (tmpMsg.GetTarget() == EArchMsgTarget::IOWinManager) {\n        if (OnIOWinMessage(tmpMsg)) {\n          x8_localGatherQueue.Clear();\n          queue.Clear();\n          return true;\n        }\n      } else\n        queue.Push(std::move(tmpMsg));\n    }\n\n    switch (mret) {\n    case CIOWin::EMessageReturn::RemoveIOWinAndExit:\n    case CIOWin::EMessageReturn::RemoveIOWin:\n      RemoveIOWin(iow);\n      break;\n    default:\n      break;\n    }\n\n    switch (mret) {\n    case CIOWin::EMessageReturn::Exit:\n    case CIOWin::EMessageReturn::RemoveIOWinAndExit:\n      return false;\n    default:\n      break;\n    }\n\n    node = next;\n  }\n\n  return false;\n}\n\nvoid CIOWinManager::PumpMessages(CArchitectureQueue& queue) {\n  while (queue) {\n    CArchitectureMessage msg = queue.Pop();\n    if (DistributeOneMessage(msg, queue))\n      break;\n  }\n}\n\nCIOWin* CIOWinManager::FindIOWin(std::string_view name) {\n  size_t findHash = std::hash<std::string_view>()(name);\n\n  for (IOWinPQNode* node = x4_pumpRoot; node; node = node->x8_next) {\n    CIOWin* iow = node->GetIOWin();\n    if (iow->GetNameHash() == findHash)\n      return iow;\n  }\n\n  for (IOWinPQNode* node = x0_drawRoot; node; node = node->x8_next) {\n    CIOWin* iow = node->GetIOWin();\n    if (iow->GetNameHash() == findHash)\n      return iow;\n  }\n\n  return nullptr;\n}\n\nstd::shared_ptr<CIOWin> CIOWinManager::FindAndShareIOWin(std::string_view name) {\n  size_t findHash = std::hash<std::string_view>()(name);\n\n  for (IOWinPQNode* node = x4_pumpRoot; node; node = node->x8_next) {\n    std::shared_ptr<CIOWin> iow = node->ShareIOWin();\n    if (iow->GetNameHash() == findHash)\n      return iow;\n  }\n\n  for (IOWinPQNode* node = x0_drawRoot; node; node = node->x8_next) {\n    std::shared_ptr<CIOWin> iow = node->ShareIOWin();\n    if (iow->GetNameHash() == findHash)\n      return iow;\n  }\n\n  return std::shared_ptr<CIOWin>();\n}\n\nvoid CIOWinManager::ChangeIOWinPriority(CIOWin* toChange, int pumpPrio, int drawPrio) {\n  IOWinPQNode* prevNode = nullptr;\n  for (IOWinPQNode* node = x4_pumpRoot; node; node = node->x8_next) {\n    CIOWin* iow = node->GetIOWin();\n    if (iow == toChange) {\n      if (prevNode)\n        prevNode->x8_next = node->x8_next;\n      node->x4_prio = pumpPrio;\n      IOWinPQNode* testNode = x4_pumpRoot;\n      IOWinPQNode* testPrevNode = nullptr;\n      while (testNode->x4_prio > pumpPrio) {\n        testPrevNode = testNode;\n        testNode = testNode->x8_next;\n      }\n      node->x8_next = testNode;\n      if (testPrevNode)\n        testPrevNode->x8_next = node;\n      else\n        x4_pumpRoot = node;\n      break;\n    }\n    prevNode = node;\n  }\n\n  prevNode = nullptr;\n  for (IOWinPQNode* node = x0_drawRoot; node; node = node->x8_next) {\n    CIOWin* iow = node->GetIOWin();\n    if (iow == toChange) {\n      if (prevNode)\n        prevNode->x8_next = node->x8_next;\n      node->x4_prio = drawPrio;\n      IOWinPQNode* testNode = x0_drawRoot;\n      IOWinPQNode* testPrevNode = nullptr;\n      while (testNode->x4_prio > drawPrio) {\n        testPrevNode = testNode;\n        testNode = testNode->x8_next;\n      }\n      node->x8_next = testNode;\n      if (testPrevNode)\n        testPrevNode->x8_next = node;\n      else\n        x0_drawRoot = node;\n      break;\n    }\n    prevNode = node;\n  }\n}\n\nvoid CIOWinManager::RemoveAllIOWins() {\n  for (IOWinPQNode* node = x0_drawRoot; node;) {\n    IOWinPQNode* n = node;\n    node = n->x8_next;\n    delete n;\n  }\n  x0_drawRoot = nullptr;\n  for (IOWinPQNode* node = x4_pumpRoot; node;) {\n    IOWinPQNode* n = node;\n    node = n->x8_next;\n    delete n;\n  }\n  x4_pumpRoot = nullptr;\n}\n\nvoid CIOWinManager::RemoveIOWin(CIOWin* chIow) {\n  IOWinPQNode* prevNode = nullptr;\n  for (IOWinPQNode* node = x4_pumpRoot; node; node = node->x8_next) {\n    CIOWin* iow = node->GetIOWin();\n    if (iow == chIow) {\n      if (prevNode)\n        prevNode->x8_next = node->x8_next;\n      else\n        x4_pumpRoot = node->x8_next;\n      delete node;\n      break;\n    }\n    prevNode = node;\n  }\n\n  prevNode = nullptr;\n  for (IOWinPQNode* node = x0_drawRoot; node; node = node->x8_next) {\n    CIOWin* iow = node->GetIOWin();\n    if (iow == chIow) {\n      if (prevNode)\n        prevNode->x8_next = node->x8_next;\n      else\n        x0_drawRoot = node->x8_next;\n      delete node;\n      break;\n    }\n    prevNode = node;\n  }\n}\n\nvoid CIOWinManager::AddIOWin(std::weak_ptr<CIOWin> chIow, int pumpPrio, int drawPrio) {\n  IOWinPQNode* node;\n  IOWinPQNode* prevNode = nullptr;\n  for (node = x4_pumpRoot; node && pumpPrio < node->x4_prio; node = node->x8_next)\n    prevNode = node;\n  IOWinPQNode* newNode = new IOWinPQNode(chIow, pumpPrio, node);\n  if (prevNode)\n    prevNode->x8_next = newNode;\n  else\n    x4_pumpRoot = newNode;\n\n  prevNode = nullptr;\n  for (node = x0_drawRoot; node && drawPrio < node->x4_prio; node = node->x8_next)\n    prevNode = node;\n  newNode = new IOWinPQNode(chIow, drawPrio, node);\n  if (prevNode)\n    prevNode->x8_next = newNode;\n  else\n    x0_drawRoot = newNode;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CIOWinManager.hpp",
    "content": "#pragma once\n\n#include <list>\n#include <memory>\n\n#include \"Runtime/CArchitectureQueue.hpp\"\n#include \"Runtime/CIOWin.hpp\"\n#include \"Runtime/rstl.hpp\"\n\nnamespace metaforce {\n\nclass CIOWinManager {\n  struct IOWinPQNode {\n    std::shared_ptr<CIOWin> x0_iowin;\n    int x4_prio;\n    CIOWinManager::IOWinPQNode* x8_next = nullptr;\n    IOWinPQNode(std::weak_ptr<CIOWin> iowin, int prio, CIOWinManager::IOWinPQNode* next)\n    : x0_iowin(iowin), x4_prio(prio), x8_next(next) {}\n    std::shared_ptr<CIOWin> ShareIOWin() const { return std::shared_ptr<CIOWin>(x0_iowin); }\n    CIOWin* GetIOWin() const { return x0_iowin.get(); }\n  };\n  IOWinPQNode* x0_drawRoot = nullptr;\n  IOWinPQNode* x4_pumpRoot = nullptr;\n  CArchitectureQueue x8_localGatherQueue;\n\npublic:\n  bool OnIOWinMessage(const CArchitectureMessage& msg);\n  void Draw() const;\n  bool DistributeOneMessage(const CArchitectureMessage& msg, CArchitectureQueue& queue);\n  void PumpMessages(CArchitectureQueue& queue);\n  CIOWin* FindIOWin(std::string_view name);\n  std::shared_ptr<CIOWin> FindAndShareIOWin(std::string_view name);\n  void ChangeIOWinPriority(CIOWin* toChange, int pumpPrio, int drawPrio);\n  void RemoveAllIOWins();\n  void RemoveIOWin(CIOWin* toRemove);\n  void AddIOWin(std::weak_ptr<CIOWin> toAdd, int pumpPrio, int drawPrio);\n  bool IsEmpty() const { return x0_drawRoot == nullptr && x4_pumpRoot == nullptr; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CInGameTweakManagerBase.hpp",
    "content": "#pragma once\n\n#include <algorithm>\n#include <string>\n#include <vector>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\nnamespace metaforce {\n\nclass CTweakValue {\npublic:\n  struct Audio {\n    float x0_fadeIn, x4_fadeOut, x8_volume;\n    std::string xc_fileName;\n    CAssetId x1c_res;\n    Audio() = default;\n    Audio(float fadeIn, float fadeOut, float vol, std::string_view fileName, u32 handle)\n    : x0_fadeIn(fadeIn), x4_fadeOut(fadeOut), x8_volume(vol), xc_fileName(fileName), x1c_res(handle) {}\n    float GetFadeIn() const { return x0_fadeIn; }\n    float GetFadeOut() const { return x4_fadeOut; }\n    float GetVolume() const { return x8_volume; }\n    std::string_view GetFileName() const { return xc_fileName; }\n    CAssetId GetResId() const { return x1c_res; }\n    static Audio None() { return Audio{0.f, 0.f, 0.f, \"\", 0}; }\n  };\n  enum class EType {};\n\nprivate:\n  EType x0_type;\n  std::string x4_key;\n  std::string x14_str;\n  Audio x24_audio;\n  union {\n    u32 x44_int;\n    float x44_flt;\n  };\n\npublic:\n  CTweakValue() = default;\n  // CTweakValue(CTextInputStream&);\n  // void PutTo(CTextOutStream&);\n  std::string_view GetName() const { return x4_key; }\n  std::string_view GetValueAsString() const;\n  void SetValueFromString(std::string_view);\n  const Audio& GetAudio() const { return x24_audio; }\n  EType GetType() const { return x0_type; }\n};\n\nclass CInGameTweakManagerBase {\nprotected:\n  std::vector<CTweakValue> x0_values;\n\npublic:\n  bool HasTweakValue(std::string_view name) const {\n    return std::any_of(x0_values.cbegin(), x0_values.cend(),\n                       [name](const auto& value) { return value.GetName() == name; });\n  }\n\n  const CTweakValue* GetTweakValue(std::string_view name) const {\n    const auto iter = std::find_if(x0_values.cbegin(), x0_values.cend(),\n                                   [name](const auto& value) { return value.GetName() == name; });\n    if (iter == x0_values.cend()) {\n      return nullptr;\n    }\n    return &*iter;\n  }\n\n  bool ReadFromMemoryCard(std::string_view name) { return true; }\n\n  static std::string GetIdentifierForMidiEvent(CAssetId world, CAssetId area, std::string_view midiObj) {\n    return fmt::format(\"World {} Area {} MidiObject: {}\", world, area, midiObj);\n  }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CInfiniteLoopDetector.cpp",
    "content": "#include \"Runtime/CInfiniteLoopDetector.hpp\"\n#include \"Runtime/Logging.hpp\"\n\nnamespace metaforce {\nnamespace {\nstd::chrono::system_clock::time_point g_WatchDog = std::chrono::system_clock::now();\nstd::mutex g_mutex;\n} // namespace\n\nCInfiniteLoopDetector::CInfiniteLoopDetector(int duration)\n: m_duration(duration), m_futureObj(m_stopRequested.get_future()) {}\n\nbool CInfiniteLoopDetector::stopRequested() const {\n  return m_futureObj.wait_for(std::chrono::milliseconds(0)) != std::future_status::timeout;\n}\nvoid CInfiniteLoopDetector::run() {\n  std::chrono::system_clock::time_point start = std::chrono::system_clock::now();\n  while (!stopRequested()) {\n    if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - start) >\n        std::chrono::milliseconds(m_duration)) {\n      std::lock_guard<std::mutex> guard(g_mutex);\n      if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - g_WatchDog) >\n          std::chrono::milliseconds(m_duration)) {\n        spdlog::fatal(\"INFINITE LOOP DETECTED!\");\n      }\n    }\n  }\n}\n\nvoid CInfiniteLoopDetector::UpdateWatchDog(std::chrono::system_clock::time_point time) {\n  std::lock_guard<std::mutex> guard(g_mutex);\n  g_WatchDog = time;\n}\n\nvoid CInfiniteLoopDetector::stop() { m_stopRequested.set_value(); }\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CInfiniteLoopDetector.hpp",
    "content": "#pragma once\n\n#include <chrono>\n#include <mutex>\n#include <future>\n\nnamespace metaforce {\nclass CInfiniteLoopDetector {\n  int m_duration = 0;\n  std::mutex m_mutex;\n  std::promise<void> m_stopRequested;\n  std::future<void> m_futureObj;\n  bool stopRequested() const;\npublic:\n  explicit CInfiniteLoopDetector(int duration=1000);\n  void run();\n  void stop();\n  static void UpdateWatchDog(std::chrono::system_clock::time_point WatchDog);\n};\n}"
  },
  {
    "path": "Runtime/CMFGameBase.hpp",
    "content": "#pragma once\n\n#include \"Runtime/CIOWin.hpp\"\n\nnamespace metaforce {\n\nclass CMFGameBase : public CIOWin {\npublic:\n  explicit CMFGameBase(const char* name) : CIOWin(name) {}\n};\n\nclass CMFGameLoaderBase : public CIOWin {\npublic:\n  explicit CMFGameLoaderBase(const char* name) : CIOWin(name) {}\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CMain.cpp",
    "content": "#include \"CResourceNameDatabase.hpp\"\n\n#include <string>\n#include <string_view>\n#include <numeric>\n#include <iostream>\n#include <vector>\n\n#include \"ImGuiEngine.hpp\"\n#include \"Runtime/Graphics/CGraphics.hpp\"\n#include \"Runtime/MP1/MP1.hpp\"\n#include \"Runtime/ConsoleVariables/FileStoreManager.hpp\"\n#include \"Runtime/ConsoleVariables/CVarManager.hpp\"\n#include \"Runtime/CInfiniteLoopDetector.hpp\"\n#include \"Runtime/Logging.hpp\"\n// #include \"amuse/BooBackend.hpp\"\n\n#ifdef _WIN32\n#ifndef WIN32_LEAN_AND_MEAN\n#define WIN32_LEAN_AND_MEAN\n#endif\n#ifndef NOMINMAX\n#define NOMINMAX\n#endif\n#include <Windows.h>\n#include <shellapi.h>\n#include <nowide/convert.hpp>\n#endif\n\n#include \"../version.h\"\n\n// #include <fenv.h>\n// #pragma STDC FENV_ACCESS ON\n\n#include <aurora/event.h>\n#include <aurora/main.h>\n#include <dolphin/vi.h>\n#include <SDL3/SDL_log.h>\n#include <SDL3/SDL_messagebox.h>\n#if defined(ANDROID)\n#include <spdlog/sinks/android_sink.h>\n#endif\n#include \"Runtime/Graphics/CTexture.hpp\"\n\nusing namespace std::literals;\n\nclass Limiter {\n  using delta_clock = std::chrono::high_resolution_clock;\n  using duration_t = std::chrono::nanoseconds;\n\npublic:\n  void Reset() { m_oldTime = delta_clock::now(); }\n\n  void Sleep(duration_t targetFrameTime) {\n    if (targetFrameTime.count() == 0) {\n      return;\n    }\n\n    auto start = delta_clock::now();\n    duration_t adjustedSleepTime = SleepTime(targetFrameTime);\n    if (adjustedSleepTime.count() > 0) {\n      NanoSleep(adjustedSleepTime);\n      duration_t overslept = TimeSince(start) - adjustedSleepTime;\n      if (overslept < duration_t{targetFrameTime}) {\n        m_overheadTimes[m_overheadTimeIdx] = overslept;\n        m_overheadTimeIdx = (m_overheadTimeIdx + 1) % m_overheadTimes.size();\n      }\n    }\n    Reset();\n  }\n\n  duration_t SleepTime(duration_t targetFrameTime) {\n    const auto sleepTime = duration_t{targetFrameTime} - TimeSince(m_oldTime);\n    m_overhead = std::accumulate(m_overheadTimes.begin(), m_overheadTimes.end(), duration_t{}) / m_overheadTimes.size();\n    if (sleepTime > m_overhead) {\n      return sleepTime - m_overhead;\n    }\n    return duration_t{0};\n  }\n\nprivate:\n  delta_clock::time_point m_oldTime;\n  std::array<duration_t, 4> m_overheadTimes{};\n  size_t m_overheadTimeIdx = 0;\n  duration_t m_overhead = duration_t{0};\n\n  duration_t TimeSince(delta_clock::time_point start) {\n    return std::chrono::duration_cast<duration_t>(delta_clock::now() - start);\n  }\n\n#if _WIN32\n  void NanoSleep(const duration_t duration) {\n    static bool initialized = false;\n    static double countPerNs;\n    static size_t numSleeps = 0;\n\n    // QueryPerformanceFrequency's result is constant, but calling it occasionally\n    // appears to stabilize QueryPerformanceCounter. Without it, the game drifts\n    // from 60hz to 144hz. (Cursed, but I suspect it's NVIDIA/G-SYNC related)\n    if (!initialized || numSleeps++ % 1000 == 0) {\n      LARGE_INTEGER freq;\n      if (QueryPerformanceFrequency(&freq) == 0) {\n        spdlog::warn(\"QueryPerformanceFrequency failed: {}\", GetLastError());\n        return;\n      }\n      countPerNs = static_cast<double>(freq.QuadPart) / 1e9;\n      initialized = true;\n      numSleeps = 0;\n    }\n\n    LARGE_INTEGER start, current;\n    QueryPerformanceCounter(&start);\n    LONGLONG ticksToWait = static_cast<LONGLONG>(duration.count() * countPerNs);\n    if (DWORD ms = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count(); ms > 1) {\n      ::Sleep(ms - 1);\n    }\n    do {\n      QueryPerformanceCounter(&current);\n      _mm_pause(); // Yield CPU\n    } while (current.QuadPart - start.QuadPart < ticksToWait);\n  }\n#else\n  void NanoSleep(const duration_t duration) { std::this_thread::sleep_for(duration); }\n#endif\n};\n\nextern std::string ExeDir;\n\nnamespace metaforce {\nstd::optional<MP1::CMain> g_mainMP1;\nSDL_Window* g_window;\n\nstatic std::string CPUFeatureString(const zeus::CPUInfo& cpuInf) {\n  std::string features;\n#if defined(__x86_64__) || defined(_M_X64)\n  auto AddFeature = [&features](const char* str) {\n    if (!features.empty())\n      features += \", \";\n    features += str;\n  };\n  if (cpuInf.AESNI)\n    AddFeature(\"AES-NI\");\n  if (cpuInf.SSE1)\n    AddFeature(\"SSE\");\n  if (cpuInf.SSE2)\n    AddFeature(\"SSE2\");\n  if (cpuInf.SSE3)\n    AddFeature(\"SSE3\");\n  if (cpuInf.SSSE3)\n    AddFeature(\"SSSE3\");\n  if (cpuInf.SSE4a)\n    AddFeature(\"SSE4a\");\n  if (cpuInf.SSE41)\n    AddFeature(\"SSE4.1\");\n  if (cpuInf.SSE42)\n    AddFeature(\"SSE4.2\");\n  if (cpuInf.AVX)\n    AddFeature(\"AVX\");\n  if (cpuInf.AVX2)\n    AddFeature(\"AVX2\");\n#endif\n  return features;\n}\n\nstruct Application {\nprivate:\n  int m_argc;\n  char** m_argv;\n  FileStoreManager& m_fileMgr;\n  CVarManager& m_cvarManager;\n  CVarCommons& m_cvarCommons;\n  CResourceNameDatabase& m_nameDatabase;\n  ImGuiConsole m_imGuiConsole;\n\n  std::string m_deferredProject;\n  bool m_projectInitialized = false;\n  // std::optional<amuse::BooBackendVoiceAllocator> m_amuseAllocWrapper;\n  // std::unique_ptr<boo::IAudioVoiceEngine> m_voiceEngine;\n\n  Limiter m_limiter{};\n\n  bool m_firstFrame = true;\n  bool m_fullscreenToggleRequested = false;\n  bool m_quitRequested = false;\n  bool m_lAltHeld = false;\n  using delta_clock = std::chrono::high_resolution_clock;\n  delta_clock::time_point m_prevFrameTime;\n\n  std::vector<u32> m_deferredControllers; // used to capture controllers added before CInputGenerator\n                                          // is built, i.e during initialization\n\npublic:\n  Application(int argc, char** argv, FileStoreManager& fileMgr, CVarManager& cvarMgr, CVarCommons& cvarCmns,\n              CResourceNameDatabase& nameDatabase)\n  : m_argc(argc)\n  , m_argv(argv)\n  , m_fileMgr(fileMgr)\n  , m_cvarManager(cvarMgr)\n  , m_cvarCommons(cvarCmns)\n  , m_nameDatabase(nameDatabase)\n  , m_imGuiConsole(cvarMgr, cvarCmns) {}\n\n  void onAppLaunched(const AuroraInfo& info) noexcept {\n    initialize();\n\n    VISetWindowTitle(fmt::format(\"Metaforce {} [{}]\", METAFORCE_WC_DESCRIBE, backend_name(info.backend)).c_str());\n\n    //    m_voiceEngine = boo::NewAudioVoiceEngine(\"metaforce\", \"Metaforce\");\n    //    m_voiceEngine->setVolume(0.7f);\n    //    m_amuseAllocWrapper.emplace(*m_voiceEngine);\n\n#if TARGET_OS_IOS || TARGET_OS_TV\n    m_deferredProject = std::string{m_fileMgr.getStoreRoot()} + \"game.iso\";\n#else\n    for (int i = 1; i < m_argc; ++i) {\n      std::string arg = m_argv[i];\n      if (m_deferredProject.empty() && !arg.starts_with('-') && !arg.starts_with('+') &&\n          (CBasics::IsDir(arg.c_str()) || CBasics::IsFile(arg.c_str())))\n        m_deferredProject = arg;\n      else if (arg == \"--no-sound\") {\n        // m_voiceEngine->setVolume(0.f);\n      }\n    }\n#endif\n\n    // m_voiceEngine->startPump();\n  }\n\n  void initialize() {\n    zeus::detectCPU();\n\n    const zeus::CPUInfo& cpuInf = zeus::cpuFeatures();\n    spdlog::info(\"CPU Name: {}\", cpuInf.cpuBrand);\n    spdlog::info(\"CPU Vendor: {}\", cpuInf.cpuVendor);\n    spdlog::info(\"CPU Features: {}\", CPUFeatureString(cpuInf));\n  }\n\n  void onSdlEvent(const SDL_Event& event) noexcept {\n    switch (event.type) {\n    case SDL_EVENT_KEY_DOWN:\n      m_lAltHeld = event.key.key == SDLK_LALT;\n      // Toggle fullscreen on ALT+ENTER\n      if (event.key.key == SDLK_RETURN && (event.key.mod & SDL_KMOD_ALT) != 0u && event.key.repeat == 0u) {\n        m_cvarCommons.m_fullscreen->fromBoolean(!m_cvarCommons.m_fullscreen->toBoolean());\n      }\n      break;\n    case SDL_EVENT_KEY_UP:\n      if (m_lAltHeld && event.key.key == SDLK_LALT) {\n        m_imGuiConsole.ToggleVisible();\n        m_lAltHeld = false;\n      }\n      break;\n    case SDL_EVENT_DROP_FILE: {\n      m_imGuiConsole.m_gameDiscSelected = event.drop.data;\n      break;\n    }\n    default:\n      break;\n    }\n  }\n\n  bool onAppIdle(float realDt) noexcept {\n#ifdef NDEBUG\n    /* Ping the watchdog to let it know we're still alive */\n    CInfiniteLoopDetector::UpdateWatchDog(std::chrono::system_clock::now());\n#endif\n\n    if (!m_projectInitialized && !m_deferredProject.empty()) {\n      spdlog::info(\"Loading game from '{}'\", m_deferredProject);\n      if (CDvdFile::Initialize(m_deferredProject)) {\n        m_projectInitialized = true;\n        m_cvarCommons.m_lastDiscPath->fromLiteral(m_deferredProject);\n      } else {\n        const std::string_view dvdErr = CDvdFile::GetLastError();\n        if (dvdErr.empty()) {\n          spdlog::error(\"Failed to open disc image '{}'\", m_deferredProject);\n          m_imGuiConsole.m_errorString = fmt::format(\"Failed to open disc image '{}'\", m_deferredProject);\n        } else {\n          spdlog::error(\"Failed to open disc image '{}': {}\", m_deferredProject, dvdErr);\n          m_imGuiConsole.m_errorString = fmt::format(\"Failed to open disc image '{}': {}\", m_deferredProject, dvdErr);\n        }\n        if (m_deferredProject.starts_with(\"content://\")) {\n          m_cvarCommons.m_lastDiscPath->fromLiteral(\"\"sv);\n        }\n      }\n      m_deferredProject.clear();\n    }\n\n    const auto targetFrameTime = getTargetFrameTime();\n    bool skipRetrace = false;\n    if (g_ResFactory != nullptr) {\n      //      OPTICK_EVENT(\"Async Load Resources\");\n      const auto idleTime = m_limiter.SleepTime(targetFrameTime);\n      skipRetrace = g_ResFactory->AsyncIdle(idleTime);\n    }\n\n    if (skipRetrace) {\n      // We stopped loading resources to catch the next frame\n      m_limiter.Reset();\n    } else {\n      // No more to load, and we're under frame time\n      {\n        // OPTICK_EVENT(\"Sleep\");\n        m_limiter.Sleep(targetFrameTime);\n      }\n    }\n\n    // OPTICK_FRAME(\"MainThread\");\n\n    // Check if fullscreen has been toggled, if so set the fullscreen cvar accordingly\n    if (m_fullscreenToggleRequested) {\n      m_cvarCommons.m_fullscreen->fromBoolean(!m_cvarCommons.getFullscreen());\n      m_fullscreenToggleRequested = false;\n    }\n\n    // Check if the user has modified the fullscreen CVar, if so set fullscreen state accordingly\n    if (m_cvarCommons.m_fullscreen->isModified()) {\n      VISetWindowFullscreen(m_cvarCommons.getFullscreen());\n    }\n\n    // Let CVarManager inform all CVar listeners of the CVar's state and clear all mdoified flags if necessary\n    m_cvarManager.proc();\n\n    if (!g_mainMP1 && m_projectInitialized) {\n      g_mainMP1.emplace(nullptr, nullptr);\n      auto result = g_mainMP1->Init(m_argc, m_argv, m_fileMgr, &m_cvarManager);\n      if (!result.empty()) {\n        spdlog::error(\"{}\", result);\n        m_imGuiConsole.m_errorString = result;\n        g_mainMP1.reset();\n        CDvdFile::Shutdown();\n        m_projectInitialized = false;\n        m_cvarCommons.m_lastDiscPath->fromLiteral(\"\"sv);\n      }\n    }\n\n    float dt = 1 / 60.f;\n    if (m_cvarCommons.m_variableDt->toBoolean()) {\n      dt = std::min(realDt, 1 / 30.f);\n    }\n\n    m_imGuiConsole.PreUpdate();\n    if (g_mainMP1) {\n      //      if (m_voiceEngine) {\n      //        m_voiceEngine->lockPump();\n      //      }\n      if (g_mainMP1->Proc(dt)) {\n        return false;\n      }\n      //      if (m_voiceEngine) {\n      //        m_voiceEngine->unlockPump();\n      //      }\n    }\n    m_imGuiConsole.PostUpdate();\n    if (!g_mainMP1 && m_imGuiConsole.m_gameDiscSelected) {\n      std::optional<std::string> result;\n      m_imGuiConsole.m_gameDiscSelected.swap(result);\n      m_deferredProject = std::move(*result);\n    }\n\n    if (m_quitRequested || m_imGuiConsole.m_quitRequested || m_cvarManager.restartRequired()) {\n      if (g_mainMP1) {\n        g_mainMP1->Quit();\n      } else {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  void onAppDraw() noexcept {\n    // OPTICK_EVENT(\"Draw\");\n    if (g_Renderer != nullptr) {\n      g_Renderer->BeginScene();\n      if (g_mainMP1) {\n        g_mainMP1->Draw();\n      }\n      g_Renderer->EndScene();\n    }\n    m_imGuiConsole.PostDraw();\n  }\n\n  void onAppPostDraw() noexcept {\n    // OPTICK_EVENT(\"PostDraw\");\n//    if (m_voiceEngine) {\n//      m_voiceEngine->pumpAndMixVoices();\n//    }\n#ifdef EMSCRIPTEN\n    CDvdFile::DoWork();\n#endif\n    CGraphics::TickRenderTimings();\n  }\n\n  void onAppWindowResized(const AuroraWindowSize& size) noexcept {\n    if (size.width != m_cvarCommons.getWindowSize().x || size.height != m_cvarCommons.getWindowSize().y) {\n      m_cvarCommons.m_windowSize->fromVec2i(zeus::CVector2i(size.width, size.height));\n    }\n\n    CGraphics::SetViewportResolution({static_cast<s32>(size.fb_width), static_cast<s32>(size.fb_height)});\n  }\n\n  void onAppWindowMoved(const AuroraWindowPos& pos) {\n    if (pos.x > 0 && pos.y > 0 &&\n        (pos.x != m_cvarCommons.getWindowPos().x || pos.y != m_cvarCommons.getWindowPos().y)) {\n      m_cvarCommons.m_windowPos->fromVec2i(zeus::CVector2i(pos.x, pos.y));\n    }\n  }\n\n  void onAppDisplayScaleChanged(float scale) noexcept { ImGuiEngine_Initialize(scale); }\n\n  void onControllerAdded(uint32_t which) noexcept { m_imGuiConsole.ControllerAdded(which); }\n\n  void onControllerRemoved(uint32_t which) noexcept { m_imGuiConsole.ControllerRemoved(which); }\n\n  void onAppExiting() noexcept {\n    m_imGuiConsole.Shutdown();\n    //    if (m_voiceEngine) {\n    //      m_voiceEngine->unlockPump();\n    //      m_voiceEngine->stopPump();\n    //    }\n    if (g_mainMP1) {\n      g_mainMP1->Shutdown();\n    }\n    g_mainMP1.reset();\n    m_cvarManager.serialize();\n    //    m_amuseAllocWrapper.reset();\n    //    m_voiceEngine.reset();\n    CDvdFile::Shutdown();\n  }\n\n  void onImGuiInit(float scale) noexcept { ImGuiEngine_Initialize(scale); }\n\n  void onImGuiAddTextures() noexcept { ImGuiEngine_AddTextures(); }\n\n  [[nodiscard]] std::chrono::nanoseconds getTargetFrameTime() const {\n    if (m_cvarCommons.getVariableFrameTime()) {\n      return std::chrono::nanoseconds{0};\n    }\n    return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::seconds{1}) / 60;\n  }\n};\n\n} // namespace metaforce\n\nstatic void SetupBasics() {\n  auto result = zeus::validateCPU();\n  if (!result.first) {\n#if _WIN32 && !WINDOWS_STORE\n    std::string msg = fmt::format(\"ERROR: This build of Metaforce requires the following CPU features:\\n{}\\n\",\n                                  metaforce::CPUFeatureString(result.second));\n    MessageBoxW(nullptr, nowide::widen(msg).c_str(), L\"CPU error\", MB_OK | MB_ICONERROR);\n#else\n    fmt::print(stderr, \"ERROR: This build of Metaforce requires the following CPU features:\\n{}\\n\",\n               metaforce::CPUFeatureString(result.second));\n#endif\n    exit(1);\n  }\n\n#if defined(ANDROID)\n  {\n    std::vector<spdlog::sink_ptr> sinks;\n    if (auto defaultLogger = spdlog::default_logger(); defaultLogger != nullptr) {\n      sinks = defaultLogger->sinks();\n    }\n    sinks.emplace_back(std::make_shared<spdlog::sinks::android_sink_mt>(\"Metaforce\"));\n    auto logger = std::make_shared<spdlog::logger>(\"metaforce-android\", sinks.begin(), sinks.end());\n    logger->set_level(spdlog::level::trace);\n    logger->flush_on(spdlog::level::warn);\n    spdlog::set_default_logger(std::move(logger));\n  }\n#endif\n\n#if SENTRY_ENABLED\n  std::string cacheDir{metaforce::FileStoreManager::instance()->getStoreRoot()};\n  logvisor::RegisterSentry(\"metaforce\", METAFORCE_WC_DESCRIBE, cacheDir.c_str());\n#endif\n}\n\nstatic bool IsClientLoggingEnabled(int argc, char** argv) {\n#ifdef EMSCRIPTEN\n  return true;\n#else\n  for (int i = 1; i < argc; ++i) {\n    if (!strncmp(argv[i], \"-l\", 2)) {\n      return true;\n    }\n  }\n  return false;\n#endif\n}\n\nstatic std::unique_ptr<metaforce::Application> g_app;\nstatic bool g_paused;\n\nstatic void aurora_log_callback(AuroraLogLevel level, const char* module, const char* message, unsigned int len) {\n  spdlog::level::level_enum severity = spdlog::level::critical;\n  switch (level) {\n  case LOG_DEBUG:\n    severity = spdlog::level::debug;\n    break;\n  case LOG_INFO:\n    severity = spdlog::level::info;\n    break;\n  case LOG_WARNING:\n    severity = spdlog::level::warn;\n    break;\n  case LOG_ERROR:\n    severity = spdlog::level::err;\n    break;\n  default:\n    break;\n  }\n  const std::string_view view(message, len);\n  spdlog::log(severity, \"[{}] {}\", module, view);\n  if (level == LOG_FATAL) {\n    spdlog::default_logger()->flush();\n    auto msg = fmt::format(\"Metaforce encountered an internal error:\\n\\n{}\", view);\n    SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, \"Metaforce\", msg.c_str(), metaforce::g_window);\n    std::abort();\n  }\n}\n\nstatic void aurora_imgui_init_callback(const AuroraWindowSize* size) { g_app->onImGuiInit(size->scale); }\n\n#if !WINDOWS_STORE\nint main(int argc, char** argv) {\n  // TODO: This seems to fix a lot of weird issues with rounding\n  //  but breaks animations, need to research why this is the case\n  //  for now it's disabled\n  // fesetround(FE_TOWARDZERO);\n  if (argc > 1 && !strcmp(argv[1], \"--dlpackage\")) {\n    fmt::print(\"{}\\n\", METAFORCE_DLPACKAGE);\n    return 100;\n  }\n\n  metaforce::FileStoreManager fileMgr{\"AxioDL\", \"metaforce\"};\n  SetupBasics();\n\n  std::vector<std::string> args;\n  for (int i = 1; i < argc; ++i) {\n    args.emplace_back(argv[i]);\n  }\n\n  auto icon = metaforce::GetIcon();\n\n  // FIXME: logvisor needs to copy this\n  std::string logFilePath;\n\n  bool restart = false;\n  do {\n    metaforce::CVarManager cvarMgr{fileMgr};\n    metaforce::CVarCommons cvarCmns{cvarMgr};\n    metaforce::CResourceNameDatabase nameDatabase{fileMgr};\n\n    cvarMgr.parseCommandLine(args);\n    if (!restart) {\n      // TODO add clear loggers func to logvisor so we can recreate loggers on restart\n      bool logging = IsClientLoggingEnabled(argc, argv);\n      // #if _WIN32\n      //       if (logging && GetFileType(GetStdHandle(STD_ERROR_HANDLE)) == FILE_TYPE_UNKNOWN) {\n      //         logvisor::CreateWin32Console();\n      //       }\n      // #endif\n      //       logvisor::RegisterStandardExceptions();\n      //       if (logging) {\n      //         logvisor::RegisterConsoleLogger();\n      //       }\n\n      std::string logFile = cvarCmns.getLogFile();\n      if (!logFile.empty()) {\n        std::time_t time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());\n        char buf[100];\n        std::strftime(buf, 100, \"%Y-%m-%d_%H-%M-%S\", std::localtime(&time));\n        logFilePath = fmt::format(\"{}/{}-{}\", fileMgr.getStoreRoot(), buf, logFile);\n        // logvisor::RegisterFileLogger(logFilePath.c_str());\n      }\n    }\n\n    g_app = std::make_unique<metaforce::Application>(argc, argv, fileMgr, cvarMgr, cvarCmns, nameDatabase);\n    std::string configPath{fileMgr.getStoreRoot()};\n\n    const AuroraConfig config{\n        .appName = \"Metaforce\",\n        .configPath = configPath.c_str(),\n        .desiredBackend = metaforce::backend_from_string(cvarCmns.getGraphicsApi()),\n        .msaa = cvarCmns.getSamples(),\n        .maxTextureAnisotropy = static_cast<uint16_t>(cvarCmns.getAnisotropy()),\n        .startFullscreen = cvarCmns.getFullscreen(),\n        .allowJoystickBackgroundEvents = cvarCmns.getAllowJoystickInBackground(),\n        .windowPosX = cvarCmns.getWindowPos().x,\n        .windowPosY = cvarCmns.getWindowPos().y,\n        .windowWidth = static_cast<uint32_t>(cvarCmns.getWindowSize().x < 0 ? 0 : cvarCmns.getWindowSize().x),\n        .windowHeight = static_cast<uint32_t>(cvarCmns.getWindowSize().y < 0 ? 0 : cvarCmns.getWindowSize().y),\n        .iconRGBA8 = icon.data.get(),\n        .iconWidth = icon.width,\n        .iconHeight = icon.height,\n        .logCallback = aurora_log_callback,\n        .imGuiInitCallback = aurora_imgui_init_callback,\n    };\n    const auto info = aurora_initialize(argc, argv, &config);\n    metaforce::g_window = info.window;\n    g_app->onImGuiAddTextures();\n    g_app->onAppLaunched(info);\n    g_app->onAppWindowResized(info.windowSize);\n    metaforce::CTexture::SetMangleMips(cvarCmns.getMangleMipmaps());\n    while (!cvarMgr.restartRequired()) {\n      const auto* event = aurora_update();\n      bool exiting = false;\n      while (event != nullptr && event->type != AURORA_NONE) {\n        switch (event->type) {\n        case AURORA_EXIT:\n          exiting = true;\n          break;\n        case AURORA_SDL_EVENT:\n          g_app->onSdlEvent(event->sdl);\n          break;\n        case AURORA_WINDOW_RESIZED:\n          g_app->onAppWindowResized(event->windowSize);\n          break;\n        case AURORA_WINDOW_MOVED:\n          g_app->onAppWindowMoved(event->windowPos);\n          break;\n        case AURORA_CONTROLLER_ADDED:\n          g_app->onControllerAdded(event->controller);\n          break;\n        case AURORA_CONTROLLER_REMOVED:\n          g_app->onControllerRemoved(event->controller);\n          break;\n        case AURORA_PAUSED:\n          g_paused = true;\n          break;\n        case AURORA_UNPAUSED:\n          g_paused = false;\n          break;\n        case AURORA_DISPLAY_SCALE_CHANGED:\n          g_app->onAppDisplayScaleChanged(event->windowSize.scale);\n          break;\n        default:\n          break;\n        }\n        if (exiting) {\n          break;\n        }\n        ++event;\n      }\n      if (exiting) {\n        break;\n      }\n      if (g_paused) {\n        continue;\n      }\n      if (!aurora_begin_frame()) {\n        continue;\n      }\n      if (!g_app->onAppIdle(1.f / 60.f /* TODO */)) {\n        break;\n      }\n      g_app->onAppDraw();\n      aurora_end_frame();\n      g_app->onAppPostDraw();\n    }\n    g_app->onAppExiting();\n    aurora_shutdown();\n    g_app.reset();\n\n    restart = cvarMgr.restartRequired();\n  } while (restart);\n  return 0;\n}\n#endif\n"
  },
  {
    "path": "Runtime/CMainFlowBase.cpp",
    "content": "#include \"Runtime/CMainFlowBase.hpp\"\n\n#include \"Runtime/CArchitectureMessage.hpp\"\n\nnamespace metaforce {\n\nCIOWin::EMessageReturn CMainFlowBase::OnMessage(const CArchitectureMessage& msg, CArchitectureQueue& queue) {\n  switch (msg.GetType()) {\n  case EArchMsgType::TimerTick:\n    AdvanceGameState(queue);\n    break;\n  case EArchMsgType::SetGameState: {\n    const CArchMsgParmInt32& state = MakeMsg::GetParmNewGameflowState(msg);\n    x14_gameState = EClientFlowStates(state.x4_parm);\n    SetGameState(x14_gameState, queue);\n    return EMessageReturn::Exit;\n  }\n  default:\n    break;\n  }\n  return EMessageReturn::Normal;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CMainFlowBase.hpp",
    "content": "#pragma once\n\n#include \"Runtime/CIOWin.hpp\"\n\nnamespace metaforce {\n\nenum class EClientFlowStates {\n  Unspecified = -1,\n  None = 0,\n  WinBad = 1,\n  WinGood = 2,\n  WinBest = 3,\n  LoseGame = 4,\n  Default = 5,\n  StateSetter = 6,\n  PreFrontEnd = 7,\n  FrontEnd = 8,\n  Game = 14,\n  GameExit = 15\n};\n\nclass CMainFlowBase : public CIOWin {\nprotected:\n  EClientFlowStates x14_gameState = EClientFlowStates::Unspecified;\n\npublic:\n  explicit CMainFlowBase(const char* name) : CIOWin(name) {}\n  EMessageReturn OnMessage(const CArchitectureMessage& msg, CArchitectureQueue& queue) override;\n  virtual void AdvanceGameState(CArchitectureQueue& queue) = 0;\n  virtual void SetGameState(EClientFlowStates state, CArchitectureQueue& queue) = 0;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CMakeLists.txt",
    "content": "macro(runtime_add_list rel_path a_list)\n    unset(tmp_list)\n    foreach (path IN LISTS ${a_list})\n        if (IS_ABSOLUTE ${path})\n            list(APPEND tmp_list \"${path}\")\n        else ()\n            list(APPEND tmp_list \"${rel_path}/${path}\")\n        endif ()\n    endforeach (path)\n    set(${a_list} \"${tmp_list}\" PARENT_SCOPE)\nendmacro(runtime_add_list)\n\nadd_subdirectory(Audio)\nadd_subdirectory(Character)\nadd_subdirectory(Graphics)\nadd_subdirectory(Collision)\nadd_subdirectory(Camera)\nadd_subdirectory(World)\nadd_subdirectory(Weapon)\nadd_subdirectory(AutoMapper)\nadd_subdirectory(GuiSys)\nadd_subdirectory(Input)\nadd_subdirectory(Particle)\n\nif (WIN32)\n    list(APPEND PLAT_SRCS CMemoryCardSysWin.cpp)\nelse ()\n    list(APPEND PLAT_SRCS CMemoryCardSysNix.cpp)\nendif ()\n\nfind_package(Python3 COMPONENTS Interpreter REQUIRED)\nadd_custom_command(OUTPUT TCastTo.hpp TCastTo.cpp DEPENDS MkCastTo.py\n        COMMAND ${Python3_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/MkCastTo.py\n        WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}\n        COMMENT \"Generating cast functions\")\n\nadd_subdirectory(MP1)\nadd_subdirectory(MP2)\nadd_subdirectory(MP3)\n\nset(CAST_TO_SOURCES\n        MkCastTo.py\n        TCastTo.hpp TCastTo.cpp)\n\nset(RUNTIME_SOURCES_A\n        RetroTypes.hpp RetroTypes.cpp\n        ${CAST_TO_SOURCES}\n        ${MP1_SOURCES}\n        ${AUDIO_SOURCES}\n        ${AUTOMAPPER_SOURCES}\n        ${CAMERA_SOURCES}\n        ${CHARACTER_SOURCES}\n        ${COLLISION_SOURCES}\n        ${GRAPHICS_SOURCES})\n\nset(RUNTIME_SOURCES_B\n        ${CAST_TO_SOURCES}\n        ${GUISYS_SOURCES}\n        ${INPUT_SOURCES}\n        ${PARTICLE_SOURCES}\n        ${WORLD_SOURCES}\n        ${WEAPON_SOURCES}\n        CInfiniteLoopDetector.hpp CInfiniteLoopDetector.cpp\n        ConsoleVariables/FileStoreManager.hpp ConsoleVariables/FileStoreManager.cpp\n        ConsoleVariables/CVar.hpp ConsoleVariables/CVar.cpp\n        ConsoleVariables/CVarManager.hpp ConsoleVariables/CVarManager.cpp\n        ConsoleVariables/CVarCommons.hpp ConsoleVariables/CVarCommons.cpp\n        Tweaks/ITweak.hpp\n        Tweaks/ITweakAutoMapper.hpp\n        Tweaks/ITweakBall.hpp\n        Tweaks/ITweakGame.hpp\n        Tweaks/ITweakGui.hpp\n        Tweaks/ITweakGuiColors.hpp\n        Tweaks/ITweakGunRes.hpp\n        Tweaks/ITweakParticle.hpp\n        Tweaks/ITweakPlayer.hpp\n        Tweaks/ITweakPlayerControl.hpp\n        Tweaks/ITweakPlayerGun.hpp Tweaks/ITweakPlayerGun.cpp\n        Tweaks/ITweakPlayerRes.hpp\n        Tweaks/ITweakSlideShow.hpp\n        Tweaks/ITweakTargeting.hpp\n        IMain.hpp\n        CStopwatch.hpp CStopwatch.cpp\n        Streams/IOStreams.hpp Streams/IOStreams.cpp\n        Streams/CMemoryStreamOut.hpp Streams/CMemoryStreamOut.cpp\n        Streams/CInputStream.hpp Streams/CInputStream.cpp\n        Streams/COutputStream.hpp Streams/COutputStream.cpp\n        Streams/CMemoryInStream.hpp\n        Streams/CZipInputStream.hpp Streams/CZipInputStream.cpp\n        Streams/ContainerReaders.hpp\n        Streams/CTextInStream.hpp Streams/CTextInStream.cpp\n        Streams/CTextOutStream.hpp Streams/CTextOutStream.cpp\n        Streams/CFileOutStream.hpp Streams/CFileOutStream.cpp\n        CGameAllocator.hpp CGameAllocator.cpp\n        CMemoryCardSys.hpp CMemoryCardSys.cpp\n        CScannableObjectInfo.hpp CScannableObjectInfo.cpp\n        CWorldSaveGameInfo.hpp CWorldSaveGameInfo.cpp\n        CDependencyGroup.hpp CDependencyGroup.cpp\n        CBasics.hpp CBasicsPC.cpp\n        CIOWin.hpp\n        CIOWinManager.hpp CIOWinManager.cpp\n        CStateManager.hpp CStateManager.cpp\n        CGameState.hpp CGameState.cpp\n        CScriptMailbox.hpp CScriptMailbox.cpp\n        CPlayerState.hpp CPlayerState.cpp\n        CRandom16.hpp CRandom16.cpp\n        CResFactory.hpp CResFactory.cpp\n        CResLoader.hpp CResLoader.cpp\n        CDvdRequest.hpp\n        CDvdFile.hpp CDvdFile.cpp\n        IObjectStore.hpp\n        CSimplePool.hpp CSimplePool.cpp\n        CGameOptions.hpp CGameOptions.cpp\n        CStaticInterference.hpp CStaticInterference.cpp\n        CCRC32.hpp CCRC32.cpp\n        IFactory.hpp\n        IObjFactory.hpp\n        CObjectList.hpp CObjectList.cpp\n        GameObjectLists.hpp GameObjectLists.cpp\n        CSortedLists.hpp CSortedLists.cpp\n        CArchitectureMessage.hpp\n        CArchitectureQueue.hpp\n        IObj.hpp\n        IVParamObj.hpp\n        CTimeProvider.hpp CTimeProvider.cpp\n        CToken.hpp CToken.cpp\n        CFactoryMgr.hpp CFactoryMgr.cpp\n        CPakFile.hpp CPakFile.cpp\n        CStringExtras.hpp CStringExtras.cpp\n        CMainFlowBase.hpp CMainFlowBase.cpp\n        CMFGameBase.hpp\n        CInGameTweakManagerBase.hpp\n        CGameDebug.hpp\n        CGameHintInfo.hpp CGameHintInfo.cpp\n        rstl.hpp\n        GameGlobalObjects.hpp GameGlobalObjects.cpp\n        GCNTypes.hpp\n        CTextureCache.hpp CTextureCache.cpp\n        CMayaSpline.hpp CMayaSpline.cpp\n        ImGuiPlayerLoadouts.hpp ImGuiPlayerLoadouts.cpp\n        CResourceNameDatabase.hpp CResourceNameDatabase.cpp\n        ${PLAT_SRCS})\n\nfunction(add_runtime_common_library name)\n    add_library(${name} ${ARGN})\n    target_compile_definitions(${name} PUBLIC \"-DMETAFORCE_TARGET_BYTE_ORDER=__BYTE_ORDER__\")\n    if (WINDOWS_STORE)\n        set_property(TARGET ${name} PROPERTY VS_WINRT_COMPONENT TRUE)\n    endif ()\nendfunction()\n\nset(RUNTIME_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR})\nset(RUNTIME_LIBRARIES zeus nod::nod NESEmulator libjpeg-turbo jbus kabufuda #OptickCore\n    imgui_support aurora::core aurora::gx aurora::pad aurora::si aurora::vi aurora::mtx spdlog::spdlog $<$<BOOL:${WIN32}>:nowide::nowide>\n    ZLIB::ZLIB\n    )\n\nadd_runtime_common_library(RuntimeCommon ${RUNTIME_SOURCES_A})\ntarget_include_directories(RuntimeCommon PUBLIC ${RUNTIME_INCLUDES})\ntarget_link_libraries(RuntimeCommon PUBLIC ${RUNTIME_LIBRARIES})\n\nadd_runtime_common_library(RuntimeCommonB ${RUNTIME_SOURCES_B})\ntarget_include_directories(RuntimeCommonB PUBLIC ${RUNTIME_INCLUDES})\ntarget_link_libraries(RuntimeCommonB PUBLIC ${RUNTIME_LIBRARIES})\n\nif (WIN32)\n    configure_file(platforms/win/metaforce.rc.in \"${CMAKE_CURRENT_SOURCE_DIR}/platforms/win/metaforce.rc\" @ONLY)\n    set(PLAT_SRCS \"${CMAKE_CURRENT_SOURCE_DIR}/platforms/win/metaforce.rc\" platforms/win/metaforce.manifest)\n    if (WINDOWS_STORE)\n        set(UWP_ASSETS\n                platforms/win/Assets/LargeTile.scale-100.png\n                platforms/win/Assets/LargeTile.scale-125.png\n                platforms/win/Assets/LargeTile.scale-150.png\n                platforms/win/Assets/LargeTile.scale-200.png\n                platforms/win/Assets/LargeTile.scale-400.png\n                platforms/win/Assets/SmallTile.scale-100.png\n                platforms/win/Assets/SmallTile.scale-125.png\n                platforms/win/Assets/SmallTile.scale-150.png\n                platforms/win/Assets/SmallTile.scale-200.png\n                platforms/win/Assets/SmallTile.scale-400.png\n                platforms/win/Assets/SplashScreen.scale-100.png\n                platforms/win/Assets/SplashScreen.scale-125.png\n                platforms/win/Assets/SplashScreen.scale-150.png\n                platforms/win/Assets/SplashScreen.scale-200.png\n                platforms/win/Assets/SplashScreen.scale-400.png\n                platforms/win/Assets/Square44x44Logo.scale-100.png\n                platforms/win/Assets/Square44x44Logo.scale-125.png\n                platforms/win/Assets/Square44x44Logo.scale-150.png\n                platforms/win/Assets/Square44x44Logo.scale-200.png\n                platforms/win/Assets/Square44x44Logo.scale-400.png\n                platforms/win/Assets/Square44x44Logo.altform-unplated_targetsize-16.png\n                platforms/win/Assets/Square44x44Logo.altform-unplated_targetsize-24.png\n                platforms/win/Assets/Square44x44Logo.altform-unplated_targetsize-32.png\n                platforms/win/Assets/Square44x44Logo.altform-unplated_targetsize-48.png\n                platforms/win/Assets/Square44x44Logo.altform-unplated_targetsize-256.png\n                platforms/win/Assets/Square150x150Logo.scale-100.png\n                platforms/win/Assets/Square150x150Logo.scale-125.png\n                platforms/win/Assets/Square150x150Logo.scale-150.png\n                platforms/win/Assets/Square150x150Logo.scale-200.png\n                platforms/win/Assets/Square150x150Logo.scale-400.png\n                platforms/win/Assets/metaforce.scale-100.png\n                platforms/win/Assets/metaforce.scale-125.png\n                platforms/win/Assets/metaforce.scale-150.png\n                platforms/win/Assets/metaforce.scale-200.png\n                platforms/win/Assets/metaforce.scale-400.png\n                platforms/win/Assets/WideTile.scale-100.png\n                platforms/win/Assets/WideTile.scale-125.png\n                platforms/win/Assets/WideTile.scale-150.png\n                platforms/win/Assets/WideTile.scale-200.png\n                platforms/win/Assets/WideTile.scale-400.png)\n        set_property(SOURCE platforms/win/Package.appxmanifest PROPERTY VS_DEPLOYMENT_CONTENT 1)\n        set_property(SOURCE ${UWP_ASSETS} PROPERTY VS_DEPLOYMENT_CONTENT 1)\n        set_property(SOURCE ${UWP_ASSETS} PROPERTY VS_DEPLOYMENT_LOCATION \"Assets\")\n        list(APPEND PLAT_SRCS ${UWP_ASSETS} platforms/win/Package.appxmanifest)\n    endif ()\nelseif (APPLE)\n    # nothing\nelseif (UNIX AND NOT ANDROID)\n    set(PLAT_LIBS rt)\nendif ()\n\nif (ANDROID)\n    add_library(metaforce SHARED CMain.cpp ${PLAT_SRCS}\n        ImGuiConsole.hpp ImGuiConsole.cpp\n        ImGuiControllerConfig.hpp ImGuiControllerConfig.cpp\n        ImGuiEntitySupport.hpp ImGuiEntitySupport.cpp)\n    set_target_properties(metaforce PROPERTIES\n        OUTPUT_NAME main\n        LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/Binaries)\nelse ()\n    add_executable(metaforce WIN32 CMain.cpp ${PLAT_SRCS}\n        ImGuiConsole.hpp ImGuiConsole.cpp\n        ImGuiControllerConfig.hpp ImGuiControllerConfig.cpp\n        ImGuiEntitySupport.hpp ImGuiEntitySupport.cpp)\nendif ()\n# RUNTIME_LIBRARIES repeated here for link ordering\ntarget_link_libraries(metaforce PUBLIC RuntimeCommon RuntimeCommonB ${RUNTIME_LIBRARIES} ${PLAT_LIBS} aurora::main)\ntarget_compile_definitions(metaforce PUBLIC \"-DMETAFORCE_TARGET_BYTE_ORDER=__BYTE_ORDER__\")\nif (ANDROID)\n    # SDLActivity loads SDL_main via dlsym on Android. Since aurora::main is a static\n    # archive, force an undefined reference so the linker keeps the SDL_main object.\n    target_link_options(metaforce PRIVATE \"-Wl,-u,SDL_main\")\nendif ()\nif (WIN32)\n    target_link_options(metaforce PRIVATE /ENTRY:wWinMainCRTStartup)\nendif ()\n\nif (APPLE)\n    if (TVOS)\n        set(RESOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/platforms/tvos)\n        set(INFO_PLIST ${RESOURCE_DIR}/Info.plist.in)\n        file(GLOB_RECURSE RESOURCE_FILES \"${RESOURCE_DIR}/Base.lproj/*\")\n        list(APPEND RESOURCE_FILES ${RESOURCE_DIR}/Assets.car)\n    elseif (IOS)\n        set(RESOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios)\n        set(INFO_PLIST ${RESOURCE_DIR}/Info.plist.in)\n        file(GLOB_RECURSE RESOURCE_FILES \"${RESOURCE_DIR}/Base.lproj/*\")\n        list(APPEND RESOURCE_FILES\n            ${RESOURCE_DIR}/Assets.car\n            ${RESOURCE_DIR}/AppIcon60x60@2x.png\n            ${RESOURCE_DIR}/AppIcon76x76@2x~ipad.png)\n    else ()\n        set(RESOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/platforms/macos)\n        set(INFO_PLIST ${RESOURCE_DIR}/Info.plist.in)\n        set(RESOURCE_FILES ${RESOURCE_DIR}/mainicon.icns)\n    endif ()\n    target_sources(metaforce PRIVATE ${RESOURCE_FILES})\n    # Add to resources, preserving directory structure\n    foreach (FILE ${RESOURCE_FILES})\n        file(RELATIVE_PATH NEW_FILE \"${RESOURCE_DIR}\" ${FILE})\n        get_filename_component(NEW_FILE_PATH ${NEW_FILE} DIRECTORY)\n        set_property(SOURCE ${FILE} PROPERTY MACOSX_PACKAGE_LOCATION \"Resources/${NEW_FILE_PATH}\")\n        source_group(\"Resources/${NEW_FILE_PATH}\" FILES \"${FILE}\")\n    endforeach ()\n    set_target_properties(\n        metaforce PROPERTIES\n        MACOSX_BUNDLE TRUE\n        MACOSX_BUNDLE_INFO_PLIST ${INFO_PLIST}\n        MACOSX_BUNDLE_BUNDLE_NAME Metaforce\n        MACOSX_BUNDLE_GUI_IDENTIFIER com.axiodl.Metaforce\n        MACOSX_BUNDLE_BUNDLE_VERSION \"${METAFORCE_VERSION_STRING}\"\n        MACOSX_BUNDLE_SHORT_VERSION_STRING \"${METAFORCE_SHORT_VERSION_STRING}\"\n        OUTPUT_NAME Metaforce\n    )\nendif ()\nif (WINDOWS_STORE)\n    set_property(TARGET metaforce PROPERTY VS_WINRT_COMPONENT TRUE)\n    # This should match the Package.appxmanifest\n    set_property(TARGET metaforce PROPERTY VS_WINDOWS_TARGET_PLATFORM_MIN_VERSION \"10.0.14393.0\")\nendif ()\nif (EMSCRIPTEN)\n    target_link_options(metaforce PRIVATE -sTOTAL_MEMORY=268435456 -sALLOW_MEMORY_GROWTH --preload-file \"${CMAKE_SOURCE_DIR}/files@/\")\nendif ()\n"
  },
  {
    "path": "Runtime/CMayaSpline.cpp",
    "content": "#include \"Runtime/CMayaSpline.hpp\"\n\n#include \"Runtime/Streams/CInputStream.hpp\"\n\nnamespace metaforce {\nvoid ValidateTangent(zeus::CVector2f& tangent) {\n  if (tangent.x() < 0.f) {\n    tangent.x() = 0.f;\n  }\n\n  const float mag = tangent.magnitude();\n  if (mag != 0.f) {\n    tangent /= mag;\n  }\n\n  if (tangent.x() == 0.f && tangent.y() != 0.f) {\n    const float mul = tangent.y() >= 0.f ? 1.f : -1.f;\n    tangent.x() = 0.0001f;\n    tangent.y() = 5729578.0f * tangent.x() * mul;\n  }\n}\n\nCMayaSplineKnot::CMayaSplineKnot(CInputStream& in) {\n  x0_time = in.ReadFloat();\n  x4_amplitude = in.ReadFloat();\n  x8_ = in.ReadInt8();\n  x9_ = in.ReadInt8();\n  if (x8_ == 5) {\n    float x = in.ReadFloat();\n    float y = in.ReadFloat();\n    xc_cachedTangentA = {x, y};\n  }\n\n  if (x9_ == 5) {\n    float x = in.ReadFloat();\n    float y = in.ReadFloat();\n    x14_cachedTangentB = {x, y};\n  }\n}\n\nvoid CMayaSplineKnot::GetTangents(CMayaSplineKnot* prev, CMayaSplineKnot* next, zeus::CVector2f& tangentA,\n                                  zeus::CVector2f& tangentB) {\n  if (xa_24_dirty) {\n    CalculateTangents(prev, next);\n  }\n\n  tangentA = xc_cachedTangentA;\n  tangentB = x14_cachedTangentB;\n}\n\nvoid CMayaSplineKnot::CalculateTangents(CMayaSplineKnot* prev, CMayaSplineKnot* next) {\n  xa_24_dirty = false;\n  bool calculateTangents = false;\n  if (x8_ == 4 && prev != nullptr) {\n    float fVar2 = std::abs(prev->GetAmplitude() - GetAmplitude());\n    float fVar3 = fVar2;\n    if (next != nullptr) {\n      fVar3 = std::abs(next->GetAmplitude() - GetAmplitude());\n    }\n    if (fVar3 <= 0.05f || fVar2 <= 0.05f) {\n      x8_ = 1;\n    }\n  }\n\n  if (x8_ == 0) {\n    if (prev == nullptr) {\n      xc_cachedTangentA = {1.f, 0.f};\n    } else {\n      xc_cachedTangentA = {GetTime() - prev->GetTime(), GetAmplitude() - prev->GetAmplitude()};\n    }\n  } else if (x8_ == 1) {\n    float fVar1 = 0.f;\n    if (prev != nullptr) {\n      fVar1 = GetTime() - prev->GetTime();\n    } else if (next != nullptr) {\n      fVar1 = next->GetTime() - GetTime();\n    }\n    xc_cachedTangentA = {fVar1, 0.f};\n  } else if (x8_ == 2) {\n    calculateTangents = true;\n  } else if (x8_ == 3) {\n    xc_cachedTangentA = zeus::skOne2f;\n  } else if (x8_ == 4) {\n    x8_ = 2;\n    calculateTangents = true;\n  }\n\n  if (x9_ == 0) {\n    if (next == nullptr) {\n      x14_cachedTangentB = {1.f, 0.f};\n    } else {\n      x14_cachedTangentB = {next->GetTime() - GetTime(), next->GetAmplitude() - GetAmplitude()};\n    }\n  } else if (x9_ == 1) {\n    float fVar1 = 0.f;\n    if (next != nullptr) {\n      fVar1 = next->GetTime() - GetTime();\n    } else if (prev != nullptr) {\n      fVar1 = GetTime() - prev->GetTime();\n    }\n\n    x14_cachedTangentB = {fVar1, 0.f};\n  } else if (x9_ == 2) {\n    calculateTangents = true;\n  } else if (x9_ == 3) {\n    x14_cachedTangentB = {1.f, 0.f};\n  } else if (x9_ == 4 && next != nullptr) {\n    float fVar1 = next->GetAmplitude() - GetAmplitude();\n    float fVar2 = fVar1;\n    if (prev != nullptr) {\n      fVar2 = prev->GetAmplitude() - GetAmplitude();\n    }\n\n    if (fVar1 <= 0.05f || fVar2 <= 0.05f) {\n      x9_ = 1;\n    }\n    calculateTangents = true;\n  }\n\n  if (calculateTangents) {\n    zeus::CVector2f tangentA;\n    zeus::CVector2f tangentB;\n\n    if (prev == nullptr && next != nullptr) {\n      tangentA = tangentB = {next->GetTime() - GetTime(), next->GetAmplitude() - GetAmplitude()};\n    } else if (prev != nullptr && next == nullptr) {\n      tangentA = tangentB = {GetTime() - prev->GetTime(), GetAmplitude() - prev->GetAmplitude()};\n    } else if (prev != nullptr && next != nullptr) {\n      float timeDiff = next->GetTime() - prev->GetTime();\n      float ampDiff = next->GetAmplitude() - prev->GetAmplitude();\n      float amp = timeDiff >= 0.0001f ? ampDiff / timeDiff : (ampDiff <= 0.f ? -5729578.0f : 5729578.0f);\n      float nextTimeDiff = next->GetTime() - GetTime();\n      float prevTimeDiff = GetTime() - prev->GetTime();\n      float ampA = 0.f;\n      float ampB = 0.;\n      float timeA = 0.;\n      float timeB = 0.;\n      if (nextTimeDiff >= 0.f) {\n        ampA = nextTimeDiff * amp;\n      } else {\n        timeA = 0.f;\n        ampA = amp;\n      }\n\n      if (prevTimeDiff >= 0.f) {\n        ampB = prevTimeDiff * amp;\n      } else {\n        timeB = 0.f;\n      }\n      tangentB = {timeB, ampB};\n      tangentA = {timeA, ampA};\n\n    } else {\n      tangentA.zeroOut();\n      tangentB.zeroOut();\n    }\n\n    if (x8_ == 2) {\n      xc_cachedTangentA = tangentA;\n    }\n    if (x9_ == 2) {\n      x14_cachedTangentB = tangentB;\n    }\n  }\n  ValidateTangent(xc_cachedTangentA);\n  ValidateTangent(x14_cachedTangentB);\n}\n\nCMayaSpline::CMayaSpline(CInputStream& in, s32 count) : x0_preInfinity(in.ReadInt8()), x4_postInfinity(in.ReadInt8()) {\n\n  u32 knotCount = in.ReadLong();\n  x8_knots.reserve(knotCount);\n  for (size_t i = 0; i < knotCount; ++i) {\n    x8_knots.emplace_back(in);\n  }\n  x18_clampMode = in.ReadInt8();\n  x1c_minAmplitudeTime = in.ReadFloat();\n  x20_maxAmplitudeTime = in.ReadFloat();\n}\n\nfloat CMayaSpline::GetMinTime() const { return x8_knots.empty() ? 0.f : x8_knots[0].GetTime(); }\nfloat CMayaSpline::GetMaxTime() const { return x8_knots.empty() ? 0.f : x8_knots[GetKnotCount() - 1].GetTime(); }\nfloat CMayaSpline::GetDuration() const { return x8_knots.empty() ? 0.f : GetMaxTime() - GetMinTime(); }\n\nfloat CMayaSpline::EvaluateAt(float time) {\n  float amplitude = EvaluateAtUnclamped(time);\n  if (x18_clampMode == 1) {\n    if (x1c_minAmplitudeTime > amplitude) {\n      return x1c_minAmplitudeTime;\n    }\n    if (x20_maxAmplitudeTime < amplitude) {\n      return x20_maxAmplitudeTime;\n    }\n    return amplitude;\n  } else if (x18_clampMode == 2) {\n    float center = x20_maxAmplitudeTime - x1c_minAmplitudeTime;\n\n    if (center > 0.f) {\n      if (amplitude <= FLT_EPSILON + x20_maxAmplitudeTime) {\n        return amplitude - (center * static_cast<float>(s32((amplitude - x20_maxAmplitudeTime) / center) + 1));\n      }\n      if (amplitude < x1c_minAmplitudeTime - FLT_EPSILON) {\n        return amplitude + (center * static_cast<float>(std::abs(s32((amplitude - x1c_minAmplitudeTime) / center))));\n      }\n    }\n  }\n\n  return amplitude;\n}\n\nfloat CMayaSpline::EvaluateAtUnclamped(float time) {\n  if (x8_knots.empty()) {\n    return 0.f;\n  }\n\n  u32 lastIdx = x8_knots.size() - 1;\n  bool bVar2 = false;\n  float retVal;\n  if (time < x8_knots[0].GetTime()) {\n    if (x0_preInfinity == 0) {\n      return x8_knots[0].GetAmplitude();\n    }\n    return EvaluateInfinities(time, true);\n  } else if (x8_knots[lastIdx].GetTime() >= time) {\n    bVar2 = false;\n    s32 local_68 = -1;\n    s32 iVar1 = x24_chachedKnotIndex;\n    if (iVar1 != -1) {\n      if (lastIdx <= iVar1 || x8_knots[lastIdx].GetTime() >= time) {\n        if (iVar1 > 0 && x8_knots[iVar1].GetTime() > time) {\n          s32 iVar3 = iVar1 - 1;\n          bVar2 = x8_knots[iVar3].GetTime() < time;\n          if (bVar2) {\n            local_68 = iVar1;\n          }\n          if (x8_knots[iVar3].GetTime() == time) {\n            x24_chachedKnotIndex = iVar3;\n            return x8_knots[x24_chachedKnotIndex].GetAmplitude();\n          }\n        }\n      } else {\n        retVal = x8_knots[iVar1 + 1].GetTime();\n        if (retVal == time) {\n          x24_chachedKnotIndex = lastIdx;\n          return x8_knots[x24_chachedKnotIndex].GetAmplitude();\n        }\n\n        if (retVal > time) {\n          bVar2 = true;\n          local_68 = iVar1 + 1;\n        }\n      }\n    }\n\n    if (!bVar2 && (FindKnot(time, local_68))) {\n      if (local_68 == 0) {\n        x24_chachedKnotIndex = 0;\n        return x8_knots[0].GetAmplitude();\n      }\n      if (local_68 == x8_knots.size()) {\n        x24_chachedKnotIndex = 0;\n        return x8_knots[lastIdx].GetAmplitude();\n      }\n    }\n\n    lastIdx = local_68 - 1;\n    if (x28_ != lastIdx) {\n      x24_chachedKnotIndex = lastIdx;\n      x28_ = lastIdx;\n      if (x8_knots[x24_chachedKnotIndex].GetX9() == 3) {\n        x2c_24_dirty = true;\n      } else {\n        x2c_24_dirty = false;\n        rstl::reserved_vector<zeus::CVector2f, 4> points;\n        FindControlPoints(x24_chachedKnotIndex, points);\n        CalculateHermiteCoefficients(points, x34_cachedHermitCoefs);\n        x30_cachedMinTime = points[0].x();\n      }\n    }\n\n    if (x2c_24_dirty) {\n      return x8_knots[x24_chachedKnotIndex].GetTime();\n    } else {\n      return EvaluateHermite(time);\n    }\n  }\n\n  if (x4_postInfinity == 0) {\n    return x8_knots[lastIdx].GetAmplitude();\n  }\n\n  return EvaluateInfinities(time, false);\n}\n\nfloat CMayaSpline::EvaluateInfinities(float time, bool pre) {\n  if (x8_knots.empty()) {\n    return 0.f;\n  }\n\n  s32 lastIdx = x8_knots.size() - 1;\n  CMayaSplineKnot* curKnot = &x8_knots[0];\n  const float startTime = x8_knots[0].GetTime();\n  const float endTime = x8_knots[lastIdx].GetAmplitude();\n  float center = endTime - startTime;\n  if (zeus::close_enough(center, 0)) {\n    return curKnot->GetAmplitude();\n  }\n\n  double tmp = 0.f;\n  float divTime =\n      (time <= endTime) ? std::modf((time - startTime) / center, &tmp) : std::modf((time - endTime) / center, &tmp);\n\n  center = center * std::abs(divTime);\n  tmp = 1.f + std::abs(tmp);\n\n  if (!pre) {\n    if (x4_postInfinity == 4) {\n      divTime = std::fmod(tmp, 2.f);\n      if (zeus::close_enough(divTime, 0.f)) {\n        center = startTime + center;\n      } else {\n        center = endTime - center;\n      }\n    } else if (x4_postInfinity == 2 || x4_postInfinity == 3) {\n      center = startTime + center;\n    } else if (x4_postInfinity == 1) {\n      center = time - endTime;\n      zeus::CVector2f tangentA;\n      zeus::CVector2f tangentB;\n      x8_knots[0].GetTangents((lastIdx < 1) ? nullptr : &x8_knots[lastIdx - 2], nullptr, tangentA, tangentB);\n      if (!zeus::close_enough(tangentB.x(), 0.f)) {\n        return x8_knots[lastIdx].GetAmplitude() + (center * tangentB.y() / tangentB.x());\n      }\n      return x8_knots[lastIdx].GetAmplitude();\n    }\n  } else if (x0_preInfinity == 4) {\n    divTime = std::fmod(tmp, 2.f);\n    if (zeus::close_enough(divTime, 0.f)) {\n      center = endTime - center;\n    } else {\n      center = startTime + center;\n    }\n  } else if (x0_preInfinity == 2 || x0_preInfinity == 3) {\n    center = endTime - center;\n  } else if (x0_preInfinity == 1) {\n    center = (startTime - time);\n    zeus::CVector2f tangentA;\n    zeus::CVector2f tangentB;\n    x8_knots[0].GetTangents(nullptr, &x8_knots[1], tangentA, tangentB);\n    if (!zeus::close_enough(tangentA.x(), 0)) {\n      return (x8_knots[0].GetAmplitude() - (center * tangentA.y() / tangentA.x()));\n    }\n    return x8_knots[0].GetAmplitude();\n  }\n\n  float eval = EvaluateAt(center);\n  if (pre && x0_preInfinity == 3) {\n    return eval - (tmp * x8_knots[lastIdx].GetAmplitude() - x8_knots[0].GetAmplitude());\n  }\n\n  if (!pre && x4_postInfinity == 3) {\n    return eval + (tmp * x8_knots[lastIdx].GetAmplitude() - x8_knots[0].GetAmplitude());\n  }\n  return eval;\n}\n\nfloat CMayaSpline::EvaluateHermite(float time) {\n  const float timeDiff = time - x30_cachedMinTime;\n  return x34_cachedHermitCoefs[0] + (timeDiff * x34_cachedHermitCoefs[1]) + (timeDiff * x34_cachedHermitCoefs[2]) +\n         (timeDiff * x34_cachedHermitCoefs[3]);\n}\n\nbool CMayaSpline::FindKnot(float time, s32& knotIndex) {\n  if (x8_knots.empty()) {\n    return false;\n  }\n\n  u32 lower = 0;\n  u32 upper = x8_knots.size();\n  while (lower < upper) {\n    u32 index = (lower + upper) / 2;\n    const auto& knot = x8_knots[index];\n    if (knot.GetTime() > time) {\n      upper = index - 1;\n    } else if (time > knot.GetTime()) {\n      lower = index + 1;\n    } else {\n      knotIndex = index;\n      return true;\n    }\n  }\n\n  knotIndex = lower;\n  return false;\n}\n\nvoid CMayaSpline::FindControlPoints(s32 knotIndex, rstl::reserved_vector<zeus::CVector2f, 4>& controlPoints) {\n  CMayaSplineKnot* knot = &x8_knots[knotIndex];\n  controlPoints.emplace_back(knot->GetTime(), knot->GetAmplitude());\n\n  zeus::CVector2f tangentA;\n  zeus::CVector2f tangentB;\n  CMayaSplineKnot* next = (knotIndex + 1 < x8_knots.size()) ? &x8_knots[knotIndex + 1] : nullptr;\n  CMayaSplineKnot* prev = (knotIndex - 1 >= 0) ? &x8_knots[knotIndex - 1] : nullptr;\n  knot->GetTangents(prev, next, tangentA, tangentB);\n\n  knot = &x8_knots[knotIndex + 1];\n  controlPoints.emplace_back(controlPoints[0] + (tangentB * zeus::CVector2f{1.f / 3.f}));\n  next = (knotIndex + 2 < x8_knots.size()) ? &x8_knots[knotIndex + 2] : nullptr;\n  prev = (knotIndex - 2 >= 0) ? &x8_knots[knotIndex - 2] : nullptr;\n  knot->GetTangents(prev, next, tangentA, tangentB);\n  zeus::CVector2f knotV = {knot->GetTime(), knot->GetAmplitude()};\n  controlPoints.emplace_back(knotV - (tangentA * zeus::CVector2f{1.f / 3.f}));\n  controlPoints.emplace_back(knotV);\n}\n\nvoid CMayaSpline::CalculateHermiteCoefficients(const rstl::reserved_vector<zeus::CVector2f, 4>& controlPoints,\n                                               float* coefs) {\n  const zeus::CVector2f point1 = controlPoints[3] - controlPoints[0];\n  const zeus::CVector2f point2 = controlPoints[1] - controlPoints[0];\n  const zeus::CVector2f point3 = controlPoints[3] - controlPoints[2];\n  float dVar7 = point2.x() != 0.f ? point2.y() / point2.x() : 5729578.0f;\n  float dVar6 = point3.x() != 0.f ? point3.y() / point3.x() : 5729578.0f;\n  const float point1XSq = point1.x() * point1.x();\n  coefs[0] =\n      ((1.f / (point1XSq)) * (((dVar7 * point1.x()) + (dVar6 * point1.x()) - point1.y()) - point1.y())) / point1.x();\n  coefs[1] = ((1.f / (point1XSq)) *\n              ((((point1.y() + (point1.y() + point1.y())) - (dVar7 * point1.x())) - (dVar7 * point1.x())) -\n               (dVar6 * point1.x())));\n  coefs[2] = dVar7;\n  coefs[3] = controlPoints[0].y();\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CMayaSpline.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include \"RetroTypes.hpp\"\n\n#include <zeus/CVector2f.hpp>\n#include <rstl.hpp>\n#include <cmath>\n#include <cfloat>\n\nnamespace metaforce {\nclass CMayaSplineKnot {\n  float x0_time;\n  float x4_amplitude;\n  u8 x8_;\n  u8 x9_;\n  bool xa_24_dirty : 1 = true;\n  u8 xb_;\n  zeus::CVector2f xc_cachedTangentA;\n  zeus::CVector2f x14_cachedTangentB;\n\npublic:\n  CMayaSplineKnot(CInputStream& in);\n\n  float GetTime() const { return x0_time; }\n  float GetAmplitude() const { return x4_amplitude; }\n  u8 GetX8() const { return x8_; }\n  u8 GetX9() const { return x9_; }\n  void GetTangents(CMayaSplineKnot* prev, CMayaSplineKnot* next, zeus::CVector2f& tangentA, zeus::CVector2f& tangentB);\n  void CalculateTangents(CMayaSplineKnot* prev, CMayaSplineKnot* next);\n};\n\nclass CMayaSpline {\n  u32 x0_preInfinity;\n  u32 x4_postInfinity;\n  std::vector<CMayaSplineKnot> x8_knots;\n  u32 x18_clampMode;\n  float x1c_minAmplitudeTime;\n  float x20_maxAmplitudeTime;\n  s32 x24_chachedKnotIndex = -1;\n  s32 x28_ = -1;\n  bool x2c_24_dirty = false;\n  float x30_cachedMinTime;\n  float x34_cachedHermitCoefs[4];\n\npublic:\n  CMayaSpline(CInputStream& in, s32 count);\n  u32 GetKnotCount() const { return x8_knots.size(); }\n  const std::vector<CMayaSplineKnot>& GetKnots() const { return x8_knots; }\n  float GetMinTime() const;\n  float GetMaxTime() const;\n  float GetDuration() const;\n\n  float EvaluateAt(float time);\n  float EvaluateAtUnclamped(float time);\n  float EvaluateInfinities(float time, bool Pre);\n  float EvaluateHermite(float time);\n  bool FindKnot(float time, int& knotIndex);\n  void FindControlPoints(s32 knotIndex, rstl::reserved_vector<zeus::CVector2f, 4>& controlPoints);\n  void CalculateHermiteCoefficients(const rstl::reserved_vector<zeus::CVector2f, 4>& controlPoits, float* coefs);\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CMemoryCardSys.cpp",
    "content": "#include \"Runtime/CMemoryCardSys.hpp\"\n\n#include \"Runtime/CCRC32.hpp\"\n#include \"Runtime/CGameState.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CTexture.hpp\"\n#include \"Runtime/GuiSys/CStringTable.hpp\"\n#include \"ConsoleVariables/CVar.hpp\"\n#include \"ConsoleVariables/CVarManager.hpp\"\n\nnamespace metaforce {\nnamespace {\nusing ECardResult = kabufuda::ECardResult;\n\nstatic std::string g_CardImagePaths[2] = {};\nstatic kabufuda::Card g_CardStates[2] = {kabufuda::Card{\"GM8E\", \"01\"}, kabufuda::Card{\"GM8E\", \"01\"}};\n// static kabufuda::ECardResult g_OpResults[2] = {};\nCVar* mc_dolphinAPath = nullptr;\nCVar* mc_dolphinBPath = nullptr;\n} // namespace\nCSaveWorldIntermediate::CSaveWorldIntermediate(CAssetId mlvl, CAssetId savw) : x0_mlvlId(mlvl), x8_savwId(savw) {\n  if (!savw.IsValid())\n    x2c_dummyWorld = std::make_unique<CDummyWorld>(mlvl, false);\n  else\n    x34_saveWorld = g_SimplePool->GetObj(SObjectTag{FOURCC('SAVW'), savw});\n}\n\nbool CSaveWorldIntermediate::InitializePump() {\n  if (x2c_dummyWorld) {\n    CDummyWorld& wld = *x2c_dummyWorld;\n    if (!wld.ICheckWorldComplete())\n      return false;\n\n    x4_strgId = wld.IGetStringTableAssetId();\n    x8_savwId = wld.IGetSaveWorldAssetId();\n    const u32 areaCount = wld.IGetAreaCount();\n\n    xc_areaIds.reserve(areaCount);\n    for (u32 i = 0; i < areaCount; ++i) {\n      const IGameArea* area = wld.IGetAreaAlways(i);\n      xc_areaIds.emplace_back(area->IGetAreaSaveId());\n    }\n\n    CAssetId mlvlId = wld.IGetWorldAssetId();\n    CWorldState& mlvlState = g_GameState->StateForWorld(mlvlId);\n    x1c_defaultLayerStates = mlvlState.GetLayerState()->x0_areaLayers;\n\n    x34_saveWorld = g_SimplePool->GetObj(SObjectTag{FOURCC('SAVW'), x8_savwId});\n    x2c_dummyWorld.reset();\n  } else {\n    if (!x34_saveWorld)\n      return true;\n    if (x34_saveWorld.IsLoaded() && x34_saveWorld.GetObj())\n      return true;\n  }\n\n  return false;\n}\n\nbool CMemoryCardSys::HasSaveWorldMemory(CAssetId wldId) const {\n  auto existingSearch = std::find_if(xc_memoryWorlds.cbegin(), xc_memoryWorlds.cend(),\n                                     [&](const auto& wld) { return wld.first == wldId; });\n  return existingSearch != xc_memoryWorlds.cend();\n}\n\nconst CSaveWorldMemory& CMemoryCardSys::GetSaveWorldMemory(CAssetId wldId) const {\n  auto existingSearch = std::find_if(xc_memoryWorlds.cbegin(), xc_memoryWorlds.cend(),\n                                     [&](const auto& wld) { return wld.first == wldId; });\n  return existingSearch->second;\n}\n\nCMemoryCardSys::CMemoryCardSys() {\n  mc_dolphinAPath = CVarManager::instance()->findOrMakeCVar(\n      \"memcard.PathA\"sv, \"Path to the memory card image for SlotA\"sv, \"\"sv,\n      (CVar::EFlags::Archive | CVar::EFlags::System | CVar::EFlags::ModifyRestart));\n  mc_dolphinBPath = CVarManager::instance()->findOrMakeCVar(\n      \"memcard.PathB\"sv, \"Path to the memory card image for SlotB\"sv, \"\"sv,\n      (CVar::EFlags::Archive | CVar::EFlags::System | CVar::EFlags::ModifyRestart));\n  x0_hints = g_SimplePool->GetObj(\"HINT_Hints\");\n  xc_memoryWorlds.reserve(16);\n  x1c_worldInter.emplace();\n  x1c_worldInter->reserve(16);\n\n  std::vector<CAssetId> orderedMLVLs;\n  orderedMLVLs.reserve(16);\n  g_ResFactory->EnumerateNamedResources([&](std::string_view name, const SObjectTag& tag) -> bool {\n    if (tag.type == FOURCC('MLVL'))\n      orderedMLVLs.emplace_back(tag.id);\n    return true;\n  });\n  std::sort(orderedMLVLs.begin(), orderedMLVLs.end());\n\n  for (const auto& mlvl : orderedMLVLs) {\n    if (!HasSaveWorldMemory(mlvl)) {\n      xc_memoryWorlds.emplace_back(mlvl, CSaveWorldMemory{});\n      x1c_worldInter->emplace_back(mlvl, CAssetId{});\n    }\n  }\n\n  x30_scanCategoryCounts.resize(6);\n}\n\nbool CMemoryCardSys::InitializePump() {\n  if (!x1c_worldInter) {\n    for (const auto& world : xc_memoryWorlds) {\n      const CSaveWorldMemory& wld = world.second;\n      if (!wld.GetWorldName())\n        continue;\n      if (!wld.GetWorldName().IsLoaded() || !wld.GetWorldName().GetObj())\n        return false;\n    }\n\n    return !(!x0_hints.IsLoaded() || !x0_hints.GetObj());\n  }\n\n  bool done = true;\n  for (CSaveWorldIntermediate& world : *x1c_worldInter) {\n    if (world.InitializePump()) {\n      if (!world.x34_saveWorld)\n        continue;\n\n      auto existingSearch = std::find_if(xc_memoryWorlds.begin(), xc_memoryWorlds.end(),\n                                         [&](const auto& test) { return test.first == world.x0_mlvlId; });\n      CSaveWorldMemory& wldMemOut = existingSearch->second;\n      wldMemOut.x4_savwId = world.x8_savwId;\n      wldMemOut.x0_strgId = world.x4_strgId;\n      wldMemOut.xc_areaIds = world.xc_areaIds;\n      wldMemOut.x1c_defaultLayerStates = world.x1c_defaultLayerStates;\n\n      CWorldSaveGameInfo& savw = *world.x34_saveWorld;\n      wldMemOut.x8_areaCount = savw.GetAreaCount();\n\n      x20_scanStates.reserve(x20_scanStates.size() + savw.GetScans().size());\n      for (const CWorldSaveGameInfo::SScanState& scan : savw.GetScans()) {\n        const auto scanStateIter = std::find_if(x20_scanStates.cbegin(), x20_scanStates.cend(), [&](const auto& test) {\n          return test.first == scan.x0_id && test.second == scan.x4_category;\n        });\n        if (scanStateIter == x20_scanStates.cend()) {\n          x20_scanStates.emplace_back(scan.x0_id, scan.x4_category);\n          ++x30_scanCategoryCounts[int(scan.x4_category)];\n        }\n      }\n\n      wldMemOut.x3c_saveWorld = std::move(world.x34_saveWorld);\n      wldMemOut.x2c_worldName = g_SimplePool->GetObj(SObjectTag{FOURCC('STRG'), wldMemOut.x0_strgId});\n    } else\n      done = false;\n  }\n\n  if (done) {\n    std::sort(x20_scanStates.begin(), x20_scanStates.end(),\n              [&](const auto& a, const auto& b) { return a.first < b.first; });\n    x1c_worldInter = std::nullopt;\n  }\n\n  return false;\n}\n\nstd::pair<CAssetId, TAreaId> CMemoryCardSys::GetAreaAndWorldIdForSaveId(s32 saveId) const {\n  for (const auto& [mlvl, saveWorld] : xc_memoryWorlds) {\n    for (TAreaId areaId = 0; areaId < saveWorld.xc_areaIds.size(); ++areaId) {\n      if (saveWorld.xc_areaIds[areaId] == saveId) {\n        return {mlvl, areaId};\n      }\n    }\n  }\n\n  return {{}, kInvalidAreaId};\n}\n\nvoid CMemoryCardSys::CCardFileInfo::LockBannerToken(CAssetId bannerTxtr, CSimplePool& sp) {\n  x3c_bannerTex = bannerTxtr;\n  x40_bannerTok.emplace(sp.GetObj({FOURCC('TXTR'), bannerTxtr}));\n}\n\nCMemoryCardSys::CCardFileInfo::Icon::Icon(CAssetId id, kabufuda::EAnimationSpeed speed, CSimplePool& sp)\n: x0_id(id), x4_speed(speed), x8_tex(sp.GetObj({FOURCC('TXTR'), id})) {}\n\nvoid CMemoryCardSys::CCardFileInfo::LockIconToken(CAssetId iconTxtr, kabufuda::EAnimationSpeed speed, CSimplePool& sp) {\n  x50_iconToks.emplace_back(iconTxtr, speed, sp);\n}\n\nu32 CMemoryCardSys::CCardFileInfo::CalculateBannerDataSize() const {\n  u32 ret = 68;\n  if (x3c_bannerTex.IsValid()) {\n    if ((*x40_bannerTok)->GetTexelFormat() == ETexelFormat::RGB5A3) {\n      ret = 6212;\n    } else {\n      ret = 3652;\n    }\n  }\n\n  bool paletteTex = false;\n  for (const Icon& icon : x50_iconToks) {\n    if (icon.x8_tex->GetTexelFormat() == ETexelFormat::RGB5A3) {\n      ret += 2048;\n    } else {\n      ret += 1024;\n      paletteTex = true;\n    }\n  }\n\n  if (paletteTex) {\n    ret += 512;\n  }\n\n  return ret;\n}\n\nu32 CMemoryCardSys::CCardFileInfo::CalculateTotalDataSize() const {\n  return (CalculateBannerDataSize() + xf4_saveBuffer.size() + 8191) & ~8191;\n}\n\nvoid CMemoryCardSys::CCardFileInfo::BuildCardBuffer() {\n  u32 bannerSz = CalculateBannerDataSize();\n  x104_cardBuffer.resize((bannerSz + xf4_saveBuffer.size() + 8191) & ~8191);\n\n  {\n    CMemoryStreamOut w(x104_cardBuffer.data(), x104_cardBuffer.size(), CMemoryStreamOut::EOwnerShip::NotOwned);\n    w.WriteLong(0);\n    char comment[64];\n    std::memset(comment, 0, std::size(comment));\n    std::strncpy(comment, x28_comment.data(), std::size(comment) - 1);\n    w.Put(reinterpret_cast<const u8*>(comment), 64);\n    WriteBannerData(w);\n    WriteIconData(w);\n  }\n  memmove(x104_cardBuffer.data() + bannerSz, xf4_saveBuffer.data(), xf4_saveBuffer.size());\n  reinterpret_cast<u32&>(*x104_cardBuffer.data()) =\n      CBasics::SwapBytes(CCRC32::Calculate(x104_cardBuffer.data() + 4, x104_cardBuffer.size() - 4));\n\n  xf4_saveBuffer.clear();\n}\n\nvoid CMemoryCardSys::CCardFileInfo::WriteBannerData(COutputStream& out) const {\n  if (x3c_bannerTex.IsValid()) {\n    const TLockedToken<CTexture>& tex = *x40_bannerTok;\n    const auto format = tex->GetTexelFormat();\n    const auto* texels = tex->GetConstBitMapData(0);\n    if (format == ETexelFormat::RGB5A3) {\n      out.Put(texels, 6144);\n    } else {\n      out.Put(texels, 3072);\n    }\n    if (format == ETexelFormat::C8) {\n      out.Put(reinterpret_cast<const u8*>(tex->GetPalette()->GetPaletteData()), 512);\n    }\n  }\n}\n\nvoid CMemoryCardSys::CCardFileInfo::WriteIconData(COutputStream& out) const {\n  const u8* palette = nullptr;\n  for (const Icon& icon : x50_iconToks) {\n    const auto format = icon.x8_tex->GetTexelFormat();\n    const auto* texels = icon.x8_tex->GetConstBitMapData(0);\n    if (format == ETexelFormat::RGB5A3) {\n      out.Put(texels, 2048);\n    } else {\n      out.Put(texels, 1024);\n    }\n    if (format == ETexelFormat::C8) {\n      palette = reinterpret_cast<const u8*>(icon.x8_tex->GetPalette()->GetPaletteData());\n    }\n  }\n  if (palette != nullptr) {\n    out.Put(palette, 512);\n  }\n}\n\nECardResult CMemoryCardSys::CCardFileInfo::PumpCardTransfer() {\n  if (x0_status == EStatus::Standby)\n    return ECardResult::READY;\n  else if (x0_status == EStatus::Transferring) {\n    ECardResult result = CMemoryCardSys::GetResultCode(GetCardPort());\n    if (result != ECardResult::BUSY)\n      x104_cardBuffer.clear();\n    if (result != ECardResult::READY)\n      return result;\n    x0_status = EStatus::Done;\n    kabufuda::CardStat stat = {};\n    result = GetStatus(stat);\n    if (result != ECardResult::READY)\n      return result;\n    result = CMemoryCardSys::SetStatus(m_handle.slot, m_handle.getFileNo(), stat);\n    if (result != ECardResult::READY)\n      return result;\n    return ECardResult::BUSY;\n  } else {\n    ECardResult result = CMemoryCardSys::GetResultCode(GetCardPort());\n    if (result == ECardResult::READY)\n      x0_status = EStatus::Standby;\n    return result;\n  }\n}\n\nECardResult CMemoryCardSys::CCardFileInfo::GetStatus(kabufuda::CardStat& stat) const {\n  ECardResult result = CMemoryCardSys::GetStatus(m_handle.slot, m_handle.getFileNo(), stat);\n  if (result != ECardResult::READY) {\n    return result;\n  }\n\n  stat.SetCommentAddr(4);\n  stat.SetIconAddr(68);\n\n  kabufuda::EImageFormat bannerFmt;\n  if (x3c_bannerTex.IsValid()) {\n    if ((*x40_bannerTok)->GetTexelFormat() == ETexelFormat::RGB5A3) {\n      bannerFmt = kabufuda::EImageFormat::RGB5A3;\n    } else {\n      bannerFmt = kabufuda::EImageFormat::C8;\n    }\n  } else {\n    bannerFmt = kabufuda::EImageFormat::None;\n  }\n  stat.SetBannerFormat(bannerFmt);\n\n  int idx = 0;\n  for (const Icon& icon : x50_iconToks) {\n    stat.SetIconFormat(icon.x8_tex->GetTexelFormat() == ETexelFormat::RGB5A3 ? kabufuda::EImageFormat::RGB5A3\n                                                                               : kabufuda::EImageFormat::C8,\n                       idx);\n    stat.SetIconSpeed(icon.x4_speed, idx);\n    ++idx;\n  }\n  if (idx < 8) {\n    stat.SetIconFormat(kabufuda::EImageFormat::None, idx);\n    stat.SetIconSpeed(kabufuda::EAnimationSpeed::End, idx);\n  }\n\n  return ECardResult::READY;\n}\n\nECardResult CMemoryCardSys::CCardFileInfo::CreateFile() {\n  return CMemoryCardSys::CreateFile(m_handle.slot, x18_fileName.c_str(), CalculateTotalDataSize(), m_handle);\n}\n\nECardResult CMemoryCardSys::CCardFileInfo::WriteFile() {\n  BuildCardBuffer();\n  // DCStoreRange(x104_cardBuffer.data(), x104_cardBuffer.size());\n  x0_status = EStatus::Transferring;\n  return CMemoryCardSys::WriteFile(m_handle, x104_cardBuffer.data(), x104_cardBuffer.size(), 0);\n}\n\nECardResult CMemoryCardSys::CCardFileInfo::CloseFile() { return CMemoryCardSys::CloseFile(m_handle); }\nstd::string CMemoryCardSys::_GetDolphinCardPath(kabufuda::ECardSlot slot) {\n  return g_CardImagePaths[static_cast<u32>(slot)];\n}\n\nvoid CMemoryCardSys::_ResolveDolphinCardPath(const CVar* cv, kabufuda::ECardSlot slot) {\n  if (cv != nullptr && cv->toLiteral().empty()) {\n    g_CardImagePaths[int(slot)] = ResolveDolphinCardPath(slot);\n  } else if (cv != nullptr) {\n    g_CardImagePaths[int(slot)] = cv->toLiteral();\n  }\n}\n\nkabufuda::ProbeResults CMemoryCardSys::CardProbe(kabufuda::ECardSlot port) {\n  _ResolveDolphinCardPath(mc_dolphinAPath, kabufuda::ECardSlot::SlotA);\n  _ResolveDolphinCardPath(mc_dolphinBPath, kabufuda::ECardSlot::SlotB);\n\n  kabufuda::ProbeResults res = kabufuda::Card::probeCardFile(g_CardImagePaths[int(port)]);\n  // g_OpResults[int(port)] = res.x0_error;\n  return res;\n}\n\nECardResult CMemoryCardSys::MountCard(kabufuda::ECardSlot port) {\n  kabufuda::Card& card = g_CardStates[int(port)];\n  if (!card.open(g_CardImagePaths[int(port)]))\n    return ECardResult::NOCARD;\n  ECardResult result = card.getError();\n  // g_OpResults[int(port)] = result;\n  if (result == ECardResult::READY)\n    return ECardResult::READY;\n  return result;\n}\n\nECardResult CMemoryCardSys::UnmountCard(kabufuda::ECardSlot port) {\n  kabufuda::Card& card = g_CardStates[int(port)];\n  if (CardResult err = card.getError()) {\n    // g_OpResults[int(port)] = err;\n    return err;\n  }\n  card.commit();\n  // g_OpResults[int(port)] = ECardResult::READY;\n  return ECardResult::READY;\n}\n\nECardResult CMemoryCardSys::CheckCard(kabufuda::ECardSlot port) {\n  kabufuda::Card& card = g_CardStates[int(port)];\n  ECardResult result = card.getError();\n  // g_OpResults[int(port)] = result;\n  return result;\n}\n\nECardResult CMemoryCardSys::CreateFile(kabufuda::ECardSlot port, const char* name, u32 size, CardFileHandle& info) {\n  kabufuda::Card& card = g_CardStates[int(port)];\n  if (CardResult err = card.getError()) {\n    // g_OpResults[int(port)] = err;\n    return err;\n  }\n  info.slot = port;\n  ECardResult result = card.createFile(name, size, info.handle);\n  // g_OpResults[int(port)] = result;\n  return result;\n}\n\nECardResult CMemoryCardSys::OpenFile(kabufuda::ECardSlot port, const char* name, CardFileHandle& info) {\n  kabufuda::Card& card = g_CardStates[int(port)];\n  if (CardResult err = card.getError()) {\n    // g_OpResults[int(port)] = err;\n    return err;\n  }\n  info.slot = port;\n  ECardResult result = card.openFile(name, info.handle);\n  // g_OpResults[int(port)] = result;\n  return result;\n}\n\nECardResult CMemoryCardSys::FastOpenFile(kabufuda::ECardSlot port, int fileNo, CardFileHandle& info) {\n  kabufuda::Card& card = g_CardStates[int(port)];\n  if (CardResult err = card.getError()) {\n    // g_OpResults[int(port)] = err;\n    return err;\n  }\n  info.slot = port;\n  ECardResult result = card.openFile(fileNo, info.handle);\n  // g_OpResults[int(port)] = result;\n  return result;\n}\n\nECardResult CMemoryCardSys::CloseFile(CardFileHandle& info) {\n  kabufuda::Card& card = g_CardStates[int(info.slot)];\n  if (CardResult err = card.getError()) {\n    // g_OpResults[int(info.slot)] = err;\n    return err;\n  }\n  card.closeFile(info.handle);\n  return ECardResult::READY;\n}\n\nECardResult CMemoryCardSys::ReadFile(CardFileHandle& info, void* buf, s32 length, s32 offset) {\n  kabufuda::Card& card = g_CardStates[int(info.slot)];\n  if (CardResult err = card.getError()) {\n    // g_OpResults[int(info.slot)] = err;\n    return err;\n  }\n  card.seek(info.handle, offset, kabufuda::SeekOrigin::Begin);\n  card.asyncRead(info.handle, buf, length);\n  // g_OpResults[int(info.slot)] = ECardResult::READY;\n  return ECardResult::READY;\n}\n\nECardResult CMemoryCardSys::WriteFile(CardFileHandle& info, const void* buf, s32 length, s32 offset) {\n  kabufuda::Card& card = g_CardStates[int(info.slot)];\n  if (CardResult err = card.getError()) {\n    // g_OpResults[int(info.slot)] = err;\n    return err;\n  }\n  card.seek(info.handle, offset, kabufuda::SeekOrigin::Begin);\n  card.asyncWrite(info.handle, buf, length);\n  // g_OpResults[int(info.slot)] = ECardResult::READY;\n  return ECardResult::READY;\n}\n\nECardResult CMemoryCardSys::GetNumFreeBytes(kabufuda::ECardSlot port, s32& freeBytes, s32& freeFiles) {\n  kabufuda::Card& card = g_CardStates[int(port)];\n  if (CardResult err = card.getError()) {\n    // g_OpResults[int(port)] = err;\n    return err;\n  }\n  card.getFreeBlocks(freeBytes, freeFiles);\n  // g_OpResults[int(port)] = ECardResult::READY;\n  return ECardResult::READY;\n}\n\nECardResult CMemoryCardSys::GetSerialNo(kabufuda::ECardSlot port, u64& serialOut) {\n  kabufuda::Card& card = g_CardStates[int(port)];\n  if (CardResult err = card.getError()) {\n    // g_OpResults[int(port)] = err;\n    return err;\n  }\n  card.getSerial(serialOut);\n  // g_OpResults[int(port)] = ECardResult::READY;\n  return ECardResult::READY;\n}\n\nECardResult CMemoryCardSys::GetResultCode(kabufuda::ECardSlot port) {\n  kabufuda::Card& card = g_CardStates[int(port)];\n  return card.getError();\n}\n\nECardResult CMemoryCardSys::GetStatus(kabufuda::ECardSlot port, int fileNo, CardStat& statOut) {\n  kabufuda::Card& card = g_CardStates[int(port)];\n  if (CardResult err = card.getError()) {\n    // g_OpResults[int(port)] = err;\n    return err;\n  }\n  ECardResult result = card.getStatus(fileNo, statOut);\n  // g_OpResults[int(port)] = result;\n  return result;\n}\n\nECardResult CMemoryCardSys::SetStatus(kabufuda::ECardSlot port, int fileNo, const CardStat& stat) {\n  kabufuda::Card& card = g_CardStates[int(port)];\n  if (CardResult err = card.getError()) {\n    // g_OpResults[int(port)] = err;\n    return err;\n  }\n  ECardResult result = card.setStatus(fileNo, stat);\n  // g_OpResults[int(port)] = result;\n  return result;\n}\n\nECardResult CMemoryCardSys::DeleteFile(kabufuda::ECardSlot port, const char* name) {\n  kabufuda::Card& card = g_CardStates[int(port)];\n  if (CardResult err = card.getError()) {\n    // g_OpResults[int(port)] = err;\n    return err;\n  }\n  ECardResult result = card.deleteFile(name);\n  // g_OpResults[int(port)] = result;\n  return result;\n}\n\nECardResult CMemoryCardSys::FastDeleteFile(kabufuda::ECardSlot port, int fileNo) {\n  kabufuda::Card& card = g_CardStates[int(port)];\n  if (CardResult err = card.getError()) {\n    // g_OpResults[int(port)] = err;\n    return err;\n  }\n  ECardResult result = card.deleteFile(fileNo);\n  // g_OpResults[int(port)] = result;\n  return result;\n}\n\nECardResult CMemoryCardSys::Rename(kabufuda::ECardSlot port, const char* oldName, const char* newName) {\n  kabufuda::Card& card = g_CardStates[int(port)];\n  if (CardResult err = card.getError()) {\n    // g_OpResults[int(port)] = err;\n    return err;\n  }\n  ECardResult result = card.renameFile(oldName, newName);\n  // g_OpResults[int(port)] = result;\n  return result;\n}\n\nECardResult CMemoryCardSys::FormatCard(kabufuda::ECardSlot port) {\n  kabufuda::Card& card = g_CardStates[int(port)];\n  card.format(port);\n  if (CardResult err = card.getError()) {\n    // g_OpResults[int(port)] = err;\n    return err;\n  }\n  // g_OpResults[int(port)] = ECardResult::READY;\n  return ECardResult::READY;\n}\n\nvoid CMemoryCardSys::CommitToDisk(kabufuda::ECardSlot port) {\n  kabufuda::Card& card = g_CardStates[int(port)];\n  card.commit();\n}\n\nbool CMemoryCardSys::CreateDolphinCard(kabufuda::ECardSlot slot) {\n  std::string path = _CreateDolphinCard(slot, slot == kabufuda::ECardSlot::SlotA ? mc_dolphinAPath->hasDefaultValue()\n                                                                                 : mc_dolphinBPath->hasDefaultValue());\n  if (CardProbe(slot).x0_error != ECardResult::READY) {\n    return false;\n  }\n\n  MountCard(slot);\n  FormatCard(slot);\n  kabufuda::Card& card = g_CardStates[int(slot)];\n  card.waitForCompletion();\n  return true;\n}\n\nvoid CMemoryCardSys::_ResetCVar(kabufuda::ECardSlot slot) {\n  switch (slot) {\n  case kabufuda::ECardSlot::SlotA:\n    mc_dolphinAPath->fromLiteral(\"\");\n    break;\n  case kabufuda::ECardSlot::SlotB:\n    mc_dolphinBPath->fromLiteral(\"\");\n    break;\n  }\n}\nvoid CMemoryCardSys::Shutdown() {\n  UnmountCard(kabufuda::ECardSlot::SlotA);\n  UnmountCard(kabufuda::ECardSlot::SlotB);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CMemoryCardSys.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <optional>\n#include <string>\n#include <vector>\n\n#include \"Runtime/CGameHintInfo.hpp\"\n#include \"Runtime/Streams/CMemoryStreamOut.hpp\"\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/CWorldSaveGameInfo.hpp\"\n#include \"Runtime/GuiSys/CStringTable.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n#include \"Runtime/rstl.hpp\"\n\n#include <kabufuda/Card.hpp>\n\nnamespace metaforce {\nclass CDummyWorld;\nclass CSimplePool;\nclass CStringTable;\n\nclass CSaveWorldMemory {\n  friend class CMemoryCardSys;\n  CAssetId x0_strgId;\n  CAssetId x4_savwId;\n  u32 x8_areaCount;\n  std::vector<s32> xc_areaIds;\n  std::vector<CWorldLayers::Area> x1c_defaultLayerStates;\n  TLockedToken<CStringTable> x2c_worldName;       /* used to be optional */\n  TLockedToken<CWorldSaveGameInfo> x3c_saveWorld; /* used to be optional */\n\npublic:\n  CAssetId GetWorldNameId() const { return x0_strgId; }\n  CAssetId GetSaveWorldAssetId() const { return x4_savwId; }\n  u32 GetAreaCount() const { return x8_areaCount; }\n  const std::vector<CWorldLayers::Area>& GetDefaultLayerStates() const { return x1c_defaultLayerStates; }\n  const TLockedToken<CStringTable>& GetWorldName() const { return x2c_worldName; }\n  const TLockedToken<CWorldSaveGameInfo>& GetSaveWorld() const { return x3c_saveWorld; }\n  const char16_t* GetFrontEndName() const {\n    if (!x2c_worldName)\n      return u\"\";\n    return x2c_worldName->GetString(0);\n  }\n};\n\nclass CSaveWorldIntermediate {\n  friend class CMemoryCardSys;\n  CAssetId x0_mlvlId;\n  CAssetId x4_strgId;\n  CAssetId x8_savwId;\n  std::vector<s32> xc_areaIds;\n  std::vector<CWorldLayers::Area> x1c_defaultLayerStates;\n  std::unique_ptr<CDummyWorld> x2c_dummyWorld;\n  TLockedToken<CWorldSaveGameInfo> x34_saveWorld; /* Used to be auto_ptr */\n\npublic:\n  CSaveWorldIntermediate(CAssetId mlvl, CAssetId savw);\n\n  bool InitializePump();\n};\n\nclass CMemoryCardSys {\n  TLockedToken<CGameHintInfo> x0_hints;\n  std::vector<std::pair<CAssetId, CSaveWorldMemory>> xc_memoryWorlds; /* MLVL as key */\n  std::optional<std::vector<CSaveWorldIntermediate>> x1c_worldInter;  /* used to be auto_ptr of vector */\n  std::vector<std::pair<CAssetId, CWorldSaveGameInfo::EScanCategory>> x20_scanStates;\n  rstl::reserved_vector<u32, 6> x30_scanCategoryCounts;\n\npublic:\n  static void _ResetCVar(kabufuda::ECardSlot slot);\n  static void _ResolveDolphinCardPath(const CVar* cv, kabufuda::ECardSlot slot);\n  static std::string ResolveDolphinCardPath(kabufuda::ECardSlot slot);\n  static bool CreateDolphinCard(kabufuda::ECardSlot slot);\n  static std::string _GetDolphinCardPath(kabufuda::ECardSlot slot);\n  static std::string _CreateDolphinCard(kabufuda::ECardSlot slot, bool dolphin);\n\n  using ECardResult = kabufuda::ECardResult;\n  struct CardResult {\n    ECardResult result;\n    CardResult(ECardResult res) : result(res) {}\n    operator ECardResult() const { return result; }\n    explicit operator bool() const { return result != ECardResult::READY; }\n  };\n\n  struct CardFileHandle {\n    kabufuda::ECardSlot slot;\n    kabufuda::FileHandle handle;\n    CardFileHandle(kabufuda::ECardSlot slot) : slot(slot) {}\n    int getFileNo() const { return handle.getFileNo(); }\n  };\n\n  using CardStat = kabufuda::CardStat;\n  const std::vector<CGameHintInfo::CGameHint>& GetHints() const { return x0_hints->GetHints(); }\n  const std::vector<std::pair<CAssetId, CSaveWorldMemory>>& GetMemoryWorlds() const { return xc_memoryWorlds; }\n  const std::vector<std::pair<CAssetId, CWorldSaveGameInfo::EScanCategory>>& GetScanStates() const {\n    return x20_scanStates;\n  }\n  u32 GetScanCategoryCount(CWorldSaveGameInfo::EScanCategory cat) const { return x30_scanCategoryCounts[int(cat)]; }\n\n  std::vector<std::pair<CAssetId, CWorldSaveGameInfo::EScanCategory>>::const_iterator\n  LookupScanState(CAssetId id) const {\n    return rstl::binary_find(x20_scanStates.cbegin(), x20_scanStates.cend(), id,\n                             [](const std::pair<CAssetId, CWorldSaveGameInfo::EScanCategory>& p) { return p.first; });\n  }\n\n  bool HasSaveWorldMemory(CAssetId wldId) const;\n  const CSaveWorldMemory& GetSaveWorldMemory(CAssetId wldId) const;\n\n  CMemoryCardSys();\n  bool InitializePump();\n\n  struct CCardFileInfo {\n    struct Icon {\n      CAssetId x0_id;\n      kabufuda::EAnimationSpeed x4_speed;\n      TLockedToken<CTexture> x8_tex;\n      Icon(CAssetId id, kabufuda::EAnimationSpeed speed, CSimplePool& sp);\n    };\n\n    enum class EStatus { Standby, Transferring, Done };\n\n    EStatus x0_status = EStatus::Standby;\n    // CARDFileInfo x4_info;\n    CardFileHandle m_handle;\n    std::string x18_fileName;\n    std::string x28_comment;\n    CAssetId x3c_bannerTex;\n    std::optional<TLockedToken<CTexture>> x40_bannerTok;\n    rstl::reserved_vector<Icon, 8> x50_iconToks;\n    std::vector<u8> xf4_saveBuffer;\n    std::vector<u8> x104_cardBuffer;\n\n    CCardFileInfo(kabufuda::ECardSlot port, std::string_view name) : m_handle(port), x18_fileName(name) {}\n\n    void LockBannerToken(CAssetId bannerTxtr, CSimplePool& sp);\n    void LockIconToken(CAssetId iconTxtr, kabufuda::EAnimationSpeed speed, CSimplePool& sp);\n\n    [[nodiscard]] kabufuda::ECardSlot GetCardPort() const { return m_handle.slot; }\n    [[nodiscard]] int GetFileNo() const { return m_handle.getFileNo(); }\n    [[nodiscard]] u32 CalculateBannerDataSize() const;\n    [[nodiscard]] u32 CalculateTotalDataSize() const;\n    void BuildCardBuffer();\n    void WriteBannerData(COutputStream& out) const;\n    void WriteIconData(COutputStream& out) const;\n    void SetComment(const std::string& c) { x28_comment = c; }\n    ECardResult PumpCardTransfer();\n    ECardResult GetStatus(CardStat& stat) const;\n    ECardResult CreateFile();\n    ECardResult WriteFile();\n    ECardResult CloseFile();\n\n    CMemoryStreamOut BeginMemoryOut(u32 sz) {\n      xf4_saveBuffer.resize(sz);\n      return CMemoryStreamOut(xf4_saveBuffer.data(), sz, CMemoryStreamOut::EOwnerShip::NotOwned, sz);\n    }\n  };\n\n  std::pair<CAssetId, TAreaId> GetAreaAndWorldIdForSaveId(s32 saveId) const;\n  static kabufuda::ProbeResults CardProbe(kabufuda::ECardSlot port);\n  static ECardResult MountCard(kabufuda::ECardSlot port);\n  static ECardResult UnmountCard(kabufuda::ECardSlot port);\n  static ECardResult CheckCard(kabufuda::ECardSlot port);\n  static ECardResult CreateFile(kabufuda::ECardSlot port, const char* name, u32 size, CardFileHandle& info);\n  static ECardResult OpenFile(kabufuda::ECardSlot port, const char* name, CardFileHandle& info);\n  static ECardResult FastOpenFile(kabufuda::ECardSlot port, int fileNo, CardFileHandle& info);\n  static ECardResult CloseFile(CardFileHandle& info);\n  static ECardResult ReadFile(CardFileHandle& info, void* buf, s32 length, s32 offset);\n  static ECardResult WriteFile(CardFileHandle& info, const void* buf, s32 length, s32 offset);\n  static ECardResult GetNumFreeBytes(kabufuda::ECardSlot port, s32& freeBytes, s32& freeFiles);\n  static ECardResult GetSerialNo(kabufuda::ECardSlot port, u64& serialOut);\n  static ECardResult GetResultCode(kabufuda::ECardSlot port);\n  static ECardResult GetStatus(kabufuda::ECardSlot port, int fileNo, CardStat& statOut);\n  static ECardResult SetStatus(kabufuda::ECardSlot port, int fileNo, const CardStat& stat);\n  static ECardResult DeleteFile(kabufuda::ECardSlot port, const char* name);\n  static ECardResult FastDeleteFile(kabufuda::ECardSlot port, int fileNo);\n  static ECardResult Rename(kabufuda::ECardSlot port, const char* oldName, const char* newName);\n  static ECardResult FormatCard(kabufuda::ECardSlot port);\n\n  static void CommitToDisk(kabufuda::ECardSlot port);\n  static void Shutdown();\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CMemoryCardSysNix.cpp",
    "content": "#include \"CMemoryCardSys.hpp\"\n\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/IMain.hpp\"\n#include \"Runtime/CBasics.hpp\"\n#include \"Runtime/Logging.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\n#include <SDL3/SDL_filesystem.h>\n\nnamespace metaforce {\n\nstatic std::optional<std::string> GetPrefPath(const char* app) {\n  char* path = SDL_GetPrefPath(nullptr, app);\n  if (path == nullptr) {\n    return {};\n  }\n  std::string str{path};\n  SDL_free(path);\n  return str;\n}\n\nstd::string CMemoryCardSys::ResolveDolphinCardPath(kabufuda::ECardSlot slot) {\n  if (g_Main->IsUSA() && !g_Main->IsTrilogy()) {\n    const auto dolphinPath = GetPrefPath(\"dolphin-emu\");\n    if (!dolphinPath) {\n      return {};\n    }\n    auto path = *dolphinPath;\n    path += fmt::format(\"GC/MemoryCard{:c}.USA.raw\", slot == kabufuda::ECardSlot::SlotA ? 'A' : 'B');\n\n    CBasics::Sstat theStat{};\n    if (CBasics::Stat(path.c_str(), &theStat) != 0 || !S_ISREG(theStat.st_mode)) {\n      /* legacy case for older dolphin versions */\n      const char* home = getenv(\"HOME\");\n      if (home == nullptr || home[0] != '/') {\n        return {};\n      }\n\n      path = home;\n#ifndef __APPLE__\n      path += fmt::format(\"/.dolphin-emu/GC/MemoryCard{:c}.USA.raw\",\n                          slot == kabufuda::ECardSlot::SlotA ? 'A' : 'B');\n#else\n      path += fmt::format(\"/Library/Application Support/Dolphin/GC/MemoryCard{:c}.USA.raw\",\n                          slot == kabufuda::ECardSlot::SlotA ? 'A' : 'B');\n#endif\n      if (CBasics::Stat(path.c_str(), &theStat) != 0 || !S_ISREG(theStat.st_mode)) {\n        return {};\n      }\n    }\n\n    return path;\n  }\n  return {};\n}\n\nstd::string CMemoryCardSys::_CreateDolphinCard(kabufuda::ECardSlot slot, bool dolphin) {\n  if (g_Main->IsUSA() && !g_Main->IsTrilogy()) {\n    if (dolphin) {\n      const auto dolphinPath = GetPrefPath(\"dolphin-emu\");\n      if (!dolphinPath) {\n        return {};\n      }\n      auto path = *dolphinPath + \"GC\";\n      int ret = mkdir(path.c_str(), 0755);\n      if (ret != 0 && errno != EEXIST) {\n        return {};\n      }\n\n      path += fmt::format(\"/MemoryCard{:c}.USA.raw\", slot == kabufuda::ECardSlot::SlotA ? 'A' : 'B');\n      auto* file = fopen(path.c_str(), \"wbe\");\n      if (file != nullptr) {\n        fclose(file);\n        return path;\n      }\n    } else {\n      std::string path = _GetDolphinCardPath(slot);\n      if (path.find('/') == std::string::npos) {\n        auto basePath = GetPrefPath(\"metaforce\");\n        if (!basePath) {\n          return {};\n        }\n        path = *basePath + _GetDolphinCardPath(slot);\n      }\n      std::string tmpPath = path.substr(0, path.find_last_of('/'));\n      int ret = mkdir(tmpPath.c_str(), 0755);\n      if (ret != 0 && ret != EEXIST) {\n        return {};\n      }\n      auto* file = fopen(path.c_str(), \"wbe\");\n      if (file != nullptr) {\n        fclose(file);\n        return path;\n      }\n    }\n  }\n  return {};\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CMemoryCardSysOSX.cpp",
    "content": "#include \"CMemoryCardSys.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/IMain.hpp\"\nnamespace metaforce {\n\nstd::string CMemoryCardSys::ResolveDolphinCardPath(kabufuda::ECardSlot slot) {\n  if (g_Main->IsUSA() && !g_Main->IsTrilogy()) {\n    const char* home = getenv(\"HOME\");\n    if (!home)\n      return {};\n\n    std::string path = home;\n    path += fmt::format(\"/Library/Application Support/Dolphin/GC/MemoryCard{:c}.USA.raw\",\n                        slot == kabufuda::ECardSlot::SlotA ? 'A' : 'B');\n\n    hecl::Sstat theStat;\n    if (hecl::Stat(path.c_str(), &theStat) || !S_ISREG(theStat.st_mode))\n      return {};\n\n    return path;\n  }\n  return {};\n}\n\nstd::string CMemoryCardSys::_CreateDolphinCard(kabufuda::ECardSlot slot, bool dolphin) {\n  if (g_Main->IsUSA() && !g_Main->IsTrilogy()) {\n    if (dolphin) {\n      const char* home = getenv(\"HOME\");\n      if (!home)\n        return {};\n\n      std::string path = home;\n      path += \"/Library/Application Support/Dolphin/GC\";\n      if (hecl::RecursiveMakeDir(path.c_str()) < 0)\n        return {};\n\n      path += fmt::format(\"/MemoryCard{:c}.USA.raw\", slot == kabufuda::ECardSlot::SlotA ? 'A' : 'B');\n      const auto fp = hecl::FopenUnique(path.c_str(), \"wb\");\n      if (fp == nullptr) {\n        return {};\n      }\n\n      return path;\n    } else {\n      std::string path = _GetDolphinCardPath(slot);\n      hecl::SanitizePath(path);\n      if (path.find('/') == std::string::npos) {\n        path = hecl::GetcwdStr() + \"/\" + _GetDolphinCardPath(slot);\n      }\n      std::string tmpPath = path.substr(0, path.find_last_of(\"/\"));\n      hecl::RecursiveMakeDir(tmpPath.c_str());\n      const auto fp = hecl::FopenUnique(path.c_str(), \"wb\");\n      if (fp) {\n        return path;\n      }\n    }\n  }\n  return {};\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CMemoryCardSysWin.cpp",
    "content": "#include \"Runtime/CMemoryCardSys.hpp\"\n\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/IMain.hpp\"\n#include \"Runtime/CBasics.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\n#include <ShlObj.h>\n#include <SDL3/SDL_filesystem.h>\n#include <nowide/stackstring.hpp>\n#include <nowide/convert.hpp>\n\nnamespace metaforce {\n\nstatic std::optional<std::string> GetPrefPath(const char* app) {\n  char* path = SDL_GetPrefPath(nullptr, app);\n  if (path == nullptr) {\n    return {};\n  }\n  std::string str{path};\n  SDL_free(path);\n  return str;\n}\n\n#if WINDOWS_STORE\nusing namespace Windows::Storage;\n#endif\n\n/* Partial path-selection logic from\n * https://github.com/dolphin-emu/dolphin/blob/master/Source/Core/UICommon/UICommon.cpp\n * Modified to not use dolphin-binary-relative paths. */\nstd::string CMemoryCardSys::ResolveDolphinCardPath(kabufuda::ECardSlot slot) {\n  if (g_Main->IsUSA() && !g_Main->IsTrilogy()) {\n#if !WINDOWS_STORE\n    /* Detect where the User directory is. There are two different cases\n     * 1. HKCU\\Software\\Dolphin Emulator\\UserConfigPath exists\n     *    -> Use this as the user directory path\n     * 2. My Documents exists\n     *    -> Use My Documents\\Dolphin Emulator as the User directory path\n     */\n\n    /* Check our registry keys */\n    HKEY hkey;\n    wchar_t configPath[MAX_PATH] = {0};\n    if (RegOpenKeyEx(HKEY_CURRENT_USER, L\"Software\\\\Dolphin Emulator\", 0, KEY_QUERY_VALUE, &hkey) == ERROR_SUCCESS) {\n      DWORD size = MAX_PATH;\n      if (RegQueryValueEx(hkey, L\"UserConfigPath\", nullptr, nullptr, (LPBYTE)configPath, &size) != ERROR_SUCCESS)\n        configPath[0] = 0;\n      RegCloseKey(hkey);\n    }\n\n    /* Get My Documents path in case we need it. */\n    wchar_t my_documents[MAX_PATH];\n    bool my_documents_found =\n        SUCCEEDED(SHGetFolderPath(nullptr, CSIDL_MYDOCUMENTS, nullptr, SHGFP_TYPE_CURRENT, my_documents));\n\n    std::string path;\n    if (configPath[0]) /* Case 1 */\n      path = nowide::narrow(configPath);\n    else if (my_documents_found) /* Case 2 */\n      path = nowide::narrow(my_documents) + \"/Dolphin Emulator\";\n    else /* Unable to find */\n      return {};\n#else\n    StorageFolder ^ localFolder = ApplicationData::Current->LocalFolder;\n    std::string path(localFolder->Path->Data());\n#endif\n\n    path += fmt::format(\"/GC/MemoryCard{}.USA.raw\", slot == kabufuda::ECardSlot::SlotA ? 'A' : 'B');\n\n    struct _stat64 theStat{};\n    if (_stat64(path.c_str(), &theStat) || !S_ISREG(theStat.st_mode))\n      return {};\n\n    return path;\n  }\n  return {};\n}\n\nstd::string CMemoryCardSys::_CreateDolphinCard(kabufuda::ECardSlot slot, bool dolphin) {\n  if (g_Main->IsUSA() && !g_Main->IsTrilogy()) {\n    if (dolphin) {\n#if !WINDOWS_STORE\n      /* Detect where the User directory is. There are two different cases\n       * 1. HKCU\\Software\\Dolphin Emulator\\UserConfigPath exists\n       *    -> Use this as the user directory path\n       * 2. My Documents exists\n       *    -> Use My Documents\\Dolphin Emulator as the User directory path\n       */\n\n      /* Check our registry keys */\n      HKEY hkey;\n      wchar_t configPath[MAX_PATH] = {0};\n      if (RegOpenKeyEx(HKEY_CURRENT_USER, L\"Software\\\\Dolphin Emulator\", 0, KEY_QUERY_VALUE, &hkey) == ERROR_SUCCESS) {\n        DWORD size = MAX_PATH;\n        if (RegQueryValueEx(hkey, L\"UserConfigPath\", nullptr, nullptr, (LPBYTE)configPath, &size) != ERROR_SUCCESS)\n          configPath[0] = 0;\n        RegCloseKey(hkey);\n      }\n\n      /* Get My Documents path in case we need it. */\n      wchar_t my_documents[MAX_PATH];\n      bool my_documents_found =\n          SUCCEEDED(SHGetFolderPath(nullptr, CSIDL_MYDOCUMENTS, nullptr, SHGFP_TYPE_CURRENT, my_documents));\n\n      std::string path;\n      if (configPath[0]) /* Case 1 */\n        path = nowide::narrow(configPath);\n      else if (my_documents_found) /* Case 2 */\n        path = nowide::narrow(my_documents) + \"/Dolphin Emulator\";\n      else /* Unable to find */\n        return {};\n#else\n      StorageFolder ^ localFolder = ApplicationData::Current->LocalFolder;\n      std::string path(localFolder->Path->Data());\n#endif\n\n      path += \"/GC\";\n      if (CBasics::RecursiveMakeDir(path.c_str()) < 0)\n        return {};\n\n      path += fmt::format(\"/MemoryCard{}.USA.raw\", slot == kabufuda::ECardSlot::SlotA ? 'A' : 'B');\n      const auto wpath = nowide::widen(path);\n      FILE* fp = _wfopen(wpath.c_str(), L\"wb\");\n      if (fp == nullptr) {\n        return {};\n      }\n      fclose(fp);\n\n      return path;\n    } else {\n      std::string path = _GetDolphinCardPath(slot);\n      if (path.find('/') == std::string::npos) {\n        auto prefPath = GetPrefPath(\"Metaforce\");\n        if (!prefPath) {\n          return {};\n        }\n        path = *prefPath + _GetDolphinCardPath(slot);\n      }\n      std::string tmpPath = path.substr(0, path.find_last_of('/'));\n      CBasics::RecursiveMakeDir(tmpPath.c_str());\n      const auto wpath = nowide::widen(path);\n      FILE* fp = _wfopen(wpath.c_str(), L\"wb\");\n      if (fp) {\n        fclose(fp);\n        return path;\n      }\n    }\n  }\n  return {};\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CObjectList.cpp",
    "content": "#include \"Runtime/CObjectList.hpp\"\n#ifndef NDEBUG\n#include \"Runtime/Logging.hpp\"\n#include \"Runtime/Formatting.hpp\"\n#endif\n\nnamespace metaforce {\n\nCObjectList::CObjectList(EGameObjectList listEnum) : x2004_listEnum(listEnum) {}\n\nvoid CObjectList::AddObject(CEntity& entity) {\n  if (IsQualified(entity)) {\n#ifndef NDEBUG\n    if (x0_list[entity.GetUniqueId().Value()].entity != nullptr &&\n        x0_list[entity.GetUniqueId().Value()].entity != &entity)\n      spdlog::fatal(\"INVALID USAGE DETECTED: Attempting to assign entity '{} ({})' to existing node '{}'!!!\",\n                    entity.GetName(), entity.GetEditorId(), entity.GetUniqueId().Value());\n#endif\n    s16 prevFirst = -1;\n    if (x2008_firstId != -1) {\n      x0_list[x2008_firstId].prev = entity.GetUniqueId().Value();\n      prevFirst = x2008_firstId;\n    }\n    x2008_firstId = entity.GetUniqueId().Value();\n    SObjectListEntry& newEnt = x0_list[x2008_firstId];\n    newEnt.entity = &entity;\n    newEnt.next = prevFirst;\n    newEnt.prev = -1;\n    ++x200a_count;\n  }\n}\n\nvoid CObjectList::RemoveObject(TUniqueId uid) {\n  SObjectListEntry& ent = x0_list[uid.Value()];\n  if (!ent.entity || ent.entity->GetUniqueId() != uid)\n    return;\n  if (uid.Value() == x2008_firstId) {\n    x2008_firstId = ent.next;\n    if (ent.next != -1)\n      x0_list[ent.next].prev = -1;\n  } else {\n    x0_list[ent.prev].next = ent.next;\n    if (ent.next != -1)\n      x0_list[ent.next].prev = ent.prev;\n  }\n  ent.entity = nullptr;\n  ent.next = -1;\n  ent.prev = -1;\n  --x200a_count;\n}\n\nconst CEntity* CObjectList::operator[](size_t i) const {\n  const SObjectListEntry& ent = x0_list[i];\n  if (!ent.entity || ent.entity->x30_26_scriptingBlocked)\n    return nullptr;\n  return ent.entity;\n}\n\nCEntity* CObjectList::operator[](size_t i) {\n  SObjectListEntry& ent = x0_list[i];\n  if (!ent.entity || ent.entity->x30_26_scriptingBlocked)\n    return nullptr;\n  return ent.entity;\n}\n\nconst CEntity* CObjectList::GetObjectById(TUniqueId uid) const {\n  if (uid == kInvalidUniqueId)\n    return nullptr;\n  const SObjectListEntry& ent = x0_list[uid.Value()];\n  if (!ent.entity || ent.entity->x30_26_scriptingBlocked)\n    return nullptr;\n  return ent.entity;\n}\n\nCEntity* CObjectList::GetObjectById(TUniqueId uid) {\n  if (uid == kInvalidUniqueId)\n    return nullptr;\n  SObjectListEntry& ent = x0_list[uid.Value()];\n  if (!ent.entity || ent.entity->x30_26_scriptingBlocked)\n    return nullptr;\n  return ent.entity;\n}\n\nconst CEntity* CObjectList::GetValidObjectById(TUniqueId uid) const {\n  if (uid == kInvalidUniqueId)\n    return nullptr;\n  const SObjectListEntry& ent = x0_list[uid.Value()];\n  if (!ent.entity)\n    return nullptr;\n  if (ent.entity->GetUniqueId() != uid)\n    return nullptr;\n  return ent.entity;\n}\n\nCEntity* CObjectList::GetValidObjectById(TUniqueId uid) {\n  if (uid == kInvalidUniqueId)\n    return nullptr;\n  SObjectListEntry& ent = x0_list[uid.Value()];\n  if (!ent.entity)\n    return nullptr;\n  if (ent.entity->GetUniqueId() != uid)\n    return nullptr;\n  return ent.entity;\n}\n\nbool CObjectList::IsQualified(const CEntity&) const { return true; }\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CObjectList.hpp",
    "content": "#pragma once\n\n#include <array>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/World/CEntity.hpp\"\n\nnamespace metaforce {\n\nenum class EGameObjectList {\n  Invalid = -1,\n  All,\n  Actor,\n  PhysicsActor,\n  GameCamera,\n  GameLight,\n  ListeningAi,\n  AiWaypoint,\n  PlatformAndDoor,\n};\n\nclass CObjectList {\n  friend class CGameArea;\n\n  struct SObjectListEntry {\n    CEntity* entity = nullptr;\n    s16 next = -1;\n    s16 prev = -1;\n  };\n  std::array<SObjectListEntry, kMaxEntities> x0_list; // was an rstl::reserved_vector\n  EGameObjectList x2004_listEnum;\n  s16 x2008_firstId = -1;\n  u16 x200a_count = 0;\n\npublic:\n  class iterator {\n    friend class CObjectList;\n    CObjectList& m_list;\n    s16 m_id;\n    iterator(CObjectList& list, s16 id) : m_list(list), m_id(id) {}\n\n  public:\n    iterator& operator++() {\n      m_id = m_list.GetNextObjectIndex(m_id);\n      return *this;\n    }\n    bool operator==(const iterator& other) const { return m_id == other.m_id; }\n    bool operator!=(const iterator& other) const { return !operator==(other); }\n    CEntity* operator*() const { return m_list.GetObjectByIndex(m_id); }\n  };\n\n  class const_iterator {\n    friend class CObjectList;\n    const CObjectList& m_list;\n    s16 m_id;\n    const_iterator(const CObjectList& list, s16 id) : m_list(list), m_id(id) {}\n\n  public:\n    const_iterator& operator++() {\n      m_id = m_list.GetNextObjectIndex(m_id);\n      return *this;\n    }\n    bool operator==(const iterator& other) const { return m_id == other.m_id; }\n    bool operator!=(const iterator& other) const { return !operator==(other); }\n    const CEntity* operator*() const { return m_list.GetObjectByIndex(m_id); }\n  };\n\n  [[nodiscard]] iterator begin() { return iterator(*this, x2008_firstId); }\n  [[nodiscard]] iterator end() { return iterator(*this, -1); }\n  [[nodiscard]] const_iterator begin() const { return const_iterator(*this, x2008_firstId); }\n  [[nodiscard]] const_iterator end() const { return const_iterator(*this, -1); }\n  [[nodiscard]] const_iterator cbegin() const { return begin(); }\n  [[nodiscard]] const_iterator cend() const { return end(); }\n\n  explicit CObjectList(EGameObjectList listEnum);\n  virtual ~CObjectList() = default;\n\n  void AddObject(CEntity& entity);\n  void RemoveObject(TUniqueId uid);\n  const CEntity* operator[](size_t i) const;\n  CEntity* operator[](size_t i);\n  const CEntity* GetObjectById(TUniqueId uid) const;\n  const CEntity* GetObjectByIndex(s16 index) const { return x0_list[index].entity; }\n  CEntity* GetObjectByIndex(s16 index) { return x0_list[index].entity; }\n  CEntity* GetObjectById(TUniqueId uid);\n  const CEntity* GetValidObjectById(TUniqueId uid) const;\n  CEntity* GetValidObjectById(TUniqueId uid);\n  s16 GetFirstObjectIndex() const { return x2008_firstId; }\n  s16 GetNextObjectIndex(s16 prev) const { return x0_list[prev].next; }\n  virtual bool IsQualified(const CEntity&) const;\n  u16 size() const { return x200a_count; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CPakFile.cpp",
    "content": "#include \"Runtime/CPakFile.hpp\"\n\n#include \"Runtime/Logging.hpp\"\n\nnamespace metaforce {\nCPakFile::CPakFile(std::string_view filename, bool buildDepList, bool worldPak, bool override) : CDvdFile(filename) {\n  if (!CDvdFile::operator bool())\n    spdlog::fatal(\"{}: Unable to open\", GetPath());\n  x28_24_buildDepList = buildDepList;\n  // x28_24_buildDepList = true; // Always do this so metaforce can rapidly pre-warm shaders\n  x28_26_worldPak = worldPak;\n  m_override = override;\n}\n\nCPakFile::~CPakFile() {\n  if (x30_dvdReq)\n    x30_dvdReq->PostCancelRequest();\n}\n\nconst SObjectTag* CPakFile::GetResIdByName(std::string_view name) const {\n  for (const std::pair<std::string, SObjectTag>& p : x54_nameList) {\n    if (CStringExtras::CompareCaseInsensitive(p.first, name)) {\n      return &p.second;\n    }\n  }\n  return nullptr;\n}\n\nvoid CPakFile::LoadResourceTable(CInputStream& r) {\n  x74_resList.reserve(\n      std::max(size_t(64), size_t(ROUND_UP_32(x4c_resTableCount * sizeof(SResInfo)) + sizeof(SResInfo) - 1)) /\n      sizeof(SResInfo));\n  if (x28_24_buildDepList)\n    x64_depList.reserve(x4c_resTableCount);\n  for (u32 i = 0; i < x4c_resTableCount; ++i) {\n    u32 flags = r.ReadLong();\n    FourCC fcc;\n    r.ReadBytes(reinterpret_cast<u8*>(&fcc), 4);\n    CAssetId id = r.Get<CAssetId>();\n    u32 size = r.ReadLong();\n    u32 offset = r.ReadLong();\n    if (fcc == FOURCC('MLVL'))\n      m_mlvlId = id;\n    x74_resList.emplace_back(id, fcc, offset, size, flags);\n    if (x28_24_buildDepList)\n      x64_depList.push_back(id);\n  }\n  std::sort(x74_resList.begin(), x74_resList.end(), [](const auto& a, const auto& b) { return a.x0_id < b.x0_id; });\n}\n\nvoid CPakFile::DataLoad() {\n  x30_dvdReq.reset();\n  CMemoryInStream r(x38_headerData.data() + x48_resTableOffset, x38_headerData.size() - x48_resTableOffset,\n                    CMemoryInStream::EOwnerShip::NotOwned);\n  LoadResourceTable(r);\n  x2c_asyncLoadPhase = EAsyncPhase::Loaded;\n  if (x28_26_worldPak) {\n    // Allocate ARAM space DMA x74_resList to ARAM\n  }\n  x38_headerData.clear();\n}\n\nvoid CPakFile::InitialHeaderLoad() {\n  CMemoryInStream r(x38_headerData.data(), x38_headerData.size(), CMemoryInStream::EOwnerShip::NotOwned);\n  x30_dvdReq.reset();\n  u32 version = r.ReadLong();\n  if (version != 0x00030005) {\n    spdlog::fatal(\"{}: Incompatible pak file version -- Current version is {:08X}, you're using {:08X}\", GetPath(),\n                  0x00030005, version);\n  }\n\n  r.ReadLong();\n  u32 nameCount = r.ReadLong();\n  x54_nameList.reserve(nameCount);\n  for (u32 i = 0; i < nameCount; ++i) {\n    SObjectTag tag(r);\n    auto name = CStringExtras::ReadString(r);\n    x54_nameList.emplace_back(name, tag);\n  }\n\n  x4c_resTableCount = r.ReadLong();\n  x48_resTableOffset = u32(r.GetReadPosition());\n  x2c_asyncLoadPhase = EAsyncPhase::DataLoad;\n  u32 newSize = ROUND_UP_32(x4c_resTableCount * 20 + x48_resTableOffset);\n  u32 origSize = u32(x38_headerData.size());\n  if (newSize > origSize) {\n    x38_headerData.resize(newSize);\n    x30_dvdReq = AsyncSeekRead(x38_headerData.data() + origSize, u32(x38_headerData.size() - origSize),\n                               ESeekOrigin::Begin, origSize);\n  } else {\n    DataLoad();\n  }\n};\n\nvoid CPakFile::Warmup() {\n  u32 length = std::min(u32(Length()), u32(8192));\n  x38_headerData.resize(length);\n  x30_dvdReq = AsyncSeekRead(x38_headerData.data(), length, ESeekOrigin::Cur, 0);\n  x2c_asyncLoadPhase = EAsyncPhase::InitialHeader;\n}\n\nconst CPakFile::SResInfo* CPakFile::GetResInfoForLoadPreferForward(CAssetId id) const {\n  if (x28_27_stashedInARAM)\n    return nullptr;\n  auto search =\n      rstl::binary_find(x74_resList.begin(), x74_resList.end(), id, [](const SResInfo& test) { return test.x0_id; });\n  if (search == x74_resList.end())\n    return nullptr;\n  const SResInfo* bestInfo = &*search;\n  s32 bestDelta = x84_currentSeek - bestInfo->GetOffset();\n  while (++search != x74_resList.end()) {\n    const SResInfo* thisInfo = &*search;\n    if (thisInfo->x0_id != id)\n      break;\n    s32 thisDelta = x84_currentSeek - bestInfo->GetOffset();\n    if ((bestDelta < 0 && (thisDelta > 0 || thisDelta > bestDelta)) ||\n        (bestDelta >= 0 && thisDelta > 0 && thisDelta < bestDelta)) {\n      bestDelta = thisDelta;\n      bestInfo = thisInfo;\n    }\n  }\n  x84_currentSeek = bestInfo->GetOffset() + bestInfo->GetSize();\n  return bestInfo;\n}\n\nconst CPakFile::SResInfo* CPakFile::GetResInfoForLoadDirectionless(CAssetId id) const {\n  if (x28_27_stashedInARAM)\n    return nullptr;\n  auto search =\n      rstl::binary_find(x74_resList.begin(), x74_resList.end(), id, [](const SResInfo& test) { return test.x0_id; });\n  if (search == x74_resList.end())\n    return nullptr;\n  const SResInfo* bestInfo = &*search;\n  s32 bestDelta = std::abs(s32(x84_currentSeek - bestInfo->GetOffset()));\n  while (++search != x74_resList.end()) {\n    const SResInfo* thisInfo = &*search;\n    if (thisInfo->x0_id != id)\n      break;\n    s32 thisDelta = std::abs(s32(x84_currentSeek - bestInfo->GetOffset()));\n    if (thisDelta < bestDelta) {\n      bestDelta = thisDelta;\n      bestInfo = thisInfo;\n    }\n  }\n  x84_currentSeek = bestInfo->GetOffset() + bestInfo->GetSize();\n  return bestInfo;\n}\n\nconst CPakFile::SResInfo* CPakFile::GetResInfo(CAssetId id) const {\n  if (x2c_asyncLoadPhase != EAsyncPhase::Loaded)\n    return nullptr;\n  if (x28_27_stashedInARAM)\n    return nullptr;\n  auto search =\n      rstl::binary_find(x74_resList.begin(), x74_resList.end(), id, [](const SResInfo& i) { return i.x0_id; });\n  if (search == x74_resList.end())\n    return nullptr;\n  return &*search;\n}\n\nvoid CPakFile::AsyncIdle() {\n  if (x2c_asyncLoadPhase == EAsyncPhase::Loaded)\n    return;\n  if (x30_dvdReq && !x30_dvdReq->IsComplete())\n    return;\n  switch (x2c_asyncLoadPhase) {\n  case EAsyncPhase::Warmup:\n    Warmup();\n    break;\n  case EAsyncPhase::InitialHeader:\n    InitialHeaderLoad();\n    break;\n  case EAsyncPhase::DataLoad:\n    DataLoad();\n    break;\n  default:\n    break;\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CPakFile.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <string>\n#include <vector>\n\n#include \"Runtime/CDvdFile.hpp\"\n#include \"Runtime/CDvdRequest.hpp\"\n#include \"Runtime/CFactoryMgr.hpp\"\n#include \"Runtime/CStringExtras.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n\nnamespace metaforce {\n\nclass CPakFile : public CDvdFile {\n  friend class CResLoader;\n\npublic:\n  struct SResInfo {\n    CAssetId x0_id;\n    bool x4_compressed : 1;\n    CFactoryMgr::ETypeTable x4_typeIdx;\n    u32 x5_offsetDiv32 : 27;\n    u32 x7_sizeDiv32 : 27;\n    SResInfo(CAssetId id, FourCC fcc, u32 offset, u32 size, u32 flags) : x0_id(id) {\n      x4_compressed = flags != 0;\n      x4_typeIdx = CFactoryMgr::FourCCToTypeIdx(fcc);\n      x5_offsetDiv32 = offset / 32;\n      x7_sizeDiv32 = size / 32;\n    }\n    u32 GetOffset() const { return x5_offsetDiv32 * 32; }\n    u32 GetSize() const { return x7_sizeDiv32 * 32; }\n    FourCC GetType() const { return CFactoryMgr::TypeIdxToFourCC(x4_typeIdx); }\n    bool IsCompressed() const { return x4_compressed; }\n    CAssetId GetId() const { return x0_id; }\n  };\n\nprivate:\n  bool x28_24_buildDepList : 1;\n  bool x28_25_aramFile : 1 = false;\n  bool x28_26_worldPak : 1;\n  bool x28_27_stashedInARAM : 1 = false;\n  bool m_override : 1;\n  enum class EAsyncPhase {\n    Warmup = 0,\n    InitialHeader = 1,\n    DataLoad = 2,\n    Loaded = 3\n  } x2c_asyncLoadPhase = EAsyncPhase::Warmup;\n  std::shared_ptr<IDvdRequest> x30_dvdReq; // Used to be auto_ptr\n  std::vector<u8> x38_headerData;\n  u32 x48_resTableOffset = 0;\n  u32 x4c_resTableCount = 0;\n  int x50_aramBase = -1;\n  std::vector<std::pair<std::string, SObjectTag>> x54_nameList;\n  std::vector<CAssetId> x64_depList;\n  std::vector<SResInfo> x74_resList;\n  mutable s32 x84_currentSeek = -1;\n  CAssetId m_mlvlId;\n  void LoadResourceTable(CInputStream& r);\n  void DataLoad();\n  void InitialHeaderLoad();\n  void Warmup();\n\npublic:\n  CPakFile(std::string_view filename, bool buildDepList, bool worldPak, bool override = false);\n  ~CPakFile();\n  const std::vector<std::pair<std::string, SObjectTag>>& GetNameList() const { return x54_nameList; }\n  const std::vector<CAssetId>& GetDepList() const { return x64_depList; }\n  const SObjectTag* GetResIdByName(std::string_view name) const;\n  const SResInfo* GetResInfoForLoadPreferForward(CAssetId id) const;\n  const SResInfo* GetResInfoForLoadDirectionless(CAssetId id) const;\n  const SResInfo* GetResInfo(CAssetId id) const;\n  bool IsWorldPak() const { return x28_26_worldPak; }\n  bool IsOverridePak() const { return m_override; }\n  u32 GetFakeStaticSize() const { return 0; }\n  void AsyncIdle();\n  CAssetId GetMLVLId() const { return m_mlvlId; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CPlayerState.cpp",
    "content": "#include \"Runtime/CPlayerState.hpp\"\n\n#include <algorithm>\n#include <array>\n#include <cstring>\n\n#include \"Runtime/CStringExtras.hpp\"\n#include \"Runtime/CMemoryCardSys.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/Camera/CCameraManager.hpp\"\n#include \"Runtime/Camera/CFirstPersonCamera.hpp\"\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\n#include <zeus/Math.hpp>\n\nnamespace metaforce {\nnamespace {\nconstexpr std::array<u32, 41> PowerUpMaxValues{\n    1, 1, 1, 1,  250, 1, 1, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n    1, 1, 1, 14, 1,   0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n};\n\n[[maybe_unused]] constexpr std::array<const char*, 41> PowerUpNames{\n    \"Power Beam\",\n    \"Ice Beam\",\n    \"Wave Beam\",\n    \"Plasma Beam\",\n    \"Missiles\",\n    \"Scan Visor\",\n    \"Morph Ball Bombs\",\n    \"Power Bombs\",\n    \"Flamethrower\",\n    \"Thermal Visor\",\n    \"Charge Beam\",\n    \"Super Missile\",\n    \"GrappleBeam\",\n    \"X-Ray Visor\",\n    \"Ice Spreader\",\n    \"Space Jump Boots\",\n    \"Morph Ball\",\n    \"Combat Visor\",\n    \"Boost Ball\",\n    \"Spider Ball\",\n    \"Power Suit\",\n    \"Gravity Suit\",\n    \"Varia Suit\",\n    \"Phazon Suit\",\n    \"Energy Tanks\",\n    \"Unknown Item 1\",\n    \"Health Refill\",\n    \"Unknown Item 2\",\n    \"Wavebuster\",\n    \"Artifact of Truth\",\n    \"Artifact of Strength\",\n    \"Artifact of Elder\",\n    \"Artifact of Wild\",\n    \"Artifact of Lifegiver\",\n    \"Artifact of Warrior\",\n    \"Artifact of Chozo\",\n    \"Artifact of Nature\",\n    \"Artifact of Sun\",\n    \"Artifact of World\",\n    \"Artifact of Spirit\",\n    \"Artifact of Newborn\",\n};\n\nconstexpr std::array<u32, 5> costs{\n    5, 10, 10, 10, 1,\n};\n\nconstexpr std::array<float, 5> ComboAmmoPeriods{\n    0.2f, 0.1f, 0.2f, 0.2f, 1.f,\n};\n} // Anonymous namespace\n\nCPlayerState::CPlayerState() { x24_powerups.resize(41); }\n\nCPlayerState::CPlayerState(CInputStream& stream) {\n  x4_enabledItems = u32(stream.ReadBits(32));\n\n  const u32 integralHP = u32(stream.ReadBits(32));\n  float realHP;\n  std::memcpy(&realHP, &integralHP, sizeof(float));\n\n  xc_health.SetHP(realHP);\n  x8_currentBeam = EBeamId(stream.ReadBits(CInputStream::GetBitCount(5)));\n  x20_currentSuit = EPlayerSuit(stream.ReadBits(CInputStream::GetBitCount(4)));\n  x24_powerups.resize(41);\n  for (size_t i = 0; i < x24_powerups.size(); ++i) {\n    if (PowerUpMaxValues[i] == 0) {\n      continue;\n    }\n\n    const u32 a = u32(stream.ReadBits(CInputStream::GetBitCount(PowerUpMaxValues[i])));\n    const u32 b = u32(stream.ReadBits(CInputStream::GetBitCount(PowerUpMaxValues[i])));\n    x24_powerups[i] = CPowerUp(a, b);\n  }\n\n  const auto& scanStates = g_MemoryCardSys->GetScanStates();\n  x170_scanTimes.reserve(scanStates.size());\n  for (const auto& state : scanStates) {\n    float time = stream.ReadBits(1) ? 1.f : 0.f;\n    x170_scanTimes.emplace_back(state.first, time);\n  }\n\n  x180_scanCompletionRate.first = u32(stream.ReadBits(CInputStream::GetBitCount(0x100u)));\n  x180_scanCompletionRate.second = u32(stream.ReadBits(CInputStream::GetBitCount(0x100u)));\n}\n\nvoid CPlayerState::PutTo(COutputStream& stream) {\n  stream.WriteBits(x4_enabledItems, 32);\n\n  const float realHP = xc_health.GetHP();\n  u32 integralHP;\n  std::memcpy(&integralHP, &realHP, sizeof(u32));\n\n  stream.WriteBits(integralHP, 32);\n  stream.WriteBits(u32(x8_currentBeam), COutputStream::GetBitCount(5));\n  stream.WriteBits(u32(x20_currentSuit), COutputStream::GetBitCount(4));\n  for (size_t i = 0; i < x24_powerups.size(); ++i) {\n    const CPowerUp& pup = x24_powerups[i];\n    stream.WriteBits(pup.x0_amount, COutputStream::GetBitCount(PowerUpMaxValues[i]));\n    stream.WriteBits(pup.x4_capacity, COutputStream::GetBitCount(PowerUpMaxValues[i]));\n  }\n\n  for (const auto& scanTime : x170_scanTimes) {\n    if (scanTime.second >= 1.f)\n      stream.WriteBits(true, 1);\n    else\n      stream.WriteBits(false, 1);\n  }\n\n  stream.WriteBits(x180_scanCompletionRate.first, COutputStream::GetBitCount(0x100));\n  stream.WriteBits(x180_scanCompletionRate.second, COutputStream::GetBitCount(0x100));\n}\n\nu32 CPlayerState::GetMissileCostForAltAttack() const { return costs[size_t(x8_currentBeam)]; }\n\nfloat CPlayerState::GetComboFireAmmoPeriod() const { return ComboAmmoPeriods[size_t(x8_currentBeam)]; }\n\nu32 CPlayerState::CalculateItemCollectionRate() const {\n  u32 total = GetItemCapacity(EItemType::PowerBombs);\n\n  if (total >= 4)\n    total -= 3;\n  total += GetItemCapacity(EItemType::WaveBeam);\n  total += GetItemCapacity(EItemType::IceBeam);\n  total += GetItemCapacity(EItemType::PlasmaBeam);\n  total += GetItemCapacity(EItemType::Missiles) / 5;\n  total += GetItemCapacity(EItemType::MorphBallBombs);\n  total += GetItemCapacity(EItemType::Flamethrower);\n  total += GetItemCapacity(EItemType::ThermalVisor);\n  total += GetItemCapacity(EItemType::ChargeBeam);\n  total += GetItemCapacity(EItemType::SuperMissile);\n  total += GetItemCapacity(EItemType::GrappleBeam);\n  total += GetItemCapacity(EItemType::XRayVisor);\n  total += GetItemCapacity(EItemType::IceSpreader);\n  total += GetItemCapacity(EItemType::SpaceJumpBoots);\n  total += GetItemCapacity(EItemType::MorphBall);\n  total += GetItemCapacity(EItemType::BoostBall);\n  total += GetItemCapacity(EItemType::SpiderBall);\n  total += GetItemCapacity(EItemType::GravitySuit);\n  total += GetItemCapacity(EItemType::VariaSuit);\n  total += GetItemCapacity(EItemType::EnergyTanks);\n  total += GetItemCapacity(EItemType::Truth);\n  total += GetItemCapacity(EItemType::Strength);\n  total += GetItemCapacity(EItemType::Elder);\n  total += GetItemCapacity(EItemType::Wild);\n  total += GetItemCapacity(EItemType::Lifegiver);\n  total += GetItemCapacity(EItemType::Warrior);\n  total += GetItemCapacity(EItemType::Chozo);\n  total += GetItemCapacity(EItemType::Nature);\n  total += GetItemCapacity(EItemType::Sun);\n  total += GetItemCapacity(EItemType::World);\n  total += GetItemCapacity(EItemType::Spirit);\n  total += GetItemCapacity(EItemType::Newborn);\n  return total + GetItemCapacity(EItemType::Wavebuster);\n}\n\nCHealthInfo& CPlayerState::GetHealthInfo() { return xc_health; }\n\nconst CHealthInfo& CPlayerState::GetHealthInfo() const { return xc_health; }\n\nCPlayerState::EPlayerSuit CPlayerState::GetCurrentSuit() const {\n  if (IsFusionEnabled())\n    return EPlayerSuit::FusionPower;\n\n  return x20_currentSuit;\n}\n\nbool CPlayerState::CanVisorSeeFog(const CStateManager& stateMgr) const {\n  EPlayerVisor activeVisor = GetActiveVisor(stateMgr);\n  if (activeVisor == EPlayerVisor::Combat || activeVisor == EPlayerVisor::Scan)\n    return true;\n  return true;\n}\n\nCPlayerState::EPlayerVisor CPlayerState::GetActiveVisor(const CStateManager& stateMgr) const {\n  const CFirstPersonCamera* cam =\n      TCastToConstPtr<CFirstPersonCamera>(stateMgr.GetCameraManager()->GetCurrentCamera(stateMgr)).GetPtr();\n  return (cam ? x14_currentVisor : EPlayerVisor::Combat);\n}\n\nvoid CPlayerState::UpdateStaticInterference(CStateManager& stateMgr, float dt) { x188_staticIntf.Update(stateMgr, dt); }\n\nvoid CPlayerState::SetScanTime(CAssetId res, float time) {\n  auto it = std::find_if(x170_scanTimes.begin(), x170_scanTimes.end(),\n                         [&](const auto& test) -> bool { return test.first == res; });\n\n  if (it != x170_scanTimes.end())\n    it->second = time;\n}\n\nfloat CPlayerState::GetScanTime(CAssetId res) const {\n  const auto it = std::find_if(x170_scanTimes.cbegin(), x170_scanTimes.cend(),\n                               [&](const auto& test) -> bool { return test.first == res; });\n\n  if (it == x170_scanTimes.end())\n    return 0.f;\n\n  return it->second;\n}\n\nbool CPlayerState::GetIsVisorTransitioning() const {\n  return x14_currentVisor != x18_transitioningVisor || x1c_visorTransitionFactor < 0.2f;\n}\n\nfloat CPlayerState::GetVisorTransitionFactor() const { return x1c_visorTransitionFactor / 0.2f; }\n\nvoid CPlayerState::UpdateVisorTransition(float dt) {\n  if (!GetIsVisorTransitioning())\n    return;\n\n  if (x14_currentVisor == x18_transitioningVisor) {\n    x1c_visorTransitionFactor += dt;\n    if (x1c_visorTransitionFactor > 0.2f)\n      x1c_visorTransitionFactor = 0.2f;\n  } else {\n    x1c_visorTransitionFactor -= dt;\n    if (x1c_visorTransitionFactor < 0.f) {\n      x14_currentVisor = x18_transitioningVisor;\n      x1c_visorTransitionFactor = std::fabs(x1c_visorTransitionFactor);\n      if (x1c_visorTransitionFactor > 0.19999f)\n        x1c_visorTransitionFactor = 0.19999f;\n    }\n  }\n}\n\nvoid CPlayerState::StartTransitionToVisor(CPlayerState::EPlayerVisor visor) {\n  if (x18_transitioningVisor == visor)\n    return;\n  x18_transitioningVisor = visor;\n}\n\nvoid CPlayerState::ResetVisor() {\n  x18_transitioningVisor = x14_currentVisor = EPlayerVisor::Combat;\n  x1c_visorTransitionFactor = 0.0f;\n}\n\nbool CPlayerState::ItemEnabled(CPlayerState::EItemType type) const {\n  if (HasPowerUp(type))\n    return (x4_enabledItems & (1 << u32(type)));\n  return false;\n}\n\nvoid CPlayerState::EnableItem(CPlayerState::EItemType type) {\n  if (HasPowerUp(type))\n    x4_enabledItems |= (1 << u32(type));\n}\n\nvoid CPlayerState::DisableItem(CPlayerState::EItemType type) {\n  if (HasPowerUp(type))\n    x4_enabledItems &= ~(1 << u32(type));\n}\n\nbool CPlayerState::HasPowerUp(CPlayerState::EItemType type) const {\n  if (type < EItemType::Max)\n    return x24_powerups[u32(type)].x4_capacity != 0;\n  return false;\n}\n\nu32 CPlayerState::GetItemCapacity(CPlayerState::EItemType type) const {\n  if (type < EItemType::Max)\n    return x24_powerups[u32(type)].x4_capacity;\n  return 0;\n}\n\nu32 CPlayerState::GetItemAmount(CPlayerState::EItemType type) const {\n  if (type == EItemType::SpaceJumpBoots || type == EItemType::PowerBombs || type == EItemType::Flamethrower ||\n      type == EItemType::EnergyTanks || type == EItemType::Missiles ||\n      (type >= EItemType::Truth && type <= EItemType::Newborn)) {\n    return x24_powerups[u32(type)].x0_amount;\n  }\n\n  return 0;\n}\n\nvoid CPlayerState::DecrPickup(CPlayerState::EItemType type, u32 amount) {\n  if (type >= EItemType::Max)\n    return;\n\n  if ((type == EItemType::Missiles || type >= EItemType::PowerBombs) && type < EItemType::ThermalVisor)\n    x24_powerups[u32(type)].x0_amount -= amount;\n}\n\nvoid CPlayerState::IncrPickup(EItemType type, u32 amount) {\n  if (type >= EItemType::Max)\n    return;\n\n  switch (type) {\n  case EItemType::Missiles:\n  case EItemType::PowerBombs:\n  case EItemType::ChargeBeam:\n  case EItemType::SpaceJumpBoots:\n  case EItemType::EnergyTanks:\n  case EItemType::Truth:\n  case EItemType::Strength:\n  case EItemType::Elder:\n  case EItemType::Wild:\n  case EItemType::Lifegiver:\n  case EItemType::Warrior:\n  case EItemType::Chozo:\n  case EItemType::Nature:\n  case EItemType::Sun:\n  case EItemType::World:\n  case EItemType::Spirit:\n  case EItemType::Newborn: {\n    CPowerUp& pup = x24_powerups[u32(type)];\n    pup.x0_amount = std::min(pup.x0_amount + u32(amount), pup.x4_capacity);\n\n    if (type == EItemType::EnergyTanks)\n      IncrPickup(EItemType::HealthRefill, 9999);\n    break;\n  }\n  case EItemType::HealthRefill:\n    xc_health.SetHP(std::min(amount + xc_health.GetHP(), CalculateHealth()));\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CPlayerState::ResetAndIncrPickUp(CPlayerState::EItemType type, u32 amount) {\n  x24_powerups[u32(type)].x0_amount = 0;\n  IncrPickup(type, amount);\n}\n\nfloat CPlayerState::CalculateHealth() {\n  return (GetEnergyTankCapacity() * x24_powerups[u32(EItemType::EnergyTanks)].x0_amount) + GetBaseHealthCapacity();\n}\n\nvoid CPlayerState::AddPowerUp(CPlayerState::EItemType type, u32 capacity) {\n  if (type >= EItemType::Max)\n    return;\n\n  CPowerUp& pup = x24_powerups[u32(type)];\n  pup.x4_capacity = zeus::clamp(0u, pup.x4_capacity + capacity, PowerUpMaxValues[u32(type)]);\n  pup.x0_amount = std::min(pup.x0_amount, pup.x4_capacity);\n  if (type >= EItemType::PowerSuit && type <= EItemType::PhazonSuit) {\n    if (HasPowerUp(EItemType::PhazonSuit))\n      x20_currentSuit = EPlayerSuit::Phazon;\n    else if (HasPowerUp(EItemType::GravitySuit))\n      x20_currentSuit = EPlayerSuit::Gravity;\n    else if (HasPowerUp(EItemType::VariaSuit))\n      x20_currentSuit = EPlayerSuit::Varia;\n    else\n      x20_currentSuit = EPlayerSuit::Power;\n  }\n}\n\nvoid CPlayerState::ReInitializePowerUp(CPlayerState::EItemType type, u32 capacity) {\n  x24_powerups[u32(type)].x4_capacity = 0;\n  AddPowerUp(type, capacity);\n}\n\nvoid CPlayerState::InitializeScanTimes() {\n  if (x170_scanTimes.size())\n    return;\n\n  const auto& scanStates = g_MemoryCardSys->GetScanStates();\n  x170_scanTimes.reserve(scanStates.size());\n  for (const auto& state : scanStates)\n    x170_scanTimes.emplace_back(state.first, 0.f);\n}\n\nu32 CPlayerState::GetPowerUpMaxValue(EItemType type) { return PowerUpMaxValues[size_t(type)]; }\n\nCPlayerState::EItemType CPlayerState::ItemNameToType(std::string_view name) {\n  static constexpr std::array<std::pair<std::string_view, EItemType>, 46> typeNameMap{{\n      {\"powerbeam\"sv, EItemType::PowerBeam},\n      {\"icebeam\"sv, EItemType::IceBeam},\n      {\"wavebeam\"sv, EItemType::WaveBeam},\n      {\"plasmabeam\"sv, EItemType::PlasmaBeam},\n      {\"missiles\"sv, EItemType::Missiles},\n      {\"scanvisor\"sv, EItemType::ScanVisor},\n      {\"bombs\"sv, EItemType::MorphBallBombs},\n      {\"ballbombs\"sv, EItemType::MorphBallBombs},\n      {\"morphballbombs\"sv, EItemType::MorphBallBombs},\n      {\"powerbombs\"sv, EItemType::PowerBombs},\n      {\"flamethrower\"sv, EItemType::Flamethrower},\n      {\"thermalvisor\"sv, EItemType::ThermalVisor},\n      {\"chargebeam\"sv, EItemType::ChargeBeam},\n      {\"supermissile\"sv, EItemType::SuperMissile},\n      {\"grapple\"sv, EItemType::GrappleBeam},\n      {\"grapplebeam\"sv, EItemType::GrappleBeam},\n      {\"xrayvisor\"sv, EItemType::XRayVisor},\n      {\"icespreader\"sv, EItemType::IceSpreader},\n      {\"spacejump\"sv, EItemType::SpaceJumpBoots},\n      {\"spacejumpboots\"sv, EItemType::SpaceJumpBoots},\n      {\"morphball\"sv, EItemType::MorphBall},\n      {\"combatvisor\"sv, EItemType::CombatVisor},\n      {\"boostball\"sv, EItemType::BoostBall},\n      {\"spiderball\"sv, EItemType::SpiderBall},\n      {\"powersuit\"sv, EItemType::PowerSuit},\n      {\"gravitysuit\"sv, EItemType::GravitySuit},\n      {\"variasuit\"sv, EItemType::VariaSuit},\n      {\"phazonsuit\"sv, EItemType::PhazonSuit},\n      {\"energytanks\"sv, EItemType::EnergyTanks},\n      {\"unknownitem1\"sv, EItemType::UnknownItem1},\n      {\"healthrefill\"sv, EItemType::HealthRefill},\n      {\"health\"sv, EItemType::HealthRefill},\n      {\"unknownitem2\"sv, EItemType::UnknownItem2},\n      {\"wavebuster\"sv, EItemType::Wavebuster},\n      {\"truth\"sv, EItemType::Truth},\n      {\"strength\"sv, EItemType::Strength},\n      {\"elder\"sv, EItemType::Elder},\n      {\"wild\"sv, EItemType::Wild},\n      {\"lifegiver\"sv, EItemType::Lifegiver},\n      {\"warrior\"sv, EItemType::Warrior},\n      {\"chozo\"sv, EItemType::Chozo},\n      {\"nature\"sv, EItemType::Nature},\n      {\"sun\"sv, EItemType::Sun},\n      {\"world\"sv, EItemType::World},\n      {\"spirit\"sv, EItemType::Spirit},\n      {\"newborn\"sv, EItemType::Newborn},\n  }};\n\n  std::string lowName{name};\n  CStringExtras::ToLower(lowName);\n\n  const auto iter = std::find_if(typeNameMap.cbegin(), typeNameMap.cend(),\n                                 [&lowName](const auto& entry) { return entry.first == lowName; });\n  if (iter == typeNameMap.cend()) {\n    return EItemType::Invalid;\n  }\n\n  return iter->second;\n}\n\nstd::string_view CPlayerState::ItemTypeToName(CPlayerState::EItemType type) {\n  switch (type) {\n  case EItemType::PowerBeam:\n    return \"Power Beam\"sv;\n  case EItemType::IceBeam:\n    return \"Ice Beam\"sv;\n  case EItemType::WaveBeam:\n    return \"Wave Beam\"sv;\n  case EItemType::PlasmaBeam:\n    return \"Plasma Beam\"sv;\n  case EItemType::Missiles:\n    return \"Missiles\"sv;\n  case EItemType::ScanVisor:\n    return \"Scan Visor\"sv;\n  case EItemType::MorphBallBombs:\n    return \"Morph Ball Bombs\"sv;\n  case EItemType::PowerBombs:\n    return \"Power Bombs\"sv;\n  case EItemType::Flamethrower:\n    return \"Flamethrower\"sv;\n  case EItemType::ThermalVisor:\n    return \"Thermal Visor\"sv;\n  case EItemType::ChargeBeam:\n    return \"Charge Beam\"sv;\n  case EItemType::SuperMissile:\n    return \"Super Missile\"sv;\n  case EItemType::GrappleBeam:\n    return \"Grapple Beam\"sv;\n  case EItemType::XRayVisor:\n    return \"X-Ray Visor\"sv;\n  case EItemType::IceSpreader:\n    return \"Ice Spreader\"sv;\n  case EItemType::SpaceJumpBoots:\n    return \"Space Jump Boots\"sv;\n  case EItemType::MorphBall:\n    return \"Morph Ball\"sv;\n  case EItemType::CombatVisor:\n    return \"Combat Visor\"sv;\n  case EItemType::BoostBall:\n    return \"Boost Ball\"sv;\n  case EItemType::SpiderBall:\n    return \"Spider Ball\"sv;\n  case EItemType::PowerSuit:\n    return \"Power Suit\"sv;\n  case EItemType::GravitySuit:\n    return \"Gravity Suit\"sv;\n  case EItemType::VariaSuit:\n    return \"Varia Suit\"sv;\n  case EItemType::PhazonSuit:\n    return \"Phazon Suit\"sv;\n  case EItemType::EnergyTanks:\n    return \"Energy Tanks\"sv;\n  case EItemType::HealthRefill:\n    return \"Health Refill\"sv;\n  case EItemType::Wavebuster:\n    return \"Wavebuster\"sv;\n  case EItemType::Truth:\n    return \"Artifact of Truth\"sv;\n  case EItemType::Strength:\n    return \"Artifact of Strength\"sv;\n  case EItemType::Elder:\n    return \"Artifact of Elder\"sv;\n  case EItemType::Wild:\n    return \"Artifact of Wild\"sv;\n  case EItemType::Lifegiver:\n    return \"Artifact of Lifegiver\"sv;\n  case EItemType::Warrior:\n    return \"Artifact of Warrior\"sv;\n  case EItemType::Chozo:\n    return \"Artifact of Chozo\"sv;\n  case EItemType::Nature:\n    return \"Artifact of Nature\"sv;\n  case EItemType::Sun:\n    return \"Artifact of Sun\"sv;\n  case EItemType::World:\n    return \"Artifact of World\"sv;\n  case EItemType::Spirit:\n    return \"Artifact of Spirit\"sv;\n  case EItemType::Newborn:\n    return \"Artifact of Newborn\"sv;\n  default:\n    return \"[unknown]\"sv;\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CPlayerState.hpp",
    "content": "#pragma once\n\n#include <string_view>\n#include <vector>\n\n#include \"Runtime/CStaticInterference.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/World/CHealthInfo.hpp\"\n\nnamespace metaforce {\n\nclass CPlayerState {\n  friend class CWorldTransManager;\n\npublic:\n  enum class EItemType : s32 {\n    Invalid = -1,\n    PowerBeam = 0,\n    IceBeam = 1,\n    WaveBeam = 2,\n    PlasmaBeam = 3,\n    Missiles = 4,\n    ScanVisor = 5,\n    MorphBallBombs = 6,\n    PowerBombs = 7,\n    Flamethrower = 8,\n    ThermalVisor = 9,\n    ChargeBeam = 10,\n    SuperMissile = 11,\n    GrappleBeam = 12,\n    XRayVisor = 13,\n    IceSpreader = 14,\n    SpaceJumpBoots = 15,\n    MorphBall = 16,\n    CombatVisor = 17,\n    BoostBall = 18,\n    SpiderBall = 19,\n    PowerSuit = 20,\n    GravitySuit = 21,\n    VariaSuit = 22,\n    PhazonSuit = 23,\n    EnergyTanks = 24,\n    UnknownItem1 = 25,\n    HealthRefill = 26,\n    UnknownItem2 = 27,\n    Wavebuster = 28,\n    Truth = 29,\n    Strength = 30,\n    Elder = 31,\n    Wild = 32,\n    Lifegiver = 33,\n    Warrior = 34,\n    Chozo = 35,\n    Nature = 36,\n    Sun = 37,\n    World = 38,\n    Spirit = 39,\n    Newborn = 40,\n\n    /* This must remain at the end of the list */\n    Max\n  };\n\n  enum class EPlayerVisor : u32 {\n    Combat,\n    XRay,\n    Scan,\n    Thermal,\n\n    /* This must remain at the end of the list */\n    Max\n  };\n\n  enum class EPlayerSuit : s32 {\n    Invalid = -1,\n    Power,\n    Gravity,\n    Varia,\n    Phazon,\n    FusionPower,\n    FusionGravity,\n    FusionVaria,\n    FusionPhazon\n  };\n\n  enum class EBeamId : s32 { Invalid = -1, Power, Ice, Wave, Plasma, Phazon, Phazon2 = 27 };\n\nprivate:\n  struct CPowerUp {\n    u32 x0_amount = 0;\n    u32 x4_capacity = 0;\n    constexpr CPowerUp() = default;\n    constexpr CPowerUp(u32 amount, u32 capacity) : x0_amount(amount), x4_capacity(capacity) {}\n  };\n  bool x0_24_alive : 1 = true;\n  bool x0_25_firingComboBeam : 1 = false;\n  bool x0_26_fusion : 1 = false;\n  u32 x4_enabledItems = 0;\n  EBeamId x8_currentBeam = EBeamId::Power;\n  CHealthInfo xc_health = {99.f, 50.f};\n  EPlayerVisor x14_currentVisor = EPlayerVisor::Combat;\n  EPlayerVisor x18_transitioningVisor = x14_currentVisor;\n  float x1c_visorTransitionFactor = 0.2f;\n  EPlayerSuit x20_currentSuit = EPlayerSuit::Power;\n  rstl::reserved_vector<CPowerUp, 41> x24_powerups;\n  std::vector<std::pair<CAssetId, float>> x170_scanTimes;\n  std::pair<u32, u32> x180_scanCompletionRate = {};\n  CStaticInterference x188_staticIntf{5};\n\n  bool m_canTakeDamage = true;\n\npublic:\n  u32 GetMissileCostForAltAttack() const;\n  float GetComboFireAmmoPeriod() const;\n  static constexpr float GetMissileComboChargeFactor() { return 1.8f; }\n  u32 CalculateItemCollectionRate() const;\n\n  CHealthInfo& GetHealthInfo();\n  const CHealthInfo& GetHealthInfo() const;\n  u32 GetPickupTotal() const { return 99; }\n  void SetIsFusionEnabled(bool val) { x0_26_fusion = val; }\n  bool IsFusionEnabled() const { return x0_26_fusion; }\n  EPlayerSuit GetCurrentSuit() const;\n  EPlayerSuit GetCurrentSuitRaw() const { return x20_currentSuit; }\n  EBeamId GetCurrentBeam() const { return x8_currentBeam; }\n  void SetCurrentBeam(EBeamId beam) { x8_currentBeam = beam; }\n  bool CanVisorSeeFog(const CStateManager& stateMgr) const;\n  EPlayerVisor GetCurrentVisor() const { return x14_currentVisor; }\n  EPlayerVisor GetTransitioningVisor() const { return x18_transitioningVisor; }\n  EPlayerVisor GetActiveVisor(const CStateManager& stateMgr) const;\n  void UpdateStaticInterference(CStateManager& stateMgr, float dt);\n  void IncreaseScanTime(u32 time, float val);\n  void SetScanTime(CAssetId res, float time);\n  float GetScanTime(CAssetId time) const;\n  bool GetIsVisorTransitioning() const;\n  float GetVisorTransitionFactor() const;\n  void UpdateVisorTransition(float dt);\n  void StartTransitionToVisor(EPlayerVisor visor);\n  void ResetVisor();\n  bool ItemEnabled(EItemType type) const;\n  void DisableItem(EItemType type);\n  void EnableItem(EItemType type);\n  bool HasPowerUp(EItemType type) const;\n  u32 GetItemCapacity(EItemType type) const;\n  u32 GetItemAmount(EItemType type) const;\n  void DecrPickup(EItemType type, u32 amount);\n  void IncrPickup(EItemType type, u32 amount);\n  void ResetAndIncrPickUp(EItemType type, u32 amount);\n  static float GetEnergyTankCapacity() { return 100.f; }\n  static float GetBaseHealthCapacity() { return 99.f; }\n  float CalculateHealth();\n  void ReInitializePowerUp(EItemType type, u32 capacity);\n  void AddPowerUp(EItemType type, u32 capacity);\n  u32 GetLogScans() const { return x180_scanCompletionRate.first; }\n  u32 GetTotalLogScans() const { return x180_scanCompletionRate.second; }\n  void SetScanCompletionRate(const std::pair<u32, u32>& p) { x180_scanCompletionRate = p; }\n  bool IsPlayerAlive() const { return x0_24_alive; }\n  void SetPlayerAlive(bool alive) { x0_24_alive = alive; }\n  bool IsFiringComboBeam() const { return x0_25_firingComboBeam; }\n  void SetFiringComboBeam(bool f) { x0_25_firingComboBeam = f; }\n  void InitializeScanTimes();\n  CStaticInterference& GetStaticInterference() { return x188_staticIntf; }\n  const std::vector<std::pair<CAssetId, float>>& GetScanTimes() const { return x170_scanTimes; }\n  CPlayerState();\n  explicit CPlayerState(CInputStream& stream);\n  void PutTo(COutputStream& stream);\n  static u32 GetPowerUpMaxValue(EItemType type);\n  static EItemType ItemNameToType(std::string_view name);\n  static std::string_view ItemTypeToName(EItemType type);\n  bool CanTakeDamage() const { return m_canTakeDamage; }\n  void SetCanTakeDamage(bool c) { m_canTakeDamage = c; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CRandom16.cpp",
    "content": "#include \"Runtime/CRandom16.hpp\"\n\nnamespace metaforce {\n\nCRandom16* CRandom16::g_randomNumber = nullptr;                // &DefaultRandom;\nCGlobalRandom* CGlobalRandom::g_currentGlobalRandom = nullptr; //&DefaultGlobalRandom;\nnamespace {\nu32 g_numNextCalls = 0;\nu32 g_lastSeed = 0;\n};\n\nvoid CRandom16::IncrementNumNextCalls() { ++g_numNextCalls; }\nu32 CRandom16::GetNumNextCalls() { return g_numNextCalls; }\nvoid CRandom16::ResetNumNextCalls() { g_numNextCalls = 0; }\nu32 CRandom16::GetLastSeed() { return g_lastSeed; }\nvoid CRandom16::SetLastSeed(u32 seed) { g_lastSeed = seed; }\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CRandom16.hpp",
    "content": "#pragma once\n\n#include \"Runtime/GCNTypes.hpp\"\n\nnamespace metaforce {\n\nclass CRandom16 {\n  s32 m_seed;\n  static CRandom16* g_randomNumber;\n\npublic:\n  explicit CRandom16(s32 seed = 99) : m_seed(seed) {}\n\n  s32 Next() {\n    IncrementNumNextCalls();\n    m_seed = (m_seed * 0x41c64e6d) + 0x00003039;\n    SetLastSeed(m_seed);\n    return (m_seed >> 16) & 0xffff;\n  }\n\n  s32 GetSeed() const { return m_seed; }\n\n  void SetSeed(s32 seed) { m_seed = seed; }\n\n  float Float() { return Next() * 0.000015259022f; }\n\n  float Range(float min, float max) { return min + Float() * (max - min); }\n\n  s32 Range(s32 min, s32 max) { return min + (Next() % ((max - min) + 1)); }\n\n  static CRandom16* GetRandomNumber() { return g_randomNumber; }\n  static void SetRandomNumber(CRandom16* rnd) { g_randomNumber = rnd; }\n  static void IncrementNumNextCalls();\n  static u32 GetNumNextCalls();\n  static void ResetNumNextCalls();\n  static u32 GetLastSeed();\n  static void SetLastSeed(u32 seed);\n};\n\nclass CGlobalRandom {\n  CRandom16& m_random;\n  CGlobalRandom* m_prev;\n  static CGlobalRandom* g_currentGlobalRandom;\n\npublic:\n  CGlobalRandom(CRandom16& rand) : m_random(rand), m_prev(g_currentGlobalRandom) {\n    g_currentGlobalRandom = this;\n    CRandom16::SetRandomNumber(&m_random);\n  }\n  ~CGlobalRandom() {\n    g_currentGlobalRandom = m_prev;\n    if (m_prev)\n      CRandom16::SetRandomNumber(&m_prev->m_random);\n    else\n      CRandom16::SetRandomNumber(nullptr);\n  }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CResFactory.cpp",
    "content": "#include \"Runtime/CResFactory.hpp\"\n\n#include \"CResourceNameDatabase.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStopwatch.hpp\"\n#include \"Runtime/Logging.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\n// #include \"optick.h\"\n\nnamespace metaforce {\nvoid CResFactory::AddToLoadList(SLoadingData&& data) {\n  const SObjectTag tag = data.x0_tag;\n  m_loadMap.insert_or_assign(tag, m_loadList.insert(m_loadList.end(), std::move(data)));\n}\n\nCFactoryFnReturn CResFactory::BuildSync(const SObjectTag& tag, const CVParamTransfer& xfer, CObjectReference* selfRef) {\n  CFactoryFnReturn ret;\n  if (x5c_factoryMgr.CanMakeMemory(tag)) {\n    std::unique_ptr<uint8_t[]> data;\n    int size = 0;\n    x4_loader.LoadMemResourceSync(tag, data, &size);\n    if (size)\n      ret = x5c_factoryMgr.MakeObjectFromMemory(tag, std::move(data), size, x4_loader.GetResourceCompression(tag), xfer,\n                                                selfRef);\n    else\n      ret = std::make_unique<TObjOwnerDerivedFromIObjUntyped>(nullptr);\n  } else {\n    if (auto rp = x4_loader.LoadNewResourceSync(tag, nullptr))\n      ret = x5c_factoryMgr.MakeObject(tag, *rp, xfer, selfRef);\n    else\n      ret = std::make_unique<TObjOwnerDerivedFromIObjUntyped>(nullptr);\n  }\n  spdlog::warn(\"sync-built {}\", tag);\n  return ret;\n}\n\nbool CResFactory::PumpResource(SLoadingData& data) {\n  // OPTICK_EVENT();\n  if (data.x8_dvdReq && data.x8_dvdReq->IsComplete()) {\n    data.x8_dvdReq.reset();\n    *data.xc_targetPtr =\n        x5c_factoryMgr.MakeObjectFromMemory(data.x0_tag, std::move(data.x10_loadBuffer), data.x14_resSize,\n                                            data.m_compressed, data.x18_cvXfer, data.m_selfRef);\n    spdlog::info(\"async-built {}\", data.x0_tag);\n    if (CResourceNameDatabase::instance()) {\n      if (const auto* name = CResourceNameDatabase::instance()->assetName(data.x0_tag.id)) {\n        spdlog::info(\"rep filepath: {}\", *name);\n      }\n    }\n    return true;\n  }\n  return false;\n}\n\nstd::unique_ptr<IObj> CResFactory::Build(const SObjectTag& tag, const CVParamTransfer& xfer,\n                                         CObjectReference* selfRef) {\n  auto search = m_loadMap.find(tag);\n  if (search != m_loadMap.end()) {\n    while (!PumpResource(*search->second) || !search->second->xc_targetPtr) {}\n    std::unique_ptr<IObj> ret = std::move(*search->second->xc_targetPtr);\n    m_loadList.erase(search->second);\n    m_loadMap.erase(search);\n    return ret;\n  }\n  return BuildSync(tag, xfer, selfRef);\n}\n\nvoid CResFactory::BuildAsync(const SObjectTag& tag, const CVParamTransfer& xfer, std::unique_ptr<IObj>* target,\n                             CObjectReference* selfRef) {\n  auto search = m_loadMap.find(tag);\n  if (search == m_loadMap.end()) {\n    SLoadingData data(tag, target, xfer, x4_loader.GetResourceCompression(tag), selfRef);\n    data.x14_resSize = x4_loader.ResourceSize(tag);\n    if (data.x14_resSize != 0) {\n      data.x10_loadBuffer = std::unique_ptr<u8[]>(new u8[data.x14_resSize]);\n      data.x8_dvdReq = x4_loader.LoadResourceAsync(tag, data.x10_loadBuffer.get());\n      AddToLoadList(std::move(data));\n    } else {\n      *target = std::make_unique<TObjOwnerDerivedFromIObjUntyped>(nullptr);\n    }\n  }\n}\n\nbool CResFactory::AsyncIdle(std::chrono::nanoseconds target) {\n  // OPTICK_EVENT();\n  if (m_loadList.empty()) {\n    return false;\n  }\n  auto startTime = std::chrono::high_resolution_clock::now();\n  do {\n    auto& task = m_loadList.front();\n    if (PumpResource(task)) {\n      m_loadMap.erase(task.x0_tag);\n      m_loadList.pop_front();\n      if (m_loadList.empty()) {\n        return false;\n      }\n    }\n  } while (std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now() - startTime) <\n           target);\n  return true;\n}\n\nvoid CResFactory::CancelBuild(const SObjectTag& tag) {\n  auto search = m_loadMap.find(tag);\n  if (search != m_loadMap.end()) {\n    if (search->second->x8_dvdReq)\n      search->second->x8_dvdReq->PostCancelRequest();\n    m_loadList.erase(search->second);\n    m_loadMap.erase(search);\n  }\n}\n\nvoid CResFactory::LoadPersistentResources(CSimplePool& sp) {\n  const auto& paks = x4_loader.GetPaks();\n  for (const auto& pak : paks) {\n    if (!pak->IsWorldPak()) {\n      for (const CAssetId& id : pak->GetDepList()) {\n        SObjectTag tag(GetResourceTypeById(id), id);\n        m_nonWorldTokens.push_back(sp.GetObj(tag));\n        m_nonWorldTokens.back().Lock();\n      }\n    }\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CResFactory.hpp",
    "content": "#pragma once\n\n#include <list>\n#include <memory>\n#include <unordered_map>\n#include <vector>\n\n#include \"Runtime/CResLoader.hpp\"\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/IFactory.hpp\"\n#include \"Runtime/IVParamObj.hpp\"\n\nnamespace metaforce {\nclass IDvdRequest;\nclass CSimplePool;\n\nclass CResFactory : public IFactory {\n  CResLoader x4_loader;\n  CFactoryMgr x5c_factoryMgr;\n\npublic:\n  struct SLoadingData {\n    SObjectTag x0_tag;\n    std::shared_ptr<IDvdRequest> x8_dvdReq;\n    std::unique_ptr<IObj>* xc_targetPtr = nullptr;\n    std::unique_ptr<u8[]> x10_loadBuffer;\n    u32 x14_resSize = 0;\n    CVParamTransfer x18_cvXfer;\n    bool m_compressed = false;\n    CObjectReference* m_selfRef = nullptr;\n\n    SLoadingData() = default;\n    SLoadingData(const SObjectTag& tag, std::unique_ptr<IObj>* ptr, const CVParamTransfer& xfer, bool compressed,\n                 CObjectReference* selfRef)\n    : x0_tag(tag), xc_targetPtr(ptr), x18_cvXfer(xfer), m_compressed(compressed), m_selfRef(selfRef) {}\n  };\n\nprivate:\n  std::list<SLoadingData> m_loadList;\n  std::unordered_map<SObjectTag, std::list<SLoadingData>::iterator> m_loadMap;\n  std::vector<CToken> m_nonWorldTokens; /* URDE: always keep non-world resources resident */\n  void AddToLoadList(SLoadingData&& data);\n  CFactoryFnReturn BuildSync(const SObjectTag&, const CVParamTransfer&, CObjectReference* selfRef);\n  bool PumpResource(SLoadingData& data);\n\npublic:\n  CResLoader& GetLoader() { return x4_loader; }\n  std::unique_ptr<IObj> Build(const SObjectTag&, const CVParamTransfer&, CObjectReference* selfRef) override;\n  void BuildAsync(const SObjectTag&, const CVParamTransfer&, std::unique_ptr<IObj>*,\n                  CObjectReference* selfRef) override;\n  bool AsyncIdle(std::chrono::nanoseconds target) override;\n  void CancelBuild(const SObjectTag&) override;\n\n  bool CanBuild(const SObjectTag& tag) override { return x4_loader.ResourceExists(tag); }\n\n  u32 ResourceSize(const metaforce::SObjectTag& tag) override { return x4_loader.ResourceSize(tag); }\n\n  std::unique_ptr<u8[]> LoadResourceSync(const metaforce::SObjectTag& tag) override {\n    return x4_loader.LoadResourceSync(tag);\n  }\n\n  std::unique_ptr<u8[]> LoadNewResourcePartSync(const metaforce::SObjectTag& tag, u32 off, u32 size) override {\n    return x4_loader.LoadNewResourcePartSync(tag, off, size);\n  }\n\n  void GetTagListForFile(const char* pakName, std::vector<SObjectTag>& out) const override {\n    return x4_loader.GetTagListForFile(pakName, out);\n  }\n\n  std::shared_ptr<IDvdRequest> LoadResourceAsync(const metaforce::SObjectTag& tag, void* target) override {\n    return x4_loader.LoadResourceAsync(tag, target);\n  }\n\n  std::shared_ptr<IDvdRequest> LoadResourcePartAsync(const metaforce::SObjectTag& tag, u32 off, u32 size,\n                                                     void* target) override {\n    return x4_loader.LoadResourcePartAsync(tag, off, size, target);\n  }\n\n  const SObjectTag* GetResourceIdByName(std::string_view name) const override {\n    return x4_loader.GetResourceIdByName(name);\n  }\n\n  FourCC GetResourceTypeById(CAssetId id) const override { return x4_loader.GetResourceTypeById(id); }\n\n  std::vector<std::pair<std::string, SObjectTag>> GetResourceIdToNameList() const {\n    return x4_loader.GetResourceIdToNameList();\n  }\n\n  void EnumerateResources(const std::function<bool(const SObjectTag&)>& lambda) const override {\n    return x4_loader.EnumerateResources(lambda);\n  }\n\n  void EnumerateNamedResources(const std::function<bool(std::string_view, const SObjectTag&)>& lambda) const override {\n    return x4_loader.EnumerateNamedResources(lambda);\n  }\n\n  void LoadPersistentResources(CSimplePool& sp);\n  void UnloadPersistentResources() { m_nonWorldTokens.clear(); }\n\n  CResLoader* GetResLoader() override { return &x4_loader; }\n  CFactoryMgr* GetFactoryMgr() override { return &x5c_factoryMgr; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CResLoader.cpp",
    "content": "#include \"Runtime/CResLoader.hpp\"\n\n#include \"Runtime/CPakFile.hpp\"\n#include \"Runtime/Logging.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\nnamespace metaforce {\nCResLoader::CResLoader() { x48_curPak = x18_pakLoadedList.end(); }\n\nconst std::vector<CAssetId>* CResLoader::GetTagListForFile(std::string_view name) const {\n  const std::string namePak = std::string(name).append(\".pak\");\n  for (const std::unique_ptr<CPakFile>& pak : x18_pakLoadedList) {\n    if (CStringExtras::CompareCaseInsensitive(namePak, pak->x18_path)) {\n      return &pak->GetDepList();\n    }\n  }\n  return nullptr;\n}\n\nvoid CResLoader::AddPakFileAsync(std::string_view name, bool buildDepList, bool worldPak, bool override) {\n  const std::string namePak = std::string(name).append(\".pak\");\n  if (CDvdFile::FileExists(namePak)) {\n    x30_pakLoadingList.emplace_back(std::make_unique<CPakFile>(namePak, buildDepList, worldPak, override));\n    ++x44_pakLoadingCount;\n  }\n}\n\nvoid CResLoader::AddPakFile(std::string_view name, bool samusPak, bool worldPak, bool override) {\n  AddPakFileAsync(name, samusPak, worldPak, override);\n  WaitForPakFileLoadingComplete();\n}\n\nvoid CResLoader::WaitForPakFileLoadingComplete() {\n  while (x44_pakLoadingCount)\n    AsyncIdlePakLoading();\n}\n\nstd::unique_ptr<CInputStream> CResLoader::LoadNewResourcePartSync(const SObjectTag& tag, u32 length, u32 offset,\n                                                                  void* extBuf) {\n  void* buf = extBuf;\n  if (buf == nullptr) {\n    buf = new u8[length];\n  }\n\n  CPakFile* const file = FindResourceForLoad(tag);\n  file->SyncSeekRead(buf, length, ESeekOrigin::Begin, x50_cachedResInfo->GetOffset() + offset);\n  return std::make_unique<CMemoryInStream>(\n      buf, length, extBuf == nullptr ? CMemoryInStream::EOwnerShip::Owned : CMemoryInStream::EOwnerShip::NotOwned);\n}\n\nvoid CResLoader::LoadMemResourceSync(const SObjectTag& tag, std::unique_ptr<u8[]>& bufOut, int* sizeOut) {\n  if (CPakFile* file = FindResourceForLoad(tag)) {\n    bufOut = std::unique_ptr<u8[]>(new u8[x50_cachedResInfo->GetSize()]);\n    file->SyncSeekRead(bufOut.get(), x50_cachedResInfo->GetSize(), ESeekOrigin::Begin, x50_cachedResInfo->GetOffset());\n    *sizeOut = x50_cachedResInfo->GetSize();\n  }\n}\n\nstd::unique_ptr<CInputStream> CResLoader::LoadResourceFromMemorySync(const SObjectTag& tag, const void* buf) {\n  FindResourceForLoad(tag);\n  std::unique_ptr<CInputStream> newStrm =\n      std::make_unique<CMemoryInStream>(buf, x50_cachedResInfo->GetSize(), CMemoryInStream::EOwnerShip::NotOwned);\n  if (x50_cachedResInfo->IsCompressed()) {\n    newStrm->ReadLong();\n    newStrm = std::make_unique<CZipInputStream>(std::move(newStrm));\n  }\n  return newStrm;\n}\n\nstd::unique_ptr<CInputStream> CResLoader::LoadNewResourceSync(const SObjectTag& tag, void* extBuf) {\n  if (CPakFile* const file = FindResourceForLoad(tag)) {\n    const size_t resSz = ROUND_UP_32(x50_cachedResInfo->GetSize());\n\n    void* buf = extBuf;\n    if (buf == nullptr) {\n      buf = new u8[resSz];\n    }\n\n    file->SyncSeekRead(buf, resSz, ESeekOrigin::Begin, x50_cachedResInfo->GetOffset());\n\n    const bool takeOwnership = extBuf == nullptr;\n    std::unique_ptr<CInputStream> newStrm = std::make_unique<CMemoryInStream>(\n        buf, resSz, takeOwnership ? CMemoryInStream::EOwnerShip::Owned : CMemoryInStream::EOwnerShip::NotOwned);\n    if (x50_cachedResInfo->IsCompressed()) {\n      newStrm->ReadLong();\n      newStrm = std::make_unique<CZipInputStream>(std::move(newStrm));\n    }\n\n    return newStrm;\n  }\n\n  return nullptr;\n}\n\nstd::shared_ptr<IDvdRequest> CResLoader::LoadResourcePartAsync(const SObjectTag& tag, u32 off, u32 size, void* buf) {\n  CPakFile* file = FindResourceForLoad(tag.id);\n  return file->AsyncSeekRead(buf, size, ESeekOrigin::Begin, x50_cachedResInfo->GetOffset() + off);\n}\n\nstd::shared_ptr<IDvdRequest> CResLoader::LoadResourceAsync(const SObjectTag& tag, void* buf) {\n  CPakFile* file = FindResourceForLoad(tag.id);\n  return file->AsyncSeekRead(buf, ROUND_UP_32(x50_cachedResInfo->GetSize()), ESeekOrigin::Begin,\n                             x50_cachedResInfo->GetOffset());\n}\n\nstd::unique_ptr<u8[]> CResLoader::LoadResourceSync(const metaforce::SObjectTag& tag) {\n  CPakFile* file = FindResourceForLoad(tag.id);\n  u32 size = ROUND_UP_32(x50_cachedResInfo->GetSize());\n  std::unique_ptr<u8[]> ret(new u8[size]);\n  file->SyncSeekRead(ret.get(), size, ESeekOrigin::Begin, x50_cachedResInfo->GetOffset());\n  return ret;\n}\n\nstd::unique_ptr<u8[]> CResLoader::LoadNewResourcePartSync(const metaforce::SObjectTag& tag, u32 off, u32 size) {\n  CPakFile* file = FindResourceForLoad(tag.id);\n  std::unique_ptr<u8[]> ret(new u8[size]);\n  file->SyncSeekRead(ret.get(), size, ESeekOrigin::Begin, x50_cachedResInfo->GetOffset() + off);\n  return ret;\n}\n\nvoid CResLoader::GetTagListForFile(const char* pakName, std::vector<SObjectTag>& out) const {\n  std::string path = std::string(pakName) + \".pak\";\n\n  for (const std::unique_ptr<CPakFile>& file : m_overridePakList) {\n    if (_GetTagListForFile(out, path, file))\n      return;\n  }\n\n  for (const std::unique_ptr<CPakFile>& file : x18_pakLoadedList) {\n    if (_GetTagListForFile(out, path, file))\n      return;\n  }\n}\n\nbool CResLoader::_GetTagListForFile(std::vector<SObjectTag>& out, const std::string& path,\n                                    const std::unique_ptr<CPakFile>& file) const {\n  if (CStringExtras::CompareCaseInsensitive(file->GetPath(), path)) {\n    const auto& depList = file->GetDepList();\n    out.reserve(depList.size());\n    for (const auto& dep : depList) {\n      const auto* const resInfo = file->GetResInfo(dep);\n      out.emplace_back(resInfo->GetType(), dep);\n    }\n    return true;\n  }\n  return false;\n}\n\nbool CResLoader::GetResourceCompression(const SObjectTag& tag) const {\n  if (FindResource(tag.id))\n    return x50_cachedResInfo->IsCompressed();\n  return false;\n}\n\nu32 CResLoader::ResourceSize(const SObjectTag& tag) const {\n  if (FindResource(tag.id))\n    return x50_cachedResInfo->GetSize();\n  return 0;\n}\n\nbool CResLoader::ResourceExists(const SObjectTag& tag) const { return FindResource(tag.id); }\n\nFourCC CResLoader::GetResourceTypeById(CAssetId id) const {\n  if (id.IsValid() && FindResource(id))\n    return x50_cachedResInfo->GetType();\n  return {};\n}\n\nconst SObjectTag* CResLoader::GetResourceIdByName(std::string_view name) const {\n  for (const std::unique_ptr<CPakFile>& file : m_overridePakList)\n    if (const SObjectTag* id = file->GetResIdByName(name))\n      return id;\n\n  for (const std::unique_ptr<CPakFile>& file : x18_pakLoadedList)\n    if (const SObjectTag* id = file->GetResIdByName(name))\n      return id;\n  return nullptr;\n}\n\nbool CResLoader::AreAllPaksLoaded() const { return x44_pakLoadingCount == 0; }\n\nvoid CResLoader::AsyncIdlePakLoading() {\n  for (auto it = x30_pakLoadingList.begin(); it != x30_pakLoadingList.end();) {\n    (*it)->AsyncIdle();\n    if ((*it)->x2c_asyncLoadPhase == CPakFile::EAsyncPhase::Loaded) {\n      MoveToCorrectLoadedList(std::move(*it));\n      it = x30_pakLoadingList.erase(it);\n      --x44_pakLoadingCount;\n      continue;\n    }\n    ++it;\n  }\n}\n\nbool CResLoader::FindResource(CAssetId id) const {\n  if (x4c_cachedResId == id)\n    return true;\n\n  for (auto it = m_overridePakList.begin(); it != m_overridePakList.end(); ++it) {\n    if (CacheFromPak(**it, id)) {\n      return true;\n    }\n  }\n\n  if (x48_curPak != x18_pakLoadedList.end())\n    if (CacheFromPak(**x48_curPak, id))\n      return true;\n\n  for (auto it = x18_pakLoadedList.begin(); it != x18_pakLoadedList.end(); ++it) {\n    if (it == x48_curPak)\n      continue;\n    if (CacheFromPak(**it, id))\n      return true;\n  }\n\n  spdlog::warn(\"Unable to find asset {}\", id);\n  return false;\n}\n\nCPakFile* CResLoader::FindResourceForLoad(CAssetId id) {\n  for (auto it = m_overridePakList.begin(); it != m_overridePakList.end(); ++it) {\n    if (CacheFromPakForLoad(**it, id)) {\n      return &**it;\n    }\n  }\n\n  if (x48_curPak != x18_pakLoadedList.end())\n    if (CacheFromPakForLoad(**x48_curPak, id))\n      return &**x48_curPak;\n\n  for (auto it = x18_pakLoadedList.begin(); it != x18_pakLoadedList.end(); ++it) {\n    if (it == x48_curPak)\n      continue;\n    if (CacheFromPakForLoad(**it, id)) {\n      x48_curPak = it;\n      return &**it;\n    }\n  }\n\n  spdlog::error(\"Unable to find asset {}\", id);\n  return nullptr;\n}\n\nCPakFile* CResLoader::FindResourceForLoad(const SObjectTag& tag) { return FindResourceForLoad(tag.id); }\n\nbool CResLoader::CacheFromPakForLoad(CPakFile& file, CAssetId id) {\n  const CPakFile::SResInfo* info;\n  if (x54_forwardSeek) {\n    info = file.GetResInfoForLoadPreferForward(id);\n    x54_forwardSeek = false;\n  } else {\n    info = file.GetResInfoForLoadDirectionless(id);\n  }\n  if (info) {\n    x4c_cachedResId = id;\n    x50_cachedResInfo = info;\n    return true;\n  }\n  return false;\n}\n\nbool CResLoader::CacheFromPak(const CPakFile& file, CAssetId id) const {\n  const CPakFile::SResInfo* info = file.GetResInfo(id);\n  if (info) {\n    x4c_cachedResId = id;\n    x50_cachedResInfo = info;\n    return true;\n  }\n  return false;\n}\n\nvoid CResLoader::MoveToCorrectLoadedList(std::unique_ptr<CPakFile>&& file) {\n  if (file->IsOverridePak())\n    m_overridePakList.push_back(std::move(file));\n  else\n    x18_pakLoadedList.push_back(std::move(file));\n}\n\nstd::vector<std::pair<std::string, SObjectTag>> CResLoader::GetResourceIdToNameList() const {\n  std::vector<std::pair<std::string, SObjectTag>> ret;\n  for (auto it = x18_pakLoadedList.begin(); it != x18_pakLoadedList.end(); ++it)\n    for (const auto& name : (*it)->GetNameList())\n      ret.push_back(name);\n  return ret;\n}\n\nvoid CResLoader::EnumerateResources(const std::function<bool(const SObjectTag&)>& lambda) const {\n  for (auto it = m_overridePakList.begin(); it != m_overridePakList.end(); ++it) {\n    for (const CAssetId& id : (*it)->GetDepList()) {\n      SObjectTag fcc(GetResourceTypeById(id), id);\n      if (!lambda(fcc))\n        return;\n    }\n  }\n  for (auto it = x18_pakLoadedList.begin(); it != x18_pakLoadedList.end(); ++it) {\n    for (const CAssetId& id : (*it)->GetDepList()) {\n      SObjectTag fcc(GetResourceTypeById(id), id);\n      if (!lambda(fcc))\n        return;\n    }\n  }\n}\n\nvoid CResLoader::EnumerateNamedResources(const std::function<bool(std::string_view, const SObjectTag&)>& lambda) const {\n  for (auto it = x18_pakLoadedList.begin(); it != x18_pakLoadedList.end(); ++it)\n    for (const auto& name : (*it)->GetNameList())\n      if (!lambda(name.first, name.second))\n        return;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CResLoader.hpp",
    "content": "#pragma once\n\n#include <functional>\n#include <list>\n#include <memory>\n#include <string>\n#include <vector>\n\n#include \"Runtime/CPakFile.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n\nnamespace metaforce {\nclass IDvdRequest;\nstruct SObjectTag;\n\nclass CResLoader {\n  std::string m_loaderPath;\n  // std::list<std::unique_ptr<CPakFile>> x0_aramList;\n  std::list<std::unique_ptr<CPakFile>> x18_pakLoadedList;\n  std::list<std::unique_ptr<CPakFile>> x30_pakLoadingList;\n  std::list<std::unique_ptr<CPakFile>>\n      m_overridePakList; // URDE Addition, Trilogy has a similar mechanism, need to verify behavior against it\n  u32 x44_pakLoadingCount = 0;\n  std::list<std::unique_ptr<CPakFile>>::iterator x48_curPak;\n  mutable CAssetId x4c_cachedResId;\n  mutable const CPakFile::SResInfo* x50_cachedResInfo = nullptr;\n  bool x54_forwardSeek = false;\n\n  bool _GetTagListForFile(std::vector<SObjectTag>& out, const std::string& path,\n                          const std::unique_ptr<CPakFile>& file) const;\n\npublic:\n  CResLoader();\n  const std::vector<CAssetId>* GetTagListForFile(std::string_view name) const;\n  void AddPakFileAsync(std::string_view name, bool buildDepList, bool worldPak, bool override = false);\n  void AddPakFile(std::string_view name, bool samusPak, bool worldPak, bool override = false);\n  void WaitForPakFileLoadingComplete();\n  std::unique_ptr<CInputStream> LoadNewResourcePartSync(const SObjectTag& tag, u32 length, u32 offset, void* extBuf);\n  void LoadMemResourceSync(const SObjectTag& tag, std::unique_ptr<u8[]>& bufOut, int* sizeOut);\n  std::unique_ptr<CInputStream> LoadResourceFromMemorySync(const SObjectTag& tag, const void* buf);\n  std::unique_ptr<CInputStream> LoadNewResourceSync(const SObjectTag& tag, void* extBuf = nullptr);\n  std::shared_ptr<IDvdRequest> LoadResourcePartAsync(const SObjectTag& tag, u32 off, u32 size, void* buf);\n  std::shared_ptr<IDvdRequest> LoadResourceAsync(const SObjectTag& tag, void* buf);\n  std::unique_ptr<u8[]> LoadResourceSync(const metaforce::SObjectTag& tag);\n  std::unique_ptr<u8[]> LoadNewResourcePartSync(const metaforce::SObjectTag& tag, u32 off, u32 size);\n  void GetTagListForFile(const char* pakName, std::vector<SObjectTag>& out) const;\n  bool GetResourceCompression(const SObjectTag& tag) const;\n  u32 ResourceSize(const SObjectTag& tag) const;\n  bool ResourceExists(const SObjectTag& tag) const;\n  FourCC GetResourceTypeById(CAssetId id) const;\n  const SObjectTag* GetResourceIdByName(std::string_view name) const;\n  bool AreAllPaksLoaded() const;\n  void AsyncIdlePakLoading();\n  bool FindResource(CAssetId id) const;\n  CPakFile* FindResourceForLoad(CAssetId id);\n  CPakFile* FindResourceForLoad(const SObjectTag& tag);\n  bool CacheFromPakForLoad(CPakFile& file, CAssetId id);\n  bool CacheFromPak(const CPakFile& file, CAssetId id) const;\n  void MoveToCorrectLoadedList(std::unique_ptr<CPakFile>&& file);\n  std::vector<std::pair<std::string, SObjectTag>> GetResourceIdToNameList() const;\n  void EnumerateResources(const std::function<bool(const SObjectTag&)>& lambda) const;\n  void EnumerateNamedResources(const std::function<bool(std::string_view, const SObjectTag&)>& lambda) const;\n  const std::list<std::unique_ptr<CPakFile>>& GetPaks() const { return x18_pakLoadedList; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CResourceNameDatabase.cpp",
    "content": "#include \"Runtime/CResourceNameDatabase.hpp\"\n\n#include \"Runtime/CBasics.hpp\"\n#include \"Runtime/ConsoleVariables/FileStoreManager.hpp\"\n#include \"Runtime/Streams/CMemoryInStream.hpp\"\n\nnamespace metaforce {\nconstexpr std::string_view kDatabaseName = \"mp_resource_names_confirmed.bin\";\nCResourceNameDatabase* CResourceNameDatabase::m_instance = nullptr;\n\nCResourceNameDatabase::CResourceNameDatabase(FileStoreManager& store) {\n  m_instance = this;\n  const std::string filename = std::string(store.getStoreRoot()) + \"/\" + std::string(kDatabaseName);\n  if (!CBasics::IsFile(filename.c_str())) {\n    return;\n  }\n\n  CBasics::Sstat st;\n  if (CBasics::Stat(filename.c_str(), &st) != 0) {\n    return;\n  }\n\n  std::unique_ptr<u8> const inBuf(new u8[st.st_size]);\n  auto* file = fopen(filename.c_str(), \"rb\");\n  (void)fread(inBuf.get(), 1, st.st_size, file);\n  (void)fclose(file);\n  CMemoryInStream mem(inBuf.get(), st.st_size, CMemoryInStream::EOwnerShip::NotOwned);\n\n  u32 count = mem.Get<u32>();\n  while ((count--) != 0u) {\n    const CAssetId assetId = mem.Get<u32>();\n    const auto name = mem.Get<std::string>();\n    m_assetNames[assetId] = name;\n  }\n}\n} // namespace metaforce"
  },
  {
    "path": "Runtime/CResourceNameDatabase.hpp",
    "content": "#pragma once\n#include \"RetroTypes.hpp\"\n\n#include <unordered_map>\n\nnamespace metaforce {\nclass FileStoreManager;\nclass CResourceNameDatabase {\npublic:\n  explicit CResourceNameDatabase(FileStoreManager& store);\n\n  bool hasAssetName(const CAssetId asset) const { return m_assetNames.contains(asset); }\n\n  const std::string* assetName(const CAssetId asset) const {\n    if (!hasAssetName(asset)) {\n      return nullptr;\n    }\n\n    return &m_assetNames.at(asset);\n  }\n\n  static CResourceNameDatabase* instance() { return m_instance; }\n\nprivate:\n  std::unordered_map<CAssetId, std::string> m_assetNames;\n\n  static CResourceNameDatabase* m_instance;\n};\n} // namespace metaforce"
  },
  {
    "path": "Runtime/CScannableObjectInfo.cpp",
    "content": "#include \"Runtime/CScannableObjectInfo.hpp\"\n\n#include \"Runtime/GameGlobalObjects.hpp\"\n\nnamespace metaforce {\nCScannableObjectInfo::CScannableObjectInfo(CInputStream& in, CAssetId resId) : x0_scannableObjectId(resId) {\n  const u32 version = in.ReadLong();\n  Load(in, version);\n\n  for (auto& bucket : x14_buckets) {\n    bucket.x4_appearanceRange *= x8_totalDownloadTime;\n  }\n\n  const float appearanceOffset = g_tweakGui->GetScanAppearanceDuration();\n  for (size_t i = 0; i < x14_buckets.size(); ++i) {\n    if (x14_buckets[i].x8_imagePos == UINT32_MAX) {\n      continue;\n    }\n\n    x8_totalDownloadTime += appearanceOffset;\n    for (size_t j = i; j < x14_buckets.size(); j++) {\n      x14_buckets[j].x4_appearanceRange += appearanceOffset;\n    }\n  }\n\n  for (size_t i = 0; i < x14_buckets.size() - 1; ++i) {\n    for (size_t j = i + 1; j < x14_buckets.size(); ++j) {\n      if (x14_buckets[i].x8_imagePos == x14_buckets[j].x8_imagePos && x14_buckets[i].x8_imagePos != UINT32_MAX) {\n        x14_buckets[j].x8_imagePos = UINT32_MAX;\n      }\n    }\n  }\n}\n\nvoid CScannableObjectInfo::Load(CInputStream& in, u32 version) {\n  in.ReadLong();\n  in.ReadLong();\n  x4_stringId = in.Get<CAssetId>();\n  if (version < 4) {\n    x8_totalDownloadTime = in.ReadFloat();\n  } else {\n    const u32 scanSpeed = in.ReadLong();\n    x8_totalDownloadTime = g_tweakGui->GetScanSpeed(scanSpeed);\n  }\n  xc_category = in.ReadLong();\n  if (version > 4) {\n    x10_important = in.ReadBool();\n  }\n\n  for (size_t i = 0; i < x14_buckets.capacity(); i++) {\n    x14_buckets.emplace_back(in, version);\n  }\n}\n\nCScannableObjectInfo::SBucket::SBucket(CInputStream& in, u32 version) {\n  x0_texture = in.Get<CAssetId>();\n  x4_appearanceRange = in.ReadFloat();\n  x8_imagePos = in.ReadLong();\n  if (version > 1) {\n    xc_size.x = in.ReadLong();\n    xc_size.y = in.ReadLong();\n    x14_interval = in.ReadFloat();\n    if (version >= 3)\n      x18_fadeDuration = in.ReadFloat();\n  }\n}\n\nCFactoryFnReturn FScannableObjectInfoFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer&,\n                                             CObjectReference*) {\n  return TToken<CScannableObjectInfo>::GetIObjObjectFor(std::make_unique<CScannableObjectInfo>(in, tag.id));\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CScannableObjectInfo.hpp",
    "content": "#pragma once\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/IFactory.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n\n#include <zeus/CVector2i.hpp>\n\nnamespace metaforce {\nclass CScannableObjectInfo {\npublic:\n  enum class EPanelType {};\n\n  static constexpr size_t NumBuckets = 4;\n\n  struct SBucket {\n    CAssetId x0_texture;\n    float x4_appearanceRange = 0.f;\n    u32 x8_imagePos = 0;\n    zeus::CVector2i xc_size;\n    float x14_interval = 0.f;\n    float x18_fadeDuration = 0.f;\n    SBucket(CInputStream&, u32 version);\n  };\n\nprivate:\n  void Load(CInputStream&, u32);\n  CAssetId x0_scannableObjectId;\n  CAssetId x4_stringId;\n  float x8_totalDownloadTime = 0.f;\n  u32 xc_category = 0;\n  bool x10_important = false;\n  rstl::reserved_vector<SBucket, NumBuckets> x14_buckets;\n\npublic:\n  CScannableObjectInfo(CInputStream&, CAssetId);\n  CAssetId GetScannableObjectId() const { return x0_scannableObjectId; }\n  CAssetId GetStringTableId() const { return x4_stringId; }\n  float GetTotalDownloadTime() const { return x8_totalDownloadTime; }\n  const SBucket& GetBucket(size_t idx) const { return x14_buckets[idx]; }\n  u32 GetCategory() const { return xc_category; }\n  bool IsImportant() const { return x10_important; }\n};\n\nCFactoryFnReturn FScannableObjectInfoFactory(const SObjectTag&, CInputStream&, const CVParamTransfer&,\n                                             CObjectReference* selfRef);\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CScriptMailbox.cpp",
    "content": "#include \"Runtime/CScriptMailbox.hpp\"\n\n#include \"Runtime/CWorldSaveGameInfo.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\n#include <algorithm>\n\nnamespace metaforce {\n\nCScriptMailbox::CScriptMailbox(CInputStream& in, const CWorldSaveGameInfo& saveWorld) {\n  const u32 relayCount = saveWorld.GetRelayCount();\n  if (saveWorld.GetRelayCount()) {\n    std::vector<bool> relayStates(saveWorld.GetRelayCount());\n    for (u32 i = 0; i < relayCount; ++i) {\n      relayStates[i] = in.ReadBits(1);\n    }\n\n    for (u32 i = 0; i < relayCount; ++i) {\n      if (!relayStates[i]) {\n        continue;\n      }\n      x0_relays.push_back(saveWorld.GetRelayEditorId(i));\n    }\n  }\n}\n\nbool CScriptMailbox::HasMsg(TEditorId id) const {\n  return std::find(x0_relays.cbegin(), x0_relays.cend(), id) != x0_relays.cend();\n}\n\nvoid CScriptMailbox::AddMsg(TEditorId id) {\n  if (HasMsg(id)) {\n    return;\n  }\n\n  x0_relays.push_back(id);\n}\n\nvoid CScriptMailbox::RemoveMsg(TEditorId id) {\n  if (!HasMsg(id)) {\n    return;\n  }\n\n  std::erase(x0_relays, id);\n}\n\nvoid CScriptMailbox::SendMsgs(TAreaId areaId, CStateManager& stateMgr) {\n  const CWorld* world = stateMgr.GetWorld();\n  u32 relayCount = world->GetRelayCount();\n\n  bool hasActiveRelays = false;\n  for (u32 i = 0; i < relayCount; ++i) {\n    const CWorld::CRelay& relay = world->GetRelay(i);\n    if (relay.GetTargetId().AreaNum() != areaId)\n      continue;\n\n    if (!HasMsg(relay.GetRelayId()))\n      continue;\n\n    stateMgr.SendScriptMsg(kInvalidUniqueId, relay.GetTargetId(), EScriptObjectMessage(relay.GetMessage()),\n                           EScriptObjectState::Any);\n    if (relay.GetActive())\n      hasActiveRelays = true;\n  }\n\n  if (!hasActiveRelays)\n    return;\n\n  for (u32 i = 0; i < relayCount; ++i) {\n    const CWorld::CRelay& relay = world->GetRelay(i);\n    if (relay.GetTargetId().AreaNum() != areaId)\n      continue;\n\n    if (!HasMsg(relay.GetRelayId()) || !relay.GetActive())\n      continue;\n\n    RemoveMsg(relay.GetRelayId());\n  }\n}\n\nvoid CScriptMailbox::PutTo(COutputStream& out, const CWorldSaveGameInfo& saveWorld) {\n  const u32 relayCount = saveWorld.GetRelayCount();\n  std::vector<bool> relays(relayCount);\n\n  for (const TEditorId& id : x0_relays) {\n    const s32 idx = saveWorld.GetRelayIndex(id);\n    if (idx >= 0) {\n      relays[idx] = true;\n    }\n  }\n\n  for (u32 i = 0; i < relayCount; ++i) {\n    out.WriteBits(u32(relays[i]), 1);\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CScriptMailbox.hpp",
    "content": "#pragma once\n\n#include <vector>\n\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/World/ScriptObjectSupport.hpp\"\n\nnamespace metaforce {\nclass CWorldSaveGameInfo;\nclass CStateManager;\n\n#if 0\nstruct CMailMessage\n{\n    TEditorId x0_id;\n    EScriptObjectMessage x4_msg;\n    bool x8_;\n    CMailMessage(TEditorId id, EScriptObjectMessage msg, bool flag) : x0_id(id), x4_msg(msg), x8_(flag) {}\n    CMailMessage(const CMailMessage& other) : x0_id(other.x0_id), x4_msg(other.x4_msg), x8_(other.x8_) {}\n\n    bool operator==(const CMailMessage& other) const\n    { return (x0_id == other.x0_id && x4_msg == other.x4_msg); }\n};\n#endif\n\nclass CScriptMailbox {\n  std::vector<TEditorId> x0_relays;\n\npublic:\n  CScriptMailbox() = default;\n  CScriptMailbox(CInputStream& in, const CWorldSaveGameInfo& saveWorld);\n\n  bool HasMsg(TEditorId id) const;\n  void AddMsg(TEditorId id);\n  void RemoveMsg(TEditorId id);\n  void SendMsgs(TAreaId areaId, CStateManager& stateMgr);\n  void PutTo(COutputStream& out, const CWorldSaveGameInfo& saveWorld);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CSimplePool.cpp",
    "content": "#include \"Runtime/CSimplePool.hpp\"\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/IVParamObj.hpp\"\n\n#include <cassert>\n\nnamespace metaforce {\n\nCSimplePool::CSimplePool(IFactory& factory)\n: x18_factory(factory), x1c_paramXfer(new TObjOwnerParam<IObjectStore*>(this)) {}\n\nCSimplePool::~CSimplePool() { assert(x8_resources.empty() && \"Dangling CSimplePool resources detected\"); }\n\nCToken CSimplePool::GetObj(const SObjectTag& tag, const CVParamTransfer& paramXfer) {\n  if (!tag) {\n    return {};\n  }\n\n  const auto iter = x8_resources.find(tag);\n  if (iter != x8_resources.end()) {\n    return CToken(iter->second);\n  }\n\n  auto* const ret = new CObjectReference(*this, nullptr, tag, paramXfer);\n  x8_resources.emplace(tag, ret);\n  return CToken(ret);\n}\n\nCToken CSimplePool::GetObj(const SObjectTag& tag) { return GetObj(tag, x1c_paramXfer); }\n\nCToken CSimplePool::GetObj(std::string_view resourceName) { return GetObj(resourceName, x1c_paramXfer); }\n\nCToken CSimplePool::GetObj(std::string_view resourceName, const CVParamTransfer& paramXfer) {\n  const SObjectTag* tag = x18_factory.GetResourceIdByName(resourceName);\n  if (!tag)\n    return {};\n  return GetObj(*tag, paramXfer);\n}\n\nbool CSimplePool::HasObject(const SObjectTag& tag) const {\n  auto iter = x8_resources.find(tag);\n  if (iter != x8_resources.cend())\n    return true;\n  return x18_factory.CanBuild(tag);\n}\n\nbool CSimplePool::ObjectIsLive(const SObjectTag& tag) const {\n  auto iter = x8_resources.find(tag);\n  if (iter == x8_resources.cend())\n    return false;\n  return iter->second->IsLoaded();\n}\n\nvoid CSimplePool::Flush() {}\n\nvoid CSimplePool::ObjectUnreferenced(const SObjectTag& tag) {\n  auto iter = x8_resources.find(tag);\n  if (iter != x8_resources.end())\n    x8_resources.erase(iter);\n}\n\nstd::vector<SObjectTag> CSimplePool::GetReferencedTags() const {\n  std::vector<SObjectTag> ret;\n  ret.reserve(x8_resources.size());\n  for (const auto& obj : x8_resources)\n    ret.push_back(obj.first);\n  return ret;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CSimplePool.hpp",
    "content": "#pragma once\n\n#include <unordered_map>\n#include <vector>\n\n#include \"Runtime/IObjectStore.hpp\"\n#include \"Runtime/IVParamObj.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n\nnamespace metaforce {\nclass CObjectReference;\nclass IFactory;\n\nclass CSimplePool : public IObjectStore {\nprotected:\n  u8 x4_;\n  u8 x5_;\n  std::unordered_map<SObjectTag, CObjectReference*> x8_resources;\n  IFactory& x18_factory;\n  CVParamTransfer x1c_paramXfer;\n\npublic:\n  CSimplePool(IFactory& factory);\n  ~CSimplePool() override;\n  CToken GetObj(const SObjectTag&, const CVParamTransfer&) override;\n  CToken GetObj(const SObjectTag&) override;\n  CToken GetObj(std::string_view) override;\n  CToken GetObj(std::string_view, const CVParamTransfer&) override;\n  bool HasObject(const SObjectTag&) const override;\n  bool ObjectIsLive(const SObjectTag&) const override;\n  IFactory& GetFactory() const override { return x18_factory; }\n  void Flush() override;\n  void ObjectUnreferenced(const SObjectTag&) override;\n  std::vector<SObjectTag> GetReferencedTags() const;\n  size_t GetLiveObjects() const { return x8_resources.size(); }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CSortedLists.cpp",
    "content": "#include \"Runtime/CSortedLists.hpp\"\n\n#include \"Runtime/World/CActor.hpp\"\n\n#include <algorithm>\n#include <cassert>\n\nnamespace metaforce {\nnamespace {\ntemplate <typename T, typename S>\nauto AccessElement(T& arr, S idx) -> typename T::reference {\n  assert(std::size(arr) > static_cast<size_t>(idx) && idx >= 0);\n  return arr[idx];\n}\n\ntemplate <typename T, typename S>\nauto AccessElement(const T& arr, S idx) -> typename T::const_reference {\n  assert(std::size(arr) > static_cast<size_t>(idx) && idx >= 0);\n  return arr[idx];\n}\n} // Anonymous namespace\n\nCSortedListManager::CSortedListManager() { Reset(); }\n\nvoid CSortedListManager::Reset() {\n  x0_nodes.fill(SNode{});\n\n  for (auto& list : xb000_sortedLists) {\n    list.Reset();\n  }\n}\n\nvoid CSortedListManager::AddToLinkedList(s16 nodeId, s16& headId, s16& tailId) {\n  if (headId == -1) {\n    AccessElement(x0_nodes, nodeId).x28_next = headId;\n    headId = nodeId;\n    tailId = nodeId;\n  } else {\n    if (AccessElement(x0_nodes, nodeId).x28_next != -1) {\n      return;\n    }\n    if (tailId == nodeId) {\n      return;\n    }\n    AccessElement(x0_nodes, nodeId).x28_next = headId;\n    headId = nodeId;\n  }\n}\n\nvoid CSortedListManager::RemoveFromList(ESortedList list, s16 idx) {\n  const auto listIndex = static_cast<size_t>(list);\n  SSortedList& sl = xb000_sortedLists[listIndex];\n\n  while (idx < sl.x800_size - 1) {\n    AccessElement(x0_nodes, AccessElement(sl.x0_ids, idx + 1)).x1c_selfIdxs[listIndex] = idx;\n    AccessElement(sl.x0_ids, idx) = AccessElement(sl.x0_ids, idx + 1);\n    ++idx;\n  }\n\n  --sl.x800_size;\n}\n\nvoid CSortedListManager::MoveInList(ESortedList list, s16 idx) {\n  const auto listIndex = static_cast<size_t>(list);\n  SSortedList& sl = xb000_sortedLists[listIndex];\n\n  while (true) {\n    if (idx > 0 && AccessElement(x0_nodes, AccessElement(sl.x0_ids, idx - 1)).x4_box[listIndex] >\n                       AccessElement(x0_nodes, AccessElement(sl.x0_ids, idx)).x4_box[listIndex]) {\n      AccessElement(x0_nodes, AccessElement(sl.x0_ids, idx - 1)).x1c_selfIdxs[listIndex] = idx;\n      AccessElement(x0_nodes, AccessElement(sl.x0_ids, idx)).x1c_selfIdxs[listIndex] = idx - 1;\n      std::swap(AccessElement(sl.x0_ids, idx), AccessElement(sl.x0_ids, idx - 1));\n      --idx;\n    } else {\n      if (idx >= sl.x800_size - 1) {\n        return;\n      }\n      if (AccessElement(x0_nodes, AccessElement(sl.x0_ids, idx + 1)).x4_box[listIndex] >=\n          AccessElement(x0_nodes, AccessElement(sl.x0_ids, idx)).x4_box[listIndex]) {\n        return;\n      }\n      AccessElement(x0_nodes, AccessElement(sl.x0_ids, idx + 1)).x1c_selfIdxs[listIndex] = idx;\n      AccessElement(x0_nodes, AccessElement(sl.x0_ids, idx)).x1c_selfIdxs[listIndex] = idx + 1;\n      std::swap(AccessElement(sl.x0_ids, idx), AccessElement(sl.x0_ids, idx + 1));\n      ++idx;\n    }\n  }\n}\n\nvoid CSortedListManager::InsertInList(ESortedList list, SNode& node) {\n  const auto listIndex = static_cast<size_t>(list);\n  SSortedList& sl = xb000_sortedLists[listIndex];\n  int insIdx = 0;\n\n  for (int i = sl.x800_size; i > 0;) {\n    /* Binary search cycle to find insert index */\n    if (AccessElement(x0_nodes, AccessElement(sl.x0_ids, insIdx + i / 2)).x4_box[listIndex] < node.x4_box[listIndex]) {\n      /* Upper */\n      insIdx = insIdx + i / 2 + 1;\n      i = i - i / 2 - 1;\n    } else {\n      /* Lower */\n      i /= 2;\n    }\n  }\n\n  /* Shift ids for insert */\n  for (int i = sl.x800_size; i > insIdx; --i) {\n    AccessElement(x0_nodes, AccessElement(sl.x0_ids, i - 1)).x1c_selfIdxs[listIndex] = i;\n    AccessElement(sl.x0_ids, i) = AccessElement(sl.x0_ids, i - 1);\n  }\n\n  /* Do insert */\n  AccessElement(sl.x0_ids, insIdx) = node.x0_actor->GetUniqueId().Value();\n  node.x1c_selfIdxs[listIndex] = s16(insIdx);\n  ++sl.x800_size;\n}\n\ns16 CSortedListManager::FindInListUpper(ESortedList list, float value) const {\n  const auto listIndex = static_cast<size_t>(list);\n  const SSortedList& sl = xb000_sortedLists[listIndex];\n  int idx = 0;\n\n  for (int i = sl.x800_size; i > 0;) {\n    // Binary search cycle to find index\n    if (!(value < AccessElement(x0_nodes, AccessElement(sl.x0_ids, idx + i / 2)).x4_box[listIndex])) {\n      // Upper\n      idx = idx + i / 2 + 1;\n      i = i - i / 2 - 1;\n    } else {\n      // Lower\n      i /= 2;\n    }\n  }\n\n  return idx;\n}\n\ns16 CSortedListManager::FindInListLower(ESortedList list, float value) const {\n  const auto listIndex = static_cast<size_t>(list);\n  const SSortedList& sl = xb000_sortedLists[listIndex];\n  int idx = 0;\n\n  for (int i = sl.x800_size; i > 0;) {\n    // Binary search cycle to find index\n    if (AccessElement(x0_nodes, AccessElement(sl.x0_ids, idx + i / 2)).x4_box[listIndex] < value) {\n      // Upper\n      idx = idx + i / 2 + 1;\n      i = i - i / 2 - 1;\n    } else {\n      // Lower\n      i /= 2;\n    }\n  }\n\n  return idx;\n}\n\ns16 CSortedListManager::ConstructIntersectionArray(const zeus::CAABox& aabb) {\n  const int minXa = FindInListLower(ESortedList::MinX, aabb.min.x());\n  const int maxXa = FindInListUpper(ESortedList::MinX, aabb.max.x());\n  const int minXb = FindInListLower(ESortedList::MaxX, aabb.min.x());\n  const int maxXb = FindInListUpper(ESortedList::MaxX, aabb.max.x());\n  const int xEnd = std::min(int(xb000_sortedLists[3].x800_size) - maxXb, minXa) + (maxXb + (maxXa - minXa) - minXb) / 2;\n\n  const int minYa = FindInListLower(ESortedList::MinY, aabb.min.y());\n  const int maxYa = FindInListUpper(ESortedList::MinY, aabb.max.y());\n  const int minYb = FindInListLower(ESortedList::MaxY, aabb.min.y());\n  const int maxYb = FindInListUpper(ESortedList::MaxY, aabb.max.y());\n  const int yEnd = std::min(int(xb000_sortedLists[4].x800_size) - maxYb, minYa) + (maxYb + (maxYa - minYa) - minYb) / 2;\n\n  const int minZa = FindInListLower(ESortedList::MinZ, aabb.min.z());\n  const int maxZa = FindInListUpper(ESortedList::MinZ, aabb.max.z());\n  const int minZb = FindInListLower(ESortedList::MaxZ, aabb.min.z());\n  const int maxZb = FindInListUpper(ESortedList::MaxZ, aabb.max.z());\n  const int zEnd = std::min(int(xb000_sortedLists[5].x800_size) - maxZb, minZa) + (maxZb + (maxZa - minZa) - minZb) / 2;\n\n  if (xEnd < yEnd && xEnd < zEnd) {\n    return CalculateIntersections(ESortedList::MinX, ESortedList::MaxX, minXa, maxXa, minXb, maxXb, ESortedList::MinY,\n                                  ESortedList::MaxY, ESortedList::MinZ, ESortedList::MaxZ, aabb);\n  } else if (yEnd < zEnd) {\n    return CalculateIntersections(ESortedList::MinY, ESortedList::MaxY, minYa, maxYa, minYb, maxYb, ESortedList::MinX,\n                                  ESortedList::MaxX, ESortedList::MinZ, ESortedList::MaxZ, aabb);\n  } else {\n    return CalculateIntersections(ESortedList::MinZ, ESortedList::MaxZ, minZa, maxZa, minZb, maxZb, ESortedList::MinX,\n                                  ESortedList::MaxX, ESortedList::MinY, ESortedList::MaxY, aabb);\n  }\n}\n\ns16 CSortedListManager::CalculateIntersections(ESortedList la, ESortedList lb, s16 a, s16 b, s16 c, s16 d,\n                                               ESortedList slA, ESortedList slB, ESortedList slC, ESortedList slD,\n                                               const zeus::CAABox& aabb) {\n  const auto listAIndex = static_cast<size_t>(la);\n  const auto listBIndex = static_cast<size_t>(lb);\n\n  s16 headId = -1;\n  s16 tailId = -1;\n  for (int i = a; i < b; ++i) {\n    AddToLinkedList(AccessElement(xb000_sortedLists[listAIndex].x0_ids, i), headId, tailId);\n  }\n  for (int i = c; i < d; ++i) {\n    AddToLinkedList(AccessElement(xb000_sortedLists[listBIndex].x0_ids, i), headId, tailId);\n  }\n\n  if (a < xb000_sortedLists[listBIndex].x800_size - d) {\n    for (int i = 0; i < a; ++i) {\n      const s16 id = AccessElement(xb000_sortedLists[listAIndex].x0_ids, i);\n      if (AccessElement(x0_nodes, id).x4_box[listBIndex] > aabb[listBIndex]) {\n        AddToLinkedList(id, headId, tailId);\n      }\n    }\n  } else {\n    for (int i = d; i < xb000_sortedLists[listBIndex].x800_size; ++i) {\n      const s16 id = AccessElement(xb000_sortedLists[listBIndex].x0_ids, i);\n      if (AccessElement(x0_nodes, id).x4_box[listAIndex] < aabb[listAIndex]) {\n        AddToLinkedList(id, headId, tailId);\n      }\n    }\n  }\n\n  for (s16* id = &headId; *id != -1;) {\n    SNode& node = AccessElement(x0_nodes, *id);\n    if (node.x4_box[size_t(slA)] > aabb[size_t(slB)] || node.x4_box[size_t(slB)] < aabb[size_t(slA)] ||\n        node.x4_box[size_t(slC)] > aabb[size_t(slD)] || node.x4_box[size_t(slD)] < aabb[size_t(slC)]) {\n      /* Not intersecting; remove from chain */\n      *id = node.x28_next;\n      node.x28_next = -1;\n      continue;\n    }\n    id = &node.x28_next;\n  }\n\n  return headId;\n}\n\nvoid CSortedListManager::BuildNearList(EntityList& out, const zeus::CVector3f& pos, const zeus::CVector3f& dir,\n                                       float mag, const CMaterialFilter& filter, const CActor* actor) {\n  if (mag == 0.f) {\n    mag = 8000.f;\n  }\n  const zeus::CVector3f ray = dir * mag;\n  const zeus::CVector3f sum = ray + pos;\n  const zeus::CVector3f maxs(std::max(pos.x(), sum.x()), std::max(pos.y(), sum.y()), std::max(pos.z(), sum.z()));\n  const zeus::CVector3f mins(std::min(sum.x(), pos.x()), std::min(sum.y(), pos.y()), std::min(sum.z(), pos.z()));\n  BuildNearList(out, zeus::CAABox(mins, maxs), filter, actor);\n}\n\nvoid CSortedListManager::BuildNearList(EntityList& out, const CActor& actor, const zeus::CAABox& aabb) {\n  const CMaterialFilter& filter = actor.GetMaterialFilter();\n  s16 id = ConstructIntersectionArray(aabb);\n  while (id != -1) {\n    SNode& node = AccessElement(x0_nodes, id);\n    if (&actor != node.x0_actor && filter.Passes(node.x0_actor->GetMaterialList()) &&\n        node.x0_actor->GetMaterialFilter().Passes(actor.GetMaterialList())) {\n      out.push_back(node.x0_actor->GetUniqueId());\n    }\n\n    id = node.x28_next;\n    node.x28_next = -1;\n  }\n}\n\nvoid CSortedListManager::BuildNearList(EntityList& out, const zeus::CAABox& aabb, const CMaterialFilter& filter,\n                                       const CActor* actor) {\n  s16 id = ConstructIntersectionArray(aabb);\n  while (id != -1) {\n    SNode& node = AccessElement(x0_nodes, id);\n    if (actor != node.x0_actor && filter.Passes(node.x0_actor->GetMaterialList())) {\n      out.push_back(node.x0_actor->GetUniqueId());\n    }\n\n    id = node.x28_next;\n    node.x28_next = -1;\n  }\n}\n\nvoid CSortedListManager::Remove(const CActor* actor) {\n  SNode& node = AccessElement(x0_nodes, actor->GetUniqueId().Value());\n  if (!node.x2a_populated) {\n    return;\n  }\n\n  RemoveFromList(ESortedList::MinX, node.x1c_selfIdxs[0]);\n  RemoveFromList(ESortedList::MaxX, node.x1c_selfIdxs[3]);\n  RemoveFromList(ESortedList::MinY, node.x1c_selfIdxs[1]);\n  RemoveFromList(ESortedList::MaxY, node.x1c_selfIdxs[4]);\n  RemoveFromList(ESortedList::MinZ, node.x1c_selfIdxs[2]);\n  RemoveFromList(ESortedList::MaxZ, node.x1c_selfIdxs[5]);\n  node.x2a_populated = false;\n}\n\nvoid CSortedListManager::Move(const CActor* actor, const zeus::CAABox& aabb) {\n  SNode& node = AccessElement(x0_nodes, actor->GetUniqueId().Value());\n  node.x4_box = aabb;\n\n  MoveInList(ESortedList::MinX, node.x1c_selfIdxs[0]);\n  MoveInList(ESortedList::MaxX, node.x1c_selfIdxs[3]);\n  MoveInList(ESortedList::MinY, node.x1c_selfIdxs[1]);\n  MoveInList(ESortedList::MaxY, node.x1c_selfIdxs[4]);\n  MoveInList(ESortedList::MinZ, node.x1c_selfIdxs[2]);\n  MoveInList(ESortedList::MaxZ, node.x1c_selfIdxs[5]);\n}\n\nvoid CSortedListManager::Insert(const CActor* actor, const zeus::CAABox& aabb) {\n  SNode& node = AccessElement(x0_nodes, actor->GetUniqueId().Value());\n  if (node.x2a_populated) {\n    Move(actor, aabb);\n    return;\n  }\n\n  SNode newNode(actor, aabb);\n  InsertInList(ESortedList::MinX, newNode);\n  InsertInList(ESortedList::MaxX, newNode);\n  InsertInList(ESortedList::MinY, newNode);\n  InsertInList(ESortedList::MaxY, newNode);\n  InsertInList(ESortedList::MinZ, newNode);\n  InsertInList(ESortedList::MaxZ, newNode);\n  node = newNode;\n}\n\nbool CSortedListManager::ActorInLists(const CActor* actor) const {\n  if (!actor) {\n    return false;\n  }\n  const SNode& node = AccessElement(x0_nodes, actor->GetUniqueId().Value());\n  return node.x2a_populated;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CSortedLists.hpp",
    "content": "#pragma once\n\n#include <array>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Collision/CMaterialFilter.hpp\"\n\n#include <zeus/CAABox.hpp>\n\nnamespace metaforce {\nenum class ESortedList { MinX, MinY, MinZ, MaxX, MaxY, MaxZ };\n\nstruct SSortedList {\n  std::array<s16, kMaxEntities> x0_ids;\n  u32 x800_size = 0;\n  void Reset() { x0_ids.fill(-1); }\n  SSortedList() { Reset(); }\n};\n\nclass CActor;\nclass CSortedListManager {\n  struct SNode {\n    const CActor* x0_actor = nullptr;\n    zeus::CAABox x4_box = zeus::skNullBox;\n    std::array<s16, 6> x1c_selfIdxs{-1, -1, -1, -1, -1, -1};\n    s16 x28_next = -1;\n    bool x2a_populated = false;\n    SNode() = default;\n    SNode(const CActor* act, const zeus::CAABox& aabb) : x0_actor(act), x4_box(aabb), x2a_populated(true) {}\n  };\n  std::array<SNode, kMaxEntities> x0_nodes;\n  std::array<SSortedList, 6> xb000_sortedLists;\n  void Reset();\n  void AddToLinkedList(s16 nodeId, s16& headId, s16& tailId);\n  void RemoveFromList(ESortedList list, s16 idx);\n  void MoveInList(ESortedList list, s16 idx);\n  void InsertInList(ESortedList list, SNode& node);\n  s16 FindInListUpper(ESortedList list, float value) const;\n  s16 FindInListLower(ESortedList list, float value) const;\n  s16 ConstructIntersectionArray(const zeus::CAABox& aabb);\n  s16 CalculateIntersections(ESortedList la, ESortedList lb, s16 a, s16 b, s16 c, s16 d, ESortedList slA,\n                             ESortedList slB, ESortedList slC, ESortedList slD, const zeus::CAABox& aabb);\n\npublic:\n  CSortedListManager();\n  void BuildNearList(EntityList& out, const zeus::CVector3f& pos, const zeus::CVector3f& dir, float mag,\n                     const CMaterialFilter& filter, const CActor* actor);\n  void BuildNearList(EntityList& out, const CActor& actor, const zeus::CAABox& aabb);\n  void BuildNearList(EntityList& out, const zeus::CAABox& aabb, const CMaterialFilter& filter, const CActor* actor);\n  void Remove(const CActor* actor);\n  void Move(const CActor* actor, const zeus::CAABox& aabb);\n  void Insert(const CActor* actor, const zeus::CAABox& aabb);\n  bool ActorInLists(const CActor* actor) const;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CStateManager.cpp",
    "content": "#include \"Runtime/CStateManager.hpp\"\n\n#include <cmath>\n\n#include \"Runtime/AutoMapper/CMapWorldInfo.hpp\"\n#include \"Runtime/Camera/CBallCamera.hpp\"\n#include \"Runtime/Camera/CCameraShakeData.hpp\"\n#include \"Runtime/Camera/CGameCamera.hpp\"\n#include \"Runtime/CGameState.hpp\"\n#include \"Runtime/CMemoryCardSys.hpp\"\n#include \"Runtime/Collision/CCollisionActor.hpp\"\n#include \"Runtime/Collision/CCollidableSphere.hpp\"\n#include \"Runtime/Collision/CGameCollision.hpp\"\n#include \"Runtime/Collision/CMaterialFilter.hpp\"\n#include \"Runtime/Collision/CollisionUtil.hpp\"\n#include \"Runtime/CPlayerState.hpp\"\n#include \"Runtime/CSortedLists.hpp\"\n#include \"Runtime/CTimeProvider.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Graphics/CLight.hpp\"\n#include \"Runtime/Input/ControlMapper.hpp\"\n#include \"Runtime/Input/CRumbleManager.hpp\"\n#include \"Runtime/MP1/CSamusHud.hpp\"\n#include \"Runtime/MP1/MP1.hpp\"\n#include \"Runtime/Particle/CDecalManager.hpp\"\n#include \"Runtime/Particle/CParticleElectric.hpp\"\n#include \"Runtime/Weapon/CProjectileWeapon.hpp\"\n#include \"Runtime/Weapon/CWeapon.hpp\"\n#include \"Runtime/Weapon/CWeaponMgr.hpp\"\n#include \"Runtime/World/CDestroyableRock.hpp\"\n#include \"Runtime/World/CGameLight.hpp\"\n#include \"Runtime/World/CPathFindSearch.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CProjectedShadow.hpp\"\n#include \"Runtime/World/CScriptDebris.hpp\"\n#include \"Runtime/World/CScriptDock.hpp\"\n#include \"Runtime/World/CScriptDoor.hpp\"\n#include \"Runtime/World/CScriptEffect.hpp\"\n#include \"Runtime/World/CScriptPlatform.hpp\"\n#include \"Runtime/World/CScriptPlayerActor.hpp\"\n#include \"Runtime/World/CScriptRoomAcoustics.hpp\"\n#include \"Runtime/World/CScriptSpawnPoint.hpp\"\n#include \"Runtime/World/CScriptSpecialFunction.hpp\"\n#include \"Runtime/World/CScriptWater.hpp\"\n#include \"Runtime/World/CSnakeWeedSwarm.hpp\"\n#include \"Runtime/World/CWallCrawlerSwarm.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n#include \"Runtime/ConsoleVariables/CVarManager.hpp\"\n#include \"Runtime/Logging.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n#include <zeus/CMRay.hpp>\n\nnamespace metaforce {\nnamespace {\nCVar* debugToolDrawAiPath = nullptr;\nCVar* debugToolDrawLighting = nullptr;\nCVar* debugToolDrawCollisionActors = nullptr;\nCVar* debugToolDrawMazePath = nullptr;\nCVar* debugToolDrawPlatformCollision = nullptr;\nCVar* sm_logScripting = nullptr;\n} // namespace\nCStateManager::CStateManager(const std::weak_ptr<CScriptMailbox>& mailbox, const std::weak_ptr<CMapWorldInfo>& mwInfo,\n                             const std::weak_ptr<CPlayerState>& playerState,\n                             const std::weak_ptr<CWorldTransManager>& wtMgr,\n                             const std::weak_ptr<CScriptLayerManager>& layerState)\n: x8b8_playerState(playerState)\n, x8bc_mailbox(mailbox)\n, x8c0_mapWorldInfo(mwInfo)\n, x8c4_worldTransManager(wtMgr)\n, x8c8_worldLayerState(layerState) {\n  x86c_stateManagerContainer = std::make_unique<CStateManagerContainer>();\n  x870_cameraManager = &x86c_stateManagerContainer->x0_cameraManager;\n  x874_sortedListManager = &x86c_stateManagerContainer->x3c0_sortedListManager;\n  x878_weaponManager = &x86c_stateManagerContainer->xe3d8_weaponManager;\n  x87c_fluidPlaneManager = &x86c_stateManagerContainer->xe3ec_fluidPlaneManager;\n  x880_envFxManager = &x86c_stateManagerContainer->xe510_envFxManager;\n  x884_actorModelParticles = &x86c_stateManagerContainer->xf168_actorModelParticles;\n  x88c_rumbleManager = &x86c_stateManagerContainer->xf250_rumbleManager;\n\n  g_Renderer->SetDrawableCallback(&CStateManager::RendererDrawCallback, this);\n  x90c_loaderFuncs.resize(int(EScriptObjectType::ScriptObjectTypeMAX));\n  x90c_loaderFuncs[size_t(EScriptObjectType::Actor)] = ScriptLoader::LoadActor;\n  x90c_loaderFuncs[size_t(EScriptObjectType::Waypoint)] = ScriptLoader::LoadWaypoint;\n  x90c_loaderFuncs[size_t(EScriptObjectType::Door)] = ScriptLoader::LoadDoor;\n  x90c_loaderFuncs[size_t(EScriptObjectType::Trigger)] = ScriptLoader::LoadTrigger;\n  x90c_loaderFuncs[size_t(EScriptObjectType::Timer)] = ScriptLoader::LoadTimer;\n  x90c_loaderFuncs[size_t(EScriptObjectType::Counter)] = ScriptLoader::LoadCounter;\n  x90c_loaderFuncs[size_t(EScriptObjectType::Effect)] = ScriptLoader::LoadEffect;\n  x90c_loaderFuncs[size_t(EScriptObjectType::Platform)] = ScriptLoader::LoadPlatform;\n  x90c_loaderFuncs[size_t(EScriptObjectType::Sound)] = ScriptLoader::LoadSound;\n  x90c_loaderFuncs[size_t(EScriptObjectType::Generator)] = ScriptLoader::LoadGenerator;\n  x90c_loaderFuncs[size_t(EScriptObjectType::Dock)] = ScriptLoader::LoadDock;\n  x90c_loaderFuncs[size_t(EScriptObjectType::Camera)] = ScriptLoader::LoadCamera;\n  x90c_loaderFuncs[size_t(EScriptObjectType::CameraWaypoint)] = ScriptLoader::LoadCameraWaypoint;\n  x90c_loaderFuncs[size_t(EScriptObjectType::NewIntroBoss)] = ScriptLoader::LoadNewIntroBoss;\n  x90c_loaderFuncs[size_t(EScriptObjectType::SpawnPoint)] = ScriptLoader::LoadSpawnPoint;\n  x90c_loaderFuncs[size_t(EScriptObjectType::CameraHint)] = ScriptLoader::LoadCameraHint;\n  x90c_loaderFuncs[size_t(EScriptObjectType::Pickup)] = ScriptLoader::LoadPickup;\n  x90c_loaderFuncs[size_t(EScriptObjectType::MemoryRelay)] = ScriptLoader::LoadMemoryRelay;\n  x90c_loaderFuncs[size_t(EScriptObjectType::RandomRelay)] = ScriptLoader::LoadRandomRelay;\n  x90c_loaderFuncs[size_t(EScriptObjectType::Relay)] = ScriptLoader::LoadRelay;\n  x90c_loaderFuncs[size_t(EScriptObjectType::Beetle)] = ScriptLoader::LoadBeetle;\n  x90c_loaderFuncs[size_t(EScriptObjectType::HUDMemo)] = ScriptLoader::LoadHUDMemo;\n  x90c_loaderFuncs[size_t(EScriptObjectType::CameraFilterKeyframe)] = ScriptLoader::LoadCameraFilterKeyframe;\n  x90c_loaderFuncs[size_t(EScriptObjectType::CameraBlurKeyframe)] = ScriptLoader::LoadCameraBlurKeyframe;\n  x90c_loaderFuncs[size_t(EScriptObjectType::DamageableTrigger)] = ScriptLoader::LoadDamageableTrigger;\n  x90c_loaderFuncs[size_t(EScriptObjectType::Debris)] = ScriptLoader::LoadDebris;\n  x90c_loaderFuncs[size_t(EScriptObjectType::CameraShaker)] = ScriptLoader::LoadCameraShaker;\n  x90c_loaderFuncs[size_t(EScriptObjectType::ActorKeyframe)] = ScriptLoader::LoadActorKeyframe;\n  x90c_loaderFuncs[size_t(EScriptObjectType::Water)] = ScriptLoader::LoadWater;\n  x90c_loaderFuncs[size_t(EScriptObjectType::Warwasp)] = ScriptLoader::LoadWarWasp;\n  x90c_loaderFuncs[size_t(EScriptObjectType::SpacePirate)] = ScriptLoader::LoadSpacePirate;\n  x90c_loaderFuncs[size_t(EScriptObjectType::FlyingPirate)] = ScriptLoader::LoadFlyingPirate;\n  x90c_loaderFuncs[size_t(EScriptObjectType::ElitePirate)] = ScriptLoader::LoadElitePirate;\n  x90c_loaderFuncs[size_t(EScriptObjectType::MetroidBeta)] = ScriptLoader::LoadMetroidBeta;\n  x90c_loaderFuncs[size_t(EScriptObjectType::ChozoGhost)] = ScriptLoader::LoadChozoGhost;\n  x90c_loaderFuncs[size_t(EScriptObjectType::CoverPoint)] = ScriptLoader::LoadCoverPoint;\n  x90c_loaderFuncs[size_t(EScriptObjectType::SpiderBallWaypoint)] = ScriptLoader::LoadSpiderBallWaypoint;\n  x90c_loaderFuncs[size_t(EScriptObjectType::BloodFlower)] = ScriptLoader::LoadBloodFlower;\n  x90c_loaderFuncs[size_t(EScriptObjectType::FlickerBat)] = ScriptLoader::LoadFlickerBat;\n  x90c_loaderFuncs[size_t(EScriptObjectType::PathCamera)] = ScriptLoader::LoadPathCamera;\n  x90c_loaderFuncs[size_t(EScriptObjectType::GrapplePoint)] = ScriptLoader::LoadGrapplePoint;\n  x90c_loaderFuncs[size_t(EScriptObjectType::PuddleSpore)] = ScriptLoader::LoadPuddleSpore;\n  x90c_loaderFuncs[size_t(EScriptObjectType::DebugCameraWaypoint)] = ScriptLoader::LoadDebugCameraWaypoint;\n  x90c_loaderFuncs[size_t(EScriptObjectType::SpiderBallAttractionSurface)] =\n      ScriptLoader::LoadSpiderBallAttractionSurface;\n  x90c_loaderFuncs[size_t(EScriptObjectType::PuddleToadGamma)] = ScriptLoader::LoadPuddleToadGamma;\n  x90c_loaderFuncs[size_t(EScriptObjectType::DistanceFog)] = ScriptLoader::LoadDistanceFog;\n  x90c_loaderFuncs[size_t(EScriptObjectType::FireFlea)] = ScriptLoader::LoadFireFlea;\n  x90c_loaderFuncs[size_t(EScriptObjectType::Metaree)] = ScriptLoader::LoadMetaree;\n  x90c_loaderFuncs[size_t(EScriptObjectType::DockAreaChange)] = ScriptLoader::LoadDockAreaChange;\n  x90c_loaderFuncs[size_t(EScriptObjectType::ActorRotate)] = ScriptLoader::LoadActorRotate;\n  x90c_loaderFuncs[size_t(EScriptObjectType::SpecialFunction)] = ScriptLoader::LoadSpecialFunction;\n  x90c_loaderFuncs[size_t(EScriptObjectType::SpankWeed)] = ScriptLoader::LoadSpankWeed;\n  x90c_loaderFuncs[size_t(EScriptObjectType::Parasite)] = ScriptLoader::LoadParasite;\n  x90c_loaderFuncs[size_t(EScriptObjectType::PlayerHint)] = ScriptLoader::LoadPlayerHint;\n  x90c_loaderFuncs[size_t(EScriptObjectType::Ripper)] = ScriptLoader::LoadRipper;\n  x90c_loaderFuncs[size_t(EScriptObjectType::PickupGenerator)] = ScriptLoader::LoadPickupGenerator;\n  x90c_loaderFuncs[size_t(EScriptObjectType::AIKeyframe)] = ScriptLoader::LoadAIKeyframe;\n  x90c_loaderFuncs[size_t(EScriptObjectType::PointOfInterest)] = ScriptLoader::LoadPointOfInterest;\n  x90c_loaderFuncs[size_t(EScriptObjectType::Drone)] = ScriptLoader::LoadDrone;\n  x90c_loaderFuncs[size_t(EScriptObjectType::Metroid)] = ScriptLoader::LoadMetroid;\n  x90c_loaderFuncs[size_t(EScriptObjectType::DebrisExtended)] = ScriptLoader::LoadDebrisExtended;\n  x90c_loaderFuncs[size_t(EScriptObjectType::Steam)] = ScriptLoader::LoadSteam;\n  x90c_loaderFuncs[size_t(EScriptObjectType::Ripple)] = ScriptLoader::LoadRipple;\n  x90c_loaderFuncs[size_t(EScriptObjectType::BallTrigger)] = ScriptLoader::LoadBallTrigger;\n  x90c_loaderFuncs[size_t(EScriptObjectType::TargetingPoint)] = ScriptLoader::LoadTargetingPoint;\n  x90c_loaderFuncs[size_t(EScriptObjectType::EMPulse)] = ScriptLoader::LoadEMPulse;\n  x90c_loaderFuncs[size_t(EScriptObjectType::IceSheegoth)] = ScriptLoader::LoadIceSheegoth;\n  x90c_loaderFuncs[size_t(EScriptObjectType::PlayerActor)] = ScriptLoader::LoadPlayerActor;\n  x90c_loaderFuncs[size_t(EScriptObjectType::Flaahgra)] = ScriptLoader::LoadFlaahgra;\n  x90c_loaderFuncs[size_t(EScriptObjectType::AreaAttributes)] = ScriptLoader::LoadAreaAttributes;\n  x90c_loaderFuncs[size_t(EScriptObjectType::FishCloud)] = ScriptLoader::LoadFishCloud;\n  x90c_loaderFuncs[size_t(EScriptObjectType::FishCloudModifier)] = ScriptLoader::LoadFishCloudModifier;\n  x90c_loaderFuncs[size_t(EScriptObjectType::VisorFlare)] = ScriptLoader::LoadVisorFlare;\n  x90c_loaderFuncs[size_t(EScriptObjectType::WorldTeleporter)] = ScriptLoader::LoadWorldTeleporter;\n  x90c_loaderFuncs[size_t(EScriptObjectType::VisorGoo)] = ScriptLoader::LoadVisorGoo;\n  x90c_loaderFuncs[size_t(EScriptObjectType::JellyZap)] = ScriptLoader::LoadJellyZap;\n  x90c_loaderFuncs[size_t(EScriptObjectType::ControllerAction)] = ScriptLoader::LoadControllerAction;\n  x90c_loaderFuncs[size_t(EScriptObjectType::Switch)] = ScriptLoader::LoadSwitch;\n  x90c_loaderFuncs[size_t(EScriptObjectType::PlayerStateChange)] = ScriptLoader::LoadPlayerStateChange;\n  x90c_loaderFuncs[size_t(EScriptObjectType::Thardus)] = ScriptLoader::LoadThardus;\n  x90c_loaderFuncs[size_t(EScriptObjectType::WallCrawlerSwarm)] = ScriptLoader::LoadWallCrawlerSwarm;\n  x90c_loaderFuncs[size_t(EScriptObjectType::AIJumpPoint)] = ScriptLoader::LoadAiJumpPoint;\n  x90c_loaderFuncs[size_t(EScriptObjectType::FlaahgraTentacle)] = ScriptLoader::LoadFlaahgraTentacle;\n  x90c_loaderFuncs[size_t(EScriptObjectType::RoomAcoustics)] = ScriptLoader::LoadRoomAcoustics;\n  x90c_loaderFuncs[size_t(EScriptObjectType::ColorModulate)] = ScriptLoader::LoadColorModulate;\n  x90c_loaderFuncs[size_t(EScriptObjectType::ThardusRockProjectile)] = ScriptLoader::LoadThardusRockProjectile;\n  x90c_loaderFuncs[size_t(EScriptObjectType::Midi)] = ScriptLoader::LoadMidi;\n  x90c_loaderFuncs[size_t(EScriptObjectType::StreamedAudio)] = ScriptLoader::LoadStreamedAudio;\n  x90c_loaderFuncs[size_t(EScriptObjectType::WorldTeleporterToo)] = ScriptLoader::LoadWorldTeleporter;\n  x90c_loaderFuncs[size_t(EScriptObjectType::Repulsor)] = ScriptLoader::LoadRepulsor;\n  x90c_loaderFuncs[size_t(EScriptObjectType::GunTurret)] = ScriptLoader::LoadGunTurret;\n  x90c_loaderFuncs[size_t(EScriptObjectType::FogVolume)] = ScriptLoader::LoadFogVolume;\n  x90c_loaderFuncs[size_t(EScriptObjectType::Babygoth)] = ScriptLoader::LoadBabygoth;\n  x90c_loaderFuncs[size_t(EScriptObjectType::Eyeball)] = ScriptLoader::LoadEyeball;\n  x90c_loaderFuncs[size_t(EScriptObjectType::RadialDamage)] = ScriptLoader::LoadRadialDamage;\n  x90c_loaderFuncs[size_t(EScriptObjectType::CameraPitchVolume)] = ScriptLoader::LoadCameraPitchVolume;\n  x90c_loaderFuncs[size_t(EScriptObjectType::EnvFxDensityController)] = ScriptLoader::LoadEnvFxDensityController;\n  x90c_loaderFuncs[size_t(EScriptObjectType::Magdolite)] = ScriptLoader::LoadMagdolite;\n  x90c_loaderFuncs[size_t(EScriptObjectType::TeamAIMgr)] = ScriptLoader::LoadTeamAIMgr;\n  x90c_loaderFuncs[size_t(EScriptObjectType::SnakeWeedSwarm)] = ScriptLoader::LoadSnakeWeedSwarm;\n  x90c_loaderFuncs[size_t(EScriptObjectType::ActorContraption)] = ScriptLoader::LoadActorContraption;\n  x90c_loaderFuncs[size_t(EScriptObjectType::Oculus)] = ScriptLoader::LoadOculus;\n  x90c_loaderFuncs[size_t(EScriptObjectType::Geemer)] = ScriptLoader::LoadGeemer;\n  x90c_loaderFuncs[size_t(EScriptObjectType::SpindleCamera)] = ScriptLoader::LoadSpindleCamera;\n  x90c_loaderFuncs[size_t(EScriptObjectType::AtomicAlpha)] = ScriptLoader::LoadAtomicAlpha;\n  x90c_loaderFuncs[size_t(EScriptObjectType::CameraHintTrigger)] = ScriptLoader::LoadCameraHintTrigger;\n  x90c_loaderFuncs[size_t(EScriptObjectType::RumbleEffect)] = ScriptLoader::LoadRumbleEffect;\n  x90c_loaderFuncs[size_t(EScriptObjectType::AmbientAI)] = ScriptLoader::LoadAmbientAI;\n  x90c_loaderFuncs[size_t(EScriptObjectType::AtomicBeta)] = ScriptLoader::LoadAtomicBeta;\n  x90c_loaderFuncs[size_t(EScriptObjectType::IceZoomer)] = ScriptLoader::LoadIceZoomer;\n  x90c_loaderFuncs[size_t(EScriptObjectType::Puffer)] = ScriptLoader::LoadPuffer;\n  x90c_loaderFuncs[size_t(EScriptObjectType::Tryclops)] = ScriptLoader::LoadTryclops;\n  x90c_loaderFuncs[size_t(EScriptObjectType::Ridley)] = ScriptLoader::LoadRidley;\n  x90c_loaderFuncs[size_t(EScriptObjectType::Seedling)] = ScriptLoader::LoadSeedling;\n  x90c_loaderFuncs[size_t(EScriptObjectType::ThermalHeatFader)] = ScriptLoader::LoadThermalHeatFader;\n  x90c_loaderFuncs[size_t(EScriptObjectType::Burrower)] = ScriptLoader::LoadBurrower;\n  x90c_loaderFuncs[size_t(EScriptObjectType::ScriptBeam)] = ScriptLoader::LoadBeam;\n  x90c_loaderFuncs[size_t(EScriptObjectType::WorldLightFader)] = ScriptLoader::LoadWorldLightFader;\n  x90c_loaderFuncs[size_t(EScriptObjectType::MetroidPrimeStage2)] = ScriptLoader::LoadMetroidPrimeEssence;\n  x90c_loaderFuncs[size_t(EScriptObjectType::MetroidPrimeStage1)] = ScriptLoader::LoadMetroidPrimeStage1;\n  x90c_loaderFuncs[size_t(EScriptObjectType::MazeNode)] = ScriptLoader::LoadMazeNode;\n  x90c_loaderFuncs[size_t(EScriptObjectType::OmegaPirate)] = ScriptLoader::LoadOmegaPirate;\n  x90c_loaderFuncs[size_t(EScriptObjectType::PhazonPool)] = ScriptLoader::LoadPhazonPool;\n  x90c_loaderFuncs[size_t(EScriptObjectType::PhazonHealingNodule)] = ScriptLoader::LoadPhazonHealingNodule;\n  x90c_loaderFuncs[size_t(EScriptObjectType::NewCameraShaker)] = ScriptLoader::LoadNewCameraShaker;\n  x90c_loaderFuncs[size_t(EScriptObjectType::ShadowProjector)] = ScriptLoader::LoadShadowProjector;\n  x90c_loaderFuncs[size_t(EScriptObjectType::EnergyBall)] = ScriptLoader::LoadEnergyBall;\n\n  CGameCollision::InitCollision();\n  ControlMapper::ResetCommandFilters();\n  x8f0_shadowTex = g_SimplePool->GetObj(\"DefaultShadow\");\n  g_StateManager = this;\n\n  if (sm_logScripting == nullptr) {\n    sm_logScripting = CVarManager::instance()->findOrMakeCVar(\n        \"stateManager.logScripting\"sv, \"Prints object communication to the console\", false,\n        CVar::EFlags::ReadOnly | CVar::EFlags::Archive | CVar::EFlags::Game);\n  }\n  m_logScriptingReference.emplace(&m_logScripting, sm_logScripting);\n}\n\nCStateManager::~CStateManager() {\n  x88c_rumbleManager->HardStopAll();\n  x880_envFxManager->Cleanup();\n  x900_activeRandom = &x8fc_random;\n  ClearGraveyard();\n  for (auto it = x808_objLists[0]->begin(); it != x808_objLists[0]->end();) {\n    CEntity* ent = *it;\n    ++it;\n    if (ent == x84c_player.get()) {\n      continue;\n    }\n    ent->AcceptScriptMsg(EScriptObjectMessage::Deleted, kInvalidUniqueId, *this);\n    RemoveObject(ent->GetUniqueId());\n    std::default_delete<CEntity>()(ent);\n  }\n  ClearGraveyard();\n  x84c_player->AcceptScriptMsg(EScriptObjectMessage::Deleted, kInvalidUniqueId, *this);\n  RemoveObject(x84c_player->GetUniqueId());\n  x84c_player.reset();\n  CCollisionPrimitive::Uninitialize();\n  g_StateManager = nullptr;\n}\n\nvoid CStateManager::UpdateThermalVisor() {\n  xf28_thermColdScale2 = 0.f;\n  xf24_thermColdScale1 = 0.f;\n\n  const auto visor = x8b8_playerState->GetActiveVisor(*this);\n  if (visor != CPlayerState::EPlayerVisor::Thermal || x8cc_nextAreaId == kInvalidAreaId) {\n    return;\n  }\n\n  CGameArea* area = x850_world->GetArea(x8cc_nextAreaId);\n  const zeus::CTransform& playerXf = x84c_player->GetTransform();\n  const zeus::CVector3f playerXYPos(playerXf.origin.x(), playerXf.origin.y(), 0.f);\n  CGameArea* lastArea = nullptr;\n  float closestDist = FLT_MAX;\n  for (const CGameArea::Dock& dock : area->GetDocks()) {\n    zeus::CVector3f dockCenter = (dock.GetPlaneVertices()[0] + dock.GetPlaneVertices()[1] + dock.GetPlaneVertices()[2] +\n                                  dock.GetPlaneVertices()[3]) *\n                                 0.25f;\n    dockCenter.z() = 0.f;\n    const float dist = (playerXYPos - dockCenter).magSquared();\n    if (dist < closestDist) {\n      const TAreaId connAreaId = dock.GetConnectedAreaId(0);\n      if (connAreaId != kInvalidAreaId) {\n        CGameArea* connArea = x850_world->GetArea(x8cc_nextAreaId);\n        if (connArea->IsPostConstructed()) {\n          const auto occState = connArea->GetPostConstructed()->x10dc_occlusionState;\n          if (occState == CGameArea::EOcclusionState::Visible) {\n            closestDist = dist;\n            lastArea = connArea;\n          }\n        }\n      }\n    }\n  }\n\n  if (lastArea != nullptr) {\n    if (closestDist != 0.f) {\n      closestDist /= std::sqrt(closestDist);\n    }\n    closestDist -= 2.f;\n    if (closestDist < 8.f) {\n      if (closestDist > 0.f) {\n        closestDist = (closestDist / 8.f) * 0.5f + 0.5f;\n      } else {\n        closestDist = 0.5f;\n      }\n\n      xf24_thermColdScale1 = (1.f - closestDist) * lastArea->GetPostConstructed()->x111c_thermalCurrent +\n                             closestDist * area->GetPostConstructed()->x111c_thermalCurrent;\n      return;\n    }\n  }\n\n  xf24_thermColdScale1 = area->GetPostConstructed()->x111c_thermalCurrent;\n}\n\nvoid CStateManager::RendererDrawCallback(void* drawable, void* ctx, int type) {\n  CStateManager& mgr = *static_cast<CStateManager*>(ctx);\n  switch (type) {\n  case 0: {\n    CActor& actor = *static_cast<CActor*>(drawable);\n    if (actor.xc8_drawnToken == mgr.x8dc_objectDrawToken) {\n      break;\n    }\n    if (actor.xc6_nextDrawNode != kInvalidUniqueId) {\n      mgr.RecursiveDrawTree(actor.xc6_nextDrawNode);\n    }\n    actor.Render(mgr);\n    actor.xc8_drawnToken = mgr.x8dc_objectDrawToken;\n    break;\n  }\n  case 1:\n    static_cast<CSimpleShadow*>(drawable)->Render(mgr.x8f0_shadowTex);\n    break;\n  case 2:\n    static_cast<CDecal*>(drawable)->Render();\n    break;\n  default:\n    break;\n  }\n}\n\nbool CStateManager::RenderLast(TUniqueId uid) {\n  if (x86c_stateManagerContainer->xf39c_renderLast.size() == 20) {\n    return false;\n  }\n  x86c_stateManagerContainer->xf39c_renderLast.push_back(uid);\n  return true;\n}\n\nvoid CStateManager::AddDrawableActorPlane(CActor& actor, const zeus::CPlane& plane, const zeus::CAABox& aabb) const {\n  actor.SetAddedToken(x8dc_objectDrawToken + 1);\n  g_Renderer->AddPlaneObject(&actor, aabb, plane, 0);\n}\n\nvoid CStateManager::AddDrawableActor(CActor& actor, const zeus::CVector3f& vec, const zeus::CAABox& aabb) const {\n  actor.SetAddedToken(x8dc_objectDrawToken + 1);\n  g_Renderer->AddDrawable(&actor, vec, aabb, 0, IRenderer::EDrawableSorting::SortedCallback);\n}\n\nbool CStateManager::SpecialSkipCinematic() {\n  if (xf38_skipCineSpecialFunc == kInvalidUniqueId) {\n    return false;\n  }\n\n  auto* ent = static_cast<CScriptSpecialFunction*>(ObjectById(xf38_skipCineSpecialFunc));\n  if (ent == nullptr || !ent->ShouldSkipCinematic(*this)) {\n    return false;\n  }\n\n  const bool hadRandom = x900_activeRandom != nullptr;\n  SetActiveRandomToDefault();\n  x870_cameraManager->SkipCinematic(*this);\n  ent->SkipCinematic(*this);\n  x900_activeRandom = hadRandom ? &x8fc_random : nullptr;\n\n  return true;\n}\n\nTAreaId CStateManager::GetVisAreaId() const {\n  const CGameCamera* cam = static_cast<const CGameCamera*>(x870_cameraManager->GetCurrentCamera(*this));\n  const CBallCamera* ballCam = x870_cameraManager->GetBallCamera();\n  const TAreaId curArea = x850_world->x68_curAreaId;\n  if (cam != ballCam) {\n    return curArea;\n  }\n\n  const zeus::CVector3f& camTranslation = ballCam->GetTranslation();\n  zeus::CAABox camAABB(camTranslation, camTranslation);\n  camAABB.accumulateBounds(x84c_player->GetTranslation());\n  EntityList nearList;\n  BuildNearList(nearList, camAABB,\n                CMaterialFilter(EMaterialTypes::AIBlock, CMaterialList(), CMaterialFilter::EFilterType::Include),\n                nullptr);\n  for (const auto& id : nearList) {\n    if (const TCastToConstPtr<CScriptDock> dock = GetObjectById(id)) {\n      if (dock->GetAreaId() == curArea && dock->HasPointCrossedDock(*this, camTranslation)) {\n        return dock->GetCurrentConnectedAreaId(*this);\n      }\n    }\n  }\n\n  return curArea;\n}\n\ns32 CStateManager::GetWeaponIdCount(TUniqueId uid, EWeaponType type) const {\n  return x878_weaponManager->GetNumActive(uid, type);\n}\n\nvoid CStateManager::RemoveWeaponId(TUniqueId uid, EWeaponType type) { x878_weaponManager->DecrCount(uid, type); }\n\nvoid CStateManager::AddWeaponId(TUniqueId uid, EWeaponType type) { x878_weaponManager->IncrCount(uid, type); }\n\nvoid CStateManager::UpdateEscapeSequenceTimer(float dt) {\n  if (xf0c_escapeTimer <= 0.f) {\n    return;\n  }\n\n  xf0c_escapeTimer = std::max(FLT_EPSILON, xf0c_escapeTimer - dt);\n  if (xf0c_escapeTimer <= FLT_EPSILON) {\n    x8b8_playerState->SetPlayerAlive(false);\n  }\n\n  if (!g_EscapeShakeCountdownInit) {\n    g_EscapeShakeCountdown = 0.f;\n    g_EscapeShakeCountdownInit = true;\n  }\n\n  g_EscapeShakeCountdown -= dt;\n  if (g_EscapeShakeCountdown >= 0.f) {\n    return;\n  }\n\n  const float factor = 1.f - xf0c_escapeTimer / xf10_escapeTotalTime;\n  const float factor2 = factor * factor;\n  const CCameraShakeData shakeData(1.f, factor2 * 0.2f * x900_activeRandom->Range(0.5f, 1.f));\n  x870_cameraManager->AddCameraShaker(shakeData, true);\n  x88c_rumbleManager->Rumble(*this, ERumbleFxId::EscapeSequenceShake, 0.75f, ERumblePriority::One);\n  g_EscapeShakeCountdown = -12.f * factor2 + 15.f;\n}\n\nvoid CStateManager::ResetEscapeSequenceTimer(float time) {\n  xf0c_escapeTimer = time;\n  xf10_escapeTotalTime = time;\n}\n\nvoid CStateManager::SetupParticleHook(const CActor& actor) const {\n  x884_actorModelParticles->SetupHook(actor.GetUniqueId());\n}\n\nvoid CStateManager::MurderScriptInstanceNames() { xb40_uniqueInstanceNames.clear(); }\n\nstd::string CStateManager::HashInstanceName(CInputStream& in) { return in.Get<std::string>(); }\n\nvoid CStateManager::SetActorAreaId(CActor& actor, TAreaId aid) {\n  const TAreaId actorAid = actor.GetAreaIdAlways();\n  if (actorAid == aid) {\n    return;\n  }\n\n  if (actorAid != kInvalidAreaId) {\n    CGameArea* area = x850_world->GetArea(actorAid);\n    if (area->IsPostConstructed()) {\n      area->GetAreaObjects()->RemoveObject(actor.GetUniqueId());\n    }\n  }\n\n  actor.x4_areaId = aid;\n\n  if (aid == kInvalidAreaId) {\n    return;\n  }\n\n  CGameArea* area = x850_world->GetArea(aid);\n  if (!area->IsPostConstructed() || area->GetAreaObjects()->GetValidObjectById(actor.GetUniqueId())) {\n    return;\n  }\n\n  area->GetAreaObjects()->AddObject(actor);\n}\n\nvoid CStateManager::TouchSky() const { x850_world->TouchSky(); }\n\nvoid CStateManager::TouchPlayerActor() {\n  if (xf6c_playerActorHead == kInvalidUniqueId) {\n    return;\n  }\n\n  if (CEntity* ent = ObjectById(xf6c_playerActorHead)) {\n    static_cast<CScriptPlayerActor*>(ent)->TouchModels(*this);\n  }\n}\n\nvoid CStateManager::DrawSpaceWarp(const zeus::CVector3f& v, float strength) const {\n  const CPlayerState::EPlayerVisor visor = x8b8_playerState->GetActiveVisor(*this);\n\n  if (visor != CPlayerState::EPlayerVisor::Scan && visor != CPlayerState::EPlayerVisor::Combat) {\n    return;\n  }\n\n  const zeus::CVector3f screenV =\n      TCastToConstPtr<CGameCamera>(x870_cameraManager->GetCurrentCamera(*this))->ConvertToScreenSpace(v);\n  g_Renderer->DrawSpaceWarp(screenV, strength);\n}\n\nvoid CStateManager::DrawReflection(const zeus::CVector3f& reflectPoint) {\n  const zeus::CAABox aabb = x84c_player->GetBoundingBox();\n  const zeus::CVector3f playerPos = aabb.center();\n  zeus::CVector3f surfToPlayer = playerPos - reflectPoint;\n  surfToPlayer.z() = 0.f;\n  const zeus::CVector3f viewPos = playerPos - surfToPlayer.normalized() * 3.5f;\n  const zeus::CTransform look = zeus::lookAt(viewPos, playerPos, {0.f, 0.f, -1.f});\n\n  const zeus::CTransform backupView = CGraphics::mViewMatrix;\n  CGraphics::SetViewPointMatrix(look);\n  const CGraphics::CProjectionState backupProj = CGraphics::GetProjectionState();\n  const CGameCamera* cam = x870_cameraManager->GetCurrentCamera(*this);\n  g_Renderer->SetPerspective(cam->GetFov(), CGraphics::GetViewportWidth(), CGraphics::GetViewportHeight(),\n                             cam->GetNearClipDistance(), cam->GetFarClipDistance());\n\n  x84c_player->RenderReflectedPlayer(*this);\n\n  CGraphics::SetViewPointMatrix(backupView);\n  CGraphics::SetProjectionState(backupProj);\n}\n\nvoid CStateManager::ReflectionDrawer(void* ctx, const zeus::CVector3f& vec) {\n  static_cast<CStateManager*>(ctx)->DrawReflection(vec);\n}\n\nvoid CStateManager::CacheReflection() { g_Renderer->CacheReflection(ReflectionDrawer, this, true); }\n\nbool CStateManager::CanCreateProjectile(TUniqueId uid, EWeaponType type, int maxAllowed) const {\n  return x878_weaponManager->GetNumActive(uid, type) < maxAllowed;\n}\n\nvoid CStateManager::BuildDynamicLightListForWorld() {\n  if (x8b8_playerState->GetActiveVisor(*this) == CPlayerState::EPlayerVisor::Thermal) {\n    x8e0_dynamicLights.clear();\n    return;\n  }\n\n  if (GetLightObjectList().size() == 0) {\n    return;\n  }\n\n  x8e0_dynamicLights.clear();\n  x8e0_dynamicLights.reserve(GetLightObjectList().size());\n\n  for (const CEntity* ent : GetLightObjectList()) {\n    const auto& light = static_cast<const CGameLight&>(*ent);\n    if (light.GetActive()) {\n      const CLight l = light.GetLight();\n      if (l.GetIntensity() > FLT_EPSILON && l.GetRadius() > FLT_EPSILON) {\n        x8e0_dynamicLights.push_back(l);\n      }\n    }\n  }\n\n  std::sort(x8e0_dynamicLights.begin(), x8e0_dynamicLights.end(), [](const CLight& a, const CLight& b) {\n    if (b.GetPriority() > a.GetPriority()) {\n      return true;\n    } else if (b.GetPriority() == a.GetPriority()) {\n      return a.GetIntensity() > b.GetIntensity();\n    } else {\n      return false;\n    }\n  });\n}\n\nvoid CStateManager::DrawDebugStuff() const {\n  if (com_developer != nullptr && !com_developer->toBoolean()) {\n    return;\n  }\n\n  // FIXME: Add proper globals for CVars\n  if (debugToolDrawAiPath == nullptr || debugToolDrawCollisionActors == nullptr || debugToolDrawLighting == nullptr ||\n      debugToolDrawMazePath == nullptr || debugToolDrawPlatformCollision == nullptr) {\n    debugToolDrawAiPath = CVarManager::instance()->findCVar(\"debugTool.drawAiPath\");\n    debugToolDrawMazePath = CVarManager::instance()->findCVar(\"debugTool.drawMazePath\");\n    debugToolDrawCollisionActors = CVarManager::instance()->findCVar(\"debugTool.drawCollisionActors\");\n    debugToolDrawLighting = CVarManager::instance()->findCVar(\"debugTool.drawLighting\");\n    debugToolDrawPlatformCollision = CVarManager::instance()->findCVar(\"debugTool.drawPlatformCollision\");\n    return;\n  }\n\n  CGraphics::SetModelMatrix(zeus::CTransform());\n  for (CEntity* ent : GetActorObjectList()) {\n    if (const TCastToPtr<CPatterned> ai = ent) {\n      if (CPathFindSearch* path = ai->GetSearchPath()) {\n        if (debugToolDrawAiPath->toBoolean()) {\n          path->DebugDraw();\n        }\n      }\n    } else if (const TCastToPtr<CGameLight> light = ent) {\n      if (debugToolDrawLighting->toBoolean()) {\n        light->DebugDraw();\n      }\n    } else if (const TCastToPtr<CCollisionActor> colAct = ent) {\n      if (colAct->GetUniqueId() == x870_cameraManager->GetBallCamera()->GetCollisionActorId()) {\n        continue;\n      }\n      if (debugToolDrawCollisionActors->toBoolean()) {\n        colAct->DebugDraw();\n      }\n    } else if (const TCastToPtr<CScriptPlatform> plat = ent) {\n      if (debugToolDrawPlatformCollision->toBoolean() && plat->GetActive()) {\n        plat->DebugDraw();\n      }\n    } else if (const TCastToPtr<CScriptTrigger> tr = ent) {\n      tr->DebugDraw();\n    }\n  }\n\n  auto* gameArea = x850_world->GetArea(x850_world->GetCurrentAreaId());\n  if (gameArea != nullptr && debugToolDrawLighting->toBoolean()) {\n    gameArea->DebugDraw();\n  }\n\n  if (xf70_currentMaze && debugToolDrawMazePath->toBoolean()) {\n    xf70_currentMaze->DebugRender();\n  }\n}\n\nvoid CStateManager::RenderCamerasAndAreaLights() {\n  x870_cameraManager->RenderCameras(*this);\n  for (auto& filter : xb84_camFilterPasses) {\n    filter.Draw();\n  }\n}\n\nvoid CStateManager::DrawE3DeathEffect() {\n  const CPlayer& player = *x84c_player;\n  if (player.x9f4_deathTime <= 0.f) {\n    return;\n  }\n\n  if (player.x2f8_morphBallState != CPlayer::EPlayerMorphBallState::Unmorphed) {\n    const float blurAmt = zeus::clamp(0.f, (player.x9f4_deathTime - 1.f) / (6.f - 1.f), 1.f);\n    if (blurAmt > 0.f) {\n      CCameraBlurPass blur;\n      blur.SetBlur(EBlurType::HiBlur, 7.f * blurAmt, 0.f, false);\n      blur.Draw();\n    }\n  }\n\n  const float whiteAmt = zeus::clamp(0.f, 1.f - player.x9f4_deathTime / (0.05f * 6.f), 1.f);\n  zeus::CColor color = zeus::skWhite;\n  color.a() = whiteAmt;\n  CCameraFilterPass::DrawFilter(EFilterType::Add, EFilterShape::Fullscreen, color, nullptr, 1.f);\n}\n\nvoid CStateManager::DrawAdditionalFilters() {\n  if (xf0c_escapeTimer >= 1.f || xf0c_escapeTimer <= 0.f || x870_cameraManager->IsInCinematicCamera()) {\n    return;\n  }\n\n  zeus::CColor color = zeus::skWhite;\n  color.a() = 1.f - xf0c_escapeTimer;\n  CCameraFilterPass::DrawFilter(EFilterType::Add, EFilterShape::Fullscreen, color, nullptr, 1.f);\n}\n\nzeus::CFrustum CStateManager::SetupDrawFrustum(const CViewport& vp) const {\n  zeus::CFrustum ret;\n  const CGameCamera* cam = x870_cameraManager->GetCurrentCamera(*this);\n  const zeus::CTransform camXf = x870_cameraManager->GetCurrentCameraTransform(*this);\n  const int vpWidth = static_cast<int>(xf2c_viewportScale.x() * vp.mWidth);\n  const int vpHeight = static_cast<int>(xf2c_viewportScale.y() * vp.mHeight);\n  const int vpLeft = (vp.mWidth - vpWidth) / 2 + vp.mLeft;\n  const int vpTop = (vp.mHeight - vpHeight) / 2 + vp.mTop;\n  g_Renderer->SetViewport(vpLeft, vpTop, vpWidth, vpHeight);\n  const float fov = std::atan(std::tan(zeus::degToRad(cam->GetFov()) * 0.5f) * xf2c_viewportScale.y()) * 2.f;\n  const float width = xf2c_viewportScale.x() * vp.mWidth;\n  const float height = xf2c_viewportScale.y() * vp.mHeight;\n  zeus::CProjection proj;\n  proj.setPersp(zeus::SProjPersp{fov, width / height, cam->GetNearClipDistance(), cam->GetFarClipDistance()});\n  ret.updatePlanes(camXf, proj);\n  return ret;\n}\n\nzeus::CFrustum CStateManager::SetupViewForDraw(const CViewport& vp) const {\n  const CGameCamera* cam = x870_cameraManager->GetCurrentCamera(*this);\n  const zeus::CTransform camXf = x870_cameraManager->GetCurrentCameraTransform(*this);\n  g_Renderer->SetWorldViewpoint(camXf);\n  CCubeModel::SetNewPlayerPositionAndTime(x84c_player->GetTranslation(), CStopwatch::GetGlobalTimerObj());\n  const int vpWidth = static_cast<int>(xf2c_viewportScale.x() * vp.mWidth);\n  const int vpHeight = static_cast<int>(xf2c_viewportScale.y() * vp.mHeight);\n  const int vpLeft = (vp.mWidth - vpWidth) / 2 + vp.mLeft;\n  const int vpTop = (vp.mHeight - vpHeight) / 2 + vp.mTop;\n  g_Renderer->SetViewport(vpLeft, vpTop, vpWidth, vpHeight);\n  CGraphics::SetDepthRange(DEPTH_WORLD, DEPTH_FAR);\n  const float fov = std::atan(std::tan(zeus::degToRad(cam->GetFov()) * 0.5f) * xf2c_viewportScale.y()) * 2.f;\n  const float width = xf2c_viewportScale.x() * vp.mWidth;\n  const float height = xf2c_viewportScale.y() * vp.mHeight;\n  g_Renderer->SetPerspective(zeus::radToDeg(fov), width, height, cam->GetNearClipDistance(), cam->GetFarClipDistance());\n  zeus::CFrustum frustum;\n  zeus::CProjection proj;\n  proj.setPersp(zeus::SProjPersp{fov, width / height, cam->GetNearClipDistance(), cam->GetFarClipDistance()});\n  frustum.updatePlanes(camXf, proj);\n  g_Renderer->SetClippingPlanes(frustum);\n  g_Renderer->PrimColor(zeus::skWhite);\n  CGraphics::SetModelMatrix(zeus::CTransform());\n  x87c_fluidPlaneManager->StartFrame(false);\n  g_Renderer->SetDebugOption(IRenderer::EDebugOption::PVSState, static_cast<int>(EPVSVisSetState::NodeFound));\n  return frustum;\n}\n\nvoid CStateManager::ResetViewAfterDraw(const CViewport& backupViewport,\n                                       const zeus::CTransform& backupViewMatrix) const {\n  g_Renderer->SetViewport(backupViewport.mLeft, backupViewport.mTop, backupViewport.mWidth, backupViewport.mHeight);\n  const CGameCamera* cam = x870_cameraManager->GetCurrentCamera(*this);\n\n  zeus::CFrustum frustum;\n  frustum.updatePlanes(backupViewMatrix, zeus::SProjPersp(zeus::degToRad(cam->GetFov()), CGraphics::GetViewportAspect(),\n                                                          cam->GetNearClipDistance(), cam->GetFarClipDistance()));\n  g_Renderer->SetClippingPlanes(frustum);\n\n  g_Renderer->SetPerspective(cam->GetFov(), CGraphics::GetViewportWidth(), CGraphics::GetViewportHeight(),\n                             cam->GetNearClipDistance(), cam->GetFarClipDistance());\n}\n\nvoid CStateManager::DrawWorld() {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CStateManager::DrawWorld\", zeus::skBlue);\n  const CTimeProvider timeProvider(xf14_curTimeMod900);\n  const CViewport backupViewport = CGraphics::mViewport;\n\n  /* Area camera is in (not necessarily player) */\n  const TAreaId visAreaId = GetVisAreaId();\n\n  x850_world->TouchSky();\n\n  const zeus::CFrustum frustum = SetupViewForDraw(CGraphics::mViewport);\n  const zeus::CTransform backupViewMatrix = CGraphics::mViewMatrix;\n\n  int areaCount = 0;\n  std::array<const CGameArea*, 10> areaArr;\n  for (const CGameArea& area : *x850_world) {\n    if (areaCount == 10) {\n      break;\n    }\n    CGameArea::EOcclusionState occState = CGameArea::EOcclusionState::Occluded;\n    if (area.IsPostConstructed()) {\n      occState = area.GetOcclusionState();\n    }\n    if (occState == CGameArea::EOcclusionState::Visible) {\n      areaArr[areaCount++] = &area;\n    }\n  }\n\n  std::sort(areaArr.begin(), areaArr.begin() + areaCount, [visAreaId](const CGameArea* a, const CGameArea* b) {\n    if (a->x4_selfIdx == b->x4_selfIdx) {\n      return false;\n    }\n    if (visAreaId == a->x4_selfIdx) {\n      return false;\n    }\n    if (visAreaId == b->x4_selfIdx) {\n      return true;\n    }\n    return CGraphics::mViewPoint.dot(a->GetAABB().center()) > CGraphics::mViewPoint.dot(b->GetAABB().center());\n  });\n\n  int pvsCount = 0;\n  std::array<CPVSVisSet, 10> pvsArr;\n  for (auto area = areaArr.cbegin(); area != areaArr.cbegin() + areaCount; ++area) {\n    const CGameArea* areaPtr = *area;\n    CPVSVisSet& pvsSet = pvsArr[pvsCount++];\n    pvsSet.Reset(EPVSVisSetState::OutOfBounds);\n    GetVisSetForArea(areaPtr->x4_selfIdx, visAreaId, pvsSet);\n  }\n\n  int mask;\n  int targetMask;\n  const auto visor = x8b8_playerState->GetActiveVisor(*this);\n  const bool thermal = visor == CPlayerState::EPlayerVisor::Thermal;\n  if (thermal) {\n    xf34_thermalFlag = EThermalDrawFlag::Cold;\n    mask = 0x34;\n    targetMask = 0;\n  } else {\n    xf34_thermalFlag = EThermalDrawFlag::Bypass;\n    mask = 1 << (visor == CPlayerState::EPlayerVisor::XRay ? 3 : 1);\n    targetMask = 0;\n  }\n\n  g_Renderer->SetThermal(thermal, g_tweakGui->GetThermalVisorLevel(), g_tweakGui->GetThermalVisorColor());\n  g_Renderer->SetThermalColdScale(xf28_thermColdScale2 + xf24_thermColdScale1);\n\n  for (int i = areaCount - 1; i >= 0; --i) {\n    //OPTICK_EVENT(\"CStateManager::DrawWorld DrawArea\");\n    const CGameArea& area = *areaArr[i];\n    SetupFogForArea(area);\n    g_Renderer->EnablePVS(pvsArr[i], area.x4_selfIdx);\n    g_Renderer->SetWorldLightFadeLevel(area.GetPostConstructed()->x1128_worldLightingLevel);\n    g_Renderer->DrawUnsortedGeometry(area.x4_selfIdx, mask, targetMask);\n  }\n\n  if (!SetupFogForDraw()) {\n    g_Renderer->SetWorldFog(ERglFogMode::None, 0.f, 1.f, zeus::skBlack);\n  }\n\n  x850_world->DrawSky(zeus::CTransform::Translate(CGraphics::mViewPoint));\n\n  if (areaCount != 0) {\n    SetupFogForArea(*areaArr[areaCount - 1]);\n  }\n\n  for (const auto& id : x86c_stateManagerContainer->xf370_) {\n    if (auto* ent = static_cast<CActor*>(ObjectById(id))) {\n      if (!thermal || (ent->xe6_27_thermalVisorFlags & 1) != 0) {\n        ent->Render(*this);\n      }\n    }\n  }\n\n  bool morphingPlayerVisible = false;\n  int thermalActorCount = 0;\n  std::array<CActor*, kMaxEntities> thermalActorArr;\n  for (int i = 0; i < areaCount; ++i) {\n    const CGameArea& area = *areaArr[i];\n    CPVSVisSet& pvs = pvsArr[i];\n    const bool isVisArea = area.x4_selfIdx == visAreaId;\n    SetupFogForArea(area);\n    g_Renderer->SetWorldLightFadeLevel(area.GetPostConstructed()->x1128_worldLightingLevel);\n    for (CEntity* ent : *area.GetAreaObjects()) {\n      if (const TCastToPtr<CActor> actor = ent) {\n        if (!actor->IsDrawEnabled()) {\n          continue;\n        }\n        const TUniqueId actorId = actor->GetUniqueId();\n        if (!thermal && area.LookupPVSUniqueID(actorId) == actorId) {\n          if (pvs.GetVisible(area.LookupPVSID(actorId)) == EPVSVisSetState::EndOfTree) {\n            continue;\n          }\n        }\n        if (x84c_player.get() == actor.GetPtr()) {\n          if (thermal) {\n            continue;\n          }\n          switch (x84c_player->GetMorphballTransitionState()) {\n          case CPlayer::EPlayerMorphBallState::Unmorphed:\n          case CPlayer::EPlayerMorphBallState::Morphed:\n            x84c_player->AddToRenderer(frustum, *this);\n            continue;\n          default:\n            morphingPlayerVisible = true;\n            continue;\n          }\n        }\n        if (!thermal || (actor->xe6_27_thermalVisorFlags & 1) != 0) {\n          actor->AddToRenderer(frustum, *this);\n        }\n        if (thermal && (actor->xe6_27_thermalVisorFlags & 2) != 0) {\n          thermalActorArr[thermalActorCount++] = actor.GetPtr();\n        }\n      }\n    }\n\n    if (isVisArea && !thermal) {\n      CDecalManager::AddToRenderer(frustum, *this);\n      x884_actorModelParticles->AddStragglersToRenderer(*this);\n    }\n\n    ++x8dc_objectDrawToken;\n\n    x84c_player->GetMorphBall()->DrawBallShadow(*this);\n\n    if (xf7c_projectedShadow != nullptr) {\n      xf7c_projectedShadow->Render(*this);\n    }\n\n    g_Renderer->EnablePVS(pvs, area.x4_selfIdx);\n    g_Renderer->DrawSortedGeometry(area.x4_selfIdx, mask, targetMask);\n  }\n\n  x880_envFxManager->Render(*this);\n\n  if (morphingPlayerVisible) {\n    x84c_player->Render(*this);\n  }\n\n  g_Renderer->PostRenderFogs();\n\n  if (thermal) {\n    if (x86c_stateManagerContainer->xf39c_renderLast.size()) {\n      CGraphics::SetDepthRange(DEPTH_SCREEN_ACTORS, DEPTH_GUN);\n      for (const auto& id : x86c_stateManagerContainer->xf39c_renderLast) {\n        if (auto* actor = static_cast<CActor*>(ObjectById(id))) {\n          if ((actor->xe6_27_thermalVisorFlags & 1) != 0) {\n            actor->Render(*this);\n          }\n        }\n      }\n      CGraphics::SetDepthRange(DEPTH_WORLD, DEPTH_FAR);\n    }\n    g_Renderer->DoThermalBlendCold();\n    xf34_thermalFlag = EThermalDrawFlag::Hot;\n\n    for (const auto& id : x86c_stateManagerContainer->xf370_) {\n      if (auto* actor = static_cast<CActor*>(ObjectById(id))) {\n        if ((actor->xe6_27_thermalVisorFlags & 2) != 0) {\n          actor->Render(*this);\n        }\n      }\n    }\n\n    for (int i = areaCount - 1; i >= 0; --i) {\n      const CGameArea& area = *areaArr[i];\n      CPVSVisSet& pvs = pvsArr[i];\n\n      g_Renderer->EnablePVS(pvs, area.x4_selfIdx);\n      g_Renderer->DrawUnsortedGeometry(area.x4_selfIdx, mask, 0x20);\n      g_Renderer->DrawAreaGeometry(area.x4_selfIdx, mask, 0x10);\n    }\n\n    ++x8dc_objectDrawToken;\n\n    for (int i = 0; i < areaCount; ++i) {\n      const CGameArea& area = *areaArr[i];\n      CPVSVisSet& pvs = pvsArr[i];\n\n      for (int j = 0; j < thermalActorCount; ++j) {\n        CActor* actor = thermalActorArr[j];\n        if (actor->GetAreaIdAlways() != area.x4_selfIdx) {\n          if (actor->GetAreaIdAlways() != kInvalidAreaId || area.x4_selfIdx != visAreaId) {\n            continue;\n          }\n        }\n        actor->AddToRenderer(frustum, *this);\n      }\n\n      if (areaCount - 1 == i) {\n        x884_actorModelParticles->AddStragglersToRenderer(*this);\n        CDecalManager::AddToRenderer(frustum, *this);\n        if (x84c_player) {\n          x84c_player->AddToRenderer(frustum, *this);\n        }\n      }\n\n      ++x8dc_objectDrawToken;\n\n      g_Renderer->EnablePVS(pvs, area.x4_selfIdx);\n      g_Renderer->DrawSortedGeometry(area.x4_selfIdx, mask, 0x10);\n    }\n\n    g_Renderer->PostRenderFogs();\n  }\n\n  x87c_fluidPlaneManager->EndFrame();\n\n  g_Renderer->SetWorldFog(ERglFogMode::None, 0.f, 1.f, zeus::skBlack);\n\n#if 0\n    if (false) {\n        CacheReflection();\n    }\n#endif\n\n  if (x84c_player) {\n    x84c_player->RenderGun(*this, x870_cameraManager->GetGlobalCameraTranslation(*this));\n  }\n\n  if (x86c_stateManagerContainer->xf39c_renderLast.size()) {\n    CGraphics::SetDepthRange(DEPTH_SCREEN_ACTORS, DEPTH_GUN);\n    for (const auto& id : x86c_stateManagerContainer->xf39c_renderLast) {\n      if (auto* actor = static_cast<CActor*>(ObjectById(id))) {\n        if (!thermal || actor->xe6_27_thermalVisorFlags & 0x2) {\n          actor->Render(*this);\n        }\n      }\n    }\n    CGraphics::SetDepthRange(DEPTH_WORLD, DEPTH_FAR);\n  }\n\n  if (thermal) {\n    g_Renderer->DoThermalBlendHot();\n    g_Renderer->SetThermal(false, 0.f, zeus::skBlack);\n    xf34_thermalFlag = EThermalDrawFlag::Bypass;\n  }\n\n  DrawDebugStuff();\n  RenderCamerasAndAreaLights();\n  ResetViewAfterDraw(backupViewport, backupViewMatrix);\n  DrawE3DeathEffect();\n  DrawAdditionalFilters();\n}\n\nvoid CStateManager::SetupFogForArea3XRange(TAreaId area) const {\n  if (area == kInvalidAreaId) {\n    area = x8cc_nextAreaId;\n  }\n\n  const CGameArea* areaObj = x850_world->GetAreaAlways(area);\n  if (areaObj->IsPostConstructed()) {\n    SetupFogForArea3XRange(*areaObj);\n  }\n}\n\nvoid CStateManager::SetupFogForArea(TAreaId area) const {\n  if (area == kInvalidAreaId) {\n    area = x8cc_nextAreaId;\n  }\n\n  const CGameArea* areaObj = x850_world->GetAreaAlways(area);\n  if (areaObj->IsPostConstructed()) {\n    SetupFogForArea(*areaObj);\n  }\n}\n\nvoid CStateManager::SetupFogForAreaNonCurrent(TAreaId area) const {\n  if (area == kInvalidAreaId) {\n    area = x8cc_nextAreaId;\n  }\n\n  const CGameArea* areaObj = x850_world->GetAreaAlways(area);\n  if (areaObj->IsPostConstructed()) {\n    SetupFogForAreaNonCurrent(*areaObj);\n  }\n}\n\nvoid CStateManager::SetupFogForArea3XRange(const CGameArea& area) const {\n  if (x8b8_playerState->GetActiveVisor(*this) != CPlayerState::EPlayerVisor::XRay) {\n    return;\n  }\n\n  const float fogDist = area.GetXRayFogDistance();\n  const float farz = (g_tweakGui->GetXRayFogNearZ() * (1.f - fogDist) + g_tweakGui->GetXRayFogFarZ() * fogDist) * 3.f;\n  g_Renderer->SetWorldFog(ERglFogMode(g_tweakGui->GetXRayFogMode()), g_tweakGui->GetXRayFogNearZ(), farz,\n                          g_tweakGui->GetXRayFogColor());\n}\n\nvoid CStateManager::SetupFogForArea(const CGameArea& area) const {\n  if (SetupFogForDraw()) {\n    return;\n  }\n\n  if (x8b8_playerState->GetActiveVisor(*this) == CPlayerState::EPlayerVisor::XRay) {\n    const float fogDist = area.GetXRayFogDistance();\n    const float farz = g_tweakGui->GetXRayFogNearZ() * (1.f - fogDist) + g_tweakGui->GetXRayFogFarZ() * fogDist;\n    g_Renderer->SetWorldFog(ERglFogMode(g_tweakGui->GetXRayFogMode()), g_tweakGui->GetXRayFogNearZ(), farz,\n                            g_tweakGui->GetXRayFogColor());\n  } else {\n    area.GetAreaFog()->SetCurrent();\n  }\n}\n\nvoid CStateManager::SetupFogForAreaNonCurrent(const CGameArea& area) const {\n  if (SetupFogForDraw()) {\n    return;\n  }\n\n  if (x8b8_playerState->GetActiveVisor(*this) != CPlayerState::EPlayerVisor::XRay) {\n    return;\n  }\n\n  const float fogDist = area.GetXRayFogDistance();\n  const float farz = g_tweakGui->GetXRayFogNearZ() * (1.f - fogDist) + g_tweakGui->GetXRayFogFarZ() * fogDist;\n  g_Renderer->SetWorldFog(ERglFogMode(g_tweakGui->GetXRayFogMode()), g_tweakGui->GetXRayFogNearZ(), farz,\n                          g_tweakGui->GetXRayFogColor());\n}\n\nbool CStateManager::SetupFogForDraw() const {\n  switch (x8b8_playerState->GetActiveVisor(*this)) {\n  case CPlayerState::EPlayerVisor::Thermal:\n    g_Renderer->SetWorldFog(ERglFogMode::None, 0.f, 1.f, zeus::skBlack);\n    return true;\n  case CPlayerState::EPlayerVisor::XRay:\n  default:\n    return false;\n  case CPlayerState::EPlayerVisor::Combat:\n  case CPlayerState::EPlayerVisor::Scan:\n    auto& fog = x870_cameraManager->Fog();\n    if (fog.IsFogDisabled()) {\n      return false;\n    }\n    fog.SetCurrent();\n    return true;\n  }\n}\n\nvoid CStateManager::PreRender() {\n  if (!xf94_24_readyToRender) {\n    return;\n  }\n\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CStateManager::PreRender\", zeus::skBlue);\n  const zeus::CFrustum frustum = SetupDrawFrustum(CGraphics::mViewport);\n  x86c_stateManagerContainer->xf370_.clear();\n  x86c_stateManagerContainer->xf39c_renderLast.clear();\n  xf7c_projectedShadow = nullptr;\n  x850_world->PreRender();\n  BuildDynamicLightListForWorld();\n  for (const CGameArea& area : *x850_world) {\n    auto occState = CGameArea::EOcclusionState::Occluded;\n    if (area.IsPostConstructed()) {\n      occState = area.GetOcclusionState();\n    }\n    if (occState == CGameArea::EOcclusionState::Visible) {\n      for (CEntity* ent : *area.GetPostConstructed()->x10c0_areaObjs) {\n        if (const TCastToPtr<CActor> act = ent) {\n          if (act->IsDrawEnabled()) {\n            act->CalculateRenderBounds();\n            act->PreRender(*this, frustum);\n          }\n        }\n      }\n    }\n  }\n\n  CacheReflection();\n  g_Renderer->PrepareDynamicLights(x8e0_dynamicLights);\n}\n\nvoid CStateManager::GetCharacterRenderMaskAndTarget(bool thawed, int& mask, int& target) const {\n  switch (x8b8_playerState->GetActiveVisor(*this)) {\n  case CPlayerState::EPlayerVisor::Combat:\n  case CPlayerState::EPlayerVisor::Scan:\n    mask = 0x1000;\n    target = 0x0;\n    break;\n  case CPlayerState::EPlayerVisor::XRay:\n    mask = 0x800;\n    target = 0x0;\n    break;\n  case CPlayerState::EPlayerVisor::Thermal:\n    if (thawed) {\n      if (xf34_thermalFlag == EThermalDrawFlag::Hot) {\n        mask = 0x600;\n        target = 0x0;\n      } else {\n        mask = 0x600;\n        target = 0x200;\n      }\n    } else {\n      if (xf34_thermalFlag == EThermalDrawFlag::Cold) {\n        mask = 0x500;\n        target = 0x0;\n      } else {\n        mask = 0x500;\n        target = 0x100;\n      }\n    }\n    break;\n  default:\n    mask = 0x0;\n    target = 0x0;\n    break;\n  }\n}\n\nbool CStateManager::GetVisSetForArea(TAreaId a, TAreaId b, CPVSVisSet& setOut) const {\n  if (b == kInvalidAreaId) {\n    return false;\n  }\n\n  const zeus::CVector3f viewPoint = CGraphics::mViewMatrix.origin;\n  zeus::CVector3f closestDockPoint = viewPoint;\n  bool hasClosestDock = false;\n  if (a != b) {\n    const CGameArea& area = *x850_world->GetGameAreas()[b];\n    if (area.IsPostConstructed()) {\n      for (const CGameArea::Dock& dock : area.GetDocks()) {\n        for (int i = 0; i < dock.GetDockRefs().size(); ++i) {\n          const TAreaId connArea = dock.GetConnectedAreaId(i);\n          if (connArea == a) {\n            const auto& verts = dock.GetPlaneVertices();\n            const zeus::CVector3f dockCenter = (verts[0] + verts[1] + verts[2] + verts[3]) * 0.25f;\n            if (hasClosestDock) {\n              if ((dockCenter - viewPoint).magSquared() >= (closestDockPoint - viewPoint).magSquared()) {\n                continue;\n              }\n            }\n            closestDockPoint = dockCenter;\n            hasClosestDock = true;\n          }\n        }\n      }\n    }\n  } else {\n    hasClosestDock = true;\n  }\n\n  if (hasClosestDock) {\n    if (CPVSAreaSet* pvs = x850_world->GetGameAreas()[a]->GetPostConstructed()->xa0_pvs.get()) {\n      const CPVSVisOctree& octree = pvs->GetVisOctree();\n      const zeus::CVector3f closestDockLocal = x850_world->GetGameAreas()[a]->GetInverseTransform() * closestDockPoint;\n      CPVSVisSet set;\n      set.SetTestPoint(octree, closestDockLocal);\n\n      if (set.GetState() == EPVSVisSetState::NodeFound) {\n        setOut = set;\n        return true;\n      }\n    }\n  }\n\n  return false;\n}\n\nvoid CStateManager::RecursiveDrawTree(TUniqueId node) {\n  if (const TCastToPtr<CActor> actor = ObjectById(node)) {\n    if (x8dc_objectDrawToken != actor->xc8_drawnToken) {\n      if (actor->xc6_nextDrawNode != kInvalidUniqueId) {\n        RecursiveDrawTree(actor->xc6_nextDrawNode);\n      }\n      if (x8dc_objectDrawToken == actor->xcc_addedToken) {\n        actor->Render(*this);\n      }\n      actor->xc8_drawnToken = x8dc_objectDrawToken;\n    }\n  }\n}\n\nvoid CStateManager::SendScriptMsg(CEntity* dest, TUniqueId src, EScriptObjectMessage msg) {\n  if (dest == nullptr || dest->x30_26_scriptingBlocked) {\n    return;\n  }\n\n  if (m_logScripting) {\n    auto srcObj = GetObjectById(src);\n    if (srcObj != nullptr) {\n      spdlog::info(\"{} is sending '{}' to '{}' id= {} -> {}\", srcObj->GetName(), ScriptObjectMessageToStr(msg),\n                   dest->GetName(), dest->GetUniqueId(), src, dest->GetUniqueId());\n    } else {\n      spdlog::info(\"Sending '{}' to '{}' id= {}\", ScriptObjectMessageToStr(msg), dest->GetName(), dest->GetUniqueId());\n    }\n  }\n\n  dest->AcceptScriptMsg(msg, src, *this);\n}\n\nvoid CStateManager::SendScriptMsg(TUniqueId dest, TUniqueId src, EScriptObjectMessage msg) {\n  CEntity* ent = ObjectById(dest);\n  SendScriptMsg(ent, src, msg);\n}\n\nvoid CStateManager::SendScriptMsgAlways(TUniqueId dest, TUniqueId src, EScriptObjectMessage msg) {\n  CEntity* dst = ObjectById(dest);\n  if (dst == nullptr) {\n    return;\n  }\n\n  if (m_logScripting) {\n    auto srcObj = GetObjectById(src);\n    if (srcObj != nullptr) {\n      spdlog::info(\"{} is sending '{}' to '{}' id= {} -> {}\", srcObj->GetName(), ScriptObjectMessageToStr(msg),\n                   dst->GetName(), dst->GetUniqueId(), src, dst->GetUniqueId());\n    } else {\n      spdlog::info(\"Sending '{}' to '{}' id= {}\", ScriptObjectMessageToStr(msg), dst->GetName(), dst->GetUniqueId());\n    }\n  }\n\n  dst->AcceptScriptMsg(msg, src, *this);\n}\n\nvoid CStateManager::SendScriptMsg(TUniqueId src, TEditorId dest, EScriptObjectMessage msg, EScriptObjectState state) {\n  // CEntity* ent = GetObjectById(src);\n  const auto search = GetIdListForScript(dest);\n  if (search.first == x890_scriptIdMap.cend()) {\n    return;\n  }\n\n  for (auto it = search.first; it != search.second; ++it) {\n    const TUniqueId id = it->second;\n    CEntity* dobj = GetAllObjectList().GetObjectById(id);\n    SendScriptMsg(dobj, src, msg);\n  }\n}\n\nvoid CStateManager::FreeScriptObjects(TAreaId aid) {\n  for (const auto& p : x890_scriptIdMap) {\n    if (p.first.AreaNum() == aid) {\n      FreeScriptObject(p.second);\n    }\n  }\n\n  std::set<TEditorId> freedObjects;\n  for (auto it = x8a4_loadedScriptObjects.begin(); it != x8a4_loadedScriptObjects.end();) {\n    if (it->first.AreaNum() == aid) {\n      freedObjects.emplace(it->first);\n      it = x8a4_loadedScriptObjects.erase(it);\n      continue;\n    }\n    ++it;\n  }\n\n  const CGameArea* area = x850_world->GetGameAreas()[aid].get();\n  if (area->IsPostConstructed()) {\n    const CGameArea::CPostConstructed* pc = area->GetPostConstructed();\n    for (CEntity* ent : *pc->x10c0_areaObjs) {\n      if (ent != nullptr && !ent->IsInUse()) {\n        FreeScriptObject(ent->GetUniqueId());\n      }\n    }\n  }\n\n  for (const auto& id : freedObjects) {\n    m_incomingConnections.erase(id);\n  }\n}\n\nvoid CStateManager::FreeScriptObject(TUniqueId id) {\n  CEntity* ent = ObjectById(id);\n  if (ent == nullptr || ent->IsInGraveyard()) {\n    return;\n  }\n\n  ent->SetIsInGraveyard(true);\n  x854_objectGraveyard.push_back(id);\n  ent->AcceptScriptMsg(EScriptObjectMessage::Deleted, kInvalidUniqueId, *this);\n  ent->SetIsScriptingBlocked(true);\n\n  if (const TCastToPtr<CActor> act = ent) {\n    x874_sortedListManager->Remove(act.GetPtr());\n    act->SetUseInSortedLists(false);\n  }\n\n  if (m_logScripting) {\n    spdlog::info(\"Removed '{}'\", ent->GetName());\n  }\n}\n\nstd::pair<const SScriptObjectStream*, TEditorId> CStateManager::GetBuildForScript(TEditorId id) const {\n  const auto search = x8a4_loadedScriptObjects.find(id);\n  if (search == x8a4_loadedScriptObjects.cend()) {\n    return {nullptr, kInvalidEditorId};\n  }\n  return {&search->second, search->first};\n}\n\nTEditorId CStateManager::GetEditorIdForUniqueId(TUniqueId id) const {\n  const CEntity* ent = GetObjectById(id);\n  if (ent != nullptr) {\n    return ent->GetEditorId();\n  }\n  return kInvalidEditorId;\n}\n\nTUniqueId CStateManager::GetIdForScript(TEditorId id) const {\n  const auto search = x890_scriptIdMap.find(id);\n  if (search == x890_scriptIdMap.cend()) {\n    return kInvalidUniqueId;\n  }\n  return search->second;\n}\n\nstd::pair<std::multimap<TEditorId, TUniqueId>::const_iterator, std::multimap<TEditorId, TUniqueId>::const_iterator>\nCStateManager::GetIdListForScript(TEditorId id) const {\n  auto ret = x890_scriptIdMap.equal_range(id);\n  if (ret.first != x890_scriptIdMap.cend() && ret.first->first != id) {\n    ret.first = x890_scriptIdMap.cend();\n    ret.second = x890_scriptIdMap.cend();\n  }\n  return ret;\n}\n\nvoid CStateManager::LoadScriptObjects(TAreaId aid, CInputStream& in, std::vector<TEditorId>& idsOut) {\n  in.ReadUint8();\n\n  const u32 objCount = in.ReadLong();\n  idsOut.reserve(idsOut.size() + objCount);\n  for (u32 i = 0; i < objCount; ++i) {\n    const auto objType = static_cast<EScriptObjectType>(in.ReadUint8());\n    const u32 objSize = in.ReadLong();\n    const u32 pos = static_cast<u32>(in.GetReadPosition());\n    const auto id = LoadScriptObject(aid, objType, objSize, in);\n    if (id.first == kInvalidEditorId) {\n      continue;\n    }\n\n    const auto build = GetBuildForScript(id.first);\n    if (build.first) {\n      continue;\n    }\n\n    x8a4_loadedScriptObjects[id.first] = SScriptObjectStream{objType, pos, objSize};\n    idsOut.push_back(id.first);\n  }\n\n  for (const auto& pair : m_incomingConnections) {\n    if (auto* ent = ObjectById(GetIdForScript(pair.first))) {\n      ent->SetIncomingConnectionList(&pair.second);\n    }\n  }\n}\n\nstd::pair<TEditorId, TUniqueId> CStateManager::LoadScriptObject(TAreaId aid, EScriptObjectType type, u32 length,\n                                                                CInputStream& in) {\n  //OPTICK_EVENT();\n  const TEditorId id = in.ReadLong();\n  const u32 connCount = in.ReadLong();\n  length -= 8;\n  std::vector<SConnection> conns;\n  conns.reserve(connCount);\n  for (u32 i = 0; i < connCount; ++i) {\n    const auto state = EScriptObjectState(in.ReadLong());\n    const auto msg = EScriptObjectMessage(in.ReadLong());\n    const TEditorId target = in.ReadLong();\n    // Metaforce Addition\n    if (m_incomingConnections.find(target) == m_incomingConnections.cend()) {\n      m_incomingConnections.emplace(target, std::set<SConnection>());\n    }\n    SConnection inConn{state, msg, id};\n    m_incomingConnections[target].emplace(inConn);\n    // End Metaforce Addition\n    length -= 12;\n    conns.push_back(SConnection{state, msg, target});\n  }\n  const u32 propCount = in.ReadLong();\n  length -= 4;\n  const auto startPos = in.GetReadPosition();\n\n  bool error = false;\n  FScriptLoader loader = {};\n  if (type < EScriptObjectType::ScriptObjectTypeMAX && type >= EScriptObjectType::Actor) {\n    loader = x90c_loaderFuncs[size_t(type)];\n  }\n\n  CEntity* ent = nullptr;\n  if (loader != nullptr) {\n    const CEntityInfo info(aid, std::move(conns), id);\n    ent = loader(*this, in, propCount, info);\n  } else {\n    error = true;\n  }\n\n  if (ent != nullptr) {\n    AddObject(ent);\n  } else {\n    error = true;\n  }\n\n  const u32 readAmt = in.GetReadPosition() - startPos;\n  if (readAmt > length) {\n    spdlog::fatal(\"Script object overread while reading {}\", ScriptObjectTypeToStr(type));\n  }\n\n  const u32 leftover = length - readAmt;\n  for (u32 i = 0; i < leftover; ++i) {\n    in.ReadChar();\n  }\n\n  if (error || ent == nullptr) {\n    spdlog::error(\"Script load error while loading {} (Editor ID: {}, Area: {})\", ScriptObjectTypeToStr(type), id, aid);\n    return {kInvalidEditorId, kInvalidUniqueId};\n  } else {\n#ifndef NDEBUG\n    spdlog::info(\"Loaded {} in area {}\", ent->GetName(), ent->GetAreaIdAlways());\n#endif\n    return {id, ent->GetUniqueId()};\n  }\n}\n\nstd::pair<TEditorId, TUniqueId> CStateManager::GenerateObject(TEditorId eid) {\n  const std::pair<const SScriptObjectStream*, TEditorId> build = GetBuildForScript(eid);\n\n  if (build.first) {\n    const CGameArea* area = x850_world->GetArea(build.second.AreaNum());\n    if (area->IsPostConstructed()) {\n      const std::pair<const u8*, u32> buf = area->GetLayerScriptBuffer(build.second.LayerNum());\n      CMemoryInStream stream(buf.first + build.first->x4_position, build.first->x8_length);\n      auto ret = LoadScriptObject(build.second.AreaNum(), build.first->x0_type, build.first->x8_length, stream);\n      // Metaforce Addition\n      if (m_incomingConnections.find(eid) != m_incomingConnections.end()) {\n        if (auto ent = ObjectById(ret.second)) {\n          ent->SetIncomingConnectionList(&m_incomingConnections[eid]);\n        }\n      }\n      // End Metaforce Addition\n      return ret;\n    }\n  }\n\n  return {kInvalidEditorId, kInvalidUniqueId};\n}\n\nvoid CStateManager::InitScriptObjects(const std::vector<TEditorId>& ids) {\n  for (const auto& id : ids) {\n    if (id == kInvalidEditorId) {\n      continue;\n    }\n\n    const TUniqueId uid = GetIdForScript(id);\n    SendScriptMsg(uid, kInvalidUniqueId, EScriptObjectMessage::InitializedInArea);\n  }\n\n  MurderScriptInstanceNames();\n}\n\nvoid CStateManager::InformListeners(const zeus::CVector3f& pos, EListenNoiseType type) {\n  for (CEntity* ent : GetListeningAiObjectList()) {\n    if (const TCastToPtr<CPatterned> ai = ent) {\n      if (!ai->GetActive()) {\n        continue;\n      }\n\n      CGameArea* area = x850_world->GetArea(ai->GetAreaIdAlways());\n      CGameArea::EOcclusionState occState = CGameArea::EOcclusionState::Occluded;\n\n      if (area->IsPostConstructed()) {\n        occState = area->GetPostConstructed()->x10dc_occlusionState;\n      }\n\n      if (occState != CGameArea::EOcclusionState::Occluded) {\n        ai->Listen(pos, type);\n      }\n    }\n  }\n}\n\nvoid CStateManager::ApplyKnockBack(CActor& actor, const CDamageInfo& info, const CDamageVulnerability& vuln,\n                                   const zeus::CVector3f& pos, float dampen) {\n  if (vuln.GetVulnerability(info.GetWeaponMode(), false) == EVulnerability::Deflect) {\n    return;\n  }\n\n  const CHealthInfo* hInfo = actor.HealthInfo(*this);\n  if (hInfo == nullptr) {\n    return;\n  }\n\n  const float dampedPower = (1.f - dampen) * info.GetKnockBackPower();\n  if (const TCastToPtr<CPlayer> player = actor) {\n    KnockBackPlayer(*player, pos, dampedPower, hInfo->GetKnockbackResistance());\n    return;\n  }\n\n  const TCastToPtr<CPatterned> ai = actor;\n  if (!ai && hInfo->GetHP() <= 0.f) {\n    if (dampedPower > hInfo->GetKnockbackResistance()) {\n      if (const TCastToPtr<CPhysicsActor> physActor = actor) {\n        const zeus::CVector3f kbVec =\n            pos * (dampedPower - hInfo->GetKnockbackResistance()) * physActor->GetMass() * 1.5f;\n        if (physActor->GetMaterialList().HasMaterial(EMaterialTypes::Immovable) ||\n            !physActor->GetMaterialList().HasMaterial(EMaterialTypes::Solid)) {\n          return;\n        }\n        physActor->ApplyImpulseWR(kbVec, zeus::CAxisAngle());\n        return;\n      }\n    }\n  }\n\n  if (ai) {\n    ai->KnockBack(pos, *this, info, dampen == 0.f ? EKnockBackType::Direct : EKnockBackType::Radius, false,\n                  dampedPower);\n  }\n}\n\nvoid CStateManager::KnockBackPlayer(CPlayer& player, const zeus::CVector3f& pos, float power, float resistance) {\n  if (player.GetMaterialList().HasMaterial(EMaterialTypes::Immovable)) {\n    return;\n  }\n\n  float usePower;\n  if (player.GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Morphed) {\n    usePower = power * 1000.f;\n    const auto surface = player.GetSurfaceRestraint();\n    if (surface != CPlayer::ESurfaceRestraints::Normal &&\n        player.GetOrbitState() == CPlayer::EPlayerOrbitState::NoOrbit) {\n      usePower /= 7.f;\n    }\n  } else {\n    usePower = power * 500.f;\n  }\n\n  const float minVel = player.GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed ? 35.f : 70.f;\n  const float playerVel = player.x138_velocity.magnitude();\n  const float maxVel = std::max(playerVel, minVel);\n  const zeus::CVector3f negVel = -player.x138_velocity;\n  usePower *= (1.f - (0.5f * zeus::CVector3f::getAngleDiff(pos, negVel)) / M_PIF);\n  player.ApplyImpulseWR(pos * usePower, zeus::CAxisAngle());\n  player.UseCollisionImpulses();\n  player.x2d4_accelerationChangeTimer = 0.25f;\n\n  const float newVel = player.x138_velocity.magnitude();\n  if (newVel > maxVel) {\n    const zeus::CVector3f vel = (1.f / newVel) * player.x138_velocity * maxVel;\n    player.SetVelocityWR(vel);\n  }\n}\n\nvoid CStateManager::ApplyDamageToWorld(TUniqueId damager, const CActor& actor, const zeus::CVector3f& pos,\n                                       const CDamageInfo& info, const CMaterialFilter& filter) {\n  const zeus::CAABox aabb(pos - info.GetRadius(), pos + info.GetRadius());\n\n  bool bomb = false;\n  const TCastToConstPtr<CWeapon> weapon = actor;\n  if (weapon) {\n    bomb = True(weapon->GetAttribField() & (EProjectileAttrib::Bombs | EProjectileAttrib::PowerBombs));\n  }\n\n  EntityList nearList;\n  BuildNearList(nearList, aabb, filter, &actor);\n  for (const auto& id : nearList) {\n    CEntity* ent = ObjectById(id);\n    if (ent == nullptr) {\n      continue;\n    }\n\n    const TCastToPtr<CPlayer> player = ent;\n    if (bomb && player) {\n      if (player->GetFrozenState()) {\n        g_GameState->SystemOptions().IncrementFrozenBallCount();\n        MP1::CSamusHud::DisplayHudMemo(u\"\", CHUDMemoParms{0.f, true, true, true});\n        player->UnFreeze(*this);\n      } else {\n        if ((weapon->GetAttribField() & EProjectileAttrib::Bombs) == EProjectileAttrib::Bombs) {\n          player->BombJump(pos, *this);\n        }\n      }\n    } else if (ent->GetUniqueId() != damager) {\n      TestBombHittingWater(actor, pos, static_cast<CActor&>(*ent));\n      if (TestRayDamage(pos, static_cast<CActor&>(*ent), nearList)) {\n        ApplyRadiusDamage(actor, pos, static_cast<CActor&>(*ent), info);\n      }\n    }\n\n    if (const TCastToPtr<CWallCrawlerSwarm> swarm = ent) {\n      swarm->ApplyRadiusDamage(pos, info, *this);\n    }\n\n    if (const TCastToPtr<CSnakeWeedSwarm> swarm = ent) {\n      swarm->ApplyRadiusDamage(pos, info, *this);\n    }\n  }\n}\n\nvoid CStateManager::ProcessRadiusDamage(const CActor& damager, CActor& damagee, TUniqueId senderId,\n                                        const CDamageInfo& info, const CMaterialFilter& filter) {\n  const zeus::CAABox aabb(damager.GetTranslation() - info.GetRadius(), damager.GetTranslation() + info.GetRadius());\n  EntityList nearList;\n  BuildNearList(nearList, aabb, filter, nullptr);\n  for (const auto& id : nearList) {\n    CEntity* ent = ObjectById(id);\n    if (ent == nullptr || ent->GetUniqueId() == damager.GetUniqueId() || ent->GetUniqueId() == senderId ||\n        ent->GetUniqueId() == damagee.GetUniqueId()) {\n      continue;\n    }\n\n    TestBombHittingWater(damager, damager.GetTranslation(), static_cast<CActor&>(*ent));\n    if (TestRayDamage(damager.GetTranslation(), static_cast<CActor&>(*ent), nearList)) {\n      ApplyRadiusDamage(damager, damager.GetTranslation(), static_cast<CActor&>(*ent), info);\n    }\n  }\n}\n\nvoid CStateManager::ApplyRadiusDamage(const CActor& a1, const zeus::CVector3f& pos, CActor& a2,\n                                      const CDamageInfo& info) {\n  zeus::CVector3f delta = a2.GetTranslation() - pos;\n  std::optional<zeus::CAABox> bounds;\n  if (delta.magSquared() < info.GetRadius() * info.GetRadius() ||\n      ((bounds = a2.GetTouchBounds()) &&\n       CCollidableSphere::Sphere_AABox_Bool(zeus::CSphere{pos, info.GetRadius()}, *bounds))) {\n    float rad = info.GetRadius();\n    if (rad > FLT_EPSILON) {\n      rad = delta.magnitude() / rad;\n    } else {\n      rad = 0.f;\n    }\n    if (rad > 0.f) {\n      delta.normalize();\n    }\n\n    bool alive = false;\n    if (const CHealthInfo* hInfo = a2.HealthInfo(*this)) {\n      if (hInfo->GetHP() > 0.f) {\n        alive = true;\n      }\n    }\n\n    const CDamageVulnerability* vuln =\n        rad > 0.f ? a2.GetDamageVulnerability(pos, delta, info) : a2.GetDamageVulnerability();\n\n    if (vuln->WeaponHurts(info.GetWeaponMode(), true)) {\n      const float dam = info.GetRadiusDamage(*vuln);\n      if (dam > 0.f) {\n        ApplyLocalDamage(pos, delta, a2, dam, info.GetWeaponMode());\n      }\n      a2.SendScriptMsgs(EScriptObjectState::Damage, *this, EScriptObjectMessage::None);\n      SendScriptMsg(&a2, a1.GetUniqueId(), EScriptObjectMessage::Damage);\n    } else {\n      a2.SendScriptMsgs(EScriptObjectState::InvulnDamage, *this, EScriptObjectMessage::None);\n      SendScriptMsg(&a2, a1.GetUniqueId(), EScriptObjectMessage::InvulnDamage);\n    }\n\n    if (alive && info.GetKnockBackPower() > 0.f) {\n      ApplyKnockBack(a2, info, *vuln, (a2.GetTranslation() - a1.GetTranslation()).normalized(), rad);\n    }\n  }\n}\n\nbool CStateManager::TestRayDamage(const zeus::CVector3f& pos, const CActor& damagee, const EntityList& nearList) const {\n  const CHealthInfo* hInfo = const_cast<CActor&>(damagee).HealthInfo(const_cast<CStateManager&>(*this));\n  if (hInfo == nullptr) {\n    return false;\n  }\n\n  static constexpr CMaterialList incList(EMaterialTypes::Solid);\n  static constexpr CMaterialList exList(EMaterialTypes::ProjectilePassthrough, EMaterialTypes::Player,\n                                        EMaterialTypes::Occluder, EMaterialTypes::Character);\n  static constexpr CMaterialFilter filter(incList, exList, CMaterialFilter::EFilterType::IncludeExclude);\n\n  const std::optional<zeus::CAABox> bounds = damagee.GetTouchBounds();\n  if (!bounds) {\n    return false;\n  }\n\n  const zeus::CVector3f center = bounds->center();\n  zeus::CVector3f dir = center - pos;\n\n  if (!dir.canBeNormalized()) {\n    return true;\n  }\n  const float origMag = dir.magnitude();\n  dir = dir * (1.f / origMag);\n\n  if (RayCollideWorld(pos, center, nearList, filter, &damagee)) {\n    return true;\n  }\n\n  const zeus::CMRay ray(pos, dir, origMag);\n  if (!MultiRayCollideWorld(ray, filter)) {\n    return false;\n  }\n\n  float depth;\n  zeus::CVector3f norm;\n  const u32 count = CollisionUtil::RayAABoxIntersection(ray, *bounds, norm, depth);\n  if (count == 0 || count == 1) {\n    return true;\n  }\n\n  return CGameCollision::RayDynamicIntersectionBool(*this, pos, dir, filter, nearList, &damagee, depth * origMag);\n}\n\nbool CStateManager::RayCollideWorld(const zeus::CVector3f& start, const zeus::CVector3f& end,\n                                    const CMaterialFilter& filter, const CActor* damagee) const {\n  zeus::CVector3f delta = end - start;\n  const float mag = delta.magnitude();\n  delta = delta / mag;\n  EntityList nearList;\n  BuildNearList(nearList, start, delta, mag, filter, damagee);\n  return RayCollideWorldInternal(start, end, filter, nearList, damagee);\n}\n\nbool CStateManager::RayCollideWorld(const zeus::CVector3f& start, const zeus::CVector3f& end,\n                                    const EntityList& nearList, const CMaterialFilter& filter,\n                                    const CActor* damagee) const {\n  return RayCollideWorldInternal(start, end, filter, nearList, damagee);\n}\n\nbool CStateManager::RayCollideWorldInternal(const zeus::CVector3f& start, const zeus::CVector3f& end,\n                                            const CMaterialFilter& filter, const EntityList& nearList,\n                                            const CActor* damagee) const {\n  const zeus::CVector3f delta = end - start;\n  if (!delta.canBeNormalized()) {\n    return true;\n  }\n\n  const float mag = delta.magnitude();\n  const zeus::CVector3f dir = delta * (1.f / mag);\n  if (!CGameCollision::RayStaticIntersectionBool(*this, start, dir, mag, filter)) {\n    return false;\n  }\n  return CGameCollision::RayDynamicIntersectionBool(*this, start, dir, filter, nearList, damagee, mag);\n}\n\nbool CStateManager::MultiRayCollideWorld(const zeus::CMRay& ray, const CMaterialFilter& filter) const {\n  zeus::CVector3f crossed = {-ray.dir.z() * ray.dir.z() - ray.dir.y() * ray.dir.x(),\n                             ray.dir.x() * ray.dir.x() - ray.dir.z() * ray.dir.y(),\n                             ray.dir.y() * ray.dir.y() - ray.dir.x() * -ray.dir.z()};\n\n  crossed.normalize();\n  const zeus::CVector3f crossed2 = ray.dir.cross(crossed) * 0.35355338f;\n  const zeus::CVector3f negCrossed2 = -crossed2;\n  const zeus::CVector3f rms = crossed * 0.35355338f;\n  const zeus::CVector3f negRms = -rms;\n\n  for (int i = 0; i < 4; ++i) {\n    const zeus::CVector3f& useCrossed = (i & 2) != 0 ? negCrossed2 : crossed2;\n    const zeus::CVector3f& useRms = (i & 1) != 0 ? rms : negRms;\n    if (CGameCollision::RayStaticIntersectionBool(*this, ray.start + useCrossed + useRms, ray.dir, ray.length,\n                                                  filter)) {\n      return true;\n    }\n  }\n\n  return false;\n}\n\nvoid CStateManager::TestBombHittingWater(const CActor& damager, const zeus::CVector3f& pos, CActor& damagee) {\n  const TCastToConstPtr<CWeapon> wpn = damager;\n  if (!wpn) {\n    return;\n  }\n\n  if (False(wpn->GetAttribField() & (EProjectileAttrib::Bombs | EProjectileAttrib::PowerBombs))) {\n    return;\n  }\n\n  const bool powerBomb = (wpn->GetAttribField() & EProjectileAttrib::PowerBombs) == EProjectileAttrib::PowerBombs;\n  const TCastToPtr<CScriptWater> water = damagee;\n  if (!water) {\n    return;\n  }\n\n  const zeus::CAABox bounds = water->GetTriggerBoundsWR();\n  const zeus::CVector3f hitPos(pos.x(), pos.y(), bounds.max.z());\n  const float bombRad = powerBomb ? 4.f : 2.f;\n  const float delta = bounds.max.z() - pos.dot(zeus::skUp);\n  if (delta <= bombRad && delta > 0.f) {\n    // Below surface\n    const float rippleFactor = 1.f - delta / bombRad;\n    if (x87c_fluidPlaneManager->GetLastRippleDeltaTime(damager.GetUniqueId()) >= 0.15f) {\n      const float bombMag = powerBomb ? 1.f : 0.75f;\n      const float mag = 0.6f * bombMag + 0.4f * bombMag * std::sin(2.f * M_PIF * rippleFactor * 0.25f);\n      water->GetFluidPlane().AddRipple(mag, damager.GetUniqueId(), hitPos, *water, *this);\n    }\n    if (!powerBomb) {\n      x87c_fluidPlaneManager->CreateSplash(damager.GetUniqueId(), *this, *water, hitPos, rippleFactor, true);\n    }\n  } else {\n    // Above surface\n    const float bombMag = powerBomb ? 2.f : 1.f;\n    if (delta <= -bombMag || delta >= 0.f) {\n      return;\n    }\n    const CRayCastResult res = RayStaticIntersection(pos, zeus::skDown, -delta, CMaterialFilter::skPassEverything);\n    if (res.IsInvalid() && x87c_fluidPlaneManager->GetLastRippleDeltaTime(damager.GetUniqueId()) >= 0.15f) {\n      // Not blocked by static geometry\n      const float mag = 0.6f * bombMag + 0.4f * bombMag * std::sin(2.f * M_PIF * -delta / bombMag * 0.25f);\n      water->GetFluidPlane().AddRipple(mag, damager.GetUniqueId(), hitPos, *water, *this);\n    }\n  }\n}\n\nbool CStateManager::ApplyLocalDamage(const zeus::CVector3f& pos, const zeus::CVector3f& dir, CActor& damagee, float dam,\n                                     const CWeaponMode& weapMode) {\n  CHealthInfo* hInfo = damagee.HealthInfo(*this);\n  if (hInfo == nullptr || dam < 0.f) {\n    return false;\n  }\n\n  if (hInfo->GetHP() <= 0.f) {\n    return true;\n  }\n\n  float mulDam = dam;\n\n  TCastToPtr<CPlayer> player = damagee;\n  CAi* ai = TCastToPtr<CPatterned>(damagee).GetPtr();\n  if (ai == nullptr) {\n    ai = TCastToPtr<CDestroyableRock>(damagee).GetPtr();\n  }\n\n  if (player) {\n    if (GetPlayerState()->CanTakeDamage()) {\n      if (x870_cameraManager->IsInCinematicCamera() ||\n          (weapMode.GetType() == EWeaponType::Phazon &&\n           x8b8_playerState->HasPowerUp(CPlayerState::EItemType::PhazonSuit))) {\n        return false;\n      }\n\n      if (g_GameState->GetHardMode()) {\n        mulDam *= g_GameState->GetHardModeDamageMultiplier();\n      }\n\n      float damReduction = 0.f;\n      if (x8b8_playerState->HasPowerUp(CPlayerState::EItemType::VariaSuit)) {\n        damReduction = g_tweakPlayer->GetVariaDamageReduction();\n      }\n      if (x8b8_playerState->HasPowerUp(CPlayerState::EItemType::GravitySuit)) {\n        damReduction = std::max(g_tweakPlayer->GetGravityDamageReduction(), damReduction);\n      }\n      if (x8b8_playerState->HasPowerUp(CPlayerState::EItemType::PhazonSuit)) {\n        damReduction = std::max(g_tweakPlayer->GetPhazonDamageReduction(), damReduction);\n      }\n\n      mulDam = -(damReduction * mulDam - mulDam);\n    } else {\n      mulDam = 0.f;\n    }\n  }\n\n  const float newHp = hInfo->GetHP() - mulDam;\n  const bool significant = std::fabs(newHp - hInfo->GetHP()) >= 0.00001;\n  hInfo->SetHP(newHp);\n\n  if (player && GetPlayerState()->CanTakeDamage()) {\n    player->TakeDamage(significant, pos, mulDam, weapMode.GetType(), *this);\n    if (newHp <= 0.f) {\n      x8b8_playerState->SetPlayerAlive(false);\n    }\n  }\n\n  if (ai != nullptr) {\n    if (significant) {\n      ai->TakeDamage(dir, mulDam);\n    }\n    if (newHp <= 0.f) {\n      ai->Death(*this, dir, EScriptObjectState::DeathRattle);\n    }\n  }\n\n  return significant;\n}\n\nbool CStateManager::ApplyDamage(TUniqueId damagerId, TUniqueId damageeId, TUniqueId radiusSender,\n                                const CDamageInfo& info, const CMaterialFilter& filter,\n                                const zeus::CVector3f& knockbackVec) {\n  CEntity* ent0 = ObjectById(damagerId);\n  CEntity* ent1 = ObjectById(damageeId);\n  const TCastToPtr<CActor> damager = ent0;\n  const TCastToPtr<CActor> damagee = ent1;\n  const bool isPlayer = TCastToPtr<CPlayer>(ent1) != nullptr;\n\n  if (damagee != nullptr) {\n    if (CHealthInfo* hInfo = damagee->HealthInfo(*this)) {\n      zeus::CVector3f position;\n      zeus::CVector3f direction = zeus::skRight;\n      const bool alive = hInfo->GetHP() > 0.f;\n      if (damager) {\n        position = damager->GetTranslation();\n        direction = damager->GetTransform().basis[1];\n      }\n\n      const CDamageVulnerability* dVuln = (damager != nullptr || isPlayer)\n                                              ? damagee->GetDamageVulnerability(position, direction, info)\n                                              : damagee->GetDamageVulnerability();\n\n      if (info.GetWeaponMode().GetType() == EWeaponType::None || dVuln->WeaponHurts(info.GetWeaponMode(), false)) {\n        if (info.GetDamage() > 0.f) {\n          ApplyLocalDamage(position, direction, *damagee, info.GetDamage(), info.GetWeaponMode());\n        }\n        damagee->SendScriptMsgs(EScriptObjectState::Damage, *this, EScriptObjectMessage::None);\n        SendScriptMsg(damagee.GetPtr(), damagerId, EScriptObjectMessage::Damage);\n      } else {\n        damagee->SendScriptMsgs(EScriptObjectState::InvulnDamage, *this, EScriptObjectMessage::None);\n        SendScriptMsg(damagee.GetPtr(), damagerId, EScriptObjectMessage::InvulnDamage);\n      }\n\n      if (alive && damager && info.GetKnockBackPower() > 0.f) {\n        zeus::CVector3f delta =\n            knockbackVec.isZero() ? (damagee->GetTranslation() - damager->GetTranslation()) : knockbackVec;\n        delta.z() = 0.0001f;\n        ApplyKnockBack(*damagee, info, *dVuln, delta.normalized(), 0.f);\n      }\n    }\n\n    if (damager && info.GetRadius() > 0.f) {\n      ProcessRadiusDamage(*damager, *damagee, radiusSender, info, filter);\n    }\n\n    if (const TCastToPtr<CWallCrawlerSwarm> swarm = ent1) {\n      if (damager) {\n        swarm->ApplyRadiusDamage(damager->GetTranslation(), info, *this);\n      }\n    }\n  }\n\n  return false;\n}\n\nvoid CStateManager::UpdateAreaSounds() {\n  rstl::reserved_vector<TAreaId, 10> areas;\n  for (CGameArea& area : *x850_world) {\n    auto occState = CGameArea::EOcclusionState::Occluded;\n    if (area.IsPostConstructed()) {\n      occState = area.GetOcclusionState();\n    }\n    if (occState == CGameArea::EOcclusionState::Visible) {\n      areas.push_back(area.GetAreaId());\n    }\n  }\n  CSfxManager::SetActiveAreas(areas);\n}\n\nvoid CStateManager::FrameEnd() {\n  CModel::FrameDone();\n  g_SimplePool->Flush();\n}\n\nvoid CStateManager::ProcessPlayerInput() {\n  if (x84c_player) {\n    x84c_player->ProcessInput(xb54_finalInput, *this);\n  }\n}\n\nvoid CStateManager::SetGameState(EGameState state) {\n  if (x904_gameState == state) {\n    return;\n  }\n\n  if (x904_gameState == EGameState::SoftPaused) {\n    x850_world->SetLoadPauseState(false);\n  }\n\n  switch (state) {\n  case EGameState::Running:\n    if (x88c_rumbleManager->IsDisabled()) {\n      x88c_rumbleManager->SetDisabled(false);\n    }\n    break;\n  case EGameState::SoftPaused:\n    if (!x88c_rumbleManager->IsDisabled()) {\n      x88c_rumbleManager->SetDisabled(true);\n    }\n    x850_world->SetLoadPauseState(true);\n    break;\n  default:\n    break;\n  }\n\n  x904_gameState = state;\n}\n\nstatic const CFinalInput s_DisabledFinalInput = {};\n\nvoid CStateManager::ProcessInput(const CFinalInput& input) {\n  if (input.ControllerIdx() == 0) {\n    const CGameCamera* cam = x870_cameraManager->GetCurrentCamera(*this);\n    bool disableInput = cam->x170_25_disablesInput;\n    if (x84c_player->x9c6_29_disableInput) {\n      disableInput = true;\n    }\n    if (disableInput) {\n      xb54_finalInput = s_DisabledFinalInput;\n      xb54_finalInput.x0_dt = input.DeltaTime();\n    } else {\n      xb54_finalInput = input;\n    }\n  }\n  x870_cameraManager->ProcessInput(input, *this);\n}\n\nvoid CStateManager::UpdateGraphicsTiming(float dt) {\n  xf14_curTimeMod900 += dt;\n  if (xf14_curTimeMod900 > 900.f) {\n    xf14_curTimeMod900 -= 900.f;\n  }\n}\n\nvoid CStateManager::Update(float dt) {\n  MP1::CMain::UpdateDiscordPresence(GetWorld()->IGetStringTableAssetId());\n\n  CElementGen::SetGlobalSeed(x8d8_updateFrameIdx);\n  CParticleElectric::SetGlobalSeed(x8d8_updateFrameIdx);\n  CDecal::SetGlobalSeed(x8d8_updateFrameIdx);\n  CProjectileWeapon::SetGlobalSeed(x8d8_updateFrameIdx);\n\n  xf08_pauseHudMessage = {};\n\n  CScriptEffect::ResetParticleCounts();\n  UpdateThermalVisor();\n  UpdateGameState();\n\n  const bool dying = x84c_player->x9f4_deathTime > 0.f;\n\n  if (x904_gameState == EGameState::Running) {\n    if (!TCastToPtr<CCinematicCamera>(x870_cameraManager->GetCurrentCamera(*this))) {\n      g_GameState->SetTotalPlayTime(g_GameState->GetTotalPlayTime() + dt);\n      UpdateHintState(dt);\n    }\n\n    for (size_t i = 0; i < numCameraPasses; ++i) {\n      xb84_camFilterPasses[i].Update(dt);\n      xd14_camBlurPasses[i].Update(dt);\n    }\n  }\n\n  if (x904_gameState != EGameState::Paused) {\n    PreThinkObjects(dt);\n    x87c_fluidPlaneManager->Update(dt);\n  }\n\n  if (x904_gameState == EGameState::Running) {\n    if (!dying) {\n      CDecalManager::Update(dt, *this);\n    }\n    UpdateSortedLists();\n    if (!dying) {\n      MovePlatforms(dt);\n      MoveActors(dt);\n    }\n    ProcessPlayerInput();\n    if (x904_gameState != EGameState::SoftPaused) {\n      CGameCollision::Move(*this, *x84c_player, dt, nullptr);\n    }\n    UpdateSortedLists();\n    if (!dying) {\n      CrossTouchActors();\n    }\n  } else {\n    ProcessPlayerInput();\n  }\n\n  if (!dying && x904_gameState == EGameState::Running) {\n    x884_actorModelParticles->Update(dt, *this);\n  }\n\n  if (x904_gameState == EGameState::Running || x904_gameState == EGameState::SoftPaused) {\n    Think(dt);\n  }\n\n  if (x904_gameState != EGameState::SoftPaused) {\n    x870_cameraManager->Update(dt, *this);\n  }\n\n  while (xf76_lastRelay != kInvalidUniqueId) {\n    if (CEntity* ent = ObjectById(xf76_lastRelay)) {\n      ent->Think(dt, *this);\n    } else {\n      xf76_lastRelay = kInvalidUniqueId;\n      break;\n    }\n  }\n\n  if (x904_gameState != EGameState::Paused) {\n    PostUpdatePlayer(dt);\n  }\n\n  if (xf84_ == xf80_hudMessageFrameCount) {\n    ShowPausedHUDMemo(xf88_, xf8c_);\n    --xf84_;\n    xf88_.Reset();\n  }\n\n  if (!dying && x904_gameState == EGameState::Running && !x870_cameraManager->IsInCinematicCamera()) {\n    UpdateEscapeSequenceTimer(dt);\n  }\n\n  x850_world->Update(dt);\n  x88c_rumbleManager->Update(dt);\n\n  if (!dying) {\n    x880_envFxManager->Update(dt, *this);\n  }\n\n  UpdateAreaSounds();\n\n  xf94_24_readyToRender = true;\n\n  if (xf94_27_inMapScreen) {\n    if (const CHintOptions::SHintState* hint = g_GameState->HintOptions().GetCurrentDisplayedHint()) {\n      if (hint->CanContinue()) {\n        g_GameState->HintOptions().DismissDisplayedHint();\n      }\n    }\n    xf94_27_inMapScreen = false;\n  }\n\n  if (!m_warping) {\n    g_GameState->CurrentWorldState().SetAreaId(x8cc_nextAreaId);\n    x850_world->TravelToArea(x8cc_nextAreaId, *this, false);\n  }\n\n  ClearGraveyard();\n  ++x8d8_updateFrameIdx;\n}\n\nvoid CStateManager::UpdateGameState() {\n  // Intentionally empty\n}\n\nvoid CStateManager::UpdateHintState(float dt) {\n  CHintOptions& ho = g_GameState->HintOptions();\n  ho.Update(dt, *this);\n  u32 nextHintIdx = -1;\n  u32 pageIdx = -1;\n  if (const CHintOptions::SHintState* state = ho.GetCurrentDisplayedHint()) {\n    const CGameHintInfo::CGameHint& next = g_MemoryCardSys->GetHints()[ho.GetNextHintIdx()];\n    for (const CGameHintInfo::SHintLocation& loc : next.GetLocations()) {\n      const auto& mwInfo = g_GameState->StateForWorld(loc.x0_mlvlId).MapWorldInfo();\n      mwInfo->SetIsMapped(loc.x8_areaId, true);\n    }\n    if (state->x4_time < next.GetTextTime()) {\n      nextHintIdx = ho.GetNextHintIdx();\n      pageIdx = state->x4_time / 3.f;\n    }\n  }\n\n  if (xeec_hintIdx != nextHintIdx || xef0_hintPeriods != pageIdx) {\n    if (nextHintIdx == -1) {\n      CHUDMemoParms memoInfo = {0.f, true, true, true};\n      MP1::CSamusHud::DisplayHudMemo(u\"\", memoInfo);\n    } else {\n      const CGameHintInfo::CGameHint& data = g_MemoryCardSys->GetHints()[nextHintIdx];\n      CHUDMemoParms memoInfo = {0.f, true, false, true};\n      MP1::CSamusHud::DeferHintMemo(data.GetStringID(), pageIdx, memoInfo);\n    }\n    xeec_hintIdx = nextHintIdx;\n    xef0_hintPeriods = pageIdx;\n  }\n}\n\nvoid CStateManager::PreThinkObjects(float dt) {\n  if (x84c_player->x9f4_deathTime > 0.f) {\n    x84c_player->DoPreThink(dt, *this);\n  } else if (x904_gameState == EGameState::SoftPaused) {\n    for (CEntity* ent : GetAllObjectList()) {\n      if (const TCastToPtr<CScriptEffect> effect = ent) {\n        effect->PreThink(dt, *this);\n      }\n    }\n  } else {\n    for (CEntity* ent : GetAllObjectList()) {\n      if (ent != nullptr && !GetCameraObjectList().GetObjectById(ent->GetUniqueId())) {\n        ent->PreThink(dt, *this);\n      }\n    }\n  }\n}\n\nvoid CStateManager::MovePlatforms(float dt) {\n  for (CEntity* ent : GetPlatformAndDoorObjectList()) {\n    if (ent == nullptr || !GetPlatformAndDoorObjectList().IsPlatform(*ent)) {\n      continue;\n    }\n\n    auto& plat = static_cast<CScriptPlatform&>(*ent);\n    if (!plat.GetActive() || plat.GetMass() == 0.f) {\n      continue;\n    }\n\n    CGameCollision::Move(*this, plat, dt, nullptr);\n  }\n}\n\nvoid CStateManager::MoveActors(float dt) {\n  for (CEntity* ent : GetPhysicsActorObjectList()) {\n    if (ent == nullptr || !ent->GetActive()) {\n      continue;\n    }\n\n    auto& physActor = static_cast<CPhysicsActor&>(*ent);\n    if (physActor.GetMass() == 0.f) {\n      continue;\n    }\n\n    if (const TCastToPtr<CPatterned> ai = physActor) {\n      bool doThink = !xf94_29_cinematicPause;\n      if (doThink && ai->GetAreaIdAlways() != kInvalidAreaId) {\n        const CGameArea* area = x850_world->GetAreaAlways(ai->GetAreaIdAlways());\n        float occTime = 0.0f;\n        if (area->IsPostConstructed()) {\n          occTime = area->GetPostConstructed()->x10e4_occludedTime;\n        }\n        if (occTime > 5.f) {\n          doThink = false;\n        }\n      }\n      if (!doThink) {\n        SendScriptMsgAlways(ai->GetUniqueId(), kInvalidUniqueId, EScriptObjectMessage::SuspendedMove);\n        continue;\n      }\n    }\n\n    if (x84c_player.get() != ent) {\n      if (!GetPlatformAndDoorObjectList().IsPlatform(*ent)) {\n        CGameCollision::Move(*this, physActor, dt, nullptr);\n      }\n    }\n  }\n}\n\nvoid CStateManager::CrossTouchActors() {\n  std::array<bool, kMaxEntities> visits{};\n  EntityList nearList;\n\n  for (CEntity* ent : GetActorObjectList()) {\n    if (ent == nullptr) {\n      continue;\n    }\n\n    auto& actor = static_cast<CActor&>(*ent);\n    if (!actor.GetActive() || !actor.GetCallTouch()) {\n      continue;\n    }\n\n    const std::optional<zeus::CAABox> touchAABB = actor.GetTouchBounds();\n    if (!touchAABB) {\n      continue;\n    }\n\n    CMaterialFilter filter = CMaterialFilter::skPassEverything;\n    if (actor.GetMaterialList().HasMaterial(EMaterialTypes::Trigger)) {\n      filter = CMaterialFilter::MakeExclude(EMaterialTypes::Trigger);\n    }\n\n    nearList.clear();\n    BuildNearList(nearList, *touchAABB, filter, &actor);\n\n    for (const auto& id : nearList) {\n      auto* ent2 = static_cast<CActor*>(ObjectById(id));\n      if (!ent2) {\n        continue;\n      }\n\n      const std::optional<zeus::CAABox> touchAABB2 = ent2->GetTouchBounds();\n      if (!ent2->GetActive() || !touchAABB2) {\n        continue;\n      }\n\n      if (visits[ent2->GetUniqueId().Value()]) {\n        continue;\n      }\n\n      if (touchAABB->intersects(*touchAABB2)) {\n        actor.Touch(*ent2, *this);\n        ent2->Touch(actor, *this);\n      }\n\n      visits[actor.GetUniqueId().Value()] = true;\n    }\n  }\n}\n\nvoid CStateManager::Think(float dt) {\n  if (x84c_player->x9f4_deathTime > 0.f) {\n    x84c_player->DoThink(dt, *this);\n    return;\n  }\n\n  if (x904_gameState == EGameState::SoftPaused) {\n    for (CEntity* ent : GetAllObjectList()) {\n      if (const TCastToPtr<CScriptEffect> effect = ent) {\n        effect->Think(dt, *this);\n      }\n    }\n  } else {\n    for (CEntity* ent : GetAllObjectList()) {\n      if (const TCastToPtr<CPatterned> ai = ent) {\n        bool doThink = !xf94_29_cinematicPause;\n        if (doThink && ai->GetAreaIdAlways() != kInvalidAreaId) {\n          const CGameArea* area = x850_world->GetAreaAlways(ai->GetAreaIdAlways());\n          float occTime = 0.0f;\n          if (area->IsPostConstructed()) {\n            occTime = area->GetPostConstructed()->x10e4_occludedTime;\n          }\n          if (occTime > 5.f) {\n            doThink = false;\n          }\n        }\n        if (!doThink) {\n          continue;\n        }\n      }\n      if (!GetCameraObjectList().GetObjectById(ent->GetUniqueId())) {\n        ent->Think(dt, *this);\n      }\n    }\n  }\n}\n\nvoid CStateManager::PostUpdatePlayer(float dt) { x84c_player->PostUpdate(dt, *this); }\n\nvoid CStateManager::ShowPausedHUDMemo(CAssetId strg, float time) {\n  xf78_hudMessageTime = time;\n  xf08_pauseHudMessage = strg;\n  DeferStateTransition(EStateManagerTransition::MessageScreen);\n}\n\nvoid CStateManager::ClearGraveyard() {\n  for (const auto& id : x854_objectGraveyard) {\n    CEntity* ent = GetAllObjectList().GetValidObjectById(id);\n    RemoveObject(id);\n    std::default_delete<CEntity>()(ent);\n  }\n  x854_objectGraveyard.clear();\n}\n\nvoid CStateManager::FrameBegin(s32 frameCount) {\n  x8d4_inputFrameIdx = frameCount;\n  CTexture::SetCurrentFrameCount(frameCount);\n  CGraphicsPalette::SetCurrentFrameCount(frameCount);\n  // SwapOutTexturesToARAM(2, 0x180000);\n}\n\nvoid CStateManager::InitializeState(CAssetId mlvlId, TAreaId aid, CAssetId mreaId) {\n  const bool hadRandom = x900_activeRandom != nullptr;\n  SetActiveRandomToDefault();\n\n  if (xb3c_initPhase == EInitPhase::LoadWorld) {\n    CreateStandardGameObjects();\n    x850_world = std::make_unique<CWorld>(*g_SimplePool, *g_ResFactory, mlvlId);\n    xb3c_initPhase = EInitPhase::LoadFirstArea;\n  }\n\n  if (xb3c_initPhase == EInitPhase::LoadFirstArea) {\n    if (!x8f0_shadowTex.IsLoaded()) {\n      return;\n    }\n    x8f0_shadowTex.GetObj();\n\n    if (!x850_world->CheckWorldComplete(this, aid, mreaId)) {\n      return;\n    }\n    x8cc_nextAreaId = x850_world->x68_curAreaId;\n    CGameArea* area = x850_world->x18_areas[x8cc_nextAreaId].get();\n    if (x850_world->ScheduleAreaToLoad(area, *this)) {\n      area->StartStreamIn(*this);\n      return;\n    }\n    xb3c_initPhase = EInitPhase::Done;\n  }\n\n  SetCurrentAreaId(x8cc_nextAreaId);\n  g_GameState->CurrentWorldState().SetAreaId(x8cc_nextAreaId);\n  x850_world->TravelToArea(x8cc_nextAreaId, *this, true);\n  UpdateRoomAcoustics(x8cc_nextAreaId);\n\n  for (CEntity* ent : GetAllObjectList()) {\n    SendScriptMsg(ent, kInvalidUniqueId, EScriptObjectMessage::WorldInitialized);\n  }\n\n  for (CEntity* ent : GetAllObjectList()) {\n    CScriptSpawnPoint* sp = TCastToPtr<CScriptSpawnPoint>(ent);\n    if (sp != nullptr && sp->x30_24_active && sp->FirstSpawn()) {\n      const zeus::CTransform& xf = sp->GetTransform();\n      const zeus::CVector3f lookVec = xf.frontVector();\n      if (lookVec.canBeNormalized()) {\n        const auto lookXf = zeus::lookAt(xf.origin, xf.origin + lookVec);\n        x84c_player->Teleport(lookXf, *this, true);\n      }\n\n      if (!g_GameState->x228_25_initPowerupsAtFirstSpawn) {\n        break;\n      }\n\n      g_GameState->x228_25_initPowerupsAtFirstSpawn = false;\n      for (int i = 0; i < int(CPlayerState::EItemType::Max); ++i) {\n        const auto iType = CPlayerState::EItemType(i);\n\n        u32 spawnPu = sp->GetPowerup(iType);\n        u32 statePu = x8b8_playerState->GetItemAmount(iType);\n        if (statePu < spawnPu) {\n          x8b8_playerState->AddPowerUp(iType, spawnPu - statePu);\n        }\n\n        spawnPu = sp->GetPowerup(iType);\n        statePu = x8b8_playerState->GetItemAmount(iType);\n        if (statePu < spawnPu) {\n          x8b8_playerState->IncrPickup(iType, spawnPu - statePu);\n        }\n      }\n    }\n  }\n\n  x84c_player->AsyncLoadSuit(*this);\n  x870_cameraManager->ResetCameras(*this);\n\n  if (!hadRandom) {\n    ClearActiveRandom();\n  } else {\n    SetActiveRandomToDefault();\n  }\n\n  x880_envFxManager->AsyncLoadResources(*this);\n}\n\nvoid CStateManager::CreateStandardGameObjects() {\n  const float height = g_tweakPlayer->GetPlayerHeight();\n  const float xyHe = g_tweakPlayer->GetPlayerXYHalfExtent();\n  const float stepUp = g_tweakPlayer->GetStepUpHeight();\n  const float stepDown = g_tweakPlayer->GetStepDownHeight();\n  const float ballRadius = g_tweakPlayer->GetPlayerBallHalfExtent();\n  const zeus::CAABox pBounds = {{-xyHe, -xyHe, 0.f}, {xyHe, xyHe, height}};\n  const auto q = zeus::CQuaternion::fromAxisAngle(zeus::CVector3f{0.f, 0.f, 1.f}, zeus::degToRad(129.6f));\n\n  x84c_player = std::make_unique<CPlayer>(\n      AllocateUniqueId(), zeus::CTransform(q), pBounds, g_tweakPlayerRes->xc4_ballTransitionsANCS,\n      zeus::CVector3f{1.65f, 1.65f, 1.65f}, 200.f, stepUp, stepDown, ballRadius,\n      CMaterialList(EMaterialTypes::Player, EMaterialTypes::Solid, EMaterialTypes::GroundCollider));\n  AddObject(*x84c_player);\n  x870_cameraManager->CreateStandardCameras(*this);\n}\n\nCObjectList* CStateManager::ObjectListById(EGameObjectList type) {\n  if (type == EGameObjectList::Invalid) {\n    return nullptr;\n  }\n  return x808_objLists[int(type)].get();\n}\n\nconst CObjectList* CStateManager::GetObjectListById(EGameObjectList type) const {\n  if (type == EGameObjectList::Invalid) {\n    return nullptr;\n  }\n  return x808_objLists[int(type)].get();\n}\n\nvoid CStateManager::RemoveObject(TUniqueId uid) {\n  if (CEntity* ent = GetAllObjectList().GetValidObjectById(uid)) {\n    if (ent->GetEditorId() != kInvalidEditorId) {\n      const auto search = x890_scriptIdMap.equal_range(ent->GetEditorId());\n      for (auto it = search.first; it != search.second;) {\n        if (it->second == uid) {\n          it = x890_scriptIdMap.erase(it);\n          continue;\n        }\n        ++it;\n      }\n    }\n    if (ent->GetAreaIdAlways() != kInvalidAreaId) {\n      CGameArea* area = x850_world->GetArea(ent->GetAreaIdAlways());\n      if (area->IsPostConstructed()) {\n        area->GetAreaObjects()->RemoveObject(uid);\n      }\n    }\n    if (const TCastToPtr<CActor> act = ent) {\n      x874_sortedListManager->Remove(act.GetPtr());\n    }\n  }\n  for (auto& list : x808_objLists) {\n    list->RemoveObject(uid);\n  }\n}\n\nvoid CStateManager::UpdateRoomAcoustics(TAreaId aid) {\n  u32 updateCount = 0;\n  std::array<CScriptRoomAcoustics*, 10> updates;\n  for (CEntity* ent : GetAllObjectList()) {\n    if (const TCastToPtr<CScriptRoomAcoustics> acoustics = ent) {\n      if (acoustics->GetAreaIdAlways() != aid || !acoustics->GetActive()) {\n        continue;\n      }\n      updates[updateCount++] = acoustics.GetPtr();\n    }\n    if (updateCount >= updates.size()) {\n      break;\n    }\n  }\n\n  if (updateCount == 0) {\n    CScriptRoomAcoustics::DisableAuxCallbacks();\n    return;\n  }\n\n  const auto idx = int(updateCount * x900_activeRandom->Float() * 0.99f);\n  updates[idx]->EnableAuxCallbacks();\n}\n\nvoid CStateManager::SetCurrentAreaId(TAreaId aid) {\n  if (aid != x8cc_nextAreaId) {\n    x8d0_prevAreaId = x8cc_nextAreaId;\n    UpdateRoomAcoustics(aid);\n    x8cc_nextAreaId = aid;\n  }\n\n  if (aid == kInvalidAreaId) {\n    return;\n  }\n  if (x8c0_mapWorldInfo->IsAreaVisited(aid)) {\n    return;\n  }\n  x8c0_mapWorldInfo->SetAreaVisited(aid, true);\n  x850_world->IGetMapWorld()->RecalculateWorldSphere(*x8c0_mapWorldInfo, *x850_world);\n}\n\nvoid CStateManager::AreaUnloaded(TAreaId) {\n  // Intentionally empty\n}\n\nvoid CStateManager::PrepareAreaUnload(TAreaId aid) {\n  for (CEntity* ent : GetAllObjectList()) {\n    if (const TCastToPtr<CScriptDoor> door = ent) {\n      if (door->IsConnectedToArea(*this, aid)) {\n        door->ForceClosed(*this);\n      }\n    }\n  }\n  FreeScriptObjects(aid);\n}\n\nvoid CStateManager::AreaLoaded(TAreaId aid) {\n  x8bc_mailbox->SendMsgs(aid, *this);\n  x880_envFxManager->AreaLoaded();\n}\n\nvoid CStateManager::BuildNearList(EntityList& listOut, const zeus::CVector3f& v1, const zeus::CVector3f& v2, float f1,\n                                  const CMaterialFilter& filter, const CActor* actor) const {\n  x874_sortedListManager->BuildNearList(listOut, v1, v2, f1, filter, actor);\n}\n\nvoid CStateManager::BuildColliderList(EntityList& listOut, const CActor& actor, const zeus::CAABox& aabb) const {\n  x874_sortedListManager->BuildNearList(listOut, actor, aabb);\n}\n\nvoid CStateManager::BuildNearList(EntityList& listOut, const zeus::CAABox& aabb, const CMaterialFilter& filter,\n                                  const CActor* actor) const {\n  x874_sortedListManager->BuildNearList(listOut, aabb, filter, actor);\n}\n\nvoid CStateManager::UpdateActorInSortedLists(CActor& act) {\n  if (!act.GetUseInSortedLists() || !act.xe4_27_notInSortedLists) {\n    return;\n  }\n\n  const std::optional<zeus::CAABox> aabb = CalculateObjectBounds(act);\n  const bool actorInLists = x874_sortedListManager->ActorInLists(&act);\n  if (actorInLists || aabb) {\n    act.xe4_27_notInSortedLists = false;\n    if (actorInLists) {\n      if (!act.GetActive() || !aabb) {\n        x874_sortedListManager->Remove(&act);\n      } else {\n        x874_sortedListManager->Move(&act, *aabb);\n      }\n    } else if (act.GetActive() && aabb) {\n      x874_sortedListManager->Insert(&act, *aabb);\n    }\n  }\n}\n\nvoid CStateManager::UpdateSortedLists() {\n  if (!x850_world) {\n    return;\n  }\n\n  for (CEntity* actor : GetActorObjectList()) {\n    UpdateActorInSortedLists(static_cast<CActor&>(*actor));\n  }\n}\n\nstd::optional<zeus::CAABox> CStateManager::CalculateObjectBounds(const CActor& actor) {\n  std::optional<zeus::CAABox> bounds = actor.GetTouchBounds();\n  if (bounds) {\n    zeus::CAABox aabb;\n    aabb.accumulateBounds(bounds->min);\n    aabb.accumulateBounds(bounds->max);\n    if (TCastToConstPtr<CPhysicsActor> physAct = actor) {\n      const zeus::CAABox physAabb = physAct->GetBoundingBox();\n      aabb.accumulateBounds(physAabb.min);\n      aabb.accumulateBounds(physAabb.max);\n    }\n    return aabb;\n  } else {\n    if (const TCastToConstPtr<CPhysicsActor> physAct = actor) {\n      return physAct->GetBoundingBox();\n    }\n  }\n  return std::nullopt;\n}\n\nvoid CStateManager::AddObject(CEntity& ent) {\n  if (ent.GetEditorId() != kInvalidEditorId) {\n    x890_scriptIdMap.insert(std::make_pair(ent.GetEditorId(), ent.GetUniqueId()));\n  }\n  for (auto& list : x808_objLists) {\n    list->AddObject(ent);\n  }\n\n  if (ent.GetAreaIdAlways() == kInvalidAreaId && x84c_player && ent.GetUniqueId() != x84c_player->GetUniqueId()) {\n    ent.x4_areaId = x84c_player->GetAreaIdAlways();\n  }\n  if (ent.GetAreaIdAlways() != kInvalidAreaId) {\n    CGameArea* area = x850_world->GetArea(ent.GetAreaIdAlways());\n    if (area->IsPostConstructed()) {\n      area->GetAreaObjects()->AddObject(ent);\n    }\n  }\n\n  if (const TCastToPtr<CActor> act = ent) {\n    UpdateActorInSortedLists(*act.GetPtr());\n  }\n\n  ent.AcceptScriptMsg(EScriptObjectMessage::Registered, kInvalidUniqueId, *this);\n\n  if (ent.GetAreaIdAlways() != kInvalidAreaId && x850_world) {\n    CGameArea* area = x850_world->GetArea(ent.GetAreaIdAlways());\n    if (area->IsValidated()) {\n      SendScriptMsg(&ent, kInvalidUniqueId, EScriptObjectMessage::InitializedInArea);\n    }\n  }\n\n  if (m_logScripting) {\n    spdlog::info(\"Added '{}'\", ent.GetName());\n  }\n}\n\nvoid CStateManager::AddObject(CEntity* ent) {\n  if (!ent) {\n    return;\n  }\n\n  AddObject(*ent);\n}\n\nCRayCastResult CStateManager::RayStaticIntersection(const zeus::CVector3f& pos, const zeus::CVector3f& dir,\n                                                    float length, const CMaterialFilter& filter) const {\n  return CGameCollision::RayStaticIntersection(*this, pos, dir, length, filter);\n}\n\nCRayCastResult CStateManager::RayWorldIntersection(TUniqueId& idOut, const zeus::CVector3f& pos,\n                                                   const zeus::CVector3f& dir, float length,\n                                                   const CMaterialFilter& filter, const EntityList& list) const {\n  return CGameCollision::RayWorldIntersection(*this, idOut, pos, dir, length, filter, list);\n}\n\nzeus::CVector3f CStateManager::Random2f(float scaleMin, float scaleMax) {\n  zeus::CVector3f ret(x900_activeRandom->Float() - 0.5f, x900_activeRandom->Float() - 0.5f, 0.f);\n  if (std::fabs(ret.x()) < 0.001f) {\n    ret.x() = 0.001f;\n  }\n  ret.normalize();\n  return ret * ((scaleMax - scaleMin) * x900_activeRandom->Float() + scaleMin);\n}\n\nvoid CStateManager::UpdateObjectInLists(CEntity& ent) {\n  for (auto& list : x808_objLists) {\n    if (list->GetValidObjectById(ent.GetUniqueId())) {\n      if (!list->IsQualified(ent)) {\n        list->RemoveObject(ent.GetUniqueId());\n      }\n    }\n    if (!list->GetValidObjectById(ent.GetUniqueId())) {\n      list->AddObject(ent);\n    }\n  }\n}\n\nTUniqueId CStateManager::AllocateUniqueId() {\n  const s16 lastIndex = x0_nextFreeIndex;\n  s16 ourIndex;\n  do {\n    ourIndex = x0_nextFreeIndex;\n    x0_nextFreeIndex = (ourIndex + 1) & 0x3ff;\n    if (x0_nextFreeIndex == lastIndex) {\n      spdlog::fatal(\"Object list full!\");\n    }\n  } while (GetAllObjectList().GetObjectByIndex(ourIndex) != nullptr);\n\n  x4_idxArr[ourIndex] = (x4_idxArr[ourIndex] + 1) & 0x3f;\n  if (TUniqueId(ourIndex, x4_idxArr[ourIndex]) == kInvalidUniqueId) {\n    x4_idxArr[ourIndex] = 0;\n  }\n\n  return TUniqueId(ourIndex, x4_idxArr[ourIndex]);\n}\n\nvoid CStateManager::DeferStateTransition(EStateManagerTransition t) {\n  if (t == EStateManagerTransition::InGame) {\n    if (xf90_deferredTransition != EStateManagerTransition::InGame) {\n      x850_world->SetLoadPauseState(false);\n      xf90_deferredTransition = EStateManagerTransition::InGame;\n    }\n  } else {\n    if (xf90_deferredTransition == EStateManagerTransition::InGame) {\n      x850_world->SetLoadPauseState(true);\n      xf90_deferredTransition = t;\n    }\n  }\n}\n\nbool CStateManager::CanShowMapScreen() const {\n  const CHintOptions::SHintState* curDispHint = g_GameState->HintOptions().GetCurrentDisplayedHint();\n  return curDispHint == nullptr || curDispHint->CanContinue();\n}\n\nstd::pair<u32, u32> CStateManager::CalculateScanCompletionRate() const {\n  u32 num = 0;\n  u32 denom = 0;\n  int idx = 0;\n  for (const std::pair<CAssetId, float>& scan : x8b8_playerState->GetScanTimes()) {\n    const auto category = g_MemoryCardSys->GetScanStates()[idx++].second;\n    if (category != CWorldSaveGameInfo::EScanCategory::None &&\n        category != CWorldSaveGameInfo::EScanCategory::Research) {\n      ++denom;\n      if (scan.second == 1.f) {\n        ++num;\n      }\n    }\n  }\n  return {num, denom};\n}\n\nvoid CStateManager::SetBossParams(TUniqueId bossId, float maxEnergy, u32 stringIdx) {\n  xf18_bossId = bossId;\n  xf1c_totalBossEnergy = maxEnergy;\n  xf20_bossStringIdx = stringIdx - (g_Main->IsUSA() && !g_Main->IsTrilogy() ? 0 : 6);\n}\n\nfloat CStateManager::IntegrateVisorFog(float f) const {\n  if (x8b8_playerState->GetActiveVisor(*this) == CPlayerState::EPlayerVisor::Scan) {\n    return (1.f - x8b8_playerState->GetVisorTransitionFactor()) * f;\n  }\n  return f;\n}\n\nfloat CStateManager::g_EscapeShakeCountdown;\nbool CStateManager::g_EscapeShakeCountdownInit = false;\n\nvoid CStateManager::sub_80044098(const CCollisionResponseData& colRespData, const CRayCastResult& rayCast,\n                                 TUniqueId uid, const CWeaponMode& weaponMode, u32 w1, u8 thermalFlags) {\n  // TODO implement\n}\n\nconst CGameArea* CStateManager::GetCurrentArea() const {\n  if (x850_world != nullptr && x850_world->GetCurrentAreaId() != kInvalidAreaId) {\n    return x850_world->GetAreaAlways(x850_world->GetCurrentAreaId());\n  }\n  return nullptr;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CStateManager.hpp",
    "content": "#pragma once\n\n#include <list>\n#include <map>\n#include <memory>\n#include <optional>\n#include <set>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"Runtime/CBasics.hpp\"\n#include \"Runtime/CRandom16.hpp\"\n#include \"Runtime/CSortedLists.hpp\"\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Camera/CCameraFilter.hpp\"\n#include \"Runtime/Camera/CCameraManager.hpp\"\n#include \"Runtime/Camera/CCameraShakeData.hpp\"\n#include \"Runtime/GameObjectLists.hpp\"\n#include \"Runtime/Input/CFinalInput.hpp\"\n#include \"Runtime/Input/CRumbleManager.hpp\"\n#include \"Runtime/Weapon/CWeaponMgr.hpp\"\n#include \"Runtime/World/CActorModelParticles.hpp\"\n#include \"Runtime/World/CAi.hpp\"\n#include \"Runtime/World/CEnvFxManager.hpp\"\n#include \"Runtime/World/CFluidPlaneManager.hpp\"\n#include \"Runtime/World/ScriptLoader.hpp\"\n#include \"Runtime/World/ScriptObjectSupport.hpp\"\n#include \"Runtime/World/CScriptMazeNode.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CVector2f.hpp>\n#include <zeus/CVector2i.hpp>\n\nnamespace metaforce {\nclass CActor;\nclass CActorModelParticles;\nclass CDamageInfo;\nclass CEnvFxManager;\nclass CFluidPlaneManager;\nclass CLight;\nclass CMapWorldInfo;\nclass CMaterialFilter;\nclass CObjectList;\nclass CPlayer;\nclass CPlayerState;\nclass CProjectedShadow;\nclass CScriptMailbox;\nclass CRumbleManager;\nclass CSortedListManager;\nclass CTexture;\nclass CWorld;\nclass CScriptLayerManager;\nclass CWorldTransManager;\n\nstruct CFinalInput;\n\nnamespace MP1 {\nclass CMFGameLoader;\n}\n\nstruct SScriptObjectStream {\n  // CEntity* x0_obj;\n  EScriptObjectType x0_type;\n  u32 x4_position;\n  u32 x8_length;\n};\n\nstruct SOnScreenTex {\n  CAssetId x0_id;\n  zeus::CVector2i x4_extent;\n  zeus::CVector2i xc_origin;\n};\n\nenum class EStateManagerTransition { InGame, MapScreen, PauseGame, LogBook, SaveGame, MessageScreen };\n\nenum class EThermalDrawFlag { Hot, Cold, Bypass };\n\nclass CStateManager {\n  friend class MP1::CMFGameLoader;\n\npublic:\n  enum class EGameState { Running, SoftPaused, Paused };\n\nprivate:\n  s16 x0_nextFreeIndex = 0;\n  std::array<u16, kMaxEntities> x4_idxArr{};\n\n  /*\n  std::unique_ptr<CObjectList> x80c_allObjs;\n  std::unique_ptr<CActorList> x814_actorObjs;\n  std::unique_ptr<CPhysicsActorList> x81c_physActorObjs;\n  std::unique_ptr<CGameCameraList> x824_cameraObjs;\n  std::unique_ptr<CGameLightList> x82c_lightObjs;\n  std::unique_ptr<CListeningAiList> x834_listenAiObjs;\n  std::unique_ptr<CAiWaypointList> x83c_aiWaypointObjs;\n  std::unique_ptr<CPlatformAndDoorList> x844_platformAndDoorObjs;\n   */\n  std::array<std::unique_ptr<CObjectList>, 8> x808_objLists{\n      std::make_unique<CObjectList>(EGameObjectList::All),\n      std::make_unique<CActorList>(),\n      std::make_unique<CPhysicsActorList>(),\n      std::make_unique<CGameCameraList>(),\n      std::make_unique<CGameLightList>(),\n      std::make_unique<CListeningAiList>(),\n      std::make_unique<CAiWaypointList>(),\n      std::make_unique<CPlatformAndDoorList>(),\n  };\n\n  std::unique_ptr<CPlayer> x84c_player;\n  std::unique_ptr<CWorld> x850_world;\n\n  /* Used to be a list of 32-element reserved_vectors */\n  std::vector<TUniqueId> x854_objectGraveyard;\n\n  struct CStateManagerContainer {\n    CCameraManager x0_cameraManager;\n    CSortedListManager x3c0_sortedListManager;\n    CWeaponMgr xe3d8_weaponManager;\n    CFluidPlaneManager xe3ec_fluidPlaneManager;\n    CEnvFxManager xe510_envFxManager;\n    CActorModelParticles xf168_actorModelParticles;\n    CRumbleManager xf250_rumbleManager;\n    u32 xf344_ = 0;\n    rstl::reserved_vector<TUniqueId, 20> xf370_;\n    rstl::reserved_vector<TUniqueId, 20> xf39c_renderLast;\n  };\n  std::unique_ptr<CStateManagerContainer> x86c_stateManagerContainer;\n  CCameraManager* x870_cameraManager = nullptr;\n  CSortedListManager* x874_sortedListManager = nullptr;\n  CWeaponMgr* x878_weaponManager = nullptr;\n  CFluidPlaneManager* x87c_fluidPlaneManager = nullptr;\n  CEnvFxManager* x880_envFxManager = nullptr;\n  CActorModelParticles* x884_actorModelParticles = nullptr;\n  CRumbleManager* x88c_rumbleManager = nullptr;\n\n  std::multimap<TEditorId, TUniqueId> x890_scriptIdMap;\n  std::map<TEditorId, SScriptObjectStream> x8a4_loadedScriptObjects;\n\n  std::shared_ptr<CPlayerState> x8b8_playerState;\n  std::shared_ptr<CScriptMailbox> x8bc_mailbox;\n  std::shared_ptr<CMapWorldInfo> x8c0_mapWorldInfo;\n  std::shared_ptr<CWorldTransManager> x8c4_worldTransManager;\n  std::shared_ptr<CScriptLayerManager> x8c8_worldLayerState;\n\n  TAreaId x8cc_nextAreaId = 0;\n  TAreaId x8d0_prevAreaId = kInvalidAreaId;\n  // u32 x8d0_extFrameIdx = 0;\n  u32 x8d4_inputFrameIdx = 0;\n  u32 x8d8_updateFrameIdx = 0;\n  u32 x8dc_objectDrawToken = 0;\n\n  std::vector<CLight> x8e0_dynamicLights;\n\n  TLockedToken<CTexture> x8f0_shadowTex; /* DefaultShadow in MiscData */\n  CRandom16 x8fc_random;\n  CRandom16* x900_activeRandom = nullptr;\n  EGameState x904_gameState = EGameState::Running;\n  rstl::reserved_vector<FScriptLoader, size_t(EScriptObjectType::ScriptObjectTypeMAX)> x90c_loaderFuncs;\n\n  enum class EInitPhase { LoadWorld, LoadFirstArea, Done } xb3c_initPhase = EInitPhase::LoadWorld;\n\n  std::set<std::string> xb40_uniqueInstanceNames;\n\n  CFinalInput xb54_finalInput;\n\n  static constexpr size_t numCameraPasses = 9;\n  std::array<CCameraFilterPass, numCameraPasses> xb84_camFilterPasses; // size: 0x2c\n  std::array<CCameraBlurPass, numCameraPasses> xd14_camBlurPasses;     // size: 0x34\n\n  s32 xeec_hintIdx = -1;\n  u32 xef0_hintPeriods = 0;\n  SOnScreenTex xef4_pendingScreenTex;\n  CAssetId xf08_pauseHudMessage;\n  float xf0c_escapeTimer = 0.f;\n  float xf10_escapeTotalTime = 0.f;\n  float xf14_curTimeMod900 = 0.f;\n  TUniqueId xf18_bossId = kInvalidUniqueId;\n  float xf1c_totalBossEnergy = 0.f;\n  u32 xf20_bossStringIdx = 0;\n  float xf24_thermColdScale1 = 0.f;\n  float xf28_thermColdScale2 = 0.f;\n  zeus::CVector2f xf2c_viewportScale = {1.f, 1.f};\n  EThermalDrawFlag xf34_thermalFlag = EThermalDrawFlag::Bypass;\n  TUniqueId xf38_skipCineSpecialFunc = kInvalidUniqueId;\n  std::list<TUniqueId> xf3c_activeFlickerBats;\n  std::list<TUniqueId> xf54_activeParasites;\n  TUniqueId xf6c_playerActorHead = kInvalidUniqueId;\n  std::unique_ptr<CMazeState> xf70_currentMaze;\n\n  TUniqueId xf74_lastTrigger = kInvalidUniqueId;\n  TUniqueId xf76_lastRelay = kInvalidUniqueId;\n\n  float xf78_hudMessageTime = 0.f;\n  CProjectedShadow* xf7c_projectedShadow = nullptr;\n  u32 xf80_hudMessageFrameCount = 0;\n  s32 xf84_ = -1;\n  CAssetId xf88_;\n  float xf8c_ = 0.f;\n  EStateManagerTransition xf90_deferredTransition = EStateManagerTransition::InGame;\n  bool xf94_24_readyToRender : 1 = false;\n  bool xf94_25_quitGame : 1 = false;\n  bool xf94_26_generatingObject : 1 = false;\n  bool xf94_27_inMapScreen : 1 = false;\n  bool xf94_28_inSaveUI : 1 = false;\n  bool xf94_29_cinematicPause : 1 = false;\n  bool xf94_30_fullThreat : 1 = false;\n\n  bool m_warping = false;\n  std::map<TEditorId, std::set<SConnection>> m_incomingConnections;\n\n  bool m_logScripting = false;\n  std::optional<CVarValueReference<bool>> m_logScriptingReference;\n  void UpdateThermalVisor();\n  static void RendererDrawCallback(void*, void*, int);\n\npublic:\n  CStateManager(const std::weak_ptr<CScriptMailbox>&, const std::weak_ptr<CMapWorldInfo>&,\n                const std::weak_ptr<CPlayerState>&, const std::weak_ptr<CWorldTransManager>&,\n                const std::weak_ptr<CScriptLayerManager>&);\n  ~CStateManager();\n\n  u32 GetInputFrameIdx() const { return x8d4_inputFrameIdx; }\n  bool RenderLast(TUniqueId);\n  void AddDrawableActorPlane(CActor& actor, const zeus::CPlane&, const zeus::CAABox& aabb) const;\n  void AddDrawableActor(CActor& actor, const zeus::CVector3f& vec, const zeus::CAABox& aabb) const;\n  bool SpecialSkipCinematic();\n  TAreaId GetVisAreaId() const;\n  s32 GetWeaponIdCount(TUniqueId, EWeaponType) const;\n  void RemoveWeaponId(TUniqueId, EWeaponType);\n  void AddWeaponId(TUniqueId, EWeaponType);\n  void UpdateEscapeSequenceTimer(float);\n  float GetEscapeSequenceTimer() const { return xf0c_escapeTimer; }\n  void ResetEscapeSequenceTimer(float);\n  void SetupParticleHook(const CActor& actor) const;\n  void MurderScriptInstanceNames();\n  std::string HashInstanceName(CInputStream& in);\n  void SetActorAreaId(CActor& actor, TAreaId);\n  void TouchSky() const;\n  void TouchPlayerActor();\n  void DrawSpaceWarp(const zeus::CVector3f&, float) const;\n  void DrawReflection(const zeus::CVector3f&);\n  static void ReflectionDrawer(void*, const zeus::CVector3f&);\n  void CacheReflection();\n  bool CanCreateProjectile(TUniqueId, EWeaponType, int) const;\n  const std::vector<CLight>& GetDynamicLightList() const { return x8e0_dynamicLights; }\n  void BuildDynamicLightListForWorld();\n  void DrawDebugStuff() const;\n  void RenderCamerasAndAreaLights();\n  void DrawE3DeathEffect();\n  void DrawAdditionalFilters();\n  zeus::CFrustum SetupDrawFrustum(const CViewport& vp) const;\n  zeus::CFrustum SetupViewForDraw(const CViewport& vp) const;\n  void ResetViewAfterDraw(const CViewport& backupViewport, const zeus::CTransform& backupViewMatrix) const;\n  void DrawWorld();\n  void SetupFogForArea3XRange(TAreaId area) const;\n  void SetupFogForArea(TAreaId area) const;\n  void SetupFogForAreaNonCurrent(TAreaId area) const;\n  void SetupFogForArea3XRange(const CGameArea& area) const;\n  void SetupFogForArea(const CGameArea& area) const;\n  void SetupFogForAreaNonCurrent(const CGameArea& area) const;\n  bool SetupFogForDraw() const;\n  void PreRender();\n  void GetCharacterRenderMaskAndTarget(bool thawed, int& mask, int& target) const;\n  bool GetVisSetForArea(TAreaId, TAreaId, CPVSVisSet& setOut) const;\n  void RecursiveDrawTree(TUniqueId);\n  void SendScriptMsg(CEntity* dest, TUniqueId src, EScriptObjectMessage msg);\n  void SendScriptMsg(TUniqueId dest, TUniqueId src, EScriptObjectMessage msg);\n  void SendScriptMsg(TUniqueId src, TEditorId dest, EScriptObjectMessage msg, EScriptObjectState state);\n  void SendScriptMsgAlways(TUniqueId dest, TUniqueId src, EScriptObjectMessage);\n  void FreeScriptObjects(TAreaId);\n  void FreeScriptObject(TUniqueId);\n  std::pair<const SScriptObjectStream*, TEditorId> GetBuildForScript(TEditorId) const;\n  TEditorId GetEditorIdForUniqueId(TUniqueId) const;\n  TUniqueId GetIdForScript(TEditorId) const;\n  std::pair<std::multimap<TEditorId, TUniqueId>::const_iterator, std::multimap<TEditorId, TUniqueId>::const_iterator>\n      GetIdListForScript(TEditorId) const;\n  std::multimap<TEditorId, TUniqueId>::const_iterator GetIdListEnd() const { return x890_scriptIdMap.cend(); }\n  void LoadScriptObjects(TAreaId, CInputStream& in, std::vector<TEditorId>& idsOut);\n  void InitializeScriptObjects(const std::vector<TEditorId>& objIds);\n  std::pair<TEditorId, TUniqueId> LoadScriptObject(TAreaId, EScriptObjectType, u32, CInputStream& in);\n  std::pair<TEditorId, TUniqueId> GenerateObject(TEditorId);\n  void InitScriptObjects(const std::vector<TEditorId>& ids);\n  void InformListeners(const zeus::CVector3f&, EListenNoiseType);\n  void ApplyKnockBack(CActor& actor, const CDamageInfo& info, const CDamageVulnerability&, const zeus::CVector3f&,\n                      float);\n  void KnockBackPlayer(CPlayer& player, const zeus::CVector3f& pos, float power, float resistance);\n  void ApplyDamageToWorld(TUniqueId, const CActor&, const zeus::CVector3f&, const CDamageInfo& info,\n                          const CMaterialFilter&);\n  void ProcessRadiusDamage(const CActor&, CActor&, TUniqueId senderId, const CDamageInfo& info, const CMaterialFilter&);\n  void ApplyRadiusDamage(const CActor&, const zeus::CVector3f&, CActor&, const CDamageInfo& info);\n  bool TestRayDamage(const zeus::CVector3f& pos, const CActor& damagee, const EntityList& nearList) const;\n  bool RayCollideWorld(const zeus::CVector3f& start, const zeus::CVector3f& end, const CMaterialFilter& filter,\n                       const CActor* damagee) const;\n  bool RayCollideWorld(const zeus::CVector3f& start, const zeus::CVector3f& end, const EntityList& nearList,\n                       const CMaterialFilter& filter, const CActor* damagee) const;\n  bool RayCollideWorldInternal(const zeus::CVector3f& start, const zeus::CVector3f& end, const CMaterialFilter& filter,\n                               const EntityList& nearList, const CActor* damagee) const;\n  bool MultiRayCollideWorld(const zeus::CMRay& ray, const CMaterialFilter& filter) const;\n  void TestBombHittingWater(const CActor& damager, const zeus::CVector3f& pos, CActor& damagee);\n  bool ApplyLocalDamage(const zeus::CVector3f&, const zeus::CVector3f&, CActor&, float, const CWeaponMode&);\n  bool ApplyDamage(TUniqueId damagerId, TUniqueId damageeId, TUniqueId radiusSender, const CDamageInfo& info,\n                   const CMaterialFilter& filter, const zeus::CVector3f& knockbackVec);\n  void UpdateAreaSounds();\n  void FrameEnd();\n  void ProcessPlayerInput();\n  void SetGameState(EGameState state);\n  EGameState GetGameState() const { return x904_gameState; }\n  void ProcessInput(const CFinalInput& input);\n  void UpdateGraphicsTiming(float dt);\n  void Update(float dt);\n  void UpdateGameState();\n  void UpdateHintState(float dt);\n  void PreThinkObjects(float dt);\n  void MovePlatforms(float dt);\n  void MoveActors(float dt);\n  void CrossTouchActors();\n  void Think(float dt);\n  void PostUpdatePlayer(float dt);\n  void ShowPausedHUDMemo(CAssetId strg, float time);\n  void ClearGraveyard();\n  void FrameBegin(s32 frameCount);\n  void InitializeState(CAssetId mlvlId, TAreaId aid, CAssetId mreaId);\n  void CreateStandardGameObjects();\n  const std::unique_ptr<CObjectList>& GetObjectList() const { return x808_objLists[0]; }\n  CObjectList* ObjectListById(EGameObjectList type);\n  const CObjectList* GetObjectListById(EGameObjectList type) const;\n  void RemoveObject(TUniqueId);\n  void UpdateRoomAcoustics(TAreaId);\n  TAreaId GetNextAreaId() const { return x8cc_nextAreaId; }\n  void SetCurrentAreaId(TAreaId);\n  CEntity* ObjectById(TUniqueId uid) const { return GetAllObjectList().GetObjectById(uid); }\n  const CEntity* GetObjectById(TUniqueId uid) const { return GetAllObjectList().GetObjectById(uid); }\n  void AreaUnloaded(TAreaId);\n  void PrepareAreaUnload(TAreaId);\n  void AreaLoaded(TAreaId);\n  void BuildNearList(EntityList& listOut, const zeus::CVector3f&, const zeus::CVector3f&, float, const CMaterialFilter&,\n                     const CActor*) const;\n  void BuildColliderList(EntityList& listOut, const CActor&, const zeus::CAABox&) const;\n  void BuildNearList(EntityList& listOut, const zeus::CAABox&, const CMaterialFilter&, const CActor*) const;\n  void UpdateActorInSortedLists(CActor&);\n  void UpdateSortedLists();\n  std::optional<zeus::CAABox> CalculateObjectBounds(const CActor&);\n  void AddObject(CEntity&);\n  void AddObject(CEntity*);\n  CRayCastResult RayStaticIntersection(const zeus::CVector3f& pos, const zeus::CVector3f& dir, float length,\n                                       const CMaterialFilter& filter) const;\n  CRayCastResult RayWorldIntersection(TUniqueId& idOut, const zeus::CVector3f& pos, const zeus::CVector3f& dir,\n                                      float length, const CMaterialFilter& filter, const EntityList& list) const;\n  void UpdateObjectInLists(CEntity&);\n  TUniqueId AllocateUniqueId();\n  void DeferStateTransition(EStateManagerTransition t);\n  EStateManagerTransition GetDeferredStateTransition() const { return xf90_deferredTransition; }\n  bool CanShowMapScreen() const;\n  TUniqueId GetSkipCinematicSpecialFunction() const { return xf38_skipCineSpecialFunc; }\n  void SetSkipCinematicSpecialFunction(TUniqueId id) { xf38_skipCineSpecialFunc = id; }\n  float GetHUDMessageTime() const { return xf78_hudMessageTime; }\n  u32 GetHUDMessageFrameCount() const { return xf80_hudMessageFrameCount; }\n  CAssetId GetPauseHUDMessage() const { return xf08_pauseHudMessage; }\n  void IncrementHUDMessageFrameCounter() { ++xf80_hudMessageFrameCount; }\n  bool ShouldQuitGame() const { return xf94_25_quitGame; }\n  void SetShouldQuitGame(bool should) { xf94_25_quitGame = should; }\n  void SetInSaveUI(bool b) { xf94_28_inSaveUI = b; }\n  bool GetInSaveUI() const { return xf94_28_inSaveUI; }\n  void SetInMapScreen(bool b) { xf94_27_inMapScreen = b; }\n  bool GetInMapScreen() const { return xf94_27_inMapScreen; }\n  bool IsFullThreat() const { return xf94_30_fullThreat; }\n  void SetIsFullThreat(bool v) { xf94_30_fullThreat = v; }\n\n  const std::shared_ptr<CPlayerState>& GetPlayerState() const { return x8b8_playerState; }\n  CRandom16* GetActiveRandom() { return x900_activeRandom; }\n  const CRandom16* GetActiveRandom() const { return x900_activeRandom; }\n  zeus::CVector3f Random2f(float scaleMin, float scaleMax);\n  void SetActiveRandomToDefault() { x900_activeRandom = &x8fc_random; }\n  void ClearActiveRandom() { x900_activeRandom = nullptr; }\n  CRumbleManager& GetRumbleManager() { return *x88c_rumbleManager; }\n  const CRumbleManager& GetRumbleManager() const { return *x88c_rumbleManager; }\n  CCameraFilterPass& GetCameraFilterPass(int idx) { return xb84_camFilterPasses[idx]; }\n  const CCameraFilterPass& GetCameraFilterPass(int idx) const { return xb84_camFilterPasses[idx]; }\n  CCameraBlurPass& GetCameraBlurPass(int idx) { return xd14_camBlurPasses[idx]; }\n  const CCameraBlurPass& GetCameraBlurPass(int idx) const { return xd14_camBlurPasses[idx]; }\n\n  CEnvFxManager* GetEnvFxManager() { return x880_envFxManager; }\n  const CEnvFxManager* GetEnvFxManager() const { return x880_envFxManager; }\n  CWorld* GetWorld() { return x850_world.get(); }\n  const CWorld* GetWorld() const { return x850_world.get(); }\n  CScriptMailbox* GetMailbox() { return x8bc_mailbox.get(); }\n  const CScriptMailbox* GetRelayTracker() const { return x8bc_mailbox.get(); }\n  CCameraManager* GetCameraManager() const { return x870_cameraManager; }\n  CFluidPlaneManager* GetFluidPlaneManager() const { return x87c_fluidPlaneManager; }\n  CActorModelParticles* GetActorModelParticles() const { return x884_actorModelParticles; }\n\n  const std::shared_ptr<CMapWorldInfo>& MapWorldInfo() const { return x8c0_mapWorldInfo; }\n  const std::shared_ptr<CWorldTransManager>& WorldTransManager() const { return x8c4_worldTransManager; }\n  const std::shared_ptr<CScriptLayerManager>& WorldLayerState() const { return x8c8_worldLayerState; }\n  std::shared_ptr<CScriptLayerManager>& WorldLayerState() { return x8c8_worldLayerState; }\n\n  CPlayer& GetPlayer() const { return *x84c_player; }\n  CPlayer* Player() const { return x84c_player.get(); }\n\n  CObjectList& GetAllObjectList() const { return *x808_objLists[0]; }\n  CActorList& GetActorObjectList() const { return static_cast<CActorList&>(*x808_objLists[1]); }\n  CPhysicsActorList& GetPhysicsActorObjectList() const { return static_cast<CPhysicsActorList&>(*x808_objLists[2]); }\n  CGameCameraList& GetCameraObjectList() const { return static_cast<CGameCameraList&>(*x808_objLists[3]); }\n  CGameLightList& GetLightObjectList() const { return static_cast<CGameLightList&>(*x808_objLists[4]); }\n  CListeningAiList& GetListeningAiObjectList() const { return static_cast<CListeningAiList&>(*x808_objLists[5]); }\n  CAiWaypointList& GetAiWaypointObjectList() const { return static_cast<CAiWaypointList&>(*x808_objLists[6]); }\n  CPlatformAndDoorList& GetPlatformAndDoorObjectList() const {\n    return static_cast<CPlatformAndDoorList&>(*x808_objLists[7]);\n  }\n  std::pair<u32, u32> CalculateScanCompletionRate() const;\n  void SetCurrentMaze(std::unique_ptr<CMazeState> maze) { xf70_currentMaze = std::move(maze); }\n  void ClearCurrentMaze() { xf70_currentMaze.reset(); }\n  CMazeState* GetCurrentMaze() { return xf70_currentMaze.get(); }\n  void SetLastTriggerId(TUniqueId uid) { xf74_lastTrigger = uid; }\n  TUniqueId GetLastTriggerId() const { return xf74_lastTrigger; }\n  void SetLastRelayId(TUniqueId uid) { xf76_lastRelay = uid; }\n  TUniqueId* GetLastRelayIdPtr() { return &xf76_lastRelay; }\n  TUniqueId GetLastRelayId() const { return xf76_lastRelay; }\n  bool GetIsGeneratingObject() const { return xf94_26_generatingObject; }\n  void SetIsGeneratingObject(bool gen) { xf94_26_generatingObject = gen; }\n  EThermalDrawFlag GetThermalDrawFlag() const { return xf34_thermalFlag; }\n  const CFinalInput& GetFinalInput() const { return xb54_finalInput; }\n  void SetBossParams(TUniqueId bossId, float maxEnergy, u32 stringIdx);\n  TUniqueId GetBossId() const { return xf18_bossId; }\n  float GetTotalBossEnergy() const { return xf1c_totalBossEnergy; }\n  u32 GetBossStringIdx() const { return xf20_bossStringIdx; }\n  void SetPendingOnScreenTex(CAssetId texId, const zeus::CVector2i& origin, const zeus::CVector2i& extent) {\n    xef4_pendingScreenTex.x0_id = texId;\n    xef4_pendingScreenTex.x4_extent = origin;\n    xef4_pendingScreenTex.xc_origin = extent;\n  }\n  const SOnScreenTex& GetPendingScreenTex() const { return xef4_pendingScreenTex; }\n  void SetViewportScale(const zeus::CVector2f& scale) { xf2c_viewportScale = scale; }\n  float GetThermalColdScale1() const { return xf24_thermColdScale1; }\n  float GetThermalColdScale2() const { return xf28_thermColdScale2; }\n  void SetThermalColdScale2(float s) { xf28_thermColdScale2 = s; }\n  float IntegrateVisorFog(float f) const;\n  u32 GetUpdateFrameIndex() const { return x8d8_updateFrameIdx; }\n  void SetCinematicPause(bool p) { xf94_29_cinematicPause = p; }\n  void QueueMessage(u32 frameCount, CAssetId msg, float f1) {\n    xf84_ = frameCount;\n    xf88_ = msg;\n    xf8c_ = f1;\n  }\n  TUniqueId GetPlayerActorHead() const { return xf6c_playerActorHead; }\n  void SetPlayerActorHead(TUniqueId id) { xf6c_playerActorHead = id; }\n  std::list<TUniqueId>& GetActiveFlickerBats() { return xf3c_activeFlickerBats; }\n  std::list<TUniqueId>& GetActiveParasites() { return xf54_activeParasites; }\n  static float g_EscapeShakeCountdown;\n  static bool g_EscapeShakeCountdownInit;\n\n  void sub_80044098(const CCollisionResponseData& colRespData, const CRayCastResult& rayCast, TUniqueId uid,\n                    const CWeaponMode& weaponMode, u32 w1, u8 thermalFlags);\n\n  const CGameArea* GetCurrentArea() const;\n  void SetWarping(bool warp) { m_warping = warp; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CStaticInterference.cpp",
    "content": "#include \"Runtime/CStaticInterference.hpp\"\n\n#include <zeus/Math.hpp>\n\nnamespace metaforce {\n\nCStaticInterference::CStaticInterference(size_t sourceCount) { x0_sources.reserve(sourceCount); }\n\nvoid CStaticInterference::RemoveSource(TUniqueId id) {\n  const auto iter =\n      std::find_if(x0_sources.cbegin(), x0_sources.cend(), [id](const auto& src) { return src.x0_id == id; });\n\n  if (iter == x0_sources.cend()) {\n    return;\n  }\n\n  x0_sources.erase(iter);\n}\n\nvoid CStaticInterference::Update(CStateManager&, float dt) {\n  std::vector<CStaticInterferenceSource> newSources;\n  newSources.reserve(x0_sources.capacity());\n  for (CStaticInterferenceSource& src : x0_sources) {\n    if (src.x8_timeLeft >= 0.f) {\n      src.x8_timeLeft -= dt;\n      newSources.push_back(src);\n    }\n  }\n  x0_sources = std::move(newSources);\n}\n\nfloat CStaticInterference::GetTotalInterference() const {\n  float validAccum = 0.f;\n  float invalidAccum = 0.f;\n  for (const CStaticInterferenceSource& src : x0_sources) {\n    if (src.x0_id == kInvalidUniqueId)\n      invalidAccum += src.x4_magnitude;\n    else\n      validAccum += src.x4_magnitude;\n  }\n  if (validAccum > 0.80000001f)\n    validAccum = 0.80000001f;\n  validAccum += invalidAccum;\n  if (validAccum > 1.f)\n    return 1.f;\n  return validAccum;\n}\n\nvoid CStaticInterference::AddSource(TUniqueId id, float magnitude, float duration) {\n  magnitude = zeus::clamp(0.f, magnitude, 1.f);\n  const auto search = std::find_if(x0_sources.begin(), x0_sources.end(),\n                                   [id](const CStaticInterferenceSource& source) { return source.x0_id == id; });\n  if (search != x0_sources.cend()) {\n    search->x4_magnitude = magnitude;\n    search->x8_timeLeft = duration;\n    return;\n  }\n\n  if (x0_sources.size() < x0_sources.capacity()) {\n    x0_sources.push_back({id, magnitude, duration});\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CStaticInterference.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include \"Runtime/RetroTypes.hpp\"\n\nnamespace metaforce {\nclass CStateManager;\n\nstruct CStaticInterferenceSource {\n  TUniqueId x0_id;\n  float x4_magnitude;\n  float x8_timeLeft;\n};\n\nclass CStaticInterference {\n  std::vector<CStaticInterferenceSource> x0_sources;\n\npublic:\n  explicit CStaticInterference(size_t sourceCount);\n  void RemoveSource(TUniqueId id);\n  void Update(CStateManager&, float dt);\n  float GetTotalInterference() const;\n  void AddSource(TUniqueId id, float magnitude, float duration);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CStopwatch.cpp",
    "content": "#include \"Runtime/CStopwatch.hpp\"\n\nnamespace metaforce {\nCStopwatch CStopwatch::mGlobalTimer = {};\n\nfloat CStopwatch::GetElapsedTime() const {\n  return static_cast<float>(std::chrono::duration_cast<MilliSeconds>(Time::now() - m_startTime).count()) / 1000.f;\n}\n\nu16 CStopwatch::GetElapsedMicros() const {\n  return std::chrono::duration_cast<MicroSeconds>(Time::now() - m_startTime).count();\n}\n\nu64 CStopwatch::GetCurMicros() const {\n  return std::chrono::duration_cast<MicroSeconds>(Time::now().time_since_epoch()).count();\n}\n\nvoid CStopwatch::InitGlobalTimer() { mGlobalTimer.Reset(); }\n\nvoid CStopwatch::Reset() { m_startTime = std::chrono::steady_clock::now(); }\n\nvoid CStopwatch::Wait(float wait) {\n  if (std::fabs(wait) < 0.001f) {\n    wait = 0.f;\n  }\n\n  auto waitDur = FloatSeconds{wait};\n  while ((Time::now() - m_startTime) < waitDur) {}\n}\n} // namespace metaforce"
  },
  {
    "path": "Runtime/CStopwatch.hpp",
    "content": "#pragma once\n\n#include \"Runtime/GCNTypes.hpp\"\n\n#include <cmath>\n#include <chrono>\n\nnamespace metaforce {\nclass CStopwatch {\nprivate:\n  static CStopwatch mGlobalTimer;\n\n  using Time = std::chrono::steady_clock;\n  using MicroSeconds = std::chrono::microseconds;\n  using MilliSeconds = std::chrono::milliseconds;\n  using FloatSeconds = std::chrono::duration<float>;\n  Time::time_point m_startTime;\n\npublic:\n  static void InitGlobalTimer();\n  static CStopwatch& GetGlobalTimerObj() { return mGlobalTimer; }\n  static float GetGlobalTime() { return mGlobalTimer.GetElapsedTime(); }\n\n  void Reset();\n  void Wait(float wait);\n\n  float GetElapsedTime() const;\n  u16 GetElapsedMicros() const;\n  u64 GetCurMicros() const;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CStringExtras.cpp",
    "content": "#include \"Runtime/CStringExtras.hpp\"\n#include \"Runtime/Streams/CInputStream.hpp\"\n\n#include <cstdint>\n\nnamespace {\n\nconstexpr char32_t kReplacementChar = 0xFFFD;\n\nvoid AppendUTF8(char32_t codePoint, std::string& out) {\n  if (codePoint <= 0x7F) {\n    out.push_back(static_cast<char>(codePoint));\n  } else if (codePoint <= 0x7FF) {\n    out.push_back(static_cast<char>(0xC0 | ((codePoint >> 6) & 0x1F)));\n    out.push_back(static_cast<char>(0x80 | (codePoint & 0x3F)));\n  } else if (codePoint <= 0xFFFF) {\n    out.push_back(static_cast<char>(0xE0 | ((codePoint >> 12) & 0x0F)));\n    out.push_back(static_cast<char>(0x80 | ((codePoint >> 6) & 0x3F)));\n    out.push_back(static_cast<char>(0x80 | (codePoint & 0x3F)));\n  } else {\n    out.push_back(static_cast<char>(0xF0 | ((codePoint >> 18) & 0x07)));\n    out.push_back(static_cast<char>(0x80 | ((codePoint >> 12) & 0x3F)));\n    out.push_back(static_cast<char>(0x80 | ((codePoint >> 6) & 0x3F)));\n    out.push_back(static_cast<char>(0x80 | (codePoint & 0x3F)));\n  }\n}\n\nvoid AppendUTF16(char32_t codePoint, std::u16string& out) {\n  if (codePoint <= 0xFFFF) {\n    out.push_back(static_cast<char16_t>(codePoint));\n    return;\n  }\n\n  codePoint -= 0x10000;\n  out.push_back(static_cast<char16_t>(0xD800 + (codePoint >> 10)));\n  out.push_back(static_cast<char16_t>(0xDC00 + (codePoint & 0x3FF)));\n}\n\n} // namespace\n\nnamespace metaforce {\nstd::string CStringExtras::ReadString(CInputStream& in) {\n  u32 strLen = in.ReadLong();\n  std::string ret;\n  u32 readLen = 512;\n  char tmp[512] = {};\n  for (; strLen > 0; strLen -= readLen) {\n    readLen = 512;\n    if (strLen <= 512) {\n      readLen = strLen;\n    }\n    in.ReadBytes(tmp, readLen);\n    ret.append(tmp, readLen);\n  }\n\n  return ret;\n}\n\nstd::string CStringExtras::ConvertToANSI(std::u16string_view sv) {\n  std::string out;\n  out.reserve(sv.size());\n  for (const char16_t c : sv) {\n    out.push_back(static_cast<char>(c));\n  }\n  return out;\n}\n\nstd::u16string CStringExtras::ConvertToUNICODE(std::string_view sv) {\n  std::u16string out;\n  out.reserve(sv.size());\n  for (const char c : sv) {\n    out.push_back(static_cast<char16_t>(c));\n  }\n  return out;\n}\n\nstd::string CStringExtras::ConvertToUTF8(std::u16string_view sv) {\n  std::string out;\n  out.reserve(sv.size());\n\n  for (size_t i = 0; i < sv.size(); ++i) {\n    char32_t codePoint = sv[i];\n    if (codePoint >= 0xD800 && codePoint <= 0xDBFF) {\n      if (i + 1 < sv.size()) {\n        const char32_t low = sv[i + 1];\n        if (low >= 0xDC00 && low <= 0xDFFF) {\n          codePoint = 0x10000 + ((codePoint - 0xD800) << 10) + (low - 0xDC00);\n          ++i;\n        } else {\n          codePoint = kReplacementChar;\n        }\n      } else {\n        codePoint = kReplacementChar;\n      }\n    } else if (codePoint >= 0xDC00 && codePoint <= 0xDFFF) {\n      codePoint = kReplacementChar;\n    }\n\n    AppendUTF8(codePoint, out);\n  }\n  return out;\n}\n\nstd::u16string CStringExtras::ConvertToUTF16(std::string_view sv) {\n  std::u16string out;\n  out.reserve(sv.size());\n\n  const auto* bytes = reinterpret_cast<const uint8_t*>(sv.data());\n  const size_t len = sv.size();\n  size_t i = 0;\n  while (i < len) {\n    const uint8_t c0 = bytes[i++];\n    char32_t codePoint = kReplacementChar;\n\n    if (c0 < 0x80) {\n      codePoint = c0;\n    } else if ((c0 & 0xE0) == 0xC0) {\n      if (i < len) {\n        const uint8_t c1 = bytes[i];\n        if ((c1 & 0xC0) == 0x80) {\n          const char32_t cp = ((c0 & 0x1F) << 6) | (c1 & 0x3F);\n          if (cp >= 0x80) {\n            codePoint = cp;\n            ++i;\n          }\n        }\n      }\n    } else if ((c0 & 0xF0) == 0xE0) {\n      if (i + 1 < len) {\n        const uint8_t c1 = bytes[i];\n        const uint8_t c2 = bytes[i + 1];\n        if ((c1 & 0xC0) == 0x80 && (c2 & 0xC0) == 0x80) {\n          const char32_t cp = ((c0 & 0x0F) << 12) | ((c1 & 0x3F) << 6) | (c2 & 0x3F);\n          if (cp >= 0x800 && !(cp >= 0xD800 && cp <= 0xDFFF)) {\n            codePoint = cp;\n            i += 2;\n          }\n        }\n      }\n    } else if ((c0 & 0xF8) == 0xF0) {\n      if (i + 2 < len) {\n        const uint8_t c1 = bytes[i];\n        const uint8_t c2 = bytes[i + 1];\n        const uint8_t c3 = bytes[i + 2];\n        if ((c1 & 0xC0) == 0x80 && (c2 & 0xC0) == 0x80 && (c3 & 0xC0) == 0x80) {\n          const char32_t cp =\n              ((c0 & 0x07) << 18) | ((c1 & 0x3F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F);\n          if (cp >= 0x10000 && cp <= 0x10FFFF) {\n            codePoint = cp;\n            i += 3;\n          }\n        }\n      }\n    }\n\n    AppendUTF16(codePoint, out);\n  }\n  return out;\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CStringExtras.hpp",
    "content": "#pragma once\n\n#include <algorithm>\n#include <cctype>\n#include <string>\n#include <sstream>\n#include <vector>\n\nnamespace metaforce {\nclass CInputStream;\nclass CStringExtras {\npublic:\n  static std::string ConvertToANSI(std::u16string_view sv);\n  static std::u16string ConvertToUNICODE(std::string_view sv);\n  // Metaforce addition: UTF-8/16 compatible versions of the above\n  static std::string ConvertToUTF8(std::u16string_view sv);\n  static std::u16string ConvertToUTF16(std::string_view sv);\n\n  // Checks if the provided views into string data can be considered equal or not based on\n  // whether or not all their characters are equal to one another in a character insensitive manner.\n  //\n  // NOTE: This differs slightly from the actual version of this function within the game executable\n  //       in order to better accomodate string views and potentially non-null-terminated string data.\n  //\n  //       In the game executable, the function essentially behaves like strcasecmp in that it returns\n  //       an int indicating whether or not the first argument is less than, equal to,\n  //       or greater than the second argument. Given no usages in the code depend on the less than or\n  //       greater than cases, but rather just care about whether or not the strings are equal to one\n  //       another, this is a safe change to make.\n  //\n  static bool CompareCaseInsensitive(std::string_view a, std::string_view b) {\n    return std::equal(a.begin(), a.end(), b.begin(), b.end(),\n                      [](char lhs, char rhs) { return std::tolower(lhs) == std::tolower(rhs); });\n  }\n\n  static int IndexOfSubstring(std::string_view haystack, std::string_view needle) {\n    std::string str(haystack);\n    std::transform(str.begin(), str.end(), str.begin(),\n                   [](char c) { return std::tolower(static_cast<unsigned char>(c)); });\n    const std::string::size_type s = str.find(needle);\n    if (s == std::string::npos) {\n      return -1;\n    }\n    return s;\n  }\n\n  static inline void ToLower(std::string& str) { std::transform(str.begin(), str.end(), str.begin(), ::tolower); }\n  static std::string ReadString(CInputStream& in);\n  static inline bool ParseBool(std::string_view boolean, bool* valid) {\n    std::string val(boolean);\n    // compare must be case insensitive\n    // This is the cleanest solution since I only need to do it once\n    ToLower(val);\n\n    // Check for true first\n    if (val == \"true\" || val == \"1\" || val == \"yes\" || val == \"on\") {\n      if (valid)\n        *valid = true;\n\n      return true;\n    }\n\n    // Now false\n    if (val == \"false\" || val == \"0\" || val == \"no\" || val == \"off\") {\n      if (valid)\n        *valid = true;\n\n      return false;\n    }\n\n    // Well that could've gone better\n\n    if (valid)\n      *valid = false;\n\n    return false;\n  }\n\n  static inline std::vector<std::string>& Split(std::string_view s, char delim, std::vector<std::string>& elems) {\n    std::string tmps(s);\n    std::stringstream ss(tmps);\n    std::string item;\n\n    while (std::getline(ss, item, delim)) {\n      elems.push_back(item);\n    }\n\n    return elems;\n  }\n\n  static inline std::vector<std::string> Split(std::string_view s, char delim) {\n    std::vector<std::string> elems;\n    Split(s, delim, elems);\n    return elems;\n  }\n\n  static inline std::string LeftTrim(const std::string &s)\n  {\n    size_t start = s.find_first_not_of(\" \\n\\r\\t\\f\\v\");\n    return (start == std::string::npos) ? \"\" : s.substr(start);\n  }\n\n  static inline std::string RightTrim(const std::string &s)\n  {\n    size_t end = s.find_last_not_of(\" \\n\\r\\t\\f\\v\");\n    return (end == std::string::npos) ? \"\" : s.substr(0, end + 1);\n  }\n\n  static inline std::string Trim(const std::string &s) {\n    return RightTrim(LeftTrim(s));\n  }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CTextureCache.cpp",
    "content": "#include \"Runtime/CTextureCache.hpp\"\n#include \"Runtime/CToken.hpp\"\n\nnamespace metaforce {\nCTextureCache::CTextureCache(CInputStream& in) {\n  u32 textureCount = in.ReadLong();\n  for (u32 i = 0; i < textureCount; ++i) {\n    CAssetId uid(in);\n    if (m_textureInfo.find(uid) == m_textureInfo.end())\n      m_textureInfo.emplace(uid, in.Get<CTextureInfo>());\n  }\n}\n\nconst CTextureInfo* CTextureCache::GetTextureInfo(CAssetId id) const {\n  auto it = m_textureInfo.find(id);\n  if (it == m_textureInfo.end())\n    return nullptr;\n  return &it->second;\n}\n\nCFactoryFnReturn FTextureCacheFactory([[maybe_unused]] const SObjectTag& tag, CInputStream& in,\n                                      [[maybe_unused]] const CVParamTransfer& vparms,\n                                      [[maybe_unused]] CObjectReference* selfRef) {\n  return TToken<CTextureCache>::GetIObjObjectFor(std::make_unique<CTextureCache>(in));\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CTextureCache.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Graphics/CTexture.hpp\"\n\n#include <map>\n\nnamespace metaforce {\nclass CPaletteInfo {\n  u32 m_format;\n  u32 m_elementCount;\n  u64 m_dolphinHash;\n\npublic:\n  explicit CPaletteInfo(CInputStream& in)\n  : m_format(in.ReadLong()), m_elementCount(in.ReadLong()), m_dolphinHash(in.ReadLongLong()) {}\n};\nclass CTextureInfo {\n  ETexelFormat m_format;\n  u32 m_mipCount;\n  u16 m_width;\n  u16 m_height;\n  u64 m_dolphinHash;\n  std::optional<CPaletteInfo> m_paletteInfo;\n\npublic:\n  explicit CTextureInfo(CInputStream& in)\n  : m_format(ETexelFormat(in.ReadLong()))\n  , m_mipCount(in.ReadLong())\n  , m_width(in.ReadShort())\n  , m_height(in.ReadShort())\n  , m_dolphinHash(in.ReadLongLong()) {\n    bool hasPal = in.ReadBool();\n    if (hasPal)\n      m_paletteInfo.emplace(in);\n  }\n};\nclass CTextureCache {\npublic:\n  std::map<CAssetId, CTextureInfo> m_textureInfo;\n\npublic:\n  explicit CTextureCache(CInputStream& in);\n\n  const CTextureInfo* GetTextureInfo(CAssetId id) const;\n};\n\nCFactoryFnReturn FTextureCacheFactory(const metaforce::SObjectTag& tag, CInputStream& in,\n                                      const metaforce::CVParamTransfer& vparms, CObjectReference* selfRef);\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CTimeProvider.cpp",
    "content": "#include \"Runtime/CTimeProvider.hpp\"\n\n#include \"Runtime/Graphics/CGraphics.hpp\"\n\nnamespace metaforce {\nstatic CTimeProvider* s_currentTimeProvider = nullptr;\n\nCTimeProvider::CTimeProvider(const float& time) : x0_currentTime(time), x8_lastProvider(s_currentTimeProvider) {\n  if (x8_lastProvider != nullptr) {\n    x8_lastProvider->x4_first = false;\n  }\n\n  s_currentTimeProvider = this;\n\n  CGraphics::SetExternalTimeProvider(this);\n}\n\nCTimeProvider::~CTimeProvider() {\n  s_currentTimeProvider = x8_lastProvider;\n  if (s_currentTimeProvider != nullptr) {\n    s_currentTimeProvider->x4_first = true;\n  }\n  CGraphics::SetExternalTimeProvider(s_currentTimeProvider);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CTimeProvider.hpp",
    "content": "#pragma once\nnamespace metaforce {\nclass CTimeProvider {\npublic:\n  const float& x0_currentTime; // in seconds\n  bool x4_first = true;\n  CTimeProvider* x8_lastProvider = nullptr;\n\n  CTimeProvider(const float& time);\n  ~CTimeProvider();\n  float GetSecondsMod900() const { return x0_currentTime; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CToken.cpp",
    "content": "#include \"Runtime/CToken.hpp\"\n\nnamespace metaforce {\nu16 CObjectReference::RemoveReference() {\n  --x0_refCount;\n  if (x0_refCount == 0) {\n    if (x10_object)\n      Unload();\n    if (IsLoading())\n      CancelLoad();\n    if (xC_objectStore)\n      xC_objectStore->ObjectUnreferenced(x4_objTag);\n  }\n  return x0_refCount;\n}\n\nCObjectReference::CObjectReference(IObjectStore& objStore, std::unique_ptr<IObj>&& obj, const SObjectTag& objTag,\n                                   CVParamTransfer buildParams)\n: x4_objTag(objTag), xC_objectStore(&objStore), x10_object(std::move(obj)), x14_params(std::move(buildParams)) {}\nCObjectReference::CObjectReference(std::unique_ptr<IObj>&& obj) : x10_object(std::move(obj)) {}\n\nvoid CObjectReference::Unlock() {\n  --x2_lockCount;\n  if (x2_lockCount)\n    return;\n  if (x10_object && xC_objectStore)\n    Unload();\n  else if (IsLoading())\n    CancelLoad();\n}\n\nvoid CObjectReference::Lock() {\n  ++x2_lockCount;\n  if (!x10_object && !x3_loading) {\n    IFactory& fac = xC_objectStore->GetFactory();\n    fac.BuildAsync(x4_objTag, x14_params, &x10_object, this);\n    x3_loading = !x10_object.operator bool();\n  }\n}\n\nvoid CObjectReference::CancelLoad() {\n  if ((xC_objectStore != nullptr) && IsLoading()) {\n    xC_objectStore->GetFactory().CancelBuild(x4_objTag);\n    x3_loading = false;\n  }\n}\n\nvoid CObjectReference::Unload() {\n  x10_object.reset();\n  x3_loading = false;\n}\n\nIObj* CObjectReference::GetObject() {\n  if (!x10_object) {\n    IFactory& factory = xC_objectStore->GetFactory();\n    x10_object = factory.Build(x4_objTag, x14_params, this);\n  }\n  x3_loading = false;\n  return x10_object.get();\n}\n\nCObjectReference::~CObjectReference() {\n  if (x10_object)\n    x10_object.reset();\n  else if (x3_loading)\n    xC_objectStore->GetFactory().CancelBuild(x4_objTag);\n}\n\nvoid CToken::RemoveRef() {\n  if (x0_objRef && x0_objRef->RemoveReference() == 0) {\n    std::default_delete<CObjectReference>()(x0_objRef);\n    x0_objRef = nullptr;\n  }\n}\n\nCToken::CToken(CObjectReference* obj) {\n  x0_objRef = obj;\n  ++x0_objRef->x0_refCount;\n}\n\nvoid CToken::Unlock() {\n  if (x0_objRef && x4_lockHeld) {\n    x0_objRef->Unlock();\n    x4_lockHeld = false;\n  }\n}\nvoid CToken::Lock() {\n  if (x0_objRef && !x4_lockHeld) {\n    x0_objRef->Lock();\n    x4_lockHeld = true;\n  }\n}\nbool CToken::IsLoaded() const {\n  if (!x0_objRef || !x4_lockHeld)\n    return false;\n  return x0_objRef->IsLoaded();\n}\nIObj* CToken::GetObj() {\n  if (!x0_objRef)\n    return nullptr;\n  Lock();\n  return x0_objRef->GetObject();\n}\nCToken& CToken::operator=(const CToken& other) {\n  if (this == &other)\n    return *this;\n  Unlock();\n  RemoveRef();\n  x0_objRef = other.x0_objRef;\n  if (x0_objRef) {\n    ++x0_objRef->x0_refCount;\n    if (other.x4_lockHeld)\n      Lock();\n  }\n  return *this;\n}\nCToken& CToken::operator=(CToken&& other) noexcept {\n  Unlock();\n  RemoveRef();\n  x0_objRef = other.x0_objRef;\n  other.x0_objRef = nullptr;\n  x4_lockHeld = other.x4_lockHeld;\n  other.x4_lockHeld = false;\n  return *this;\n}\nCToken::CToken(const CToken& other) : x0_objRef(other.x0_objRef) {\n  if (x0_objRef) {\n    ++x0_objRef->x0_refCount;\n    if (other.x4_lockHeld)\n      Lock();\n  }\n}\nCToken::CToken(CToken&& other) noexcept : x0_objRef(other.x0_objRef), x4_lockHeld(other.x4_lockHeld) {\n  other.x0_objRef = nullptr;\n  other.x4_lockHeld = false;\n}\nCToken::CToken(IObj* obj) {\n  x0_objRef = new CObjectReference(std::unique_ptr<IObj>(obj));\n  ++x0_objRef->x0_refCount;\n  Lock();\n}\nCToken::CToken(std::unique_ptr<IObj>&& obj) {\n  x0_objRef = new CObjectReference(std::move(obj));\n  ++x0_objRef->x0_refCount;\n  Lock();\n}\nconst SObjectTag* CToken::GetObjectTag() const {\n  if (!x0_objRef)\n    return nullptr;\n  return &x0_objRef->GetObjectTag();\n}\nCToken::~CToken() {\n  if (x0_objRef) {\n    if (x4_lockHeld)\n      x0_objRef->Unlock();\n    RemoveRef();\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CToken.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/IFactory.hpp\"\n#include \"Runtime/IObj.hpp\"\n#include \"Runtime/IObjectStore.hpp\"\n#include \"Runtime/IVParamObj.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n\nnamespace metaforce {\nclass IObjectStore;\n\n/** Shared data-structure for CToken references, analogous to std::shared_ptr */\nclass CObjectReference {\n  friend class CSimplePool;\n  friend class CToken;\n\n  u16 x0_refCount = 0;\n  u16 x2_lockCount = 0;\n  bool x3_loading = false; /* Rightmost bit of lockCount */\n  SObjectTag x4_objTag;\n  IObjectStore* xC_objectStore = nullptr;\n  std::unique_ptr<IObj> x10_object;\n  CVParamTransfer x14_params;\n\n  /** Mechanism by which CToken decrements 1st ref-count, indicating CToken invalidation or reset.\n   *  Reaching 0 indicates the CToken should delete the CObjectReference */\n  u16 RemoveReference();\n\n  CObjectReference(IObjectStore& objStore, std::unique_ptr<IObj>&& obj, const SObjectTag& objTag,\n                   CVParamTransfer buildParams);\n  CObjectReference(std::unique_ptr<IObj>&& obj);\n\n  /** Indicates an asynchronous load transaction has been submitted and is not yet finished */\n  bool IsLoading() const { return x3_loading; }\n\n  /** Indicates an asynchronous load transaction has finished and object is completely loaded */\n  bool IsLoaded() const { return x10_object.operator bool(); }\n\n  /** Decrements 2nd ref-count, performing unload or async-load-cancel if 0 reached */\n  void Unlock();\n\n  /** Increments 2nd ref-count, performing async-factory-load if needed */\n  void Lock();\n\n  void CancelLoad();\n\n  /** Pointer-synchronized object-destructor, another building Lock cycle may be performed after */\n  void Unload();\n\n  /** Synchronous object-fetch, guaranteed to return complete object on-demand, blocking build if not ready */\n  IObj* GetObject();\n\npublic:\n  const SObjectTag& GetObjectTag() const { return x4_objTag; }\n\n  ~CObjectReference();\n};\n\n/** Counted meta-object, reference-counting against a shared CObjectReference\n *  This class is analogous to std::shared_ptr and C++11 rvalues have been implemented accordingly\n *  (default/empty constructor, move constructor/assign) */\nclass CToken {\n  friend class CModel;\n  friend class CSimplePool;\n\n  CObjectReference* x0_objRef = nullptr;\n  bool x4_lockHeld = false;\n\n  void RemoveRef();\n\n  CToken(CObjectReference* obj);\n\npublic:\n  /* Added to test for non-null state */\n  explicit operator bool() const { return HasReference(); }\n  bool HasReference() const { return x0_objRef != nullptr; }\n\n  void Unlock();\n  void Lock();\n  bool IsLocked() const { return x4_lockHeld; }\n  bool IsLoaded() const;\n  IObj* GetObj();\n  const IObj* GetObj() const { return const_cast<CToken*>(this)->GetObj(); }\n  CToken& operator=(const CToken& other);\n  CToken& operator=(CToken&& other) noexcept;\n  CToken() = default;\n  CToken(const CToken& other);\n  CToken(CToken&& other) noexcept;\n  CToken(IObj* obj);\n  CToken(std::unique_ptr<IObj>&& obj);\n  const SObjectTag* GetObjectTag() const;\n  const CObjectReference* GetObjectReference() const { return x0_objRef; }\n  ~CToken();\n};\n\ntemplate <class T>\nclass TToken : public CToken {\npublic:\n  static std::unique_ptr<TObjOwnerDerivedFromIObj<T>> GetIObjObjectFor(std::unique_ptr<T>&& obj) {\n    return TObjOwnerDerivedFromIObj<T>::GetNewDerivedObject(std::move(obj));\n  }\n  TToken() = default;\n  virtual ~TToken() = default;\n  TToken(const CToken& other) : CToken(other) {}\n  TToken(CToken&& other) : CToken(std::move(other)) {}\n  TToken(std::unique_ptr<T>&& obj) : CToken(GetIObjObjectFor(std::move(obj))) {}\n  TToken& operator=(std::unique_ptr<T>&& obj) {\n    *this = CToken(GetIObjObjectFor(std::move(obj)));\n    return *this;\n  }\n  virtual void Unlock() { CToken::Unlock(); }\n  virtual void Lock() { CToken::Lock(); }\n  virtual T* GetObj() {\n    TObjOwnerDerivedFromIObj<T>* owner = static_cast<TObjOwnerDerivedFromIObj<T>*>(CToken::GetObj());\n    if (owner)\n      return owner->GetObj();\n    return nullptr;\n  }\n  virtual const T* GetObj() const { return const_cast<TToken<T>*>(this)->GetObj(); }\n  virtual TToken& operator=(const CToken& other) {\n    CToken::operator=(other);\n    return *this;\n  }\n  T* operator->() { return GetObj(); }\n  const T* operator->() const { return GetObj(); }\n  T& operator*() { return *GetObj(); }\n  const T& operator*() const { return *GetObj(); }\n};\n\ntemplate <class T>\nclass TCachedToken : public TToken<T> {\nprotected:\n  T* m_obj = nullptr;\n\npublic:\n  TCachedToken() = default;\n  TCachedToken(const CToken& other) : TToken<T>(other) {}\n  TCachedToken(CToken&& other) : TToken<T>(std::move(other)) {}\n  T* GetObj() override {\n    if (!m_obj)\n      m_obj = TToken<T>::GetObj();\n    return m_obj;\n  }\n  const T* GetObj() const override { return const_cast<TCachedToken<T>*>(this)->GetObj(); }\n  void Unlock() override {\n    TToken<T>::Unlock();\n    m_obj = nullptr;\n  }\n\n  TCachedToken& operator=(const TCachedToken& other) {\n    TToken<T>::operator=(other);\n    m_obj = nullptr;\n    return *this;\n  }\n  TCachedToken& operator=(const CToken& other) override {\n    TToken<T>::operator=(other);\n    m_obj = nullptr;\n    return *this;\n  }\n\n  bool IsNull() const { return m_obj == nullptr; }\n};\n\ntemplate <class T>\nclass TLockedToken : public TCachedToken<T> {\npublic:\n  TLockedToken() = default;\n  TLockedToken(const TLockedToken& other) : TCachedToken<T>(other) { CToken::Lock(); }\n  TLockedToken& operator=(const TLockedToken& other) {\n    CToken oldTok = std::move(*this);\n    TCachedToken<T>::operator=(other);\n    CToken::Lock();\n    return *this;\n  }\n  TLockedToken(const CToken& other) : TCachedToken<T>(other) { CToken::Lock(); }\n  TLockedToken& operator=(const CToken& other) override {\n    CToken oldTok = std::move(*this);\n    TCachedToken<T>::operator=(other);\n    CToken::Lock();\n    return *this;\n  }\n  TLockedToken(CToken&& other) {\n    CToken oldTok = std::move(*this);\n    *this = TCachedToken<T>(std::move(other));\n    CToken::Lock();\n  }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CWorldSaveGameInfo.cpp",
    "content": "#include \"Runtime/CWorldSaveGameInfo.hpp\"\n\n#include \"Runtime/CToken.hpp\"\n\nnamespace metaforce {\nCWorldSaveGameInfo::CWorldSaveGameInfo(CInputStream& in) {\n  in.ReadLong();\n  const u32 version = in.ReadLong();\n  if (version > 1) {\n    x0_areaCount = in.ReadLong();\n  }\n\n  if (version > 2) {\n    const u32 cinematicCount = in.ReadLong();\n    x4_cinematics.reserve(cinematicCount);\n    for (u32 i = 0; i < cinematicCount; ++i) {\n      x4_cinematics.emplace_back(in.ReadLong());\n    }\n\n    const u32 relayCount = in.ReadLong();\n    x14_relays.reserve(relayCount);\n    for (u32 i = 0; i < relayCount; ++i) {\n      x14_relays.emplace_back(in.ReadLong());\n    }\n  }\n\n  const u32 layerCount = in.ReadLong();\n  x24_layers.reserve(layerCount);\n  for (u32 i = 0; i < layerCount; ++i) {\n    SLayerState& st = x24_layers.emplace_back();\n    st.x0_area = in.ReadLong();\n    st.x4_layer = in.ReadLong();\n  }\n\n  const u32 doorCount = in.ReadLong();\n  x34_doors.reserve(doorCount);\n  for (u32 i = 0; i < doorCount; ++i) {\n    x34_doors.emplace_back(in.ReadLong());\n  }\n\n  if (version <= 0) {\n    return;\n  }\n\n  const u32 scanCount = in.ReadLong();\n  x44_scans.reserve(scanCount);\n  for (u32 i = 0; i < scanCount; ++i) {\n    SScanState& st = x44_scans.emplace_back();\n    st.x0_id = in.Get<CAssetId>();\n    st.x4_category = EScanCategory(in.ReadLong());\n  }\n}\n\nu32 CWorldSaveGameInfo::GetAreaCount() const { return x0_areaCount; }\n\nu32 CWorldSaveGameInfo::GetCinematicCount() const { return x4_cinematics.size(); }\n\ns32 CWorldSaveGameInfo::GetCinematicIndex(const TEditorId& id) const {\n  auto it = std::find(x4_cinematics.begin(), x4_cinematics.end(), id);\n  if (it == x4_cinematics.end())\n    return -1;\n  return it - x4_cinematics.begin();\n}\n\nu32 CWorldSaveGameInfo::GetRelayCount() const { return x14_relays.size(); }\n\ns32 CWorldSaveGameInfo::GetRelayIndex(const TEditorId& id) const {\n  auto it = std::find(x14_relays.begin(), x14_relays.end(), id);\n  if (it == x14_relays.end())\n    return -1;\n  return it - x14_relays.begin();\n}\n\nTEditorId CWorldSaveGameInfo::GetRelayEditorId(u32 idx) const { return x14_relays[idx]; }\n\nu32 CWorldSaveGameInfo::GetDoorCount() const { return x34_doors.size(); }\n\ns32 CWorldSaveGameInfo::GetDoorIndex(const TEditorId& id) const {\n  auto it = std::find(x34_doors.begin(), x34_doors.end(), id);\n  if (it == x34_doors.end())\n    return -1;\n  return it - x34_doors.begin();\n}\n\nCFactoryFnReturn FWorldSaveGameInfoFactory([[maybe_unused]] const SObjectTag& tag, CInputStream& in,\n                                           [[maybe_unused]] const CVParamTransfer& param,\n                                           [[maybe_unused]] CObjectReference* selfRef) {\n  return TToken<CWorldSaveGameInfo>::GetIObjObjectFor(std::make_unique<CWorldSaveGameInfo>(in));\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/CWorldSaveGameInfo.hpp",
    "content": "#pragma once\n\n#include <vector>\n\n#include \"Runtime/CFactoryMgr.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n\nnamespace metaforce {\n\nclass CWorldSaveGameInfo {\npublic:\n  enum class EScanCategory { None, Data, Lore, Creature, Research, Artifact };\n\n  struct SScanState {\n    CAssetId x0_id;\n    EScanCategory x4_category;\n  };\n\n  struct SLayerState {\n    TAreaId x0_area;\n    u32 x4_layer;\n  };\n\nprivate:\n  u32 x0_areaCount;\n  std::vector<TEditorId> x4_cinematics;\n  std::vector<TEditorId> x14_relays;\n  std::vector<SLayerState> x24_layers;\n  std::vector<TEditorId> x34_doors;\n  std::vector<SScanState> x44_scans;\n\npublic:\n  explicit CWorldSaveGameInfo(CInputStream& in);\n  u32 GetAreaCount() const;\n  u32 GetCinematicCount() const;\n  s32 GetCinematicIndex(const TEditorId& id) const;\n  const std::vector<TEditorId>& GetCinematics() const { return x4_cinematics; }\n  const std::vector<TEditorId>& GetDoors() const { return x34_doors; }\n  const std::vector<SScanState>& GetScans() const { return x44_scans; }\n  u32 GetRelayCount() const;\n  s32 GetRelayIndex(const TEditorId& id) const;\n  TEditorId GetRelayEditorId(u32 idx) const;\n  u32 GetDoorCount() const;\n  s32 GetDoorIndex(const TEditorId& id) const;\n};\n\nCFactoryFnReturn FWorldSaveGameInfoFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& param,\n                                           CObjectReference* selfRef);\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Camera/CBallCamera.cpp",
    "content": "#include \"Runtime/Camera/CBallCamera.hpp\"\n\n#include <algorithm>\n#include <cmath>\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Camera/CFirstPersonCamera.hpp\"\n#include \"Runtime/Camera/CPathCamera.hpp\"\n#include \"Runtime/Collision/CCollisionActor.hpp\"\n#include \"Runtime/Collision/CGameCollision.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Input/ControlMapper.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CScriptCameraHint.hpp\"\n#include \"Runtime/World/CScriptDock.hpp\"\n#include \"Runtime/World/CScriptDoor.hpp\"\n#include \"Runtime/World/CScriptSpindleCamera.hpp\"\n#include \"Runtime/World/CScriptWater.hpp\"\n#include \"Runtime/rstl.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nvoid CCameraSpring::Reset() {\n  x4_k2Sqrt = 2.f * std::sqrt(x0_k);\n  x10_dx = 0.f;\n}\n\nfloat CCameraSpring::ApplyDistanceSpringNoMax(float targetX, float curX, float dt) {\n  float useX = xc_tardis * x10_dx * dt + curX;\n  x10_dx += xc_tardis * (x0_k * (targetX - curX) - x4_k2Sqrt * x10_dx) * dt;\n  return std::max(useX, targetX);\n}\n\nfloat CCameraSpring::ApplyDistanceSpring(float targetX, float curX, float dt) {\n  float useX = xc_tardis * x10_dx * dt + curX;\n  x10_dx += xc_tardis * (x0_k * (targetX - curX) - x4_k2Sqrt * x10_dx) * dt;\n  useX = std::max(useX, targetX);\n  if (useX - targetX > x8_max) {\n    useX = targetX + x8_max;\n  }\n  return useX;\n}\n\nCBallCamera::CBallCamera(TUniqueId uid, TUniqueId watchedId, const zeus::CTransform& xf, float fovy, float znear,\n                         float zfar, float aspect)\n: CGameCamera(uid, true, \"Ball Camera\", CEntityInfo(kInvalidAreaId, CEntity::NullConnectionList), xf, fovy, znear, zfar,\n              aspect, watchedId, false, 0)\n, x214_ballCameraSpring(g_tweakBall->GetBallCameraSpringConstant(), g_tweakBall->GetBallCameraSpringMax(),\n                        g_tweakBall->GetBallCameraSpringTardis())\n, x228_ballCameraCentroidSpring(g_tweakBall->GetBallCameraCentroidSpringConstant(),\n                                g_tweakBall->GetBallCameraCentroidSpringMax(),\n                                g_tweakBall->GetBallCameraCentroidSpringTardis() * 1.1f)\n, x23c_ballCameraLookAtSpring(g_tweakBall->GetBallCameraLookAtSpringConstant(),\n                              g_tweakBall->GetBallCameraLookAtSpringMax(),\n                              g_tweakBall->GetBallCameraLookAtSpringTardis())\n, x250_ballCameraCentroidDistanceSpring(g_tweakBall->GetBallCameraCentroidDistanceSpringConstant(),\n                                        g_tweakBall->GetBallCameraCentroidDistanceSpringMax(),\n                                        g_tweakBall->GetBallCameraCentroidDistanceSpringTardis())\n, x41c_ballCameraChaseSpring(g_tweakBall->GetBallCameraChaseSpringConstant(),\n                             g_tweakBall->GetBallCameraChaseSpringMax(), g_tweakBall->GetBallCameraChaseSpringTardis())\n, x448_ballCameraBoostSpring(g_tweakBall->GetBallCameraBoostSpringConstant(),\n                             g_tweakBall->GetBallCameraBoostSpringMax(),\n                             g_tweakBall->GetBallCameraBoostSpringTardis()) {\n  x190_curMinDistance = g_tweakBall->GetBallCameraMinSpeedDistance();\n  x194_targetMinDistance = g_tweakBall->GetBallCameraMinSpeedDistance();\n  x198_maxDistance = g_tweakBall->GetBallCameraMaxSpeedDistance();\n  x19c_backwardsDistance = g_tweakBall->GetBallCameraBackwardsDistance();\n\n  x1a0_elevation = g_tweakBall->GetBallCameraElevation();\n  x1a4_curAnglePerSecond = g_tweakBall->GetBallCameraAnglePerSecond();\n  x1a8_targetAnglePerSecond = g_tweakBall->GetBallCameraAnglePerSecond();\n  x1b4_lookAtOffset = g_tweakBall->GetBallCameraOffset();\n\n  x404_chaseElevation = g_tweakBall->GetBallCameraChaseElevation();\n  x408_chaseDistance = g_tweakBall->GetBallCameraChaseDistance();\n  x40c_chaseAnglePerSecond = g_tweakBall->GetBallCameraChaseAnglePerSecond();\n  x410_chaseLookAtOffset = g_tweakBall->GetBallCameraChaseLookAtOffset();\n\n  x430_boostElevation = g_tweakBall->GetBallCameraBoostElevation();\n  x434_boostDistance = g_tweakBall->GetBallCameraBoostDistance();\n  x438_boostAnglePerSecond = g_tweakBall->GetBallCameraBoostAnglePerSecond();\n  x43c_boostLookAtOffset = g_tweakBall->GetBallCameraBoostLookAtOffset();\n\n  x468_conservativeDoorCamDistance = g_tweakBall->GetConservativeDoorCameraDistance();\n\n  x47c_failsafeState = std::make_unique<SFailsafeState>();\n  x480_ = std::make_unique<u32>();\n\n  SetupColliders(x264_smallColliders, 2.31f, 2.31f, 0.1f, 3, 2.f, 0.5f, -M_PIF / 2.f);\n  SetupColliders(x274_mediumColliders, 4.62f, 4.62f, 0.1f, 6, 2.f, 0.5f, -M_PIF / 2.f);\n  SetupColliders(x284_largeColliders, 7.f, 7.f, 0.1f, 12, 2.f, 0.5f, -M_PIF / 2.f);\n}\n\nvoid CBallCamera::SetupColliders(std::vector<CCameraCollider>& out, float xMag, float zMag, float radius, int count,\n                                 float k, float max, float startAngle) {\n  out.reserve(count);\n  float theta = startAngle;\n  for (int i = 0; i < count; ++i) {\n    float z = std::cos(theta) * zMag;\n    if (theta > M_PIF / 2.f) {\n      z *= 0.25f;\n    }\n    out.emplace_back(radius, zeus::CVector3f{xMag * std::sin(theta), 0.f, z}, CCameraSpring{k, max, 1.f}, 1.f);\n    theta += 2.f * M_PIF / float(count);\n  }\n}\n\nvoid CBallCamera::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CBallCamera::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) {\n  CGameCamera::AcceptScriptMsg(msg, objId, stateMgr);\n  switch (msg) {\n  case EScriptObjectMessage::Registered: {\n    x46c_collisionActorId = stateMgr.AllocateUniqueId();\n    auto* colAct =\n        new CCollisionActor(x46c_collisionActorId, GetAreaId(), kInvalidUniqueId, true, 0.3f, 1.f, \"BallCamera\"sv);\n    colAct->SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(\n        {EMaterialTypes::Solid}, {EMaterialTypes::Player, EMaterialTypes::CameraPassthrough}));\n    colAct->SetMaterialList({EMaterialTypes::ProjectilePassthrough, EMaterialTypes::ScanPassthrough,\n                             EMaterialTypes::SeeThrough, EMaterialTypes::CameraPassthrough});\n    colAct->SetTranslation(GetTranslation());\n    stateMgr.AddObject(colAct);\n    colAct->SetMovable(false);\n    CMotionState mState(GetTranslation(), zeus::CNUQuaternion::fromAxisAngle(zeus::skForward, 0.f), zeus::skZero3f,\n                        zeus::CAxisAngle());\n    colAct->SetLastNonCollidingState(mState);\n    SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(\n        {}, {EMaterialTypes::Solid, EMaterialTypes::ProjectilePassthrough, EMaterialTypes::Player,\n             EMaterialTypes::Character, EMaterialTypes::CameraPassthrough}));\n    RemoveMaterial(EMaterialTypes::Solid, stateMgr);\n    break;\n  }\n  case EScriptObjectMessage::Deleted:\n    stateMgr.FreeScriptObject(x46c_collisionActorId);\n    x46c_collisionActorId = kInvalidUniqueId;\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CBallCamera::ProcessInput(const CFinalInput& input, CStateManager& mgr) {\n  if (input.ControllerIdx() != 0) {\n    return;\n  }\n\n  if (TCastToConstPtr<CPlayer> player = mgr.GetObjectById(xe8_watchedObject)) {\n    if (player->GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed) {\n      switch (x400_state) {\n      case EBallCameraState::Chase:\n        if (!ControlMapper::GetDigitalInput(ControlMapper::ECommands::ChaseCamera, input) || player->IsInFreeLook()) {\n          SetState(EBallCameraState::Default, mgr);\n        }\n        break;\n      case EBallCameraState::Boost:\n        if (!player->GetMorphBall()->IsInBoost()) {\n          SetState(EBallCameraState::Default, mgr);\n        }\n        break;\n      case EBallCameraState::Default:\n        if (x18c_25_chaseAllowed && ControlMapper::GetPressInput(ControlMapper::ECommands::ChaseCamera, input)) {\n          SetState(EBallCameraState::Chase, mgr);\n        }\n        break;\n      default:\n        break;\n      }\n\n      if (x18c_26_boostAllowed && x400_state != EBallCameraState::Boost &&\n          (player->GetMorphBall()->IsInBoost() || player->GetMorphBall()->GetBoostChargeTime() > 0.f)) {\n        SetState(EBallCameraState::Boost, mgr);\n      }\n    }\n  }\n}\n\nvoid CBallCamera::Reset(const zeus::CTransform& xf, CStateManager& mgr) {\n  x214_ballCameraSpring.Reset();\n  x228_ballCameraCentroidSpring.Reset();\n  x23c_ballCameraLookAtSpring.Reset();\n  x250_ballCameraCentroidDistanceSpring.Reset();\n  x41c_ballCameraChaseSpring.Reset();\n  x448_ballCameraBoostSpring.Reset();\n\n  zeus::CVector3f desiredPos = FindDesiredPosition(x190_curMinDistance, x1a0_elevation, xf.basis[1], mgr, false);\n\n  if (TCastToConstPtr<CPlayer> player = mgr.GetObjectById(xe8_watchedObject)) {\n    ResetPosition(mgr);\n    x310_idealLookVec = x1b4_lookAtOffset;\n    x31c_predictedLookPos = x1d8_lookPos;\n    if ((x1d8_lookPos - desiredPos).canBeNormalized()) {\n      TeleportCamera(zeus::lookAt(desiredPos, x1d8_lookPos), mgr);\n    } else {\n      zeus::CTransform camXf = player->CreateTransformFromMovementDirection();\n      camXf.origin = desiredPos;\n      TeleportCamera(camXf, mgr);\n      mgr.GetCameraManager()->SetPlayerCamera(mgr, GetUniqueId());\n    }\n\n    x2e8_ballVelFlat = 0.f;\n    x2ec_maxBallVel = 0.f;\n    x190_curMinDistance = x194_targetMinDistance;\n    x2fc_ballDeltaFlat = zeus::skZero3f;\n    x2f0_ballDelta = zeus::skZero3f;\n    x18d_28_obtuseDirection = false;\n    x308_speedFactor = 0.f;\n    x2dc_prevBallPos = player->GetBallPosition();\n    x294_dampedPos = GetTranslation();\n    x2a0_smallCentroid = zeus::skZero3f;\n    x2ac_mediumCentroid = zeus::skZero3f;\n    x2b8_largeCentroid = zeus::skZero3f;\n    x2c4_smallCollidersObsCount = 0;\n    x2c8_mediumCollidersObsCount = 0;\n    x2cc_largeCollidersObsCount = 0;\n    x2d0_smallColliderIt = 0;\n    x2d4_mediumColliderIt = 0;\n    x2d8_largeColliderIt = 0;\n    x32c_colliderMag = 1.f;\n    x18d_25_avoidGeometryFull = true;\n    x18d_27_forceProcessing = true;\n    Think(0.1f, mgr);\n    x18d_25_avoidGeometryFull = false;\n    x18d_27_forceProcessing = false;\n  }\n}\n\nvoid CBallCamera::Render(CStateManager& mgr) {\n  // Empty\n}\n\nvoid CBallCamera::SetState(EBallCameraState state, CStateManager& mgr) {\n  switch (state) {\n  case EBallCameraState::ToBall: {\n    zeus::CTransform xf = mgr.GetCameraManager()->GetFirstPersonCamera()->GetTransform();\n    SetTransform(xf);\n    TeleportCamera(xf.origin, mgr);\n    SetFovInterpolation(mgr.GetCameraManager()->GetFirstPersonCamera()->GetFov(), CCameraManager::ThirdPersonFOV(), 1.f,\n                        0.f);\n    x36c_splineState = ESplineState::Invalid;\n    [[fallthrough]];\n  }\n  case EBallCameraState::Default:\n  case EBallCameraState::Chase:\n  case EBallCameraState::Boost:\n    mgr.SetGameState(CStateManager::EGameState::Running);\n    break;\n  case EBallCameraState::FromBall:\n    mgr.GetCameraManager()->SetPlayerCamera(mgr, GetUniqueId());\n    mgr.SetGameState(CStateManager::EGameState::Running);\n    SetFovInterpolation(GetFov(), CCameraManager::FirstPersonFOV(), 1.f, 0.f);\n    x36c_splineState = ESplineState::Invalid;\n    break;\n  default:\n    break;\n  }\n\n  x400_state = state;\n}\n\nconstexpr CMaterialFilter BallCameraFilter = CMaterialFilter::MakeIncludeExclude(\n    {EMaterialTypes::Solid}, {EMaterialTypes::ProjectilePassthrough, EMaterialTypes::Player, EMaterialTypes::Character,\n                              EMaterialTypes::CameraPassthrough});\n\nvoid CBallCamera::BuildSplineNav(CStateManager& mgr) {\n  zeus::CVector3f ballPos = mgr.GetPlayer().GetBallPosition();\n  TUniqueId intersectId = kInvalidUniqueId;\n  EntityList nearList;\n  CRayCastResult result =\n      mgr.RayWorldIntersection(intersectId, ballPos, zeus::skDown, 20.f, BallCameraFilter, nearList);\n  float downFactor = result.IsValid() ? zeus::clamp(0.f, result.GetT() / 20.f, 1.f) : 1.f;\n  x36c_splineState = ESplineState::Nav;\n  x370_24_reevalSplineEnd = true;\n  x3d0_24_camBehindFloorOrWall = false;\n  x37c_camSpline.Reset(4);\n  x37c_camSpline.AddKnot(GetTranslation(), zeus::skForward);\n  float elevation = x1a0_elevation;\n  float distance = x190_curMinDistance;\n  ConstrainElevationAndDistance(elevation, distance, 0.f, mgr);\n  zeus::CVector3f pt1(x35c_splineIntermediatePos.x(), x35c_splineIntermediatePos.y(), GetTranslation().z());\n  x37c_camSpline.AddKnot(pt1, zeus::skForward);\n  zeus::CVector3f pt2 = pt1 + (x35c_splineIntermediatePos - GetTranslation()) * (0.5f + downFactor);\n  x37c_camSpline.AddKnot(pt2, zeus::skForward);\n  zeus::CVector3f pt2Ball = ballPos - pt2;\n  if (pt2Ball.canBeNormalized()) {\n    pt2Ball.normalize();\n  } else {\n    pt2Ball = mgr.GetPlayer().GetMoveDir();\n  }\n  zeus::CVector3f desiredPosition = FindDesiredPosition(distance, elevation, pt2Ball, mgr, false);\n  x37c_camSpline.AddKnot(desiredPosition, zeus::skForward);\n  x37c_camSpline.UpdateSplineLength();\n  x3d0_24_camBehindFloorOrWall = false;\n  CMaterialList intersectMat;\n  x3c8_collisionExcludeList = CMaterialList(EMaterialTypes::Floor, EMaterialTypes::Ceiling);\n  if (!SplineIntersectTest(intersectMat, mgr)) {\n    if (intersectMat.HasMaterial(EMaterialTypes::Floor) || intersectMat.HasMaterial(EMaterialTypes::Wall)) {\n      x3d0_24_camBehindFloorOrWall = true;\n      x3c8_collisionExcludeList = CMaterialList();\n    }\n  }\n  x374_splineCtrl = 0.5f * downFactor + 2.f;\n  x378_splineCtrlRange = 2.5f;\n}\n\nvoid CBallCamera::BuildSplineArc(CStateManager& mgr) {\n  zeus::CVector3f ballPos = mgr.GetPlayer().GetBallPosition();\n  x36c_splineState = ESplineState::Arc;\n  x370_24_reevalSplineEnd = false;\n  x37c_camSpline.Reset(4);\n  x37c_camSpline.AddKnot(GetTranslation(), zeus::skForward);\n  float elevation = x1a0_elevation;\n  float distance = x190_curMinDistance;\n  ConstrainElevationAndDistance(elevation, distance, 0.f, mgr);\n  zeus::CVector3f halfwayPoint = (ballPos.toVec2f() - GetTranslation().toVec2f()) * 0.5f + GetTranslation().toVec2f();\n  halfwayPoint.z() = GetTranslation().z();\n  zeus::CVector3f delta = GetTranslation() - halfwayPoint;\n  zeus::CQuaternion rot;\n  rot.rotateZ(zeus::degToRad(45.f));\n  if (mgr.GetPlayer().GetMoveDir().cross(x34_transform.basis[1]).z() >= 0.f) {\n    rot = zeus::CQuaternion();\n    rot.rotateZ(zeus::degToRad(-45.f));\n  }\n  delta = rot.transform(delta);\n  zeus::CVector3f pt1 = halfwayPoint + delta;\n  TUniqueId intersectId = kInvalidUniqueId;\n  EntityList nearList;\n  CRayCastResult result =\n      mgr.RayWorldIntersection(intersectId, pt1, -delta.normalized(), delta.magnitude(), BallCameraFilter, nearList);\n  if (result.IsValid()) {\n    pt1 = delta.normalized() * 1.5f + result.GetPoint();\n  } else {\n    pt1 = halfwayPoint + delta;\n  }\n  x37c_camSpline.AddKnot(pt1, zeus::skForward);\n  FindDesiredPosition(distance, elevation, mgr.GetPlayer().GetMoveDir(), mgr, false);\n  delta = rot.transform(delta);\n  zeus::CVector3f pt2 = halfwayPoint + delta;\n  result =\n      mgr.RayWorldIntersection(intersectId, pt2, -delta.normalized(), delta.magnitude(), BallCameraFilter, nearList);\n  if (result.IsValid()) {\n    pt2 = delta.normalized() * 2.f + result.GetPoint();\n  } else {\n    pt2 = halfwayPoint + delta;\n  }\n  x37c_camSpline.AddKnot(pt2, zeus::skForward);\n  delta = rot.transform(delta);\n  zeus::CVector3f pt3 = delta + halfwayPoint;\n  result =\n      mgr.RayWorldIntersection(intersectId, pt3, -delta.normalized(), delta.magnitude(), BallCameraFilter, nearList);\n  if (result.IsValid()) {\n    pt3 = delta.normalized() * 2.f + result.GetPoint();\n  } else {\n    pt3 = halfwayPoint + delta;\n  }\n  x37c_camSpline.AddKnot(pt3, zeus::skForward);\n  CMaterialList intersectMat;\n  if (!SplineIntersectTest(intersectMat, mgr) && intersectMat.HasMaterial(EMaterialTypes::Wall)) {\n    delta = pt1 - halfwayPoint;\n    result =\n        mgr.RayWorldIntersection(intersectId, pt1, -delta.normalized(), delta.magnitude(), BallCameraFilter, nearList);\n    if (result.IsValid() && !result.GetMaterial().HasMaterial(EMaterialTypes::Pillar)) {\n      x37c_camSpline.SetKnotPosition(1, result.GetPoint() - delta.normalized() * 0.3f * 1.25f);\n    }\n    delta = pt2 - halfwayPoint;\n    result =\n        mgr.RayWorldIntersection(intersectId, pt2, -delta.normalized(), delta.magnitude(), BallCameraFilter, nearList);\n    if (result.IsValid() && !result.GetMaterial().HasMaterial(EMaterialTypes::Pillar)) {\n      x37c_camSpline.SetKnotPosition(2, result.GetPoint() - delta.normalized() * 0.3f * 1.25f);\n    }\n    x37c_camSpline.UpdateSplineLength();\n    if (!SplineIntersectTest(intersectMat, mgr)) {\n      x36c_splineState = ESplineState::Invalid;\n      return;\n    }\n  }\n  x374_splineCtrl = 0.5f;\n  x378_splineCtrlRange = 0.5f;\n  x37c_camSpline.UpdateSplineLength();\n  x3c8_collisionExcludeList = CMaterialList();\n}\n\nbool CBallCamera::ShouldResetSpline(CStateManager& mgr) const {\n  return x400_state != EBallCameraState::ToBall && !mgr.GetCameraManager()->IsInterpolationCameraActive() &&\n         mgr.GetPlayer().GetMorphBall()->GetSpiderBallState() != CMorphBall::ESpiderBallState::Active &&\n         x36c_splineState == ESplineState::Invalid &&\n         (x188_behaviour > EBallCameraBehaviour::SpindleCamera ||\n          x188_behaviour < EBallCameraBehaviour::HintFixedPosition);\n}\n\nvoid CBallCamera::UpdatePlayerMovement(float dt, CStateManager& mgr) {\n  x2ec_maxBallVel = std::fabs(mgr.GetPlayer().GetActualBallMaxVelocity(dt));\n  zeus::CVector3f ballPos = mgr.GetPlayer().GetBallPosition();\n  x2f0_ballDelta = ballPos - x2dc_prevBallPos;\n  x2fc_ballDeltaFlat = x2f0_ballDelta;\n  x2fc_ballDeltaFlat.z() = 0.f;\n  if (x2fc_ballDeltaFlat.canBeNormalized()) {\n    x2e8_ballVelFlat = x2fc_ballDeltaFlat.magnitude() / dt;\n  } else {\n    x2e8_ballVelFlat = 0.f;\n  }\n  x2dc_prevBallPos = ballPos;\n  x18d_28_obtuseDirection = false;\n  zeus::CVector3f camToBallFlat = ballPos - GetTranslation();\n  camToBallFlat.z() = 0.f;\n  if (camToBallFlat.canBeNormalized()) {\n    camToBallFlat.normalize();\n    if (std::fabs(std::acos(zeus::clamp(-1.f, camToBallFlat.dot(mgr.GetPlayer().GetMoveDir()), 1.f))) >\n        zeus::degToRad(100.f)) {\n      x18d_28_obtuseDirection = true;\n    }\n  }\n  x308_speedFactor = 0.f;\n  float tmpVel = x2e8_ballVelFlat - 4.f;\n  if (tmpVel > 0.f) {\n    x308_speedFactor =\n        zeus::clamp(-1.f, std::fabs(std::sin(zeus::degToRad(tmpVel / (x2ec_maxBallVel - 4.f) * 90.f))), 1.f);\n  }\n  x190_curMinDistance = x308_speedFactor * (x198_maxDistance - x194_targetMinDistance) + x194_targetMinDistance;\n  if (x308_speedFactor > 0.5f && mgr.GetPlayer().GetPlayerMovementState() == CPlayer::EPlayerMovementState::OnGround) {\n    x30c_speedingTime += dt * x308_speedFactor;\n  } else {\n    x30c_speedingTime = 0.f;\n  }\n  x30c_speedingTime = zeus::clamp(0.f, x30c_speedingTime, 3.f);\n}\n\nvoid CBallCamera::UpdateTransform(const zeus::CVector3f& lookDir, const zeus::CVector3f& pos, float dt,\n                                  CStateManager& mgr) {\n  zeus::CVector3f useLookDir = lookDir;\n  if (x18d_31_overrideLookDir) {\n    if (const CScriptCameraHint* hint = mgr.GetCameraManager()->GetCameraHint(mgr)) {\n      useLookDir = hint->GetTransform().basis[1];\n    }\n  }\n  zeus::CVector3f lookDirFlat = useLookDir;\n  lookDirFlat.z() = 0.f;\n  if (!lookDirFlat.canBeNormalized()) {\n    SetTranslation(pos);\n    return;\n  }\n  zeus::CVector3f curLookDir = x34_transform.basis[1];\n  if (curLookDir.canBeNormalized()) {\n    curLookDir.normalize();\n  } else {\n    SetTransform(zeus::lookAt(pos, pos + useLookDir));\n    return;\n  }\n  float lookDirDot = zeus::clamp(-1.f, curLookDir.dot(useLookDir), 1.f);\n  if (std::fabs(lookDirDot) >= 1.f) {\n    SetTransform(zeus::lookAt(pos, pos + useLookDir));\n  } else {\n    float angleSpeedMul = zeus::clamp(0.f, std::acos(lookDirDot) / (zeus::degToRad(60.f) * dt), 1.f);\n    float angleDelta = dt * x1a4_curAnglePerSecond * angleSpeedMul;\n    float lookUpDot = std::fabs(zeus::clamp(-1.f, useLookDir.dot(zeus::skUp), 1.f));\n    float maxAngleDelta = (1.f - lookUpDot) * zeus::degToRad(720.f) * dt;\n    if (x36c_splineState == ESplineState::Nav) {\n      maxAngleDelta = zeus::degToRad(240.f) * dt;\n      if (angleDelta > maxAngleDelta) {\n        angleDelta = maxAngleDelta;\n      }\n    }\n    if (angleDelta > maxAngleDelta && !mgr.GetPlayer().IsMorphBallTransitioning() && lookUpDot > 0.999f) {\n      angleDelta = maxAngleDelta;\n    }\n    switch (x400_state) {\n    case EBallCameraState::Chase:\n      if (x18c_25_chaseAllowed) {\n        angleDelta = dt * x40c_chaseAnglePerSecond * angleSpeedMul;\n      }\n      break;\n    case EBallCameraState::Boost:\n      angleDelta = dt * x438_boostAnglePerSecond * angleSpeedMul;\n      break;\n    default:\n      break;\n    }\n    if (x18d_26_lookAtBall || mgr.GetCameraManager()->IsInterpolationCameraActive()) {\n      x18d_26_lookAtBall = false;\n      SetTransform(zeus::CQuaternion::lookAt(curLookDir, useLookDir, 2.f * M_PIF).toTransform() *\n                   x34_transform.getRotation());\n    } else {\n      SetTransform(zeus::CQuaternion::lookAt(curLookDir, useLookDir, angleDelta).toTransform() *\n                   x34_transform.getRotation());\n    }\n  }\n  SetTranslation(pos);\n}\n\nzeus::CVector3f CBallCamera::ConstrainYawAngle(const CPlayer& player, float distance, float yawSpeed, float dt,\n                                               CStateManager& mgr) const {\n  zeus::CVector3f playerToCamFlat = GetTranslation() - player.GetTranslation();\n  playerToCamFlat.z() = 0.f;\n  zeus::CVector3f lookDir = player.GetTransform().basis[1];\n  if (player.GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed) {\n    lookDir = player.GetMoveDir();\n    TCastToConstPtr<CScriptDoor> door = mgr.GetObjectById(x3dc_tooCloseActorId);\n    if ((!door || !door->x2a8_26_isOpen) &&\n        (x400_state == EBallCameraState::Boost || x400_state == EBallCameraState::Chase)) {\n      lookDir = player.GetLeaveMorphDir();\n    }\n  }\n  if (player.GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Unmorphing) {\n    lookDir = player.GetLeaveMorphDir();\n  }\n  if (lookDir.canBeNormalized()) {\n    lookDir.normalize();\n  } else {\n    lookDir = -playerToCamFlat;\n  }\n  if (playerToCamFlat.canBeNormalized()) {\n    playerToCamFlat.normalize();\n  } else {\n    return -lookDir;\n  }\n  float angleProj = zeus::clamp(-1.f, playerToCamFlat.dot(-lookDir), 1.f);\n  if (angleProj >= 1.f) {\n    return -lookDir;\n  }\n  return zeus::CQuaternion::lookAt(playerToCamFlat, -lookDir,\n                                   distance * dt * zeus::clamp(0.f, std::acos(angleProj) / yawSpeed, 1.f))\n      .transform(playerToCamFlat);\n}\n\nvoid CBallCamera::CheckFailsafe(float dt, CStateManager& mgr) {\n  zeus::CVector3f ballPos = mgr.GetPlayer().GetBallPosition();\n  x18d_24_prevClearLOS = x18c_31_clearLOS;\n  zeus::CVector3f camToBall = ballPos - GetTranslation();\n  float camToBallMag = camToBall.magnitude();\n  camToBall.normalize();\n  EntityList nearList;\n  mgr.BuildNearList(nearList, GetTranslation(), camToBall, camToBallMag, BallCameraFilter, nullptr);\n  CRayCastResult result = mgr.RayWorldIntersection(x368_obscuringObjectId, GetTranslation(), camToBall, camToBallMag,\n                                                   BallCameraFilter, nearList);\n  if (result.IsValid()) {\n    x350_obscuringMaterial = result.GetMaterial();\n    if (!mgr.RayCollideWorld(GetTranslation(), ballPos, nearList, BallCameraFilter, &mgr.GetPlayer()) &&\n        !mgr.RayCollideWorld(GetTranslation(), mgr.GetPlayer().GetTranslation(), nearList, BallCameraFilter,\n                             &mgr.GetPlayer())) {\n      x18c_31_clearLOS = false;\n      if (x18d_24_prevClearLOS) {\n        x35c_splineIntermediatePos = ballPos;\n        if (ShouldResetSpline(mgr) && !x18e_25_noSpline && x350_obscuringMaterial.HasMaterial(EMaterialTypes::Floor) &&\n            mgr.RayCollideWorld(ballPos, ballPos + zeus::CVector3f(0.f, 0.f, -2.5f), nearList, BallCameraFilter,\n                                nullptr)) {\n          BuildSplineNav(mgr);\n        }\n      }\n    }\n  } else {\n    x18c_31_clearLOS = true;\n    x350_obscuringMaterial = CMaterialList(EMaterialTypes::NoStepLogic);\n  }\n\n  if (!x18c_31_clearLOS) {\n    x34c_obscuredTime += dt;\n    if (ShouldResetSpline(mgr) && !x18e_25_noSpline && x350_obscuringMaterial.HasMaterial(EMaterialTypes::Pillar)) {\n      BuildSplineArc(mgr);\n    }\n  } else {\n    x34c_obscuredTime = 0.f;\n  }\n\n  x358_unobscureMag = zeus::clamp(0.f, x34c_obscuredTime * 0.5f, 1.f);\n\n  x3e4_pendingFailsafe =\n      x18c_27_obscureAvoidance &&\n      (x34c_obscuredTime > 2.f || (x3dc_tooCloseActorId != kInvalidUniqueId && x34c_obscuredTime > 1.f)) &&\n      !x18c_31_clearLOS && x36c_splineState == ESplineState::Invalid;\n\n  bool doFailsafe = x3e4_pendingFailsafe;\n  if ((GetTranslation() - ballPos).magnitude() < 0.3f + g_tweakPlayer->GetPlayerBallHalfExtent()) {\n    doFailsafe = true;\n  }\n\n  if (x18e_27_nearbyDoorClosed) {\n    x18e_27_nearbyDoorClosed = false;\n    if (result.IsValid()) {\n      doFailsafe = true;\n    }\n  }\n\n  if (x18e_28_nearbyDoorClosing) {\n    x18e_28_nearbyDoorClosing = false;\n    if (IsBallNearDoor(GetTranslation(), mgr)) {\n      doFailsafe = true;\n    }\n  }\n\n  if (doFailsafe) {\n    ActivateFailsafe(dt, mgr);\n  }\n}\n\nvoid CBallCamera::UpdateObjectTooCloseId(CStateManager& mgr) {\n  x3e0_tooCloseActorDist = 1000000.f;\n  x3dc_tooCloseActorId = kInvalidUniqueId;\n  const zeus::CVector3f ballPos = mgr.GetPlayer().GetBallPosition();\n  for (const CEntity* ent : mgr.GetPlatformAndDoorObjectList()) {\n    if (const TCastToConstPtr<CScriptDoor> door = ent) {\n      if (mgr.GetPlayer().GetAreaIdAlways() == door->GetAreaIdAlways()) {\n        door->GetBoundingBox();\n        const float minMag = std::min((door->GetTranslation() - GetTranslation()).magnitude(),\n                                      (door->GetTranslation() - ballPos).magnitude());\n        if (minMag < 30.f && minMag < x3e0_tooCloseActorDist) {\n          x3dc_tooCloseActorId = door->GetUniqueId();\n          x3e0_tooCloseActorDist = minMag;\n        }\n      }\n    }\n  }\n}\n\nvoid CBallCamera::UpdateAnglePerSecond(float dt) {\n  float delta = x1a8_targetAnglePerSecond - x1a4_curAnglePerSecond;\n  if (std::fabs(delta) >= M_PIF / 1800.f) {\n    x1a4_curAnglePerSecond += zeus::clamp(-1.f, delta / M_PIF, 1.f) * (10.471975f * dt);\n  } else {\n    x1a4_curAnglePerSecond = x1a8_targetAnglePerSecond;\n  }\n}\n\nvoid CBallCamera::UpdateUsingPathCameras(float dt, CStateManager& mgr) {\n  if (const TCastToConstPtr<CPathCamera> cam = mgr.ObjectById(mgr.GetCameraManager()->GetPathCameraId())) {\n    TeleportCamera(cam->GetTransform(), mgr);\n    x18d_26_lookAtBall = true;\n  }\n}\n\nzeus::CVector3f CBallCamera::GetFixedLookTarget(const zeus::CVector3f& hintToLookDir, CStateManager& mgr) const {\n  const CScriptCameraHint* hint = mgr.GetCameraManager()->GetCameraHint(mgr);\n  if (hint == nullptr) {\n    return hintToLookDir;\n  }\n\n  zeus::CVector3f hintDir = hint->GetTransform().basis[1];\n  zeus::CVector3f hintDirFlat = hintDir;\n  hintDirFlat.z() = 0.f;\n  if (hintDir.canBeNormalized() && hintDirFlat.canBeNormalized()) {\n    hintDir.normalize();\n    hintDirFlat.normalize();\n  } else {\n    hintDir = zeus::skForward;\n    hintDirFlat = zeus::skForward;\n  }\n\n  zeus::CVector3f hintToLookDirFlat = hintToLookDir;\n  hintToLookDirFlat.z() = 0.f;\n  if (hintToLookDir.canBeNormalized() && hintToLookDirFlat.canBeNormalized()) {\n    hintToLookDirFlat.normalize();\n  } else {\n    hintToLookDirFlat = hintDirFlat;\n  }\n\n  float attitude = std::acos(zeus::clamp(-1.f, hintToLookDir.dot(hintToLookDirFlat), 1.f));\n  if (x18c_29_clampAttitude) {\n    float refAttitude = std::acos(zeus::clamp(-1.f, hintDir.dot(hintDirFlat), 1.f));\n    attitude = refAttitude + zeus::clamp(-x1ac_attitudeRange, attitude - refAttitude, x1ac_attitudeRange);\n  }\n\n  if (hintToLookDir.z() >= 0.f) {\n    attitude = -attitude;\n  }\n\n  float azimuth = std::acos(zeus::clamp(-1.f, hintToLookDirFlat.dot(hintDirFlat), 1.f));\n  if (x18c_30_clampAzimuth) {\n    azimuth = zeus::clamp(-x1b0_azimuthRange, azimuth, x1b0_azimuthRange);\n  }\n\n  if (hintToLookDirFlat.x() * hintDirFlat.y() - hintDirFlat.x() * hintToLookDirFlat.y() >= 0.f) {\n    azimuth = -azimuth;\n  }\n\n  zeus::CQuaternion quat;\n  quat.rotateZ(azimuth);\n  zeus::CVector3f aziLookDirFlat = quat.transform(hintDirFlat);\n  zeus::CVector3f attitudeAxis(aziLookDirFlat.y(), -aziLookDirFlat.x(), 0.f);\n  attitudeAxis.normalize();\n  return zeus::CQuaternion::fromAxisAngle(attitudeAxis, -attitude).transform(aziLookDirFlat);\n}\n\nvoid CBallCamera::UpdateUsingFixedCameras(float dt, CStateManager& mgr) {\n  if (const CScriptCameraHint* hint = mgr.GetCameraManager()->GetCameraHint(mgr)) {\n    switch (x188_behaviour) {\n    case EBallCameraBehaviour::HintFixedPosition: {\n      zeus::CVector3f hintToLookPos = x1d8_lookPos - hint->GetTranslation();\n      if (hintToLookPos.canBeNormalized()) {\n        hintToLookPos = GetFixedLookTarget(hintToLookPos.normalized(), mgr);\n        if ((hint->GetHint().GetOverrideFlags() & 0x40) != 0) {\n          x18d_26_lookAtBall = true;\n        }\n        UpdateTransform(hintToLookPos, hint->GetTranslation(), dt, mgr);\n      }\n      break;\n    }\n    case EBallCameraBehaviour::HintFixedTransform:\n      SetTransform(hint->GetTransform());\n      break;\n    default:\n      break;\n    }\n    TeleportCamera(GetTranslation(), mgr);\n  }\n}\n\nzeus::CVector3f CBallCamera::ComputeVelocity(const zeus::CVector3f& curVel, const zeus::CVector3f& posDelta) const {\n  zeus::CVector3f ret = posDelta;\n  if (x470_clampVelTimer > 0.f && ret.canBeNormalized() && !x18d_28_obtuseDirection) {\n    float mag = ret.magnitude();\n    mag = zeus::clamp(-x474_clampVelRange, mag, x474_clampVelRange);\n    ret = ret.normalized() * mag;\n  }\n  return ret;\n}\n\nzeus::CVector3f CBallCamera::TweenVelocity(const zeus::CVector3f& curVel, const zeus::CVector3f& newVel, float rate,\n                                           float dt) {\n  zeus::CVector3f velDelta = newVel - curVel;\n  if (velDelta.canBeNormalized()) {\n    float t = zeus::clamp(-1.f, velDelta.magnitude() / (rate * dt), 1.f);\n    return velDelta.normalized() * rate * dt * t + curVel;\n  }\n  return newVel;\n}\n\nzeus::CVector3f CBallCamera::MoveCollisionActor(const zeus::CVector3f& pos, float dt, CStateManager& mgr) {\n  if (const TCastToPtr<CPhysicsActor> act = mgr.ObjectById(x46c_collisionActorId)) {\n    const zeus::CVector3f posDelta = pos - act->GetTranslation();\n    if (!posDelta.canBeNormalized() || posDelta.magnitude() < 0.01f) {\n      act->Stop();\n      return act->GetTranslation();\n    }\n    const zeus::CVector3f oldTranslation = act->GetTranslation();\n    const zeus::CVector3f oldVel = act->GetVelocity();\n    const zeus::CVector3f newVel = ComputeVelocity(oldVel, posDelta * (1.f / dt));\n    act->SetVelocityWR(newVel);\n    act->SetMovable(true);\n    act->AddMaterial(EMaterialTypes::Solid, mgr);\n    CGameCollision::Move(mgr, *act, dt, nullptr);\n    zeus::CVector3f posDelta2 = act->GetTranslation() - pos;\n    if (posDelta2.canBeNormalized() && posDelta2.magnitude() > 0.1f) {\n      act->SetTranslation(oldTranslation);\n      act->SetVelocityWR(TweenVelocity(oldVel, newVel, 50.f, dt));\n      CGameCollision::Move(mgr, *act, dt, nullptr);\n      posDelta2 = act->GetTranslation() - pos;\n      if (posDelta2.magnitude() > 0.1f) {\n        x478_shortMoveCount += 1;\n      } else {\n        x478_shortMoveCount = 0;\n      }\n    } else {\n      act->Stop();\n      x478_shortMoveCount = 0;\n    }\n    act->SetMovable(false);\n    act->RemoveMaterial(EMaterialTypes::Solid, mgr);\n    return act->GetTranslation();\n  }\n  return pos;\n}\n\nvoid CBallCamera::UpdateUsingFreeLook(float dt, CStateManager& mgr) {\n  if (x400_state == EBallCameraState::ToBall || x400_state == EBallCameraState::FromBall) {\n    x36c_splineState = ESplineState::Invalid;\n    return;\n  }\n\n  if (x36c_splineState == ESplineState::Nav && x188_behaviour <= EBallCameraBehaviour::SpindleCamera &&\n      x188_behaviour >= EBallCameraBehaviour::HintFixedPosition) {\n    x36c_splineState = ESplineState::Invalid;\n    return;\n  }\n\n  float elevation = x1a0_elevation;\n  float distance = x190_curMinDistance;\n  ConstrainElevationAndDistance(elevation, distance, 0.f, mgr);\n\n  zeus::CVector3f ballPos = mgr.GetPlayer().GetBallPosition();\n  zeus::CVector3f knotToBall = ballPos - x37c_camSpline.GetKnotPosition(2);\n  if (knotToBall.canBeNormalized()) {\n    knotToBall.normalize();\n  } else {\n    knotToBall = mgr.GetPlayer().GetMoveDir();\n  }\n  zeus::CVector3f knot3 = x37c_camSpline.GetKnotPosition(3);\n  zeus::CVector3f desiredPos = FindDesiredPosition(distance, elevation, knotToBall, mgr, false);\n\n  if (x370_24_reevalSplineEnd) {\n    x37c_camSpline.SetKnotPosition(3, desiredPos);\n  }\n\n  x374_splineCtrl -= dt;\n\n  float splineT = 1.f - zeus::clamp(0.f, x374_splineCtrl / x378_splineCtrlRange, 1.f);\n  if (x36c_splineState == ESplineState::Nav) {\n    CMaterialList intersectMat;\n    if (!SplineIntersectTest(intersectMat, mgr)) {\n      x37c_camSpline.SetKnotPosition(3, knot3);\n      if (intersectMat.HasMaterial(EMaterialTypes::Floor)) {\n        x36c_splineState = ESplineState::Invalid;\n        return;\n      }\n    }\n  }\n\n  if (x374_splineCtrl <= 0.f || (splineT > 0.75f && x18c_31_clearLOS)) {\n    if (x36c_splineState == ESplineState::Arc && !x18c_31_clearLOS) {\n      CMaterialList intersectMat;\n      if (!SplineIntersectTest(intersectMat, mgr)) {\n        x36c_splineState = ESplineState::Invalid;\n      } else {\n        zeus::CVector3f oldKnot2 = x37c_camSpline.GetKnotPosition(2);\n        zeus::CVector3f oldKnot1 = x37c_camSpline.GetKnotPosition(1);\n        BuildSplineArc(mgr);\n        x37c_camSpline.SetKnotPosition(3, x37c_camSpline.GetKnotPosition(1));\n        x37c_camSpline.SetKnotPosition(2, x37c_camSpline.GetKnotPosition(0));\n        x37c_camSpline.SetKnotPosition(1, oldKnot2);\n        x37c_camSpline.SetKnotPosition(0, oldKnot1);\n        x37c_camSpline.UpdateSplineLength();\n        x374_splineCtrl =\n            x378_splineCtrlRange - x378_splineCtrlRange * (x37c_camSpline.GetKnotT(2) / x37c_camSpline.x44_length);\n        x374_splineCtrl -= dt;\n        splineT = zeus::clamp(0.f, x374_splineCtrl / x378_splineCtrlRange, 1.f);\n      }\n    } else {\n      x36c_splineState = ESplineState::Invalid;\n    }\n  }\n\n  x37c_camSpline.UpdateSplineLength();\n  const zeus::CVector3f pos =\n      x37c_camSpline.GetInterpolatedSplinePointByLength(splineT * x37c_camSpline.x44_length).origin;\n  if (const TCastToPtr<CPhysicsActor> act = mgr.ObjectById(x46c_collisionActorId)) {\n    CMaterialFilter filter = act->GetMaterialFilter();\n    CMaterialFilter tmpFilter = filter;\n    tmpFilter.IncludeList().Add(EMaterialTypes::Wall);\n    tmpFilter.ExcludeList().Add(x3c8_collisionExcludeList);\n    act->SetMaterialFilter(tmpFilter);\n    MoveCollisionActor(pos, dt, mgr);\n    act->SetMaterialFilter(filter);\n  }\n\n  zeus::CVector3f lookDir = x1d8_lookPos - desiredPos;\n  if (x18d_26_lookAtBall) {\n    lookDir = ballPos - desiredPos;\n  }\n\n  if (lookDir.canBeNormalized()) {\n    lookDir.normalize();\n    UpdateTransform(lookDir, desiredPos, dt, mgr);\n  }\n\n  TeleportCamera(desiredPos, mgr);\n\n  if (x3d0_24_camBehindFloorOrWall && x374_splineCtrl / x378_splineCtrlRange < 0.5f) {\n    x36c_splineState = ESplineState::Invalid;\n  }\n}\n\nzeus::CVector3f CBallCamera::InterpolateCameraElevation(const zeus::CVector3f& camPos, float dt) {\n  if (x1a0_elevation < 2.f) {\n    return camPos;\n  }\n\n  zeus::CVector3f ret = camPos;\n  if (!x18c_31_clearLOS && x350_obscuringMaterial.HasMaterial(EMaterialTypes::Floor)) {\n    x3d4_elevInterpTimer = 1.f;\n    ret.z() = x3d8_elevInterpStart = GetTranslation().z();\n  } else if (x3d4_elevInterpTimer > 0.f) {\n    x3d4_elevInterpTimer -= dt;\n    ret.z() = (camPos.z() - x3d8_elevInterpStart) * (1.f - zeus::clamp(0.f, x3d4_elevInterpTimer, 1.f)) +\n              x3d8_elevInterpStart;\n  }\n\n  return ret;\n}\n\nzeus::CVector3f CBallCamera::CalculateCollidersCentroid(const std::vector<CCameraCollider>& colliderList,\n                                                        int numObscured) const {\n  if (colliderList.size() < 3) {\n    return zeus::skForward;\n  }\n\n  int clearColliders = 0;\n  const CCameraCollider* prevCol = &colliderList.back();\n  float accumCross = 0.f;\n  float accumX = 0.f;\n  float accumZ = 0.f;\n  for (const CCameraCollider& col : colliderList) {\n    if (prevCol->x4c_occlusionCount < 2 && col.x4c_occlusionCount < 2) {\n      float z0 = prevCol->x50_scale * prevCol->x8_lastLocalPos.z();\n      float x1 = prevCol->x50_scale * col.x8_lastLocalPos.x();\n      float x0 = prevCol->x50_scale * prevCol->x8_lastLocalPos.x();\n      float z1 = prevCol->x50_scale * col.x8_lastLocalPos.z();\n\n      float cross = x0 * z1 - x1 * z0;\n      accumCross += cross;\n      accumX += cross * (x1 + x0);\n      accumZ += cross * (z1 + z0);\n    } else {\n      clearColliders += 1;\n    }\n    prevCol = &col;\n  }\n\n  if (static_cast<float>(clearColliders / colliderList.size()) <= x330_clearColliderThreshold) {\n    return zeus::skForward;\n  }\n  if (0.f != accumCross) {\n    float baryCross = 3.f * accumCross;\n    return {accumX / baryCross, 0.f, accumZ / baryCross};\n  }\n\n  return {0.f, 2.f, 0.f};\n}\n\nzeus::CVector3f CBallCamera::ApplyColliders() {\n  zeus::CVector3f smallCentroid = CalculateCollidersCentroid(x264_smallColliders, x2c4_smallCollidersObsCount);\n  zeus::CVector3f mediumCentroid = CalculateCollidersCentroid(x274_mediumColliders, x2c8_mediumCollidersObsCount);\n  zeus::CVector3f largeCentroid = CalculateCollidersCentroid(x284_largeColliders, x2cc_largeCollidersObsCount);\n\n  if (smallCentroid.y() == 0.f) {\n    x2a0_smallCentroid = smallCentroid;\n  } else {\n    x2a0_smallCentroid = zeus::skZero3f;\n  }\n\n  float centroidX = x2a0_smallCentroid.x();\n  float centroidZ = x2a0_smallCentroid.z();\n\n  if (mediumCentroid.y() == 0.f) {\n    x2ac_mediumCentroid = mediumCentroid;\n  } else {\n    x2ac_mediumCentroid = zeus::skZero3f;\n  }\n\n  centroidX += x2ac_mediumCentroid.x();\n  centroidZ += x2ac_mediumCentroid.z();\n\n  if (largeCentroid.y() == 0.f) {\n    x2b8_largeCentroid = largeCentroid;\n  } else {\n    x2b8_largeCentroid = zeus::skZero3f;\n  }\n\n  centroidX += x2b8_largeCentroid.x();\n  centroidZ += x2b8_largeCentroid.z();\n\n  if (x18c_31_clearLOS) {\n    centroidX /= 1.5f;\n  }\n  centroidZ /= 3.f;\n\n  if (!x18c_31_clearLOS && x368_obscuringObjectId == kInvalidUniqueId) {\n    float xMul = 1.5f;\n    float zMul = 1.f;\n    if (x350_obscuringMaterial.HasMaterial(EMaterialTypes::Floor)) {\n      zMul += 2.f * x358_unobscureMag;\n    }\n    if (x350_obscuringMaterial.HasMaterial(EMaterialTypes::Wall)) {\n      xMul += 3.f * zeus::clamp(0.f, x358_unobscureMag - 0.25f, 1.f);\n    }\n    centroidX *= xMul;\n    centroidZ *= zMul;\n  }\n\n  if (!x18c_28_volumeCollider) {\n    return zeus::skZero3f;\n  }\n\n  if (std::fabs(centroidX) < 0.05f) {\n    centroidX = 0.f;\n  }\n  if (std::fabs(centroidZ) < 0.05f) {\n    centroidZ = 0.f;\n  }\n\n  if (x18c_31_clearLOS) {\n    centroidZ *= 0.5f;\n  }\n\n  return {centroidX, 0.f, centroidZ};\n}\n\nvoid CBallCamera::UpdateColliders(const zeus::CTransform& xf, std::vector<CCameraCollider>& colliderList, int& it,\n                                  int count, float tolerance, const EntityList& nearList, float dt,\n                                  CStateManager& mgr) {\n  if (it < colliderList.size()) {\n    x310_idealLookVec = {0.f, g_tweakBall->GetBallCameraOffset().y(), g_tweakPlayer->GetPlayerBallHalfExtent()};\n    x310_idealLookVec.y() *= x308_speedFactor;\n    x31c_predictedLookPos = mgr.GetPlayer().GetMoveDir() * x310_idealLookVec.y();\n    x31c_predictedLookPos.z() = float(x310_idealLookVec.z());\n    x31c_predictedLookPos += mgr.GetPlayer().GetTranslation();\n    zeus::CTransform predictedLookXf = zeus::lookAt(xf.origin, x31c_predictedLookPos);\n    float toleranceRecip = 1.f / tolerance;\n    for (int i = 0; i < count; ++i) {\n      zeus::CVector3f localPos = colliderList[it].x14_localPos;\n      zeus::CVector3f worldPos = predictedLookXf.rotate(localPos) + predictedLookXf.origin;\n      if ((colliderList[it].x2c_lastWorldPos - worldPos).magnitude() < 0.1f) {\n        localPos = colliderList[it].x8_lastLocalPos;\n        worldPos = colliderList[it].x2c_lastWorldPos;\n      }\n      zeus::CVector3f centerToCollider = worldPos - predictedLookXf.origin;\n      float mag = centerToCollider.magnitude();\n      if (centerToCollider.canBeNormalized()) {\n        centerToCollider.normalize();\n        TUniqueId intersectId = kInvalidUniqueId;\n        CRayCastResult result = mgr.RayWorldIntersection(intersectId, predictedLookXf.origin, centerToCollider,\n                                                         mag + colliderList[it].x4_radius, BallCameraFilter, nearList);\n        if (result.IsValid()) {\n          zeus::CVector3f centerToPoint = centerToCollider * (result.GetT() - colliderList[it].x4_radius);\n          worldPos = centerToPoint + predictedLookXf.origin;\n          localPos = predictedLookXf.getRotation().inverse() * centerToPoint;\n        }\n      }\n      colliderList[it].x2c_lastWorldPos = worldPos;\n      colliderList[it].x8_lastLocalPos = localPos;\n      zeus::CVector3f scaledWorldColliderPos = centerToCollider * mag * toleranceRecip;\n      scaledWorldColliderPos = scaledWorldColliderPos * x308_speedFactor + x31c_predictedLookPos;\n      colliderList[it].x20_scaledWorldPos = scaledWorldColliderPos;\n      if (mgr.RayCollideWorld(worldPos, scaledWorldColliderPos, nearList, BallCameraFilter, nullptr)) {\n        colliderList[it].x4c_occlusionCount = 0;\n      } else {\n        colliderList[it].x4c_occlusionCount += 1;\n      }\n      it += 1;\n      if (it == colliderList.size()) {\n        it = 0;\n      }\n    }\n  }\n}\n\nzeus::CVector3f CBallCamera::AvoidGeometry(const zeus::CTransform& xf, const EntityList& nearList, float dt,\n                                           CStateManager& mgr) {\n  switch (x328_avoidGeomCycle) {\n  case 0:\n    UpdateColliders(xf, x264_smallColliders, x2d0_smallColliderIt, 1, 4.f, nearList, dt, mgr);\n    break;\n  case 1:\n    UpdateColliders(xf, x274_mediumColliders, x2d4_mediumColliderIt, 3, 4.f, nearList, dt, mgr);\n    break;\n  case 2:\n  case 3:\n    UpdateColliders(xf, x284_largeColliders, x2d8_largeColliderIt, 4, 4.f, nearList, dt, mgr);\n    break;\n  default:\n    break;\n  }\n\n  x328_avoidGeomCycle += 1;\n  if (x328_avoidGeomCycle >= 4) {\n    x328_avoidGeomCycle = 0;\n  }\n\n  return ApplyColliders();\n}\n\nzeus::CVector3f CBallCamera::AvoidGeometryFull(const zeus::CTransform& xf, const EntityList& nearList, float dt,\n                                               CStateManager& mgr) {\n  UpdateColliders(xf, x264_smallColliders, x2d0_smallColliderIt, x264_smallColliders.size(), 4.f, nearList, dt, mgr);\n  UpdateColliders(xf, x274_mediumColliders, x2d4_mediumColliderIt, x274_mediumColliders.size(), 4.f, nearList, dt, mgr);\n  UpdateColliders(xf, x284_largeColliders, x2d8_largeColliderIt, x284_largeColliders.size(), 4.f, nearList, dt, mgr);\n  return ApplyColliders();\n}\n\nzeus::CAABox CBallCamera::CalculateCollidersBoundingBox(const std::vector<CCameraCollider>& colliderList,\n                                                        CStateManager& mgr) const {\n  zeus::CAABox aabb;\n  for (const CCameraCollider& col : colliderList) {\n    aabb.accumulateBounds(col.x2c_lastWorldPos);\n  }\n  aabb.accumulateBounds(mgr.GetPlayer().GetTranslation());\n  return aabb;\n}\n\nint CBallCamera::CountObscuredColliders(const std::vector<CCameraCollider>& colliderList) const {\n  int ret = 0;\n  for (const CCameraCollider& c : colliderList) {\n    if (c.x4c_occlusionCount >= 2) {\n      ++ret;\n    }\n  }\n  return ret;\n}\n\nvoid CBallCamera::UpdateCollidersDistances(std::vector<CCameraCollider>& colliderList, float xMag, float zMag,\n                                           float angOffset) {\n  float theta = angOffset;\n  for (CCameraCollider& col : colliderList) {\n    float z = std::cos(theta) * zMag;\n    if (theta > M_PIF / 2.f) {\n      z *= 0.25f;\n    }\n    col.x14_localPos = {std::sin(theta) * xMag, 0.f, z};\n    theta += 2.f * M_PIF / float(colliderList.size());\n  }\n}\n\nvoid CBallCamera::UpdateUsingColliders(float dt, CStateManager& mgr) {\n  if (mgr.GetPlayer().GetBombJumpCount() == 1) {\n    return;\n  }\n\n  zeus::CVector3f ballPos = mgr.GetPlayer().GetBallPosition();\n\n  if (mgr.GetPlayer().GetBombJumpCount() == 2) {\n    zeus::CVector3f camToLookDir = x1d8_lookPos - GetTranslation();\n    if (x18d_26_lookAtBall) {\n      camToLookDir = ballPos - GetTranslation();\n    }\n\n    if (camToLookDir.canBeNormalized()) {\n      camToLookDir.normalize();\n      UpdateTransform(camToLookDir, GetTranslation(), dt, mgr);\n    }\n  } else if (mgr.GetPlayer().GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Unmorphed ||\n             x18d_25_avoidGeometryFull) {\n    // zeus::CTransform oldXf = x34_transform;\n    zeus::CVector3f oldPos = GetTranslation();\n    x2c4_smallCollidersObsCount = CountObscuredColliders(x264_smallColliders);\n    x2c8_mediumCollidersObsCount = CountObscuredColliders(x274_mediumColliders);\n    x2cc_largeCollidersObsCount = CountObscuredColliders(x284_largeColliders);\n    zeus::CVector3f posAtBallLevel = {0.f, 0.f, GetTranslation().z() - ballPos.z()};\n    zeus::CVector3f ballToCamFlat = GetTranslation() - ballPos;\n    ballToCamFlat.z() = 0.f;\n    float ballToCamFlatMag = 0.f;\n    if (ballToCamFlat.canBeNormalized()) {\n      ballToCamFlatMag = ballToCamFlat.magnitude();\n    } else {\n      ballToCamFlat = -mgr.GetPlayer().GetMoveDir();\n    }\n    posAtBallLevel = GetTranslation() - posAtBallLevel;\n    zeus::CTransform ballToUnderCamLook;\n    if ((posAtBallLevel - ballPos).canBeNormalized()) {\n      ballToUnderCamLook = zeus::lookAt(ballPos, posAtBallLevel);\n    }\n    float distance =\n        x214_ballCameraSpring.ApplyDistanceSpring(x190_curMinDistance, ballToCamFlatMag, (3.f + x308_speedFactor) * dt);\n    zeus::CVector3f camToBall = ballPos - GetTranslation();\n    camToBall.z() = 0.f;\n    if (camToBall.canBeNormalized()) {\n      camToBall.normalize();\n      if (std::fabs(std::acos(zeus::clamp(-1.f, camToBall.dot(mgr.GetPlayer().GetMoveDir()), 1.f))) >\n              zeus::degToRad(150.f) &&\n          mgr.GetPlayer().GetVelocity().canBeNormalized()) {\n        distance = x214_ballCameraSpring.ApplyDistanceSpring(\n            x308_speedFactor * (x19c_backwardsDistance - x190_curMinDistance) + x190_curMinDistance, ballToCamFlatMag,\n            3.f * dt);\n      }\n    }\n    x334_collidersAABB = CalculateCollidersBoundingBox(x284_largeColliders, mgr);\n    EntityList nearList;\n    mgr.BuildNearList(nearList, x334_collidersAABB, BallCameraFilter,\n                      TCastToConstPtr<CActor>(mgr.GetObjectById(x46c_collisionActorId)).GetPtr());\n    if (!x18c_31_clearLOS && x368_obscuringObjectId == kInvalidUniqueId) {\n      if (x34c_obscuredTime > 0.f || x350_obscuringMaterial.HasMaterial(EMaterialTypes::Floor) ||\n          x350_obscuringMaterial.HasMaterial(EMaterialTypes::Wall)) {\n        x32c_colliderMag += 2.f * dt;\n        if (x32c_colliderMag < 2.f) {\n          x32c_colliderMag = 2.f;\n        }\n        if (x32c_colliderMag > 2.f) {\n          x32c_colliderMag = 2.f;\n        }\n        UpdateCollidersDistances(x264_smallColliders, 2.31f * x32c_colliderMag, 2.31f * x32c_colliderMag * 0.5f,\n                                 -M_PIF / 2.f);\n        UpdateCollidersDistances(x274_mediumColliders, 4.62f * x32c_colliderMag, 4.62f * x32c_colliderMag * 0.5f,\n                                 -M_PIF / 2.f);\n        UpdateCollidersDistances(x284_largeColliders, 7.f * x32c_colliderMag, 7.f * x32c_colliderMag * 0.5f,\n                                 -M_PIF / 2.f);\n      }\n    } else {\n      float targetColliderMag = 1.f;\n      if (x18d_24_prevClearLOS && mgr.GetPlayer().GetMoveSpeed() < 1.f) {\n        targetColliderMag = 0.25f;\n      }\n      x32c_colliderMag += (targetColliderMag - x32c_colliderMag) * dt * 2.f;\n      UpdateCollidersDistances(x264_smallColliders, x32c_colliderMag * 2.31f, x32c_colliderMag * 2.31f, -M_PIF / 2.f);\n      UpdateCollidersDistances(x274_mediumColliders, x32c_colliderMag * 4.62f, x32c_colliderMag * 4.62f, -M_PIF / 2.f);\n      UpdateCollidersDistances(x284_largeColliders, x32c_colliderMag * 7.f, x32c_colliderMag * 7.f, -M_PIF / 2.f);\n    }\n\n    float elevation = x1a0_elevation;\n    bool noDoor = !ConstrainElevationAndDistance(elevation, distance, dt, mgr);\n    zeus::CVector3f desiredBallToCam = ballToUnderCamLook.rotate({0.f, distance, elevation});\n\n    if (TCastToConstPtr<CScriptDoor> door = mgr.GetObjectById(x3dc_tooCloseActorId)) {\n      if (!door->x2a8_26_isOpen) {\n        if (x400_state == EBallCameraState::Boost) {\n          zeus::CVector3f ballToCam = GetTranslation() - ballPos;\n          if (ballToCam.canBeNormalized()) {\n            ballToCam.normalize();\n          } else {\n            ballToCam = GetTransform().basis[1];\n          }\n          if (std::fabs(ballToCamFlatMag - x430_boostElevation) < 1.f) {\n            ballToCam = ConstrainYawAngle(mgr.GetPlayer(), g_tweakBall->GetBallCameraBoostDistance(),\n                                          g_tweakBall->GetBallCameraBoostYawSpeed(), dt, mgr);\n          }\n          ballToCam.normalize();\n          ballToCam.z() = 0.f;\n          ballToCam = ballToCam * distance;\n          ballToCam.z() = 1.f;\n          desiredBallToCam = ballToCam;\n          noDoor = false;\n        }\n        if (x18c_25_chaseAllowed &&\n            (x400_state == EBallCameraState::Chase || x188_behaviour == EBallCameraBehaviour::FreezeLookPosition)) {\n          zeus::CVector3f ballToCam = GetTranslation() - ballPos;\n          if (ballToCam.canBeNormalized()) {\n            ballToCam.normalize();\n          } else {\n            ballToCam = GetTransform().basis[1];\n          }\n          if (std::fabs(ballToCamFlatMag - x404_chaseElevation) < 3.f) {\n            ballToCam = ConstrainYawAngle(mgr.GetPlayer(), g_tweakBall->GetBallCameraChaseDistance(),\n                                          g_tweakBall->GetBallCameraChaseYawSpeed(), dt, mgr);\n          }\n          ballToCam.z() = 0.f;\n          ballToCam.normalize();\n          ballToCam = ballToCam * distance;\n          ballToCam.z() = g_tweakBall->GetBallCameraElevation();\n          desiredBallToCam = ballToCam;\n          noDoor = false;\n        }\n      }\n    }\n\n    if (x188_behaviour == EBallCameraBehaviour::HintBallToCam) {\n      desiredBallToCam = x45c_overrideBallToCam;\n      if (x18c_27_obscureAvoidance) {\n        zeus::CVector3f ballToCamDir = x45c_overrideBallToCam;\n        if (ballToCamDir.canBeNormalized()) {\n          ballToCamDir.normalize();\n        } else {\n          ballToCamDir = -mgr.GetPlayer().GetMoveDir();\n        }\n        TUniqueId intersectId = kInvalidUniqueId;\n        CRayCastResult result =\n            mgr.RayWorldIntersection(intersectId, ballPos, ballToCamDir, distance, BallCameraFilter, nearList);\n        if (result.IsValid()) {\n          desiredBallToCam = ballToCamDir * result.GetT() * 0.9f;\n        }\n      }\n      noDoor = false;\n    }\n\n    distance = desiredBallToCam.magnitude();\n    zeus::CVector3f desiredCamPos = ballPos + desiredBallToCam;\n    float d = 0.f;\n    if (DetectCollision(ballPos, desiredCamPos, 0.3f, d, mgr)) {\n      if (d >= 1.f) {\n        desiredBallToCam = desiredBallToCam.normalized() * d;\n        desiredCamPos = ballPos + desiredBallToCam;\n      } else {\n        desiredBallToCam = ballPos + GetTranslation();\n        desiredCamPos = GetTranslation();\n      }\n    }\n\n    zeus::CTransform lookXf = zeus::lookAt(desiredCamPos, x1d8_lookPos);\n    zeus::CTransform oldLookXf = zeus::lookAt(GetTranslation(), x1d8_lookPos);\n    x1e4_nextLookXf = lookXf;\n    lookXf = oldLookXf;\n    zeus::CVector3f colliderPointLocal;\n    if (x18d_25_avoidGeometryFull || !x18c_31_clearLOS) {\n      colliderPointLocal = AvoidGeometryFull(lookXf, nearList, dt, mgr);\n    } else {\n      colliderPointLocal = AvoidGeometry(lookXf, nearList, dt, mgr);\n    }\n\n    zeus::CVector3f ballToCam2 = GetTranslation() - ballPos;\n    ballToCam2.z() = 0.f;\n    if (ballToCam2.magnitude() < 2.f) {\n      if (x18c_31_clearLOS && x478_shortMoveCount > 2) {\n        colliderPointLocal = colliderPointLocal / float(x478_shortMoveCount);\n      }\n      if (d < 3.f) {\n        colliderPointLocal = colliderPointLocal * 0.25f;\n        if (x18c_31_clearLOS && x478_shortMoveCount > 0) {\n          colliderPointLocal = colliderPointLocal * x308_speedFactor;\n        }\n      }\n      if (d < 1.f) {\n        colliderPointLocal = zeus::skZero3f;\n      }\n    }\n\n    zeus::CVector3f camDelta = lookXf.rotate(colliderPointLocal) + desiredCamPos - ballPos;\n    if (camDelta.canBeNormalized()) {\n      camDelta.normalize();\n    }\n    zeus::CVector3f desiredPos = camDelta * distance + ballPos;\n\n    if (x188_behaviour == EBallCameraBehaviour::PathCameraDesiredPos) {\n      if (TCastToConstPtr<CPathCamera> cam = mgr.GetObjectById(mgr.GetCameraManager()->GetPathCameraId())) {\n        desiredPos = cam->GetTranslation();\n      }\n    }\n\n    camDelta = x294_dampedPos - desiredPos;\n    float camDeltaMag = camDelta.magnitude();\n    if (camDelta.canBeNormalized()) {\n      camDelta.normalize();\n    }\n\n    x294_dampedPos = camDelta * x228_ballCameraCentroidSpring.ApplyDistanceSpring(0.f, camDeltaMag, dt) + desiredPos;\n    zeus::CVector3f posDelta = oldPos - x294_dampedPos;\n    camDeltaMag = posDelta.magnitude();\n    if (posDelta.canBeNormalized()) {\n      posDelta.normalize();\n    }\n\n    float cDistSpringMag = x250_ballCameraCentroidDistanceSpring.ApplyDistanceSpring(\n        0.f, camDeltaMag, (x18d_28_obtuseDirection ? 3.f : 1.f) * dt);\n    if (x400_state == EBallCameraState::Boost) {\n      cDistSpringMag = x448_ballCameraBoostSpring.ApplyDistanceSpring(0.f, camDeltaMag, dt);\n    } else if (x18c_25_chaseAllowed &&\n               (x400_state == EBallCameraState::Chase || x188_behaviour == EBallCameraBehaviour::FreezeLookPosition)) {\n      cDistSpringMag = x41c_ballCameraChaseSpring.ApplyDistanceSpring(0.f, camDeltaMag, dt);\n    }\n\n    zeus::CVector3f finalPos = posDelta * cDistSpringMag + x294_dampedPos;\n    if (mgr.GetPlayer().GetMorphBall()->GetSpiderBallState() != CMorphBall::ESpiderBallState::Active &&\n        !x18e_24_noElevationVelClamp && mgr.GetPlayer().GetVelocity().z() > 8.f) {\n      zeus::CVector3f delta = finalPos - oldPos;\n      delta.z() = zeus::clamp(-0.1f * dt, float(delta.z()), 0.1f * dt);\n      finalPos = oldPos + delta;\n    }\n\n    if (noDoor && x400_state != EBallCameraState::ToBall) {\n      finalPos = InterpolateCameraElevation(finalPos, dt);\n    }\n\n    if (x18d_29_noElevationInterp) {\n      finalPos.z() = elevation + ballPos.z();\n    }\n\n    if (ballToCam2.magnitude() < 2.f) {\n      if (finalPos.z() < 2.f + ballPos.z()) {\n        finalPos.z() = 2.f + ballPos.z();\n      }\n      x214_ballCameraSpring.Reset();\n    }\n\n    finalPos = ClampElevationToWater(finalPos, mgr);\n    if (ballToCam2.magnitude() < 2.f && x3dc_tooCloseActorId != kInvalidUniqueId && x3e0_tooCloseActorDist < 5.f) {\n      if (TCastToConstPtr<CScriptDoor> door = mgr.GetObjectById(x3dc_tooCloseActorId)) {\n        if (!door->x2a8_26_isOpen) {\n          finalPos = GetTranslation();\n        }\n      }\n    }\n\n    float backupZ = finalPos.z();\n    finalPos = MoveCollisionActor(finalPos, dt, mgr);\n\n    if (x18c_31_clearLOS && x478_shortMoveCount > 0) {\n      finalPos.z() = backupZ;\n      finalPos = MoveCollisionActor(finalPos, dt, mgr);\n    }\n\n    zeus::CVector3f lookDir = x1d8_lookPos - finalPos;\n    if (x18d_26_lookAtBall) {\n      lookDir = ballPos - finalPos;\n    }\n    if (lookDir.canBeNormalized()) {\n      lookDir.normalize();\n      UpdateTransform(lookDir, finalPos, dt, mgr);\n    }\n\n    if (x470_clampVelTimer > 0.f) {\n      x470_clampVelTimer -= dt;\n    }\n  }\n}\n\nvoid CBallCamera::UpdateUsingSpindleCameras(float dt, CStateManager& mgr) {\n  if (const TCastToConstPtr<CScriptSpindleCamera> cam = mgr.ObjectById(mgr.GetCameraManager()->GetSpindleCameraId())) {\n    TeleportCamera(cam->GetTransform(), mgr);\n    x18d_26_lookAtBall = true;\n  }\n}\n\nzeus::CVector3f CBallCamera::ClampElevationToWater(zeus::CVector3f& pos, CStateManager& mgr) const {\n  zeus::CVector3f ret = pos;\n  if (const TCastToConstPtr<CScriptWater> water = mgr.GetObjectById(mgr.GetPlayer().GetFluidId())) {\n    const float waterZ = water->GetTriggerBoundsWR().max.z();\n    if (pos.z() >= waterZ && pos.z() - waterZ <= 0.25f) {\n      ret.z() = 0.25f + waterZ;\n    } else if (pos.z() < waterZ && pos.z() - waterZ >= -0.12f) {\n      ret.z() = waterZ - 0.12f;\n    }\n  }\n  return ret;\n}\n\nvoid CBallCamera::UpdateTransitionFromBallCamera(CStateManager& mgr) {\n  float morphFactor = mgr.GetPlayer().GetMorphFactor();\n  zeus::CVector3f eyePos = mgr.GetPlayer().GetEyePosition();\n  zeus::CVector3f delta = mgr.GetPlayer().GetTranslation() - x47c_failsafeState->x84_playerPos;\n  x47c_failsafeState->x90_splinePoints[1] += delta;\n  x47c_failsafeState->x90_splinePoints[2] += delta;\n  x47c_failsafeState->x90_splinePoints[3] += delta;\n  zeus::CVector3f splinePoint = GetFailsafeSplinePoint(x47c_failsafeState->x90_splinePoints, morphFactor);\n  splinePoint.z() = (splinePoint.z() - eyePos.z()) * zeus::clamp(0.f, 1.f - 1.5f * morphFactor, 1.f) + eyePos.z();\n  zeus::CVector3f deltaFlat = eyePos - splinePoint;\n  deltaFlat.z() = 0.f;\n  if (deltaFlat.magnitude() > 0.001f) {\n    SetTransform(zeus::lookAt(splinePoint, zeus::CVector3f::lerp(x1d8_lookPos, eyePos, morphFactor)));\n  } else {\n    SetTransform(mgr.GetCameraManager()->GetFirstPersonCamera()->GetTransform());\n    SetTranslation(splinePoint);\n  }\n  mgr.GetCameraManager()->GetFirstPersonCamera()->Reset(x34_transform, mgr);\n  x47c_failsafeState->x84_playerPos = mgr.GetPlayer().GetTranslation();\n}\n\nvoid CBallCamera::UpdateUsingTransitions(float dt, CStateManager& mgr) {\n  if (x400_state == EBallCameraState::FromBall) {\n    UpdateTransitionFromBallCamera(mgr);\n    return;\n  }\n\n  x18d_26_lookAtBall = false;\n  zeus::CVector3f ballPos = mgr.GetPlayer().GetBallPosition();\n  zeus::CVector3f eyePos = mgr.GetPlayer().GetEyePosition();\n  ballPos.z() += x1b4_lookAtOffset.z();\n\n  zeus::CVector3f lookDir = x34_transform.basis[1];\n  zeus::CTransform xe8 = x34_transform;\n\n  switch (x400_state) {\n  case EBallCameraState::ToBall: {\n    float elevation = x1a0_elevation;\n    float distance = x194_targetMinDistance;\n    ConstrainElevationAndDistance(elevation, distance, dt, mgr);\n    distance = x194_targetMinDistance;\n    const bool r28 = IsBallNearDoor(GetTranslation(), mgr) || x478_shortMoveCount > 2;\n    const zeus::CVector3f toDesired =\n        FindDesiredPosition(distance, elevation, mgr.GetPlayer().GetMoveDir(), mgr, r28) - eyePos;\n    zeus::CVector3f finalPos = toDesired * mgr.GetPlayer().GetMorphFactor() + eyePos;\n    if (const TCastToPtr<CPhysicsActor> act = mgr.ObjectById(x46c_collisionActorId)) {\n      act->SetTranslation(GetTranslation());\n      finalPos = ClampElevationToWater(finalPos, mgr);\n      finalPos = MoveCollisionActor(finalPos, dt, mgr);\n      zeus::CVector3f camToLookDir = x1d8_lookPos - finalPos;\n      if (camToLookDir.canBeNormalized()) {\n        camToLookDir.normalize();\n        const float devDot = std::fabs(zeus::clamp(-1.f, lookDir.dot(camToLookDir), 1.f));\n        const float devAngle = zeus::clamp(-1.f, mgr.GetPlayer().GetMorphFactor() * 0.5f, 1.f) * std::acos(devDot);\n        if (devDot < 1.f) {\n          SetTransform(zeus::CQuaternion::lookAt(xe8.basis[1], camToLookDir, devAngle).toTransform() *\n                       xe8.getRotation());\n        } else {\n          SetTransform(zeus::lookAt(zeus::skZero3f, camToLookDir));\n        }\n      }\n    }\n    SetTransform(ValidateCameraTransform(x34_transform, xe8));\n    SetTranslation(finalPos);\n    TeleportCamera(finalPos, mgr);\n    break;\n  }\n  case EBallCameraState::FromBall: {\n    if (std::fabs(mgr.GetPlayer().GetMorphFactor() - 1.f) < 0.00001f) {\n      SetTransform(mgr.GetPlayer().GetTransform());\n      SetTranslation(mgr.GetPlayer().GetEyePosition());\n    } else {\n      float morphT = zeus::clamp(-1.f, mgr.GetPlayer().GetMorphFactor() / 0.9f, 1.f);\n      zeus::CVector3f finalPos = GetTranslation();\n      zeus::CVector3f eyeToCam = GetTranslation() - eyePos;\n      if (eyeToCam.canBeNormalized()) {\n        float distance = eyeToCam.magnitude();\n        distance = std::min(distance, (1.f - mgr.GetPlayer().GetMorphFactor()) * x190_curMinDistance);\n        float yawSpeed = M_PIF;\n        zeus::CVector3f playerToCamDir = GetTranslation() - mgr.GetPlayer().GetTranslation();\n        zeus::CVector3f moveDir = mgr.GetPlayer().GetMoveDir();\n        if (playerToCamDir.canBeNormalized()) {\n          playerToCamDir.normalize();\n        } else {\n          playerToCamDir = -moveDir;\n        }\n        if (moveDir.canBeNormalized()) {\n          moveDir.normalize();\n          yawSpeed = std::fabs(std::acos(zeus::clamp(-1.f, playerToCamDir.dot(-moveDir), 1.f))) * morphT / dt;\n        }\n        zeus::CVector3f useLookDir = ConstrainYawAngle(mgr.GetPlayer(), yawSpeed, zeus::degToRad(10.f), dt, mgr);\n        useLookDir.z() = 0.f;\n        useLookDir.normalize();\n        zeus::CVector3f camPos = useLookDir * distance + eyePos;\n        camPos.z() = (GetTranslation().z() - eyePos.z()) * morphT + eyePos.z();\n        finalPos = ClampElevationToWater(camPos, mgr);\n        finalPos = MoveCollisionActor(finalPos, dt, mgr);\n        zeus::CVector3f finalToBall = ballPos - finalPos;\n        finalToBall.z() = 0.f;\n        zeus::CVector3f lookPos = ballPos;\n        lookPos.z() = morphT * (eyePos.z() - ballPos.z()) + ballPos.z();\n        if (finalToBall.canBeNormalized()) {\n          SetTransform(zeus::lookAt(finalPos, lookPos));\n        } else {\n          SetTransform(mgr.GetCameraManager()->GetFirstPersonCamera()->GetTransform());\n        }\n      } else {\n        SetTransform(mgr.GetCameraManager()->GetFirstPersonCamera()->GetTransform());\n      }\n      SetTranslation(finalPos);\n    }\n    break;\n  }\n  default:\n    break;\n  }\n\n  mgr.GetCameraManager()->GetFirstPersonCamera()->Reset(x34_transform, mgr);\n}\n\nzeus::CTransform CBallCamera::UpdateCameraPositions(float dt, const zeus::CTransform& oldXf,\n                                                    const zeus::CTransform& newXf) {\n  zeus::CTransform useXf = newXf;\n  if (std::fabs(oldXf.basis[1].z()) > 0.9f && std::fabs(newXf.basis[1].z()) > 0.9f &&\n      oldXf.basis[0].dot(newXf.basis[0]) <= 0.999f) {\n    zeus::CVector3f newRight =\n        zeus::CQuaternion::clampedRotateTo(oldXf.basis[0], newXf.basis[0], zeus::degToRad(2.f * dt)).toTransform() *\n        oldXf.basis[0];\n    if (newRight.dot(newXf.basis[1]) <= 0.999f) {\n      zeus::CVector3f newUp = newXf.basis[1].cross(newRight).normalized();\n      zeus::CVector3f newForward = newXf.basis[1].normalized();\n      useXf = {newUp.cross(newForward), newXf.basis[1], newUp, newXf.origin};\n    }\n  }\n  return useXf;\n}\n\nzeus::CVector3f CBallCamera::GetFailsafeSplinePoint(const std::vector<zeus::CVector3f>& points, float t) {\n  t *= float(points.size() - 3);\n  int baseIdx = 0;\n  while (t > 1.f) {\n    t -= 1.f;\n    baseIdx += 1;\n  }\n  return zeus::getBezierPoint(points[baseIdx], points[baseIdx + 1], points[baseIdx + 2], points[baseIdx + 3], t);\n}\n\nbool CBallCamera::CheckFailsafeFromMorphBallState(CStateManager& mgr) const {\n  TUniqueId xbb8 = kInvalidUniqueId;\n  float curT = 0.f;\n  EntityList nearList;\n  rstl::reserved_vector<CRayCastResult, 6> resultsA;\n  rstl::reserved_vector<CRayCastResult, 6> resultsB;\n  while (curT < 6.f) {\n    zeus::CVector3f pointA = GetFailsafeSplinePoint(x47c_failsafeState->x90_splinePoints, curT / 6.f);\n    zeus::CVector3f pointB = GetFailsafeSplinePoint(x47c_failsafeState->x90_splinePoints, (1.f + curT) / 6.f);\n    zeus::CVector3f pointDelta = pointB - pointA;\n    if (pointDelta.magnitude() > 0.1f) {\n      resultsA.push_back(mgr.RayWorldIntersection(xbb8, pointA, pointDelta.normalized(), pointDelta.magnitude(),\n                                                  BallCameraFilter, nearList));\n      resultsB.push_back(mgr.RayWorldIntersection(xbb8, pointB, -pointDelta.normalized(), pointDelta.magnitude(),\n                                                  BallCameraFilter, nearList));\n    } else {\n      resultsA.push_back({});\n      resultsB.push_back({});\n    }\n    curT += 1.f;\n  }\n  for (size_t i = 0; i < resultsA.size(); ++i) {\n    const CRayCastResult& resA = resultsA[i];\n    const CRayCastResult& resB = resultsB[i];\n    if (resA.IsValid()) {\n      zeus::CVector3f separation = resA.GetPoint() - resB.GetPoint();\n      if (separation.magnitude() < 0.00001f) {\n        separation = GetFailsafeSplinePoint(x47c_failsafeState->x90_splinePoints, (1.f + i) / 6.f) - resA.GetPoint();\n      }\n      if (separation.magnitude() > 0.3f) {\n        return false;\n      }\n    }\n  }\n  return true;\n}\n\nbool CBallCamera::SplineIntersectTest(CMaterialList& intersectMat, CStateManager& mgr) const {\n  EntityList nearList;\n  TUniqueId xe38 = kInvalidUniqueId;\n  rstl::reserved_vector<CRayCastResult, 12> xacc;\n  rstl::reserved_vector<CRayCastResult, 12> xd10;\n  constexpr auto filter =\n      CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid, EMaterialTypes::Floor, EMaterialTypes::Wall},\n                                          {EMaterialTypes::ProjectilePassthrough, EMaterialTypes::Player,\n                                           EMaterialTypes::Character, EMaterialTypes::CameraPassthrough});\n  float curT = 0.f;\n  while (curT < 12.f) {\n    zeus::CVector3f xdb0 = x37c_camSpline.GetInterpolatedSplinePointByTime(curT, 12.f);\n    zeus::CVector3f xdbc = x37c_camSpline.GetInterpolatedSplinePointByTime(curT, 12.f);\n    zeus::CVector3f xdc8 = xdbc - xdb0;\n    if (xdc8.magnitude() > 0.1f) {\n      xacc.push_back(mgr.RayWorldIntersection(xe38, xdb0, xdc8.normalized(), xdc8.magnitude(), filter, nearList));\n      xd10.push_back(mgr.RayWorldIntersection(xe38, xdbc, -xdc8.normalized(), xdc8.magnitude(), filter, nearList));\n    } else {\n      xacc.push_back({});\n      xd10.push_back({});\n    }\n    curT += 1.f;\n  }\n  for (size_t i = 0; i < xacc.size(); ++i) {\n    const CRayCastResult& resA = xacc[i];\n    const CRayCastResult& resB = xd10[i];\n    if (resA.IsValid()) {\n      zeus::CVector3f xdd4 = resA.GetPoint() - resB.GetPoint();\n      if (xdd4.magnitude() < 0.00001f) {\n        xdd4 = x37c_camSpline.GetInterpolatedSplinePointByTime(1.f + i, 12.f) - resA.GetPoint();\n      }\n      if (xdd4.magnitude() > 0.3f) {\n        intersectMat = resA.GetMaterial();\n        return false;\n      }\n    }\n  }\n  return true;\n}\n\nbool CBallCamera::IsBallNearDoor(const zeus::CVector3f& pos, CStateManager& mgr) {\n  const TCastToConstPtr<CScriptDoor> door =\n      mgr.GetObjectById(mgr.GetCameraManager()->GetBallCamera()->x3dc_tooCloseActorId);\n  if (!door || door->x2a8_26_isOpen) {\n    return false;\n  }\n\n  const auto tb = door->GetTouchBounds();\n  const zeus::CAABox testAABB(pos - 0.3f, pos + 0.3f);\n  if (!tb || !tb->intersects(testAABB)) {\n    return false;\n  }\n\n  if (const TCastToConstPtr<CScriptDock> dock = mgr.GetObjectById(door->x282_dockId)) {\n    if (std::fabs(dock->GetPlane(mgr).pointToPlaneDist(pos)) < 1.15f) {\n      return true;\n    }\n  }\n\n  return false;\n}\n\nvoid CBallCamera::ActivateFailsafe(float dt, CStateManager& mgr) {\n  float elevation = x1a0_elevation;\n  float distance = x194_targetMinDistance;\n  ConstrainElevationAndDistance(elevation, distance, dt, mgr);\n  zeus::CVector3f desiredPos = FindDesiredPosition(distance, elevation, mgr.GetPlayer().GetMoveDir(), mgr, true);\n  SetTranslation(desiredPos);\n  ResetPosition(mgr);\n  TeleportCamera(zeus::lookAt(desiredPos, x1d8_lookPos), mgr);\n  mgr.GetCameraManager()->SetPlayerCamera(mgr, GetUniqueId());\n  x3e4_pendingFailsafe = false;\n  x34c_obscuredTime = 0.f;\n}\n\nbool CBallCamera::ConstrainElevationAndDistance(float& elevation, float& distance, float dt, CStateManager& mgr) {\n  zeus::CVector3f ballToCam = GetTranslation() - mgr.GetPlayer().GetBallPosition();\n  float ballToCamMag = 0.f;\n  if (ballToCam.canBeNormalized()) {\n    ballToCamMag = ballToCam.toVec2f().magnitude();\n  } else {\n    ballToCam = -mgr.GetPlayer().GetMoveDir();\n  }\n\n  bool doorClose = false;\n  float stretchFac = 1.f;\n  float newDistance = distance;\n  float baseElevation = elevation;\n  float springSpeed = 1.f;\n  if (const TCastToConstPtr<CScriptDoor> door = mgr.GetObjectById(x3dc_tooCloseActorId)) {\n    if (!door->x2a8_29_ballDoor) {\n      stretchFac = zeus::clamp(-1.f, std::fabs(x3e0_tooCloseActorDist / (3.f * distance)), 1.f);\n      if (x3e0_tooCloseActorDist < 3.f * distance) {\n        doorClose = true;\n      }\n      if (door->x2a8_26_isOpen) {\n        newDistance = stretchFac * (distance - x468_conservativeDoorCamDistance) + x468_conservativeDoorCamDistance;\n      } else {\n        newDistance = stretchFac * (distance - 5.f) + 5.f;\n      }\n      if (x18d_28_obtuseDirection) {\n        newDistance *= 1.f + x308_speedFactor;\n      }\n      baseElevation = door->x2a8_26_isOpen ? 0.75f : 1.5f;\n      springSpeed = 4.f;\n    }\n  }\n\n  x214_ballCameraSpring.ApplyDistanceSpring(newDistance, ballToCamMag, dt * springSpeed);\n  distance = newDistance;\n  elevation = (elevation - baseElevation) * stretchFac + baseElevation;\n\n  return doorClose;\n}\n\nzeus::CVector3f CBallCamera::FindDesiredPosition(float distance, float elevation, const zeus::CVector3f& dir,\n                                                 CStateManager& mgr, bool fullTest) {\n  TCastToConstPtr<CPlayer> player = mgr.GetObjectById(xe8_watchedObject);\n  if (!player) {\n    return zeus::skZero3f;\n  }\n\n  zeus::CVector3f useDir = dir;\n  if (!dir.canBeNormalized()) {\n    useDir = zeus::skForward;\n  }\n\n  zeus::CTransform lookDirXf = zeus::lookAt(zeus::skZero3f, useDir);\n  zeus::CVector3f ballPos = player->GetBallPosition();\n  float elev = elevation;\n  float dist = distance;\n  ConstrainElevationAndDistance(elev, dist, 0.f, mgr);\n  zeus::CVector3f eyePos = player->GetEyePosition();\n  if (!mgr.RayCollideWorld(ballPos, eyePos, BallCameraFilter, nullptr)) {\n    eyePos = ballPos;\n  }\n\n  zeus::CVector3f idealLookVec(0.f, -dist, elev - (eyePos.z() - ballPos.z()));\n  idealLookVec = lookDirXf.getRotation() * idealLookVec;\n  zeus::CVector3f lookVec(0.f, distance, elev - (eyePos.z() - ballPos.z()));\n  float idealLookDist = idealLookVec.magnitude();\n  float resolveLOSIntervalAng = zeus::degToRad(30.f);\n  bool foundClear = false;\n  bool clear = !DetectCollision(eyePos, eyePos + idealLookVec, 0.3f, idealLookDist, mgr);\n  if (!clear && idealLookDist <= 0.f) {\n    zeus::CAABox x13ac(ballPos - distance, ballPos + distance);\n    x13ac.min.z() = float(ballPos.z());\n    x13ac.max.z() = elev + ballPos.z();\n    EntityList nearList;\n    mgr.BuildNearList(nearList, x13ac, BallCameraFilter,\n                      TCastToConstPtr<CActor>(mgr.GetObjectById(x46c_collisionActorId)).GetPtr());\n    zeus::CQuaternion rotNeg;\n    rotNeg.rotateZ(-resolveLOSIntervalAng);\n    zeus::CTransform xfNeg = rotNeg.toTransform();\n    zeus::CQuaternion rotPos;\n    rotPos.rotateZ(resolveLOSIntervalAng);\n    zeus::CTransform xfPos = rotPos.toTransform();\n    while (!foundClear && idealLookDist > dist) {\n      idealLookVec.normalize();\n      idealLookVec = idealLookVec * idealLookDist;\n      zeus::CVector3f lookVecNeg = xfNeg.rotate(idealLookVec);\n      zeus::CVector3f lookVecPos = xfPos.rotate(idealLookVec);\n      for (int i = 0; float(i) < 180.f / zeus::radToDeg(resolveLOSIntervalAng); ++i) {\n        if (mgr.RayCollideWorld(eyePos, eyePos + lookVecNeg, nearList, BallCameraFilter, nullptr)) {\n          foundClear = true;\n          lookVec = lookVecNeg;\n          break;\n        }\n\n        if (mgr.RayCollideWorld(eyePos, eyePos + lookVecPos, nearList, BallCameraFilter, nullptr)) {\n          foundClear = true;\n          lookVec = lookVecPos;\n          break;\n        }\n\n        lookVecNeg = xfNeg * lookVecNeg;\n        lookVecPos = xfPos * lookVecPos;\n      }\n      idealLookDist -= 0.3f;\n    }\n  } else {\n    if (idealLookDist < 2.f) {\n      idealLookVec.normalize();\n      idealLookVec = idealLookVec * 2.f;\n    }\n    zeus::CQuaternion rotNeg;\n    rotNeg.rotateZ(-resolveLOSIntervalAng);\n    zeus::CTransform xfNeg = rotNeg.toTransform();\n    zeus::CVector3f lookVecNeg = xfNeg * idealLookVec;\n    zeus::CQuaternion rotPos;\n    rotPos.rotateZ(resolveLOSIntervalAng);\n    zeus::CTransform xfPos = rotPos.toTransform();\n    zeus::CVector3f lookVecPos = xfPos * idealLookVec;\n    if (clear || (!fullTest && (idealLookDist > 2.f || x2e8_ballVelFlat > 1.25f))) {\n      idealLookVec.normalize();\n      lookVec = idealLookVec * idealLookDist;\n      foundClear = true;\n    } else {\n      for (int i = 0; float(i) < 180.f / zeus::radToDeg(resolveLOSIntervalAng); ++i) {\n        idealLookDist = lookVecNeg.magnitude();\n        if (!DetectCollision(eyePos, eyePos + lookVecNeg, 0.3f, idealLookDist, mgr) || idealLookDist > 2.f) {\n          lookVecNeg.normalize();\n          lookVec = lookVecNeg * idealLookDist;\n          foundClear = true;\n          break;\n        }\n        idealLookDist = lookVecPos.magnitude();\n        if (!DetectCollision(eyePos, eyePos + lookVecPos, 0.3f, idealLookDist, mgr) || idealLookDist > 2.f) {\n          lookVecPos.normalize();\n          lookVec = lookVecPos * idealLookDist;\n          foundClear = true;\n          break;\n        }\n        lookVecNeg = xfNeg * lookVecNeg;\n        lookVecPos = xfPos * lookVecPos;\n      }\n      if (!foundClear) {\n        zeus::CAABox findBounds(ballPos - distance, ballPos + distance);\n        findBounds.min.z() = float(ballPos.z());\n        findBounds.max.z() = elev + ballPos.z();\n        EntityList nearList;\n        mgr.BuildNearList(nearList, findBounds, BallCameraFilter,\n                          TCastToConstPtr<CActor>(mgr.GetObjectById(x46c_collisionActorId)).GetPtr());\n        zeus::CQuaternion rotNeg2;\n        rotNeg2.rotateZ(-resolveLOSIntervalAng);\n        zeus::CTransform xfNeg2 = rotNeg2.toTransform();\n        zeus::CQuaternion rotPos2;\n        rotPos2.rotateZ(resolveLOSIntervalAng);\n        zeus::CTransform xfPos2 = rotPos2.toTransform();\n        while (!foundClear && idealLookDist > dist) {\n          idealLookVec.normalize();\n          idealLookVec = idealLookVec * idealLookDist;\n          zeus::CVector3f lookVecNeg2 = xfNeg2.rotate(idealLookVec);\n          zeus::CVector3f lookVecPos2 = xfPos2.rotate(idealLookVec);\n          for (int i = 0; float(i) < 180.f / zeus::radToDeg(resolveLOSIntervalAng); ++i) {\n            if (mgr.RayCollideWorld(eyePos, eyePos + lookVecNeg2, nearList, BallCameraFilter, nullptr)) {\n              foundClear = true;\n              lookVec = lookVecNeg2;\n              break;\n            }\n            if (mgr.RayCollideWorld(eyePos, eyePos + lookVecPos2, nearList, BallCameraFilter, nullptr)) {\n              foundClear = true;\n              lookVec = lookVecPos2;\n              break;\n            }\n\n            lookVecNeg2 = xfNeg2 * lookVecNeg2;\n            lookVecPos2 = xfPos2 * lookVecPos2;\n          }\n          idealLookDist -= 0.3f;\n        }\n      }\n    }\n  }\n\n  if (!foundClear) {\n    return GetTranslation();\n  }\n\n  return eyePos + lookVec;\n}\n\nbool CBallCamera::DetectCollision(const zeus::CVector3f& from, const zeus::CVector3f& to, float radius, float& d,\n                                  CStateManager& mgr) {\n  zeus::CVector3f delta = to - from;\n  float deltaMag = delta.magnitude();\n  zeus::CVector3f deltaNorm = delta * (1.f / deltaMag);\n  bool clear = true;\n\n  if (deltaMag > 0.000001f) {\n    float margin = 2.f * radius;\n    zeus::CAABox aabb;\n    aabb.accumulateBounds(from);\n    aabb.accumulateBounds(to);\n    aabb = zeus::CAABox(aabb.min - margin, aabb.max + margin);\n    EntityList nearList;\n    mgr.BuildColliderList(nearList, mgr.GetPlayer(), aabb);\n    CAreaCollisionCache cache(aabb);\n    CGameCollision::BuildAreaCollisionCache(mgr, cache);\n    if (cache.HasCacheOverflowed()) {\n      clear = false;\n    }\n    CCollidableSphere cSphere({zeus::skZero3f, radius}, {EMaterialTypes::Solid});\n    if (CGameCollision::DetectCollisionBoolean_Cached(\n            mgr, cache, cSphere, zeus::CTransform::Translate(from),\n            CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid},\n                                                {EMaterialTypes::ProjectilePassthrough, EMaterialTypes::Player,\n                                                 EMaterialTypes::Character, EMaterialTypes::CameraPassthrough}),\n            nearList)) {\n      d = -1.f;\n      return true;\n    }\n    if (clear) {\n      TUniqueId intersectId = kInvalidUniqueId;\n      CCollisionInfo info;\n      double dTmp = deltaMag;\n      if (CGameCollision::DetectCollision_Cached_Moving(\n              mgr, cache, cSphere, zeus::CTransform::Translate(from),\n              CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid},\n                                                  {EMaterialTypes::ProjectilePassthrough, EMaterialTypes::Player,\n                                                   EMaterialTypes::Character, EMaterialTypes::CameraPassthrough}),\n              nearList, deltaNorm, intersectId, info, dTmp)) {\n        d = float(dTmp);\n        clear = false;\n      }\n    }\n  }\n  return !clear;\n}\n\nvoid CBallCamera::Think(float dt, CStateManager& mgr) {\n  mgr.SetActorAreaId(*this, mgr.GetNextAreaId());\n  UpdatePlayerMovement(dt, mgr);\n  const TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(x46c_collisionActorId);\n  if (colAct) {\n    mgr.SetActorAreaId(*colAct, mgr.GetNextAreaId());\n  }\n\n  switch (mgr.GetPlayer().GetCameraState()) {\n  default:\n    if (!x18d_27_forceProcessing) {\n      if (colAct) {\n        colAct->SetActive(false);\n      }\n      return;\n    }\n    [[fallthrough]];\n  case CPlayer::EPlayerCameraState::Ball:\n  case CPlayer::EPlayerCameraState::Transitioning:\n  case CPlayer::EPlayerCameraState::Two: {\n    if (colAct) {\n      colAct->SetActive(true);\n    }\n    zeus::CTransform oldXf = x34_transform;\n    if (mgr.GetPlayer().GetBombJumpCount() != 1) {\n      UpdateLookAtPosition(dt, mgr);\n    }\n    CheckFailsafe(dt, mgr);\n    UpdateObjectTooCloseId(mgr);\n    UpdateAnglePerSecond(dt);\n    switch (x400_state) {\n    case EBallCameraState::Default:\n    case EBallCameraState::Chase:\n    case EBallCameraState::Boost:\n      switch (x188_behaviour) {\n      case EBallCameraBehaviour::PathCamera:\n        UpdateUsingPathCameras(dt, mgr);\n        break;\n      case EBallCameraBehaviour::HintFixedPosition:\n      case EBallCameraBehaviour::HintFixedTransform:\n        UpdateUsingFixedCameras(dt, mgr);\n        break;\n      case EBallCameraBehaviour::PathCameraDesiredPos:\n      case EBallCameraBehaviour::Default:\n      case EBallCameraBehaviour::FreezeLookPosition:\n      case EBallCameraBehaviour::HintBallToCam:\n        if (x36c_splineState != ESplineState::Invalid) {\n          UpdateUsingFreeLook(dt, mgr);\n        } else {\n          UpdateUsingColliders(dt, mgr);\n        }\n        break;\n      case EBallCameraBehaviour::SpindleCamera:\n        UpdateUsingSpindleCameras(dt, mgr);\n        break;\n      default:\n        break;\n      }\n      break;\n    case EBallCameraState::ToBall:\n    case EBallCameraState::FromBall:\n      UpdateUsingTransitions(dt, mgr);\n      break;\n    default:\n      break;\n    }\n\n    SetTransform(ValidateCameraTransform(UpdateCameraPositions(dt, oldXf, x34_transform), oldXf));\n    break;\n  }\n  }\n}\n\nbool CBallCamera::CheckTransitionLineOfSight(const zeus::CVector3f& eyePos, const zeus::CVector3f& behindPos,\n                                             float& eyeToOccDist, float colRadius, CStateManager& mgr) {\n  zeus::CVector3f eyeToBehind = behindPos - eyePos;\n  float eyeToBehindMag = eyeToBehind.magnitude();\n  zeus::CVector3f eyeToBehindNorm = eyeToBehind * (1.f / eyeToBehindMag);\n  bool clear = true;\n  if (eyeToBehindMag > 0.000001f) {\n    float margin = 2.f * colRadius;\n    zeus::CAABox aabb;\n    aabb.accumulateBounds(eyePos);\n    aabb.accumulateBounds(behindPos);\n    aabb = zeus::CAABox(aabb.min - margin, aabb.max + margin);\n    EntityList nearList;\n    mgr.BuildColliderList(nearList, mgr.GetPlayer(), aabb);\n    CAreaCollisionCache cache(aabb);\n    CGameCollision::BuildAreaCollisionCache(mgr, cache);\n    if (cache.HasCacheOverflowed()) {\n      clear = false;\n    }\n    if (clear) {\n      CCollisionInfo cinfo;\n      double d = eyeToBehindMag;\n      TUniqueId intersectId = kInvalidUniqueId;\n      CCollidableSphere cSphere({zeus::skZero3f, colRadius}, {EMaterialTypes::Solid});\n      if (CGameCollision::DetectCollision_Cached_Moving(\n              mgr, cache, cSphere, zeus::CTransform::Translate(eyePos),\n              CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid},\n                                                  {EMaterialTypes::ProjectilePassthrough, EMaterialTypes::Player,\n                                                   EMaterialTypes::Character, EMaterialTypes::CameraPassthrough}),\n              nearList, eyeToBehindNorm, intersectId, cinfo, d)) {\n        eyeToOccDist = float(d);\n        clear = false;\n      }\n    }\n  }\n  return !clear;\n}\n\nbool CBallCamera::TransitionFromMorphBallState(CStateManager& mgr) {\n  x47c_failsafeState->x0_playerXf = mgr.GetPlayer().GetTransform();\n  x47c_failsafeState->x30_camXf = GetTransform();\n  x47c_failsafeState->x60_lookPos = x1d8_lookPos;\n  x47c_failsafeState->x84_playerPos = x47c_failsafeState->x0_playerXf.origin;\n  zeus::CVector3f eyePos = mgr.GetPlayer().GetEyePosition();\n  float lookDist = (x47c_failsafeState->x60_lookPos - x47c_failsafeState->x30_camXf.origin).magnitude();\n  zeus::CVector3f behindPos = x47c_failsafeState->x0_playerXf.basis[1] * (0.6f * -lookDist) + eyePos;\n  float eyeToOccDist = 0.f;\n  if (CheckTransitionLineOfSight(eyePos, behindPos, eyeToOccDist, 0.6f, mgr)) {\n    x47c_failsafeState->x6c_behindPos = x47c_failsafeState->x0_playerXf.basis[1] * -eyeToOccDist + eyePos;\n  } else {\n    x47c_failsafeState->x6c_behindPos = behindPos;\n  }\n  x47c_failsafeState->x90_splinePoints.clear();\n  x47c_failsafeState->x90_splinePoints.reserve(4);\n  x47c_failsafeState->x90_splinePoints.push_back(x47c_failsafeState->x30_camXf.origin);\n  x47c_failsafeState->x90_splinePoints.push_back(x47c_failsafeState->x6c_behindPos);\n  x47c_failsafeState->x90_splinePoints.push_back(x47c_failsafeState->x6c_behindPos);\n  x47c_failsafeState->x90_splinePoints.push_back(eyePos);\n  return CheckFailsafeFromMorphBallState(mgr);\n}\n\nvoid CBallCamera::TeleportColliders(std::vector<CCameraCollider>& colliderList, const zeus::CVector3f& pos) {\n  for (CCameraCollider& collider : colliderList) {\n    collider.x2c_lastWorldPos = pos;\n    collider.x14_localPos = pos;\n    collider.x20_scaledWorldPos = pos;\n  }\n}\n\nvoid CBallCamera::TeleportCamera(const zeus::CVector3f& pos, CStateManager& mgr) {\n  x294_dampedPos = pos;\n  TeleportColliders(x264_smallColliders, pos);\n  TeleportColliders(x274_mediumColliders, pos);\n  TeleportColliders(x284_largeColliders, pos);\n  if (const TCastToPtr<CCollisionActor> act = mgr.ObjectById(x46c_collisionActorId)) {\n    act->SetTranslation(pos);\n  }\n}\n\nvoid CBallCamera::TeleportCamera(const zeus::CTransform& xf, CStateManager& mgr) {\n  SetTransform(xf);\n  TeleportCamera(xf.origin, mgr);\n}\n\nvoid CBallCamera::ResetToTweaks(CStateManager& mgr) {\n  x188_behaviour = EBallCameraBehaviour::Default;\n  x18c_25_chaseAllowed = true;\n  x18c_26_boostAllowed = true;\n  x18c_27_obscureAvoidance = true;\n  x18c_28_volumeCollider = true;\n  x18c_29_clampAttitude = false;\n  x18c_30_clampAzimuth = false;\n  x194_targetMinDistance = g_tweakBall->GetBallCameraMinSpeedDistance();\n  x198_maxDistance = g_tweakBall->GetBallCameraMaxSpeedDistance();\n  x19c_backwardsDistance = g_tweakBall->GetBallCameraBackwardsDistance();\n  x214_ballCameraSpring =\n      CCameraSpring(g_tweakBall->GetBallCameraSpringConstant(), g_tweakBall->GetBallCameraSpringMax(),\n                    g_tweakBall->GetBallCameraSpringTardis());\n  x250_ballCameraCentroidDistanceSpring = CCameraSpring(g_tweakBall->GetBallCameraCentroidDistanceSpringConstant(),\n                                                        g_tweakBall->GetBallCameraCentroidDistanceSpringMax(),\n                                                        g_tweakBall->GetBallCameraCentroidDistanceSpringTardis());\n  x1b4_lookAtOffset = g_tweakBall->GetBallCameraOffset();\n  x410_chaseLookAtOffset = g_tweakBall->GetBallCameraChaseLookAtOffset();\n  x1a0_elevation = g_tweakBall->GetBallCameraElevation();\n  x1ac_attitudeRange = M_PIF / 2.f;\n  x1b0_azimuthRange = M_PIF / 2.f;\n  SetFovInterpolation(x15c_currentFov, CCameraManager::ThirdPersonFOV(), 1.f, 0.f);\n  x1a8_targetAnglePerSecond = g_tweakBall->GetBallCameraAnglePerSecond();\n  x18d_29_noElevationInterp = false;\n  x18d_30_directElevation = false;\n  x18d_31_overrideLookDir = false;\n  x18e_24_noElevationVelClamp = false;\n  x18e_25_noSpline = false;\n  x18e_26_ = false;\n}\n\nvoid CBallCamera::UpdateLookAtPosition(float dt, CStateManager& mgr) {\n  if (TCastToConstPtr<CPlayer> player = mgr.GetObjectById(xe8_watchedObject)) {\n    zeus::CVector3f ballPos = player->GetBallPosition();\n    if (player->IsMorphBallTransitioning()) {\n      x1d8_lookPos = ballPos;\n      x1d8_lookPos.z() += x1b4_lookAtOffset.z();\n      x1c0_lookPosAhead = x1d8_lookPos;\n      x1cc_fixedLookPos = x1d8_lookPos;\n    } else {\n      zeus::CVector3f dirNorm = player->GetMoveDir();\n      dirNorm.normalize();\n      zeus::CVector3f lookAtOffsetAhead(x308_speedFactor * x1b4_lookAtOffset.x(),\n                                        x308_speedFactor * x1b4_lookAtOffset.y(), x1b4_lookAtOffset.z());\n      if (x18c_25_chaseAllowed && (x400_state == EBallCameraState::Chase || x400_state == EBallCameraState::One)) {\n        lookAtOffsetAhead = zeus::CVector3f(x308_speedFactor * x410_chaseLookAtOffset.x(),\n                                            x308_speedFactor * x410_chaseLookAtOffset.y(), x410_chaseLookAtOffset.z());\n      }\n      if (mgr.GetCameraManager()->IsInterpolationCameraActive()) {\n        lookAtOffsetAhead = zeus::CVector3f(0.f, 0.f, x1b4_lookAtOffset.z());\n      }\n      zeus::CTransform moveXf = player->CreateTransformFromMovementDirection().getRotation();\n      if (x2fc_ballDeltaFlat.canBeNormalized()) {\n        lookAtOffsetAhead = moveXf * lookAtOffsetAhead;\n      }\n      zeus::CVector3f lookAtPosAhead = ballPos + lookAtOffsetAhead;\n      x1c0_lookPosAhead = lookAtPosAhead;\n      x1cc_fixedLookPos = ballPos + zeus::CVector3f(0.f, 0.f, lookAtOffsetAhead.z());\n      zeus::CVector3f aheadToCurrentLookDelta = x1d8_lookPos - lookAtPosAhead;\n      float aheadToCurrentLookMag = aheadToCurrentLookDelta.magnitude();\n      if (aheadToCurrentLookDelta.canBeNormalized()) {\n        aheadToCurrentLookDelta.normalize();\n      }\n      float lookAtSpringMag = x23c_ballCameraLookAtSpring.ApplyDistanceSpringNoMax(\n          0.f, aheadToCurrentLookMag, (2.f * zeus::clamp(0.f, x30c_speedingTime / 3.f, 1.f) + 1.f) * dt);\n      if (lookAtSpringMag > 0.0001f) {\n        lookAtPosAhead += aheadToCurrentLookDelta * lookAtSpringMag;\n      }\n      aheadToCurrentLookDelta = lookAtPosAhead - x1d8_lookPos;\n      if (x18d_26_lookAtBall) {\n        x1d8_lookPos = ballPos;\n        x1d8_lookPos.z() += x1b4_lookAtOffset.z();\n      } else {\n        x1d8_lookPos = lookAtPosAhead;\n      }\n      switch (x188_behaviour) {\n      case EBallCameraBehaviour::Default:\n      case EBallCameraBehaviour::FreezeLookPosition:\n      case EBallCameraBehaviour::HintBallToCam:\n      case EBallCameraBehaviour::HintInitializePosition:\n      case EBallCameraBehaviour::PathCameraDesiredPos:\n      case EBallCameraBehaviour::PathCamera:\n        if (mgr.GetCameraManager()->IsInterpolationCameraActive()) {\n          x1d8_lookPos = x1c0_lookPosAhead;\n          x1cc_fixedLookPos = x1c0_lookPosAhead;\n        }\n        break;\n      case EBallCameraBehaviour::HintFixedPosition:\n        x1d8_lookPos = x1cc_fixedLookPos;\n        x1c0_lookPosAhead = x1d8_lookPos;\n        break;\n      case EBallCameraBehaviour::HintFixedTransform:\n      case EBallCameraBehaviour::SpindleCamera:\n        x1d8_lookPos = x1cc_fixedLookPos;\n        x1c0_lookPosAhead = x1cc_fixedLookPos;\n        break;\n      }\n      if (x18d_30_directElevation) {\n        x1d8_lookPos.z() = ballPos.z() + x1b4_lookAtOffset.z();\n        x1c0_lookPosAhead.z() = float(x1d8_lookPos.z());\n        x1cc_fixedLookPos.z() = float(x1d8_lookPos.z());\n      }\n      if (x18d_31_overrideLookDir) {\n        if (const CScriptCameraHint* hint = mgr.GetCameraManager()->GetCameraHint(mgr)) {\n          x1d8_lookPos = hint->GetTransform().basis[1] * 10.f + GetTranslation();\n          x1c0_lookPosAhead = x1d8_lookPos;\n          x1cc_fixedLookPos = x1d8_lookPos;\n        }\n      }\n    }\n  }\n}\n\nzeus::CTransform CBallCamera::UpdateLookDirection(const zeus::CVector3f& dir, CStateManager& mgr) {\n  zeus::CVector3f useDir = dir;\n  if (!dir.canBeNormalized()) {\n    useDir = zeus::skForward;\n  }\n  float elevation = x1a0_elevation;\n  float distance = x190_curMinDistance;\n  ConstrainElevationAndDistance(elevation, distance, 0.f, mgr);\n  zeus::CVector3f pos = FindDesiredPosition(distance, elevation, useDir, mgr, false);\n  UpdateLookAtPosition(0.f, mgr);\n  return zeus::lookAt(pos, x1d8_lookPos);\n}\n\nvoid CBallCamera::ApplyCameraHint(CStateManager& mgr) {\n  if (const CScriptCameraHint* hint = mgr.GetCameraManager()->GetCameraHint(mgr)) {\n    ResetToTweaks(mgr);\n    x188_behaviour = hint->GetHint().GetBehaviourType();\n    x18c_25_chaseAllowed = (hint->GetHint().GetOverrideFlags() & 0x2) != 0;\n    x18c_26_boostAllowed = (hint->GetHint().GetOverrideFlags() & 0x4) != 0;\n    x18c_27_obscureAvoidance = (hint->GetHint().GetOverrideFlags() & 0x8) != 0;\n    x18c_28_volumeCollider = (hint->GetHint().GetOverrideFlags() & 0x10) != 0;\n    if ((hint->GetHint().GetOverrideFlags() & 0x40) != 0) {\n      x18d_26_lookAtBall = true;\n    }\n    x18d_29_noElevationInterp = (hint->GetHint().GetOverrideFlags() & 0x4000) != 0;\n    x18d_30_directElevation = (hint->GetHint().GetOverrideFlags() & 0x8000) != 0;\n    x18d_31_overrideLookDir = (hint->GetHint().GetOverrideFlags() & 0x10000) != 0;\n    x18e_24_noElevationVelClamp = (hint->GetHint().GetOverrideFlags() & 0x20000) != 0;\n    x18e_25_noSpline = x18e_26_ = (hint->GetHint().GetOverrideFlags() & 0x80000) != 0;\n    if ((hint->GetHint().GetOverrideFlags() & 0x400000) != 0) {\n      x194_targetMinDistance = hint->GetHint().GetMinDist();\n    }\n    if ((hint->GetHint().GetOverrideFlags() & 0x800000) != 0) {\n      x198_maxDistance = hint->GetHint().GetMaxDist();\n    }\n    if ((hint->GetHint().GetOverrideFlags() & 0x1000000) != 0) {\n      x19c_backwardsDistance = hint->GetHint().GetBackwardsDist();\n    }\n    if ((hint->GetHint().GetOverrideFlags() & 0x80000000) != 0) {\n      x1a0_elevation = hint->GetHint().GetElevation();\n    }\n    if ((hint->GetHint().GetOverrideFlags() & 0x2000000) != 0) {\n      x1b4_lookAtOffset = hint->GetHint().GetLookAtOffset();\n    }\n    if ((hint->GetHint().GetOverrideFlags() & 0x4000000) != 0) {\n      x410_chaseLookAtOffset = hint->GetHint().GetChaseLookAtOffset();\n    }\n    if ((hint->GetHint().GetOverrideFlags() & 0x10000000) != 0) {\n      x18c_29_clampAttitude = true;\n      x1ac_attitudeRange = hint->GetHint().GetAttitudeRange();\n    } else {\n      x18c_29_clampAttitude = false;\n    }\n    if ((hint->GetHint().GetOverrideFlags() & 0x20000000) != 0) {\n      x18c_30_clampAzimuth = true;\n      x1b0_azimuthRange = hint->GetHint().GetAzimuthRange();\n    } else {\n      x18c_30_clampAzimuth = false;\n    }\n    if ((hint->GetHint().GetOverrideFlags() & 0x8000000) != 0) {\n      SetFovInterpolation(x15c_currentFov, hint->GetHint().GetFov(), 1.f, 0.f);\n    }\n    if ((hint->GetHint().GetOverrideFlags() & 0x40000000) != 0) {\n      x1a8_targetAnglePerSecond = hint->GetHint().GetAnglePerSecond();\n    }\n    if ((hint->GetHint().GetOverrideFlags() & 0x200) != 0) {\n      mgr.GetPlayer().SetControlDirectionInterpolation(hint->GetHint().GetControlInterpDur());\n    } else {\n      mgr.GetPlayer().ResetControlDirectionInterpolation();\n    }\n    switch (hint->GetHint().GetBehaviourType()) {\n    case EBallCameraBehaviour::HintBallToCam: {\n      x45c_overrideBallToCam = hint->GetHint().GetBallToCam();\n      ResetPosition(mgr);\n      zeus::CVector3f camPos = mgr.GetPlayer().GetBallPosition() + hint->GetHint().GetBallToCam();\n      if ((hint->GetHint().GetOverrideFlags() & 0x1) != 0) {\n        float distance = hint->GetHint().GetBallToCam().toVec2f().magnitude();\n        zeus::CVector3f camToBall = -zeus::CVector3f(hint->GetHint().GetBallToCam().toVec2f()).normalized();\n        camPos = FindDesiredPosition(distance, hint->GetHint().GetBallToCam().z(), camToBall, mgr, false);\n      }\n      TeleportCamera(zeus::lookAt(camPos, x1d8_lookPos), mgr);\n      break;\n    }\n    case EBallCameraBehaviour::HintFixedTransform: {\n      ResetPosition(mgr);\n      TeleportCamera(hint->GetTransform(), mgr);\n      break;\n    }\n    case EBallCameraBehaviour::Default: {\n      if ((hint->GetHint().GetOverrideFlags() & 0x20) != 0) {\n        ResetPosition(mgr);\n        if ((hint->GetHint().GetOverrideFlags() & 0x40000) != 0) {\n          zeus::CVector3f lookDir =\n              mgr.GetPlayer().GetTranslation() - mgr.GetCameraManager()->GetCurrentCameraTransform(mgr).origin;\n          lookDir.z() = 0.f;\n          if (lookDir.canBeNormalized()) {\n            lookDir.normalize();\n          } else {\n            lookDir = mgr.GetPlayer().GetMoveDir();\n          }\n          TeleportCamera(UpdateLookDirection(lookDir, mgr), mgr);\n        } else {\n          TeleportCamera(zeus::lookAt(hint->GetTranslation(), x1d8_lookPos), mgr);\n        }\n      }\n      break;\n    }\n    case EBallCameraBehaviour::HintFixedPosition: {\n      ResetPosition(mgr);\n      TeleportCamera(zeus::lookAt(hint->GetTranslation(), x1d8_lookPos), mgr);\n      break;\n    }\n    case EBallCameraBehaviour::FreezeLookPosition:\n    case EBallCameraBehaviour::HintInitializePosition: {\n      if ((hint->GetHint().GetOverrideFlags() & 0x20) != 0) {\n        ResetPosition(mgr);\n        float elevation = x1a0_elevation;\n        float distance = x190_curMinDistance;\n        ConstrainElevationAndDistance(elevation, distance, 0.f, mgr);\n        TeleportCamera(zeus::lookAt(FindDesiredPosition(distance, elevation, mgr.GetPlayer().GetMoveDir(), mgr, false),\n                                    x1cc_fixedLookPos),\n                       mgr);\n      }\n      break;\n    }\n    default:\n      break;\n    }\n\n    if ((hint->GetHint().GetOverrideFlags() & 0x20) != 0) {\n      mgr.GetCameraManager()->SetPlayerCamera(mgr, GetUniqueId());\n    }\n  }\n}\n\nvoid CBallCamera::ResetPosition(CStateManager& mgr) {\n  x1d8_lookPos = mgr.GetPlayer().GetBallPosition();\n  x1d8_lookPos.z() += x1b4_lookAtOffset.z();\n  x1c0_lookPosAhead = x1d8_lookPos;\n  x1cc_fixedLookPos = x1d8_lookPos;\n}\n\nvoid CBallCamera::DoorClosed(TUniqueId doorId) {\n  if (doorId != x3dc_tooCloseActorId) {\n    return;\n  }\n  x18e_27_nearbyDoorClosed = true;\n}\n\nvoid CBallCamera::DoorClosing(TUniqueId doorId) {\n  if (doorId != x3dc_tooCloseActorId) {\n    return;\n  }\n  x18e_28_nearbyDoorClosing = true;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Camera/CBallCamera.hpp",
    "content": "#pragma once\n\n#include <cmath>\n#include <memory>\n#include <vector>\n\n#include \"Runtime/Camera/CCameraSpline.hpp\"\n#include \"Runtime/Camera/CGameCamera.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CPlayer;\n\nclass CCameraSpring {\n  float x0_k;\n  float x4_k2Sqrt;\n  float x8_max;\n  float xc_tardis;\n  float x10_dx = 0.f;\n\npublic:\n  CCameraSpring(float k, float max, float tardis)\n  : x0_k(k), x4_k2Sqrt(2.f * std::sqrt(k)), x8_max(max), xc_tardis(tardis) {}\n  void Reset();\n  float ApplyDistanceSpringNoMax(float targetX, float curX, float dt);\n  float ApplyDistanceSpring(float targetX, float curX, float dt);\n};\n\nclass CCameraCollider {\n  friend class CBallCamera;\n  float x4_radius;\n  zeus::CVector3f x8_lastLocalPos;\n  zeus::CVector3f x14_localPos;\n  zeus::CVector3f x20_scaledWorldPos;\n  zeus::CVector3f x2c_lastWorldPos;\n  CCameraSpring x38_spring;\n  u32 x4c_occlusionCount = 0;\n  float x50_scale;\n\npublic:\n  CCameraCollider(float radius, const zeus::CVector3f& vec, const CCameraSpring& spring, float scale)\n  : x4_radius(radius)\n  , x8_lastLocalPos(vec)\n  , x14_localPos(vec)\n  , x20_scaledWorldPos(vec)\n  , x2c_lastWorldPos(vec)\n  , x38_spring(spring)\n  , x50_scale(scale) {}\n};\n\nclass CBallCamera : public CGameCamera {\npublic:\n  DEFINE_ENTITY\n  enum class EBallCameraState { Default, One, Chase, Boost, ToBall, FromBall };\n  enum class EBallCameraBehaviour {\n    Default,\n    FreezeLookPosition, // Unused\n    HintBallToCam,\n    HintInitializePosition,\n    HintFixedPosition,\n    HintFixedTransform,\n    PathCameraDesiredPos, // Unused\n    PathCamera,\n    SpindleCamera\n  };\n  enum class ESplineState { Invalid, Nav, Arc };\n\nprivate:\n  struct SFailsafeState {\n    zeus::CTransform x0_playerXf;\n    zeus::CTransform x30_camXf;\n    zeus::CVector3f x60_lookPos;\n    zeus::CVector3f x6c_behindPos;\n    zeus::CVector3f x78_;\n    zeus::CVector3f x84_playerPos;\n    std::vector<zeus::CVector3f> x90_splinePoints;\n  };\n\n  EBallCameraBehaviour x188_behaviour = EBallCameraBehaviour::Default;\n  bool x18c_24_ : 1 = true;\n  bool x18c_25_chaseAllowed : 1 = true;\n  bool x18c_26_boostAllowed : 1 = true;\n  bool x18c_27_obscureAvoidance : 1 = true;\n  bool x18c_28_volumeCollider : 1 = true;\n  bool x18c_29_clampAttitude : 1 = false;\n  bool x18c_30_clampAzimuth : 1 = false;\n  bool x18c_31_clearLOS : 1 = true;\n  bool x18d_24_prevClearLOS : 1 = true;\n  bool x18d_25_avoidGeometryFull : 1 = false;\n  bool x18d_26_lookAtBall : 1 = false;\n  bool x18d_27_forceProcessing : 1 = false;\n  bool x18d_28_obtuseDirection : 1 = false;\n  bool x18d_29_noElevationInterp : 1 = false;\n  bool x18d_30_directElevation : 1 = false;\n  bool x18d_31_overrideLookDir : 1 = false;\n  bool x18e_24_noElevationVelClamp : 1 = false;\n  bool x18e_25_noSpline : 1 = false;\n  bool x18e_26_ : 1 = false;\n  bool x18e_27_nearbyDoorClosed : 1 = false;\n  bool x18e_28_nearbyDoorClosing : 1 = false;\n  float x190_curMinDistance;\n  float x194_targetMinDistance;\n  float x198_maxDistance;\n  float x19c_backwardsDistance;\n  float x1a0_elevation;\n  float x1a4_curAnglePerSecond;\n  float x1a8_targetAnglePerSecond;\n  float x1ac_attitudeRange = zeus::degToRad(89.f);\n  float x1b0_azimuthRange = zeus::degToRad(89.f);\n  zeus::CVector3f x1b4_lookAtOffset;\n  zeus::CVector3f x1c0_lookPosAhead;\n  zeus::CVector3f x1cc_fixedLookPos;\n  zeus::CVector3f x1d8_lookPos;\n  zeus::CTransform x1e4_nextLookXf;\n  CCameraSpring x214_ballCameraSpring;\n  CCameraSpring x228_ballCameraCentroidSpring;\n  CCameraSpring x23c_ballCameraLookAtSpring;\n  CCameraSpring x250_ballCameraCentroidDistanceSpring;\n  std::vector<CCameraCollider> x264_smallColliders;\n  std::vector<CCameraCollider> x274_mediumColliders;\n  std::vector<CCameraCollider> x284_largeColliders;\n  zeus::CVector3f x294_dampedPos;\n  zeus::CVector3f x2a0_smallCentroid = zeus::skUp;\n  zeus::CVector3f x2ac_mediumCentroid = zeus::skUp;\n  zeus::CVector3f x2b8_largeCentroid = zeus::skUp;\n  int x2c4_smallCollidersObsCount = 0;\n  int x2c8_mediumCollidersObsCount = 0;\n  int x2cc_largeCollidersObsCount = 0;\n  int x2d0_smallColliderIt = 0;\n  int x2d4_mediumColliderIt = 0;\n  int x2d8_largeColliderIt = 0;\n  zeus::CVector3f x2dc_prevBallPos;\n  float x2e8_ballVelFlat = 0.f;\n  float x2ec_maxBallVel = 0.f;\n  zeus::CVector3f x2f0_ballDelta;\n  zeus::CVector3f x2fc_ballDeltaFlat;\n  float x308_speedFactor = 0.f;\n  float x30c_speedingTime = 0.f;\n  zeus::CVector3f x310_idealLookVec;\n  zeus::CVector3f x31c_predictedLookPos;\n  u32 x328_avoidGeomCycle = 0;\n  float x32c_colliderMag = 1.f;\n  float x330_clearColliderThreshold = 0.2f;\n  zeus::CAABox x334_collidersAABB = zeus::skNullBox;\n  float x34c_obscuredTime = 0.f;\n  CMaterialList x350_obscuringMaterial = {EMaterialTypes::NoStepLogic};\n  float x358_unobscureMag = 0.f;\n  zeus::CVector3f x35c_splineIntermediatePos;\n  TUniqueId x368_obscuringObjectId = kInvalidUniqueId;\n  ESplineState x36c_splineState = ESplineState::Invalid;\n  bool x370_24_reevalSplineEnd : 1 = false;\n  float x374_splineCtrl = 0.f;\n  float x378_splineCtrlRange;\n  CCameraSpline x37c_camSpline{false};\n  CMaterialList x3c8_collisionExcludeList = {EMaterialTypes::NoStepLogic};\n  bool x3d0_24_camBehindFloorOrWall : 1 = false;\n  float x3d4_elevInterpTimer = 0.f;\n  float x3d8_elevInterpStart = 0.f;\n  TUniqueId x3dc_tooCloseActorId = kInvalidUniqueId;\n  float x3e0_tooCloseActorDist = 10000.f;\n  bool x3e4_pendingFailsafe = false;\n  float x3e8_ = 0.f;\n  float x3ec_ = 0.f;\n  float x3f0_ = 0.f;\n  float x3f4_ = 2.f;\n  float x3f8_ = 0.f;\n  float x3fc_ = 0.f;\n  EBallCameraState x400_state = EBallCameraState::Default;\n  float x404_chaseElevation;\n  float x408_chaseDistance;\n  float x40c_chaseAnglePerSecond;\n  zeus::CVector3f x410_chaseLookAtOffset;\n  CCameraSpring x41c_ballCameraChaseSpring;\n  float x430_boostElevation;\n  float x434_boostDistance;\n  float x438_boostAnglePerSecond;\n  zeus::CVector3f x43c_boostLookAtOffset;\n  CCameraSpring x448_ballCameraBoostSpring;\n  zeus::CVector3f x45c_overrideBallToCam;\n  float x468_conservativeDoorCamDistance;\n  TUniqueId x46c_collisionActorId = kInvalidUniqueId;\n  float x470_clampVelTimer = 0.f;\n  float x474_clampVelRange = 0.f;\n  u32 x478_shortMoveCount = 0;\n  std::unique_ptr<SFailsafeState> x47c_failsafeState;\n  std::unique_ptr<u32> x480_;\n\n  void SetupColliders(std::vector<CCameraCollider>& out, float xMag, float zMag, float radius, int count, float k,\n                      float max, float startAngle);\n  void BuildSplineNav(CStateManager& mgr);\n  void BuildSplineArc(CStateManager& mgr);\n  bool ShouldResetSpline(CStateManager& mgr) const;\n  void UpdatePlayerMovement(float dt, CStateManager& mgr);\n  void UpdateTransform(const zeus::CVector3f& lookDir, const zeus::CVector3f& pos, float dt, CStateManager& mgr);\n  zeus::CVector3f ConstrainYawAngle(const CPlayer& player, float distance, float yawSpeed, float dt,\n                                    CStateManager& mgr) const;\n  void CheckFailsafe(float dt, CStateManager& mgr);\n  void UpdateObjectTooCloseId(CStateManager& mgr);\n  void UpdateAnglePerSecond(float dt);\n  void UpdateUsingPathCameras(float dt, CStateManager& mgr);\n  zeus::CVector3f GetFixedLookTarget(const zeus::CVector3f& hintToLookDir, CStateManager& mgr) const;\n  void UpdateUsingFixedCameras(float dt, CStateManager& mgr);\n  [[nodiscard]] zeus::CVector3f ComputeVelocity(const zeus::CVector3f& curVel, const zeus::CVector3f& posDelta) const;\n  zeus::CVector3f TweenVelocity(const zeus::CVector3f& curVel, const zeus::CVector3f& newVel, float rate, float dt);\n  zeus::CVector3f MoveCollisionActor(const zeus::CVector3f& pos, float dt, CStateManager& mgr);\n  void UpdateUsingFreeLook(float dt, CStateManager& mgr);\n  zeus::CVector3f InterpolateCameraElevation(const zeus::CVector3f& camPos, float dt);\n  [[nodiscard]] zeus::CVector3f CalculateCollidersCentroid(const std::vector<CCameraCollider>& colliderList,\n                                                           int numObscured) const;\n  zeus::CVector3f ApplyColliders();\n  void UpdateColliders(const zeus::CTransform& xf, std::vector<CCameraCollider>& colliderList, int& it, int count,\n                       float tolerance, const EntityList& nearList, float dt, CStateManager& mgr);\n  zeus::CVector3f AvoidGeometry(const zeus::CTransform& xf, const EntityList& nearList, float dt, CStateManager& mgr);\n  zeus::CVector3f AvoidGeometryFull(const zeus::CTransform& xf, const EntityList& nearList, float dt,\n                                    CStateManager& mgr);\n  zeus::CAABox CalculateCollidersBoundingBox(const std::vector<CCameraCollider>& colliderList,\n                                             CStateManager& mgr) const;\n  [[nodiscard]] int CountObscuredColliders(const std::vector<CCameraCollider>& colliderList) const;\n  void UpdateCollidersDistances(std::vector<CCameraCollider>& colliderList, float xMag, float zMag, float angOffset);\n  void UpdateUsingColliders(float dt, CStateManager& mgr);\n  void UpdateUsingSpindleCameras(float dt, CStateManager& mgr);\n  zeus::CVector3f ClampElevationToWater(zeus::CVector3f& pos, CStateManager& mgr) const;\n  void UpdateTransitionFromBallCamera(CStateManager& mgr);\n  void UpdateUsingTransitions(float dt, CStateManager& mgr);\n  zeus::CTransform UpdateCameraPositions(float dt, const zeus::CTransform& oldXf, const zeus::CTransform& newXf);\n  static zeus::CVector3f GetFailsafeSplinePoint(const std::vector<zeus::CVector3f>& points, float t);\n  bool CheckFailsafeFromMorphBallState(CStateManager& mgr) const;\n  bool SplineIntersectTest(CMaterialList& intersectMat, CStateManager& mgr) const;\n  void ActivateFailsafe(float dt, CStateManager& mgr);\n  bool ConstrainElevationAndDistance(float& elevation, float& distance, float dt, CStateManager& mgr);\n  zeus::CVector3f FindDesiredPosition(float distance, float elevation, const zeus::CVector3f& dir, CStateManager& mgr,\n                                      bool fullTest);\n  static bool DetectCollision(const zeus::CVector3f& from, const zeus::CVector3f& to, float radius, float& d,\n                              CStateManager& mgr);\n  void TeleportColliders(std::vector<CCameraCollider>& colliderList, const zeus::CVector3f& pos);\n  static bool CheckTransitionLineOfSight(const zeus::CVector3f& eyePos, const zeus::CVector3f& behindPos,\n                                         float& eyeToOccDist, float colRadius, CStateManager& mgr);\n\npublic:\n  CBallCamera(TUniqueId uid, TUniqueId watchedId, const zeus::CTransform& xf, float fovy, float znear, float zfar,\n              float aspect);\n\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) override;\n  void ProcessInput(const CFinalInput& input, CStateManager& mgr) override;\n  void Reset(const zeus::CTransform&, CStateManager& mgr) override;\n  void Render(CStateManager& mgr) override;\n  [[nodiscard]] EBallCameraBehaviour GetBehaviour() const { return x188_behaviour; }\n  [[nodiscard]] EBallCameraState GetState() const { return x400_state; }\n  void SetState(EBallCameraState state, CStateManager& mgr);\n  void Think(float dt, CStateManager& mgr) override;\n  bool TransitionFromMorphBallState(CStateManager& mgr);\n  [[nodiscard]] TUniqueId GetTooCloseActorId() const { return x3dc_tooCloseActorId; }\n  [[nodiscard]] float GetTooCloseActorDistance() const { return x3e0_tooCloseActorDist; }\n  void TeleportCamera(const zeus::CVector3f& pos, CStateManager& mgr);\n  void TeleportCamera(const zeus::CTransform& xf, CStateManager& mgr);\n  [[nodiscard]] const zeus::CVector3f& GetLookPos() const { return x1d8_lookPos; }\n  void ResetToTweaks(CStateManager& mgr);\n  void UpdateLookAtPosition(float dt, CStateManager& mgr);\n  zeus::CTransform UpdateLookDirection(const zeus::CVector3f& dir, CStateManager& mgr);\n  void SetClampVelTimer(float f) { x470_clampVelTimer = f; }\n  void SetClampVelRange(float f) { x474_clampVelRange = f; }\n  void ApplyCameraHint(CStateManager& mgr);\n  void ResetPosition(CStateManager& mgr);\n  void DoorClosed(TUniqueId doorId);\n  void DoorClosing(TUniqueId doorId);\n  [[nodiscard]] const zeus::CVector3f& GetLookPosAhead() const { return x1c0_lookPosAhead; }\n  [[nodiscard]] const zeus::CVector3f& GetFixedLookPos() const { return x1cc_fixedLookPos; }\n  const TUniqueId GetCollisionActorId() const { return x46c_collisionActorId; }\n\n  static bool IsBallNearDoor(const zeus::CVector3f& pos, CStateManager& mgr);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Camera/CCameraFilter.cpp",
    "content": "#include \"Runtime/Camera/CCameraFilter.hpp\"\n\n#include \"Runtime/CDvdFile.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Graphics/CGX.hpp\"\n#include \"Runtime/Graphics/CGraphics.hpp\"\n\n#include <algorithm>\n#include <zeus/CColor.hpp>\n\nnamespace metaforce {\n\nvoid CCameraFilterPass::Update(float dt) {\n  if (x10_remTime <= 0.f)\n    return;\n\n  EFilterType origType = x0_curType;\n\n  x10_remTime = std::max(0.f, x10_remTime - dt);\n  x18_curColor = zeus::CColor::lerp(x1c_nextColor, x14_prevColor, x10_remTime / xc_duration);\n\n  if (x10_remTime == 0.f) {\n    x0_curType = x4_nextType;\n    if (x0_curType == EFilterType::Passthru) {\n      x24_texObj = TLockedToken<CTexture>();\n      x20_nextTxtr = {};\n    }\n  }\n}\n\nvoid CCameraFilterPass::SetFilter(EFilterType type, EFilterShape shape, float time, const zeus::CColor& color,\n                                  CAssetId txtr) {\n  if (time == 0.f) {\n    xc_duration = 0.f;\n    x10_remTime = 0.f;\n\n    if (txtr.IsValid())\n      x24_texObj = g_SimplePool->GetObj({FOURCC('TXTR'), txtr});\n\n    x4_nextType = type;\n    x0_curType = type;\n    x8_shape = shape;\n    x1c_nextColor = color;\n    x18_curColor = color;\n    x14_prevColor = color;\n    x20_nextTxtr = txtr;\n  } else {\n    EFilterType origType = x0_curType;\n    CAssetId origTxtr = x20_nextTxtr;\n\n    x1c_nextColor = color;\n    x14_prevColor = x18_curColor;\n    x8_shape = shape;\n    x20_nextTxtr = txtr;\n    if (txtr.IsValid())\n      x24_texObj = g_SimplePool->GetObj({FOURCC('TXTR'), txtr});\n    x10_remTime = time;\n    xc_duration = time;\n    x0_curType = x4_nextType;\n    x4_nextType = type;\n    if (type == EFilterType::Passthru) {\n      if (x0_curType == EFilterType::Multiply)\n        x1c_nextColor = zeus::skWhite;\n      else if (x0_curType == EFilterType::Add || x0_curType == EFilterType::Blend)\n        x1c_nextColor.a() = 0.f;\n    } else {\n      if (x0_curType == EFilterType::Passthru) {\n        if (type == EFilterType::Multiply) {\n          x18_curColor = zeus::skWhite;\n        } else if (type == EFilterType::Add || type == EFilterType::Blend) {\n          x18_curColor = x1c_nextColor;\n          x18_curColor.a() = 0.f;\n          x14_prevColor = x18_curColor;\n        }\n      }\n      x0_curType = x4_nextType;\n    }\n  }\n}\n\nvoid CCameraFilterPass::DisableFilter(float time) {\n  SetFilter(EFilterType::Passthru, x8_shape, time, zeus::skWhite, {});\n}\n\nvoid CCameraFilterPass::Draw() {\n  DrawFilter(x0_curType, x8_shape, x18_curColor, x24_texObj.GetObj(), GetT(x4_nextType == EFilterType::Passthru));\n}\n\nfloat CCameraFilterPass::GetT(bool invert) const {\n  float tmp;\n  if (xc_duration == 0.f)\n    tmp = 1.f;\n  else\n    tmp = 1.f - x10_remTime / xc_duration;\n  if (invert)\n    return 1.f - tmp;\n  return tmp;\n}\n\nvoid CCameraFilterPass::DrawFilter(EFilterType type, EFilterShape shape, const zeus::CColor& color, CTexture* tex,\n                                   float lod) {\n  switch (type) {\n  case EFilterType::Multiply:\n    g_Renderer->SetBlendMode_ColorMultiply();\n    break;\n  case EFilterType::Invert:\n    g_Renderer->SetBlendMode_InvertDst();\n    break;\n  case EFilterType::Add:\n    g_Renderer->SetBlendMode_AdditiveAlpha();\n    break;\n  case EFilterType::Subtract:\n    CGX::SetBlendMode(GX_BM_SUBTRACT, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR);\n    break;\n  case EFilterType::Blend:\n    g_Renderer->SetBlendMode_AlphaBlended();\n    break;\n  case EFilterType::Widescreen:\n    return;\n  case EFilterType::SceneAdd:\n    g_Renderer->SetBlendMode_AdditiveDestColor();\n    break;\n  case EFilterType::NoColor:\n    g_Renderer->SetBlendMode_NoColorWrite();\n    break;\n  default:\n    return;\n  }\n  DrawFilterShape(shape, color, tex, lod);\n  g_Renderer->SetBlendMode_AlphaBlended();\n}\n\nvoid CCameraFilterPass::DrawFullScreenColoredQuad(const zeus::CColor& color) {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CCameraFilterPass::DrawFullScreenColoredQuad\", zeus::skBlue);\n  const auto [lt, rb] = g_Renderer->SetViewportOrtho(true, -4096.f, 4096.f);\n  g_Renderer->SetDepthReadWrite(false, false);\n  g_Renderer->BeginTriangleStrip(4);\n  g_Renderer->PrimColor(color);\n  g_Renderer->PrimVertex({lt.x() - 1.f, 0.f, 1.f + rb.y()});\n  g_Renderer->PrimVertex({lt.x() - 1.f, 0.f, lt.y() - 1.f});\n  g_Renderer->PrimVertex({1.f + rb.x(), 0.f, 1.f + rb.y()});\n  g_Renderer->PrimVertex({1.f + rb.x(), 0.f, lt.y() - 1.f});\n  g_Renderer->EndPrimitive();\n}\n\nvoid CCameraFilterPass::DrawFilterShape(EFilterShape shape, const zeus::CColor& color, CTexture* tex, float lod) {\n  switch (shape) {\n  default:\n    if (tex == nullptr) {\n      DrawFullScreenColoredQuad(color);\n    } else {\n      DrawFullScreenTexturedQuad(color, tex, lod);\n    }\n    break;\n  case EFilterShape::FullscreenQuarters:\n    if (tex == nullptr) {\n      DrawFullScreenColoredQuad(color);\n    } else {\n      DrawFullScreenTexturedQuadQuarters(color, tex, lod);\n    }\n    break;\n  case EFilterShape::CinemaBars:\n    DrawWideScreen(color, tex, lod);\n    break;\n  case EFilterShape::ScanLinesEven:\n    DrawScanLines(color, true);\n    break;\n  case EFilterShape::ScanLinesOdd:\n    DrawScanLines(color, false);\n    break;\n  case EFilterShape::RandomStatic:\n    DrawRandomStatic(color, 1.f, false);\n    break;\n  case EFilterShape::CookieCutterDepthRandomStatic:\n    DrawRandomStatic(color, lod, true);\n    break;\n  }\n}\n\nvoid CCameraFilterPass::DrawFullScreenTexturedQuadQuarters(const zeus::CColor& color, CTexture* tex, float lod) {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CCameraFilterPass::DrawFullScreenTexturedQuadQuarters\", zeus::skBlue);\n  const auto [lt, rb] = g_Renderer->SetViewportOrtho(true, -4096.f, 4096.f);\n  CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvModulate);\n  CGraphics::SetTevOp(ERglTevStage::Stage1, CTevCombiners::kEnvPassthru);\n  g_Renderer->SetDepthReadWrite(false, false);\n  if (tex != nullptr) {\n    tex->Load(GX_TEXMAP0, EClampMode::Clamp);\n  }\n  CGraphics::SetCullMode(ERglCullMode::None);\n  for (int i = 0; i < 4; ++i) {\n    g_Renderer->SetModelMatrix(zeus::CTransform::Scale((i & 1) != 0 ? 1.f : -1.f, 0.f, (i & 2) != 0 ? 1.f : -1.f));\n    CGraphics::StreamBegin(ERglPrimitive::TriangleStrip);\n    CGraphics::StreamColor(color);\n    CGraphics::StreamTexcoord(lod, lod);\n    CGraphics::StreamVertex(lt.x(), 0.f, rb.y());\n    CGraphics::StreamTexcoord(lod, 0.f);\n    CGraphics::StreamVertex(lt.x(), 0.f, 0.f);\n    CGraphics::StreamTexcoord(0.f, lod);\n    CGraphics::StreamVertex(0.f, 0.f, rb.y());\n    CGraphics::StreamTexcoord(0.f, 0.f);\n    CGraphics::StreamVertex(0.f, 0.f, 0.f);\n    CGraphics::StreamEnd();\n  }\n  CGraphics::SetCullMode(ERglCullMode::Front);\n}\n\nvoid CCameraFilterPass::DrawFullScreenTexturedQuad(const zeus::CColor& color, CTexture* tex, float lod) {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CCameraFilterPass::DrawFullScreenTexturedQuad\", zeus::skBlue);\n  const float u = 0.5f - 0.5f * lod;\n  const float v = 0.5f + 0.5f * lod;\n  const auto [lt, rb] = g_Renderer->SetViewportOrtho(true, -4096.f, 4096.f);\n  g_Renderer->SetDepthReadWrite(false, false);\n  if (tex != nullptr) {\n    tex->Load(GX_TEXMAP0, EClampMode::Repeat);\n  }\n  CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvModulate);\n  CGraphics::SetTevOp(ERglTevStage::Stage1, CTevCombiners::kEnvPassthru);\n  CGraphics::StreamBegin(ERglPrimitive::TriangleStrip);\n  CGraphics::StreamColor(color);\n  CGraphics::StreamTexcoord(u, v);\n  CGraphics::StreamVertex(lt.x() - 1.f, 0.f, 1.f + rb.y());\n  CGraphics::StreamTexcoord(u, u);\n  CGraphics::StreamVertex(lt.x() - 1.f, 0.f, lt.y() - 1.f);\n  CGraphics::StreamTexcoord(v, v);\n  CGraphics::StreamVertex(1.f + rb.x(), 0.f, 1.f + rb.y());\n  CGraphics::StreamTexcoord(v, u);\n  CGraphics::StreamVertex(1.f + rb.x(), 0.f, lt.y() - 1.f);\n  CGraphics::StreamEnd();\n}\n\nvoid CCameraFilterPass::DrawRandomStatic(const zeus::CColor& color, float alpha, bool cookieCutterDepth) {\n  // TODO this shouldn't be here\n  static CTexture m_randomStatic{ETexelFormat::IA4, 640, 448, 1, \"Camera Random Static\"};\n\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CCameraFilterPass::DrawRandomStatic\", zeus::skBlue);\n  const auto [lb, rt] = g_Renderer->SetViewportOrtho(true, 0.f, 1.f);\n  if (cookieCutterDepth) {\n    CGraphics::SetAlphaCompare(ERglAlphaFunc::GEqual, static_cast<u8>((1.f - alpha) * 255.f), ERglAlphaOp::And,\n                               ERglAlphaFunc::Always, 0);\n    g_Renderer->SetDepthReadWrite(true, true);\n    CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvModulate);\n    CGraphics::SetTevOp(ERglTevStage::Stage1, CTevCombiners::kEnvPassthru);\n  } else {\n    g_Renderer->SetDepthReadWrite(false, false);\n    CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvModulateColor);\n    CGraphics::SetTevOp(ERglTevStage::Stage1, CTevCombiners::kEnvPassthru);\n  }\n\n  // Upload random static texture (game reads from .text)\n  const u8* buf = CDvdFile::GetDolBuf() + 0x4f60;\n  u8* out = m_randomStatic.Lock();\n  memcpy(out, buf + ROUND_UP_32(rand() & 0x7fff), m_randomStatic.GetMemoryAllocated());\n  m_randomStatic.UnLock();\n  m_randomStatic.Load(GX_TEXMAP0, EClampMode::Clamp);\n\n  CGraphics::StreamBegin(ERglPrimitive::TriangleStrip);\n  CGraphics::StreamColor(color);\n  CGraphics::StreamTexcoord(0.f, 1.f);\n  CGraphics::StreamVertex(lb.x() - 1.f, 0.01f, rt.y() + 1.f);\n  CGraphics::StreamTexcoord(0.f, 0.f);\n  CGraphics::StreamVertex(lb.x() - 1.f, 0.01f, lb.y() - 1.f);\n  CGraphics::StreamTexcoord(1.f, 1.f);\n  CGraphics::StreamVertex(rt.x() + 1.f, 0.01f, rt.y() + 1.f);\n  CGraphics::StreamTexcoord(1.f, 0.f);\n  CGraphics::StreamVertex(rt.x() + 1.f, 0.01f, lb.y() - 1.f);\n  CGraphics::StreamEnd();\n  if (cookieCutterDepth) {\n    CGraphics::SetAlphaCompare(ERglAlphaFunc::Always, 0, ERglAlphaOp::And, ERglAlphaFunc::Always, 0);\n  }\n}\n\nvoid CCameraFilterPass::DrawScanLines(const zeus::CColor& color, bool even) {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CCameraFilterPass::DrawScanLines\", zeus::skBlue);\n  const auto [lt, rb] = g_Renderer->SetViewportOrtho(true, -4096.f, 4096.f);\n  g_Renderer->SetDepthReadWrite(false, false);\n  g_Renderer->SetModelMatrix({});\n  // CGraphics::SetLineWidth(2.f, 5);\n  // g_Renderer->BeginLines(...);\n  // TODO\n  // CGraphics::SetLineWidth(1.f, 5);\n}\n\nvoid CCameraFilterPass::DrawWideScreen(const zeus::CColor& color, CTexture* tex, float lod) {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CCameraFilterPass::DrawWideScreen\", zeus::skBlue);\n  //  const auto vp = g_Renderer->SetViewportOrtho(true, -4096.f, 4096.f);\n  //  float f = -((vp.second.x() - vp.first.x()) * 0.0625f * 9.f - (vp.second.y() - vp.first.y())) * 0.5f;\n  //  g_Renderer->SetDepthReadWrite(false, false);\n  //  g_Renderer->SetModelMatrix({});\n  //  if (tex != nullptr) {\n  //    tex->Load(GX_TEXMAP0, EClampMode::Repeat);\n  //  }\n  //  CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::sTevPass805a5ebc);\n  //  CGraphics::SetTevOp(ERglTevStage::Stage1, CTevCombiners::skPassThru);\n  //  CGraphics::StreamBegin(GX_TRIANGLESTRIP);\n  //  float x = rand() % 4000;\n}\n\nvoid CCameraBlurPass::Draw(bool clearDepth) {\n  if (x10_curType == EBlurType::NoBlur)\n    return;\n\n  // TODO\n  //  if (x10_curType == EBlurType::Xray) {\n  //    if (!m_xrayShader)\n  //      m_xrayShader.emplace(x0_paletteTex);\n  //    m_xrayShader->draw(x1c_curValue);\n  //  } else {\n  //    if (!m_shader)\n  //      m_shader.emplace();\n  //    m_shader->draw(x1c_curValue, clearDepth);\n  //    if (clearDepth)\n  //      CGraphics::SetDepthRange(DEPTH_NEAR, DEPTH_FAR);\n  //  }\n}\n\nvoid CCameraBlurPass::Update(float dt) {\n  if (x28_remainingTime > 0.f) {\n    x28_remainingTime = std::max(x28_remainingTime - dt, 0.f);\n    x1c_curValue = x18_endValue + (x20_startValue - x18_endValue) * x28_remainingTime / x24_totalTime;\n\n    if (x28_remainingTime != 0.f)\n      return;\n\n    x10_curType = x14_endType;\n  }\n}\n\nvoid CCameraBlurPass::SetBlur(EBlurType type, float amount, float duration, bool usePersistentFb) {\n  // TODO impl usePersistentFb\n  if (duration == 0.f) {\n    x24_totalTime = 0.f;\n    x28_remainingTime = 0.f;\n    x18_endValue = amount;\n    x1c_curValue = amount;\n    x20_startValue = amount;\n\n    if (x10_curType == EBlurType::NoBlur) {\n      if (type == EBlurType::Xray)\n        x0_paletteTex = g_SimplePool->GetObj(\"TXTR_XRayPalette\");\n    }\n\n    x14_endType = type;\n    x10_curType = type;\n    x2c_usePersistent = usePersistentFb;\n  } else {\n    x2c_usePersistent = usePersistentFb;\n    x24_totalTime = duration;\n    x28_remainingTime = duration;\n    x18_endValue = x1c_curValue;\n    x20_startValue = amount;\n\n    if (type != x14_endType) {\n      if (x10_curType == EBlurType::NoBlur) {\n        if (type == EBlurType::Xray)\n          x0_paletteTex = g_SimplePool->GetObj(\"TXTR_XRayPalette\");\n        x10_curType = type;\n      }\n      x14_endType = type;\n    }\n  }\n}\n\nvoid CCameraBlurPass::DisableBlur(float duration) { SetBlur(EBlurType::NoBlur, 0.f, duration, x2c_usePersistent); }\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Camera/CCameraFilter.hpp",
    "content": "#pragma once\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/Graphics/CTexture.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n\n#include <zeus/CColor.hpp>\n\nnamespace metaforce {\nenum class EFilterType {\n  Passthru,\n  Multiply,\n  Invert,\n  Add,\n  Subtract,\n  Blend,\n  Widescreen,\n  SceneAdd,\n  NoColor,\n  InvDstMultiply\n};\n\nenum class EFilterShape {\n  Fullscreen,\n  FullscreenHalvesLeftRight,\n  FullscreenHalvesTopBottom,\n  FullscreenQuarters,\n  CinemaBars,\n  ScanLinesEven,\n  ScanLinesOdd,\n  RandomStatic,\n  CookieCutterDepthRandomStatic\n};\n\nclass CCameraFilterPass {\n  EFilterType x0_curType = EFilterType::Passthru;\n  EFilterType x4_nextType = EFilterType::Passthru;\n  EFilterShape x8_shape = EFilterShape::Fullscreen;\n  float xc_duration = 0.f;\n  float x10_remTime = 0.f;\n  zeus::CColor x14_prevColor;\n  zeus::CColor x18_curColor;\n  zeus::CColor x1c_nextColor;\n  CAssetId x20_nextTxtr;\n  TLockedToken<CTexture> x24_texObj; // Used to be auto_ptr\n\n  [[nodiscard]] float GetT(bool invert) const;\n\n  static void DrawFilterShape(EFilterShape shape, const zeus::CColor& color, CTexture* tex, float lod);\n  static void DrawFullScreenColoredQuad(const zeus::CColor& color);\n  static void DrawFullScreenTexturedQuad(const zeus::CColor& color, CTexture* tex, float lod);\n  static void DrawFullScreenTexturedQuadQuarters(const zeus::CColor& color, CTexture* tex, float lod);\n  static void DrawRandomStatic(const zeus::CColor& color, float alpha, bool cookieCutterDepth);\n  static void DrawScanLines(const zeus::CColor& color, bool even);\n  static void DrawWideScreen(const zeus::CColor& color, CTexture* tex, float lod);\n\npublic:\n  void Update(float dt);\n  void SetFilter(EFilterType type, EFilterShape shape, float time, const zeus::CColor& color, CAssetId txtr);\n  void DisableFilter(float time);\n  void Draw();\n\n  static void DrawFilter(EFilterType type, EFilterShape shape, const zeus::CColor& color, CTexture* tex, float lod);\n};\n\nenum class EBlurType { NoBlur, LoBlur, HiBlur, Xray };\n\nclass CCameraBlurPass {\n  TLockedToken<CTexture> x0_paletteTex;\n  EBlurType x10_curType = EBlurType::NoBlur;\n  EBlurType x14_endType = EBlurType::NoBlur;\n  float x18_endValue = 0.f;\n  float x1c_curValue = 0.f;\n  float x20_startValue = 0.f;\n  float x24_totalTime = 0.f;\n  float x28_remainingTime = 0.f;\n  bool x2c_usePersistent = false;\n  bool x2d_noPersistentCopy = false;\n  u32 x30_persistentBuf = 0;\n\npublic:\n  void Draw(bool clearDepth = false);\n  void Update(float dt);\n  void SetBlur(EBlurType type, float amount, float duration, bool usePersistentFb);\n  void DisableBlur(float duration);\n  EBlurType GetCurrType() const { return x10_curType; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Camera/CCameraManager.cpp",
    "content": "#include \"Runtime/Camera/CCameraManager.hpp\"\n\n#include <algorithm>\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Camera/CBallCamera.hpp\"\n#include \"Runtime/Camera/CCameraShakeData.hpp\"\n#include \"Runtime/Camera/CCinematicCamera.hpp\"\n#include \"Runtime/Camera/CFirstPersonCamera.hpp\"\n#include \"Runtime/Camera/CInterpolationCamera.hpp\"\n#include \"Runtime/Camera/CPathCamera.hpp\"\n#include \"Runtime/World/CExplosion.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CScriptCameraHint.hpp\"\n#include \"Runtime/World/CScriptSpindleCamera.hpp\"\n#include \"Runtime/World/CScriptWater.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nfloat CCameraManager::sFirstPersonFOV = 55.f;\n\nCCameraManager::CCameraManager(TUniqueId curCameraId) : x0_curCameraId(curCameraId) {\n  CSfxManager::AddListener(CSfxManager::ESfxChannels::Game, zeus::skZero3f, zeus::skZero3f, {1.f, 0.f, 0.f},\n                           {0.f, 0.f, 1.f}, 50.f, 50.f, 1000.f, 1, 1.f);\n  sFirstPersonFOV = g_tweakGame->GetFirstPersonFOV();\n}\n\nbool CCameraManager::IsInFirstPersonCamera() const { return x7c_fpCamera->GetUniqueId() == x0_curCameraId; }\n\nzeus::CVector3f CCameraManager::GetGlobalCameraTranslation(const CStateManager& stateMgr) const {\n  const CGameCamera* camera = GetCurrentCamera(stateMgr);\n  return camera->GetTransform().rotate(x30_shakeOffset);\n}\n\nzeus::CTransform CCameraManager::GetCurrentCameraTransform(const CStateManager& stateMgr) const {\n  const CGameCamera* camera = GetCurrentCamera(stateMgr);\n  return camera->GetTransform() * zeus::CTransform::Translate(x30_shakeOffset);\n}\n\nvoid CCameraManager::RemoveCameraShaker(u32 id) {\n  const auto iter = std::find_if(x14_shakers.cbegin(), x14_shakers.cend(),\n                                 [id](const auto& shaker) { return shaker.xbc_shakerId == id; });\n  if (iter == x14_shakers.cend()) {\n    return;\n  }\n  x14_shakers.erase(iter);\n}\n\nint CCameraManager::AddCameraShaker(const CCameraShakeData& data, bool sfx) {\n  x14_shakers.emplace_back(data).xbc_shakerId = ++x2c_lastShakeId;\n  if (!xa0_24_pendingRumble) {\n    xa0_24_pendingRumble = true;\n    x90_rumbleCooldown = 0.5f;\n  }\n  if (sfx && data.x0_duration > 0.f) {\n    float vol = zeus::clamp(100.f, std::max(data.GetMaxAMComponent(), data.GetMaxFMComponent()) * 9.f + 100.f, 127.f);\n    CSfxHandle sfxHandle;\n    if (data.xc0_flags & 0x1)\n      sfxHandle = CSfxManager::AddEmitter(SFXamb_x_rumble_lp_00, data.xc4_sfxPos, zeus::skZero3f, vol / 127.f, false,\n                                          false, 0x7f, kInvalidAreaId);\n    else\n      sfxHandle = CSfxManager::SfxStart(SFXamb_x_rumble_lp_00, vol / 127.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n    sfxHandle->SetTimeRemaining(data.x0_duration);\n  }\n  return x2c_lastShakeId;\n}\n\nvoid CCameraManager::EnterCinematic(CStateManager& mgr) {\n  mgr.GetPlayer().GetPlayerGun()->CancelFiring(mgr);\n  mgr.GetPlayer().UnFreeze(mgr);\n\n  for (const CEntity* ent : mgr.GetAllObjectList()) {\n    if (const TCastToConstPtr<CExplosion> explo = ent) {\n      mgr.FreeScriptObject(explo->GetUniqueId());\n    } else if (const TCastToConstPtr<CWeapon> weap = ent) {\n      if (weap->GetActive()) {\n        if (False(weap->GetAttribField() & EProjectileAttrib::KeepInCinematic)) {\n          if (TCastToConstPtr<CPatterned>(mgr.GetObjectById(weap->GetOwnerId())) ||\n              TCastToConstPtr<CPlayer>(mgr.GetObjectById(weap->GetOwnerId())))\n            mgr.FreeScriptObject(weap->GetUniqueId());\n        }\n      }\n    }\n  }\n}\n\nvoid CCameraManager::AddCinemaCamera(TUniqueId id, CStateManager& stateMgr) {\n  if (x4_cineCameras.empty()) {\n    EnterCinematic(stateMgr);\n  }\n\n  RemoveCinemaCamera(id, stateMgr);\n  x4_cineCameras.push_back(id);\n\n  if (const TCastToPtr<CCinematicCamera> cam = stateMgr.ObjectById(id)) {\n    // Into player eye\n    if ((cam->GetFlags() & 0x4) != 0) {\n      float time = 4.f;\n      float delayTime = cam->GetDuration() - 4.f;\n      if (delayTime < 0.f) {\n        delayTime = 0.f;\n        time = cam->GetDuration();\n      }\n      cam->SetFovInterpolation(cam->GetFov(), 55.f, time, delayTime);\n    }\n  }\n}\n\nvoid CCameraManager::SetInsideFluid(bool isInside, TUniqueId fluidId) {\n  if (isInside) {\n    ++x74_fluidCounter;\n    x78_fluidId = fluidId;\n  } else {\n    --x74_fluidCounter;\n  }\n}\n\nvoid CCameraManager::Update(float dt, CStateManager& stateMgr) {\n  UpdateCameraHints(dt, stateMgr);\n  ThinkCameras(dt, stateMgr);\n  UpdateListener(stateMgr);\n  UpdateRumble(dt, stateMgr);\n  UpdateFog(dt, stateMgr);\n}\n\nCGameCamera* CCameraManager::GetCurrentCamera(CStateManager& stateMgr) const {\n  CObjectList* camList = stateMgr.ObjectListById(EGameObjectList::GameCamera);\n  return static_cast<CGameCamera*>(camList->GetObjectById(GetCurrentCameraId()));\n}\n\nconst CGameCamera* CCameraManager::GetCurrentCamera(const CStateManager& stateMgr) const {\n  const CObjectList* camList = stateMgr.GetObjectListById(EGameObjectList::GameCamera);\n  return static_cast<const CGameCamera*>(camList->GetObjectById(GetCurrentCameraId()));\n}\n\nvoid CCameraManager::CreateStandardCameras(CStateManager& stateMgr) {\n  TUniqueId fpId = stateMgr.AllocateUniqueId();\n  x7c_fpCamera =\n      new CFirstPersonCamera(fpId, zeus::CTransform(), stateMgr.Player()->GetUniqueId(),\n                             g_tweakPlayer->GetOrbitCameraSpeed(), sFirstPersonFOV, NearPlane(), FarPlane(), Aspect());\n  stateMgr.AddObject(x7c_fpCamera);\n  stateMgr.Player()->SetCameraState(CPlayer::EPlayerCameraState::FirstPerson, stateMgr);\n  SetCurrentCameraId(fpId, stateMgr);\n\n  x80_ballCamera = new CBallCamera(stateMgr.AllocateUniqueId(), stateMgr.Player()->GetUniqueId(), zeus::CTransform(),\n                                   ThirdPersonFOV(), NearPlane(), FarPlane(), Aspect());\n  stateMgr.AddObject(x80_ballCamera);\n\n  x88_interpCamera = new CInterpolationCamera(stateMgr.AllocateUniqueId(), zeus::CTransform());\n  stateMgr.AddObject(x88_interpCamera);\n}\n\nvoid CCameraManager::SkipCinematic(CStateManager& stateMgr) {\n  const TUniqueId camId = GetCurrentCameraId();\n  auto* ent = static_cast<CCinematicCamera*>(stateMgr.ObjectById(camId));\n  while (ent) {\n    ent->SetActive(false);\n    ent->WasDeactivated(stateMgr);\n    ent = TCastToPtr<CCinematicCamera>(GetCurrentCamera(stateMgr)).GetPtr();\n  }\n  stateMgr.GetPlayer().UpdateCinematicState(stateMgr);\n  x7c_fpCamera->SkipCinematic();\n}\n\nvoid CCameraManager::SetPathCamera(TUniqueId id, CStateManager& mgr) {\n  xa4_pathCamId = id;\n  if (const TCastToPtr<CPathCamera> cam = mgr.ObjectById(id)) {\n    cam->Reset(GetCurrentCameraTransform(mgr), mgr);\n    x80_ballCamera->TeleportCamera(cam->GetTransform(), mgr);\n  }\n}\n\nvoid CCameraManager::SetSpindleCamera(TUniqueId id, CStateManager& mgr) {\n  xa2_spindleCamId = id;\n  if (const TCastToPtr<CScriptSpindleCamera> cam = mgr.ObjectById(id)) {\n    cam->Reset(GetCurrentCameraTransform(mgr), mgr);\n    x80_ballCamera->TeleportCamera(cam->GetTransform(), mgr);\n  }\n}\n\nvoid CCameraManager::InterpolateToBallCamera(const zeus::CTransform& xf, TUniqueId camId,\n                                             const zeus::CVector3f& lookPos, float maxTime, float positionSpeed,\n                                             float rotationSpeed, bool sinusoidal, CStateManager& mgr) {\n  if (!IsInFirstPersonCamera()) {\n    x88_interpCamera->SetInterpolation(xf, lookPos, maxTime, positionSpeed, rotationSpeed, camId, sinusoidal, mgr);\n    if (!ShouldBypassInterpolation())\n      SetCurrentCameraId(x88_interpCamera->GetUniqueId(), mgr);\n  }\n}\n\nvoid CCameraManager::RestoreHintlessCamera(CStateManager& mgr) {\n  const TCastToConstPtr<CScriptCameraHint> hint = mgr.ObjectById(xa6_camHintId);\n  const zeus::CTransform ballCamXf = x80_ballCamera->GetTransform();\n\n  xa6_camHintId = kInvalidUniqueId;\n  xa8_hintPriority = 1000;\n\n  if (!hint) {\n    return;\n  }\n\n  zeus::CVector3f camToPlayerFlat = mgr.GetPlayer().GetTranslation() - ballCamXf.origin;\n  camToPlayerFlat.z() = 0.f;\n  if (camToPlayerFlat.canBeNormalized()) {\n    camToPlayerFlat.normalize();\n  } else {\n    camToPlayerFlat = mgr.GetPlayer().GetMoveDir();\n  }\n\n  x80_ballCamera->ResetToTweaks(mgr);\n  x80_ballCamera->UpdateLookAtPosition(0.f, mgr);\n  if (!mgr.GetPlayer().IsMorphBallTransitioning() &&\n      hint->GetHint().GetBehaviourType() != CBallCamera::EBallCameraBehaviour::Default) {\n    if ((hint->GetHint().GetOverrideFlags() & 0x1000) != 0) {\n      x80_ballCamera->SetClampVelRange(hint->GetHint().GetClampVelRange());\n      x80_ballCamera->SetClampVelTimer(hint->GetHint().GetClampVelTime());\n    } else {\n      x80_ballCamera->TeleportCamera(x80_ballCamera->UpdateLookDirection(camToPlayerFlat, mgr), mgr);\n      InterpolateToBallCamera(ballCamXf, x80_ballCamera->GetUniqueId(), x80_ballCamera->GetLookPos(),\n                              hint->GetHint().GetClampVelTime(), hint->GetHint().GetClampVelRange(),\n                              hint->GetHint().GetClampRotRange(), (hint->GetHint().GetOverrideFlags() & 0x800) != 0,\n                              mgr);\n    }\n  }\n}\n\nvoid CCameraManager::SkipBallCameraCinematic(CStateManager& mgr) {\n  if (IsInCinematicCamera()) {\n    x80_ballCamera->TeleportCamera(GetLastCineCamera(mgr)->GetTransform(), mgr);\n    x80_ballCamera->SetFovInterpolation(GetLastCineCamera(mgr)->GetFov(), x80_ballCamera->GetFov(), 1.f, 0.f);\n    SkipCinematic(mgr);\n    SetCurrentCameraId(x80_ballCamera->GetUniqueId(), mgr);\n  }\n}\n\nvoid CCameraManager::ApplyCameraHint(const CScriptCameraHint& hint, CStateManager& mgr) {\n  if (x80_ballCamera->GetState() == CBallCamera::EBallCameraState::ToBall) {\n    x80_ballCamera->SetState(CBallCamera::EBallCameraState::Default, mgr);\n    mgr.GetPlayer().SetCameraState(CPlayer::EPlayerCameraState::Ball, mgr);\n  }\n\n  const TCastToConstPtr<CScriptCameraHint> oldHint = mgr.ObjectById(xa6_camHintId);\n  xa6_camHintId = hint.GetUniqueId();\n  xa8_hintPriority = hint.GetPriority();\n\n  const zeus::CTransform camXf = GetCurrentCameraTransform(mgr);\n  x80_ballCamera->ApplyCameraHint(mgr);\n\n  if ((hint.GetHint().GetOverrideFlags() & 0x20) != 0) {\n    x80_ballCamera->ResetPosition(mgr);\n  }\n\n  switch (hint.GetHint().GetBehaviourType()) {\n  case CBallCamera::EBallCameraBehaviour::PathCameraDesiredPos:\n  case CBallCamera::EBallCameraBehaviour::PathCamera:\n    SetPathCamera(hint.GetDelegatedCamera(), mgr);\n    break;\n  case CBallCamera::EBallCameraBehaviour::SpindleCamera:\n    SetSpindleCamera(hint.GetDelegatedCamera(), mgr);\n    break;\n  default:\n    SetPathCamera(kInvalidUniqueId, mgr);\n    SetSpindleCamera(kInvalidUniqueId, mgr);\n    break;\n  }\n\n  if ((hint.GetHint().GetOverrideFlags() & 0x2000) != 0) {\n    SkipBallCameraCinematic(mgr);\n  }\n\n  x80_ballCamera->UpdateLookAtPosition(0.f, mgr);\n\n  if ((hint.GetHint().GetOverrideFlags() & 0x20) == 0 &&\n      (hint.GetHint().GetBehaviourType() != CBallCamera::EBallCameraBehaviour::Default ||\n       (oldHint && oldHint->GetHint().GetBehaviourType() != CBallCamera::EBallCameraBehaviour::Default))) {\n    InterpolateToBallCamera(camXf, x80_ballCamera->GetUniqueId(), x80_ballCamera->GetLookPos(),\n                            hint.GetHint().GetInterpolateTime(), hint.GetHint().GetClampVelRange(),\n                            hint.GetHint().GetClampRotRange(), (hint.GetHint().GetOverrideFlags() & 0x400) != 0, mgr);\n  }\n}\n\nvoid CCameraManager::UpdateCameraHints(float, CStateManager& mgr) {\n  bool invalidHintRemoved = false;\n  for (auto it = xac_cameraHints.begin(); it != xac_cameraHints.end();) {\n    if (!TCastToConstPtr<CScriptCameraHint>(mgr.ObjectById(it->second))) {\n      invalidHintRemoved = true;\n      it = xac_cameraHints.erase(it);\n      continue;\n    }\n    ++it;\n  }\n\n  bool inactiveHintRemoved = false;\n  for (const auto& id : x2b0_inactiveCameraHints) {\n    if (const TCastToConstPtr<CScriptCameraHint> hint = mgr.GetObjectById(id)) {\n      if (hint->GetHelperCount() == 0 || hint->GetInactive()) {\n        for (auto it = xac_cameraHints.begin(); it != xac_cameraHints.end(); ++it) {\n          if (it->second == id) {\n            xac_cameraHints.erase(it);\n            if (xa6_camHintId == id) {\n              inactiveHintRemoved = true;\n              SetPathCamera(kInvalidUniqueId, mgr);\n              SetSpindleCamera(kInvalidUniqueId, mgr);\n            }\n            break;\n          }\n        }\n      }\n    }\n  }\n  x2b0_inactiveCameraHints.clear();\n\n  bool activeHintAdded = false;\n  for (const auto& id : x334_activeCameraHints) {\n    if (const TCastToConstPtr<CScriptCameraHint> hint = mgr.GetObjectById(id)) {\n      bool activeHintPresent = false;\n      for (auto it = xac_cameraHints.begin(); it != xac_cameraHints.end(); ++it) {\n        if (it->second == id) {\n          activeHintPresent = true;\n          break;\n        }\n      }\n\n      if (!activeHintPresent) {\n        activeHintAdded = true;\n        xac_cameraHints.emplace_back(hint->GetPriority(), id);\n      }\n    }\n  }\n  x334_activeCameraHints.clear();\n\n  if (inactiveHintRemoved || activeHintAdded || invalidHintRemoved) {\n    std::sort(xac_cameraHints.begin(), xac_cameraHints.end(),\n              [](const auto& a, const auto& b) { return a.first < b.first; });\n    zeus::CTransform ballCamXf = x80_ballCamera->GetTransform();\n    if ((inactiveHintRemoved || invalidHintRemoved) && xac_cameraHints.empty()) {\n      RestoreHintlessCamera(mgr);\n      return;\n    }\n    bool foundHint = false;\n    const CScriptCameraHint* bestHint = nullptr;\n    for (auto& h : xac_cameraHints) {\n      if (const TCastToConstPtr<CScriptCameraHint> hint = mgr.ObjectById(h.second)) {\n        bestHint = hint.GetPtr();\n        foundHint = true;\n        break;\n      }\n    }\n    if (!foundHint) {\n      RestoreHintlessCamera(mgr);\n    }\n\n    bool changeHint = false;\n    if (bestHint && foundHint) {\n      if ((bestHint->GetHint().GetOverrideFlags() & 0x80) != 0 && xac_cameraHints.size() > 1) {\n        zeus::CVector3f ballPos = mgr.GetPlayer().GetBallPosition();\n        if ((bestHint->GetHint().GetOverrideFlags() & 0x100) != 0) {\n          zeus::CVector3f camToBall = ballPos - ballCamXf.origin;\n          if (camToBall.canBeNormalized()) {\n            camToBall.normalize();\n          } else {\n            camToBall = ballCamXf.basis[1];\n          }\n\n          for (auto it = xac_cameraHints.begin() + 1; it != xac_cameraHints.end(); ++it) {\n            if (const TCastToConstPtr<CScriptCameraHint> hint = mgr.ObjectById(it->second)) {\n              if ((hint->GetHint().GetOverrideFlags() & 0x80) != 0 && hint->GetPriority() == bestHint->GetPriority() &&\n                  hint->GetAreaIdAlways() == bestHint->GetAreaIdAlways()) {\n                zeus::CVector3f hintToBall = ballPos - bestHint->GetTranslation();\n                if (hintToBall.canBeNormalized()) {\n                  hintToBall.normalize();\n                } else {\n                  hintToBall = bestHint->GetTransform().basis[1];\n                }\n\n                const float camHintDot = zeus::clamp(-1.f, camToBall.dot(hintToBall), 1.f);\n\n                zeus::CVector3f thisHintToBall = ballPos - hint->GetTranslation();\n                if (thisHintToBall.canBeNormalized()) {\n                  thisHintToBall.normalize();\n                } else {\n                  thisHintToBall = hint->GetTransform().basis[1];\n                }\n\n                const float camThisHintDot = zeus::clamp(-1.f, camToBall.dot(thisHintToBall), 1.f);\n\n                if (camThisHintDot > camHintDot) {\n                  bestHint = hint.GetPtr();\n                }\n              } else {\n                break;\n              }\n            } else {\n              break;\n            }\n          }\n        } else {\n          if (const TCastToConstPtr<CActor> act = mgr.GetObjectById(bestHint->GetFirstHelper())) {\n            const zeus::CVector3f f26 = act->GetTranslation() - mgr.GetPlayer().GetBallPosition();\n            zeus::CVector3f ballToHelper = f26;\n            if (ballToHelper.canBeNormalized()) {\n              ballToHelper.normalize();\n            } else {\n              ballToHelper = bestHint->GetTransform().basis[1];\n            }\n\n            for (auto it = xac_cameraHints.begin() + 1; it != xac_cameraHints.end(); ++it) {\n              if (const TCastToConstPtr<CScriptCameraHint> hint = mgr.ObjectById(it->second)) {\n                if ((hint->GetHint().GetOverrideFlags() & 0x80) != 0 &&\n                    hint->GetPriority() == bestHint->GetPriority() &&\n                    hint->GetAreaIdAlways() == bestHint->GetAreaIdAlways()) {\n                  zeus::CVector3f hintToHelper = act->GetTranslation() - bestHint->GetTranslation();\n                  if (hintToHelper.canBeNormalized()) {\n                    hintToHelper.normalize();\n                  } else {\n                    hintToHelper = bestHint->GetTransform().basis[1];\n                  }\n\n                  const float ballHintDot = zeus::clamp(-1.f, ballToHelper.dot(hintToHelper), 1.f);\n\n                  zeus::CVector3f thisBallToHelper = f26;\n                  if (thisBallToHelper.canBeNormalized()) {\n                    thisBallToHelper.normalize();\n                  } else {\n                    thisBallToHelper = hint->GetTransform().basis[1];\n                  }\n\n                  zeus::CVector3f thisHintToHelper = act->GetTranslation() - hint->GetTranslation();\n                  if (thisHintToHelper.canBeNormalized()) {\n                    thisHintToHelper.normalize();\n                  } else {\n                    thisHintToHelper = hint->GetTransform().basis[1];\n                  }\n\n                  const float thisBallHintDot = zeus::clamp(-1.f, thisBallToHelper.dot(thisHintToHelper), 1.f);\n\n                  if (thisBallHintDot > ballHintDot) {\n                    bestHint = hint.GetPtr();\n                  }\n                } else {\n                  break;\n                }\n              } else {\n                break;\n              }\n            }\n          }\n        }\n\n        if (bestHint->GetUniqueId() != xa6_camHintId) {\n          changeHint = true;\n        }\n      } else if (xa6_camHintId != bestHint->GetUniqueId()) {\n        if (bestHint->GetHint().GetBehaviourType() == CBallCamera::EBallCameraBehaviour::HintInitializePosition) {\n          if ((bestHint->GetHint().GetOverrideFlags() & 0x20) != 0) {\n            x80_ballCamera->TeleportCamera(zeus::lookAt(bestHint->GetTranslation(), x80_ballCamera->GetLookPos()), mgr);\n          }\n          DeleteCameraHint(bestHint->GetUniqueId(), mgr);\n          if ((bestHint->GetHint().GetOverrideFlags() & 0x2000) != 0) {\n            SkipBallCameraCinematic(mgr);\n          }\n          changeHint = false;\n        } else {\n          changeHint = true;\n        }\n      }\n\n      if (changeHint) {\n        ApplyCameraHint(*bestHint, mgr);\n      }\n    }\n  }\n}\n\nvoid CCameraManager::ThinkCameras(float dt, CStateManager& mgr) {\n  CGameCameraList& gcList = mgr.GetCameraObjectList();\n\n  for (CEntity* ent : gcList) {\n    if (const TCastToPtr<CGameCamera> gc = ent) {\n      gc->Think(dt, mgr);\n      gc->UpdatePerspective(dt);\n    }\n  }\n\n  if (IsInCinematicCamera()) {\n    return;\n  }\n\n  const TUniqueId camId = GetLastCameraId();\n  if (const CGameCamera* cam = TCastToConstPtr<CGameCamera>(mgr.GetObjectById(camId))) {\n    x3bc_curFov = cam->GetFov();\n  }\n}\n\nvoid CCameraManager::UpdateFog(float dt, CStateManager& mgr) {\n  if (x98_fogDensitySpeed != 0.f) {\n    x94_fogDensityFactor += dt * x98_fogDensitySpeed;\n    if ((x98_fogDensitySpeed > 0.f) ? x94_fogDensityFactor > x9c_fogDensityFactorTarget\n                                    : x94_fogDensityFactor < x9c_fogDensityFactorTarget) {\n      x94_fogDensityFactor = x9c_fogDensityFactorTarget;\n      x98_fogDensitySpeed = 0.f;\n    }\n  }\n\n  if (x74_fluidCounter) {\n    if (const TCastToConstPtr<CScriptWater> water = mgr.GetObjectById(x78_fluidId)) {\n      const zeus::CVector2f zRange(GetCurrentCamera(mgr)->GetNearClipDistance(),\n                                   CalculateFogDensity(mgr, water.GetPtr()));\n      x3c_fog.SetFogExplicit(ERglFogMode::PerspExp, water->GetInsideFogColor(), zRange);\n      if (mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::Thermal) {\n        mgr.GetCameraFilterPass(4).DisableFilter(0.f);\n      } else {\n        mgr.GetCameraFilterPass(4).SetFilter(EFilterType::Multiply, EFilterShape::Fullscreen, 0.f,\n                                             water->GetInsideFogColor(), {});\n      }\n    }\n    xa0_26_inWater = true;\n  } else if (xa0_26_inWater) {\n    mgr.GetCameraManager()->x3c_fog.DisableFog();\n    mgr.GetCameraFilterPass(4).DisableFilter(0.f);\n    xa0_26_inWater = false;\n  }\n\n  x3c_fog.Update(dt);\n}\n\nvoid CCameraManager::UpdateRumble(float dt, CStateManager& mgr) {\n  x30_shakeOffset = zeus::skZero3f;\n  for (auto it = x14_shakers.begin(); it != x14_shakers.end();) {\n    CCameraShakeData& shaker = *it;\n    shaker.Update(dt, mgr);\n    if (shaker.x4_curTime >= shaker.x0_duration) {\n      it = x14_shakers.erase(it);\n      continue;\n    }\n    x30_shakeOffset += shaker.GetPoint();\n    ++it;\n  }\n\n  if (!x14_shakers.empty() && !xa0_25_rumbling && xa0_24_pendingRumble) {\n    mgr.GetRumbleManager().Rumble(mgr, ERumbleFxId::CameraShake, 1.f, ERumblePriority::Two);\n    xa0_25_rumbling = true;\n  }\n\n  if (x90_rumbleCooldown > 0.f) {\n    x90_rumbleCooldown -= dt;\n  } else if (xa0_25_rumbling) {\n    xa0_24_pendingRumble = false;\n    xa0_25_rumbling = false;\n  }\n\n  if (mgr.GetPlayer().GetCameraState() != CPlayer::EPlayerCameraState::FirstPerson && !IsInCinematicCamera()) {\n    x30_shakeOffset = zeus::skZero3f;\n  }\n}\n\nvoid CCameraManager::UpdateListener(CStateManager& mgr) {\n  const zeus::CTransform xf = GetCurrentCameraTransform(mgr);\n  CSfxManager::UpdateListener(xf.origin, zeus::skZero3f, xf.frontVector(), xf.upVector(), 1.f);\n}\n\nfloat CCameraManager::CalculateFogDensity(CStateManager& mgr, const CScriptWater* water) const {\n  const float distanceFactor = 1.f - water->GetFluidPlane().GetAlpha();\n  float distance = 0;\n  if (mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::GravitySuit)) {\n    distance =\n        g_tweakGame->GetGravityWaterFogDistanceRange() * distanceFactor + g_tweakGame->GetGravityWaterFogDistanceBase();\n  } else {\n    distance = g_tweakGame->GetWaterFogDistanceRange() * distanceFactor + g_tweakGame->GetWaterFogDistanceBase();\n  }\n\n  return distance * x94_fogDensityFactor;\n}\n\nvoid CCameraManager::ResetCameras(CStateManager& mgr) {\n  zeus::CTransform xf = mgr.GetPlayer().CreateTransformFromMovementDirection();\n  xf.origin = mgr.GetPlayer().GetEyePosition();\n\n  for (CEntity* ent : mgr.GetCameraObjectList()) {\n    const TCastToPtr<CGameCamera> camObj(ent);\n    camObj->Reset(xf, mgr);\n  }\n}\n\nvoid CCameraManager::SetSpecialCameras(CFirstPersonCamera& fp, CBallCamera& ball) {\n  x7c_fpCamera = &fp;\n  x80_ballCamera = &ball;\n}\n\nvoid CCameraManager::ProcessInput(const CFinalInput& input, CStateManager& stateMgr) {\n  for (CEntity* ent : stateMgr.GetCameraObjectList()) {\n    if (ent == nullptr) {\n      continue;\n    }\n    auto& cam = static_cast<CGameCamera&>(*ent);\n    if (input.ControllerIdx() != cam.x16c_controllerIdx) {\n      continue;\n    }\n    cam.ProcessInput(input, stateMgr);\n  }\n}\n\nvoid CCameraManager::RenderCameras(CStateManager& mgr) {\n  for (CEntity* cam : mgr.GetCameraObjectList()) {\n    static_cast<CGameCamera*>(cam)->Render(mgr);\n  }\n}\n\nvoid CCameraManager::SetupBallCamera(CStateManager& mgr) {\n  if (const TCastToConstPtr<CScriptCameraHint> hint = mgr.ObjectById(xa6_camHintId)) {\n    if (hint->GetHint().GetBehaviourType() == CBallCamera::EBallCameraBehaviour::HintInitializePosition) {\n      if ((hint->GetHint().GetOverrideFlags() & 0x20) != 0) {\n        x80_ballCamera->TeleportCamera(hint->GetTransform(), mgr);\n      }\n      AddInactiveCameraHint(xa6_camHintId, mgr);\n    } else {\n      ApplyCameraHint(*hint, mgr);\n    }\n  }\n}\n\nvoid CCameraManager::SetPlayerCamera(CStateManager& mgr, TUniqueId newCamId) {\n  if (x88_interpCamera->GetActive()) {\n    x88_interpCamera->SetActive(false);\n    x80_ballCamera->SkipFovInterpolation();\n    if (!ShouldBypassInterpolation())\n      SetCurrentCameraId(newCamId, mgr);\n  }\n}\n\nfloat CCameraManager::GetCameraBobMagnitude() const {\n  return 1.f - zeus::clamp(-1.f,\n                           std::fabs(zeus::clamp(-1.f, x7c_fpCamera->GetTransform().basis[1].dot(zeus::skUp), 1.f)) /\n                               std::cos(2.f * M_PIF / 12.f),\n                           1.f);\n}\n\nbool CCameraManager::HasBallCameraInitialPositionHint(CStateManager& mgr) const {\n  if (HasCameraHint(mgr)) {\n    switch (mgr.GetCameraManager()->GetCameraHint(mgr)->GetHint().GetBehaviourType()) {\n    case CBallCamera::EBallCameraBehaviour::HintBallToCam:\n    case CBallCamera::EBallCameraBehaviour::HintFixedPosition:\n    case CBallCamera::EBallCameraBehaviour::HintFixedTransform:\n    case CBallCamera::EBallCameraBehaviour::PathCamera:\n    case CBallCamera::EBallCameraBehaviour::SpindleCamera:\n      return true;\n    default:\n      return false;\n    }\n  }\n  return false;\n}\n\nvoid CCameraManager::RemoveCinemaCamera(TUniqueId uid, CStateManager& mgr) {\n  const auto search = std::find(x4_cineCameras.cbegin(), x4_cineCameras.cend(), uid);\n\n  if (search == x4_cineCameras.cend()) {\n    return;\n  }\n\n  x4_cineCameras.erase(search);\n}\n\nvoid CCameraManager::DeleteCameraHint(TUniqueId id, CStateManager& mgr) {\n  const TCastToPtr<CScriptCameraHint> hint = mgr.ObjectById(id);\n\n  if (!hint) {\n    return;\n  }\n\n  const auto search = std::find_if(x2b0_inactiveCameraHints.cbegin(), x2b0_inactiveCameraHints.cend(),\n                                   [id](TUniqueId tid) { return tid == id; });\n\n  if (search != x2b0_inactiveCameraHints.cend()) {\n    return;\n  }\n\n  hint->ClearIdList();\n  hint->SetInactive(true);\n  if (x2b0_inactiveCameraHints.size() != 64) {\n    x2b0_inactiveCameraHints.push_back(id);\n  }\n}\n\nvoid CCameraManager::AddInactiveCameraHint(TUniqueId id, CStateManager& mgr) {\n  if (const TCastToConstPtr<CScriptCameraHint> hint = mgr.ObjectById(id)) {\n    const auto search = std::find_if(x2b0_inactiveCameraHints.cbegin(), x2b0_inactiveCameraHints.cend(),\n                                     [id](TUniqueId tid) { return tid == id; });\n    if (search == x2b0_inactiveCameraHints.cend() && x2b0_inactiveCameraHints.size() != 64) {\n      x2b0_inactiveCameraHints.push_back(id);\n    }\n  }\n}\n\nvoid CCameraManager::AddActiveCameraHint(TUniqueId id, CStateManager& mgr) {\n  if (const TCastToConstPtr<CScriptCameraHint> hint = mgr.ObjectById(id)) {\n    const auto search = std::find_if(x334_activeCameraHints.cbegin(), x334_activeCameraHints.cend(),\n                                     [id](TUniqueId tid) { return tid == id; });\n    if (search == x334_activeCameraHints.cend() && xac_cameraHints.size() != 64 &&\n        x334_activeCameraHints.size() != 64) {\n      x334_activeCameraHints.push_back(id);\n    }\n  }\n}\n\nTUniqueId CCameraManager::GetLastCineCameraId() const {\n  if (x4_cineCameras.empty()) {\n    return kInvalidUniqueId;\n  }\n  return x4_cineCameras.back();\n}\n\nconst CCinematicCamera* CCameraManager::GetLastCineCamera(CStateManager& mgr) const {\n  return static_cast<const CCinematicCamera*>(mgr.GetObjectById(GetLastCineCameraId()));\n}\n\nconst CScriptCameraHint* CCameraManager::GetCameraHint(CStateManager& mgr) const {\n  return TCastToConstPtr<CScriptCameraHint>(mgr.GetObjectById(xa6_camHintId)).GetPtr();\n}\n\nbool CCameraManager::HasCameraHint(CStateManager& mgr) const {\n  if (xac_cameraHints.empty() || xa6_camHintId == kInvalidUniqueId)\n    return false;\n  return mgr.GetObjectById(xa6_camHintId) != nullptr;\n}\n\nbool CCameraManager::IsInterpolationCameraActive() const { return x88_interpCamera->GetActive(); }\n\nvoid CCameraManager::SetFogDensity(float fogDensityTarget, float fogDensitySpeed) {\n  x9c_fogDensityFactorTarget = fogDensityTarget;\n  x98_fogDensitySpeed = (x9c_fogDensityFactorTarget >= x94_fogDensityFactor ? fogDensitySpeed : -fogDensitySpeed);\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Camera/CCameraManager.hpp",
    "content": "#pragma once\n\n#include <list>\n#include <vector>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/World/CGameArea.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CBallCamera;\nclass CCameraShakeData;\nclass CCinematicCamera;\nclass CFirstPersonCamera;\nclass CGameCamera;\nclass CInterpolationCamera;\nclass CScriptCameraHint;\nclass CScriptWater;\nclass CStateManager;\n\nstruct CFinalInput;\n\nclass CCameraManager {\n  static float sFirstPersonFOV;\n  TUniqueId x0_curCameraId;\n  std::vector<TUniqueId> x4_cineCameras;\n  std::list<CCameraShakeData> x14_shakers;\n  u32 x2c_lastShakeId = 0;\n  zeus::CVector3f x30_shakeOffset;\n  CGameArea::CAreaFog x3c_fog;\n  int x74_fluidCounter = 0;\n  TUniqueId x78_fluidId = kInvalidUniqueId;\n  CFirstPersonCamera* x7c_fpCamera = nullptr;\n  CBallCamera* x80_ballCamera = nullptr;\n  s16 x84_rumbleId = -1;\n  CInterpolationCamera* x88_interpCamera = nullptr;\n  float x90_rumbleCooldown = 0.f;\n  float x94_fogDensityFactor = 1.f;\n  float x98_fogDensitySpeed = 0.f;\n  float x9c_fogDensityFactorTarget = 1.f;\n  bool xa0_24_pendingRumble : 1 = false;\n  bool xa0_25_rumbling : 1 = false;\n  bool xa0_26_inWater : 1 = false;\n  TUniqueId xa2_spindleCamId = kInvalidUniqueId;\n  TUniqueId xa4_pathCamId = kInvalidUniqueId;\n  TUniqueId xa6_camHintId = kInvalidUniqueId;\n  s32 xa8_hintPriority = 1000;\n  rstl::reserved_vector<std::pair<s32, TUniqueId>, 64> xac_cameraHints;\n  rstl::reserved_vector<TUniqueId, 64> x2b0_inactiveCameraHints;\n  rstl::reserved_vector<TUniqueId, 64> x334_activeCameraHints;\n  bool x3b8_24_ : 1 = false;\n  bool x3b8_25_ : 1 = false;\n  float x3bc_curFov = 60.f;\n\n  void SetPathCamera(TUniqueId id, CStateManager& mgr);\n  void SetSpindleCamera(TUniqueId id, CStateManager& mgr);\n  void RestoreHintlessCamera(CStateManager& mgr);\n  void InterpolateToBallCamera(const zeus::CTransform& xf, TUniqueId camId, const zeus::CVector3f& lookPos,\n                               float maxTime, float positionSpeed, float rotationSpeed, bool sinusoidal,\n                               CStateManager& mgr);\n  void SkipBallCameraCinematic(CStateManager& mgr);\n  void ApplyCameraHint(const CScriptCameraHint& hint, CStateManager& mgr);\n\n  void EnterCinematic(CStateManager& mgr);\n\npublic:\n  explicit CCameraManager(TUniqueId curCameraId = kInvalidUniqueId);\n\n  static float Aspect() { return 1.42f; }\n  static float FarPlane() { return 750.0f; }\n  static float NearPlane() { return 0.2f; }\n  static float FirstPersonFOV() { return sFirstPersonFOV; }\n  static float ThirdPersonFOV() { return 60.0f; }\n\n  void ResetCameras(CStateManager& mgr);\n  void SetSpecialCameras(CFirstPersonCamera& fp, CBallCamera& ball);\n  bool IsInCinematicCamera() const { return x4_cineCameras.size() != 0; }\n  bool IsInFirstPersonCamera() const;\n  zeus::CVector3f GetGlobalCameraTranslation(const CStateManager& stateMgr) const;\n  zeus::CTransform GetCurrentCameraTransform(const CStateManager& stateMgr) const;\n  void RemoveCameraShaker(u32 id);\n  int AddCameraShaker(const CCameraShakeData& data, bool sfx);\n  void AddCinemaCamera(TUniqueId id, CStateManager& stateMgr);\n  void RemoveCinemaCamera(TUniqueId uid, CStateManager& mgr);\n  void SetInsideFluid(bool isInside, TUniqueId fluidId);\n  void Update(float dt, CStateManager& stateMgr);\n  CGameCamera* GetCurrentCamera(CStateManager& stateMgr) const;\n  const CGameCamera* GetCurrentCamera(const CStateManager& stateMgr) const;\n  void SetCurrentCameraId(TUniqueId id, CStateManager& stateMgr) { x0_curCameraId = id; }\n  void CreateStandardCameras(CStateManager& stateMgr);\n  TUniqueId GetCurrentCameraId() const {\n    if (x4_cineCameras.size())\n      return x4_cineCameras.back();\n    return x0_curCameraId;\n  }\n  TUniqueId GetLastCameraId() const {\n    if (x4_cineCameras.size())\n      return x4_cineCameras.back();\n    return kInvalidUniqueId;\n  }\n\n  void SkipCinematic(CStateManager& stateMgr);\n\n  CFirstPersonCamera* GetFirstPersonCamera() { return x7c_fpCamera; }\n  const CFirstPersonCamera* GetFirstPersonCamera() const { return x7c_fpCamera; }\n\n  CBallCamera* GetBallCamera() { return x80_ballCamera; }\n  const CBallCamera* GetBallCamera() const { return x80_ballCamera; }\n\n  CGameArea::CAreaFog& Fog() { return x3c_fog; }\n  const CGameArea::CAreaFog& Fog() const { return x3c_fog; }\n\n  float GetCameraBobMagnitude() const;\n\n  void UpdateCameraHints(float dt, CStateManager& mgr);\n  void ThinkCameras(float dt, CStateManager& mgr);\n  void UpdateFog(float dt, CStateManager& mgr);\n  void UpdateRumble(float dt, CStateManager& mgr);\n  void UpdateListener(CStateManager& mgr);\n\n  float CalculateFogDensity(CStateManager& mgr, const CScriptWater* water) const;\n  void SetFogDensity(float fogDensityTarget, float fogDensitySpeed);\n\n  void ProcessInput(const CFinalInput& input, CStateManager& stateMgr);\n\n  void RenderCameras(CStateManager& mgr);\n  void SetupBallCamera(CStateManager& mgr);\n  void SetPlayerCamera(CStateManager& mgr, TUniqueId newCamId);\n  int GetFluidCounter() const { return x74_fluidCounter; }\n  bool HasBallCameraInitialPositionHint(CStateManager& mgr) const;\n\n  void DeleteCameraHint(TUniqueId id, CStateManager& mgr);\n  void AddInactiveCameraHint(TUniqueId id, CStateManager& mgr);\n  void AddActiveCameraHint(TUniqueId id, CStateManager& mgr);\n\n  TUniqueId GetLastCineCameraId() const;\n  TUniqueId GetSpindleCameraId() const { return xa2_spindleCamId; }\n  TUniqueId GetPathCameraId() const { return xa4_pathCamId; }\n  const CCinematicCamera* GetLastCineCamera(CStateManager& mgr) const;\n  const CScriptCameraHint* GetCameraHint(CStateManager& mgr) const;\n  bool HasCameraHint(CStateManager& mgr) const;\n  bool IsInterpolationCameraActive() const;\n\n  bool ShouldBypassInterpolation() { return false; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Camera/CCameraShakeData.cpp",
    "content": "#include \"Runtime/Camera/CCameraShakeData.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/ScriptLoader.hpp\"\n\n#include <algorithm>\n#include <cmath>\n\n#include <zeus/Math.hpp>\n\nnamespace metaforce {\n\nSCameraShakePoint SCameraShakePoint::LoadCameraShakePoint(CInputStream& in) {\n  u32 useEnvelope = ScriptLoader::LoadParameterFlags(in);\n  float attackTime = in.ReadFloat();\n  float sustainTime = in.ReadFloat();\n  float duration = in.ReadFloat();\n  float magnitude = in.ReadFloat();\n  return {useEnvelope != 0, attackTime, sustainTime, duration, magnitude};\n}\n\nCCameraShakerComponent CCameraShakerComponent::LoadNewCameraShakerComponent(CInputStream& in) {\n  u32 useModulation = ScriptLoader::LoadParameterFlags(in);\n  SCameraShakePoint am = SCameraShakePoint::LoadCameraShakePoint(in);\n  SCameraShakePoint fm = SCameraShakePoint::LoadCameraShakePoint(in);\n  return {useModulation != 0, am, fm};\n}\n\nCCameraShakeData::CCameraShakeData(CInputStream& in) {\n  in.ReadLong();\n  in.ReadFloat();\n  in.ReadFloat();\n  in.ReadFloat();\n  in.ReadFloat();\n  in.ReadFloat();\n  in.ReadFloat();\n  in.ReadFloat();\n  in.ReadBool();\n  BuildProjectileCameraShake(0.5f, 0.75f);\n}\n\nvoid SCameraShakePoint::Update(float curTime) {\n  float offTimePoint = xc_attackTime + x10_sustainTime;\n  float factor = 1.f;\n  if (curTime < xc_attackTime && xc_attackTime > 0.f)\n    factor = zeus::clamp(0.f, curTime / xc_attackTime, 1.f);\n  if (curTime >= offTimePoint && x14_duration > 0.f)\n    factor = 1.f - (curTime - offTimePoint) / x14_duration;\n  x4_value = x8_magnitude * factor;\n}\n\nvoid CCameraShakerComponent::Update(float curTime, float duration, float distAtt) {\n  if (std::fabs(duration) < 0.00001f || !x4_useModulation) {\n    x38_value = 0.f;\n    return;\n  }\n\n  x20_fm.Update(curTime);\n  float freq = 1.f + x20_fm.GetValue();\n  x8_am.Update(curTime);\n  x38_value = x8_am.GetValue() * std::sin(2.f * M_PIF * (duration - curTime) * freq);\n  x38_value *= distAtt;\n}\n\nvoid CCameraShakeData::Update(float dt, CStateManager& mgr) {\n  x4_curTime += dt;\n  float distAtt = 1.f;\n  if (xc0_flags & 0x1)\n    distAtt = 1.f - zeus::clamp(0.f, (xc4_sfxPos - mgr.GetPlayer().GetTranslation()).magnitude() / xd0_sfxDist, 1.f);\n  x8_shakerX.Update(x4_curTime, x0_duration, distAtt);\n  x44_shakerY.Update(x4_curTime, x0_duration, distAtt);\n  x80_shakerZ.Update(x4_curTime, x0_duration, distAtt);\n}\n\nzeus::CVector3f CCameraShakeData::GetPoint() const {\n  return {x8_shakerX.GetValue(), x44_shakerY.GetValue(), x80_shakerZ.GetValue()};\n}\n\nfloat CCameraShakeData::GetMaxAMComponent() const {\n  float ret = 0.f;\n  if (x8_shakerX.x4_useModulation)\n    ret = x8_shakerX.x8_am.GetValue();\n  if (x44_shakerY.x4_useModulation)\n    ret = std::max(ret, x44_shakerY.x8_am.GetValue());\n  if (x80_shakerZ.x4_useModulation)\n    ret = std::max(ret, x80_shakerZ.x8_am.GetValue());\n  return ret;\n}\n\nfloat CCameraShakeData::GetMaxFMComponent() const {\n  float ret = 0.f;\n  if (x8_shakerX.x4_useModulation)\n    ret = x8_shakerX.x20_fm.GetValue();\n  if (x44_shakerY.x4_useModulation)\n    ret = std::max(ret, x44_shakerY.x20_fm.GetValue());\n  if (x80_shakerZ.x4_useModulation)\n    ret = std::max(ret, x80_shakerZ.x20_fm.GetValue());\n  return ret;\n}\n\nCCameraShakeData CCameraShakeData::LoadCameraShakeData(CInputStream& in) {\n  const float xMag = in.ReadFloat();\n  in.ReadFloat();\n  const float yMag = in.ReadFloat();\n  in.ReadFloat();\n  const float zMag = in.ReadFloat();\n  in.ReadFloat();\n  const float duration = in.ReadFloat();\n\n  const SCameraShakePoint xAM(false, 0.f, 0.f, duration, 2.f * xMag);\n  const SCameraShakePoint yAM(false, 0.f, 0.f, duration, 2.f * yMag);\n  const SCameraShakePoint zAM(false, 0.f, 0.f, duration, 2.f * zMag);\n  const SCameraShakePoint xFM(false, 0.f, 0.f, 0.5f * duration, 3.f);\n  const SCameraShakePoint yFM(false, 0.f, 0.f, 0.5f * duration, 0.f);\n  const SCameraShakePoint zFM(false, 0.f, 0.f, 0.5f * duration, 3.f);\n\n  const CCameraShakerComponent shakerX(true, xAM, xFM);\n  const CCameraShakerComponent shakerY;\n  const CCameraShakerComponent shakerZ(true, zAM, zFM);\n\n  return {duration, 100.f, 0, zeus::skZero3f, shakerX, shakerY, shakerZ};\n}\n\nconst CCameraShakeData CCameraShakeData::skChargedShotCameraShakeData{\n    0.3f,\n    100.f,\n    0,\n    zeus::skZero3f,\n    CCameraShakerComponent{},\n    CCameraShakerComponent{\n        true,\n        {false, 0.f, 0.f, 0.3f, -1.f},\n        {true, 0.f, 0.f, 0.05f, 0.3f},\n    },\n    CCameraShakerComponent{},\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Camera/CCameraShakeData.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CRandom16;\nclass CStateManager;\n\nstruct SCameraShakePoint {\n  friend class CCameraShakeData;\n  bool x0_useEnvelope = false;\n  float x4_value = 0.f;\n  float x8_magnitude = 0.f;\n  float xc_attackTime = 0.f;\n  float x10_sustainTime = 0.f;\n  float x14_duration = 0.f;\n  constexpr SCameraShakePoint() noexcept = default;\n  constexpr SCameraShakePoint(bool useEnvelope, float attackTime, float sustainTime, float duration,\n                              float magnitude) noexcept\n  : x0_useEnvelope(useEnvelope)\n  , x8_magnitude(magnitude)\n  , xc_attackTime(attackTime)\n  , x10_sustainTime(sustainTime)\n  , x14_duration(duration) {}\n  [[nodiscard]] constexpr float GetValue() const noexcept { return x0_useEnvelope ? x8_magnitude : x4_value; }\n  static SCameraShakePoint LoadCameraShakePoint(CInputStream& in);\n  void Update(float curTime);\n};\n\nclass CCameraShakerComponent {\n  friend class CCameraShakeData;\n  bool x4_useModulation = false;\n  SCameraShakePoint x8_am, x20_fm;\n  float x38_value = 0.f;\n\npublic:\n  constexpr CCameraShakerComponent() noexcept = default;\n  constexpr CCameraShakerComponent(bool useModulation, const SCameraShakePoint& am,\n                                   const SCameraShakePoint& fm) noexcept\n  : x4_useModulation(useModulation), x8_am(am), x20_fm(fm) {}\n  static CCameraShakerComponent LoadNewCameraShakerComponent(CInputStream& in);\n  void Update(float curTime, float duration, float distAtt);\n  [[nodiscard]] constexpr float GetValue() const noexcept { return x38_value; }\n};\n\nclass CCameraShakeData {\n  friend class CCameraManager;\n  float x0_duration;\n  float x4_curTime = 0.f;\n  CCameraShakerComponent x8_shakerX;\n  CCameraShakerComponent x44_shakerY;\n  CCameraShakerComponent x80_shakerZ;\n  u32 xbc_shakerId = 0;\n  u32 xc0_flags; // 0x1: positional sfx\n  zeus::CVector3f xc4_sfxPos;\n  float xd0_sfxDist;\n\npublic:\n  static const CCameraShakeData skChargedShotCameraShakeData;\n\n  constexpr CCameraShakeData(float duration, float sfxDist, u32 flags, const zeus::CVector3f& sfxPos,\n                             const CCameraShakerComponent& shaker1, const CCameraShakerComponent& shaker2,\n                             const CCameraShakerComponent& shaker3) noexcept\n  : x0_duration(duration)\n  , x8_shakerX(shaker1)\n  , x44_shakerY(shaker2)\n  , x80_shakerZ(shaker3)\n  , xc0_flags(flags)\n  , xc4_sfxPos(sfxPos)\n  , xd0_sfxDist(sfxDist) {}\n\n  constexpr CCameraShakeData(float duration, float magnitude) noexcept\n  : CCameraShakeData(\n        duration, 100.f, 0, zeus::skZero3f, CCameraShakerComponent{}, CCameraShakerComponent{},\n        CCameraShakerComponent{true, SCameraShakePoint{false, 0.25f * duration, 0.f, 0.75f * duration, magnitude},\n                               SCameraShakePoint{true, 0.f, 0.f, 0.5f * duration, 2.f}}) {}\n\n  explicit CCameraShakeData(CInputStream&);\n\n  static constexpr CCameraShakeData BuildLandingCameraShakeData(float duration, float magnitude) noexcept {\n    return {\n        duration,\n        100.f,\n        0,\n        zeus::skZero3f,\n        CCameraShakerComponent{\n            true,\n            SCameraShakePoint(false, 0.15f * duration, 0.f, 0.85f * duration, magnitude),\n            SCameraShakePoint(true, 0.f, 0.f, 0.4f * duration, 1.5f),\n        },\n        CCameraShakerComponent{},\n        CCameraShakerComponent{\n            true,\n            SCameraShakePoint(false, 0.25f * duration, 0.f, 0.75f * duration, magnitude),\n            SCameraShakePoint(true, 0.f, 0.f, 0.5f * duration, 2.f),\n        },\n    };\n  }\n\n  static constexpr CCameraShakeData BuildProjectileCameraShake(float duration, float magnitude) noexcept {\n    return {\n        duration,\n        100.f,\n        0,\n        zeus::skZero3f,\n        CCameraShakerComponent{\n            true,\n            SCameraShakePoint(false, 0.f, 0.f, duration, magnitude),\n            SCameraShakePoint(true, 0.f, 0.f, 0.5f * duration, 3.f),\n        },\n        CCameraShakerComponent{},\n        CCameraShakerComponent{},\n    };\n  }\n\n  static constexpr CCameraShakeData BuildMissileCameraShake(float duration, float magnitude, float sfxDistance,\n                                                            const zeus::CVector3f& sfxPos) noexcept {\n    CCameraShakeData ret(duration, magnitude);\n    ret.SetSfxPositionAndDistance(sfxPos, sfxDistance);\n    return ret;\n  }\n\n  static constexpr CCameraShakeData BuildPhazonCameraShakeData(float duration, float magnitude) noexcept {\n    return {\n        duration,\n        100.f,\n        0,\n        zeus::skZero3f,\n        CCameraShakerComponent{\n            true,\n            SCameraShakePoint(false, 0.15f * duration, 0.f, 0.25f * duration, magnitude),\n            SCameraShakePoint(true, 0.f, 0.f, 0.4f * duration, 0.3f),\n        },\n        CCameraShakerComponent{},\n        CCameraShakerComponent{\n            true,\n            SCameraShakePoint(false, 0.25f * duration, 0.f, 0.25f * duration, magnitude),\n            SCameraShakePoint(true, 0.f, 0.f, 0.5f * duration, 0.5f),\n        },\n    };\n  }\n\n  static constexpr CCameraShakeData BuildPatternedExplodeShakeData(float duration, float magnitude) noexcept {\n    return {\n        duration,\n        100.f,\n        0,\n        zeus::skZero3f,\n        CCameraShakerComponent{\n            true,\n            SCameraShakePoint(false, 0.25f * duration, 0.f, 0.75f * duration, magnitude),\n            SCameraShakePoint(true, 0.f, 0.f, 0.5f * duration, 2.0f),\n        },\n        CCameraShakerComponent{},\n        CCameraShakerComponent{},\n    };\n  }\n\n  static constexpr CCameraShakeData BuildPatternedExplodeShakeData(const zeus::CVector3f& pos, float duration,\n                                                                   float magnitude, float distance) noexcept {\n    CCameraShakeData shakeData = BuildPatternedExplodeShakeData(duration, magnitude);\n    shakeData.SetSfxPositionAndDistance(pos, distance);\n    return shakeData;\n  }\n\n  void Update(float dt, CStateManager& mgr);\n  zeus::CVector3f GetPoint() const;\n  float GetMaxAMComponent() const;\n  float GetMaxFMComponent() const;\n  void SetShakerId(u32 id) { xbc_shakerId = id; }\n  u32 GetShakerId() const { return xbc_shakerId; }\n  static CCameraShakeData LoadCameraShakeData(CInputStream& in);\n  constexpr void SetSfxPositionAndDistance(const zeus::CVector3f& pos, float sfxDistance) noexcept {\n    xc0_flags |= 0x1;\n    xc4_sfxPos = pos;\n    xd0_sfxDist = sfxDistance;\n  }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Camera/CCameraSpline.cpp",
    "content": "#include \"Runtime/Camera/CCameraSpline.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CScriptCameraWaypoint.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nCCameraSpline::CCameraSpline(bool closedLoop) : x48_closedLoop(closedLoop) {}\n\nvoid CCameraSpline::CalculateKnots(TUniqueId cameraId, const std::vector<SConnection>& connections,\n                                   CStateManager& mgr) {\n  const SConnection* lastConn = nullptr;\n\n  for (const SConnection& conn : connections) {\n    if (conn.x0_state == EScriptObjectState::CameraPath && conn.x4_msg == EScriptObjectMessage::Follow) {\n      lastConn = &conn;\n    }\n  }\n\n  if (lastConn != nullptr) {\n    TCastToConstPtr<CScriptCameraWaypoint> waypoint = mgr.ObjectById(mgr.GetIdForScript(lastConn->x8_objId));\n    x14_wpTracker.clear();\n    x14_wpTracker.reserve(4);\n    while (waypoint) {\n      const auto search = std::find_if(x14_wpTracker.cbegin(), x14_wpTracker.cend(),\n                                       [&waypoint](const auto& a) { return a == waypoint->GetUniqueId(); });\n      if (search == x14_wpTracker.cend()) {\n        x14_wpTracker.push_back(waypoint->GetUniqueId());\n        waypoint = mgr.ObjectById(waypoint->GetRandomNextWaypointId(mgr));\n      }\n    }\n    Reset(x14_wpTracker.size());\n    x14_wpTracker.clear();\n\n    waypoint = mgr.ObjectById(mgr.GetIdForScript(lastConn->x8_objId));\n    while (waypoint) {\n      const auto search = std::find_if(x14_wpTracker.cbegin(), x14_wpTracker.cend(),\n                                       [&waypoint](const auto& a) { return a == waypoint->GetUniqueId(); });\n      if (search == x14_wpTracker.cend()) {\n        x14_wpTracker.push_back(waypoint->GetUniqueId());\n        AddKnot(waypoint->GetTranslation(), waypoint->GetTransform().basis[1]);\n        waypoint = mgr.ObjectById(waypoint->GetRandomNextWaypointId(mgr));\n      }\n    }\n  }\n}\n\nvoid CCameraSpline::Initialize(TUniqueId cameraId, const std::vector<SConnection>& connections, CStateManager& mgr) {\n  CalculateKnots(cameraId, connections, mgr);\n  x44_length = CalculateSplineLength();\n}\n\nvoid CCameraSpline::Reset(size_t size) {\n  x4_positions.clear();\n  x24_t.clear();\n  x34_directions.clear();\n\n  if (size == 0) {\n    return;\n  }\n\n  x4_positions.reserve(size);\n  x24_t.reserve(size);\n  x34_directions.reserve(size);\n}\n\nvoid CCameraSpline::AddKnot(const zeus::CVector3f& pos, const zeus::CVector3f& dir) {\n  x4_positions.push_back(pos);\n  x34_directions.push_back(dir);\n}\n\nvoid CCameraSpline::SetKnotPosition(size_t idx, const zeus::CVector3f& pos) {\n  if (idx >= x4_positions.size()) {\n    return;\n  }\n  x4_positions[idx] = pos;\n}\n\nconst zeus::CVector3f& CCameraSpline::GetKnotPosition(size_t idx) const {\n  if (idx >= x4_positions.size()) {\n    return zeus::skZero3f;\n  }\n  return x4_positions[idx];\n}\n\nfloat CCameraSpline::GetKnotT(size_t idx) const {\n  if (idx >= x4_positions.size()) {\n    return 0.f;\n  }\n  return x24_t[idx];\n}\n\nfloat CCameraSpline::CalculateSplineLength() {\n  float ret = 0.f;\n  x24_t.clear();\n  if (!x4_positions.empty()) {\n    zeus::CVector3f prevPoint = x4_positions[0];\n    float tDiv = 1.f / float(x4_positions.size() - 1);\n    for (size_t i = 0; i < x4_positions.size(); ++i) {\n      float subT = 0.f;\n      float baseT = i * tDiv;\n      x24_t.push_back(ret);\n      while (subT <= tDiv) {\n        subT += tDiv * 0.03125f;\n        zeus::CVector3f nextPoint = GetInterpolatedSplinePointByTime(baseT + subT, 1.f);\n        zeus::CVector3f delta = nextPoint - prevPoint;\n        if (delta.canBeNormalized()) {\n          prevPoint = nextPoint;\n          ret += delta.magnitude();\n        }\n      }\n    }\n\n    x24_t.push_back(ret);\n    if (x48_closedLoop) {\n      zeus::CVector3f delta = x4_positions[0] - x4_positions[x4_positions.size() - 1];\n      if (delta.canBeNormalized())\n        ret += delta.magnitude();\n    }\n\n    return ret;\n  }\n  return 0.f;\n}\n\nbool CCameraSpline::GetSurroundingPoints(size_t idx, rstl::reserved_vector<zeus::CVector3f, 4>& positions,\n                                         rstl::reserved_vector<zeus::CVector3f, 4>& directions) const {\n  if (x4_positions.size() <= 3 || idx < 0 || idx >= x4_positions.size()) {\n    return false;\n  }\n\n  if (idx > 0) {\n    positions.push_back(x4_positions[idx - 1]);\n    directions.push_back(x34_directions[idx - 1]);\n  } else if (x48_closedLoop) {\n    positions.push_back(x4_positions[x4_positions.size() - 1]);\n    directions.push_back(x34_directions[x4_positions.size() - 1]);\n  } else {\n    positions.push_back(x4_positions[0] - (x4_positions[1] - x4_positions[0]));\n    directions.push_back(x34_directions[0]);\n  }\n\n  positions.push_back(x4_positions[idx]);\n  directions.push_back(x34_directions[idx]);\n\n  if (idx + 1 >= x4_positions.size()) {\n    if (x48_closedLoop) {\n      positions.push_back(x4_positions[idx - x4_positions.size()]);\n      directions.push_back(x34_directions[idx - x4_positions.size()]);\n    } else {\n      positions.push_back(x4_positions[x4_positions.size() - 1] -\n                          (x4_positions[x4_positions.size() - 2] - x4_positions[x4_positions.size() - 1]));\n      directions.push_back(x34_directions[x4_positions.size() - 1]);\n    }\n  } else {\n    positions.push_back(x4_positions[idx + 1]);\n    directions.push_back(x34_directions[idx + 1]);\n  }\n\n  if (idx + 2 >= x4_positions.size()) {\n    if (x48_closedLoop) {\n      positions.push_back(x4_positions[idx + 2 - x4_positions.size()]);\n      directions.push_back(x34_directions[idx + 2 - x4_positions.size()]);\n    } else {\n      positions.push_back(x4_positions[x4_positions.size() - 1] -\n                          (x4_positions[x4_positions.size() - 2] - x4_positions[x4_positions.size() - 1]));\n      directions.push_back(x34_directions[x4_positions.size() - 1]);\n    }\n  } else {\n    positions.push_back(x4_positions[idx + 2]);\n    directions.push_back(x34_directions[idx + 2]);\n  }\n\n  return true;\n}\n\nzeus::CTransform CCameraSpline::GetInterpolatedSplinePointByLength(float pos) const {\n  if (x4_positions.empty())\n    return zeus::CTransform();\n\n  size_t baseIdx = 0;\n  size_t i;\n  for (i = 1; i < x4_positions.size(); ++i) {\n    if (x24_t[i] > pos) {\n      baseIdx = i - 1;\n      break;\n    }\n  }\n\n  if (i == x4_positions.size())\n    baseIdx = i - 1;\n\n  if (pos < 0.f)\n    baseIdx = 0;\n\n  if (pos >= x44_length) {\n    if (x48_closedLoop) {\n      pos -= x44_length;\n      baseIdx = 0;\n    } else {\n      baseIdx = x4_positions.size() - 2;\n      pos = x44_length;\n    }\n  }\n\n  float range;\n  if (baseIdx == x4_positions.size() - 1) {\n    if (x48_closedLoop)\n      range = x44_length - x24_t[baseIdx];\n    else\n      range = x44_length - x24_t[x4_positions.size() - 2];\n  } else {\n    range = x24_t[baseIdx + 1] - x24_t[baseIdx];\n  }\n\n  float t = zeus::clamp(0.f, (pos - x24_t[baseIdx]) / range, 1.f);\n\n  rstl::reserved_vector<zeus::CVector3f, 4> positions;\n  rstl::reserved_vector<zeus::CVector3f, 4> directions;\n  if (GetSurroundingPoints(baseIdx, positions, directions)) {\n    float f1 = zeus::clamp(-1.f, directions[1].dot(directions[2]), 1.f);\n    if (f1 >= 1.f) {\n      zeus::CTransform ret = zeus::lookAt(zeus::skZero3f, directions[2]);\n      ret.origin = zeus::getCatmullRomSplinePoint(positions[0], positions[1], positions[2], positions[3], t);\n      return ret;\n    } else {\n      zeus::CTransform ret = zeus::lookAt(\n          zeus::skZero3f,\n          zeus::CQuaternion::lookAt(directions[1], directions[2], std::acos(f1) * t).transform(directions[1]));\n      ret.origin = zeus::getCatmullRomSplinePoint(positions[0], positions[1], positions[2], positions[3], t);\n      return ret;\n    }\n  }\n\n  return zeus::CTransform();\n}\n\nzeus::CVector3f CCameraSpline::GetInterpolatedSplinePointByTime(float time, float range) const {\n  if (x4_positions.empty())\n    return {};\n\n  rstl::reserved_vector<zeus::CVector3f, 4> positions;\n  rstl::reserved_vector<zeus::CVector3f, 4> directions;\n  float rangeFac = range / float(x4_positions.size() - 1);\n  int baseIdx = std::min(int(x4_positions.size() - 1), int(time / rangeFac));\n  if (GetSurroundingPoints(baseIdx, positions, directions))\n    return zeus::getCatmullRomSplinePoint(positions[0], positions[1], positions[2], positions[3],\n                                          (time - float(baseIdx) * rangeFac) / rangeFac);\n\n  return {};\n}\n\nfloat CCameraSpline::FindClosestLengthOnSpline(float time, const zeus::CVector3f& p) const {\n  float ret = -1.f;\n  float minLenDelta = 10000.f;\n  float minMag = 10000.f;\n\n  size_t iterations = x4_positions.size() - 1;\n  if (x48_closedLoop)\n    iterations += 1;\n\n  for (size_t i = 0; i < iterations; ++i) {\n    const zeus::CVector3f& thisPos = x4_positions[i];\n    const zeus::CVector3f* nextPos;\n    if (!x48_closedLoop) {\n      nextPos = &x4_positions[i + 1];\n    } else {\n      if (i == x4_positions.size() - 1)\n        nextPos = &x4_positions[0];\n      else\n        nextPos = &x4_positions[i + 1];\n    }\n\n    zeus::CVector3f delta = *nextPos - thisPos;\n    zeus::CVector3f nextDelta;\n    zeus::CVector3f revDelta = thisPos - *nextPos;\n    zeus::CVector3f nextRevDelta;\n\n    if (i != 0) {\n      nextDelta = delta + thisPos - x4_positions[i - 1];\n    } else {\n      zeus::CVector3f extrap = x4_positions[0] - x4_positions[1] + x4_positions[0];\n      if (x48_closedLoop)\n        extrap = x4_positions.back();\n      nextDelta = delta + thisPos - extrap;\n    }\n    nextDelta.normalize();\n\n    if (i < x4_positions.size() - 2) {\n      nextRevDelta = revDelta + *nextPos - x4_positions[i + 2];\n    } else {\n      zeus::CVector3f extrap;\n      if (x48_closedLoop) {\n        if (i == iterations - 1)\n          extrap = x4_positions[1];\n        else\n          extrap = x4_positions[0];\n      } else {\n        extrap = x4_positions[i + 1] - x4_positions[i] + x4_positions[i + 1];\n      }\n      nextRevDelta = revDelta + *nextPos - extrap;\n    }\n    nextRevDelta.normalize();\n\n    nextDelta.normalize();\n    nextRevDelta.normalize();\n    zeus::CVector3f ptToPlayer = p - thisPos;\n    float proj = ptToPlayer.dot(nextDelta) / nextDelta.dot(delta.normalized());\n    zeus::CVector3f nextPtToPlayer = p - *nextPos;\n    float nextProj = nextPtToPlayer.dot(nextRevDelta) / nextRevDelta.dot(revDelta.normalized());\n    float t = proj / (proj + nextProj);\n\n    if (!x48_closedLoop) {\n      if (i == 0 && t < 0.f)\n        t = 0.f;\n      if (i == x4_positions.size() - 2 && t > 1.f)\n        t = 1.f;\n    }\n\n    if (t >= 0.f && t <= 1.f) {\n      float tLen;\n      if (i == x4_positions.size() - 1)\n        tLen = x44_length - x24_t[i];\n      else\n        tLen = x24_t[i + 1] - x24_t[i];\n\n      float lenT = t * tLen + x24_t[i];\n      zeus::CVector3f pointDelta = p - GetInterpolatedSplinePointByLength(lenT).origin;\n      float mag = 0.f;\n      if (pointDelta.canBeNormalized())\n        mag = pointDelta.magnitude();\n      float lenDelta = std::fabs(lenT - time);\n      if (x48_closedLoop && lenDelta > x44_length - lenDelta)\n        lenDelta = x44_length - lenDelta;\n      if (zeus::close_enough(std::fabs(mag - minMag), 0.f)) {\n        if (lenDelta < minLenDelta) {\n          ret = lenT;\n          minLenDelta = lenDelta;\n        }\n      } else {\n        if (mag < minMag) {\n          ret = lenT;\n          minLenDelta = lenDelta;\n          minMag = mag;\n        }\n      }\n    }\n  }\n\n  return std::max(ret, 0.f);\n}\n\nfloat CCameraSpline::ValidateLength(float t) const {\n  if (x48_closedLoop) {\n    while (t >= x44_length)\n      t -= x44_length;\n    while (t < 0.f)\n      t += x44_length;\n    return t;\n  } else {\n    return zeus::clamp(0.f, t, x44_length);\n  }\n}\n\nfloat CCameraSpline::ClampLength(const zeus::CVector3f& pos, bool collide, const CMaterialFilter& filter,\n                                 const CStateManager& mgr) const {\n  if (x4_positions.empty())\n    return 0.f;\n\n  if (x48_closedLoop)\n    return 0.f;\n\n  zeus::CVector3f deltaA = pos - x4_positions.front();\n  zeus::CVector3f deltaB = pos - x4_positions.back();\n  float magA = deltaA.magnitude();\n  float magB = deltaB.magnitude();\n  if (!deltaA.canBeNormalized())\n    return 0.f;\n  if (!deltaB.canBeNormalized())\n    return x44_length;\n\n  if (collide) {\n    bool collideA = mgr.RayStaticIntersection(x4_positions.front(), deltaA.normalized(), magA, filter).IsValid();\n    bool collideB = mgr.RayStaticIntersection(x4_positions.back(), deltaB.normalized(), magB, filter).IsValid();\n    if (collideA)\n      return x44_length;\n    if (collideB)\n      return 0.f;\n  }\n\n  if (magA < magB)\n    return 0.f;\n  else\n    return x44_length;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Camera/CCameraSpline.hpp",
    "content": "#pragma once\n\n#include \"World/CEntityInfo.hpp\"\n#include \"zeus/CVector3f.hpp\"\n\nnamespace metaforce {\nclass CStateManager;\nclass CMaterialFilter;\n\nclass CCameraSpline {\n  friend class CBallCamera;\n  std::vector<zeus::CVector3f> x4_positions;\n  std::vector<TUniqueId> x14_wpTracker;\n  std::vector<float> x24_t;\n  std::vector<zeus::CVector3f> x34_directions;\n  float x44_length = 0.f;\n  bool x48_closedLoop = false;\n  bool GetSurroundingPoints(size_t idx, rstl::reserved_vector<zeus::CVector3f, 4>& positions,\n                            rstl::reserved_vector<zeus::CVector3f, 4>& directions) const;\n\npublic:\n  explicit CCameraSpline(bool closedLoop);\n  void CalculateKnots(TUniqueId cameraId, const std::vector<SConnection>& connections, CStateManager& mgr);\n  void Initialize(TUniqueId cameraId, const std::vector<SConnection>& connections, CStateManager& mgr);\n  void Reset(size_t size);\n  void AddKnot(const zeus::CVector3f& pos, const zeus::CVector3f& dir);\n  void SetKnotPosition(size_t idx, const zeus::CVector3f& pos);\n  const zeus::CVector3f& GetKnotPosition(size_t idx) const;\n  float GetKnotT(size_t idx) const;\n  float CalculateSplineLength();\n  void UpdateSplineLength() { x44_length = CalculateSplineLength(); }\n  zeus::CTransform GetInterpolatedSplinePointByLength(float pos) const;\n  zeus::CVector3f GetInterpolatedSplinePointByTime(float time, float range) const;\n  float FindClosestLengthOnSpline(float time, const zeus::CVector3f& p) const;\n  float ValidateLength(float t) const;\n  float ClampLength(const zeus::CVector3f& pos, bool collide, const CMaterialFilter& filter,\n                    const CStateManager& mgr) const;\n  s32 GetSize() const { return x4_positions.size(); }\n  float GetLength() const { return x44_length; }\n  bool IsClosedLoop() const { return x48_closedLoop; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Camera/CCinematicCamera.cpp",
    "content": "#include \"Runtime/Camera/CCinematicCamera.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Character/CAnimTreeNode.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CScriptActor.hpp\"\n#include \"Runtime/World/CScriptCameraWaypoint.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCCinematicCamera::CCinematicCamera(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                                   const zeus::CTransform& xf, bool active, float shotDuration, float fovy, float znear,\n                                   float zfar, float aspect, u32 flags)\n: CGameCamera(uid, active, name, info, xf, fovy, znear, zfar, aspect, kInvalidUniqueId, (flags & 0x20) != 0, 0)\n, x1e8_duration(shotDuration)\n, x1f0_origFovy(fovy)\n, x1fc_origOrientation(zeus::CQuaternion(xf.basis))\n, x21c_flags(flags) {\n  x220_24_ = false;\n}\n\nvoid CCinematicCamera::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CCinematicCamera::ProcessInput(const CFinalInput&, CStateManager& mgr) {\n  // Empty\n}\n\nvoid CCinematicCamera::Reset(const zeus::CTransform&, CStateManager& mgr) {\n  // Empty\n}\n\nvoid CCinematicCamera::WasDeactivated(CStateManager& mgr) {\n  mgr.GetCameraManager()->RemoveCinemaCamera(GetUniqueId(), mgr);\n  mgr.GetPlayer().GetMorphBall()->LoadMorphBallModel(mgr);\n  if ((x21c_flags & 0x100) != 0) {\n    mgr.SetCinematicPause(false);\n  }\n  x188_viewPoints.clear();\n  x198_viewOrientations.clear();\n  x1a8_viewPointArrivals.clear();\n  x1b8_targets.clear();\n  x1c8_targetArrivals.clear();\n  x1d8_viewHFovs.clear();\n}\n\nzeus::CVector3f CCinematicCamera::GetInterpolatedSplinePoint(const std::vector<zeus::CVector3f>& points, int& idxOut,\n                                                             float tin) const {\n  if (points.empty()) {\n    return {};\n  }\n\n  const float cycleT = std::fmod(tin, x1e8_duration);\n  const float durPerPoint = x1e8_duration / float(points.size() - 1);\n  idxOut = int(cycleT / durPerPoint);\n  const float t = (cycleT - float(idxOut) * durPerPoint) / durPerPoint;\n\n  if (points.size() == 1) {\n    return points.front();\n  }\n  if (points.size() == 2) {\n    return (points[1] - points[0]) * t + points[0];\n  }\n\n  zeus::CVector3f ptA;\n  if (idxOut > 0) {\n    ptA = points[idxOut - 1];\n  } else {\n    ptA = points[0] - (points[1] - points[0]);\n  }\n\n  const zeus::CVector3f ptB = points[idxOut];\n  zeus::CVector3f ptC;\n  if (size_t(idxOut + 1) >= points.size()) {\n    const zeus::CVector3f& tmpA = points[points.size() - 1];\n    const zeus::CVector3f& tmpB = points[points.size() - 2];\n    ptC = tmpA - (tmpB - tmpA);\n  } else {\n    ptC = points[idxOut + 1];\n  }\n\n  zeus::CVector3f ptD;\n  if (size_t(idxOut + 2) >= points.size()) {\n    const zeus::CVector3f& tmpA = points[points.size() - 1];\n    const zeus::CVector3f& tmpB = points[points.size() - 2];\n    ptD = tmpA - (tmpB - tmpA);\n  } else {\n    ptD = points[idxOut + 2];\n  }\n\n  return zeus::getCatmullRomSplinePoint(ptA, ptB, ptC, ptD, t);\n}\n\nzeus::CQuaternion CCinematicCamera::GetInterpolatedOrientation(const std::vector<zeus::CQuaternion>& rotations,\n                                                               float tin) const {\n  if (rotations.empty()) {\n    return x1fc_origOrientation;\n  }\n\n  if (rotations.size() == 1) {\n    return rotations.front();\n  }\n\n  const float cycleT = std::fmod(tin, x1e8_duration);\n  const float durPerPoint = x1e8_duration / float(rotations.size() - 1);\n  const int idx = int(cycleT / durPerPoint);\n  const float t = (cycleT - float(idx) * durPerPoint) / durPerPoint;\n  return zeus::CQuaternion::slerp(rotations[idx], rotations[idx + 1], t);\n}\n\nfloat CCinematicCamera::GetInterpolatedHFov(const std::vector<float>& fovs, float tin) const {\n  if (fovs.empty()) {\n    return x1f0_origFovy;\n  }\n\n  if (fovs.size() == 1) {\n    return fovs.front();\n  }\n\n  const float cycleT = std::fmod(tin, x1e8_duration);\n  const float durPerPoint = x1e8_duration / float(fovs.size() - 1);\n  const int idx = int(cycleT / durPerPoint);\n  const float t = (cycleT - float(idx) * durPerPoint) / durPerPoint;\n  return (fovs[idx + 1] - fovs[idx]) * t + fovs[idx];\n}\n\nfloat CCinematicCamera::GetMoveOutofIntoAlpha() const {\n  const float startDist = 0.25f + x160_znear;\n  const float endDist = 1.f * startDist;\n  const float deltaMag = (GetTranslation() - x210_moveIntoEyePos).magnitude();\n\n  if (deltaMag >= startDist && deltaMag <= endDist) {\n    return (deltaMag - startDist) / (endDist - startDist);\n  }\n\n  if (deltaMag > endDist) {\n    return 1.f;\n  }\n\n  return 0.f;\n}\n\nvoid CCinematicCamera::DeactivateSelf(CStateManager& mgr) {\n  SetActive(false);\n  SendScriptMsgs(EScriptObjectState::Inactive, mgr, EScriptObjectMessage::None);\n  WasDeactivated(mgr);\n}\n\nvoid CCinematicCamera::Think(float dt, CStateManager& mgr) {\n  if (GetActive()) {\n    zeus::CVector3f viewPoint = GetTranslation();\n    if (!x188_viewPoints.empty()) {\n      int idx = 0;\n      viewPoint = GetInterpolatedSplinePoint(x188_viewPoints, idx, x1ec_t);\n      if (idx > x1f4_passedViewPoint) {\n        x1f4_passedViewPoint = idx;\n        SendArrivedMsg(x1a8_viewPointArrivals[x1f4_passedViewPoint], mgr);\n      }\n    }\n\n    const zeus::CQuaternion orientation = GetInterpolatedOrientation(x198_viewOrientations, x1ec_t);\n\n    if ((x21c_flags & 0x1) == 0) {\n      if (!x1b8_targets.empty()) {\n        int idx = 0;\n        zeus::CVector3f target = GetInterpolatedSplinePoint(x1b8_targets, idx, x1ec_t);\n        if (x1b8_targets.size() == 1) {\n          if (const TCastToConstPtr<CActor> act = mgr.GetObjectById(x1c8_targetArrivals.front())) {\n            target = act->GetTranslation();\n          } else {\n            x1ec_t = x1e8_duration;\n          }\n        }\n        if (idx > x1f8_passedTarget) {\n          x1f8_passedTarget = idx;\n          SendArrivedMsg(x1c8_targetArrivals[x1f8_passedTarget], mgr);\n        }\n        const zeus::CVector3f upVec = orientation.transform(zeus::skUp);\n        if ((target - viewPoint).toVec2f().magnitude() < 0.0011920929f) {\n          SetTranslation(target);\n        } else {\n          SetTransform(zeus::lookAt(viewPoint, target, upVec));\n        }\n      } else {\n        SetTransform(zeus::CTransform(orientation, viewPoint));\n      }\n    } else {\n      zeus::CVector3f target = mgr.GetPlayer().GetTranslation();\n      if (mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed) {\n        target.z() += mgr.GetPlayer().GetMorphBall()->GetBallRadius();\n      } else {\n        target.z() += mgr.GetPlayer().GetEyeHeight();\n      }\n\n      const zeus::CVector3f upVec = orientation.transform(zeus::skUp);\n      if ((target - viewPoint).toVec2f().magnitude() < 0.0011920929f) {\n        SetTranslation(target);\n      } else {\n        SetTransform(zeus::lookAt(viewPoint, target, upVec));\n      }\n    }\n\n    x15c_currentFov = GetInterpolatedHFov(x1d8_viewHFovs, x1ec_t) / x168_aspect;\n    x170_24_perspDirty = true;\n\n    if (x20c_lookAtId != kInvalidUniqueId) {\n      if (const TCastToPtr<CScriptActor> act = mgr.ObjectById(x20c_lookAtId)) {\n        if (act->IsPlayerActor()) {\n          act->SetDrawFlags({5, 0, 3, zeus::CColor(1.f, GetMoveOutofIntoAlpha())});\n        }\n      }\n    }\n\n    x1ec_t += dt;\n    if (x1ec_t > x1e8_duration) {\n      for (auto i = static_cast<size_t>(x1f4_passedViewPoint) + 1; i < x1a8_viewPointArrivals.size(); ++i) {\n        SendArrivedMsg(x1a8_viewPointArrivals[i], mgr);\n      }\n      for (auto i = static_cast<size_t>(x1f8_passedTarget) + 1; i < x1c8_targetArrivals.size(); ++i) {\n        SendArrivedMsg(x1c8_targetArrivals[i], mgr);\n      }\n      DeactivateSelf(mgr);\n    }\n  }\n}\n\nvoid CCinematicCamera::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  CGameCamera::AcceptScriptMsg(msg, uid, mgr);\n  switch (msg) {\n  case EScriptObjectMessage::InitializedInArea:\n    if ((x21c_flags & 0x4) != 0 || (x21c_flags & 0x2) != 0) {\n      for (const SConnection& conn : x20_conns) {\n        const TUniqueId id = mgr.GetIdForScript(conn.x8_objId);\n        if (const TCastToConstPtr<CScriptActor> act = mgr.ObjectById(id)) {\n          if (act->IsPlayerActor()) {\n            x20c_lookAtId = id;\n            if (conn.x4_msg != EScriptObjectMessage::Deactivate && conn.x4_msg != EScriptObjectMessage::Reset) {\n              break;\n            }\n          }\n        }\n      }\n    }\n    break;\n  case EScriptObjectMessage::Activate:\n    CalculateWaypoints(mgr);\n    if ((x21c_flags & 1) == 0 && x220_24_ && x1b8_targets.empty()) {\n      break;\n    }\n    x1ec_t = 0.f;\n    Think(0.f, mgr);\n    mgr.GetCameraManager()->AddCinemaCamera(GetUniqueId(), mgr);\n    x1f4_passedViewPoint = 0;\n    if (!x1a8_viewPointArrivals.empty()) {\n      SendArrivedMsg(x1a8_viewPointArrivals[x1f4_passedViewPoint], mgr);\n    }\n    x1f8_passedTarget = 0;\n    if (!x1c8_targetArrivals.empty()) {\n      SendArrivedMsg(x1c8_targetArrivals[x1f8_passedTarget], mgr);\n    }\n    if ((x21c_flags & 0x100) != 0) {\n      mgr.SetCinematicPause(true);\n    }\n    break;\n  case EScriptObjectMessage::Deactivate:\n    WasDeactivated(mgr);\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CCinematicCamera::CalculateMoveOutofIntoEyePosition(bool outOfEye, CStateManager& mgr) {\n  zeus::CQuaternion q(mgr.GetPlayer().GetTransform().basis);\n  zeus::CVector3f eyePos = mgr.GetPlayer().GetEyePosition();\n  if (x20c_lookAtId != kInvalidUniqueId) {\n    if (const TCastToConstPtr<CScriptActor> act = mgr.GetObjectById(x20c_lookAtId)) {\n      if (act->IsPlayerActor()) {\n        if (const CModelData* mData = act->GetModelData()) {\n          if (const CAnimData* aData = mData->GetAnimationData()) {\n            if (const CAnimTreeNode* root = aData->GetRootAnimationTree().get()) {\n              const CSegId lEye = aData->GetLocatorSegId(\"L_eye\"sv);\n              const CSegId rEye = aData->GetLocatorSegId(\"R_eye\"sv);\n              if (lEye.IsValid() && rEye.IsValid()) {\n                const CCharAnimTime time =\n                    outOfEye ? CCharAnimTime(0.f) : root->VGetSteadyStateAnimInfo().GetDuration();\n                const CCharAnimTime* pTime = outOfEye ? nullptr : &time;\n                eyePos = ((act->GetTransform() * mData->GetScaledLocatorTransformDynamic(\"L_eye\"sv, pTime)).origin +\n                          (act->GetTransform() * mData->GetScaledLocatorTransformDynamic(\"R_eye\"sv, pTime)).origin) *\n                         0.5f;\n                q = zeus::CQuaternion(act->GetTransform().basis);\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n\n  zeus::CVector3f behindPos = eyePos;\n  zeus::CVector3f behindDelta = q.transform({0.f, -g_tweakPlayerRes->xf0_cinematicMoveOutofIntoPlayerDistance, 0.f});\n  if (!outOfEye) {\n    behindPos += behindDelta;\n    behindDelta = -behindDelta;\n  }\n\n  for (size_t i = 0; i < 2; ++i) {\n    x188_viewPoints[outOfEye ? i : x188_viewPoints.size() - (2 - i)] = behindPos;\n    x198_viewOrientations[outOfEye ? i : x198_viewOrientations.size() - (2 - i)] = q;\n    x1b8_targets[outOfEye ? i : x1b8_targets.size() - (2 - i)] = eyePos;\n    behindPos += behindDelta;\n  }\n\n  x210_moveIntoEyePos = eyePos;\n}\n\nvoid CCinematicCamera::GenerateMoveOutofIntoPoints(bool outOfEye, CStateManager& mgr) {\n  const zeus::CQuaternion q(mgr.GetPlayer().GetTransform().basis);\n  const zeus::CVector3f eyePos = mgr.GetPlayer().GetEyePosition();\n  zeus::CVector3f behindDelta = q.transform({0.f, -g_tweakPlayerRes->xf0_cinematicMoveOutofIntoPlayerDistance, 0.f});\n  zeus::CVector3f behindPos = eyePos;\n  if (!outOfEye) {\n    behindPos += behindDelta;\n    behindDelta = -behindDelta;\n  }\n  for (int i = 0; i < 2; ++i) {\n    x188_viewPoints.emplace_back(behindPos);\n    x198_viewOrientations.emplace_back(q);\n    x1a8_viewPointArrivals.emplace_back(mgr.GetPlayer().GetUniqueId());\n    x1b8_targets.emplace_back(eyePos);\n    x1c8_targetArrivals.emplace_back(kInvalidUniqueId);\n    behindPos += behindDelta;\n  }\n  CalculateMoveOutofIntoEyePosition(outOfEye, mgr);\n}\n\nbool CCinematicCamera::PickRandomActiveConnection(const std::vector<SConnection>& conns, SConnection& randConn,\n                                                  CStateManager& mgr) {\n  int count = 0;\n  for (const SConnection& conn : conns) {\n    if (conn.x0_state == EScriptObjectState::Arrived && conn.x4_msg == EScriptObjectMessage::Next) {\n      if (const TCastToConstPtr<CActor> act = mgr.GetObjectById(mgr.GetIdForScript(conn.x8_objId))) {\n        if (act->GetActive()) {\n          ++count;\n        }\n      }\n    }\n  }\n\n  if (count == 0) {\n    return false;\n  }\n\n  const int randIdx = mgr.GetActiveRandom()->Next() % count;\n  int idx = 0;\n  for (const SConnection& conn : conns) {\n    if (conn.x0_state == EScriptObjectState::Arrived && conn.x4_msg == EScriptObjectMessage::Next) {\n      if (const TCastToConstPtr<CActor> act = mgr.GetObjectById(mgr.GetIdForScript(conn.x8_objId))) {\n        if (act->GetActive()) {\n          if (randIdx == idx) {\n            randConn = conn;\n            break;\n          }\n          ++idx;\n        }\n      }\n    }\n  }\n\n  return true;\n}\n\nvoid CCinematicCamera::CalculateWaypoints(CStateManager& mgr) {\n  const SConnection* firstVP = nullptr;\n  const SConnection* firstTarget = nullptr;\n  for (const SConnection& conn : x20_conns) {\n    if (conn.x0_state == EScriptObjectState::CameraPath && conn.x4_msg == EScriptObjectMessage::Activate) {\n      firstVP = &conn;\n    } else if (conn.x0_state == EScriptObjectState::CameraTarget && conn.x4_msg == EScriptObjectMessage::Activate) {\n      firstTarget = &conn;\n    }\n  }\n\n  x188_viewPoints.clear();\n  x188_viewPoints.reserve(3);\n  x198_viewOrientations.clear();\n  x198_viewOrientations.reserve(3);\n  x1a8_viewPointArrivals.clear();\n  x1a8_viewPointArrivals.reserve(3);\n  x1b8_targets.clear();\n  x1b8_targets.reserve(3);\n  x1c8_targetArrivals.clear();\n  x1c8_targetArrivals.reserve(3);\n  x1d8_viewHFovs.clear();\n  x1d8_viewHFovs.reserve(3);\n\n  x220_24_ = false;\n\n  if ((x21c_flags & 0x2) != 0 && (x21c_flags & 0x200) == 0) {\n    GenerateMoveOutofIntoPoints(true, mgr);\n  }\n\n  if (firstVP) {\n    TCastToConstPtr<CActor> wp = mgr.GetObjectById(mgr.GetIdForScript(firstVP->x8_objId));\n    while (wp) {\n      x188_viewPoints.push_back(wp->GetTranslation());\n      x198_viewOrientations.emplace_back(wp->GetTransform().basis);\n      if (const TCastToConstPtr<CScriptCameraWaypoint> cwp = wp.GetPtr()) {\n        x1d8_viewHFovs.push_back(cwp->GetHFov());\n      }\n      const auto search = std::find_if(x1a8_viewPointArrivals.cbegin(), x1a8_viewPointArrivals.cend(),\n                                       [&wp](TUniqueId id) { return id == wp->GetUniqueId(); });\n      if (search == x1a8_viewPointArrivals.cend()) {\n        x1a8_viewPointArrivals.push_back(wp->GetUniqueId());\n        SConnection randConn;\n        if (PickRandomActiveConnection(wp->GetConnectionList(), randConn, mgr)) {\n          wp = mgr.GetObjectById(mgr.GetIdForScript(randConn.x8_objId));\n        } else {\n          break;\n        }\n      } else {\n        break;\n      }\n    }\n  }\n\n  if (firstTarget) {\n    TCastToConstPtr<CActor> tgt = mgr.GetObjectById(mgr.GetIdForScript(firstTarget->x8_objId));\n    while (tgt) {\n      x1b8_targets.push_back(tgt->GetTranslation());\n      const auto search = std::find_if(x1c8_targetArrivals.cbegin(), x1c8_targetArrivals.cend(),\n                                       [&tgt](TUniqueId id) { return id == tgt->GetUniqueId(); });\n      if (search == x1c8_targetArrivals.cend()) {\n        x1c8_targetArrivals.push_back(tgt->GetUniqueId());\n        SConnection randConn;\n        if (PickRandomActiveConnection(tgt->GetConnectionList(), randConn, mgr)) {\n          tgt = mgr.GetObjectById(mgr.GetIdForScript(randConn.x8_objId));\n        } else {\n          break;\n        }\n      } else {\n        break;\n      }\n    }\n  }\n\n  if ((x21c_flags & 0x4) != 0 && (x21c_flags & 0x200) == 0) {\n    GenerateMoveOutofIntoPoints(false, mgr);\n  }\n}\n\nvoid CCinematicCamera::SendArrivedMsg(TUniqueId reciever, CStateManager& mgr) {\n  mgr.SendScriptMsgAlways(reciever, GetUniqueId(), EScriptObjectMessage::Arrived);\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Camera/CCinematicCamera.hpp",
    "content": "#pragma once\n\n#include <string_view>\n#include <vector>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Camera/CGameCamera.hpp\"\n\n#include <zeus/CQuaternion.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\n\nclass CCinematicCamera : public CGameCamera {\n  std::vector<zeus::CVector3f> x188_viewPoints;\n  std::vector<zeus::CQuaternion> x198_viewOrientations;\n  std::vector<TUniqueId> x1a8_viewPointArrivals;\n  std::vector<zeus::CVector3f> x1b8_targets;\n  std::vector<TUniqueId> x1c8_targetArrivals;\n  std::vector<float> x1d8_viewHFovs;\n  float x1e8_duration;\n  float x1ec_t = 0.f;\n  float x1f0_origFovy;\n  int x1f4_passedViewPoint = 0;\n  int x1f8_passedTarget = 0;\n  zeus::CQuaternion x1fc_origOrientation;\n  TUniqueId x20c_lookAtId = kInvalidUniqueId;\n  zeus::CVector3f x210_moveIntoEyePos;\n  u32 x21c_flags; // 0x1: look at player, 0x2: out of player eye, 0x4: into player eye, 0x10: finish cine skip,\n                  // 0x20: disable input, 0x40: draw player, 0x80: check failsafe, 0x100: cinematic pause,\n                  // 0x200: disable out of into\n  bool x220_24_;\n  zeus::CVector3f GetInterpolatedSplinePoint(const std::vector<zeus::CVector3f>& points, int& idxOut, float t) const;\n  zeus::CQuaternion GetInterpolatedOrientation(const std::vector<zeus::CQuaternion>& rotations, float t) const;\n  float GetInterpolatedHFov(const std::vector<float>& fovs, float t) const;\n  float GetMoveOutofIntoAlpha() const;\n  void DeactivateSelf(CStateManager& mgr);\n  void CalculateMoveOutofIntoEyePosition(bool outOfEye, CStateManager& mgr);\n  void GenerateMoveOutofIntoPoints(bool outOfEye, CStateManager& mgr);\n  static bool PickRandomActiveConnection(const std::vector<SConnection>& conns, SConnection& randConn,\n                                         CStateManager& mgr);\n  void CalculateWaypoints(CStateManager& mgr);\n\npublic:\n  DEFINE_ENTITY\n  CCinematicCamera(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                   bool active, float shotDuration, float fovy, float znear, float zfar, float aspect, u32 flags);\n\n  void Accept(IVisitor& visitor) override;\n  void Think(float dt, CStateManager& mgr) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) override;\n  void ProcessInput(const CFinalInput&, CStateManager& mgr) override;\n  void Reset(const zeus::CTransform&, CStateManager& mgr) override;\n  u32 GetFlags() const { return x21c_flags; }\n  void WasDeactivated(CStateManager& mgr);\n  void SendArrivedMsg(TUniqueId reciever, CStateManager& mgr);\n  float GetDuration() const { return x1e8_duration; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Camera/CFirstPersonCamera.cpp",
    "content": "#include \"Runtime/Camera/CFirstPersonCamera.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CScriptCameraPitchVolume.hpp\"\n#include \"Runtime/World/CScriptGrapplePoint.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCFirstPersonCamera::CFirstPersonCamera(TUniqueId uid, const zeus::CTransform& xf, TUniqueId watchedObj,\n                                       float orbitCameraSpeed, float fov, float nearz, float farz, float aspect)\n: CGameCamera(uid, true, \"First Person Camera\", CEntityInfo(kInvalidAreaId, CEntity::NullConnectionList), xf, fov,\n              nearz, farz, aspect, watchedObj, false, 0)\n, x188_orbitCameraSpeed(orbitCameraSpeed)\n, x190_gunFollowXf(xf) {\n  MP1::tw_FieldOfView->addListener([this](CVar* cv) { _fovListener(cv); });\n}\n\nvoid CFirstPersonCamera::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CFirstPersonCamera::PreThink(float dt, CStateManager& mgr) {\n  // Empty\n}\n\nvoid CFirstPersonCamera::Think(float dt, CStateManager& mgr) {\n  if (TCastToPtr<CPlayer> player = mgr.ObjectById(xe8_watchedObject)) {\n    if (!x1c6_24_deferBallTransitionProcessing) {\n      if (player->GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed) {\n        if (player->GetCameraState() != CPlayer::EPlayerCameraState::Spawned)\n          return;\n        SetTransform(player->CreateTransformFromMovementDirection());\n        SetTranslation(player->GetEyePosition());\n        return;\n      }\n      if (player->GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Unmorphed) {\n        if (player->GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Unmorphing)\n          return;\n        if (std::fabs(player->GetMorphFactor() - 1.f) >= 0.00001f)\n          return;\n      }\n    } else {\n      x1c6_24_deferBallTransitionProcessing = false;\n    }\n    zeus::CTransform backupXf = x34_transform;\n    UpdateElevation(mgr);\n    UpdateTransform(mgr, dt);\n    SetTransform(ValidateCameraTransform(x34_transform, backupXf));\n    if (x1d4_closeInTimer > 0.f)\n      x1d4_closeInTimer -= dt;\n  }\n}\n\nvoid CFirstPersonCamera::ProcessInput(const CFinalInput&, CStateManager& mgr) {\n  // Empty\n}\n\nvoid CFirstPersonCamera::Reset(const zeus::CTransform& xf, CStateManager& mgr) {\n  SetTransform(xf);\n  SetTranslation(mgr.GetPlayer().GetEyePosition());\n  x190_gunFollowXf = x34_transform;\n}\n\nvoid CFirstPersonCamera::SkipCinematic() {\n  x1c8_closeInVec = zeus::skZero3f;\n  x1d4_closeInTimer = 0.f;\n}\n\nvoid CFirstPersonCamera::CalculateGunFollowOrientationAndTransform(zeus::CTransform& gunXf, zeus::CQuaternion& gunQ,\n                                                                   float dt, zeus::CVector3f& rVec) const {\n  zeus::CVector3f gunFrontVec = x190_gunFollowXf.frontVector();\n  gunFrontVec.z() = 0.f;\n  if (gunFrontVec.canBeNormalized())\n    gunFrontVec.normalize();\n\n  zeus::CVector3f rVecNoZ = rVec;\n  rVecNoZ.z() = 0.f;\n  if (rVecNoZ.canBeNormalized())\n    rVecNoZ.normalize();\n\n  gunXf = zeus::CQuaternion::lookAt(gunFrontVec, rVecNoZ, 2.f * M_PIF).toTransform() * x190_gunFollowXf.getRotation();\n\n  zeus::CVector3f newgunFront = gunXf.frontVector();\n  if (newgunFront.canBeNormalized())\n    newgunFront.normalize();\n\n  float angle = newgunFront.dot(rVec);\n  angle = zeus::clamp(-1.f, angle, 1.f);\n  gunQ = zeus::CQuaternion::lookAt(newgunFront, rVec, zeus::clamp(0.f, std::acos(angle) / dt, 1.f) * dt);\n}\n\nvoid CFirstPersonCamera::UpdateTransform(CStateManager& mgr, float dt) {\n  TCastToPtr<CPlayer> player(mgr.ObjectById(GetWatchedObject()));\n  if (!player) {\n    SetTransform(zeus::CTransform());\n    return;\n  }\n\n  zeus::CTransform playerXf = player->GetTransform();\n  zeus::CVector3f rVec = playerXf.rotate(\n      {0.f, zeus::clamp(-1.f, std::cos(x1c0_pitch), 1.0f), zeus::clamp(-1.f, std::sin(x1c0_pitch), 1.0f)});\n  if (player->x3dc_inFreeLook) {\n    float angle = player->x3ec_freeLookPitchAngle;\n    float angleClamp = g_tweakPlayer->GetVerticalFreeLookAngleVel() - std::fabs(x1c0_pitch);\n    angle = zeus::clamp(-angleClamp, angle, angleClamp);\n    zeus::CVector3f vec;\n    vec.z() = std::sin(angle);\n    vec.y() = std::cos(-player->x3e4_freeLookYawAngle) * std::cos(angle);\n    vec.x() = std::sin(-player->x3e4_freeLookYawAngle) * std::cos(angle);\n    if (g_tweakPlayer->GetFreeLookTurnsPlayer()) {\n      vec.x() = 0.f;\n      if (!zeus::close_enough(vec, zeus::skZero3f))\n        vec.normalize();\n    }\n\n    rVec = zeus::CQuaternion::lookAt({0.f, 1.f, 0.f}, rVec, 2.f * M_PIF).transform(vec);\n  }\n\n  zeus::CVector3f eyePos = player->GetEyePosition();\n  if (x1d4_closeInTimer > 0.f) {\n    eyePos += zeus::clamp(0.f, 0.5f * x1d4_closeInTimer, 1.f) * x1c8_closeInVec;\n    player->GetCameraBob()->ResetCameraBobTime();\n    player->GetCameraBob()->SetCameraBobTransform(zeus::CTransform());\n  }\n\n  switch (player->GetOrbitState()) {\n  case CPlayer::EPlayerOrbitState::ForcedOrbitObject:\n  case CPlayer::EPlayerOrbitState::OrbitObject: {\n    const CActor* act = TCastToConstPtr<CActor>(mgr.GetObjectById(player->x310_orbitTargetId));\n    if (act && act->GetMaterialList().HasMaterial(EMaterialTypes::Orbit)) {\n      zeus::CVector3f v = player->x314_orbitPoint - eyePos;\n      if (v.canBeNormalized())\n        v.normalize();\n\n      rVec = v;\n    } else {\n      rVec = player->x314_orbitPoint - eyePos;\n    }\n    break;\n  }\n  case CPlayer::EPlayerOrbitState::OrbitPoint:\n  case CPlayer::EPlayerOrbitState::OrbitCarcass: {\n    if (!player->x3dd_lookButtonHeld) {\n      rVec = player->x314_orbitPoint - eyePos;\n    }\n    break;\n  }\n  case CPlayer::EPlayerOrbitState::NoOrbit: {\n    if (player->GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Unmorphed &&\n        !player->x3dc_inFreeLook && x1c4_pitchId == kInvalidUniqueId) {\n      if (player->x294_jumpCameraTimer > 0.f) {\n        float angle = zeus::clamp(0.f,\n                                  (player->x294_jumpCameraTimer - g_tweakPlayer->GetJumpCameraPitchDownStart()) /\n                                      g_tweakPlayer->GetJumpCameraPitchDownFull(),\n                                  1.f) *\n                      g_tweakPlayer->GetJumpCameraPitchDownAngle();\n        angle += x1c0_pitch;\n        rVec.x() = 0.f;\n        rVec.y() = std::cos(angle);\n        rVec.z() = -std::sin(angle);\n\n        rVec = playerXf.rotate(rVec);\n      } else if (player->x29c_fallCameraTimer > 0.f) {\n        float angle = zeus::clamp(0.f,\n                                  (player->x29c_fallCameraTimer - g_tweakPlayer->GetFallCameraPitchDownStart()) /\n                                      g_tweakPlayer->GetFallCameraPitchDownFull(),\n                                  1.f) *\n                      g_tweakPlayer->GetFallCameraPitchDownAngle();\n        rVec.x() = 0.f;\n        rVec.y() = std::cos(angle);\n        rVec.z() = -std::sin(angle);\n\n        rVec = playerXf.rotate(rVec);\n      }\n    }\n    break;\n  }\n  default:\n    break;\n  }\n\n  if (rVec.canBeNormalized())\n    rVec.normalize();\n\n  zeus::CTransform gunXf = x190_gunFollowXf;\n  zeus::CQuaternion qGun;\n\n  if (!player->x3dc_inFreeLook) {\n    switch (player->GetOrbitState()) {\n    default: {\n      CalculateGunFollowOrientationAndTransform(gunXf, qGun, dt * g_tweakPlayer->GetFirstPersonCameraSpeed(), rVec);\n      break;\n    }\n    case CPlayer::EPlayerOrbitState::Grapple: {\n      CalculateGunFollowOrientationAndTransform(gunXf, qGun, dt * g_tweakPlayer->GetGrappleCameraSpeed(), rVec);\n      break;\n    }\n    case CPlayer::EPlayerOrbitState::OrbitPoint:\n    case CPlayer::EPlayerOrbitState::OrbitCarcass: {\n      CalculateGunFollowOrientationAndTransform(gunXf, qGun, dt * g_tweakPlayer->GetOrbitCameraSpeed() * 0.25f, rVec);\n      break;\n    }\n    case CPlayer::EPlayerOrbitState::ForcedOrbitObject:\n    case CPlayer::EPlayerOrbitState::OrbitObject: {\n      zeus::CVector3f gunFrontVec = x190_gunFollowXf.frontVector();\n\n      if (gunFrontVec.canBeNormalized())\n        gunFrontVec.normalize();\n\n      float scaledDt = (dt * g_tweakPlayer->GetOrbitCameraSpeed());\n      float angle = gunFrontVec.dot(rVec);\n      angle = zeus::clamp(-1.f, angle, 1.f);\n      float clampedAngle = zeus::clamp(0.f, std::acos(angle) / scaledDt, 1.f);\n      if (angle > 0.999f || x18c_lockCamera || player->x374_orbitLockEstablished)\n        qGun = zeus::CQuaternion::lookAt(gunFrontVec, rVec, 2.f * M_PIF);\n      else\n        qGun = zeus::CQuaternion::lookAt(gunFrontVec, rVec, scaledDt * clampedAngle);\n\n      const CScriptGrapplePoint* gPoint =\n          TCastToConstPtr<CScriptGrapplePoint>(mgr.GetObjectById(player->x310_orbitTargetId));\n      if (gPoint && player->x29c_fallCameraTimer > 0.f) {\n        gunFrontVec = x190_gunFollowXf.frontVector();\n        if (gunFrontVec.canBeNormalized())\n          gunFrontVec.normalize();\n\n        zeus::CVector3f rVecCpy = rVec;\n        rVecCpy.z() = 0.f;\n        if (rVecCpy.canBeNormalized())\n          rVecCpy.normalize();\n\n        gunXf =\n            zeus::CQuaternion::lookAt(gunFrontVec, rVecCpy, 2.f * M_PIF).toTransform() * x190_gunFollowXf.getRotation();\n\n        gunFrontVec = gunXf.frontVector();\n        if (gunFrontVec.canBeNormalized())\n          gunFrontVec.normalize();\n\n        // float angle = gunFrontVec.dot(rVec);\n        // float sdt = dt * g_tweakPlayer->GetGrappleCameraSpeed();\n\n        // angle = zeus::clamp(-1.f, angle, 1.f);\n        // angle = zeus::clamp(0.f, std::acos(angle) / sdt, 1.f);\n        qGun = zeus::CQuaternion::lookAt(gunFrontVec, rVecCpy, 2.f * M_PIF);\n      }\n      break;\n    }\n    }\n  } else {\n    zeus::CVector3f gunFront = x190_gunFollowXf.frontVector();\n    gunFront.z() = 0.f;\n    if (gunFront.canBeNormalized())\n      gunFront.normalize();\n\n    zeus::CVector3f rVecCpy = rVec;\n    rVecCpy.z() = 0.f;\n    if (rVecCpy.canBeNormalized())\n      rVecCpy.normalize();\n\n    gunXf = zeus::CQuaternion::lookAt(gunFront, rVecCpy, 2.f * M_PIF).toTransform() * x190_gunFollowXf.getRotation();\n    gunFront = gunXf.frontVector();\n    if (gunFront.canBeNormalized())\n      gunFront.normalize();\n\n    float angle = gunFront.dot(rVec);\n    angle = zeus::clamp(-1.f, angle, 1.f);\n    float sdt = dt * g_tweakPlayer->GetFreeLookSpeed();\n    qGun = zeus::CQuaternion::lookAt(\n        gunFront, rVec,\n        sdt * zeus::clamp(0.f, g_tweakPlayer->GetFreeLookDampenFactor() * (std::acos(angle) / sdt), 1.f));\n  }\n  zeus::CTransform bobXf = player->GetCameraBob()->GetCameraBobTransformation();\n\n  if (player->GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed ||\n      player->GetOrbitState() == CPlayer::EPlayerOrbitState::Grapple ||\n      player->GetGrappleState() != CPlayer::EGrappleState::None ||\n      mgr.GetGameState() == CStateManager::EGameState::SoftPaused || mgr.GetCameraManager()->IsInCinematicCamera() ||\n      x1d4_closeInTimer > 0.f) {\n    bobXf = zeus::CTransform();\n    player->GetCameraBob()->SetCameraBobTransform(bobXf);\n  }\n\n  x190_gunFollowXf = qGun.toTransform() * gunXf;\n  SetTransform(x190_gunFollowXf * bobXf.getRotation());\n  x190_gunFollowXf.origin = eyePos;\n  CActor::SetTranslation(eyePos + player->GetTransform().rotate(bobXf.origin));\n  x190_gunFollowXf.orthonormalize();\n}\n\nvoid CFirstPersonCamera::UpdateElevation(CStateManager& mgr) {\n  x1c0_pitch = 0.f;\n  if (TCastToConstPtr<CPlayer> player = mgr.GetObjectById(xe8_watchedObject)) {\n    if (x1c4_pitchId != kInvalidUniqueId) {\n      if (TCastToConstPtr<CScriptCameraPitchVolume> pvol = mgr.GetObjectById(x1c4_pitchId)) {\n        zeus::CVector3f pitchDirFlat = pvol->GetTransform().basis[1];\n        pitchDirFlat.z() = 0.f;\n        if (!pitchDirFlat.canBeNormalized())\n          pitchDirFlat = zeus::skForward;\n\n        zeus::CVector3f playerDirFlat = player->GetTransform().basis[1];\n        playerDirFlat.z() = 0.f;\n        playerDirFlat.normalize();\n\n        float pitchDot = zeus::clamp(-1.f, pitchDirFlat.dot(playerDirFlat), 1.f);\n        if (pitchDot < 0.f)\n          x1c0_pitch = pvol->GetDownPitch() * -pitchDot;\n        else\n          x1c0_pitch = pvol->GetUpPitch() * -pitchDot;\n\n        zeus::CVector3f pvolToPlayerFlat = player->GetTranslation() - pvol->GetTranslation();\n        pvolToPlayerFlat.z() = 0.f;\n        float pitchMul = 0.f;\n        if (pvolToPlayerFlat.canBeNormalized()) {\n          float pvolPlayerProj =\n              std::fabs(zeus::clamp(-1.f, pvolToPlayerFlat.dot(pitchDirFlat), 1.f)) * pvolToPlayerFlat.magnitude();\n          if (pvolPlayerProj <= pvol->GetMaxInterpolationDistance())\n            pitchMul = 1.f;\n          else\n            pitchMul = 1.f - zeus::clamp(-1.f,\n                                         (pvolPlayerProj - pvol->GetMaxInterpolationDistance()) /\n                                             (pvol->GetScale().y() - pvol->GetMaxInterpolationDistance()),\n                                         1.f);\n        }\n        x1c0_pitch *= pitchMul;\n      }\n    }\n  }\n}\n\nvoid CFirstPersonCamera::_fovListener(CVar* cv) {\n  x15c_currentFov = x180_perspInterpStartFov = x184_perspInterpEndFov = cv->toReal();\n  x170_24_perspDirty = true;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Camera/CFirstPersonCamera.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Camera/CGameCamera.hpp\"\n\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\n\nclass CFirstPersonCamera : public CGameCamera {\n  float x188_orbitCameraSpeed;\n  bool x18c_lockCamera = false;\n  zeus::CTransform x190_gunFollowXf;\n  float x1c0_pitch = 0.f;\n  TUniqueId x1c4_pitchId = kInvalidUniqueId;\n  bool x1c6_24_deferBallTransitionProcessing : 1 = false;\n  zeus::CVector3f x1c8_closeInVec;\n  float x1d4_closeInTimer = 0.f;\n  void _fovListener(CVar* cv);\n\npublic:\n  DEFINE_ENTITY\n  CFirstPersonCamera(TUniqueId, const zeus::CTransform& xf, TUniqueId, float orbitCameraSpeed, float fov,\n                     float nearplane, float farplane, float aspect);\n\n  void Accept(IVisitor& visitor) override;\n  void PreThink(float dt, CStateManager& mgr) override;\n  void Think(float dt, CStateManager& mgr) override;\n  void ProcessInput(const CFinalInput&, CStateManager& mgr) override;\n  void Reset(const zeus::CTransform&, CStateManager& mgr) override;\n\n  void SkipCinematic();\n  const zeus::CTransform& GetGunFollowTransform() const { return x190_gunFollowXf; }\n  void UpdateTransform(CStateManager& mgr, float dt);\n  void UpdateElevation(CStateManager& mgr);\n  void CalculateGunFollowOrientationAndTransform(zeus::CTransform&, zeus::CQuaternion&, float, zeus::CVector3f&) const;\n  void SetScriptPitchId(TUniqueId uid) { x1c4_pitchId = uid; }\n  void SetLockCamera(bool v) { x18c_lockCamera = v; }\n  void DeferBallTransitionProcessing() { x1c6_24_deferBallTransitionProcessing = true; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Camera/CGameCamera.cpp",
    "content": "#include \"Runtime/Camera/CGameCamera.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Camera/CCameraManager.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n\nnamespace metaforce {\n\nCGameCamera::CGameCamera(TUniqueId uid, bool active, std::string_view name, const CEntityInfo& info,\n                         const zeus::CTransform& xf, float fovy, float znear, float zfar, float aspect,\n                         TUniqueId watchedId, bool disableInput, u32 controllerIdx)\n: CActor(uid, active, name, info, xf, CModelData::CModelDataNull(), CMaterialList(EMaterialTypes::NoStepLogic),\n         CActorParameters::None(), kInvalidUniqueId)\n, xe8_watchedObject(watchedId)\n, x12c_origXf(xf)\n, x15c_currentFov(fovy)\n, x160_znear(znear)\n, x164_zfar(zfar)\n, x168_aspect(aspect)\n, x16c_controllerIdx(controllerIdx)\n, x170_25_disablesInput(disableInput)\n, x180_perspInterpStartFov(fovy)\n, x184_perspInterpEndFov(fovy) {\n\n  xe7_29_drawEnabled = false;\n}\n\nvoid CGameCamera::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  if (msg == EScriptObjectMessage::AddSplashInhabitant) {\n    mgr.GetCameraManager()->SetInsideFluid(true, uid);\n    return;\n  } else if (msg == EScriptObjectMessage::RemoveSplashInhabitant) {\n    mgr.GetCameraManager()->SetInsideFluid(false, kInvalidUniqueId);\n    return;\n  }\n\n  CActor::AcceptScriptMsg(msg, uid, mgr);\n}\n\nvoid CGameCamera::SetActive(bool active) {\n  CActor::SetActive(active);\n  xe7_29_drawEnabled = false;\n}\n\nzeus::CMatrix4f CGameCamera::GetPerspectiveMatrix() const {\n  if (x170_24_perspDirty) {\n    xec_perspectiveMatrix = CGraphics::CalculatePerspectiveMatrix(x15c_currentFov, x168_aspect, x160_znear, x164_zfar);\n    x170_24_perspDirty = false;\n  }\n\n  return xec_perspectiveMatrix;\n}\n\nzeus::CVector3f CGameCamera::ConvertToScreenSpace(const zeus::CVector3f& v) const {\n  zeus::CVector3f rVec = x34_transform.transposeRotate(v - x34_transform.origin);\n\n  if (rVec.isZero())\n    return {-1.f, -1.f, 1.f};\n\n  return GetPerspectiveMatrix().multiplyOneOverW(rVec);\n}\n\nzeus::CTransform CGameCamera::ValidateCameraTransform(const zeus::CTransform& newXf,\n                                                      const zeus::CTransform& oldXf) const {\n  zeus::CTransform xfCpy(newXf);\n  if (!zeus::close_enough(newXf.rightVector().magnitude(), 1.f) ||\n      !zeus::close_enough(newXf.frontVector().magnitude(), 1.f) ||\n      !zeus::close_enough(newXf.upVector().magnitude(), 1.f))\n    xfCpy.orthonormalize();\n  float f2 = zeus::clamp(-1.f, newXf.frontVector().dot(zeus::skUp), 1.f);\n  if (std::fabs(f2) > 0.999f)\n    xfCpy = oldXf;\n\n  if (xfCpy.upVector().z() < -0.2f)\n    xfCpy = zeus::CQuaternion::fromAxisAngle(xfCpy.frontVector(), M_PIF).toTransform() * xfCpy;\n\n  if (!zeus::close_enough(xfCpy.rightVector().z(), 0.f) && !zeus::close_enough(xfCpy.upVector().z(), 0.f)) {\n    if (xfCpy.frontVector().canBeNormalized())\n      xfCpy = zeus::lookAt(zeus::skZero3f, xfCpy.frontVector());\n    else\n      xfCpy = oldXf;\n  }\n\n  xfCpy.origin = newXf.origin;\n  return xfCpy;\n}\n\nvoid CGameCamera::UpdatePerspective(float dt) {\n  if (x174_delayTime > 0.f) {\n    x174_delayTime -= dt;\n    return;\n  }\n\n  if (x178_perspInterpRemTime <= 0.f)\n    return;\n\n  x178_perspInterpRemTime -= dt;\n  if (x178_perspInterpRemTime <= 0.f) {\n    x15c_currentFov = x184_perspInterpEndFov;\n    x170_24_perspDirty = true;\n  } else {\n    x15c_currentFov = zeus::clamp(0.f, (x178_perspInterpRemTime / x17c_perspInterpDur), 1.f) *\n                          (x180_perspInterpStartFov - x184_perspInterpEndFov) +\n                      x184_perspInterpEndFov;\n    x170_24_perspDirty = true;\n  }\n}\n\nvoid CGameCamera::SetFovInterpolation(float start, float fov, float time, float delayTime) {\n  if (time < 0.f) {\n    x15c_currentFov = fov;\n    x170_24_perspDirty = true;\n    x184_perspInterpEndFov = fov;\n    x178_perspInterpRemTime = x174_delayTime = 0.f;\n  } else {\n    x174_delayTime = std::max(0.f, delayTime);\n    x17c_perspInterpDur = time;\n    x178_perspInterpRemTime = time;\n    x180_perspInterpStartFov = start;\n    x184_perspInterpEndFov = fov;\n    x15c_currentFov = start;\n    x170_24_perspDirty = true;\n  }\n}\n\nvoid CGameCamera::SkipFovInterpolation() {\n  if (x178_perspInterpRemTime > 0) {\n    x15c_currentFov = x184_perspInterpEndFov;\n    x170_24_perspDirty = true;\n  }\n\n  x178_perspInterpRemTime = x174_delayTime = 0.f;\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Camera/CGameCamera.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/World/CActor.hpp\"\n\n#include <zeus/CMatrix4f.hpp>\n#include <zeus/CTransform.hpp>\n\nnamespace metaforce {\nstruct CFinalInput;\n\nclass CGameCamera : public CActor {\n  friend class CCameraManager;\n  friend class CStateManager;\n\nprotected:\n  TUniqueId xe8_watchedObject;\n  mutable zeus::CMatrix4f xec_perspectiveMatrix;\n  zeus::CTransform x12c_origXf;\n  float x15c_currentFov;\n  float x160_znear;\n  float x164_zfar;\n  float x168_aspect;\n  u32 x16c_controllerIdx;\n  mutable bool x170_24_perspDirty : 1 = true;\n  bool x170_25_disablesInput : 1;\n  float x174_delayTime = 0.f;\n  float x178_perspInterpRemTime = 0.f;\n  float x17c_perspInterpDur = 0.f;\n  float x180_perspInterpStartFov;\n  float x184_perspInterpEndFov;\n\npublic:\n  DEFINE_ENTITY\n  CGameCamera(TUniqueId, bool active, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n              float fov, float nearz, float farz, float aspect, TUniqueId watchedId, bool disableInput,\n              u32 controllerIdx);\n\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void SetActive(bool active) override;\n  virtual void ProcessInput(const CFinalInput&, CStateManager& mgr) = 0;\n  virtual void Reset(const zeus::CTransform&, CStateManager& mgr) = 0;\n\n  zeus::CMatrix4f GetPerspectiveMatrix() const;\n  zeus::CVector3f ConvertToScreenSpace(const zeus::CVector3f&) const;\n  zeus::CTransform ValidateCameraTransform(const zeus::CTransform&, const zeus::CTransform&) const;\n  float GetNearClipDistance() const { return x160_znear; }\n  float GetFarClipDistance() const { return x164_zfar; }\n  float GetAspectRatio() const { return x168_aspect; }\n  TUniqueId GetWatchedObject() const { return xe8_watchedObject; }\n  float GetFov() const { return x15c_currentFov; }\n  void GetControllerNumber() const;\n  bool DisablesInput() const;\n  void UpdatePerspective(float);\n  void SetFovInterpolation(float start, float end, float time, float delayTime);\n  void SkipFovInterpolation();\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Camera/CInterpolationCamera.cpp",
    "content": "#include \"Runtime/Camera/CInterpolationCamera.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Camera/CBallCamera.hpp\"\n#include \"Runtime/Camera/CCameraManager.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CScriptSpindleCamera.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCInterpolationCamera::CInterpolationCamera(TUniqueId uid, const zeus::CTransform& xf)\n: CGameCamera(uid, false, \"Interpolation Camera\",\n              CEntityInfo(kInvalidAreaId, CEntity::NullConnectionList, kInvalidEditorId), xf,\n              CCameraManager::ThirdPersonFOV(), CCameraManager::NearPlane(), CCameraManager::FarPlane(),\n              CCameraManager::Aspect(), kInvalidUniqueId, false, 0) {}\n\nvoid CInterpolationCamera::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CInterpolationCamera::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) {\n  CGameCamera::AcceptScriptMsg(msg, sender, mgr);\n}\n\nvoid CInterpolationCamera::ProcessInput(const CFinalInput& input, CStateManager& mgr) {\n  // Empty\n}\n\nvoid CInterpolationCamera::Render(CStateManager& mgr) {\n  // Empty\n}\n\nvoid CInterpolationCamera::Reset(const zeus::CTransform&, CStateManager& mgr) {\n  // Empty\n}\n\nvoid CInterpolationCamera::Think(float dt, CStateManager& mgr) {\n  if (!GetActive())\n    return;\n\n  x15c_currentFov = mgr.GetCameraManager()->GetBallCamera()->GetFov();\n  x170_24_perspDirty = true;\n  if (mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Unmorphing)\n    DeactivateInterpCamera(mgr);\n\n  x18c_time += dt;\n  if (x18c_time > x190_maxTime)\n    x18c_time = x190_maxTime;\n\n  zeus::CTransform xf = GetTransform();\n\n  if (TCastToConstPtr<CGameCamera> cam = mgr.GetObjectById(x188_targetId)) {\n    zeus::CVector3f targetOrigin = cam->GetTranslation();\n    zeus::CVector3f ballLookPos = mgr.GetCameraManager()->GetBallCamera()->GetLookPos();\n    if (mgr.GetCameraManager()->GetBallCamera()->GetBehaviour() == CBallCamera::EBallCameraBehaviour::SpindleCamera) {\n      if (TCastToConstPtr<CScriptSpindleCamera> spindle =\n              mgr.GetObjectById(mgr.GetCameraManager()->GetSpindleCameraId())) {\n        float mag = (mgr.GetPlayer().GetTranslation() - spindle->GetTranslation()).magnitude();\n        ballLookPos = spindle->GetTranslation() + (mag * spindle->GetTransform().frontVector());\n      }\n    }\n    bool deactivate = false;\n\n    if (x1d8_24_sinusoidal)\n      deactivate = InterpolateSinusoidal(xf, targetOrigin, ballLookPos, x190_maxTime, x18c_time);\n    else\n      deactivate = InterpolateWithDistance(xf, targetOrigin, ballLookPos, x1d0_positionSpeed, x1d4_rotationSpeed, dt,\n                                           x190_maxTime, x18c_time);\n\n    SetTransform(xf);\n    if (deactivate)\n      DeactivateInterpCamera(mgr);\n  } else\n    DeactivateInterpCamera(mgr);\n}\n\nvoid CInterpolationCamera::SetInterpolation(const zeus::CTransform& xf, const zeus::CVector3f& lookPos, float maxTime,\n                                            float positionSpeed, float rotationSpeed, TUniqueId camId, bool sinusoidal,\n                                            CStateManager& mgr) {\n  SetActive(true);\n  SetTransform(xf);\n  x1c4_lookPos = lookPos;\n  x188_targetId = camId;\n  x1d8_24_sinusoidal = sinusoidal;\n  x190_maxTime = maxTime;\n  x1d0_positionSpeed = positionSpeed;\n  x1d4_rotationSpeed = rotationSpeed;\n  x1dc_closeInAngle = 2.f * M_PIF;\n  x18c_time = 0.f;\n\n  if (TCastToConstPtr<CGameCamera> cam = (mgr.GetObjectById(camId))) {\n    x15c_currentFov = cam->GetFov();\n    x170_24_perspDirty = true;\n  }\n}\n\nvoid CInterpolationCamera::DeactivateInterpCamera(CStateManager& mgr) {\n  SetActive(false);\n  if (!mgr.GetCameraManager()->ShouldBypassInterpolation())\n    mgr.GetCameraManager()->SetCurrentCameraId(x188_targetId, mgr);\n}\n\nbool CInterpolationCamera::InterpolateSinusoidal(zeus::CTransform& xf, const zeus::CVector3f& targetOrigin,\n                                                 const zeus::CVector3f& lookPos, float maxTime, float curTime) {\n  if (curTime > maxTime)\n    curTime = maxTime;\n\n  float t = zeus::clamp(-1.f, curTime / maxTime, 1.f);\n  float sinT = std::sin(t * (M_PIF / 2.f));\n  t *= 2.f;\n  zeus::CVector3f interpOrigin = (1.f - (t - sinT)) * (GetTranslation() - targetOrigin) + targetOrigin;\n  zeus::CVector3f lookDir = lookPos - interpOrigin;\n  if (lookDir.canBeNormalized())\n    lookDir.normalize();\n  else\n    lookDir = x34_transform.basis[1];\n  zeus::CVector3f lookDirFlat = lookDir;\n  lookDirFlat.z() = 0.f;\n  if (lookDirFlat.canBeNormalized()) {\n    t = zeus::clamp(-1.f, t, 1.f);\n    float lookProj = zeus::clamp(-1.f, x34_transform.basis[1].dot(lookDir), 1.f);\n    float ang = (1.f - t) * std::acos(lookProj);\n    if (ang > x1dc_closeInAngle)\n      ang = x1dc_closeInAngle;\n    else\n      x1dc_closeInAngle = ang;\n    zeus::CTransform lookXf = zeus::lookAt(interpOrigin, interpOrigin + lookDir);\n    if (std::fabs(lookProj) < 0.999999f) {\n      zeus::CVector3f xfLookDir = zeus::CQuaternion::lookAt(lookDir, x34_transform.basis[1], ang).transform(lookDir);\n      lookXf = zeus::lookAt(interpOrigin, interpOrigin + xfLookDir);\n    }\n    xf = lookXf;\n  } else {\n    xf = x34_transform;\n    xf.origin = interpOrigin;\n  }\n\n  return curTime >= maxTime;\n}\n\nbool CInterpolationCamera::InterpolateWithDistance(zeus::CTransform& xf, const zeus::CVector3f& targetOrigin,\n                                                   const zeus::CVector3f& lookPos, float positionSpeed,\n                                                   float rotationSpeed, float dt, float maxTime, float curTime) {\n  zeus::CVector3f interpOrigin = xf.origin;\n  zeus::CVector3f originDir = targetOrigin - interpOrigin;\n  float sdt = positionSpeed * dt;\n  bool ret = false;\n  bool positionFail = false;\n  if (originDir.canBeNormalized() && originDir.magnitude() > sdt) {\n    float lookDist = originDir.magnitude();\n    originDir.normalize();\n    float scale = zeus::clamp(-1.f, lookDist / 0.5f, 1.f) * sdt;\n    interpOrigin += originDir * scale;\n    if (lookDist < scale) {\n      interpOrigin = targetOrigin;\n      positionFail = true;\n    }\n  } else {\n    interpOrigin = targetOrigin;\n    positionFail = true;\n  }\n\n  zeus::CVector3f lookPosDelta = lookPos - x1c4_lookPos;\n  if (lookPosDelta.magnitude() > sdt) {\n    float deltaMag = lookPosDelta.magnitude();\n    lookPosDelta.normalize();\n    float scale = zeus::clamp(-1.f, deltaMag / 0.5f, 1.f) * sdt;\n    x1c4_lookPos += lookPosDelta * scale;\n  } else {\n    x1c4_lookPos = lookPos;\n  }\n\n  zeus::CVector3f lookDir = x1c4_lookPos - interpOrigin;\n  if (lookDir.canBeNormalized())\n    lookDir.normalize();\n  else\n    lookDir = x34_transform.basis[1];\n\n  float lookProj = zeus::clamp(-1.f, xf.basis[1].dot(lookDir), 1.f);\n  float ang = zeus::clamp(-1.f, std::acos(lookProj) / (M_PIF / 6.f), 1.f) * rotationSpeed * dt;\n\n  zeus::CVector3f lookDirFlat = lookDir;\n  lookDirFlat.z() = 0.f;\n  bool rotationFail = false;\n  if (lookDirFlat.canBeNormalized()) {\n    zeus::CTransform lookXf = zeus::lookAt(interpOrigin, interpOrigin + lookDir);\n    if (lookProj < 0.999999f)\n      lookXf = zeus::CQuaternion::lookAt(xf.basis[1], lookDir, ang).toTransform() * xf.getRotation();\n    else\n      rotationFail = true;\n    lookXf.origin = interpOrigin;\n    xf = lookXf;\n  } else {\n    xf = x34_transform;\n    xf.origin = interpOrigin;\n    rotationFail = true;\n  }\n\n  if (positionFail && rotationFail)\n    ret = true;\n\n  if (curTime >= maxTime && lookProj >= 0.9999f)\n    ret = true;\n\n  return ret;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Camera/CInterpolationCamera.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Camera/CGameCamera.hpp\"\n\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\n\nclass CInterpolationCamera : public CGameCamera {\n  TUniqueId x188_targetId = kInvalidUniqueId;\n  float x18c_time = 0.f;\n  float x190_maxTime = 0.f;\n  zeus::CTransform x194_;\n  zeus::CVector3f x1c4_lookPos;\n  float x1d0_positionSpeed = 0.f;\n  float x1d4_rotationSpeed = 0.f;\n  float x1d8_ = 0.f;\n  bool x1d8_24_sinusoidal : 1 = false;\n  float x1dc_closeInAngle = M_PIF * 2.f;\n\n  bool InterpolateSinusoidal(zeus::CTransform& xf, const zeus::CVector3f& targetOrigin, const zeus::CVector3f& lookPos,\n                             float maxTime, float curTime);\n  bool InterpolateWithDistance(zeus::CTransform& xf, const zeus::CVector3f& targetOrigin,\n                               const zeus::CVector3f& lookPos, float positionSpeed, float rotationSpeed, float dt,\n                               float maxTime, float curTime);\n\npublic:\n  DEFINE_ENTITY\n  explicit CInterpolationCamera(TUniqueId uid, const zeus::CTransform& xf);\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void ProcessInput(const CFinalInput&, CStateManager& mgr) override;\n  void Render(CStateManager&) override;\n  void Reset(const zeus::CTransform&, CStateManager& mgr) override;\n  void Think(float, CStateManager&) override;\n  void SetInterpolation(const zeus::CTransform& xf, const zeus::CVector3f& lookPos, float maxTime, float positionSpeed,\n                        float rotationSpeed, TUniqueId camId, bool sinusoidal, CStateManager& mgr);\n  void DeactivateInterpCamera(CStateManager&);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Camera/CMakeLists.txt",
    "content": "set(CAMERA_SOURCES\n        CCameraManager.hpp CCameraManager.cpp\n        CGameCamera.hpp CGameCamera.cpp\n        CFirstPersonCamera.hpp CFirstPersonCamera.cpp\n        CBallCamera.hpp CBallCamera.cpp\n        CInterpolationCamera.hpp CInterpolationCamera.cpp\n        CPathCamera.hpp CPathCamera.cpp\n        CCinematicCamera.hpp CCinematicCamera.cpp\n        CCameraShakeData.hpp CCameraShakeData.cpp\n        CCameraFilter.hpp CCameraFilter.cpp\n        CCameraSpline.hpp CCameraSpline.cpp)\n\nruntime_add_list(Camera CAMERA_SOURCES)\n"
  },
  {
    "path": "Runtime/Camera/CPathCamera.cpp",
    "content": "#include \"Runtime/Camera/CPathCamera.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Camera/CBallCamera.hpp\"\n#include \"Runtime/Camera/CCameraManager.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CScriptCameraHint.hpp\"\n#include \"Runtime/World/CScriptDoor.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCPathCamera::CPathCamera(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                         bool active, float lengthExtent, float filterMag, float filterProportion, float minEaseDist,\n                         float maxEaseDist, u32 flags, EInitialSplinePosition initPos)\n: CGameCamera(uid, active, name, info, xf, CCameraManager::ThirdPersonFOV(), CCameraManager::NearPlane(),\n              CCameraManager::FarPlane(), CCameraManager::Aspect(), kInvalidUniqueId, false, 0)\n, x188_spline(flags & 1)\n, x1dc_lengthExtent(lengthExtent)\n, x1e0_filterMag(filterMag)\n, x1e4_filterProportion(filterProportion)\n, x1e8_initPos(initPos)\n, x1ec_flags(flags)\n, x1f0_minEaseDist(minEaseDist)\n, x1f4_maxEaseDist(maxEaseDist) {}\n\nvoid CPathCamera::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CPathCamera::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  CGameCamera::AcceptScriptMsg(msg, uid, mgr);\n\n  if (GetActive() && msg == EScriptObjectMessage::InitializedInArea)\n    x188_spline.Initialize(GetUniqueId(), GetConnectionList(), mgr);\n}\n\nvoid CPathCamera::Think(float dt, CStateManager& mgr) {\n  if (!GetActive())\n    return;\n\n  if (mgr.GetCameraManager()->GetPathCameraId() != GetUniqueId())\n    return;\n\n  if (x188_spline.GetSize() <= 0)\n    return;\n\n  zeus::CTransform xf = GetTransform();\n  zeus::CVector3f ballLook = mgr.GetCameraManager()->GetBallCamera()->GetLookPos();\n  if ((x1ec_flags & 0x10)) {\n    if (const CScriptCameraHint* hint = mgr.GetCameraManager()->GetCameraHint(mgr))\n      ballLook.z() = hint->GetTranslation().z();\n  }\n\n  if (!mgr.GetPlayer().GetVelocity().canBeNormalized() && (ballLook - GetTranslation()).canBeNormalized()) {\n    if (x1ec_flags & 4)\n      SetTransform(x188_spline.GetInterpolatedSplinePointByLength(x1d4_pos));\n    else\n      SetTransform(zeus::lookAt(GetTranslation(), ballLook));\n    return;\n  }\n\n  xf = MoveAlongSpline(dt, mgr);\n  SetTranslation(xf.origin);\n\n  if (x1ec_flags & 0x20)\n    ClampToClosedDoor(mgr);\n\n  zeus::CVector3f tmp = ballLook - GetTranslation();\n  tmp.z() = 0.f;\n  if (tmp.canBeNormalized())\n    SetTransform(zeus::lookAt(GetTranslation(), ballLook));\n\n  if (x1ec_flags & 4)\n    SetTransform(xf);\n}\n\nvoid CPathCamera::ProcessInput(const CFinalInput&, CStateManager& mgr) {\n  // Empty\n}\n\nconstexpr CMaterialFilter kLineOfSightFilter =\n    CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {EMaterialTypes::ProjectilePassthrough});\n\nvoid CPathCamera::Reset(const zeus::CTransform&, CStateManager& mgr) {\n  CPlayer& player = mgr.GetPlayer();\n  zeus::CVector3f playerPt =\n      player.GetTranslation() + zeus::CVector3f(0.f, 0.f, g_tweakPlayer->GetPlayerBallHalfExtent());\n  float closestLength = x188_spline.FindClosestLengthOnSpline(0.f, playerPt);\n\n  float negLength = std::max(0.f, closestLength - x1dc_lengthExtent);\n  zeus::CVector3f negPoint = x188_spline.GetInterpolatedSplinePointByLength(negLength).origin;\n\n  float posLength = std::min(x188_spline.GetLength(), closestLength + x1dc_lengthExtent);\n  zeus::CVector3f posPoint = x188_spline.GetInterpolatedSplinePointByLength(posLength).origin;\n\n  zeus::CTransform camXf = mgr.GetCameraManager()->GetBallCamera()->GetTransform();\n  if (player.GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Morphed)\n    camXf = mgr.GetCameraManager()->GetCurrentCameraTransform(mgr);\n\n  bool neg = false;\n  if (x1e8_initPos == EInitialSplinePosition::BallCamBasis) {\n    zeus::CVector3f tmp = playerPt - negPoint;\n    if (tmp.canBeNormalized()) {\n      if (tmp.normalized().dot(camXf.basis[1]) > 0.f)\n        neg = true;\n    }\n  } else {\n    neg = x1e8_initPos == EInitialSplinePosition::Negative;\n  }\n\n#if 0\n    zeus::CVector3f camToSpline = splinePt - camXf.origin;\n    mgr.RayStaticIntersection(camXf.origin, camToSpline.normalized(), camToSpline.magnitude(), kLineOfSightFilter);\n    zeus::CVector3f camToSpline2 = splinePt2 - camXf.origin;\n    mgr.RayStaticIntersection(camXf.origin, camToSpline2.normalized(), camToSpline2.magnitude(), kLineOfSightFilter);\n#endif\n\n  zeus::CVector3f viewPoint;\n  if (neg) {\n    x1d4_pos = negLength;\n    viewPoint = negPoint;\n  } else {\n    x1d4_pos = posLength;\n    viewPoint = posPoint;\n  }\n\n  if (x1e8_initPos == EInitialSplinePosition::ClampBasis) {\n    if (x188_spline.ClampLength(playerPt, false, kLineOfSightFilter, mgr) <= negLength) {\n      x1d4_pos = negLength;\n      viewPoint = negPoint;\n    } else {\n      x1d4_pos = posLength;\n      viewPoint = posPoint;\n    }\n  }\n\n  SetTransform(zeus::lookAt(viewPoint, mgr.GetCameraManager()->GetBallCamera()->GetFixedLookPos()));\n}\n\nzeus::CTransform CPathCamera::MoveAlongSpline(float t, CStateManager& mgr) {\n  zeus::CTransform ret = x34_transform;\n  x1d8_time = x188_spline.FindClosestLengthOnSpline(x1d8_time, mgr.GetPlayer().GetTranslation());\n  float f30 = x1dc_lengthExtent;\n  if (x1ec_flags & 0x8) {\n    zeus::CVector3f splineToPlayer =\n        mgr.GetPlayer().GetTranslation() - x188_spline.GetInterpolatedSplinePointByLength(x1d8_time).origin;\n    float distToPlayer = 0.f;\n    splineToPlayer.z() = 0.f;\n    if (splineToPlayer.canBeNormalized())\n      distToPlayer = splineToPlayer.magnitude();\n    float easedDistT = (distToPlayer - x1f0_minEaseDist) / (x1f4_maxEaseDist - x1f0_minEaseDist);\n    f30 *= 1.f - std::sin(zeus::degToRad(zeus::clamp(0.f, easedDistT, 1.f) * 90.f));\n  }\n\n  float newPos;\n  if (x188_spline.IsClosedLoop()) {\n    float lenA = x188_spline.ValidateLength(x1d8_time + f30);\n    newPos = x188_spline.ValidateLength(x1d8_time - f30);\n    float disp = std::fabs(x1d4_pos - x1d8_time);\n    float remLen = x188_spline.GetLength() - disp;\n    if (x1d4_pos > x1d8_time) {\n      if (disp <= remLen)\n        newPos = lenA;\n    } else {\n      if (disp > remLen)\n        newPos = lenA;\n    }\n  } else {\n    if (x1d4_pos > x1d8_time)\n      newPos = x188_spline.ValidateLength(x1d8_time + f30);\n    else\n      newPos = x188_spline.ValidateLength(x1d8_time - f30);\n  }\n\n  if (x1ec_flags & 0x2) {\n    x1d4_pos = newPos;\n    ret = x188_spline.GetInterpolatedSplinePointByLength(x1d4_pos);\n  } else {\n    if (x188_spline.IsClosedLoop()) {\n      float absDelta = std::fabs(newPos - x1d4_pos);\n      absDelta = std::min(absDelta, x188_spline.GetLength() - absDelta);\n      float tBias = zeus::clamp(-1.f, absDelta / x1e4_filterProportion, 1.f) * x1e0_filterMag * t;\n      float tmpAbs = std::fabs(x1d4_pos - newPos);\n      float absDelta2 = x188_spline.GetLength() - tmpAbs;\n      if (x1d4_pos > newPos) {\n        if (tmpAbs <= absDelta2)\n          tBias *= -1.f;\n      } else {\n        if (tmpAbs > absDelta2)\n          tBias *= -1.f;\n      }\n      x1d4_pos = x188_spline.ValidateLength(x1d4_pos + tBias);\n    } else {\n      x1d4_pos = x188_spline.ValidateLength(\n          zeus::clamp(-1.f, (newPos - x1d4_pos) / x1e4_filterProportion, 1.f) * x1e0_filterMag * t + x1d4_pos);\n    }\n    ret = x188_spline.GetInterpolatedSplinePointByLength(x1d4_pos);\n  }\n\n  return ret;\n}\n\nvoid CPathCamera::ClampToClosedDoor(CStateManager& mgr) {\n  if (TCastToConstPtr<CScriptDoor> door =\n          mgr.GetObjectById(mgr.GetCameraManager()->GetBallCamera()->GetTooCloseActorId())) {\n    if (!door->IsOpen() && CBallCamera::IsBallNearDoor(GetTranslation(), mgr)) {\n      x1d4_pos = (x1d4_pos > x1d8_time) ? x1d8_time - x1dc_lengthExtent : x1d8_time + x1dc_lengthExtent;\n      SetTranslation(x188_spline.GetInterpolatedSplinePointByLength(x1d4_pos).origin);\n    }\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Camera/CPathCamera.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Camera/CCameraSpline.hpp\"\n#include \"Runtime/Camera/CGameCamera.hpp\"\n\nnamespace metaforce {\n\nclass CPathCamera : public CGameCamera {\npublic:\n  enum class EInitialSplinePosition { BallCamBasis, Negative, Positive, ClampBasis };\n\nprivate:\n  CCameraSpline x188_spline;\n  float x1d4_pos = 0.f;\n  float x1d8_time = 0.f;\n  float x1dc_lengthExtent;\n  float x1e0_filterMag;\n  float x1e4_filterProportion;\n  EInitialSplinePosition x1e8_initPos;\n  u32 x1ec_flags;\n  float x1f0_minEaseDist;\n  float x1f4_maxEaseDist;\n\npublic:\n  DEFINE_ENTITY\n  CPathCamera(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf, bool active,\n              float lengthExtent, float filterMag, float filterProportion, float minEaseDist, float maxEaseDist,\n              u32 flags, EInitialSplinePosition initPos);\n\n  void Accept(IVisitor&) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void Think(float, CStateManager&) override;\n  void Render(CStateManager&) override {}\n  void ProcessInput(const CFinalInput&, CStateManager& mgr) override;\n  void Reset(const zeus::CTransform&, CStateManager& mgr) override;\n  zeus::CTransform MoveAlongSpline(float, CStateManager&);\n  void ClampToClosedDoor(CStateManager&);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CActorLights.cpp",
    "content": "#include \"Runtime/Character/CActorLights.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Camera/CFirstPersonCamera.hpp\"\n#include \"Runtime/Collision/CGameCollision.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Graphics/CModel.hpp\"\n#include \"Runtime/World/CExplosion.hpp\"\n#include \"Runtime/World/CGameArea.hpp\"\n#include \"Runtime/World/CGameLight.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\ns32 CActorLights::sFrameSchedulerCount = 0;\nCActorLights::CActorLights(u32 areaUpdateFramePeriod, const zeus::CVector3f& actorPosBias, int maxDynamicLights,\n                           int maxAreaLights, bool ambientChannelOverflow, bool layer2, bool disableWorldLights,\n                           float positionUpdateThreshold)\n: x298_28_inArea(!disableWorldLights && maxAreaLights > 0)\n, x298_29_ambienceGenerated(ambientChannelOverflow)\n, x298_30_layer2(layer2)\n, x298_31_disableWorldLights(disableWorldLights)\n, x2a8_areaUpdateFramePeriod(areaUpdateFramePeriod)\n, x2ac_actorPosBias(actorPosBias)\n, x2b8_maxAreaLights(maxAreaLights)\n, x2bc_maxDynamicLights(maxDynamicLights)\n, x2cc_actorPositionDeltaUpdateThreshold(positionUpdateThreshold * positionUpdateThreshold) {\n  sFrameSchedulerCount++;\n  sFrameSchedulerCount &= 7;\n}\n\nvoid CActorLights::BuildConstantAmbientLighting() {\n  x299_26_ambientOnly = true;\n  x298_24_dirty = true;\n  x29c_shadowLightArrIdx = -1;\n  x2a0_shadowLightIdx = -1;\n}\n\nvoid CActorLights::BuildConstantAmbientLighting(const zeus::CColor& color) {\n  x299_26_ambientOnly = false;\n  x288_ambientColor = color;\n  x294_aid = kInvalidAreaId;\n  x298_24_dirty = true;\n  x298_26_hasAreaLights = true;\n  x29c_shadowLightArrIdx = -1;\n  x2a0_shadowLightIdx = -1;\n}\n\nvoid CActorLights::BuildFakeLightList(const std::vector<CLight>& lights, const zeus::CColor& color) {\n  BuildConstantAmbientLighting(color);\n  x0_areaLights.clear();\n  x144_dynamicLights = lights;\n}\n\nvoid CActorLights::BuildFaceLightList(const CStateManager& mgr, const CGameArea& area, const zeus::CAABox& aabb) {\n  zeus::CTransform fpTransform = mgr.GetCameraManager()->GetFirstPersonCamera()->GetTransform();\n  x298_26_hasAreaLights = true;\n  x288_ambientColor = zeus::skBlack;\n  x144_dynamicLights.clear();\n  zeus::CColor accumColor = zeus::skBlack;\n  for (CEntity* light : mgr.GetLightObjectList()) {\n    if (!light || !light->GetActive())\n      continue;\n    CGameLight* castLight = static_cast<CGameLight*>(light);\n    if (TCastToConstPtr<CExplosion> explosion = mgr.GetObjectById(castLight->GetParentId())) {\n      CLight originalLight = castLight->GetLight();\n      CLight explosionLight = originalLight;\n      explosionLight.SetAttenuation(\n          explosionLight.GetAttenuationConstant() * g_tweakGui->GetExplosionLightFalloffMultConstant(),\n          explosionLight.GetAttenuationLinear() * g_tweakGui->GetExplosionLightFalloffMultLinear(),\n          explosionLight.GetAttenuationQuadratic() * g_tweakGui->GetExplosionLightFalloffMultQuadratic());\n      zeus::CVector3f camToExplo = explosion->GetTranslation() - fpTransform.origin;\n      if (fpTransform.transposeRotate(camToExplo).dot(zeus::skForward) >= 0.f) {\n        camToExplo.y() = -camToExplo.y() + ITweakGui::FaceReflectionDistanceDebugValueToActualValue(\n                                               g_tweakGui->GetFaceReflectionDistance());\n        camToExplo.z() = -camToExplo.z() +\n                         ITweakGui::FaceReflectionHeightDebugValueToActualValue(g_tweakGui->GetFaceReflectionHeight());\n        explosionLight.SetPosition(fpTransform * camToExplo);\n        zeus::CSphere sphere(originalLight.GetPosition(), originalLight.GetRadius());\n        if (aabb.intersects(sphere)) {\n          accumColor += explosionLight.GetNormalIndependentLightingAtPoint(fpTransform.origin);\n          if (originalLight.GetIntensity() > FLT_EPSILON && originalLight.GetRadius() > FLT_EPSILON)\n            x144_dynamicLights.push_back(explosionLight);\n        }\n      }\n    }\n  }\n\n  float greyscale = accumColor.rgbDot(zeus::CColor(0.3f, 0.6f, 0.1f));\n  if (greyscale < 0.012f)\n    x144_dynamicLights.clear();\n\n  if (greyscale > 0.03f) {\n    float attMul = 1.f / (0.03f / greyscale);\n    for (CLight& light : x144_dynamicLights)\n      light.SetAttenuation(light.GetAttenuationConstant() * attMul, light.GetAttenuationLinear() * attMul,\n                           light.GetAttenuationQuadratic() * attMul);\n  }\n}\n\nstruct SLightValue {\n  u32 x0_areaLightIdx;\n  zeus::CColor x4_color;\n  float x10_colorMag;\n  float x14_accumulatedMag = 0.f;\n  EPVSVisSetState x18_visiblity;\n};\n\nvoid CActorLights::MergeOverflowLight(CLight& out, zeus::CColor& color, const CLight& in, float colorMag) {\n  color += in.GetColor() * colorMag;\n  out.SetAngleAttenuation(in.GetAngleAttenuationConstant() * colorMag + out.GetAngleAttenuationConstant(),\n                          in.GetAngleAttenuationLinear() * colorMag + out.GetAngleAttenuationLinear(),\n                          in.GetAngleAttenuationQuadratic() * colorMag + out.GetAngleAttenuationQuadratic());\n  out.SetAttenuation(in.GetAttenuationConstant() * colorMag + out.GetAttenuationConstant(),\n                     in.GetAttenuationLinear() * colorMag + out.GetAttenuationLinear(),\n                     in.GetAttenuationQuadratic() * colorMag + out.GetAttenuationQuadratic());\n  out.SetPosition(in.GetPosition() * colorMag + out.GetPosition());\n  out.SetDirection(in.GetDirection() * colorMag + out.GetDirection());\n}\n\nvoid CActorLights::AddOverflowToLights(const CLight& light, const zeus::CColor& color, float mag) {\n  if (mag < 0.001f || x2b8_maxAreaLights < 1)\n    return;\n\n  mag = 1.f / mag;\n  zeus::CColor useColor = color * mag;\n  useColor.a() = 1.f;\n  x0_areaLights.push_back(\n      CLight::BuildCustom(light.GetPosition() * mag, light.GetDirection() * mag, useColor,\n                          light.GetAttenuationConstant() * mag, light.GetAttenuationLinear() * mag,\n                          light.GetAttenuationQuadratic() * mag, light.GetAngleAttenuationConstant() * mag,\n                          light.GetAngleAttenuationLinear() * mag, light.GetAngleAttenuationQuadratic() * mag));\n}\n\nvoid CActorLights::MoveAmbienceToLights(const zeus::CColor& color) {\n  if (x298_29_ambienceGenerated || x0_areaLights.empty()) {\n    x288_ambientColor += color * 0.333333f;\n    x288_ambientColor.a() = 1.f;\n    return;\n  }\n\n  zeus::CColor useColor = x0_areaLights[0].GetColor() + color;\n  float maxComponent = std::max(useColor.r(), std::max(useColor.g(), useColor.b()));\n  if (maxComponent > FLT_EPSILON)\n    useColor *= (1.f / maxComponent);\n  useColor.a() = 1.f;\n  x0_areaLights[0].SetColor(useColor);\n}\n\nvoid CActorLights::MultiplyLightingLevels(float level) {\n  x288_ambientColor *= level;\n  for (CLight& light : x0_areaLights) {\n    zeus::CColor color = light.GetColor();\n    color *= level;\n    color.a() = 1.f;\n    light.SetColor(color);\n  }\n}\n\nvoid CActorLights::UpdateBrightLight() {\n  if (x2dc_brightLightLag > 0 && x299_24_inBrightLight)\n    --x2dc_brightLightLag;\n  else if (x2dc_brightLightLag < 15 && !x299_24_inBrightLight)\n    ++x2dc_brightLightLag;\n  x299_25_useBrightLightLag = true;\n}\n\nbool CActorLights::BuildAreaLightList(const CStateManager& mgr, const CGameArea& area, const zeus::CAABox& aabb) {\n  const std::vector<CWorldLight>& lightList =\n      x298_30_layer2 ? area.GetPostConstructed()->x80_lightsB : area.GetPostConstructed()->x60_lightsA;\n  const std::vector<CLight>& gfxLightList =\n      x298_30_layer2 ? area.GetPostConstructed()->x90_gfxLightsB : area.GetPostConstructed()->x70_gfxLightsA;\n  float worldLightingLevel = area.GetPostConstructed()->x1128_worldLightingLevel;\n  x298_26_hasAreaLights = lightList.size() != 0;\n  if (!x298_26_hasAreaLights || !x298_28_inArea) {\n    /* World lights disabled */\n    if (x298_31_disableWorldLights)\n      x2d4_worldLightingLevel = worldLightingLevel;\n    x29c_shadowLightArrIdx = -1;\n    return true;\n  }\n\n  zeus::CVector3f vec;\n  if (!x298_24_dirty && x294_aid == area.GetAreaId()) {\n    /* Early return if not ready for update */\n    if (mgr.GetInputFrameIdx() - x2a4_lastUpdateFrame < x2a8_areaUpdateFramePeriod)\n      return false;\n    x2a4_lastUpdateFrame = mgr.GetInputFrameIdx();\n    vec = aabb.center() + x2ac_actorPosBias;\n    if (x2d4_worldLightingLevel == worldLightingLevel)\n      if ((x2c0_lastActorPos - vec).magSquared() < x2cc_actorPositionDeltaUpdateThreshold)\n        return false;\n    x2c0_lastActorPos = vec;\n  } else {\n    if (x294_aid != area.GetAreaId())\n      x2d8_brightLightIdx = -1;\n    x2a4_lastUpdateFrame = sFrameSchedulerCount + mgr.GetInputFrameIdx();\n    vec = aabb.center() + x2ac_actorPosBias;\n    x2c0_lastActorPos = vec;\n  }\n\n  /* Reset lighting state */\n  x2d4_worldLightingLevel = worldLightingLevel;\n  x298_24_dirty = false;\n  x294_aid = area.GetAreaId();\n  x29c_shadowLightArrIdx = -1;\n  x288_ambientColor = zeus::skClear;\n\n  /* Find candidate lights via PVS */\n  bool use2ndLayer;\n  if (x298_30_layer2) {\n    if (const CPVSAreaSet* pvs = area.GetAreaVisSet())\n      use2ndLayer = pvs->Has2ndLayerLights();\n    else\n      use2ndLayer = true;\n  } else {\n    use2ndLayer = false;\n  }\n\n  CPVSVisSet sets[3];\n  sets[0].Reset(EPVSVisSetState::OutOfBounds);\n  sets[1].Reset(EPVSVisSetState::OutOfBounds);\n  sets[2].Reset(EPVSVisSetState::OutOfBounds);\n\n  if (const CPVSAreaSet* pvs = area.GetAreaVisSet()) {\n    zeus::CVector3f localVec = area.GetInverseTransform() * vec;\n    sets[0].SetTestPoint(pvs->GetVisOctree(), localVec);\n    localVec = area.GetInverseTransform() * aabb.max;\n    sets[1].SetTestPoint(pvs->GetVisOctree(), localVec);\n    localVec = area.GetInverseTransform() * aabb.min;\n    sets[2].SetTestPoint(pvs->GetVisOctree(), localVec);\n  }\n\n  std::vector<SLightValue> valList;\n  valList.reserve(lightList.size());\n\n  auto lightIt = lightList.begin();\n  int lightIdx = 0;\n  for (const CLight& light : gfxLightList) {\n    if (light.GetType() == ELightType::LocalAmbient) {\n      /* Take ambient here */\n      x288_ambientColor = light.GetNormalIndependentLightingAtPoint(vec);\n    } else {\n      EPVSVisSetState visible = EPVSVisSetState::OutOfBounds;\n      if (area.GetAreaVisSet() && lightIt->DoesCastShadows()) {\n        u32 pvsIdx = use2ndLayer ? area.Get2ndPVSLightFeature(lightIdx) : area.Get1stPVSLightFeature(lightIdx);\n        visible = sets[0].GetVisible(pvsIdx);\n        if (visible != EPVSVisSetState::OutOfBounds)\n          visible = std::max(visible, sets[1].GetVisible(pvsIdx));\n        if (visible != EPVSVisSetState::OutOfBounds)\n          visible = std::max(visible, sets[2].GetVisible(pvsIdx));\n      }\n      if (visible != EPVSVisSetState::EndOfTree) {\n        zeus::CSphere sphere(light.GetPosition(), light.GetRadius() * 2.f);\n        if (aabb.intersects(sphere)) {\n          /* Light passes as candidate */\n          SLightValue& value = valList.emplace_back();\n          value.x0_areaLightIdx = lightIdx;\n          value.x4_color = light.GetNormalIndependentLightingAtPoint(vec);\n          value.x4_color.a() = 0.f;\n          value.x10_colorMag = value.x4_color.magnitude();\n          value.x18_visiblity = visible;\n        }\n      }\n    }\n    ++lightIt;\n    ++lightIdx;\n  }\n\n  /* Sort lights most intense to least intense */\n  std::sort(valList.begin(), valList.end(),\n            [](const SLightValue& a, const SLightValue& b) { return a.x10_colorMag > b.x10_colorMag; });\n\n  if (x298_27_findShadowLight) {\n    /* Accumulate magnitudes up to most intense for shadow dynamic range check */\n    x288_ambientColor.a() = 0.f;\n    float mag = x288_ambientColor.magnitude();\n    for (auto it = valList.rbegin(); it != valList.rend(); ++it) {\n      mag += it->x10_colorMag;\n      it->x14_accumulatedMag = mag;\n    }\n  }\n\n  /* Ambient color for overflow area lights */\n  zeus::CColor overflowAmbColor = zeus::skClear;\n\n  /* Averaged light for overflow area lights */\n  CLight overflowLight =\n      CLight::BuildCustom(zeus::skZero3f, zeus::skZero3f, zeus::skBlack, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f);\n  zeus::CColor overflowLightColor = zeus::skClear;\n  float overflowMag = 0.f;\n\n  /* Max significant lights */\n  int maxAreaLights = !x298_29_ambienceGenerated ? x2b8_maxAreaLights - 1 : x2b8_maxAreaLights;\n  x0_areaLights.clear();\n\n  /* Filter for performing final light visibility test */\n  constexpr auto filter = CMaterialFilter::MakeIncludeExclude(\n      CMaterialList(EMaterialTypes::Solid),\n      CMaterialList(EMaterialTypes::Projectile, EMaterialTypes::ProjectilePassthrough, EMaterialTypes::SeeThrough));\n  u32 mostSigLightIdx = 0;\n\n  /* Narrowphase test candidates starting with most intense */\n  for (size_t i = 0; i < valList.size(); ++i) {\n    const SLightValue& value = valList[i];\n    const CLight& light = gfxLightList[value.x0_areaLightIdx];\n    if (x0_areaLights.size() < maxAreaLights) {\n      /* Significant light */\n      bool actorToLightContact = true;\n      bool castShadows = lightList[value.x0_areaLightIdx].DoesCastShadows() && x298_25_castShadows;\n      bool outOfBounds = area.GetAreaVisSet() && value.x18_visiblity == EPVSVisSetState::OutOfBounds;\n      if (castShadows) {\n        /* Process shadow cast */\n        zeus::CVector3f delta = light.GetPosition() - vec;\n        float deltaMag = delta.magnitude();\n        bool useShadow = false;\n        if (x298_27_findShadowLight && x29c_shadowLightArrIdx == -1 && light.GetType() != ELightType::LocalAmbient &&\n            deltaMag > 2.f && !aabb.pointInside(light.GetPosition())) {\n          /* Perform shadow dynamic range check */\n          if (!x0_areaLights.size() ||\n              (x0_areaLights.size() == 1 && value.x10_colorMag / valList[mostSigLightIdx].x10_colorMag > 0.5f)) {\n            useShadow = value.x10_colorMag / value.x14_accumulatedMag >\n                        x2d0_shadowDynamicRangeThreshold / (1.f + x2d0_shadowDynamicRangeThreshold);\n          }\n        }\n        if (useShadow) {\n          /* Note shadow light */\n          x29c_shadowLightArrIdx = x0_areaLights.size();\n          x2a0_shadowLightIdx = value.x0_areaLightIdx;\n        } else if (!outOfBounds) {\n          /* Note brightest light contact */\n          delta = delta * 1.f / deltaMag;\n          actorToLightContact = CGameCollision::RayStaticIntersectionArea(area, vec, delta, deltaMag, filter);\n          if (i == 0) {\n            x299_24_inBrightLight = actorToLightContact;\n            if (x2d8_brightLightIdx != value.x0_areaLightIdx) {\n              x2dc_brightLightLag = actorToLightContact ? 0 : 15;\n              x2d8_brightLightIdx = value.x0_areaLightIdx;\n            }\n            x299_25_useBrightLightLag = false;\n            actorToLightContact = true;\n          }\n        }\n      }\n      if (actorToLightContact) {\n        /* Add to final list */\n        if (x0_areaLights.size() == 0)\n          mostSigLightIdx = i;\n        x0_areaLights.push_back(light);\n      }\n    } else {\n      /* Overflow light */\n      if (!x298_29_ambienceGenerated && value.x10_colorMag > 0.001f) {\n        /* Average parameters into final light */\n        MergeOverflowLight(overflowLight, overflowLightColor, light, value.x10_colorMag);\n        overflowMag += value.x10_colorMag;\n      } else {\n        /* Average color into ambient channel */\n        overflowAmbColor += value.x4_color;\n      }\n    }\n  }\n\n  /* Finalize overflow lights */\n  if (!x298_29_ambienceGenerated)\n    AddOverflowToLights(overflowLight, overflowLightColor, overflowMag);\n  else\n    MoveAmbienceToLights(overflowAmbColor);\n\n  /* Clamp ambient color */\n  if (x288_ambientColor.r() > 1.f)\n    x288_ambientColor.r() = 1.f;\n  if (x288_ambientColor.g() > 1.f)\n    x288_ambientColor.g() = 1.f;\n  if (x288_ambientColor.b() > 1.f)\n    x288_ambientColor.b() = 1.f;\n  x288_ambientColor.a() = 1.f;\n\n  /* Multiply down lighting with world fader level */\n  if (worldLightingLevel < 1.f)\n    MultiplyLightingLevels(worldLightingLevel);\n\n  return true;\n}\n\nvoid CActorLights::BuildDynamicLightList(const CStateManager& mgr, const zeus::CAABox& aabb) {\n  UpdateBrightLight();\n  x299_26_ambientOnly = false;\n  x144_dynamicLights.clear();\n\n  if (!x29a_findNearestDynamicLights) {\n    for (const CLight& light : mgr.GetDynamicLightList()) {\n      zeus::CSphere sphere(light.GetPosition(), light.GetRadius());\n      if (aabb.intersects(sphere))\n        x144_dynamicLights.push_back(light);\n      if (x144_dynamicLights.size() >= x2bc_maxDynamicLights)\n        break;\n    }\n  } else {\n    const CLight* addedLights[8] = {};\n    for (int i = 0; i < x2bc_maxDynamicLights && i < 8; ++i) {\n      float minRad = FLT_MAX;\n      for (const CLight& light : mgr.GetDynamicLightList()) {\n        zeus::CSphere sphere(light.GetPosition(), light.GetRadius());\n        float intRadius = aabb.intersectionRadius(sphere);\n        if (intRadius >= 0.f && intRadius < minRad) {\n          bool alreadyIn = false;\n          for (int j = 0; j < i; ++j) {\n            if (&light == addedLights[j]) {\n              alreadyIn = true;\n              break;\n            }\n          }\n          if (alreadyIn)\n            continue;\n          addedLights[i] = &light;\n          minRad = intRadius;\n        }\n      }\n      if (addedLights[i])\n        x144_dynamicLights.push_back(*addedLights[i]);\n      if (x144_dynamicLights.size() >= x2bc_maxDynamicLights)\n        break;\n    }\n  }\n}\n\nstd::vector<CLight> CActorLights::BuildLightVector() const {\n  std::vector<CLight> lights;\n\n  if (!x0_areaLights.empty()) {\n    if (x2dc_brightLightLag != 0 && x299_25_useBrightLightLag) {\n      CLight overrideLight = x0_areaLights[0];\n      overrideLight.SetColor(overrideLight.GetColor() * (1.f - x2dc_brightLightLag / 15.f));\n      lights.push_back(overrideLight);\n    } else {\n      lights.push_back(x0_areaLights[0]);\n    }\n\n    for (auto it = x0_areaLights.begin() + 1; it != x0_areaLights.end(); ++it) {\n      lights.push_back(*it);\n    }\n  }\n\n  for (const CLight& light : x144_dynamicLights) {\n    lights.push_back(light);\n  }\n\n  return lights;\n}\n\nvoid CActorLights::ActivateLights() const {\n  if (x298_28_inArea) {\n    if (!x298_26_hasAreaLights || x299_26_ambientOnly) {\n      g_Renderer->SetAmbientColor(zeus::skWhite);\n      CGraphics::DisableAllLights();\n      return;\n    }\n  }\n  auto ambient = x288_ambientColor;\n  ambient.a() = 1.f;\n  g_Renderer->SetAmbientColor(ambient);\n\n  const auto lights = BuildLightVector();\n  if (lights.empty()) {\n    CGraphics::DisableAllLights();\n  } else {\n    for (ERglLight idx = 0; const auto& item : lights) {\n      CGraphics::LoadLight(idx, item);\n      idx++;\n    }\n    // Sets n LSB to 1\n    CGraphics::SetLightState(static_cast<ERglLight>((1 << lights.size()) + 255));\n  }\n\n  if (x298_31_disableWorldLights) {\n    g_Renderer->SetAmbientColor(zeus::skBlack);\n    g_Renderer->SetGXRegister1Color({x2d4_worldLightingLevel});\n  }\n}\n\nvoid CActorLights::DisableAreaLights() {\n  x2b8_maxAreaLights = 0;\n  x298_26_hasAreaLights = false;\n  x298_28_inArea = false;\n}\n\nconst CLight& CActorLights::GetLight(u32 idx) const {\n  if (x298_28_inArea) {\n    if (idx < x0_areaLights.size())\n      return x0_areaLights[idx];\n    return x144_dynamicLights[idx - x0_areaLights.size()];\n  }\n  return x144_dynamicLights[idx];\n}\n\nu32 CActorLights::GetActiveLightCount() const {\n  if (x298_28_inArea)\n    return x0_areaLights.size() + x144_dynamicLights.size();\n  return x144_dynamicLights.size();\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CActorLights.hpp",
    "content": "#pragma once\n\n#include <vector>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Graphics/CLight.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CColor.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CBooModel;\nclass CGameArea;\nclass CStateManager;\n\nclass CActorLights {\n  static s32 sFrameSchedulerCount;\n  std::vector<CLight> x0_areaLights;\n  std::vector<CLight> x144_dynamicLights;\n  zeus::CColor x288_ambientColor = zeus::skBlack;\n  TAreaId x294_aid = kInvalidAreaId;\n  bool x298_24_dirty : 1 = true;\n  bool x298_25_castShadows : 1 = true;\n  bool x298_26_hasAreaLights : 1 = false;\n  bool x298_27_findShadowLight : 1 = false;\n  bool x298_28_inArea : 1;\n  bool x298_29_ambienceGenerated : 1;\n  bool x298_30_layer2 : 1;\n  bool x298_31_disableWorldLights : 1;\n  bool x299_24_inBrightLight : 1 = true;\n  bool x299_25_useBrightLightLag : 1 = false;\n  bool x299_26_ambientOnly : 1 = false;\n  bool x29a_findNearestDynamicLights = false;\n  s32 x29c_shadowLightArrIdx = -1;\n  s32 x2a0_shadowLightIdx = -1;\n  u32 x2a4_lastUpdateFrame = 0;\n  u32 x2a8_areaUpdateFramePeriod;\n  zeus::CVector3f x2ac_actorPosBias;\n  int x2b8_maxAreaLights;\n  int x2bc_maxDynamicLights;\n  zeus::CVector3f x2c0_lastActorPos;\n  float x2cc_actorPositionDeltaUpdateThreshold;\n  float x2d0_shadowDynamicRangeThreshold = 0.f;\n  float x2d4_worldLightingLevel = 1.f;\n  s32 x2d8_brightLightIdx = -1;\n  u32 x2dc_brightLightLag = 0;\n\n  static void MergeOverflowLight(CLight& out, zeus::CColor& color, const CLight& in, float colorMag);\n  void AddOverflowToLights(const CLight& light, const zeus::CColor& color, float mag);\n  void MoveAmbienceToLights(const zeus::CColor& color);\n  void MultiplyLightingLevels(float level);\n  void UpdateBrightLight();\n\npublic:\n  CActorLights(u32 areaUpdateFramePeriod, const zeus::CVector3f& actorPosBias, int maxDynamicLights, int maxAreaLights,\n               bool ambientChannelOverflow, bool layer2, bool disableWorldLights, float positionUpdateThreshold);\n\n  void BuildConstantAmbientLighting();\n  void BuildConstantAmbientLighting(const zeus::CColor& color);\n  void BuildFakeLightList(const std::vector<CLight>& lights, const zeus::CColor& color);\n  void BuildFaceLightList(const CStateManager& mgr, const CGameArea& area, const zeus::CAABox& aabb);\n  bool BuildAreaLightList(const CStateManager& mgr, const CGameArea& area, const zeus::CAABox& aabb);\n  void BuildDynamicLightList(const CStateManager& mgr, const zeus::CAABox& aabb);\n  std::vector<CLight> BuildLightVector() const;\n  void ActivateLights() const;\n  void SetCastShadows(bool v) { x298_25_castShadows = v; }\n  void SetHasAreaLights(bool v) { x298_26_hasAreaLights = v; }\n  void SetFindShadowLight(bool v) { x298_27_findShadowLight = v; }\n  void SetShadowDynamicRangeThreshold(float t) { x2d0_shadowDynamicRangeThreshold = t; }\n  void SetAmbienceGenerated(bool v) { x298_29_ambienceGenerated = v; }\n  void DisableAreaLights();\n  void SetMaxAreaLights(int l) { x2b8_maxAreaLights = l; }\n  void SetMaxDynamicLights(int l) { x2bc_maxDynamicLights = l; }\n  void SetFindNearestDynamicLights(bool v) { x29a_findNearestDynamicLights = v; }\n  void SetAmbientColor(const zeus::CColor& color) { x288_ambientColor = color; }\n  const zeus::CColor& GetAmbientColor() const { return x288_ambientColor; }\n  const CLight& GetLight(u32 idx) const;\n  u32 GetActiveLightCount() const;\n  int GetMaxAreaLights() const { return x2b8_maxAreaLights; }\n  const std::vector<CLight>& GetAreaLights() const { return x0_areaLights; }\n  const std::vector<CLight>& GetDynamicLights() const { return x144_dynamicLights; }\n  bool GetIsDirty() const { return x298_24_dirty; }\n  void SetDirty() { x298_24_dirty = true; }\n  bool HasShadowLight() const { return x29c_shadowLightArrIdx != -1; }\n  s32 GetShadowLightArrIndex() const { return x29c_shadowLightArrIdx; }\n  s32 GetShadowLightIndex() const { return x2a0_shadowLightIdx; }\n  u32 GetAreaUpdateFramePeriod() const { return x2a8_areaUpdateFramePeriod; }\n  void SetAreaUpdateFramePeriod(u32 p) { x2a8_areaUpdateFramePeriod = p; }\n  zeus::CVector3f GetActorPositionBias() const { return x2ac_actorPosBias; }\n  void SetActorPositionBias(const zeus::CVector3f& bias) { x2ac_actorPosBias = bias; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAdditiveAnimPlayback.cpp",
    "content": "#include \"Runtime/Character/CAdditiveAnimPlayback.hpp\"\n\n#include \"Runtime/Character/CAnimTreeNode.hpp\"\n#include \"Runtime/Character/CCharLayoutInfo.hpp\"\n#include \"Runtime/Character/CSegStatementSet.hpp\"\n\nnamespace metaforce {\n\nCAdditiveAnimPlayback::CAdditiveAnimPlayback(const std::weak_ptr<CAnimTreeNode>& anim, float weight, bool active,\n                                             const CAdditiveAnimationInfo& info, bool fadeOut)\n: x0_info(info), x8_anim(anim.lock()), xc_targetWeight(weight), x14_active(active) {\n  if (!active && fadeOut)\n    x20_needsFadeOut = true;\n}\n\nvoid CAdditiveAnimPlayback::AddToSegStatementSet(const CSegIdList& list, const CCharLayoutInfo& layout,\n                                                 CSegStatementSet& setOut) const {\n  CSegStatementSet stackSet;\n  x8_anim->VGetSegStatementSet(list, stackSet);\n  for (const CSegId& id : list.GetList()) {\n    CAnimPerSegmentData& data = stackSet[id];\n    data.x10_offset = layout.GetFromParentUnrotated(id);\n    data.x1c_hasOffset = true;\n  }\n  setOut.Add(list, layout, stackSet, x10_curWeight);\n}\n\nvoid CAdditiveAnimPlayback::Update(float dt) {\n  switch (x1c_phase) {\n  case EAdditivePlaybackPhase::FadingIn: {\n    float a = x0_info.GetFadeInDuration();\n    float b = x18_weightTimer + dt;\n    x18_weightTimer = std::min(b, a);\n    if (a > 0.f)\n      x10_curWeight = x18_weightTimer / a * xc_targetWeight;\n    else\n      x10_curWeight = xc_targetWeight;\n\n    if (std::fabs(x10_curWeight - xc_targetWeight) < 0.00001f)\n      x1c_phase = EAdditivePlaybackPhase::FadedIn;\n\n    break;\n  }\n  case EAdditivePlaybackPhase::FadingOut: {\n    float a = x18_weightTimer - dt;\n    x18_weightTimer = std::max(a, 0.f);\n    if (x0_info.GetFadeOutDuration() > 0.f)\n      x10_curWeight = x18_weightTimer / x0_info.GetFadeOutDuration() * xc_targetWeight;\n    else\n      x10_curWeight = 0.f;\n\n    if (std::fabs(x10_curWeight) < 0.00001f)\n      x1c_phase = EAdditivePlaybackPhase::FadedOut;\n    break;\n  }\n  default:\n    break;\n  }\n}\n\nvoid CAdditiveAnimPlayback::FadeOut() {\n  switch (x1c_phase) {\n  case EAdditivePlaybackPhase::FadedOut:\n  case EAdditivePlaybackPhase::FadedIn:\n    x18_weightTimer = x0_info.GetFadeOutDuration();\n    break;\n  case EAdditivePlaybackPhase::FadingIn:\n    x18_weightTimer = x18_weightTimer / x0_info.GetFadeInDuration() * x0_info.GetFadeOutDuration();\n    break;\n  default:\n    break;\n  }\n\n  if (x0_info.GetFadeOutDuration() > 0.f)\n    x1c_phase = EAdditivePlaybackPhase::FadingOut;\n  else\n    x1c_phase = EAdditivePlaybackPhase::FadedOut;\n  x10_curWeight = 0.f;\n}\n\nvoid CAdditiveAnimPlayback::SetWeight(float w) {\n  xc_targetWeight = w;\n  switch (x1c_phase) {\n  case EAdditivePlaybackPhase::FadingIn: {\n    if (x0_info.GetFadeInDuration() > 0.f)\n      x10_curWeight = x18_weightTimer / x0_info.GetFadeInDuration() * xc_targetWeight;\n    else\n      x10_curWeight = xc_targetWeight;\n    break;\n  }\n  case EAdditivePlaybackPhase::FadingOut: {\n    if (x0_info.GetFadeOutDuration() > 0.f)\n      x10_curWeight = x18_weightTimer / x0_info.GetFadeOutDuration() * xc_targetWeight;\n    else\n      x10_curWeight = xc_targetWeight;\n    break;\n  }\n  default:\n    x10_curWeight = xc_targetWeight;\n    break;\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAdditiveAnimPlayback.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Streams/CInputStream.hpp\"\n\nnamespace metaforce {\nclass CAdditiveAnimationInfo;\nclass CAnimTreeNode;\nclass CCharLayoutInfo;\nclass CSegIdList;\nclass CSegStatementSet;\n\nclass CAdditiveAnimationInfo {\n  float x0_fadeInDur = 0.f;\n  float x4_fadeOutDur = 0.f;\n\npublic:\n  void read(CInputStream& in) {\n    x0_fadeInDur = in.ReadFloat();\n    x4_fadeOutDur = in.ReadFloat();\n  }\n  CAdditiveAnimationInfo() = default;\n  explicit CAdditiveAnimationInfo(CInputStream& in) { read(in); }\n  float GetFadeInDuration() const { return x0_fadeInDur; }\n  float GetFadeOutDuration() const { return x4_fadeOutDur; }\n};\n\nenum class EAdditivePlaybackPhase { None, FadingIn, FadingOut, FadedIn, FadedOut };\n\nclass CAdditiveAnimPlayback {\n  CAdditiveAnimationInfo x0_info;\n  std::shared_ptr<CAnimTreeNode> x8_anim;\n  float xc_targetWeight;\n  float x10_curWeight = 0.f;\n  bool x14_active;\n  float x18_weightTimer = 0.f;\n  EAdditivePlaybackPhase x1c_phase = EAdditivePlaybackPhase::FadingIn;\n  bool x20_needsFadeOut = false;\n\npublic:\n  CAdditiveAnimPlayback(const std::weak_ptr<CAnimTreeNode>& anim, float weight, bool active,\n                        const CAdditiveAnimationInfo& info, bool fadeOut);\n\n  void AddToSegStatementSet(const CSegIdList& list, const CCharLayoutInfo&, CSegStatementSet&) const;\n  void Update(float dt);\n  void FadeOut();\n  void SetWeight(float w);\n  float GetTargetWeight() const { return xc_targetWeight; }\n  bool IsActive() const { return x14_active; }\n  void SetActive(bool active) { x14_active = active; }\n  const std::shared_ptr<CAnimTreeNode>& GetAnim() const { return x8_anim; }\n  std::shared_ptr<CAnimTreeNode>& GetAnim() { return x8_anim; }\n  EAdditivePlaybackPhase GetPhase() const { return x1c_phase; }\n  void SetNeedsFadeOut(bool b) { x20_needsFadeOut = b; }\n  bool NeedsFadeOut() const { return x20_needsFadeOut; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAdditiveBodyState.cpp",
    "content": "#include \"Runtime/Character/CAdditiveBodyState.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Character/CBodyController.hpp\"\n#include \"Runtime/Character/CAnimTreeNode.hpp\"\n#include \"Runtime/Character/CPASDatabase.hpp\"\n#include \"Runtime/Character/CPASAnimParmData.hpp\"\n\nnamespace metaforce {\n\nvoid CABSAim::Start(CBodyController& bc, CStateManager& mgr) {\n  // const CBCAdditiveAimCmd* cmd =\n  //    static_cast<const CBCAdditiveAimCmd*>(bc.GetCommandMgr().GetCmd(EBodyStateCmd::AdditiveAim));\n  const CPASAnimState* aimState = bc.GetPASDatabase().GetAnimState(pas::EAnimationState::AdditiveAim);\n\n  // Left, Right, Up, Down\n  for (size_t i = 0; i < x8_anims.size(); ++i) {\n    const CPASAnimParmData parms(pas::EAnimationState::AdditiveAim, CPASAnimParm::FromEnum(s32(i)));\n    const std::pair<float, s32> best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1);\n    x8_anims[i] = best.second;\n    x18_angles[i] = zeus::degToRad(aimState->GetAnimParmData(x8_anims[i], 1).GetReal32Value());\n  }\n\n  const CAnimData& animData = *bc.GetOwner().GetModelData()->GetAnimationData();\n  x28_hWeight = -animData.GetAdditiveAnimationWeight(x8_anims[0]);\n  x28_hWeight += animData.GetAdditiveAnimationWeight(x8_anims[1]);\n  x30_vWeight = -animData.GetAdditiveAnimationWeight(x8_anims[3]);\n  x30_vWeight += animData.GetAdditiveAnimationWeight(x8_anims[2]);\n\n  x4_needsIdle = false;\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::AdditiveIdle))\n    x4_needsIdle = true;\n}\n\npas::EAnimationState CABSAim::GetBodyStateTransition(float dt, CBodyController& bc) const {\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::AdditiveReaction))\n    return pas::EAnimationState::AdditiveReaction;\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::AdditiveFlinch))\n    return pas::EAnimationState::AdditiveFlinch;\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::AdditiveIdle) || x4_needsIdle)\n    return pas::EAnimationState::AdditiveIdle;\n  return pas::EAnimationState::Invalid;\n}\n\npas::EAnimationState CABSAim::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) {\n  const pas::EAnimationState st = GetBodyStateTransition(dt, bc);\n  if (st == pas::EAnimationState::Invalid) {\n    const zeus::CVector3f& target = bc.GetCommandMgr().GetAdditiveTargetVector();\n    if (target.canBeNormalized()) {\n      CAnimData& animData = *bc.GetOwner().GetModelData()->GetAnimationData();\n\n      float hAngle = zeus::clamp(-x18_angles[0], std::atan2(target.x(), target.y()), x18_angles[1]);\n      hAngle *= 0.63661975f;\n      hAngle = zeus::clamp(-3.f, (hAngle - x28_hWeight) * 0.25f / dt, 3.f);\n      x2c_hWeightVel += dt * zeus::clamp(-10.f, (hAngle - x2c_hWeightVel) / dt, 10.f);\n\n      float hypotenuse = std::sqrt(target.y() * target.y() + target.x() * target.x());\n      float vAngle = zeus::clamp(-x18_angles[3], std::atan2(target.z(), hypotenuse), x18_angles[2]);\n      vAngle *= 0.63661975f;\n      vAngle = zeus::clamp(-3.f, (vAngle - x30_vWeight) * 0.25f / dt, 3.f);\n      x34_vWeightVel += dt * zeus::clamp(-10.f, (vAngle - x34_vWeightVel) / dt, 10.f);\n\n      float newHWeight = dt * x2c_hWeightVel + x28_hWeight;\n      if (newHWeight != x28_hWeight) {\n        if (std::fabs(x28_hWeight) > 0.f && x28_hWeight * newHWeight <= 0.f)\n          animData.DelAdditiveAnimation(x8_anims[x28_hWeight < 0.f ? 0 : 1]);\n        float absWeight = std::fabs(newHWeight);\n        if (absWeight > 0.f)\n          animData.AddAdditiveAnimation(x8_anims[newHWeight < 0.f ? 0 : 1], absWeight, false, false);\n      }\n\n      float newVWeight = dt * x34_vWeightVel + x30_vWeight;\n      if (newVWeight != x30_vWeight) {\n        if (std::fabs(x30_vWeight) > 0.f && x30_vWeight * newVWeight <= 0.f)\n          animData.DelAdditiveAnimation(x8_anims[x30_vWeight > 0.f ? 2 : 3]);\n        float absWeight = std::fabs(newVWeight);\n        if (absWeight > 0.f)\n          animData.AddAdditiveAnimation(x8_anims[newVWeight > 0.f ? 2 : 3], absWeight, false, false);\n      }\n\n      x28_hWeight = newHWeight;\n      x30_vWeight = newVWeight;\n    }\n  }\n  return st;\n}\n\nvoid CABSAim::Shutdown(CBodyController& bc) {\n  CAnimData& animData = *bc.GetOwner().GetModelData()->GetAnimationData();\n\n  if (x28_hWeight != 0.f)\n    animData.DelAdditiveAnimation(x8_anims[x28_hWeight < 0.f ? 0 : 1]);\n  if (x30_vWeight != 0.f)\n    animData.DelAdditiveAnimation(x8_anims[x30_vWeight > 0.f ? 2 : 3]);\n}\n\nvoid CABSFlinch::Start(CBodyController& bc, CStateManager& mgr) {\n  const CBCAdditiveFlinchCmd* cmd =\n      static_cast<const CBCAdditiveFlinchCmd*>(bc.GetCommandMgr().GetCmd(EBodyStateCmd::AdditiveFlinch));\n  x4_weight = cmd->GetWeight();\n\n  CPASAnimParmData parms(pas::EAnimationState::AdditiveFlinch);\n  std::pair<float, s32> best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1);\n  x8_anim = best.second;\n\n  CAnimData& animData = *bc.GetOwner().GetModelData()->GetAnimationData();\n  animData.AddAdditiveAnimation(x8_anim, x4_weight, false, true);\n}\n\npas::EAnimationState CABSFlinch::GetBodyStateTransition(float dt, CBodyController& bc) const {\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::AdditiveReaction))\n    return pas::EAnimationState::AdditiveReaction;\n  return pas::EAnimationState::Invalid;\n}\n\npas::EAnimationState CABSFlinch::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) {\n  const pas::EAnimationState st = GetBodyStateTransition(dt, bc);\n  if (st == pas::EAnimationState::Invalid) {\n    CAnimData& animData = *bc.GetOwner().GetModelData()->GetAnimationData();\n    CCharAnimTime rem = animData.GetAdditiveAnimationTree(x8_anim)->VGetTimeRemaining();\n    if (std::fabs(rem.GetSeconds()) < 0.00001f)\n      return pas::EAnimationState::AdditiveIdle;\n  }\n  return st;\n}\n\npas::EAnimationState CABSIdle::GetBodyStateTransition(float dt, CBodyController& bc) const {\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::AdditiveReaction))\n    return pas::EAnimationState::AdditiveReaction;\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::AdditiveFlinch))\n    return pas::EAnimationState::AdditiveFlinch;\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::AdditiveAim))\n    return pas::EAnimationState::AdditiveAim;\n  return pas::EAnimationState::Invalid;\n}\n\npas::EAnimationState CABSIdle::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) {\n  return GetBodyStateTransition(dt, bc);\n}\n\nvoid CABSReaction::Start(CBodyController& bc, CStateManager& mgr) {\n  const CBCAdditiveReactionCmd* cmd =\n      static_cast<const CBCAdditiveReactionCmd*>(bc.GetCommandMgr().GetCmd(EBodyStateCmd::AdditiveReaction));\n  x4_weight = cmd->GetWeight();\n  xc_type = cmd->GetType();\n  x10_active = cmd->GetIsActive();\n\n  CPASAnimParmData parms(pas::EAnimationState::AdditiveReaction, CPASAnimParm::FromEnum(s32(xc_type)));\n  std::pair<float, s32> best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1);\n  x8_anim = best.second;\n\n  if (x8_anim != -1) {\n    CAnimData& animData = *bc.GetOwner().GetModelData()->GetAnimationData();\n    animData.AddAdditiveAnimation(x8_anim, x4_weight, x10_active, false);\n  }\n}\n\npas::EAnimationState CABSReaction::GetBodyStateTransition(float dt, CBodyController& bc) const {\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::AdditiveReaction) && xc_type == pas::EAdditiveReactionType::IceBreakout)\n    return pas::EAnimationState::AdditiveReaction;\n  return pas::EAnimationState::Invalid;\n}\n\npas::EAnimationState CABSReaction::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) {\n  const pas::EAnimationState st = GetBodyStateTransition(dt, bc);\n  if (st == pas::EAnimationState::Invalid) {\n    if (x8_anim == -1)\n      return pas::EAnimationState::AdditiveIdle;\n\n    CAnimData& animData = *bc.GetOwner().GetModelData()->GetAnimationData();\n    if (x10_active) {\n      if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::StopReaction)) {\n        StopAnimation(bc);\n        bc.GetOwner().RemoveEmitter();\n        return pas::EAnimationState::AdditiveIdle;\n      }\n    } else {\n      if (animData.IsAdditiveAnimationAdded(x8_anim)) {\n        CCharAnimTime rem = animData.GetAdditiveAnimationTree(x8_anim)->VGetTimeRemaining();\n        if (std::fabs(rem.GetSeconds()) < 0.00001f) {\n          StopAnimation(bc);\n          return pas::EAnimationState::AdditiveIdle;\n        }\n      } else {\n        return pas::EAnimationState::AdditiveIdle;\n      }\n    }\n  }\n  return st;\n}\n\nvoid CABSReaction::StopAnimation(CBodyController& bc) {\n  if (x8_anim != -1) {\n    CAnimData& animData = *bc.GetOwner().GetModelData()->GetAnimationData();\n    animData.DelAdditiveAnimation(x8_anim);\n    x8_anim = -1;\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAdditiveBodyState.hpp",
    "content": "#pragma once\n\n#include <array>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Character/CBodyStateCmdMgr.hpp\"\n#include \"Runtime/Character/CharacterCommon.hpp\"\n\nnamespace metaforce {\nclass CActor;\nclass CBodyController;\nclass CStateManager;\n\nclass CAdditiveBodyState {\npublic:\n  virtual ~CAdditiveBodyState() = default;\n  virtual bool ApplyHeadTracking() const { return true; }\n  virtual bool CanShoot() const { return true; }\n  virtual void Start(CBodyController& bc, CStateManager& mgr) = 0;\n  virtual pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) = 0;\n  virtual void Shutdown(CBodyController& bc) = 0;\n};\n\nclass CABSAim : public CAdditiveBodyState {\n  bool x4_needsIdle = false;\n  std::array<s32, 4> x8_anims{};\n  std::array<float, 4> x18_angles{};\n  float x28_hWeight = 0.f;\n  float x2c_hWeightVel = 0.f;\n  float x30_vWeight = 0.f;\n  float x34_vWeightVel = 0.f;\n  pas::EAnimationState GetBodyStateTransition(float dt, CBodyController& bc) const;\n\npublic:\n  void Start(CBodyController& bc, CStateManager& mgr) override;\n  pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override;\n  void Shutdown(CBodyController& bc) override;\n};\n\nclass CABSFlinch : public CAdditiveBodyState {\n  float x4_weight = 1.f;\n  u32 x8_anim = 0;\n  pas::EAnimationState GetBodyStateTransition(float dt, CBodyController& bc) const;\n\npublic:\n  void Start(CBodyController& bc, CStateManager& mgr) override;\n  pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override;\n  void Shutdown(CBodyController& bc) override {}\n};\n\nclass CABSIdle : public CAdditiveBodyState {\n  pas::EAnimationState GetBodyStateTransition(float dt, CBodyController& bc) const;\n\npublic:\n  void Start(CBodyController& bc, CStateManager& mgr) override {}\n  pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override;\n  void Shutdown(CBodyController& bc) override {}\n};\n\nclass CABSReaction : public CAdditiveBodyState {\n  float x4_weight = 1.f;\n  s32 x8_anim = -1;\n  pas::EAdditiveReactionType xc_type = pas::EAdditiveReactionType::Invalid;\n  bool x10_active = false;\n  pas::EAnimationState GetBodyStateTransition(float dt, CBodyController& bc) const;\n  void StopAnimation(CBodyController& bc);\n\npublic:\n  void Start(CBodyController& bc, CStateManager& mgr) override;\n  pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override;\n  void Shutdown(CBodyController& bc) override { StopAnimation(bc); }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAllFormatsAnimSource.cpp",
    "content": "#include \"Runtime/Character/CAllFormatsAnimSource.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/Character/CAnimSourceReader.hpp\"\n#include \"Runtime/Character/CFBStreamedAnimReader.hpp\"\n#include \"Runtime/Logging.hpp\"\n\nnamespace metaforce {\nvoid CAnimFormatUnion::SubConstruct(u8* storage, EAnimFormat fmt, CInputStream& in, IObjectStore& store) {\n  switch (fmt) {\n  case EAnimFormat::Uncompressed:\n    new (storage) CAnimSource(in, store);\n    break;\n  case EAnimFormat::BitstreamCompressed:\n    new (storage) CFBStreamedCompression(in, store, false);\n    break;\n  case EAnimFormat::BitstreamCompressed24:\n    new (storage) CFBStreamedCompression(in, store, true);\n    break;\n  default:\n    spdlog::fatal(\"unable to read ANIM format {}\", static_cast<int>(fmt));\n  }\n}\n\nCAnimFormatUnion::CAnimFormatUnion(CInputStream& in, IObjectStore& store) {\n  x0_format = EAnimFormat(in.ReadLong());\n  SubConstruct(x4_storage, x0_format, in, store);\n}\n\nCAnimFormatUnion::~CAnimFormatUnion() {\n  switch (x0_format) {\n  case EAnimFormat::Uncompressed:\n    reinterpret_cast<CAnimSource*>(x4_storage)->~CAnimSource();\n    break;\n  case EAnimFormat::BitstreamCompressed:\n  case EAnimFormat::BitstreamCompressed24:\n    reinterpret_cast<CFBStreamedCompression*>(x4_storage)->~CFBStreamedCompression();\n    break;\n  default:\n    break;\n  }\n}\n\nstd::shared_ptr<IAnimReader> CAllFormatsAnimSource::GetNewReader(const TLockedToken<CAllFormatsAnimSource>& tok,\n                                                                 const CCharAnimTime& startTime) {\n  switch (tok->x0_format) {\n  case EAnimFormat::Uncompressed:\n    return std::make_shared<CAnimSourceReader>(tok, startTime);\n  case EAnimFormat::BitstreamCompressed:\n  case EAnimFormat::BitstreamCompressed24:\n    return std::make_shared<CFBStreamedAnimReader>(tok, startTime);\n  default:\n    break;\n  }\n  return {};\n}\n\nCAllFormatsAnimSource::CAllFormatsAnimSource(CInputStream& in, IObjectStore& store, const SObjectTag& tag)\n: CAnimFormatUnion(in, store), x74_tag(tag) {}\n\nCFactoryFnReturn AnimSourceFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& params,\n                                   CObjectReference* selfRef) {\n  CSimplePool* sp = params.GetOwnedObj<CSimplePool*>();\n  return TToken<CAllFormatsAnimSource>::GetIObjObjectFor(std::make_unique<CAllFormatsAnimSource>(in, *sp, tag));\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAllFormatsAnimSource.hpp",
    "content": "#pragma once\n\n#include <algorithm>\n#include <memory>\n\n#include \"Runtime/CFactoryMgr.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Character/CAnimSource.hpp\"\n#include \"Runtime/Character/CFBStreamedCompression.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass IAnimReader;\nclass IObjectStore;\n\nenum class EAnimFormat { Uncompressed, Unknown, BitstreamCompressed, BitstreamCompressed24 };\n\nclass CAnimFormatUnion {\n  friend class CAllFormatsAnimSource;\n  union {\n    EAnimFormat x0_format;\n    u8 _align[16];\n  };\n  u8 x4_storage[std::max(sizeof(CAnimSource), sizeof(CFBStreamedCompression))];\n  static void SubConstruct(u8* storage, EAnimFormat fmt, CInputStream& in, IObjectStore& store);\n\npublic:\n  explicit CAnimFormatUnion(CInputStream& in, IObjectStore& store);\n  ~CAnimFormatUnion();\n  EAnimFormat GetFormat() const { return x0_format; }\n  CAnimSource& GetAsCAnimSource() { return *reinterpret_cast<CAnimSource*>(x4_storage); }\n  CFBStreamedCompression& GetAsCFBStreamedCompression() {\n    return *reinterpret_cast<CFBStreamedCompression*>(x4_storage);\n  }\n};\n\nclass CAllFormatsAnimSource : public CAnimFormatUnion {\n  zeus::CVector3f x68_;\n  SObjectTag x74_tag;\n\npublic:\n  explicit CAllFormatsAnimSource(CInputStream& in, IObjectStore& store, const SObjectTag& tag);\n  static std::shared_ptr<IAnimReader> GetNewReader(const TLockedToken<CAllFormatsAnimSource>& tok,\n                                                   const CCharAnimTime& startTime);\n};\n\nCFactoryFnReturn AnimSourceFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& params,\n                                   CObjectReference* selfRef);\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimCharacterSet.cpp",
    "content": "#include \"Runtime/Character/CAnimCharacterSet.hpp\"\n\n#include \"Runtime/CToken.hpp\"\n\nnamespace metaforce {\n\nCAnimCharacterSet::CAnimCharacterSet(CInputStream& in)\n: x0_version(in.ReadShort()), x4_characterSet(in), x1c_animationSet(in) {}\n\nCFactoryFnReturn FAnimCharacterSet(const SObjectTag&, CInputStream& in, const CVParamTransfer&,\n                                   CObjectReference* selfRef) {\n  return TToken<CAnimCharacterSet>::GetIObjObjectFor(std::make_unique<CAnimCharacterSet>(in));\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimCharacterSet.hpp",
    "content": "#pragma once\n\n#include \"Runtime/CFactoryMgr.hpp\"\n#include \"Runtime/Character/CAnimationSet.hpp\"\n#include \"Runtime/Character/CCharacterSet.hpp\"\n\nnamespace metaforce {\n\nclass CAnimCharacterSet {\n  u16 x0_version;\n  CCharacterSet x4_characterSet;\n  CAnimationSet x1c_animationSet;\n\npublic:\n  explicit CAnimCharacterSet(CInputStream& in);\n  const CCharacterSet& GetCharacterSet() const { return x4_characterSet; }\n  const CAnimationSet& GetAnimationSet() const { return x1c_animationSet; }\n};\n\nCFactoryFnReturn FAnimCharacterSet(const SObjectTag&, CInputStream&, const CVParamTransfer&, CObjectReference* selfRef);\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimData.cpp",
    "content": "#include \"Runtime/Character/CAnimData.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Character/CAdditiveAnimPlayback.hpp\"\n#include \"Runtime/Character/CAllFormatsAnimSource.hpp\"\n#include \"Runtime/Character/CAnimPerSegmentData.hpp\"\n#include \"Runtime/Character/CAnimPlaybackParms.hpp\"\n#include \"Runtime/Character/CAnimTreeBlend.hpp\"\n#include \"Runtime/Character/CAnimTreeNode.hpp\"\n#include \"Runtime/Character/CAnimationManager.hpp\"\n#include \"Runtime/Character/CBoolPOINode.hpp\"\n#include \"Runtime/Character/CCharLayoutInfo.hpp\"\n#include \"Runtime/Character/CCharacterFactory.hpp\"\n#include \"Runtime/Character/CCharacterInfo.hpp\"\n#include \"Runtime/Character/CInt32POINode.hpp\"\n#include \"Runtime/Character/CParticleGenInfo.hpp\"\n#include \"Runtime/Character/CParticlePOINode.hpp\"\n#include \"Runtime/Character/CPrimitive.hpp\"\n#include \"Runtime/Character/CSegStatementSet.hpp\"\n#include \"Runtime/Character/CSoundPOINode.hpp\"\n#include \"Runtime/Character/CTransitionManager.hpp\"\n#include \"Runtime/Character/IAnimReader.hpp\"\n#include \"Runtime/Graphics/CSkinnedModel.hpp\"\n#include \"Runtime/Graphics/CGX.hpp\"\n#include \"Runtime/Logging.hpp\"\n\nnamespace metaforce {\nrstl::reserved_vector<CBoolPOINode, 8> CAnimData::g_BoolPOINodes;\nrstl::reserved_vector<CInt32POINode, 16> CAnimData::g_Int32POINodes;\nrstl::reserved_vector<CParticlePOINode, 20> CAnimData::g_ParticlePOINodes;\nrstl::reserved_vector<CSoundPOINode, 20> CAnimData::g_SoundPOINodes;\nrstl::reserved_vector<CInt32POINode, 16> CAnimData::g_TransientInt32POINodes;\n\nvoid CAnimData::FreeCache() {}\n\nvoid CAnimData::InitializeCache() {}\n\nCAnimData::CAnimData(CAssetId id, const CCharacterInfo& character, int defaultAnim, int charIdx, bool loop,\n                     TLockedToken<CCharLayoutInfo> layout, TToken<CSkinnedModel> model,\n                     const std::optional<TToken<CSkinnedModelWithAvgNormals>>& iceModel,\n                     const std::weak_ptr<CAnimSysContext>& ctx, std::shared_ptr<CAnimationManager> animMgr,\n                     std::shared_ptr<CTransitionManager> transMgr, TLockedToken<CCharacterFactory> charFactory)\n: x0_charFactory(charFactory)\n, xc_charInfo(character)\n, xcc_layoutData(layout)\n, xd8_modelData(std::move(model))\n, xfc_animCtx(ctx.lock())\n, x100_animMgr(std::move(animMgr))\n, x108_aabb()\n, x1d8_selfId(id)\n, x1fc_transMgr(std::move(transMgr))\n, x204_charIdx(charIdx)\n, x208_defaultAnim(defaultAnim)\n, x224_pose(layout->GetSegIdList().GetList().size())\n, x2fc_poseBuilder(CLayoutDescription{layout}) {\n  x220_25_loop = loop;\n\n  if (iceModel)\n    xe4_iceModelData = *iceModel;\n\n  g_BoolPOINodes.resize(8);\n  g_Int32POINodes.resize(16);\n  g_ParticlePOINodes.resize(20);\n  g_SoundPOINodes.resize(20);\n  g_TransientInt32POINodes.resize(16);\n\n  xd8_modelData->CalculateDefault();\n  for (const auto& item : xd8_modelData->GetModel()->GetPositions()) {\n    x108_aabb.accumulateBounds({item.x, item.y, item.z});\n  }\n  x120_particleDB.CacheParticleDesc(xc_charInfo.GetParticleResData());\n\n  CHierarchyPoseBuilder pb(CLayoutDescription{xcc_layoutData});\n  pb.BuildNoScale(x224_pose);\n  x220_30_poseBuilt = true;\n\n  if (defaultAnim == -1) {\n    defaultAnim = 0;\n    spdlog::warn(\"Character {} has invalid initial animation, so defaulting to first.\", character.GetCharacterName());\n  }\n\n  auto treeNode = GetAnimationManager()->GetAnimationTree(character.GetAnimationIndex(defaultAnim),\n                                                          CMetaAnimTreeBuildOrders::NoSpecialOrders());\n  if (treeNode != x1f8_animRoot) {\n    x1f8_animRoot = std::move(treeNode);\n  }\n}\n\nvoid CAnimData::SetParticleEffectState(std::string_view effectName, bool active, CStateManager& mgr) {\n  auto search = std::find_if(xc_charInfo.x98_effects.begin(), xc_charInfo.x98_effects.end(),\n                             [effectName](const auto& v) { return v.first == effectName; });\n  if (search != xc_charInfo.x98_effects.end())\n    for (const auto& p : search->second)\n      x120_particleDB.SetParticleEffectState(p.GetComponentName(), active, mgr);\n}\n\nvoid CAnimData::InitializeEffects(CStateManager& mgr, TAreaId aId, const zeus::CVector3f& scale) {\n  for (const auto& effects : xc_charInfo.GetEffectList()) {\n    for (const auto& effect : effects.second) {\n      x120_particleDB.CacheParticleDesc(effect.GetParticleTag());\n      const CParticleData data{effect.GetParticleTag(), effect.GetSegmentName(), effect.GetScale(),\n                               effect.GetParentedMode()};\n      x120_particleDB.AddParticleEffect(effect.GetComponentName(), effect.GetFlags(), data, scale, mgr, aId, true,\n                                        x21c_particleLightIdx);\n      x120_particleDB.SetParticleEffectState(effect.GetComponentName(), false, mgr);\n    }\n  }\n}\n\nCAssetId CAnimData::GetEventResourceIdForAnimResourceId(CAssetId id) const {\n  return x0_charFactory->GetEventResourceIdForAnimResourceId(id);\n}\n\nvoid CAnimData::AddAdditiveSegData(const CSegIdList& list, CSegStatementSet& stSet) {\n  for (auto& additive : x434_additiveAnims)\n    if (additive.second.GetTargetWeight() > 0.00001f)\n      additive.second.AddToSegStatementSet(list, *xcc_layoutData.GetObj(), stSet);\n}\n\nSAdvancementResults CAnimData::AdvanceAdditiveAnim(std::shared_ptr<CAnimTreeNode>& anim, const CCharAnimTime& time) {\n  SAdvancementResults ret = anim->VAdvanceView(time);\n  auto simplified = anim->Simplified();\n  if (simplified)\n    anim = CAnimTreeNode::Cast(std::move(*simplified));\n  return ret;\n}\n\nSAdvancementDeltas CAnimData::AdvanceAdditiveAnims(float dt) {\n  CCharAnimTime time(dt);\n\n  SAdvancementDeltas deltas = {};\n\n  for (auto& additive : x434_additiveAnims) {\n    std::shared_ptr<CAnimTreeNode>& anim = additive.second.GetAnim();\n    if (additive.second.IsActive()) {\n      while (time.GreaterThanZero() && std::fabs(time.GetSeconds()) >= 0.00001f) {\n        x210_passedIntCount +=\n            u32(anim->GetInt32POIList(time, g_Int32POINodes.data(), g_Int32POINodes.size(), x210_passedIntCount, 0));\n        x20c_passedBoolCount +=\n            u32(anim->GetBoolPOIList(time, g_BoolPOINodes.data(), g_BoolPOINodes.size(), x20c_passedBoolCount, 0));\n        x214_passedParticleCount +=\n            u32(anim->GetParticlePOIList(time, g_ParticlePOINodes.data(), 8, x214_passedParticleCount, 0));\n        x218_passedSoundCount += u32(anim->GetSoundPOIList(time, g_SoundPOINodes.data(), 8, x218_passedSoundCount, 0));\n\n        SAdvancementResults results = AdvanceAdditiveAnim(anim, time);\n        deltas.x0_posDelta += results.x8_deltas.x0_posDelta;\n        deltas.xc_rotDelta *= results.x8_deltas.xc_rotDelta;\n        time = results.x0_remTime;\n      }\n    } else {\n      CCharAnimTime remTime = anim->VGetTimeRemaining();\n      while (remTime.GreaterThanZero() && std::fabs(remTime.GetSeconds()) >= 0.00001f) {\n        x210_passedIntCount +=\n            u32(anim->GetInt32POIList(time, g_Int32POINodes.data(), g_Int32POINodes.size(), x210_passedIntCount, 0));\n        x20c_passedBoolCount +=\n            u32(anim->GetBoolPOIList(time, g_BoolPOINodes.data(), g_BoolPOINodes.size(), x20c_passedBoolCount, 0));\n        x214_passedParticleCount +=\n            u32(anim->GetParticlePOIList(time, g_ParticlePOINodes.data(), 8, x214_passedParticleCount, 0));\n        x218_passedSoundCount += u32(anim->GetSoundPOIList(time, g_SoundPOINodes.data(), 8, x218_passedSoundCount, 0));\n\n        SAdvancementResults results = AdvanceAdditiveAnim(anim, time);\n        deltas.x0_posDelta += results.x8_deltas.x0_posDelta;\n        deltas.xc_rotDelta *= results.x8_deltas.xc_rotDelta;\n        CCharAnimTime tmpTime = anim->VGetTimeRemaining();\n        if (tmpTime < results.x0_remTime)\n          remTime = tmpTime;\n        else\n          remTime = results.x0_remTime;\n      }\n    }\n  }\n\n  return deltas;\n}\n\nSAdvancementDeltas CAnimData::UpdateAdditiveAnims(float dt) {\n  for (auto it = x434_additiveAnims.begin(); it != x434_additiveAnims.end();) {\n    it->second.Update(dt);\n    CCharAnimTime timeRem = it->second.GetAnim()->VGetTimeRemaining();\n    if (timeRem.EpsilonZero() && it->second.NeedsFadeOut())\n      it->second.FadeOut();\n    if (it->second.GetPhase() == EAdditivePlaybackPhase::FadedOut) {\n      it = x434_additiveAnims.erase(it);\n      continue;\n    }\n    ++it;\n  }\n\n  return AdvanceAdditiveAnims(dt);\n}\n\nbool CAnimData::IsAdditiveAnimation(s32 idx) const {\n  s32 animIdx = xc_charInfo.GetAnimationIndex(idx);\n  return x0_charFactory->HasAdditiveInfo(animIdx);\n}\n\nbool CAnimData::IsAdditiveAnimationAdded(s32 idx) const {\n  s32 animIdx = xc_charInfo.GetAnimationIndex(idx);\n  auto search = std::find_if(x434_additiveAnims.cbegin(), x434_additiveAnims.cend(),\n                             [animIdx](const auto& pair) { return pair.first == animIdx; });\n  return search != x434_additiveAnims.cend();\n}\n\nconst std::shared_ptr<CAnimTreeNode>& CAnimData::GetAdditiveAnimationTree(s32 idx) const {\n  s32 animIdx = xc_charInfo.GetAnimationIndex(idx);\n  auto search = std::find_if(x434_additiveAnims.cbegin(), x434_additiveAnims.cend(),\n                             [animIdx](const auto& pair) { return pair.first == animIdx; });\n  return search->second.GetAnim();\n}\n\nbool CAnimData::IsAdditiveAnimationActive(s32 idx) const {\n  s32 animIdx = xc_charInfo.GetAnimationIndex(idx);\n  auto search = std::find_if(x434_additiveAnims.cbegin(), x434_additiveAnims.cend(),\n                             [animIdx](const auto& pair) { return pair.first == animIdx; });\n  if (search == x434_additiveAnims.cend())\n    return false;\n  return search->second.IsActive();\n}\n\nvoid CAnimData::DelAdditiveAnimation(s32 idx) {\n  s32 animIdx = xc_charInfo.GetAnimationIndex(idx);\n  auto search = std::find_if(x434_additiveAnims.begin(), x434_additiveAnims.end(),\n                             [animIdx](const auto& pair) { return pair.first == animIdx; });\n  if (search != x434_additiveAnims.cend() && search->second.GetPhase() != EAdditivePlaybackPhase::FadingOut &&\n      search->second.GetPhase() != EAdditivePlaybackPhase::FadedOut) {\n    search->second.FadeOut();\n  }\n}\n\nvoid CAnimData::AddAdditiveAnimation(s32 idx, float weight, bool active, bool fadeOut) {\n  s32 animIdx = xc_charInfo.GetAnimationIndex(idx);\n  auto search = std::find_if(x434_additiveAnims.begin(), x434_additiveAnims.end(),\n                             [animIdx](const auto& pair) { return pair.first == animIdx; });\n  if (search != x434_additiveAnims.cend()) {\n    search->second.SetActive(active);\n    search->second.SetWeight(weight);\n    search->second.SetNeedsFadeOut(!search->second.IsActive() && fadeOut);\n  } else {\n    std::shared_ptr<CAnimTreeNode> node =\n        GetAnimationManager()->GetAnimationTree(animIdx, CMetaAnimTreeBuildOrders::NoSpecialOrders());\n    const CAdditiveAnimationInfo& info = x0_charFactory->FindAdditiveInfo(animIdx);\n    x434_additiveAnims.emplace_back(\n        std::make_pair(animIdx, CAdditiveAnimPlayback(node, weight, active, info, fadeOut)));\n  }\n}\n\nfloat CAnimData::GetAdditiveAnimationWeight(s32 idx) const {\n  s32 animIdx = xc_charInfo.GetAnimationIndex(idx);\n  auto search = std::find_if(x434_additiveAnims.cbegin(), x434_additiveAnims.cend(),\n                             [animIdx](const auto& pair) { return pair.first == animIdx; });\n  if (search != x434_additiveAnims.cend())\n    return search->second.GetTargetWeight();\n  return 0.f;\n}\n\nstd::shared_ptr<CAnimationManager> CAnimData::GetAnimationManager() { return x100_animMgr; }\n\nvoid CAnimData::SetPhase(float ph) { x1f8_animRoot->VSetPhase(ph); }\n\nvoid CAnimData::Touch(CSkinnedModel& model, int shadIdx) const { model.GetModel()->Touch(shadIdx); }\n\nSAdvancementDeltas CAnimData::GetAdvancementDeltas(const CCharAnimTime& a, const CCharAnimTime& b) const {\n  return x1f8_animRoot->VGetAdvancementResults(a, b).x8_deltas;\n}\n\nCCharAnimTime CAnimData::GetTimeOfUserEvent(EUserEventType type, const CCharAnimTime& time) const {\n  const size_t count =\n      x1f8_animRoot->GetInt32POIList(time, g_TransientInt32POINodes.data(), g_TransientInt32POINodes.size(), 0, 64);\n  for (size_t i = 0; i < count; ++i) {\n    CInt32POINode& poi = g_TransientInt32POINodes[i];\n    if (poi.GetPoiType() == EPOIType::UserEvent && EUserEventType(poi.GetValue()) == type) {\n      CCharAnimTime ret = poi.GetTime();\n      for (; i < count; ++i)\n        g_TransientInt32POINodes[i] = CInt32POINode();\n      return ret;\n    } else {\n      poi = CInt32POINode();\n    }\n  }\n  return CCharAnimTime::Infinity();\n}\n\nvoid CAnimData::MultiplyPlaybackRate(float mul) { x200_speedScale *= mul; }\n\nvoid CAnimData::SetPlaybackRate(float set) { x200_speedScale = set; }\n\nvoid CAnimData::SetRandomPlaybackRate(CRandom16& r) {\n  for (size_t i = 0; i < x210_passedIntCount; ++i) {\n    const CInt32POINode& poi = g_Int32POINodes[i];\n    if (poi.GetPoiType() == EPOIType::RandRate) {\n      float tmp = (r.Next() % poi.GetValue()) / 100.f;\n      if ((r.Next() % 100) < 50)\n        x200_speedScale = 1.f + tmp;\n      else\n        x200_speedScale = 1.f - tmp;\n      break;\n    }\n  }\n}\n\nvoid CAnimData::CalcPlaybackAlignmentParms(const CAnimPlaybackParms& parms,\n                                           const std::shared_ptr<CAnimTreeNode>& node) {\n  zeus::CQuaternion orient;\n  x1e8_alignRot = zeus::CQuaternion();\n\n  x220_27_ = false;\n  if (parms.GetDeltaOrient() && parms.GetObjectXform()) {\n    ResetPOILists();\n    x210_passedIntCount += u32(node->GetInt32POIList(CCharAnimTime::Infinity(), g_Int32POINodes.data(),\n                                                     g_Int32POINodes.size(), x210_passedIntCount, 64));\n    for (size_t i = 0; i < x210_passedIntCount; ++i) {\n      const CInt32POINode& poi = g_Int32POINodes[i];\n      if (poi.GetPoiType() == EPOIType::UserEvent && EUserEventType(poi.GetValue()) == EUserEventType::AlignTargetRot) {\n        SAdvancementResults res = node->VGetAdvancementResults(poi.GetTime(), 0.f);\n        orient = zeus::CQuaternion::slerp(zeus::CQuaternion(),\n                                          *parms.GetDeltaOrient() *\n                                              zeus::CQuaternion(parms.GetObjectXform()->buildMatrix3f().inverted()) *\n                                              res.x8_deltas.xc_rotDelta.inverse(),\n                                          1.f / (60.f * poi.GetTime().GetSeconds()));\n        x1e8_alignRot = orient;\n        x220_27_ = true;\n      }\n    }\n  }\n\n  if (!x220_27_) {\n    bool didAlign = false;\n    bool didStart = false;\n    zeus::CVector3f posStart, posAlign;\n    CCharAnimTime timeStart, timeAlign;\n    if (parms.GetTargetPos() && parms.GetObjectXform()) {\n      ResetPOILists();\n      x210_passedIntCount += u32(node->GetInt32POIList(CCharAnimTime::Infinity(), g_Int32POINodes.data(),\n                                                       g_Int32POINodes.size(), x210_passedIntCount, 64));\n      for (size_t i = 0; i < x210_passedIntCount; ++i) {\n        const CInt32POINode& poi = g_Int32POINodes[i];\n        if (poi.GetPoiType() == EPOIType::UserEvent) {\n          if (EUserEventType(poi.GetValue()) == EUserEventType::AlignTargetPosStart) {\n            didStart = true;\n            SAdvancementResults res = node->VGetAdvancementResults(poi.GetTime(), 0.f);\n            posStart = res.x8_deltas.x0_posDelta;\n            timeStart = poi.GetTime();\n\n            if (parms.GetIsUseLocator())\n              posStart += GetLocatorTransform(poi.GetLocatorName(), &poi.GetTime()).origin;\n\n            if (didAlign)\n              break;\n          } else if (EUserEventType(poi.GetValue()) == EUserEventType::AlignTargetPos) {\n            didAlign = true;\n            SAdvancementResults res = node->VGetAdvancementResults(poi.GetTime(), 0.f);\n            posAlign = res.x8_deltas.x0_posDelta;\n            timeAlign = poi.GetTime();\n\n            if (parms.GetIsUseLocator())\n              posAlign += GetLocatorTransform(poi.GetLocatorName(), &poi.GetTime()).origin;\n\n            if (didStart)\n              break;\n          }\n        }\n      }\n\n      if (didAlign && didStart) {\n        zeus::CVector3f scaleStart = *parms.GetObjectScale() * posStart;\n        zeus::CVector3f scaleAlign = *parms.GetObjectScale() * posAlign;\n        x1dc_alignPos =\n            (parms.GetObjectXform()->inverse() * *parms.GetTargetPos() - scaleStart - (scaleAlign - scaleStart)) /\n            *parms.GetObjectScale() * (1.f / (timeAlign.GetSeconds() - timeStart.GetSeconds()));\n        x220_28_ = true;\n        x220_26_aligningPos = false;\n      } else {\n        x1dc_alignPos = zeus::skZero3f;\n        x220_28_ = false;\n        x220_26_aligningPos = false;\n      }\n    }\n  } else {\n    bool didStart = false;\n    bool didAlign = false;\n    CCharAnimTime timeStart, timeAlign;\n    zeus::CVector3f startPos;\n    if (parms.GetTargetPos() && parms.GetObjectXform()) {\n      ResetPOILists();\n      x210_passedIntCount += u32(node->GetInt32POIList(CCharAnimTime::Infinity(), g_Int32POINodes.data(),\n                                                       g_Int32POINodes.size(), x210_passedIntCount, 64));\n      for (size_t i = 0; i < x210_passedIntCount; ++i) {\n        CInt32POINode& poi = g_Int32POINodes[i];\n        if (poi.GetPoiType() == EPOIType::UserEvent) {\n          if (EUserEventType(poi.GetValue()) == EUserEventType::AlignTargetPosStart) {\n            didStart = true;\n            timeStart = poi.GetTime();\n            if (didAlign)\n              break;\n          } else if (EUserEventType(poi.GetValue()) == EUserEventType::AlignTargetPos) {\n            didAlign = true;\n            timeAlign = poi.GetTime();\n            if (didStart)\n              break;\n          }\n        }\n      }\n\n      if (didAlign && didStart) {\n        CCharAnimTime frameInterval(1.f / 60.f);\n        orient = zeus::CQuaternion();\n        x1e8_alignRot = zeus::CQuaternion();\n        x220_27_ = true;\n        CCharAnimTime time;\n        zeus::CVector3f pos;\n        zeus::CQuaternion quat;\n        bool foundStartPos = false;\n        while (time < timeAlign) {\n          SAdvancementResults res = node->VGetAdvancementResults(frameInterval, time);\n          pos += quat.toTransform() * res.x8_deltas.x0_posDelta;\n          quat *= (res.x8_deltas.xc_rotDelta * orient);\n          if (!foundStartPos && time >= timeStart) {\n            startPos = pos;\n            foundStartPos = true;\n          }\n          time += frameInterval;\n        }\n        zeus::CVector3f scaleStart = startPos * *parms.GetObjectScale();\n        zeus::CVector3f scaleAlign = pos * *parms.GetObjectScale();\n        x1dc_alignPos =\n            (parms.GetObjectXform()->inverse() * *parms.GetTargetPos() - scaleStart - (scaleAlign - scaleStart)) /\n            *parms.GetObjectScale() * (1.f / (timeAlign.GetSeconds() - timeStart.GetSeconds()));\n        x220_28_ = true;\n        x220_26_aligningPos = false;\n      } else {\n        x1dc_alignPos = zeus::skZero3f;\n        x220_28_ = false;\n        x220_26_aligningPos = false;\n      }\n    } else {\n      x1dc_alignPos = zeus::skZero3f;\n      x220_28_ = false;\n      x220_26_aligningPos = false;\n    }\n  }\n}\n\nzeus::CTransform CAnimData::GetLocatorTransform(CSegId id, const CCharAnimTime* time) const {\n  if (id.IsInvalid()) {\n    return {};\n  }\n\n  if (time || !x220_31_poseCached) {\n    const_cast<CAnimData*>(this)->RecalcPoseBuilder(time);\n    const_cast<CAnimData*>(this)->x220_31_poseCached = time == nullptr;\n  }\n\n  zeus::CTransform ret;\n  if (!x220_30_poseBuilt) {\n    x2fc_poseBuilder.BuildTransform(id, ret);\n  } else {\n    ret.setRotation(x224_pose.GetRotation(id));\n    ret.origin = x224_pose.GetOffset(id);\n  }\n  return ret;\n}\n\nzeus::CTransform CAnimData::GetLocatorTransform(std::string_view name, const CCharAnimTime* time) const {\n  return GetLocatorTransform(xcc_layoutData->GetSegIdFromString(name), time);\n}\n\nbool CAnimData::IsAnimTimeRemaining(float rem, std::string_view name) const {\n  if (!x1f8_animRoot)\n    return false;\n  return x1f8_animRoot->VGetTimeRemaining().GetSeconds() >= rem;\n}\n\nfloat CAnimData::GetAnimTimeRemaining(std::string_view name) const {\n  float rem = x1f8_animRoot->VGetTimeRemaining().GetSeconds();\n  if (x200_speedScale)\n    return rem / x200_speedScale;\n  return rem;\n}\n\nfloat CAnimData::GetAnimationDuration(int animIn) const {\n  std::shared_ptr<IMetaAnim> anim = x100_animMgr->GetMetaAnimation(xc_charInfo.GetAnimationIndex(animIn));\n  std::set<CPrimitive> prims;\n  anim->GetUniquePrimitives(prims);\n\n  SObjectTag tag{FOURCC('ANIM'), {}};\n  float durAccum = 0.f;\n  for (const CPrimitive& prim : prims) {\n    tag.id = prim.GetAnimResId();\n    TLockedToken<CAllFormatsAnimSource> animRes = xfc_animCtx->xc_store.GetObj(tag);\n\n    CCharAnimTime dur;\n    switch (animRes->GetFormat()) {\n    case EAnimFormat::Uncompressed:\n    default: {\n      const CAnimSource& src = animRes->GetAsCAnimSource();\n      dur = src.GetDuration();\n      break;\n    }\n    case EAnimFormat::BitstreamCompressed:\n    case EAnimFormat::BitstreamCompressed24: {\n      const CFBStreamedCompression& src = animRes->GetAsCFBStreamedCompression();\n      dur = src.GetAnimationDuration();\n      break;\n    }\n    }\n\n    durAccum += dur.GetSeconds();\n  }\n\n  if (anim->GetType() == EMetaAnimType::Random)\n    return durAccum / float(prims.size());\n  return durAccum;\n}\n\nstd::shared_ptr<CAnimSysContext> CAnimData::GetAnimSysContext() const { return xfc_animCtx; }\n\nstd::shared_ptr<CAnimationManager> CAnimData::GetAnimationManager() const { return x100_animMgr; }\n\nvoid CAnimData::RecalcPoseBuilder(const CCharAnimTime* time) {\n  if (!x1f8_animRoot)\n    return;\n\n  const CSegIdList& segIdList = GetCharLayoutInfo().GetSegIdList();\n  CSegStatementSet segSet;\n  if (time)\n    x1f8_animRoot->VGetSegStatementSet(segIdList, segSet, *time);\n  else\n    x1f8_animRoot->VGetSegStatementSet(segIdList, segSet);\n\n  AddAdditiveSegData(segIdList, segSet);\n\n  for (const CSegId& id : segIdList.GetList()) {\n    if (id == 3)\n      continue;\n    CAnimPerSegmentData& segData = segSet[id];\n    if (segData.x1c_hasOffset)\n      x2fc_poseBuilder.Insert(id, segData.x0_rotation, segData.x10_offset);\n    else\n      x2fc_poseBuilder.Insert(id, segData.x0_rotation);\n  }\n}\n\nvoid CAnimData::RenderAuxiliary(const zeus::CFrustum& frustum) const { x120_particleDB.AddToRendererClipped(frustum); }\n\nvoid CAnimData::Render(CSkinnedModel& model, const CModelFlags& drawFlags, CVertexMorphEffect* morphEffect,\n                       TConstVectorRef averagedNormals) {\n  SetupRender(model, morphEffect, averagedNormals);\n  DrawSkinnedModel(model, drawFlags);\n}\n\nvoid CAnimData::SetupRender(CSkinnedModel& model, CVertexMorphEffect* morphEffect, TConstVectorRef averagedNormals) {\n  //OPTICK_EVENT();\n  if (!x220_30_poseBuilt) {\n    x2fc_poseBuilder.BuildNoScale(x224_pose);\n    x220_30_poseBuilt = true;\n  }\n  PoseSkinnedModel(model, x224_pose, morphEffect, averagedNormals);\n}\n\nvoid CAnimData::DrawSkinnedModel(CSkinnedModel& model, const CModelFlags& flags) {\n  CGX::SetChanCtrl(CGX::EChannelId::Channel0, GX_ENABLE, GX_SRC_REG, GX_SRC_REG, CGraphics::mLightActive,\n                   CGraphics::mLightActive.any() ? GX_DF_CLAMP : GX_DF_NONE,\n                   CGraphics::mLightActive.any() ? GX_AF_SPOT : GX_AF_NONE);\n  model.Draw(flags);\n}\n\nvoid CAnimData::PreRender() {\n  if (!x220_31_poseCached) {\n    RecalcPoseBuilder(nullptr);\n    x220_31_poseCached = true;\n    x220_30_poseBuilt = false;\n  }\n}\n\nvoid CAnimData::BuildPose() {\n  if (!x220_31_poseCached) {\n    RecalcPoseBuilder(nullptr);\n    x220_31_poseCached = true;\n    x220_30_poseBuilt = false;\n  }\n\n  if (!x220_30_poseBuilt) {\n    x2fc_poseBuilder.BuildNoScale(x224_pose);\n    x220_30_poseBuilt = true;\n  }\n}\n\nvoid CAnimData::PrimitiveSetToTokenVector(const std::set<CPrimitive>& primSet, std::vector<CToken>& tokensOut,\n                                          bool preLock) {\n  tokensOut.reserve(primSet.size());\n\n  SObjectTag tag{FOURCC('ANIM'), {}};\n  for (const CPrimitive& prim : primSet) {\n    tag.id = prim.GetAnimResId();\n    tokensOut.push_back(g_SimplePool->GetObj(tag));\n    if (preLock)\n      tokensOut.back().Lock();\n  }\n}\n\nvoid CAnimData::GetAnimationPrimitives(const CAnimPlaybackParms& parms, std::set<CPrimitive>& primsOut) const {\n  std::shared_ptr<IMetaAnim> animA =\n      x100_animMgr->GetMetaAnimation(xc_charInfo.GetAnimationIndex(parms.GetAnimationId()));\n  animA->GetUniquePrimitives(primsOut);\n\n  if (parms.GetSecondAnimationId() != -1) {\n    std::shared_ptr<IMetaAnim> animB =\n        x100_animMgr->GetMetaAnimation(xc_charInfo.GetAnimationIndex(parms.GetSecondAnimationId()));\n    animB->GetUniquePrimitives(primsOut);\n  }\n}\n\nvoid CAnimData::SetAnimation(const CAnimPlaybackParms& parms, bool noTrans) {\n  if (parms.GetAnimationId() == x40c_playbackParms.GetAnimationId() ||\n      (parms.GetSecondAnimationId() == x40c_playbackParms.GetSecondAnimationId() &&\n       parms.GetSecondAnimationId() != -1) ||\n      (parms.GetBlendFactor() == x40c_playbackParms.GetBlendFactor() && parms.GetBlendFactor() != 1.f)) {\n    if (x220_29_animationJustStarted)\n      return;\n  }\n\n  x40c_playbackParms.SetAnimationId(parms.GetAnimationId());\n  x40c_playbackParms.SetSecondAnimationId(parms.GetSecondAnimationId());\n  x40c_playbackParms.SetBlendFactor(parms.GetBlendFactor());\n  x200_speedScale = 1.f;\n  x208_defaultAnim = parms.GetAnimationId();\n\n  s32 animIdxA = xc_charInfo.GetAnimationIndex(parms.GetAnimationId());\n\n  ResetPOILists();\n\n  std::shared_ptr<CAnimTreeNode> blendNode;\n  if (parms.GetSecondAnimationId() != -1) {\n    s32 animIdxB = xc_charInfo.GetAnimationIndex(parms.GetSecondAnimationId());\n\n    std::shared_ptr<CAnimTreeNode> treeA =\n        x100_animMgr->GetAnimationTree(animIdxA, CMetaAnimTreeBuildOrders::NoSpecialOrders());\n    std::shared_ptr<CAnimTreeNode> treeB =\n        x100_animMgr->GetAnimationTree(animIdxB, CMetaAnimTreeBuildOrders::NoSpecialOrders());\n\n    blendNode =\n        std::make_shared<CAnimTreeBlend>(false, treeA, treeB, parms.GetBlendFactor(),\n                                         CAnimTreeBlend::CreatePrimitiveName(treeA, treeB, parms.GetBlendFactor()));\n  } else {\n    blendNode = x100_animMgr->GetAnimationTree(animIdxA, CMetaAnimTreeBuildOrders::NoSpecialOrders());\n  }\n\n  if (!noTrans && x1f8_animRoot)\n    x1f8_animRoot = x1fc_transMgr->GetTransitionTree(x1f8_animRoot, blendNode);\n  else\n    x1f8_animRoot = blendNode;\n\n  x220_24_animating = parms.GetIsPlayAnimation();\n  CalcPlaybackAlignmentParms(parms, blendNode);\n  ResetPOILists();\n  x220_29_animationJustStarted = true;\n}\n\nSAdvancementDeltas CAnimData::DoAdvance(float dt, bool& suspendParticles, CRandom16& random, bool advTree) {\n  suspendParticles = false;\n\n  zeus::CVector3f offsetPre, offsetPost;\n  zeus::CQuaternion quatPre, quatPost;\n\n  ResetPOILists();\n  float scaleDt = dt * x200_speedScale;\n  if (x2fc_poseBuilder.HasRoot()) {\n    SAdvancementDeltas deltas = UpdateAdditiveAnims(scaleDt);\n    offsetPre = deltas.x0_posDelta;\n    quatPre = deltas.xc_rotDelta;\n  }\n\n  if (!x220_24_animating) {\n    suspendParticles = true;\n    return {};\n  }\n\n  if (x220_29_animationJustStarted) {\n    x220_29_animationJustStarted = false;\n    suspendParticles = true;\n  }\n\n  if (advTree && x1f8_animRoot) {\n    SetRandomPlaybackRate(random);\n    CCharAnimTime time(scaleDt);\n    if (x220_25_loop) {\n      while (time.GreaterThanZero() && !time.EpsilonZero()) {\n        x210_passedIntCount += u32(x1f8_animRoot->GetInt32POIList(time, g_Int32POINodes.data(), g_Int32POINodes.size(),\n                                                                  x210_passedIntCount, 0));\n        x20c_passedBoolCount += u32(\n            x1f8_animRoot->GetBoolPOIList(time, g_BoolPOINodes.data(), g_BoolPOINodes.size(), x20c_passedBoolCount, 0));\n        x214_passedParticleCount +=\n            u32(x1f8_animRoot->GetParticlePOIList(time, g_ParticlePOINodes.data(), 16, x214_passedParticleCount, 0));\n        x218_passedSoundCount +=\n            u32(x1f8_animRoot->GetSoundPOIList(time, g_SoundPOINodes.data(), 16, x218_passedSoundCount, 0));\n        AdvanceAnim(time, offsetPost, quatPost);\n      }\n    } else {\n      CCharAnimTime remTime = x1f8_animRoot->VGetTimeRemaining();\n      while (!remTime.EpsilonZero() && !time.EpsilonZero()) {\n        x210_passedIntCount += u32(x1f8_animRoot->GetInt32POIList(time, g_Int32POINodes.data(), g_Int32POINodes.size(),\n                                                                  x210_passedIntCount, 0));\n        x20c_passedBoolCount += u32(\n            x1f8_animRoot->GetBoolPOIList(time, g_BoolPOINodes.data(), g_BoolPOINodes.size(), x20c_passedBoolCount, 0));\n        x214_passedParticleCount +=\n            u32(x1f8_animRoot->GetParticlePOIList(time, g_ParticlePOINodes.data(), 16, x214_passedParticleCount, 0));\n        x218_passedSoundCount +=\n            u32(x1f8_animRoot->GetSoundPOIList(time, g_SoundPOINodes.data(), 16, x218_passedSoundCount, 0));\n        AdvanceAnim(time, offsetPost, quatPost);\n        remTime = x1f8_animRoot->VGetTimeRemaining();\n        time = std::max(0.f, std::min(remTime.GetSeconds(), time.GetSeconds()));\n        if (remTime.EpsilonZero()) {\n          x220_24_animating = false;\n          x1dc_alignPos = zeus::skZero3f;\n          x220_28_ = false;\n          x220_26_aligningPos = false;\n        }\n      }\n    }\n\n    x220_31_poseCached = false;\n    x220_30_poseBuilt = false;\n  }\n\n  return {offsetPost + offsetPre, quatPost * quatPre};\n}\n\nSAdvancementDeltas CAnimData::Advance(float dt, const zeus::CVector3f& scale, CStateManager& stateMgr, TAreaId aid,\n                                      bool advTree) {\n  bool suspendParticles;\n  SAdvancementDeltas deltas = DoAdvance(dt, suspendParticles, *stateMgr.GetActiveRandom(), advTree);\n  if (suspendParticles)\n    x120_particleDB.SuspendAllActiveEffects(stateMgr);\n\n  for (size_t i = 0; i < x214_passedParticleCount; ++i) {\n    const CParticlePOINode& node = g_ParticlePOINodes[i];\n    if (node.GetCharacterIndex() == -1 || node.GetCharacterIndex() == x204_charIdx) {\n      x120_particleDB.AddParticleEffect(node.GetString(), node.GetFlags(), node.GetParticleData(), scale, stateMgr, aid,\n                                        false, x21c_particleLightIdx);\n    }\n  }\n\n  return deltas;\n}\n\nSAdvancementDeltas CAnimData::AdvanceIgnoreParticles(float dt, CRandom16& random, bool advTree) {\n  bool suspendParticles;\n  return DoAdvance(dt, suspendParticles, random, advTree);\n}\n\nvoid CAnimData::AdvanceAnim(CCharAnimTime& time, zeus::CVector3f& offset, zeus::CQuaternion& quat) {\n  SAdvancementResults results;\n  std::optional<std::unique_ptr<IAnimReader>> simplified;\n\n  if (x104_animDir == EAnimDir::Forward) {\n    results = x1f8_animRoot->VAdvanceView(time);\n    simplified = x1f8_animRoot->Simplified();\n  }\n\n  if (simplified)\n    x1f8_animRoot = CAnimTreeNode::Cast(std::move(*simplified));\n\n  if ((x220_28_ || x220_27_) && x210_passedIntCount > 0) {\n    for (size_t i = 0; i < x210_passedIntCount; ++i) {\n      const CInt32POINode& node = g_Int32POINodes[i];\n      if (node.GetPoiType() == EPOIType::UserEvent) {\n        switch (EUserEventType(node.GetValue())) {\n        case EUserEventType::AlignTargetPosStart: {\n          x220_26_aligningPos = true;\n          break;\n        }\n        case EUserEventType::AlignTargetPos: {\n          x1dc_alignPos = zeus::skZero3f;\n          x220_28_ = false;\n          x220_26_aligningPos = false;\n          break;\n        }\n        case EUserEventType::AlignTargetRot: {\n          x1e8_alignRot = zeus::CQuaternion();\n          x220_27_ = false;\n          break;\n        }\n        default:\n          break;\n        }\n      }\n    }\n  }\n\n  offset += results.x8_deltas.x0_posDelta;\n  if (x220_26_aligningPos)\n    offset += x1dc_alignPos * time.GetSeconds();\n\n  zeus::CQuaternion rot = results.x8_deltas.xc_rotDelta * x1e8_alignRot;\n  quat = quat * rot;\n  x1dc_alignPos = rot.transform(x1dc_alignPos);\n  time = results.x0_remTime;\n}\n\nvoid CAnimData::SetXRayModel(const TLockedToken<CModel>& model, const TLockedToken<CSkinRules>& skinRules) {\n  xf4_xrayModel = std::make_shared<CSkinnedModel>(model, skinRules, xd8_modelData->GetLayoutInfo());\n  xf4_xrayModel->CalculateDefault();\n}\n\nvoid CAnimData::SetInfraModel(const TLockedToken<CModel>& model, const TLockedToken<CSkinRules>& skinRules) {\n  xf8_infraModel = std::make_shared<CSkinnedModel>(model, skinRules, xd8_modelData->GetLayoutInfo());\n  xf4_xrayModel->CalculateDefault();\n}\n\nvoid CAnimData::PoseSkinnedModel(CSkinnedModel& model, const CPoseAsTransforms& pose, CVertexMorphEffect* morphEffect,\n                                 TConstVectorRef averagedNormals) {\n  model.Calculate(pose, morphEffect, averagedNormals, nullptr);\n}\n\nvoid CAnimData::AdvanceParticles(const zeus::CTransform& xf, float dt, const zeus::CVector3f& vec,\n                                 CStateManager& stateMgr) {\n  x120_particleDB.Update(dt, x224_pose, *xcc_layoutData, xf, vec, stateMgr);\n}\n\nfloat CAnimData::GetAverageVelocity(int animIn) const {\n  std::shared_ptr<IMetaAnim> anim = x100_animMgr->GetMetaAnimation(xc_charInfo.GetAnimationIndex(animIn));\n  std::set<CPrimitive> prims;\n  anim->GetUniquePrimitives(prims);\n\n  SObjectTag tag{FOURCC('ANIM'), {}};\n  float velAccum = 0.f;\n  float durAccum = 0.f;\n  for (const CPrimitive& prim : prims) {\n    tag.id = prim.GetAnimResId();\n    TLockedToken<CAllFormatsAnimSource> animRes = xfc_animCtx->xc_store.GetObj(tag);\n\n    CCharAnimTime dur;\n    float avgVel;\n    switch (animRes->GetFormat()) {\n    case EAnimFormat::Uncompressed:\n    default: {\n      const CAnimSource& src = animRes->GetAsCAnimSource();\n      dur = src.GetDuration();\n      avgVel = src.GetAverageVelocity();\n      break;\n    }\n    case EAnimFormat::BitstreamCompressed:\n    case EAnimFormat::BitstreamCompressed24: {\n      const CFBStreamedCompression& src = animRes->GetAsCFBStreamedCompression();\n      dur = src.GetAnimationDuration();\n      avgVel = src.GetAverageVelocity();\n      break;\n    }\n    }\n\n    velAccum += dur.GetSeconds() * avgVel;\n    durAccum += dur.GetSeconds();\n  }\n\n  if (durAccum > 0.f)\n    return velAccum / durAccum;\n  return 0.f;\n}\n\nvoid CAnimData::ResetPOILists() {\n  x20c_passedBoolCount = 0;\n  x210_passedIntCount = 0;\n  x214_passedParticleCount = 0;\n  x218_passedSoundCount = 0;\n}\n\nCSegId CAnimData::GetLocatorSegId(std::string_view name) const { return xcc_layoutData->GetSegIdFromString(name); }\n\nzeus::CAABox CAnimData::GetBoundingBox(const zeus::CTransform& xf) const {\n  return GetBoundingBox().getTransformedAABox(xf);\n}\n\nzeus::CAABox CAnimData::GetBoundingBox() const {\n  auto aabbList = xc_charInfo.GetAnimBBoxList();\n  if (aabbList.empty())\n    return x108_aabb;\n\n  CAnimTreeEffectiveContribution contrib = x1f8_animRoot->GetContributionOfHighestInfluence();\n  auto search = rstl::binary_find(\n      aabbList.cbegin(), aabbList.cend(), contrib.x4_name,\n      [](const std::pair<std::string, zeus::CAABox>& other) -> const std::string& { return other.first; });\n  if (search == aabbList.cend())\n    return x108_aabb;\n\n  return search->second;\n}\n\nvoid CAnimData::SubstituteModelData(const TCachedToken<CSkinnedModel>& model) {\n  xd8_modelData = model;\n  x108_aabb = {};\n  for (const auto& item : xd8_modelData->GetModel()->GetPositions()) {\n    x108_aabb.accumulateBounds({item.x, item.y, item.z});\n  }\n}\n\nvoid CAnimData::SetParticleCEXTValue(std::string_view name, int idx, float value) {\n  auto search = std::find_if(xc_charInfo.x98_effects.begin(), xc_charInfo.x98_effects.end(),\n                             [&name](const auto& v) { return v.first == name; });\n  if (search != xc_charInfo.x98_effects.end() && search->second.size())\n    x120_particleDB.SetCEXTValue(search->second.front().GetComponentName(), idx, value);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimData.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <set>\n#include <vector>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Character/CAdditiveAnimPlayback.hpp\"\n#include \"Runtime/Character/CAnimPlaybackParms.hpp\"\n#include \"Runtime/Character/CCharLayoutInfo.hpp\"\n#include \"Runtime/Character/CCharacterFactory.hpp\"\n#include \"Runtime/Character/CCharacterInfo.hpp\"\n#include \"Runtime/Character/CHierarchyPoseBuilder.hpp\"\n#include \"Runtime/Character/CParticleDatabase.hpp\"\n#include \"Runtime/Character/CPoseAsTransforms.hpp\"\n#include \"Runtime/Character/IAnimReader.hpp\"\n#include \"Runtime/Graphics/CSkinnedModel.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CQuaternion.hpp>\n#include <zeus/CVector3f.hpp>\n\nenum class EUserEventType {\n  Projectile = 0,\n  EggLay = 1,\n  LoopedSoundStop = 2,\n  AlignTargetPos = 3,\n  AlignTargetRot = 4,\n  ChangeMaterial = 5,\n  Delete = 6,\n  GenerateEnd = 7,\n  DamageOn = 8,\n  DamageOff = 9,\n  AlignTargetPosStart = 10,\n  DeGenerate = 11,\n  Landing = 12,\n  TakeOff = 13,\n  FadeIn = 14,\n  FadeOut = 15,\n  ScreenShake = 16,\n  BeginAction = 17,\n  EndAction = 18,\n  BecomeRagDoll = 19,\n  IkLock = 20,\n  IkRelease = 21,\n  BreakLockOn = 22,\n  BecomeShootThrough = 23,\n  RemoveCollision = 24,\n  ObjectPickUp = 25,\n  ObjectDrop = 26,\n  EventStart = 27,\n  EventStop = 28,\n  Activate = 29,\n  Deactivate = 30,\n  SoundPlay = 31,\n  SoundStop = 32,\n  EffectOn = 33,\n  EffectOff = 34\n};\n\nnamespace metaforce {\nclass CAnimTreeNode;\nclass CAnimationManager;\nclass CBoolPOINode;\nclass CCharAnimTime;\nclass CCharLayoutInfo;\nclass CInt32POINode;\nclass CModel;\nclass CSkinnedModelWithAvgNormals;\nclass CParticlePOINode;\nclass CPrimitive;\nclass CRandom16;\nclass CSegIdList;\nclass CSegStatementSet;\nclass CSkinRules;\nclass CSoundPOINode;\nclass CStateManager;\nclass CTransitionManager;\nclass CVertexMorphEffect;\nclass IAnimReader;\nclass IMetaAnim;\n\nstruct CAnimSysContext;\nstruct CModelFlags;\nstruct SAdvancementDeltas;\nstruct SAdvancementResults;\n\nclass CAnimData {\n  friend class CModelData;\n  friend class CActor;\n  friend class CPlayerGun;\n  friend class CGrappleArm;\n  friend class CWallCrawlerSwarm;\n\npublic:\n  enum class EAnimDir { Forward, Backward };\n\nprivate:\n  TLockedToken<CCharacterFactory> x0_charFactory;\n  CCharacterInfo xc_charInfo;\n  TLockedToken<CCharLayoutInfo> xcc_layoutData;\n  TLockedToken<CSkinnedModel> xd8_modelData;\n  TLockedToken<CSkinnedModelWithAvgNormals> xe4_iceModelData;\n  std::shared_ptr<CSkinnedModel> xf4_xrayModel;\n  std::shared_ptr<CSkinnedModel> xf8_infraModel;\n  std::shared_ptr<CAnimSysContext> xfc_animCtx;\n  std::shared_ptr<CAnimationManager> x100_animMgr;\n  EAnimDir x104_animDir = EAnimDir::Forward;\n  zeus::CAABox x108_aabb;\n  CParticleDatabase x120_particleDB;\n  CAssetId x1d8_selfId;\n  zeus::CVector3f x1dc_alignPos;\n  zeus::CQuaternion x1e8_alignRot;\n  std::shared_ptr<CAnimTreeNode> x1f8_animRoot;\n  std::shared_ptr<CTransitionManager> x1fc_transMgr;\n\n  float x200_speedScale = 1.f;\n  s32 x204_charIdx;\n  s32 x208_defaultAnim;\n  u32 x20c_passedBoolCount = 0;\n  u32 x210_passedIntCount = 0;\n  u32 x214_passedParticleCount = 0;\n  u32 x218_passedSoundCount = 0;\n  s32 x21c_particleLightIdx = 0;\n  bool x220_24_animating : 1 = false;\n  bool x220_25_loop : 1 = false;\n  bool x220_26_aligningPos : 1 = false;\n  bool x220_27_ : 1 = false;\n  bool x220_28_ : 1 = false;\n  bool x220_29_animationJustStarted : 1 = false;\n  bool x220_30_poseBuilt : 1 = false;\n  bool x220_31_poseCached : 1 = false;\n  CPoseAsTransforms x224_pose;\n  CHierarchyPoseBuilder x2fc_poseBuilder;\n\n  CAnimPlaybackParms x40c_playbackParms;\n  rstl::reserved_vector<std::pair<s32, CAdditiveAnimPlayback>, 8> x434_additiveAnims;\n\n  static rstl::reserved_vector<CBoolPOINode, 8> g_BoolPOINodes;\n  static rstl::reserved_vector<CInt32POINode, 16> g_Int32POINodes;\n  static rstl::reserved_vector<CParticlePOINode, 20> g_ParticlePOINodes;\n  static rstl::reserved_vector<CSoundPOINode, 20> g_SoundPOINodes;\n  static rstl::reserved_vector<CInt32POINode, 16> g_TransientInt32POINodes;\n\npublic:\n  CAnimData(CAssetId, const CCharacterInfo& character, int defaultAnim, int charIdx, bool loop,\n            TLockedToken<CCharLayoutInfo> layout, TToken<CSkinnedModel> model,\n            const std::optional<TToken<CSkinnedModelWithAvgNormals>>& iceModel,\n            const std::weak_ptr<CAnimSysContext>& ctx, std::shared_ptr<CAnimationManager> animMgr,\n            std::shared_ptr<CTransitionManager> transMgr, TLockedToken<CCharacterFactory> charFactory);\n\n  void SetParticleEffectState(std::string_view effectName, bool active, CStateManager& mgr);\n  void InitializeEffects(CStateManager& mgr, TAreaId aId, const zeus::CVector3f& scale);\n  CAssetId GetEventResourceIdForAnimResourceId(CAssetId id) const;\n  void AddAdditiveSegData(const CSegIdList& list, CSegStatementSet& stSet);\n  static SAdvancementResults AdvanceAdditiveAnim(std::shared_ptr<CAnimTreeNode>& anim, const CCharAnimTime& time);\n  SAdvancementDeltas AdvanceAdditiveAnims(float dt);\n  SAdvancementDeltas UpdateAdditiveAnims(float dt);\n  bool IsAdditiveAnimation(s32 idx) const;\n  bool IsAdditiveAnimationAdded(s32 idx) const;\n  const std::shared_ptr<CAnimTreeNode>& GetRootAnimationTree() const { return x1f8_animRoot; }\n  const std::shared_ptr<CAnimTreeNode>& GetAdditiveAnimationTree(s32 idx) const;\n  bool IsAdditiveAnimationActive(s32 idx) const;\n  void DelAdditiveAnimation(s32 idx);\n  void AddAdditiveAnimation(s32 idx, float weight, bool active, bool fadeOut);\n  float GetAdditiveAnimationWeight(s32 idx) const;\n  std::shared_ptr<CAnimationManager> GetAnimationManager();\n  const CCharacterInfo& GetCharacterInfo() const { return xc_charInfo; }\n  const CCharLayoutInfo& GetCharLayoutInfo() const { return *xcc_layoutData.GetObj(); }\n  void SetPhase(float ph);\n  void Touch(CSkinnedModel& model, int shaderIdx) const;\n  SAdvancementDeltas GetAdvancementDeltas(const CCharAnimTime& a, const CCharAnimTime& b) const;\n  CCharAnimTime GetTimeOfUserEvent(EUserEventType type, const CCharAnimTime& time) const;\n  void MultiplyPlaybackRate(float mul);\n  void SetPlaybackRate(float set);\n  void SetRandomPlaybackRate(CRandom16& r);\n  void CalcPlaybackAlignmentParms(const CAnimPlaybackParms& parms, const std::shared_ptr<CAnimTreeNode>& node);\n  zeus::CTransform GetLocatorTransform(CSegId id, const CCharAnimTime* time) const;\n  zeus::CTransform GetLocatorTransform(std::string_view name, const CCharAnimTime* time) const;\n  bool IsAnimTimeRemaining(float rem, std::string_view name) const;\n  float GetAnimTimeRemaining(std::string_view name) const;\n  float GetAnimationDuration(int animIn) const;\n  bool GetIsLoop() const { return x220_25_loop; }\n  void EnableLooping(bool val) {\n    x220_25_loop = val;\n    x220_24_animating = true;\n  }\n  void EnableAnimation(bool val) { x220_24_animating = val; }\n  bool IsAnimating() const { return x220_24_animating; }\n  void SetAnimDir(EAnimDir dir) { x104_animDir = dir; }\n  std::shared_ptr<CAnimSysContext> GetAnimSysContext() const;\n  std::shared_ptr<CAnimationManager> GetAnimationManager() const;\n  void RecalcPoseBuilder(const CCharAnimTime* time);\n  void RenderAuxiliary(const zeus::CFrustum& frustum) const;\n  void Render(CSkinnedModel& model, const CModelFlags& drawFlags, CVertexMorphEffect* morphEffect,\n              TConstVectorRef averagedNormals);\n  void SetupRender(CSkinnedModel& model, CVertexMorphEffect* morphEffect, TConstVectorRef averagedNormals);\n  static void DrawSkinnedModel(CSkinnedModel& model, const CModelFlags& flags);\n  void PreRender();\n  void BuildPose();\n  const CPoseAsTransforms& GetPose() const { return x224_pose; }\n  static void PrimitiveSetToTokenVector(const std::set<CPrimitive>& primSet, std::vector<CToken>& tokensOut,\n                                        bool preLock);\n  void GetAnimationPrimitives(const CAnimPlaybackParms& parms, std::set<CPrimitive>& primsOut) const;\n  void SetAnimation(const CAnimPlaybackParms& parms, bool noTrans);\n  SAdvancementDeltas DoAdvance(float dt, bool& suspendParticles, CRandom16& random, bool advTree);\n  SAdvancementDeltas Advance(float dt, const zeus::CVector3f& scale, CStateManager& stateMgr, TAreaId aid,\n                             bool advTree);\n  SAdvancementDeltas AdvanceIgnoreParticles(float dt, CRandom16& random, bool advTree);\n  void AdvanceAnim(CCharAnimTime& time, zeus::CVector3f& offset, zeus::CQuaternion& quat);\n  void SetXRayModel(const TLockedToken<CModel>& model, const TLockedToken<CSkinRules>& skinRules);\n  std::shared_ptr<CSkinnedModel> GetXRayModel() const { return xf4_xrayModel; }\n  void SetInfraModel(const TLockedToken<CModel>& model, const TLockedToken<CSkinRules>& skinRules);\n  std::shared_ptr<CSkinnedModel> GetInfraModel() const { return xf8_infraModel; }\n  TLockedToken<CSkinnedModel>& GetModelData() { return xd8_modelData; }\n  const TLockedToken<CSkinnedModel>& GetModelData() const { return xd8_modelData; }\n\n  static void PoseSkinnedModel(CSkinnedModel& model, const CPoseAsTransforms& pose, CVertexMorphEffect* morphEffect,\n                               TConstVectorRef averagedNormals);\n  void AdvanceParticles(const zeus::CTransform& xf, float dt, const zeus::CVector3f&, CStateManager& stateMgr);\n  float GetAverageVelocity(int animIn) const;\n  void ResetPOILists();\n  CSegId GetLocatorSegId(std::string_view name) const;\n  zeus::CAABox GetBoundingBox(const zeus::CTransform& xf) const;\n  zeus::CAABox GetBoundingBox() const;\n  void SubstituteModelData(const TCachedToken<CSkinnedModel>& model);\n  static void FreeCache();\n  static void InitializeCache();\n  CHierarchyPoseBuilder& PoseBuilder() { return x2fc_poseBuilder; }\n  const CHierarchyPoseBuilder& GetPoseBuilder() const { return x2fc_poseBuilder; }\n  const CParticleDatabase& GetParticleDB() const { return x120_particleDB; }\n  CParticleDatabase& GetParticleDB() { return x120_particleDB; }\n  void SetParticleCEXTValue(std::string_view name, int idx, float value);\n\n  float GetSpeedScale() const { return x200_speedScale; }\n  u32 GetPassedBoolPOICount() const { return x20c_passedBoolCount; }\n  u32 GetPassedIntPOICount() const { return x210_passedIntCount; }\n  u32 GetPassedParticlePOICount() const { return x214_passedParticleCount; }\n  u32 GetPassedSoundPOICount() const { return x218_passedSoundCount; }\n\n  s32 GetCharacterIndex() const { return x204_charIdx; }\n  u16 GetDefaultAnimation() const { return x208_defaultAnim; }\n  TLockedToken<CSkinnedModelWithAvgNormals>& GetIceModel() { return xe4_iceModelData; }\n  const TLockedToken<CSkinnedModelWithAvgNormals>& GetIceModel() const { return xe4_iceModelData; }\n  void SetParticleLightIdx(s32 idx) { x21c_particleLightIdx = idx; }\n\n  void MarkPoseDirty() { x220_30_poseBuilt = false; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimPOIData.cpp",
    "content": "#include \"Runtime/Character/CAnimPOIData.hpp\"\n\n#include \"Runtime/CToken.hpp\"\n\nnamespace metaforce {\n\nCAnimPOIData::CAnimPOIData(CInputStream& in) : x0_version(in.ReadLong()) {\n  u32 boolCount = in.ReadLong();\n  x4_boolNodes.reserve(boolCount);\n  for (u32 i = 0; i < boolCount; ++i)\n    x4_boolNodes.emplace_back(in);\n\n  u32 int32Count = in.ReadLong();\n  x14_int32Nodes.reserve(int32Count);\n  for (u32 i = 0; i < int32Count; ++i)\n    x14_int32Nodes.emplace_back(in);\n\n  u32 particleCount = in.ReadLong();\n  x24_particleNodes.reserve(particleCount);\n  for (u32 i = 0; i < particleCount; ++i)\n    x24_particleNodes.emplace_back(in);\n\n  if (x0_version >= 2) {\n    u32 soundCount = in.ReadLong();\n    x34_soundNodes.reserve(soundCount);\n    for (u32 i = 0; i < soundCount; ++i)\n      x34_soundNodes.emplace_back(in);\n  }\n}\n\nCFactoryFnReturn AnimPOIDataFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& parms,\n                                    CObjectReference* selfRef) {\n  return TToken<CAnimPOIData>::GetIObjObjectFor(std::make_unique<CAnimPOIData>(in));\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimPOIData.hpp",
    "content": "#pragma once\n\n#include <vector>\n\n#include \"Runtime/CFactoryMgr.hpp\"\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/Character/CBoolPOINode.hpp\"\n#include \"Runtime/Character/CInt32POINode.hpp\"\n#include \"Runtime/Character/CParticlePOINode.hpp\"\n#include \"Runtime/Character/CSoundPOINode.hpp\"\n\nnamespace metaforce {\n\nclass CAnimPOIData {\n  u32 x0_version;\n  std::vector<CBoolPOINode> x4_boolNodes;\n  std::vector<CInt32POINode> x14_int32Nodes;\n  std::vector<CParticlePOINode> x24_particleNodes;\n  std::vector<CSoundPOINode> x34_soundNodes;\n\npublic:\n  explicit CAnimPOIData(CInputStream& in);\n\n  const std::vector<CBoolPOINode>& GetBoolPOIStream() const { return x4_boolNodes; }\n  const std::vector<CInt32POINode>& GetInt32POIStream() const { return x14_int32Nodes; }\n  const std::vector<CParticlePOINode>& GetParticlePOIStream() const { return x24_particleNodes; }\n  const std::vector<CSoundPOINode>& GetSoundPOIStream() const { return x34_soundNodes; }\n};\n\nCFactoryFnReturn AnimPOIDataFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& parms,\n                                    CObjectReference* selfRef);\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimPerSegmentData.hpp",
    "content": "#pragma once\n\n#include <zeus/CQuaternion.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\n\nstruct CAnimPerSegmentData {\n  zeus::CQuaternion x0_rotation;\n  zeus::CVector3f x10_offset;\n  bool x1c_hasOffset = false;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimPlaybackParms.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n\n#include <zeus/CQuaternion.hpp>\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CAnimPlaybackParms {\n  s32 x0_animA = -1;\n  s32 x4_animB = -1;\n  float x8_blendWeight = 1.f;\n  bool xc_animating = true;\n  // s32 x10_ = 0;\n  const zeus::CVector3f* x14_targetPos = nullptr;\n  bool x18_useLocator = false;\n  const zeus::CQuaternion* x1c_deltaOrient = nullptr;\n  const zeus::CTransform* x20_objectXf = nullptr;\n  const zeus::CVector3f* x24_objectScale = nullptr;\n\npublic:\n  constexpr CAnimPlaybackParms() = default;\n  constexpr CAnimPlaybackParms(s32 animA, s32 animB, float blendWeight, bool animating)\n  : x0_animA(animA), x4_animB(animB), x8_blendWeight(blendWeight), xc_animating(animating) {}\n  constexpr CAnimPlaybackParms(s32 anim, const zeus::CQuaternion* deltaOrient, const zeus::CVector3f* targetPos,\n                               const zeus::CTransform* xf, const zeus::CVector3f* scale, bool useLocator)\n  : x0_animA(anim)\n  , x14_targetPos(targetPos)\n  , x18_useLocator(useLocator)\n  , x1c_deltaOrient(deltaOrient)\n  , x20_objectXf(xf)\n  , x24_objectScale(scale) {}\n\n  constexpr const zeus::CTransform* GetObjectXform() const { return x20_objectXf; }\n  constexpr const zeus::CQuaternion* GetDeltaOrient() const { return x1c_deltaOrient; }\n  constexpr const zeus::CVector3f* GetTargetPos() const { return x14_targetPos; }\n  constexpr bool GetIsUseLocator() const { return x18_useLocator; }\n  constexpr const zeus::CVector3f* GetObjectScale() const { return x24_objectScale; }\n  constexpr s32 GetAnimationId() const { return x0_animA; }\n  constexpr s32 GetSecondAnimationId() const { return x4_animB; }\n  constexpr float GetBlendFactor() const { return x8_blendWeight; }\n  constexpr void SetAnimationId(s32 id) { x0_animA = id; }\n  constexpr void SetSecondAnimationId(s32 id) { x4_animB = id; }\n  constexpr void SetBlendFactor(float f) { x8_blendWeight = f; }\n  constexpr bool GetIsPlayAnimation() const { return xc_animating; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimSource.cpp",
    "content": "#include \"Runtime/Character/CAnimSource.hpp\"\n\n#include <algorithm>\n\n#include \"Runtime/Character/CAnimPOIData.hpp\"\n#include \"Runtime/Character/CSegId.hpp\"\n#include \"Runtime/Character/CSegIdList.hpp\"\n#include \"Runtime/Character/CSegStatementSet.hpp\"\n\nnamespace metaforce {\n\nstatic constexpr float ClampZeroToOne(float in) { return std::clamp(in, 0.0f, 1.0f); }\n\nu32 RotationAndOffsetStorage::DataSizeInBytes(u32 rotPerFrame, u32 transPerFrame, u32 frameCount) {\n  return (transPerFrame * 12 + rotPerFrame * 16) * frameCount;\n}\n\nvoid RotationAndOffsetStorage::CopyRotationsAndOffsets(const std::vector<zeus::CQuaternion>& rots,\n                                                       const std::vector<zeus::CVector3f>& offs, u32 frameCount,\n                                                       float* arrOut) {\n  std::vector<zeus::CQuaternion>::const_iterator rit = rots.cbegin();\n  std::vector<zeus::CVector3f>::const_iterator oit = offs.cbegin();\n  u32 rotsPerFrame = rots.size() / frameCount;\n  u32 offsPerFrame = offs.size() / frameCount;\n  for (u32 i = 0; i < frameCount; ++i) {\n    for (u32 j = 0; j < rotsPerFrame; ++j) {\n      const zeus::CQuaternion& rot = *rit++;\n      arrOut[0] = rot.w();\n      arrOut[1] = rot.x();\n      arrOut[2] = rot.y();\n      arrOut[3] = rot.z();\n      arrOut += 4;\n    }\n    for (u32 j = 0; j < offsPerFrame; ++j) {\n      const zeus::CVector3f& off = *oit++;\n      arrOut[0] = off.x();\n      arrOut[1] = off.y();\n      arrOut[2] = off.z();\n      arrOut += 3;\n    }\n  }\n}\n\nstd::unique_ptr<float[]> RotationAndOffsetStorage::GetRotationsAndOffsets(const std::vector<zeus::CQuaternion>& rots,\n                                                                          const std::vector<zeus::CVector3f>& offs,\n                                                                          u32 frameCount) {\n  u32 size = DataSizeInBytes(rots.size() / frameCount, offs.size() / frameCount, frameCount);\n  auto ret = std::make_unique<float[]>((size / 4 + 1) * 4);\n  CopyRotationsAndOffsets(rots, offs, frameCount, ret.get());\n  return ret;\n}\n\nRotationAndOffsetStorage::CRotationAndOffsetVectors::CRotationAndOffsetVectors(CInputStream& in) {\n  const u32 quatCount = in.ReadLong();\n  x0_rotations.reserve(quatCount);\n  for (u32 i = 0; i < quatCount; ++i) {\n    x0_rotations.emplace_back() = in.Get<zeus::CQuaternion>();\n  }\n\n  const u32 vecCount = in.ReadLong();\n  x10_offsets.reserve(vecCount);\n  for (u32 i = 0; i < vecCount; ++i) {\n    x10_offsets.emplace_back() = in.Get<zeus::CVector3f>();\n  }\n}\n\nu32 RotationAndOffsetStorage::GetFrameSizeInBytes() const { return (x10_transPerFrame * 12 + xc_rotPerFrame * 16); }\n\nRotationAndOffsetStorage::RotationAndOffsetStorage(const CRotationAndOffsetVectors& vectors, u32 frameCount) {\n  x0_storage = GetRotationsAndOffsets(vectors.x0_rotations, vectors.x10_offsets, frameCount);\n  x8_frameCount = frameCount;\n  xc_rotPerFrame = vectors.x0_rotations.size() / frameCount;\n  x10_transPerFrame = vectors.x10_offsets.size() / frameCount;\n}\n\nstatic std::vector<u8> ReadIndexTable(CInputStream& in) {\n  std::vector<u8> ret;\n  u32 count = in.ReadLong();\n  ret.reserve(count);\n  for (u32 i = 0; i < count; ++i)\n    ret.push_back(in.ReadUint8());\n  return ret;\n}\n\nvoid CAnimSource::CalcAverageVelocity() {\n  u8 rootIdx = x20_rotationChannels[3];\n  u8 rootTransIdx = x30_translationChannels[rootIdx];\n  float accum = 0.f;\n  const u32 floatsPerFrame = x40_data.x10_transPerFrame * 3 + x40_data.xc_rotPerFrame * 4;\n  const u32 rotFloatsPerFrame = x40_data.xc_rotPerFrame * 4;\n  for (u32 i = 1; i < x10_frameCount; ++i) {\n    const float* frameDataA = &x40_data.x0_storage[(i - 1) * floatsPerFrame + rotFloatsPerFrame + rootTransIdx * 3];\n    const float* frameDataB = &x40_data.x0_storage[i * floatsPerFrame + rotFloatsPerFrame + rootTransIdx * 3];\n    zeus::CVector3f vecA(frameDataA[0], frameDataA[1], frameDataA[2]);\n    zeus::CVector3f vecB(frameDataB[0], frameDataB[1], frameDataB[2]);\n    float frameVel = (vecB - vecA).magnitude();\n    if (frameVel > 0.00001f)\n      accum += frameVel;\n  }\n  x60_averageVelocity = accum / x0_duration.GetSeconds();\n}\n\nCAnimSource::CAnimSource(CInputStream& in, IObjectStore& store)\n: x0_duration(in)\n, x8_interval(in)\n, x10_frameCount(in.ReadLong())\n, x1c_rootBone(in)\n, x20_rotationChannels(ReadIndexTable(in))\n, x30_translationChannels(ReadIndexTable(in))\n, x40_data(RotationAndOffsetStorage::CRotationAndOffsetVectors(in), x10_frameCount)\n, x54_evntId(in) {\n  if (x54_evntId.IsValid()) {\n    x58_evntData = store.GetObj({SBIG('EVNT'), x54_evntId});\n    x58_evntData.GetObj();\n  }\n  CalcAverageVelocity();\n}\n\nvoid CAnimSource::GetSegStatementSet(const CSegIdList& list, CSegStatementSet& set, const CCharAnimTime& time) const {\n  const auto frameIdx = u32(time / x8_interval);\n  const auto nextFrameIdx = (frameIdx + 1) % x10_frameCount;\n  float remTime = time.GetSeconds() - frameIdx * x8_interval.GetSeconds();\n  if (std::fabs(remTime) < 0.00001f) {\n    remTime = 0.f;\n  }\n\n  const float t = ClampZeroToOne(remTime / x8_interval.GetSeconds());\n  const u32 floatsPerFrame = x40_data.x10_transPerFrame * 3 + x40_data.xc_rotPerFrame * 4;\n  const u32 rotFloatsPerFrame = x40_data.xc_rotPerFrame * 4;\n\n  for (const CSegId& id : list.GetList()) {\n    const u8 rotIdx = x20_rotationChannels[id];\n    if (rotIdx != 0xff) {\n      const float* frameDataA = &x40_data.x0_storage[frameIdx * floatsPerFrame + rotIdx * 4];\n      const float* frameDataB = &x40_data.x0_storage[nextFrameIdx * floatsPerFrame + rotIdx * 4];\n\n      const zeus::CQuaternion quatA(frameDataA[0], frameDataA[1], frameDataA[2], frameDataA[3]);\n      const zeus::CQuaternion quatB(frameDataB[0], frameDataB[1], frameDataB[2], frameDataB[3]);\n      set[id].x0_rotation = zeus::CQuaternion::slerp(quatA, quatB, t);\n\n      const u8 transIdx = x30_translationChannels[rotIdx];\n      if (transIdx != 0xff) {\n        const float* frameVecDataA = &x40_data.x0_storage[frameIdx * floatsPerFrame + rotFloatsPerFrame + transIdx * 3];\n        const float* frameVecDataB =\n            &x40_data.x0_storage[nextFrameIdx * floatsPerFrame + rotFloatsPerFrame + transIdx * 3];\n        const zeus::CVector3f vecA(frameVecDataA[0], frameVecDataA[1], frameVecDataA[2]);\n        const zeus::CVector3f vecB(frameVecDataB[0], frameVecDataB[1], frameVecDataB[2]);\n        set[id].x10_offset = zeus::CVector3f::lerp(vecA, vecB, t);\n        set[id].x1c_hasOffset = true;\n      }\n    }\n  }\n}\n\nconst std::vector<CSoundPOINode>& CAnimSource::GetSoundPOIStream() const { return x58_evntData->GetSoundPOIStream(); }\n\nconst std::vector<CParticlePOINode>& CAnimSource::GetParticlePOIStream() const {\n  return x58_evntData->GetParticlePOIStream();\n}\n\nconst std::vector<CInt32POINode>& CAnimSource::GetInt32POIStream() const { return x58_evntData->GetInt32POIStream(); }\n\nconst std::vector<CBoolPOINode>& CAnimSource::GetBoolPOIStream() const { return x58_evntData->GetBoolPOIStream(); }\n\nzeus::CQuaternion CAnimSource::GetRotation(const CSegId& seg, const CCharAnimTime& time) const {\n  u8 rotIdx = x20_rotationChannels[seg];\n  if (rotIdx != 0xff) {\n    const auto frameIdx = u32(time / x8_interval);\n    const auto nextFrameIdx = (frameIdx + 1) % x10_frameCount;\n    float remTime = time.GetSeconds() - frameIdx * x8_interval.GetSeconds();\n    if (std::fabs(remTime) < 0.00001f)\n      remTime = 0.f;\n    float t = ClampZeroToOne(remTime / x8_interval.GetSeconds());\n\n    const u32 floatsPerFrame = x40_data.x10_transPerFrame * 3 + x40_data.xc_rotPerFrame * 4;\n    const float* frameDataA = &x40_data.x0_storage[frameIdx * floatsPerFrame + rotIdx * 4];\n    const float* frameDataB = &x40_data.x0_storage[nextFrameIdx * floatsPerFrame + rotIdx * 4];\n\n    zeus::CQuaternion quatA(frameDataA[0], frameDataA[1], frameDataA[2], frameDataA[3]);\n    zeus::CQuaternion quatB(frameDataB[0], frameDataB[1], frameDataB[2], frameDataB[3]);\n    return zeus::CQuaternion::slerp(quatA, quatB, t);\n  } else {\n    return {};\n  }\n}\n\nzeus::CVector3f CAnimSource::GetOffset(const CSegId& seg, const CCharAnimTime& time) const {\n  u8 rotIdx = x20_rotationChannels[seg];\n  if (rotIdx != 0xff) {\n    u8 transIdx = x30_translationChannels[rotIdx];\n    if (transIdx == 0xff)\n      return {};\n\n    const auto frameIdx = u32(time / x8_interval);\n    const auto nextFrameIdx = (frameIdx + 1) % x10_frameCount;\n    float remTime = time.GetSeconds() - frameIdx * x8_interval.GetSeconds();\n    if (std::fabs(remTime) < 0.00001f)\n      remTime = 0.f;\n    float t = ClampZeroToOne(remTime / x8_interval.GetSeconds());\n\n    const u32 floatsPerFrame = x40_data.x10_transPerFrame * 3 + x40_data.xc_rotPerFrame * 4;\n    const u32 rotFloatsPerFrame = x40_data.xc_rotPerFrame * 4;\n    const float* frameDataA = &x40_data.x0_storage[frameIdx * floatsPerFrame + rotFloatsPerFrame + transIdx * 3];\n    const float* frameDataB = &x40_data.x0_storage[nextFrameIdx * floatsPerFrame + rotFloatsPerFrame + transIdx * 3];\n    zeus::CVector3f vecA(frameDataA[0], frameDataA[1], frameDataA[2]);\n    zeus::CVector3f vecB(frameDataB[0], frameDataB[1], frameDataB[2]);\n    return zeus::CVector3f::lerp(vecA, vecB, t);\n  } else {\n    return {};\n  }\n}\n\nbool CAnimSource::HasOffset(const CSegId& seg) const {\n  u8 rotIdx = x20_rotationChannels[seg];\n  if (rotIdx == 0xff)\n    return false;\n  u8 transIdx = x30_translationChannels[rotIdx];\n  return transIdx != 0xff;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimSource.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <vector>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Character/CCharAnimTime.hpp\"\n#include \"Runtime/Character/CSegId.hpp\"\n\n#include <zeus/CQuaternion.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CAnimPOIData;\nclass CBoolPOINode;\nclass CInt32POINode;\nclass CParticlePOINode;\nclass CSegId;\nclass CSegIdList;\nclass CSegStatementSet;\nclass CSoundPOINode;\nclass IObjectStore;\n\nclass RotationAndOffsetStorage {\n  friend class CAnimSource;\n  std::unique_ptr<float[]> x0_storage;\n  u32 x8_frameCount;\n  u32 xc_rotPerFrame;\n  u32 x10_transPerFrame;\n\n  std::unique_ptr<float[]> GetRotationsAndOffsets(const std::vector<zeus::CQuaternion>& rots,\n                                                  const std::vector<zeus::CVector3f>& offs, u32 frameCount);\n  static void CopyRotationsAndOffsets(const std::vector<zeus::CQuaternion>& rots,\n                                      const std::vector<zeus::CVector3f>& offs, u32 frameCount, float*);\n  static u32 DataSizeInBytes(u32 rotPerFrame, u32 transPerFrame, u32 frameCount);\n\npublic:\n  struct CRotationAndOffsetVectors {\n    std::vector<zeus::CQuaternion> x0_rotations;\n    std::vector<zeus::CVector3f> x10_offsets;\n    explicit CRotationAndOffsetVectors(CInputStream& in);\n  };\n  u32 GetFrameSizeInBytes() const;\n  RotationAndOffsetStorage(const CRotationAndOffsetVectors& vectors, u32 frameCount);\n};\n\nclass CAnimSource {\n  friend class CAnimSourceInfo;\n  CCharAnimTime x0_duration;\n  CCharAnimTime x8_interval;\n  u32 x10_frameCount;\n  CSegId x1c_rootBone;\n  std::vector<u8> x20_rotationChannels;\n  std::vector<u8> x30_translationChannels;\n  RotationAndOffsetStorage x40_data;\n  CAssetId x54_evntId;\n  TCachedToken<CAnimPOIData> x58_evntData;\n  float x60_averageVelocity;\n\n  void CalcAverageVelocity();\n\npublic:\n  explicit CAnimSource(CInputStream& in, IObjectStore& store);\n\n  void GetSegStatementSet(const CSegIdList& list, CSegStatementSet& set, const CCharAnimTime& time) const;\n\n  const std::vector<CSoundPOINode>& GetSoundPOIStream() const;\n  const std::vector<CParticlePOINode>& GetParticlePOIStream() const;\n  const std::vector<CInt32POINode>& GetInt32POIStream() const;\n  const std::vector<CBoolPOINode>& GetBoolPOIStream() const;\n  const TCachedToken<CAnimPOIData>& GetPOIData() const { return x58_evntData; }\n  float GetAverageVelocity() const { return x60_averageVelocity; }\n  zeus::CQuaternion GetRotation(const CSegId& seg, const CCharAnimTime& time) const;\n  zeus::CVector3f GetOffset(const CSegId& seg, const CCharAnimTime& time) const;\n  bool HasOffset(const CSegId& seg) const;\n  const CCharAnimTime& GetDuration() const { return x0_duration; }\n  const CSegId& GetRootBoneId() const { return x1c_rootBone; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimSourceReader.cpp",
    "content": "#include \"Runtime/Character/CAnimSourceReader.hpp\"\n\n#include <algorithm>\n\n#include \"Runtime/Character/CBoolPOINode.hpp\"\n#include \"Runtime/Character/CFBStreamedAnimReader.hpp\"\n#include \"Runtime/Character/CInt32POINode.hpp\"\n#include \"Runtime/Character/CParticlePOINode.hpp\"\n#include \"Runtime/Character/CSoundPOINode.hpp\"\n\nnamespace metaforce {\n\nCAnimSourceInfo::CAnimSourceInfo(TSubAnimTypeToken<CAnimSource> token) : x4_token(std::move(token)) {}\n\nbool CAnimSourceInfo::HasPOIData() const { return x4_token->x58_evntData.HasReference(); }\n\nconst std::vector<CBoolPOINode>& CAnimSourceInfo::GetBoolPOIStream() const { return x4_token->GetBoolPOIStream(); }\n\nconst std::vector<CInt32POINode>& CAnimSourceInfo::GetInt32POIStream() const { return x4_token->GetInt32POIStream(); }\n\nconst std::vector<CParticlePOINode>& CAnimSourceInfo::GetParticlePOIStream() const {\n  return x4_token->GetParticlePOIStream();\n}\n\nconst std::vector<CSoundPOINode>& CAnimSourceInfo::GetSoundPOIStream() const { return x4_token->GetSoundPOIStream(); }\n\nCCharAnimTime CAnimSourceInfo::GetAnimationDuration() const { return x4_token->GetDuration(); }\n\nstd::set<std::pair<std::string, s32>> CAnimSourceReaderBase::GetUniqueParticlePOIs() const {\n  const std::vector<CParticlePOINode>& particleNodes = x4_sourceInfo->GetParticlePOIStream();\n  std::set<std::pair<std::string, s32>> ret;\n  for (const CParticlePOINode& node : particleNodes) {\n    if (node.GetUnique()) {\n      ret.emplace(node.GetString(), node.GetIndex());\n    }\n  }\n  return ret;\n}\n\nstd::set<std::pair<std::string, s32>> CAnimSourceReaderBase::GetUniqueInt32POIs() const {\n  const std::vector<CInt32POINode>& int32Nodes = x4_sourceInfo->GetInt32POIStream();\n  std::set<std::pair<std::string, s32>> ret;\n  for (const CInt32POINode& node : int32Nodes) {\n    if (node.GetUnique()) {\n      ret.emplace(node.GetString(), node.GetIndex());\n    }\n  }\n  return ret;\n}\n\nstd::set<std::pair<std::string, s32>> CAnimSourceReaderBase::GetUniqueBoolPOIs() const {\n  const std::vector<CBoolPOINode>& boolNodes = x4_sourceInfo->GetBoolPOIStream();\n  std::set<std::pair<std::string, s32>> ret;\n  for (const CBoolPOINode& node : boolNodes) {\n    if (node.GetUnique()) {\n      ret.emplace(node.GetString(), node.GetIndex());\n    }\n  }\n  return ret;\n}\n\nvoid CAnimSourceReaderBase::PostConstruct(const CCharAnimTime& time) {\n  x14_passedBoolCount = 0;\n  x18_passedIntCount = 0;\n  x1c_passedParticleCount = 0;\n  x20_passedSoundCount = 0;\n\n  if (x4_sourceInfo->HasPOIData()) {\n    std::set<std::pair<std::string, s32>> boolPOIs = GetUniqueBoolPOIs();\n    std::set<std::pair<std::string, s32>> int32POIs = GetUniqueInt32POIs();\n    std::set<std::pair<std::string, s32>> particlePOIs = GetUniqueParticlePOIs();\n\n    x24_boolStates.resize(boolPOIs.size());\n    x34_int32States.resize(int32POIs.size());\n    x44_particleStates.resize(particlePOIs.size());\n\n    for (const auto& poi : boolPOIs)\n      x24_boolStates[poi.second] = std::make_pair(poi.first, false);\n    for (const auto& poi : int32POIs)\n      x34_int32States[poi.second] = std::make_pair(poi.first, 0);\n    for (const auto& poi : particlePOIs)\n      x44_particleStates[poi.second] = std::make_pair(poi.first, CParticleData::EParentedMode::Initial);\n  }\n\n  CCharAnimTime tmpTime = time;\n  if (tmpTime.GreaterThanZero()) {\n    while (tmpTime.GreaterThanZero()) {\n      SAdvancementResults res = VAdvanceView(tmpTime);\n      tmpTime = res.x0_remTime;\n    }\n  } else if (x4_sourceInfo->HasPOIData()) {\n    UpdatePOIStates();\n    if (!time.GreaterThanZero()) {\n      x14_passedBoolCount = 0;\n      x18_passedIntCount = 0;\n      x1c_passedParticleCount = 0;\n      x20_passedSoundCount = 0;\n    }\n  }\n}\n\nvoid CAnimSourceReaderBase::UpdatePOIStates() {\n  const std::vector<CBoolPOINode>& boolNodes = x4_sourceInfo->GetBoolPOIStream();\n  const std::vector<CInt32POINode>& int32Nodes = x4_sourceInfo->GetInt32POIStream();\n  const std::vector<CParticlePOINode>& particleNodes = x4_sourceInfo->GetParticlePOIStream();\n  const std::vector<CSoundPOINode>& soundNodes = x4_sourceInfo->GetSoundPOIStream();\n\n  while (x14_passedBoolCount < boolNodes.size() && boolNodes[x14_passedBoolCount].GetTime() <= xc_curTime) {\n    const auto& node = boolNodes[x14_passedBoolCount];\n    if (node.GetIndex() >= 0) {\n      x24_boolStates[node.GetIndex()].second = node.GetValue();\n    }\n    ++x14_passedBoolCount;\n  }\n\n  while (x18_passedIntCount < int32Nodes.size() && int32Nodes[x18_passedIntCount].GetTime() <= xc_curTime) {\n    const auto& node = int32Nodes[x18_passedIntCount];\n    if (node.GetIndex() >= 0) {\n      x34_int32States[node.GetIndex()].second = node.GetValue();\n    }\n    ++x18_passedIntCount;\n  }\n\n  while (x1c_passedParticleCount < particleNodes.size() &&\n         particleNodes[x1c_passedParticleCount].GetTime() <= xc_curTime) {\n    const auto& node = particleNodes[x1c_passedParticleCount];\n    if (node.GetIndex() >= 0) {\n      x44_particleStates[node.GetIndex()].second = node.GetParticleData().GetParentedMode();\n    }\n    ++x1c_passedParticleCount;\n  }\n\n  while (x20_passedSoundCount < soundNodes.size() && soundNodes[x20_passedSoundCount].GetTime() <= xc_curTime) {\n    ++x20_passedSoundCount;\n  }\n}\n\nsize_t CAnimSourceReaderBase::VGetBoolPOIList(const CCharAnimTime& time, CBoolPOINode* listOut, size_t capacity,\n                                              size_t iterator, u32 unk) const {\n  if (x4_sourceInfo->HasPOIData()) {\n    const std::vector<CBoolPOINode>& boolNodes = x4_sourceInfo->GetBoolPOIStream();\n    return _getPOIList(time, listOut, capacity, iterator, unk, boolNodes, xc_curTime, *x4_sourceInfo,\n                       x14_passedBoolCount);\n  }\n  return 0;\n}\n\nsize_t CAnimSourceReaderBase::VGetInt32POIList(const CCharAnimTime& time, CInt32POINode* listOut, size_t capacity,\n                                               size_t iterator, u32 unk) const {\n  if (x4_sourceInfo->HasPOIData()) {\n    const std::vector<CInt32POINode>& int32Nodes = x4_sourceInfo->GetInt32POIStream();\n    return _getPOIList(time, listOut, capacity, iterator, unk, int32Nodes, xc_curTime, *x4_sourceInfo,\n                       x18_passedIntCount);\n  }\n  return 0;\n}\n\nsize_t CAnimSourceReaderBase::VGetParticlePOIList(const CCharAnimTime& time, CParticlePOINode* listOut, size_t capacity,\n                                                  size_t iterator, u32 unk) const {\n  if (x4_sourceInfo->HasPOIData()) {\n    const std::vector<CParticlePOINode>& particleNodes = x4_sourceInfo->GetParticlePOIStream();\n    return _getPOIList(time, listOut, capacity, iterator, unk, particleNodes, xc_curTime, *x4_sourceInfo,\n                       x1c_passedParticleCount);\n  }\n  return 0;\n}\n\nsize_t CAnimSourceReaderBase::VGetSoundPOIList(const CCharAnimTime& time, CSoundPOINode* listOut, size_t capacity,\n                                               size_t iterator, u32 unk) const {\n  if (x4_sourceInfo->HasPOIData()) {\n    const std::vector<CSoundPOINode>& soundNodes = x4_sourceInfo->GetSoundPOIStream();\n    return _getPOIList(time, listOut, capacity, iterator, unk, soundNodes, xc_curTime, *x4_sourceInfo,\n                       x20_passedSoundCount);\n  }\n  return 0;\n}\n\nbool CAnimSourceReaderBase::VGetBoolPOIState(std::string_view name) const {\n  const auto iter = std::find_if(x24_boolStates.cbegin(), x24_boolStates.cend(),\n                                 [name](const auto& entry) { return entry.first == name; });\n\n  if (iter == x24_boolStates.cend()) {\n    return false;\n  }\n\n  return iter->second;\n}\n\ns32 CAnimSourceReaderBase::VGetInt32POIState(std::string_view name) const {\n  const auto iter = std::find_if(x34_int32States.cbegin(), x34_int32States.cend(),\n                                 [name](const auto& entry) { return entry.first == name; });\n\n  if (iter == x34_int32States.cend()) {\n    return 0;\n  }\n\n  return iter->second;\n}\n\nCParticleData::EParentedMode CAnimSourceReaderBase::VGetParticlePOIState(std::string_view name) const {\n  const auto iter = std::find_if(x44_particleStates.cbegin(), x44_particleStates.cend(),\n                                 [name](const auto& entry) { return entry.first == name; });\n\n  if (iter == x44_particleStates.cend()) {\n    return CParticleData::EParentedMode::Initial;\n  }\n\n  return iter->second;\n}\n\nCAnimSourceReaderBase::CAnimSourceReaderBase(std::unique_ptr<IAnimSourceInfo>&& sourceInfo, const CCharAnimTime& time)\n: x4_sourceInfo(std::move(sourceInfo)), xc_curTime(time) {}\n\nCAnimSourceReaderBase::CAnimSourceReaderBase(std::unique_ptr<IAnimSourceInfo>&& sourceInfo,\n                                             const CAnimSourceReaderBase& other)\n: x4_sourceInfo(std::move(sourceInfo))\n, xc_curTime(other.xc_curTime)\n, x14_passedBoolCount(other.x14_passedBoolCount)\n, x18_passedIntCount(other.x18_passedIntCount)\n, x1c_passedParticleCount(other.x1c_passedParticleCount)\n, x20_passedSoundCount(other.x20_passedSoundCount)\n, x24_boolStates(other.x24_boolStates)\n, x34_int32States(other.x34_int32States)\n, x44_particleStates(other.x44_particleStates) {}\n\nSAdvancementResults CAnimSourceReader::VGetAdvancementResults(const CCharAnimTime& dt,\n                                                              const CCharAnimTime& startOff) const {\n  SAdvancementResults ret;\n  CCharAnimTime accum = xc_curTime + startOff;\n\n  if (xc_curTime + startOff >= x54_source->GetDuration()) {\n    ret.x0_remTime = dt;\n    return ret;\n  } else if (dt.EqualsZero()) {\n    return ret;\n  } else {\n    CCharAnimTime prevTime = accum;\n    accum += dt;\n    CCharAnimTime remTime;\n    if (accum > x54_source->GetDuration()) {\n      remTime = accum - x54_source->GetDuration();\n      accum = x54_source->GetDuration();\n    }\n\n    zeus::CQuaternion ra = x54_source->GetRotation(3, prevTime).inverse();\n    zeus::CQuaternion rb = x54_source->GetRotation(3, accum);\n    ret.x0_remTime = remTime;\n    ret.x8_deltas.xc_rotDelta = rb * ra;\n\n    if (x54_source->HasOffset(3)) {\n      zeus::CVector3f ta = x54_source->GetOffset(3, prevTime);\n      zeus::CVector3f tb = x54_source->GetOffset(3, accum);\n      ret.x8_deltas.x0_posDelta = zeus::CMatrix3f(rb.inverse()) * (tb - ta);\n    }\n\n    return ret;\n  }\n}\n\nvoid CAnimSourceReader::VSetPhase(float phase) {\n  xc_curTime = phase * x54_source->GetDuration().GetSeconds();\n  if (x54_source->GetPOIData()) {\n    UpdatePOIStates();\n    if (!xc_curTime.GreaterThanZero()) {\n      x14_passedBoolCount = 0;\n      x18_passedIntCount = 0;\n      x1c_passedParticleCount = 0;\n      x20_passedSoundCount = 0;\n    }\n  }\n}\n\nSAdvancementResults CAnimSourceReader::VReverseView(const CCharAnimTime& dt) {\n  SAdvancementResults ret;\n\n  if (xc_curTime.EqualsZero()) {\n    xc_curTime = x54_source->GetDuration();\n    ret.x0_remTime = dt;\n    return ret;\n  } else if (dt.EqualsZero()) {\n    return ret;\n  } else {\n    CCharAnimTime prevTime = xc_curTime;\n    xc_curTime -= dt;\n    CCharAnimTime remTime;\n    if (xc_curTime < CCharAnimTime()) {\n      remTime = CCharAnimTime() - xc_curTime;\n      xc_curTime = CCharAnimTime();\n    }\n\n    if (x54_source->GetPOIData())\n      UpdatePOIStates();\n\n    zeus::CQuaternion ra = x54_source->GetRotation(3, prevTime).inverse();\n    zeus::CQuaternion rb = x54_source->GetRotation(3, xc_curTime);\n    ret.x0_remTime = remTime;\n    ret.x8_deltas.xc_rotDelta = rb * ra;\n\n    if (x54_source->HasOffset(3)) {\n      zeus::CVector3f ta = x54_source->GetOffset(3, prevTime);\n      zeus::CVector3f tb = x54_source->GetOffset(3, xc_curTime);\n      ret.x8_deltas.x0_posDelta = zeus::CMatrix3f(rb.inverse()) * (tb - ta);\n    }\n\n    return ret;\n  }\n}\n\nstd::unique_ptr<IAnimReader> CAnimSourceReader::VClone() const { return std::make_unique<CAnimSourceReader>(*this); }\n\nvoid CAnimSourceReader::VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut) const {\n  x54_source->GetSegStatementSet(list, setOut, xc_curTime);\n}\n\nvoid CAnimSourceReader::VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut,\n                                            const CCharAnimTime& time) const {\n  x54_source->GetSegStatementSet(list, setOut, time);\n}\n\nSAdvancementResults CAnimSourceReader::VAdvanceView(const CCharAnimTime& dt) {\n  SAdvancementResults ret;\n\n  if (xc_curTime == x54_source->GetDuration()) {\n    xc_curTime = {};\n    x14_passedBoolCount = 0;\n    x18_passedIntCount = 0;\n    x1c_passedParticleCount = 0;\n    x20_passedSoundCount = 0;\n    ret.x0_remTime = dt;\n    return ret;\n  } else if (dt.EqualsZero()) {\n    return ret;\n  } else {\n    CCharAnimTime prevTime = xc_curTime;\n    xc_curTime += dt;\n    CCharAnimTime remTime;\n    if (xc_curTime > x54_source->GetDuration()) {\n      remTime = xc_curTime - x54_source->GetDuration();\n      xc_curTime = x54_source->GetDuration();\n    }\n\n    if (x54_source->GetPOIData())\n      UpdatePOIStates();\n\n    zeus::CQuaternion ra = x54_source->GetRotation(3, prevTime).inverse();\n    zeus::CQuaternion rb = x54_source->GetRotation(3, xc_curTime);\n    ret.x0_remTime = remTime;\n    ret.x8_deltas.xc_rotDelta = rb * ra;\n\n    if (x54_source->HasOffset(3)) {\n      zeus::CVector3f ta = x54_source->GetOffset(3, prevTime);\n      zeus::CVector3f tb = x54_source->GetOffset(3, xc_curTime);\n      ret.x8_deltas.x0_posDelta = zeus::CMatrix3f(rb.inverse()) * (tb - ta);\n    }\n\n    return ret;\n  }\n}\n\nCCharAnimTime CAnimSourceReader::VGetTimeRemaining() const { return x54_source->GetDuration() - xc_curTime; }\n\nCSteadyStateAnimInfo CAnimSourceReader::VGetSteadyStateAnimInfo() const { return x64_steadyStateInfo; }\n\nbool CAnimSourceReader::VHasOffset(const CSegId& seg) const { return x54_source->HasOffset(seg); }\n\nzeus::CVector3f CAnimSourceReader::VGetOffset(const CSegId& seg) const {\n  return x54_source->GetOffset(seg, xc_curTime);\n}\n\nzeus::CVector3f CAnimSourceReader::VGetOffset(const CSegId& seg, const CCharAnimTime& time) const {\n  return x54_source->GetOffset(seg, time);\n}\n\nzeus::CQuaternion CAnimSourceReader::VGetRotation(const CSegId& seg) const {\n  return x54_source->GetRotation(seg, xc_curTime);\n}\n\nCAnimSourceReader::CAnimSourceReader(const TSubAnimTypeToken<CAnimSource>& source, const CCharAnimTime& time)\n: CAnimSourceReaderBase(std::make_unique<CAnimSourceInfo>(source), {})\n, x54_source(source)\n, x64_steadyStateInfo(false, source->GetDuration(), source->GetOffset(source->GetRootBoneId(), time)) {\n  PostConstruct(time);\n}\n\nCAnimSourceReader::CAnimSourceReader(const CAnimSourceReader& other)\n: CAnimSourceReaderBase(std::make_unique<CAnimSourceInfo>(other.x54_source), other)\n, x54_source(other.x54_source)\n, x64_steadyStateInfo(other.x64_steadyStateInfo) {}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimSourceReader.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <set>\n#include <utility>\n#include <vector>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/Character/CAnimSource.hpp\"\n#include \"Runtime/Character/CParticleData.hpp\"\n#include \"Runtime/Character/IAnimReader.hpp\"\n\nnamespace metaforce {\n\nclass IAnimSourceInfo {\npublic:\n  virtual ~IAnimSourceInfo() = default;\n  virtual bool HasPOIData() const = 0;\n  virtual const std::vector<CBoolPOINode>& GetBoolPOIStream() const = 0;\n  virtual const std::vector<CInt32POINode>& GetInt32POIStream() const = 0;\n  virtual const std::vector<CParticlePOINode>& GetParticlePOIStream() const = 0;\n  virtual const std::vector<CSoundPOINode>& GetSoundPOIStream() const = 0;\n  virtual CCharAnimTime GetAnimationDuration() const = 0;\n};\n\nclass CAnimSourceInfo : public IAnimSourceInfo {\n  TSubAnimTypeToken<CAnimSource> x4_token;\n\npublic:\n  explicit CAnimSourceInfo(TSubAnimTypeToken<CAnimSource> token);\n  bool HasPOIData() const override;\n  const std::vector<CBoolPOINode>& GetBoolPOIStream() const override;\n  const std::vector<CInt32POINode>& GetInt32POIStream() const override;\n  const std::vector<CParticlePOINode>& GetParticlePOIStream() const override;\n  const std::vector<CSoundPOINode>& GetSoundPOIStream() const override;\n  CCharAnimTime GetAnimationDuration() const override;\n};\n\nclass CAnimSourceReaderBase : public IAnimReader {\nprotected:\n  std::unique_ptr<IAnimSourceInfo> x4_sourceInfo;\n  CCharAnimTime xc_curTime;\n  u32 x14_passedBoolCount = 0;\n  u32 x18_passedIntCount = 0;\n  u32 x1c_passedParticleCount = 0;\n  u32 x20_passedSoundCount = 0;\n  std::vector<std::pair<std::string, bool>> x24_boolStates;\n  std::vector<std::pair<std::string, s32>> x34_int32States;\n  std::vector<std::pair<std::string, CParticleData::EParentedMode>> x44_particleStates;\n\n  std::set<std::pair<std::string, s32>> GetUniqueParticlePOIs() const;\n  std::set<std::pair<std::string, s32>> GetUniqueInt32POIs() const;\n  std::set<std::pair<std::string, s32>> GetUniqueBoolPOIs() const;\n\nprotected:\n  void PostConstruct(const CCharAnimTime& time);\n  void UpdatePOIStates();\n  CAnimSourceReaderBase(std::unique_ptr<IAnimSourceInfo>&& sourceInfo, const CAnimSourceReaderBase& other);\n\npublic:\n  CAnimSourceReaderBase(std::unique_ptr<IAnimSourceInfo>&& sourceInfo, const CCharAnimTime& time);\n\n  size_t VGetBoolPOIList(const CCharAnimTime& time, CBoolPOINode* listOut, size_t capacity, size_t iterator,\n                         u32) const override;\n  size_t VGetInt32POIList(const CCharAnimTime& time, CInt32POINode* listOut, size_t capacity, size_t iterator,\n                          u32) const override;\n  size_t VGetParticlePOIList(const CCharAnimTime& time, CParticlePOINode* listOut, size_t capacity, size_t iterator,\n                             u32) const override;\n  size_t VGetSoundPOIList(const CCharAnimTime& time, CSoundPOINode* listOut, size_t capacity, size_t iterator,\n                          u32) const override;\n  bool VGetBoolPOIState(std::string_view name) const override;\n  s32 VGetInt32POIState(std::string_view name) const override;\n  CParticleData::EParentedMode VGetParticlePOIState(std::string_view name) const override;\n\n  using IAnimReader::VGetOffset;\n  virtual zeus::CVector3f VGetOffset(const CSegId& seg, const CCharAnimTime& b) const = 0;\n  virtual bool VSupportsReverseView() const = 0;\n  virtual SAdvancementResults VReverseView(const CCharAnimTime& time) = 0;\n\n  const CCharAnimTime& GetCurTime() const { return xc_curTime; }\n};\n\nclass CAnimSourceReader : public CAnimSourceReaderBase {\n  TSubAnimTypeToken<CAnimSource> x54_source;\n  CSteadyStateAnimInfo x64_steadyStateInfo;\n\npublic:\n  CAnimSourceReader(const TSubAnimTypeToken<CAnimSource>& source, const CCharAnimTime& time);\n  CAnimSourceReader(const CAnimSourceReader& other);\n\n  SAdvancementResults VGetAdvancementResults(const CCharAnimTime& a, const CCharAnimTime& b) const override;\n  bool VSupportsReverseView() const override { return true; }\n  void VSetPhase(float) override;\n  SAdvancementResults VReverseView(const CCharAnimTime& time) override;\n  std::unique_ptr<IAnimReader> VClone() const override;\n  void VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut) const override;\n  void VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut, const CCharAnimTime& time) const override;\n  SAdvancementResults VAdvanceView(const CCharAnimTime& a) override;\n  CCharAnimTime VGetTimeRemaining() const override;\n  CSteadyStateAnimInfo VGetSteadyStateAnimInfo() const override;\n  bool VHasOffset(const CSegId& seg) const override;\n  zeus::CVector3f VGetOffset(const CSegId& seg) const override;\n  zeus::CVector3f VGetOffset(const CSegId& seg, const CCharAnimTime& time) const override;\n  zeus::CQuaternion VGetRotation(const CSegId& seg) const override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimSysContext.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/CRandom16.hpp\"\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/Character/CTransitionDatabaseGame.hpp\"\n\nnamespace metaforce {\nclass CSimplePool;\n\nstruct CAnimSysContext {\n  TToken<CTransitionDatabaseGame> x0_transDB;\n  std::shared_ptr<CRandom16> x8_random;\n  CSimplePool& xc_store;\n\n  CAnimSysContext(TToken<CTransitionDatabaseGame> transDB, u32 randomSeed, CSimplePool& store)\n  : x0_transDB(std::move(transDB)), x8_random(std::make_shared<CRandom16>(randomSeed)), xc_store(store) {}\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimTreeAnimReaderContainer.cpp",
    "content": "#include \"Runtime/Character/CAnimTreeAnimReaderContainer.hpp\"\n\n#include \"Runtime/Character/CFBStreamedAnimReader.hpp\"\n\nnamespace metaforce {\n\nCAnimTreeAnimReaderContainer::CAnimTreeAnimReaderContainer(std::string_view name, std::shared_ptr<IAnimReader> reader,\n                                                           u32 dbIdx)\n: CAnimTreeNode(name), x14_reader(std::move(reader)), x1c_animDbIdx(dbIdx) {}\n\nu32 CAnimTreeAnimReaderContainer::Depth() const { return 1; }\n\nCAnimTreeEffectiveContribution CAnimTreeAnimReaderContainer::VGetContributionOfHighestInfluence() const {\n  return {1.f, x4_name, VGetSteadyStateAnimInfo(), VGetTimeRemaining(), x1c_animDbIdx};\n}\n\nu32 CAnimTreeAnimReaderContainer::VGetNumChildren() const { return 0; }\n\nstd::shared_ptr<IAnimReader> CAnimTreeAnimReaderContainer::VGetBestUnblendedChild() const { return {}; }\n\nSAdvancementResults CAnimTreeAnimReaderContainer::VAdvanceView(const CCharAnimTime& dt) {\n  return x14_reader->VAdvanceView(dt);\n}\n\nCCharAnimTime CAnimTreeAnimReaderContainer::VGetTimeRemaining() const { return x14_reader->VGetTimeRemaining(); }\n\nCSteadyStateAnimInfo CAnimTreeAnimReaderContainer::VGetSteadyStateAnimInfo() const {\n  return x14_reader->VGetSteadyStateAnimInfo();\n}\n\nbool CAnimTreeAnimReaderContainer::VHasOffset(const CSegId& seg) const { return x14_reader->VHasOffset(seg); }\n\nzeus::CVector3f CAnimTreeAnimReaderContainer::VGetOffset(const CSegId& seg) const {\n  return x14_reader->VGetOffset(seg);\n}\n\nzeus::CQuaternion CAnimTreeAnimReaderContainer::VGetRotation(const CSegId& seg) const {\n  return x14_reader->VGetRotation(seg);\n}\n\nsize_t CAnimTreeAnimReaderContainer::VGetBoolPOIList(const CCharAnimTime& time, CBoolPOINode* listOut, size_t capacity,\n                                                     size_t iterator, u32 unk) const {\n  return x14_reader->GetBoolPOIList(time, listOut, capacity, iterator, unk);\n}\n\nsize_t CAnimTreeAnimReaderContainer::VGetInt32POIList(const CCharAnimTime& time, CInt32POINode* listOut,\n                                                      size_t capacity, size_t iterator, u32 unk) const {\n  return x14_reader->GetInt32POIList(time, listOut, capacity, iterator, unk);\n}\n\nsize_t CAnimTreeAnimReaderContainer::VGetParticlePOIList(const CCharAnimTime& time, CParticlePOINode* listOut,\n                                                         size_t capacity, size_t iterator, u32 unk) const {\n  return x14_reader->GetParticlePOIList(time, listOut, capacity, iterator, unk);\n}\n\nsize_t CAnimTreeAnimReaderContainer::VGetSoundPOIList(const CCharAnimTime& time, CSoundPOINode* listOut,\n                                                      size_t capacity, size_t iterator, u32 unk) const {\n  return x14_reader->GetSoundPOIList(time, listOut, capacity, iterator, unk);\n}\n\nbool CAnimTreeAnimReaderContainer::VGetBoolPOIState(std::string_view name) const {\n  return x14_reader->VGetBoolPOIState(name);\n}\n\ns32 CAnimTreeAnimReaderContainer::VGetInt32POIState(std::string_view name) const {\n  return x14_reader->VGetInt32POIState(name);\n}\n\nCParticleData::EParentedMode CAnimTreeAnimReaderContainer::VGetParticlePOIState(std::string_view name) const {\n  return x14_reader->VGetParticlePOIState(name);\n}\n\nvoid CAnimTreeAnimReaderContainer::VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut) const {\n  return x14_reader->VGetSegStatementSet(list, setOut);\n}\n\nvoid CAnimTreeAnimReaderContainer::VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut,\n                                                       const CCharAnimTime& time) const {\n  return x14_reader->VGetSegStatementSet(list, setOut, time);\n}\n\nstd::unique_ptr<IAnimReader> CAnimTreeAnimReaderContainer::VClone() const {\n  return std::make_unique<CAnimTreeAnimReaderContainer>(x4_name, x14_reader->Clone(), x1c_animDbIdx);\n}\n\nstd::optional<std::unique_ptr<IAnimReader>> CAnimTreeAnimReaderContainer::VSimplified() { return {}; }\n\nvoid CAnimTreeAnimReaderContainer::VSetPhase(float ph) { x14_reader->VSetPhase(ph); }\n\nSAdvancementResults CAnimTreeAnimReaderContainer::VGetAdvancementResults(const CCharAnimTime& a,\n                                                                         const CCharAnimTime& b) const {\n  return x14_reader->VGetAdvancementResults(a, b);\n}\n\nvoid CAnimTreeAnimReaderContainer::VGetWeightedReaders(\n    rstl::reserved_vector<std::pair<float, std::weak_ptr<IAnimReader>>, 16>& out, float w) const {\n  out.emplace_back(std::make_pair(w, x14_reader));\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimTreeAnimReaderContainer.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <string_view>\n#include <utility>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Character/CAnimTreeNode.hpp\"\n\nnamespace metaforce {\n\nclass CAnimTreeAnimReaderContainer : public CAnimTreeNode {\n  std::shared_ptr<IAnimReader> x14_reader;\n  u32 x1c_animDbIdx;\n\npublic:\n  CAnimTreeAnimReaderContainer(std::string_view name, std::shared_ptr<IAnimReader> reader, u32 animDbIdx);\n\n  u32 Depth() const override;\n  CAnimTreeEffectiveContribution VGetContributionOfHighestInfluence() const override;\n  u32 VGetNumChildren() const override;\n  std::shared_ptr<IAnimReader> VGetBestUnblendedChild() const override;\n  void VGetWeightedReaders(rstl::reserved_vector<std::pair<float, std::weak_ptr<IAnimReader>>, 16>& out,\n                           float w) const override;\n\n  SAdvancementResults VAdvanceView(const CCharAnimTime& a) override;\n  CCharAnimTime VGetTimeRemaining() const override;\n  CSteadyStateAnimInfo VGetSteadyStateAnimInfo() const override;\n  bool VHasOffset(const CSegId& seg) const override;\n  zeus::CVector3f VGetOffset(const CSegId& seg) const override;\n  zeus::CQuaternion VGetRotation(const CSegId& seg) const override;\n  size_t VGetBoolPOIList(const CCharAnimTime& time, CBoolPOINode* listOut, size_t capacity, size_t iterator,\n                         u32) const override;\n  size_t VGetInt32POIList(const CCharAnimTime& time, CInt32POINode* listOut, size_t capacity, size_t iterator,\n                          u32) const override;\n  size_t VGetParticlePOIList(const CCharAnimTime& time, CParticlePOINode* listOut, size_t capacity, size_t iterator,\n                             u32) const override;\n  size_t VGetSoundPOIList(const CCharAnimTime& time, CSoundPOINode* listOut, size_t capacity, size_t iterator,\n                          u32) const override;\n  bool VGetBoolPOIState(std::string_view name) const override;\n  s32 VGetInt32POIState(std::string_view name) const override;\n  CParticleData::EParentedMode VGetParticlePOIState(std::string_view name) const override;\n  void VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut) const override;\n  void VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut, const CCharAnimTime& time) const override;\n  std::unique_ptr<IAnimReader> VClone() const override;\n  std::optional<std::unique_ptr<IAnimReader>> VSimplified() override;\n  void VSetPhase(float) override;\n  SAdvancementResults VGetAdvancementResults(const CCharAnimTime& a, const CCharAnimTime& b) const override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimTreeBlend.cpp",
    "content": "#include \"Runtime/Character/CAnimTreeBlend.hpp\"\n\nnamespace metaforce {\n\nstd::string CAnimTreeBlend::CreatePrimitiveName(const std::shared_ptr<CAnimTreeNode>& a,\n                                                const std::shared_ptr<CAnimTreeNode>& b, float scale) {\n  return \"\";\n}\n\nCAnimTreeBlend::CAnimTreeBlend(bool b1, const std::shared_ptr<CAnimTreeNode>& a,\n                               const std::shared_ptr<CAnimTreeNode>& b, float blendWeight, std::string_view name)\n: CAnimTreeTweenBase(b1, a, b, 1 | 2, name), x24_blendWeight(blendWeight) {}\n\nSAdvancementResults CAnimTreeBlend::VAdvanceView(const CCharAnimTime& dt) {\n  IncAdvancementDepth();\n  SAdvancementResults resA = x14_a->VAdvanceView(dt);\n  SAdvancementResults resB = x18_b->VAdvanceView(dt);\n  DecAdvancementDepth();\n  if (ShouldCullTree()) {\n    if (GetBlendingWeight() < 0.5f)\n      x20_25_cullSelector = 1;\n    else\n      x20_25_cullSelector = 2;\n  }\n\n  const SAdvancementResults& maxRemTime = (resA.x0_remTime < resB.x0_remTime) ? resB : resA;\n  if (x1c_flags & 0x1) {\n    return {maxRemTime.x0_remTime, SAdvancementDeltas::Blend(resA.x8_deltas, resB.x8_deltas, GetBlendingWeight())};\n  } else {\n    return resB;\n  }\n}\n\nCCharAnimTime CAnimTreeBlend::VGetTimeRemaining() const {\n  CCharAnimTime remA = x14_a->VGetTimeRemaining();\n  CCharAnimTime remB = x18_b->VGetTimeRemaining();\n  return (remA < remB) ? remB : remA;\n}\n\nCSteadyStateAnimInfo CAnimTreeBlend::VGetSteadyStateAnimInfo() const {\n  CSteadyStateAnimInfo ssA = x14_a->VGetSteadyStateAnimInfo();\n  CSteadyStateAnimInfo ssB = x18_b->VGetSteadyStateAnimInfo();\n  zeus::CVector3f resOffset;\n  if (ssA.GetDuration() < ssB.GetDuration()) {\n    resOffset = ssA.GetOffset() * (ssB.GetDuration() / ssA.GetDuration()) * x24_blendWeight +\n                ssB.GetOffset() * (1.f - x24_blendWeight);\n  } else if (ssB.GetDuration() < ssA.GetDuration()) {\n    resOffset = ssA.GetOffset() * x24_blendWeight +\n                ssB.GetOffset() * (ssA.GetDuration() / ssB.GetDuration()) * (1.f - x24_blendWeight);\n  } else {\n    resOffset = ssA.GetOffset() + ssB.GetOffset();\n  }\n\n  return {ssA.IsLooping(), (ssA.GetDuration() < ssB.GetDuration()) ? ssB.GetDuration() : ssA.GetDuration(), resOffset};\n}\n\nstd::unique_ptr<IAnimReader> CAnimTreeBlend::VClone() const {\n  return std::make_unique<CAnimTreeBlend>(x20_24_b1, CAnimTreeNode::Cast(x14_a->Clone()),\n                                          CAnimTreeNode::Cast(x18_b->Clone()), x24_blendWeight, x4_name);\n}\n\nvoid CAnimTreeBlend::SetBlendingWeight(float w) { x24_blendWeight = w; }\n\nfloat CAnimTreeBlend::VGetBlendingWeight() const { return x24_blendWeight; }\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimTreeBlend.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <string>\n\n#include \"Runtime/Character/CAnimTreeTweenBase.hpp\"\n\nnamespace metaforce {\n\nclass CAnimTreeBlend : public CAnimTreeTweenBase {\n  float x24_blendWeight;\n\npublic:\n  static std::string CreatePrimitiveName(const std::shared_ptr<CAnimTreeNode>& a,\n                                         const std::shared_ptr<CAnimTreeNode>& b, float scale);\n\n  CAnimTreeBlend(bool, const std::shared_ptr<CAnimTreeNode>& a, const std::shared_ptr<CAnimTreeNode>& b,\n                 float blendWeight, std::string_view name);\n\n  SAdvancementResults VAdvanceView(const CCharAnimTime& dt) override;\n  CCharAnimTime VGetTimeRemaining() const override;\n  CSteadyStateAnimInfo VGetSteadyStateAnimInfo() const override;\n  std::unique_ptr<IAnimReader> VClone() const override;\n  void SetBlendingWeight(float w) override;\n  float VGetBlendingWeight() const override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimTreeDoubleChild.cpp",
    "content": "#include \"Runtime/Character/CAnimTreeDoubleChild.hpp\"\n\nnamespace metaforce {\n\nCAnimTreeDoubleChild::CAnimTreeDoubleChild(const std::weak_ptr<CAnimTreeNode>& a, const std::weak_ptr<CAnimTreeNode>& b,\n                                           std::string_view name)\n: CAnimTreeNode(name), x14_a(a.lock()), x18_b(b.lock()) {}\n\nCAnimTreeDoubleChild::CDoubleChildAdvancementResult\nCAnimTreeDoubleChild::AdvanceViewBothChildren(const CCharAnimTime& time, bool runLeft, bool loopLeft) {\n  CCharAnimTime lRemTime = time;\n  CCharAnimTime totalTime;\n  if (!runLeft)\n    totalTime = CCharAnimTime();\n  else if (loopLeft)\n    totalTime = CCharAnimTime::Infinity();\n  else\n    totalTime = x14_a->VGetTimeRemaining();\n\n  SAdvancementDeltas leftDeltas, rightDeltas;\n  CCharAnimTime rRemTime = time;\n  if (time.GreaterThanZero()) {\n    while (lRemTime.GreaterThanZero() && !lRemTime.EpsilonZero() && totalTime.GreaterThanZero() &&\n           (loopLeft || !totalTime.EpsilonZero())) {\n      SAdvancementResults res = x14_a->VAdvanceView(lRemTime);\n      auto simp = x14_a->Simplified();\n      if (simp)\n        x14_a = CAnimTreeNode::Cast(std::move(*simp));\n      leftDeltas.x0_posDelta += res.x8_deltas.x0_posDelta;\n      leftDeltas.xc_rotDelta = leftDeltas.xc_rotDelta * res.x8_deltas.xc_rotDelta;\n      if (!loopLeft)\n        totalTime = x14_a->VGetTimeRemaining();\n      lRemTime = res.x0_remTime;\n    }\n\n    while (rRemTime.GreaterThanZero() && !rRemTime.EpsilonZero()) {\n      SAdvancementResults res = x18_b->VAdvanceView(rRemTime);\n      auto simp = x18_b->Simplified();\n      if (simp)\n        x18_b = CAnimTreeNode::Cast(std::move(*simp));\n      rightDeltas.x0_posDelta += res.x8_deltas.x0_posDelta;\n      rightDeltas.xc_rotDelta = rightDeltas.xc_rotDelta * res.x8_deltas.xc_rotDelta;\n      rRemTime = res.x0_remTime;\n    }\n  }\n\n  return {time, leftDeltas, rightDeltas};\n}\n\nSAdvancementResults CAnimTreeDoubleChild::VAdvanceView(const CCharAnimTime& a) {\n  SAdvancementResults resA = x14_a->VAdvanceView(a);\n  SAdvancementResults resB = x14_a->VAdvanceView(a);\n  return (resA.x0_remTime > resB.x0_remTime) ? resA : resB;\n}\n\nsize_t CAnimTreeDoubleChild::VGetBoolPOIList(const CCharAnimTime& time, CBoolPOINode* listOut, size_t capacity,\n                                             size_t iterator, u32 unk) const {\n  size_t newCapacity = x14_a->GetBoolPOIList(time, listOut, capacity, iterator, unk);\n  newCapacity += x18_b->GetBoolPOIList(time, listOut, capacity, newCapacity + iterator, unk);\n  if (newCapacity > capacity) {\n    newCapacity = capacity;\n  }\n\n  std::sort(listOut, listOut + newCapacity);\n\n  return newCapacity;\n}\n\nsize_t CAnimTreeDoubleChild::VGetInt32POIList(const CCharAnimTime& time, CInt32POINode* listOut, size_t capacity,\n                                              size_t iterator, u32 unk) const {\n  size_t newCapacity = x14_a->GetInt32POIList(time, listOut, capacity, iterator, unk);\n  newCapacity += x18_b->GetInt32POIList(time, listOut, capacity, newCapacity + iterator, unk);\n  if (newCapacity > capacity) {\n    newCapacity = capacity;\n  }\n\n  std::sort(listOut, listOut + newCapacity);\n\n  return newCapacity;\n}\n\nsize_t CAnimTreeDoubleChild::VGetParticlePOIList(const CCharAnimTime& time, CParticlePOINode* listOut, size_t capacity,\n                                                 size_t iterator, u32 unk) const {\n  size_t newCapacity = x14_a->GetParticlePOIList(time, listOut, capacity, iterator, unk);\n  newCapacity += x18_b->GetParticlePOIList(time, listOut, capacity, newCapacity + iterator, unk);\n  if (newCapacity > capacity) {\n    newCapacity = capacity;\n  }\n\n  std::sort(listOut, listOut + newCapacity);\n\n  return newCapacity;\n}\n\nsize_t CAnimTreeDoubleChild::VGetSoundPOIList(const CCharAnimTime& time, CSoundPOINode* listOut, size_t capacity,\n                                              size_t iterator, u32 unk) const {\n  size_t newCapacity = x14_a->GetSoundPOIList(time, listOut, capacity, iterator, unk);\n  newCapacity += x18_b->GetSoundPOIList(time, listOut, capacity, newCapacity + iterator, unk);\n  if (newCapacity > capacity) {\n    newCapacity = capacity;\n  }\n\n  std::sort(listOut, listOut + newCapacity);\n\n  return newCapacity;\n}\n\nbool CAnimTreeDoubleChild::VGetBoolPOIState(std::string_view name) const { return x18_b->VGetBoolPOIState(name); }\n\ns32 CAnimTreeDoubleChild::VGetInt32POIState(std::string_view name) const { return x18_b->VGetInt32POIState(name); }\n\nCParticleData::EParentedMode CAnimTreeDoubleChild::VGetParticlePOIState(std::string_view name) const {\n  return x18_b->VGetParticlePOIState(name);\n}\n\nvoid CAnimTreeDoubleChild::VSetPhase(float phase) {\n  x14_a->VSetPhase(phase);\n  x18_b->VSetPhase(phase);\n}\n\nSAdvancementResults CAnimTreeDoubleChild::VGetAdvancementResults(const CCharAnimTime& a, const CCharAnimTime& b) const {\n  SAdvancementResults resA = x14_a->VGetAdvancementResults(a, b);\n  SAdvancementResults resB = x18_b->VGetAdvancementResults(a, b);\n  return (resA.x0_remTime > resB.x0_remTime) ? resA : resB;\n}\n\nu32 CAnimTreeDoubleChild::Depth() const { return std::max(x14_a->Depth(), x18_b->Depth()) + 1; }\n\nCAnimTreeEffectiveContribution CAnimTreeDoubleChild::VGetContributionOfHighestInfluence() const {\n  CAnimTreeEffectiveContribution cA = x14_a->GetContributionOfHighestInfluence();\n  CAnimTreeEffectiveContribution cB = x18_b->GetContributionOfHighestInfluence();\n\n  float leftWeight = (1.f - VGetRightChildWeight()) * cA.GetContributionWeight();\n  float rightWeight = VGetRightChildWeight() * cB.GetContributionWeight();\n\n  if (leftWeight > rightWeight) {\n    return {leftWeight, cA.GetPrimitiveName(), cA.GetSteadyStateAnimInfo(), cA.GetTimeRemaining(),\n            cA.GetAnimDatabaseIndex()};\n  } else {\n    return {rightWeight, cB.GetPrimitiveName(), cB.GetSteadyStateAnimInfo(), cB.GetTimeRemaining(),\n            cB.GetAnimDatabaseIndex()};\n  }\n}\n\nu32 CAnimTreeDoubleChild::VGetNumChildren() const { return x14_a->VGetNumChildren() + x18_b->VGetNumChildren() + 2; }\n\nstd::shared_ptr<IAnimReader> CAnimTreeDoubleChild::VGetBestUnblendedChild() const {\n  std::shared_ptr<CAnimTreeNode> bestChild = (VGetRightChildWeight() > 0.5f) ? x18_b : x14_a;\n  if (!bestChild)\n    return {};\n  return bestChild->GetBestUnblendedChild();\n}\n\nvoid CAnimTreeDoubleChild::VGetWeightedReaders(\n    rstl::reserved_vector<std::pair<float, std::weak_ptr<IAnimReader>>, 16>& out, float w) const {\n  x14_a->VGetWeightedReaders(out, w);\n  x18_b->VGetWeightedReaders(out, w);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimTreeDoubleChild.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <string_view>\n\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Character/CAnimTreeNode.hpp\"\n\nnamespace metaforce {\n\nclass CAnimTreeDoubleChild : public CAnimTreeNode {\npublic:\n  class CDoubleChildAdvancementResult {\n    CCharAnimTime x0_trueAdvancement;\n    SAdvancementDeltas x8_leftDeltas;\n    SAdvancementDeltas x24_rightDeltas;\n\n  public:\n    CDoubleChildAdvancementResult(const CCharAnimTime& trueAdvancement, const SAdvancementDeltas& leftDeltas,\n                                  const SAdvancementDeltas& rightDeltas)\n    : x0_trueAdvancement(trueAdvancement), x8_leftDeltas(leftDeltas), x24_rightDeltas(rightDeltas) {}\n    const SAdvancementDeltas& GetLeftAdvancementDeltas() const { return x8_leftDeltas; }\n    const SAdvancementDeltas& GetRightAdvancementDeltas() const { return x24_rightDeltas; }\n    const CCharAnimTime& GetTrueAdvancement() const { return x0_trueAdvancement; }\n  };\n\nprotected:\n  std::shared_ptr<CAnimTreeNode> x14_a;\n  std::shared_ptr<CAnimTreeNode> x18_b;\n\n  CDoubleChildAdvancementResult AdvanceViewBothChildren(const CCharAnimTime& time, bool runLeft, bool loopLeft);\n\npublic:\n  CAnimTreeDoubleChild(const std::weak_ptr<CAnimTreeNode>& a, const std::weak_ptr<CAnimTreeNode>& b,\n                       std::string_view name);\n  SAdvancementResults VAdvanceView(const CCharAnimTime& a) override;\n  size_t VGetBoolPOIList(const CCharAnimTime& time, CBoolPOINode* listOut, size_t capacity, size_t iterator,\n                         u32) const override;\n  size_t VGetInt32POIList(const CCharAnimTime& time, CInt32POINode* listOut, size_t capacity, size_t iterator,\n                          u32) const override;\n  size_t VGetParticlePOIList(const CCharAnimTime& time, CParticlePOINode* listOut, size_t capacity, size_t iterator,\n                             u32) const override;\n  size_t VGetSoundPOIList(const CCharAnimTime& time, CSoundPOINode* listOut, size_t capacity, size_t iterator,\n                          u32) const override;\n  bool VGetBoolPOIState(std::string_view name) const override;\n  s32 VGetInt32POIState(std::string_view name) const override;\n  CParticleData::EParentedMode VGetParticlePOIState(std::string_view name) const override;\n  void VSetPhase(float) override;\n  SAdvancementResults VGetAdvancementResults(const CCharAnimTime& a, const CCharAnimTime& b) const override;\n  u32 Depth() const override;\n  CAnimTreeEffectiveContribution VGetContributionOfHighestInfluence() const override;\n  u32 VGetNumChildren() const override;\n  std::shared_ptr<IAnimReader> VGetBestUnblendedChild() const override;\n  void VGetWeightedReaders(rstl::reserved_vector<std::pair<float, std::weak_ptr<IAnimReader>>, 16>& out,\n                           float w) const override;\n\n  virtual float VGetRightChildWeight() const = 0;\n  float GetRightChildWeight() const { return VGetRightChildWeight(); }\n\n  const std::shared_ptr<CAnimTreeNode>& GetLeftChild() const { return x14_a; }\n  const std::shared_ptr<CAnimTreeNode>& GetRightChild() const { return x18_b; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimTreeLoopIn.cpp",
    "content": "#include \"Runtime/Character/CAnimTreeLoopIn.hpp\"\n\n#include \"Runtime/Character/CTreeUtils.hpp\"\n\nnamespace metaforce {\n\nstd::string CAnimTreeLoopIn::CreatePrimitiveName(const std::weak_ptr<CAnimTreeNode>& a,\n                                                 const std::weak_ptr<CAnimTreeNode>& b,\n                                                 const std::weak_ptr<CAnimTreeNode>& c) {\n  return {};\n}\n\nCAnimTreeLoopIn::CAnimTreeLoopIn(const std::weak_ptr<CAnimTreeNode>& a, const std::weak_ptr<CAnimTreeNode>& b,\n                                 const std::weak_ptr<CAnimTreeNode>& c, const CAnimSysContext& animCtx,\n                                 std::string_view name)\n: CAnimTreeSingleChild(CTreeUtils::GetTransitionTree(a, c, animCtx), name)\n, x18_nextAnim(b.lock())\n, x20_animCtx(animCtx)\n, x30_fundamentals(CSequenceHelper(x14_child, x18_nextAnim, animCtx).ComputeSequenceFundamentals()) {}\n\nCAnimTreeLoopIn::CAnimTreeLoopIn(const std::weak_ptr<CAnimTreeNode>& a, const std::weak_ptr<CAnimTreeNode>& b,\n                                 bool didLoopIn, CAnimSysContext animCtx, std::string_view name,\n                                 CSequenceFundamentals fundamentals, const CCharAnimTime& time)\n: CAnimTreeSingleChild(a, name)\n, x18_nextAnim(b.lock())\n, x1c_didLoopIn(didLoopIn)\n, x20_animCtx(std::move(animCtx))\n, x30_fundamentals(std::move(fundamentals))\n, x88_curTime(time) {}\n\nCAnimTreeEffectiveContribution CAnimTreeLoopIn::VGetContributionOfHighestInfluence() const {\n  return x14_child->GetContributionOfHighestInfluence();\n}\n\nstd::optional<std::unique_ptr<IAnimReader>> CAnimTreeLoopIn::VSimplified() {\n  CCharAnimTime remTime = x14_child->VGetTimeRemaining();\n  if (remTime.GreaterThanZero() && !remTime.EpsilonZero()) {\n    auto simp = x14_child->Simplified();\n    if (simp)\n      x14_child = CAnimTreeNode::Cast(std::move(*simp));\n  } else if (x1c_didLoopIn && x14_child->VGetTimeRemaining().EqualsZero()) {\n    return x14_child->Clone();\n  }\n  return {};\n}\n\nstd::shared_ptr<IAnimReader> CAnimTreeLoopIn::VGetBestUnblendedChild() const {\n  if (std::shared_ptr<IAnimReader> bestChild = x14_child->GetBestUnblendedChild()) {\n    return std::make_shared<CAnimTreeLoopIn>(CAnimTreeNode::Cast(bestChild->Clone()), x18_nextAnim, x1c_didLoopIn,\n                                             x20_animCtx, x4_name, x30_fundamentals, x88_curTime);\n  }\n  return {};\n}\n\nstd::unique_ptr<IAnimReader> CAnimTreeLoopIn::VClone() const {\n  return std::make_unique<CAnimTreeLoopIn>(CAnimTreeNode::Cast(x14_child->Clone()), x18_nextAnim, x1c_didLoopIn,\n                                           x20_animCtx, x4_name, x30_fundamentals, x88_curTime);\n}\n\nsize_t CAnimTreeLoopIn::VGetBoolPOIList(const CCharAnimTime& time, CBoolPOINode* listOut, size_t capacity,\n                                        size_t iterator, u32 unk) const {\n  return _getPOIList<CBoolPOINode>(time, listOut, capacity, iterator, unk, x30_fundamentals.GetBoolPointsOfInterest(),\n                                   x88_curTime);\n}\n\nsize_t CAnimTreeLoopIn::VGetInt32POIList(const CCharAnimTime& time, CInt32POINode* listOut, size_t capacity,\n                                         size_t iterator, u32 unk) const {\n  return _getPOIList<CInt32POINode>(time, listOut, capacity, iterator, unk, x30_fundamentals.GetInt32PointsOfInterest(),\n                                    x88_curTime);\n}\n\nsize_t CAnimTreeLoopIn::VGetParticlePOIList(const CCharAnimTime& time, CParticlePOINode* listOut, size_t capacity,\n                                            size_t iterator, u32 unk) const {\n  return _getPOIList<CParticlePOINode>(time, listOut, capacity, iterator, unk,\n                                       x30_fundamentals.GetParticlePointsOfInterest(), x88_curTime);\n}\n\nsize_t CAnimTreeLoopIn::VGetSoundPOIList(const CCharAnimTime& time, CSoundPOINode* listOut, size_t capacity,\n                                         size_t iterator, u32 unk) const {\n  return _getPOIList<CSoundPOINode>(time, listOut, capacity, iterator, unk, x30_fundamentals.GetSoundPointsOfInterest(),\n                                    x88_curTime);\n}\n\nCSteadyStateAnimInfo CAnimTreeLoopIn::VGetSteadyStateAnimInfo() const {\n  return x30_fundamentals.GetSteadyStateAnimInfo();\n}\n\nCCharAnimTime CAnimTreeLoopIn::VGetTimeRemaining() const {\n  return x30_fundamentals.GetSteadyStateAnimInfo().GetDuration() - x88_curTime;\n}\n\nSAdvancementResults CAnimTreeLoopIn::VAdvanceView(const CCharAnimTime& dt) {\n  std::shared_ptr<CAnimTreeNode> origChild = x14_child;\n  SAdvancementResults res = origChild->VAdvanceView(dt);\n  x88_curTime += dt - res.x0_remTime;\n  CCharAnimTime remTime = origChild->VGetTimeRemaining();\n  if ((remTime.EpsilonZero() || (dt - res.x0_remTime).EpsilonZero()) && !x1c_didLoopIn) {\n    x14_child = CTreeUtils::GetTransitionTree(origChild, x18_nextAnim, x20_animCtx);\n    x1c_didLoopIn = true;\n  }\n  return res;\n}\n\n} // namespace metaforce"
  },
  {
    "path": "Runtime/Character/CAnimTreeLoopIn.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <string>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/Character/CAnimSysContext.hpp\"\n#include \"Runtime/Character/CAnimTreeSingleChild.hpp\"\n#include \"Runtime/Character/CSequenceHelper.hpp\"\n\nnamespace metaforce {\n\nclass CAnimTreeLoopIn : public CAnimTreeSingleChild {\n  std::shared_ptr<CAnimTreeNode> x18_nextAnim;\n  bool x1c_didLoopIn = false;\n  CAnimSysContext x20_animCtx;\n  CSequenceFundamentals x30_fundamentals;\n  CCharAnimTime x88_curTime;\n\npublic:\n  static std::string CreatePrimitiveName(const std::weak_ptr<CAnimTreeNode>& a, const std::weak_ptr<CAnimTreeNode>& b,\n                                         const std::weak_ptr<CAnimTreeNode>& c);\n  CAnimTreeLoopIn(const std::weak_ptr<CAnimTreeNode>& a, const std::weak_ptr<CAnimTreeNode>& b,\n                  const std::weak_ptr<CAnimTreeNode>& c, const CAnimSysContext& animCtx, std::string_view name);\n  CAnimTreeLoopIn(const std::weak_ptr<CAnimTreeNode>& a, const std::weak_ptr<CAnimTreeNode>& b, bool didLoopIn,\n                  CAnimSysContext animCtx, std::string_view name, CSequenceFundamentals fundamentals,\n                  const CCharAnimTime& time);\n  CAnimTreeEffectiveContribution VGetContributionOfHighestInfluence() const override;\n  bool VSupportsReverseView() const { return false; }\n  std::optional<std::unique_ptr<IAnimReader>> VSimplified() override;\n  std::shared_ptr<IAnimReader> VGetBestUnblendedChild() const override;\n  std::unique_ptr<IAnimReader> VClone() const override;\n  size_t VGetBoolPOIList(const CCharAnimTime& time, CBoolPOINode* listOut, size_t capacity, size_t iterator,\n                         u32) const override;\n  size_t VGetInt32POIList(const CCharAnimTime& time, CInt32POINode* listOut, size_t capacity, size_t iterator,\n                          u32) const override;\n  size_t VGetParticlePOIList(const CCharAnimTime& time, CParticlePOINode* listOut, size_t capacity, size_t iterator,\n                             u32) const override;\n  size_t VGetSoundPOIList(const CCharAnimTime& time, CSoundPOINode* listOut, size_t capacity, size_t iterator,\n                          u32) const override;\n  CSteadyStateAnimInfo VGetSteadyStateAnimInfo() const override;\n  CCharAnimTime VGetTimeRemaining() const override;\n  SAdvancementResults VAdvanceView(const CCharAnimTime& dt) override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimTreeNode.cpp",
    "content": "#include \"Runtime/Character/CAnimTreeNode.hpp\"\n\nnamespace metaforce {\n\nCAnimTreeEffectiveContribution CAnimTreeNode::GetContributionOfHighestInfluence() const {\n  return VGetContributionOfHighestInfluence();\n}\n\nu32 CAnimTreeNode::GetNumChildren() const { return VGetNumChildren(); }\n\nstd::shared_ptr<IAnimReader> CAnimTreeNode::GetBestUnblendedChild() const { return VGetBestUnblendedChild(); }\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimTreeNode.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <string>\n#include <utility>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/Character/IAnimReader.hpp\"\n\nnamespace metaforce {\n\nclass CAnimTreeNode : public IAnimReader {\nprotected:\n  std::string x4_name;\n\npublic:\n  explicit CAnimTreeNode(std::string_view name) : x4_name(name) {}\n  bool IsCAnimTreeNode() const override { return true; }\n  static std::shared_ptr<CAnimTreeNode> Cast(std::unique_ptr<IAnimReader>&& ptr) {\n    if (ptr->IsCAnimTreeNode())\n      return std::static_pointer_cast<CAnimTreeNode>(std::shared_ptr<IAnimReader>(std::move(ptr)));\n    return {};\n  }\n\n  virtual u32 Depth() const = 0;\n  virtual CAnimTreeEffectiveContribution VGetContributionOfHighestInfluence() const = 0;\n  virtual u32 VGetNumChildren() const = 0;\n  virtual std::shared_ptr<IAnimReader> VGetBestUnblendedChild() const = 0;\n  virtual void VGetWeightedReaders(rstl::reserved_vector<std::pair<float, std::weak_ptr<IAnimReader>>, 16>& out,\n                                   float w) const = 0;\n  void GetWeightedReaders(rstl::reserved_vector<std::pair<float, std::weak_ptr<IAnimReader>>, 16>& out, float w) const {\n    VGetWeightedReaders(out, w);\n  }\n\n  CAnimTreeEffectiveContribution GetContributionOfHighestInfluence() const;\n  u32 GetNumChildren() const;\n  std::shared_ptr<IAnimReader> GetBestUnblendedChild() const;\n\n  std::string_view GetName() const { return x4_name; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimTreeSequence.cpp",
    "content": "#include \"Runtime/Character/CAnimTreeSequence.hpp\"\n\n#include \"Runtime/Character/CAnimSysContext.hpp\"\n#include \"Runtime/Character/CTreeUtils.hpp\"\n#include \"Runtime/Character/IMetaAnim.hpp\"\n\nnamespace metaforce {\n\nCAnimTreeSequence::CAnimTreeSequence(std::vector<std::shared_ptr<IMetaAnim>> seq, CAnimSysContext animSys,\n                                     std::string_view name)\n: CAnimTreeSingleChild(seq[0]->GetAnimationTree(animSys, CMetaAnimTreeBuildOrders::NoSpecialOrders()), name)\n, x18_animCtx(std::move(animSys))\n, x28_sequence(std::move(seq))\n, x3c_fundamentals(CSequenceHelper(x28_sequence, x18_animCtx).ComputeSequenceFundamentals())\n, x94_curTime(0.f) {}\n\nCAnimTreeSequence::CAnimTreeSequence(const std::shared_ptr<CAnimTreeNode>& curNode,\n                                     std::vector<std::shared_ptr<IMetaAnim>> metaAnims, CAnimSysContext animSys,\n                                     std::string_view name, CSequenceFundamentals fundamentals,\n                                     const CCharAnimTime& time)\n: CAnimTreeSingleChild(curNode, name)\n, x18_animCtx(std::move(animSys))\n, x28_sequence(std::move(metaAnims))\n, x3c_fundamentals(std::move(fundamentals))\n, x94_curTime(time) {}\n\nCAnimTreeEffectiveContribution CAnimTreeSequence::VGetContributionOfHighestInfluence() const {\n  return x14_child->GetContributionOfHighestInfluence();\n}\n\nstd::shared_ptr<IAnimReader> CAnimTreeSequence::VGetBestUnblendedChild() const {\n  std::shared_ptr<IAnimReader> ch = x14_child->GetBestUnblendedChild();\n  if (!ch)\n    return ch;\n  return std::make_shared<CAnimTreeSequence>(\n      std::static_pointer_cast<CAnimTreeNode>(std::shared_ptr<IAnimReader>(ch->Clone())), x28_sequence, x18_animCtx,\n      x4_name, x3c_fundamentals, x94_curTime);\n}\n\nSAdvancementResults CAnimTreeSequence::VAdvanceView(const CCharAnimTime& dt) {\n  CCharAnimTime totalDelta;\n  zeus::CVector3f posDelta;\n  zeus::CQuaternion rotDelta;\n\n  std::shared_ptr<CAnimTreeNode> curChild = x14_child;\n  if (x38_curIdx >= x28_sequence.size() && curChild->VGetTimeRemaining().EqualsZero()) {\n    x3c_fundamentals = CSequenceHelper(x28_sequence, x18_animCtx).ComputeSequenceFundamentals();\n    x38_curIdx = 0;\n    x14_child = CTreeUtils::GetTransitionTree(\n        curChild, x28_sequence[x38_curIdx]->GetAnimationTree(x18_animCtx, CMetaAnimTreeBuildOrders::NoSpecialOrders()),\n        x18_animCtx);\n    curChild = x14_child;\n  }\n\n  CCharAnimTime remTime = dt;\n  // Note: EpsilonZero check added\n  while (remTime.GreaterThanZero() && !remTime.EpsilonZero() && x38_curIdx < x28_sequence.size()) {\n    CCharAnimTime chRem = curChild->VGetTimeRemaining();\n    if (chRem.EqualsZero()) {\n      ++x38_curIdx;\n      if (x38_curIdx < x28_sequence.size()) {\n        x14_child = CTreeUtils::GetTransitionTree(\n            curChild,\n            x28_sequence[x38_curIdx]->GetAnimationTree(x18_animCtx, CMetaAnimTreeBuildOrders::NoSpecialOrders()),\n            x18_animCtx);\n      }\n    }\n    curChild = x14_child;\n    if (x38_curIdx < x28_sequence.size()) {\n      SAdvancementResults res = curChild->VAdvanceView(remTime);\n      if (auto simp = curChild->Simplified()) {\n        curChild = CAnimTreeNode::Cast(std::move(*simp));\n        x14_child = curChild;\n      }\n      CCharAnimTime prevRemTime = remTime;\n      remTime = res.x0_remTime;\n      totalDelta += prevRemTime - remTime;\n      posDelta += res.x8_deltas.x0_posDelta;\n      rotDelta = rotDelta * res.x8_deltas.xc_rotDelta;\n    }\n  }\n\n  x94_curTime += totalDelta;\n  return {dt - totalDelta, {posDelta, rotDelta}};\n}\n\nCCharAnimTime CAnimTreeSequence::VGetTimeRemaining() const {\n  if (x38_curIdx == x28_sequence.size() - 1)\n    return x14_child->VGetTimeRemaining();\n  return x3c_fundamentals.GetSteadyStateAnimInfo().GetDuration() - x94_curTime;\n}\n\nCSteadyStateAnimInfo CAnimTreeSequence::VGetSteadyStateAnimInfo() const {\n  return x3c_fundamentals.GetSteadyStateAnimInfo();\n}\n\nsize_t CAnimTreeSequence::VGetBoolPOIList(const CCharAnimTime& time, CBoolPOINode* listOut, size_t capacity,\n                                          size_t iterator, u32 unk) const {\n  return _getPOIList(time, listOut, capacity, iterator, unk, x3c_fundamentals.GetBoolPointsOfInterest(), x94_curTime);\n}\n\nsize_t CAnimTreeSequence::VGetInt32POIList(const CCharAnimTime& time, CInt32POINode* listOut, size_t capacity,\n                                           size_t iterator, u32 unk) const {\n  return _getPOIList(time, listOut, capacity, iterator, unk, x3c_fundamentals.GetInt32PointsOfInterest(), x94_curTime);\n}\n\nsize_t CAnimTreeSequence::VGetParticlePOIList(const CCharAnimTime& time, CParticlePOINode* listOut, size_t capacity,\n                                              size_t iterator, u32 unk) const {\n  return _getPOIList(time, listOut, capacity, iterator, unk, x3c_fundamentals.GetParticlePointsOfInterest(),\n                     x94_curTime);\n}\n\nsize_t CAnimTreeSequence::VGetSoundPOIList(const CCharAnimTime& time, CSoundPOINode* listOut, size_t capacity,\n                                           size_t iterator, u32 unk) const {\n  return _getPOIList(time, listOut, capacity, iterator, unk, x3c_fundamentals.GetSoundPointsOfInterest(), x94_curTime);\n}\n\nstd::unique_ptr<IAnimReader> CAnimTreeSequence::VClone() const {\n  return std::make_unique<CAnimTreeSequence>(\n      std::static_pointer_cast<CAnimTreeNode>(std::shared_ptr<IAnimReader>(x14_child->Clone())), x28_sequence,\n      x18_animCtx, x4_name, x3c_fundamentals, x94_curTime);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimTreeSequence.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <vector>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/Character/CAnimSysContext.hpp\"\n#include \"Runtime/Character/CAnimTreeSingleChild.hpp\"\n#include \"Runtime/Character/CSequenceHelper.hpp\"\n\nnamespace metaforce {\nclass IMetaAnim;\nclass CTransitionDatabaseGame;\n\nclass CAnimTreeSequence : public CAnimTreeSingleChild {\n  CAnimSysContext x18_animCtx;\n  std::vector<std::shared_ptr<IMetaAnim>> x28_sequence;\n  u32 x38_curIdx = 0;\n  CSequenceFundamentals x3c_fundamentals;\n  CCharAnimTime x94_curTime;\n\npublic:\n  CAnimTreeSequence(std::vector<std::shared_ptr<IMetaAnim>> seq, CAnimSysContext animSys, std::string_view name);\n  CAnimTreeSequence(const std::shared_ptr<CAnimTreeNode>& curNode, std::vector<std::shared_ptr<IMetaAnim>> metaAnims,\n                    CAnimSysContext animSys, std::string_view name, CSequenceFundamentals fundamentals,\n                    const CCharAnimTime& time);\n\n  CAnimTreeEffectiveContribution VGetContributionOfHighestInfluence() const override;\n  std::shared_ptr<IAnimReader> VGetBestUnblendedChild() const override;\n  bool VSupportsReverseView() const { return false; }\n\n  SAdvancementResults VAdvanceView(const CCharAnimTime& dt) override;\n  CCharAnimTime VGetTimeRemaining() const override;\n  CSteadyStateAnimInfo VGetSteadyStateAnimInfo() const override;\n  size_t VGetBoolPOIList(const CCharAnimTime& time, CBoolPOINode* listOut, size_t capacity, size_t iterator,\n                         u32) const override;\n  size_t VGetInt32POIList(const CCharAnimTime& time, CInt32POINode* listOut, size_t capacity, size_t iterator,\n                          u32) const override;\n  size_t VGetParticlePOIList(const CCharAnimTime& time, CParticlePOINode* listOut, size_t capacity, size_t iterator,\n                             u32) const override;\n  size_t VGetSoundPOIList(const CCharAnimTime& time, CSoundPOINode* listOut, size_t capacity, size_t iterator,\n                          u32) const override;\n  std::unique_ptr<IAnimReader> VClone() const override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimTreeSingleChild.cpp",
    "content": "#include \"Runtime/Character/CAnimTreeSingleChild.hpp\"\n\nnamespace metaforce {\n\nCAnimTreeSingleChild::CAnimTreeSingleChild(const std::weak_ptr<CAnimTreeNode>& node, std::string_view name)\n: CAnimTreeNode(name), x14_child(node.lock()) {}\n\nSAdvancementResults CAnimTreeSingleChild::VAdvanceView(const CCharAnimTime& dt) { return x14_child->VAdvanceView(dt); }\n\nCCharAnimTime CAnimTreeSingleChild::VGetTimeRemaining() const { return x14_child->VGetTimeRemaining(); }\n\nbool CAnimTreeSingleChild::VHasOffset(const CSegId& seg) const { return x14_child->VHasOffset(seg); }\n\nzeus::CVector3f CAnimTreeSingleChild::VGetOffset(const CSegId& seg) const { return x14_child->VGetOffset(seg); }\n\nzeus::CQuaternion CAnimTreeSingleChild::VGetRotation(const CSegId& seg) const { return x14_child->VGetRotation(seg); }\n\nsize_t CAnimTreeSingleChild::VGetBoolPOIList(const CCharAnimTime& time, CBoolPOINode* listOut, size_t capacity,\n                                             size_t iterator, u32 unk) const {\n  return x14_child->GetBoolPOIList(time, listOut, capacity, iterator, unk);\n}\n\nsize_t CAnimTreeSingleChild::VGetInt32POIList(const CCharAnimTime& time, CInt32POINode* listOut, size_t capacity,\n                                              size_t iterator, u32 unk) const {\n  return x14_child->GetInt32POIList(time, listOut, capacity, iterator, unk);\n}\n\nsize_t CAnimTreeSingleChild::VGetParticlePOIList(const CCharAnimTime& time, CParticlePOINode* listOut, size_t capacity,\n                                                 size_t iterator, u32 unk) const {\n  return x14_child->GetParticlePOIList(time, listOut, capacity, iterator, unk);\n}\n\nsize_t CAnimTreeSingleChild::VGetSoundPOIList(const CCharAnimTime& time, CSoundPOINode* listOut, size_t capacity,\n                                              size_t iterator, u32 unk) const {\n  return x14_child->GetSoundPOIList(time, listOut, capacity, iterator, unk);\n}\n\nbool CAnimTreeSingleChild::VGetBoolPOIState(std::string_view name) const { return x14_child->VGetBoolPOIState(name); }\n\ns32 CAnimTreeSingleChild::VGetInt32POIState(std::string_view name) const { return x14_child->VGetInt32POIState(name); }\n\nCParticleData::EParentedMode CAnimTreeSingleChild::VGetParticlePOIState(std::string_view name) const {\n  return x14_child->VGetParticlePOIState(name);\n}\n\nvoid CAnimTreeSingleChild::VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut) const {\n  x14_child->VGetSegStatementSet(list, setOut);\n}\n\nvoid CAnimTreeSingleChild::VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut,\n                                               const CCharAnimTime& time) const {\n  x14_child->VGetSegStatementSet(list, setOut, time);\n}\n\nvoid CAnimTreeSingleChild::VSetPhase(float phase) { x14_child->VSetPhase(phase); }\n\nSAdvancementResults CAnimTreeSingleChild::VGetAdvancementResults(const CCharAnimTime& a, const CCharAnimTime& b) const {\n  return x14_child->VGetAdvancementResults(a, b);\n}\n\nu32 CAnimTreeSingleChild::Depth() const { return x14_child->Depth() + 1; }\n\nu32 CAnimTreeSingleChild::VGetNumChildren() const { return x14_child->VGetNumChildren() + 1; }\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimTreeSingleChild.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Character/CAnimTreeNode.hpp\"\n\nnamespace metaforce {\n\nclass CAnimTreeSingleChild : public CAnimTreeNode {\nprotected:\n  std::shared_ptr<CAnimTreeNode> x14_child;\n\npublic:\n  CAnimTreeSingleChild(const std::weak_ptr<CAnimTreeNode>& node, std::string_view name);\n\n  SAdvancementResults VAdvanceView(const CCharAnimTime& dt) override;\n  CCharAnimTime VGetTimeRemaining() const override;\n  bool VHasOffset(const CSegId& seg) const override;\n  zeus::CVector3f VGetOffset(const CSegId& seg) const override;\n  zeus::CQuaternion VGetRotation(const CSegId& seg) const override;\n  size_t VGetBoolPOIList(const CCharAnimTime& time, CBoolPOINode* listOut, size_t capacity, size_t iterator,\n                         u32) const override;\n  size_t VGetInt32POIList(const CCharAnimTime& time, CInt32POINode* listOut, size_t capacity, size_t iterator,\n                          u32) const override;\n  size_t VGetParticlePOIList(const CCharAnimTime& time, CParticlePOINode* listOut, size_t capacity, size_t iterator,\n                             u32) const override;\n  size_t VGetSoundPOIList(const CCharAnimTime& time, CSoundPOINode* listOut, size_t capacity, size_t iterator,\n                          u32) const override;\n  bool VGetBoolPOIState(std::string_view name) const override;\n  s32 VGetInt32POIState(std::string_view name) const override;\n  CParticleData::EParentedMode VGetParticlePOIState(std::string_view name) const override;\n  void VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut) const override;\n  void VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut, const CCharAnimTime& time) const override;\n  void VSetPhase(float) override;\n  SAdvancementResults VGetAdvancementResults(const CCharAnimTime& a, const CCharAnimTime& b) const override;\n  u32 Depth() const override;\n  u32 VGetNumChildren() const override;\n  void VGetWeightedReaders(rstl::reserved_vector<std::pair<float, std::weak_ptr<IAnimReader>>, 16>& out,\n                           float w) const override {\n    x14_child->VGetWeightedReaders(out, w);\n  }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimTreeTimeScale.cpp",
    "content": "#include \"Runtime/Character/CAnimTreeTimeScale.hpp\"\n\nnamespace metaforce {\n\nCAnimTreeTimeScale::CAnimTreeTimeScale(const std::weak_ptr<CAnimTreeNode>& node, float scale, std::string_view name)\n: CAnimTreeSingleChild(node, name)\n, x18_timeScale(new CConstantAnimationTimeScale(scale))\n, x28_targetAccelTime(CCharAnimTime::Infinity()) {}\n\nCAnimTreeTimeScale::CAnimTreeTimeScale(const std::weak_ptr<CAnimTreeNode>& node,\n                                       std::unique_ptr<IVaryingAnimationTimeScale>&& timeScale,\n                                       const CCharAnimTime& time, std::string_view name)\n: CAnimTreeSingleChild(node, name), x18_timeScale(std::move(timeScale)), x28_targetAccelTime(time) {\n  x30_initialTime = x14_child->VGetSteadyStateAnimInfo().GetDuration() - x14_child->VGetTimeRemaining();\n}\n\nstd::string CAnimTreeTimeScale::CreatePrimitiveName(const std::weak_ptr<CAnimTreeNode>&, float, const CCharAnimTime&,\n                                                    float) {\n  return {};\n}\n\nCCharAnimTime CAnimTreeTimeScale::GetRealLifeTime(const CCharAnimTime& time) const {\n  CCharAnimTime timeRem = x14_child->VGetTimeRemaining();\n\n  CCharAnimTime ret = std::min(timeRem, time);\n  if (x28_targetAccelTime > CCharAnimTime()) {\n    if (ret < CCharAnimTime(x28_targetAccelTime - x20_curAccelTime))\n      return x18_timeScale->VTimeScaleIntegral(x20_curAccelTime.GetSeconds(), (x20_curAccelTime + ret).GetSeconds());\n    else {\n      CCharAnimTime integral =\n          x18_timeScale->VTimeScaleIntegral(x20_curAccelTime.GetSeconds(), x28_targetAccelTime.GetSeconds());\n\n      if (integral > ret)\n        return x18_timeScale->VFindUpperLimit(x20_curAccelTime.GetSeconds(), ret.GetSeconds()) -\n               x20_curAccelTime.GetSeconds();\n      else\n        return integral + (ret - integral);\n    }\n  }\n\n  return ret;\n}\n\nvoid CAnimTreeTimeScale::VSetPhase(float phase) { x14_child->VSetPhase(phase); }\n\nstd::optional<std::unique_ptr<IAnimReader>> CAnimTreeTimeScale::VSimplified() {\n  if (auto simp = x14_child->Simplified()) {\n    auto newNode = std::make_unique<CAnimTreeTimeScale>(CAnimTreeNode::Cast(std::move(*simp)), x18_timeScale->Clone(),\n                                                        x28_targetAccelTime, x4_name);\n    newNode->x20_curAccelTime = x20_curAccelTime;\n    newNode->x30_initialTime = x30_initialTime;\n    return {std::move(newNode)};\n  }\n\n  if (x20_curAccelTime == x28_targetAccelTime) {\n    return {x14_child->Clone()};\n  }\n\n  return std::nullopt;\n}\n\nsize_t CAnimTreeTimeScale::VGetBoolPOIList(const CCharAnimTime& time, CBoolPOINode* listOut, size_t capacity,\n                                           size_t iterator, u32 unk) const {\n  const CCharAnimTime useTime =\n      time == CCharAnimTime::Infinity() ? x14_child->VGetTimeRemaining() : GetRealLifeTime(time);\n  const size_t ret = x14_child->GetBoolPOIList(useTime, listOut, capacity, iterator, unk);\n  if (x28_targetAccelTime > CCharAnimTime()) {\n    for (size_t i = 0; i < ret; ++i) {\n      listOut[iterator + i].SetTime(GetRealLifeTime(listOut[i].GetTime()));\n    }\n  }\n  return ret;\n}\n\nsize_t CAnimTreeTimeScale::VGetInt32POIList(const CCharAnimTime& time, CInt32POINode* listOut, size_t capacity,\n                                            size_t iterator, u32 unk) const {\n  const CCharAnimTime useTime =\n      time == CCharAnimTime::Infinity() ? x14_child->VGetTimeRemaining() : GetRealLifeTime(time);\n  const size_t ret = x14_child->GetInt32POIList(useTime, listOut, capacity, iterator, unk);\n  if (x28_targetAccelTime > CCharAnimTime()) {\n    for (size_t i = 0; i < ret; ++i) {\n      listOut[iterator + i].SetTime(GetRealLifeTime(listOut[i].GetTime()));\n    }\n  }\n  return ret;\n}\n\nsize_t CAnimTreeTimeScale::VGetParticlePOIList(const CCharAnimTime& time, CParticlePOINode* listOut, size_t capacity,\n                                               size_t iterator, u32 unk) const {\n  const CCharAnimTime useTime =\n      time == CCharAnimTime::Infinity() ? x14_child->VGetTimeRemaining() : GetRealLifeTime(time);\n  const size_t ret = x14_child->GetParticlePOIList(useTime, listOut, capacity, iterator, unk);\n  if (x28_targetAccelTime > CCharAnimTime()) {\n    for (size_t i = 0; i < ret; ++i) {\n      listOut[iterator + i].SetTime(GetRealLifeTime(listOut[i].GetTime()));\n    }\n  }\n  return ret;\n}\n\nsize_t CAnimTreeTimeScale::VGetSoundPOIList(const CCharAnimTime& time, CSoundPOINode* listOut, size_t capacity,\n                                            size_t iterator, u32 unk) const {\n  const CCharAnimTime useTime =\n      (time == CCharAnimTime::Infinity()) ? x14_child->VGetTimeRemaining() : GetRealLifeTime(time);\n  const size_t ret = x14_child->GetSoundPOIList(useTime, listOut, capacity, iterator, unk);\n  if (x28_targetAccelTime > CCharAnimTime()) {\n    for (size_t i = 0; i < ret; ++i) {\n      listOut[iterator + i].SetTime(GetRealLifeTime(listOut[i].GetTime()));\n    }\n  }\n  return ret;\n}\n\nbool CAnimTreeTimeScale::VGetBoolPOIState(std::string_view name) const { return x14_child->VGetBoolPOIState(name); }\n\ns32 CAnimTreeTimeScale::VGetInt32POIState(std::string_view name) const { return x14_child->VGetInt32POIState(name); }\n\nCParticleData::EParentedMode CAnimTreeTimeScale::VGetParticlePOIState(std::string_view name) const {\n  return x14_child->VGetParticlePOIState(name);\n}\n\nCAnimTreeEffectiveContribution CAnimTreeTimeScale::VGetContributionOfHighestInfluence() const {\n  CAnimTreeEffectiveContribution c = x14_child->VGetContributionOfHighestInfluence();\n  return {c.GetContributionWeight(), c.GetPrimitiveName(), VGetSteadyStateAnimInfo(), VGetTimeRemaining(),\n          c.GetAnimDatabaseIndex()};\n}\n\nstd::shared_ptr<IAnimReader> CAnimTreeTimeScale::VGetBestUnblendedChild() const {\n  if (std::shared_ptr<IAnimReader> bestChild = x14_child->VGetBestUnblendedChild()) {\n    auto newNode = std::make_shared<CAnimTreeTimeScale>(CAnimTreeNode::Cast(bestChild->Clone()), x18_timeScale->Clone(),\n                                                        x28_targetAccelTime, x4_name);\n    newNode->x20_curAccelTime = x20_curAccelTime;\n    newNode->x30_initialTime = x30_initialTime;\n    return {std::move(newNode)};\n  }\n  return nullptr;\n}\n\nstd::unique_ptr<IAnimReader> CAnimTreeTimeScale::VClone() const {\n  auto newNode = std::make_unique<CAnimTreeTimeScale>(CAnimTreeNode::Cast(x14_child->Clone()), x18_timeScale->Clone(),\n                                                      x28_targetAccelTime, x4_name);\n  newNode->x20_curAccelTime = x20_curAccelTime;\n  newNode->x30_initialTime = x30_initialTime;\n  return {std::move(newNode)};\n}\n\nCSteadyStateAnimInfo CAnimTreeTimeScale::VGetSteadyStateAnimInfo() const {\n  CSteadyStateAnimInfo ssInfo = x14_child->VGetSteadyStateAnimInfo();\n  if (x28_targetAccelTime == CCharAnimTime::Infinity()) {\n    return {ssInfo.IsLooping(), x18_timeScale->VFindUpperLimit(0.f, ssInfo.GetDuration().GetSeconds()),\n            ssInfo.GetOffset()};\n  } else {\n    CCharAnimTime time;\n    if (x20_curAccelTime.GreaterThanZero())\n      time = x18_timeScale->VTimeScaleIntegral(0.f, x20_curAccelTime.GetSeconds());\n    return {ssInfo.IsLooping(), x30_initialTime + time + VGetTimeRemaining(), ssInfo.GetOffset()};\n  }\n}\n\nCCharAnimTime CAnimTreeTimeScale::VGetTimeRemaining() const {\n  CCharAnimTime timeRem = x14_child->VGetTimeRemaining();\n  if (x28_targetAccelTime == CCharAnimTime::Infinity())\n    return CCharAnimTime(x18_timeScale->VFindUpperLimit(x20_curAccelTime.GetSeconds(), timeRem.GetSeconds())) -\n           x20_curAccelTime;\n  else\n    return GetRealLifeTime(timeRem);\n}\n\nSAdvancementResults CAnimTreeTimeScale::VAdvanceView(const CCharAnimTime& dt) {\n  if (dt.EqualsZero() && dt > CCharAnimTime())\n    return x14_child->VAdvanceView(dt);\n\n  CCharAnimTime origAccelTime = x20_curAccelTime;\n  CCharAnimTime newTime = x20_curAccelTime + dt;\n  if (newTime < x28_targetAccelTime) {\n    SAdvancementResults res =\n        x14_child->VAdvanceView(x18_timeScale->VTimeScaleIntegral(origAccelTime.GetSeconds(), newTime.GetSeconds()));\n    if (res.x0_remTime.EqualsZero()) {\n      x20_curAccelTime = newTime;\n      res.x0_remTime = CCharAnimTime();\n      return res;\n    } else {\n      x20_curAccelTime =\n          x18_timeScale->VFindUpperLimit(origAccelTime.GetSeconds(), (newTime - res.x0_remTime).GetSeconds());\n      res.x0_remTime = dt - (x20_curAccelTime - origAccelTime);\n      return res;\n    }\n  } else {\n    CCharAnimTime newDt(\n        x18_timeScale->VTimeScaleIntegral(origAccelTime.GetSeconds(), x28_targetAccelTime.GetSeconds()));\n    SAdvancementResults res2;\n    if (newDt.GreaterThanZero())\n      res2 = x14_child->VAdvanceView(newDt);\n    x20_curAccelTime = x28_targetAccelTime;\n    res2.x0_remTime = res2.x0_remTime + (newTime - x28_targetAccelTime);\n    return res2;\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimTreeTimeScale.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <string>\n#include <vector>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/Character/CAnimTreeSingleChild.hpp\"\n#include \"Runtime/Character/CTimeScaleFunctions.hpp\"\n\nnamespace metaforce {\n\nclass CAnimTreeTimeScale : public CAnimTreeSingleChild {\n  std::unique_ptr<IVaryingAnimationTimeScale> x18_timeScale;\n  CCharAnimTime x20_curAccelTime;\n  CCharAnimTime x28_targetAccelTime;\n  CCharAnimTime x30_initialTime;\n\npublic:\n  CAnimTreeTimeScale(const std::weak_ptr<CAnimTreeNode>& node, float timeScale, std::string_view name);\n  CAnimTreeTimeScale(const std::weak_ptr<CAnimTreeNode>& node, std::unique_ptr<IVaryingAnimationTimeScale>&& timeScale,\n                     const CCharAnimTime& time, std::string_view name);\n\n  static std::string CreatePrimitiveName(const std::weak_ptr<CAnimTreeNode>&, float, const CCharAnimTime&, float);\n\n  CCharAnimTime GetRealLifeTime(const CCharAnimTime&) const;\n  void VSetPhase(float) override;\n  std::optional<std::unique_ptr<IAnimReader>> VSimplified() override;\n\n  size_t VGetBoolPOIList(const CCharAnimTime& time, CBoolPOINode* listOut, size_t capacity, size_t iterator,\n                         u32) const override;\n  size_t VGetInt32POIList(const CCharAnimTime& time, CInt32POINode* listOut, size_t capacity, size_t iterator,\n                          u32) const override;\n  size_t VGetParticlePOIList(const CCharAnimTime& time, CParticlePOINode* listOut, size_t capacity, size_t iterator,\n                             u32) const override;\n  size_t VGetSoundPOIList(const CCharAnimTime& time, CSoundPOINode* listOut, size_t capacity, size_t iterator,\n                          u32) const override;\n  bool VGetBoolPOIState(std::string_view name) const override;\n  s32 VGetInt32POIState(std::string_view name) const override;\n  CParticleData::EParentedMode VGetParticlePOIState(std::string_view name) const override;\n\n  CAnimTreeEffectiveContribution VGetContributionOfHighestInfluence() const override;\n  std::shared_ptr<IAnimReader> VGetBestUnblendedChild() const override;\n  std::unique_ptr<IAnimReader> VClone() const override;\n  CSteadyStateAnimInfo VGetSteadyStateAnimInfo() const override;\n  CCharAnimTime VGetTimeRemaining() const override;\n  SAdvancementResults VAdvanceView(const CCharAnimTime& dt) override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimTreeTransition.cpp",
    "content": "#include \"Runtime/Character/CAnimTreeTransition.hpp\"\n\nnamespace metaforce {\n\nstd::string CAnimTreeTransition::CreatePrimitiveName(const std::weak_ptr<CAnimTreeNode>&,\n                                                     const std::weak_ptr<CAnimTreeNode>&, float) {\n  return {};\n}\n\nCAnimTreeTransition::CAnimTreeTransition(bool b1, const std::weak_ptr<CAnimTreeNode>& a,\n                                         const std::weak_ptr<CAnimTreeNode>& b, const CCharAnimTime& transDur,\n                                         const CCharAnimTime& timeInTrans, bool runA, bool loopA, int flags,\n                                         std::string_view name, bool initialized)\n: CAnimTreeTweenBase(b1, a, b, flags, name)\n, x24_transDur(transDur)\n, x2c_timeInTrans(timeInTrans)\n, x34_runA(runA)\n, x35_loopA(loopA)\n, x36_initialized(initialized) {}\n\nCAnimTreeTransition::CAnimTreeTransition(bool b1, const std::weak_ptr<CAnimTreeNode>& a,\n                                         const std::weak_ptr<CAnimTreeNode>& b, const CCharAnimTime& transDur,\n                                         bool runA, int flags, std::string_view name)\n: CAnimTreeTweenBase(b1, a, b, flags, name)\n, x24_transDur(transDur)\n, x34_runA(runA)\n, x35_loopA(a.lock()->VGetBoolPOIState(\"Loop\")) {}\n\nstd::shared_ptr<IAnimReader> CAnimTreeTransition::VGetBestUnblendedChild() const {\n  std::shared_ptr<IAnimReader> child = x18_b->GetBestUnblendedChild();\n  return (child ? child : x18_b);\n}\n\nCCharAnimTime CAnimTreeTransition::VGetTimeRemaining() const {\n  CCharAnimTime transTimeRem = x24_transDur - x2c_timeInTrans;\n  CCharAnimTime rightTimeRem = x18_b->VGetTimeRemaining();\n  return (rightTimeRem < transTimeRem) ? transTimeRem : rightTimeRem;\n}\n\nCSteadyStateAnimInfo CAnimTreeTransition::VGetSteadyStateAnimInfo() const {\n  CSteadyStateAnimInfo bInfo = x18_b->VGetSteadyStateAnimInfo();\n\n  if (x24_transDur < bInfo.GetDuration())\n    return CSteadyStateAnimInfo(bInfo.IsLooping(), bInfo.GetDuration(), bInfo.GetOffset());\n  return CSteadyStateAnimInfo(bInfo.IsLooping(), x24_transDur, bInfo.GetOffset());\n}\n\nstd::unique_ptr<IAnimReader> CAnimTreeTransition::VClone() const {\n  return std::make_unique<CAnimTreeTransition>(\n      x20_24_b1, std::static_pointer_cast<CAnimTreeNode>(std::shared_ptr<IAnimReader>(x14_a->Clone())),\n      std::static_pointer_cast<CAnimTreeNode>(std::shared_ptr<IAnimReader>(x18_b->Clone())), x24_transDur,\n      x2c_timeInTrans, x34_runA, x35_loopA, x1c_flags, x4_name, x36_initialized);\n}\n\nstd::optional<std::unique_ptr<IAnimReader>> CAnimTreeTransition::VSimplified() {\n  if (zeus::close_enough(GetBlendingWeight(), 1.f)) {\n    if (auto simp = x18_b->Simplified())\n      return simp;\n    return {x18_b->Clone()};\n  }\n  return CAnimTreeTweenBase::VSimplified();\n}\n\nstd::optional<std::unique_ptr<IAnimReader>> CAnimTreeTransition::VReverseSimplified() {\n  if (zeus::close_enough(GetBlendingWeight(), 0.f))\n    return {x14_a->Clone()};\n  return CAnimTreeTweenBase::VReverseSimplified();\n}\n\nSAdvancementResults CAnimTreeTransition::AdvanceViewForTransitionalPeriod(const CCharAnimTime& time) {\n  IncAdvancementDepth();\n  CDoubleChildAdvancementResult res = AdvanceViewBothChildren(time, x34_runA, x35_loopA);\n  DecAdvancementDepth();\n  if (res.GetTrueAdvancement().EqualsZero())\n    return {};\n\n  float oldWeight = GetBlendingWeight();\n  x2c_timeInTrans += res.GetTrueAdvancement();\n  float newWeight = GetBlendingWeight();\n\n  if (ShouldCullTree()) {\n    if (newWeight < 0.5f)\n      x20_25_cullSelector = 1;\n    else\n      x20_25_cullSelector = 2;\n  }\n\n  if (x1c_flags & 0x1) {\n    return {res.GetTrueAdvancement(),\n            SAdvancementDeltas::Interpolate(res.GetLeftAdvancementDeltas(), res.GetRightAdvancementDeltas(), oldWeight,\n                                            newWeight)};\n  }\n\n  return {res.GetTrueAdvancement(), res.GetRightAdvancementDeltas()};\n}\n\nSAdvancementResults CAnimTreeTransition::VAdvanceView(const CCharAnimTime& time) {\n  if (time.EqualsZero()) {\n    IncAdvancementDepth();\n    x18_b->VAdvanceView(time);\n    if (x34_runA)\n      x14_a->VAdvanceView(time);\n    DecAdvancementDepth();\n    if (ShouldCullTree())\n      x20_25_cullSelector = 1;\n    return {};\n  }\n\n  if (!x36_initialized)\n    x36_initialized = true;\n\n  if (x2c_timeInTrans + time < x24_transDur) {\n    SAdvancementResults res = AdvanceViewForTransitionalPeriod(time);\n    res.x0_remTime = time - res.x0_remTime;\n    return res;\n  }\n\n  CCharAnimTime transTimeRem = x24_transDur - x2c_timeInTrans;\n  SAdvancementResults res;\n  if (transTimeRem.GreaterThanZero()) {\n    res = AdvanceViewForTransitionalPeriod(transTimeRem);\n    if (res.x0_remTime != transTimeRem)\n      return res;\n\n    // NOTE: URDE can hit an infinite loop if transTimeRem\n    // becomes negative (floating point inaccuracy).\n    // This line was moved into this branch as a workaround.\n    res.x0_remTime = time - transTimeRem;\n  }\n\n  return res;\n}\n\nvoid CAnimTreeTransition::SetBlendingWeight(float w) {\n  std::static_pointer_cast<CAnimTreeTweenBase>(x18_b)->SetBlendingWeight(w);\n}\n\nfloat CAnimTreeTransition::VGetBlendingWeight() const {\n  if (x24_transDur.GreaterThanZero())\n    return x2c_timeInTrans.GetSeconds() / x24_transDur.GetSeconds();\n  return 0.f;\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimTreeTransition.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <string_view>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Character/CAnimTreeTweenBase.hpp\"\n\nnamespace metaforce {\n\nclass CAnimTreeTransition : public CAnimTreeTweenBase {\nprotected:\n  CCharAnimTime x24_transDur;\n  CCharAnimTime x2c_timeInTrans;\n  bool x34_runA;\n  bool x35_loopA;\n  bool x36_initialized = false;\n  SAdvancementResults AdvanceViewForTransitionalPeriod(const CCharAnimTime& time);\n\npublic:\n  static std::string CreatePrimitiveName(const std::weak_ptr<CAnimTreeNode>&, const std::weak_ptr<CAnimTreeNode>&,\n                                         float);\n\n  CAnimTreeTransition(bool b1, const std::weak_ptr<CAnimTreeNode>& a, const std::weak_ptr<CAnimTreeNode>& b,\n                      const CCharAnimTime& transDur, const CCharAnimTime& timeInTrans, bool runA, bool loopA, int flags,\n                      std::string_view name, bool initialized);\n  CAnimTreeTransition(bool b1, const std::weak_ptr<CAnimTreeNode>& a, const std::weak_ptr<CAnimTreeNode>& b,\n                      const CCharAnimTime& transDur, bool runA, int flags, std::string_view name);\n  std::shared_ptr<IAnimReader> VGetBestUnblendedChild() const override;\n  CCharAnimTime VGetTimeRemaining() const override;\n  CSteadyStateAnimInfo VGetSteadyStateAnimInfo() const override;\n  std::unique_ptr<IAnimReader> VClone() const override;\n  std::optional<std::unique_ptr<IAnimReader>> VSimplified() override;\n  std::optional<std::unique_ptr<IAnimReader>> VReverseSimplified() override;\n  SAdvancementResults VAdvanceView(const CCharAnimTime& a) override;\n  void SetBlendingWeight(float w) override;\n  float VGetBlendingWeight() const override;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimTreeTweenBase.cpp",
    "content": "#include \"Runtime/Character/CAnimTreeTweenBase.hpp\"\n\n#include \"Runtime/Character/CSegIdList.hpp\"\n#include \"Runtime/Character/CSegStatementSet.hpp\"\n\nnamespace metaforce {\n\ns32 CAnimTreeTweenBase::sAdvancementDepth = 0;\n\nCAnimTreeTweenBase::CAnimTreeTweenBase(bool b1, const std::weak_ptr<CAnimTreeNode>& a,\n                                       const std::weak_ptr<CAnimTreeNode>& b, int flags, std::string_view name)\n: CAnimTreeDoubleChild(a, b, name), x1c_flags(flags), x20_24_b1{b1} {}\n\nvoid CAnimTreeTweenBase::VGetWeightedReaders(\n    rstl::reserved_vector<std::pair<float, std::weak_ptr<IAnimReader>>, 16>& out, float w) const {\n  float weight = GetBlendingWeight();\n  x14_a->VGetWeightedReaders(out, (1.f - weight) * w);\n  x18_b->VGetWeightedReaders(out, weight * w);\n}\n\nvoid CAnimTreeTweenBase::VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut) const {\n  float w = GetBlendingWeight();\n  static int sStack = 0;\n  ++sStack;\n  if (w >= 1.f) {\n    x18_b->VGetSegStatementSet(list, setOut);\n  } else if (sStack > 3) {\n    const auto& n = w > 0.5f ? x18_b : x14_a;\n    auto ptr = n->GetBestUnblendedChild();\n    if (!ptr) {\n      ptr = n;\n    }\n    ptr->VGetSegStatementSet(list, setOut);\n  } else {\n    CSegStatementSet setA, setB;\n    x14_a->VGetSegStatementSet(list, setA);\n    x18_b->VGetSegStatementSet(list, setB);\n    for (CSegId id : list.GetList()) {\n      if (w < 0.0001f) {\n        setOut[id].x0_rotation = setA[id].x0_rotation;\n        if (setA[id].x1c_hasOffset) {\n          setOut[id].x10_offset = setA[id].x10_offset;\n          setOut[id].x1c_hasOffset = true;\n        }\n      } else {\n        setOut[id].x0_rotation = zeus::CQuaternion::slerpShort(setA[id].x0_rotation, setB[id].x0_rotation, w);\n        if (setA[id].x1c_hasOffset && setB[id].x1c_hasOffset) {\n          setOut[id].x10_offset = zeus::CVector3f::lerp(setA[id].x10_offset, setB[id].x10_offset, w);\n          setOut[id].x1c_hasOffset = true;\n        }\n      }\n    }\n  }\n  --sStack;\n}\n\nvoid CAnimTreeTweenBase::VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut,\n                                             const CCharAnimTime& time) const {\n  float w = GetBlendingWeight();\n  static int sStack = 0;\n  ++sStack;\n  if (w >= 1.f) {\n    x18_b->VGetSegStatementSet(list, setOut, time);\n  } else if (sStack > 3) {\n    const auto& n = w > 0.5f ? x18_b : x14_a;\n    n->GetBestUnblendedChild()->VGetSegStatementSet(list, setOut, time);\n  } else {\n    CSegStatementSet setA, setB;\n    x14_a->VGetSegStatementSet(list, setA, time);\n    x18_b->VGetSegStatementSet(list, setB, time);\n    for (CSegId id : list.GetList()) {\n      setOut[id].x0_rotation = zeus::CQuaternion::slerpShort(setA[id].x0_rotation, setB[id].x0_rotation, w);\n      if (setA[id].x1c_hasOffset && setB[id].x1c_hasOffset) {\n        setOut[id].x10_offset = zeus::CVector3f::lerp(setA[id].x10_offset, setB[id].x10_offset, w);\n        setOut[id].x1c_hasOffset = true;\n      }\n    }\n  }\n  --sStack;\n}\n\nbool CAnimTreeTweenBase::VHasOffset(const CSegId& seg) const {\n  return (x14_a->VHasOffset(seg) && x18_b->VHasOffset(seg));\n}\n\nzeus::CVector3f CAnimTreeTweenBase::VGetOffset(const CSegId& seg) const {\n  const float weight = GetBlendingWeight();\n  if (weight >= 1.0f)\n    return x18_b->VGetOffset(seg);\n\n  const zeus::CVector3f oA = x14_a->VGetOffset(seg);\n  const zeus::CVector3f oB = x18_b->VGetOffset(seg);\n  return zeus::CVector3f::lerp(oA, oB, weight);\n}\n\nzeus::CQuaternion CAnimTreeTweenBase::VGetRotation(const CSegId& seg) const {\n  const float weight = GetBlendingWeight();\n  if (weight >= 1.0f)\n    return x18_b->VGetRotation(seg);\n\n  const zeus::CQuaternion qA = x14_a->VGetRotation(seg);\n  const zeus::CQuaternion qB = x18_b->VGetRotation(seg);\n  return zeus::CQuaternion::slerp(qA, qB, weight);\n}\n\nstd::optional<std::unique_ptr<IAnimReader>> CAnimTreeTweenBase::VSimplified() {\n  if (x20_25_cullSelector == 0) {\n    auto simpA = x14_a->Simplified();\n    auto simpB = x18_b->Simplified();\n    if (!simpA && !simpB)\n      return {};\n    auto clone = Clone();\n    if (simpA)\n      static_cast<CAnimTreeTweenBase&>(*clone).x14_a = CAnimTreeNode::Cast(std::move(*simpA));\n    if (simpB)\n      static_cast<CAnimTreeTweenBase&>(*clone).x18_b = CAnimTreeNode::Cast(std::move(*simpB));\n    return {std::move(clone)};\n  } else {\n    auto tmp = (x20_25_cullSelector == 1) ? x18_b : x14_a;\n    auto tmpUnblended = tmp->GetBestUnblendedChild();\n    if (!tmpUnblended)\n      return {tmp->Clone()};\n    else\n      return {tmpUnblended->Clone()};\n  }\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimTreeTweenBase.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <utility>\n\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Character/CAnimTreeDoubleChild.hpp\"\n\nnamespace metaforce {\n\nclass CAnimTreeTweenBase : public CAnimTreeDoubleChild {\n  static s32 sAdvancementDepth;\n\nprotected:\n  int x1c_flags;\n  bool x20_24_b1 : 1;\n  u8 x20_25_cullSelector : 2 = 0;\n\npublic:\n  CAnimTreeTweenBase(bool, const std::weak_ptr<CAnimTreeNode>& a, const std::weak_ptr<CAnimTreeNode>& b, int,\n                     std::string_view name);\n\n  virtual void SetBlendingWeight(float w) = 0;\n  virtual float VGetBlendingWeight() const = 0;\n\n  float GetBlendingWeight() const { return VGetBlendingWeight(); }\n\n  void VGetWeightedReaders(rstl::reserved_vector<std::pair<float, std::weak_ptr<IAnimReader>>, 16>& out,\n                           float w) const override;\n  float VGetRightChildWeight() const override { return GetBlendingWeight(); }\n\n  void VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut) const override;\n  void VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut, const CCharAnimTime& time) const override;\n  bool VHasOffset(const CSegId& seg) const override;\n  zeus::CVector3f VGetOffset(const CSegId& seg) const override;\n  zeus::CQuaternion VGetRotation(const CSegId& seg) const override;\n\n  std::optional<std::unique_ptr<IAnimReader>> VSimplified() override;\n  virtual std::optional<std::unique_ptr<IAnimReader>> VReverseSimplified() { return CAnimTreeTweenBase::VSimplified(); }\n\n  static bool ShouldCullTree() { return 3 <= sAdvancementDepth; }\n  static void IncAdvancementDepth() { sAdvancementDepth++; }\n  static void DecAdvancementDepth() { sAdvancementDepth--; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimation.cpp",
    "content": "#include \"Runtime/Character/CAnimation.hpp\"\n\n#include \"Runtime/Character/CMetaAnimFactory.hpp\"\n\nnamespace metaforce {\n\nCAnimation::CAnimation(CInputStream& in) {\n  x0_name = in.Get<std::string>();\n  x10_anim = CMetaAnimFactory::CreateMetaAnim(in);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimation.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <string>\n\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/Character/CMetaAnimFactory.hpp\"\n\nnamespace metaforce {\nclass IMetaAnim;\n\nclass CAnimation {\n  std::string x0_name;\n  std::shared_ptr<IMetaAnim> x10_anim;\n\npublic:\n  explicit CAnimation(CInputStream& in);\n  const std::shared_ptr<IMetaAnim>& GetMetaAnim() const { return x10_anim; }\n  std::string_view GetMetaAnimName() const { return x0_name; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimationDatabase.hpp",
    "content": "#pragma once\n\n#include <set>\n#include <string_view>\n#include <vector>\n\n#include \"Runtime/RetroTypes.hpp\"\n\nnamespace metaforce {\nclass CPrimitive;\nclass IMetaAnim;\n\nclass CAnimationDatabase {\npublic:\n  virtual ~CAnimationDatabase() = default;\n  virtual const std::shared_ptr<IMetaAnim>& GetMetaAnim(s32) const = 0;\n  virtual u32 GetNumMetaAnims() const = 0;\n  virtual const char* GetMetaAnimName(s32) const = 0;\n  virtual void GetAllUniquePrimitives(std::vector<CPrimitive>&) const = 0;\n  virtual void GetUniquePrimitivesFromMetaAnim(std::set<CPrimitive>&, std::string_view) const = 0;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimationDatabaseGame.cpp",
    "content": "#include \"Runtime/Character/CAnimationDatabaseGame.hpp\"\n\n#include \"Runtime/Character/CAnimation.hpp\"\n#include \"Runtime/Character/CPrimitive.hpp\"\n#include \"Runtime/Character/IMetaAnim.hpp\"\n\nnamespace metaforce {\n\nCAnimationDatabaseGame::CAnimationDatabaseGame(const std::vector<CAnimation>& anims) {\n  x10_anims.reserve(anims.size());\n  for (const CAnimation& anim : anims)\n    x10_anims.emplace_back(anim.GetMetaAnim());\n}\n\nconst std::shared_ptr<IMetaAnim>& CAnimationDatabaseGame::GetMetaAnim(s32 idx) const { return x10_anims[idx]; }\n\nu32 CAnimationDatabaseGame::GetNumMetaAnims() const { return x10_anims.size(); }\n\nconst char* CAnimationDatabaseGame::GetMetaAnimName(s32 idx) const {\n  return \"Meta-animation name unavailable in Release mode.\";\n}\n\nvoid CAnimationDatabaseGame::GetAllUniquePrimitives(std::vector<CPrimitive>& primsOut) const {\n  std::set<CPrimitive> primitives;\n  for (const std::shared_ptr<IMetaAnim>& anim : x10_anims)\n    anim->GetUniquePrimitives(primitives);\n  primsOut.reserve(primitives.size());\n  for (const CPrimitive& prim : primitives)\n    primsOut.push_back(prim);\n}\n\nvoid CAnimationDatabaseGame::GetUniquePrimitivesFromMetaAnim(std::set<CPrimitive>& primsOut,\n                                                             std::string_view name) const {\n  // Empty\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimationDatabaseGame.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <vector>\n\n#include \"Runtime/Character/CAnimationDatabase.hpp\"\n\nnamespace metaforce {\nclass CAnimation;\n\nclass CAnimationDatabaseGame final : public CAnimationDatabase {\n  std::vector<std::shared_ptr<IMetaAnim>> x10_anims;\n\npublic:\n  explicit CAnimationDatabaseGame(const std::vector<CAnimation>& anims);\n  const std::shared_ptr<IMetaAnim>& GetMetaAnim(s32 idx) const override;\n  u32 GetNumMetaAnims() const override;\n  const char* GetMetaAnimName(s32 idx) const override;\n  void GetAllUniquePrimitives(std::vector<CPrimitive>& primsOut) const override;\n  void GetUniquePrimitivesFromMetaAnim(std::set<CPrimitive>& primsOut, std::string_view name) const override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimationManager.cpp",
    "content": "#include \"Runtime/Character/CAnimationManager.hpp\"\n\n#include \"Runtime/Character/CAnimationDatabaseGame.hpp\"\n#include \"Runtime/Character/IMetaAnim.hpp\"\n\nnamespace metaforce {\n\nconst CAnimationDatabaseGame* CAnimationManager::GetAnimationDatabase() const { return x0_animDB.GetObj(); }\n\nstd::shared_ptr<CAnimTreeNode> CAnimationManager::GetAnimationTree(s32 animIdx,\n                                                                   const CMetaAnimTreeBuildOrders& orders) const {\n  const std::shared_ptr<IMetaAnim>& anim = x0_animDB->GetMetaAnim(animIdx);\n  return anim->GetAnimationTree(x8_sysCtx, orders);\n}\n\nconst std::shared_ptr<IMetaAnim>& CAnimationManager::GetMetaAnimation(s32 idx) const {\n  return x0_animDB->GetMetaAnim(idx);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimationManager.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/Character/CAnimationDatabaseGame.hpp\"\n#include \"Runtime/Character/CAnimSysContext.hpp\"\n\nnamespace metaforce {\nclass CAnimTreeNode;\nclass CSimplePool;\nclass IMetaAnim;\n\nstruct CMetaAnimTreeBuildOrders;\n\nclass CAnimationManager {\n  TToken<CAnimationDatabaseGame> x0_animDB;\n  CAnimSysContext x8_sysCtx;\n\npublic:\n  CAnimationManager(TToken<CAnimationDatabaseGame> animDB, CAnimSysContext sysCtx)\n  : x0_animDB(std::move(animDB)), x8_sysCtx(std::move(sysCtx)) {}\n\n  const CAnimationDatabaseGame* GetAnimationDatabase() const;\n  std::shared_ptr<CAnimTreeNode> GetAnimationTree(s32, const CMetaAnimTreeBuildOrders& orders) const;\n  const std::shared_ptr<IMetaAnim>& GetMetaAnimation(s32) const;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimationSet.cpp",
    "content": "#include \"Runtime/Character/CAnimationSet.hpp\"\n\n#include \"Runtime/Character/CMetaTransFactory.hpp\"\n\nnamespace metaforce {\n\nCAnimationSet::CAnimationSet(CInputStream& in) : x0_tableCount(in.ReadShort()) {\n  u32 animationCount = in.ReadLong();\n  x4_animations.reserve(animationCount);\n  for (u32 i = 0; i < animationCount; ++i)\n    x4_animations.emplace_back(in);\n\n  u32 transitionCount = in.ReadLong();\n  x14_transitions.reserve(transitionCount);\n  for (u32 i = 0; i < transitionCount; ++i)\n    x14_transitions.emplace_back(in);\n\n  x24_defaultTransition = CMetaTransFactory::CreateMetaTrans(in);\n\n  if (x0_tableCount > 1) {\n    u32 additiveAnimCount = in.ReadLong();\n    x28_additiveInfo.reserve(additiveAnimCount);\n    for (u32 i = 0; i < additiveAnimCount; ++i) {\n      u32 id = in.ReadLong();\n      x28_additiveInfo.emplace_back(id, in);\n    }\n    x38_defaultAdditiveInfo.read(in);\n  }\n\n  if (x0_tableCount > 2) {\n    u32 halfTransitionCount = in.ReadLong();\n    x40_halfTransitions.reserve(halfTransitionCount);\n    for (u32 i = 0; i < halfTransitionCount; ++i)\n      x40_halfTransitions.emplace_back(in);\n  }\n\n  if (x0_tableCount > 3) {\n    u32 animResourcesCount = in.ReadLong();\n    x50_animRes.reserve(animResourcesCount);\n    for (u32 i = 0; i < animResourcesCount; ++i) {\n      CAssetId anim = in.Get<CAssetId>();\n      CAssetId evnt = in.Get<CAssetId>();\n      x50_animRes.emplace_back(anim, evnt);\n    }\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAnimationSet.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <utility>\n#include <vector>\n\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Character/CAdditiveAnimPlayback.hpp\"\n#include \"Runtime/Character/CAnimation.hpp\"\n#include \"Runtime/Character/CHalfTransition.hpp\"\n#include \"Runtime/Character/CTransition.hpp\"\n\nnamespace metaforce {\n\nclass CAnimationSet {\n  u16 x0_tableCount;\n  std::vector<CAnimation> x4_animations;\n  std::vector<CTransition> x14_transitions;\n  std::shared_ptr<IMetaTrans> x24_defaultTransition;\n  std::vector<std::pair<u32, CAdditiveAnimationInfo>> x28_additiveInfo;\n  CAdditiveAnimationInfo x38_defaultAdditiveInfo;\n  std::vector<CHalfTransition> x40_halfTransitions;\n  std::vector<std::pair<CAssetId, CAssetId>> x50_animRes;\n\npublic:\n  explicit CAnimationSet(CInputStream& in);\n\n  const std::vector<CAnimation>& GetAnimations() const { return x4_animations; }\n  const std::vector<CTransition>& GetTransitions() const { return x14_transitions; }\n  const std::shared_ptr<IMetaTrans>& GetDefaultTransition() const { return x24_defaultTransition; }\n  const std::vector<CHalfTransition>& GetHalfTransitions() const { return x40_halfTransitions; }\n  const std::vector<std::pair<u32, CAdditiveAnimationInfo>>& GetAdditiveInfo() const { return x28_additiveInfo; }\n  const CAdditiveAnimationInfo& GetDefaultAdditiveInfo() const { return x38_defaultAdditiveInfo; }\n  const std::vector<std::pair<CAssetId, CAssetId>>& GetAnimResIds() const { return x50_animRes; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAssetFactory.cpp",
    "content": "#include \"Runtime/Character/CAssetFactory.hpp\"\n\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Character/CAnimCharacterSet.hpp\"\n#include \"Runtime/Character/CCharacterFactory.hpp\"\n#include \"Runtime/Character/CCharLayoutInfo.hpp\"\n#include \"Runtime/Character/CModelData.hpp\"\n\nnamespace metaforce {\n\nCFactoryFnReturn CCharacterFactoryBuilder::CDummyFactory::Build(const SObjectTag& tag, const CVParamTransfer&,\n                                                                CObjectReference* selfRef) {\n  TLockedToken<CAnimCharacterSet> ancs = g_SimplePool->GetObj({SBIG('ANCS'), tag.id});\n  return TToken<CCharacterFactory>::GetIObjObjectFor(\n      std::make_unique<CCharacterFactory>(*g_SimplePool, *ancs.GetObj(), tag.id));\n}\n\nvoid CCharacterFactoryBuilder::CDummyFactory::BuildAsync(const SObjectTag& tag, const CVParamTransfer& parms,\n                                                         std::unique_ptr<IObj>* objOut, CObjectReference* selfRef) {\n  *objOut = Build(tag, parms, selfRef);\n}\n\nvoid CCharacterFactoryBuilder::CDummyFactory::CancelBuild(const SObjectTag&) {}\n\nbool CCharacterFactoryBuilder::CDummyFactory::CanBuild(const SObjectTag&) { return true; }\n\nconst SObjectTag* CCharacterFactoryBuilder::CDummyFactory::GetResourceIdByName(std::string_view) const {\n  return nullptr;\n}\n\nFourCC CCharacterFactoryBuilder::CDummyFactory::GetResourceTypeById(CAssetId id) const { return {}; }\n\nvoid CCharacterFactoryBuilder::CDummyFactory::EnumerateResources(\n    const std::function<bool(const SObjectTag&)>& lambda) const {}\n\nvoid CCharacterFactoryBuilder::CDummyFactory::EnumerateNamedResources(\n    const std::function<bool(std::string_view, const SObjectTag&)>& lambda) const {}\n\nu32 CCharacterFactoryBuilder::CDummyFactory::ResourceSize(const metaforce::SObjectTag& tag) { return 0; }\n\nstd::shared_ptr<IDvdRequest>\nCCharacterFactoryBuilder::CDummyFactory::LoadResourceAsync(const metaforce::SObjectTag& tag, void* target) {\n  return {};\n}\n\nstd::shared_ptr<IDvdRequest>\nCCharacterFactoryBuilder::CDummyFactory::LoadResourcePartAsync(const metaforce::SObjectTag& tag, u32 off, u32 size,\n                                                               void* target) {\n  return {};\n}\n\nstd::unique_ptr<u8[]> CCharacterFactoryBuilder::CDummyFactory::LoadResourceSync(const metaforce::SObjectTag& tag) {\n  return {};\n}\n\nstd::unique_ptr<u8[]> CCharacterFactoryBuilder::CDummyFactory::LoadNewResourcePartSync(const metaforce::SObjectTag& tag,\n                                                                                       u32 off, u32 size) {\n  return {};\n}\n\nCCharacterFactoryBuilder::CCharacterFactoryBuilder() : x4_dummyStore(x0_dummyFactory) {}\n\nTToken<CCharacterFactory> CCharacterFactoryBuilder::GetFactory(const CAnimRes& res) {\n  return x4_dummyStore.GetObj({SBIG('ANCS'), res.GetId()});\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CAssetFactory.hpp",
    "content": "#pragma once\n\n#include <functional>\n#include <memory>\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/IFactory.hpp\"\n#include \"Runtime/IObj.hpp\"\n\nnamespace metaforce {\nclass CCharacterFactory;\nclass CAnimRes;\n\nclass CCharacterFactoryBuilder {\npublic:\n  class CDummyFactory : public IFactory {\n  public:\n    CFactoryFnReturn Build(const SObjectTag&, const CVParamTransfer&, CObjectReference* selfRef) override;\n    void BuildAsync(const SObjectTag&, const CVParamTransfer&, std::unique_ptr<IObj>*,\n                    CObjectReference* selfRef) override;\n    void CancelBuild(const SObjectTag&) override;\n    bool CanBuild(const SObjectTag&) override;\n    const SObjectTag* GetResourceIdByName(std::string_view) const override;\n    FourCC GetResourceTypeById(CAssetId id) const override;\n\n    void EnumerateResources(const std::function<bool(const SObjectTag&)>& lambda) const override;\n    void EnumerateNamedResources(const std::function<bool(std::string_view, const SObjectTag&)>& lambda) const override;\n\n    u32 ResourceSize(const metaforce::SObjectTag& tag) override;\n    std::shared_ptr<IDvdRequest> LoadResourceAsync(const metaforce::SObjectTag& tag, void* target) override;\n    std::shared_ptr<IDvdRequest> LoadResourcePartAsync(const metaforce::SObjectTag& tag, u32 off, u32 size,\n                                                       void* target) override;\n    std::unique_ptr<u8[]> LoadResourceSync(const metaforce::SObjectTag& tag) override;\n    std::unique_ptr<u8[]> LoadNewResourcePartSync(const metaforce::SObjectTag& tag, u32 off, u32 size) override;\n  };\n\nprivate:\n  CDummyFactory x0_dummyFactory;\n  CSimplePool x4_dummyStore;\n\npublic:\n  CCharacterFactoryBuilder();\n  TToken<CCharacterFactory> GetFactory(const CAnimRes& res);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CBodyController.cpp",
    "content": "#include \"Runtime/Character/CBodyController.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Character/CPASAnimParm.hpp\"\n#include \"Runtime/Character/CPASAnimParmData.hpp\"\n#include \"Runtime/World/CActor.hpp\"\n#include \"Runtime/World/CActorModelParticles.hpp\"\n#include \"Runtime/World/CPhysicsActor.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCBodyController::CBodyController(CActor& actor, float turnSpeed, EBodyType bodyType)\n: x0_actor(actor), x2a4_bodyStateInfo(actor, bodyType), x2f4_bodyType(bodyType), x2fc_turnSpeed(turnSpeed) {\n  x2a4_bodyStateInfo.x18_bodyController = this;\n}\n\nvoid CBodyController::EnableAnimation(bool enable) {\n  x0_actor.GetModelData()->GetAnimationData()->EnableAnimation(enable);\n}\n\nvoid CBodyController::Activate(CStateManager& mgr) {\n  x300_25_active = true;\n  x2a4_bodyStateInfo.SetState(pas::EAnimationState(GetPASDatabase().GetDefaultState()));\n  x2a4_bodyStateInfo.GetCurrentState()->Start(*this, mgr);\n  x2a4_bodyStateInfo.GetCurrentAdditiveState()->Start(*this, mgr);\n}\n\nvoid CBodyController::UpdateBody(float dt, CStateManager& mgr) {\n  UpdateFrozenInfo(dt, mgr);\n  if (x320_fireDur > 0.f) {\n    if (x328_timeOnFire > x320_fireDur) {\n      x328_timeOnFire = 0.f;\n      x320_fireDur = 0.f;\n    } else {\n      x328_timeOnFire += dt;\n    }\n  } else if (x324_electrocutionDur > 0.f) {\n    if (x32c_timeElectrocuting > x324_electrocutionDur) {\n      x32c_timeElectrocuting = 0.f;\n      x324_electrocutionDur = 0.f;\n    } else {\n      x32c_timeElectrocuting += dt;\n    }\n  }\n\n  if (GetPercentageFrozen() < 1.f && x300_28_playDeathAnims) {\n    pas::EAnimationState nextState = x2a4_bodyStateInfo.GetCurrentState()->UpdateBody(dt, *this, mgr);\n    if (nextState != pas::EAnimationState::Invalid) {\n      x2a4_bodyStateInfo.GetCurrentState()->Shutdown(*this);\n      x2a4_bodyStateInfo.SetState(nextState);\n      x2a4_bodyStateInfo.GetCurrentState()->Start(*this, mgr);\n    }\n\n    nextState = x2a4_bodyStateInfo.GetCurrentAdditiveState()->UpdateBody(dt, *this, mgr);\n    if (nextState != pas::EAnimationState::Invalid) {\n      x2a4_bodyStateInfo.GetCurrentAdditiveState()->Shutdown(*this);\n      x2a4_bodyStateInfo.SetAdditiveState(nextState);\n      x2a4_bodyStateInfo.GetCurrentAdditiveState()->Start(*this, mgr);\n    }\n  }\n}\n\nvoid CBodyController::SetTurnSpeed(float speed) { x2fc_turnSpeed = std::max(0.f, speed); }\n\nvoid CBodyController::Update(float dt, CStateManager& mgr) {\n  SetPlaybackRate(1.f);\n  if (x300_25_active) {\n    x300_24_animationOver = !x0_actor.GetModelData()->GetAnimationData()->IsAnimTimeRemaining(dt, \"Whole Body\"sv);\n    x4_cmdMgr.BlendSteeringCmds();\n    x2dc_rot = zeus::CQuaternion();\n    UpdateBody(dt, mgr);\n    if (TCastToPtr<CPhysicsActor> act = x0_actor)\n      act->RotateInOneFrameOR(x2dc_rot, dt);\n    x4_cmdMgr.Reset();\n  }\n}\n\nbool CBodyController::HasBodyState(pas::EAnimationState state) const { return GetPASDatabase().HasState(state); }\n\nvoid CBodyController::SetCurrentAnimation(const CAnimPlaybackParms& parms, bool loop, bool noTrans) {\n  x0_actor.GetModelData()->GetAnimationData()->SetAnimation(parms, noTrans);\n  x0_actor.GetModelData()->EnableLooping(loop);\n  x2f8_curAnim = parms.GetAnimationId();\n}\n\nfloat CBodyController::GetAnimTimeRemaining() const {\n  return x0_actor.GetModelData()->GetAnimationData()->GetAnimTimeRemaining(\"Whole Body\");\n}\n\nvoid CBodyController::SetPlaybackRate(float rate) {\n  x0_actor.GetModelData()->GetAnimationData()->SetPlaybackRate(rate);\n}\n// GX uses a HW approximation of 3/8 + 5/8 instead of 1/3 + 2/3.\n\nconst CPASDatabase& CBodyController::GetPASDatabase() const {\n  return x0_actor.GetModelData()->GetAnimationData()->GetCharacterInfo().GetPASDatabase();\n}\n\nvoid CBodyController::MultiplyPlaybackRate(float mul) {\n  x0_actor.GetModelData()->GetAnimationData()->MultiplyPlaybackRate(mul);\n}\n\nvoid CBodyController::FaceDirection(const zeus::CVector3f& v0, float dt) {\n  if (x300_26_frozen)\n    return;\n  zeus::CVector3f noZ = v0;\n  noZ.z() = 0.f;\n  if (noZ.canBeNormalized()) {\n    if (TCastToPtr<CPhysicsActor> act = x0_actor) {\n      zeus::CQuaternion rot = zeus::CQuaternion::lookAt(act->GetTransform().basis[1], noZ.normalized(),\n                                                        zeus::degToRad(dt * x2fc_turnSpeed));\n      rot.setImaginary(act->GetTransform().transposeRotate(rot.getImaginary()));\n      act->RotateInOneFrameOR(rot, dt);\n    }\n  }\n}\n\nvoid CBodyController::FaceDirection3D(const zeus::CVector3f& v0, const zeus::CVector3f& v1, float dt) {\n  if (x300_26_frozen)\n    return;\n  if (v0.canBeNormalized() && v1.canBeNormalized()) {\n    if (TCastToPtr<CPhysicsActor> act = x0_actor) {\n      zeus::CUnitVector3f uv0 = v0;\n      zeus::CUnitVector3f uv1 = v1;\n      float dot = uv0.dot(uv1);\n      if (!zeus::close_enough(dot, 1.f)) {\n        if (dot < -0.9999f) {\n          zeus::CQuaternion rot =\n              zeus::CQuaternion::fromAxisAngle(act->GetTransform().basis[2], zeus::degToRad(dt * x2fc_turnSpeed));\n          rot.setImaginary(act->GetTransform().transposeRotate(rot.getImaginary()));\n          act->RotateInOneFrameOR(rot, dt);\n        } else {\n          zeus::CQuaternion rot = zeus::CQuaternion::clampedRotateTo(uv1, uv0, zeus::degToRad(dt * x2fc_turnSpeed));\n          rot.setImaginary(x0_actor.GetTransform().transposeRotate(rot.getImaginary()));\n          act->RotateInOneFrameOR(rot, dt);\n        }\n      }\n    }\n  }\n}\n\nbool CBodyController::HasBodyInfo(const CActor& actor) {\n  return actor.GetModelData()->GetAnimationData()->GetCharacterInfo().GetPASDatabase().GetNumAnimStates() != 0;\n}\n\nvoid CBodyController::PlayBestAnimation(const CPASAnimParmData& parms, CRandom16& r) {\n  std::pair<float, s32> best = GetPASDatabase().FindBestAnimation(parms, r, -1);\n  CAnimPlaybackParms playParms(best.second, -1, 1.f, true);\n  SetCurrentAnimation(playParms, false, false);\n}\n\nvoid CBodyController::LoopBestAnimation(const CPASAnimParmData& parms, CRandom16& r) {\n  std::pair<float, s32> best = GetPASDatabase().FindBestAnimation(parms, r, -1);\n  CAnimPlaybackParms playParms(best.second, -1, 1.f, true);\n  SetCurrentAnimation(playParms, true, false);\n}\n\nvoid CBodyController::Freeze(float intoFreezeDur, float frozenDur, float breakoutDur) {\n  x304_intoFreezeDur = intoFreezeDur;\n  x308_frozenDur = frozenDur;\n  x30c_breakoutDur = breakoutDur;\n  x300_26_frozen = true;\n  x300_27_hasBeenFrozen = true;\n\n  if (TCastToPtr<CPhysicsActor> act = x0_actor) {\n    x314_backedUpForce = act->GetConstantForce();\n    act->SetConstantForce(zeus::skZero3f);\n    act->SetMomentumWR(zeus::skZero3f);\n  }\n\n  x320_fireDur = 0.f;\n  x328_timeOnFire = 0.f;\n  x310_timeFrozen = 0.f;\n}\n\nvoid CBodyController::UnFreeze() {\n  SetPlaybackRate(1.f);\n  x300_26_frozen = false;\n  x304_intoFreezeDur = 0.f;\n  x308_frozenDur = 0.f;\n  x30c_breakoutDur = 0.f;\n  x310_timeFrozen = 0.f;\n  x0_actor.SetVolume(1.f);\n\n  if (TCastToPtr<CPhysicsActor> act = x0_actor) {\n    act->SetConstantForce(x314_backedUpForce);\n    act->SetVelocityWR(x314_backedUpForce * (1.f / act->GetMass()));\n  }\n}\n\nfloat CBodyController::GetPercentageFrozen() const {\n  float sum = x304_intoFreezeDur + x308_frozenDur + x30c_breakoutDur;\n  if (x310_timeFrozen == 0.f || sum == 0.f)\n    return 0.f;\n\n  if (x310_timeFrozen <= x304_intoFreezeDur && x304_intoFreezeDur > 0.f)\n    return x310_timeFrozen / x304_intoFreezeDur;\n\n  if (x310_timeFrozen < sum - x30c_breakoutDur)\n    return 1.f;\n  if (x30c_breakoutDur <= 0.f)\n    return 1.f;\n\n  return 1.f - (x310_timeFrozen - (x308_frozenDur + x304_intoFreezeDur)) / x30c_breakoutDur;\n}\n\nvoid CBodyController::SetOnFire(float duration) {\n  x320_fireDur = duration;\n  x328_timeOnFire = 0.f;\n  if (IsFrozen())\n    UnFreeze();\n}\n\nvoid CBodyController::DouseFlames() {\n  if (x320_fireDur <= 0.f)\n    return;\n  x320_fireDur = 0.f;\n  x328_timeOnFire = 0.f;\n}\n\nvoid CBodyController::SetElectrocuting(float duration) {\n  if (!IsElectrocuting()) {\n    CBCAdditiveReactionCmd reaction(pas::EAdditiveReactionType::Electrocution, 1.f, true);\n    x4_cmdMgr.DeliverCmd(reaction);\n  }\n  x324_electrocutionDur = duration;\n  x32c_timeElectrocuting = 0.f;\n  if (IsFrozen())\n    UnFreeze();\n  else if (IsOnFire())\n    DouseFlames();\n}\n\nvoid CBodyController::DouseElectrocuting() {\n  x324_electrocutionDur = 0.f;\n  x32c_timeElectrocuting = 0.f;\n  CBodyStateCmd cmd(EBodyStateCmd::StopReaction);\n  x4_cmdMgr.DeliverCmd(cmd);\n}\n\nvoid CBodyController::UpdateFrozenInfo(float dt, CStateManager& mgr) {\n  if (x300_26_frozen) {\n    float totalTime = x304_intoFreezeDur + x308_frozenDur + x30c_breakoutDur;\n    if (x310_timeFrozen > totalTime &&\n        x2a4_bodyStateInfo.GetCurrentAdditiveStateId() != pas::EAnimationState::AdditiveReaction) {\n      UnFreeze();\n      x0_actor.SendScriptMsgs(EScriptObjectState::UnFrozen, mgr, EScriptObjectMessage::None);\n      mgr.GetActorModelParticles()->StartIce(x0_actor);\n      return;\n    }\n    if (x310_timeFrozen <= totalTime) {\n      float percUnfrozen = 1.f;\n      if (x310_timeFrozen < totalTime - x30c_breakoutDur)\n        percUnfrozen = 1.f - GetPercentageFrozen();\n      MultiplyPlaybackRate(percUnfrozen);\n      x310_timeFrozen += dt;\n      x0_actor.SetVolume(percUnfrozen);\n      if (x310_timeFrozen > totalTime && HasIceBreakoutState()) {\n        CBCAdditiveReactionCmd cmd(pas::EAdditiveReactionType::IceBreakout, 1.f, false);\n        x4_cmdMgr.DeliverCmd(cmd);\n      }\n    }\n  }\n}\n\nbool CBodyController::HasIceBreakoutState() const {\n  CPASAnimParmData parms(pas::EAnimationState::AdditiveReaction,\n                         CPASAnimParm::FromEnum(static_cast<s32>(pas::EAdditiveReactionType::IceBreakout)));\n  std::pair<float, s32> best = GetPASDatabase().FindBestAnimation(parms, -1);\n  return best.first > 0.f;\n}\n\nvoid CBodyController::StopElectrocution() {\n  x324_electrocutionDur = 0.f;\n  x32c_timeElectrocuting = 0.f;\n  x4_cmdMgr.DeliverCmd(CBodyStateCmd(EBodyStateCmd::StopReaction));\n}\n\nvoid CBodyController::FrozenBreakout() {\n  if (x300_26_frozen) {\n    float timeToBreakout = x304_intoFreezeDur + x308_frozenDur;\n    if (x310_timeFrozen < timeToBreakout)\n      x310_timeFrozen = timeToBreakout;\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CBodyController.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Character/CBodyStateCmdMgr.hpp\"\n#include \"Runtime/Character/CBodyStateInfo.hpp\"\n#include \"Runtime/Character/CharacterCommon.hpp\"\n\n#include <zeus/CQuaternion.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CActor;\nclass CAnimPlaybackParms;\nclass CPASAnimParmData;\nclass CPASDatabase;\nclass CRandom16;\nclass CStateManager;\n\nstruct CFinalInput;\n\nclass CBodyController {\n  CActor& x0_actor;\n  CBodyStateCmdMgr x4_cmdMgr;\n  CBodyStateInfo x2a4_bodyStateInfo;\n  zeus::CQuaternion x2dc_rot;\n  pas::ELocomotionType x2ec_locomotionType = pas::ELocomotionType::Relaxed;\n  pas::EFallState x2f0_fallState = pas::EFallState::Zero;\n  EBodyType x2f4_bodyType;\n  s32 x2f8_curAnim = -1;\n  float x2fc_turnSpeed;\n  bool x300_24_animationOver : 1 = false;\n  bool x300_25_active : 1 = false;\n  bool x300_26_frozen : 1 = false;\n  bool x300_27_hasBeenFrozen : 1 = false;\n  bool x300_28_playDeathAnims : 1 = true;\n  float x304_intoFreezeDur = 0.f;\n  float x308_frozenDur = 0.f;\n  float x30c_breakoutDur = 0.f;\n  float x310_timeFrozen = 0.f;\n  zeus::CVector3f x314_backedUpForce;\n  float x320_fireDur = 0.f;\n  float x324_electrocutionDur = 0.f;\n  float x328_timeOnFire = 0.f;\n  float x32c_timeElectrocuting = 0.f;\n  float x330_restrictedFlyerMoveSpeed = 0.f;\n\npublic:\n  CBodyController(CActor& owner, float turnSpeed, EBodyType bodyType);\n  pas::EAnimationState GetCurrentStateId() const { return x2a4_bodyStateInfo.GetCurrentStateId(); }\n  CBodyStateCmdMgr& GetCommandMgr() { return x4_cmdMgr; }\n  const CBodyStateCmdMgr& GetCommandMgr() const { return x4_cmdMgr; }\n  void SetDoDeathAnims(bool d) { x300_28_playDeathAnims = d; }\n  bool IsElectrocuting() const { return x324_electrocutionDur > 0.f; }\n  bool IsOnFire() const { return x320_fireDur > 0.f; }\n  bool IsFrozen() const { return x300_26_frozen; }\n  const CBodyStateInfo& GetBodyStateInfo() const { return x2a4_bodyStateInfo; }\n  CBodyStateInfo& BodyStateInfo() { return x2a4_bodyStateInfo; }\n  float GetTurnSpeed() const { return x2fc_turnSpeed; }\n  void SetLocomotionType(pas::ELocomotionType type) { x2ec_locomotionType = type; }\n  pas::ELocomotionType GetLocomotionType() const { return x2ec_locomotionType; }\n  CActor& GetOwner() const { return x0_actor; }\n  bool IsAnimationOver() const { return x300_24_animationOver; }\n  void EnableAnimation(bool enable);\n  bool ShouldPlayDeathAnims() const { return x300_28_playDeathAnims; }\n  s32 GetCurrentAnimId() const { return x2f8_curAnim; }\n  void Activate(CStateManager& mgr);\n  CAdditiveBodyState* GetCurrentAdditiveState() { return x2a4_bodyStateInfo.GetCurrentAdditiveState(); }\n  void SetState(pas::EAnimationState state) { x2a4_bodyStateInfo.SetState(state); }\n  void Update(float dt, CStateManager& mgr);\n  bool ShouldBeHurled() const { return HasBodyState(pas::EAnimationState::Hurled); }\n  bool HasBodyState(pas::EAnimationState state) const;\n  pas::EFallState GetFallState() const { return x2f0_fallState; }\n  void SetFallState(pas::EFallState state) { x2f0_fallState = state; }\n  void UpdateBody(float dt, CStateManager& mgr);\n  void SetAdditiveState(pas::EAnimationState state) { x2a4_bodyStateInfo.SetAdditiveState(state); }\n  void SetTurnSpeed(float speed);\n  void SetCurrentAnimation(const CAnimPlaybackParms& parms, bool loop, bool noTrans);\n  float GetAnimTimeRemaining() const;\n  void SetPlaybackRate(float rate);\n  void MultiplyPlaybackRate(float mul);\n  void SetDeltaRotation(const zeus::CQuaternion& q) { x2dc_rot *= q; }\n  void FaceDirection(const zeus::CVector3f& v0, float dt);\n  void FaceDirection3D(const zeus::CVector3f& v0, const zeus::CVector3f& v1, float dt);\n  static bool HasBodyInfo(const CActor& actor);\n  const CPASDatabase& GetPASDatabase() const;\n  void PlayBestAnimation(const CPASAnimParmData& parms, CRandom16& r);\n  void LoopBestAnimation(const CPASAnimParmData& parms, CRandom16& r);\n  void Freeze(float intoFreezeDur, float frozenDur, float breakoutDur);\n  void UnFreeze();\n  float GetPercentageFrozen() const;\n  void SetOnFire(float duration);\n  void DouseFlames();\n  void SetElectrocuting(float duration);\n  void DouseElectrocuting();\n  void UpdateFrozenInfo(float dt, CStateManager& mgr);\n  bool HasIceBreakoutState() const;\n  void StopElectrocution();\n  void FrozenBreakout();\n  pas::EAnimationState GetCurrentAdditiveStateId() const { return x2a4_bodyStateInfo.GetCurrentAdditiveStateId(); }\n  EBodyType GetBodyType() const { return x2f4_bodyType; }\n  bool HasBeenFrozen() const { return x300_27_hasBeenFrozen; }\n  float GetRestrictedFlyerMoveSpeed() const { return x330_restrictedFlyerMoveSpeed; }\n  void SetRestrictedFlyerMoveSpeed(float speed) { x330_restrictedFlyerMoveSpeed = speed; }\n  bool GetActive() const { return x300_25_active; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CBodyState.cpp",
    "content": "#include \"Runtime/Character/CBodyState.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Character/CBodyController.hpp\"\n#include \"Runtime/Character/CPASAnimParmData.hpp\"\n#include \"Runtime/World/CActor.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nvoid CBSAttack::Start(CBodyController& bc, CStateManager& mgr) {\n  const auto* cmd = static_cast<const CBCMeleeAttackCmd*>(bc.GetCommandMgr().GetCmd(EBodyStateCmd::MeleeAttack));\n  const CPASDatabase& pasDatabase = bc.GetPASDatabase();\n  const CPASAnimParmData parms(pas::EAnimationState::MeleeAttack, CPASAnimParm::FromEnum(s32(cmd->GetAttackSeverity())),\n                               CPASAnimParm::FromEnum(s32(bc.GetLocomotionType())));\n  const std::pair<float, s32> best = pasDatabase.FindBestAnimation(parms, *mgr.GetActiveRandom(), -1);\n  const CAnimPlaybackParms playParms(best.second, -1, 1.f, true);\n  bc.SetCurrentAnimation(playParms, false, false);\n  if (cmd->HasAttackTargetPos()) {\n    x20_targetPos = cmd->GetAttackTargetPos();\n\n    CCharAnimTime evTime = bc.GetOwner().GetModelData()->GetAnimationData()->GetTimeOfUserEvent(\n        EUserEventType::AlignTargetPosStart, CCharAnimTime::Infinity());\n    x2c_alignTargetPosStartTime = (evTime != CCharAnimTime::Infinity()) ? evTime.GetSeconds() : 0.f;\n\n    evTime = bc.GetOwner().GetModelData()->GetAnimationData()->GetTimeOfUserEvent(EUserEventType::AlignTargetPos,\n                                                                                  CCharAnimTime::Infinity());\n    x30_alignTargetPosTime = (evTime != CCharAnimTime::Infinity()) ? evTime.GetSeconds() : bc.GetAnimTimeRemaining();\n  } else {\n    x20_targetPos = zeus::skZero3f;\n    x2c_alignTargetPosStartTime = -1.f;\n    x30_alignTargetPosTime = -1.f;\n  }\n\n  x4_nextState = pas::EAnimationState::Locomotion;\n  x34_curTime = 0.f;\n}\n\npas::EAnimationState CBSAttack::GetBodyStateTransition(float dt, const CBodyController& bc) {\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Hurled)) {\n    return pas::EAnimationState::Hurled;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockDown)) {\n    return pas::EAnimationState::Fall;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopHitReaction)) {\n    return pas::EAnimationState::LoopReaction;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockBack)) {\n    return pas::EAnimationState::KnockBack;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Locomotion)) {\n    return pas::EAnimationState::Locomotion;\n  }\n  if (const auto* cmd = static_cast<const CBCSlideCmd*>(bc.GetCommandMgr().GetCmd(EBodyStateCmd::Slide))) {\n    x8_slide = *cmd;\n    x4_nextState = pas::EAnimationState::Slide;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Generate)) {\n    return pas::EAnimationState::Generate;\n  }\n  if (bc.IsAnimationOver()) {\n    if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::MeleeAttack)) {\n      return pas::EAnimationState::MeleeAttack;\n    }\n    if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::ProjectileAttack)) {\n      return pas::EAnimationState::ProjectileAttack;\n    }\n    if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopAttack)) {\n      return pas::EAnimationState::LoopAttack;\n    }\n    return x4_nextState;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::NextState)) {\n    return x4_nextState;\n  }\n  return pas::EAnimationState::Invalid;\n}\n\nvoid CBSAttack::UpdatePhysicsActor(const CBodyController& bc, float dt) {\n  if (x20_targetPos.isZero()) {\n    return;\n  }\n\n  if (x34_curTime < x2c_alignTargetPosStartTime || x34_curTime > x30_alignTargetPosTime) {\n    return;\n  }\n\n  if (const TCastToPtr<CPhysicsActor> act = bc.GetOwner()) {\n    zeus::CVector3f delta = x20_targetPos - act->GetTranslation();\n    const float dur = x30_alignTargetPosTime - x2c_alignTargetPosStartTime;\n    if (dur > 0.f) {\n      delta *= zeus::CVector3f(dt / dur);\n    }\n    const zeus::CVector3f localDelta = act->GetTransform().transposeRotate(delta);\n    act->ApplyImpulseWR(act->GetMoveToORImpulseWR(localDelta, dt), zeus::CAxisAngle());\n  }\n}\n\npas::EAnimationState CBSAttack::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) {\n  x34_curTime += dt;\n  const pas::EAnimationState st = GetBodyStateTransition(dt, bc);\n  if (st == pas::EAnimationState::Invalid) {\n    if (!bc.GetCommandMgr().GetTargetVector().isZero()) {\n      bc.FaceDirection(bc.GetCommandMgr().GetTargetVector(), dt);\n    }\n    UpdatePhysicsActor(bc, dt);\n  } else if (st == pas::EAnimationState::Slide) {\n    bc.GetCommandMgr().DeliverCmd(x8_slide);\n  }\n  return st;\n}\n\nvoid CBSProjectileAttack::Start(CBodyController& bc, CStateManager& mgr) {\n  const auto* cmd =\n      static_cast<const CBCProjectileAttackCmd*>(bc.GetCommandMgr().GetCmd(EBodyStateCmd::ProjectileAttack));\n  zeus::CVector3f localDelta =\n      bc.GetOwner().GetTransform().transposeRotate(cmd->GetTargetPosition() - bc.GetOwner().GetTranslation());\n  zeus::CRelAngle angle = std::atan2(localDelta.y(), localDelta.x());\n  angle.makeRel();\n  const float attackAngle = angle.asDegrees();\n  const CPASAnimParmData parms(\n      pas::EAnimationState::ProjectileAttack, CPASAnimParm::FromEnum(s32(cmd->GetAttackSeverity())),\n      CPASAnimParm::FromReal32(angle.asDegrees()), CPASAnimParm::FromEnum(s32(bc.GetLocomotionType())));\n  const std::pair<float, s32> best1 = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1);\n  if (cmd->BlendTwoClosest()) {\n    const std::pair<float, s32> best2 =\n        bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), best1.second);\n    const CPASAnimState* projAttackState = bc.GetPASDatabase().GetAnimState(pas::EAnimationState::ProjectileAttack);\n    float angle1 = projAttackState->GetAnimParmData(best1.second, 1).GetReal32Value();\n    float angle2 = projAttackState->GetAnimParmData(best2.second, 1).GetReal32Value();\n    if (angle1 - angle2 > 180.f) {\n      angle2 += 360.f;\n    } else if (angle2 - angle1 > 180.f) {\n      angle1 += 360.f;\n    }\n    const CAnimPlaybackParms playParms(best1.second, best2.second, (angle1 - attackAngle) / (angle1 - angle2), true);\n    bc.SetCurrentAnimation(playParms, false, false);\n  } else {\n    const CAnimPlaybackParms playParms(best1.second, -1, 1.f, true);\n    bc.SetCurrentAnimation(playParms, false, false);\n  }\n}\n\npas::EAnimationState CBSProjectileAttack::GetBodyStateTransition(float dt, const CBodyController& bc) const {\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Hurled)) {\n    return pas::EAnimationState::Hurled;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockDown)) {\n    return pas::EAnimationState::Fall;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopHitReaction)) {\n    return pas::EAnimationState::LoopReaction;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockBack)) {\n    return pas::EAnimationState::KnockBack;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Locomotion)) {\n    return pas::EAnimationState::Locomotion;\n  }\n  if (bc.IsAnimationOver() || bc.GetCommandMgr().GetCmd(EBodyStateCmd::NextState)) {\n    return pas::EAnimationState::Locomotion;\n  }\n  return pas::EAnimationState::Invalid;\n}\n\npas::EAnimationState CBSProjectileAttack::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) {\n  const pas::EAnimationState st = GetBodyStateTransition(dt, bc);\n  if (st == pas::EAnimationState::Invalid) {\n    if (!bc.GetCommandMgr().GetTargetVector().isZero()) {\n      bc.FaceDirection(bc.GetCommandMgr().GetTargetVector(), dt);\n    }\n  }\n  return st;\n}\n\nvoid CBSDie::Start(CBodyController& bc, CStateManager& mgr) {\n  bool shouldReset = true;\n  if (bc.ShouldPlayDeathAnims()) {\n    const CPASAnimParmData parms(pas::EAnimationState::Death, CPASAnimParm::FromEnum(s32(bc.GetFallState())));\n    const std::pair<float, s32> best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1);\n    if (best.first > 0.f) {\n      const CAnimPlaybackParms playParms(best.second, -1, 1.f, true);\n      bc.SetCurrentAnimation(playParms, false, false);\n      x4_remTime = bc.GetAnimTimeRemaining();\n      shouldReset = false;\n    }\n  }\n\n  if (shouldReset) {\n    bc.EnableAnimation(false);\n    x4_remTime = bc.ShouldPlayDeathAnims() ? 3.f : 4.f;\n  }\n\n  x8_isDead = false;\n}\n\npas::EAnimationState CBSDie::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) {\n  x4_remTime -= dt;\n  if (x4_remTime <= 0.f) {\n    bc.EnableAnimation(false);\n    x8_isDead = true;\n  }\n  return pas::EAnimationState::Invalid;\n}\n\nvoid CBSFall::Start(CBodyController& bc, CStateManager& mgr) {\n  const auto* cmd = static_cast<const CBCKnockDownCmd*>(bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockDown));\n  zeus::CVector3f localDir = bc.GetOwner().GetTransform().transposeRotate(cmd->GetHitDirection());\n  zeus::CRelAngle angle = std::atan2(localDir.y(), localDir.x());\n  angle.makeRel();\n  const CPASAnimParmData parms(pas::EAnimationState::Fall, CPASAnimParm::FromReal32(angle.asDegrees()),\n                               CPASAnimParm::FromEnum(s32(cmd->GetHitSeverity())));\n  const std::pair<float, s32> best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1);\n  const CAnimPlaybackParms playParms(best.second, -1, 1.f, true);\n  bc.SetCurrentAnimation(playParms, false, false);\n  const CPASAnimState* knockdownState = bc.GetPASDatabase().GetAnimState(pas::EAnimationState::Fall);\n  if (!knockdownState->GetAnimParmData(best.second, 2).GetBoolValue()) {\n    const float animAngle = zeus::degToRad(knockdownState->GetAnimParmData(best.second, 0).GetReal32Value());\n    zeus::CRelAngle delta1 = angle - animAngle;\n    delta1.makeRel();\n    zeus::CRelAngle delta2 = animAngle - angle;\n    delta2.makeRel();\n    const float minAngle = std::min(float(delta1), float(delta2));\n    x8_remTime = 0.15f * bc.GetAnimTimeRemaining();\n    const float flippedAngle = (delta1 > M_PIF) ? -minAngle : minAngle;\n    x4_rotateSpeed = (x8_remTime > FLT_EPSILON) ? flippedAngle / x8_remTime : flippedAngle;\n  } else {\n    x8_remTime = 0.f;\n    x4_rotateSpeed = 0.f;\n  }\n  xc_fallState = pas::EFallState(knockdownState->GetAnimParmData(best.second, 3).GetEnumValue());\n}\n\npas::EAnimationState CBSFall::GetBodyStateTransition(float dt, const CBodyController& bc) const {\n  if (bc.IsAnimationOver()) {\n    return pas::EAnimationState::LieOnGround;\n  }\n  return pas::EAnimationState::Invalid;\n}\n\npas::EAnimationState CBSFall::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) {\n  const pas::EAnimationState st = GetBodyStateTransition(dt, bc);\n  if (st == pas::EAnimationState::Invalid && x8_remTime > 0.f) {\n    zeus::CQuaternion quat;\n    quat.rotateZ(x4_rotateSpeed * dt);\n    bc.SetDeltaRotation(quat);\n    x8_remTime -= dt;\n  }\n  return st;\n}\n\nvoid CBSFall::Shutdown(CBodyController& bc) { bc.SetFallState(xc_fallState); }\n\nvoid CBSGetup::Start(CBodyController& bc, CStateManager& mgr) {\n  const auto* cmd = static_cast<const CBCGetupCmd*>(bc.GetCommandMgr().GetCmd(EBodyStateCmd::Getup));\n  const CPASAnimParmData parms(pas::EAnimationState::Getup, CPASAnimParm::FromEnum(s32(bc.GetFallState())),\n                               CPASAnimParm::FromEnum(s32(cmd->GetGetupType())));\n  const std::pair<float, s32> best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1);\n  if (best.first > FLT_EPSILON) {\n    if (best.second != bc.GetCurrentAnimId()) {\n      const CAnimPlaybackParms playParms(best.second, -1, 1.f, true);\n      bc.SetCurrentAnimation(playParms, false, false);\n    }\n    x4_fallState = pas::EFallState(\n        bc.GetPASDatabase().GetAnimState(pas::EAnimationState::Getup)->GetAnimParmData(best.second, 2).GetEnumValue());\n  } else {\n    x4_fallState = pas::EFallState::Zero;\n  }\n}\n\npas::EAnimationState CBSGetup::GetBodyStateTransition(float dt, const CBodyController& bc) const {\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Hurled)) {\n    return pas::EAnimationState::Hurled;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockDown)) {\n    return pas::EAnimationState::Fall;\n  }\n  if (bc.IsAnimationOver()) {\n    if (x4_fallState == pas::EFallState::Zero) {\n      return pas::EAnimationState::Locomotion;\n    }\n    return pas::EAnimationState::Getup;\n  }\n  return pas::EAnimationState::Invalid;\n}\n\npas::EAnimationState CBSGetup::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) {\n  return GetBodyStateTransition(dt, bc);\n}\n\nvoid CBSGetup::Shutdown(CBodyController& bc) { bc.SetFallState(x4_fallState); }\n\nvoid CBSKnockBack::Start(CBodyController& bc, CStateManager& mgr) {\n  const auto* cmd = static_cast<const CBCKnockBackCmd*>(bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockBack));\n  const zeus::CVector3f localDir = bc.GetOwner().GetTransform().transposeRotate(cmd->GetHitDirection());\n  zeus::CRelAngle angle = std::atan2(localDir.y(), localDir.x());\n  angle.makeRel();\n  const CPASAnimParmData parms(pas::EAnimationState::KnockBack, CPASAnimParm::FromReal32(angle.asDegrees()),\n                               CPASAnimParm::FromEnum(s32(cmd->GetHitSeverity())));\n  const std::pair<float, s32> best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1);\n  const CAnimPlaybackParms playParms(best.second, -1, 1.f, true);\n  bc.SetCurrentAnimation(playParms, false, false);\n  const CPASAnimState* knockbackState = bc.GetPASDatabase().GetAnimState(pas::EAnimationState::KnockBack);\n  if (!knockbackState->GetAnimParmData(best.second, 2).GetBoolValue()) {\n    const float animAngle = zeus::degToRad(knockbackState->GetAnimParmData(best.second, 0).GetReal32Value());\n    zeus::CRelAngle delta1 = angle - animAngle;\n    delta1.makeRel();\n    zeus::CRelAngle delta2 = animAngle - angle;\n    delta2.makeRel();\n    const float minAngle = std::min(float(delta1), float(delta2));\n    const float flippedAngle = (delta1 > M_PIF) ? -minAngle : minAngle;\n    xc_remTime = 0.15f * bc.GetAnimTimeRemaining();\n    x8_rotateSpeed = (xc_remTime > FLT_EPSILON) ? flippedAngle / xc_remTime : flippedAngle;\n  } else {\n    xc_remTime = 0.f;\n    x8_rotateSpeed = 0.f;\n  }\n  x4_curTime = 0.f;\n}\n\npas::EAnimationState CBSKnockBack::GetBodyStateTransition(float dt, const CBodyController& bc) const {\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Hurled)) {\n    return pas::EAnimationState::Hurled;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockDown)) {\n    return pas::EAnimationState::Fall;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopHitReaction)) {\n    return pas::EAnimationState::LoopReaction;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockBack) && x4_curTime > 0.2f) {\n    return pas::EAnimationState::KnockBack;\n  }\n  if (bc.IsAnimationOver()) {\n    return pas::EAnimationState::Locomotion;\n  }\n  return pas::EAnimationState::Invalid;\n}\n\npas::EAnimationState CBSKnockBack::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) {\n  const pas::EAnimationState st = GetBodyStateTransition(dt, bc);\n  if (st == pas::EAnimationState::Invalid) {\n    x4_curTime += dt;\n    if (xc_remTime > 0.f) {\n      zeus::CQuaternion quat;\n      quat.rotateZ(x8_rotateSpeed * dt);\n      bc.SetDeltaRotation(quat);\n      xc_remTime -= dt;\n    }\n  }\n  return st;\n}\n\nCBSLieOnGround::CBSLieOnGround(CActor& actor) {\n  x4_24_hasGroundHit = actor.GetModelData()->GetAnimationData()->GetCharacterInfo().GetPASDatabase().HasState(\n      pas::EAnimationState::GroundHit);\n}\n\nvoid CBSLieOnGround::Start(CBodyController& bc, CStateManager& mgr) {\n  const CPASAnimParmData parms(pas::EAnimationState::LieOnGround, CPASAnimParm::FromEnum(s32(bc.GetFallState())));\n  const std::pair<float, s32> best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1);\n  if (best.first > 0.f) {\n    const CAnimPlaybackParms playParms(best.second, -1, 1.f, true);\n    bc.SetCurrentAnimation(playParms, true, false);\n  } else {\n    bc.EnableAnimation(false);\n  }\n}\n\npas::EAnimationState CBSLieOnGround::GetBodyStateTransition(float dt, const CBodyController& bc) const {\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Die)) {\n    return pas::EAnimationState::Death;\n  }\n  if (x4_24_hasGroundHit && bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockBack)) {\n    return pas::EAnimationState::GroundHit;\n  }\n  if (!bc.GetCommandMgr().GetCmd(EBodyStateCmd::Locomotion) && bc.GetCommandMgr().GetCmd(EBodyStateCmd::Getup)) {\n    return pas::EAnimationState::Getup;\n  }\n  return pas::EAnimationState::Invalid;\n}\n\npas::EAnimationState CBSLieOnGround::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) {\n  return GetBodyStateTransition(dt, bc);\n}\n\nvoid CBSLieOnGround::Shutdown(CBodyController& bc) { bc.EnableAnimation(true); }\n\nvoid CBSStep::Start(CBodyController& bc, CStateManager& mgr) {\n  const auto* cmd = static_cast<const CBCStepCmd*>(bc.GetCommandMgr().GetCmd(EBodyStateCmd::Step));\n  const CPASAnimParmData parms(pas::EAnimationState::Step, CPASAnimParm::FromEnum(s32(cmd->GetStepDirection())),\n                               CPASAnimParm::FromEnum(s32(cmd->GetStepType())));\n  bc.PlayBestAnimation(parms, *mgr.GetActiveRandom());\n}\n\npas::EAnimationState CBSStep::GetBodyStateTransition(float dt, const CBodyController& bc) const {\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Hurled)) {\n    return pas::EAnimationState::Hurled;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockDown)) {\n    return pas::EAnimationState::Fall;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopHitReaction)) {\n    return pas::EAnimationState::LoopReaction;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockBack)) {\n    return pas::EAnimationState::KnockBack;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Locomotion)) {\n    return pas::EAnimationState::Locomotion;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Generate)) {\n    return pas::EAnimationState::Generate;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::MeleeAttack)) {\n    return pas::EAnimationState::MeleeAttack;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::ProjectileAttack)) {\n    return pas::EAnimationState::ProjectileAttack;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopAttack)) {\n    return pas::EAnimationState::LoopAttack;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Jump)) {\n    return pas::EAnimationState::Jump;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopReaction)) {\n    return pas::EAnimationState::LoopReaction;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Scripted)) {\n    return pas::EAnimationState::Scripted;\n  }\n  if (bc.IsAnimationOver() || bc.GetCommandMgr().GetCmd(EBodyStateCmd::NextState)) {\n    return pas::EAnimationState::Locomotion;\n  }\n  return pas::EAnimationState::Invalid;\n}\n\npas::EAnimationState CBSStep::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) {\n  const pas::EAnimationState st = GetBodyStateTransition(dt, bc);\n  if (st == pas::EAnimationState::Invalid && !bc.GetCommandMgr().GetTargetVector().isZero()) {\n    bc.FaceDirection(bc.GetCommandMgr().GetTargetVector(), dt);\n  }\n  return st;\n}\n\nvoid CBSTurn::Start(CBodyController& bc, CStateManager& mgr) {\n  const zeus::CVector3f& lookDir = bc.GetOwner().GetTransform().basis[1];\n  const zeus::CVector2f lookDir2d(lookDir.toVec2f());\n  x8_dest = zeus::CVector2f(bc.GetCommandMgr().GetFaceVector().toVec2f());\n  const float deltaAngle = zeus::radToDeg(zeus::CVector2f::getAngleDiff(lookDir2d, x8_dest));\n  x10_turnDir = pas::ETurnDirection(zeus::CVector2f(lookDir2d.y(), -lookDir2d.x()).dot(x8_dest) > 0.f);\n  const CPASAnimParmData parms(pas::EAnimationState::Turn, CPASAnimParm::FromEnum(s32(x10_turnDir)),\n                               CPASAnimParm::FromReal32(deltaAngle),\n                               CPASAnimParm::FromEnum(s32(bc.GetLocomotionType())));\n  const std::pair<float, s32> best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1);\n  const CAnimPlaybackParms playParms(best.second, -1, 1.f, true);\n  bc.SetCurrentAnimation(playParms, false, false);\n  const float animAngle =\n      bc.GetPASDatabase().GetAnimState(pas::EAnimationState::Turn)->GetAnimParmData(best.second, 1).GetReal32Value();\n  x4_rotateSpeed =\n      zeus::degToRad((x10_turnDir == pas::ETurnDirection::Left) ? animAngle - deltaAngle : deltaAngle - animAngle);\n  const float timeRem = bc.GetAnimTimeRemaining();\n  if (timeRem > 0.f) {\n    x4_rotateSpeed /= timeRem;\n  }\n}\n\nbool CBSTurn::FacingDest(const CBodyController& bc) const {\n  const zeus::CVector3f& lookDir = bc.GetOwner().GetTransform().basis[1];\n  const zeus::CVector2f lookDir2d(lookDir.toVec2f());\n  const zeus::CVector2f leftDir(lookDir2d.y(), -lookDir2d.x());\n\n  if (x10_turnDir == pas::ETurnDirection::Left) {\n    if (leftDir.dot(x8_dest) < 0.f) {\n      return true;\n    }\n  } else {\n    if (leftDir.dot(x8_dest) > 0.f) {\n      return true;\n    }\n  }\n\n  return false;\n}\n\npas::EAnimationState CBSTurn::GetBodyStateTransition(float dt, CBodyController& bc) const {\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Hurled)) {\n    return pas::EAnimationState::Hurled;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockDown)) {\n    return pas::EAnimationState::Fall;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopHitReaction)) {\n    return pas::EAnimationState::LoopReaction;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockBack)) {\n    return pas::EAnimationState::KnockBack;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Locomotion)) {\n    return pas::EAnimationState::Locomotion;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Generate)) {\n    return pas::EAnimationState::Generate;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::MeleeAttack)) {\n    return pas::EAnimationState::MeleeAttack;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::ProjectileAttack)) {\n    return pas::EAnimationState::ProjectileAttack;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopAttack)) {\n    return pas::EAnimationState::LoopAttack;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopReaction)) {\n    return pas::EAnimationState::LoopReaction;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Jump)) {\n    return pas::EAnimationState::Jump;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Step)) {\n    return pas::EAnimationState::Step;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Scripted)) {\n    return pas::EAnimationState::Scripted;\n  }\n  if (bc.IsAnimationOver() || FacingDest(bc) || !bc.GetCommandMgr().GetMoveVector().isZero()) {\n    return pas::EAnimationState::Locomotion;\n  }\n  return pas::EAnimationState::Invalid;\n}\n\npas::EAnimationState CBSTurn::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) {\n  const pas::EAnimationState st = GetBodyStateTransition(dt, bc);\n  if (st == pas::EAnimationState::Invalid) {\n    zeus::CQuaternion quat;\n    quat.rotateZ(x4_rotateSpeed * dt);\n    bc.SetDeltaRotation(quat);\n  }\n  return st;\n}\n\nvoid CBSFlyerTurn::Start(CBodyController& bc, CStateManager& mgr) {\n  if (bc.GetPASDatabase().GetAnimState(pas::EAnimationState::Turn)->HasAnims()) {\n    CBSTurn::Start(bc, mgr);\n  } else {\n    x8_dest = zeus::CVector2f(bc.GetCommandMgr().GetFaceVector().toVec2f());\n    const zeus::CVector3f& lookDir = bc.GetOwner().GetTransform().basis[1];\n    const zeus::CVector2f lookDir2d(lookDir.toVec2f());\n    x10_turnDir = pas::ETurnDirection(zeus::CVector2f(lookDir2d.y(), -lookDir2d.x()).dot(x8_dest) > 0.f);\n    const CPASAnimParmData parms(pas::EAnimationState::Locomotion, CPASAnimParm::FromEnum(0),\n                                 CPASAnimParm::FromEnum(s32(bc.GetLocomotionType())));\n    const std::pair<float, s32> best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1);\n    if (best.second != bc.GetCurrentAnimId()) {\n      const CAnimPlaybackParms playParms(best.second, -1, 1.f, true);\n      bc.SetCurrentAnimation(playParms, true, false);\n    }\n  }\n}\n\npas::EAnimationState CBSFlyerTurn::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) {\n  if (bc.GetPASDatabase().GetAnimState(pas::EAnimationState::Turn)->HasAnims()) {\n    return CBSTurn::UpdateBody(dt, bc, mgr);\n  }\n\n  const pas::EAnimationState st = GetBodyStateTransition(dt, bc);\n  if (st == pas::EAnimationState::Invalid) {\n    if (!bc.GetCommandMgr().GetFaceVector().isZero()) {\n      x8_dest = zeus::CVector2f(bc.GetCommandMgr().GetFaceVector().toVec2f());\n      const zeus::CVector3f& lookDir = bc.GetOwner().GetTransform().basis[1];\n      const zeus::CVector2f lookDir2d(lookDir.toVec2f());\n      x10_turnDir = pas::ETurnDirection(zeus::CVector2f(lookDir2d.y(), -lookDir2d.x()).dot(x8_dest) > 0.f);\n    }\n    bc.FaceDirection(zeus::CVector3f(x8_dest.x(), x8_dest.y(), 0.f), dt);\n  }\n  return st;\n}\n\nvoid CBSLoopAttack::Start(CBodyController& bc, CStateManager& mgr) {\n  const auto* cmd = static_cast<const CBCLoopAttackCmd*>(bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopAttack));\n  x8_loopAttackType = cmd->GetAttackType();\n  xc_24_waitForAnimOver = cmd->WaitForAnimOver();\n  xc_25_advance = false;\n\n  if (bc.GetLocomotionType() == pas::ELocomotionType::Crouch) {\n    x4_state = pas::ELoopState::Loop;\n    const CPASAnimParmData parms(pas::EAnimationState::LoopAttack, CPASAnimParm::FromEnum(s32(x4_state)),\n                                 CPASAnimParm::FromEnum(s32(x8_loopAttackType)));\n    bc.LoopBestAnimation(parms, *mgr.GetActiveRandom());\n  } else {\n    x4_state = pas::ELoopState::Begin;\n    const CPASAnimParmData parms(pas::EAnimationState::LoopAttack, CPASAnimParm::FromEnum(s32(x4_state)),\n                                 CPASAnimParm::FromEnum(s32(x8_loopAttackType)));\n    const std::pair<float, s32> best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1);\n    if (best.first > FLT_EPSILON) {\n      const CAnimPlaybackParms playParms(best.second, -1, 1.f, true);\n      bc.SetCurrentAnimation(playParms, false, false);\n    } else {\n      x4_state = pas::ELoopState::Loop;\n      const CPASAnimParmData loopParms(pas::EAnimationState::LoopAttack, CPASAnimParm::FromEnum(s32(x4_state)),\n                                       CPASAnimParm::FromEnum(s32(x8_loopAttackType)));\n      bc.LoopBestAnimation(loopParms, *mgr.GetActiveRandom());\n    }\n  }\n}\n\npas::EAnimationState CBSLoopAttack::GetBodyStateTransition(float dt, const CBodyController& bc) const {\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Hurled)) {\n    return pas::EAnimationState::Hurled;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockDown)) {\n    return pas::EAnimationState::Fall;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopHitReaction)) {\n    return pas::EAnimationState::LoopReaction;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockBack)) {\n    return pas::EAnimationState::KnockBack;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Locomotion)) {\n    return pas::EAnimationState::Locomotion;\n  }\n\n  if (x4_state == pas::ELoopState::End) {\n    if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::MeleeAttack)) {\n      return pas::EAnimationState::MeleeAttack;\n    }\n    if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::ProjectileAttack)) {\n      return pas::EAnimationState::ProjectileAttack;\n    }\n    if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopAttack)) {\n      return pas::EAnimationState::LoopAttack;\n    }\n    if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Step)) {\n      return pas::EAnimationState::Step;\n    }\n    if (!bc.GetCommandMgr().GetMoveVector().isZero()) {\n      return pas::EAnimationState::Locomotion;\n    }\n    if (!bc.GetCommandMgr().GetFaceVector().isZero()) {\n      return pas::EAnimationState::Turn;\n    }\n  }\n\n  return pas::EAnimationState::Invalid;\n}\n\npas::EAnimationState CBSLoopAttack::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) {\n  const pas::EAnimationState st = GetBodyStateTransition(dt, bc);\n  if (st == pas::EAnimationState::Invalid) {\n    xc_25_advance |= bc.GetCommandMgr().GetCmd(EBodyStateCmd::ExitState) != nullptr;\n    switch (x4_state) {\n    case pas::ELoopState::Begin:\n      if (xc_25_advance && (!xc_24_waitForAnimOver || bc.IsAnimationOver())) {\n        x4_state = pas::ELoopState::Invalid;\n        return pas::EAnimationState::Locomotion;\n      }\n      if (bc.IsAnimationOver()) {\n        const CPASAnimParmData parms(pas::EAnimationState::LoopAttack, CPASAnimParm::FromEnum(1),\n                                     CPASAnimParm::FromEnum(s32(x8_loopAttackType)));\n        bc.LoopBestAnimation(parms, *mgr.GetActiveRandom());\n        x4_state = pas::ELoopState::Loop;\n      } else if (!bc.GetCommandMgr().GetTargetVector().isZero()) {\n        bc.FaceDirection(bc.GetCommandMgr().GetTargetVector(), dt);\n      }\n      break;\n    case pas::ELoopState::Loop:\n      if (xc_25_advance && (!xc_24_waitForAnimOver || bc.IsAnimationOver())) {\n        if (bc.GetLocomotionType() != pas::ELocomotionType::Crouch) {\n          const CPASAnimParmData parms(pas::EAnimationState::LoopAttack, CPASAnimParm::FromEnum(2),\n                                       CPASAnimParm::FromEnum(s32(x8_loopAttackType)));\n          bc.PlayBestAnimation(parms, *mgr.GetActiveRandom());\n          x4_state = pas::ELoopState::End;\n        } else {\n          x4_state = pas::ELoopState::Invalid;\n          return pas::EAnimationState::Locomotion;\n        }\n      }\n      break;\n    case pas::ELoopState::End:\n      if (bc.IsAnimationOver()) {\n        x4_state = pas::ELoopState::Invalid;\n        return pas::EAnimationState::Locomotion;\n      }\n      break;\n    default:\n      break;\n    }\n  }\n  return st;\n}\n\nvoid CBSLoopReaction::Start(CBodyController& bc, CStateManager& mgr) {\n  if (const auto* cmd =\n          static_cast<const CBCLoopReactionCmd*>(bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopReaction))) {\n    x8_reactionType = cmd->GetReactionType();\n    xc_24_loopHit = false;\n  } else {\n    const auto* hcmd =\n        static_cast<const CBCLoopHitReactionCmd*>(bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopHitReaction));\n    x8_reactionType = hcmd->GetReactionType();\n    xc_24_loopHit = true;\n  }\n\n  x4_state = pas::ELoopState::Begin;\n  const CPASAnimParmData parms(pas::EAnimationState::LoopReaction, CPASAnimParm::FromEnum(s32(x8_reactionType)),\n                               CPASAnimParm::FromEnum(s32(x4_state)));\n  const std::pair<float, s32> best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1);\n  if (best.first > FLT_EPSILON) {\n    const CAnimPlaybackParms playParms(best.second, -1, 1.f, true);\n    bc.SetCurrentAnimation(playParms, false, false);\n  } else {\n    x4_state = pas::ELoopState::Loop;\n    const CPASAnimParmData loopParms(pas::EAnimationState::LoopReaction, CPASAnimParm::FromEnum(s32(x8_reactionType)),\n                                     CPASAnimParm::FromEnum(s32(x4_state)));\n    // Intentionally using parms instead of loopParms\n    bc.LoopBestAnimation(parms, *mgr.GetActiveRandom());\n  }\n}\n\npas::EAnimationState CBSLoopReaction::GetBodyStateTransition(float dt, const CBodyController& bc) const {\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Hurled)) {\n    return pas::EAnimationState::Hurled;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockDown)) {\n    return pas::EAnimationState::Fall;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockBack)) {\n    return pas::EAnimationState::KnockBack;\n  }\n  if (!xc_24_loopHit && bc.GetCommandMgr().GetCmd(EBodyStateCmd::Locomotion)) {\n    return pas::EAnimationState::Locomotion;\n  }\n\n  if (x4_state == pas::ELoopState::End) {\n    if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::MeleeAttack)) {\n      return pas::EAnimationState::MeleeAttack;\n    }\n    if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::ProjectileAttack)) {\n      return pas::EAnimationState::ProjectileAttack;\n    }\n    if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopAttack)) {\n      return pas::EAnimationState::LoopAttack;\n    }\n    if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Step)) {\n      return pas::EAnimationState::Step;\n    }\n    if (!bc.GetCommandMgr().GetMoveVector().isZero()) {\n      return pas::EAnimationState::Locomotion;\n    }\n    if (!bc.GetCommandMgr().GetFaceVector().isZero()) {\n      return pas::EAnimationState::Turn;\n    }\n  }\n  return pas::EAnimationState::Invalid;\n}\n\nbool CBSLoopReaction::PlayExitAnimation(CBodyController& bc, CStateManager& mgr) const {\n  const CPASAnimParmData parms(pas::EAnimationState::LoopReaction, CPASAnimParm::FromEnum(int(x8_reactionType)),\n                               CPASAnimParm::FromEnum(2));\n  const std::pair<float, s32> best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1);\n  if (best.first > 0.f) {\n    const CAnimPlaybackParms playParms(best.second, -1, 1.f, true);\n    bc.SetCurrentAnimation(playParms, false, false);\n    return true;\n  }\n  return false;\n}\n\npas::EAnimationState CBSLoopReaction::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) {\n  const pas::EAnimationState st = GetBodyStateTransition(dt, bc);\n  if (st == pas::EAnimationState::Invalid) {\n    switch (x4_state) {\n    case pas::ELoopState::Begin:\n      if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::ExitState)) {\n        if (PlayExitAnimation(bc, mgr)) {\n          x4_state = pas::ELoopState::End;\n        } else {\n          x4_state = pas::ELoopState::Invalid;\n          return pas::EAnimationState::Locomotion;\n        }\n      } else {\n        if (bc.IsAnimationOver()) {\n          const CPASAnimParmData parms(pas::EAnimationState::LoopReaction, CPASAnimParm::FromEnum(s32(x8_reactionType)),\n                                       CPASAnimParm::FromEnum(1));\n          bc.LoopBestAnimation(parms, *mgr.GetActiveRandom());\n          x4_state = pas::ELoopState::Loop;\n        } else if (!bc.GetCommandMgr().GetTargetVector().isZero()) {\n          bc.FaceDirection(bc.GetCommandMgr().GetTargetVector(), dt);\n        }\n      }\n      break;\n    case pas::ELoopState::Loop:\n      if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::ExitState)) {\n        if (PlayExitAnimation(bc, mgr)) {\n          x4_state = pas::ELoopState::End;\n        } else {\n          x4_state = pas::ELoopState::Invalid;\n          return pas::EAnimationState::Locomotion;\n        }\n      } else if (!bc.GetCommandMgr().GetTargetVector().isZero()) {\n        bc.FaceDirection(bc.GetCommandMgr().GetTargetVector(), dt);\n      }\n      break;\n    case pas::ELoopState::End:\n      if (bc.IsAnimationOver()) {\n        x4_state = pas::ELoopState::Invalid;\n        return pas::EAnimationState::Locomotion;\n      }\n      break;\n    default:\n      break;\n    }\n  }\n  return st;\n}\n\nvoid CBSGroundHit::Start(CBodyController& bc, CStateManager& mgr) {\n  const auto* cmd = static_cast<const CBCKnockBackCmd*>(bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockBack));\n  const zeus::CVector3f localDir = bc.GetOwner().GetTransform().transposeRotate(cmd->GetHitDirection());\n  zeus::CRelAngle angle = std::atan2(localDir.y(), localDir.x());\n  angle.makeRel();\n  const CPASAnimParmData parms(pas::EAnimationState::GroundHit, CPASAnimParm::FromEnum(s32(bc.GetFallState())),\n                               CPASAnimParm::FromReal32(angle.asDegrees()));\n  const std::pair<float, s32> best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1);\n  const CAnimPlaybackParms playParms(best.second, -1, 1.f, true);\n  bc.SetCurrentAnimation(playParms, false, false);\n  const CPASAnimState* groundHitState = bc.GetPASDatabase().GetAnimState(pas::EAnimationState::GroundHit);\n  if (!groundHitState->GetAnimParmData(best.second, 2).GetBoolValue()) {\n    const float animAngle = zeus::degToRad(groundHitState->GetAnimParmData(best.second, 1).GetReal32Value());\n    zeus::CRelAngle delta1 = angle - animAngle;\n    delta1.makeRel();\n    zeus::CRelAngle delta2 = animAngle - angle;\n    delta2.makeRel();\n    const float minAngle = std::min(float(delta1), float(delta2));\n    const float flippedAngle = (delta1 > M_PIF) ? -minAngle : minAngle;\n    x8_remTime = 0.15f * bc.GetAnimTimeRemaining();\n    x4_rotateSpeed = (x8_remTime > FLT_EPSILON) ? flippedAngle / x8_remTime : flippedAngle;\n  } else {\n    x8_remTime = 0.f;\n    x4_rotateSpeed = 0.f;\n  }\n  xc_fallState = pas::EFallState(groundHitState->GetAnimParmData(best.second, 3).GetEnumValue());\n}\n\npas::EAnimationState CBSGroundHit::GetBodyStateTransition(float dt, const CBodyController& bc) const {\n  if (!bc.IsAnimationOver()) {\n    return pas::EAnimationState::Invalid;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Die)) {\n    return pas::EAnimationState::Death;\n  }\n  return pas::EAnimationState::LieOnGround;\n}\n\npas::EAnimationState CBSGroundHit::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) {\n  const pas::EAnimationState st = GetBodyStateTransition(dt, bc);\n  if (st == pas::EAnimationState::Invalid && x8_remTime > 0.f) {\n    zeus::CQuaternion quat;\n    quat.rotateZ(x4_rotateSpeed * dt);\n    bc.SetDeltaRotation(quat);\n    x8_remTime -= dt;\n  }\n  return st;\n}\n\nvoid CBSGroundHit::Shutdown(CBodyController& bc) { bc.SetFallState(xc_fallState); }\n\nvoid CBSGenerate::Start(CBodyController& bc, CStateManager& mgr) {\n  const auto* cmd = static_cast<const CBCGenerateCmd*>(bc.GetCommandMgr().GetCmd(EBodyStateCmd::Generate));\n  s32 anim;\n  if (!cmd->UseSpecialAnimId()) {\n    const CPASAnimParmData parms(pas::EAnimationState::Generate, CPASAnimParm::FromEnum(s32(cmd->GetGenerateType())));\n    const std::pair<float, s32> best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1);\n    anim = best.second;\n  } else {\n    anim = cmd->GetSpecialAnimId();\n  }\n\n  if (cmd->HasExitTargetPos()) {\n    const CAnimPlaybackParms playParms(anim, nullptr, &cmd->GetExitTargetPos(), &bc.GetOwner().GetTransform(),\n                                       &bc.GetOwner().GetModelData()->GetScale(), false);\n    bc.SetCurrentAnimation(playParms, false, false);\n  } else {\n    const CAnimPlaybackParms playParms(anim, -1, 1.f, true);\n    bc.SetCurrentAnimation(playParms, false, false);\n  }\n}\n\npas::EAnimationState CBSGenerate::GetBodyStateTransition(float dt, const CBodyController& bc) const {\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Hurled)) {\n    return pas::EAnimationState::Hurled;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockDown)) {\n    return pas::EAnimationState::Fall;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Generate)) {\n    return pas::EAnimationState::Generate;\n  }\n  if (bc.IsAnimationOver() || bc.GetCommandMgr().GetCmd(EBodyStateCmd::NextState)) {\n    return pas::EAnimationState::Locomotion;\n  }\n  return pas::EAnimationState::Invalid;\n}\n\npas::EAnimationState CBSGenerate::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) {\n  const pas::EAnimationState st = GetBodyStateTransition(dt, bc);\n  if (st == pas::EAnimationState::Invalid) {\n    if (!bc.GetCommandMgr().GetTargetVector().isZero()) {\n      bc.FaceDirection(bc.GetCommandMgr().GetTargetVector(), dt);\n    }\n  }\n  return st;\n}\n\nvoid CBSJump::Start(CBodyController& bc, CStateManager& mgr) {\n  const auto* cmd = static_cast<const CBCJumpCmd*>(bc.GetCommandMgr().GetCmd(EBodyStateCmd::Jump));\n  x8_jumpType = cmd->GetJumpType();\n  xc_waypoint1 = cmd->GetJumpTarget();\n  x24_waypoint2 = cmd->GetSecondJumpTarget();\n  x30_25_wallJump = cmd->IsWallJump();\n  x30_28_startInJumpLoop = cmd->StartInJumpLoop();\n  x30_24_bodyForceSet = false;\n  x30_27_wallBounceComplete = false;\n  if (x30_25_wallJump) {\n    x30_26_wallBounceRight =\n        (xc_waypoint1 - bc.GetOwner().GetTranslation()).cross(zeus::skUp).dot(x24_waypoint2 - xc_waypoint1) < 0.f;\n  }\n  if (!cmd->StartInJumpLoop()) {\n    x4_state = pas::EJumpState::IntoJump;\n    const CPASAnimParmData parms(pas::EAnimationState::Jump, CPASAnimParm::FromEnum(s32(x4_state)),\n                                 CPASAnimParm::FromEnum(s32(x8_jumpType)));\n    bc.PlayBestAnimation(parms, *mgr.GetActiveRandom());\n  } else {\n    PlayJumpLoop(mgr, bc);\n  }\n}\n\npas::EAnimationState CBSJump::GetBodyStateTransition(float dt, const CBodyController& bc) const {\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Hurled)) {\n    return pas::EAnimationState::Hurled;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockDown)) {\n    return pas::EAnimationState::Fall;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Jump) && bc.GetBodyType() == EBodyType::WallWalker) {\n    return pas::EAnimationState::Jump;\n  }\n  return pas::EAnimationState::Invalid;\n}\n\nbool CBSJump::CheckForWallJump(CBodyController& bc, CStateManager& mgr) {\n  if (!x30_25_wallJump || x30_27_wallBounceComplete) {\n    return false;\n  }\n\n  if (const TCastToPtr<CPatterned> act = bc.GetOwner()) {\n    const float distToWall = (xc_waypoint1 - act->GetTranslation()).magnitude();\n    const zeus::CAABox aabb = act->GetBoundingBox();\n    const float xExtent = (aabb.max.x() - aabb.min.x()) * 0.5f;\n    if (distToWall < 1.414f * xExtent || (act->MadeSolidCollision() && distToWall < 3.f * xExtent)) {\n      x4_state = x30_26_wallBounceRight ? pas::EJumpState::WallBounceRight : pas::EJumpState::WallBounceLeft;\n      const CPASAnimParmData parms(pas::EAnimationState::Jump, CPASAnimParm::FromEnum(s32(x4_state)),\n                                   CPASAnimParm::FromEnum(s32(x8_jumpType)));\n      bc.PlayBestAnimation(parms, *mgr.GetActiveRandom());\n      mgr.SendScriptMsg(act.GetPtr(), kInvalidUniqueId, EScriptObjectMessage::OnFloor);\n      return true;\n    }\n  }\n\n  return false;\n}\n\nvoid CBSJump::CheckForLand(CBodyController& bc, CStateManager& mgr) {\n  if (const TCastToPtr<CPatterned> act = bc.GetOwner()) {\n    if (act->MadeSolidCollision() || act->IsOnGround()) {\n      x4_state = pas::EJumpState::OutOfJump;\n      const CPASAnimParmData parms(pas::EAnimationState::Jump, CPASAnimParm::FromEnum(s32(x4_state)),\n                                   CPASAnimParm::FromEnum(s32(x8_jumpType)));\n      bc.PlayBestAnimation(parms, *mgr.GetActiveRandom());\n      mgr.SendScriptMsg(act.GetPtr(), kInvalidUniqueId, EScriptObjectMessage::OnFloor);\n    }\n  }\n}\n\nvoid CBSJump::PlayJumpLoop(CStateManager& mgr, CBodyController& bc) {\n  const CPASAnimParmData parms(pas::EAnimationState::Jump, CPASAnimParm::FromEnum(1),\n                               CPASAnimParm::FromEnum(s32(x8_jumpType)));\n  const std::pair<float, s32> best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1);\n  if (best.first > 99.f) {\n    x4_state = pas::EJumpState::AmbushJump;\n    const CAnimPlaybackParms playParms(best.second, -1, 1.f, true);\n    bc.SetCurrentAnimation(playParms, false, false);\n  } else {\n    x4_state = pas::EJumpState::Loop;\n    const CPASAnimParmData loopParms(pas::EAnimationState::Jump, CPASAnimParm::FromEnum(s32(x4_state)),\n                                     CPASAnimParm::FromEnum(s32(x8_jumpType)));\n    bc.LoopBestAnimation(loopParms, *mgr.GetActiveRandom());\n  }\n\n  if (const TCastToPtr<CPhysicsActor> act = bc.GetOwner()) {\n    mgr.SendScriptMsg(act.GetPtr(), kInvalidUniqueId, EScriptObjectMessage::Falling);\n    mgr.SendScriptMsg(act.GetPtr(), kInvalidUniqueId, EScriptObjectMessage::Jumped);\n    x30_24_bodyForceSet = false;\n    x18_velocity = act->GetVelocity();\n  }\n}\n\npas::EAnimationState CBSJump::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) {\n  const pas::EAnimationState st = GetBodyStateTransition(dt, bc);\n  if (st == pas::EAnimationState::Invalid) {\n    switch (x4_state) {\n    case pas::EJumpState::IntoJump:\n      if (bc.IsAnimationOver()) {\n        PlayJumpLoop(mgr, bc);\n      }\n      break;\n    case pas::EJumpState::AmbushJump:\n      if (!x30_24_bodyForceSet) {\n        if (const TCastToPtr<CPhysicsActor> act = bc.GetOwner()) {\n          act->SetConstantForce(act->GetMass() * x18_velocity);\n        }\n        x30_24_bodyForceSet = true;\n      }\n      if (!bc.GetCommandMgr().GetTargetVector().isZero()) {\n        bc.FaceDirection(bc.GetCommandMgr().GetTargetVector(), dt);\n      }\n      if (bc.IsAnimationOver()) {\n        x4_state = pas::EJumpState::Loop;\n        const CPASAnimParmData parms(pas::EAnimationState::Jump, CPASAnimParm::FromEnum(s32(x4_state)),\n                                     CPASAnimParm::FromEnum(s32(x8_jumpType)));\n        bc.LoopBestAnimation(parms, *mgr.GetActiveRandom());\n      } else {\n        if (!CheckForWallJump(bc, mgr)) {\n          CheckForLand(bc, mgr);\n        }\n      }\n      break;\n    case pas::EJumpState::Loop:\n      if (!x30_24_bodyForceSet) {\n        if (const TCastToPtr<CPhysicsActor> act = bc.GetOwner()) {\n          act->SetConstantForce(act->GetMass() * x18_velocity);\n        }\n        x30_24_bodyForceSet = true;\n      }\n      if (!bc.GetCommandMgr().GetTargetVector().isZero()) {\n        bc.FaceDirection(bc.GetCommandMgr().GetTargetVector(), dt);\n      }\n      if (!CheckForWallJump(bc, mgr)) {\n        CheckForLand(bc, mgr);\n      }\n      break;\n    case pas::EJumpState::WallBounceLeft:\n    case pas::EJumpState::WallBounceRight:\n      if (const TCastToPtr<CPhysicsActor> act = bc.GetOwner()) {\n        act->Stop();\n        act->SetMomentumWR(zeus::skZero3f);\n      }\n      if (bc.IsAnimationOver()) {\n        mgr.SendScriptMsg(&bc.GetOwner(), kInvalidUniqueId, EScriptObjectMessage::Falling);\n        x4_state = pas::EJumpState::Loop;\n        const CPASAnimParmData parms(pas::EAnimationState::Jump, CPASAnimParm::FromEnum(s32(x4_state)),\n                                     CPASAnimParm::FromEnum(s32(x8_jumpType)));\n        bc.LoopBestAnimation(parms, *mgr.GetActiveRandom());\n        x30_27_wallBounceComplete = true;\n        if (const TCastToPtr<CPatterned> act = bc.GetOwner()) {\n          const zeus::CVector3f toFinal = x24_waypoint2 - act->GetTranslation();\n          const float tmp = std::sqrt(act->GetGravityConstant() / (2.f * toFinal.z()));\n          act->SetVelocityWR(zeus::CVector3f(tmp * toFinal.x(), tmp * toFinal.y(), 0.f));\n        }\n      }\n      break;\n    case pas::EJumpState::OutOfJump:\n      if (bc.IsAnimationOver()) {\n        x4_state = pas::EJumpState::Invalid;\n        return pas::EAnimationState::Locomotion;\n      }\n      break;\n    default:\n      break;\n    }\n  }\n  return st;\n}\n\nbool CBSJump::ApplyAnimationDeltas() const {\n  return !(x4_state == pas::EJumpState::AmbushJump || x4_state == pas::EJumpState::Loop);\n}\n\nbool CBSJump::IsInAir(const CBodyController& bc) const {\n  return x4_state == pas::EJumpState::AmbushJump || x4_state == pas::EJumpState::Loop;\n}\n\nbool CBSJump::CanShoot() const { return x4_state == pas::EJumpState::AmbushJump || x4_state == pas::EJumpState::Loop; }\n\nvoid CBSHurled::Start(CBodyController& bc, CStateManager& mgr) {\n  const auto* cmd = static_cast<const CBCHurledCmd*>(bc.GetCommandMgr().GetCmd(EBodyStateCmd::Hurled));\n  x4_state = pas::EHurledState(cmd->GetSkipLaunchState());\n  const zeus::CVector3f localDir = bc.GetOwner().GetTransform().transposeRotate(cmd->GetHitDirection());\n  zeus::CRelAngle angle = std::atan2(localDir.y(), localDir.x());\n  angle.makeRel();\n  x8_knockAngle = angle.asDegrees();\n  const CPASAnimParmData parms(pas::EAnimationState::Hurled, CPASAnimParm::FromInt32(-1),\n                               CPASAnimParm::FromReal32(x8_knockAngle), CPASAnimParm::FromEnum(s32(x4_state)));\n  const std::pair<float, s32> best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1);\n  const CAnimPlaybackParms playParms(best.second, -1, 1.f, true);\n  bc.SetCurrentAnimation(playParms, false, false);\n  const CPASAnimState* hurledState = bc.GetPASDatabase().GetAnimState(pas::EAnimationState::Hurled);\n  xc_animSeries = hurledState->GetAnimParmData(best.second, 0).GetInt32Value();\n  mgr.SendScriptMsg(&bc.GetOwner(), kInvalidUniqueId, EScriptObjectMessage::Falling);\n  mgr.SendScriptMsg(&bc.GetOwner(), kInvalidUniqueId, EScriptObjectMessage::Jumped);\n  if (!zeus::close_enough(cmd->GetLaunchVelocity(), zeus::skZero3f, 0.0001f)) {\n    if (const TCastToPtr<CPhysicsActor> act = bc.GetOwner()) {\n      act->SetConstantForce(act->GetMass() * cmd->GetLaunchVelocity());\n    }\n  }\n  const float animAngle = zeus::degToRad(hurledState->GetAnimParmData(best.second, 1).GetReal32Value());\n  zeus::CRelAngle delta1 = angle - animAngle;\n  delta1.makeRel();\n  zeus::CRelAngle delta2 = animAngle - angle;\n  delta2.makeRel();\n  const float minAngle = std::min(float(delta1), float(delta2));\n  x14_remTime = 0.15f * bc.GetAnimTimeRemaining();\n  const float flippedAngle = (delta1 > M_PIF) ? -minAngle : minAngle;\n  x10_rotateSpeed = (x14_remTime > FLT_EPSILON) ? flippedAngle / x14_remTime : flippedAngle;\n  x18_curTime = 0.f;\n  x2c_24_needsRecover = false;\n}\n\npas::EAnimationState CBSHurled::GetBodyStateTransition(float dt, CBodyController& bc) const {\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::NextState)) {\n    return pas::EAnimationState::LieOnGround;\n  }\n\n  if (x18_curTime > 0.25f) {\n    if (auto* cmd = static_cast<CBCHurledCmd*>(bc.GetCommandMgr().GetCmd(EBodyStateCmd::Hurled))) {\n      cmd->SetSkipLaunchState(true);\n      return pas::EAnimationState::Hurled;\n    }\n  }\n\n  return pas::EAnimationState::Invalid;\n}\n\nvoid CBSHurled::Recover(CStateManager& mgr, CBodyController& bc, pas::EHurledState state) {\n  const CPASAnimParmData parms(pas::EAnimationState::Hurled, CPASAnimParm::FromInt32(xc_animSeries),\n                               CPASAnimParm::FromReal32(x8_knockAngle), CPASAnimParm::FromEnum(s32(state)));\n  const std::pair<float, s32> best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1);\n  if (best.first > FLT_EPSILON) {\n    const CAnimPlaybackParms playParms(best.second, -1, 1.f, true);\n    bc.SetCurrentAnimation(playParms, false, false);\n    x4_state = state;\n    if (const TCastToPtr<CPhysicsActor> act = bc.GetOwner()) {\n      act->SetMomentumWR(zeus::skZero3f);\n    }\n  }\n  x2c_24_needsRecover = false;\n}\n\nvoid CBSHurled::PlayStrikeWallAnimation(CBodyController& bc, CStateManager& mgr) {\n  const CPASAnimParmData parms(pas::EAnimationState::Hurled, CPASAnimParm::FromInt32(xc_animSeries),\n                               CPASAnimParm::FromReal32(x8_knockAngle), CPASAnimParm::FromEnum(3));\n  const std::pair<float, s32> best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1);\n\n  if (best.first <= FLT_EPSILON) {\n    return;\n  }\n\n  const CAnimPlaybackParms playParms(best.second, -1, 1.f, true);\n  bc.SetCurrentAnimation(playParms, false, false);\n  x4_state = pas::EHurledState::StrikeWall;\n}\n\nvoid CBSHurled::PlayLandAnimation(CBodyController& bc, CStateManager& mgr) {\n  const CPASAnimParmData parms(pas::EAnimationState::Hurled, CPASAnimParm::FromInt32(xc_animSeries),\n                               CPASAnimParm::FromReal32(x8_knockAngle), CPASAnimParm::FromEnum(s32(x4_state)));\n  const std::pair<float, s32> best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1);\n  const CAnimPlaybackParms playParms(best.second, -1, 1.f, true);\n  bc.SetCurrentAnimation(playParms, false, false);\n  const CPASAnimState* hurledState = bc.GetPASDatabase().GetAnimState(pas::EAnimationState::Hurled);\n  bc.SetFallState(pas::EFallState(hurledState->GetAnimParmData(best.second, 3).GetEnumValue()));\n  if (const TCastToPtr<CPhysicsActor> act = bc.GetOwner()) {\n    mgr.SendScriptMsg(act.GetPtr(), kInvalidUniqueId, EScriptObjectMessage::OnFloor);\n  }\n}\n\nbool CBSHurled::ShouldStartStrikeWall(const CBodyController& bc) const {\n  if (const TCastToConstPtr<CPatterned> ai = bc.GetOwner()) {\n    if (ai->MadeSolidCollision()) {\n      if (!ai->IsOnGround()) {\n        return true;\n      }\n    }\n  }\n  return false;\n}\n\nbool CBSHurled::ShouldStartLand(float dt, const CBodyController& bc) const {\n  bool ret = true;\n  if (const TCastToConstPtr<CPatterned> ai = bc.GetOwner()) {\n    ret = false;\n    if (ai->IsOnGround()) {\n      return true;\n    }\n    if (zeus::close_enough(ai->GetTranslation(), x1c_lastTranslation, 0.0001f) && ai->GetVelocity().z() < 0.f) {\n      x28_landedDur += dt;\n      if (x28_landedDur >= 0.25f) {\n        ret = true;\n      }\n    } else {\n      x28_landedDur = 0.f;\n    }\n    x1c_lastTranslation = ai->GetTranslation();\n  }\n  return ret;\n}\n\npas::EAnimationState CBSHurled::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) {\n  const pas::EAnimationState st = GetBodyStateTransition(dt, bc);\n  if (st == pas::EAnimationState::Invalid) {\n    x18_curTime += dt;\n    if (x14_remTime > 0.f) {\n      zeus::CQuaternion quat;\n      quat.rotateZ(x10_rotateSpeed * dt);\n      bc.SetDeltaRotation(quat);\n      x14_remTime -= dt;\n    }\n    if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::ExitState)) {\n      x2c_24_needsRecover = true;\n    }\n    switch (x4_state) {\n    case pas::EHurledState::KnockIntoAir: {\n      if (bc.IsAnimationOver()) {\n        const CPASAnimParmData parms(pas::EAnimationState::Hurled, CPASAnimParm::FromInt32(xc_animSeries),\n                                     CPASAnimParm::FromReal32(x8_knockAngle), CPASAnimParm::FromEnum(1));\n        bc.LoopBestAnimation(parms, *mgr.GetActiveRandom());\n        x4_state = pas::EHurledState::KnockLoop;\n        x28_landedDur = 0.f;\n      }\n      break;\n    }\n    case pas::EHurledState::KnockLoop:\n      if (ShouldStartLand(dt, bc)) {\n        x4_state = pas::EHurledState::KnockDown;\n        PlayLandAnimation(bc, mgr);\n      } else if (ShouldStartStrikeWall(bc)) {\n        PlayStrikeWallAnimation(bc, mgr);\n        if (const TCastToPtr<CPatterned> ai = bc.GetOwner()) {\n          ai->SetVelocityWR(zeus::skDown * (2.f * dt * ai->GetGravityConstant()));\n        }\n      } else if (x2c_24_needsRecover) {\n        Recover(mgr, bc, pas::EHurledState::Six);\n      }\n      break;\n    case pas::EHurledState::StrikeWall:\n      if (bc.IsAnimationOver()) {\n        x4_state = pas::EHurledState::StrikeWallFallLoop;\n        const CPASAnimParmData parms(pas::EAnimationState::Hurled, CPASAnimParm::FromInt32(xc_animSeries),\n                                     CPASAnimParm::FromReal32(x8_knockAngle), CPASAnimParm::FromEnum(s32(x4_state)));\n        bc.LoopBestAnimation(parms, *mgr.GetActiveRandom());\n        x28_landedDur = 0.f;\n      }\n      break;\n    case pas::EHurledState::StrikeWallFallLoop:\n      if (ShouldStartLand(dt, bc)) {\n        x4_state = pas::EHurledState::OutOfStrikeWall;\n        PlayLandAnimation(bc, mgr);\n      } else if (x2c_24_needsRecover) {\n        Recover(mgr, bc, pas::EHurledState::Seven);\n      }\n      break;\n    case pas::EHurledState::Six:\n    case pas::EHurledState::Seven:\n      if (const TCastToPtr<CPhysicsActor> act = bc.GetOwner()) {\n        act->SetVelocityWR(act->GetVelocity() * std::pow(0.9, 60.0 * dt));\n      }\n      if (bc.IsAnimationOver()) {\n        x4_state = pas::EHurledState::Invalid;\n        return pas::EAnimationState::Locomotion;\n      }\n      break;\n    case pas::EHurledState::KnockDown:\n    case pas::EHurledState::OutOfStrikeWall:\n      if (bc.IsAnimationOver()) {\n        x4_state = pas::EHurledState::Invalid;\n        if (bc.GetFallState() == pas::EFallState::Zero) {\n          return pas::EAnimationState::Locomotion;\n        }\n        return pas::EAnimationState::LieOnGround;\n      }\n      break;\n    default:\n      break;\n    }\n  }\n  return st;\n}\n\nvoid CBSSlide::Start(CBodyController& bc, CStateManager& mgr) {\n  const auto* cmd = static_cast<const CBCSlideCmd*>(bc.GetCommandMgr().GetCmd(EBodyStateCmd::Slide));\n  const zeus::CVector3f localDir = bc.GetOwner().GetTransform().transposeRotate(cmd->GetSlideDirection());\n  const float angle = std::atan2(localDir.y(), localDir.x());\n  const CPASAnimParmData parms(pas::EAnimationState::Slide, CPASAnimParm::FromEnum(s32(cmd->GetSlideType())),\n                               CPASAnimParm::FromReal32(zeus::radToDeg(angle)));\n  const std::pair<float, s32> best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1);\n  const CAnimPlaybackParms playParms(best.second, -1, 1.f, true);\n  bc.SetCurrentAnimation(playParms, false, false);\n  const float timeRem = bc.GetAnimTimeRemaining();\n  if (timeRem > FLT_EPSILON) {\n    const CPASAnimState* slideState = bc.GetPASDatabase().GetAnimState(pas::EAnimationState::Slide);\n    const float animAngle = zeus::degToRad(slideState->GetAnimParmData(best.second, 1).GetReal32Value());\n    const float delta1 = zeus::CRelAngle(angle - animAngle).asRel();\n    const float flippedAngle = (delta1 > M_PIF) ? delta1 - 2.f * M_PIF : delta1;\n    x4_rotateSpeed = flippedAngle / timeRem;\n  } else {\n    x4_rotateSpeed = 0.f;\n  }\n}\n\npas::EAnimationState CBSSlide::GetBodyStateTransition(float dt, const CBodyController& bc) const {\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Hurled)) {\n    return pas::EAnimationState::Hurled;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockDown)) {\n    return pas::EAnimationState::Fall;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopHitReaction)) {\n    return pas::EAnimationState::LoopReaction;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockBack)) {\n    return pas::EAnimationState::KnockBack;\n  }\n  if (bc.IsAnimationOver()) {\n    return pas::EAnimationState::Locomotion;\n  }\n  return pas::EAnimationState::Invalid;\n}\n\npas::EAnimationState CBSSlide::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) {\n  const pas::EAnimationState st = GetBodyStateTransition(dt, bc);\n  if (st == pas::EAnimationState::Invalid && x4_rotateSpeed != 0.f) {\n    zeus::CQuaternion quat;\n    quat.rotateZ(x4_rotateSpeed * dt);\n    bc.SetDeltaRotation(quat);\n  }\n  return st;\n}\n\nvoid CBSTaunt::Start(CBodyController& bc, CStateManager& mgr) {\n  const auto* cmd = static_cast<const CBCTauntCmd*>(bc.GetCommandMgr().GetCmd(EBodyStateCmd::Taunt));\n  const CPASAnimParmData parms(pas::EAnimationState::Taunt, CPASAnimParm::FromEnum(s32(cmd->GetTauntType())));\n  bc.PlayBestAnimation(parms, *mgr.GetActiveRandom());\n}\n\npas::EAnimationState CBSTaunt::GetBodyStateTransition(float dt, const CBodyController& bc) const {\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Hurled)) {\n    return pas::EAnimationState::Hurled;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockDown)) {\n    return pas::EAnimationState::Fall;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopHitReaction)) {\n    return pas::EAnimationState::LoopReaction;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockBack)) {\n    return pas::EAnimationState::KnockBack;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Locomotion)) {\n    return pas::EAnimationState::Locomotion;\n  }\n  if (bc.IsAnimationOver()) {\n    return pas::EAnimationState::Locomotion;\n  }\n  return pas::EAnimationState::Invalid;\n}\n\npas::EAnimationState CBSTaunt::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) {\n  const pas::EAnimationState st = GetBodyStateTransition(dt, bc);\n  if (st == pas::EAnimationState::Invalid && !bc.GetCommandMgr().GetTargetVector().isZero()) {\n    bc.FaceDirection(bc.GetCommandMgr().GetTargetVector(), dt);\n  }\n  return st;\n}\n\nvoid CBSScripted::Start(CBodyController& bc, CStateManager& mgr) {\n  const auto* cmd = static_cast<const CBCScriptedCmd*>(bc.GetCommandMgr().GetCmd(EBodyStateCmd::Scripted));\n  x4_24_loopAnim = cmd->IsLooped();\n  x4_25_timedLoop = cmd->GetUseLoopDuration();\n  x8_remTime = cmd->GetLoopDuration();\n  const CAnimPlaybackParms playParms(cmd->GetAnimId(), -1, 1.f, true);\n  bc.SetCurrentAnimation(playParms, cmd->IsLooped(), false);\n}\n\npas::EAnimationState CBSScripted::GetBodyStateTransition(float dt, const CBodyController& bc) const {\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Hurled)) {\n    return pas::EAnimationState::Hurled;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockDown)) {\n    return pas::EAnimationState::Fall;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopHitReaction)) {\n    return pas::EAnimationState::LoopReaction;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockBack)) {\n    return pas::EAnimationState::KnockBack;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Scripted)) {\n    return pas::EAnimationState::Scripted;\n  }\n  if (x4_24_loopAnim) {\n    if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::ExitState)) {\n      return pas::EAnimationState::Locomotion;\n    }\n  } else if (bc.IsAnimationOver()) {\n    return pas::EAnimationState::Locomotion;\n  }\n  return pas::EAnimationState::Invalid;\n}\n\npas::EAnimationState CBSScripted::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) {\n  const pas::EAnimationState st = GetBodyStateTransition(dt, bc);\n  if (st == pas::EAnimationState::Invalid) {\n    if (!bc.GetCommandMgr().GetTargetVector().isZero()) {\n      bc.FaceDirection(bc.GetCommandMgr().GetTargetVector(), dt);\n    }\n    if (x4_24_loopAnim && x4_25_timedLoop) {\n      x8_remTime -= dt;\n      if (x8_remTime <= 0.f) {\n        return pas::EAnimationState::Locomotion;\n      }\n    }\n  }\n  return st;\n}\n\nvoid CBSCover::Start(CBodyController& bc, CStateManager& mgr) {\n  const auto* cmd = static_cast<const CBCCoverCmd*>(bc.GetCommandMgr().GetCmd(EBodyStateCmd::Cover));\n  x8_coverDirection = cmd->GetDirection();\n  x4_state = pas::ECoverState::IntoCover;\n  const CPASAnimParmData parms(pas::EAnimationState::Cover, CPASAnimParm::FromEnum(s32(x4_state)),\n                               CPASAnimParm::FromEnum(s32(x8_coverDirection)));\n  const std::pair<float, s32> best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1);\n  const zeus::CQuaternion orientDelta =\n      zeus::CQuaternion::lookAt(zeus::skForward, cmd->GetAlignDirection(), 2.f * M_PIF);\n  const CAnimPlaybackParms playParms(best.second, &orientDelta, &cmd->GetTarget(), &bc.GetOwner().GetTransform(),\n                                     &bc.GetOwner().GetModelData()->GetScale(), false);\n  bc.SetCurrentAnimation(playParms, false, false);\n  xc_needsExit = false;\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::ExitState)) {\n    xc_needsExit = true;\n  }\n}\n\npas::EAnimationState CBSCover::GetBodyStateTransition(float dt, const CBodyController& bc) const {\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Hurled)) {\n    return pas::EAnimationState::Hurled;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockDown)) {\n    return pas::EAnimationState::Fall;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopHitReaction)) {\n    return pas::EAnimationState::LoopReaction;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockBack)) {\n    return pas::EAnimationState::KnockBack;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Locomotion)) {\n    return pas::EAnimationState::Locomotion;\n  }\n  return pas::EAnimationState::Invalid;\n}\n\npas::EAnimationState CBSCover::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) {\n  const pas::EAnimationState st = GetBodyStateTransition(dt, bc);\n  if (st == pas::EAnimationState::Invalid) {\n    switch (x4_state) {\n    case pas::ECoverState::Lean:\n    case pas::ECoverState::IntoCover:\n      if (bc.IsAnimationOver()) {\n        x4_state = pas::ECoverState::Cover;\n        const CPASAnimParmData parms(pas::EAnimationState::Cover, CPASAnimParm::FromEnum(s32(x4_state)),\n                                     CPASAnimParm::FromEnum(s32(x8_coverDirection)));\n        bc.LoopBestAnimation(parms, *mgr.GetActiveRandom());\n      }\n      if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::ExitState)) {\n        xc_needsExit = true;\n      }\n      break;\n    case pas::ECoverState::Cover:\n      if (!bc.GetCommandMgr().GetTargetVector().isZero()) {\n        bc.FaceDirection(bc.GetCommandMgr().GetTargetVector(), dt);\n      }\n      if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::ExitState) || xc_needsExit) {\n        xc_needsExit = false;\n        x4_state = pas::ECoverState::OutOfCover;\n        const CPASAnimParmData parms(pas::EAnimationState::Cover, CPASAnimParm::FromEnum(s32(x4_state)),\n                                     CPASAnimParm::FromEnum(s32(x8_coverDirection)));\n        bc.PlayBestAnimation(parms, *mgr.GetActiveRandom());\n      } else if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LeanFromCover)) {\n        x4_state = pas::ECoverState::Lean;\n        const CPASAnimParmData parms(pas::EAnimationState::Cover, CPASAnimParm::FromEnum(s32(x4_state)),\n                                     CPASAnimParm::FromEnum(s32(x8_coverDirection)));\n        bc.PlayBestAnimation(parms, *mgr.GetActiveRandom());\n      }\n      break;\n    case pas::ECoverState::OutOfCover:\n      if (bc.IsAnimationOver()) {\n        x4_state = pas::ECoverState::Invalid;\n        return pas::EAnimationState::Locomotion;\n      }\n      break;\n    default:\n      break;\n    }\n  }\n  return st;\n}\n\nvoid CBSWallHang::Start(CBodyController& bc, CStateManager& mgr) {\n  const auto* cmd = static_cast<const CBCWallHangCmd*>(bc.GetCommandMgr().GetCmd(EBodyStateCmd::WallHang));\n  x4_state = pas::EWallHangState::IntoJump;\n  x8_wpId = cmd->GetTarget();\n  x18_25_needsExit = false;\n  const CPASAnimParmData parms(pas::EAnimationState::WallHang, CPASAnimParm::FromEnum(s32(x4_state)));\n  bc.PlayBestAnimation(parms, *mgr.GetActiveRandom());\n}\n\npas::EAnimationState CBSWallHang::GetBodyStateTransition(float dt, const CBodyController& bc) const {\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Hurled)) {\n    return pas::EAnimationState::Hurled;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockDown)) {\n    return pas::EAnimationState::Fall;\n  }\n  return pas::EAnimationState::Invalid;\n}\n\nvoid CBSWallHang::FixInPlace(CBodyController& bc) {\n  if (const TCastToPtr<CPatterned> ai = bc.GetOwner()) {\n    ai->SetConstantForce(zeus::skZero3f);\n    ai->SetVelocityWR(zeus::skZero3f);\n  }\n}\n\nbool CBSWallHang::CheckForLand(CBodyController& bc, CStateManager& mgr) {\n  if (const TCastToPtr<CPatterned> ai = bc.GetOwner()) {\n    if (ai->MadeSolidCollision() || ai->IsOnGround()) {\n      x4_state = pas::EWallHangState::DetachOutOfJump;\n      const CPASAnimParmData parms(pas::EAnimationState::WallHang, CPASAnimParm::FromEnum(s32(x4_state)));\n      bc.PlayBestAnimation(parms, *mgr.GetActiveRandom());\n      mgr.SendScriptMsg(ai.GetPtr(), kInvalidUniqueId, EScriptObjectMessage::OnFloor);\n      return true;\n    }\n  }\n  return false;\n}\n\nbool CBSWallHang::CheckForWall(CBodyController& bc, CStateManager& mgr) {\n  if (const TCastToPtr<CPatterned> ai = bc.GetOwner()) {\n    float magSq = 10.f;\n    const TCastToConstPtr<CActor> wp = mgr.ObjectById(x8_wpId);\n    if (wp) {\n      magSq = (wp->GetTranslation() - ai->GetTranslation()).magSquared();\n    }\n\n    if (magSq < 1.f || ai->MadeSolidCollision()) {\n      x4_state = pas::EWallHangState::IntoWallHang;\n      const CPASAnimParmData parms(pas::EAnimationState::WallHang, CPASAnimParm::FromEnum(s32(x4_state)));\n      const std::pair<float, s32> best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1);\n      const zeus::CVector3f& target = wp ? wp->GetTranslation() : ai->GetTranslation();\n      const CAnimPlaybackParms playParms(best.second, nullptr, &target, &bc.GetOwner().GetTransform(),\n                                         &bc.GetOwner().GetModelData()->GetScale(), false);\n      bc.SetCurrentAnimation(playParms, false, false);\n      ai->SetVelocityWR(zeus::skZero3f);\n      ai->SetMomentumWR(zeus::skZero3f);\n      mgr.SendScriptMsg(ai.GetPtr(), kInvalidUniqueId, EScriptObjectMessage::OnFloor);\n      return true;\n    }\n  }\n  return false;\n}\n\nvoid CBSWallHang::SetLaunchVelocity(CBodyController& bc) {\n  if (x18_24_launched) {\n    return;\n  }\n\n  if (const TCastToPtr<CPhysicsActor> act = bc.GetOwner()) {\n    act->SetVelocityWR(xc_launchVel);\n    act->SetConstantForce(xc_launchVel * act->GetMass());\n  }\n\n  x18_24_launched = true;\n}\n\npas::EAnimationState CBSWallHang::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) {\n  const pas::EAnimationState st = GetBodyStateTransition(dt, bc);\n  if (st == pas::EAnimationState::Invalid) {\n    switch (x4_state) {\n    case pas::EWallHangState::IntoJump: {\n      const CPASAnimParmData parms(pas::EAnimationState::WallHang, CPASAnimParm::FromEnum(1));\n      const std::pair<float, s32> best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1);\n      if (best.first > 0.f) {\n        x4_state = pas::EWallHangState::JumpArc;\n        const CAnimPlaybackParms playParms(best.second, -1, 1.f, true);\n        bc.SetCurrentAnimation(playParms, false, false);\n      } else {\n        x4_state = pas::EWallHangState::JumpAirLoop;\n        const CPASAnimParmData loopParms(pas::EAnimationState::WallHang, CPASAnimParm::FromEnum(s32(x4_state)));\n        bc.LoopBestAnimation(loopParms, *mgr.GetActiveRandom());\n      }\n      if (const TCastToPtr<CPhysicsActor> act = bc.GetOwner()) {\n        mgr.SendScriptMsg(act.GetPtr(), kInvalidUniqueId, EScriptObjectMessage::Jumped);\n        if (const TCastToConstPtr<CActor> wp = mgr.ObjectById(x8_wpId)) {\n          const zeus::CVector3f toWp = wp->GetTranslation() - act->GetTranslation();\n          if (toWp.z() > 0.f) {\n            float tmp = -act->GetMomentum().z() / act->GetMass();\n            const float tmp2 = std::sqrt(tmp * 2.f * toWp.z());\n            tmp = 1.f / (tmp2 / tmp);\n            xc_launchVel = zeus::CVector3f(tmp * toWp.x(), tmp * toWp.y(), tmp2);\n            x18_24_launched = false;\n          }\n        }\n      }\n      break;\n    }\n    case pas::EWallHangState::JumpArc: {\n      SetLaunchVelocity(bc);\n      if (bc.IsAnimationOver()) {\n        x4_state = pas::EWallHangState::JumpAirLoop;\n        const CPASAnimParmData parms(pas::EAnimationState::WallHang, CPASAnimParm::FromEnum(s32(x4_state)));\n        bc.LoopBestAnimation(parms, *mgr.GetActiveRandom());\n      } else {\n        CheckForWall(bc, mgr);\n      }\n      break;\n    }\n    case pas::EWallHangState::JumpAirLoop: {\n      SetLaunchVelocity(bc);\n      if (!CheckForWall(bc, mgr)) {\n        CheckForLand(bc, mgr);\n      }\n      break;\n    }\n    case pas::EWallHangState::IntoWallHang: {\n      if (bc.IsAnimationOver()) {\n        x4_state = pas::EWallHangState::WallHang;\n        const CPASAnimParmData parms(pas::EAnimationState::WallHang, CPASAnimParm::FromEnum(s32(x4_state)));\n        bc.LoopBestAnimation(parms, *mgr.GetActiveRandom());\n      } else if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::ExitState)) {\n        x18_25_needsExit = true;\n      }\n      break;\n    }\n    case pas::EWallHangState::WallHang: {\n      if (!bc.GetCommandMgr().GetTargetVector().isZero()) {\n        if (const TCastToConstPtr<CActor> wp = mgr.ObjectById(x8_wpId)) {\n          if (const TCastToConstPtr<CActor> act = bc.GetOwner()) {\n            const zeus::CVector3f lookDir = bc.GetCommandMgr().GetTargetVector().normalized();\n            const float actorDotWp = act->GetTransform().basis[1].dot(wp->GetTransform().basis[1]);\n            const float lookDotWp = lookDir.dot(wp->GetTransform().basis[1]);\n            if (actorDotWp < -0.5f || lookDotWp > 0.5f) {\n              bc.FaceDirection(lookDir, dt);\n            }\n          }\n        }\n      }\n      if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::ExitState) || x18_25_needsExit) {\n        x4_state = pas::EWallHangState::OutOfWallHang;\n        const CPASAnimParmData parms(pas::EAnimationState::WallHang, CPASAnimParm::FromEnum(s32(x4_state)));\n        bc.PlayBestAnimation(parms, *mgr.GetActiveRandom());\n      }\n      FixInPlace(bc);\n      break;\n    }\n    case pas::EWallHangState::Five: {\n      if (bc.IsAnimationOver()) {\n        x4_state = pas::EWallHangState::WallHang;\n        const CPASAnimParmData parms(pas::EAnimationState::WallHang, CPASAnimParm::FromEnum(s32(x4_state)));\n        bc.LoopBestAnimation(parms, *mgr.GetActiveRandom());\n      }\n      FixInPlace(bc);\n      break;\n    }\n    case pas::EWallHangState::OutOfWallHang: {\n      if (bc.IsAnimationOver()) {\n        const CPASAnimParmData parms(pas::EAnimationState::WallHang, CPASAnimParm::FromEnum(7));\n        const std::pair<float, s32> best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1);\n        if (best.first > 0.f) {\n          x4_state = pas::EWallHangState::OutOfWallHangTurn;\n          const CAnimPlaybackParms playParms(best.second, -1, 1.f, true);\n          bc.SetCurrentAnimation(playParms, false, false);\n        } else {\n          x4_state = pas::EWallHangState::DetachJumpLoop;\n          const CPASAnimParmData loopParms(pas::EAnimationState::WallHang, CPASAnimParm::FromEnum(s32(x4_state)));\n          bc.LoopBestAnimation(loopParms, *mgr.GetActiveRandom());\n        }\n        if (const TCastToPtr<CPhysicsActor> act = bc.GetOwner()) {\n          mgr.SendScriptMsg(act.GetPtr(), kInvalidUniqueId, EScriptObjectMessage::Jumped);\n          x18_24_launched = false;\n          if (const TCastToConstPtr<CActor> wp = mgr.ObjectById(x8_wpId)) {\n            xc_launchVel = 15.f * wp->GetTransform().basis[1];\n            xc_launchVel.z() = 5.f;\n          } else {\n            xc_launchVel = -15.f * act->GetTransform().basis[1];\n          }\n          act->SetAngularMomentum(zeus::CAxisAngle());\n        }\n      }\n      break;\n    }\n    case pas::EWallHangState::OutOfWallHangTurn: {\n      SetLaunchVelocity(bc);\n      if (bc.IsAnimationOver()) {\n        x4_state = pas::EWallHangState::DetachJumpLoop;\n        const CPASAnimParmData parms(pas::EAnimationState::WallHang, CPASAnimParm::FromEnum(s32(x4_state)));\n        bc.LoopBestAnimation(parms, *mgr.GetActiveRandom());\n      } else {\n        CheckForLand(bc, mgr);\n      }\n      break;\n    }\n    case pas::EWallHangState::DetachJumpLoop: {\n      SetLaunchVelocity(bc);\n      CheckForLand(bc, mgr);\n      break;\n    }\n    case pas::EWallHangState::DetachOutOfJump: {\n      if (bc.IsAnimationOver()) {\n        x4_state = pas::EWallHangState::Invalid;\n        return pas::EAnimationState::Locomotion;\n      }\n      break;\n    }\n    default:\n      break;\n    }\n  }\n  return st;\n}\n\nbool CBSWallHang::IsInAir(const CBodyController& bc) const {\n  switch (x4_state) {\n  case pas::EWallHangState::JumpArc:\n  case pas::EWallHangState::JumpAirLoop:\n  case pas::EWallHangState::OutOfWallHangTurn:\n  case pas::EWallHangState::DetachJumpLoop:\n    return true;\n  default:\n    return false;\n  }\n}\n\nbool CBSWallHang::ApplyAnimationDeltas() const {\n  switch (x4_state) {\n  case pas::EWallHangState::IntoJump:\n  case pas::EWallHangState::IntoWallHang:\n  case pas::EWallHangState::WallHang:\n  case pas::EWallHangState::Five:\n  case pas::EWallHangState::OutOfWallHang:\n  case pas::EWallHangState::DetachOutOfJump:\n    return true;\n  default:\n    return false;\n  }\n}\n\nbool CBSWallHang::ApplyHeadTracking() const {\n  switch (x4_state) {\n  case pas::EWallHangState::WallHang:\n  case pas::EWallHangState::Five:\n    return true;\n  default:\n    return false;\n  }\n}\n\nbool CBSWallHang::ApplyGravity() const {\n  switch (x4_state) {\n  case pas::EWallHangState::WallHang:\n  case pas::EWallHangState::IntoWallHang:\n  case pas::EWallHangState::OutOfWallHang:\n    return false;\n  default:\n    return true;\n  }\n}\n\nfloat CBSLocomotion::GetStartVelocityMagnitude(const CBodyController& bc) const {\n  if (const TCastToConstPtr<CPhysicsActor> act = bc.GetOwner()) {\n    const float maxSpeed = bc.GetBodyStateInfo().GetMaxSpeed();\n    float ret = 0.f;\n    if (maxSpeed > 0.f) {\n      ret = act->GetVelocity().magnitude() / maxSpeed;\n    }\n    return std::min(1.f, ret);\n  }\n  return 0.f;\n}\n\nvoid CBSLocomotion::ReStartBodyState(CBodyController& bc, bool maintainVel) {\n  UpdateLocomotionAnimation(0.f, maintainVel ? GetStartVelocityMagnitude(bc) : 0.f, bc, true);\n}\n\nfloat CBSLocomotion::ComputeWeightPercentage(const std::pair<s32, float>& a, const std::pair<s32, float>& b,\n                                             float f) const {\n  const float range = b.second - a.second;\n  if (range > FLT_EPSILON) {\n    return std::max(0.f, std::min(1.f, (f - a.second) / range));\n  }\n  return 0.f;\n}\n\nvoid CBSLocomotion::Start(CBodyController& bc, CStateManager& mgr) {\n  x4_locomotionType = bc.GetLocomotionType();\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::MaintainVelocity)) {\n    ReStartBodyState(bc, true);\n  } else {\n    ReStartBodyState(bc, false);\n  }\n}\n\npas::EAnimationState CBSLocomotion::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) {\n  const pas::EAnimationState st = GetBodyStateTransition(dt, bc);\n  if (st == pas::EAnimationState::Invalid) {\n    UpdateLocomotionAnimation(dt, ApplyLocomotionPhysics(dt, bc), bc, false);\n  }\n  return st;\n}\n\nvoid CBSLocomotion::Shutdown(CBodyController& bc) { bc.MultiplyPlaybackRate(1.f); }\n\nfloat CBSLocomotion::ApplyLocomotionPhysics(float dt, CBodyController& bc) {\n  if (const TCastToConstPtr<CPhysicsActor> act = bc.GetOwner()) {\n    const zeus::CVector3f vec = (zeus::close_enough(bc.GetCommandMgr().GetFaceVector(), zeus::skZero3f, 0.0001f))\n                                    ? bc.GetCommandMgr().GetMoveVector()\n                                    : bc.GetCommandMgr().GetFaceVector();\n    if (vec.canBeNormalized()) {\n      if (IsPitchable()) {\n        zeus::CVector3f tmp = vec;\n        tmp.z() = 0.f;\n        zeus::CVector3f lookVec = act->GetTransform().basis[1];\n        lookVec.z() = 0.f;\n        lookVec.normalize();\n        bc.FaceDirection3D(tmp, lookVec, dt);\n        zeus::CVector3f lookVec2 = act->GetTransform().basis[1];\n        lookVec2.z() = float(vec.z());\n        lookVec2.normalize();\n        if (!zeus::close_enough(lookVec, lookVec2, 0.0001f)) {\n          const zeus::CRelAngle pitchAngle =\n              std::min(bc.GetBodyStateInfo().GetMaximumPitch(), zeus::CVector3f::getAngleDiff(vec, tmp));\n          lookVec2 = zeus::CVector3f::slerp(lookVec, lookVec2, pitchAngle);\n        }\n        bc.FaceDirection3D(lookVec2, act->GetTransform().basis[1], dt);\n        zeus::CVector3f lookVec3 = act->GetTransform().basis[1];\n        lookVec3.z() = 0.f;\n        bc.FaceDirection3D(lookVec3, act->GetTransform().basis[1], dt);\n      } else {\n        bc.FaceDirection(vec.normalized(), dt);\n      }\n    }\n    return std::min(1.f, bc.GetCommandMgr().GetMoveVector().magnitude());\n  }\n  return 0.f;\n}\n\npas::EAnimationState CBSLocomotion::GetBodyStateTransition(float, CBodyController& bc) {\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Hurled)) {\n    return pas::EAnimationState::Hurled;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockDown)) {\n    return pas::EAnimationState::Fall;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopHitReaction)) {\n    return pas::EAnimationState::LoopReaction;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockBack)) {\n    return pas::EAnimationState::KnockBack;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Locomotion)) {\n    bc.GetCommandMgr().ClearLocomotionCmds();\n    return pas::EAnimationState::Invalid;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Slide)) {\n    return pas::EAnimationState::Slide;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Generate)) {\n    return pas::EAnimationState::Generate;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::MeleeAttack)) {\n    return pas::EAnimationState::MeleeAttack;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::ProjectileAttack)) {\n    return pas::EAnimationState::ProjectileAttack;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopAttack)) {\n    return pas::EAnimationState::LoopAttack;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopReaction)) {\n    return pas::EAnimationState::LoopReaction;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Jump)) {\n    return pas::EAnimationState::Jump;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Taunt)) {\n    return pas::EAnimationState::Taunt;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Step)) {\n    return pas::EAnimationState::Step;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Cover)) {\n    return pas::EAnimationState::Cover;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::WallHang)) {\n    return pas::EAnimationState::WallHang;\n  }\n  if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Scripted)) {\n    return pas::EAnimationState::Scripted;\n  }\n  if (bc.GetCommandMgr().GetMoveVector().isZero()) {\n    if (!bc.GetCommandMgr().GetFaceVector().isZero()) {\n      if (!IsMoving()) {\n        return pas::EAnimationState::Turn;\n      }\n    }\n  }\n  if (x4_locomotionType != bc.GetLocomotionType()) {\n    return pas::EAnimationState::Locomotion;\n  }\n  return pas::EAnimationState::Invalid;\n}\n\nCBSBiPedLocomotion::CBSBiPedLocomotion(CActor& actor) {\n  const CPASDatabase& pasDatabase = actor.GetModelData()->GetAnimationData()->GetCharacterInfo().GetPASDatabase();\n  for (int i = 0; i < 14; ++i) {\n    rstl::reserved_vector<std::pair<s32, float>, 8>& innerVec = x8_anims.emplace_back();\n    for (int j = 0; j < 8; ++j) {\n      const CPASAnimParmData parms(pas::EAnimationState::Locomotion, CPASAnimParm::FromEnum(j),\n                                   CPASAnimParm::FromEnum(i));\n      const std::pair<float, s32> best = pasDatabase.FindBestAnimation(parms, -1);\n      float avgVel = 0.f;\n      if (best.second != -1) {\n        avgVel = actor.GetAverageAnimVelocity(best.second);\n        if (j == 0) {\n          avgVel = 0.f;\n        }\n      }\n      innerVec.push_back({best.second, avgVel});\n    }\n  }\n}\n\nvoid CBSBiPedLocomotion::Start(CBodyController& bc, CStateManager& mgr) {\n  x3c8_primeTime = 0.f;\n  CBSLocomotion::Start(bc, mgr);\n}\n\npas::EAnimationState CBSBiPedLocomotion::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) {\n  if (x3c8_primeTime < 0.2f) {\n    x3c8_primeTime += dt;\n  }\n  return CBSLocomotion::UpdateBody(dt, bc, mgr);\n}\n\nfloat CBSBiPedLocomotion::GetLocomotionSpeed(pas::ELocomotionType type, pas::ELocomotionAnim anim) const {\n  return GetLocoAnimation(type, anim).second;\n}\n\nfloat CBSBiPedLocomotion::UpdateRun(float vel, CBodyController& bc, pas::ELocomotionAnim anim) {\n  const std::pair<s32, float>& walk = GetLocoAnimation(x4_locomotionType, pas::ELocomotionAnim::Walk);\n  const std::pair<s32, float>& run = GetLocoAnimation(x4_locomotionType, pas::ELocomotionAnim::Run);\n  const float perc = ComputeWeightPercentage(walk, run, vel);\n\n  if (perc < 0.4f) {\n    const float rate = walk.second > 0.f ? vel / walk.second : 1.f;\n    if (anim != pas::ELocomotionAnim::Walk && bc.GetCurrentAnimId() != walk.first) {\n      const CAnimPlaybackParms playParms(walk.first, -1, 1.f, true);\n      bc.SetCurrentAnimation(playParms, true, false);\n      x3c8_primeTime = 0.f;\n    }\n    bc.MultiplyPlaybackRate(rate);\n    x3c4_anim = pas::ELocomotionAnim::Walk;\n    return rate;\n  } else {\n    const float rate = std::min(1.f, vel / run.second);\n    if (anim != pas::ELocomotionAnim::Run && bc.GetCurrentAnimId() != run.first) {\n      const CAnimPlaybackParms playParms(run.first, -1, 1.f, true);\n      bc.SetCurrentAnimation(playParms, true, false);\n      x3c8_primeTime = 0.f;\n    }\n    bc.MultiplyPlaybackRate(rate);\n    x3c4_anim = pas::ELocomotionAnim::Run;\n    return rate;\n  }\n}\n\nfloat CBSBiPedLocomotion::UpdateWalk(float vel, CBodyController& bc, pas::ELocomotionAnim anim) {\n  if (anim != pas::ELocomotionAnim::Walk) {\n    const std::pair<s32, float>& walk = GetLocoAnimation(x4_locomotionType, pas::ELocomotionAnim::Walk);\n    if (bc.GetCurrentAnimId() != walk.first) {\n      const CAnimPlaybackParms playParms(walk.first, -1, 1.f, true);\n      bc.SetCurrentAnimation(playParms, true, false);\n      x3c8_primeTime = 0.f;\n    }\n    x3c4_anim = pas::ELocomotionAnim::Walk;\n  }\n\n  const std::pair<s32, float>& idle = GetLocoAnimation(x4_locomotionType, pas::ELocomotionAnim::Idle);\n  const std::pair<s32, float>& walk = GetLocoAnimation(x4_locomotionType, pas::ELocomotionAnim::Walk);\n  const float perc = std::max(0.5f, ComputeWeightPercentage(idle, walk, vel));\n  bc.MultiplyPlaybackRate(perc);\n  return perc;\n}\n\nconstexpr std::array Strafes{\n    pas::ELocomotionAnim::StrafeRight, pas::ELocomotionAnim::StrafeLeft, pas::ELocomotionAnim::Walk,\n    pas::ELocomotionAnim::BackUp,      pas::ELocomotionAnim::StrafeUp,   pas::ELocomotionAnim::StrafeDown,\n};\n\nfloat CBSBiPedLocomotion::UpdateStrafe(float vel, CBodyController& bc, pas::ELocomotionAnim anim) {\n  if (const TCastToConstPtr<CPhysicsActor> act = bc.GetOwner()) {\n    const zeus::CVector3f localVec = act->GetTransform().transposeRotate(bc.GetCommandMgr().GetMoveVector());\n    const zeus::CVector3f localVecSq = localVec * localVec;\n    int maxComp = 0;\n    for (int i = 0; i < 3; ++i) {\n      if (localVecSq[i] >= localVecSq[maxComp]) {\n        maxComp = i;\n      }\n    }\n    const int strafeKey = maxComp * 2 + (localVec[maxComp] > 0.f ? 0 : 1);\n    const pas::ELocomotionAnim strafeType = Strafes[strafeKey];\n    const float rate = vel * GetLocomotionSpeed(x4_locomotionType, strafeType);\n    if (anim != strafeType) {\n      const std::pair<s32, float>& strafe = GetLocoAnimation(x4_locomotionType, strafeType);\n      if (bc.GetCurrentAnimId() != strafe.first) {\n        const CAnimPlaybackParms playParms(strafe.first, -1, 1.f, true);\n        bc.SetCurrentAnimation(playParms, true, false);\n        x3c8_primeTime = 0.f;\n      }\n      x3c4_anim = strafeType;\n    }\n    const std::pair<s32, float>& idle = GetLocoAnimation(x4_locomotionType, pas::ELocomotionAnim::Idle);\n    const std::pair<s32, float>& strafe = GetLocoAnimation(x4_locomotionType, strafeType);\n    const float perc = std::max(0.5f, ComputeWeightPercentage(idle, strafe, rate));\n    bc.MultiplyPlaybackRate(perc);\n  }\n  return 1.f;\n}\n\nfloat CBSBiPedLocomotion::UpdateLocomotionAnimation(float dt, float velMag, CBodyController& bc, bool init) {\n  if (init || x3c8_primeTime >= 0.2f) {\n    const auto anim = init ? pas::ELocomotionAnim::Invalid : x3c4_anim;\n    const float maxSpeed = velMag * GetLocomotionSpeed(x4_locomotionType, pas::ELocomotionAnim::Run);\n    if (IsStrafing(bc) && velMag >= 0.01f) {\n      return UpdateStrafe(velMag, bc, anim);\n    }\n\n    if (maxSpeed < 0.01f) {\n      if (anim != pas::ELocomotionAnim::Idle || init) {\n        if (!bc.GetBodyStateInfo().GetLocoAnimChangeAtEndOfAnimOnly() || bc.GetAnimTimeRemaining() <= dt || init) {\n          const std::pair<s32, float>& best = GetLocoAnimation(x4_locomotionType, pas::ELocomotionAnim::Idle);\n          if (bc.GetCurrentAnimId() != best.first) {\n            const CAnimPlaybackParms playParms(best.first, -1, 1.f, true);\n            bc.SetCurrentAnimation(playParms, true, false);\n            x3c8_primeTime = 0.f;\n          }\n          x3c4_anim = pas::ELocomotionAnim::Idle;\n        }\n      }\n    } else {\n      const std::pair<s32, float>& best = GetLocoAnimation(x4_locomotionType, pas::ELocomotionAnim::Walk);\n      if (maxSpeed < best.second) {\n        return UpdateWalk(maxSpeed, bc, anim);\n      }\n      return UpdateRun(maxSpeed, bc, anim);\n    }\n  }\n\n  return 1.0f;\n}\n\nbool CBSBiPedLocomotion::IsStrafing(const CBodyController& bc) const {\n  if (!zeus::close_enough(bc.GetCommandMgr().GetMoveVector(), zeus::skZero3f, 0.0001f)) {\n    if (!zeus::close_enough(bc.GetCommandMgr().GetFaceVector(), zeus::skZero3f, 0.0001f)) {\n      return true;\n    }\n  }\n  return false;\n}\n\nCBSFlyerLocomotion::CBSFlyerLocomotion(CActor& actor, bool pitchable)\n: CBSBiPedLocomotion(actor), x3cc_pitchable(pitchable) {}\n\nfloat CBSFlyerLocomotion::ApplyLocomotionPhysics(float dt, CBodyController& bc) {\n  const float ret = CBSLocomotion::ApplyLocomotionPhysics(dt, bc);\n  if (const TCastToPtr<CPhysicsActor> act = bc.GetOwner()) {\n    if (std::fabs(bc.GetCommandMgr().GetMoveVector().z()) > 0.01f &&\n        (!x3cc_pitchable || bc.GetBodyStateInfo().GetMaximumPitch() < 0.17453292f)) {\n      zeus::CVector3f dir;\n      dir.z() = dt * bc.GetBodyStateInfo().GetMaxSpeed() * bc.GetCommandMgr().GetMoveVector().z();\n      act->ApplyImpulseWR(act->GetMoveToORImpulseWR(dir, dt), zeus::CAxisAngle());\n    }\n  }\n  return ret;\n}\n\nCBSWallWalkerLocomotion::CBSWallWalkerLocomotion(CActor& actor) : CBSBiPedLocomotion(actor) {}\n\nfloat CBSWallWalkerLocomotion::ApplyLocomotionPhysics(float dt, CBodyController& bc) {\n  if (const TCastToConstPtr<CPhysicsActor> act = bc.GetOwner()) {\n    const float maxSpeed = bc.GetBodyStateInfo().GetMaxSpeed();\n    const zeus::CVector3f scaledMove = bc.GetCommandMgr().GetMoveVector() * maxSpeed;\n    if ((zeus::CVector3f::getAngleDiff(bc.GetCommandMgr().GetFaceVector(), scaledMove) < (M_PIF / 2.f)\n             ? scaledMove\n             : bc.GetCommandMgr().GetFaceVector())\n            .canBeNormalized()) {\n      bc.FaceDirection3D(scaledMove.normalized(), act->GetTransform().basis[1], dt);\n    }\n    zeus::CVector3f impulse = act->GetMoveToORImpulseWR(act->GetTransform().transposeRotate(scaledMove * dt), dt);\n    impulse = act->GetMass() > FLT_EPSILON ? impulse / act->GetMass()\n                                           : zeus::CVector3f(0.f, act->GetVelocity().magnitude(), 0.f);\n    if (maxSpeed > FLT_EPSILON) {\n      return std::min(1.f, impulse.magnitude() / maxSpeed);\n    }\n  }\n  return 0.f;\n}\n\nCBSNewFlyerLocomotion::CBSNewFlyerLocomotion(CActor& actor) : CBSBiPedLocomotion(actor) {}\n\nfloat CBSNewFlyerLocomotion::ApplyLocomotionPhysics(float dt, CBodyController& bc) {\n  if (TCastToConstPtr<CPhysicsActor>(bc.GetOwner())) {\n    bc.FaceDirection(bc.GetCommandMgr().GetFaceVector(), dt);\n  }\n  return 0.f;\n}\n\nconstexpr std::array RunStrafes{\n    pas::ELocomotionAnim::StrafeRight, pas::ELocomotionAnim::StrafeLeft, pas::ELocomotionAnim::Run,\n    pas::ELocomotionAnim::BackUp,      pas::ELocomotionAnim::StrafeUp,   pas::ELocomotionAnim::StrafeDown,\n};\n\nfloat CBSNewFlyerLocomotion::UpdateLocomotionAnimation(float dt, float velMag, CBodyController& bc, bool init) {\n  if (const TCastToConstPtr<CPhysicsActor> act = bc.GetOwner()) {\n    pas::ELocomotionAnim strafeType = pas::ELocomotionAnim::Idle;\n    if (bc.GetCommandMgr().GetMoveVector().canBeNormalized()) {\n      const zeus::CVector3f localVec = act->GetTransform().transposeRotate(bc.GetCommandMgr().GetMoveVector());\n      const zeus::CVector3f localVecSq = localVec * localVec;\n      int maxComp = 0;\n      for (int i = 0; i < 3; ++i) {\n        if (localVecSq[i] >= localVecSq[maxComp]) {\n          maxComp = i;\n        }\n      }\n      const int strafeKey = maxComp * 2 + (localVec[maxComp] > 0.f ? 0 : 1);\n      strafeType = RunStrafes[strafeKey];\n    }\n\n    if (init || strafeType != x3c4_anim) {\n      const std::pair<s32, float>& strafe = GetLocoAnimation(x4_locomotionType, strafeType);\n      if (init || bc.GetCurrentAnimId() != strafe.first) {\n        const CAnimPlaybackParms playParms(strafe.first, -1, 1.f, true);\n        bc.SetCurrentAnimation(playParms, true, false);\n      }\n      x3c4_anim = strafeType;\n    }\n  }\n\n  return 1.f;\n}\n\nCBSRestrictedLocomotion::CBSRestrictedLocomotion(CActor& actor) {\n  const CPASDatabase& pasDatabase = actor.GetModelData()->GetAnimationData()->GetCharacterInfo().GetPASDatabase();\n  for (int i = 0; i < 14; ++i) {\n    const CPASAnimParmData parms(pas::EAnimationState::Locomotion, CPASAnimParm::FromEnum(0),\n                                 CPASAnimParm::FromEnum(i));\n    const std::pair<float, s32> best = pasDatabase.FindBestAnimation(parms, -1);\n    x8_anims.push_back(best.second);\n  }\n}\n\nfloat CBSRestrictedLocomotion::UpdateLocomotionAnimation(float dt, float velMag, CBodyController& bc, bool init) {\n  const pas::ELocomotionAnim anim = init ? pas::ELocomotionAnim::Invalid : x44_anim;\n  if (anim != pas::ELocomotionAnim::Idle) {\n    const s32 newAnim = x8_anims[int(x4_locomotionType)];\n    if (newAnim != bc.GetCurrentAnimId()) {\n      const CAnimPlaybackParms playParms(newAnim, -1, 1.f, true);\n      bc.SetCurrentAnimation(playParms, true, false);\n    }\n    x44_anim = pas::ELocomotionAnim::Idle;\n  }\n  return 1.f;\n}\n\nCBSRestrictedFlyerLocomotion::CBSRestrictedFlyerLocomotion(CActor& actor) : CBSRestrictedLocomotion(actor) {}\n\nfloat CBSRestrictedFlyerLocomotion::ApplyLocomotionPhysics(float dt, CBodyController& bc) {\n  if (const TCastToPtr<CPhysicsActor> act = bc.GetOwner()) {\n    bc.FaceDirection(bc.GetCommandMgr().GetFaceVector(), dt);\n    act->ApplyImpulseWR(bc.GetCommandMgr().GetMoveVector() * bc.GetRestrictedFlyerMoveSpeed() * act->GetMass(),\n                        zeus::CAxisAngle());\n  }\n  return 0.f;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CBodyState.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Character/CBodyStateCmdMgr.hpp\"\n#include \"Runtime/Character/CharacterCommon.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CBodyController;\nclass CStateManager;\nclass CActor;\nclass CBodyState {\npublic:\n  virtual ~CBodyState() = default;\n  virtual bool IsInAir(const CBodyController&) const { return false; }\n  virtual bool IsDead() const { return false; }\n  virtual bool IsDying() const { return false; }\n  virtual bool IsMoving() const { return false; }\n  virtual bool ApplyGravity() const { return true; }\n  virtual bool ApplyHeadTracking() const { return true; }\n  virtual bool ApplyAnimationDeltas() const { return true; }\n  virtual bool CanShoot() const { return false; }\n  virtual void Start(CBodyController&, CStateManager&) = 0;\n  virtual pas::EAnimationState UpdateBody(float, CBodyController&, CStateManager&) = 0;\n  virtual void Shutdown(CBodyController&) = 0;\n};\n\nclass CBSAttack : public CBodyState {\n  pas::EAnimationState x4_nextState = pas::EAnimationState::Invalid;\n  CBCSlideCmd x8_slide;\n  zeus::CVector3f x20_targetPos;\n  float x2c_alignTargetPosStartTime = -1.f;\n  float x30_alignTargetPosTime = -1.f;\n  float x34_curTime = 0.f;\n  pas::EAnimationState GetBodyStateTransition(float dt, const CBodyController& bc);\n  void UpdatePhysicsActor(const CBodyController& bc, float dt);\n\npublic:\n  bool CanShoot() const override { return false; }\n  void Start(CBodyController& bc, CStateManager& mgr) override;\n  pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override;\n  void Shutdown(CBodyController&) override {}\n};\n\nclass CBSProjectileAttack : public CBodyState {\n  pas::EAnimationState GetBodyStateTransition(float dt, const CBodyController& bc) const;\n\npublic:\n  bool CanShoot() const override { return true; }\n  void Start(CBodyController& bc, CStateManager& mgr) override;\n  pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override;\n  void Shutdown(CBodyController&) override {}\n};\n\nclass CBSDie : public CBodyState {\n  float x4_remTime = 0.f;\n  bool x8_isDead = false;\n\npublic:\n  bool IsDead() const override { return x8_isDead; }\n  bool IsDying() const override { return true; }\n  void Start(CBodyController& bc, CStateManager& mgr) override;\n  pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override;\n  void Shutdown(CBodyController&) override {}\n};\n\nclass CBSFall : public CBodyState {\n  float x4_rotateSpeed = 0.f;\n  float x8_remTime = 0.f;\n  pas::EFallState xc_fallState = pas::EFallState::Invalid;\n  pas::EAnimationState GetBodyStateTransition(float dt, const CBodyController& bc) const;\n\npublic:\n  void Start(CBodyController& bc, CStateManager& mgr) override;\n  pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override;\n  void Shutdown(CBodyController& bc) override;\n};\n\nclass CBSGetup : public CBodyState {\n  pas::EFallState x4_fallState = pas::EFallState::Invalid;\n  pas::EAnimationState GetBodyStateTransition(float dt, const CBodyController& bc) const;\n\npublic:\n  void Start(CBodyController& bc, CStateManager& mgr) override;\n  pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override;\n  void Shutdown(CBodyController& bc) override;\n};\n\nclass CBSKnockBack : public CBodyState {\n  float x4_curTime = 0.f;\n  float x8_rotateSpeed = 0.f;\n  float xc_remTime = 0.f;\n  pas::EAnimationState GetBodyStateTransition(float dt, const CBodyController& bc) const;\n\npublic:\n  bool IsMoving() const override { return true; }\n  void Start(CBodyController& bc, CStateManager& mgr) override;\n  pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override;\n  void Shutdown(CBodyController&) override {}\n};\n\nclass CBSLieOnGround : public CBodyState {\n  bool x4_24_hasGroundHit : 1;\n  pas::EAnimationState GetBodyStateTransition(float dt, const CBodyController& bc) const;\n\npublic:\n  explicit CBSLieOnGround(CActor& actor);\n  void Start(CBodyController& bc, CStateManager& mgr) override;\n  pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override;\n  void Shutdown(CBodyController& bc) override;\n};\n\nclass CBSStep : public CBodyState {\n  pas::EAnimationState GetBodyStateTransition(float dt, const CBodyController& bc) const;\n\npublic:\n  bool IsMoving() const override { return true; }\n  bool CanShoot() const override { return true; }\n  void Start(CBodyController& bc, CStateManager& mgr) override;\n  pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override;\n  void Shutdown(CBodyController&) override {}\n};\n\nclass CBSTurn : public CBodyState {\nprotected:\n  float x4_rotateSpeed = 0.f;\n  zeus::CVector2f x8_dest;\n  pas::ETurnDirection x10_turnDir = pas::ETurnDirection::Invalid;\n  bool FacingDest(const CBodyController& bc) const;\n\npublic:\n  bool CanShoot() const override { return true; }\n  void Start(CBodyController& bc, CStateManager& mgr) override;\n  pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override;\n  void Shutdown(CBodyController&) override {}\n  virtual pas::EAnimationState GetBodyStateTransition(float dt, CBodyController& bc) const;\n};\n\nclass CBSFlyerTurn : public CBSTurn {\npublic:\n  void Start(CBodyController& bc, CStateManager& mgr) override;\n  pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override;\n};\n\nclass CBSLoopAttack : public CBodyState {\n  pas::ELoopState x4_state = pas::ELoopState::Invalid;\n  pas::ELoopAttackType x8_loopAttackType = pas::ELoopAttackType::Invalid;\n  bool xc_24_waitForAnimOver : 1 = false;\n  bool xc_25_advance : 1 = false;\n  pas::EAnimationState GetBodyStateTransition(float dt, const CBodyController& bc) const;\n\npublic:\n  CBSLoopAttack() = default;\n  bool CanShoot() const override { return true; }\n  void Start(CBodyController& bc, CStateManager& mgr) override;\n  pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override;\n  void Shutdown(CBodyController&) override {}\n};\n\nclass CBSLoopReaction : public CBodyState {\n  pas::ELoopState x4_state = pas::ELoopState::Invalid;\n  pas::EReactionType x8_reactionType = pas::EReactionType::Invalid;\n  bool xc_24_loopHit : 1 = false;\n  pas::EAnimationState GetBodyStateTransition(float dt, const CBodyController& bc) const;\n  bool PlayExitAnimation(CBodyController& bc, CStateManager& mgr) const;\n\npublic:\n  CBSLoopReaction() = default;\n  void Start(CBodyController& bc, CStateManager& mgr) override;\n  pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override;\n  void Shutdown(CBodyController&) override {}\n};\n\nclass CBSGroundHit : public CBodyState {\n  float x4_rotateSpeed = 0.f;\n  float x8_remTime = 0.f;\n  pas::EFallState xc_fallState = pas::EFallState::Invalid;\n  pas::EAnimationState GetBodyStateTransition(float dt, const CBodyController& bc) const;\n\npublic:\n  void Start(CBodyController& bc, CStateManager& mgr) override;\n  pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override;\n  void Shutdown(CBodyController& bc) override;\n};\n\nclass CBSGenerate : public CBodyState {\n  pas::EAnimationState GetBodyStateTransition(float dt, const CBodyController& bc) const;\n\npublic:\n  void Start(CBodyController& bc, CStateManager& mgr) override;\n  pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override;\n  void Shutdown(CBodyController&) override {}\n};\n\nclass CBSJump : public CBodyState {\n  pas::EJumpState x4_state = pas::EJumpState::Invalid;\n  pas::EJumpType x8_jumpType{};\n  zeus::CVector3f xc_waypoint1;\n  zeus::CVector3f x18_velocity;\n  zeus::CVector3f x24_waypoint2;\n  bool x30_24_bodyForceSet : 1 = false;\n  bool x30_25_wallJump : 1 = false;\n  bool x30_26_wallBounceRight : 1 = false;\n  bool x30_27_wallBounceComplete : 1 = false;\n  bool x30_28_startInJumpLoop : 1 = false;\n  pas::EAnimationState GetBodyStateTransition(float dt, const CBodyController& bc) const;\n  bool CheckForWallJump(CBodyController& bc, CStateManager& mgr);\n  void CheckForLand(CBodyController& bc, CStateManager& mgr);\n  void PlayJumpLoop(CStateManager& mgr, CBodyController& bc);\n\npublic:\n  CBSJump() = default;\n  bool IsMoving() const override { return true; }\n  bool ApplyHeadTracking() const override { return false; }\n  bool CanShoot() const override;\n  void Start(CBodyController& bc, CStateManager& mgr) override;\n  pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override;\n  bool ApplyAnimationDeltas() const override;\n  bool IsInAir(const CBodyController& bc) const override;\n  void Shutdown(CBodyController&) override {}\n};\n\nclass CBSHurled : public CBodyState {\n  pas::EHurledState x4_state = pas::EHurledState::Invalid;\n  float x8_knockAngle = 0.f;\n  int xc_animSeries = -1;\n  float x10_rotateSpeed = 0.f;\n  float x14_remTime = 0.f;\n  float x18_curTime = 0.f;\n  mutable zeus::CVector3f x1c_lastTranslation;\n  mutable float x28_landedDur = 0.f;\n  bool x2c_24_needsRecover : 1 = false;\n  pas::EAnimationState GetBodyStateTransition(float dt, CBodyController& bc) const;\n  void Recover(CStateManager& mgr, CBodyController& bc, pas::EHurledState state);\n  void PlayStrikeWallAnimation(CBodyController& bc, CStateManager& mgr);\n  void PlayLandAnimation(CBodyController& bc, CStateManager& mgr);\n  bool ShouldStartStrikeWall(const CBodyController& bc) const;\n  bool ShouldStartLand(float dt, const CBodyController& bc) const;\n\npublic:\n  CBSHurled() = default;\n  bool IsMoving() const override { return true; }\n  bool IsInAir(const CBodyController&) const override { return true; }\n  bool ApplyHeadTracking() const override { return false; }\n  void Start(CBodyController& bc, CStateManager& mgr) override;\n  pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override;\n  void Shutdown(CBodyController&) override {}\n};\n\nclass CBSSlide : public CBodyState {\n  float x4_rotateSpeed = 0.f;\n  pas::EAnimationState GetBodyStateTransition(float dt, const CBodyController& bc) const;\n\npublic:\n  bool ApplyHeadTracking() const override { return false; }\n  bool IsMoving() const override { return true; }\n  void Start(CBodyController& bc, CStateManager& mgr) override;\n  pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override;\n  void Shutdown(CBodyController&) override {}\n};\n\nclass CBSTaunt : public CBodyState {\n  pas::EAnimationState GetBodyStateTransition(float dt, const CBodyController& bc) const;\n\npublic:\n  void Start(CBodyController& bc, CStateManager& mgr) override;\n  pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override;\n  void Shutdown(CBodyController&) override {}\n};\n\nclass CBSScripted : public CBodyState {\n  bool x4_24_loopAnim : 1 = false;\n  bool x4_25_timedLoop : 1 = false;\n  float x8_remTime = 0.f;\n  pas::EAnimationState GetBodyStateTransition(float dt, const CBodyController& bc) const;\n\npublic:\n  CBSScripted() = default;\n  bool ApplyHeadTracking() const override { return false; }\n  void Start(CBodyController& bc, CStateManager& mgr) override;\n  pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override;\n  void Shutdown(CBodyController&) override {}\n};\n\nclass CBSCover : public CBodyState {\n  pas::ECoverState x4_state = pas::ECoverState::Invalid;\n  pas::ECoverDirection x8_coverDirection = pas::ECoverDirection::Invalid;\n  bool xc_needsExit = false;\n  pas::EAnimationState GetBodyStateTransition(float dt, const CBodyController& bc) const;\n\npublic:\n  bool ApplyHeadTracking() const override { return false; }\n  bool IsMoving() const override { return true; }\n  bool CanShoot() const override { return x4_state == pas::ECoverState::Lean; }\n  void Start(CBodyController& bc, CStateManager& mgr) override;\n  pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override;\n  void Shutdown(CBodyController&) override {}\n};\n\nclass CBSWallHang : public CBodyState {\n  pas::EWallHangState x4_state = pas::EWallHangState::Invalid;\n  TUniqueId x8_wpId = kInvalidUniqueId;\n  zeus::CVector3f xc_launchVel;\n  bool x18_24_launched : 1 = false;\n  bool x18_25_needsExit : 1 = false;\n  pas::EAnimationState GetBodyStateTransition(float dt, const CBodyController& bc) const;\n  void FixInPlace(CBodyController& bc);\n  bool CheckForLand(CBodyController& bc, CStateManager& mgr);\n  bool CheckForWall(CBodyController& bc, CStateManager& mgr);\n  void SetLaunchVelocity(CBodyController& bc);\n\npublic:\n  CBSWallHang() = default;\n  bool IsMoving() const override { return true; }\n  bool CanShoot() const override { return x4_state == pas::EWallHangState::WallHang; }\n  bool IsInAir(const CBodyController& bc) const override;\n  bool ApplyGravity() const override;\n  bool ApplyHeadTracking() const override;\n  bool ApplyAnimationDeltas() const override;\n  void Start(CBodyController& bc, CStateManager& mgr) override;\n  pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override;\n  void Shutdown(CBodyController&) override {}\n};\n\nclass CBSLocomotion : public CBodyState {\nprotected:\n  pas::ELocomotionType x4_locomotionType = pas::ELocomotionType::Invalid;\n  float GetStartVelocityMagnitude(const CBodyController& bc) const;\n  void ReStartBodyState(CBodyController& bc, bool maintainVel);\n  float ComputeWeightPercentage(const std::pair<s32, float>& a, const std::pair<s32, float>& b, float f) const;\n\npublic:\n  bool IsMoving() const override = 0;\n  bool CanShoot() const override { return true; }\n  void Start(CBodyController& bc, CStateManager& mgr) override;\n  pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override;\n  void Shutdown(CBodyController& bc) override;\n  virtual bool IsPitchable() const { return false; }\n  virtual float GetLocomotionSpeed(pas::ELocomotionType type, pas::ELocomotionAnim anim) const = 0;\n  virtual float ApplyLocomotionPhysics(float dt, CBodyController& bc);\n  virtual float UpdateLocomotionAnimation(float dt, float velMag, CBodyController& bc, bool init) = 0;\n  virtual pas::EAnimationState GetBodyStateTransition(float dt, CBodyController& bc);\n};\n\nclass CBSBiPedLocomotion : public CBSLocomotion {\nprotected:\n  rstl::reserved_vector<rstl::reserved_vector<std::pair<s32, float>, 8>, 14> x8_anims;\n  pas::ELocomotionAnim x3c4_anim = pas::ELocomotionAnim::Invalid;\n  float x3c8_primeTime = 0.0f;\n  float UpdateRun(float vel, CBodyController& bc, pas::ELocomotionAnim anim);\n  float UpdateWalk(float vel, CBodyController& bc, pas::ELocomotionAnim anim);\n  float UpdateStrafe(float vel, CBodyController& bc, pas::ELocomotionAnim anim);\n  const std::pair<s32, float>& GetLocoAnimation(pas::ELocomotionType type, pas::ELocomotionAnim anim) const {\n    return x8_anims[size_t(type)][size_t(anim)];\n  }\n\npublic:\n  explicit CBSBiPedLocomotion(CActor& actor);\n  bool IsMoving() const override { return x3c4_anim != pas::ELocomotionAnim::Idle; }\n  void Start(CBodyController& bc, CStateManager& mgr) override;\n  pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override;\n  float GetLocomotionSpeed(pas::ELocomotionType type, pas::ELocomotionAnim anim) const override;\n  float UpdateLocomotionAnimation(float dt, float velMag, CBodyController& bc, bool init) override;\n  virtual bool IsStrafing(const CBodyController& bc) const;\n};\n\nclass CBSFlyerLocomotion : public CBSBiPedLocomotion {\n  bool x3cc_pitchable;\n\npublic:\n  explicit CBSFlyerLocomotion(CActor& actor, bool pitchable);\n  bool IsPitchable() const override { return x3cc_pitchable; }\n  float ApplyLocomotionPhysics(float dt, CBodyController& bc) override;\n  virtual bool IsBackPedal(CBodyController& bc) const { return false; }\n};\n\nclass CBSWallWalkerLocomotion : public CBSBiPedLocomotion {\npublic:\n  explicit CBSWallWalkerLocomotion(CActor& actor);\n  float ApplyLocomotionPhysics(float dt, CBodyController& bc) override;\n};\n\nclass CBSNewFlyerLocomotion : public CBSBiPedLocomotion {\npublic:\n  explicit CBSNewFlyerLocomotion(CActor& actor);\n  float ApplyLocomotionPhysics(float dt, CBodyController& bc) override;\n  float UpdateLocomotionAnimation(float dt, float velMag, CBodyController& bc, bool init) override;\n};\n\nclass CBSRestrictedLocomotion : public CBSLocomotion {\n  rstl::reserved_vector<s32, 14> x8_anims;\n  pas::ELocomotionAnim x44_anim = pas::ELocomotionAnim::Invalid;\n\npublic:\n  explicit CBSRestrictedLocomotion(CActor& actor);\n  bool IsMoving() const override { return false; }\n  float GetLocomotionSpeed(pas::ELocomotionType type, pas::ELocomotionAnim anim) const override { return 0.f; }\n  float UpdateLocomotionAnimation(float dt, float velMag, CBodyController& bc, bool init) override;\n};\n\nclass CBSRestrictedFlyerLocomotion : public CBSRestrictedLocomotion {\npublic:\n  explicit CBSRestrictedFlyerLocomotion(CActor& actor);\n  float ApplyLocomotionPhysics(float dt, CBodyController& bc) override;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CBodyStateCmdMgr.cpp",
    "content": "#include \"Runtime/Character/CBodyStateCmdMgr.hpp\"\n\n#include <cfloat>\n\nnamespace metaforce {\n\nCBodyStateCmdMgr::CBodyStateCmdMgr() {\n  x40_commandTable.push_back(&xb8_getup);\n  x40_commandTable.push_back(&xc4_step);\n  x40_commandTable.push_back(&xd4_die);\n  x40_commandTable.push_back(&xdc_knockDown);\n  x40_commandTable.push_back(&xf4_knockBack);\n  x40_commandTable.push_back(&x10c_meleeAttack);\n  x40_commandTable.push_back(&x128_projectileAttack);\n  x40_commandTable.push_back(&x144_loopAttack);\n  x40_commandTable.push_back(&x154_loopReaction);\n  x40_commandTable.push_back(&x160_loopHitReaction);\n  x40_commandTable.push_back(&x16c_exitState);\n  x40_commandTable.push_back(&x174_leanFromCover);\n  x40_commandTable.push_back(&x17c_nextState);\n  x40_commandTable.push_back(&x184_maintainVelocity);\n  x40_commandTable.push_back(&x18c_generate);\n  x40_commandTable.push_back(&x1ac_hurled);\n  x40_commandTable.push_back(&x1d0_jump);\n  x40_commandTable.push_back(&x1f8_slide);\n  x40_commandTable.push_back(&x210_taunt);\n  x40_commandTable.push_back(&x21c_scripted);\n  x40_commandTable.push_back(&x230_cover);\n  x40_commandTable.push_back(&x254_wallHang);\n  x40_commandTable.push_back(&x260_locomotion);\n  x40_commandTable.push_back(&x268_additiveIdle);\n  x40_commandTable.push_back(&x270_additiveAim);\n  x40_commandTable.push_back(&x278_additiveFlinch);\n  x40_commandTable.push_back(&x284_additiveReaction);\n  x40_commandTable.push_back(&x298_stopReaction);\n}\n\nvoid CBodyStateCmdMgr::DeliverCmd(const CBCLocomotionCmd& cmd) {\n  if (cmd.GetWeight() <= FLT_EPSILON)\n    return;\n  x3c_steeringSpeed += cmd.GetWeight();\n  x0_move += cmd.GetMoveVector() * cmd.GetWeight();\n  xc_face += cmd.GetFaceVector() * cmd.GetWeight();\n}\n\nvoid CBodyStateCmdMgr::BlendSteeringCmds() {\n  if (x3c_steeringSpeed > FLT_EPSILON) {\n    float stepMul = 1.f / x3c_steeringSpeed;\n    xc_face *= zeus::CVector3f(stepMul);\n\n    switch (x30_steeringMode) {\n    case ESteeringBlendMode::Normal:\n      x0_move *= zeus::CVector3f(stepMul);\n      break;\n    case ESteeringBlendMode::FullSpeed:\n      if (!zeus::close_enough(x0_move, zeus::skZero3f, 0.0001f)) {\n        x0_move.normalize();\n        x0_move *= zeus::CVector3f(x38_steeringSpeedMax);\n      }\n      break;\n    case ESteeringBlendMode::Clamped:\n      x0_move *= zeus::CVector3f(stepMul);\n      if (!zeus::close_enough(x0_move, zeus::skZero3f, 0.0001f)) {\n        if (x0_move.magnitude() < x34_steeringSpeedMin)\n          x0_move = x0_move.normalized() * x34_steeringSpeedMin;\n        else if (x0_move.magnitude() > x38_steeringSpeedMax)\n          x0_move = x0_move.normalized() * x38_steeringSpeedMax;\n      }\n      break;\n    default:\n      break;\n    }\n  }\n}\n\nvoid CBodyStateCmdMgr::Reset() {\n  x0_move = zeus::skZero3f;\n  xc_face = zeus::skZero3f;\n  x18_target = zeus::skZero3f;\n  x3c_steeringSpeed = 0.f;\n  xb4_deliveredCmdMask = 0;\n}\n\nvoid CBodyStateCmdMgr::ClearLocomotionCmds() {\n  x0_move = zeus::skZero3f;\n  xc_face = zeus::skZero3f;\n  x3c_steeringSpeed = 0.f;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CBodyStateCmdMgr.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Character/CharacterCommon.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\n\nclass CBodyStateCmd {\n  EBodyStateCmd x4_cmd;\n\npublic:\n  virtual ~CBodyStateCmd() = default;\n  constexpr explicit CBodyStateCmd(EBodyStateCmd cmd) : x4_cmd(cmd) {}\n  constexpr EBodyStateCmd GetCommandId() const { return x4_cmd; }\n};\n\nclass CBCMeleeAttackCmd : public CBodyStateCmd {\n  pas::ESeverity x8_severity = pas::ESeverity::Invalid;\n  zeus::CVector3f xc_targetPos;\n  bool x18_hasTargetPos = false;\n\npublic:\n  constexpr explicit CBCMeleeAttackCmd() : CBodyStateCmd(EBodyStateCmd::MeleeAttack) {}\n  constexpr explicit CBCMeleeAttackCmd(pas::ESeverity severity)\n  : CBodyStateCmd(EBodyStateCmd::MeleeAttack), x8_severity(severity) {}\n  constexpr explicit CBCMeleeAttackCmd(pas::ESeverity severity, const zeus::CVector3f& target)\n  : CBodyStateCmd(EBodyStateCmd::MeleeAttack), x8_severity(severity), xc_targetPos(target), x18_hasTargetPos(true) {}\n  constexpr pas::ESeverity GetAttackSeverity() const { return x8_severity; }\n  constexpr bool HasAttackTargetPos() const { return x18_hasTargetPos; }\n  constexpr const zeus::CVector3f& GetAttackTargetPos() const { return xc_targetPos; }\n};\n\nclass CBCProjectileAttackCmd : public CBodyStateCmd {\n  pas::ESeverity x8_severity = pas::ESeverity::Invalid;\n  zeus::CVector3f xc_target;\n  bool x18_blendAnims = false;\n\npublic:\n  constexpr explicit CBCProjectileAttackCmd() : CBodyStateCmd(EBodyStateCmd::ProjectileAttack) {}\n  constexpr explicit CBCProjectileAttackCmd(pas::ESeverity severity, const zeus::CVector3f& vec, bool b)\n  : CBodyStateCmd(EBodyStateCmd::ProjectileAttack), x8_severity(severity), xc_target(vec), x18_blendAnims(b) {}\n  constexpr pas::ESeverity GetAttackSeverity() const { return x8_severity; }\n  constexpr const zeus::CVector3f& GetTargetPosition() const { return xc_target; }\n  constexpr bool BlendTwoClosest() const { return x18_blendAnims; }\n};\n\nclass CBCStepCmd : public CBodyStateCmd {\n  pas::EStepDirection x8_dir = pas::EStepDirection::Invalid;\n  pas::EStepType xc_type = pas::EStepType::Normal;\n\npublic:\n  constexpr explicit CBCStepCmd() : CBodyStateCmd(EBodyStateCmd::Step) {}\n  constexpr explicit CBCStepCmd(pas::EStepDirection dir, pas::EStepType type)\n  : CBodyStateCmd(EBodyStateCmd::Step), x8_dir(dir), xc_type(type) {}\n  constexpr pas::EStepDirection GetStepDirection() const { return x8_dir; }\n  constexpr pas::EStepType GetStepType() const { return xc_type; }\n};\n\nclass CBCJumpCmd : public CBodyStateCmd {\n  pas::EJumpType x8_type = pas::EJumpType::Normal;\n  zeus::CVector3f xc_waypoint1;\n  zeus::CVector3f x18_waypoint2;\n  bool x24_24_wallJump : 1 = false;\n  bool x24_25_startInJumpLoop : 1 = false;\n\npublic:\n  constexpr explicit CBCJumpCmd() : CBodyStateCmd(EBodyStateCmd::Jump) {}\n  constexpr explicit CBCJumpCmd(const zeus::CVector3f& wp1, pas::EJumpType type, bool startInLoop = false)\n  : CBodyStateCmd(EBodyStateCmd::Jump), x8_type(type), xc_waypoint1(wp1), x24_25_startInJumpLoop{startInLoop} {}\n  constexpr explicit CBCJumpCmd(const zeus::CVector3f& wp1, const zeus::CVector3f& wp2, pas::EJumpType type)\n  : CBodyStateCmd(EBodyStateCmd::Jump), x8_type(type), xc_waypoint1(wp1), x18_waypoint2(wp2), x24_24_wallJump{true} {}\n  constexpr pas::EJumpType GetJumpType() const { return x8_type; }\n  constexpr const zeus::CVector3f& GetJumpTarget() const { return xc_waypoint1; }\n  constexpr const zeus::CVector3f& GetSecondJumpTarget() const { return x18_waypoint2; }\n  constexpr bool IsWallJump() const { return x24_24_wallJump; }\n  constexpr bool StartInJumpLoop() const { return x24_25_startInJumpLoop; }\n};\n\nclass CBCGenerateCmd : public CBodyStateCmd {\n  pas::EGenerateType x8_type = pas::EGenerateType::Invalid;\n  zeus::CVector3f xc_targetPos;\n  s32 x18_animId = -1;\n  bool x1c_24_targetTransform : 1 = false;\n  bool x1c_25_overrideAnim : 1 = false;\n\npublic:\n  constexpr explicit CBCGenerateCmd() : CBodyStateCmd(EBodyStateCmd::Generate) {}\n  constexpr explicit CBCGenerateCmd(pas::EGenerateType type) : CBodyStateCmd(EBodyStateCmd::Generate), x8_type(type) {}\n  constexpr explicit CBCGenerateCmd(pas::EGenerateType type, s32 animId)\n  : CBodyStateCmd(EBodyStateCmd::Generate), x8_type(type), x18_animId(animId), x1c_25_overrideAnim{animId != -1} {}\n  constexpr explicit CBCGenerateCmd(pas::EGenerateType type, const zeus::CVector3f& vec, bool targetTransform = false,\n                                    bool overrideAnim = false)\n  : CBodyStateCmd(EBodyStateCmd::Generate)\n  , x8_type(type)\n  , xc_targetPos(vec)\n  , x1c_24_targetTransform{targetTransform}\n  , x1c_25_overrideAnim{overrideAnim} {}\n  constexpr pas::EGenerateType GetGenerateType() const { return x8_type; }\n  constexpr const zeus::CVector3f& GetExitTargetPos() const { return xc_targetPos; }\n  constexpr bool HasExitTargetPos() const { return x1c_24_targetTransform; }\n  constexpr s32 GetSpecialAnimId() const { return x18_animId; }\n  constexpr bool UseSpecialAnimId() const { return x1c_25_overrideAnim; }\n};\n\nclass CBCKnockBackCmd : public CBodyStateCmd {\n  zeus::CVector3f x8_dir;\n  pas::ESeverity x14_severity = pas::ESeverity::Invalid;\n\npublic:\n  constexpr explicit CBCKnockBackCmd() : CBodyStateCmd(EBodyStateCmd::KnockBack) {}\n  constexpr explicit CBCKnockBackCmd(const zeus::CVector3f& vec, pas::ESeverity severity)\n  : CBodyStateCmd(EBodyStateCmd::KnockBack), x8_dir(vec), x14_severity(severity) {}\n  constexpr const zeus::CVector3f& GetHitDirection() const { return x8_dir; }\n  constexpr pas::ESeverity GetHitSeverity() const { return x14_severity; }\n};\n\nclass CBCHurledCmd : public CBodyStateCmd {\n  zeus::CVector3f x8_direction;\n  zeus::CVector3f x14_launchVel;\n  bool x20_startInKnockLoop = false;\n\npublic:\n  constexpr explicit CBCHurledCmd() : CBodyStateCmd(EBodyStateCmd::Hurled) {}\n  constexpr explicit CBCHurledCmd(const zeus::CVector3f& dir, const zeus::CVector3f& launchVel,\n                                  bool startInLoop = false)\n  : CBodyStateCmd(EBodyStateCmd::Hurled)\n  , x8_direction(dir)\n  , x14_launchVel(launchVel)\n  , x20_startInKnockLoop(startInLoop) {}\n  constexpr const zeus::CVector3f& GetHitDirection() const { return x8_direction; }\n  constexpr const zeus::CVector3f& GetLaunchVelocity() const { return x14_launchVel; }\n  constexpr bool GetSkipLaunchState() const { return x20_startInKnockLoop; }\n  constexpr void SetSkipLaunchState(bool s) { x20_startInKnockLoop = s; }\n};\n\nclass CBCGetupCmd : public CBodyStateCmd {\n  pas::EGetupType x8_type = pas::EGetupType::Invalid;\n\npublic:\n  constexpr explicit CBCGetupCmd() : CBodyStateCmd(EBodyStateCmd::Getup) {}\n  constexpr explicit CBCGetupCmd(pas::EGetupType type) : CBodyStateCmd(EBodyStateCmd::Getup), x8_type(type) {}\n  constexpr pas::EGetupType GetGetupType() const { return x8_type; }\n};\n\nclass CBCLoopReactionCmd : public CBodyStateCmd {\n  pas::EReactionType x8_type = pas::EReactionType::Invalid;\n\npublic:\n  constexpr explicit CBCLoopReactionCmd() : CBodyStateCmd(EBodyStateCmd::LoopReaction) {}\n  constexpr explicit CBCLoopReactionCmd(pas::EReactionType type)\n  : CBodyStateCmd(EBodyStateCmd::LoopReaction), x8_type(type) {}\n  constexpr pas::EReactionType GetReactionType() const { return x8_type; }\n};\n\nclass CBCLoopHitReactionCmd : public CBodyStateCmd {\n  pas::EReactionType x8_type = pas::EReactionType::Invalid;\n\npublic:\n  constexpr explicit CBCLoopHitReactionCmd() : CBodyStateCmd(EBodyStateCmd::LoopHitReaction) {}\n  constexpr explicit CBCLoopHitReactionCmd(pas::EReactionType type)\n  : CBodyStateCmd(EBodyStateCmd::LoopHitReaction), x8_type(type) {}\n  constexpr pas::EReactionType GetReactionType() const { return x8_type; }\n};\n\nclass CBCKnockDownCmd : public CBodyStateCmd {\n  zeus::CVector3f x8_dir;\n  pas::ESeverity x14_severity = pas::ESeverity::Invalid;\n\npublic:\n  constexpr explicit CBCKnockDownCmd() : CBodyStateCmd(EBodyStateCmd::KnockDown) {}\n  constexpr explicit CBCKnockDownCmd(const zeus::CVector3f& vec, pas::ESeverity severity)\n  : CBodyStateCmd(EBodyStateCmd::KnockDown), x8_dir(vec), x14_severity(severity) {}\n  constexpr const zeus::CVector3f& GetHitDirection() const { return x8_dir; }\n  constexpr pas::ESeverity GetHitSeverity() const { return x14_severity; }\n};\n\nclass CBCSlideCmd : public CBodyStateCmd {\n  pas::ESlideType x8_type = pas::ESlideType::Invalid;\n  zeus::CVector3f xc_dir;\n\npublic:\n  constexpr explicit CBCSlideCmd() : CBodyStateCmd(EBodyStateCmd::Slide) {}\n  constexpr explicit CBCSlideCmd(pas::ESlideType type, const zeus::CVector3f& dir)\n  : CBodyStateCmd(EBodyStateCmd::Slide), x8_type(type), xc_dir(dir) {}\n  constexpr pas::ESlideType GetSlideType() const { return x8_type; }\n  constexpr const zeus::CVector3f& GetSlideDirection() const { return xc_dir; }\n};\n\nclass CBCScriptedCmd : public CBodyStateCmd {\n  s32 x8_anim = -1;\n  bool xc_24_loopAnim : 1 = false;\n  bool xc_25_timedLoop : 1 = false;\n  float x10_loopDur = 0.f;\n\npublic:\n  constexpr explicit CBCScriptedCmd() : CBodyStateCmd(EBodyStateCmd::Scripted) {}\n  constexpr explicit CBCScriptedCmd(int i, bool b1, bool b2, float f)\n  : CBodyStateCmd(EBodyStateCmd::Scripted), x8_anim(i), xc_24_loopAnim{b1}, xc_25_timedLoop{b2}, x10_loopDur(f) {}\n  constexpr s32 GetAnimId() const { return x8_anim; }\n  constexpr bool IsLooped() const { return xc_24_loopAnim; }\n  constexpr bool GetUseLoopDuration() const { return xc_25_timedLoop; }\n  constexpr float GetLoopDuration() const { return x10_loopDur; }\n};\n\nclass CBCCoverCmd : public CBodyStateCmd {\n  pas::ECoverDirection x8_dir = pas::ECoverDirection::Invalid;\n  zeus::CVector3f xc_targetPos;\n  zeus::CVector3f x18_alignDir;\n\npublic:\n  constexpr explicit CBCCoverCmd() : CBodyStateCmd(EBodyStateCmd::Cover) {}\n  constexpr explicit CBCCoverCmd(pas::ECoverDirection dir, const zeus::CVector3f& v1, const zeus::CVector3f& v2)\n  : CBodyStateCmd(EBodyStateCmd::Cover), x8_dir(dir), xc_targetPos(v1), x18_alignDir(v2) {}\n  constexpr pas::ECoverDirection GetDirection() const { return x8_dir; }\n  constexpr const zeus::CVector3f& GetTarget() const { return xc_targetPos; }\n  constexpr const zeus::CVector3f& GetAlignDirection() const { return x18_alignDir; }\n};\n\nclass CBCWallHangCmd : public CBodyStateCmd {\n  TUniqueId x8_wpId = kInvalidUniqueId;\n\npublic:\n  constexpr explicit CBCWallHangCmd() : CBodyStateCmd(EBodyStateCmd::WallHang) {}\n  constexpr explicit CBCWallHangCmd(TUniqueId uid) : CBodyStateCmd(EBodyStateCmd::WallHang), x8_wpId(uid) {}\n  constexpr TUniqueId GetTarget() const { return x8_wpId; }\n};\n\nclass CBCAdditiveAimCmd : public CBodyStateCmd {\npublic:\n  constexpr explicit CBCAdditiveAimCmd() : CBodyStateCmd(EBodyStateCmd::AdditiveAim) {}\n};\n\nclass CBCAdditiveFlinchCmd : public CBodyStateCmd {\n  float x8_weight = 1.f;\n\npublic:\n  constexpr explicit CBCAdditiveFlinchCmd() : CBodyStateCmd(EBodyStateCmd::AdditiveFlinch) {}\n  constexpr explicit CBCAdditiveFlinchCmd(float f) : CBodyStateCmd(EBodyStateCmd::AdditiveFlinch), x8_weight(f) {}\n  constexpr float GetWeight() const { return x8_weight; }\n};\n\nclass CBCAdditiveReactionCmd : public CBodyStateCmd {\n  float x8_weight = 1.f;\n  pas::EAdditiveReactionType xc_type = pas::EAdditiveReactionType::Invalid;\n  bool x10_active = false;\n\npublic:\n  constexpr explicit CBCAdditiveReactionCmd() : CBodyStateCmd(EBodyStateCmd::AdditiveReaction) {}\n  constexpr explicit CBCAdditiveReactionCmd(pas::EAdditiveReactionType type, float weight, bool active)\n  : CBodyStateCmd(EBodyStateCmd::AdditiveReaction), x8_weight(weight), xc_type(type), x10_active(active) {}\n  constexpr pas::EAdditiveReactionType GetType() const { return xc_type; }\n  constexpr float GetWeight() const { return x8_weight; }\n  constexpr bool GetIsActive() const { return x10_active; }\n};\n\nclass CBCLoopAttackCmd : public CBodyStateCmd {\n  pas::ELoopAttackType x8_type = pas::ELoopAttackType::Invalid;\n  bool xc_waitForAnimOver = false;\n\npublic:\n  constexpr explicit CBCLoopAttackCmd() : CBodyStateCmd(EBodyStateCmd::LoopAttack) {}\n  constexpr explicit CBCLoopAttackCmd(pas::ELoopAttackType type, bool waitForAnimOver = false)\n  : CBodyStateCmd(EBodyStateCmd::LoopAttack), x8_type(type), xc_waitForAnimOver(waitForAnimOver) {}\n  constexpr pas::ELoopAttackType GetAttackType() const { return x8_type; }\n  constexpr bool WaitForAnimOver() const { return xc_waitForAnimOver; }\n};\n\nclass CBCTauntCmd : public CBodyStateCmd {\n  pas::ETauntType x8_type = pas::ETauntType::Invalid;\n\npublic:\n  constexpr explicit CBCTauntCmd() : CBodyStateCmd(EBodyStateCmd::Taunt) {}\n  constexpr explicit CBCTauntCmd(pas::ETauntType type) : CBodyStateCmd(EBodyStateCmd::Taunt), x8_type(type) {}\n  constexpr pas::ETauntType GetTauntType() const { return x8_type; }\n};\n\nclass CBCLocomotionCmd {\n  zeus::CVector3f x0_move;\n  zeus::CVector3f xc_face;\n  float x18_weight;\n\npublic:\n  constexpr explicit CBCLocomotionCmd(const zeus::CVector3f& move, const zeus::CVector3f& face, float weight)\n  : x0_move(move), xc_face(face), x18_weight(weight) {}\n  constexpr const zeus::CVector3f& GetMoveVector() const { return x0_move; }\n  constexpr const zeus::CVector3f& GetFaceVector() const { return xc_face; }\n  constexpr float GetWeight() const { return x18_weight; }\n};\n\nenum class ESteeringBlendMode { Normal, FullSpeed, Clamped };\n\nclass CBodyStateCmdMgr {\n  zeus::CVector3f x0_move;\n  zeus::CVector3f xc_face;\n  zeus::CVector3f x18_target;\n  zeus::CVector3f x24_additiveTarget;\n  ESteeringBlendMode x30_steeringMode = ESteeringBlendMode::Normal;\n  float x34_steeringSpeedMin = 0.f;\n  float x38_steeringSpeedMax = 1.f;\n  float x3c_steeringSpeed = 0.f;\n  rstl::reserved_vector<CBodyStateCmd*, 28> x40_commandTable;\n  u32 xb4_deliveredCmdMask = 0;\n  CBCGetupCmd xb8_getup;\n  CBCStepCmd xc4_step;\n  CBodyStateCmd xd4_die{EBodyStateCmd::Die};\n  CBCKnockDownCmd xdc_knockDown;\n  CBCKnockBackCmd xf4_knockBack;\n  CBCMeleeAttackCmd x10c_meleeAttack;\n  CBCProjectileAttackCmd x128_projectileAttack;\n  CBCLoopAttackCmd x144_loopAttack;\n  CBCLoopReactionCmd x154_loopReaction;\n  CBCLoopHitReactionCmd x160_loopHitReaction;\n  CBodyStateCmd x16c_exitState{EBodyStateCmd::ExitState};\n  CBodyStateCmd x174_leanFromCover{EBodyStateCmd::LeanFromCover};\n  CBodyStateCmd x17c_nextState{EBodyStateCmd::NextState};\n  CBodyStateCmd x184_maintainVelocity{EBodyStateCmd::MaintainVelocity};\n  CBCGenerateCmd x18c_generate;\n  CBCHurledCmd x1ac_hurled;\n  CBCJumpCmd x1d0_jump;\n  CBCSlideCmd x1f8_slide;\n  CBCTauntCmd x210_taunt;\n  CBCScriptedCmd x21c_scripted;\n  CBCCoverCmd x230_cover;\n  CBCWallHangCmd x254_wallHang;\n  CBodyStateCmd x260_locomotion{EBodyStateCmd::Locomotion};\n  CBodyStateCmd x268_additiveIdle{EBodyStateCmd::AdditiveIdle};\n  CBCAdditiveAimCmd x270_additiveAim;\n  CBCAdditiveFlinchCmd x278_additiveFlinch;\n  CBCAdditiveReactionCmd x284_additiveReaction;\n  CBodyStateCmd x298_stopReaction{EBodyStateCmd::StopReaction};\n  void DeliverCmd(EBodyStateCmd cmd) { xb4_deliveredCmdMask |= (1 << int(cmd)); }\n\npublic:\n  CBodyStateCmdMgr();\n  void DeliverCmd(const CBodyStateCmd& cmd) {\n    *x40_commandTable[int(cmd.GetCommandId())] = cmd;\n    DeliverCmd(cmd.GetCommandId());\n  }\n  void DeliverCmd(const CBCGetupCmd& cmd) {\n    xb8_getup = cmd;\n    DeliverCmd(EBodyStateCmd::Getup);\n  }\n  void DeliverCmd(const CBCStepCmd& cmd) {\n    xc4_step = cmd;\n    DeliverCmd(EBodyStateCmd::Step);\n  }\n  void DeliverCmd(const CBCKnockDownCmd& cmd) {\n    xdc_knockDown = cmd;\n    DeliverCmd(EBodyStateCmd::KnockDown);\n  }\n  void DeliverCmd(const CBCKnockBackCmd& cmd) {\n    xf4_knockBack = cmd;\n    DeliverCmd(EBodyStateCmd::KnockBack);\n  }\n  void DeliverCmd(const CBCMeleeAttackCmd& cmd) {\n    x10c_meleeAttack = cmd;\n    DeliverCmd(EBodyStateCmd::MeleeAttack);\n  }\n  void DeliverCmd(const CBCProjectileAttackCmd& cmd) {\n    x128_projectileAttack = cmd;\n    DeliverCmd(EBodyStateCmd::ProjectileAttack);\n  }\n  void DeliverCmd(const CBCLoopAttackCmd& cmd) {\n    x144_loopAttack = cmd;\n    DeliverCmd(EBodyStateCmd::LoopAttack);\n  }\n  void DeliverCmd(const CBCLoopReactionCmd& cmd) {\n    x154_loopReaction = cmd;\n    DeliverCmd(EBodyStateCmd::LoopReaction);\n  }\n  void DeliverCmd(const CBCLoopHitReactionCmd& cmd) {\n    x160_loopHitReaction = cmd;\n    DeliverCmd(EBodyStateCmd::LoopHitReaction);\n  }\n  void DeliverCmd(const CBCGenerateCmd& cmd) {\n    x18c_generate = cmd;\n    DeliverCmd(EBodyStateCmd::Generate);\n  }\n  void DeliverCmd(const CBCHurledCmd& cmd) {\n    x1ac_hurled = cmd;\n    DeliverCmd(EBodyStateCmd::Hurled);\n  }\n  void DeliverCmd(const CBCJumpCmd& cmd) {\n    x1d0_jump = cmd;\n    DeliverCmd(EBodyStateCmd::Jump);\n  }\n  void DeliverCmd(const CBCSlideCmd& cmd) {\n    x1f8_slide = cmd;\n    DeliverCmd(EBodyStateCmd::Slide);\n  }\n  void DeliverCmd(const CBCTauntCmd& cmd) {\n    x210_taunt = cmd;\n    DeliverCmd(EBodyStateCmd::Taunt);\n  }\n  void DeliverCmd(const CBCScriptedCmd& cmd) {\n    x21c_scripted = cmd;\n    DeliverCmd(EBodyStateCmd::Scripted);\n  }\n  void DeliverCmd(const CBCCoverCmd& cmd) {\n    x230_cover = cmd;\n    DeliverCmd(EBodyStateCmd::Cover);\n  }\n  void DeliverCmd(const CBCWallHangCmd& cmd) {\n    x254_wallHang = cmd;\n    DeliverCmd(EBodyStateCmd::WallHang);\n  }\n  void DeliverCmd(const CBCAdditiveAimCmd& cmd) {\n    x270_additiveAim = cmd;\n    DeliverCmd(EBodyStateCmd::AdditiveAim);\n  }\n  void DeliverCmd(const CBCAdditiveFlinchCmd& cmd) {\n    x278_additiveFlinch = cmd;\n    DeliverCmd(EBodyStateCmd::AdditiveFlinch);\n  }\n  void DeliverCmd(const CBCAdditiveReactionCmd& cmd) {\n    x284_additiveReaction = cmd;\n    DeliverCmd(EBodyStateCmd::AdditiveReaction);\n  }\n  void DeliverCmd(const CBCLocomotionCmd& cmd);\n  void DeliverFaceVector(const zeus::CVector3f& f) { xc_face = f; }\n  void DeliverTargetVector(const zeus::CVector3f& t) { x18_target = t; }\n  void DeliverAdditiveTargetVector(const zeus::CVector3f& t) { x24_additiveTarget = t; }\n  void SetSteeringBlendSpeed(float s) { x3c_steeringSpeed = s; }\n  void SetSteeringBlendMode(ESteeringBlendMode m) { x30_steeringMode = m; }\n  void SetSteeringSpeedRange(float rmin, float rmax) {\n    x34_steeringSpeedMin = rmin;\n    x38_steeringSpeedMax = rmax;\n  }\n  void BlendSteeringCmds();\n  void Reset();\n  void ClearLocomotionCmds();\n\n  CBodyStateCmd* GetCmd(EBodyStateCmd cmd) {\n    if ((xb4_deliveredCmdMask & (1U << u32(cmd))) != 0) {\n      return x40_commandTable[size_t(cmd)];\n    }\n    return nullptr;\n  }\n  const CBodyStateCmd* GetCmd(EBodyStateCmd cmd) const {\n    if ((xb4_deliveredCmdMask & (1U << u32(cmd))) != 0) {\n      return x40_commandTable[size_t(cmd)];\n    }\n    return nullptr;\n  }\n\n  const zeus::CVector3f& GetMoveVector() const { return x0_move; }\n  const zeus::CVector3f& GetFaceVector() const { return xc_face; }\n  const zeus::CVector3f& GetTargetVector() const { return x18_target; }\n  const zeus::CVector3f& GetAdditiveTargetVector() const { return x24_additiveTarget; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CBodyStateInfo.cpp",
    "content": "#include \"Runtime/Character/CBodyStateInfo.hpp\"\n\n#include \"Runtime/Character/CBodyController.hpp\"\n#include \"Runtime/World/CActor.hpp\"\n\nnamespace metaforce {\n\nCBodyStateInfo::CBodyStateInfo(CActor& actor, EBodyType type) {\n  x34_24_changeLocoAtEndOfAnimOnly = false;\n  const CPASDatabase& pasDatabase = actor.GetModelData()->GetAnimationData()->GetCharacterInfo().GetPASDatabase();\n  for (size_t i = 0; i < pasDatabase.GetNumAnimStates(); ++i) {\n    const CPASAnimState* state = pasDatabase.GetAnimStateByIndex(i);\n    std::unique_ptr<CBodyState> bs;\n\n    switch (type) {\n    case EBodyType::BiPedal:\n      bs = SetupBiPedalBodyStates(state->GetStateId(), actor);\n      break;\n    case EBodyType::Restricted:\n    default:\n      bs = SetupRestrictedBodyStates(state->GetStateId(), actor);\n      break;\n    case EBodyType::Flyer:\n      bs = SetupFlyerBodyStates(state->GetStateId(), actor);\n      break;\n    case EBodyType::Pitchable:\n      bs = SetupPitchableBodyStates(state->GetStateId(), actor);\n      break;\n    case EBodyType::WallWalker:\n      bs = SetupWallWalkerBodyStates(state->GetStateId(), actor);\n      break;\n    case EBodyType::NewFlyer:\n      bs = SetupNewFlyerBodyStates(state->GetStateId(), actor);\n      break;\n    case EBodyType::RestrictedFlyer:\n      bs = SetupRestrictedFlyerBodyStates(state->GetStateId(), actor);\n      break;\n    }\n\n    if (bs)\n      x0_stateMap[pas::EAnimationState(state->GetStateId())] = std::move(bs);\n  }\n\n  x1c_additiveStates.reserve(4);\n  x1c_additiveStates.emplace_back(pas::EAnimationState::AdditiveIdle, std::make_unique<CABSIdle>());\n  x1c_additiveStates.emplace_back(pas::EAnimationState::AdditiveAim, std::make_unique<CABSAim>());\n  x1c_additiveStates.emplace_back(pas::EAnimationState::AdditiveFlinch, std::make_unique<CABSFlinch>());\n  x1c_additiveStates.emplace_back(pas::EAnimationState::AdditiveReaction, std::make_unique<CABSReaction>());\n}\n\nstd::unique_ptr<CBodyState> CBodyStateInfo::SetupRestrictedFlyerBodyStates(pas::EAnimationState stateId,\n                                                                           CActor& actor) const {\n  switch (stateId) {\n  case pas::EAnimationState::Fall:\n    return std::make_unique<CBSFall>();\n  case pas::EAnimationState::Getup:\n    return std::make_unique<CBSGetup>();\n  case pas::EAnimationState::LieOnGround:\n    return std::make_unique<CBSLieOnGround>(actor);\n  case pas::EAnimationState::Step:\n    return std::make_unique<CBSStep>();\n  case pas::EAnimationState::Death:\n    return std::make_unique<CBSDie>();\n  case pas::EAnimationState::Locomotion:\n    return std::make_unique<CBSRestrictedFlyerLocomotion>(actor);\n  case pas::EAnimationState::KnockBack:\n    return std::make_unique<CBSKnockBack>();\n  case pas::EAnimationState::MeleeAttack:\n    return std::make_unique<CBSAttack>();\n  case pas::EAnimationState::ProjectileAttack:\n    return std::make_unique<CBSProjectileAttack>();\n  case pas::EAnimationState::LoopAttack:\n    return std::make_unique<CBSLoopAttack>();\n  case pas::EAnimationState::Turn:\n    return std::make_unique<CBSTurn>();\n  case pas::EAnimationState::LoopReaction:\n    return std::make_unique<CBSLoopReaction>();\n  case pas::EAnimationState::GroundHit:\n    return std::make_unique<CBSGroundHit>();\n  case pas::EAnimationState::Generate:\n    return std::make_unique<CBSGenerate>();\n  case pas::EAnimationState::Jump:\n    return std::make_unique<CBSJump>();\n  case pas::EAnimationState::Hurled:\n    return std::make_unique<CBSHurled>();\n  case pas::EAnimationState::Slide:\n    return std::make_unique<CBSSlide>();\n  case pas::EAnimationState::Taunt:\n    return std::make_unique<CBSTaunt>();\n  case pas::EAnimationState::Scripted:\n    return std::make_unique<CBSScripted>();\n  default:\n    return {};\n  }\n}\n\nstd::unique_ptr<CBodyState> CBodyStateInfo::SetupNewFlyerBodyStates(pas::EAnimationState stateId, CActor& actor) const {\n  switch (stateId) {\n  case pas::EAnimationState::Fall:\n    return std::make_unique<CBSFall>();\n  case pas::EAnimationState::Getup:\n    return std::make_unique<CBSGetup>();\n  case pas::EAnimationState::LieOnGround:\n    return std::make_unique<CBSLieOnGround>(actor);\n  case pas::EAnimationState::Step:\n    return std::make_unique<CBSStep>();\n  case pas::EAnimationState::Death:\n    return std::make_unique<CBSDie>();\n  case pas::EAnimationState::Locomotion:\n    return std::make_unique<CBSNewFlyerLocomotion>(actor);\n  case pas::EAnimationState::KnockBack:\n    return std::make_unique<CBSKnockBack>();\n  case pas::EAnimationState::MeleeAttack:\n    return std::make_unique<CBSAttack>();\n  case pas::EAnimationState::ProjectileAttack:\n    return std::make_unique<CBSProjectileAttack>();\n  case pas::EAnimationState::LoopAttack:\n    return std::make_unique<CBSLoopAttack>();\n  case pas::EAnimationState::Turn:\n    return std::make_unique<CBSTurn>();\n  case pas::EAnimationState::LoopReaction:\n    return std::make_unique<CBSLoopReaction>();\n  case pas::EAnimationState::GroundHit:\n    return std::make_unique<CBSGroundHit>();\n  case pas::EAnimationState::Generate:\n    return std::make_unique<CBSGenerate>();\n  case pas::EAnimationState::Jump:\n    return std::make_unique<CBSJump>();\n  case pas::EAnimationState::Hurled:\n    return std::make_unique<CBSHurled>();\n  case pas::EAnimationState::Slide:\n    return std::make_unique<CBSSlide>();\n  case pas::EAnimationState::Taunt:\n    return std::make_unique<CBSTaunt>();\n  case pas::EAnimationState::Scripted:\n    return std::make_unique<CBSScripted>();\n  default:\n    return {};\n  }\n}\n\nstd::unique_ptr<CBodyState> CBodyStateInfo::SetupWallWalkerBodyStates(pas::EAnimationState stateId,\n                                                                      CActor& actor) const {\n  switch (stateId) {\n  case pas::EAnimationState::Fall:\n    return std::make_unique<CBSFall>();\n  case pas::EAnimationState::Getup:\n    return std::make_unique<CBSGetup>();\n  case pas::EAnimationState::LieOnGround:\n    return std::make_unique<CBSLieOnGround>(actor);\n  case pas::EAnimationState::Step:\n    return std::make_unique<CBSStep>();\n  case pas::EAnimationState::Death:\n    return std::make_unique<CBSDie>();\n  case pas::EAnimationState::Locomotion:\n    return std::make_unique<CBSWallWalkerLocomotion>(actor);\n  case pas::EAnimationState::KnockBack:\n    return std::make_unique<CBSKnockBack>();\n  case pas::EAnimationState::MeleeAttack:\n    return std::make_unique<CBSAttack>();\n  case pas::EAnimationState::ProjectileAttack:\n    return std::make_unique<CBSProjectileAttack>();\n  case pas::EAnimationState::LoopAttack:\n    return std::make_unique<CBSLoopAttack>();\n  case pas::EAnimationState::Turn:\n    return std::make_unique<CBSFlyerTurn>();\n  case pas::EAnimationState::LoopReaction:\n    return std::make_unique<CBSLoopReaction>();\n  case pas::EAnimationState::GroundHit:\n    return std::make_unique<CBSGroundHit>();\n  case pas::EAnimationState::Generate:\n    return std::make_unique<CBSGenerate>();\n  case pas::EAnimationState::Jump:\n    return std::make_unique<CBSJump>();\n  case pas::EAnimationState::Hurled:\n    return std::make_unique<CBSHurled>();\n  case pas::EAnimationState::Slide:\n    return std::make_unique<CBSSlide>();\n  case pas::EAnimationState::Taunt:\n    return std::make_unique<CBSTaunt>();\n  case pas::EAnimationState::Scripted:\n    return std::make_unique<CBSScripted>();\n  default:\n    return {};\n  }\n}\n\nstd::unique_ptr<CBodyState> CBodyStateInfo::SetupPitchableBodyStates(pas::EAnimationState stateId,\n                                                                     CActor& actor) const {\n  switch (stateId) {\n  case pas::EAnimationState::Fall:\n    return std::make_unique<CBSFall>();\n  case pas::EAnimationState::Getup:\n    return std::make_unique<CBSGetup>();\n  case pas::EAnimationState::LieOnGround:\n    return std::make_unique<CBSLieOnGround>(actor);\n  case pas::EAnimationState::Step:\n    return std::make_unique<CBSStep>();\n  case pas::EAnimationState::Death:\n    return std::make_unique<CBSDie>();\n  case pas::EAnimationState::Locomotion:\n    return std::make_unique<CBSFlyerLocomotion>(actor, true);\n  case pas::EAnimationState::KnockBack:\n    return std::make_unique<CBSKnockBack>();\n  case pas::EAnimationState::MeleeAttack:\n    return std::make_unique<CBSAttack>();\n  case pas::EAnimationState::ProjectileAttack:\n    return std::make_unique<CBSProjectileAttack>();\n  case pas::EAnimationState::LoopAttack:\n    return std::make_unique<CBSLoopAttack>();\n  case pas::EAnimationState::Turn:\n    return std::make_unique<CBSFlyerTurn>();\n  case pas::EAnimationState::LoopReaction:\n    return std::make_unique<CBSLoopReaction>();\n  case pas::EAnimationState::GroundHit:\n    return std::make_unique<CBSGroundHit>();\n  case pas::EAnimationState::Generate:\n    return std::make_unique<CBSGenerate>();\n  case pas::EAnimationState::Jump:\n    return std::make_unique<CBSJump>();\n  case pas::EAnimationState::Hurled:\n    return std::make_unique<CBSHurled>();\n  case pas::EAnimationState::Slide:\n    return std::make_unique<CBSSlide>();\n  case pas::EAnimationState::Taunt:\n    return std::make_unique<CBSTaunt>();\n  case pas::EAnimationState::Scripted:\n    return std::make_unique<CBSScripted>();\n  default:\n    return {};\n  }\n}\n\nstd::unique_ptr<CBodyState> CBodyStateInfo::SetupFlyerBodyStates(pas::EAnimationState stateId, CActor& actor) const {\n  switch (stateId) {\n  case pas::EAnimationState::Fall:\n    return std::make_unique<CBSFall>();\n  case pas::EAnimationState::Getup:\n    return std::make_unique<CBSGetup>();\n  case pas::EAnimationState::LieOnGround:\n    return std::make_unique<CBSLieOnGround>(actor);\n  case pas::EAnimationState::Step:\n    return std::make_unique<CBSStep>();\n  case pas::EAnimationState::Death:\n    return std::make_unique<CBSDie>();\n  case pas::EAnimationState::Locomotion:\n    return std::make_unique<CBSFlyerLocomotion>(actor, false);\n  case pas::EAnimationState::KnockBack:\n    return std::make_unique<CBSKnockBack>();\n  case pas::EAnimationState::MeleeAttack:\n    return std::make_unique<CBSAttack>();\n  case pas::EAnimationState::ProjectileAttack:\n    return std::make_unique<CBSProjectileAttack>();\n  case pas::EAnimationState::LoopAttack:\n    return std::make_unique<CBSLoopAttack>();\n  case pas::EAnimationState::Turn:\n    return std::make_unique<CBSFlyerTurn>();\n  case pas::EAnimationState::LoopReaction:\n    return std::make_unique<CBSLoopReaction>();\n  case pas::EAnimationState::GroundHit:\n    return std::make_unique<CBSGroundHit>();\n  case pas::EAnimationState::Generate:\n    return std::make_unique<CBSGenerate>();\n  case pas::EAnimationState::Jump:\n    return std::make_unique<CBSJump>();\n  case pas::EAnimationState::Hurled:\n    return std::make_unique<CBSHurled>();\n  case pas::EAnimationState::Slide:\n    return std::make_unique<CBSSlide>();\n  case pas::EAnimationState::Taunt:\n    return std::make_unique<CBSTaunt>();\n  case pas::EAnimationState::Scripted:\n    return std::make_unique<CBSScripted>();\n  default:\n    return {};\n  }\n}\n\nstd::unique_ptr<CBodyState> CBodyStateInfo::SetupRestrictedBodyStates(pas::EAnimationState stateId,\n                                                                      CActor& actor) const {\n  switch (stateId) {\n  case pas::EAnimationState::Fall:\n    return std::make_unique<CBSFall>();\n  case pas::EAnimationState::Getup:\n    return std::make_unique<CBSGetup>();\n  case pas::EAnimationState::LieOnGround:\n    return std::make_unique<CBSLieOnGround>(actor);\n  case pas::EAnimationState::Step:\n    return std::make_unique<CBSStep>();\n  case pas::EAnimationState::Death:\n    return std::make_unique<CBSDie>();\n  case pas::EAnimationState::Locomotion:\n    return std::make_unique<CBSRestrictedLocomotion>(actor);\n  case pas::EAnimationState::KnockBack:\n    return std::make_unique<CBSKnockBack>();\n  case pas::EAnimationState::MeleeAttack:\n    return std::make_unique<CBSAttack>();\n  case pas::EAnimationState::ProjectileAttack:\n    return std::make_unique<CBSProjectileAttack>();\n  case pas::EAnimationState::LoopAttack:\n    return std::make_unique<CBSLoopAttack>();\n  case pas::EAnimationState::Turn:\n    return std::make_unique<CBSTurn>();\n  case pas::EAnimationState::LoopReaction:\n    return std::make_unique<CBSLoopReaction>();\n  case pas::EAnimationState::GroundHit:\n    return std::make_unique<CBSGroundHit>();\n  case pas::EAnimationState::Generate:\n    return std::make_unique<CBSGenerate>();\n  case pas::EAnimationState::Jump:\n    return std::make_unique<CBSJump>();\n  case pas::EAnimationState::Hurled:\n    return std::make_unique<CBSHurled>();\n  case pas::EAnimationState::Slide:\n    return std::make_unique<CBSSlide>();\n  case pas::EAnimationState::Taunt:\n    return std::make_unique<CBSTaunt>();\n  case pas::EAnimationState::Scripted:\n    return std::make_unique<CBSScripted>();\n  case pas::EAnimationState::Cover:\n    return std::make_unique<CBSCover>();\n  default:\n    return {};\n  }\n}\n\nstd::unique_ptr<CBodyState> CBodyStateInfo::SetupBiPedalBodyStates(pas::EAnimationState stateId, CActor& actor) const {\n  switch (stateId) {\n  case pas::EAnimationState::Fall:\n    return std::make_unique<CBSFall>();\n  case pas::EAnimationState::Getup:\n    return std::make_unique<CBSGetup>();\n  case pas::EAnimationState::LieOnGround:\n    return std::make_unique<CBSLieOnGround>(actor);\n  case pas::EAnimationState::Step:\n    return std::make_unique<CBSStep>();\n  case pas::EAnimationState::Death:\n    return std::make_unique<CBSDie>();\n  case pas::EAnimationState::Locomotion:\n    return std::make_unique<CBSBiPedLocomotion>(actor);\n  case pas::EAnimationState::KnockBack:\n    return std::make_unique<CBSKnockBack>();\n  case pas::EAnimationState::MeleeAttack:\n    return std::make_unique<CBSAttack>();\n  case pas::EAnimationState::ProjectileAttack:\n    return std::make_unique<CBSProjectileAttack>();\n  case pas::EAnimationState::LoopAttack:\n    return std::make_unique<CBSLoopAttack>();\n  case pas::EAnimationState::Turn:\n    return std::make_unique<CBSTurn>();\n  case pas::EAnimationState::LoopReaction:\n    return std::make_unique<CBSLoopReaction>();\n  case pas::EAnimationState::GroundHit:\n    return std::make_unique<CBSGroundHit>();\n  case pas::EAnimationState::Generate:\n    return std::make_unique<CBSGenerate>();\n  case pas::EAnimationState::Jump:\n    return std::make_unique<CBSJump>();\n  case pas::EAnimationState::Hurled:\n    return std::make_unique<CBSHurled>();\n  case pas::EAnimationState::Slide:\n    return std::make_unique<CBSSlide>();\n  case pas::EAnimationState::Taunt:\n    return std::make_unique<CBSTaunt>();\n  case pas::EAnimationState::Scripted:\n    return std::make_unique<CBSScripted>();\n  case pas::EAnimationState::Cover:\n    return std::make_unique<CBSCover>();\n  case pas::EAnimationState::WallHang:\n    return std::make_unique<CBSWallHang>();\n  default:\n    return {};\n  }\n}\n\nfloat CBodyStateInfo::GetLocomotionSpeed(pas::ELocomotionAnim anim) const {\n  auto search = x0_stateMap.find(pas::EAnimationState::Locomotion);\n  if (search != x0_stateMap.cend() && search->second && x18_bodyController) {\n    const CBSLocomotion& bs = static_cast<const CBSLocomotion&>(*search->second);\n    return bs.GetLocomotionSpeed(x18_bodyController->GetLocomotionType(), anim);\n  }\n  return 0.f;\n}\n\nfloat CBodyStateInfo::GetMaxSpeed() const {\n  float ret = GetLocomotionSpeed(pas::ELocomotionAnim::Run);\n  if (std::fabs(ret) < 0.00001f) {\n    for (int i = 0; i < 8; ++i) {\n      float tmp = GetLocomotionSpeed(pas::ELocomotionAnim(i));\n      if (tmp > ret)\n        ret = tmp;\n    }\n  }\n  return ret;\n}\n\nCBodyState* CBodyStateInfo::GetCurrentState() {\n  auto search = x0_stateMap.find(x14_state);\n  if (search == x0_stateMap.end())\n    return nullptr;\n  return search->second.get();\n}\n\nconst CBodyState* CBodyStateInfo::GetCurrentState() const {\n  auto search = x0_stateMap.find(x14_state);\n  if (search == x0_stateMap.end())\n    return nullptr;\n  return search->second.get();\n}\n\nvoid CBodyStateInfo::SetState(pas::EAnimationState s) {\n  auto search = x0_stateMap.find(s);\n  if (search == x0_stateMap.end())\n    return;\n  x14_state = s;\n}\n\nCAdditiveBodyState* CBodyStateInfo::GetCurrentAdditiveState() {\n  for (auto& state : x1c_additiveStates) {\n    if (x2c_additiveState == state.first)\n      return state.second.get();\n  }\n  return nullptr;\n}\n\nvoid CBodyStateInfo::SetAdditiveState(pas::EAnimationState s) {\n  for (auto& state : x1c_additiveStates) {\n    if (s == state.first) {\n      x2c_additiveState = s;\n      return;\n    }\n  }\n}\n\nbool CBodyStateInfo::ApplyHeadTracking() const {\n  if (x14_state == pas::EAnimationState::Invalid)\n    return false;\n  return GetCurrentState()->ApplyHeadTracking();\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CBodyStateInfo.hpp",
    "content": "#pragma once\n\n#include <map>\n#include <memory>\n#include <utility>\n#include <vector>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Character/CAdditiveBodyState.hpp\"\n#include \"Runtime/Character/CBodyState.hpp\"\n#include \"Runtime/Character/CharacterCommon.hpp\"\n\nnamespace metaforce {\nclass CActor;\n\nclass CBodyStateInfo {\n  friend class CBodyController;\n  std::map<pas::EAnimationState, std::unique_ptr<CBodyState>> x0_stateMap;\n  pas::EAnimationState x14_state = pas::EAnimationState::Invalid;\n  CBodyController* x18_bodyController = nullptr;\n  std::vector<std::pair<pas::EAnimationState, std::unique_ptr<CAdditiveBodyState>>> x1c_additiveStates;\n  pas::EAnimationState x2c_additiveState = pas::EAnimationState::AdditiveIdle;\n  float x30_maxPitch = 0.f;\n  bool x34_24_changeLocoAtEndOfAnimOnly;\n  std::unique_ptr<CBodyState> SetupRestrictedFlyerBodyStates(pas::EAnimationState stateId, CActor& actor) const;\n  std::unique_ptr<CBodyState> SetupNewFlyerBodyStates(pas::EAnimationState stateId, CActor& actor) const;\n  std::unique_ptr<CBodyState> SetupWallWalkerBodyStates(pas::EAnimationState stateId, CActor& actor) const;\n  std::unique_ptr<CBodyState> SetupPitchableBodyStates(pas::EAnimationState stateId, CActor& actor) const;\n  std::unique_ptr<CBodyState> SetupFlyerBodyStates(pas::EAnimationState stateId, CActor& actor) const;\n  std::unique_ptr<CBodyState> SetupRestrictedBodyStates(pas::EAnimationState stateId, CActor& actor) const;\n  std::unique_ptr<CBodyState> SetupBiPedalBodyStates(pas::EAnimationState stateId, CActor& actor) const;\n\npublic:\n  CBodyStateInfo(CActor& actor, EBodyType type);\n  float GetLocomotionSpeed(pas::ELocomotionAnim anim) const;\n  float GetMaxSpeed() const;\n  float GetMaximumPitch() const { return x30_maxPitch; }\n  void SetMaximumPitch(float pitch) { x30_maxPitch = pitch; }\n  bool GetLocoAnimChangeAtEndOfAnimOnly() const { return x34_24_changeLocoAtEndOfAnimOnly; }\n  void SetLocoAnimChangeAtEndOfAnimOnly(bool s) { x34_24_changeLocoAtEndOfAnimOnly = s; }\n  CBodyState* GetCurrentState();\n  const CBodyState* GetCurrentState() const;\n  pas::EAnimationState GetCurrentStateId() const { return x14_state; }\n  void SetState(pas::EAnimationState s);\n  CAdditiveBodyState* GetCurrentAdditiveState();\n  pas::EAnimationState GetCurrentAdditiveStateId() const { return x2c_additiveState; }\n  void SetAdditiveState(pas::EAnimationState s);\n  bool ApplyHeadTracking() const;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CBoneTracking.cpp",
    "content": "#include \"Runtime/Character/CBoneTracking.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Character/CAnimData.hpp\"\n#include \"Runtime/Character/CBodyController.hpp\"\n#include \"Runtime/Character/CHierarchyPoseBuilder.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCBoneTracking::CBoneTracking(const CAnimData& animData, std::string_view bone, float maxTrackingAngle, float angSpeed,\n                             EBoneTrackingFlags flags)\n: x14_segId(animData.GetCharLayoutInfo().GetSegIdFromString(bone))\n, x1c_maxTrackingAngle(maxTrackingAngle)\n, x20_angSpeed(angSpeed)\n, x36_26_noParent(True(flags & EBoneTrackingFlags::NoParent))\n, x36_27_noParentOrigin(True(flags & EBoneTrackingFlags::NoParentOrigin))\n, x36_28_noHorizontalAim(True(flags & EBoneTrackingFlags::NoHorizontalAim))\n, x36_29_parentIk(True(flags & EBoneTrackingFlags::ParentIk)) {}\n\nvoid CBoneTracking::Update(float dt) { x18_time += dt; }\n\nvoid CBoneTracking::PreRender(const CStateManager& mgr, CAnimData& animData, const zeus::CTransform& xf,\n                              const zeus::CVector3f& vec, const CBodyController& bodyController) {\n  TCastToPtr<CPatterned> patterned = bodyController.GetOwner();\n\n  PreRender(mgr, animData, xf, vec,\n            (bodyController.GetBodyStateInfo().ApplyHeadTracking() && patterned && patterned->ApplyBoneTracking()));\n}\n\nvoid CBoneTracking::PreRender(const CStateManager& mgr, CAnimData& animData, const zeus::CTransform& worldXf,\n                              const zeus::CVector3f& localOffsetScale, bool tracking) {\n  if (x14_segId == 0)\n    return;\n  CHierarchyPoseBuilder& pb = animData.PoseBuilder();\n  TCastToConstPtr<CActor> targetAct = mgr.GetObjectById(x34_target);\n  if (x36_24_active && tracking && (targetAct || x24_targetPosition)) {\n    x36_25_hasTrackedRotation = true;\n    const auto& layoutInfo = pb.CharLayoutInfo();\n    CSegId bone;\n    if (x36_26_noParent)\n      bone = x14_segId;\n    else\n      bone = layoutInfo->GetRootNode()->GetBoneMap()[x14_segId].x0_parentId;\n    zeus::CTransform parentBoneXf;\n    pb.BuildTransform(bone, parentBoneXf);\n    zeus::CVector3f pos = parentBoneXf.origin;\n    if (x36_27_noParentOrigin && !x36_26_noParent) {\n      zeus::CTransform thisBoneXf;\n      pb.BuildTransform(x14_segId, thisBoneXf);\n      pos = thisBoneXf.origin;\n    }\n    parentBoneXf.origin = pos * localOffsetScale;\n    zeus::CTransform finalXf = worldXf * parentBoneXf;\n    zeus::CVector3f localDir =\n        finalXf\n            .transposeRotate((targetAct ? targetAct->GetAimPosition(mgr, 0.f) : *x24_targetPosition) - finalXf.origin)\n            .normalized();\n    if (x36_28_noHorizontalAim)\n      localDir = zeus::CVector3f(0.f, localDir.toVec2f().magnitude(), localDir.z());\n    if (x36_29_parentIk) {\n      float negElev = -parentBoneXf.basis[1].z();\n      zeus::CVector3f ikBase(0.f, std::sqrt(1.f - negElev * negElev), negElev);\n      float angle = zeus::CVector3f::getAngleDiff(ikBase, localDir);\n      angle = std::min(angle, x1c_maxTrackingAngle);\n      localDir = zeus::CVector3f::slerp(ikBase, localDir, angle);\n    } else {\n      float angle = zeus::CVector3f::getAngleDiff(zeus::skForward, localDir);\n      angle = std::min(angle, x1c_maxTrackingAngle);\n      localDir = zeus::CVector3f::slerp(zeus::skForward, localDir, angle);\n    }\n    float angle = zeus::CVector3f::getAngleDiff(x0_curRotation.transform(zeus::skForward), localDir);\n    float clampedAngle = std::min(angle, x18_time * x20_angSpeed);\n    if (clampedAngle > 1.0e-05f) {\n      x0_curRotation = zeus::CQuaternion::slerpShort(\n          x0_curRotation, zeus::CQuaternion::lookAt(zeus::skForward, zeus::CUnitVector3f(localDir), 2.f * M_PIF),\n          clampedAngle / angle);\n    }\n    pb.GetTreeMap()[x14_segId].x4_rotation = x0_curRotation;\n    animData.MarkPoseDirty();\n  } else if (x36_25_hasTrackedRotation) {\n    zeus::CQuaternion qb = pb.GetTreeMap()[x14_segId].x4_rotation;\n    float angle =\n        zeus::CVector3f::getAngleDiff(x0_curRotation.transform(zeus::skForward), qb.transform(zeus::skForward));\n    float maxAngDelta = x18_time * x20_angSpeed;\n    float clampedAngle = std::min(angle, maxAngDelta);\n    if (clampedAngle > 0.5f * maxAngDelta) {\n      x0_curRotation = zeus::CQuaternion::slerpShort(x0_curRotation, qb, clampedAngle / angle);\n      pb.GetTreeMap()[x14_segId].x4_rotation = x0_curRotation;\n      animData.MarkPoseDirty();\n    } else {\n      x36_25_hasTrackedRotation = false;\n      x0_curRotation = qb;\n    }\n  } else {\n    x0_curRotation = pb.GetTreeMap()[x14_segId].x4_rotation;\n  }\n  x18_time = 0.f;\n}\n\nvoid CBoneTracking::SetActive(bool active) { x36_24_active = active; }\n\nvoid CBoneTracking::SetTarget(TUniqueId target) { x34_target = target; }\n\nvoid CBoneTracking::UnsetTarget() { x34_target = kInvalidUniqueId; }\n\nvoid CBoneTracking::SetTargetPosition(const zeus::CVector3f& targetPos) { x24_targetPosition = targetPos; }\n\nvoid CBoneTracking::SetNoHorizontalAim(bool b) { x36_28_noHorizontalAim = b; }\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CBoneTracking.hpp",
    "content": "#pragma once\n\n#include <optional>\n#include <string_view>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Character/CSegId.hpp\"\n\n#include <zeus/CQuaternion.hpp>\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CAnimData;\nclass CStateManager;\nclass CBodyController;\n\nenum class EBoneTrackingFlags { None = 0, NoParent = 1, NoParentOrigin = 2, NoHorizontalAim = 4, ParentIk = 8 };\nENABLE_BITWISE_ENUM(EBoneTrackingFlags)\n\nclass CBoneTracking {\n  zeus::CQuaternion x0_curRotation = zeus::CQuaternion();\n  float x10_ = 0.f;\n  CSegId x14_segId;\n  float x18_time = 0.f;\n  float x1c_maxTrackingAngle;\n  float x20_angSpeed;\n  std::optional<zeus::CVector3f> x24_targetPosition;\n  TUniqueId x34_target = kInvalidUniqueId;\n  bool x36_24_active : 1 = false;\n  bool x36_25_hasTrackedRotation : 1 = false;\n  bool x36_26_noParent : 1;\n  bool x36_27_noParentOrigin : 1;\n  bool x36_28_noHorizontalAim : 1;\n  bool x36_29_parentIk : 1;\n\npublic:\n  CBoneTracking(const CAnimData& animData, std::string_view bone, float maxTrackingAngle, float angSpeed,\n                EBoneTrackingFlags flags);\n  void Update(float dt);\n  void PreRender(const CStateManager& mgr, CAnimData& animData, const zeus::CTransform& xf, const zeus::CVector3f& vec,\n                 const CBodyController& bodyController);\n  void PreRender(const CStateManager& mgr, CAnimData& animData, const zeus::CTransform& worldXf,\n                 const zeus::CVector3f& localOffsetScale, bool tracking);\n  void SetActive(bool active);\n  void SetTarget(TUniqueId id);\n  void UnsetTarget();\n  void SetTargetPosition(const zeus::CVector3f& pos);\n  void SetNoHorizontalAim(bool b);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CBoolPOINode.cpp",
    "content": "#include \"Runtime/Character/CBoolPOINode.hpp\"\n\n#include \"Runtime/Character/CAnimSourceReader.hpp\"\n\nnamespace metaforce {\n\nCBoolPOINode::CBoolPOINode() : CPOINode(\"root\", EPOIType::EmptyBool, CCharAnimTime(), -1, false, 1.f, -1, 0) {}\n\nCBoolPOINode::CBoolPOINode(CInputStream& in) : CPOINode(in), x38_val(in.ReadBool()) {}\n\nCBoolPOINode CBoolPOINode::CopyNodeMinusStartTime(const CBoolPOINode& node, const CCharAnimTime& startTime) {\n  CBoolPOINode ret = node;\n  ret.x1c_time -= startTime;\n  return ret;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CBoolPOINode.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Character/CPOINode.hpp\"\n\nnamespace metaforce {\nclass IAnimSourceInfo;\n\nclass CBoolPOINode : public CPOINode {\n  bool x38_val = false;\n\npublic:\n  explicit CBoolPOINode();\n  explicit CBoolPOINode(CInputStream& in);\n  bool GetValue() const { return x38_val; }\n  static CBoolPOINode CopyNodeMinusStartTime(const CBoolPOINode& node, const CCharAnimTime& startTime);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CCharAnimTime.cpp",
    "content": "#include \"Runtime/Character/CCharAnimTime.hpp\"\n\n#include <algorithm>\n#include <cmath>\n\nnamespace metaforce {\n\nbool CCharAnimTime::EqualsZero() const {\n  if (x4_type == EType::ZeroIncreasing || x4_type == EType::ZeroSteady || x4_type == EType::ZeroDecreasing)\n    return true;\n\n  return x0_time == 0.f;\n}\n\nbool CCharAnimTime::EpsilonZero() const { return (std::fabs(x0_time) < 0.00001f); }\n\nbool CCharAnimTime::GreaterThanZero() const {\n  if (EqualsZero())\n    return false;\n  return x0_time > 0.f;\n}\n\nbool CCharAnimTime::operator==(const CCharAnimTime& other) const {\n  if (x4_type == EType::NonZero) {\n    if (other.x4_type == EType::NonZero)\n      return x0_time == other.x0_time;\n    return false;\n  }\n\n  if (EqualsZero()) {\n    if (other.EqualsZero()) {\n      int type = -1;\n      if (x4_type != EType::ZeroDecreasing) {\n        if (x4_type != EType::ZeroSteady)\n          type = 1;\n        else\n          type = 0;\n      }\n\n      int otherType = -1;\n      if (other.x4_type != EType::ZeroDecreasing) {\n        if (other.x4_type != EType::ZeroSteady)\n          otherType = 1;\n        else\n          otherType = 0;\n      }\n\n      return type == otherType;\n    }\n    return false;\n  }\n\n  if (other.x4_type == EType::Infinity)\n    return x0_time * other.x0_time > 0.f;\n\n  return false;\n}\n\nbool CCharAnimTime::operator!=(const CCharAnimTime& other) const { return !(*this == other); }\n\nbool CCharAnimTime::operator>=(const CCharAnimTime& other) const {\n  if (*this == other)\n    return true;\n\n  return *this > other;\n}\n\nbool CCharAnimTime::operator<=(const CCharAnimTime& other) const {\n  if (*this == other)\n    return true;\n\n  return *this < other;\n}\n\nbool CCharAnimTime::operator>(const CCharAnimTime& other) const { return (!(*this == other) && !(*this < other)); }\n\nbool CCharAnimTime::operator<(const CCharAnimTime& other) const {\n  if (x4_type == EType::NonZero) {\n    if (other.x4_type == EType::NonZero) {\n      return x0_time < other.x0_time;\n    }\n\n    return other.EqualsZero() ? x0_time < 0.f : other.x0_time > 0;\n  }\n\n  if (!EqualsZero()) {\n    if (other.x4_type == EType::Infinity) {\n      return x0_time >= 0 || other.x0_time <= 0.f;\n    }\n\n    return x0_time < 0.f;\n  }\n\n  if (!other.EqualsZero()) {\n    if (other.x4_type == EType::NonZero) {\n      return other.x0_time > 0.f;\n    }\n\n    return other.x0_time > 0.f;\n  }\n\n  int type = x4_type == EType::ZeroDecreasing ? -1 : x4_type == EType::ZeroSteady ? 0 : 1;\n  int otherType = other.x4_type == EType::ZeroDecreasing ? -1 : other.x4_type == EType::ZeroSteady ? 0 : 1;\n\n  return type < otherType;\n}\n\nCCharAnimTime& CCharAnimTime::operator*=(const CCharAnimTime& other) { return *this = *this * other; }\n\nCCharAnimTime& CCharAnimTime::operator+=(const CCharAnimTime& other) { return *this = *this + other; }\n\nCCharAnimTime CCharAnimTime::operator+(const CCharAnimTime& other) const {\n  if (x4_type == EType::Infinity && other.x4_type == EType::Infinity) {\n    if (other.x0_time != x0_time)\n      return {};\n    return *this;\n  } else if (x4_type == EType::Infinity)\n    return *this;\n  else if (other.x4_type == EType::Infinity)\n    return other;\n\n  if (!EqualsZero() || !other.EqualsZero())\n    return {x0_time + other.x0_time};\n\n  int type = -1;\n  if (x4_type != EType::ZeroDecreasing) {\n    type = x4_type == EType::ZeroSteady ? 0 : 1;\n  }\n\n  int otherType = -1;\n  if (other.x4_type != EType::ZeroDecreasing) {\n    otherType = other.x4_type == EType::ZeroSteady ? 0 : 1;\n  }\n\n  type += otherType;\n  otherType = std::max(-1, std::min(type, 1));\n\n  if (otherType == -1)\n    return {EType::ZeroDecreasing, 0.f};\n  else if (otherType == 0)\n    return {EType::ZeroSteady, 0.f};\n  else\n    return {EType::ZeroIncreasing, 0.f};\n}\n\nCCharAnimTime& CCharAnimTime::operator-=(const CCharAnimTime& other) { return *this = *this - other; }\n\nCCharAnimTime CCharAnimTime::operator-(const CCharAnimTime& other) const {\n  if (x4_type == EType::Infinity && other.x4_type == EType::Infinity) {\n    if (other.x0_time == x0_time)\n      return {};\n    return *this;\n  } else if (x4_type == EType::Infinity)\n    return *this;\n  else if (other.x4_type == EType::Infinity) {\n    return {EType::Infinity, -other.x0_time};\n  }\n\n  if (!EqualsZero() || !other.EqualsZero())\n    return {x0_time - other.x0_time};\n\n  int type = -1;\n  if (x4_type != EType::ZeroDecreasing) {\n    if (x4_type != EType::ZeroSteady)\n      type = 1;\n    else\n      type = 0;\n  }\n\n  int otherType = -1;\n  if (other.x4_type != EType::ZeroDecreasing) {\n    if (other.x4_type != EType::ZeroSteady)\n      otherType = 1;\n    else\n      otherType = 0;\n  }\n\n  type -= otherType;\n  if (type == -1)\n    return {EType::ZeroDecreasing, 0.f};\n  else if (type == 0)\n    return {EType::ZeroSteady, 0.f};\n  else\n    return {EType::ZeroIncreasing, 0.f};\n}\n\nCCharAnimTime CCharAnimTime::operator*(const CCharAnimTime& other) const {\n  if (x4_type == EType::Infinity && other.x4_type == EType::Infinity) {\n    if (other.x0_time != x0_time)\n      return {};\n    return *this;\n  } else if (x4_type == EType::Infinity)\n    return *this;\n  else if (other.x4_type == EType::Infinity)\n    return other;\n\n  if (!EqualsZero() || !other.EqualsZero())\n    return {x0_time * other.x0_time};\n\n  int type = -1;\n  if (x4_type != EType::ZeroDecreasing) {\n    if (x4_type != EType::ZeroSteady)\n      type = 1;\n    else\n      type = 0;\n  }\n\n  int otherType = -1;\n  if (other.x4_type != EType::ZeroDecreasing) {\n    if (other.x4_type != EType::ZeroSteady)\n      otherType = 1;\n    else\n      otherType = 0;\n  }\n\n  type += otherType;\n  otherType = std::max(-1, std::min(type, 1));\n\n  if (otherType == -1)\n    return {EType::ZeroDecreasing, 0.f};\n  else if (otherType == 0)\n    return {EType::ZeroSteady, 0.f};\n  else\n    return {EType::ZeroIncreasing, 0.f};\n}\n\nCCharAnimTime CCharAnimTime::operator*(const float& other) const {\n  if (other == 0.f)\n    return {};\n\n  if (!EqualsZero())\n    return {x0_time * other};\n\n  if (other > 0.f)\n    return *this;\n\n  if (x4_type == EType::ZeroDecreasing) {\n    return {EType::ZeroIncreasing, 0.f};\n  } else if (x4_type == EType::ZeroSteady) {\n    return {EType::ZeroSteady, 0.f};\n  } else {\n    return {EType::ZeroDecreasing, 0.f};\n  }\n}\n\nfloat CCharAnimTime::operator/(const CCharAnimTime& other) const {\n  if (other.EqualsZero())\n    return 0.f;\n\n  return x0_time / other.x0_time;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CCharAnimTime.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Streams/IOStreams.hpp\"\n\n#undef min\n#undef max\n\nnamespace metaforce {\n\nclass CCharAnimTime {\npublic:\n  enum class EType { NonZero, ZeroIncreasing, ZeroSteady, ZeroDecreasing, Infinity };\n\nprivate:\n  float x0_time = 0.f;\n  EType x4_type = EType::ZeroSteady;\n\npublic:\n  constexpr CCharAnimTime() = default;\n  constexpr CCharAnimTime(float time) : x0_time(time), x4_type(x0_time != 0.f ? EType::NonZero : EType::ZeroSteady) {}\n  constexpr CCharAnimTime(EType type, float t) : x0_time(t), x4_type(type) {}\n  explicit CCharAnimTime(CInputStream& in) : x0_time(in.ReadFloat()), x4_type(EType(in.ReadLong())) {}\n\n  static constexpr CCharAnimTime Infinity() { return {EType::Infinity, 1.0f}; }\n  float GetSeconds() const { return x0_time; }\n\n  bool EqualsZero() const;\n  bool EpsilonZero() const;\n  bool GreaterThanZero() const;\n  bool operator==(const CCharAnimTime& other) const;\n  bool operator!=(const CCharAnimTime& other) const;\n  bool operator>=(const CCharAnimTime& other) const;\n  bool operator<=(const CCharAnimTime& other) const;\n  bool operator>(const CCharAnimTime& other) const;\n  bool operator<(const CCharAnimTime& other) const;\n  CCharAnimTime& operator*=(const CCharAnimTime& other);\n  CCharAnimTime& operator+=(const CCharAnimTime& other);\n  CCharAnimTime operator+(const CCharAnimTime& other) const;\n  CCharAnimTime& operator-=(const CCharAnimTime& other);\n  CCharAnimTime operator-(const CCharAnimTime& other) const;\n  CCharAnimTime operator*(const CCharAnimTime& other) const;\n  CCharAnimTime operator*(const float& other) const;\n  float operator/(const CCharAnimTime& other) const;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CCharLayoutInfo.cpp",
    "content": "#include \"Runtime/Character/CCharLayoutInfo.hpp\"\n\n#include \"Runtime/CToken.hpp\"\n\nnamespace metaforce {\n\nzeus::CVector3f CCharLayoutInfo::GetFromParentUnrotated(const CSegId& id) const {\n  const CCharLayoutNode::Bone& bone = x0_node->GetBoneMap()[id];\n\n  if (x0_node->GetBoneMap().HasElement(bone.x0_parentId)) {\n    const CCharLayoutNode::Bone& parent = x0_node->GetBoneMap()[bone.x0_parentId];\n    return bone.x4_origin - parent.x4_origin;\n  } else {\n    return bone.x4_origin;\n  }\n}\n\nzeus::CVector3f CCharLayoutInfo::GetFromRootUnrotated(const CSegId& id) const {\n  const CCharLayoutNode::Bone& bone = x0_node->GetBoneMap()[id];\n  return bone.x4_origin;\n}\n\nCSegId CCharLayoutInfo::GetSegIdFromString(std::string_view name) const {\n  const auto it = x18_segIdMap.find(name);\n\n  if (it == x18_segIdMap.cend()) {\n    return {};\n  }\n\n  return it->second;\n}\n\nvoid CCharLayoutNode::Bone::read(CInputStream& in) {\n  x0_parentId = CSegId(in);\n  x4_origin = in.Get<zeus::CVector3f>();\n\n  const u32 chCount = in.ReadLong();\n  x10_children.reserve(chCount);\n  for (u32 i = 0; i < chCount; ++i) {\n    x10_children.emplace_back(in);\n  }\n}\n\nCCharLayoutNode::CCharLayoutNode(CInputStream& in) : x0_boneMap(in.ReadLong()) {\n  const u32 cap = x0_boneMap.GetCapacity();\n\n  for (u32 i = 0; i < cap; ++i) {\n    const u32 thisId = in.ReadLong();\n    Bone& bone = x0_boneMap[thisId];\n    bone.read(in);\n  }\n}\n\nCCharLayoutInfo::CCharLayoutInfo(CInputStream& in) : x0_node(std::make_shared<CCharLayoutNode>(in)), x8_segIdList(in) {\n  const u32 mapCount = in.ReadLong();\n\n  for (u32 i = 0; i < mapCount; ++i) {\n    std::string key = in.Get<std::string>();\n    x18_segIdMap.emplace(std::move(key), in);\n  }\n}\n\nCFactoryFnReturn FCharLayoutInfo(const SObjectTag&, CInputStream& in, const CVParamTransfer&,\n                                 CObjectReference* selfRef) {\n  return TToken<CCharLayoutInfo>::GetIObjObjectFor(std::make_unique<CCharLayoutInfo>(in));\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CCharLayoutInfo.hpp",
    "content": "#pragma once\n\n#include <map>\n#include <memory>\n#include <string>\n#include <vector>\n\n#include \"Runtime/CFactoryMgr.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/Character/CSegId.hpp\"\n#include \"Runtime/Character/CSegIdList.hpp\"\n#include \"Runtime/Character/TSegIdMap.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\n\nclass CCharLayoutNode {\npublic:\n  struct Bone {\n    CSegId x0_parentId;\n    zeus::CVector3f x4_origin;\n    std::vector<CSegId> x10_children;\n    void read(CInputStream& in);\n  };\n\nprivate:\n  TSegIdMap<Bone> x0_boneMap;\n\npublic:\n  explicit CCharLayoutNode(CInputStream& in);\n  const TSegIdMap<Bone>& GetBoneMap() const { return x0_boneMap; }\n};\n\nclass CCharLayoutInfo {\n  std::shared_ptr<CCharLayoutNode> x0_node;\n  CSegIdList x8_segIdList;\n  std::map<std::string, CSegId, std::less<>> x18_segIdMap;\n\npublic:\n  explicit CCharLayoutInfo(CInputStream& in);\n  const std::shared_ptr<CCharLayoutNode>& GetRootNode() const { return x0_node; }\n  const CSegIdList& GetSegIdList() const { return x8_segIdList; }\n  zeus::CVector3f GetFromParentUnrotated(const CSegId& id) const;\n  zeus::CVector3f GetFromRootUnrotated(const CSegId& id) const;\n  CSegId GetSegIdFromString(std::string_view name) const;\n};\n\nCFactoryFnReturn FCharLayoutInfo(const SObjectTag&, CInputStream&, const CVParamTransfer&, CObjectReference* selfRef);\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CCharacterFactory.cpp",
    "content": "#include \"Runtime/Character/CCharacterFactory.hpp\"\n\n#include \"Runtime/CRandom16.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Character/CAdditiveAnimPlayback.hpp\"\n#include \"Runtime/Character/CAnimCharacterSet.hpp\"\n#include \"Runtime/Character/CAnimData.hpp\"\n#include \"Runtime/Character/CAnimationDatabaseGame.hpp\"\n#include \"Runtime/Character/CAnimationManager.hpp\"\n#include \"Runtime/Character/CCharLayoutInfo.hpp\"\n#include \"Runtime/Character/CParticleGenInfo.hpp\"\n#include \"Runtime/Character/CPrimitive.hpp\"\n#include \"Runtime/Character/CTransitionDatabaseGame.hpp\"\n#include \"Runtime/Character/CTransitionManager.hpp\"\n#include \"Runtime/Graphics/CSkinnedModel.hpp\"\n\nnamespace metaforce {\n\nCFactoryFnReturn CCharacterFactory::CDummyFactory::Build(const SObjectTag& tag, const CVParamTransfer& params,\n                                                         CObjectReference* selfRef) {\n\n  const CCharacterInfo& charInfo = *params.GetOwnedObj<const CCharacterInfo*>();\n\n  switch (tag.type.toUint32()) {\n  case 0:\n    return TToken<CSkinnedModel>::GetIObjObjectFor(std::make_unique<CSkinnedModel>(\n        *g_SimplePool, charInfo.GetModelId(), charInfo.GetSkinRulesId(), charInfo.GetCharLayoutInfoId()));\n  case 1:\n    return TToken<CSkinnedModel>::GetIObjObjectFor(std::make_unique<CSkinnedModelWithAvgNormals>(\n        *g_SimplePool, charInfo.GetIceModelId(), charInfo.GetIceSkinRulesId(), charInfo.GetCharLayoutInfoId()));\n  default:\n    break;\n  }\n\n  return {};\n}\n\nvoid CCharacterFactory::CDummyFactory::BuildAsync(const SObjectTag& tag, const CVParamTransfer& parms,\n                                                  std::unique_ptr<IObj>* objOut, CObjectReference* selfRef) {\n  *objOut = Build(tag, parms, selfRef);\n}\n\nvoid CCharacterFactory::CDummyFactory::CancelBuild(const SObjectTag&) {}\n\nbool CCharacterFactory::CDummyFactory::CanBuild(const SObjectTag&) { return true; }\n\nconst SObjectTag* CCharacterFactory::CDummyFactory::GetResourceIdByName(std::string_view) const { return nullptr; }\n\nFourCC CCharacterFactory::CDummyFactory::GetResourceTypeById(CAssetId id) const { return {}; }\n\nvoid CCharacterFactory::CDummyFactory::EnumerateResources(const std::function<bool(const SObjectTag&)>& lambda) const {}\nvoid CCharacterFactory::CDummyFactory::EnumerateNamedResources(\n    const std::function<bool(std::string_view, const SObjectTag&)>& lambda) const {}\n\nu32 CCharacterFactory::CDummyFactory::ResourceSize(const metaforce::SObjectTag& tag) { return 0; }\n\nstd::shared_ptr<IDvdRequest> CCharacterFactory::CDummyFactory::LoadResourceAsync(const metaforce::SObjectTag& tag,\n                                                                                 void* target) {\n  return {};\n}\n\nstd::shared_ptr<IDvdRequest> CCharacterFactory::CDummyFactory::LoadResourcePartAsync(const metaforce::SObjectTag& tag,\n                                                                                     u32 off, u32 size, void* target) {\n  return {};\n}\n\nstd::unique_ptr<u8[]> CCharacterFactory::CDummyFactory::LoadResourceSync(const metaforce::SObjectTag& tag) {\n  return {};\n}\n\nstd::unique_ptr<u8[]> CCharacterFactory::CDummyFactory::LoadNewResourcePartSync(const metaforce::SObjectTag& tag,\n                                                                                u32 off, u32 size) {\n  return {};\n}\n\nstd::unique_ptr<CAnimData> CCharacterFactory::CreateCharacter(int charIdx, bool loop,\n                                                              const TLockedToken<CCharacterFactory>& factory,\n                                                              int defaultAnim) {\n  const CCharacterInfo& charInfo = x4_charInfoDB[charIdx];\n  const CVParamTransfer charParm(new TObjOwnerParam<const CCharacterInfo*>(&charInfo));\n\n  TToken<CSkinnedModel> skinnedModel = x70_cacheResPool.GetObj({FourCC(0u), charInfo.GetModelId()}, charParm);\n\n  std::optional<TToken<CSkinnedModelWithAvgNormals>> iceModel;\n  if (charInfo.GetIceModelId().IsValid() && charInfo.GetIceSkinRulesId().IsValid()) {\n    iceModel.emplace(x70_cacheResPool.GetObj({FourCC(1u), charInfo.GetIceModelId()}, charParm));\n  }\n\n  return std::make_unique<CAnimData>(x68_selfId, charInfo, defaultAnim, charIdx, loop, x14_charLayoutInfoDB[charIdx],\n                                     std::move(skinnedModel), iceModel, x24_sysContext, x28_animMgr, x2c_transMgr,\n                                     factory);\n}\n\nCAssetId CCharacterFactory::GetEventResourceIdForAnimResourceId(CAssetId id) const {\n  auto search = std::find_if(x58_animResources.cbegin(), x58_animResources.cend(),\n                             [&](const std::pair<CAssetId, CAssetId>& elem) -> bool { return id == elem.first; });\n  if (search == x58_animResources.cend())\n    return CAssetId();\n  return search->second;\n}\n\nconst CAdditiveAnimationInfo& CCharacterFactory::FindAdditiveInfo(s32 idx) const {\n  auto search = rstl::binary_find(x40_additiveInfo.cbegin(), x40_additiveInfo.cend(), idx,\n                                  [](const auto& anim) { return anim.first; });\n\n  if (search == x40_additiveInfo.cend())\n    return x50_defaultAdditiveInfo;\n  return search->second;\n}\n\nbool CCharacterFactory::HasAdditiveInfo(s32 idx) const {\n  auto search = rstl::binary_find(x40_additiveInfo.cbegin(), x40_additiveInfo.cend(), idx,\n                                  [](const auto& anim) { return anim.first; });\n  return search != x40_additiveInfo.cend();\n}\n\nstd::vector<CCharacterInfo> CCharacterFactory::GetCharacterInfoDB(const CAnimCharacterSet& ancs) {\n  std::vector<CCharacterInfo> ret;\n  const std::map<u32, CCharacterInfo>& charInfoMap = ancs.GetCharacterSet().GetCharacterInfoMap();\n  ret.reserve(charInfoMap.size());\n  for (const auto& charInfo : charInfoMap)\n    ret.push_back(charInfo.second);\n  return ret;\n}\n\nstd::vector<TLockedToken<CCharLayoutInfo>>\nCCharacterFactory::GetCharLayoutInfoDB(CSimplePool& store, const std::vector<CCharacterInfo>& chars) {\n  std::vector<TLockedToken<CCharLayoutInfo>> ret;\n  ret.reserve(chars.size());\n  for (const CCharacterInfo& charInfo : chars)\n    ret.push_back(store.GetObj({SBIG('CINF'), charInfo.GetCharLayoutInfoId()}));\n  return ret;\n}\n\nCCharacterFactory::CCharacterFactory(CSimplePool& store, const CAnimCharacterSet& ancs, CAssetId selfId)\n: x4_charInfoDB(GetCharacterInfoDB(ancs))\n, x14_charLayoutInfoDB(GetCharLayoutInfoDB(store, x4_charInfoDB))\n, x24_sysContext(std::make_shared<CAnimSysContext>(\n      TToken<CTransitionDatabaseGame>(std::make_unique<CTransitionDatabaseGame>(\n          ancs.GetAnimationSet().GetTransitions(), ancs.GetAnimationSet().GetHalfTransitions(),\n          ancs.GetAnimationSet().GetDefaultTransition())),\n      2334, store))\n, x28_animMgr(std::make_shared<CAnimationManager>(\n      TToken<CAnimationDatabaseGame>(std::make_unique<CAnimationDatabaseGame>(ancs.GetAnimationSet().GetAnimations())),\n      *x24_sysContext))\n, x2c_transMgr(std::make_shared<CTransitionManager>(*x24_sysContext))\n, x40_additiveInfo(ancs.GetAnimationSet().GetAdditiveInfo())\n, x50_defaultAdditiveInfo(ancs.GetAnimationSet().GetDefaultAdditiveInfo())\n, x58_animResources(ancs.GetAnimationSet().GetAnimResIds())\n, x68_selfId(selfId)\n, x70_cacheResPool(x6c_dummyFactory) {\n  std::vector<CPrimitive> primitives;\n  x28_animMgr->GetAnimationDatabase()->GetAllUniquePrimitives(primitives);\n  x30_animSourceDB.reserve(primitives.size());\n  for (const CPrimitive& prim : primitives) {\n    x30_animSourceDB.emplace_back(store.GetObj({SBIG('ANIM'), prim.GetAnimResId()}));\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CCharacterFactory.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <string_view>\n#include <utility>\n#include <vector>\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/IFactory.hpp\"\n#include \"Runtime/IObjFactory.hpp\"\n#include \"Runtime/Character/CAnimationSet.hpp\"\n#include \"Runtime/Character/CCharacterInfo.hpp\"\n\nnamespace metaforce {\nclass CAdditiveAnimationInfo;\nclass CAllFormatsAnimSource;\nclass CAnimCharacterSet;\nclass CAnimData;\nclass CAnimationManager;\nclass CCharLayoutInfo;\nclass CSimplePool;\nclass CTransitionDatabaseGame;\nclass CTransitionManager;\n\nclass CCharacterFactory : public IObjFactory {\npublic:\n  class CDummyFactory : public IFactory {\n  public:\n    CFactoryFnReturn Build(const SObjectTag&, const CVParamTransfer&, CObjectReference* selfRef) override;\n    void BuildAsync(const SObjectTag&, const CVParamTransfer&, std::unique_ptr<IObj>*,\n                    CObjectReference* selfRef) override;\n    void CancelBuild(const SObjectTag&) override;\n    bool CanBuild(const SObjectTag&) override;\n    const SObjectTag* GetResourceIdByName(std::string_view) const override;\n    FourCC GetResourceTypeById(CAssetId id) const override;\n\n    void EnumerateResources(const std::function<bool(const SObjectTag&)>& lambda) const override;\n    void EnumerateNamedResources(const std::function<bool(std::string_view, const SObjectTag&)>& lambda) const override;\n\n    u32 ResourceSize(const metaforce::SObjectTag& tag) override;\n    std::shared_ptr<IDvdRequest> LoadResourceAsync(const metaforce::SObjectTag& tag, void* target) override;\n    std::shared_ptr<IDvdRequest> LoadResourcePartAsync(const metaforce::SObjectTag& tag, u32 off, u32 size,\n                                                       void* target) override;\n    std::unique_ptr<u8[]> LoadResourceSync(const metaforce::SObjectTag& tag) override;\n    std::unique_ptr<u8[]> LoadNewResourcePartSync(const metaforce::SObjectTag& tag, u32 off, u32 size) override;\n  };\n\nprivate:\n  std::vector<CCharacterInfo> x4_charInfoDB;\n  std::vector<TLockedToken<CCharLayoutInfo>> x14_charLayoutInfoDB;\n  std::shared_ptr<CAnimSysContext> x24_sysContext;\n  std::shared_ptr<CAnimationManager> x28_animMgr;\n  std::shared_ptr<CTransitionManager> x2c_transMgr;\n  std::vector<TCachedToken<CAllFormatsAnimSource>> x30_animSourceDB;\n  std::vector<std::pair<u32, CAdditiveAnimationInfo>> x40_additiveInfo;\n  CAdditiveAnimationInfo x50_defaultAdditiveInfo;\n  std::vector<std::pair<CAssetId, CAssetId>> x58_animResources;\n  CAssetId x68_selfId;\n  CDummyFactory x6c_dummyFactory;\n  CSimplePool x70_cacheResPool;\n\n  static std::vector<CCharacterInfo> GetCharacterInfoDB(const CAnimCharacterSet& ancs);\n  static std::vector<TLockedToken<CCharLayoutInfo>> GetCharLayoutInfoDB(CSimplePool& store,\n                                                                        const std::vector<CCharacterInfo>& chars);\n\npublic:\n  CCharacterFactory(CSimplePool& store, const CAnimCharacterSet& ancs, CAssetId);\n\n  std::unique_ptr<CAnimData> CreateCharacter(int charIdx, bool loop, const TLockedToken<CCharacterFactory>& factory,\n                                             int defaultAnim);\n  CAssetId GetEventResourceIdForAnimResourceId(CAssetId animId) const;\n\n  const CCharacterInfo& GetCharInfo(int charIdx) const { return x4_charInfoDB[charIdx]; }\n  const CAdditiveAnimationInfo& FindAdditiveInfo(s32 idx) const;\n  bool HasAdditiveInfo(s32 idx) const;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CCharacterInfo.cpp",
    "content": "#include \"Runtime/Character/CCharacterInfo.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n\nnamespace metaforce {\n\nCCharacterInfo::CParticleResData::CParticleResData(CInputStream& in, u16 tableCount) {\n  const u32 partCount = in.ReadLong();\n  x0_part.reserve(partCount);\n  for (u32 i = 0; i < partCount; ++i) {\n    x0_part.emplace_back(in.Get<CAssetId>());\n  }\n\n  const u32 swhcCount = in.ReadLong();\n  x10_swhc.reserve(swhcCount);\n  for (u32 i = 0; i < swhcCount; ++i) {\n    x10_swhc.emplace_back(in.Get<CAssetId>());\n  }\n\n  const u32 unkCount = in.ReadLong();\n  x20_elsc.reserve(unkCount);\n  for (u32 i = 0; i < unkCount; ++i) {\n    x20_elsc.emplace_back(in.Get<CAssetId>());\n  }\n\n  if (tableCount > 5) {\n    const u32 elscCount = in.ReadLong();\n    x30_elsc.reserve(elscCount);\n    for (u32 i = 0; i < elscCount; ++i) {\n      x30_elsc.emplace_back(in.Get<CAssetId>());\n    }\n  }\n}\n\nstatic std::vector<std::pair<s32, std::pair<std::string, std::string>>> MakeAnimInfoVector(CInputStream& in) {\n  std::vector<std::pair<s32, std::pair<std::string, std::string>>> ret;\n  const u32 animInfoCount = in.ReadLong();\n  ret.reserve(animInfoCount);\n  for (u32 i = 0; i < animInfoCount; ++i) {\n    const s32 idx = in.ReadLong();\n    std::string a = in.Get<std::string>();\n    std::string b = in.Get<std::string>();\n    ret.emplace_back(idx, std::make_pair(std::move(a), std::move(b)));\n  }\n  return ret;\n}\n\nCCharacterInfo::CCharacterInfo(CInputStream& in)\n: x0_tableCount(in.ReadShort())\n, x4_name(in.Get<std::string>())\n, x14_cmdl(in)\n, x18_cskr(in)\n, x1c_cinf(in)\n, x20_animInfo(MakeAnimInfoVector(in))\n, x30_pasDatabase(in)\n, x44_partRes(in, x0_tableCount)\n, x84_unk(in.ReadLong()) {\n  if (x0_tableCount > 1) {\n    const u32 aabbCount = in.ReadLong();\n    x88_aabbs.reserve(aabbCount);\n    for (u32 i = 0; i < aabbCount; ++i) {\n      std::string name = in.Get<std::string>();\n      x88_aabbs.emplace_back(std::move(name), zeus::CAABox());\n      x88_aabbs.back().second = in.Get<zeus::CAABox>();\n    }\n  }\n\n  if (x0_tableCount > 2) {\n    const u32 effectCount = in.ReadLong();\n    x98_effects.reserve(effectCount);\n    for (u32 i = 0; i < effectCount; ++i) {\n      std::string name = in.Get<std::string>();\n      x98_effects.emplace_back(std::move(name), std::vector<CEffectComponent>());\n      std::vector<CEffectComponent>& comps = x98_effects.back().second;\n      const u32 compCount = in.ReadLong();\n      comps.reserve(compCount);\n      for (u32 j = 0; j < compCount; ++j) {\n        comps.emplace_back(in);\n      }\n    }\n  }\n\n  if (x0_tableCount > 3) {\n    xa8_cmdlOverlay = in.Get<CAssetId>();\n    xac_cskrOverlay = in.Get<CAssetId>();\n  }\n\n  if (x0_tableCount > 4) {\n    const u32 aidxCount = in.ReadLong();\n    xb0_animIdxs.reserve(aidxCount);\n    for (u32 i = 0; i < aidxCount; ++i) {\n      xb0_animIdxs.push_back(in.ReadLong());\n    }\n  }\n}\n\ns32 CCharacterInfo::GetAnimationIndex(std::string_view name) const {\n  for (const auto& pair : x20_animInfo) {\n    if (pair.second.second == name)\n      return pair.first;\n  }\n\n  return -1;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CCharacterInfo.hpp",
    "content": "#pragma once\n\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Character/CEffectComponent.hpp\"\n#include \"Runtime/Character/CPASDatabase.hpp\"\n\n#include <zeus/CAABox.hpp>\n\nnamespace metaforce {\n\nclass CCharacterInfo {\n  friend class CAnimData;\n\npublic:\n  struct CParticleResData {\n    std::vector<CAssetId> x0_part;\n    std::vector<CAssetId> x10_swhc;\n    std::vector<CAssetId> x20_elsc;\n    std::vector<CAssetId> x30_elsc;\n    CParticleResData(CInputStream& in, u16 tableCount);\n    CParticleResData(std::vector<CAssetId> part, std::vector<CAssetId> swhc, std::vector<CAssetId> elsc1,\n                     std::vector<CAssetId> elsc2)\n    : x0_part(std::move(part)), x10_swhc(std::move(swhc)), x20_elsc(std::move(elsc1)), x30_elsc(std::move(elsc2)) {}\n  };\n\nprivate:\n  u16 x0_tableCount;\n  std::string x4_name;\n  CAssetId x14_cmdl;\n  CAssetId x18_cskr;\n  CAssetId x1c_cinf;\n  std::vector<std::pair<s32, std::pair<std::string, std::string>>> x20_animInfo;\n  CPASDatabase x30_pasDatabase;\n  CParticleResData x44_partRes;\n  u32 x84_unk;\n  std::vector<std::pair<std::string, zeus::CAABox>> x88_aabbs;\n  std::vector<std::pair<std::string, std::vector<CEffectComponent>>> x98_effects;\n\n  CAssetId xa8_cmdlOverlay;\n  CAssetId xac_cskrOverlay;\n\n  std::vector<s32> xb0_animIdxs;\n\npublic:\n  explicit CCharacterInfo(CInputStream& in);\n\n  std::string_view GetCharacterName() const { return x4_name; }\n  CAssetId GetModelId() const { return x14_cmdl; }\n  CAssetId GetSkinRulesId() const { return x18_cskr; }\n  CAssetId GetCharLayoutInfoId() const { return x1c_cinf; }\n\n  const std::vector<std::pair<std::string, zeus::CAABox>>& GetAnimBBoxList() const { return x88_aabbs; }\n  const std::vector<std::pair<std::string, std::vector<CEffectComponent>>>& GetEffectList() const {\n    return x98_effects;\n  }\n\n  CAssetId GetIceModelId() const { return xa8_cmdlOverlay; }\n  CAssetId GetIceSkinRulesId() const { return xac_cskrOverlay; }\n\n  const CParticleResData& GetParticleResData() const { return x44_partRes; }\n  s32 GetAnimationIndex(s32 idx) const { return xb0_animIdxs.at(idx); }\n  const CPASDatabase& GetPASDatabase() const { return x30_pasDatabase; }\n\n  s32 GetAnimationIndex(std::string_view) const;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CCharacterSet.cpp",
    "content": "#include \"Runtime/Character/CCharacterSet.hpp\"\n\nnamespace metaforce {\n\nCCharacterSet::CCharacterSet(CInputStream& in) : x0_version(in.ReadShort()) {\n  u32 charCount = in.ReadLong();\n  for (u32 i = 0; i < charCount; ++i) {\n    u32 id = in.ReadLong();\n    x4_characters.emplace(id, in);\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CCharacterSet.hpp",
    "content": "#pragma once\n\n#include <map>\n\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/Character/CCharacterInfo.hpp\"\n\nnamespace metaforce {\n\nclass CCharacterSet {\n  u16 x0_version;\n  std::map<u32, CCharacterInfo> x4_characters;\n\npublic:\n  explicit CCharacterSet(CInputStream& in);\n  const std::map<u32, CCharacterInfo>& GetCharacterInfoMap() const { return x4_characters; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CEffectComponent.cpp",
    "content": "#include \"Runtime/Character/CEffectComponent.hpp\"\n\nnamespace metaforce {\n\nSObjectTag CEffectComponent::GetSObjectTagFromStream(CInputStream& in) { return in.Get<SObjectTag>(); }\n\nCEffectComponent::CEffectComponent(CInputStream& in) {\n  x0_name = in.Get<std::string>();\n  x10_tag = GetSObjectTagFromStream(in);\n  x18_boneName = in.Get<std::string>();\n  x28_scale = in.ReadFloat();\n  x2c_parentedMode = CParticleData::EParentedMode(in.ReadLong());\n  x30_flags = in.ReadLong();\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CEffectComponent.hpp",
    "content": "#pragma once\n\n#include <string>\n\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Character/CParticleData.hpp\"\n\nnamespace metaforce {\n\nclass CEffectComponent {\n  std::string x0_name;\n  SObjectTag x10_tag;\n  std::string x18_boneName;\n  float x28_scale;\n  CParticleData::EParentedMode x2c_parentedMode;\n  u32 x30_flags;\n  static SObjectTag GetSObjectTagFromStream(CInputStream& in);\n\npublic:\n  explicit CEffectComponent(CInputStream& in);\n\n  std::string_view GetComponentName() const { return x0_name; }\n  const SObjectTag& GetParticleTag() const { return x10_tag; }\n  std::string_view GetSegmentName() const { return x18_boneName; }\n  float GetScale() const { return x28_scale; }\n  CParticleData::EParentedMode GetParentedMode() const { return x2c_parentedMode; }\n  u32 GetFlags() const { return x30_flags; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CFBStreamedAnimReader.cpp",
    "content": "#include \"Runtime/Character/CFBStreamedAnimReader.hpp\"\n\n#include <algorithm>\n#include <cmath>\n#include <cstring>\n#include <memory>\n\n#include \"Runtime/Character/CSegIdList.hpp\"\n#include \"Runtime/Character/CSegStatementSet.hpp\"\n\nnamespace metaforce {\n\nvoid CFBStreamedAnimReaderTotals::Allocate(u32 chanCount) {\n  const u32 chan2 = chanCount * 2;\n  const u32 chan32 = chanCount * 32;\n\n  const size_t sz = chan32 + chanCount + chan2 + chan32;\n  x0_buffer = std::make_unique<u8[]>(sz);\n  x4_cumulativeInts32 = reinterpret_cast<s32*>(x0_buffer.get());\n  x8_hasTrans1 = reinterpret_cast<u8*>(x4_cumulativeInts32 + chanCount * 8);\n  xc_segIds2 = reinterpret_cast<u16*>(x8_hasTrans1 + chanCount);\n  x10_computedFloats32 = reinterpret_cast<float*>(xc_segIds2 + chanCount);\n}\n\nvoid CFBStreamedAnimReaderTotals::Initialize(const CFBStreamedCompression& source) {\n  x1c_curKey = 0;\n  x20_calculated = false;\n  const u8* chans = source.GetPerChannelHeaders();\n  u32 boneChanCount = *reinterpret_cast<const u32*>(chans);\n  chans += 4;\n\n  if (source.m_pc) {\n    for (unsigned b = 0; b < boneChanCount; ++b) {\n      xc_segIds2[b] = *reinterpret_cast<const u32*>(chans);\n      chans += 8;\n\n      s32* cumulativesOut = &x4_cumulativeInts32[8 * b];\n      const s32* cumulativesIn = reinterpret_cast<const s32*>(chans);\n      cumulativesOut[0] = 0;\n      cumulativesOut[1] = cumulativesIn[0] >> 8;\n      cumulativesOut[2] = cumulativesIn[1] >> 8;\n      cumulativesOut[3] = cumulativesIn[2] >> 8;\n      chans += 12;\n\n      u32 tCount = *reinterpret_cast<const u32*>(chans);\n      chans += 4;\n      if (tCount) {\n        x8_hasTrans1[b] = true;\n        const s32* cumulativesIn = reinterpret_cast<const s32*>(chans);\n        cumulativesOut[4] = cumulativesIn[0] >> 8;\n        cumulativesOut[5] = cumulativesIn[1] >> 8;\n        cumulativesOut[6] = cumulativesIn[2] >> 8;\n        chans += 12;\n      } else\n        x8_hasTrans1[b] = false;\n    }\n  } else {\n    for (unsigned b = 0; b < boneChanCount; ++b) {\n      xc_segIds2[b] = *reinterpret_cast<const u32*>(chans);\n      chans += 6;\n\n      s32* cumulativesOut = &x4_cumulativeInts32[8 * b];\n      cumulativesOut[0] = 0;\n      cumulativesOut[1] = *reinterpret_cast<const s16*>(chans);\n      cumulativesOut[2] = *reinterpret_cast<const s16*>(chans + 3);\n      cumulativesOut[3] = *reinterpret_cast<const s16*>(chans + 6);\n      chans += 9;\n\n      u16 tCount = *reinterpret_cast<const u16*>(chans);\n      chans += 2;\n      if (tCount) {\n        x8_hasTrans1[b] = true;\n        cumulativesOut[4] = *reinterpret_cast<const s16*>(chans);\n        cumulativesOut[5] = *reinterpret_cast<const s16*>(chans + 3);\n        cumulativesOut[6] = *reinterpret_cast<const s16*>(chans + 6);\n        chans += 9;\n      } else\n        x8_hasTrans1[b] = false;\n    }\n  }\n}\n\nCFBStreamedAnimReaderTotals::CFBStreamedAnimReaderTotals(const CFBStreamedCompression& source) {\n  const CFBStreamedCompression::Header& header = source.MainHeader();\n  x14_rotDiv = header.rotDiv;\n  x18_transMult = header.translationMult;\n\n  const u8* chans = source.GetPerChannelHeaders();\n  x24_boneChanCount = *reinterpret_cast<const u32*>(chans);\n  Allocate(x24_boneChanCount);\n  Initialize(source);\n}\n\nvoid CFBStreamedAnimReaderTotals::IncrementInto(CBitLevelLoader& loader, const CFBStreamedCompression& source,\n                                                CFBStreamedAnimReaderTotals& dest) {\n  dest.x20_calculated = false;\n\n  const u8* chans = source.GetPerChannelHeaders();\n  u32 boneChanCount = *reinterpret_cast<const u32*>(chans);\n  chans += 4;\n\n  if (source.m_pc) {\n    for (unsigned b = 0; b < boneChanCount; ++b) {\n      chans += 8;\n\n      const s32* cumulativesIn = &x4_cumulativeInts32[8 * b];\n      s32* cumulativesOut = &dest.x4_cumulativeInts32[8 * b];\n      const s32* qsIn = reinterpret_cast<const s32*>(chans);\n      cumulativesOut[0] = loader.LoadBool();\n      cumulativesOut[1] = cumulativesIn[1] + loader.LoadSigned(qsIn[0] & 0xff);\n      cumulativesOut[2] = cumulativesIn[2] + loader.LoadSigned(qsIn[1] & 0xff);\n      cumulativesOut[3] = cumulativesIn[3] + loader.LoadSigned(qsIn[2] & 0xff);\n      chans += 12;\n\n      u32 tCount = *reinterpret_cast<const u32*>(chans);\n      chans += 4;\n      if (tCount) {\n        const s32* qsIn = reinterpret_cast<const s32*>(chans);\n        cumulativesOut[4] = cumulativesIn[4] + loader.LoadSigned(qsIn[0] & 0xff);\n        cumulativesOut[5] = cumulativesIn[5] + loader.LoadSigned(qsIn[1] & 0xff);\n        cumulativesOut[6] = cumulativesIn[6] + loader.LoadSigned(qsIn[2] & 0xff);\n        chans += 12;\n      }\n    }\n  } else {\n    for (unsigned b = 0; b < boneChanCount; ++b) {\n      chans += 6;\n\n      const s32* cumulativesIn = &x4_cumulativeInts32[8 * b];\n      s32* cumulativesOut = &dest.x4_cumulativeInts32[8 * b];\n      cumulativesOut[0] = loader.LoadBool();\n      cumulativesOut[1] = cumulativesIn[1] + loader.LoadSigned(*reinterpret_cast<const u8*>(chans + 2));\n      cumulativesOut[2] = cumulativesIn[2] + loader.LoadSigned(*reinterpret_cast<const u8*>(chans + 5));\n      cumulativesOut[3] = cumulativesIn[3] + loader.LoadSigned(*reinterpret_cast<const u8*>(chans + 8));\n      chans += 9;\n\n      u16 tCount = *reinterpret_cast<const u16*>(chans);\n      chans += 2;\n      if (tCount) {\n        cumulativesOut[4] = cumulativesIn[4] + loader.LoadSigned(*reinterpret_cast<const u8*>(chans + 2));\n        cumulativesOut[5] = cumulativesIn[5] + loader.LoadSigned(*reinterpret_cast<const u8*>(chans + 5));\n        cumulativesOut[6] = cumulativesIn[6] + loader.LoadSigned(*reinterpret_cast<const u8*>(chans + 8));\n        chans += 9;\n      }\n    }\n  }\n\n  dest.x1c_curKey = x1c_curKey + 1;\n}\n\nvoid CFBStreamedAnimReaderTotals::CalculateDown() {\n  for (unsigned b = 0; b < x24_boneChanCount; ++b) {\n    const s32* cumulativesIn = &x4_cumulativeInts32[8 * b];\n    float* compOut = &x10_computedFloats32[8 * b];\n\n    float q = M_PIF / 2.f / float(x14_rotDiv);\n    compOut[1] = std::sin(cumulativesIn[1] * q);\n    compOut[2] = std::sin(cumulativesIn[2] * q);\n    compOut[3] = std::sin(cumulativesIn[3] * q);\n\n    compOut[0] =\n        std::sqrt(std::max(1.f - (compOut[1] * compOut[1] + compOut[2] * compOut[2] + compOut[3] * compOut[3]), 0.f));\n    if (cumulativesIn[0])\n      compOut[0] = -compOut[0];\n\n    if (x8_hasTrans1[b]) {\n      compOut[4] = cumulativesIn[4] * x18_transMult;\n      compOut[5] = cumulativesIn[5] * x18_transMult;\n      compOut[6] = cumulativesIn[6] * x18_transMult;\n    }\n  }\n  x20_calculated = true;\n}\n\nCFBStreamedPairOfTotals::CFBStreamedPairOfTotals(const TSubAnimTypeToken<CFBStreamedCompression>& source)\n: x0_source(source), xc_rotsAndOffs(source->xc_rotsAndOffs.get()), x14_a(*source), x3c_b(*source) {}\n\nvoid CFBStreamedPairOfTotals::SetTime(CBitLevelLoader& loader, const CCharAnimTime& time) {\n  /* Implementation is a bit different than original;\n   * T evaluated pre-emptively with key indices.\n   * CalculateDown is also called here as needed. */\n\n  const CFBStreamedCompression::Header& header = x0_source->MainHeader();\n  CCharAnimTime interval(header.interval);\n  const u32* timeBitmap = x0_source->GetTimes();\n  CCharAnimTime priorTime(0);\n  CCharAnimTime curTime(0);\n\n  int prior = -1;\n  int next = -1;\n  int cur = 0;\n  for (unsigned b = 0; b < timeBitmap[0]; ++b) {\n    int word = b / 32;\n    int bit = b % 32;\n    if ((timeBitmap[word + 1] >> bit) & 1) {\n      if (curTime <= time) {\n        prior = cur;\n        priorTime = curTime;\n      } else if (curTime > time) {\n        next = cur;\n        if (prior == -1) {\n          prior = cur;\n          priorTime = curTime;\n          x78_t = 0.f;\n        } else {\n          x78_t = (time - priorTime) / (curTime - priorTime);\n        }\n        break;\n      }\n      ++cur;\n    }\n    curTime += interval;\n  }\n\n  if (prior != -1 && u32(prior) < Prior().x1c_curKey) {\n    Prior().Initialize(*x0_source);\n    Next().Initialize(*x0_source);\n    loader.Reset();\n  }\n\n  if (prior != -1 && next == -1) {\n    next = prior;\n    x78_t = 1.f;\n  }\n  if (next != -1) {\n    while (u32(next) > Next().x1c_curKey) {\n      DoIncrement(loader);\n    }\n  }\n\n  if (!Prior().IsCalculated()) {\n    Prior().CalculateDown();\n  }\n  if (!Next().IsCalculated()) {\n    Next().CalculateDown();\n  }\n}\n\nvoid CFBStreamedPairOfTotals::DoIncrement(CBitLevelLoader& loader) {\n  x10_nextSel ^= 1;\n  Prior().IncrementInto(loader, *x0_source, Next());\n}\n\nu32 CBitLevelLoader::LoadUnsigned(u8 q) {\n  u32 byteCur = (m_bitIdx / 32) * 4;\n  u32 bitRem = m_bitIdx % 32;\n\n  /* Fill 32 bit buffer with region containing bits */\n  /* Make them least significant */\n  u32 tempBuf = *reinterpret_cast<const u32*>(m_data + byteCur) >> bitRem;\n\n  /* If this shift underflows the value, buffer the next 32 bits */\n  /* And tack onto shifted buffer */\n  if ((bitRem + q) > 32) {\n    u32 tempBuf2 = *reinterpret_cast<const u32*>(m_data + byteCur + 4);\n    tempBuf |= (tempBuf2 << (32 - bitRem));\n  }\n\n  /* Mask it */\n  u32 mask = (1 << q) - 1;\n  tempBuf &= mask;\n\n  /* Return delta value */\n  m_bitIdx += q;\n  return tempBuf;\n}\n\ns32 CBitLevelLoader::LoadSigned(u8 q) {\n  u32 byteCur = (m_bitIdx / 32) * 4;\n  u32 bitRem = m_bitIdx % 32;\n\n  /* Fill 32 bit buffer with region containing bits */\n  /* Make them least significant */\n  u32 tempBuf = *reinterpret_cast<const u32*>(m_data + byteCur) >> bitRem;\n\n  /* If this shift underflows the value, buffer the next 32 bits */\n  /* And tack onto shifted buffer */\n  if ((bitRem + q) > 32) {\n    u32 tempBuf2 = *reinterpret_cast<const u32*>(m_data + byteCur + 4);\n    tempBuf |= (tempBuf2 << (32 - bitRem));\n  }\n\n  /* Mask it */\n  u32 mask = (1 << q) - 1;\n  tempBuf &= mask;\n\n  /* Sign extend */\n  u32 sign = (tempBuf >> (q - 1)) & 0x1;\n  if (sign)\n    tempBuf |= ~0u << q;\n\n  /* Return delta value */\n  m_bitIdx += q;\n  return s32(tempBuf);\n}\n\nbool CBitLevelLoader::LoadBool() {\n  u32 byteCur = (m_bitIdx / 32) * 4;\n  u32 bitRem = m_bitIdx % 32;\n\n  /* Fill 32 bit buffer with region containing bits */\n  /* Make them least significant */\n  u32 tempBuf = *reinterpret_cast<const u32*>(m_data + byteCur) >> bitRem;\n\n  /* That's it */\n  m_bitIdx += 1;\n  return tempBuf & 0x1;\n}\n\nCSegIdToIndexConverter::CSegIdToIndexConverter(const CFBStreamedAnimReaderTotals& totals) {\n  std::fill(std::begin(x0_indices), std::end(x0_indices), -1);\n  for (u32 b = 0; b < totals.x24_boneChanCount; ++b) {\n    u16 segId = totals.xc_segIds2[b];\n    if (segId >= 100)\n      continue;\n    x0_indices[segId] = b;\n  }\n}\n\nCFBStreamedAnimReader::CFBStreamedAnimReader(const TSubAnimTypeToken<CFBStreamedCompression>& source,\n                                             const CCharAnimTime& time)\n: CAnimSourceReaderBase(std::make_unique<TAnimSourceInfo<CFBStreamedCompression>>(source), {})\n, x54_source(source)\n, x64_steadyStateInfo(source->IsLooping(), source->GetAnimationDuration(), source->GetRootOffset())\n, x7c_totals(source)\n, x104_bitstreamData(source->GetBitstreamPointer())\n, x108_bitLoader(x104_bitstreamData)\n, x114_segIdToIndex(x7c_totals.x10_nextSel ? x7c_totals.x14_a : x7c_totals.x3c_b) {\n  x7c_totals.SetTime(x108_bitLoader, CCharAnimTime());\n  PostConstruct(time);\n}\n\nbool CFBStreamedAnimReader::HasOffset(const CSegId& seg) const {\n  s32 idx = x114_segIdToIndex.SegIdToIndex(seg);\n  if (idx == -1)\n    return false;\n  return x7c_totals.Prior().x8_hasTrans1[idx];\n}\n\nzeus::CVector3f CFBStreamedAnimReader::GetOffset(const CSegId& seg) const {\n  s32 idx = x114_segIdToIndex.SegIdToIndex(seg);\n  if (idx == -1)\n    return {};\n  const float* af = x7c_totals.Prior().GetFloats(idx);\n  const float* bf = x7c_totals.Next().GetFloats(idx);\n  zeus::CVector3f a(af[4], af[5], af[6]);\n  zeus::CVector3f b(bf[4], bf[5], bf[6]);\n  return zeus::CVector3f::lerp(a, b, x7c_totals.GetT());\n}\n\nzeus::CQuaternion CFBStreamedAnimReader::GetRotation(const CSegId& seg) const {\n  s32 idx = x114_segIdToIndex.SegIdToIndex(seg);\n  if (idx == -1)\n    return {};\n  const float* af = x7c_totals.Prior().GetFloats(idx);\n  const float* bf = x7c_totals.Next().GetFloats(idx);\n  zeus::CQuaternion a(af[0], af[1], af[2], af[3]);\n  zeus::CQuaternion b(bf[0], bf[1], bf[2], bf[3]);\n  return zeus::CQuaternion::slerp(a, b, x7c_totals.GetT());\n}\n\nSAdvancementResults CFBStreamedAnimReader::VGetAdvancementResults(const CCharAnimTime& dt,\n                                                                  const CCharAnimTime& startOff) const {\n  SAdvancementResults res = {};\n\n  CCharAnimTime resolveTime = xc_curTime + startOff;\n  CCharAnimTime animDur = x54_source->GetAnimationDuration();\n  if (resolveTime >= animDur || dt.EqualsZero())\n    return res;\n\n  const_cast<CFBStreamedAnimReader*>(this)->x7c_totals.SetTime(const_cast<CFBStreamedAnimReader*>(this)->x108_bitLoader,\n                                                               resolveTime);\n  zeus::CQuaternion priorQ = GetRotation(3);\n  zeus::CVector3f priorV = GetOffset(3);\n\n  CCharAnimTime nextTime = resolveTime + dt;\n  if (nextTime > animDur) {\n    nextTime = animDur;\n    res.x0_remTime = nextTime - animDur;\n  }\n\n  const_cast<CFBStreamedAnimReader*>(this)->x7c_totals.SetTime(const_cast<CFBStreamedAnimReader*>(this)->x108_bitLoader,\n                                                               nextTime);\n  zeus::CQuaternion nextQ = GetRotation(3);\n  zeus::CVector3f nextV = GetOffset(3);\n\n  res.x8_deltas.xc_rotDelta = priorQ.inverse() * nextQ;\n  if (HasOffset(3))\n    res.x8_deltas.x0_posDelta = res.x8_deltas.xc_rotDelta.transform(nextV - priorV);\n\n  return res;\n}\n\nvoid CFBStreamedAnimReader::VSetPhase(float ph) {\n  xc_curTime = x64_steadyStateInfo.GetDuration() * ph;\n  x7c_totals.SetTime(x108_bitLoader, xc_curTime);\n  if (x54_source->HasPOIData()) {\n    UpdatePOIStates();\n    if (!xc_curTime.GreaterThanZero()) {\n      x14_passedBoolCount = 0;\n      x18_passedIntCount = 0;\n      x1c_passedParticleCount = 0;\n      x20_passedSoundCount = 0;\n    }\n  }\n}\n\nSAdvancementResults CFBStreamedAnimReader::VReverseView(const CCharAnimTime& time) { return {}; }\n\nstd::unique_ptr<IAnimReader> CFBStreamedAnimReader::VClone() const {\n  return std::make_unique<CFBStreamedAnimReader>(x54_source, xc_curTime);\n}\n\nvoid CFBStreamedAnimReader::VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut) const {\n  const_cast<CFBStreamedAnimReader*>(this)->x7c_totals.SetTime(const_cast<CFBStreamedAnimReader*>(this)->x108_bitLoader,\n                                                               xc_curTime);\n\n  for (const CSegId& id : list.GetList()) {\n    CAnimPerSegmentData& out = setOut[id];\n    out.x0_rotation = GetRotation(id);\n    out.x1c_hasOffset = HasOffset(id);\n    if (out.x1c_hasOffset)\n      out.x10_offset = GetOffset(id);\n  }\n}\n\nvoid CFBStreamedAnimReader::VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut,\n                                                const CCharAnimTime& time) const {\n  const_cast<CFBStreamedAnimReader*>(this)->x7c_totals.SetTime(const_cast<CFBStreamedAnimReader*>(this)->x108_bitLoader,\n                                                               time);\n\n  for (const CSegId& id : list.GetList()) {\n    CAnimPerSegmentData& out = setOut[id];\n    out.x0_rotation = GetRotation(id);\n    out.x1c_hasOffset = HasOffset(id);\n    if (out.x1c_hasOffset)\n      out.x10_offset = GetOffset(id);\n  }\n}\n\nSAdvancementResults CFBStreamedAnimReader::VAdvanceView(const CCharAnimTime& dt) {\n  SAdvancementResults res = {};\n\n  CCharAnimTime animDur = x54_source->GetAnimationDuration();\n  if (xc_curTime == animDur) {\n    xc_curTime = CCharAnimTime();\n    x7c_totals.SetTime(x108_bitLoader, xc_curTime);\n    x14_passedBoolCount = 0;\n    x18_passedIntCount = 0;\n    x1c_passedParticleCount = 0;\n    x20_passedSoundCount = 0;\n    res.x0_remTime = dt;\n    return res;\n  } else if (dt.EqualsZero()) {\n    return res;\n  }\n\n  zeus::CQuaternion priorQ = GetRotation(3);\n  zeus::CVector3f priorV = GetOffset(3);\n\n  xc_curTime += dt;\n  CCharAnimTime overTime;\n  if (xc_curTime > animDur) {\n    overTime = xc_curTime - animDur;\n    xc_curTime = animDur;\n  }\n\n  x7c_totals.SetTime(x108_bitLoader, xc_curTime);\n  if (x54_source->HasPOIData())\n    UpdatePOIStates();\n\n  zeus::CQuaternion nextQ = GetRotation(3);\n  zeus::CVector3f nextV = GetOffset(3);\n\n  res.x0_remTime = overTime;\n  res.x8_deltas.xc_rotDelta = nextQ * priorQ.inverse();\n  if (HasOffset(3))\n    res.x8_deltas.x0_posDelta = nextQ.inverse().transform(nextV - priorV);\n\n  return res;\n}\n\nCCharAnimTime CFBStreamedAnimReader::VGetTimeRemaining() const {\n  return x54_source->GetAnimationDuration() - xc_curTime;\n}\n\nCSteadyStateAnimInfo CFBStreamedAnimReader::VGetSteadyStateAnimInfo() const { return x64_steadyStateInfo; }\n\nbool CFBStreamedAnimReader::VHasOffset(const CSegId& seg) const { return HasOffset(seg); }\n\nzeus::CVector3f CFBStreamedAnimReader::VGetOffset(const CSegId& seg) const {\n  const_cast<CFBStreamedAnimReader*>(this)->x7c_totals.SetTime(const_cast<CFBStreamedAnimReader*>(this)->x108_bitLoader,\n                                                               xc_curTime);\n  return GetOffset(seg);\n}\n\nzeus::CVector3f CFBStreamedAnimReader::VGetOffset(const CSegId& seg, const CCharAnimTime& time) const {\n  const_cast<CFBStreamedAnimReader*>(this)->x7c_totals.SetTime(const_cast<CFBStreamedAnimReader*>(this)->x108_bitLoader,\n                                                               time);\n  return GetOffset(seg);\n}\n\nzeus::CQuaternion CFBStreamedAnimReader::VGetRotation(const CSegId& seg) const {\n  const_cast<CFBStreamedAnimReader*>(this)->x7c_totals.SetTime(const_cast<CFBStreamedAnimReader*>(this)->x108_bitLoader,\n                                                               xc_curTime);\n  return GetRotation(seg);\n}\n\ntemplate class TAnimSourceInfo<CFBStreamedCompression>;\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CFBStreamedAnimReader.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <vector>\n\n#include \"Runtime/Character/CAnimSourceReader.hpp\"\n#include \"Runtime/Character/CFBStreamedCompression.hpp\"\n\nnamespace metaforce {\nclass CBitLevelLoader;\n\ntemplate <class T>\nclass TAnimSourceInfo : public IAnimSourceInfo {\n  TSubAnimTypeToken<T> x4_token;\n\npublic:\n  explicit TAnimSourceInfo(TSubAnimTypeToken<T> token) : x4_token(std::move(token)) {}\n  bool HasPOIData() const override { return x4_token->HasPOIData(); }\n  const std::vector<CBoolPOINode>& GetBoolPOIStream() const override { return x4_token->GetBoolPOIStream(); }\n  const std::vector<CInt32POINode>& GetInt32POIStream() const override { return x4_token->GetInt32POIStream(); }\n  const std::vector<CParticlePOINode>& GetParticlePOIStream() const override {\n    return x4_token->GetParticlePOIStream();\n  }\n  const std::vector<CSoundPOINode>& GetSoundPOIStream() const override { return x4_token->GetSoundPOIStream(); }\n  CCharAnimTime GetAnimationDuration() const override { return x4_token->GetAnimationDuration(); }\n};\n\nclass CFBStreamedAnimReaderTotals {\n  friend class CSegIdToIndexConverter;\n  friend class CFBStreamedPairOfTotals;\n  friend class CFBStreamedAnimReader;\n  std::unique_ptr<u8[]> x0_buffer;\n  s32* x4_cumulativeInts32; /* Used to be 16 per channel */\n  u8* x8_hasTrans1;\n  u16* xc_segIds2;\n  float* x10_computedFloats32;\n  u32 x14_rotDiv;\n  float x18_transMult;\n  u32 x1c_curKey = 0;\n  bool x20_calculated = false;\n  u32 x24_boneChanCount;\n  void Allocate(u32 chanCount);\n\npublic:\n  explicit CFBStreamedAnimReaderTotals(const CFBStreamedCompression& source);\n  void Initialize(const CFBStreamedCompression& source);\n  void IncrementInto(CBitLevelLoader& loader, const CFBStreamedCompression& source, CFBStreamedAnimReaderTotals& dest);\n  void CalculateDown();\n  bool IsCalculated() const { return x20_calculated; }\n  const float* GetFloats(int chanIdx) const { return &x10_computedFloats32[chanIdx * 8]; }\n};\n\nclass CFBStreamedPairOfTotals {\n  friend class CFBStreamedAnimReader;\n\n  TSubAnimTypeToken<CFBStreamedCompression> x0_source;\n  u32* xc_rotsAndOffs;\n  bool x10_nextSel = true;\n  CFBStreamedAnimReaderTotals x14_a;\n  CFBStreamedAnimReaderTotals x3c_b;\n  float x78_t = 0.f;\n\npublic:\n  explicit CFBStreamedPairOfTotals(const TSubAnimTypeToken<CFBStreamedCompression>& source);\n  void SetTime(CBitLevelLoader& loader, const CCharAnimTime& time);\n  void DoIncrement(CBitLevelLoader& loader);\n  float GetT() const { return x78_t; }\n  CFBStreamedAnimReaderTotals& Next() { return x10_nextSel ? x3c_b : x14_a; }\n  CFBStreamedAnimReaderTotals& Prior() { return x10_nextSel ? x14_a : x3c_b; }\n  const CFBStreamedAnimReaderTotals& Next() const { return x10_nextSel ? x3c_b : x14_a; }\n  const CFBStreamedAnimReaderTotals& Prior() const { return x10_nextSel ? x14_a : x3c_b; }\n};\n\nclass CBitLevelLoader {\n  const u8* m_data;\n  size_t m_bitIdx = 0;\n\npublic:\n  explicit CBitLevelLoader(const void* data) : m_data(reinterpret_cast<const u8*>(data)) {}\n  void Reset() { m_bitIdx = 0; }\n  u32 LoadUnsigned(u8 q);\n  s32 LoadSigned(u8 q);\n  bool LoadBool();\n  size_t GetCurBit() const { return m_bitIdx; }\n};\n\nclass CSegIdToIndexConverter {\n  std::array<s32, 100> x0_indices;\n\npublic:\n  explicit CSegIdToIndexConverter(const CFBStreamedAnimReaderTotals& totals);\n  s32 SegIdToIndex(const CSegId& id) const { return x0_indices[id]; }\n};\n\nclass CFBStreamedAnimReader : public CAnimSourceReaderBase {\n  TSubAnimTypeToken<CFBStreamedCompression> x54_source;\n  CSteadyStateAnimInfo x64_steadyStateInfo;\n  CFBStreamedPairOfTotals x7c_totals;\n  const u8* x104_bitstreamData;\n  CBitLevelLoader x108_bitLoader;\n  CSegIdToIndexConverter x114_segIdToIndex;\n  bool HasOffset(const CSegId& seg) const;\n  zeus::CVector3f GetOffset(const CSegId& seg) const;\n  zeus::CQuaternion GetRotation(const CSegId& seg) const;\n\npublic:\n  explicit CFBStreamedAnimReader(const TSubAnimTypeToken<CFBStreamedCompression>& source, const CCharAnimTime& time);\n\n  SAdvancementResults VGetAdvancementResults(const CCharAnimTime& a, const CCharAnimTime& b) const override;\n  bool VSupportsReverseView() const override { return false; }\n  void VSetPhase(float) override;\n  SAdvancementResults VReverseView(const CCharAnimTime& time) override;\n  std::unique_ptr<IAnimReader> VClone() const override;\n  void VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut) const override;\n  void VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut, const CCharAnimTime& time) const override;\n  SAdvancementResults VAdvanceView(const CCharAnimTime& a) override;\n  CCharAnimTime VGetTimeRemaining() const override;\n  CSteadyStateAnimInfo VGetSteadyStateAnimInfo() const override;\n  bool VHasOffset(const CSegId& seg) const override;\n  zeus::CVector3f VGetOffset(const CSegId& seg) const override;\n  zeus::CVector3f VGetOffset(const CSegId& seg, const CCharAnimTime& time) const override;\n  zeus::CQuaternion VGetRotation(const CSegId& seg) const override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CFBStreamedCompression.cpp",
    "content": "#include \"Runtime/Character/CFBStreamedCompression.hpp\"\n\n#include <cstring>\n#include <type_traits>\n#include \"Runtime/Character/CFBStreamedAnimReader.hpp\"\n\nnamespace metaforce {\nnamespace {\ntemplate <typename T>\nT ReadValue(const u8* data) {\n  static_assert(std::is_trivially_copyable_v<T>);\n\n  T value = 0;\n  std::memcpy(&value, data, sizeof(value));\n  return value;\n}\n\ntemplate <typename T>\nvoid WriteValue(u8* data, T value) {\n  static_assert(std::is_trivially_copyable_v<T>);\n  std::memcpy(data, &value, sizeof(value));\n}\n} // Anonymous namespace\n\nCFBStreamedCompression::CFBStreamedCompression(CInputStream& in, IObjectStore& objStore, bool pc) : m_pc(pc) {\n  x0_scratchSize = in.ReadLong();\n  x4_evnt = in.Get<CAssetId>();\n\n  xc_rotsAndOffs = GetRotationsAndOffsets(x0_scratchSize / 4 + 1, in);\n\n  if (x4_evnt.IsValid())\n    x8_evntToken = objStore.GetObj(SObjectTag{FOURCC('EVNT'), x4_evnt});\n\n  x10_averageVelocity = CalculateAverageVelocity(GetPerChannelHeaders());\n}\n\nconst u32* CFBStreamedCompression::GetTimes() const { return xc_rotsAndOffs.get() + 9; }\n\nconst u8* CFBStreamedCompression::GetPerChannelHeaders() const {\n  const u32* bitmap = GetTimes();\n  const u32 bitmapWordCount = (bitmap[0] + 31) / 32;\n  return reinterpret_cast<const u8*>(bitmap + bitmapWordCount + 1);\n}\n\nconst u8* CFBStreamedCompression::GetBitstreamPointer() const {\n  const u32* bitmap = GetTimes();\n  const u32 bitmapWordCount = (bitmap[0] + 31) / 32;\n\n  const u8* chans = reinterpret_cast<const u8*>(bitmap + bitmapWordCount + 1);\n  const u32 boneChanCount = ReadValue<u32>(chans);\n\n  chans += 4;\n\n  if (m_pc) {\n    for (u32 b = 0; b < boneChanCount; ++b) {\n      chans += 20;\n\n      const u32 tCount = ReadValue<u32>(chans);\n\n      chans += 4;\n      if (tCount != 0) {\n        chans += 12;\n      }\n    }\n  } else {\n    for (u32 b = 0; b < boneChanCount; ++b) {\n      chans += 15;\n\n      const u16 tCount = ReadValue<u16>(chans);\n\n      chans += 2;\n      if (tCount != 0) {\n        chans += 9;\n      }\n    }\n  }\n\n  return chans;\n}\n\nstd::unique_ptr<u32[]> CFBStreamedCompression::GetRotationsAndOffsets(u32 words, CInputStream& in) const {\n  std::unique_ptr<u32[]> ret(new u32[words]);\n\n  Header head;\n  head.read(in);\n  std::memcpy(ret.get(), &head, sizeof(head));\n\n  u32* bitmapOut = &ret[9];\n  const u32 bitmapBitCount = in.ReadLong();\n  bitmapOut[0] = bitmapBitCount;\n  const u32 bitmapWordCount = (bitmapBitCount + 31) / 32;\n  for (u32 i = 0; i < bitmapWordCount; ++i) {\n    bitmapOut[i + 1] = in.ReadLong();\n  }\n\n  in.ReadLong();\n  u8* chans = reinterpret_cast<u8*>(bitmapOut + bitmapWordCount + 1);\n  u8* bs = ReadBoneChannelDescriptors(chans, in);\n  const u32 bsWords = ComputeBitstreamWords(chans);\n\n  u32* bsPtr = reinterpret_cast<u32*>(bs);\n  for (u32 w = 0; w < bsWords; ++w)\n    bsPtr[w] = in.ReadLong();\n\n  return ret;\n}\n\nu8* CFBStreamedCompression::ReadBoneChannelDescriptors(u8* out, CInputStream& in) const {\n  const u32 boneChanCount = in.ReadLong();\n  WriteValue(out, boneChanCount);\n  out += 4;\n\n  if (m_pc) {\n    for (u32 b = 0; b < boneChanCount; ++b) {\n      WriteValue(out, in.ReadLong());\n      out += 4;\n\n      WriteValue(out, in.ReadLong());\n      out += 4;\n\n      for (int i = 0; i < 3; ++i) {\n        WriteValue(out, in.ReadLong());\n        out += 4;\n      }\n\n      const u32 tCount = in.ReadLong();\n      WriteValue(out, tCount);\n      out += 4;\n\n      if (tCount != 0) {\n        for (int i = 0; i < 3; ++i) {\n          WriteValue(out, in.ReadLong());\n          out += 4;\n        }\n      }\n    }\n  } else {\n    for (u32 b = 0; b < boneChanCount; ++b) {\n      WriteValue(out, in.ReadLong());\n      out += 4;\n\n      WriteValue(out, in.ReadShort());\n      out += 2;\n\n      for (int i = 0; i < 3; ++i) {\n        WriteValue(out, in.ReadShort());\n        out += 2;\n        WriteValue(out, in.ReadUint8());\n        out += 1;\n      }\n\n      const u16 tCount = in.ReadShort();\n      WriteValue(out, tCount);\n      out += 2;\n\n      if (tCount != 0) {\n        for (int i = 0; i < 3; ++i) {\n          WriteValue(out, in.ReadShort());\n          out += 2;\n          WriteValue(out, in.ReadUint8());\n          out += 1;\n        }\n      }\n    }\n  }\n\n  return out;\n}\n\nu32 CFBStreamedCompression::ComputeBitstreamWords(const u8* chans) const {\n  const u32 boneChanCount = ReadValue<u32>(chans);\n  chans += 4;\n\n  u32 keyCount;\n\n  u32 totalBits = 0;\n  if (m_pc) {\n    keyCount = ReadValue<u32>(chans + 0x4);\n    for (u32 c = 0; c < boneChanCount; ++c) {\n      chans += 0x8;\n      totalBits += 1;\n      totalBits += ReadValue<u32>(chans) & 0xff;\n      totalBits += ReadValue<u32>(chans + 0x4) & 0xff;\n      totalBits += ReadValue<u32>(chans + 0x8) & 0xff;\n      const u32 tKeyCount = ReadValue<u32>(chans + 0xc);\n      chans += 0x10;\n      if (tKeyCount != 0) {\n        totalBits += ReadValue<u32>(chans) & 0xff;\n        totalBits += ReadValue<u32>(chans + 0x4) & 0xff;\n        totalBits += ReadValue<u32>(chans + 0x8) & 0xff;\n        chans += 0xc;\n      }\n    }\n  } else {\n    keyCount = ReadValue<u16>(chans + 0x4);\n    for (u32 c = 0; c < boneChanCount; ++c) {\n      chans += 0x6;\n      totalBits += 1;\n      totalBits += ReadValue<u8>(chans + 0x2);\n      totalBits += ReadValue<u8>(chans + 0x5);\n      totalBits += ReadValue<u8>(chans + 0x8);\n      const u16 tKeyCount = ReadValue<u16>(chans + 0x9);\n      chans += 0xb;\n      if (tKeyCount != 0) {\n        totalBits += ReadValue<u8>(chans + 0x2);\n        totalBits += ReadValue<u8>(chans + 0x5);\n        totalBits += ReadValue<u8>(chans + 0x8);\n        chans += 0x9;\n      }\n    }\n  }\n\n  return (totalBits * keyCount + 31) / 32;\n}\n\nfloat CFBStreamedCompression::CalculateAverageVelocity(const u8* chans) const {\n  const u32 boneChanCount = ReadValue<u32>(chans);\n  chans += 4;\n\n  u32 keyCount;\n  u32 rootIdx = 0;\n  if (m_pc) {\n    keyCount = ReadValue<u32>(chans + 0x4);\n    for (u32 c = 0; c < boneChanCount; ++c) {\n      const u32 boneId = ReadValue<u32>(chans);\n      if (boneId == 3) {\n        break;\n      }\n      ++rootIdx;\n\n      chans += 0x8;\n      const u32 tKeyCount = ReadValue<u32>(chans + 0xc);\n      chans += 0x10;\n      if (tKeyCount != 0) {\n        chans += 0xc;\n      }\n    }\n  } else {\n    keyCount = ReadValue<u16>(chans + 0x4);\n    for (u32 c = 0; c < boneChanCount; ++c) {\n      const u32 boneId = ReadValue<u32>(chans);\n      if (boneId == 3) {\n        break;\n      }\n      ++rootIdx;\n\n      chans += 0x6;\n      const u16 tKeyCount = ReadValue<u16>(chans + 0x9);\n      chans += 0xb;\n      if (tKeyCount != 0) {\n        chans += 0x9;\n      }\n    }\n  }\n\n  CBitLevelLoader loader(GetBitstreamPointer());\n  CFBStreamedAnimReaderTotals tempTotals(*this);\n  tempTotals.CalculateDown();\n  const float* floats = tempTotals.GetFloats(rootIdx);\n  zeus::CVector3f transCompA(floats[4], floats[5], floats[6]);\n\n  float accumMag = 0.f;\n  for (u32 i = 0; i < keyCount; ++i) {\n    tempTotals.IncrementInto(loader, *this, tempTotals);\n    tempTotals.CalculateDown();\n    zeus::CVector3f transCompB(floats[4], floats[5], floats[6]);\n    accumMag += (transCompB - transCompA).magnitude();\n    transCompA = transCompB;\n  }\n\n  return accumMag / GetAnimationDuration().GetSeconds();\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CFBStreamedCompression.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <vector>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Character/CAnimPOIData.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass IObjectStore;\n\nclass CFBStreamedCompression {\n  friend class CFBStreamedAnimReader;\n  friend class CFBStreamedAnimReaderTotals;\n  friend class CFBStreamedPairOfTotals;\n\npublic:\n  struct Header {\n    u32 unk0;\n    float duration;\n    float interval;\n    u32 rootBoneId;\n    u32 looping;\n    u32 rotDiv;\n    float translationMult;\n    u32 boneChannelCount;\n    u32 unk3;\n\n    void read(CInputStream& in) {\n      /* unk0 */\n      unk0 = in.ReadLong();\n      /* duration */\n      duration = in.ReadFloat();\n      /* interval */\n      interval = in.ReadFloat();\n      /* rootBoneId */\n      rootBoneId = in.ReadLong();\n      /* looping */\n      looping = in.ReadLong();\n      /* rotDiv */\n      rotDiv = in.ReadLong();\n      /* translationMult */\n      translationMult = in.ReadFloat();\n      /* boneChannelCount */\n      boneChannelCount = in.ReadLong();\n      /* unk3 */\n      unk3 = in.ReadLong();\n    }\n  };\n\nprivate:\n  bool m_pc;\n  u32 x0_scratchSize;\n  CAssetId x4_evnt;\n  TLockedToken<CAnimPOIData> x8_evntToken;\n  std::unique_ptr<u32[]> xc_rotsAndOffs;\n  float x10_averageVelocity;\n  zeus::CVector3f x14_rootOffset;\n\n  u8* ReadBoneChannelDescriptors(u8* out, CInputStream& in) const;\n  u32 ComputeBitstreamWords(const u8* chans) const;\n  std::unique_ptr<u32[]> GetRotationsAndOffsets(u32 words, CInputStream& in) const;\n  float CalculateAverageVelocity(const u8* chans) const;\n\npublic:\n  explicit CFBStreamedCompression(CInputStream& in, IObjectStore& objStore, bool pc);\n  const Header& MainHeader() const { return *reinterpret_cast<const Header*>(xc_rotsAndOffs.get()); }\n  const u32* GetTimes() const;\n  const u8* GetPerChannelHeaders() const;\n  const u8* GetBitstreamPointer() const;\n  bool IsLooping() const { return MainHeader().looping; }\n  CCharAnimTime GetAnimationDuration() const { return MainHeader().duration; }\n  float GetAverageVelocity() const { return x10_averageVelocity; }\n  const zeus::CVector3f& GetRootOffset() const { return x14_rootOffset; }\n  bool HasPOIData() const { return x8_evntToken.HasReference(); }\n  const std::vector<CBoolPOINode>& GetBoolPOIStream() const { return x8_evntToken->GetBoolPOIStream(); }\n  const std::vector<CInt32POINode>& GetInt32POIStream() const { return x8_evntToken->GetInt32POIStream(); }\n  const std::vector<CParticlePOINode>& GetParticlePOIStream() const { return x8_evntToken->GetParticlePOIStream(); }\n  const std::vector<CSoundPOINode>& GetSoundPOIStream() const { return x8_evntToken->GetSoundPOIStream(); }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CGroundMovement.cpp",
    "content": "#include \"Runtime/Character/CGroundMovement.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Collision/CAABoxFilter.hpp\"\n#include \"Runtime/Collision/CCollisionInfoList.hpp\"\n#include \"Runtime/Collision/CGameCollision.hpp\"\n#include \"Runtime/Collision/CollisionUtil.hpp\"\n#include \"Runtime/World/CPhysicsActor.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CScriptPlatform.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n#include \"math.h\"\n\nnamespace metaforce {\n\nvoid CGroundMovement::CheckFalling(CPhysicsActor& actor, CStateManager& mgr, float /*dt*/) {\n  bool oob = true;\n  zeus::CAABox plBox = *actor.GetTouchBounds();\n  for (const CGameArea& area : *mgr.GetWorld()) {\n    if (area.GetAABB().intersects(plBox)) {\n      oob = false;\n      break;\n    }\n  }\n\n  if (oob) {\n    mgr.SendScriptMsg(&actor, kInvalidUniqueId, EScriptObjectMessage::OnFloor);\n    actor.SetAngularVelocityWR(actor.GetAngularVelocityWR() * 0.98f);\n    zeus::CVector3f vel = actor.GetTransform().transposeRotate(actor.GetVelocity());\n    vel.z() = 0.f;\n    actor.SetVelocityOR(vel);\n    actor.SetMomentumWR(zeus::skZero3f);\n  } else {\n    mgr.SendScriptMsg(&actor, kInvalidUniqueId, EScriptObjectMessage::Falling);\n  }\n}\n\nvoid CGroundMovement::MoveGroundCollider(CStateManager& mgr, CPhysicsActor& actor, float dt,\n                                         const EntityList* nearList) {\n  CMotionState oldState = actor.GetMotionState();\n  CMotionState newState = actor.PredictMotion_Internal(dt);\n  float deltaMag = newState.x0_translation.magnitude();\n  TUniqueId idDetect = kInvalidUniqueId;\n  CCollisionInfoList collisionList;\n  zeus::CAABox motionVol = actor.GetMotionVolume(dt);\n  EntityList useColliderList;\n  if (nearList != nullptr) {\n    useColliderList = *nearList;\n  }\n  mgr.BuildColliderList(useColliderList, actor, motionVol);\n  CAreaCollisionCache cache(motionVol);\n  float collideDt = dt;\n  if (actor.GetCollisionPrimitive()->GetPrimType() != FOURCC('OBTG')) {\n    CGameCollision::BuildAreaCollisionCache(mgr, cache);\n    if (deltaMag > 0.5f * CGameCollision::GetMinExtentForCollisionPrimitive(*actor.GetCollisionPrimitive())) {\n      zeus::CVector3f point = actor.GetCollisionPrimitive()->CalculateAABox(actor.GetPrimitiveTransform()).center();\n      TUniqueId intersectId = kInvalidUniqueId;\n      CMaterialFilter filter = CMaterialFilter::MakeInclude({EMaterialTypes::Solid});\n      CRayCastResult result = mgr.RayWorldIntersection(intersectId, point, newState.x0_translation.normalized(),\n                                                       deltaMag, filter, useColliderList);\n      if (result.IsValid()) {\n        collideDt = dt * (result.GetT() / deltaMag);\n        newState = actor.PredictMotion_Internal(collideDt);\n      }\n    }\n  }\n  actor.MoveCollisionPrimitive(newState.x0_translation);\n  if (CGameCollision::DetectCollision_Cached(mgr, cache, *actor.GetCollisionPrimitive(), actor.GetPrimitiveTransform(),\n                                             actor.GetMaterialFilter(), useColliderList, idDetect, collisionList)) {\n    actor.AddMotionState(newState);\n    float resolved = 0.f;\n    if (ResolveUpDown(cache, mgr, actor, actor.GetMaterialFilter(), useColliderList, actor.GetStepUpHeight(), 0.f,\n                      resolved, collisionList)) {\n      actor.SetMotionState(oldState);\n      MoveGroundColliderXY(cache, mgr, actor, actor.GetMaterialFilter(), useColliderList, collideDt);\n    }\n\n  } else {\n    actor.AddMotionState(newState);\n  }\n\n  float stepDown = actor.GetStepDownHeight();\n  float resolved = 0.f;\n  collisionList.Clear();\n  TUniqueId stepZId = kInvalidUniqueId;\n  if (stepDown >= 0.f && MoveGroundColliderZ(cache, mgr, actor, actor.GetMaterialFilter(), useColliderList, -stepDown,\n                                             resolved, collisionList, stepZId)) {\n    if (collisionList.GetCount() > 0) {\n      CCollisionInfoList filteredList;\n      CollisionUtil::FilterByClosestNormal(zeus::CVector3f{0.f, 0.f, 1.f}, collisionList, filteredList);\n      if (filteredList.GetCount() > 0) {\n        if (CGameCollision::IsFloor(filteredList.Front().GetMaterialLeft(), filteredList.Front().GetNormalLeft())) {\n          if (TCastToPtr<CScriptPlatform> plat = mgr.ObjectById(stepZId)) {\n            mgr.SendScriptMsg(plat.GetPtr(), actor.GetUniqueId(), EScriptObjectMessage::AddPlatformRider);\n          }\n          CGameCollision::SendMaterialMessage(mgr, filteredList.Front().GetMaterialLeft(), actor);\n          mgr.SendScriptMsg(&actor, kInvalidUniqueId, EScriptObjectMessage::OnFloor);\n        } else {\n          CheckFalling(actor, mgr, dt);\n        }\n      }\n    }\n  } else {\n    CheckFalling(actor, mgr, dt);\n  }\n\n  actor.ClearForcesAndTorques();\n  actor.MoveCollisionPrimitive(zeus::skZero3f);\n  if (actor.GetMaterialList().HasMaterial(EMaterialTypes::Player)) {\n    CGameCollision::CollisionFailsafe(mgr, cache, actor, *actor.GetCollisionPrimitive(), useColliderList, 0.f, 1);\n  }\n}\n\nbool CGroundMovement::ResolveUpDown(CAreaCollisionCache& cache, CStateManager& mgr, CPhysicsActor& actor,\n                                    const CMaterialFilter& filter, EntityList& nearList, float stepUp, float stepDown,\n                                    float& fOut, CCollisionInfoList& list) {\n  float zextent = stepDown;\n  if (list.GetCount() <= 0) {\n    return true;\n  }\n\n  zeus::CAABox aabb = zeus::CAABox();\n  zeus::CVector3f normAccum = zeus::skZero3f;\n  for (CCollisionInfo& info : list) {\n    if (CGameCollision::IsFloor(info.GetMaterialLeft(), info.GetNormalLeft())) {\n      aabb.accumulateBounds(info.GetPoint());\n      aabb.accumulateBounds(info.GetExtreme());\n      normAccum += info.GetNormalLeft();\n    }\n  }\n\n  if (normAccum.canBeNormalized()) {\n    normAccum.normalize();\n  } else {\n    return true;\n  }\n\n  zeus::CAABox actorAABB = actor.GetBoundingBox();\n  if (normAccum.z() >= 0.f) {\n    zextent = aabb.max.z() - actorAABB.min.z() + 0.02f;\n    if (zextent > stepUp) {\n      return true;\n    }\n  } else {\n    zextent = aabb.min.z() - actorAABB.max.z() - 0.02f;\n    if (zextent < -stepDown) {\n      return true;\n    }\n  }\n\n  actor.MoveCollisionPrimitive({0.f, 0.f, zextent});\n\n  if (!CGameCollision::DetectCollisionBoolean_Cached(mgr, cache, *actor.GetCollisionPrimitive(),\n                                                     actor.GetPrimitiveTransform(), filter, nearList)) {\n    fOut = zextent;\n    actor.SetTranslation(actor.GetTranslation() + zeus::CVector3f(0.f, 0.f, zextent));\n    actor.MoveCollisionPrimitive(zeus::skZero3f);\n\n    bool floor = false;\n    for (CCollisionInfo& info : list) {\n      if (CGameCollision::IsFloor(info.GetMaterialLeft(), info.GetNormalLeft())) {\n        floor = true;\n        break;\n      }\n    }\n\n    if (!floor) {\n      mgr.SendScriptMsg(&actor, kInvalidUniqueId, EScriptObjectMessage::LandOnNotFloor);\n    }\n\n    return false;\n  }\n\n  return true;\n}\n\nbool CGroundMovement::MoveGroundColliderZ(CAreaCollisionCache& cache, CStateManager& mgr, CPhysicsActor& actor,\n                                          const CMaterialFilter& filter, EntityList& nearList, float amt,\n                                          float& resolved, CCollisionInfoList& list, TUniqueId& idOut) {\n  actor.MoveCollisionPrimitive({0.f, 0.f, amt});\n\n  zeus::CAABox aabb = zeus::CAABox();\n  if (CGameCollision::DetectCollision_Cached(mgr, cache, *actor.GetCollisionPrimitive(), actor.GetPrimitiveTransform(),\n                                             filter, nearList, idOut, list)) {\n    for (CCollisionInfo& info : list) {\n      aabb.accumulateBounds(info.GetPoint());\n      aabb.accumulateBounds(info.GetExtreme());\n    }\n\n    zeus::CAABox actorAABB = actor.GetBoundingBox();\n    float zextent = 0.f;\n    if (amt > 0.f) {\n      zextent = aabb.min.z() - actorAABB.max.z() - 0.02f + amt;\n    } else {\n      zextent = aabb.max.z() - actorAABB.min.z() + 0.02f + amt;\n    }\n\n    actor.MoveCollisionPrimitive({0.f, 0.f, zextent});\n\n    if (!CGameCollision::DetectCollisionBoolean_Cached(mgr, cache, *actor.GetCollisionPrimitive(),\n                                                       actor.GetPrimitiveTransform(), filter, nearList)) {\n      resolved = zextent;\n      actor.SetTranslation(actor.GetTranslation() + zeus::CVector3f(0.f, 0.f, zextent));\n      actor.MoveCollisionPrimitive(zeus::skZero3f);\n    }\n\n    bool floor = false;\n    for (CCollisionInfo& info : list) {\n      if (CGameCollision::IsFloor(info.GetMaterialLeft(), info.GetNormalLeft())) {\n        floor = true;\n        break;\n      }\n    }\n\n    if (!floor) {\n      mgr.SendScriptMsg(&actor, kInvalidUniqueId, EScriptObjectMessage::LandOnNotFloor);\n    }\n\n    CCollisionInfoList filteredList;\n    if (amt > 0.f) {\n      CollisionUtil::FilterByClosestNormal({0.f, 0.f, -1.f}, list, filteredList);\n    } else {\n      CollisionUtil::FilterByClosestNormal({0.f, 0.f, 1.f}, list, filteredList);\n    }\n\n    if (filteredList.GetCount() > 0) {\n      CGameCollision::MakeCollisionCallbacks(mgr, actor, idOut, filteredList);\n    }\n\n    return true;\n  }\n\n  return false;\n}\n\nvoid CGroundMovement::MoveGroundColliderXY(CAreaCollisionCache& cache, CStateManager& mgr, CPhysicsActor& actor,\n                                           const CMaterialFilter& filter, EntityList& nearList, float dt) {\n  bool didCollide = false;\n  bool isPlayer = actor.GetMaterialList().HasMaterial(EMaterialTypes::Player);\n  float remDt = dt;\n  float originalDt = dt;\n  TCastToPtr<CPhysicsActor> otherActor;\n  CCollisionInfoList collisionList;\n  CMotionState newMState = actor.PredictMotion_Internal(dt);\n  float transMag = newMState.x0_translation.magnitude();\n  float divMag = NAN;\n  if (isPlayer) {\n    divMag = std::max(transMag / 5.f, 0.005f);\n  } else {\n    divMag = std::max(transMag / 3.f, 0.02f);\n  }\n\n  float minExtent = 0.5f * CGameCollision::GetMinExtentForCollisionPrimitive(*actor.GetCollisionPrimitive());\n  if (transMag > minExtent) {\n    dt = minExtent * (dt / transMag);\n    originalDt = dt;\n    newMState = actor.PredictMotion_Internal(dt);\n    divMag = std::min(divMag, minExtent);\n  }\n\n  float nonCollideDt = dt;\n  do {\n    actor.MoveCollisionPrimitive(newMState.x0_translation);\n    collisionList.Clear();\n    TUniqueId otherId = kInvalidUniqueId;\n    bool collided =\n        CGameCollision::DetectCollision_Cached(mgr, cache, *actor.GetCollisionPrimitive(),\n                                               actor.GetPrimitiveTransform(), filter, nearList, otherId, collisionList);\n    if (collided) {\n      otherActor = mgr.ObjectById(otherId);\n    }\n    actor.MoveCollisionPrimitive(zeus::skZero3f);\n    if (collided) {\n      didCollide = true;\n      if (newMState.x0_translation.magnitude() < divMag) {\n        CCollisionInfoList backfaceFilteredList;\n        CCollisionInfoList floorFilteredList;\n        zeus::CVector3f deltaVel = actor.GetVelocity();\n        if (otherActor) {\n          deltaVel -= otherActor->GetVelocity();\n        }\n        CollisionUtil::FilterOutBackfaces(deltaVel, collisionList, backfaceFilteredList);\n        CAABoxFilter::FilterBoxFloorCollisions(backfaceFilteredList, floorFilteredList);\n        CGameCollision::MakeCollisionCallbacks(mgr, actor, otherId, floorFilteredList);\n        if (floorFilteredList.GetCount() == 0 && isPlayer) {\n          CMotionState lastNonCollideState = actor.GetLastNonCollidingState();\n          lastNonCollideState.x1c_velocity *= zeus::CVector3f(0.5f);\n          lastNonCollideState.x28_angularMomentum *= zeus::CVector3f(0.5f);\n          actor.SetMotionState(lastNonCollideState);\n        }\n        for (const CCollisionInfo& info : floorFilteredList) {\n          CCollisionInfo infoCopy = info;\n          float restitution =\n              CGameCollision::GetCoefficientOfRestitution(infoCopy) + actor.GetCoefficientOfRestitutionModifier();\n          if (otherActor) {\n            CGameCollision::CollideWithDynamicBodyNoRot(actor, *otherActor, infoCopy, restitution, true);\n          } else {\n            CGameCollision::CollideWithStaticBodyNoRot(actor, infoCopy.GetMaterialLeft(), infoCopy.GetMaterialRight(),\n                                                       infoCopy.GetNormalLeft(), restitution, true);\n          }\n        }\n        remDt -= dt;\n        nonCollideDt = std::min(originalDt, remDt);\n        dt = nonCollideDt;\n      } else {\n        nonCollideDt *= 0.5f;\n        dt *= 0.5f;\n      }\n    } else {\n      actor.AddMotionState(newMState);\n      remDt -= dt;\n      dt = nonCollideDt;\n      actor.MoveCollisionPrimitive(zeus::skZero3f);\n    }\n\n    newMState = actor.PredictMotion_Internal(dt);\n  } while (remDt > 0.f);\n\n  if (!didCollide && !actor.GetMaterialList().HasMaterial(EMaterialTypes::GroundCollider)) {\n    mgr.SendScriptMsg(&actor, kInvalidUniqueId, EScriptObjectMessage::Falling);\n  }\n\n  actor.MoveCollisionPrimitive(zeus::skZero3f);\n}\n\nzeus::CVector3f CGroundMovement::CollisionDamping(const zeus::CVector3f& vel, const zeus::CVector3f& dir,\n                                                  const zeus::CVector3f& cNorm, float normCoeff, float deltaCoeff) {\n  zeus::CVector3f dampedDir = (cNorm * -2.f * cNorm.dot(dir) + dir).normalized();\n  zeus::CVector3f dampedNorm = cNorm * cNorm.dot(dampedDir);\n  return (dampedDir - dampedNorm) * vel.magnitude() * deltaCoeff + normCoeff * vel.magnitude() * dampedNorm;\n}\n\nvoid CGroundMovement::MoveGroundCollider_New(CStateManager& mgr, CPhysicsActor& actor, float dt,\n                                             const EntityList* nearList) {\n  zeus::CAABox motionVol = actor.GetMotionVolume(dt);\n  EntityList useNearList;\n  if (nearList != nullptr) {\n    useNearList = *nearList;\n  } else {\n    mgr.BuildColliderList(useNearList, actor, motionVol);\n  }\n\n  CAreaCollisionCache cache(motionVol);\n  CGameCollision::BuildAreaCollisionCache(mgr, cache);\n  auto& player = static_cast<CPlayer&>(actor);\n  player.x9c5_28_slidingOnWall = false;\n  bool applyJump = player.x258_movementState == CPlayer::EPlayerMovementState::ApplyJump;\n  bool dampUnderwater = false;\n  if (player.x9c4_31_inWaterMovement) {\n    if (!mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::GravitySuit)) {\n      dampUnderwater = true;\n    }\n  }\n\n  bool noJump = (player.x258_movementState != CPlayer::EPlayerMovementState::ApplyJump &&\n                 player.x258_movementState != CPlayer::EPlayerMovementState::Jump);\n\n  float stepDown = player.GetStepDownHeight();\n  float stepUp = player.GetStepUpHeight();\n\n  bool doStepDown = true;\n  CMaterialList material(EMaterialTypes::NoStepLogic);\n  SMoveObjectResult result;\n\n  if (!applyJump) {\n    const SMovementOptions opts{\n        .x0_setWaterLandingForce = false,\n        .x4_waterLandingForceCoefficient = 0.f,\n        .x8_minimumWaterLandingForce = 0.f,\n        .xc_anyZThreshold = 0.37f,\n        .x10_downwardZThreshold = 0.25f,\n        .x14_waterLandingVelocityReduction = 0.f,\n        .x18_dampForceAndMomentum = true,\n        .x19_alwaysClip = false,\n        .x1a_disableClipForFloorOnly = noJump,\n        .x1c_maxCollisionCycles = 4,\n        .x20_minimumTranslationDelta = 0.002f,\n        .x24_dampedNormalCoefficient = 0.f,\n        .x28_dampedDeltaCoefficient = 1.f,\n        .x2c_floorElasticForce = 0.f,\n        .x30_wallElasticConstant = 0.02f,\n        .x34_wallElasticLinear = 0.2f,\n        .x38_maxPositiveVerticalVelocity = player.GetMaximumPlayerPositiveVerticalVelocity(mgr),\n        .x3c_floorPlaneNormal = player.GetLastFloorPlaneNormal(),\n    };\n\n    if (noJump) {\n      zeus::CVector3f vel = player.GetVelocity();\n      vel.z() = 0.f;\n      actor.SetVelocityWR(vel);\n      actor.x15c_force.z() = 0.f;\n      actor.x150_momentum.z() = 0.f;\n      actor.x168_impulse.z() = 0.f;\n    }\n\n    CPhysicsState physStatePre = actor.GetPhysicsState();\n    CMaterialList material2 = MoveObjectAnalytical(mgr, actor, dt, useNearList, cache, opts, result);\n    CPhysicsState physStatePost = actor.GetPhysicsState();\n\n    /* NoStepLogic must be the only set material bit to bypass step logic */\n    if (material2.XOR({EMaterialTypes::NoStepLogic}) != 0u) {\n      SMovementOptions optsCopy = opts;\n      optsCopy.x19_alwaysClip = noJump;\n      optsCopy.x30_wallElasticConstant = 0.03f;\n\n      const zeus::CVector3f postToPre = physStatePre.GetTranslation() - physStatePost.GetTranslation();\n      const float postToPreMag = postToPre.magSquared();\n      const float quarterStepUp = 0.25f * stepUp;\n\n      rstl::reserved_vector<CPhysicsState, 2> physStateList;\n      rstl::reserved_vector<float, 2> stepDeltaList;\n      rstl::reserved_vector<CCollisionInfo, 2> collisionInfoList;\n      rstl::reserved_vector<TUniqueId, 2> uniqueIdList;\n      rstl::reserved_vector<CMaterialList, 2> materialListList;\n      bool done = false;\n      for (int i = 0; i < 2 && !done; ++i) {\n        double useStepUp = (i == 0) ? quarterStepUp : stepUp;\n        actor.SetPhysicsState(physStatePre);\n        CCollisionInfo collisionInfo;\n        TUniqueId id = kInvalidUniqueId;\n        CGameCollision::DetectCollision_Cached_Moving(mgr, cache, *actor.GetCollisionPrimitive(), actor.GetTransform(),\n                                                      actor.GetMaterialFilter(), useNearList, {0.f, 0.f, 1.f}, id,\n                                                      collisionInfo, useStepUp);\n        if (collisionInfo.IsValid()) {\n          useStepUp = std::max(0.0, useStepUp - optsCopy.x20_minimumTranslationDelta);\n          done = true;\n        }\n\n        if (useStepUp > 0.0005) {\n          actor.SetTranslation(actor.GetTranslation() + zeus::CVector3f(0.f, 0.f, static_cast<float>(useStepUp)));\n\n          SMoveObjectResult result2;\n          CMaterialList material3 = MoveObjectAnalytical(mgr, actor, dt, useNearList, cache, optsCopy, result2);\n          CCollisionInfo collisionInfo2;\n          double useStepDown2 = useStepUp + stepDown;\n          TUniqueId id2 = kInvalidUniqueId;\n          if (useStepDown2 > 0.0) {\n            CGameCollision::DetectCollision_Cached_Moving(mgr, cache, *actor.GetCollisionPrimitive(),\n                                                          actor.GetTransform(), actor.GetMaterialFilter(), useNearList,\n                                                          {0.f, 0.f, -1.f}, id2, collisionInfo2, useStepDown2);\n          } else {\n            useStepDown2 = 0.0;\n          }\n\n          float minStep = std::min(useStepUp, useStepDown2);\n          zeus::CVector3f offsetStep = actor.GetTranslation() - zeus::CVector3f(0.f, 0.f, minStep);\n          bool floor = (collisionInfo2.IsValid() &&\n                        CGameCollision::CanBlock(collisionInfo2.GetMaterialLeft(), collisionInfo2.GetNormalLeft()));\n          zeus::CVector3f postToPre2 = physStatePre.GetTranslation() - offsetStep;\n          float stepDelta = postToPre2.magSquared();\n          if (floor && postToPreMag < stepDelta) {\n            useStepDown2 = std::max(0.0, useStepDown2 - 0.0005);\n            actor.SetTranslation(actor.GetTranslation() - zeus::CVector3f(0.f, 0.f, static_cast<float>(useStepDown2)));\n            physStateList.push_back(actor.GetPhysicsState());\n            stepDeltaList.push_back(stepDelta);\n            collisionInfoList.push_back(collisionInfo2);\n            uniqueIdList.push_back(id2);\n            materialListList.push_back(material3);\n          }\n        }\n      }\n\n      if (physStateList.empty()) {\n        actor.SetPhysicsState(physStatePost);\n        material = material2;\n      } else {\n        float maxFloat = -1.0e10f;\n        int maxIdx = -1;\n        for (size_t i = 0; i < physStateList.size(); ++i) {\n          if (maxFloat < stepDeltaList[i]) {\n            maxFloat = stepDeltaList[i];\n            maxIdx = i;\n          }\n        }\n\n        actor.SetPhysicsState(physStateList[maxIdx]);\n        mgr.SendScriptMsg(&actor, kInvalidUniqueId, EScriptObjectMessage::OnFloor);\n        if (CEntity* ent = mgr.ObjectById(uniqueIdList[maxIdx])) {\n          result.x0_id.emplace(uniqueIdList[maxIdx]);\n          result.x8_collision.emplace(collisionInfoList[maxIdx]);\n          if (TCastToPtr<CScriptPlatform>(ent)) {\n            mgr.SendScriptMsg(ent, actor.GetUniqueId(), EScriptObjectMessage::AddPlatformRider);\n          }\n        }\n\n        CCollisionInfo& cInfo = collisionInfoList[maxIdx];\n        CGameCollision::SendMaterialMessage(mgr, cInfo.GetMaterialLeft(), actor);\n        doStepDown = false;\n        actor.SetLastFloorPlaneNormal({cInfo.GetNormalLeft()});\n      }\n    }\n  } else {\n    const SMovementOptions opts{\n        .x0_setWaterLandingForce = true,\n        .x4_waterLandingForceCoefficient = dampUnderwater ? 35.f : 1.f,\n        .x8_minimumWaterLandingForce = dampUnderwater ? 5.f : 0.f,\n        .xc_anyZThreshold = dampUnderwater ? 0.05f : 0.37f,\n        .x10_downwardZThreshold = dampUnderwater ? 0.01f : 0.25f,\n        .x14_waterLandingVelocityReduction = dampUnderwater ? 0.2f : 0.f,\n        .x18_dampForceAndMomentum = false,\n        .x19_alwaysClip = false,\n        .x1a_disableClipForFloorOnly = false,\n        .x1c_maxCollisionCycles = 4,\n        .x20_minimumTranslationDelta = 0.002f,\n        .x24_dampedNormalCoefficient = 0.f,\n        .x28_dampedDeltaCoefficient = 1.f,\n        .x2c_floorElasticForce = 0.1f,\n        .x30_wallElasticConstant = 0.2f,\n        .x38_maxPositiveVerticalVelocity = player.GetMaximumPlayerPositiveVerticalVelocity(mgr),\n        .x3c_floorPlaneNormal = player.GetLastFloorPlaneNormal(),\n    };\n\n    material = MoveObjectAnalytical(mgr, actor, dt, useNearList, cache, opts, result);\n  }\n\n  if (doStepDown) {\n    CCollisionInfo collisionInfo;\n    double stepDown2 = actor.GetStepDownHeight();\n    float zOffset = 0.f;\n    TUniqueId id = kInvalidUniqueId;\n    if (stepDown2 > FLT_EPSILON) {\n      zeus::CTransform xf = actor.GetTransform();\n      xf.origin += zeus::CVector3f(0.f, 0.f, 0.0005f);\n      if (!CGameCollision::DetectCollisionBoolean_Cached(mgr, cache, *actor.GetCollisionPrimitive(), xf,\n                                                         actor.GetMaterialFilter(), useNearList)) {\n        actor.SetTranslation(xf.origin);\n        zOffset = 0.0005f;\n        stepDown2 += 0.0005;\n      }\n\n      CGameCollision::DetectCollision_Cached_Moving(mgr, cache, *actor.GetCollisionPrimitive(), actor.GetTransform(),\n                                                    actor.GetMaterialFilter(), useNearList, {0.f, 0.f, -1.f}, id,\n                                                    collisionInfo, stepDown2);\n    }\n\n    if (id != kInvalidUniqueId) {\n      result.x0_id.emplace(id);\n      result.x8_collision.emplace(collisionInfo);\n    }\n\n    if (!collisionInfo.IsValid() ||\n        !CGameCollision::CanBlock(collisionInfo.GetMaterialLeft(), collisionInfo.GetNormalLeft())) {\n      if (zOffset > 0.f) {\n        zeus::CTransform xf = actor.GetTransform();\n        xf.origin -= zeus::CVector3f(0.f, 0.f, zOffset);\n      }\n\n      if (collisionInfo.IsValid()) {\n        player.x9c5_28_slidingOnWall = true;\n      }\n      CheckFalling(actor, mgr, dt);\n      player.SetLastFloorPlaneNormal({});\n    } else {\n      mgr.SendScriptMsg(&actor, kInvalidUniqueId, EScriptObjectMessage::OnFloor);\n      stepDown2 = std::max(0.0, stepDown2 - 0.0005);\n      actor.SetTranslation(actor.GetTranslation() - zeus::CVector3f(0.f, 0.f, static_cast<float>(stepDown2)));\n      if (TCastToPtr<CScriptPlatform> plat = mgr.ObjectById(id)) {\n        mgr.SendScriptMsg(plat.GetPtr(), actor.GetUniqueId(), EScriptObjectMessage::AddPlatformRider);\n      }\n      CGameCollision::SendMaterialMessage(mgr, collisionInfo.GetMaterialLeft(), actor);\n      actor.SetLastFloorPlaneNormal({collisionInfo.GetNormalLeft()});\n    }\n  }\n\n  actor.ClearForcesAndTorques();\n  if (material.HasMaterial(EMaterialTypes::Wall)) {\n    player.SetPlayerHitWallDuringMove();\n  }\n\n  if (result.x0_id) {\n    CCollisionInfoList list;\n    list.Add(*result.x8_collision, false);\n    CGameCollision::MakeCollisionCallbacks(mgr, actor, *result.x0_id, list);\n  }\n\n  CMotionState mState = actor.GetMotionState();\n  mState.x0_translation = actor.GetLastNonCollidingState().x0_translation;\n  mState.x1c_velocity = actor.GetLastNonCollidingState().x1c_velocity;\n  actor.SetLastNonCollidingState(mState);\n\n  const CCollisionPrimitive* usePrim = actor.GetCollisionPrimitive();\n  std::unique_ptr<CCollisionPrimitive> prim;\n  if (usePrim->GetPrimType() == FOURCC('AABX')) {\n    const auto& existingAABB = static_cast<const CCollidableAABox&>(*usePrim);\n    prim = std::make_unique<CCollidableAABox>(\n        zeus::CAABox(existingAABB.GetBox().min + 0.0001f, existingAABB.GetBox().max - 0.0001f), usePrim->GetMaterial());\n    usePrim = prim.get();\n  } else if (usePrim->GetPrimType() == FOURCC('SPHR')) {\n    const auto& existingSphere = static_cast<const CCollidableSphere&>(*usePrim);\n    prim = std::make_unique<CCollidableSphere>(\n        zeus::CSphere(existingSphere.GetSphere().position, existingSphere.GetSphere().radius - 0.0001f),\n        usePrim->GetMaterial());\n    usePrim = prim.get();\n  }\n\n  CGameCollision::CollisionFailsafe(mgr, cache, actor, *usePrim, useNearList, 0.f, 1);\n}\n\nbool CGroundMovement::RemoveNormalComponent(const zeus::CVector3f& a, const zeus::CVector3f& b, zeus::CVector3f& c,\n                                            float& d) {\n  float dot = a.dot(c);\n  if (std::fabs(dot) > 0.99f) {\n    return false;\n  }\n  float dot2 = b.dot(c);\n  float dot3 = b.dot((c - a * dot).normalized());\n  if (dot2 > 0.f && dot3 < 0.f) {\n    return false;\n  }\n  if (std::fabs(dot2) > 0.01f && std::fabs(dot3 / dot2) > 4.f) {\n    return false;\n  }\n  c -= dot * a;\n  d = dot;\n  return true;\n}\n\nbool CGroundMovement::RemoveNormalComponent(const zeus::CVector3f& a, zeus::CVector3f& b) {\n  float dot = a.dot(b);\n  if (std::fabs(dot) > 0.99f) {\n    return false;\n  }\n  b -= a * dot;\n  return true;\n}\n\nstatic bool RemovePositiveZComponentFromNormal(zeus::CVector3f& vec) {\n  if (vec.z() > 0.f && vec.z() < 0.99f) {\n    vec.z() = 0.f;\n    vec.normalize();\n    return true;\n  }\n  return false;\n}\n\nCMaterialList CGroundMovement::MoveObjectAnalytical(CStateManager& mgr, CPhysicsActor& actor, float dt,\n                                                    EntityList& nearList, CAreaCollisionCache& cache,\n                                                    const SMovementOptions& opts, SMoveObjectResult& result) {\n  result.x6c_processedCollisions = 0;\n  CMaterialList ret;\n  zeus::CVector3f floorPlaneNormal = opts.x3c_floorPlaneNormal ? *opts.x3c_floorPlaneNormal : zeus::skZero3f;\n  bool floorCollision = opts.x3c_floorPlaneNormal.has_value();\n  float remDt = dt;\n  for (size_t i = 0; remDt > 0.f; ++i) {\n    float collideDt = remDt;\n\n    CMotionState mState = actor.PredictMotion_Internal(remDt);\n    double mag = mState.x0_translation.magnitude();\n    zeus::CVector3f normTrans = (1.f / ((float(mag) > FLT_EPSILON) ? float(mag) : 1.f)) * mState.x0_translation;\n    TUniqueId id = kInvalidUniqueId;\n    CCollisionInfo collisionInfo;\n\n    if (mag > opts.x20_minimumTranslationDelta) {\n      double oldMag = mag;\n      CGameCollision::DetectCollision_Cached_Moving(mgr, cache, *actor.GetCollisionPrimitive(),\n                                                    actor.GetPrimitiveTransform(), actor.GetMaterialFilter(), nearList,\n                                                    normTrans, id, collisionInfo, mag);\n      if (id != kInvalidUniqueId) {\n        result.x0_id.emplace(id);\n        result.x8_collision.emplace(collisionInfo);\n      }\n      collideDt = static_cast<float>(mag / oldMag * remDt);\n    }\n\n    mag = std::max(0.f, float(mag) - opts.x20_minimumTranslationDelta);\n\n    zeus::CVector3f collisionNorm = collisionInfo.GetNormalLeft();\n    bool floor = CGameCollision::CanBlock(collisionInfo.GetMaterialLeft(), collisionNorm);\n    bool clipCollision = true;\n    if (!opts.x19_alwaysClip) {\n      if (!opts.x1a_disableClipForFloorOnly || floor) {\n        clipCollision = false;\n      }\n    }\n\n    float collisionFloorDot = 0.f;\n\n    if (collisionInfo.IsValid()) {\n      result.x6c_processedCollisions += 1;\n      if (floor) {\n        ret.Add(EMaterialTypes::Floor);\n        floorPlaneNormal = collisionInfo.GetNormalLeft();\n        floorCollision = true;\n      } else {\n        ret.Add(EMaterialTypes::Wall);\n      }\n\n      if (clipCollision) {\n        if (floorCollision) {\n          if (!CGroundMovement::RemoveNormalComponent(floorPlaneNormal, normTrans, collisionNorm, collisionFloorDot)) {\n            RemovePositiveZComponentFromNormal(collisionNorm);\n          } else {\n            collisionNorm.normalize();\n          }\n        } else {\n          RemovePositiveZComponentFromNormal(collisionNorm);\n        }\n      }\n\n      mState = actor.PredictMotion_Internal(collideDt);\n    }\n\n    mState.x0_translation = normTrans * static_cast<float>(mag);\n    actor.AddMotionState(mState);\n\n    if (collisionInfo.IsValid()) {\n      zeus::CVector3f vel =\n          actor.GetVelocity().canBeNormalized()\n              ? CGroundMovement::CollisionDamping(actor.GetVelocity(), actor.GetVelocity().normalized(), collisionNorm,\n                                                  opts.x24_dampedNormalCoefficient, opts.x28_dampedDeltaCoefficient)\n              : zeus::skZero3f;\n      float elasticForce = floor ? opts.x2c_floorElasticForce\n                                 : opts.x34_wallElasticLinear * collisionFloorDot + opts.x30_wallElasticConstant;\n      float dot = collisionNorm.dot(vel);\n      if (dot < elasticForce) {\n        vel += (elasticForce - dot) * collisionNorm;\n      }\n      if (clipCollision && floorCollision) {\n        if (!CGroundMovement::RemoveNormalComponent(floorPlaneNormal, vel)) {\n          vel.z() = 0.f;\n        }\n      }\n      if (vel.z() > opts.x38_maxPositiveVerticalVelocity) {\n        vel *= zeus::CVector3f(opts.x38_maxPositiveVerticalVelocity / vel.z());\n      }\n\n      if (opts.x18_dampForceAndMomentum) {\n        if (actor.x15c_force.canBeNormalized()) {\n          // zeus::CVector3f prevForce = actor.x15c_force;\n          actor.x15c_force = CGroundMovement::CollisionDamping(actor.x15c_force, actor.x15c_force.normalized(),\n                                                               collisionNorm, 0.f, 1.f);\n        }\n        if (actor.x150_momentum.canBeNormalized()) {\n          actor.x150_momentum = CGroundMovement::CollisionDamping(actor.x150_momentum, actor.x150_momentum.normalized(),\n                                                                  collisionNorm, 0.f, 1.f);\n        }\n      }\n\n      if (opts.x0_setWaterLandingForce && !floor) {\n        if (collisionInfo.GetNormalLeft().z() < -0.1f && vel.z() > 0.f) {\n          vel.z() *= 0.5f;\n        }\n\n        float zNormAbs = std::fabs(collisionInfo.GetNormalLeft().z());\n        if ((zNormAbs > opts.x10_downwardZThreshold && vel.z() < 0.f) || zNormAbs > opts.xc_anyZThreshold) {\n          actor.x15c_force = zeus::CVector3f(\n              0.f, 0.f,\n              -(1.f + std::max(opts.x4_waterLandingForceCoefficient * zNormAbs, opts.x8_minimumWaterLandingForce)) *\n                  actor.GetWeight());\n          vel *= zeus::CVector3f(1.f - opts.x14_waterLandingVelocityReduction);\n        }\n      }\n\n      actor.SetVelocityWR(vel);\n    } else {\n      zeus::CVector3f vel = actor.x138_velocity;\n      if (actor.x138_velocity.z() > opts.x38_maxPositiveVerticalVelocity) {\n        vel *= zeus::CVector3f(opts.x38_maxPositiveVerticalVelocity / vel.z());\n      }\n\n      actor.SetVelocityWR(vel);\n    }\n\n    actor.ClearImpulses();\n\n    remDt -= collideDt;\n    if (i >= opts.x1c_maxCollisionCycles) {\n      break;\n    }\n  }\n\n  result.x70_processedDt = dt - remDt;\n  return ret;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CGroundMovement.hpp",
    "content": "#pragma once\n\n#include <optional>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Collision/CCollisionInfo.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CAreaCollisionCache;\nclass CCollisionInfoList;\nclass CMaterialFilter;\nclass CPhysicsActor;\nclass CStateManager;\n\nclass CGroundMovement {\npublic:\n  struct SMovementOptions {\n    bool x0_setWaterLandingForce;\n    float x4_waterLandingForceCoefficient;\n    float x8_minimumWaterLandingForce;\n    float xc_anyZThreshold;\n    float x10_downwardZThreshold;\n    float x14_waterLandingVelocityReduction;\n    bool x18_dampForceAndMomentum;\n    bool x19_alwaysClip;\n    bool x1a_disableClipForFloorOnly;\n    u32 x1c_maxCollisionCycles;\n    float x20_minimumTranslationDelta;\n    float x24_dampedNormalCoefficient;\n    float x28_dampedDeltaCoefficient;\n    float x2c_floorElasticForce;\n    float x30_wallElasticConstant;\n    float x34_wallElasticLinear;\n    float x38_maxPositiveVerticalVelocity;\n    std::optional<zeus::CVector3f> x3c_floorPlaneNormal;\n  };\n\n  struct SMoveObjectResult {\n    std::optional<TUniqueId> x0_id;\n    std::optional<CCollisionInfo> x8_collision;\n    u32 x6c_processedCollisions;\n    float x70_processedDt;\n  };\n\n  static void CheckFalling(CPhysicsActor& actor, CStateManager& mgr, float);\n  static void MoveGroundCollider(CStateManager& mgr, CPhysicsActor& actor, float dt, const EntityList* nearList);\n  static bool ResolveUpDown(CAreaCollisionCache& cache, CStateManager& mgr, CPhysicsActor& actor,\n                            const CMaterialFilter& filter, EntityList& nearList, float, float, float&,\n                            CCollisionInfoList& list);\n  static bool MoveGroundColliderZ(CAreaCollisionCache& cache, CStateManager& mgr, CPhysicsActor& actor,\n                                  const CMaterialFilter& filter, EntityList& nearList, float, float&,\n                                  CCollisionInfoList& list, TUniqueId& idOut);\n  static void MoveGroundColliderXY(CAreaCollisionCache& cache, CStateManager& mgr, CPhysicsActor& actor,\n                                   const CMaterialFilter& filter, EntityList& nearList, float);\n  static zeus::CVector3f CollisionDamping(const zeus::CVector3f& a, const zeus::CVector3f& b, const zeus::CVector3f& c,\n                                          float d, float e);\n  static void MoveGroundCollider_New(CStateManager& mgr, CPhysicsActor& actor, float, const EntityList* nearList);\n  static bool RemoveNormalComponent(const zeus::CVector3f&, const zeus::CVector3f&, zeus::CVector3f&, float&);\n  static bool RemoveNormalComponent(const zeus::CVector3f& a, zeus::CVector3f& b);\n  static CMaterialList MoveObjectAnalytical(CStateManager& mgr, CPhysicsActor& actor, float, EntityList& nearList,\n                                            CAreaCollisionCache& cache, const SMovementOptions& opts,\n                                            SMoveObjectResult& result);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CHalfTransition.cpp",
    "content": "#include \"Runtime/Character/CHalfTransition.hpp\"\n\n#include \"Runtime/Character/CMetaTransFactory.hpp\"\n\nnamespace metaforce {\n\nCHalfTransition::CHalfTransition(CInputStream& in) {\n  x0_id = in.ReadLong();\n  x4_trans = CMetaTransFactory::CreateMetaTrans(in);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CHalfTransition.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/Character/IMetaTrans.hpp\"\n\nnamespace metaforce {\n\nclass CHalfTransition {\n  u32 x0_id;\n  std::shared_ptr<IMetaTrans> x4_trans;\n\npublic:\n  explicit CHalfTransition(CInputStream& in);\n  u32 GetId() const { return x0_id; }\n  const std::shared_ptr<IMetaTrans>& GetMetaTrans() const { return x4_trans; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CHierarchyPoseBuilder.cpp",
    "content": "#include \"Runtime/Character/CHierarchyPoseBuilder.hpp\"\n\n#include \"Runtime/Character/CAnimData.hpp\"\n#include \"Runtime/Character/CCharLayoutInfo.hpp\"\n\n#include <zeus/CEulerAngles.hpp>\n\nnamespace metaforce {\n\nvoid CHierarchyPoseBuilder::BuildIntoHierarchy(const CCharLayoutInfo& layout, const CSegId& boneId,\n                                               const CSegId& nullId) {\n  if (!x38_treeMap.HasElement(boneId)) {\n    const CCharLayoutNode::Bone& bone = layout.GetRootNode()->GetBoneMap()[boneId];\n    if (bone.x0_parentId == nullId) {\n      x30_rootId = boneId;\n      x34_hasRoot = true;\n      zeus::CVector3f origin = layout.GetFromParentUnrotated(boneId);\n      CTreeNode& node = x38_treeMap[boneId];\n      node.x14_offset = origin;\n    } else {\n      BuildIntoHierarchy(layout, bone.x0_parentId, nullId);\n      zeus::CVector3f origin = layout.GetFromParentUnrotated(boneId);\n      CTreeNode& pNode = x38_treeMap[bone.x0_parentId];\n      CTreeNode& node = x38_treeMap[boneId];\n      node.x14_offset = origin;\n      node.x1_sibling = pNode.x0_child;\n      pNode.x0_child = boneId;\n    }\n  }\n}\n\nvoid CHierarchyPoseBuilder::RecursivelyBuildNoScale(const CSegId& boneId, const CTreeNode& node,\n                                                    CPoseAsTransforms& pose, const zeus::CQuaternion& parentRot,\n                                                    const zeus::CMatrix3f& parentXf,\n                                                    const zeus::CVector3f& parentOffset) const {\n  zeus::CQuaternion quat = parentRot * node.x4_rotation;\n  zeus::CMatrix3f xf = quat;\n  zeus::CVector3f xfOffset = parentXf * node.x14_offset + parentOffset;\n  pose.Insert(boneId, quat, xfOffset);\n\n  CSegId curBone = node.x0_child;\n  while (curBone != 0) {\n    const CTreeNode& node = x38_treeMap[curBone];\n    RecursivelyBuild(curBone, node, pose, quat, xf, xfOffset);\n    curBone = node.x1_sibling;\n  }\n}\n\nvoid CHierarchyPoseBuilder::RecursivelyBuild(const CSegId& boneId, const CTreeNode& node, CPoseAsTransforms& pose,\n                                             const zeus::CQuaternion& parentRot, const zeus::CMatrix3f& parentXf,\n                                             const zeus::CVector3f& parentOffset) const {\n  zeus::CQuaternion quat = parentRot * node.x4_rotation;\n\n  float scale;\n  if (x0_layoutDesc.GetScaledLayoutDescription()) {\n    scale = x0_layoutDesc.GetScaledLayoutDescription()->GlobalScale();\n  } else {\n    scale = 1.f;\n  }\n\n  zeus::CMatrix3f rotation;\n  if (scale == 1.f)\n    rotation = quat;\n  else\n    rotation = parentXf * (zeus::CMatrix3f{node.x4_rotation} * zeus::CMatrix3f{scale});\n\n  zeus::CVector3f offset = parentOffset + (parentXf * node.x14_offset);\n  pose.Insert(boneId, rotation, offset);\n\n  CSegId curBone = node.x0_child;\n  while (curBone != 0) {\n    const CTreeNode& node = x38_treeMap[curBone];\n    RecursivelyBuild(curBone, node, pose, quat, quat, offset);\n    curBone = node.x1_sibling;\n  }\n}\n\nvoid CHierarchyPoseBuilder::BuildTransform(const CSegId& boneId, zeus::CTransform& xfOut) const {\n  TLockedToken<CCharLayoutInfo> layoutInfoTok;\n  float scale;\n  if (x0_layoutDesc.GetScaledLayoutDescription()) {\n    layoutInfoTok = x0_layoutDesc.GetScaledLayoutDescription()->ScaledLayout();\n    scale = x0_layoutDesc.GetScaledLayoutDescription()->GlobalScale();\n  } else {\n    layoutInfoTok = x0_layoutDesc.GetCharLayoutInfo();\n    scale = 1.f;\n  }\n  const CCharLayoutInfo& layoutInfo = *layoutInfoTok.GetObj();\n\n  u32 idCount = 0;\n  CSegId buildIDs[100];\n  {\n    CSegId curId = boneId;\n    while (curId != 2) {\n      buildIDs[idCount++] = curId;\n      curId = layoutInfo.GetRootNode()->GetBoneMap()[curId].x0_parentId;\n    }\n  }\n\n  zeus::CQuaternion accumRot;\n  zeus::CMatrix3f accumXF;\n  zeus::CVector3f accumPos;\n  for (CSegId* id = &buildIDs[idCount]; id != buildIDs; --id) {\n    CSegId& thisId = id[-1];\n    const CTreeNode& node = x38_treeMap[thisId];\n    accumRot *= node.x4_rotation;\n    accumPos += accumXF * node.x14_offset;\n    if (scale == 1.f)\n      accumXF = accumRot;\n    else\n      accumXF = accumXF * zeus::CMatrix3f(node.x4_rotation) * zeus::CMatrix3f(scale);\n  }\n\n  xfOut.setRotation(accumXF);\n  xfOut.origin = accumPos;\n}\n\nvoid CHierarchyPoseBuilder::BuildNoScale(CPoseAsTransforms& pose) {\n  pose.Clear();\n  const CTreeNode& node = x38_treeMap[x30_rootId];\n  zeus::CQuaternion quat;\n  zeus::CMatrix3f mtx;\n  zeus::CVector3f vec;\n  RecursivelyBuildNoScale(x30_rootId, node, pose, quat, mtx, vec);\n}\n\nvoid CHierarchyPoseBuilder::Insert(const CSegId& boneId, const zeus::CQuaternion& quat) {\n  CTreeNode& node = x38_treeMap[boneId];\n  node.x4_rotation = quat;\n}\n\nvoid CHierarchyPoseBuilder::Insert(const CSegId& boneId, const zeus::CQuaternion& quat, const zeus::CVector3f& offset) {\n  CTreeNode& node = x38_treeMap[boneId];\n  node.x4_rotation = quat;\n  node.x14_offset = offset;\n}\n\nCHierarchyPoseBuilder::CHierarchyPoseBuilder(const CLayoutDescription& layout)\n: x0_layoutDesc(layout), x38_treeMap(layout.GetCharLayoutInfo()->GetSegIdList().GetList().size()) {\n  TLockedToken<CCharLayoutInfo> layoutInfoTok;\n  if (layout.GetScaledLayoutDescription())\n    layoutInfoTok = layout.GetScaledLayoutDescription()->ScaledLayout();\n  else\n    layoutInfoTok = layout.GetCharLayoutInfo();\n  const CCharLayoutInfo& layoutInfo = *layoutInfoTok.GetObj();\n\n  const CSegIdList& segIDs = layoutInfo.GetSegIdList();\n  for (const CSegId& id : segIDs.GetList())\n    BuildIntoHierarchy(layoutInfo, id, 2);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CHierarchyPoseBuilder.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Character/CLayoutDescription.hpp\"\n#include \"Runtime/Character/CSegId.hpp\"\n#include \"Runtime/Character/TSegIdMap.hpp\"\n\n#include <zeus/CQuaternion.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CCharLayoutInfo;\nclass CLayoutDescription;\nclass CPoseAsTransforms;\n\nclass CHierarchyPoseBuilder {\npublic:\n  struct CTreeNode {\n    CSegId x0_child = 0;\n    CSegId x1_sibling = 0;\n    zeus::CQuaternion x4_rotation;\n    zeus::CVector3f x14_offset;\n  };\n\nprivate:\n  CLayoutDescription x0_layoutDesc;\n  CSegId x30_rootId;\n  bool x34_hasRoot = false;\n  TSegIdMap<CTreeNode> x38_treeMap;\n\n  void BuildIntoHierarchy(const CCharLayoutInfo& layout, const CSegId& boneId, const CSegId& nullId);\n  void RecursivelyBuildNoScale(const CSegId& boneId, const CTreeNode& node, CPoseAsTransforms& pose,\n                               const zeus::CQuaternion& rot, const zeus::CMatrix3f& scale,\n                               const zeus::CVector3f& offset) const;\n  void RecursivelyBuild(const CSegId& boneId, const CTreeNode& node, CPoseAsTransforms& pose,\n                        const zeus::CQuaternion& rot, const zeus::CMatrix3f& scale,\n                        const zeus::CVector3f& offset) const;\n\npublic:\n  explicit CHierarchyPoseBuilder(const CLayoutDescription& layout);\n\n  const TLockedToken<CCharLayoutInfo>& CharLayoutInfo() const { return x0_layoutDesc.ScaledLayout(); }\n  bool HasRoot() const { return x34_hasRoot; }\n  void BuildTransform(const CSegId& boneId, zeus::CTransform& xfOut) const;\n  void BuildNoScale(CPoseAsTransforms& pose);\n  void Insert(const CSegId& boneId, const zeus::CQuaternion& quat);\n  void Insert(const CSegId& boneId, const zeus::CQuaternion& quat, const zeus::CVector3f& offset);\n  TSegIdMap<CTreeNode>& GetTreeMap() { return x38_treeMap; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CIkChain.cpp",
    "content": "#include \"Runtime/Character/CIkChain.hpp\"\n\n#include \"Runtime/Character/CAnimData.hpp\"\n\nnamespace metaforce {\n\nvoid CIkChain::Update(float dt) {\n  if (x44_24_activated)\n    x40_time = zeus::min(x40_time + dt, 1.f);\n  else\n    x40_time = zeus::max(0.f, x40_time - dt);\n}\n\nvoid CIkChain::Deactivate() { x44_24_activated = false; }\n\nvoid CIkChain::Activate(const CAnimData& animData, const CSegId& segId, const zeus::CTransform& xf) {\n  x0_bone = segId;\n  const auto& info = animData.GetPoseBuilder().CharLayoutInfo();\n  x1_p1 = info->GetRootNode()->GetBoneMap()[x0_bone].x0_parentId;\n  if (x1_p1 != 2) {\n    x2_p2 = info->GetRootNode()->GetBoneMap()[x1_p1].x0_parentId;\n    x4_p2p1Dir = info->GetFromParentUnrotated(x1_p1);\n    x1c_p2p1Length = x4_p2p1Dir.magnitude();\n    x4_p2p1Dir = x4_p2p1Dir / x1c_p2p1Length;\n    x10_p1BoneDir = info->GetFromParentUnrotated(x0_bone);\n    x20_p1BoneLength = x10_p1BoneDir.magnitude();\n    x10_p1BoneDir = x10_p1BoneDir / x20_p1BoneLength;\n    x34_holdPos = xf.origin;\n    x24_holdRot = zeus::CQuaternion(xf.basis);\n    x44_24_activated = true;\n  }\n}\n\nvoid CIkChain::PreRender(CAnimData& animData, const zeus::CTransform& xf, const zeus::CVector3f& scale) {\n  if (x40_time > 0.f) {\n    zeus::CTransform p2Xf = animData.GetLocatorTransform(x2_p2, nullptr);\n    zeus::CVector3f localDelta = xf.transposeRotate(x34_holdPos - xf.origin);\n    localDelta /= scale;\n    localDelta = p2Xf.transposeRotate(localDelta - p2Xf.origin);\n    zeus::CQuaternion p2Rot = animData.PoseBuilder().GetTreeMap()[x2_p2].x4_rotation;\n    zeus::CQuaternion p1Rot = animData.PoseBuilder().GetTreeMap()[x1_p1].x4_rotation;\n    zeus::CQuaternion boneRot = animData.PoseBuilder().GetTreeMap()[x0_bone].x4_rotation;\n    zeus::CQuaternion newP2Rot = p2Rot;\n    zeus::CQuaternion newP1Rot = p1Rot;\n    Solve(newP2Rot, newP1Rot, localDelta);\n    zeus::CQuaternion newBoneRot =\n        (zeus::CQuaternion((xf * p2Xf).basis) * p2Rot.inverse() * newP2Rot * newP1Rot).inverse() * x24_holdRot;\n    if (x40_time < 1.f) {\n      newP2Rot = zeus::CQuaternion::slerpShort(p2Rot, newP2Rot, x40_time);\n      newP1Rot = zeus::CQuaternion::slerpShort(p1Rot, newP1Rot, x40_time);\n      newBoneRot = zeus::CQuaternion::slerpShort(boneRot, newBoneRot, x40_time);\n    }\n    animData.PoseBuilder().GetTreeMap()[x2_p2].x4_rotation = newP2Rot;\n    animData.PoseBuilder().GetTreeMap()[x1_p1].x4_rotation = newP1Rot;\n    animData.PoseBuilder().GetTreeMap()[x0_bone].x4_rotation = newBoneRot;\n    animData.MarkPoseDirty();\n  }\n}\n\nvoid CIkChain::Solve(zeus::CQuaternion& q1, zeus::CQuaternion& q2, const zeus::CVector3f& vec) {\n  const float mag = vec.magnitude();\n  const float magSq = mag * mag;\n  const float twoMag = (2.0f * mag);\n  float f29 =\n      std::acos(zeus::clamp(-1.f,\n                            (((x20_p1BoneLength * magSq) + x20_p1BoneLength) - (x1c_p2p1Length * x1c_p2p1Length)) /\n                                (twoMag * x20_p1BoneLength),\n                            1.f));\n  float f30 = std::acos(zeus::clamp(\n      -1.f,\n      ((x1c_p2p1Length * (magSq - (x20_p1BoneLength * x20_p1BoneLength))) + x1c_p2p1Length) / (twoMag * x1c_p2p1Length),\n      1.f));\n\n  zeus::CVector3f vecA = q2.transform(x10_p1BoneDir);\n  zeus::CVector3f crossVecA = x4_p2p1Dir.cross(vecA);\n  float crossAMag = crossVecA.magnitude();\n  crossVecA *= zeus::CVector3f(1.f / crossVecA.magnitude());\n  float angle = std::asin(zeus::min(crossAMag, 1.f));\n  if (x4_p2p1Dir.dot(vecA) < 0.f)\n    angle = M_PIF - angle;\n  q2 = zeus::CQuaternion::fromAxisAngle(crossVecA, (f30 + f29) - angle) * q2;\n  zeus::CVector3f v1 = q1.transform((x1c_p2p1Length * x4_p2p1Dir) + (x20_p1BoneLength * q2.transform(x10_p1BoneDir)));\n  zeus::CVector3f v2 = q1.transform(vec);\n  zeus::CVector3f crossVecB = v1.normalized().cross((1.f / mag) * v2);\n  angle = std::asin(zeus::min(crossVecB.magnitude(), 1.f));\n  if (v1.dot((1.f / mag) * v2) < 0.f)\n    angle = M_PIF - angle;\n\n  q1 = zeus::CQuaternion::fromAxisAngle(crossVecB * (1.f / crossVecB.magnitude()), angle) * q1;\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CIkChain.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Character/CSegId.hpp\"\n\n#include <zeus/CQuaternion.hpp>\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CAnimData;\nclass CSegId;\nclass CIkChain {\n  CSegId x0_bone;\n  CSegId x1_p1;\n  CSegId x2_p2;\n  zeus::CVector3f x4_p2p1Dir = zeus::skForward;\n  zeus::CVector3f x10_p1BoneDir = zeus::skForward;\n  float x1c_p2p1Length = 1.f;\n  float x20_p1BoneLength = 1.f;\n  zeus::CQuaternion x24_holdRot;\n  zeus::CVector3f x34_holdPos;\n  float x40_time = 0.f;\n  bool x44_24_activated : 1 = false;\n\npublic:\n  CIkChain() = default;\n\n  bool GetActive() const { return x44_24_activated; }\n  void Update(float);\n  void Deactivate();\n  void Activate(const CAnimData&, const CSegId&, const zeus::CTransform&);\n  void PreRender(CAnimData&, const zeus::CTransform&, const zeus::CVector3f&);\n  void Solve(zeus::CQuaternion&, zeus::CQuaternion&, const zeus::CVector3f&);\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CInt32POINode.cpp",
    "content": "#include \"Runtime/Character/CInt32POINode.hpp\"\n\n#include \"Runtime/Character/CAnimSourceReader.hpp\"\n\nnamespace metaforce {\nCInt32POINode::CInt32POINode()\n: CInt32POINode(\"\"sv, EPOIType::EmptyInt32, CCharAnimTime(), -1, false, 1.f, -1, 0, 0, \"root\"sv) {}\n\nCInt32POINode::CInt32POINode(std::string_view name, EPOIType type, const CCharAnimTime& time, s32 index, bool unique,\n                             float weight, s32 charIndex, s32 flags, s32 val, std::string_view locator)\n: CPOINode(name, type, time, index, unique, weight, charIndex, flags), x38_val(val), x3c_locatorName(locator) {}\n\nCInt32POINode::CInt32POINode(CInputStream& in)\n: CPOINode(in), x38_val(in.ReadLong()), x3c_locatorName(in.Get<std::string>()) {}\n\nCInt32POINode CInt32POINode::CopyNodeMinusStartTime(const CInt32POINode& node, const CCharAnimTime& startTime) {\n  CInt32POINode ret = node;\n  ret.x1c_time -= startTime;\n  return ret;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CInt32POINode.hpp",
    "content": "#pragma once\n\n#include <string>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/Character/CPOINode.hpp\"\n\nnamespace metaforce {\nclass IAnimSourceInfo;\n\nclass CInt32POINode : public CPOINode {\n  s32 x38_val;\n  std::string x3c_locatorName;\n\npublic:\n  CInt32POINode();\n  CInt32POINode(std::string_view, EPOIType, const CCharAnimTime&, s32, bool, float, s32, s32, s32, std::string_view);\n  explicit CInt32POINode(CInputStream& in);\n  s32 GetValue() const { return x38_val; }\n  std::string_view GetLocatorName() const { return x3c_locatorName; }\n\n  static CInt32POINode CopyNodeMinusStartTime(const CInt32POINode& node, const CCharAnimTime& startTime);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CLayoutDescription.hpp",
    "content": "#pragma once\n\n#include <optional>\n\n#include \"Runtime/CToken.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CCharLayoutInfo;\n\nclass CLayoutDescription {\npublic:\n  class CScaledLayoutDescription {\n    TLockedToken<CCharLayoutInfo> x0_layoutToken;\n    float xc_scale = 0.0f;\n    std::optional<zeus::CVector3f> x10_scaleVec;\n\n  public:\n    const TLockedToken<CCharLayoutInfo>& ScaledLayout() const { return x0_layoutToken; }\n    float GlobalScale() const { return xc_scale; }\n    const std::optional<zeus::CVector3f>& GetScaleVec() const { return x10_scaleVec; }\n  };\n\nprivate:\n  TLockedToken<CCharLayoutInfo> x0_layoutToken;\n  std::optional<CScaledLayoutDescription> xc_scaled;\n\npublic:\n  explicit CLayoutDescription(const TLockedToken<CCharLayoutInfo>& token) : x0_layoutToken(token) {}\n\n  const std::optional<CScaledLayoutDescription>& GetScaledLayoutDescription() const { return xc_scaled; }\n\n  const TLockedToken<CCharLayoutInfo>& GetCharLayoutInfo() const { return x0_layoutToken; }\n  bool UsesScale() const { return bool(xc_scaled); }\n  const TLockedToken<CCharLayoutInfo>& ScaledLayout() const {\n    if (UsesScale())\n      return xc_scaled->ScaledLayout();\n    return x0_layoutToken;\n  }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CMakeLists.txt",
    "content": "set(CHARACTER_SOURCES\n        CharacterCommon.hpp CharacterCommon.cpp\n        CAssetFactory.hpp CAssetFactory.cpp\n        CCharacterFactory.hpp CCharacterFactory.cpp\n        CModelData.hpp CModelData.cpp\n        CAnimData.hpp CAnimData.cpp\n        CCharAnimTime.hpp CCharAnimTime.cpp\n        IMetaAnim.hpp IMetaAnim.cpp\n        IMetaTrans.hpp\n        IVaryingAnimationTimeScale.hpp\n        CAnimationDatabase.hpp\n        CAnimationDatabaseGame.hpp CAnimationDatabaseGame.cpp\n        CTransitionDatabase.hpp\n        CTransitionDatabaseGame.hpp CTransitionDatabaseGame.cpp\n        CHierarchyPoseBuilder.hpp CHierarchyPoseBuilder.cpp\n        CPoseAsTransforms.hpp CPoseAsTransforms.cpp\n        CCharLayoutInfo.hpp CCharLayoutInfo.cpp\n        CLayoutDescription.hpp\n        CSegIdList.hpp CSegIdList.cpp\n        CSegId.hpp\n        TSegIdMap.hpp\n        CIkChain.hpp CIkChain.cpp\n        CSkinRules.hpp CSkinRules.cpp\n        CAnimCharacterSet.hpp CAnimCharacterSet.cpp\n        CAnimationSet.hpp CAnimationSet.cpp\n        CCharacterSet.hpp CCharacterSet.cpp\n        CCharacterInfo.hpp CCharacterInfo.cpp\n        CPASDatabase.hpp CPASDatabase.cpp\n        CPASAnimState.hpp CPASAnimState.cpp\n        CPASParmInfo.hpp CPASParmInfo.cpp\n        CPASAnimInfo.hpp CPASAnimInfo.cpp\n        CPASAnimParm.hpp\n        CPASAnimParmData.hpp CPASAnimParmData.cpp\n        CEffectComponent.hpp CEffectComponent.cpp\n        CAnimation.hpp CAnimation.cpp\n        CAnimationManager.hpp CAnimationManager.cpp\n        CTransition.hpp CTransition.cpp\n        CTransitionManager.hpp CTransitionManager.cpp\n        CMetaAnimFactory.hpp CMetaAnimFactory.cpp\n        CMetaAnimPlay.hpp CMetaAnimPlay.cpp\n        CMetaAnimBlend.hpp CMetaAnimBlend.cpp\n        CMetaAnimPhaseBlend.hpp CMetaAnimPhaseBlend.cpp\n        CMetaAnimRandom.hpp CMetaAnimRandom.cpp\n        CMetaAnimSequence.hpp CMetaAnimSequence.cpp\n        CMetaTransFactory.hpp CMetaTransFactory.cpp\n        CMetaTransMetaAnim.hpp CMetaTransMetaAnim.cpp\n        CMetaTransTrans.hpp CMetaTransTrans.cpp\n        CMetaTransPhaseTrans.hpp CMetaTransPhaseTrans.cpp\n        CMetaTransSnap.hpp CMetaTransSnap.cpp\n        CAnimTreeLoopIn.hpp CAnimTreeLoopIn.cpp\n        CAnimTreeSequence.hpp CAnimTreeSequence.cpp\n        CSequenceHelper.hpp CSequenceHelper.cpp\n        CAnimTreeAnimReaderContainer.hpp CAnimTreeAnimReaderContainer.cpp\n        CTreeUtils.hpp CTreeUtils.cpp\n        CAnimTreeBlend.hpp CAnimTreeBlend.cpp\n        CAnimTreeNode.hpp CAnimTreeNode.cpp\n        CAnimTreeTimeScale.hpp CAnimTreeTimeScale.cpp\n        CAnimTreeTransition.hpp CAnimTreeTransition.cpp\n        CAnimTreeTweenBase.hpp CAnimTreeTweenBase.cpp\n        CAnimTreeSingleChild.hpp CAnimTreeSingleChild.cpp\n        CAnimTreeDoubleChild.hpp CAnimTreeDoubleChild.cpp\n        CAnimPlaybackParms.hpp\n        IAnimReader.hpp IAnimReader.cpp\n        CPrimitive.hpp CPrimitive.cpp\n        CHalfTransition.hpp CHalfTransition.cpp\n        CTimeScaleFunctions.hpp CTimeScaleFunctions.cpp\n        CParticleData.hpp CParticleData.cpp\n        CParticleDatabase.hpp CParticleDatabase.cpp\n        CParticleGenInfo.hpp CParticleGenInfo.cpp\n        CAnimPOIData.hpp CAnimPOIData.cpp\n        CPOINode.hpp CPOINode.cpp\n        CBoolPOINode.hpp CBoolPOINode.cpp\n        CInt32POINode.hpp CInt32POINode.cpp\n        CSoundPOINode.hpp CSoundPOINode.cpp\n        CParticlePOINode.hpp CParticlePOINode.cpp\n        CAnimSourceReader.hpp CAnimSourceReader.cpp\n        CAnimSource.hpp CAnimSource.cpp\n        CFBStreamedAnimReader.hpp CFBStreamedAnimReader.cpp\n        CFBStreamedCompression.hpp CFBStreamedCompression.cpp\n        CAllFormatsAnimSource.hpp CAllFormatsAnimSource.cpp\n        CSegStatementSet.hpp CSegStatementSet.cpp\n        CAnimPerSegmentData.hpp\n        CAdditiveAnimPlayback.hpp CAdditiveAnimPlayback.cpp\n        CActorLights.hpp CActorLights.cpp\n        CAnimSysContext.hpp\n        CBodyState.hpp CBodyState.cpp\n        CAdditiveBodyState.hpp CAdditiveBodyState.cpp\n        CBodyStateCmdMgr.hpp CBodyStateCmdMgr.cpp\n        CBodyController.hpp CBodyController.cpp\n        CGroundMovement.hpp CGroundMovement.cpp\n        CSteeringBehaviors.hpp CSteeringBehaviors.cpp\n        CBodyStateInfo.hpp CBodyStateInfo.cpp\n        CBoneTracking.hpp CBoneTracking.cpp\n        CRagDoll.hpp CRagDoll.cpp)\n\nruntime_add_list(Character CHARACTER_SOURCES)\n"
  },
  {
    "path": "Runtime/Character/CMetaAnimBlend.cpp",
    "content": "#include \"Runtime/Character/CMetaAnimBlend.hpp\"\n\n#include \"Runtime/Character/CAnimTreeBlend.hpp\"\n#include \"Runtime/Character/CMetaAnimFactory.hpp\"\n\nnamespace metaforce {\n\nCMetaAnimBlend::CMetaAnimBlend(CInputStream& in) {\n  x4_animA = CMetaAnimFactory::CreateMetaAnim(in);\n  x8_animB = CMetaAnimFactory::CreateMetaAnim(in);\n  xc_blend = in.ReadFloat();\n  x10_ = in.ReadBool();\n}\n\nvoid CMetaAnimBlend::GetUniquePrimitives(std::set<CPrimitive>& primsOut) const {\n  x4_animA->GetUniquePrimitives(primsOut);\n  x4_animA->GetUniquePrimitives(primsOut);\n}\n\nstd::shared_ptr<CAnimTreeNode> CMetaAnimBlend::VGetAnimationTree(const CAnimSysContext& animSys,\n                                                                 const CMetaAnimTreeBuildOrders& orders) const {\n  CMetaAnimTreeBuildOrders oa = CMetaAnimTreeBuildOrders::NoSpecialOrders();\n  CMetaAnimTreeBuildOrders ob = orders.x0_recursiveAdvance\n                                    ? CMetaAnimTreeBuildOrders::PreAdvanceForAll(*orders.x0_recursiveAdvance)\n                                    : CMetaAnimTreeBuildOrders::NoSpecialOrders();\n  auto a = x4_animA->GetAnimationTree(animSys, oa);\n  auto b = x8_animB->GetAnimationTree(animSys, ob);\n  return std::make_shared<CAnimTreeBlend>(x10_, a, b, xc_blend, CAnimTreeBlend::CreatePrimitiveName(a, b, xc_blend));\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CMetaAnimBlend.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/Character/IMetaAnim.hpp\"\n\nnamespace metaforce {\n\nclass CMetaAnimBlend : public IMetaAnim {\n  std::shared_ptr<IMetaAnim> x4_animA;\n  std::shared_ptr<IMetaAnim> x8_animB;\n  float xc_blend;\n  bool x10_;\n\npublic:\n  explicit CMetaAnimBlend(CInputStream& in);\n  EMetaAnimType GetType() const override { return EMetaAnimType::Blend; }\n\n  void GetUniquePrimitives(std::set<CPrimitive>& primsOut) const override;\n  std::shared_ptr<CAnimTreeNode> VGetAnimationTree(const CAnimSysContext& animSys,\n                                                   const CMetaAnimTreeBuildOrders& orders) const override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CMetaAnimFactory.cpp",
    "content": "#include \"Runtime/Character/CMetaAnimFactory.hpp\"\n\n#include \"Runtime/Character/CMetaAnimBlend.hpp\"\n#include \"Runtime/Character/CMetaAnimPhaseBlend.hpp\"\n#include \"Runtime/Character/CMetaAnimPlay.hpp\"\n#include \"Runtime/Character/CMetaAnimRandom.hpp\"\n#include \"Runtime/Character/CMetaAnimSequence.hpp\"\n\nnamespace metaforce {\n\nstd::shared_ptr<IMetaAnim> CMetaAnimFactory::CreateMetaAnim(CInputStream& in) {\n  EMetaAnimType type = EMetaAnimType(in.ReadLong());\n\n  switch (type) {\n  case EMetaAnimType::Play:\n    return std::make_shared<CMetaAnimPlay>(in);\n  case EMetaAnimType::Blend:\n    return std::make_shared<CMetaAnimBlend>(in);\n  case EMetaAnimType::PhaseBlend:\n    return std::make_shared<CMetaAnimPhaseBlend>(in);\n  case EMetaAnimType::Random:\n    return std::make_shared<CMetaAnimRandom>(in);\n  case EMetaAnimType::Sequence:\n    return std::make_shared<CMetaAnimSequence>(in);\n  default:\n    break;\n  }\n\n  return {};\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CMetaAnimFactory.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/Character/IMetaAnim.hpp\"\n\nnamespace metaforce {\n\nclass CMetaAnimFactory {\npublic:\n  static std::shared_ptr<IMetaAnim> CreateMetaAnim(CInputStream& in);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CMetaAnimPhaseBlend.cpp",
    "content": "#include \"Runtime/Character/CMetaAnimPhaseBlend.hpp\"\n\n#include \"Runtime/Character/CAnimTreeBlend.hpp\"\n#include \"Runtime/Character/CAnimTreeTimeScale.hpp\"\n#include \"Runtime/Character/CMetaAnimFactory.hpp\"\n\nnamespace metaforce {\n\nCMetaAnimPhaseBlend::CMetaAnimPhaseBlend(CInputStream& in) {\n  x4_animA = CMetaAnimFactory::CreateMetaAnim(in);\n  x8_animB = CMetaAnimFactory::CreateMetaAnim(in);\n  xc_blend = in.ReadFloat();\n  x10_ = in.ReadBool();\n}\n\nvoid CMetaAnimPhaseBlend::GetUniquePrimitives(std::set<CPrimitive>& primsOut) const {\n  x4_animA->GetUniquePrimitives(primsOut);\n  x8_animB->GetUniquePrimitives(primsOut);\n}\n\nstd::shared_ptr<CAnimTreeNode> CMetaAnimPhaseBlend::VGetAnimationTree(const CAnimSysContext& animSys,\n                                                                      const CMetaAnimTreeBuildOrders& orders) const {\n  if (orders.x0_recursiveAdvance)\n    return GetAnimationTree(animSys, CMetaAnimTreeBuildOrders::PreAdvanceForAll(*orders.x0_recursiveAdvance));\n\n  auto a = x4_animA->GetAnimationTree(animSys, CMetaAnimTreeBuildOrders::NoSpecialOrders());\n  auto b = x8_animB->GetAnimationTree(animSys, CMetaAnimTreeBuildOrders::NoSpecialOrders());\n  auto da = a->GetContributionOfHighestInfluence().GetSteadyStateAnimInfo().GetDuration();\n  auto db = b->GetContributionOfHighestInfluence().GetSteadyStateAnimInfo().GetDuration();\n  auto dblend = da + (db - da) * xc_blend;\n  float fa = da / dblend;\n  float fb = db / dblend;\n\n  auto tsa = std::make_shared<CAnimTreeTimeScale>(\n      a, fa, CAnimTreeTimeScale::CreatePrimitiveName(a, fa, CCharAnimTime::Infinity(), -1.f));\n  auto tsb = std::make_shared<CAnimTreeTimeScale>(\n      b, fb, CAnimTreeTimeScale::CreatePrimitiveName(b, fb, CCharAnimTime::Infinity(), -1.f));\n\n  return std::make_shared<CAnimTreeBlend>(x10_, tsa, tsb, xc_blend,\n                                          CAnimTreeBlend::CreatePrimitiveName(tsa, tsb, xc_blend));\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CMetaAnimPhaseBlend.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/Character/IMetaAnim.hpp\"\n\nnamespace metaforce {\n\nclass CMetaAnimPhaseBlend : public IMetaAnim {\n  std::shared_ptr<IMetaAnim> x4_animA;\n  std::shared_ptr<IMetaAnim> x8_animB;\n  float xc_blend;\n  bool x10_;\n\npublic:\n  explicit CMetaAnimPhaseBlend(CInputStream& in);\n  EMetaAnimType GetType() const override { return EMetaAnimType::PhaseBlend; }\n\n  void GetUniquePrimitives(std::set<CPrimitive>& primsOut) const override;\n  std::shared_ptr<CAnimTreeNode> VGetAnimationTree(const CAnimSysContext& animSys,\n                                                   const CMetaAnimTreeBuildOrders& orders) const override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CMetaAnimPlay.cpp",
    "content": "#include \"Runtime/Character/CMetaAnimPlay.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/Character/CAllFormatsAnimSource.hpp\"\n#include \"Runtime/Character/CAnimSysContext.hpp\"\n#include \"Runtime/Character/CAnimTreeAnimReaderContainer.hpp\"\n\nnamespace metaforce {\n\nCMetaAnimPlay::CMetaAnimPlay(CInputStream& in) : x4_primitive(in), x1c_startTime(in) {}\n\nvoid CMetaAnimPlay::GetUniquePrimitives(std::set<CPrimitive>& primsOut) const { primsOut.insert(x4_primitive); }\n\nstd::shared_ptr<CAnimTreeNode> CMetaAnimPlay::VGetAnimationTree(const CAnimSysContext& animSys,\n                                                                const CMetaAnimTreeBuildOrders& orders) const {\n  if (orders.x0_recursiveAdvance)\n    return GetAnimationTree(animSys, CMetaAnimTreeBuildOrders::PreAdvanceForAll(*orders.x0_recursiveAdvance));\n\n  TLockedToken<CAllFormatsAnimSource> prim =\n      animSys.xc_store.GetObj(SObjectTag{FOURCC('ANIM'), x4_primitive.GetAnimResId()});\n  return std::make_shared<CAnimTreeAnimReaderContainer>(\n      x4_primitive.GetName(), CAllFormatsAnimSource::GetNewReader(prim, x1c_startTime), x4_primitive.GetAnimDbIdx());\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CMetaAnimPlay.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/Character/CPrimitive.hpp\"\n#include \"Runtime/Character/IMetaAnim.hpp\"\n\nnamespace metaforce {\n\nclass CMetaAnimPlay : public IMetaAnim {\n  CPrimitive x4_primitive;\n  CCharAnimTime x1c_startTime;\n\npublic:\n  explicit CMetaAnimPlay(CInputStream& in);\n  EMetaAnimType GetType() const override { return EMetaAnimType::Play; }\n\n  void GetUniquePrimitives(std::set<CPrimitive>& primsOut) const override;\n  std::shared_ptr<CAnimTreeNode> VGetAnimationTree(const CAnimSysContext& animSys,\n                                                   const CMetaAnimTreeBuildOrders& orders) const override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CMetaAnimRandom.cpp",
    "content": "#include \"Runtime/Character/CMetaAnimRandom.hpp\"\n\n#include \"Runtime/Character/CAnimSysContext.hpp\"\n#include \"Runtime/Character/CMetaAnimFactory.hpp\"\n\nnamespace metaforce {\n\nCMetaAnimRandom::RandomData CMetaAnimRandom::CreateRandomData(CInputStream& in) {\n  CMetaAnimRandom::RandomData ret;\n  u32 randCount = in.ReadLong();\n  ret.reserve(randCount);\n\n  for (u32 i = 0; i < randCount; ++i) {\n    std::shared_ptr<IMetaAnim> metaAnim = CMetaAnimFactory::CreateMetaAnim(in);\n    ret.emplace_back(std::move(metaAnim), in.ReadLong());\n  }\n\n  return ret;\n}\n\nCMetaAnimRandom::CMetaAnimRandom(CInputStream& in) : x4_randomData(CreateRandomData(in)) {}\n\nvoid CMetaAnimRandom::GetUniquePrimitives(std::set<CPrimitive>& primsOut) const {\n  for (const auto& pair : x4_randomData)\n    pair.first->GetUniquePrimitives(primsOut);\n}\n\nstd::shared_ptr<CAnimTreeNode> CMetaAnimRandom::VGetAnimationTree(const CAnimSysContext& animSys,\n                                                                  const CMetaAnimTreeBuildOrders& orders) const {\n  const u32 r = animSys.x8_random->Range(1, 100);\n  const std::pair<std::shared_ptr<IMetaAnim>, u32>* useRd = nullptr;\n  for (const auto& rd : x4_randomData) {\n    useRd = &rd;\n    if (r <= rd.second) {\n      break;\n    }\n  }\n\n  return useRd->first->GetAnimationTree(animSys, orders);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CMetaAnimRandom.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <utility>\n#include <vector>\n\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/Character/IMetaAnim.hpp\"\n\nnamespace metaforce {\n\nclass CMetaAnimRandom : public IMetaAnim {\n  using RandomData = std::vector<std::pair<std::shared_ptr<IMetaAnim>, u32>>;\n  RandomData x4_randomData;\n  static RandomData CreateRandomData(CInputStream& in);\n\npublic:\n  explicit CMetaAnimRandom(CInputStream& in);\n  EMetaAnimType GetType() const override { return EMetaAnimType::Random; }\n\n  void GetUniquePrimitives(std::set<CPrimitive>& primsOut) const override;\n  std::shared_ptr<CAnimTreeNode> VGetAnimationTree(const CAnimSysContext& animSys,\n                                                   const CMetaAnimTreeBuildOrders& orders) const override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CMetaAnimSequence.cpp",
    "content": "#include \"Runtime/Character/CMetaAnimSequence.hpp\"\n\n#include \"Runtime/Character/CAnimTreeSequence.hpp\"\n#include \"Runtime/Character/CMetaAnimFactory.hpp\"\n\nnamespace metaforce {\n\nstd::vector<std::shared_ptr<IMetaAnim>> CMetaAnimSequence::CreateSequence(CInputStream& in) {\n  std::vector<std::shared_ptr<IMetaAnim>> ret;\n  u32 seqCount = in.ReadLong();\n  ret.reserve(seqCount);\n\n  for (u32 i = 0; i < seqCount; ++i)\n    ret.push_back(CMetaAnimFactory::CreateMetaAnim(in));\n\n  return ret;\n}\n\nCMetaAnimSequence::CMetaAnimSequence(CInputStream& in) : x4_sequence(CreateSequence(in)) {}\n\nvoid CMetaAnimSequence::GetUniquePrimitives(std::set<CPrimitive>& primsOut) const {\n  for (const std::shared_ptr<IMetaAnim>& anim : x4_sequence)\n    anim->GetUniquePrimitives(primsOut);\n}\n\nstd::shared_ptr<CAnimTreeNode> CMetaAnimSequence::VGetAnimationTree(const CAnimSysContext& animSys,\n                                                                    const CMetaAnimTreeBuildOrders& orders) const {\n  if (orders.x0_recursiveAdvance)\n    return GetAnimationTree(animSys, CMetaAnimTreeBuildOrders::PreAdvanceForAll(*orders.x0_recursiveAdvance));\n\n#if 0\n  /* Originally used to generate name string */\n  std::vector<std::string> anims;\n  anims.reserve(anims.size());\n  for (const std::shared_ptr<IMetaAnim>& anim : x4_sequence) {\n    std::shared_ptr<CAnimTreeNode> chNode = anim->GetAnimationTree(animSys, orders);\n    anims.emplace_back(chNode->GetName());\n  }\n#endif\n\n  return std::make_shared<CAnimTreeSequence>(x4_sequence, animSys, \"\");\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CMetaAnimSequence.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <vector>\n\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/Character/IMetaAnim.hpp\"\n\nnamespace metaforce {\n\nclass CMetaAnimSequence : public IMetaAnim {\n  std::vector<std::shared_ptr<IMetaAnim>> x4_sequence;\n  std::vector<std::shared_ptr<IMetaAnim>> CreateSequence(CInputStream& in);\n\npublic:\n  explicit CMetaAnimSequence(CInputStream& in);\n  EMetaAnimType GetType() const override { return EMetaAnimType::Sequence; }\n\n  void GetUniquePrimitives(std::set<CPrimitive>& primsOut) const override;\n  std::shared_ptr<CAnimTreeNode> VGetAnimationTree(const CAnimSysContext& animSys,\n                                                   const CMetaAnimTreeBuildOrders& orders) const override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CMetaTransFactory.cpp",
    "content": "#include \"Runtime/Character/CMetaTransFactory.hpp\"\n\n#include \"Runtime/Character/CMetaTransMetaAnim.hpp\"\n#include \"Runtime/Character/CMetaTransPhaseTrans.hpp\"\n#include \"Runtime/Character/CMetaTransSnap.hpp\"\n#include \"Runtime/Character/CMetaTransTrans.hpp\"\n\nnamespace metaforce {\n\nstd::shared_ptr<IMetaTrans> CMetaTransFactory::CreateMetaTrans(CInputStream& in) {\n  EMetaTransType type = EMetaTransType(in.ReadLong());\n\n  switch (type) {\n  case EMetaTransType::MetaAnim:\n    return std::make_shared<CMetaTransMetaAnim>(in);\n  case EMetaTransType::Trans:\n    return std::make_shared<CMetaTransTrans>(in);\n  case EMetaTransType::PhaseTrans:\n    return std::make_shared<CMetaTransPhaseTrans>(in);\n  case EMetaTransType::Snap:\n    return std::make_shared<CMetaTransSnap>();\n  default:\n    break;\n  }\n\n  return {};\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CMetaTransFactory.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/Character/IMetaTrans.hpp\"\n\nnamespace metaforce {\n\nclass CMetaTransFactory {\npublic:\n  static std::shared_ptr<IMetaTrans> CreateMetaTrans(CInputStream& in);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CMetaTransMetaAnim.cpp",
    "content": "#include \"Runtime/Character/CMetaTransMetaAnim.hpp\"\n\n#include \"Runtime/Character/CAnimTreeLoopIn.hpp\"\n#include \"Runtime/Character/CMetaAnimFactory.hpp\"\n\nnamespace metaforce {\n\nCMetaTransMetaAnim::CMetaTransMetaAnim(CInputStream& in) : x4_metaAnim(CMetaAnimFactory::CreateMetaAnim(in)) {}\n\nstd::shared_ptr<CAnimTreeNode> CMetaTransMetaAnim::VGetTransitionTree(const std::weak_ptr<CAnimTreeNode>& a,\n                                                                      const std::weak_ptr<CAnimTreeNode>& b,\n                                                                      const CAnimSysContext& animSys) const {\n  std::shared_ptr<CAnimTreeNode> animNode =\n      x4_metaAnim->GetAnimationTree(animSys, CMetaAnimTreeBuildOrders::NoSpecialOrders());\n  return std::make_shared<CAnimTreeLoopIn>(a, b, animNode, animSys,\n                                           CAnimTreeLoopIn::CreatePrimitiveName(a, b, animNode));\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CMetaTransMetaAnim.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/Character/IMetaAnim.hpp\"\n#include \"Runtime/Character/IMetaTrans.hpp\"\n\nnamespace metaforce {\n\nclass CMetaTransMetaAnim : public IMetaTrans {\n  std::shared_ptr<IMetaAnim> x4_metaAnim;\n\npublic:\n  explicit CMetaTransMetaAnim(CInputStream& in);\n  EMetaTransType GetType() const override { return EMetaTransType::MetaAnim; }\n\n  std::shared_ptr<CAnimTreeNode> VGetTransitionTree(const std::weak_ptr<CAnimTreeNode>& a,\n                                                    const std::weak_ptr<CAnimTreeNode>& b,\n                                                    const CAnimSysContext& animSys) const override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CMetaTransPhaseTrans.cpp",
    "content": "#include \"Runtime/Character/CMetaTransPhaseTrans.hpp\"\n\n#include \"Runtime/Character/CAnimTreeNode.hpp\"\n#include \"Runtime/Character/CAnimTreeTimeScale.hpp\"\n#include \"Runtime/Character/CAnimTreeTransition.hpp\"\n#include \"Runtime/Character/CTimeScaleFunctions.hpp\"\n\nnamespace metaforce {\n\nCMetaTransPhaseTrans::CMetaTransPhaseTrans(CInputStream& in) {\n  x4_transDur = CCharAnimTime(in);\n  xc_ = in.ReadBool();\n  xd_runA = in.ReadBool();\n  x10_flags = in.ReadLong();\n}\n\nstd::shared_ptr<CAnimTreeNode> CMetaTransPhaseTrans::VGetTransitionTree(const std::weak_ptr<CAnimTreeNode>& a,\n                                                                        const std::weak_ptr<CAnimTreeNode>& b,\n                                                                        const CAnimSysContext& animSys) const {\n  std::shared_ptr<CAnimTreeNode> nA = a.lock();\n  CAnimTreeEffectiveContribution cA = nA->GetContributionOfHighestInfluence();\n  std::shared_ptr<CAnimTreeNode> nB = b.lock();\n  CAnimTreeEffectiveContribution cB = nB->GetContributionOfHighestInfluence();\n  float y2A = cA.GetSteadyStateAnimInfo().GetDuration() / cB.GetSteadyStateAnimInfo().GetDuration();\n  float y1B = cB.GetSteadyStateAnimInfo().GetDuration() / cA.GetSteadyStateAnimInfo().GetDuration();\n\n  nB->VSetPhase(zeus::clamp(0.f, 1.f - cA.GetTimeRemaining() / cA.GetSteadyStateAnimInfo().GetDuration(), 1.f));\n  auto tsA = std::make_shared<CAnimTreeTimeScale>(\n      a, std::make_unique<CLinearAnimationTimeScale>(CCharAnimTime{}, 1.f, x4_transDur, y2A), x4_transDur,\n      CAnimTreeTimeScale::CreatePrimitiveName(a, 1.f, x4_transDur, y2A));\n  auto tsB = std::make_shared<CAnimTreeTimeScale>(\n      b, std::make_unique<CLinearAnimationTimeScale>(CCharAnimTime{}, y1B, x4_transDur, 1.f), x4_transDur,\n      CAnimTreeTimeScale::CreatePrimitiveName(b, y1B, x4_transDur, 1.f));\n\n  return std::make_shared<CAnimTreeTransition>(\n      xc_, tsA, tsB, x4_transDur, xd_runA, x10_flags,\n      CAnimTreeTransition::CreatePrimitiveName(tsA, tsB, x4_transDur.GetSeconds()));\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CMetaTransPhaseTrans.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/Character/CCharAnimTime.hpp\"\n#include \"Runtime/Character/IMetaTrans.hpp\"\n\nnamespace metaforce {\n\nclass CMetaTransPhaseTrans : public IMetaTrans {\n  CCharAnimTime x4_transDur;\n  bool xc_;\n  bool xd_runA;\n  u32 x10_flags;\n\npublic:\n  explicit CMetaTransPhaseTrans(CInputStream& in);\n  EMetaTransType GetType() const override { return EMetaTransType::PhaseTrans; }\n\n  std::shared_ptr<CAnimTreeNode> VGetTransitionTree(const std::weak_ptr<CAnimTreeNode>& a,\n                                                    const std::weak_ptr<CAnimTreeNode>& b,\n                                                    const CAnimSysContext& animSys) const override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CMetaTransSnap.cpp",
    "content": "#include \"Runtime/Character/CMetaTransSnap.hpp\"\n\nnamespace metaforce {\n\nstd::shared_ptr<CAnimTreeNode> CMetaTransSnap::VGetTransitionTree(const std::weak_ptr<CAnimTreeNode>& a,\n                                                                  const std::weak_ptr<CAnimTreeNode>& b,\n                                                                  const CAnimSysContext& animSys) const {\n  return b.lock();\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CMetaTransSnap.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/Character/IMetaTrans.hpp\"\n\nnamespace metaforce {\n\nclass CMetaTransSnap : public IMetaTrans {\npublic:\n  EMetaTransType GetType() const override { return EMetaTransType::Snap; }\n\n  std::shared_ptr<CAnimTreeNode> VGetTransitionTree(const std::weak_ptr<CAnimTreeNode>& a,\n                                                    const std::weak_ptr<CAnimTreeNode>& b,\n                                                    const CAnimSysContext& animSys) const override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CMetaTransTrans.cpp",
    "content": "#include \"Runtime/Character/CMetaTransTrans.hpp\"\n\n#include \"Runtime/Character/CAnimTreeTransition.hpp\"\n\nnamespace metaforce {\n\nCMetaTransTrans::CMetaTransTrans(CInputStream& in) {\n  x4_transDur = in.Get<CCharAnimTime>();\n  xc_ = in.ReadBool();\n  xd_runA = in.ReadBool();\n  x10_flags = in.ReadLong();\n}\n\nstd::shared_ptr<CAnimTreeNode> CMetaTransTrans::VGetTransitionTree(const std::weak_ptr<CAnimTreeNode>& a,\n                                                                   const std::weak_ptr<CAnimTreeNode>& b,\n                                                                   const CAnimSysContext& animSys) const {\n  return std::make_shared<CAnimTreeTransition>(\n      xc_, a, b, x4_transDur, xd_runA, x10_flags,\n      CAnimTreeTransition::CreatePrimitiveName(a, b, x4_transDur.GetSeconds()));\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CMetaTransTrans.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/Character/CCharAnimTime.hpp\"\n#include \"Runtime/Character/IMetaTrans.hpp\"\n\nnamespace metaforce {\n\nclass CMetaTransTrans : public IMetaTrans {\n  CCharAnimTime x4_transDur;\n  bool xc_;\n  bool xd_runA;\n  u32 x10_flags;\n\npublic:\n  explicit CMetaTransTrans(CInputStream& in);\n  EMetaTransType GetType() const override { return EMetaTransType::Trans; }\n\n  std::shared_ptr<CAnimTreeNode> VGetTransitionTree(const std::weak_ptr<CAnimTreeNode>& a,\n                                                    const std::weak_ptr<CAnimTreeNode>& b,\n                                                    const CAnimSysContext& animSys) const override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CModelData.cpp",
    "content": "#include \"Runtime/Character/CModelData.hpp\"\n\n#include \"Runtime/CPlayerState.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Character/CActorLights.hpp\"\n#include \"Runtime/Character/CAdditiveAnimPlayback.hpp\"\n#include \"Runtime/Character/CAnimData.hpp\"\n#include \"Runtime/Character/CAssetFactory.hpp\"\n#include \"Runtime/Character/CCharacterFactory.hpp\"\n#include \"Runtime/Character/IAnimReader.hpp\"\n#include \"Runtime/Graphics/CGraphics.hpp\"\n#include \"Runtime/Graphics/CSkinnedModel.hpp\"\n#include \"Runtime/Graphics/CVertexMorphEffect.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Logging.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\nnamespace metaforce {\nCModelData::~CModelData() = default;\n\nCModelData::CModelData() {}\nCModelData CModelData::CModelDataNull() { return CModelData(); }\n\nCModelData::CModelData(const CStaticRes& res) : x0_scale(res.GetScale()) {\n  x1c_normalModel = g_SimplePool->GetObj({SBIG('CMDL'), res.GetId()});\n  if (!x1c_normalModel)\n    spdlog::fatal(\"unable to find CMDL {}\", res.GetId());\n}\n\nCModelData::CModelData(const CAnimRes& res) : x0_scale(res.GetScale()) {\n  TToken<CCharacterFactory> factory = g_CharFactoryBuilder->GetFactory(res);\n  x10_animData = factory->CreateCharacter(res.GetCharacterNodeId(), res.CanLoop(), factory, res.GetDefaultAnim());\n}\n\nSAdvancementDeltas CModelData::GetAdvancementDeltas(const CCharAnimTime& a, const CCharAnimTime& b) const {\n  if (x10_animData)\n    return x10_animData->GetAdvancementDeltas(a, b);\n  else\n    return {};\n}\n\nvoid CModelData::Render(const CStateManager& stateMgr, const zeus::CTransform& xf, const CActorLights* lights,\n                        const CModelFlags& drawFlags) {\n  Render(GetRenderingModel(stateMgr), xf, lights, drawFlags);\n}\n\nbool CModelData::IsLoaded(int shaderIdx) {\n  if (x10_animData) {\n    if (!x10_animData->xd8_modelData->GetModel()->IsLoaded(shaderIdx))\n      return false;\n    if (auto* model = x10_animData->xf4_xrayModel.get())\n      if (!model->GetModel()->IsLoaded(shaderIdx))\n        return false;\n    if (auto* model = x10_animData->xf8_infraModel.get())\n      if (!model->GetModel()->IsLoaded(shaderIdx))\n        return false;\n  }\n\n  if (auto* model = x1c_normalModel.GetObj())\n    if (!model->IsLoaded(shaderIdx))\n      return false;\n  if (auto* model = x2c_xrayModel.GetObj())\n    if (!model->IsLoaded(shaderIdx))\n      return false;\n  if (auto* model = x3c_infraModel.GetObj())\n    if (!model->IsLoaded(shaderIdx))\n      return false;\n\n  return true;\n}\n\nu32 CModelData::GetNumMaterialSets() const {\n  if (x10_animData)\n    return x10_animData->GetModelData()->GetModel()->GetNumMaterialSets();\n\n  if (x1c_normalModel)\n    return x1c_normalModel->GetNumMaterialSets();\n\n  return 1;\n}\n\nCModelData::EWhichModel CModelData::GetRenderingModel(const CStateManager& stateMgr) {\n  switch (stateMgr.GetPlayerState()->GetActiveVisor(stateMgr)) {\n  case CPlayerState::EPlayerVisor::XRay:\n    return CModelData::EWhichModel::XRay;\n  case CPlayerState::EPlayerVisor::Thermal:\n    if (stateMgr.GetThermalDrawFlag() == EThermalDrawFlag::Cold)\n      return CModelData::EWhichModel::Thermal;\n    return CModelData::EWhichModel::ThermalHot;\n  default:\n    return CModelData::EWhichModel::Normal;\n  }\n}\n\nCSkinnedModel& CModelData::PickAnimatedModel(EWhichModel which) const {\n  CSkinnedModel* ret = nullptr;\n  switch (which) {\n  case EWhichModel::XRay:\n    ret = x10_animData->xf4_xrayModel.get();\n    break;\n  case EWhichModel::Thermal:\n  case EWhichModel::ThermalHot:\n    ret = x10_animData->xf8_infraModel.get();\n    break;\n  default:\n    break;\n  }\n  if (ret)\n    return *ret;\n  return *x10_animData->xd8_modelData.GetObj();\n}\n\nTLockedToken<CModel>& CModelData::PickStaticModel(EWhichModel which) {\n  switch (which) {\n  case EWhichModel::XRay:\n    if (x2c_xrayModel) {\n      return x2c_xrayModel;\n    }\n    break;\n  case EWhichModel::Thermal:\n  case EWhichModel::ThermalHot:\n    if (x3c_infraModel) {\n      return x3c_infraModel;\n    }\n    break;\n  default:\n    break;\n  }\n  return x1c_normalModel;\n}\n\nvoid CModelData::SetXRayModel(const std::pair<CAssetId, CAssetId>& modelSkin) {\n  if (modelSkin.first.IsValid()) {\n    if (g_ResFactory->GetResourceTypeById(modelSkin.first) == SBIG('CMDL')) {\n      if (x10_animData && modelSkin.second.IsValid() &&\n          g_ResFactory->GetResourceTypeById(modelSkin.second) == SBIG('CSKR')) {\n        x10_animData->SetXRayModel(g_SimplePool->GetObj({SBIG('CMDL'), modelSkin.first}),\n                                   g_SimplePool->GetObj({SBIG('CSKR'), modelSkin.second}));\n      } else {\n        x2c_xrayModel = g_SimplePool->GetObj({SBIG('CMDL'), modelSkin.first});\n        if (!x2c_xrayModel)\n          spdlog::fatal(\"unable to find CMDL {}\", modelSkin.first);\n      }\n    }\n  }\n}\n\nvoid CModelData::SetInfraModel(const std::pair<CAssetId, CAssetId>& modelSkin) {\n  if (modelSkin.first.IsValid()) {\n    if (g_ResFactory->GetResourceTypeById(modelSkin.first) == SBIG('CMDL')) {\n      if (x10_animData && modelSkin.second.IsValid() &&\n          g_ResFactory->GetResourceTypeById(modelSkin.second) == SBIG('CSKR')) {\n        x10_animData->SetInfraModel(g_SimplePool->GetObj({SBIG('CMDL'), modelSkin.first}),\n                                    g_SimplePool->GetObj({SBIG('CSKR'), modelSkin.second}));\n      } else {\n        x3c_infraModel = g_SimplePool->GetObj({SBIG('CMDL'), modelSkin.first});\n        if (!x3c_infraModel)\n          spdlog::fatal(\"unable to find CMDL {}\", modelSkin.first);\n      }\n    }\n  }\n}\n\nbool CModelData::IsDefinitelyOpaque(EWhichModel which) const {\n  if (x10_animData) {\n    CSkinnedModel& model = PickAnimatedModel(which);\n    return model.GetModel()->IsOpaque();\n  } else {\n    const auto& model = PickStaticModel(which);\n    return model->IsOpaque();\n  }\n}\n\nbool CModelData::GetIsLoop() const {\n  if (!x10_animData)\n    return false;\n  return x10_animData->GetIsLoop();\n}\n\nfloat CModelData::GetAnimationDuration(int idx) const {\n  if (!x10_animData)\n    return 0.f;\n  return x10_animData->GetAnimationDuration(idx);\n}\n\nvoid CModelData::EnableLooping(bool enable) {\n  if (!x10_animData)\n    return;\n  x10_animData->EnableLooping(enable);\n}\n\nvoid CModelData::AdvanceParticles(const zeus::CTransform& xf, float dt, CStateManager& stateMgr) {\n  if (!x10_animData)\n    return;\n  x10_animData->AdvanceParticles(xf, dt, x0_scale, stateMgr);\n}\n\nzeus::CAABox CModelData::GetBounds() const {\n  if (x10_animData) {\n    return x10_animData->GetBoundingBox(zeus::CTransform::Scale(x0_scale));\n  } else {\n    const zeus::CAABox& aabb = x1c_normalModel->GetAABB();\n    return zeus::CAABox(aabb.min * x0_scale, aabb.max * x0_scale);\n  }\n}\n\nzeus::CAABox CModelData::GetBounds(const zeus::CTransform& xf) const {\n  zeus::CTransform xf2 = xf * zeus::CTransform::Scale(x0_scale);\n  if (x10_animData)\n    return x10_animData->GetBoundingBox(xf2);\n  else\n    return x1c_normalModel->GetAABB().getTransformedAABox(xf2);\n}\n\nzeus::CTransform CModelData::GetScaledLocatorTransformDynamic(std::string_view name, const CCharAnimTime* time) const {\n  zeus::CTransform xf = GetLocatorTransformDynamic(name, time);\n  xf.origin *= x0_scale;\n  return xf;\n}\n\nzeus::CTransform CModelData::GetScaledLocatorTransform(std::string_view name) const {\n  zeus::CTransform xf = GetLocatorTransform(name);\n  xf.origin *= x0_scale;\n  return xf;\n}\n\nzeus::CTransform CModelData::GetLocatorTransformDynamic(std::string_view name, const CCharAnimTime* time) const {\n  if (x10_animData)\n    return x10_animData->GetLocatorTransform(name, time);\n  else\n    return {};\n}\n\nzeus::CTransform CModelData::GetLocatorTransform(std::string_view name) const {\n  if (x10_animData)\n    return x10_animData->GetLocatorTransform(name, nullptr);\n  else\n    return {};\n}\n\nSAdvancementDeltas CModelData::AdvanceAnimationIgnoreParticles(float dt, CRandom16& rand, bool advTree) {\n  if (x10_animData)\n    return x10_animData->AdvanceIgnoreParticles(dt, rand, advTree);\n  else\n    return {};\n}\n\nSAdvancementDeltas CModelData::AdvanceAnimation(float dt, CStateManager& stateMgr, TAreaId aid, bool advTree) {\n  if (x10_animData)\n    return x10_animData->Advance(dt, x0_scale, stateMgr, aid, advTree);\n  else\n    return {};\n}\n\nbool CModelData::IsAnimating() const {\n  if (!x10_animData)\n    return false;\n  return x10_animData->IsAnimating();\n}\n\nbool CModelData::IsInFrustum(const zeus::CTransform& xf, const zeus::CFrustum& frustum) const {\n  if (!x10_animData && !x1c_normalModel)\n    return true;\n  return frustum.aabbFrustumTest(GetBounds(xf));\n}\n\nvoid CModelData::RenderParticles(const zeus::CFrustum& frustum) const {\n  if (x10_animData)\n    x10_animData->RenderAuxiliary(frustum);\n}\n\nvoid CModelData::Touch(EWhichModel which, int shaderIdx) {\n  if (x10_animData)\n    x10_animData->Touch(PickAnimatedModel(which), shaderIdx);\n  else\n    PickStaticModel(which)->Touch(shaderIdx);\n}\n\nvoid CModelData::Touch(const CStateManager& stateMgr, int shaderIdx) { Touch(GetRenderingModel(stateMgr), shaderIdx); }\n\nvoid CModelData::RenderThermal(const zeus::CTransform& xf, const zeus::CColor& mulColor, const zeus::CColor& addColor,\n                               const CModelFlags& flags) {\n  CGraphics::SetModelMatrix(xf * zeus::CTransform::Scale(x0_scale));\n  CGraphics::DisableAllLights();\n\n  if (x10_animData) {\n    CSkinnedModel& model = PickAnimatedModel(EWhichModel::ThermalHot);\n    x10_animData->SetupRender(model, nullptr, {});\n    ThermalDraw(model, mulColor, addColor, flags);\n  } else {\n    auto& model = PickStaticModel(EWhichModel::ThermalHot);\n    g_Renderer->DrawThermalModel(*model, mulColor, addColor, {}, {}, flags);\n  }\n}\n\nvoid CModelData::RenderUnsortedParts(EWhichModel which, const zeus::CTransform& xf, const CActorLights* lights,\n                                     const CModelFlags& drawFlags) {\n  if ((x14_25_sortThermal && which == EWhichModel::ThermalHot) || x10_animData || !x1c_normalModel ||\n      drawFlags.x0_blendMode > 4) {\n    x14_24_renderSorted = false;\n    return;\n  }\n\n  CGraphics::SetModelMatrix(xf * zeus::CTransform::Scale(x0_scale));\n\n  if (lights != nullptr) {\n    lights->ActivateLights();\n  } else {\n    CGraphics::DisableAllLights();\n    g_Renderer->SetAmbientColor(x18_ambientColor);\n  }\n\n  PickStaticModel(which)->DrawUnsortedParts(drawFlags);\n  g_Renderer->SetAmbientColor(zeus::skWhite);\n  CGraphics::DisableAllLights();\n  x14_24_renderSorted = true;\n}\n\nvoid CModelData::Render(EWhichModel which, const zeus::CTransform& xf, const CActorLights* lights,\n                        const CModelFlags& drawFlags) {\n  if (x14_25_sortThermal && which == EWhichModel::ThermalHot) {\n    zeus::CColor mul(drawFlags.x4_color.a(), drawFlags.x4_color.a());\n    RenderThermal(xf, mul, {0.f, 0.f, 0.f, 0.25f}, drawFlags);\n    return;\n  }\n\n  CGraphics::SetModelMatrix(xf * zeus::CTransform::Scale(x0_scale));\n\n  if (lights != nullptr) {\n    lights->ActivateLights();\n  } else {\n    CGraphics::DisableAllLights();\n    g_Renderer->SetAmbientColor(x18_ambientColor);\n  }\n\n  if (x10_animData) {\n    x10_animData->Render(PickAnimatedModel(which), drawFlags, nullptr, {});\n  } else {\n    // TODO supposed to be optional_object?\n    if (x1c_normalModel) {\n      auto& model = PickStaticModel(which);\n      if (x14_24_renderSorted) {\n        model->DrawSortedParts(drawFlags);\n      } else {\n        model->Draw(drawFlags);\n      }\n    }\n  }\n\n  g_Renderer->SetAmbientColor(zeus::skWhite);\n  CGraphics::DisableAllLights();\n  x14_24_renderSorted = false;\n}\n\nvoid CModelData::FlatDraw(EWhichModel which, const zeus::CTransform& xf, bool unsortedOnly, const CModelFlags& flags) {\n  g_Renderer->SetModelMatrix(xf * zeus::CTransform::Scale(x0_scale));\n  CGraphics::DisableAllLights();\n  if (!x10_animData) {\n    g_Renderer->DrawModelFlat(*PickStaticModel(which), flags, unsortedOnly, {}, {});\n  } else {\n    auto model = PickAnimatedModel(which);\n    x10_animData->SetupRender(model, nullptr, {});\n    model.DoDrawCallback([=](TConstVectorRef positions, TConstVectorRef normals) {\n      auto m = model.GetModel();\n      g_Renderer->DrawModelFlat(*m, flags, unsortedOnly, positions, normals);\n    });\n  }\n}\n\nvoid CModelData::MultiLightingDraw(EWhichModel which, const zeus::CTransform& xf, const CActorLights* lights,\n                                   const zeus::CColor& alphaColor, const zeus::CColor& additiveColor) {\n  CModel* model = nullptr;\n  const auto callback = [&](TConstVectorRef positions, TConstVectorRef normals) {\n    CGraphics::DisableAllLights();\n    constexpr CModelFlags flags1{5, 0, 3, zeus::CColor{1.f, 0.f}};\n    const CModelFlags flags2{5, 0, 1, alphaColor};\n    const CModelFlags flags3{7, 0, 1, additiveColor};\n    if (positions.empty()) {\n      model->Draw(flags1);\n      if (lights != nullptr) {\n        lights->ActivateLights();\n      }\n      model->Draw(flags2);\n      model->Draw(flags3);\n    } else {\n      model->Draw(positions, normals, flags1);\n      if (lights != nullptr) {\n        lights->ActivateLights();\n      }\n      model->Draw(positions, normals, flags2);\n      model->Draw(positions, normals, flags3);\n    }\n  };\n  CGraphics::SetModelMatrix(xf * zeus::CTransform::Scale(x0_scale));\n  if (x10_animData) {\n    auto& skinnedModel = PickAnimatedModel(which);\n    x10_animData->SetupRender(skinnedModel, nullptr, {});\n    model = skinnedModel.GetModel().GetObj();\n    skinnedModel.DoDrawCallback(callback);\n  } else {\n    model = PickStaticModel(which).GetObj();\n    callback({}, {});\n  }\n}\n\nvoid CModelData::MultiPassDraw(EWhichModel which, const zeus::CTransform& xf, const CActorLights* lights,\n                               const CModelFlags* flags, u32 count) {\n  CGraphics::SetModelMatrix(xf * zeus::CTransform::Scale(x0_scale));\n  if (lights == nullptr) {\n    CGraphics::DisableAllLights();\n    g_Renderer->SetAmbientColor(x18_ambientColor);\n  } else {\n    lights->ActivateLights();\n  }\n  if (x10_animData) {\n    auto& skinnedModel = PickAnimatedModel(which);\n    x10_animData->SetupRender(skinnedModel, nullptr, {});\n    auto& model = *skinnedModel.GetModel();\n    skinnedModel.DoDrawCallback([&](auto positions, auto normals) {\n      for (int i = 0; i < count; ++i) {\n        model.Draw(positions, normals, flags[i]);\n      }\n    });\n  } else {\n    auto& model = *PickStaticModel(which);\n    for (int i = 0; i < count; ++i) {\n      model.Draw(flags[i]);\n    }\n  }\n}\n\nvoid CModelData::DisintegrateDraw(const CStateManager& mgr, const zeus::CTransform& xf, CTexture& tex,\n                                  const zeus::CColor& addColor, float t) {\n  DisintegrateDraw(GetRenderingModel(mgr), xf, tex, addColor, t);\n}\n\nvoid CModelData::DisintegrateDraw(EWhichModel which, const zeus::CTransform& xf, CTexture& tex,\n                                  const zeus::CColor& addColor, float t) {\n  zeus::CTransform scaledXf = xf * zeus::CTransform::Scale(x0_scale);\n  CGraphics::SetModelMatrix(scaledXf);\n  CGraphics::DisableAllLights();\n  const auto aabb = GetBounds(scaledXf);\n  if (x10_animData) {\n    auto& model = PickAnimatedModel(which);\n    x10_animData->SetupRender(model, nullptr, {});\n    model.DoDrawCallback([&](auto positions, auto normals) {\n      g_Renderer->DrawModelDisintegrate(*model.GetModel(), tex, addColor, positions, normals, t);\n    });\n  } else {\n    g_Renderer->DrawModelDisintegrate(*PickStaticModel(which), tex, addColor, {}, {}, t);\n  }\n}\n\nvoid CModelData::ThermalDraw(CSkinnedModel& model, const zeus::CColor& mulColor, const zeus::CColor& addColor,\n                             const CModelFlags& flags) {\n  model.DoDrawCallback([&](auto positions, auto normals) {\n    g_Renderer->DrawThermalModel(*model.GetModel(), mulColor, addColor, positions, normals, flags);\n  });\n}\n\nvoid CModelData::ThermalDraw(CSkinnedModel& model, TConstVectorRef positions, TConstVectorRef normals,\n                             const zeus::CColor& mulColor, const zeus::CColor& addColor, const CModelFlags& flags) {\n  g_Renderer->DrawThermalModel(*model.GetModel(), mulColor, addColor, positions, normals, flags);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CModelData.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Character/CAnimData.hpp\"\n#include \"Runtime/Graphics/CModel.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CColor.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CActorLights;\nclass CAnimData;\nclass CCharAnimTime;\nclass CModel;\nclass CRandom16;\nclass CSkinnedModel;\nclass CStateManager;\nstruct CModelFlags;\nstruct SAdvancementDeltas;\n\nclass CStaticRes {\n  CAssetId x0_cmdlId;\n  zeus::CVector3f x4_scale;\n\npublic:\n  constexpr CStaticRes() = default;\n  CStaticRes(CAssetId id, const zeus::CVector3f& scale) : x0_cmdlId(id), x4_scale(scale) {}\n\n  CAssetId GetId() const { return x0_cmdlId; }\n  const zeus::CVector3f& GetScale() const { return x4_scale; }\n  explicit operator bool() const { return x0_cmdlId.IsValid(); }\n};\n\nclass CAnimRes {\n  CAssetId x0_ancsId;\n  s32 x4_charIdx = -1;\n  zeus::CVector3f x8_scale;\n  bool x14_canLoop = false;\n  /* NOTE: x18_bodyType - Removed in retail */\n  s32 x18_defaultAnim = -1; /* NOTE: used to be x1c in demo */\npublic:\n  CAnimRes() = default;\n  CAnimRes(CAssetId ancs, s32 charIdx, const zeus::CVector3f& scale, const s32 defaultAnim, bool loop)\n  : x0_ancsId(ancs), x4_charIdx(charIdx), x8_scale(scale), x14_canLoop(loop), x18_defaultAnim(defaultAnim) {}\n\n  CAssetId GetId() const { return x0_ancsId; }\n  s32 GetCharacterNodeId() const { return x4_charIdx; }\n  void SetCharacterNodeId(s32 id) { x4_charIdx = id; }\n  const zeus::CVector3f& GetScale() const { return x8_scale; }\n  bool CanLoop() const { return x14_canLoop; }\n  void SetCanLoop(bool loop) { x14_canLoop = loop; }\n  s32 GetDefaultAnim() const { return x18_defaultAnim; }\n  void SetDefaultAnim(s32 anim) { x18_defaultAnim = anim; }\n};\n\nclass CModelData {\n  friend class CActor;\n  zeus::CVector3f x0_scale;\n  std::unique_ptr<CAnimData> x10_animData;\n  bool x14_24_renderSorted : 1 = false;\n  bool x14_25_sortThermal : 1 = false;\n  zeus::CColor x18_ambientColor;\n\n  // were rstl::optional_object<TCachedToken<CModel>>\n  TLockedToken<CModel> x1c_normalModel;\n  TLockedToken<CModel> x2c_xrayModel;\n  TLockedToken<CModel> x3c_infraModel;\n\npublic:\n  enum class EWhichModel { Normal, XRay, Thermal, ThermalHot };\n\n  void SetSortThermal(bool sort) { x14_25_sortThermal = sort; }\n  bool GetSortThermal() const { return x14_25_sortThermal; }\n\n  ~CModelData();\n  explicit CModelData(const CStaticRes& res);\n  explicit CModelData(const CAnimRes& res);\n  CModelData(CModelData&&) = default;\n  CModelData& operator=(CModelData&&) = default;\n  CModelData();\n  static CModelData CModelDataNull();\n\n  SAdvancementDeltas GetAdvancementDeltas(const CCharAnimTime& a, const CCharAnimTime& b) const;\n  bool IsLoaded(int shaderIdx);\n  static EWhichModel GetRenderingModel(const CStateManager& stateMgr);\n  CSkinnedModel& PickAnimatedModel(EWhichModel which) const;\n  TLockedToken<CModel>& PickStaticModel(EWhichModel which);\n  const TLockedToken<CModel>& PickStaticModel(EWhichModel which) const {\n    return const_cast<CModelData*>(this)->PickStaticModel(which);\n  }\n  void SetXRayModel(const std::pair<CAssetId, CAssetId>& modelSkin);\n  void SetInfraModel(const std::pair<CAssetId, CAssetId>& modelSkin);\n  bool IsDefinitelyOpaque(EWhichModel) const;\n  bool GetIsLoop() const;\n  float GetAnimationDuration(int idx) const;\n  void EnableLooping(bool enable);\n  void AdvanceParticles(const zeus::CTransform& xf, float dt, CStateManager& stateMgr);\n  zeus::CAABox GetBounds() const;\n  zeus::CAABox GetBounds(const zeus::CTransform& xf) const;\n  zeus::CTransform GetScaledLocatorTransformDynamic(std::string_view name, const CCharAnimTime* time) const;\n  zeus::CTransform GetScaledLocatorTransform(std::string_view name) const;\n  zeus::CTransform GetLocatorTransformDynamic(std::string_view name, const CCharAnimTime* time) const;\n  zeus::CTransform GetLocatorTransform(std::string_view name) const;\n  SAdvancementDeltas AdvanceAnimationIgnoreParticles(float dt, CRandom16& rand, bool advTree);\n  SAdvancementDeltas AdvanceAnimation(float dt, CStateManager& stateMgr, TAreaId aid, bool advTree);\n  bool IsAnimating() const;\n  bool IsInFrustum(const zeus::CTransform& xf, const zeus::CFrustum& frustum) const;\n  void RenderParticles(const zeus::CFrustum& frustum) const;\n  void Touch(EWhichModel, int shaderIdx);\n  void Touch(const CStateManager& stateMgr, int shaderIdx);\n  void RenderThermal(const zeus::CTransform& xf, const zeus::CColor& mulColor, const zeus::CColor& addColor,\n                     const CModelFlags& flags);\n  void RenderUnsortedParts(EWhichModel, const zeus::CTransform& xf, const CActorLights* lights,\n                           const CModelFlags& drawFlags);\n  void Render(const CStateManager& stateMgr, const zeus::CTransform& xf, const CActorLights* lights,\n              const CModelFlags& drawFlags);\n  void Render(EWhichModel, const zeus::CTransform& xf, const CActorLights* lights, const CModelFlags& drawFlags);\n  void FlatDraw(EWhichModel which, const zeus::CTransform& xf, bool unsortedOnly, const CModelFlags& flags);\n\n  void MultiLightingDraw(EWhichModel which, const zeus::CTransform& xf, const CActorLights* lights,\n                         const zeus::CColor& alphaColor, const zeus::CColor& additiveColor);\n  void MultiPassDraw(EWhichModel which, const zeus::CTransform& xf, const CActorLights* lights,\n                     const CModelFlags* flags, u32 count);\n  void DisintegrateDraw(const CStateManager& mgr, const zeus::CTransform& xf, CTexture& tex,\n                        const zeus::CColor& addColor, float t);\n  void DisintegrateDraw(EWhichModel which, const zeus::CTransform& xf, CTexture& tex, const zeus::CColor& addColor,\n                        float t);\n  static void ThermalDraw(CSkinnedModel& model, const zeus::CColor& mulColor, const zeus::CColor& addColor,\n                          const CModelFlags& flags);\n  static void ThermalDraw(CSkinnedModel& model, TConstVectorRef positions, TConstVectorRef normals,\n                          const zeus::CColor& mulColor, const zeus::CColor& addColor, const CModelFlags& flags);\n\n  CAnimData* GetAnimationData() { return x10_animData.get(); }\n  const CAnimData* GetAnimationData() const { return x10_animData.get(); }\n  const TLockedToken<CModel>& GetNormalModel() const { return x1c_normalModel; }\n  const TLockedToken<CModel>& GetXRayModel() const { return x2c_xrayModel; }\n  const TLockedToken<CModel>& GetThermalModel() const { return x3c_infraModel; }\n  bool IsNull() const { return !x10_animData && !x1c_normalModel; }\n  u32 GetNumMaterialSets() const;\n\n  const zeus::CVector3f& GetScale() const { return x0_scale; }\n  void SetScale(const zeus::CVector3f& scale) { x0_scale = scale; }\n  bool HasAnimData() const { return x10_animData != nullptr; }\n  bool HasNormalModel() const { return x1c_normalModel.HasReference(); }\n  bool HasModel(EWhichModel which) const {\n    if (x10_animData) {\n      switch (which) {\n      case EWhichModel::Normal:\n        return true;\n      case EWhichModel::XRay:\n        return x10_animData->GetXRayModel() != nullptr;\n      case EWhichModel::Thermal:\n        return x10_animData->GetInfraModel() != nullptr;\n      default:\n        return false;\n      }\n    }\n\n    switch (which) {\n    case EWhichModel::Normal:\n      return x1c_normalModel.HasReference();\n    case EWhichModel::XRay:\n      return x2c_xrayModel.HasReference();\n    case EWhichModel::Thermal:\n      return x3c_infraModel.HasReference();\n    default:\n      return false;\n    }\n  }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CPASAnimInfo.cpp",
    "content": "#include \"Runtime/Character/CPASAnimInfo.hpp\"\n\nnamespace metaforce {\n\nCPASAnimInfo::CPASAnimInfo(u32 id, rstl::reserved_vector<CPASAnimParm::UParmValue, 8>&& parms)\n: x0_id(id), x4_parms(std::move(parms)) {}\n\nCPASAnimParm::UParmValue CPASAnimInfo::GetAnimParmValue(size_t idx) const {\n  if (idx >= x4_parms.size()) {\n    return CPASAnimParm::UParmValue{};\n  }\n  return x4_parms[idx];\n}\n\nCPASAnimParm CPASAnimInfo::GetAnimParmData(size_t idx, CPASAnimParm::EParmType type) const {\n  if (idx >= x4_parms.size()) {\n    return CPASAnimParm::NoParameter();\n  }\n\n  const CPASAnimParm::UParmValue& parm = x4_parms[idx];\n\n  switch (type) {\n  case CPASAnimParm::EParmType::Int32:\n    return CPASAnimParm::FromInt32(parm.m_int);\n  case CPASAnimParm::EParmType::UInt32:\n    return CPASAnimParm::FromUint32(parm.m_uint);\n  case CPASAnimParm::EParmType::Float:\n    return CPASAnimParm::FromReal32(parm.m_float);\n  case CPASAnimParm::EParmType::Bool:\n    return CPASAnimParm::FromBool(parm.m_bool);\n  case CPASAnimParm::EParmType::Enum:\n    return CPASAnimParm::FromEnum(parm.m_int);\n  default:\n    return CPASAnimParm::NoParameter();\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CPASAnimInfo.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Character/CPASAnimParm.hpp\"\n\nnamespace metaforce {\n\nclass CPASAnimInfo {\n  u32 x0_id;\n  rstl::reserved_vector<CPASAnimParm::UParmValue, 8> x4_parms;\n\npublic:\n  explicit CPASAnimInfo(u32 id) : x0_id(id) {}\n  explicit CPASAnimInfo(u32 id, rstl::reserved_vector<CPASAnimParm::UParmValue, 8>&& parms);\n  u32 GetAnimId() const { return x0_id; }\n  CPASAnimParm::UParmValue GetAnimParmValue(size_t idx) const;\n  CPASAnimParm GetAnimParmData(size_t idx, CPASAnimParm::EParmType type) const;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CPASAnimParm.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n\nnamespace metaforce {\n\nclass CPASAnimParm {\npublic:\n  enum class EParmType { None = -1, Int32 = 0, UInt32 = 1, Float = 2, Bool = 3, Enum = 4 };\n  union UParmValue {\n    s32 m_int;\n    u32 m_uint;\n    float m_float;\n    bool m_bool;\n  };\n\nprivate:\n  UParmValue x0_value;\n  EParmType x4_type;\n\npublic:\n  constexpr CPASAnimParm(UParmValue val, EParmType tp) : x0_value(val), x4_type(tp) {}\n\n  [[nodiscard]] constexpr EParmType GetParameterType() const { return x4_type; }\n  [[nodiscard]] constexpr s32 GetEnumValue() const { return x0_value.m_int; }\n  [[nodiscard]] constexpr bool GetBoolValue() const { return x0_value.m_bool; }\n  [[nodiscard]] constexpr float GetReal32Value() const { return x0_value.m_float; }\n  [[nodiscard]] constexpr u32 GetUint32Value() const { return x0_value.m_uint; }\n  [[nodiscard]] constexpr s32 GetInt32Value() const { return x0_value.m_int; }\n\n  [[nodiscard]] static constexpr CPASAnimParm FromEnum(s32 val) {\n    UParmValue valin{};\n    valin.m_int = val;\n    return CPASAnimParm(valin, EParmType::Enum);\n  }\n\n  [[nodiscard]] static constexpr CPASAnimParm FromBool(bool val) {\n    UParmValue valin{};\n    valin.m_bool = val;\n    return CPASAnimParm(valin, EParmType::Bool);\n  }\n\n  [[nodiscard]] static constexpr CPASAnimParm FromReal32(float val) {\n    UParmValue valin{};\n    valin.m_float = val;\n    return CPASAnimParm(valin, EParmType::Float);\n  }\n\n  [[nodiscard]] static constexpr CPASAnimParm FromUint32(u32 val) {\n    UParmValue valin{};\n    valin.m_uint = val;\n    return CPASAnimParm(valin, EParmType::UInt32);\n  }\n\n  [[nodiscard]] static constexpr CPASAnimParm FromInt32(s32 val) {\n    UParmValue valin{};\n    valin.m_int = val;\n    return CPASAnimParm(valin, EParmType::Int32);\n  }\n\n  [[nodiscard]] static constexpr CPASAnimParm NoParameter() {\n    UParmValue valin{};\n    valin.m_int = -1;\n    return CPASAnimParm(valin, EParmType::None);\n  }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CPASAnimParmData.cpp",
    "content": "#include \"Runtime/Character/CPASAnimParmData.hpp\"\n\nnamespace metaforce {\n\nCPASAnimParmData::CPASAnimParmData(pas::EAnimationState stateId, const CPASAnimParm& parm1, const CPASAnimParm& parm2,\n                                   const CPASAnimParm& parm3, const CPASAnimParm& parm4, const CPASAnimParm& parm5,\n                                   const CPASAnimParm& parm6, const CPASAnimParm& parm7, const CPASAnimParm& parm8)\n: x0_stateId(stateId) {\n  x4_parms.push_back(parm1);\n  x4_parms.push_back(parm2);\n  x4_parms.push_back(parm3);\n  x4_parms.push_back(parm4);\n  x4_parms.push_back(parm5);\n  x4_parms.push_back(parm6);\n  x4_parms.push_back(parm7);\n  x4_parms.push_back(parm8);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CPASAnimParmData.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Character/CharacterCommon.hpp\"\n#include \"Runtime/Character/CPASAnimParm.hpp\"\n\nnamespace metaforce {\nclass CPASAnimParmData {\n  pas::EAnimationState x0_stateId;\n  rstl::reserved_vector<CPASAnimParm, 8> x4_parms;\n\npublic:\n  CPASAnimParmData() = default;\n\n  explicit CPASAnimParmData(pas::EAnimationState stateId, const CPASAnimParm& parm1 = CPASAnimParm::NoParameter(),\n                            const CPASAnimParm& parm2 = CPASAnimParm::NoParameter(),\n                            const CPASAnimParm& parm3 = CPASAnimParm::NoParameter(),\n                            const CPASAnimParm& parm4 = CPASAnimParm::NoParameter(),\n                            const CPASAnimParm& parm5 = CPASAnimParm::NoParameter(),\n                            const CPASAnimParm& parm6 = CPASAnimParm::NoParameter(),\n                            const CPASAnimParm& parm7 = CPASAnimParm::NoParameter(),\n                            const CPASAnimParm& parm8 = CPASAnimParm::NoParameter());\n\n  pas::EAnimationState GetStateId() const { return x0_stateId; }\n  const rstl::reserved_vector<CPASAnimParm, 8>& GetAnimParmData() const { return x4_parms; }\n\n  static auto NoParameters(pas::EAnimationState stateId) { return CPASAnimParmData(stateId); }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CPASAnimState.cpp",
    "content": "#include \"Runtime/Character/CPASAnimState.hpp\"\n\n#include \"Runtime/CRandom16.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Character/CharacterCommon.hpp\"\n#include \"Runtime/Character/CPASAnimParmData.hpp\"\n\n#include <algorithm>\n#include <cfloat>\n#include <cmath>\n\n#include <zeus/Math.hpp>\n\nnamespace metaforce {\n\nCPASAnimState::CPASAnimState(CInputStream& in) {\n  x0_id = static_cast<pas::EAnimationState>(in.ReadLong());\n  u32 parmCount = in.ReadLong();\n  u32 animCount = in.ReadLong();\n\n  x4_parms.reserve(parmCount);\n  x14_anims.reserve(animCount);\n  x24_selectionCache.reserve(animCount);\n\n  for (u32 i = 0; i < parmCount; ++i)\n    x4_parms.emplace_back(in);\n\n  for (u32 i = 0; i < animCount; ++i) {\n    s32 id = in.ReadLong();\n    rstl::reserved_vector<CPASAnimParm::UParmValue, 8> parms;\n    for (const CPASParmInfo& parm : x4_parms) {\n      CPASAnimParm::UParmValue val = {};\n      switch (parm.GetParameterType()) {\n      case CPASAnimParm::EParmType::Int32:\n        val.m_int = in.ReadInt32();\n        break;\n      case CPASAnimParm::EParmType::UInt32:\n        val.m_uint = in.ReadLong();\n        break;\n      case CPASAnimParm::EParmType::Float:\n        val.m_float = in.ReadFloat();\n        break;\n      case CPASAnimParm::EParmType::Bool:\n        val.m_bool = in.ReadBool();\n        break;\n      case CPASAnimParm::EParmType::Enum:\n        val.m_int = in.ReadInt32();\n        break;\n      default:\n        break;\n      }\n      parms.push_back(val);\n    }\n\n    auto search =\n        std::lower_bound(x14_anims.begin(), x14_anims.end(), id,\n                         [](const CPASAnimInfo& item, const u32& testId) -> bool { return item.GetAnimId() < testId; });\n    x14_anims.emplace(search, id, std::move(parms));\n  }\n}\n\nCPASAnimState::CPASAnimState(pas::EAnimationState stateId) : x0_id(stateId) {}\n\nCPASAnimParm CPASAnimState::GetAnimParmData(s32 animId, size_t parmIdx) const {\n  const auto search = rstl::binary_find(x14_anims.cbegin(), x14_anims.cend(), animId,\n                                        [](const CPASAnimInfo& item) { return item.GetAnimId(); });\n  if (search == x14_anims.cend()) {\n    return CPASAnimParm::NoParameter();\n  }\n\n  const CPASParmInfo& parm = x4_parms.at(parmIdx);\n  return search->GetAnimParmData(parmIdx, parm.GetParameterType());\n}\n\ns32 CPASAnimState::PickRandomAnimation(CRandom16& rand) const {\n  if (x24_selectionCache.size() == 1)\n    return x24_selectionCache[0];\n\n  if (x24_selectionCache.size() > 1) {\n    u32 idx = u32(floor(rand.Float()));\n    if (idx == x24_selectionCache.size())\n      idx--;\n\n    return x24_selectionCache[idx];\n  }\n\n  return -1;\n}\n\nstd::pair<float, s32> CPASAnimState::FindBestAnimation(const rstl::reserved_vector<CPASAnimParm, 8>& parms,\n                                                       CRandom16& rand, s32 ignoreAnim) const {\n  x24_selectionCache.clear();\n  float weight = -1.f;\n\n  for (const CPASAnimInfo& info : x14_anims) {\n    if (info.GetAnimId() == ignoreAnim)\n      continue;\n\n    float calcWeight = 1.f;\n    if (x4_parms.size() > 0)\n      calcWeight = 0.f;\n\n    u32 unweightedCount = 0;\n\n    for (size_t i = 0; i < x4_parms.size(); ++i) {\n      CPASAnimParm::UParmValue val = info.GetAnimParmValue(i);\n      const CPASParmInfo& parmInfo = x4_parms[i];\n      float parmWeight = parmInfo.GetParameterWeight();\n\n      float computedWeight = 0.f;\n      switch (parmInfo.GetWeightFunction()) {\n      case CPASParmInfo::EWeightFunction::AngularPercent:\n        computedWeight = ComputeAngularPercentErrorWeight(i, parms[i], val);\n        break;\n      case CPASParmInfo::EWeightFunction::ExactMatch:\n        computedWeight = ComputeExactMatchWeight(i, parms[i], val);\n        break;\n      case CPASParmInfo::EWeightFunction::PercentError:\n        computedWeight = ComputePercentErrorWeight(i, parms[i], val);\n        break;\n      case CPASParmInfo::EWeightFunction::NoWeight:\n        unweightedCount++;\n        break;\n      default:\n        break;\n      }\n\n      calcWeight += parmWeight * computedWeight;\n    }\n\n    if (unweightedCount == x4_parms.size())\n      calcWeight = 1.0f;\n\n    if (calcWeight > weight) {\n      x24_selectionCache.clear();\n      x24_selectionCache.push_back(info.GetAnimId());\n      weight = calcWeight;\n    } else if (weight == calcWeight) {\n      x24_selectionCache.push_back(info.GetAnimId());\n      weight = calcWeight;\n    }\n  }\n\n  return {weight, PickRandomAnimation(rand)};\n}\n\nfloat CPASAnimState::ComputeExactMatchWeight(size_t, const CPASAnimParm& parm, CPASAnimParm::UParmValue parmVal) const {\n  switch (parm.GetParameterType()) {\n  case CPASAnimParm::EParmType::Int32:\n    return (parm.GetInt32Value() == parmVal.m_int ? 1.f : 0.f);\n  case CPASAnimParm::EParmType::UInt32:\n    return (parm.GetUint32Value() == parmVal.m_uint ? 1.f : 0.f);\n  case CPASAnimParm::EParmType::Float:\n    return ((parmVal.m_float - parm.GetReal32Value()) < FLT_EPSILON ? 1.f : 0.f);\n  case CPASAnimParm::EParmType::Bool:\n    return (parm.GetBoolValue() == parmVal.m_bool ? 1.f : 0.f);\n  case CPASAnimParm::EParmType::Enum:\n    return (parm.GetEnumValue() == parmVal.m_int ? 1.f : 0.f);\n  default:\n    break;\n  }\n\n  return 0.f;\n}\n\nfloat CPASAnimState::ComputePercentErrorWeight(size_t idx, const CPASAnimParm& parm,\n                                               CPASAnimParm::UParmValue parmVal) const {\n  float range = 0.f;\n  float val = 0.f;\n\n  switch (parm.GetParameterType()) {\n  case CPASAnimParm::EParmType::Int32: {\n    const CPASParmInfo& info = x4_parms[idx];\n    range = info.GetWeightMaxValue().m_int - info.GetWeightMinValue().m_int;\n    val = std::fabs(parm.GetInt32Value() - parmVal.m_int);\n    break;\n  }\n  case CPASAnimParm::EParmType::UInt32: {\n    const CPASParmInfo& info = x4_parms[idx];\n    range = info.GetWeightMaxValue().m_uint - info.GetWeightMinValue().m_uint;\n    val = std::fabs(int(parmVal.m_uint) - int(parm.GetUint32Value()));\n    break;\n  }\n  case CPASAnimParm::EParmType::Float: {\n    const CPASParmInfo& info = x4_parms[idx];\n    range = info.GetWeightMaxValue().m_float - info.GetWeightMinValue().m_float;\n    val = std::fabs(parm.GetReal32Value() - parmVal.m_float);\n    break;\n  }\n  case CPASAnimParm::EParmType::Bool: {\n    val = parm.GetBoolValue() == parmVal.m_bool ? 0.f : 1.f;\n    break;\n  }\n  case CPASAnimParm::EParmType::Enum: {\n    const CPASParmInfo& info = x4_parms[idx];\n    range = info.GetWeightMaxValue().m_int - info.GetWeightMinValue().m_int;\n    val = std::fabs(parm.GetEnumValue() - parmVal.m_int);\n    break;\n  }\n  default:\n    break;\n  }\n\n  if (range > FLT_EPSILON)\n    return 1.f - val / range;\n\n  return (val < FLT_EPSILON ? 1.f : 0.f);\n}\n\nfloat CPASAnimState::ComputeAngularPercentErrorWeight(size_t idx, const CPASAnimParm& parm,\n                                                      CPASAnimParm::UParmValue parmVal) const {\n  float range = 0.f;\n  float val = 0.f;\n\n  switch (parm.GetParameterType()) {\n  case CPASAnimParm::EParmType::Int32: {\n    const CPASParmInfo& info = x4_parms[idx];\n    range = info.GetWeightMaxValue().m_int - info.GetWeightMinValue().m_int;\n    val = std::fabs(parmVal.m_int - parm.GetInt32Value());\n    break;\n  }\n  case CPASAnimParm::EParmType::UInt32: {\n    const CPASParmInfo& info = x4_parms[idx];\n    range = info.GetWeightMaxValue().m_uint - info.GetWeightMinValue().m_uint;\n    val = std::fabs(int(parmVal.m_uint) - int(parm.GetUint32Value()));\n    break;\n  }\n  case CPASAnimParm::EParmType::Float: {\n    const CPASParmInfo& info = x4_parms[idx];\n    range = info.GetWeightMaxValue().m_float - info.GetWeightMinValue().m_float;\n    val = std::fabs(parm.GetReal32Value() - parmVal.m_float);\n    break;\n  }\n  case CPASAnimParm::EParmType::Bool: {\n    val = parm.GetBoolValue() == parmVal.m_bool ? 0.f : 1.f;\n    break;\n  }\n  case CPASAnimParm::EParmType::Enum: {\n    const CPASParmInfo& info = x4_parms[idx];\n    range = info.GetWeightMaxValue().m_int - info.GetWeightMinValue().m_int + 1;\n    val = std::fabs(parm.GetEnumValue() - parmVal.m_int);\n    break;\n  }\n  default:\n    break;\n  }\n\n  if (range > FLT_EPSILON) {\n    float mid = 0.5f * range;\n    float offset = 1.f - ((val > mid ? range - val : val) / mid);\n    return zeus::clamp(0.f, offset, 1.f);\n  }\n\n  return (val >= FLT_EPSILON ? 0.f : 1.f);\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CPASAnimState.hpp",
    "content": "#pragma once\n\n#include <utility>\n#include <vector>\n\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/Character/CharacterCommon.hpp\"\n#include \"Runtime/Character/CPASAnimInfo.hpp\"\n#include \"Runtime/Character/CPASParmInfo.hpp\"\n\nnamespace metaforce {\nclass CRandom16;\nclass CPASAnimParmData;\nclass CPASAnimState {\n  pas::EAnimationState x0_id;\n  std::vector<CPASParmInfo> x4_parms;\n  std::vector<CPASAnimInfo> x14_anims;\n  mutable std::vector<s32> x24_selectionCache;\n\n  float ComputeExactMatchWeight(size_t idx, const CPASAnimParm& parm, CPASAnimParm::UParmValue parmVal) const;\n  float ComputePercentErrorWeight(size_t idx, const CPASAnimParm& parm, CPASAnimParm::UParmValue parmVal) const;\n  float ComputeAngularPercentErrorWeight(size_t idx, const CPASAnimParm& parm, CPASAnimParm::UParmValue parmVal) const;\n\n  s32 PickRandomAnimation(CRandom16& rand) const;\n\npublic:\n  explicit CPASAnimState(CInputStream& in);\n  explicit CPASAnimState(pas::EAnimationState stateId);\n  pas::EAnimationState GetStateId() const { return x0_id; }\n  size_t GetNumAnims() const { return x14_anims.size(); }\n  bool HasAnims() const { return !x14_anims.empty(); }\n  CPASAnimParm GetAnimParmData(s32 animId, size_t parmIdx) const;\n  std::pair<float, s32> FindBestAnimation(const rstl::reserved_vector<CPASAnimParm, 8>& parms, CRandom16& rand,\n                                          s32 ignoreAnim) const;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CPASDatabase.cpp",
    "content": "#include \"Runtime/Character/CPASDatabase.hpp\"\n\n#include \"Runtime/CRandom16.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Character/CPASAnimParmData.hpp\"\n\n#include <algorithm>\n\nnamespace metaforce {\n\nvoid CPASDatabase::AddAnimState(CPASAnimState&& state) {\n  auto it = std::lower_bound(x0_states.begin(), x0_states.end(), state,\n                             [](const CPASAnimState& item, const CPASAnimState& test) -> bool {\n                               return item.GetStateId() < test.GetStateId();\n                             });\n  x0_states.insert(it, std::move(state));\n}\n\nCPASDatabase::CPASDatabase(CInputStream& in) {\n  in.ReadLong();\n  u32 animStateCount = in.ReadLong();\n  u32 defaultState = in.ReadLong();\n\n  x0_states.reserve(animStateCount);\n  for (u32 i = 0; i < animStateCount; ++i) {\n    CPASAnimState state(in);\n    AddAnimState(std::move(state));\n  }\n\n  if (animStateCount)\n    SetDefaultState(defaultState);\n}\n\nstd::pair<float, s32> CPASDatabase::FindBestAnimation(const CPASAnimParmData& data, s32 ignoreAnim) const {\n  CRandom16 rnd(4660);\n  return FindBestAnimation(data, rnd, ignoreAnim);\n}\n\nstd::pair<float, s32> CPASDatabase::FindBestAnimation(const CPASAnimParmData& data, CRandom16& rand,\n                                                      s32 ignoreAnim) const {\n  auto it = rstl::binary_find(x0_states.cbegin(), x0_states.cend(), data.GetStateId(),\n                              [](const CPASAnimState& item) { return item.GetStateId(); });\n\n  if (it == x0_states.cend())\n    return {0.f, -1};\n\n  return (*it).FindBestAnimation(data.GetAnimParmData(), rand, ignoreAnim);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CPASDatabase.hpp",
    "content": "#pragma once\n\n#include <algorithm>\n#include <utility>\n#include <vector>\n\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/Character/CPASAnimState.hpp\"\n\nnamespace metaforce {\n\nclass CRandom16;\nclass CPASAnimParmData;\nclass CPASDatabase {\n  std::vector<CPASAnimState> x0_states;\n  s32 x10_defaultState;\n  void AddAnimState(CPASAnimState&& state);\n  void SetDefaultState(s32 state) { x10_defaultState = state; }\n\npublic:\n  explicit CPASDatabase(CInputStream& in);\n\n  std::pair<float, s32> FindBestAnimation(const CPASAnimParmData& data, s32 ignoreAnim) const;\n  std::pair<float, s32> FindBestAnimation(const CPASAnimParmData& data, CRandom16& rand, s32 ignoreAnim) const;\n  s32 GetDefaultState() const { return x10_defaultState; }\n  size_t GetNumAnimStates() const { return x0_states.size(); }\n  const CPASAnimState* GetAnimState(pas::EAnimationState id) const {\n    for (const CPASAnimState& state : x0_states)\n      if (id == state.GetStateId())\n        return &state;\n\n    return nullptr;\n  }\n  const CPASAnimState* GetAnimStateByIndex(size_t index) const {\n    if (index >= x0_states.size()) {\n      return nullptr;\n    }\n\n    return &x0_states[index];\n  }\n\n  bool HasState(pas::EAnimationState id) const {\n    const auto& st = std::find_if(x0_states.begin(), x0_states.end(),\n                                  [&id](const CPASAnimState& other) -> bool { return other.GetStateId() == id; });\n    return st != x0_states.end();\n  }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CPASParmInfo.cpp",
    "content": "#include \"Runtime/Character/CPASParmInfo.hpp\"\n\nnamespace metaforce {\n\nCPASParmInfo::CPASParmInfo(CInputStream& in)\n: x0_type(CPASAnimParm::EParmType(in.ReadLong())), x4_weightFunction(EWeightFunction(in.ReadLong())) {\n  xc_min.m_int = 0;\n  x10_max.m_int = 0;\n\n  x8_weight = in.ReadFloat();\n\n  switch (x0_type) {\n  case CPASAnimParm::EParmType::Int32:\n    xc_min.m_int = in.ReadInt32();\n    x10_max.m_int = in.ReadInt32();\n    break;\n  case CPASAnimParm::EParmType::UInt32:\n    xc_min.m_uint = in.ReadLong();\n    x10_max.m_uint = in.ReadLong();\n    break;\n  case CPASAnimParm::EParmType::Float:\n    xc_min.m_float = in.ReadFloat();\n    x10_max.m_float = in.ReadFloat();\n    break;\n  case CPASAnimParm::EParmType::Bool:\n    xc_min.m_bool = in.ReadBool();\n    x10_max.m_bool = in.ReadBool();\n    break;\n  case CPASAnimParm::EParmType::Enum:\n    xc_min.m_int = in.ReadInt32();\n    x10_max.m_int = in.ReadInt32();\n    break;\n  default:\n    break;\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CPASParmInfo.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/Character/CPASAnimParm.hpp\"\n\nnamespace metaforce {\n\nclass CPASParmInfo {\npublic:\n  enum class EWeightFunction { ExactMatch, PercentError, AngularPercent, NoWeight };\n\nprivate:\n  CPASAnimParm::EParmType x0_type;\n  EWeightFunction x4_weightFunction;\n  float x8_weight;\n  CPASAnimParm::UParmValue xc_min;\n  CPASAnimParm::UParmValue x10_max;\n\npublic:\n  explicit CPASParmInfo(CInputStream& in);\n  CPASAnimParm::EParmType GetParameterType() const { return x0_type; }\n  EWeightFunction GetWeightFunction() const { return x4_weightFunction; }\n  float GetParameterWeight() const { return x8_weight; }\n  CPASAnimParm::UParmValue GetWeightMinValue() const { return xc_min; }\n  CPASAnimParm::UParmValue GetWeightMaxValue() const { return x10_max; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CPOINode.cpp",
    "content": "#include \"Runtime/Character/CPOINode.hpp\"\n\n#include \"Runtime/Character/CAnimSourceReader.hpp\"\n#include \"Runtime/Character/CBoolPOINode.hpp\"\n#include \"Runtime/Character/CInt32POINode.hpp\"\n#include \"Runtime/Character/CParticlePOINode.hpp\"\n#include \"Runtime/Character/CSoundPOINode.hpp\"\n\nnamespace metaforce {\n\nCPOINode::CPOINode(std::string_view name, EPOIType type, const CCharAnimTime& time, s32 index, bool unique,\n                   float weight, s32 e, s32 f)\n: x4_(1)\n, x8_name(name)\n, x18_type(type)\n, x1c_time(time)\n, x24_index(index)\n, x28_unique(unique)\n, x2c_weight(weight)\n, x30_charIdx(e)\n, x34_flags(f) {}\n\nCPOINode::CPOINode(CInputStream& in)\n: x4_(in.ReadShort())\n, x8_name(in.Get<std::string>())\n, x18_type(EPOIType(in.ReadShort()))\n, x1c_time(in)\n, x24_index(in.ReadInt32())\n, x28_unique(in.ReadBool())\n, x2c_weight(in.ReadFloat())\n, x30_charIdx(in.ReadInt32())\n, x34_flags(in.ReadInt32()) {}\n\nbool CPOINode::operator>(const CPOINode& other) const { return x1c_time > other.x1c_time; }\n\nbool CPOINode::operator<(const CPOINode& other) const { return x1c_time < other.x1c_time; }\n\ntemplate <class T>\nsize_t _getPOIList(const CCharAnimTime& time, T* listOut, size_t capacity, size_t iterator, u32 unk1,\n                   const std::vector<T>& stream, const CCharAnimTime& curTime, const IAnimSourceInfo& animInfo,\n                   size_t passedCount) {\n  size_t ret = 0;\n  if (animInfo.HasPOIData() && stream.size()) {\n    const CCharAnimTime dur = animInfo.GetAnimationDuration();\n    CCharAnimTime targetTime = curTime + time;\n    if (targetTime >= dur) {\n      targetTime = dur;\n    }\n\n    if (passedCount >= stream.size()) {\n      return ret;\n    }\n\n    CCharAnimTime nodeTime = stream[passedCount].GetTime();\n    while (passedCount < stream.size() && nodeTime <= targetTime) {\n      const size_t idx = iterator + ret;\n      if (idx < capacity) {\n        listOut[idx] = T::CopyNodeMinusStartTime(stream[passedCount], curTime);\n        ++ret;\n      }\n      ++passedCount;\n      if (passedCount < stream.size())\n        nodeTime = stream[passedCount].GetTime();\n    }\n  }\n  return ret;\n}\n\ntemplate <class T>\nsize_t _getPOIList(const CCharAnimTime& time, T* listOut, size_t capacity, size_t iterator, u32 unk1,\n                   const std::vector<T>& stream, const CCharAnimTime& curTime) {\n  size_t ret = 0;\n\n  const CCharAnimTime targetTime = curTime + time;\n\n  for (size_t it = iterator; it < stream.size(); ++it) {\n    const CCharAnimTime nodeTime = stream[it].GetTime();\n    if (nodeTime > targetTime) {\n      return ret;\n    }\n    const size_t idx = iterator + ret;\n    if (nodeTime >= curTime && idx < capacity) {\n      listOut[idx] = T::CopyNodeMinusStartTime(stream[it], curTime);\n      ++ret;\n    }\n  }\n\n  return ret;\n}\n\ntemplate size_t _getPOIList<CBoolPOINode>(const CCharAnimTime& time, CBoolPOINode* listOut, size_t capacity,\n                                          size_t iterator, u32 unk1, const std::vector<CBoolPOINode>& stream,\n                                          const CCharAnimTime& curTime, const IAnimSourceInfo& animInfo,\n                                          size_t passedCount);\ntemplate size_t _getPOIList<CBoolPOINode>(const CCharAnimTime& time, CBoolPOINode* listOut, size_t capacity,\n                                          size_t iterator, u32 unk1, const std::vector<CBoolPOINode>& stream,\n                                          const CCharAnimTime& curTime);\n\ntemplate size_t _getPOIList<CInt32POINode>(const CCharAnimTime& time, CInt32POINode* listOut, size_t capacity,\n                                           size_t iterator, u32 unk1, const std::vector<CInt32POINode>& stream,\n                                           const CCharAnimTime& curTime, const IAnimSourceInfo& animInfo,\n                                           size_t passedCount);\ntemplate size_t _getPOIList<CInt32POINode>(const CCharAnimTime& time, CInt32POINode* listOut, size_t capacity,\n                                           size_t iterator, u32 unk1, const std::vector<CInt32POINode>& stream,\n                                           const CCharAnimTime& curTime);\n\ntemplate size_t _getPOIList<CParticlePOINode>(const CCharAnimTime& time, CParticlePOINode* listOut, size_t capacity,\n                                              size_t iterator, u32 unk1, const std::vector<CParticlePOINode>& stream,\n                                              const CCharAnimTime& curTime, const IAnimSourceInfo& animInfo,\n                                              size_t passedCount);\ntemplate size_t _getPOIList<CParticlePOINode>(const CCharAnimTime& time, CParticlePOINode* listOut, size_t capacity,\n                                              size_t iterator, u32 unk1, const std::vector<CParticlePOINode>& stream,\n                                              const CCharAnimTime& curTime);\n\ntemplate size_t _getPOIList<CSoundPOINode>(const CCharAnimTime& time, CSoundPOINode* listOut, size_t capacity,\n                                           size_t iterator, u32 unk1, const std::vector<CSoundPOINode>& stream,\n                                           const CCharAnimTime& curTime, const IAnimSourceInfo& animInfo,\n                                           size_t passedCount);\ntemplate size_t _getPOIList<CSoundPOINode>(const CCharAnimTime& time, CSoundPOINode* listOut, size_t capacity,\n                                           size_t iterator, u32 unk1, const std::vector<CSoundPOINode>& stream,\n                                           const CCharAnimTime& curTime);\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CPOINode.hpp",
    "content": "#pragma once\n\n#include <string>\n#include <vector>\n\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/Character/CCharAnimTime.hpp\"\n\nnamespace metaforce {\nclass IAnimSourceInfo;\n\nenum class EPOIType : u16 {\n  Loop = 0,\n  EmptyBool = 1,\n  EmptyInt32 = 2,\n  SoundInt32 = 4,\n  Particle = 5,\n  UserEvent = 6,\n  RandRate = 7,\n  Sound = 8,\n};\n\nclass CPOINode {\nprotected:\n  u16 x4_ = 1;\n  std::string x8_name;\n  EPOIType x18_type;\n  CCharAnimTime x1c_time;\n  s32 x24_index;\n  bool x28_unique;\n  float x2c_weight;\n  s32 x30_charIdx = -1;\n  s32 x34_flags;\n\npublic:\n  explicit CPOINode(std::string_view name, EPOIType type, const CCharAnimTime& time, s32 index, bool unique,\n                    float weight, s32 charIdx, s32 flags);\n  explicit CPOINode(CInputStream& in);\n  virtual ~CPOINode() = default;\n\n  std::string_view GetString() const { return x8_name; }\n  const CCharAnimTime& GetTime() const { return x1c_time; }\n  void SetTime(const CCharAnimTime& time) { x1c_time = time; }\n  EPOIType GetPoiType() const { return x18_type; }\n  s32 GetIndex() const { return x24_index; }\n  bool GetUnique() const { return x28_unique; }\n  float GetWeight() const { return x2c_weight; }\n  s32 GetCharacterIndex() const { return x30_charIdx; }\n  s32 GetFlags() const { return x34_flags; }\n\n  bool operator>(const CPOINode& other) const;\n  bool operator<(const CPOINode& other) const;\n};\n\ntemplate <class T>\nsize_t _getPOIList(const CCharAnimTime& time, T* listOut, size_t capacity, size_t iterator, u32 unk1,\n                   const std::vector<T>& stream, const CCharAnimTime& curTime, const IAnimSourceInfo& animInfo,\n                   size_t passedCount);\n\ntemplate <class T>\nsize_t _getPOIList(const CCharAnimTime& time, T* listOut, size_t capacity, size_t iterator, u32 unk1,\n                   const std::vector<T>& stream, const CCharAnimTime& curTime);\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CParticleData.cpp",
    "content": "#include \"Runtime/Character/CParticleData.hpp\"\n\nnamespace metaforce {\n\nCParticleData::CParticleData(CInputStream& in)\n: x0_duration(in.ReadLong())\n, x4_particle(in)\n, xc_boneName(in.Get<std::string>())\n, x1c_scale(in.ReadFloat())\n, x20_parentMode(EParentedMode(in.ReadLong())) {}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CParticleData.hpp",
    "content": "#pragma once\n\n#include <string>\n\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\n\nclass CParticleData {\npublic:\n  enum class EParentedMode { Initial, ContinuousEmitter, ContinuousSystem };\n\nprivate:\n  u32 x0_duration = 0;\n  SObjectTag x4_particle;\n  std::string xc_boneName = \"root\";\n  float x1c_scale = 1.f;\n  EParentedMode x20_parentMode = EParentedMode::Initial;\n\npublic:\n  CParticleData() = default;\n  CParticleData(const SObjectTag& tag, std::string_view boneName, float scale, EParentedMode mode)\n  : x4_particle(tag), xc_boneName(boneName), x1c_scale(scale), x20_parentMode(mode) {}\n  explicit CParticleData(CInputStream& in);\n  u32 GetDuration() const { return x0_duration; }\n  const SObjectTag& GetTag() const { return x4_particle; }\n  std::string_view GetSegmentName() const { return xc_boneName; }\n  float GetScale() const { return x1c_scale; }\n  EParentedMode GetParentedMode() const { return x20_parentMode; }\n};\n\nclass CAuxiliaryParticleData {\n  u32 x0_duration = 0;\n  SObjectTag x4_particle;\n  zeus::CVector3f xc_translation;\n  float x18_scale = 1.f;\n\npublic:\n  CAuxiliaryParticleData(u32 duration, const SObjectTag& particle, const zeus::CVector3f& translation, float scale)\n  : x0_duration(duration), x4_particle(particle), xc_translation(translation), x18_scale(scale) {}\n  u32 GetDuration() const { return x0_duration; }\n  const SObjectTag& GetTag() const { return x4_particle; }\n  const zeus::CVector3f& GetTranslation() const { return xc_translation; }\n  float GetScale() const { return x18_scale; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CParticleDatabase.cpp",
    "content": "#include \"Runtime/Character/CParticleDatabase.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Character/CCharLayoutInfo.hpp\"\n#include \"Runtime/Character/CPoseAsTransforms.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/Particle/CParticleElectric.hpp\"\n#include \"Runtime/Particle/CParticleSwoosh.hpp\"\n\nnamespace metaforce {\n\nCParticleDatabase::CParticleDatabase() = default;\n\nvoid CParticleDatabase::CacheParticleDesc(const SObjectTag& tag) {}\n\nvoid CParticleDatabase::CacheParticleDesc(const CCharacterInfo::CParticleResData& desc) {\n  for (CAssetId id : desc.x0_part) {\n    auto search = x0_particleDescs.find(id);\n    if (search == x0_particleDescs.cend())\n      x0_particleDescs[id] =\n          std::make_shared<TLockedToken<CGenDescription>>(g_SimplePool->GetObj(SObjectTag{FOURCC('PART'), id}));\n  }\n  for (CAssetId id : desc.x10_swhc) {\n    auto search = x14_swooshDescs.find(id);\n    if (search == x14_swooshDescs.cend())\n      x14_swooshDescs[id] =\n          std::make_shared<TLockedToken<CSwooshDescription>>(g_SimplePool->GetObj(SObjectTag{FOURCC('SWHC'), id}));\n  }\n  for (CAssetId id : desc.x20_elsc) {\n    auto search = x28_electricDescs.find(id);\n    if (search == x28_electricDescs.cend())\n      x28_electricDescs[id] =\n          std::make_shared<TLockedToken<CElectricDescription>>(g_SimplePool->GetObj(SObjectTag{FOURCC('ELSC'), id}));\n  }\n}\n\nvoid CParticleDatabase::SetModulationColorAllActiveEffectsForParticleDB(const zeus::CColor& color, DrawMap& map) {\n  for (auto& e : map) {\n    if (e.second) {\n      e.second->SetModulationColor(color);\n    }\n  }\n}\n\nvoid CParticleDatabase::SetModulationColorAllActiveEffects(const zeus::CColor& color) {\n  SetModulationColorAllActiveEffectsForParticleDB(color, x3c_rendererDrawLoop);\n  SetModulationColorAllActiveEffectsForParticleDB(color, x50_firstDrawLoop);\n  SetModulationColorAllActiveEffectsForParticleDB(color, x64_lastDrawLoop);\n  SetModulationColorAllActiveEffectsForParticleDB(color, x78_rendererDraw);\n  SetModulationColorAllActiveEffectsForParticleDB(color, x8c_firstDraw);\n  SetModulationColorAllActiveEffectsForParticleDB(color, xa0_lastDraw);\n}\n\nvoid CParticleDatabase::SuspendAllActiveEffectsForParticleDB(CStateManager& mgr, DrawMap& map) {\n  for (auto& e : map) {\n    e.second->SetParticleEmission(false, mgr);\n  }\n}\n\nvoid CParticleDatabase::SuspendAllActiveEffects(CStateManager& stateMgr) {\n  SuspendAllActiveEffectsForParticleDB(stateMgr, x3c_rendererDrawLoop);\n  SuspendAllActiveEffectsForParticleDB(stateMgr, x50_firstDrawLoop);\n  SuspendAllActiveEffectsForParticleDB(stateMgr, x64_lastDrawLoop);\n}\n\nvoid CParticleDatabase::DeleteAllLightsForParticleDB(CStateManager& mgr, DrawMap& map) {\n  for (auto& e : map) {\n    e.second->DeleteLight(mgr);\n  }\n}\n\nvoid CParticleDatabase::DeleteAllLights(CStateManager& mgr) {\n  DeleteAllLightsForParticleDB(mgr, x3c_rendererDrawLoop);\n  DeleteAllLightsForParticleDB(mgr, x50_firstDrawLoop);\n  DeleteAllLightsForParticleDB(mgr, x64_lastDrawLoop);\n  DeleteAllLightsForParticleDB(mgr, x78_rendererDraw);\n  DeleteAllLightsForParticleDB(mgr, x8c_firstDraw);\n  DeleteAllLightsForParticleDB(mgr, xa0_lastDraw);\n}\n\nvoid CParticleDatabase::UpdateParticleGenDB(float dt, const CPoseAsTransforms& pose, const CCharLayoutInfo& charInfo,\n                                            const zeus::CTransform& xf, const zeus::CVector3f& scale,\n                                            CStateManager& stateMgr, DrawMap& map, bool deleteIfDone) {\n  for (auto it = map.begin(); it != map.end();) {\n    CParticleGenInfo& info = *it->second;\n    if (info.GetIsActive()) {\n      if (info.GetType() == EParticleGenType::Normal) {\n        const CSegId segId = charInfo.GetSegIdFromString(info.GetLocatorName());\n        if (segId.IsInvalid()) {\n          ++it;\n          continue;\n        }\n        if (!pose.ContainsDataFor(segId)) {\n          ++it;\n          continue;\n        }\n        const zeus::CVector3f& off = pose.GetOffset(segId);\n        switch (info.GetParentedMode()) {\n        case CParticleData::EParentedMode::Initial: {\n          if (info.GetIsGrabInitialData()) {\n            zeus::CTransform segXf((info.GetFlags() & 0x10) ? zeus::CMatrix3f() : pose.GetRotation(segId),\n                                   off * scale);\n            zeus::CTransform compXf = xf * segXf;\n            info.SetCurTransform(compXf.getRotation());\n            info.SetCurOffset(compXf.origin);\n            info.SetCurrentTime(0.f);\n            info.SetIsGrabInitialData(false);\n          }\n\n          info.SetOrientation(info.GetCurTransform(), stateMgr);\n          info.SetTranslation(info.GetCurOffset(), stateMgr);\n\n          if (info.GetFlags() & 0x2000)\n            info.SetGlobalScale(info.GetCurScale() * scale);\n          else\n            info.SetGlobalScale(info.GetCurScale());\n\n          break;\n        }\n        case CParticleData::EParentedMode::ContinuousEmitter:\n        case CParticleData::EParentedMode::ContinuousSystem: {\n          if (info.GetIsGrabInitialData()) {\n            info.SetCurrentTime(0.f);\n            info.SetIsGrabInitialData(false);\n          }\n\n          zeus::CTransform segXf(pose.GetRotation(segId), off * scale);\n          zeus::CTransform compXf = xf * segXf;\n\n          if (info.GetParentedMode() == CParticleData::EParentedMode::ContinuousEmitter) {\n            info.SetTranslation(compXf.origin, stateMgr);\n            if (info.GetFlags() & 0x10)\n              info.SetOrientation(xf.getRotation(), stateMgr);\n            else\n              info.SetOrientation(compXf.getRotation(), stateMgr);\n          } else {\n            info.SetGlobalTranslation(compXf.origin, stateMgr);\n            if (info.GetFlags() & 0x10)\n              info.SetGlobalOrientation(xf.getRotation(), stateMgr);\n            else\n              info.SetGlobalOrientation(compXf.getRotation(), stateMgr);\n          }\n\n          if (info.GetFlags() & 0x2000)\n            info.SetGlobalScale(info.GetCurScale() * scale);\n          else\n            info.SetGlobalScale(info.GetCurScale());\n\n          break;\n        }\n        default:\n          break;\n        }\n      }\n\n      float sec = (info.GetInactiveStartTime() == 0.f) ? 10000000.f : info.GetInactiveStartTime();\n      if (info.GetCurrentTime() > sec) {\n        info.SetIsActive(false);\n        info.SetParticleEmission(false, stateMgr);\n        info.MarkFinishTime();\n        if (info.GetFlags() & 1)\n          info.DestroyParticles();\n      }\n    }\n\n    info.Update(dt, stateMgr);\n\n    if (!info.GetIsActive()) {\n      if (!info.HasActiveParticles() && info.GetCurrentTime() - info.GetFinishTime() > 5.f && deleteIfDone) {\n        info.DeleteLight(stateMgr);\n        it = map.erase(it);\n        continue;\n      }\n    } else if (info.IsSystemDeletable()) {\n      info.DeleteLight(stateMgr);\n      it = map.erase(it);\n      continue;\n    }\n\n    info.OffsetTime(dt);\n    ++it;\n  }\n}\n\nvoid CParticleDatabase::Update(float dt, const CPoseAsTransforms& pose, const CCharLayoutInfo& charInfo,\n                               const zeus::CTransform& xf, const zeus::CVector3f& scale, CStateManager& stateMgr) {\n  if (!xb4_24_updatesEnabled)\n    return;\n\n  UpdateParticleGenDB(dt, pose, charInfo, xf, scale, stateMgr, x3c_rendererDrawLoop, true);\n  UpdateParticleGenDB(dt, pose, charInfo, xf, scale, stateMgr, x50_firstDrawLoop, true);\n  UpdateParticleGenDB(dt, pose, charInfo, xf, scale, stateMgr, x64_lastDrawLoop, true);\n  UpdateParticleGenDB(dt, pose, charInfo, xf, scale, stateMgr, x78_rendererDraw, false);\n  UpdateParticleGenDB(dt, pose, charInfo, xf, scale, stateMgr, x8c_firstDraw, false);\n  UpdateParticleGenDB(dt, pose, charInfo, xf, scale, stateMgr, xa0_lastDraw, false);\n\n  xb4_25_anySystemsDrawnWithModel =\n      (x50_firstDrawLoop.size() || x64_lastDrawLoop.size() || x8c_firstDraw.size() || xa0_lastDraw.size());\n}\n\nvoid CParticleDatabase::RenderParticleGenMap(const DrawMap& map) {\n  for (const auto& e : map) {\n    e.second->Render();\n  }\n}\n\nvoid CParticleDatabase::RenderParticleGenMapMasked(const DrawMap& map, int mask, int target) {\n  for (const auto& e : map) {\n    if ((e.second->GetFlags() & mask) == target) {\n      e.second->Render();\n    }\n  }\n}\n\nvoid CParticleDatabase::AddToRendererClippedParticleGenMap(const DrawMap& map, const zeus::CFrustum& frustum) {\n  for (const auto& e : map) {\n    const auto bounds = e.second->GetBounds();\n    if (bounds && frustum.aabbFrustumTest(*bounds)) {\n      e.second->AddToRenderer();\n    }\n  }\n}\n\nvoid CParticleDatabase::AddToRendererClippedParticleGenMapMasked(const DrawMap& map, const zeus::CFrustum& frustum,\n                                                                 int mask, int target) {\n  for (const auto& e : map) {\n    if ((e.second->GetFlags() & mask) == target) {\n      const auto bounds = e.second->GetBounds();\n      if (bounds && frustum.aabbFrustumTest(*bounds)) {\n        e.second->AddToRenderer();\n      }\n    }\n  }\n}\n\nvoid CParticleDatabase::RenderSystemsToBeDrawnLastMasked(int mask, int target) const {\n  RenderParticleGenMapMasked(xa0_lastDraw, mask, target);\n  RenderParticleGenMapMasked(x64_lastDrawLoop, mask, target);\n}\n\nvoid CParticleDatabase::RenderSystemsToBeDrawnLast() const {\n  RenderParticleGenMap(xa0_lastDraw);\n  RenderParticleGenMap(x64_lastDrawLoop);\n}\n\nvoid CParticleDatabase::RenderSystemsToBeDrawnFirstMasked(int mask, int target) const {\n  RenderParticleGenMapMasked(x8c_firstDraw, mask, target);\n  RenderParticleGenMapMasked(x50_firstDrawLoop, mask, target);\n}\n\nvoid CParticleDatabase::RenderSystemsToBeDrawnFirst() const {\n  RenderParticleGenMap(x8c_firstDraw);\n  RenderParticleGenMap(x50_firstDrawLoop);\n}\n\nvoid CParticleDatabase::AddToRendererClippedMasked(const zeus::CFrustum& frustum, int mask, int target) const {\n  AddToRendererClippedParticleGenMapMasked(x78_rendererDraw, frustum, mask, target);\n  AddToRendererClippedParticleGenMapMasked(x3c_rendererDrawLoop, frustum, mask, target);\n}\n\nvoid CParticleDatabase::AddToRendererClipped(const zeus::CFrustum& frustum) const {\n  AddToRendererClippedParticleGenMap(x78_rendererDraw, frustum);\n  AddToRendererClippedParticleGenMap(x3c_rendererDrawLoop, frustum);\n}\n\nCParticleGenInfo* CParticleDatabase::GetParticleEffect(std::string_view name) const {\n  if (const auto search = x3c_rendererDrawLoop.find(name); search != x3c_rendererDrawLoop.cend()) {\n    return search->second.get();\n  }\n\n  if (const auto search = x50_firstDrawLoop.find(name); search != x50_firstDrawLoop.cend()) {\n    return search->second.get();\n  }\n\n  if (const auto search = x64_lastDrawLoop.find(name); search != x64_lastDrawLoop.cend()) {\n    return search->second.get();\n  }\n\n  if (const auto search = x78_rendererDraw.find(name); search != x78_rendererDraw.cend()) {\n    return search->second.get();\n  }\n\n  if (const auto search = x8c_firstDraw.find(name); search != x8c_firstDraw.cend()) {\n    return search->second.get();\n  }\n\n  if (const auto search = xa0_lastDraw.find(name); search != xa0_lastDraw.cend()) {\n    return search->second.get();\n  }\n\n  return nullptr;\n}\n\nvoid CParticleDatabase::SetParticleEffectState(std::string_view name, bool active, CStateManager& mgr) {\n  if (CParticleGenInfo* info = GetParticleEffect(name)) {\n    info->SetParticleEmission(active, mgr);\n    info->SetIsActive(active);\n    if (!active && (info->GetFlags() & 1) != 0)\n      info->DestroyParticles();\n    info->SetIsGrabInitialData(true);\n  }\n}\n\nvoid CParticleDatabase::SetCEXTValue(std::string_view name, int idx, float value) {\n  if (CParticleGenInfo* info = GetParticleEffect(name)) {\n    std::static_pointer_cast<CElementGen>(static_cast<CParticleGenInfoGeneric*>(info)->GetParticleSystem())\n        ->SetExternalVar(idx, value);\n  }\n}\n\ntemplate <class T, class U>\nstatic s32 _getGraphicLightId(const T& system, const U& desc) {\n  if (system->SystemHasLight())\n    return s32(desc.GetObjectTag()->id.Value());\n  return -1;\n}\n\nvoid CParticleDatabase::AddAuxiliaryParticleEffect(std::string_view name, int flags, const CAuxiliaryParticleData& data,\n                                                   const zeus::CVector3f& scale, CStateManager& mgr, TAreaId aid,\n                                                   int lightId) {\n  if (CParticleGenInfo* info = GetParticleEffect(name)) {\n    if (!info->GetIsActive()) {\n      info->SetParticleEmission(true, mgr);\n      info->SetIsActive(true);\n      info->SetIsGrabInitialData(true);\n      info->SetFlags(flags);\n    }\n    return;\n  }\n\n  zeus::CVector3f scaleVec;\n  if (flags & 0x2)\n    scaleVec.splat(data.GetScale());\n  else\n    scaleVec = scale * data.GetScale();\n\n  switch (data.GetTag().type.toUint32()) {\n  case SBIG('PART'): {\n    const auto search = x0_particleDescs.find(data.GetTag().id);\n    if (search != x0_particleDescs.cend()) {\n      auto sys = std::make_shared<CElementGen>(*search->second);\n      auto newGen = std::make_unique<CParticleGenInfoGeneric>(\n          data.GetTag(), sys, data.GetDuration(), \"NOT_A_VALID_LOCATOR\", scaleVec,\n          CParticleData::EParentedMode::Initial, flags, mgr, aid, lightId + _getGraphicLightId(sys, *search->second),\n          EParticleGenType::Auxiliary);\n\n      newGen->SetGlobalTranslation(data.GetTranslation(), mgr);\n      newGen->SetIsGrabInitialData(false);\n      InsertParticleGen(false, flags, name, std::move(newGen));\n    }\n    break;\n  }\n  default:\n    break;\n  }\n}\n\nvoid CParticleDatabase::AddParticleEffect(std::string_view name, int flags, const CParticleData& data,\n                                          const zeus::CVector3f& scale, CStateManager& mgr, TAreaId aid, bool oneShot,\n                                          int lightId) {\n  if (CParticleGenInfo* info = GetParticleEffect(name)) {\n    if (!info->GetIsActive()) {\n      info->SetParticleEmission(true, mgr);\n      info->SetIsActive(true);\n      info->SetIsGrabInitialData(true);\n      info->SetFlags(flags);\n    }\n    return;\n  }\n\n  zeus::CVector3f scaleVec;\n  if (flags & 0x2)\n    scaleVec.splat(data.GetScale());\n  else\n    scaleVec = scale * data.GetScale();\n\n  std::unique_ptr<CParticleGenInfo> newGen;\n  switch (data.GetTag().type.toUint32()) {\n  case SBIG('PART'): {\n    const auto search = x0_particleDescs.find(data.GetTag().id);\n    if (search != x0_particleDescs.cend()) {\n      auto sys = std::make_shared<CElementGen>(*search->second);\n      newGen = std::make_unique<CParticleGenInfoGeneric>(\n          data.GetTag(), sys, data.GetDuration(), data.GetSegmentName(), scaleVec, data.GetParentedMode(), flags, mgr,\n          aid, lightId + _getGraphicLightId(sys, *search->second), EParticleGenType::Normal);\n    }\n    break;\n  }\n  case SBIG('SWHC'): {\n    const auto search = x14_swooshDescs.find(data.GetTag().id);\n    if (search != x14_swooshDescs.cend()) {\n      auto sys = std::make_shared<CParticleSwoosh>(*search->second, 0);\n      newGen = std::make_unique<CParticleGenInfoGeneric>(data.GetTag(), sys, data.GetDuration(), data.GetSegmentName(),\n                                                         scaleVec, data.GetParentedMode(), flags, mgr, aid, -1,\n                                                         EParticleGenType::Normal);\n    }\n    break;\n  }\n  case SBIG('ELSC'): {\n    const auto search = x28_electricDescs.find(data.GetTag().id);\n    if (search != x28_electricDescs.cend()) {\n      auto sys = std::make_shared<CParticleElectric>(*search->second);\n      newGen = std::make_unique<CParticleGenInfoGeneric>(\n          data.GetTag(), sys, data.GetDuration(), data.GetSegmentName(), scaleVec, data.GetParentedMode(), flags, mgr,\n          aid, lightId + _getGraphicLightId(sys, *search->second), EParticleGenType::Normal);\n    }\n    break;\n  }\n  default:\n    break;\n  }\n\n  if (newGen) {\n    newGen->SetIsActive(true);\n    newGen->SetParticleEmission(true, mgr);\n    newGen->SetIsGrabInitialData(true);\n    InsertParticleGen(oneShot, flags, name, std::move(newGen));\n  }\n}\n\nvoid CParticleDatabase::InsertParticleGen(bool oneShot, int flags, std::string_view name,\n                                          std::unique_ptr<CParticleGenInfo>&& gen) {\n  DrawMap* useMap;\n  if (oneShot) {\n    if ((flags & 0x40) != 0) {\n      useMap = &xa0_lastDraw;\n    } else if ((flags & 0x20) != 0) {\n      useMap = &x8c_firstDraw;\n    } else {\n      useMap = &x78_rendererDraw;\n    }\n  } else {\n    if ((flags & 0x40) != 0) {\n      useMap = &x64_lastDrawLoop;\n    } else if ((flags & 0x20) != 0) {\n      useMap = &x50_firstDrawLoop;\n    } else {\n      useMap = &x3c_rendererDrawLoop;\n    }\n  }\n\n  useMap->emplace(name, std::move(gen));\n\n  if ((flags & 0x60) != 0) {\n    xb4_25_anySystemsDrawnWithModel = true;\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CParticleDatabase.hpp",
    "content": "#pragma once\n\n#include <map>\n#include <memory>\n#include <string>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/Character/CCharacterInfo.hpp\"\n#include \"Runtime/Character/CParticleGenInfo.hpp\"\n#include \"Runtime/Particle/CGenDescription.hpp\"\n#include \"Runtime/Particle/CSwooshDescription.hpp\"\n#include \"Runtime/Particle/CElectricDescription.hpp\"\n\n#include <zeus/CColor.hpp>\n#include <zeus/CFrustum.hpp>\n\nnamespace metaforce {\nclass CCharLayoutInfo;\nclass CPoseAsTransforms;\n\nclass CParticleDatabase {\n  using DrawMap = std::map<std::string, std::unique_ptr<CParticleGenInfo>, std::less<>>;\n\n  std::map<CAssetId, std::shared_ptr<TLockedToken<CGenDescription>>> x0_particleDescs;\n  std::map<CAssetId, std::shared_ptr<TLockedToken<CSwooshDescription>>> x14_swooshDescs;\n  std::map<CAssetId, std::shared_ptr<TLockedToken<CElectricDescription>>> x28_electricDescs;\n  DrawMap x3c_rendererDrawLoop;\n  DrawMap x50_firstDrawLoop;\n  DrawMap x64_lastDrawLoop;\n  DrawMap x78_rendererDraw;\n  DrawMap x8c_firstDraw;\n  DrawMap xa0_lastDraw;\n  bool xb4_24_updatesEnabled : 1 = true;\n  bool xb4_25_anySystemsDrawnWithModel : 1 = false;\n\n  static void SetModulationColorAllActiveEffectsForParticleDB(const zeus::CColor& color, DrawMap& map);\n  static void SuspendAllActiveEffectsForParticleDB(CStateManager& mgr, DrawMap& map);\n  static void DeleteAllLightsForParticleDB(CStateManager& mgr, DrawMap& map);\n  static void RenderParticleGenMap(const DrawMap& map);\n  static void RenderParticleGenMapMasked(const DrawMap& map, int mask, int target);\n  static void AddToRendererClippedParticleGenMap(const DrawMap& map, const zeus::CFrustum& frustum);\n  static void AddToRendererClippedParticleGenMapMasked(const DrawMap& map, const zeus::CFrustum& frustum, int mask,\n                                                       int target);\n  static void UpdateParticleGenDB(float dt, const CPoseAsTransforms& pose, const CCharLayoutInfo& charInfo,\n                                  const zeus::CTransform& xf, const zeus::CVector3f& vec, CStateManager& stateMgr,\n                                  DrawMap& map, bool deleteIfDone);\n\npublic:\n  CParticleDatabase();\n  void CacheParticleDesc(const SObjectTag& tag);\n  void CacheParticleDesc(const CCharacterInfo::CParticleResData& desc);\n  void SetModulationColorAllActiveEffects(const zeus::CColor& color);\n  void SuspendAllActiveEffects(CStateManager& stateMgr);\n  void DeleteAllLights(CStateManager& stateMgr);\n  void Update(float dt, const CPoseAsTransforms& pose, const CCharLayoutInfo& charInfo, const zeus::CTransform& xf,\n              const zeus::CVector3f& scale, CStateManager& stateMgr);\n  void RenderSystemsToBeDrawnLastMasked(int mask, int target) const;\n  void RenderSystemsToBeDrawnLast() const;\n  void RenderSystemsToBeDrawnFirstMasked(int mask, int target) const;\n  void RenderSystemsToBeDrawnFirst() const;\n  void AddToRendererClippedMasked(const zeus::CFrustum& frustum, int mask, int target) const;\n  void AddToRendererClipped(const zeus::CFrustum& frustum) const;\n  CParticleGenInfo* GetParticleEffect(std::string_view name) const;\n  void SetParticleEffectState(std::string_view name, bool active, CStateManager& mgr);\n  void SetCEXTValue(std::string_view name, int idx, float value);\n  void AddAuxiliaryParticleEffect(std::string_view name, int flags, const CAuxiliaryParticleData& data,\n                                  const zeus::CVector3f& scale, CStateManager& mgr, TAreaId aid, int lightId);\n  void AddParticleEffect(std::string_view name, int flags, const CParticleData& data, const zeus::CVector3f& scale,\n                         CStateManager& mgr, TAreaId aid, bool oneShot, int lightId);\n  void InsertParticleGen(bool oneShot, int flags, std::string_view name, std::unique_ptr<CParticleGenInfo>&& gen);\n  bool AreAnySystemsDrawnWithModel() const { return xb4_25_anySystemsDrawnWithModel; }\n  void SetUpdatesEnabled(bool active) { xb4_24_updatesEnabled = active; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CParticleGenInfo.cpp",
    "content": "#include \"Runtime/Character/CParticleGenInfo.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Graphics/IRenderer.hpp\"\n#include \"Runtime/Particle/CParticleGen.hpp\"\n#include \"Runtime/World/CGameLight.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCParticleGenInfo::CParticleGenInfo(const SObjectTag& part, int frameCount, std::string_view boneName,\n                                   const zeus::CVector3f& scale, CParticleData::EParentedMode parentMode, int flags,\n                                   EParticleGenType type)\n: x4_part(part)\n, xc_seconds(frameCount / 60.f)\n, x10_boneName(boneName)\n, x28_parentMode(parentMode)\n, x2c_flags(flags)\n, x30_particleScale(scale)\n, x80_type(type) {}\n\nstatic TUniqueId _initializeLight(const std::weak_ptr<CParticleGen>& system, CStateManager& stateMgr, TAreaId areaId,\n                                  int lightId) {\n  TUniqueId ret = kInvalidUniqueId;\n  std::shared_ptr<CParticleGen> systemRef = system.lock();\n  if (systemRef->SystemHasLight()) {\n    ret = stateMgr.AllocateUniqueId();\n    stateMgr.AddObject(\n        new CGameLight(ret, areaId, false, \"ParticleLight\",\n                       zeus::CTransform(systemRef->GetOrientation().buildMatrix3f(), systemRef->GetTranslation()),\n                       kInvalidUniqueId, systemRef->GetLight(), u32(lightId), 0, 0.f));\n  }\n  return ret;\n}\n\nCParticleGenInfoGeneric::CParticleGenInfoGeneric(const SObjectTag& part, const std::weak_ptr<CParticleGen>& system,\n                                                 int frameCount, std::string_view boneName,\n                                                 const zeus::CVector3f& scale, CParticleData::EParentedMode parentMode,\n                                                 int flags, CStateManager& stateMgr, TAreaId areaId, int lightId,\n                                                 EParticleGenType state)\n: CParticleGenInfo(part, frameCount, boneName, scale, parentMode, flags, state), x84_system(system) {\n  if (lightId == -1)\n    x88_lightId = kInvalidUniqueId;\n  else\n    x88_lightId = _initializeLight(system, stateMgr, areaId, lightId);\n}\n\nvoid CParticleGenInfoGeneric::AddToRenderer() { g_Renderer->AddParticleGen(*x84_system); }\n\nvoid CParticleGenInfoGeneric::Render() { x84_system->Render(); }\n\nvoid CParticleGenInfoGeneric::Update(float dt, CStateManager& stateMgr) {\n  x84_system->Update(dt);\n\n  if (x88_lightId == kInvalidUniqueId) {\n    return;\n  }\n\n  if (const TCastToPtr<CGameLight> gl = stateMgr.ObjectById(x88_lightId)) {\n    gl->SetLight(x84_system->GetLight());\n  }\n}\n\nvoid CParticleGenInfoGeneric::SetOrientation(const zeus::CTransform& xf, CStateManager& stateMgr) {\n  x84_system->SetOrientation(xf);\n\n  if (x88_lightId == kInvalidUniqueId) {\n    return;\n  }\n\n  if (const TCastToPtr<CGameLight> gl = stateMgr.ObjectById(x88_lightId)) {\n    gl->SetRotation(zeus::CQuaternion(xf.buildMatrix3f()));\n  }\n}\n\nvoid CParticleGenInfoGeneric::SetTranslation(const zeus::CVector3f& trans, CStateManager& stateMgr) {\n  x84_system->SetTranslation(trans);\n\n  if (x88_lightId == kInvalidUniqueId) {\n    return;\n  }\n\n  if (const TCastToPtr<CGameLight> gl = stateMgr.ObjectById(x88_lightId)) {\n    gl->SetTranslation(trans);\n  }\n}\n\nvoid CParticleGenInfoGeneric::SetGlobalOrientation(const zeus::CTransform& xf, CStateManager& stateMgr) {\n  x84_system->SetGlobalOrientation(xf);\n\n  if (x88_lightId == kInvalidUniqueId) {\n    return;\n  }\n\n  if (const TCastToPtr<CGameLight> gl = stateMgr.ObjectById(x88_lightId)) {\n    gl->SetRotation(zeus::CQuaternion(xf.buildMatrix3f()));\n  }\n}\n\nvoid CParticleGenInfoGeneric::SetGlobalTranslation(const zeus::CVector3f& trans, CStateManager& stateMgr) {\n  x84_system->SetGlobalTranslation(trans);\n\n  if (x88_lightId == kInvalidUniqueId) {\n    return;\n  }\n\n  if (const TCastToPtr<CGameLight> gl = stateMgr.ObjectById(x88_lightId)) {\n    gl->SetTranslation(trans);\n  }\n}\n\nvoid CParticleGenInfoGeneric::SetGlobalScale(const zeus::CVector3f& scale) { x84_system->SetGlobalScale(scale); }\n\nvoid CParticleGenInfoGeneric::SetParticleEmission(bool isActive, CStateManager& stateMgr) {\n  x84_system->SetParticleEmission(isActive);\n\n  if (x88_lightId == kInvalidUniqueId) {\n    return;\n  }\n\n  if (const TCastToPtr<CGameLight> gl = stateMgr.ObjectById(x88_lightId)) {\n    gl->SetActive(isActive);\n  }\n}\n\nbool CParticleGenInfoGeneric::IsSystemDeletable() const { return x84_system->IsSystemDeletable(); }\n\nstd::optional<zeus::CAABox> CParticleGenInfoGeneric::GetBounds() const { return x84_system->GetBounds(); }\n\nbool CParticleGenInfoGeneric::HasActiveParticles() const { return x84_system->GetParticleCount() != 0; }\n\nvoid CParticleGenInfoGeneric::DestroyParticles() { x84_system->DestroyParticles(); }\n\nbool CParticleGenInfoGeneric::HasLight() const { return x84_system->SystemHasLight(); }\n\nTUniqueId CParticleGenInfoGeneric::GetLightId() const { return x88_lightId; }\n\nvoid CParticleGenInfoGeneric::DeleteLight(CStateManager& mgr) {\n  if (x88_lightId != kInvalidUniqueId) {\n    mgr.FreeScriptObject(x88_lightId);\n    x88_lightId = kInvalidUniqueId;\n  }\n}\n\nvoid CParticleGenInfoGeneric::SetModulationColor(const zeus::CColor& color) { x84_system->SetModulationColor(color); }\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CParticleGenInfo.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Character/CParticleData.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CParticleGen;\nclass CStateManager;\nstruct SObjectTag;\n\nenum class EParticleGenType { Normal, Auxiliary };\n\nclass CParticleGenInfo {\n  SObjectTag x4_part;\n  float xc_seconds;\n  std::string x10_boneName;\n  float x20_curTime = 0.f;\n  bool x24_active = false;\n  CParticleData::EParentedMode x28_parentMode;\n  s32 x2c_flags;\n  zeus::CVector3f x30_particleScale;\n  float x3c_finishTime = 0.f;\n  bool x40_grabInitialData = false;\n  zeus::CTransform x44_transform;\n  zeus::CVector3f x74_offset;\n  EParticleGenType x80_type;\n\npublic:\n  CParticleGenInfo(const SObjectTag& part, int frameCount, std::string_view boneName, const zeus::CVector3f& scale,\n                   CParticleData::EParentedMode parentMode, int flags, EParticleGenType type);\n\n  virtual ~CParticleGenInfo() = default;\n  virtual void AddToRenderer() = 0;\n  virtual void Render() = 0;\n  virtual void Update(float dt, CStateManager& stateMgr) = 0;\n  virtual void SetOrientation(const zeus::CTransform& xf, CStateManager& stateMgr) = 0;\n  virtual void SetTranslation(const zeus::CVector3f& trans, CStateManager& stateMgr) = 0;\n  virtual void SetGlobalOrientation(const zeus::CTransform& xf, CStateManager& stateMgr) = 0;\n  virtual void SetGlobalTranslation(const zeus::CVector3f& trans, CStateManager& stateMgr) = 0;\n  virtual void SetGlobalScale(const zeus::CVector3f& scale) = 0;\n  virtual void SetParticleEmission(bool isActive, CStateManager& stateMgr) = 0;\n  virtual bool IsSystemDeletable() const = 0;\n  virtual std::optional<zeus::CAABox> GetBounds() const = 0;\n  virtual bool HasActiveParticles() const = 0;\n  virtual void DestroyParticles() = 0;\n  virtual bool HasLight() const = 0;\n  virtual TUniqueId GetLightId() const = 0;\n  virtual void DeleteLight(CStateManager& stateMgr) = 0;\n  virtual void SetModulationColor(const zeus::CColor& color) = 0;\n\n  void SetFlags(s32 flags) { x2c_flags = flags; }\n  s32 GetFlags() const { return x2c_flags; }\n  void SetIsGrabInitialData(bool grabInitialData) { x40_grabInitialData = grabInitialData; }\n  bool GetIsGrabInitialData() const { return x40_grabInitialData; }\n  bool GetIsActive() const { return x24_active; }\n  void SetIsActive(bool isActive) { x24_active = isActive; }\n  void OffsetTime(float dt) { x20_curTime += dt; }\n  const zeus::CVector3f& GetCurOffset() const { return x74_offset; }\n  void SetCurOffset(const zeus::CVector3f& offset) { x74_offset = offset; }\n  const zeus::CTransform& GetCurTransform() const { return x44_transform; }\n  void SetCurTransform(const zeus::CTransform& xf) { x44_transform = xf; }\n  const zeus::CVector3f& GetCurScale() const { return x30_particleScale; }\n  void SetCurScale(const zeus::CVector3f& scale) { x30_particleScale = scale; }\n  void SetInactiveStartTime(float seconds) { xc_seconds = seconds; }\n  float GetInactiveStartTime() const { return xc_seconds; }\n  void MarkFinishTime() { x3c_finishTime = x20_curTime; }\n  float GetFinishTime() const { return x3c_finishTime; }\n  float GetCurrentTime() const { return x20_curTime; }\n  void SetCurrentTime(float time) { x20_curTime = time; }\n  EParticleGenType GetType() const { return x80_type; }\n\n  CParticleData::EParentedMode GetParentedMode() const { return x28_parentMode; }\n  std::string_view GetLocatorName() const { return x10_boneName; }\n};\n\nclass CParticleGenInfoGeneric : public CParticleGenInfo {\n  std::shared_ptr<CParticleGen> x84_system;\n  TUniqueId x88_lightId;\n\npublic:\n  CParticleGenInfoGeneric(const SObjectTag& part, const std::weak_ptr<CParticleGen>& system, int frames,\n                          std::string_view boneName, const zeus::CVector3f& scale,\n                          CParticleData::EParentedMode parentMode, int flags, CStateManager& stateMgr, TAreaId areaId,\n                          int lightId, EParticleGenType state);\n\n  void AddToRenderer() override;\n  void Render() override;\n  void Update(float dt, CStateManager& stateMgr) override;\n  void SetOrientation(const zeus::CTransform& xf, CStateManager& stateMgr) override;\n  void SetTranslation(const zeus::CVector3f& trans, CStateManager& stateMgr) override;\n  void SetGlobalOrientation(const zeus::CTransform& xf, CStateManager& stateMgr) override;\n  void SetGlobalTranslation(const zeus::CVector3f& trans, CStateManager& stateMgr) override;\n  void SetGlobalScale(const zeus::CVector3f& scale) override;\n  void SetParticleEmission(bool isActive, CStateManager& stateMgr) override;\n  bool IsSystemDeletable() const override;\n  std::optional<zeus::CAABox> GetBounds() const override;\n  bool HasActiveParticles() const override;\n  void DestroyParticles() override;\n  bool HasLight() const override;\n  TUniqueId GetLightId() const override;\n  void DeleteLight(CStateManager& mgr) override;\n  void SetModulationColor(const zeus::CColor& color) override;\n  const std::shared_ptr<CParticleGen>& GetParticleSystem() const { return x84_system; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CParticlePOINode.cpp",
    "content": "#include \"Runtime/Character/CParticlePOINode.hpp\"\n\n#include \"Runtime/Character/CAnimSourceReader.hpp\"\n\nnamespace metaforce {\n\nCParticlePOINode::CParticlePOINode() : CPOINode(\"root\", EPOIType::Particle, CCharAnimTime(), -1, false, 1.f, -1, 0) {}\n\nCParticlePOINode::CParticlePOINode(CInputStream& in) : CPOINode(in), x38_data(in) {}\n\nCParticlePOINode CParticlePOINode::CopyNodeMinusStartTime(const CParticlePOINode& node,\n                                                          const CCharAnimTime& startTime) {\n  CParticlePOINode ret = node;\n  ret.x1c_time -= startTime;\n  return ret;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CParticlePOINode.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Character/CParticleData.hpp\"\n#include \"Runtime/Character/CPOINode.hpp\"\n\nnamespace metaforce {\nclass IAnimSourceInfo;\n\nclass CParticlePOINode : public CPOINode {\n  CParticleData x38_data;\n\npublic:\n  explicit CParticlePOINode();\n  explicit CParticlePOINode(CInputStream& in);\n  const CParticleData& GetParticleData() const { return x38_data; }\n\n  static CParticlePOINode CopyNodeMinusStartTime(const CParticlePOINode& node, const CCharAnimTime& startTime);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CPoseAsTransforms.cpp",
    "content": "#include \"Runtime/Character/CPoseAsTransforms.hpp\"\n\n#include \"Runtime/Character/CCharLayoutInfo.hpp\"\n\nnamespace metaforce {\n\nCPoseAsTransforms::CPoseAsTransforms(u8 boneCount)\n: x1_count(boneCount), xd0_transformArr(std::make_unique<zeus::CTransform[]>(boneCount)) {}\n\nbool CPoseAsTransforms::ContainsDataFor(const CSegId& id) const {\n  const std::pair<CSegId, CSegId>& link = x8_links[id];\n  return link.first.IsValid() || link.second.IsValid();\n}\n\nvoid CPoseAsTransforms::Clear() {\n  x8_links.fill({});\n  xd4_lastInserted = 0;\n  x0_nextId = 0;\n}\n\nvoid CPoseAsTransforms::AccumulateScaledTransform(const CSegId& id, zeus::CMatrix3f& rotation, float scale) const {\n  rotation.addScaledMatrix(GetRotation(id), scale);\n}\n\nconst zeus::CTransform& CPoseAsTransforms::GetTransform(const CSegId& id) const {\n  const std::pair<CSegId, CSegId>& link = x8_links[id];\n  assert(link.second.IsValid());\n  return xd0_transformArr[link.second];\n}\n\nconst zeus::CVector3f& CPoseAsTransforms::GetOffset(const CSegId& id) const {\n  const std::pair<CSegId, CSegId>& link = x8_links[id];\n  assert(link.second.IsValid());\n  return xd0_transformArr[link.second].origin;\n}\n\nconst zeus::CMatrix3f& CPoseAsTransforms::GetRotation(const CSegId& id) const {\n  const std::pair<CSegId, CSegId>& link = x8_links[id];\n  assert(link.second.IsValid());\n  return xd0_transformArr[link.second].basis;\n}\n\nvoid CPoseAsTransforms::Insert(const CSegId& id, const zeus::CMatrix3f& rotation, const zeus::CVector3f& offset) {\n  xd0_transformArr[x0_nextId] = zeus::CTransform(rotation, offset);\n\n  std::pair<CSegId, CSegId>& link = x8_links[id];\n  link.first = xd4_lastInserted;\n  link.second = x0_nextId;\n  xd4_lastInserted = id;\n  ++x0_nextId;\n}\n\nCSegId CPoseAsTransforms::GetParent(const CSegId& id) const {\n  const std::pair<CSegId, CSegId>& link = x8_links[id];\n  assert(link.first.IsValid());\n  return link.first;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CPoseAsTransforms.hpp",
    "content": "#pragma once\n\n#include <array>\n#include <memory>\n#include <utility>\n\n#include \"Runtime/Character/CCharLayoutInfo.hpp\"\n#include \"Runtime/Character/CSegId.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n\n#include <zeus/CMatrix3f.hpp>\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CPoseAsTransforms {\n  friend class CAnimData;\n\nprivate:\n  CSegId x0_nextId = 0;\n  CSegId x1_count;\n  std::array<std::pair<CSegId, CSegId>, 100> x8_links;\n  std::unique_ptr<zeus::CTransform[]> xd0_transformArr;\n  CSegId xd4_lastInserted = 0;\n\npublic:\n  explicit CPoseAsTransforms(u8 boneCount);\n\n  void Clear();\n  void AccumulateScaledTransform(const CSegId& id, zeus::CMatrix3f& rotation, float scale) const;\n  void Insert(const CSegId& id, const zeus::CMatrix3f& rotation, const zeus::CVector3f& offset);\n\n  [[nodiscard]] bool ContainsDataFor(const CSegId& id) const;\n  [[nodiscard]] const zeus::CTransform& GetTransform(const CSegId& id) const;\n  [[nodiscard]] const zeus::CVector3f& GetOffset(const CSegId& id) const;\n  [[nodiscard]] const zeus::CMatrix3f& GetRotation(const CSegId& id) const;\n  [[nodiscard]] CSegId GetLastInserted() const { return xd4_lastInserted; }\n  [[nodiscard]] CSegId GetParent(const CSegId& id) const;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CPrimitive.cpp",
    "content": "#include \"Runtime/Character/CPrimitive.hpp\"\n\nnamespace metaforce {\n\nCPrimitive::CPrimitive(CInputStream& in) {\n  x0_animId = in.Get<CAssetId>();\n  x4_animIdx = in.ReadLong();\n  x8_animName = in.Get<std::string>();\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CPrimitive.hpp",
    "content": "#pragma once\n\n#include <string>\n\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n\nnamespace metaforce {\n\nclass CPrimitive {\n  CAssetId x0_animId;\n  u32 x4_animIdx;\n  std::string x8_animName;\n\npublic:\n  explicit CPrimitive(CInputStream& in);\n  CAssetId GetAnimResId() const { return x0_animId; }\n  u32 GetAnimDbIdx() const { return x4_animIdx; }\n  std::string_view GetName() const { return x8_animName; }\n  bool operator<(const CPrimitive& other) const { return x8_animName < other.x8_animName; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CRagDoll.cpp",
    "content": "#include \"Runtime/Character/CRagDoll.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Collision/CCollidableSphere.hpp\"\n#include \"Runtime/Collision/CCollisionInfo.hpp\"\n#include \"Runtime/Collision/CGameCollision.hpp\"\n#include \"Runtime/Collision/CMaterialFilter.hpp\"\n#include \"Runtime/Collision/CMetroidAreaCollider.hpp\"\n\nnamespace metaforce {\n\nvoid CRagDoll::CRagDollLengthConstraint::Update() {\n  zeus::CVector3f delta = x4_p2->x4_curPos - x0_p1->x4_curPos;\n  float magSq = delta.magSquared();\n  float lenSq = x8_length * x8_length;\n  bool doSolve = true;\n  switch (xc_ineqType) {\n  case 1: // Min\n    doSolve = magSq < lenSq;\n    break;\n  case 2: // Max\n    doSolve = magSq > lenSq;\n    break;\n  default:\n    break;\n  }\n  if (!doSolve)\n    return;\n  zeus::CVector3f solveVec = delta * (lenSq / (magSq + lenSq) - 0.5f);\n  x0_p1->x4_curPos -= solveVec;\n  x4_p2->x4_curPos += solveVec;\n}\n\nvoid CRagDoll::CRagDollJointConstraint::Update() {\n  // L_hip, R_shoulder, L_shoulder, L_hip, L_knee, L_ankle\n  zeus::CVector3f P4ToP5 = x10_p5->x4_curPos - xc_p4->x4_curPos; // L_hip->L_knee\n  zeus::CVector3f cross =\n      P4ToP5.cross((x8_p3->x4_curPos - x0_p1->x4_curPos).cross(x4_p2->x4_curPos - x0_p1->x4_curPos));\n  // L_hip->L_knee X (L_hip->L_shoulder X L_hip->R_shoulder)\n  if (cross.canBeNormalized()) {\n    zeus::CVector3f hipUp = cross.cross(P4ToP5).normalized();\n    float dot = (x14_p6->x4_curPos - x10_p5->x4_curPos).dot(hipUp);\n    if (dot > 0.f) {\n      zeus::CVector3f solveVec = 0.5f * dot * hipUp;\n      x14_p6->x4_curPos -= solveVec;\n      x10_p5->x4_curPos += solveVec;\n    }\n  }\n}\n\nvoid CRagDoll::CRagDollPlaneConstraint::Update() {\n  zeus::CVector3f P1ToP2 = (x4_p2->x4_curPos - x0_p1->x4_curPos).normalized();\n  float dot = P1ToP2.dot(xc_p4->x4_curPos - x8_p3->x4_curPos);\n  if (dot < 0.f) {\n    zeus::CVector3f solveVec = 0.5f * dot * P1ToP2;\n    xc_p4->x4_curPos -= solveVec;\n    x10_p5->x4_curPos += solveVec;\n  }\n}\n\nCRagDoll::CRagDoll(float normalGravity, float floatingGravity, float overTime, u32 flags)\n: x44_normalGravity(normalGravity)\n, x48_floatingGravity(floatingGravity)\n, x50_overTimer(overTime)\n, x68_27_continueSmallMovements(bool(flags & 0x1))\n, x68_28_noOverTimer(bool(flags & 0x2))\n, x68_29_noAiCollision(bool(flags & 0x4)) {}\n\nvoid CRagDoll::AccumulateForces(float dt, float waterTop) {\n  float fps = 1.f / dt;\n  x64_angTimer += dt;\n  if (x64_angTimer > 4.f)\n    x64_angTimer -= 4.f;\n  float targetZ = std::sin(zeus::degToRad(90.f) * x64_angTimer) * 0.1f + (waterTop - 0.2f);\n  zeus::CVector3f centerOfVolume;\n  float totalVolume = 0.f;\n  for (auto& particle : x4_particles) {\n    float volume = particle.x10_radius * particle.x10_radius * particle.x10_radius;\n    totalVolume += volume;\n    centerOfVolume += particle.x4_curPos * volume;\n    float fromTargetZ = particle.x4_curPos.z() - targetZ;\n    float verticalAcc = x48_floatingGravity;\n    float termVelCoefficient = 0.f;\n    if (std::fabs(fromTargetZ) < 0.5f) {\n      termVelCoefficient = 0.5f * fromTargetZ / 0.5f + 0.5f;\n      verticalAcc = x48_floatingGravity * -fromTargetZ / 0.5f;\n    } else if (fromTargetZ > 0.f) {\n      verticalAcc = x44_normalGravity;\n      termVelCoefficient = 1.f;\n    }\n    particle.x20_velocity.z() += verticalAcc;\n    zeus::CVector3f vel = (particle.x4_curPos - particle.x14_prevPos) * fps;\n    float velMag = vel.magnitude();\n    if (velMag > FLT_EPSILON) {\n      particle.x20_velocity -=\n          vel * (1.f / velMag) *\n          ((velMag * velMag * 0.75f * (1.2f * termVelCoefficient + 1000.f * (1.f - termVelCoefficient))) /\n           (8000.f * particle.x10_radius));\n    }\n  }\n  zeus::CVector3f averageTorque;\n  centerOfVolume = centerOfVolume / totalVolume;\n  for (const auto& particle : x4_particles) {\n    float volume = particle.x10_radius * particle.x10_radius * particle.x10_radius;\n    averageTorque += (particle.x4_curPos - centerOfVolume).cross(particle.x4_curPos - particle.x14_prevPos) * volume;\n  }\n  averageTorque = averageTorque * (fps / totalVolume);\n  if (averageTorque.canBeNormalized())\n    for (auto& particle : x4_particles)\n      particle.x20_velocity -= averageTorque.cross(particle.x4_curPos - centerOfVolume) * 25.f;\n}\n\nvoid CRagDoll::AddParticle(CSegId id, const zeus::CVector3f& prevPos, const zeus::CVector3f& curPos, float radius) {\n  x4_particles.emplace_back(id, curPos, radius, prevPos);\n}\n\nvoid CRagDoll::AddLengthConstraint(int i1, int i2) {\n  x14_lengthConstraints.emplace_back(&x4_particles[i1], &x4_particles[i2],\n                                     (x4_particles[i1].x4_curPos - x4_particles[i2].x4_curPos).magnitude(), 0);\n}\n\nvoid CRagDoll::AddMaxLengthConstraint(int i1, int i2, float length) {\n  x14_lengthConstraints.emplace_back(&x4_particles[i1], &x4_particles[i2], length, 2);\n}\n\nvoid CRagDoll::AddMinLengthConstraint(int i1, int i2, float length) {\n  x14_lengthConstraints.emplace_back(&x4_particles[i1], &x4_particles[i2], length, 1);\n}\n\nvoid CRagDoll::AddJointConstraint(int i1, int i2, int i3, int i4, int i5, int i6) {\n  x24_jointConstraints.emplace_back(&x4_particles[i1], &x4_particles[i2], &x4_particles[i3], &x4_particles[i4],\n                                    &x4_particles[i5], &x4_particles[i6]);\n}\n\nzeus::CQuaternion CRagDoll::BoneAlign(CHierarchyPoseBuilder& pb, const CCharLayoutInfo& charInfo, int i1, int i2,\n                                      const zeus::CQuaternion& q) {\n  zeus::CVector3f fromParent = charInfo.GetFromParentUnrotated(x4_particles[i2].x0_id);\n  zeus::CVector3f delta = x4_particles[i2].x4_curPos - x4_particles[i1].x4_curPos;\n  delta = q.inverse().transform(delta);\n  zeus::CQuaternion ret = zeus::CQuaternion::shortestRotationArc(fromParent, delta);\n  pb.GetTreeMap()[x4_particles[i1].x0_id].x4_rotation = ret;\n  return ret;\n}\n\nzeus::CAABox CRagDoll::CalculateRenderBounds() const {\n  zeus::CAABox aabb;\n  for (const auto& particle : x4_particles) {\n    aabb.accumulateBounds(\n        zeus::CAABox(particle.x4_curPos - particle.x10_radius, particle.x4_curPos + particle.x10_radius));\n  }\n  return aabb;\n}\n\nvoid CRagDoll::CheckStatic(float dt) {\n  x4c_impactCount = 0;\n  x54_impactVel = 0.f;\n  float halfDt = 0.5f * dt;\n  float halfDeltaUnitSq = halfDt * halfDt;\n  x58_averageVel = zeus::skZero3f;\n  bool movingSlowly = true;\n  for (auto& particle : x4_particles) {\n    zeus::CVector3f delta = particle.x4_curPos - particle.x14_prevPos;\n    x58_averageVel += delta;\n    if (delta.magSquared() > halfDeltaUnitSq)\n      movingSlowly = false;\n    if (particle.x3c_24_impactPending) {\n      x4c_impactCount += 1;\n      x54_impactVel = std::max(particle.x38_impactFrameVel, x54_impactVel);\n    }\n  }\n  if (!x4_particles.empty())\n    x58_averageVel = x58_averageVel * (1.f / (dt * x4_particles.size()));\n  x54_impactVel /= dt;\n  if (!x68_28_noOverTimer) {\n    x50_overTimer -= dt;\n    if (x50_overTimer <= 0.f)\n      x68_25_over = true;\n  }\n  if (movingSlowly && x68_24_prevMovingSlowly)\n    x68_25_over = true;\n  x68_24_prevMovingSlowly = movingSlowly;\n}\n\nvoid CRagDoll::ClearForces() {\n  for (auto& particle : x4_particles)\n    particle.x20_velocity = zeus::skZero3f;\n}\n\nvoid CRagDoll::SatisfyConstraints(CStateManager& mgr) {\n  for (auto& length : x14_lengthConstraints)\n    length.Update();\n  for (auto& joint : x24_jointConstraints)\n    joint.Update();\n  for (auto& plane : x34_planeConstraints)\n    plane.Update();\n  if (SatisfyWorldConstraints(mgr, 1))\n    SatisfyWorldConstraints(mgr, 2);\n}\n\nbool CRagDoll::SatisfyWorldConstraints(CStateManager& mgr, int pass) {\n  zeus::CAABox aabb;\n  for (const auto& particle : x4_particles) {\n    if (pass == 1 || particle.x3c_24_impactPending) {\n      aabb.accumulateBounds(particle.x14_prevPos - particle.x10_radius);\n      aabb.accumulateBounds(particle.x14_prevPos + particle.x10_radius);\n      aabb.accumulateBounds(particle.x4_curPos - particle.x10_radius);\n      aabb.accumulateBounds(particle.x4_curPos + particle.x10_radius);\n    }\n  }\n\n  CAreaCollisionCache ccache(aabb);\n  CGameCollision::BuildAreaCollisionCache(mgr, ccache);\n  bool needs2ndPass = false;\n\n  TUniqueId bestId = kInvalidUniqueId;\n  CMaterialList include;\n  if (x68_29_noAiCollision)\n    include = CMaterialList(EMaterialTypes::Solid);\n  else\n    include = CMaterialList(EMaterialTypes::Solid, EMaterialTypes::AIBlock);\n\n  CMaterialList exclude;\n  if (x68_29_noAiCollision)\n    exclude = CMaterialList(EMaterialTypes::Character, EMaterialTypes::Player, EMaterialTypes::AIBlock,\n                            EMaterialTypes::Occluder);\n  else\n    exclude = CMaterialList(EMaterialTypes::Character, EMaterialTypes::Player);\n\n  EntityList nearList;\n  CMaterialFilter filter = CMaterialFilter::MakeIncludeExclude(include, exclude);\n  mgr.BuildNearList(nearList, aabb, filter, nullptr);\n\n  for (auto& particle : x4_particles) {\n    if (pass == 1 || particle.x3c_24_impactPending) {\n      zeus::CVector3f delta = particle.x4_curPos - particle.x14_prevPos;\n      float deltaMag = delta.magnitude();\n      if (deltaMag > 0.0001f) {\n        delta = delta * (1.f / deltaMag);\n        double d = deltaMag;\n        CCollidableSphere sphere(zeus::CSphere(particle.x14_prevPos, particle.x10_radius), include);\n        CCollisionInfo info;\n        CGameCollision::DetectCollision_Cached_Moving(mgr, ccache, sphere, {}, filter, nearList, delta, bestId, info,\n                                                      d);\n        if (info.IsValid()) {\n          needs2ndPass = true;\n          switch (pass) {\n          case 1: {\n            particle.x3c_24_impactPending = true;\n            float dot = delta.dot(info.GetNormalLeft());\n            particle.x2c_impactResponseDelta = -0.125f * dot * deltaMag * info.GetNormalLeft();\n            particle.x38_impactFrameVel = -dot * deltaMag;\n            particle.x4_curPos += (0.0001f - (deltaMag - float(d)) * dot) * info.GetNormalLeft();\n            break;\n          }\n          case 2:\n            particle.x4_curPos = float(d - 0.0001) * delta + particle.x14_prevPos;\n            break;\n          default:\n            break;\n          }\n        }\n      } else if (!x68_27_continueSmallMovements) {\n        particle.x4_curPos = particle.x14_prevPos;\n      }\n    }\n  }\n\n  return needs2ndPass;\n}\n\nvoid CRagDoll::SatisfyWorldConstraintsOnConstruction(CStateManager& mgr) {\n  for (auto& particle : x4_particles)\n    particle.x3c_24_impactPending = true;\n  SatisfyWorldConstraints(mgr, 2);\n  for (auto& particle : x4_particles)\n    particle.x14_prevPos = particle.x4_curPos;\n}\n\nvoid CRagDoll::Verlet(float dt) {\n  for (auto& particle : x4_particles) {\n    zeus::CVector3f oldPos = particle.x4_curPos;\n    particle.x4_curPos += (particle.x4_curPos - particle.x14_prevPos) * (particle.x3c_24_impactPending ? 0.9f : 1.f);\n    particle.x4_curPos += particle.x20_velocity * (dt * dt);\n    particle.x4_curPos += particle.x2c_impactResponseDelta;\n    particle.x14_prevPos = oldPos;\n    zeus::CVector3f deltaPos = particle.x4_curPos - particle.x14_prevPos;\n    if (deltaPos.magSquared() > 4.f)\n      particle.x4_curPos = deltaPos.normalized() * 2.f + particle.x14_prevPos;\n    particle.x3c_24_impactPending = false;\n    particle.x2c_impactResponseDelta = zeus::skZero3f;\n  }\n}\n\nvoid CRagDoll::PreRender(const zeus::CVector3f& v, CModelData& mData) {\n  // Empty\n}\n\nvoid CRagDoll::Update(CStateManager& mgr, float dt, float waterTop) {\n  if (!x68_25_over || x68_27_continueSmallMovements) {\n    AccumulateForces(dt, waterTop);\n    Verlet(dt);\n    SatisfyConstraints(mgr);\n    ClearForces();\n    CheckStatic(dt);\n  }\n}\n\nvoid CRagDoll::Prime(CStateManager& mgr, const zeus::CTransform& xf, CModelData& mData) {\n  zeus::CVector3f scale = mData.GetScale();\n  CAnimData* aData = mData.GetAnimationData();\n  aData->BuildPose();\n  for (auto& particle : x4_particles) {\n    if (particle.x0_id.IsValid()) {\n      particle.x4_curPos = xf * (aData->GetPose().GetOffset(particle.x0_id) * scale);\n    }\n  }\n  SatisfyWorldConstraints(mgr, 2);\n  for (auto& particle : x4_particles) {\n    particle.x3c_24_impactPending = false;\n  }\n  x68_26_primed = true;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CRagDoll.hpp",
    "content": "#pragma once\n\n#include <vector>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Character/CSegId.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CQuaternion.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CCharLayoutInfo;\nclass CHierarchyPoseBuilder;\nclass CModelData;\nclass CStateManager;\n\nclass CRagDoll {\nprotected:\n  class CRagDollParticle {\n    friend class CRagDoll;\n    CSegId x0_id;\n    zeus::CVector3f x4_curPos;\n    float x10_radius;\n    zeus::CVector3f x14_prevPos;\n    zeus::CVector3f x20_velocity;\n    zeus::CVector3f x2c_impactResponseDelta;\n    float x38_impactFrameVel = 0.f;\n    bool x3c_24_impactPending : 1 = false;\n    bool x3c_25_ : 1 = false;\n\n  public:\n    CRagDollParticle(CSegId id, const zeus::CVector3f& curPos, float radius, const zeus::CVector3f& prevPos)\n    : x0_id(id), x4_curPos(curPos), x10_radius(radius), x14_prevPos(prevPos) {}\n    CSegId GetBone() const { return x0_id; }\n    const zeus::CVector3f& GetPosition() const { return x4_curPos; }\n    zeus::CVector3f& Position() { return x4_curPos; }\n    const zeus::CVector3f& GetVelocity() const { return x20_velocity; }\n    zeus::CVector3f& Velocity() { return x20_velocity; }\n    float GetRadius() const { return x10_radius; }\n  };\n  class CRagDollLengthConstraint {\n    friend class CRagDoll;\n    CRagDollParticle* x0_p1;\n    CRagDollParticle* x4_p2;\n    float x8_length;\n    int xc_ineqType;\n\n  public:\n    CRagDollLengthConstraint(CRagDollParticle* p1, CRagDollParticle* p2, float f1, int i1)\n    : x0_p1(p1), x4_p2(p2), x8_length(f1), xc_ineqType(i1) {}\n    void Update();\n    float GetLength() const { return x8_length; }\n  };\n  class CRagDollJointConstraint {\n    friend class CRagDoll;\n    CRagDollParticle* x0_p1;  // Shoulder plane 0\n    CRagDollParticle* x4_p2;  // Shoulder plane 1\n    CRagDollParticle* x8_p3;  // Shoulder plane 2\n    CRagDollParticle* xc_p4;  // Shoulder\n    CRagDollParticle* x10_p5; // Elbow\n    CRagDollParticle* x14_p6; // Wrist\n  public:\n    CRagDollJointConstraint(CRagDollParticle* p1, CRagDollParticle* p2, CRagDollParticle* p3, CRagDollParticle* p4,\n                            CRagDollParticle* p5, CRagDollParticle* p6)\n    : x0_p1(p1), x4_p2(p2), x8_p3(p3), xc_p4(p4), x10_p5(p5), x14_p6(p6) {}\n    void Update();\n  };\n  class CRagDollPlaneConstraint {\n    friend class CRagDoll;\n    CRagDollParticle* x0_p1;\n    CRagDollParticle* x4_p2;\n    CRagDollParticle* x8_p3;\n    CRagDollParticle* xc_p4;\n    CRagDollParticle* x10_p5;\n\n  public:\n    CRagDollPlaneConstraint(CRagDollParticle* p1, CRagDollParticle* p2, CRagDollParticle* p3, CRagDollParticle* p4,\n                            CRagDollParticle* p5)\n    : x0_p1(p1), x4_p2(p2), x8_p3(p3), xc_p4(p4), x10_p5(p5) {}\n    void Update();\n  };\n  std::vector<CRagDollParticle> x4_particles;\n  std::vector<CRagDollLengthConstraint> x14_lengthConstraints;\n  std::vector<CRagDollJointConstraint> x24_jointConstraints;\n  std::vector<CRagDollPlaneConstraint> x34_planeConstraints;\n  float x44_normalGravity;\n  float x48_floatingGravity;\n  u32 x4c_impactCount = 0;\n  float x50_overTimer;\n  float x54_impactVel = 0.f;\n  zeus::CVector3f x58_averageVel;\n  float x64_angTimer = 0.f;\n  bool x68_24_prevMovingSlowly : 1 = false;\n  bool x68_25_over : 1 = false;\n  bool x68_26_primed : 1 = false;\n  bool x68_27_continueSmallMovements : 1;\n  bool x68_28_noOverTimer : 1;\n  bool x68_29_noAiCollision : 1;\n  void AccumulateForces(float dt, float waterTop);\n  void SetNumParticles(int num) { x4_particles.reserve(num); }\n  void AddParticle(CSegId id, const zeus::CVector3f& prevPos, const zeus::CVector3f& curPos, float radius);\n  void SetNumLengthConstraints(int num) { x14_lengthConstraints.reserve(num); }\n  void AddLengthConstraint(int i1, int i2);\n  void AddMaxLengthConstraint(int i1, int i2, float length);\n  void AddMinLengthConstraint(int i1, int i2, float length);\n  void SetNumJointConstraints(int num) { x24_jointConstraints.reserve(num); }\n  void AddJointConstraint(int i1, int i2, int i3, int i4, int i5, int i6);\n  zeus::CQuaternion BoneAlign(CHierarchyPoseBuilder& pb, const CCharLayoutInfo& charInfo, int i1, int i2,\n                              const zeus::CQuaternion& q);\n  void CheckStatic(float dt);\n  void ClearForces();\n  void SatisfyConstraints(CStateManager& mgr);\n  bool SatisfyWorldConstraints(CStateManager& mgr, int pass);\n  void SatisfyWorldConstraintsOnConstruction(CStateManager& mgr);\n  void Verlet(float dt);\n\npublic:\n  virtual ~CRagDoll() = default;\n  CRagDoll(float normalGravity, float floatingGravity, float overTime, u32 flags);\n\n  virtual void PreRender(const zeus::CVector3f& v, CModelData& mData);\n  virtual void Update(CStateManager& mgr, float dt, float waterTop);\n  virtual void Prime(CStateManager& mgr, const zeus::CTransform& xf, CModelData& mData);\n\n  zeus::CAABox CalculateRenderBounds() const;\n  bool IsPrimed() const { return x68_26_primed; }\n  bool WillContinueSmallMovements() const { return x68_27_continueSmallMovements; }\n  bool IsOver() const { return x68_25_over; }\n  void SetNoOverTimer(bool b) { x68_28_noOverTimer = b; }\n  void SetContinueSmallMovements(bool b) { x68_27_continueSmallMovements = b; }\n  u32 GetImpactCount() const { return x4c_impactCount; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CSegId.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n\nnamespace metaforce {\n\nclass CSegId {\n  u8 x0_segId = 0xFF;\n\npublic:\n  constexpr CSegId() noexcept = default;\n  constexpr CSegId(u8 id) noexcept : x0_segId(id) {}\n  explicit CSegId(CInputStream& in) : x0_segId(in.ReadLong()) {}\n  constexpr CSegId& operator++() noexcept {\n    ++x0_segId;\n    return *this;\n  }\n  constexpr CSegId& operator--() noexcept {\n    --x0_segId;\n    return *this;\n  }\n  constexpr operator u8() const noexcept { return x0_segId; }\n\n  constexpr bool IsValid() const noexcept { return !IsInvalid(); }\n  constexpr bool IsInvalid() const noexcept { return x0_segId == 0xFF; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CSegIdList.cpp",
    "content": "#include \"Runtime/Character/CSegIdList.hpp\"\n\nnamespace metaforce {\n\nCSegIdList::CSegIdList(CInputStream& in) {\n  u32 count = in.ReadLong();\n  x0_list.reserve(count);\n  for (u32 i = 0; i < count; ++i)\n    x0_list.emplace_back(in);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CSegIdList.hpp",
    "content": "#pragma once\n\n#include <vector>\n\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/Character/CSegId.hpp\"\n\nnamespace metaforce {\n\nclass CSegIdList {\n  std::vector<CSegId> x0_list;\n\npublic:\n  explicit CSegIdList(CInputStream& in);\n  const std::vector<CSegId>& GetList() const { return x0_list; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CSegStatementSet.cpp",
    "content": "#include \"Runtime/Character/CSegStatementSet.hpp\"\n\n#include \"Runtime/Character/CCharLayoutInfo.hpp\"\n#include \"Runtime/Character/CSegIdList.hpp\"\n\nnamespace metaforce {\n\nvoid CSegStatementSet::Add(const CSegIdList& list, const CCharLayoutInfo& layout, const CSegStatementSet& other,\n                           float weight) {\n  for (const CSegId& id : list.GetList()) {\n    x4_segData[id].x0_rotation *=\n        zeus::CQuaternion::slerp(zeus::CQuaternion(), other.x4_segData[id].x0_rotation, weight);\n    if (other.x4_segData[id].x1c_hasOffset && x4_segData[id].x1c_hasOffset) {\n      zeus::CVector3f off = other.x4_segData[id].x10_offset - layout.GetFromParentUnrotated(id);\n      x4_segData[id].x10_offset += off * weight;\n    }\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CSegStatementSet.hpp",
    "content": "#pragma once\n\n#include <array>\n\n#include \"Runtime/Character/CAnimPerSegmentData.hpp\"\n#include \"Runtime/Character/CSegId.hpp\"\n\nnamespace metaforce {\nclass CCharLayoutInfo;\nclass CSegIdList;\n\nclass CSegStatementSet {\nprivate:\n  /* Used to be a pointer to arbitrary subclass-provided storage,\n   * now it's a self-stored array */\n  std::array<CAnimPerSegmentData, 100> x4_segData;\n\npublic:\n  void Add(const CSegIdList& list, const CCharLayoutInfo& layout, const CSegStatementSet& other, float weight);\n\n  CAnimPerSegmentData& operator[](const CSegId& idx) { return x4_segData[idx]; }\n  const CAnimPerSegmentData& operator[](const CSegId& idx) const { return x4_segData[idx]; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CSequenceHelper.cpp",
    "content": "#include \"Runtime/Character/CSequenceHelper.hpp\"\n\n#include <array>\n\n#include \"Runtime/Character/CAnimSysContext.hpp\"\n#include \"Runtime/Character/CBoolPOINode.hpp\"\n#include \"Runtime/Character/CInt32POINode.hpp\"\n#include \"Runtime/Character/CParticlePOINode.hpp\"\n#include \"Runtime/Character/CSoundPOINode.hpp\"\n#include \"Runtime/Character/CTreeUtils.hpp\"\n#include \"Runtime/Character/IMetaAnim.hpp\"\n\nnamespace metaforce {\n\nCSequenceFundamentals::CSequenceFundamentals(const CSteadyStateAnimInfo& ssInfo, std::vector<CBoolPOINode> boolNodes,\n                                             std::vector<CInt32POINode> int32Nodes,\n                                             std::vector<CParticlePOINode> particleNodes,\n                                             std::vector<CSoundPOINode> soundNodes)\n: x0_ssInfo(ssInfo)\n, x18_boolNodes(std::move(boolNodes))\n, x28_int32Nodes(std::move(int32Nodes))\n, x38_particleNodes(std::move(particleNodes))\n, x48_soundNodes(std::move(soundNodes)) {}\n\nCSequenceHelper::CSequenceHelper(const std::shared_ptr<CAnimTreeNode>& a, const std::shared_ptr<CAnimTreeNode>& b,\n                                 CAnimSysContext animCtx)\n: x0_animCtx(std::move(animCtx)) {\n  x10_treeNodes.reserve(2);\n  x10_treeNodes.push_back(a);\n  x10_treeNodes.push_back(b);\n}\n\nCSequenceHelper::CSequenceHelper(const std::vector<std::shared_ptr<IMetaAnim>>& nodes, CAnimSysContext animCtx)\n: x0_animCtx(std::move(animCtx)) {\n  x10_treeNodes.reserve(nodes.size());\n  for (const std::shared_ptr<IMetaAnim>& meta : nodes)\n    x10_treeNodes.push_back(meta->GetAnimationTree(x0_animCtx, CMetaAnimTreeBuildOrders::NoSpecialOrders()));\n}\n\nCSequenceFundamentals CSequenceHelper::ComputeSequenceFundamentals() {\n  CCharAnimTime duration;\n  zeus::CVector3f offset;\n  std::vector<CBoolPOINode> boolNodes;\n  std::vector<CInt32POINode> int32Nodes;\n  std::vector<CParticlePOINode> particleNodes;\n  std::vector<CSoundPOINode> soundNodes;\n  if (x10_treeNodes.size() > 0) {\n    std::shared_ptr<CAnimTreeNode> node = CAnimTreeNode::Cast(x10_treeNodes[0]->Clone());\n    for (size_t i = 0; i < x10_treeNodes.size(); ++i) {\n      std::array<CBoolPOINode, 64> boolNodeArr;\n      const size_t numBools =\n          node->GetBoolPOIList(CCharAnimTime::Infinity(), boolNodeArr.data(), boolNodeArr.size(), 0, 0);\n      boolNodes.reserve(boolNodes.size() + numBools);\n      for (size_t j = 0; j < numBools; ++j) {\n        CBoolPOINode& n = boolNodeArr[j];\n        n.SetTime(n.GetTime() + duration);\n        boolNodes.push_back(n);\n      }\n\n      std::array<CInt32POINode, 64> int32NodeArr;\n      const size_t numInt32s =\n          node->GetInt32POIList(CCharAnimTime::Infinity(), int32NodeArr.data(), int32NodeArr.size(), 0, 0);\n      int32Nodes.reserve(int32Nodes.size() + numInt32s);\n      for (size_t j = 0; j < numInt32s; ++j) {\n        CInt32POINode& n = int32NodeArr[j];\n        n.SetTime(n.GetTime() + duration);\n        int32Nodes.push_back(n);\n      }\n\n      std::array<CParticlePOINode, 64> particleNodeArr;\n      const size_t numParticles =\n          node->GetParticlePOIList(CCharAnimTime::Infinity(), particleNodeArr.data(), particleNodeArr.size(), 0, 0);\n      particleNodes.reserve(particleNodes.size() + numParticles);\n      for (size_t j = 0; j < numParticles; ++j) {\n        CParticlePOINode& n = particleNodeArr[j];\n        n.SetTime(n.GetTime() + duration);\n        particleNodes.push_back(n);\n      }\n\n      std::array<CSoundPOINode, 64> soundNodeArr;\n      const size_t numSounds =\n          node->GetSoundPOIList(CCharAnimTime::Infinity(), soundNodeArr.data(), soundNodeArr.size(), 0, 0);\n      soundNodes.reserve(soundNodes.size() + numSounds);\n      for (size_t j = 0; j < numSounds; ++j) {\n        CSoundPOINode& n = soundNodeArr[j];\n        n.SetTime(n.GetTime() + duration);\n        soundNodes.push_back(n);\n      }\n\n      duration += node->VGetTimeRemaining();\n\n      CCharAnimTime remTime = node->VGetTimeRemaining();\n      while (!remTime.EqualsZero() && !remTime.EpsilonZero()) {\n        SAdvancementResults res = node->VAdvanceView(remTime);\n        auto simp = node->Simplified();\n        if (simp)\n          node = CAnimTreeNode::Cast(std::move(*simp));\n        // CCharAnimTime prevRemTime = remTime;\n        remTime = res.x0_remTime;\n        /* This was originally accumulating uninitialized register values (stack variable misuse?) */\n        offset += res.x8_deltas.x0_posDelta;\n      }\n\n      if (i < x10_treeNodes.size() - 1) {\n        node = CTreeUtils::GetTransitionTree(node, CAnimTreeNode::Cast(x10_treeNodes[i + 1]->Clone()), x0_animCtx);\n      }\n    }\n  }\n\n  return {{false, duration, offset}, boolNodes, int32Nodes, particleNodes, soundNodes};\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CSequenceHelper.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <vector>\n\n#include \"Runtime/Character/CAnimSysContext.hpp\"\n#include \"Runtime/Character/CAnimTreeNode.hpp\"\n#include \"Runtime/Character/CBoolPOINode.hpp\"\n#include \"Runtime/Character/CInt32POINode.hpp\"\n#include \"Runtime/Character/CParticlePOINode.hpp\"\n#include \"Runtime/Character/CSoundPOINode.hpp\"\n#include \"Runtime/Character/CTransitionDatabaseGame.hpp\"\n\nnamespace metaforce {\nclass IMetaAnim;\n\nclass CSequenceFundamentals {\n  CSteadyStateAnimInfo x0_ssInfo;\n  std::vector<CBoolPOINode> x18_boolNodes;\n  std::vector<CInt32POINode> x28_int32Nodes;\n  std::vector<CParticlePOINode> x38_particleNodes;\n  std::vector<CSoundPOINode> x48_soundNodes;\n\npublic:\n  CSequenceFundamentals(const CSteadyStateAnimInfo& ssInfo, std::vector<CBoolPOINode> boolNodes,\n                        std::vector<CInt32POINode> int32Nodes, std::vector<CParticlePOINode> particleNodes,\n                        std::vector<CSoundPOINode> soundNodes);\n\n  const CSteadyStateAnimInfo& GetSteadyStateAnimInfo() const { return x0_ssInfo; }\n  const std::vector<CBoolPOINode>& GetBoolPointsOfInterest() const { return x18_boolNodes; }\n  const std::vector<CInt32POINode>& GetInt32PointsOfInterest() const { return x28_int32Nodes; }\n  const std::vector<CParticlePOINode>& GetParticlePointsOfInterest() const { return x38_particleNodes; }\n  const std::vector<CSoundPOINode>& GetSoundPointsOfInterest() const { return x48_soundNodes; }\n};\n\nclass CSequenceHelper {\n  CAnimSysContext x0_animCtx;\n  std::vector<std::shared_ptr<CAnimTreeNode>> x10_treeNodes;\n  std::vector<bool> x20_;\n\npublic:\n  CSequenceHelper(const std::shared_ptr<CAnimTreeNode>& a, const std::shared_ptr<CAnimTreeNode>& b,\n                  CAnimSysContext animCtx);\n  CSequenceHelper(const std::vector<std::shared_ptr<IMetaAnim>>& nodes, CAnimSysContext animCtx);\n  CSequenceFundamentals ComputeSequenceFundamentals();\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CSkinRules.cpp",
    "content": "#include \"Runtime/Character/CSkinRules.hpp\"\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/Character/CPoseAsTransforms.hpp\"\n#include \"Runtime/Graphics/CModel.hpp\"\n\nnamespace metaforce {\n\nstatic u32 ReadCount(CInputStream& in) {\n  s32 result = in.ReadLong();\n  if (result == -1) {\n    return in.ReadLong();\n  }\n  u8 junk[784];\n  u32 iVar2 = 0;\n  for (u32 i = 0; i < (result * 3); i += iVar2) {\n    iVar2 = ((result * 3) - i);\n    iVar2 = 192 < iVar2 ? 192 : iVar2;\n    in.Get(junk, iVar2 * 4);\n  }\n  return result;\n}\n\nCSkinRules::CSkinRules(CInputStream& in) {\n  u32 weightCount = in.ReadLong();\n  x0_bones.reserve(weightCount);\n  for (int i = 0; i < weightCount; ++i) {\n    x0_bones.emplace_back(in);\n  }\n  x10_vertexCount = ReadCount(in);\n  x14_normalCount = ReadCount(in);\n}\n\nvoid CSkinRules::BuildAccumulatedTransforms(const CPoseAsTransforms& pose, const CCharLayoutInfo& info) {\n  std::array<zeus::CVector3f, 100> points;\n  CSegId segId = pose.GetLastInserted();\n  while (segId != 0) {\n    zeus::CVector3f origin;\n    if (segId != 3) { // root ID\n      origin = info.GetFromRootUnrotated(segId);\n    }\n    const auto rotatedOrigin = pose.GetRotation(segId) * origin;\n    points[segId] = pose.GetOffset(segId) - rotatedOrigin;\n    segId = pose.GetParent(segId);\n  }\n  for (auto& bone : x0_bones) {\n    bone.BuildAccumulatedTransform(pose, points.data());\n  }\n}\n\nvoid CSkinRules::BuildPoints(TConstVectorRef positions, TVectorRef out) {\n  size_t offset = 0;\n  for (auto& bone : x0_bones) {\n    u32 vertexCount = bone.GetVertexCount();\n    bone.BuildPoints(positions.data() + offset, out, vertexCount);\n    offset += vertexCount;\n  }\n}\n\nvoid CSkinRules::BuildNormals(TConstVectorRef normals, TVectorRef out) {\n  size_t offset = 0;\n  for (auto& bone : x0_bones) {\n    u32 vertexCount = bone.GetVertexCount();\n    bone.BuildNormals(normals.data() + offset, out, vertexCount);\n    offset += vertexCount;\n  }\n}\n\nCFactoryFnReturn FSkinRulesFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& params,\n                                   CObjectReference* selfRef) {\n  return TToken<CSkinRules>::GetIObjObjectFor(std::make_unique<CSkinRules>(in));\n}\n\nstatic inline auto StreamInSkinWeighting(CInputStream& in) {\n  rstl::reserved_vector<SSkinWeighting, 3> weights;\n  u32 weightCount = in.ReadLong();\n  for (int i = 0; i < std::min(3u, weightCount); ++i) {\n    weights.emplace_back(in);\n  }\n  for (int i = 3; i < weightCount; ++i) {\n    SSkinWeighting{in};\n  }\n  return weights;\n}\n\nCVirtualBone::CVirtualBone(CInputStream& in) : x0_weights(StreamInSkinWeighting(in)), x1c_vertexCount(in.ReadLong()) {}\n\nvoid CVirtualBone::BuildPoints(const aurora::Vec3<float>* in, TVectorRef out, u32 count) const {\n  for (u32 i = 0; i < count; ++i) {\n    const auto& vec = in[i];\n    zeus::CVector3f zout = x20_xf * zeus::CVector3f{vec.x, vec.y, vec.z};\n    out->emplace_back(zout.x(), zout.y(), zout.z());\n  }\n}\n\nvoid CVirtualBone::BuildNormals(const aurora::Vec3<float>* in, TVectorRef out, u32 count) const {\n  for (u32 i = 0; i < count; ++i) {\n    const auto& vec = in[i];\n    zeus::CVector3f zout = x50_rotation * zeus::CVector3f{vec.x, vec.y, vec.z};\n    out->emplace_back(zout.x(), zout.y(), zout.z());\n  }\n}\n\nvoid CVirtualBone::BuildAccumulatedTransform(const CPoseAsTransforms& pose, const zeus::CVector3f* points) {\n  BuildFinalPosMatrix(pose, points);\n  x50_rotation = pose.GetRotation(x0_weights[0].x0_id);\n}\n\nstatic inline zeus::CMatrix3f WeightedMatrix(const zeus::CMatrix3f& m1, float w1, const zeus::CMatrix3f& m2, float w2) {\n  return {\n      m1[0] * w1 + m2[0] * w2,\n      m1[1] * w1 + m2[1] * w2,\n      m1[2] * w1 + m2[2] * w2,\n  };\n}\n\nstatic inline zeus::CVector3f WeightedVector(const zeus::CVector3f& v1, float w1, const zeus::CVector3f& v2, float w2) {\n  return v1 * w1 + v2 * w2;\n}\n\nvoid CVirtualBone::BuildFinalPosMatrix(const CPoseAsTransforms& pose, const zeus::CVector3f* points) {\n  if (x0_weights.size() == 1) {\n    const auto id = x0_weights[0].x0_id;\n    x20_xf = {pose.GetRotation(id), points[id]};\n  } else if (x0_weights.size() == 2) {\n    const auto w0 = x0_weights[0];\n    const auto w1 = x0_weights[1];\n    x20_xf = {\n        WeightedMatrix(pose.GetRotation(w0.x0_id), w0.x4_weight, pose.GetRotation(w1.x0_id), w1.x4_weight),\n        WeightedVector(points[w0.x0_id], w0.x4_weight, points[w1.x0_id], w1.x4_weight),\n    };\n  } else if (x0_weights.size() == 3) {\n    const auto w0 = x0_weights[0];\n    const auto w1 = x0_weights[1];\n    const auto w2 = x0_weights[2];\n    auto rot = WeightedMatrix(pose.GetRotation(w0.x0_id), w0.x4_weight, pose.GetRotation(w1.x0_id), w1.x4_weight);\n    auto pos = WeightedVector(points[w0.x0_id], w0.x4_weight, points[w1.x0_id], w1.x4_weight);\n    pose.AccumulateScaledTransform(w2.x0_id, rot, w2.x4_weight);\n    x20_xf = {rot, pos + points[w2.x0_id] * w2.x4_weight};\n  } else {\n    x20_xf = {};\n  }\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CSkinRules.hpp",
    "content": "#pragma once\n\n#include <vector>\n\n#include \"Runtime/CFactoryMgr.hpp\"\n#include \"Runtime/Character/CSegId.hpp\"\n#include \"Runtime/Graphics/CCubeModel.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CCharLayoutInfo;\nclass CPoseAsTransforms;\nclass CModel;\n\nstruct SSkinWeighting {\n  CSegId x0_id;\n  float x4_weight;\n  explicit SSkinWeighting(CInputStream& in) : x0_id(in), x4_weight(in.ReadFloat()) {}\n};\n\nclass CVirtualBone {\n  friend class CSkinnedModel;\n\n  rstl::reserved_vector<SSkinWeighting, 3> x0_weights;\n  u32 x1c_vertexCount;\n  zeus::CTransform x20_xf;\n  zeus::CMatrix3f x50_rotation;\n\npublic:\n  explicit CVirtualBone(CInputStream& in);\n\n  void BuildPoints(const aurora::Vec3<float>* in, TVectorRef out, u32 count) const;\n  void BuildNormals(const aurora::Vec3<float>* in, TVectorRef out, u32 count) const;\n  void BuildAccumulatedTransform(const CPoseAsTransforms& pose, const zeus::CVector3f* points);\n\n  [[nodiscard]] const auto& GetWeights() const { return x0_weights; }\n  [[nodiscard]] u32 GetVertexCount() const { return x1c_vertexCount; }\n\nprivate:\n  void BuildFinalPosMatrix(const CPoseAsTransforms& pose, const zeus::CVector3f* points);\n};\n\nclass CSkinRules {\n  friend class CSkinnedModel;\n\n  std::vector<CVirtualBone> x0_bones;\n  u32 x10_vertexCount = 0;\n  u32 x14_normalCount = 0;\n\npublic:\n  explicit CSkinRules(CInputStream& in);\n\n  void BuildPoints(TConstVectorRef positions, TVectorRef out);\n  void BuildNormals(TConstVectorRef normals, TVectorRef out);\n  void BuildAccumulatedTransforms(const CPoseAsTransforms& pose, const CCharLayoutInfo& info);\n\n  [[nodiscard]] u32 GetVertexCount() const { return x10_vertexCount; }\n  [[nodiscard]] u32 GetNormalCount() const { return x14_normalCount; }\n};\n\nCFactoryFnReturn FSkinRulesFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& params,\n                                   CObjectReference* selfRef);\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CSoundPOINode.cpp",
    "content": "#include \"Runtime/Character/CSoundPOINode.hpp\"\n\n#include \"Runtime/Character/CAnimSourceReader.hpp\"\n\nnamespace metaforce {\n\nCSoundPOINode::CSoundPOINode()\n: CPOINode(\"root\", EPOIType::Sound, CCharAnimTime(), -1, false, 1.f, -1, 0)\n, x38_sfxId(0)\n, x3c_falloff(0.f)\n, x40_maxDist(0.f) {}\n\nCSoundPOINode::CSoundPOINode(CInputStream& in)\n: CPOINode(in), x38_sfxId(in.ReadLong()), x3c_falloff(in.ReadFloat()), x40_maxDist(in.ReadFloat()) {}\n\nCSoundPOINode::CSoundPOINode(std::string_view name, EPOIType a, const CCharAnimTime& time, u32 b, bool c, float d,\n                             u32 e, u32 f, u32 sfxId, float falloff, float maxDist)\n: CPOINode(name, a, time, b, c, d, e, f), x38_sfxId(sfxId), x3c_falloff(falloff), x40_maxDist(maxDist) {}\n\nCSoundPOINode CSoundPOINode::CopyNodeMinusStartTime(const CSoundPOINode& node, const CCharAnimTime& startTime) {\n  CSoundPOINode ret = node;\n  ret.x1c_time -= startTime;\n  return ret;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CSoundPOINode.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Character/CCharAnimTime.hpp\"\n#include \"Runtime/Character/CPOINode.hpp\"\n\nnamespace metaforce {\nclass IAnimSourceInfo;\n\nclass CSoundPOINode : public CPOINode {\n  u32 x38_sfxId;\n  float x3c_falloff;\n  float x40_maxDist;\n\npublic:\n  explicit CSoundPOINode();\n  explicit CSoundPOINode(CInputStream& in);\n  explicit CSoundPOINode(std::string_view name, EPOIType type, const CCharAnimTime& time, u32 b, bool c, float d, u32 e,\n                         u32 f, u32 sfxId, float falloff, float maxDist);\n\n  static CSoundPOINode CopyNodeMinusStartTime(const CSoundPOINode& node, const CCharAnimTime& startTime);\n  u32 GetSfxId() const { return x38_sfxId; }\n  float GetFalloff() const { return x3c_falloff; }\n  float GetMaxDist() const { return x40_maxDist; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CSteeringBehaviors.cpp",
    "content": "#include \"Runtime/Character/CSteeringBehaviors.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CPhysicsActor.hpp\"\n\nnamespace metaforce {\n\nzeus::CVector3f CSteeringBehaviors::Flee(const CPhysicsActor& actor, const zeus::CVector3f& v0) const {\n  zeus::CVector3f actVec = actor.GetTranslation() - v0;\n  if (actVec.canBeNormalized())\n    return actVec.normalized();\n\n  return actor.GetTransform().frontVector();\n}\n\nzeus::CVector3f CSteeringBehaviors::Seek(const CPhysicsActor& actor, const zeus::CVector3f& target) const {\n  zeus::CVector3f posDiff = target - actor.GetTranslation();\n  if (posDiff.canBeNormalized())\n    return posDiff.normalized();\n\n  return {};\n}\n\nzeus::CVector3f CSteeringBehaviors::Arrival(const CPhysicsActor& actor, const zeus::CVector3f& dest,\n                                            float dampingRadius) const {\n  zeus::CVector3f posDiff = dest - actor.GetTranslation();\n  if (!posDiff.canBeNormalized())\n    return {};\n\n  if (posDiff.magSquared() < (dampingRadius * dampingRadius))\n    dampingRadius = posDiff.magSquared() / (dampingRadius * dampingRadius);\n  else\n    dampingRadius = 1.f;\n\n  return dampingRadius * posDiff.normalized();\n}\n\nzeus::CVector3f CSteeringBehaviors::Pursuit(const CPhysicsActor& actor, const zeus::CVector3f& v0,\n                                            const zeus::CVector3f& v1) const {\n  zeus::CVector3f target;\n  if (!ProjectLinearIntersection(actor.GetTranslation(), actor.GetVelocity().magnitude(), v0, v1, target))\n    target = v1 * 1.f + v0;\n  return CSteeringBehaviors::Seek(actor, target);\n}\n\nzeus::CVector3f CSteeringBehaviors::Separation(const CPhysicsActor& actor, const zeus::CVector3f& pos,\n                                               float separation) const {\n  zeus::CVector3f posDiff = actor.GetTranslation() - pos;\n  if (posDiff.magSquared() >= separation * separation)\n    return {};\n\n  if (!posDiff.canBeNormalized())\n    return actor.GetTransform().frontVector();\n\n  return (1.f - (posDiff.magSquared() / (separation * separation))) * posDiff.normalized();\n}\n\nzeus::CVector3f CSteeringBehaviors::Alignment(const CPhysicsActor& actor, EntityList& list,\n                                              const CStateManager& mgr) const {\n  zeus::CVector3f align;\n\n  if (!list.empty()) {\n    for (const TUniqueId& id : list)\n      if (const CActor* act = static_cast<const CActor*>(mgr.GetObjectById(id)))\n        align += act->GetTransform().frontVector();\n\n    align *= zeus::CVector3f(1.f / float(list.size()));\n  }\n\n  float diff = zeus::CVector3f::getAngleDiff(actor.GetTransform().frontVector(), align);\n  return align * (diff / M_PIF);\n}\n\nzeus::CVector3f CSteeringBehaviors::Cohesion(const CPhysicsActor& actor, EntityList& list, float dampingRadius,\n                                             const CStateManager& mgr) const {\n  zeus::CVector3f dest;\n  if (!list.empty()) {\n    for (const TUniqueId& id : list)\n      if (const CActor* act = static_cast<const CActor*>(mgr.GetObjectById(id)))\n        dest += act->GetTranslation();\n\n    dest *= zeus::CVector3f(1.f / float(list.size()));\n    return Arrival(actor, dest, dampingRadius);\n  }\n  return dest;\n}\n\nzeus::CVector2f CSteeringBehaviors::Flee2D(const CPhysicsActor& actor, const zeus::CVector2f& v0) const {\n  zeus::CVector2f diffVec = actor.GetTranslation().toVec2f() - v0;\n  if (diffVec.magSquared() > FLT_EPSILON)\n    return diffVec.normalized();\n  else\n    return actor.GetTransform().basis[1].toVec2f();\n}\n\nzeus::CVector2f CSteeringBehaviors::Arrival2D(const CPhysicsActor& actor, const zeus::CVector2f& v0) const {\n  zeus::CVector2f diffVec = v0 - actor.GetTranslation().toVec2f();\n  if (diffVec.magSquared() > FLT_EPSILON)\n    return diffVec.normalized();\n  else\n    return {};\n}\n\nbool CSteeringBehaviors::SolveQuadratic(float a, float b, float c, float& xPos, float& xNeg) {\n  float numSq = b * b - 4.f * a * c;\n  if (numSq < FLT_EPSILON || std::fabs(a) < FLT_EPSILON)\n    return false;\n\n  numSq = std::sqrt(numSq);\n  float denom = 2.f * a;\n  xPos = (-b + numSq) / denom;\n  xNeg = (-b - numSq) / denom;\n  return true;\n}\n\nbool CSteeringBehaviors::SolveCubic(const rstl::reserved_vector<float, 4>& in, rstl::reserved_vector<float, 4>& out) {\n  if (in[3] != 0.f) {\n    float f3 = 3.f * in[3];\n    float f31 = in[2] / f3;\n    float f4 = in[1] / f3 - f31 * f31;\n    float f0 = (f31 * f4 - in[0]) / in[3];\n    float f1 = 2.f * f31 * f31;\n    f3 = f4 * f4 * f4;\n    float f24 = -0.5f * (f31 * f1 - f0);\n    f1 = f24 * f24 + f3;\n    if (f1 < 0.f) {\n      float f25 = std::acos(zeus::clamp(-1.f, f24 / std::sqrt(-f3), 1.f));\n      f24 = 2.f * std::pow(-f3, 0.166667f);\n      for (float f23 = 0.f; f23 < 2.01f; f23 += 1.f)\n        out.push_back(std::cos((2.f * f23 * M_PIF + f25) / 3.f) * f24 - f31);\n      if (out[1] < out[0])\n        std::swap(out[1], out[0]);\n      if (out[2] < out[1])\n        std::swap(out[2], out[1]);\n      if (out[1] < out[0])\n        std::swap(out[1], out[0]);\n    } else {\n      float f30 = std::sqrt(f1);\n      float f25 = std::pow(std::fabs(f24 + f30), 0.333333f);\n      f1 = std::pow(std::fabs(f24 - f30), 0.333333f);\n      f1 = (f24 - f30) > 0.f ? f1 : -f1;\n      f25 = (f24 + f30) > 0.f ? f25 : -f25;\n      out.push_back(f25 + f1 - f31);\n    }\n    for (float& f : out) {\n      float f8 = (2.f * in[2] + 3.f * f * in[3]) * f + in[1];\n      if (f8 != 0.f)\n        f -= (((f * in[3] + in[2]) * f + in[1]) * f + in[0]) / f8;\n    }\n  } else if (in[2] != 0.f) {\n    float f23 = 0.5f * in[1] / in[2];\n    float f1 = f23 * f23 - (in[1] / in[2]);\n    if (f1 >= 0.f) {\n      f1 = std::sqrt(f1);\n      out.push_back(-f23 - f1);\n      out.push_back(-f23 + f1);\n    }\n  } else if (in[1] != 0.f) {\n    out.push_back(-in[0] / in[1]);\n  }\n  return out.size() != 0;\n}\n\nbool CSteeringBehaviors::SolveQuartic(const rstl::reserved_vector<float, 5>& in, rstl::reserved_vector<float, 4>& out) {\n  if (in[4] == 0.f) {\n    rstl::reserved_vector<float, 4> newIn;\n    newIn.push_back(in[0]);\n    newIn.push_back(in[1]);\n    newIn.push_back(in[2]);\n    newIn.push_back(in[3]);\n    return SolveCubic(newIn, out);\n  } else {\n    rstl::reserved_vector<float, 4> newIn;\n    float f30 = in[3] / (4.f * in[4]);\n    float f2 = in[1] / in[4];\n    float f29 = f30 * (8.f * f30 * f30 - 2.f * in[2] / in[4]) + f2;\n    float f31 = -6.f * f30 * f30 + (in[2] / in[4]);\n    float f28 = f30 * (f30 * (-3.f * f30 * f30 + (in[2] / in[4])) - f2) + (in[0] / in[4]);\n    newIn.push_back(4.f * f28 * f31 - f29 * f29);\n    newIn.push_back(-8.f * f28);\n    newIn.push_back(-4.f * f31);\n    newIn.push_back(8.f);\n    rstl::reserved_vector<float, 4> newOut;\n    if (SolveCubic(newIn, newOut)) {\n      float f26 = 2.f * newOut.back() - f31;\n      f31 = std::sqrt(f26);\n      float f1;\n      if (f31 == 0.f) {\n        f1 = newOut.back() * newOut.back() - f28;\n        if (f1 < 0.f)\n          return false;\n        f1 = std::sqrt(f1);\n      } else {\n        f1 = f29 / (2.f * f31);\n      }\n      float f1b = f26 - (newOut.back() + f1) * 4.f;\n      f26 = f26 - (newOut.back() - f1) * 4.f;\n      if (f1b >= 0.f) {\n        f1b = std::sqrt(f1b);\n        out.push_back((f31 - f1b) * 0.5f - f30);\n        out.push_back((f31 + f1b) * 0.5f - f30);\n      }\n      if (f26 >= 0.f) {\n        f1b = std::sqrt(f26);\n        out.push_back((-f31 - f1b) * 0.5f - f30);\n        out.push_back((-f31 + f1b) * 0.5f - f30);\n      }\n      for (float& f : out) {\n        float f10 = ((3.f * in[3] + 4.f * f * in[4]) * f + 2.f * in[2]) * f + in[1];\n        if (f10 != 0.f)\n          f -= ((((f * in[4] + in[3]) * f + in[2]) * f + in[1]) * f + in[0]) / f10;\n      }\n      if (out.size() > 2) {\n        if (out[2] < out[0])\n          std::swap(out[2], out[0]);\n        if (out[3] < out[1])\n          std::swap(out[3], out[1]);\n        if (out[1] < out[0])\n          std::swap(out[1], out[0]);\n        if (out[3] < out[2])\n          std::swap(out[3], out[2]);\n        if (out[2] < out[1])\n          std::swap(out[2], out[1]);\n      }\n    }\n    return out.size() != 0;\n  }\n}\n\nbool CSteeringBehaviors::ProjectLinearIntersection(const zeus::CVector3f& v0, float f1, const zeus::CVector3f& v1,\n                                                   const zeus::CVector3f& v2, zeus::CVector3f& v3) {\n  zeus::CVector3f posDiff = v1 - v0;\n  float xPos, xNeg;\n  if (SolveQuadratic(v2.magSquared() - f1 * f1, posDiff.dot(v2) * 2.f, posDiff.magSquared(), xPos, xNeg) &&\n      xNeg > 0.f) {\n    v3 = v2 * xNeg + v1;\n    return true;\n  }\n  return false;\n}\n\nbool CSteeringBehaviors::ProjectLinearIntersection(const zeus::CVector3f& v0, float f1, const zeus::CVector3f& v1,\n                                                   const zeus::CVector3f& v2, const zeus::CVector3f& v3,\n                                                   zeus::CVector3f& v4) {\n  rstl::reserved_vector<float, 5> newIn;\n  rstl::reserved_vector<float, 4> newOut;\n  zeus::CVector3f f7 = v1 - v0;\n  newIn.push_back(f7.magSquared());\n  newIn.push_back(f7.dot(v2) * 2.f);\n  newIn.push_back(f7.dot(v3) + v2.magSquared() - f1 * f1);\n  newIn.push_back(v2.dot(v3));\n  newIn.push_back(v3.magSquared() * 0.25f);\n  bool ret = false;\n  if (SolveQuartic(newIn, newOut))\n    for (float& f : newOut)\n      if (f > 0.f) {\n        ret = true;\n        v4 = v1 + v2 * f + 0.5f * f * f * v3;\n      }\n  return ret;\n}\n\nbool CSteeringBehaviors::ProjectOrbitalIntersection(const zeus::CVector3f& v0, float f1, float f2,\n                                                    const zeus::CVector3f& v1, const zeus::CVector3f& v2,\n                                                    const zeus::CVector3f& v3, zeus::CVector3f& v4) {\n  if (f1 > 0.f) {\n    if (v2.canBeNormalized()) {\n      zeus::CVector3f _12c = (v1 - v3).toVec2f();\n      if (_12c.canBeNormalized()) {\n        zeus::CVector3f f25 = v1;\n        zeus::CVector3f f22 = v2;\n        float f17 = (f25 - v0).magnitude() / f1 - 0.f;\n        float f18 = FLT_MAX;\n        zeus::CVector3f _150 = _12c.normalized();\n        float f26 = _150.dot(f22);\n        float f27 = _150.cross(zeus::skUp).dot(f22);\n        for (float f19 = 0.f; f17 < f18 && f19 < 4.f;) {\n          if (zeus::close_enough(f17, f2) || f17 < 0.f) {\n            v4 = f25;\n            return true;\n          }\n          f25 += f2 * f22;\n          f18 = f17;\n          _12c = (f25 - v3).toVec2f();\n          if (!_12c.canBeNormalized())\n            break;\n          zeus::CVector3f _168 = _12c.normalized();\n          f22 = _168.cross(zeus::skUp) * f27 + f26 * _168;\n          f19 += f2;\n          f17 = (f25 - v0).magnitude() / f1 - f19;\n        }\n      } else {\n        return ProjectLinearIntersection(v0, f1, v1, v2, v4);\n      }\n    } else {\n      v4 = v1;\n      return true;\n    }\n  }\n  return false;\n}\n\nbool CSteeringBehaviors::ProjectOrbitalIntersection(const zeus::CVector3f& v0, float f1, float f2,\n                                                    const zeus::CVector3f& v1, const zeus::CVector3f& v2,\n                                                    const zeus::CVector3f& v3, const zeus::CVector3f& v4,\n                                                    zeus::CVector3f& v5) {\n  if (f1 > 0.f) {\n    zeus::CVector3f _12c = (v1 - v4).toVec2f();\n    if (v2.canBeNormalized() && _12c.canBeNormalized()) {\n      zeus::CVector3f f24 = v1;\n      zeus::CVector3f f21 = v2;\n      float f16 = (f24 - v0).magnitude() / f1 - 0.f;\n      float f17 = FLT_MAX;\n      zeus::CVector3f _150 = _12c.normalized();\n      float f25 = _150.dot(f21);\n      float f26 = _150.cross(zeus::skUp).dot(f21);\n      for (float f18 = 0.f; f16 < f17 && f18 < 4.f;) {\n        if (zeus::close_enough(f16, f2) || f16 < 0.f) {\n          v5 = f24;\n          return true;\n        }\n        f24 += f2 * f21;\n        f17 = f16;\n        f18 += f2;\n        f16 = (f24 - v0).magnitude() / f1 - f18;\n        _12c = (f24 - v4).toVec2f();\n        if (!_12c.canBeNormalized())\n          break;\n        zeus::CVector3f _168 = _12c.normalized();\n        f21 = _168.cross(zeus::skUp) * f26 + f25 * _168;\n      }\n    } else {\n      return ProjectLinearIntersection(v0, f1, v1, v2, v3, v5);\n    }\n  }\n  return false;\n}\n\nzeus::CVector3f CSteeringBehaviors::ProjectOrbitalPosition(const zeus::CVector3f& pos, const zeus::CVector3f& vel,\n                                                           const zeus::CVector3f& orbitPoint, float dt,\n                                                           float preThinkDt) {\n  zeus::CVector3f usePos = pos;\n  if (vel.canBeNormalized()) {\n    zeus::CVector3f pointToPos = pos - orbitPoint;\n    pointToPos.z() = 0.f;\n    if (pointToPos.canBeNormalized()) {\n      zeus::CVector3f useVel = vel;\n      pointToPos.normalize();\n      float f29 = pointToPos.dot(useVel);\n      float f30 = pointToPos.cross(zeus::skUp).dot(useVel);\n      for (float curDt = 0.f; curDt < dt;) {\n        usePos += preThinkDt * useVel;\n        zeus::CVector3f usePointToPos = usePos - orbitPoint;\n        usePointToPos.z() = 0.f;\n        if (usePointToPos.canBeNormalized()) {\n          usePointToPos.normalize();\n          useVel = usePointToPos.cross(zeus::skUp) * f30 + usePointToPos * f29;\n        }\n        curDt += std::min(dt - curDt, preThinkDt);\n      }\n    }\n  }\n  return usePos;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CSteeringBehaviors.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n\n#include <zeus/CVector2f.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CPhysicsActor;\nclass CStateManager;\n\nclass CSteeringBehaviors {\n  float x0_ = M_PIF / 2.f;\n\npublic:\n  zeus::CVector3f Flee(const CPhysicsActor& actor, const zeus::CVector3f& v0) const;\n  zeus::CVector3f Seek(const CPhysicsActor& actor, const zeus::CVector3f& target) const;\n  zeus::CVector3f Arrival(const CPhysicsActor& actor, const zeus::CVector3f& dest, float dampingRadius) const;\n  zeus::CVector3f Pursuit(const CPhysicsActor& actor, const zeus::CVector3f& v0, const zeus::CVector3f& v1) const;\n  zeus::CVector3f Separation(const CPhysicsActor& actor, const zeus::CVector3f& pos, float separation) const;\n  zeus::CVector3f Alignment(const CPhysicsActor& actor, EntityList& list, const CStateManager& mgr) const;\n  zeus::CVector3f Cohesion(const CPhysicsActor& actor, EntityList& list, float dampingRadius,\n                           const CStateManager& mgr) const;\n  zeus::CVector2f Flee2D(const CPhysicsActor& actor, const zeus::CVector2f& v0) const;\n  zeus::CVector2f Arrival2D(const CPhysicsActor& actor, const zeus::CVector2f& v0) const;\n  static bool SolveQuadratic(float, float, float, float&, float&);\n  static bool SolveCubic(const rstl::reserved_vector<float, 4>& in, rstl::reserved_vector<float, 4>& out);\n  static bool SolveQuartic(const rstl::reserved_vector<float, 5>& in, rstl::reserved_vector<float, 4>& out);\n  static bool ProjectLinearIntersection(const zeus::CVector3f& v0, float f1, const zeus::CVector3f& v1,\n                                        const zeus::CVector3f& v2, zeus::CVector3f& v3);\n  static bool ProjectLinearIntersection(const zeus::CVector3f& v0, float f1, const zeus::CVector3f& v1,\n                                        const zeus::CVector3f& v2, const zeus::CVector3f& v3, zeus::CVector3f& v4);\n  static bool ProjectOrbitalIntersection(const zeus::CVector3f& v0, float f1, float f2, const zeus::CVector3f& v1,\n                                         const zeus::CVector3f& v2, const zeus::CVector3f& v3, zeus::CVector3f& v4);\n  static bool ProjectOrbitalIntersection(const zeus::CVector3f& v0, float f1, float f2, const zeus::CVector3f& v1,\n                                         const zeus::CVector3f& v2, const zeus::CVector3f& v3,\n                                         const zeus::CVector3f& v4, zeus::CVector3f& v5);\n  static zeus::CVector3f ProjectOrbitalPosition(const zeus::CVector3f& pos, const zeus::CVector3f& vel,\n                                                const zeus::CVector3f& orbitPoint, float dt, float preThinkDt);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CTimeScaleFunctions.cpp",
    "content": "#include \"Runtime/Character/CTimeScaleFunctions.hpp\"\n\n#include <zeus/Math.hpp>\n\nnamespace metaforce {\n\nstd::unique_ptr<IVaryingAnimationTimeScale> IVaryingAnimationTimeScale::Clone() const { return VClone(); }\n\nfloat CConstantAnimationTimeScale::VTimeScaleIntegral(float lowerLimit, float upperLimit) const {\n  return (upperLimit - lowerLimit) * x4_scale;\n}\n\nfloat CConstantAnimationTimeScale::VFindUpperLimit(float lowerLimit, float root) const {\n  return (root / x4_scale) + lowerLimit;\n}\n\nstd::unique_ptr<IVaryingAnimationTimeScale> CConstantAnimationTimeScale::VClone() const {\n  return std::make_unique<CConstantAnimationTimeScale>(x4_scale);\n}\n\nstd::unique_ptr<IVaryingAnimationTimeScale> CConstantAnimationTimeScale::VGetFunctionMirrored(float) const {\n  return Clone();\n}\n\nCLinearAnimationTimeScale::CLinearAnimationTimeScale(const CCharAnimTime& t1, float y1, const CCharAnimTime& t2,\n                                                     float y2) {\n  float y2my1 = y2 - y1;\n  float t2mt1 = (t2 - t1).GetSeconds();\n  x4_desc.x4_slope = y2my1 / t2mt1;\n  x4_desc.x8_yIntercept = y1 - y2my1 / t2mt1 * t1.GetSeconds();\n  x4_desc.xc_t1 = t1.GetSeconds();\n  x4_desc.x10_t2 = t2.GetSeconds();\n}\n\nstd::unique_ptr<IVaryingAnimationTimeScale>\nCLinearAnimationTimeScale::CFunctionDescription::FunctionMirroredAround(float value) const {\n  float slope = -x4_slope;\n  float t1 = 2.f * value - x10_t2;\n  float t2 = 2.f * value - xc_t1;\n  float newYInt = x8_yIntercept - x4_slope * 2.f * value;\n  float y1 = slope * t1 + newYInt;\n  float y2 = slope * t2 + newYInt;\n  return std::make_unique<CLinearAnimationTimeScale>(t1, y1, t2, y2);\n}\n\nfloat CLinearAnimationTimeScale::VTimeScaleIntegral(float lowerLimit, float upperLimit) const {\n  if (lowerLimit <= upperLimit)\n    return TimeScaleIntegralWithSortedLimits(x4_desc, lowerLimit, upperLimit);\n  else\n    return -TimeScaleIntegralWithSortedLimits(x4_desc, upperLimit, lowerLimit);\n}\n\nfloat CLinearAnimationTimeScale::TimeScaleIntegralWithSortedLimits(const CFunctionDescription& desc, float lowerLimit,\n                                                                   float upperLimit) {\n  float lowerEval = desc.x4_slope * lowerLimit + desc.x8_yIntercept;\n  float upperEval = desc.x4_slope * upperLimit + desc.x8_yIntercept;\n  return (upperLimit - lowerLimit) * 0.5f * (lowerEval + upperEval);\n}\n\nfloat CLinearAnimationTimeScale::VFindUpperLimit(float lowerLimit, float root) const {\n  return FindUpperLimitFromRoot(x4_desc, lowerLimit, root);\n}\n\nfloat CLinearAnimationTimeScale::FindUpperLimitFromRoot(const CFunctionDescription& desc, float lowerLimit,\n                                                        float root) {\n  float M = 0.5f * desc.x4_slope;\n  float upperLimit = lowerLimit;\n  float m = 2.f * M;\n  float lowerIntegration = M * lowerLimit * lowerLimit + desc.x8_yIntercept * lowerLimit;\n  for (int i = 0; i < 16; ++i) {\n    float factor = (M * upperLimit * upperLimit + desc.x8_yIntercept * upperLimit - lowerIntegration - root) /\n                   (m * upperLimit + desc.x8_yIntercept);\n    upperLimit -= factor;\n    if (zeus::close_enough(factor, 0.f))\n      return upperLimit;\n  }\n  return -1.f;\n}\n\nstd::unique_ptr<IVaryingAnimationTimeScale> CLinearAnimationTimeScale::VClone() const {\n  float y1 = x4_desc.x4_slope * x4_desc.xc_t1 + x4_desc.x8_yIntercept;\n  float y2 = x4_desc.x4_slope * x4_desc.x10_t2 + x4_desc.x8_yIntercept;\n  return std::make_unique<CLinearAnimationTimeScale>(x4_desc.xc_t1, y1, x4_desc.x10_t2, y2);\n}\n\nstd::unique_ptr<IVaryingAnimationTimeScale> CLinearAnimationTimeScale::VGetFunctionMirrored(float value) const {\n  return x4_desc.FunctionMirroredAround(value);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CTimeScaleFunctions.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Character/CCharAnimTime.hpp\"\n\nnamespace metaforce {\n\nenum class EVaryingAnimationTimeScaleType { Constant, Linear };\n\nclass IVaryingAnimationTimeScale {\npublic:\n  virtual ~IVaryingAnimationTimeScale() = default;\n  virtual EVaryingAnimationTimeScaleType GetType() const = 0;\n  virtual float VTimeScaleIntegral(float lowerLimit, float upperLimit) const = 0;\n  virtual float VFindUpperLimit(float lowerLimit, float root) const = 0;\n  virtual std::unique_ptr<IVaryingAnimationTimeScale> VClone() const = 0;\n  virtual std::unique_ptr<IVaryingAnimationTimeScale> VGetFunctionMirrored(float value) const = 0;\n  std::unique_ptr<IVaryingAnimationTimeScale> Clone() const;\n};\n\nclass CConstantAnimationTimeScale : public IVaryingAnimationTimeScale {\nprivate:\n  float x4_scale;\n\npublic:\n  explicit CConstantAnimationTimeScale(float scale) : x4_scale(scale) {}\n\n  EVaryingAnimationTimeScaleType GetType() const override { return EVaryingAnimationTimeScaleType::Constant; }\n  float VTimeScaleIntegral(float lowerLimit, float upperLimit) const override;\n  float VFindUpperLimit(float lowerLimit, float root) const override;\n  std::unique_ptr<IVaryingAnimationTimeScale> VClone() const override;\n  std::unique_ptr<IVaryingAnimationTimeScale> VGetFunctionMirrored(float value) const override;\n};\n\nclass CLinearAnimationTimeScale : public IVaryingAnimationTimeScale {\n  struct CFunctionDescription {\n    float x4_slope;\n    float x8_yIntercept;\n    float xc_t1;\n    float x10_t2;\n    std::unique_ptr<IVaryingAnimationTimeScale> FunctionMirroredAround(float value) const;\n  } x4_desc;\n  static float FindUpperLimitFromRoot(const CFunctionDescription& desc, float lowerLimit, float root);\n  static float TimeScaleIntegralWithSortedLimits(const CFunctionDescription& desc, float lowerLimit, float upperLimit);\n\npublic:\n  explicit CLinearAnimationTimeScale(const CCharAnimTime& t1, float y1, const CCharAnimTime& t2, float y2);\n\n  EVaryingAnimationTimeScaleType GetType() const override { return EVaryingAnimationTimeScaleType::Linear; }\n  float VTimeScaleIntegral(float lowerLimit, float upperLimit) const override;\n  float VFindUpperLimit(float lowerLimit, float root) const override;\n  std::unique_ptr<IVaryingAnimationTimeScale> VClone() const override;\n  std::unique_ptr<IVaryingAnimationTimeScale> VGetFunctionMirrored(float value) const override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CTransition.cpp",
    "content": "#include \"Runtime/Character/CTransition.hpp\"\n\nnamespace metaforce {\n\nCTransition::CTransition(CInputStream& in)\n: x0_id(in.ReadLong())\n, x4_animA(in.ReadLong())\n, x8_animB(in.ReadLong())\n, xc_trans(CMetaTransFactory::CreateMetaTrans(in)) {}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CTransition.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <utility>\n\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/Character/CMetaTransFactory.hpp\"\n\nnamespace metaforce {\n\nclass CTransition {\n  u32 x0_id;\n  u32 x4_animA;\n  u32 x8_animB;\n  std::shared_ptr<IMetaTrans> xc_trans;\n\npublic:\n  explicit CTransition(CInputStream& in);\n  u32 GetAnimA() const { return x4_animA; }\n  u32 GetAnimB() const { return x8_animB; }\n  std::pair<u32, u32> GetAnimPair() const { return {x4_animA, x8_animB}; }\n  const std::shared_ptr<IMetaTrans>& GetMetaTrans() const { return xc_trans; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CTransitionDatabase.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include \"Runtime/RetroTypes.hpp\"\n\nnamespace metaforce {\nclass IMetaTrans;\n\nclass CTransitionDatabase {\npublic:\n  virtual ~CTransitionDatabase() = default;\n  virtual const std::shared_ptr<IMetaTrans>& GetMetaTrans(u32, u32) const = 0;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CTransitionDatabaseGame.cpp",
    "content": "#include \"Runtime/Character/CTransitionDatabaseGame.hpp\"\n\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Character/CHalfTransition.hpp\"\n#include \"Runtime/Character/CTransition.hpp\"\n\n#include <algorithm>\n#include <utility>\n\nnamespace metaforce {\n\nCTransitionDatabaseGame::CTransitionDatabaseGame(const std::vector<CTransition>& transitions,\n                                                 const std::vector<CHalfTransition>& halfTransitions,\n                                                 std::shared_ptr<IMetaTrans> defaultTrans)\n: x10_defaultTrans(std::move(defaultTrans)) {\n  x14_transitions.reserve(transitions.size());\n  for (const CTransition& trans : transitions)\n    x14_transitions.emplace_back(trans.GetAnimPair(), trans.GetMetaTrans());\n  std::sort(x14_transitions.begin(), x14_transitions.end(),\n            [](const auto& a, const auto& b) { return a.first < b.first; });\n\n  x24_halfTransitions.reserve(halfTransitions.size());\n  for (const CHalfTransition& trans : halfTransitions)\n    x24_halfTransitions.emplace_back(trans.GetId(), trans.GetMetaTrans());\n  std::sort(x24_halfTransitions.begin(), x24_halfTransitions.end(),\n            [](const auto& a, const auto& b) { return a.first < b.first; });\n}\n\nconst std::shared_ptr<IMetaTrans>& CTransitionDatabaseGame::GetMetaTrans(u32 a, u32 b) const {\n  const auto it = rstl::binary_find(x14_transitions.cbegin(), x14_transitions.cend(), std::make_pair(a, b),\n                                    [](const auto& p) { return p.first; });\n  if (it != x14_transitions.cend()) {\n    return it->second;\n  }\n\n  const auto it2 = rstl::binary_find(x24_halfTransitions.cbegin(), x24_halfTransitions.cend(), b,\n                                     [](const auto& p) { return p.first; });\n  if (it2 != x24_halfTransitions.cend()) {\n    return it2->second;\n  }\n\n  return x10_defaultTrans;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CTransitionDatabaseGame.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <utility>\n#include <vector>\n\n#include \"Runtime/Character/CTransitionDatabase.hpp\"\n\nnamespace metaforce {\nclass CTransition;\nclass CHalfTransition;\n\nclass CTransitionDatabaseGame final : public CTransitionDatabase {\n  std::shared_ptr<IMetaTrans> x10_defaultTrans;\n  std::vector<std::pair<std::pair<u32, u32>, std::shared_ptr<IMetaTrans>>> x14_transitions;\n  std::vector<std::pair<u32, std::shared_ptr<IMetaTrans>>> x24_halfTransitions;\n\npublic:\n  CTransitionDatabaseGame(const std::vector<CTransition>& transitions,\n                          const std::vector<CHalfTransition>& halfTransitions,\n                          std::shared_ptr<IMetaTrans> defaultTrans);\n  const std::shared_ptr<IMetaTrans>& GetMetaTrans(u32, u32) const override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CTransitionManager.cpp",
    "content": "#include \"Runtime/Character/CTransitionManager.hpp\"\n\n#include \"Runtime/Character/CTreeUtils.hpp\"\n\nnamespace metaforce {\n\nstd::shared_ptr<CAnimTreeNode> CTransitionManager::GetTransitionTree(const std::shared_ptr<CAnimTreeNode>& a,\n                                                                     const std::shared_ptr<CAnimTreeNode>& b) const {\n  return CTreeUtils::GetTransitionTree(a, b, x0_animCtx);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CTransitionManager.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/Character/CAnimSysContext.hpp\"\n#include \"Runtime/Character/CTransitionDatabaseGame.hpp\"\n\nnamespace metaforce {\nclass CAnimTreeNode;\nclass CRandom16;\nclass CSimplePool;\n\nclass CTransitionManager {\n  CAnimSysContext x0_animCtx;\n\npublic:\n  explicit CTransitionManager(CAnimSysContext sysCtx) : x0_animCtx(std::move(sysCtx)) {}\n  std::shared_ptr<CAnimTreeNode> GetTransitionTree(const std::shared_ptr<CAnimTreeNode>& a,\n                                                   const std::shared_ptr<CAnimTreeNode>& b) const;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CTreeUtils.cpp",
    "content": "#include \"Runtime/Character/CTreeUtils.hpp\"\n\n#include \"Runtime/Character/CAnimSysContext.hpp\"\n#include \"Runtime/Character/CAnimTreeNode.hpp\"\n#include \"Runtime/Character/CTransitionDatabaseGame.hpp\"\n#include \"Runtime/Character/IMetaTrans.hpp\"\n\nnamespace metaforce {\n\nstd::shared_ptr<CAnimTreeNode> CTreeUtils::GetTransitionTree(const std::weak_ptr<CAnimTreeNode>& a,\n                                                             const std::weak_ptr<CAnimTreeNode>& b,\n                                                             const CAnimSysContext& animCtx) {\n  CAnimTreeEffectiveContribution contribA = a.lock()->GetContributionOfHighestInfluence();\n  CAnimTreeEffectiveContribution contribB = b.lock()->GetContributionOfHighestInfluence();\n  return animCtx.x0_transDB->GetMetaTrans(contribA.GetAnimDatabaseIndex(), contribB.GetAnimDatabaseIndex())\n      ->GetTransitionTree(a, b, animCtx);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CTreeUtils.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/RetroTypes.hpp\"\n\nnamespace metaforce {\nclass CAnimTreeNode;\nstruct CAnimSysContext;\n\nclass CTreeUtils {\npublic:\n  static std::shared_ptr<CAnimTreeNode> GetTransitionTree(const std::weak_ptr<CAnimTreeNode>& a,\n                                                          const std::weak_ptr<CAnimTreeNode>& b,\n                                                          const CAnimSysContext& animCtx);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/CharacterCommon.cpp",
    "content": "#include \"CharacterCommon.hpp\"\n\nusing namespace std::literals;\n\nnamespace metaforce::pas {\nstd::string_view AnimationStateToStr(EAnimationState state) {\n  switch (state) {\n  case EAnimationState::Invalid:\n    return \"Invalid\"sv;\n  case EAnimationState::Fall:\n    return \"Fall\"sv;\n  case EAnimationState::Getup:\n    return \"Getup\"sv;\n  case EAnimationState::LieOnGround:\n    return \"LieOnGround\"sv;\n  case EAnimationState::Step:\n    return \"Step\"sv;\n  case EAnimationState::Death:\n    return \"Death\"sv;\n  case EAnimationState::Locomotion:\n    return \"Locomotion\"sv;\n  case EAnimationState::KnockBack:\n    return \"KnockBack\"sv;\n  case EAnimationState::MeleeAttack:\n    return \"MeleeAttack\"sv;\n  case EAnimationState::Turn:\n    return \"Turn\"sv;\n  case EAnimationState::LoopAttack:\n    return \"LoopAttack\"sv;\n  case EAnimationState::LoopReaction:\n    return \"LoopReaction\"sv;\n  case EAnimationState::GroundHit:\n    return \"GroundHit\"sv;\n  case EAnimationState::Generate:\n    return \"Generate\"sv;\n  case EAnimationState::Jump:\n    return \"Jump\"sv;\n  case EAnimationState::Hurled:\n    return \"Hurled\"sv;\n  case EAnimationState::Slide:\n    return \"Slide\"sv;\n  case EAnimationState::Taunt:\n    return \"Taunt\"sv;\n  case EAnimationState::Scripted:\n    return \"Scripted\"sv;\n  case EAnimationState::ProjectileAttack:\n    return \"ProjectileAttack\"sv;\n  case EAnimationState::Cover:\n    return \"Cover\"sv;\n  case EAnimationState::WallHang:\n    return \"WallHang\"sv;\n  case EAnimationState::AdditiveIdle:\n    return \"AdditiveIdle\"sv;\n  case EAnimationState::AdditiveAim:\n    return \"AdditiveAim\"sv;\n  case EAnimationState::AdditiveFlinch:\n    return \"AdditiveFlinch\"sv;\n  case EAnimationState::AdditiveReaction:\n    return \"AdditiveReaction\"sv;\n  default:\n    return \"[unknown]\";\n  }\n}\n} // namespace metaforce::pas\n"
  },
  {
    "path": "Runtime/Character/CharacterCommon.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\nnamespace metaforce {\nnamespace pas {\nenum class ELocomotionType {\n  Invalid = -1,\n  Crouch = 0,\n  Relaxed = 1,\n  Lurk = 2,\n  Combat = 3,\n  Internal4 = 4,\n  Internal5 = 5,\n  Internal6 = 6,\n  Internal7 = 7,\n  Internal8 = 8,\n  Internal9 = 9,\n  Internal10 = 10,\n  Internal11 = 11,\n  Internal12 = 12,\n  Internal13 = 13,\n  Internal14 = 14\n};\n\nenum class ELocomotionAnim { Invalid = -1, Idle, Walk, Run, BackUp, StrafeLeft, StrafeRight, StrafeUp, StrafeDown };\n\nenum class EAnimationState {\n  Invalid = -1,\n  Fall = 0,\n  Getup = 1,\n  LieOnGround = 2,\n  Step = 3,\n  Death = 4,\n  Locomotion = 5,\n  KnockBack = 6,\n  MeleeAttack = 7,\n  Turn = 8,\n  LoopAttack = 9,\n  LoopReaction = 10,\n  GroundHit = 11,\n  Generate = 12,\n  Jump = 13,\n  Hurled = 14,\n  Slide = 15,\n  Taunt = 16,\n  Scripted = 17,\n  ProjectileAttack = 18,\n  Cover = 19,\n  WallHang = 20,\n  AdditiveIdle = 21,\n  AdditiveAim = 22,\n  AdditiveFlinch = 23,\n  AdditiveReaction = 24\n};\n\nstd::string_view AnimationStateToStr(EAnimationState state);\n\nenum class EHurledState {\n  Invalid = -1,\n  KnockIntoAir,\n  KnockLoop,\n  KnockDown,\n  StrikeWall,\n  StrikeWallFallLoop,\n  OutOfStrikeWall,\n  Six,\n  Seven\n};\n\nenum class EFallState { Invalid = -1, Zero, One, Two };\n\nenum class EReactionType { Invalid = -1, Zero, One, Two, Three };\n\nenum class EAdditiveReactionType { Invalid = -1, Electrocution, One, Two, IceBreakout, Four, Five, Six, Seven };\n\nenum class EJumpType { Normal, One, Ambush };\n\nenum class EJumpState { Invalid = -1, IntoJump, AmbushJump, Loop, OutOfJump, WallBounceLeft, WallBounceRight };\n\nenum class EStepDirection { Invalid = -1, Forward = 0, Backward = 1, Left = 2, Right = 3, Up = 4, Down = 5 };\n\nenum class EStepType { Normal = 0, Dodge = 1, BreakDodge = 2, RollDodge = 3 };\nenum class ESeverity {\n  Invalid = -1,\n  Zero = 0,\n  One = 1,\n  Two = 2,\n  Three = 3,\n  Four = 4,\n  Five = 5,\n  Six = 6,\n  Seven = 7,\n  Eight = 8\n};\n\nenum class EGetupType { Invalid = -1, Zero = 0, One = 1, Two = 2 };\n\nenum class ELoopState { Invalid = -1, Begin, Loop, End };\n\nenum class ELoopAttackType { Invalid = -1, Zero, One, Two, Three };\n\nenum class EGenerateType { Invalid = -1, Zero, One, Two, Three, Four, Five, Six, Seven, Eight };\n\nenum class ESlideType { Invalid = -1, Zero = 0 };\n\nenum class ETauntType { Invalid = -1, Zero, One, Two };\n\nenum class ECoverState { Invalid = -1, IntoCover, Cover, Lean, OutOfCover };\n\nenum class ECoverDirection { Invalid = -1, Left, Right };\n\nenum class ETurnDirection { Invalid = -1, Right, Left };\n\nenum class EWallHangState {\n  Invalid = -1,\n  IntoJump,\n  JumpArc,\n  JumpAirLoop,\n  IntoWallHang,\n  WallHang,\n  Five,\n  OutOfWallHang,\n  OutOfWallHangTurn,\n  DetachJumpLoop,\n  DetachOutOfJump\n};\n} // namespace pas\n\nenum class EBodyType { Invalid, BiPedal, Restricted, Flyer, Pitchable, RestrictedFlyer, WallWalker, NewFlyer };\n\nenum class EBodyStateCmd {\n  Getup,\n  Step,\n  Die,\n  KnockDown,\n  KnockBack,\n  MeleeAttack,\n  ProjectileAttack,\n  LoopAttack,\n  LoopReaction,\n  LoopHitReaction,\n  ExitState,\n  LeanFromCover,\n  NextState,\n  MaintainVelocity,\n  Generate,\n  Hurled,\n  Jump,\n  Slide,\n  Taunt,\n  Scripted,\n  Cover,\n  WallHang,\n  Locomotion,\n  AdditiveIdle,\n  AdditiveAim,\n  AdditiveFlinch,\n  AdditiveReaction,\n  StopReaction\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/IAnimReader.cpp",
    "content": "#include \"Runtime/Character/IAnimReader.hpp\"\n\n#include \"Runtime/Character/CCharAnimTime.hpp\"\n\nnamespace metaforce {\n\nSAdvancementDeltas SAdvancementDeltas::Interpolate(const SAdvancementDeltas& a, const SAdvancementDeltas& b,\n                                                   float oldWeight, float newWeight) {\n  float weightSum = oldWeight + newWeight;\n  return {b.x0_posDelta * weightSum * 0.5f - a.x0_posDelta * (weightSum - 2.f) * 0.5f,\n          zeus::CQuaternion::slerpShort(a.xc_rotDelta, b.xc_rotDelta, weightSum * 0.5f)};\n}\n\nSAdvancementDeltas SAdvancementDeltas::Blend(const SAdvancementDeltas& a, const SAdvancementDeltas& b, float w) {\n  return {b.x0_posDelta * w - a.x0_posDelta * (1.f - w),\n          zeus::CQuaternion::slerpShort(a.xc_rotDelta, b.xc_rotDelta, w)};\n}\n\nSAdvancementResults IAnimReader::VGetAdvancementResults(const CCharAnimTime& a, const CCharAnimTime& b) const {\n  SAdvancementResults ret;\n  ret.x0_remTime = a;\n  return ret;\n}\n\nsize_t IAnimReader::GetBoolPOIList(const CCharAnimTime& time, CBoolPOINode* listOut, size_t capacity, size_t iterator,\n                                   u32 unk) const {\n  if (time.GreaterThanZero()) {\n    return VGetBoolPOIList(time, listOut, capacity, iterator, unk);\n  }\n  return 0;\n}\n\nsize_t IAnimReader::GetInt32POIList(const CCharAnimTime& time, CInt32POINode* listOut, size_t capacity, size_t iterator,\n                                    u32 unk) const {\n  if (time.GreaterThanZero()) {\n    return VGetInt32POIList(time, listOut, capacity, iterator, unk);\n  }\n  return 0;\n}\n\nsize_t IAnimReader::GetParticlePOIList(const CCharAnimTime& time, CParticlePOINode* listOut, size_t capacity,\n                                       size_t iterator, u32 unk) const {\n  if (time.GreaterThanZero()) {\n    return VGetParticlePOIList(time, listOut, capacity, iterator, unk);\n  }\n  return 0;\n}\n\nsize_t IAnimReader::GetSoundPOIList(const CCharAnimTime& time, CSoundPOINode* listOut, size_t capacity, size_t iterator,\n                                    u32 unk) const {\n  if (time.GreaterThanZero()) {\n    return VGetSoundPOIList(time, listOut, capacity, iterator, unk);\n  }\n  return 0;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/IAnimReader.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <optional>\n#include <string_view>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Character/CAllFormatsAnimSource.hpp\"\n#include \"Runtime/Character/CCharAnimTime.hpp\"\n#include \"Runtime/Character/CParticleData.hpp\"\n\n#include <zeus/CQuaternion.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CBoolPOINode;\nclass CInt32POINode;\nclass CParticlePOINode;\nclass CSegId;\nclass CSegIdList;\nclass CSegStatementSet;\nclass CSoundPOINode;\n\nstruct SAdvancementDeltas {\n  zeus::CVector3f x0_posDelta;\n  zeus::CQuaternion xc_rotDelta;\n\n  static SAdvancementDeltas Interpolate(const SAdvancementDeltas& a, const SAdvancementDeltas& b, float oldWeight,\n                                        float newWeight);\n  static SAdvancementDeltas Blend(const SAdvancementDeltas& a, const SAdvancementDeltas& b, float w);\n};\n\nstruct SAdvancementResults {\n  CCharAnimTime x0_remTime;\n  SAdvancementDeltas x8_deltas;\n};\n\nclass CSteadyStateAnimInfo {\n  CCharAnimTime x0_duration;\n  zeus::CVector3f x8_offset;\n  bool x14_looping = false;\n\npublic:\n  CSteadyStateAnimInfo(bool looping, const CCharAnimTime& duration, const zeus::CVector3f& offset)\n  : x0_duration(duration), x8_offset(offset), x14_looping(looping) {}\n\n  const CCharAnimTime& GetDuration() const { return x0_duration; }\n  const zeus::CVector3f& GetOffset() const { return x8_offset; }\n  bool IsLooping() const { return x14_looping; }\n};\n\nstruct CAnimTreeEffectiveContribution {\n  float x0_contributionWeight;\n  std::string x4_name;\n  CSteadyStateAnimInfo x14_ssInfo;\n  CCharAnimTime x2c_remTime;\n  u32 x34_dbIdx;\n\npublic:\n  CAnimTreeEffectiveContribution(float cweight, std::string_view name, const CSteadyStateAnimInfo& ssInfo,\n                                 const CCharAnimTime& remTime, u32 dbIdx)\n  : x0_contributionWeight(cweight), x4_name(name), x14_ssInfo(ssInfo), x2c_remTime(remTime), x34_dbIdx(dbIdx) {}\n  float GetContributionWeight() const { return x0_contributionWeight; }\n  std::string_view GetPrimitiveName() const { return x4_name; }\n  const CSteadyStateAnimInfo& GetSteadyStateAnimInfo() const { return x14_ssInfo; }\n  const CCharAnimTime& GetTimeRemaining() const { return x2c_remTime; }\n  u32 GetAnimDatabaseIndex() const { return x34_dbIdx; }\n};\n\ntemplate <class T>\nclass TSubAnimTypeToken : public TLockedToken<CAllFormatsAnimSource> {};\n\ntemplate <>\nclass TSubAnimTypeToken<CAnimSource> : public TLockedToken<CAnimSource> {\npublic:\n  // Converting constructor\n  TSubAnimTypeToken(const TLockedToken<CAllFormatsAnimSource>& token) : TLockedToken<CAnimSource>(token) {}\n\n  CAnimSource* GetObj() override {\n    CAllFormatsAnimSource* source = reinterpret_cast<CAllFormatsAnimSource*>(TLockedToken<CAnimSource>::GetObj());\n    return &source->GetAsCAnimSource();\n  }\n\n  const CAnimSource* GetObj() const override { return const_cast<TSubAnimTypeToken<CAnimSource>*>(this)->GetObj(); }\n};\n\ntemplate <>\nclass TSubAnimTypeToken<CFBStreamedCompression> : public TLockedToken<CFBStreamedCompression> {\npublic:\n  // Converting constructor\n  TSubAnimTypeToken(const TLockedToken<CAllFormatsAnimSource>& token) : TLockedToken<CFBStreamedCompression>(token) {}\n\n  CFBStreamedCompression* GetObj() override {\n    CAllFormatsAnimSource* source =\n        reinterpret_cast<CAllFormatsAnimSource*>(TLockedToken<CFBStreamedCompression>::GetObj());\n    return &source->GetAsCFBStreamedCompression();\n  }\n\n  const CFBStreamedCompression* GetObj() const override {\n    return const_cast<TSubAnimTypeToken<CFBStreamedCompression>*>(this)->GetObj();\n  }\n};\n\nclass IAnimReader {\npublic:\n  virtual ~IAnimReader() = default;\n  virtual bool IsCAnimTreeNode() const { return false; }\n  virtual SAdvancementResults VAdvanceView(const CCharAnimTime& a) = 0;\n  virtual CCharAnimTime VGetTimeRemaining() const = 0;\n  virtual CSteadyStateAnimInfo VGetSteadyStateAnimInfo() const = 0;\n  virtual bool VHasOffset(const CSegId& seg) const = 0;\n  virtual zeus::CVector3f VGetOffset(const CSegId& seg) const = 0;\n  virtual zeus::CQuaternion VGetRotation(const CSegId& seg) const = 0;\n  virtual size_t VGetBoolPOIList(const CCharAnimTime& time, CBoolPOINode* listOut, size_t capacity, size_t iterator,\n                                 u32) const = 0;\n  virtual size_t VGetInt32POIList(const CCharAnimTime& time, CInt32POINode* listOut, size_t capacity, size_t iterator,\n                                  u32) const = 0;\n  virtual size_t VGetParticlePOIList(const CCharAnimTime& time, CParticlePOINode* listOut, size_t capacity,\n                                     size_t iterator, u32) const = 0;\n  virtual size_t VGetSoundPOIList(const CCharAnimTime& time, CSoundPOINode* listOut, size_t capacity, size_t iterator,\n                                  u32) const = 0;\n  virtual bool VGetBoolPOIState(std::string_view name) const = 0;\n  virtual s32 VGetInt32POIState(std::string_view name) const = 0;\n  virtual CParticleData::EParentedMode VGetParticlePOIState(std::string_view name) const = 0;\n  virtual void VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut) const = 0;\n  virtual void VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut,\n                                   const CCharAnimTime& time) const = 0;\n  virtual std::unique_ptr<IAnimReader> VClone() const = 0;\n  virtual std::optional<std::unique_ptr<IAnimReader>> VSimplified() { return {}; }\n  virtual void VSetPhase(float) = 0;\n  virtual SAdvancementResults VGetAdvancementResults(const CCharAnimTime& a, const CCharAnimTime& b) const;\n\n  size_t GetBoolPOIList(const CCharAnimTime& time, CBoolPOINode* listOut, size_t capacity, size_t iterator, u32) const;\n  size_t GetInt32POIList(const CCharAnimTime& time, CInt32POINode* listOut, size_t capacity, size_t iterator,\n                         u32) const;\n  size_t GetParticlePOIList(const CCharAnimTime& time, CParticlePOINode* listOut, size_t capacity, size_t iterator,\n                            u32) const;\n  size_t GetSoundPOIList(const CCharAnimTime& time, CSoundPOINode* listOut, size_t capacity, size_t iterator,\n                         u32) const;\n\n  std::optional<std::unique_ptr<IAnimReader>> Simplified() { return VSimplified(); }\n\n  std::unique_ptr<IAnimReader> Clone() const { return VClone(); }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/IMetaAnim.cpp",
    "content": "#include \"Runtime/Character/IMetaAnim.hpp\"\n\n#include <array>\n\n#include \"Runtime/Character/CAnimTreeNode.hpp\"\n#include \"Runtime/Character/CBoolPOINode.hpp\"\n#include \"Runtime/Character/CCharAnimTime.hpp\"\n#include \"Runtime/Character/IAnimReader.hpp\"\n\nnamespace metaforce {\n\nstd::shared_ptr<CAnimTreeNode> IMetaAnim::GetAnimationTree(const CAnimSysContext& animSys,\n                                                           const CMetaAnimTreeBuildOrders& orders) const {\n  if (orders.x44_singleAdvance) {\n    std::shared_ptr<CAnimTreeNode> tree = VGetAnimationTree(animSys, CMetaAnimTreeBuildOrders::NoSpecialOrders());\n    if (orders.x44_singleAdvance->IsTime() || orders.x44_singleAdvance->IsString()) {\n      CCharAnimTime time = GetTime(*orders.x44_singleAdvance, *tree);\n      AdvanceAnim(*tree, time);\n    }\n    return tree;\n  }\n  return VGetAnimationTree(animSys, CMetaAnimTreeBuildOrders::NoSpecialOrders());\n}\n\nvoid IMetaAnim::AdvanceAnim(IAnimReader& anim, const CCharAnimTime& dt) {\n  CCharAnimTime remDt = dt;\n  while (remDt > CCharAnimTime()) {\n    SAdvancementResults res = anim.VAdvanceView(remDt);\n    remDt = res.x0_remTime;\n  }\n}\n\nCCharAnimTime IMetaAnim::GetTime(const CPreAdvanceIndicator& ind, const IAnimReader& anim) {\n  if (ind.IsTime()) {\n    return ind.GetTime();\n  }\n\n  std::array<CBoolPOINode, 64> nodes;\n  const CCharAnimTime rem = anim.VGetTimeRemaining();\n  const size_t count = anim.VGetBoolPOIList(rem, nodes.data(), nodes.size(), 0, 0);\n  const char* cmpStr = ind.GetString();\n  for (size_t i = 0; i < count; ++i) {\n    const CBoolPOINode& node = nodes[i];\n    if (node.GetString() != cmpStr || !node.GetValue()) {\n      continue;\n    }\n    return node.GetTime();\n  }\n\n  return {};\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/IMetaAnim.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <optional>\n#include <set>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Character/CCharAnimTime.hpp\"\n\nnamespace metaforce {\nclass CAnimTreeNode;\nclass CPrimitive;\nclass IAnimReader;\nstruct CAnimSysContext;\nstruct CMetaAnimTreeBuildOrders;\n\nenum class EMetaAnimType { Play, Blend, PhaseBlend, Random, Sequence };\n\nclass CPreAdvanceIndicator {\n  bool x0_isTime;\n  CCharAnimTime x4_time;\n  const char* xc_string;\n  /*\n  u32 x10_;\n  u32 x14_;\n  u32 x18_fireTime;\n  u32 x1c_damageDelay;\n  u32 x20_;\n  u32 x24_;\n  u32 x28_;\n  u32 x2c_;\n  u32 x30_;\n  u32 x34_;\n  u32 x38_;\n  u16 x3c_;\n  */\npublic:\n  explicit CPreAdvanceIndicator(const CCharAnimTime& time) : x0_isTime(true), x4_time(time) {}\n  explicit CPreAdvanceIndicator(const char* string) : x0_isTime(false), xc_string(string) {}\n  const char* GetString() const { return xc_string; }\n  bool IsString() const { return !x0_isTime; }\n  const CCharAnimTime& GetTime() const { return x4_time; }\n  bool IsTime() const { return x0_isTime; }\n};\n\nstruct CMetaAnimTreeBuildOrders {\n  std::optional<CPreAdvanceIndicator> x0_recursiveAdvance;\n  std::optional<CPreAdvanceIndicator> x44_singleAdvance;\n  static CMetaAnimTreeBuildOrders NoSpecialOrders() { return {}; }\n  static CMetaAnimTreeBuildOrders PreAdvanceForAll(const CPreAdvanceIndicator& ind) {\n    CMetaAnimTreeBuildOrders ret;\n    ret.x44_singleAdvance.emplace(ind);\n    return ret;\n  }\n};\n\nclass IMetaAnim {\npublic:\n  virtual ~IMetaAnim() = default;\n  virtual std::shared_ptr<CAnimTreeNode> GetAnimationTree(const CAnimSysContext& animSys,\n                                                          const CMetaAnimTreeBuildOrders& orders) const;\n  virtual void GetUniquePrimitives(std::set<CPrimitive>& primsOut) const = 0;\n  virtual EMetaAnimType GetType() const = 0;\n  virtual std::shared_ptr<CAnimTreeNode> VGetAnimationTree(const CAnimSysContext& animSys,\n                                                           const CMetaAnimTreeBuildOrders& orders) const = 0;\n\n  static void AdvanceAnim(IAnimReader& anim, const CCharAnimTime& dt);\n  static CCharAnimTime GetTime(const CPreAdvanceIndicator& ind, const IAnimReader& anim);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/IMetaTrans.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/RetroTypes.hpp\"\n\nnamespace metaforce {\nclass CAnimTreeNode;\nstruct CAnimSysContext;\n\nenum class EMetaTransType { MetaAnim, Trans, PhaseTrans, Snap };\n\nclass IMetaTrans {\npublic:\n  virtual ~IMetaTrans() = default;\n  virtual std::shared_ptr<CAnimTreeNode> VGetTransitionTree(const std::weak_ptr<CAnimTreeNode>& a,\n                                                            const std::weak_ptr<CAnimTreeNode>& b,\n                                                            const CAnimSysContext& animSys) const = 0;\n  virtual EMetaTransType GetType() const = 0;\n\n  std::shared_ptr<CAnimTreeNode> GetTransitionTree(const std::weak_ptr<CAnimTreeNode>& a,\n                                                   const std::weak_ptr<CAnimTreeNode>& b,\n                                                   const CAnimSysContext& animSys) {\n    return VGetTransitionTree(a, b, animSys);\n  }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/IVaryingAnimationTimeScale.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/Character/CCharAnimTime.hpp\"\n\nnamespace metaforce {\nclass IVaryingAnimationTimeScale {\npublic:\n  virtual ~IVaryingAnimationTimeScale() = default;\n  virtual u32 GetType() const = 0;\n  virtual float VTimeScaleIntegral(const float&, const float&) const = 0;\n  virtual float VFindUpperLimit(const float&, const float&) const = 0;\n  virtual std::unique_ptr<IVaryingAnimationTimeScale> VClone() const = 0;\n  virtual std::unique_ptr<IVaryingAnimationTimeScale> VGetFunctionMirrored(const float&) const = 0;\n  CCharAnimTime FindUpperLimit(const CCharAnimTime& a, const CCharAnimTime& b) const { return VFindUpperLimit(a, b); }\n\n  CCharAnimTime TimeScaleIntegral(const CCharAnimTime& a, const CCharAnimTime& b) const {\n    return VTimeScaleIntegral(a, b);\n  }\n\n  std::unique_ptr<IVaryingAnimationTimeScale> Clone() const { return VClone(); }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Character/TSegIdMap.hpp",
    "content": "#pragma once\n\n#include <array>\n#include <memory>\n#include <utility>\n\n#include \"Runtime/Character/CSegId.hpp\"\n\nnamespace metaforce {\n\ntemplate <class T>\nclass TSegIdMap {\n  CSegId x0_boneCount = 0;\n  CSegId x1_capacity = 0;\n  u32 x4_maxCapacity = 100;\n  std::array<std::pair<CSegId, CSegId>, 100> x8_indirectionMap;\n  std::unique_ptr<T[]> xd0_bones;\n  CSegId xd4_curPrevBone = 0;\n\npublic:\n  explicit TSegIdMap(const CSegId& capacity) : x1_capacity(capacity), xd0_bones(new T[capacity]) {}\n\n  T& operator[](const CSegId& id) { return SetElement(id); }\n  const T& operator[](const CSegId& id) const { return xd0_bones[x8_indirectionMap[id].second]; }\n\n  T& SetElement(const CSegId& id, T&& obj) {\n    size_t idx;\n\n    if (HasElement(id)) {\n      idx = x8_indirectionMap[id].second;\n    } else {\n      x8_indirectionMap[id] = std::make_pair(xd4_curPrevBone, x0_boneCount);\n      xd4_curPrevBone = id;\n      idx = x0_boneCount;\n      ++x0_boneCount;\n    }\n\n    xd0_bones[idx] = std::move(obj);\n    return xd0_bones[idx];\n  }\n\n  T& SetElement(const CSegId& id) {\n    size_t idx;\n\n    if (HasElement(id)) {\n      idx = x8_indirectionMap[id].second;\n    } else {\n      x8_indirectionMap[id] = std::make_pair(xd4_curPrevBone, x0_boneCount);\n      xd4_curPrevBone = id;\n      idx = x0_boneCount;\n      ++x0_boneCount;\n    }\n\n    return xd0_bones[idx];\n  }\n\n  void DelElement(const CSegId& id) {\n    if (!HasElement(id)) {\n      return;\n    }\n\n    if (id == xd4_curPrevBone) {\n      xd4_curPrevBone = x8_indirectionMap[id].first;\n    }\n\n    x8_indirectionMap[id] = {};\n    --x0_boneCount;\n  }\n\n  bool HasElement(const CSegId& id) const { return x8_indirectionMap[id].first.IsValid(); }\n\n  u32 GetCapacity() const { return x1_capacity; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CAABoxFilter.cpp",
    "content": "#include \"Runtime/Collision/CAABoxFilter.hpp\"\n\n#include \"Runtime/Collision/CCollisionInfoList.hpp\"\n#include \"Runtime/Collision/CollisionUtil.hpp\"\n\nnamespace metaforce {\n\nvoid CAABoxFilter::Filter(const CCollisionInfoList& in, CCollisionInfoList& out) const {\n  FilterBoxFloorCollisions(in, out);\n}\n\nvoid CAABoxFilter::FilterBoxFloorCollisions(const CCollisionInfoList& in, CCollisionInfoList& out) {\n  float minZ = 10000.f;\n  for (const CCollisionInfo& info : in) {\n    if (info.GetMaterialLeft().HasMaterial(EMaterialTypes::Wall) && info.GetPoint().z() < minZ)\n      minZ = info.GetPoint().z();\n  }\n  CCollisionInfoList temp;\n  for (const CCollisionInfo& info : in) {\n    if (info.GetMaterialLeft().HasMaterial(EMaterialTypes::Floor)) {\n      if (info.GetPoint().z() < minZ)\n        temp.Add(info, false);\n    } else {\n      temp.Add(info, false);\n    }\n  }\n  CollisionUtil::AddAverageToFront(temp, out);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CAABoxFilter.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Collision/ICollisionFilter.hpp\"\n\nnamespace metaforce {\nclass CCollisionInfoList;\n\nclass CAABoxFilter : public ICollisionFilter {\npublic:\n  explicit CAABoxFilter(CActor& actor) : ICollisionFilter(actor) {}\n  void Filter(const CCollisionInfoList& in, CCollisionInfoList& out) const override;\n  static void FilterBoxFloorCollisions(const CCollisionInfoList& in, CCollisionInfoList& out);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CAreaOctTree.cpp",
    "content": "#include \"Runtime/Collision/CAreaOctTree.hpp\"\n\n#include \"Runtime/CBasics.hpp\"\n#include \"Runtime/Collision/CMaterialFilter.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n\n#include <array>\n#include <cfloat>\n#include <cmath>\n#include <utility>\n\n#include <zeus/CVector2i.hpp>\n\nnamespace metaforce {\n\nstatic bool _close_enough(float f1, float f2, float epsilon) { return std::fabs(f1 - f2) <= epsilon; }\n\nstatic bool BoxLineTest(const zeus::CAABox& aabb, const zeus::CLine& line, float& lT, float& hT) {\n  zeus::simd_floats aabbMinF(aabb.min.mSimd);\n  zeus::simd_floats aabbMaxF(aabb.max.mSimd);\n  zeus::simd_floats lineOrigin(line.origin.mSimd);\n  zeus::simd_floats lineDir(line.dir.mSimd);\n  const float* aabbMin = aabbMinF.data();\n  const float* aabbMax = aabbMaxF.data();\n  const float* lorigin = lineOrigin.data();\n  const float* ldir = lineDir.data();\n  lT = -FLT_MAX;\n  hT = FLT_MAX;\n\n  for (int i = 0; i < 3; ++i) {\n    if (_close_enough(*ldir, 0.f, 0.000099999997f))\n      if (*lorigin < *aabbMin || *lorigin > *aabbMax)\n        return false;\n\n    if (*ldir < 0.f) {\n      if (*aabbMax - *lorigin < lT * *ldir)\n        lT = (*aabbMax - *lorigin) * 1.f / *ldir;\n      if (*aabbMin - *lorigin > hT * *ldir)\n        hT = (*aabbMin - *lorigin) * 1.f / *ldir;\n    } else {\n      if (*aabbMin - *lorigin > lT * *ldir)\n        lT = (*aabbMin - *lorigin) * 1.f / *ldir;\n      if (*aabbMax - *lorigin < hT * *ldir)\n        hT = (*aabbMax - *lorigin) * 1.f / *ldir;\n    }\n\n    ++aabbMin;\n    ++aabbMax;\n    ++lorigin;\n    ++ldir;\n  }\n\n  return lT <= hT;\n}\n\nconstexpr std::array SomeIndexA{1, 2, 4};\n\nconstexpr std::array SomeIndexB{1, 2, 0};\n\nconstexpr std::array<std::array<int, 8>, 8> SomeIndexC{{\n    {0, 1, 2, 4, 5, 6, 8, 0xA},\n    {0, 1, 2, 3, 5, 6, 8, 0xA},\n    {0, 1, 2, 4, 5, 6, 9, 0xB},\n    {0, 1, 2, 3, 5, 6, 9, 0xC},\n    {0, 1, 2, 4, 5, 7, 8, 0xD},\n    {0, 1, 2, 3, 5, 7, 8, 0xE},\n    {0, 1, 2, 4, 5, 7, 9, 0xF},\n    {0, 1, 2, 3, 5, 7, 9, 0xF},\n}};\n\nconstexpr std::array<std::pair<int, std::array<int, 3>>, 16> SubdivIndex{{\n    {0, {0, 0, 0}},\n    {1, {0, 0, 0}},\n    {1, {1, 0, 0}},\n    {2, {0, 1, 0}},\n    {2, {1, 0, 0}},\n    {1, {2, 0, 0}},\n    {2, {0, 2, 0}},\n    {2, {2, 0, 0}},\n    {2, {2, 1, 0}},\n    {2, {1, 2, 0}},\n    {3, {0, 2, 1}},\n    {3, {1, 0, 2}},\n    {3, {0, 1, 2}},\n    {3, {2, 1, 0}},\n    {3, {2, 0, 1}},\n    {3, {1, 2, 0}},\n}};\n\nbool CAreaOctTree::Node::LineTestInternal(const zeus::CLine& line, const CMaterialFilter& filter, float lT, float hT,\n                                          float maxT, const zeus::CVector3f& vec) const {\n  float lowT = (1.f - FLT_EPSILON * 100.f) * lT;\n  float highT = (1.f + FLT_EPSILON * 100.f) * hT;\n  if (maxT != 0.f) {\n    if (lowT < 0.f)\n      lowT = 0.f;\n    if (highT > maxT)\n      highT = maxT;\n    if (lowT > highT)\n      return true;\n  }\n\n  if (x20_nodeType == ETreeType::Leaf) {\n    TriListReference triList = GetTriangleArray();\n    for (u16 i = 0; i < triList.GetSize(); ++i) {\n      CCollisionSurface triangle = x1c_owner.GetMasterListTriangle(triList.GetAt(i));\n\n      // https://en.wikipedia.org/wiki/Möller–Trumbore_intersection_algorithm\n      // Find vectors for two edges sharing V0\n      zeus::CVector3f e0 = triangle.GetVert(1) - triangle.GetVert(0);\n      zeus::CVector3f e1 = triangle.GetVert(2) - triangle.GetVert(0);\n\n      // Begin calculating determinant - also used to calculate u parameter\n      zeus::CVector3f P = line.dir.cross(e1);\n      float det = P.dot(e0);\n\n      // If determinant is near zero, ray lies in plane of triangle\n      // or ray is parallel to plane of triangle\n      if (std::fabs(det) < (FLT_EPSILON * 10.f))\n        continue;\n      float invDet = 1.f / det;\n\n      // Calculate distance from V1 to ray origin\n      zeus::CVector3f T = line.origin - triangle.GetVert(0);\n\n      // Calculate u parameter and test bound\n      float u = invDet * T.dot(P);\n\n      // The intersection lies outside of the triangle\n      if (u < 0.f || u > 1.f)\n        continue;\n\n      // Prepare to test v parameter\n      zeus::CVector3f Q = T.cross(e0);\n\n      // Calculate T parameter and test bound\n      float t = invDet * Q.dot(e1);\n      if (t >= highT || t < lowT)\n        continue;\n\n      // Calculate V parameter and test bound\n      float v = invDet * Q.dot(line.dir);\n      if (v < 0.f || u + v > 1.f)\n        continue;\n\n      // Do material filter\n      CMaterialList matList(triangle.GetSurfaceFlags());\n      if (filter.Passes(matList))\n        return false;\n    }\n  } else if (x20_nodeType == ETreeType::Branch) {\n    if (GetChildFlags() == 0xA) // 2 leaves\n    {\n      for (int i = 0; i < 2; ++i) {\n        Node child = GetChild(i);\n        float tf1 = lT;\n        float tf2 = hT;\n        if (BoxLineTest(child.GetBoundingBox(), line, tf1, tf2))\n          if (!child.LineTestInternal(line, filter, tf1, tf2, maxT, vec))\n            return false;\n      }\n      return true;\n    }\n\n    zeus::CVector3f center = x0_aabb.center();\n\n    zeus::CVector3f r6 = line.origin + lT * line.dir;\n    zeus::CVector3f r7 = line.origin + hT * line.dir;\n    zeus::CVector3f r9 = vec * (center - line.origin);\n\n    int r28 = 0;\n    int r25 = 0;\n    int r26 = 0;\n    for (size_t i = 0; i < 3; ++i) {\n      if (r6[i] >= center[i])\n        r28 |= SomeIndexA[i];\n      if (r7[i] >= center[i])\n        r25 |= SomeIndexA[i];\n      if (r9[i] < r9[SomeIndexB[i]])\n        r26 |= SomeIndexA[i];\n    }\n\n    float f21 = lT;\n    int r26b = r28;\n    const std::pair<int, std::array<int, 3>>& idx = SubdivIndex[SomeIndexC[r26][r28 ^ r25]];\n    for (int i = 0; i <= idx.first; ++i) {\n      float f22 = (i < idx.first) ? r9[idx.second[i]] : hT;\n      if (f22 > lowT && f21 <= f22) {\n        Node child = GetChild(r26b);\n        if (child.x20_nodeType != ETreeType::Invalid)\n          if (!child.LineTestInternal(line, filter, f21, f22, maxT, vec))\n            return false;\n      }\n      if (i < idx.first)\n        r26b ^= 1 << idx.second[i];\n      f21 = f22;\n    }\n  }\n\n  return true;\n}\n\nvoid CAreaOctTree::Node::LineTestExInternal(const zeus::CLine& line, const CMaterialFilter& filter, SRayResult& res,\n                                            float lT, float hT, float maxT, const zeus::CVector3f& dirRecip) const {\n  float lowT = (1.f - FLT_EPSILON * 100.f) * lT;\n  float highT = (1.f + FLT_EPSILON * 100.f) * hT;\n  if (maxT != 0.f) {\n    if (lowT < 0.f)\n      lowT = 0.f;\n    if (highT > maxT)\n      highT = maxT;\n    if (lowT > highT)\n      return;\n  }\n\n  if (x20_nodeType == ETreeType::Leaf) {\n    TriListReference triList = GetTriangleArray();\n    float bestT = highT;\n    bool foundTriangle = false;\n    SRayResult tmpRes;\n\n    for (u16 i = 0; i < triList.GetSize(); ++i) {\n      CCollisionSurface triangle = x1c_owner.GetMasterListTriangle(triList.GetAt(i));\n\n      // https://en.wikipedia.org/wiki/Möller–Trumbore_intersection_algorithm\n      // Find vectors for two edges sharing V0\n      zeus::CVector3f e0 = triangle.GetVert(1) - triangle.GetVert(0);\n      zeus::CVector3f e1 = triangle.GetVert(2) - triangle.GetVert(0);\n\n      // Begin calculating determinant - also used to calculate u parameter\n      zeus::CVector3f P = line.dir.cross(e1);\n      float det = P.dot(e0);\n\n      // If determinant is near zero, ray lies in plane of triangle\n      // or ray is parallel to plane of triangle\n      if (std::fabs(det) < (FLT_EPSILON * 10.f))\n        continue;\n      float invDet = 1.f / det;\n\n      // Calculate distance from V1 to ray origin\n      zeus::CVector3f T = line.origin - triangle.GetVert(0);\n\n      // Calculate u parameter and test bound\n      float u = invDet * T.dot(P);\n\n      // The intersection lies outside of the triangle\n      if (u < 0.f || u > 1.f)\n        continue;\n\n      // Prepare to test v parameter\n      zeus::CVector3f Q = T.cross(e0);\n\n      // Calculate T parameter and test bound\n      float t = invDet * Q.dot(e1);\n      if (t >= bestT || t < lowT)\n        continue;\n\n      // Calculate V parameter and test bound\n      float v = invDet * Q.dot(line.dir);\n      if (v < 0.f || u + v > 1.f)\n        continue;\n\n      // Do material filter\n      CMaterialList matList(triangle.GetSurfaceFlags());\n      if (filter.Passes(matList) && t <= bestT) {\n        bestT = t;\n        foundTriangle = true;\n        tmpRes.x10_surface.emplace(triangle);\n        tmpRes.x3c_t = t;\n      }\n    }\n\n    if (foundTriangle) {\n      res = tmpRes;\n      res.x0_plane = res.x10_surface->GetPlane();\n    }\n  } else if (x20_nodeType == ETreeType::Branch) {\n    if (GetChildFlags() == 0xA) // 2 leaves\n    {\n      std::array<SRayResult, 2> tmpRes;\n      for (int i = 0; i < 2; ++i) {\n        Node child = GetChild(i);\n        float tf1 = lT;\n        float tf2 = hT;\n        if (BoxLineTest(child.GetBoundingBox(), line, tf1, tf2))\n          child.LineTestExInternal(line, filter, tmpRes[i], tf1, tf2, maxT, dirRecip);\n      }\n\n      if (!tmpRes[0].x10_surface && !tmpRes[1].x10_surface) {\n        res = SRayResult();\n      } else if (tmpRes[0].x10_surface && tmpRes[1].x10_surface) {\n        if (tmpRes[0].x3c_t < tmpRes[1].x3c_t)\n          res = tmpRes[0];\n        else\n          res = tmpRes[1];\n      } else if (tmpRes[0].x10_surface) {\n        res = tmpRes[0];\n      } else {\n        res = tmpRes[1];\n      }\n\n      if (res.x3c_t > highT)\n        res = SRayResult();\n\n      return;\n    }\n\n    zeus::CVector3f center = x0_aabb.center(); // r26\n\n    zeus::CVector3f lowPoint = line.origin + lT * line.dir;\n    zeus::CVector3f highPoint = line.origin + hT * line.dir;\n    std::array<int, 4> comps{-1, -1, -1, 0};\n    std::array<float, 3> compT;\n\n    int numComps = 0;\n    for (size_t i = 0; i < compT.size(); ++i) {\n      if (lowPoint[i] >= center[i] || highPoint[i] <= center[i]) {\n        if (highPoint[i] >= center[i] || lowPoint[i] <= center[i]) {\n          continue;\n        }\n      }\n      if (_close_enough(line.dir[i], 0.f, 0.000099999997f)) {\n        continue;\n      }\n      comps[numComps++] = static_cast<int>(i);\n      compT[i] = dirRecip[i] * (center[i] - line.origin[i]);\n    }\n\n    // Sort componentT least to greatest\n    switch (numComps) {\n    default:\n      return;\n    case 0:\n    case 1:\n      break;\n    case 2:\n      if (compT[comps[1]] < compT[comps[0]])\n        std::swap(comps[1], comps[0]);\n      break;\n    case 3:\n      if (compT[0] < compT[1]) {\n        if (compT[0] >= compT[2]) {\n          comps[0] = 2;\n          comps[1] = 0;\n          comps[2] = 1;\n        } else if (compT[1] < compT[2]) {\n          comps[0] = 0;\n          comps[1] = 1;\n          comps[2] = 2;\n        } else {\n          comps[0] = 0;\n          comps[1] = 2;\n          comps[2] = 1;\n        }\n      } else {\n        if (compT[1] >= compT[2]) {\n          comps[0] = 2;\n          comps[1] = 1;\n          comps[2] = 0;\n        } else if (compT[0] < compT[2]) {\n          comps[0] = 1;\n          comps[1] = 0;\n          comps[2] = 2;\n        } else {\n          comps[0] = 1;\n          comps[1] = 2;\n          comps[2] = 0;\n        }\n      }\n      break;\n    }\n\n    zeus::CVector3f lineStart = line.origin + (lT * line.dir);\n    int selector = 0;\n    if (lineStart.x() >= center.x())\n      selector = 1;\n    if (lineStart.y() >= center.y())\n      selector |= 1 << 1;\n    if (lineStart.z() >= center.z())\n      selector |= 1 << 2;\n\n    float tmpLoT = lT;\n    for (int i = -1; i < numComps; ++i) {\n      if (i >= 0)\n        selector ^= 1 << comps[i];\n      float tmpHiT = (i < numComps - 1) ? compT[comps[i + 1]] : hT;\n      if (tmpHiT > lowT && tmpLoT <= tmpHiT) {\n        Node child = GetChild(selector);\n        if (child.x20_nodeType != ETreeType::Invalid)\n          child.LineTestExInternal(line, filter, res, tmpLoT, tmpHiT, maxT, dirRecip);\n        if (res.x10_surface) {\n          if (res.x3c_t > highT)\n            res = SRayResult();\n          break;\n        }\n      }\n      tmpLoT = tmpHiT;\n    }\n  }\n}\n\nbool CAreaOctTree::Node::LineTest(const zeus::CLine& line, const CMaterialFilter& filter, float length) const {\n  if (x20_nodeType == ETreeType::Invalid)\n    return true;\n\n  float f1 = 0.f;\n  float f2 = 0.f;\n  if (!BoxLineTest(x0_aabb, line, f1, f2))\n    return true;\n\n  zeus::CVector3f recip = 1.f / line.dir;\n  return LineTestInternal(line, filter, f1 - 0.000099999997f, f2 + 0.000099999997f, length, recip);\n}\n\nvoid CAreaOctTree::Node::LineTestEx(const zeus::CLine& line, const CMaterialFilter& filter, SRayResult& res,\n                                    float length) const {\n  if (x20_nodeType == ETreeType::Invalid)\n    return;\n\n  float lT = 0.f;\n  float hT = 0.f;\n  if (!BoxLineTest(x0_aabb, line, lT, hT))\n    return;\n\n  zeus::CVector3f recip = 1.f / line.dir;\n  LineTestExInternal(line, filter, res, lT - 0.000099999997f, hT + 0.000099999997f, length, recip);\n}\n\nCAreaOctTree::Node CAreaOctTree::Node::GetChild(int idx) const {\n  u16 flags = *reinterpret_cast<const u16*>(x18_ptr);\n  const u32* offsets = reinterpret_cast<const u32*>(x18_ptr + 4);\n  ETreeType type = ETreeType((flags >> (2 * idx)) & 0x3);\n\n  if (type == ETreeType::Branch) {\n    zeus::CAABox pos, neg, res;\n    x0_aabb.splitZ(neg, pos);\n    if (idx & 4) {\n      zeus::CAABox(pos).splitY(neg, pos);\n      if (idx & 2) {\n        zeus::CAABox(pos).splitX(neg, pos);\n        if (idx & 1)\n          res = pos;\n        else\n          res = neg;\n      } else {\n        zeus::CAABox(neg).splitX(neg, pos);\n        if (idx & 1)\n          res = pos;\n        else\n          res = neg;\n      }\n    } else {\n      zeus::CAABox(neg).splitY(neg, pos);\n      if (idx & 2) {\n        zeus::CAABox(pos).splitX(neg, pos);\n        if (idx & 1)\n          res = pos;\n        else\n          res = neg;\n      } else {\n        zeus::CAABox(neg).splitX(neg, pos);\n        if (idx & 1)\n          res = pos;\n        else\n          res = neg;\n      }\n    }\n\n    return Node(x18_ptr + offsets[idx] + 36, res, x1c_owner, ETreeType::Branch);\n  } else if (type == ETreeType::Leaf) {\n    const float* aabb = reinterpret_cast<const float*>(x18_ptr + offsets[idx] + 36);\n    zeus::CAABox aabbObj(aabb[0], aabb[1], aabb[2], aabb[3], aabb[4], aabb[5]);\n    return Node(aabb, aabbObj, x1c_owner, ETreeType::Leaf);\n  } else {\n    return Node(nullptr, zeus::skNullBox, x1c_owner, ETreeType::Invalid);\n  }\n}\n\nvoid CAreaOctTree::SwapTreeNode(u8* ptr, Node::ETreeType type) {\n  if (type == Node::ETreeType::Branch) {\n    u16* typeBits = reinterpret_cast<u16*>(ptr);\n    *typeBits = CBasics::SwapBytes(*typeBits);\n    u32* offsets = reinterpret_cast<u32*>(ptr + 4);\n\n    for (int i = 0; i < 8; ++i) {\n      Node::ETreeType ctype = Node::ETreeType((*typeBits >> (2 * i)) & 0x3);\n      offsets[i] = CBasics::SwapBytes(offsets[i]);\n      SwapTreeNode(ptr + offsets[i] + 36, ctype);\n    }\n  } else if (type == Node::ETreeType::Leaf) {\n    float* aabb = reinterpret_cast<float*>(ptr);\n    aabb[0] = CBasics::SwapBytes(aabb[0]);\n    aabb[1] = CBasics::SwapBytes(aabb[1]);\n    aabb[2] = CBasics::SwapBytes(aabb[2]);\n    aabb[3] = CBasics::SwapBytes(aabb[3]);\n    aabb[4] = CBasics::SwapBytes(aabb[4]);\n    aabb[5] = CBasics::SwapBytes(aabb[5]);\n\n    u16* countIdxs = reinterpret_cast<u16*>(ptr + 24);\n    *countIdxs = CBasics::SwapBytes(*countIdxs);\n    for (u16 i = 0; i < *countIdxs; ++i)\n      countIdxs[i + 1] = CBasics::SwapBytes(countIdxs[i + 1]);\n  }\n}\n\nCAreaOctTree::CAreaOctTree(const zeus::CAABox& aabb, Node::ETreeType treeType, const u8* buf, const u8* treeBuf,\n                           u32 matCount, const u32* materials, const u8* vertMats, const u8* edgeMats,\n                           const u8* polyMats, u32 edgeCount, const CCollisionEdge* edges, u32 polyCount,\n                           const u16* polyEdges, u32 vertCount, const float* verts)\n: x0_aabb(aabb)\n, x18_treeType(treeType)\n, x1c_buf(buf)\n, x20_treeBuf(treeBuf)\n, x24_matCount(matCount)\n, x28_materials(materials)\n, x2c_vertMats(vertMats)\n, x30_edgeMats(edgeMats)\n, x34_polyMats(polyMats)\n, x38_edgeCount(edgeCount)\n, x3c_edges(edges)\n, x40_polyCount(polyCount)\n, x44_polyEdges(polyEdges)\n, x48_vertCount(vertCount)\n, x4c_verts(verts) {\n  SwapTreeNode(const_cast<u8*>(x20_treeBuf), treeType);\n\n  for (u32 i = 0; i < matCount; ++i)\n    const_cast<u32*>(x28_materials)[i] = CBasics::SwapBytes(x28_materials[i]);\n\n  for (u32 i = 0; i < edgeCount; ++i)\n    const_cast<CCollisionEdge*>(x3c_edges)[i].swapBig();\n\n  for (u32 i = 0; i < polyCount; ++i)\n    const_cast<u16*>(x44_polyEdges)[i] = CBasics::SwapBytes(x44_polyEdges[i]);\n\n  for (u32 i = 0; i < vertCount * 3; ++i)\n    const_cast<float*>(x4c_verts)[i] = CBasics::SwapBytes(x4c_verts[i]);\n}\n\nstd::unique_ptr<CAreaOctTree> CAreaOctTree::MakeFromMemory(const u8* buf, unsigned int size) {\n  CMemoryInStream r(buf + 8, size - 8, CMemoryInStream::EOwnerShip::NotOwned);\n  r.ReadLong();\n  r.ReadLong();\n  zeus::CAABox aabb = r.Get<zeus::CAABox>();\n  Node::ETreeType nodeType = Node::ETreeType(r.ReadLong());\n  u32 treeSize = r.ReadLong();\n  const u8* cur = reinterpret_cast<const u8*>(buf) + 8 + r.GetReadPosition();\n\n  const u8* treeBuf = cur;\n  cur += treeSize;\n\n  u32 matCount = CBasics::SwapBytes(*reinterpret_cast<const u32*>(cur));\n  cur += 4;\n  const u32* matBuf = reinterpret_cast<const u32*>(cur);\n  cur += 4 * matCount;\n\n  u32 vertMatsCount = CBasics::SwapBytes(*reinterpret_cast<const u32*>(cur));\n  cur += 4;\n  const u8* vertMatsBuf = cur;\n  cur += vertMatsCount;\n\n  u32 edgeMatsCount = CBasics::SwapBytes(*reinterpret_cast<const u32*>(cur));\n  cur += 4;\n  const u8* edgeMatsBuf = cur;\n  cur += edgeMatsCount;\n\n  u32 polyMatsCount = CBasics::SwapBytes(*reinterpret_cast<const u32*>(cur));\n  cur += 4;\n  const u8* polyMatsBuf = cur;\n  cur += polyMatsCount;\n\n  u32 edgeCount = CBasics::SwapBytes(*reinterpret_cast<const u32*>(cur));\n  cur += 4;\n  const CCollisionEdge* edgeBuf = reinterpret_cast<const CCollisionEdge*>(cur);\n  cur += edgeCount * sizeof(edgeCount);\n\n  u32 polyCount = CBasics::SwapBytes(*reinterpret_cast<const u32*>(cur));\n  cur += 4;\n  const u16* polyBuf = reinterpret_cast<const u16*>(cur);\n  cur += polyCount * 2;\n\n  u32 vertCount = CBasics::SwapBytes(*reinterpret_cast<const u32*>(cur));\n  cur += 4;\n  const float* vertBuf = reinterpret_cast<const float*>(cur);\n\n  return std::make_unique<CAreaOctTree>(aabb, nodeType, reinterpret_cast<const u8*>(buf + 8), treeBuf, matCount, matBuf,\n                                        vertMatsBuf, edgeMatsBuf, polyMatsBuf, edgeCount, edgeBuf, polyCount, polyBuf,\n                                        vertCount, vertBuf);\n}\n\nCCollisionSurface CAreaOctTree::GetMasterListTriangle(u16 idx) const {\n  const CCollisionEdge& e0 = x3c_edges[x44_polyEdges[idx * 3]];\n  const CCollisionEdge& e1 = x3c_edges[x44_polyEdges[idx * 3 + 1]];\n  u16 vert2 = e1.GetVertIndex2();\n  if (e1.GetVertIndex1() != e0.GetVertIndex1())\n    if (e1.GetVertIndex1() != e0.GetVertIndex2())\n      vert2 = e1.GetVertIndex1();\n\n  u32 material = x28_materials[x34_polyMats[idx]];\n  if (material & 0x2000000)\n    return CCollisionSurface(GetVert(e0.GetVertIndex2()), GetVert(e0.GetVertIndex1()), GetVert(vert2), material);\n  else\n    return CCollisionSurface(GetVert(e0.GetVertIndex1()), GetVert(e0.GetVertIndex2()), GetVert(vert2), material);\n}\n\nvoid CAreaOctTree::GetTriangleVertexIndices(u16 idx, u16 indicesOut[3]) const {\n  const CCollisionEdge& e0 = x3c_edges[x44_polyEdges[idx * 3]];\n  const CCollisionEdge& e1 = x3c_edges[x44_polyEdges[idx * 3 + 1]];\n  indicesOut[2] = (e1.GetVertIndex1() != e0.GetVertIndex1() && e1.GetVertIndex1() != e0.GetVertIndex2())\n                      ? e1.GetVertIndex1()\n                      : e1.GetVertIndex2();\n\n  u32 material = x28_materials[x34_polyMats[idx]];\n  if (material & 0x2000000) {\n    indicesOut[0] = e0.GetVertIndex2();\n    indicesOut[1] = e0.GetVertIndex1();\n  } else {\n    indicesOut[0] = e0.GetVertIndex1();\n    indicesOut[1] = e0.GetVertIndex2();\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CAreaOctTree.hpp",
    "content": "#pragma once\n\n#include <optional>\n#include <memory>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Collision/CCollisionEdge.hpp\"\n#include \"Runtime/Collision/CCollisionSurface.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CLine.hpp>\n#include <zeus/CPlane.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CMaterialFilter;\nclass CAreaOctTree {\n  friend class CBooRenderer;\n\npublic:\n  struct SRayResult {\n    zeus::CPlane x0_plane;\n    std::optional<CCollisionSurface> x10_surface;\n    float x3c_t;\n  };\n\n  class TriListReference {\n    const u16* m_ptr;\n\n  public:\n    explicit TriListReference(const u16* ptr) : m_ptr(ptr) {}\n    u16 GetAt(int idx) const { return m_ptr[idx + 1]; }\n    u16 GetSize() const { return m_ptr[0]; }\n  };\n\n  class Node {\n  public:\n    enum class ETreeType { Invalid, Branch, Leaf };\n\n  private:\n    zeus::CAABox x0_aabb;\n    const u8* x18_ptr;\n    const CAreaOctTree& x1c_owner;\n    ETreeType x20_nodeType;\n\n    bool LineTestInternal(const zeus::CLine& line, const CMaterialFilter& filter, float lT, float hT, float maxT,\n                          const zeus::CVector3f& vec) const;\n    void LineTestExInternal(const zeus::CLine& line, const CMaterialFilter& filter, SRayResult& res, float lT, float hT,\n                            float maxT, const zeus::CVector3f& dirRecip) const;\n\n  public:\n    Node(const void* ptr, const zeus::CAABox& aabb, const CAreaOctTree& owner, ETreeType type)\n    : x0_aabb(aabb), x18_ptr(reinterpret_cast<const u8*>(ptr)), x1c_owner(owner), x20_nodeType(type) {}\n\n    bool LineTest(const zeus::CLine& line, const CMaterialFilter& filter, float length) const;\n    void LineTestEx(const zeus::CLine& line, const CMaterialFilter& filter, SRayResult& res, float length) const;\n\n    const CAreaOctTree& GetOwner() const { return x1c_owner; }\n\n    const zeus::CAABox& GetBoundingBox() const { return x0_aabb; }\n\n    u16 GetChildFlags() const { return *reinterpret_cast<const u16*>(x18_ptr); }\n\n    Node GetChild(int idx) const;\n\n    TriListReference GetTriangleArray() const { return TriListReference(reinterpret_cast<const u16*>(x18_ptr + 24)); }\n\n    ETreeType GetChildType(int idx) const {\n      u16 flags = *reinterpret_cast<const u16*>(x18_ptr);\n      return ETreeType((flags >> (2 * idx)) & 0x3);\n    }\n\n    ETreeType GetTreeType() const { return x20_nodeType; }\n  };\n\n  zeus::CAABox x0_aabb;\n  Node::ETreeType x18_treeType;\n  const u8* x1c_buf;\n  const u8* x20_treeBuf;\n  u32 x24_matCount;\n  const u32* x28_materials;\n  const u8* x2c_vertMats;\n  const u8* x30_edgeMats;\n  const u8* x34_polyMats;\n  u32 x38_edgeCount;\n  const CCollisionEdge* x3c_edges;\n  u32 x40_polyCount;\n  const u16* x44_polyEdges;\n  u32 x48_vertCount;\n  const float* x4c_verts;\n\n  void SwapTreeNode(u8* ptr, Node::ETreeType type);\n\npublic:\n  CAreaOctTree(const zeus::CAABox& aabb, Node::ETreeType treeType, const u8* buf, const u8* treeBuf, u32 matCount,\n               const u32* materials, const u8* vertMats, const u8* edgeMats, const u8* polyMats, u32 edgeCount,\n               const CCollisionEdge* edges, u32 polyCount, const u16* polyEdges, u32 vertCount, const float* verts);\n\n  const zeus::CAABox& GetAABB() const { return x0_aabb; }\n  Node GetRootNode() const { return Node(x20_treeBuf, x0_aabb, *this, x18_treeType); }\n  const u8* GetTreeMemory() const { return x20_treeBuf; }\n  zeus::CVector3f GetVert(int idx) const {\n    const float* vert = &x4c_verts[idx * 3];\n    return zeus::CVector3f(vert[0], vert[1], vert[2]);\n  }\n  const CCollisionEdge& GetEdge(int idx) const { return x3c_edges[idx]; }\n  u32 GetVertMaterial(int idx) const { return x28_materials[x2c_vertMats[idx]]; }\n  u32 GetEdgeMaterial(int idx) const { return x28_materials[x30_edgeMats[idx]]; }\n  u32 GetTriangleMaterial(int idx) const { return x28_materials[x34_polyMats[idx]]; }\n  u32 GetNumEdges() const { return x38_edgeCount; }\n  u32 GetNumVerts() const { return x48_vertCount; }\n  u32 GetNumTriangles() const { return x40_polyCount; }\n  CCollisionSurface GetMasterListTriangle(u16 idx) const;\n  void GetTriangleVertexIndices(u16 idx, u16 indicesOut[3]) const;\n  const u16* GetTriangleEdgeIndices(u16 idx) const { return &x44_polyEdges[idx * 3]; }\n\n  static std::unique_ptr<CAreaOctTree> MakeFromMemory(const u8* buf, unsigned int size);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CBallFilter.cpp",
    "content": "#include \"Runtime/Collision/CBallFilter.hpp\"\n#include \"Runtime/Collision/CollisionUtil.hpp\"\n\nnamespace metaforce {\n\nvoid CBallFilter::Filter(const CCollisionInfoList& in, CCollisionInfoList& out) const {\n  CollisionUtil::AddAverageToFront(in, out);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CBallFilter.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Collision/ICollisionFilter.hpp\"\n\nnamespace metaforce {\nclass CCollisionInfoList;\nclass CPhysicsActor;\n\nclass CBallFilter : public ICollisionFilter {\npublic:\n  explicit CBallFilter(CActor& actor) : ICollisionFilter(actor) {}\n  void Filter(const CCollisionInfoList& in, CCollisionInfoList& out) const override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CCollidableAABox.cpp",
    "content": "#include \"Runtime/Collision/CCollidableAABox.hpp\"\n\n#include \"Runtime/Collision/CCollidableSphere.hpp\"\n#include \"Runtime/Collision/CCollisionInfo.hpp\"\n#include \"Runtime/Collision/CInternalRayCastStructure.hpp\"\n#include \"Runtime/Collision/CollisionUtil.hpp\"\n\nnamespace metaforce {\nconstexpr CCollisionPrimitive::Type sType(CCollidableAABox::SetStaticTableIndex, \"CCollidableAABox\");\n\nCCollidableAABox::CCollidableAABox() = default;\n\nCCollidableAABox::CCollidableAABox(const zeus::CAABox& aabox, const CMaterialList& list)\n: CCollisionPrimitive(list), x10_aabox(aabox) {}\n\nzeus::CAABox CCollidableAABox::Transform(const zeus::CTransform& xf) const {\n  return {xf.origin + x10_aabox.min, xf.origin + x10_aabox.max};\n}\n\nu32 CCollidableAABox::GetTableIndex() const { return sTableIndex; }\n\nzeus::CAABox CCollidableAABox::CalculateAABox(const zeus::CTransform& xf) const { return Transform(xf); }\n\nzeus::CAABox CCollidableAABox::CalculateLocalAABox() const { return x10_aabox; }\n\nFourCC CCollidableAABox::GetPrimType() const { return SBIG('AABX'); }\n\nCRayCastResult CCollidableAABox::CastRayInternal(const CInternalRayCastStructure& rayCast) const {\n  if (!rayCast.GetFilter().Passes(GetMaterial()))\n    return {};\n  zeus::CTransform rayCastXfInv = rayCast.GetTransform().inverse();\n  zeus::CVector3f localRayStart = rayCastXfInv * rayCast.GetRay().start;\n  zeus::CVector3f localRayDir = rayCastXfInv.rotate(rayCast.GetRay().dir);\n  float tMin, tMax;\n  int axis;\n  bool sign;\n  if (!CollisionUtil::BoxLineTest(x10_aabox, localRayStart, localRayDir, tMin, tMax, axis, sign) || tMin < 0.f ||\n      (rayCast.GetMaxTime() > 0.f && tMin > rayCast.GetMaxTime()))\n    return {};\n\n  zeus::CVector3f planeNormal;\n  planeNormal[axis] = sign ? 1.f : -1.f;\n  float planeD = axis ? x10_aabox.min[axis] : -x10_aabox.max[axis];\n  CRayCastResult result(tMin, localRayStart + tMin * localRayDir, zeus::CPlane(planeNormal, planeD), GetMaterial());\n  result.Transform(rayCast.GetTransform());\n  return result;\n}\n\nconst CCollisionPrimitive::Type& CCollidableAABox::GetType() { return sType; }\n\nvoid CCollidableAABox::SetStaticTableIndex(u32 index) { sTableIndex = index; }\n\nbool CCollidableAABox::CollideMovingAABox(const CInternalCollisionStructure& collision, const zeus::CVector3f& dir,\n                                          double& dOut, CCollisionInfo& infoOut) {\n  const CCollidableAABox& p0 = static_cast<const CCollidableAABox&>(collision.GetLeft().GetPrim());\n  const CCollidableAABox& p1 = static_cast<const CCollidableAABox&>(collision.GetRight().GetPrim());\n\n  zeus::CAABox b0 = p0.Transform(collision.GetLeft().GetTransform());\n  zeus::CAABox b1 = p1.Transform(collision.GetRight().GetTransform());\n\n  double d;\n  zeus::CVector3f point, normal;\n  if (CollisionUtil::AABox_AABox_Moving(b0, b1, dir, d, point, normal) && d > 0.0 && d < dOut) {\n    dOut = d;\n    infoOut = CCollisionInfo(point, p0.GetMaterial(), p1.GetMaterial(), normal, -normal);\n    return true;\n  }\n\n  return false;\n}\n\nbool CCollidableAABox::CollideMovingSphere(const CInternalCollisionStructure& collision, const zeus::CVector3f& dir,\n                                           double& dOut, CCollisionInfo& infoOut) {\n  const CCollidableAABox& p0 = static_cast<const CCollidableAABox&>(collision.GetLeft().GetPrim());\n  const CCollidableSphere& p1 = static_cast<const CCollidableSphere&>(collision.GetRight().GetPrim());\n\n  zeus::CAABox b0 = p0.CalculateAABox(collision.GetLeft().GetTransform());\n  zeus::CSphere s1 = p1.Transform(collision.GetRight().GetTransform());\n\n  double d = dOut;\n  zeus::CVector3f point, normal;\n  if (CollisionUtil::MovingSphereAABox(s1, b0, -dir, d, point, normal) && d < dOut) {\n    point = s1.position - s1.radius * normal;\n    dOut = d;\n    infoOut = CCollisionInfo(point, p0.GetMaterial(), p1.GetMaterial(), -normal);\n    return true;\n  }\n\n  return false;\n}\n\nnamespace Collide {\n\nbool AABox_AABox(const CInternalCollisionStructure& collision, CCollisionInfoList& list) {\n  const CCollidableAABox& box0 = static_cast<const CCollidableAABox&>(collision.GetLeft().GetPrim());\n  const CCollidableAABox& box1 = static_cast<const CCollidableAABox&>(collision.GetRight().GetPrim());\n\n  zeus::CAABox aabb0 = box0.Transform(collision.GetLeft().GetTransform());\n  zeus::CAABox aabb1 = box1.Transform(collision.GetRight().GetTransform());\n\n  return CollisionUtil::AABoxAABoxIntersection(aabb0, box0.GetMaterial(), aabb1, box1.GetMaterial(), list);\n}\n\nbool AABox_AABox_Bool(const CInternalCollisionStructure& collision) {\n  const CCollidableAABox& box0 = static_cast<const CCollidableAABox&>(collision.GetLeft().GetPrim());\n  const CCollidableAABox& box1 = static_cast<const CCollidableAABox&>(collision.GetRight().GetPrim());\n\n  zeus::CAABox aabb0 = box0.Transform(collision.GetLeft().GetTransform());\n  zeus::CAABox aabb1 = box1.Transform(collision.GetRight().GetTransform());\n\n  return CollisionUtil::AABoxAABoxIntersection(aabb0, aabb1);\n}\n\n} // namespace Collide\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CCollidableAABox.hpp",
    "content": "#pragma once\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/Collision/CCollisionPrimitive.hpp\"\n\n#include <zeus/CAABox.hpp>\n\nnamespace metaforce {\nnamespace Collide {\nbool AABox_AABox(const CInternalCollisionStructure&, CCollisionInfoList&);\nbool AABox_AABox_Bool(const CInternalCollisionStructure&);\n} // namespace Collide\n\nclass CCollidableAABox : public CCollisionPrimitive {\n  static inline u32 sTableIndex = UINT32_MAX;\n\n  zeus::CAABox x10_aabox;\n\npublic:\n  CCollidableAABox();\n  CCollidableAABox(const zeus::CAABox&, const CMaterialList&);\n\n  zeus::CAABox Transform(const zeus::CTransform&) const;\n  u32 GetTableIndex() const override;\n  zeus::CAABox CalculateAABox(const zeus::CTransform&) const override;\n  zeus::CAABox CalculateLocalAABox() const override;\n  FourCC GetPrimType() const override;\n  CRayCastResult CastRayInternal(const CInternalRayCastStructure&) const override;\n  const zeus::CAABox& GetBox() const { return x10_aabox; }\n  zeus::CAABox& Box() { return x10_aabox; }\n  void SetBox(const zeus::CAABox& box) { x10_aabox = box; }\n\n  static const CCollisionPrimitive::Type& GetType();\n  static void SetStaticTableIndex(u32 index);\n  static bool CollideMovingAABox(const CInternalCollisionStructure&, const zeus::CVector3f&, double&, CCollisionInfo&);\n  static bool CollideMovingSphere(const CInternalCollisionStructure&, const zeus::CVector3f&, double&, CCollisionInfo&);\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CCollidableCollisionSurface.cpp",
    "content": "#include \"Runtime/Collision/CCollidableCollisionSurface.hpp\"\n\nnamespace metaforce {\nconstexpr CCollisionPrimitive::Type sType(CCollidableCollisionSurface::SetStaticTableIndex,\n                                          \"CCollidableCollisionSurface\");\n\nconst CCollisionPrimitive::Type& CCollidableCollisionSurface::GetType() { return sType; }\n\nvoid CCollidableCollisionSurface::SetStaticTableIndex(u32 index) { sTableIndex = index; }\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CCollidableCollisionSurface.hpp",
    "content": "#pragma once\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/Collision/CCollisionPrimitive.hpp\"\n\nnamespace metaforce {\nclass CCollidableCollisionSurface {\n  static inline u32 sTableIndex = UINT32_MAX;\n\npublic:\n  static const CCollisionPrimitive::Type& GetType();\n  static void SetStaticTableIndex(u32 index);\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CCollidableOBBTree.cpp",
    "content": "#include \"Runtime/Collision/CCollidableOBBTree.hpp\"\n\n#include <array>\n\n#include \"Runtime/Collision/CCollisionInfoList.hpp\"\n#include \"Runtime/Collision/CInternalRayCastStructure.hpp\"\n#include \"Runtime/Collision/CMaterialFilter.hpp\"\n#include \"Runtime/Collision/CollisionUtil.hpp\"\n\nnamespace metaforce {\n\nCCollidableOBBTree::CCollidableOBBTree(const COBBTree* tree, const metaforce::CMaterialList& material)\n: CCollisionPrimitive(material), x10_tree(tree) {}\n\nbool CCollidableOBBTree::LineIntersectsLeaf(const COBBTree::CLeafData& leaf, CRayCastInfo& info) const {\n  bool ret = false;\n  u16 intersectIdx = 0;\n  for (size_t i = 0; i < leaf.GetSurfaceVector().size(); ++i) {\n    u16 surfIdx = leaf.GetSurfaceVector()[i];\n    CCollisionSurface surface = x10_tree->GetSurface(surfIdx);\n    CMaterialList matList = GetMaterial();\n    matList.Add(surface.GetSurfaceFlags());\n    if (info.GetMaterialFilter().Passes(matList)) {\n      if (CollisionUtil::RayTriangleIntersection(info.GetRay().start, info.GetRay().dir, surface.GetVerts(),\n                                                 info.Magnitude())) {\n        intersectIdx = surfIdx;\n        ret = true;\n      }\n    }\n  }\n\n  if (ret) {\n    CCollisionSurface surf = x10_tree->GetSurface(intersectIdx);\n    info.Plane() = surf.GetPlane();\n    info.Material() = CMaterialList(surf.GetSurfaceFlags());\n  }\n\n  return ret;\n}\n\nbool CCollidableOBBTree::LineIntersectsOBBTree(const COBBTree::CNode& n0, const COBBTree::CNode& n1,\n                                               CRayCastInfo& info) const {\n  bool ret = false;\n  float t0, t1;\n  bool intersects0 = false;\n  bool intersects1 = false;\n  if (CollisionUtil::LineIntersectsOBBox(n0.GetOBB(), info.GetRay(), t0) && t0 < info.GetMagnitude())\n    intersects0 = true;\n  if (CollisionUtil::LineIntersectsOBBox(n1.GetOBB(), info.GetRay(), t1) && t1 < info.GetMagnitude())\n    intersects1 = true;\n\n  if (intersects0 && intersects1) {\n    if (t0 < t1) {\n      if (n0.IsLeaf() ? LineIntersectsLeaf(n0.GetLeafData(), info)\n                      : LineIntersectsOBBTree(n0.GetLeft(), n0.GetRight(), info)) {\n        if (info.GetMagnitude() < t1)\n          return true;\n        ret = true;\n      }\n      if (n1.IsLeaf()) {\n        if (LineIntersectsLeaf(n1.GetLeafData(), info))\n          return true;\n      } else {\n        if (LineIntersectsOBBTree(n1.GetLeft(), n1.GetRight(), info))\n          return true;\n      }\n    } else {\n      if (n1.IsLeaf() ? LineIntersectsLeaf(n1.GetLeafData(), info)\n                      : LineIntersectsOBBTree(n1.GetLeft(), n1.GetRight(), info)) {\n        if (info.GetMagnitude() < t0)\n          return true;\n        ret = true;\n      }\n      if (n0.IsLeaf()) {\n        if (LineIntersectsLeaf(n0.GetLeafData(), info))\n          return true;\n      } else {\n        if (LineIntersectsOBBTree(n0.GetLeft(), n0.GetRight(), info))\n          return true;\n      }\n    }\n  } else if (intersects0) {\n    return n0.IsLeaf() ? LineIntersectsLeaf(n0.GetLeafData(), info)\n                       : LineIntersectsOBBTree(n0.GetLeft(), n0.GetRight(), info);\n  } else if (intersects1) {\n    return n1.IsLeaf() ? LineIntersectsLeaf(n1.GetLeafData(), info)\n                       : LineIntersectsOBBTree(n1.GetLeft(), n1.GetRight(), info);\n  }\n\n  return ret;\n}\n\nbool CCollidableOBBTree::LineIntersectsOBBTree(const COBBTree::CNode& node, CRayCastInfo& info) const {\n  float t;\n  bool ret = false;\n\n  if (CollisionUtil::LineIntersectsOBBox(node.GetOBB(), info.GetRay(), t) && t < info.GetMagnitude()) {\n    if (node.IsLeaf()) {\n      if (LineIntersectsLeaf(node.GetLeafData(), info))\n        ret = true;\n    } else {\n      if (LineIntersectsOBBTree(node.GetLeft(), node.GetRight(), info))\n        ret = true;\n    }\n    const_cast<COBBTree::CNode&>(node).SetHit(true);\n  } else {\n    const_cast<CCollidableOBBTree&>(*this).x18_misses += 1;\n  }\n\n  return ret;\n}\n\nCRayCastResult CCollidableOBBTree::LineIntersectsTree(const zeus::CMRay& ray, const CMaterialFilter& filter,\n                                                      float maxTime, const zeus::CTransform& xf) const {\n  zeus::CMRay useRay = ray.getInvUnscaledTransformRay(xf);\n  CRayCastInfo info(useRay, filter, maxTime);\n  if (LineIntersectsOBBTree(x10_tree->GetRoot(), info)) {\n    zeus::CPlane xfPlane = TransformPlane(info.GetPlane(), xf);\n    return CRayCastResult(info.GetMagnitude(), ray.start + info.GetMagnitude() * ray.dir, xfPlane, info.GetMaterial());\n  } else {\n    return {};\n  }\n}\n\nzeus::CPlane CCollidableOBBTree::TransformPlane(const zeus::CPlane& pl, const zeus::CTransform& xf) {\n  zeus::CVector3f normal = xf.rotate(pl.normal());\n  return zeus::CPlane(normal, (xf * (pl.normal() * pl.d())).dot(normal));\n}\n\nbool CCollidableOBBTree::SphereCollideWithLeafMoving(const COBBTree::CLeafData& leaf, const zeus::CTransform& xf,\n                                                     const zeus::CSphere& sphere, const CMaterialList& matList,\n                                                     const CMaterialFilter& filter, const zeus::CVector3f& dir,\n                                                     double& dOut, CCollisionInfo& infoOut) const {\n  bool ret = false;\n\n  zeus::CAABox aabb(sphere.position - sphere.radius, sphere.position + sphere.radius);\n  zeus::CAABox moveAABB = aabb;\n  zeus::CVector3f moveVec = float(dOut) * dir;\n  moveAABB.accumulateBounds(aabb.max + moveVec);\n  moveAABB.accumulateBounds(aabb.min + moveVec);\n\n  zeus::CVector3f center = moveAABB.center();\n  zeus::CVector3f extent = moveAABB.extents();\n\n  for (u16 triIdx : leaf.GetSurfaceVector()) {\n    CCollisionSurface surf = x10_tree->GetTransformedSurface(triIdx, xf);\n    CMaterialList triMat = GetMaterial();\n    triMat.Add(CMaterialList(surf.GetSurfaceFlags()));\n    if (filter.Passes(triMat)) {\n      if (CollisionUtil::TriBoxOverlap(center, extent, surf.GetVert(0), surf.GetVert(1), surf.GetVert(2))) {\n        const_cast<CCollidableOBBTree&>(*this).x1c_hits += 1;\n\n        zeus::CVector3f surfNormal = surf.GetNormal();\n        if ((sphere.position + moveVec - surf.GetVert(0)).dot(surfNormal) <= sphere.radius) {\n          const float mag = (sphere.radius - (sphere.position - surf.GetVert(0)).dot(surfNormal)) / dir.dot(surfNormal);\n          const zeus::CVector3f intersectPoint = sphere.position + mag * dir;\n\n          const std::array<bool, 3> outsideEdges{\n              (intersectPoint - surf.GetVert(0)).dot((surf.GetVert(1) - surf.GetVert(0)).cross(surfNormal)) < 0.f,\n              (intersectPoint - surf.GetVert(1)).dot((surf.GetVert(2) - surf.GetVert(1)).cross(surfNormal)) < 0.f,\n              (intersectPoint - surf.GetVert(2)).dot((surf.GetVert(0) - surf.GetVert(2)).cross(surfNormal)) < 0.f,\n          };\n\n          if (mag >= 0.f && !outsideEdges[0] && !outsideEdges[1] && !outsideEdges[2] && mag < dOut) {\n            infoOut = CCollisionInfo(intersectPoint - sphere.radius * surfNormal, matList, triMat, surfNormal);\n            dOut = mag;\n            ret = true;\n          }\n\n          const bool intersects = (sphere.position - surf.GetVert(0)).dot(surfNormal) <= sphere.radius;\n          std::array<bool, 3> testVert{true, true, true};\n          const u16* edgeIndices = x10_tree->GetTriangleEdgeIndices(triIdx);\n          for (int k = 0; k < 3; ++k) {\n            if (intersects || outsideEdges[k]) {\n              u16 edgeIdx = edgeIndices[k];\n              if (CMetroidAreaCollider::g_DupPrimitiveCheckCount != CMetroidAreaCollider::g_DupEdgeList[edgeIdx]) {\n                CMetroidAreaCollider::g_DupEdgeList[edgeIdx] = CMetroidAreaCollider::g_DupPrimitiveCheckCount;\n                CMaterialList edgeMat(x10_tree->GetEdgeMaterial(edgeIdx));\n                if (!edgeMat.HasMaterial(EMaterialTypes::NoEdgeCollision)) {\n                  int nextIdx = (k + 1) % 3;\n                  zeus::CVector3f edgeVec = surf.GetVert(nextIdx) - surf.GetVert(k);\n                  float edgeVecMag = edgeVec.magnitude();\n                  edgeVec *= zeus::CVector3f(1.f / edgeVecMag);\n                  float dirDotEdge = dir.dot(edgeVec);\n                  zeus::CVector3f edgeRej = dir - dirDotEdge * edgeVec;\n                  float edgeRejMagSq = edgeRej.magSquared();\n                  zeus::CVector3f vertToSphere = sphere.position - surf.GetVert(k);\n                  float vtsDotEdge = vertToSphere.dot(edgeVec);\n                  zeus::CVector3f vtsRej = vertToSphere - vtsDotEdge * edgeVec;\n                  if (edgeRejMagSq > 0.f) {\n                    const float tmp = 2.f * vtsRej.dot(edgeRej);\n                    const float tmp2 =\n                        4.f * edgeRejMagSq * (vtsRej.magSquared() - sphere.radius * sphere.radius) - tmp * tmp;\n                    if (tmp2 >= 0.f) {\n                      const float mag2 = 0.5f / edgeRejMagSq * (-tmp - std::sqrt(tmp2));\n                      if (mag2 >= 0.f) {\n                        const float t = mag2 * dirDotEdge + vtsDotEdge;\n                        if (t >= 0.f && t <= edgeVecMag && mag2 < dOut) {\n                          zeus::CVector3f point = surf.GetVert(k) + t * edgeVec;\n                          infoOut = CCollisionInfo(point, matList, edgeMat,\n                                                   (sphere.position + mag2 * dir - point).normalized());\n                          dOut = mag2;\n                          ret = true;\n                          testVert[k] = false;\n                          testVert[nextIdx] = false;\n                        } else if (t < -sphere.radius && dirDotEdge <= 0.f) {\n                          testVert[k] = false;\n                        } else if (t > edgeVecMag + sphere.radius && dirDotEdge >= 0.0) {\n                          testVert[nextIdx] = false;\n                        }\n                      }\n                    } else {\n                      testVert[k] = false;\n                      testVert[nextIdx] = false;\n                    }\n                  }\n                }\n              }\n            }\n          }\n\n          const auto vertIndices = x10_tree->GetTriangleVertexIndices(triIdx);\n          for (int k = 0; k < 3; ++k) {\n            const u16 vertIdx = vertIndices[k];\n            if (testVert[k]) {\n              if (CMetroidAreaCollider::g_DupPrimitiveCheckCount != CMetroidAreaCollider::g_DupVertexList[vertIdx]) {\n                CMetroidAreaCollider::g_DupVertexList[vertIdx] = CMetroidAreaCollider::g_DupPrimitiveCheckCount;\n                double d = dOut;\n                if (CollisionUtil::RaySphereIntersection_Double(zeus::CSphere(surf.GetVert(k), sphere.radius),\n                                                                sphere.position, dir, d) &&\n                    d >= 0.0) {\n                  infoOut = CCollisionInfo(surf.GetVert(k), matList, x10_tree->GetVertMaterial(vertIdx),\n                                           (sphere.position + dir * d - surf.GetVert(k)).normalized());\n                  dOut = d;\n                  ret = true;\n                }\n              }\n            } else {\n              CMetroidAreaCollider::g_DupVertexList[vertIdx] = CMetroidAreaCollider::g_DupPrimitiveCheckCount;\n            }\n          }\n        }\n      } else {\n        const u16* edgeIndices = x10_tree->GetTriangleEdgeIndices(triIdx);\n        CMetroidAreaCollider::g_DupEdgeList[edgeIndices[0]] = CMetroidAreaCollider::g_DupPrimitiveCheckCount;\n        CMetroidAreaCollider::g_DupEdgeList[edgeIndices[1]] = CMetroidAreaCollider::g_DupPrimitiveCheckCount;\n        CMetroidAreaCollider::g_DupEdgeList[edgeIndices[2]] = CMetroidAreaCollider::g_DupPrimitiveCheckCount;\n\n        const auto vertIndices = x10_tree->GetTriangleVertexIndices(triIdx);\n        CMetroidAreaCollider::g_DupVertexList[vertIndices[0]] = CMetroidAreaCollider::g_DupPrimitiveCheckCount;\n        CMetroidAreaCollider::g_DupVertexList[vertIndices[1]] = CMetroidAreaCollider::g_DupPrimitiveCheckCount;\n        CMetroidAreaCollider::g_DupVertexList[vertIndices[2]] = CMetroidAreaCollider::g_DupPrimitiveCheckCount;\n      }\n    }\n  }\n\n  return ret;\n}\n\nbool CCollidableOBBTree::SphereCollisionMoving(const COBBTree::CNode& node, const zeus::CTransform& xf,\n                                               const zeus::CSphere& sphere, const zeus::COBBox& obb,\n                                               const CMaterialList& material, const CMaterialFilter& filter,\n                                               const zeus::CVector3f& dir, double& dOut, CCollisionInfo& info) const {\n  bool ret = false;\n\n  const_cast<CCollidableOBBTree&>(*this).x14_tries += 1;\n  if (obb.OBBIntersectsBox(node.GetOBB())) {\n    const_cast<COBBTree::CNode&>(node).SetHit(true);\n    if (node.IsLeaf()) {\n      if (SphereCollideWithLeafMoving(node.GetLeafData(), xf, sphere, material, filter, dir, dOut, info))\n        ret = true;\n    } else {\n      if (SphereCollisionMoving(node.GetLeft(), xf, sphere, obb, material, filter, dir, dOut, info))\n        ret = true;\n      if (SphereCollisionMoving(node.GetRight(), xf, sphere, obb, material, filter, dir, dOut, info))\n        ret = true;\n    }\n  } else {\n    const_cast<CCollidableOBBTree&>(*this).x18_misses += 1;\n  }\n\n  return ret;\n}\n\nbool CCollidableOBBTree::AABoxCollideWithLeafMoving(const COBBTree::CLeafData& leaf, const zeus::CTransform& xf,\n                                                    const zeus::CAABox& aabb, const CMaterialList& matList,\n                                                    const CMaterialFilter& filter,\n                                                    const CMovingAABoxComponents& components,\n                                                    const zeus::CVector3f& dir, double& dOut,\n                                                    CCollisionInfo& infoOut) const {\n  bool ret = false;\n\n  zeus::CAABox movedAABB = components.x6e8_aabb;\n  zeus::CVector3f moveVec = float(dOut) * dir;\n  movedAABB.accumulateBounds(aabb.min + moveVec);\n  movedAABB.accumulateBounds(aabb.max + moveVec);\n\n  zeus::CVector3f center = movedAABB.center();\n  zeus::CVector3f extent = movedAABB.extents();\n\n  zeus::CVector3f normal, point;\n\n  for (u16 triIdx : leaf.GetSurfaceVector()) {\n    CCollisionSurface surf = x10_tree->GetTransformedSurface(triIdx, xf);\n    CMaterialList triMat = GetMaterial();\n    triMat.Add(CMaterialList(surf.GetSurfaceFlags()));\n    if (filter.Passes(triMat)) {\n      if (CollisionUtil::TriBoxOverlap(center, extent, surf.GetVert(0), surf.GetVert(1), surf.GetVert(2))) {\n        const_cast<CCollidableOBBTree&>(*this).x1c_hits += 1;\n\n        const auto vertIndices = x10_tree->GetTriangleVertexIndices(triIdx);\n\n        double d = dOut;\n        if (CMetroidAreaCollider::MovingAABoxCollisionCheck_BoxVertexTri(surf, aabb, components.x6c4_vertIdxs, dir, d,\n                                                                         normal, point) &&\n            d < dOut) {\n          ret = true;\n          infoOut = CCollisionInfo(point, matList, triMat, normal);\n          dOut = d;\n        }\n\n        for (int k = 0; k < 3; ++k) {\n          u16 vertIdx = vertIndices[k];\n          const zeus::CVector3f& vtx = surf.GetVert(k);\n          if (CMetroidAreaCollider::g_DupPrimitiveCheckCount != CMetroidAreaCollider::g_DupVertexList[vertIdx]) {\n            CMetroidAreaCollider::g_DupVertexList[vertIdx] = CMetroidAreaCollider::g_DupPrimitiveCheckCount;\n            if (movedAABB.pointInside(vtx)) {\n              d = dOut;\n              if (CMetroidAreaCollider::MovingAABoxCollisionCheck_TriVertexBox(vtx, aabb, dir, d, normal, point) &&\n                  d < dOut) {\n                CMaterialList vertMat(x10_tree->GetVertMaterial(vertIdx));\n                ret = true;\n                infoOut = CCollisionInfo(point, matList, vertMat, normal);\n                dOut = d;\n              }\n            }\n          }\n        }\n\n        const u16* edgeIndices = x10_tree->GetTriangleEdgeIndices(triIdx);\n        for (int k = 0; k < 3; ++k) {\n          u16 edgeIdx = edgeIndices[k];\n          if (CMetroidAreaCollider::g_DupPrimitiveCheckCount != CMetroidAreaCollider::g_DupEdgeList[edgeIdx]) {\n            CMetroidAreaCollider::g_DupEdgeList[edgeIdx] = CMetroidAreaCollider::g_DupPrimitiveCheckCount;\n            CMaterialList edgeMat(x10_tree->GetEdgeMaterial(edgeIdx));\n            if (!edgeMat.HasMaterial(EMaterialTypes::NoEdgeCollision)) {\n              d = dOut;\n              if (CMetroidAreaCollider::MovingAABoxCollisionCheck_Edge(surf.GetVert(k), surf.GetVert((k + 1) % 3),\n                                                                       components.x0_edges, dir, d, normal, point) &&\n                  d < dOut) {\n                ret = true;\n                infoOut = CCollisionInfo(point, matList, edgeMat, normal);\n                dOut = d;\n              }\n            }\n          }\n        }\n      } else {\n        const u16* edgeIndices = x10_tree->GetTriangleEdgeIndices(triIdx);\n        CMetroidAreaCollider::g_DupEdgeList[edgeIndices[0]] = CMetroidAreaCollider::g_DupPrimitiveCheckCount;\n        CMetroidAreaCollider::g_DupEdgeList[edgeIndices[1]] = CMetroidAreaCollider::g_DupPrimitiveCheckCount;\n        CMetroidAreaCollider::g_DupEdgeList[edgeIndices[2]] = CMetroidAreaCollider::g_DupPrimitiveCheckCount;\n\n        const auto vertIndices = x10_tree->GetTriangleVertexIndices(triIdx);\n        CMetroidAreaCollider::g_DupVertexList[vertIndices[0]] = CMetroidAreaCollider::g_DupPrimitiveCheckCount;\n        CMetroidAreaCollider::g_DupVertexList[vertIndices[1]] = CMetroidAreaCollider::g_DupPrimitiveCheckCount;\n        CMetroidAreaCollider::g_DupVertexList[vertIndices[2]] = CMetroidAreaCollider::g_DupPrimitiveCheckCount;\n      }\n    }\n  }\n\n  return ret;\n}\n\nbool CCollidableOBBTree::AABoxCollisionMoving(const COBBTree::CNode& node, const zeus::CTransform& xf,\n                                              const zeus::CAABox& aabb, const zeus::COBBox& obb,\n                                              const CMaterialList& material, const CMaterialFilter& filter,\n                                              const CMovingAABoxComponents& components, const zeus::CVector3f& dir,\n                                              double& dOut, CCollisionInfo& info) const {\n  bool ret = false;\n\n  const_cast<CCollidableOBBTree&>(*this).x14_tries += 1;\n  if (obb.OBBIntersectsBox(node.GetOBB())) {\n    const_cast<COBBTree::CNode&>(node).SetHit(true);\n    if (node.IsLeaf()) {\n      if (AABoxCollideWithLeafMoving(node.GetLeafData(), xf, aabb, material, filter, components, dir, dOut, info))\n        ret = true;\n    } else {\n      if (AABoxCollisionMoving(node.GetLeft(), xf, aabb, obb, material, filter, components, dir, dOut, info))\n        ret = true;\n      if (AABoxCollisionMoving(node.GetRight(), xf, aabb, obb, material, filter, components, dir, dOut, info))\n        ret = true;\n    }\n  } else {\n    const_cast<CCollidableOBBTree&>(*this).x18_misses += 1;\n  }\n\n  return ret;\n}\n\nbool CCollidableOBBTree::SphereCollisionBoolean(const COBBTree::CNode& node, const zeus::CTransform& xf,\n                                                const zeus::CSphere& sphere, const zeus::COBBox& obb,\n                                                const CMaterialFilter& filter) const {\n  const_cast<CCollidableOBBTree&>(*this).x14_tries += 1;\n  if (obb.OBBIntersectsBox(node.GetOBB())) {\n    const_cast<COBBTree::CNode&>(node).SetHit(true);\n    if (node.IsLeaf()) {\n      for (u16 surfIdx : node.GetLeafData().GetSurfaceVector()) {\n        CCollisionSurface surf = x10_tree->GetTransformedSurface(surfIdx, xf);\n        CMaterialList triMat = GetMaterial();\n        triMat.Add(CMaterialList(surf.GetSurfaceFlags()));\n        if (filter.Passes(triMat) &&\n            CollisionUtil::TriSphereOverlap(sphere, surf.GetVert(0), surf.GetVert(1), surf.GetVert(2)))\n          return true;\n      }\n    } else {\n      if (SphereCollisionBoolean(node.GetLeft(), xf, sphere, obb, filter))\n        return true;\n      if (SphereCollisionBoolean(node.GetRight(), xf, sphere, obb, filter))\n        return true;\n    }\n  } else {\n    const_cast<CCollidableOBBTree&>(*this).x18_misses += 1;\n  }\n\n  return false;\n}\n\nbool CCollidableOBBTree::AABoxCollisionBoolean(const COBBTree::CNode& node, const zeus::CTransform& xf,\n                                               const zeus::CAABox& aabb, const zeus::COBBox& obb,\n                                               const CMaterialFilter& filter) const {\n  zeus::CVector3f center = aabb.center();\n  zeus::CVector3f extent = aabb.extents();\n\n  const_cast<CCollidableOBBTree&>(*this).x14_tries += 1;\n  if (obb.OBBIntersectsBox(node.GetOBB())) {\n    const_cast<COBBTree::CNode&>(node).SetHit(true);\n    if (node.IsLeaf()) {\n      for (u16 surfIdx : node.GetLeafData().GetSurfaceVector()) {\n        CCollisionSurface surf = x10_tree->GetTransformedSurface(surfIdx, xf);\n        CMaterialList triMat = GetMaterial();\n        triMat.Add(CMaterialList(surf.GetSurfaceFlags()));\n        if (filter.Passes(triMat) &&\n            CollisionUtil::TriBoxOverlap(center, extent, surf.GetVert(0), surf.GetVert(1), surf.GetVert(2)))\n          return true;\n      }\n    } else {\n      if (AABoxCollisionBoolean(node.GetLeft(), xf, aabb, obb, filter))\n        return true;\n      if (AABoxCollisionBoolean(node.GetRight(), xf, aabb, obb, filter))\n        return true;\n    }\n  } else {\n    const_cast<CCollidableOBBTree&>(*this).x18_misses += 1;\n  }\n\n  return false;\n}\n\nbool CCollidableOBBTree::SphereCollideWithLeaf(const COBBTree::CLeafData& leaf, const zeus::CTransform& xf,\n                                               const zeus::CSphere& sphere, const CMaterialList& material,\n                                               const CMaterialFilter& filter, CCollisionInfoList& infoList) const {\n  bool ret = false;\n  zeus::CVector3f point, normal;\n\n  for (u16 surfIdx : leaf.GetSurfaceVector()) {\n    CCollisionSurface surf = x10_tree->GetTransformedSurface(surfIdx, xf);\n    CMaterialList triMat = GetMaterial();\n    triMat.Add(CMaterialList(surf.GetSurfaceFlags()));\n    if (filter.Passes(triMat)) {\n      const_cast<CCollidableOBBTree&>(*this).x1c_hits += 1;\n      if (CollisionUtil::TriSphereIntersection(sphere, surf.GetVert(0), surf.GetVert(1), surf.GetVert(2), point,\n                                               normal)) {\n        CCollisionInfo collision(point, material, triMat, normal);\n        infoList.Add(collision, false);\n        ret = true;\n      }\n    }\n  }\n\n  return ret;\n}\n\nbool CCollidableOBBTree::SphereCollision(const COBBTree::CNode& node, const zeus::CTransform& xf,\n                                         const zeus::CSphere& sphere, const zeus::COBBox& obb,\n                                         const CMaterialList& material, const CMaterialFilter& filter,\n                                         CCollisionInfoList& infoList) const {\n  bool ret = false;\n\n  const_cast<CCollidableOBBTree&>(*this).x14_tries += 1;\n  if (obb.OBBIntersectsBox(node.GetOBB())) {\n    const_cast<COBBTree::CNode&>(node).SetHit(true);\n    if (node.IsLeaf()) {\n      if (SphereCollideWithLeaf(node.GetLeafData(), xf, sphere, material, filter, infoList))\n        ret = true;\n    } else {\n      if (SphereCollision(node.GetLeft(), xf, sphere, obb, material, filter, infoList))\n        ret = true;\n      if (SphereCollision(node.GetRight(), xf, sphere, obb, material, filter, infoList))\n        ret = true;\n    }\n  } else {\n    const_cast<CCollidableOBBTree&>(*this).x18_misses += 1;\n  }\n\n  return ret;\n}\n\nbool CCollidableOBBTree::AABoxCollideWithLeaf(const COBBTree::CLeafData& leaf, const zeus::CTransform& xf,\n                                              const zeus::CAABox& aabb, const CMaterialList& material,\n                                              const CMaterialFilter& filter, const std::array<zeus::CPlane, 6>& planes,\n                                              CCollisionInfoList& infoList) const {\n  bool ret = false;\n  zeus::CVector3f center = aabb.center();\n  zeus::CVector3f extent = aabb.extents();\n\n  for (u16 surfIdx : leaf.GetSurfaceVector()) {\n    CCollisionSurface surf = x10_tree->GetTransformedSurface(surfIdx, xf);\n    CMaterialList triMat = GetMaterial();\n    triMat.Add(CMaterialList(surf.GetSurfaceFlags()));\n    if (filter.Passes(triMat) &&\n        CollisionUtil::TriBoxOverlap(center, extent, surf.GetVert(0), surf.GetVert(1), surf.GetVert(2))) {\n      zeus::CAABox newAABB = zeus::CAABox();\n      const_cast<CCollidableOBBTree&>(*this).x1c_hits += 1;\n      if (CMetroidAreaCollider::ConvexPolyCollision(planes, surf.GetVerts(), newAABB)) {\n        zeus::CPlane plane = surf.GetPlane();\n        CCollisionInfo collision(newAABB, triMat, material, plane.normal(), -plane.normal());\n        infoList.Add(collision, false);\n        ret = true;\n      }\n    }\n  }\n\n  return ret;\n}\n\nbool CCollidableOBBTree::AABoxCollision(const COBBTree::CNode& node, const zeus::CTransform& xf,\n                                        const zeus::CAABox& aabb, const zeus::COBBox& obb,\n                                        const CMaterialList& material, const CMaterialFilter& filter,\n                                        const std::array<zeus::CPlane, 6>& planes, CCollisionInfoList& infoList) const {\n  bool ret = false;\n\n  const_cast<CCollidableOBBTree&>(*this).x14_tries += 1;\n  if (obb.OBBIntersectsBox(node.GetOBB())) {\n    const_cast<COBBTree::CNode&>(node).SetHit(true);\n    if (node.IsLeaf()) {\n      if (AABoxCollideWithLeaf(node.GetLeafData(), xf, aabb, material, filter, planes, infoList))\n        ret = true;\n    } else {\n      if (AABoxCollision(node.GetLeft(), xf, aabb, obb, material, filter, planes, infoList))\n        ret = true;\n      if (AABoxCollision(node.GetRight(), xf, aabb, obb, material, filter, planes, infoList))\n        ret = true;\n    }\n  } else {\n    const_cast<CCollidableOBBTree&>(*this).x18_misses += 1;\n  }\n\n  return ret;\n}\n\nFourCC CCollidableOBBTree::GetPrimType() const { return SBIG('OBBT'); }\n\nCRayCastResult CCollidableOBBTree::CastRayInternal(const CInternalRayCastStructure& rayCast) const {\n  return LineIntersectsTree(rayCast.GetRay(), rayCast.GetFilter(), rayCast.GetMaxTime(), rayCast.GetTransform());\n}\n\nzeus::CAABox CCollidableOBBTree::CalculateAABox(const zeus::CTransform& xf) const {\n  return zeus::COBBox::FromAABox(x10_tree->CalculateLocalAABox(), xf).calculateAABox();\n}\n\nzeus::CAABox CCollidableOBBTree::CalculateLocalAABox() const { return x10_tree->CalculateLocalAABox(); }\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CCollidableOBBTree.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Collision/CCollisionPrimitive.hpp\"\n#include \"Runtime/Collision/CMetroidAreaCollider.hpp\"\n#include \"Runtime/Collision/COBBTree.hpp\"\n\n#include <zeus/CMRay.hpp>\n#include <zeus/COBBox.hpp>\n#include <zeus/CPlane.hpp>\n\nnamespace metaforce {\nclass CRayCastInfo {\n  const zeus::CMRay& x0_ray;\n  const CMaterialFilter& x4_filter;\n  float x8_mag;\n  zeus::CPlane xc_plane = {zeus::skUp, 0.f};\n  CMaterialList x20_material;\n\npublic:\n  CRayCastInfo(const zeus::CMRay& ray, const CMaterialFilter& filter, float mag)\n  : x0_ray(ray), x4_filter(filter), x8_mag(mag) {}\n  const zeus::CMRay& GetRay() const { return x0_ray; }\n  const CMaterialFilter& GetMaterialFilter() const { return x4_filter; }\n  float GetMagnitude() const { return x8_mag; }\n  float& Magnitude() { return x8_mag; }\n  const zeus::CPlane& GetPlane() const { return xc_plane; }\n  zeus::CPlane& Plane() { return xc_plane; }\n  const CMaterialList& GetMaterial() const { return x20_material; }\n  CMaterialList& Material() { return x20_material; }\n};\n\nclass CCollidableOBBTree : public CCollisionPrimitive {\n  friend class CCollidableOBBTreeGroup;\n  const COBBTree* x10_tree = nullptr;\n  u32 x14_tries = 0;\n  u32 x18_misses = 0;\n  u32 x1c_hits = 0;\n  static inline u32 sTableIndex = 0;\n  bool LineIntersectsLeaf(const COBBTree::CLeafData& leaf, CRayCastInfo& info) const;\n  bool LineIntersectsOBBTree(const COBBTree::CNode& n0, const COBBTree::CNode& n1, CRayCastInfo& info) const;\n  bool LineIntersectsOBBTree(const COBBTree::CNode& node, CRayCastInfo& info) const;\n  CRayCastResult LineIntersectsTree(const zeus::CMRay& ray, const CMaterialFilter& filter, float maxTime,\n                                    const zeus::CTransform& xf) const;\n  static zeus::CPlane TransformPlane(const zeus::CPlane& pl, const zeus::CTransform& xf);\n  bool SphereCollideWithLeafMoving(const COBBTree::CLeafData& leaf, const zeus::CTransform& xf,\n                                   const zeus::CSphere& sphere, const CMaterialList& material,\n                                   const CMaterialFilter& filter, const zeus::CVector3f& dir, double& dOut,\n                                   CCollisionInfo& info) const;\n  bool SphereCollisionMoving(const COBBTree::CNode& node, const zeus::CTransform& xf, const zeus::CSphere& sphere,\n                             const zeus::COBBox& obb, const CMaterialList& material, const CMaterialFilter& filter,\n                             const zeus::CVector3f& dir, double& dOut, CCollisionInfo& info) const;\n  bool AABoxCollideWithLeafMoving(const COBBTree::CLeafData& leaf, const zeus::CTransform& xf, const zeus::CAABox& aabb,\n                                  const CMaterialList& material, const CMaterialFilter& filter,\n                                  const CMovingAABoxComponents& components, const zeus::CVector3f& dir, double& dOut,\n                                  CCollisionInfo& info) const;\n  bool AABoxCollisionMoving(const COBBTree::CNode& node, const zeus::CTransform& xf, const zeus::CAABox& aabb,\n                            const zeus::COBBox& obb, const CMaterialList& material, const CMaterialFilter& filter,\n                            const CMovingAABoxComponents& components, const zeus::CVector3f& dir, double& dOut,\n                            CCollisionInfo& info) const;\n  bool SphereCollisionBoolean(const COBBTree::CNode& node, const zeus::CTransform& xf, const zeus::CSphere& sphere,\n                              const zeus::COBBox& obb, const CMaterialFilter& filter) const;\n  bool AABoxCollisionBoolean(const COBBTree::CNode& node, const zeus::CTransform& xf, const zeus::CAABox& aabb,\n                             const zeus::COBBox& obb, const CMaterialFilter& filter) const;\n  bool SphereCollideWithLeaf(const COBBTree::CLeafData& leaf, const zeus::CTransform& xf, const zeus::CSphere& sphere,\n                             const CMaterialList& material, const CMaterialFilter& filter,\n                             CCollisionInfoList& infoList) const;\n  bool SphereCollision(const COBBTree::CNode& node, const zeus::CTransform& xf, const zeus::CSphere& sphere,\n                       const zeus::COBBox& obb, const CMaterialList& material, const CMaterialFilter& filter,\n                       CCollisionInfoList& infoList) const;\n  bool AABoxCollideWithLeaf(const COBBTree::CLeafData& leaf, const zeus::CTransform& xf, const zeus::CAABox& aabb,\n                            const CMaterialList& material, const CMaterialFilter& filter,\n                            const std::array<zeus::CPlane, 6>& planes, CCollisionInfoList& infoList) const;\n  bool AABoxCollision(const COBBTree::CNode& node, const zeus::CTransform& xf, const zeus::CAABox& aabb,\n                      const zeus::COBBox& obb, const CMaterialList& material, const CMaterialFilter& filter,\n                      const std::array<zeus::CPlane, 6>& planes, CCollisionInfoList& infoList) const;\n\npublic:\n  CCollidableOBBTree(const COBBTree* tree, const CMaterialList& material);\n  ~CCollidableOBBTree() override = default;\n  void ResetTestStats() const;\n  void ResetTestStatsRecurse(const COBBTree::CNode&) const;\n  u32 GetTableIndex() const override { return sTableIndex; }\n  zeus::CAABox CalculateAABox(const zeus::CTransform&) const override;\n  zeus::CAABox CalculateLocalAABox() const override;\n  FourCC GetPrimType() const override;\n  CRayCastResult CastRayInternal(const CInternalRayCastStructure&) const override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CCollidableOBBTreeGroup.cpp",
    "content": "#include \"Runtime/Collision/CCollidableOBBTreeGroup.hpp\"\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/Collision/CCollidableAABox.hpp\"\n#include \"Runtime/Collision/CCollidableOBBTree.hpp\"\n#include \"Runtime/Collision/CCollidableSphere.hpp\"\n#include \"Runtime/Collision/CInternalRayCastStructure.hpp\"\n#include \"Runtime/Collision/CollisionUtil.hpp\"\n\nnamespace metaforce {\nconstexpr CCollisionPrimitive::Type sType(CCollidableOBBTreeGroup::SetStaticTableIndex, \"CCollidableOBBTreeGroup\");\n\nCCollidableOBBTreeGroupContainer::CCollidableOBBTreeGroupContainer(CInputStream& in) {\n  const u32 treeCount = in.ReadLong();\n  x0_trees.reserve(treeCount);\n\n  for (u32 i = 0; i < treeCount; i++) {\n    auto tree = std::make_unique<COBBTree>(in);\n    x0_trees.push_back(std::move(tree));\n  }\n\n  x10_aabbs.reserve(x0_trees.size());\n\n  for (const std::unique_ptr<COBBTree>& tree : x0_trees) {\n    x10_aabbs.push_back(CCollidableOBBTree(tree.get(), CMaterialList()).CalculateLocalAABox());\n    x20_aabox.accumulateBounds(x10_aabbs.back());\n  }\n}\n\nCCollidableOBBTreeGroupContainer::CCollidableOBBTreeGroupContainer(const zeus::CVector3f& extent,\n                                                                   const zeus::CVector3f& center) {\n  x0_trees.push_back(COBBTree::BuildOrientedBoundingBoxTree(extent, center));\n\n  for (const std::unique_ptr<COBBTree>& tree : x0_trees) {\n    x10_aabbs.push_back(CCollidableOBBTree(tree.get(), CMaterialList()).CalculateLocalAABox());\n    x20_aabox.accumulateBounds(x10_aabbs.back());\n  }\n}\n\nCCollidableOBBTreeGroup::CCollidableOBBTreeGroup(const CCollidableOBBTreeGroupContainer* container,\n                                                 const CMaterialList& matList)\n: CCollisionPrimitive(matList), x10_container(container) {}\n\nvoid CCollidableOBBTreeGroup::ResetTestStats() const { /* Remove me? */\n}\n\nu32 CCollidableOBBTreeGroup::GetTableIndex() const { return sTableIndex; }\n\nzeus::CAABox CCollidableOBBTreeGroup::CalculateAABox(const zeus::CTransform& xf) const {\n  return x10_container->x20_aabox.getTransformedAABox(xf);\n}\n\nzeus::CAABox CCollidableOBBTreeGroup::CalculateLocalAABox() const { return x10_container->x20_aabox; }\n\nFourCC CCollidableOBBTreeGroup::GetPrimType() const { return SBIG('OBTG'); }\n\nCRayCastResult CCollidableOBBTreeGroup::CastRayInternal(const CInternalRayCastStructure& rayCast) const {\n  CRayCastResult ret;\n\n  zeus::CMRay xfRay = rayCast.GetRay().getInvUnscaledTransformRay(rayCast.GetTransform());\n  auto aabbIt = x10_container->x10_aabbs.cbegin();\n  float mag = rayCast.GetMaxTime();\n  for (const std::unique_ptr<COBBTree>& tree : x10_container->x0_trees) {\n    CCollidableOBBTree obbTree(tree.get(), GetMaterial());\n    float tMin = 0.f;\n    float tMax = 0.f;\n    if (CollisionUtil::RayAABoxIntersection(xfRay, *aabbIt++, tMin, tMax) != 0u) {\n      CInternalRayCastStructure localCast(xfRay.start, xfRay.dir, mag, zeus::CTransform(), rayCast.GetFilter());\n      CRayCastResult localResult = obbTree.CastRayInternal(localCast);\n      if (localResult.IsValid()) {\n        if (ret.IsInvalid() || localResult.GetT() < ret.GetT()) {\n          ret = localResult;\n          mag = localResult.GetT();\n        }\n      }\n    }\n  }\n\n  ret.Transform(rayCast.GetTransform());\n  return ret;\n}\n\nconst CCollisionPrimitive::Type& CCollidableOBBTreeGroup::GetType() { return sType; }\n\nvoid CCollidableOBBTreeGroup::SetStaticTableIndex(u32 index) { sTableIndex = index; }\n\nbool CCollidableOBBTreeGroup::SphereCollide(const CInternalCollisionStructure& collision, CCollisionInfoList& list) {\n  bool ret = false;\n  const auto& p0 = static_cast<const CCollidableSphere&>(collision.GetLeft().GetPrim());\n  const auto& p1 = static_cast<const CCollidableOBBTreeGroup&>(collision.GetRight().GetPrim());\n\n  zeus::CSphere s0 = p0.Transform(collision.GetLeft().GetTransform());\n  zeus::COBBox obb1 = zeus::COBBox::FromAABox(p0.CalculateLocalAABox(), collision.GetRight().GetTransform().inverse() *\n                                                                            collision.GetLeft().GetTransform());\n\n  for (const std::unique_ptr<COBBTree>& tree : p1.x10_container->x0_trees) {\n    CCollidableOBBTree obbTree(tree.get(), p1.GetMaterial());\n    if (obbTree.SphereCollision(obbTree.x10_tree->GetRoot(), collision.GetRight().GetTransform(), s0, obb1,\n                                p0.GetMaterial(), collision.GetLeft().GetFilter(), list)) {\n      ret = true;\n    }\n  }\n\n  return ret;\n}\n\nbool CCollidableOBBTreeGroup::SphereCollideBoolean(const CInternalCollisionStructure& collision) {\n  const auto& p0 = static_cast<const CCollidableSphere&>(collision.GetLeft().GetPrim());\n  const auto& p1 = static_cast<const CCollidableOBBTreeGroup&>(collision.GetRight().GetPrim());\n\n  zeus::CSphere s0 = p0.Transform(collision.GetLeft().GetTransform());\n  zeus::COBBox obb1 = zeus::COBBox::FromAABox(p0.CalculateLocalAABox(), collision.GetRight().GetTransform().inverse() *\n                                                                            collision.GetLeft().GetTransform());\n\n  for (const std::unique_ptr<COBBTree>& tree : p1.x10_container->x0_trees) {\n    CCollidableOBBTree obbTree(tree.get(), p1.GetMaterial());\n    if (obbTree.SphereCollisionBoolean(obbTree.x10_tree->GetRoot(), collision.GetRight().GetTransform(), s0, obb1,\n                                       collision.GetLeft().GetFilter())) {\n      return true;\n    }\n  }\n\n  return false;\n}\n\nbool CCollidableOBBTreeGroup::CollideMovingSphere(const CInternalCollisionStructure& collision,\n                                                  const zeus::CVector3f& dir, double& mag, CCollisionInfo& info) {\n  bool ret = false;\n  const auto& p0 = static_cast<const CCollidableSphere&>(collision.GetLeft().GetPrim());\n  const auto& p1 = static_cast<const CCollidableOBBTreeGroup&>(collision.GetRight().GetPrim());\n\n  zeus::CSphere s0 = p0.Transform(collision.GetLeft().GetTransform());\n\n  zeus::CAABox movedAABB = p0.CalculateLocalAABox();\n  zeus::CVector3f moveVec = float(mag) * dir;\n  movedAABB.accumulateBounds(movedAABB.min + moveVec);\n  movedAABB.accumulateBounds(movedAABB.max + moveVec);\n\n  zeus::COBBox p0Obb = zeus::COBBox::FromAABox(movedAABB, collision.GetRight().GetTransform().inverse() *\n                                                              collision.GetLeft().GetTransform());\n\n  for (const std::unique_ptr<COBBTree>& tree : p1.x10_container->x0_trees) {\n    CCollidableOBBTree obbTree(tree.get(), p1.GetMaterial());\n    CMetroidAreaCollider::ResetInternalCounters();\n    if (obbTree.SphereCollisionMoving(obbTree.x10_tree->GetRoot(), collision.GetRight().GetTransform(), s0, p0Obb,\n                                      p0.GetMaterial(), collision.GetLeft().GetFilter(), dir, mag, info)) {\n      ret = true;\n    }\n  }\n\n  return ret;\n}\n\nbool CCollidableOBBTreeGroup::AABoxCollide(const CInternalCollisionStructure& collision, CCollisionInfoList& list) {\n  bool ret = false;\n  const auto& p0 = static_cast<const CCollidableAABox&>(collision.GetLeft().GetPrim());\n  const auto& p1 = static_cast<const CCollidableOBBTreeGroup&>(collision.GetRight().GetPrim());\n\n  const zeus::CAABox b0 = p0.CalculateAABox(collision.GetLeft().GetTransform());\n  const zeus::COBBox p0Obb = zeus::COBBox::FromAABox(\n      p0.CalculateLocalAABox(), collision.GetRight().GetTransform().inverse() * collision.GetLeft().GetTransform());\n\n  const std::array<zeus::CPlane, 6> planes{{\n      {zeus::skRight, b0.min.dot(zeus::skRight)},\n      {zeus::skLeft, b0.max.dot(zeus::skLeft)},\n      {zeus::skForward, b0.min.dot(zeus::skForward)},\n      {zeus::skBack, b0.max.dot(zeus::skBack)},\n      {zeus::skUp, b0.min.dot(zeus::skUp)},\n      {zeus::skDown, b0.max.dot(zeus::skDown)},\n  }};\n\n  for (const std::unique_ptr<COBBTree>& tree : p1.x10_container->x0_trees) {\n    CCollidableOBBTree obbTree(tree.get(), p1.GetMaterial());\n    if (obbTree.AABoxCollision(obbTree.x10_tree->GetRoot(), collision.GetRight().GetTransform(), b0, p0Obb,\n                               p0.GetMaterial(), collision.GetLeft().GetFilter(), planes, list)) {\n      ret = true;\n    }\n  }\n\n  return ret;\n}\n\nbool CCollidableOBBTreeGroup::AABoxCollideBoolean(const CInternalCollisionStructure& collision) {\n  const auto& p0 = static_cast<const CCollidableAABox&>(collision.GetLeft().GetPrim());\n  const auto& p1 = static_cast<const CCollidableOBBTreeGroup&>(collision.GetRight().GetPrim());\n\n  zeus::CAABox b0 = p0.CalculateAABox(collision.GetLeft().GetTransform());\n  zeus::COBBox p0Obb = zeus::COBBox::FromAABox(p0.CalculateLocalAABox(), collision.GetRight().GetTransform().inverse() *\n                                                                             collision.GetLeft().GetTransform());\n\n  for (const std::unique_ptr<COBBTree>& tree : p1.x10_container->x0_trees) {\n    CCollidableOBBTree obbTree(tree.get(), p1.GetMaterial());\n    if (obbTree.AABoxCollisionBoolean(obbTree.x10_tree->GetRoot(), collision.GetRight().GetTransform(), b0, p0Obb,\n                                      collision.GetLeft().GetFilter())) {\n      return true;\n    }\n  }\n\n  return false;\n}\n\nbool CCollidableOBBTreeGroup::CollideMovingAABox(const CInternalCollisionStructure& collision,\n                                                 const zeus::CVector3f& dir, double& mag, CCollisionInfo& info) {\n  bool ret = false;\n  const auto& p0 = static_cast<const CCollidableAABox&>(collision.GetLeft().GetPrim());\n  const auto& p1 = static_cast<const CCollidableOBBTreeGroup&>(collision.GetRight().GetPrim());\n\n  zeus::CAABox b0 = p0.CalculateAABox(collision.GetLeft().GetTransform());\n\n  CMovingAABoxComponents components(b0, dir);\n\n  zeus::CAABox movedAABB = p0.CalculateLocalAABox();\n  zeus::CVector3f moveVec = float(mag) * dir;\n  movedAABB.accumulateBounds(movedAABB.min + moveVec);\n  movedAABB.accumulateBounds(movedAABB.max + moveVec);\n\n  zeus::COBBox p0Obb = zeus::COBBox::FromAABox(movedAABB, collision.GetRight().GetTransform().inverse() *\n                                                              collision.GetLeft().GetTransform());\n\n  for (const std::unique_ptr<COBBTree>& tree : p1.x10_container->x0_trees) {\n    CCollidableOBBTree obbTree(tree.get(), p1.GetMaterial());\n    CMetroidAreaCollider::ResetInternalCounters();\n    if (obbTree.AABoxCollisionMoving(obbTree.x10_tree->GetRoot(), collision.GetRight().GetTransform(), b0, p0Obb,\n                                     p0.GetMaterial(), collision.GetLeft().GetFilter(), components, dir, mag, info)) {\n      ret = true;\n    }\n  }\n\n  return ret;\n}\n\nCFactoryFnReturn FCollidableOBBTreeGroupFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& vparms,\n                                                CObjectReference* selfRef) {\n  return TToken<CCollidableOBBTreeGroupContainer>::GetIObjObjectFor(\n      std::make_unique<CCollidableOBBTreeGroupContainer>(in));\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CCollidableOBBTreeGroup.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <vector>\n\n#include \"Runtime/CFactoryMgr.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/Collision/COBBTree.hpp\"\n#include \"Runtime/Collision/CCollisionPrimitive.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CCollidableOBBTreeGroupContainer {\n  friend class CCollidableOBBTreeGroup;\n  std::vector<std::unique_ptr<COBBTree>> x0_trees;\n  std::vector<zeus::CAABox> x10_aabbs;\n  zeus::CAABox x20_aabox;\n\npublic:\n  explicit CCollidableOBBTreeGroupContainer(CInputStream& in);\n  CCollidableOBBTreeGroupContainer(const zeus::CVector3f&, const zeus::CVector3f&);\n  u32 NumTrees() const { return x0_trees.size(); }\n};\n\nclass CCollidableOBBTreeGroup : public CCollisionPrimitive {\n  static inline u32 sTableIndex = UINT32_MAX;\n  const CCollidableOBBTreeGroupContainer* x10_container;\n\npublic:\n  CCollidableOBBTreeGroup(const CCollidableOBBTreeGroupContainer*, const CMaterialList&);\n  ~CCollidableOBBTreeGroup() override = default;\n\n  void ResetTestStats() const;\n  u32 GetTableIndex() const override;\n  zeus::CAABox CalculateAABox(const zeus::CTransform&) const override;\n  zeus::CAABox CalculateLocalAABox() const override;\n  FourCC GetPrimType() const override;\n  CRayCastResult CastRayInternal(const CInternalRayCastStructure&) const override;\n  COBBTree const* GetOBBTreeAABox(int index) const { return x10_container->x0_trees[index].get(); }\n  CCollidableOBBTreeGroupContainer const* GetContainer() const { return x10_container; }\n\n  static const Type& GetType();\n  static void SetStaticTableIndex(u32 index);\n  /* Sphere Collide */\n  static bool SphereCollide(const CInternalCollisionStructure&, CCollisionInfoList&);\n  static bool SphereCollideBoolean(const CInternalCollisionStructure&);\n  static bool CollideMovingSphere(const CInternalCollisionStructure&, const zeus::CVector3f&, double&, CCollisionInfo&);\n  /* AABox Collide */\n  static bool AABoxCollide(const CInternalCollisionStructure&, CCollisionInfoList&);\n  static bool AABoxCollideBoolean(const CInternalCollisionStructure&);\n  static bool CollideMovingAABox(const CInternalCollisionStructure&, const zeus::CVector3f&, double&, CCollisionInfo&);\n};\n\nCFactoryFnReturn FCollidableOBBTreeGroupFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& vparms,\n                                                CObjectReference* selfRef);\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CCollidableSphere.cpp",
    "content": "#include \"Runtime/Collision/CCollidableSphere.hpp\"\n\n#include \"Runtime/Collision/CCollidableAABox.hpp\"\n#include \"Runtime/Collision/CCollisionInfoList.hpp\"\n#include \"Runtime/Collision/CInternalRayCastStructure.hpp\"\n#include \"Runtime/Collision/CollisionUtil.hpp\"\n\nnamespace metaforce {\nconstexpr CCollisionPrimitive::Type sType(CCollidableSphere::SetStaticTableIndex, \"CCollidableSphere\");\n\nnamespace Collide {\n\nbool Sphere_AABox(const CInternalCollisionStructure& collision, CCollisionInfoList& list) {\n  const auto& p0 = static_cast<const CCollidableSphere&>(collision.GetLeft().GetPrim());\n  const auto& p1 = static_cast<const CCollidableAABox&>(collision.GetRight().GetPrim());\n\n  zeus::CSphere s0 = p0.Transform(collision.GetLeft().GetTransform());\n  zeus::CAABox b1 = p1.Transform(collision.GetRight().GetTransform());\n\n  float distSq = 0.f;\n  int flags = 0;\n  for (int i = 0; i < 3; ++i) {\n    if (s0.position[i] < b1.min[i]) {\n      if (s0.position[i] + s0.radius >= b1.min[i]) {\n        float dist = s0.position[i] - b1.min[i];\n        distSq += dist * dist;\n        flags |= 1 << (2 * i);\n      } else {\n        return false;\n      }\n    } else if (s0.position[i] > b1.max[i]) {\n      if (s0.position[i] - s0.radius <= b1.max[i]) {\n        float dist = s0.position[i] - b1.max[i];\n        distSq += dist * dist;\n        flags |= 1 << (2 * i + 1);\n      } else {\n        return false;\n      }\n    }\n  }\n\n  if (flags == 0) {\n    zeus::CVector3f normal = (s0.position - b1.center()).normalized();\n    zeus::CVector3f point = s0.position + normal * s0.radius;\n    CCollisionInfo info(point, p0.GetMaterial(), p1.GetMaterial(), normal);\n    list.Add(info, false);\n    return true;\n  }\n\n  if (distSq > s0.radius * s0.radius) {\n    return false;\n  }\n\n  zeus::CVector3f point;\n  switch (flags) {\n  case 0x1a:\n    point = zeus::CVector3f(b1.max.x(), b1.max.y(), b1.min.z());\n    break;\n  case 0x19:\n    point = zeus::CVector3f(b1.min.x(), b1.max.y(), b1.min.z());\n    break;\n  case 0x16:\n    point = zeus::CVector3f(b1.max.x(), b1.min.y(), b1.min.z());\n    break;\n  case 0x15:\n    point = zeus::CVector3f(b1.min.x(), b1.min.y(), b1.min.z());\n    break;\n  case 0x2a:\n    point = zeus::CVector3f(b1.max.x(), b1.max.y(), b1.max.z());\n    break;\n  case 0x29:\n    point = zeus::CVector3f(b1.min.x(), b1.max.y(), b1.max.z());\n    break;\n  case 0x26:\n    point = zeus::CVector3f(b1.max.x(), b1.min.y(), b1.max.z());\n    break;\n  case 0x25:\n    point = zeus::CVector3f(b1.min.x(), b1.min.y(), b1.max.z());\n    break;\n  case 0x11:\n    point = zeus::CVector3f(b1.min.x(), s0.position.y(), b1.min.z());\n    break;\n  case 0x12:\n    point = zeus::CVector3f(b1.max.x(), s0.position.y(), b1.min.z());\n    break;\n  case 0x14:\n    point = zeus::CVector3f(s0.position.x(), b1.min.y(), b1.min.z());\n    break;\n  case 0x18:\n    point = zeus::CVector3f(s0.position.x(), b1.max.y(), b1.min.z());\n    break;\n  case 0x5:\n    point = zeus::CVector3f(b1.min.x(), b1.min.y(), s0.position.z());\n    break;\n  case 0x6:\n    point = zeus::CVector3f(b1.max.x(), b1.min.y(), s0.position.z());\n    break;\n  case 0x9:\n    point = zeus::CVector3f(b1.min.x(), b1.max.y(), s0.position.z());\n    break;\n  case 0xa:\n    point = zeus::CVector3f(b1.max.x(), b1.max.y(), s0.position.z());\n    break;\n  case 0x21:\n    point = zeus::CVector3f(b1.min.x(), s0.position.y(), b1.max.z());\n    break;\n  case 0x22:\n    point = zeus::CVector3f(b1.max.x(), s0.position.y(), b1.max.z());\n    break;\n  case 0x24:\n    point = zeus::CVector3f(s0.position.x(), b1.min.y(), b1.max.z());\n    break;\n  case 0x28:\n    point = zeus::CVector3f(s0.position.x(), b1.max.y(), b1.max.z());\n    break;\n  case 0x1:\n    point = zeus::CVector3f(b1.min.x(), s0.position.y(), s0.position.z());\n    break;\n  case 0x2:\n    point = zeus::CVector3f(b1.max.x(), s0.position.y(), s0.position.z());\n    break;\n  case 0x4:\n    point = zeus::CVector3f(s0.position.x(), b1.min.y(), s0.position.z());\n    break;\n  case 0x8:\n    point = zeus::CVector3f(s0.position.x(), b1.max.y(), s0.position.z());\n    break;\n  case 0x10:\n    point = zeus::CVector3f(s0.position.x(), s0.position.y(), b1.min.z());\n    break;\n  case 0x20:\n    point = zeus::CVector3f(s0.position.x(), s0.position.y(), b1.max.z());\n    break;\n  default:\n    break;\n  }\n\n  CCollisionInfo info(point, p0.GetMaterial(), p1.GetMaterial(), (s0.position - point).normalized());\n  list.Add(info, false);\n  return true;\n}\n\nbool Sphere_AABox_Bool(const CInternalCollisionStructure& collision) {\n  const auto& p0 = static_cast<const CCollidableSphere&>(collision.GetLeft().GetPrim());\n  const auto& p1 = static_cast<const CCollidableAABox&>(collision.GetRight().GetPrim());\n\n  zeus::CSphere s0 = p0.Transform(collision.GetLeft().GetTransform());\n  zeus::CAABox b1 = p1.Transform(collision.GetRight().GetTransform());\n\n  return CCollidableSphere::Sphere_AABox_Bool(s0, b1);\n}\n\nbool Sphere_Sphere(const CInternalCollisionStructure& collision, CCollisionInfoList& list) {\n  const auto& p0 = static_cast<const CCollidableSphere&>(collision.GetLeft().GetPrim());\n  const auto& p1 = static_cast<const CCollidableSphere&>(collision.GetRight().GetPrim());\n\n  zeus::CSphere s0 = p0.Transform(collision.GetLeft().GetTransform());\n  zeus::CSphere s1 = p1.Transform(collision.GetRight().GetTransform());\n\n  float radiusSum = s0.radius + s1.radius;\n  zeus::CVector3f delta = s0.position - s1.position;\n  float deltaMagSq = delta.magSquared();\n  if (deltaMagSq <= radiusSum * radiusSum) {\n    zeus::CVector3f deltaNorm = delta.canBeNormalized() ? (1.f / std::sqrt(deltaMagSq)) * delta : zeus::skRight;\n    zeus::CVector3f collisionPoint = deltaNorm * s1.radius + s1.position;\n    CCollisionInfo info(collisionPoint, p0.GetMaterial(), p1.GetMaterial(), deltaNorm);\n    list.Add(info, false);\n\n    return true;\n  }\n\n  return false;\n}\n\nbool Sphere_Sphere_Bool(const CInternalCollisionStructure& collision) {\n  const auto& p0 = static_cast<const CCollidableSphere&>(collision.GetLeft().GetPrim());\n  const auto& p1 = static_cast<const CCollidableSphere&>(collision.GetRight().GetPrim());\n\n  zeus::CSphere s0 = p0.Transform(collision.GetLeft().GetTransform());\n  zeus::CSphere s1 = p1.Transform(collision.GetRight().GetTransform());\n\n  float radiusSum = s0.radius + s1.radius;\n  return (s0.position - s1.position).magSquared() <= radiusSum * radiusSum;\n}\n\n} // namespace Collide\n\nCCollidableSphere::CCollidableSphere(const zeus::CSphere& sphere, const CMaterialList& list)\n: CCollisionPrimitive(list), x10_sphere(sphere) {}\n\nzeus::CSphere CCollidableSphere::Transform(const zeus::CTransform& xf) const {\n  return zeus::CSphere(xf * x10_sphere.position, x10_sphere.radius);\n}\n\nu32 CCollidableSphere::GetTableIndex() const { return sTableIndex; }\n\nzeus::CAABox CCollidableSphere::CalculateAABox(const zeus::CTransform& xf) const {\n  zeus::CVector3f xfPos = xf * x10_sphere.position;\n  return {xfPos - x10_sphere.radius, xfPos + x10_sphere.radius};\n}\n\nzeus::CAABox CCollidableSphere::CalculateLocalAABox() const {\n  return {x10_sphere.position - x10_sphere.radius, x10_sphere.position + x10_sphere.radius};\n}\n\nFourCC CCollidableSphere::GetPrimType() const { return SBIG('SPHR'); }\n\nCRayCastResult CCollidableSphere::CastRayInternal(const CInternalRayCastStructure& rayCast) const {\n  if (!rayCast.GetFilter().Passes(GetMaterial())) {\n    return {};\n  }\n\n  zeus::CSphere xfSphere = Transform(rayCast.GetTransform());\n  float t = 0.f;\n  zeus::CVector3f point;\n  if (CollisionUtil::RaySphereIntersection(xfSphere, rayCast.GetRay().start, rayCast.GetRay().dir, rayCast.GetMaxTime(),\n                                           t, point)) {\n    zeus::CVector3f delta = point - xfSphere.position;\n    float deltaMag = delta.magnitude();\n    zeus::CUnitVector3f planeNormal = (deltaMag > 0.01f) ? delta * (1.f / deltaMag) : rayCast.GetRay().dir;\n    float planeD = point.dot(planeNormal);\n    return CRayCastResult(t, point, zeus::CPlane(planeNormal, planeD), GetMaterial());\n  }\n\n  return {};\n}\n\nconst CCollisionPrimitive::Type& CCollidableSphere::GetType() { return sType; }\n\nbool CCollidableSphere::CollideMovingAABox(const CInternalCollisionStructure& collision, const zeus::CVector3f& dir,\n                                           double& dOut, CCollisionInfo& infoOut) {\n  const auto& p0 = static_cast<const CCollidableSphere&>(collision.GetLeft().GetPrim());\n  const auto& p1 = static_cast<const CCollidableAABox&>(collision.GetRight().GetPrim());\n\n  zeus::CSphere s0 = p0.Transform(collision.GetLeft().GetTransform());\n  zeus::CAABox b1 = p1.CalculateAABox(collision.GetRight().GetTransform());\n\n  double d = dOut;\n  zeus::CVector3f point;\n  zeus::CVector3f normal;\n  if (CollisionUtil::MovingSphereAABox(s0, b1, dir, d, point, normal) && d < dOut) {\n    dOut = d;\n    infoOut = CCollisionInfo(point, p0.GetMaterial(), p1.GetMaterial(), normal);\n    return true;\n  }\n\n  return false;\n}\n\nbool CCollidableSphere::CollideMovingSphere(const CInternalCollisionStructure& collision, const zeus::CVector3f& dir,\n                                            double& dOut, CCollisionInfo& infoOut) {\n  const auto& p0 = static_cast<const CCollidableSphere&>(collision.GetLeft().GetPrim());\n  const auto& p1 = static_cast<const CCollidableSphere&>(collision.GetRight().GetPrim());\n\n  zeus::CSphere s0 = p0.Transform(collision.GetLeft().GetTransform());\n  zeus::CSphere s1 = p1.Transform(collision.GetRight().GetTransform());\n\n  double d = dOut;\n  if (CollisionUtil::RaySphereIntersection_Double(zeus::CSphere(s1.position, s0.radius + s1.radius), s0.position, dir,\n                                                  d) &&\n      d >= 0.0 && d < dOut) {\n    dOut = d;\n    zeus::CVector3f normal = (s0.position + float(d) * dir - s1.position).normalized();\n    infoOut = CCollisionInfo(s1.position + s1.radius * normal, p0.GetMaterial(), p1.GetMaterial(), normal);\n    return true;\n  }\n\n  return false;\n}\n\nbool CCollidableSphere::Sphere_AABox_Bool(const zeus::CSphere& sphere, const zeus::CAABox& aabb) {\n  float mag = 0.f;\n\n  for (int i = 0; i < 3; ++i) {\n    if (sphere.position[i] < aabb.min[i]) {\n      float tmp = sphere.position[i] - aabb.min[i];\n      mag += tmp * tmp;\n    } else if (sphere.position[i] > aabb.max[i]) {\n      float tmp = sphere.position[i] - aabb.max[i];\n      mag += tmp * tmp;\n    }\n  }\n\n  return mag <= sphere.radius * sphere.radius;\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CCollidableSphere.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Collision/CCollisionPrimitive.hpp\"\n\n#include <zeus/CSphere.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nnamespace Collide {\nbool Sphere_AABox(const CInternalCollisionStructure&, CCollisionInfoList&);\nbool Sphere_AABox_Bool(const CInternalCollisionStructure&);\nbool Sphere_Sphere(const CInternalCollisionStructure&, CCollisionInfoList&);\nbool Sphere_Sphere_Bool(const CInternalCollisionStructure&);\n} // namespace Collide\n\nclass CCollidableSphere : public CCollisionPrimitive {\n  static inline u32 sTableIndex = UINT32_MAX;\n\n  zeus::CSphere x10_sphere;\n\npublic:\n  CCollidableSphere(const zeus::CSphere&, const CMaterialList&);\n\n  const zeus::CSphere& GetSphere() const { return x10_sphere; }\n  void SetSphereCenter(const zeus::CVector3f& center) { x10_sphere.position = center; }\n  void SetSphereRadius(float radius) { x10_sphere.radius = radius; }\n  zeus::CSphere Transform(const zeus::CTransform& xf) const;\n\n  u32 GetTableIndex() const override;\n  zeus::CAABox CalculateAABox(const zeus::CTransform&) const override;\n  zeus::CAABox CalculateLocalAABox() const override;\n  FourCC GetPrimType() const override;\n  CRayCastResult CastRayInternal(const CInternalRayCastStructure&) const override;\n\n  static const Type& GetType();\n  static void SetStaticTableIndex(u32 index) { sTableIndex = index; }\n  static bool CollideMovingAABox(const CInternalCollisionStructure&, const zeus::CVector3f&, double&, CCollisionInfo&);\n  static bool CollideMovingSphere(const CInternalCollisionStructure&, const zeus::CVector3f&, double&, CCollisionInfo&);\n  static bool Sphere_AABox_Bool(const zeus::CSphere& sphere, const zeus::CAABox& aabb);\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CCollisionActor.cpp",
    "content": "#include \"Runtime/Collision/CCollisionActor.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Camera/CGameCamera.hpp\"\n#include \"Runtime/Collision/CCollidableOBBTreeGroup.hpp\"\n#include \"Runtime/Collision/CCollidableSphere.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nconstexpr CMaterialList skDefaultCollisionActorMaterials =\n    CMaterialList(EMaterialTypes::Solid, EMaterialTypes::CollisionActor, EMaterialTypes::ScanPassthrough,\n                  EMaterialTypes::CameraPassthrough);\n\nCCollisionActor::CCollisionActor(TUniqueId uid, TAreaId areaId, TUniqueId owner, const zeus::CVector3f& extent,\n                                 const zeus::CVector3f& center, bool active, float mass, std::string_view name)\n: CPhysicsActor(uid, active, \"CollisionActor\", CEntityInfo(areaId, CEntity::NullConnectionList), zeus::CTransform(),\n                CModelData::CModelDataNull(), skDefaultCollisionActorMaterials, zeus::skNullBox, SMoverData(mass),\n                CActorParameters::None(), 0.3f, 0.1f)\n, x258_primitiveType(EPrimitiveType::OBBTreeGroup)\n, x25c_owner(owner)\n, x260_boxSize(extent)\n, x26c_center(center)\n, x278_obbContainer(std::make_unique<CCollidableOBBTreeGroupContainer>(extent, center))\n, x27c_obbTreeGroupPrimitive(std::make_unique<CCollidableOBBTreeGroup>(x278_obbContainer.get(), GetMaterialList())) {\n  x10_name += ' ';\n  x10_name += name;\n  SetCoefficientOfRestitutionModifier(0.5f);\n  SetCallTouch(false);\n  SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(\n      {EMaterialTypes::Solid}, {EMaterialTypes::CollisionActor, EMaterialTypes::NoStaticCollision}));\n}\n\nCCollisionActor::CCollisionActor(TUniqueId uid, TAreaId areaId, TUniqueId owner, const zeus::CVector3f& boxSize,\n                                 bool active, float mass, std::string_view name)\n: CPhysicsActor(uid, active, \"CollisionActor\", CEntityInfo(areaId, CEntity::NullConnectionList), zeus::CTransform(),\n                CModelData::CModelDataNull(), skDefaultCollisionActorMaterials, zeus::skNullBox, SMoverData(mass),\n                CActorParameters::None(), 0.3f, 0.1f)\n, x258_primitiveType(EPrimitiveType::AABox)\n, x25c_owner(owner)\n, x260_boxSize(boxSize)\n, x280_aaboxPrimitive(\n      std::make_unique<CCollidableAABox>(zeus::CAABox(-0.5f * boxSize, 0.5f * boxSize),\n                                         CMaterialList(EMaterialTypes::Solid, EMaterialTypes::NoStaticCollision))) {\n  x10_name += ' ';\n  x10_name += name;\n  SetCoefficientOfRestitutionModifier(0.5f);\n  SetCallTouch(false);\n  SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(\n      {EMaterialTypes::Solid}, {EMaterialTypes::CollisionActor, EMaterialTypes::NoStaticCollision}));\n}\n\nCCollisionActor::CCollisionActor(TUniqueId uid, TAreaId areaId, TUniqueId owner, bool active, float radius, float mass,\n                                 std::string_view name)\n: CPhysicsActor(uid, active, \"CollisionActor\", CEntityInfo(areaId, CEntity::NullConnectionList), zeus::CTransform(),\n                CModelData::CModelDataNull(), skDefaultCollisionActorMaterials, zeus::skNullBox, SMoverData(mass),\n                CActorParameters::None(), 0.3f, 0.1f)\n, x258_primitiveType(EPrimitiveType::Sphere)\n, x25c_owner(owner)\n, x284_spherePrimitive(std::make_unique<CCollidableSphere>(\n      zeus::CSphere(zeus::skZero3f, radius), CMaterialList(EMaterialTypes::NoStaticCollision, EMaterialTypes::Solid)))\n, x288_sphereRadius(radius) {\n  x10_name += ' ';\n  x10_name += name;\n  SetCoefficientOfRestitutionModifier(0.5f);\n  SetCallTouch(false);\n  SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(\n      {EMaterialTypes::Solid}, {EMaterialTypes::CollisionActor, EMaterialTypes::NoStaticCollision}));\n}\n\nvoid CCollisionActor::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CCollisionActor::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) {\n  switch (msg) {\n  case EScriptObjectMessage::Falling:\n  case EScriptObjectMessage::Registered:\n  case EScriptObjectMessage::Deleted:\n  case EScriptObjectMessage::InitializedInArea:\n    break;\n  case EScriptObjectMessage::Touched:\n  case EScriptObjectMessage::Damage:\n  case EScriptObjectMessage::InvulnDamage: {\n    if (CEntity* ent = mgr.ObjectById(x25c_owner)) {\n      x2fc_lastTouched = sender;\n      mgr.SendScriptMsg(ent, GetUniqueId(), msg);\n    }\n  } break;\n  default:\n    mgr.SendScriptMsgAlways(x25c_owner, GetUniqueId(), msg);\n    break;\n  }\n\n  CActor::AcceptScriptMsg(msg, sender, mgr);\n}\n\nCHealthInfo* CCollisionActor::HealthInfo(CStateManager&) { return &x28c_healthInfo; }\n\nconst CDamageVulnerability* CCollisionActor::GetDamageVulnerability() const { return &x294_damageVuln; }\n\nconst CDamageVulnerability* CCollisionActor::GetDamageVulnerability(const zeus::CVector3f&, const zeus::CVector3f&,\n                                                                    const CDamageInfo&) const {\n  return GetDamageVulnerability();\n}\n\nvoid CCollisionActor::SetDamageVulnerability(const CDamageVulnerability& vuln) { x294_damageVuln = vuln; }\n\nzeus::CVector3f CCollisionActor::GetScanObjectIndicatorPosition(const CStateManager& mgr) const {\n  const auto* gameCamera = static_cast<const CGameCamera*>(mgr.GetCameraManager()->GetCurrentCamera(mgr));\n\n  float scanScale;\n  if (x258_primitiveType == EPrimitiveType::Sphere) {\n    scanScale = GetSphereRadius();\n  } else {\n    const zeus::CVector3f v = GetBoxSize();\n    float comp = v.x() >= v.y() ? v.z() : v.y();\n\n    if (comp < v.z()) {\n      comp = v.x();\n    }\n\n    scanScale = 0.5f * comp;\n  }\n  scanScale *= 3.0f;\n  zeus::CVector3f orbitPos = GetOrbitPosition(mgr);\n  return orbitPos - scanScale * (orbitPos - gameCamera->GetTranslation()).normalized();\n}\n\nconst CCollisionPrimitive* CCollisionActor::GetCollisionPrimitive() const {\n  if (x258_primitiveType == EPrimitiveType::OBBTreeGroup) {\n    return x27c_obbTreeGroupPrimitive.get();\n  }\n  if (x258_primitiveType == EPrimitiveType::AABox) {\n    return x280_aaboxPrimitive.get();\n  }\n  return x284_spherePrimitive.get();\n}\n\nEWeaponCollisionResponseTypes CCollisionActor::GetCollisionResponseType(const zeus::CVector3f&, const zeus::CVector3f&,\n                                                                        const CWeaponMode&, EProjectileAttrib) const {\n  return x300_responseType;\n}\n\nzeus::CTransform CCollisionActor::GetPrimitiveTransform() const {\n  zeus::CTransform xf = x34_transform;\n  xf.origin = CPhysicsActor::GetPrimitiveTransform().origin;\n  return xf;\n}\n\nstd::optional<zeus::CAABox> CCollisionActor::GetTouchBounds() const {\n  std::optional<zeus::CAABox> aabox;\n  if (x258_primitiveType == EPrimitiveType::OBBTreeGroup)\n    aabox = {x27c_obbTreeGroupPrimitive->CalculateAABox(x34_transform)};\n  else if (x258_primitiveType == EPrimitiveType::AABox)\n    aabox = {x280_aaboxPrimitive->CalculateAABox(x34_transform)};\n  else if (x258_primitiveType == EPrimitiveType::Sphere)\n    aabox = {x284_spherePrimitive->CalculateAABox(x34_transform)};\n\n  aabox->accumulateBounds(aabox->max + x304_extendedTouchBounds);\n  aabox->accumulateBounds(aabox->min - x304_extendedTouchBounds);\n\n  return aabox;\n}\n\nvoid CCollisionActor::OnScanStateChanged(EScanState state, CStateManager& mgr) {\n  if (const TCastToPtr<CActor> actor = mgr.ObjectById(x25c_owner)) {\n    actor->OnScanStateChanged(state, mgr);\n  }\n\n  CActor::OnScanStateChanged(state, mgr);\n}\n\nvoid CCollisionActor::Touch(CActor& actor, CStateManager& mgr) {\n  x2fc_lastTouched = actor.GetUniqueId();\n  mgr.SendScriptMsgAlways(x25c_owner, GetUniqueId(), EScriptObjectMessage::Touched);\n}\n\nzeus::CVector3f CCollisionActor::GetOrbitPosition(const CStateManager&) const { return GetTouchBounds()->center(); }\n\nvoid CCollisionActor::SetSphereRadius(float radius) {\n  if (x258_primitiveType != EPrimitiveType::Sphere) {\n    return;\n  }\n\n  x288_sphereRadius = radius;\n  x284_spherePrimitive->SetSphereRadius(radius);\n}\n\nvoid CCollisionActor::DebugDraw() {\n  // zeus::CAABox aabox;\n  // if (x258_primitiveType == EPrimitiveType::OBBTreeGroup)\n  //   aabox = x27c_obbTreeGroupPrimitive->CalculateAABox(x34_transform);\n  // else if (x258_primitiveType == EPrimitiveType::AABox)\n  //   aabox = x280_aaboxPrimitive->CalculateAABox(x34_transform);\n  // else if (x258_primitiveType == EPrimitiveType::Sphere)\n  //   aabox = x284_spherePrimitive->CalculateAABox(x34_transform);\n  // m_aabox.setAABB(aabox);\n  // zeus::CColor col = !GetActive() ? zeus::skRed : zeus::skGreen;\n  // col.a() = 0.5f;\n  // m_aabox.draw(col);\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CCollisionActor.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <string_view>\n\n#include \"Runtime/World/CDamageVulnerability.hpp\"\n#include \"Runtime/World/CHealthInfo.hpp\"\n#include \"Runtime/World/CPhysicsActor.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CCollidableSphere;\nclass CCollidableOBBTreeGroup;\nclass CCollidableOBBTreeGroupContainer;\n\nclass CCollisionActor : public CPhysicsActor {\n  enum class EPrimitiveType { OBBTreeGroup, AABox, Sphere };\n\n  EPrimitiveType x258_primitiveType;\n  TUniqueId x25c_owner;\n  zeus::CVector3f x260_boxSize;\n  zeus::CVector3f x26c_center;\n  std::unique_ptr<CCollidableOBBTreeGroupContainer> x278_obbContainer;\n  std::unique_ptr<CCollidableOBBTreeGroup> x27c_obbTreeGroupPrimitive;\n  std::unique_ptr<CCollidableAABox> x280_aaboxPrimitive;\n  std::unique_ptr<CCollidableSphere> x284_spherePrimitive;\n  float x288_sphereRadius;\n  CHealthInfo x28c_healthInfo = CHealthInfo(0.f);\n  CDamageVulnerability x294_damageVuln = CDamageVulnerability::NormalVulnerabilty();\n  TUniqueId x2fc_lastTouched = kInvalidUniqueId;\n  EWeaponCollisionResponseTypes x300_responseType = EWeaponCollisionResponseTypes::EnemyNormal;\n  zeus::CVector3f x304_extendedTouchBounds = zeus::skZero3f;\n\npublic:\n  DEFINE_ENTITY\n  CCollisionActor(TUniqueId uid, TAreaId areaId, TUniqueId owner, const zeus::CVector3f& extent,\n                  const zeus::CVector3f& center, bool active, float mass, std::string_view name);\n  CCollisionActor(TUniqueId uid, TAreaId areaId, TUniqueId owner, const zeus::CVector3f& boxSize, bool active,\n                  float mass, std::string_view name);\n  CCollisionActor(TUniqueId uid, TAreaId areaId, TUniqueId owner, bool active, float radius, float mass,\n                  std::string_view name);\n\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) override;\n  void DebugDraw();\n  CHealthInfo* HealthInfo(CStateManager& mgr) override;\n  const CDamageVulnerability* GetDamageVulnerability() const override;\n  const CDamageVulnerability* GetDamageVulnerability(const zeus::CVector3f& vec1, const zeus::CVector3f& vec2,\n                                                     const CDamageInfo& dInfo) const override;\n  void OnScanStateChanged(EScanState state, CStateManager& mgr) override;\n\n  void Touch(CActor& actor, CStateManager& mgr) override;\n  zeus::CVector3f GetOrbitPosition(const CStateManager& mgr) const override;\n  const CCollisionPrimitive* GetCollisionPrimitive() const override;\n  EWeaponCollisionResponseTypes GetCollisionResponseType(const zeus::CVector3f& vec1, const zeus::CVector3f& vec2,\n                                                         const CWeaponMode& mode,\n                                                         EProjectileAttrib attribute) const override;\n  void SetWeaponCollisionResponseType(EWeaponCollisionResponseTypes type) { x300_responseType = type; }\n  zeus::CTransform GetPrimitiveTransform() const override;\n  std::optional<zeus::CAABox> GetTouchBounds() const override;\n  void SetDamageVulnerability(const CDamageVulnerability& vuln);\n  const zeus::CVector3f& GetBoxSize() const { return x260_boxSize; }\n  TUniqueId GetOwnerId() const { return x25c_owner; }\n  TUniqueId GetLastTouchedObject() const { return x2fc_lastTouched; }\n  zeus::CVector3f GetScanObjectIndicatorPosition(const CStateManager& mgr) const override;\n  void SetExtendedTouchBounds(const zeus::CVector3f& boundExt) { x304_extendedTouchBounds = boundExt; }\n  void SetSphereRadius(float radius);\n  float GetSphereRadius() const { return x288_sphereRadius; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CCollisionActorManager.cpp",
    "content": "#include \"Runtime/Collision/CCollisionActorManager.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Collision/CCollisionActor.hpp\"\n#include \"Runtime/Collision/CMaterialList.hpp\"\n#include \"Runtime/World/CActor.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCCollisionActorManager::CCollisionActorManager(CStateManager& mgr, TUniqueId owner, TAreaId area,\n                                               const std::vector<CJointCollisionDescription>& descs, bool active)\n: x10_ownerId(owner), x12_active(active) {\n  if (TCastToConstPtr<CActor> act = mgr.GetObjectById(x10_ownerId)) {\n    const zeus::CTransform xf = act->GetTransform();\n    const CAnimData* animData = act->GetModelData()->GetAnimationData();\n    const zeus::CVector3f scale = act->GetModelData()->GetScale();\n    const zeus::CTransform scaleXf = zeus::CTransform::Scale(scale);\n\n    x0_jointDescriptions.reserve(descs.size());\n    for (const CJointCollisionDescription& desc : descs) {\n      CJointCollisionDescription modDesc = desc;\n      modDesc.ScaleAllBounds(scale);\n      const zeus::CTransform locXf = GetWRLocatorTransform(*animData, modDesc.GetPivotId(), xf, scaleXf);\n\n      if (modDesc.GetNextId().IsInvalid()) {\n        // We only have the pivot id\n        const TUniqueId newId = mgr.AllocateUniqueId();\n        CCollisionActor* newAct = nullptr;\n\n        if (modDesc.GetType() == CJointCollisionDescription::ECollisionType::Sphere) {\n          newAct = new CCollisionActor(newId, area, x10_ownerId, active, modDesc.GetRadius(), modDesc.GetMass(),\n                                       desc.GetName());\n        } else if (modDesc.GetType() == CJointCollisionDescription::ECollisionType::OBB) {\n          newAct = new CCollisionActor(newId, area, x10_ownerId, modDesc.GetBounds(), modDesc.GetPivotPoint(), active,\n                                       modDesc.GetMass(), desc.GetName());\n        } else {\n          newAct = new CCollisionActor(newId, area, x10_ownerId, modDesc.GetBounds(), active, modDesc.GetMass(),\n                                       desc.GetName());\n        }\n\n        newAct->SetTransform(locXf);\n        mgr.AddObject(newAct);\n        x0_jointDescriptions.push_back(desc);\n        x0_jointDescriptions.back().SetCollisionActorId(newId);\n      } else { // We have another bone in to connect to!\n        const zeus::CTransform locXf2 = GetWRLocatorTransform(*animData, modDesc.GetNextId(), xf, scaleXf);\n        const float dist = (locXf2.origin - locXf.origin).magnitude();\n\n        if (modDesc.GetType() != CJointCollisionDescription::ECollisionType::OBBAutoSize) {\n          const TUniqueId newId = mgr.AllocateUniqueId();\n          auto* newAct = new CCollisionActor(newId, area, x10_ownerId, active, modDesc.GetRadius(), modDesc.GetMass(),\n                                             desc.GetName());\n\n          newAct->SetTransform(locXf);\n          mgr.AddObject(newAct);\n          x0_jointDescriptions.push_back(CJointCollisionDescription::SphereCollision(\n              modDesc.GetPivotId(), modDesc.GetRadius(), modDesc.GetName(), 0.001f));\n          x0_jointDescriptions.back().SetCollisionActorId(newId);\n\n          const u32 numSeps = u32(dist / modDesc.GetMaxSeparation());\n          if (numSeps != 0) {\n            x0_jointDescriptions.reserve(x0_jointDescriptions.capacity() + numSeps);\n            const float pitch = dist / float(numSeps + 1);\n            for (u32 i = 0; i < numSeps; ++i) {\n              const float separation = pitch * float(i + 1);\n              x0_jointDescriptions.push_back(CJointCollisionDescription::SphereSubdivideCollision(\n                  modDesc.GetPivotId(), modDesc.GetNextId(), modDesc.GetRadius(), separation,\n                  CJointCollisionDescription::EOrientationType::One, modDesc.GetName(), 0.001f));\n\n              const TUniqueId newId2 = mgr.AllocateUniqueId();\n              auto* newAct2 = new CCollisionActor(newId2, area, x10_ownerId, active, modDesc.GetRadius(),\n                                                  modDesc.GetMass(), desc.GetName());\n              if (modDesc.GetOrientationType() == CJointCollisionDescription::EOrientationType::Zero) {\n                newAct2->SetTransform(zeus::CTransform::Translate(locXf.origin + (separation * locXf.basis[1])));\n              } else {\n                const zeus::CVector3f delta = (locXf2.origin - locXf.origin).normalized();\n                zeus::CVector3f upVector = locXf.basis[2];\n\n                if (zeus::close_enough(std::fabs(delta.dot(upVector)), 1.f)) {\n                  upVector = locXf.basis[1];\n                }\n\n                const zeus::CTransform lookAt = zeus::lookAt(zeus::skZero3f, delta, upVector);\n                newAct2->SetTransform(zeus::CTransform::Translate(locXf.origin + (separation * lookAt.basis[1])));\n              }\n\n              mgr.AddObject(newAct2);\n              x0_jointDescriptions.back().SetCollisionActorId(newId2);\n            }\n          }\n        } else {\n          if (dist <= FLT_EPSILON) {\n            continue;\n          }\n\n          zeus::CVector3f bounds = modDesc.GetBounds();\n          bounds.y() += dist;\n          auto* newAct =\n              new CCollisionActor(mgr.AllocateUniqueId(), area, x10_ownerId, bounds,\n                                  zeus::CVector3f(0.f, 0.5f * dist, 0.f), active, modDesc.GetMass(), desc.GetName());\n\n          if (modDesc.GetOrientationType() == CJointCollisionDescription::EOrientationType::Zero) {\n            newAct->SetTransform(locXf);\n          } else {\n            const zeus::CVector3f delta = (locXf2.origin - locXf.origin).normalized();\n            zeus::CVector3f upVector = locXf.basis[2];\n\n            if (zeus::close_enough(std::fabs(delta.dot(upVector)), 1.f)) {\n              upVector = locXf.basis[1];\n            }\n\n            newAct->SetTransform(zeus::lookAt(locXf.origin, locXf.origin + delta, upVector));\n          }\n\n          mgr.AddObject(newAct);\n          x0_jointDescriptions.push_back(desc);\n          x0_jointDescriptions.back().SetCollisionActorId(newAct->GetUniqueId());\n        }\n      }\n    }\n  }\n}\n\nvoid CCollisionActorManager::Destroy(CStateManager& mgr) {\n  for (const CJointCollisionDescription& desc : x0_jointDescriptions) {\n    mgr.FreeScriptObject(desc.GetCollisionActorId());\n  }\n\n  x13_destroyed = true;\n}\n\nvoid CCollisionActorManager::SetActive(CStateManager& mgr, bool active) {\n  x12_active = active;\n  for (const CJointCollisionDescription& jDesc : x0_jointDescriptions) {\n    if (TCastToPtr<CActor> act = mgr.ObjectById(jDesc.GetCollisionActorId())) {\n      const bool curActive = act->GetActive();\n      if (curActive != active) {\n        act->SetActive(active);\n        if (!active) {\n          Update(0.f, mgr, EUpdateOptions::WorldSpace);\n        }\n      }\n    }\n  }\n}\n\nvoid CCollisionActorManager::AddMaterial(CStateManager& mgr, const CMaterialList& list) {\n  for (const CJointCollisionDescription& jDesc : x0_jointDescriptions) {\n    if (TCastToPtr<CActor> act = mgr.ObjectById(jDesc.GetCollisionActorId())) {\n      act->AddMaterial(list);\n    }\n  }\n}\n\nvoid CCollisionActorManager::SetMovable(CStateManager& mgr, bool movable) {\n  if (x14_movable == movable) {\n    return;\n  }\n\n  x14_movable = movable;\n\n  for (const CJointCollisionDescription& jDesc : x0_jointDescriptions) {\n    if (TCastToPtr<CCollisionActor> act = mgr.ObjectById(jDesc.GetCollisionActorId())) {\n      act->SetMovable(x14_movable);\n      act->SetUseInSortedLists(x14_movable);\n    }\n  }\n}\n\nvoid CCollisionActorManager::Update(float dt, CStateManager& mgr, EUpdateOptions opts) {\n  if (!x14_movable) {\n    SetMovable(mgr, true);\n  }\n\n  if (!x12_active) {\n    return;\n  }\n\n  if (const TCastToConstPtr<CActor> act = mgr.ObjectById(x10_ownerId)) {\n    const CAnimData& animData = *act->GetModelData()->GetAnimationData();\n    const zeus::CTransform actXf = act->GetTransform();\n    const zeus::CTransform scaleXf = zeus::CTransform::Scale(act->GetModelData()->GetScale());\n\n    for (const CJointCollisionDescription& jDesc : x0_jointDescriptions) {\n      if (TCastToPtr<CCollisionActor> cAct = mgr.ObjectById(jDesc.GetCollisionActorId())) {\n        const zeus::CTransform pivotXf = GetWRLocatorTransform(animData, jDesc.GetPivotId(), actXf, scaleXf);\n        zeus::CVector3f origin = pivotXf.origin;\n\n        if (jDesc.GetType() == CJointCollisionDescription::ECollisionType::OBB ||\n            jDesc.GetType() == CJointCollisionDescription::ECollisionType::OBBAutoSize) {\n          if (jDesc.GetOrientationType() == CJointCollisionDescription::EOrientationType::Zero) {\n            cAct->SetTransform(zeus::CQuaternion(pivotXf.basis).toTransform(cAct->GetTranslation()));\n          } else {\n            const zeus::CTransform nextXf = GetWRLocatorTransform(animData, jDesc.GetNextId(), actXf, scaleXf);\n            cAct->SetTransform(zeus::CQuaternion(zeus::lookAt(pivotXf.origin, nextXf.origin, pivotXf.basis[2]).basis)\n                                   .toTransform(cAct->GetTranslation()));\n          }\n        } else if (jDesc.GetType() == CJointCollisionDescription::ECollisionType::SphereSubdivide) {\n          if (jDesc.GetOrientationType() == CJointCollisionDescription::EOrientationType::Zero) {\n            origin += jDesc.GetMaxSeparation() * pivotXf.basis[1];\n          } else {\n            const zeus::CTransform nextXf = GetWRLocatorTransform(animData, jDesc.GetNextId(), actXf, scaleXf);\n            origin += zeus::lookAt(origin, nextXf.origin, pivotXf.basis[2]).basis[1] * jDesc.GetMaxSeparation();\n          }\n        }\n\n        if (opts == EUpdateOptions::ObjectSpace) {\n          cAct->MoveToOR(cAct->GetTransform().transposeRotate(origin - cAct->GetTranslation()), dt);\n        } else {\n          cAct->SetTranslation(origin);\n        }\n      }\n    }\n  }\n}\n\nzeus::CTransform CCollisionActorManager::GetWRLocatorTransform(const CAnimData& animData, CSegId id,\n                                                               const zeus::CTransform& worldXf,\n                                                               const zeus::CTransform& localXf) {\n  zeus::CTransform locXf = animData.GetLocatorTransform(id, nullptr);\n  const zeus::CVector3f origin = worldXf * (localXf * locXf.origin);\n  locXf = worldXf.multiplyIgnoreTranslation(locXf);\n  locXf.origin = origin;\n  return locXf;\n}\n\nstd::optional<zeus::CVector3f> CCollisionActorManager::GetDeviation(const CStateManager& mgr, CSegId seg) {\n  for (const CJointCollisionDescription& desc : x0_jointDescriptions) {\n    if (desc.GetPivotId() != seg) {\n      continue;\n    }\n\n    if (const TCastToConstPtr<CActor> act = mgr.GetObjectById(x10_ownerId)) {\n      if (const TCastToConstPtr<CCollisionActor> colAct = mgr.GetObjectById(desc.GetCollisionActorId())) {\n        const zeus::CTransform xf =\n            GetWRLocatorTransform(*act->GetModelData()->GetAnimationData(), desc.GetPivotId(), act->GetTransform(),\n                                  zeus::CTransform::Scale(act->GetModelData()->GetScale()));\n\n        return {colAct->GetTranslation() - xf.origin};\n      }\n    }\n  }\n\n  return std::nullopt;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CCollisionActorManager.hpp",
    "content": "#pragma once\n\n#include <optional>\n#include <vector>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Collision/CJointCollisionDescription.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CAnimData;\nclass CCollisionActor;\nclass CMaterialList;\nclass CStateManager;\n\nclass CCollisionActorManager {\npublic:\n  enum class EUpdateOptions { ObjectSpace, WorldSpace };\n\nprivate:\n  std::vector<CJointCollisionDescription> x0_jointDescriptions;\n  TUniqueId x10_ownerId;\n  bool x12_active;\n  bool x13_destroyed = false;\n  bool x14_movable = true;\n\npublic:\n  CCollisionActorManager(CStateManager& mgr, TUniqueId owner, TAreaId area,\n                         const std::vector<CJointCollisionDescription>& descs, bool active);\n\n  void Update(float dt, CStateManager& mgr, EUpdateOptions opts);\n  void Destroy(CStateManager& mgr);\n  void SetActive(CStateManager& mgr, bool active);\n  [[nodiscard]] bool GetActive() const { return x12_active; }\n  void AddMaterial(CStateManager& mgr, const CMaterialList& list);\n  void SetMovable(CStateManager& mgr, bool movable);\n\n  [[nodiscard]] u32 GetNumCollisionActors() const { return x0_jointDescriptions.size(); }\n  std::optional<zeus::CVector3f> GetDeviation(const CStateManager&, CSegId);\n  [[nodiscard]] const CJointCollisionDescription& GetCollisionDescFromIndex(u32 i) const {\n    return x0_jointDescriptions[i];\n  }\n  static zeus::CTransform GetWRLocatorTransform(const CAnimData& animData, CSegId id, const zeus::CTransform& worldXf,\n                                                const zeus::CTransform& localXf);\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CCollisionEdge.cpp",
    "content": "#include \"Runtime/Collision/CCollisionEdge.hpp\"\n#include \"Runtime/Streams/CInputStream.hpp\"\n\nnamespace metaforce {\nCCollisionEdge::CCollisionEdge(CInputStream& in) {\n  x0_index1 = in.ReadShort();\n  x2_index2 = in.ReadShort();\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CCollisionEdge.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n\nnamespace metaforce {\nclass CCollisionEdge {\n  u16 x0_index1 = -1;\n  u16 x2_index2 = -1;\n\npublic:\n  constexpr CCollisionEdge() noexcept = default;\n  explicit CCollisionEdge(CInputStream&);\n  constexpr CCollisionEdge(u16 v0, u16 v1) noexcept : x0_index1(v0), x2_index2(v1) {}\n\n  [[nodiscard]] constexpr u16 GetVertIndex1() const noexcept { return x0_index1; }\n  [[nodiscard]] constexpr u16 GetVertIndex2() const noexcept { return x2_index2; }\n\n  constexpr void swapBig() noexcept {\n    x0_index1 = SBig(x0_index1);\n    x2_index2 = SBig(x2_index2);\n  }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CCollisionInfo.cpp",
    "content": "#include \"Runtime/Collision/CCollisionInfo.hpp\"\n\n#include <utility>\n\nnamespace metaforce {\n\nCCollisionInfo CCollisionInfo::GetSwapped() const {\n  CCollisionInfo ret;\n  ret.x0_point = x0_point;\n  ret.xc_extentX = xc_extentX;\n  ret.x30_valid = x30_valid;\n  ret.x31_hasExtents = x31_hasExtents;\n  ret.x38_materialLeft = x40_materialRight;\n  ret.x40_materialRight = x38_materialLeft;\n  ret.x48_normalLeft = x54_normalRight;\n  ret.x54_normalRight = x48_normalLeft;\n  return ret;\n}\n\nvoid CCollisionInfo::Swap() {\n  x48_normalLeft = -x48_normalLeft;\n  x54_normalRight = -x54_normalRight;\n  std::swap(x38_materialLeft, x40_materialRight);\n}\n\nzeus::CVector3f CCollisionInfo::GetExtreme() const { return x0_point + xc_extentX + x18_extentY + x24_extentZ; }\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CCollisionInfo.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Collision/CMaterialList.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CMatrix3f.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CCollisionInfo {\n  friend class CCollisionInfoList;\n\n  zeus::CVector3f x0_point;\n  zeus::CVector3f xc_extentX;\n  zeus::CVector3f x18_extentY;\n  zeus::CVector3f x24_extentZ;\n  bool x30_valid = false;\n  bool x31_hasExtents = false;\n  CMaterialList x38_materialLeft;\n  CMaterialList x40_materialRight;\n  zeus::CUnitVector3f x48_normalLeft;\n  zeus::CUnitVector3f x54_normalRight;\n\npublic:\n  CCollisionInfo() = default;\n  CCollisionInfo(const zeus::CVector3f& point, const CMaterialList& list1, const CMaterialList& list2,\n                 const zeus::CUnitVector3f& normalLeft, const zeus::CUnitVector3f& normalRight)\n  : x0_point(point)\n  , x30_valid(true)\n  , x31_hasExtents(false)\n  , x38_materialLeft(list2)\n  , x40_materialRight(list1)\n  , x48_normalLeft(normalLeft)\n  , x54_normalRight(normalRight) {}\n  CCollisionInfo(const zeus::CVector3f& point, const CMaterialList& list1, const CMaterialList& list2,\n                 const zeus::CUnitVector3f& normal)\n  : x0_point(point)\n  , x30_valid(true)\n  , x31_hasExtents(false)\n  , x38_materialLeft(list2)\n  , x40_materialRight(list1)\n  , x48_normalLeft(normal)\n  , x54_normalRight(-normal) {}\n  CCollisionInfo(const zeus::CAABox& aabox, const CMaterialList& list1, const CMaterialList& list2,\n                 const zeus::CUnitVector3f& normalLeft, const zeus::CUnitVector3f& normalRight)\n  : x0_point(aabox.min)\n  , xc_extentX(aabox.max.x() - aabox.min.x(), 0.f, 0.f)\n  , x18_extentY(0.f, aabox.max.y() - aabox.min.y(), 0.f)\n  , x24_extentZ(0.f, 0.f, aabox.max.z() - aabox.min.z())\n  , x30_valid(true)\n  , x31_hasExtents(true)\n  , x38_materialLeft(list2)\n  , x40_materialRight(list1)\n  , x48_normalLeft(normalLeft)\n  , x54_normalRight(normalRight) {}\n\n  CCollisionInfo GetSwapped() const;\n  bool IsValid() const { return x30_valid; }\n  const CMaterialList& GetMaterialLeft() const { return x38_materialLeft; }\n  const CMaterialList& GetMaterialRight() const { return x40_materialRight; }\n  zeus::CVector3f GetExtreme() const;\n  void Swap();\n  const zeus::CUnitVector3f& GetNormalLeft() const { return x48_normalLeft; }\n  const zeus::CUnitVector3f& GetNormalRight() const { return x54_normalRight; }\n  const zeus::CVector3f& GetPoint() const { return x0_point; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CCollisionInfoList.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Collision/CCollisionInfo.hpp\"\n\nnamespace metaforce {\nclass CCollisionInfoList {\n  rstl::reserved_vector<CCollisionInfo, 32> x0_list;\n\npublic:\n  CCollisionInfoList() = default;\n\n  zeus::CVector3f GetAverageLeftNormal() const {\n    zeus::CVector3f ret;\n    for (const auto& inf : x0_list) {\n      ret += inf.GetNormalLeft();\n    }\n\n    return ret / x0_list.size();\n  }\n  zeus::CVector3f GetAveragePoint() const {\n    zeus::CVector3f ret;\n    for (const auto& inf : x0_list) {\n      ret += inf.GetPoint();\n    }\n\n    return ret / x0_list.size();\n  }\n  CMaterialList GetUnionOfAllLeftMaterials() const {\n    CMaterialList list;\n    for (const auto& inf : x0_list) {\n      list.Union(inf.GetMaterialLeft());\n    }\n\n    return list;\n  }\n  size_t GetCount() const { return x0_list.size(); }\n  void Swap(s32 idx) {\n    if (idx >= x0_list.size())\n      return;\n    x0_list[idx].Swap();\n  }\n\n  void Add(const CCollisionInfo& info, bool swap) {\n    if (x0_list.size() == 32)\n      return;\n    if (!swap)\n      x0_list.push_back(info);\n    else\n      x0_list.push_back(info.GetSwapped());\n  }\n  void Clear() { x0_list.clear(); }\n  const CCollisionInfo& Front() const { return x0_list.front(); }\n  const CCollisionInfo& GetItem(int i) const { return x0_list[i]; }\n\n  auto end() noexcept { return x0_list.end(); }\n  auto end() const noexcept { return x0_list.end(); }\n  auto begin() noexcept { return x0_list.begin(); }\n  auto begin() const noexcept { return x0_list.begin(); }\n\n  void AccumulateNewContactsInto(CCollisionInfoList& other_list) {\n    for (CCollisionInfo const& cur_info : x0_list) {\n      bool dont_add_new_info = false;\n      for (CCollisionInfo& other_info : other_list) {\n        if (!zeus::close_enough(other_info.GetPoint(), cur_info.GetPoint(), 0.1f)) {\n          continue;\n        }\n        zeus::CVector3f norm = other_info.GetNormalLeft().normalized();\n        if (zeus::close_enough(norm, cur_info.GetNormalLeft(), 1.2f)) {\n          dont_add_new_info = true;\n          other_info.x0_point = (other_info.x0_point + cur_info.x0_point) * 0.5f;\n          other_info.x38_materialLeft.Add(cur_info.x38_materialLeft);\n          other_info.x40_materialRight.Add(cur_info.x40_materialRight);\n          other_info.x48_normalLeft = other_info.x48_normalLeft + cur_info.x48_normalLeft;\n          break;\n        }\n      }\n      if (!dont_add_new_info) {\n        other_list.Add(cur_info, false);\n      }\n    }\n    for (CCollisionInfo& other_info : other_list.x0_list) {\n      other_info.x48_normalLeft.normalize();\n      other_info.x54_normalRight = -other_info.x48_normalLeft;\n    }\n  }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CCollisionPrimitive.cpp",
    "content": "#include \"Runtime/Collision/CCollisionPrimitive.hpp\"\n\n#include <algorithm>\n#include <climits>\n#include <cstring>\n#include <iterator>\n\n#include \"Runtime/Collision/CCollisionInfoList.hpp\"\n#include \"Runtime/Collision/CInternalRayCastStructure.hpp\"\n#include \"Runtime/Collision/CMaterialFilter.hpp\"\n#include \"Runtime/Collision/InternalColliders.hpp\"\n\nnamespace metaforce {\ns32 CCollisionPrimitive::sNumTypes = 0;\nbool CCollisionPrimitive::sInitComplete = false;\nbool CCollisionPrimitive::sTypesAdded = false;\nbool CCollisionPrimitive::sTypesAdding = false;\nbool CCollisionPrimitive::sCollidersAdded = false;\nbool CCollisionPrimitive::sCollidersAdding = false;\nstd::unique_ptr<std::vector<CCollisionPrimitive::Type>> CCollisionPrimitive::sCollisionTypeList;\nstd::unique_ptr<std::vector<ComparisonFunc>> CCollisionPrimitive::sTableOfCollidables;\nstd::unique_ptr<std::vector<BooleanComparisonFunc>> CCollisionPrimitive::sTableOfBooleanCollidables;\nstd::unique_ptr<std::vector<MovingComparisonFunc>> CCollisionPrimitive::sTableOfMovingCollidables;\nComparisonFunc CCollisionPrimitive::sNullCollider = {};\nBooleanComparisonFunc CCollisionPrimitive::sNullBooleanCollider = {};\nMovingComparisonFunc CCollisionPrimitive::sNullMovingCollider = {};\nCCollisionPrimitive::CCollisionPrimitive(const CMaterialList& list) : x8_material(list) {}\n\nvoid CCollisionPrimitive::SetMaterial(const CMaterialList& material) { x8_material = material; }\n\nconst CMaterialList& CCollisionPrimitive::GetMaterial() const { return x8_material; }\n\nCRayCastResult CCollisionPrimitive::CastRay(const zeus::CVector3f& start, const zeus::CVector3f& dir, float length,\n                                            const CMaterialFilter& filter, const zeus::CTransform& xf) const {\n  return CastRayInternal(CInternalRayCastStructure(start, dir, length, xf, filter));\n}\n\nstd::vector<CCollisionPrimitive::Type>::const_iterator CCollisionPrimitive::TypeIndexFromTypeInfo(const char* name) {\n  return std::find_if(sCollisionTypeList->cbegin(), sCollisionTypeList->cend(),\n                      [name](const auto& type) { return std::strcmp(name, type.GetInfo()) == 0; });\n}\n\nbool CCollisionPrimitive::InternalCollide(const CInternalCollisionStructure& collision, CCollisionInfoList& list) {\n  u32 idx0 = collision.GetLeft().GetPrim().GetTableIndex();\n  u32 idx1 = collision.GetRight().GetPrim().GetTableIndex();\n\n  ComparisonFunc func;\n  if (idx0 == UINT32_MAX || idx1 == UINT32_MAX) {\n    sNullCollider = nullptr;\n    func = sNullCollider;\n  } else {\n    func = (*sTableOfCollidables)[sNumTypes * idx1 + idx0];\n  }\n\n  if (func) {\n    if (!collision.GetLeft().GetFilter().Passes(collision.GetRight().GetPrim().GetMaterial()) ||\n        !collision.GetRight().GetFilter().Passes(collision.GetLeft().GetPrim().GetMaterial()))\n      return false;\n    return func(collision, list);\n  }\n\n  if (idx0 == UINT32_MAX || idx1 == UINT32_MAX) {\n    sNullCollider = nullptr;\n    func = sNullCollider;\n  } else {\n    func = (*sTableOfCollidables)[sNumTypes * idx0 + idx1];\n  }\n\n  if (func) {\n    if (!collision.GetLeft().GetFilter().Passes(collision.GetRight().GetPrim().GetMaterial()) ||\n        !collision.GetRight().GetFilter().Passes(collision.GetLeft().GetPrim().GetMaterial()))\n      return false;\n    CInternalCollisionStructure swappedCollision(collision.GetRight(), collision.GetLeft());\n    u32 startListCount = list.GetCount();\n    if (func(swappedCollision, list)) {\n      for (auto it = list.begin() + startListCount; it != list.end(); ++it)\n        it->Swap();\n      return true;\n    }\n  }\n\n  return false;\n}\n\nbool CCollisionPrimitive::Collide(const CInternalCollisionStructure::CPrimDesc& prim0,\n                                  const CInternalCollisionStructure::CPrimDesc& prim1, CCollisionInfoList& list) {\n  return InternalCollide({prim0, prim1}, list);\n}\n\nbool CCollisionPrimitive::InternalCollideBoolean(const CInternalCollisionStructure& collision) {\n  u32 idx0 = collision.GetLeft().GetPrim().GetTableIndex();\n  u32 idx1 = collision.GetRight().GetPrim().GetTableIndex();\n\n  BooleanComparisonFunc func;\n  if (idx0 == UINT32_MAX || idx1 == UINT32_MAX) {\n    sNullBooleanCollider = nullptr;\n    func = sNullBooleanCollider;\n  } else {\n    func = (*sTableOfBooleanCollidables)[sNumTypes * idx1 + idx0];\n  }\n\n  if (func) {\n    if (!collision.GetLeft().GetFilter().Passes(collision.GetRight().GetPrim().GetMaterial()) ||\n        !collision.GetRight().GetFilter().Passes(collision.GetLeft().GetPrim().GetMaterial()))\n      return false;\n    return func(collision);\n  }\n\n  if (idx0 == UINT32_MAX || idx1 == UINT32_MAX) {\n    sNullBooleanCollider = nullptr;\n    func = sNullBooleanCollider;\n  } else {\n    func = (*sTableOfBooleanCollidables)[sNumTypes * idx0 + idx1];\n  }\n\n  if (func) {\n    if (!collision.GetLeft().GetFilter().Passes(collision.GetRight().GetPrim().GetMaterial()) ||\n        !collision.GetRight().GetFilter().Passes(collision.GetLeft().GetPrim().GetMaterial()))\n      return false;\n    CInternalCollisionStructure swappedCollision(collision.GetRight(), collision.GetLeft());\n    return func(swappedCollision);\n  }\n\n  CCollisionInfoList list;\n  return InternalCollide(collision, list);\n}\n\nbool CCollisionPrimitive::CollideBoolean(const CInternalCollisionStructure::CPrimDesc& prim0,\n                                         const CInternalCollisionStructure::CPrimDesc& prim1) {\n  return InternalCollideBoolean({prim0, prim1});\n}\n\nbool CCollisionPrimitive::InternalCollideMoving(const CInternalCollisionStructure& collision,\n                                                const zeus::CVector3f& dir, double& dOut, CCollisionInfo& infoOut) {\n  u32 idx0 = collision.GetLeft().GetPrim().GetTableIndex();\n  u32 idx1 = collision.GetRight().GetPrim().GetTableIndex();\n\n  MovingComparisonFunc func;\n  if (idx0 == UINT32_MAX || idx1 == UINT32_MAX) {\n    sNullMovingCollider = nullptr;\n    func = sNullMovingCollider;\n  } else {\n    func = (*sTableOfMovingCollidables)[sNumTypes * idx1 + idx0];\n  }\n\n  if (func) {\n    if (!collision.GetLeft().GetFilter().Passes(collision.GetRight().GetPrim().GetMaterial()) ||\n        !collision.GetRight().GetFilter().Passes(collision.GetLeft().GetPrim().GetMaterial()))\n      return false;\n    return func(collision, dir, dOut, infoOut);\n  }\n\n  return false;\n}\n\nbool CCollisionPrimitive::CollideMoving(const CInternalCollisionStructure::CPrimDesc& prim0,\n                                        const CInternalCollisionStructure::CPrimDesc& prim1, const zeus::CVector3f& dir,\n                                        double& dOut, CCollisionInfo& infoOut) {\n  return InternalCollideMoving({prim0, prim1}, dir, dOut, infoOut);\n}\n\nvoid CCollisionPrimitive::InitBeginTypes() {\n  sCollisionTypeList = std::make_unique<std::vector<Type>>();\n  sCollisionTypeList->reserve(3);\n  sTypesAdding = true;\n  InternalColliders::AddTypes();\n}\n\nvoid CCollisionPrimitive::InitAddType(const Type& tp) {\n  tp.GetSetter()(sCollisionTypeList->size());\n  sCollisionTypeList->push_back(tp);\n}\n\nvoid CCollisionPrimitive::InitEndTypes() {\n  sCollisionTypeList->shrink_to_fit();\n  sNumTypes = sCollisionTypeList->size();\n  sTypesAdding = false;\n  sTypesAdded = true;\n}\n\nvoid CCollisionPrimitive::InitBeginColliders() {\n  const size_t tableSz = sCollisionTypeList->size() * sCollisionTypeList->size();\n  sTableOfCollidables = std::make_unique<std::vector<ComparisonFunc>>(tableSz);\n  sTableOfBooleanCollidables = std::make_unique<std::vector<BooleanComparisonFunc>>(tableSz);\n  sTableOfMovingCollidables = std::make_unique<std::vector<MovingComparisonFunc>>(tableSz);\n  sCollidersAdding = true;\n  InternalColliders::AddColliders();\n}\n\nvoid CCollisionPrimitive::InitAddBooleanCollider(const BooleanComparison& cmp) {\n  const auto iter1 = TypeIndexFromTypeInfo(cmp.GetType1());\n  const auto iter2 = TypeIndexFromTypeInfo(cmp.GetType2());\n  const auto index1 = std::distance(sCollisionTypeList->cbegin(), iter1);\n  const auto index2 = std::distance(sCollisionTypeList->cbegin(), iter2);\n  const bool hasReachedEnd = iter1 == sCollisionTypeList->cend() || iter2 == sCollisionTypeList->cend();\n\n  if (index1 >= sNumTypes || index2 >= sNumTypes || hasReachedEnd) {\n    return;\n  }\n\n  BooleanComparisonFunc& funcOut =\n      hasReachedEnd ? sNullBooleanCollider : (*sTableOfBooleanCollidables)[index2 * sNumTypes + index1];\n  funcOut = cmp.GetCollider();\n}\n\nvoid CCollisionPrimitive::InitAddBooleanCollider(BooleanComparisonFunc cmp, const char* a, const char* b) {\n  InitAddBooleanCollider({cmp, a, b});\n}\n\nvoid CCollisionPrimitive::InitAddMovingCollider(const MovingComparison& cmp) {\n  const auto iter1 = TypeIndexFromTypeInfo(cmp.GetType1());\n  const auto iter2 = TypeIndexFromTypeInfo(cmp.GetType2());\n  const auto index1 = std::distance(sCollisionTypeList->cbegin(), iter1);\n  const auto index2 = std::distance(sCollisionTypeList->cbegin(), iter2);\n  const bool hasReachedEnd = iter1 == sCollisionTypeList->cend() || iter2 == sCollisionTypeList->cend();\n\n  if (index1 >= sNumTypes || index2 >= sNumTypes || hasReachedEnd) {\n    return;\n  }\n\n  MovingComparisonFunc& funcOut =\n      hasReachedEnd ? sNullMovingCollider : (*sTableOfMovingCollidables)[index2 * sNumTypes + index1];\n  funcOut = cmp.GetCollider();\n}\n\nvoid CCollisionPrimitive::InitAddMovingCollider(MovingComparisonFunc cmp, const char* a, const char* b) {\n  InitAddMovingCollider({cmp, a, b});\n}\n\nvoid CCollisionPrimitive::InitAddCollider(const Comparison& cmp) {\n  const auto iter1 = TypeIndexFromTypeInfo(cmp.GetType1());\n  const auto iter2 = TypeIndexFromTypeInfo(cmp.GetType2());\n  const auto index1 = std::distance(sCollisionTypeList->cbegin(), iter1);\n  const auto index2 = std::distance(sCollisionTypeList->cbegin(), iter2);\n  const bool hasReachedEnd = iter1 == sCollisionTypeList->cend() || iter2 == sCollisionTypeList->cend();\n\n  if (index1 >= sNumTypes || index2 >= sNumTypes || hasReachedEnd) {\n    return;\n  }\n\n  ComparisonFunc& funcOut = hasReachedEnd ? sNullCollider : (*sTableOfCollidables)[index2 * sNumTypes + index1];\n  funcOut = cmp.GetCollider();\n}\n\nvoid CCollisionPrimitive::InitAddCollider(ComparisonFunc cmp, const char* a, const char* b) {\n  InitAddCollider({cmp, a, b});\n}\n\nvoid CCollisionPrimitive::InitEndColliders() {\n  sCollidersAdding = false;\n  sCollidersAdded = true;\n  sInitComplete = true;\n}\n\nvoid CCollisionPrimitive::Uninitialize() {\n  sInitComplete = false;\n  sCollidersAdding = false;\n  sCollidersAdded = false;\n  sTypesAdding = false;\n  sTypesAdded = false;\n  sNumTypes = 0;\n  sCollisionTypeList.reset();\n  sTableOfCollidables.reset();\n  sTableOfMovingCollidables.reset();\n  sTableOfBooleanCollidables.reset();\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CCollisionPrimitive.hpp",
    "content": "#pragma once\n\n#include <functional>\n#include <memory>\n#include <vector>\n\n#include \"Runtime/Collision/CMaterialList.hpp\"\n#include \"Runtime/Collision/CRayCastResult.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CCollisionPrimitive;\nclass CMaterialFilter;\n\nclass CInternalCollisionStructure {\npublic:\n  class CPrimDesc {\n    const CCollisionPrimitive& x0_prim;\n    const CMaterialFilter& x4_filter;\n    zeus::CTransform x8_xf;\n\n  public:\n    CPrimDesc(const CCollisionPrimitive& prim, const CMaterialFilter& filter, const zeus::CTransform& xf)\n    : x0_prim(prim), x4_filter(filter), x8_xf(xf) {}\n    const CCollisionPrimitive& GetPrim() const { return x0_prim; }\n    const CMaterialFilter& GetFilter() const { return x4_filter; }\n    const zeus::CTransform& GetTransform() const { return x8_xf; }\n  };\n\nprivate:\n  CPrimDesc x0_p0;\n  CPrimDesc x38_p1;\n\npublic:\n  CInternalCollisionStructure(const CPrimDesc& p0, const CPrimDesc& p1) : x0_p0(p0), x38_p1(p1) {}\n  const CPrimDesc& GetLeft() const { return x0_p0; }\n  const CPrimDesc& GetRight() const { return x38_p1; }\n};\n\nclass COBBTree;\nclass CCollisionInfo;\nclass CCollisionInfoList;\nclass CInternalRayCastStructure;\n\nusing BooleanComparisonFunc = bool (*)(const CInternalCollisionStructure&);\nusing ComparisonFunc = bool (*)(const CInternalCollisionStructure&, CCollisionInfoList&);\nusing MovingComparisonFunc = bool (*)(const CInternalCollisionStructure&, const zeus::CVector3f&, double&,\n                                      CCollisionInfo&);\nusing PrimitiveSetter = void (*)(u32);\n\nclass CCollisionPrimitive {\npublic:\n  class Type {\n    PrimitiveSetter x0_setter = nullptr;\n    const char* x4_info = nullptr;\n\n  public:\n    constexpr Type() noexcept = default;\n    constexpr Type(PrimitiveSetter setter, const char* info) noexcept : x0_setter(setter), x4_info(info) {}\n\n    constexpr const char* GetInfo() const noexcept { return x4_info; }\n    constexpr PrimitiveSetter GetSetter() const noexcept { return x0_setter; }\n  };\n\n  class Comparison {\n    ComparisonFunc x0_collider;\n    const char* x4_type1;\n    const char* x8_type2;\n\n  public:\n    constexpr Comparison(ComparisonFunc collider, const char* type1, const char* type2) noexcept\n    : x0_collider(collider), x4_type1(type1), x8_type2(type2) {}\n\n    constexpr ComparisonFunc GetCollider() const noexcept { return x0_collider; }\n    constexpr const char* GetType1() const noexcept { return x4_type1; }\n    constexpr const char* GetType2() const noexcept { return x8_type2; }\n  };\n\n  class MovingComparison {\n    MovingComparisonFunc x0_collider;\n    const char* x4_type1;\n    const char* x8_type2;\n\n  public:\n    constexpr MovingComparison(MovingComparisonFunc collider, const char* type1, const char* type2) noexcept\n    : x0_collider(collider), x4_type1(type1), x8_type2(type2) {}\n\n    constexpr MovingComparisonFunc GetCollider() const noexcept { return x0_collider; }\n    constexpr const char* GetType1() const noexcept { return x4_type1; }\n    constexpr const char* GetType2() const noexcept { return x8_type2; }\n  };\n\n  class BooleanComparison {\n    BooleanComparisonFunc x0_collider;\n    const char* x4_type1;\n    const char* x8_type2;\n\n  public:\n    constexpr BooleanComparison(BooleanComparisonFunc collider, const char* type1, const char* type2) noexcept\n    : x0_collider(collider), x4_type1(type1), x8_type2(type2) {}\n\n    constexpr BooleanComparisonFunc GetCollider() const noexcept { return x0_collider; }\n    constexpr const char* GetType1() const noexcept { return x4_type1; }\n    constexpr const char* GetType2() const noexcept { return x8_type2; }\n  };\n\nprivate:\n  CMaterialList x8_material;\n  static s32 sNumTypes;\n  static bool sInitComplete;\n  static bool sTypesAdded;\n  static bool sTypesAdding;\n  static bool sCollidersAdded;\n  static bool sCollidersAdding;\n  static std::unique_ptr<std::vector<Type>> sCollisionTypeList;\n  static std::unique_ptr<std::vector<ComparisonFunc>> sTableOfCollidables;\n  static std::unique_ptr<std::vector<BooleanComparisonFunc>> sTableOfBooleanCollidables;\n  static std::unique_ptr<std::vector<MovingComparisonFunc>> sTableOfMovingCollidables;\n  static ComparisonFunc sNullCollider;\n  static BooleanComparisonFunc sNullBooleanCollider;\n  static MovingComparisonFunc sNullMovingCollider;\n\n  // Attempts to locate an entry within the collision type list that matches the supplied name.\n  // Returns the end iterator in the event of no matches.\n  static std::vector<Type>::const_iterator TypeIndexFromTypeInfo(const char* name);\n\n  static bool InternalCollide(const CInternalCollisionStructure& collision, CCollisionInfoList& list);\n  static bool InternalCollideBoolean(const CInternalCollisionStructure& collision);\n  static bool InternalCollideMoving(const CInternalCollisionStructure& collision, const zeus::CVector3f& dir,\n                                    double& dOut, CCollisionInfo& infoOut);\n\npublic:\n  CCollisionPrimitive() = default;\n  explicit CCollisionPrimitive(const CMaterialList& list);\n  virtual u32 GetTableIndex() const = 0;\n  virtual void SetMaterial(const CMaterialList&);\n  virtual const CMaterialList& GetMaterial() const;\n  virtual zeus::CAABox CalculateAABox(const zeus::CTransform&) const = 0;\n  virtual zeus::CAABox CalculateLocalAABox() const = 0;\n  virtual FourCC GetPrimType() const = 0;\n  virtual ~CCollisionPrimitive() = default;\n  virtual CRayCastResult CastRayInternal(const CInternalRayCastStructure&) const = 0;\n  CRayCastResult CastRay(const zeus::CVector3f& start, const zeus::CVector3f& dir, float length,\n                         const CMaterialFilter& filter, const zeus::CTransform& xf) const;\n\n  static bool Collide(const CInternalCollisionStructure::CPrimDesc& prim0,\n                      const CInternalCollisionStructure::CPrimDesc& prim1, CCollisionInfoList& list);\n  static bool CollideBoolean(const CInternalCollisionStructure::CPrimDesc& prim0,\n                             const CInternalCollisionStructure::CPrimDesc& prim1);\n  static bool CollideMoving(const CInternalCollisionStructure::CPrimDesc& prim0,\n                            const CInternalCollisionStructure::CPrimDesc& prim1, const zeus::CVector3f& dir,\n                            double& dOut, CCollisionInfo& infoOut);\n\n  static void InitBeginTypes();\n  static void InitAddType(const Type& tp);\n  static void InitEndTypes();\n\n  static void InitBeginColliders();\n  static void InitAddBooleanCollider(const BooleanComparison& cmp);\n  static void InitAddBooleanCollider(BooleanComparisonFunc, const char*, const char*);\n  static void InitAddMovingCollider(const MovingComparison& cmp);\n  static void InitAddMovingCollider(MovingComparisonFunc, const char*, const char*);\n  static void InitAddCollider(const Comparison& cmp);\n  static void InitAddCollider(ComparisonFunc, const char*, const char*);\n  static void InitEndColliders();\n\n  static void Uninitialize();\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CCollisionResponseData.cpp",
    "content": "#include \"Runtime/Collision/CCollisionResponseData.hpp\"\n\n#include <array>\n\n#include \"Runtime/CRandom16.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/Graphics/CModel.hpp\"\n#include \"Runtime/Particle/CDecalDescription.hpp\"\n#include \"Runtime/Particle/CElectricDescription.hpp\"\n#include \"Runtime/Particle/CGenDescription.hpp\"\n#include \"Runtime/Particle/CParticleDataFactory.hpp\"\n#include \"Runtime/Particle/CSwooshDescription.hpp\"\n\nnamespace metaforce {\nnamespace {\nconstexpr std::array skWorldMaterialTable{\n    EWeaponCollisionResponseTypes::Default, EWeaponCollisionResponseTypes::Unknown2,\n    EWeaponCollisionResponseTypes::Metal,   EWeaponCollisionResponseTypes::Grass,\n    EWeaponCollisionResponseTypes::Ice,     EWeaponCollisionResponseTypes::Goo,\n    EWeaponCollisionResponseTypes::Metal,   EWeaponCollisionResponseTypes::Wood,\n    EWeaponCollisionResponseTypes::Grass,   EWeaponCollisionResponseTypes::Lava,\n    EWeaponCollisionResponseTypes::Lava,    EWeaponCollisionResponseTypes::Ice,\n    EWeaponCollisionResponseTypes::Mud,     EWeaponCollisionResponseTypes::Metal,\n    EWeaponCollisionResponseTypes::Default, EWeaponCollisionResponseTypes::Goo,\n    EWeaponCollisionResponseTypes::Goo,     EWeaponCollisionResponseTypes::Sand,\n    EWeaponCollisionResponseTypes::Default, EWeaponCollisionResponseTypes::Default,\n    EWeaponCollisionResponseTypes::Default, EWeaponCollisionResponseTypes::Metal,\n    EWeaponCollisionResponseTypes::Default, EWeaponCollisionResponseTypes::Default,\n    EWeaponCollisionResponseTypes::Default, EWeaponCollisionResponseTypes::Default,\n    EWeaponCollisionResponseTypes::Default, EWeaponCollisionResponseTypes::Default,\n    EWeaponCollisionResponseTypes::Default, EWeaponCollisionResponseTypes::Default,\n    EWeaponCollisionResponseTypes::Default, EWeaponCollisionResponseTypes::Default,\n};\n\nconstexpr s32 kInvalidSFX = -1;\n\nconstexpr std::array<FourCC, 94> kWCRTSFXIDs{{\n    SBIG('NSFX'), SBIG('DSFX'), SBIG('CSFX'), SBIG('MSFX'), SBIG('GRFX'), SBIG('ICFX'), SBIG('GOFX'), SBIG('WSFX'),\n    SBIG('WTFX'), SBIG('2MUD'), SBIG('2LAV'), SBIG('2SAN'), SBIG('2PRJ'), SBIG('DCFX'), SBIG('DSFX'), SBIG('DSHX'),\n    SBIG('DEFX'), SBIG('ESFX'), SBIG('SHFX'), SBIG('BEFX'), SBIG('WWFX'), SBIG('TAFX'), SBIG('GTFX'), SBIG('SPFX'),\n    SBIG('FPFX'), SBIG('FFFX'), SBIG('PAFX'), SBIG('BMFX'), SBIG('BFFX'), SBIG('PBFX'), SBIG('IBFX'), SBIG('4SVA'),\n    SBIG('4RPR'), SBIG('4MTR'), SBIG('4PDS'), SBIG('4FLB'), SBIG('4DRN'), SBIG('4MRE'), SBIG('CZFX'), SBIG('JZAS'),\n    SBIG('2ISE'), SBIG('2BSE'), SBIG('2ATB'), SBIG('2ATA'), SBIG('BSFX'), SBIG('WSFX'), SBIG('TSFX'), SBIG('GSFX'),\n    SBIG('SSFX'), SBIG('FSFX'), SBIG('SFFX'), SBIG('PSFX'), SBIG('MSFX'), SBIG('SBFX'), SBIG('PBSX'), SBIG('IBSX'),\n    SBIG('5SVA'), SBIG('5RPR'), SBIG('5MTR'), SBIG('5PDS'), SBIG('5FLB'), SBIG('5DRN'), SBIG('5MRE'), SBIG('CSFX'),\n    SBIG('JZPS'), SBIG('4ISE'), SBIG('4BSE'), SBIG('4ATB'), SBIG('4ATA'), SBIG('BHFX'), SBIG('WHFX'), SBIG('THFX'),\n    SBIG('GHFX'), SBIG('SHFX'), SBIG('FHFX'), SBIG('HFFX'), SBIG('PHFX'), SBIG('MHFX'), SBIG('HBFX'), SBIG('PBHX'),\n    SBIG('IBHX'), SBIG('6SVA'), SBIG('6RPR'), SBIG('6MTR'), SBIG('6PDS'), SBIG('6FLB'), SBIG('6DRN'), SBIG('6MRE'),\n    SBIG('CHFX'), SBIG('JZHS'), SBIG('6ISE'), SBIG('6BSE'), SBIG('6ATB'), SBIG('6ATA'),\n}};\n\nconstexpr std::array<FourCC, 94> kWCRTIDs{{\n    SBIG('NODP'), SBIG('DEFS'), SBIG('CRTS'), SBIG('MTLS'), SBIG('GRAS'), SBIG('ICEE'), SBIG('GOOO'), SBIG('WODS'),\n    SBIG('WATR'), SBIG('1MUD'), SBIG('1LAV'), SBIG('1SAN'), SBIG('1PRJ'), SBIG('DCHR'), SBIG('DCHS'), SBIG('DCSH'),\n    SBIG('DENM'), SBIG('DESP'), SBIG('DESH'), SBIG('BTLE'), SBIG('WASP'), SBIG('TALP'), SBIG('PTGM'), SBIG('SPIR'),\n    SBIG('FPIR'), SBIG('FFLE'), SBIG('PARA'), SBIG('BMON'), SBIG('BFLR'), SBIG('PBOS'), SBIG('IBOS'), SBIG('1SVA'),\n    SBIG('1RPR'), SBIG('1MTR'), SBIG('1PDS'), SBIG('1FLB'), SBIG('1DRN'), SBIG('1MRE'), SBIG('CHOZ'), SBIG('JZAP'),\n    SBIG('1ISE'), SBIG('1BSE'), SBIG('1ATB'), SBIG('1ATA'), SBIG('BTSP'), SBIG('WWSP'), SBIG('TASP'), SBIG('TGSP'),\n    SBIG('SPSP'), SBIG('FPSP'), SBIG('FFSP'), SBIG('PSSP'), SBIG('BMSP'), SBIG('BFSP'), SBIG('PBSP'), SBIG('IBSP'),\n    SBIG('2SVA'), SBIG('2RPR'), SBIG('2MTR'), SBIG('2PDS'), SBIG('2FLB'), SBIG('2DRN'), SBIG('2MRE'), SBIG('CHSP'),\n    SBIG('JZSP'), SBIG('3ISE'), SBIG('3BSE'), SBIG('3ATB'), SBIG('3ATA'), SBIG('BTSH'), SBIG('WWSH'), SBIG('TASH'),\n    SBIG('TGSH'), SBIG('SPSH'), SBIG('FPSH'), SBIG('FFSH'), SBIG('PSSH'), SBIG('BMSH'), SBIG('BFSH'), SBIG('PBSH'),\n    SBIG('IBSH'), SBIG('3SVA'), SBIG('3RPR'), SBIG('3MTR'), SBIG('3PDS'), SBIG('3FLB'), SBIG('3DRN'), SBIG('3MRE'),\n    SBIG('CHSH'), SBIG('JZSH'), SBIG('5ISE'), SBIG('5BSE'), SBIG('5ATB'), SBIG('5ATA'),\n}};\n\nconstexpr std::array<FourCC, 14> kWCRTDecalIDs{{\n    SBIG('NCDL'),\n    SBIG('DDCL'),\n    SBIG('CODL'),\n    SBIG('MEDL'),\n    SBIG('GRDL'),\n    SBIG('ICDL'),\n    SBIG('GODL'),\n    SBIG('WODL'),\n    SBIG('WTDL'),\n    SBIG('3MUD'),\n    SBIG('3LAV'),\n    SBIG('3SAN'),\n    SBIG('CHDL'),\n    SBIG('ENDL'),\n}};\n\nusing CPF = CParticleDataFactory;\n} // Anonymous namespace\n\nvoid CCollisionResponseData::AddParticleSystemToResponse(EWeaponCollisionResponseTypes type, CInputStream& in,\n                                                         CSimplePool* resPool) {\n  const auto i = size_t(type);\n  const std::vector<CAssetId> tracker(8);\n  x0_generators[i] = CPF::GetChildGeneratorDesc(in, resPool, tracker).x0_res;\n}\n\nbool CCollisionResponseData::CheckAndAddDecalToResponse(FourCC clsId, CInputStream& in, CSimplePool* resPool) {\n  size_t i = 0;\n  for (const FourCC& type : kWCRTDecalIDs) {\n    if (type == clsId) {\n      const FourCC cls = CPF::GetClassID(in);\n      if (cls == SBIG('NONE')) {\n        return true;\n      }\n\n      const CAssetId id = in.Get<CAssetId>();\n      if (!id.IsValid()) {\n        return true;\n      }\n\n      x20_decals[i].emplace(resPool->GetObj({FOURCC('DPSC'), id}));\n      return true;\n    }\n    i++;\n  }\n  return false;\n}\n\nbool CCollisionResponseData::CheckAndAddSoundFXToResponse(FourCC clsId, CInputStream& in) {\n  size_t i = 0;\n  for (const FourCC& type : kWCRTSFXIDs) {\n    if (type == clsId) {\n      const FourCC cls = CPF::GetClassID(in);\n      if (cls == SBIG('NONE')) {\n        return true;\n      }\n\n      x10_sfx[i] = CPF::GetInt(in);\n      return true;\n    }\n    i++;\n  }\n\n  return false;\n}\n\nbool CCollisionResponseData::CheckAndAddParticleSystemToResponse(FourCC clsId, CInputStream& in, CSimplePool* resPool) {\n  size_t i = 0;\n  for (const FourCC& type : kWCRTIDs) {\n    if (type == clsId) {\n      AddParticleSystemToResponse(EWeaponCollisionResponseTypes(i), in, resPool);\n      return true;\n    }\n    i++;\n  }\n  return false;\n}\n\nbool CCollisionResponseData::CheckAndAddResourceToResponse(FourCC clsId, CInputStream& in, CSimplePool* resPool) {\n  if (CheckAndAddParticleSystemToResponse(clsId, in, resPool))\n    return true;\n  if (CheckAndAddSoundFXToResponse(clsId, in))\n    return true;\n  if (CheckAndAddDecalToResponse(clsId, in, resPool))\n    return true;\n\n  return false;\n}\n\nCCollisionResponseData::CCollisionResponseData(CInputStream& in, CSimplePool* resPool)\n: x0_generators(94), x10_sfx(94, kInvalidSFX), x20_decals(94) {\n  FourCC clsId = CPF::GetClassID(in);\n  if (clsId == UncookedResType()) {\n    CRandom16 rand;\n    CGlobalRandom gr(rand);\n\n    while (clsId != SBIG('_END')) {\n      clsId = CPF::GetClassID(in);\n      if (CheckAndAddResourceToResponse(clsId, in, resPool))\n        continue;\n\n      if (clsId == SBIG('RNGE')) {\n        CPF::GetClassID(in);\n        x30_RNGE = CPF::GetReal(in);\n      } else if (clsId == SBIG('FOFF')) {\n        CPF::GetClassID(in);\n        x34_FOFF = CPF::GetReal(in);\n      }\n    }\n  }\n}\n\nconst std::optional<TLockedToken<CGenDescription>>&\nCCollisionResponseData::GetParticleDescription(EWeaponCollisionResponseTypes type) const {\n  if (x0_generators[size_t(type)]) {\n    return x0_generators[size_t(type)];\n  }\n\n  bool foundType = false;\n  if (ResponseTypeIsEnemyNormal(type)) {\n    type = EWeaponCollisionResponseTypes::EnemyNormal;\n    foundType = true;\n  } else if (ResponseTypeIsEnemySpecial(type)) {\n    type = EWeaponCollisionResponseTypes::EnemySpecial;\n    foundType = true;\n  } else if (ResponseTypeIsEnemyShielded(type)) {\n    type = EWeaponCollisionResponseTypes::EnemyShielded;\n    foundType = true;\n  }\n\n  if (foundType && !x0_generators[size_t(type)]) {\n    type = EWeaponCollisionResponseTypes::EnemyNormal;\n  }\n\n  if (!x0_generators[size_t(type)] && type != EWeaponCollisionResponseTypes::None) {\n    type = EWeaponCollisionResponseTypes::Default;\n  }\n\n  return x0_generators[size_t(type)];\n}\n\nconst std::optional<TLockedToken<CDecalDescription>>&\nCCollisionResponseData::GetDecalDescription(EWeaponCollisionResponseTypes type) const {\n  return x20_decals[size_t(type)];\n}\n\ns32 CCollisionResponseData::GetSoundEffectId(EWeaponCollisionResponseTypes type) const {\n  if (x10_sfx[size_t(type)] == kInvalidSFX) {\n    if (ResponseTypeIsEnemyNormal(type))\n      type = EWeaponCollisionResponseTypes::EnemyNormal;\n    else if (ResponseTypeIsEnemySpecial(type))\n      type = EWeaponCollisionResponseTypes::EnemySpecial;\n    else if (ResponseTypeIsEnemyShielded(type))\n      type = EWeaponCollisionResponseTypes::EnemyShielded;\n    else\n      type = EWeaponCollisionResponseTypes::Default;\n  }\n\n  return x10_sfx[size_t(type)];\n}\n\nEWeaponCollisionResponseTypes CCollisionResponseData::GetWorldCollisionResponseType(s32 id) {\n  if (id < 0 || size_t(id) >= skWorldMaterialTable.size()) {\n    return EWeaponCollisionResponseTypes::Default;\n  }\n  return skWorldMaterialTable[id];\n}\n\nbool CCollisionResponseData::ResponseTypeIsEnemyNormal(EWeaponCollisionResponseTypes type) {\n  return (type >= EWeaponCollisionResponseTypes::Unknown19 && type <= EWeaponCollisionResponseTypes::AtomicAlpha);\n}\n\nbool CCollisionResponseData::ResponseTypeIsEnemySpecial(EWeaponCollisionResponseTypes type) {\n  return (type >= EWeaponCollisionResponseTypes::Unknown44 && type <= EWeaponCollisionResponseTypes::Unknown68);\n}\n\nbool CCollisionResponseData::ResponseTypeIsEnemyShielded(EWeaponCollisionResponseTypes type) {\n  return (type >= EWeaponCollisionResponseTypes::Unknown69 &&\n          type <= EWeaponCollisionResponseTypes::AtomicAlphaReflect);\n}\n\nFourCC CCollisionResponseData::UncookedResType() { return SBIG('CRSM'); }\n\nCFactoryFnReturn FCollisionResponseDataFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& vparms,\n                                               CObjectReference*) {\n  CSimplePool* sp = vparms.GetOwnedObj<CSimplePool*>();\n  return TToken<CCollisionResponseData>::GetIObjObjectFor(std::make_unique<CCollisionResponseData>(in, sp));\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CCollisionResponseData.hpp",
    "content": "#pragma once\n\n#include <optional>\n#include <vector>\n\n#include \"Runtime/CFactoryMgr.hpp\"\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/IObj.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Collision/CMaterialList.hpp\"\n#include \"Runtime/Particle/CDecalDescription.hpp\"\n\nnamespace metaforce {\nclass CGenDescription;\nclass CSimplePool;\n\nenum class EWeaponCollisionResponseTypes {\n  None,\n  Default,\n  Unknown2,\n  Metal,\n  Grass,\n  Ice,\n  Goo,\n  Wood,\n  Water,\n  Mud,\n  Lava,\n  Sand,\n  Projectile,\n  OtherProjectile,\n  Unknown14,\n  Unknown15,\n  EnemyNormal,\n  EnemySpecial,\n  EnemyShielded,\n  Unknown19,\n  Unknown20,\n  Unknown21,\n  Unknown22,\n  Unknown23,\n  Unknown24,\n  Unknown25,\n  Unknown26,\n  Unknown27,\n  Unknown28,\n  Unknown29,\n  Unknown30,\n  Unknown31,\n  Unknown32,\n  Unknown33,\n  Unknown34,\n  Unknown35,\n  Unknown36,\n  Unknown37,\n  ChozoGhost,\n  Unknown39,\n  Unknown40,\n  Unknown41,\n  AtomicBeta,\n  AtomicAlpha,\n  Unknown44,\n  Unknown45,\n  Unknown46,\n  Unknown47,\n  Unknown48,\n  Unknown49,\n  Unknown50,\n  Unknown51,\n  Unknown52,\n  Unknown53,\n  Unknown54,\n  Unknown55,\n  Unknown56,\n  Unknown57,\n  Unknown58,\n  Unknown59,\n  Unknown60,\n  Unknown61,\n  Unknown62,\n  Unknown63,\n  Unknown64,\n  Unknown65,\n  Unknown66,\n  Unknown67,\n  Unknown68,\n  Unknown69,\n  Unknown70,\n  Unknown71,\n  Unknown72,\n  Unknown73,\n  Unknown74,\n  Unknown75,\n  Unknown76,\n  Unknown77,\n  Unknown78,\n  Unknown79,\n  Unknown80,\n  Unknown81,\n  Unknown82,\n  Unknown83,\n  Unknown84,\n  Unknown85,\n  Unknown86,\n  Unknown87,\n  Unknown88,\n  Unknown89,\n  Unknown90,\n  Unknown91,\n  AtomicBetaReflect,\n  AtomicAlphaReflect\n};\n\nclass CCollisionResponseData {\n  std::vector<std::optional<TLockedToken<CGenDescription>>> x0_generators;\n  std::vector<s32> x10_sfx;\n  std::vector<std::optional<TLockedToken<CDecalDescription>>> x20_decals;\n  float x30_RNGE = 50.0f;\n  float x34_FOFF = 0.2f;\n\n  void AddParticleSystemToResponse(EWeaponCollisionResponseTypes type, CInputStream& in, CSimplePool* resPool);\n  bool CheckAndAddDecalToResponse(FourCC clsId, CInputStream& in, CSimplePool* resPool);\n  bool CheckAndAddSoundFXToResponse(FourCC clsId, CInputStream& in);\n  bool CheckAndAddParticleSystemToResponse(FourCC clsId, CInputStream& in, CSimplePool* resPool);\n  bool CheckAndAddResourceToResponse(FourCC clsId, CInputStream& in, CSimplePool* resPool);\n\npublic:\n  explicit CCollisionResponseData(CInputStream& in, CSimplePool* resPool);\n  const std::optional<TLockedToken<CGenDescription>>& GetParticleDescription(EWeaponCollisionResponseTypes type) const;\n  const std::optional<TLockedToken<CDecalDescription>>& GetDecalDescription(EWeaponCollisionResponseTypes type) const;\n  s32 GetSoundEffectId(EWeaponCollisionResponseTypes type) const;\n  static EWeaponCollisionResponseTypes GetWorldCollisionResponseType(s32 id);\n  static bool ResponseTypeIsEnemyShielded(EWeaponCollisionResponseTypes type);\n  static bool ResponseTypeIsEnemyNormal(EWeaponCollisionResponseTypes type);\n  static bool ResponseTypeIsEnemySpecial(EWeaponCollisionResponseTypes type);\n  float GetAudibleRange() const { return x30_RNGE; }\n  float GetAudibleFallOff() const { return x34_FOFF; }\n  static FourCC UncookedResType();\n};\n\nCFactoryFnReturn FCollisionResponseDataFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& vparms,\n                                               CObjectReference* selfRef);\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CCollisionSurface.cpp",
    "content": "#include \"Runtime/Collision/CCollisionSurface.hpp\"\n\n#include <zeus/CUnitVector.hpp>\n\nnamespace metaforce {\nCCollisionSurface::CCollisionSurface(const zeus::CVector3f& a, const zeus::CVector3f& b, const zeus::CVector3f& c,\n                                     u32 flags)\n: x0_data{a, b, c}, x24_flags(flags) {}\n\nzeus::CVector3f CCollisionSurface::GetNormal() const {\n  const zeus::CVector3f v1 = (x0_data[1] - x0_data[0]).cross(x0_data[2] - x0_data[0]);\n  return zeus::CUnitVector3f(v1, true);\n}\n\nzeus::CPlane CCollisionSurface::GetPlane() const {\n  const zeus::CVector3f norm = GetNormal();\n  return {norm, norm.dot(x0_data[0])};\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CCollisionSurface.hpp",
    "content": "#pragma once\n\n#include <array>\n\n#include \"Runtime/GCNTypes.hpp\"\n\n#include <zeus/CPlane.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CCollisionSurface {\npublic:\n  using Vertices = std::array<zeus::CVector3f, 3>;\n\nprivate:\n  Vertices x0_data;\n  u32 x24_flags;\n\npublic:\n  CCollisionSurface(const zeus::CVector3f& a, const zeus::CVector3f& b, const zeus::CVector3f& c, u32 flags);\n\n  zeus::CVector3f GetNormal() const;\n  const zeus::CVector3f& GetVert(s32 idx) const { return x0_data[idx]; }\n  const Vertices& GetVerts() const { return x0_data; }\n  zeus::CPlane GetPlane() const;\n  u32 GetSurfaceFlags() const { return x24_flags; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CGameCollision.cpp",
    "content": "#include \"Runtime/Collision/CGameCollision.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Character/CGroundMovement.hpp\"\n#include \"Runtime/Collision/CAABoxFilter.hpp\"\n#include \"Runtime/Collision/CBallFilter.hpp\"\n#include \"Runtime/Collision/CCollidableOBBTreeGroup.hpp\"\n#include \"Runtime/Collision/CCollidableSphere.hpp\"\n#include \"Runtime/Collision/CCollisionInfoList.hpp\"\n#include \"Runtime/Collision/CMaterialFilter.hpp\"\n#include \"Runtime/Collision/CMaterialList.hpp\"\n#include \"Runtime/Collision/CMetroidAreaCollider.hpp\"\n#include \"Runtime/Collision/CollisionUtil.hpp\"\n#include \"Runtime/World/CActor.hpp\"\n#include \"Runtime/World/CScriptPlatform.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nnamespace {\nstatic constexpr bool skPlayerUsesNewColliderLogic = true;\n}\nstatic float CollisionImpulseFiniteVsInfinite(float mass, float velNormDot, float restitution) {\n  return mass * -(1.f + restitution) * velNormDot;\n}\n\nstatic float CollisionImpulseFiniteVsFinite(float mass0, float mass1, float velNormDot, float restitution) {\n  return (-(1.f + restitution) * velNormDot) / ((1.f / mass0) + (1.f / mass1));\n}\n\nvoid CGameCollision::InitCollision() {\n  /* Types */\n  CCollisionPrimitive::InitBeginTypes();\n  CCollisionPrimitive::InitAddType(CCollidableOBBTreeGroup::GetType());\n  CCollisionPrimitive::InitEndTypes();\n\n  /* Colliders */\n  CCollisionPrimitive::InitBeginColliders();\n  CCollisionPrimitive::InitAddCollider(CCollidableOBBTreeGroup::SphereCollide, \"CCollidableSphere\",\n                                       \"CCollidableOBBTreeGroup\");\n  CCollisionPrimitive::InitAddCollider(CCollidableOBBTreeGroup::AABoxCollide, \"CCollidableAABox\",\n                                       \"CCollidableOBBTreeGroup\");\n  CCollisionPrimitive::InitAddBooleanCollider(CCollidableOBBTreeGroup::SphereCollideBoolean, \"CCollidableSphere\",\n                                              \"CCollidableOBBTreeGroup\");\n  CCollisionPrimitive::InitAddBooleanCollider(CCollidableOBBTreeGroup::AABoxCollideBoolean, \"CCollidableAABox\",\n                                              \"CCollidableOBBTreeGroup\");\n  CCollisionPrimitive::InitAddMovingCollider(CCollidableOBBTreeGroup::CollideMovingAABox, \"CCollidableAABox\",\n                                             \"CCollidableOBBTreeGroup\");\n  CCollisionPrimitive::InitAddMovingCollider(CCollidableOBBTreeGroup::CollideMovingSphere, \"CCollidableSphere\",\n                                             \"CCollidableOBBTreeGroup\");\n  CCollisionPrimitive::InitAddCollider(CGameCollision::NullCollisionCollider, \"CCollidableOBBTreeGroup\",\n                                       \"CCollidableOBBTreeGroup\");\n  CCollisionPrimitive::InitAddBooleanCollider(CGameCollision::NullBooleanCollider, \"CCollidableOBBTreeGroup\",\n                                              \"CCollidableOBBTreeGroup\");\n  CCollisionPrimitive::InitAddMovingCollider(CGameCollision::NullMovingCollider, \"CCollidableOBBTreeGroup\",\n                                             \"CCollidableOBBTreeGroup\");\n  CCollisionPrimitive::InitEndColliders();\n}\n\nvoid CGameCollision::MovePlayer(CStateManager& mgr, CPhysicsActor& actor, float dt, const EntityList* colliderList) {\n  actor.SetAngularEnabled(true);\n  actor.AddMotionState(actor.PredictAngularMotion(dt));\n  if (!actor.IsUseStandardCollider()) {\n    if (!actor.GetMaterialList().HasMaterial(EMaterialTypes::GroundCollider)) {\n      MoveAndCollide(mgr, actor, dt, CBallFilter(actor), colliderList);\n    } else if (skPlayerUsesNewColliderLogic) {\n      CGroundMovement::MoveGroundCollider_New(mgr, actor, dt, colliderList);\n    } else {\n      CGroundMovement::MoveGroundCollider(mgr, actor, dt, colliderList);\n    }\n  } else {\n    MoveAndCollide(mgr, actor, dt, CBallFilter(actor), colliderList);\n  }\n  actor.SetAngularEnabled(false);\n}\n\nvoid CGameCollision::MoveAndCollide(CStateManager& mgr, CPhysicsActor& actor, float dt, const ICollisionFilter& filter,\n                                    const EntityList* colliderList) {\n  bool isPlayer = actor.GetMaterialList().HasMaterial(EMaterialTypes::Player);\n  bool r28 = false;\n  bool r27 = false;\n  int r26 = 0;\n  float f31 = dt;\n  float _4AC4 = dt;\n  float _4AC8 = dt;\n  CCollisionInfoList accumList;\n  CMotionState mState = actor.PredictMotion_Internal(dt);\n  float transMag = mState.x0_translation.magnitude();\n  float m1 = 0.0005f / actor.GetCollisionAccuracyModifier();\n  float m2 = transMag / (5.f * actor.GetCollisionAccuracyModifier());\n  float mMax = std::max(m1, m2);\n  float m3 = 0.001f / actor.GetCollisionAccuracyModifier();\n\n  zeus::CAABox motionVol = actor.GetMotionVolume(dt);\n  EntityList useColliderList;\n  if (colliderList)\n    useColliderList = *colliderList;\n  else\n    mgr.BuildColliderList(useColliderList, actor, zeus::CAABox(motionVol.min - 1.f, motionVol.max + 1.f));\n  CAreaCollisionCache cache(motionVol);\n  if (actor.GetCollisionPrimitive()->GetPrimType() != FOURCC('OBTG') &&\n      !actor.GetMaterialFilter().GetExcludeList().HasMaterial(EMaterialTypes::NoStaticCollision)) {\n    BuildAreaCollisionCache(mgr, cache);\n    zeus::CVector3f pos = actor.GetCollisionPrimitive()->CalculateAABox(actor.GetPrimitiveTransform()).center();\n    float halfExtent = 0.5f * GetMinExtentForCollisionPrimitive(*actor.GetCollisionPrimitive());\n    if (transMag > halfExtent) {\n      TUniqueId id = kInvalidUniqueId;\n      zeus::CVector3f dir = (1.f / transMag) * mState.x0_translation;\n      CRayCastResult intersectRes =\n          mgr.RayWorldIntersection(id, pos, dir, transMag, actor.GetMaterialFilter(), useColliderList);\n      if (intersectRes.IsValid()) {\n        f31 = dt * (intersectRes.GetT() / transMag);\n        mState = actor.PredictMotion_Internal(f31);\n        _4AC8 = halfExtent * (dt / transMag);\n        mMax = std::min(mMax, halfExtent);\n      }\n    }\n  }\n\n  float f27 = f31;\n  while (true) {\n    actor.MoveCollisionPrimitive(mState.x0_translation);\n    if (DetectCollisionBoolean_Cached(mgr, cache, *actor.GetCollisionPrimitive(), actor.GetPrimitiveTransform(),\n                                      actor.GetMaterialFilter(), useColliderList)) {\n      r28 = true;\n      if (mState.x0_translation.magnitude() < mMax) {\n        r27 = true;\n        accumList.Clear();\n        TUniqueId id = kInvalidUniqueId;\n        DetectCollision_Cached(mgr, cache, *actor.GetCollisionPrimitive(), actor.GetPrimitiveTransform(),\n                               actor.GetMaterialFilter(), useColliderList, id, accumList);\n        TCastToPtr<CPhysicsActor> otherActor = mgr.ObjectById(id);\n        actor.MoveCollisionPrimitive(zeus::skZero3f);\n        zeus::CVector3f relVel = GetActorRelativeVelocities(actor, otherActor.GetPtr());\n        CCollisionInfoList filterList0, filterList1;\n        CollisionUtil::FilterOutBackfaces(relVel, accumList, filterList0);\n        if (filterList0.GetCount() > 0) {\n          filter.Filter(filterList0, filterList1);\n          if (!filterList1.GetCount() && actor.GetMaterialList().HasMaterial(EMaterialTypes::Player)) {\n            CMotionState mState = actor.GetLastNonCollidingState();\n            mState.x1c_velocity *= zeus::CVector3f(0.5f);\n            mState.x28_angularMomentum *= zeus::CVector3f(0.5f);\n            actor.SetMotionState(mState);\n          }\n        }\n        MakeCollisionCallbacks(mgr, actor, id, filterList1);\n        SendScriptMessages(mgr, actor, otherActor.GetPtr(), filterList1);\n        ResolveCollisions(actor, otherActor.GetPtr(), filterList1);\n        _4AC4 -= f31;\n        f27 = std::min(_4AC4, _4AC8);\n        f31 = f27;\n      } else {\n        f27 *= 0.5f;\n        f31 *= 0.5f;\n      }\n    } else {\n      actor.AddMotionState(mState);\n      _4AC4 -= f31;\n      f31 = f27;\n      actor.ClearImpulses();\n      actor.MoveCollisionPrimitive(zeus::skZero3f);\n    }\n\n    ++r26;\n    if (_4AC4 > 0.f && ((mState.x0_translation.magnitude() > m3 && r27) || !r27) && r26 <= 1000)\n      mState = actor.PredictMotion_Internal(f31);\n    else\n      break;\n  }\n\n  f27 = _4AC4 / dt;\n  if (!r28 && !actor.GetMaterialList().HasMaterial(EMaterialTypes::GroundCollider))\n    mgr.SendScriptMsg(&actor, kInvalidUniqueId, EScriptObjectMessage::Falling);\n\n  if (isPlayer)\n    CollisionFailsafe(mgr, cache, actor, *actor.GetCollisionPrimitive(), useColliderList, f27, 2);\n\n  actor.ClearForcesAndTorques();\n  actor.MoveCollisionPrimitive(zeus::skZero3f);\n}\n\nzeus::CVector3f CGameCollision::GetActorRelativeVelocities(const CPhysicsActor& act0, const CPhysicsActor* act1) {\n  zeus::CVector3f ret = act0.GetVelocity();\n  if (act1) {\n    bool rider = false;\n    if (const TCastToConstPtr<CScriptPlatform> plat = act1) {\n      rider = plat->IsRider(act0.GetUniqueId());\n    }\n    if (!rider) {\n      ret -= act1->GetVelocity();\n    }\n  }\n  return ret;\n}\n\nvoid CGameCollision::Move(CStateManager& mgr, CPhysicsActor& actor, float dt, const EntityList* colliderList) {\n  if (!actor.IsMovable())\n    return;\n  if (actor.GetMaterialList().HasMaterial(EMaterialTypes::GroundCollider) || actor.WillMove(mgr)) {\n    if (actor.IsAngularEnabled())\n      actor.AddMotionState(actor.PredictAngularMotion(dt));\n    actor.UseCollisionImpulses();\n    if (actor.GetMaterialList().HasMaterial(EMaterialTypes::Solid)) {\n      if (actor.GetMaterialList().HasMaterial(EMaterialTypes::Player))\n        MovePlayer(mgr, actor, dt, colliderList);\n      else if (actor.GetMaterialList().HasMaterial(EMaterialTypes::GroundCollider))\n        CGroundMovement::MoveGroundCollider(mgr, actor, dt, colliderList);\n      else\n        MoveAndCollide(mgr, actor, dt, CAABoxFilter(actor), colliderList);\n    } else {\n      actor.AddMotionState(actor.PredictMotion_Internal(dt));\n      actor.ClearForcesAndTorques();\n    }\n    mgr.UpdateActorInSortedLists(actor);\n  }\n}\n\nbool CGameCollision::CanBlock(const CMaterialList& mat, const zeus::CUnitVector3f& v) {\n  if ((mat.HasMaterial(EMaterialTypes::Character) && !mat.HasMaterial(EMaterialTypes::SolidCharacter)) ||\n      mat.HasMaterial(EMaterialTypes::NoPlayerCollision)) {\n    return false;\n  }\n\n  if (mat.HasMaterial(EMaterialTypes::Floor)) {\n    return true;\n  }\n\n  return (v.z() > 0.85f);\n}\n\nbool CGameCollision::IsFloor(const CMaterialList& mat, const zeus::CUnitVector3f& v) {\n  return mat.HasMaterial(EMaterialTypes::Floor) || v.z() > 0.85f;\n}\n\nvoid CGameCollision::SendMaterialMessage(CStateManager& mgr, const CMaterialList& mat, CActor& act) {\n  EScriptObjectMessage msg;\n  if (mat.HasMaterial(EMaterialTypes::Ice)) {\n    msg = EScriptObjectMessage::OnIceSurface;\n  } else if (mat.HasMaterial(EMaterialTypes::MudSlow)) {\n    msg = EScriptObjectMessage::OnMudSlowSurface;\n  } else {\n    msg = EScriptObjectMessage::OnNormalSurface;\n  }\n\n  mgr.SendScriptMsg(&act, kInvalidUniqueId, msg);\n}\n\nCRayCastResult CGameCollision::RayStaticIntersection(const CStateManager& mgr, const zeus::CVector3f& pos,\n                                                     const zeus::CVector3f& dir, float length,\n                                                     const CMaterialFilter& filter) {\n  CRayCastResult ret;\n  float bestT = length;\n  if (bestT <= 0.f) {\n    bestT = 100000.f;\n  }\n\n  zeus::CLine line(pos, dir);\n  for (const CGameArea& area : *mgr.GetWorld()) {\n    CAreaOctTree::SRayResult rayRes;\n    CAreaOctTree& collision = *area.GetPostConstructed()->x0_collision;\n    collision.GetRootNode().LineTestEx(line, filter, rayRes, length);\n    if (!rayRes.x10_surface || (length != 0.f && length < rayRes.x3c_t)) {\n      continue;\n    }\n\n    if (rayRes.x3c_t < bestT) {\n      ret = CRayCastResult(rayRes.x3c_t, dir * rayRes.x3c_t + pos, rayRes.x0_plane,\n                           rayRes.x10_surface->GetSurfaceFlags());\n      bestT = rayRes.x3c_t;\n    }\n  }\n\n  return ret;\n}\n\nbool CGameCollision::RayStaticIntersectionBool(const CStateManager& mgr, const zeus::CVector3f& start,\n                                               const zeus::CVector3f& dir, float length,\n                                               const CMaterialFilter& filter) {\n  if (length <= 0.f) {\n    length = 100000.f;\n  }\n  zeus::CLine line(start, dir);\n  for (const CGameArea& area : *mgr.GetWorld()) {\n    const CAreaOctTree& collision = *area.GetPostConstructed()->x0_collision;\n    CAreaOctTree::Node root = collision.GetRootNode();\n    if (!root.LineTest(line, filter, length)) {\n      return false;\n    }\n  }\n\n  return true;\n}\n\nCRayCastResult CGameCollision::RayDynamicIntersection(const CStateManager& mgr, TUniqueId& idOut,\n                                                      const zeus::CVector3f& pos, const zeus::CVector3f& dir,\n                                                      float length, const CMaterialFilter& filter,\n                                                      const EntityList& nearList) {\n  CRayCastResult ret;\n  float bestT = length;\n  if (bestT <= 0.f) {\n    bestT = 100000.f;\n  }\n\n  for (TUniqueId id : nearList) {\n    const CEntity* ent = mgr.GetObjectById(id);\n    if (const TCastToConstPtr<CPhysicsActor> physActor = ent) {\n      const zeus::CTransform xf = physActor->GetPrimitiveTransform();\n      const CCollisionPrimitive* prim = physActor->GetCollisionPrimitive();\n      const CRayCastResult res = prim->CastRay(pos, dir, bestT, filter, xf);\n      if (!res.IsInvalid() && res.GetT() < bestT) {\n        bestT = res.GetT();\n        ret = res;\n        idOut = physActor->GetUniqueId();\n      }\n    }\n  }\n\n  return ret;\n}\n\nbool CGameCollision::RayDynamicIntersectionBool(const CStateManager& mgr, const zeus::CVector3f& pos,\n                                                const zeus::CVector3f& dir, const CMaterialFilter& filter,\n                                                const EntityList& nearList, const CActor* damagee, float length) {\n  if (length <= 0.f) {\n    length = 100000.f;\n  }\n\n  for (TUniqueId id : nearList) {\n    const CEntity* ent = mgr.GetObjectById(id);\n    if (const TCastToConstPtr<CPhysicsActor> physActor = ent) {\n      if (damagee != nullptr && physActor->GetUniqueId() == damagee->GetUniqueId()) {\n        continue;\n      }\n      const zeus::CTransform xf = physActor->GetPrimitiveTransform();\n      const CCollisionPrimitive* prim = physActor->GetCollisionPrimitive();\n      const CRayCastResult res = prim->CastRay(pos, dir, length, filter, xf);\n      if (!res.IsInvalid()) {\n        return false;\n      }\n    }\n  }\n\n  return true;\n}\n\nCRayCastResult CGameCollision::RayWorldIntersection(const CStateManager& mgr, TUniqueId& idOut,\n                                                    const zeus::CVector3f& pos, const zeus::CVector3f& dir, float mag,\n                                                    const CMaterialFilter& filter, const EntityList& nearList) {\n  CRayCastResult staticRes = RayStaticIntersection(mgr, pos, dir, mag, filter);\n  CRayCastResult dynamicRes = RayDynamicIntersection(mgr, idOut, pos, dir, mag, filter, nearList);\n\n  if (dynamicRes.IsValid()) {\n    if (staticRes.IsInvalid()) {\n      return dynamicRes;\n    }\n    if (staticRes.GetT() >= dynamicRes.GetT()) {\n      return dynamicRes;\n    }\n  }\n  return staticRes;\n}\n\nbool CGameCollision::RayStaticIntersectionArea(const CGameArea& area, const zeus::CVector3f& pos,\n                                               const zeus::CVector3f& dir, float mag, const CMaterialFilter& filter) {\n  if (mag <= 0.f) {\n    mag = 100000.f;\n  }\n  CAreaOctTree::Node node = area.GetPostConstructed()->x0_collision->GetRootNode();\n  zeus::CLine line(pos, dir);\n  return node.LineTest(line, filter, mag);\n}\n\nvoid CGameCollision::BuildAreaCollisionCache(const CStateManager& mgr, CAreaCollisionCache& cache) {\n  cache.ClearCache();\n  for (const CGameArea& area : *mgr.GetWorld()) {\n    const CAreaOctTree& areaCollision = *area.GetPostConstructed()->x0_collision;\n    CMetroidAreaCollider::COctreeLeafCache octreeCache(areaCollision);\n    CMetroidAreaCollider::BuildOctreeLeafCache(areaCollision.GetRootNode(), cache.GetCacheBounds(), octreeCache);\n    cache.AddOctreeLeafCache(octreeCache);\n  }\n}\n\nfloat CGameCollision::GetMinExtentForCollisionPrimitive(const CCollisionPrimitive& prim) {\n  if (prim.GetPrimType() == FOURCC('SPHR')) {\n    const auto& sphere = static_cast<const CCollidableSphere&>(prim);\n    return 2.f * sphere.GetSphere().radius;\n  }\n  if (prim.GetPrimType() == FOURCC('AABX')) {\n    const auto& aabx = static_cast<const CCollidableAABox&>(prim);\n    const zeus::CVector3f extent = aabx.GetBox().max - aabx.GetBox().min;\n    float minExtent = std::min(extent.x(), extent.y());\n    minExtent = std::min(minExtent, extent.z());\n    return minExtent;\n  }\n  if (prim.GetPrimType() == FOURCC('ABSH')) {\n    // Combination AABB / Sphere cut from game\n  }\n  return 1.f;\n}\n\nbool CGameCollision::DetectCollisionBoolean(const CStateManager& mgr, const CCollisionPrimitive& prim,\n                                            const zeus::CTransform& xf, const CMaterialFilter& filter,\n                                            const EntityList& nearList) {\n  if (!filter.GetExcludeList().HasMaterial(EMaterialTypes::NoStaticCollision) &&\n      DetectStaticCollisionBoolean(mgr, prim, xf, filter)) {\n    return true;\n  }\n  if (DetectDynamicCollisionBoolean(prim, xf, nearList, mgr)) {\n    return true;\n  }\n  return false;\n}\n\nbool CGameCollision::DetectCollisionBoolean_Cached(const CStateManager& mgr, CAreaCollisionCache& cache,\n                                                   const CCollisionPrimitive& prim, const zeus::CTransform& xf,\n                                                   const CMaterialFilter& filter, const EntityList& nearList) {\n  if (!filter.GetExcludeList().HasMaterial(EMaterialTypes::NoStaticCollision) &&\n      DetectStaticCollisionBoolean_Cached(mgr, cache, prim, xf, filter)) {\n    return true;\n  }\n  if (DetectDynamicCollisionBoolean(prim, xf, nearList, mgr)) {\n    return true;\n  }\n  return false;\n}\n\nbool CGameCollision::DetectStaticCollisionBoolean(const CStateManager& mgr, const CCollisionPrimitive& prim,\n                                                  const zeus::CTransform& xf, const CMaterialFilter& filter) {\n  if (prim.GetPrimType() == FOURCC('OBTG')) {\n    return false;\n  }\n\n  if (prim.GetPrimType() == FOURCC('AABX')) {\n    zeus::CAABox aabb = prim.CalculateAABox(xf);\n    for (const CGameArea& area : *mgr.GetWorld()) {\n      if (CMetroidAreaCollider::AABoxCollisionCheckBoolean(*area.GetPostConstructed()->x0_collision, aabb, filter)) {\n        return true;\n      }\n    }\n  } else if (prim.GetPrimType() == FOURCC('SPHR')) {\n    const auto& sphere = static_cast<const CCollidableSphere&>(prim);\n    zeus::CAABox aabb = prim.CalculateAABox(xf);\n    zeus::CSphere xfSphere = sphere.Transform(xf);\n    for (const CGameArea& area : *mgr.GetWorld()) {\n      if (CMetroidAreaCollider::SphereCollisionCheckBoolean(*area.GetPostConstructed()->x0_collision, aabb, xfSphere,\n                                                            filter)) {\n        return true;\n      }\n    }\n  } else if (prim.GetPrimType() == FOURCC('ABSH')) {\n    // Combination AABB / Sphere cut from game\n  }\n\n  return false;\n}\n\nbool CGameCollision::DetectStaticCollisionBoolean_Cached(const CStateManager& mgr, CAreaCollisionCache& cache,\n                                                         const CCollisionPrimitive& prim, const zeus::CTransform& xf,\n                                                         const CMaterialFilter& filter) {\n  if (prim.GetPrimType() == FOURCC('OBTG')) {\n    return false;\n  }\n\n  zeus::CAABox aabb = prim.CalculateAABox(xf);\n  if (!aabb.inside(cache.GetCacheBounds())) {\n    zeus::CAABox newAABB(aabb.min - 0.2f, aabb.max + 0.2f);\n    newAABB.accumulateBounds(cache.GetCacheBounds());\n    cache.SetCacheBounds(newAABB);\n    BuildAreaCollisionCache(mgr, cache);\n  }\n\n  if (cache.HasCacheOverflowed()) {\n    return DetectStaticCollisionBoolean(mgr, prim, xf, filter);\n  }\n\n  if (prim.GetPrimType() == FOURCC('AABX')) {\n    for (const CMetroidAreaCollider::COctreeLeafCache& leafCache : cache) {\n      if (CMetroidAreaCollider::AABoxCollisionCheckBoolean_Cached(leafCache, aabb, filter)) {\n        return true;\n      }\n    }\n  } else if (prim.GetPrimType() == FOURCC('SPHR')) {\n    const auto& sphere = static_cast<const CCollidableSphere&>(prim);\n    zeus::CSphere xfSphere = sphere.Transform(xf);\n    for (const CMetroidAreaCollider::COctreeLeafCache& leafCache : cache) {\n      if (CMetroidAreaCollider::SphereCollisionCheckBoolean_Cached(leafCache, aabb, xfSphere, filter)) {\n        return true;\n      }\n    }\n  } else if (prim.GetPrimType() == FOURCC('ABSH')) {\n    // Combination AABB / Sphere cut from game\n  }\n\n  return false;\n}\n\nbool CGameCollision::DetectDynamicCollisionBoolean(const CCollisionPrimitive& prim, const zeus::CTransform& xf,\n                                                   const EntityList& nearList, const CStateManager& mgr) {\n  for (const auto& id : nearList) {\n    if (const TCastToConstPtr<CPhysicsActor> actor = mgr.GetObjectById(id)) {\n      const CInternalCollisionStructure::CPrimDesc p0(prim, CMaterialFilter::skPassEverything, xf);\n      const CInternalCollisionStructure::CPrimDesc p1(\n          *actor->GetCollisionPrimitive(), CMaterialFilter::skPassEverything, actor->GetPrimitiveTransform());\n      if (CCollisionPrimitive::CollideBoolean(p0, p1)) {\n        return true;\n      }\n    }\n  }\n\n  return false;\n}\n\nbool CGameCollision::DetectCollision_Cached(const CStateManager& mgr, CAreaCollisionCache& cache,\n                                            const CCollisionPrimitive& prim, const zeus::CTransform& xf,\n                                            const CMaterialFilter& filter, const EntityList& nearList, TUniqueId& idOut,\n                                            CCollisionInfoList& infoList) {\n  idOut = kInvalidUniqueId;\n  bool ret = false;\n  if (!filter.GetExcludeList().HasMaterial(EMaterialTypes::NoStaticCollision)) {\n    if (DetectStaticCollision_Cached(mgr, cache, prim, xf, filter, infoList)) {\n      ret = true;\n    }\n  }\n\n  TUniqueId id = kInvalidUniqueId;\n  if (DetectDynamicCollision(prim, xf, nearList, id, infoList, mgr)) {\n    ret = true;\n    idOut = id;\n  }\n\n  return ret;\n}\n\nbool CGameCollision::DetectCollision_Cached_Moving(const CStateManager& mgr, CAreaCollisionCache& cache,\n                                                   const CCollisionPrimitive& prim, const zeus::CTransform& xf,\n                                                   const CMaterialFilter& filter, const EntityList& nearList,\n                                                   const zeus::CVector3f& dir, TUniqueId& idOut,\n                                                   CCollisionInfo& infoOut, double& d) {\n  bool ret = false;\n  idOut = kInvalidUniqueId;\n  if (!filter.GetExcludeList().HasMaterial(EMaterialTypes::NoStaticCollision)) {\n    if (CGameCollision::DetectStaticCollision_Cached_Moving(mgr, cache, prim, xf, filter, dir, infoOut, d)) {\n      ret = true;\n    }\n  }\n\n  if (CGameCollision::DetectDynamicCollisionMoving(prim, xf, nearList, dir, idOut, infoOut, d, mgr)) {\n    ret = true;\n  }\n\n  return ret;\n}\n\nbool CGameCollision::DetectStaticCollision(const CStateManager& mgr, const CCollisionPrimitive& prim,\n                                           const zeus::CTransform& xf, const CMaterialFilter& filter,\n                                           CCollisionInfoList& list) {\n  if (prim.GetPrimType() == FOURCC('OBTG')) {\n    return false;\n  }\n\n  bool ret = false;\n  if (prim.GetPrimType() == FOURCC('AABX')) {\n    zeus::CAABox aabb = prim.CalculateAABox(xf);\n    for (const CGameArea& area : *mgr.GetWorld()) {\n      if (CMetroidAreaCollider::AABoxCollisionCheck(*area.GetPostConstructed()->x0_collision, aabb, filter,\n                                                    prim.GetMaterial(), list)) {\n        ret = true;\n      }\n    }\n  } else if (prim.GetPrimType() == FOURCC('SPHR')) {\n    const auto& sphere = static_cast<const CCollidableSphere&>(prim);\n    zeus::CAABox aabb = prim.CalculateAABox(xf);\n    zeus::CSphere xfSphere = sphere.Transform(xf);\n    for (const CGameArea& area : *mgr.GetWorld()) {\n      if (CMetroidAreaCollider::SphereCollisionCheck(*area.GetPostConstructed()->x0_collision, aabb, xfSphere,\n                                                     prim.GetMaterial(), filter, list)) {\n        ret = true;\n      }\n    }\n  } else if (prim.GetPrimType() == FOURCC('ABSH')) {\n    // Combination AABB / Sphere cut from game\n  }\n\n  return ret;\n}\n\nbool CGameCollision::DetectStaticCollision_Cached(const CStateManager& mgr, CAreaCollisionCache& cache,\n                                                  const CCollisionPrimitive& prim, const zeus::CTransform& xf,\n                                                  const CMaterialFilter& filter, CCollisionInfoList& list) {\n  if (prim.GetPrimType() == FOURCC('OBTG')) {\n    return false;\n  }\n\n  bool ret = false;\n  zeus::CAABox calcAABB = prim.CalculateAABox(xf);\n  if (!calcAABB.inside(cache.GetCacheBounds())) {\n    zeus::CAABox newAABB(calcAABB.min - 0.2f, calcAABB.max + 0.2f);\n    newAABB.accumulateBounds(cache.GetCacheBounds());\n    cache.SetCacheBounds(newAABB);\n    BuildAreaCollisionCache(mgr, cache);\n  }\n\n  if (cache.HasCacheOverflowed()) {\n    return DetectStaticCollision(mgr, prim, xf, filter, list);\n  }\n\n  if (prim.GetPrimType() == FOURCC('AABX')) {\n    for (const CMetroidAreaCollider::COctreeLeafCache& leafCache : cache) {\n      if (CMetroidAreaCollider::AABoxCollisionCheck_Cached(leafCache, calcAABB, filter, prim.GetMaterial(), list)) {\n        ret = true;\n      }\n    }\n  } else if (prim.GetPrimType() == FOURCC('SPHR')) {\n    const auto& sphere = static_cast<const CCollidableSphere&>(prim);\n    zeus::CSphere xfSphere = sphere.Transform(xf);\n    for (const CMetroidAreaCollider::COctreeLeafCache& leafCache : cache) {\n      if (CMetroidAreaCollider::SphereCollisionCheck_Cached(leafCache, calcAABB, xfSphere, prim.GetMaterial(), filter,\n                                                            list)) {\n        ret = true;\n      }\n    }\n  } else if (prim.GetPrimType() == FOURCC('ABSH')) {\n    // Combination AABB / Sphere cut from game\n  }\n\n  return ret;\n}\n\nbool CGameCollision::DetectStaticCollision_Cached_Moving(const CStateManager& mgr, CAreaCollisionCache& cache,\n                                                         const CCollisionPrimitive& prim, const zeus::CTransform& xf,\n                                                         const CMaterialFilter& filter, const zeus::CVector3f& dir,\n                                                         CCollisionInfo& infoOut, double& dOut) {\n  if (prim.GetPrimType() == FOURCC('OBTG')) {\n    return false;\n  }\n\n  zeus::CVector3f offset = float(dOut) * dir;\n  zeus::CAABox aabb = prim.CalculateAABox(xf);\n  zeus::CAABox offsetAABB = aabb;\n  offsetAABB.accumulateBounds(offset + offsetAABB.min);\n  offsetAABB.accumulateBounds(offset + offsetAABB.max);\n\n  if (!offsetAABB.inside(cache.GetCacheBounds())) {\n    zeus::CAABox newAABB(offsetAABB.min - 0.2f, offsetAABB.max + 0.2f);\n    newAABB.accumulateBounds(cache.GetCacheBounds());\n    cache.SetCacheBounds(newAABB);\n    BuildAreaCollisionCache(mgr, cache);\n  }\n\n  if (prim.GetPrimType() == FOURCC('AABX')) {\n    for (const CMetroidAreaCollider::COctreeLeafCache& leafCache : cache) {\n      CCollisionInfo info;\n      double d = dOut;\n      if (CMetroidAreaCollider::MovingAABoxCollisionCheck_Cached(\n              leafCache, aabb, filter, CMaterialList(EMaterialTypes::Solid), dir, dOut, info, d) &&\n          d < dOut) {\n        infoOut = info;\n        dOut = d;\n      }\n    }\n  } else if (prim.GetPrimType() == FOURCC('SPHR')) {\n    const auto& sphere = static_cast<const CCollidableSphere&>(prim);\n    zeus::CSphere xfSphere = sphere.Transform(xf);\n    for (const CMetroidAreaCollider::COctreeLeafCache& leafCache : cache) {\n      CCollisionInfo info;\n      double d = dOut;\n      if (CMetroidAreaCollider::MovingSphereCollisionCheck_Cached(\n              leafCache, aabb, xfSphere, filter, CMaterialList(EMaterialTypes::Solid), dir, dOut, info, d) &&\n          d < dOut) {\n        infoOut = info;\n        dOut = d;\n      }\n    }\n  }\n\n  return infoOut.IsValid();\n}\n\nbool CGameCollision::DetectDynamicCollision(const CCollisionPrimitive& prim, const zeus::CTransform& xf,\n                                            const EntityList& nearList, TUniqueId& idOut, CCollisionInfoList& list,\n                                            const CStateManager& mgr) {\n  for (const auto& id : nearList) {\n    if (const TCastToConstPtr<CPhysicsActor> actor = mgr.GetObjectById(id)) {\n      const CInternalCollisionStructure::CPrimDesc p0(prim, CMaterialFilter::skPassEverything, xf);\n      const CInternalCollisionStructure::CPrimDesc p1(\n          *actor->GetCollisionPrimitive(), CMaterialFilter::skPassEverything, actor->GetPrimitiveTransform());\n      if (CCollisionPrimitive::Collide(p0, p1, list)) {\n        idOut = actor->GetUniqueId();\n        return true;\n      }\n    }\n  }\n\n  return false;\n}\n\nbool CGameCollision::DetectDynamicCollisionMoving(const CCollisionPrimitive& prim, const zeus::CTransform& xf,\n                                                  const EntityList& nearList, const zeus::CVector3f& dir,\n                                                  TUniqueId& idOut, CCollisionInfo& infoOut, double& dOut,\n                                                  const CStateManager& mgr) {\n  bool ret = false;\n  for (const auto& id : nearList) {\n    double d = dOut;\n    CCollisionInfo info;\n    if (const TCastToConstPtr<CPhysicsActor> actor = mgr.GetObjectById(id)) {\n      const CInternalCollisionStructure::CPrimDesc p0(prim, CMaterialFilter::skPassEverything, xf);\n      const CInternalCollisionStructure::CPrimDesc p1(\n          *actor->GetCollisionPrimitive(), CMaterialFilter::skPassEverything, actor->GetPrimitiveTransform());\n      if (CCollisionPrimitive::CollideMoving(p0, p1, dir, d, info) && d < dOut) {\n        ret = true;\n        infoOut = info;\n        dOut = d;\n        idOut = actor->GetUniqueId();\n      }\n    }\n  }\n\n  return ret;\n}\n\nbool CGameCollision::DetectCollision(const CStateManager& mgr, const CCollisionPrimitive& prim,\n                                     const zeus::CTransform& xf, const CMaterialFilter& filter,\n                                     const EntityList& nearList, TUniqueId& idOut, CCollisionInfoList& infoOut) {\n  bool ret = false;\n  CMaterialList exclude = filter.ExcludeList();\n  if (!exclude.HasMaterial(EMaterialTypes::Occluder) && DetectStaticCollision(mgr, prim, xf, filter, infoOut)) {\n    ret = true;\n  }\n\n  TUniqueId tmpId = kInvalidUniqueId;\n  if (DetectDynamicCollision(prim, xf, nearList, tmpId, infoOut, mgr)) {\n    ret = true;\n    idOut = tmpId;\n  }\n  return ret;\n}\n\nvoid CGameCollision::MakeCollisionCallbacks(CStateManager& mgr, CPhysicsActor& actor, TUniqueId id,\n                                            const CCollisionInfoList& list) {\n  actor.CollidedWith(id, list, mgr);\n\n  if (id == kInvalidUniqueId) {\n    return;\n  }\n\n  if (const TCastToPtr<CPhysicsActor> physicalActor = mgr.ObjectById(id)) {\n    CCollisionInfoList swappedList = list;\n    for (CCollisionInfo& info : swappedList) {\n      info.Swap();\n    }\n    physicalActor->CollidedWith(physicalActor->GetUniqueId(), list, mgr);\n  }\n}\n\nvoid CGameCollision::SendScriptMessages(CStateManager& mgr, CActor& a0, CActor* a1, const CCollisionInfoList& list) {\n  bool onFloor = false;\n  bool platform = false;\n\n  for (const CCollisionInfo& info : list) {\n    if (IsFloor(info.GetMaterialLeft(), info.GetNormalLeft())) {\n      onFloor = true;\n      if (info.GetMaterialLeft().HasMaterial(EMaterialTypes::Platform)) {\n        platform = true;\n      }\n      SendMaterialMessage(mgr, info.GetMaterialLeft(), a0);\n    }\n  }\n\n  if (onFloor) {\n    mgr.SendScriptMsg(&a0, kInvalidUniqueId, EScriptObjectMessage::OnFloor);\n    if (platform) {\n      if (const TCastToPtr<CScriptPlatform> plat = a1) {\n        mgr.SendScriptMsg(plat.GetPtr(), a0.GetUniqueId(), EScriptObjectMessage::AddPlatformRider);\n      }\n    } else if (a1 != nullptr) {\n      if (const TCastToPtr<CScriptPlatform> plat = a0) {\n        for (const CCollisionInfo& info : list) {\n          if (IsFloor(info.GetMaterialRight(), info.GetNormalRight())) {\n            if (info.GetMaterialRight().HasMaterial(EMaterialTypes::Platform)) {\n              platform = true;\n            }\n            SendMaterialMessage(mgr, info.GetMaterialLeft(), a0);\n          }\n        }\n        if (platform) {\n          mgr.SendScriptMsg(plat.GetPtr(), a1->GetUniqueId(), EScriptObjectMessage::AddPlatformRider);\n        }\n      }\n    }\n  }\n}\n\nvoid CGameCollision::ResolveCollisions(CPhysicsActor& a0, CPhysicsActor* a1, const CCollisionInfoList& list) {\n  for (const CCollisionInfo& info : list) {\n    CCollisionInfo infoCopy = info;\n    const float restitution = GetCoefficientOfRestitution(infoCopy) + a0.GetCoefficientOfRestitutionModifier();\n    if (a1 != nullptr) {\n      CollideWithDynamicBodyNoRot(a0, *a1, infoCopy, restitution, false);\n    } else {\n      CollideWithStaticBodyNoRot(a0, infoCopy.GetMaterialLeft(), infoCopy.GetMaterialRight(), infoCopy.GetNormalLeft(),\n                                 restitution, false);\n    }\n  }\n}\n\nvoid CGameCollision::CollideWithDynamicBodyNoRot(CPhysicsActor& a0, CPhysicsActor& a1, const CCollisionInfo& info,\n                                                 float restitution, bool zeroZ) {\n  zeus::CVector3f normal = info.GetNormalLeft();\n  if (zeroZ) {\n    normal.z() = 0.f;\n  }\n\n  zeus::CVector3f relVel = GetActorRelativeVelocities(a0, &a1);\n  float velNormDot = relVel.dot(normal);\n\n  float a0MaxCollisionVel = std::max(a0.GetVelocity().magnitude(), a0.GetMaximumCollisionVelocity());\n  float a1MaxCollisionVel = std::max(a1.GetVelocity().magnitude(), a1.GetMaximumCollisionVelocity());\n\n  bool a0Move = !a0.GetMaterialList().HasMaterial(EMaterialTypes::Immovable) && a0.GetMass() != 0.f;\n  bool a1Move = !a1.GetMaterialList().HasMaterial(EMaterialTypes::Immovable) && a1.GetMass() != 0.f;\n\n  if (velNormDot < -0.0001f) {\n    if (a0Move) {\n      if (a1Move) {\n        float impulse = CollisionImpulseFiniteVsFinite(a0.GetMass(), a1.GetMass(), velNormDot, restitution);\n        a0.ApplyImpulseWR(normal * impulse, zeus::CAxisAngle());\n        a1.ApplyImpulseWR(normal * -impulse, zeus::CAxisAngle());\n      } else {\n        float impulse = CollisionImpulseFiniteVsInfinite(a0.GetMass(), velNormDot, restitution);\n        a0.ApplyImpulseWR(normal * impulse, zeus::CAxisAngle());\n      }\n    } else {\n      if (a1Move) {\n        float impulse = CollisionImpulseFiniteVsInfinite(a1.GetMass(), velNormDot, restitution);\n        a1.ApplyImpulseWR(normal * -impulse, zeus::CAxisAngle());\n      } else {\n        a0.SetVelocityWR(zeus::skZero3f);\n        a1.SetVelocityWR(zeus::skZero3f);\n      }\n    }\n    a0.UseCollisionImpulses();\n    a1.UseCollisionImpulses();\n  } else if (velNormDot < 0.1f) {\n    if (a0Move) {\n      float impulse = 0.05f * a0.GetMass();\n      a0.ApplyImpulseWR(normal * impulse, zeus::CAxisAngle());\n      a0.UseCollisionImpulses();\n    }\n    if (a1Move) {\n      float impulse = -0.05f * a1.GetMass();\n      a1.ApplyImpulseWR(normal * impulse, zeus::CAxisAngle());\n      a1.UseCollisionImpulses();\n    }\n  }\n\n  if (a0.GetVelocity().magnitude() > a0MaxCollisionVel) {\n    a0.SetVelocityWR(a0.GetVelocity().normalized() * a0MaxCollisionVel);\n  }\n  if (a1.GetVelocity().magnitude() > a1MaxCollisionVel) {\n    a1.SetVelocityWR(a1.GetVelocity().normalized() * a1MaxCollisionVel);\n  }\n}\n\nvoid CGameCollision::CollideWithStaticBodyNoRot(CPhysicsActor& a0, const CMaterialList& m0, const CMaterialList& m1,\n                                                const zeus::CUnitVector3f& normal, float restitution, bool zeroZ) {\n  zeus::CUnitVector3f useNorm = normal;\n  if (zeroZ && m0.HasMaterial(EMaterialTypes::Player) && !m1.HasMaterial(EMaterialTypes::Floor)) {\n    useNorm.z() = 0.f;\n  }\n\n  if (useNorm.canBeNormalized()) {\n    useNorm.normalize();\n    float velNormDot = a0.GetVelocity().dot(useNorm);\n    if (velNormDot < -0.0001f) {\n      a0.ApplyImpulseWR(useNorm * CollisionImpulseFiniteVsInfinite(a0.GetMass(), velNormDot, restitution),\n                        zeus::CAxisAngle());\n      a0.UseCollisionImpulses();\n    } else if (velNormDot < 0.001f) {\n      a0.ApplyImpulseWR(0.05f * a0.GetMass() * useNorm, zeus::CAxisAngle());\n      a0.UseCollisionImpulses();\n    }\n  }\n}\n\nvoid CGameCollision::CollisionFailsafe(const CStateManager& mgr, CAreaCollisionCache& cache, CPhysicsActor& actor,\n                                       const CCollisionPrimitive& prim, const EntityList& nearList, float f1,\n                                       u32 failsafeTicks) {\n  actor.MoveCollisionPrimitive(zeus::skZero3f);\n  if (f1 > 0.5f) {\n    actor.SetNumTicksPartialUpdate(actor.GetNumTicksPartialUpdate() + 1);\n  }\n\n  if (actor.GetNumTicksPartialUpdate() > 1 ||\n      DetectCollisionBoolean_Cached(mgr, cache, prim, actor.GetPrimitiveTransform(), actor.GetMaterialFilter(),\n                                    nearList)) {\n    actor.SetNumTicksPartialUpdate(0);\n    actor.SetNumTicksStuck(actor.GetNumTicksStuck() + 1);\n    if (actor.GetNumTicksStuck() < failsafeTicks) {\n      return;\n    }\n\n    CMotionState oldMState = actor.GetMotionState();\n    CMotionState lastNonCollide = actor.GetLastNonCollidingState();\n    actor.SetMotionState(lastNonCollide);\n    if (!DetectCollisionBoolean_Cached(mgr, cache, prim, actor.GetPrimitiveTransform(), actor.GetMaterialFilter(),\n                                       nearList)) {\n      lastNonCollide.x1c_velocity *= zeus::CVector3f(0.5f);\n      lastNonCollide.x28_angularMomentum *= zeus::CVector3f(0.5f);\n      actor.SetLastNonCollidingState(lastNonCollide);\n      //++gDebugPrintCount;\n      actor.SetNumTicksStuck(0);\n    } else {\n      actor.SetMotionState(oldMState);\n      if (auto nonIntersectVec = FindNonIntersectingVector(mgr, cache, actor, prim, nearList)) {\n        oldMState.x0_translation += *nonIntersectVec;\n        actor.SetMotionState(oldMState);\n        actor.SetLastNonCollidingState(actor.GetMotionState());\n        //++gDebugPrintCount;\n      } else {\n        //++gDebugPrintCount;\n        lastNonCollide.x1c_velocity *= zeus::CVector3f(0.5f);\n        lastNonCollide.x28_angularMomentum *= zeus::CVector3f(0.5f);\n        actor.SetLastNonCollidingState(lastNonCollide);\n      }\n    }\n  } else {\n    actor.SetLastNonCollidingState(actor.GetMotionState());\n    actor.SetNumTicksStuck(0);\n  }\n}\n\nstd::optional<zeus::CVector3f>\nCGameCollision::FindNonIntersectingVector(const CStateManager& mgr, CAreaCollisionCache& cache, CPhysicsActor& actor,\n                                          const CCollisionPrimitive& prim, const EntityList& nearList) {\n  zeus::CTransform xf = actor.GetPrimitiveTransform();\n  zeus::CVector3f origOrigin = xf.origin;\n  zeus::CVector3f center = prim.CalculateAABox(xf).center();\n  for (int i = 2; i < 1000; i += (i / 2)) {\n    const float pos = static_cast<float>(i) * 0.005f;\n    const float neg = -pos;\n    for (int j = 0; j < 26; ++j) {\n      zeus::CVector3f vec;\n      switch (j) {\n      case 0:\n        vec = {0.f, pos, 0.f};\n        break;\n      case 1:\n        vec = {0.f, neg, 0.f};\n        break;\n      case 2:\n        vec = {pos, 0.f, 0.f};\n        break;\n      case 3:\n        vec = {neg, 0.f, 0.f};\n        break;\n      case 4:\n        vec = {0.f, 0.f, pos};\n        break;\n      case 5:\n        vec = {0.f, 0.f, neg};\n        break;\n      case 6:\n        vec = {0.f, pos, pos};\n        break;\n      case 7:\n        vec = {0.f, neg, neg};\n        break;\n      case 8:\n        vec = {0.f, neg, pos};\n        break;\n      case 9:\n        vec = {0.f, pos, neg};\n        break;\n      case 10:\n        vec = {pos, 0.f, pos};\n        break;\n      case 11:\n        vec = {neg, 0.f, neg};\n        break;\n      case 12:\n        vec = {neg, 0.f, pos};\n        break;\n      case 13:\n        vec = {pos, 0.f, neg};\n        break;\n      case 14:\n        vec = {pos, pos, 0.f};\n        break;\n      case 15:\n        vec = {neg, neg, 0.f};\n        break;\n      case 16:\n        vec = {neg, pos, 0.f};\n        break;\n      case 17:\n        vec = {pos, neg, 0.f};\n        break;\n      case 18:\n        vec = {pos, pos, pos};\n        break;\n      case 19:\n        vec = {neg, pos, pos};\n        break;\n      case 20:\n        vec = {pos, neg, pos};\n        break;\n      case 21:\n        vec = {neg, neg, pos};\n        break;\n      case 22:\n        vec = {pos, pos, neg};\n        break;\n      case 23:\n        vec = {neg, pos, neg};\n        break;\n      case 24:\n        vec = {pos, neg, neg};\n        break;\n      case 25:\n        vec = {neg, neg, neg};\n        break;\n      default:\n        break;\n      }\n\n      zeus::CVector3f worldPoint = vec + origOrigin;\n      if (mgr.GetWorld()->GetAreaAlways(mgr.GetNextAreaId())->GetAABB().pointInside(worldPoint)) {\n        if (mgr.RayCollideWorld(center, center + vec, nearList, CMaterialFilter::skPassEverything, &actor)) {\n          xf.origin = worldPoint;\n          if (!DetectCollisionBoolean_Cached(mgr, cache, prim, xf, actor.GetMaterialFilter(), nearList)) {\n            return {vec};\n          }\n        }\n      }\n    }\n  }\n\n  return {};\n}\n\nvoid CGameCollision::AvoidStaticCollisionWithinRadius(const CStateManager& mgr, CPhysicsActor& actor, u32 iterations,\n                                                      float dt, float height, float size, float mass, float radius) {\n  const zeus::CVector3f& actorPos = actor.GetTranslation();\n  const zeus::CVector3f pos = actorPos + zeus::CVector3f{0.f, 0.f, height};\n  const float largeRadius = 1.2f * radius;\n  const zeus::CVector3f diff{size + largeRadius, size + largeRadius, largeRadius};\n  const zeus::CAABox aabb{pos - diff, pos + diff};\n  CAreaCollisionCache cache{aabb};\n  BuildAreaCollisionCache(mgr, cache);\n\n  const CCollidableSphere prim{{pos, radius}, {EMaterialTypes::Solid}};\n  if (!DetectStaticCollisionBoolean_Cached(mgr, cache, prim, {}, CMaterialFilter::MakeExclude(EMaterialTypes::Floor))) {\n    zeus::CVector3f velocity = zeus::skZero3f;\n    float seg = zeus::degToRad(360.f) / iterations;\n    for (u32 i = 0; i < iterations; ++i) {\n      const float angle = seg * i;\n      const zeus::CVector3f vec{std::sin(angle), std::cos(angle), 0.f};\n      double out = size;\n      CCollisionInfo info{};\n      if (cache.HasCacheOverflowed()) {\n        cache.ClearCache();\n        zeus::CAABox aabb2{pos, pos};\n        aabb2.accumulateBounds(actorPos + (size * vec));\n        cache.SetCacheBounds(zeus::CAABox{aabb2.min - radius, aabb2.max + radius});\n        BuildAreaCollisionCache(mgr, cache);\n      }\n\n      if (DetectStaticCollision_Cached_Moving(mgr, cache, prim, {}, CMaterialFilter::MakeExclude(EMaterialTypes::Floor),\n                                              vec, info, out)) {\n        float force = static_cast<float>((size - out) / size) / iterations;\n        velocity -= force * vec;\n      }\n    }\n    actor.SetVelocityWR(actor.GetVelocity() + (dt * (mass * velocity)));\n  }\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CGameCollision.hpp",
    "content": "#pragma once\n\n#include <optional>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Collision/CCollisionPrimitive.hpp\"\n#include \"Runtime/Collision/CMetroidAreaCollider.hpp\"\n#include \"Runtime/Collision/CRayCastResult.hpp\"\n\n#include <zeus/CPlane.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\n\nclass CActor;\nclass CCollisionInfo;\nclass CCollisionInfoList;\nclass CGameArea;\nclass CMaterialFilter;\nclass CMaterialList;\nclass CPhysicsActor;\nclass CStateManager;\nclass ICollisionFilter;\n\nclass CGameCollision {\n  static void MovePlayer(CStateManager& mgr, CPhysicsActor& actor, float dt, const EntityList* colliderList);\n  static void MoveAndCollide(CStateManager& mgr, CPhysicsActor& actor, float dt, const ICollisionFilter& filter,\n                             const EntityList* colliderList);\n  static zeus::CVector3f GetActorRelativeVelocities(const CPhysicsActor& act0, const CPhysicsActor* act1);\n\npublic:\n  static float GetCoefficientOfRestitution(const CCollisionInfo&) { return 0.f; }\n  static bool NullMovingCollider(const CInternalCollisionStructure&, const zeus::CVector3f&, double&, CCollisionInfo&) {\n    return false;\n  }\n  static bool NullBooleanCollider(const CInternalCollisionStructure&) { return false; }\n  static bool NullCollisionCollider(const CInternalCollisionStructure&, CCollisionInfoList&) { return false; }\n  static void InitCollision();\n  static void Move(CStateManager& mgr, CPhysicsActor& actor, float dt, const EntityList* colliderList);\n\n  static bool CanBlock(const CMaterialList&, const zeus::CUnitVector3f&);\n  static bool IsFloor(const CMaterialList&, const zeus::CUnitVector3f&);\n  static void SendMaterialMessage(CStateManager&, const CMaterialList&, CActor&);\n  static CRayCastResult RayStaticIntersection(const CStateManager& mgr, const zeus::CVector3f& pos,\n                                              const zeus::CVector3f& dir, float mag, const CMaterialFilter& filter);\n  static bool RayStaticIntersectionBool(const CStateManager& mgr, const zeus::CVector3f& start,\n                                        const zeus::CVector3f& dir, float length, const CMaterialFilter& filter);\n  static CRayCastResult RayDynamicIntersection(const CStateManager& mgr, TUniqueId& idOut, const zeus::CVector3f& pos,\n                                               const zeus::CVector3f& dir, float mag, const CMaterialFilter& filter,\n                                               const EntityList& nearList);\n  static bool RayDynamicIntersectionBool(const CStateManager& mgr, const zeus::CVector3f& pos,\n                                         const zeus::CVector3f& dir, const CMaterialFilter& filter,\n                                         const EntityList& nearList, const CActor* damagee, float length);\n  static CRayCastResult RayWorldIntersection(const CStateManager& mgr, TUniqueId& idOut, const zeus::CVector3f& pos,\n                                             const zeus::CVector3f& dir, float mag, const CMaterialFilter& filter,\n                                             const EntityList& nearList);\n  static bool RayStaticIntersectionArea(const CGameArea& area, const zeus::CVector3f& pos, const zeus::CVector3f& dir,\n                                        float mag, const CMaterialFilter& filter);\n  static void BuildAreaCollisionCache(const CStateManager& mgr, CAreaCollisionCache& cache);\n  static float GetMinExtentForCollisionPrimitive(const CCollisionPrimitive& prim);\n  static bool DetectCollisionBoolean(const CStateManager& mgr, const CCollisionPrimitive& prim,\n                                     const zeus::CTransform& xf, const CMaterialFilter& filter,\n                                     const EntityList& nearList);\n  static bool DetectCollisionBoolean_Cached(const CStateManager& mgr, CAreaCollisionCache& cache,\n                                            const CCollisionPrimitive& prim, const zeus::CTransform& xf,\n                                            const CMaterialFilter& filter, const EntityList& nearList);\n  static bool DetectStaticCollisionBoolean(const CStateManager& mgr, const CCollisionPrimitive& prim,\n                                           const zeus::CTransform& xf, const CMaterialFilter& filter);\n  static bool DetectStaticCollisionBoolean_Cached(const CStateManager& mgr, CAreaCollisionCache& cache,\n                                                  const CCollisionPrimitive& prim, const zeus::CTransform& xf,\n                                                  const CMaterialFilter& filter);\n  static bool DetectDynamicCollisionBoolean(const CCollisionPrimitive& prim, const zeus::CTransform& xf,\n                                            const EntityList& nearList, const CStateManager& mgr);\n  static bool DetectCollision_Cached(const CStateManager& mgr, CAreaCollisionCache& cache,\n                                     const CCollisionPrimitive& prim, const zeus::CTransform& xf,\n                                     const CMaterialFilter& filter, const EntityList& nearList, TUniqueId& idOut,\n                                     CCollisionInfoList& infoList);\n  static bool DetectCollision_Cached_Moving(const CStateManager& mgr, CAreaCollisionCache& cache,\n                                            const CCollisionPrimitive& prim, const zeus::CTransform& xf,\n                                            const CMaterialFilter& filter, const EntityList& nearList,\n                                            const zeus::CVector3f& vec, TUniqueId& idOut, CCollisionInfo& infoOut,\n                                            double&);\n  static bool DetectStaticCollision(const CStateManager& mgr, const CCollisionPrimitive& prim,\n                                    const zeus::CTransform& xf, const CMaterialFilter& filter,\n                                    CCollisionInfoList& list);\n  static bool DetectStaticCollision_Cached(const CStateManager& mgr, CAreaCollisionCache& cache,\n                                           const CCollisionPrimitive& prim, const zeus::CTransform& xf,\n                                           const CMaterialFilter& filter, CCollisionInfoList& list);\n  static bool DetectStaticCollision_Cached_Moving(const CStateManager& mgr, CAreaCollisionCache& cache,\n                                                  const CCollisionPrimitive& prim, const zeus::CTransform& xf,\n                                                  const CMaterialFilter& filter, const zeus::CVector3f& vec,\n                                                  CCollisionInfo& infoOut, double& d);\n  static bool DetectDynamicCollision(const CCollisionPrimitive& prim, const zeus::CTransform& xf,\n                                     const EntityList& nearList, TUniqueId& idOut, CCollisionInfoList& list,\n                                     const CStateManager& mgr);\n  static bool DetectDynamicCollisionMoving(const CCollisionPrimitive& prim, const zeus::CTransform& xf,\n                                           const EntityList& nearList, const zeus::CVector3f& vec, TUniqueId& idOut,\n                                           CCollisionInfo& infoOut, double& d, const CStateManager& mgr);\n  static bool DetectCollision(const CStateManager& mgr, const CCollisionPrimitive& prim, const zeus::CTransform& xf,\n                              const CMaterialFilter& filter, const EntityList& nearList, TUniqueId& idOut,\n                              CCollisionInfoList& infoOut);\n  static void MakeCollisionCallbacks(CStateManager& mgr, CPhysicsActor& actor, TUniqueId id,\n                                     const CCollisionInfoList& list);\n  static void SendScriptMessages(CStateManager& mgr, CActor& a0, CActor* a1, const CCollisionInfoList& list);\n  static void ResolveCollisions(CPhysicsActor& a0, CPhysicsActor* a1, const CCollisionInfoList& list);\n  static void CollideWithDynamicBodyNoRot(CPhysicsActor& a0, CPhysicsActor& a1, const CCollisionInfo& info,\n                                          float restitution, bool);\n  static void CollideWithStaticBodyNoRot(CPhysicsActor& a0, const CMaterialList& m0, const CMaterialList& m1,\n                                         const zeus::CUnitVector3f& normal, float restitution, bool);\n  static void CollisionFailsafe(const CStateManager& mgr, CAreaCollisionCache& cache, CPhysicsActor& actor,\n                                const CCollisionPrimitive& prim, const EntityList& nearList, float, u32 failsafeTicks);\n  static std::optional<zeus::CVector3f> FindNonIntersectingVector(const CStateManager& mgr, CAreaCollisionCache& cache,\n                                                                  CPhysicsActor& actor, const CCollisionPrimitive& prim,\n                                                                  const EntityList& nearList);\n  static void AvoidStaticCollisionWithinRadius(const CStateManager& mgr, CPhysicsActor& actor, u32 iterations, float dt,\n                                               float height, float size, float mass, float radius);\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CInternalRayCastStructure.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Collision/CMaterialFilter.hpp\"\n\n#include <zeus/CMRay.hpp>\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CInternalRayCastStructure {\n  zeus::CMRay x0_ray;\n  float x38_maxTime;\n  zeus::CTransform x3c_xf;\n  const CMaterialFilter& x6c_filter;\n\npublic:\n  CInternalRayCastStructure(const zeus::CVector3f& start, const zeus::CVector3f& dir, float length,\n                            const zeus::CTransform& xf, const CMaterialFilter& filter)\n  : x0_ray(start, dir, length), x38_maxTime(length), x3c_xf(xf), x6c_filter(filter) {}\n\n  const zeus::CMRay& GetRay() const { return x0_ray; }\n  const zeus::CVector3f& GetStart() const { return x0_ray.start; }\n  const zeus::CVector3f& GetNormal() const { return x0_ray.end; }\n  float GetMaxTime() const { return x38_maxTime; }\n  const zeus::CTransform& GetTransform() const { return x3c_xf; }\n  const CMaterialFilter& GetFilter() const { return x6c_filter; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CJointCollisionDescription.cpp",
    "content": "#include \"Runtime/Collision/CJointCollisionDescription.hpp\"\n\nnamespace metaforce {\n\nCJointCollisionDescription::CJointCollisionDescription(ECollisionType colType, CSegId pivotId, CSegId nextId,\n                                                       const zeus::CVector3f& bounds, const zeus::CVector3f& pivotPoint,\n                                                       float radius, float maxSeparation, EOrientationType orientType,\n                                                       std::string_view name, float mass)\n: x0_colType(colType)\n, x4_orientType(orientType)\n, x8_pivotId(pivotId)\n, x9_nextId(nextId)\n, xc_bounds(bounds)\n, x18_pivotPoint(pivotPoint)\n, x24_radius(radius)\n, x28_maxSeparation(maxSeparation)\n, x2c_name(name)\n, x40_mass(mass) {}\n\nCJointCollisionDescription CJointCollisionDescription::SphereSubdivideCollision(CSegId pivotId, CSegId nextId,\n                                                                                float radius, float maxSeparation,\n                                                                                EOrientationType orientType,\n                                                                                std::string_view name, float mass) {\n  return CJointCollisionDescription(ECollisionType::SphereSubdivide, pivotId, nextId, zeus::skZero3f, zeus::skZero3f,\n                                    radius, maxSeparation, orientType, name, mass);\n}\n\nCJointCollisionDescription CJointCollisionDescription::SphereCollision(CSegId pivotId, float radius,\n                                                                       std::string_view name, float mass) {\n  return CJointCollisionDescription(ECollisionType::Sphere, pivotId, {}, zeus::skZero3f, zeus::skZero3f, radius, 0.f,\n                                    EOrientationType::Zero, name, mass);\n}\n\nCJointCollisionDescription CJointCollisionDescription::AABoxCollision(CSegId pivotId, const zeus::CVector3f& bounds,\n                                                                      std::string_view name, float mass) {\n  return CJointCollisionDescription(ECollisionType::AABox, pivotId, {}, bounds, zeus::skZero3f, 0.f, 0.f,\n                                    EOrientationType::Zero, name, mass);\n}\n\nCJointCollisionDescription CJointCollisionDescription::OBBAutoSizeCollision(CSegId pivotId, CSegId nextId,\n                                                                            const zeus::CVector3f& bounds,\n                                                                            EOrientationType orientType,\n                                                                            std::string_view name, float mass) {\n  return CJointCollisionDescription(ECollisionType::OBBAutoSize, pivotId, nextId, bounds, zeus::skZero3f, 0.f, 0.f,\n                                    orientType, name, mass);\n}\n\nCJointCollisionDescription CJointCollisionDescription::OBBCollision(CSegId pivotId, const zeus::CVector3f& bounds,\n                                                                    const zeus::CVector3f& pivotPoint,\n                                                                    std::string_view name, float mass) {\n  return CJointCollisionDescription(ECollisionType::OBB, pivotId, {}, bounds, pivotPoint, 0.f, 0.f,\n                                    EOrientationType::Zero, name, mass);\n}\n\nvoid CJointCollisionDescription::ScaleAllBounds(const zeus::CVector3f& scale) {\n  xc_bounds *= scale;\n  x24_radius *= scale.x();\n  x28_maxSeparation *= scale.x();\n  x18_pivotPoint *= scale;\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CJointCollisionDescription.hpp",
    "content": "#pragma once\n\n#include <string>\n\n#include \"Runtime/Character/CSegId.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nstruct SJointInfo {\n  const char* from;\n  const char* to;\n  float radius;\n  float separation;\n};\n\nstruct SAABoxJointInfo {\n  const char* name;\n  zeus::CVector3f extents;\n};\n\nstruct SOBBJointInfo {\n  const char* from;\n  const char* to;\n  zeus::CVector3f bounds;\n};\n\nstruct SOBBRadiiJointInfo {\n  const char* from;\n  const char* to;\n  float radius;\n};\n\nstruct SSphereJointInfo {\n  const char* name;\n  float radius;\n};\n\nclass CJointCollisionDescription {\npublic:\n  enum class ECollisionType {\n    Sphere,\n    SphereSubdivide,\n    AABox,\n    OBBAutoSize,\n    OBB,\n  };\n\n  enum class EOrientationType { Zero, One };\n\nprivate:\n  ECollisionType x0_colType;\n  EOrientationType x4_orientType;\n  CSegId x8_pivotId;\n  CSegId x9_nextId;\n  zeus::CVector3f xc_bounds;\n  zeus::CVector3f x18_pivotPoint;\n  float x24_radius;\n  float x28_maxSeparation;\n  std::string x2c_name;\n  TUniqueId x3c_actorId = kInvalidUniqueId;\n  float x40_mass;\n\npublic:\n  CJointCollisionDescription(ECollisionType, CSegId, CSegId, const zeus::CVector3f&, const zeus::CVector3f&, float,\n                             float, EOrientationType, std::string_view, float);\n  static CJointCollisionDescription SphereSubdivideCollision(CSegId pivotId, CSegId nextId, float radius,\n                                                             float maxSeparation, EOrientationType orientType,\n                                                             std::string_view name, float mass);\n  static CJointCollisionDescription SphereCollision(CSegId pivotId, float radius, std::string_view name, float mass);\n  static CJointCollisionDescription AABoxCollision(CSegId pivotId, const zeus::CVector3f& bounds, std::string_view name,\n                                                   float mass);\n  static CJointCollisionDescription OBBAutoSizeCollision(CSegId pivotId, CSegId nextId, const zeus::CVector3f& bounds,\n                                                         EOrientationType orientType, std::string_view, float mass);\n  static CJointCollisionDescription OBBCollision(CSegId pivotId, const zeus::CVector3f& bounds,\n                                                 const zeus::CVector3f& pivotPoint, std::string_view name, float mass);\n  std::string_view GetName() const { return x2c_name; }\n  TUniqueId GetCollisionActorId() const { return x3c_actorId; }\n  void SetCollisionActorId(TUniqueId id) { x3c_actorId = id; }\n  const zeus::CVector3f& GetBounds() const { return xc_bounds; }\n  float GetRadius() const { return x24_radius; }\n  float GetMaxSeparation() const { return x28_maxSeparation; }\n  EOrientationType GetOrientationType() const { return x4_orientType; }\n  float GetMass() const { return x40_mass; }\n  const zeus::CVector3f& GetPivotPoint() const { return x18_pivotPoint; }\n  ECollisionType GetType() const { return x0_colType; }\n  CSegId GetNextId() const { return x9_nextId; }\n  CSegId GetPivotId() const { return x8_pivotId; }\n  void ScaleAllBounds(const zeus::CVector3f& scale);\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CMakeLists.txt",
    "content": "set(COLLISION_SOURCES\n        CAreaOctTree.hpp CAreaOctTree.cpp\n        CollisionUtil.hpp CollisionUtil.cpp\n        CGameCollision.hpp CGameCollision.cpp\n        CCollisionResponseData.hpp CCollisionResponseData.cpp\n        CCollisionInfo.hpp CCollisionInfo.cpp\n        CCollisionInfoList.hpp\n        CCollisionEdge.hpp CCollisionEdge.cpp\n        CCollisionSurface.hpp CCollisionSurface.cpp\n        InternalColliders.hpp InternalColliders.cpp\n        CMetroidAreaCollider.hpp CMetroidAreaCollider.cpp\n        COBBTree.hpp COBBTree.cpp\n        CCollidableAABox.hpp CCollidableAABox.cpp\n        CCollidableCollisionSurface.hpp CCollidableCollisionSurface.cpp\n        CCollidableSphere.hpp CCollidableSphere.cpp\n        CCollidableOBBTree.hpp CCollidableOBBTree.cpp\n        CCollidableOBBTreeGroup.hpp CCollidableOBBTreeGroup.cpp\n        CCollisionPrimitive.hpp CCollisionPrimitive.cpp\n        CMaterialList.hpp\n        CMaterialFilter.hpp CMaterialFilter.cpp\n        CInternalRayCastStructure.hpp\n        CRayCastResult.hpp CRayCastResult.cpp\n        CCollisionActor.hpp CCollisionActor.cpp\n        CCollisionActorManager.hpp CCollisionActorManager.cpp\n        CJointCollisionDescription.hpp CJointCollisionDescription.cpp\n        CAABoxFilter.hpp CAABoxFilter.cpp\n        CBallFilter.hpp CBallFilter.cpp\n        ICollisionFilter.hpp)\n\nruntime_add_list(Collision COLLISION_SOURCES)\n"
  },
  {
    "path": "Runtime/Collision/CMaterialFilter.cpp",
    "content": "#include \"Runtime/Collision/CMaterialFilter.hpp\"\n\nnamespace metaforce {\nconst CMaterialFilter CMaterialFilter::skPassEverything({0x00000000FFFFFFFF}, {0}, EFilterType::Always);\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CMaterialFilter.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Collision/CMaterialList.hpp\"\n\nnamespace metaforce {\nclass CMaterialFilter {\npublic:\n  enum class EFilterType { Always, Include, Exclude, IncludeExclude };\n\nprivate:\n  CMaterialList x0_include = CMaterialList(0x00000000FFFFFFFF);\n  CMaterialList x8_exclude = CMaterialList(0);\n  EFilterType x10_type = EFilterType::Always;\n\npublic:\n  static const CMaterialFilter skPassEverything;\n\n  constexpr CMaterialFilter(const CMaterialList& include, const CMaterialList& exclude, EFilterType type) noexcept\n  : x0_include(include), x8_exclude(exclude), x10_type(type) {}\n\n  static constexpr CMaterialFilter MakeInclude(const CMaterialList& include) noexcept {\n    return CMaterialFilter(include, {}, EFilterType::Include);\n  }\n\n  static constexpr CMaterialFilter MakeExclude(const CMaterialList& exclude) noexcept {\n    return CMaterialFilter({u64(0x00000000FFFFFFFF)}, exclude, EFilterType::Exclude);\n  }\n\n  static constexpr CMaterialFilter MakeIncludeExclude(const CMaterialList& include,\n                                                      const CMaterialList& exclude) noexcept {\n    return CMaterialFilter(include, exclude, EFilterType::IncludeExclude);\n  }\n\n  constexpr const CMaterialList& GetIncludeList() const noexcept { return x0_include; }\n  constexpr const CMaterialList& GetExcludeList() const noexcept { return x8_exclude; }\n  constexpr CMaterialList& IncludeList() noexcept { return x0_include; }\n  constexpr CMaterialList& ExcludeList() noexcept { return x8_exclude; }\n  const CMaterialList& IncludeList() const noexcept { return x0_include; }\n  const CMaterialList& ExcludeList() const noexcept { return x8_exclude; }\n\n  constexpr bool Passes(const CMaterialList& list) const noexcept {\n    switch (x10_type) {\n    case EFilterType::Always:\n      return true;\n    case EFilterType::Include:\n      return (list.x0_list & x0_include.x0_list) != 0;\n    case EFilterType::Exclude:\n      return (list.x0_list & x8_exclude.x0_list) == 0;\n    case EFilterType::IncludeExclude:\n      if ((list.x0_list & x0_include.x0_list) == 0)\n        return false;\n      return (list.x0_list & x8_exclude.x0_list) == 0;\n    default:\n      return true;\n    }\n  }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CMaterialList.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n\nnamespace metaforce {\nenum class EMaterialTypes {\n  NoStepLogic = 0,\n  Stone = 1,\n  Metal = 2,\n  Grass = 3,\n  Ice = 4,\n  Pillar = 5,\n  MetalGrating = 6,\n  Phazon = 7,\n  Dirt = 8,\n  Lava = 9,\n  LavaStone = 10,\n  Snow = 11,\n  MudSlow = 12,\n  HalfPipe = 13,\n  Mud = 14,\n  Glass = 15,\n  Shield = 16,\n  Sand = 17,\n  ProjectilePassthrough = 18,\n  Solid = 19,\n  NoPlatformCollision = 20,\n  CameraPassthrough = 21,\n  Wood = 22,\n  Organic = 23,\n  NoEdgeCollision = 24,\n  RedundantEdgeOrFlippedTri = 25,\n  SeeThrough = 26,\n  ScanPassthrough = 27,\n  AIPassthrough = 28,\n  Ceiling = 29,\n  Wall = 30,\n  Floor = 31,\n  Player = 32,\n  Character = 33,\n  Trigger = 34,\n  Projectile = 35,\n  Bomb = 36,\n  GroundCollider = 37,\n  NoStaticCollision = 38,\n  Scannable = 39,\n  Target = 40,\n  Orbit = 41,\n  Occluder = 42,\n  Immovable = 43,\n  Debris = 44,\n  PowerBomb = 45,\n  Unknown46 = 46,\n  CollisionActor = 47,\n  AIBlock = 48,\n  Platform = 49,\n  NonSolidDamageable = 50,\n  RadarObject = 51,\n  PlatformSlave = 52,\n  AIJoint = 53,\n  Unknown54 = 54,\n  SolidCharacter = 55,\n  ExcludeFromLineOfSightTest = 56,\n  ExcludeFromRadar = 57,\n  NoPlayerCollision = 58,\n  SixtyThree = 63\n};\n\nclass CMaterialList {\n  friend class CMaterialFilter;\n  u64 x0_list = 0;\n\npublic:\n  constexpr CMaterialList() noexcept = default;\n  constexpr CMaterialList(u64 flags) noexcept : x0_list(flags) {}\n  constexpr CMaterialList(EMaterialTypes t1, EMaterialTypes t2, EMaterialTypes t3, EMaterialTypes t4, EMaterialTypes t5,\n                          EMaterialTypes t6) noexcept\n  : CMaterialList(t1, t2, t3, t4, t5) {\n    x0_list |= u64{1} << u64(t6);\n  }\n\n  constexpr CMaterialList(EMaterialTypes t1, EMaterialTypes t2, EMaterialTypes t3, EMaterialTypes t4,\n                          EMaterialTypes t5) noexcept\n  : CMaterialList(t1, t2, t3, t4) {\n    x0_list |= u64{1} << u64(t5);\n  }\n\n  constexpr CMaterialList(EMaterialTypes t1, EMaterialTypes t2, EMaterialTypes t3, EMaterialTypes t4) noexcept\n  : CMaterialList(t1, t2, t3) {\n    x0_list |= u64{1} << u64(t4);\n  }\n\n  constexpr CMaterialList(EMaterialTypes t1, EMaterialTypes t2, EMaterialTypes t3) noexcept : CMaterialList(t1, t2) {\n    x0_list |= u64{1} << u64(t3);\n  }\n\n  constexpr CMaterialList(EMaterialTypes t1, EMaterialTypes t2) noexcept : CMaterialList(t1) {\n    x0_list |= u64{1} << u64(t2);\n  }\n\n  constexpr CMaterialList(EMaterialTypes t1) noexcept : x0_list(u64{1} << u64(t1)) {}\n\n  constexpr u64 GetValue() const noexcept { return x0_list; }\n\n  static constexpr s32 BitPosition(u64 flags) noexcept {\n    for (s32 ret = 0, i = 0; i < 32; ++i) {\n      if ((flags & 1) != 0u) {\n        return ret;\n      }\n      flags >>= 1;\n      ++ret;\n    }\n    return -1;\n  }\n\n  constexpr void Add(EMaterialTypes type) noexcept { x0_list |= (u64{1} << u64(type)); }\n\n  constexpr void Add(const CMaterialList& l) noexcept { x0_list |= l.x0_list; }\n\n  constexpr void Remove(EMaterialTypes type) noexcept { x0_list &= ~(u64{1} << u64(type)); }\n\n  constexpr void Remove(const CMaterialList& other) noexcept { x0_list &= ~(other.x0_list); }\n\n  constexpr bool HasMaterial(EMaterialTypes type) const noexcept { return (x0_list & (u64{1} << u64(type))) != 0; }\n\n  constexpr bool SharesMaterials(const CMaterialList& other) const noexcept {\n    return (other.x0_list & x0_list) ? true : false;\n  }\n\n  constexpr u64 Intersection(const CMaterialList& other) const noexcept { return other.x0_list & x0_list; }\n\n  constexpr u64 XOR(const CMaterialList& other) const noexcept { return x0_list ^ other.x0_list; }\n  void Union(const CMaterialList& other) noexcept { x0_list |= other.x0_list; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CMetroidAreaCollider.cpp",
    "content": "#include \"Runtime/Collision/CMetroidAreaCollider.hpp\"\n\n#include \"Runtime/Collision/CCollisionInfoList.hpp\"\n#include \"Runtime/Collision/CMaterialFilter.hpp\"\n#include \"Runtime/Collision/CollisionUtil.hpp\"\n\nnamespace metaforce {\n\nu32 CMetroidAreaCollider::g_CalledClip = 0;\nu32 CMetroidAreaCollider::g_RejectedByClip = 0;\nu32 CMetroidAreaCollider::g_TrianglesProcessed = 0;\nu32 CMetroidAreaCollider::g_DupTrianglesProcessed = 0;\nu16 CMetroidAreaCollider::g_DupPrimitiveCheckCount = 0;\nstd::array<u16, 0x2800> CMetroidAreaCollider::g_DupVertexList{};\nstd::array<u16, 0x6000> CMetroidAreaCollider::g_DupEdgeList{};\nstd::array<u16, 0x4000> CMetroidAreaCollider::g_DupTriangleList{};\n\nCAABoxAreaCache::CAABoxAreaCache(const zeus::CAABox& aabb, const std::array<zeus::CPlane, 6>& pl,\n                                 const CMaterialFilter& filter, const CMaterialList& material,\n                                 CCollisionInfoList& collisionList)\n: x0_aabb(aabb)\n, x4_planes(pl)\n, x8_filter(filter)\n, xc_material(material)\n, x10_collisionList(collisionList)\n, x14_center(aabb.center())\n, x20_halfExtent(aabb.extents()) {}\n\nCBooleanAABoxAreaCache::CBooleanAABoxAreaCache(const zeus::CAABox& aabb, const CMaterialFilter& filter)\n: x0_aabb(aabb), x4_filter(filter), x8_center(aabb.center()), x14_halfExtent(aabb.extents()) {}\n\nCSphereAreaCache::CSphereAreaCache(const zeus::CAABox& aabb, const zeus::CSphere& sphere, const CMaterialFilter& filter,\n                                   const CMaterialList& material, CCollisionInfoList& collisionList)\n: x0_aabb(aabb), x4_sphere(sphere), x8_filter(filter), xc_material(material), x10_collisionList(collisionList) {}\n\nCBooleanSphereAreaCache::CBooleanSphereAreaCache(const zeus::CAABox& aabb, const zeus::CSphere& sphere,\n                                                 const CMaterialFilter& filter)\n: x0_aabb(aabb), x4_sphere(sphere), x8_filter(filter) {}\n\nSBoxEdge::SBoxEdge(const zeus::CAABox& aabb, int idx, const zeus::CVector3f& dir)\n: x0_seg(aabb.getEdge(zeus::CAABox::EBoxEdgeId(idx)))\n, x28_start(x0_seg.x0_start)\n, x40_end(x0_seg.x18_end)\n, x58_delta(x40_end - x28_start)\n, x70_coDir(x58_delta.cross(dir).asNormalized())\n, x88_dirCoDirDot(x28_start.dot(x70_coDir)) {}\n\nstatic void FlagEdgeIndicesForFace(int face, std::array<bool, 12>& edgeFlags) {\n  switch (face) {\n  case 0:\n    edgeFlags[10] = true;\n    edgeFlags[11] = true;\n    edgeFlags[2] = true;\n    edgeFlags[4] = true;\n    return;\n  case 1:\n    edgeFlags[8] = true;\n    edgeFlags[9] = true;\n    edgeFlags[0] = true;\n    edgeFlags[6] = true;\n    return;\n  case 2:\n    edgeFlags[4] = true;\n    edgeFlags[5] = true;\n    edgeFlags[6] = true;\n    edgeFlags[7] = true;\n    return;\n  case 3:\n    edgeFlags[0] = true;\n    edgeFlags[1] = true;\n    edgeFlags[2] = true;\n    edgeFlags[3] = true;\n    return;\n  case 4:\n    edgeFlags[7] = true;\n    edgeFlags[8] = true;\n    edgeFlags[3] = true;\n    edgeFlags[11] = true;\n    return;\n  case 5:\n    edgeFlags[1] = true;\n    edgeFlags[5] = true;\n    edgeFlags[9] = true;\n    edgeFlags[10] = true;\n    return;\n  default:\n    break;\n  }\n}\n\nstatic void FlagVertexIndicesForFace(int face, std::array<bool, 8>& vertFlags) {\n  switch (face) {\n  case 0:\n    vertFlags[1] = true;\n    vertFlags[3] = true;\n    vertFlags[5] = true;\n    vertFlags[7] = true;\n    return;\n  case 1:\n    vertFlags[0] = true;\n    vertFlags[2] = true;\n    vertFlags[4] = true;\n    vertFlags[6] = true;\n    return;\n  case 2:\n    vertFlags[2] = true;\n    vertFlags[3] = true;\n    vertFlags[6] = true;\n    vertFlags[7] = true;\n    return;\n  case 3:\n    vertFlags[0] = true;\n    vertFlags[1] = true;\n    vertFlags[4] = true;\n    vertFlags[5] = true;\n    return;\n  case 4:\n    vertFlags[4] = true;\n    vertFlags[5] = true;\n    vertFlags[6] = true;\n    vertFlags[7] = true;\n    return;\n  case 5:\n    vertFlags[0] = true;\n    vertFlags[1] = true;\n    vertFlags[2] = true;\n    vertFlags[3] = true;\n    return;\n  default:\n    break;\n  }\n}\n\nCMovingAABoxComponents::CMovingAABoxComponents(const zeus::CAABox& aabb, const zeus::CVector3f& dir) : x6e8_aabb(aabb) {\n  std::array<bool, 12> edgeFlags{};\n  std::array<bool, 8> vertFlags{};\n  int useFaces = 0;\n\n  for (int i = 0; i < 3; ++i) {\n    if (dir[i] != 0.f) {\n      const int face = i * 2 + (dir[i] < 0.f);\n      FlagEdgeIndicesForFace(face, edgeFlags);\n      FlagVertexIndicesForFace(face, vertFlags);\n      useFaces += 1;\n    }\n  }\n\n  for (size_t i = 0; i < edgeFlags.size(); ++i) {\n    if (edgeFlags[i]) {\n      x0_edges.emplace_back(aabb, s32(i), dir);\n    }\n  }\n\n  for (size_t i = 0; i < vertFlags.size(); ++i) {\n    if (vertFlags[i]) {\n      x6c4_vertIdxs.push_back(u32(i));\n    }\n  }\n\n  if (useFaces == 1) {\n    x6e8_aabb = zeus::CAABox();\n    x6e8_aabb.accumulateBounds(aabb.getPoint(x6c4_vertIdxs[0]));\n    x6e8_aabb.accumulateBounds(aabb.getPoint(x6c4_vertIdxs[1]));\n    x6e8_aabb.accumulateBounds(aabb.getPoint(x6c4_vertIdxs[2]));\n    x6e8_aabb.accumulateBounds(aabb.getPoint(x6c4_vertIdxs[3]));\n  }\n}\n\nCMetroidAreaCollider::COctreeLeafCache::COctreeLeafCache(const CAreaOctTree& octTree) : x0_octTree(octTree) {}\n\nvoid CMetroidAreaCollider::COctreeLeafCache::AddLeaf(const CAreaOctTree::Node& node) {\n  if (x4_nodeCache.size() == x4_nodeCache.capacity()) {\n    x908_24_overflow = true;\n    return;\n  }\n\n  x4_nodeCache.push_back(node);\n}\n\nvoid CMetroidAreaCollider::BuildOctreeLeafCache(const CAreaOctTree::Node& node, const zeus::CAABox& aabb,\n                                                CMetroidAreaCollider::COctreeLeafCache& cache) {\n  for (int i = 0; i < 8; ++i) {\n    CAreaOctTree::Node::ETreeType type = node.GetChildType(i);\n    if (type != CAreaOctTree::Node::ETreeType::Invalid) {\n      CAreaOctTree::Node ch = node.GetChild(i);\n      if (aabb.intersects(ch.GetBoundingBox())) {\n        if (type == CAreaOctTree::Node::ETreeType::Leaf)\n          cache.AddLeaf(ch);\n        else\n          BuildOctreeLeafCache(ch, aabb, cache);\n      }\n    }\n  }\n}\n\nstatic zeus::CVector3f ClipRayToPlane(const zeus::CVector3f& a, const zeus::CVector3f& b, const zeus::CPlane& plane) {\n  return (1.f - -plane.pointToPlaneDist(a) / (b - a).dot(plane.normal())) * (a - b) + b;\n}\n\nbool CMetroidAreaCollider::ConvexPolyCollision(const std::array<zeus::CPlane, 6>& planes,\n                                               const std::array<zeus::CVector3f, 3>& verts, zeus::CAABox& aabb) {\n  std::array<rstl::reserved_vector<zeus::CVector3f, 20>, 2> vecs;\n\n  g_CalledClip += 1;\n  g_RejectedByClip -= 1;\n\n  vecs[0].push_back(verts[0]);\n  vecs[0].push_back(verts[1]);\n  vecs[0].push_back(verts[2]);\n\n  int vecIdx = 0;\n  int otherVecIdx = 1;\n  for (int i = 0; i < 6; ++i) {\n    rstl::reserved_vector<zeus::CVector3f, 20>& vec = vecs[vecIdx];\n    rstl::reserved_vector<zeus::CVector3f, 20>& otherVec = vecs[otherVecIdx];\n    otherVec.clear();\n\n    bool inFrontOf = planes[i].pointToPlaneDist(vec.front()) >= 0.f;\n    for (size_t j = 0; j < vec.size(); ++j) {\n      const zeus::CVector3f& b = vec[(j + 1) % vec.size()];\n      if (inFrontOf)\n        otherVec.push_back(vec[j]);\n      bool nextInFrontOf = planes[i].pointToPlaneDist(b) >= 0.f;\n      if (nextInFrontOf ^ inFrontOf)\n        otherVec.push_back(ClipRayToPlane(vec[j], b, planes[i]));\n      inFrontOf = nextInFrontOf;\n    }\n\n    if (otherVec.empty())\n      return false;\n\n    vecIdx ^= 1;\n    otherVecIdx ^= 1;\n  }\n\n  rstl::reserved_vector<zeus::CVector3f, 20>& accumVec = vecs[otherVecIdx ^ 1];\n  for (const zeus::CVector3f& point : accumVec)\n    aabb.accumulateBounds(point);\n\n  g_RejectedByClip -= 1;\n  return true;\n}\n\nbool CMetroidAreaCollider::AABoxCollisionCheckBoolean_Cached(const COctreeLeafCache& leafCache,\n                                                             const zeus::CAABox& aabb, const CMaterialFilter& filter) {\n  CBooleanAABoxAreaCache cache(aabb, filter);\n\n  for (const CAreaOctTree::Node& node : leafCache.x4_nodeCache) {\n    if (cache.x0_aabb.intersects(node.GetBoundingBox())) {\n      CAreaOctTree::TriListReference list = node.GetTriangleArray();\n      for (int j = 0; j < list.GetSize(); ++j) {\n        ++g_TrianglesProcessed;\n        CCollisionSurface surf = node.GetOwner().GetMasterListTriangle(list.GetAt(j));\n        if (cache.x4_filter.Passes(CMaterialList(surf.GetSurfaceFlags()))) {\n          if (CollisionUtil::TriBoxOverlap(cache.x8_center, cache.x14_halfExtent, surf.GetVert(0), surf.GetVert(1),\n                                           surf.GetVert(2)))\n            return true;\n        }\n      }\n    }\n  }\n\n  return false;\n}\n\nbool CMetroidAreaCollider::AABoxCollisionCheckBoolean_Internal(const CAreaOctTree::Node& node,\n                                                               const CBooleanAABoxAreaCache& cache) {\n  for (int i = 0; i < 8; ++i) {\n    CAreaOctTree::Node::ETreeType type = node.GetChildType(i);\n    if (type != CAreaOctTree::Node::ETreeType::Invalid) {\n      CAreaOctTree::Node ch = node.GetChild(i);\n      if (cache.x0_aabb.intersects(ch.GetBoundingBox())) {\n        if (type == CAreaOctTree::Node::ETreeType::Leaf) {\n          CAreaOctTree::TriListReference list = ch.GetTriangleArray();\n          for (int j = 0; j < list.GetSize(); ++j) {\n            ++g_TrianglesProcessed;\n            CCollisionSurface surf = ch.GetOwner().GetMasterListTriangle(list.GetAt(j));\n            if (cache.x4_filter.Passes(CMaterialList(surf.GetSurfaceFlags()))) {\n              if (CollisionUtil::TriBoxOverlap(cache.x8_center, cache.x14_halfExtent, surf.GetVert(0), surf.GetVert(1),\n                                               surf.GetVert(2)))\n                return true;\n            }\n          }\n        } else {\n          if (AABoxCollisionCheckBoolean_Internal(ch, cache))\n            return true;\n        }\n      }\n    }\n  }\n  return false;\n}\n\nbool CMetroidAreaCollider::AABoxCollisionCheckBoolean(const CAreaOctTree& octTree, const zeus::CAABox& aabb,\n                                                      const CMaterialFilter& filter) {\n  CBooleanAABoxAreaCache cache(aabb, filter);\n  return AABoxCollisionCheckBoolean_Internal(octTree.GetRootNode(), cache);\n}\n\nbool CMetroidAreaCollider::SphereCollisionCheckBoolean_Cached(const COctreeLeafCache& leafCache,\n                                                              const zeus::CAABox& aabb, const zeus::CSphere& sphere,\n                                                              const CMaterialFilter& filter) {\n  CBooleanSphereAreaCache cache(aabb, sphere, filter);\n\n  for (const CAreaOctTree::Node& node : leafCache.x4_nodeCache) {\n    if (cache.x0_aabb.intersects(node.GetBoundingBox())) {\n      CAreaOctTree::TriListReference list = node.GetTriangleArray();\n      for (int j = 0; j < list.GetSize(); ++j) {\n        ++g_TrianglesProcessed;\n        CCollisionSurface surf = node.GetOwner().GetMasterListTriangle(list.GetAt(j));\n        if (cache.x8_filter.Passes(CMaterialList(surf.GetSurfaceFlags()))) {\n          if (CollisionUtil::TriSphereOverlap(cache.x4_sphere, surf.GetVert(0), surf.GetVert(1), surf.GetVert(2)))\n            return true;\n        }\n      }\n    }\n  }\n\n  return false;\n}\n\nbool CMetroidAreaCollider::SphereCollisionCheckBoolean_Internal(const CAreaOctTree::Node& node,\n                                                                const CBooleanSphereAreaCache& cache) {\n  for (int i = 0; i < 8; ++i) {\n    CAreaOctTree::Node::ETreeType type = node.GetChildType(i);\n    if (type != CAreaOctTree::Node::ETreeType::Invalid) {\n      CAreaOctTree::Node ch = node.GetChild(i);\n      if (cache.x0_aabb.intersects(ch.GetBoundingBox())) {\n        if (type == CAreaOctTree::Node::ETreeType::Leaf) {\n          CAreaOctTree::TriListReference list = ch.GetTriangleArray();\n          for (int j = 0; j < list.GetSize(); ++j) {\n            ++g_TrianglesProcessed;\n            CCollisionSurface surf = ch.GetOwner().GetMasterListTriangle(list.GetAt(j));\n            if (cache.x8_filter.Passes(CMaterialList(surf.GetSurfaceFlags()))) {\n              if (CollisionUtil::TriSphereOverlap(cache.x4_sphere, surf.GetVert(0), surf.GetVert(1), surf.GetVert(2)))\n                return true;\n            }\n          }\n        } else {\n          if (SphereCollisionCheckBoolean_Internal(ch, cache))\n            return true;\n        }\n      }\n    }\n  }\n  return false;\n}\n\nbool CMetroidAreaCollider::SphereCollisionCheckBoolean(const CAreaOctTree& octTree, const zeus::CAABox& aabb,\n                                                       const zeus::CSphere& sphere, const CMaterialFilter& filter) {\n  CAreaOctTree::Node node = octTree.GetRootNode();\n  CBooleanSphereAreaCache cache(aabb, sphere, filter);\n  return SphereCollisionCheckBoolean_Internal(node, cache);\n}\n\nbool CMetroidAreaCollider::AABoxCollisionCheck_Cached(const COctreeLeafCache& leafCache, const zeus::CAABox& aabb,\n                                                      const CMaterialFilter& filter, const CMaterialList& matList,\n                                                      CCollisionInfoList& list) {\n  bool ret = false;\n  const std::array<zeus::CPlane, 6> planes{{\n      {zeus::skRight, aabb.min.dot(zeus::skRight)},\n      {zeus::skLeft, aabb.max.dot(zeus::skLeft)},\n      {zeus::skForward, aabb.min.dot(zeus::skForward)},\n      {zeus::skBack, aabb.max.dot(zeus::skBack)},\n      {zeus::skUp, aabb.min.dot(zeus::skUp)},\n      {zeus::skDown, aabb.max.dot(zeus::skDown)},\n  }};\n  CAABoxAreaCache cache(aabb, planes, filter, matList, list);\n\n  ResetInternalCounters();\n\n  for (const CAreaOctTree::Node& node : leafCache.x4_nodeCache) {\n    if (aabb.intersects(node.GetBoundingBox())) {\n      CAreaOctTree::TriListReference listRef = node.GetTriangleArray();\n      for (int j = 0; j < listRef.GetSize(); ++j) {\n        ++g_TrianglesProcessed;\n        u16 triIdx = listRef.GetAt(j);\n        if (g_DupPrimitiveCheckCount == g_DupTriangleList[triIdx]) {\n          g_DupTrianglesProcessed += 1;\n        } else {\n          g_DupTriangleList[triIdx] = g_DupPrimitiveCheckCount;\n          CCollisionSurface surf = node.GetOwner().GetMasterListTriangle(triIdx);\n          CMaterialList material(surf.GetSurfaceFlags());\n          if (cache.x8_filter.Passes(material)) {\n            if (CollisionUtil::TriBoxOverlap(cache.x14_center, cache.x20_halfExtent, surf.GetVert(0), surf.GetVert(1),\n                                             surf.GetVert(2))) {\n              zeus::CAABox aabb2 = zeus::CAABox();\n              if (ConvexPolyCollision(cache.x4_planes, surf.GetVerts(), aabb2)) {\n                zeus::CPlane plane = surf.GetPlane();\n                CCollisionInfo collision(aabb2, cache.xc_material, material, plane.normal(), -plane.normal());\n                cache.x10_collisionList.Add(collision, false);\n                ret = true;\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n\n  return ret;\n}\n\nbool CMetroidAreaCollider::AABoxCollisionCheck_Internal(const CAreaOctTree::Node& node, const CAABoxAreaCache& cache) {\n  bool ret = false;\n\n  switch (node.GetTreeType()) {\n  case CAreaOctTree::Node::ETreeType::Invalid:\n    return false;\n  case CAreaOctTree::Node::ETreeType::Branch: {\n    for (int i = 0; i < 8; ++i) {\n      CAreaOctTree::Node ch = node.GetChild(i);\n      if (ch.GetBoundingBox().intersects(cache.x0_aabb))\n        if (AABoxCollisionCheck_Internal(ch, cache))\n          ret = true;\n    }\n    break;\n  }\n  case CAreaOctTree::Node::ETreeType::Leaf: {\n    CAreaOctTree::TriListReference list = node.GetTriangleArray();\n    for (int j = 0; j < list.GetSize(); ++j) {\n      ++g_TrianglesProcessed;\n      u16 triIdx = list.GetAt(j);\n      if (g_DupPrimitiveCheckCount == g_DupTriangleList[triIdx]) {\n        g_DupTrianglesProcessed += 1;\n      } else {\n        g_DupTriangleList[triIdx] = g_DupPrimitiveCheckCount;\n        CCollisionSurface surf = node.GetOwner().GetMasterListTriangle(triIdx);\n        CMaterialList material(surf.GetSurfaceFlags());\n        if (cache.x8_filter.Passes(material)) {\n          if (CollisionUtil::TriBoxOverlap(cache.x14_center, cache.x20_halfExtent, surf.GetVert(0), surf.GetVert(1),\n                                           surf.GetVert(2))) {\n            zeus::CAABox aabb = zeus::CAABox();\n            if (ConvexPolyCollision(cache.x4_planes, surf.GetVerts(), aabb)) {\n              zeus::CPlane plane = surf.GetPlane();\n              CCollisionInfo collision(aabb, cache.xc_material, material, plane.normal(), -plane.normal());\n              cache.x10_collisionList.Add(collision, false);\n              ret = true;\n            }\n          }\n        }\n      }\n    }\n    break;\n  }\n  default:\n    break;\n  }\n\n  return ret;\n}\n\nbool CMetroidAreaCollider::AABoxCollisionCheck(const CAreaOctTree& octTree, const zeus::CAABox& aabb,\n                                               const CMaterialFilter& filter, const CMaterialList& matList,\n                                               CCollisionInfoList& list) {\n  const std::array<zeus::CPlane, 6> planes{{\n      {zeus::skRight, aabb.min.dot(zeus::skRight)},\n      {zeus::skLeft, aabb.max.dot(zeus::skLeft)},\n      {zeus::skForward, aabb.min.dot(zeus::skForward)},\n      {zeus::skBack, aabb.max.dot(zeus::skBack)},\n      {zeus::skUp, aabb.min.dot(zeus::skUp)},\n      {zeus::skDown, aabb.max.dot(zeus::skDown)},\n  }};\n  const CAABoxAreaCache cache(aabb, planes, filter, matList, list);\n\n  ResetInternalCounters();\n\n  const CAreaOctTree::Node node = octTree.GetRootNode();\n  return AABoxCollisionCheck_Internal(node, cache);\n}\n\nbool CMetroidAreaCollider::SphereCollisionCheck_Cached(const COctreeLeafCache& leafCache, const zeus::CAABox& aabb,\n                                                       const zeus::CSphere& sphere, const CMaterialList& matList,\n                                                       const CMaterialFilter& filter, CCollisionInfoList& clist) {\n  ResetInternalCounters();\n\n  bool ret = false;\n  zeus::CVector3f point, normal;\n\n  for (const CAreaOctTree::Node& node : leafCache.x4_nodeCache) {\n    if (aabb.intersects(node.GetBoundingBox())) {\n      CAreaOctTree::TriListReference list = node.GetTriangleArray();\n      for (int j = 0; j < list.GetSize(); ++j) {\n        ++g_TrianglesProcessed;\n        u16 triIdx = list.GetAt(j);\n        if (g_DupPrimitiveCheckCount == g_DupTriangleList[triIdx]) {\n          g_DupTrianglesProcessed += 1;\n        } else {\n          g_DupTriangleList[triIdx] = g_DupPrimitiveCheckCount;\n          CCollisionSurface surf = node.GetOwner().GetMasterListTriangle(triIdx);\n          CMaterialList material(surf.GetSurfaceFlags());\n          if (filter.Passes(material)) {\n            if (CollisionUtil::TriSphereIntersection(sphere, surf.GetVert(0), surf.GetVert(1), surf.GetVert(2), point,\n                                                     normal)) {\n              CCollisionInfo collision(point, matList, material, normal);\n              clist.Add(collision, false);\n              ret = true;\n            }\n          }\n        }\n      }\n    }\n  }\n\n  return ret;\n}\n\nbool CMetroidAreaCollider::SphereCollisionCheck_Internal(const CAreaOctTree::Node& node,\n                                                         const CSphereAreaCache& cache) {\n  bool ret = false;\n  zeus::CVector3f point, normal;\n\n  for (int i = 0; i < 8; ++i) {\n    CAreaOctTree::Node::ETreeType chTp = node.GetChildType(i);\n    if (chTp != CAreaOctTree::Node::ETreeType::Invalid) {\n      CAreaOctTree::Node ch = node.GetChild(i);\n      if (cache.x0_aabb.intersects(ch.GetBoundingBox())) {\n        if (chTp == CAreaOctTree::Node::ETreeType::Leaf) {\n          CAreaOctTree::TriListReference list = ch.GetTriangleArray();\n          for (int j = 0; j < list.GetSize(); ++j) {\n            ++g_TrianglesProcessed;\n            u16 triIdx = list.GetAt(j);\n            if (g_DupPrimitiveCheckCount == g_DupTriangleList[triIdx]) {\n              g_DupTrianglesProcessed += 1;\n            } else {\n              g_DupTriangleList[triIdx] = g_DupPrimitiveCheckCount;\n              CCollisionSurface surf = ch.GetOwner().GetMasterListTriangle(triIdx);\n              CMaterialList material(surf.GetSurfaceFlags());\n              if (cache.x8_filter.Passes(material)) {\n                if (CollisionUtil::TriSphereIntersection(cache.x4_sphere, surf.GetVert(0), surf.GetVert(1),\n                                                         surf.GetVert(2), point, normal)) {\n                  CCollisionInfo collision(point, cache.xc_material, material, normal);\n                  cache.x10_collisionList.Add(collision, false);\n                  ret = true;\n                }\n              }\n            }\n          }\n        } else {\n          if (SphereCollisionCheck_Internal(ch, cache))\n            ret = true;\n        }\n      }\n    }\n  }\n\n  return ret;\n}\n\nbool CMetroidAreaCollider::SphereCollisionCheck(const CAreaOctTree& octTree, const zeus::CAABox& aabb,\n                                                const zeus::CSphere& sphere, const CMaterialList& matList,\n                                                const CMaterialFilter& filter, CCollisionInfoList& list) {\n  CSphereAreaCache cache(aabb, sphere, filter, matList, list);\n  ResetInternalCounters();\n  CAreaOctTree::Node node = octTree.GetRootNode();\n  return SphereCollisionCheck_Internal(node, cache);\n}\n\nbool CMetroidAreaCollider::MovingAABoxCollisionCheck_BoxVertexTri(\n    const CCollisionSurface& surf, const zeus::CAABox& aabb, const rstl::reserved_vector<u32, 8>& vertIndices,\n    const zeus::CVector3f& dir, double& d, zeus::CVector3f& normalOut, zeus::CVector3f& pointOut) {\n  bool ret = false;\n  for (u32 idx : vertIndices) {\n    zeus::CVector3f point = aabb.getPoint(idx);\n    if (CollisionUtil::RayTriangleIntersection_Double(point, dir, surf.GetVerts(), d)) {\n      pointOut = float(d) * dir + point;\n      normalOut = surf.GetNormal();\n      ret = true;\n    }\n  }\n  return ret;\n}\n\nbool CMetroidAreaCollider::MovingAABoxCollisionCheck_TriVertexBox(const zeus::CVector3f& vert, const zeus::CAABox& aabb,\n                                                                  const zeus::CVector3f& dir, double& dOut,\n                                                                  zeus::CVector3f& normal, zeus::CVector3f& point) {\n  zeus::CMRay ray(vert, -dir, dOut);\n  zeus::CVector3f norm;\n  double d;\n  if (CollisionUtil::RayAABoxIntersection_Double(ray, aabb, norm, d) == 2) {\n    d *= dOut;\n    if (d < dOut) {\n      normal = -norm;\n      dOut = d;\n      point = vert;\n      return true;\n    }\n  }\n  return false;\n}\n\nbool CMetroidAreaCollider::MovingAABoxCollisionCheck_Edge(const zeus::CVector3f& ev0, const zeus::CVector3f& ev1,\n                                                          const rstl::reserved_vector<SBoxEdge, 12>& edges,\n                                                          const zeus::CVector3f& dir, double& d,\n                                                          zeus::CVector3f& normal, zeus::CVector3f& point) {\n  bool ret = false;\n\n  for (const SBoxEdge& edge : edges) {\n    zeus::CVector3d ev0d = ev0;\n    zeus::CVector3d ev1d = ev1;\n    if ((edge.x70_coDir.dot(ev1d) >= edge.x88_dirCoDirDot) != (edge.x70_coDir.dot(ev0d) >= edge.x88_dirCoDirDot)) {\n      zeus::CVector3d delta = ev0d - ev1d;\n      zeus::CVector3d cross0 = edge.x58_delta.cross(delta);\n      if (cross0.magSquared() >= DBL_EPSILON) {\n        zeus::CVector3d cross0Norm = cross0.asNormalized();\n        if (cross0Norm.dot(dir) >= 0.0) {\n          ev1d = ev0;\n          ev0d = ev1;\n          delta = ev0d - ev1d;\n          cross0Norm = edge.x58_delta.cross(delta).asNormalized();\n        }\n\n        zeus::CVector3d clipped =\n            ev0d + (-(ev0d.dot(edge.x70_coDir) - edge.x88_dirCoDirDot) / delta.dot(edge.x70_coDir)) * delta;\n        int maxCompIdx = (std::fabs(edge.x70_coDir.x()) > std::fabs(edge.x70_coDir.y())) ? 0 : 1;\n        if (std::fabs(edge.x70_coDir[maxCompIdx]) < std::fabs(edge.x70_coDir.z()))\n          maxCompIdx = 2;\n\n        int ci0, ci1;\n        switch (maxCompIdx) {\n        case 0:\n          ci0 = 1;\n          ci1 = 2;\n          break;\n        case 1:\n          ci0 = 0;\n          ci1 = 2;\n          break;\n        default:\n          ci0 = 0;\n          ci1 = 1;\n          break;\n        }\n\n        double mag = (edge.x58_delta[ci0] * (clipped[ci1] - edge.x28_start[ci1]) -\n                      edge.x58_delta[ci1] * (clipped[ci0] - edge.x28_start[ci0])) /\n                     (edge.x58_delta[ci0] * dir[ci1] - edge.x58_delta[ci1] * dir[ci0]);\n        if (mag >= 0.0 && mag < d) {\n          zeus::CVector3d clippedMag = clipped - mag * zeus::CVector3d(dir);\n          if ((edge.x28_start - clippedMag).dot(edge.x40_end - clippedMag) < 0.0 && mag < d) {\n            normal = cross0Norm.asCVector3f();\n            d = mag;\n            point = clipped.asCVector3f();\n            ret = true;\n          }\n        }\n      }\n    }\n  }\n\n  return ret;\n}\n\nbool CMetroidAreaCollider::MovingAABoxCollisionCheck_Cached(const COctreeLeafCache& leafCache, const zeus::CAABox& aabb,\n                                                            const CMaterialFilter& filter, const CMaterialList& matList,\n                                                            const zeus::CVector3f& dir, float mag,\n                                                            CCollisionInfo& infoOut, double& dOut) {\n  bool ret = false;\n  ResetInternalCounters();\n  dOut = mag;\n\n  CMovingAABoxComponents components(aabb, dir);\n\n  zeus::CAABox movedAABB = components.x6e8_aabb;\n  zeus::CVector3f moveVec = mag * dir;\n  movedAABB.accumulateBounds(aabb.min + moveVec);\n  movedAABB.accumulateBounds(aabb.max + moveVec);\n\n  zeus::CVector3f center = movedAABB.center();\n  zeus::CVector3f extent = movedAABB.extents();\n\n  zeus::CVector3f normal, point;\n\n  for (const CAreaOctTree::Node& node : leafCache.x4_nodeCache) {\n    if (movedAABB.intersects(node.GetBoundingBox())) {\n      CAreaOctTree::TriListReference list = node.GetTriangleArray();\n      for (int j = 0; j < list.GetSize(); ++j) {\n        u16 triIdx = list.GetAt(j);\n        if (g_DupPrimitiveCheckCount != g_DupTriangleList[triIdx]) {\n          g_TrianglesProcessed += 1;\n          g_DupTriangleList[triIdx] = g_DupPrimitiveCheckCount;\n          CMaterialList triMat(node.GetOwner().GetTriangleMaterial(triIdx));\n          if (filter.Passes(triMat)) {\n            std::array<u16, 3> vertIndices;\n            node.GetOwner().GetTriangleVertexIndices(triIdx, vertIndices.data());\n            CCollisionSurface surf(node.GetOwner().GetVert(vertIndices[0]), node.GetOwner().GetVert(vertIndices[1]),\n                                   node.GetOwner().GetVert(vertIndices[2]), triMat.GetValue());\n\n            if (CollisionUtil::TriBoxOverlap(center, extent, surf.GetVert(0), surf.GetVert(1), surf.GetVert(2))) {\n              bool triRet = false;\n              double d = dOut;\n              if (MovingAABoxCollisionCheck_BoxVertexTri(surf, aabb, components.x6c4_vertIdxs, dir, d, normal, point) &&\n                  d < dOut) {\n                triRet = true;\n                ret = true;\n                infoOut = CCollisionInfo(point, matList, triMat, normal);\n                dOut = d;\n              }\n\n              for (const u16 vertIdx : vertIndices) {\n                zeus::CVector3f vtx = node.GetOwner().GetVert(vertIdx);\n                if (g_DupPrimitiveCheckCount != g_DupVertexList[vertIdx]) {\n                  g_DupVertexList[vertIdx] = g_DupPrimitiveCheckCount;\n                  if (movedAABB.pointInside(vtx)) {\n                    d = dOut;\n                    if (MovingAABoxCollisionCheck_TriVertexBox(vtx, aabb, dir, d, normal, point) && d < dOut) {\n                      CMaterialList vertMat(node.GetOwner().GetVertMaterial(vertIdx));\n                      triRet = true;\n                      ret = true;\n                      infoOut = CCollisionInfo(point, matList, vertMat, normal);\n                      dOut = d;\n                    }\n                  }\n                }\n              }\n\n              const u16* edgeIndices = node.GetOwner().GetTriangleEdgeIndices(triIdx);\n              for (int k = 0; k < 3; ++k) {\n                u16 edgeIdx = edgeIndices[k];\n                if (g_DupPrimitiveCheckCount != g_DupEdgeList[edgeIdx]) {\n                  g_DupEdgeList[edgeIdx] = g_DupPrimitiveCheckCount;\n                  CMaterialList edgeMat(node.GetOwner().GetEdgeMaterial(edgeIdx));\n                  if (!edgeMat.HasMaterial(EMaterialTypes::NoEdgeCollision)) {\n                    d = dOut;\n                    const CCollisionEdge& edge = node.GetOwner().GetEdge(edgeIdx);\n                    if (MovingAABoxCollisionCheck_Edge(node.GetOwner().GetVert(edge.GetVertIndex1()),\n                                                       node.GetOwner().GetVert(edge.GetVertIndex2()),\n                                                       components.x0_edges, dir, d, normal, point) &&\n                        d < dOut) {\n                      triRet = true;\n                      ret = true;\n                      infoOut = CCollisionInfo(point, matList, edgeMat, normal);\n                      dOut = d;\n                    }\n                  }\n                }\n              }\n\n              if (triRet) {\n                moveVec = float(dOut) * dir;\n                movedAABB = components.x6e8_aabb;\n                movedAABB.accumulateBounds(aabb.min + moveVec);\n                movedAABB.accumulateBounds(aabb.max + moveVec);\n                center = movedAABB.center();\n                extent = movedAABB.extents();\n              }\n            } else {\n              const u16* edgeIndices = node.GetOwner().GetTriangleEdgeIndices(triIdx);\n              g_DupEdgeList[edgeIndices[0]] = g_DupPrimitiveCheckCount;\n              g_DupEdgeList[edgeIndices[1]] = g_DupPrimitiveCheckCount;\n              g_DupEdgeList[edgeIndices[2]] = g_DupPrimitiveCheckCount;\n              g_DupVertexList[vertIndices[0]] = g_DupPrimitiveCheckCount;\n              g_DupVertexList[vertIndices[1]] = g_DupPrimitiveCheckCount;\n              g_DupVertexList[vertIndices[2]] = g_DupPrimitiveCheckCount;\n            }\n          }\n        }\n      }\n    }\n  }\n\n  return ret;\n}\n\nbool CMetroidAreaCollider::MovingSphereCollisionCheck_Cached(const COctreeLeafCache& leafCache,\n                                                             const zeus::CAABox& aabb, const zeus::CSphere& sphere,\n                                                             const CMaterialFilter& filter,\n                                                             const CMaterialList& matList, const zeus::CVector3f& dir,\n                                                             float mag, CCollisionInfo& infoOut, double& dOut) {\n  bool ret = false;\n  ResetInternalCounters();\n  dOut = mag;\n\n  zeus::CAABox movedAABB = aabb;\n  zeus::CVector3f moveVec = mag * dir;\n  movedAABB.accumulateBounds(aabb.min + moveVec);\n  movedAABB.accumulateBounds(aabb.max + moveVec);\n\n  zeus::CVector3f center = movedAABB.center();\n  zeus::CVector3f extent = movedAABB.extents();\n\n  for (const CAreaOctTree::Node& node : leafCache.x4_nodeCache) {\n    if (movedAABB.intersects(node.GetBoundingBox())) {\n      CAreaOctTree::TriListReference list = node.GetTriangleArray();\n      for (int j = 0; j < list.GetSize(); ++j) {\n        u16 triIdx = list.GetAt(j);\n        if (g_DupPrimitiveCheckCount != g_DupTriangleList[triIdx]) {\n          g_TrianglesProcessed += 1;\n          g_DupTriangleList[triIdx] = g_DupPrimitiveCheckCount;\n          CMaterialList triMat(node.GetOwner().GetTriangleMaterial(triIdx));\n          if (filter.Passes(triMat)) {\n            std::array<u16, 3> vertIndices;\n            node.GetOwner().GetTriangleVertexIndices(triIdx, vertIndices.data());\n            CCollisionSurface surf(node.GetOwner().GetVert(vertIndices[0]), node.GetOwner().GetVert(vertIndices[1]),\n                                   node.GetOwner().GetVert(vertIndices[2]), triMat.GetValue());\n\n            if (CollisionUtil::TriBoxOverlap(center, extent, surf.GetVert(0), surf.GetVert(1), surf.GetVert(2))) {\n              zeus::CVector3f surfNormal = surf.GetNormal();\n              if ((sphere.position + moveVec - surf.GetVert(0)).dot(surfNormal) <= sphere.radius) {\n                bool triRet = false;\n\n                float mag = (sphere.radius - (sphere.position - surf.GetVert(0)).dot(surfNormal)) / dir.dot(surfNormal);\n                zeus::CVector3f intersectPoint = sphere.position + mag * dir;\n\n                const std::array<bool, 3> outsideEdges{\n                    (intersectPoint - surf.GetVert(0)).dot(surfNormal.cross(surf.GetVert(1) - surf.GetVert(0))) < 0.f,\n                    (intersectPoint - surf.GetVert(1)).dot(surfNormal.cross(surf.GetVert(2) - surf.GetVert(1))) < 0.f,\n                    (intersectPoint - surf.GetVert(2)).dot(surfNormal.cross(surf.GetVert(0) - surf.GetVert(2))) < 0.f,\n                };\n\n                if (mag >= 0.f && !outsideEdges[0] && !outsideEdges[1] && !outsideEdges[2] && mag < dOut) {\n                  infoOut = CCollisionInfo(intersectPoint - sphere.radius * surfNormal, matList, triMat, surfNormal);\n                  dOut = mag;\n                  triRet = true;\n                  ret = true;\n                }\n\n                bool intersects = (sphere.position - surf.GetVert(0)).dot(surfNormal) <= sphere.radius;\n                std::array<bool, 3> testVert{true, true, true};\n                const u16* edgeIndices = node.GetOwner().GetTriangleEdgeIndices(triIdx);\n                for (int k = 0; k < 3; ++k) {\n                  if (intersects || outsideEdges[k]) {\n                    u16 edgeIdx = edgeIndices[k];\n                    if (g_DupPrimitiveCheckCount != g_DupEdgeList[edgeIdx]) {\n                      g_DupEdgeList[edgeIdx] = g_DupPrimitiveCheckCount;\n                      CMaterialList edgeMat(node.GetOwner().GetEdgeMaterial(edgeIdx));\n                      if (!edgeMat.HasMaterial(EMaterialTypes::NoEdgeCollision)) {\n                        int nextIdx = (k + 1) % 3;\n                        zeus::CVector3f edgeVec = surf.GetVert(nextIdx) - surf.GetVert(k);\n                        float edgeVecMag = edgeVec.magnitude();\n                        edgeVec *= zeus::CVector3f(1.f / edgeVecMag);\n                        float dirDotEdge = dir.dot(edgeVec);\n                        zeus::CVector3f edgeRej = dir - dirDotEdge * edgeVec;\n                        float edgeRejMagSq = edgeRej.magSquared();\n                        zeus::CVector3f vertToSphere = sphere.position - surf.GetVert(k);\n                        float vtsDotEdge = vertToSphere.dot(edgeVec);\n                        zeus::CVector3f vtsRej = vertToSphere - vtsDotEdge * edgeVec;\n                        if (edgeRejMagSq > 0.f) {\n                          float tmp = 2.f * vtsRej.dot(edgeRej);\n                          float tmp2 =\n                              4.f * edgeRejMagSq * (vtsRej.magSquared() - sphere.radius * sphere.radius) - tmp * tmp;\n                          if (tmp2 >= 0.f) {\n                            float mag = 0.5f / edgeRejMagSq * (-tmp - std::sqrt(tmp2));\n                            if (mag >= 0.f) {\n                              float t = mag * dirDotEdge + vtsDotEdge;\n                              if (t >= 0.f && t <= edgeVecMag && mag < dOut) {\n                                zeus::CVector3f point = surf.GetVert(k) + t * edgeVec;\n                                infoOut = CCollisionInfo(point, matList, edgeMat,\n                                                         (sphere.position + mag * dir - point).normalized());\n                                dOut = mag;\n                                triRet = true;\n                                ret = true;\n                                testVert[k] = false;\n                                testVert[nextIdx] = false;\n                              } else if (t < -sphere.radius && dirDotEdge <= 0.f) {\n                                testVert[k] = false;\n                              } else if (t > edgeVecMag + sphere.radius && dirDotEdge >= 0.0) {\n                                testVert[nextIdx] = false;\n                              }\n                            }\n                          } else {\n                            testVert[k] = false;\n                            testVert[nextIdx] = false;\n                          }\n                        }\n                      }\n                    }\n                  }\n                }\n\n                for (int k = 0; k < 3; ++k) {\n                  u16 vertIdx = vertIndices[k];\n                  if (testVert[k]) {\n                    if (g_DupPrimitiveCheckCount != g_DupVertexList[vertIdx]) {\n                      g_DupVertexList[vertIdx] = g_DupPrimitiveCheckCount;\n                      double d = dOut;\n                      if (CollisionUtil::RaySphereIntersection_Double(zeus::CSphere(surf.GetVert(k), sphere.radius),\n                                                                      sphere.position, dir, d) &&\n                          d >= 0.0) {\n                        infoOut = CCollisionInfo(surf.GetVert(k), matList, node.GetOwner().GetVertMaterial(vertIdx),\n                                                 (sphere.position + dir * d - surf.GetVert(k)).normalized());\n                        dOut = d;\n                        triRet = true;\n                        ret = true;\n                      }\n                    }\n                  } else {\n                    g_DupVertexList[vertIdx] = g_DupPrimitiveCheckCount;\n                  }\n                }\n\n                if (triRet) {\n                  moveVec = float(dOut) * dir;\n                  movedAABB = aabb;\n                  movedAABB.accumulateBounds(aabb.min + moveVec);\n                  movedAABB.accumulateBounds(aabb.max + moveVec);\n                  center = movedAABB.center();\n                  extent = movedAABB.extents();\n                }\n              }\n            } else {\n              const u16* edgeIndices = node.GetOwner().GetTriangleEdgeIndices(triIdx);\n              g_DupEdgeList[edgeIndices[0]] = g_DupPrimitiveCheckCount;\n              g_DupEdgeList[edgeIndices[1]] = g_DupPrimitiveCheckCount;\n              g_DupEdgeList[edgeIndices[2]] = g_DupPrimitiveCheckCount;\n              g_DupVertexList[vertIndices[0]] = g_DupPrimitiveCheckCount;\n              g_DupVertexList[vertIndices[1]] = g_DupPrimitiveCheckCount;\n              g_DupVertexList[vertIndices[2]] = g_DupPrimitiveCheckCount;\n            }\n          }\n        }\n      }\n    }\n  }\n\n  return ret;\n}\n\nvoid CMetroidAreaCollider::ResetInternalCounters() {\n  g_CalledClip = 0;\n  g_RejectedByClip = 0;\n  g_TrianglesProcessed = 0;\n  g_DupTrianglesProcessed = 0;\n  if (g_DupPrimitiveCheckCount == 0xffff) {\n    g_DupVertexList.fill(0);\n    g_DupEdgeList.fill(0);\n    g_DupTriangleList.fill(0);\n    g_DupPrimitiveCheckCount += 1;\n  }\n  g_DupPrimitiveCheckCount += 1;\n}\n\nvoid CAreaCollisionCache::ClearCache() {\n  x18_leafCaches.clear();\n  x1b40_24_leafOverflow = false;\n  x1b40_25_cacheOverflow = false;\n}\n\nvoid CAreaCollisionCache::AddOctreeLeafCache(const CMetroidAreaCollider::COctreeLeafCache& leafCache) {\n  if (!leafCache.GetNumLeaves())\n    return;\n\n  if (leafCache.HasCacheOverflowed())\n    x1b40_24_leafOverflow = true;\n\n  if (x18_leafCaches.size() < 3) {\n    x18_leafCaches.push_back(leafCache);\n  } else {\n    x1b40_24_leafOverflow = true;\n    x1b40_25_cacheOverflow = true;\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CMetroidAreaCollider.hpp",
    "content": "#pragma once\n\n#include <array>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Collision/CAreaOctTree.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CLineSeg.hpp>\n#include <zeus/CVector3d.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CCollisionInfo;\nclass CCollisionInfoList;\nclass CMaterialList;\n\nclass CAABoxAreaCache {\n  friend class CMetroidAreaCollider;\n  const zeus::CAABox& x0_aabb;\n  const std::array<zeus::CPlane, 6>& x4_planes;\n  const CMaterialFilter& x8_filter;\n  const CMaterialList& xc_material;\n  CCollisionInfoList& x10_collisionList;\n  zeus::CVector3f x14_center;\n  zeus::CVector3f x20_halfExtent;\n\npublic:\n  CAABoxAreaCache(const zeus::CAABox& aabb, const std::array<zeus::CPlane, 6>& pl, const CMaterialFilter& filter,\n                  const CMaterialList& material, CCollisionInfoList& collisionList);\n};\n\nclass CBooleanAABoxAreaCache {\n  friend class CMetroidAreaCollider;\n  const zeus::CAABox& x0_aabb;\n  const CMaterialFilter& x4_filter;\n  zeus::CVector3f x8_center;\n  zeus::CVector3f x14_halfExtent;\n\npublic:\n  CBooleanAABoxAreaCache(const zeus::CAABox& aabb, const CMaterialFilter& filter);\n};\n\nclass CSphereAreaCache {\n  friend class CMetroidAreaCollider;\n  const zeus::CAABox& x0_aabb;\n  const zeus::CSphere& x4_sphere;\n  const CMaterialFilter& x8_filter;\n  const CMaterialList& xc_material;\n  CCollisionInfoList& x10_collisionList;\n\npublic:\n  CSphereAreaCache(const zeus::CAABox& aabb, const zeus::CSphere& sphere, const CMaterialFilter& filter,\n                   const CMaterialList& material, CCollisionInfoList& collisionList);\n};\n\nclass CBooleanSphereAreaCache {\n  friend class CMetroidAreaCollider;\n  const zeus::CAABox& x0_aabb;\n  const zeus::CSphere& x4_sphere;\n  const CMaterialFilter& x8_filter;\n\npublic:\n  CBooleanSphereAreaCache(const zeus::CAABox& aabb, const zeus::CSphere& sphere, const CMaterialFilter& filter);\n};\n\nstruct SBoxEdge {\n  zeus::CLineSeg x0_seg;\n  zeus::CVector3d x28_start;\n  zeus::CVector3d x40_end;\n  zeus::CVector3d x58_delta;\n  zeus::CVector3d x70_coDir;\n  double x88_dirCoDirDot;\n  SBoxEdge(const zeus::CAABox& aabb, int idx, const zeus::CVector3f& dir);\n};\n\nclass CMovingAABoxComponents {\n  friend class CMetroidAreaCollider;\n  friend class CCollidableOBBTree;\n  rstl::reserved_vector<SBoxEdge, 12> x0_edges;\n  rstl::reserved_vector<u32, 8> x6c4_vertIdxs;\n  zeus::CAABox x6e8_aabb;\n\npublic:\n  CMovingAABoxComponents(const zeus::CAABox& aabb, const zeus::CVector3f& dir);\n};\n\nclass CMetroidAreaCollider {\n  friend class CCollidableOBBTree;\n  static u32 g_CalledClip;\n  static u32 g_RejectedByClip;\n  static u32 g_TrianglesProcessed;\n  static u32 g_DupTrianglesProcessed;\n  static u16 g_DupPrimitiveCheckCount;\n  static std::array<u16, 0x2800> g_DupVertexList;\n  static std::array<u16, 0x6000> g_DupEdgeList;\n  static std::array<u16, 0x4000> g_DupTriangleList;\n  static bool AABoxCollisionCheckBoolean_Internal(const CAreaOctTree::Node& node, const CBooleanAABoxAreaCache& cache);\n  static bool AABoxCollisionCheck_Internal(const CAreaOctTree::Node& node, const CAABoxAreaCache& cache);\n\n  static bool SphereCollisionCheckBoolean_Internal(const CAreaOctTree::Node& node,\n                                                   const CBooleanSphereAreaCache& cache);\n  static bool SphereCollisionCheck_Internal(const CAreaOctTree::Node& node, const CSphereAreaCache& cache);\n\n  static bool MovingAABoxCollisionCheck_BoxVertexTri(const CCollisionSurface& surf, const zeus::CAABox& aabb,\n                                                     const rstl::reserved_vector<u32, 8>& vertIndices,\n                                                     const zeus::CVector3f& dir, double& d, zeus::CVector3f& normal,\n                                                     zeus::CVector3f& point);\n  static bool MovingAABoxCollisionCheck_TriVertexBox(const zeus::CVector3f& vert, const zeus::CAABox& aabb,\n                                                     const zeus::CVector3f& dir, double& d, zeus::CVector3f& normal,\n                                                     zeus::CVector3f& point);\n  static bool MovingAABoxCollisionCheck_Edge(const zeus::CVector3f& ev0, const zeus::CVector3f& ev1,\n                                             const rstl::reserved_vector<SBoxEdge, 12>& edges,\n                                             const zeus::CVector3f& dir, double& d, zeus::CVector3f& normal,\n                                             zeus::CVector3f& point);\n\npublic:\n  class COctreeLeafCache {\n    friend class CMetroidAreaCollider;\n    const CAreaOctTree& x0_octTree;\n    rstl::reserved_vector<CAreaOctTree::Node, 64> x4_nodeCache;\n    bool x908_24_overflow : 1 = false;\n\n  public:\n    explicit COctreeLeafCache(const CAreaOctTree& octTree);\n    void AddLeaf(const CAreaOctTree::Node& node);\n    u32 GetNumLeaves() const { return x4_nodeCache.size(); }\n    bool HasCacheOverflowed() const { return x908_24_overflow; }\n    const CAreaOctTree& GetOctTree() const { return x0_octTree; }\n    rstl::reserved_vector<CAreaOctTree::Node, 64>::const_iterator begin() const { return x4_nodeCache.begin(); }\n    rstl::reserved_vector<CAreaOctTree::Node, 64>::const_iterator end() const { return x4_nodeCache.end(); }\n  };\n  static void BuildOctreeLeafCache(const CAreaOctTree::Node& root, const zeus::CAABox& aabb,\n                                   CMetroidAreaCollider::COctreeLeafCache& cache);\n  static bool ConvexPolyCollision(const std::array<zeus::CPlane, 6>& planes,\n                                  const std::array<zeus::CVector3f, 3>& verts, zeus::CAABox& aabb);\n\n  static bool AABoxCollisionCheckBoolean_Cached(const COctreeLeafCache& leafCache, const zeus::CAABox& aabb,\n                                                const CMaterialFilter& filter);\n  static bool AABoxCollisionCheckBoolean(const CAreaOctTree& octTree, const zeus::CAABox& aabb,\n                                         const CMaterialFilter& filter);\n\n  static bool SphereCollisionCheckBoolean_Cached(const COctreeLeafCache& leafCache, const zeus::CAABox& aabb,\n                                                 const zeus::CSphere& sphere, const CMaterialFilter& filter);\n  static bool SphereCollisionCheckBoolean(const CAreaOctTree& octTree, const zeus::CAABox& aabb,\n                                          const zeus::CSphere& sphere, const CMaterialFilter& filter);\n\n  static bool AABoxCollisionCheck_Cached(const COctreeLeafCache& leafCache, const zeus::CAABox& aabb,\n                                         const CMaterialFilter& filter, const CMaterialList& matList,\n                                         CCollisionInfoList& list);\n  static bool AABoxCollisionCheck(const CAreaOctTree& octTree, const zeus::CAABox& aabb, const CMaterialFilter& filter,\n                                  const CMaterialList& matList, CCollisionInfoList& list);\n\n  static bool SphereCollisionCheck_Cached(const COctreeLeafCache& leafCache, const zeus::CAABox& aabb,\n                                          const zeus::CSphere& sphere, const CMaterialList& matList,\n                                          const CMaterialFilter& filter, CCollisionInfoList& list);\n  static bool SphereCollisionCheck(const CAreaOctTree& octTree, const zeus::CAABox& aabb, const zeus::CSphere& sphere,\n                                   const CMaterialList& matList, const CMaterialFilter& filter,\n                                   CCollisionInfoList& list);\n\n  static bool MovingAABoxCollisionCheck_Cached(const COctreeLeafCache& leafCache, const zeus::CAABox& aabb,\n                                               const CMaterialFilter& filter, const CMaterialList& matList,\n                                               const zeus::CVector3f& dir, float mag, CCollisionInfo& infoOut,\n                                               double& dOut);\n  static bool MovingSphereCollisionCheck_Cached(const COctreeLeafCache& leafCache, const zeus::CAABox& aabb,\n                                                const zeus::CSphere& sphere, const CMaterialFilter& filter,\n                                                const CMaterialList& matList, const zeus::CVector3f& dir, float mag,\n                                                CCollisionInfo& infoOut, double& dOut);\n  static void ResetInternalCounters();\n  static std::array<u16, 0x4000>& GetTriangleList() { return g_DupTriangleList; }\n  static u16 GetPrimitiveCheckCount() { return g_DupPrimitiveCheckCount; }\n};\n\nclass CAreaCollisionCache {\n  zeus::CAABox x0_aabb;\n  rstl::reserved_vector<CMetroidAreaCollider::COctreeLeafCache, 3> x18_leafCaches;\n  bool x1b40_24_leafOverflow : 1 = false;\n  bool x1b40_25_cacheOverflow : 1 = false;\n\npublic:\n  explicit CAreaCollisionCache(const zeus::CAABox& aabb) : x0_aabb(aabb) {}\n  void ClearCache();\n  const zeus::CAABox& GetCacheBounds() const { return x0_aabb; }\n  void SetCacheBounds(const zeus::CAABox& aabb) { x0_aabb = aabb; }\n  void AddOctreeLeafCache(const CMetroidAreaCollider::COctreeLeafCache& leafCache);\n  u32 GetNumCaches() const { return x18_leafCaches.size(); }\n  const CMetroidAreaCollider::COctreeLeafCache& GetOctreeLeafCache(int idx) { return x18_leafCaches[idx]; }\n  bool HasCacheOverflowed() const { return x1b40_24_leafOverflow; }\n  rstl::reserved_vector<CMetroidAreaCollider::COctreeLeafCache, 3>::const_iterator begin() const {\n    return x18_leafCaches.begin();\n  }\n  rstl::reserved_vector<CMetroidAreaCollider::COctreeLeafCache, 3>::const_iterator end() const {\n    return x18_leafCaches.end();\n  }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/COBBTree.cpp",
    "content": "#include \"Runtime/Collision/COBBTree.hpp\"\n\n#include <array>\n\n#include \"Runtime/Collision/CCollidableOBBTreeGroup.hpp\"\n\nnamespace metaforce {\nnamespace {\nconstexpr std::array<u8, 18> DefaultEdgeMaterials{\n    2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 2, 0, 0, 2, 2,\n};\n\nconstexpr std::array<u8, 12> DefaultSurfaceMaterials{\n    0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,\n};\n\nconstexpr std::array<CCollisionEdge, 18> DefaultEdges{{\n    {4, 1},\n    {1, 5},\n    {5, 4},\n    {4, 0},\n    {0, 1},\n    {7, 2},\n    {2, 6},\n    {6, 7},\n    {7, 3},\n    {3, 2},\n    {6, 0},\n    {4, 6},\n    {2, 0},\n    {5, 3},\n    {7, 5},\n    {1, 3},\n    {6, 5},\n    {0, 3},\n}};\n\nconstexpr std::array<u16, 36> DefaultSurfaceIndices{\n    0,  1, 2,  0,  3, 4,  5,  6,  7, 5,  8,  9, 10, 3,  11, 10, 6,  12,\n    13, 8, 14, 13, 1, 15, 16, 14, 7, 16, 11, 2, 17, 15, 4,  17, 12, 9,\n};\n\n/* This is exactly what retro did >.< */\nu32 verify_deaf_babe(CInputStream& in) { return in.ReadLong(); }\n\n/* This is exactly what retro did >.< */\nu32 verify_version(CInputStream& in) { return in.ReadLong(); }\n} // Anonymous namespace\n\nCOBBTree::COBBTree(CInputStream& in)\n: x0_magic(verify_deaf_babe(in))\n, x4_version(verify_version(in))\n, x8_memsize(in.ReadLong())\n, x18_indexData(in)\n, x88_root(std::make_unique<CNode>(in)) {}\n\nstd::unique_ptr<COBBTree> COBBTree::BuildOrientedBoundingBoxTree(const zeus::CVector3f& extent,\n                                                                 const zeus::CVector3f& center) {\n  zeus::CAABox aabb(extent * -0.5f + center, extent * 0.5f + center);\n  std::unique_ptr<COBBTree> ret = std::make_unique<COBBTree>();\n  COBBTree::SIndexData& idxData = ret->x18_indexData;\n  idxData.x0_materials.reserve(3);\n  idxData.x0_materials.push_back(0x40180000);\n  idxData.x0_materials.push_back(0x42180000);\n  idxData.x0_materials.push_back(0x41180000);\n  idxData.x10_vertMaterials = std::vector<u8>(8, u8(0));\n  idxData.x20_edgeMaterials = std::vector<u8>(DefaultEdgeMaterials.cbegin(), DefaultEdgeMaterials.cend());\n  idxData.x30_surfaceMaterials = std::vector<u8>(DefaultSurfaceMaterials.cbegin(), DefaultSurfaceMaterials.cend());\n  idxData.x40_edges = std::vector<CCollisionEdge>(DefaultEdges.cbegin(), DefaultEdges.cend());\n  idxData.x50_surfaceIndices = std::vector<u16>(DefaultSurfaceIndices.cbegin(), DefaultSurfaceIndices.cend());\n  idxData.x60_vertices.reserve(8);\n  for (int i = 0; i < 8; ++i) {\n    idxData.x60_vertices.push_back(aabb.getPoint(i));\n  }\n  std::vector<u16> surface;\n  surface.reserve(12);\n  for (int i = 0; i < 12; ++i) {\n    surface.push_back(i);\n  }\n  ret->x88_root = std::make_unique<CNode>(zeus::CTransform::Translate(center), extent * 0.5f, nullptr, nullptr,\n                                          std::make_unique<CLeafData>(std::move(surface)));\n  return ret;\n}\n\nCCollisionSurface COBBTree::GetSurface(u16 idx) const {\n  const auto surfIdx = size_t{idx} * 3;\n  const CCollisionEdge e0 = x18_indexData.x40_edges[x18_indexData.x50_surfaceIndices[surfIdx]];\n  const CCollisionEdge e1 = x18_indexData.x40_edges[x18_indexData.x50_surfaceIndices[surfIdx + 1]];\n  const u16 vert1 = e0.GetVertIndex1();\n  const u16 vert2 = e0.GetVertIndex2();\n  u16 vert3 = e1.GetVertIndex1();\n\n  if (vert3 == vert1 || vert3 == vert2) {\n    vert3 = e1.GetVertIndex2();\n  }\n\n  const u32 mat = x18_indexData.x0_materials[x18_indexData.x30_surfaceMaterials[idx]];\n\n  if ((mat & 0x2000000) != 0) {\n    return CCollisionSurface(x18_indexData.x60_vertices[vert2], x18_indexData.x60_vertices[vert1],\n                             x18_indexData.x60_vertices[vert3], mat);\n  }\n  return CCollisionSurface(x18_indexData.x60_vertices[vert1], x18_indexData.x60_vertices[vert2],\n                           x18_indexData.x60_vertices[vert3], mat);\n}\n\nstd::array<u16, 3> COBBTree::GetTriangleVertexIndices(u16 idx) const {\n  const auto surfIdx = size_t{idx} * 3;\n  const CCollisionEdge& e0 = x18_indexData.x40_edges[x18_indexData.x50_surfaceIndices[surfIdx]];\n  const CCollisionEdge& e1 = x18_indexData.x40_edges[x18_indexData.x50_surfaceIndices[surfIdx + 1]];\n  std::array<u16, 3> indices{};\n\n  indices[2] = (e1.GetVertIndex1() != e0.GetVertIndex1() && e1.GetVertIndex1() != e0.GetVertIndex2())\n                   ? e1.GetVertIndex1()\n                   : e1.GetVertIndex2();\n\n  const u32 material = x18_indexData.x0_materials[x18_indexData.x30_surfaceMaterials[idx]];\n  if ((material & 0x2000000) != 0) {\n    indices[0] = e0.GetVertIndex2();\n    indices[1] = e0.GetVertIndex1();\n  } else {\n    indices[0] = e0.GetVertIndex1();\n    indices[1] = e0.GetVertIndex2();\n  }\n\n  return indices;\n}\n\nCCollisionSurface COBBTree::GetTransformedSurface(u16 idx, const zeus::CTransform& xf) const {\n  const auto surfIdx = size_t{idx} * 3;\n  const CCollisionEdge e0 = x18_indexData.x40_edges[x18_indexData.x50_surfaceIndices[surfIdx]];\n  const CCollisionEdge e1 = x18_indexData.x40_edges[x18_indexData.x50_surfaceIndices[surfIdx + 1]];\n  const u16 vert1 = e0.GetVertIndex1();\n  const u16 vert2 = e0.GetVertIndex2();\n  u16 vert3 = e1.GetVertIndex1();\n\n  if (vert3 == vert1 || vert3 == vert2) {\n    vert3 = e1.GetVertIndex2();\n  }\n\n  const u32 mat = x18_indexData.x0_materials[x18_indexData.x30_surfaceMaterials[idx]];\n\n  if ((mat & 0x2000000) != 0) {\n    return CCollisionSurface(xf * x18_indexData.x60_vertices[vert2], xf * x18_indexData.x60_vertices[vert1],\n                             xf * x18_indexData.x60_vertices[vert3], mat);\n  }\n  return CCollisionSurface(xf * x18_indexData.x60_vertices[vert1], xf * x18_indexData.x60_vertices[vert2],\n                           xf * x18_indexData.x60_vertices[vert3], mat);\n}\n\nzeus::CAABox COBBTree::CalculateLocalAABox() const { return CalculateAABox(zeus::CTransform()); }\n\nzeus::CAABox COBBTree::CalculateAABox(const zeus::CTransform& xf) const {\n  if (x88_root) {\n    return x88_root->GetOBB().calculateAABox(xf);\n  }\n  return zeus::CAABox();\n}\n\nCOBBTree::SIndexData::SIndexData(CInputStream& in) {\n  u32 count = in.ReadLong();\n  x0_materials.reserve(count);\n  for (u32 i = 0; i < count; i++) {\n    x0_materials.emplace_back(in.ReadLong());\n  }\n\n  count = in.ReadLong();\n  for (u32 i = 0; i < count; i++) {\n    x10_vertMaterials.emplace_back(in.ReadUint8());\n  }\n  count = in.ReadLong();\n  for (u32 i = 0; i < count; i++) {\n    x20_edgeMaterials.emplace_back(in.ReadUint8());\n  }\n  count = in.ReadLong();\n  for (u32 i = 0; i < count; i++) {\n    x30_surfaceMaterials.emplace_back(in.ReadUint8());\n  }\n\n  count = in.ReadLong();\n  for (u32 i = 0; i < count; i++) {\n    x40_edges.emplace_back(in);\n  }\n\n  count = in.ReadLong();\n  for (u32 i = 0; i < count; i++) {\n    x50_surfaceIndices.emplace_back(in.ReadShort());\n  }\n\n  count = in.ReadLong();\n  for (u32 i = 0; i < count; i++) {\n    x60_vertices.emplace_back(in.Get<zeus::CVector3f>());\n  }\n}\n\nCOBBTree::CNode::CNode(const zeus::CTransform& xf, const zeus::CVector3f& point, std::unique_ptr<CNode>&& left,\n                       std::unique_ptr<CNode>&& right, std::unique_ptr<CLeafData>&& leaf)\n: x0_obb(xf, point)\n, x3c_isLeaf(leaf != nullptr)\n, x40_left(std::move(left))\n, x44_right(std::move(right))\n, x48_leaf(std::move(leaf)) {}\n\nCOBBTree::CNode::CNode(CInputStream& in) {\n  x0_obb = in.Get<zeus::COBBox>();\n  x3c_isLeaf = in.ReadBool();\n  if (x3c_isLeaf)\n    x48_leaf = std::make_unique<CLeafData>(in);\n  else {\n    x40_left = std::make_unique<CNode>(in);\n    x44_right = std::make_unique<CNode>(in);\n  }\n}\n\nsize_t COBBTree::CNode::GetMemoryUsage() const {\n  size_t ret = 0;\n  if (x3c_isLeaf)\n    ret = x48_leaf->GetMemoryUsage() + /*sizeof(CNode)*/ 80;\n  else {\n    if (x40_left)\n      ret = x40_left->GetMemoryUsage() + /*sizeof(CNode)*/ 80;\n    if (x44_right)\n      ret += x44_right->GetMemoryUsage();\n  }\n\n  return (ret + 3) & ~3;\n}\n\nCOBBTree::CLeafData::CLeafData(std::vector<u16>&& surface) : x0_surface(std::move(surface)) {}\n\nconst std::vector<u16>& COBBTree::CLeafData::GetSurfaceVector() const { return x0_surface; }\n\nsize_t COBBTree::CLeafData::GetMemoryUsage() const {\n  const size_t ret = (x0_surface.size() * 2) + /*sizeof(CLeafData)*/ 16;\n  return (ret + 3) & ~3;\n}\n\nCOBBTree::CLeafData::CLeafData(CInputStream& in) {\n  const u32 edgeCount = in.ReadLong();\n  for (u32 i = 0; i < edgeCount; i++) {\n    x0_surface.emplace_back(in.ReadShort());\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/COBBTree.hpp",
    "content": "#pragma once\n\n#include <array>\n#include <memory>\n#include <vector>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Collision/CCollisionEdge.hpp\"\n#include \"Runtime/Collision/CCollisionSurface.hpp\"\n\n#include <zeus/COBBox.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CCollidableOBBTreeGroupContainer;\n\nclass COBBTree {\npublic:\n  struct SIndexData {\n    std::vector<u32> x0_materials;\n    std::vector<u8> x10_vertMaterials;\n    std::vector<u8> x20_edgeMaterials;\n    std::vector<u8> x30_surfaceMaterials;\n    std::vector<CCollisionEdge> x40_edges;\n    std::vector<u16> x50_surfaceIndices;\n    std::vector<zeus::CVector3f> x60_vertices;\n    SIndexData() = default;\n    explicit SIndexData(CInputStream&);\n  };\n\n  class CLeafData {\n    std::vector<u16> x0_surface;\n\n  public:\n    CLeafData() = default;\n    explicit CLeafData(std::vector<u16>&& surface);\n    explicit CLeafData(CInputStream&);\n\n    const std::vector<u16>& GetSurfaceVector() const;\n    size_t GetMemoryUsage() const;\n  };\n\n  class CNode {\n    zeus::COBBox x0_obb;\n    bool x3c_isLeaf = false;\n    std::unique_ptr<CNode> x40_left;\n    std::unique_ptr<CNode> x44_right;\n    std::unique_ptr<CLeafData> x48_leaf;\n    bool x4c_hit = false;\n\n  public:\n    CNode() = default;\n    CNode(const zeus::CTransform&, const zeus::CVector3f&, std::unique_ptr<CNode>&&, std::unique_ptr<CNode>&&,\n          std::unique_ptr<CLeafData>&&);\n    explicit CNode(CInputStream&);\n\n    bool WasHit() const { return x4c_hit; }\n    void SetHit(bool h) { x4c_hit = h; }\n    const CNode& GetLeft() const { return *x40_left; }\n    const CNode& GetRight() const { return *x44_right; }\n    const CLeafData& GetLeafData() const { return *x48_leaf; }\n    const zeus::COBBox& GetOBB() const { return x0_obb; }\n    size_t GetMemoryUsage() const;\n    bool IsLeaf() const { return x3c_isLeaf; }\n  };\n\nprivate:\n  u32 x0_magic = 0;\n  u32 x4_version = 0;\n  u32 x8_memsize = 0;\n  /* CSimpleAllocator xc_ We're not using this but lets keep track*/\n  SIndexData x18_indexData;\n  std::unique_ptr<CNode> x88_root;\n\npublic:\n  COBBTree() = default;\n  explicit COBBTree(CInputStream&);\n\n  static std::unique_ptr<COBBTree> BuildOrientedBoundingBoxTree(const zeus::CVector3f&, const zeus::CVector3f&);\n  CCollisionSurface GetSurface(u16 idx) const;\n  const u16* GetTriangleEdgeIndices(u16 idx) const { return &x18_indexData.x50_surfaceIndices[idx * 3]; }\n\n  // In the game binary, this used to use an out pointer for the indices after the index.\n  std::array<u16, 3> GetTriangleVertexIndices(u16 idx) const;\n\n  const CCollisionEdge& GetEdge(int idx) const { return x18_indexData.x40_edges[idx]; }\n  const zeus::CVector3f& GetVert(int idx) const { return x18_indexData.x60_vertices[idx]; }\n  u32 GetVertMaterial(u16 idx) const { return x18_indexData.x0_materials[x18_indexData.x10_vertMaterials[idx]]; }\n  u32 GetEdgeMaterial(u16 idx) const { return x18_indexData.x0_materials[x18_indexData.x20_edgeMaterials[idx]]; }\n  CCollisionSurface GetTransformedSurface(u16 idx, const zeus::CTransform& xf) const;\n  zeus::CAABox CalculateLocalAABox() const;\n  zeus::CAABox CalculateAABox(const zeus::CTransform&) const;\n  const CNode& GetRoot() const { return *x88_root; }\n  u32 NumSurfaceMaterials() const { return x18_indexData.x30_surfaceMaterials.size(); }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CRayCastResult.cpp",
    "content": "#include \"Runtime/Collision/CRayCastResult.hpp\"\n\nnamespace metaforce {\n\nvoid CRayCastResult::MakeInvalid() {\n  /* NOTE: CRayCastResult: Enable this if it's required, this is a total guess - Phil */\n#if 0\n    x0_time = 0.f;\n    x4_point.zeroOut();\n    x10_plane.vec.zeroOut();;\n    x10_plane.d = 0.f;\n    x28_material = CMaterialList();\n#endif\n  x20_invalid = EInvalid::Invalid;\n}\n\nvoid CRayCastResult::Transform(const zeus::CTransform& xf) {\n  x4_point = xf * x4_point;\n  x10_plane = zeus::CPlane(xf.rotate(x10_plane.normal()), x10_plane.normal().dot(x4_point));\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CRayCastResult.hpp",
    "content": "#pragma once\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/Collision/CMaterialList.hpp\"\n\n#include <zeus/CPlane.hpp>\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CRayCastResult {\npublic:\n  enum class EInvalid : u8 { Invalid, Valid };\n\nprivate:\n  float x0_t = 0.f;\n  zeus::CVector3f x4_point;\n  zeus::CPlane x10_plane;\n  EInvalid x20_invalid = EInvalid::Invalid;\n  CMaterialList x28_material;\n\npublic:\n  CRayCastResult() = default;\n  CRayCastResult(const CRayCastResult& other, EInvalid invalid)\n  : x0_t(other.x0_t)\n  , x4_point(other.x4_point)\n  , x10_plane(other.x10_plane)\n  , x20_invalid(invalid)\n  , x28_material(other.x28_material) {}\n\n  CRayCastResult(float t, const zeus::CVector3f& point, const zeus::CPlane& plane, const CMaterialList& matList)\n  : x0_t(t), x4_point(point), x10_plane(plane), x20_invalid(EInvalid::Valid), x28_material(matList) {}\n\n  void MakeInvalid();\n\n  bool IsInvalid() const { return x20_invalid == EInvalid::Invalid; }\n  bool IsValid() const { return x20_invalid == EInvalid::Valid; }\n  float GetT() const { return x0_t; }\n  const zeus::CVector3f& GetPoint() const { return x4_point; }\n  const zeus::CPlane& GetPlane() const { return x10_plane; }\n  const CMaterialList& GetMaterial() const { return x28_material; }\n\n  void Transform(const zeus::CTransform&);\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/CollisionUtil.cpp",
    "content": "#include \"Runtime/Collision/CollisionUtil.hpp\"\n\n#include <algorithm>\n#include <array>\n#include <tuple>\n\n#include \"Runtime/Collision/CCollisionInfo.hpp\"\n#include \"Runtime/Collision/CCollisionInfoList.hpp\"\n\n#include <zeus/CVector3d.hpp>\n\nnamespace metaforce::CollisionUtil {\nbool LineIntersectsOBBox(const zeus::COBBox& obb, const zeus::CMRay& ray, float& d) {\n  zeus::CVector3f norm;\n  return RayAABoxIntersection(ray.getInvUnscaledTransformRay(obb.transform), {-obb.extents, obb.extents}, norm, d);\n}\n\nu32 RayAABoxIntersection(const zeus::CMRay& ray, const zeus::CAABox& aabb, float& tMin, float& tMax) {\n  tMin = -999999.f;\n  tMax = 999999.f;\n\n  for (size_t i = 0; i < 3; ++i) {\n    if (std::fabs(ray.dir[i]) < 0.00001f) {\n      if (ray.start[i] < aabb.min[i] || ray.start[i] > aabb.max[i]) {\n        return 0;\n      }\n    } else {\n      if (ray.dir[i] < 0.f) {\n        const float startToMax = aabb.max[i] - ray.start[i];\n        const float startToMin = aabb.min[i] - ray.start[i];\n        const float dirRecip = 1.f / ray.dir[i];\n        if (startToMax < tMin * ray.dir[i]) {\n          tMin = startToMax * dirRecip;\n        }\n        if (startToMin > tMax * ray.dir[i]) {\n          tMax = startToMin * dirRecip;\n        }\n      } else {\n        const float startToMin = aabb.min[i] - ray.start[i];\n        const float startToMax = aabb.max[i] - ray.start[i];\n        const float dirRecip = 1.f / ray.dir[i];\n        if (startToMin > tMin * ray.dir[i]) {\n          tMin = startToMin * dirRecip;\n        }\n        if (startToMax < tMax * ray.dir[i]) {\n          tMax = startToMax * dirRecip;\n        }\n      }\n    }\n  }\n\n  return tMin <= tMax ? 2 : 0;\n}\n\nu32 RayAABoxIntersection(const zeus::CMRay& ray, const zeus::CAABox& aabb, zeus::CVector3f& norm, float& d) {\n  std::array<int, 3> sign{2, 2, 2};\n  bool bad = true;\n  zeus::CVector3f rayStart = ray.start;\n  zeus::CVector3f rayDelta = ray.delta;\n  zeus::CVector3f aabbMin = aabb.min;\n  zeus::CVector3f aabbMax = aabb.max;\n\n  zeus::CVector3f vec0 = {-1.f, -1.f, -1.f};\n  zeus::CVector3f vec1;\n\n  if (rayDelta.x() != 0.f && rayDelta.y() != 0.f && rayDelta.z() != 0.f) {\n    for (size_t i = 0; i < sign.size(); ++i) {\n      if (rayStart[i] < aabbMin[i]) {\n        sign[i] = 1;\n        bad = false;\n        vec0[i] = (aabbMin[i] - rayStart[i]) / rayDelta[i];\n      } else if (rayStart[i] > aabbMax[i]) {\n        sign[i] = 0;\n        bad = false;\n        vec0[i] = (aabbMax[i] - rayStart[i]) / rayDelta[i];\n      }\n    }\n\n    if (bad) {\n      d = 0.f;\n      return 1;\n    }\n  } else {\n    zeus::CVector3f end;\n    for (size_t i = 0; i < sign.size(); ++i) {\n      if (rayStart[i] < aabbMin[i]) {\n        sign[i] = 1;\n        bad = false;\n        end[i] = float(aabbMin[i]);\n      } else if (rayStart[i] > aabbMax[i]) {\n        sign[i] = 0;\n        bad = false;\n        end[i] = float(aabbMax[i]);\n      }\n    }\n\n    if (bad) {\n      d = 0.f;\n      return 1;\n    }\n\n    for (size_t i = 0; i < sign.size(); ++i) {\n      if (sign[i] != 2 && rayDelta[i] != 0.f) {\n        vec0[i] = (end[i] - rayStart[i]) / rayDelta[i];\n      }\n    }\n  }\n\n  float maxComp = vec0.x();\n  size_t maxCompIdx = 0;\n  if (maxComp < vec0.y()) {\n    maxComp = vec0.y();\n    maxCompIdx = 1;\n  }\n  if (maxComp < vec0.z()) {\n    maxComp = vec0.z();\n    maxCompIdx = 2;\n  }\n\n  if (maxComp < 0.f || maxComp > 1.f)\n    return 0;\n\n  for (size_t i = 0; i < 3; ++i) {\n    if (maxCompIdx != i) {\n      vec1[i] = maxComp * rayDelta[i] + rayStart[i];\n      if (vec1[i] > aabbMax[i]) {\n        return 0;\n      }\n    }\n  }\n\n  d = maxComp;\n  norm = zeus::skZero3f;\n  norm[maxCompIdx] = (sign[maxCompIdx] == 1) ? -1.f : 1.f;\n  return 2;\n}\n\nu32 RayAABoxIntersection_Double(const zeus::CMRay& ray, const zeus::CAABox& aabb, zeus::CVector3f& norm, double& d) {\n  std::array<int, 3> sign{2, 2, 2};\n  bool bad = true;\n  zeus::CVector3d rayStart = ray.start;\n  zeus::CVector3d rayDelta = ray.delta;\n  zeus::CVector3d aabbMin = aabb.min;\n  zeus::CVector3d aabbMax = aabb.max;\n\n  zeus::CVector3d vec0 = {-1.0, -1.0, -1.0};\n  zeus::CVector3d vec1;\n\n  if (rayDelta.x() != 0.0 && rayDelta.y() != 0.0 && rayDelta.z() != 0.0) {\n    for (size_t i = 0; i < sign.size(); ++i) {\n      if (rayStart[i] < aabbMin[i]) {\n        sign[i] = 1;\n        bad = false;\n        vec0[i] = (aabbMin[i] - rayStart[i]) / rayDelta[i];\n      } else if (rayStart[i] > aabbMax[i]) {\n        sign[i] = 0;\n        bad = false;\n        vec0[i] = (aabbMax[i] - rayStart[i]) / rayDelta[i];\n      }\n    }\n\n    if (bad) {\n      d = 0.0;\n      return 1;\n    }\n  } else {\n    zeus::CVector3d end;\n    for (size_t i = 0; i < sign.size(); ++i) {\n      if (rayStart[i] < aabbMin[i]) {\n        sign[i] = 1;\n        bad = false;\n        end[i] = double(aabbMin[i]);\n      } else if (rayStart[i] > aabbMax[i]) {\n        sign[i] = 0;\n        bad = false;\n        end[i] = double(aabbMax[i]);\n      }\n    }\n\n    if (bad) {\n      d = 0.0;\n      return 1;\n    }\n\n    for (size_t i = 0; i < sign.size(); ++i) {\n      if (sign[i] != 2 && rayDelta[i] != 0.0) {\n        vec0[i] = (end[i] - rayStart[i]) / rayDelta[i];\n      }\n    }\n  }\n\n  double maxComp = vec0.x();\n  size_t maxCompIdx = 0;\n  if (maxComp < vec0.y()) {\n    maxComp = vec0.y();\n    maxCompIdx = 1;\n  }\n  if (maxComp < vec0.z()) {\n    maxComp = vec0.z();\n    maxCompIdx = 2;\n  }\n\n  if (maxComp < 0.0 || maxComp > 1.0)\n    return 0;\n\n  for (size_t i = 0; i < 3; ++i) {\n    if (maxCompIdx != i) {\n      vec1[i] = maxComp * rayDelta[i] + rayStart[i];\n      if (vec1[i] > aabbMax[i]) {\n        return 0;\n      }\n    }\n  }\n\n  d = maxComp;\n  norm = zeus::skZero3f;\n  norm[maxCompIdx] = (sign[maxCompIdx] == 1) ? -1.0 : 1.0;\n  return 2;\n}\n\nbool RaySphereIntersection_Double(const zeus::CSphere& sphere, const zeus::CVector3f& pos, const zeus::CVector3f& dir,\n                                  double& T) {\n  const zeus::CVector3d sPosD = sphere.position;\n  const zeus::CVector3d posD = pos;\n  const zeus::CVector3d sphereToPos = posD - sPosD;\n  const double f30 = sphereToPos.dot(zeus::CVector3d(dir)) * 2.0;\n  const double f1 = f30 * f30 - 4.0 * (sphereToPos.magSquared() - sphere.radius * sphere.radius);\n\n  if (f1 >= 0.0) {\n    const double intersectT = 0.5 * (-f30 - std::sqrt(f1));\n    if (T == 0 || intersectT < T) {\n      T = intersectT;\n      return true;\n    }\n  }\n\n  return false;\n}\n\nbool RaySphereIntersection(const zeus::CSphere& sphere, const zeus::CVector3f& pos, const zeus::CVector3f& dir,\n                           float mag, float& T, zeus::CVector3f& point) {\n  const zeus::CVector3f rayToSphere = sphere.position - pos;\n  const float magSq = rayToSphere.magSquared();\n  const float dirDot = rayToSphere.dot(dir);\n  const float radSq = sphere.radius * sphere.radius;\n\n  if (dirDot < 0.f && magSq > radSq) {\n    return false;\n  }\n\n  const float intersectSq = radSq - (magSq - dirDot * dirDot);\n  if (intersectSq < 0.f) {\n    return false;\n  }\n\n  T = magSq > radSq ? dirDot - std::sqrt(intersectSq) : dirDot + std::sqrt(intersectSq);\n  if (T < mag || mag == 0.f) {\n    point = pos + T * dir;\n    return true;\n  }\n\n  return false;\n}\n\nbool RayTriangleIntersection_Double(const zeus::CVector3f& point, const zeus::CVector3f& dir,\n                                    const std::array<zeus::CVector3f, 3>& verts, double& d) {\n  const zeus::CVector3d v0tov1 = verts[1] - verts[0];\n  const zeus::CVector3d v0tov2 = verts[2] - verts[0];\n  const zeus::CVector3d cross0 = zeus::CVector3d(dir).cross(v0tov2);\n  const double dot0 = v0tov1.dot(cross0);\n  if (dot0 < DBL_EPSILON) {\n    return false;\n  }\n\n  const zeus::CVector3d v0toPoint = point - verts[0];\n  const double dot1 = v0toPoint.dot(cross0);\n  if (dot1 < 0.0 || dot1 > dot0) {\n    return false;\n  }\n\n  const zeus::CVector3d cross1 = v0toPoint.cross(v0tov1);\n  const double dot2 = cross1.dot(dir);\n  if (dot2 < 0.0 || dot1 + dot2 > dot0) {\n    return false;\n  }\n\n  const double final = 1.0 / dot0 * cross1.dot(v0tov2);\n  if (final < 0.0 || final >= d) {\n    return false;\n  }\n\n  d = final;\n  return true;\n}\n\nbool RayTriangleIntersection(const zeus::CVector3f& point, const zeus::CVector3f& dir,\n                             const std::array<zeus::CVector3f, 3>& verts, float& d) {\n  const zeus::CVector3f v0tov1 = verts[1] - verts[0];\n  const zeus::CVector3f v0tov2 = verts[2] - verts[0];\n  const zeus::CVector3f cross0 = dir.cross(v0tov2);\n  const float dot0 = v0tov1.dot(cross0);\n  if (dot0 < DBL_EPSILON) {\n    return false;\n  }\n\n  const zeus::CVector3f v0toPoint = point - verts[0];\n  const float dot1 = v0toPoint.dot(cross0);\n  if (dot1 < 0.f || dot1 > dot0) {\n    return false;\n  }\n\n  const zeus::CVector3f cross1 = v0toPoint.cross(v0tov1);\n  const float dot2 = cross1.dot(dir);\n  if (dot2 < 0.f || dot1 + dot2 > dot0) {\n    return false;\n  }\n\n  const float final = 1.f / dot0 * cross1.dot(v0tov2);\n  if (final < 0.f || final >= d) {\n    return false;\n  }\n\n  d = final;\n  return true;\n}\n\nvoid FilterOutBackfaces(const zeus::CVector3f& vec, const CCollisionInfoList& in, CCollisionInfoList& out) {\n  if (vec.canBeNormalized()) {\n    const zeus::CVector3f norm = vec.normalized();\n    for (const CCollisionInfo& info : in) {\n      if (info.GetNormalLeft().dot(norm) < 0.001f) {\n        out.Add(info, false);\n      }\n    }\n  } else {\n    out = in;\n  }\n}\n\nvoid FilterByClosestNormal(const zeus::CVector3f& norm, const CCollisionInfoList& in, CCollisionInfoList& out) {\n  float maxDot = -1.1f;\n  int idx = -1;\n  int i = 0;\n  for (const CCollisionInfo& info : in) {\n    const float dot = info.GetNormalLeft().dot(norm);\n    if (dot > maxDot) {\n      maxDot = dot;\n      idx = i;\n    }\n    ++i;\n  }\n\n  if (idx != -1) {\n    out.Add(in.GetItem(idx), false);\n  }\n}\n\nconstexpr std::array<zeus::CVector3f, 6> AABBNormalTable{{\n    {-1.f, 0.f, 0.f},\n    {1.f, 0.f, 0.f},\n    {0.f, -1.f, 0.f},\n    {0.f, 1.f, 0.f},\n    {0.f, 0.f, -1.f},\n    {0.f, 0.f, 1.f},\n}};\n\nbool AABoxAABoxIntersection(const zeus::CAABox& aabb0, const CMaterialList& list0, const zeus::CAABox& aabb1,\n                            const CMaterialList& list1, CCollisionInfoList& infoList) {\n  const zeus::CVector3f maxOfMin(std::max(aabb0.min.x(), aabb1.min.x()), std::max(aabb0.min.y(), aabb1.min.y()),\n                                 std::max(aabb0.min.z(), aabb1.min.z()));\n  const zeus::CVector3f minOfMax(std::min(aabb0.max.x(), aabb1.max.x()), std::min(aabb0.max.y(), aabb1.max.y()),\n                                 std::min(aabb0.max.z(), aabb1.max.z()));\n\n  if (maxOfMin.x() >= minOfMax.x() || maxOfMin.y() >= minOfMax.y() || maxOfMin.z() >= minOfMax.z()) {\n    return false;\n  }\n\n  const zeus::CAABox boolAABB(maxOfMin, minOfMax);\n\n  const std::array<int, 3> ineqFlags{\n      (aabb0.min.x() <= aabb1.min.x() ? 1 << 0 : 0) | (aabb0.min.x() <= aabb1.max.x() ? 1 << 1 : 0) |\n          (aabb0.max.x() <= aabb1.min.x() ? 1 << 2 : 0) | (aabb0.max.x() <= aabb1.max.x() ? 1 << 3 : 0),\n      (aabb0.min.y() <= aabb1.min.y() ? 1 << 0 : 0) | (aabb0.min.y() <= aabb1.max.y() ? 1 << 1 : 0) |\n          (aabb0.max.y() <= aabb1.min.y() ? 1 << 2 : 0) | (aabb0.max.y() <= aabb1.max.y() ? 1 << 3 : 0),\n      (aabb0.min.z() <= aabb1.min.z() ? 1 << 0 : 0) | (aabb0.min.z() <= aabb1.max.z() ? 1 << 1 : 0) |\n          (aabb0.max.z() <= aabb1.min.z() ? 1 << 2 : 0) | (aabb0.max.z() <= aabb1.max.z() ? 1 << 3 : 0),\n  };\n\n  for (size_t i = 0; i < ineqFlags.size(); ++i) {\n    switch (ineqFlags[i]) {\n    case 0x2: // aabb0.min <= aabb1.max\n    {\n      const CCollisionInfo info(boolAABB, list0, list1, AABBNormalTable[i * 2 + 1], -AABBNormalTable[i * 2 + 1]);\n      infoList.Add(info, false);\n      break;\n    }\n    case 0xB: // aabb0.min <= aabb1.min && aabb0.max <= aabb1.min && aabb0.max <= aabb1.max\n    {\n      const CCollisionInfo info(boolAABB, list0, list1, AABBNormalTable[i * 2], -AABBNormalTable[i * 2]);\n      infoList.Add(info, false);\n      break;\n    }\n    default:\n      break;\n    }\n  }\n\n  if (infoList.GetCount() != 0) {\n    return true;\n  }\n\n  {\n    const CCollisionInfo info(boolAABB, list0, list1, AABBNormalTable[4], -AABBNormalTable[4]);\n    infoList.Add(info, false);\n  }\n\n  {\n    const CCollisionInfo info(boolAABB, list0, list1, AABBNormalTable[5], -AABBNormalTable[5]);\n    infoList.Add(info, false);\n  }\n\n  return true;\n}\n\nbool AABoxAABoxIntersection(const zeus::CAABox& aabb0, const zeus::CAABox& aabb1) { return aabb0.intersects(aabb1); }\n\n/* http://fileadmin.cs.lth.se/cs/Personal/Tomas_Akenine-Moller/code/tribox2.txt */\n/********************************************************/\n/* AABB-triangle overlap test code                      */\n/* by Tomas Akenine-Möller                              */\n/* Function: int triBoxOverlap(float boxcenter[3],      */\n/*          float boxhalfsize[3],float triverts[3][3]); */\n/* History:                                             */\n/*   2001-03-05: released the code in its first version */\n/*   2001-06-18: changed the order of the tests, faster */\n/*                                                      */\n/* Acknowledgement: Many thanks to Pierre Terdiman for  */\n/* suggestions and discussions on how to optimize code. */\n/* Thanks to David Hunt for finding a \">=\"-bug!         */\n/********************************************************/\n\nstatic bool planeBoxOverlap(const zeus::CVector3f& normal, float d, const zeus::CVector3f& maxbox) {\n  zeus::CVector3f vmin;\n  zeus::CVector3f vmax;\n\n  for (int q = 0; q <= 2; q++) {\n    if (normal[q] > 0.0f) {\n      vmin[q] = -maxbox[q];\n      vmax[q] = maxbox[q];\n    } else {\n      vmin[q] = maxbox[q];\n      vmax[q] = -maxbox[q];\n    }\n  }\n\n  if (normal.dot(vmin) + d > 0.0f) {\n    return false;\n  }\n\n  if (normal.dot(vmax) + d >= 0.0f) {\n    return true;\n  }\n\n  return false;\n}\n\n/*======================== X-tests ========================*/\n#define AXISTEST_X01(a, b, fa, fb)                                                                                     \\\n  do {                                                                                                                 \\\n    p0 = a * v0.y() - b * v0.z();                                                                                      \\\n    p2 = a * v2.y() - b * v2.z();                                                                                      \\\n    if (p0 < p2) {                                                                                                     \\\n      min = p0;                                                                                                        \\\n      max = p2;                                                                                                        \\\n    } else {                                                                                                           \\\n      min = p2;                                                                                                        \\\n      max = p0;                                                                                                        \\\n    }                                                                                                                  \\\n    rad = fa * boxhalfsize.y() + fb * boxhalfsize.z();                                                                 \\\n    if (min > rad || max < -rad)                                                                                       \\\n      return false;                                                                                                    \\\n  } while (false)\n\n#define AXISTEST_X2(a, b, fa, fb)                                                                                      \\\n  do {                                                                                                                 \\\n    p0 = a * v0.y() - b * v0.z();                                                                                      \\\n    p1 = a * v1.y() - b * v1.z();                                                                                      \\\n    if (p0 < p1) {                                                                                                     \\\n      min = p0;                                                                                                        \\\n      max = p1;                                                                                                        \\\n    } else {                                                                                                           \\\n      min = p1;                                                                                                        \\\n      max = p0;                                                                                                        \\\n    }                                                                                                                  \\\n    rad = fa * boxhalfsize.y() + fb * boxhalfsize.z();                                                                 \\\n    if (min > rad || max < -rad)                                                                                       \\\n      return false;                                                                                                    \\\n  } while (false)\n\n/*======================== Y-tests ========================*/\n#define AXISTEST_Y02(a, b, fa, fb)                                                                                     \\\n  do {                                                                                                                 \\\n    p0 = -a * v0.x() + b * v0.z();                                                                                     \\\n    p2 = -a * v2.x() + b * v2.z();                                                                                     \\\n    if (p0 < p2) {                                                                                                     \\\n      min = p0;                                                                                                        \\\n      max = p2;                                                                                                        \\\n    } else {                                                                                                           \\\n      min = p2;                                                                                                        \\\n      max = p0;                                                                                                        \\\n    }                                                                                                                  \\\n    rad = fa * boxhalfsize.x() + fb * boxhalfsize.z();                                                                 \\\n    if (min > rad || max < -rad)                                                                                       \\\n      return false;                                                                                                    \\\n  } while (false)\n\n#define AXISTEST_Y1(a, b, fa, fb)                                                                                      \\\n  do {                                                                                                                 \\\n    p0 = -a * v0.x() + b * v0.z();                                                                                     \\\n    p1 = -a * v1.x() + b * v1.z();                                                                                     \\\n    if (p0 < p1) {                                                                                                     \\\n      min = p0;                                                                                                        \\\n      max = p1;                                                                                                        \\\n    } else {                                                                                                           \\\n      min = p1;                                                                                                        \\\n      max = p0;                                                                                                        \\\n    }                                                                                                                  \\\n    rad = fa * boxhalfsize.x() + fb * boxhalfsize.z();                                                                 \\\n    if (min > rad || max < -rad)                                                                                       \\\n      return false;                                                                                                    \\\n  } while (false)\n\n/*======================== Z-tests ========================*/\n\n#define AXISTEST_Z12(a, b, fa, fb)                                                                                     \\\n  do {                                                                                                                 \\\n    p1 = a * v1.x() - b * v1.y();                                                                                      \\\n    p2 = a * v2.x() - b * v2.y();                                                                                      \\\n    if (p2 < p1) {                                                                                                     \\\n      min = p2;                                                                                                        \\\n      max = p1;                                                                                                        \\\n    } else {                                                                                                           \\\n      min = p1;                                                                                                        \\\n      max = p2;                                                                                                        \\\n    }                                                                                                                  \\\n    rad = fa * boxhalfsize.x() + fb * boxhalfsize.y();                                                                 \\\n    if (min > rad || max < -rad)                                                                                       \\\n      return false;                                                                                                    \\\n  } while (false)\n\n#define AXISTEST_Z0(a, b, fa, fb)                                                                                      \\\n  do {                                                                                                                 \\\n    p0 = a * v0.x() - b * v0.y();                                                                                      \\\n    p1 = a * v1.x() - b * v1.y();                                                                                      \\\n    if (p0 < p1) {                                                                                                     \\\n      min = p0;                                                                                                        \\\n      max = p1;                                                                                                        \\\n    } else {                                                                                                           \\\n      min = p1;                                                                                                        \\\n      max = p0;                                                                                                        \\\n    }                                                                                                                  \\\n    rad = fa * boxhalfsize.x() + fb * boxhalfsize.y();                                                                 \\\n    if (min > rad || max < -rad)                                                                                       \\\n      return false;                                                                                                    \\\n  } while (false)\n\nbool TriBoxOverlap(const zeus::CVector3f& boxcenter, const zeus::CVector3f& boxhalfsize,\n                   const zeus::CVector3f& trivert0, const zeus::CVector3f& trivert1, const zeus::CVector3f& trivert2) {\n\n  /*    use separating axis theorem to test overlap between triangle and box */\n  /*    need to test for overlap in these directions: */\n  /*    1) the {x,y,z}-directions (actually, since we use the AABB of the triangle */\n  /*       we do not even need to test these) */\n  /*    2) normal of the triangle */\n  /*    3) crossproduct(edge from tri, {x,y,z}-directin) */\n  /*       this gives 3x3=9 more tests */\n  float min, max, d, p0, p1, p2, rad, fex, fey, fez;\n\n  /* This is the fastest branch on Sun */\n  /* move everything so that the boxcenter is in (0,0,0) */\n  const zeus::CVector3f v0 = trivert0 - boxcenter;\n  const zeus::CVector3f v1 = trivert1 - boxcenter;\n  const zeus::CVector3f v2 = trivert2 - boxcenter;\n\n  /* compute triangle edges */\n  const zeus::CVector3f e0 = v1 - v0; // Tri edge 0\n  const zeus::CVector3f e1 = v2 - v1; // Tri edge 1\n  const zeus::CVector3f e2 = v0 - v2; // Tri edge 2\n\n  /* Bullet 3:  */\n  /*  test the 9 tests first (this was faster) */\n  fex = std::fabs(e0.x());\n  fey = std::fabs(e0.y());\n  fez = std::fabs(e0.z());\n  AXISTEST_X01(e0.z(), e0.y(), fez, fey);\n  AXISTEST_Y02(e0.z(), e0.x(), fez, fex);\n  AXISTEST_Z12(e0.y(), e0.x(), fey, fex);\n\n  fex = std::fabs(e1.x());\n  fey = std::fabs(e1.y());\n  fez = std::fabs(e1.z());\n  AXISTEST_X01(e1.z(), e1.y(), fez, fey);\n  AXISTEST_Y02(e1.z(), e1.x(), fez, fex);\n  AXISTEST_Z0(e1.y(), e1.x(), fey, fex);\n\n  fex = std::fabs(e2.x());\n  fey = std::fabs(e2.y());\n  fez = std::fabs(e2.z());\n  AXISTEST_X2(e2.z(), e2.y(), fez, fey);\n  AXISTEST_Y1(e2.z(), e2.x(), fez, fex);\n  AXISTEST_Z12(e2.y(), e2.x(), fey, fex);\n\n  /* Bullet 1: */\n  /*  first test overlap in the {x,y,z}-directions */\n  /*  find min, max of the triangle each direction, and test for overlap in */\n  /*  that direction -- this is equivalent to testing a minimal AABB around */\n  /*  the triangle against the AABB */\n\n  /* test in X-direction */\n  std::tie(min, max) = std::minmax<float>({v0.x(), v1.x(), v2.x()});\n  if (min > boxhalfsize.x() || max < -boxhalfsize.x()) {\n    return false;\n  }\n\n  /* test in Y-direction */\n  std::tie(min, max) = std::minmax<float>({v0.y(), v1.y(), v2.y()});\n  if (min > boxhalfsize.y() || max < -boxhalfsize.y()) {\n    return false;\n  }\n\n  /* test in Z-direction */\n  std::tie(min, max) = std::minmax<float>({v0.z(), v1.z(), v2.z()});\n  if (min > boxhalfsize.z() || max < -boxhalfsize.z()) {\n    return false;\n  }\n\n  /* Bullet 2: */\n  /*  test if the box intersects the plane of the triangle */\n  /*  compute plane equation of triangle: normal*x+d=0 */\n  const zeus::CVector3f normal = e0.cross(e1);\n  d = -normal.dot(v0); /* plane eq: normal.x+d=0 */\n  if (!planeBoxOverlap(normal, d, boxhalfsize)) {\n    return false;\n  }\n\n  return true; /* box and triangle overlaps */\n}\n\ndouble TriPointSqrDist(const zeus::CVector3f& point, const zeus::CVector3f& trivert0, const zeus::CVector3f& trivert1,\n                       const zeus::CVector3f& trivert2, float* baryX, float* baryY) {\n  const zeus::CVector3d A = trivert0 - point;\n  const zeus::CVector3d B = trivert1 - trivert0;\n  const zeus::CVector3d C = trivert2 - trivert0;\n\n  const double bMag = B.magSquared();\n  const double cMag = C.magSquared();\n  const double bDotC = B.dot(C);\n  const double aDotB = A.dot(B);\n  const double aDotC = A.dot(C);\n  double ret = A.magSquared();\n\n  const double rej = std::fabs(bMag * cMag - bDotC * bDotC);\n  double retB = bDotC * aDotC - cMag * aDotB;\n  double retA = bDotC * aDotB - bMag * aDotC;\n\n  if (retB + retA <= rej) {\n    if (retB < 0.0) {\n      if (retA < 0.0) {\n        if (aDotB < 0.0) {\n          retA = 0.0;\n          if (-aDotB >= bMag) {\n            retB = 1.0;\n            ret += 2.0 * aDotB + bMag;\n          } else {\n            retB = -aDotB / bMag;\n            ret += aDotB * retB;\n          }\n        } else {\n          retB = 0.0;\n          if (aDotC >= 0.0) {\n            retA = 0.0;\n          } else if (-aDotC >= cMag) {\n            retA = 1.0;\n            ret += 2.0 * aDotC + cMag;\n          } else {\n            retA = -aDotC / cMag;\n            ret += aDotC * retA;\n          }\n        }\n      } else {\n        retB = 0.0;\n        if (aDotC >= 0.0) {\n          retA = 0.0;\n        } else if (-aDotC >= cMag) {\n          retA = 1.0;\n          ret += 2.0 * aDotC + cMag;\n        } else {\n          retA = -aDotC / cMag;\n          ret += aDotC * retA;\n        }\n      }\n    } else if (retA < 0.0) {\n      retA = 0.0;\n      if (aDotB >= 0.0) {\n        retB = 0.0;\n      } else if (-aDotB >= bMag) {\n        retB = 1.0;\n        ret += 2.0 * aDotB + bMag;\n      } else {\n        retB = -aDotB / bMag;\n        ret += aDotB * retB;\n      }\n    } else {\n      const float f3 = 1.0 / rej;\n      retA *= f3;\n      retB *= f3;\n      ret += retB * (2.0 * aDotB + (bMag * retB + bDotC * retA)) + retA * (2.0 * aDotC + (bDotC * retB + cMag * retA));\n    }\n  } else if (retB < 0.0) {\n    retB = bDotC + aDotB;\n    retA = cMag + aDotC;\n    if (retA > retB) {\n      retA -= retB;\n      retB = bMag - 2.0 * bDotC;\n      retB += cMag;\n      if (retA >= retB) {\n        retB = 1.0;\n        retA = 0.0;\n        ret += 2.0 * aDotB + bMag;\n      } else {\n        retB = retA / retB;\n        retA = 1.0 - retB;\n        ret +=\n            retB * (2.0 * aDotB + (bMag * retB + bDotC * retA)) + retA * (2.0 * aDotC + (bDotC * retB + cMag * retA));\n      }\n    } else {\n      retB = 0.0;\n      if (retA <= 0.0) {\n        retA = 1.0;\n        ret += 2.0 * aDotC + cMag;\n      } else if (aDotC >= 0.0) {\n        retA = 0.0;\n      } else {\n        retA = -aDotC / cMag;\n        ret += aDotC * retA;\n      }\n    }\n  } else {\n    if (retA < 0.0) {\n      retB = bDotC + aDotC;\n      retA = bMag + aDotB;\n      if (retA > retB) {\n        retA -= retB;\n        retB = bMag - 2.0 * bDotC;\n        retB += cMag;\n        if (retA >= retB) {\n          retA = 1.0;\n          retB = 0.0;\n          ret += 2.0 * aDotC + cMag;\n        } else {\n          retA /= retB;\n          retB = 1.0 - retA;\n          ret +=\n              retB * (2.0 * aDotB + (bMag * retB + bDotC * retA)) + retA * (2.0 * aDotC + (bDotC * retB + cMag * retA));\n        }\n      } else {\n        retA = 0.0;\n        if (retA <= 0.0) {\n          retB = 1.0;\n          ret += 2.0 * aDotB + bMag;\n        } else if (aDotB >= 0.0) {\n          retB = 0.0;\n        } else {\n          retB = -aDotB / bMag;\n          ret += aDotB * retB;\n        }\n      }\n    } else {\n      retB = cMag + aDotC;\n      retB -= bDotC;\n      retA = retB - aDotB;\n      if (retA <= 0.0) {\n        retB = 0.0;\n        retA = 1.0;\n        ret += 2.0 * aDotC + cMag;\n      } else {\n        retB = bMag - 2.0 * bDotC;\n        retB += cMag;\n        if (retA >= retB) {\n          retB = 1.0;\n          retA = 0.0;\n          ret += 2.0 * aDotB + bMag;\n        } else {\n          retB = retA / retB;\n          retA = 1.0 - retB;\n          ret +=\n              retB * (2.0 * aDotB + (bMag * retB + bDotC * retA)) + retA * (2.0 * aDotC + (bDotC * retB + cMag * retA));\n        }\n      }\n    }\n  }\n\n  if (baryX != nullptr) {\n    *baryX = float(retA);\n  }\n  if (baryY != nullptr) {\n    *baryY = float(retB);\n  }\n\n  return ret;\n}\n\nbool TriSphereOverlap(const zeus::CSphere& sphere, const zeus::CVector3f& trivert0, const zeus::CVector3f& trivert1,\n                      const zeus::CVector3f& trivert2) {\n  return TriPointSqrDist(sphere.position, trivert0, trivert1, trivert2, nullptr, nullptr) <=\n         sphere.radius * sphere.radius;\n}\n\nbool TriSphereIntersection(const zeus::CSphere& sphere, const zeus::CVector3f& trivert0,\n                           const zeus::CVector3f& trivert1, const zeus::CVector3f& trivert2, zeus::CVector3f& point,\n                           zeus::CVector3f& normal) {\n  float baryX;\n  float baryY;\n  if (TriPointSqrDist(sphere.position, trivert0, trivert1, trivert2, &baryX, &baryY) > sphere.radius * sphere.radius) {\n    return false;\n  }\n\n  const zeus::CVector3f barys(baryX, baryY, 1.f - (baryX + baryY));\n  point = zeus::baryToWorld(trivert2, trivert1, trivert0, barys);\n\n  if (baryX == 0.f || baryX == 1.f || baryY == 0.f || baryY == 1.f || barys.z() == 0.f || barys.z() == 1.f) {\n    normal = -sphere.getSurfaceNormal(point);\n  } else {\n    normal = (trivert1 - trivert0).cross(trivert2 - trivert0).normalized();\n  }\n\n  return true;\n}\n\nbool BoxLineTest(const zeus::CAABox& aabb, const zeus::CVector3f& point, const zeus::CVector3f& dir, float& tMin,\n                 float& tMax, int& axis, bool& sign) {\n  tMin = -999999.f;\n  tMax = 999999.f;\n\n  for (size_t i = 0; i < 3; ++i) {\n    if (dir[i] == 0.f) {\n      if (point[i] < aabb.min[i] || point[i] > aabb.max[i]) {\n        return false;\n      }\n    }\n\n    const float dirRecip = 1.f / dir[i];\n    float tmpMin;\n    float tmpMax;\n    if (dir[i] < 0.f) {\n      tmpMin = (aabb.max[i] - point[i]) * dirRecip;\n      tmpMax = (aabb.min[i] - point[i]) * dirRecip;\n    } else {\n      tmpMin = (aabb.min[i] - point[i]) * dirRecip;\n      tmpMax = (aabb.max[i] - point[i]) * dirRecip;\n    }\n\n    if (tmpMin > tMin) {\n      sign = dir[i] < 0.f;\n      axis = i;\n      tMin = tmpMin;\n    }\n\n    if (tmpMax < tMax) {\n      tMax = tmpMax;\n    }\n  }\n\n  return tMin <= tMax;\n}\n\nbool LineCircleIntersection2d(const zeus::CVector3f& point, const zeus::CVector3f& dir, const zeus::CSphere& sphere,\n                              int axis1, int axis2, float& d) {\n  const zeus::CVector3f delta = sphere.position - point;\n  const zeus::CVector2f deltaVec(delta[axis1], delta[axis2]);\n  const zeus::CVector2f dirVec(dir[axis1], dir[axis2]);\n\n  const float dirVecMag = dirVec.magnitude();\n  if (dirVecMag < FLT_EPSILON) {\n    return false;\n  }\n\n  const float deltaVecDot = deltaVec.dot(dirVec / dirVecMag);\n  const float deltaVecMagSq = deltaVec.magSquared();\n\n  const float sphereRadSq = sphere.radius * sphere.radius;\n  if (deltaVecDot < 0.f && deltaVecMagSq > sphereRadSq) {\n    return false;\n  }\n\n  const float tSq = sphereRadSq - (deltaVecMagSq - deltaVecDot * deltaVecDot);\n  if (tSq < 0.f) {\n    return false;\n  }\n\n  const float t = std::sqrt(tSq);\n\n  d = (deltaVecMagSq > sphereRadSq) ? deltaVecDot - t : deltaVecDot + t;\n  d /= dirVecMag;\n\n  return true;\n}\n\nbool MovingSphereAABox(const zeus::CSphere& sphere, const zeus::CAABox& aabb, const zeus::CVector3f& dir, double& dOut,\n                       zeus::CVector3f& point, zeus::CVector3f& normal) {\n  const zeus::CAABox expAABB(aabb.min - sphere.radius, aabb.max + sphere.radius);\n  float tMin;\n  float tMax;\n  int axis;\n  bool sign;\n  if (!BoxLineTest(expAABB, sphere.position, dir, tMin, tMax, axis, sign)) {\n    return false;\n  }\n\n  point = sphere.position + tMin * dir;\n\n  const int nextAxis1 = (axis + 1) % 3; // r0\n  const int nextAxis2 = (axis + 2) % 3; // r5\n\n  const bool inMin1 = point[nextAxis1] >= aabb.min[nextAxis1]; // r6\n  const bool inMax1 = point[nextAxis1] <= aabb.max[nextAxis1]; // r8\n  const bool inBounds1 = inMin1 && inMax1;                     // r9\n  const bool inMin2 = point[nextAxis2] >= aabb.min[nextAxis2]; // r7\n  const bool inMax2 = point[nextAxis2] <= aabb.max[nextAxis2]; // r4\n  const bool inBounds2 = inMin2 && inMax2;                     // r8\n\n  if (inBounds1 && inBounds2) {\n    if (tMin < 0.f || tMin > dOut) {\n      return false;\n    }\n    normal[axis] = sign ? 1.f : -1.f;\n    dOut = tMin;\n    point -= normal * sphere.radius;\n    return true;\n  }\n\n  if (!inBounds1 && !inBounds2) {\n    const int pointFlags = (1 << axis) * sign | (1 << nextAxis1) * inMin1 | (1 << nextAxis2) * inMin2;\n    const zeus::CVector3f aabbPoint = aabb.getPoint(pointFlags);\n    float d;\n    if (CollisionUtil::RaySphereIntersection(zeus::CSphere(aabbPoint, sphere.radius), sphere.position, dir, dOut, d,\n                                             point)) {\n      int useAxis = -1;\n      for (int i = 0; i < 3; ++i) {\n        if ((pointFlags & (1 << i)) ? aabbPoint[i] > point[i] : aabbPoint[i] < point[i]) {\n          useAxis = i;\n          break;\n        }\n      }\n\n      if (useAxis == -1) {\n        normal = (point - aabbPoint).normalized();\n        point -= sphere.radius * normal;\n        return true;\n      }\n\n      const int useAxisNext1 = (useAxis + 1) % 3;\n      const int useAxisNext2 = (useAxis + 2) % 3;\n\n      float d;\n      if (CollisionUtil::LineCircleIntersection2d(sphere.position, dir, zeus::CSphere(aabbPoint, sphere.radius),\n                                                  useAxisNext1, useAxisNext2, d) &&\n          d > 0.f && d < dOut) {\n        if (point[useAxis] > aabb.max[useAxis]) {\n          const int useAxisBit = 1 << useAxis;\n          if (pointFlags & useAxisBit) {\n            return false;\n          }\n\n          const zeus::CVector3f aabbPoint1 = aabb.getPoint(pointFlags | useAxisBit);\n          if (CollisionUtil::RaySphereIntersection(zeus::CSphere(aabbPoint1, sphere.radius), sphere.position, dir, dOut,\n                                                   d, point)) {\n            dOut = d;\n            normal = (point - aabbPoint1).normalized();\n            point -= normal * sphere.radius;\n            return true;\n          } else {\n            return false;\n          }\n        } else if (point[useAxis] < aabb.min[useAxis]) {\n          const int useAxisBit = 1 << useAxis;\n          if (!(pointFlags & useAxisBit)) {\n            return false;\n          }\n\n          const zeus::CVector3f aabbPoint1 = aabb.getPoint(pointFlags ^ useAxisBit);\n          if (CollisionUtil::RaySphereIntersection(zeus::CSphere(aabbPoint1, sphere.radius), sphere.position, dir, dOut,\n                                                   d, point)) {\n            dOut = d;\n            normal = (point - aabbPoint1).normalized();\n            point -= normal * sphere.radius;\n            return true;\n          } else {\n            return false;\n          }\n        } else {\n          normal = point - aabbPoint;\n          normal.normalize();\n          point -= normal * sphere.radius;\n          return true;\n        }\n      }\n    } else {\n      int reverseCount = 0;\n      float dMin = 1.0e10f;\n      int minAxis = 0;\n      for (int i = 0; i < 3; ++i) {\n        if (std::fabs(dir[i]) > FLT_EPSILON) {\n          const bool pointMax = (pointFlags & (1 << i)) != 0;\n          if (pointMax != (dir[i] > 0.f)) {\n            ++reverseCount;\n            const float d = 1.f / dir[i] * ((pointMax ? aabb.max[i] : aabb.min[i]) - sphere.position[i]);\n            if (d < 0.f) {\n              return false;\n            }\n            if (d < dMin) {\n              dMin = d;\n              minAxis = i;\n            }\n          }\n        }\n      }\n\n      if (reverseCount < 2) {\n        return false;\n      }\n\n      const int useAxisNext1 = (minAxis + 1) % 3;\n      const int useAxisNext2 = (minAxis + 2) % 3;\n      float d;\n      if (CollisionUtil::LineCircleIntersection2d(sphere.position, dir, zeus::CSphere(aabbPoint, sphere.radius),\n                                                  useAxisNext1, useAxisNext2, d) &&\n          d > 0.f && d < dOut) {\n        point = sphere.position + d * dir;\n        if (point[minAxis] > aabb.max[minAxis]) {\n          return false;\n        }\n        if (point[minAxis] < aabb.min[minAxis]) {\n          return false;\n        }\n\n        dOut = d;\n        normal = point - aabbPoint;\n        normal.normalize();\n        point -= sphere.radius * normal;\n        return true;\n      } else {\n        return false;\n      }\n    }\n  }\n\n  const bool useNextAxis1 = inBounds1 ? nextAxis2 : nextAxis1;\n  const bool useNextAxis2 = inBounds1 ? nextAxis1 : nextAxis2;\n\n  const int pointFlags = ((1 << int(useNextAxis1)) * (inBounds1 ? inMin2 : inMin1)) | ((1 << axis) * sign);\n  const zeus::CVector3f aabbPoint2 = aabb.getPoint(pointFlags);\n  float d;\n  if (LineCircleIntersection2d(sphere.position, dir, zeus::CSphere(aabbPoint2, sphere.radius), axis, useNextAxis1, d) &&\n      d > 0.f && d < dOut) {\n    point = sphere.position + d * dir;\n    if (point[useNextAxis2] > aabb.max[useNextAxis2]) {\n      const zeus::CVector3f aabbPoint3 = aabb.getPoint(pointFlags | (1 << int(useNextAxis2)));\n      if (point[useNextAxis2] < expAABB.max[useNextAxis2]) {\n        if (RaySphereIntersection(zeus::CSphere(aabbPoint3, sphere.radius), sphere.position, dir, dOut, d, point)) {\n          dOut = d;\n          normal = (point - aabbPoint3).normalized();\n          point -= sphere.radius * normal;\n          return true;\n        }\n      }\n      return false;\n    }\n\n    if (point[useNextAxis2] < aabb.min[useNextAxis2]) {\n      if (point[useNextAxis2] > expAABB.min[useNextAxis2]) {\n        if (RaySphereIntersection(zeus::CSphere(aabbPoint2, sphere.radius), sphere.position, dir, dOut, d, point)) {\n          dOut = d;\n          normal = (point - aabbPoint2).normalized();\n          point -= sphere.radius * normal;\n          return true;\n        }\n      }\n      return false;\n    } else {\n      dOut = d;\n      normal = point - aabbPoint2;\n      normal.normalize();\n      point -= sphere.radius * normal;\n      return true;\n    }\n  }\n\n  return false;\n}\n\nbool AABox_AABox_Moving(const zeus::CAABox& aabb0, const zeus::CAABox& aabb1, const zeus::CVector3f& dir, double& d,\n                        zeus::CVector3f& point, zeus::CVector3f& normal) {\n  zeus::CVector3d vecMin(-FLT_MAX);\n  zeus::CVector3d vecMax(FLT_MAX);\n\n  for (size_t i = 0; i < 3; ++i) {\n    if (std::fabs(dir[i]) < FLT_EPSILON) {\n      if (aabb0.min[i] >= aabb1.min[i] && aabb0.min[i] <= aabb1.max[i]) {\n        continue;\n      }\n      if (aabb0.max[i] >= aabb1.min[i] && aabb0.max[i] <= aabb1.max[i]) {\n        continue;\n      }\n      if (aabb0.min[i] < aabb1.min[i] && aabb0.max[i] > aabb1.max[i]) {\n        continue;\n      }\n      return false;\n    } else {\n      if (aabb0.max[i] < aabb1.min[i] && dir[i] > 0.f) {\n        vecMin[i] = (aabb1.min[i] - aabb0.max[i]) / dir[i];\n      } else if (aabb1.max[i] < aabb0.min[i] && dir[i] < 0.f) {\n        vecMin[i] = (aabb1.max[i] - aabb0.min[i]) / dir[i];\n      } else if (aabb1.max[i] > aabb0.min[i] && dir[i] < 0.f) {\n        vecMin[i] = (aabb1.max[i] - aabb0.min[i]) / dir[i];\n      } else if (aabb0.max[i] > aabb1.min[i] && dir[i] > 0.f) {\n        vecMin[i] = (aabb1.min[i] - aabb0.max[i]) / dir[i];\n      }\n\n      if (aabb1.max[i] > aabb0.min[i] && dir[i] > 0.f) {\n        vecMax[i] = (aabb1.max[i] - aabb0.min[i]) / dir[i];\n      } else if (aabb0.max[i] > aabb1.min[i] && dir[i] < 0.f) {\n        vecMax[i] = (aabb1.min[i] - aabb0.max[i]) / dir[i];\n      } else if (aabb0.max[i] < aabb1.min[i] && dir[i] < 0.f) {\n        vecMax[i] = (aabb1.min[i] - aabb0.max[i]) / dir[i];\n      } else if (aabb1.max[i] < aabb0.min[i] && dir[i] > 0.f) {\n        vecMax[i] = (aabb1.max[i] - aabb0.min[i]) / dir[i];\n      }\n    }\n  }\n\n  int maxAxis = 0;\n  if (vecMin[1] > vecMin[0]) {\n    maxAxis = 1;\n  }\n  if (vecMin[2] > vecMin[maxAxis]) {\n    maxAxis = 2;\n  }\n\n  const double minMax = std::min(std::min(vecMax[2], vecMax[1]), vecMax[0]);\n  if (vecMin[maxAxis] > minMax) {\n    return false;\n  }\n  d = vecMin[maxAxis];\n\n  normal = zeus::skZero3f;\n  normal[maxAxis] = dir[maxAxis] > 0.f ? -1.f : 1.f;\n\n  for (size_t i = 0; i < 3; ++i) {\n    point[i] = dir[i] > 0.f ? aabb0.max[i] : aabb0.min[i];\n  }\n\n  point += float(d) * dir;\n  return true;\n}\n\nvoid AddAverageToFront(const CCollisionInfoList& in, CCollisionInfoList& out) {\n  if (in.GetCount() > 1) {\n    zeus::CVector3f pointAccum;\n    zeus::CVector3f normAccum;\n\n    for (const CCollisionInfo& info : in) {\n      pointAccum += info.GetPoint();\n      normAccum += info.GetNormalLeft();\n    }\n\n    if (normAccum.canBeNormalized()) {\n      out.Add(CCollisionInfo(pointAccum / float(in.GetCount()), in.GetItem(0).GetMaterialRight(),\n                             in.GetItem(0).GetMaterialLeft(), normAccum.normalized()),\n              false);\n    }\n  }\n\n  for (const CCollisionInfo& info : in) {\n    out.Add(info, false);\n  }\n}\n} // namespace metaforce::CollisionUtil\n"
  },
  {
    "path": "Runtime/Collision/CollisionUtil.hpp",
    "content": "#pragma once\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/Collision/CMaterialList.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CMRay.hpp>\n#include <zeus/COBBox.hpp>\n#include <zeus/CSphere.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CCollisionInfoList;\nnamespace CollisionUtil {\nbool LineIntersectsOBBox(const zeus::COBBox&, const zeus::CMRay&, float&);\nu32 RayAABoxIntersection(const zeus::CMRay&, const zeus::CAABox&, float&, float&);\nu32 RayAABoxIntersection(const zeus::CMRay&, const zeus::CAABox&, zeus::CVector3f&, float&);\nu32 RayAABoxIntersection_Double(const zeus::CMRay&, const zeus::CAABox&, zeus::CVector3f&, double&);\nbool RaySphereIntersection_Double(const zeus::CSphere&, const zeus::CVector3f&, const zeus::CVector3f&, double&);\nbool RaySphereIntersection(const zeus::CSphere& sphere, const zeus::CVector3f& pos, const zeus::CVector3f& dir,\n                           float mag, float& T, zeus::CVector3f& point);\nbool RayTriangleIntersection_Double(const zeus::CVector3f& point, const zeus::CVector3f& dir,\n                                    const std::array<zeus::CVector3f, 3>& verts, double& d);\nbool RayTriangleIntersection(const zeus::CVector3f& point, const zeus::CVector3f& dir,\n                             const std::array<zeus::CVector3f, 3>& verts, float& d);\nvoid FilterOutBackfaces(const zeus::CVector3f& vec, const CCollisionInfoList& in, CCollisionInfoList& out);\nvoid FilterByClosestNormal(const zeus::CVector3f& norm, const CCollisionInfoList& in, CCollisionInfoList& out);\nbool AABoxAABoxIntersection(const zeus::CAABox& aabb0, const CMaterialList& list0, const zeus::CAABox& aabb1,\n                            const CMaterialList& list1, CCollisionInfoList& infoList);\nbool AABoxAABoxIntersection(const zeus::CAABox& aabb0, const zeus::CAABox& aabb1);\nbool TriBoxOverlap(const zeus::CVector3f& boxcenter, const zeus::CVector3f& boxhalfsize,\n                   const zeus::CVector3f& trivert0, const zeus::CVector3f& trivert1, const zeus::CVector3f& trivert2);\ndouble TriPointSqrDist(const zeus::CVector3f& point, const zeus::CVector3f& trivert0, const zeus::CVector3f& trivert1,\n                       const zeus::CVector3f& trivert2, float* baryX, float* baryY);\nbool TriSphereOverlap(const zeus::CSphere& sphere, const zeus::CVector3f& trivert0, const zeus::CVector3f& trivert1,\n                      const zeus::CVector3f& trivert2);\nbool TriSphereIntersection(const zeus::CSphere& sphere, const zeus::CVector3f& trivert0,\n                           const zeus::CVector3f& trivert1, const zeus::CVector3f& trivert2, zeus::CVector3f& point,\n                           zeus::CVector3f& normal);\nbool BoxLineTest(const zeus::CAABox& aabb, const zeus::CVector3f& point, const zeus::CVector3f& dir, float& tMin,\n                 float& tMax, int& axis, bool& sign);\nbool LineCircleIntersection2d(const zeus::CVector3f& point, const zeus::CVector3f& dir, const zeus::CSphere& sphere,\n                              int axis1, int axis2, float& d);\nbool MovingSphereAABox(const zeus::CSphere& sphere, const zeus::CAABox& aabb, const zeus::CVector3f& dir, double& d,\n                       zeus::CVector3f& point, zeus::CVector3f& normal);\nbool AABox_AABox_Moving(const zeus::CAABox& aabb0, const zeus::CAABox& aabb1, const zeus::CVector3f& dir, double& d,\n                        zeus::CVector3f& point, zeus::CVector3f& normal);\nvoid AddAverageToFront(const CCollisionInfoList& in, CCollisionInfoList& out);\n} // namespace CollisionUtil\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/ICollisionFilter.hpp",
    "content": "#pragma once\n\nnamespace metaforce {\nclass CActor;\nclass CCollisionInfoList;\n\nclass ICollisionFilter {\n  CActor& x4_actor;\n\nprotected:\n  explicit ICollisionFilter(CActor& actor) : x4_actor(actor) {}\n\npublic:\n  virtual ~ICollisionFilter() = default;\n  virtual void Filter(const CCollisionInfoList& in, CCollisionInfoList& out) const = 0;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Collision/InternalColliders.cpp",
    "content": "#include \"Runtime/Collision/InternalColliders.hpp\"\n\n#include \"Runtime/Collision/CCollidableAABox.hpp\"\n#include \"Runtime/Collision/CCollidableCollisionSurface.hpp\"\n#include \"Runtime/Collision/CCollidableSphere.hpp\"\n\nnamespace metaforce::InternalColliders {\nvoid AddTypes() {\n  CCollisionPrimitive::InitAddType(CCollidableAABox::GetType());\n  CCollisionPrimitive::InitAddType(CCollidableCollisionSurface::GetType());\n  CCollisionPrimitive::InitAddType(CCollidableSphere::GetType());\n}\n\nvoid AddColliders() {\n  CCollisionPrimitive::InitAddCollider(Collide::AABox_AABox, \"CCollidableAABox\", \"CCollidableAABox\");\n  CCollisionPrimitive::InitAddCollider(Collide::Sphere_AABox, \"CCollidableSphere\", \"CCollidableAABox\");\n  CCollisionPrimitive::InitAddCollider(Collide::Sphere_Sphere, \"CCollidableSphere\", \"CCollidableSphere\");\n  CCollisionPrimitive::InitAddBooleanCollider(Collide::AABox_AABox_Bool, \"CCollidableAABox\", \"CCollidableAABox\");\n  CCollisionPrimitive::InitAddBooleanCollider(Collide::Sphere_AABox_Bool, \"CCollidableSphere\", \"CCollidableAABox\");\n  CCollisionPrimitive::InitAddBooleanCollider(Collide::Sphere_Sphere_Bool, \"CCollidableSphere\", \"CCollidableSphere\");\n  CCollisionPrimitive::InitAddMovingCollider(CCollidableAABox::CollideMovingAABox, \"CCollidableAABox\",\n                                             \"CCollidableAABox\");\n  CCollisionPrimitive::InitAddMovingCollider(CCollidableAABox::CollideMovingSphere, \"CCollidableAABox\",\n                                             \"CCollidableSphere\");\n  CCollisionPrimitive::InitAddMovingCollider(CCollidableSphere::CollideMovingAABox, \"CCollidableSphere\",\n                                             \"CCollidableAABox\");\n  CCollisionPrimitive::InitAddMovingCollider(CCollidableSphere::CollideMovingSphere, \"CCollidableSphere\",\n                                             \"CCollidableSphere\");\n}\n} // namespace metaforce::InternalColliders\n"
  },
  {
    "path": "Runtime/Collision/InternalColliders.hpp",
    "content": "#pragma once\n\nnamespace metaforce::InternalColliders {\nvoid AddTypes();\nvoid AddColliders();\n} // namespace metaforce::InternalColliders\n"
  },
  {
    "path": "Runtime/ConsoleVariables/CVar.cpp",
    "content": "﻿#include \"Runtime/CStringExtras.hpp\"\n#include \"Runtime/ConsoleVariables/CVar.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\n#include \"Runtime/ConsoleVariables/CVarManager.hpp\"\n\nnamespace metaforce {\nextern CVar* com_developer;\nextern CVar* com_enableCheats;\n\nusing namespace std::literals;\n\nCVar::CVar(std::string_view name, std::string_view value, std::string_view help, CVar::EFlags flags)\n: CVar(name, help, EType::Literal) {\n  fromLiteral(value);\n  init(flags);\n}\n\nCVar::CVar(std::string_view name, const zeus::CVector2i& value, std::string_view help, EFlags flags)\n: CVar(name, help, EType::Vec2i) {\n  fromVec2i(value);\n  init(flags);\n}\n\nCVar::CVar(std::string_view name, const zeus::CVector2f& value, std::string_view help, EFlags flags)\n: CVar(name, help, EType::Vec2f) {\n  fromVec2f(value);\n  init(flags);\n}\n\nCVar::CVar(std::string_view name, const zeus::CVector2d& value, std::string_view help, EFlags flags)\n: CVar(name, help, EType::Vec2d) {\n  fromVec2d(value);\n\n  init(flags);\n}\n\nCVar::CVar(std::string_view name, const zeus::CVector3f& value, std::string_view help, EFlags flags)\n: CVar(name, help, EType::Vec3f) {\n  fromVec3f(value);\n  init(flags, false);\n}\n\nCVar::CVar(std::string_view name, const zeus::CVector3d& value, std::string_view help, EFlags flags)\n: CVar(name, help, EType::Vec3d) {\n  fromVec3d(value);\n  init(flags, false);\n}\n\nCVar::CVar(std::string_view name, const zeus::CVector4f& value, std::string_view help, EFlags flags)\n: CVar(name, help, EType::Vec4f) {\n  fromVec4f(value);\n  init(flags, false);\n}\n\nCVar::CVar(std::string_view name, const zeus::CVector4d& value, std::string_view help, EFlags flags)\n: CVar(name, help, EType::Vec4d) {\n  fromVec4d(value);\n  init(flags, false);\n}\n\nCVar::CVar(std::string_view name, double value, std::string_view help, EFlags flags) : CVar(name, help, EType::Real) {\n  fromReal(value);\n  init(flags);\n}\n\nCVar::CVar(std::string_view name, bool value, std::string_view help, CVar::EFlags flags)\n: CVar(name, help, EType::Boolean) {\n  fromBoolean(value);\n  init(flags);\n}\n\nCVar::CVar(std::string_view name, int32_t value, std::string_view help, CVar::EFlags flags)\n: CVar(name, help, EType::Signed) {\n  fromInteger(value);\n  init(flags);\n}\n\nCVar::CVar(std::string_view name, uint32_t value, std::string_view help, CVar::EFlags flags)\n: CVar(name, help, EType::Unsigned) {\n  fromInteger(value);\n  init(flags);\n}\n\nstd::string CVar::help() const {\n  return m_help + (m_defaultValue.empty() ? \"\" : \"\\ndefault: \" + m_defaultValue) + (isReadOnly() ? \" [ReadOnly]\" : \"\");\n}\n\nzeus::CVector2i CVar::toVec2i(bool* isValid) const {\n  if (m_type != EType::Vec2i) {\n    if (isValid != nullptr)\n      *isValid = false;\n\n    return {};\n  }\n\n  if (isValid != nullptr)\n    *isValid = true;\n\n  std::array<int, 2> f;\n  std::sscanf(m_value.c_str(), \"%i %i\", &f[0], &f[1]);\n  return {f[0], f[1]};\n}\n\n\nzeus::CVector2f CVar::toVec2f(bool* isValid) const {\n  if (m_type != EType::Vec2f) {\n    if (isValid != nullptr)\n      *isValid = false;\n\n    return {};\n  }\n\n  if (isValid != nullptr)\n    *isValid = true;\n\n  std::array<float, 2> f;\n  std::sscanf(m_value.c_str(), \"%g %g\", &f[0], &f[1]);\n  return {f[0], f[1]};\n}\n\nzeus::CVector2d CVar::toVec2d(bool* isValid) const {\n  if (m_type != EType::Vec2d) {\n    if (isValid != nullptr)\n      *isValid = false;\n\n    return {};\n  }\n\n  if (isValid != nullptr)\n    *isValid = true;\n\n  std::array<double, 2> f;\n  std::sscanf(m_value.c_str(), \"%lg %lg\", &f[0], &f[1]);\n  return {f[0], f[1]};\n}\n\nzeus::CVector3f CVar::toVec3f(bool* isValid) const {\n  if (m_type != EType::Vec3f) {\n    if (isValid != nullptr)\n      *isValid = false;\n\n    return {};\n  }\n\n  if (isValid != nullptr)\n    *isValid = true;\n\n  std::array<float, 3> f;\n  std::sscanf(m_value.c_str(), \"%g %g %g\", &f[0], &f[1], &f[2]);\n  return {f[0], f[1], f[2]};\n}\n\nzeus::CVector3d CVar::toVec3d(bool* isValid) const {\n  if (m_type != EType::Vec3d) {\n    if (isValid != nullptr)\n      *isValid = false;\n\n    return {};\n  }\n\n  if (isValid != nullptr)\n    *isValid = true;\n\n  std::array<double, 3> f;\n  std::sscanf(m_value.c_str(), \"%lg %lg %lg\", &f[0], &f[1], &f[2]);\n  return {f[0], f[1], f[2]};\n}\n\nzeus::CVector4f CVar::toVec4f(bool* isValid) const {\n  if (m_type != EType::Vec4f) {\n    if (isValid != nullptr)\n      *isValid = false;\n\n    return {};\n  }\n\n  if (isValid != nullptr)\n    *isValid = true;\n\n  std::array<float, 4> f;\n  std::sscanf(m_value.c_str(), \"%g %g %g %g\", &f[0], &f[1], &f[2], &f[3]);\n  return {f[0], f[1], f[2], f[3]};\n}\n\nzeus::CVector4d CVar::toVec4d(bool* isValid) const {\n  if (m_type != EType::Vec4d) {\n    if (isValid != nullptr)\n      *isValid = false;\n\n    return {};\n  }\n\n  if (isValid != nullptr)\n    *isValid = true;\n\n  std::array<double, 4> f{};\n  std::sscanf(m_value.c_str(), \"%lg %lg %lg %lg\", &f[0], &f[1], &f[2], &f[3]);\n  return {f[0], f[1], f[2], f[3]};\n}\n\ndouble CVar::toReal(bool* isValid) const {\n  if (m_type != EType::Real) {\n    if (isValid)\n      *isValid = false;\n    return 0.0f;\n  }\n\n  if (isValid != nullptr)\n    *isValid = true;\n\n  return strtod(m_value.c_str(), nullptr);\n}\n\nbool CVar::toBoolean(bool* isValid) const {\n  if (m_type != EType::Boolean) {\n    if (isValid)\n      *isValid = false;\n\n    return false;\n  }\n\n  return CStringExtras::ParseBool(m_value, isValid);\n}\n\nint32_t CVar::toSigned(bool* isValid) const {\n  if (m_type != EType::Signed && m_type != EType::Unsigned) {\n    if (isValid)\n      *isValid = false;\n    return 0;\n  }\n\n  if (isValid != nullptr)\n    *isValid = true;\n\n  return strtol(m_value.c_str(), nullptr, 0);\n}\n\nuint32_t CVar::toUnsigned(bool* isValid) const {\n  if (m_type != EType::Signed && m_type != EType::Unsigned) {\n    if (isValid)\n      *isValid = false;\n    return 0;\n  }\n\n  if (isValid != nullptr)\n    *isValid = true;\n\n  return strtoul(m_value.c_str(), nullptr, 0);\n}\n\nstd::string CVar::toLiteral(bool* isValid) const {\n  if (m_type != EType::Literal && (com_developer && com_developer->toBoolean())) {\n    if (isValid != nullptr)\n      *isValid = false;\n  } else if (isValid != nullptr) {\n    *isValid = true;\n  }\n\n  // Even if it's not a literal, it's still safe to return\n  return m_value;\n}\n\nbool CVar::fromVec2i(const zeus::CVector2i& val) {\n  if (!safeToModify(EType::Vec2i))\n    return false;\n\n  m_value.assign(fmt::format(\"{} {}\", val.x, val.y));\n  m_flags |= EFlags::Modified;\n  return true;\n}\n\nbool CVar::fromVec2f(const zeus::CVector2f& val) {\n  if (!safeToModify(EType::Vec2f))\n    return false;\n\n  m_value.assign(fmt::format(\"{} {}\", val.x(), val.y()));\n  m_flags |= EFlags::Modified;\n  return true;\n}\n\nbool CVar::fromVec2d(const zeus::CVector2d& val) {\n  if (!safeToModify(EType::Vec2d))\n    return false;\n\n  m_value.assign(fmt::format(\"{} {}\", val.x(), val.y()));\n  m_flags |= EFlags::Modified;\n  return true;\n}\n\nbool CVar::fromVec3f(const zeus::CVector3f& val) {\n  if (!safeToModify(EType::Vec3f))\n    return false;\n\n  m_value.assign(fmt::format(\"{} {} {}\", val.x(), val.y(), val.z()));\n  m_flags |= EFlags::Modified;\n  return true;\n}\n\nbool CVar::fromVec3d(const zeus::CVector3d& val) {\n  if (!safeToModify(EType::Vec3d))\n    return false;\n\n  m_value.assign(fmt::format(\"{} {} {}\", val.x(), val.y(), val.z()));\n  m_flags |= EFlags::Modified;\n  return true;\n}\n\nbool CVar::fromVec4f(const zeus::CVector4f& val) {\n  if (!safeToModify(EType::Vec4f))\n    return false;\n\n  m_value.assign(fmt::format(\"{} {} {} {}\", val.x(), val.y(), val.z(), val.w()));\n  m_flags |= EFlags::Modified;\n  return true;\n}\n\nbool CVar::fromVec4d(const zeus::CVector4d& val) {\n  if (!safeToModify(EType::Vec4d))\n    return false;\n\n  m_value.assign(fmt::format(\"{} {} {} {}\", val.x(), val.y(), val.z(), val.w()));\n  m_flags |= EFlags::Modified;\n  return true;\n}\n\nbool CVar::fromReal(double val) {\n  if (!safeToModify(EType::Real))\n    return false;\n\n  m_value.assign(fmt::format(\"{}\", val));\n  setModified();\n  return true;\n}\n\nbool CVar::fromBoolean(bool val) {\n  if (!safeToModify(EType::Boolean))\n    return false;\n\n  if (val)\n    m_value = \"true\"sv;\n  else\n    m_value = \"false\"sv;\n\n  setModified();\n  return true;\n}\n\nbool CVar::fromInteger(int32_t val) {\n  if ((com_developer && com_enableCheats) && (!com_developer->toBoolean() || !com_enableCheats->toBoolean()) &&\n      isCheat())\n    return false;\n\n  // We'll accept both signed an unsigned input\n  if (m_type != EType::Signed && m_type != EType::Unsigned)\n    return false;\n\n  if (isReadOnly() && (com_developer && !com_developer->toBoolean()))\n    return false;\n\n  // Properly format based on signedness\n  m_value = fmt::format(\"{}\", (m_type == EType::Signed ? val : static_cast<uint32_t>(val)));\n  setModified();\n  return true;\n}\n\nbool CVar::fromInteger(uint32_t val) {\n  if ((com_developer && com_enableCheats) && (!com_developer->toBoolean() || !com_enableCheats->toBoolean()) &&\n      isCheat())\n    return false;\n\n  // We'll accept both signed an unsigned input\n  if (m_type != EType::Signed && m_type != EType::Unsigned)\n    return false;\n\n  if (isReadOnly() && (com_developer && !com_developer->toBoolean()))\n    return false;\n\n  // Properly format based on signedness\n  m_value = fmt::format(\"{}\", (m_type == EType::Unsigned ? val : static_cast<int32_t>(val)));\n  setModified();\n  return true;\n}\n\nbool CVar::fromLiteral(std::string_view val) {\n  if (!safeToModify(EType::Literal))\n    return false;\n\n  m_value.assign(val);\n  setModified();\n  return true;\n}\n\nbool CVar::fromLiteralToType(std::string_view val) {\n  if (!safeToModify(m_type) || !isValidInput(val))\n    return false;\n  m_value = val;\n  setModified();\n  return true;\n}\n\nbool CVar::isModified() const { return True(m_flags & EFlags::Modified); }\nbool CVar::modificationRequiresRestart() const { return True(m_flags & EFlags::ModifyRestart); }\n\nbool CVar::isReadOnly() const { return True(m_flags & EFlags::ReadOnly); }\n\nbool CVar::isCheat() const { return True(m_flags & EFlags::Cheat); }\n\nbool CVar::isHidden() const { return True(m_flags & EFlags::Hidden); }\n\nbool CVar::isArchive() const { return True(m_flags & EFlags::Archive); }\n\nbool CVar::isInternalArchivable() const { return True(m_flags & EFlags::InternalArchivable); }\n\nbool CVar::isColor() const {\n  return True(m_flags & EFlags::Color) && (m_type == EType::Vec3f || m_type == EType::Vec3d || m_type == EType::Vec3f ||\n                                           m_type == EType::Vec4f || m_type == EType::Vec4d);\n}\n\nbool CVar::isNoDeveloper() const { return True(m_flags & EFlags::NoDeveloper); }\n\nbool CVar::wasDeserialized() const { return m_wasDeserialized; }\n\nbool CVar::hasDefaultValue() const { return m_defaultValue == m_value; }\n\nvoid CVar::clearModified() {\n  if (!modificationRequiresRestart())\n    m_flags &= ~EFlags::Modified;\n}\n\nvoid CVar::forceClearModified() { m_flags &= ~EFlags::Modified; }\n\nvoid CVar::setModified() { m_flags |= EFlags::Modified; }\n\nvoid CVar::unlock() {\n  if (isReadOnly() && !m_unlocked) {\n    m_oldFlags = m_flags;\n    m_flags &= ~EFlags::ReadOnly;\n    m_unlocked = true;\n  }\n}\n\nvoid CVar::lock() {\n  if (!isReadOnly() && m_unlocked) {\n    // We want to keep if we've been modified so we can inform our listeners\n    bool modified = True(m_flags & EFlags::Modified);\n    m_flags = m_oldFlags;\n    // If we've been modified insert that back into m_flags\n    if (modified) {\n      m_flags |= EFlags::Modified;\n    }\n    m_unlocked = false;\n  }\n}\n\nvoid CVar::dispatch() {\n  for (const ListenerFunc& listen : m_listeners)\n    listen(this);\n  for (auto* ref : m_valueReferences) {\n    ref->updateValue();\n  }\n}\n\nbool isInt(std::string_view v) {\n  char* p;\n  std::strtol(v.data(), &p, 10);\n  return p != nullptr && *p == 0;\n}\n\nbool isInt(const std::vector<std::string>& v) {\n  for (auto& s : v) {\n    if (!isInt(s))\n      return false;\n  }\n  return true;\n}\n\nbool isReal(std::string_view v) {\n  char* p;\n  std::strtod(v.data(), &p);\n  return p != nullptr && *p == 0;\n}\nbool isReal(const std::vector<std::string>& v) {\n  for (auto& s : v) {\n    if (!isReal(s))\n      return false;\n  }\n  return true;\n}\n\nbool CVar::isValidInput(std::string_view input) const {\n  std::vector<std::string> parts = CStringExtras::Split(input, ' ');\n  char* p;\n  switch (m_type) {\n  case EType::Boolean: {\n    bool valid = false;\n    CStringExtras::ParseBool(input, &valid);\n    return valid;\n  }\n  case EType::Signed:\n    std::strtol(input.data(), &p, 0);\n    return p != nullptr && *p == 0;\n  case EType::Unsigned:\n    std::strtoul(input.data(), &p, 0);\n    return p != nullptr && *p == 0;\n  case EType::Real: {\n    bool size = parts.size() == 1;\n    bool ret = isReal(input);\n    return ret && size;\n  }\n  case EType::Literal:\n    return true;\n  case EType::Vec2i:\n    return parts.size() == 2 && isInt(parts);\n  case EType::Vec2f:\n  case EType::Vec2d:\n    return parts.size() == 2 && isReal(parts);\n  case EType::Vec3f:\n  case EType::Vec3d:\n    return parts.size() == 3 && isReal(parts);\n  case EType::Vec4f:\n  case EType::Vec4d:\n    return parts.size() == 4 && isReal(parts);\n  }\n\n  return false;\n}\n\nbool CVar::safeToModify(EType type) const {\n  // Are we NoDevelper?\n  if (isNoDeveloper())\n    return false;\n\n  // Are we a cheat?\n  if (isCheat() && (com_developer && com_enableCheats) &&\n      (!com_developer->toBoolean() || !com_enableCheats->toBoolean()))\n    return false;\n\n  // Are we read only?\n  if (isReadOnly() && (com_developer && !com_developer->toBoolean()))\n    return false;\n\n  return m_type == type;\n}\n\nvoid CVar::init(EFlags flags, bool removeColor) {\n  m_defaultValue = m_value;\n  m_flags = flags;\n  if (removeColor) {\n    // If the user specifies color, we don't want it\n    m_flags &= ~EFlags::Color;\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/ConsoleVariables/CVar.hpp",
    "content": "#pragma once\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"zeus/zeus.hpp\"\n\n#include <functional>\n#include <string>\n#include <vector>\nnamespace metaforce {\nnamespace StoreCVar {\nenum class EType : uint32_t { Boolean, Signed, Unsigned, Real, Literal, Vec2i, Vec2f, Vec2d, Vec3f, Vec3d, Vec4f, Vec4d };\nenum class EFlags {\n  None = 0,\n  System = (1 << 0),\n  Game = (1 << 1),\n  Editor = (1 << 2),\n  Gui = (1 << 3),\n  Cheat = (1 << 4),\n  Hidden = (1 << 5),\n  ReadOnly = (1 << 6),\n  Archive = (1 << 7),\n  InternalArchivable = (1 << 8),\n  Modified = (1 << 9),\n  ModifyRestart = (1 << 10), //!< If this bit is set, any modification will inform the user that a restart is required\n  Color = (1 << 11), //!< If this bit is set, Vec3f and Vec4f will be displayed in the console with a colored square\n  NoDeveloper = (1 << 12), //!< Not even developer mode can modify this\n  Any = -1\n};\nENABLE_BITWISE_ENUM(EFlags)\n\nclass CVar {\npublic:\n  std::string m_name;\n  std::string m_value;\n};\n\nstruct CVarContainer {\n  u32 magic = 'CVAR';\n  std::vector<CVar> cvars;\n};\n\n} // namespace StoreCVar\n\nclass CVarManager;\nclass ICVarValueReference;\nclass CVar : protected StoreCVar::CVar {\n  friend class CVarManager;\n\npublic:\n  typedef std::function<void(CVar*)> ListenerFunc;\n\n  using EType = StoreCVar::EType;\n  using EFlags = StoreCVar::EFlags;\n\n  CVar(std::string_view name, std::string_view value, std::string_view help, EFlags flags);\n  CVar(std::string_view name, const zeus::CVector2i& value, std::string_view help, EFlags flags);\n  CVar(std::string_view name, const zeus::CVector2f& value, std::string_view help, EFlags flags);\n  CVar(std::string_view name, const zeus::CVector2d& value, std::string_view help, EFlags flags);\n  CVar(std::string_view name, const zeus::CVector3f& value, std::string_view help, EFlags flags);\n  CVar(std::string_view name, const zeus::CVector3d& value, std::string_view help, EFlags flags);\n  CVar(std::string_view name, const zeus::CVector4f& value, std::string_view help, EFlags flags);\n  CVar(std::string_view name, const zeus::CVector4d& value, std::string_view help, EFlags flags);\n  CVar(std::string_view name, double value, std::string_view help, EFlags flags);\n  CVar(std::string_view name, bool value, std::string_view help, EFlags flags);\n  CVar(std::string_view name, int32_t value, std::string_view help, EFlags flags);\n  CVar(std::string_view name, uint32_t value, std::string_view help, EFlags flags);\n\n  std::string_view name() const { return m_name; }\n  std::string_view rawHelp() const { return m_help; }\n  std::string_view defaultValue() const { return m_defaultValue; }\n  std::string help() const;\n  std::string value() const { return m_value; }\n\n  template <typename T>\n  inline bool toValue(T& value) const;\n  zeus::CVector2i toVec2i(bool* isValid = nullptr) const;\n  zeus::CVector2f toVec2f(bool* isValid = nullptr) const;\n  zeus::CVector2d toVec2d(bool* isValid = nullptr) const;\n  zeus::CVector3f toVec3f(bool* isValid = nullptr) const;\n  zeus::CVector3d toVec3d(bool* isValid = nullptr) const;\n  zeus::CVector4f toVec4f(bool* isValid = nullptr) const;\n  zeus::CVector4d toVec4d(bool* isValid = nullptr) const;\n  double toReal(bool* isValid = nullptr) const;\n  bool toBoolean(bool* isValid = nullptr) const;\n  int32_t toSigned(bool* isValid = nullptr) const;\n  uint32_t toUnsigned(bool* isValid = nullptr) const;\n  std::string toLiteral(bool* isValid = nullptr) const;\n\n  template <typename T>\n  inline bool fromValue(T value) {\n    return false;\n  }\n  bool fromVec2i(const zeus::CVector2i& val);\n  bool fromVec2f(const zeus::CVector2f& val);\n  bool fromVec2d(const zeus::CVector2d& val);\n  bool fromVec3f(const zeus::CVector3f& val);\n  bool fromVec3d(const zeus::CVector3d& val);\n  bool fromVec4f(const zeus::CVector4f& val);\n  bool fromVec4d(const zeus::CVector4d& val);\n  bool fromReal(double val);\n  bool fromBoolean(bool val);\n  bool fromInteger(int32_t val);\n  bool fromInteger(uint32_t val);\n  bool fromLiteral(std::string_view val);\n  bool fromLiteralToType(std::string_view val);\n\n  bool isVec2i() const { return m_type == EType::Vec2i; }\n  bool isVec2f() const { return m_type == EType::Vec2f; }\n  bool isVec2d() const { return m_type == EType::Vec2d; }\n  bool isVec3f() const { return m_type == EType::Vec3f; }\n  bool isVec3d() const { return m_type == EType::Vec3d; }\n  bool isVec4f() const { return m_type == EType::Vec4f; }\n  bool isVec4d() const { return m_type == EType::Vec4d; }\n  bool isFloat() const { return m_type == EType::Real; }\n  bool isBoolean() const { return m_type == EType::Boolean; }\n  bool isInteger() const { return m_type == EType::Signed || m_type == EType::Unsigned; }\n  bool isLiteral() const { return m_type == EType::Literal; }\n  bool isModified() const;\n  bool modificationRequiresRestart() const;\n  bool isReadOnly() const;\n  bool isCheat() const;\n  bool isHidden() const;\n  bool isArchive() const;\n  bool isInternalArchivable() const;\n  bool isNoDeveloper() const;\n  bool isColor() const;\n  bool wasDeserialized() const;\n  bool hasDefaultValue() const;\n\n  EType type() const { return m_type; }\n  EFlags flags() const { return (m_unlocked ? m_oldFlags : m_flags); }\n\n  /*!\n   * \\brief Unlocks the CVar for writing if it is ReadOnly.\n   * <b>Handle with care!!!</b> if you use unlock(), make sure\n   * you lock the cvar using lock()\n   * \\see lock\n   */\n  void unlock();\n\n  /*!\n   * \\brief Locks the CVar to prevent writing if it is ReadOnly.\n   * Unlike its partner function unlock, lock is harmless\n   * \\see unlock\n   */\n  void lock();\n\n  void addListener(ListenerFunc func) { m_listeners.push_back(std::move(func)); }\n  void addVariableReference(ICVarValueReference* v) { m_valueReferences.push_back(v); }\n  void removeVariableReference(ICVarValueReference* v) {\n    auto it = std::find(m_valueReferences.begin(), m_valueReferences.end(), v);\n    if (it != m_valueReferences.end()) {\n      m_valueReferences.erase(it);\n    }\n  }\n\n  bool isValidInput(std::string_view input) const;\n\nprivate:\n  CVar(std::string_view name, std::string_view help, EType type) : m_help(help), m_type(type) { m_name = name; }\n  void dispatch();\n  void clearModified();\n  void forceClearModified();\n  void setModified();\n  std::string m_help;\n  EType m_type;\n  std::string m_defaultValue;\n  EFlags m_flags = EFlags::None;\n  EFlags m_oldFlags = EFlags::None;\n  bool m_unlocked = false;\n  bool m_wasDeserialized = false;\n  std::vector<ListenerFunc> m_listeners;\n  std::vector<ICVarValueReference*> m_valueReferences;\n  bool safeToModify(EType type) const;\n  void init(EFlags flags, bool removeColor = true);\n};\n\ntemplate <>\ninline bool CVar::toValue(zeus::CVector2f& value) const {\n  bool isValid = false;\n  value = toVec2f(&isValid);\n  return isValid;\n}\ntemplate <>\ninline bool CVar::toValue(zeus::CVector2d& value) const {\n  bool isValid = false;\n  value = toVec2d(&isValid);\n  return isValid;\n}\ntemplate <>\ninline bool CVar::toValue(zeus::CVector3f& value) const {\n  bool isValid = false;\n  value = toVec3f(&isValid);\n  return isValid;\n}\ntemplate <>\ninline bool CVar::toValue(zeus::CVector3d& value) const {\n  bool isValid = false;\n  value = toVec3d(&isValid);\n  return isValid;\n}\ntemplate <>\ninline bool CVar::toValue(zeus::CVector4f& value) const {\n  bool isValid = false;\n  value = toVec4f(&isValid);\n  return isValid;\n}\ntemplate <>\ninline bool CVar::toValue(zeus::CVector4d& value) const {\n  bool isValid = false;\n  value = toVec4d(&isValid);\n  return isValid;\n}\ntemplate <>\ninline bool CVar::toValue(double& value) const {\n  bool isValid = false;\n  value = toReal(&isValid);\n  return isValid;\n}\ntemplate <>\ninline bool CVar::toValue(float& value) const {\n  bool isValid = false;\n  value = static_cast<float>(toReal(&isValid));\n  return isValid;\n}\ntemplate <>\ninline bool CVar::toValue(bool& value) const {\n  bool isValid = false;\n  value = toBoolean(&isValid);\n  return isValid;\n}\ntemplate <>\ninline bool CVar::toValue(int32_t& value) const {\n  bool isValid = false;\n  value = toSigned(&isValid);\n  return isValid;\n}\ntemplate <>\ninline bool CVar::toValue(uint32_t& value) const {\n  bool isValid = false;\n  value = toUnsigned(&isValid);\n  return isValid;\n}\ntemplate <>\ninline bool CVar::toValue(std::string& value) const {\n  bool isValid = false;\n  value = toLiteral(&isValid);\n  return isValid;\n}\n\ntemplate <>\ninline bool CVar::fromValue(const zeus::CVector2f& val) {\n  return fromVec2f(val);\n}\ntemplate <>\ninline bool CVar::fromValue(const zeus::CVector2d& val) {\n  return fromVec2d(val);\n}\ntemplate <>\ninline bool CVar::fromValue(const zeus::CVector3f& val) {\n  return fromVec3f(val);\n}\ntemplate <>\ninline bool CVar::fromValue(const zeus::CVector3d& val) {\n  return fromVec3d(val);\n}\ntemplate <>\ninline bool CVar::fromValue(const zeus::CVector4f& val) {\n  return fromVec4f(val);\n}\ntemplate <>\ninline bool CVar::fromValue(const zeus::CVector4d& val) {\n  return fromVec4d(val);\n}\ntemplate <>\ninline bool CVar::fromValue(float val) {\n  return fromReal(val);\n}\ntemplate <>\ninline bool CVar::fromValue(double val) {\n  return fromReal(val);\n}\ntemplate <>\ninline bool CVar::fromValue(bool val) {\n  return fromBoolean(val);\n}\ntemplate <>\ninline bool CVar::fromValue(int32_t val) {\n  return fromInteger(val);\n}\ntemplate <>\ninline bool CVar::fromValue(uint32_t val) {\n  return fromInteger(val);\n}\ntemplate <>\ninline bool CVar::fromValue(std::string_view val) {\n  return fromLiteral(val);\n}\n\nclass CVarUnlocker {\n  CVar* m_cvar;\n\npublic:\n  CVarUnlocker(CVar* cvar) : m_cvar(cvar) {\n    if (m_cvar)\n      m_cvar->unlock();\n  }\n  ~CVarUnlocker() {\n    if (m_cvar)\n      m_cvar->lock();\n  }\n};\nclass ICVarValueReference {\nprotected:\n  CVar* m_cvar = nullptr;\n\npublic:\n  ICVarValueReference() = default;\n  explicit ICVarValueReference(CVar* cv) : m_cvar(cv) {\n    if (m_cvar != nullptr) {\n      m_cvar->addVariableReference(this);\n    }\n  }\n  virtual ~ICVarValueReference() {\n    if (m_cvar != nullptr) {\n      m_cvar->removeVariableReference(this);\n    }\n    m_cvar = nullptr;\n  }\n  virtual void updateValue() = 0;\n};\n\ntemplate <typename T>\nclass CVarValueReference : public ICVarValueReference {\n  T* m_valueRef = nullptr;\n\npublic:\n  CVarValueReference() = default;\n  explicit CVarValueReference(T* t, CVar* cv) : ICVarValueReference(cv) {\n    m_valueRef = t;\n    if (m_valueRef && m_cvar) {\n      m_cvar->toValue(*m_valueRef);\n    }\n  }\n\n  void updateValue() override {\n    if (m_valueRef != nullptr && m_cvar->isModified()) {\n      m_cvar->toValue(*m_valueRef);\n    }\n  }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/ConsoleVariables/CVarCommons.cpp",
    "content": "#include \"Runtime/ConsoleVariables/CVarCommons.hpp\"\n\nnamespace metaforce {\nnamespace {\nCVarCommons* m_instance = nullptr;\n}\n\nCVarCommons::CVarCommons(CVarManager& manager) : m_mgr(manager) {\n  m_fullscreen = m_mgr.findOrMakeCVar(\"fullscreen\"sv, \"Start in fullscreen\"sv, false,\n                                      CVar::EFlags::System | CVar::EFlags::Archive);\n  m_allowJoystickInBackground =\n      m_mgr.findOrMakeCVar(\"allowJoystickInBackground\"sv, \"Enable joystick input while window does not have focus\"sv,\n                           true, CVar::EFlags::System | CVar::EFlags::Archive);\n  m_graphicsApi = m_mgr.findOrMakeCVar(\"graphicsApi\"sv, \"API to use for rendering graphics\"sv, DEFAULT_GRAPHICS_API,\n                                       CVar::EFlags::System | CVar::EFlags::Archive | CVar::EFlags::ModifyRestart);\n  m_drawSamples = m_mgr.findOrMakeCVar(\"drawSamples\"sv, \"Number of MSAA samples to use for render targets\"sv, 1,\n                                       CVar::EFlags::System | CVar::EFlags::Archive | CVar::EFlags::ModifyRestart);\n  m_texAnisotropy =\n      m_mgr.findOrMakeCVar(\"texAnisotropy\"sv, \"Number of anisotropic samples to use for sampling textures\"sv, 1,\n                           CVar::EFlags::System | CVar::EFlags::Archive | CVar::EFlags::ModifyRestart);\n  m_deepColor = m_mgr.findOrMakeCVar(\"deepColor\"sv, \"Allow framebuffer with color depth greater-then 24-bits\"sv, false,\n                                     CVar::EFlags::System | CVar::EFlags::Archive | CVar::EFlags::ModifyRestart);\n  m_variableDt = m_mgr.findOrMakeCVar(\"variableDt\", \"Enable variable delta time (experimental)\", false,\n                                      (CVar::EFlags::System | CVar::EFlags::Archive | CVar::EFlags::ModifyRestart));\n  m_windowSize = m_mgr.findOrMakeCVar(\"windowSize\", \"Stores the last known window size\", zeus::CVector2i(1280, 960),\n                                      (CVar::EFlags::System | CVar::EFlags::Archive));\n  m_windowPos = m_mgr.findOrMakeCVar(\"windowPos\", \"Stores the last known window position\", zeus::CVector2i(-1, -1),\n                                     (CVar::EFlags::System | CVar::EFlags::Archive));\n\n  m_debugOverlayPlayerInfo = m_mgr.findOrMakeCVar(\n      \"debugOverlay.playerInfo\"sv, \"Displays information about the player, such as location and orientation\"sv, false,\n      CVar::EFlags::Game | CVar::EFlags::Archive | CVar::EFlags::ReadOnly);\n  m_debugOverlayWorldInfo = m_mgr.findOrMakeCVar(\n      \"debugOverlay.worldInfo\"sv, \"Displays information about the current world, such as world asset ID, and areaId\"sv,\n      false, CVar::EFlags::Game | CVar::EFlags::Archive | CVar::EFlags::ReadOnly);\n  m_debugOverlayAreaInfo = m_mgr.findOrMakeCVar(\n      \"debugOverlay.areaInfo\"sv,\n      \"Displays information about the current area, such as asset ID, object/layer counts, and active layer bits\"sv,\n      false, CVar::EFlags::Game | CVar::EFlags::Archive | CVar::EFlags::ReadOnly);\n  m_debugOverlayLayerInfo =\n      m_mgr.findOrMakeCVar(\"debugOverlay.layerInfo\"sv, \"Displays information about the currently active area layers\"sv,\n                           false, CVar::EFlags::Game | CVar::EFlags::Archive | CVar::EFlags::ReadOnly);\n  m_debugOverlayShowFrameCounter =\n      m_mgr.findOrMakeCVar(\"debugOverlay.showFrameCounter\"sv, \"Displays the current frame index\"sv, false,\n                           CVar::EFlags::Game | CVar::EFlags::Archive | CVar::EFlags::ReadOnly);\n  m_debugOverlayShowFramerate =\n      m_mgr.findOrMakeCVar(\"debugOverlay.showFramerate\"sv, \"Displays the current framerate\"sv, false,\n                           CVar::EFlags::Game | CVar::EFlags::Archive | CVar::EFlags::ReadOnly);\n  m_debugOverlayShowInGameTime =\n      m_mgr.findOrMakeCVar(\"debugOverlay.showInGameTime\"sv, \"Displays the current in game time\"sv, false,\n                           CVar::EFlags::Game | CVar::EFlags::Archive | CVar::EFlags::ReadOnly);\n  m_debugOverlayShowRoomTimer = m_mgr.findOrMakeCVar(\n      \"debugOverlay.showRoomTimer\", \"Displays the current/last room timers in seconds and frames\"sv, false,\n      CVar::EFlags::Game | CVar::EFlags::Archive | CVar::EFlags::ReadOnly);\n  m_debugOverlayShowResourceStats = m_mgr.findOrMakeCVar(\n      \"debugOverlay.showResourceStats\"sv, \"Displays the current live resource object and token counts\"sv, false,\n      CVar::EFlags::Game | CVar::EFlags::Archive | CVar::EFlags::ReadOnly);\n  m_debugOverlayShowRandomStats =\n      m_mgr.findOrMakeCVar(\"debugOverlay.showRandomStats\", \"Displays the current number of random calls per frame\"sv,\n                           false, CVar::EFlags::Game | CVar::EFlags::Archive | CVar::EFlags::ReadOnly);\n  m_debugOverlayPipelineInfo =\n      m_mgr.findOrMakeCVar(\"debugOverlay.pipelineInfo\"sv, \"Displays the current pipeline memory usage per frame\"sv,\n                           false, CVar::EFlags::Game | CVar::EFlags::Archive | CVar::EFlags::ReadOnly);\n  m_debugOverlayDrawCallInfo =\n      m_mgr.findOrMakeCVar(\"debugOverlay.drawCallInfo\"sv, \"Displays the current number of draw calls per frame\"sv,\n                           false, CVar::EFlags::Game | CVar::EFlags::Archive | CVar::EFlags::ReadOnly);\n  m_debugOverlayBufferInfo =\n      m_mgr.findOrMakeCVar(\"debugOverlay.bufferInfo\"sv, \"Displays the current buffer memory usage per frame\"sv, false,\n                           CVar::EFlags::Game | CVar::EFlags::Archive | CVar::EFlags::ReadOnly);\n  m_debugOverlayShowInput = m_mgr.findOrMakeCVar(\"debugOverlay.showInput\"sv, \"Displays controller input\"sv, false,\n                                                 CVar::EFlags::Game | CVar::EFlags::Archive | CVar::EFlags::ReadOnly);\n  m_debugOverlayCorner =\n      m_mgr.findOrMakeCVar(\"debugOverlay.overlayCorner\"sv, \"ImGui debug overlay corner\"sv, 2 /* bottom-left */,\n                           CVar::EFlags::System | CVar::EFlags::Archive | CVar::EFlags::Hidden);\n  m_debugInputOverlayCorner =\n      m_mgr.findOrMakeCVar(\"debugOverlay.inputOverlayCorner\"sv, \"ImGui input overlay corner\"sv, 3 /* bottom-right */,\n                           CVar::EFlags::System | CVar::EFlags::Archive | CVar::EFlags::Hidden);\n\n  m_debugToolDrawAiPath =\n      m_mgr.findOrMakeCVar(\"debugTool.drawAiPath\", \"Draws the selected paths of any AI in the room\"sv, false,\n                           CVar::EFlags::Game | CVar::EFlags::Archive | CVar::EFlags::ReadOnly);\n  m_debugToolDrawLighting = m_mgr.findOrMakeCVar(\"debugTool.drawLighting\", \"Draws the lighting setup in a room\"sv,\n                                                 false, CVar::EFlags::Game | CVar::EFlags::ReadOnly);\n  m_debugToolDrawCollisionActors =\n      m_mgr.findOrMakeCVar(\"debugTool.drawCollisionActors\", \"Draws the collision actors for enemies and objects\"sv,\n                           false, CVar::EFlags::Game | CVar::EFlags::ReadOnly);\n  m_debugToolDrawMazePath = m_mgr.findOrMakeCVar(\"debugTool.drawMazePath\", \"Draws the maze path in Dynamo\"sv, false,\n                                                 CVar::EFlags::Game | CVar::EFlags::Archive | CVar::EFlags::ReadOnly);\n  m_debugToolDrawPlatformCollision =\n      m_mgr.findOrMakeCVar(\"debugTool.drawPlatformCollision\", \"Draws the bounding boxes of platforms\"sv, false,\n                           CVar::EFlags::Game | CVar::EFlags::Archive | CVar::EFlags::ReadOnly);\n  m_debugToolMangleMipmaps = m_mgr.findOrMakeCVar(\n      \"debugTool.mangleMipmaps\", \"Sets each mipmap of a texture to a known color based on distance.\"sv, false,\n      CVar::EFlags::Game | CVar::EFlags::Archive | CVar::EFlags::ReadOnly | CVar::EFlags::ModifyRestart);\n  m_logFile = m_mgr.findOrMakeCVar(\"logFile\"sv, \"Any log prints will be stored to this file upon exit\"sv, \"app.log\"sv,\n                                   CVar::EFlags::System | CVar::EFlags::Archive | CVar::EFlags::ModifyRestart);\n  m_lastDiscPath = m_mgr.findOrMakeCVar(\"lastDiscPath\"sv, \"Most recently loaded disc image path\"sv, \"\"sv,\n                                        CVar::EFlags::System | CVar::EFlags::Archive | CVar::EFlags::Hidden);\n  m_instance = this;\n}\n\nCVarCommons* CVarCommons::instance() { return m_instance; }\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/ConsoleVariables/CVarCommons.hpp",
    "content": "#pragma once\n\n#include <algorithm>\n#include <cstdint>\n#include <string>\n\n#include \"Runtime/ConsoleVariables/CVarManager.hpp\"\n\n#undef min\n#undef max\n\nnamespace metaforce {\n\nusing namespace std::literals;\n\n#if defined(__APPLE__)\n#define DEFAULT_GRAPHICS_API \"Metal\"sv\n#else\n#define DEFAULT_GRAPHICS_API \"Vulkan\"sv\n#endif\n\nstruct CVarCommons {\n  CVarManager& m_mgr;\n  CVar* m_fullscreen = nullptr;\n  CVar* m_allowJoystickInBackground = nullptr;\n  CVar* m_graphicsApi = nullptr;\n  CVar* m_drawSamples = nullptr;\n  CVar* m_texAnisotropy = nullptr;\n  CVar* m_deepColor = nullptr;\n  CVar* m_variableDt = nullptr;\n  CVar* m_windowSize = nullptr;\n  CVar* m_windowPos = nullptr;\n\n  CVar* m_debugOverlayPlayerInfo = nullptr;\n  CVar* m_debugOverlayWorldInfo = nullptr;\n  CVar* m_debugOverlayAreaInfo = nullptr;\n  CVar* m_debugOverlayLayerInfo = nullptr;\n  CVar* m_debugOverlayShowFrameCounter = nullptr;\n  CVar* m_debugOverlayShowFramerate = nullptr;\n  CVar* m_debugOverlayShowInGameTime = nullptr;\n  CVar* m_debugOverlayShowResourceStats = nullptr;\n  CVar* m_debugOverlayShowRandomStats = nullptr;\n  CVar* m_debugOverlayShowRoomTimer = nullptr;\n  CVar* m_debugOverlayPipelineInfo = nullptr;\n  CVar* m_debugOverlayDrawCallInfo = nullptr;\n  CVar* m_debugOverlayBufferInfo = nullptr;\n  CVar* m_debugOverlayShowInput = nullptr;\n  CVar* m_debugOverlayCorner = nullptr;\n  CVar* m_debugInputOverlayCorner = nullptr;\n  CVar* m_debugToolDrawAiPath = nullptr;\n  CVar* m_debugToolDrawLighting = nullptr;\n  CVar* m_debugToolDrawCollisionActors = nullptr;\n  CVar* m_debugToolDrawMazePath = nullptr;\n  CVar* m_debugToolDrawPlatformCollision = nullptr;\n  CVar* m_debugToolMangleMipmaps = nullptr;\n  CVar* m_logFile = nullptr;\n  CVar* m_lastDiscPath = nullptr;\n\n  CVarCommons(CVarManager& manager);\n\n  bool getFullscreen() const { return m_fullscreen->toBoolean(); }\n  bool getAllowJoystickInBackground() const { return m_allowJoystickInBackground->toBoolean(); }\n\n  void setFullscreen(bool b) { m_fullscreen->fromBoolean(b); }\n\n  std::string getGraphicsApi() const { return m_graphicsApi->toLiteral(); }\n\n  void setGraphicsApi(std::string_view api) { m_graphicsApi->fromLiteral(api); }\n\n  uint32_t getSamples() const { return std::max(1u, m_drawSamples->toUnsigned()); }\n\n  void setSamples(uint32_t v) { m_drawSamples->fromInteger(std::max(uint32_t(1), v)); }\n\n  uint32_t getAnisotropy() const { return std::max(1u, uint32_t(m_texAnisotropy->toUnsigned())); }\n  zeus::CVector2i getWindowSize() const { return m_windowSize->toVec2i(); }\n  zeus::CVector2i getWindowPos() const { return m_windowPos->toVec2i(); }\n\n  void setAnisotropy(uint32_t v) { m_texAnisotropy->fromInteger(std::max(1u, v)); }\n\n  bool getDeepColor() const { return m_deepColor->toBoolean(); }\n\n  void setDeepColor(bool b) { m_deepColor->fromBoolean(b); }\n\n  bool getVariableFrameTime() const { return m_variableDt->toBoolean(); }\n\n  void setVariableFrameTime(bool b) { m_variableDt->fromBoolean(b); }\n\n  std::string getLogFile() const { return m_logFile->toLiteral(); };\n\n  void setLogFile(std::string_view log) { m_logFile->fromLiteral(log); }\n\n  bool getMangleMipmaps() const { return m_debugToolMangleMipmaps->toBoolean(); }\n  void setMangleMipmaps(bool b) { m_debugToolMangleMipmaps->fromBoolean(b); }\n\n  void serialize() { m_mgr.serialize(); }\n\n  static CVarCommons* instance();\n};\n\n} // namespace hecl\n"
  },
  {
    "path": "Runtime/ConsoleVariables/CVarManager.cpp",
    "content": "#include \"Runtime/ConsoleVariables/CVarManager.hpp\"\n\n#include \"Runtime/ConsoleVariables/FileStoreManager.hpp\"\n#include \"Runtime/CBasics.hpp\"\n#include \"Runtime/Streams/CTextInStream.hpp\"\n#include \"Runtime/Streams/CTextOutStream.hpp\"\n#include \"Runtime/Streams/CMemoryInStream.hpp\"\n#include \"Runtime/Streams/CMemoryStreamOut.hpp\"\n#include \"Runtime/CStringExtras.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\n#include <algorithm>\n#include <memory>\n#include <regex>\n\n#if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG)\n#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)\n#endif\n\n#if !defined(S_ISDIR) && defined(S_IFMT) && defined(S_IFDIR)\n#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)\n#endif\n\nnamespace metaforce {\n\nCVar* com_developer = nullptr;\nCVar* com_configfile = nullptr;\nCVar* com_enableCheats = nullptr;\nCVar* com_cubemaps = nullptr;\n\nstatic const std::regex cmdLineRegexEnable(R\"(^\\+([\\w\\.]+)([=])?([\\/\\\\\\s\\w\\.\\-]+)?)\");\nstatic const std::regex cmdLineRegexDisable(R\"(^\\-([\\w\\.]+)([=])?([\\/\\\\\\s\\w\\.\\-]+)?)\");\n\nCVarManager* CVarManager::m_instance = nullptr;\n\nCVarManager::CVarManager(FileStoreManager& store, bool useBinary) : m_store(store), m_useBinary(useBinary) {\n  m_instance = this;\n  com_configfile =\n      newCVar(\"config\", \"File to store configuration\", std::string(\"config\"),\n              CVar::EFlags::System | CVar::EFlags::ReadOnly | CVar::EFlags::NoDeveloper | CVar::EFlags::Hidden);\n  com_developer = newCVar(\"developer\", \"Enables developer mode\", false,\n                          (CVar::EFlags::System | CVar::EFlags::ReadOnly | CVar::EFlags::InternalArchivable));\n  com_enableCheats = newCVar(\n      \"cheats\", \"Enable cheats\", false,\n      (CVar::EFlags::System | CVar::EFlags::ReadOnly | CVar::EFlags::Hidden | CVar::EFlags::InternalArchivable));\n  com_cubemaps = newCVar(\"cubemaps\", \"Enable cubemaps\", false,\n                         (CVar::EFlags::Game | CVar::EFlags::ReadOnly | CVar::EFlags::InternalArchivable));\n}\n\nCVarManager::~CVarManager() {}\n\nCVar* CVarManager::registerCVar(std::unique_ptr<CVar>&& cvar) {\n  std::string tmp(cvar->name());\n  CStringExtras::ToLower(tmp);\n\n  if (m_cvars.find(tmp) != m_cvars.end()) {\n    return nullptr;\n  }\n\n  CVar* ret = cvar.get();\n  m_cvars.insert_or_assign(std::move(tmp), std::move(cvar));\n  return ret;\n}\n\nCVar* CVarManager::findCVar(std::string_view name) {\n  std::string lower(name);\n  CStringExtras::ToLower(lower);\n  auto search = m_cvars.find(lower);\n  if (search == m_cvars.end())\n    return nullptr;\n\n  return search->second.get();\n}\n\nstd::vector<CVar*> CVarManager::archivedCVars() const {\n  std::vector<CVar*> ret;\n  for (const auto& pair : m_cvars)\n    if (pair.second->isArchive())\n      ret.push_back(pair.second.get());\n\n  return ret;\n}\n\nstd::vector<CVar*> CVarManager::cvars(CVar::EFlags filter) const {\n  std::vector<CVar*> ret;\n  for (const auto& pair : m_cvars)\n    if (filter == CVar::EFlags::Any || True(pair.second->flags() & filter))\n      ret.push_back(pair.second.get());\n\n  return ret;\n}\n\nvoid CVarManager::deserialize(CVar* cvar) {\n  /* Make sure we're not trying to deserialize a CVar that is invalid*/\n  if (!cvar) {\n    return;\n  }\n\n  /* First let's check for a deferred value */\n  std::string lowName = cvar->name().data();\n  CStringExtras::ToLower(lowName);\n  if (const auto iter = m_deferedCVars.find(lowName); iter != m_deferedCVars.end()) {\n    std::string val = std::move(iter->second);\n    m_deferedCVars.erase(lowName);\n    if (cvar->isBoolean() && val.empty()) {\n      // We were deferred without a value, assume true\n      cvar->fromBoolean(true);\n      cvar->m_wasDeserialized = true;\n      return;\n    }\n    if (!val.empty() && cvar->fromLiteralToType(val)) {\n      cvar->m_wasDeserialized = true;\n      return;\n    }\n  }\n\n  /* Enforce isArchive and isInternalArchivable now that we've checked if it's been deferred */\n  if (!cvar->isArchive() && !cvar->isInternalArchivable()) {\n    return;\n  }\n  /* We were either unable to find a deferred value or got an invalid value */\n  std::string filename = std::string(m_store.getStoreRoot()) + '/' + com_configfile->toLiteral() + \".yaml\";\n  auto container = loadCVars(filename);\n  auto serialized =\n      std::find_if(container.cbegin(), container.cend(), [&cvar](const auto& c) { return c.m_name == cvar->name(); });\n  if (serialized != container.cend()) {\n    if (cvar->m_value != serialized->m_value) {\n      {\n        CVarUnlocker lc(cvar);\n        cvar->fromLiteralToType(serialized->m_value);\n        cvar->m_wasDeserialized = true;\n      }\n      if (cvar->modificationRequiresRestart()) {\n        cvar->dispatch();\n        cvar->forceClearModified();\n      }\n    }\n  }\n}\n\nvoid CVarManager::serialize() {\n  std::string filename = std::string(m_store.getStoreRoot()) + '/' + com_configfile->toLiteral() + \".yaml\";\n\n  /* If we have an existing config load it in, so we can update it */\n  auto container = loadCVars(filename);\n\n  u32 minLength = 0;\n  bool write = false;\n  for (const auto& pair : m_cvars) {\n    const auto& cvar = pair.second;\n\n    if (cvar->isArchive() || (cvar->isInternalArchivable() && cvar->wasDeserialized() && !cvar->hasDefaultValue())) {\n      write = true;\n      /* Look for an existing CVar in the file... */\n      auto serialized =\n          std::find_if(container.begin(), container.end(), [&cvar](const auto& c) { return c.m_name == cvar->name(); });\n      if (serialized != container.end()) {\n        /* Found it! Update the value */\n        serialized->m_value = cvar->value();\n      } else {\n        /* Store this value as a new CVar in the config */\n        container.emplace_back(StoreCVar::CVar{std::string(cvar->name()), cvar->value()});\n      }\n    }\n  }\n  /* Compute length needed for all cvars */\n  std::for_each(container.cbegin(), container.cend(),\n                [&minLength](const auto& cvar) { minLength += cvar.m_name.length() + cvar.m_value.length() + 2; });\n  /* Only write the CVars if any have been modified */\n  if (!write) {\n    return;\n  }\n\n  // Allocate enough space to write all the strings with some space to spare\n  const auto requiredLen = minLength + (4 * container.size());\n  std::unique_ptr<u8[]> workBuf(new u8[requiredLen]);\n  CMemoryStreamOut memOut(workBuf.get(), requiredLen, CMemoryStreamOut::EOwnerShip::NotOwned, 32);\n  CTextOutStream textOut(memOut);\n  for (const auto& cvar : container) {\n    auto str = fmt::format(\"{}: {}\", cvar.m_name, cvar.m_value);\n    textOut.WriteString(str);\n  }\n\n  auto* file = fopen(filename.c_str(),\n#ifdef _MSC_VER\n                     \"wb\"\n#else\n                     \"wbe\"\n#endif\n  );\n  if (file != nullptr) {\n    u32 writeLen = memOut.GetWritePosition();\n    u32 offset = 0;\n    while (offset < writeLen) {\n      offset += fwrite(workBuf.get() + offset, 1, writeLen - offset, file);\n    }\n    fflush(file);\n  }\n  fclose(file);\n}\n\nstd::vector<StoreCVar::CVar> CVarManager::loadCVars(const std::string& filename) const {\n  std::vector<StoreCVar::CVar> ret;\n  CBasics::Sstat st;\n  if (CBasics::Stat(filename.c_str(), &st) == 0 && S_ISREG(st.st_mode)) {\n\n    auto* file = fopen(filename.c_str(),\n#ifdef _MSC_VER\n                       \"rb\"\n#else\n                       \"rbe\"\n#endif\n    );\n    if (file != nullptr) {\n\n      std::unique_ptr<u8> inBuf(new u8[st.st_size]);\n      fread(inBuf.get(), 1, st.st_size, file);\n      fclose(file);\n      CMemoryInStream mem(inBuf.get(), st.st_size, CMemoryInStream::EOwnerShip::NotOwned);\n      CTextInStream textIn(mem, st.st_size);\n      while (!textIn.IsEOF()) {\n        auto cvString = textIn.GetNextLine();\n        if (cvString.empty()) {\n          continue;\n        }\n        auto parts = CStringExtras::Split(cvString, ':');\n        if (parts.size() < 2) {\n          continue;\n        }\n        const auto key = CStringExtras::Trim(parts[0]);\n        auto value = parts[1];\n        for (size_t i = 2; i < parts.size(); ++i) {\n          value += \":\";\n          value += parts[i];\n        }\n        value = CStringExtras::Trim(value);\n        ret.emplace_back(StoreCVar::CVar{key, value});\n      }\n    }\n  }\n  return ret;\n}\n\nCVarManager* CVarManager::instance() { return m_instance; }\n\nvoid CVarManager::setDeveloperMode(bool v, bool setDeserialized) {\n  com_developer->unlock();\n  com_developer->fromBoolean(v);\n  if (setDeserialized)\n    com_developer->m_wasDeserialized = true;\n  com_developer->lock();\n  com_developer->setModified();\n}\n\nvoid CVarManager::setCheatsEnabled(bool v, bool setDeserialized) {\n  com_enableCheats->unlock();\n  com_enableCheats->fromBoolean(v);\n  if (setDeserialized)\n    com_enableCheats->m_wasDeserialized = true;\n  com_enableCheats->lock();\n  com_enableCheats->setModified();\n}\n\nbool CVarManager::restartRequired() const {\n  return std::any_of(m_cvars.cbegin(), m_cvars.cend(), [](const auto& entry) {\n    return entry.second->isModified() && entry.second->modificationRequiresRestart();\n  });\n}\n\nvoid CVarManager::parseCommandLine(const std::vector<std::string>& args) {\n  bool oldDeveloper = suppressDeveloper();\n  std::string developerName(com_developer->name());\n  CStringExtras::ToLower(developerName);\n  for (const std::string& arg : args) {\n    if (arg[0] != '+' && arg[0] != '-') {\n      continue;\n    }\n\n    std::smatch matches;\n    std::string cvarName;\n    std::string cvarValue;\n    bool set = false;\n    if (std::regex_match(arg, matches, cmdLineRegexEnable)) {\n      std::vector<std::string> realMatches;\n      for (auto match : matches) {\n        if (match.matched) {\n          realMatches.push_back(match);\n        }\n      }\n      if (realMatches.size() == 2) {\n        cvarName = matches[1].str();\n      } else if (realMatches.size() == 4) {\n        cvarName = matches[1].str();\n        cvarValue = matches[3].str();\n      }\n      set = true;\n    } else if (std::regex_match(arg, matches, cmdLineRegexDisable)) {\n      std::vector<std::string> realMatches;\n      for (auto match : matches) {\n        if (match.matched) {\n          realMatches.push_back(match);\n        }\n      }\n      if (realMatches.size() == 2) {\n        cvarName = matches[1].str();\n      } else if (realMatches.size() == 4) {\n        cvarName = matches[1].str();\n        cvarValue = matches[3].str();\n      }\n      set = false;\n    }\n\n    if (CVar* cv = findCVar(cvarName)) {\n      if (cvarValue.empty() && cv->isBoolean()) {\n        // We were set from the command line with an empty value, assume true\n        cv->fromBoolean(set);\n      } else if (!cvarValue.empty()) {\n        cv->fromLiteralToType(cvarValue);\n      }\n      cv->m_wasDeserialized = true;\n      cv->forceClearModified();\n      CStringExtras::ToLower(cvarName);\n      if (developerName == cvarName) {\n        /* Make sure we're not overriding developer mode when we restore */\n        oldDeveloper = com_developer->toBoolean();\n      }\n    } else {\n      /* Unable to find an existing CVar, let's defer for the time being 8 */\n      CStringExtras::ToLower(cvarName);\n      if (cvarValue.empty()) {\n        cvarValue = set ? \"true\" : \"false\";\n      }\n      m_deferedCVars.insert(std::make_pair<std::string, std::string>(std::move(cvarName), std::move(cvarValue)));\n    }\n  }\n\n  restoreDeveloper(oldDeveloper);\n}\n\nbool CVarManager::suppressDeveloper() {\n  bool oldDeveloper = com_developer->toBoolean();\n  CVarUnlocker unlock(com_developer);\n  com_developer->fromBoolean(true);\n\n  return oldDeveloper;\n}\n\nvoid CVarManager::restoreDeveloper(bool oldDeveloper) {\n  CVarUnlocker unlock(com_developer);\n  com_developer->fromBoolean(oldDeveloper);\n}\nvoid CVarManager::proc() {\n  for (const auto& [name, cvar] : m_cvars) {\n    if (cvar->isModified()) {\n      cvar->dispatch();\n    }\n    if (cvar->isModified() && !cvar->modificationRequiresRestart()) {\n      // Clear the modified flag now that we've informed everyone we've changed\n      cvar->clearModified();\n    }\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/ConsoleVariables/CVarManager.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <map>\n#include <vector>\n\n#include \"Runtime/ConsoleVariables/CVar.hpp\"\n\nnamespace metaforce {\nclass FileStoreManager;\nextern CVar* com_developer;\nextern CVar* com_configfile;\nextern CVar* com_enableCheats;\nextern CVar* com_cubemaps;\nclass CVarManager final {\n  using CVarContainer = StoreCVar::CVarContainer;\n  template <typename T>\n  CVar* _newCVar(std::string_view name, std::string_view help, const T& value, CVar::EFlags flags) {\n    if (CVar* ret = registerCVar(std::make_unique<CVar>(name, value, help, flags))) {\n      deserialize(ret);\n      return ret;\n    }\n    return nullptr;\n  }\n\n  FileStoreManager& m_store;\n  bool m_useBinary;\n  static CVarManager* m_instance;\n\npublic:\n  CVarManager() = delete;\n  CVarManager(const CVarManager&) = delete;\n  CVarManager& operator=(const CVarManager&) = delete;\n  CVarManager& operator=(const CVarManager&&) = delete;\n  CVarManager(FileStoreManager& store, bool useBinary = false);\n  ~CVarManager();\n\n  CVar* newCVar(std::string_view name, std::string_view help, const  zeus::CVector2i& value, CVar::EFlags flags) {\n    return _newCVar<const zeus::CVector2i>(name, help, value, flags);\n  }\n\n  CVar* newCVar(std::string_view name, std::string_view help, const  zeus::CVector2f& value, CVar::EFlags flags) {\n    return _newCVar<const zeus::CVector2f>(name, help, value, flags);\n  }\n  CVar* newCVar(std::string_view name, std::string_view help, const  zeus::CVector2d& value, CVar::EFlags flags) {\n    return _newCVar<const zeus::CVector2d>(name, help, value, flags);\n  }\n  CVar* newCVar(std::string_view name, std::string_view help, const  zeus::CVector3f& value, CVar::EFlags flags) {\n    return _newCVar<const zeus::CVector3f>(name, help, value, flags);\n  }\n  CVar* newCVar(std::string_view name, std::string_view help, const  zeus::CVector3d& value, CVar::EFlags flags) {\n    return _newCVar<const zeus::CVector3d>(name, help, value, flags);\n  }\n  CVar* newCVar(std::string_view name, std::string_view help, const  zeus::CVector4f& value, CVar::EFlags flags) {\n    return _newCVar<const zeus::CVector4f>(name, help, value, flags);\n  }\n  CVar* newCVar(std::string_view name, std::string_view help, const  zeus::CVector4d& value, CVar::EFlags flags) {\n    return _newCVar<const zeus::CVector4d>(name, help, value, flags);\n  }\n  CVar* newCVar(std::string_view name, std::string_view help, std::string_view value, CVar::EFlags flags) {\n    return _newCVar<std::string_view>(name, help, value, flags);\n  }\n  CVar* newCVar(std::string_view name, std::string_view help, bool value, CVar::EFlags flags) {\n    return _newCVar<bool>(name, help, value, flags);\n  }\n  // Float and double are internally identical, all floating point values are stored as `double`\n  CVar* newCVar(std::string_view name, std::string_view help, float value, CVar::EFlags flags) {\n    return _newCVar<double>(name, help, static_cast<double>(value), flags);\n  }\n  CVar* newCVar(std::string_view name, std::string_view help, double value, CVar::EFlags flags) {\n    return _newCVar<double>(name, help, value, flags);\n  }\n  // Integer CVars can be seamlessly converted between either type, the distinction is to make usage absolutely clear\n  CVar* newCVar(std::string_view name, std::string_view help, int32_t value, CVar::EFlags flags) {\n    return _newCVar<int32_t>(name, help, value, flags);\n  }\n\n  CVar* newCVar(std::string_view name, std::string_view help, uint32_t value, CVar::EFlags flags) {\n    return _newCVar<uint32_t>(name, help, value, flags);\n  }\n\n  CVar* registerCVar(std::unique_ptr<CVar>&& cvar);\n\n  CVar* findCVar(std::string_view name);\n  template <class... _Args>\n  CVar* findOrMakeCVar(std::string_view name, _Args&&... args) {\n    if (CVar* cv = findCVar(name))\n      return cv;\n    return newCVar(name, std::forward<_Args>(args)...);\n  }\n\n  std::vector<CVar*> archivedCVars() const;\n  std::vector<CVar*> cvars(CVar::EFlags filter = CVar::EFlags::Any) const;\n\n  void deserialize(CVar* cvar);\n  void serialize();\n\n  static CVarManager* instance();\n\n  void proc();\n\n  void setDeveloperMode(bool v, bool setDeserialized = false);\n  void setCheatsEnabled(bool v, bool setDeserialized = false);\n  bool restartRequired() const;\n\n  void parseCommandLine(const std::vector<std::string>& args);\n\n  FileStoreManager& fileStoreManager() { return m_store; }\n\nprivate:\n  bool suppressDeveloper();\n  void restoreDeveloper(bool oldDeveloper);\n\n  std::unordered_map<std::string, std::unique_ptr<CVar>> m_cvars;\n  std::map<std::string, std::string> m_deferedCVars;\n  std::vector<StoreCVar::CVar> loadCVars(const std::string& filename) const;\n};\n\n} // namespace hecl\n"
  },
  {
    "path": "Runtime/ConsoleVariables/FileStoreManager.cpp",
    "content": "#include \"Runtime/ConsoleVariables/FileStoreManager.hpp\"\n\n#include \"Runtime/CBasics.hpp\"\n#include \"Runtime/Logging.hpp\"\n\n#include <SDL3/SDL.h>\n#if _WIN32\n#include <nowide/convert.hpp>\n#endif\n\n#if _WIN32\n#include <ShlObj.h>\n#endif\n\n#if WINDOWS_STORE\nusing namespace Windows::Storage;\n#endif\n\nnamespace metaforce {\nnamespace {\nFileStoreManager* g_instance = nullptr;\n}\n\nFileStoreManager::FileStoreManager(std::string_view org, std::string_view domain) : m_org(org), m_domain(domain) {\n  if (g_instance != nullptr) {\n    spdlog::fatal(\"Attempting to build another FileStoreManager!!\");\n  }\n\n  auto prefPath = SDL_GetPrefPath(org.data(), domain.data());\n  if (prefPath == nullptr) {\n#if _WIN32\n#if !WINDOWS_STORE\n    WCHAR home[MAX_PATH];\n    if (!SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_PROFILE, NULL, 0, home)))\n      spdlog::fatal(\"unable to locate profile for file store\");\n\n    std::string path = nowide::narrow(home);\n#else\n    StorageFolder ^ cacheFolder = ApplicationData::Current->LocalCacheFolder;\n    std::string path(cacheFolder->Path->Data());\n#endif\n    path += \"/.\" + m_org;\n\n    CBasics::MakeDir(path.c_str());\n    path += '/';\n    path += domain.data();\n\n    CBasics::MakeDir(path.c_str());\n    m_storeRoot = path;\n#else\n    const char* xdg_data_home = getenv(\"XDG_DATA_HOME\");\n    std::string path;\n    if (xdg_data_home) {\n      if (xdg_data_home[0] != '/')\n        spdlog::fatal(\"invalid $XDG_DATA_HOME for file store (must be absolute)\");\n      path = xdg_data_home;\n    } else {\n      const char* home = getenv(\"HOME\");\n      if (!home)\n        spdlog::fatal(\"unable to locate $HOME for file store\");\n      path = home;\n      path += \"/.local/share\";\n    }\n    path += \"/\" + m_org + \"/\" + domain.data();\n    if (CBasics::RecursiveMakeDir(path.c_str()) != 0) {\n      spdlog::fatal(\"unable to mkdir at {}\", path);\n    }\n    m_storeRoot = path;\n#endif\n  } else {\n    m_storeRoot = std::string(prefPath);\n    SDL_free(prefPath);\n  }\n  g_instance = this;\n}\n\nFileStoreManager* FileStoreManager::instance() {\n  if (g_instance == nullptr) {\n    spdlog::fatal(\"Requested FileStoreManager instance before it's built!\");\n  }\n  return g_instance;\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/ConsoleVariables/FileStoreManager.hpp",
    "content": "#pragma once\n#include <string>\n#include <string_view>\n\nnamespace metaforce {\n/**\n * @brief Per-platform file store resolution\n */\nclass FileStoreManager {\n  std::string m_org;\n  std::string m_domain;\n  std::string m_storeRoot;\n\npublic:\n  FileStoreManager(FileStoreManager&) = delete;\n  FileStoreManager(FileStoreManager&&) = delete;\n  void operator=(FileStoreManager&) = delete;\n  void operator=(FileStoreManager&&) = delete;\n  FileStoreManager(std::string_view org, std::string_view domain);\n  std::string_view getOrg() const { return m_org; }\n  std::string_view getDomain() const { return m_domain; }\n  /**\n   * @brief Returns the full path to the file store, including domain\n   * @return Full path to store e.g /home/foo/.hecl/bar\n   */\n  std::string_view getStoreRoot() const { return m_storeRoot; }\n  static FileStoreManager* instance();\n};\n}"
  },
  {
    "path": "Runtime/Flags.hpp",
    "content": "#pragma once\n\n#include <type_traits>\n\nnamespace metaforce {\ntemplate <typename BitType>\nclass Flags {\npublic:\n  using MaskType = std::underlying_type_t<BitType>;\n\n  // constructors\n  constexpr Flags() noexcept : m_mask(0) {}\n\n  constexpr Flags(BitType bit) noexcept : m_mask(static_cast<MaskType>(bit)) {}\n\n  constexpr Flags(Flags<BitType> const& rhs) noexcept : m_mask(rhs.m_mask) {}\n\n  constexpr explicit Flags(MaskType flags) noexcept : m_mask(flags) {}\n\n  [[nodiscard]] constexpr bool IsSet(Flags<BitType> const bit) const noexcept { return bool(*this & bit); }\n\n  // relational operators\n  bool operator==(Flags<BitType> const& rhs) const noexcept { return m_mask == rhs.m_mask; }\n\n  // logical operator\n  constexpr bool operator!() const noexcept { return !m_mask; }\n\n  // bitwise operators\n  constexpr Flags<BitType> operator&(Flags<BitType> const& rhs) const noexcept {\n    return Flags<BitType>(m_mask & rhs.m_mask);\n  }\n\n  constexpr Flags<BitType> operator|(Flags<BitType> const& rhs) const noexcept {\n    return Flags<BitType>(m_mask | rhs.m_mask);\n  }\n\n  constexpr Flags<BitType> operator^(Flags<BitType> const& rhs) const noexcept {\n    return Flags<BitType>(m_mask ^ rhs.m_mask);\n  }\n\n  // assignment operators\n  constexpr Flags<BitType>& operator=(Flags<BitType> const& rhs) noexcept {\n    m_mask = rhs.m_mask;\n    return *this;\n  }\n\n  constexpr Flags<BitType>& operator|=(Flags<BitType> const& rhs) noexcept {\n    m_mask |= rhs.m_mask;\n    return *this;\n  }\n\n  constexpr Flags<BitType>& operator&=(Flags<BitType> const& rhs) noexcept {\n    m_mask &= rhs.m_mask;\n    return *this;\n  }\n\n  constexpr Flags<BitType>& operator^=(Flags<BitType> const& rhs) noexcept {\n    m_mask ^= rhs.m_mask;\n    return *this;\n  }\n\n  // cast operators\n  explicit constexpr operator bool() const noexcept { return m_mask != 0; }\n\n  explicit constexpr operator MaskType() const noexcept { return m_mask; }\n\nprivate:\n  MaskType m_mask;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Formatting.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n\n#include <fmt/format.h> // IWYU pragma: export\n#include <fmt/xchar.h>\n\n#include <zeus/CMatrix3f.hpp>\n#include <zeus/CMatrix4f.hpp>\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector2f.hpp>\n#include <zeus/CVector3f.hpp>\n\n#define FMT_CUSTOM_FORMATTER(Type, str, ...)                                                                           \\\n  template <typename CharT>                                                                                            \\\n  struct fmt::formatter<Type, CharT> : fmt::formatter<std::basic_string_view<CharT>, CharT> {                          \\\n    template <typename FormatContext>                                                                                  \\\n    auto format(const Type& obj, FormatContext& ctx) const -> decltype(ctx.out()) {                                    \\\n      if constexpr (std::is_same_v<CharT, char>) {                                                                     \\\n        return fmt::format_to(ctx.out(), str __VA_OPT__(, ) __VA_ARGS__);                                              \\\n      } else if constexpr (std::is_same_v<CharT, char8_t>) {                                                           \\\n        return fmt::format_to(ctx.out(), u8##str __VA_OPT__(, ) __VA_ARGS__);                                          \\\n      } else if constexpr (std::is_same_v<CharT, char16_t>) {                                                          \\\n        return fmt::format_to(ctx.out(), u##str __VA_OPT__(, ) __VA_ARGS__);                                           \\\n      } else if constexpr (std::is_same_v<CharT, char32_t>) {                                                          \\\n        return fmt::format_to(ctx.out(), U##str __VA_OPT__(, ) __VA_ARGS__);                                           \\\n      } else if constexpr (std::is_same_v<CharT, wchar_t>) {                                                           \\\n        return fmt::format_to(ctx.out(), L##str __VA_OPT__(, ) __VA_ARGS__);                                           \\\n      } else {                                                                                                         \\\n        static_assert(!sizeof(CharT), \"Unsupported character type for formatter\");                                     \\\n      }                                                                                                                \\\n    }                                                                                                                  \\\n  }\n\nFMT_CUSTOM_FORMATTER(metaforce::CAssetId, \"{:08X}\", obj.Value());\nFMT_CUSTOM_FORMATTER(metaforce::FourCC, \"{:c}{:c}{:c}{:c}\", obj.getChars()[0], obj.getChars()[1], obj.getChars()[2],\n                     obj.getChars()[3]);\nFMT_CUSTOM_FORMATTER(metaforce::SObjectTag, \"{} {}\", obj.type, obj.id);\nFMT_CUSTOM_FORMATTER(metaforce::TEditorId, \"{:08X}\", obj.id);\nstatic_assert(sizeof(metaforce::kUniqueIdType) == sizeof(u16),\n              \"TUniqueId size does not match expected size! Update TUniqueId format string!\");\nFMT_CUSTOM_FORMATTER(metaforce::TUniqueId, \"{:04X}\", obj.id);\n\nFMT_CUSTOM_FORMATTER(zeus::CVector3f, \"({} {} {})\", obj.x(), obj.y(), obj.z());\nFMT_CUSTOM_FORMATTER(zeus::CVector2f, \"({} {})\", obj.x(), obj.y());\nFMT_CUSTOM_FORMATTER(zeus::CMatrix3f,\n                     \"\\n({} {} {})\"\n                     \"\\n({} {} {})\"\n                     \"\\n({} {} {})\",\n                     obj[0][0], obj[1][0], obj[2][0], obj[0][1], obj[1][1], obj[2][1], obj[0][2], obj[1][2], obj[2][2]);\nFMT_CUSTOM_FORMATTER(zeus::CMatrix4f,\n                     \"\\n({} {} {} {})\"\n                     \"\\n({} {} {} {})\"\n                     \"\\n({} {} {} {})\"\n                     \"\\n({} {} {} {})\",\n                     obj[0][0], obj[1][0], obj[2][0], obj[3][0], obj[0][1], obj[1][1], obj[2][1], obj[3][1], obj[0][2],\n                     obj[1][2], obj[2][2], obj[3][2], obj[0][3], obj[1][3], obj[2][3], obj[3][3]);\nFMT_CUSTOM_FORMATTER(zeus::CTransform,\n                     \"\\n({} {} {} {})\"\n                     \"\\n({} {} {} {})\"\n                     \"\\n({} {} {} {})\",\n                     obj.basis[0][0], obj.basis[1][0], obj.basis[2][0], obj.origin[0], obj.basis[0][1], obj.basis[1][1],\n                     obj.basis[2][1], obj.origin[1], obj.basis[0][2], obj.basis[1][2], obj.basis[2][2], obj.origin[2]);"
  },
  {
    "path": "Runtime/GCNTypes.hpp",
    "content": "#pragma once\n\n#include <cstdint>\n#include <cstdlib>\n\nusing s8 = int8_t;\nusing u8 = uint8_t;\nusing s16 = int16_t;\nusing u16 = uint16_t;\nusing s32 = int32_t;\nusing u32 = uint32_t;\nusing s64 = int64_t;\nusing u64 = uint64_t;\n\n#ifndef ROUND_UP_256\n#define ROUND_UP_256(val) (((val) + 255) & ~255)\n#endif\n#ifndef ROUND_UP_64\n#define ROUND_UP_64(val) (((val) + 63) & ~63)\n#endif\n#ifndef ROUND_UP_32\n#define ROUND_UP_32(val) (((val) + 31) & ~31)\n#endif\n#ifndef ROUND_UP_16\n#define ROUND_UP_16(val) (((val) + 15) & ~15)\n#endif\n#ifndef ROUND_UP_8\n#define ROUND_UP_8(val) (((val) + 7) & ~7)\n#endif\n#ifndef ROUND_UP_4\n#define ROUND_UP_4(val) (((val) + 3) & ~3)\n#endif\n\n#ifndef ENABLE_BITWISE_ENUM\n#define ENABLE_BITWISE_ENUM(type)                                                                                      \\\n  constexpr type operator|(type a, type b) noexcept {                                                                  \\\n    using T = std::underlying_type_t<type>;                                                                            \\\n    return type(static_cast<T>(a) | static_cast<T>(b));                                                                \\\n  }                                                                                                                    \\\n  constexpr type operator&(type a, type b) noexcept {                                                                  \\\n    using T = std::underlying_type_t<type>;                                                                            \\\n    return type(static_cast<T>(a) & static_cast<T>(b));                                                                \\\n  }                                                                                                                    \\\n  constexpr type& operator|=(type& a, type b) noexcept {                                                               \\\n    using T = std::underlying_type_t<type>;                                                                            \\\n    a = type(static_cast<T>(a) | static_cast<T>(b));                                                                   \\\n    return a;                                                                                                          \\\n  }                                                                                                                    \\\n  constexpr type& operator&=(type& a, type b) noexcept {                                                               \\\n    using T = std::underlying_type_t<type>;                                                                            \\\n    a = type(static_cast<T>(a) & static_cast<T>(b));                                                                   \\\n    return a;                                                                                                          \\\n  }                                                                                                                    \\\n  constexpr type operator~(type key) noexcept {                                                                        \\\n    using T = std::underlying_type_t<type>;                                                                            \\\n    return type(~static_cast<T>(key));                                                                                 \\\n  }                                                                                                                    \\\n  constexpr bool True(type key) noexcept {                                                                             \\\n    using T = std::underlying_type_t<type>;                                                                            \\\n    return static_cast<T>(key) != 0;                                                                                   \\\n  }                                                                                                                    \\\n  constexpr bool False(type key) noexcept {                                                                            \\\n    using T = std::underlying_type_t<type>;                                                                            \\\n    return static_cast<T>(key) == 0;                                                                                   \\\n  }\n#endif"
  },
  {
    "path": "Runtime/GameGlobalObjects.cpp",
    "content": "#include \"Runtime/GameGlobalObjects.hpp\"\n\nnamespace metaforce {\nnamespace MP1 {\nclass CGameArchitectureSupport* g_archSupport = nullptr;\n}\n\nclass IMain* g_Main = nullptr;\nclass CMemoryCardSys* g_MemoryCardSys = nullptr;\nclass IFactory* g_ResFactory = nullptr;\nclass CSimplePool* g_SimplePool = nullptr;\nclass CCharacterFactoryBuilder* g_CharFactoryBuilder = nullptr;\nclass CAiFuncMap* g_AiFuncMap = nullptr;\nclass CGameState* g_GameState = nullptr;\nclass CInGameTweakManagerBase* g_TweakManager = nullptr;\nclass CCubeRenderer* g_Renderer = nullptr;\nclass CStringTable* g_MainStringTable = nullptr;\nclass CTextureCache* g_TextureCache = nullptr;\nclass CInputGenerator* g_InputGenerator = nullptr;\nclass IController* g_Controller = nullptr;\nclass CStateManager* g_StateManager = nullptr;\n\nITweakGame* g_tweakGame = nullptr;\nITweakPlayer* g_tweakPlayer = nullptr;\nITweakPlayerControl* g_tweakPlayerControl = nullptr;\nITweakPlayerControl* g_tweakPlayerControlAlt = nullptr;\nITweakPlayerControl* g_currentPlayerControl = nullptr;\nITweakPlayerGun* g_tweakPlayerGun = nullptr;\nITweakGunRes* g_tweakGunRes = nullptr;\nITweakPlayerRes* g_tweakPlayerRes = nullptr;\nITweakTargeting* g_tweakTargeting = nullptr;\nITweakAutoMapper* g_tweakAutoMapper = nullptr;\nITweakGui* g_tweakGui = nullptr;\nITweakSlideShow* g_tweakSlideShow = nullptr;\nITweakParticle* g_tweakParticle = nullptr;\nITweakBall* g_tweakBall = nullptr;\nITweakGuiColors* g_tweakGuiColors = nullptr;\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GameGlobalObjects.hpp",
    "content": "#pragma once\n\n#define USE_DOWNCAST_TWEAKS 1\n\n#if USE_DOWNCAST_TWEAKS\n#include \"Runtime/MP1/Tweaks/CTweakAutoMapper.hpp\"\n#include \"Runtime/MP1/Tweaks/CTweakBall.hpp\"\n#include \"Runtime/MP1/Tweaks/CTweakGame.hpp\"\n#include \"Runtime/MP1/Tweaks/CTweakGui.hpp\"\n#include \"Runtime/MP1/Tweaks/CTweakGui.hpp\"\n#include \"Runtime/MP1/Tweaks/CTweakGuiColors.hpp\"\n#include \"Runtime/MP1/Tweaks/CTweakGunRes.hpp\"\n#include \"Runtime/MP1/Tweaks/CTweakParticle.hpp\"\n#include \"Runtime/MP1/Tweaks/CTweakPlayer.hpp\"\n#include \"Runtime/MP1/Tweaks/CTweakPlayerControl.hpp\"\n#include \"Runtime/MP1/Tweaks/CTweakPlayerGun.hpp\"\n#include \"Runtime/MP1/Tweaks/CTweakPlayerRes.hpp\"\n#include \"Runtime/MP1/Tweaks/CTweakSlideShow.hpp\"\n#include \"Runtime/MP1/Tweaks/CTweakTargeting.hpp\"\n#include \"Runtime/MP1/Tweaks/CTweakGuiColors.hpp\"\n#include \"Runtime/MP1/Tweaks/CTweakTargeting.hpp\"\n#else\n#include \"Runtime/Tweaks/ITweakAutoMapper.hpp\"\n#include \"Runtime/Tweaks/ITweakBall.hpp\"\n#include \"Runtime/Tweaks/ITweakGame.hpp\"\n#include \"Runtime/Tweaks/ITweakGui.hpp\"\n#include \"Runtime/Tweaks/ITweakGui.hpp\"\n#include \"Runtime/Tweaks/ITweakGuiColors.hpp\"\n#include \"Runtime/Tweaks/ITweakGunRes.hpp\"\n#include \"Runtime/Tweaks/ITweakParticle.hpp\"\n#include \"Runtime/Tweaks/ITweakPlayer.hpp\"\n#include \"Runtime/Tweaks/ITweakPlayerControl.hpp\"\n#include \"Runtime/Tweaks/ITweakPlayerGun.hpp\"\n#include \"Runtime/Tweaks/ITweakPlayerRes.hpp\"\n#include \"Runtime/Tweaks/ITweakSlideShow.hpp\"\n#include \"Runtime/Tweaks/ITweakTargeting.hpp\"\n#endif\n\n#include \"Runtime/CTextureCache.hpp\"\n\nnamespace metaforce {\nextern class IMain* g_Main;\nnamespace MP1 {\nextern class CGameArchitectureSupport* g_archSupport;\n}\nextern class CMemoryCardSys* g_MemoryCardSys;\nextern class IFactory* g_ResFactory;\nextern class CSimplePool* g_SimplePool;\nextern class CCharacterFactoryBuilder* g_CharFactoryBuilder;\nextern class CAiFuncMap* g_AiFuncMap;\nextern class CGameState* g_GameState;\nextern class CInGameTweakManagerBase* g_TweakManager;\nextern class CCubeRenderer* g_Renderer;\nextern class CStringTable* g_MainStringTable;\nextern class CTextureCache* g_TextureCache;\nextern class CInputGenerator* g_InputGenerator;\nextern class IController* g_Controller;\nextern class CStateManager* g_StateManager;\n\n#if USE_DOWNCAST_TWEAKS\nusing ITweakGame = metaforce::MP1::CTweakGame;\nusing ITweakPlayer = metaforce::MP1::CTweakPlayer;\nusing ITweakPlayerControl = metaforce::MP1::CTweakPlayerControl;\nusing ITweakPlayerGun = metaforce::MP1::CTweakPlayerGun;\nusing ITweakGunRes = metaforce::MP1::CTweakGunRes;\nusing ITweakAutoMapper = metaforce::MP1::CTweakAutoMapper;\nusing ITweakGui = metaforce::MP1::CTweakGui;\nusing ITweakSlideShow = metaforce::MP1::CTweakSlideShow;\nusing ITweakParticle = metaforce::MP1::CTweakParticle;\nusing ITweakBall = metaforce::MP1::CTweakBall;\nusing ITweakGuiColors = metaforce::MP1::CTweakGuiColors;\nusing ITweakPlayerRes = metaforce::MP1::CTweakPlayerRes;\nusing ITweakTargeting = metaforce::MP1::CTweakTargeting;\n#else\nusing ITweakGame = metaforce::Tweaks::ITweakGame;\nusing ITweakPlayer = metaforce::Tweaks::ITweakPlayer;\nusing ITweakPlayerControl = metaforce::Tweaks::ITweakPlayerControl;\nusing ITweakPlayerGun = metaforce::Tweaks::ITweakPlayerGun;\nusing ITweakGunRes = metaforce::Tweaks::ITweakGunRes;\nusing ITweakAutoMapper = metaforce::Tweaks::ITweakAutoMapper;\nusing ITweakGui = metaforce::Tweaks::ITweakGui;\nusing ITweakSlideShow = metaforce::Tweaks::ITweakSlideShow;\nusing ITweakParticle = metaforce::Tweaks::ITweakParticle;\nusing ITweakBall = metaforce::Tweaks::ITweakBall;\nusing ITweakGuiColors = metaforce::Tweaks::ITweakGuiColors;\nusing ITweakPlayerRes = metaforce::Tweaks::ITweakPlayerRes;\nusing ITweakTargeting = metaforce::Tweaks::ITweakTargeting;\n#endif\n\nextern ITweakGame* g_tweakGame;\nextern ITweakPlayer* g_tweakPlayer;\nextern ITweakPlayerControl* g_tweakPlayerControl;\nextern ITweakPlayerControl* g_tweakPlayerControlAlt;\nextern ITweakPlayerControl* g_currentPlayerControl;\nextern ITweakPlayerGun* g_tweakPlayerGun;\nextern ITweakGunRes* g_tweakGunRes;\nextern ITweakPlayerRes* g_tweakPlayerRes;\nextern ITweakTargeting* g_tweakTargeting;\nextern ITweakAutoMapper* g_tweakAutoMapper;\nextern ITweakGui* g_tweakGui;\nextern ITweakSlideShow* g_tweakSlideShow;\nextern ITweakParticle* g_tweakParticle;\nextern ITweakBall* g_tweakBall;\nextern ITweakGuiColors* g_tweakGuiColors;\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GameObjectLists.cpp",
    "content": "#include \"Runtime/GameObjectLists.hpp\"\n\n#include \"Runtime/Camera/CGameCamera.hpp\"\n#include \"Runtime/World/CGameLight.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n#include \"Runtime/World/CScriptAiJumpPoint.hpp\"\n#include \"Runtime/World/CScriptCoverPoint.hpp\"\n#include \"Runtime/World/CScriptDoor.hpp\"\n#include \"Runtime/World/CScriptPlatform.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCActorList::CActorList() : CObjectList(EGameObjectList::Actor) {}\n\nbool CActorList::IsQualified(const CEntity& ent) const { return TCastToConstPtr<CActor>(ent).IsValid(); }\n\nCPhysicsActorList::CPhysicsActorList() : CObjectList(EGameObjectList::PhysicsActor) {}\n\nbool CPhysicsActorList::IsQualified(const CEntity& ent) const { return TCastToConstPtr<CPhysicsActor>(ent).IsValid(); }\n\nCGameCameraList::CGameCameraList() : CObjectList(EGameObjectList::GameCamera) {}\n\nbool CGameCameraList::IsQualified(const CEntity& ent) const { return TCastToConstPtr<CGameCamera>(ent).IsValid(); }\n\nCListeningAiList::CListeningAiList() : CObjectList(EGameObjectList::ListeningAi) {}\n\nbool CListeningAiList::IsQualified(const CEntity& ent) const {\n  const TCastToConstPtr<CPatterned> ai(ent);\n  return ai && ai->IsListening();\n}\n\nCAiWaypointList::CAiWaypointList() : CObjectList(EGameObjectList::AiWaypoint) {}\n\nbool CAiWaypointList::IsQualified(const CEntity& ent) const {\n  return TCastToConstPtr<CScriptCoverPoint>(ent) || TCastToConstPtr<CScriptAiJumpPoint>(ent);\n}\n\nCPlatformAndDoorList::CPlatformAndDoorList() : CObjectList(EGameObjectList::PlatformAndDoor) {}\n\nbool CPlatformAndDoorList::IsQualified(const CEntity& ent) const { return IsDoor(ent) || IsPlatform(ent); }\n\nbool CPlatformAndDoorList::IsDoor(const CEntity& ent) const { return TCastToConstPtr<CScriptDoor>(ent).IsValid(); }\n\nbool CPlatformAndDoorList::IsPlatform(const CEntity& ent) const {\n  return TCastToConstPtr<CScriptPlatform>(ent).IsValid();\n}\n\nCGameLightList::CGameLightList() : CObjectList(EGameObjectList::GameLight) {}\n\nbool CGameLightList::IsQualified(const CEntity& lt) const { return TCastToConstPtr<CGameLight>(lt).IsValid(); }\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GameObjectLists.hpp",
    "content": "#pragma once\n\n#include \"Runtime/CObjectList.hpp\"\n\nnamespace metaforce {\n\nclass CActorList : public CObjectList {\npublic:\n  CActorList();\n\n  bool IsQualified(const CEntity&) const override;\n};\n\nclass CPhysicsActorList : public CObjectList {\npublic:\n  CPhysicsActorList();\n  bool IsQualified(const CEntity&) const override;\n};\n\nclass CGameCameraList : public CObjectList {\npublic:\n  CGameCameraList();\n  bool IsQualified(const CEntity&) const override;\n};\n\nclass CListeningAiList : public CObjectList {\npublic:\n  CListeningAiList();\n  bool IsQualified(const CEntity&) const override;\n};\n\nclass CAiWaypointList : public CObjectList {\npublic:\n  CAiWaypointList();\n  bool IsQualified(const CEntity&) const override;\n};\n\nclass CPlatformAndDoorList : public CObjectList {\npublic:\n  CPlatformAndDoorList();\n\n  bool IsQualified(const CEntity&) const override;\n  bool IsDoor(const CEntity&) const;\n  bool IsPlatform(const CEntity&) const;\n};\n\nclass CGameLightList : public CObjectList {\npublic:\n  CGameLightList();\n\n  bool IsQualified(const CEntity&) const override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CCubeMaterial.cpp",
    "content": "#include \"Graphics/CCubeMaterial.hpp\"\n\n#include \"GameGlobalObjects.hpp\"\n#include \"Graphics/CCubeModel.hpp\"\n#include \"Graphics/CCubeRenderer.hpp\"\n#include \"Graphics/CCubeSurface.hpp\"\n#include \"Graphics/CGX.hpp\"\n#include \"Graphics/CModel.hpp\"\n#include \"Logging.hpp\"\n\nnamespace metaforce {\nstatic u32 sReflectionType = 0;\nstatic u32 sLastMaterialUnique = UINT32_MAX;\nstatic const u8* sLastMaterialCached = nullptr;\nstatic const CCubeModel* sLastModelCached = nullptr;\nstatic const CCubeModel* sRenderingModel = nullptr;\nstatic float sReflectionAlpha = 0.f;\n\nvoid CCubeMaterial::SetCurrent(const CModelFlags& flags, const CCubeSurface& surface, CCubeModel& model) {\n  if (sLastMaterialCached == x0_data) {\n    if (sReflectionType == 1) {\n      if (sLastModelCached == sRenderingModel) {\n        return;\n      }\n    } else if (sReflectionType != 2) {\n      return;\n    }\n  }\n\n  if (CCubeModel::sRenderModelBlack) {\n    SetCurrentBlack();\n    return;\n  }\n\n  sRenderingModel = &model;\n  sLastMaterialCached = x0_data;\n\n  u32 numIndStages = 0;\n  const auto matFlags = GetFlags();\n  const u8* materialDataCur = x0_data;\n\n  const bool reflection = bool(\n      matFlags & (Flags(CCubeMaterialFlagBits::fSamusReflection) | CCubeMaterialFlagBits::fSamusReflectionSurfaceEye));\n  if (reflection) {\n    if (!(matFlags & CCubeMaterialFlagBits::fSamusReflectionSurfaceEye)) {\n      EnsureViewDepStateCached(nullptr);\n    } else {\n      EnsureViewDepStateCached(&surface);\n    }\n  }\n\n  u32 texCount = SBig(*reinterpret_cast<const u32*>(materialDataCur + 4));\n  if (flags.x2_flags & CModelFlagBits::NoTextureLock) {\n    materialDataCur += (2 + texCount) * 4;\n  } else {\n    materialDataCur += 8;\n    for (u32 i = 0; i < texCount; ++i) {\n      u32 texIdx = SBig(*reinterpret_cast<const u32*>(materialDataCur));\n      model.GetTexture(texIdx)->Load(static_cast<GXTexMapID>(i), EClampMode::Repeat);\n      materialDataCur += 4;\n    }\n  }\n\n  auto groupIdx = SBig(*reinterpret_cast<const u32*>(materialDataCur + 4));\n  if (sLastMaterialUnique != UINT32_MAX && sLastMaterialUnique == groupIdx && sReflectionType == 0) {\n    return;\n  }\n  sLastMaterialUnique = groupIdx;\n\n  u32 vatFlags = SBig(*reinterpret_cast<const u32*>(materialDataCur));\n  CGX::SetVtxDescv_Compressed(vatFlags);\n  materialDataCur += 8;\n\n  bool packedLightMaps = matFlags.IsSet(CCubeMaterialFlagBits::fLightmapUvArray);\n  if (packedLightMaps != CCubeModel::sUsingPackedLightmaps) {\n    model.SetUsingPackedLightmaps(packedLightMaps);\n  }\n\n  u32 finalKColorCount = 0;\n  if (matFlags & CCubeMaterialFlagBits::fKonstValues) {\n    u32 konstCount = SBig(*reinterpret_cast<const u32*>(materialDataCur));\n    finalKColorCount = konstCount;\n    materialDataCur += 4;\n    for (u32 i = 0; i < konstCount; ++i) {\n      u32 kColor = SBig(*reinterpret_cast<const u32*>(materialDataCur));\n      materialDataCur += 4;\n      CGX::SetTevKColor(static_cast<GXTevKColorID>(i), kColor);\n    }\n  }\n\n  u32 blendFactors = SBig(*reinterpret_cast<const u32*>(materialDataCur));\n  materialDataCur += 4;\n  if (g_Renderer->IsInAreaDraw()) {\n    CGX::SetBlendMode(GX_BM_BLEND, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR);\n  } else {\n    SetupBlendMode(blendFactors, flags, matFlags.IsSet(CCubeMaterialFlagBits::fAlphaTest));\n  }\n\n  bool indTex = matFlags.IsSet(CCubeMaterialFlagBits::fSamusReflectionIndirectTexture);\n  u32 indTexSlot = 0;\n  if (indTex) {\n    indTexSlot = SBig(*reinterpret_cast<const u32*>(materialDataCur));\n    materialDataCur += 4;\n  }\n\n  HandleDepth(flags.x2_flags, matFlags);\n\n  u32 chanCount = SBig(*reinterpret_cast<const u32*>(materialDataCur));\n  materialDataCur += 4;\n  u32 firstChan = SBig(*reinterpret_cast<const u32*>(materialDataCur));\n  materialDataCur += 4 * chanCount;\n  u32 finalNumColorChans = HandleColorChannels(chanCount, firstChan);\n\n  u32 firstTev = 0;\n  if (CCubeModel::sRenderModelShadow)\n    firstTev = 2;\n\n  u32 matTevCount = SBig(*reinterpret_cast<const u32*>(materialDataCur));\n  materialDataCur += 4;\n  u32 finalTevCount = matTevCount;\n\n  const u32* texMapTexCoordFlags = reinterpret_cast<const u32*>(materialDataCur + matTevCount * 20);\n  const u32* tcgs = reinterpret_cast<const u32*>(texMapTexCoordFlags + matTevCount);\n  bool usesTevReg2 = false;\n\n  u32 finalCCFlags = 0;\n  u32 finalACFlags = 0;\n\n  if (g_Renderer->IsThermalVisorActive()) {\n    finalTevCount = firstTev + 1;\n    u32 ccFlags = SBig(*reinterpret_cast<const u32*>(materialDataCur + 8));\n    finalCCFlags = ccFlags;\n    auto outputReg = static_cast<GXTevRegID>(ccFlags >> 9 & 0x3);\n    if (outputReg == GX_TEVREG0) {\n      materialDataCur += 20;\n      texMapTexCoordFlags += 1;\n      finalCCFlags = SBig(*reinterpret_cast<const u32*>(materialDataCur + 8));\n      GXSetTevColor(GX_TEVREG0, GXColor{0xc0, 0xc0, 0xc0, 0xc0});\n    }\n    finalACFlags = SBig(*reinterpret_cast<const u32*>(materialDataCur + 12));\n    HandleTev(firstTev, reinterpret_cast<const u32*>(materialDataCur), texMapTexCoordFlags,\n              CCubeModel::sRenderModelShadow);\n    usesTevReg2 = false;\n  } else {\n    finalTevCount = firstTev + matTevCount;\n    for (u32 i = firstTev; i < finalTevCount; ++i) {\n      HandleTev(i, reinterpret_cast<const u32*>(materialDataCur), texMapTexCoordFlags,\n                CCubeModel::sRenderModelShadow && i == firstTev);\n      u32 ccFlags = SBig(*reinterpret_cast<const u32*>(materialDataCur + 8));\n      finalCCFlags = ccFlags;\n      finalACFlags = SBig(*reinterpret_cast<const u32*>(materialDataCur + 12));\n      auto outputReg = static_cast<GXTevRegID>(ccFlags >> 9 & 0x3);\n      if (outputReg == GX_TEVREG2) {\n        usesTevReg2 = true;\n      }\n      materialDataCur += 20;\n      texMapTexCoordFlags += 1;\n    }\n  }\n\n  u32 tcgCount = 0;\n  if (g_Renderer->IsThermalVisorActive()) {\n    u32 fullTcgCount = SBig(*tcgs);\n    tcgCount = std::min(fullTcgCount, 2u);\n    for (u32 i = 0; i < tcgCount; ++i) {\n      CGX::SetTexCoordGen(GXTexCoordID(i), SBig(tcgs[i + 1]));\n    }\n    tcgs += fullTcgCount + 1;\n  } else {\n    tcgCount = SBig(*tcgs);\n    for (u32 i = 0; i < tcgCount; ++i) {\n      CGX::SetTexCoordGen(GXTexCoordID(i), SBig(tcgs[i + 1]));\n    }\n    tcgs += tcgCount + 1;\n  }\n\n  const u32* uvAnim = tcgs;\n  u32 animCount = SBig(uvAnim[1]);\n  uvAnim += 2;\n  u32 texMtx = GX_TEXMTX0;\n  u32 pttTexMtx = GX_PTTEXMTX0;\n  for (u32 i = 0; i < animCount; ++i) {\n    u32 size = HandleAnimatedUV(uvAnim, static_cast<GXTexMtx>(texMtx), static_cast<GXPTTexMtx>(pttTexMtx));\n    if (size == 0)\n      break;\n    uvAnim += size;\n    texMtx += 3;\n    pttTexMtx += 3;\n  }\n\n  if (flags.x0_blendMode != 0) {\n    HandleTransparency(finalTevCount, finalKColorCount, flags, blendFactors, finalCCFlags, finalACFlags);\n  }\n\n  if (reflection) {\n    if (sReflectionAlpha > 0.f) {\n      u32 additionalTevs = 0;\n      if (indTex) {\n        additionalTevs = HandleReflection(usesTevReg2, indTexSlot, 0, finalTevCount, texCount, tcgCount,\n                                          finalKColorCount, finalCCFlags, finalACFlags);\n        numIndStages = 1;\n        tcgCount += 2;\n      } else {\n        additionalTevs = HandleReflection(usesTevReg2, 255, 0, finalTevCount, texCount, tcgCount, finalKColorCount,\n                                          finalCCFlags, finalACFlags);\n        tcgCount += 1;\n      }\n      texCount += 1;\n      finalTevCount += additionalTevs;\n      finalKColorCount += 1;\n    } else if (((finalCCFlags >> 9) & 0x3) != 0) {\n      DoPassthru(finalTevCount);\n      finalTevCount += 1;\n    }\n  }\n\n  if (CCubeModel::sRenderModelShadow) {\n    DoModelShadow(texCount, tcgCount);\n    tcgCount += 1;\n  }\n\n  CGX::SetNumIndStages(numIndStages);\n  CGX::SetNumTevStages(finalTevCount);\n  CGX::SetNumTexGens(tcgCount);\n  CGX::SetNumChans(finalNumColorChans);\n}\n\nvoid CCubeMaterial::SetCurrentBlack() {\n  const auto flags = GetFlags();\n  const auto vatFlags = GetVatFlags();\n  if (flags.IsSet(CCubeMaterialFlagBits::fDepthSorting) || flags.IsSet(CCubeMaterialFlagBits::fAlphaTest)) {\n    CGX::SetBlendMode(GX_BM_BLEND, GX_BL_ZERO, GX_BL_ONE, GX_LO_CLEAR);\n  } else {\n    CGX::SetBlendMode(GX_BM_BLEND, GX_BL_ONE, GX_BL_ZERO, GX_LO_CLEAR);\n  }\n  CGX::SetVtxDescv_Compressed(vatFlags);\n  CGX::SetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO /* ? CC_ONE */);\n  CGX::SetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO /* ? CA_KONST */);\n  CGX::SetTevKAlphaSel(GX_TEVSTAGE0, GX_TEV_KASEL_1);\n  CGX::SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_POS, GX_IDENTITY, false, GX_PTIDENTITY);\n  CGX::SetStandardTevColorAlphaOp(GX_TEVSTAGE0);\n  CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);\n  CGX::SetNumTevStages(1);\n  CGX::SetNumChans(0);\n  CGX::SetNumTexGens(1);\n  CGX::SetNumIndStages(0);\n}\n\nvoid CCubeMaterial::SetupBlendMode(u32 blendFactors, const CModelFlags& flags, bool alphaTest) {\n  auto newSrcFactor = static_cast<GXBlendFactor>(blendFactors & 0xffff);\n  auto newDstFactor = static_cast<GXBlendFactor>(blendFactors >> 16 & 0xffff);\n  if (alphaTest) {\n    // discard fragments with alpha < 0.25\n    CGX::SetAlphaCompare(GX_GEQUAL, 64, GX_AOP_OR, GX_NEVER, 0);\n    newSrcFactor = GX_BL_ONE;\n    newDstFactor = GX_BL_ZERO;\n  } else {\n    CGX::SetAlphaCompare(GX_ALWAYS, 0, GX_AOP_OR, GX_ALWAYS, 0);\n  }\n\n  if (flags.x0_blendMode > 4 && newSrcFactor == GX_BL_ONE) {\n    newSrcFactor = GX_BL_SRCALPHA;\n    if (newDstFactor == GX_BL_ZERO) {\n      newDstFactor = flags.x0_blendMode > 6 ? GX_BL_ONE : GX_BL_INVSRCALPHA;\n    }\n  }\n\n  CGX::SetBlendMode(GX_BM_BLEND, newSrcFactor, newDstFactor, GX_LO_CLEAR);\n}\n\nvoid CCubeMaterial::HandleDepth(CModelFlagsFlags modelFlags, CCubeMaterialFlags matFlags) {\n  GXCompare func = GX_NEVER;\n  if (!(modelFlags & CModelFlagBits::DepthTest)) {\n    func = GX_ALWAYS;\n  } else if (modelFlags & CModelFlagBits::DepthGreater) {\n    func = modelFlags & CModelFlagBits::DepthNonInclusive ? GX_GREATER : GX_GEQUAL;\n  } else {\n    func = modelFlags & CModelFlagBits::DepthNonInclusive ? GX_LESS : GX_LEQUAL;\n  }\n  bool depthWrite = modelFlags & CModelFlagBits::DepthUpdate && matFlags & CCubeMaterialFlagBits::fDepthWrite;\n  CGX::SetZMode(true, func, depthWrite);\n}\n\nvoid CCubeMaterial::ResetCachedMaterials() {\n  KillCachedViewDepState();\n  sLastMaterialUnique = UINT32_MAX;\n  sRenderingModel = nullptr;\n  sLastMaterialCached = nullptr;\n}\n\nvoid CCubeMaterial::KillCachedViewDepState() { sLastModelCached = nullptr; }\n\nvoid CCubeMaterial::EnsureViewDepStateCached(const CCubeSurface* surface) {\n  // TODO\n  if ((surface != nullptr || sLastModelCached != sRenderingModel) && sRenderingModel != nullptr) {\n    sLastModelCached = sRenderingModel;\n    if (surface == nullptr) {\n      sReflectionType = 1;\n    } else {\n      sReflectionType = 2;\n    }\n    if (g_Renderer->IsReflectionDirty()) {\n\n    } else {\n      g_Renderer->SetReflectionDirty(true);\n    }\n  }\n}\n\nu32 CCubeMaterial::HandleColorChannels(u32 chanCount, u32 firstChan) {\n  static constexpr GXColor sGXBlack = {0, 0, 0, 0};\n  static constexpr GXColor sGXWhite = {0xFF, 0xFF, 0xFF, 0xFF};\n\n  if (CCubeModel::sRenderModelShadow) {\n    if (chanCount != 0) {\n      CGX::SetChanAmbColor(CGX::EChannelId::Channel1, sGXBlack);\n      CGX::SetChanMatColor(CGX::EChannelId::Channel1, sGXWhite);\n\n      CGX::SetChanCtrl(CGX::EChannelId::Channel1, true, GX_SRC_REG, GX_SRC_REG, CCubeModel::sChannel1EnableLightMask,\n                       GX_DF_CLAMP, GX_AF_SPOT);\n\n      const auto chan0Lights = CGraphics::GetLightMask() & ~CCubeModel::sChannel0DisableLightMask;\n      CGX::SetChanCtrl_Compressed(CGX::EChannelId::Channel0, chan0Lights, firstChan);\n      if (chan0Lights.any()) {\n        CGX::SetChanMatColor(CGX::EChannelId::Channel0, sGXWhite);\n      } else {\n        CGX::SetChanMatColor(CGX::EChannelId::Channel0, CGX::GetChanAmbColor(CGX::EChannelId::Channel0));\n      }\n    }\n    return 2;\n  }\n\n  if (chanCount == 2) {\n    CGX::SetChanAmbColor(CGX::EChannelId::Channel1, sGXBlack);\n    CGX::SetChanMatColor(CGX::EChannelId::Channel1, sGXWhite);\n  } else {\n    CGX::SetChanCtrl(CGX::EChannelId::Channel1, false, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE);\n  }\n\n  if (chanCount >= 1) {\n    const auto lightMask = CGraphics::GetLightMask();\n    CGX::SetChanCtrl_Compressed(CGX::EChannelId::Channel0, lightMask, firstChan);\n    if (lightMask.any()) {\n      CGX::SetChanMatColor(CGX::EChannelId::Channel0, sGXWhite);\n    } else {\n      CGX::SetChanMatColor(CGX::EChannelId::Channel0, CGX::GetChanAmbColor(CGX::EChannelId::Channel0));\n    }\n  } else {\n    CGX::SetChanCtrl(CGX::EChannelId::Channel0, false, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE);\n  }\n\n  return chanCount;\n}\n\nvoid CCubeMaterial::HandleTev(u32 tevCur, const u32* materialDataCur, const u32* texMapTexCoordFlags,\n                              bool shadowMapsEnabled) {\n  const u32 colorArgs = shadowMapsEnabled ? 0x7a04f : SBig(materialDataCur[0]);\n  const u32 alphaArgs = SBig(materialDataCur[1]);\n  const u32 colorOps = SBig(materialDataCur[2]);\n  const u32 alphaOps = SBig(materialDataCur[3]);\n\n  const auto stage = static_cast<GXTevStageID>(tevCur);\n  CGX::SetStandardDirectTev_Compressed(stage, colorArgs, alphaArgs, colorOps, alphaOps);\n\n  u32 tmtcFlags = SBig(*texMapTexCoordFlags);\n  u32 matFlags = SBig(materialDataCur[4]);\n  CGX::SetTevOrder(stage, static_cast<GXTexCoordID>(tmtcFlags & 0xFF), static_cast<GXTexMapID>(tmtcFlags >> 8 & 0xFF),\n                   static_cast<GXChannelID>(matFlags & 0xFF));\n  CGX::SetTevKColorSel(stage, static_cast<GXTevKColorSel>(matFlags >> 0x8 & 0xFF));\n  CGX::SetTevKAlphaSel(stage, static_cast<GXTevKAlphaSel>(matFlags >> 0x10 & 0xFF));\n}\n\nu32 CCubeMaterial::HandleAnimatedUV(const u32* uvAnim, GXTexMtx texMtx, GXPTTexMtx ptTexMtx) {\n  static const Mtx postMtx = {\n      {0.5f, 0.0f, 0.0f, 0.5f},\n      {0.0f, 0.0f, 0.5f, 0.5f},\n      {0.0f, 0.0f, 0.0f, 1.0f},\n  };\n  static Mtx translateMtx = {\n      {1.0f, 0.0f, 0.0f, 0.0f},\n      {0.0f, 1.0f, 0.0f, 0.0f},\n      {0.0f, 0.0f, 1.0f, 0.0f},\n  };\n  u32 type = SBig(*uvAnim);\n  const float* params = reinterpret_cast<const float*>(uvAnim + 1);\n  switch (type) {\n  case 0: {\n    auto xf = CGraphics::GetViewMatrix().quickInverse().multiplyIgnoreTranslation(CGraphics::GetModelMatrix());\n    xf.origin.zeroOut();\n    Mtx mtx;\n    xf.toCStyleMatrix(mtx);\n    GXLoadTexMtxImm(mtx, texMtx, GX_MTX3x4);\n    GXLoadTexMtxImm(postMtx, ptTexMtx, GX_MTX3x4);\n    return 1;\n  }\n  case 1: {\n    auto xf = CGraphics::GetViewMatrix().quickInverse() * CGraphics::GetModelMatrix();\n    Mtx mtx;\n    xf.toCStyleMatrix(mtx);\n    GXLoadTexMtxImm(mtx, texMtx, GX_MTX3x4);\n    GXLoadTexMtxImm(postMtx, ptTexMtx, GX_MTX3x4);\n    return 1;\n  }\n  case 2: {\n    const float f1 = SBig(params[0]);\n    const float f2 = SBig(params[1]);\n    const float f3 = SBig(params[2]);\n    const float f4 = SBig(params[3]);\n    const float seconds = CGraphics::GetSecondsMod900();\n    translateMtx[0][3] = f1 + seconds * f3;\n    translateMtx[1][3] = f2 + seconds * f4;\n    GXLoadTexMtxImm(translateMtx, texMtx, GX_MTX3x4);\n    return 5;\n  }\n  case 3: {\n    const float f1 = SBig(params[0]);\n    const float f2 = SBig(params[1]);\n    const float seconds = CGraphics::GetSecondsMod900();\n    const float angle = f1 + seconds * f2;\n    const float asin = std::sin(angle);\n    const float acos = std::cos(angle);\n    Mtx mtx = {\n        {acos, -asin, 0.f, (1.f - (acos - asin)) * 0.5f},\n        {asin, acos, 0.f, (1.f - (asin + acos)) * 0.5f},\n        {0.f, 0.f, 1.f, 0.f},\n    };\n    GXLoadTexMtxImm(mtx, texMtx, GX_MTX3x4);\n    return 3;\n  }\n  case 4:\n  case 5: {\n    const float f1 = SBig(params[0]);\n    const float f2 = SBig(params[1]);\n    const float f3 = SBig(params[2]);\n    const float f4 = SBig(params[3]);\n    const float value = (f4 + CGraphics::GetSecondsMod900()) * f1 * f3;\n    const float fmod = std::fmod(value, 1.f);\n    const float fs = std::trunc(fmod * f2);\n    const float v2 = fs * f3;\n    if (type == 4) {\n      translateMtx[0][3] = v2;\n      translateMtx[1][3] = 0.f;\n    } else {\n      translateMtx[0][3] = 0.f;\n      translateMtx[1][3] = v2;\n    }\n    GXLoadTexMtxImm(translateMtx, texMtx, GX_MTX3x4);\n    return 5;\n  }\n  case 6: {\n    static const Mtx sTexMtx = {\n        {0.f, 0.f, 0.f, 0.f},\n        {0.f, 0.f, 0.f, 0.f},\n        {0.f, 0.f, 0.f, 0.f},\n    };\n    static const Mtx sPtMtx = {\n        {0.5f, 0.f, 0.f, 0.f},\n        {0.f, 0.f, 0.5f, 0.f},\n        {0.f, 0.f, 0.f, 1.f},\n    };\n    const zeus::CTransform& mm = CGraphics::GetModelMatrix();\n    Mtx tmpTexMtx;\n    Mtx tmpPtMtx;\n    memcpy(&tmpTexMtx, &sTexMtx, sizeof(Mtx));\n    tmpTexMtx[0][0] = mm.basis[0][0];\n    tmpTexMtx[0][1] = mm.basis[1][0];\n    tmpTexMtx[0][2] = mm.basis[2][0];\n    tmpTexMtx[1][0] = mm.basis[0][1];\n    tmpTexMtx[1][1] = mm.basis[1][1];\n    tmpTexMtx[1][2] = mm.basis[2][1];\n    tmpTexMtx[2][0] = mm.basis[0][2];\n    tmpTexMtx[2][1] = mm.basis[1][2];\n    tmpTexMtx[2][2] = mm.basis[2][2];\n    memcpy(&tmpPtMtx, &sPtMtx, sizeof(Mtx));\n    tmpPtMtx[0][3] = mm.origin.x() * 0.05f;\n    tmpPtMtx[1][3] = mm.origin.y() * 0.05f;\n    GXLoadTexMtxImm(tmpTexMtx, texMtx, GX_MTX3x4);\n    GXLoadTexMtxImm(tmpPtMtx, ptTexMtx, GX_MTX3x4);\n    return 1;\n  }\n  case 7: {\n    static const Mtx sPtMtx = {\n        {0.f, 0.f, 0.f, 0.f},\n        {0.f, 0.f, 0.f, 0.f},\n        {0.f, 0.f, 0.f, 1.f},\n    };\n    const zeus::CTransform& vm = CGraphics::GetViewMatrix();\n    zeus::CTransform xf = vm.quickInverse().multiplyIgnoreTranslation(CGraphics::GetModelMatrix());\n    float v = SBig(params[0]) / 2.f;\n    float v03 = 0.025f * (vm.origin.x() + vm.origin.y()) * SBig(params[1]);\n    float v13 = 0.05f * vm.origin.z() * SBig(params[1]);\n    float v03f = std::fmod(v03, 1.f);\n    float v13f = std::fmod(v13, 1.f);\n    xf.origin.zeroOut();\n    Mtx mtx;\n    xf.toCStyleMatrix(mtx);\n    Mtx tmpPtMtx;\n    memcpy(&tmpPtMtx, &sPtMtx, sizeof(Mtx));\n    tmpPtMtx[0][0] = v;\n    tmpPtMtx[0][3] = v03f;\n    tmpPtMtx[1][2] = v;\n    tmpPtMtx[1][3] = v13f;\n    GXLoadTexMtxImm(mtx, texMtx, GX_MTX3x4);\n    GXLoadTexMtxImm(tmpPtMtx, ptTexMtx, GX_MTX3x4);\n    return 3;\n  }\n  default:\n    return 0;\n  }\n}\n\nvoid CCubeMaterial::HandleTransparency(u32& finalTevCount, u32& finalKColorCount, const CModelFlags& modelFlags,\n                                       u32 blendFactors, u32& finalCCFlags, u32& finalACFlags) {\n  if (modelFlags.x0_blendMode == 2) {\n    u16 dstFactor = blendFactors >> 16 & 0xffff;\n    if (dstFactor == 1) {\n      return;\n    }\n  }\n  if (modelFlags.x0_blendMode == 3) {\n    // Stage outputting splatted KAlpha as color to reg0\n    auto stage = static_cast<GXTevStageID>(finalTevCount);\n    CGX::SetTevColorIn(stage, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_KONST);\n    CGX::SetTevAlphaIn(stage, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_APREV);\n    CGX::SetTevColorOp(stage, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, true, GX_TEVREG0);\n    CGX::SetTevKColorSel(stage, static_cast<GXTevKColorSel>(finalKColorCount + GX_TEV_KCSEL_K0_A));\n    CGX::SetTevAlphaOp(stage, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, true, GX_TEVPREV);\n    CGX::SetTevOrder(stage, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);\n    CGX::SetTevDirect(stage);\n\n    // Stage interpolating from splatted KAlpha using KColor\n    stage = static_cast<GXTevStageID>(stage + 1);\n    CGX::SetTevColorIn(stage, GX_CC_CPREV, GX_CC_C0, GX_CC_KONST, GX_CC_ZERO);\n    CGX::SetTevAlphaIn(stage, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_APREV);\n    CGX::SetTevKColorSel(stage, static_cast<GXTevKColorSel>(finalKColorCount + GX_TEV_KCSEL_K0));\n    CGX::SetStandardTevColorAlphaOp(stage);\n    CGX::SetTevDirect(stage);\n    CGX::SetTevOrder(stage, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);\n    CGX::SetTevKColor(static_cast<GXTevKColorID>(finalKColorCount), modelFlags.x4_color);\n\n    finalKColorCount += 1;\n    finalTevCount += 2;\n  } else {\n    auto stage = static_cast<GXTevStageID>(finalTevCount);\n    if (modelFlags.x0_blendMode == 8) {\n      CGX::SetTevAlphaIn(stage, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_KONST); // Set KAlpha\n    } else {\n      CGX::SetTevAlphaIn(stage, GX_CA_ZERO, GX_CA_KONST, GX_CA_APREV, GX_CA_ZERO); // Mul KAlpha\n    }\n    if (modelFlags.x0_blendMode == 2) {\n      CGX::SetTevColorIn(stage, GX_CC_ZERO, GX_CC_ONE, GX_CC_CPREV, GX_CC_KONST); // Add KColor\n    } else {\n      CGX::SetTevColorIn(stage, GX_CC_ZERO, GX_CC_KONST, GX_CC_CPREV, GX_CC_ZERO); // Mul KColor\n    }\n    CGX::SetStandardTevColorAlphaOp(stage);\n\n    finalCCFlags = 0x100; // Just clamp, output prev reg\n    finalACFlags = 0x100;\n\n    CGX::SetTevDirect(stage);\n    CGX::SetTevOrder(stage, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);\n    CGX::SetTevKColor(static_cast<GXTevKColorID>(finalKColorCount), modelFlags.x4_color);\n    CGX::SetTevKColorSel(stage, static_cast<GXTevKColorSel>(finalKColorCount + GX_TEV_KCSEL_K0));\n    CGX::SetTevKAlphaSel(stage, static_cast<GXTevKAlphaSel>(finalKColorCount + GX_TEV_KASEL_K0_A));\n\n    finalTevCount += 1;\n    finalKColorCount += 1;\n  }\n}\n\nu32 CCubeMaterial::HandleReflection(bool usesTevReg2, u32 indTexSlot, u32 r5, u32 finalTevCount, u32 texCount,\n                                    u32 tcgCount, u32 finalKColorCount, u32& finalCCFlags, u32& finalACFlags) {\n  u32 out = 0;\n  GXTevColorArg colorArg = GX_CC_KONST;\n  if (usesTevReg2) {\n    colorArg = GX_CC_C2;\n    const auto stage = static_cast<GXTevStageID>(finalTevCount);\n    CGX::SetTevColorIn(stage, GX_CC_ZERO, GX_CC_C2, GX_CC_KONST, GX_CC_ZERO);\n    CGX::SetTevAlphaIn(stage, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_A2);\n    CGX::SetTevColorOp(stage, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, true, GX_TEVREG2);\n    CGX::SetTevAlphaOp(stage, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, true, GX_TEVREG2);\n    CGX::SetTevOrder(stage, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_ZERO);\n    out = 1;\n  }\n  CGX::SetTevKColor(static_cast<GXTevKColorID>(finalKColorCount), zeus::CColor{sReflectionAlpha, sReflectionAlpha});\n  CGX::SetTevKColorSel(static_cast<GXTevStageID>(finalTevCount),\n                       static_cast<GXTevKColorSel>(GX_TEV_KCSEL_K0 + finalKColorCount));\n\n  const auto stage = static_cast<GXTevStageID>(finalTevCount + out);\n  // tex = g_Renderer->GetRealReflection\n  // tex.Load(texCount, 0)\n\n  // TODO\n\n  finalACFlags = 0;\n  finalCCFlags = 0;\n\n  // aurora::gfx::set_tev_order(stage, ...)\n\n  return out; //+ 1;\n}\n\nvoid CCubeMaterial::DoPassthru(u32 finalTevCount) {\n  const auto stage = static_cast<GXTevStageID>(finalTevCount);\n  CGX::SetTevColorIn(stage, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_CPREV);\n  CGX::SetTevAlphaIn(stage, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_APREV);\n  CGX::SetTevOrder(stage, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);\n  CGX::SetTevDirect(stage);\n  CGX::SetStandardTevColorAlphaOp(stage);\n}\n\nvoid CCubeMaterial::DoModelShadow(u32 texCount, u32 tcgCount) {\n  CCubeModel::sShadowTexture->Load(static_cast<GXTexMapID>(texCount), EClampMode::Repeat);\n  const auto& xf = CCubeModel::sTextureProjectionTransform;\n  Mtx mtx = {\n      {xf.basis[0][0], xf.basis[1][0], xf.basis[2][0], xf.origin.x()},\n      {xf.basis[0][2], xf.basis[1][2], xf.basis[2][2], xf.origin.z()},\n      {0.f, 0.f, 0.f, 1.f},\n  };\n  GXLoadTexMtxImm(mtx, GX_TEXMTX5, GX_MTX3x4);\n\n  CGX::SetTexCoordGen(static_cast<GXTexCoordID>(tcgCount), GX_TG_MTX3x4, GX_TG_POS, GX_TEXMTX5, GX_FALSE,\n                      GX_PTIDENTITY);\n  CGX::SetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVREG0);\n  CGX::SetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVREG0);\n  CGX::SetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_RASC, GX_CC_TEXC, GX_CC_ZERO);\n  CGX::SetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_RASA);\n  CGX::SetTevOrder(GX_TEVSTAGE0, static_cast<GXTexCoordID>(tcgCount), static_cast<GXTexMapID>(texCount), GX_COLOR1A1);\n\n  CGX::SetTevColorOp(GX_TEVSTAGE1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVREG0);\n  CGX::SetTevAlphaOp(GX_TEVSTAGE1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVREG0);\n  CGX::SetTevColorIn(GX_TEVSTAGE1, GX_CC_ZERO, GX_CC_RASC, GX_CC_ONE, GX_CC_C0);\n  CGX::SetTevAlphaIn(GX_TEVSTAGE1, GX_CA_ZERO, GX_CA_RASA, GX_CA_KONST, GX_CA_A0);\n  CGX::SetTevKAlphaSel(GX_TEVSTAGE1, GX_TEV_KASEL_1);\n  CGX::SetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0);\n}\n\nstatic GXTevStageID sCurrentTevStage = GX_MAX_TEVSTAGE;\nvoid CCubeMaterial::EnsureTevsDirect() {\n  if (sCurrentTevStage == GX_MAX_TEVSTAGE) {\n    return;\n  }\n\n  CGX::SetNumIndStages(0);\n  CGX::SetTevDirect(sCurrentTevStage);\n  sCurrentTevStage = GX_MAX_TEVSTAGE;\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CCubeMaterial.hpp",
    "content": "#pragma once\n\n#include \"CToken.hpp\"\n#include \"GCNTypes.hpp\"\n#include \"Graphics/CTexture.hpp\"\n#include \"Graphics/CModel.hpp\"\n#include \"IObjectStore.hpp\"\n\nnamespace metaforce {\nclass CCubeModel;\nclass CCubeSurface;\n\nenum class CCubeMaterialFlagBits : u32 {\n  fKonstValues = 0x8,\n  fDepthSorting = 0x10,\n  fAlphaTest = 0x20,\n  fSamusReflection = 0x40,\n  fDepthWrite = 0x80,\n  fSamusReflectionSurfaceEye = 0x100,\n  fShadowOccluderMesh = 0x200,\n  fSamusReflectionIndirectTexture = 0x400,\n  fLightmap = 0x800,\n  fLightmapUvArray = 0x2000,\n  fTextureSlotMask = 0xffff0000\n};\nusing CCubeMaterialFlags = Flags<CCubeMaterialFlagBits>;\n\nclass CCubeMaterial {\n  const u8* x0_data;\n\npublic:\n  explicit CCubeMaterial(const u8* data) : x0_data(data) {}\n\n  void SetCurrent(const CModelFlags& flags, const CCubeSurface& surface, CCubeModel& model);\n\n  [[nodiscard]] u32 GetCompressedBlend() {\n    const u32* ptr = reinterpret_cast<const u32*>(x0_data + (GetTextureCount() * 4) + 16);\n    if (GetFlags() & CCubeMaterialFlagBits::fKonstValues) {\n      ptr += SBig(*ptr) + 1;\n    }\n    return SBig(*ptr);\n  }\n  [[nodiscard]] CCubeMaterialFlags GetFlags() const {\n    return CCubeMaterialFlags(SBig(*reinterpret_cast<const u32*>(x0_data)));\n  }\n  [[nodiscard]] u32 GetVatFlags() const {\n    return SBig(*reinterpret_cast<const u32*>(x0_data + 8 + (GetTextureCount() * 4)));\n  }\n  [[nodiscard]] u32 GetUsedTextureSlots() const { return static_cast<u32>(GetFlags()) >> 16; }\n  [[nodiscard]] u32 GetTextureCount() const { return SBig(*reinterpret_cast<const u32*>(x0_data + 4)); }\n  [[nodiscard]] u32 GetVertexDesc() const {\n    return SBig(*reinterpret_cast<const u32*>(x0_data + (GetTextureCount() * 4) + 8));\n  }\n\n  static void ResetCachedMaterials();\n  static void EnsureViewDepStateCached(const CCubeSurface* surface);\n  static void KillCachedViewDepState();\n  static void EnsureTevsDirect();\n\nprivate:\n  void SetCurrentBlack();\n\n  static void SetupBlendMode(u32 blendFactors, const CModelFlags& flags, bool alphaTest);\n  static void HandleDepth(CModelFlagsFlags modelFlags, CCubeMaterialFlags matFlags);\n  static u32 HandleColorChannels(u32 chanCount, u32 firstChan);\n  static void HandleTev(u32 tevCur, const u32* materialDataCur, const u32* texMapTexCoordFlags, bool shadowMapsEnabled);\n  static u32 HandleAnimatedUV(const u32* uvAnim, GXTexMtx texMtx, GXPTTexMtx pttTexMtx);\n  static void HandleTransparency(u32& finalTevCount, u32& finalKColorCount, const CModelFlags& modelFlags,\n                                 u32 blendFactors, u32& finalCCFlags, u32& finalACFlags);\n  static u32 HandleReflection(bool usesTevReg2, u32 indTexSlot, u32 r5, u32 finalTevCount, u32 texCount, u32 tcgCount,\n                              u32 finalKColorCount, u32& finalCCFlags, u32& finalACFlags);\n  static void DoPassthru(u32 finalTevCount);\n  static void DoModelShadow(u32 texCount, u32 tcgCount);\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CCubeModel.cpp",
    "content": "#include \"CCubeModel.hpp\"\n\n#include \"CSimplePool.hpp\"\n#include \"Graphics/CCubeMaterial.hpp\"\n#include \"Graphics/CCubeSurface.hpp\"\n#include \"Graphics/CGraphics.hpp\"\n#include \"Graphics/CModel.hpp\"\n#include \"Graphics/CGX.hpp\"\n\nnamespace metaforce {\nbool CCubeModel::sRenderModelBlack = false;\nbool CCubeModel::sRenderModelShadow = false;\nbool CCubeModel::sUsingPackedLightmaps = false;\nCTexture* CCubeModel::sShadowTexture = nullptr;\nzeus::CTransform CCubeModel::sTextureProjectionTransform;\nGX::LightMask CCubeModel::sChannel0DisableLightMask;\nGX::LightMask CCubeModel::sChannel1EnableLightMask;\n\nstatic bool sDrawingOccluders = false;\nstatic bool sDrawingWireframe = false;\n\nstatic zeus::CVector3f sPlayerPosition;\n\nCCubeModel::CCubeModel(std::vector<CCubeSurface>* surfaces, std::vector<TCachedToken<CTexture>>* textures,\n                       u8* materialData, std::span<const u8> positions, std::span<const u8> colors,\n                       std::span<const u8> normals, std::span<const u8> texCoords, std::span<const u8> packedTexCoords,\n                       const zeus::CAABox& aabb, u8 flags, bool b1, u32 idx)\n: x0_modelInstance(surfaces, materialData, positions, colors, normals, texCoords, packedTexCoords)\n, x1c_textures(textures)\n, x20_worldAABB(aabb)\n, x40_24_texturesLoaded(!b1)\n, x41_visorFlags(flags)\n, x44_idx(idx) {\n  for (auto& surf : *x0_modelInstance.Surfaces()) {\n    surf.SetParent(this);\n  }\n\n  for (u32 i = x0_modelInstance.Surfaces()->size(); i > 0; --i) {\n    auto& surf = (*x0_modelInstance.Surfaces())[i - 1];\n    const auto matFlags = GetMaterialByIndex(surf.GetMaterialIndex()).GetFlags();\n    if (!matFlags.IsSet(CCubeMaterialFlagBits::fDepthSorting)) {\n      surf.SetNextSurface(x38_firstUnsortedSurf);\n      x38_firstUnsortedSurf = &surf;\n    } else {\n      surf.SetNextSurface(x3c_firstSortedSurf);\n      x3c_firstSortedSurf = &surf;\n    }\n  }\n}\n\nCCubeMaterial CCubeModel::GetMaterialByIndex(u32 idx) {\n  u32 materialOffset = 0;\n  const u8* matData = x0_modelInstance.GetMaterialPointer();\n  matData += (x1c_textures->size() + 1) * 4;\n  if (idx != 0) {\n    materialOffset = SBig(*reinterpret_cast<const u32*>(matData + (idx * 4)));\n  }\n\n  u32 materialCount = SBig(*reinterpret_cast<const u32*>(matData));\n  return CCubeMaterial(matData + materialOffset + (materialCount * 4) + 4);\n}\n\nbool CCubeModel::TryLockTextures() {\n  if (!x40_24_texturesLoaded) {\n    bool texturesLoading = false;\n    for (auto& texture : *x1c_textures) {\n      texture.Lock();\n      bool loadTexture = true;\n      if (!texture.HasReference()) {\n        if (!texture.IsLocked() || texture.IsNull()) {\n          loadTexture = false;\n        } else {\n          texture.GetObj();\n        }\n      }\n      if (loadTexture) {\n        // if (!texture->LoadToMRAM()) {\n        //   texturesLoading = true;\n        // }\n      }\n    }\n    if (!texturesLoading) {\n      x40_24_texturesLoaded = true;\n    }\n  }\n  return x40_24_texturesLoaded;\n}\n\nvoid CCubeModel::UnlockTextures() {\n  for (auto& token : *x1c_textures) {\n    token.Unlock();\n  }\n}\n\nvoid CCubeModel::RemapMaterialData(u8* data, std::vector<TCachedToken<CTexture>>& textures) {\n  x0_modelInstance.SetMaterialPointer(data);\n  x1c_textures = &textures;\n  x40_24_texturesLoaded = false;\n}\n\nvoid CCubeModel::MakeTexturesFromMats(const u8* ptr, std::vector<TCachedToken<CTexture>>& textures, IObjectStore* store,\n                                      bool b1) {\n  const u32* curId = reinterpret_cast<const u32*>(ptr + 4);\n  u32 textureCount = SBig(*reinterpret_cast<const u32*>(ptr));\n  textures.reserve(textureCount);\n  for (u32 i = 0; i < textureCount; ++i) {\n    textures.emplace_back(store->GetObj({FOURCC('TXTR'), SBig(curId[i])}));\n\n    if (!b1 && textures.back().IsNull()) {\n      textures.back().GetObj();\n    }\n  }\n}\n\nvoid CCubeModel::Draw(const CModelFlags& flags) {\n  CCubeMaterial::KillCachedViewDepState();\n  SetArraysCurrent();\n  DrawSurfaces(flags);\n}\n\nvoid CCubeModel::Draw(TConstVectorRef positions, TConstVectorRef normals, const CModelFlags& flags) {\n  CCubeMaterial::KillCachedViewDepState();\n  SetSkinningArraysCurrent(positions, normals);\n  DrawSurfaces(flags);\n}\n\nvoid CCubeModel::DrawAlpha(const CModelFlags& flags) {\n  CCubeMaterial::KillCachedViewDepState();\n  SetArraysCurrent();\n  DrawAlphaSurfaces(flags);\n}\n\nvoid CCubeModel::DrawAlphaSurfaces(const CModelFlags& flags) {\n  if (sDrawingWireframe) {\n    const auto* surface = x3c_firstSortedSurf;\n    while (surface != nullptr) {\n      DrawSurfaceWireframe(*surface);\n      surface = surface->GetNextSurface();\n    }\n  } else if (TryLockTextures()) {\n    const auto* surface = x3c_firstSortedSurf;\n    while (surface != nullptr) {\n      DrawSurface(*surface, flags);\n      surface = surface->GetNextSurface();\n    }\n  }\n}\n\nvoid CCubeModel::DrawFlat(TConstVectorRef positions, TConstVectorRef normals, ESurfaceSelection surfaces) {\n  if (positions.empty()) {\n    SetArraysCurrent();\n  } else {\n    SetSkinningArraysCurrent(positions, normals);\n  }\n  if (surfaces != ESurfaceSelection::Sorted) {\n    const auto* surface = x38_firstUnsortedSurf;\n    while (surface != nullptr) {\n      const auto mat = GetMaterialByIndex(surface->GetMaterialIndex());\n      CGX::SetVtxDescv_Compressed(mat.GetVertexDesc());\n      CGX::CallDisplayList(surface->GetDisplayList(), surface->GetDisplayListSize());\n      surface = surface->GetNextSurface();\n    }\n  }\n  if (surfaces != ESurfaceSelection::Unsorted) {\n    const auto* surface = x3c_firstSortedSurf;\n    while (surface != nullptr) {\n      const auto mat = GetMaterialByIndex(surface->GetMaterialIndex());\n      CGX::SetVtxDescv_Compressed(mat.GetVertexDesc());\n      CGX::CallDisplayList(surface->GetDisplayList(), surface->GetDisplayListSize());\n      surface = surface->GetNextSurface();\n    }\n  }\n}\n\nvoid CCubeModel::DrawNormal(TConstVectorRef positions, TConstVectorRef normals, ESurfaceSelection surfaces) {\n  CGX::SetNumIndStages(0);\n  CGX::SetNumTevStages(1);\n  CGX::SetNumTexGens(1);\n  CGX::SetZMode(true, GX_LEQUAL, true);\n  CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);\n  CGX::SetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO);\n  CGX::SetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO);\n  CGX::SetStandardTevColorAlphaOp(GX_TEVSTAGE0);\n  CGX::SetBlendMode(GX_BM_BLEND, GX_BL_ZERO, GX_BL_ONE, GX_LO_CLEAR);\n  DrawFlat(positions, normals, surfaces);\n}\n\nvoid CCubeModel::DrawNormal(const CModelFlags& flags) {\n  CCubeMaterial::KillCachedViewDepState();\n  SetArraysCurrent();\n  DrawNormalSurfaces(flags);\n}\n\nvoid CCubeModel::DrawNormalSurfaces(const CModelFlags& flags) {\n  if (sDrawingWireframe) {\n    const auto* surface = x38_firstUnsortedSurf;\n    while (surface != nullptr) {\n      DrawSurfaceWireframe(*surface);\n      surface = surface->GetNextSurface();\n    }\n  } else if (TryLockTextures()) {\n    const auto* surface = x38_firstUnsortedSurf;\n    while (surface != nullptr) {\n      DrawSurface(*surface, flags);\n      surface = surface->GetNextSurface();\n    }\n  }\n}\n\nvoid CCubeModel::DrawSurface(const CCubeSurface& surface, const CModelFlags& flags) {\n  auto mat = GetMaterialByIndex(surface.GetMaterialIndex());\n  if (!mat.GetFlags().IsSet(CCubeMaterialFlagBits::fShadowOccluderMesh) || sDrawingOccluders) {\n    mat.SetCurrent(flags, surface, *this);\n    CGX::CallDisplayList(surface.GetDisplayList(), surface.GetDisplayListSize());\n  }\n}\n\nvoid CCubeModel::DrawSurfaces(const CModelFlags& flags) {\n  if (sDrawingWireframe) {\n    const auto* surface = x38_firstUnsortedSurf;\n    while (surface != nullptr) {\n      DrawSurfaceWireframe(*surface);\n      surface = surface->GetNextSurface();\n    }\n    surface = x3c_firstSortedSurf;\n    while (surface != nullptr) {\n      DrawSurfaceWireframe(*surface);\n      surface = surface->GetNextSurface();\n    }\n  } else if (TryLockTextures()) {\n    const auto* surface = x38_firstUnsortedSurf;\n    while (surface != nullptr) {\n      DrawSurface(*surface, flags);\n      surface = surface->GetNextSurface();\n    }\n    surface = x3c_firstSortedSurf;\n    while (surface != nullptr) {\n      DrawSurface(*surface, flags);\n      surface = surface->GetNextSurface();\n    }\n  }\n}\n\nvoid CCubeModel::DrawSurfaceWireframe(const CCubeSurface& surface) {\n  auto mat = GetMaterialByIndex(surface.GetMaterialIndex());\n  // TODO convert vertices to line strips and draw\n}\n\nvoid CCubeModel::EnableShadowMaps(CTexture& shadowTex, const zeus::CTransform& textureProjXf,\n                                  GX::LightMask chan0DisableMask, GX::LightMask chan1EnableLightMask) {\n  sRenderModelShadow = true;\n  sShadowTexture = &shadowTex;\n  sTextureProjectionTransform = textureProjXf;\n  sChannel0DisableLightMask = chan0DisableMask;\n  sChannel1EnableLightMask = chan1EnableLightMask;\n}\n\nvoid CCubeModel::DisableShadowMaps() { sRenderModelShadow = false; }\n\nvoid CCubeModel::SetArraysCurrent() {\n  CGX::SetArray(GX_VA_POS, x0_modelInstance.GetVertexPointer(), 12);\n  CGX::SetArray(GX_VA_NRM, x0_modelInstance.GetNormalPointer(), (x41_visorFlags & 1) != 0 ? 6 : 12);\n  SetStaticArraysCurrent();\n}\n\nvoid CCubeModel::SetDrawingOccluders(bool v) { sDrawingOccluders = v; }\n\nvoid CCubeModel::SetModelWireframe(bool v) { sDrawingWireframe = v; }\n\nvoid CCubeModel::SetNewPlayerPositionAndTime(const zeus::CVector3f& pos, const CStopwatch& time) {\n  sPlayerPosition = pos;\n  CCubeMaterial::KillCachedViewDepState();\n  // TODO time\n}\n\nvoid CCubeModel::SetRenderModelBlack(bool v) {\n  sRenderModelBlack = v;\n  // TODO another value is set here, but always 0?\n}\n\nvoid CCubeModel::SetSkinningArraysCurrent(TConstVectorRef positions, TConstVectorRef normals) {\n  // Aurora addition: clear current array to force reupload\n  CGX::ClearArray(GX_VA_POS);\n  CGX::ClearArray(GX_VA_NRM);\n  CGX::SetArray(GX_VA_POS, positions);\n  CGX::SetArray(GX_VA_NRM, normals);\n  // colors unused\n  SetStaticArraysCurrent();\n}\n\nvoid CCubeModel::SetStaticArraysCurrent() {\n  if (!x0_modelInstance.GetColorPointer().empty()) {\n    CGX::SetArray(GX_VA_CLR0, x0_modelInstance.GetColorPointer(), 4);\n  }\n  const auto packedTexCoords = x0_modelInstance.GetPackedTCPointer();\n  const auto texCoords = x0_modelInstance.GetTCPointer();\n  sUsingPackedLightmaps = !packedTexCoords.empty();\n  if (sUsingPackedLightmaps) {\n    CGX::SetArray(GX_VA_TEX0, packedTexCoords, 4);\n  } else {\n    CGX::SetArray(GX_VA_TEX0, texCoords, 8);\n  }\n  for (int i = GX_VA_TEX1; i <= GX_VA_TEX7; ++i) {\n    CGX::SetArray(static_cast<GXAttr>(i), texCoords, 8);\n  }\n  CCubeMaterial::KillCachedViewDepState();\n}\n\nvoid CCubeModel::SetUsingPackedLightmaps(bool v) {\n  sUsingPackedLightmaps = v;\n  if (v) {\n    CGX::SetArray(GX_VA_TEX0, x0_modelInstance.GetPackedTCPointer(), 4);\n  } else {\n    CGX::SetArray(GX_VA_TEX0, x0_modelInstance.GetTCPointer(), 8);\n  }\n}\n\ntemplate <>\naurora::Vec2<float> cinput_stream_helper(CInputStream& in) {\n  const auto x = in.ReadFloat();\n  const auto y = in.ReadFloat();\n  return {x, y};\n}\n\ntemplate <>\naurora::Vec3<float> cinput_stream_helper(CInputStream& in) {\n  const auto x = in.ReadFloat();\n  const auto y = in.ReadFloat();\n  const auto z = in.ReadFloat();\n  return {x, y, z};\n}\n\ntemplate <>\naurora::Vec2<u16> cinput_stream_helper(CInputStream& in) {\n  const auto x = in.ReadUint16();\n  const auto y = in.ReadUint16();\n  return {x, y};\n}\n\ntemplate <>\naurora::Vec3<s16> cinput_stream_helper(CInputStream& in) {\n  const auto x = in.ReadInt16();\n  const auto y = in.ReadInt16();\n  const auto z = in.ReadInt16();\n  return {x, y, z};\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CCubeModel.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <vector>\n#include <span>\n#include <aurora/math.hpp>\n\n#include \"CStopwatch.hpp\"\n#include \"CToken.hpp\"\n#include \"GCNTypes.hpp\"\n#include \"Graphics/CTexture.hpp\"\n#include \"IObjectStore.hpp\"\n\nnamespace metaforce {\nclass CCubeSurface;\nclass CCubeMaterial;\nstruct CModelFlags;\n\nenum class ESurfaceSelection {\n  Unsorted,\n  Sorted,\n  All,\n};\n\n// These parameters were originally float*\nusing TVectorRef = std::vector<aurora::Vec3<float>>*;\nusing TConstVectorRef = std::span<const aurora::Vec3<float>>;\n\ntemplate <typename T>\nstd::span<const u8> byte_span(const std::vector<T>& vec) {\n  return std::span(reinterpret_cast<const u8*>(vec.data()), vec.size() * sizeof(T));\n}\n\nclass CCubeModel {\n  friend class CModel;\n  friend class CCubeMaterial;\n\nprivate:\n  class ModelInstance {\n    std::vector<CCubeSurface>* x0_surfacePtrs; // was rstl::vector<void*>*\n    u8* x4_materialData;                       //\n    std::span<const u8> x8_positions;          // was void*\n    std::span<const u8> xc_normals;            // was void*\n    std::span<const u8> x10_colors;            // was void*\n    std::span<const u8> x14_texCoords;         // was void*\n    std::span<const u8> x18_packedTexCoords;   // was void*\n\n  public:\n    ModelInstance(std::vector<CCubeSurface>* surfaces, u8* material, std::span<const u8> positions,\n                  std::span<const u8> colors, std::span<const u8> normals, std::span<const u8> texCoords,\n                  std::span<const u8> packedTexCoords)\n    : x0_surfacePtrs(surfaces)\n    , x4_materialData(material)\n    , x8_positions(positions)\n    , xc_normals(normals)\n    , x10_colors(colors)\n    , x14_texCoords(texCoords)\n    , x18_packedTexCoords(packedTexCoords) {}\n\n    /*\n     * These functions have been slightly modified from their original to return the actual vector instead of a raw\n     * pointer\n     */\n    [[nodiscard]] std::vector<CCubeSurface>* Surfaces() const { return x0_surfacePtrs; }\n    [[nodiscard]] u8* GetMaterialPointer() const { return x4_materialData; }\n    void SetMaterialPointer(u8* mat) { x4_materialData = mat; }\n    [[nodiscard]] std::span<const u8> GetVertexPointer() const { return x8_positions; }\n    [[nodiscard]] std::span<const u8> GetNormalPointer() const { return xc_normals; }\n    [[nodiscard]] std::span<const u8> GetColorPointer() const { return x10_colors; }\n    [[nodiscard]] std::span<const u8> GetTCPointer() const { return x14_texCoords; }\n    [[nodiscard]] std::span<const u8> GetPackedTCPointer() const { return x18_packedTexCoords; }\n  };\n\n  ModelInstance x0_modelInstance;\n  std::vector<TCachedToken<CTexture>>* x1c_textures;\n  zeus::CAABox x20_worldAABB;\n  CCubeSurface* x38_firstUnsortedSurf = nullptr;\n  CCubeSurface* x3c_firstSortedSurf = nullptr;\n  bool x40_24_texturesLoaded : 1 = false;\n  bool x40_25_modelVisible : 1 = false;\n  u8 x41_visorFlags;\n  u32 x44_idx;\n\npublic:\n  CCubeModel(std::vector<CCubeSurface>* surfaces, std::vector<TCachedToken<CTexture>>* textures, u8* materialData,\n             std::span<const u8> positions, std::span<const u8> colors, std::span<const u8> normals,\n             std::span<const u8> texCoords, std::span<const u8> packedTexCoords, const zeus::CAABox& aabb, u8 flags,\n             bool b1, u32 idx);\n\n  CCubeMaterial GetMaterialByIndex(u32 idx);\n  bool TryLockTextures();\n  void UnlockTextures();\n  void RemapMaterialData(u8* data, std::vector<TCachedToken<CTexture>>& textures);\n  void Draw(const CModelFlags& flags);\n  void DrawAlpha(const CModelFlags& flags);\n  void DrawFlat(TConstVectorRef positions, TConstVectorRef normals, ESurfaceSelection surfaces);\n  void DrawNormal(TConstVectorRef positions, TConstVectorRef normals, ESurfaceSelection surfaces);\n  void DrawNormal(const CModelFlags& flags);\n  void DrawSurface(const CCubeSurface& surface, const CModelFlags& flags);\n  void DrawSurfaceWireframe(const CCubeSurface& surface);\n  void SetArraysCurrent();\n  void SetUsingPackedLightmaps(bool v);\n  zeus::CAABox GetBounds() const { return x20_worldAABB; }\n  u8 GetFlags() const { return x41_visorFlags; }\n  bool AreTexturesLoaded() const { return x40_24_texturesLoaded; }\n  void SetVisible(bool v) { x40_25_modelVisible = v; }\n  bool IsVisible() const { return x40_25_modelVisible; }\n  [[nodiscard]] u32 GetIndex() const { return x44_idx; }\n  [[nodiscard]] CCubeSurface* GetFirstUnsortedSurface() { return x38_firstUnsortedSurf; }\n  [[nodiscard]] const CCubeSurface* GetFirstUnsortedSurface() const { return x38_firstUnsortedSurf; }\n  [[nodiscard]] CCubeSurface* GetFirstSortedSurface() { return x3c_firstSortedSurf; }\n  [[nodiscard]] const CCubeSurface* GetFirstSortedSurface() const { return x3c_firstSortedSurf; }\n\n  [[nodiscard]] TConstVectorRef GetPositions() const {\n    const auto sp = x0_modelInstance.GetVertexPointer();\n    return {reinterpret_cast<const aurora::Vec3<float>*>(sp.data()), sp.size() / sizeof(aurora::Vec3<float>)};\n  }\n  [[nodiscard]] TConstVectorRef GetNormals() const {\n    const auto sp = x0_modelInstance.GetNormalPointer();\n    return {reinterpret_cast<const aurora::Vec3<float>*>(sp.data()), sp.size() / sizeof(aurora::Vec3<float>)};\n  }\n  [[nodiscard]] TCachedToken<CTexture>& GetTexture(u32 idx) const { return x1c_textures->at(idx); }\n\n  static void EnableShadowMaps(CTexture& shadowTex, const zeus::CTransform& textureProjXf,\n                               GX::LightMask chan0DisableMask, GX::LightMask chan1EnableLightMask);\n  static void DisableShadowMaps();\n  static void MakeTexturesFromMats(const u8* ptr, std::vector<TCachedToken<CTexture>>& texture, IObjectStore* store,\n                                   bool b1);\n  static void SetDrawingOccluders(bool v);\n  static void SetModelWireframe(bool v);\n  static void SetNewPlayerPositionAndTime(const zeus::CVector3f& pos, const CStopwatch& time);\n  static void SetRenderModelBlack(bool v);\n\nprivate:\n  void Draw(TConstVectorRef positions, TConstVectorRef normals, const CModelFlags& flags);\n  void DrawAlphaSurfaces(const CModelFlags& flags);\n  void DrawNormalSurfaces(const CModelFlags& flags);\n  void DrawSurfaces(const CModelFlags& flags);\n  void SetSkinningArraysCurrent(TConstVectorRef positions, TConstVectorRef normals);\n  void SetStaticArraysCurrent();\n\n  static bool sRenderModelBlack;\n  static bool sUsingPackedLightmaps;\n  static bool sRenderModelShadow;\n  static CTexture* sShadowTexture;\n  static zeus::CTransform sTextureProjectionTransform;\n  static GX::LightMask sChannel0DisableLightMask;\n  static GX::LightMask sChannel1EnableLightMask;\n};\n\ntemplate <>\naurora::Vec2<float> cinput_stream_helper(CInputStream& in);\ntemplate <>\naurora::Vec3<float> cinput_stream_helper(CInputStream& in);\ntemplate <>\naurora::Vec2<u16> cinput_stream_helper(CInputStream& in);\ntemplate <>\naurora::Vec3<s16> cinput_stream_helper(CInputStream& in);\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CCubeRenderer.cpp",
    "content": "#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeMaterial.hpp\"\n#include \"Runtime/Graphics/CCubeModel.hpp\"\n#include \"Runtime/Graphics/CCubeSurface.hpp\"\n#include \"Runtime/Graphics/CDrawable.hpp\"\n#include \"Runtime/Graphics/CDrawablePlaneObject.hpp\"\n#include \"Runtime/Graphics/CGX.hpp\"\n#include \"Runtime/Graphics/CLight.hpp\"\n#include \"Runtime/Graphics/CMetroidModelInstance.hpp\"\n#include \"Runtime/Graphics/CModel.hpp\"\n#include \"Runtime/World/CGameArea.hpp\"\n#include \"Runtime/Particle/CParticleGen.hpp\"\n#include \"Runtime/Particle/CDecal.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/CDvdFile.hpp\"\n#include \"Runtime/Logging.hpp\"\n\n#include <utility>\n\nnamespace metaforce {\n/* TODO: This is to fix some areas exceeding the max drawable count, the proper number is 128 drawables per bucket */\n// using BucketHolderType = rstl::reserved_vector<CDrawable*, 128>;\nusing BucketHolderType = rstl::reserved_vector<CDrawable*, 132>;\nstatic rstl::reserved_vector<CDrawable, 512> sDataHolder;\nstatic rstl::reserved_vector<BucketHolderType, 50> sBucketsHolder;\nstatic rstl::reserved_vector<CDrawablePlaneObject, 8> sPlaneObjectDataHolder;\nstatic rstl::reserved_vector<u16, 8> sPlaneObjectBucketHolder;\n\nclass Buckets {\n  friend class CCubeRenderer;\n\n  static inline rstl::reserved_vector<u16, 50> sBucketIndex;\n  static inline rstl::reserved_vector<CDrawable, 512>* sData = nullptr;\n  static inline rstl::reserved_vector<BucketHolderType, 50>* sBuckets = nullptr;\n  static inline rstl::reserved_vector<CDrawablePlaneObject, 8>* sPlaneObjectData = nullptr;\n  static inline rstl::reserved_vector<u16, 8>* sPlaneObjectBucket = nullptr;\n  static constexpr std::array skWorstMinMaxDistance{99999.0f, -99999.0f};\n  static inline std::array sMinMaxDistance{99999.0f, -99999.0f};\n\npublic:\n  static void Clear();\n  static void Sort();\n  static void InsertPlaneObject(float closeDist, float farDist, const zeus::CAABox& aabb, bool invertTest,\n                                const zeus::CPlane& plane, bool zOnly, EDrawableType dtype, void* data);\n  static void Insert(const zeus::CVector3f& pos, const zeus::CAABox& aabb, EDrawableType dtype, void* data,\n                     const zeus::CPlane& plane, u16 extraSort);\n  static void Shutdown();\n  static void Init();\n};\n\nvoid Buckets::Clear() {\n  sData->clear();\n  sBucketIndex.clear();\n  sPlaneObjectData->clear();\n  sPlaneObjectBucket->clear();\n  for (BucketHolderType& bucket : *sBuckets) {\n    bucket.clear();\n  }\n  sMinMaxDistance = skWorstMinMaxDistance;\n}\n\nvoid Buckets::Sort() {\n  float delta = std::max(1.f, sMinMaxDistance[1] - sMinMaxDistance[0]);\n  float pitch = 49.f / delta;\n  for (auto it = sPlaneObjectData->begin(); it != sPlaneObjectData->end(); ++it) {\n    if (sPlaneObjectBucket->size() != sPlaneObjectBucket->capacity()) {\n      sPlaneObjectBucket->push_back(s16(it - sPlaneObjectData->begin()));\n    }\n  }\n\n  u32 precision = 50;\n  if (!sPlaneObjectBucket->empty()) {\n    std::sort(sPlaneObjectBucket->begin(), sPlaneObjectBucket->end(),\n              [](u16 a, u16 b) { return (*sPlaneObjectData)[a].GetDistance() < (*sPlaneObjectData)[b].GetDistance(); });\n    precision = 50 / u32(sPlaneObjectBucket->size() + 1);\n    pitch = 1.f / (delta / float(precision - 2));\n\n    s32 accum = 0;\n    for (u16 idx : *sPlaneObjectBucket) {\n      ++accum;\n      CDrawablePlaneObject& planeObj = (*sPlaneObjectData)[idx];\n      planeObj.x24_targetBucket = u16(precision * accum);\n    }\n  }\n\n  for (CDrawable& drawable : *sData) {\n    s32 slot = -1;\n    float relDist = drawable.GetDistance() - sMinMaxDistance[0];\n    if (sPlaneObjectBucket->empty()) {\n      slot = zeus::clamp(1, s32(relDist * pitch), 49);\n    } else {\n      slot = zeus::clamp(0, s32(relDist * pitch), s32(precision) - 2);\n      for (u16 idx : *sPlaneObjectBucket) {\n        CDrawablePlaneObject& planeObj = (*sPlaneObjectData)[idx];\n        bool partial = false;\n        bool full = false;\n        if (planeObj.x3c_25_zOnly) {\n          partial = drawable.GetBounds().max.z() > planeObj.GetPlane().d();\n          full = drawable.GetBounds().min.z() > planeObj.GetPlane().d();\n        } else {\n          partial = planeObj.GetPlane().pointToPlaneDist(\n                        drawable.GetBounds().closestPointAlongVector(planeObj.GetPlane().normal())) > 0.f;\n          full = planeObj.GetPlane().pointToPlaneDist(\n                     drawable.GetBounds().furthestPointAlongVector(planeObj.GetPlane().normal())) > 0.f;\n        }\n        bool cont = false;\n        if (drawable.GetType() == EDrawableType::Particle) {\n          cont = planeObj.x3c_24_invertTest ? !partial : full;\n        } else {\n          cont = planeObj.x3c_24_invertTest ? (!partial || !full) : (partial || full);\n        }\n        if (!cont) {\n          break;\n        }\n        slot += s32(precision);\n      }\n    }\n\n    if (slot == -1) {\n      slot = 49;\n    }\n    BucketHolderType& bucket = (*sBuckets)[slot];\n    if (bucket.size() < bucket.capacity()) {\n      bucket.push_back(&drawable);\n    }\n    // else\n    //    spdlog::fatal(\"Full bucket!!!\");\n  }\n\n  u16 bucketIdx = u16(sBuckets->size());\n  for (auto it = sBuckets->rbegin(); it != sBuckets->rend(); ++it) {\n    --bucketIdx;\n    sBucketIndex.push_back(bucketIdx);\n    BucketHolderType& bucket = *it;\n    if (bucket.size()) {\n      std::sort(bucket.begin(), bucket.end(), [](CDrawable* a, CDrawable* b) {\n        if (a->GetDistance() == b->GetDistance())\n          return a->GetExtraSort() > b->GetExtraSort();\n        return a->GetDistance() > b->GetDistance();\n      });\n    }\n  }\n\n  for (auto it = sPlaneObjectBucket->rbegin(); it != sPlaneObjectBucket->rend(); ++it) {\n    CDrawablePlaneObject& planeObj = (*sPlaneObjectData)[*it];\n    BucketHolderType& bucket = (*sBuckets)[planeObj.x24_targetBucket];\n    bucket.push_back(&planeObj);\n  }\n}\n\nvoid Buckets::InsertPlaneObject(float closeDist, float farDist, const zeus::CAABox& aabb, bool invertTest,\n                                const zeus::CPlane& plane, bool zOnly, EDrawableType dtype, void* data) {\n  if (sPlaneObjectData->size() == sPlaneObjectData->capacity()) {\n    return;\n  }\n  sPlaneObjectData->emplace_back(dtype, closeDist, farDist, aabb, invertTest, plane, zOnly, data);\n}\n\nvoid Buckets::Insert(const zeus::CVector3f& pos, const zeus::CAABox& aabb, EDrawableType dtype, void* data,\n                     const zeus::CPlane& plane, u16 extraSort) {\n  if (sData->size() == sData->capacity()) {\n    spdlog::fatal(\"Rendering buckets filled to capacity\");\n    return;\n  }\n\n  const float dist = plane.pointToPlaneDist(pos);\n  sData->emplace_back(dtype, extraSort, dist, aabb, data);\n  sMinMaxDistance[0] = std::min(sMinMaxDistance[0], dist);\n  sMinMaxDistance[1] = std::max(sMinMaxDistance[1], dist);\n}\n\nvoid Buckets::Shutdown() {\n  sData = nullptr;\n  sBuckets = nullptr;\n  sPlaneObjectData = nullptr;\n  sPlaneObjectBucket = nullptr;\n}\n\nvoid Buckets::Init() {\n  sData = &sDataHolder;\n  sBuckets = &sBucketsHolder;\n  sBuckets->resize(50);\n  sPlaneObjectData = &sPlaneObjectDataHolder;\n  sPlaneObjectBucket = &sPlaneObjectBucketHolder;\n  sMinMaxDistance = skWorstMinMaxDistance;\n}\n\nCCubeRenderer::CAreaListItem::CAreaListItem(const std::vector<CMetroidModelInstance>* geom,\n                                            const CAreaRenderOctTree* octTree,\n                                            std::unique_ptr<std::vector<TCachedToken<CTexture>>>&& textures,\n                                            std::unique_ptr<std::vector<std::unique_ptr<CCubeModel>>>&& models,\n                                            s32 areaIdx)\n: x0_geometry(geom)\n, x4_octTree(octTree)\n, x8_textures(std::move(textures))\n, x10_models(std::move(models))\n, x18_areaIdx(areaIdx) {}\n\nCCubeRenderer::CCubeRenderer(IObjectStore& store, IFactory& resFac) : x8_factory(resFac), xc_store(store) {\n  void* data = xe4_blackTex.Lock();\n  memset(data, 0, 32);\n  xe4_blackTex.UnLock();\n  GenerateReflectionTex();\n  GenerateFogVolumeRampTex();\n  GenerateSphereRampTex();\n  LoadThermoPalette();\n  g_Renderer = this;\n  Buckets::Init();\n  // GX draw sync\n}\n\nCCubeRenderer::~CCubeRenderer() { g_Renderer = nullptr; }\n\nvoid CCubeRenderer::GenerateReflectionTex() {\n  // TODO\n}\n\nvoid CCubeRenderer::GenerateFogVolumeRampTex() {\n  constexpr double fogVolFar = 750.0;\n  constexpr double fogVolNear = 0.2;\n  u8* data = x1b8_fogVolumeRamp.Lock();\n  u16 height = x1b8_fogVolumeRamp.GetHeight();\n  u16 width = x1b8_fogVolumeRamp.GetWidth();\n  for (size_t y = 0; y < height; ++y) {\n    for (size_t x = 0; x < width; ++x) {\n      const int tmp = int(y << 16 | x << 8 | 0x7f);\n      const double a =\n          zeus::clamp(0.0,\n                      (-150.0 / (tmp / double(0xffffff) * (fogVolFar - fogVolNear) - fogVolFar) - fogVolNear) * 3.0 /\n                          (fogVolFar - fogVolNear),\n                      1.0);\n      data[y * width + x] = (a * a + a) / 2.0;\n    }\n  }\n  x1b8_fogVolumeRamp.UnLock();\n}\n\nvoid CCubeRenderer::GenerateSphereRampTex() {\n  u8* data = x220_sphereRamp.Lock();\n  const size_t height = x220_sphereRamp.GetHeight();\n  const size_t width = x220_sphereRamp.GetWidth();\n  const float halfRes = height / 2.f;\n  for (size_t y = 0; y < height; ++y) {\n    for (size_t x = 0; x < width; ++x) {\n      const zeus::CVector2f vec{\n          (static_cast<float>(x) - halfRes) / halfRes,\n          (static_cast<float>(y) - halfRes) / halfRes,\n      };\n      data[y * width + x] = 255 - zeus::clamp(0.f, vec.canBeNormalized() ? vec.magnitude() : 0.f, 1.f) * 255;\n    }\n  }\n  x220_sphereRamp.UnLock();\n}\n\nvoid CCubeRenderer::LoadThermoPalette() {\n  auto* out = x288_thermoPalette.Lock();\n  TToken<CTexture> token = xc_store.GetObj(\"TXTR_ThermoPalette\");\n  const auto* data = token.GetObj()->GetPalette()->GetPaletteData();\n  memcpy(out, data, 32);\n  x288_thermoPalette.UnLock();\n}\n\nvoid CCubeRenderer::ReallyDrawPhazonSuitIndirectEffect(const zeus::CColor& vertColor, CTexture& maskTex,\n                                                       CTexture& indTex, const zeus::CColor& modColor, float scale,\n                                                       float offX, float offY) {\n  // TODO\n}\n\nvoid CCubeRenderer::ReallyDrawPhazonSuitEffect(const zeus::CColor& modColor, CTexture& maskTex) {\n  // TODO\n}\n\nvoid CCubeRenderer::DoPhazonSuitIndirectAlphaBlur(float blurRadius, float f2) {\n  // TODO\n}\n\nvoid CCubeRenderer::AddWorldSurfaces(CCubeModel& model) {\n  for (auto* it = model.GetFirstSortedSurface(); it != nullptr; it = it->GetNextSurface()) {\n    auto mat = model.GetMaterialByIndex(it->GetMaterialIndex());\n    auto blend = mat.GetCompressedBlend();\n    auto bounds = it->GetBounds();\n    auto pos = bounds.closestPointAlongVector(xb0_viewPlane.normal());\n    Buckets::Insert(pos, bounds, EDrawableType::WorldSurface, it, xb0_viewPlane, static_cast<u16>(blend == 0x50004));\n  }\n}\n\nvoid CCubeRenderer::AddStaticGeometry(const std::vector<CMetroidModelInstance>* geometry,\n                                      const CAreaRenderOctTree* octTree, s32 areaIdx) {\n  auto search = FindStaticGeometry(geometry);\n  if (search == x1c_areaListItems.end()) {\n    auto textures = std::make_unique<std::vector<TCachedToken<CTexture>>>();\n    auto models = std::make_unique<std::vector<std::unique_ptr<CCubeModel>>>();\n    if (!geometry->empty()) {\n      CCubeModel::MakeTexturesFromMats((*geometry)[0].GetMaterialPointer(), *textures.get(), &xc_store, false);\n      models->reserve(geometry->size());\n      s32 instIdx = 0;\n      for (const CMetroidModelInstance& inst : *geometry) {\n        models->emplace_back(std::make_unique<CCubeModel>(\n            const_cast<std::vector<CCubeSurface>*>(inst.GetSurfaces()), textures.get(),\n            const_cast<u8*>(inst.GetMaterialPointer()), inst.GetVertexPointer(), inst.GetColorPointer(),\n            inst.GetNormalPointer(), inst.GetTCPointer(), inst.GetPackedTCPointer(), inst.GetBoundingBox(),\n            inst.GetFlags(), false, instIdx));\n        ++instIdx;\n      }\n    }\n    x1c_areaListItems.emplace_back(geometry, octTree, std::move(textures), std::move(models), areaIdx);\n  }\n}\n\nvoid CCubeRenderer::EnablePVS(const CPVSVisSet& set, u32 areaIdx) {\n  xc8_pvs.emplace(set);\n  xe0_pvsAreaIdx = areaIdx;\n}\n\nvoid CCubeRenderer::DisablePVS() { xc8_pvs.reset(); }\n\nvoid CCubeRenderer::RemoveStaticGeometry(const std::vector<CMetroidModelInstance>* geometry) {\n  auto search = FindStaticGeometry(geometry);\n  if (search != x1c_areaListItems.end()) {\n    x1c_areaListItems.erase(search);\n  }\n}\n\nvoid CCubeRenderer::DrawUnsortedGeometry(s32 areaIdx, s32 mask, s32 targetMask) {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\n      fmt::format(\"CCubeRenderer::DrawUnsortedGeometry areaIdx={} mask={} targetMask={}\", areaIdx, mask, targetMask)\n          .c_str(),\n      zeus::skBlue);\n\n  SetupRendererStates(true);\n  CModelFlags flags;\n  CAreaListItem* lastOctreeItem = nullptr;\n\n  for (CAreaListItem& item : x1c_areaListItems) {\n    if (areaIdx != -1 && item.x18_areaIdx != areaIdx) {\n      continue;\n    }\n\n    if (item.x4_octTree != nullptr) {\n      lastOctreeItem = &item;\n    }\n\n    CPVSVisSet* pvs = nullptr;\n    if (xc8_pvs) {\n      pvs = &*xc8_pvs;\n    }\n\n    if (xe0_pvsAreaIdx != item.x18_areaIdx) {\n      pvs = nullptr;\n    }\n\n    u32 idx = 0;\n    for (auto it = item.x10_models->begin(); it != item.x10_models->end(); ++it, ++idx) {\n      const auto& model = *it;\n      if (pvs != nullptr) {\n        bool vis = pvs->GetVisible(idx) != EPVSVisSetState::EndOfTree;\n        switch (xc0_pvsMode) {\n        case EPVSMode::PVS: {\n          if (!vis) {\n            model->SetVisible(false);\n            continue;\n          }\n          break;\n        }\n        case EPVSMode::PVSAndMask: {\n          if (!vis && (model->GetFlags() & mask) != targetMask) {\n            model->SetVisible(false);\n            continue;\n          }\n          break;\n        }\n        default:\n          break;\n        }\n      }\n\n      if ((model->GetFlags() & mask) != targetMask) {\n        model->SetVisible(false);\n        continue;\n      }\n\n      if (!x44_frustumPlanes.aabbFrustumTest(model->GetBounds())) {\n        model->SetVisible(false);\n        continue;\n      }\n\n      if (x318_25_drawWireframe) {\n        model->SetVisible(false);\n        HandleUnsortedModelWireframe(lastOctreeItem, *model);\n        continue;\n      }\n\n      model->SetVisible(true);\n      HandleUnsortedModel(lastOctreeItem, *model, flags);\n    }\n  }\n\n  SetupCGraphicsState();\n}\n\nvoid CCubeRenderer::DrawSortedGeometry(s32 areaIdx, s32 mask, s32 targetMask) {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\n      fmt::format(\"CCubeRenderer::DrawSortedGeometry areaIdx={} mask={} targetMask={}\", areaIdx, mask, targetMask)\n          .c_str(),\n      zeus::skBlue);\n\n  SetupRendererStates(true);\n  const CAreaListItem* item = nullptr;\n  for (const auto& areaListItem : x1c_areaListItems) {\n    if (areaIdx == -1 || areaIdx == areaListItem.x18_areaIdx) {\n      if (areaListItem.x4_octTree != nullptr) {\n        item = &areaListItem;\n      }\n\n      for (const auto& model : *areaListItem.x10_models) {\n        if (model->IsVisible()) {\n          AddWorldSurfaces(*model);\n        }\n      }\n    }\n  }\n  Buckets::Sort();\n  RenderBucketItems(item);\n  SetupCGraphicsState();\n  DrawRenderBucketsDebug();\n  Buckets::Clear();\n}\n\nvoid CCubeRenderer::DrawStaticGeometry(s32 areaIdx, s32 mask, s32 targetMask) {\n  DrawUnsortedGeometry(areaIdx, mask, targetMask);\n  DrawSortedGeometry(areaIdx, mask, targetMask);\n}\n\nvoid CCubeRenderer::DrawAreaGeometry(s32 areaIdx, s32 mask, s32 targetMask) {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\n      fmt::format(\"CCubeRenderer::DrawAreaGeometry areaIdx={} mask={} targetMask={}\", areaIdx, mask, targetMask)\n          .c_str(),\n      zeus::skBlue);\n\n  x318_30_inAreaDraw = true;\n  SetupRendererStates(true);\n  constexpr CModelFlags flags{0, 0, 3, zeus::skWhite};\n\n  for (CAreaListItem& item : x1c_areaListItems) {\n    if (areaIdx == -1 || item.x18_areaIdx == areaIdx) {\n      CPVSVisSet* pvs = xc8_pvs ? &*xc8_pvs : nullptr;\n      if (xe0_pvsAreaIdx != item.x18_areaIdx) {\n        pvs = nullptr;\n      }\n      s32 modelIdx = 0;\n      for (auto it = item.x10_models->begin(); it != item.x10_models->end(); ++it, ++modelIdx) {\n        const auto& model = *it;\n        if (pvs != nullptr) {\n          bool visible = pvs->GetVisible(modelIdx) != EPVSVisSetState::EndOfTree;\n          if ((xc0_pvsMode == EPVSMode::PVS && !visible) || (xc0_pvsMode == EPVSMode::PVSAndMask && visible)) {\n            continue;\n          }\n        }\n        if ((model->GetFlags() & mask) != targetMask) {\n          continue;\n        }\n        if (!x44_frustumPlanes.aabbFrustumTest(model->GetBounds())) {\n          continue;\n        }\n\n        model->SetArraysCurrent();\n        for (const auto* surf = model->GetFirstUnsortedSurface(); surf != nullptr; surf = surf->GetNextSurface()) {\n          model->DrawSurface(*surf, flags);\n        }\n        for (const auto* surf = model->GetFirstSortedSurface(); surf != nullptr; surf = surf->GetNextSurface()) {\n          model->DrawSurface(*surf, flags);\n        }\n      }\n    }\n  }\n\n  SetupCGraphicsState();\n  x318_30_inAreaDraw = false;\n}\n\nvoid CCubeRenderer::RenderBucketItems(const CAreaListItem* item) {\n  SCOPED_GRAPHICS_DEBUG_GROUP(fmt::format(\"CCubeRenderer::RenderBucketItems areaIdx={}\", item->x18_areaIdx).c_str(),\n                              zeus::skBlue);\n\n  CCubeModel* lastModel = nullptr;\n  EDrawableType lastDrawableType = EDrawableType::Invalid;\n  for (u16 idx : Buckets::sBucketIndex) {\n    BucketHolderType& bucket = (*Buckets::sBuckets)[idx];\n    for (CDrawable* drawable : bucket) {\n      EDrawableType type = drawable->GetType();\n      switch (type) {\n      case EDrawableType::Particle: {\n        if (lastDrawableType != EDrawableType::Particle) {\n          SetupCGraphicsState();\n        }\n\n        static_cast<CParticleGen*>(drawable->GetData())->Render();\n        break;\n      }\n      case EDrawableType::WorldSurface: {\n        if (lastDrawableType != EDrawableType::WorldSurface) {\n          SetupRendererStates(false);\n          lastModel = nullptr;\n        }\n\n        auto* surface = static_cast<CCubeSurface*>(drawable->GetData());\n        auto* model = surface->GetParent();\n        if (model != lastModel) {\n          model->SetArraysCurrent();\n          ActivateLightsForModel(item, *model);\n        }\n        model->DrawSurface(*surface, CModelFlags(0, 0, 1, zeus::skWhite));\n        break;\n      }\n      default: {\n        if (type != lastDrawableType) {\n          CCubeMaterial::EnsureTevsDirect();\n        }\n        if (xa8_drawableCallback != nullptr) {\n          xa8_drawableCallback(drawable->GetData(), xac_drawableCallbackUserData, s32(drawable->GetType()) - 2);\n        }\n        break;\n      }\n      }\n      lastDrawableType = type;\n    }\n  }\n}\n\nvoid CCubeRenderer::PostRenderFogs() {\n  for (const auto& warp : x2c4_spaceWarps) {\n    ReallyDrawSpaceWarp(warp.first, warp.second);\n  }\n  x2c4_spaceWarps.clear();\n\n  x2ac_fogVolumes.sort([](const CFogVolumeListItem& a, const CFogVolumeListItem& b) {\n    zeus::CAABox aabbA = a.x34_aabb.getTransformedAABox(a.x0_transform);\n    bool insideA = aabbA.pointInside(\n        zeus::CVector3f(CGraphics::mViewMatrix.origin.x(), CGraphics::mViewMatrix.origin.y(), aabbA.min.z()));\n\n    zeus::CAABox aabbB = b.x34_aabb.getTransformedAABox(b.x0_transform);\n    bool insideB = aabbB.pointInside(\n        zeus::CVector3f(CGraphics::mViewMatrix.origin.x(), CGraphics::mViewMatrix.origin.y(), aabbB.min.z()));\n\n    if (insideA != insideB) {\n      return insideA;\n    }\n\n    float dotA = aabbA.furthestPointAlongVector(CGraphics::mViewMatrix.basis[1]).dot(CGraphics::mViewMatrix.basis[1]);\n    float dotB = aabbB.furthestPointAlongVector(CGraphics::mViewMatrix.basis[1]).dot(CGraphics::mViewMatrix.basis[1]);\n    return dotA < dotB;\n  });\n  for (const CFogVolumeListItem& fog : x2ac_fogVolumes) {\n    CGraphics::SetModelMatrix(fog.x0_transform);\n    ReallyRenderFogVolume(fog.x30_color, fog.x34_aabb, fog.x4c_model.GetObj(), fog.x5c_skinnedModel);\n  }\n  x2ac_fogVolumes.clear();\n}\n\nvoid CCubeRenderer::SetModelMatrix(const zeus::CTransform& xf) { CGraphics::SetModelMatrix(xf); }\n\nvoid CCubeRenderer::HandleUnsortedModel(CAreaListItem* areaItem, CCubeModel& model, const CModelFlags& flags) {\n  if (model.GetFirstUnsortedSurface() == nullptr) {\n    return;\n  }\n  model.SetArraysCurrent();\n  ActivateLightsForModel(areaItem, model);\n  for (auto* it = model.GetFirstUnsortedSurface(); it != nullptr; it = it->GetNextSurface()) {\n    model.DrawSurface(*it, CModelFlags(0, 0, 3, zeus::skWhite));\n  }\n}\n\nvoid CCubeRenderer::HandleUnsortedModelWireframe(CAreaListItem* areaItem, CCubeModel& model) {\n  model.SetArraysCurrent();\n  ActivateLightsForModel(areaItem, model);\n  for (auto* it = model.GetFirstUnsortedSurface(); it != nullptr; it = it->GetNextSurface()) {\n    model.DrawSurfaceWireframe(*it);\n  }\n  for (auto* it = model.GetFirstSortedSurface(); it != nullptr; it = it->GetNextSurface()) {\n    model.DrawSurfaceWireframe(*it);\n  }\n}\n\nconstexpr bool TestBit(const u32* words, size_t bit) { return (words[bit / 32] & (1U << (bit & 0x1f))) != 0; }\n\nvoid CCubeRenderer::ActivateLightsForModel(const CAreaListItem* areaItem, CCubeModel& model) {\n  constexpr u32 LightCount = 4;\n  GX::LightMask lightMask;\n\n  if (!x300_dynamicLights.empty()) {\n    std::array<u32, LightCount> addedLights{};\n    std::array<float, LightCount> lightRads{-1.f, -1.f, -1.f, -1.f};\n\n    u32 lightOctreeWordCount = 0;\n    const u32* lightOctreeWords = nullptr;\n    if (areaItem != nullptr && model.GetIndex() != UINT32_MAX) {\n      lightOctreeWordCount = areaItem->x4_octTree->x14_bitmapWordCount;\n      lightOctreeWords = areaItem->x1c_lightOctreeWords.data();\n    }\n\n    u32 lightIdx = 0;\n    for (const auto& light : x300_dynamicLights) {\n      if (lightIdx >= LightCount) {\n        break;\n      }\n\n      if (lightOctreeWords == nullptr || TestBit(lightOctreeWords, model.GetIndex())) {\n        bool loaded = false;\n        const float radius =\n            model.GetBounds().intersectionRadius(zeus::CSphere(light.GetPosition(), light.GetRadius()));\n\n        if (lightIdx > 0) {\n          for (u32 i = 0; i < lightIdx; ++i) {\n            if (addedLights[i] == light.GetId()) {\n              if (radius >= 0.f && radius < lightRads[i]) {\n                lightRads[i] = radius;\n                CGraphics::LoadLight(i, light);\n                loaded = true;\n              }\n              break;\n            }\n          }\n        }\n\n        if (!loaded) {\n          lightRads[lightIdx] = radius;\n          if (radius >= 0.f) {\n            CGraphics::LoadLight(lightIdx, light);\n            addedLights[lightIdx] = light.GetId();\n            lightMask.set(lightIdx);\n            ++lightIdx;\n          }\n        }\n      }\n\n      lightOctreeWords += lightOctreeWordCount;\n    }\n  }\n\n  if (lightMask.any()) {\n    CGraphics::SetLightState(lightMask);\n    CGX::SetChanMatColor(CGX::EChannelId::Channel0, zeus::skWhite);\n  } else {\n    CGraphics::DisableAllLights();\n    CGX::SetChanMatColor(CGX::EChannelId::Channel0, CGX::GetChanAmbColor(CGX::EChannelId::Channel0));\n  }\n}\n\nvoid CCubeRenderer::AddParticleGen(CParticleGen& gen) {\n  auto bounds = gen.GetBounds();\n\n  if (bounds) {\n    auto closestPoint = bounds->closestPointAlongVector(xb0_viewPlane.normal());\n    Buckets::Insert(closestPoint, *bounds, EDrawableType::Particle, reinterpret_cast<void*>(&gen), xb0_viewPlane, 0);\n  }\n}\n\nvoid CCubeRenderer::AddParticleGen(CParticleGen& gen, const zeus::CVector3f& pos, const zeus::CAABox& bounds) {\n  Buckets::Insert(pos, bounds, EDrawableType::Particle, reinterpret_cast<void*>(&gen), xb0_viewPlane, 0);\n}\n\nvoid CCubeRenderer::AddPlaneObject(void* obj, const zeus::CAABox& aabb, const zeus::CPlane& plane, s32 type) {\n\n  const auto closestPoint = aabb.closestPointAlongVector(xb0_viewPlane.normal());\n  const auto closestDist = xb0_viewPlane.pointToPlaneDist(closestPoint);\n  const auto furthestPoint = aabb.furthestPointAlongVector(xb0_viewPlane.normal());\n  const auto furthestDist = xb0_viewPlane.pointToPlaneDist(furthestPoint);\n\n  if (closestDist >= 0.f || furthestDist >= 0.f) {\n    const bool zOnly = plane.normal() == zeus::skUp;\n    const bool invertTest = zOnly ? CGraphics::mViewMatrix.origin.z() >= plane.d()\n                                  : plane.pointToPlaneDist(CGraphics::mViewMatrix.origin) >= 0.f;\n    Buckets::InsertPlaneObject(closestDist, furthestDist, aabb, invertTest, plane, zOnly, EDrawableType(type + 2), obj);\n  }\n}\n\nvoid CCubeRenderer::AddDrawable(void* obj, const zeus::CVector3f& pos, const zeus::CAABox& aabb, s32 mode,\n                                IRenderer::EDrawableSorting sorting) {\n  if (sorting == EDrawableSorting::UnsortedCallback) {\n    xa8_drawableCallback(obj, xac_drawableCallbackUserData, mode);\n  } else {\n    Buckets::Insert(pos, aabb, EDrawableType(mode + 2), obj, xb0_viewPlane, 0);\n  }\n}\n\nvoid CCubeRenderer::SetDrawableCallback(IRenderer::TDrawableCallback cb, void* ctx) {\n  xa8_drawableCallback = cb;\n  xac_drawableCallbackUserData = ctx;\n}\n\nvoid CCubeRenderer::SetWorldViewpoint(const zeus::CTransform& xf) {\n  CGraphics::SetViewPointMatrix(xf);\n  auto front = xf.frontVector();\n  xb0_viewPlane = zeus::CPlane(front, front.dot(xf.origin));\n}\n\nvoid CCubeRenderer::SetPerspective(float fovy, float aspect, float znear, float zfar) {\n  CGraphics::SetPerspective(fovy, aspect, znear, zfar);\n}\n\nvoid CCubeRenderer::SetPerspective(float fovy, float width, float height, float znear, float zfar) {\n  CGraphics::SetPerspective(fovy, width / height, znear, zfar);\n}\n\nstd::pair<zeus::CVector2f, zeus::CVector2f> CCubeRenderer::SetViewportOrtho(bool centered, float znear, float zfar) {\n  auto left = static_cast<float>(centered ? CGraphics::GetViewportLeft() - CGraphics::GetViewportHalfWidth()\n                                          : CGraphics::GetViewportLeft());\n  auto bottom = static_cast<float>(centered ? CGraphics::GetViewportTop() - CGraphics::GetViewportHalfHeight()\n                                            : CGraphics::GetViewportTop());\n  auto right = static_cast<float>(CGraphics::GetViewportLeft() +\n                                  (centered ? CGraphics::GetViewportWidth() / 2 : CGraphics::GetViewportWidth()));\n  auto top = static_cast<float>(CGraphics::GetViewportTop() +\n                                (centered ? CGraphics::GetViewportHeight() / 2 : CGraphics::GetViewportHeight()));\n  CGraphics::SetOrtho(left, right, top, bottom, znear, zfar);\n  CGraphics::SetViewPointMatrix({});\n  CGraphics::SetModelMatrix({});\n  return {{left, bottom}, {right, top}};\n}\n\nvoid CCubeRenderer::SetClippingPlanes(const zeus::CFrustum& frustum) { x44_frustumPlanes = frustum; }\n\nvoid CCubeRenderer::SetViewport(s32 left, s32 bottom, s32 width, s32 height) {\n  CGraphics::SetViewport(left, bottom, width, height);\n  CGraphics::SetScissor(left, bottom, width, height);\n}\n\nvoid CCubeRenderer::BeginScene() {\n  CGraphics::SetUseVideoFilter(true);\n  CGraphics::SetViewport(0, 0, CGraphics::mRenderModeObj.fbWidth, CGraphics::mRenderModeObj.xfbHeight);\n\n  CGraphics::SetClearColor(zeus::skClear);\n  CGraphics::SetCullMode(ERglCullMode::Front);\n  CGraphics::SetDepthWriteMode(true, ERglEnum::LEqual, true);\n  CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::InvSrcAlpha,\n                          ERglLogicOp::Clear);\n  CGraphics::SetPerspective(75.f, CGraphics::mViewport.mWidth / CGraphics::mViewport.mHeight, 1.f, 4096.f);\n  CGraphics::SetModelMatrix(zeus::CTransform());\n  if (x310_phazonSuitMaskCountdown != 0) {\n    --x310_phazonSuitMaskCountdown;\n    if (x310_phazonSuitMaskCountdown == 0) {\n      x314_phazonSuitMask.reset();\n    }\n  }\n\n  x318_27_currentRGBA6 = x318_26_requestRGBA6;\n  if (!x318_31_persistRGBA6) {\n    x318_26_requestRGBA6 = false;\n  }\n\n  GXSetPixelFmt(x318_27_currentRGBA6 ? GX_PF_RGBA6_Z24 : GX_PF_RGB8_Z24, GX_ZC_LINEAR);\n  GXSetAlphaUpdate(true);\n  GXSetDstAlpha(true, 0.f);\n  CGraphics::BeginScene();\n}\n\nvoid CCubeRenderer::EndScene() {\n  x318_31_persistRGBA6 = !CGraphics::mIsBeginSceneClearFb;\n  CGraphics::EndScene();\n\n  if (x2dc_reflectionAge < 2) {\n    ++x2dc_reflectionAge;\n  } else {\n    x14c_reflectionTex.reset();\n  };\n}\n\nvoid CCubeRenderer::SetDebugOption(IRenderer::EDebugOption option, s32 value) {\n  if (option == EDebugOption::PVSState) {\n    if (xc8_pvs) {\n      xc8_pvs->SetState(EPVSVisSetState(value));\n    }\n  } else if (option == EDebugOption::PVSMode) {\n    xc0_pvsMode = EPVSMode(value);\n  } else if (option == EDebugOption::FogDisabled) {\n    x318_28_disableFog = true;\n  }\n}\n\nvoid CCubeRenderer::BeginPrimitive(IRenderer::EPrimitiveType type, s32 nverts) {\n  constexpr std::array vtxDescList{\n      GXVtxDescList{GX_VA_POS, GX_DIRECT},\n      GXVtxDescList{GX_VA_NRM, GX_DIRECT},\n      GXVtxDescList{GX_VA_CLR0, GX_DIRECT},\n      GXVtxDescList{GX_VA_NULL, GX_NONE},\n  };\n  CGX::SetChanCtrl(CGX::EChannelId::Channel0, false, GX_SRC_REG, GX_SRC_VTX, {}, GX_DF_NONE, GX_AF_NONE);\n  CGX::SetNumChans(1);\n  CGX::SetNumTexGens(0);\n  CGX::SetNumTevStages(1);\n  CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0);\n  CGX::SetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_RASC);\n  CGX::SetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_RASA);\n  CGX::SetStandardTevColorAlphaOp(GX_TEVSTAGE0);\n  x18_primVertCount = nverts;\n  CGX::SetVtxDescv(vtxDescList.data());\n  CGX::Begin(static_cast<GXPrimitive>(type), GX_VTXFMT0, nverts);\n}\n\nvoid CCubeRenderer::BeginLines(s32 nverts) { BeginPrimitive(EPrimitiveType::Lines, nverts); }\n\nvoid CCubeRenderer::BeginLineStrip(s32 nverts) { BeginPrimitive(EPrimitiveType::LineStrip, nverts); }\n\nvoid CCubeRenderer::BeginTriangles(s32 nverts) { BeginPrimitive(EPrimitiveType::Triangles, nverts); }\n\nvoid CCubeRenderer::BeginTriangleStrip(s32 nverts) { BeginPrimitive(EPrimitiveType::TriangleStrip, nverts); }\n\nvoid CCubeRenderer::BeginTriangleFan(s32 nverts) { BeginPrimitive(EPrimitiveType::TriangleFan, nverts); }\n\nvoid CCubeRenderer::PrimVertex(const zeus::CVector3f& vertex) {\n  --x18_primVertCount;\n  GXPosition3f32(vertex);\n  GXNormal3f32(x2e4_primNormal);\n  GXColor4f32(x2e0_primColor);\n}\n\nvoid CCubeRenderer::PrimNormal(const zeus::CVector3f& normal) { x2e4_primNormal = normal; }\n\nvoid CCubeRenderer::PrimColor(float r, float g, float b, float a) { PrimColor({r, g, b, a}); }\n\nvoid CCubeRenderer::PrimColor(const zeus::CColor& color) { x2e0_primColor = color; }\n\nvoid CCubeRenderer::EndPrimitive() {\n  while (x18_primVertCount > 0) {\n    PrimVertex(zeus::skZero3f);\n  }\n  CGX::End();\n}\n\nvoid CCubeRenderer::SetAmbientColor(const zeus::CColor& color) { CGraphics::SetAmbientColor(color); }\n\nvoid CCubeRenderer::DrawString(const char* string, s32 x, s32 y) { x10_font.DrawString(string, x, y, zeus::skWhite); }\n\nfloat CCubeRenderer::GetFPS() { return CGraphics::GetFPS(); }\n\nvoid CCubeRenderer::CacheReflection(IRenderer::TReflectionCallback cb, void* ctx, bool clearAfter) {\n  // TODO\n}\n\nvoid CCubeRenderer::DrawSpaceWarp(const zeus::CVector3f& pt, float strength) {\n  if (pt.z() < 1.f) {\n    ReallyDrawSpaceWarp(pt, strength);\n  }\n}\n\nvoid CCubeRenderer::DrawThermalModel(CModel& model, const zeus::CColor& multCol, const zeus::CColor& addCol,\n                                     TConstVectorRef positions, TConstVectorRef normals, const CModelFlags& flags) {\n  model.UpdateLastFrame();\n  DoThermalModelDraw(model.GetInstance(), multCol, addCol, positions, normals, flags);\n}\n\nvoid CCubeRenderer::DrawModelDisintegrate(CModel& model, CTexture& tex, const zeus::CColor& color,\n                                          TConstVectorRef positions, TConstVectorRef normals, float t) {\n  tex.Load(GX_TEXMAP0, EClampMode::Clamp);\n  CGX::SetNumIndStages(0);\n  CGX::SetNumTevStages(2);\n  CGX::SetNumTexGens(2);\n  CGX::SetNumChans(0);\n  CGX::SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR);\n  CGX::SetStandardTevColorAlphaOp(GX_TEVSTAGE0);\n  CGX::SetStandardTevColorAlphaOp(GX_TEVSTAGE1);\n  CGX::SetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_TEXC);\n  CGX::SetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_TEXA);\n  CGX::SetTevColorIn(GX_TEVSTAGE1, GX_CC_ZERO, GX_CC_TEXC, GX_CC_CPREV, GX_CC_KONST);\n  CGX::SetTevAlphaIn(GX_TEVSTAGE1, GX_CA_ZERO, GX_CA_TEXA, GX_CA_APREV, GX_CA_ZERO);\n  CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL);\n  CGX::SetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD1, GX_TEXMAP0, GX_COLOR_NULL);\n  CGX::SetTevKColorSel(GX_TEVSTAGE1, GX_TEV_KCSEL_K0);\n  CGX::SetTevKColor(GX_KCOLOR0, color);\n  const auto bounds = model.GetInstance().GetBounds();\n  const auto rotation = zeus::CTransform::RotateX(zeus::degToRad(-45.f));\n  const auto transformedBounds = bounds.getTransformedAABox(rotation);\n  const auto xf = zeus::CTransform::Scale(5.f / (transformedBounds.max - transformedBounds.min)) *\n                  zeus::CTransform::Translate(-transformedBounds.min) * rotation;\n  Mtx mtx;\n  xf.toCStyleMatrix(mtx);\n  float f1 = -(1.f - t) * 6.f + 1.f;\n  float f2 = t * -0.85f - 0.15f;\n  const Mtx ptTex0 = {\n      {1.0f, 1.0f, 0.0f, t},\n      {0.0f, 0.0f, 1.0f, f1},\n      {0.0f, 0.0f, 0.0f, 1.0f},\n  };\n  const Mtx ptTex1 = {\n      {1.0f, 1.0f, 0.0f, f2},\n      {0.0f, 0.0f, 1.0f, f1},\n      {0.0f, 0.0f, 0.0f, 1.0f},\n  };\n  GXLoadTexMtxImm(mtx, GX_TEXMTX0, GX_MTX3x4);\n  GXLoadTexMtxImm(ptTex0, GX_PTTEXMTX0, GX_MTX3x4);\n  GXLoadTexMtxImm(ptTex1, GX_PTTEXMTX1, GX_MTX3x4);\n  CGX::SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX3x4, GX_TG_POS, GX_TEXMTX0, false, GX_PTTEXMTX0);\n  CGX::SetTexCoordGen(GX_TEXCOORD1, GX_TG_MTX3x4, GX_TG_POS, GX_TEXMTX0, false, GX_PTTEXMTX1);\n  CGX::SetAlphaCompare(GX_GREATER, 0, GX_AOP_AND, GX_ALWAYS, 0);\n  CGX::SetZMode(true, GX_LEQUAL, true);\n  model.UpdateLastFrame();\n  model.GetInstance().DrawFlat(positions, normals, ESurfaceSelection::All);\n  CGX::SetAlphaCompare(GX_ALWAYS, 0, GX_AOP_AND, GX_ALWAYS, 0);\n}\n\nvoid CCubeRenderer::DrawModelFlat(CModel& model, const CModelFlags& flags, bool unsortedOnly, TConstVectorRef positions,\n                                  TConstVectorRef normals) {\n  if (flags.x0_blendMode >= 7) {\n    CGX::SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_ONE, GX_LO_CLEAR);\n  } else if (flags.x0_blendMode >= 5) {\n    CGX::SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR);\n  } else {\n    CGX::SetBlendMode(GX_BM_BLEND, GX_BL_ONE, GX_BL_ZERO, GX_LO_CLEAR);\n  }\n  CGX::SetZMode(true, flags.x2_flags & CModelFlagBits::DepthTest ? GX_LEQUAL : GX_ALWAYS,\n                flags.x2_flags.IsSet(CModelFlagBits::DepthUpdate));\n  CGX::SetNumTevStages(1);\n  CGX::SetNumTexGens(1);\n  CGX::SetNumChans(0);\n  CGX::SetNumIndStages(0);\n  CGX::SetAlphaCompare(GX_ALWAYS, 0, GX_AOP_AND, GX_ALWAYS, 0);\n  CGX::SetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_KONST);\n  CGX::SetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_KONST);\n  CGX::SetTevKColor(GX_KCOLOR0, flags.x4_color);\n  CGX::SetTevKColorSel(GX_TEVSTAGE0, GX_TEV_KCSEL_K0);\n  CGX::SetTevKAlphaSel(GX_TEVSTAGE0, GX_TEV_KASEL_K0_A);\n  CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);\n  CGX::SetStandardTevColorAlphaOp(GX_TEVSTAGE0);\n  CGX::SetTevDirect(GX_TEVSTAGE0);\n  CGX::SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_POS, GX_IDENTITY, false, GX_PTIDENTITY);\n  model.UpdateLastFrame();\n  model.GetInstance().DrawFlat(positions, normals, unsortedOnly ? ESurfaceSelection::Unsorted : ESurfaceSelection::All);\n}\n\nvoid CCubeRenderer::SetWireframeFlags(s32 flags) {\n  CCubeModel::SetModelWireframe((flags & 0x1) != 0);\n  x318_25_drawWireframe = (flags & 0x2) != 0;\n}\n\nvoid CCubeRenderer::SetWorldFog(ERglFogMode mode, float startz, float endz, const zeus::CColor& color) {\n  CGraphics::SetFog(x318_28_disableFog ? ERglFogMode::None : mode, startz, endz, color);\n}\n\nvoid CCubeRenderer::RenderFogVolume(const zeus::CColor& color, const zeus::CAABox& aabb,\n                                    const TLockedToken<CModel>* model, const CSkinnedModel* sModel) {\n  if (!x318_28_disableFog) {\n    x2ac_fogVolumes.emplace_back(CGraphics::mModelMatrix, color, aabb, model, sModel);\n  }\n}\n\nvoid CCubeRenderer::SetThermal(bool thermal, float level, const zeus::CColor& color) {\n  x318_29_thermalVisor = thermal;\n  x2f0_thermalVisorLevel = level;\n  x2f4_thermColor = color;\n  CDecal::SetMoveRedToAlphaBuffer(false);\n  CElementGen::SetMoveRedToAlphaBuffer(false);\n}\n\nvoid CCubeRenderer::SetThermalColdScale(float scale) { x2f8_thermColdScale = zeus::clamp(0.f, scale, 1.f); }\n\nvoid CCubeRenderer::DoThermalBlendCold() {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CCubeRenderer::DoThermalBlendCold\", zeus::skBlue);\n\n  // Capture EFB\n  x318_26_requestRGBA6 = true;\n  GXSetAlphaUpdate(true);\n  GXSetDstAlpha(false, 0);\n  const auto height = CGraphics::GetViewportHeight();\n  const auto width = CGraphics::GetViewportWidth();\n  const auto top = CGraphics::GetViewportTop();\n  const auto left = CGraphics::GetViewportLeft();\n  CGX::SetZMode(true, GX_LEQUAL, false);\n  GXSetTexCopySrc(left, top, width, height);\n  GXSetTexCopyDst(width, height, GX_TF_I4, false);\n  GXCopyTex(CGraphics::mpSpareBuffer, true);\n  CGraphics::LoadDolphinSpareTexture(width, height, GX_TF_I4, nullptr, GX_TEXMAP7);\n\n  // Upload random static texture (game reads from .text)\n  const u8* buf = CDvdFile::GetDolBuf() + 0x4f60;\n  u8* out = m_thermalRandomStatic.Lock();\n  memcpy(out, buf + ROUND_UP_32(x2a8_thermalRand.Next()), m_thermalRandomStatic.GetMemoryAllocated());\n  m_thermalRandomStatic.UnLock();\n  m_thermalRandomStatic.Load(GX_TEXMAP0, EClampMode::Clamp);\n  m_thermalRandomStatic.Load(GX_TEXMAP1, EClampMode::Clamp);\n\n  // Configure indirect texturing\n  const float level = std::clamp(x2f0_thermalVisorLevel * 0.5f, 0.f, 0.5f);\n  const aurora::Mat3x2<float> mtx{\n      aurora::Vec2{(1.f - level) * 0.1f, 0.f},\n      aurora::Vec2{0.f, 0.f},\n      aurora::Vec2{0.f, level},\n  };\n  GXSetIndTexMtx(GX_ITM_0, &mtx, -2);\n  CGX::SetTevIndirect(GX_TEVSTAGE0, GX_INDTEXSTAGE0, GX_ITF_8, GX_ITB_STU, GX_ITM_0, GX_ITW_OFF, GX_ITW_OFF, false,\n                      false, GX_ITBA_OFF);\n  GXSetIndTexOrder(GX_INDTEXSTAGE0, GX_TEXCOORD0, GX_TEXMAP0);\n\n  // Configure register colors\n  const auto color0 = zeus::CColor::lerp(x2f4_thermColor, zeus::skWhite, x2f8_thermColdScale);\n  const float bAlpha = x2f8_thermColdScale < 0.5f ? x2f8_thermColdScale * 2.f : 1.f;\n  const float bFac = (1.f - bAlpha) / 8.f;\n  const zeus::CColor color1{bFac, bAlpha};\n  float cFac;\n  if (x2f8_thermColdScale < 0.25f) {\n    cFac = 0.f;\n  } else if (x2f8_thermColdScale >= 1.f) {\n    cFac = 1.f;\n  } else {\n    cFac = (x2f8_thermColdScale - 0.25f) * 4.f / 3.f;\n  }\n  const zeus::CColor color2{cFac, cFac};\n  GXSetTevColor(GX_TEVREG0, to_gx_color(color0));\n  GXSetTevColor(GX_TEVREG1, to_gx_color(color1));\n  GXSetTevColor(GX_TEVREG2, to_gx_color(color2));\n\n  // Configure TEV stage 0\n  GXSetTevSwapMode(GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP1);\n  CGX::SetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_TEXC, GX_CC_C0, GX_CC_C2);\n  CGX::SetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_TEXA, GX_CA_A1, GX_CA_A2);\n  CGX::SetStandardTevColorAlphaOp(GX_TEVSTAGE0);\n  CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP7, GX_COLOR_NULL);\n\n  // Configure TEV stage 1\n  GXSetTevSwapMode(GX_TEVSTAGE1, GX_TEV_SWAP0, GX_TEV_SWAP1);\n  CGX::SetTevColorIn(GX_TEVSTAGE1, GX_CC_ZERO, GX_CC_TEXC, GX_CC_C1, GX_CC_CPREV);\n  CGX::SetTevColorOp(GX_TEVSTAGE1, GX_TEV_SUB, GX_TB_ZERO, GX_CS_SCALE_1, true, GX_TEVPREV);\n  CGX::SetTevAlphaIn(GX_TEVSTAGE1, GX_CA_ZERO, GX_CA_A1, GX_CA_TEXA, GX_CA_APREV);\n  CGX::SetTevAlphaOp(GX_TEVSTAGE1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_4, true, GX_TEVPREV);\n  CGX::SetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD0, GX_TEXMAP1, GX_COLOR_NULL);\n\n  // Configure everything else\n  CGX::SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX3x4, GX_TG_TEX0, GX_IDENTITY, false, GX_PTIDENTITY);\n  CGX::SetAlphaCompare(GX_ALWAYS, 0, GX_AOP_AND, GX_ALWAYS, 0);\n  CGX::SetNumTevStages(2);\n  CGX::SetNumTexGens(1);\n  CGX::SetNumChans(0);\n  CGX::SetNumIndStages(1);\n  CGX::SetZMode(false, GX_ALWAYS, false);\n  constexpr std::array vtxDescList{\n      GXVtxDescList{GX_VA_POS, GX_DIRECT},\n      GXVtxDescList{GX_VA_TEX0, GX_DIRECT},\n      GXVtxDescList{GX_VA_NULL, GX_NONE},\n  };\n  CGX::SetVtxDescv(vtxDescList.data());\n  CGX::SetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_ZERO, GX_LO_CLEAR);\n\n  // Backup & set viewport/projection\n  const auto backupViewMatrix = CGraphics::mViewMatrix;\n  const auto backupProjectionState = CGraphics::GetProjectionState();\n  CGraphics::SetOrtho(0.f, static_cast<float>(width), 0.f, static_cast<float>(height), -4096.f, 4096.f);\n  CGraphics::SetViewPointMatrix({});\n  CGraphics::SetModelMatrix({});\n  GXPixModeSync();\n\n  // Draw\n  CGX::Begin(GX_TRIANGLEFAN, GX_VTXFMT0, 4);\n  GXPosition3f32(0.f, 0.5f, 0.f);\n  GXTexCoord2f32(0.f, 0.f);\n  GXPosition3f32(0.f, 0.5f, static_cast<float>(height));\n  GXTexCoord2f32(0.f, 1.f);\n  GXPosition3f32(static_cast<float>(width), 0.5f, static_cast<float>(height));\n  GXTexCoord2f32(1.f, 1.f);\n  GXPosition3f32(static_cast<float>(width), 0.5f, 0.f);\n  GXTexCoord2f32(1.f, 0.f);\n  CGX::End();\n\n  // Cleanup\n  GXSetTevSwapMode(GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP0);\n  GXSetTevSwapMode(GX_TEVSTAGE1, GX_TEV_SWAP0, GX_TEV_SWAP0);\n  CGX::SetNumIndStages(0);\n  CGX::SetTevDirect(GX_TEVSTAGE0);\n  GXSetDstAlpha(false, 255);\n  CGraphics::SetProjectionState(backupProjectionState);\n  CGraphics::SetViewPointMatrix(backupViewMatrix);\n  CDecal::SetMoveRedToAlphaBuffer(true);\n  CElementGen::SetMoveRedToAlphaBuffer(true);\n}\n\nvoid CCubeRenderer::DoThermalBlendHot() {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CCubeRenderer::DoThermalBlendHot\", zeus::skRed);\n\n  GXSetAlphaUpdate(false);\n  GXSetDstAlpha(true, 0);\n  const auto height = CGraphics::GetViewportHeight();\n  const auto width = CGraphics::GetViewportWidth();\n  const auto top = CGraphics::GetViewportTop();\n  const auto left = CGraphics::GetViewportLeft();\n  CGX::SetZMode(true, GX_LEQUAL, true);\n  GXSetTexCopySrc(left, top, width, height);\n  GXSetTexCopyDst(width, height, GX_TF_I4, false);\n  GXCopyTex(CGraphics::mpSpareBuffer, false);\n  x288_thermoPalette.Load();\n  CGraphics::LoadDolphinSpareTexture(width, height, GX_TF_C4, GX_TLUT0, nullptr, GX_TEXMAP7);\n  CGX::SetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_TEXA, GX_CC_TEXC, GX_CC_ZERO);\n  CGX::SetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_TEXA);\n  CGX::SetStandardTevColorAlphaOp(GX_TEVSTAGE0);\n  CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP7, GX_COLOR_NULL);\n  CGX::SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX3x4, GX_TG_TEX0, GX_IDENTITY, false, GX_PTIDENTITY);\n  CGX::SetAlphaCompare(GX_ALWAYS, 0, GX_AOP_AND, GX_ALWAYS, 0);\n  CGX::SetNumTevStages(1);\n  CGX::SetNumTexGens(1);\n  CGX::SetNumChans(0);\n  CGX::SetZMode(false, GX_LEQUAL, false);\n  constexpr std::array vtxDescList{\n      GXVtxDescList{GX_VA_POS, GX_DIRECT},\n      GXVtxDescList{GX_VA_TEX0, GX_DIRECT},\n      GXVtxDescList{GX_VA_NULL, GX_NONE},\n  };\n  CGX::SetVtxDescv(vtxDescList.data());\n  CGX::SetBlendMode(GX_BM_BLEND, GX_BL_DSTALPHA, GX_BL_INVDSTALPHA, GX_LO_CLEAR);\n\n  // Backup & set viewport/projection\n  const auto backupViewMatrix = CGraphics::mViewMatrix;\n  const auto backupProjectionState = CGraphics::GetProjectionState();\n  CGraphics::SetOrtho(0.f, static_cast<float>(width), 0.f, static_cast<float>(height), -4096.f, 4096.f);\n  CGraphics::SetViewPointMatrix({});\n  CGraphics::SetModelMatrix({});\n  GXPixModeSync();\n\n  // Draw\n  CGX::Begin(GX_TRIANGLEFAN, GX_VTXFMT0, 4);\n  GXPosition3f32(0.f, 0.5f, 0.f);\n  GXTexCoord2f32(0.f, 0.f);\n  GXPosition3f32(0.f, 0.5f, static_cast<float>(height));\n  GXTexCoord2f32(0.f, 1.f);\n  GXPosition3f32(static_cast<float>(width), 0.5f, static_cast<float>(height));\n  GXTexCoord2f32(1.f, 1.f);\n  GXPosition3f32(static_cast<float>(width), 0.5f, 0.f);\n  GXTexCoord2f32(1.f, 0.f);\n  CGX::End();\n\n  // Cleanup\n  CGX::SetNumIndStages(0);\n  CGX::SetTevDirect(GX_TEVSTAGE0);\n  GXSetAlphaUpdate(true);\n  CGraphics::SetProjectionState(backupProjectionState);\n  CGraphics::SetViewPointMatrix(backupViewMatrix);\n  CDecal::SetMoveRedToAlphaBuffer(false);\n  CElementGen::SetMoveRedToAlphaBuffer(false);\n}\n\nu32 CCubeRenderer::GetStaticWorldDataSize() {\n  // TODO\n  return 0;\n}\n\nvoid CCubeRenderer::SetGXRegister1Color(const zeus::CColor& color) { GXSetTevColor(GX_TEVREG1, to_gx_color(color)); }\n\nvoid CCubeRenderer::SetWorldLightFadeLevel(float level) { x2fc_tevReg1Color = zeus::CColor(level, level, level, 1.f); }\n\nvoid CCubeRenderer::PrepareDynamicLights(const std::vector<CLight>& lights) {\n  x300_dynamicLights = lights;\n\n  for (CAreaListItem& area : x1c_areaListItems) {\n    if (const CAreaRenderOctTree* arot = area.x4_octTree) {\n      area.x1c_lightOctreeWords.clear();\n      area.x1c_lightOctreeWords.resize(arot->x14_bitmapWordCount * lights.size());\n      u32* wordPtr = area.x1c_lightOctreeWords.data();\n      for (const CLight& light : lights) {\n        float radius = light.GetRadius();\n        zeus::CVector3f vMin = light.GetPosition() - radius;\n        zeus::CVector3f vMax = light.GetPosition() + radius;\n        zeus::CAABox aabb(vMin, vMax);\n        arot->FindOverlappingModels(wordPtr, aabb);\n        wordPtr += arot->x14_bitmapWordCount;\n      }\n    }\n  }\n}\n\nvoid CCubeRenderer::AllocatePhazonSuitMaskTexture() {\n  x318_26_requestRGBA6 = true;\n  if (!x314_phazonSuitMask) {\n    x314_phazonSuitMask = std::make_unique<CTexture>(ETexelFormat::I8, CGraphics::GetViewportWidth() / 4,\n                                                     CGraphics::GetViewportHeight() / 4, 1, \"Phazon Suit Mask\"sv);\n  }\n  x310_phazonSuitMaskCountdown = 2;\n}\n\nvoid CCubeRenderer::DrawPhazonSuitIndirectEffect(const zeus::CColor& nonIndirectMod,\n                                                 const TLockedToken<CTexture>& indTex, const zeus::CColor& indirectMod,\n                                                 float blurRadius, float scale, float offX, float offY) {\n  if (x318_27_currentRGBA6 && x310_phazonSuitMaskCountdown != 0) {\n    const auto backupViewMatrix = CGraphics::mViewMatrix;\n    const auto backupProjectionState = CGraphics::GetProjectionState();\n    if (!x314_phazonSuitMask || x314_phazonSuitMask->GetWidth() != CGraphics::GetViewportWidth() / 4 ||\n        x314_phazonSuitMask->GetHeight() != CGraphics::GetViewportHeight() / 4) {\n      return;\n    }\n    DoPhazonSuitIndirectAlphaBlur(blurRadius, blurRadius);\n    // TODO copy into x314_phazonSuitMask\n    if (indTex) {\n      ReallyDrawPhazonSuitIndirectEffect(zeus::skWhite, *x314_phazonSuitMask, const_cast<CTexture&>(*indTex),\n                                         indirectMod, scale, offX, offY);\n    } else {\n      ReallyDrawPhazonSuitEffect(nonIndirectMod, *x314_phazonSuitMask);\n    }\n    // TODO unlock x314\n    CGraphics::SetViewPointMatrix(backupViewMatrix);\n    CGraphics::SetProjectionState(backupProjectionState);\n    x310_phazonSuitMaskCountdown = 2;\n  }\n  GXSetDstAlpha(false, 0.f);\n}\n\nvoid CCubeRenderer::DrawXRayOutline(const zeus::CAABox& aabb) {\n  // TODO\n}\n\nstd::list<CCubeRenderer::CAreaListItem>::iterator\nCCubeRenderer::FindStaticGeometry(const std::vector<CMetroidModelInstance>* geometry) {\n  return std::find_if(x1c_areaListItems.begin(), x1c_areaListItems.end(),\n                      [&](const CAreaListItem& item) { return item.x0_geometry == geometry; });\n}\n\nvoid CCubeRenderer::FindOverlappingWorldModels(std::vector<u32>& modelBits, const zeus::CAABox& aabb) const {\n  u32 bitmapWords = 0;\n  for (const CAreaListItem& item : x1c_areaListItems) {\n    if (item.x4_octTree != nullptr) {\n      bitmapWords += item.x4_octTree->x14_bitmapWordCount;\n    }\n  }\n\n  if (bitmapWords == 0u) {\n    modelBits.clear();\n    return;\n  }\n\n  modelBits.clear();\n  modelBits.resize(bitmapWords);\n\n  u32 curWord = 0;\n  for (const CAreaListItem& item : x1c_areaListItems) {\n    if (item.x4_octTree == nullptr) {\n      continue;\n    }\n\n    item.x4_octTree->FindOverlappingModels(modelBits.data() + curWord, aabb);\n\n    u32 wordModel = 0;\n    for (u32 i = 0; i < item.x4_octTree->x14_bitmapWordCount; ++i, wordModel += 32) {\n      u32& word = modelBits[curWord + i];\n      if (word == 0) {\n        continue;\n      }\n      for (u32 j = 0; j < 32; ++j) {\n        if (((1U << j) & word) != 0) {\n          const zeus::CAABox& modelAABB = (*item.x10_models)[wordModel + j]->GetBounds();\n          if (!modelAABB.intersects(aabb)) {\n            word &= ~(1U << j);\n          }\n        }\n      }\n    }\n\n    curWord += item.x4_octTree->x14_bitmapWordCount;\n  }\n}\n\ns32 CCubeRenderer::DrawOverlappingWorldModelIDs(s32 alphaVal, const std::vector<u32>& modelBits,\n                                                const zeus::CAABox& aabb) {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CCubeRenderer::DrawOverlappingWorldModelIDs\", zeus::skGrey);\n  SetupRendererStates(true);\n\n  constexpr CModelFlags flags{0, 0, 3, zeus::skWhite};\n\n  u32 curWord = 0;\n  for (const CAreaListItem& item : x1c_areaListItems) {\n    if (item.x4_octTree == nullptr) {\n      continue;\n    }\n\n    u32 wordModel = 0;\n    for (u32 i = 0; i < item.x4_octTree->x14_bitmapWordCount; ++i, wordModel += 32) {\n      const u32& word = modelBits[curWord + i];\n      if (word == 0) {\n        continue;\n      }\n      for (u32 j = 0; j < 32; ++j) {\n        if (((1U << j) & word) != 0) {\n          if (alphaVal > 255) {\n            SetupCGraphicsState();\n            return alphaVal;\n          }\n\n          auto& model = *(*item.x10_models)[wordModel + j];\n          GXSetDstAlpha(true, alphaVal);\n          CCubeMaterial::KillCachedViewDepState();\n          model.SetArraysCurrent();\n          for (const auto* surf = model.GetFirstUnsortedSurface(); surf != nullptr; surf = surf->GetNextSurface()) {\n            if (surf->GetBounds().intersects(aabb)) {\n              model.DrawSurface(*surf, flags);\n            }\n          }\n          alphaVal += 4;\n        }\n      }\n    }\n\n    curWord += item.x4_octTree->x14_bitmapWordCount;\n  }\n\n  SetupCGraphicsState();\n  return alphaVal;\n}\n\nvoid CCubeRenderer::DrawOverlappingWorldModelShadows(s32 alphaVal, const std::vector<u32>& modelBits,\n                                                     const zeus::CAABox& aabb) {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CBooRenderer::DrawOverlappingWorldModelShadows\", zeus::skGrey);\n\n  u32 curWord = 0;\n  for (const CAreaListItem& item : x1c_areaListItems) {\n    if (item.x4_octTree == nullptr) {\n      continue;\n    }\n\n    u32 wordModel = 0;\n    for (u32 i = 0; i < item.x4_octTree->x14_bitmapWordCount; ++i, wordModel += 32) {\n      const u32& word = modelBits[curWord + i];\n      if (word == 0) {\n        continue;\n      }\n      for (u32 j = 0; j < 32; ++j) {\n        if (((1U << j) & word) != 0) {\n          if (alphaVal > 255) {\n            return;\n          }\n\n          auto& model = *(*item.x10_models)[wordModel + j];\n          CGX::SetTevKColor(GX_KCOLOR0, zeus::CColor{0.f, static_cast<float>(alphaVal) / 255.f});\n          model.SetArraysCurrent();\n          for (const auto* surf = model.GetFirstUnsortedSurface(); surf != nullptr; surf = surf->GetNextSurface()) {\n            if (surf->GetBounds().intersects(aabb)) {\n              const auto& material = model.GetMaterialByIndex(surf->GetMaterialIndex());\n              CGX::SetVtxDescv_Compressed(material.GetVertexDesc());\n              CGX::CallDisplayList(surf->GetDisplayList(), surf->GetDisplayListSize());\n            }\n          }\n          alphaVal += 4;\n        }\n      }\n    }\n\n    curWord += item.x4_octTree->x14_bitmapWordCount;\n  }\n}\n\nvoid CCubeRenderer::SetupCGraphicsState() {\n  CGraphics::DisableAllLights();\n  CGraphics::SetModelMatrix({});\n  CTevCombiners::ResetStates();\n  CGraphics::SetAmbientColor({0.4f});\n  CGX::SetChanMatColor(CGX::EChannelId::Channel0, zeus::skWhite);\n  CGraphics::SetDepthWriteMode(true, ERglEnum::LEqual, true);\n  CGX::SetChanCtrl(CGX::EChannelId::Channel1, false, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE);\n  CCubeMaterial::EnsureTevsDirect();\n}\n\nvoid CCubeRenderer::SetupRendererStates(bool depthWrite) {\n  CGraphics::DisableAllLights();\n  CGraphics::SetModelMatrix({});\n  CGraphics::SetAmbientColor(zeus::skClear);\n  CGraphics::SetDepthWriteMode(true, ERglEnum::LEqual, depthWrite);\n  CCubeMaterial::ResetCachedMaterials();\n  GXSetTevColor(GX_TEVREG1, to_gx_color(x2fc_tevReg1Color));\n}\n\nconstexpr Mtx MvPostXf = {\n    {0.5f, 0.0f, 0.0f, 0.5f},\n    {0.0f, 0.5f, 0.5f, 0.5f},\n    {0.0f, 0.0f, 0.0f, 1.0f},\n};\n\nvoid CCubeRenderer::DoThermalModelDraw(CCubeModel& model, const zeus::CColor& multCol, const zeus::CColor& addCol,\n                                       TConstVectorRef positions, TConstVectorRef normals, const CModelFlags& flags) {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CCubeRenderer::DoThermalModelDraw\", zeus::skBlue);\n  CGX::SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX3x4, GX_TG_NRM, GX_TEXMTX0, true, GX_PTTEXMTX0);\n  CGX::SetNumTexGens(1);\n  CGX::SetNumChans(0);\n  x220_sphereRamp.Load(GX_TEXMAP0, EClampMode::Clamp);\n  zeus::CTransform xf = CGraphics::mViewMatrix.quickInverse().multiplyIgnoreTranslation(CGraphics::mModelMatrix);\n  xf.origin.zeroOut();\n  Mtx mtx;\n  xf.toCStyleMatrix(mtx);\n  GXLoadTexMtxImm(mtx, GX_TEXMTX0, GX_MTX3x4);\n  GXLoadTexMtxImm(MvPostXf, GX_PTTEXMTX0, GX_MTX3x4);\n  CGX::SetStandardTevColorAlphaOp(GX_TEVSTAGE0);\n  CGX::SetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_C0, GX_CC_TEXC, GX_CC_KONST);\n  CGX::SetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_TEXA, GX_CA_A0, GX_CA_KONST);\n  CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL);\n  CGX::SetNumTevStages(1);\n  CGX::SetTevKColor(GX_KCOLOR0, addCol);\n  CGX::SetTevKColorSel(GX_TEVSTAGE0, GX_TEV_KCSEL_K0);\n  CGX::SetTevKAlphaSel(GX_TEVSTAGE0, GX_TEV_KASEL_K0_A);\n  GXSetTevColor(GX_TEVREG0, to_gx_color(multCol));\n  CGX::SetAlphaCompare(GX_ALWAYS, 0, GX_AOP_OR, GX_ALWAYS, 0);\n  CGX::SetBlendMode(GX_BM_BLEND, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR);\n  CGX::SetZMode(flags.x2_flags.IsSet(CModelFlagBits::DepthTest), GX_LEQUAL,\n                flags.x2_flags.IsSet(CModelFlagBits::DepthUpdate));\n  model.DrawFlat(positions, normals,\n                 flags.x2_flags.IsSet(CModelFlagBits::ThermalUnsortedOnly) ? ESurfaceSelection::Unsorted\n                                                                           : ESurfaceSelection::All);\n}\n\nvoid CCubeRenderer::ReallyDrawSpaceWarp(const zeus::CVector3f& pt, float strength) {\n  return; // TODO\n\n  float vpLeft = static_cast<float>(CGraphics::GetViewportLeft());\n  float vpTop = static_cast<float>(CGraphics::GetViewportTop());\n  float vpHalfWidth = static_cast<float>(CGraphics::GetViewportWidth() / 2);\n  float vpHalfHeight = static_cast<float>(CGraphics::GetViewportHeight() / 2);\n  float local_100 = vpLeft + vpHalfWidth * pt.x() + vpHalfWidth;\n  float local_fc = vpTop + vpHalfHeight * -pt.y() + vpHalfHeight;\n  zeus::CVector2i CStack264{static_cast<s32>(local_100) & ~3, static_cast<s32>(local_fc) & ~3};\n  auto v2right = CStack264 - zeus::CVector2i{96, 96};\n  auto v2left = CStack264 + zeus::CVector2i{96, 96};\n  zeus::CVector2f uv1min{0.f, 0.f};\n  zeus::CVector2f uv1max{1.f, 1.f};\n\n  s32 aleft = CGraphics::GetViewportLeft() & ~3;\n  s32 atop = CGraphics::GetViewportTop() & ~3;\n  s32 aright = (CGraphics::GetViewportLeft() + CGraphics::GetViewportWidth() + 3) & ~3;\n  s32 abottom = (CGraphics::GetViewportTop() + CGraphics::GetViewportHeight() + 3) & ~3;\n  if (v2right.x < aleft) {\n    uv1min.x() = static_cast<float>(aleft - v2right.x) * 0.005208333f;\n    v2right.x = aleft;\n  }\n  if (v2right.y < atop) {\n    uv1min.y() = static_cast<float>(atop - v2right.x) * 0.005208333f;\n    v2right.y = atop;\n  }\n  if (v2left.x > aright) {\n    uv1max.x() = 1.f - static_cast<float>(v2left.x - aright) * 0.005208333f;\n    v2left.x = aright;\n  }\n  if (v2left.y > abottom) {\n    uv1max.y() = 1.f - static_cast<float>(v2left.y - abottom) * 0.005208333f;\n    v2left.y = abottom;\n  }\n  const auto v2sub = v2left - v2right;\n  if (v2sub.x > 0 && v2sub.y > 0) {\n    GXFogType fogType;\n    float fogStartZ;\n    float fogEndZ;\n    float fogNearZ;\n    float fogFarZ;\n    GXColor fogColor;\n    CGX::GetFog(&fogType, &fogStartZ, &fogEndZ, &fogNearZ, &fogFarZ, &fogColor);\n    CGX::SetFog(GX_FOG_NONE, fogStartZ, fogEndZ, fogNearZ, fogFarZ, fogColor);\n    GXSetTexCopySrc(v2right.x, v2right.y, v2sub.x, v2sub.y);\n    GXSetTexCopyDst(v2sub.x, v2sub.y, GX_TF_RGBA8, false);\n    GXCopyTex(CGraphics::mpSpareBuffer, false);\n    GXPixModeSync();\n    CGraphics::LoadDolphinSpareTexture(v2sub.x, v2sub.y, GX_TF_RGBA8, nullptr, GX_TEXMAP7);\n    x150_reflectionTex.Load(GX_TEXMAP1, EClampMode::Clamp);\n    CGX::SetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_TEXC);\n    CGX::SetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, true, GX_TEVPREV);\n    CGX::SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX3x4, GX_TG_TEX0, GX_IDENTITY, false, GX_PTIDENTITY);\n    CGX::SetTexCoordGen(GX_TEXCOORD1, GX_TG_MTX3x4, GX_TG_TEX1, GX_IDENTITY, false, GX_PTIDENTITY);\n    CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP7, GX_COLOR_NULL);\n  }\n}\n\nvoid CCubeRenderer::ReallyRenderFogVolume(const zeus::CColor& color, const zeus::CAABox& aabb, const CModel* model,\n                                          const CSkinnedModel* sModel) {\n  // TODO\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CCubeRenderer.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Graphics/CCubeModel.hpp\"\n#include \"Runtime/Graphics/CPVSVisSet.hpp\"\n#include \"Runtime/Graphics/CTexture.hpp\"\n#include \"Runtime/Graphics/IRenderer.hpp\"\n#include \"Runtime/CRandom16.hpp\"\n#include \"Runtime/Graphics/CFont.hpp\"\n\n#include <list>\n\nnamespace metaforce {\nclass IObjectStore;\nclass IFactory;\n\nclass CCubeRenderer final : public IRenderer {\n  // TODO function for controlling x318_26_requestRGBA6\n  // then these can be removed\n  friend class CMorphBallShadow;\n  friend class CWorldTransManager;\n\n  struct CAreaListItem {\n    const std::vector<CMetroidModelInstance>* x0_geometry;\n    const CAreaRenderOctTree* x4_octTree;\n    /* originally auto_ptrs of vectors */\n    std::unique_ptr<std::vector<TCachedToken<CTexture>>> x8_textures;\n    std::unique_ptr<std::vector<std::unique_ptr<CCubeModel>>> x10_models;\n    s32 x18_areaIdx;\n    /* Per-area octree-word major, light bits minor */\n    std::vector<u32> x1c_lightOctreeWords;\n\n    CAreaListItem(const std::vector<CMetroidModelInstance>* geom, const CAreaRenderOctTree* octTree,\n                  std::unique_ptr<std::vector<TCachedToken<CTexture>>>&& textures,\n                  std::unique_ptr<std::vector<std::unique_ptr<CCubeModel>>>&& models, s32 areaIdx);\n  };\n\n  struct CFogVolumeListItem {\n    zeus::CTransform x0_transform;\n    zeus::CColor x30_color;\n    zeus::CAABox x34_aabb;\n    TLockedToken<CModel> x4c_model;\n    // bool x58_b; Optional for model token\n    const CSkinnedModel* x5c_skinnedModel = nullptr;\n    CFogVolumeListItem(const zeus::CTransform& xf, const zeus::CColor& color, const zeus::CAABox& aabb,\n                       const TLockedToken<CModel>* model, const CSkinnedModel* sModel)\n    : x0_transform(xf), x30_color(color), x34_aabb(aabb), x5c_skinnedModel(sModel) {\n      if (model)\n        x4c_model = *model;\n    }\n  };\n\nprivate:\n  IFactory& x8_factory;\n  IObjectStore& xc_store;\n  CFont x10_font{1.f};\n  u32 x18_primVertCount = 0;\n  std::list<CAreaListItem> x1c_areaListItems;\n  // TODO x34...x40\n  zeus::CFrustum x44_frustumPlanes; // {zeus::skIdentityMatrix4f, 1.5707964f, 1.f, 1.f, false, 100.f}\n  TDrawableCallback xa8_drawableCallback = nullptr;\n  void* xac_drawableCallbackUserData = nullptr;\n  zeus::CPlane xb0_viewPlane{0.f, 1.f, 0.f, 0.f};\n  enum class EPVSMode : u8 { Mask, PVS, PVSAndMask } xc0_pvsMode = EPVSMode::Mask;\n  int xc4_; // ?\n  std::optional<CPVSVisSet> xc8_pvs;\n  u32 xe0_pvsAreaIdx = UINT32_MAX;\n  CTexture xe4_blackTex{ETexelFormat::RGB565, 4, 4, 1, \"Black Texture\"};\n  std::unique_ptr<CTexture> x14c_reflectionTex;\n  CTexture x150_reflectionTex{ETexelFormat::IA8, 32, 32, 1, \"Reflection Texture\"};\n  CTexture x1b8_fogVolumeRamp{ETexelFormat::I8, 256, 256, 1, \"Fog Volume Ramp Texture\"};\n  CTexture x220_sphereRamp{ETexelFormat::I8, 32, 32, 1, \"Sphere Ramp Texture\"};\n  CGraphicsPalette x288_thermoPalette{EPaletteFormat::RGB565, 16};\n  CRandom16 x2a8_thermalRand{20};\n  std::list<CFogVolumeListItem> x2ac_fogVolumes;\n  std::list<std::pair<zeus::CVector3f, float>> x2c4_spaceWarps;\n  u32 x2dc_reflectionAge = 2;\n  zeus::CColor x2e0_primColor = zeus::skWhite;\n  zeus::CVector3f x2e4_primNormal = zeus::skForward;\n  float x2f0_thermalVisorLevel = 1.f;\n  zeus::CColor x2f4_thermColor{1.f, 0.f, 1.f, 1.f};\n  float x2f8_thermColdScale = 0.f; // ??? byte in code\n  zeus::CColor x2fc_tevReg1Color{1.f, 0.f, 1.f, 1.f};\n  std::vector<CLight> x300_dynamicLights;\n  u32 x310_phazonSuitMaskCountdown = 0;\n  std::unique_ptr<CTexture> x314_phazonSuitMask;\n  bool x318_24_refectionDirty : 1 = false;\n  bool x318_25_drawWireframe : 1 = false;\n  bool x318_26_requestRGBA6 : 1 = false;\n  bool x318_27_currentRGBA6 : 1 = false;\n  bool x318_28_disableFog : 1 = false;\n  bool x318_29_thermalVisor : 1 = false;\n  bool x318_30_inAreaDraw : 1 = false;\n  bool x318_31_persistRGBA6 : 1 = false;\n\n  CTexture m_thermalRandomStatic{ETexelFormat::IA4, 640, 448, 1, \"Thermal Random Static\"};\n\n  void GenerateReflectionTex();\n  void GenerateFogVolumeRampTex();\n  void GenerateSphereRampTex();\n  void LoadThermoPalette();\n\n  void ReallyDrawPhazonSuitIndirectEffect(const zeus::CColor& vertColor, CTexture& maskTex, CTexture& indTex,\n                                          const zeus::CColor& modColor, float scale, float offX, float offY);\n  void ReallyDrawPhazonSuitEffect(const zeus::CColor& modColor, CTexture& maskTex);\n  void DoPhazonSuitIndirectAlphaBlur(float blurRadius, float f2);\n  void ReallyDrawSpaceWarp(const zeus::CVector3f& pt, float strength);\n  void ReallyRenderFogVolume(const zeus::CColor& color, const zeus::CAABox& aabb, const CModel* model,\n                             const CSkinnedModel* sModel);\n\npublic:\n  CCubeRenderer(IObjectStore& store, IFactory& resFac);\n  ~CCubeRenderer() override;\n\n  void AddWorldSurfaces(CCubeModel& model);\n  void AddStaticGeometry(const std::vector<CMetroidModelInstance>* geometry, const CAreaRenderOctTree* octTree,\n                         s32 areaIdx) override;\n  void EnablePVS(const CPVSVisSet& set, u32 areaIdx) override;\n  void DisablePVS() override;\n  void RemoveStaticGeometry(const std::vector<CMetroidModelInstance>* geometry) override;\n  void DrawUnsortedGeometry(s32 areaIdx, s32 mask, s32 targetMask) override;\n  void DrawSortedGeometry(s32 areaIdx, s32 mask, s32 targetMask) override;\n  void DrawStaticGeometry(s32 areaIdx, s32 mask, s32 targetMask) override;\n  void DrawAreaGeometry(s32 areaIdx, s32 mask, s32 targetMask) override;\n  void PostRenderFogs() override;\n  void SetModelMatrix(const zeus::CTransform& xf) override;\n  void AddParticleGen(CParticleGen& gen) override;\n  void AddParticleGen(CParticleGen& gen, const zeus::CVector3f& pos, const zeus::CAABox& bounds) override;\n  void AddPlaneObject(void* obj, const zeus::CAABox& aabb, const zeus::CPlane& plane, s32 type) override;\n  void AddDrawable(void* obj, const zeus::CVector3f& pos, const zeus::CAABox& aabb, s32 mode,\n                   EDrawableSorting sorting) override;\n  void SetDrawableCallback(TDrawableCallback cb, void* ctx) override;\n  void SetWorldViewpoint(const zeus::CTransform& xf) override;\n  void SetPerspective(float fovy, float aspect, float znear, float zfar) override;\n  void SetPerspective(float fovy, float width, float height, float znear, float zfar) override;\n  std::pair<zeus::CVector2f, zeus::CVector2f> SetViewportOrtho(bool centered, float znear, float zfar) override;\n  void SetClippingPlanes(const zeus::CFrustum& frustum) override;\n  void SetViewport(s32 left, s32 right, s32 width, s32 height) override;\n  void SetDepthReadWrite(bool read, bool write) override {\n    CGraphics::SetDepthWriteMode(read, ERglEnum::LEqual, write);\n  }\n  void SetBlendMode_AdditiveAlpha() override {\n    CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::One, ERglLogicOp::Clear);\n  }\n  void SetBlendMode_AlphaBlended() override {\n    CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::InvSrcAlpha,\n                            ERglLogicOp::Clear);\n  }\n  void SetBlendMode_ColorMultiply() override {\n    CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::Zero, ERglBlendFactor::SrcColor, ERglLogicOp::Clear);\n  }\n  void SetBlendMode_InvertDst() override {\n    CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::InvSrcColor, ERglBlendFactor::Zero,\n                            ERglLogicOp::Clear);\n  }\n  void SetBlendMode_InvertSrc() override {\n    CGraphics::SetBlendMode(ERglBlendMode::Logic, ERglBlendFactor::One, ERglBlendFactor::Zero, ERglLogicOp::InvCopy);\n  }\n  void SetBlendMode_NoColorWrite() override {\n    CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::Zero, ERglBlendFactor::One, ERglLogicOp::Clear);\n  }\n  void SetBlendMode_Replace() override {\n    CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::One, ERglBlendFactor::Zero, ERglLogicOp::Clear);\n  }\n  void SetBlendMode_AdditiveDestColor() override {\n    CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcColor, ERglBlendFactor::One, ERglLogicOp::Clear);\n  }\n  void BeginScene() override;\n  void EndScene() override;\n  void SetDebugOption(EDebugOption, s32) override;\n  void BeginPrimitive(EPrimitiveType, s32) override;\n  void BeginLines(int) override;\n  void BeginLineStrip(int) override;\n  void BeginTriangles(int) override;\n  void BeginTriangleStrip(int) override;\n  void BeginTriangleFan(int) override;\n  void PrimVertex(const zeus::CVector3f&) override;\n  void PrimNormal(const zeus::CVector3f&) override;\n  void PrimColor(float, float, float, float) override;\n  void PrimColor(const zeus::CColor&) override;\n  void EndPrimitive() override;\n  void SetAmbientColor(const zeus::CColor& color) override;\n  void DrawString(const char* string, s32, s32) override;\n  float GetFPS() override;\n  void CacheReflection(TReflectionCallback cb, void* ctx, bool clearAfter) override;\n  void DrawSpaceWarp(const zeus::CVector3f& pt, float strength) override;\n  void DrawThermalModel(CModel& model, const zeus::CColor& multCol, const zeus::CColor& addCol,\n                        TConstVectorRef positions, TConstVectorRef normals, const CModelFlags& flags) override;\n  void DrawModelDisintegrate(CModel& model, CTexture& tex, const zeus::CColor& color, TConstVectorRef positions,\n                             TConstVectorRef normals, float t) override;\n  void DrawModelFlat(CModel& model, const CModelFlags& flags, bool unsortedOnly, TConstVectorRef positions,\n                     TConstVectorRef normals) override;\n  void SetWireframeFlags(s32 flags) override;\n  void SetWorldFog(ERglFogMode mode, float startz, float endz, const zeus::CColor& color) override;\n  void RenderFogVolume(const zeus::CColor& color, const zeus::CAABox& aabb, const TLockedToken<CModel>* model,\n                       const CSkinnedModel* sModel) override;\n  void SetThermal(bool thermal, float level, const zeus::CColor& color) override;\n  void SetThermalColdScale(float scale) override;\n  void DoThermalBlendCold() override;\n  void DoThermalBlendHot() override;\n  u32 GetStaticWorldDataSize() override;\n  void SetGXRegister1Color(const zeus::CColor& color) override;\n  void SetWorldLightFadeLevel(float level) override;\n  void PrepareDynamicLights(const std::vector<CLight>& lights) override;\n\n  // Non-virtual functions\n  void SetupRendererStates(bool depthWrite);\n  void AllocatePhazonSuitMaskTexture();\n  void DrawPhazonSuitIndirectEffect(const zeus::CColor& nonIndirectMod, const TLockedToken<CTexture>& indTex,\n                                    const zeus::CColor& indirectMod, float blurRadius, float scale, float offX,\n                                    float offY);\n  void DrawXRayOutline(const zeus::CAABox& aabb);\n  std::list<CAreaListItem>::iterator FindStaticGeometry(const std::vector<CMetroidModelInstance>* geometry);\n  void FindOverlappingWorldModels(std::vector<u32>& modelBits, const zeus::CAABox& aabb) const;\n  s32 DrawOverlappingWorldModelIDs(s32 alphaVal, const std::vector<u32>& modelBits, const zeus::CAABox& aabb);\n  void DrawOverlappingWorldModelShadows(s32 alphaVal, const std::vector<u32>& modelBits, const zeus::CAABox& aabb);\n  void RenderBucketItems(const CAreaListItem* lights);\n  void DrawRenderBucketsDebug() {}\n\n  void HandleUnsortedModel(CAreaListItem* areaItem, CCubeModel& model, const CModelFlags& flags);\n  void HandleUnsortedModelWireframe(CAreaListItem* areaItem, CCubeModel& model);\n\n  void ActivateLightsForModel(const CAreaListItem* areaItem, CCubeModel& model);\n\n  void DoThermalModelDraw(CCubeModel& model, const zeus::CColor& multCol, const zeus::CColor& addCol,\n                          TConstVectorRef positions, TConstVectorRef normals, const CModelFlags& flags);\n\n  // Getters\n  [[nodiscard]] bool IsInAreaDraw() const { return x318_30_inAreaDraw; }\n  [[nodiscard]] bool IsReflectionDirty() const { return x318_24_refectionDirty; }\n  void SetReflectionDirty(bool v) { x318_24_refectionDirty = v; }\n  [[nodiscard]] bool IsThermalVisorActive() const { return x318_29_thermalVisor; }\n  CTexture* GetRealReflection() {\n    x2dc_reflectionAge = 0;\n    if (x14c_reflectionTex) {\n      return x14c_reflectionTex.get();\n    }\n\n    return &xe4_blackTex;\n  }\n\n  static void SetupCGraphicsState();\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CCubeSurface.cpp",
    "content": "#include \"CCubeSurface.hpp\"\n\n#include \"Streams/IOStreams.hpp\"\n\nnamespace metaforce {\nCCubeSurface::CCubeSurface(const u8* ptr, u32 len) : x0_data(ptr) {\n  CMemoryInStream mem(ptr, len, CMemoryInStream::EOwnerShip::NotOwned);\n  x0_center = mem.Get<zeus::CVector3f>();\n  xc_materialIndex = mem.ReadLong();\n  x10_displayListSize = mem.ReadLong();\n  mem.ReadLong(); // x14_parent\n  mem.ReadLong(); // x18_nextSurface\n  x1c_extraSize = mem.ReadLong();\n  x20_normal = mem.Get<zeus::CVector3f>();\n  if (x1c_extraSize > 0) {\n    x24_bounds = mem.Get<zeus::CAABox>();\n  }\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CCubeSurface.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <vector>\n\n#include \"GCNTypes.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CCubeModel;\n\nclass CCubeSurface {\n  static constexpr zeus::CVector3f skDefaultNormal{1.f, 0.f, 0.f};\n  const u8* x0_data;\n\n  // Extracted from surface data\n  zeus::CVector3f x0_center;\n  u32 xc_materialIndex;\n  u32 x10_displayListSize;\n  CCubeModel* x14_parent = nullptr;\n  CCubeSurface* x18_nextSurface = nullptr;\n  u32 x1c_extraSize;\n  zeus::CVector3f x20_normal;\n  zeus::CAABox x24_bounds;\n\npublic:\n  explicit CCubeSurface(const u8* ptr, u32 len); // Metaforce addition for extracting surface data\n\n  // bool IsValid() const;\n  [[nodiscard]] CCubeModel* GetParent() { return x14_parent; }\n  [[nodiscard]] const CCubeModel* GetParent() const { return x14_parent; }\n  void SetParent(CCubeModel* parent) { x14_parent = parent; }\n  [[nodiscard]] CCubeSurface* GetNextSurface() { return x18_nextSurface; }\n  [[nodiscard]] const CCubeSurface* GetNextSurface() const { return x18_nextSurface; }\n  void SetNextSurface(CCubeSurface* next) { x18_nextSurface = next; }\n  [[nodiscard]] u32 GetMaterialIndex() const { return xc_materialIndex; }\n  [[nodiscard]] u32 GetDisplayListSize() const { return x10_displayListSize & 0x7fffffff; }\n  [[nodiscard]] u32 GetNormalHint() const { return (x10_displayListSize >> 31) & 1; }\n  [[nodiscard]] const u8* GetDisplayList() const { return x0_data + GetSurfaceHeaderSize(); }\n  [[nodiscard]] u32 GetSurfaceHeaderSize() const { return (0x4b + x1c_extraSize) & ~31; }\n  [[nodiscard]] zeus::CVector3f GetCenter() const { return x0_center; }\n  [[nodiscard]] zeus::CAABox GetBounds() const {\n    return x1c_extraSize != 0 ? x24_bounds : zeus::CAABox{x0_center, x0_center};\n  }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CDrawable.hpp",
    "content": "#pragma once\n\n#include \"Runtime/GCNTypes.hpp\"\n#include <zeus/CAABox.hpp>\n\nnamespace metaforce {\nenum class EDrawableType : u16 {\n  WorldSurface,\n  Particle,\n  Actor,\n  SimpleShadow,\n  Decal,\n  Invalid = 0xFFFF,\n};\n\nclass CDrawable {\n  EDrawableType x0_type;\n  u16 x2_extraSort;\n  void* x4_data;\n  zeus::CAABox x8_aabb;\n  float x20_viewDist;\n\npublic:\n  CDrawable(EDrawableType dtype, u16 extraSort, float planeDot, const zeus::CAABox& aabb, void* data)\n  : x0_type(dtype), x2_extraSort(extraSort), x4_data(data), x8_aabb(aabb), x20_viewDist(planeDot) {}\n\n  EDrawableType GetType() const { return x0_type; }\n  const zeus::CAABox& GetBounds() const { return x8_aabb; }\n  float GetDistance() const { return x20_viewDist; }\n  void* GetData() { return x4_data; }\n  const void* GetData() const { return x4_data; }\n  u16 GetExtraSort() const { return x2_extraSort; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CDrawablePlaneObject.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Graphics/CDrawable.hpp\"\n#include <zeus/CPlane.hpp>\n\nnamespace metaforce {\nclass CDrawablePlaneObject : public CDrawable {\n  friend class Buckets;\n  u16 x24_targetBucket = 0;\n  float x28_farDist;\n  zeus::CPlane x2c_plane;\n  bool x3c_24_invertTest : 1;\n  bool x3c_25_zOnly : 1;\n\npublic:\n  CDrawablePlaneObject(EDrawableType dtype, float closeDist, float farDist, const zeus::CAABox& aabb, bool invertTest,\n                       const zeus::CPlane& plane, bool zOnly, void* data)\n  : CDrawable(dtype, 0, closeDist, aabb, data)\n  , x28_farDist(farDist)\n  , x2c_plane(plane)\n  , x3c_24_invertTest{invertTest}\n  , x3c_25_zOnly{zOnly} {}\n  const zeus::CPlane& GetPlane() const { return x2c_plane; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CFont.cpp",
    "content": "#include \"Runtime/Graphics/CFont.hpp\"\n\n#include \"Runtime/Graphics/CGraphics.hpp\"\n\nnamespace metaforce {\n/* TODO: Custom I8 font */\nstd::array<u8, 65536> CFont::sSystemFont = {\n    /* Omitted due to copyright issues */};\n\nu32 CFont::sNumInstances = 0;\nstd::unique_ptr<CTexture> CFont::mpTexture;\n\nCFont::CFont(float scale) : x0_fontSize(16.f * scale), x4_scale(scale) {\n  if (sNumInstances == 0) {\n    mpTexture = std::make_unique<CTexture>(ETexelFormat::I8, 256, 256, 1, \"Font Texture\");\n    u8* fontData = new u8[(mpTexture->GetBitDepth() * mpTexture->GetWidth() * mpTexture->GetHeight()) / 8];\n    memcpy(fontData, sSystemFont.data(), sSystemFont.size());\n    // u8* textureData = mpTexture->GetBitMapData();\n    // LinearToTile8(textureData, fontData);\n    delete[] fontData;\n    // mpTexture->UnLock();\n  }\n  ++sNumInstances;\n}\n\nvoid CFont::Shutdown() {\n  mpTexture.reset();\n}\n\nvoid CFont::TileCopy8(u8* dest, const u8* src) {\n  for (u32 i = 0; i < 4; ++i) {\n    dest[0] = src[0];\n    dest[1] = src[1];\n    dest[2] = src[2];\n    dest[3] = src[3];\n    dest[4] = src[4];\n    dest[5] = src[5];\n    dest[6] = src[6];\n    dest[7] = src[7];\n    src += 256;\n    dest += 8;\n  }\n}\n\nvoid CFont::LinearToTile8(u8* dest, const u8* src) {\n  int iVar1 = 0;\n  int iVar2 = 0;\n  for (size_t y = 0; y < 256; y += 4) {\n    iVar2 = iVar1;\n    for (size_t x = 0; x < 256; x += 8) {\n      TileCopy8(dest, src + iVar2);\n      dest += 32;\n      iVar2 += 8;\n    }\n    iVar1 += 1024;\n  }\n}\n\nvoid CFont::DrawString(const char* str, int x, int y, const zeus::CColor& col) {\n  // bool bVar2 = CGraphics::BeginRender2D(*mpTexture.get());\n  // char chr = *str;\n  // while (chr != 0) {\n  //   u32 cellSize = static_cast<u32>(16.f * x4_scale);\n  //   ++str;\n  //   CGraphics::DoRender2D(*mpTexture, x, y, (chr & 0xf) * 16, 0xf0, 0x10, cellSize, cellSize, col);\n  //   chr = *str;\n  // }\n  // CGraphics::EndRender2D(bVar2);\n}\n\nu32 CFont::StringWidth(const char* str) const {\n  u32 width = 0;\n  char chr = *str;\n  while (chr != 0) {\n    ++str;\n    width += static_cast<u32>(15.f * x4_scale);\n    chr = *str;\n  }\n\n  return width;\n}\n\nu32 CFont::CharsWidth(const char* str, u32 len) const { return len * (15.f * x4_scale); }\n\nu32 CFont::CharWidth(const char chr) const { return 15.f * x4_scale; }\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CFont.hpp",
    "content": "#pragma once\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/Graphics/CTexture.hpp\"\n\n#include <zeus/CColor.hpp>\n\n#include <memory>\n#include <array>\n\nnamespace metaforce {\nclass CFont {\n  static std::array<u8, 65536> sSystemFont;\n  static u32 sNumInstances;\n  static std::unique_ptr<CTexture> mpTexture;\n  float x0_fontSize;\n  float x4_scale;\n  void TileCopy8(u8* dest, const u8* src);\n  void LinearToTile8(u8* dest, const u8* src);\npublic:\n  explicit CFont(float scale);\n\n  void DrawString(const char* str, int x, int y, const zeus::CColor& color);\n  u32 StringWidth(const char* str) const;\n  u32 CharsWidth(const char* str, u32 len) const;\n  u32 CharWidth(const char chr) const;\n\n  static void Shutdown();\n};\n}"
  },
  {
    "path": "Runtime/Graphics/CGX.cpp",
    "content": "#include \"CGX.hpp\"\n\n#include \"Graphics/CTexture.hpp\"\n\nnamespace metaforce::CGX {\nSGXState sGXState{};\nstd::array<GXVtxDescList, 12> sVtxDescList{};\n\nvoid SetLineWidth(u8 width, GXTexOffset offset) noexcept {\n  u16 flags = width | offset << 8;\n  if (flags != sGXState.x54_lineWidthAndOffset) {\n    sGXState.x54_lineWidthAndOffset = flags;\n    GXSetLineWidth(width, offset);\n  }\n}\n\nvoid ResetGXStates() noexcept {\n  sGXState.x48_descList = 0;\n  GXClearVtxDesc();\n  sGXState.x0_arrayPtrs.fill(nullptr);\n  for (GXTexMapID id = GX_TEXMAP0; id < GX_MAX_TEXMAP; id = static_cast<GXTexMapID>(id + 1)) {\n    CTexture::InvalidateTexMap(id);\n  }\n  for (GXTevKColorID id = GX_KCOLOR0; const auto& item : sGXState.x58_kColors) {\n    GXSetTevKColor(id, item);\n    id = static_cast<GXTevKColorID>(id + 1);\n  }\n  GXSetTevSwapModeTable(GX_TEV_SWAP1, GX_CH_RED, GX_CH_GREEN, GX_CH_BLUE, GX_CH_RED);\n  GXSetTevSwapModeTable(GX_TEV_SWAP2, GX_CH_RED, GX_CH_GREEN, GX_CH_BLUE, GX_CH_GREEN);\n  GXSetTevSwapModeTable(GX_TEV_SWAP3, GX_CH_RED, GX_CH_GREEN, GX_CH_BLUE, GX_CH_BLUE);\n  SetAlphaCompare(GX_ALWAYS, 0, GX_AOP_AND, GX_ALWAYS, 0);\n  GXSetCurrentMtx(GX_PNMTX0);\n  SetNumIndStages(0);\n  for (int i = 0; i < GX_MAX_INDTEXSTAGE; i++) {\n    GXSetIndTexCoordScale(static_cast<GXIndTexStageID>(i), GX_ITS_1, GX_ITS_1);\n  }\n  for (int i = 0; i < GX_MAX_TEVSTAGE; i++) {\n    SetTevDirect(static_cast<GXTevStageID>(i));\n  }\n  for (int i = 0; i < GX_MAX_TEXCOORD; i++) {\n    GXSetTexCoordScaleManually(static_cast<GXTexCoordID>(i), false, 0, 0);\n  }\n  GXSetZTexture(GX_ZT_DISABLE, GX_TF_Z8, 0);\n}\n} // namespace metaforce::CGX\n"
  },
  {
    "path": "Runtime/Graphics/CGX.hpp",
    "content": "#pragma once\n\n#include \"Graphics/GX.hpp\"\n#include \"RetroTypes.hpp\"\n\n#include <span>\n#include <zeus/CColor.hpp>\n\nnamespace metaforce::CGX {\nenum class EChannelId {\n  Channel0, // GX_COLOR0\n  Channel1, // GX_COLOR1\n};\n\nstruct STevState {\n  u32 x0_colorInArgs = 0;\n  u32 x4_alphaInArgs = 0;\n  u32 x8_colorOps = 0;\n  u32 xc_alphaOps = 0;\n  u32 x10_indFlags = 0;\n  u32 x14_tevOrderFlags = 0;\n  GXTevKColorSel x18_kColorSel = GX_TEV_KCSEL_1;\n  GXTevKAlphaSel x19_kAlphaSel = GX_TEV_KASEL_1;\n};\nstruct STexState {\n  u32 x0_coordGen = 0;\n};\nstruct SGXState {\n  std::array<void*, 12> x0_arrayPtrs{};\n  std::array<u16, 2> x30_prevChanCtrls{};\n  std::array<u16, 2> x34_chanCtrls{0x4000, 0x4000};\n  std::array<GXColor, 2> x38_chanAmbColors;\n  std::array<GXColor, 2> x40_chanMatColors;\n  u32 x48_descList = 0;\n  union {\n    u8 x4c_chanFlags = 0;\n    // Ordering swapped for LE\n    struct {\n      u8 numDirty : 1;\n      u8 chansDirty : 2;\n      u8 unused : 5;\n    } x4c_flags;\n  };\n  u8 x4d_prevNumChans = 0;\n  u8 x4e_numChans = 0;\n  u8 x4f_numTexGens = 0;\n  u8 x50_numTevStages = 0;\n  u8 x51_numIndStages = 0;\n  u8 x52_zmode = 0;\n  GXFogType x53_fogType = GX_FOG_NONE;\n  u16 x54_lineWidthAndOffset = 0;\n  u16 x56_blendMode = 0;\n  std::array<GXColor, GX_MAX_KCOLOR> x58_kColors;\n  std::array<STevState, GX_MAX_TEVSTAGE> x68_tevStates;\n  std::array<STexState, GX_MAX_TEXCOORD> x228_texStates;\n  u32 x248_alphaCompare = 0;\n  float x24c_fogStartZ = 0.f;\n  float x250_fogEndZ = 0.f;\n  float x254_fogNearZ = 0.f;\n  float x258_fogFarZ = 0.f;\n  GXColor x25c_fogColor;\n};\nextern SGXState sGXState;\nextern std::array<GXVtxDescList, 12> sVtxDescList;\n\nstatic inline u32 MaskAndShiftLeft(u32 v, u32 m, u32 s) { return (v & m) << s; }\nstatic inline u32 ShiftRightAndMask(u32 v, u32 m, u32 s) { return (v >> s) & m; }\n\nstatic inline void update_fog(u32 value) noexcept {\n  if (sGXState.x53_fogType == GX_FOG_NONE || (sGXState.x56_blendMode & 0xE0) == (value & 0xE0)) {\n    return;\n  }\n  if ((value & 0xE0) == 0x20) {\n    GXSetFogColor(GX_CLEAR);\n    return;\n  }\n  GXSetFogColor(sGXState.x25c_fogColor);\n}\n\nstatic inline void FlushState() noexcept {\n  if (sGXState.x4c_chanFlags & 1) {\n    GXSetNumChans(sGXState.x4e_numChans);\n    sGXState.x4d_prevNumChans = sGXState.x4e_numChans;\n  }\n  if (sGXState.x4c_chanFlags & 2) {\n    u16 flags = sGXState.x34_chanCtrls[0];\n    GXBool enable = ShiftRightAndMask(flags, 1, 0);\n    GXColorSrc ambSrc = static_cast<GXColorSrc>(ShiftRightAndMask(flags, 1, 1));\n    GXColorSrc matSrc = static_cast<GXColorSrc>(ShiftRightAndMask(flags, 1, 2));\n    u32 lightMask = ShiftRightAndMask(flags, 0xFF, 3);\n    GXDiffuseFn diffFn = static_cast<GXDiffuseFn>(ShiftRightAndMask(flags, 3, 11));\n    GXAttnFn attnFn = static_cast<GXAttnFn>(ShiftRightAndMask(flags, 3, 13));\n    GXSetChanCtrl(GX_COLOR0, enable, ambSrc, matSrc, lightMask, diffFn, attnFn);\n    sGXState.x30_prevChanCtrls[0] = sGXState.x34_chanCtrls[0];\n  }\n  if (sGXState.x4c_chanFlags & 4) {\n    u16 flags = sGXState.x34_chanCtrls[1];\n    GXBool enable = ShiftRightAndMask(flags, 1, 0);\n    GXColorSrc ambSrc = static_cast<GXColorSrc>(ShiftRightAndMask(flags, 1, 1));\n    GXColorSrc matSrc = static_cast<GXColorSrc>(ShiftRightAndMask(flags, 1, 2));\n    u32 lightMask = ShiftRightAndMask(flags, 0xFF, 3);\n    GXDiffuseFn diffFn = static_cast<GXDiffuseFn>(ShiftRightAndMask(flags, 3, 11));\n    GXAttnFn attnFn = static_cast<GXAttnFn>(ShiftRightAndMask(flags, 3, 13));\n    GXSetChanCtrl(GX_COLOR1, enable, ambSrc, matSrc, lightMask, diffFn, attnFn);\n    sGXState.x30_prevChanCtrls[1] = sGXState.x34_chanCtrls[1];\n  }\n  sGXState.x4c_chanFlags = 0;\n}\n\nstatic inline void Begin(GXPrimitive primitive, GXVtxFmt fmt, u16 nverts) noexcept {\n  if (sGXState.x4c_chanFlags != 0) {\n    FlushState();\n  }\n  GXBegin(primitive, fmt, nverts);\n}\n\nstatic inline void End() noexcept { GXEnd(); }\n\nstatic inline void CallDisplayList(const void* data, u32 nbytes) noexcept {\n  if (sGXState.x4c_chanFlags != 0) {\n    FlushState();\n  }\n  GXCallDisplayList(data, nbytes);\n}\n\nstatic inline const GXColor& GetChanAmbColor(EChannelId id) noexcept {\n  const auto idx = std::underlying_type_t<EChannelId>(id);\n  return sGXState.x38_chanAmbColors[idx];\n}\n\nvoid ResetGXStates() noexcept;\n\nstatic inline void SetAlphaCompare(GXCompare comp0, u8 ref0, GXAlphaOp op, GXCompare comp1, u8 ref1) noexcept {\n  u32 flags = ref1 << 17 | (comp1 & 7) << 14 | (op & 7) << 11 | ref0 << 3 | (comp0 & 7);\n  if (flags != sGXState.x248_alphaCompare) {\n    sGXState.x248_alphaCompare = flags;\n    GXSetAlphaCompare(comp0, ref0, op, comp1, ref1);\n    GXSetZCompLoc(comp0 == GX_ALWAYS);\n  }\n}\n\nstatic inline void SetArray(GXAttr attr, std::span<const u8> data, u8 stride) noexcept {\n  const auto* ptr = static_cast<const void*>(data.data());\n  if (ptr != nullptr && sGXState.x0_arrayPtrs[attr - GX_VA_POS] != ptr) {\n    sGXState.x0_arrayPtrs[attr - GX_VA_POS] = const_cast<void*>(ptr);\n    GXSetArray(attr, data.data(), data.size(), stride, true);\n  }\n}\n\ntemplate <typename T>\nstatic inline void SetArray(GXAttr attr, std::span<const T> data) noexcept {\n  const auto* ptr = static_cast<const void*>(data.data());\n  if (ptr != nullptr && sGXState.x0_arrayPtrs[attr - GX_VA_POS] != ptr) {\n    sGXState.x0_arrayPtrs[attr - GX_VA_POS] = const_cast<void*>(ptr);\n    GXSetArray(attr, data.data(), data.size_bytes(), sizeof(T), true);\n  }\n}\n\ntemplate <typename T, size_t N>\nstatic inline void SetArray(GXAttr attr, const std::array<T, N>& data) noexcept {\n  const auto* ptr = static_cast<const void*>(data.data());\n  if (ptr != nullptr && sGXState.x0_arrayPtrs[attr - GX_VA_POS] != ptr) {\n    sGXState.x0_arrayPtrs[attr - GX_VA_POS] = const_cast<void*>(ptr);\n    GXSetArray(attr, ptr, data.size() * sizeof(T), sizeof(T), true);\n  }\n}\n\n// Aurora addition: clear array to force reupload\nstatic inline void ClearArray(GXAttr attr) noexcept {\n  GXSetArray(attr, nullptr, 0, 0, true);\n  sGXState.x0_arrayPtrs[attr - GX_VA_POS] = nullptr;\n}\n\nstatic inline void SetBlendMode(GXBlendMode mode, GXBlendFactor srcFac, GXBlendFactor dstFac, GXLogicOp op) noexcept {\n  const u16 flags = (op & 0xF) << 8 | (dstFac & 7) << 5 | (srcFac & 7) << 2 | (mode & 3);\n  if (flags != sGXState.x56_blendMode) {\n    update_fog(flags);\n    sGXState.x56_blendMode = flags;\n    GXSetBlendMode(mode, srcFac, dstFac, op);\n  }\n}\n\nstatic inline void SetChanAmbColor(EChannelId id, GXColor color) noexcept {\n  const auto idx = std::underlying_type_t<EChannelId>(id);\n  if (color != sGXState.x38_chanAmbColors[idx]) {\n    sGXState.x38_chanAmbColors[idx] = color;\n    GXSetChanAmbColor(GXChannelID(idx + GX_COLOR0A0), color);\n  }\n}\nstatic inline void SetChanAmbColor(EChannelId id, const zeus::CColor& color) noexcept {\n  SetChanAmbColor(id, to_gx_color(color));\n}\n\nstatic inline void SetChanCtrl(EChannelId channel, GXBool enable, GXColorSrc ambSrc, GXColorSrc matSrc,\n                               GX::LightMask lights, GXDiffuseFn diffFn, GXAttnFn attnFn) noexcept {\n  const auto idx = static_cast<std::underlying_type_t<EChannelId>>(channel);\n  u16& state = sGXState.x34_chanCtrls[idx];\n  u16 prevFlags = sGXState.x30_prevChanCtrls[idx];\n  if (lights.none()) {\n    enable = GX_FALSE;\n  }\n  u32 flags = MaskAndShiftLeft(enable, 1, 0) | MaskAndShiftLeft(ambSrc, 1, 1) | MaskAndShiftLeft(matSrc, 1, 2) |\n              MaskAndShiftLeft(lights.to_ulong(), 0xFF, 3) | MaskAndShiftLeft(diffFn, 3, 11) |\n              MaskAndShiftLeft(attnFn, 3, 13);\n  state = flags;\n  sGXState.x4c_chanFlags = ((flags != prevFlags) << (idx + 1)) | (sGXState.x4c_chanFlags & ~(1 << (idx + 1)));\n}\n\nstatic inline void SetChanCtrl_Compressed(EChannelId channel, GX::LightMask lights, u32 ctrl) {\n  const auto idx = static_cast<std::underlying_type_t<EChannelId>>(channel);\n  u16& state = sGXState.x34_chanCtrls[idx];\n  u16 prevFlags = sGXState.x30_prevChanCtrls[idx];\n  u32 flags = ctrl & ~1;\n  if (lights.any()) {\n    flags = ctrl | (lights.to_ulong() & 0xFF) << 3;\n  }\n  state = flags;\n  sGXState.x4c_chanFlags = ((flags != prevFlags) << (idx + 1)) | (sGXState.x4c_chanFlags & ~(1 << (idx + 1)));\n}\n\nstatic inline void SetChanMatColor(EChannelId id, GXColor color) noexcept {\n  const auto idx = std::underlying_type_t<EChannelId>(id);\n  if (color != sGXState.x40_chanMatColors[idx]) {\n    sGXState.x40_chanMatColors[idx] = color;\n    GXSetChanMatColor(GXChannelID(idx + GX_COLOR0A0), color);\n  }\n}\nstatic inline void SetChanMatColor(EChannelId id, const zeus::CColor& color) noexcept {\n  SetChanMatColor(id, to_gx_color(color));\n}\n\nstatic inline void SetFog(GXFogType type, float startZ, float endZ, float nearZ, float farZ,\n                          const GXColor& color) noexcept {\n  sGXState.x25c_fogColor = color;\n  sGXState.x53_fogType = type;\n  sGXState.x24c_fogStartZ = startZ;\n  sGXState.x250_fogEndZ = endZ;\n  sGXState.x254_fogNearZ = nearZ;\n  sGXState.x258_fogFarZ = farZ;\n  auto fogColor = color;\n  if ((sGXState.x56_blendMode & 0xE0) == 0x20) {\n    fogColor = GX_CLEAR;\n  }\n  GXSetFog(type, startZ, endZ, nearZ, farZ, fogColor);\n}\n\nvoid SetIndTexMtxSTPointFive(GXIndTexMtxID id, s8 scaleExp) noexcept;\n\nvoid SetLineWidth(u8 width, GXTexOffset offset) noexcept;\n\nstatic inline void SetNumChans(u8 num) noexcept {\n  sGXState.x4e_numChans = num;\n  sGXState.x4c_flags.numDirty = sGXState.x4e_numChans != sGXState.x4d_prevNumChans;\n}\n\nstatic inline void SetNumIndStages(u8 num) noexcept {\n  auto& state = sGXState.x51_numIndStages;\n  if (num != state) {\n    state = num;\n    GXSetNumIndStages(num);\n  }\n}\n\nstatic inline void SetNumTevStages(u8 num) noexcept {\n  auto& state = sGXState.x50_numTevStages;\n  if (num != state) {\n    state = num;\n    GXSetNumTevStages(num);\n  }\n}\n\nstatic inline void SetNumTexGens(u8 num) noexcept {\n  auto& state = sGXState.x4f_numTexGens;\n  if (num != state) {\n    state = num;\n    GXSetNumTexGens(num);\n  }\n}\n\nstatic inline void SetStandardTevColorAlphaOp(GXTevStageID stageId) noexcept {\n  auto& state = sGXState.x68_tevStates[stageId];\n  if (state.x8_colorOps != 0x100 || state.xc_alphaOps != 0x100) {\n    state.x8_colorOps = 0x100;\n    state.xc_alphaOps = 0x100;\n    GXSetTevColorOp(stageId, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, true, GX_TEVPREV);\n    GXSetTevAlphaOp(stageId, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, true, GX_TEVPREV);\n  }\n}\n\nstatic inline void SetTevAlphaIn(GXTevStageID stageId, GXTevAlphaArg a, GXTevAlphaArg b, GXTevAlphaArg c,\n                                 GXTevAlphaArg d) noexcept {\n  u32 flags = (d & 31) << 15 | (c & 31) << 10 | (b & 31) << 5 | (a & 31);\n  auto& state = sGXState.x68_tevStates[stageId].x4_alphaInArgs;\n  if (flags != state) {\n    state = flags;\n    GXSetTevAlphaIn(stageId, a, b, c, d);\n  }\n}\n\nstatic inline void SetTevAlphaOp(GXTevStageID stageId, GXTevOp op, GXTevBias bias, GXTevScale scale, GXBool clamp,\n                                 GXTevRegID outReg) noexcept {\n  u32 flags = (outReg & 3) << 9 | (u8(clamp) & 1) << 8 | (scale & 3) << 6 | (bias & 3) << 4 | (op & 15);\n  auto& state = sGXState.x68_tevStates[stageId].xc_alphaOps;\n  if (flags != state) {\n    state = flags;\n    GXSetTevAlphaOp(stageId, op, bias, scale, clamp, outReg);\n  }\n}\n\nstatic inline void SetTevAlphaOp_Compressed(GXTevStageID stageId, u32 ops) noexcept {\n  auto& state = sGXState.x68_tevStates[stageId].xc_alphaOps;\n  if (ops != state) {\n    state = ops;\n    GXSetTevAlphaOp(stageId, GXTevOp(ops & 31), GXTevBias(ops >> 4 & 3), GXTevScale(ops >> 6 & 3), GXBool(ops >> 8 & 1),\n                    GXTevRegID(ops >> 9 & 3));\n  }\n}\n\nstatic inline void SetTevColorIn(GXTevStageID stageId, GXTevColorArg a, GXTevColorArg b, GXTevColorArg c,\n                                 GXTevColorArg d) noexcept {\n  u32 flags = (d & 31) << 15 | (c & 31) << 10 | (b & 31) << 5 | (a & 31);\n  auto& state = sGXState.x68_tevStates[stageId].x0_colorInArgs;\n  if (flags != state) {\n    state = flags;\n    GXSetTevColorIn(stageId, a, b, c, d);\n  }\n}\n\nstatic inline void SetTevColorOp(GXTevStageID stageId, GXTevOp op, GXTevBias bias, GXTevScale scale, GXBool clamp,\n                                 GXTevRegID outReg) noexcept {\n  u32 flags = (outReg & 3) << 9 | (u8(clamp) & 1) << 8 | (scale & 3) << 6 | (bias & 3) << 4 | (op & 15);\n  auto& state = sGXState.x68_tevStates[stageId].x8_colorOps;\n  if (flags != state) {\n    state = flags;\n    GXSetTevColorOp(stageId, op, bias, scale, clamp, outReg);\n  }\n}\n\nstatic inline void SetTevColorOp_Compressed(GXTevStageID stageId, u32 ops) noexcept {\n  auto& state = sGXState.x68_tevStates[stageId].x8_colorOps;\n  if (ops != state) {\n    state = ops;\n    GXSetTevColorOp(stageId, GXTevOp(ops & 31), GXTevBias(ops >> 4 & 3), GXTevScale(ops >> 6 & 3), GXBool(ops >> 8 & 1),\n                    GXTevRegID(ops >> 9 & 3));\n  }\n}\n\nstatic inline void SetTevDirect(GXTevStageID stageId) noexcept {\n  auto& state = sGXState.x68_tevStates[stageId].x10_indFlags;\n  if (state != 0) {\n    state = 0;\n    GXSetTevDirect(stageId);\n  }\n}\n\nstatic inline void SetStandardDirectTev_Compressed(GXTevStageID stageId, u32 colorArgs, u32 alphaArgs, u32 colorOps,\n                                                   u32 alphaOps) noexcept {\n  auto& state = sGXState.x68_tevStates[stageId];\n  SetTevDirect(stageId);\n  if (state.x0_colorInArgs != colorArgs) {\n    state.x0_colorInArgs = colorArgs;\n    GXSetTevColorIn(stageId, GXTevColorArg(colorArgs & 31), GXTevColorArg(colorArgs >> 5 & 31),\n                    GXTevColorArg(colorArgs >> 10 & 31), GXTevColorArg(colorArgs >> 15 & 31));\n  }\n  if (state.x4_alphaInArgs != alphaArgs) {\n    state.x4_alphaInArgs = alphaArgs;\n    GXSetTevAlphaIn(stageId, GXTevAlphaArg(alphaArgs & 31), GXTevAlphaArg(alphaArgs >> 5 & 31),\n                    GXTevAlphaArg(alphaArgs >> 10 & 31), GXTevAlphaArg(alphaArgs >> 15 & 31));\n  }\n  if (colorOps != alphaOps || (colorOps & 0x1FF) != 0x100) {\n    SetTevColorOp_Compressed(stageId, colorOps);\n    SetTevAlphaOp_Compressed(stageId, alphaOps);\n  } else if (colorOps != state.x8_colorOps || colorOps != state.xc_alphaOps) {\n    state.x8_colorOps = colorOps;\n    state.xc_alphaOps = colorOps;\n    const auto outReg = GXTevRegID(colorOps >> 9 & 3);\n    GXSetTevColorOp(stageId, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, true, outReg);\n    GXSetTevAlphaOp(stageId, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, true, outReg);\n  }\n}\n\nstatic inline void SetTevIndirect(GXTevStageID stageId, GXIndTexStageID indStage, GXIndTexFormat fmt,\n                                  GXIndTexBiasSel biasSel, GXIndTexMtxID mtxSel, GXIndTexWrap wrapS, GXIndTexWrap wrapT,\n                                  GXBool addPrev, GXBool indLod, GXIndTexAlphaSel alphaSel) noexcept {\n  // TODO\n  GXSetTevIndirect(stageId, indStage, fmt, biasSel, mtxSel, wrapS, wrapT, addPrev, indLod, alphaSel);\n}\n\nstatic inline void SetTevIndWarp(GXTevStageID stageId, GXIndTexStageID indStage, GXBool signedOffset,\n                                 GXBool replaceMode, GXIndTexMtxID mtxSel) noexcept {\n  // TODO\n  GXSetTevIndWarp(stageId, indStage, signedOffset, replaceMode, mtxSel);\n}\n\nstatic inline void SetTevKAlphaSel(GXTevStageID stageId, GXTevKAlphaSel sel) noexcept {\n  auto& state = sGXState.x68_tevStates[stageId].x19_kAlphaSel;\n  if (sel != state) {\n    state = sel;\n    GXSetTevKAlphaSel(stageId, sel);\n  }\n}\n\nstatic inline void SetTevKColor(GXTevKColorID id, const GXColor& color) noexcept {\n  auto& state = sGXState.x58_kColors[id];\n  if (color != state) {\n    state = color;\n    GXSetTevKColor(id, color);\n  }\n}\nstatic inline void SetTevKColor(GXTevKColorID id, const zeus::CColor& color) noexcept {\n  SetTevKColor(id, to_gx_color(color));\n}\n\nstatic inline void SetTevKColorSel(GXTevStageID stageId, GXTevKColorSel sel) noexcept {\n  auto& state = sGXState.x68_tevStates[stageId].x18_kColorSel;\n  if (sel != state) {\n    state = sel;\n    GXSetTevKColorSel(stageId, sel);\n  }\n}\n\nstatic inline void SetTevOrder(GXTevStageID stageId, GXTexCoordID texCoord, GXTexMapID texMap,\n                               GXChannelID color) noexcept {\n  u32 flags = (color & 0xFF) << 16 | (texMap & 0xFF) << 8 | (texCoord & 0xFF);\n  auto& state = sGXState.x68_tevStates[stageId].x14_tevOrderFlags;\n  if (flags != state) {\n    state = flags;\n    GXSetTevOrder(stageId, texCoord, texMap, color);\n  }\n}\n\nstatic inline void SetTexCoordGen(GXTexCoordID dstCoord, GXTexGenType fn, GXTexGenSrc src, GXTexMtx mtx,\n                                  GXBool normalize, GXPTTexMtx postMtx) noexcept {\n  u32 flags = ((postMtx - GX_PTTEXMTX0) & 63) << 15 | (u8(normalize) & 1) << 14 | ((mtx - GX_TEXMTX0) & 31) << 9 |\n              (src & 31) << 4 | (fn & 15);\n  auto& state = sGXState.x228_texStates[dstCoord].x0_coordGen;\n  if (flags != state) {\n    state = flags;\n    GXSetTexCoordGen2(dstCoord, fn, src, mtx, normalize, postMtx);\n  }\n}\n\nstatic inline void SetTexCoordGen(GXTexCoordID dstCoord, u32 flags) noexcept {\n  auto& state = sGXState.x228_texStates[dstCoord].x0_coordGen;\n  if (flags != state) {\n    state = flags;\n    GXSetTexCoordGen2(dstCoord, GXTexGenType(flags & 15), GXTexGenSrc(flags >> 4 & 31),\n                      GXTexMtx((flags >> 9 & 31) + GX_TEXMTX0), GXBool(flags >> 14 & 1),\n                      GXPTTexMtx((flags >> 15 & 63) + GX_PTTEXMTX0));\n  }\n}\n\nstatic inline void SetVtxDescv_Compressed(u32 flags) noexcept {\n  if (flags == sGXState.x48_descList) {\n    return;\n  }\n  GXVtxDescList* list = sVtxDescList.data();\n  for (u32 idx = 0; idx < sVtxDescList.size() - 1; ++idx) {\n    u32 shift = idx * 2;\n    if ((flags & 3 << shift) == (sGXState.x48_descList & 3 << shift)) {\n      continue;\n    }\n    list->attr = static_cast<GXAttr>(GX_VA_POS + idx);\n    list->type = static_cast<GXAttrType>(flags >> shift & 3);\n    ++list;\n  }\n  list->attr = GX_VA_NULL;\n  list->type = GX_NONE;\n  GXSetVtxDescv(sVtxDescList.data());\n  sGXState.x48_descList = flags;\n}\n\nstatic inline void SetVtxDescv(const GXVtxDescList* descList) noexcept {\n  u32 flags = 0;\n  for (; descList->attr != GX_VA_NULL; ++descList) {\n    flags |= (descList->type & 3) << (descList->attr - GX_VA_POS) * 2;\n  }\n  SetVtxDescv_Compressed(flags);\n}\n\nstatic inline void SetZMode(GXBool compareEnable, GXCompare func, GXBool updateEnable) noexcept {\n  u32 flags = (func & 0xFF) << 2 | (u8(updateEnable) << 1) | (u8(compareEnable) & 1);\n  auto& state = sGXState.x52_zmode;\n  if (flags != state) {\n    state = flags;\n    GXSetZMode(compareEnable, func, updateEnable);\n  }\n}\n\nstatic inline void GetFog(GXFogType* fogType, float* fogStartZ, float* fogEndZ, float* fogNearZ, float* fogFarZ,\n                          GXColor* fogColor) {\n  if (fogType != nullptr) {\n    *fogType = sGXState.x53_fogType;\n  }\n  if (fogStartZ != nullptr) {\n    *fogStartZ = sGXState.x24c_fogStartZ;\n  }\n  if (fogEndZ != nullptr) {\n    *fogEndZ = sGXState.x250_fogEndZ;\n  }\n  if (fogNearZ != nullptr) {\n    *fogNearZ = sGXState.x254_fogNearZ;\n  }\n  if (fogFarZ != nullptr) {\n    *fogFarZ = sGXState.x258_fogFarZ;\n  }\n  if (fogColor != nullptr) {\n    *fogColor = sGXState.x25c_fogColor;\n  }\n}\n} // namespace metaforce::CGX\n"
  },
  {
    "path": "Runtime/Graphics/CGraphics.cpp",
    "content": "#include \"Runtime/Graphics/CGraphics.hpp\"\n\n#include \"Runtime/CTimeProvider.hpp\"\n#include \"Runtime/Graphics/CTexture.hpp\"\n#include \"Runtime/GuiSys/CGuiSys.hpp\"\n#include \"Runtime/Graphics/CGX.hpp\"\n#include \"Runtime/Logging.hpp\"\n\n#include <dolphin/gx.h>\n#include <dolphin/vi.h>\n#include <zeus/Math.hpp>\n\nnamespace metaforce {\nusing CVector3f = zeus::CVector3f;\nusing CVector2i = zeus::CVector2i;\nusing CTransform4f = zeus::CTransform;\nusing CColor = zeus::CColor;\nusing uchar = unsigned char;\nusing uint = unsigned int;\nusing ushort = unsigned short;\n\nCGraphics::CRenderState CGraphics::sRenderState;\nVecPtr CGraphics::vtxBuffer;\nVecPtr CGraphics::nrmBuffer;\nVec2Ptr CGraphics::txtBuffer0;\nVec2Ptr CGraphics::txtBuffer1;\nuint* CGraphics::clrBuffer;\nbool CGraphics::mJustReset;\nERglCullMode CGraphics::mCullMode;\nint CGraphics::mNumLightsActive;\nfloat CGraphics::mDepthNear;\nVecPtr CGraphics::mpVtxBuffer;\nVecPtr CGraphics::mpNrmBuffer;\nVec2Ptr CGraphics::mpTxtBuffer0;\nVec2Ptr CGraphics::mpTxtBuffer1;\nuint* CGraphics::mpClrBuffer;\n\nstruct {\n  Vec vtx;\n  Vec nrm;\n  Vec2 uv0;\n  Vec2 uv1;\n  u32 color;\n  u16 textureUsed;\n  u8 streamFlags;\n} vtxDescr;\n\nCVector3f CGraphics::kDefaultPositionVector(0.f, 0.f, 0.f);\nCVector3f CGraphics::kDefaultDirectionVector(0.f, 1.f, 0.f);\nCGraphics::CProjectionState CGraphics::mProj(true, -1.f, 1.f, 1.f, -1.f, 1.f, 100.f);\nCTransform4f CGraphics::mViewMatrix = CTransform4f();\nCTransform4f CGraphics::mModelMatrix = CTransform4f();\nCColor CGraphics::mClearColor = zeus::skBlack;\nCVector3f CGraphics::mViewPoint(0.f, 0.f, 0.f);\nGXLightObj CGraphics::mLightObj[8];\n// GXTexRegion CGraphics::mTexRegions[GX_MAX_TEXMAP];\n// GXTexRegion CGraphics::mTexRegionsCI[GX_MAX_TEXMAP / 2];\nGXRenderModeObj CGraphics::mRenderModeObj;\nMtx CGraphics::mGXViewPointMatrix;\nMtx CGraphics::mGXModelMatrix;\nMtx CGraphics::mGxModelView;\nMtx CGraphics::mCameraMtx;\n\nint CGraphics::mNumPrimitives;\nint CGraphics::mFrameCounter;\nfloat CGraphics::mFramesPerSecond;\nfloat CGraphics::mLastFramesPerSecond;\nint CGraphics::mNumBreakpointsWaiting;\nint CGraphics::mFlippingState;\nbool CGraphics::mLastFrameUsedAbove;\nbool CGraphics::mInterruptLastFrameUsedAbove;\nGX::LightMask CGraphics::mLightActive;\nGX::LightMask CGraphics::mLightsWereOn;\nvoid* CGraphics::mpFrameBuf1;\nvoid* CGraphics::mpFrameBuf2;\nvoid* CGraphics::mpCurrenFrameBuf;\nint CGraphics::mSpareBufferSize;\nvoid* CGraphics::mpSpareBuffer;\nint CGraphics::mSpareBufferTexCacheSize;\n// GXTexRegionCallback CGraphics::mGXDefaultTexRegionCallback;\nvoid* CGraphics::mpFifo;\nGXFifoObj* CGraphics::mpFifoObj;\nuint CGraphics::mRenderTimings;\nfloat CGraphics::mSecondsMod900;\nCTimeProvider* CGraphics::mpExternalTimeProvider;\nint CGraphics::mScreenStretch;\nint CGraphics::mScreenPositionX;\nint CGraphics::mScreenPositionY;\n\nCViewport CGraphics::mViewport = {0, 0, 640, 480, 320.f, 240.f};\nELightType CGraphics::mLightTypes[8] = {\n    ELightType::Directional, ELightType::Directional, ELightType::Directional, ELightType::Directional,\n    ELightType::Directional, ELightType::Directional, ELightType::Directional, ELightType::Directional,\n};\n\n// const CTevCombiners::CTevPass& CGraphics::kEnvPassthru = CTevCombiners::kEnvPassthru;\nbool CGraphics::mIsBeginSceneClearFb = true;\nERglEnum CGraphics::mDepthFunc = ERglEnum::LEqual;\nERglPrimitive CGraphics::mCurrentPrimitive = ERglPrimitive::Points;\nfloat CGraphics::mDepthFar = 1.f;\nu32 CGraphics::mClearDepthValue = GX_MAX_Z24;\nbool CGraphics::mIsGXModelMatrixIdentity = true;\nbool CGraphics::mFirstFrame = true;\nGXBool CGraphics::mUseVideoFilter = GX_ENABLE;\nfloat CGraphics::mBrightness = 1.f;\n\nconst GXTexMapID CGraphics::kSpareBufferTexMapID = GX_TEXMAP7;\n\n// We don't actually store anything here\nstatic std::array<uchar, 1> sSpareFrameBuffer;\n\nvoid CGraphics::DisableAllLights() {\n  mNumLightsActive = 0;\n  mLightActive.reset();\n  CGX::SetChanCtrl(CGX::EChannelId::Channel0, false, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE);\n}\n\nstatic inline GXLightID get_hw_light_index(ERglLight light) {\n  return static_cast<GXLightID>((1 << light) & (GX_MAX_LIGHT - 1));\n}\n\nvoid CGraphics::LoadLight(ERglLight light, const CLight& info) {\n  GXLightID lightId = get_hw_light_index(light);\n  ELightType type = info.GetType();\n  CVector3f pos = info.GetPosition();\n  CVector3f dir = info.GetDirection();\n\n  switch (type) {\n  case ELightType::Spot: {\n    MTXMultVec(mCameraMtx, reinterpret_cast<VecPtr>(&pos), reinterpret_cast<VecPtr>(&pos));\n    GXLightObj* obj = &mLightObj[light];\n    GXInitLightPos(obj, pos.x(), pos.y(), pos.z());\n    MTXMultVecSR(mCameraMtx, reinterpret_cast<VecPtr>(&dir), reinterpret_cast<VecPtr>(&dir));\n    GXInitLightDir(obj, dir.x(), dir.y(), dir.z());\n    GXInitLightAttn(obj, 1.f, 0.f, 0.f, info.GetAttenuationConstant(), info.GetAttenuationLinear(),\n                    info.GetAttenuationQuadratic());\n    GXInitLightSpot(obj, info.GetSpotCutoff(), GX_SP_COS2);\n    break;\n  }\n  case ELightType::Point:\n  case ELightType::LocalAmbient: {\n    MTXMultVec(mCameraMtx, reinterpret_cast<VecPtr>(&pos), reinterpret_cast<VecPtr>(&pos));\n    GXInitLightPos(&mLightObj[light], pos.x(), pos.y(), pos.z());\n    GXInitLightAttn(&mLightObj[light], 1.f, 0.f, 0.f, info.GetAttenuationConstant(), info.GetAttenuationLinear(),\n                    info.GetAttenuationQuadratic());\n    break;\n  }\n  case ELightType::Directional: {\n    MTXMultVecSR(mCameraMtx, reinterpret_cast<VecPtr>(&dir), reinterpret_cast<VecPtr>(&dir));\n    dir = -dir;\n    GXInitLightPos(&mLightObj[light], dir.x() * 1048576.f, dir.y() * 1048576.f, dir.z() * 1048576.f);\n    GXInitLightAttn(&mLightObj[light], 1.f, 0.f, 0.f, 1.f, 0.f, 0.f);\n    break;\n  }\n  case ELightType::Custom: {\n    MTXMultVec(mCameraMtx, reinterpret_cast<VecPtr>(&pos), reinterpret_cast<VecPtr>(&pos));\n    GXLightObj* obj = &mLightObj[light];\n    GXInitLightPos(obj, pos.x(), pos.y(), pos.z());\n    MTXMultVecSR(mCameraMtx, reinterpret_cast<VecPtr>(&dir), reinterpret_cast<VecPtr>(&dir));\n    GXInitLightDir(obj, dir.x(), dir.y(), dir.z());\n    GXInitLightAttn(obj, info.GetAngleAttenuationConstant(), info.GetAngleAttenuationLinear(),\n                    info.GetAngleAttenuationQuadratic(), info.GetAttenuationConstant(), info.GetAttenuationLinear(),\n                    info.GetAttenuationQuadratic());\n    break;\n  }\n  default:\n    break;\n  }\n\n  GXInitLightColor(&mLightObj[light], to_gx_color(info.GetColor()));\n  GXLoadLightObjImm(&mLightObj[light], lightId);\n  mLightTypes[light] = info.GetType();\n}\n\nvoid CGraphics::EnableLight(ERglLight light) {\n  CGX::SetNumChans(1);\n  GX::LightMask lightsWereOn = mLightActive;\n  if (!lightsWereOn.test(light)) {\n    mLightActive.set(light);\n    CGX::SetChanCtrl(CGX::EChannelId::Channel0, true, GX_SRC_REG, GX_SRC_REG, mLightActive, GX_DF_CLAMP, GX_AF_SPOT);\n    ++mNumLightsActive;\n  }\n  mLightsWereOn = mLightActive;\n}\n\nvoid CGraphics::SetLightState(GX::LightMask lights) {\n  GXAttnFn attnFn = GX_AF_NONE;\n  if (lights.any()) {\n    attnFn = GX_AF_SPOT;\n  }\n  GXDiffuseFn diffFn = GX_DF_NONE;\n  if (lights.any()) {\n    diffFn = GX_DF_CLAMP;\n  }\n  CGX::SetChanCtrl(CGX::EChannelId::Channel0, lights.any() ? GX_ENABLE : GX_DISABLE, GX_SRC_REG,\n                   (vtxDescr.streamFlags & 2) != 0 ? GX_SRC_VTX : GX_SRC_REG, lights, diffFn, attnFn);\n  mLightActive = lights;\n  mNumLightsActive = lights.count();\n}\n\nvoid CGraphics::SetAmbientColor(const zeus::CColor& col) {\n  CGX::SetChanAmbColor(CGX::EChannelId::Channel0, col);\n  CGX::SetChanAmbColor(CGX::EChannelId::Channel1, col);\n}\n\nvoid CGraphics::SetFog(ERglFogMode mode, float startz, float endz, const zeus::CColor& color) {\n  CGX::SetFog(static_cast<GXFogType>(mode), startz, endz, mProj.GetNear(), mProj.GetFar(), to_gx_color(color));\n}\n\nvoid CGraphics::SetDepthWriteMode(const bool test, ERglEnum comp, const bool write) {\n  mDepthFunc = comp;\n  CGX::SetZMode(test, static_cast<GXCompare>(comp), write);\n}\n\nvoid CGraphics::SetBlendMode(ERglBlendMode mode, ERglBlendFactor src, ERglBlendFactor dst, ERglLogicOp op) {\n  CGX::SetBlendMode(static_cast<GXBlendMode>(mode), static_cast<GXBlendFactor>(src), static_cast<GXBlendFactor>(dst),\n                    static_cast<GXLogicOp>(op));\n}\n\nvoid CGraphics::SetCullMode(ERglCullMode cullMode) {\n  mCullMode = cullMode;\n  GXSetCullMode(static_cast<GXCullMode>(cullMode));\n}\n\nvoid CGraphics::ClearBackAndDepthBuffers() {\n  GXInvalidateTexAll();\n  GXSetViewport(0.f, 0.f, mRenderModeObj.fbWidth, mRenderModeObj.xfbHeight, 0.f, 1.f);\n  GXInvalidateVtxCache();\n}\n\nvoid CGraphics::BeginScene() { ClearBackAndDepthBuffers(); }\n\nvoid CGraphics::EndScene() {\n  CGX::SetZMode(true, GX_LEQUAL, true);\n  // volatile int& numBreakPt = const_cast< volatile int& >(mNumBreakpointsWaiting);\n  // while (numBreakPt > 0) {\n  //   OSYieldThread();\n  // }\n  ++mNumBreakpointsWaiting;\n  void*& frameBuf = mpCurrenFrameBuf;\n  float brightness = std::clamp(mBrightness, 0.f, 2.f);\n  static const u8 copyFilter[7] = {0x00, 0x00, 0x15, 0x16, 0x15, 0x00, 0x00};\n  const u8* inFilter = mUseVideoFilter ? mRenderModeObj.vfilter : copyFilter;\n  u8 vfilter[7];\n  for (int i = 0; i < 7; i++) {\n    vfilter[i] = static_cast<u8>(static_cast<float>(inFilter[i]) * brightness);\n  }\n  GXSetCopyFilter(mRenderModeObj.aa, mRenderModeObj.sample_pattern, true, vfilter);\n  GXCopyDisp(frameBuf, mIsBeginSceneClearFb ? GX_TRUE : GX_FALSE);\n  GXSetCopyFilter(mRenderModeObj.aa, mRenderModeObj.sample_pattern, mUseVideoFilter ? GX_ENABLE : GX_DISABLE,\n                  mRenderModeObj.vfilter);\n  // GXSetBreakPtCallback(SwapBuffers);\n  // VISetPreRetraceCallback(VideoPreCallback);\n  // VISetPostRetraceCallback(VideoPostCallback);\n  GXFlush();\n  GXFifoObj* fifo = GXGetGPFifo();\n  void* readPtr;\n  void* writePtr;\n  GXGetFifoPtrs(fifo, &readPtr, &writePtr);\n  // GXEnableBreakPt(writePtr);\n  mLastFrameUsedAbove = mInterruptLastFrameUsedAbove;\n  ++mFrameCounter;\n  // CFrameDelayedKiller::fn_8036CB90();\n}\n\nstatic constexpr GXVtxDescList skPosColorTexDirect[] = {\n    {GX_VA_POS, GX_DIRECT},\n    {GX_VA_CLR0, GX_DIRECT},\n    {GX_VA_TEX0, GX_DIRECT},\n    {GX_VA_NULL, GX_DIRECT},\n};\n\nvoid CGraphics::Render2D(CTexture& tex, int x, int y, int w, int h, const zeus::CColor& col, bool scale) {\n  Mtx44 proj;\n  if (scale) {\n    const float viewportAspect = GetViewportAspect();\n    float left = -320.f;\n    float right = 320.f;\n    float top = 224.f;\n    float bottom = -224.f;\n    if (viewportAspect > 4.f / 3.f) {\n      float width = 224.0f * viewportAspect;\n      left = -width;\n      right = width;\n    } else {\n      float height = 320.0f / viewportAspect;\n      top = height;\n      bottom = -height;\n    }\n    MTXOrtho(proj, top, bottom, left, right, -1.f, -10.f);\n  } else {\n    MTXOrtho(proj, mViewport.mHeight / 2, -(mViewport.mHeight / 2), -(mViewport.mWidth / 2), mViewport.mWidth / 2, -1.f,\n             -10.f);\n  }\n  GXSetProjection(proj, GX_ORTHOGRAPHIC);\n  uint c = col.toRGBA();\n\n  Mtx mtx;\n  MTXIdentity(mtx);\n  GXLoadPosMtxImm(mtx, GX_PNMTX0);\n\n  float x2, y2, x1, y1;\n  if (scale) {\n    x1 = x - 320;\n    y1 = y - 224;\n    x2 = x1 + w;\n    y2 = y1 + h;\n  } else {\n    x1 = x - mViewport.mWidth / 2;\n    y1 = y - mViewport.mHeight / 2;\n    x2 = x1 + w;\n    y2 = y1 + h;\n  }\n\n  // Save state + setup\n  CGX::SetVtxDescv(skPosColorTexDirect);\n  SetTevStates(6);\n  mLightsWereOn = mLightActive;\n  if (mLightActive.any()) {\n    DisableAllLights();\n  }\n  ERglCullMode cullMode = mCullMode;\n  SetCullMode(ERglCullMode::None);\n  tex.Load(GX_TEXMAP0, EClampMode::Repeat);\n\n  // Draw\n  CGX::Begin(GX_TRIANGLESTRIP, GX_VTXFMT0, 4);\n  GXPosition3f32(x1, y1, 1.f);\n  GXColor1u32(c);\n  GXTexCoord2f32(0.f, 0.f);\n  GXPosition3f32(x2, y1, 1.f);\n  GXColor1u32(c);\n  GXTexCoord2f32(1.f, 0.f);\n  GXPosition3f32(x1, y2, 1.f);\n  GXColor1u32(c);\n  GXTexCoord2f32(0.f, 1.f);\n  GXPosition3f32(x2, y2, 1.f);\n  GXColor1u32(c);\n  GXTexCoord2f32(1.f, 1.f);\n  CGX::End();\n\n  // Restore state\n  if (mLightsWereOn.any()) {\n    SetLightState(mLightsWereOn);\n  }\n  FlushProjection();\n  mIsGXModelMatrixIdentity = false;\n  SetModelMatrix(mModelMatrix);\n  SetCullMode(cullMode);\n}\n\nvoid CGraphics::SetAlphaCompare(ERglAlphaFunc comp0, uchar ref0, ERglAlphaOp op, ERglAlphaFunc comp1, uchar ref1) {\n  CGX::SetAlphaCompare(static_cast<GXCompare>(comp0), ref0, static_cast<GXAlphaOp>(op), static_cast<GXCompare>(comp1),\n                       ref1);\n}\n\nvoid CGraphics::SetViewPointMatrix(const zeus::CTransform& xf) {\n  mViewMatrix = xf;\n  mGXViewPointMatrix[0][0] = xf.basis[0][0];\n  mGXViewPointMatrix[0][1] = xf.basis[0][1];\n  mGXViewPointMatrix[0][2] = xf.basis[0][2];\n  mGXViewPointMatrix[0][3] = 0.f;\n  mGXViewPointMatrix[1][0] = xf.basis[2][0];\n  mGXViewPointMatrix[1][1] = xf.basis[2][1];\n  mGXViewPointMatrix[1][2] = xf.basis[2][2];\n  mGXViewPointMatrix[1][3] = 0.f;\n  mGXViewPointMatrix[2][0] = -xf.basis[1][0];\n  mGXViewPointMatrix[2][1] = -xf.basis[1][1];\n  mGXViewPointMatrix[2][2] = -xf.basis[1][2];\n  mGXViewPointMatrix[2][3] = 0.f;\n  mViewPoint = xf.origin;\n  SetViewMatrix();\n}\n\nvoid CGraphics::SetIdentityViewPointMatrix() {\n  mViewMatrix = CTransform4f();\n  MTXIdentity(mGXViewPointMatrix);\n  mGXViewPointMatrix[2][2] = 0.f;\n  mGXViewPointMatrix[1][1] = 0.f;\n  mGXViewPointMatrix[1][2] = 1.f;\n  mGXViewPointMatrix[2][1] = -1.f;\n  mViewPoint = CVector3f();\n  SetViewMatrix();\n}\n\nvoid CGraphics::SetViewMatrix() {\n  Mtx mtx;\n  MTXTrans(mtx, -mViewPoint.x(), -mViewPoint.y(), -mViewPoint.z());\n  MTXConcat(mGXViewPointMatrix, mtx, mCameraMtx);\n  if (mIsGXModelMatrixIdentity) {\n    MTXCopy(mCameraMtx, mGxModelView);\n  } else {\n    MTXConcat(mCameraMtx, mGXModelMatrix, mGxModelView);\n  }\n  GXLoadPosMtxImm(mGxModelView, GX_PNMTX0);\n\n  Mtx nrmMtx;\n  MTXInvXpose(mGxModelView, nrmMtx);\n  GXLoadNrmMtxImm(nrmMtx, GX_PNMTX0);\n}\n\nvoid CGraphics::SetModelMatrix(const zeus::CTransform& xf) {\n  if (xf == zeus::CTransform()) {\n    if (!mIsGXModelMatrixIdentity) {\n      mModelMatrix = xf;\n      mIsGXModelMatrixIdentity = true;\n      SetViewMatrix();\n    }\n    return;\n  }\n\n  mModelMatrix = xf;\n  mIsGXModelMatrixIdentity = false;\n  mGXModelMatrix[0][0] = xf.basis[0][0];\n  mGXModelMatrix[0][1] = xf.basis[1][0];\n  mGXModelMatrix[0][2] = xf.basis[2][0];\n  mGXModelMatrix[0][3] = xf.origin.x();\n  mGXModelMatrix[1][0] = xf.basis[0][1];\n  mGXModelMatrix[1][1] = xf.basis[1][1];\n  mGXModelMatrix[1][2] = xf.basis[2][1];\n  mGXModelMatrix[1][3] = xf.origin.y();\n  mGXModelMatrix[2][0] = xf.basis[0][2];\n  mGXModelMatrix[2][1] = xf.basis[1][2];\n  mGXModelMatrix[2][2] = xf.basis[2][2];\n  mGXModelMatrix[2][3] = xf.origin.z();\n  SetViewMatrix();\n}\n\nvoid CGraphics::SetIdentityModelMatrix() {\n  if (!mIsGXModelMatrixIdentity) {\n    mModelMatrix = CTransform4f();\n    mIsGXModelMatrixIdentity = true;\n    SetViewMatrix();\n  }\n}\n\nzeus::CMatrix4f CGraphics::CalculatePerspectiveMatrix(float fovy, float aspect, float znear, float zfar) {\n  float t = std::tan(zeus::degToRad(fovy) / 2.f);\n  float right = aspect * 2.f * znear * t * 0.5f;\n  float left = -right;\n  float top = znear * 2.f * t * 0.5f;\n  float bottom = -top;\n  return zeus::CMatrix4f{\n      // clang-format off\n    (2.f * znear) / (right - left),\n    -(right + left) / (right - left),\n    0.f,\n    0.f,\n    0.f,\n    -(top + bottom) / (top - bottom),\n    (2.f * znear) / (top - bottom),\n    0.f,\n    0.f,\n    (zfar + znear) / (zfar - znear),\n    0.f,\n    -(2.f * zfar * znear) / (zfar - znear),\n    0.f,\n    1.f,\n    0.f,\n    0.f,\n      // clang-format on\n  };\n}\n\nzeus::CMatrix4f CGraphics::GetPerspectiveProjectionMatrix() {\n  return zeus::CMatrix4f{\n      // clang-format off\n    (mProj.GetNear() * 2.f) / (mProj.GetRight() - mProj.GetLeft()),\n    -(mProj.GetRight() + mProj.GetLeft()) / (mProj.GetRight() - mProj.GetLeft()),\n    0.f,\n    0.f,\n    0.f,\n    -(mProj.GetTop() + mProj.GetBottom()) / (mProj.GetTop() - mProj.GetBottom()),\n    (mProj.GetNear() * 2.f) / (mProj.GetTop() - mProj.GetBottom()),\n    0.f,\n    0.f,\n    (mProj.GetFar() + mProj.GetNear()) / (mProj.GetFar() - mProj.GetNear()),\n    0.f,\n    -(mProj.GetFar() * 2.f * mProj.GetNear()) / (mProj.GetFar() - mProj.GetNear()),\n    0.f,\n    1.f,\n    0.f,\n    0.f\n      // clang-format on\n  };\n}\n\nconst CGraphics::CProjectionState& CGraphics::GetProjectionState() { return mProj; }\n\nvoid CGraphics::SetProjectionState(const CProjectionState& proj) {\n  mProj = proj;\n  FlushProjection();\n}\n\nvoid CGraphics::SetPerspective(float fovy, float aspect, float znear, float zfar) {\n  float t = tan(zeus::degToRad(fovy) / 2.f);\n  mProj = CProjectionState(true,                               // Is Projection\n                           -(aspect * 2.f * znear * t * 0.5f), // Left\n                           (aspect * 2.f * znear * t * 0.5f),  // Right\n                           (znear * 2.f * t * 0.5f),           // Top\n                           -(znear * 2.f * t * 0.5f),          // Bottom\n                           znear, zfar);\n  FlushProjection();\n}\n\nvoid CGraphics::SetOrtho(float left, float right, float top, float bottom, float znear, float zfar) {\n  mProj = CProjectionState(false, left, right, top, bottom, znear, zfar);\n  FlushProjection();\n}\n\nvoid CGraphics::FlushProjection() {\n  float right = mProj.GetRight();\n  float left = mProj.GetLeft();\n  float top = mProj.GetTop();\n  float bottom = mProj.GetBottom();\n  float nearPlane = mProj.GetNear();\n  float farPlane = mProj.GetFar();\n  if (mProj.IsPerspective()) {\n    Mtx44 mtx;\n    MTXFrustum(mtx, top, bottom, left, right, nearPlane, farPlane);\n    GXSetProjection(mtx, GX_PERSPECTIVE);\n  } else {\n    Mtx44 mtx;\n    MTXOrtho(mtx, top, bottom, left, right, nearPlane, farPlane);\n    GXSetProjection(mtx, GX_ORTHOGRAPHIC);\n  }\n}\n\nzeus::CVector2i CGraphics::ProjectPoint(const zeus::CVector3f& point) {\n  zeus::CVector3f vec = GetPerspectiveProjectionMatrix().multiplyOneOverW(point);\n  vec.x() = vec.x() * mViewport.mHalfWidth + mViewport.mHalfWidth;\n  vec.y() = -vec.y() * mViewport.mHalfHeight + mViewport.mHalfHeight;\n  return CVector2i(vec.x(), vec.y());\n}\n\nstatic CVector3f TransposeMultiply(const CTransform4f& self, const CVector3f& in) {\n  return self.transposeRotate({in.x() - self.origin.x(), in.y() - self.origin.y(), in.z() - self.origin.z()});\n}\n\nCGraphics::CClippedScreenRect CGraphics::ClipScreenRectFromMS(const CVector3f& p1, const CVector3f& p2,\n                                                              ETexelFormat fmt) {\n  return ClipScreenRectFromVS(TransposeMultiply(mViewMatrix, mModelMatrix * p1),\n                              TransposeMultiply(mViewMatrix, mModelMatrix * p2), fmt);\n}\n\nCGraphics::CClippedScreenRect CGraphics::ClipScreenRectFromVS(const CVector3f& p1, const CVector3f& p2,\n                                                              ETexelFormat fmt) {\n  if (p1.isZero() || p2.isZero()) {\n    return CClippedScreenRect();\n  }\n  if (p1.y() < GetProjectionState().GetNear() || p2.y() < GetProjectionState().GetNear()) {\n    return CClippedScreenRect();\n  }\n  if (p1.y() > GetProjectionState().GetFar() || p2.y() > GetProjectionState().GetFar()) {\n    return CClippedScreenRect();\n  }\n\n  CVector2i p1p = ProjectPoint(p1);\n  CVector2i p2p = ProjectPoint(p2);\n\n  int minX = std::min(p1p.x, p2p.x);\n  int minY = std::min(p1p.y, p2p.y);\n\n  int maxX = abs(p1p.x - p2p.x);\n  int maxY = abs(p1p.y - p2p.y);\n\n  int left = minX & ~1;\n  if (left >= mViewport.mLeft + mViewport.mWidth) {\n    return CClippedScreenRect();\n  }\n\n  int right = minX + ((maxX + 2) & ~1);\n  if (right <= mViewport.mLeft) {\n    return CClippedScreenRect();\n  }\n  left = std::max(left, mViewport.mLeft) & ~1;\n  right = (std::min(right, mViewport.mLeft + mViewport.mWidth) + 1) & ~1;\n\n  int top = minY & ~1;\n  if (top >= mViewport.mTop + mViewport.mHeight) {\n    return CClippedScreenRect();\n  }\n\n  int bottom = minY + ((maxY + 2) & ~1);\n  if (bottom <= mViewport.mTop) {\n    return CClippedScreenRect();\n  }\n  top = std::max(top, mViewport.mTop) & ~1;\n  bottom = (std::min(bottom, mViewport.mTop + mViewport.mHeight) + 1) & ~1;\n\n  float minV = static_cast<float>(minY - top) / static_cast<float>(bottom - top);\n  float maxV = static_cast<float>(maxY + (minY - top) + 1) / static_cast<float>(bottom - top);\n\n  int texAlign = 4;\n  switch (fmt) {\n  case ETexelFormat::I8:\n    texAlign = 8;\n    break;\n  case ETexelFormat::IA8:\n  case ETexelFormat::RGB565:\n  case ETexelFormat::RGB5A3:\n    texAlign = 4;\n    break;\n  case ETexelFormat::RGBA8:\n    texAlign = 2;\n    break;\n  default:\n    break;\n  }\n\n  int texWidth = (texAlign + ((right - left) - 1)) & ~(texAlign - 1);\n  float minU = static_cast<float>(minX - left) / static_cast<float>(texWidth);\n  float maxU = static_cast<float>(maxX + (minX - left) + 1) / static_cast<float>(texWidth);\n  return CClippedScreenRect(left, top, right - left, bottom - top, texWidth, minU, maxU, minV, maxV);\n}\n\nvoid CGraphics::SetViewportResolution(const zeus::CVector2i& res) {\n  mRenderModeObj.fbWidth = res.x;\n  mRenderModeObj.efbHeight = res.y;\n  mRenderModeObj.xfbHeight = res.y;\n  SetViewport(0, 0, res.x, res.y);\n  if (g_GuiSys)\n    g_GuiSys->OnViewportResize();\n}\n\nvoid CGraphics::SetViewport(int left, int bottom, int width, int height) {\n  mViewport.mLeft = left;\n  mViewport.mTop = mRenderModeObj.efbHeight - (bottom + height);\n  mViewport.mWidth = width;\n  mViewport.mHeight = height;\n  mViewport.mHalfWidth = static_cast<float>(width / 2);\n  mViewport.mHalfHeight = static_cast<float>(height / 2);\n  GXSetViewport(static_cast<float>(mViewport.mLeft), static_cast<float>(mViewport.mTop),\n                static_cast<float>(mViewport.mWidth), static_cast<float>(mViewport.mHeight), mDepthNear, mDepthFar);\n}\n\nvoid CGraphics::SetScissor(int left, int bottom, int width, int height) {\n  GXSetScissor(left, mRenderModeObj.efbHeight - (bottom + height), width, height);\n}\n\nvoid CGraphics::SetDepthRange(float nearPlane, float farPlane) {\n  mDepthNear = nearPlane;\n  mDepthFar = farPlane;\n  GXSetViewport(static_cast<float>(mViewport.mLeft), static_cast<float>(mViewport.mTop),\n                static_cast<float>(mViewport.mWidth), static_cast<float>(mViewport.mHeight), mDepthNear, mDepthFar);\n}\n\nfloat CGraphics::GetSecondsMod900() {\n  if (mpExternalTimeProvider != nullptr) {\n    return mpExternalTimeProvider->GetSecondsMod900();\n  }\n  return mSecondsMod900;\n}\n\nvoid CGraphics::TickRenderTimings() {\n  //OPTICK_EVENT();\n  mRenderTimings = (mRenderTimings + 1) % (900 * 60);\n  mSecondsMod900 = static_cast<float>(mRenderTimings) / 60.f;\n}\n\nvoid CGraphics::SetUseVideoFilter(bool b) {\n  mUseVideoFilter = b;\n  GXSetCopyFilter(mRenderModeObj.aa, mRenderModeObj.sample_pattern, b ? GX_ENABLE : GX_DISABLE, mRenderModeObj.vfilter);\n}\n\nvoid CGraphics::SetClearColor(const CColor& color) {\n  mClearColor = color;\n  GXSetCopyClear(to_gx_color(color), mClearDepthValue);\n}\n\nvoid CGraphics::SetCopyClear(const CColor& color, float depth) {\n  mClearColor = color;\n  mClearDepthValue = static_cast<u32>(depth * GX_MAX_Z24);\n  GXSetCopyClear(to_gx_color(color), mClearDepthValue);\n}\n\nvoid CGraphics::SetIsBeginSceneClearFb(bool b) { mIsBeginSceneClearFb = b; }\n\nvoid CGraphics::SetTevOp(ERglTevStage stage, const CTevCombiners::CTevPass& pass) {\n  CTevCombiners::SetupPass(stage, pass);\n}\n\n#define STREAM_PRIM_BUFFER_SIZE 240\n\nstatic std::array<Vec, STREAM_PRIM_BUFFER_SIZE + 1> sVtxBuffer;\nstatic std::array<Vec, STREAM_PRIM_BUFFER_SIZE + 1> sNrmBuffer;\nstatic std::array<Vec2, STREAM_PRIM_BUFFER_SIZE + 1> sTxt0Buffer;\nstatic std::array<Vec2, STREAM_PRIM_BUFFER_SIZE + 1> sTxt1Buffer;\nstatic std::array<u32, STREAM_PRIM_BUFFER_SIZE + 1> sClrBuffer;\n\nstatic const uchar kHasNormals = 1;\nstatic const uchar kHasColor = 2;\nstatic const uchar kHasTexture = 4;\n\nvoid CGraphics::StreamBegin(ERglPrimitive primitive) {\n  vtxBuffer = sVtxBuffer.data();\n  nrmBuffer = sNrmBuffer.data();\n  txtBuffer0 = sTxt0Buffer.data();\n  txtBuffer1 = sTxt1Buffer.data();\n  clrBuffer = sClrBuffer.data();\n  ResetVertexDataStream(true);\n  mCurrentPrimitive = primitive;\n  vtxDescr.streamFlags = kHasColor;\n}\n\nvoid CGraphics::StreamNormal(const zeus::CVector3f& nrm) {\n  vtxDescr.nrm.x = nrm.x();\n  vtxDescr.nrm.y = nrm.y();\n  vtxDescr.nrm.z = nrm.z();\n  vtxDescr.streamFlags |= kHasNormals;\n}\n\nvoid CGraphics::StreamColor(const zeus::CColor& color) {\n  vtxDescr.color = color.toRGBA();\n  vtxDescr.streamFlags |= kHasColor;\n}\n\nvoid CGraphics::StreamTexcoord(const zeus::CVector2f& uv) {\n  vtxDescr.uv0.x = uv.x();\n  vtxDescr.uv0.y = uv.y();\n  vtxDescr.streamFlags |= kHasTexture;\n  vtxDescr.textureUsed |= 1;\n}\n\nvoid CGraphics::StreamVertex(const zeus::CVector3f& pos) {\n  vtxDescr.vtx.x = pos.x();\n  vtxDescr.vtx.y = pos.y();\n  vtxDescr.vtx.z = pos.z();\n  UpdateVertexDataStream();\n}\n\nvoid CGraphics::StreamEnd() {\n  if (mNumPrimitives != 0) {\n    FlushStream();\n  }\n  vtxBuffer = nullptr;\n  vtxDescr.streamFlags = 0;\n  vtxDescr.textureUsed = 0;\n  nrmBuffer = nullptr;\n  txtBuffer0 = nullptr;\n  txtBuffer1 = nullptr;\n  clrBuffer = nullptr;\n}\n\nvoid CGraphics::SetLineWidth(float w, ERglTexOffset offs) {\n  CGX::SetLineWidth(static_cast<uchar>(w * 6.f), static_cast<GXTexOffset>(offs));\n}\n\nvoid CGraphics::UpdateVertexDataStream() {\n  ++mNumPrimitives;\n  mpVtxBuffer->x = vtxDescr.vtx.x;\n  mpVtxBuffer->y = vtxDescr.vtx.y;\n  mpVtxBuffer->z = vtxDescr.vtx.z;\n  ++mpVtxBuffer;\n  if ((vtxDescr.streamFlags & kHasNormals) != 0) {\n    mpNrmBuffer->x = vtxDescr.nrm.x;\n    mpNrmBuffer->y = vtxDescr.nrm.y;\n    mpNrmBuffer->z = vtxDescr.nrm.z;\n    ++mpNrmBuffer;\n  }\n  if ((vtxDescr.streamFlags & kHasTexture) != 0) {\n    mpTxtBuffer0->x = vtxDescr.uv0.x;\n    mpTxtBuffer0->y = vtxDescr.uv0.y;\n    ++mpTxtBuffer0;\n\n    mpTxtBuffer1->x = vtxDescr.uv1.x;\n    mpTxtBuffer1->y = vtxDescr.uv1.y;\n    ++mpTxtBuffer1;\n  }\n  if ((vtxDescr.streamFlags & kHasColor) != 0) {\n    *mpClrBuffer = vtxDescr.color;\n    ++mpClrBuffer;\n  }\n  mJustReset = 0;\n  if (mNumPrimitives == STREAM_PRIM_BUFFER_SIZE) {\n    FlushStream();\n    ResetVertexDataStream(false);\n  }\n}\n\nvoid CGraphics::FlushStream() {\n  GXVtxDescList vtxDesc[10];\n\n  GXVtxDescList* curDesc = vtxDesc;\n  const GXVtxDescList vtxDescPos = {GX_VA_POS, GX_DIRECT};\n  *curDesc++ = vtxDescPos;\n\n  if ((vtxDescr.streamFlags & kHasNormals) != 0) {\n    const GXVtxDescList vtxDescNrm = {GX_VA_NRM, GX_DIRECT};\n    *curDesc++ = vtxDescNrm;\n  }\n\n  if ((vtxDescr.streamFlags & kHasColor) != 0) {\n    const GXVtxDescList vtxDescClr0 = {GX_VA_CLR0, GX_DIRECT};\n    *curDesc++ = vtxDescClr0;\n  }\n\n  if ((vtxDescr.streamFlags & kHasTexture) != 0) {\n    const GXVtxDescList vtxDescTex0 = {GX_VA_TEX0, GX_DIRECT};\n    *curDesc++ = vtxDescTex0;\n  }\n\n  const GXVtxDescList vtxDescNull = {GX_VA_NULL, GX_NONE};\n  *curDesc = vtxDescNull;\n\n  CGX::SetVtxDescv(vtxDesc);\n  SetTevStates(vtxDescr.streamFlags);\n  FullRender();\n}\n\nvoid CGraphics::FullRender() {\n  CGX::Begin(static_cast<GXPrimitive>(mCurrentPrimitive), GX_VTXFMT0, mNumPrimitives);\n  switch (vtxDescr.streamFlags) {\n  case 0:\n    for (int i = 0; i < mNumPrimitives; i++) {\n      const Vec& vtx = vtxBuffer[i];\n      GXPosition3f32(vtx.x, vtx.y, vtx.z);\n    }\n    break;\n  case kHasNormals:\n    for (int i = 0; i < mNumPrimitives; i++) {\n      const Vec& vtx = vtxBuffer[i];\n      GXPosition3f32(vtx.x, vtx.y, vtx.z);\n      const Vec& nrm = nrmBuffer[i];\n      GXNormal3f32(nrm.x, nrm.y, nrm.z);\n    }\n    break;\n  case kHasColor:\n    for (int i = 0; i < mNumPrimitives; i++) {\n      const Vec& vtx = vtxBuffer[i];\n      GXPosition3f32(vtx.x, vtx.y, vtx.z);\n      GXColor1u32(clrBuffer[i]);\n    }\n    break;\n  case kHasTexture:\n    for (int i = 0; i < mNumPrimitives; i++) {\n      const Vec& vtx = vtxBuffer[i];\n      GXPosition3f32(vtx.x, vtx.y, vtx.z);\n      const Vec2& uv = txtBuffer0[i];\n      GXTexCoord2f32(uv.x, uv.y);\n    }\n    break;\n  case kHasNormals | kHasTexture:\n    for (int i = 0; i < mNumPrimitives; i++) {\n      const Vec& vtx = vtxBuffer[i];\n      GXPosition3f32(vtx.x, vtx.y, vtx.z);\n      const Vec& nrm = nrmBuffer[i];\n      GXNormal3f32(nrm.x, nrm.y, nrm.z);\n      const Vec2& uv = txtBuffer0[i];\n      GXTexCoord2f32(uv.x, uv.y);\n    }\n    break;\n  case kHasNormals | kHasColor:\n    for (int i = 0; i < mNumPrimitives; i++) {\n      const Vec& vtx = vtxBuffer[i];\n      GXPosition3f32(vtx.x, vtx.y, vtx.z);\n      const Vec& nrm = nrmBuffer[i];\n      GXNormal3f32(nrm.x, nrm.y, nrm.z);\n      GXColor1u32(clrBuffer[i]);\n    }\n    break;\n  case kHasColor | kHasTexture:\n    for (int i = 0; i < mNumPrimitives; i++) {\n      const Vec& vtx = vtxBuffer[i];\n      GXPosition3f32(vtx.x, vtx.y, vtx.z);\n      GXColor1u32(clrBuffer[i]);\n      const Vec2& uv = txtBuffer0[i];\n      GXTexCoord2f32(uv.x, uv.y);\n    }\n    break;\n  case kHasNormals | kHasColor | kHasTexture:\n    for (int i = 0; i < mNumPrimitives; i++) {\n      const Vec& vtx = vtxBuffer[i];\n      GXPosition3f32(vtx.x, vtx.y, vtx.z);\n      const Vec& nrm = nrmBuffer[i];\n      GXNormal3f32(nrm.x, nrm.y, nrm.z);\n      GXColor1u32(clrBuffer[i]);\n      const Vec2& uv = txtBuffer0[i];\n      GXTexCoord2f32(uv.x, uv.y);\n    }\n    break;\n  }\n  CGX::End();\n}\n\nvoid CGraphics::ResetVertexDataStream(bool initial) {\n  mpVtxBuffer = vtxBuffer;\n  mpNrmBuffer = nrmBuffer;\n  mpTxtBuffer0 = txtBuffer0;\n  mpTxtBuffer1 = txtBuffer1;\n  mpClrBuffer = clrBuffer;\n  mNumPrimitives = 0;\n\n  if (initial) {\n    return;\n  }\n\n  switch (mCurrentPrimitive) {\n  case ERglPrimitive::TriangleFan:\n    mpVtxBuffer = vtxBuffer + 1;\n    memcpy(mpVtxBuffer, &vtxDescr.vtx, sizeof(Vec));\n    ++mpVtxBuffer;\n\n    if ((vtxDescr.streamFlags & kHasNormals) != 0) {\n      ++mpNrmBuffer;\n      memcpy(mpNrmBuffer, &vtxDescr.nrm, sizeof(Vec));\n      ++mpNrmBuffer;\n    }\n\n    if ((vtxDescr.streamFlags & kHasTexture) != 0) {\n      ++mpTxtBuffer0;\n      memcpy(mpTxtBuffer0, &vtxDescr.uv0, sizeof(Vec2));\n      ++mpTxtBuffer0;\n\n      ++mpTxtBuffer1;\n      memcpy(mpTxtBuffer1, &vtxDescr.uv1, sizeof(Vec2));\n      ++mpTxtBuffer1;\n    }\n\n    if ((vtxDescr.streamFlags & kHasColor) != 0) {\n      ++mpClrBuffer;\n      *mpClrBuffer = vtxDescr.color;\n      ++mpClrBuffer;\n    }\n\n    mNumPrimitives += 2;\n    break;\n\n  default:\n    break;\n  }\n\n  mJustReset = 1;\n}\n\nvoid CGraphics::DrawPrimitive(ERglPrimitive primitive, const zeus::CVector3f* pos, const zeus::CVector3f& normal,\n                              const zeus::CColor& col, s32 numVerts) {\n  StreamBegin(primitive);\n  StreamNormal(normal);\n  StreamColor(col);\n  for (u32 i = 0; i < numVerts; ++i) {\n    StreamVertex(pos[i]);\n  }\n  StreamEnd();\n}\n\nvoid CGraphics::SetTevStates(u32 flags) noexcept {\n  switch (flags) {\n  case 0:\n  case kHasNormals:\n  case kHasColor:\n  case kHasNormals | kHasColor:\n    CGX::SetNumChans(1);\n    CGX::SetNumTexGens(0);\n    CGX::SetNumTevStages(1);\n    CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0);\n    CGX::SetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0);\n    break;\n  case kHasTexture:\n  case kHasNormals | kHasTexture:\n  case kHasColor | kHasTexture:\n  case kHasNormals | kHasColor | kHasTexture:\n    CGX::SetNumChans(1);\n    if ((vtxDescr.textureUsed & 3) != 0) {\n      CGX::SetNumTexGens(2);\n    } else {\n      CGX::SetNumTexGens(1);\n    }\n    CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0);\n    CGX::SetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD1, GX_TEXMAP1, GX_COLOR0A0);\n    break;\n  }\n  CGX::SetNumIndStages(0);\n  CGX::SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY, false, GX_PTIDENTITY);\n  CGX::SetTexCoordGen(GX_TEXCOORD1, GX_TG_MTX2x4, GX_TG_TEX1, GX_IDENTITY, false, GX_PTIDENTITY);\n\n  GX::LightMask light = mLightActive;\n  GXAttnFn attnFn = GX_AF_NONE;\n  if (light.any()) {\n    attnFn = GX_AF_SPOT;\n  }\n  GXDiffuseFn diffFn = GX_DF_NONE;\n  if (light.any()) {\n    diffFn = GX_DF_CLAMP;\n  }\n  CGX::SetChanCtrl(CGX::EChannelId::Channel0, light.any() ? GX_ENABLE : GX_DISABLE, GX_SRC_REG,\n                   (flags & kHasColor) ? GX_SRC_VTX : GX_SRC_REG, light, diffFn, attnFn);\n}\n\nbool CGraphics::Startup() {\n  // mpFifo = fifoBase;\n  // mpFifoObj = GXInit(fifoBase, fifoSize);\n  mpFifoObj = GXInit(nullptr, 0);\n  // GXFifoObj fifoObj;\n  // GXInitFifoBase(&fifoObj, mpFifo, fifoSize);\n  // GXSetCPUFifo(&fifoObj);\n  // GXSetGPFifo(&fifoObj);\n  // GXInitFifoLimits(mpFifoObj, fifoSize - 0x4000, fifoSize - 0x10000);\n  // GXSetCPUFifo(mpFifoObj);\n  // GXSetGPFifo(mpFifoObj);\n  // GXSetMisc(GX_MT_XF_FLUSH, 8);\n  GXSetDither(GX_FALSE);\n  CGX::ResetGXStates();\n  InitGraphicsVariables();\n  ConfigureFrameBuffer(/*osContext*/);\n  // for (int i = 0; i < ARRAY_SIZE(mTexRegions); i++) {\n  //   GXInitTexCacheRegion(&mTexRegions[i], false, 0x8000 * i, GX_TEXCACHE_32K, 0x80000 + (0x8000 * i),\n  //   GX_TEXCACHE_32K);\n  // }\n  // for (int i = 0; i < ARRAY_SIZE(mTexRegionsCI); i++) {\n  //   GXInitTexCacheRegion(&mTexRegionsCI[i], false, (8 + (2 * i)) << 0xF, GX_TEXCACHE_32K, (9 + (2 * i)) << 0xF,\n  //                        GX_TEXCACHE_32K);\n  // }\n  // mGXDefaultTexRegionCallback = GXSetTexRegionCallback(TexRegionCallback);\n  mSpareBufferSize = sSpareFrameBuffer.size();\n  mpSpareBuffer = sSpareFrameBuffer.data();\n  mSpareBufferTexCacheSize = 0x10000;\n  return true;\n}\n\n#define ARRAY_SIZE(arr) static_cast<int>(sizeof(arr) / sizeof(arr[0]))\n\nvoid CGraphics::InitGraphicsVariables() {\n  for (int i = 0; i < ARRAY_SIZE(mLightTypes); ++i) {\n    mLightTypes[i] = ELightType::Directional;\n  }\n  mLightActive = 0;\n  SetDepthWriteMode(false, mDepthFunc, false);\n  SetCullMode(ERglCullMode::None);\n  SetAmbientColor(CColor(0.2f, 0.2f, 0.2f, 1.f));\n  mIsGXModelMatrixIdentity = false;\n  SetIdentityViewPointMatrix();\n  SetIdentityModelMatrix();\n  SetViewport(0, 0, mViewport.mWidth, mViewport.mHeight);\n  SetPerspective(60.f, static_cast<float>(mViewport.mWidth) / static_cast<float>(mViewport.mHeight), mProj.GetNear(),\n                 mProj.GetFar());\n  SetCopyClear(mClearColor, 1.f);\n  constexpr GXColor white = {0xFF, 0xFF, 0xFF, 0xFF};\n  CGX::SetChanMatColor(CGX::EChannelId::Channel0, white);\n  sRenderState.ResetFlushAll();\n}\n\nvoid CGraphics::InitGraphicsDefaults() {\n  SetDepthRange(0.f, 1.f);\n  mIsGXModelMatrixIdentity = false;\n  SetModelMatrix(mModelMatrix);\n  SetViewPointMatrix(mViewMatrix);\n  SetDepthWriteMode(false, mDepthFunc, false);\n  SetCullMode(mCullMode);\n  SetViewport(mViewport.mLeft, mViewport.mTop, mViewport.mWidth, mViewport.mHeight);\n  FlushProjection();\n  CTevCombiners::Init();\n  DisableAllLights();\n  SetDefaultVtxAttrFmt();\n}\n\nvoid CGraphics::ConfigureFrameBuffer(/*const COsContext& osContext*/) {\n  // mRenderModeObj = osContext.GetRenderModeObj();\n  // mpFrameBuf1 = osContext.GetFramebuf1();\n  // mpFrameBuf2 = osContext.GetFramebuf2();\n  // VIConfigure(&mRenderModeObj);\n  // VISetNextFrameBuffer(mpFrameBuf1);\n  mpCurrenFrameBuf = mpFrameBuf2;\n  GXSetViewport(0.f, 0.f, static_cast<float>(mRenderModeObj.fbWidth), static_cast<float>(mRenderModeObj.efbHeight), 0.f,\n                1.f);\n  GXSetScissor(0, 0, mRenderModeObj.fbWidth, mRenderModeObj.efbHeight);\n  GXSetDispCopySrc(0, 0, mRenderModeObj.fbWidth, mRenderModeObj.efbHeight);\n  GXSetDispCopyDst(mRenderModeObj.fbWidth, mRenderModeObj.efbHeight);\n  GXSetDispCopyYScale(static_cast<float>(mRenderModeObj.xfbHeight) / static_cast<float>(mRenderModeObj.efbHeight));\n  GXSetCopyFilter(mRenderModeObj.aa, mRenderModeObj.sample_pattern, GX_ENABLE, mRenderModeObj.vfilter);\n  if (mRenderModeObj.aa) {\n    GXSetPixelFmt(GX_PF_RGB565_Z16, GX_ZC_LINEAR);\n  } else {\n    GXSetPixelFmt(GX_PF_RGB8_Z24, GX_ZC_LINEAR);\n  }\n  GXSetDispCopyGamma(GX_GM_1_0);\n  GXCopyDisp(mpCurrenFrameBuf, true);\n  VIFlush();\n  // VIWaitForRetrace();\n  // VIWaitForRetrace();\n  mViewport.mWidth = mRenderModeObj.fbWidth;\n  mViewport.mHeight = mRenderModeObj.efbHeight;\n  InitGraphicsDefaults();\n}\n\nvoid CGraphics::SetDefaultVtxAttrFmt() {\n  GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0);\n  GXSetVtxAttrFmt(GX_VTXFMT1, GX_VA_POS, GX_POS_XYZ, GX_F32, 0);\n  GXSetVtxAttrFmt(GX_VTXFMT2, GX_VA_POS, GX_POS_XYZ, GX_F32, 0);\n  GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_NRM, GX_NRM_XYZ, GX_F32, 0);\n  GXSetVtxAttrFmt(GX_VTXFMT1, GX_VA_NRM, GX_NRM_XYZ, GX_S16, 14);\n  GXSetVtxAttrFmt(GX_VTXFMT2, GX_VA_NRM, GX_NRM_XYZ, GX_S16, 14);\n  GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);\n  GXSetVtxAttrFmt(GX_VTXFMT1, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);\n  GXSetVtxAttrFmt(GX_VTXFMT2, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);\n  GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0);\n  GXSetVtxAttrFmt(GX_VTXFMT1, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0);\n  GXSetVtxAttrFmt(GX_VTXFMT2, GX_VA_TEX0, GX_TEX_ST, GX_U16, 15);\n  for (int i = 1; i <= 7; ++i) {\n    GXAttr attr = static_cast<GXAttr>(GX_VA_TEX0 + i);\n    GXSetVtxAttrFmt(GX_VTXFMT0, attr, GX_TEX_ST, GX_F32, 0);\n    GXSetVtxAttrFmt(GX_VTXFMT1, attr, GX_TEX_ST, GX_F32, 0);\n    GXSetVtxAttrFmt(GX_VTXFMT2, attr, GX_TEX_ST, GX_F32, 0);\n  }\n}\n\nvoid CGraphics::ResetGfxStates() noexcept { sRenderState.Set(0); }\n\nvoid CGraphics::LoadDolphinSpareTexture(int width, int height, GXTexFmt fmt, void* data, GXTexMapID texId) {\n  TGXTexObj texObj;\n  GXInitTexObj(&texObj, data != nullptr ? data : mpSpareBuffer, width, height, fmt, GX_CLAMP, GX_CLAMP, GX_DISABLE);\n  GXInitTexObjLOD(&texObj, GX_NEAR, GX_NEAR, 0.f, 0.f, 0.f, GX_DISABLE, GX_DISABLE, GX_ANISO_1);\n  GXLoadTexObj(&texObj, texId);\n  // CTexture::InvalidateTexmap(texId);\n  // if (texId == GX_TEXMAP7) {\n  //   GXInvalidateTexRegion(&mTexRegions[0]);\n  // }\n}\n\nvoid CGraphics::LoadDolphinSpareTexture(int width, int height, GXCITexFmt fmt, GXTlut tlut, void* data,\n                                        GXTexMapID texId) {\n  TGXTexObj texObj;\n  GXInitTexObjCI(&texObj, data != nullptr ? data : mpSpareBuffer, width, height, fmt, GX_CLAMP, GX_CLAMP, GX_DISABLE,\n                 tlut);\n  GXInitTexObjLOD(&texObj, GX_NEAR, GX_NEAR, 0.f, 0.f, 0.f, GX_DISABLE, GX_DISABLE, GX_ANISO_1);\n  GXLoadTexObj(&texObj, texId);\n  // CTexture::InvalidateTexmap(texId);\n  // if (texId == GX_TEXMAP7) {\n  //   GXInvalidateTexRegion(&mTexRegions[0]);\n  // }\n}\n\nCGraphics::CRenderState::CRenderState() {\n  x0_ = 0;\n  x4_ = 0;\n}\n\nvoid CGraphics::CRenderState::Flush() {}\n\nint CGraphics::CRenderState::SetVtxState(const float* pos, const float* nrm, const uint* clr) {\n  // CGX::SetArray(GX_VA_POS, pos, 12);\n  // CGX::SetArray(GX_VA_NRM, nrm, 12);\n  // CGX::SetArray(GX_VA_CLR0, clr, 4);\n  int result = 1;\n  if (nrm != nullptr) {\n    result |= 2;\n  }\n  if (clr != nullptr) {\n    result |= 16;\n  }\n  return result;\n}\n\nvoid CGraphics::CRenderState::ResetFlushAll() {\n  x0_ = 0;\n  SetVtxState(nullptr, nullptr, nullptr);\n  for (int i = 0; i < 8; i++) {\n    CGX::SetArray(static_cast<GXAttr>(GX_VA_TEX0 + i), std::span<const CVector3f>{});\n  }\n  Flush();\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CGraphics.hpp",
    "content": "#pragma once\n\n#include \"Runtime/ConsoleVariables/CVar.hpp\"\n#include \"Runtime/Graphics/CLight.hpp\"\n#include \"Runtime/Graphics/CTexture.hpp\"\n#include \"Runtime/Graphics/CTevCombiners.hpp\"\n#include \"Runtime/Graphics/GX.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n\n#include <vector>\n\n#include <zeus/CColor.hpp>\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector2f.hpp>\n#include <zeus/CVector2i.hpp>\n\n#include <aurora/gfx.h>\n// #include <optick.h>\n#include <dolphin/mtx.h>\n\nnamespace metaforce {\nclass CTexture;\nextern CVar* g_disableLighting;\nclass CTimeProvider;\n\nenum class ERglCullMode : std::underlying_type_t<GXCullMode> {\n  None = GX_CULL_NONE,\n  Front = GX_CULL_FRONT,\n  Back = GX_CULL_BACK,\n  All = GX_CULL_ALL,\n};\n\nenum class ERglBlendMode : std::underlying_type_t<GXBlendMode> {\n  None = GX_BM_NONE,\n  Blend = GX_BM_BLEND,\n  Logic = GX_BM_LOGIC,\n  Subtract = GX_BM_SUBTRACT,\n  Max = GX_MAX_BLENDMODE,\n};\n\nenum class ERglBlendFactor : std::underlying_type_t<GXBlendFactor> {\n  Zero = GX_BL_ZERO,\n  One = GX_BL_ONE,\n  SrcColor = GX_BL_SRCCLR,\n  InvSrcColor = GX_BL_INVSRCCLR,\n  SrcAlpha = GX_BL_SRCALPHA,\n  InvSrcAlpha = GX_BL_INVSRCALPHA,\n  DstAlpha = GX_BL_DSTALPHA,\n  InvDstAlpha = GX_BL_INVDSTALPHA,\n  DstColor = GX_BL_DSTCLR,\n  InvDstColor = GX_BL_INVDSTCLR,\n};\n\nenum class ERglLogicOp : std::underlying_type_t<GXLogicOp> {\n  Clear = GX_LO_CLEAR,\n  And = GX_LO_AND,\n  RevAnd = GX_LO_REVAND,\n  Copy = GX_LO_COPY,\n  InvAnd = GX_LO_INVAND,\n  NoOp = GX_LO_NOOP,\n  Xor = GX_LO_XOR,\n  Or = GX_LO_OR,\n  Nor = GX_LO_NOR,\n  Equiv = GX_LO_EQUIV,\n  Inv = GX_LO_INV,\n  RevOr = GX_LO_REVOR,\n  InvCopy = GX_LO_INVCOPY,\n  InvOr = GX_LO_INVOR,\n  NAnd = GX_LO_NAND,\n  Set = GX_LO_SET,\n};\n\nenum class ERglAlphaFunc : std::underlying_type_t<GXCompare> {\n  Never = GX_NEVER,\n  Less = GX_LESS,\n  Equal = GX_EQUAL,\n  LEqual = GX_LEQUAL,\n  Greater = GX_GREATER,\n  NEqual = GX_NEQUAL,\n  GEqual = GX_GEQUAL,\n  Always = GX_ALWAYS,\n};\n\nenum class ERglAlphaOp : std::underlying_type_t<GXAlphaOp> {\n  And = GX_AOP_AND,\n  Or = GX_AOP_OR,\n  Xor = GX_AOP_XOR,\n  XNor = GX_AOP_XNOR,\n  Max = GX_MAX_ALPHAOP,\n};\n\nenum class ERglEnum : std::underlying_type_t<GXCompare> {\n  Never = GX_NEVER,\n  Less = GX_LESS,\n  Equal = GX_EQUAL,\n  LEqual = GX_LEQUAL,\n  Greater = GX_GREATER,\n  NEqual = GX_NEQUAL,\n  GEqual = GX_GEQUAL,\n  Always = GX_ALWAYS,\n};\n\nenum class ERglPrimitive : std::underlying_type_t<GXPrimitive> {\n  Quads = GX_QUADS,\n  Triangles = GX_TRIANGLES,\n  TriangleStrip = GX_TRIANGLESTRIP,\n  TriangleFan = GX_TRIANGLEFAN,\n  Lines = GX_LINES,\n  LineStrip = GX_LINESTRIP,\n  Points = GX_POINTS,\n};\n\nusing ERglLight = u8;\n\nenum class ERglTexOffset : std::underlying_type_t<GXTexOffset> {\n  Zero = GX_TO_ZERO,\n  Sixteenth = GX_TO_SIXTEENTH,\n  Eighth = GX_TO_EIGHTH,\n  Fourth = GX_TO_FOURTH,\n  Half = GX_TO_HALF,\n  One = GX_TO_ONE,\n};\n\nenum class ERglFogMode : std::underlying_type_t<GXFogType> {\n  None = GX_FOG_NONE,\n\n  PerspLin = GX_FOG_PERSP_LIN,\n  PerspExp = GX_FOG_PERSP_EXP,\n  PerspExp2 = GX_FOG_ORTHO_EXP2,\n  PerspRevExp = GX_FOG_PERSP_REVEXP,\n  PerspRevExp2 = GX_FOG_PERSP_REVEXP2,\n\n  OrthoLin = GX_FOG_ORTHO_LIN,\n  OrthoExp = GX_FOG_ORTHO_EXP,\n  OrthoExp2 = GX_FOG_ORTHO_EXP2,\n  OrthoRevExp = GX_FOG_ORTHO_REVEXP,\n  OrthoRevExp2 = GX_FOG_ORTHO_REVEXP2,\n};\n\nstruct CViewport {\n  int mLeft;\n  int mTop;\n  int mWidth;\n  int mHeight;\n  float mHalfWidth;\n  float mHalfHeight;\n};\n\n// TODO\ntypedef struct {\n  float x;\n  float y;\n} Vec2, *Vec2Ptr;\n\n#define DEPTH_FAR 1.f\n#define DEPTH_SKY 0.999f\n#define DEPTH_TARGET_MANAGER 0.12500012f\n#define DEPTH_WORLD (1.f / 8.f)\n#define DEPTH_GUN (1.f / 32.f)\n#define DEPTH_SCREEN_ACTORS (1.f / 64.f)\n#define DEPTH_HUD (1.f / 512.f)\n#define DEPTH_NEAR 0.f\n#define CUBEMAP_RES 256\n#define CUBEMAP_MIPS 6\n\nenum class ETexelFormat;\n\nclass CGraphics {\npublic:\n  using CVector3f = zeus::CVector3f;\n  using CTransform4f = zeus::CTransform;\n  using CColor = zeus::CColor;\n  using uchar = unsigned char;\n  using uint = unsigned int;\n\n  class CRenderState {\n  public:\n    CRenderState();\n\n    void Flush();\n    void ResetFlushAll();\n    int SetVtxState(const float* pos, const float* nrm, const uint* clr);\n\n    // In map this takes two args, but x4 is unused?\n    void Set(int v0) { x0_ = v0; }\n\n  private:\n    int x0_;\n    int x4_;\n  };\n\n  class CProjectionState {\n  public:\n    CProjectionState(bool persp, float left, float right, float top, float bottom, float nearPlane, float farPlane)\n    : x0_persp(persp)\n    , x4_left(left)\n    , x8_right(right)\n    , xc_top(top)\n    , x10_bottom(bottom)\n    , x14_near(nearPlane)\n    , x18_far(farPlane) {}\n\n    bool IsPerspective() const { return x0_persp; }\n    float GetLeft() const { return x4_left; }\n    float GetRight() const { return x8_right; }\n    float GetTop() const { return xc_top; }\n    float GetBottom() const { return x10_bottom; }\n    float GetNear() const { return x14_near; }\n    float GetFar() const { return x18_far; }\n\n  private:\n    bool x0_persp;\n    float x4_left;\n    float x8_right;\n    // TODO: I think top/bottom are flipped\n    float xc_top;\n    float x10_bottom;\n    float x14_near;\n    float x18_far;\n  };\n\n  class CClippedScreenRect {\n  public:\n    CClippedScreenRect() : x0_valid(false) {}\n    CClippedScreenRect(int x, int y, int width, int height, int texWidth, float minU, float maxU, float minV,\n                       float maxV)\n    : x0_valid(true)\n    , x4_x(x)\n    , x8_y(y)\n    , xc_width(width)\n    , x10_height(height)\n    , x14_texWidth(texWidth)\n    , x18_minU(minU)\n    , x1c_maxU(maxU)\n    , x20_minV(minV)\n    , x24_maxV(maxV) {}\n\n    bool IsValid() const { return x0_valid; }\n    int GetX() const { return x4_x; }\n    int GetY() const { return x8_y; }\n    int GetWidth() const { return xc_width; }\n    int GetHeight() const { return x10_height; }\n    int GetTexWidth() const { return x14_texWidth; }\n    float GetMinU() const { return x18_minU; }\n    float GetMaxU() const { return x1c_maxU; }\n    float GetMinV() const { return x20_minV; }\n    float GetMaxV() const { return x24_maxV; }\n\n  private:\n    bool x0_valid;\n    int x4_x;\n    int x8_y;\n    int xc_width;\n    int x10_height;\n    int x14_texWidth;\n    float x18_minU;\n    float x1c_maxU;\n    float x20_minV;\n    float x24_maxV;\n  };\n\n  static CRenderState sRenderState;\n  static VecPtr vtxBuffer;\n  static VecPtr nrmBuffer;\n  static Vec2Ptr txtBuffer0;\n  static Vec2Ptr txtBuffer1;\n  static uint* clrBuffer;\n  static bool mJustReset;\n  static ERglCullMode mCullMode;\n  static int mNumLightsActive;\n  static float mDepthNear;\n  static VecPtr mpVtxBuffer;\n  static VecPtr mpNrmBuffer;\n  static Vec2Ptr mpTxtBuffer0;\n  static Vec2Ptr mpTxtBuffer1;\n  static uint* mpClrBuffer;\n  static int mNumPrimitives;\n  static int mFrameCounter;\n  static float mFramesPerSecond;\n  static float mLastFramesPerSecond;\n  static int mNumBreakpointsWaiting;\n  static int mFlippingState;\n  static bool mLastFrameUsedAbove;\n  static bool mInterruptLastFrameUsedAbove;\n  static GX::LightMask mLightActive;\n  static GX::LightMask mLightsWereOn;\n  static void* mpFrameBuf1;\n  static void* mpFrameBuf2;\n  static void* mpCurrenFrameBuf;\n  static int mSpareBufferSize;\n  static void* mpSpareBuffer;\n  static int mSpareBufferTexCacheSize;\n  // static GXTexRegionCallback mGXDefaultTexRegionCallback;\n  static void* mpFifo;\n  static GXFifoObj* mpFifoObj;\n  static uint mRenderTimings;\n  static float mSecondsMod900;\n  static CTimeProvider* mpExternalTimeProvider;\n  static int mScreenStretch;\n  static int mScreenPositionX;\n  static int mScreenPositionY;\n\n  static CVector3f kDefaultPositionVector;\n  static CVector3f kDefaultDirectionVector;\n  static CProjectionState mProj;\n  static CTransform4f mViewMatrix;\n  static CTransform4f mModelMatrix;\n  static CColor mClearColor;\n  static CVector3f mViewPoint;\n  static CViewport mViewport;\n  static ELightType mLightTypes[8];\n  static GXLightObj mLightObj[8];\n  // static GXTexRegion mTexRegions[GX_MAX_TEXMAP];\n  // static GXTexRegion mTexRegionsCI[GX_MAX_TEXMAP / 2];\n  static GXRenderModeObj mRenderModeObj;\n  static Mtx mGXViewPointMatrix;\n  static Mtx mGXModelMatrix;\n  static Mtx mGxModelView;\n  static Mtx mCameraMtx;\n\n  static bool mIsBeginSceneClearFb;\n  static ERglEnum mDepthFunc;\n  static ERglPrimitive mCurrentPrimitive;\n  static float mDepthFar;\n  static u32 mClearDepthValue; // = GX_MAX_Z24\n  static bool mIsGXModelMatrixIdentity;\n  static bool mFirstFrame;\n  static GXBool mUseVideoFilter;\n  static float mBrightness;\n\n  static const GXTexMapID kSpareBufferTexMapID;\n\n  static bool Startup();\n  static void InitGraphicsVariables();\n  static void InitGraphicsDefaults();\n  static void ConfigureFrameBuffer();\n  static void SetDefaultVtxAttrFmt();\n  static void DisableAllLights();\n  static void LoadLight(ERglLight light, const CLight& info);\n  static void EnableLight(ERglLight light);\n  static void SetLightState(GX::LightMask lightState);\n  static void SetAmbientColor(const zeus::CColor& col);\n  static void SetFog(ERglFogMode mode, float startz, float endz, const zeus::CColor& color);\n  static void SetDepthWriteMode(bool test, ERglEnum comp, bool write);\n  static void SetBlendMode(ERglBlendMode, ERglBlendFactor, ERglBlendFactor, ERglLogicOp);\n  static void SetCullMode(ERglCullMode);\n  static void BeginScene();\n  static void EndScene();\n  static void Render2D(CTexture& tex, int x, int y, int w, int h, const zeus::CColor& col, bool scale);\n  static void SetAlphaCompare(ERglAlphaFunc comp0, u8 ref0, ERglAlphaOp op, ERglAlphaFunc comp1, u8 ref1);\n  static void SetViewPointMatrix(const zeus::CTransform& xf);\n  static void SetViewMatrix();\n  static void SetModelMatrix(const zeus::CTransform& xf);\n  static zeus::CMatrix4f CalculatePerspectiveMatrix(float fovy, float aspect, float znear, float zfar);\n  static zeus::CMatrix4f GetPerspectiveProjectionMatrix();\n  static const CProjectionState& GetProjectionState();\n  static void SetProjectionState(const CProjectionState&);\n  static void SetPerspective(float fovy, float aspect, float znear, float zfar);\n  static void SetOrtho(float left, float right, float top, float bottom, float znear, float zfar);\n  static void FlushProjection();\n  static zeus::CVector2i ProjectPoint(const zeus::CVector3f& point);\n  static CClippedScreenRect ClipScreenRectFromVS(const CVector3f& p1, const CVector3f& p2, ETexelFormat fmt);\n  static CClippedScreenRect ClipScreenRectFromMS(const CVector3f& p1, const CVector3f& p2, ETexelFormat fmt);\n\n  static void SetViewportResolution(const zeus::CVector2i& res);\n  static void SetViewport(int leftOff, int bottomOff, int width, int height);\n  static void SetScissor(int leftOff, int bottomOff, int width, int height);\n  static void SetDepthRange(float nearPlane, float farPlane);\n  static void SetIdentityViewPointMatrix();\n  static void SetIdentityModelMatrix();\n  static void ClearBackAndDepthBuffers();\n\n  static void SetExternalTimeProvider(CTimeProvider* provider) { mpExternalTimeProvider = provider; }\n  static float GetSecondsMod900();\n  static void TickRenderTimings();\n  static int GetFrameCounter() { return mFrameCounter; }\n  static float GetFPS() { return mFramesPerSecond; }\n  static void SetUseVideoFilter(bool);\n  static void SetClearColor(const zeus::CColor& color);\n  static void SetCopyClear(const zeus::CColor& color, float depth);\n  static void SetIsBeginSceneClearFb(bool clear);\n  static int GetViewportLeft() { return mViewport.mLeft; }\n  static int GetViewportTop() { return mViewport.mTop; }\n  static int GetViewportWidth() { return mViewport.mWidth; }\n  static int GetViewportHeight() { return mViewport.mHeight; }\n  static float GetViewportHalfWidth() { return mViewport.mHalfWidth; }\n  static float GetViewportHalfHeight() { return mViewport.mHalfHeight; }\n  static float GetViewportAspect() {\n    return static_cast<float>(mViewport.mWidth) / static_cast<float>(mViewport.mHeight);\n  }\n  static const CVector3f& GetViewPoint() { return mViewPoint; }\n  static const CTransform4f& GetViewMatrix() { return mViewMatrix; }\n  static const CTransform4f& GetModelMatrix() { return mModelMatrix; }\n  static GX::LightMask GetLightMask() { return mLightActive; }\n\n  static void LoadDolphinSpareTexture(int width, int height, GXTexFmt format, void* data, GXTexMapID id);\n  static void LoadDolphinSpareTexture(int width, int height, GXCITexFmt format, GXTlut tlut, void* data, GXTexMapID id);\n\n  static void ResetGfxStates() noexcept;\n  static void SetTevStates(u32 flags) noexcept;\n  static void SetTevOp(ERglTevStage stage, const CTevCombiners::CTevPass& pass);\n  static void StreamBegin(ERglPrimitive primitive);\n  static void StreamNormal(const zeus::CVector3f& nrm);\n  static void StreamColor(const zeus::CColor& color);\n  static inline void StreamColor(float r, float g, float b, float a) { StreamColor({r, g, b, a}); }\n  static void StreamTexcoord(const zeus::CVector2f& uv);\n  static inline void StreamTexcoord(float x, float y) { StreamTexcoord({x, y}); }\n  static void StreamVertex(const zeus::CVector3f& pos);\n  static inline void StreamVertex(float xyz) { StreamVertex({xyz, xyz, xyz}); }\n  static inline void StreamVertex(float x, float y, float z) { StreamVertex({x, y, z}); }\n  static void StreamEnd();\n  static void UpdateVertexDataStream();\n  static void ResetVertexDataStream(bool end);\n  static void FlushStream();\n  static void FullRender();\n  static void DrawPrimitive(ERglPrimitive primitive, const zeus::CVector3f* pos, const zeus::CVector3f& normal,\n                            const zeus::CColor& col, s32 numVerts);\n  static void SetLineWidth(float width, ERglTexOffset offs);\n};\n\ntemplate <class VTX>\nclass TriFanToStrip {\n  std::vector<VTX>& m_vec;\n  size_t m_start;\n  size_t m_added = 0;\n\npublic:\n  explicit TriFanToStrip(std::vector<VTX>& vec) : m_vec(vec), m_start(vec.size()) {}\n\n  void AddVert(const VTX& vert) {\n    ++m_added;\n    if (m_added > 3 && (m_added & 1) == 0) {\n      m_vec.reserve(m_vec.size() + 3);\n      m_vec.push_back(m_vec.back());\n      m_vec.push_back(m_vec[m_start]);\n    }\n    m_vec.push_back(vert);\n  }\n\n  template <class... _Args>\n  void EmplaceVert(_Args&&... args) {\n    ++m_added;\n    if (m_added > 3 && (m_added & 1) == 0) {\n      m_vec.reserve(m_vec.size() + 3);\n      m_vec.push_back(m_vec.back());\n      m_vec.push_back(m_vec[m_start]);\n    }\n    m_vec.emplace_back(std::forward<_Args>(args)...);\n  }\n\n  //  void Draw() const { CGraphics::DrawArray(m_start, m_vec.size() - m_start); }\n};\n\n#ifdef AURORA_GFX_DEBUG_GROUPS\nstruct ScopedDebugGroup {\n  inline ScopedDebugGroup(const char* label) noexcept { push_debug_group(label); }\n  inline ~ScopedDebugGroup() noexcept { pop_debug_group(); }\n};\n#define SCOPED_GRAPHICS_DEBUG_GROUP(name, ...)                                                                         \\\n  ScopedDebugGroup _GfxDbg_ { name }\n#else\n#define SCOPED_GRAPHICS_DEBUG_GROUP(name, ...)\n// OPTICK_EVENT_DYNAMIC(name)\n#endif\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CGraphicsPalette.cpp",
    "content": "#include \"Runtime/Graphics/CGraphicsPalette.hpp\"\n#include \"Runtime/Streams/CInputStream.hpp\"\n\nnamespace metaforce {\nu32 CGraphicsPalette::sCurrentFrameCount = 0;\n\nCGraphicsPalette::CGraphicsPalette(EPaletteFormat fmt, int count)\n: x0_fmt(fmt), x8_entryCount(count), xc_entries(std::make_unique<u16[]>(count)) {\n  GXInitTlutObj(&x10_tlutObj, xc_entries.get(), static_cast<GXTlutFmt>(x0_fmt), x8_entryCount);\n}\n\nCGraphicsPalette::CGraphicsPalette(CInputStream& in) : x0_fmt(EPaletteFormat(in.ReadLong())) {\n  u16 w = in.ReadShort();\n  u16 h = in.ReadShort();\n  x8_entryCount = w * h;\n  xc_entries = std::make_unique<u16[]>(x8_entryCount);\n  in.Get(reinterpret_cast<u8*>(xc_entries.get()), x8_entryCount * sizeof(u16));\n  GXInitTlutObj(&x10_tlutObj, xc_entries.get(), static_cast<GXTlutFmt>(x0_fmt), x8_entryCount);\n  // DCFlushRange(xc_entries.get(), x8_entryCount * 2);\n}\n\nCGraphicsPalette::~CGraphicsPalette() {\n#ifdef AURORA\n  GXDestroyTlutObj(&x10_tlutObj);\n#endif\n}\n\nvoid CGraphicsPalette::Load() {\n  GXLoadTlut(&x10_tlutObj, GX_TLUT0);\n  x4_frameLoaded = sCurrentFrameCount;\n}\n\nvoid CGraphicsPalette::UnLock() {\n  // DCStoreRange(xc_lut, x8_numEntries << 1);\n  GXInitTlutObj(&x10_tlutObj, xc_entries.get(), static_cast<GXTlutFmt>(x0_fmt), x8_entryCount);\n  // DCFlushRange(xc_lut, x8_numEntries << 1);\n  x1c_locked = false;\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CGraphicsPalette.hpp",
    "content": "#pragma once\n\n#include \"RetroTypes.hpp\"\n#include \"GX.hpp\"\n\n#include <memory>\n\nnamespace metaforce {\nclass CInputStream;\n\nenum class EPaletteFormat : std::underlying_type_t<GXTlutFmt> {\n  IA8 = GX_TL_IA8,\n  RGB565 = GX_TL_RGB565,\n  RGB5A3 = GX_TL_RGB5A3,\n};\n\nclass CGraphicsPalette {\n  static u32 sCurrentFrameCount;\n  friend class CTextRenderBuffer;\n  EPaletteFormat x0_fmt;\n  u32 x4_frameLoaded{};\n  u32 x8_entryCount;\n  std::unique_ptr<u16[]> xc_entries;\n  GXTlutObj x10_tlutObj;\n  bool x1c_locked = false;\n\npublic:\n  explicit CGraphicsPalette(EPaletteFormat fmt, int count);\n  explicit CGraphicsPalette(CInputStream& in);\n  ~CGraphicsPalette();\n\n  u16* Lock() {\n    x1c_locked = true;\n    return xc_entries.get();\n  }\n  void UnLock();\n  void Load();\n\n  [[nodiscard]] const u16* GetPaletteData() const { return xc_entries.get(); }\n\n  static void SetCurrentFrameCount(u32 frameCount) { sCurrentFrameCount = frameCount; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CLight.cpp",
    "content": "#include \"Runtime/Graphics/CLight.hpp\"\n\n#include <cfloat>\n\nnamespace metaforce {\n\nconstexpr zeus::CVector3f kDefaultPosition(0.f, 0.f, 0.f);\nconstexpr zeus::CVector3f kDefaultDirection(0.f, -1.f, 0.f);\n\nfloat CLight::CalculateLightRadius() const {\n  if (x28_distL < FLT_EPSILON &&  x2c_distQ < FLT_EPSILON) {\n    return FLT_MAX;\n  }\n\n  float intensity = GetIntensity();\n  float ret = 0.f;\n  if (x2c_distQ > FLT_EPSILON) {\n    constexpr float mulVal = std::min(0.05882353f, 0.2f); // Yes, retro really did do this\n    if (intensity > FLT_EPSILON) {\n      return std::sqrt(intensity / (mulVal * x2c_distQ));\n    }\n  } else {\n    constexpr float mulVal = std::min(0.05882353f, 0.2f); // See above comment\n    if (x28_distL > FLT_EPSILON) {\n      return intensity / (mulVal * x28_distL);\n    }\n  }\n\n  return 0.f;\n}\n\nfloat CLight::GetIntensity() const {\n  if (x4c_24_intensityDirty) {\n    x4c_24_intensityDirty = false;\n    float coef = 1.f;\n    if (x1c_type == ELightType::Custom) {\n      coef = x30_angleC;\n    }\n    x48_cachedIntensity = coef * std::max({x18_color.r(), x18_color.g(), x18_color.b()});\n  }\n  return x48_cachedIntensity;\n}\n\nfloat CLight::GetRadius() const {\n  if (x4c_25_radiusDirty) {\n    x44_cachedRadius = CalculateLightRadius();\n    x4c_25_radiusDirty = false;\n  }\n  return x44_cachedRadius;\n}\n\nCLight::CLight(const zeus::CVector3f& pos, const zeus::CVector3f& dir, const zeus::CColor& color, float distC,\n               float distL, float distQ, float angleC, float angleL, float angleQ)\n: x0_pos(pos)\n, xc_dir(dir)\n, x18_color(color)\n, x24_distC(distC)\n, x28_distL(distL)\n, x2c_distQ(distQ)\n, x30_angleC(angleC)\n, x34_angleL(angleL)\n, x38_angleQ(angleQ) {}\n\nCLight::CLight(ELightType type, const zeus::CVector3f& pos, const zeus::CVector3f& dir, const zeus::CColor& color,\n               float cutoff)\n: x0_pos(pos), xc_dir(dir), x18_color(color), x1c_type(type), x20_spotCutoff(cutoff) {}\n\nzeus::CColor CLight::GetNormalIndependentLightingAtPoint(const zeus::CVector3f& point) const {\n  if (x1c_type == ELightType::LocalAmbient)\n    return x18_color;\n\n  float dist = std::max((x0_pos - point).magnitude(), FLT_EPSILON);\n  return x18_color * (1.f / (dist * (x2c_distQ * dist) + (x28_distL * dist + x24_distC)));\n}\n\nCLight CLight::BuildDirectional(const zeus::CVector3f& dir, const zeus::CColor& color) {\n  return CLight(ELightType::Directional, kDefaultPosition, dir, color, 180.f);\n}\n\nCLight CLight::BuildSpot(const zeus::CVector3f& pos, const zeus::CVector3f& dir, const zeus::CColor& color,\n                         float cutoff) {\n  return CLight(ELightType::Spot, pos, dir, color, cutoff);\n}\n\nCLight CLight::BuildPoint(const zeus::CVector3f& pos, const zeus::CColor& color) {\n  return CLight(ELightType::Point, pos, kDefaultDirection, color, 180.f);\n}\n\nCLight CLight::BuildCustom(const zeus::CVector3f& pos, const zeus::CVector3f& dir, const zeus::CColor& color,\n                           float distC, float distL, float distQ, float angleC, float angleL, float angleQ) {\n  return CLight(pos, dir, color, distC, distL, distQ, angleC, angleL, angleQ);\n}\n\nCLight CLight::BuildLocalAmbient(const zeus::CVector3f& pos, const zeus::CColor& color) {\n  return CLight(ELightType::LocalAmbient, pos, kDefaultDirection, color, 180.f);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CLight.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n\n#include <zeus/CColor.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\n\nenum class ELightType {\n  Spot = 0,\n  Point = 1,\n  Directional = 2,\n  LocalAmbient = 3,\n  Custom = 4,\n};\nenum class EFalloffType { Constant, Linear, Quadratic };\n\nclass CLight {\n  friend class CGuiLight;\n  friend class CBooModel;\n  friend class CBooRenderer;\n  friend class CGameLight;\n\n  zeus::CVector3f x0_pos;\n  zeus::CVector3f xc_dir = zeus::skDown;\n  zeus::CColor x18_color = zeus::skClear;\n  ELightType x1c_type = ELightType::Custom;\n  float x20_spotCutoff = 0.f;\n  float x24_distC = 0.f;\n  float x28_distL = 1.f;\n  float x2c_distQ = 0.f;\n  float x30_angleC = 0.f;\n  float x34_angleL = 1.f;\n  float x38_angleQ = 0.f;\n  u32 x3c_priority = 0;\n  u32 x40_lightId = 0; // Serves as unique key\n  mutable float x44_cachedRadius = 0.f;\n  mutable float x48_cachedIntensity = 0.f;\n  mutable bool x4c_24_intensityDirty : 1 = true;\n  mutable bool x4c_25_radiusDirty : 1 = true;\n\n  float CalculateLightRadius() const;\n\npublic:\n  CLight() = default;\n\n  CLight(const zeus::CVector3f& pos, const zeus::CVector3f& dir, const zeus::CColor& color, float distC, float distL,\n         float distQ, float angleC, float angleL, float angleQ);\n\n  CLight(ELightType type, const zeus::CVector3f& pos, const zeus::CVector3f& dir, const zeus::CColor& color,\n         float cutoff);\n\n  void SetPosition(const zeus::CVector3f& pos) { x0_pos = pos; }\n\n  const zeus::CVector3f& GetPosition() const { return x0_pos; }\n\n  void SetDirection(const zeus::CVector3f& dir) { xc_dir = dir; }\n\n  const zeus::CVector3f& GetDirection() const { return xc_dir; }\n\n  void SetColor(const zeus::CColor& col) {\n    x18_color = col;\n    x4c_24_intensityDirty = true;\n    x4c_25_radiusDirty = true;\n  }\n\n  void SetAttenuation(float constant, float linear, float quadratic) {\n    x24_distC = constant;\n    x28_distL = linear;\n    x2c_distQ = quadratic;\n    x4c_24_intensityDirty = true;\n    x4c_25_radiusDirty = true;\n  }\n  float GetSpotCutoff() const { return x20_spotCutoff; }\n  float GetAttenuationConstant() const { return x24_distC; }\n  float GetAttenuationLinear() const { return x28_distL; }\n  float GetAttenuationQuadratic() const { return x2c_distQ; }\n\n  void SetAngleAttenuation(float constant, float linear, float quadratic) {\n    x30_angleC = constant;\n    x34_angleL = linear;\n    x38_angleQ = quadratic;\n    x4c_24_intensityDirty = true;\n    x4c_25_radiusDirty = true;\n  }\n  float GetAngleAttenuationConstant() const { return x30_angleC; }\n  float GetAngleAttenuationLinear() const { return x34_angleL; }\n  float GetAngleAttenuationQuadratic() const { return x38_angleQ; }\n\n  ELightType GetType() const { return x1c_type; }\n  u32 GetId() const { return x40_lightId; }\n  u32 GetPriority() const { return x3c_priority; }\n  float GetIntensity() const;\n  float GetRadius() const;\n  const zeus::CColor& GetColor() const { return x18_color; }\n  zeus::CColor GetNormalIndependentLightingAtPoint(const zeus::CVector3f& point) const;\n\n  static CLight BuildDirectional(const zeus::CVector3f& dir, const zeus::CColor& color);\n  static CLight BuildSpot(const zeus::CVector3f& pos, const zeus::CVector3f& dir, const zeus::CColor& color,\n                          float cutoff);\n  static CLight BuildPoint(const zeus::CVector3f& pos, const zeus::CColor& color);\n  static CLight BuildCustom(const zeus::CVector3f& pos, const zeus::CVector3f& dir, const zeus::CColor& color,\n                            float distC, float distL, float distQ, float angleC, float angleL, float angleQ);\n  static CLight BuildLocalAmbient(const zeus::CVector3f& pos, const zeus::CColor& color);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CMakeLists.txt",
    "content": "set(GRAPHICS_SOURCES\n        IRenderer.hpp\n        IWeaponRenderer.hpp IWeaponRenderer.cpp\n        CCubeMaterial.cpp CCubeMaterial.hpp\n        CCubeModel.cpp CCubeModel.hpp\n        CCubeRenderer.cpp CCubeRenderer.hpp\n        CCubeSurface.cpp CCubeSurface.hpp\n        CDrawable.hpp\n        CDrawablePlaneObject.hpp\n        CMetroidModelInstance.cpp CMetroidModelInstance.hpp\n        CLight.hpp CLight.cpp\n        CTevCombiners.cpp CTevCombiners.hpp\n        CTexture.hpp CTexture.cpp\n        CModel.cpp CModel.hpp\n        CSkinnedModel.hpp CSkinnedModel.cpp\n        CVertexMorphEffect.hpp CVertexMorphEffect.cpp\n        CMoviePlayer.hpp CMoviePlayer.cpp\n        CGraphicsPalette.hpp CGraphicsPalette.cpp\n        CGX.hpp CGX.cpp\n        CPVSVisSet.hpp CPVSVisSet.cpp\n        CPVSVisOctree.hpp CPVSVisOctree.cpp\n        CPVSAreaSet.hpp CPVSAreaSet.cpp\n        CGraphics.hpp CGraphics.cpp\n        CSimpleShadow.hpp CSimpleShadow.cpp\n        CRainSplashGenerator.hpp CRainSplashGenerator.cpp\n        CFont.hpp CFont.cpp\n)\n\nruntime_add_list(Graphics GRAPHICS_SOURCES)\n"
  },
  {
    "path": "Runtime/Graphics/CMetroidModelInstance.cpp",
    "content": "#include \"CMetroidModelInstance.hpp\"\n\n#include \"Graphics/CCubeSurface.hpp\"\n#include \"Streams/IOStreams.hpp\"\n\nnamespace metaforce {\nCMetroidModelInstance::CMetroidModelInstance(std::span<const u8> modelHeader, const u8* materialData,\n                                             std::span<const u8> positions, std::span<const u8> normals,\n                                             std::span<const u8> colors, std::span<const u8> texCoords,\n                                             std::span<const u8> packedTexCoords, std::vector<CCubeSurface>&& surfaces)\n: x4c_materialData(materialData), x50_surfaces(std::move(surfaces)) {\n  {\n    CMemoryInStream stream{modelHeader.data(), static_cast<u32>(modelHeader.size_bytes())};\n    x0_visorFlags = stream.ReadUint32();\n    x4_worldXf = stream.Get<zeus::CTransform>();\n    x34_worldAABB = stream.Get<zeus::CAABox>();\n  }\n  {\n    u32 numVertices = positions.size_bytes() / 12;\n    x60_positions.reserve(numVertices);\n    CMemoryInStream stream{positions.data(), static_cast<u32>(positions.size_bytes())};\n    for (u32 i = 0; i < numVertices; ++i) {\n      x60_positions.push_back(stream.Get<aurora::Vec3<float>>());\n    }\n  }\n  {\n    // Always short normals in MREA\n    u32 numNormals = normals.size_bytes() / 6;\n    x64_normals.reserve(numNormals);\n    CMemoryInStream stream{normals.data(), static_cast<u32>(normals.size_bytes())};\n    for (u32 i = 0; i < numNormals; ++i) {\n      x64_normals.push_back(stream.Get<aurora::Vec3<s16>>());\n    }\n  }\n  {\n    u32 numColors = colors.size_bytes() / 4;\n    x68_colors.reserve(numColors);\n    CMemoryInStream stream{colors.data(), static_cast<u32>(colors.size_bytes())};\n    for (u32 i = 0; i < numColors; ++i) {\n      x68_colors.push_back(stream.ReadUint32());\n    }\n  }\n  {\n    u32 numTexCoords = texCoords.size_bytes() / 8;\n    x6c_texCoords.reserve(numTexCoords);\n    CMemoryInStream stream{texCoords.data(), static_cast<u32>(texCoords.size_bytes())};\n    for (u32 i = 0; i < numTexCoords; ++i) {\n      x6c_texCoords.push_back(stream.Get<aurora::Vec2<float>>());\n    }\n  }\n  {\n    u32 numPackedTexCoords = packedTexCoords.size_bytes() / 4;\n    x70_packedTexCoords.reserve(numPackedTexCoords);\n    CMemoryInStream stream{packedTexCoords.data(), static_cast<u32>(packedTexCoords.size_bytes())};\n    for (u32 i = 0; i < numPackedTexCoords; ++i) {\n      x70_packedTexCoords.push_back(stream.Get<aurora::Vec2<u16>>());\n    }\n  }\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CMetroidModelInstance.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <span>\n#include <unordered_map>\n#include <vector>\n\n#include \"Runtime/Graphics/CCubeModel.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CTransform.hpp>\n#include <aurora/math.hpp>\n\nnamespace metaforce {\nclass CCubeSurface;\n\nclass CMetroidModelInstance {\n  u32 x0_visorFlags = 0;\n  zeus::CTransform x4_worldXf;\n  zeus::CAABox x34_worldAABB;\n  const u8* x4c_materialData = nullptr;\n  std::vector<CCubeSurface> x50_surfaces;             // was rstl::vector<void*>*\n  std::vector<aurora::Vec3<float>> x60_positions;     // was void*\n  std::vector<aurora::Vec3<s16>> x64_normals;         // was void*\n  std::vector<u32> x68_colors;                        // was void*\n  std::vector<aurora::Vec2<float>> x6c_texCoords;     // was void*\n  std::vector<aurora::Vec2<u16>> x70_packedTexCoords; // was void*\n\npublic:\n  CMetroidModelInstance() = default;\n  CMetroidModelInstance(std::span<const u8> modelHeader, const u8* materialData, std::span<const u8> positions,\n                        std::span<const u8> normals, std::span<const u8> colors, std::span<const u8> texCoords,\n                        std::span<const u8> packedTexCoords, std::vector<CCubeSurface>&& surfaces);\n\n  [[nodiscard]] u32 GetFlags() const { return x0_visorFlags; }\n  [[nodiscard]] const zeus::CAABox& GetBoundingBox() const { return x34_worldAABB; }\n  [[nodiscard]] std::vector<CCubeSurface>* GetSurfaces() { return &x50_surfaces; }\n  [[nodiscard]] const std::vector<CCubeSurface>* GetSurfaces() const { return &x50_surfaces; }\n  [[nodiscard]] const u8* GetMaterialPointer() const { return x4c_materialData; }\n  [[nodiscard]] std::span<const u8> GetVertexPointer() const { return byte_span(x60_positions); }\n  [[nodiscard]] std::span<const u8> GetNormalPointer() const { return byte_span(x64_normals); }\n  [[nodiscard]] std::span<const u8> GetColorPointer() const { return byte_span(x68_colors); }\n  [[nodiscard]] std::span<const u8> GetTCPointer() const { return byte_span(x6c_texCoords); }\n  [[nodiscard]] std::span<const u8> GetPackedTCPointer() const { return byte_span(x70_packedTexCoords); }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CModel.cpp",
    "content": "#include \"CModel.hpp\"\n\n#include \"CBasics.hpp\"\n#include \"Graphics/CCubeMaterial.hpp\"\n#include \"Graphics/CCubeModel.hpp\"\n#include \"Graphics/CCubeSurface.hpp\"\n#include \"Streams/IOStreams.hpp\"\n\nnamespace metaforce {\nvoid CModel::SShader::UnlockTextures() {\n  for (auto& token : x0_textures) {\n    token.Unlock();\n  }\n}\n\nu32 CModel::sTotalMemory = 0;\nu32 CModel::sFrameCounter = 0;\nbool CModel::sIsTextureTimeoutEnabled = true;\nCModel* CModel::sThisFrameList = nullptr;\nCModel* CModel::sOneFrameList = nullptr;\nCModel* CModel::sTwoFrameList = nullptr;\n\nstatic u8* MemoryFromPartData(u8*& dataCur, const u32*& secSizeCur) {\n  u8* ret = nullptr;\n  if (*secSizeCur != 0) {\n    ret = dataCur;\n  }\n  dataCur += CBasics::SwapBytes(*secSizeCur);\n  ++secSizeCur;\n  return ret;\n}\n\n// For ease of reading byte swapped data\nstatic CMemoryInStream StreamFromPartData(u8*& dataCur, const u32*& secSizeCur) {\n  const auto secSize = CBasics::SwapBytes(*secSizeCur);\n  return {MemoryFromPartData(dataCur, secSizeCur), secSize};\n}\n\nCModel::CModel(std::unique_ptr<u8[]> in, u32 dataLen, IObjectStore* store)\n: x0_data(std::move(in))\n, x4_dataLen(dataLen)\n, x34_next(sThisFrameList)\n, x38_lastFrame(CGraphics::GetFrameCounter() - 2) {\n  u8* data = x0_data.get();\n  u32 flags = CBasics::SwapBytes(*reinterpret_cast<u32*>(data + 8));\n  u32 sectionSizeStart = 0x2c;\n  if (CBasics::SwapBytes(*reinterpret_cast<u32*>(data + 4)) == 1) {\n    sectionSizeStart = 0x28;\n  }\n  const u32* secSizeCur = reinterpret_cast<u32*>(data + sectionSizeStart);\n  s32 numMatSets = 1;\n  if (CBasics::SwapBytes(*reinterpret_cast<u32*>(data + 4)) > 1) {\n    numMatSets = CBasics::SwapBytes(*reinterpret_cast<s32*>(data + 0x28));\n  }\n  u8* dataCur = data + ROUND_UP_32(sectionSizeStart + CBasics::SwapBytes(*reinterpret_cast<s32*>(data + 0x24)) * 4);\n  x18_matSets.reserve(numMatSets);\n  for (s32 i = 0; i < numMatSets; ++i) {\n    x18_matSets.emplace_back(MemoryFromPartData(dataCur, secSizeCur));\n    auto& shader = x18_matSets.back();\n    CCubeModel::MakeTexturesFromMats(shader.x10_data, shader.x0_textures, store, true);\n    x4_dataLen += shader.x0_textures.size() * sizeof(TCachedToken<CTexture>);\n  }\n\n  u32 numVertices = CBasics::SwapBytes(*secSizeCur) / 12;\n  m_positions.reserve(numVertices);\n  auto positions = StreamFromPartData(dataCur, secSizeCur);\n  for (u32 i = 0; i < numVertices; ++i) {\n    m_positions.emplace_back(positions.Get<aurora::Vec3<float>>());\n  }\n\n  u32 numNormals = CBasics::SwapBytes(*secSizeCur);\n  numNormals /= (flags & 2) == 0 ? 12 : 6;\n  if ((flags & 2) == 0) {\n    m_normals.reserve(numNormals);\n  } else {\n    m_shortNormals.reserve(numNormals);\n  }\n  auto normals = StreamFromPartData(dataCur, secSizeCur);\n  for (u32 i = 0; i < numNormals; ++i) {\n    if ((flags & 2) == 0) {\n      m_normals.emplace_back(normals.Get<aurora::Vec3<float>>());\n    } else {\n      m_shortNormals.emplace_back(normals.Get<aurora::Vec3<s16>>());\n    }\n  }\n\n  u32 numColors = CBasics::SwapBytes(*secSizeCur) / 4;\n  m_colors.reserve(numColors);\n  auto vtxColors = StreamFromPartData(dataCur, secSizeCur);\n  for (u32 i = 0; i < numColors; ++i) {\n    m_colors.emplace_back(vtxColors.ReadUint32());\n  }\n\n  u32 numFloatUVs = CBasics::SwapBytes(*secSizeCur) / 8;\n  m_floatUVs.reserve(numFloatUVs);\n  auto floatUVs = StreamFromPartData(dataCur, secSizeCur);\n  for (u32 i = 0; i < numFloatUVs; ++i) {\n    m_floatUVs.emplace_back(floatUVs.Get<aurora::Vec2<float>>());\n  }\n\n  if ((flags & 4) != 0) {\n    u32 numShortUVs = CBasics::SwapBytes(*secSizeCur) / 4;\n    m_shortUVs.reserve(numShortUVs);\n    auto shortUVs = StreamFromPartData(dataCur, secSizeCur);\n    for (u32 i = 0; i < numShortUVs; ++i) {\n      m_shortUVs.emplace_back(shortUVs.Get<aurora::Vec2<u16>>());\n    }\n  }\n\n  auto surfaceInfo = StreamFromPartData(dataCur, secSizeCur);\n  auto surfaceCount = surfaceInfo.ReadUint32();\n  x8_surfaces.reserve(surfaceCount);\n  for (u32 i = 0; i < surfaceCount; ++i) {\n    if (x8_surfaces.capacity() <= x8_surfaces.size()) {\n      x8_surfaces.reserve(x8_surfaces.capacity() * 2);\n    }\n    const auto secSize = CBasics::SwapBytes(*secSizeCur);\n    x8_surfaces.emplace_back(MemoryFromPartData(dataCur, secSizeCur), secSize);\n  }\n\n  const float* bounds = reinterpret_cast<float*>(data + 12);\n  const zeus::CAABox aabb{\n      {CBasics::SwapBytes(bounds[0]), CBasics::SwapBytes(bounds[1]), CBasics::SwapBytes(bounds[2])},\n      {CBasics::SwapBytes(bounds[3]), CBasics::SwapBytes(bounds[4]), CBasics::SwapBytes(bounds[5])},\n  };\n\n  /* This constructor has been changed from the original to take into account platform differences */\n  x28_modelInst = std::make_unique<CCubeModel>(&x8_surfaces, &x18_matSets[0].x0_textures, x18_matSets[0].x10_data,\n                                               byte_span(m_positions), byte_span(m_colors),\n                                               (flags & 2) == 0 ? byte_span(m_normals) : byte_span(m_shortNormals),\n                                               byte_span(m_floatUVs), byte_span(m_shortUVs), aabb, flags, true, -1);\n\n  sThisFrameList = this;\n  if (x34_next != nullptr) {\n    x34_next->x30_prev = this;\n  }\n  x4_dataLen += x8_surfaces.size() * 4;\n  sTotalMemory += x4_dataLen;\n  // DCFlushRange(x0_data, dataLen);\n}\n\nCModel::~CModel() {\n  RemoveFromList();\n  sTotalMemory -= x4_dataLen;\n}\n\nvoid CModel::UpdateLastFrame() { x38_lastFrame = CGraphics::GetFrameCounter(); }\n\nvoid CModel::MoveToThisFrameList() {\n  UpdateLastFrame();\n  if (sThisFrameList == this) {\n    return;\n  }\n\n  RemoveFromList();\n  if (sThisFrameList != nullptr) {\n    x34_next = sThisFrameList;\n    x34_next->x30_prev = this;\n  }\n\n  sThisFrameList = this;\n}\n\nvoid CModel::RemoveFromList() {\n  if (x30_prev == nullptr) {\n    if (sThisFrameList == this) {\n      sThisFrameList = x34_next;\n    } else if (sOneFrameList == this) {\n      sOneFrameList = x34_next;\n    } else if (sTwoFrameList == this) {\n      sTwoFrameList = x34_next;\n    }\n  } else {\n    x30_prev->x34_next = x34_next;\n  }\n  if (x34_next != nullptr) {\n    x34_next->x30_prev = x30_prev;\n  }\n  x30_prev = nullptr;\n  x34_next = nullptr;\n}\n\nvoid CModel::FrameDone() {\n  ++sFrameCounter;\n  if (sIsTextureTimeoutEnabled) {\n    auto* iter = sTwoFrameList;\n    while (iter != nullptr) {\n      auto* next = iter->x34_next;\n      iter->VerifyCurrentShader(0);\n      for (auto& shader : iter->x18_matSets) {\n        shader.UnlockTextures();\n      }\n\n      iter->x28_modelInst->UnlockTextures();\n      iter->x34_next = nullptr;\n      iter->x30_prev = nullptr;\n      iter = next;\n    }\n\n    sTwoFrameList = sOneFrameList;\n    sOneFrameList = sThisFrameList;\n    sThisFrameList = nullptr;\n  }\n}\n\nvoid CModel::EnableTextureTimeout() { sIsTextureTimeoutEnabled = true; }\n\nvoid CModel::DisableTextureTimeout() { sIsTextureTimeoutEnabled = false; }\n\nTConstVectorRef CModel::GetPositions() const { return x28_modelInst->GetPositions(); }\n\nTConstVectorRef CModel::GetNormals() const { return x28_modelInst->GetNormals(); }\n\nvoid CModel::VerifyCurrentShader(u32 matIdx) {\n  if (matIdx > x18_matSets.size()) {\n    matIdx = 0;\n  }\n  if (matIdx == x2c_currentMatIdx) {\n    if (x2e_lastFrame != 0 && x2e_lastFrame < sFrameCounter) {\n      for (size_t idx = 0; auto& mat : x18_matSets) {\n        if (idx != matIdx) {\n          mat.UnlockTextures();\n        }\n        idx++;\n      }\n    }\n  } else {\n    x2c_currentMatIdx = matIdx;\n    auto& mat = x18_matSets[matIdx];\n    x28_modelInst->RemapMaterialData(mat.x10_data, mat.x0_textures);\n    if (x18_matSets.size() > 1) {\n      x2e_lastFrame = sFrameCounter + 2;\n    }\n  }\n}\n\nbool CModel::IsLoaded(u32 matIdx) {\n  VerifyCurrentShader(matIdx);\n  const auto& textures = *x28_modelInst->x1c_textures;\n  if (textures.empty()) {\n    return true;\n  }\n  for (const auto& token : textures) {\n    if (token.IsNull() && !token.IsLoaded()) {\n      return false;\n    }\n  }\n  return true;\n}\n\nvoid CModel::Touch(u32 matIdx) {\n  MoveToThisFrameList();\n  VerifyCurrentShader(matIdx);\n  if (x28_modelInst->TryLockTextures()) {\n    for (auto& texture : *x28_modelInst->x1c_textures) {\n      if (!texture.IsNull()) {\n        // texture->LoadToMRAM();\n      }\n    }\n  }\n}\n\nvoid CModel::Draw(CModelFlags flags) {\n  if (flags.x2_flags & CModelFlagBits::DrawNormal) {\n    x28_modelInst->DrawNormal({}, {}, ESurfaceSelection::All);\n  }\n  CCubeMaterial::ResetCachedMaterials();\n  MoveToThisFrameList();\n  VerifyCurrentShader(flags.x1_matSetIdx);\n  x28_modelInst->Draw(flags);\n}\n\nvoid CModel::Draw(TConstVectorRef positions, TConstVectorRef normals, const CModelFlags& flags) {\n  if (flags.x2_flags & CModelFlagBits::DrawNormal) {\n    x28_modelInst->DrawNormal(positions, normals, ESurfaceSelection::All);\n  }\n  CCubeMaterial::ResetCachedMaterials();\n  MoveToThisFrameList();\n  VerifyCurrentShader(flags.x1_matSetIdx);\n  x28_modelInst->Draw(positions, normals, flags);\n}\n\nvoid CModel::DrawSortedParts(CModelFlags flags) {\n  if (flags.x2_flags & CModelFlagBits::DrawNormal) {\n    x28_modelInst->DrawNormal({}, {}, ESurfaceSelection::Sorted);\n  }\n  CCubeMaterial::ResetCachedMaterials();\n  MoveToThisFrameList();\n  VerifyCurrentShader(flags.x1_matSetIdx);\n  x28_modelInst->DrawAlpha(flags);\n}\n\nvoid CModel::DrawUnsortedParts(CModelFlags flags) {\n  if (flags.x2_flags & CModelFlagBits::DrawNormal) {\n    x28_modelInst->DrawNormal({}, {}, ESurfaceSelection::Unsorted);\n  }\n  CCubeMaterial::ResetCachedMaterials();\n  MoveToThisFrameList();\n  VerifyCurrentShader(flags.x1_matSetIdx);\n  x28_modelInst->DrawNormal(flags);\n}\n\nCFactoryFnReturn FModelFactory(const metaforce::SObjectTag& tag, std::unique_ptr<u8[]>&& in, u32 len,\n                               const metaforce::CVParamTransfer& vparms, CObjectReference* selfRef) {\n  IObjectStore* store = vparms.GetOwnedObj<IObjectStore*>();\n  CFactoryFnReturn ret = TToken<CModel>::GetIObjObjectFor(std::make_unique<CModel>(std::move(in), len, store));\n  return ret;\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CModel.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <vector>\n\n#include \"CToken.hpp\"\n#include \"GCNTypes.hpp\"\n#include \"Graphics/CCubeModel.hpp\"\n#include \"Graphics/CCubeSurface.hpp\"\n#include \"Graphics/CTexture.hpp\"\n#include \"IObjectStore.hpp\"\n#include \"Flags.hpp\"\n\nnamespace metaforce {\nclass CCubeMaterial;\n\nenum class CModelFlagBits : u16 {\n  DepthTest = 0x1,\n  DepthUpdate = 0x2,\n  NoTextureLock = 0x4,\n  DepthGreater = 0x8,\n  DepthNonInclusive = 0x10,\n  DrawNormal = 0x20,\n  ThermalUnsortedOnly = 0x40,\n};\nusing CModelFlagsFlags = Flags<CModelFlagBits>;\n\nstruct CModelFlags {\n  /**\n   * 2: add color\n   * >6: additive\n   * >4: blend\n   * else opaque\n   */\n  u8 x0_blendMode = 0;\n  u8 x1_matSetIdx = 0;\n  CModelFlagsFlags x2_flags{};\n  /**\n   * Set into kcolor slot specified by material\n   */\n  zeus::CColor x4_color;\n\n  constexpr CModelFlags() = default;\n  constexpr CModelFlags(u8 blendMode, u8 shadIdx, u16 flags, const zeus::CColor& col)\n  : x0_blendMode(blendMode), x1_matSetIdx(shadIdx), x2_flags(flags), x4_color(col) {}\n  constexpr CModelFlags(u8 blendMode, u8 shadIdx, CModelFlagsFlags flags, const zeus::CColor& col)\n  : x0_blendMode(blendMode), x1_matSetIdx(shadIdx), x2_flags(flags), x4_color(col) {}\n\n  bool operator==(const CModelFlags& other) const {\n    return x0_blendMode == other.x0_blendMode && x1_matSetIdx == other.x1_matSetIdx && x2_flags == other.x2_flags &&\n           x4_color == other.x4_color;\n  }\n\n  bool operator!=(const CModelFlags& other) const { return !operator==(other); }\n};\n\nclass CModel {\npublic:\n  struct SShader {\n    std::vector<TCachedToken<CTexture>> x0_textures;\n    u8* x10_data;\n\n    explicit SShader(u8* data) : x10_data(data) {}\n\n    void UnlockTextures();\n  };\n\nprivate:\n  static u32 sTotalMemory;\n  static u32 sFrameCounter;\n  static bool sIsTextureTimeoutEnabled;\n  static CModel* sThisFrameList;\n  static CModel* sOneFrameList;\n  static CModel* sTwoFrameList;\n\n  std::unique_ptr<u8[]> x0_data;\n  u32 x4_dataLen;\n  std::vector<CCubeSurface> x8_surfaces; // was rstl::vector<void*>\n  std::vector<SShader> x18_matSets;\n  std::unique_ptr<CCubeModel> x28_modelInst = nullptr;\n  u16 x2c_currentMatIdx = 0;\n  u16 x2e_lastFrame = 0; // Last frame that the model switched materials\n  CModel* x30_prev = nullptr;\n  CModel* x34_next;\n  u32 x38_lastFrame;\n\n  /* Resident copies of maintained data */\n  std::vector<aurora::Vec3<float>> m_positions;\n  std::vector<aurora::Vec3<float>> m_normals;\n  std::vector<aurora::Vec3<s16>> m_shortNormals;\n  std::vector<u32> m_colors;\n  std::vector<aurora::Vec2<float>> m_floatUVs;\n  std::vector<aurora::Vec2<u16>> m_shortUVs;\n\npublic:\n  CModel(std::unique_ptr<u8[]> in, u32 dataLen, IObjectStore* store);\n  ~CModel();\n\n  void UpdateLastFrame();\n  void MoveToThisFrameList();\n  void RemoveFromList();\n  void VerifyCurrentShader(u32 matIdx);\n  void Touch(u32 matIdx);\n  void Draw(CModelFlags flags);\n  void Draw(TConstVectorRef positions, TConstVectorRef normals, const CModelFlags& flags);\n  void DrawSortedParts(CModelFlags flags);\n  void DrawUnsortedParts(CModelFlags flags);\n  bool IsLoaded(u32 matIdx);\n\n  [[nodiscard]] TConstVectorRef GetPositions() const;\n  [[nodiscard]] TConstVectorRef GetNormals() const;\n  [[nodiscard]] u32 GetNumMaterialSets() const { return x18_matSets.size(); }\n  [[nodiscard]] bool IsOpaque() const { return x28_modelInst->x3c_firstSortedSurf == nullptr; }\n  [[nodiscard]] const zeus::CAABox& GetAABB() const { return x28_modelInst->x20_worldAABB; }\n  [[nodiscard]] auto& GetInstance() { return *x28_modelInst; }\n  [[nodiscard]] const auto& GetInstance() const { return *x28_modelInst; }\n\n  static void FrameDone();\n  static void EnableTextureTimeout();\n  static void DisableTextureTimeout();\n};\n\nCFactoryFnReturn FModelFactory(const metaforce::SObjectTag& tag, std::unique_ptr<u8[]>&& in, u32 len,\n                               const metaforce::CVParamTransfer& vparms, CObjectReference* selfRef);\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CMoviePlayer.cpp",
    "content": "#include \"Graphics/CMoviePlayer.hpp\"\n\n#include \"Audio/g721.h\"\n#include \"CDvdRequest.hpp\"\n#include \"Graphics/CGraphics.hpp\"\n#include \"Graphics/CCubeRenderer.hpp\"\n#include \"Graphics/CGX.hpp\"\n#include \"GameGlobalObjects.hpp\"\n\n// #include <amuse/DSPCodec.hpp>\n#include <turbojpeg.h>\n\nnamespace metaforce {\n\nstatic void MyTHPYuv2RgbTextureSetup(void* dataY, void* dataU, void* dataV, u16 width, u16 height) {\n  TGXTexObj texV;\n  TGXTexObj texU;\n  TGXTexObj texY;\n  GXInitTexObj(&texY, dataY, width, height, static_cast<GXTexFmt>(GX_TF_R8_PC), GX_CLAMP, GX_CLAMP, false);\n  GXInitTexObjLOD(&texY, GX_NEAR, GX_NEAR, 0.0, 0.0, 0.0, false, false, GX_ANISO_1);\n  GXLoadTexObj(&texY, GX_TEXMAP0);\n  GXInitTexObj(&texU, dataU, width / 2, height / 2, static_cast<GXTexFmt>(GX_TF_R8_PC), GX_CLAMP, GX_CLAMP, false);\n  GXInitTexObjLOD(&texU, GX_NEAR, GX_NEAR, 0.0, 0.0, 0.0, false, false, GX_ANISO_1);\n  GXLoadTexObj(&texU, GX_TEXMAP1);\n  GXInitTexObj(&texV, dataV, width / 2, height / 2, static_cast<GXTexFmt>(GX_TF_R8_PC), GX_CLAMP, GX_CLAMP, false);\n  GXInitTexObjLOD(&texV, GX_NEAR, GX_NEAR, 0.0, 0.0, 0.0, false, false, GX_ANISO_1);\n  GXLoadTexObj(&texV, GX_TEXMAP2);\n  CTexture::InvalidateTexMap(GX_TEXMAP0);\n  CTexture::InvalidateTexMap(GX_TEXMAP1);\n  CTexture::InvalidateTexMap(GX_TEXMAP2);\n}\n\nconst std::array<u8, 32> InterlaceTex{\n    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 0, 0, 0, 0, 0, 0, 0,\n    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 0, 0, 0, 0, 0, 0, 0,\n};\nstatic void MyTHPGXYuv2RgbSetup(bool interlaced2ndFrame, bool fieldFlip) {\n  CGX::SetZMode(true, GX_ALWAYS, false);\n  CGX::SetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_ZERO, GX_LO_CLEAR);\n  CGX::SetNumChans(0);\n  CGX::SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY, false, GX_PTIDENTITY);\n  CGX::SetTexCoordGen(GX_TEXCOORD1, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY, false, GX_PTIDENTITY);\n  if (false && !fieldFlip) {\n    CGX::SetNumTexGens(3);\n    CGX::SetTexCoordGen(GX_TEXCOORD2, GX_TG_MTX2x4, GX_TG_POS, GX_TEXMTX0, false, GX_PTIDENTITY);\n    float n = interlaced2ndFrame ? 0.25f : 0.f;\n    float mtx[8] = {0.125f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.25f, n};\n    GXLoadTexMtxImm(mtx, GX_TEXMTX0, GX_MTX2x4);\n    TGXTexObj texObj;\n    GXInitTexObj(&texObj, InterlaceTex.data(), 8, 4, GX_TF_I8, GX_REPEAT, GX_REPEAT, false);\n    GXInitTexObjLOD(&texObj, GX_NEAR, GX_NEAR, 0.f, 0.f, 0.f, false, false, GX_ANISO_1);\n    GXLoadTexObj(&texObj, GX_TEXMAP3);\n    CTexture::InvalidateTexMap(GX_TEXMAP3);\n    CGX::SetTevOrder(GX_TEVSTAGE4, GX_TEXCOORD2, GX_TEXMAP3, GX_COLOR_NULL);\n    CGX::SetStandardTevColorAlphaOp(GX_TEVSTAGE4);\n    CGX::SetTevColorIn(GX_TEVSTAGE4, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_CPREV);\n    CGX::SetTevAlphaIn(GX_TEVSTAGE4, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_TEXA);\n    CGX::SetAlphaCompare(GX_LESS, 128, GX_AOP_AND, GX_ALWAYS, 0);\n    CGX::SetNumTevStages(5);\n  } else {\n    CGX::SetNumTexGens(2);\n    CGX::SetNumTevStages(4);\n  }\n  constexpr std::array vtxDescList{\n      GXVtxDescList{GX_VA_POS, GX_DIRECT},\n      GXVtxDescList{GX_VA_TEX0, GX_DIRECT},\n      GXVtxDescList{GX_VA_NULL, GX_NONE},\n  };\n  CGX::SetVtxDescv(vtxDescList.data());\n  GXSetColorUpdate(true);\n  GXSetAlphaUpdate(false);\n  GXInvalidateTexAll();\n  GXSetVtxAttrFmt(GX_VTXFMT7, GX_VA_POS, GX_POS_XYZ, GX_F32, 0);\n  GXSetVtxAttrFmt(GX_VTXFMT7, GX_VA_TEX0, GX_TEX_ST, GX_U16, 0);\n  CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD1, GX_TEXMAP1, GX_COLOR_NULL);\n  CGX::SetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_TEXC, GX_CC_KONST, GX_CC_C0);\n  CGX::SetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, false, GX_TEVPREV);\n  CGX::SetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_TEXA, GX_CA_KONST, GX_CA_A0);\n  CGX::SetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_SUB, GX_TB_ZERO, GX_CS_SCALE_1, false, GX_TEVPREV);\n  CGX::SetTevKColorSel(GX_TEVSTAGE0, GX_TEV_KCSEL_K0);\n  CGX::SetTevKAlphaSel(GX_TEVSTAGE0, GX_TEV_KASEL_K0_A);\n  CGX::SetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD1, GX_TEXMAP2, GX_COLOR_NULL);\n  CGX::SetTevColorIn(GX_TEVSTAGE1, GX_CC_ZERO, GX_CC_TEXC, GX_CC_KONST, GX_CC_CPREV);\n  CGX::SetTevColorOp(GX_TEVSTAGE1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, false, GX_TEVPREV);\n  CGX::SetTevAlphaIn(GX_TEVSTAGE1, GX_CA_ZERO, GX_CA_TEXA, GX_CA_KONST, GX_CA_APREV);\n  CGX::SetTevAlphaOp(GX_TEVSTAGE1, GX_TEV_SUB, GX_TB_ZERO, GX_CS_SCALE_1, false, GX_TEVPREV);\n  CGX::SetTevKColorSel(GX_TEVSTAGE1, GX_TEV_KCSEL_K1);\n  CGX::SetTevKAlphaSel(GX_TEVSTAGE1, GX_TEV_KASEL_K1_A);\n  CGX::SetTevOrder(GX_TEVSTAGE2, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL);\n  CGX::SetTevColorIn(GX_TEVSTAGE2, GX_CC_ZERO, GX_CC_TEXC, GX_CC_ONE, GX_CC_CPREV);\n  CGX::SetTevColorOp(GX_TEVSTAGE2, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, true, GX_TEVPREV);\n  CGX::SetTevAlphaIn(GX_TEVSTAGE2, GX_CA_TEXA, GX_CA_ZERO, GX_CA_ZERO, GX_CA_APREV);\n  CGX::SetTevAlphaOp(GX_TEVSTAGE2, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, true, GX_TEVPREV);\n  CGX::SetTevOrder(GX_TEVSTAGE3, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);\n  CGX::SetTevColorIn(GX_TEVSTAGE3, GX_CC_APREV, GX_CC_CPREV, GX_CC_KONST, GX_CC_ZERO);\n  CGX::SetTevColorOp(GX_TEVSTAGE3, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, true, GX_TEVPREV);\n  CGX::SetTevAlphaIn(GX_TEVSTAGE3, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO);\n  CGX::SetTevAlphaOp(GX_TEVSTAGE3, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, true, GX_TEVPREV);\n  CGX::SetTevKColorSel(GX_TEVSTAGE3, GX_TEV_KCSEL_K2);\n  GXSetTevColorS10(GX_TEVREG0, GXColorS10{-90, 0, -114, 135});\n  CGX::SetTevKColor(GX_KCOLOR0, GXColor{0x00, 0x00, 0xe2, 0x58});\n  CGX::SetTevKColor(GX_KCOLOR1, GXColor{0xb3, 0x00, 0x00, 0xb6});\n  CGX::SetTevKColor(GX_KCOLOR2, GXColor{0xff, 0x00, 0xff, 0x80});\n}\nstatic void MyTHPGXRestore() {\n  CGX::SetZMode(true, GX_ALWAYS, false);\n  CGX::SetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_ZERO, GX_LO_SET);\n  CGX::SetNumTexGens(1);\n  CGX::SetNumChans(0);\n  CGX::SetNumTevStages(1);\n  CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL);\n  CGX::SetAlphaCompare(GX_ALWAYS, 0, GX_AOP_AND, GX_ALWAYS, 0);\n}\n\n/* used in the original to look up fixed-point dividends on a\n * MIDI-style volume scale (0-127) -> (n/0x8000) */\nstatic const std::array<u16, 128> StaticVolumeLookup = {\n    0x0000, 0x0002, 0x0008, 0x0012, 0x0020, 0x0032, 0x0049, 0x0063, 0x0082, 0x00A4, 0x00CB, 0x00F5, 0x0124,\n    0x0157, 0x018E, 0x01C9, 0x0208, 0x024B, 0x0292, 0x02DD, 0x032C, 0x037F, 0x03D7, 0x0432, 0x0492, 0x04F5,\n    0x055D, 0x05C9, 0x0638, 0x06AC, 0x0724, 0x07A0, 0x0820, 0x08A4, 0x092C, 0x09B8, 0x0A48, 0x0ADD, 0x0B75,\n    0x0C12, 0x0CB2, 0x0D57, 0x0DFF, 0x0EAC, 0x0F5D, 0x1012, 0x10CA, 0x1187, 0x1248, 0x130D, 0x13D7, 0x14A4,\n    0x1575, 0x164A, 0x1724, 0x1801, 0x18E3, 0x19C8, 0x1AB2, 0x1BA0, 0x1C91, 0x1D87, 0x1E81, 0x1F7F, 0x2081,\n    0x2187, 0x2291, 0x239F, 0x24B2, 0x25C8, 0x26E2, 0x2801, 0x2923, 0x2A4A, 0x2B75, 0x2CA3, 0x2DD6, 0x2F0D,\n    0x3048, 0x3187, 0x32CA, 0x3411, 0x355C, 0x36AB, 0x37FF, 0x3956, 0x3AB1, 0x3C11, 0x3D74, 0x3EDC, 0x4048,\n    0x41B7, 0x432B, 0x44A3, 0x461F, 0x479F, 0x4923, 0x4AAB, 0x4C37, 0x4DC7, 0x4F5C, 0x50F4, 0x5290, 0x5431,\n    0x55D6, 0x577E, 0x592B, 0x5ADC, 0x5C90, 0x5E49, 0x6006, 0x61C7, 0x638C, 0x6555, 0x6722, 0x68F4, 0x6AC9,\n    0x6CA2, 0x6E80, 0x7061, 0x7247, 0x7430, 0x761E, 0x7810, 0x7A06, 0x7C00, 0x7DFE, 0x8000};\n\n/* shared resources */\nstatic tjhandle TjHandle = nullptr;\n\n/* RSF audio state */\nstatic const u8* StaticAudio = nullptr;\nstatic u32 StaticAudioSize = 0;\nstatic u32 StaticAudioOffset = 0;\nstatic u16 StaticVolumeAtten = 0x50F4;\nstatic u32 StaticLoopBegin = 0;\nstatic u32 StaticLoopEnd = 0;\nstatic g72x_state StaticStateLeft = {};\nstatic g72x_state StaticStateRight = {};\n\n/* THP SFX audio */\nstatic float SfxVolume = 1.f;\n\nvoid CMoviePlayer::Initialize() { TjHandle = tjInitDecompress(); }\n\nvoid CMoviePlayer::Shutdown() {\n  tjDestroy(TjHandle);\n  TjHandle = nullptr;\n}\n\nvoid CMoviePlayer::THPHeader::swapBig() {\n  magic = SBig(magic);\n  version = SBig(version);\n  maxBufferSize = SBig(maxBufferSize);\n  maxAudioSamples = SBig(maxAudioSamples);\n  fps = SBig(fps);\n  numFrames = SBig(numFrames);\n  firstFrameSize = SBig(firstFrameSize);\n  dataSize = SBig(dataSize);\n  componentDataOffset = SBig(componentDataOffset);\n  offsetsDataOffset = SBig(offsetsDataOffset);\n  firstFrameOffset = SBig(firstFrameOffset);\n  lastFrameOffset = SBig(lastFrameOffset);\n}\n\nvoid CMoviePlayer::THPComponents::swapBig() { numComponents = SBig(numComponents); }\n\nvoid CMoviePlayer::THPVideoInfo::swapBig() {\n  width = SBig(width);\n  height = SBig(height);\n}\n\nvoid CMoviePlayer::THPAudioInfo::swapBig() {\n  numChannels = SBig(numChannels);\n  sampleRate = SBig(sampleRate);\n  numSamples = SBig(numSamples);\n}\n\nvoid CMoviePlayer::THPFrameHeader::swapBig() {\n  nextSize = SBig(nextSize);\n  prevSize = SBig(prevSize);\n  imageSize = SBig(imageSize);\n  audioSize = SBig(audioSize);\n}\n\nvoid CMoviePlayer::THPAudioFrameHeader::swapBig() {\n  channelSize = SBig(channelSize);\n  numSamples = SBig(numSamples);\n  for (int i = 0; i < 2; ++i) {\n    for (int j = 0; j < 8; ++j) {\n      channelCoefs[i][j][0] = SBig(channelCoefs[i][j][0]);\n      channelCoefs[i][j][1] = SBig(channelCoefs[i][j][1]);\n    }\n    channelPrevs[i][0] = SBig(channelPrevs[i][0]);\n    channelPrevs[i][1] = SBig(channelPrevs[i][1]);\n  }\n}\n\n/* Slightly modified from THPAudioDecode present in SDK; always interleaves */\nu32 CMoviePlayer::THPAudioDecode(s16* buffer, const u8* audioFrame, bool stereo) {\n  THPAudioFrameHeader header = *((const THPAudioFrameHeader*)audioFrame);\n  //  header.swapBig();\n  //  audioFrame += sizeof(THPAudioFrameHeader);\n  //\n  //  if (stereo) {\n  //    for (int i = 0; i < 2; ++i) {\n  //      unsigned samples = header.numSamples;\n  //      s16* bufferCur = buffer + i;\n  //      int16_t prev1 = header.channelPrevs[i][0];\n  //      int16_t prev2 = header.channelPrevs[i][1];\n  //      for (u32 f = 0; f < (header.numSamples + 13) / 14; ++f) {\n  //        DSPDecompressFrameStereoStride(bufferCur, audioFrame, header.channelCoefs[i], &prev1, &prev2, samples);\n  //        samples -= 14;\n  //        bufferCur += 28;\n  //        audioFrame += 8;\n  //      }\n  //    }\n  //  } else {\n  //    unsigned samples = header.numSamples;\n  //    s16* bufferCur = buffer;\n  //    int16_t prev1 = header.channelPrevs[0][0];\n  //    int16_t prev2 = header.channelPrevs[0][1];\n  //    for (u32 f = 0; f < (header.numSamples + 13) / 14; ++f) {\n  //      DSPDecompressFrameStereoDupe(bufferCur, audioFrame, header.channelCoefs[0], &prev1, &prev2, samples);\n  //      samples -= 14;\n  //      bufferCur += 28;\n  //      audioFrame += 8;\n  //    }\n  //  }\n\n  return header.numSamples;\n}\n\nCMoviePlayer::CMoviePlayer(const char* path, float preLoadSeconds, bool loop, bool deinterlace)\n: CDvdFile(path), xec_preLoadSeconds(preLoadSeconds), xf4_24_loop(loop), m_deinterlace(deinterlace) {\n  /* Read THP header information */\n  u8 buf[64];\n  SyncRead(buf, 64);\n  memmove(&x28_thpHead, buf, 48);\n  x28_thpHead.swapBig();\n\n  u32 cur = x28_thpHead.componentDataOffset;\n  SyncSeekRead(buf, 32, ESeekOrigin::Begin, cur);\n  memmove(&x58_thpComponents, buf, 20);\n  cur += 20;\n  x58_thpComponents.swapBig();\n\n  for (u32 i = 0; i < x58_thpComponents.numComponents; ++i) {\n    switch (x58_thpComponents.comps[i]) {\n    case THPComponents::Type::Video:\n      SyncSeekRead(buf, 32, ESeekOrigin::Begin, cur);\n      memmove(&x6c_videoInfo, buf, 8);\n      cur += 8;\n      x6c_videoInfo.swapBig();\n      break;\n    case THPComponents::Type::Audio:\n      SyncSeekRead(buf, 32, ESeekOrigin::Begin, cur);\n      memmove(&x74_audioInfo, buf, 12);\n      cur += 12;\n      x74_audioInfo.swapBig();\n      xf4_25_hasAudio = true;\n      break;\n    default:\n      break;\n    }\n  }\n\n  /* Initial read state */\n  xb4_nextReadOff = x28_thpHead.firstFrameOffset;\n  xb0_nextReadSize = x28_thpHead.firstFrameSize;\n  xb8_readSizeWrapped = x28_thpHead.firstFrameSize;\n  xbc_readOffWrapped = x28_thpHead.firstFrameOffset;\n\n  xe4_totalSeconds = x28_thpHead.numFrames / x28_thpHead.fps;\n  if (xec_preLoadSeconds < 0.f) {\n    /* Load whole video */\n    xec_preLoadSeconds = xe4_totalSeconds;\n    xf0_preLoadFrames = x28_thpHead.numFrames;\n  } else if (xec_preLoadSeconds > 0.f) {\n    /* Pre-load video portion */\n    u32 frame = xec_preLoadSeconds * x28_thpHead.fps;\n    xf0_preLoadFrames = std::min(frame, x28_thpHead.numFrames);\n    xec_preLoadSeconds = std::min(xe4_totalSeconds, xec_preLoadSeconds);\n  }\n\n  if (xf0_preLoadFrames > 0)\n    xa0_bufferQueue.reserve(xf0_preLoadFrames);\n\n  /* Allocate textures here (rather than at decode time) */\n  x80_textures.reserve(3);\n  for (int i = 0; i < 3; ++i) {\n    CTHPTextureSet& set = x80_textures.emplace_back();\n    if (xf4_25_hasAudio)\n      set.audioBuf.reset(new s16[x28_thpHead.maxAudioSamples * 2]);\n  }\n\n  /* Temporary planar YUV decode buffer, resulting planes copied to Boo */\n  m_yuvBuf.reset(new uint8_t[tjBufSizeYUV(x6c_videoInfo.width, x6c_videoInfo.height, TJ_420)]);\n\n  /* Schedule initial read */\n  PostDVDReadRequestIfNeeded();\n\n  m_hpad = 0.5f;\n  m_vpad = 0.5f;\n}\n\nvoid CMoviePlayer::SetStaticAudioVolume(int vol) {\n  StaticVolumeAtten = StaticVolumeLookup[std::max(0, std::min(127, vol))];\n}\n\nvoid CMoviePlayer::SetStaticAudio(const void* data, u32 size, u32 loopBegin, u32 loopEnd) {\n  StaticAudio = reinterpret_cast<const u8*>(data);\n  StaticAudioSize = size;\n  StaticLoopBegin = loopBegin;\n  StaticLoopEnd = loopEnd;\n  StaticAudioOffset = 0;\n\n  g72x_init_state(&StaticStateLeft);\n  g72x_init_state(&StaticStateRight);\n}\n\nvoid CMoviePlayer::SetSfxVolume(u8 volume) { SfxVolume = std::min(volume, u8(127)) / 127.f; }\n\nvoid CMoviePlayer::MixAudio(s16* out, const s16* in, u32 samples) {\n  /* No audio frames ready */\n  //  if (xd4_audioSlot == UINT32_MAX) {\n  //    if (in)\n  //      memmove(out, in, samples * 4);\n  //    else\n  //      memset(out, 0, samples * 4);\n  //    return;\n  //  }\n  //\n  //  while (samples) {\n  //    CTHPTextureSet* tex = &x80_textures[xd4_audioSlot];\n  //    u32 thisSamples = std::min(tex->audioSamples - tex->playedSamples, samples);\n  //    if (!thisSamples) {\n  //      /* Advance frame */\n  //      ++xd4_audioSlot;\n  //      if (xd4_audioSlot >= x80_textures.size())\n  //        xd4_audioSlot = 0;\n  //      tex = &x80_textures[xd4_audioSlot];\n  //      thisSamples = std::min(tex->audioSamples - tex->playedSamples, samples);\n  //    }\n  //\n  //    if (thisSamples) {\n  //      /* mix samples with `in` or no mix */\n  //      if (in) {\n  //        for (u32 i = 0; i < thisSamples; ++i, out += 2, in += 2) {\n  //          out[0] = DSPSampClamp(in[0] + s32(tex->audioBuf[(i + tex->playedSamples) * 2]) * 0x50F4 / 0x8000 *\n  //          SfxVolume); out[1] =\n  //              DSPSampClamp(in[1] + s32(tex->audioBuf[(i + tex->playedSamples) * 2 + 1]) * 0x50F4 / 0x8000 *\n  //              SfxVolume);\n  //        }\n  //      } else {\n  //        for (u32 i = 0; i < thisSamples; ++i, out += 2) {\n  //          out[0] = DSPSampClamp(s32(tex->audioBuf[(i + tex->playedSamples) * 2]) * 0x50F4 / 0x8000 * SfxVolume);\n  //          out[1] = DSPSampClamp(s32(tex->audioBuf[(i + tex->playedSamples) * 2 + 1]) * 0x50F4 / 0x8000 * SfxVolume);\n  //        }\n  //      }\n  //      tex->playedSamples += thisSamples;\n  //      samples -= thisSamples;\n  //    } else {\n  //      /* metaforce addition: failsafe for buffer overrun */\n  //      if (in)\n  //        memmove(out, in, samples * 4);\n  //      else\n  //        memset(out, 0, samples * 4);\n  //      // fprintf(stderr, \"dropped %d samples\\n\", samples);\n  //      return;\n  //    }\n  //  }\n}\n\nvoid CMoviePlayer::MixStaticAudio(s16* out, const s16* in, u32 samples) {\n  //  if (!StaticAudio)\n  //    return;\n  //  while (samples) {\n  //    u32 thisSamples = std::min(StaticLoopEnd - StaticAudioOffset, samples);\n  //    const u8* thisOffsetLeft = &StaticAudio[StaticAudioOffset / 2];\n  //    const u8* thisOffsetRight = &StaticAudio[StaticAudioSize / 2 + StaticAudioOffset / 2];\n  //\n  //    /* metaforce addition: mix samples with `in` or no mix */\n  //    if (in) {\n  //      for (u32 i = 0; i < thisSamples; i += 2) {\n  //        out[0] = DSPSampClamp(\n  //            in[0] + s32(g721_decoder(thisOffsetLeft[0] & 0xf, &StaticStateLeft) * StaticVolumeAtten / 0x8000));\n  //        out[1] = DSPSampClamp(\n  //            in[1] + s32(g721_decoder(thisOffsetRight[0] & 0xf, &StaticStateRight) * StaticVolumeAtten / 0x8000));\n  //        out[2] = DSPSampClamp(\n  //            in[2] + s32(g721_decoder(thisOffsetLeft[0] >> 4 & 0xf, &StaticStateLeft) * StaticVolumeAtten / 0x8000));\n  //        out[3] = DSPSampClamp(\n  //            in[3] + s32(g721_decoder(thisOffsetRight[0] >> 4 & 0xf, &StaticStateRight) * StaticVolumeAtten /\n  //            0x8000));\n  //        thisOffsetLeft += 1;\n  //        thisOffsetRight += 1;\n  //        out += 4;\n  //        in += 4;\n  //      }\n  //    } else {\n  //      for (u32 i = 0; i < thisSamples; i += 2) {\n  //        out[0] =\n  //            DSPSampClamp(s32(g721_decoder(thisOffsetLeft[0] & 0xf, &StaticStateLeft) * StaticVolumeAtten / 0x8000));\n  //        out[1] =\n  //            DSPSampClamp(s32(g721_decoder(thisOffsetRight[0] & 0xf, &StaticStateRight) * StaticVolumeAtten /\n  //            0x8000));\n  //        out[2] = DSPSampClamp(\n  //            s32(g721_decoder(thisOffsetLeft[0] >> 4 & 0xf, &StaticStateLeft) * StaticVolumeAtten / 0x8000));\n  //        out[3] = DSPSampClamp(\n  //            s32(g721_decoder(thisOffsetRight[0] >> 4 & 0xf, &StaticStateRight) * StaticVolumeAtten / 0x8000));\n  //        thisOffsetLeft += 1;\n  //        thisOffsetRight += 1;\n  //        out += 4;\n  //      }\n  //    }\n  //\n  //    StaticAudioOffset += thisSamples;\n  //    if (StaticAudioOffset == StaticLoopEnd)\n  //      StaticAudioOffset = StaticLoopBegin;\n  //    samples -= thisSamples;\n  //  }\n}\n\nvoid CMoviePlayer::Rewind() {\n  if (x98_request) {\n    x98_request->PostCancelRequest();\n    x98_request.reset();\n  }\n\n  xb0_nextReadSize = x28_thpHead.firstFrameSize;\n  xb4_nextReadOff = x28_thpHead.firstFrameOffset;\n  xb8_readSizeWrapped = x28_thpHead.firstFrameSize;\n  xbc_readOffWrapped = x28_thpHead.firstFrameOffset;\n\n  xc0_curLoadFrame = 0;\n  xc4_requestFrameWrapped = 0;\n  xc8_curFrame = 0;\n  xcc_decodedTexSlot = 0;\n  xd0_drawTexSlot = -1;\n  xd4_audioSlot = -1;\n  xd8_decodedTexCount = 0;\n  xdc_frameRem = 0.f;\n  xe8_curSeconds = 0.f;\n}\n\nbool CMoviePlayer::DrawVideo() {\n  // TODO\n  // if (!xa0_bufferQueue.empty()) {\n  //   return false;\n  // }\n\n  g_Renderer->SetDepthReadWrite(false, false);\n  g_Renderer->SetViewportOrtho(false, -4096.f, 4096.f);\n\n  const s32 vpHeight = CGraphics::GetViewportHeight();\n  const s32 vpWidth = CGraphics::GetViewportWidth();\n  const s32 vpTop = CGraphics::GetViewportTop();\n  const s32 vpLeft = CGraphics::GetViewportLeft();\n#ifdef AURORA\n  // Scale to full size, maintaining aspect ratio\n  const float scale = std::min(static_cast<float>(CGraphics::GetViewportWidth()) / 640.0f,\n                               static_cast<float>(CGraphics::GetViewportHeight()) / 448.0f);\n  const s32 vidWidth = x6c_videoInfo.width * scale;\n  const s32 vidHeight = x6c_videoInfo.height * scale;\n#else\n  const s32 vidWidth = x6c_videoInfo.width;\n  const s32 vidHeight = x6c_videoInfo.height;\n#endif\n  const s32 centerX = (vidWidth - vpWidth) / 2;\n  const s32 centerY = (vidHeight - vpHeight) / 2;\n  const s32 vl = vpLeft - centerX;\n  const s32 vr = vpLeft + vpWidth + centerX;\n  const s32 vb = vpTop + vpHeight + centerY;\n  const s32 vt = vpTop - centerY;\n  zeus::CVector3f v1;\n  zeus::CVector3f v2;\n  zeus::CVector3f v3;\n  zeus::CVector3f v4;\n  v1.x() = vl;\n  v1.y() = 0.0;\n  v1.z() = vb;\n  v2.x() = vr;\n  v2.y() = 0.0;\n  v2.z() = vb;\n  v3.x() = vl;\n  v3.y() = 0.0;\n  v3.z() = vt;\n  v4.x() = vr;\n  v4.y() = 0.0;\n  v4.z() = vt;\n\n  DrawFrame(v1, v2, v3, v4);\n  return true;\n}\n\nvoid CMoviePlayer::DrawFrame(const zeus::CVector3f& v1, const zeus::CVector3f& v2, const zeus::CVector3f& v3,\n                             const zeus::CVector3f& v4) {\n  if (xd0_drawTexSlot == UINT32_MAX || !GetIsFullyCached()) {\n    return;\n  }\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CMoviePlayer::DrawFrame\", zeus::skYellow);\n\n  CGraphics::SetUseVideoFilter(xf4_26_fieldFlip);\n\n  MyTHPGXYuv2RgbSetup(CGraphics::mLastFrameUsedAbove, xf4_26_fieldFlip);\n  uintptr_t planeSize = x6c_videoInfo.width * x6c_videoInfo.height;\n  uintptr_t planeSizeQuarter = planeSize / 4;\n  MyTHPYuv2RgbTextureSetup(m_yuvBuf.get(), m_yuvBuf.get() + planeSize, m_yuvBuf.get() + planeSize + planeSizeQuarter,\n                           x6c_videoInfo.width, x6c_videoInfo.height);\n\n  CGX::Begin(GX_TRIANGLEFAN, GX_VTXFMT7, 4);\n  GXPosition3f32(v1);\n  GXTexCoord2u16(0, 0);\n  GXPosition3f32(v3);\n  GXTexCoord2u16(0, 1);\n  GXPosition3f32(v4);\n  GXTexCoord2u16(1, 1);\n  GXPosition3f32(v2);\n  GXTexCoord2u16(1, 0);\n  CGX::End();\n  MyTHPGXRestore();\n\n  /* ensure second field is being displayed by VI to signal advance\n   * (faked in metaforce with continuous xor) */\n  if (xfc_fieldIndex == 0 && CGraphics::mLastFrameUsedAbove)\n    xf4_26_fieldFlip = true;\n\n  ++xfc_fieldIndex;\n}\n\nvoid CMoviePlayer::Update(float dt) {\n  if (xc0_curLoadFrame < xf0_preLoadFrames) {\n    /* in buffering phase, ensure read data is stored for mem-cache access */\n    if (x98_request && x98_request->IsComplete()) {\n      ReadCompleted();\n      if (xc0_curLoadFrame >= xa0_bufferQueue.size() && xc0_curLoadFrame < xf0_preLoadFrames &&\n          xa0_bufferQueue.size() < x28_thpHead.numFrames) {\n        PostDVDReadRequestIfNeeded();\n      }\n    }\n  } else {\n    /* out of buffering phase, skip mem-cache straight to decode */\n    if (x98_request) {\n      bool flag = false;\n      if (xc4_requestFrameWrapped >= xa0_bufferQueue.size() && xc0_curLoadFrame >= xa0_bufferQueue.size())\n        flag = true;\n      if (x98_request->IsComplete() && xd8_decodedTexCount < 2 && flag) {\n        DecodeFromRead(x90_requestBuf.get());\n        ReadCompleted();\n        PostDVDReadRequestIfNeeded();\n        ++xd8_decodedTexCount;\n        ++xc4_requestFrameWrapped;\n        if (xc4_requestFrameWrapped >= x28_thpHead.numFrames && xf4_24_loop)\n          xc4_requestFrameWrapped = 0;\n      }\n    }\n  }\n\n  /* submit request for additional read to keep stream-consumer happy\n   * (if buffer slot is available) */\n  if (!x98_request && xe0_playMode == EPlayMode::Playing && xa0_bufferQueue.size() < x28_thpHead.numFrames) {\n    PostDVDReadRequestIfNeeded();\n  }\n\n  /* decode frame directly from mem-cache if needed */\n  if (xd8_decodedTexCount < 2) {\n    if (xe0_playMode == EPlayMode::Playing && xc4_requestFrameWrapped < xf0_preLoadFrames) {\n      u32 minFrame = std::min(u32(xa0_bufferQueue.size()) - 1, xc4_requestFrameWrapped);\n      if (minFrame == UINT32_MAX)\n        return;\n      std::unique_ptr<uint8_t[]>& frameData = xa0_bufferQueue[minFrame];\n      DecodeFromRead(frameData.get());\n      ++xd8_decodedTexCount;\n      ++xc4_requestFrameWrapped;\n      if (xc4_requestFrameWrapped >= x28_thpHead.numFrames && xf4_24_loop)\n        xc4_requestFrameWrapped = 0;\n    }\n  }\n\n  /* paused THPs shall not pass */\n  if (xd8_decodedTexCount <= 0 || xe0_playMode != EPlayMode::Playing)\n    return;\n\n  /* timing update */\n  xe8_curSeconds += dt;\n  if (xf4_24_loop)\n    xe8_curSeconds = std::fmod(xe8_curSeconds, xe4_totalSeconds);\n  else\n    xe8_curSeconds = std::min(xe4_totalSeconds, xe8_curSeconds);\n\n  /* test remainder threshold, determine if frame needs to be advanced */\n  float frameDt = 1.f / x28_thpHead.fps;\n  float rem = xdc_frameRem - dt;\n  if (rem <= 0.f) {\n    if (!xf4_26_fieldFlip) {\n      /* second field has drawn, advance consumer-queue to next THP frame */\n      ++xd0_drawTexSlot;\n      if (xd0_drawTexSlot >= x80_textures.size())\n        xd0_drawTexSlot = 0;\n      if (xd4_audioSlot == UINT32_MAX)\n        xd4_audioSlot = 0;\n      --xd8_decodedTexCount;\n      ++xc8_curFrame;\n      if (xc8_curFrame == x28_thpHead.numFrames && xf4_24_loop)\n        xc8_curFrame = 0;\n      rem += frameDt;\n      xfc_fieldIndex = 0;\n    } else {\n      /* advance timing within second field */\n      rem += dt;\n      xf4_26_fieldFlip = false;\n    }\n  }\n  xdc_frameRem = rem;\n}\n\nvoid CMoviePlayer::DecodeFromRead(const void* data) {\n  const u8* inptr = (u8*)data;\n  CTHPTextureSet& tex = x80_textures[xcc_decodedTexSlot];\n\n  THPFrameHeader frameHeader = *static_cast<const THPFrameHeader*>(data);\n  frameHeader.swapBig();\n  inptr += 8 + x58_thpComponents.numComponents * 4;\n\n  for (u32 i = 0; i < x58_thpComponents.numComponents; ++i) {\n    switch (x58_thpComponents.comps[i]) {\n    case THPComponents::Type::Video: {\n      tjDecompressToYUV(TjHandle, (u8*)inptr, frameHeader.imageSize, m_yuvBuf.get(), 0);\n      inptr += frameHeader.imageSize;\n      break;\n    }\n    case THPComponents::Type::Audio:\n      memset(tex.audioBuf.get(), 0, x28_thpHead.maxAudioSamples * 4);\n      tex.audioSamples = THPAudioDecode(tex.audioBuf.get(), (u8*)inptr, x74_audioInfo.numChannels == 2);\n      tex.playedSamples = 0;\n      inptr += frameHeader.audioSize;\n      break;\n    default:\n      break;\n    }\n  }\n\n  /* advance YUV producer-queue slot */\n  ++xcc_decodedTexSlot;\n  if (xcc_decodedTexSlot == x80_textures.size())\n    xcc_decodedTexSlot = 0;\n}\n\nvoid CMoviePlayer::ReadCompleted() {\n  std::unique_ptr<uint8_t[]> buffer = std::move(x90_requestBuf);\n  x98_request.reset();\n  const THPFrameHeader* frameHeader = reinterpret_cast<const THPFrameHeader*>(buffer.get());\n\n  /* transfer request buffer to mem-cache if needed */\n  if (xc0_curLoadFrame == xa0_bufferQueue.size() && xf0_preLoadFrames > xc0_curLoadFrame)\n    xa0_bufferQueue.push_back(std::move(buffer));\n\n  /* store params of next read operation */\n  xb4_nextReadOff += xb0_nextReadSize;\n  xb0_nextReadSize = SBig(frameHeader->nextSize);\n  ++xc0_curLoadFrame;\n\n  if (xc0_curLoadFrame == xf0_preLoadFrames) {\n    if (x28_thpHead.numFrames == xc0_curLoadFrame) {\n      xb8_readSizeWrapped = x28_thpHead.firstFrameSize;\n      xbc_readOffWrapped = x28_thpHead.firstFrameOffset;\n    } else {\n      xb8_readSizeWrapped = xb0_nextReadSize;\n      xbc_readOffWrapped = xb4_nextReadOff;\n    }\n  }\n\n  /* handle loop-event read */\n  if (xc0_curLoadFrame >= x28_thpHead.numFrames && xf4_24_loop) {\n    xb4_nextReadOff = xbc_readOffWrapped;\n    xb0_nextReadSize = xb8_readSizeWrapped;\n    xc0_curLoadFrame = xf0_preLoadFrames;\n  }\n}\n\nvoid CMoviePlayer::PostDVDReadRequestIfNeeded() {\n  if (xc0_curLoadFrame < x28_thpHead.numFrames) {\n    x90_requestBuf.reset(new uint8_t[xb0_nextReadSize]);\n    x98_request = AsyncSeekRead(x90_requestBuf.get(), xb0_nextReadSize, ESeekOrigin::Begin, xb4_nextReadOff);\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CMoviePlayer.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <vector>\n\n#include \"Runtime/CDvdFile.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Graphics/CGraphics.hpp\"\n\n#include <zeus/CColor.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\n\nclass CMoviePlayer : public CDvdFile {\npublic:\n  enum class EPlayMode { Stopped, Playing };\n\nprivate:\n  struct THPHeader {\n    u32 magic;\n    u32 version;\n    u32 maxBufferSize;\n    u32 maxAudioSamples;\n    float fps;\n    u32 numFrames;\n    u32 firstFrameSize;\n    u32 dataSize;\n    u32 componentDataOffset;\n    u32 offsetsDataOffset;\n    u32 firstFrameOffset;\n    u32 lastFrameOffset;\n    void swapBig();\n  } x28_thpHead{};\n\n  struct THPComponents {\n    u32 numComponents;\n    enum class Type : u8 { Video = 0x0, Audio = 0x1, None = 0xff } comps[16];\n    void swapBig();\n  } x58_thpComponents{};\n\n  struct THPVideoInfo {\n    u32 width;\n    u32 height;\n    void swapBig();\n  } x6c_videoInfo{};\n\n  struct THPAudioInfo {\n    u32 numChannels;\n    u32 sampleRate;\n    u32 numSamples;\n    void swapBig();\n  } x74_audioInfo{};\n\n  struct THPFrameHeader {\n    u32 nextSize;\n    u32 prevSize;\n    u32 imageSize;\n    u32 audioSize;\n    void swapBig();\n  };\n\n  struct THPAudioFrameHeader {\n    u32 channelSize;\n    u32 numSamples;\n    s16 channelCoefs[2][8][2];\n    s16 channelPrevs[2][2];\n    void swapBig();\n  };\n\n  struct CTHPTextureSet {\n    std::array<TGXTexObj, 2> Y;\n    TGXTexObj U;\n    TGXTexObj V;\n    u32 playedSamples = 0;\n    u32 audioSamples = 0;\n    std::unique_ptr<s16[]> audioBuf;\n  };\n  std::vector<CTHPTextureSet> x80_textures;\n  std::unique_ptr<uint8_t[]> x90_requestBuf;\n  std::shared_ptr<IDvdRequest> x98_request;\n  std::vector<std::unique_ptr<uint8_t[]>> xa0_bufferQueue;\n\n  u32 xb0_nextReadSize = 0;\n  u32 xb4_nextReadOff = 0;\n  u32 xb8_readSizeWrapped = 0;\n  u32 xbc_readOffWrapped = 0;\n  u32 xc0_curLoadFrame = 0;\n  u32 xc4_requestFrameWrapped = 0;\n  u32 xc8_curFrame = 0;\n  u32 xcc_decodedTexSlot = 0;\n  u32 xd0_drawTexSlot = -1;\n  u32 xd4_audioSlot = -1;\n  s32 xd8_decodedTexCount = 0;\n  float xdc_frameRem = 0.f;\n  EPlayMode xe0_playMode = EPlayMode::Playing;\n  float xe4_totalSeconds = 0.f;\n  float xe8_curSeconds = 0.f;\n  float xec_preLoadSeconds;\n  u32 xf0_preLoadFrames = 0;\n  bool xf4_24_loop : 1;\n  bool xf4_25_hasAudio : 1 = false;\n  bool xf4_26_fieldFlip : 1 = false;\n  bool m_deinterlace : 1;\n  u32 xf8_ = 0;\n  u32 xfc_fieldIndex = 0;\n\n  std::unique_ptr<uint8_t[]> m_yuvBuf;\n  float m_hpad;\n  float m_vpad;\n\n  void DecodeFromRead(const void* data);\n  void PostDVDReadRequestIfNeeded();\n  void ReadCompleted();\n\n  static u32 THPAudioDecode(s16* buffer, const u8* audioFrame, bool stereo);\n\npublic:\n  CMoviePlayer(const char* path, float preLoadSeconds, bool loop, bool deinterlace);\n\n  static void DisableStaticAudio() { SetStaticAudio(nullptr, 0, 0, 0); }\n  static void SetStaticAudioVolume(int vol);\n  static void SetStaticAudio(const void* data, u32 size, u32 loopBegin, u32 loopEnd);\n  static void SetSfxVolume(u8 volume);\n  static void MixStaticAudio(s16* out, const s16* in, u32 samples);\n  void MixAudio(s16* out, const s16* in, u32 samples);\n  void Rewind();\n\n  bool GetIsMovieFinishedPlaying() const {\n    if (xf4_24_loop)\n      return false;\n    return xc8_curFrame == x28_thpHead.numFrames;\n  }\n  bool IsLooping() const { return xf4_24_loop; }\n  bool GetIsFullyCached() const { return xa0_bufferQueue.size() >= xf0_preLoadFrames; }\n  float GetPlayedSeconds() const { return xdc_frameRem + xe8_curSeconds; }\n  float GetTotalSeconds() const { return xe4_totalSeconds; }\n  void SetPlayMode(EPlayMode mode) { xe0_playMode = mode; }\n  bool DrawVideo();\n  void DrawFrame(const zeus::CVector3f& v1, const zeus::CVector3f& v2, const zeus::CVector3f& v3,\n                 const zeus::CVector3f& v4);\n  void Update(float dt);\n  u32 GetWidth() const { return x6c_videoInfo.width; }\n  u32 GetHeight() const { return x6c_videoInfo.width; }\n\n  static void Initialize();\n  static void Shutdown();\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CPVSAreaSet.cpp",
    "content": "#include \"Runtime/Graphics/CPVSAreaSet.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n\nnamespace metaforce {\n\nCPVSAreaSet::CPVSAreaSet(const u8* data, u32 len) {\n  CMemoryInStream r(data, len);\n  x0_numFeatures = r.ReadLong();\n  x4_numLights = r.ReadLong();\n  x8_num2ndLights = r.ReadLong();\n  xc_numActors = r.ReadLong();\n  x10_leafSize = r.ReadLong();\n  x14_lightIndexCount = r.ReadLong();\n  x18_entityIndex.reserve(xc_numActors);\n  for (u32 i = 0; i < xc_numActors; ++i)\n    x18_entityIndex.push_back(r.ReadLong());\n  x1c_lightLeaves = data + r.GetReadPosition();\n  const u8* octreeData = x1c_lightLeaves + x14_lightIndexCount * x10_leafSize;\n  x20_octree = CPVSVisOctree::MakePVSVisOctree(octreeData);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CPVSAreaSet.hpp",
    "content": "#pragma once\n\n#include <vector>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Graphics/CPVSVisOctree.hpp\"\n\nnamespace metaforce {\n\nclass CPVSAreaSet {\n  u32 x0_numFeatures;\n  u32 x4_numLights;\n  u32 x8_num2ndLights;\n  u32 xc_numActors;\n  u32 x10_leafSize;\n  u32 x14_lightIndexCount;\n  std::vector<u32> x18_entityIndex;\n  const u8* x1c_lightLeaves;\n  CPVSVisOctree x20_octree;\n\n  CPVSVisSet _GetLightSet(size_t lightIdx) const {\n    CPVSVisSet ret;\n    ret.SetFromMemory(x20_octree.GetNumObjects(), x20_octree.GetNumLights(), x1c_lightLeaves + x10_leafSize * lightIdx);\n    return ret;\n  }\n\npublic:\n  explicit CPVSAreaSet(const u8* data, u32 len);\n  u32 GetNumFeatures() const { return x0_numFeatures; }\n  u32 GetNumActors() const { return xc_numActors; }\n  u32 Get1stLightIndex(u32 lightIdx) const { return x0_numFeatures + x8_num2ndLights + lightIdx; }\n  u32 Get2ndLightIndex(u32 lightIdx) const { return x0_numFeatures + lightIdx; }\n  bool Has2ndLayerLights() const { return x8_num2ndLights != 0; }\n  u32 GetEntityIdByIndex(size_t idx) const { return x18_entityIndex[idx]; }\n  const CPVSVisOctree& GetVisOctree() const { return x20_octree; }\n  CPVSVisSet Get1stLightSet(size_t lightIdx) const { return _GetLightSet(x8_num2ndLights + lightIdx); }\n  CPVSVisSet Get2ndLightSet(size_t lightIdx) const { return _GetLightSet(lightIdx); }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CPVSVisOctree.cpp",
    "content": "#include \"Runtime/Graphics/CPVSVisOctree.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n\n#include <array>\n\nnamespace metaforce {\n\nCPVSVisOctree CPVSVisOctree::MakePVSVisOctree(const u8* data) {\n  CMemoryInStream r(data, 68);\n  const zeus::CAABox aabb = r.Get<zeus::CAABox>();\n  const u32 numObjects = r.ReadLong();\n  const u32 numLights = r.ReadLong();\n  r.ReadLong();\n  return CPVSVisOctree(aabb, numObjects, numLights, data + r.GetReadPosition());\n}\n\nCPVSVisOctree::CPVSVisOctree(const zeus::CAABox& aabb, u32 numObjects, u32 numLights, const u8* c)\n: x0_aabb(aabb)\n, x18_numObjects(numObjects)\n, x1c_numLights(numLights)\n, x20_bufferFlag(c != nullptr)\n, x24_octreeData(c)\n, x2c_searchAabb(x0_aabb) {\n  x20_bufferFlag = false;\n}\n\nu32 CPVSVisOctree::GetNumChildren(u8 byte) const {\n  static constexpr std::array<u32, 8> numChildTable{0, 2, 2, 4, 2, 4, 4, 8};\n  return numChildTable[byte & 0x7];\n}\n\nu32 CPVSVisOctree::GetChildIndex(const u8*, const zeus::CVector3f&) const { return 0; }\n\ns32 CPVSVisOctree::IterateSearch(u8 nodeData, const zeus::CVector3f& tp) const {\n  if (!(nodeData & 0x7))\n    return -1; // Leaf node\n\n  zeus::CVector3f newMin = x2c_searchAabb.center();\n  zeus::CVector3f newMax;\n  std::array<bool, 3> highFlags{};\n\n  if (tp.x() > newMin.x()) {\n    newMax.x() = x2c_searchAabb.max.x();\n    highFlags[0] = true;\n  } else {\n    newMax.x() = float(newMin.x());\n    newMin.x() = float(x2c_searchAabb.min.x());\n    highFlags[0] = false;\n  }\n\n  if (tp.y() > newMin.y()) {\n    newMax.y() = float(x2c_searchAabb.max.y());\n    highFlags[1] = true;\n  } else {\n    newMax.y() = float(newMin.y());\n    newMin.y() = float(x2c_searchAabb.min.y());\n    highFlags[1] = false;\n  }\n\n  if (tp.z() > newMin.z()) {\n    newMax.z() = float(x2c_searchAabb.max.z());\n    highFlags[2] = true;\n  } else {\n    newMax.z() = float(newMin.z());\n    newMin.z() = float(x2c_searchAabb.min.z());\n    highFlags[2] = false;\n  }\n\n  std::array<u8, 2> axisCounts{1, 1};\n  if (nodeData & 0x1)\n    axisCounts[0] = 2;\n  if (nodeData & 0x2)\n    axisCounts[1] = 2;\n\n  zeus::CAABox& newSearch = const_cast<zeus::CAABox&>(x2c_searchAabb);\n  if (nodeData & 0x1) {\n    newSearch.min.x() = float(newMin.x());\n    newSearch.max.x() = float(newMax.x());\n  }\n  if (nodeData & 0x2) {\n    newSearch.min.y() = float(newMin.y());\n    newSearch.max.y() = float(newMax.y());\n  }\n  if (nodeData & 0x4) {\n    newSearch.min.z() = float(newMin.z());\n    newSearch.max.z() = float(newMax.z());\n  }\n\n  // Branch node - calculate next relative pointer\n  return highFlags[0] * bool(nodeData & 0x1) + highFlags[1] * axisCounts[0] * bool(nodeData & 0x2) +\n         highFlags[2] * axisCounts[0] * axisCounts[1] * bool(nodeData & 0x4);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CPVSVisOctree.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Graphics/CPVSVisSet.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\n\nclass CPVSVisOctree {\n  zeus::CAABox x0_aabb;\n  u32 x18_numObjects = 0;\n  u32 x1c_numLights = 0;\n  bool x20_bufferFlag = false;\n  const u8* x24_octreeData = nullptr;\n  zeus::CAABox x2c_searchAabb;\n\npublic:\n  static CPVSVisOctree MakePVSVisOctree(const u8* data);\n  CPVSVisOctree() = default;\n  CPVSVisOctree(const zeus::CAABox& aabb, u32 numObjects, u32 numLights, const u8* c);\n  u32 GetNumChildren(u8 byte) const;\n  u32 GetChildIndex(const u8*, const zeus::CVector3f&) const;\n  const zeus::CAABox& GetBounds() const { return x0_aabb; }\n  const u8* GetOctreeData() const { return x24_octreeData; }\n\n  u32 GetNumObjects() const { return x18_numObjects; }\n  u32 GetNumLights() const { return x1c_numLights; }\n  void ResetSearch() const { const_cast<CPVSVisOctree&>(*this).x2c_searchAabb = x0_aabb; }\n  s32 IterateSearch(u8 nodeData, const zeus::CVector3f& tp) const;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CPVSVisSet.cpp",
    "content": "#include \"Runtime/Graphics/CPVSVisSet.hpp\"\n\n#include \"Runtime/Graphics/CPVSVisOctree.hpp\"\n\nnamespace metaforce {\n\nvoid CPVSVisSet::Reset(EPVSVisSetState state) {\n  x0_state = state;\n  x4_numBits = 0;\n  x8_numLights = 0;\n  // xc_ = false;\n  x10_ptr = nullptr;\n}\n\nEPVSVisSetState CPVSVisSet::GetVisible(u32 idx) const {\n  if (x0_state != EPVSVisSetState::NodeFound)\n    return x0_state;\n\n  u32 numFeatures = x4_numBits - x8_numLights;\n  if (idx < numFeatures) {\n    /* This is a feature lookup */\n    if (!(x10_ptr[idx / 8] & (1 << (idx & 0x7))))\n      return EPVSVisSetState::EndOfTree;\n    return EPVSVisSetState::OutOfBounds;\n  }\n\n  /* This is a light lookup */\n  u32 lightTest = idx - numFeatures + idx;\n  const u8* ptr = &x10_ptr[lightTest / 8];\n  lightTest &= 0x7;\n  if (lightTest != 0x7)\n    return EPVSVisSetState((ptr[0] & (0x3 << lightTest)) >> lightTest);\n  return EPVSVisSetState((ptr[0] >> 7) | ((ptr[1] & 0x1) << 1));\n}\n\nvoid CPVSVisSet::SetFromMemory(u32 numBits, u32 numLights, const u8* leafPtr) {\n  x0_state = EPVSVisSetState::NodeFound;\n  x4_numBits = numBits;\n  x8_numLights = numLights;\n  x10_ptr = leafPtr;\n}\n\nvoid CPVSVisSet::SetTestPoint(const CPVSVisOctree& octree, const zeus::CVector3f& point) {\n  if (!octree.GetBounds().pointInside(point)) {\n    Reset(EPVSVisSetState::OutOfBounds);\n    return;\n  }\n\n  const u8* octCur = octree.GetOctreeData();\n  octree.ResetSearch();\n  s32 nextNodeRel;\n  u8 curNode;\n  while ((nextNodeRel = octree.IterateSearch((curNode = *octCur++), point)) != -1) {\n    if (nextNodeRel) {\n      /* Skip node data */\n      if (!(curNode & 0x60)) {\n        octCur += SBig(reinterpret_cast<const u16*>(octCur)[nextNodeRel - 1]);\n      } else if (curNode & 0x20) {\n        octCur += *(octCur + nextNodeRel - 1);\n      } else {\n        const u8* tmp = octCur + (nextNodeRel - 1) * 3;\n        octCur += (tmp[0] << 16) + (tmp[1] << 8) + tmp[2];\n      }\n    }\n\n    /* Skip children data */\n    if (!(curNode & 0x60)) {\n      octCur += (octree.GetNumChildren(curNode) - 1) * 2;\n    } else if (curNode & 0x20) {\n      octCur += octree.GetNumChildren(curNode) - 1;\n    } else {\n      octCur += (octree.GetNumChildren(curNode) - 1) * 3;\n    }\n  }\n\n  /* Handle leaf type */\n  switch (curNode & 0x18) {\n  case 0x18:\n    SetFromMemory(octree.GetNumObjects(), octree.GetNumLights(), octCur);\n    break;\n  case 0x10:\n    Reset(EPVSVisSetState::EndOfTree);\n    break;\n  case 0x08:\n  default:\n    Reset(EPVSVisSetState::OutOfBounds);\n    break;\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CPVSVisSet.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CPVSVisOctree;\n\nenum class EPVSVisSetState { EndOfTree, NodeFound, OutOfBounds };\n\nclass CPVSVisSet {\n  EPVSVisSetState x0_state;\n  u32 x4_numBits;\n  u32 x8_numLights;\n  // bool xc_; Used to be part of auto_ptr\n  const u8* x10_ptr;\n\npublic:\n  void Reset(EPVSVisSetState state);\n  EPVSVisSetState GetState() const { return x0_state; }\n  EPVSVisSetState GetVisible(u32 idx) const;\n  void SetFromMemory(u32 numBits, u32 numLights, const u8* leafPtr);\n  void SetTestPoint(const CPVSVisOctree& octree, const zeus::CVector3f&);\n  void SetState(EPVSVisSetState state) { x0_state = state; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CRainSplashGenerator.cpp",
    "content": "#include \"Runtime/Graphics/CRainSplashGenerator.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\nnamespace metaforce {\n\nCRainSplashGenerator::CRainSplashGenerator(const zeus::CVector3f& scale, u32 maxSplashes, u32 genRate, float minZ,\n                                           float alpha)\n: x14_scale(scale), x2c_minZ(minZ) {\n  x30_alpha = std::min(1.f, alpha);\n  x44_genRate = std::min(maxSplashes, genRate);\n  x0_rainSplashes.reserve(maxSplashes);\n  for (u32 i = 0; i < maxSplashes; ++i)\n    x0_rainSplashes.emplace_back();\n}\n\nvoid CRainSplashGenerator::SSplashLine::Draw(float alpha, float dt, const zeus::CVector3f& pos) {\n  if (x0_t > 0.f) {\n    float delta = dt * xc_speed;\n    float vt = std::max(0.f, x0_t - delta * x15_length);\n    auto vertCount = u32((x0_t - vt) / delta + 1.f);\n    // m_renderer.Reset();\n    for (u32 i = 0; i < vertCount; ++i) {\n      float vertAlpha = vt * alpha;\n      zeus::CVector3f vec(vt * x4_xEnd, vt * x8_yEnd, -4.f * vt * (vt - 1.f) * x10_zParabolaHeight);\n      vec += pos;\n      vt += delta;\n      // m_renderer.AddVertex(vec, zeus::CColor(1.f, vertAlpha), 1);\n    }\n    // m_renderer.Render(g_Renderer->IsThermalVisorHotPass());\n  }\n}\n\nvoid CRainSplashGenerator::SRainSplash::Draw(float alpha, float dt, const zeus::CVector3f& pos) {\n  for (SSplashLine& line : x0_lines) {\n    line.Draw(alpha, dt, pos);\n  }\n}\n\nvoid CRainSplashGenerator::DoDraw(const zeus::CTransform& xf) {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CRainSplashGenerator::DoDraw\", zeus::skYellow);\n  CGraphics::SetModelMatrix(xf);\n  if (x40_queueSize > 0) {\n    if (x38_queueTail <= x3c_queueHead) {\n      for (size_t i = x3c_queueHead; i < x0_rainSplashes.size(); ++i) {\n        SRainSplash& splash = x0_rainSplashes[i];\n        splash.Draw(x30_alpha, x28_dt, splash.x64_pos);\n      }\n      for (size_t i = 0; i < x38_queueTail; ++i) {\n        SRainSplash& splash = x0_rainSplashes[i];\n        splash.Draw(x30_alpha, x28_dt, splash.x64_pos);\n      }\n    } else {\n      for (size_t i = x3c_queueHead; i < x38_queueTail; ++i) {\n        SRainSplash& splash = x0_rainSplashes[i];\n        splash.Draw(x30_alpha, x28_dt, splash.x64_pos);\n      }\n    }\n  }\n}\n\nvoid CRainSplashGenerator::Draw(const zeus::CTransform& xf) {\n  if (!x48_25_raining) {\n    return;\n  }\n\n  DoDraw(xf);\n}\n\nCRainSplashGenerator::SSplashLine::SSplashLine() {}\n\nCRainSplashGenerator::SRainSplash::SRainSplash() {\n  for (size_t i = 0; i < x0_lines.capacity(); ++i) {\n    x0_lines.emplace_back();\n  }\n}\n\nvoid CRainSplashGenerator::SSplashLine::Update(float dt, CStateManager& mgr) {\n  if (!x16_active)\n    return;\n  if (x0_t <= 0.8f) {\n    x14_ = u8(5.f * (1.f - x0_t) + 3.f * x0_t);\n    x0_t += dt * xc_speed;\n  } else if (x15_length != 0) {\n    x15_length -= 1;\n  } else {\n    x16_active = false;\n    xc_speed = mgr.GetActiveRandom()->Range(4.0f, 8.0f);\n    x10_zParabolaHeight = mgr.GetActiveRandom()->Range(0.015625f, 0.03125f);\n    x4_xEnd = mgr.GetActiveRandom()->Range(-0.125f, 0.125f);\n    x8_yEnd = mgr.GetActiveRandom()->Range(-0.125f, 0.125f);\n    x15_length = u8(mgr.GetActiveRandom()->Range(1, 2));\n  }\n}\n\nvoid CRainSplashGenerator::SRainSplash::Update(float dt, CStateManager& mgr) {\n  for (SSplashLine& point : x0_lines)\n    point.Update(dt, mgr);\n}\n\nbool CRainSplashGenerator::SRainSplash::IsActive() const {\n  bool ret = false;\n  for (const SSplashLine& line : x0_lines)\n    ret |= line.x16_active;\n  return ret;\n}\n\nvoid CRainSplashGenerator::UpdateRainSplashRange(CStateManager& mgr, int start, int end, float dt) {\n  for (int i = start; i < end; ++i) {\n    SRainSplash& set = x0_rainSplashes[i];\n    set.Update(dt, mgr);\n    if (!set.IsActive()) {\n      x40_queueSize -= 1;\n      x3c_queueHead += 1;\n      if (x3c_queueHead >= x0_rainSplashes.size())\n        x3c_queueHead = 0;\n    }\n  }\n}\n\nvoid CRainSplashGenerator::UpdateRainSplashes(CStateManager& mgr, float magnitude, float dt) {\n  x20_generateTimer += dt;\n  x24_generateInterval = 1.f / (70.f * magnitude);\n  if (x40_queueSize > 0) {\n    if (x38_queueTail <= x3c_queueHead) {\n      UpdateRainSplashRange(mgr, x3c_queueHead, int(x0_rainSplashes.size()), dt);\n      UpdateRainSplashRange(mgr, 0, x38_queueTail, dt);\n    } else {\n      UpdateRainSplashRange(mgr, x3c_queueHead, x38_queueTail, dt);\n    }\n  }\n}\n\nvoid CRainSplashGenerator::Update(float dt, CStateManager& mgr) {\n  EEnvFxType neededFx = mgr.GetWorld()->GetNeededEnvFx();\n  x28_dt = dt;\n  x48_25_raining = false;\n  if (neededFx != EEnvFxType::None && mgr.GetEnvFxManager()->IsSplashActive() &&\n      mgr.GetEnvFxManager()->GetRainMagnitude() != 0.f && neededFx == EEnvFxType::Rain) {\n    UpdateRainSplashes(mgr, mgr.GetEnvFxManager()->GetRainMagnitude(), dt);\n    x48_25_raining = true;\n  }\n}\n\nu32 CRainSplashGenerator::GetNextBestPt(u32 pt, const SSkinningWorkspace& workspace, CRandom16& rand, float minZ) {\n  const auto& refVertA = workspace.m_vertexWorkspace[pt];\n  const zeus::CVector3f refVert{refVertA.x, refVertA.y, refVertA.z};\n  float maxDist = 0.f;\n  u32 nextPt = pt;\n  for (int i = 0; i < 3; ++i) {\n    const auto idx = u32(rand.Range(0, int(workspace.m_vertexWorkspace.size() - 1)));\n    const auto& vertA = workspace.m_vertexWorkspace[idx];\n    const zeus::CVector3f vert{vertA.x, vertA.y, vertA.z};\n    const auto& normA = workspace.m_normalWorkspace[idx];\n    const zeus::CVector3f norm{normA.x, normA.y, normA.z};\n    const float distSq = (refVert - vert).magSquared();\n    if (distSq > maxDist && norm.dot(zeus::skUp) >= 0.f && (vert.z() <= 0.f || vert.z() > minZ)) {\n      nextPt = idx;\n      maxDist = distSq;\n    }\n  }\n  return nextPt;\n}\n\nvoid CRainSplashGenerator::SRainSplash::SetPoint(const zeus::CVector3f& pos) {\n  for (SSplashLine& line : x0_lines)\n    line.SetActive();\n  x64_pos = pos;\n}\n\nvoid CRainSplashGenerator::AddPoint(const zeus::CVector3f& pos) {\n  if (x38_queueTail >= x0_rainSplashes.size())\n    x38_queueTail = 0;\n  x0_rainSplashes[x38_queueTail].SetPoint(pos);\n  x40_queueSize += 1;\n  x38_queueTail += 1;\n}\n\nvoid CRainSplashGenerator::GeneratePoints(const SSkinningWorkspace& workspace) {\n  if (!x48_25_raining)\n    return;\n\n  if (x20_generateTimer > x24_generateInterval) {\n    for (u32 i = 0; i < x44_genRate; ++i) {\n      if (x40_queueSize >= x0_rainSplashes.size())\n        break;\n      x34_curPoint = GetNextBestPt(x34_curPoint, workspace, x10_random, x2c_minZ);\n      const auto& vert = workspace.m_vertexWorkspace[x34_curPoint];\n      AddPoint(x14_scale * zeus::CVector3f{vert.x, vert.y, vert.z});\n    }\n    x20_generateTimer = 0.f;\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CRainSplashGenerator.hpp",
    "content": "#pragma once\n\n#include <utility>\n#include <vector>\n\n#include \"Runtime/CRandom16.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Graphics/CSkinnedModel.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CStateManager;\n\nclass CRainSplashGenerator {\n  struct SSplashLine {\n    float x0_t = 0.f;\n    float x4_xEnd = 0.f;\n    float x8_yEnd = 0.f;\n    float xc_speed = 4.f;\n    float x10_zParabolaHeight = 0.015625f;\n    u8 x14_ = 3;\n    u8 x15_length = 1;\n    bool x16_active = true; // used to be one-bit bitfield\n\n    explicit SSplashLine();\n    void Update(float dt, CStateManager& mgr);\n    void Draw(float alpha, float dt, const zeus::CVector3f& pos);\n    void SetActive() { x16_active = true; }\n  };\n  struct SRainSplash {\n    rstl::reserved_vector<SSplashLine, 4> x0_lines;\n    zeus::CVector3f x64_pos;\n    float x70_ = 0.f;\n    explicit SRainSplash();\n    SRainSplash(const SRainSplash&) = delete;\n    SRainSplash& operator=(const SRainSplash&) = delete;\n    SRainSplash(SRainSplash&&) = default;\n    SRainSplash& operator=(SRainSplash&&) = default;\n    void Update(float dt, CStateManager& mgr);\n    bool IsActive() const;\n    void Draw(float alpha, float dt, const zeus::CVector3f& pos);\n    void SetPoint(const zeus::CVector3f& pos);\n  };\n  std::vector<SRainSplash> x0_rainSplashes;\n  CRandom16 x10_random{99};\n  zeus::CVector3f x14_scale;\n  float x20_generateTimer = 0.0f;\n  float x24_generateInterval = 0.0f;\n  float x28_dt = 0.0f;\n  float x2c_minZ;\n  float x30_alpha;\n  u32 x34_curPoint = 0;\n  u32 x38_queueTail = 0;\n  u32 x3c_queueHead = 0;\n  u32 x40_queueSize = 0;\n  u32 x44_genRate;\n  bool x48_24 : 1 = false;\n  bool x48_25_raining : 1 = true;\n  void UpdateRainSplashRange(CStateManager& mgr, int start, int end, float dt);\n  void UpdateRainSplashes(CStateManager& mgr, float magnitude, float dt);\n  void DoDraw(const zeus::CTransform& xf);\n  static u32 GetNextBestPt(u32 pt, const SSkinningWorkspace& workspace, CRandom16& rand, float minZ);\n  void AddPoint(const zeus::CVector3f& pos);\n\npublic:\n  CRainSplashGenerator(const zeus::CVector3f& scale, u32 maxSplashes, u32 genRate, float minZ, float alpha);\n  void Update(float dt, CStateManager& mgr);\n  void GeneratePoints(const SSkinningWorkspace& workspace);\n  void Draw(const zeus::CTransform& xf);\n  bool IsRaining() const { return x48_25_raining; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CSimpleShadow.cpp",
    "content": "#include \"Runtime/Graphics/CSimpleShadow.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Collision/CGameCollision.hpp\"\n\nnamespace metaforce {\n\nCSimpleShadow::CSimpleShadow(float scale, float userAlpha, float maxObjHeight, float displacement)\n: x30_scale(scale), x38_userAlpha(userAlpha), x40_maxObjHeight(maxObjHeight), x44_displacement(displacement) {}\n\nzeus::CAABox CSimpleShadow::GetMaxShadowBox(const zeus::CAABox& aabb) const {\n  float extent = x34_radius * x30_scale;\n  zeus::CVector3f center = aabb.center();\n  zeus::CAABox expandedAABB = aabb;\n  expandedAABB.accumulateBounds({center.x() + extent, center.y() + extent, center.z() - GetMaxObjectHeight()});\n  expandedAABB.accumulateBounds({center.x() - extent, center.y() - extent, center.z() - GetMaxObjectHeight()});\n  return expandedAABB;\n}\n\nzeus::CAABox CSimpleShadow::GetBounds() const {\n  float extent = x34_radius * x30_scale;\n  return {\n      {x0_xf.origin.x() - extent, x0_xf.origin.y() - extent, x0_xf.origin.z() - extent},\n      {x0_xf.origin.x() + extent, x0_xf.origin.y() + extent, x0_xf.origin.z() + extent},\n  };\n}\n\nvoid CSimpleShadow::Render(TLockedToken<CTexture>& tex) {\n  if (!x48_24_collision)\n    return;\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CSimpleShadow::Render\", zeus::skGrey);\n\n  CGraphics::DisableAllLights();\n  CGraphics::SetModelMatrix(x0_xf);\n  tex->Load(GX_TEXMAP0, EClampMode::Repeat);\n\n  CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvModulate);\n  CGraphics::SetTevOp(ERglTevStage::Stage1, CTevCombiners::kEnvPassthru);\n  CGraphics::SetAlphaCompare(ERglAlphaFunc::Always, 0, ERglAlphaOp::And, ERglAlphaFunc::Always, 0);\n  CGraphics::SetDepthWriteMode(true, ERglEnum::LEqual, false);\n  CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::InvSrcAlpha,\n                          ERglLogicOp::Clear);\n  CGraphics::StreamBegin(ERglPrimitive::Quads);\n  float radius = x34_radius * x30_scale;\n  CGraphics::StreamColor(zeus::CColor{1.f, x3c_heightAlpha * x38_userAlpha});\n  CGraphics::StreamTexcoord(0.f, 0.f);\n  CGraphics::StreamVertex(-radius, 0.f, -radius);\n  CGraphics::StreamTexcoord(0.f, 1.f);\n  CGraphics::StreamVertex(radius, 0.f, -radius);\n  CGraphics::StreamTexcoord(1.f, 1.f);\n  CGraphics::StreamVertex(radius, 0.f, radius);\n  CGraphics::StreamTexcoord(1.f, 0.f);\n  CGraphics::StreamVertex(-radius, 0.f, radius);\n  CGraphics::StreamEnd();\n}\n\nvoid CSimpleShadow::Calculate(const zeus::CAABox& aabb, const zeus::CTransform& xf, const CStateManager& mgr) {\n  x48_24_collision = false;\n  float halfHeight = (aabb.max.z() - aabb.min.z()) * 0.5f;\n  zeus::CVector3f pos = xf.origin + zeus::CVector3f(0.f, 0.f, halfHeight);\n  CRayCastResult res = mgr.RayStaticIntersection(pos, zeus::skDown, x40_maxObjHeight,\n                                                 CMaterialFilter::MakeExclude({EMaterialTypes::SeeThrough}));\n  float height = x40_maxObjHeight;\n  if (res.IsValid()) {\n    x48_24_collision = true;\n    height = res.GetT();\n  }\n\n  if (height > 0.1f + halfHeight) {\n    EntityList nearList;\n    mgr.BuildNearList(nearList, pos, zeus::skDown, x40_maxObjHeight, CMaterialFilter::MakeInclude(CMaterialList(EMaterialTypes::Floor)), nullptr);\n    \n    TUniqueId cid = kInvalidUniqueId;\n    CRayCastResult resD = CGameCollision::RayDynamicIntersection(mgr, cid, pos, zeus::skDown, x40_maxObjHeight,\n                                                                 CMaterialFilter::skPassEverything, nearList);\n    if (resD.IsValid() && resD.GetT() < height) {\n      x48_24_collision = true;\n      height = resD.GetT();\n      res = resD;\n    }\n  }\n\n  if (x48_24_collision) {\n    x3c_heightAlpha = 1.f - height / x40_maxObjHeight;\n    x0_xf = zeus::lookAt(res.GetPlane().normal(), zeus::skZero3f);\n    x0_xf.origin = res.GetPlane().normal() * x44_displacement + res.GetPoint();\n    if (x48_25_alwaysCalculateRadius || !x48_26_radiusCalculated) {\n      float xExtent = aabb.max.x() - aabb.min.x();\n      float yExtent = aabb.max.y() - aabb.min.y();\n      x34_radius = std::sqrt(xExtent * xExtent + yExtent * yExtent) * 0.5f;\n      x48_26_radiusCalculated = true;\n    }\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CSimpleShadow.hpp",
    "content": "#pragma once\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/Graphics/CTexture.hpp\"\n\n#include <optional>\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CTransform.hpp>\n\nnamespace metaforce {\nclass CTexture;\nclass CStateManager;\n\nclass CSimpleShadow {\n  zeus::CTransform x0_xf;\n  float x30_scale;\n  float x34_radius = 1.f;\n  float x38_userAlpha;\n  float x3c_heightAlpha = 1.f;\n  float x40_maxObjHeight;\n  float x44_displacement;\n  bool x48_24_collision : 1 = false;\n  bool x48_25_alwaysCalculateRadius : 1 = true;\n  bool x48_26_radiusCalculated : 1 = false;\n\npublic:\n  CSimpleShadow(float scale, float userAlpha, float maxObjHeight, float displacement);\n  bool Valid() const { return x48_24_collision; }\n  zeus::CAABox GetMaxShadowBox(const zeus::CAABox& aabb) const;\n  zeus::CAABox GetBounds() const;\n  void SetAlwaysCalculateRadius(bool b) { x48_25_alwaysCalculateRadius = b; }\n  float GetMaxObjectHeight() const { return x40_maxObjHeight; }\n  void SetUserAlpha(float a) { x38_userAlpha = a; }\n  const zeus::CTransform& GetTransform() const { return x0_xf; }\n  void Render(TLockedToken<CTexture>& tex);\n  void Calculate(const zeus::CAABox& aabb, const zeus::CTransform& xf, const CStateManager& mgr);\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CSkinnedModel.cpp",
    "content": "#include \"Runtime/Graphics/CSkinnedModel.hpp\"\n\n#include \"Runtime/Character/CSkinRules.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Graphics/CVertexMorphEffect.hpp\"\n#include \"Runtime/Logging.hpp\"\n\n#include <list>\n\nnamespace metaforce {\nCSkinnedModel::CSkinnedModel(const TLockedToken<CModel>& model, const TLockedToken<CSkinRules>& skinRules,\n                             const TLockedToken<CCharLayoutInfo>& layoutInfo)\n: x4_model(std::move(model))\n, x10_skinRules(std::move(skinRules))\n, x1c_layoutInfo(std::move(layoutInfo))\n, m_workspace(*x10_skinRules) {\n  if (!x4_model) {\n    spdlog::fatal(\"bad model token provided to CSkinnedModel\");\n  }\n  if (!x10_skinRules) {\n    spdlog::fatal(\"bad skin token provided to CSkinnedModel\");\n  }\n  if (!x1c_layoutInfo) {\n    spdlog::fatal(\"bad character layout token provided to CSkinnedModel\");\n  }\n}\n\nCSkinnedModel::CSkinnedModel(IObjectStore& store, CAssetId model, CAssetId skinRules, CAssetId layoutInfo)\n: CSkinnedModel(store.GetObj(SObjectTag{FOURCC('CMDL'), model}), store.GetObj(SObjectTag{FOURCC('CSKR'), skinRules}),\n                store.GetObj(SObjectTag{FOURCC('CINF'), layoutInfo})) {}\n\nvoid CSkinnedModel::AllocateStorage() {\n  if (x34_owned) {\n    m_workspace.Reset(*x10_skinRules);\n  }\n}\n\nvoid CSkinnedModel::Calculate(const CPoseAsTransforms& pose, CVertexMorphEffect* morphEffect,\n                              TConstVectorRef averagedNormals, SSkinningWorkspace* workspace) {\n  if (workspace == nullptr) {\n    if (x35_disableWorkspaces) {\n      x10_skinRules->BuildAccumulatedTransforms(pose, *x1c_layoutInfo);\n      return;\n    }\n    AllocateStorage();\n    workspace = &m_workspace;\n  } else {\n    workspace->Reset(*x10_skinRules);\n  }\n\n  x10_skinRules->BuildAccumulatedTransforms(pose, *x1c_layoutInfo);\n  x10_skinRules->BuildPoints(x4_model->GetPositions(), &workspace->m_vertexWorkspace);\n  x10_skinRules->BuildNormals(x4_model->GetNormals(), &workspace->m_normalWorkspace);\n\n  if (morphEffect) {\n    morphEffect->MorphVertices(*workspace, averagedNormals, x10_skinRules, pose, x10_skinRules->GetVertexCount());\n  }\n  if (g_PointGenFunc != nullptr) {\n    g_PointGenFunc(*workspace);\n  }\n}\n\nvoid CSkinnedModel::Draw(TConstVectorRef verts, TConstVectorRef norms, const CModelFlags& drawFlags) {\n  //OPTICK_EVENT();\n  x4_model->Draw(verts, norms, drawFlags);\n  // PostDrawFunc();\n}\n\nvoid CSkinnedModel::Draw(const CModelFlags& drawFlags) {\n  if (x35_disableWorkspaces) {\n    const auto mtx = CGraphics::mModelMatrix;\n    CGraphics::SetModelMatrix(mtx * x10_skinRules->x0_bones.front().x20_xf);\n    x4_model->Draw(drawFlags);\n    CGraphics::SetModelMatrix(mtx);\n  } else if (m_workspace.IsEmpty()) {\n    x4_model->Draw(drawFlags);\n  } else {\n    x4_model->Draw(m_workspace.m_vertexWorkspace, m_workspace.m_normalWorkspace, drawFlags);\n    // PostDrawFunc();\n  }\n}\n\nvoid CSkinnedModel::DoDrawCallback(const FCustomDraw& func) const {\n  if (x35_disableWorkspaces) {\n    const auto mtx = CGraphics::mModelMatrix;\n    CGraphics::SetModelMatrix(mtx * x10_skinRules->x0_bones.front().x20_xf);\n    func(x4_model->GetPositions(), x4_model->GetNormals());\n    CGraphics::SetModelMatrix(mtx);\n  } else if (m_workspace.IsEmpty()) {\n    func(x4_model->GetPositions(), x4_model->GetNormals());\n  } else {\n    func(m_workspace.m_vertexWorkspace, m_workspace.m_normalWorkspace);\n    // PostDrawFunc();\n  }\n}\n\nvoid CSkinnedModel::CalculateDefault() { m_workspace.Clear(); }\n\nSSkinningWorkspace CSkinnedModel::CloneWorkspace() { return m_workspace; }\n\nCSkinnedModelWithAvgNormals::CSkinnedModelWithAvgNormals(IObjectStore& store, CAssetId model, CAssetId skinRules,\n                                                         CAssetId layoutInfo)\n: CSkinnedModel(store, model, skinRules, layoutInfo) {\n  const auto vertexCount = GetSkinRules()->GetVertexCount();\n  const auto modelPositions = GetModel()->GetPositions();\n\n  x40_averagedNormals.resize(vertexCount);\n  std::vector<std::pair<zeus::CVector3f, std::list<u32>>> vertMap;\n  for (int vertIdx = 0; vertIdx < vertexCount; ++vertIdx) {\n    const auto curPosV = modelPositions[vertIdx];\n    const zeus::CVector3f curPos{curPosV.x, curPosV.y, curPosV.z};\n    if (std::find_if(vertMap.cbegin(), vertMap.cend(), [=](const auto& pair) { return pair.first.isEqu(curPos); }) ==\n        vertMap.cend()) {\n      auto& [_, list] = vertMap.emplace_back(curPos, std::list<u32>{});\n      for (int idx = vertIdx; idx < vertexCount; ++idx) {\n        // Originally uses ==, but adjusted to match above\n        const auto& mpv = modelPositions[idx];\n        const zeus::CVector3f mpz{mpv.x, mpv.y, mpv.z};\n        if (mpz.isEqu(curPos)) {\n          list.emplace_back(idx);\n        }\n      }\n    }\n  }\n\n  const auto& modelNormals = GetModel()->GetNormals();\n  for (const auto& [_, idxs] : vertMap) {\n    zeus::CVector3f averagedNormal;\n    for (const auto idx : idxs) {\n      const auto& mnv = modelNormals[idx];\n      averagedNormal += zeus::CVector3f{mnv.x, mnv.y, mnv.z};\n    }\n    averagedNormal.normalize();\n    for (const auto idx : idxs) {\n      x40_averagedNormals[idx] = {averagedNormal.x(), averagedNormal.y(), averagedNormal.z()};\n    }\n  }\n}\n\nFPointGenerator CSkinnedModel::g_PointGenFunc;\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CSkinnedModel.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <optional>\n#include <utility>\n#include <vector>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/Character/CSkinRules.hpp\"\n#include \"Runtime/Graphics/CModel.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CCharLayoutInfo;\nclass CModel;\nclass CPoseAsTransforms;\nclass CVertexMorphEffect;\nclass IObjectStore;\n\n// Originally vert + normal workspaces were allocated together, but here separated for ease of use\nstruct SSkinningWorkspace {\n  std::vector<aurora::Vec3<float>> m_vertexWorkspace;\n  std::vector<aurora::Vec3<float>> m_normalWorkspace;\n\n  SSkinningWorkspace(const CSkinRules& rules) { Reset(rules); }\n  void Reset(const CSkinRules& rules) {\n    m_vertexWorkspace.clear();\n    m_normalWorkspace.clear();\n    m_vertexWorkspace.reserve(rules.GetVertexCount());\n    m_normalWorkspace.reserve(rules.GetNormalCount());\n  }\n  void Clear() {\n    m_vertexWorkspace.clear();\n    m_normalWorkspace.clear();\n  }\n  [[nodiscard]] bool IsEmpty() const { return m_vertexWorkspace.empty() || m_normalWorkspace.empty(); }\n};\n\n// Lambda instead of userdata pointer\nusing FCustomDraw = std::function<void(TConstVectorRef positions, TConstVectorRef normals)>;\nusing FPointGenerator = std::function<void(const SSkinningWorkspace& workspace)>;\n\nclass CSkinnedModel {\n  TLockedToken<CModel> x4_model;\n  TLockedToken<CSkinRules> x10_skinRules;\n  TLockedToken<CCharLayoutInfo> x1c_layoutInfo;\n  // rstl::auto_ptr<float[]> x24_vertWorkspace;\n  // rstl::auto_ptr<float[]> x2c_normalWorkspace;\n  SSkinningWorkspace m_workspace;\n  bool x34_owned = true;\n  bool x35_disableWorkspaces = false;\n\npublic:\n  enum class EDataOwnership { Unowned, Owned };\n  CSkinnedModel(const TLockedToken<CModel>& model, const TLockedToken<CSkinRules>& skinRules,\n                const TLockedToken<CCharLayoutInfo>& layoutInfo /*, EDataOwnership ownership*/);\n  CSkinnedModel(IObjectStore& store, CAssetId model, CAssetId skinRules, CAssetId layoutInfo);\n  virtual ~CSkinnedModel() = default;\n\n  TLockedToken<CModel>& GetModel() { return x4_model; }\n  const TLockedToken<CModel>& GetModel() const { return x4_model; }\n  const TLockedToken<CSkinRules>& GetSkinRules() const { return x10_skinRules; }\n  void SetLayoutInfo(const TLockedToken<CCharLayoutInfo>& inf) { x1c_layoutInfo = inf; }\n  const TLockedToken<CCharLayoutInfo>& GetLayoutInfo() const { return x1c_layoutInfo; }\n\n  void AllocateStorage();\n  // Metaforce addition: Originally morphEffect is rstl::optional_object<CVertexMorphEffect>*\n  // This prevents constructing it as a reference to the held pointer in CPatterned, thus in\n  // retail it's copied in every invocation of RenderIceModelWithFlags.\n  void Calculate(const CPoseAsTransforms& pose, CVertexMorphEffect* morphEffect, TConstVectorRef averagedNormals,\n                 SSkinningWorkspace* workspace);\n  void Draw(TConstVectorRef verts, TConstVectorRef normals, const CModelFlags& drawFlags);\n  void Draw(const CModelFlags& drawFlags);\n  void DoDrawCallback(const FCustomDraw& func) const;\n  void CalculateDefault();\n  // Originally returns cloned vertex workspace, with arg for cloned normal workspace\n  SSkinningWorkspace CloneWorkspace();\n\n  static void SetPointGeneratorFunc(FPointGenerator func) { g_PointGenFunc = std::move(func); }\n  static void ClearPointGeneratorFunc() { g_PointGenFunc = nullptr; }\n  static FPointGenerator g_PointGenFunc;\n};\n\nclass CSkinnedModelWithAvgNormals : public CSkinnedModel {\n  std::vector<aurora::Vec3<float>> x40_averagedNormals; // was rstl::auto_ptr<float[]>\n\npublic:\n  CSkinnedModelWithAvgNormals(IObjectStore& store, CAssetId model, CAssetId skinRules, CAssetId layoutInfo);\n  ~CSkinnedModelWithAvgNormals() override = default;\n\n  TConstVectorRef GetAveragedNormals() const { return x40_averagedNormals; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CTevCombiners.cpp",
    "content": "#include \"Graphics/CTevCombiners.hpp\"\n\n#include \"Graphics/CGX.hpp\"\n\nnamespace metaforce::CTevCombiners {\nu32 CTevPass::sNextUniquePass = 0;\n\nvoid CTevPass::Execute(ERglTevStage stage) const {\n  const auto stageId = GXTevStageID(stage);\n  CGX::SetTevColorIn(stageId, x4_colorPass.x0_a, x4_colorPass.x4_b, x4_colorPass.x8_c, x4_colorPass.xc_d);\n  CGX::SetTevAlphaIn(stageId, x14_alphaPass.x0_a, x14_alphaPass.x4_b, x14_alphaPass.x8_c, x14_alphaPass.xc_d);\n  CGX::SetTevColorOp(stageId, x24_colorOp.x4_op, x24_colorOp.x8_bias, x24_colorOp.xc_scale, x24_colorOp.x0_clamp,\n                     x24_colorOp.x10_regId);\n  CGX::SetTevAlphaOp(stageId, x38_alphaOp.x4_op, x38_alphaOp.x8_bias, x38_alphaOp.xc_scale, x38_alphaOp.x0_clamp,\n                     x38_alphaOp.x10_regId);\n  CGX::SetTevKColorSel(stageId, GX_TEV_KCSEL_8_8);\n  CGX::SetTevKAlphaSel(stageId, GX_TEV_KASEL_8_8);\n}\n\nconstexpr u32 maxTevPasses = 2;\nstatic u32 sNumEnabledPasses;\nstatic std::array<bool, maxTevPasses> sValidPasses;\n\nconst CTevPass kEnvPassthru{\n    {GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_RASC},\n    {GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_RASA},\n};\nconst CTevPass kEnvBlendCTandCConCF{\n    {GX_CC_C0, GX_CC_TEXC, GX_CC_RASC, GX_CC_ZERO},\n    {GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_RASA},\n};\nconst CTevPass kEnvModulateConstColor{\n    {GX_CC_ZERO, GX_CC_RASC, GX_CC_C0, GX_CC_ZERO},\n    {GX_CA_ZERO, GX_CA_RASA, GX_CA_A0, GX_CA_ZERO},\n};\nconst CTevPass kEnvConstColor{\n    {GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_C0},\n    {GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_A0},\n};\nconst CTevPass kEnvModulate{\n    {GX_CC_ZERO, GX_CC_RASC, GX_CC_TEXC, GX_CC_ZERO},\n    {GX_CA_ZERO, GX_CA_RASA, GX_CA_TEXA, GX_CA_ZERO},\n};\nconst CTevPass kEnvDecal{\n    {GX_CC_RASC, GX_CC_TEXC, GX_CC_TEXA, GX_CC_ZERO},\n    {GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_RASA},\n};\nconst CTevPass kEnvBlend{\n    {GX_CC_RASC, GX_CC_ONE, GX_CC_TEXC, GX_CC_ZERO},\n    {GX_CA_ZERO, GX_CA_TEXA, GX_CA_RASA, GX_CA_ZERO},\n};\nconst CTevPass kEnvReplace{\n    {GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_TEXC},\n    {GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_TEXA},\n};\nconst CTevPass kEnvModulateAlpha{\n    {GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_RASC},\n    {GX_CA_ZERO, GX_CA_TEXA, GX_CA_RASA, GX_CA_ZERO},\n};\nconst CTevPass kEnvModulateColor{\n    {GX_CC_ZERO, GX_CC_TEXC, GX_CC_RASC, GX_CC_ZERO},\n    {GX_CA_ZERO, GX_CA_KONST, GX_CA_RASA, GX_CA_ZERO},\n};\nconst CTevPass kEnvModulateColorByAlpha{\n    {GX_CC_ZERO, GX_CC_CPREV, GX_CC_APREV, GX_CC_ZERO},\n    {GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_APREV},\n};\n\nvoid Init() {\n  sNumEnabledPasses = maxTevPasses;\n  sValidPasses.fill(true);\n  for (int i = 0; i < maxTevPasses; ++i) {\n    DeletePass(static_cast<ERglTevStage>(i));\n  }\n  sValidPasses.fill(false);\n  RecomputePasses();\n}\n\nvoid SetupPass(ERglTevStage stage, const CTevPass& pass) {\n  if (pass == kEnvPassthru) {\n    DeletePass(stage);\n    return;\n  }\n  if (SetPassCombiners(stage, pass)) {\n    sValidPasses[static_cast<size_t>(stage)] = true;\n    RecomputePasses();\n  }\n}\n\nvoid DeletePass(ERglTevStage stage) {\n  SetPassCombiners(stage, kEnvPassthru);\n  sValidPasses[static_cast<size_t>(stage)] = false;\n  RecomputePasses();\n}\n\nbool SetPassCombiners(ERglTevStage stage, const CTevPass& pass) {\n  pass.Execute(stage);\n  return true;\n}\n\nvoid RecomputePasses() {\n  u8 tmp = static_cast<u8>((sValidPasses[maxTevPasses - 1] != 0));\n  tmp++;\n  sNumEnabledPasses = tmp;\n  CGX::SetNumTevStages(tmp);\n}\n\nvoid ResetStates() {\n  sValidPasses.fill(false);\n  kEnvPassthru.Execute(ERglTevStage::Stage0);\n  sNumEnabledPasses = 1;\n  CGX::SetNumTevStages(1);\n}\n} // namespace metaforce::CTevCombiners\n"
  },
  {
    "path": "Runtime/Graphics/CTevCombiners.hpp",
    "content": "#pragma once\n\n#include \"Graphics/GX.hpp\"\n#include \"RetroTypes.hpp\"\n\n#include <cstring>\n\nnamespace metaforce {\nenum class ERglTevStage : std::underlying_type_t<GXTevStageID> {\n  Stage0 = GX_TEVSTAGE0,\n  Stage1 = GX_TEVSTAGE1,\n  Stage2 = GX_TEVSTAGE2,\n  Stage3 = GX_TEVSTAGE3,\n  Stage4 = GX_TEVSTAGE4,\n  Stage5 = GX_TEVSTAGE5,\n  Stage6 = GX_TEVSTAGE6,\n  Stage7 = GX_TEVSTAGE7,\n  Stage8 = GX_TEVSTAGE8,\n  Stage9 = GX_TEVSTAGE9,\n  Stage10 = GX_TEVSTAGE10,\n  Stage11 = GX_TEVSTAGE11,\n  Stage12 = GX_TEVSTAGE12,\n  Stage13 = GX_TEVSTAGE13,\n  Stage14 = GX_TEVSTAGE14,\n  Stage15 = GX_TEVSTAGE15,\n  Max = GX_MAX_TEVSTAGE,\n};\n\nnamespace CTevCombiners {\nstruct CTevOp {\n  bool x0_clamp = true;\n  GXTevOp x4_op = GX_TEV_ADD;\n  GXTevBias x8_bias = GX_TB_ZERO;\n  GXTevScale xc_scale = GX_CS_SCALE_1;\n  GXTevRegID x10_regId = GX_TEVPREV;\n\n  constexpr CTevOp() = default;\n  constexpr CTevOp(bool clamp, GXTevOp op, GXTevBias bias, GXTevScale scale, GXTevRegID regId)\n  : x0_clamp(clamp), x4_op(op), x8_bias(bias), xc_scale(scale), x10_regId(regId) {}\n  constexpr CTevOp(u32 compressedDesc)\n  : x0_clamp((compressedDesc >> 8 & 1) != 0)\n  , x4_op(static_cast<GXTevOp>(compressedDesc & 0xF))\n  , x8_bias(static_cast<GXTevBias>(compressedDesc >> 4 & 3))\n  , xc_scale(static_cast<GXTevScale>(compressedDesc >> 6 & 3))\n  , x10_regId(static_cast<GXTevRegID>(compressedDesc >> 9 & 3)) {}\n\n  bool operator==(const CTevOp& rhs) const {\n    return x0_clamp == rhs.x0_clamp && x4_op == rhs.x4_op && x8_bias == rhs.x8_bias && xc_scale == rhs.xc_scale;\n  }\n};\nstruct ColorPass {\n  GXTevColorArg x0_a;\n  GXTevColorArg x4_b;\n  GXTevColorArg x8_c;\n  GXTevColorArg xc_d;\n\n  constexpr ColorPass(GXTevColorArg a, GXTevColorArg b, GXTevColorArg c, GXTevColorArg d)\n  : x0_a(a), x4_b(b), x8_c(c), xc_d(d) {}\n  constexpr ColorPass(u32 compressedDesc)\n  : x0_a(static_cast<GXTevColorArg>(compressedDesc & 0x1F))\n  , x4_b(static_cast<GXTevColorArg>(compressedDesc >> 5 & 0x1F))\n  , x8_c(static_cast<GXTevColorArg>(compressedDesc >> 10 & 0x1F))\n  , xc_d(static_cast<GXTevColorArg>(compressedDesc >> 15 & 0x1F)) {}\n\n  bool operator==(const ColorPass& rhs) const { return memcmp(this, &rhs, sizeof(*this)) == 0; }\n};\nstruct AlphaPass {\n  GXTevAlphaArg x0_a;\n  GXTevAlphaArg x4_b;\n  GXTevAlphaArg x8_c;\n  GXTevAlphaArg xc_d;\n\n  constexpr AlphaPass(GXTevAlphaArg a, GXTevAlphaArg b, GXTevAlphaArg c, GXTevAlphaArg d)\n  : x0_a(a), x4_b(b), x8_c(c), xc_d(d) {}\n  constexpr AlphaPass(u32 compressedDesc)\n  : x0_a(static_cast<GXTevAlphaArg>(compressedDesc & 0x1F))\n  , x4_b(static_cast<GXTevAlphaArg>(compressedDesc >> 5 & 0x1F))\n  , x8_c(static_cast<GXTevAlphaArg>(compressedDesc >> 10 & 0x1F))\n  , xc_d(static_cast<GXTevAlphaArg>(compressedDesc >> 15 & 0x1F)) {}\n\n  bool operator==(const AlphaPass& rhs) const { return memcmp(this, &rhs, sizeof(*this)) == 0; }\n};\nclass CTevPass {\n  u32 x0_id;\n  ColorPass x4_colorPass;\n  AlphaPass x14_alphaPass;\n  CTevOp x24_colorOp;\n  CTevOp x38_alphaOp;\n\n  static u32 sNextUniquePass;\n\npublic:\n  CTevPass(const ColorPass& colPass, const AlphaPass& alphaPass, const CTevOp& colorOp = {}, const CTevOp& alphaOp = {})\n  : x0_id(++sNextUniquePass)\n  , x4_colorPass(colPass)\n  , x14_alphaPass(alphaPass)\n  , x24_colorOp(colorOp)\n  , x38_alphaOp(alphaOp) {}\n\n  void Execute(ERglTevStage stage) const;\n\n  bool operator==(const CTevPass& rhs) const {\n    return x0_id == rhs.x0_id && x4_colorPass == rhs.x4_colorPass && x14_alphaPass == rhs.x14_alphaPass &&\n           x24_colorOp == rhs.x24_colorOp && x38_alphaOp == rhs.x38_alphaOp;\n  }\n};\n\nextern const CTevPass kEnvPassthru;\n// TODO move below to CGraphics\nextern const CTevPass kEnvBlendCTandCConCF;\nextern const CTevPass kEnvModulateConstColor;\nextern const CTevPass kEnvConstColor;\nextern const CTevPass kEnvModulate;\nextern const CTevPass kEnvDecal;\nextern const CTevPass kEnvBlend;\nextern const CTevPass kEnvReplace;\nextern const CTevPass kEnvModulateAlpha;\nextern const CTevPass kEnvModulateColor;\nextern const CTevPass kEnvModulateColorByAlpha;\n\nvoid Init();\nvoid SetupPass(ERglTevStage stage, const CTevPass& pass);\nvoid DeletePass(ERglTevStage stage);\nbool SetPassCombiners(ERglTevStage stage, const CTevPass& pass);\nvoid RecomputePasses();\nvoid ResetStates();\n} // namespace CTevCombiners\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CTexture.cpp",
    "content": "#include \"Graphics/CTexture.hpp\"\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\n#include \"Runtime/CBasics.hpp\"\n\n#include <zeus/Math.hpp>\n#include <magic_enum.hpp>\n\nnamespace metaforce {\nstatic std::array<CTexture*, GX_MAX_TEXMAP> sLoadedTextures{};\n\nCTexture::CTexture(ETexelFormat fmt, u16 w, u16 h, s32 mips, std::string_view label)\n: x0_fmt(fmt)\n, x4_w(w)\n, x6_h(h)\n, x8_mips(mips)\n, x9_bitsPerPixel(TexelFormatBitsPerPixel(fmt))\n, x64_frameAllocated(sCurrentFrameCount)\n, m_label(fmt::format(\"{} ({})\", label, magic_enum::enum_name(fmt))) {\n  InitBitmapBuffers(fmt, w, h, mips);\n  InitTextureObjs();\n}\n\nCTexture::CTexture(CInputStream& in, std::string_view label, EAutoMipmap automip, EBlackKey blackKey)\n: x0_fmt(static_cast<ETexelFormat>(in.ReadLong()))\n, x4_w(in.ReadShort())\n, x6_h(in.ReadShort())\n, x8_mips(in.ReadLong())\n, x64_frameAllocated(sCurrentFrameCount)\n, m_label(fmt::format(\"{} ({})\", label, magic_enum::enum_name(x0_fmt))) {\n  bool hasPalette = (x0_fmt == ETexelFormat::C4 || x0_fmt == ETexelFormat::C8 || x0_fmt == ETexelFormat::C14X2);\n  if (hasPalette) {\n    x10_graphicsPalette = std::make_unique<CGraphicsPalette>(in);\n    xa_25_canLoadPalette = true;\n  }\n  x9_bitsPerPixel = TexelFormatBitsPerPixel(x0_fmt);\n  InitBitmapBuffers(x0_fmt, x4_w, x6_h, x8_mips);\n  u32 bufLen = 0;\n  if (x8_mips > 0) {\n    for (u32 i = 0; i < x8_mips; ++i) {\n      u32 curMip = i & 63;\n      const u32 width = ROUND_UP_4(x4_w >> curMip);\n      const u32 height = ROUND_UP_4(x6_h >> curMip);\n      bufLen += (width * height * x9_bitsPerPixel) / 8;\n    }\n  }\n\n  for (u32 i = 0, len = 0; i < bufLen; i += len) {\n    len = bufLen - i;\n    if (len > 256) {\n      len = 256;\n    }\n\n    auto image_ptr = /*x44_aramToken.GetMRAMSafe() */ x44_aramToken_x4_buff.get();\n    in.Get(image_ptr + i, len);\n    // DCFlushRangeNoSync(x44_aramToken_x4_buff.get() + i, ROUND_UP_32(len));\n  }\n\n  if (sMangleMips) {\n    for (u32 i = 1; i < x8_mips; ++i) {\n      MangleMipmap(i);\n    }\n  }\n\n  InitTextureObjs();\n}\n\nCTexture::~CTexture() = default;\n\nu8* CTexture::Lock() {\n  xa_24_locked = true;\n  return GetBitMapData(0);\n}\n\nvoid CTexture::UnLock() {\n  xa_24_locked = false;\n  CountMemory();\n  // DCFlushRange(x44_aramToken.GetMRAMSafe(), ROUND_UP_32(xc_memoryAllocated));\n\n  // Aurora change: track when texture data needs to be invalidated\n  m_needsTexObjDataLoad = true;\n}\n\nvoid CTexture::Load(GXTexMapID id, EClampMode clamp) {\n  if (sLoadedTextures[id] != this || xa_29_canLoadObj) {\n    auto* data = /*x44_aramToken.GetMRAMSafe() */ x44_aramToken_x4_buff.get();\n    CountMemory();\n    if (HasPalette()) {\n      x10_graphicsPalette->Load();\n      xa_25_canLoadPalette = false;\n    }\n    xa_29_canLoadObj = false;\n    if (x40_clampMode != clamp) {\n      x40_clampMode = !xa_26_isPowerOfTwo ? EClampMode::Clamp : clamp;\n      GXInitTexObjWrapMode(&x20_texObj, static_cast<GXTexWrapMode>(x40_clampMode),\n                           static_cast<GXTexWrapMode>(x40_clampMode));\n    }\n\n    // Aurora change: track when texture data needs to be invalidated\n    if (m_needsTexObjDataLoad) {\n      GXInitTexObjData(&x20_texObj, data);\n      m_needsTexObjDataLoad = false;\n    }\n    GXLoadTexObj(&x20_texObj, id);\n    sLoadedTextures[id] = this;\n    x64_frameAllocated = sCurrentFrameCount;\n  }\n}\n\nvoid CTexture::LoadMipLevel(s32 mip, GXTexMapID id, EClampMode clamp) {\n  auto* image_ptr = /*x44_aramToken.GetMRAMSafe() */ x44_aramToken_x4_buff.get();\n  u32 width = x4_w;\n  u32 height = x6_h;\n  u32 iVar15 = 0;\n  u32 offset = 0;\n  if (mip > 0) {\n    for (u32 i = 0; i < mip; ++i) {\n      offset += ROUND_UP_32((x9_bitsPerPixel * (ROUND_UP_4(width) * ROUND_UP_4(height))) / 8);\n      width /= 2;\n      height /= 2;\n    }\n  }\n\n  TGXTexObj texObj;\n  const auto wrap = static_cast<GXTexWrapMode>(clamp);\n  GXInitTexObj(&texObj, image_ptr + offset, width, height, static_cast<GXTexFmt>(x18_gxFormat), wrap, wrap, false);\n  GXInitTexObjLOD(&texObj, GX_LINEAR, GX_LINEAR, 0.f, 0.f, 0.f, false, false, GX_ANISO_1);\n  if (HasPalette()) {\n    x10_graphicsPalette->Load();\n    xa_25_canLoadPalette = false;\n  }\n  GXLoadTexObj(&texObj, id);\n  x64_frameAllocated = sCurrentFrameCount;\n  sLoadedTextures[id] = nullptr;\n}\n\nvoid CTexture::MakeSwappable() {\n  if (!xa_27_noSwap) {\n    return;\n  }\n\n  xa_27_noSwap = false;\n}\n\nconst u8* CTexture::GetConstBitMapData(s32 mip) const {\n  u32 buffOffset = 0;\n  if (x8_mips > mip) {\n    for (u32 i = 0; i < mip; ++i) {\n      buffOffset += (x9_bitsPerPixel >> 3) * (x4_w >> (i & 0x3f)) * (x6_h >> (i & 0x3f));\n    }\n  }\n  return x44_aramToken_x4_buff.get() + buffOffset; /* x44_aramToken.GetMRAMSafe() + buffOffset*/\n}\n\nu8* CTexture::GetBitMapData(s32 mip) const { return const_cast<u8*>(GetConstBitMapData(mip)); }\n\nvoid CTexture::InitBitmapBuffers(ETexelFormat fmt, u16 width, u16 height, s32 mips) {\n  switch (fmt) {\n  case ETexelFormat::I4:\n    x18_gxFormat = GX_TF_I4;\n    break;\n  case ETexelFormat::I8:\n    x18_gxFormat = GX_TF_I8;\n    break;\n  case ETexelFormat::IA4:\n    x18_gxFormat = GX_TF_IA4;\n    break;\n  case ETexelFormat::IA8:\n    x18_gxFormat = GX_TF_IA8;\n    break;\n  case ETexelFormat::C4:\n    x1c_gxCIFormat = GX_TF_C4;\n    break;\n  case ETexelFormat::C8:\n    x1c_gxCIFormat = GX_TF_C8;\n    break;\n  case ETexelFormat::C14X2:\n    x1c_gxCIFormat = GX_TF_C14X2;\n    break;\n  case ETexelFormat::RGB565:\n    x18_gxFormat = GX_TF_RGB565;\n    break;\n  case ETexelFormat::RGB5A3:\n    x18_gxFormat = GX_TF_RGB5A3;\n    break;\n  case ETexelFormat::RGBA8:\n    x18_gxFormat = GX_TF_RGBA8;\n    break;\n  case ETexelFormat::CMPR:\n    x18_gxFormat = GX_TF_CMPR;\n    break;\n  // Metaforce additions\n  case ETexelFormat::R8PC:\n    x18_gxFormat = GX_TF_R8_PC;\n    break;\n  case ETexelFormat::RGBA8PC:\n    x18_gxFormat = GX_TF_RGBA8_PC;\n    break;\n  default:\n    break;\n  }\n\n  u32 format = (x0_fmt == ETexelFormat::C4 || x0_fmt == ETexelFormat::C8 || x0_fmt == ETexelFormat::C14X2)\n                   ? u32(x1c_gxCIFormat)\n                   : x18_gxFormat;\n  xc_memoryAllocated = GXGetTexBufferSize(width, height, format, mips > 1, mips > 1 ? 11 : 0);\n  x44_aramToken_x4_buff = std::make_unique<u8[]>(xc_memoryAllocated);\n  /*x44_aramToken.PostConstruct(buf, xc_memoryAllocated, 1);*/\n  CountMemory();\n}\n\nvoid CTexture::InitTextureObjs() {\n  xa_26_isPowerOfTwo = zeus::floorPowerOfTwo(x4_w) == x4_w && zeus::floorPowerOfTwo(x6_h) == x6_h;\n\n  if (!xa_26_isPowerOfTwo) {\n    x40_clampMode = EClampMode::Clamp;\n  }\n\n  CountMemory();\n  if (IsCITexture()) {\n    GXInitTexObjCI(&x20_texObj, x44_aramToken_x4_buff.get(), x4_w, x6_h, x1c_gxCIFormat,\n                   static_cast<GXTexWrapMode>(x40_clampMode), static_cast<GXTexWrapMode>(x40_clampMode), x8_mips > 1,\n                   0);\n  } else {\n    GXInitTexObj(&x20_texObj, x44_aramToken_x4_buff.get(), x4_w, x6_h, static_cast<GXTexFmt>(x18_gxFormat),\n                 static_cast<GXTexWrapMode>(x40_clampMode), static_cast<GXTexWrapMode>(x40_clampMode), x8_mips > 1);\n    GXInitTexObjLOD(&x20_texObj, x8_mips > 1 ? GX_LIN_MIP_LIN : GX_LINEAR, GX_LINEAR, 0.f,\n                    static_cast<float>(x8_mips) - 1.f, 0.f, false, false, x8_mips > 1 ? GX_ANISO_4 : GX_ANISO_1);\n  }\n  xa_29_canLoadObj = true;\n}\n\nvoid CTexture::CountMemory() {\n  if (xa_28_counted) {\n    return;\n  }\n\n  xa_28_counted = true;\n  sTotalAllocatedMemory += xc_memoryAllocated;\n}\n\nvoid CTexture::UncountMemory() {\n  if (!xa_28_counted) {\n    return;\n  }\n\n  xa_28_counted = false;\n  sTotalAllocatedMemory -= xc_memoryAllocated;\n}\n\nvoid CTexture::MangleMipmap(u32 mip) {\n  if (mip >= x8_mips) {\n    return;\n  }\n\n  constexpr u32 colors[4] = {\n      0x000000FF,\n      0x0000FF00,\n      0x00FF0000,\n      0x0000FFFF,\n  };\n  const u32 color = colors[(mip - 1) & 3];\n  const u16 rgb565Color = ((color >> 3) & 0x001F) | // B\n                          ((color >> 5) & 0x07E0) | // G\n                          ((color >> 8) & 0xF800);  // R\n  const u16 rgb555Color = ((color >> 3) & 0x001F) | // B\n                          ((color >> 6) & 0x03E0) | // G\n                          ((color >> 9) & 0x7C00);  // R\n  const u16 rgb4Color = ((color >> 4) & 0x000F) |   // B\n                        ((color >> 8) & 0x00F0) |   // G\n                        ((color >> 12) & 0x0F00);   // R\n\n  int width = GetWidth();\n  int height = GetHeight();\n\n  int offset = 0;\n  for (int i = 0; i < mip; i++) {\n    offset += width * height;\n    width /= 2;\n    height /= 2;\n  }\n\n  switch (GetTexelFormat()) {\n  case ETexelFormat::RGB565: {\n    const auto ptr = reinterpret_cast<u16*>(x44_aramToken_x4_buff.get()); // mARAMToken.GetMRAMSafe());\n    for (int i = 0; i < width * height; ++i) {\n      ptr[i + offset] = rgb565Color;\n      CBasics::Swap2Bytes(reinterpret_cast<u8*>(&ptr[i + offset]));\n    }\n    break;\n  }\n  case ETexelFormat::CMPR: {\n    auto ptr = reinterpret_cast<u16*>(x44_aramToken_x4_buff.get()) + offset / 4;\n    for (int i = 0; i < width * height / 16; ++i, ptr += 4) {\n      ptr[0] = rgb565Color;\n      CBasics::Swap2Bytes(reinterpret_cast<u8*>(&ptr[0]));\n      ptr[1] = rgb565Color;\n      CBasics::Swap2Bytes(reinterpret_cast<u8*>(&ptr[1]));\n      ptr[2] = 0;\n      ptr[3] = 0;\n    }\n    break;\n  }\n  case ETexelFormat::RGB5A3: {\n    const auto ptr = reinterpret_cast<u16*>(x44_aramToken_x4_buff.get());\n    for (int i = 0; i < width * height; ++i) {\n      u16& val = ptr[i + offset];\n      CBasics::Swap2Bytes(reinterpret_cast<u8*>(&val));\n      if ((val & 0x8000) != 0) {\n        val = rgb555Color | 0x8000;\n      } else {\n        val = (val & 0xF000) | rgb4Color;\n      }\n      CBasics::Swap2Bytes(reinterpret_cast<u8*>(&val));\n    }\n    break;\n  }\n  default:\n    break;\n  }\n}\n\nu32 CTexture::TexelFormatBitsPerPixel(ETexelFormat fmt) {\n  switch (fmt) {\n  case ETexelFormat::I4:\n  case ETexelFormat::C4:\n  case ETexelFormat::CMPR:\n    return 4;\n  case ETexelFormat::I8:\n  case ETexelFormat::IA4:\n  case ETexelFormat::C8:\n    return 8;\n  case ETexelFormat::IA8:\n  case ETexelFormat::C14X2:\n  case ETexelFormat::RGB565:\n  case ETexelFormat::RGB5A3:\n    return 16;\n  case ETexelFormat::RGBA8:\n    return 32;\n  default:\n    return 0;\n  }\n}\n\nbool CTexture::sMangleMips = false;\nu32 CTexture::sCurrentFrameCount = 0;\nu32 CTexture::sTotalAllocatedMemory = 0;\n\nvoid CTexture::InvalidateTexMap(GXTexMapID id) { sLoadedTextures[id] = nullptr; }\n\nCFactoryFnReturn FTextureFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& vparms,\n                                 CObjectReference* selfRef) {\n  const auto label = fmt::format(\"{} {}\", tag.type, tag.id);\n  return TToken<CTexture>::GetIObjObjectFor(std::make_unique<CTexture>(in, label));\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CTexture.hpp",
    "content": "#pragma once\n\n#include \"Runtime/CFactoryMgr.hpp\"\n#include \"Runtime/Graphics/CGraphics.hpp\"\n#include \"Runtime/Graphics/CGraphicsPalette.hpp\"\n#include \"Runtime/Graphics/GX.hpp\"\n#include \"Runtime/IObj.hpp\"\n#include \"Runtime/Streams/CInputStream.hpp\"\n#include \"GX.hpp\"\n\nnamespace metaforce {\nenum class ETexelFormat {\n  Invalid = -1,\n  I4 = 0,\n  I8 = 1,\n  IA4 = 2,\n  IA8 = 3,\n  C4 = 4,\n  C8 = 5,\n  C14X2 = 6,\n  RGB565 = 7,\n  RGB5A3 = 8,\n  RGBA8 = 9,\n  CMPR = 10,\n  // Metaforce addition: non-converting formats\n  RGBA8PC = 11,\n  R8PC = 12,\n};\n\nenum class EClampMode : std::underlying_type_t<GXTexWrapMode> {\n  Clamp = GX_CLAMP,\n  Repeat = GX_REPEAT,\n  Mirror = GX_MIRROR,\n};\n\nclass CTexture {\n  class CDumpedBitmapDataReloader {\n    int x0_;\n    u32 x4_;\n    int x8_;\n    u32 xc_;\n    bool x10_;\n    int x14_;\n    void* x18_;\n  };\n\npublic:\n  enum class EAutoMipmap {\n    Zero,\n    One,\n  };\n\n  enum class EBlackKey { Zero, One };\n\n  enum class EFontType {\n    None = -1,\n    OneLayer = 0,        /* Fill bit0 */\n    OneLayerOutline = 1, /* Fill bit0, Outline bit1 */\n    FourLayers = 2,\n    TwoLayersOutlines = 3, /* Fill bit0/2, Outline bit1/3 */\n    TwoLayers = 4,         /* Fill bit0/1 and copied to bit2/3 */\n    TwoLayersOutlines2 = 8 /* Fill bit2/3, Outline bit0/1 */\n  };\n\nprivate:\n  static bool sMangleMips;\n  static u32 sCurrentFrameCount;\n  static u32 sTotalAllocatedMemory;\n\n  ETexelFormat x0_fmt = ETexelFormat::Invalid;\n  u16 x4_w = 0;\n  u16 x6_h = 0;\n  u8 x8_mips = 0;\n  u8 x9_bitsPerPixel = 0;\n  bool xa_24_locked : 1 = false;\n  bool xa_25_canLoadPalette : 1 = false;\n  bool xa_26_isPowerOfTwo : 1 = false;\n  bool xa_27_noSwap : 1 = true;\n  bool xa_28_counted : 1 = false;\n  bool xa_29_canLoadObj : 1 = false;\n  u32 xc_memoryAllocated = 0;\n  std::unique_ptr<CGraphicsPalette> x10_graphicsPalette;\n  std::unique_ptr<CDumpedBitmapDataReloader> x14_bitmapReloader;\n  u32 x18_gxFormat = GX_TF_RGB565;\n  GXCITexFmt x1c_gxCIFormat = GX_TF_C8;\n  TGXTexObj x20_texObj;\n  EClampMode x40_clampMode = EClampMode::Repeat;\n  std::unique_ptr<u8[]> x44_aramToken_x4_buff; // was CARAMToken\n  u32 x64_frameAllocated{};\n\n  // Metaforce additions\n  std::string m_label;\n  bool m_needsTexObjDataLoad = true;\n\n  void InitBitmapBuffers(ETexelFormat fmt, u16 width, u16 height, s32 mips);\n  void InitTextureObjs();\n  void CountMemory();\n  void UncountMemory();\n  void MangleMipmap(u32 mip);\n  static u32 TexelFormatBitsPerPixel(ETexelFormat fmt);\n\npublic:\n  // Label parameters are new for Metaforce\n  CTexture(ETexelFormat fmt, u16 w, u16 h, s32 mips, std::string_view label);\n  CTexture(CInputStream& in, std::string_view label, EAutoMipmap automip = EAutoMipmap::Zero,\n           EBlackKey blackKey = EBlackKey::Zero);\n  ~CTexture();\n\n  [[nodiscard]] ETexelFormat GetTexelFormat() const { return x0_fmt; }\n  [[nodiscard]] u16 GetWidth() const { return x4_w; }\n  [[nodiscard]] u16 GetHeight() const { return x6_h; }\n  [[nodiscard]] u8 GetNumberOfMipMaps() const { return x8_mips; }\n  [[nodiscard]] u32 GetBitDepth() const { return x9_bitsPerPixel; }\n  [[nodiscard]] u32 GetMemoryAllocated() const { return xc_memoryAllocated; }\n  [[nodiscard]] const std::unique_ptr<CGraphicsPalette>& GetPalette() const { return x10_graphicsPalette; }\n  [[nodiscard]] bool HasPalette() const { return x10_graphicsPalette != nullptr; }\n  [[nodiscard]] u8* Lock();\n  void UnLock();\n  void Load(GXTexMapID id, EClampMode clamp);\n  void LoadMipLevel(s32 mip, GXTexMapID id, EClampMode clamp);\n  // void UnloadBitmapData(u32) const;\n  // void TryReloadBitmapData(CResFactory&) const;\n  // void LoadToMRAM() const;\n  // void LoadToARAM() const;\n  // bool IsARAMTransferInProgress() const { return false; }\n  void MakeSwappable();\n\n  [[nodiscard]] const u8* GetConstBitMapData(s32 mip) const;\n  [[nodiscard]] u8* GetBitMapData(s32 mip) const;\n  [[nodiscard]] bool IsCITexture() const {\n    return x0_fmt == ETexelFormat::C4 || x0_fmt == ETexelFormat::C8 || x0_fmt == ETexelFormat::C14X2;\n  }\n\n  static void InvalidateTexMap(GXTexMapID id);\n  static void SetMangleMips(bool b) { sMangleMips = b; }\n  static void SetCurrentFrameCount(u32 frameCount) { sCurrentFrameCount = frameCount; }\n};\n\nCFactoryFnReturn FTextureFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& vparms,\n                                 CObjectReference* selfRef);\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CVertexMorphEffect.cpp",
    "content": "#include \"Runtime/Graphics/CVertexMorphEffect.hpp\"\n\n#include \"Runtime/Character/CSkinRules.hpp\"\n#include \"Runtime/Graphics/CSkinnedModel.hpp\"\n\nnamespace metaforce {\n\nCVertexMorphEffect::CVertexMorphEffect(const zeus::CUnitVector3f& dir, const zeus::CVector3f& pos, float duration,\n                                       float diagExtent, CRandom16& random)\n: x0_dir(dir), xc_pos(pos), x18_duration(duration), x20_diagExtent(diagExtent), x24_random(random) {}\n\nvoid CVertexMorphEffect::MorphVertices(SSkinningWorkspace& workspace, TConstVectorRef averagedNormals,\n                                       TLockedToken<CSkinRules>& skinRules, const CPoseAsTransforms& pose,\n                                       u32 vertexCount) {\n  if (x28_indices.empty()) {\n    std::vector<aurora::Vec3<float>> normalsOut;\n    normalsOut.reserve(vertexCount);\n    skinRules->BuildNormals(averagedNormals, &normalsOut);\n    for (int i = 0; i < vertexCount; ++i) {\n      const auto& nov = normalsOut[i];\n      const zeus::CVector3f noz{nov.x, nov.y, nov.z};\n      float dist = noz.dot(x0_dir);\n      if (dist > 0.5f) {\n        x28_indices.emplace_back(i);\n        const auto vert = workspace.m_vertexWorkspace[i];\n        const auto length = vert.x + vert.y + vert.z;\n        x38_floats.emplace_back((length - std::trunc(length)) * (dist - 0.5f));\n      }\n    }\n  }\n  for (int i = 0; i < x28_indices.size(); ++i) {\n    const auto scale = x1c_elapsed / x18_duration;\n    auto& out = workspace.m_vertexWorkspace[x28_indices[i]];\n    const auto add = scale * x20_diagExtent * x38_floats[i] * x0_dir;\n    out.x += add.x();\n    out.y += add.y();\n    out.z += add.z();\n  }\n}\n\nvoid CVertexMorphEffect::Reset(const zeus::CVector3f& dir, const zeus::CVector3f& pos, float duration) {\n  x0_dir = dir;\n  xc_pos = pos;\n  x18_duration = duration;\n  x1c_elapsed = 0.f;\n  x28_indices.clear();\n  x38_floats.clear();\n}\n\nvoid CVertexMorphEffect::Update(float dt) { x1c_elapsed = std::min(x1c_elapsed + dt, x18_duration); }\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/CVertexMorphEffect.hpp",
    "content": "#pragma once\n\n#include <utility>\n#include <vector>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/Character/CPoseAsTransforms.hpp\"\n#include \"Runtime/Graphics/CCubeModel.hpp\"\n\n#include <zeus/CUnitVector.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CRandom16;\nclass CSkinRules;\nstruct SSkinningWorkspace;\n\nclass CVertexMorphEffect {\n  zeus::CUnitVector3f x0_dir;\n  zeus::CVector3f xc_pos;\n  float x18_duration;\n  float x1c_elapsed = 0.f;\n  float x20_diagExtent;\n  CRandom16& x24_random;\n  std::vector<u32> x28_indices;\n  std::vector<float> x38_floats;\n\npublic:\n  CVertexMorphEffect(const zeus::CUnitVector3f& dir, const zeus::CVector3f& pos, float duration, float diagExtent,\n                     CRandom16& random);\n  void MorphVertices(SSkinningWorkspace& workspace, TConstVectorRef averagedNormals,\n                     TLockedToken<CSkinRules>& skinRules, const CPoseAsTransforms& pose, u32 vertexCount);\n  void Reset(const zeus::CVector3f& dir, const zeus::CVector3f& pos, float duration);\n  void Update(float dt);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/GX.hpp",
    "content": "#pragma once\n\n#include <dolphin/gx.h>\n\n#include <bitset>\n#include <cstring>\n\n#include <zeus/CColor.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace GX {\nconstexpr u8 MaxLights = 8;\nusing LightMask = std::bitset<MaxLights>;\n} // namespace GX\n\nconstexpr GXColor GX_BLACK{0, 0, 0, 255};\nconstexpr GXColor GX_WHITE{255, 255, 255, 255};\nconstexpr GXColor GX_CLEAR{0, 0, 0, 0};\n\ninline bool operator==(const GXColor& lhs, const GXColor& rhs) noexcept {\n  return lhs.r == rhs.r && lhs.g == rhs.g && lhs.b == rhs.b && lhs.a == rhs.a;\n}\ninline bool operator!=(const GXColor& lhs, const GXColor& rhs) noexcept { return !(lhs == rhs); }\n\nstatic inline void GXPosition3f32(const zeus::CVector3f& v) { GXPosition3f32(v.x(), v.y(), v.z()); }\nstatic inline void GXNormal3f32(const zeus::CVector3f& v) { GXNormal3f32(v.x(), v.y(), v.z()); }\nstatic inline void GXTexCoord2f32(const zeus::CVector2f& v) { GXTexCoord2f32(v.x(), v.y()); }\nstatic inline void GXColor4f32(const zeus::CColor& v) { GXColor4f32(v.r(), v.g(), v.b(), v.a()); }\n\nstatic inline GXColor to_gx_color(const zeus::CColor& color) {\n  return {\n      static_cast<u8>(color.r() * 255.f),\n      static_cast<u8>(color.g() * 255.f),\n      static_cast<u8>(color.b() * 255.f),\n      static_cast<u8>(color.a() * 255.f),\n  };\n}\nstatic inline zeus::CColor from_gx_color(GXColor color) {\n  return {\n      static_cast<float>(color.r) / 255.f,\n      static_cast<float>(color.g) / 255.f,\n      static_cast<float>(color.b) / 255.f,\n      static_cast<float>(color.a) / 255.f,\n  };\n}\n\nclass GXTexObjRAII : public GXTexObj {\npublic:\n  GXTexObjRAII() : GXTexObj() {}\n  ~GXTexObjRAII() { GXDestroyTexObj(this); }\n\n  void reset() { GXDestroyTexObj(this); }\n\n  GXTexObjRAII(const GXTexObjRAII&) = delete;\n  GXTexObjRAII& operator=(const GXTexObjRAII&) = delete;\n  GXTexObjRAII(GXTexObjRAII&& o) noexcept : GXTexObj(o) {\n    std::memset(static_cast<GXTexObj*>(&o), 0, sizeof(GXTexObj));\n  }\n  GXTexObjRAII& operator=(GXTexObjRAII&& o) noexcept {\n    if (this != &o) {\n      GXDestroyTexObj(this);\n      std::memcpy(static_cast<GXTexObj*>(this), &o, sizeof(GXTexObj));\n      std::memset(static_cast<GXTexObj*>(&o), 0, sizeof(GXTexObj));\n    }\n    return *this;\n  }\n};\nstatic_assert(sizeof(GXTexObjRAII) == sizeof(GXTexObj), \"GXTexObjRAII should have the same size as GXTexObj\");\nusing TGXTexObj = GXTexObjRAII;"
  },
  {
    "path": "Runtime/Graphics/IRenderer.hpp",
    "content": "#pragma once\n\n#include <functional>\n#include <vector>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/Graphics/CGraphics.hpp\"\n#include \"Runtime/Graphics/CModel.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CColor.hpp>\n#include <zeus/CFrustum.hpp>\n#include <zeus/CPlane.hpp>\n#include <zeus/CRectangle.hpp>\n\nnamespace metaforce {\nclass CAreaOctTree;\nclass CLight;\nclass CMetroidModelInstance;\nclass CPVSVisSet;\nclass CParticleGen;\nclass CSkinnedModel;\nstruct CAreaRenderOctTree;\nstruct CModelFlags;\nstruct SShader;\n\nclass IRenderer {\npublic:\n  using TDrawableCallback = void (*)(void*, void*, int);\n  using TReflectionCallback = std::function<void(void*, const zeus::CVector3f&)>;\n\n  enum class EDrawableSorting { SortedCallback, UnsortedCallback };\n  enum class EDebugOption { Invalid = -1, PVSMode, PVSState, FogDisabled };\n  enum class EPrimitiveType {\n    Triangles = GX_TRIANGLES,\n    TriangleFan = GX_TRIANGLEFAN,\n    TriangleStrip = GX_TRIANGLESTRIP,\n    Lines = GX_LINES,\n    LineStrip = GX_LINESTRIP,\n  };\n\n  virtual ~IRenderer() = default;\n  virtual void AddStaticGeometry(const std::vector<CMetroidModelInstance>* geometry, const CAreaRenderOctTree* octTree,\n                                 s32 areaIdx) = 0;\n  virtual void EnablePVS(const CPVSVisSet& set, u32 areaIdx) = 0;\n  virtual void DisablePVS() = 0;\n  virtual void RemoveStaticGeometry(const std::vector<CMetroidModelInstance>* geometry) = 0;\n  virtual void DrawUnsortedGeometry(s32 areaIdx, s32 mask, s32 targetMask) = 0;\n  virtual void DrawSortedGeometry(s32 areaIdx, s32 mask, s32 targetMask) = 0;\n  virtual void DrawStaticGeometry(s32 areaIdx, s32 mask, s32 targetMask) = 0;\n  virtual void DrawAreaGeometry(s32 areaIdx, s32 mask, s32 targetMask) = 0;\n  virtual void PostRenderFogs() = 0;\n  virtual void SetModelMatrix(const zeus::CTransform& xf) = 0;\n  virtual void AddParticleGen(CParticleGen& gen) = 0;\n  virtual void AddParticleGen(CParticleGen& gen, const zeus::CVector3f& pos, const zeus::CAABox& bounds) = 0;\n  virtual void AddPlaneObject(void* obj, const zeus::CAABox& aabb, const zeus::CPlane& plane, s32 type) = 0;\n  virtual void AddDrawable(void* obj, const zeus::CVector3f& pos, const zeus::CAABox& aabb, s32 mode,\n                           EDrawableSorting sorting) = 0;\n  virtual void SetDrawableCallback(TDrawableCallback cb, void* ctx) = 0;\n  virtual void SetWorldViewpoint(const zeus::CTransform& xf) = 0;\n  virtual void SetPerspective(float fovy, float width, float height, float znear, float zfar) = 0;\n  virtual void SetPerspective(float fovy, float aspect, float znear, float zfar) = 0;\n  virtual std::pair<zeus::CVector2f, zeus::CVector2f> SetViewportOrtho(bool centered, float znear, float zfar) = 0;\n  virtual void SetClippingPlanes(const zeus::CFrustum& frustum) = 0;\n  virtual void SetViewport(s32 left, s32 bottom, s32 width, s32 height) = 0;\n  virtual void SetDepthReadWrite(bool, bool) = 0;\n  virtual void SetBlendMode_AdditiveAlpha() = 0;\n  virtual void SetBlendMode_AlphaBlended() = 0;\n  virtual void SetBlendMode_NoColorWrite() = 0;\n  virtual void SetBlendMode_ColorMultiply() = 0;\n  virtual void SetBlendMode_InvertDst() = 0;\n  virtual void SetBlendMode_InvertSrc() = 0;\n  virtual void SetBlendMode_Replace() = 0;\n  virtual void SetBlendMode_AdditiveDestColor() = 0;\n  virtual void SetDebugOption(EDebugOption, s32) = 0;\n  virtual void BeginScene() = 0;\n  virtual void EndScene() = 0;\n  virtual void BeginPrimitive(EPrimitiveType, s32) = 0;\n  virtual void BeginLines(s32) = 0;\n  virtual void BeginLineStrip(s32) = 0;\n  virtual void BeginTriangles(s32) = 0;\n  virtual void BeginTriangleStrip(s32) = 0;\n  virtual void BeginTriangleFan(s32) = 0;\n  virtual void PrimVertex(const zeus::CVector3f&) = 0;\n  virtual void PrimNormal(const zeus::CVector3f&) = 0;\n  virtual void PrimColor(float, float, float, float) = 0;\n  virtual void PrimColor(const zeus::CColor&) = 0;\n  virtual void EndPrimitive() = 0;\n  virtual void SetAmbientColor(const zeus::CColor& color) = 0;\n  virtual void DrawString(const char* string, int, int) = 0;\n  virtual float GetFPS() = 0;\n  virtual void CacheReflection(TReflectionCallback cb, void* ctx, bool clearAfter) = 0;\n  virtual void DrawSpaceWarp(const zeus::CVector3f& pt, float strength) = 0;\n  virtual void DrawThermalModel(CModel& model, const zeus::CColor& multCol, const zeus::CColor& addCol,\n                                TConstVectorRef positions, TConstVectorRef normals, const CModelFlags& flags) = 0;\n  virtual void DrawModelDisintegrate(CModel& model, CTexture& tex, const zeus::CColor& color, TConstVectorRef positions,\n                                     TConstVectorRef normals, float t) = 0;\n  virtual void DrawModelFlat(CModel& model, const CModelFlags& flags, bool unsortedOnly, TConstVectorRef positions,\n                             TConstVectorRef normals) = 0;\n  virtual void SetWireframeFlags(s32 flags) = 0;\n  virtual void SetWorldFog(ERglFogMode mode, float startz, float endz, const zeus::CColor& color) = 0;\n  virtual void RenderFogVolume(const zeus::CColor& color, const zeus::CAABox& aabb, const TLockedToken<CModel>* model,\n                               const CSkinnedModel* sModel) = 0;\n  virtual void SetThermal(bool thermal, float level, const zeus::CColor& color) = 0;\n  virtual void SetThermalColdScale(float scale) = 0;\n  virtual void DoThermalBlendCold() = 0;\n  virtual void DoThermalBlendHot() = 0;\n  virtual u32 GetStaticWorldDataSize() = 0;\n  virtual void SetGXRegister1Color(const zeus::CColor& color) = 0;\n  virtual void SetWorldLightFadeLevel(float level) = 0;\n  // Something\n  virtual void PrepareDynamicLights(const std::vector<CLight>& lights) = 0;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/IWeaponRenderer.cpp",
    "content": "#include \"Runtime/Graphics/IWeaponRenderer.hpp\"\n\n#include \"Runtime/Particle/CParticleGen.hpp\"\n\nnamespace metaforce {\n\nvoid CDefaultWeaponRenderer::AddParticleGen(CParticleGen& gen) { gen.Render(); }\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Graphics/IWeaponRenderer.hpp",
    "content": "#pragma once\n\nnamespace metaforce {\nclass CParticleGen;\n\nclass IWeaponRenderer {\npublic:\n  virtual ~IWeaponRenderer() = default;\n  virtual void AddParticleGen(CParticleGen&) = 0;\n};\n\nclass CDefaultWeaponRenderer : public IWeaponRenderer {\npublic:\n  void AddParticleGen(CParticleGen&) override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CAuiEnergyBarT01.cpp",
    "content": "#include \"Runtime/GuiSys/CAuiEnergyBarT01.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/GuiSys/CGuiSys.hpp\"\n#include \"Runtime/GuiSys/CGuiWidgetDrawParms.hpp\"\n\nnamespace metaforce {\n\nCAuiEnergyBarT01::CAuiEnergyBarT01(const CGuiWidgetParms& parms, CSimplePool* sp, CAssetId txtrId)\n: CGuiWidget(parms), xb8_txtrId(txtrId) {\n  if (g_GuiSys->GetUsageMode() != CGuiSys::EUsageMode::Two) {\n    xbc_tex = sp->GetObj(SObjectTag{FOURCC('TXTR'), xb8_txtrId});\n  }\n}\n\nstd::pair<zeus::CVector3f, zeus::CVector3f> CAuiEnergyBarT01::DownloadBarCoordFunc(float t) {\n  const float x = 12.5f * t - 6.25f;\n  return {zeus::CVector3f{x, 0.f, -0.2f}, zeus::CVector3f{x, 0.f, 0.2f}};\n}\n\nvoid CAuiEnergyBarT01::Update(float dt) {\n  if (x100_shadowDrainDelayTimer > 0.f) {\n    x100_shadowDrainDelayTimer = std::max(x100_shadowDrainDelayTimer - dt, 0.f);\n  }\n\n  if (xf8_filledEnergy < xf4_setEnergy) {\n    if (xf1_wrapping) {\n      xf8_filledEnergy -= dt * xe4_filledSpeed;\n      if (xf8_filledEnergy < 0.f) {\n        xf8_filledEnergy = std::max(xf4_setEnergy, xf8_filledEnergy + xe0_maxEnergy);\n        xf1_wrapping = false;\n        xfc_shadowEnergy = xe0_maxEnergy;\n      }\n    } else {\n      xf8_filledEnergy = std::min(xf4_setEnergy, xf8_filledEnergy + dt * xe4_filledSpeed);\n    }\n  } else if (xf8_filledEnergy > xf4_setEnergy) {\n    if (xf1_wrapping) {\n      xf8_filledEnergy += dt * xe4_filledSpeed;\n      if (xf8_filledEnergy > xe0_maxEnergy) {\n        xf8_filledEnergy = std::min(xf4_setEnergy, xf8_filledEnergy - xe0_maxEnergy);\n        xf1_wrapping = false;\n        xfc_shadowEnergy = xf8_filledEnergy;\n      }\n    } else {\n      xf8_filledEnergy = std::max(xf4_setEnergy, xf8_filledEnergy - dt * xe4_filledSpeed);\n    }\n  }\n\n  if (xfc_shadowEnergy < xf8_filledEnergy) {\n    xfc_shadowEnergy = xf8_filledEnergy;\n  } else if (xfc_shadowEnergy > xf8_filledEnergy && x100_shadowDrainDelayTimer == 0.f) {\n    xfc_shadowEnergy = std::max(xf8_filledEnergy, xfc_shadowEnergy - dt * xe8_shadowSpeed);\n  }\n\n  CGuiWidget::Update(dt);\n}\n\nvoid CAuiEnergyBarT01::Draw(const CGuiWidgetDrawParms& drawParms) {\n  CGraphics::SetModelMatrix(x34_worldXF);\n  if (!xbc_tex || !xbc_tex.IsLoaded() || xd8_coordFunc == nullptr) {\n    return;\n  }\n\n  CGraphics::SetDepthWriteMode(true, ERglEnum::LEqual, false);\n  CGraphics::SetAmbientColor(zeus::skWhite);\n  CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::One, ERglLogicOp::Clear);\n\n  const float filledT = xe0_maxEnergy > 0.f ? xf8_filledEnergy / xe0_maxEnergy : 0.f;\n  const float shadowT = xe0_maxEnergy > 0.f ? xfc_shadowEnergy / xe0_maxEnergy : 0.f;\n\n  zeus::CColor filledColor = xd0_filledColor;\n  filledColor.a() *= drawParms.x0_alphaMod;\n  filledColor *= xa8_color2;\n\n  zeus::CColor shadowColor = xd4_shadowColor;\n  shadowColor.a() *= drawParms.x0_alphaMod;\n  shadowColor *= xa8_color2;\n\n  zeus::CColor emptyColor = xcc_emptyColor;\n  emptyColor.a() *= drawParms.x0_alphaMod;\n  emptyColor *= xa8_color2;\n\n  zeus::CColor useCol = emptyColor;\n  for (u32 i = 0; i < 3; ++i) {\n    float barOffT;\n    if (i == 0) {\n      barOffT = 0.f;\n    } else if (i == 1) {\n      barOffT = filledT;\n    } else {\n      barOffT = shadowT;\n    }\n\n    float barMaxT;\n    if (i == 0) {\n      barMaxT = filledT;\n    } else if (i == 1) {\n      barMaxT = shadowT;\n    } else {\n      barMaxT = 1.f;\n    }\n\n    if (i == 0) {\n      useCol = filledColor;\n    } else if (i == 1) {\n      useCol = shadowColor;\n    } else {\n      useCol = emptyColor;\n    }\n\n    if (barOffT != barMaxT) {\n      CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvModulate);\n      CGraphics::SetTevOp(ERglTevStage::Stage1, CTevCombiners::kEnvPassthru);\n      xbc_tex->Load(GX_TEXMAP0, EClampMode::Repeat);\n      CGraphics::StreamBegin(ERglPrimitive::TriangleStrip);\n      CGraphics::StreamColor(useCol);\n      auto coords = xd8_coordFunc(barOffT);\n      while (barOffT < barMaxT) {\n        CGraphics::StreamTexcoord(barOffT, 0.f);\n        CGraphics::StreamVertex(coords.first);\n        CGraphics::StreamTexcoord(barOffT, 1.f);\n        CGraphics::StreamVertex(coords.second);\n        barOffT += xdc_tesselation;\n        if (barOffT < barMaxT) {\n          coords = xd8_coordFunc(barOffT);\n        } else {\n          coords = xd8_coordFunc(barMaxT);\n          CGraphics::StreamTexcoord(barMaxT, 0.f);\n          CGraphics::StreamVertex(coords.first);\n          CGraphics::StreamTexcoord(barMaxT, 1.f);\n          CGraphics::StreamVertex(coords.second);\n        }\n      }\n      CGraphics::StreamEnd();\n    }\n  }\n  CGraphics::SetDepthWriteMode(true, ERglEnum::LEqual, true);\n}\n\nvoid CAuiEnergyBarT01::SetCurrEnergy(float e, ESetMode mode) {\n  e = zeus::clamp(0.f, e, xe0_maxEnergy);\n  if (e == xf4_setEnergy) {\n    return;\n  }\n  if (xf0_alwaysResetDelayTimer || xf8_filledEnergy == xfc_shadowEnergy) {\n    x100_shadowDrainDelayTimer = xec_shadowDrainDelay;\n  }\n  xf1_wrapping = mode == ESetMode::Wrapped;\n  xf4_setEnergy = e;\n  if (mode == ESetMode::Insta) {\n    xf8_filledEnergy = xf4_setEnergy;\n  }\n}\n\nvoid CAuiEnergyBarT01::SetMaxEnergy(float maxEnergy) {\n  xe0_maxEnergy = maxEnergy;\n  xf4_setEnergy = std::min(xe0_maxEnergy, xf4_setEnergy);\n  xf8_filledEnergy = std::min(xe0_maxEnergy, xf8_filledEnergy);\n  xfc_shadowEnergy = std::min(xe0_maxEnergy, xfc_shadowEnergy);\n}\n\nstd::shared_ptr<CGuiWidget> CAuiEnergyBarT01::Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp) {\n  CGuiWidgetParms parms = ReadWidgetHeader(frame, in);\n  auto tex = in.Get<CAssetId>();\n  std::shared_ptr<CGuiWidget> ret = std::make_shared<CAuiEnergyBarT01>(parms, sp, tex);\n  ret->ParseBaseInfo(frame, in, parms);\n  return ret;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CAuiEnergyBarT01.hpp",
    "content": "#pragma once\n\n#include <array>\n#include <memory>\n#include <vector>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/Graphics/CTexture.hpp\"\n#include \"Runtime/GuiSys/CGuiWidget.hpp\"\n\n#include <zeus/CColor.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CSimplePool;\n\nclass CAuiEnergyBarT01 : public CGuiWidget {\npublic:\n  using FCoordFunc = std::pair<zeus::CVector3f, zeus::CVector3f> (*)(float t);\n  enum class ESetMode { Normal, Wrapped, Insta };\n\nprivate:\n  CAssetId xb8_txtrId;\n  TLockedToken<CTexture> xbc_tex; // Used to be optional\n  zeus::CColor xcc_emptyColor;\n  zeus::CColor xd0_filledColor;\n  zeus::CColor xd4_shadowColor;\n  FCoordFunc xd8_coordFunc = nullptr;\n  float xdc_tesselation = 1.f;\n  float xe0_maxEnergy = 0.f;\n  float xe4_filledSpeed = 1000.f;\n  float xe8_shadowSpeed = 1000.f;\n  float xec_shadowDrainDelay = 0.f;\n  bool xf0_alwaysResetDelayTimer = false;\n  bool xf1_wrapping = false;\n  float xf4_setEnergy = 0.f;\n  float xf8_filledEnergy = 0.f;\n  float xfc_shadowEnergy = 0.f;\n  float x100_shadowDrainDelayTimer = 0.f;\n\npublic:\n  CAuiEnergyBarT01(const CGuiWidgetParms& parms, CSimplePool* sp, CAssetId txtrId);\n  FourCC GetWidgetTypeID() const override { return FOURCC('ENRG'); }\n  static std::pair<zeus::CVector3f, zeus::CVector3f> DownloadBarCoordFunc(float t);\n  void Update(float dt) override;\n  void Draw(const CGuiWidgetDrawParms& drawParms) override;\n  float GetActualFraction() const { return xe0_maxEnergy == 0.f ? 0.f : xf4_setEnergy / xe0_maxEnergy; }\n  float GetSetEnergy() const { return xf4_setEnergy; }\n  float GetMaxEnergy() const { return xe0_maxEnergy; }\n  float GetFilledEnergy() const { return xf8_filledEnergy; }\n  void SetCurrEnergy(float e, ESetMode mode);\n  void SetCoordFunc(FCoordFunc func) { xd8_coordFunc = func; }\n  void SetEmptyColor(const zeus::CColor& c) { xcc_emptyColor = c; }\n  void SetFilledColor(const zeus::CColor& c) { xd0_filledColor = c; }\n  void SetShadowColor(const zeus::CColor& c) { xd4_shadowColor = c; }\n  void SetMaxEnergy(float maxEnergy);\n  void ResetMaxEnergy() { SetMaxEnergy(xdc_tesselation); }\n  void SetTesselation(float t) { xdc_tesselation = t; }\n  void SetIsAlwaysResetTimer(bool b) { xf0_alwaysResetDelayTimer = b; }\n  void SetFilledDrainSpeed(float s) { xe4_filledSpeed = s; }\n  void SetShadowDrainSpeed(float s) { xe8_shadowSpeed = s; }\n  void SetShadowDrainDelay(float d) { xec_shadowDrainDelay = d; }\n  static std::shared_ptr<CGuiWidget> Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CAuiImagePane.cpp",
    "content": "#include \"Runtime/GuiSys/CAuiImagePane.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/Camera/CCameraFilter.hpp\"\n#include \"Runtime/Graphics/CTexture.hpp\"\n#include \"Runtime/Graphics/CGX.hpp\"\n#include \"Runtime/GuiSys/CGuiWidgetDrawParms.hpp\"\n\nnamespace metaforce {\n\nCAuiImagePane::CAuiImagePane(const CGuiWidgetParms& parms, CSimplePool* sp, CAssetId tex0, CAssetId tex1,\n                             rstl::reserved_vector<zeus::CVector3f, 4>&& coords,\n                             rstl::reserved_vector<zeus::CVector2f, 4>&& uvs, bool initTex)\n: CGuiWidget(parms), xc8_tex0(tex0), xcc_tex1(tex1), xe0_coords(std::move(coords)), x114_uvs(std::move(uvs)) {\n  if (initTex)\n    SetTextureID0(tex0, sp);\n}\n\nstd::shared_ptr<CGuiWidget> CAuiImagePane::Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp) {\n  CGuiWidgetParms parms = ReadWidgetHeader(frame, in);\n  in.ReadLong();\n  in.ReadLong();\n  in.ReadLong();\n  u32 coordCount = in.ReadLong();\n  rstl::reserved_vector<zeus::CVector3f, 4> coords;\n  for (u32 i = 0; i < coordCount; ++i)\n    coords.push_back(in.Get<zeus::CVector3f>());\n  u32 uvCount = in.ReadLong();\n  rstl::reserved_vector<zeus::CVector2f, 4> uvs;\n  for (u32 i = 0; i < uvCount; ++i)\n    uvs.push_back(in.Get<zeus::CVector2f>());\n  std::shared_ptr<CGuiWidget> ret =\n      std::make_shared<CAuiImagePane>(parms, sp, CAssetId(), CAssetId(), std::move(coords), std::move(uvs), true);\n  ret->ParseBaseInfo(frame, in, parms);\n  return ret;\n}\n\nvoid CAuiImagePane::Reset(ETraversalMode mode) {\n  xc8_tex0 = CAssetId();\n  xb8_tex0Tok = TLockedToken<CTexture>();\n  CGuiWidget::Reset(mode);\n}\n\nvoid CAuiImagePane::Update(float dt) {\n  xd0_uvBias0.x() = std::fmod(xd0_uvBias0.x(), 1.f);\n  xd0_uvBias0.y() = std::fmod(xd0_uvBias0.y(), 1.f);\n  if (x138_tileSize != zeus::skZero2f && xb8_tex0Tok.IsLoaded()) {\n    zeus::CVector2f tmp = zeus::CVector2f(xb8_tex0Tok->GetWidth(), xb8_tex0Tok->GetHeight()) / x138_tileSize;\n    x144_frameTimer = std::fmod(x144_frameTimer + dt * x140_interval, std::floor(tmp.x()) * std::floor(tmp.y()));\n  }\n\n  CGuiWidget::Update(dt);\n}\n\nvoid CAuiImagePane::DoDrawImagePane(const zeus::CColor& color, CTexture& tex, int frame, float alpha,\n                                    bool noBlur) const {\n  zeus::CColor useColor = color;\n  useColor.a() *= alpha;\n\n  rstl::reserved_vector<zeus::CVector2f, 4> vec;\n  const rstl::reserved_vector<zeus::CVector2f, 4>* useUVs;\n  if (x138_tileSize != zeus::skZero2f) {\n    const zeus::CVector2f res(xb8_tex0Tok->GetWidth(), xb8_tex0Tok->GetHeight());\n    const zeus::CVector2f tmp = res / x138_tileSize;\n    const zeus::CVector2f tmpRecip = x138_tileSize / res;\n    const float x0 = tmpRecip.x() * static_cast<float>(frame % static_cast<int>(tmp.x()));\n    const float x1 = x0 + tmpRecip.x();\n    const float y0 = tmpRecip.y() * static_cast<float>(frame % static_cast<int>(tmp.y()));\n    const float y1 = y0 - tmpRecip.y();\n    vec.push_back(zeus::CVector2f(x0, y0));\n    vec.push_back(zeus::CVector2f(x0, y1));\n    vec.push_back(zeus::CVector2f(x1, y0));\n    vec.push_back(zeus::CVector2f(x1, y1));\n    useUVs = &vec;\n  } else {\n    useUVs = &x114_uvs;\n  }\n\n  if (!noBlur) {\n    if ((x14c_deResFactor == 0.f && alpha == 1.f) || tex.GetNumberOfMipMaps() == 1) {\n      CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvModulate);\n      CGraphics::SetTevOp(ERglTevStage::Stage1, CTevCombiners::kEnvPassthru);\n      tex.LoadMipLevel(0, GX_TEXMAP0, EClampMode::Repeat);\n      CGraphics::StreamBegin(ERglPrimitive::TriangleStrip);\n      CGraphics::StreamColor(useColor);\n      for (u32 i = 0; i < useUVs->size(); ++i) {\n        CGraphics::StreamTexcoord((*useUVs)[i] + xd0_uvBias0);\n        CGraphics::StreamVertex(xe0_coords[i]);\n      }\n      CGraphics::StreamEnd();\n    } else {\n      u32 mipCount = tex.GetNumberOfMipMaps() - 1;\n      float fadeFactor = (1.f - x14c_deResFactor) * alpha;\n      float fadeQ = -(fadeFactor * fadeFactor * fadeFactor - 1.f);\n      fadeFactor = fadeQ * static_cast<float>(mipCount);\n      u32 mip1 = fadeFactor;\n      u32 mip2 = mip1;\n      if (fadeQ != static_cast<float>(mip1 / mipCount)) {\n        mip2 = mip1 + 1;\n      }\n\n      float rgba1 = (fadeFactor - static_cast<float>(mip1));\n      float rgba2 = 1.f - rgba1;\n      tex.LoadMipLevel(mip1, GX_TEXMAP0, EClampMode::Repeat);\n      tex.LoadMipLevel(mip2, GX_TEXMAP1, EClampMode::Repeat);\n      std::array<GXVtxDescList, 3> list{{\n          {GX_VA_POS, GX_DIRECT},\n          {GX_VA_TEX0, GX_DIRECT},\n          {GX_VA_NULL, GX_NONE},\n      }};\n\n      CGX::SetVtxDescv(list.data());\n      CGX::SetNumChans(0);\n      CGX::SetNumTexGens(2);\n      CGX::SetNumTevStages(2);\n      GXTevStageID stage = GX_TEVSTAGE0;\n      while (stage < GX_TEVSTAGE2) {\n        GXTevColorArg colorD = stage == GX_TEVSTAGE0 ? GX_CC_ZERO : GX_CC_CPREV;\n        CGX::SetTevColorIn(stage, GX_CC_ZERO, GX_CC_TEXC, GX_CC_KONST, colorD);\n        GXTevAlphaArg alphaD = stage == GX_TEVSTAGE0 ? GX_CA_ZERO : GX_CA_APREV;\n        CGX::SetTevAlphaIn(stage, GX_CA_ZERO, GX_CA_TEXA, GX_CA_KONST, alphaD);\n        CGX::SetTevColorOp(stage, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, true, GX_TEVPREV);\n        CGX::SetTevAlphaOp(stage, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, true, GX_TEVPREV);\n        stage = static_cast<GXTevStageID>(stage + GX_TEVSTAGE1);\n      }\n      CGX::SetTevKAlphaSel(GX_TEVSTAGE0, GX_TEV_KASEL_K0_A);\n      CGX::SetTevKColorSel(GX_TEVSTAGE0, GX_TEV_KCSEL_K0);\n      CGX::SetTevKAlphaSel(GX_TEVSTAGE1, GX_TEV_KASEL_K1_A);\n      CGX::SetTevKColorSel(GX_TEVSTAGE1, GX_TEV_KCSEL_K1);\n      zeus::CColor col1 = useColor * zeus::CColor(rgba2, rgba2, rgba2, rgba2);\n      zeus::CColor col2 = useColor * zeus::CColor(rgba1, rgba1, rgba1, rgba1);\n      CGX::SetTevKColor(GX_KCOLOR0, col1);\n      CGX::SetTevKColor(GX_KCOLOR1, col2);\n      CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL);\n      CGX::SetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD1, GX_TEXMAP1, GX_COLOR_NULL);\n      CGX::SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY, false, GX_PTIDENTITY);\n      CGX::SetTexCoordGen(GX_TEXCOORD1, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY, false, GX_PTIDENTITY);\n      CGX::Begin(GX_TRIANGLESTRIP, GX_VTXFMT0, 4);\n      for (u32 idx = 0; const auto& coord : xe0_coords) {\n        GXPosition3f32(coord);\n        GXTexCoord2f32((*useUVs)[idx] + xd0_uvBias0);\n        ++idx;\n      }\n      CGX::End();\n    }\n  } else {\n    CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvModulateAlpha);\n    CGraphics::SetTevOp(ERglTevStage::Stage1, CTevCombiners::kEnvPassthru);\n    tex.Load(GX_TEXMAP0, EClampMode::Repeat);\n    CGraphics::StreamBegin(ERglPrimitive::TriangleStrip);\n    CGraphics::StreamColor(useColor);\n    for (u32 i = 0; i < useUVs->size(); ++i) {\n      CGraphics::StreamTexcoord((*useUVs)[i]);\n      CGraphics::StreamVertex(xe0_coords[i] + xd0_uvBias0);\n    }\n    CGraphics::StreamEnd();\n  }\n}\n\nvoid CAuiImagePane::Draw(const CGuiWidgetDrawParms& params) {\n  CGraphics::SetModelMatrix(x34_worldXF);\n  if (!GetIsVisible() || !xb8_tex0Tok.IsLoaded()) {\n    return;\n  }\n  SCOPED_GRAPHICS_DEBUG_GROUP(fmt::format(\"CAuiImagePane::Draw {}\", m_name).c_str(), zeus::skCyan);\n  GetIsFinishedLoadingWidgetSpecific();\n  zeus::CColor color = xa8_color2;\n  color.a() *= params.x0_alphaMod;\n  CGraphics::SetDepthWriteMode(true, ERglEnum::LEqual,\n                               xac_drawFlags == EGuiModelDrawFlags::Shadeless ||\n                                   xac_drawFlags == EGuiModelDrawFlags::Opaque);\n  float blur0 = 1.f;\n  float blur1 = 0.f;\n  const int frame0 = static_cast<int>(x144_frameTimer);\n  int frame1 = 0;\n  if (x140_interval < 1.f && x140_interval > 0.f) {\n    zeus::CVector2f tmp = zeus::CVector2f(xb8_tex0Tok->GetWidth(), xb8_tex0Tok->GetHeight()) / x138_tileSize;\n    frame1 = (frame0 + 1) % static_cast<int>(tmp.x() * tmp.y());\n    if (x148_fadeDuration == 0.f)\n      blur1 = 1.f;\n    else\n      blur1 = std::min(std::fmod(x144_frameTimer, 1.f) / x148_fadeDuration, 1.f);\n    blur0 = 1.f - blur1;\n  }\n\n  // Alpha blend\n  CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::InvSrcAlpha,\n                          ERglLogicOp::Clear);\n  DoDrawImagePane(color * zeus::CColor(0.f, 0.5f), *xb8_tex0Tok, frame0, 1.f, true);\n\n  if (x150_flashFactor > 0.f) {\n    // Additive blend\n    zeus::CColor color2 = xa8_color2;\n    color2.a() = x150_flashFactor;\n    CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::One, ERglLogicOp::Clear);\n    DoDrawImagePane(color2, *xb8_tex0Tok, frame0, blur0, false);\n    if (blur1 > 0.f)\n      DoDrawImagePane(color2, *xb8_tex0Tok, frame1, blur1, false);\n  }\n\n  switch (xac_drawFlags) {\n  case EGuiModelDrawFlags::Shadeless:\n  case EGuiModelDrawFlags::Opaque:\n    // Opaque blend\n    CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::InvSrcAlpha,\n                            ERglLogicOp::Clear);\n    DoDrawImagePane(color, *xb8_tex0Tok, frame0, blur0, false);\n    if (blur1 > 0.f) {\n      DoDrawImagePane(color, *xb8_tex0Tok, frame1, blur1, false);\n    }\n    break;\n  case EGuiModelDrawFlags::Alpha:\n    // Alpha blend\n    CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::InvSrcAlpha,\n                            ERglLogicOp::Clear);\n    DoDrawImagePane(color, *xb8_tex0Tok, frame0, blur0, false);\n    if (blur1 > 0.f) {\n      DoDrawImagePane(color, *xb8_tex0Tok, frame1, blur1, false);\n    }\n    break;\n  case EGuiModelDrawFlags::Additive:\n    // Additive blend\n    CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::One, ERglLogicOp::Clear);\n    DoDrawImagePane(color, *xb8_tex0Tok, frame0, blur0, false);\n    if (blur1 > 0.f) {\n      DoDrawImagePane(color, *xb8_tex0Tok, frame1, blur1, false);\n    }\n    break;\n  case EGuiModelDrawFlags::AlphaAdditiveOverdraw:\n    // Alpha blend\n    CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::InvSrcAlpha,\n                            ERglLogicOp::Clear);\n    DoDrawImagePane(color, *xb8_tex0Tok, frame0, blur0, false);\n    if (blur1 > 0.f) {\n      DoDrawImagePane(color, *xb8_tex0Tok, frame1, blur1, false);\n    }\n    // Full additive blend\n    CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::One, ERglBlendFactor::One, ERglLogicOp::Clear);\n    DoDrawImagePane(color, *xb8_tex0Tok, frame0, blur0, false);\n    if (blur1 > 0.f) {\n      DoDrawImagePane(color, *xb8_tex0Tok, frame1, blur1, false);\n    }\n    break;\n  default:\n    break;\n  }\n}\n\nbool CAuiImagePane::GetIsFinishedLoadingWidgetSpecific() { return !xb8_tex0Tok || xb8_tex0Tok.IsLoaded(); }\n\nvoid CAuiImagePane::SetTextureID0(CAssetId tex, CSimplePool* sp) {\n  xc8_tex0 = tex;\n  if (!sp)\n    return;\n  if (xc8_tex0.IsValid())\n    xb8_tex0Tok = sp->GetObj({FOURCC('TXTR'), xc8_tex0});\n  else\n    xb8_tex0Tok = TLockedToken<CTexture>();\n}\n\nvoid CAuiImagePane::SetAnimationParms(const zeus::CVector2f& tileSize, float interval, float fadeDuration) {\n  x138_tileSize = tileSize;\n  x140_interval = interval;\n  x144_frameTimer = 0.f;\n  x148_fadeDuration = fadeDuration;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CAuiImagePane.hpp",
    "content": "#pragma once\n\n#include <array>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/GuiSys/CGuiWidget.hpp\"\n\n#include <zeus/CVector2f.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace zeus {\nclass CColor;\n}\n\nnamespace metaforce {\nclass CSimplePool;\nclass CTexture;\n\nclass CAuiImagePane : public CGuiWidget {\n  TLockedToken<CTexture> xb8_tex0Tok; // Used to be optional\n  CAssetId xc8_tex0;\n  CAssetId xcc_tex1;\n  zeus::CVector2f xd0_uvBias0;\n  zeus::CVector2f xd8_uvBias1;\n  rstl::reserved_vector<zeus::CVector3f, 4> xe0_coords;\n  rstl::reserved_vector<zeus::CVector2f, 4> x114_uvs;\n  zeus::CVector2f x138_tileSize;\n  float x140_interval = 0.f;\n  float x144_frameTimer = 0.f;\n  float x148_fadeDuration = 0.f;\n  float x14c_deResFactor = 0.f;\n  float x150_flashFactor = 0.f;\n  void DoDrawImagePane(const zeus::CColor& color, CTexture& tex, int frame, float blurAmt, bool noBlur) const;\n\npublic:\n  CAuiImagePane(const CGuiWidgetParms& parms, CSimplePool* sp, CAssetId, CAssetId,\n                rstl::reserved_vector<zeus::CVector3f, 4>&& coords, rstl::reserved_vector<zeus::CVector2f, 4>&& uvs,\n                bool initTex);\n  FourCC GetWidgetTypeID() const override { return FOURCC('IMGP'); }\n  static std::shared_ptr<CGuiWidget> Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp);\n\n  void Reset(ETraversalMode mode) override;\n  void Update(float dt) override;\n  void Draw(const CGuiWidgetDrawParms& params) override;\n  bool GetIsFinishedLoadingWidgetSpecific() override;\n  void SetTextureID0(CAssetId tex, CSimplePool* sp);\n  void SetAnimationParms(const zeus::CVector2f& vec, float interval, float duration);\n  void SetDeResFactor(float d) { x14c_deResFactor = d; }\n  void SetFlashFactor(float t) { x150_flashFactor = t; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CAuiMeter.cpp",
    "content": "#include \"Runtime/GuiSys/CAuiMeter.hpp\"\n\n#include <algorithm>\n#include <memory>\n\n#include <zeus/Math.hpp>\n\nnamespace metaforce {\n\nCAuiMeter::CAuiMeter(const CGuiWidgetParms& parms, bool noRoundUp, u32 maxCapacity, u32 workerCount)\n: CGuiGroup(parms, 0, false), xc4_noRoundUp(noRoundUp), xc8_maxCapacity(maxCapacity), xcc_capacity(maxCapacity) {\n  xd4_workers.reserve(workerCount);\n}\n\nvoid CAuiMeter::UpdateMeterWorkers() {\n  float scale = xd4_workers.size() / float(xc8_maxCapacity);\n\n  size_t etankCap;\n  if (xc4_noRoundUp)\n    etankCap = xcc_capacity * scale;\n  else\n    etankCap = xcc_capacity * scale + 0.5f;\n\n  size_t etankFill;\n  if (xc4_noRoundUp)\n    etankFill = xd0_value * scale;\n  else\n    etankFill = xd0_value * scale + 0.5f;\n\n  for (size_t i = 0; i < xd4_workers.size(); ++i) {\n    CGuiGroup* worker = xd4_workers[i];\n    if (!worker)\n      continue;\n\n    CGuiWidget* fullTank = worker->GetWorkerWidget(0);\n    CGuiWidget* emptyTank = worker->GetWorkerWidget(1);\n\n    if (i < etankFill) {\n      if (fullTank)\n        fullTank->SetIsVisible(true);\n      if (emptyTank)\n        emptyTank->SetIsVisible(false);\n    } else if (i < etankCap) {\n      if (fullTank)\n        fullTank->SetIsVisible(false);\n      if (emptyTank)\n        emptyTank->SetIsVisible(true);\n    } else {\n      if (fullTank)\n        fullTank->SetIsVisible(false);\n      if (emptyTank)\n        emptyTank->SetIsVisible(false);\n    }\n  }\n}\n\nvoid CAuiMeter::OnVisibleChange() {\n  if (GetIsVisible())\n    UpdateMeterWorkers();\n}\n\nvoid CAuiMeter::SetCurrValue(s32 val) {\n  xd0_value = zeus::clamp(0, val, xcc_capacity);\n  UpdateMeterWorkers();\n}\n\nvoid CAuiMeter::SetCapacity(s32 cap) {\n  xcc_capacity = zeus::clamp(0, cap, xc8_maxCapacity);\n  xd0_value = std::min(xcc_capacity, xd0_value);\n  UpdateMeterWorkers();\n}\n\nvoid CAuiMeter::SetMaxCapacity(s32 cap) {\n  xc8_maxCapacity = std::max(0, cap);\n  xcc_capacity = std::min(xc8_maxCapacity, xcc_capacity);\n  xd0_value = std::min(xcc_capacity, xd0_value);\n  UpdateMeterWorkers();\n}\n\nCGuiWidget* CAuiMeter::GetWorkerWidget(int id) const { return xd4_workers[id]; }\n\nbool CAuiMeter::AddWorkerWidget(CGuiWidget* worker) {\n  CGuiGroup::AddWorkerWidget(worker);\n  if (worker->GetWorkerId() >= xd4_workers.size())\n    xd4_workers.resize(worker->GetWorkerId() + 1);\n  xd4_workers[worker->GetWorkerId()] = static_cast<CGuiGroup*>(worker);\n  return true;\n}\n\nstd::shared_ptr<CGuiWidget> CAuiMeter::Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp) {\n  CGuiWidgetParms parms = ReadWidgetHeader(frame, in);\n  in.ReadBool();\n  bool noRoundUp = in.ReadBool();\n  u32 maxCapacity = in.ReadLong();\n  u32 workerCount = in.ReadLong();\n  std::shared_ptr<CAuiMeter> ret = std::make_shared<CAuiMeter>(parms, noRoundUp, maxCapacity, workerCount);\n  ret->ParseBaseInfo(frame, in, parms);\n  return ret;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CAuiMeter.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <vector>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/GuiSys/CGuiGroup.hpp\"\n\nnamespace metaforce {\nclass CSimplePool;\n\nclass CAuiMeter : public CGuiGroup {\n  bool xc4_noRoundUp;\n  s32 xc8_maxCapacity;\n  s32 xcc_capacity;\n  s32 xd0_value = 0;\n  std::vector<CGuiGroup*> xd4_workers;\n  void UpdateMeterWorkers();\n\npublic:\n  CAuiMeter(const CGuiWidgetParms& parms, bool noRoundUp, u32 maxCapacity, u32 workerCount);\n  FourCC GetWidgetTypeID() const override { return FOURCC('METR'); }\n\n  void OnVisibleChange() override;\n  void SetCurrValue(s32 val);\n  void SetCapacity(s32 cap);\n  void SetMaxCapacity(s32 cap);\n  CGuiWidget* GetWorkerWidget(int id) const override;\n  bool AddWorkerWidget(CGuiWidget* worker) override;\n\n  static std::shared_ptr<CGuiWidget> Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CCompoundTargetReticle.cpp",
    "content": "#include \"Runtime/GuiSys/CCompoundTargetReticle.hpp\"\n\n#include <cstdlib>\n\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Camera/CGameCamera.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CScriptGrapplePoint.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nnamespace {\nconstexpr char skCrosshairsReticleAssetName[] = \"CMDL_Crosshairs\";\n[[maybe_unused]] constexpr char skOrbitZoneReticleAssetName[] = \"CMDL_OrbitZone\";\nconstexpr char skSeekerAssetName[] = \"CMDL_Seeker\";\nconstexpr char skLockConfirmAssetName[] = \"CMDL_LockConfirm\";\nconstexpr char skTargetFlowerAssetName[] = \"CMDL_TargetFlower\";\nconstexpr char skMissileBracketAssetName[] = \"CMDL_MissileBracket\";\nconstexpr char skChargeGaugeAssetName[] = \"CMDL_ChargeGauge\";\nconstexpr char skChargeBeamTickAssetName[] = \"CMDL_ChargeTickFirst\";\nconstexpr char skOuterBeamIconSquareNameBase[] = \"CMDL_BeamSquare\";\nconstexpr char skInnerBeamIconName[] = \"CMDL_InnerBeamIcon\";\nconstexpr char skLockFireAssetName[] = \"CMDL_LockFire\";\nconstexpr char skLockDaggerAssetName[] = \"CMDL_LockDagger0\";\nconstexpr char skGrappleReticleAssetName[] = \"CMDL_Grapple\";\nconstexpr char skXRayRingModelName[] = \"CMDL_XRayRetRing\";\nconstexpr char skThermalReticleAssetName[] = \"CMDL_ThermalRet\";\n\nfloat offshoot_func(float f1, float f2, float f3) { return (f1 * 0.5f) + std::sin((f3 - 0.5f) * f2); }\n\nfloat calculate_premultiplied_overshoot_offset(float f1) { return 2.f * (M_PIF - std::asin(1.f / f1)); }\n} // Anonymous namespace\n\nconstexpr CTargetReticleRenderState CTargetReticleRenderState::skZeroRenderState(kInvalidUniqueId, 1.f, zeus::skZero3f,\n                                                                                 0.f, 1.f, true);\n\nCCompoundTargetReticle::SOuterItemInfo::SOuterItemInfo(std::string_view res) : x0_model(g_SimplePool->GetObj(res)) {}\n\nCCompoundTargetReticle::CCompoundTargetReticle(const CStateManager& mgr)\n: x0_leadingOrientation(mgr.GetCameraManager()->GetCurrentCamera(mgr)->GetTransform().buildMatrix3f())\n, x10_laggingOrientation(mgr.GetCameraManager()->GetCurrentCamera(mgr)->GetTransform().buildMatrix3f())\n, x2c_overshootOffsetHalf(0.5f * g_tweakTargeting->GetChargeGaugeOvershootOffset())\n, x30_premultOvershootOffset(\n      calculate_premultiplied_overshoot_offset(g_tweakTargeting->GetChargeGaugeOvershootOffset()))\n, x34_crosshairs(g_SimplePool->GetObj(skCrosshairsReticleAssetName))\n, x40_seeker(g_SimplePool->GetObj(skSeekerAssetName))\n, x4c_lockConfirm(g_SimplePool->GetObj(skLockConfirmAssetName))\n, x58_targetFlower(g_SimplePool->GetObj(skTargetFlowerAssetName))\n, x64_missileBracket(g_SimplePool->GetObj(skMissileBracketAssetName))\n, x70_innerBeamIcon(g_SimplePool->GetObj(skInnerBeamIconName))\n, x7c_lockFire(g_SimplePool->GetObj(skLockFireAssetName))\n, x88_lockDagger(g_SimplePool->GetObj(skLockDaggerAssetName))\n, x94_grapple(g_SimplePool->GetObj(skGrappleReticleAssetName))\n, xa0_chargeTickFirst(g_SimplePool->GetObj(skChargeBeamTickAssetName))\n, xac_xrayRetRing(g_SimplePool->GetObj(skXRayRingModelName))\n, xb8_thermalReticle(g_SimplePool->GetObj(skThermalReticleAssetName))\n, xc4_chargeGauge(skChargeGaugeAssetName)\n, xf4_targetPos(CalculateOrbitZoneReticlePosition(mgr, false))\n, x100_laggingTargetPos(CalculateOrbitZoneReticlePosition(mgr, true))\n, x208_lockonTimer(g_tweakTargeting->GetLockonDuration()) {\n  xe0_outerBeamIconSquares.reserve(9);\n  for (size_t i = 0; i < xe0_outerBeamIconSquares.capacity(); ++i) {\n    xe0_outerBeamIconSquares.emplace_back(fmt::format(\"{}{}\", skOuterBeamIconSquareNameBase, i));\n  }\n  x34_crosshairs.Lock();\n}\n\n// CCompoundTargetReticle::SScanReticuleRenderer::SScanReticuleRenderer() {\n//  CGraphics::CommitResources([this](boo::IGraphicsDataFactory::Context& ctx) {\n//    for (size_t i = 0; i < m_lineRenderers.size(); ++i) {\n//      m_lineRenderers[i].emplace(CLineRenderer::EPrimitiveMode::Lines, 8, {}, true, true);\n//      for (auto& stripRenderer : m_stripRenderers[i]) {\n//        stripRenderer.emplace(CLineRenderer::EPrimitiveMode::LineStrip, 4, {}, true, true);\n//      }\n//    }\n//    return true;\n//  } BooTrace);\n// }\n\nCCompoundTargetReticle::EReticleState CCompoundTargetReticle::GetDesiredReticleState(const CStateManager& mgr) const {\n  switch (mgr.GetPlayerState()->GetCurrentVisor()) {\n  case CPlayerState::EPlayerVisor::Scan:\n    return EReticleState::Scan;\n  case CPlayerState::EPlayerVisor::XRay:\n    return EReticleState::XRay;\n  case CPlayerState::EPlayerVisor::Combat:\n  default:\n    return EReticleState::Combat;\n  case CPlayerState::EPlayerVisor::Thermal:\n    return EReticleState::Thermal;\n  }\n}\n\nvoid CCompoundTargetReticle::Update(float dt, const CStateManager& mgr) {\n  const float angle = x10_laggingOrientation.angleFrom(x0_leadingOrientation).asDegrees();\n  float t;\n  if (angle < 0.1f || angle > 45.f) {\n    t = 1.f;\n  } else {\n    t = std::min(1.f, g_tweakTargeting->GetAngularLagSpeed() * dt / angle);\n  }\n  x10_laggingOrientation =\n      t == 1.f ? x0_leadingOrientation : zeus::CQuaternion::slerp(x10_laggingOrientation, x0_leadingOrientation, t);\n  xf4_targetPos = CalculateOrbitZoneReticlePosition(mgr, false);\n  x100_laggingTargetPos = CalculateOrbitZoneReticlePosition(mgr, true);\n  UpdateCurrLockOnGroup(dt, mgr);\n  UpdateNextLockOnGroup(dt, mgr);\n  UpdateOrbitZoneGroup(dt, mgr);\n  const EReticleState desiredState = GetDesiredReticleState(mgr);\n  if (desiredState != x20_prevState && x20_prevState == x24_nextState) {\n    x24_nextState = desiredState;\n    x28_noDrawTicks = 2;\n  }\n  if (x20_prevState != x24_nextState && x28_noDrawTicks <= 0) {\n    x20_prevState = x24_nextState;\n    bool combat = false;\n    bool scan = false;\n    bool xray = false;\n    bool thermal = false;\n    switch (x24_nextState) {\n    case EReticleState::Combat:\n      combat = true;\n      break;\n    case EReticleState::Scan:\n      scan = true;\n      break;\n    case EReticleState::XRay:\n      xray = true;\n      break;\n    case EReticleState::Thermal:\n      thermal = true;\n      break;\n    default:\n      break;\n    }\n    if (combat) {\n      x40_seeker.Lock();\n    } else {\n      x40_seeker.Unlock();\n    }\n    if (combat) {\n      x4c_lockConfirm.Lock();\n    } else {\n      x4c_lockConfirm.Unlock();\n    }\n    if (combat) {\n      x58_targetFlower.Lock();\n    } else {\n      x58_targetFlower.Unlock();\n    }\n    if (combat) {\n      x64_missileBracket.Lock();\n    } else {\n      x64_missileBracket.Unlock();\n    }\n    if (combat) {\n      x70_innerBeamIcon.Lock();\n    } else {\n      x70_innerBeamIcon.Unlock();\n    }\n    if (combat) {\n      x7c_lockFire.Lock();\n    } else {\n      x7c_lockFire.Unlock();\n    }\n    if (combat) {\n      x88_lockDagger.Lock();\n    } else {\n      x88_lockDagger.Unlock();\n    }\n    if (combat) {\n      xa0_chargeTickFirst.Lock();\n    } else {\n      xa0_chargeTickFirst.Unlock();\n    }\n    if (xray) {\n      xac_xrayRetRing.Lock();\n    } else {\n      xac_xrayRetRing.Unlock();\n    }\n    if (thermal) {\n      xb8_thermalReticle.Lock();\n    } else {\n      xb8_thermalReticle.Unlock();\n    }\n    if (combat) {\n      xc4_chargeGauge.x0_model.Lock();\n    } else {\n      xc4_chargeGauge.x0_model.Unlock();\n    }\n    if (!scan) {\n      x94_grapple.Lock();\n    } else {\n      x94_grapple.Unlock();\n    }\n    for (SOuterItemInfo& info : xe0_outerBeamIconSquares) {\n      if (combat) {\n        info.x0_model.Lock();\n      } else {\n        info.x0_model.Unlock();\n      }\n    }\n  }\n\n  const CPlayerGun* gun = mgr.GetPlayer().GetPlayerGun();\n  const bool fullyCharged = (gun->IsCharging() ? gun->GetChargeBeamFactor() : 0.f) >= 1.f;\n  if (fullyCharged != x21a_fullyCharged) {\n    x21a_fullyCharged = fullyCharged;\n  }\n  if (x21a_fullyCharged) {\n    x214_fullChargeFadeTimer = std::min(dt / g_tweakTargeting->GetFullChargeFadeDuration() + x214_fullChargeFadeTimer,\n                                        g_tweakTargeting->GetFullChargeFadeDuration());\n  } else {\n    x214_fullChargeFadeTimer =\n        std::max(x214_fullChargeFadeTimer - dt / g_tweakTargeting->GetFullChargeFadeDuration(), 0.f);\n  }\n\n  const bool missileActive = gun->GetMissleMode() == CPlayerGun::EMissileMode::Active;\n  if (missileActive != x1f4_missileActive) {\n    if (x1f8_missileBracketTimer != 0.f) {\n      x1f8_missileBracketTimer = FLT_EPSILON - x1f8_missileBracketTimer;\n    } else {\n      x1f8_missileBracketTimer = FLT_EPSILON;\n    }\n    x1f4_missileActive = missileActive;\n  }\n\n  const CPlayerState::EBeamId beam = gun->GetCurrentBeam();\n  if (beam != x200_beam) {\n    x204_chargeGaugeOvershootTimer = g_tweakTargeting->GetChargeGaugeOvershootDuration();\n    for (size_t i = 0; i < xe0_outerBeamIconSquares.size(); ++i) {\n      zeus::CRelAngle baseAngle = g_tweakTargeting->GetOuterBeamSquareAngles(int(beam))[i];\n      baseAngle.makeRel();\n      SOuterItemInfo& icon = xe0_outerBeamIconSquares[i];\n      zeus::CRelAngle offshootAngleDelta = baseAngle.asRadians() - icon.x10_rotAng;\n      if ((i & 0x1) == 1) {\n        offshootAngleDelta =\n            (baseAngle > 0.f) ? zeus::CRelAngle(-2.f * M_PIF - baseAngle) : zeus::CRelAngle(2.f * M_PIF + baseAngle);\n      }\n      icon.xc_offshootBaseAngle = icon.x10_rotAng;\n      icon.x18_offshootAngleDelta = offshootAngleDelta;\n      icon.x14_baseAngle = baseAngle;\n    }\n    zeus::CRelAngle baseAngle = g_tweakTargeting->GetChargeGaugeAngle(int(beam));\n    baseAngle.makeRel();\n    float offshootAngleDelta = baseAngle.asRadians() - xc4_chargeGauge.x10_rotAng;\n    if ((rand() & 0x1) == 1) {\n      offshootAngleDelta =\n          (offshootAngleDelta > 0.f) ? -2.f * M_PIF - offshootAngleDelta : 2.f * M_PIF + offshootAngleDelta;\n    }\n    xc4_chargeGauge.xc_offshootBaseAngle = xc4_chargeGauge.x10_rotAng;\n    xc4_chargeGauge.x18_offshootAngleDelta = offshootAngleDelta;\n    xc4_chargeGauge.x14_baseAngle = baseAngle;\n    x200_beam = beam;\n    x208_lockonTimer = 0.f;\n  }\n\n  if ((gun->GetLastFireButtonStates() & 0x1) != 0) {\n    if (!x218_beamShot) {\n      x210_lockFireTimer = g_tweakTargeting->GetLockFireDuration();\n    }\n    x218_beamShot = true;\n  } else {\n    x218_beamShot = false;\n  }\n\n  if ((gun->GetLastFireButtonStates() & 0x2) != 0) {\n    if (!x219_missileShot) {\n      x1fc_missileBracketScaleTimer = g_tweakTargeting->GetMissileBracketScaleDuration();\n    }\n    x219_missileShot = true;\n  } else {\n    x219_missileShot = false;\n  }\n\n  if (const TCastToConstPtr<CScriptGrapplePoint> point = mgr.GetObjectById(xf2_nextTargetId)) {\n    if (point->GetUniqueId() != x1dc_grapplePoint0) {\n      float tmp;\n      if (point->GetUniqueId() == x1de_grapplePoint1) {\n        tmp = std::max(FLT_EPSILON, x1e4_grapplePoint1T);\n      } else {\n        tmp = FLT_EPSILON;\n      }\n      x1de_grapplePoint1 = x1dc_grapplePoint0;\n      x1e4_grapplePoint1T = x1e0_grapplePoint0T;\n      x1e0_grapplePoint0T = tmp;\n      x1dc_grapplePoint0 = point->GetUniqueId();\n    }\n  } else if (x1dc_grapplePoint0 != kInvalidUniqueId) {\n    x1de_grapplePoint1 = x1dc_grapplePoint0;\n    x1e4_grapplePoint1T = x1e0_grapplePoint0T;\n    x1e0_grapplePoint0T = 0.f;\n    x1dc_grapplePoint0 = kInvalidUniqueId;\n  }\n\n  if (x1e0_grapplePoint0T > 0.f) {\n    x1e0_grapplePoint0T = std::min(dt / 0.5f + x1e0_grapplePoint0T, 1.f);\n  }\n\n  if (x1e4_grapplePoint1T > 0.f) {\n    x1e4_grapplePoint1T = std::max(0.f, x1e4_grapplePoint1T - dt / 0.5f);\n    if (x1e4_grapplePoint1T == 0.f) {\n      x1de_grapplePoint1 = kInvalidUniqueId;\n    }\n  }\n\n  x1f0_xrayRetAngle =\n      zeus::CRelAngle(zeus::degToRad(g_tweakTargeting->GetXRayRetAngleSpeed() * dt) + x1f0_xrayRetAngle).asRel();\n  x1ec_seekerAngle =\n      zeus::CRelAngle(zeus::degToRad(g_tweakTargeting->GetSeekerAngleSpeed() * dt) + x1ec_seekerAngle).asRel();\n}\n\nvoid CTargetReticleRenderState::InterpolateWithClamp(const CTargetReticleRenderState& a, CTargetReticleRenderState& out,\n                                                     const CTargetReticleRenderState& b, float t) {\n  t = zeus::clamp(0.f, t, 1.f);\n  const float omt = 1.f - t;\n  out.x4_radiusWorld = omt * a.x4_radiusWorld + t * b.x4_radiusWorld;\n  out.x14_factor = omt * a.x14_factor + t * b.x14_factor;\n  out.x18_minVpClampScale = omt * a.x18_minVpClampScale + t * b.x18_minVpClampScale;\n  out.x8_positionWorld = zeus::CVector3f::lerp(a.x8_positionWorld, b.x8_positionWorld, t);\n\n  if (t == 1.f) {\n    out.x0_target = b.x0_target;\n  } else if (t == 0.f) {\n    out.x0_target = a.x0_target;\n  } else {\n    out.x0_target = kInvalidUniqueId;\n  }\n}\n\nstatic bool IsDamageOrbit(CPlayer::EPlayerOrbitRequest req) {\n  switch (req) {\n  case CPlayer::EPlayerOrbitRequest::Five:\n  case CPlayer::EPlayerOrbitRequest::ActivateOrbitSource:\n  case CPlayer::EPlayerOrbitRequest::ProjectileCollide:\n  case CPlayer::EPlayerOrbitRequest::Freeze:\n  case CPlayer::EPlayerOrbitRequest::DamageOnGrapple:\n    return true;\n  default:\n    return false;\n  }\n}\n\nvoid CCompoundTargetReticle::UpdateCurrLockOnGroup(float dt, const CStateManager& mgr) {\n  const TUniqueId targetId = mgr.GetPlayer().GetOrbitTargetId();\n  if (targetId != xf0_targetId) {\n    if (targetId != kInvalidUniqueId) {\n      if (TCastToConstPtr<CScriptGrapplePoint>(mgr.GetObjectById(targetId))) {\n        CSfxManager::SfxStart(SFXui_lockon_grapple, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n      } else {\n        CSfxManager::SfxStart(SFXui_lockon_poi, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n      }\n    }\n\n    if (targetId == kInvalidUniqueId) {\n      x12c_currGroupA = x10c_currGroupInterp;\n      x14c_currGroupB.SetFactor(0.f);\n      x16c_currGroupDur =\n          IsDamageOrbit(mgr.GetPlayer().GetOrbitRequest()) ? 0.65f : g_tweakTargeting->GetCurrLockOnEnterDuration();\n    } else {\n      x12c_currGroupA = x10c_currGroupInterp;\n      if (xf0_targetId == kInvalidUniqueId) {\n        x12c_currGroupA.SetTargetId(targetId);\n      }\n      x14c_currGroupB = CTargetReticleRenderState(\n          targetId, 1.f, zeus::skZero3f, 1.f,\n          IsGrappleTarget(targetId, mgr) ? g_tweakTargeting->GetGrappleMinClampScale() : 1.f, false);\n      x16c_currGroupDur = xf0_targetId == kInvalidUniqueId ? g_tweakTargeting->GetCurrLockOnExitDuration()\n                                                           : g_tweakTargeting->GetCurrLockOnSwitchDuration();\n    }\n\n    x170_currGroupTimer = x16c_currGroupDur;\n    xf0_targetId = targetId;\n  }\n\n  if (x170_currGroupTimer > 0.f) {\n    UpdateTargetParameters(x12c_currGroupA, mgr);\n    UpdateTargetParameters(x14c_currGroupB, mgr);\n    x170_currGroupTimer = std::max(0.f, x170_currGroupTimer - dt);\n    CTargetReticleRenderState::InterpolateWithClamp(x12c_currGroupA, x10c_currGroupInterp, x14c_currGroupB,\n                                                    1.f - x170_currGroupTimer / x16c_currGroupDur);\n  } else {\n    UpdateTargetParameters(x10c_currGroupInterp, mgr);\n  }\n\n  if (x1f8_missileBracketTimer != 0.f && x1f8_missileBracketTimer < g_tweakTargeting->GetMissileBracketDuration()) {\n    if (x1f8_missileBracketTimer < 0.f) {\n      x1f8_missileBracketTimer = std::min(0.f, x1f8_missileBracketTimer + dt);\n    } else {\n      x1f8_missileBracketTimer = std::min(g_tweakTargeting->GetMissileBracketDuration(), x1f8_missileBracketTimer + dt);\n    }\n  }\n\n  if (x204_chargeGaugeOvershootTimer > 0.f) {\n    x204_chargeGaugeOvershootTimer = std::max(0.f, x204_chargeGaugeOvershootTimer - dt);\n    if (x204_chargeGaugeOvershootTimer == 0.f) {\n      for (auto& iconSquare : xe0_outerBeamIconSquares) {\n        iconSquare.x10_rotAng = iconSquare.x14_baseAngle;\n      }\n      xc4_chargeGauge.x10_rotAng = xc4_chargeGauge.x14_baseAngle;\n      x208_lockonTimer = FLT_EPSILON;\n    } else {\n      const float offshoot =\n          offshoot_func(x2c_overshootOffsetHalf, x30_premultOvershootOffset,\n                        1.f - x204_chargeGaugeOvershootTimer / g_tweakTargeting->GetChargeGaugeOvershootDuration());\n      for (auto& item : xe0_outerBeamIconSquares) {\n        item.x10_rotAng = zeus::CRelAngle(item.x18_offshootAngleDelta * offshoot + item.xc_offshootBaseAngle).asRel();\n      }\n      xc4_chargeGauge.x10_rotAng =\n          zeus::CRelAngle(xc4_chargeGauge.x18_offshootAngleDelta * offshoot + xc4_chargeGauge.xc_offshootBaseAngle)\n              .asRel();\n    }\n  }\n\n  if (x208_lockonTimer > 0.f && x208_lockonTimer < g_tweakTargeting->GetLockonDuration()) {\n    x208_lockonTimer = std::min(g_tweakTargeting->GetLockonDuration(), x208_lockonTimer + dt);\n  }\n  if (x210_lockFireTimer > 0.f) {\n    x210_lockFireTimer = std::max(0.f, x210_lockFireTimer - dt);\n  }\n  if (x1fc_missileBracketScaleTimer > 0.f) {\n    x1fc_missileBracketScaleTimer = std::max(0.f, x1fc_missileBracketScaleTimer - dt);\n  }\n}\n\nvoid CCompoundTargetReticle::UpdateNextLockOnGroup(float dt, const CStateManager& mgr) {\n  TUniqueId nextTargetId = mgr.GetPlayer().GetOrbitNextTargetId();\n  if (mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::Scan &&\n      mgr.GetPlayer().GetOrbitTargetId() != kInvalidUniqueId) {\n    nextTargetId = mgr.GetPlayer().GetOrbitTargetId();\n  }\n\n  if (nextTargetId != xf2_nextTargetId) {\n    if (nextTargetId == kInvalidUniqueId) {\n      x194_nextGroupA = x174_nextGroupInterp;\n      x1b4_nextGroupB = CTargetReticleRenderState(\n          kInvalidUniqueId, 1.f,\n          (x20_prevState == EReticleState::XRay || x20_prevState == EReticleState::Thermal) ? x100_laggingTargetPos\n                                                                                            : xf4_targetPos,\n          0.f, 1.f, true);\n      x1d4_nextGroupDur = x1d8_nextGroupTimer = g_tweakTargeting->GetNextLockOnExitDuration();\n      xf2_nextTargetId = nextTargetId;\n    } else {\n      x194_nextGroupA = x174_nextGroupInterp;\n      x1b4_nextGroupB = CTargetReticleRenderState(\n          nextTargetId, 1.f, zeus::skZero3f, 1.f,\n          IsGrappleTarget(nextTargetId, mgr) ? g_tweakTargeting->GetGrappleMinClampScale() : 1.f, true);\n      x1d4_nextGroupDur = x1d8_nextGroupTimer = xf2_nextTargetId == kInvalidUniqueId\n                                                    ? g_tweakTargeting->GetNextLockOnEnterDuration()\n                                                    : g_tweakTargeting->GetNextLockOnSwitchDuration();\n      xf2_nextTargetId = nextTargetId;\n    }\n  }\n\n  if (x1d8_nextGroupTimer > 0.f) {\n    UpdateTargetParameters(x194_nextGroupA, mgr);\n    UpdateTargetParameters(x1b4_nextGroupB, mgr);\n    x1d8_nextGroupTimer = std::max(0.f, x1d8_nextGroupTimer - dt);\n    CTargetReticleRenderState::InterpolateWithClamp(x194_nextGroupA, x174_nextGroupInterp, x1b4_nextGroupB,\n                                                    1.f - x1d8_nextGroupTimer / x1d4_nextGroupDur);\n  } else {\n    UpdateTargetParameters(x174_nextGroupInterp, mgr);\n  }\n}\n\nvoid CCompoundTargetReticle::UpdateOrbitZoneGroup(float dt, const CStateManager& mgr) {\n  if (xf0_targetId == kInvalidUniqueId && xf2_nextTargetId != kInvalidUniqueId) {\n    x20c_ = std::min(1.f, 2.f * dt + x20c_);\n  } else {\n    x20c_ = std::max(0.f, x20c_ - 2.f * dt);\n  }\n\n  if (mgr.GetPlayer().IsShowingCrosshairs() &&\n      mgr.GetPlayerState()->GetCurrentVisor() != CPlayerState::EPlayerVisor::Scan) {\n    x1e8_crosshairsScale = std::min(1.f, dt / g_tweakTargeting->GetCrosshairsScaleDuration() + x1e8_crosshairsScale);\n  } else {\n    x1e8_crosshairsScale = std::max(0.f, x1e8_crosshairsScale - dt / g_tweakTargeting->GetCrosshairsScaleDuration());\n  }\n}\n\nvoid CCompoundTargetReticle::Draw(const CStateManager& mgr, bool hideLockon) {\n  if (mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Unmorphed &&\n      !mgr.GetCameraManager()->IsInCinematicCamera()) {\n    SCOPED_GRAPHICS_DEBUG_GROUP(\"CCompoundTargetReticle::Draw\", zeus::skCyan);\n    const zeus::CTransform camXf = mgr.GetCameraManager()->GetCurrentCameraTransform(mgr);\n    CGraphics::SetViewPointMatrix(camXf);\n    if (!hideLockon) {\n      DrawCurrLockOnGroup(camXf.basis, mgr);\n      DrawNextLockOnGroup(camXf.basis, mgr);\n      DrawOrbitZoneGroup(camXf.basis, mgr);\n    }\n    DrawGrappleGroup(camXf.basis, mgr, hideLockon);\n  }\n\n  if (x28_noDrawTicks > 0) {\n    --x28_noDrawTicks;\n  }\n}\n\nvoid CCompoundTargetReticle::DrawGrapplePoint(const CScriptGrapplePoint& point, float t, const CStateManager& mgr,\n                                              const zeus::CMatrix3f& rot, bool zEqual) {\n  zeus::CVector3f orbitPos = point.GetOrbitPosition(mgr);\n  zeus::CColor color;\n\n  if (point.GetGrappleParameters().GetLockSwingTurn()) {\n    color = g_tweakTargeting->GetLockedGrapplePointSelectColor();\n  } else {\n    color = g_tweakTargeting->GetGrapplePointSelectColor();\n  }\n\n  color = zeus::CColor::lerp(g_tweakTargeting->GetGrapplePointColor(), color, t);\n  zeus::CMatrix3f scale(\n      CalculateClampedScale(orbitPos, 1.f, g_tweakTargeting->GetGrappleClampMin(),\n                            g_tweakTargeting->GetGrappleClampMax(), mgr) *\n      ((1.f - t) * g_tweakTargeting->GetGrappleScale() + t * g_tweakTargeting->GetGrappleSelectScale()));\n  zeus::CTransform modelXf(rot * scale, orbitPos);\n  CGraphics::SetModelMatrix(modelXf);\n  CModelFlags flags(7, 0, 0, color);\n  x94_grapple->Draw(flags);\n}\n\nvoid CCompoundTargetReticle::DrawGrappleGroup(const zeus::CMatrix3f& rot, const CStateManager& mgr,\n                                              bool hideLockon) {\n  if (x28_noDrawTicks > 0) {\n    return;\n  }\n\n  if (mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::GrappleBeam) && x94_grapple.IsLoaded() &&\n      x20_prevState != EReticleState::Scan) {\n    if (hideLockon) {\n      for (const CEntity* ent : mgr.GetAllObjectList()) {\n        if (TCastToConstPtr<CScriptGrapplePoint> point = ent) {\n          if (point->GetActive()) {\n            if (point->GetAreaIdAlways() != kInvalidAreaId) {\n              const CGameArea* area = mgr.GetWorld()->GetAreaAlways(point->GetAreaIdAlways());\n              const auto occState =\n                  area->IsPostConstructed() ? area->GetOcclusionState() : CGameArea::EOcclusionState::Occluded;\n              if (occState != CGameArea::EOcclusionState::Visible) {\n                continue;\n              }\n            }\n            float t = 0.f;\n            if (point->GetUniqueId() == x1dc_grapplePoint0) {\n              t = x1e0_grapplePoint0T;\n            } else if (point->GetUniqueId() == x1de_grapplePoint1) {\n              t = x1e4_grapplePoint1T;\n            }\n            if (std::fabs(t) < 0.00001f) {\n              DrawGrapplePoint(*point, t, mgr, rot, true);\n            }\n          }\n        }\n      }\n    } else {\n      const TCastToConstPtr<CScriptGrapplePoint> point0 = mgr.GetObjectById(x1dc_grapplePoint0);\n      const TCastToConstPtr<CScriptGrapplePoint> point1 = mgr.GetObjectById(x1de_grapplePoint1);\n      for (int i = 0; i < 2; ++i) {\n        const CScriptGrapplePoint* point = i == 0 ? point0.GetPtr() : point1.GetPtr();\n        const float t = i == 0 ? x1e0_grapplePoint0T : x1e4_grapplePoint1T;\n        if (point) {\n          DrawGrapplePoint(*point, t, mgr, rot, false);\n        }\n      }\n    }\n  }\n}\n\nvoid CCompoundTargetReticle::DrawCurrLockOnGroup(const zeus::CMatrix3f& rot, const CStateManager& mgr) {\n  if (x28_noDrawTicks > 0) {\n    return;\n  }\n  if (x1e0_grapplePoint0T + x1e4_grapplePoint1T > 0 || x10c_currGroupInterp.GetFactor() == 0.f) {\n    return;\n  }\n\n  float lockBreakAlpha = x10c_currGroupInterp.GetFactor();\n  float visorFactor = mgr.GetPlayerState()->GetVisorTransitionFactor();\n  bool lockConfirm = false;\n  bool lockReticule = false;\n  switch (x20_prevState) {\n  case EReticleState::Combat:\n    lockConfirm = true;\n    lockReticule = true;\n    break;\n  case EReticleState::Scan:\n    lockConfirm = true;\n    break;\n  default:\n    break;\n  }\n\n  zeus::CMatrix3f lockBreakXf;\n  zeus::CColor lockBreakColor = zeus::skClear;\n  if (IsDamageOrbit(mgr.GetPlayer().GetOrbitRequest()) && x14c_currGroupB.GetFactor() == 0.f) {\n    zeus::CMatrix3f lockBreakRM;\n    for (int i = 0; i < 4; ++i) {\n      const int a = rand() % 9;\n      const auto b = std::div(a, 3);\n      lockBreakRM[b.rem][b.quot] += rand() / float(RAND_MAX) - 0.5f;\n    }\n    lockBreakXf = lockBreakRM.transposed();\n    if (x10c_currGroupInterp.GetFactor() > 0.8f) {\n      lockBreakColor = zeus::CColor(1.f, (x10c_currGroupInterp.GetFactor() - 0.8f) * 0.3f / 0.2f);\n    }\n    lockBreakAlpha = x10c_currGroupInterp.GetFactor() > 0.75f\n                         ? 1.f\n                         : std::max(0.f, (x10c_currGroupInterp.GetFactor() - 0.55f) / 0.2f);\n  }\n\n  if (lockConfirm && x4c_lockConfirm.IsLoaded()) {\n    const zeus::CMatrix3f scale(CalculateClampedScale(x10c_currGroupInterp.GetTargetPositionWorld(),\n                                                      x10c_currGroupInterp.GetRadiusWorld(),\n                                                      x10c_currGroupInterp.GetMinViewportClampScale() *\n                                                          g_tweakTargeting->GetLockConfirmClampMin(),\n                                                      g_tweakTargeting->GetLockConfirmClampMax(), mgr) *\n                                g_tweakTargeting->GetLockConfirmScale() / x10c_currGroupInterp.GetFactor());\n    const zeus::CTransform modelXf(lockBreakXf * (rot * zeus::CMatrix3f::RotateY(x1ec_seekerAngle) * scale),\n                                   x10c_currGroupInterp.GetTargetPositionWorld());\n    CGraphics::SetModelMatrix(modelXf);\n    zeus::CColor color = g_tweakTargeting->GetLockConfirmColor();\n    color.a() *= lockBreakAlpha;\n    const CModelFlags flags(7, 0, 0, lockBreakColor + color);\n    x4c_lockConfirm->Draw(flags);\n  }\n\n  if (lockReticule) {\n    if (x58_targetFlower.IsLoaded()) {\n      const zeus::CMatrix3f scale(CalculateClampedScale(x10c_currGroupInterp.GetTargetPositionWorld(),\n                                                        x10c_currGroupInterp.GetRadiusWorld(),\n                                                        x10c_currGroupInterp.GetMinViewportClampScale() *\n                                                            g_tweakTargeting->GetTargetFlowerClampMin(),\n                                                        g_tweakTargeting->GetTargetFlowerClampMax(), mgr) *\n                                  g_tweakTargeting->GetTargetFlowerScale() / lockBreakAlpha);\n      const zeus::CTransform modelXf(lockBreakXf * (rot * zeus::CMatrix3f::RotateY(x1f0_xrayRetAngle) * scale),\n                                     x10c_currGroupInterp.GetTargetPositionWorld());\n      CGraphics::SetModelMatrix(modelXf);\n      zeus::CColor color = g_tweakTargeting->GetTargetFlowerColor();\n      color.a() *= lockBreakAlpha * visorFactor;\n      const CModelFlags flags(7, 0, 0, lockBreakColor + color);\n      x58_targetFlower->Draw(flags);\n    }\n\n    if (x1f8_missileBracketTimer != 0.f && x64_missileBracket.IsLoaded()) {\n      const float t =\n          std::fabs((x1fc_missileBracketScaleTimer - 0.5f * g_tweakTargeting->GetMissileBracketScaleDuration()) / 0.5f *\n                    g_tweakTargeting->GetMissileBracketScaleDuration());\n      const float tscale = ((1.f - t) * g_tweakTargeting->GetMissileBracketScaleEnd() +\n                            t * g_tweakTargeting->GetMissileBracketScaleStart());\n      const zeus::CMatrix3f scale(CalculateClampedScale(x10c_currGroupInterp.GetTargetPositionWorld(),\n                                                        x10c_currGroupInterp.GetRadiusWorld(),\n                                                        x10c_currGroupInterp.GetMinViewportClampScale() *\n                                                            g_tweakTargeting->GetMissileBracketClampMin(),\n                                                        g_tweakTargeting->GetMissileBracketClampMax(), mgr) *\n                                  std::fabs(x1f8_missileBracketTimer) / g_tweakTargeting->GetMissileBracketDuration() *\n                                  tscale / x10c_currGroupInterp.GetFactor());\n      for (int i = 0; i < 4; ++i) {\n        const zeus::CTransform modelXf(\n            lockBreakXf * rot * zeus::CMatrix3f(zeus::CVector3f{i < 2 ? 1.f : -1.f, 1.f, i & 0x1 ? 1.f : -1.f}) * scale,\n            x10c_currGroupInterp.GetTargetPositionWorld());\n        CGraphics::SetModelMatrix(modelXf);\n        zeus::CColor color = g_tweakTargeting->GetMissileBracketColor();\n        color.a() *= lockBreakAlpha * visorFactor;\n        const CModelFlags flags(7, 0, 0, lockBreakColor + color);\n        x64_missileBracket->Draw(flags);\n      }\n    }\n\n    const zeus::CMatrix3f scale(CalculateClampedScale(x10c_currGroupInterp.GetTargetPositionWorld(),\n                                                      x10c_currGroupInterp.GetRadiusWorld(),\n                                                      x10c_currGroupInterp.GetMinViewportClampScale() *\n                                                          g_tweakTargeting->GetChargeGaugeClampMin(),\n                                                      g_tweakTargeting->GetChargeGaugeClampMax(), mgr) *\n                                1.f / x10c_currGroupInterp.GetFactor() * g_tweakTargeting->GetOuterBeamSquaresScale());\n    zeus::CMatrix3f outerBeamXf = rot * scale;\n    for (int i = 0; i < 9; ++i) {\n      SOuterItemInfo& info = xe0_outerBeamIconSquares[i];\n      if (info.x0_model.IsLoaded()) {\n        zeus::CTransform modelXf(lockBreakXf * outerBeamXf * zeus::CMatrix3f::RotateY(info.x10_rotAng),\n                                 x10c_currGroupInterp.GetTargetPositionWorld());\n        CGraphics::SetModelMatrix(modelXf);\n        zeus::CColor color = g_tweakTargeting->GetOuterBeamSquareColor();\n        color.a() *= lockBreakAlpha * visorFactor;\n        CModelFlags flags(7, 0, 0, lockBreakColor + color);\n        info.x0_model->Draw(flags);\n      }\n    }\n\n    if (xc4_chargeGauge.x0_model.IsLoaded()) {\n      const zeus::CMatrix3f scale(CalculateClampedScale(x10c_currGroupInterp.GetTargetPositionWorld(),\n                                                        x10c_currGroupInterp.GetRadiusWorld(),\n                                                        x10c_currGroupInterp.GetMinViewportClampScale() *\n                                                            g_tweakTargeting->GetChargeGaugeClampMin(),\n                                                        g_tweakTargeting->GetChargeGaugeClampMax(), mgr) *\n                                  g_tweakTargeting->GetChargeGaugeScale() / x10c_currGroupInterp.GetFactor());\n      const zeus::CMatrix3f chargeGaugeXf = rot * scale * zeus::CMatrix3f::RotateY(xc4_chargeGauge.x10_rotAng);\n      const float pulseT =\n          std::fabs(std::fmod(CGraphics::GetSecondsMod900(), g_tweakTargeting->GetChargeGaugePulsePeriod()));\n      const zeus::CColor gaugeColor =\n          zeus::CColor::lerp(g_tweakTargeting->GetChargeGaugeNonFullColor(),\n                             zeus::CColor::lerp(g_tweakTargeting->GetChargeGaugePulseColorHigh(),\n                                                g_tweakTargeting->GetChargeGaugePulseColorLow(),\n                                                pulseT < 0.5f * g_tweakTargeting->GetChargeGaugePulsePeriod()\n                                                    ? pulseT / (0.5f * g_tweakTargeting->GetChargeGaugePulsePeriod())\n                                                    : (g_tweakTargeting->GetChargeGaugePulsePeriod() - pulseT) /\n                                                          (0.5f * g_tweakTargeting->GetChargeGaugePulsePeriod())),\n                             x214_fullChargeFadeTimer / g_tweakTargeting->GetFullChargeFadeDuration());\n      zeus::CTransform modelXf(lockBreakXf * chargeGaugeXf, x10c_currGroupInterp.GetTargetPositionWorld());\n      CGraphics::SetModelMatrix(modelXf);\n      zeus::CColor color = gaugeColor;\n      color.a() *= lockBreakAlpha * visorFactor;\n      const CModelFlags flags(7, 0, 0, lockBreakColor + color);\n      xc4_chargeGauge.x0_model->Draw(flags);\n\n      if (xa0_chargeTickFirst.IsLoaded()) {\n        const CPlayerGun* gun = mgr.GetPlayer().GetPlayerGun();\n        const int numTicks =\n            int(g_tweakTargeting->GetChargeTickCount() * (gun->IsCharging() ? gun->GetChargeBeamFactor() : 0.f));\n        for (int i = 0; i < numTicks; ++i) {\n          const CModelFlags tickFlags(7, 0, 0, lockBreakColor + color);\n          xa0_chargeTickFirst->Draw(tickFlags);\n          modelXf.rotateLocalY(g_tweakTargeting->GetChargeTickAnglePitch());\n          CGraphics::SetModelMatrix(modelXf);\n        }\n      }\n    }\n\n    if (x208_lockonTimer > 0.f && x70_innerBeamIcon.IsLoaded()) {\n      const zeus::CColor* iconColor;\n      switch (x200_beam) {\n      case CPlayerState::EBeamId::Power:\n        iconColor = &g_tweakTargeting->GetInnerBeamColorPower();\n        break;\n      case CPlayerState::EBeamId::Ice:\n        iconColor = &g_tweakTargeting->GetInnerBeamColorIce();\n        break;\n      case CPlayerState::EBeamId::Wave:\n        iconColor = &g_tweakTargeting->GetInnerBeamColorWave();\n        break;\n      default:\n        iconColor = &g_tweakTargeting->GetInnerBeamColorPlasma();\n        break;\n      }\n\n      const zeus::CMatrix3f scale(\n          CalculateClampedScale(x10c_currGroupInterp.GetTargetPositionWorld(), x10c_currGroupInterp.GetRadiusWorld(),\n                                x10c_currGroupInterp.GetMinViewportClampScale() *\n                                    g_tweakTargeting->GetInnerBeamClampMin(),\n                                g_tweakTargeting->GetInnerBeamClampMax(), mgr) *\n          g_tweakTargeting->GetInnerBeamScale() * (x208_lockonTimer / g_tweakTargeting->GetLockonDuration()) /\n          x10c_currGroupInterp.GetFactor());\n      const zeus::CTransform modelXf(lockBreakXf * rot * scale, x10c_currGroupInterp.GetTargetPositionWorld());\n      CGraphics::SetModelMatrix(modelXf);\n      zeus::CColor color = *iconColor;\n      color.a() *= lockBreakAlpha * visorFactor;\n      const CModelFlags flags(7, 0, 0, lockBreakColor + color);\n      x70_innerBeamIcon->Draw(flags);\n    }\n\n    if (x210_lockFireTimer > 0.f && x7c_lockFire.IsLoaded()) {\n      const zeus::CMatrix3f scale(CalculateClampedScale(x10c_currGroupInterp.GetTargetPositionWorld(),\n                                                        x10c_currGroupInterp.GetRadiusWorld(),\n                                                        x10c_currGroupInterp.GetMinViewportClampScale() *\n                                                            g_tweakTargeting->GetLockFireClampMin(),\n                                                        g_tweakTargeting->GetLockFireClampMax(), mgr) *\n                                  g_tweakTargeting->GetLockFireScale() / x10c_currGroupInterp.GetFactor());\n      const zeus::CTransform modelXf(lockBreakXf * rot * scale * zeus::CMatrix3f::RotateY(x1f0_xrayRetAngle),\n                                     x10c_currGroupInterp.GetTargetPositionWorld());\n      CGraphics::SetModelMatrix(modelXf);\n      zeus::CColor color = g_tweakTargeting->GetLockFireColor();\n      color.a() *= visorFactor * lockBreakAlpha * (x210_lockFireTimer / g_tweakTargeting->GetLockFireDuration());\n      const CModelFlags flags(7, 0, 0, lockBreakColor + color);\n      x7c_lockFire->Draw(flags);\n    }\n\n    if (x208_lockonTimer > 0.f && x88_lockDagger.IsLoaded()) {\n      const float t = std::fabs((x210_lockFireTimer - 0.5f * g_tweakTargeting->GetLockFireDuration()) / 0.5f *\n                                g_tweakTargeting->GetLockFireDuration());\n      const float tscale =\n          ((1.f - t) * g_tweakTargeting->GetLockDaggerScaleEnd() + t * g_tweakTargeting->GetLockDaggerScaleStart());\n      const zeus::CMatrix3f scale(\n          CalculateClampedScale(x10c_currGroupInterp.GetTargetPositionWorld(), x10c_currGroupInterp.GetRadiusWorld(),\n                                x10c_currGroupInterp.GetMinViewportClampScale() *\n                                    g_tweakTargeting->GetLockDaggerClampMin(),\n                                g_tweakTargeting->GetLockDaggerClampMax(), mgr) *\n          tscale * (x208_lockonTimer / g_tweakTargeting->GetLockonDuration()) / x10c_currGroupInterp.GetFactor());\n      zeus::CMatrix3f lockDaggerXf = rot * scale;\n      for (int i = 0; i < 3; ++i) {\n        float ang;\n        switch (i) {\n        case 0:\n          ang = g_tweakTargeting->GetLockDaggerAngle0();\n          break;\n        case 1:\n          ang = g_tweakTargeting->GetLockDaggerAngle1();\n          break;\n        default:\n          ang = g_tweakTargeting->GetLockDaggerAngle2();\n          break;\n        }\n        const zeus::CTransform modelXf(lockBreakXf * lockDaggerXf * zeus::CMatrix3f::RotateY(ang),\n                                       x10c_currGroupInterp.GetTargetPositionWorld());\n        CGraphics::SetModelMatrix(modelXf);\n        zeus::CColor color = g_tweakTargeting->GetLockDaggerColor();\n        color.a() *= visorFactor * lockBreakAlpha;\n        const CModelFlags flags(7, 0, 0, lockBreakColor + color);\n        x88_lockDagger->Draw(flags);\n      }\n    }\n  }\n}\n\nvoid CCompoundTargetReticle::DrawNextLockOnGroup(const zeus::CMatrix3f& rot, const CStateManager& mgr) {\n  if (x28_noDrawTicks > 0) {\n    return;\n  }\n\n  zeus::CVector3f position = x174_nextGroupInterp.GetTargetPositionWorld();\n  float visorFactor = mgr.GetPlayerState()->GetVisorTransitionFactor();\n\n  bool scanRet = false;\n  bool xrayRet = false;\n  bool thermalRet = false;\n  switch (x20_prevState) {\n  case EReticleState::Scan:\n    scanRet = true;\n    break;\n  case EReticleState::XRay:\n    xrayRet = true;\n    break;\n  case EReticleState::Thermal:\n    thermalRet = true;\n    break;\n  default:\n    break;\n  }\n\n  if (!xrayRet && x174_nextGroupInterp.GetFactor() > 0.f && x40_seeker.IsLoaded()) {\n    const zeus::CMatrix3f scale(\n        CalculateClampedScale(position, x174_nextGroupInterp.GetRadiusWorld(),\n                              x174_nextGroupInterp.GetMinViewportClampScale() * g_tweakTargeting->GetSeekerClampMin(),\n                              g_tweakTargeting->GetSeekerClampMax(), mgr) *\n        g_tweakTargeting->GetSeekerScale());\n    const zeus::CTransform modelXf(rot * zeus::CMatrix3f::RotateY(x1ec_seekerAngle) * scale,\n                                   x174_nextGroupInterp.GetTargetPositionWorld());\n    CGraphics::SetModelMatrix(modelXf);\n    zeus::CColor color = g_tweakTargeting->GetSeekerColor();\n    color.a() *= x174_nextGroupInterp.GetFactor();\n    const CModelFlags flags(7, 0, 0, color);\n    x40_seeker->Draw(flags);\n  }\n\n  if (xrayRet && xac_xrayRetRing.IsLoaded()) {\n    const zeus::CMatrix3f scale(\n        CalculateClampedScale(position, x174_nextGroupInterp.GetRadiusWorld(),\n                              x174_nextGroupInterp.GetMinViewportClampScale() * g_tweakTargeting->GetReticuleClampMin(),\n                              g_tweakTargeting->GetReticuleClampMax(), mgr) *\n        g_tweakTargeting->GetReticuleScale());\n    const zeus::CTransform modelXf(rot * scale * zeus::CMatrix3f::RotateY(x1f0_xrayRetAngle),\n                                   x174_nextGroupInterp.GetTargetPositionWorld());\n    CGraphics::SetModelMatrix(modelXf);\n    zeus::CColor color = g_tweakTargeting->GetXRayRetRingColor();\n    color.a() *= visorFactor;\n    const CModelFlags flags(7, 0, 0, color);\n    xac_xrayRetRing->Draw(flags);\n  }\n\n  if (thermalRet && xb8_thermalReticle.IsLoaded()) {\n    const zeus::CMatrix3f scale(\n        CalculateClampedScale(position, x174_nextGroupInterp.GetRadiusWorld(),\n                              x174_nextGroupInterp.GetMinViewportClampScale() * g_tweakTargeting->GetReticuleClampMin(),\n                              g_tweakTargeting->GetReticuleClampMax(), mgr) *\n        g_tweakTargeting->GetReticuleScale());\n    const zeus::CTransform modelXf(rot * scale, x174_nextGroupInterp.GetTargetPositionWorld());\n    CGraphics::SetModelMatrix(modelXf);\n    zeus::CColor color = g_tweakTargeting->GetThermalReticuleColor();\n    color.a() *= visorFactor;\n    const CModelFlags flags(7, 0, 0, color);\n    xb8_thermalReticle->Draw(flags);\n  }\n\n  if (scanRet && visorFactor > 0.f) {\n    float factor = visorFactor * x174_nextGroupInterp.GetFactor();\n    const zeus::CMatrix3f scale(CalculateClampedScale(position, x174_nextGroupInterp.GetRadiusWorld(),\n                                                      x174_nextGroupInterp.GetMinViewportClampScale() *\n                                                          g_tweakTargeting->GetScanTargetClampMin(),\n                                                      g_tweakTargeting->GetScanTargetClampMax(), mgr) *\n                                (1.f / factor));\n    const zeus::CTransform modelXf(rot * scale, x174_nextGroupInterp.GetTargetPositionWorld());\n    CGraphics::SetModelMatrix(modelXf);\n    // compare, GX_LESS, no update\n    float alpha = 0.5f * factor;\n    zeus::CColor color = g_tweakGuiColors->GetScanReticuleColor();\n    color.a() *= alpha;\n    // for (size_t i = 0; i < m_scanRetRenderer.m_lineRenderers.size(); ++i) {\n    //   const float lineWidth = i != 0 ? 2.5f : 1.f;\n    //   auto& rend = *m_scanRetRenderer.m_lineRenderers[i];\n    //   rend.Reset();\n    //   rend.AddVertex({-0.5f, 0.f, 0.f}, color, lineWidth);\n    //   rend.AddVertex({-20.5f, 0.f, 0.f}, color, lineWidth);\n    //   rend.AddVertex({0.5f, 0.f, 0.f}, color, lineWidth);\n    //   rend.AddVertex({20.5f, 0.f, 0.f}, color, lineWidth);\n    //   rend.AddVertex({0.f, 0.f, -0.5f}, color, lineWidth);\n    //   rend.AddVertex({0.f, 0.f, -20.5f}, color, lineWidth);\n    //   rend.AddVertex({0.f, 0.f, 0.5f}, color, lineWidth);\n    //   rend.AddVertex({0.f, 0.f, 20.5f}, color, lineWidth);\n    //   rend.Render();\n    //\n    //   for (size_t j = 0; j < m_scanRetRenderer.m_stripRenderers[i].size(); ++j) {\n    //     const float xSign = j < 2 ? -1.f : 1.f;\n    //     const float zSign = (j & 0x1) != 0 ? -1.f : 1.f;\n    //     // begin line strip\n    //     auto& stripRend = *m_scanRetRenderer.m_stripRenderers[i][j];\n    //     stripRend.Reset();\n    //     stripRend.AddVertex({0.5f * xSign, 0.f, 0.1f * zSign}, color, lineWidth);\n    //     stripRend.AddVertex({0.5f * xSign, 0.f, 0.35f * zSign}, color, lineWidth);\n    //     stripRend.AddVertex({0.35f * xSign, 0.f, 0.5f * zSign}, color, lineWidth);\n    //     stripRend.AddVertex({0.1f * xSign, 0.f, 0.5f * zSign}, color, lineWidth);\n    //     stripRend.Render();\n    //   }\n    // }\n  }\n}\n\nvoid CCompoundTargetReticle::DrawOrbitZoneGroup(const zeus::CMatrix3f& rot, const CStateManager& mgr) {\n  if (x28_noDrawTicks > 0) {\n    return;\n  }\n\n  if (x1e8_crosshairsScale > 0.f && x34_crosshairs.IsLoaded()) {\n    CGraphics::SetModelMatrix(zeus::CTransform(rot, xf4_targetPos) * zeus::CTransform::Scale(x1e8_crosshairsScale));\n    zeus::CColor color = g_tweakTargeting->GetCrosshairsColor();\n    color.a() *= x1e8_crosshairsScale;\n    const CModelFlags flags(7, 0, 0, color);\n    x34_crosshairs->Draw(flags);\n  }\n}\n\nvoid CCompoundTargetReticle::UpdateTargetParameters(CTargetReticleRenderState& state, const CStateManager& mgr) {\n  if (const auto act = TCastToConstPtr<CActor>(mgr.GetAllObjectList().GetObjectById(state.GetTargetId()))) {\n    state.SetRadiusWorld(CalculateRadiusWorld(*act, mgr));\n    state.SetTargetPositionWorld(CalculatePositionWorld(*act, mgr));\n  } else if (state.GetIsOrbitZoneIdlePosition()) {\n    state.SetRadiusWorld(1.f);\n    state.SetTargetPositionWorld((x20_prevState == EReticleState::XRay || x20_prevState == EReticleState::Thermal)\n                                     ? x100_laggingTargetPos\n                                     : xf4_targetPos);\n  }\n}\n\nfloat CCompoundTargetReticle::CalculateRadiusWorld(const CActor& act, const CStateManager& mgr) const {\n  const auto tb = act.GetTouchBounds();\n  const zeus::CAABox aabb = tb ? *tb : zeus::CAABox(act.GetAimPosition(mgr, 0.f), act.GetAimPosition(mgr, 0.f));\n\n  float radius;\n  const zeus::CVector3f delta = aabb.max - aabb.min;\n  switch (g_tweakTargeting->GetTargetRadiusMode()) {\n  case 0: {\n    radius = std::min(delta.x(), std::min(delta.y(), delta.z())) * 0.5f;\n    break;\n  }\n  case 1: {\n    radius = std::max(delta.x(), std::max(delta.y(), delta.z())) * 0.5f;\n    break;\n  }\n  default: {\n    radius = (delta.x() + delta.y() + delta.z()) / 6.f;\n    break;\n  }\n  }\n\n  return radius > 0.f ? radius : 1.f;\n}\n\nzeus::CVector3f CCompoundTargetReticle::CalculatePositionWorld(const CActor& act, const CStateManager& mgr) const {\n  if (x20_prevState == EReticleState::Scan) {\n    return act.GetOrbitPosition(mgr);\n  }\n  return act.GetAimPosition(mgr, 0.f);\n}\n\nzeus::CVector3f CCompoundTargetReticle::CalculateOrbitZoneReticlePosition(const CStateManager& mgr, bool lag) const {\n  const CGameCamera* curCam = mgr.GetCameraManager()->GetCurrentCamera(mgr);\n  const float distMul =\n      224.f / float(g_tweakPlayer->GetOrbitScreenBoxHalfExtentY(0)) / std::tan(zeus::degToRad(0.5f * curCam->GetFov()));\n  const zeus::CTransform camXf = mgr.GetCameraManager()->GetCurrentCameraTransform(mgr);\n  zeus::CVector3f lookDir = camXf.basis[1];\n  if (lag) {\n    lookDir = x10_laggingOrientation.transform(lookDir);\n  }\n  return lookDir * distMul + camXf.origin;\n}\n\nbool CCompoundTargetReticle::IsGrappleTarget(TUniqueId uid, const CStateManager& mgr) const {\n  return TCastToConstPtr<CScriptGrapplePoint>(mgr.GetAllObjectList().GetObjectById(uid)).operator bool();\n}\n\nfloat CCompoundTargetReticle::CalculateClampedScale(const zeus::CVector3f& pos, float scale, float clampMin,\n                                                    float clampMax, const CStateManager& mgr) {\n  const CGameCamera* cam = mgr.GetCameraManager()->GetCurrentCamera(mgr);\n  mgr.GetCameraManager()->GetCurrentCameraTransform(mgr);\n  zeus::CVector3f viewPos = cam->GetTransform().transposeRotate(pos - cam->GetTransform().origin);\n  const float realX = cam->GetPerspectiveMatrix().multiplyOneOverW(viewPos).x();\n  const float offsetX = cam->GetPerspectiveMatrix().multiplyOneOverW(viewPos + zeus::CVector3f(scale, 0.f, 0.f)).x();\n  const float unclampedX = (offsetX - realX) * 640;\n  return zeus::clamp(clampMin, unclampedX, clampMax) / unclampedX * scale;\n}\n\nvoid CCompoundTargetReticle::Touch() {\n  if (x34_crosshairs.IsLoaded()) {\n    x34_crosshairs->Touch(0);\n  }\n\n  if (x40_seeker.IsLoaded()) {\n    x40_seeker->Touch(0);\n  }\n\n  if (x4c_lockConfirm.IsLoaded()) {\n    x4c_lockConfirm->Touch(0);\n  }\n\n  if (x58_targetFlower.IsLoaded()) {\n    x58_targetFlower->Touch(0);\n  }\n\n  if (x64_missileBracket.IsLoaded()) {\n    x64_missileBracket->Touch(0);\n  }\n\n  if (x70_innerBeamIcon.IsLoaded()) {\n    x70_innerBeamIcon->Touch(0);\n  }\n\n  if (x7c_lockFire.IsLoaded()) {\n    x7c_lockFire->Touch(0);\n  }\n\n  if (x88_lockDagger.IsLoaded()) {\n    x88_lockDagger->Touch(0);\n  }\n\n  if (x94_grapple.IsLoaded()) {\n    x94_grapple->Touch(0);\n  }\n\n  if (xa0_chargeTickFirst.IsLoaded()) {\n    xa0_chargeTickFirst->Touch(0);\n  }\n\n  if (xac_xrayRetRing.IsLoaded()) {\n    xac_xrayRetRing->Touch(0);\n  }\n\n  if (xb8_thermalReticle.IsLoaded()) {\n    xb8_thermalReticle->Touch(0);\n  }\n\n  if (xc4_chargeGauge.x0_model.IsLoaded()) {\n    xc4_chargeGauge.x0_model->Touch(0);\n  }\n\n  for (SOuterItemInfo& info : xe0_outerBeamIconSquares) {\n    if (info.x0_model.IsLoaded()) {\n      info.x0_model->Touch(0);\n    }\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CCompoundTargetReticle.hpp",
    "content": "#pragma once\n\n#include <array>\n#include <vector>\n\n#include \"Runtime/CPlayerState.hpp\"\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n\n#include <zeus/CQuaternion.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CActor;\nclass CModel;\nclass CScriptGrapplePoint;\nclass CStateManager;\n\nclass CTargetReticleRenderState {\n  TUniqueId x0_target;\n  float x4_radiusWorld;\n  zeus::CVector3f x8_positionWorld;\n  float x14_factor;\n  float x18_minVpClampScale;\n  bool x1c_orbitZoneIdlePosition;\n\npublic:\n  static const CTargetReticleRenderState skZeroRenderState;\n\n  constexpr CTargetReticleRenderState(TUniqueId target, float radiusWorld, const zeus::CVector3f& positionWorld,\n                                      float factor, float minVpClampScale, bool orbitZoneIdlePosition)\n  : x0_target(target)\n  , x4_radiusWorld(radiusWorld)\n  , x8_positionWorld(positionWorld)\n  , x14_factor(factor)\n  , x18_minVpClampScale(minVpClampScale)\n  , x1c_orbitZoneIdlePosition(orbitZoneIdlePosition) {}\n  constexpr void SetTargetId(TUniqueId id) { x0_target = id; }\n  constexpr void SetFactor(float factor) { x14_factor = factor; }\n  constexpr void SetIsOrbitZoneIdlePosition(bool orbit) { x1c_orbitZoneIdlePosition = orbit; }\n  constexpr float GetMinViewportClampScale() const { return x18_minVpClampScale; }\n  constexpr float GetFactor() const { return x14_factor; }\n  constexpr float GetRadiusWorld() const { return x4_radiusWorld; }\n  constexpr const zeus::CVector3f& GetTargetPositionWorld() const { return x8_positionWorld; }\n  constexpr bool GetIsOrbitZoneIdlePosition() const { return x1c_orbitZoneIdlePosition; }\n  constexpr void SetTargetPositionWorld(const zeus::CVector3f& position) { x8_positionWorld = position; }\n  constexpr void SetRadiusWorld(float radius) { x4_radiusWorld = radius; }\n  constexpr TUniqueId GetTargetId() const { return x0_target; }\n  constexpr void SetMinViewportClampScale(float scale) { x18_minVpClampScale = scale; }\n  static void InterpolateWithClamp(const CTargetReticleRenderState& a, CTargetReticleRenderState& out,\n                                   const CTargetReticleRenderState& b, float t);\n};\n\nclass CCompoundTargetReticle {\npublic:\n  struct SOuterItemInfo {\n    TCachedToken<CModel> x0_model;\n    float xc_offshootBaseAngle = 0.f;\n    float x10_rotAng = 0.f;\n    float x14_baseAngle = 0.f;\n    float x18_offshootAngleDelta = 0.f;\n    explicit SOuterItemInfo(std::string_view);\n  };\n\nprivate:\n  enum class EReticleState { Combat, Scan, XRay, Thermal, Four, Unspecified };\n\n  zeus::CQuaternion x0_leadingOrientation;\n  zeus::CQuaternion x10_laggingOrientation;\n  EReticleState x20_prevState = EReticleState::Unspecified;\n  EReticleState x24_nextState = EReticleState::Unspecified;\n  u32 x28_noDrawTicks = 0;\n  float x2c_overshootOffsetHalf;\n  float x30_premultOvershootOffset;\n  TCachedToken<CModel> x34_crosshairs;\n  TCachedToken<CModel> x40_seeker;\n  TCachedToken<CModel> x4c_lockConfirm;\n  TCachedToken<CModel> x58_targetFlower;\n  TCachedToken<CModel> x64_missileBracket;\n  TCachedToken<CModel> x70_innerBeamIcon;\n  TCachedToken<CModel> x7c_lockFire;\n  TCachedToken<CModel> x88_lockDagger;\n  TCachedToken<CModel> x94_grapple;\n  TCachedToken<CModel> xa0_chargeTickFirst;\n  TCachedToken<CModel> xac_xrayRetRing;\n  TCachedToken<CModel> xb8_thermalReticle;\n  SOuterItemInfo xc4_chargeGauge;\n  std::vector<SOuterItemInfo> xe0_outerBeamIconSquares;\n  TUniqueId xf0_targetId;\n  TUniqueId xf2_nextTargetId;\n  zeus::CVector3f xf4_targetPos;\n  zeus::CVector3f x100_laggingTargetPos;\n  CTargetReticleRenderState x10c_currGroupInterp = CTargetReticleRenderState::skZeroRenderState;\n  CTargetReticleRenderState x12c_currGroupA = CTargetReticleRenderState::skZeroRenderState;\n  CTargetReticleRenderState x14c_currGroupB = CTargetReticleRenderState::skZeroRenderState;\n  float x16c_currGroupDur = 0.f;\n  float x170_currGroupTimer = 0.f;\n  CTargetReticleRenderState x174_nextGroupInterp = CTargetReticleRenderState::skZeroRenderState;\n  CTargetReticleRenderState x194_nextGroupA = CTargetReticleRenderState::skZeroRenderState;\n  CTargetReticleRenderState x1b4_nextGroupB = CTargetReticleRenderState::skZeroRenderState;\n  float x1d4_nextGroupDur = 0.f;\n  float x1d8_nextGroupTimer = 0.f;\n  TUniqueId x1dc_grapplePoint0 = kInvalidUniqueId;\n  TUniqueId x1de_grapplePoint1 = kInvalidUniqueId;\n  float x1e0_grapplePoint0T = 0.f;\n  float x1e4_grapplePoint1T = 0.f;\n  float x1e8_crosshairsScale = 0.f;\n  float x1ec_seekerAngle = 0.f;\n  float x1f0_xrayRetAngle = 0.f;\n  bool x1f4_missileActive = false;\n  float x1f8_missileBracketTimer = 0.f;\n  float x1fc_missileBracketScaleTimer = 0.f;\n  CPlayerState::EBeamId x200_beam = CPlayerState::EBeamId::Power;\n  float x204_chargeGaugeOvershootTimer = 0.f;\n  float x208_lockonTimer;\n  float x20c_ = 0.f;\n  float x210_lockFireTimer = 0.f;\n  float x214_fullChargeFadeTimer = 0.f;\n  bool x218_beamShot = false;\n  bool x219_missileShot = false;\n  bool x21a_fullyCharged = false;\n  u8 x21b_ = 0;\n  u32 x21c_ = 0;\n  u32 x220_ = 0;\n  u32 x228_ = 0;\n\n  void DrawGrapplePoint(const CScriptGrapplePoint& point, float t, const CStateManager& mgr, const zeus::CMatrix3f& rot,\n                        bool zEqual);\n\npublic:\n  explicit CCompoundTargetReticle(const CStateManager&);\n\n  void SetLeadingOrientation(const zeus::CQuaternion& o) { x0_leadingOrientation = o; }\n  bool CheckLoadComplete() const { return true; }\n  EReticleState GetDesiredReticleState(const CStateManager&) const;\n  void Update(float, const CStateManager&);\n  void UpdateCurrLockOnGroup(float, const CStateManager&);\n  void UpdateNextLockOnGroup(float, const CStateManager&);\n  void UpdateOrbitZoneGroup(float, const CStateManager&);\n  void Draw(const CStateManager&, bool hideLockon);\n  void DrawGrappleGroup(const zeus::CMatrix3f& rot, const CStateManager&, bool);\n  void DrawCurrLockOnGroup(const zeus::CMatrix3f& rot, const CStateManager&);\n  void DrawNextLockOnGroup(const zeus::CMatrix3f& rot, const CStateManager&);\n  void DrawOrbitZoneGroup(const zeus::CMatrix3f& rot, const CStateManager&);\n  void UpdateTargetParameters(CTargetReticleRenderState&, const CStateManager&);\n  float CalculateRadiusWorld(const CActor&, const CStateManager&) const;\n  zeus::CVector3f CalculatePositionWorld(const CActor&, const CStateManager&) const;\n  zeus::CVector3f CalculateOrbitZoneReticlePosition(const CStateManager& mgr, bool lag) const;\n  bool IsGrappleTarget(TUniqueId, const CStateManager&) const;\n  static float CalculateClampedScale(const zeus::CVector3f&, float, float, float, const CStateManager&);\n  void Touch();\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CConsoleOutputWindow.cpp",
    "content": "#include \"Runtime/GuiSys/CConsoleOutputWindow.hpp\"\n#include \"Runtime/Graphics/CGraphics.hpp\"\n\nnamespace metaforce {\n\nCConsoleOutputWindow::CConsoleOutputWindow(int, float, float) : CIOWin(\"Console Output Window\") {}\n\nCIOWin::EMessageReturn CConsoleOutputWindow::OnMessage(const CArchitectureMessage&, CArchitectureQueue&) {\n  return EMessageReturn::Normal;\n}\n\nvoid CConsoleOutputWindow::Draw() {\n  // SCOPED_GRAPHICS_DEBUG_GROUP(\"CConsoleOutputWindow::Draw\", zeus::skGreen);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CConsoleOutputWindow.hpp",
    "content": "#pragma once\n\n#include \"Runtime/CIOWin.hpp\"\n\nnamespace metaforce {\n\nclass CConsoleOutputWindow : public CIOWin {\npublic:\n  CConsoleOutputWindow(int, float, float);\n  EMessageReturn OnMessage(const CArchitectureMessage&, CArchitectureQueue&) override;\n  void Draw() override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CDrawStringOptions.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/GuiSys/CGuiTextSupport.hpp\"\n\nnamespace metaforce {\n\nclass CDrawStringOptions {\n  friend class CColorOverrideInstruction;\n  friend class CFontRenderState;\n  friend class CRasterFont;\n  friend class CTextExecuteBuffer;\n  friend class CBlockInstruction;\n  friend class CWordInstruction;\n\n  ETextDirection x0_direction = ETextDirection::Horizontal;\n  std::vector<CTextColor> x4_colors;\n\npublic:\n  CDrawStringOptions() : x4_colors(16) {}\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CErrorOutputWindow.cpp",
    "content": "#include \"Runtime/GuiSys/CErrorOutputWindow.hpp\"\n\n#include \"Runtime/Graphics/CGraphics.hpp\"\n\nnamespace metaforce {\n\nCErrorOutputWindow::CErrorOutputWindow(bool flag) : CIOWin(\"Error Output Window\") {\n  x18_24_ = false;\n  x18_25_ = true;\n  x18_26_ = true;\n  x18_27_ = true;\n  x18_28_ = flag;\n}\n\nCIOWin::EMessageReturn CErrorOutputWindow::OnMessage(const CArchitectureMessage&, CArchitectureQueue&) {\n  return EMessageReturn::Normal;\n}\n\nvoid CErrorOutputWindow::Draw() {\n  // SCOPED_GRAPHICS_DEBUG_GROUP(\"CErrorOutputWindow::Draw\", zeus::skGreen);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CErrorOutputWindow.hpp",
    "content": "#pragma once\n\n#include \"Runtime/CIOWin.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n\nnamespace metaforce {\n\nclass CErrorOutputWindow : public CIOWin {\npublic:\n  enum class State { Zero, One, Two };\n\nprivate:\n  State x14_state = State::Zero;\n  bool x18_24_;\n  bool x18_25_;\n  bool x18_26_;\n  bool x18_27_;\n  bool x18_28_;\n  const char16_t* x1c_msg;\n\npublic:\n  explicit CErrorOutputWindow(bool);\n  EMessageReturn OnMessage(const CArchitectureMessage&, CArchitectureQueue&) override;\n  bool GetIsContinueDraw() const override { return int(x14_state) < 2; }\n  void Draw() override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CFontImageDef.cpp",
    "content": "#include \"Runtime/GuiSys/CFontImageDef.hpp\"\n\n#include <algorithm>\n\n#include \"Runtime/Graphics/CTexture.hpp\"\n\nnamespace metaforce {\n\nCFontImageDef::CFontImageDef(const std::vector<TToken<CTexture>>& texs, float interval,\n                             const zeus::CVector2f& cropFactor)\n: x0_fps(interval), x14_cropFactor(cropFactor) {\n  x4_texs.reserve(texs.size());\n  for (const TToken<CTexture>& tok : texs) {\n    x4_texs.emplace_back(tok);\n  }\n}\n\nCFontImageDef::CFontImageDef(const TToken<CTexture>& tex, const zeus::CVector2f& cropFactor)\n: x0_fps(0.f), x14_cropFactor(cropFactor) {\n  x4_texs.emplace_back(tex);\n}\n\nbool CFontImageDef::IsLoaded() const {\n  return std::all_of(x4_texs.cbegin(), x4_texs.cend(), [](const auto& token) { return token.IsLoaded(); });\n}\n\ns32 CFontImageDef::CalculateBaseline() const {\n  const CTexture* tex = x4_texs.front().GetObj();\n  return s32(tex->GetHeight() * x14_cropFactor.y()) * 2.5f / 3.f;\n}\n\ns32 CFontImageDef::CalculateHeight() const {\n  const CTexture* tex = x4_texs.front().GetObj();\n  s32 scaledH = tex->GetHeight() * x14_cropFactor.y();\n  return scaledH - (scaledH - CalculateBaseline());\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CFontImageDef.hpp",
    "content": "#pragma once\n\n#include <vector>\n\n#include \"Runtime/CToken.hpp\"\n\n#include <zeus/CVector2f.hpp>\n\nnamespace metaforce {\nclass CTexture;\n\nclass CFontImageDef {\npublic:\n  float x0_fps;\n  std::vector<TLockedToken<CTexture>> x4_texs;\n  zeus::CVector2f x14_cropFactor;\n\n  CFontImageDef(const std::vector<TToken<CTexture>>& texs, float fps, const zeus::CVector2f& cropFactor);\n  CFontImageDef(const TToken<CTexture>& tex, const zeus::CVector2f& cropFactor);\n  bool IsLoaded() const;\n  s32 CalculateBaseline() const;\n  s32 CalculateHeight() const;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CFontRenderState.cpp",
    "content": "#include \"Runtime/GuiSys/CFontRenderState.hpp\"\n\n#include \"Runtime/GuiSys/CRasterFont.hpp\"\n\nnamespace metaforce {\n\nCFontRenderState::CFontRenderState() {\n  x54_colors[0] = zeus::skWhite;\n  x54_colors[1] = zeus::skGrey;\n  x54_colors[2] = zeus::skWhite;\n  RefreshPalette();\n}\n\nzeus::CColor CFontRenderState::ConvertToTextureSpace(const CTextColor& col) const { return col; }\n\nvoid CFontRenderState::PopState() {\n  static_cast<CSaveableState&>(*this) = x10c_pushedStates.back();\n  x10c_pushedStates.pop_back();\n  RefreshPalette();\n}\n\nvoid CFontRenderState::PushState() { x10c_pushedStates.push_back(*this); }\n\nvoid CFontRenderState::SetColor(EColorType tp, const CTextColor& col) {\n  switch (tp) {\n  case EColorType::Main:\n  case EColorType::Outline:\n  case EColorType::Geometry:\n    x54_colors[size_t(tp)] = col;\n    break;\n  case EColorType::Foreground:\n    x54_colors[0] = col;\n    break;\n  case EColorType::Background:\n    x54_colors[1] = col;\n    break;\n  }\n  RefreshColor(tp);\n}\n\nvoid CFontRenderState::RefreshPalette() {\n  RefreshColor(EColorType::Foreground);\n  RefreshColor(EColorType::Background);\n}\n\nvoid CFontRenderState::RefreshColor(EColorType tp) {\n  switch (tp) {\n  case EColorType::Main:\n    if (!x48_font)\n      return;\n    switch (x48_font->GetMode()) {\n    case EColorType::Main:\n    case EColorType::Outline:\n      if (!x64_colorOverrides[0]) {\n        x0_drawStrOpts.x4_colors[0] = ConvertToTextureSpace(x54_colors[0]);\n      }\n      break;\n    default:\n      break;\n    }\n    break;\n  case EColorType::Outline:\n    if (!x48_font)\n      return;\n    if (x64_colorOverrides[1])\n      return;\n    if (x48_font->GetMode() == EColorType::Outline)\n      x0_drawStrOpts.x4_colors[1] = ConvertToTextureSpace(x54_colors[1]);\n    break;\n  case EColorType::Geometry:\n    if (!x64_colorOverrides[2])\n      x0_drawStrOpts.x4_colors[2] = ConvertToTextureSpace(x54_colors[2]);\n    break;\n  case EColorType::Foreground:\n    RefreshColor(EColorType::Main);\n    RefreshColor(EColorType::Geometry);\n    break;\n  case EColorType::Background:\n    RefreshColor(EColorType::Outline);\n    break;\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CFontRenderState.hpp",
    "content": "#pragma once\n\n#include <list>\n#include <vector>\n\n#include \"Runtime/GuiSys/CDrawStringOptions.hpp\"\n#include \"Runtime/GuiSys/CGuiTextSupport.hpp\"\n#include \"Runtime/GuiSys/CSaveableState.hpp\"\n\nnamespace metaforce {\nclass CBlockInstruction;\nclass CLineInstruction;\n\nclass CFontRenderState : public CSaveableState {\n  friend class CBlockInstruction;\n  friend class CImageInstruction;\n  friend class CLineInstruction;\n  friend class CTextInstruction;\n  friend class CWordInstruction;\n\n  CBlockInstruction* x88_curBlock = nullptr;\n  CDrawStringOptions x8c_drawOpts;\n  s32 xd4_curX = 0;\n  s32 xd8_curY = 0;\n  const CLineInstruction* xdc_currentLineInst = nullptr;\n  std::vector<u32> xe8_;\n  std::vector<u8> xf8_;\n  bool x108_lineInitialized = true;\n  std::list<CSaveableState> x10c_pushedStates;\n\npublic:\n  CFontRenderState();\n  zeus::CColor ConvertToTextureSpace(const CTextColor& col) const;\n  void PopState();\n  void PushState();\n  void SetColor(EColorType tp, const CTextColor& col);\n  void RefreshPalette();\n  void RefreshColor(EColorType tp);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CGuiCamera.cpp",
    "content": "#include \"Runtime/GuiSys/CGuiCamera.hpp\"\n\n#include \"Runtime/Graphics/CGraphics.hpp\"\n#include \"Runtime/GuiSys/CGuiFrame.hpp\"\n#include \"Runtime/GuiSys/CGuiWidgetDrawParms.hpp\"\n\nnamespace metaforce {\n\nCGuiCamera::CGuiCamera(const CGuiWidgetParms& parms, float left, float right, float top, float bottom, float znear,\n                       float zfar)\n: CGuiWidget(parms), xb8_projtype(EProjection::Orthographic), m_proj(left, right, top, bottom, znear, zfar) {}\n\nCGuiCamera::CGuiCamera(const CGuiWidgetParms& parms, float fov, float aspect, float znear, float zfar)\n: CGuiWidget(parms), xb8_projtype(EProjection::Perspective), m_proj(fov, aspect, znear, zfar) {}\n\nzeus::CVector3f CGuiCamera::ConvertToScreenSpace(const zeus::CVector3f& vec) const {\n  zeus::CVector3f local = RotateTranslateW2O(vec);\n  if (local.isZero())\n    return {-1.f, -1.f, 1.f};\n\n  zeus::CMatrix4f mat =\n      CGraphics::CalculatePerspectiveMatrix(m_proj.xbc_fov, m_proj.xc0_aspect, m_proj.xc4_znear, m_proj.xc8_zfar);\n  local = zeus::CVector3f(local.x(), local.z(), -local.y());\n  return mat.multiplyOneOverW(local);\n}\n\nvoid CGuiCamera::Draw(const CGuiWidgetDrawParms& parms) {\n  if (xb8_projtype == EProjection::Perspective) {\n    CGraphics::SetPerspective(m_proj.xbc_fov, m_proj.xc0_aspect, m_proj.xc4_znear, m_proj.xc8_zfar);\n  } else {\n    CGraphics::SetOrtho(m_proj.xbc_left, m_proj.xc0_right, m_proj.xc4_top, m_proj.xc8_bottom, m_proj.xcc_znear,\n                        m_proj.xd0_zfar);\n  }\n  CGraphics::SetViewPointMatrix(GetGuiFrame()->GetAspectTransform() *\n                                zeus::CTransform::Translate(parms.x4_cameraOffset) * x34_worldXF);\n  CGuiWidget::Draw(parms);\n}\n\nstd::shared_ptr<CGuiWidget> CGuiCamera::Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp) {\n  CGuiWidgetParms parms = ReadWidgetHeader(frame, in);\n  EProjection proj = EProjection(in.ReadLong());\n  std::shared_ptr<CGuiCamera> ret = {};\n  switch (proj) {\n  case EProjection::Perspective: {\n    float fov = in.ReadFloat();\n    float aspect = in.ReadFloat();\n    float znear = in.ReadFloat();\n    float zfar = in.ReadFloat();\n    ret = std::make_shared<CGuiCamera>(parms, fov, aspect, znear, zfar);\n    break;\n  }\n  case EProjection::Orthographic: {\n    float left = in.ReadFloat();\n    float right = in.ReadFloat();\n    float top = in.ReadFloat();\n    float bottom = in.ReadFloat();\n    float znear = in.ReadFloat();\n    float zfar = in.ReadFloat();\n    ret = std::make_shared<CGuiCamera>(parms, left, right, top, bottom, znear, zfar);\n    break;\n  }\n  }\n  frame->SetFrameCamera(ret->shared_from_this());\n  ret->ParseBaseInfo(frame, in, parms);\n  return ret;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CGuiCamera.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include \"Runtime/GuiSys/CGuiWidget.hpp\"\n\nnamespace metaforce {\nclass CSimplePool;\n\nclass CGuiCamera : public CGuiWidget {\npublic:\n  enum class EProjection { Perspective, Orthographic };\n  struct SProjection {\n    union {\n      struct {\n        float xbc_left;\n        float xc0_right;\n        float xc4_top;\n        float xc8_bottom;\n        float xcc_znear;\n        float xd0_zfar;\n      };\n      struct {\n        float xbc_fov;\n        float xc0_aspect;\n        float xc4_znear;\n        float xc8_zfar;\n      };\n    };\n    SProjection(float left, float right, float top, float bottom, float znear, float zfar)\n    : xbc_left(left), xc0_right(right), xc4_top(top), xc8_bottom(bottom), xcc_znear(znear), xd0_zfar(zfar) {}\n    SProjection(float fov, float aspect, float znear, float zfar)\n    : xbc_fov(fov), xc0_aspect(aspect), xc4_znear(znear), xc8_zfar(zfar) {}\n  };\n\nprivate:\n  EProjection xb8_projtype;\n  SProjection m_proj;\n\npublic:\n  CGuiCamera(const CGuiWidgetParms& parms, float left, float right, float top, float bottom, float znear, float zfar);\n  CGuiCamera(const CGuiWidgetParms& parms, float fov, float aspect, float znear, float zfar);\n  FourCC GetWidgetTypeID() const override { return FOURCC('CAMR'); }\n\n  static std::shared_ptr<CGuiWidget> Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp);\n\n  zeus::CVector3f ConvertToScreenSpace(const zeus::CVector3f& vec) const;\n  const SProjection& GetProjection() const { return m_proj; }\n  void SetFov(float fov) { m_proj.xbc_fov = fov; }\n  void Draw(const CGuiWidgetDrawParms& parms) override;\n\n  std::shared_ptr<CGuiCamera> shared_from_this() {\n    return std::static_pointer_cast<CGuiCamera>(CGuiObject::shared_from_this());\n  }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CGuiCompoundWidget.cpp",
    "content": "#include \"Runtime/GuiSys/CGuiCompoundWidget.hpp\"\n\nnamespace metaforce {\n\nCGuiCompoundWidget::CGuiCompoundWidget(const CGuiWidgetParms& parms) : CGuiWidget(parms) {}\n\nvoid CGuiCompoundWidget::OnVisibleChange() {\n  CGuiWidget* child = static_cast<CGuiWidget*>(GetChildObject());\n  while (child) {\n    child->SetIsVisible(GetIsVisible());\n    child = static_cast<CGuiWidget*>(child->GetNextSibling());\n  }\n  CGuiWidget::OnVisibleChange();\n}\n\nvoid CGuiCompoundWidget::OnActiveChange() {\n  CGuiWidget* child = static_cast<CGuiWidget*>(GetChildObject());\n  while (child) {\n    child->SetIsActive(GetIsActive());\n    child = static_cast<CGuiWidget*>(child->GetNextSibling());\n  }\n  CGuiWidget::OnActiveChange();\n}\n\nCGuiWidget* CGuiCompoundWidget::GetWorkerWidget(int id) const {\n  CGuiWidget* child = static_cast<CGuiWidget*>(GetChildObject());\n  while (child) {\n    if (child->GetWorkerId() == id)\n      return child;\n    child = static_cast<CGuiWidget*>(child->GetNextSibling());\n  }\n  return nullptr;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CGuiCompoundWidget.hpp",
    "content": "#pragma once\n\n#include \"Runtime/GuiSys/CGuiWidget.hpp\"\n\nnamespace metaforce {\n\nclass CGuiCompoundWidget : public CGuiWidget {\npublic:\n  explicit CGuiCompoundWidget(const CGuiWidgetParms& parms);\n  FourCC GetWidgetTypeID() const override { return FourCC(-1); }\n\n  void OnVisibleChange() override;\n  void OnActiveChange() override;\n  virtual CGuiWidget* GetWorkerWidget(int id) const;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CGuiFrame.cpp",
    "content": "#include \"Runtime/GuiSys/CGuiFrame.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/Graphics/CGraphics.hpp\"\n#include \"Runtime/Graphics/CModel.hpp\"\n#include \"Runtime/GuiSys/CGuiCamera.hpp\"\n#include \"Runtime/GuiSys/CGuiHeadWidget.hpp\"\n#include \"Runtime/GuiSys/CGuiLight.hpp\"\n#include \"Runtime/GuiSys/CGuiSys.hpp\"\n#include \"Runtime/GuiSys/CGuiTableGroup.hpp\"\n#include \"Runtime/GuiSys/CGuiWidget.hpp\"\n#include \"Runtime/GuiSys/CGuiWidgetDrawParms.hpp\"\n#include \"Runtime/Input/CFinalInput.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\n#include <zeus/CColor.hpp>\n\nnamespace metaforce {\n\nCGuiFrame::CGuiFrame(CAssetId id, CGuiSys& sys, int a, int b, int c, CSimplePool* sp)\n: x0_id(id), x8_guiSys(sys), x4c_a(a), x50_b(b), x54_c(c) {\n  x3c_lights.reserve(8);\n  m_indexedLights.reserve(8);\n  x10_rootWidget = std::make_unique<CGuiWidget>(CGuiWidget::CGuiWidgetParms(\n      this, false, 0, 0, false, false, false, zeus::skWhite, CGuiWidget::EGuiModelDrawFlags::Alpha, false,\n      x8_guiSys.x8_mode != CGuiSys::EUsageMode::Zero, \"<root>\"s));\n  x8_guiSys.m_registeredFrames.insert(this);\n}\n\nCGuiFrame::~CGuiFrame() { x8_guiSys.m_registeredFrames.erase(this); }\n\nCGuiWidget* CGuiFrame::FindWidget(std::string_view name) const {\n  s16 id = x18_idDB.FindWidgetID(name);\n  if (id == -1)\n    return nullptr;\n  return FindWidget(id);\n}\n\nCGuiWidget* CGuiFrame::FindWidget(s16 id) const { return x10_rootWidget->FindWidget(id); }\n\nvoid CGuiFrame::SortDrawOrder() {\n  std::sort(x2c_widgets.begin(), x2c_widgets.end(),\n            [](const std::shared_ptr<CGuiWidget>& a, const std::shared_ptr<CGuiWidget>& b) -> bool {\n              return a->GetWorldPosition().y() > b->GetWorldPosition().y();\n            });\n}\n\nvoid CGuiFrame::EnableLights(ERglLight lights) const {\n  CGraphics::DisableAllLights();\n\n  zeus::CColor ambColor(zeus::skBlack);\n  ERglLight lightId = 0;\n  int enabledLights = 0;\n  for (CGuiLight* light : m_indexedLights) {\n    if (light == nullptr || !light->GetIsVisible()) {\n      ++lightId;\n      continue;\n    }\n    if ((lights & (1 << lightId)) != 0) {\n      const auto& geomCol = light->GetGeometryColor();\n      if (geomCol.r() != 0.f || geomCol.g() != 0.f || geomCol.b() != 0.f) {\n        CGraphics::LoadLight(lightId, light->BuildLight());\n        CGraphics::EnableLight(lightId);\n      }\n      // accumulate ambient color\n      ambColor += light->GetAmbientLightColor();\n      ++enabledLights;\n    }\n    ++lightId;\n  }\n  if (enabledLights == 0) {\n    CGraphics::SetAmbientColor(zeus::skWhite);\n  } else {\n    CGraphics::SetAmbientColor(ambColor);\n  }\n}\n\nvoid CGuiFrame::DisableLights() const { CGraphics::DisableAllLights(); }\n\nvoid CGuiFrame::RemoveLight(CGuiLight* light) {\n  if (m_indexedLights.empty())\n    return;\n  m_indexedLights[light->GetLightId()] = nullptr;\n}\n\nvoid CGuiFrame::AddLight(CGuiLight* light) {\n  if (m_indexedLights.empty())\n    m_indexedLights.resize(8);\n  m_indexedLights[light->GetLightId()] = light;\n}\n\nvoid CGuiFrame::RegisterLight(std::shared_ptr<CGuiLight>&& light) { x3c_lights.push_back(std::move(light)); }\n\nbool CGuiFrame::GetIsFinishedLoading() const {\n  if (x58_24_loaded)\n    return true;\n  for (const auto& widget : x2c_widgets) {\n    if (widget->GetIsFinishedLoading())\n      continue;\n    return false;\n  }\n  x58_24_loaded = true;\n  return true;\n}\n\nvoid CGuiFrame::Touch() const {\n  for (const auto& widget : x2c_widgets)\n    widget->Touch();\n}\n\nvoid CGuiFrame::SetAspectConstraint(float c) {\n  m_aspectConstraint = c;\n  CGuiSys::ViewportResizeFrame(this);\n}\n\nvoid CGuiFrame::SetMaxAspect(float c) {\n  m_maxAspect = c;\n  CGuiSys::ViewportResizeFrame(this);\n}\n\nvoid CGuiFrame::Reset() { x10_rootWidget->Reset(ETraversalMode::Children); }\n\nvoid CGuiFrame::Update(float dt) { xc_headWidget->Update(dt); }\n\nvoid CGuiFrame::Draw(const CGuiWidgetDrawParms& parms) const {\n  SCOPED_GRAPHICS_DEBUG_GROUP(fmt::format(\"CGuiFrame::Draw FRME_{}\", x0_id).c_str(), zeus::skMagenta);\n  CGraphics::SetCullMode(ERglCullMode::None);\n  CGraphics::ResetGfxStates();\n  CGraphics::SetAmbientColor(zeus::skWhite);\n  DisableLights();\n  x14_camera->Draw(parms);\n  CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvModulate);\n  CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::InvSrcAlpha,\n                          ERglLogicOp::Clear);\n\n  for (const auto& widget : x2c_widgets)\n    if (widget->GetIsVisible())\n      widget->Draw(parms);\n\n  CGraphics::SetCullMode(ERglCullMode::Front);\n}\n\nCGuiWidget* CGuiFrame::BestCursorHit(const zeus::CVector2f& point, const CGuiWidgetDrawParms& parms) const {\n  x14_camera->Draw(parms);\n  zeus::CMatrix4f vp = CGraphics::GetPerspectiveProjectionMatrix(); // TODO * CGraphics::mCameraMtx;\n  CGuiWidget* ret = nullptr;\n  for (const auto& widget : x2c_widgets)\n    if (widget->GetMouseActive() && widget->TestCursorHit(vp, point))\n      ret = widget.get();\n  return ret;\n}\n\nvoid CGuiFrame::Initialize() {\n  SortDrawOrder();\n  xc_headWidget->SetColor(xc_headWidget->xa4_color);\n  xc_headWidget->DispatchInitialize();\n}\n\nvoid CGuiFrame::LoadWidgetsInGame(CInputStream& in, CSimplePool* sp, u32 version) {\n  u32 count = in.ReadLong();\n  x2c_widgets.reserve(count);\n  for (u32 i = 0; i < count; ++i) {\n    FourCC type;\n    in.Get(reinterpret_cast<u8*>(&type), 4);\n    std::shared_ptr<CGuiWidget> widget = CGuiSys::CreateWidgetInGame(type, in, this, sp, version);\n    switch (widget->GetWidgetTypeID().toUint32()) {\n    case SBIG('CAMR'):\n    case SBIG('LITE'):\n    case SBIG('BGND'):\n      break;\n    default:\n      x2c_widgets.push_back(std::move(widget));\n      break;\n    }\n  }\n  Initialize();\n}\n\nvoid CGuiFrame::ProcessUserInput(const CFinalInput& input) const {\n  if (input.ControllerIdx() != 0) {\n    return;\n  }\n\n  for (const auto& widget : x2c_widgets) {\n    if (widget->GetIsActive()) {\n      widget->ProcessUserInput(input);\n    }\n  }\n}\n\nbool CGuiFrame::ProcessMouseInput(const CFinalInput& input, const CGuiWidgetDrawParms& parms) {\n  if (const auto& kbm = input.GetKBM()) {\n    zeus::CVector2f point(kbm->m_mouseCoord.norm[0] * 2.f - 1.f, kbm->m_mouseCoord.norm[1] * 2.f - 1.f);\n    CGuiWidget* hit = BestCursorHit(point, parms);\n    if (hit != m_lastMouseOverWidget) {\n      if (m_inMouseDown && m_mouseDownWidget != hit) {\n        m_inCancel = true;\n        if (m_mouseUpCb)\n          m_mouseUpCb(m_mouseDownWidget, true);\n      } else if (m_inCancel && m_mouseDownWidget == hit) {\n        m_inCancel = false;\n        if (m_mouseDownCb)\n          m_mouseDownCb(m_mouseDownWidget, true);\n      }\n      if (m_mouseOverChangeCb)\n        m_mouseOverChangeCb(m_lastMouseOverWidget, hit);\n      if (hit)\n        hit->m_lastScroll.emplace(kbm->m_accumScroll);\n      m_lastMouseOverWidget = hit;\n    }\n    if (hit && hit->m_lastScroll) {\n      SScrollDelta delta = kbm->m_accumScroll - *hit->m_lastScroll;\n      hit->m_lastScroll.emplace(kbm->m_accumScroll);\n      if (!delta.isZero()) {\n        hit->m_integerScroll += delta;\n        if (m_mouseScrollCb)\n          m_mouseScrollCb(hit, delta, int(hit->m_integerScroll.delta[0]), int(hit->m_integerScroll.delta[1]));\n        hit->m_integerScroll.delta[0] -= std::trunc(hit->m_integerScroll.delta[0]);\n        hit->m_integerScroll.delta[1] -= std::trunc(hit->m_integerScroll.delta[1]);\n      }\n    }\n    if (!m_inMouseDown && kbm->m_mouseButtons[size_t(EMouseButton::Primary)]) {\n      m_inMouseDown = true;\n      m_inCancel = false;\n      m_mouseDownWidget = hit;\n      if (m_mouseDownCb)\n        m_mouseDownCb(hit, false);\n      if (hit)\n        return true;\n    } else if (m_inMouseDown && !kbm->m_mouseButtons[size_t(EMouseButton::Primary)]) {\n      m_inMouseDown = false;\n      m_inCancel = false;\n      if (m_mouseDownWidget == m_lastMouseOverWidget) {\n        if (m_mouseDownWidget) {\n          if (CGuiTableGroup* p = static_cast<CGuiTableGroup*>(m_mouseDownWidget->GetParent())) {\n            if (p->GetWidgetTypeID() == FOURCC('TBGP')) {\n              s16 workerIdx = m_mouseDownWidget->GetWorkerId();\n              if (workerIdx >= 0)\n                p->DoSelectWorker(workerIdx);\n            }\n          }\n        }\n        if (m_mouseUpCb)\n          m_mouseUpCb(m_mouseDownWidget, false);\n      }\n    }\n  }\n  return false;\n}\n\nvoid CGuiFrame::ResetMouseState() {\n  m_inMouseDown = false;\n  m_inCancel = false;\n  m_mouseDownWidget = nullptr;\n  m_lastMouseOverWidget = nullptr;\n}\n\nstd::unique_ptr<CGuiFrame> CGuiFrame::CreateFrame(CAssetId frmeId, CGuiSys& sys, CInputStream& in, CSimplePool* sp) {\n  u32 version = in.ReadLong();\n  int a = in.ReadLong();\n  int b = in.ReadLong();\n  int c = in.ReadLong();\n\n  std::unique_ptr<CGuiFrame> ret = std::make_unique<CGuiFrame>(frmeId, sys, a, b, c, sp);\n  ret->LoadWidgetsInGame(in, sp, version);\n  return ret;\n}\n\nstd::unique_ptr<IObj> RGuiFrameFactoryInGame(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& cvParms,\n                                             CObjectReference* selfRef) {\n  CSimplePool* sp = cvParms.GetOwnedObj<CSimplePool*>();\n  std::unique_ptr<CGuiFrame> frame(CGuiFrame::CreateFrame(tag.id, *g_GuiSys, in, sp));\n  return TToken<CGuiFrame>::GetIObjObjectFor(std::move(frame));\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CGuiFrame.hpp",
    "content": "#pragma once\n\n#include <array>\n#include <functional>\n#include <memory>\n#include <vector>\n\n#include \"Runtime/IObj.hpp\"\n#include \"Runtime/GuiSys/CGuiHeadWidget.hpp\"\n#include \"Runtime/GuiSys/CGuiWidgetIdDB.hpp\"\n#include \"Runtime/GuiSys/CGuiWidget.hpp\"\n#include \"Runtime/Graphics/CGraphics.hpp\"\n\nnamespace metaforce {\nclass CGuiCamera;\nclass CGuiLight;\nclass CGuiSys;\nclass CLight;\nclass CObjectReference;\nclass CSimplePool;\nclass CVParamTransfer;\nstruct CFinalInput;\n\nclass CGuiFrame {\n  friend class CGuiSys;\n\nprivate:\n  std::vector<CGuiLight*> m_indexedLights;\n\n  CAssetId x0_id;\n  u32 x4_ = 0;\n  CGuiSys& x8_guiSys;\n  std::shared_ptr<CGuiHeadWidget> xc_headWidget;\n  std::shared_ptr<CGuiWidget> x10_rootWidget;\n  std::shared_ptr<CGuiCamera> x14_camera;\n  CGuiWidgetIdDB x18_idDB;\n  std::vector<std::shared_ptr<CGuiWidget>> x2c_widgets;\n  std::vector<std::shared_ptr<CGuiLight>> x3c_lights;\n  int x4c_a;\n  int x50_b;\n  int x54_c;\n  mutable bool x58_24_loaded : 1 = false;\n\n  zeus::CTransform m_aspectTransform;\n  float m_aspectConstraint = -1.f;\n  float m_maxAspect = -1.f;\n\n  bool m_inMouseDown = false;\n  bool m_inCancel = false;\n  CGuiWidget* m_mouseDownWidget = nullptr;\n  CGuiWidget* m_lastMouseOverWidget = nullptr;\n  std::function<void(CGuiWidget*, CGuiWidget*)> m_mouseOverChangeCb;\n  std::function<void(CGuiWidget*, bool)> m_mouseDownCb;\n  std::function<void(CGuiWidget*, bool)> m_mouseUpCb;\n  std::function<void(CGuiWidget*, const SScrollDelta&, int, int)> m_mouseScrollCb;\n\npublic:\n  CGuiFrame(CAssetId id, CGuiSys& sys, int a, int b, int c, CSimplePool* sp);\n  ~CGuiFrame();\n\n  CGuiSys& GetGuiSys() { return x8_guiSys; }\n  const CGuiSys& GetGuiSys() const { return x8_guiSys; }\n  CAssetId GetAssetId() const { return x0_id; }\n\n  CGuiLight* GetFrameLight(int idx) const { return m_indexedLights[idx]; }\n  CGuiCamera* GetFrameCamera() const { return x14_camera.get(); }\n  CGuiWidget* FindWidget(std::string_view name) const;\n  CGuiWidget* FindWidget(s16 id) const;\n  void SetFrameCamera(std::shared_ptr<CGuiCamera>&& camr) { x14_camera = std::move(camr); }\n  void SetHeadWidget(std::shared_ptr<CGuiHeadWidget>&& hwig) { xc_headWidget = std::move(hwig); }\n  CGuiHeadWidget* GetHeadWidget() const { return xc_headWidget.get(); }\n  void SortDrawOrder();\n  void EnableLights(ERglLight lights) const;\n  void DisableLights() const;\n  void RemoveLight(CGuiLight* light);\n  void AddLight(CGuiLight* light);\n  void RegisterLight(std::shared_ptr<CGuiLight>&& light);\n  bool GetIsFinishedLoading() const;\n  void Touch() const;\n  const zeus::CTransform& GetAspectTransform() const { return m_aspectTransform; }\n  void SetAspectConstraint(float c);\n  void SetMaxAspect(float c);\n  void SetMouseOverChangeCallback(std::function<void(CGuiWidget*, CGuiWidget*)>&& cb) {\n    m_mouseOverChangeCb = std::move(cb);\n  }\n  void SetMouseDownCallback(std::function<void(CGuiWidget*, bool)>&& cb) { m_mouseDownCb = std::move(cb); }\n  void SetMouseUpCallback(std::function<void(CGuiWidget*, bool)>&& cb) { m_mouseUpCb = std::move(cb); }\n  void SetMouseScrollCallback(std::function<void(CGuiWidget*, const SScrollDelta&, int, int)>&& cb) {\n    m_mouseScrollCb = std::move(cb);\n  }\n\n  void Reset();\n  void Update(float dt);\n  void Draw(const CGuiWidgetDrawParms& parms) const;\n  CGuiWidget* BestCursorHit(const zeus::CVector2f& point, const CGuiWidgetDrawParms& parms) const;\n  void Initialize();\n  void LoadWidgetsInGame(CInputStream& in, CSimplePool* sp, u32 version);\n  void ProcessUserInput(const CFinalInput& input) const;\n  bool ProcessMouseInput(const CFinalInput& input, const CGuiWidgetDrawParms& parms);\n  void ResetMouseState();\n\n  CGuiWidgetIdDB& GetWidgetIdDB() { return x18_idDB; }\n  const CGuiWidgetIdDB& GetWidgetIdDB() const { return x18_idDB; }\n\n  static std::unique_ptr<CGuiFrame> CreateFrame(CAssetId frmeId, CGuiSys& sys, CInputStream& in, CSimplePool* sp);\n};\n\nstd::unique_ptr<IObj> RGuiFrameFactoryInGame(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& vparms,\n                                             CObjectReference* selfRef);\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CGuiGroup.cpp",
    "content": "#include \"Runtime/GuiSys/CGuiGroup.hpp\"\n\nnamespace metaforce {\n\nvoid CGuiGroup::LoadWidgetFnMap() {}\n\nCGuiGroup::CGuiGroup(const CGuiWidgetParms& parms, int defaultWorker, bool b)\n: CGuiCompoundWidget(parms), xbc_selectedWorker(defaultWorker), xc0_b(b) {}\n\nvoid CGuiGroup::SelectWorkerWidget(int workerId, bool setActive, bool setVisible) {\n  CGuiWidget* child = static_cast<CGuiWidget*>(GetChildObject());\n  while (child) {\n    if (child->GetWorkerId() == workerId) {\n      CGuiWidget* sel = GetSelectedWidget();\n      if (setActive) {\n        sel->SetIsActive(false);\n        child->SetIsActive(true);\n      }\n      if (setVisible) {\n        sel->SetVisibility(false, ETraversalMode::Single);\n        child->SetVisibility(true, ETraversalMode::Single);\n      }\n      break;\n    }\n    child = static_cast<CGuiWidget*>(child->GetNextSibling());\n  }\n}\n\nCGuiWidget* CGuiGroup::GetSelectedWidget() { return GetWorkerWidget(xbc_selectedWorker); }\n\nconst CGuiWidget* CGuiGroup::GetSelectedWidget() const { return GetWorkerWidget(xbc_selectedWorker); }\n\nbool CGuiGroup::AddWorkerWidget(CGuiWidget* worker) {\n  ++xb8_workerCount;\n  return true;\n}\n\nvoid CGuiGroup::OnActiveChange() {\n  CGuiWidget* sel = GetSelectedWidget();\n  if (sel)\n    sel->SetIsActive(true);\n}\n\nstd::shared_ptr<CGuiWidget> CGuiGroup::Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp) {\n  CGuiWidgetParms parms = ReadWidgetHeader(frame, in);\n  s16 defaultWorker = in.ReadInt16();\n  bool b = in.ReadBool();\n  std::shared_ptr<CGuiWidget> ret = std::make_shared<CGuiGroup>(parms, defaultWorker, b);\n  ret->ParseBaseInfo(frame, in, parms);\n  return ret;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CGuiGroup.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include \"Runtime/GuiSys/CGuiCompoundWidget.hpp\"\n\nnamespace metaforce {\n\nclass CGuiGroup : public CGuiCompoundWidget {\n  u32 xb8_workerCount = 0;\n  int xbc_selectedWorker;\n  bool xc0_b;\n\npublic:\n  CGuiGroup(const CGuiWidgetParms& parms, int defaultWorker, bool b);\n  FourCC GetWidgetTypeID() const override { return FOURCC('GRUP'); }\n\n  void SelectWorkerWidget(int workerId, bool setActive, bool setVisible);\n  CGuiWidget* GetSelectedWidget();\n  const CGuiWidget* GetSelectedWidget() const;\n  bool AddWorkerWidget(CGuiWidget* worker) override;\n  void OnActiveChange() override;\n\n  static std::shared_ptr<CGuiWidget> Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp);\n  static void LoadWidgetFnMap();\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CGuiHeadWidget.cpp",
    "content": "#include \"Runtime/GuiSys/CGuiHeadWidget.hpp\"\n#include \"Runtime/GuiSys/CGuiFrame.hpp\"\n\nnamespace metaforce {\n\nCGuiHeadWidget::CGuiHeadWidget(const CGuiWidgetParms& parms) : CGuiWidget(parms) {}\n\nstd::shared_ptr<CGuiWidget> CGuiHeadWidget::Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp) {\n  CGuiWidgetParms parms = ReadWidgetHeader(frame, in);\n  std::shared_ptr<CGuiHeadWidget> ret = std::make_shared<CGuiHeadWidget>(parms);\n  frame->SetHeadWidget(ret->shared_from_this());\n  ret->ParseBaseInfo(frame, in, parms);\n  return ret;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CGuiHeadWidget.hpp",
    "content": "#pragma once\n\n#include \"Runtime/GuiSys/CGuiWidget.hpp\"\n\nnamespace metaforce {\n\nclass CGuiHeadWidget : public CGuiWidget {\npublic:\n  FourCC GetWidgetTypeID() const override { return FOURCC('HWIG'); }\n  explicit CGuiHeadWidget(const CGuiWidgetParms& parms);\n  static std::shared_ptr<CGuiWidget> Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp);\n\n  std::shared_ptr<CGuiHeadWidget> shared_from_this() {\n    return std::static_pointer_cast<CGuiHeadWidget>(CGuiObject::shared_from_this());\n  }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CGuiLight.cpp",
    "content": "#include \"Runtime/GuiSys/CGuiLight.hpp\"\n#include \"Runtime/GuiSys/CGuiFrame.hpp\"\n\nnamespace metaforce {\n\nCGuiLight::CGuiLight(const CGuiWidgetParms& parms, const CLight& light)\n: CGuiWidget(parms)\n, xb8_type(light.x1c_type)\n, xbc_spotCutoff(light.x20_spotCutoff)\n, xc0_distC(light.x24_distC)\n, xc4_distL(light.x28_distL)\n, xc8_distQ(light.x2c_distQ)\n, xcc_angleC(light.x30_angleC)\n, xd0_angleL(light.x34_angleL)\n, xd4_angleQ(light.x38_angleQ)\n, xd8_lightId(light.x40_lightId) {}\n\nCGuiLight::~CGuiLight() { xb0_frame->RemoveLight(this); }\n\nCLight CGuiLight::BuildLight() const {\n  CLight ret = CLight::BuildLocalAmbient(zeus::skZero3f, zeus::skBlack);\n\n  switch (xb8_type) {\n  case ELightType::Spot:\n    ret = CLight::BuildSpot(GetWorldPosition(), x34_worldXF.basis[1], xa4_color, xbc_spotCutoff);\n    break;\n  case ELightType::Point:\n    ret = CLight::BuildPoint(GetWorldPosition(), xa4_color);\n    break;\n  case ELightType::Directional:\n    ret = CLight::BuildDirectional(x34_worldXF.basis[1], xa4_color);\n    break;\n  default:\n    break;\n  }\n\n  ret.SetAttenuation(xc0_distC, xc4_distL, xc8_distQ);\n  ret.SetAngleAttenuation(xcc_angleC, xd0_angleL, xd4_angleQ);\n  return ret;\n}\n\nvoid CGuiLight::SetIsVisible(bool vis) {\n  if (vis)\n    xb0_frame->AddLight(this);\n  else\n    xb0_frame->RemoveLight(this);\n  CGuiWidget::SetIsVisible(vis);\n}\n\nstd::shared_ptr<CGuiWidget> CGuiLight::Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp) {\n  CGuiWidgetParms parms = ReadWidgetHeader(frame, in);\n\n  ELightType tp = ELightType(in.ReadLong());\n  float distC = in.ReadFloat();\n  float distL = in.ReadFloat();\n  float distQ = in.ReadFloat();\n  float angC = in.ReadFloat();\n  float angL = in.ReadFloat();\n  float angQ = in.ReadFloat();\n  u32 lightId = in.ReadLong();\n\n  std::shared_ptr<CGuiLight> ret = {};\n  switch (tp) {\n  case ELightType::Spot: {\n    float cutoff = in.ReadFloat();\n    CLight lt = CLight::BuildSpot(zeus::skZero3f, zeus::skZero3f, parms.x10_color, cutoff);\n    lt.SetAttenuation(distC, distL, distQ);\n    lt.SetAngleAttenuation(angC, angL, angQ);\n    lt.x40_lightId = lightId;\n    ret = std::make_shared<CGuiLight>(parms, lt);\n    break;\n  }\n  case ELightType::Point: {\n    CLight lt = CLight::BuildPoint(zeus::skZero3f, parms.x10_color);\n    lt.SetAttenuation(distC, distL, distQ);\n    lt.x40_lightId = lightId;\n    ret = std::make_shared<CGuiLight>(parms, lt);\n    break;\n  }\n  case ELightType::Directional: {\n    CLight lt = CLight::BuildDirectional(zeus::skZero3f, parms.x10_color);\n    lt.x40_lightId = lightId;\n    ret = std::make_shared<CGuiLight>(parms, lt);\n    break;\n  }\n  default:\n    break;\n  }\n\n  ret->ParseBaseInfo(frame, in, parms);\n  frame->RegisterLight(ret->shared_from_this());\n  frame->AddLight(ret.get());\n  return ret;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CGuiLight.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/Graphics/CLight.hpp\"\n#include \"Runtime/GuiSys/CGuiWidget.hpp\"\n\n#include <zeus/CColor.hpp>\n\nnamespace metaforce {\nclass CSimplePool;\n\nclass CGuiLight : public CGuiWidget {\n  ELightType xb8_type;\n  float xbc_spotCutoff;\n  float xc0_distC;\n  float xc4_distL;\n  float xc8_distQ;\n  float xcc_angleC;\n  float xd0_angleL;\n  float xd4_angleQ;\n  u32 xd8_lightId;\n  zeus::CColor xdc_ambColor = zeus::skBlack;\n\npublic:\n  ~CGuiLight() override;\n  CGuiLight(const CGuiWidgetParms& parms, const CLight& light);\n  FourCC GetWidgetTypeID() const override { return FOURCC('LITE'); }\n\n  CLight BuildLight() const;\n  void SetIsVisible(bool vis) override;\n  u32 GetLightId() const { return xd8_lightId; }\n  const zeus::CColor& GetAmbientLightColor() const { return xdc_ambColor; }\n  void SetSpotCutoff(float v) { xbc_spotCutoff = v; }\n  void SetDistC(float v) { xc0_distC = v; }\n  void SetDistL(float v) { xc4_distL = v; }\n  void SetDistQ(float v) { xc8_distQ = v; }\n  void SetAngleC(float v) { xcc_angleC = v; }\n  void SetAngleL(float v) { xd0_angleL = v; }\n  void SetAngleQ(float v) { xd4_angleQ = v; }\n  void SetLightId(u32 idx) { xd8_lightId = idx; }\n  void SetAmbientLightColor(const zeus::CColor& color) { xdc_ambColor = color; }\n\n  static std::shared_ptr<CGuiWidget> Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp);\n\n  std::shared_ptr<CGuiLight> shared_from_this() {\n    return std::static_pointer_cast<CGuiLight>(CGuiObject::shared_from_this());\n  }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CGuiModel.cpp",
    "content": "#include \"Runtime/GuiSys/CGuiModel.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/Graphics/CGraphics.hpp\"\n#include \"Runtime/GuiSys/CGuiFrame.hpp\"\n#include \"Runtime/GuiSys/CGuiSys.hpp\"\n#include \"Runtime/GuiSys/CGuiWidgetDrawParms.hpp\"\n\nnamespace metaforce {\n\nCGuiModel::CGuiModel(const CGuiWidgetParms& parms, CSimplePool* sp, CAssetId modelId, u32 lightMask, bool flag)\n: CGuiWidget(parms), xc8_modelId(modelId), xcc_lightMask(lightMask) {\n  if (!flag || !modelId.IsValid() || parms.x0_frame->GetGuiSys().GetUsageMode() == CGuiSys::EUsageMode::Two) {\n    return;\n  }\n  xb8_model = sp->GetObj({SBIG('CMDL'), modelId});\n}\n\nbool CGuiModel::GetIsFinishedLoadingWidgetSpecific() {\n  if (!xb8_model) {\n    return true;\n  }\n  if (!xb8_model.IsLoaded()) {\n    return false;\n  }\n  xb8_model->Touch(0);\n  return xb8_model->IsLoaded(0);\n}\n\nvoid CGuiModel::Touch() {\n  if (CModel* const model = xb8_model.GetObj()) {\n    model->Touch(0);\n  }\n}\n\nvoid CGuiModel::Draw(const CGuiWidgetDrawParms& parms) {\n  CGraphics::SetModelMatrix(x34_worldXF);\n  if (!xb8_model) {\n    return;\n  }\n  if (!GetIsFinishedLoading()) {\n    return;\n  }\n  CModel* const model = xb8_model.GetObj();\n  if (model == nullptr) {\n    return;\n  }\n\n  if (GetIsVisible()) {\n    SCOPED_GRAPHICS_DEBUG_GROUP(fmt::format(\"CGuiModel::Draw {}\", m_name).c_str(), zeus::skCyan);\n    zeus::CColor moduCol = xa8_color2;\n    moduCol.a() *= parms.x0_alphaMod;\n    xb0_frame->EnableLights(xcc_lightMask);\n    if (xb6_29_cullFaces) {\n      CGraphics::SetCullMode(ERglCullMode::Front);\n    }\n\n    switch (xac_drawFlags) {\n    case EGuiModelDrawFlags::Shadeless: {\n      constexpr CModelFlags flags(0, 0, 3, zeus::skWhite);\n      model->Draw(flags);\n      break;\n    }\n    case EGuiModelDrawFlags::Opaque: {\n      CModelFlags flags(1, 0, 3, moduCol);\n      model->Draw(flags);\n      break;\n    }\n    case EGuiModelDrawFlags::Alpha: {\n      CModelFlags flags(5, 0, (u32(xb7_24_depthWrite) << 1) | u32(xb6_31_depthTest), moduCol);\n      model->Draw(flags);\n      break;\n    }\n    case EGuiModelDrawFlags::Additive: {\n      CModelFlags flags(7, 0, (u32(xb7_24_depthWrite) << 1) | u32(xb6_31_depthTest), moduCol);\n      model->Draw(flags);\n      break;\n    }\n    case EGuiModelDrawFlags::AlphaAdditiveOverdraw: {\n      const CModelFlags flags(5, 0, (u32(xb6_30_depthGreater) << 4) | u32(xb6_31_depthTest), moduCol);\n      model->Draw(flags);\n\n      const CModelFlags overdrawFlags(\n          8, 0, (u32(xb6_30_depthGreater) << 4) | (u32(xb7_24_depthWrite) << 1) | u32(xb6_31_depthTest), moduCol);\n      model->Draw(overdrawFlags);\n      break;\n    }\n    default:\n      break;\n    }\n\n    if (xb6_29_cullFaces) {\n      CGraphics::SetCullMode(ERglCullMode::None);\n    }\n    xb0_frame->DisableLights();\n  }\n\n  CGuiWidget::Draw(parms);\n}\n\nbool CGuiModel::TestCursorHit(const zeus::CMatrix4f& vp, const zeus::CVector2f& point) const {\n  if (!xb8_model || !xb8_model.IsLoaded()) {\n    return false;\n  }\n  return xb8_model->GetAABB().projectedPointTest(vp * x34_worldXF.toMatrix4f(), point);\n}\n\nstd::shared_ptr<CGuiWidget> CGuiModel::Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp) {\n  CGuiWidgetParms parms = ReadWidgetHeader(frame, in);\n\n  auto model = in.Get<CAssetId>();\n  in.ReadLong();\n  u32 lightMask = in.ReadLong();\n\n  std::shared_ptr<CGuiWidget> ret = std::make_shared<CGuiModel>(parms, sp, model, lightMask, true);\n  ret->ParseBaseInfo(frame, in, parms);\n  return ret;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CGuiModel.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/Graphics/CModel.hpp\"\n#include \"Runtime/GuiSys/CGuiWidget.hpp\"\n\nnamespace metaforce {\nclass CSimplePool;\n\nclass CGuiModel : public CGuiWidget {\n  TLockedToken<CModel> xb8_model;\n  CAssetId xc8_modelId;\n  u32 xcc_lightMask;\n\npublic:\n  CGuiModel(const CGuiWidgetParms& parms, CSimplePool* sp, CAssetId modelId, u32 lightMask, bool flag);\n  FourCC GetWidgetTypeID() const override { return FOURCC('MODL'); }\n\n  std::vector<CAssetId> GetModelAssets() const { return {xc8_modelId}; }\n  const TLockedToken<CModel>& GetModel() const { return xb8_model; }\n  bool GetIsFinishedLoadingWidgetSpecific() override;\n  void Touch() override;\n  void Draw(const CGuiWidgetDrawParms& parms) override;\n  bool TestCursorHit(const zeus::CMatrix4f& vp, const zeus::CVector2f& point) const override;\n\n  static std::shared_ptr<CGuiWidget> Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CGuiObject.cpp",
    "content": "#include \"Runtime/GuiSys/CGuiObject.hpp\"\n\n#include \"Runtime/GuiSys/CGuiWidgetDrawParms.hpp\"\n\nnamespace metaforce {\n\nvoid CGuiObject::Update(float dt) {\n  if (x68_child)\n    x68_child->Update(dt);\n  if (x6c_nextSibling)\n    x6c_nextSibling->Update(dt);\n}\n\nvoid CGuiObject::Draw(const CGuiWidgetDrawParms& parms) {\n  if (x68_child)\n    x68_child->Draw(parms);\n  if (x6c_nextSibling)\n    x6c_nextSibling->Draw(parms);\n}\n\nvoid CGuiObject::MoveInWorld(const zeus::CVector3f& vec) {\n  // if (x64_parent)\n  //   x64_parent->RotateW2O(vec);\n  x4_localXF.origin += vec;\n  RecalculateTransforms();\n}\n\nvoid CGuiObject::SetLocalPosition(const zeus::CVector3f& pos) { MoveInWorld(pos - x4_localXF.origin); }\n\nvoid CGuiObject::RotateReset() {\n  x4_localXF.basis = zeus::CMatrix3f();\n  RecalculateTransforms();\n}\n\nzeus::CVector3f CGuiObject::RotateW2O(const zeus::CVector3f& vec) const { return x34_worldXF.transposeRotate(vec); }\n\nzeus::CVector3f CGuiObject::RotateO2P(const zeus::CVector3f& vec) const { return x4_localXF.rotate(vec); }\n\nzeus::CVector3f CGuiObject::RotateTranslateW2O(const zeus::CVector3f& vec) const {\n  return x34_worldXF.transposeRotate(vec - x34_worldXF.origin);\n}\n\nvoid CGuiObject::MultiplyO2P(const zeus::CTransform& xf) {\n  x4_localXF = xf * x4_localXF;\n  RecalculateTransforms();\n}\n\nvoid CGuiObject::AddChildObject(CGuiObject* obj, bool makeWorldLocal, bool atEnd) {\n  obj->x64_parent = this;\n\n  if (!x68_child) {\n    x68_child = obj;\n  } else if (atEnd) {\n    CGuiObject* prev = nullptr;\n    CGuiObject* cur = x68_child;\n    for (; cur; cur = cur->x6c_nextSibling) {\n      prev = cur;\n    }\n    if (prev)\n      prev->x6c_nextSibling = obj;\n  } else {\n    obj->x6c_nextSibling = x68_child;\n    x68_child = obj;\n  }\n\n  if (makeWorldLocal) {\n    zeus::CVector3f negParentWorld = -x34_worldXF.origin;\n    zeus::CMatrix3f basisMat(x34_worldXF.basis[0] / x34_worldXF.basis[0].magnitude(),\n                             x34_worldXF.basis[1] / x34_worldXF.basis[1].magnitude(),\n                             x34_worldXF.basis[2] / x34_worldXF.basis[2].magnitude());\n    zeus::CVector3f xfWorld = basisMat * negParentWorld;\n    obj->x4_localXF = zeus::CTransform(basisMat, xfWorld) * obj->x34_worldXF;\n  }\n\n  RecalculateTransforms();\n}\n\nCGuiObject* CGuiObject::RemoveChildObject(CGuiObject* obj, bool makeWorldLocal) {\n  CGuiObject* prev = nullptr;\n  CGuiObject* cur = x68_child;\n  for (; cur && cur != obj; cur = cur->x6c_nextSibling) {\n    prev = cur;\n  }\n  if (!cur)\n    return nullptr;\n  if (prev)\n    prev->x6c_nextSibling = cur->x6c_nextSibling;\n  cur->x6c_nextSibling = nullptr;\n  cur->x64_parent = nullptr;\n\n  if (makeWorldLocal)\n    cur->x4_localXF = cur->x34_worldXF;\n  cur->RecalculateTransforms();\n\n  return cur;\n}\n\nvoid CGuiObject::RecalculateTransforms() {\n  if (x64_parent)\n    x34_worldXF = x64_parent->x34_worldXF * x4_localXF;\n  else\n    x34_worldXF = x4_localXF;\n\n  if (x6c_nextSibling)\n    x6c_nextSibling->RecalculateTransforms();\n  if (x68_child)\n    x68_child->RecalculateTransforms();\n}\n\nvoid CGuiObject::SetO2WTransform(const zeus::CTransform& xf) {\n  x4_localXF = GetParent()->x34_worldXF.inverse() * xf;\n  RecalculateTransforms();\n}\n\nvoid CGuiObject::SetLocalTransform(const zeus::CTransform& xf) {\n  x4_localXF = xf;\n  RecalculateTransforms();\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CGuiObject.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n\n#include <zeus/CQuaternion.hpp>\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\n#include <memory>\n\nnamespace metaforce {\nstruct CGuiWidgetDrawParms;\n\nclass CGuiObject : public std::enable_shared_from_this<CGuiObject> {\nprotected:\n  zeus::CTransform m_initLocalXF;\n  zeus::CTransform x4_localXF;\n  zeus::CTransform x34_worldXF;\n  CGuiObject* x64_parent = nullptr;\n  CGuiObject* x68_child = nullptr;\n  CGuiObject* x6c_nextSibling = nullptr;\n\npublic:\n  virtual ~CGuiObject() = default;\n  virtual void Update(float dt);\n  virtual void Draw(const CGuiWidgetDrawParms& parms);\n  virtual bool TestCursorHit(const zeus::CMatrix4f& vp, const zeus::CVector2f& point) const { return false; }\n  virtual void Initialize() = 0;\n\n  void MoveInWorld(const zeus::CVector3f& vec);\n  const zeus::CVector3f& GetInitialLocalPosition() const { return m_initLocalXF.origin; }\n  const zeus::CTransform& GetInitialLocalTransform() const { return m_initLocalXF; }\n  const zeus::CVector3f& GetLocalPosition() const { return x4_localXF.origin; }\n  const zeus::CTransform& GetLocalTransform() const { return x4_localXF; }\n  void SetLocalPosition(const zeus::CVector3f& pos);\n  const zeus::CVector3f& GetWorldPosition() const { return x34_worldXF.origin; }\n  const zeus::CTransform& GetWorldTransform() const { return x34_worldXF; }\n  void RotateReset();\n  zeus::CVector3f RotateW2O(const zeus::CVector3f& vec) const;\n  zeus::CVector3f RotateO2P(const zeus::CVector3f& vec) const;\n  zeus::CVector3f RotateTranslateW2O(const zeus::CVector3f& vec) const;\n  void MultiplyO2P(const zeus::CTransform& xf);\n  void AddChildObject(CGuiObject* obj, bool makeWorldLocal, bool atEnd);\n  CGuiObject* RemoveChildObject(CGuiObject* obj, bool makeWorldLocal);\n  CGuiObject* GetParent() const { return x64_parent; }\n  CGuiObject* GetChildObject() const { return x68_child; }\n  CGuiObject* GetNextSibling() const { return x6c_nextSibling; }\n  void RecalculateTransforms();\n  void SetO2WTransform(const zeus::CTransform& xf);\n  void SetLocalTransform(const zeus::CTransform& xf);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CGuiPane.cpp",
    "content": "#include \"Runtime/GuiSys/CGuiPane.hpp\"\n#include \"Runtime/GuiSys/CGuiWidgetDrawParms.hpp\"\n\n#include \"Runtime/Graphics/CGraphics.hpp\"\n\nnamespace metaforce {\n\nCGuiPane::CGuiPane(const CGuiWidgetParms& parms, const zeus::CVector2f& dim, const zeus::CVector3f& scaleCenter)\n: CGuiWidget(parms), xb8_dim(dim), xc8_scaleCenter(scaleCenter) {\n  CGuiPane::InitializeBuffers();\n}\n\nvoid CGuiPane::Draw(const CGuiWidgetDrawParms& parms) {\n  CGraphics::SetModelMatrix(x34_worldXF * zeus::CTransform::Translate(xc8_scaleCenter));\n  if (GetIsVisible()) {\n    auto col = xa8_color2;\n    col.a() = parms.x0_alphaMod * xa8_color2.a();\n\n    CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvPassthru);\n    CGraphics::DrawPrimitive(ERglPrimitive::TriangleStrip, xc0_verts.data(), skDefaultNormal, col, xc0_verts.size());\n  }\n  CGuiWidget::Draw(parms);\n}\nvoid CGuiPane::ScaleDimensions(const zeus::CVector3f& scale) {\n  InitializeBuffers();\n  for (auto& vert : xc0_verts) {\n    vert -= xc8_scaleCenter;\n    vert *= scale;\n    vert += xc8_scaleCenter;\n  }\n}\n\nvoid CGuiPane::SetDimensions(const zeus::CVector2f& dim, bool initBuffers) {\n  xb8_dim = dim;\n  if (initBuffers)\n    InitializeBuffers();\n}\n\nzeus::CVector2f CGuiPane::GetDimensions() const { return xb8_dim; }\n\nvoid CGuiPane::InitializeBuffers() {\n#if 0\n  if (xc0_verts == nullptr) {\n    xc0_verts = new float[3 * 4];\n  }\n#endif\n  xc0_verts[0].assign(-xb8_dim.x() * 0.5f, 0.f, xb8_dim.y() * 0.5f);\n  xc0_verts[1].assign(-xb8_dim.x() * 0.5f, 0.f, -xb8_dim.y() * 0.5f);\n  xc0_verts[2].assign(xb8_dim.x() * 0.5f, 0.f, xb8_dim.y() * 0.5f);\n  xc0_verts[3].assign(xb8_dim.x() * 0.5f, 0.f, -xb8_dim.y() * 0.5f);\n}\n\nvoid CGuiPane::WriteData(COutputStream& out, bool flag) const {}\n\nstd::shared_ptr<CGuiWidget> CGuiPane::Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp) {\n  CGuiWidgetParms parms = ReadWidgetHeader(frame, in);\n  zeus::CVector2f dim = in.Get<zeus::CVector2f>();\n  zeus::CVector3f scaleCenter = in.Get<zeus::CVector3f>();\n  std::shared_ptr<CGuiWidget> ret = std::make_shared<CGuiPane>(parms, dim, scaleCenter);\n  ret->ParseBaseInfo(frame, in, parms);\n  return ret;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CGuiPane.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <vector>\n\n#include \"Runtime/GuiSys/CGuiWidget.hpp\"\n\n#include <zeus/CVector2f.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\n\nclass CGuiPane : public CGuiWidget {\n  static constexpr zeus::CVector3f skDefaultNormal{0.f, -1.f, 0.f};\nprotected:\n  zeus::CVector2f xb8_dim;\n\n  /* Originally a vert-buffer pointer for GX */\n  std::array<zeus::CVector3f, 4> xc0_verts;\n\n  zeus::CVector3f xc8_scaleCenter;\n\npublic:\n  CGuiPane(const CGuiWidgetParms& parms, const zeus::CVector2f& dim, const zeus::CVector3f& scaleCenter);\n  FourCC GetWidgetTypeID() const override { return FOURCC('PANE'); }\n\n  void Draw(const CGuiWidgetDrawParms& parms) override;\n  virtual void ScaleDimensions(const zeus::CVector3f& scale);\n  virtual void SetDimensions(const zeus::CVector2f& dim, bool initVBO);\n  virtual zeus::CVector2f GetDimensions() const;\n  virtual void InitializeBuffers();\n  virtual void WriteData(COutputStream& out, bool flag) const;\n\n  static std::shared_ptr<CGuiWidget> Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CGuiSliderGroup.cpp",
    "content": "#include \"Runtime/GuiSys/CGuiSliderGroup.hpp\"\n\n#include \"Runtime/GuiSys/CGuiModel.hpp\"\n#include \"Runtime/Input/CFinalInput.hpp\"\n\nnamespace metaforce {\n\nCGuiSliderGroup::CGuiSliderGroup(const CGuiWidgetParms& parms, float min, float max, float def, float inc)\n: CGuiCompoundWidget(parms)\n, xb8_minVal(min)\n, xbc_maxVal(max)\n, xc0_roundedCurVal(def)\n, xc4_curVal(def)\n, xc8_increment(inc) {}\n\nvoid CGuiSliderGroup::SetSelectionChangedCallback(std::function<void(CGuiSliderGroup*, float)>&& func) {\n  xd8_changeCallback = std::move(func);\n}\n\nvoid CGuiSliderGroup::SetCurVal(float cur) {\n  xc0_roundedCurVal = zeus::clamp(xb8_minVal, cur, xbc_maxVal);\n  xc4_curVal = xc0_roundedCurVal;\n}\n\nvoid CGuiSliderGroup::StartDecreasing() {\n  xf0_state = EState::Decreasing;\n  xf4_24_inputPending = true;\n}\n\nvoid CGuiSliderGroup::StartIncreasing() {\n  xf0_state = EState::Increasing;\n  xf4_24_inputPending = true;\n}\n\nbool CGuiSliderGroup::TestCursorHit(const zeus::CMatrix4f& vp, const zeus::CVector2f& point) const {\n  if (xcc_sliderRangeWidgets[0]->GetWidgetTypeID() != FOURCC('MODL')) {\n    return false;\n  }\n\n  CGuiModel* bar = static_cast<CGuiModel*>(xcc_sliderRangeWidgets[0]);\n  const auto& modelTok = bar->GetModel();\n  if (!modelTok || !modelTok.IsLoaded()) {\n    return false;\n  }\n\n  const zeus::CVector3f& s0 = xcc_sliderRangeWidgets[0]->GetIdlePosition();\n  const zeus::CVector3f& s1 = xcc_sliderRangeWidgets[1]->GetIdlePosition();\n\n  zeus::CVector3f backupPos = bar->GetLocalPosition();\n  bar->SetLocalPosition(s0);\n  zeus::CVector2f p0 = vp.multiplyOneOverW(bar->GetWorldPosition()).toVec2f();\n  auto aabb = modelTok->GetAABB().getTransformedAABox(bar->GetWorldTransform());\n  bar->SetLocalPosition(s1);\n  zeus::CVector2f p1 = vp.multiplyOneOverW(bar->GetWorldPosition()).toVec2f();\n  aabb.accumulateBounds(modelTok->GetAABB().getTransformedAABox(bar->GetWorldTransform()));\n  bar->SetLocalPosition(backupPos);\n\n  zeus::CVector2f pDelta = p1 - p0;\n  float magSq = pDelta.magSquared();\n  float t = 0.f;\n  if (magSq > 0.00001f)\n    t = pDelta.dot(point - p0) / magSq;\n  m_mouseT = zeus::clamp(0.f, t, 1.f);\n\n  m_mouseInside = aabb.projectedPointTest(vp, point);\n  return m_mouseInside;\n}\n\nvoid CGuiSliderGroup::ProcessUserInput(const CFinalInput& input) {\n  if (input.DMouseButton(EMouseButton::Primary) && m_mouseInside)\n    m_mouseDown = true;\n  else if (!input.DMouseButton(EMouseButton::Primary))\n    m_mouseDown = false;\n  if (input.DLALeft()) {\n    StartDecreasing();\n    return;\n  }\n  if (input.DLARight()) {\n    StartIncreasing();\n    return;\n  }\n  if (input.PDPLeft()) {\n    StartDecreasing();\n    return;\n  }\n  if (input.PDPRight()) {\n    StartIncreasing();\n    return;\n  }\n}\n\nvoid CGuiSliderGroup::Update(float dt) {\n  float fullRange = xbc_maxVal - xb8_minVal;\n  float t1 = fullRange * dt;\n\n  float incCurVal;\n  for (incCurVal = xb8_minVal; incCurVal <= xc4_curVal; incCurVal += xc8_increment) {}\n\n  float upperIncVal = std::min(incCurVal, xbc_maxVal);\n  float lowerIncVal = upperIncVal - xc8_increment;\n\n  float oldCurVal = xc4_curVal;\n  if (xf0_state == EState::Decreasing) {\n    if (xf4_24_inputPending)\n      xc4_curVal = std::max(oldCurVal - t1, xb8_minVal);\n    else\n      xc4_curVal = std::max(oldCurVal - t1, lowerIncVal);\n  } else if (xf0_state == EState::Increasing) {\n    if (xf4_24_inputPending)\n      xc4_curVal = std::min(oldCurVal + t1, xbc_maxVal);\n    else if (xc4_curVal != lowerIncVal)\n      xc4_curVal = std::min(oldCurVal + t1, upperIncVal);\n  }\n\n  if (m_mouseDown) {\n    xc4_curVal = m_mouseT * fullRange + xb8_minVal;\n    xf0_state = EState::MouseMove;\n  }\n\n  if (xc4_curVal == oldCurVal)\n    xf0_state = EState::None;\n\n  oldCurVal = xc0_roundedCurVal;\n  if (upperIncVal - xc4_curVal <= xc4_curVal - lowerIncVal)\n    xc0_roundedCurVal = upperIncVal;\n  else\n    xc0_roundedCurVal = lowerIncVal;\n\n  if (oldCurVal != xc0_roundedCurVal && xd8_changeCallback)\n    xd8_changeCallback(this, oldCurVal);\n\n  float fac;\n  if (xbc_maxVal == xb8_minVal)\n    fac = 0.f;\n  else\n    fac = (xc4_curVal - xb8_minVal) / (xbc_maxVal - xb8_minVal);\n\n  const zeus::CVector3f& s0 = xcc_sliderRangeWidgets[0]->GetIdlePosition();\n  const zeus::CVector3f& s1 = xcc_sliderRangeWidgets[1]->GetIdlePosition();\n\n  xcc_sliderRangeWidgets[0]->SetLocalPosition(s1 * fac + s0 * (1.f - fac));\n  xf4_24_inputPending = false;\n}\n\nbool CGuiSliderGroup::AddWorkerWidget(CGuiWidget* worker) {\n  if (worker->GetWorkerId() < 0 || worker->GetWorkerId() > 1)\n    return true;\n  xcc_sliderRangeWidgets[worker->GetWorkerId()] = worker;\n  return true;\n}\n\nCGuiWidget* CGuiSliderGroup::GetWorkerWidget(int id) const {\n  if (id < 0 || id > 1)\n    return nullptr;\n  return xcc_sliderRangeWidgets[id];\n}\n\nstd::shared_ptr<CGuiWidget> CGuiSliderGroup::Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp) {\n  CGuiWidgetParms parms = ReadWidgetHeader(frame, in);\n\n  float min = in.ReadFloat();\n  float max = in.ReadFloat();\n  float cur = in.ReadFloat();\n  float increment = in.ReadFloat();\n\n  std::shared_ptr<CGuiWidget> ret = std::make_shared<CGuiSliderGroup>(parms, min, max, cur, increment);\n  ret->ParseBaseInfo(frame, in, parms);\n  return ret;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CGuiSliderGroup.hpp",
    "content": "#pragma once\n\n#include <array>\n#include <functional>\n#include <memory>\n\n#include \"Runtime/GuiSys/CGuiCompoundWidget.hpp\"\n\nnamespace metaforce {\nclass CSimplePool;\n\nclass CGuiSliderGroup : public CGuiCompoundWidget {\npublic:\n  enum class EState { None, Decreasing, Increasing, MouseMove };\n\nprivate:\n  float xb8_minVal;\n  float xbc_maxVal;\n  float xc0_roundedCurVal;\n  float xc4_curVal;\n  float xc8_increment;\n  std::array<CGuiWidget*, 2> xcc_sliderRangeWidgets{};\n  std::function<void(CGuiSliderGroup*, float)> xd8_changeCallback;\n  EState xf0_state = EState::None;\n  bool xf4_24_inputPending : 1 = false;\n  mutable bool m_mouseInside : 1 = false;\n  bool m_mouseDown : 1 = false;\n\n  mutable float m_mouseT = 0.f;\n\n  void StartDecreasing();\n  void StartIncreasing();\n\npublic:\n  CGuiSliderGroup(const CGuiWidgetParms& parms, float a, float b, float c, float d);\n  FourCC GetWidgetTypeID() const override { return FOURCC('SLGP'); }\n\n  EState GetState() const { return xf0_state; }\n  void SetSelectionChangedCallback(std::function<void(CGuiSliderGroup*, float)>&& func);\n  void SetIncrement(float inc) { xc8_increment = inc; }\n  void SetMinVal(float min) {\n    xb8_minVal = min;\n    SetCurVal(xc0_roundedCurVal);\n  }\n  void SetMaxVal(float max) {\n    xbc_maxVal = max;\n    SetCurVal(xc0_roundedCurVal);\n  }\n  void SetCurVal(float cur);\n  float GetGurVal() const { return xc0_roundedCurVal; }\n\n  bool TestCursorHit(const zeus::CMatrix4f& vp, const zeus::CVector2f& point) const override;\n\n  void ProcessUserInput(const CFinalInput& input) override;\n  void Update(float dt) override;\n\n  bool AddWorkerWidget(CGuiWidget* worker) override;\n  CGuiWidget* GetWorkerWidget(int id) const override;\n\n  static std::shared_ptr<CGuiWidget> Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CGuiSys.cpp",
    "content": "#include \"Runtime/GuiSys/CGuiSys.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/GuiSys/CAuiEnergyBarT01.hpp\"\n#include \"Runtime/GuiSys/CAuiImagePane.hpp\"\n#include \"Runtime/GuiSys/CAuiMeter.hpp\"\n#include \"Runtime/GuiSys/CGuiCamera.hpp\"\n#include \"Runtime/GuiSys/CGuiFrame.hpp\"\n#include \"Runtime/GuiSys/CGuiGroup.hpp\"\n#include \"Runtime/GuiSys/CGuiHeadWidget.hpp\"\n#include \"Runtime/GuiSys/CGuiLight.hpp\"\n#include \"Runtime/GuiSys/CGuiModel.hpp\"\n#include \"Runtime/GuiSys/CGuiPane.hpp\"\n#include \"Runtime/GuiSys/CGuiSliderGroup.hpp\"\n#include \"Runtime/GuiSys/CGuiTableGroup.hpp\"\n#include \"Runtime/GuiSys/CGuiTextPane.hpp\"\n#include \"Runtime/GuiSys/CGuiWidget.hpp\"\n#include \"Runtime/GuiSys/CTextExecuteBuffer.hpp\"\n#include \"Runtime/GuiSys/CTextParser.hpp\"\n\nnamespace metaforce {\n\nCGuiSys* g_GuiSys = nullptr;\nCTextExecuteBuffer* g_TextExecuteBuf = nullptr;\nCTextParser* g_TextParser = nullptr;\n\nstd::shared_ptr<CGuiWidget> CGuiSys::CreateWidgetInGame(FourCC type, CInputStream& in, CGuiFrame* frame,\n                                                        CSimplePool* sp, u32 version) {\n  switch (type.toUint32()) {\n  case SBIG('BWIG'):\n    return CGuiWidget::Create(frame, in, sp);\n  case SBIG('HWIG'):\n    return CGuiHeadWidget::Create(frame, in, sp);\n  case SBIG('LITE'):\n    return CGuiLight::Create(frame, in, sp);\n  case SBIG('CAMR'):\n    return CGuiCamera::Create(frame, in, sp);\n  case SBIG('GRUP'):\n    return CGuiGroup::Create(frame, in, sp);\n  case SBIG('PANE'):\n    return CGuiPane::Create(frame, in, sp);\n  case SBIG('IMGP'):\n    return CAuiImagePane::Create(frame, in, sp);\n  case SBIG('METR'):\n    return CAuiMeter::Create(frame, in, sp);\n  case SBIG('MODL'):\n    return CGuiModel::Create(frame, in, sp);\n  case SBIG('TBGP'):\n    return CGuiTableGroup::Create(frame, in, sp);\n  case SBIG('SLGP'):\n    return CGuiSliderGroup::Create(frame, in, sp);\n  case SBIG('TXPN'):\n    return CGuiTextPane::Create(frame, in, sp, version);\n  case SBIG('ENRG'):\n    return CAuiEnergyBarT01::Create(frame, in, sp);\n  default:\n    return {};\n  }\n}\n\nCGuiSys::CGuiSys(IFactory& resFactory, CSimplePool& resStore, EUsageMode mode)\n: x0_resFactory(resFactory)\n, x4_resStore(resStore)\n, x8_mode(mode)\n, xc_textExecuteBuf(std::make_unique<CTextExecuteBuffer>())\n, x10_textParser(std::make_unique<CTextParser>(resStore)) {\n  g_TextExecuteBuf = xc_textExecuteBuf.get();\n  g_TextParser = x10_textParser.get();\n}\n\nvoid CGuiSys::OnViewportResize() {\n  for (CGuiFrame* frame : m_registeredFrames)\n    ViewportResizeFrame(frame);\n}\n\nvoid CGuiSys::ViewportResizeFrame(CGuiFrame* frame) {\n  if (frame->m_aspectConstraint > 0.f) {\n    float hPad, vPad;\n    if (CGraphics::GetViewportAspect() >= frame->m_aspectConstraint) {\n      hPad = frame->m_aspectConstraint / CGraphics::GetViewportAspect();\n      vPad = frame->m_aspectConstraint / 1.38f;\n    } else {\n      hPad = 1.f;\n      vPad = CGraphics::GetViewportAspect() / 1.38f;\n    }\n    frame->m_aspectTransform = zeus::CTransform::Scale({hPad, 1.f, vPad});\n  } else if (frame->m_maxAspect > 0.f) {\n    if (CGraphics::GetViewportAspect() > frame->m_maxAspect)\n      frame->m_aspectTransform =\n          zeus::CTransform::Scale({frame->m_maxAspect / CGraphics::GetViewportAspect(), 1.f, 1.f});\n    else\n      frame->m_aspectTransform = zeus::CTransform();\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CGuiSys.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <stack>\n#include <unordered_set>\n#include <vector>\n\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/GuiSys/CSaveableState.hpp\"\n\nnamespace metaforce {\nclass CGuiFrame;\nclass CGuiObject;\nclass CGuiWidget;\nclass CSimplePool;\nclass CTextExecuteBuffer;\nclass CTextParser;\nclass CVParamTransfer;\nclass IFactory;\nstruct SObjectTag;\n\nclass CGuiSys {\n  friend class CGuiFrame;\n\npublic:\n  enum class EUsageMode { Zero, One, Two };\n\nprivate:\n  IFactory& x0_resFactory;\n  CSimplePool& x4_resStore;\n  EUsageMode x8_mode;\n  std::unique_ptr<CTextExecuteBuffer> xc_textExecuteBuf;\n  std::unique_ptr<CTextParser> x10_textParser;\n  std::unordered_set<CGuiFrame*> m_registeredFrames;\n\n  static std::shared_ptr<CGuiWidget> CreateWidgetInGame(FourCC type, CInputStream& in, CGuiFrame* frame,\n                                                        CSimplePool* sp, u32 version);\n\npublic:\n  CGuiSys(IFactory& resFactory, CSimplePool& resStore, EUsageMode mode);\n\n  CSimplePool& GetResStore() { return x4_resStore; }\n  const CSimplePool& GetResStore() const { return x4_resStore; }\n  EUsageMode GetUsageMode() const { return x8_mode; }\n\n  void OnViewportResize();\n  static void ViewportResizeFrame(CGuiFrame* frame);\n};\n\n/** Global GuiSys instance */\nextern CGuiSys* g_GuiSys;\n\n/** Global CTextExecuteBuffer instance */\nextern CTextExecuteBuffer* g_TextExecuteBuf;\n\n/** Global CTextParser instance */\nextern CTextParser* g_TextParser;\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CGuiTableGroup.cpp",
    "content": "#include \"Runtime/GuiSys/CGuiTableGroup.hpp\"\n#include \"Runtime/Input/CFinalInput.hpp\"\n\nnamespace metaforce {\n\nbool CGuiTableGroup::CRepeatState::Update(float dt, bool state) {\n  if (x0_timer == 0.f) {\n    if (state) {\n      x0_timer = 0.6f;\n      return true;\n    }\n  } else {\n    if (state) {\n      x0_timer -= dt;\n      if (x0_timer <= 0.f) {\n        x0_timer = 0.05f;\n        return true;\n      }\n    } else {\n      x0_timer = 0.f;\n    }\n  }\n  return false;\n}\n\nCGuiTableGroup::CGuiTableGroup(const CGuiWidgetParms& parms, int elementCount, int defaultSel, bool selectWraparound)\n: CGuiCompoundWidget(parms)\n, xc0_elementCount(elementCount)\n, xc4_userSelection(defaultSel)\n, xc8_prevUserSelection(defaultSel)\n, xcc_defaultUserSelection(defaultSel)\n, xd0_selectWraparound(selectWraparound) {}\n\nvoid CGuiTableGroup::ProcessUserInput(const CFinalInput& input) {\n  if (input.PA() || input.PSpecialKey(ESpecialKey::Enter)) {\n    DoAdvance();\n  } else if (input.PB() || input.PSpecialKey(ESpecialKey::Esc)) {\n    DoCancel();\n  } else {\n    bool decrement;\n    if (xd1_vertical)\n      decrement = (input.DLAUp() || input.DDPUp());\n    else\n      decrement = (input.DLALeft() || input.DDPLeft());\n\n    bool increment;\n    if (xd1_vertical)\n      increment = (input.DLADown() || input.DDPDown());\n    else\n      increment = (input.DLARight() || input.DDPRight());\n\n    if (xb8_decRepeat.Update(input.DeltaTime(), decrement) && decrement) {\n      DoDecrement();\n      return;\n    }\n\n    if (xbc_incRepeat.Update(input.DeltaTime(), increment) && increment) {\n      DoIncrement();\n      return;\n    }\n  }\n}\n\nbool CGuiTableGroup::IsWorkerSelectable(int idx) const {\n  if (CGuiWidget* widget = GetWorkerWidget(idx))\n    return widget->GetIsSelectable();\n  return false;\n}\n\nvoid CGuiTableGroup::SelectWorker(int idx) {\n  idx = zeus::clamp(0, idx, xc0_elementCount - 1);\n  if (idx < xc4_userSelection) {\n    while (idx != xc4_userSelection)\n      DoSelectPrevRow();\n  } else {\n    while (idx != xc4_userSelection)\n      DoSelectNextRow();\n  }\n}\n\nvoid CGuiTableGroup::DoSelectWorker(int worker) {\n  if (worker == xc4_userSelection)\n    return;\n  if (IsWorkerSelectable(worker)) {\n    int oldSel = xc4_userSelection;\n    SelectWorker(worker);\n    if (x104_doMenuSelChange)\n      x104_doMenuSelChange(this, oldSel);\n  }\n}\n\nvoid CGuiTableGroup::SetWorkersMouseActive(bool active) {\n  CGuiWidget* child = static_cast<CGuiWidget*>(GetChildObject());\n  while (child) {\n    if (child->GetWorkerId() != -1)\n      child->SetMouseActive(active);\n    child = static_cast<CGuiWidget*>(child->GetNextSibling());\n  }\n}\n\nvoid CGuiTableGroup::DeactivateWorker(CGuiWidget* widget) { widget->SetIsActive(false); }\n\nvoid CGuiTableGroup::ActivateWorker(CGuiWidget* widget) { widget->SetIsActive(true); }\n\nCGuiTableGroup::TableSelectReturn CGuiTableGroup::DecrementSelectedRow() {\n  xc8_prevUserSelection = xc4_userSelection;\n  --xc4_userSelection;\n  if (xc4_userSelection < 0) {\n    xc4_userSelection = xd0_selectWraparound ? xc0_elementCount - 1 : 0;\n    return xd0_selectWraparound ? TableSelectReturn::WrappedAround : TableSelectReturn::Unchanged;\n  }\n  return TableSelectReturn::Changed;\n}\n\nCGuiTableGroup::TableSelectReturn CGuiTableGroup::IncrementSelectedRow() {\n  xc8_prevUserSelection = xc4_userSelection;\n  ++xc4_userSelection;\n  if (xc4_userSelection >= xc0_elementCount) {\n    xc4_userSelection = xd0_selectWraparound ? 0 : xc0_elementCount - 1;\n    return xd0_selectWraparound ? TableSelectReturn::WrappedAround : TableSelectReturn::Unchanged;\n  }\n  return TableSelectReturn::Changed;\n}\n\nvoid CGuiTableGroup::DoSelectPrevRow() {\n  DecrementSelectedRow();\n  DeactivateWorker(GetWorkerWidget(xc8_prevUserSelection));\n  ActivateWorker(GetWorkerWidget(xc4_userSelection));\n}\n\nvoid CGuiTableGroup::DoSelectNextRow() {\n  IncrementSelectedRow();\n  DeactivateWorker(GetWorkerWidget(xc8_prevUserSelection));\n  ActivateWorker(GetWorkerWidget(xc4_userSelection));\n}\n\nvoid CGuiTableGroup::DoCancel() {\n  if (xec_doMenuCancel)\n    xec_doMenuCancel(this);\n}\n\nvoid CGuiTableGroup::DoAdvance() {\n  if (xd4_doMenuAdvance)\n    xd4_doMenuAdvance(this);\n}\n\nbool CGuiTableGroup::PreDecrement() {\n  if (xd0_selectWraparound) {\n    for (int sel = (xc4_userSelection + xc0_elementCount - 1) % xc0_elementCount; sel != xc4_userSelection;\n         sel = (sel + xc0_elementCount - 1) % xc0_elementCount) {\n      if (IsWorkerSelectable(sel)) {\n        SelectWorker(sel);\n        return true;\n      }\n    }\n\n  } else {\n    for (int sel = std::max(-1, xc4_userSelection - 1); sel >= 0; --sel) {\n      if (IsWorkerSelectable(sel)) {\n        SelectWorker(sel);\n        return true;\n      }\n    }\n  }\n\n  return false;\n}\n\nvoid CGuiTableGroup::DoDecrement() {\n  int oldSel = xc4_userSelection;\n  if (!PreDecrement())\n    return;\n  if (x104_doMenuSelChange)\n    x104_doMenuSelChange(this, oldSel);\n}\n\nbool CGuiTableGroup::PreIncrement() {\n  if (xd0_selectWraparound) {\n    for (int sel = (xc4_userSelection + 1) % xc0_elementCount; sel != xc4_userSelection;\n         sel = (sel + 1) % xc0_elementCount) {\n      if (IsWorkerSelectable(sel)) {\n        SelectWorker(sel);\n        return true;\n      }\n    }\n\n  } else {\n    for (int sel = std::min(xc0_elementCount, xc4_userSelection + 1); sel < xc0_elementCount; ++sel) {\n      if (IsWorkerSelectable(sel)) {\n        SelectWorker(sel);\n        return true;\n      }\n    }\n  }\n\n  return false;\n}\n\nvoid CGuiTableGroup::DoIncrement() {\n  int oldSel = xc4_userSelection;\n  if (!PreIncrement())\n    return;\n  if (x104_doMenuSelChange)\n    x104_doMenuSelChange(this, oldSel);\n}\n\nstd::shared_ptr<CGuiWidget> CGuiTableGroup::Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp) {\n  CGuiWidgetParms parms = ReadWidgetHeader(frame, in);\n\n  int elementCount = in.ReadInt16();\n  in.ReadInt16();\n  in.ReadLong();\n  int defaultSel = in.ReadInt16();\n  in.ReadInt16();\n  bool selectWraparound = in.ReadBool();\n  in.ReadBool();\n  in.ReadFloat();\n  in.ReadFloat();\n  in.ReadBool();\n  in.ReadFloat();\n  in.ReadInt16();\n  in.ReadInt16();\n  in.ReadInt16();\n  in.ReadInt16();\n\n  std::shared_ptr<CGuiWidget> ret = std::make_shared<CGuiTableGroup>(parms, elementCount, defaultSel, selectWraparound);\n  ret->ParseBaseInfo(frame, in, parms);\n  return ret;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CGuiTableGroup.hpp",
    "content": "#pragma once\n\n#include <functional>\n#include <memory>\n\n#include \"Runtime/GuiSys/CGuiCompoundWidget.hpp\"\n\nnamespace metaforce {\n\nclass CGuiTableGroup : public CGuiCompoundWidget {\npublic:\n  class CRepeatState {\n    float x0_timer = 0.f;\n\n  public:\n    bool Update(float dt, bool state);\n  };\n\n  enum class TableSelectReturn { Changed, Unchanged, WrappedAround };\n\nprivate:\n  CRepeatState xb8_decRepeat;\n  CRepeatState xbc_incRepeat;\n  int xc0_elementCount;\n  int xc4_userSelection;\n  int xc8_prevUserSelection;\n  int xcc_defaultUserSelection;\n  bool xd0_selectWraparound;\n  bool xd1_vertical = true;\n  std::function<void(CGuiTableGroup*)> xd4_doMenuAdvance;\n  std::function<void(CGuiTableGroup*)> xec_doMenuCancel;\n  std::function<void(CGuiTableGroup*, int)> x104_doMenuSelChange;\n\n  void DeactivateWorker(CGuiWidget* widget);\n  void ActivateWorker(CGuiWidget* widget);\n\n  TableSelectReturn DecrementSelectedRow();\n  TableSelectReturn IncrementSelectedRow();\n  void DoSelectPrevRow();\n  void DoSelectNextRow();\n\n  void DoCancel();\n  void DoAdvance();\n  bool PreDecrement();\n  void DoDecrement();\n  bool PreIncrement();\n  void DoIncrement();\n\npublic:\n  CGuiTableGroup(const CGuiWidgetParms& parms, int, int, bool);\n  FourCC GetWidgetTypeID() const override { return FOURCC('TBGP'); }\n\n  void SetMenuAdvanceCallback(std::function<void(CGuiTableGroup*)>&& cb) { xd4_doMenuAdvance = std::move(cb); }\n\n  void SetMenuCancelCallback(std::function<void(CGuiTableGroup*)>&& cb) { xec_doMenuCancel = std::move(cb); }\n\n  void SetMenuSelectionChangeCallback(std::function<void(CGuiTableGroup*, int)>&& cb) {\n    x104_doMenuSelChange = std::move(cb);\n  }\n\n  void SetColors(const zeus::CColor& selected, const zeus::CColor& unselected) {\n    int id = -1;\n    while (CGuiWidget* worker = GetWorkerWidget(++id)) {\n      if (id == xc4_userSelection)\n        worker->SetColor(selected);\n      else\n        worker->SetColor(unselected);\n    }\n  }\n\n  void SetVertical(bool v) { xd1_vertical = v; }\n\n  void SetUserSelection(int sel) {\n    xc8_prevUserSelection = xc4_userSelection;\n    xc4_userSelection = sel;\n  }\n\n  int GetElementCount() const { return xc0_elementCount; }\n\n  int GetUserSelection() const { return xc4_userSelection; }\n\n  bool IsWorkerSelectable(int) const;\n\n  void SelectWorker(int);\n\n  void DoSelectWorker(int);\n\n  void SetWorkersMouseActive(bool);\n\n  void ProcessUserInput(const CFinalInput& input) override;\n\n  bool AddWorkerWidget(CGuiWidget* worker) override { return true; }\n\n  static std::shared_ptr<CGuiWidget> Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CGuiTextPane.cpp",
    "content": "#include \"Runtime/GuiSys/CGuiTextPane.hpp\"\n\n#include <array>\n\n#include \"Runtime/Graphics/CGraphics.hpp\"\n#include \"Runtime/Graphics/CGraphicsPalette.hpp\"\n#include \"Runtime/GuiSys/CFontImageDef.hpp\"\n#include \"Runtime/GuiSys/CGuiFrame.hpp\"\n#include \"Runtime/GuiSys/CGuiSys.hpp\"\n#include \"Runtime/GuiSys/CGuiWidgetDrawParms.hpp\"\n\nnamespace metaforce {\nnamespace {\nconstexpr std::array<zeus::CVector3f, 4> NormalPoints{{\n    {0.f, 0.f, -1.f},\n    {1.f, 0.f, -1.f},\n    {1.f, 0.f, 0.f},\n    {0.f, 0.f, 0.f},\n}};\nbool testProjectedLine(const zeus::CVector2f& a, const zeus::CVector2f& b, const zeus::CVector2f& point) {\n  const zeus::CVector2f normal = (b - a).perpendicularVector().normalized();\n  return point.dot(normal) >= a.dot(normal);\n}\n} // Anonymous namespace\n\nbool CGuiTextPane::sDrawPaneRects = false;\nCGuiTextPane::CGuiTextPane(const CGuiWidgetParms& parms, CSimplePool* sp, const zeus::CVector2f& dim,\n                           const zeus::CVector3f& vec, CAssetId fontId, const CGuiTextProperties& props,\n                           const zeus::CColor& fontCol, const zeus::CColor& outlineCol, s32 extentX, s32 extentY,\n                           CAssetId jpFontId, s32 jpExtentX, s32 jpExtentY)\n: CGuiPane(parms, dim, vec)\n, xd4_textSupport(fontId, props, fontCol, outlineCol, zeus::skWhite, extentX, extentY, sp, xac_drawFlags) {}\n\nvoid CGuiTextPane::Update(float dt) {\n  CGuiWidget::Update(dt);\n  xd4_textSupport.Update(dt);\n}\n\nbool CGuiTextPane::GetIsFinishedLoadingWidgetSpecific() { return xd4_textSupport.GetIsTextSupportFinishedLoading(); }\n\nvoid CGuiTextPane::SetDimensions(const zeus::CVector2f& dim, bool initVBO) {\n  CGuiPane::SetDimensions(dim, initVBO);\n  if (initVBO)\n    InitializeBuffers();\n}\n\nvoid CGuiTextPane::ScaleDimensions(const zeus::CVector3f& scale) {}\n\nvoid CGuiTextPane::Draw(const CGuiWidgetDrawParms& parms) {\n  if (sDrawPaneRects) {\n    CGuiPane::Draw({0.2f * parms.x0_alphaMod, parms.x4_cameraOffset});\n  }\n\n  if (!GetIsVisible()) {\n    return;\n  }\n  SCOPED_GRAPHICS_DEBUG_GROUP(fmt::format(\"CGuiTextPane::Draw {}\", m_name).c_str(), zeus::skCyan);\n\n  zeus::CVector2f dims = GetDimensions();\n\n  if (xd4_textSupport.x34_extentX != 0) {\n    dims.x() /= float(xd4_textSupport.x34_extentX);\n  } else {\n    dims.x() = 0.f;\n  }\n\n  if (xd4_textSupport.x38_extentY != 0) {\n    dims.y() /= float(xd4_textSupport.x38_extentY);\n  } else {\n    dims.y() = 0.f;\n  }\n\n  const zeus::CTransform local = zeus::CTransform::Translate(xc0_verts.front() + xc8_scaleCenter) *\n                                 zeus::CTransform::Scale(dims.x(), 1.f, dims.y());\n  CGraphics::SetModelMatrix(x34_worldXF * local);\n\n  zeus::CColor geomCol = xa8_color2;\n  geomCol.a() *= parms.x0_alphaMod;\n  xd4_textSupport.SetGeometryColor(geomCol);\n\n  CGraphics::SetDepthWriteMode(xb6_31_depthTest, ERglEnum::LEqual, xb7_24_depthWrite);\n\n  switch (xac_drawFlags) {\n  case EGuiModelDrawFlags::Shadeless:\n  case EGuiModelDrawFlags::Opaque:\n    CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::One, ERglBlendFactor::Zero, ERglLogicOp::Clear);\n    xd4_textSupport.Render();\n    break;\n  case EGuiModelDrawFlags::Alpha:\n    CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::InvSrcAlpha,\n                            ERglLogicOp::Clear);\n    xd4_textSupport.Render();\n    break;\n  case EGuiModelDrawFlags::Additive:\n    CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::One, ERglLogicOp::Clear);\n    xd4_textSupport.Render();\n    break;\n  case EGuiModelDrawFlags::AlphaAdditiveOverdraw:\n    CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::InvSrcAlpha,\n                            ERglLogicOp::Clear);\n    xd4_textSupport.Render();\n    xd4_textSupport.SetGeometryColor(geomCol * zeus::CColor(geomCol.a(), 1.f));\n    CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::One, ERglBlendFactor::One, ERglLogicOp::Clear);\n    xd4_textSupport.Render();\n    break;\n  }\n}\n\nbool CGuiTextPane::TestCursorHit(const zeus::CMatrix4f& vp, const zeus::CVector2f& point) const {\n  const zeus::CVector2f dims = GetDimensions();\n  const zeus::CTransform local = zeus::CTransform::Translate(xc0_verts.front() + xc8_scaleCenter) *\n                                 zeus::CTransform::Scale(dims.x(), 1.f, dims.y());\n  const zeus::CMatrix4f mvp = vp * (x34_worldXF * local).toMatrix4f();\n\n  std::array<zeus::CVector2f, 4> projPoints;\n  for (size_t i = 0; i < projPoints.size(); ++i) {\n    projPoints[i] = mvp.multiplyOneOverW(NormalPoints[i]).toVec2f();\n  }\n\n  size_t j;\n  for (j = 0; j < 3; ++j) {\n    if (!testProjectedLine(projPoints[j], projPoints[j + 1], point)) {\n      break;\n    }\n  }\n  return j == 3 && testProjectedLine(projPoints[3], projPoints[0], point);\n}\n\nstd::shared_ptr<CGuiWidget> CGuiTextPane::Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp, u32 version) {\n  const CGuiWidgetParms parms = ReadWidgetHeader(frame, in);\n  const zeus::CVector2f dim = in.Get<zeus::CVector2f>();\n  const zeus::CVector3f vec = in.Get<zeus::CVector3f>();\n  const CAssetId fontId = in.Get<CAssetId>();\n  const bool wordWrap = in.ReadBool();\n  const bool horizontal = in.ReadBool();\n  const auto justification = EJustification(in.ReadLong());\n  const auto vJustification = EVerticalJustification(in.ReadLong());\n  const CGuiTextProperties props(wordWrap, horizontal, justification, vJustification);\n  const zeus::CColor fontCol = in.Get<zeus::CColor>();\n  const zeus::CColor outlineCol = in.Get<zeus::CColor>();\n  const int extentX = static_cast<int>(in.ReadFloat());\n  const int extentY = static_cast<int>(in.ReadFloat());\n  int jpExtentX = extentX;\n  int jpExtentY = extentY;\n  CAssetId jpFontId = fontId;\n  if (version != 0) {\n    jpFontId = in.Get<CAssetId>();\n    jpExtentX = in.ReadLong();\n    jpExtentY = in.ReadLong();\n  }\n  auto ret = std::make_shared<CGuiTextPane>(parms, sp, dim, vec, fontId, props, fontCol, outlineCol, extentX, extentY,\n                                            jpFontId, jpExtentX, jpExtentY);\n  ret->ParseBaseInfo(frame, in, parms);\n  ret->InitializeBuffers();\n  ret->TextSupport().SetText(u\"\");\n  return ret;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CGuiTextPane.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <vector>\n\n#include \"Runtime/GuiSys/CGuiPane.hpp\"\n#include \"Runtime/GuiSys/CGuiTextSupport.hpp\"\n\nnamespace metaforce {\n\nclass CGuiTextPane : public CGuiPane {\n  static bool sDrawPaneRects;\n  CGuiTextSupport xd4_textSupport;\n\npublic:\n  CGuiTextPane(const CGuiWidgetParms& parms, CSimplePool* sp, const zeus::CVector2f& dim, const zeus::CVector3f& vec,\n               CAssetId fontId, const CGuiTextProperties& props, const zeus::CColor& col1, const zeus::CColor& col2,\n               s32 padX, s32 padY, CAssetId jpFontId, s32 jpExtentX, s32 jpExtentY);\n  FourCC GetWidgetTypeID() const override { return FOURCC('TXPN'); }\n\n  CGuiTextSupport& TextSupport() { return xd4_textSupport; }\n  const CGuiTextSupport& GetTextSupport() const { return xd4_textSupport; }\n  void Update(float dt) override;\n  bool GetIsFinishedLoadingWidgetSpecific() override;\n  std::vector<CAssetId> GetFontAssets() const { return {xd4_textSupport.x5c_fontId}; }\n  void SetDimensions(const zeus::CVector2f& dim, bool initVBO) override;\n  void ScaleDimensions(const zeus::CVector3f& scale) override;\n  void Draw(const CGuiWidgetDrawParms& parms) override;\n  bool TestCursorHit(const zeus::CMatrix4f& vp, const zeus::CVector2f& point) const override;\n\n  static std::shared_ptr<CGuiWidget> Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp, u32 version);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CGuiTextSupport.cpp",
    "content": "#include \"Runtime/GuiSys/CGuiTextSupport.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/Graphics/CGraphics.hpp\"\n#include \"Runtime/Graphics/CGraphicsPalette.hpp\"\n#include \"Runtime/GuiSys/CFontImageDef.hpp\"\n#include \"Runtime/GuiSys/CGuiSys.hpp\"\n#include \"Runtime/GuiSys/CRasterFont.hpp\"\n#include \"Runtime/GuiSys/CTextExecuteBuffer.hpp\"\n#include \"Runtime/GuiSys/CTextParser.hpp\"\n#include \"Runtime/CStringExtras.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\nnamespace metaforce {\n\nCGuiTextSupport::CGuiTextSupport(CAssetId fontId, const CGuiTextProperties& props, const zeus::CColor& fontCol,\n                                 const zeus::CColor& outlineCol, const zeus::CColor& geomCol, s32 padX, s32 padY,\n                                 CSimplePool* store, CGuiWidget::EGuiModelDrawFlags drawFlags)\n: x14_props(props)\n, x24_fontColor(fontCol)\n, x28_outlineColor(outlineCol)\n, x2c_geometryColor(geomCol)\n, x34_extentX(padX)\n, x38_extentY(padY)\n, x5c_fontId(fontId)\n, m_drawFlags(drawFlags) {\n  x2cc_font = store->GetObj({SBIG('FONT'), fontId});\n}\n\nCTextRenderBuffer* CGuiTextSupport::GetCurrentPageRenderBuffer() {\n  if (x60_renderBuf && !x308_multipageFlag) {\n    return &*x60_renderBuf;\n  }\n\n  if (!x308_multipageFlag || x2ec_renderBufferPages.size() <= x304_pageCounter) {\n    return nullptr;\n  }\n\n  int idx = 0;\n  for (CTextRenderBuffer& buf : x2ec_renderBufferPages) {\n    if (idx++ == x304_pageCounter) {\n      return &buf;\n    }\n  }\n\n  return nullptr;\n}\n\nconst CTextRenderBuffer* CGuiTextSupport::GetCurrentPageRenderBuffer() const {\n  if (x60_renderBuf && !x308_multipageFlag) {\n    return &*x60_renderBuf;\n  }\n\n  if (!x308_multipageFlag || x2ec_renderBufferPages.size() <= x304_pageCounter) {\n    return nullptr;\n  }\n\n  int idx = 0;\n  for (const CTextRenderBuffer& buf : x2ec_renderBufferPages) {\n    if (idx++ == x304_pageCounter) {\n      return &buf;\n    }\n  }\n\n  return nullptr;\n}\n\nfloat CGuiTextSupport::GetCurrentAnimationOverAge() const {\n  float ret = 0.f;\n  if (const CTextRenderBuffer* buf = GetCurrentPageRenderBuffer()) {\n    if (x50_typeEnable) {\n      if (!x40_primStartTimes.empty()) {\n        const auto& lastTime = x40_primStartTimes.back();\n        ret = std::max(ret, (buf->GetPrimitiveCount() - lastTime.second) / x58_chRate + lastTime.first);\n      } else {\n        ret = std::max(ret, buf->GetPrimitiveCount() / x58_chRate);\n      }\n    }\n  }\n  return ret;\n}\n\nfloat CGuiTextSupport::GetNumCharsTotal() const {\n  if (const CTextRenderBuffer* buf = GetCurrentPageRenderBuffer()) {\n    if (x50_typeEnable) {\n      return buf->GetPrimitiveCount();\n    }\n  }\n  return 0.f;\n}\n\nfloat CGuiTextSupport::GetNumCharsPrinted() const {\n  if (const CTextRenderBuffer* buf = GetCurrentPageRenderBuffer()) {\n    if (x50_typeEnable) {\n      const float charsPrinted = x3c_curTime * x58_chRate;\n      return std::min(charsPrinted, float(buf->GetPrimitiveCount()));\n    }\n  }\n  return 0.f;\n}\n\nfloat CGuiTextSupport::GetTotalAnimationTime() const {\n  if (const CTextRenderBuffer* buf = GetCurrentPageRenderBuffer()) {\n    if (x50_typeEnable) {\n      return buf->GetPrimitiveCount() / x58_chRate;\n    }\n  }\n  return 0.f;\n}\n\nbool CGuiTextSupport::IsAnimationDone() const { return x3c_curTime >= GetTotalAnimationTime(); }\n\nvoid CGuiTextSupport::SetTypeWriteEffectOptions(bool enable, float chFadeTime, float chRate) {\n  x50_typeEnable = enable;\n  x54_chFadeTime = std::max(chFadeTime, 0.0001f);\n  x58_chRate = std::max(chRate, 1.f);\n  if (enable) {\n    if (CTextRenderBuffer* buf = GetCurrentPageRenderBuffer()) {\n      float chStartTime = 0.f;\n      for (u32 i = 0; i < buf->GetPrimitiveCount(); ++i) {\n        for (const std::pair<float, int>& p : x40_primStartTimes) {\n          if (p.second < i)\n            continue;\n          if (p.second != i)\n            break;\n          chStartTime = p.first;\n          break;\n        }\n\n        //buf->SetPrimitiveOpacity(i, std::min(std::max(0.f, (x3c_curTime - chStartTime) / x54_chFadeTime), 1.f));\n        chStartTime += 1.f / x58_chRate;\n      }\n    }\n  }\n}\n\nvoid CGuiTextSupport::Update(float dt) {\n  if (x50_typeEnable) {\n    if (CTextRenderBuffer* buf = GetCurrentPageRenderBuffer()) {\n      float chStartTime = 0.f;\n      for (u32 i = 0; i < buf->GetPrimitiveCount(); ++i) {\n        for (const std::pair<float, int>& p : x40_primStartTimes) {\n          if (p.second < i)\n            continue;\n          if (p.second != i)\n            break;\n          chStartTime = p.first;\n          break;\n        }\n\n        auto primitive = buf->GetPrimitive(i);\n        float alpha = std::clamp((x3c_curTime - chStartTime) / x54_chFadeTime, 0.f, 1.f);\n        chStartTime += 1.f / x58_chRate;\n        primitive.x0_color1 = zeus::CColor{alpha, alpha};\n        buf->SetPrimitive(primitive, i);\n      }\n    }\n    x3c_curTime += dt;\n  }\n\n  x10_curTimeMod900 = std::fmod(x10_curTimeMod900 + dt, 900.f);\n}\n\nvoid CGuiTextSupport::ClearRenderBuffer() {\n  x60_renderBuf = std::nullopt;\n  x2ec_renderBufferPages.clear();\n}\n\nvoid CGuiTextSupport::CheckAndRebuildTextBuffer() {\n  g_TextExecuteBuf->Clear();\n  g_TextExecuteBuf->x18_textState.x7c_enableWordWrap = x14_props.x0_wordWrap;\n  g_TextExecuteBuf->BeginBlock(0, 0, x34_extentX, x38_extentY, x30_imageBaseline,\n                               ETextDirection(!x14_props.x1_horizontal), x14_props.x4_justification,\n                               x14_props.x8_vertJustification);\n  g_TextExecuteBuf->AddColor(EColorType::Main, x24_fontColor);\n  g_TextExecuteBuf->AddColor(EColorType::Outline, x28_outlineColor);\n\n  std::u16string initStr;\n  if (x5c_fontId.IsValid())\n    initStr = fmt::format(u\"&font={};\", x5c_fontId);\n  initStr += x0_string;\n\n  g_TextParser->ParseText(*g_TextExecuteBuf, initStr.c_str(), initStr.size(), x14_props.xc_txtrMap);\n\n  g_TextExecuteBuf->EndBlock();\n}\n\nbool CGuiTextSupport::CheckAndRebuildRenderBuffer() {\n  if (x308_multipageFlag || x60_renderBuf) {\n    if (!x308_multipageFlag || x2ec_renderBufferPages.size()) {\n      return true;\n    }\n  }\n\n  CheckAndRebuildTextBuffer();\n  x2bc_assets = g_TextExecuteBuf->GetAssets();\n\n  if (!_GetIsTextSupportFinishedLoading())\n    return false;\n\n  CheckAndRebuildTextBuffer();\n  if (x308_multipageFlag) {\n    zeus::CVector2i extent(x34_extentX, x38_extentY);\n    x2ec_renderBufferPages = g_TextExecuteBuf->BuildRenderBufferPages(extent, m_drawFlags);\n  } else {\n    x60_renderBuf.emplace(g_TextExecuteBuf->BuildRenderBuffer(m_drawFlags));\n    x2dc_oneBufBounds = x60_renderBuf->AccumulateTextBounds();\n  }\n  g_TextExecuteBuf->Clear();\n  Update(0.f);\n\n  return true;\n}\n\nconst std::pair<zeus::CVector2i, zeus::CVector2i>& CGuiTextSupport::GetBounds() {\n  CheckAndRebuildRenderBuffer();\n  return x2dc_oneBufBounds;\n}\n\nvoid CGuiTextSupport::AutoSetExtent() {\n  const auto& bounds = GetBounds();\n  x34_extentX = bounds.second.x;\n  x38_extentY = bounds.second.y;\n}\n\nvoid CGuiTextSupport::Render() {\n  CheckAndRebuildRenderBuffer();\n  if (CTextRenderBuffer* buf = GetCurrentPageRenderBuffer()) {\n    SCOPED_GRAPHICS_DEBUG_GROUP(\"CGuiTextSupport::Render\", zeus::skBlue);\n    zeus::CTransform oldModel = CGraphics::mModelMatrix;\n    CGraphics::SetModelMatrix(oldModel * zeus::CTransform::Scale(1.f, 1.f, -1.f));\n    buf->Render(x2c_geometryColor, x10_curTimeMod900);\n    CGraphics::SetModelMatrix(oldModel);\n  }\n}\n\nvoid CGuiTextSupport::SetGeometryColor(const zeus::CColor& col) { x2c_geometryColor = col; }\n\nvoid CGuiTextSupport::SetOutlineColor(const zeus::CColor& col) {\n  if (col == x28_outlineColor) {\n    return;\n  }\n\n  ClearRenderBuffer();\n  x28_outlineColor = col;\n}\n\nvoid CGuiTextSupport::SetFontColor(const zeus::CColor& col) {\n  if (col == x24_fontColor) {\n    return;\n  }\n\n  ClearRenderBuffer();\n  x24_fontColor = col;\n}\n\nvoid CGuiTextSupport::AddText(std::u16string_view str) {\n  if (x60_renderBuf) {\n    const float t = GetCurrentAnimationOverAge();\n    x40_primStartTimes.emplace_back(std::max(t, x3c_curTime), x60_renderBuf->GetPrimitiveCount());\n  }\n  x0_string += str;\n  ClearRenderBuffer();\n}\n\nvoid CGuiTextSupport::SetText(std::u16string_view str, bool multipage) {\n  if (x0_string == str) {\n    return;\n  }\n\n  x40_primStartTimes.clear();\n  x3c_curTime = 0.f;\n  x0_string = str;\n  ClearRenderBuffer();\n  x308_multipageFlag = multipage;\n  x304_pageCounter = 0;\n}\n\nvoid CGuiTextSupport::SetText(std::string_view str, bool multipage) {\n  SetText(CStringExtras::ConvertToUNICODE(str), multipage);\n}\n\nbool CGuiTextSupport::_GetIsTextSupportFinishedLoading() {\n  for (CToken& tok : x2bc_assets) {\n    tok.Lock();\n\n    if (!tok.IsLoaded()) {\n      return false;\n    }\n  }\n\n  if (!x2cc_font) {\n    return true;\n  }\n\n  if (x2cc_font.IsLoaded()) {\n    return x2cc_font->IsFinishedLoading();\n  }\n\n  return false;\n}\n\nvoid CGuiTextSupport::SetJustification(EJustification j) {\n  if (j == x14_props.x4_justification) {\n    return;\n  }\n\n  x14_props.x4_justification = j;\n  ClearRenderBuffer();\n}\n\nvoid CGuiTextSupport::SetVerticalJustification(EVerticalJustification j) {\n  if (j == x14_props.x8_vertJustification) {\n    return;\n  }\n\n  x14_props.x8_vertJustification = j;\n  ClearRenderBuffer();\n}\n\nvoid CGuiTextSupport::SetImageBaseline(bool b) {\n  if (b == x30_imageBaseline) {\n    return;\n  }\n\n  x30_imageBaseline = b;\n  ClearRenderBuffer();\n}\n\nbool CGuiTextSupport::GetIsTextSupportFinishedLoading() {\n  CheckAndRebuildRenderBuffer();\n  return _GetIsTextSupportFinishedLoading();\n}\n\nvoid CGuiTextSupport::SetControlTXTRMap(const std::vector<std::pair<CAssetId, CAssetId>>* txtrMap) {\n  if (x14_props.xc_txtrMap == txtrMap) {\n    return;\n  }\n\n  x14_props.xc_txtrMap = txtrMap;\n  ClearRenderBuffer();\n}\n\nint CGuiTextSupport::GetTotalPageCount() {\n  if (CheckAndRebuildRenderBuffer())\n    return x2ec_renderBufferPages.size();\n  return -1;\n}\n\nvoid CGuiTextSupport::SetPage(int page) {\n  x304_pageCounter = page;\n  x40_primStartTimes.clear();\n  x3c_curTime = 0.f;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CGuiTextSupport.hpp",
    "content": "#pragma once\n\n#include <list>\n#include <optional>\n#include <string>\n#include <vector>\n\n#include \"Runtime/CWorldSaveGameInfo.hpp\"\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/GuiSys/CGuiWidget.hpp\"\n#include \"Runtime/GuiSys/CRasterFont.hpp\"\n#include \"Runtime/GuiSys/CTextRenderBuffer.hpp\"\n\n#include <zeus/CColor.hpp>\n\nnamespace metaforce {\nclass CSimplePool;\nclass CTextExecuteBuffer;\nclass CTextRenderBuffer;\n\nenum class EJustification {\n  Left = 0,\n  Center,\n  Right,\n  Full,\n  NLeft,\n  NCenter,\n  NRight,\n  LeftMono,\n  CenterMono,\n  RightMono,\n};\n\nenum class EVerticalJustification {\n  Top = 0,\n  Center,\n  Bottom,\n  Full,\n  NTop,\n  NCenter,\n  NBottom,\n  TopMono,\n  CenterMono,\n  RightMono,\n};\n\nenum class ETextDirection {\n  Horizontal,\n  Vertical,\n};\n\nclass CGuiTextProperties {\n  friend class CGuiTextSupport;\n  bool x0_wordWrap;\n  bool x1_horizontal;\n  EJustification x4_justification;\n  EVerticalJustification x8_vertJustification;\n  const std::vector<std::pair<CAssetId, CAssetId>>* xc_txtrMap;\n\npublic:\n  CGuiTextProperties(bool wordWrap, bool horizontal, EJustification justification,\n                     EVerticalJustification vertJustification,\n                     const std::vector<std::pair<CAssetId, CAssetId>>* txtrMap = nullptr)\n  : x0_wordWrap(wordWrap)\n  , x1_horizontal(horizontal)\n  , x4_justification(justification)\n  , x8_vertJustification(vertJustification)\n  , xc_txtrMap(txtrMap) {}\n};\n\nclass CGuiTextSupport {\n  friend class CGuiTextPane;\n  std::u16string x0_string;\n  float x10_curTimeMod900 = 0.f;\n  CGuiTextProperties x14_props;\n  zeus::CColor x24_fontColor;\n  zeus::CColor x28_outlineColor;\n  zeus::CColor x2c_geometryColor;\n  bool x30_imageBaseline = false;\n  s32 x30_; // new in PAL/JP\n  s32 x34_; // \"\"\n  s32 x34_extentX;\n  s32 x38_extentY;\n  float x3c_curTime = 0.f;\n  std::vector<std::pair<float, int>> x40_primStartTimes;\n  bool x50_typeEnable = false;\n  float x54_chFadeTime = 0.1f;\n  float x58_chRate = 10.0f;\n  CAssetId x5c_fontId;\n  CGuiWidget::EGuiModelDrawFlags m_drawFlags;\n  std::optional<CTextRenderBuffer> x60_renderBuf;\n  std::vector<CToken> x2bc_assets;\n  TLockedToken<CRasterFont> x2cc_font;\n  std::pair<zeus::CVector2i, zeus::CVector2i> x2dc_oneBufBounds;\n\n  std::list<CTextRenderBuffer> x2ec_renderBufferPages;\n  int x304_pageCounter = 0;\n  bool x308_multipageFlag = false;\n\n  CTextRenderBuffer* GetCurrentPageRenderBuffer();\n  const CTextRenderBuffer* GetCurrentPageRenderBuffer() const;\n\n  bool _GetIsTextSupportFinishedLoading();\n\npublic:\n  CGuiTextSupport(CAssetId fontId, const CGuiTextProperties& props, const zeus::CColor& fontCol,\n                  const zeus::CColor& outlineCol, const zeus::CColor& geomCol, s32 extX, s32 extY, CSimplePool* store,\n                  CGuiWidget::EGuiModelDrawFlags drawFlags);\n  float GetCurrentAnimationOverAge() const;\n  float GetNumCharsTotal() const;\n  float GetNumCharsPrinted() const;\n  float GetTotalAnimationTime() const;\n  bool IsAnimationDone() const;\n  void SetTypeWriteEffectOptions(bool enable, float chFadeTime, float chRate);\n  void Update(float dt);\n  void ClearRenderBuffer();\n  void CheckAndRebuildTextBuffer();\n  bool CheckAndRebuildRenderBuffer();\n  const std::pair<zeus::CVector2i, zeus::CVector2i>& GetBounds();\n  void AutoSetExtent();\n\n  void Render();\n  void SetGeometryColor(const zeus::CColor& col);\n  void SetOutlineColor(const zeus::CColor& col);\n  void SetFontColor(const zeus::CColor& col);\n  void AddText(std::u16string_view str);\n  void SetText(std::u16string_view str, bool multipage = false);\n  void SetText(std::string_view str, bool multipage = false);\n  void SetJustification(EJustification j);\n  void SetVerticalJustification(EVerticalJustification j);\n  void SetImageBaseline(bool b);\n  bool GetIsTextSupportFinishedLoading();\n  float GetCurTimeMod900() const { return x10_curTimeMod900; }\n  s32 GetExtentX() const { return x34_extentX; }\n  void SetExtentX(s32 extent) {\n    x34_extentX = extent;\n    ClearRenderBuffer();\n  }\n  s32 GetExtentY() const { return x38_extentY; }\n  void SetExtentY(s32 extent) {\n    x38_extentY = extent;\n    ClearRenderBuffer();\n  }\n  float GetCurTime() const { return x3c_curTime; }\n  void SetCurTime(float t) { x3c_curTime = t; }\n  std::u16string_view GetString() const { return x0_string; }\n  void SetControlTXTRMap(const std::vector<std::pair<CAssetId, CAssetId>>* txtrMap);\n  int GetPageCounter() const { return x304_pageCounter; }\n  int GetTotalPageCount();\n  void SetPage(int page);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CGuiWidget.cpp",
    "content": "#include \"Runtime/Graphics/CGraphics.hpp\"\n#include \"Runtime/GuiSys/CGuiWidget.hpp\"\n#include \"Runtime/GuiSys/CGuiFrame.hpp\"\n#include \"Runtime/Logging.hpp\"\n\nnamespace metaforce {\nCGuiWidget::CGuiWidget(const CGuiWidgetParms& parms)\n: x70_selfId(parms.x6_selfId)\n, x72_parentId(parms.x8_parentId)\n, m_initColor(parms.x10_color)\n, xa4_color(parms.x10_color)\n, xa8_color2(parms.x10_color)\n, xac_drawFlags(parms.x14_drawFlags)\n, xb0_frame(parms.x0_frame)\n, m_name(parms.m_name) {\n  xb6_24_pg = parms.xd_g;\n  xb6_25_isVisible = parms.xa_defaultVisible;\n  xb6_26_isActive = parms.xb_defaultActive;\n  xb6_29_cullFaces = parms.xc_cullFaces;\n  RecalcWidgetColor(ETraversalMode::Single);\n}\n\nCGuiWidget::CGuiWidgetParms CGuiWidget::ReadWidgetHeader(CGuiFrame* frame, CInputStream& in) {\n  std::string name = in.Get<std::string>();\n  s16 selfId = frame->GetWidgetIdDB().AddWidget(name);\n  std::string parent = in.Get<std::string>();\n  s16 parentId = frame->GetWidgetIdDB().AddWidget(parent);\n\n  bool useAnimController = in.ReadBool();\n  bool defaultVis = in.ReadBool();\n  bool defaultActive = in.ReadBool();\n  bool cullFaces = in.ReadBool();\n  zeus::CColor color = in.Get<zeus::CColor>();\n  EGuiModelDrawFlags df = EGuiModelDrawFlags(in.ReadLong());\n\n  return CGuiWidget::CGuiWidgetParms(frame, useAnimController, selfId, parentId, defaultVis, defaultActive, cullFaces,\n                                     color, df, true, false, std::move(name));\n}\n\nstd::shared_ptr<CGuiWidget> CGuiWidget::Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp) {\n  CGuiWidgetParms parms = ReadWidgetHeader(frame, in);\n  std::shared_ptr<CGuiWidget> ret = std::make_shared<CGuiWidget>(parms);\n  ret->ParseBaseInfo(frame, in, parms);\n  return ret;\n}\n\nvoid CGuiWidget::Initialize() {}\n\nvoid CGuiWidget::ParseBaseInfo(CGuiFrame* frame, CInputStream& in, const CGuiWidgetParms& parms) {\n  CGuiWidget* parent = frame->FindWidget(parms.x8_parentId);\n  bool isWorker = in.ReadBool();\n  if (isWorker)\n    xb4_workerId = in.ReadInt16();\n  zeus::CVector3f trans = in.Get<zeus::CVector3f>();\n  zeus::CMatrix3f orient = in.Get<zeus::CMatrix3f>();\n  x74_transform = zeus::CTransform(orient, trans);\n  m_initTransform = x74_transform;\n  ReapplyXform();\n  in.Get<zeus::CVector3f>(); // Unused\n  in.ReadLong();\n  in.ReadShort();\n  if (isWorker) {\n    if (!parent->AddWorkerWidget(this)) {\n      spdlog::warn(\"Warning: Discarding useless worker id. Parent is not a compound widget.\");\n      xb4_workerId = -1;\n    }\n  }\n  parent->AddChildWidget(this, false, true);\n  m_initLocalXF = x4_localXF;\n}\n\nvoid CGuiWidget::Reset(ETraversalMode mode) {\n  xa4_color = m_initColor;\n  xa8_color2 = m_initColor;\n  x74_transform = m_initTransform;\n  ReapplyXform();\n  x4_localXF = m_initLocalXF;\n  RecalculateTransforms();\n\n  switch (mode) {\n  case ETraversalMode::Children: {\n    CGuiWidget* child = static_cast<CGuiWidget*>(GetChildObject());\n    if (child)\n      child->Reset(ETraversalMode::ChildrenAndSiblings);\n    break;\n  }\n  case ETraversalMode::ChildrenAndSiblings: {\n    CGuiWidget* child = static_cast<CGuiWidget*>(GetChildObject());\n    if (child)\n      child->Reset(ETraversalMode::ChildrenAndSiblings);\n    CGuiWidget* nextSib = static_cast<CGuiWidget*>(GetNextSibling());\n    if (nextSib)\n      nextSib->Reset(ETraversalMode::ChildrenAndSiblings);\n    break;\n  }\n  default:\n    break;\n  }\n}\n\nvoid CGuiWidget::Update(float dt) {\n  CGuiWidget* ch = static_cast<CGuiWidget*>(GetChildObject());\n  if (ch)\n    ch->Update(dt);\n  CGuiWidget* sib = static_cast<CGuiWidget*>(GetNextSibling());\n  if (sib)\n    sib->Update(dt);\n}\n\nvoid CGuiWidget::Draw(const CGuiWidgetDrawParms& parms) {}\nvoid CGuiWidget::ProcessUserInput(const CFinalInput& input) {}\nvoid CGuiWidget::Touch() {}\n\nbool CGuiWidget::GetIsVisible() const { return xb6_25_isVisible; }\n\nbool CGuiWidget::GetIsActive() const { return xb6_26_isActive; }\n\nbool CGuiWidget::GetMouseActive() const { return m_mouseActive; }\n\nvoid CGuiWidget::InitializeRGBAFactor() {\n  CGuiWidget* child = static_cast<CGuiWidget*>(GetChildObject());\n  if (child)\n    child->InitializeRGBAFactor();\n  CGuiWidget* nextSib = static_cast<CGuiWidget*>(GetNextSibling());\n  if (nextSib)\n    nextSib->InitializeRGBAFactor();\n}\n\nbool CGuiWidget::GetIsFinishedLoadingWidgetSpecific() { return true; }\n\nvoid CGuiWidget::SetTransform(const zeus::CTransform& xf) {\n  x74_transform = xf;\n  ReapplyXform();\n}\n\nvoid CGuiWidget::SetIdlePosition(const zeus::CVector3f& pos, bool reapply) {\n  x74_transform.origin = pos;\n  if (reapply)\n    ReapplyXform();\n}\n\nvoid CGuiWidget::ReapplyXform() {\n  RotateReset();\n  SetLocalPosition(zeus::skZero3f);\n  MultiplyO2P(x74_transform);\n}\n\nvoid CGuiWidget::AddChildWidget(CGuiWidget* widget, bool makeWorldLocal, bool atEnd) {\n  AddChildObject(widget, makeWorldLocal, atEnd);\n}\n\nbool CGuiWidget::AddWorkerWidget(CGuiWidget* worker) { return false; }\n\nvoid CGuiWidget::SetVisibility(bool visible, ETraversalMode mode) {\n  switch (mode) {\n  case ETraversalMode::Children: {\n    auto* child = static_cast<CGuiWidget*>(GetChildObject());\n    if (child) {\n      child->SetVisibility(visible, ETraversalMode::ChildrenAndSiblings);\n    }\n    break;\n  }\n  case ETraversalMode::ChildrenAndSiblings: {\n    auto* child = static_cast<CGuiWidget*>(GetChildObject());\n    if (child) {\n      child->SetVisibility(visible, ETraversalMode::ChildrenAndSiblings);\n    }\n    auto* nextSib = static_cast<CGuiWidget*>(GetNextSibling());\n    if (nextSib) {\n      nextSib->SetVisibility(visible, ETraversalMode::ChildrenAndSiblings);\n    }\n    break;\n  }\n  default:\n    break;\n  }\n  SetIsVisible(visible);\n}\n\nvoid CGuiWidget::RecalcWidgetColor(ETraversalMode mode) {\n  CGuiWidget* parent = static_cast<CGuiWidget*>(GetParent());\n  if (parent)\n    xa8_color2 = xa4_color * parent->xa8_color2;\n  else\n    xa8_color2 = xa4_color;\n\n  switch (mode) {\n  case ETraversalMode::ChildrenAndSiblings: {\n    CGuiWidget* nextSib = static_cast<CGuiWidget*>(GetNextSibling());\n    if (nextSib)\n      nextSib->RecalcWidgetColor(ETraversalMode::ChildrenAndSiblings);\n    [[fallthrough]];\n  }\n  case ETraversalMode::Children: {\n    CGuiWidget* child = static_cast<CGuiWidget*>(GetChildObject());\n    if (child)\n      child->RecalcWidgetColor(ETraversalMode::ChildrenAndSiblings);\n    break;\n  }\n  default:\n    break;\n  }\n}\n\nCGuiWidget* CGuiWidget::FindWidget(s16 id) {\n  if (x70_selfId == id)\n    return this;\n  CGuiWidget* child = static_cast<CGuiWidget*>(GetChildObject());\n  if (child) {\n    CGuiWidget* found = child->FindWidget(id);\n    if (found)\n      return found;\n  }\n  CGuiWidget* nextSib = static_cast<CGuiWidget*>(GetNextSibling());\n  if (nextSib) {\n    CGuiWidget* found = nextSib->FindWidget(id);\n    if (found)\n      return found;\n  }\n  return nullptr;\n}\n\nbool CGuiWidget::GetIsFinishedLoading() { return GetIsFinishedLoadingWidgetSpecific(); }\n\nvoid CGuiWidget::DispatchInitialize() {\n  Initialize();\n  CGuiWidget* ch = static_cast<CGuiWidget*>(GetChildObject());\n  if (ch)\n    ch->DispatchInitialize();\n  CGuiWidget* sib = static_cast<CGuiWidget*>(GetNextSibling());\n  if (sib)\n    sib->DispatchInitialize();\n}\n\nvoid CGuiWidget::SetColor(const zeus::CColor& color) {\n  xa4_color = color;\n  RecalcWidgetColor(ETraversalMode::Children);\n}\n\nvoid CGuiWidget::OnActiveChange() {}\nvoid CGuiWidget::OnVisibleChange() {}\n\nvoid CGuiWidget::SetIsVisible(bool visible) {\n  xb6_25_isVisible = visible;\n  OnVisibleChange();\n}\n\nvoid CGuiWidget::SetIsActive(bool active) {\n  if (active == xb6_26_isActive) {\n    return;\n  }\n\n  xb6_26_isActive = active;\n  OnActiveChange();\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CGuiWidget.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <optional>\n#include <string>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/GuiSys/CGuiObject.hpp\"\n#include \"Runtime/Input/CKeyboardMouseController.hpp\"\n\n#include <zeus/CColor.hpp>\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CGuiFrame;\nclass CGuiTextSupport;\nstruct CFinalInput;\nclass CSimplePool;\n\nenum class ETraversalMode { ChildrenAndSiblings = 0, Children = 1, Single = 2 };\n\nenum class EGuiTextureClampModeHorz { NoClamp = 0, Right = 1, Left = 2, Center = 3 };\n\nenum class EGuiTextureClampModeVert { NoClamp = 0, Top = 1, Bottom = 2, Center = 3 };\n\nclass CGuiWidget : public CGuiObject {\n  friend class CGuiFrame;\n\npublic:\n  enum class EGuiModelDrawFlags { Shadeless = 0, Opaque = 1, Alpha = 2, Additive = 3, AlphaAdditiveOverdraw = 4 };\n  struct CGuiWidgetParms {\n    CGuiFrame* x0_frame;\n    bool x4_useAnimController;\n    s16 x6_selfId;\n    s16 x8_parentId;\n    bool xa_defaultVisible;\n    bool xb_defaultActive;\n    bool xc_cullFaces;\n    bool xd_g;\n    bool xe_h;\n    zeus::CColor x10_color;\n    EGuiModelDrawFlags x14_drawFlags;\n    std::string m_name;\n    CGuiWidgetParms(CGuiFrame* frame, bool useAnimController, s16 selfId, s16 parentId, bool defaultVisible,\n                    bool defaultActive, bool cullFaces, const zeus::CColor& color, EGuiModelDrawFlags drawFlags, bool g,\n                    bool h, std::string&& name)\n    : x0_frame(frame)\n    , x4_useAnimController(useAnimController)\n    , x6_selfId(selfId)\n    , x8_parentId(parentId)\n    , xa_defaultVisible(defaultVisible)\n    , xb_defaultActive(defaultActive)\n    , xc_cullFaces(cullFaces)\n    , xd_g(g)\n    , xe_h(h)\n    , x10_color(color)\n    , x14_drawFlags(drawFlags)\n    , m_name(std::move(name)) {}\n  };\n\nprotected:\n  s16 x70_selfId;\n  s16 x72_parentId;\n  zeus::CTransform m_initTransform;\n  zeus::CTransform x74_transform;\n  zeus::CColor m_initColor;\n  zeus::CColor xa4_color;\n  zeus::CColor xa8_color2;\n  EGuiModelDrawFlags xac_drawFlags;\n  CGuiFrame* xb0_frame;\n  s16 xb4_workerId = -1;\n  bool xb6_24_pg : 1;\n  bool xb6_25_isVisible : 1;\n  bool xb6_26_isActive : 1;\n  bool xb6_27_isSelectable : 1 = true;\n  bool xb6_28_eventLock : 1 = false;\n  bool xb6_29_cullFaces : 1;\n  bool xb6_30_depthGreater : 1 = false;\n  bool xb6_31_depthTest : 1 = true;\n  bool xb7_24_depthWrite : 1 = false;\n  bool xb7_25_ : 1 = true;\n  bool m_mouseActive : 1 = false;\n\n  std::optional<SScrollDelta> m_lastScroll;\n  SScrollDelta m_integerScroll;\n\n  std::string m_name;\n\npublic:\n  explicit CGuiWidget(const CGuiWidgetParms& parms);\n\n  static CGuiWidgetParms ReadWidgetHeader(CGuiFrame* frame, CInputStream& in);\n  static std::shared_ptr<CGuiWidget> Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp);\n\n  void Update(float dt) override;\n  void Draw(const CGuiWidgetDrawParms& drawParms) override;\n  void Initialize() override;\n\n  virtual void Reset(ETraversalMode mode);\n  virtual void ProcessUserInput(const CFinalInput& input);\n  virtual void Touch();\n  virtual bool GetIsVisible() const;\n  virtual bool GetIsActive() const;\n  virtual bool GetMouseActive() const;\n  virtual FourCC GetWidgetTypeID() const { return FOURCC('BWIG'); }\n  virtual bool AddWorkerWidget(CGuiWidget* worker);\n  virtual bool GetIsFinishedLoadingWidgetSpecific();\n  virtual void OnVisibleChange();\n  virtual void OnActiveChange();\n\n  s16 GetSelfId() const { return x70_selfId; }\n  s16 GetParentId() const { return x72_parentId; }\n  s16 GetWorkerId() const { return xb4_workerId; }\n  const zeus::CTransform& GetTransform() const { return x74_transform; }\n  zeus::CTransform& GetTransform() { return x74_transform; }\n  const zeus::CVector3f& GetIdlePosition() const { return x74_transform.origin; }\n  void SetTransform(const zeus::CTransform& xf);\n  const zeus::CColor& GetIntermediateColor() const { return xa4_color; }\n  const zeus::CColor& GetGeometryColor() const { return xa8_color2; }\n  void SetIdlePosition(const zeus::CVector3f& pos, bool reapply);\n  void ReapplyXform();\n  virtual void SetIsVisible(bool visible);\n  void SetIsActive(bool active);\n  bool GetIsSelectable() const { return xb6_27_isSelectable; }\n  void SetIsSelectable(bool selectable) { xb6_27_isSelectable = selectable; }\n  void SetMouseActive(bool mouseActive) { m_mouseActive = mouseActive; }\n\n  void ParseBaseInfo(CGuiFrame* frame, CInputStream& in, const CGuiWidgetParms& parms);\n  void AddChildWidget(CGuiWidget* widget, bool makeWorldLocal, bool atEnd);\n  void SetVisibility(bool visible, ETraversalMode mode);\n  void RecalcWidgetColor(ETraversalMode mode);\n  void SetColor(const zeus::CColor& color);\n  void InitializeRGBAFactor();\n  CGuiWidget* FindWidget(s16 id);\n  bool GetIsFinishedLoading();\n  void DispatchInitialize();\n  void SetDepthGreater(bool depthGreater) { xb6_30_depthGreater = depthGreater; }\n  void SetDepthTest(bool depthTest) { xb6_31_depthTest = depthTest; }\n  void SetDepthWrite(bool depthWrite) { xb7_24_depthWrite = depthWrite; }\n\n  CGuiFrame* GetGuiFrame() const { return xb0_frame; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CGuiWidgetDrawParms.hpp",
    "content": "#pragma once\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\n\nstruct CGuiWidgetDrawParms {\n  float x0_alphaMod = 1.f;\n  zeus::CVector3f x4_cameraOffset;\n\n  constexpr CGuiWidgetDrawParms() = default;\n  constexpr CGuiWidgetDrawParms(float alphaMod, const zeus::CVector3f& cameraOff)\n  : x0_alphaMod(alphaMod), x4_cameraOffset(cameraOff) {}\n\n  static constexpr CGuiWidgetDrawParms Default() { return {}; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CGuiWidgetIdDB.cpp",
    "content": "#include \"Runtime/GuiSys/CGuiWidgetIdDB.hpp\"\n\nnamespace metaforce {\n\nCGuiWidgetIdDB::CGuiWidgetIdDB() {\n  AddWidget(\"kGSYS_DummyWidgetID\", 0);\n  AddWidget(\"kGSYS_HeadWidgetID\", 1);\n  AddWidget(\"kGSYS_DefaultCameraID\");\n  AddWidget(\"kGSYS_DefaultLightID\");\n}\n\ns16 CGuiWidgetIdDB::FindWidgetID(std::string_view name) const {\n  // TODO: Heterogeneous lookup when C++20 available\n  auto search = x0_dbMap.find(name.data());\n  if (search == x0_dbMap.cend())\n    return -1;\n  return search->second;\n}\n\ns16 CGuiWidgetIdDB::AddWidget(std::string_view name, s16 id) {\n  s16 findId = FindWidgetID(name);\n  if (findId == -1) {\n    if (id >= x14_lastPoolId)\n      x14_lastPoolId = id;\n    x0_dbMap.emplace(name, id);\n    findId = id;\n  }\n  return findId;\n}\n\ns16 CGuiWidgetIdDB::AddWidget(std::string_view name) {\n  s16 findId = FindWidgetID(name);\n  if (findId == -1) {\n    ++x14_lastPoolId;\n    x0_dbMap.emplace(name, x14_lastPoolId);\n    findId = x14_lastPoolId;\n  }\n  return findId;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CGuiWidgetIdDB.hpp",
    "content": "#pragma once\n\n#include <string>\n#include <string_view>\n#include <unordered_map>\n#include \"Runtime/GCNTypes.hpp\"\n\nnamespace metaforce {\n\nclass CGuiWidgetIdDB {\n  std::unordered_map<std::string, s16> x0_dbMap;\n  s16 x14_lastPoolId = 0;\n\npublic:\n  CGuiWidgetIdDB();\n  s16 FindWidgetID(std::string_view name) const;\n  s16 AddWidget(std::string_view name, s16 id);\n  s16 AddWidget(std::string_view name);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CHudBallInterface.cpp",
    "content": "#include \"Runtime/GuiSys/CHudBallInterface.hpp\"\n\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/GuiSys/CGuiCamera.hpp\"\n#include \"Runtime/GuiSys/CGuiFrame.hpp\"\n#include \"Runtime/GuiSys/CGuiGroup.hpp\"\n#include \"Runtime/GuiSys/CGuiModel.hpp\"\n#include \"Runtime/GuiSys/CGuiTextPane.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\nnamespace metaforce {\n\nCHudBallInterface::CHudBallInterface(CGuiFrame& selHud, int pbAmount, int pbCapacity, int availableBombs, bool hasBombs,\n                                     bool hasPb)\n: x40_pbAmount(pbAmount), x44_pbCapacity(pbCapacity), x48_availableBombs(availableBombs), x4c_hasPb(hasPb) {\n  x0_camera = selHud.GetFrameCamera();\n  x4_basewidget_bombstuff = selHud.FindWidget(\"basewidget_bombstuff\");\n  x8_basewidget_bombdeco = selHud.FindWidget(\"basewidget_bombdeco\");\n  xc_model_bombicon = static_cast<CGuiModel*>(selHud.FindWidget(\"model_bombicon\"));\n  x10_textpane_bombdigits = static_cast<CGuiTextPane*>(selHud.FindWidget(\"textpane_bombdigits\"));\n  for (int i = 0; i < 3; ++i) {\n    CGuiGroup* grp = static_cast<CGuiGroup*>(selHud.FindWidget(fmt::format(\"group_bombcount{}\", i)));\n    CGuiWidget* filled = grp->GetWorkerWidget(1);\n    CGuiWidget* empty = grp->GetWorkerWidget(0);\n    x14_group_bombfilled.push_back(filled);\n    x24_group_bombempty.push_back(empty);\n    if (filled)\n      filled->SetColor(g_tweakGuiColors->GetBallBombFilledColor());\n    if (empty)\n      empty->SetColor(g_tweakGuiColors->GetBallBombEmptyColor());\n  }\n  x8_basewidget_bombdeco->SetColor(g_tweakGuiColors->GetBallBombDecoColor());\n  x34_camPos = x0_camera->GetLocalPosition();\n  if (CGuiWidget* w = selHud.FindWidget(\"basewidget_energydeco\"))\n    w->SetColor(g_tweakGuiColors->GetBallBombEnergyColor());\n  SetBombParams(pbAmount, pbCapacity, availableBombs, hasBombs, hasPb, true);\n}\n\nvoid CHudBallInterface::UpdatePowerBombReadoutColors() {\n  zeus::CColor fontColor;\n  zeus::CColor outlineColor;\n  if (x40_pbAmount > 0) {\n    fontColor = g_tweakGuiColors->GetPowerBombDigitAvailableFont();\n    outlineColor = g_tweakGuiColors->GetPowerBombDigitAvailableOutline();\n  } else if (x44_pbCapacity > 0) {\n    fontColor = g_tweakGuiColors->GetPowerBombDigitDelpetedFont();\n    outlineColor = g_tweakGuiColors->GetPowerBombDigitDelpetedOutline();\n  } else {\n    fontColor = zeus::skClear;\n    outlineColor = zeus::skClear;\n  }\n  x10_textpane_bombdigits->TextSupport().SetFontColor(fontColor);\n  x10_textpane_bombdigits->TextSupport().SetOutlineColor(outlineColor);\n\n  zeus::CColor iconColor;\n  if (x40_pbAmount > 0 && x4c_hasPb)\n    iconColor = g_tweakGuiColors->GetPowerBombIconAvailableColor();\n  else if (x44_pbCapacity > 0)\n    iconColor = g_tweakGuiColors->GetPowerBombIconDepletedColor();\n  else\n    iconColor = zeus::skClear;\n\n  xc_model_bombicon->SetColor(iconColor);\n}\n\nvoid CHudBallInterface::SetBombParams(int pbAmount, int pbCapacity, int availableBombs, bool hasBombs, bool hasPb,\n                                      bool init) {\n  if (pbAmount != x40_pbAmount || init) {\n    x10_textpane_bombdigits->TextSupport().SetText(fmt::format(\"{:02d}\", pbAmount));\n    x40_pbAmount = pbAmount;\n    UpdatePowerBombReadoutColors();\n  }\n\n  if (x44_pbCapacity != pbCapacity || init) {\n    x44_pbCapacity = pbCapacity;\n    UpdatePowerBombReadoutColors();\n  }\n\n  if (x4c_hasPb != hasPb) {\n    x4c_hasPb = hasPb;\n    UpdatePowerBombReadoutColors();\n  }\n\n  for (int i = 0; i < 3; ++i) {\n    bool lit = i < availableBombs;\n    x14_group_bombfilled[i]->SetVisibility(lit && hasBombs, ETraversalMode::Children);\n    x24_group_bombempty[i]->SetVisibility(!lit && hasBombs, ETraversalMode::Children);\n  }\n\n  x48_availableBombs = availableBombs;\n\n  x8_basewidget_bombdeco->SetVisibility(hasBombs && x44_pbCapacity > 0, ETraversalMode::Children);\n}\n\nvoid CHudBallInterface::SetBallModeFactor(float t) {\n  float tmp = 0.5f * 448.f * g_tweakGui->GetBallViewportYReduction();\n  x0_camera->SetLocalTransform(\n      zeus::CTransform::Translate(x34_camPos + zeus::CVector3f(0.f, 0.f, (t * tmp - tmp) * 0.01f)));\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CHudBallInterface.hpp",
    "content": "#pragma once\n\n#include \"Runtime/rstl.hpp\"\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CGuiCamera;\nclass CGuiFrame;\nclass CGuiModel;\nclass CGuiTextPane;\nclass CGuiWidget;\n\nclass CHudBallInterface {\n  CGuiCamera* x0_camera;\n  CGuiWidget* x4_basewidget_bombstuff;\n  CGuiWidget* x8_basewidget_bombdeco;\n  CGuiModel* xc_model_bombicon;\n  CGuiTextPane* x10_textpane_bombdigits;\n  rstl::reserved_vector<CGuiWidget*, 3> x14_group_bombfilled;\n  rstl::reserved_vector<CGuiWidget*, 3> x24_group_bombempty;\n  zeus::CVector3f x34_camPos;\n  int x40_pbAmount;\n  int x44_pbCapacity;\n  int x48_availableBombs;\n  bool x4c_hasPb;\n  void UpdatePowerBombReadoutColors();\n\npublic:\n  CHudBallInterface(CGuiFrame& selHud, int pbAmount, int pbCapacity, int availableBombs, bool hasBombs, bool hasPb);\n  void SetBombParams(int pbAmount, int pbCapacity, int availableBombs, bool hasBombs, bool hasPb, bool init);\n  void SetBallModeFactor(float t);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CHudBossEnergyInterface.cpp",
    "content": "#include \"Runtime/GuiSys/CHudBossEnergyInterface.hpp\"\n\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/GuiSys/CAuiEnergyBarT01.hpp\"\n#include \"Runtime/GuiSys/CGuiFrame.hpp\"\n#include \"Runtime/GuiSys/CGuiTextPane.hpp\"\n\nnamespace metaforce {\n\nCHudBossEnergyInterface::CHudBossEnergyInterface(CGuiFrame& selHud) {\n  x14_basewidget_bossenergystuff = selHud.FindWidget(\"basewidget_bossenergystuff\");\n  x18_energybart01_bossbar = static_cast<CAuiEnergyBarT01*>(selHud.FindWidget(\"energybart01_bossbar\"));\n  x1c_textpane_boss = static_cast<CGuiTextPane*>(selHud.FindWidget(\"textpane_boss\"));\n\n  x18_energybart01_bossbar->SetCoordFunc(BossEnergyCoordFunc);\n  x18_energybart01_bossbar->SetTesselation(0.2f);\n\n  const auto& [filled, empty, shadow] = g_tweakGuiColors->GetVisorEnergyBarColors(0);\n  x18_energybart01_bossbar->SetFilledColor(filled);\n  x18_energybart01_bossbar->SetShadowColor(shadow);\n  x18_energybart01_bossbar->SetEmptyColor(empty);\n}\n\nvoid CHudBossEnergyInterface::Update(float dt) {\n  if (x10_24_visible)\n    x4_fader = std::min(x4_fader + dt, 1.f);\n  else\n    x4_fader = std::max(0.f, x4_fader - dt);\n\n  if (x4_fader > 0.f) {\n    zeus::CColor color = zeus::skWhite;\n    color.a() = x0_alpha * x4_fader;\n    x14_basewidget_bossenergystuff->SetColor(color);\n    x14_basewidget_bossenergystuff->SetVisibility(true, ETraversalMode::Children);\n  } else {\n    x14_basewidget_bossenergystuff->SetVisibility(false, ETraversalMode::Children);\n  }\n}\n\nvoid CHudBossEnergyInterface::SetBossParams(bool visible, std::u16string_view name, float curEnergy, float maxEnergy) {\n  x10_24_visible = visible;\n  if (visible) {\n    x18_energybart01_bossbar->SetFilledDrainSpeed(maxEnergy);\n    x18_energybart01_bossbar->SetCurrEnergy(curEnergy, CAuiEnergyBarT01::ESetMode::Normal);\n    x18_energybart01_bossbar->SetMaxEnergy(maxEnergy);\n    x1c_textpane_boss->TextSupport().SetText(name);\n  }\n  x8_curEnergy = curEnergy;\n  xc_maxEnergy = maxEnergy;\n}\n\nstd::pair<zeus::CVector3f, zeus::CVector3f> CHudBossEnergyInterface::BossEnergyCoordFunc(float t) {\n  float x = 9.25f * t - 4.625f;\n  return {zeus::CVector3f(x, 0.f, 0.f), zeus::CVector3f(x, 0.f, 0.4f)};\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CHudBossEnergyInterface.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CAuiEnergyBarT01;\nclass CGuiFrame;\nclass CGuiTextPane;\nclass CGuiWidget;\n\nclass CHudBossEnergyInterface {\n  float x0_alpha = 0.f;\n  float x4_fader = 0.f;\n  float x8_curEnergy = 0.f;\n  float xc_maxEnergy = 0.f;\n  bool x10_24_visible : 1 = false;\n  CGuiWidget* x14_basewidget_bossenergystuff;\n  CAuiEnergyBarT01* x18_energybart01_bossbar;\n  CGuiTextPane* x1c_textpane_boss;\n\npublic:\n  explicit CHudBossEnergyInterface(CGuiFrame& selHud);\n  void Update(float dt);\n  void SetAlpha(float a) { x0_alpha = a; }\n  void SetBossParams(bool visible, std::u16string_view name, float curEnergy, float maxEnergy);\n  static std::pair<zeus::CVector3f, zeus::CVector3f> BossEnergyCoordFunc(float t);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CHudDecoInterface.cpp",
    "content": "#include \"Runtime/GuiSys/CHudDecoInterface.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/GuiSys/CAuiEnergyBarT01.hpp\"\n#include \"Runtime/GuiSys/CGuiCamera.hpp\"\n#include \"Runtime/GuiSys/CGuiFrame.hpp\"\n#include \"Runtime/GuiSys/CGuiModel.hpp\"\n#include \"Runtime/GuiSys/CGuiTextPane.hpp\"\n#include \"Runtime/GuiSys/CGuiWidgetDrawParms.hpp\"\n#include \"Runtime/MP1/CSamusHud.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nvoid IHudDecoInterface::SetReticuleTransform(const zeus::CMatrix3f& xf) {}\nvoid IHudDecoInterface::SetDecoRotation(float angle) {}\nvoid IHudDecoInterface::SetFrameColorValue(float v) {}\nvoid IHudDecoInterface::Draw() {}\nvoid IHudDecoInterface::ProcessInput(const CFinalInput& input) {}\nfloat IHudDecoInterface::GetHudTextAlpha() const { return 1.f; }\n\nCHudDecoInterfaceCombat::CHudDecoInterfaceCombat(CGuiFrame& selHud) {\n  x6c_camera = selHud.GetFrameCamera();\n  x2c_camPos = x6c_camera->GetLocalPosition();\n  x70_basewidget_pivot = selHud.FindWidget(\"basewidget_pivot\");\n  x74_basewidget_deco = selHud.FindWidget(\"basewidget_deco\");\n  x78_basewidget_tickdeco0 = selHud.FindWidget(\"basewidget_tickdeco0\");\n  x7c_basewidget_frame = selHud.FindWidget(\"basewidget_frame\");\n  x14_pivotPosition = x70_basewidget_pivot->GetIdlePosition();\n  x78_basewidget_tickdeco0->SetColor(g_tweakGuiColors->GetTickDecoColor());\n  x38_basePosition = x7c_basewidget_frame->GetLocalPosition();\n  x44_baseRotation = x7c_basewidget_frame->GetLocalTransform().buildMatrix3f();\n  CHudDecoInterfaceCombat::UpdateHudAlpha();\n}\n\nvoid CHudDecoInterfaceCombat::UpdateVisibility() {\n  bool vis = x68_24_visDebug && x68_25_visGame;\n  x74_basewidget_deco->SetVisibility(vis, ETraversalMode::Children);\n  x78_basewidget_tickdeco0->SetVisibility(vis, ETraversalMode::Children);\n}\n\nvoid CHudDecoInterfaceCombat::SetIsVisibleDebug(bool v) {\n  x68_24_visDebug = v;\n  UpdateVisibility();\n}\n\nvoid CHudDecoInterfaceCombat::SetIsVisibleGame(bool v) {\n  x68_25_visGame = v;\n  UpdateVisibility();\n}\n\nvoid CHudDecoInterfaceCombat::SetHudRotation(const zeus::CQuaternion& rot) { x4_rotation = rot; }\n\nvoid CHudDecoInterfaceCombat::SetHudOffset(const zeus::CVector3f& off) { x20_offset = off; }\n\nvoid CHudDecoInterfaceCombat::SetDamageTransform(const zeus::CMatrix3f& rotation, const zeus::CVector3f& position) {\n  x7c_basewidget_frame->SetLocalTransform(zeus::CTransform(rotation * x44_baseRotation, position + x38_basePosition));\n}\n\nvoid CHudDecoInterfaceCombat::SetFrameColorValue(float v) {\n  zeus::CColor color = v > 0.f ? zeus::skWhite : g_tweakGuiColors->GetHudFrameColor();\n  x7c_basewidget_frame->SetColor(color);\n}\n\nvoid CHudDecoInterfaceCombat::Update(float dt, const CStateManager& stateMgr) {\n  x6c_camera->SetO2WTransform(\n      MP1::CSamusHud::BuildFinalCameraTransform(x4_rotation, x14_pivotPosition + x20_offset, x2c_camPos));\n}\n\nvoid CHudDecoInterfaceCombat::UpdateCameraDebugSettings(float fov, float y, float z) {\n  x6c_camera->SetFov(fov);\n  x2c_camPos.y() = y;\n  x2c_camPos.z() = z;\n}\n\nvoid CHudDecoInterfaceCombat::UpdateHudAlpha() {\n  zeus::CColor color = zeus::skWhite;\n  color.a() = g_GameState->GameOptions().GetHUDAlpha() / 255.f;\n  x70_basewidget_pivot->SetColor(color);\n}\n\nCHudDecoInterfaceScan::CHudDecoInterfaceScan(CGuiFrame& selHud) : x14_selHud(selHud), x18_scanDisplay(selHud) {\n  x4_scanHudFlat = g_SimplePool->GetObj(\"FRME_ScanHudFlat\");\n  x234_sidesPositioner = g_tweakGui->GetScanSidesPositionStart();\n  x244_camera = selHud.GetFrameCamera();\n  x248_basewidget_pivot = selHud.FindWidget(\"basewidget_pivot\");\n  x24c_basewidget_leftside = selHud.FindWidget(\"basewidget_leftside\");\n  x250_basewidget_rightside = selHud.FindWidget(\"basewidget_rightside\");\n  x1f4_pivotPosition = x248_basewidget_pivot->GetIdlePosition();\n  if (CGuiWidget* deco = selHud.FindWidget(\"basewidget_deco\"))\n    deco->SetColor(g_tweakGuiColors->GetHudFrameColor());\n\n  x218_leftsidePosition = x24c_basewidget_leftside->GetLocalPosition();\n  zeus::CTransform leftXf(zeus::CMatrix3f::RotateZ(g_tweakGui->GetScanSidesAngle()), x218_leftsidePosition);\n  x24c_basewidget_leftside->SetLocalTransform(leftXf);\n  if (CGuiWidget* w = selHud.FindWidget(\"basewidget_databankl\")) {\n    zeus::CTransform xf(zeus::CMatrix3f::RotateZ(g_tweakGui->GetScanSidesAngle() * -1.f), w->GetLocalPosition());\n    w->SetLocalTransform(xf);\n  }\n  if (CGuiWidget* w = selHud.FindWidget(\"basewidget_leftguages\")) {\n    zeus::CTransform xf(zeus::CMatrix3f(zeus::CVector3f{g_tweakGui->GetScanSidesXScale(), 1.f, 1.f}),\n                        w->GetLocalPosition());\n    w->SetLocalTransform(xf);\n  }\n\n  x224_rightsidePosition = x250_basewidget_rightside->GetLocalPosition();\n  zeus::CTransform rightXf(zeus::CMatrix3f::RotateZ(g_tweakGui->GetScanSidesAngle() * -1.f), x224_rightsidePosition);\n  x250_basewidget_rightside->SetLocalTransform(rightXf);\n  if (CGuiWidget* w = selHud.FindWidget(\"basewidget_databankr\")) {\n    zeus::CTransform xf(zeus::CMatrix3f::RotateZ(g_tweakGui->GetScanSidesAngle()), w->GetLocalPosition());\n    w->SetLocalTransform(xf);\n  }\n  if (CGuiWidget* w = selHud.FindWidget(\"basewidget_rightguages\")) {\n    zeus::CTransform xf(zeus::CMatrix3f(zeus::CVector3f{g_tweakGui->GetScanSidesXScale(), 1.f, 1.f}),\n                        w->GetLocalPosition());\n    w->SetLocalTransform(xf);\n  }\n\n  zeus::CVector3f sidesPos(x234_sidesPositioner, 0.f, 0.f);\n  x24c_basewidget_leftside->SetLocalPosition(x24c_basewidget_leftside->RotateO2P(x218_leftsidePosition + sidesPos));\n  x250_basewidget_rightside->SetLocalPosition(x250_basewidget_rightside->RotateO2P(x224_rightsidePosition - sidesPos));\n  x234_sidesPositioner = FLT_MAX;\n\n  CHudDecoInterfaceScan::UpdateHudAlpha();\n}\n\nvoid CHudDecoInterfaceScan::UpdateVisibility() {\n  // Empty\n}\n\nvoid CHudDecoInterfaceScan::SetIsVisibleDebug(bool v) {\n  x240_24_visDebug = v;\n  UpdateVisibility();\n}\n\nvoid CHudDecoInterfaceScan::SetIsVisibleGame(bool v) {\n  x240_25_visGame = v;\n  UpdateVisibility();\n}\n\nvoid CHudDecoInterfaceScan::SetHudRotation(const zeus::CQuaternion& rot) { x1e4_rotation = rot; }\n\nvoid CHudDecoInterfaceScan::SetHudOffset(const zeus::CVector3f& off) { x200_offset = off; }\n\nvoid CHudDecoInterfaceScan::SetReticuleTransform(const zeus::CMatrix3f& xf) {\n  // Empty\n}\n\nvoid CHudDecoInterfaceScan::SetDamageTransform(const zeus::CMatrix3f& rotation, const zeus::CVector3f& position) {\n  // Empty\n}\n\nvoid CHudDecoInterfaceScan::SetFrameColorValue(float v) {\n  // Empty\n}\n\nvoid CHudDecoInterfaceScan::InitializeFlatFrame() {\n  x10_loadedScanHudFlat = x4_scanHudFlat.GetObj();\n  x10_loadedScanHudFlat->SetMaxAspect(1.33f);\n  x10_loadedScanHudFlat->GetFrameCamera()->SetO2WTransform(zeus::CTransform::Translate(x20c_camPos));\n  x258_flat_basewidget_scanguage = x10_loadedScanHudFlat->FindWidget(\"basewidget_scanguage\");\n  x258_flat_basewidget_scanguage->SetVisibility(false, ETraversalMode::Children);\n  x254_flat_textpane_scanning = static_cast<CGuiTextPane*>(x10_loadedScanHudFlat->FindWidget(\"textpane_scanning\"));\n  x25c_flat_energybart01_scanbar =\n      static_cast<CAuiEnergyBarT01*>(x10_loadedScanHudFlat->FindWidget(\"energybart01_scanbar\"));\n  x264_flat_textpane_message = static_cast<CGuiTextPane*>(x10_loadedScanHudFlat->FindWidget(\"textpane_message\"));\n  x268_flat_textpane_scrollmessage =\n      static_cast<CGuiTextPane*>(x10_loadedScanHudFlat->FindWidget(\"textpane_scrollmessage\"));\n  x260_flat_basewidget_textgroup = x10_loadedScanHudFlat->FindWidget(\"basewidget_textgroup\");\n  x26c_flat_model_xmark = static_cast<CGuiModel*>(x10_loadedScanHudFlat->FindWidget(\"model_xmark\"));\n  x270_flat_model_abutton = static_cast<CGuiModel*>(x10_loadedScanHudFlat->FindWidget(\"model_abutton\"));\n  x274_flat_model_dash = static_cast<CGuiModel*>(x10_loadedScanHudFlat->FindWidget(\"model_dash\"));\n  x260_flat_basewidget_textgroup->SetVisibility(false, ETraversalMode::Children);\n  x254_flat_textpane_scanning->SetIsVisible(false);\n  x254_flat_textpane_scanning->TextSupport().SetFontColor(g_tweakGuiColors->GetHudMessageFill());\n  x254_flat_textpane_scanning->TextSupport().SetOutlineColor(g_tweakGuiColors->GetHudMessageOutline());\n  x25c_flat_energybart01_scanbar->SetCoordFunc(CAuiEnergyBarT01::DownloadBarCoordFunc);\n  x25c_flat_energybart01_scanbar->ResetMaxEnergy();\n  x25c_flat_energybart01_scanbar->SetFilledColor(zeus::CColor(0.4f, 0.68f, 0.88f, 1.f));\n  x25c_flat_energybart01_scanbar->SetShadowColor(zeus::skClear);\n  x25c_flat_energybart01_scanbar->SetEmptyColor(zeus::skClear);\n  x25c_flat_energybart01_scanbar->SetFilledDrainSpeed(999.f);\n  x25c_flat_energybart01_scanbar->SetShadowDrainSpeed(999.f);\n  x25c_flat_energybart01_scanbar->SetShadowDrainDelay(0.f);\n  x25c_flat_energybart01_scanbar->SetIsAlwaysResetTimer(false);\n  x26c_flat_model_xmark->SetVisibility(false, ETraversalMode::Children);\n  x26c_flat_model_xmark->SetVisibility(false, ETraversalMode::Children);\n  x270_flat_model_abutton->SetVisibility(false, ETraversalMode::Children);\n  x274_flat_model_dash->SetVisibility(false, ETraversalMode::Children);\n}\n\nconst CScannableObjectInfo* CHudDecoInterfaceScan::GetCurrScanInfo(const CStateManager& stateMgr) const {\n  if (x1d4_latestScanState == CPlayer::EPlayerScanState::NotScanning)\n    return nullptr;\n\n  if (TCastToConstPtr<CActor> act = stateMgr.GetObjectById(x1d2_latestScanningObject))\n    return act->GetScannableObjectInfo();\n\n  return nullptr;\n}\n\nvoid CHudDecoInterfaceScan::UpdateScanDisplay(const CStateManager& stateMgr, float dt) {\n  CPlayer& player = stateMgr.GetPlayer();\n  CPlayer::EPlayerScanState scanState = player.GetScanningState();\n  if (scanState != x1d4_latestScanState) {\n    if (player.IsNewScanScanning()) {\n      if (scanState == CPlayer::EPlayerScanState::ScanComplete) {\n        if (x1d4_latestScanState == CPlayer::EPlayerScanState::Scanning) {\n          // Scan complete\n          x254_flat_textpane_scanning->TextSupport().SetText(g_MainStringTable->GetString(15));\n          x254_flat_textpane_scanning->TextSupport().SetTypeWriteEffectOptions(false, 0.f, 40.f);\n          x238_scanningTextAlpha = 2.f;\n        }\n      } else if (scanState == CPlayer::EPlayerScanState::Scanning) {\n        // Scanning\n        x254_flat_textpane_scanning->TextSupport().SetText(g_MainStringTable->GetString(14));\n        x254_flat_textpane_scanning->TextSupport().SetTypeWriteEffectOptions(false, 0.f, 40.f);\n        x238_scanningTextAlpha = 2.f;\n      }\n    }\n    x1d4_latestScanState = scanState;\n  }\n\n  if (player.GetScanningObjectId() != x1d2_latestScanningObject)\n    x1d2_latestScanningObject = player.GetScanningObjectId();\n\n  if (player.GetOrbitTargetId() != x1d0_latestHudPoi) {\n    x1d0_latestHudPoi = player.GetOrbitTargetId();\n    if (x1d0_latestHudPoi != kInvalidUniqueId) {\n      if (!player.ObjectInScanningRange(x1d0_latestHudPoi, stateMgr)) {\n        // Object out of scanning range\n        x254_flat_textpane_scanning->TextSupport().SetText(g_MainStringTable->GetString(16));\n        x254_flat_textpane_scanning->TextSupport().SetTypeWriteEffectOptions(true, 0.f, 40.f);\n        x238_scanningTextAlpha = 1.f;\n      }\n    }\n  }\n\n  const CScannableObjectInfo* scanInfo = GetCurrScanInfo(stateMgr);\n  if (x1d2_latestScanningObject != x18_scanDisplay.x10_objId || !scanInfo) {\n    x18_scanDisplay.StopScan();\n    if (x18_scanDisplay.xc_state == CScanDisplay::EScanState::Inactive && scanInfo) {\n      x18_scanDisplay.StartScan(x1d2_latestScanningObject, *scanInfo, x264_flat_textpane_message,\n                                x268_flat_textpane_scrollmessage, x260_flat_basewidget_textgroup, x26c_flat_model_xmark,\n                                x270_flat_model_abutton, x274_flat_model_dash, player.GetScanningTime());\n    }\n  }\n\n  x18_scanDisplay.Update(dt, player.GetScanningTime());\n\n  if (x1d2_latestScanningObject != kInvalidUniqueId && GetCurrScanInfo(stateMgr))\n    if (TCastToConstPtr<CActor> act = stateMgr.GetObjectById(x1d2_latestScanningObject))\n      if (const CScannableObjectInfo* actScan = act->GetScannableObjectInfo())\n        x25c_flat_energybart01_scanbar->SetCurrEnergy(x1d8_scanningTime / actScan->GetTotalDownloadTime(),\n                                                      CAuiEnergyBarT01::ESetMode::Normal);\n\n  if (x1d4_latestScanState != CPlayer::EPlayerScanState::Scanning)\n    if (x1d0_latestHudPoi == kInvalidUniqueId || player.ObjectInScanningRange(x1d0_latestHudPoi, stateMgr))\n      x238_scanningTextAlpha = std::max(0.f, x238_scanningTextAlpha - dt);\n\n  if (x238_scanningTextAlpha > 0.f) {\n    zeus::CColor color = zeus::skWhite;\n    color.a() = std::min(x238_scanningTextAlpha, 1.f);\n    x254_flat_textpane_scanning->SetColor(color);\n    x254_flat_textpane_scanning->SetIsVisible(true);\n  } else {\n    x254_flat_textpane_scanning->SetIsVisible(false);\n  }\n\n  if (GetCurrScanInfo(stateMgr))\n    x23c_scanBarAlpha = std::min(x23c_scanBarAlpha + 2.f * dt, 1.f);\n  else\n    x23c_scanBarAlpha = std::max(0.f, x23c_scanBarAlpha - 2.f * dt);\n\n  if (x23c_scanBarAlpha > 0.f) {\n    zeus::CColor color = zeus::skWhite;\n    color.a() = std::min(x23c_scanBarAlpha, 1.f);\n    x258_flat_basewidget_scanguage->SetColor(color);\n    x258_flat_basewidget_scanguage->SetVisibility(true, ETraversalMode::Children);\n  } else {\n    x258_flat_basewidget_scanguage->SetVisibility(false, ETraversalMode::Children);\n  }\n}\n\nvoid CHudDecoInterfaceScan::Update(float dt, const CStateManager& stateMgr) {\n  CPlayer& player = stateMgr.GetPlayer();\n  CPlayer::EPlayerScanState scanState = player.GetScanningState();\n  if (scanState != CPlayer::EPlayerScanState::NotScanning)\n    x1d8_scanningTime = player.GetScanningTime();\n\n  if (scanState == CPlayer::EPlayerScanState::Scanning || scanState == CPlayer::EPlayerScanState::ScanComplete)\n    x230_sidesTimer = std::min(x230_sidesTimer + dt, g_tweakGui->GetScanSidesEndTime());\n  else\n    x230_sidesTimer = std::max(0.f, x230_sidesTimer - dt);\n\n  float sidesT = x230_sidesTimer < g_tweakGui->GetScanSidesStartTime()\n                     ? 0.f\n                     : (x230_sidesTimer - g_tweakGui->GetScanSidesStartTime()) / g_tweakGui->GetScanSidesDuration();\n  float oldSidesPositioner = x234_sidesPositioner;\n  x234_sidesPositioner =\n      (1.f - sidesT) * g_tweakGui->GetScanSidesPositionStart() + sidesT * g_tweakGui->GetScanSidesPositionEnd();\n  if (oldSidesPositioner != x234_sidesPositioner) {\n    zeus::CVector3f sidesPos(x234_sidesPositioner, 0.f, 0.f);\n    x24c_basewidget_leftside->SetLocalPosition(x218_leftsidePosition + x24c_basewidget_leftside->RotateO2P(sidesPos));\n    x250_basewidget_rightside->SetLocalPosition(x224_rightsidePosition -\n                                                x250_basewidget_rightside->RotateO2P(sidesPos));\n  }\n\n  x244_camera->SetO2WTransform(\n      MP1::CSamusHud::BuildFinalCameraTransform(x1e4_rotation, x1f4_pivotPosition + x200_offset, x20c_camPos));\n\n  if (!x10_loadedScanHudFlat) {\n    if (!x4_scanHudFlat.IsLoaded() || !x4_scanHudFlat->GetIsFinishedLoading())\n      return;\n    InitializeFlatFrame();\n  }\n\n  x10_loadedScanHudFlat->Update(dt);\n  UpdateScanDisplay(stateMgr, dt);\n}\n\nvoid CHudDecoInterfaceScan::Draw() {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CHudDecoInterfaceScan::Draw\", zeus::skGreen);\n  x18_scanDisplay.Draw();\n  if (x10_loadedScanHudFlat != nullptr) {\n    x10_loadedScanHudFlat->Draw(CGuiWidgetDrawParms::Default());\n  }\n}\n\nvoid CHudDecoInterfaceScan::ProcessInput(const CFinalInput& input) { x18_scanDisplay.ProcessInput(input); }\n\nvoid CHudDecoInterfaceScan::UpdateCameraDebugSettings(float fov, float y, float z) {\n  x244_camera->SetFov(fov);\n  x20c_camPos.y() = y;\n  x20c_camPos.z() = z;\n}\n\nvoid CHudDecoInterfaceScan::UpdateHudAlpha() {\n  zeus::CColor color = zeus::skWhite;\n  color.a() = g_GameState->GameOptions().GetHUDAlpha() / 255.f;\n  x248_basewidget_pivot->SetColor(color);\n}\n\nfloat CHudDecoInterfaceScan::GetHudTextAlpha() const {\n  return 1.f - std::max(std::min(x238_scanningTextAlpha, 1.f), x18_scanDisplay.x1a8_bodyAlpha);\n}\n\nCHudDecoInterfaceXRay::CHudDecoInterfaceXRay(CGuiFrame& selHud) {\n  xa0_camera = selHud.GetFrameCamera();\n  x30_camPos = xa0_camera->GetLocalPosition();\n\n  xa4_basewidget_pivot = selHud.FindWidget(\"basewidget_pivot\");\n  xa8_basewidget_seeker = selHud.FindWidget(\"basewidget_seeker\");\n  xac_basewidget_rotate = selHud.FindWidget(\"basewidget_rotate\");\n\n  if (CGuiWidget* w = selHud.FindWidget(\"basewidget_energydeco\"))\n    w->SetColor(g_tweakGuiColors->GetXRayEnergyDecoColor());\n\n  if (CGuiWidget* w = selHud.FindWidget(\"model_frame\"))\n    w->SetDepthWrite(true);\n  if (CGuiWidget* w = selHud.FindWidget(\"model_frame1\"))\n    w->SetDepthWrite(true);\n  if (CGuiWidget* w = selHud.FindWidget(\"model_frame2\"))\n    w->SetDepthWrite(true);\n  if (CGuiWidget* w = selHud.FindWidget(\"model_frame3\"))\n    w->SetDepthWrite(true);\n  if (CGuiWidget* w = selHud.FindWidget(\"model_misslieslider\"))\n    w->SetDepthWrite(true);\n  if (CGuiWidget* w = selHud.FindWidget(\"model_threatslider\"))\n    w->SetDepthWrite(true);\n\n  CHudDecoInterfaceXRay::UpdateHudAlpha();\n}\n\nvoid CHudDecoInterfaceXRay::UpdateVisibility() {\n  // Empty\n}\n\nvoid CHudDecoInterfaceXRay::SetIsVisibleDebug(bool v) {\n  x9c_24_visDebug = v;\n  UpdateVisibility();\n}\n\nvoid CHudDecoInterfaceXRay::SetIsVisibleGame(bool v) {\n  x9c_25_visGame = v;\n  UpdateVisibility();\n}\n\nvoid CHudDecoInterfaceXRay::SetHudRotation(const zeus::CQuaternion& rot) { x8_rotation = rot; }\n\nvoid CHudDecoInterfaceXRay::SetHudOffset(const zeus::CVector3f& off) { x24_offset = off; }\n\nvoid CHudDecoInterfaceXRay::SetReticuleTransform(const zeus::CMatrix3f& xf) { x3c_reticuleXf = xf; }\n\nvoid CHudDecoInterfaceXRay::SetDecoRotation(float angle) {\n  xac_basewidget_rotate->SetLocalTransform(\n      zeus::CTransform(zeus::CMatrix3f::RotateY(angle), xac_basewidget_rotate->GetLocalPosition()));\n}\n\nvoid CHudDecoInterfaceXRay::SetDamageTransform(const zeus::CMatrix3f& rotation, const zeus::CVector3f& position) {\n  // Empty\n}\n\nvoid CHudDecoInterfaceXRay::SetFrameColorValue(float v) {\n  // Empty\n}\n\nvoid CHudDecoInterfaceXRay::Update(float dt, const CStateManager& stateMgr) {\n  if (stateMgr.GetPlayer().GetOrbitState() == CPlayer::EPlayerOrbitState::OrbitObject)\n    x4_seekerScale = std::max(x4_seekerScale - 3.f * dt, 0.35f);\n  else\n    x4_seekerScale = std::min(3.f * dt + x4_seekerScale, 1.f);\n\n  xa0_camera->SetO2WTransform(\n      MP1::CSamusHud::BuildFinalCameraTransform(x8_rotation, x18_pivotPosition + x24_offset, x30_camPos));\n\n  xa8_basewidget_seeker->SetLocalTransform(\n      zeus::CTransform(zeus::CMatrix3f(x4_seekerScale) * x3c_reticuleXf, x60_seekerPosition));\n}\n\nvoid CHudDecoInterfaceXRay::UpdateCameraDebugSettings(float fov, float y, float z) {\n  xa0_camera->SetFov(fov);\n  x30_camPos.y() = y;\n  x30_camPos.z() = z;\n}\n\nvoid CHudDecoInterfaceXRay::UpdateHudAlpha() {\n  zeus::CColor color = zeus::skWhite;\n  color.a() = g_GameState->GameOptions().GetHUDAlpha() / 255.f;\n  xa4_basewidget_pivot->SetColor(color);\n}\n\nCHudDecoInterfaceThermal::CHudDecoInterfaceThermal(CGuiFrame& selHud) {\n  x74_camera = selHud.GetFrameCamera();\n  x2c_camPos = x74_camera->GetLocalPosition();\n\n  x78_basewidget_pivot = selHud.FindWidget(\"basewidget_pivot\");\n  x7c_basewidget_reticle = selHud.FindWidget(\"basewidget_reticle\");\n  x80_model_retflash = static_cast<CGuiModel*>(selHud.FindWidget(\"model_retflash\"));\n\n  x14_pivotPosition = x78_basewidget_pivot->GetIdlePosition();\n  x5c_reticulePosition = x7c_basewidget_reticle->GetIdlePosition();\n\n  if (CGuiWidget* w = selHud.FindWidget(\"basewidget_deco\"))\n    w->SetColor(g_tweakGuiColors->GetThermalDecoColor());\n\n  if (CGuiWidget* w = selHud.FindWidget(\"basewidget_oultlinesa\"))\n    w->SetColor(g_tweakGuiColors->GetThermalOutlinesColor());\n\n  if (CGuiWidget* w = selHud.FindWidget(\"basewidget_lock\"))\n    w->SetColor(g_tweakGuiColors->GetThermalLockColor());\n\n  if (CGuiWidget* w = selHud.FindWidget(\"basewidget_reticle\"))\n    w->SetColor(g_tweakGuiColors->GetThermalOutlinesColor());\n\n  if (CGuiWidget* w = selHud.FindWidget(\"basewidget_lockon\"))\n    w->SetColor(g_tweakGuiColors->GetThermalOutlinesColor());\n\n  if (CGuiWidget* w = selHud.FindWidget(\"model_threaticon\"))\n    w->SetColor(g_tweakGuiColors->GetThermalOutlinesColor());\n\n  if (CGuiWidget* w = selHud.FindWidget(\"model_missileicon\"))\n    w->SetColor(g_tweakGuiColors->GetThermalOutlinesColor());\n\n  if (CGuiWidget* w = selHud.FindWidget(\"basewidget_lock\")) {\n    for (CGuiWidget* c = static_cast<CGuiWidget*>(w->GetChildObject()); c;\n         c = static_cast<CGuiWidget*>(c->GetNextSibling())) {\n      x84_lockonWidgets.emplace_back(c, c->GetLocalTransform());\n      c->SetLocalTransform(c->GetLocalTransform() * zeus::CTransform::Scale(x68_lockonScale));\n    }\n  }\n\n  x14_pivotPosition = x78_basewidget_pivot->GetIdlePosition();\n  CHudDecoInterfaceThermal::UpdateHudAlpha();\n}\n\nvoid CHudDecoInterfaceThermal::UpdateVisibility() {\n  // Empty\n}\n\nvoid CHudDecoInterfaceThermal::SetIsVisibleDebug(bool v) {\n  x70_24_visDebug = v;\n  UpdateVisibility();\n}\n\nvoid CHudDecoInterfaceThermal::SetIsVisibleGame(bool v) {\n  x70_25_visGame = v;\n  UpdateVisibility();\n}\n\nvoid CHudDecoInterfaceThermal::SetHudRotation(const zeus::CQuaternion& rot) { x4_rotation = rot; }\n\nvoid CHudDecoInterfaceThermal::SetHudOffset(const zeus::CVector3f& off) { x20_offset = off; }\n\nvoid CHudDecoInterfaceThermal::SetReticuleTransform(const zeus::CMatrix3f& xf) { x38_reticuleXf = xf; }\n\nvoid CHudDecoInterfaceThermal::SetDamageTransform(const zeus::CMatrix3f& rotation, const zeus::CVector3f& position) {\n  // Empty\n}\n\nvoid CHudDecoInterfaceThermal::Update(float dt, const CStateManager& stateMgr) {\n  float oldLockonScale = x68_lockonScale;\n  if (stateMgr.GetPlayer().GetOrbitTargetId() != kInvalidUniqueId)\n    x68_lockonScale = std::max(x68_lockonScale - 15.f * dt, 1.f);\n  else\n    x68_lockonScale = std::min(x68_lockonScale + 15.f * dt, 5.f);\n\n  if (oldLockonScale != x68_lockonScale)\n    for (auto& lockWidget : x84_lockonWidgets)\n      lockWidget.first->SetLocalTransform(lockWidget.second * zeus::CTransform::Scale(x68_lockonScale));\n\n  x6c_retflashTimer += dt;\n  if (x6c_retflashTimer > 1.f)\n    x6c_retflashTimer -= 2.f;\n\n  zeus::CColor flashColor = zeus::skWhite;\n  flashColor.a() = std::fabs(x6c_retflashTimer) * 0.5f + 0.5f;\n  x80_model_retflash->SetColor(flashColor);\n\n  x74_camera->SetO2WTransform(\n      MP1::CSamusHud::BuildFinalCameraTransform(x4_rotation, x14_pivotPosition + x20_offset, x2c_camPos));\n\n  x7c_basewidget_reticle->SetLocalTransform(zeus::CTransform(x38_reticuleXf, x5c_reticulePosition));\n}\n\nvoid CHudDecoInterfaceThermal::UpdateCameraDebugSettings(float fov, float y, float z) {\n  x74_camera->SetFov(fov);\n  x2c_camPos.y() = y;\n  x2c_camPos.z() = z;\n}\n\nvoid CHudDecoInterfaceThermal::UpdateHudAlpha() {\n  zeus::CColor color = zeus::skWhite;\n  color.a() = g_GameState->GameOptions().GetHUDAlpha() / 255.f;\n  x78_basewidget_pivot->SetColor(color);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CHudDecoInterface.hpp",
    "content": "#pragma once\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/GuiSys/CScanDisplay.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n\n#include <zeus/CMatrix3f.hpp>\n#include <zeus/CQuaternion.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CGuiFrame;\nstruct CFinalInput;\nclass CStateManager;\nclass CGuiCamera;\nclass CGuiWidget;\nclass CAuiEnergyBarT01;\nclass CGuiModel;\n\nclass IHudDecoInterface {\npublic:\n  virtual void SetIsVisibleDebug(bool v) = 0;\n  virtual void SetIsVisibleGame(bool v) = 0;\n  virtual void SetHudRotation(const zeus::CQuaternion& rot) = 0;\n  virtual void SetHudOffset(const zeus::CVector3f& off) = 0;\n  virtual void SetReticuleTransform(const zeus::CMatrix3f& xf);\n  virtual void SetDecoRotation(float angle);\n  virtual void SetDamageTransform(const zeus::CMatrix3f& rotation, const zeus::CVector3f& position) = 0;\n  virtual void SetFrameColorValue(float v);\n  virtual void Update(float dt, const CStateManager& stateMgr) = 0;\n  virtual void Draw();\n  virtual void ProcessInput(const CFinalInput& input);\n  virtual void UpdateCameraDebugSettings(float fov, float y, float z) = 0;\n  virtual void UpdateHudAlpha() = 0;\n  virtual float GetHudTextAlpha() const;\n  virtual ~IHudDecoInterface() = default;\n};\n\nclass CHudDecoInterfaceCombat : public IHudDecoInterface {\n  zeus::CQuaternion x4_rotation;\n  zeus::CVector3f x14_pivotPosition;\n  zeus::CVector3f x20_offset;\n  zeus::CVector3f x2c_camPos;\n  zeus::CVector3f x38_basePosition;\n  zeus::CMatrix3f x44_baseRotation;\n  bool x68_24_visDebug : 1 = true;\n  bool x68_25_visGame : 1 = true;\n  CGuiCamera* x6c_camera;\n  CGuiWidget* x70_basewidget_pivot;\n  CGuiWidget* x74_basewidget_deco;\n  CGuiWidget* x78_basewidget_tickdeco0;\n  CGuiWidget* x7c_basewidget_frame;\n  void UpdateVisibility();\n\npublic:\n  explicit CHudDecoInterfaceCombat(CGuiFrame& selHud);\n  void SetIsVisibleDebug(bool v) override;\n  void SetIsVisibleGame(bool v) override;\n  void SetHudRotation(const zeus::CQuaternion& rot) override;\n  void SetHudOffset(const zeus::CVector3f& off) override;\n  void SetDamageTransform(const zeus::CMatrix3f& rotation, const zeus::CVector3f& position) override;\n  void SetFrameColorValue(float v) override;\n  void Update(float dt, const CStateManager& stateMgr) override;\n  void UpdateCameraDebugSettings(float fov, float y, float z) override;\n  void UpdateHudAlpha() override;\n};\n\nclass CHudDecoInterfaceScan : public IHudDecoInterface {\n  TLockedToken<CGuiFrame> x4_scanHudFlat;\n  CGuiFrame* x10_loadedScanHudFlat = nullptr;\n  CGuiFrame& x14_selHud;\n  CScanDisplay x18_scanDisplay;\n  TUniqueId x1d0_latestHudPoi = kInvalidUniqueId;\n  TUniqueId x1d2_latestScanningObject = kInvalidUniqueId;\n  CPlayer::EPlayerScanState x1d4_latestScanState = CPlayer::EPlayerScanState::NotScanning;\n  float x1d8_scanningTime = 0.f;\n  float x1dc_ = 0.f;\n  float x1e0_ = 1.f;\n  zeus::CQuaternion x1e4_rotation;\n  zeus::CVector3f x1f4_pivotPosition;\n  zeus::CVector3f x200_offset;\n  zeus::CVector3f x20c_camPos;\n  zeus::CVector3f x218_leftsidePosition;\n  zeus::CVector3f x224_rightsidePosition;\n  float x230_sidesTimer = 0.f;\n  float x234_sidesPositioner;\n  float x238_scanningTextAlpha = 0.f;\n  float x23c_scanBarAlpha = 0.f;\n  bool x240_24_visDebug : 1 = true;\n  bool x240_25_visGame : 1 = true;\n  CGuiCamera* x244_camera;\n  CGuiWidget* x248_basewidget_pivot;\n  CGuiWidget* x24c_basewidget_leftside;\n  CGuiWidget* x250_basewidget_rightside;\n  CGuiTextPane* x254_flat_textpane_scanning;\n  CGuiWidget* x258_flat_basewidget_scanguage;\n  CAuiEnergyBarT01* x25c_flat_energybart01_scanbar;\n  CGuiWidget* x260_flat_basewidget_textgroup;\n  CGuiTextPane* x264_flat_textpane_message;\n  CGuiTextPane* x268_flat_textpane_scrollmessage;\n  CGuiModel* x26c_flat_model_xmark;\n  CGuiModel* x270_flat_model_abutton;\n  CGuiModel* x274_flat_model_dash;\n  void UpdateVisibility();\n\npublic:\n  explicit CHudDecoInterfaceScan(CGuiFrame& selHud);\n  void SetIsVisibleDebug(bool v) override;\n  void SetIsVisibleGame(bool v) override;\n  void SetHudRotation(const zeus::CQuaternion& rot) override;\n  void SetHudOffset(const zeus::CVector3f& off) override;\n  void SetReticuleTransform(const zeus::CMatrix3f& xf) override;\n  void SetDamageTransform(const zeus::CMatrix3f& rotation, const zeus::CVector3f& position) override;\n  void SetFrameColorValue(float v) override;\n  void InitializeFlatFrame();\n  const CScannableObjectInfo* GetCurrScanInfo(const CStateManager& stateMgr) const;\n  void UpdateScanDisplay(const CStateManager& stateMgr, float dt);\n  void Update(float dt, const CStateManager& stateMgr) override;\n  void Draw() override;\n  void ProcessInput(const CFinalInput& input) override;\n  void UpdateCameraDebugSettings(float fov, float y, float z) override;\n  void UpdateHudAlpha() override;\n  float GetHudTextAlpha() const override;\n};\n\nclass CHudDecoInterfaceXRay : public IHudDecoInterface {\n  float x4_seekerScale = 1.f;\n  zeus::CQuaternion x8_rotation;\n  zeus::CVector3f x18_pivotPosition;\n  zeus::CVector3f x24_offset;\n  zeus::CVector3f x30_camPos;\n  zeus::CMatrix3f x3c_reticuleXf;\n  zeus::CVector3f x60_seekerPosition;\n  zeus::CVector3f x6c_;\n  zeus::CMatrix3f x78_;\n  bool x9c_24_visDebug : 1 = true;\n  bool x9c_25_visGame : 1 = true;\n  CGuiCamera* xa0_camera;\n  CGuiWidget* xa4_basewidget_pivot;\n  CGuiWidget* xa8_basewidget_seeker;\n  CGuiWidget* xac_basewidget_rotate;\n  void UpdateVisibility();\n\npublic:\n  explicit CHudDecoInterfaceXRay(CGuiFrame& selHud);\n  void SetIsVisibleDebug(bool v) override;\n  void SetIsVisibleGame(bool v) override;\n  void SetHudRotation(const zeus::CQuaternion& rot) override;\n  void SetHudOffset(const zeus::CVector3f& off) override;\n  void SetReticuleTransform(const zeus::CMatrix3f& xf) override;\n  void SetDecoRotation(float angle) override;\n  void SetDamageTransform(const zeus::CMatrix3f& rotation, const zeus::CVector3f& position) override;\n  void SetFrameColorValue(float v) override;\n  void Update(float dt, const CStateManager& stateMgr) override;\n  void UpdateCameraDebugSettings(float fov, float y, float z) override;\n  void UpdateHudAlpha() override;\n};\n\nclass CHudDecoInterfaceThermal : public IHudDecoInterface {\n  zeus::CQuaternion x4_rotation;\n  zeus::CVector3f x14_pivotPosition;\n  zeus::CVector3f x20_offset;\n  zeus::CVector3f x2c_camPos;\n  zeus::CMatrix3f x38_reticuleXf;\n  zeus::CVector3f x5c_reticulePosition;\n  float x68_lockonScale = 5.f;\n  float x6c_retflashTimer = 0.f;\n  bool x70_24_visDebug : 1 = true;\n  bool x70_25_visGame : 1 = true;\n  CGuiCamera* x74_camera;\n  CGuiWidget* x78_basewidget_pivot;\n  CGuiWidget* x7c_basewidget_reticle;\n  CGuiModel* x80_model_retflash;\n  std::vector<std::pair<CGuiWidget*, zeus::CTransform>> x84_lockonWidgets;\n  void UpdateVisibility();\n\npublic:\n  explicit CHudDecoInterfaceThermal(CGuiFrame& selHud);\n  void SetIsVisibleDebug(bool v) override;\n  void SetIsVisibleGame(bool v) override;\n  void SetHudRotation(const zeus::CQuaternion& rot) override;\n  void SetHudOffset(const zeus::CVector3f& off) override;\n  void SetReticuleTransform(const zeus::CMatrix3f& xf) override;\n  void SetDamageTransform(const zeus::CMatrix3f& rotation, const zeus::CVector3f& position) override;\n  void Update(float dt, const CStateManager& stateMgr) override;\n  void UpdateCameraDebugSettings(float fov, float y, float z) override;\n  void UpdateHudAlpha() override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CHudEnergyInterface.cpp",
    "content": "#include \"Runtime/GuiSys/CHudEnergyInterface.hpp\"\n\n#include <array>\n\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Audio/CSfxManager.hpp\"\n#include \"Runtime/GuiSys/CAuiEnergyBarT01.hpp\"\n#include \"Runtime/GuiSys/CAuiMeter.hpp\"\n#include \"Runtime/GuiSys/CGuiFrame.hpp\"\n#include \"Runtime/GuiSys/CGuiTextPane.hpp\"\n#include \"Runtime/GuiSys/CStringTable.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\nnamespace metaforce {\n\nconstexpr std::array<CAuiEnergyBarT01::FCoordFunc, 5> CoordFuncs{\n    CHudEnergyInterface::CombatEnergyCoordFunc, CHudEnergyInterface::CombatEnergyCoordFunc,\n    CHudEnergyInterface::XRayEnergyCoordFunc,   CHudEnergyInterface::ThermalEnergyCoordFunc,\n    CHudEnergyInterface::BallEnergyCoordFunc,\n};\n\nconstexpr std::array Tesselations{\n    0.2f, 0.2f, 0.1f, 0.2f, 1.f,\n};\n\nCHudEnergyInterface::CHudEnergyInterface(CGuiFrame& selHud, float tankEnergy, int totalEnergyTanks, int numTanksFilled,\n                                         bool energyLow, EHudType hudType)\n: x0_hudType(hudType)\n, xc_tankEnergy(tankEnergy)\n, x10_totalEnergyTanks(totalEnergyTanks)\n, x14_numTanksFilled(numTanksFilled)\n, x1c_27_energyLow(energyLow) {\n  x20_textpane_energydigits = static_cast<CGuiTextPane*>(selHud.FindWidget(\"textpane_energydigits\"));\n  x24_meter_energytanks = static_cast<CAuiMeter*>(selHud.FindWidget(\"meter_energytanks\"));\n  x28_textpane_energywarning = static_cast<CGuiTextPane*>(selHud.FindWidget(\"textpane_energywarning\"));\n  x2c_energybart01_energybar = static_cast<CAuiEnergyBarT01*>(selHud.FindWidget(\"energybart01_energybar\"));\n\n  x2c_energybart01_energybar->SetCoordFunc(CoordFuncs[size_t(hudType)]);\n  x2c_energybart01_energybar->SetTesselation(Tesselations[size_t(hudType)]);\n\n  ITweakGuiColors::SVisorEnergyBarColors barColors = g_tweakGuiColors->GetVisorEnergyBarColors(int(hudType));\n  ITweakGuiColors::SVisorEnergyInitColors initColors = g_tweakGuiColors->GetVisorEnergyInitColors(int(hudType));\n\n  x20_textpane_energydigits->TextSupport().SetFontColor(initColors.digitsFont);\n  x20_textpane_energydigits->TextSupport().SetOutlineColor(initColors.digitsOutline);\n\n  x2c_energybart01_energybar->SetMaxEnergy(CPlayerState::GetBaseHealthCapacity());\n  x2c_energybart01_energybar->SetFilledColor(barColors.filled);\n  x2c_energybart01_energybar->SetShadowColor(barColors.shadow);\n  x2c_energybart01_energybar->SetEmptyColor(barColors.empty);\n  x2c_energybart01_energybar->SetFilledDrainSpeed(g_tweakGui->GetEnergyBarFilledSpeed());\n  x2c_energybart01_energybar->SetShadowDrainSpeed(g_tweakGui->GetEnergyBarShadowSpeed());\n  x2c_energybart01_energybar->SetShadowDrainDelay(g_tweakGui->GetEnergyBarDrainDelay());\n  x2c_energybart01_energybar->SetIsAlwaysResetTimer(g_tweakGui->GetEnergyBarAlwaysResetDelay());\n\n  x24_meter_energytanks->SetMaxCapacity(14);\n\n  if (x28_textpane_energywarning) {\n    x28_textpane_energywarning->TextSupport().SetFontColor(g_tweakGuiColors->GetEnergyWarningFont());\n    x28_textpane_energywarning->TextSupport().SetOutlineColor(g_tweakGuiColors->GetEnergyWarningOutline());\n    if (x1c_27_energyLow)\n      x28_textpane_energywarning->TextSupport().SetText(g_MainStringTable->GetString(9));\n    else\n      x28_textpane_energywarning->TextSupport().SetText(u\"\");\n  }\n\n  for (int i = 0; i < 14; ++i) {\n    CGuiGroup* g = static_cast<CGuiGroup*>(x24_meter_energytanks->GetWorkerWidget(i));\n    if (CGuiWidget* w = g->GetWorkerWidget(0))\n      w->SetColor(initColors.tankFilled);\n    if (CGuiWidget* w = g->GetWorkerWidget(1))\n      w->SetColor(initColors.tankEmpty);\n  }\n}\n\nvoid CHudEnergyInterface::Update(float dt, float energyLowPulse) {\n  if (x28_textpane_energywarning) {\n    if (x1c_27_energyLow) {\n      x4_energyLowFader = std::min(x4_energyLowFader + 2.f * dt, 1.f);\n      zeus::CColor color = zeus::skWhite;\n      color.a() = x4_energyLowFader * energyLowPulse;\n      x28_textpane_energywarning->SetColor(color);\n    } else {\n      x4_energyLowFader = std::max(0.f, x4_energyLowFader - 2.f * dt);\n      zeus::CColor color = zeus::skWhite;\n      color.a() = x4_energyLowFader * energyLowPulse;\n      x28_textpane_energywarning->SetColor(color);\n    }\n\n    if (x28_textpane_energywarning->GetGeometryColor().a())\n      x28_textpane_energywarning->SetIsVisible(true);\n    else\n      x28_textpane_energywarning->SetIsVisible(false);\n  }\n\n  if (x2c_energybart01_energybar->GetFilledEnergy() != x18_cachedBarEnergy || x1c_26_barDirty) {\n    x1c_26_barDirty = false;\n    x18_cachedBarEnergy = x2c_energybart01_energybar->GetFilledEnergy();\n    std::string string =\n        fmt::format(\"{:02d}\", int(std::fmod(x18_cachedBarEnergy, CPlayerState::GetEnergyTankCapacity())));\n    x20_textpane_energydigits->TextSupport().SetText(string);\n  }\n\n  ITweakGuiColors::SVisorEnergyBarColors barColors = g_tweakGuiColors->GetVisorEnergyBarColors(int(x0_hudType));\n  zeus::CColor emptyColor = x1c_27_energyLow ? g_tweakGuiColors->GetEnergyBarEmptyLowEnergy() : barColors.empty;\n  zeus::CColor filledColor = x1c_27_energyLow ? g_tweakGuiColors->GetEnergyBarFilledLowEnergy() : barColors.filled;\n  zeus::CColor shadowColor = x1c_27_energyLow ? g_tweakGuiColors->GetEnergyBarShadowLowEnergy() : barColors.shadow;\n  zeus::CColor useFillColor = zeus::CColor::lerp(filledColor, g_tweakGuiColors->GetEnergyBarFlashColor(), x8_flashMag);\n  if (x1c_27_energyLow)\n    useFillColor = zeus::CColor::lerp(useFillColor, zeus::CColor(1.f, 0.8f, 0.4f, 1.f), energyLowPulse);\n  x2c_energybart01_energybar->SetFilledColor(useFillColor);\n  x2c_energybart01_energybar->SetShadowColor(shadowColor);\n  x2c_energybart01_energybar->SetEmptyColor(emptyColor);\n}\n\nvoid CHudEnergyInterface::SetEnergyLow(bool energyLow) {\n  if (x1c_27_energyLow == energyLow)\n    return;\n\n  std::u16string string;\n  if (energyLow)\n    string = g_MainStringTable->GetString(9);\n\n  if (x28_textpane_energywarning)\n    x28_textpane_energywarning->TextSupport().SetText(string);\n\n  if (energyLow)\n    CSfxManager::SfxStart(SFXui_energy_low, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n\n  x1c_27_energyLow = energyLow;\n}\n\nvoid CHudEnergyInterface::SetFlashMagnitude(float mag) { x8_flashMag = zeus::clamp(0.f, mag, 1.f); }\n\nvoid CHudEnergyInterface::SetNumFilledEnergyTanks(int numTanksFilled) {\n  x14_numTanksFilled = numTanksFilled;\n  x24_meter_energytanks->SetCurrValue(numTanksFilled);\n}\n\nvoid CHudEnergyInterface::SetNumTotalEnergyTanks(int totalEnergyTanks) {\n  x10_totalEnergyTanks = totalEnergyTanks;\n  x24_meter_energytanks->SetCapacity(totalEnergyTanks);\n}\n\nvoid CHudEnergyInterface::SetCurrEnergy(float tankEnergy, bool wrapped) {\n  xc_tankEnergy = tankEnergy;\n  x2c_energybart01_energybar->SetCurrEnergy(tankEnergy, tankEnergy == 0.f ? CAuiEnergyBarT01::ESetMode::Insta\n                                                                          : CAuiEnergyBarT01::ESetMode(wrapped));\n}\n\nstd::pair<zeus::CVector3f, zeus::CVector3f> CHudEnergyInterface::CombatEnergyCoordFunc(float t) {\n  float theta = 0.46764705f * t - 0.15882353f;\n  float x = 17.f * std::sin(theta);\n  float y = 17.f * std::cos(theta) - 17.f;\n  return {zeus::CVector3f(x, y, 0.4f), zeus::CVector3f(x, y, 0.f)};\n}\n\nstd::pair<zeus::CVector3f, zeus::CVector3f> CHudEnergyInterface::XRayEnergyCoordFunc(float t) {\n  float theta = 1.8207964f - 0.69f * t;\n  float x = std::cos(theta);\n  float z = std::sin(theta);\n  return {zeus::CVector3f(9.4f * x, 0.f, 9.4f * z), zeus::CVector3f(9.f * x, 0.f, 9.f * z)};\n}\n\nstd::pair<zeus::CVector3f, zeus::CVector3f> CHudEnergyInterface::ThermalEnergyCoordFunc(float t) {\n  float x = 8.1663399f * t;\n  return {zeus::CVector3f(x, 0.f, 0.f), zeus::CVector3f(x, 0.f, 0.4355512f)};\n}\n\nstd::pair<zeus::CVector3f, zeus::CVector3f> CHudEnergyInterface::BallEnergyCoordFunc(float t) {\n  float x = 1.6666f * t;\n  return {zeus::CVector3f(x, 0.f, 0.f), zeus::CVector3f(x, 0.f, 0.088887997f)};\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CHudEnergyInterface.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/GuiSys/CHudInterface.hpp\"\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CAuiEnergyBarT01;\nclass CAuiMeter;\nclass CGuiFrame;\nclass CGuiTextPane;\nclass CGuiWidget;\n\nclass CHudEnergyInterface {\n  EHudType x0_hudType;\n  float x4_energyLowFader = 0.f;\n  float x8_flashMag = 0.f;\n  float xc_tankEnergy;\n  int x10_totalEnergyTanks;\n  int x14_numTanksFilled;\n  float x18_cachedBarEnergy = 0.f;\n  bool x1c_24_ : 1 = true;\n  bool x1c_25_ : 1 = true;\n  bool x1c_26_barDirty : 1 = true;\n  bool x1c_27_energyLow : 1;\n  CGuiTextPane* x20_textpane_energydigits;\n  CAuiMeter* x24_meter_energytanks;\n  CGuiTextPane* x28_textpane_energywarning;\n  CAuiEnergyBarT01* x2c_energybart01_energybar;\n\npublic:\n  CHudEnergyInterface(CGuiFrame& selHud, float tankEnergy, int totalEnergyTanks, int numTanksFilled, bool energyLow,\n                      EHudType hudType);\n  void Update(float dt, float energyLowPulse);\n  void SetEnergyLow(bool energyLow);\n  void SetFlashMagnitude(float mag);\n  void SetNumFilledEnergyTanks(int numTanksFilled);\n  void SetNumTotalEnergyTanks(int totalEnergyTanks);\n  void SetCurrEnergy(float tankEnergy, bool wrapped);\n  static std::pair<zeus::CVector3f, zeus::CVector3f> CombatEnergyCoordFunc(float t);\n  static std::pair<zeus::CVector3f, zeus::CVector3f> XRayEnergyCoordFunc(float t);\n  static std::pair<zeus::CVector3f, zeus::CVector3f> ThermalEnergyCoordFunc(float t);\n  static std::pair<zeus::CVector3f, zeus::CVector3f> BallEnergyCoordFunc(float t);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CHudFreeLookInterface.cpp",
    "content": "#include \"Runtime/GuiSys/CHudFreeLookInterface.hpp\"\n\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/GuiSys/CGuiFrame.hpp\"\n#include \"Runtime/GuiSys/CGuiModel.hpp\"\n\nnamespace metaforce {\n\nCHudFreeLookInterface::CHudFreeLookInterface(CGuiFrame& selHud, EHudType hudType, bool inFreeLook, bool lookControlHeld,\n                                             bool lockedOnObj)\n: x4_hudType(hudType)\n, x70_24_inFreeLook(inFreeLook)\n, x70_25_lookControlHeld(lookControlHeld)\n, x70_26_lockedOnObj(lockedOnObj) {\n  x6c_lockOnInterp = (lockedOnObj && hudType == EHudType::Scan) ? 0.f : 1.f;\n\n  x74_basewidget_freelookleft = selHud.FindWidget(\"basewidget_freelookleft\");\n  x78_model_shieldleft = static_cast<CGuiModel*>(selHud.FindWidget(\"model_shieldleft\"));\n  x7c_model_freelookleft = static_cast<CGuiModel*>(selHud.FindWidget(\"model_freelookleft\"));\n  x80_basewidget_freelookright = selHud.FindWidget(\"basewidget_freelookright\");\n  x84_model_shieldright = static_cast<CGuiModel*>(selHud.FindWidget(\"model_shieldright\"));\n  x88_model_freelookright = static_cast<CGuiModel*>(selHud.FindWidget(\"model_freelookright\"));\n  x8c_basewidget_outlinesb = selHud.FindWidget(\"basewidget_outlinesb\");\n\n  x8_freeLookLeftXf = x7c_model_freelookleft->GetTransform();\n  x38_freeLookRightXf = x88_model_freelookright->GetTransform();\n\n  x78_model_shieldleft->SetDepthWrite(true);\n  x84_model_shieldright->SetDepthWrite(true);\n}\n\nvoid CHudFreeLookInterface::Update(float dt) {\n  if (x70_24_inFreeLook)\n    x68_freeLookInterp = std::min(x68_freeLookInterp + dt / g_tweakGui->GetFreeLookFadeTime(), 1.f);\n  else\n    x68_freeLookInterp = std::max(0.f, x68_freeLookInterp - dt / g_tweakGui->GetFreeLookFadeTime());\n\n  if (x70_26_lockedOnObj && x4_hudType == EHudType::Scan)\n    x6c_lockOnInterp = std::min(x6c_lockOnInterp + 2.f * dt, 1.f);\n  else\n    x6c_lockOnInterp = std::max(0.f, x6c_lockOnInterp - 2.f * dt);\n}\n\nvoid CHudFreeLookInterface::SetIsVisibleDebug(bool v) {\n  x70_27_visibleDebug = v;\n  UpdateVisibility();\n}\n\nvoid CHudFreeLookInterface::SetIsVisibleGame(bool v) {\n  x70_28_visibleGame = v;\n  UpdateVisibility();\n}\n\nvoid CHudFreeLookInterface::UpdateVisibility() {\n  bool vis = x70_27_visibleDebug && x70_28_visibleGame;\n  x74_basewidget_freelookleft->SetVisibility(vis, ETraversalMode::Children);\n  x80_basewidget_freelookright->SetVisibility(vis, ETraversalMode::Children);\n  if (vis)\n    Update(0.f);\n}\n\nvoid CHudFreeLookInterface::SetFreeLookState(bool inFreeLook, bool lookControlHeld, bool lockedOnObj,\n                                             float vertLookAngle) {\n  x70_24_inFreeLook = inFreeLook;\n  vertLookAngle *= 8.f;\n  x70_25_lookControlHeld = lookControlHeld;\n  x70_26_lockedOnObj = lockedOnObj;\n\n  x7c_model_freelookleft->SetLocalTransform(x8_freeLookLeftXf * zeus::CTransform::Translate(0.f, 0.f, vertLookAngle));\n\n  x88_model_freelookright->SetLocalTransform(x38_freeLookRightXf *\n                                             zeus::CTransform::Translate(0.f, 0.f, vertLookAngle));\n\n  zeus::CColor color = zeus::skWhite;\n  float totalInterp = x68_freeLookInterp * (1.f - x6c_lockOnInterp);\n  color.a() = totalInterp;\n  x74_basewidget_freelookleft->SetColor(color);\n  x80_basewidget_freelookright->SetColor(color);\n\n  if (x8c_basewidget_outlinesb) {\n    color.a() = 0.7f * totalInterp + 0.3f;\n    x8c_basewidget_outlinesb->SetColor(color);\n  }\n\n  const bool visible = totalInterp != 0.0f;\n  x74_basewidget_freelookleft->SetVisibility(visible, ETraversalMode::Children);\n  x80_basewidget_freelookright->SetVisibility(visible, ETraversalMode::Children);\n}\n\nCHudFreeLookInterfaceXRay::CHudFreeLookInterfaceXRay(CGuiFrame& selHud, bool inFreeLook, bool lookControlHeld,\n                                                     bool lockedOnObj) {\n  x20_inFreeLook = inFreeLook;\n  x21_lookControlHeld = lookControlHeld;\n  x24_basewidget_freelook = selHud.FindWidget(\"basewidget_freelook\");\n  x28_model_shield = static_cast<CGuiModel*>(selHud.FindWidget(\"model_shield\"));\n  x2c_model_freelookleft = static_cast<CGuiModel*>(selHud.FindWidget(\"model_freelookleft\"));\n  x30_model_freelookright = static_cast<CGuiModel*>(selHud.FindWidget(\"model_freelookright\"));\n\n  x4_freeLookLeftPos = x2c_model_freelookleft->GetLocalPosition();\n  x10_freeLookRightPos = x30_model_freelookright->GetLocalPosition();\n\n  x28_model_shield->SetDepthWrite(true);\n}\n\nvoid CHudFreeLookInterfaceXRay::Update(float dt) {\n  if (x20_inFreeLook)\n    x1c_freeLookInterp = std::min(x1c_freeLookInterp + dt / g_tweakGui->GetFreeLookFadeTime(), 1.f);\n  else\n    x1c_freeLookInterp = std::max(0.f, x1c_freeLookInterp - dt / g_tweakGui->GetFreeLookFadeTime());\n}\n\nvoid CHudFreeLookInterfaceXRay::UpdateVisibility() {\n  bool vis = x22_24_visibleDebug && x22_25_visibleGame;\n  x2c_model_freelookleft->SetVisibility(vis, ETraversalMode::Children);\n  x30_model_freelookright->SetVisibility(vis, ETraversalMode::Children);\n  if (vis)\n    Update(0.f);\n}\n\nvoid CHudFreeLookInterfaceXRay::SetIsVisibleDebug(bool v) {\n  x22_24_visibleDebug = v;\n  UpdateVisibility();\n}\n\nvoid CHudFreeLookInterfaceXRay::SetIsVisibleGame(bool v) {\n  x22_25_visibleGame = v;\n  UpdateVisibility();\n}\n\nvoid CHudFreeLookInterfaceXRay::SetFreeLookState(bool inFreeLook, bool lookControlHeld, bool lockedOnObj,\n                                                 float vertLookAngle) {\n  x20_inFreeLook = inFreeLook;\n  x21_lookControlHeld = lookControlHeld;\n\n  x2c_model_freelookleft->SetLocalTransform(\n      zeus::CTransform(zeus::CMatrix3f::RotateY(vertLookAngle), x4_freeLookLeftPos));\n  x30_model_freelookright->SetLocalTransform(\n      zeus::CTransform(zeus::CMatrix3f::RotateY(-vertLookAngle), x10_freeLookRightPos));\n\n  zeus::CColor color = zeus::skWhite;\n  color.a() = x1c_freeLookInterp;\n  x24_basewidget_freelook->SetColor(color);\n\n  const bool visible = x1c_freeLookInterp != 0.0f;\n  x24_basewidget_freelook->SetVisibility(visible, ETraversalMode::Children);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CHudFreeLookInterface.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/GuiSys/CHudInterface.hpp\"\n#include <zeus/CTransform.hpp>\n\nnamespace metaforce {\nclass CGuiFrame;\nclass CGuiModel;\nclass CGuiWidget;\n\nclass IFreeLookInterface {\npublic:\n  virtual ~IFreeLookInterface() = default;\n  virtual void Update(float dt) = 0;\n  virtual void SetIsVisibleDebug(bool v) = 0;\n  virtual void SetIsVisibleGame(bool v) = 0;\n  virtual void SetFreeLookState(bool inFreeLook, bool lookControlHeld, bool lockedOnObj, float vertLookAngle) = 0;\n};\n\nclass CHudFreeLookInterface : public IFreeLookInterface {\n  EHudType x4_hudType;\n  zeus::CTransform x8_freeLookLeftXf;\n  zeus::CTransform x38_freeLookRightXf;\n  float x68_freeLookInterp = 0.f;\n  float x6c_lockOnInterp;\n  bool x70_24_inFreeLook : 1;\n  bool x70_25_lookControlHeld : 1;\n  bool x70_26_lockedOnObj : 1;\n  bool x70_27_visibleDebug : 1 = true;\n  bool x70_28_visibleGame : 1 = true;\n  CGuiWidget* x74_basewidget_freelookleft;\n  CGuiModel* x78_model_shieldleft;\n  CGuiModel* x7c_model_freelookleft;\n  CGuiWidget* x80_basewidget_freelookright;\n  CGuiModel* x84_model_shieldright;\n  CGuiModel* x88_model_freelookright;\n  CGuiWidget* x8c_basewidget_outlinesb;\n  void UpdateVisibility();\n\npublic:\n  CHudFreeLookInterface(CGuiFrame& selHud, EHudType hudType, bool inFreeLook, bool lookControlHeld, bool lockedOnObj);\n  void Update(float dt) override;\n  void SetIsVisibleDebug(bool v) override;\n  void SetIsVisibleGame(bool v) override;\n  void SetFreeLookState(bool inFreeLook, bool lookControlHeld, bool lockedOnObj, float vertLookAngle) override;\n};\n\nclass CHudFreeLookInterfaceXRay : public IFreeLookInterface {\n  zeus::CVector3f x4_freeLookLeftPos;\n  zeus::CVector3f x10_freeLookRightPos;\n  float x1c_freeLookInterp = 0.f;\n  bool x20_inFreeLook;\n  bool x21_lookControlHeld;\n  bool x22_24_visibleDebug : 1 = true;\n  bool x22_25_visibleGame : 1 = true;\n  CGuiWidget* x24_basewidget_freelook;\n  CGuiModel* x28_model_shield;\n  CGuiModel* x2c_model_freelookleft;\n  CGuiModel* x30_model_freelookright;\n  void UpdateVisibility();\n\npublic:\n  CHudFreeLookInterfaceXRay(CGuiFrame& selHud, bool inFreeLook, bool lookControlHeld, bool lockedOnObj);\n  void Update(float dt) override;\n  void SetIsVisibleDebug(bool v) override;\n  void SetIsVisibleGame(bool v) override;\n  void SetFreeLookState(bool inFreeLook, bool lookControlHeld, bool lockedOnObj, float vertLookAngle) override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CHudHelmetInterface.cpp",
    "content": "#include \"Runtime/GuiSys/CHudHelmetInterface.hpp\"\n\n#include \"Runtime/CGameState.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/GuiSys/CGuiCamera.hpp\"\n#include \"Runtime/GuiSys/CGuiFrame.hpp\"\n\nnamespace metaforce {\n\nCHudHelmetInterface::CHudHelmetInterface(CGuiFrame& helmetFrame) {\n  x40_camera = helmetFrame.GetFrameCamera();\n  x44_BaseWidget_Pivot = helmetFrame.FindWidget(\"BaseWidget_Pivot\");\n  x48_BaseWidget_Helmet = helmetFrame.FindWidget(\"BaseWidget_Helmet\");\n  x4c_BaseWidget_Glow = helmetFrame.FindWidget(\"BaseWidget_Glow\");\n  x50_BaseWidget_HelmetLight = helmetFrame.FindWidget(\"BaseWidget_HelmetLight\");\n  x24_pivotPosition = x44_BaseWidget_Pivot->GetIdlePosition();\n  x50_BaseWidget_HelmetLight->SetColor(g_tweakGuiColors->GetHelmetLightColor());\n}\n\nvoid CHudHelmetInterface::UpdateVisibility() {\n  x48_BaseWidget_Helmet->SetVisibility(x3c_24_helmetVisibleDebug && x3c_25_helmetVisibleGame, ETraversalMode::Children);\n  x4c_BaseWidget_Glow->SetVisibility(x3c_26_glowVisibleDebug && x3c_27_glowVisibleGame, ETraversalMode::Children);\n}\n\nvoid CHudHelmetInterface::Update(float dt) {\n  if (x3c_28_hudLagDirty) {\n    x3c_28_hudLagDirty = false;\n    x44_BaseWidget_Pivot->SetTransform(zeus::CTransform(x0_hudLagRotation, x24_pivotPosition + x30_hudLagPosition));\n  }\n}\n\nvoid CHudHelmetInterface::SetHudLagOffset(const zeus::CVector3f& off) {\n  x30_hudLagPosition = off;\n  x3c_28_hudLagDirty = true;\n}\n\nvoid CHudHelmetInterface::SetHudLagRotation(const zeus::CMatrix3f& rot) {\n  x0_hudLagRotation = rot;\n  x3c_28_hudLagDirty = true;\n}\n\nvoid CHudHelmetInterface::AddHelmetLightValue(float val) {\n  x50_BaseWidget_HelmetLight->SetColor(g_tweakGuiColors->GetHelmetLightColor() + zeus::CColor(val, val));\n}\n\nvoid CHudHelmetInterface::UpdateCameraDebugSettings(float fov, float y, float z) {\n  x40_camera->SetFov(fov);\n  x40_camera->SetTransform(zeus::CTransform(x40_camera->GetTransform().buildMatrix3f(), zeus::CVector3f(0.f, y, z)));\n}\n\nvoid CHudHelmetInterface::UpdateHelmetAlpha() {\n  zeus::CColor color = zeus::skWhite;\n  color.a() = g_GameState->GameOptions().GetHelmetAlpha() / 255.f;\n  x44_BaseWidget_Pivot->SetColor(color);\n}\n\nvoid CHudHelmetInterface::SetIsVisibleDebug(bool helmet, bool glow) {\n  x3c_24_helmetVisibleDebug = helmet;\n  x3c_26_glowVisibleDebug = glow;\n  UpdateVisibility();\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CHudHelmetInterface.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n#include <zeus/CMatrix3f.hpp>\n\nnamespace metaforce {\nclass CGuiCamera;\nclass CGuiFrame;\nclass CGuiWidget;\n\nclass CHudHelmetInterface {\n  zeus::CMatrix3f x0_hudLagRotation;\n  zeus::CVector3f x24_pivotPosition;\n  zeus::CVector3f x30_hudLagPosition;\n  bool x3c_24_helmetVisibleDebug : 1 = true;\n  bool x3c_25_helmetVisibleGame : 1 = true;\n  bool x3c_26_glowVisibleDebug : 1 = true;\n  bool x3c_27_glowVisibleGame : 1 = true;\n  bool x3c_28_hudLagDirty : 1 = false;\n  CGuiCamera* x40_camera;\n  CGuiWidget* x44_BaseWidget_Pivot;\n  CGuiWidget* x48_BaseWidget_Helmet;\n  CGuiWidget* x4c_BaseWidget_Glow;\n  CGuiWidget* x50_BaseWidget_HelmetLight;\n  void UpdateVisibility();\n\npublic:\n  explicit CHudHelmetInterface(CGuiFrame& helmetFrame);\n  void Update(float dt);\n  void SetHudLagOffset(const zeus::CVector3f& off);\n  void SetHudLagRotation(const zeus::CMatrix3f& rot);\n  void AddHelmetLightValue(float val);\n  void UpdateCameraDebugSettings(float fov, float y, float z);\n  void UpdateHelmetAlpha();\n  void SetIsVisibleDebug(bool helmet, bool glow);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CHudInterface.hpp",
    "content": "#pragma once\n\nnamespace metaforce {\n\nenum class EHudType { Combat, Scan, XRay, Thermal, Ball };\n\n}\n"
  },
  {
    "path": "Runtime/GuiSys/CHudMissileInterface.cpp",
    "content": "#include \"Runtime/GuiSys/CHudMissileInterface.hpp\"\n\n#include <array>\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/GuiSys/CAuiEnergyBarT01.hpp\"\n#include \"Runtime/GuiSys/CGuiFrame.hpp\"\n#include \"Runtime/GuiSys/CGuiModel.hpp\"\n#include \"Runtime/GuiSys/CGuiTextPane.hpp\"\n#include \"Runtime/GuiSys/CStringTable.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\nnamespace metaforce {\n\nconstexpr std::array<CAuiEnergyBarT01::FCoordFunc, 5> CoordFuncs{\n    CHudMissileInterface::CombatMissileBarCoordFunc,  nullptr, CHudMissileInterface::XRayMissileBarCoordFunc,\n    CHudMissileInterface::ThermalMissileBarCoordFunc, nullptr,\n};\n\nconstexpr std::array IconTranslateRanges{\n    6.05f, 0.f, 0.f, 8.4f, 0.f,\n};\n\nCHudMissileInterface::CHudMissileInterface(CGuiFrame& selHud, int missileCapacity, int numMissiles, float chargeFactor,\n                                           bool missilesActive, EHudType hudType, const CStateManager& mgr)\n: x0_hudType(hudType)\n, x4_missileCapacity(missileCapacity)\n, x8_numMissles(numMissiles)\n, x4c_chargeBeamFactor(chargeFactor)\n, x58_24_missilesActive(missilesActive) {\n  x5c_basewidget_missileicon = selHud.FindWidget(\"basewidget_missileicon\");\n  x60_textpane_missiledigits = static_cast<CGuiTextPane*>(selHud.FindWidget(\"textpane_missiledigits\"));\n  x64_energybart01_missilebar = static_cast<CAuiEnergyBarT01*>(selHud.FindWidget(\"energybart01_missilebar\"));\n  x68_textpane_missilewarning = static_cast<CGuiTextPane*>(selHud.FindWidget(\"textpane_missilewarning\"));\n  x6c_model_missilearrowup = static_cast<CGuiModel*>(selHud.FindWidget(\"model_missilearrowup\"));\n  x70_model_missilearrowdown = static_cast<CGuiModel*>(selHud.FindWidget(\"model_missilearrowdown\"));\n  x74_basewidget_missileicon = selHud.FindWidget(\"basewidget_missileicon\");\n\n  x58_27_hasArrows = x6c_model_missilearrowup && x70_model_missilearrowdown;\n  x58_28_notXRay = hudType != EHudType::XRay;\n\n  x10_missleIconXf = x74_basewidget_missileicon->GetLocalTransform();\n\n  x60_textpane_missiledigits->TextSupport().SetFontColor(g_tweakGuiColors->GetMissileDigitsFont());\n  x60_textpane_missiledigits->TextSupport().SetOutlineColor(g_tweakGuiColors->GetMissileDigitsOutline());\n  x74_basewidget_missileicon->SetColor(g_tweakGuiColors->GetMissileIconColorInactive());\n  x64_energybart01_missilebar->SetEmptyColor(g_tweakGuiColors->GetMissileBarEmpty());\n  x64_energybart01_missilebar->SetFilledColor(g_tweakGuiColors->GetMissileBarFilled());\n  x64_energybart01_missilebar->SetShadowColor(g_tweakGuiColors->GetMissileBarShadow());\n  x64_energybart01_missilebar->SetCoordFunc(CoordFuncs[size_t(hudType)]);\n  x64_energybart01_missilebar->SetTesselation(hudType == EHudType::Combat ? 1.f : 0.1f);\n  x64_energybart01_missilebar->SetMaxEnergy(5.f);\n  x64_energybart01_missilebar->SetFilledDrainSpeed(g_tweakGui->GetEnergyBarFilledSpeed());\n  x64_energybart01_missilebar->SetShadowDrainSpeed(g_tweakGui->GetEnergyBarShadowSpeed());\n  x64_energybart01_missilebar->SetShadowDrainDelay(g_tweakGui->GetEnergyBarDrainDelay());\n  x64_energybart01_missilebar->SetIsAlwaysResetTimer(true);\n\n  if (x68_textpane_missilewarning) {\n    x68_textpane_missilewarning->TextSupport().SetFontColor(g_tweakGuiColors->GetMissileWarningFont());\n    x68_textpane_missilewarning->TextSupport().SetOutlineColor(g_tweakGuiColors->GetMissileWarningOutline());\n  }\n\n  SetNumMissiles(x8_numMissles, mgr);\n  x44_latestStatus = GetMissileInventoryStatus();\n}\n\nvoid CHudMissileInterface::UpdateVisibility(const CStateManager& mgr) {\n  bool vis = x58_25_visibleDebug && x58_26_visibleGame;\n  x5c_basewidget_missileicon->SetVisibility(vis, ETraversalMode::Children);\n  x64_energybart01_missilebar->SetVisibility(vis, ETraversalMode::Children);\n  if (vis)\n    Update(0.f, mgr);\n}\n\nvoid CHudMissileInterface::Update(float dt, const CStateManager& mgr) {\n  if (x4_missileCapacity < 1)\n    x5c_basewidget_missileicon->SetIsVisible(false);\n  else\n    x5c_basewidget_missileicon->SetIsVisible(true);\n\n  if (x54_missileIconIncrement < 0.f) {\n    x54_missileIconIncrement -= 3.f * dt;\n    if (x54_missileIconIncrement <= -1.f)\n      x54_missileIconIncrement = 1.f;\n  } else if (x54_missileIconIncrement > 0.f) {\n    x54_missileIconIncrement = std::max(0.f, x54_missileIconIncrement - dt);\n  }\n\n  zeus::CColor addColor = g_tweakGuiColors->GetMissileIconColorActive() * x54_missileIconIncrement;\n\n  if (x50_missileIconAltDeplete > 0.f) {\n    x74_basewidget_missileicon->SetColor(zeus::CColor::lerp(g_tweakGuiColors->GetMissileIconColorInactive(),\n                                                            g_tweakGuiColors->GetMissileIconColorDepleteAlt(),\n                                                            x50_missileIconAltDeplete) +\n                                         addColor);\n  } else {\n    if (x4c_chargeBeamFactor > 0.f) {\n      float factor = std::min(x4c_chargeBeamFactor / CPlayerState::GetMissileComboChargeFactor(), 1.f);\n      if (x8_numMissles > mgr.GetPlayerState()->GetMissileCostForAltAttack()) {\n        x74_basewidget_missileicon->SetColor(zeus::CColor::lerp(g_tweakGuiColors->GetMissileIconColorInactive(),\n                                                                g_tweakGuiColors->GetMissileIconColorChargedCanAlt(),\n                                                                factor) +\n                                             addColor);\n      } else {\n        x74_basewidget_missileicon->SetColor(zeus::CColor::lerp(g_tweakGuiColors->GetMissileIconColorInactive(),\n                                                                g_tweakGuiColors->GetMissileIconColorChargedNoAlt(),\n                                                                factor) +\n                                             addColor);\n      }\n    } else {\n      if (x58_24_missilesActive)\n        x74_basewidget_missileicon->SetColor(g_tweakGuiColors->GetMissileIconColorActive() + addColor);\n      else\n        x74_basewidget_missileicon->SetColor(g_tweakGuiColors->GetMissileIconColorInactive() + addColor);\n    }\n  }\n\n  x50_missileIconAltDeplete = std::max(0.f, x50_missileIconAltDeplete - dt);\n\n  x64_energybart01_missilebar->SetMaxEnergy(x4_missileCapacity);\n  x64_energybart01_missilebar->SetCurrEnergy(x8_numMissles, CAuiEnergyBarT01::ESetMode::Normal);\n\n  if (x58_28_notXRay) {\n    x74_basewidget_missileicon->SetLocalTransform(\n        x10_missleIconXf *\n        zeus::CTransform::Translate(\n            0.f, 0.f, x8_numMissles * IconTranslateRanges[size_t(x0_hudType)] / float(x4_missileCapacity)));\n  }\n\n  if (x58_27_hasArrows) {\n    if (xc_arrowTimer > 0.f) {\n      xc_arrowTimer = std::max(0.f, xc_arrowTimer - dt);\n      zeus::CColor color = g_tweakGuiColors->GetMissileIconColorActive();\n      color.a() *= xc_arrowTimer / g_tweakGui->GetMissileArrowVisTime();\n      x6c_model_missilearrowup->SetColor(color);\n      x70_model_missilearrowdown->SetIsVisible(false);\n    } else if (xc_arrowTimer < 0.f) {\n      xc_arrowTimer = std::min(0.f, xc_arrowTimer + dt);\n      zeus::CColor color = g_tweakGuiColors->GetMissileIconColorActive();\n      color.a() *= -xc_arrowTimer / g_tweakGui->GetMissileArrowVisTime();\n      x70_model_missilearrowdown->SetColor(color);\n      x6c_model_missilearrowup->SetIsVisible(false);\n    } else {\n      x6c_model_missilearrowup->SetIsVisible(false);\n      x70_model_missilearrowdown->SetIsVisible(false);\n    }\n  }\n\n  if (x68_textpane_missilewarning) {\n    EInventoryStatus curStatus = GetMissileInventoryStatus();\n    if (curStatus != x44_latestStatus) {\n      std::u16string string;\n      switch (curStatus) {\n      case EInventoryStatus::Warning:\n        string = g_MainStringTable->GetString(12); // Missiles Low\n        break;\n      case EInventoryStatus::Depleted:\n        string = g_MainStringTable->GetString(13); // Depleted\n        break;\n      default:\n        break;\n      }\n      x68_textpane_missilewarning->TextSupport().SetText(string);\n\n      if (x44_latestStatus == EInventoryStatus::Normal && curStatus == EInventoryStatus::Warning) {\n        CSfxManager::SfxStart(SFXui_missile_warning, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n        x48_missileWarningPulse = g_tweakGui->GetMissileWarningPulseTime();\n      } else if (curStatus == EInventoryStatus::Depleted) {\n        CSfxManager::SfxStart(SFXui_missile_warning, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n        x48_missileWarningPulse = g_tweakGui->GetMissileWarningPulseTime();\n      }\n\n      x44_latestStatus = curStatus;\n    }\n\n    x48_missileWarningPulse = std::max(0.f, x48_missileWarningPulse - dt);\n    float warnPulse = std::min(x48_missileWarningPulse, 1.f);\n\n    if (x44_latestStatus != EInventoryStatus::Normal)\n      x40_missileWarningAlpha = std::min(x40_missileWarningAlpha + 2.f * dt, 1.f);\n    else\n      x40_missileWarningAlpha = std::max(0.f, x40_missileWarningAlpha - 2.f * dt);\n\n    float tmp = std::fabs(std::fmod(CGraphics::GetSecondsMod900(), 0.5f));\n    if (tmp < 0.25f)\n      tmp = tmp / 0.25f;\n    else\n      tmp = (0.5f - tmp) / 0.25f;\n\n    zeus::CColor color = zeus::skWhite;\n    color.a() = x40_missileWarningAlpha * tmp * warnPulse;\n    x68_textpane_missilewarning->SetColor(color);\n    if (x68_textpane_missilewarning->GetGeometryColor().a())\n      x68_textpane_missilewarning->SetIsVisible(true);\n    else\n      x68_textpane_missilewarning->SetIsVisible(false);\n  }\n}\n\nvoid CHudMissileInterface::SetIsVisibleGame(bool v, const CStateManager& mgr) {\n  x58_26_visibleGame = v;\n  UpdateVisibility(mgr);\n}\n\nvoid CHudMissileInterface::SetIsVisibleDebug(bool v, const CStateManager& mgr) {\n  x58_25_visibleDebug = v;\n  UpdateVisibility(mgr);\n}\n\nvoid CHudMissileInterface::SetIsMissilesActive(bool active) { x58_24_missilesActive = active; }\n\nvoid CHudMissileInterface::SetChargeBeamFactor(float t) { x4c_chargeBeamFactor = t; }\n\nvoid CHudMissileInterface::SetNumMissiles(int numMissiles, const CStateManager& mgr) {\n  numMissiles = zeus::clamp(0, numMissiles, 999);\n\n  x60_textpane_missiledigits->TextSupport().SetText(fmt::format(\"{:3d}\", numMissiles));\n\n  if (x8_numMissles < numMissiles) {\n    xc_arrowTimer = g_tweakGui->GetMissileArrowVisTime();\n    x54_missileIconIncrement = -FLT_EPSILON;\n  } else if (x8_numMissles > numMissiles) {\n    xc_arrowTimer = -g_tweakGui->GetMissileArrowVisTime();\n  }\n\n  if (mgr.GetPlayerState()->GetMissileCostForAltAttack() + numMissiles <= x8_numMissles)\n    x50_missileIconAltDeplete = 1.f;\n\n  x8_numMissles = numMissiles;\n}\n\nvoid CHudMissileInterface::SetMissileCapacity(int missileCapacity) { x4_missileCapacity = missileCapacity; }\n\nCHudMissileInterface::EInventoryStatus CHudMissileInterface::GetMissileInventoryStatus() const {\n  if (x64_energybart01_missilebar->GetSetEnergy() == 0.f)\n    return EInventoryStatus::Depleted;\n  return EInventoryStatus(x64_energybart01_missilebar->GetActualFraction() < g_tweakGui->GetMissileWarningFraction());\n}\n\nstd::pair<zeus::CVector3f, zeus::CVector3f> CHudMissileInterface::CombatMissileBarCoordFunc(float t) {\n  const float z = t * IconTranslateRanges[size_t(EHudType::Combat)];\n  return {zeus::CVector3f(0.f, 0.f, z), zeus::CVector3f(0.3f, 0.f, z)};\n}\n\nstd::pair<zeus::CVector3f, zeus::CVector3f> CHudMissileInterface::XRayMissileBarCoordFunc(float t) {\n  const float theta = 0.8f * (t - 0.5f);\n  const float x = 9.55f * std::cos(theta);\n  const float z = 9.55f * std::sin(theta);\n  return {zeus::CVector3f(x - 0.4f, 0.f, z), zeus::CVector3f(x, 0.f, z)};\n}\n\nstd::pair<zeus::CVector3f, zeus::CVector3f> CHudMissileInterface::ThermalMissileBarCoordFunc(float t) {\n  const float transRange = IconTranslateRanges[size_t(EHudType::Thermal)];\n  const float a = 0.08f * transRange;\n  const float b = t * transRange;\n\n  float c;\n  if (b < a) {\n    c = b / a;\n  } else if (b < transRange - a) {\n    c = 1.f;\n  } else {\n    c = 1.f - (b - (transRange - a)) / a;\n  }\n\n  return {zeus::CVector3f(-0.5f * c - 0.1f, 0.f, b), zeus::CVector3f(-0.1f, 0.f, b)};\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CHudMissileInterface.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/GuiSys/CHudInterface.hpp\"\n#include <zeus/CTransform.hpp>\n\nnamespace metaforce {\nclass CAuiEnergyBarT01;\nclass CGuiFrame;\nclass CGuiModel;\nclass CGuiTextPane;\nclass CGuiWidget;\nclass CStateManager;\n\nclass CHudMissileInterface {\n  enum class EInventoryStatus { Normal, Warning, Depleted };\n\n  EHudType x0_hudType;\n  int x4_missileCapacity;\n  int x8_numMissles;\n  float xc_arrowTimer = 0.f;\n  zeus::CTransform x10_missleIconXf;\n  float x40_missileWarningAlpha = 0.f;\n  EInventoryStatus x44_latestStatus = EInventoryStatus::Normal;\n  float x48_missileWarningPulse = 0.f;\n  float x4c_chargeBeamFactor;\n  float x50_missileIconAltDeplete = 0.f;\n  float x54_missileIconIncrement = 0.f;\n  bool x58_24_missilesActive : 1;\n  bool x58_25_visibleDebug : 1 = true;\n  bool x58_26_visibleGame : 1 = true;\n  bool x58_27_hasArrows : 1;\n  bool x58_28_notXRay : 1;\n  CGuiWidget* x5c_basewidget_missileicon;\n  CGuiTextPane* x60_textpane_missiledigits;\n  CAuiEnergyBarT01* x64_energybart01_missilebar;\n  CGuiTextPane* x68_textpane_missilewarning;\n  CGuiModel* x6c_model_missilearrowup;\n  CGuiModel* x70_model_missilearrowdown;\n  CGuiWidget* x74_basewidget_missileicon;\n  void UpdateVisibility(const CStateManager& mgr);\n\npublic:\n  CHudMissileInterface(CGuiFrame& selHud, int missileCapacity, int numMissiles, float chargeFactor, bool missilesActive,\n                       EHudType hudType, const CStateManager& mgr);\n  void Update(float dt, const CStateManager& mgr);\n  void SetIsVisibleGame(bool v, const CStateManager& mgr);\n  void SetIsVisibleDebug(bool v, const CStateManager& mgr);\n  void SetIsMissilesActive(bool active);\n  void SetChargeBeamFactor(float t);\n  void SetNumMissiles(int numMissiles, const CStateManager& mgr);\n  void SetMissileCapacity(int missileCapacity);\n  EInventoryStatus GetMissileInventoryStatus() const;\n  static std::pair<zeus::CVector3f, zeus::CVector3f> CombatMissileBarCoordFunc(float t);\n  static std::pair<zeus::CVector3f, zeus::CVector3f> XRayMissileBarCoordFunc(float t);\n  static std::pair<zeus::CVector3f, zeus::CVector3f> ThermalMissileBarCoordFunc(float t);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CHudRadarInterface.cpp",
    "content": "#include \"Runtime/GuiSys/CHudRadarInterface.hpp\"\n\n#include \"Runtime/CGameState.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Camera/CGameCamera.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/GuiSys/CGuiCamera.hpp\"\n#include \"Runtime/GuiSys/CGuiFrame.hpp\"\n#include \"Runtime/GuiSys/CGuiWidgetDrawParms.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CWallCrawlerSwarm.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\n#include <zeus/CEulerAngles.hpp>\n\nnamespace metaforce {\n\nCHudRadarInterface::CHudRadarInterface(CGuiFrame& baseHud, CStateManager& stateMgr) {\n  x0_txtrRadarPaint = g_SimplePool->GetObj(\"TXTR_RadarPaint\");\n  x40_BaseWidget_RadarStuff = baseHud.FindWidget(\"BaseWidget_RadarStuff\");\n  x44_camera = baseHud.GetFrameCamera();\n  xc_radarStuffXf = x40_BaseWidget_RadarStuff->GetLocalTransform();\n  x40_BaseWidget_RadarStuff->SetColor(g_tweakGuiColors->GetRadarStuffColor());\n}\n\nvoid CHudRadarInterface::DoDrawRadarPaint(float radius) {\n  radius *= 4.f;\n\n  CGraphics::StreamBegin(ERglPrimitive::TriangleStrip);\n  CGraphics::StreamTexcoord(0.f, 1.f);\n  CGraphics::StreamVertex(-radius, 0.f, radius);\n  CGraphics::StreamTexcoord(0.f, 0.f);\n  CGraphics::StreamVertex(-radius, 0.f, -radius);\n  CGraphics::StreamTexcoord(1.f, 1.f);\n  CGraphics::StreamVertex(radius, 0.f, radius);\n  CGraphics::StreamTexcoord(1.f, 0.f);\n  CGraphics::StreamVertex(radius, 0.f, -radius);\n  CGraphics::StreamEnd();\n}\n\nvoid CHudRadarInterface::DrawRadarPaint(const zeus::CVector3f& enemyPos, float radius, float alpha,\n                                        const SRadarPaintDrawParms& parms) {\n  const zeus::CVector2f playerToEnemy = enemyPos.toVec2f() - parms.x0_playerPos.toVec2f();\n  const float zDelta = std::fabs(enemyPos.z() - parms.x0_playerPos.z());\n\n  if (playerToEnemy.magnitude() > parms.x78_xyRadius || zDelta > parms.x7c_zRadius) {\n    return;\n  }\n\n  if (zDelta > parms.x80_ZCloseRadius) {\n    alpha *= 1.f - (zDelta - parms.x80_ZCloseRadius) / (parms.x7c_zRadius - parms.x80_ZCloseRadius);\n  }\n\n  const zeus::CVector2f scopeScaled = playerToEnemy * parms.x70_scopeScalar;\n  g_Renderer->SetModelMatrix(\n      parms.x3c_postTranslate *\n      zeus::CTransform::Translate(parms.xc_preTranslate * zeus::CVector3f(scopeScaled.x(), 0.f, scopeScaled.y())));\n\n  zeus::CColor color = g_tweakGuiColors->GetRadarEnemyPaintColor();\n  color.a() *= alpha;\n  color.a() *= parms.x74_alpha;\n  CGraphics::StreamColor(color);\n  DoDrawRadarPaint(radius);\n}\n\nvoid CHudRadarInterface::SetIsVisibleGame(bool v) {\n  x3c_24_visibleGame = v;\n  x40_BaseWidget_RadarStuff->SetVisibility(x3c_25_visibleDebug && x3c_24_visibleGame, ETraversalMode::Children);\n}\n\nvoid CHudRadarInterface::Update(float dt, const CStateManager& mgr) {\n  const CPlayerState& playerState = *mgr.GetPlayerState();\n  const float visorTransFactor = (playerState.GetCurrentVisor() == CPlayerState::EPlayerVisor::Combat)\n                                     ? playerState.GetVisorTransitionFactor()\n                                     : 0.f;\n  zeus::CColor color = g_tweakGuiColors->GetRadarStuffColor();\n  color.a() *= g_GameState->GameOptions().GetHUDAlpha() / 255.f * visorTransFactor;\n  x40_BaseWidget_RadarStuff->SetColor(color);\n  const bool tweakVis = g_tweakGui->GetHudVisMode() >= ITweakGui::EHudVisMode::Three;\n\n  if (tweakVis == x3c_25_visibleDebug) {\n    return;\n  }\n\n  x3c_25_visibleDebug = tweakVis;\n  x40_BaseWidget_RadarStuff->SetVisibility(x3c_25_visibleDebug && x3c_24_visibleGame, ETraversalMode::Children);\n}\n\nvoid CHudRadarInterface::Draw(const CStateManager& mgr, float alpha) {\n  alpha *= g_GameState->GameOptions().GetHUDAlpha() / 255.f;\n  if (g_tweakGui->GetHudVisMode() == ITweakGui::EHudVisMode::Zero || !x3c_24_visibleGame || !x0_txtrRadarPaint ||\n      !x0_txtrRadarPaint.IsLoaded()) {\n    return;\n  }\n\n  SRadarPaintDrawParms drawParms;\n\n  const CPlayer& player = mgr.GetPlayer();\n  if (player.IsOverrideRadarRadius()) {\n    drawParms.x78_xyRadius = player.GetRadarXYRadiusOverride();\n    drawParms.x7c_zRadius = player.GetRadarZRadiusOverride();\n    drawParms.x80_ZCloseRadius = 0.667f * drawParms.x7c_zRadius;\n  } else {\n    drawParms.x78_xyRadius = g_tweakGui->GetRadarXYRadius();\n    drawParms.x7c_zRadius = g_tweakGui->GetRadarZRadius();\n    drawParms.x80_ZCloseRadius = g_tweakGui->GetRadarZCloseRadius();\n  }\n\n  drawParms.x6c_scopeRadius = g_tweakGui->GetRadarScopeCoordRadius();\n  drawParms.x70_scopeScalar = drawParms.x6c_scopeRadius / drawParms.x78_xyRadius;\n\n  const float camZ =\n      zeus::CEulerAngles(zeus::CQuaternion(mgr.GetCameraManager()->GetCurrentCamera(mgr)->GetTransform().basis)).z();\n  zeus::CRelAngle angleZ(camZ);\n  angleZ.makeRel();\n  drawParms.xc_preTranslate = zeus::CTransform::RotateY(angleZ);\n  drawParms.x3c_postTranslate = x40_BaseWidget_RadarStuff->GetWorldTransform();\n  const float enemyRadius = g_tweakGui->GetRadarEnemyPaintRadius();\n\n  x44_camera->Draw(CGuiWidgetDrawParms{0.f, zeus::CVector3f{}});\n\n  g_Renderer->SetModelMatrix(drawParms.x3c_postTranslate);\n  g_Renderer->SetBlendMode_AdditiveAlpha();\n  x0_txtrRadarPaint->Load(GX_TEXMAP0, EClampMode::Repeat);\n  CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvModulate);\n  g_Renderer->SetDepthReadWrite(false, false);\n  zeus::CColor playerColor = g_tweakGuiColors->GetRadarPlayerPaintColor();\n  playerColor.a() *= alpha;\n  CGraphics::StreamColor(playerColor);\n  DoDrawRadarPaint(g_tweakGui->GetRadarPlayerPaintRadius());\n\n  const zeus::CAABox radarBounds(\n      player.GetTranslation().x() - drawParms.x78_xyRadius, player.GetTranslation().y() - drawParms.x78_xyRadius,\n      player.GetTranslation().z() - drawParms.x7c_zRadius, player.GetTranslation().x() + drawParms.x78_xyRadius,\n      player.GetTranslation().y() + drawParms.x78_xyRadius, player.GetTranslation().z() + drawParms.x7c_zRadius);\n\n  EntityList nearList;\n  mgr.BuildNearList(nearList, radarBounds,\n                    CMaterialFilter(CMaterialList(EMaterialTypes::Target, EMaterialTypes::RadarObject),\n                                    CMaterialList(EMaterialTypes::ExcludeFromRadar),\n                                    CMaterialFilter::EFilterType::IncludeExclude),\n                    nullptr);\n  drawParms.x0_playerPos = mgr.GetPlayer().GetTranslation();\n  drawParms.x74_alpha = alpha;\n\n  for (const auto& id : nearList) {\n    if (const TCastToConstPtr<CActor> act = mgr.GetObjectById(id)) {\n      if (!act->GetActive()) {\n        continue;\n      }\n      if (const TCastToConstPtr<CWallCrawlerSwarm> swarm = act.GetPtr()) {\n        const float radius = enemyRadius * 0.5f;\n        for (const CWallCrawlerSwarm::CBoid& boid : swarm->GetBoids()) {\n          if (!boid.GetActive()) {\n            continue;\n          }\n          DrawRadarPaint(boid.GetTranslation(), radius, 0.5f, drawParms);\n        }\n      } else {\n        DrawRadarPaint(act->GetTranslation(), enemyRadius, 1.f, drawParms);\n      }\n    }\n  }\n\n  g_Renderer->SetDepthReadWrite(true, true);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CHudRadarInterface.hpp",
    "content": "#pragma once\n\n#include <vector>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/Graphics/CTexture.hpp\"\n\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CGuiCamera;\nclass CGuiFrame;\nclass CGuiWidget;\nclass CStateManager;\n\nclass CHudRadarInterface {\n  struct SRadarPaintDrawParms {\n    zeus::CVector3f x0_playerPos;\n    zeus::CTransform xc_preTranslate;\n    zeus::CTransform x3c_postTranslate;\n    float x6c_scopeRadius;\n    float x70_scopeScalar;\n    float x74_alpha;\n    float x78_xyRadius;\n    float x7c_zRadius;\n    float x80_ZCloseRadius;\n  };\n  TLockedToken<CTexture> x0_txtrRadarPaint;\n  zeus::CTransform xc_radarStuffXf;\n  bool x3c_24_visibleGame : 1 = true;\n  bool x3c_25_visibleDebug : 1 = true;\n  CGuiWidget* x40_BaseWidget_RadarStuff;\n  CGuiCamera* x44_camera;\n\n  void DoDrawRadarPaint(float radius);\n  void DrawRadarPaint(const zeus::CVector3f& enemyPos, float radius, float alpha, const SRadarPaintDrawParms& parms);\n\npublic:\n  CHudRadarInterface(CGuiFrame& baseHud, CStateManager& stateMgr);\n  void SetIsVisibleGame(bool v);\n  void Update(float dt, const CStateManager& mgr);\n  void Draw(const CStateManager& mgr, float alpha);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CHudThreatInterface.cpp",
    "content": "#include \"Runtime/GuiSys/CHudThreatInterface.hpp\"\n\n#include <array>\n\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/GuiSys/CAuiEnergyBarT01.hpp\"\n#include \"Runtime/GuiSys/CGuiFrame.hpp\"\n#include \"Runtime/GuiSys/CGuiModel.hpp\"\n#include \"Runtime/GuiSys/CGuiTextPane.hpp\"\n#include \"Runtime/GuiSys/CStringTable.hpp\"\n#include \"Runtime/Audio/CSfxManager.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\nnamespace metaforce {\n\nconstexpr std::array<CAuiEnergyBarT01::FCoordFunc, 5> CoordFuncs{\n    CHudThreatInterface::CombatThreatBarCoordFunc,  nullptr, CHudThreatInterface::XRayThreatBarCoordFunc,\n    CHudThreatInterface::ThermalThreatBarCoordFunc, nullptr,\n};\n\nconstexpr std::array IconTranslateRanges{\n    6.05f, 0.f, 0.f, 8.4f, 0.f,\n};\n\nCHudThreatInterface::CHudThreatInterface(CGuiFrame& selHud, EHudType hudType, float threatDist)\n: x4_hudType(hudType), x10_threatDist(threatDist) {\n  x58_basewidget_threatstuff = selHud.FindWidget(\"basewidget_threatstuff\");\n  x5c_basewidget_threaticon = selHud.FindWidget(\"basewidget_threaticon\");\n  x60_model_threatarrowup = static_cast<CGuiModel*>(selHud.FindWidget(\"model_threatarrowup\"));\n  x64_model_threatarrowdown = static_cast<CGuiModel*>(selHud.FindWidget(\"model_threatarrowdown\"));\n  x68_textpane_threatwarning = static_cast<CGuiTextPane*>(selHud.FindWidget(\"textpane_threatwarning\"));\n  x6c_energybart01_threatbar = static_cast<CAuiEnergyBarT01*>(selHud.FindWidget(\"energybart01_threatbar\"));\n  x70_textpane_threatdigits = static_cast<CGuiTextPane*>(selHud.FindWidget(\"textpane_threatdigits\"));\n\n  if (x70_textpane_threatdigits) {\n    x70_textpane_threatdigits->TextSupport().SetFontColor(g_tweakGuiColors->GetThreatDigitsFont());\n    x70_textpane_threatdigits->TextSupport().SetOutlineColor(g_tweakGuiColors->GetThreatDigitsOutline());\n  }\n\n  x54_26_hasArrows = x60_model_threatarrowup && x64_model_threatarrowdown;\n  x54_27_notXRay = hudType != EHudType::XRay;\n\n  x5c_basewidget_threaticon->SetColor(g_tweakGuiColors->GetThreatIconColor());\n  x18_threatIconXf = x5c_basewidget_threaticon->GetLocalTransform();\n\n  x6c_energybart01_threatbar->SetFilledColor(g_tweakGuiColors->GetThreatBarFilled());\n  x6c_energybart01_threatbar->SetShadowColor(g_tweakGuiColors->GetThreatBarShadow());\n  x6c_energybart01_threatbar->SetEmptyColor(g_tweakGuiColors->GetThreatBarEmpty());\n  x6c_energybart01_threatbar->SetCoordFunc(CoordFuncs[size_t(hudType)]);\n  x6c_energybart01_threatbar->SetTesselation(hudType == EHudType::Combat ? 1.f : 0.1f);\n  x6c_energybart01_threatbar->SetMaxEnergy(g_tweakGui->GetThreatRange());\n  x6c_energybart01_threatbar->SetFilledDrainSpeed(9999.f);\n  x6c_energybart01_threatbar->SetShadowDrainSpeed(9999.f);\n  x6c_energybart01_threatbar->SetShadowDrainDelay(0.f);\n  x6c_energybart01_threatbar->SetIsAlwaysResetTimer(false);\n\n  if (x68_textpane_threatwarning) {\n    x68_textpane_threatwarning->TextSupport().SetFontColor(g_tweakGuiColors->GetThreatWarningFont());\n    x68_textpane_threatwarning->TextSupport().SetOutlineColor(g_tweakGuiColors->GetThreatWarningOutline());\n  }\n}\n\nvoid CHudThreatInterface::SetThreatDistance(float threatDist) { x10_threatDist = threatDist; }\n\nvoid CHudThreatInterface::SetIsVisibleDebug(bool v) {\n  x54_24_visibleDebug = v;\n  UpdateVisibility();\n}\n\nvoid CHudThreatInterface::SetIsVisibleGame(bool v) {\n  x54_25_visibleGame = v;\n  UpdateVisibility();\n}\n\nvoid CHudThreatInterface::UpdateVisibility() {\n  bool vis = x54_24_visibleDebug && x54_25_visibleGame;\n  x58_basewidget_threatstuff->SetVisibility(vis, ETraversalMode::Children);\n  if (vis)\n    SetThreatDistance(0.f);\n}\n\nvoid CHudThreatInterface::Update(float dt) {\n  zeus::CColor warningColor = zeus::CColor::lerp(g_tweakGuiColors->GetThreatIconColor(),\n                                                 g_tweakGuiColors->GetThreatIconWarningColor(), x50_warningColorLerp);\n\n  float maxThreatEnergy = g_tweakGui->GetThreatRange();\n  if (x70_textpane_threatdigits) {\n    if (x10_threatDist < maxThreatEnergy) {\n      x70_textpane_threatdigits->SetIsVisible(true);\n      x70_textpane_threatdigits->TextSupport().SetText(\n          fmt::format(\"{:01.1f}\", std::max(0.f, x10_threatDist)));\n    } else {\n      x70_textpane_threatdigits->SetIsVisible(false);\n    }\n  }\n\n  if (x54_26_hasArrows) {\n    if (x14_arrowTimer > 0.f) {\n      x60_model_threatarrowup->SetIsVisible(true);\n      x14_arrowTimer = std::max(0.f, x14_arrowTimer - dt);\n      zeus::CColor color = warningColor;\n      color.a() = x14_arrowTimer / g_tweakGui->GetMissileArrowVisTime();\n      x60_model_threatarrowup->SetColor(color);\n      x64_model_threatarrowdown->SetIsVisible(false);\n    } else if (x14_arrowTimer < 0.f) {\n      x64_model_threatarrowdown->SetIsVisible(true);\n      x14_arrowTimer = std::min(0.f, x14_arrowTimer + dt);\n      zeus::CColor color = warningColor;\n      color.a() = -x14_arrowTimer / g_tweakGui->GetMissileArrowVisTime();\n      x64_model_threatarrowdown->SetColor(color);\n      x60_model_threatarrowup->SetIsVisible(false);\n    } else {\n      x60_model_threatarrowup->SetIsVisible(false);\n      x64_model_threatarrowdown->SetIsVisible(false);\n    }\n  }\n\n  if (x10_threatDist <= maxThreatEnergy) {\n    float tmp = x10_threatDist - (maxThreatEnergy - x6c_energybart01_threatbar->GetSetEnergy());\n    if (tmp < -0.01f)\n      x14_arrowTimer = g_tweakGui->GetMissileArrowVisTime();\n    else if (tmp > 0.01f)\n      x14_arrowTimer = -g_tweakGui->GetMissileArrowVisTime();\n  } else {\n    x14_arrowTimer = 0.f;\n  }\n\n  if (x10_threatDist <= maxThreatEnergy) {\n    x6c_energybart01_threatbar->SetCurrEnergy(x6c_energybart01_threatbar->GetMaxEnergy() - x10_threatDist,\n                                              CAuiEnergyBarT01::ESetMode::Normal);\n    x5c_basewidget_threaticon->SetColor(warningColor);\n  } else {\n    x6c_energybart01_threatbar->SetCurrEnergy(0.f, CAuiEnergyBarT01::ESetMode::Normal);\n    x5c_basewidget_threaticon->SetColor(g_tweakGuiColors->GetThreatIconSafeColor());\n  }\n\n  if (x54_27_notXRay) {\n    x5c_basewidget_threaticon->SetLocalTransform(\n        x18_threatIconXf * zeus::CTransform::Translate(0.f, 0.f,\n                                                       std::max(0.f, maxThreatEnergy - x10_threatDist) *\n                                                           IconTranslateRanges[size_t(x4_hudType)] / maxThreatEnergy));\n  }\n\n  if (x68_textpane_threatwarning) {\n    if (x6c_energybart01_threatbar->GetActualFraction() > g_tweakGui->GetThreatWarningFraction())\n      x68_textpane_threatwarning->SetIsVisible(true);\n    else\n      x68_textpane_threatwarning->SetIsVisible(false);\n\n    EThreatStatus newStatus;\n    if (maxThreatEnergy == x6c_energybart01_threatbar->GetSetEnergy())\n      newStatus = EThreatStatus::Damage;\n    else if (x6c_energybart01_threatbar->GetActualFraction() > g_tweakGui->GetThreatWarningFraction())\n      newStatus = EThreatStatus::Warning;\n    else\n      newStatus = EThreatStatus::Normal;\n\n    if (x4c_threatStatus != newStatus) {\n      std::u16string string;\n      if (newStatus == EThreatStatus::Warning)\n        string = g_MainStringTable->GetString(10);\n      else if (newStatus == EThreatStatus::Damage)\n        string = g_MainStringTable->GetString(11);\n\n      x68_textpane_threatwarning->TextSupport().SetText(string);\n\n      if (x4c_threatStatus == EThreatStatus::Normal && newStatus == EThreatStatus::Warning)\n        CSfxManager::SfxStart(SFXui_threat_warning, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n      else if (newStatus == EThreatStatus::Damage)\n        CSfxManager::SfxStart(SFXui_threat_damage, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n\n      x4c_threatStatus = newStatus;\n    }\n  }\n\n  float oldX8 = x8_damagePulseTimer;\n  x8_damagePulseTimer = std::fmod(x8_damagePulseTimer + dt, 0.5f);\n  if (x8_damagePulseTimer < 0.25f)\n    xc_damagePulse = x8_damagePulseTimer / 0.25f;\n  else\n    xc_damagePulse = (0.5f - x8_damagePulseTimer) / 0.25f;\n\n  if (x4c_threatStatus == EThreatStatus::Damage && x8_damagePulseTimer < oldX8)\n    CSfxManager::SfxStart(SFXui_threat_damage, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n\n  if (x68_textpane_threatwarning) {\n    if (x4c_threatStatus != EThreatStatus::Normal) {\n      x48_warningLerpAlpha = std::min(x48_warningLerpAlpha + 2.f * dt, 1.f);\n      zeus::CColor color = zeus::skWhite;\n      color.a() = x48_warningLerpAlpha * xc_damagePulse;\n      x68_textpane_threatwarning->SetColor(color);\n    } else {\n      x48_warningLerpAlpha = std::max(0.f, x48_warningLerpAlpha - 2.f * dt);\n      zeus::CColor color = zeus::skWhite;\n      color.a() = x48_warningLerpAlpha * xc_damagePulse;\n      x68_textpane_threatwarning->SetColor(color);\n    }\n    if (x68_textpane_threatwarning->GetGeometryColor().a() > 0.f)\n      x68_textpane_threatwarning->SetIsVisible(true);\n    else\n      x68_textpane_threatwarning->SetIsVisible(false);\n  }\n\n  if (x4c_threatStatus == EThreatStatus::Damage)\n    x50_warningColorLerp = std::min(x50_warningColorLerp + 2.f * dt, 1.f);\n  else\n    x50_warningColorLerp = std::max(0.f, x50_warningColorLerp - 2.f * dt);\n}\n\nstd::pair<zeus::CVector3f, zeus::CVector3f> CHudThreatInterface::CombatThreatBarCoordFunc(float t) {\n  const float z = IconTranslateRanges[size_t(EHudType::Combat)] * t;\n  return {zeus::CVector3f(-0.3f, 0.f, z), zeus::CVector3f(0.f, 0.f, z)};\n}\n\nstd::pair<zeus::CVector3f, zeus::CVector3f> CHudThreatInterface::XRayThreatBarCoordFunc(float t) {\n  const float theta = 0.8f * (t - 0.5f);\n  const float x = -9.55f * std::cos(theta);\n  const float z = 9.55f * std::sin(theta);\n  return {zeus::CVector3f(0.4f + x, 0.f, z), zeus::CVector3f(x, 0.f, z)};\n}\n\nstd::pair<zeus::CVector3f, zeus::CVector3f> CHudThreatInterface::ThermalThreatBarCoordFunc(float t) {\n  const float transRange = IconTranslateRanges[size_t(EHudType::Thermal)];\n  const float a = 0.08f * transRange;\n  const float b = t * transRange;\n\n  float c;\n  if (b < a) {\n    c = b / a;\n  } else if (b < transRange - a) {\n    c = 1.f;\n  } else {\n    c = 1.f - (b - (transRange - a)) / a;\n  }\n\n  return {zeus::CVector3f(0.1f, 0.f, b), zeus::CVector3f(0.5f * c + 0.1f, 0.f, b)};\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CHudThreatInterface.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/GuiSys/CHudInterface.hpp\"\n#include <zeus/CTransform.hpp>\n\nnamespace metaforce {\nclass CAuiEnergyBarT01;\nclass CGuiFrame;\nclass CGuiModel;\nclass CGuiTextPane;\nclass CGuiWidget;\n\nclass CHudThreatInterface {\n  enum class EThreatStatus { Normal, Warning, Damage };\n\n  EHudType x4_hudType;\n  float x8_damagePulseTimer = 0.f;\n  float xc_damagePulse = 0.f;\n  float x10_threatDist;\n  float x14_arrowTimer = 0.f;\n  zeus::CTransform x18_threatIconXf;\n  float x48_warningLerpAlpha = 0.f;\n  EThreatStatus x4c_threatStatus = EThreatStatus::Normal;\n  float x50_warningColorLerp = 0.f;\n  bool x54_24_visibleDebug : 1 = true;\n  bool x54_25_visibleGame : 1 = true;\n  bool x54_26_hasArrows : 1;\n  bool x54_27_notXRay : 1;\n  CGuiWidget* x58_basewidget_threatstuff;\n  CGuiWidget* x5c_basewidget_threaticon;\n  CGuiModel* x60_model_threatarrowup;\n  CGuiModel* x64_model_threatarrowdown;\n  CGuiTextPane* x68_textpane_threatwarning;\n  CAuiEnergyBarT01* x6c_energybart01_threatbar;\n  CGuiTextPane* x70_textpane_threatdigits;\n  void UpdateVisibility();\n\npublic:\n  CHudThreatInterface(CGuiFrame& selHud, EHudType hudType, float threatDist);\n  void SetThreatDistance(float threatDist);\n  void SetIsVisibleDebug(bool v);\n  void SetIsVisibleGame(bool v);\n  void Update(float dt);\n  static std::pair<zeus::CVector3f, zeus::CVector3f> CombatThreatBarCoordFunc(float t);\n  static std::pair<zeus::CVector3f, zeus::CVector3f> XRayThreatBarCoordFunc(float t);\n  static std::pair<zeus::CVector3f, zeus::CVector3f> ThermalThreatBarCoordFunc(float t);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CHudVisorBeamMenu.cpp",
    "content": "#include \"Runtime/GuiSys/CHudVisorBeamMenu.hpp\"\n\n#include <array>\n\n#include \"Runtime/CGameState.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Audio/CSfxManager.hpp\"\n#include \"Runtime/GuiSys/CGuiFrame.hpp\"\n#include \"Runtime/GuiSys/CGuiModel.hpp\"\n#include \"Runtime/GuiSys/CGuiTextPane.hpp\"\n#include \"Runtime/GuiSys/CStringTable.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\nnamespace metaforce {\n\nconstexpr std::array BaseMenuNames{\n    \"BaseWidget_VisorMenu\"sv,\n    \"BaseWidget_BeamMenu\"sv,\n};\n\nconstexpr std::array TextNames{\n    \"TextPane_VisorMenu\"sv,\n    \"TextPane_BeamMenu\"sv,\n};\n\nconstexpr std::array BaseTitleNames{\n    \"basewidget_visormenutitle\"sv,\n    \"basewidget_beammenutitle\"sv,\n};\n\nconstexpr std::array ModelNames{\n    \"model_visor\"sv,\n    \"model_beam\"sv,\n};\n\nconstexpr std::array<std::array<char, 4>, 2> MenuItemOrders{{\n    {'1', '0', '3', '2'},\n    {'3', '2', '1', '0'},\n}};\n\nconstexpr std::array<std::array<int, 4>, 2> MenuStringIdx{{\n    {0, 2, 1, 3}, // Combat, XRay, Scan, Thermal\n    {4, 5, 6, 7}, // Power, Ice, Wave, Plasma\n}};\n\nconstexpr std::array<u16, 2> SelectionSfxs{\n    SFXui_select_visor,\n    SFXui_select_beam,\n};\n\nCHudVisorBeamMenu::CHudVisorBeamMenu(CGuiFrame& baseHud, EHudVisorBeamMenu type,\n                                     const rstl::reserved_vector<bool, 4>& enables)\n: x0_baseHud(baseHud), x4_type(type) {\n  x7c_animDur = g_tweakGui->GetBeamVisorMenuAnimTime();\n  x80_24_swapBeamControls = g_GameState->GameOptions().GetSwapBeamControls();\n\n  EHudVisorBeamMenu swappedType;\n  if (x80_24_swapBeamControls)\n    swappedType = EHudVisorBeamMenu(1 - int(x4_type));\n  else\n    swappedType = x4_type;\n\n  x20_textpane_menu = static_cast<CGuiTextPane*>(x0_baseHud.FindWidget(TextNames[size_t(swappedType)]));\n  x1c_basewidget_menutitle = x0_baseHud.FindWidget(BaseTitleNames[size_t(swappedType)]);\n  x18_basewidget_menu = x0_baseHud.FindWidget(BaseMenuNames[size_t(swappedType)]);\n\n  x24_model_ghost =\n      static_cast<CGuiModel*>(x0_baseHud.FindWidget(fmt::format(\"{}ghost\", ModelNames[size_t(x4_type)])));\n\n  x28_menuItems.resize(4);\n  for (size_t i = 0; i < x28_menuItems.size(); i++) {\n    const auto modelName = ModelNames[size_t(x4_type)];\n    const auto menuItemOrder = MenuItemOrders[size_t(x4_type)][i];\n\n    SMenuItem& item = x28_menuItems[i];\n    item.x0_model_loz =\n        static_cast<CGuiModel*>(x0_baseHud.FindWidget(fmt::format(\"{}loz{}\", modelName, menuItemOrder)));\n    item.x4_model_icon =\n        static_cast<CGuiModel*>(x0_baseHud.FindWidget(fmt::format(\"{}icon{}\", modelName, menuItemOrder)));\n    item.xc_opacity = enables[i] ? 1.f : 0.f;\n  }\n\n  if (x4_type == EHudVisorBeamMenu::Visor) {\n    x20_textpane_menu->TextSupport().SetFontColor(g_tweakGuiColors->GetVisorMenuTextFont());\n    x20_textpane_menu->TextSupport().SetOutlineColor(g_tweakGuiColors->GetVisorMenuTextOutline());\n  } else {\n    x20_textpane_menu->TextSupport().SetFontColor(g_tweakGuiColors->GetBeamMenuTextFont());\n    x20_textpane_menu->TextSupport().SetOutlineColor(g_tweakGuiColors->GetBeamMenuTextOutline());\n  }\n\n  zeus::CColor titleColor = zeus::skWhite;\n  titleColor.a() = 0.f;\n  x1c_basewidget_menutitle->SetColor(titleColor);\n\n  x20_textpane_menu->TextSupport().SetText(\n      g_MainStringTable->GetString(MenuStringIdx[size_t(x4_type)][x8_selectedItem]));\n\n  for (size_t i = 0; i < x28_menuItems.size(); ++i) {\n    SMenuItem& item = x28_menuItems[i];\n    item.x0_model_loz->SetColor(g_tweakGuiColors->GetVisorBeamMenuLozColor());\n    UpdateMenuWidgetTransform(i, *item.x0_model_loz, 1.f);\n  }\n\n  Update(0.f, true);\n}\n\nvoid CHudVisorBeamMenu::UpdateMenuWidgetTransform(size_t idx, CGuiWidget& w, float t) {\n  const float translate = t * g_tweakGui->GetVisorBeamMenuItemTranslate();\n  const float scale =\n      t * g_tweakGui->GetVisorBeamMenuItemInactiveScale() + (1.f - t) * g_tweakGui->GetVisorBeamMenuItemActiveScale();\n  if (x4_type == EHudVisorBeamMenu::Visor) {\n    if (idx == 2) {\n      idx = 3;\n    } else if (idx == 3) {\n      idx = 2;\n    }\n  } else {\n    if (idx == 1) {\n      idx = 2;\n    } else if (idx == 2) {\n      idx = 1;\n    }\n  }\n\n  switch (idx) {\n  case 0:\n    w.SetO2WTransform(x18_basewidget_menu->GetWorldTransform() * zeus::CTransform::Translate(0.f, 0.f, translate) *\n                      zeus::CTransform::Scale(scale));\n    break;\n  case 1:\n    w.SetO2WTransform(x18_basewidget_menu->GetWorldTransform() * zeus::CTransform::Translate(translate, 0.f, 0.f) *\n                      zeus::CTransform::Scale(scale));\n    break;\n  case 2:\n    w.SetO2WTransform(x18_basewidget_menu->GetWorldTransform() * zeus::CTransform::Translate(0.f, 0.f, -translate) *\n                      zeus::CTransform::Scale(scale));\n    break;\n  case 3:\n    w.SetO2WTransform(x18_basewidget_menu->GetWorldTransform() * zeus::CTransform::Translate(-translate, 0.f, 0.f) *\n                      zeus::CTransform::Scale(scale));\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CHudVisorBeamMenu::Update(float dt, bool init) {\n  bool curSwapBeamControls = g_GameState->GameOptions().GetSwapBeamControls();\n  if (x80_24_swapBeamControls != curSwapBeamControls) {\n    x80_24_swapBeamControls = curSwapBeamControls;\n    EHudVisorBeamMenu swappedType;\n    if (x80_24_swapBeamControls)\n      swappedType = EHudVisorBeamMenu(1 - int(x4_type));\n    else\n      swappedType = x4_type;\n\n    x18_basewidget_menu = x0_baseHud.FindWidget(BaseMenuNames[size_t(swappedType)]);\n    x20_textpane_menu = static_cast<CGuiTextPane*>(x0_baseHud.FindWidget(TextNames[size_t(swappedType)]));\n    x1c_basewidget_menutitle = x0_baseHud.FindWidget(BaseTitleNames[size_t(swappedType)]);\n\n    for (size_t i = 0; i < x28_menuItems.size(); ++i) {\n      SMenuItem& item = x28_menuItems[i];\n      UpdateMenuWidgetTransform(i, *item.x4_model_icon, item.x8_positioner);\n      UpdateMenuWidgetTransform(i, *item.x0_model_loz, 1.f);\n    }\n\n    UpdateMenuWidgetTransform(size_t(x8_selectedItem), *x24_model_ghost, x28_menuItems[x8_selectedItem].x8_positioner);\n  }\n\n  zeus::CColor activeColor = g_tweakGuiColors->GetVisorBeamMenuItemActive();\n  zeus::CColor inactiveColor = g_tweakGuiColors->GetVisorBeamMenuItemInactive();\n  zeus::CColor lozColor = g_tweakGuiColors->GetVisorBeamMenuLozColor();\n  std::array<zeus::CColor, 4> tmpColors;\n\n  for (size_t i = 0; i < x28_menuItems.size(); ++i) {\n    SMenuItem& item = x28_menuItems[i];\n    if (item.xc_opacity > 0.f) {\n      item.xc_opacity = std::min(item.xc_opacity + dt, 1.f);\n    }\n    tmpColors[i] = zeus::CColor::lerp(activeColor, zeus::skClear, item.xc_opacity);\n  }\n\n  switch (x6c_animPhase) {\n  case EAnimPhase::Steady:\n    for (size_t i = 0; i < x28_menuItems.size(); ++i) {\n      SMenuItem& item = x28_menuItems[i];\n\n      const bool isSelectedItem = x8_selectedItem == int(i);\n      const bool isClear = item.xc_opacity == 0.0f;\n\n      const zeus::CColor& color0 = isSelectedItem ? activeColor : inactiveColor;\n      const zeus::CColor& color1 = isSelectedItem ? lozColor : inactiveColor;\n      const zeus::CColor iconColor = isClear ? zeus::skClear : color0 + tmpColors[i];\n      const zeus::CColor lColor = isClear ? lozColor : color1 + tmpColors[i];\n\n      item.x4_model_icon->SetColor(iconColor);\n      item.x0_model_loz->SetColor(lColor);\n      item.x8_positioner = isSelectedItem ? 0.f : 1.f;\n    }\n    x24_model_ghost->SetColor(activeColor);\n    break;\n  case EAnimPhase::SelectFlash: {\n    zeus::CColor color = zeus::skWhite;\n    color.a() = 0.f;\n    x1c_basewidget_menutitle->SetColor(color);\n\n    zeus::CColor& color0 = std::fmod(x10_interp, 0.1f) > 0.05f ? activeColor : inactiveColor;\n    SMenuItem& item0 = x28_menuItems[xc_pendingSelection];\n    color = color0 + tmpColors[xc_pendingSelection];\n    item0.x4_model_icon->SetColor(color);\n    item0.x0_model_loz->SetColor(color);\n\n    SMenuItem& item1 = x28_menuItems[x8_selectedItem];\n    color = zeus::CColor::lerp(inactiveColor, activeColor, x10_interp) + tmpColors[x8_selectedItem];\n    item1.x4_model_icon->SetColor(color);\n    item1.x0_model_loz->SetColor(lozColor);\n\n    for (size_t i = 0; i < x28_menuItems.size(); ++i) {\n      const bool isSelectedItem = x8_selectedItem == int(i);\n      x28_menuItems[i].x8_positioner = isSelectedItem ? 1.f - x10_interp : 1.f;\n    }\n\n    x24_model_ghost->SetColor(zeus::CColor::lerp(activeColor, inactiveColor, item1.x8_positioner));\n    break;\n  }\n  case EAnimPhase::Animate:\n    for (size_t i = 0; i < x28_menuItems.size(); ++i) {\n      SMenuItem& item = x28_menuItems[i];\n      const bool isSelectedItem = x8_selectedItem == int(i);\n      const bool isClear = item.xc_opacity == 0.f;\n      const zeus::CColor& color0 = isSelectedItem ? activeColor : inactiveColor;\n      const zeus::CColor iconColor = isClear ? zeus::skClear : color0 + tmpColors[i];\n\n      item.x4_model_icon->SetColor(iconColor);\n      item.x0_model_loz->SetColor((isClear || isSelectedItem) ? lozColor : inactiveColor);\n      item.x8_positioner = isSelectedItem ? 1.f - x10_interp : 1.f;\n    }\n    x24_model_ghost->SetColor(\n        zeus::CColor::lerp(activeColor, inactiveColor, x28_menuItems[x8_selectedItem].x8_positioner));\n    break;\n  default:\n    break;\n  }\n\n  if (x78_textFader > 0.f) {\n    x78_textFader = std::max(0.f, x78_textFader - dt);\n    zeus::CColor color = zeus::skWhite;\n    color.a() = x78_textFader / x7c_animDur;\n    x1c_basewidget_menutitle->SetColor(color);\n  }\n\n  if (x14_26_dirty || init) {\n    x14_26_dirty = false;\n    for (size_t i = 0; i < x28_menuItems.size(); ++i) {\n      SMenuItem& item = x28_menuItems[i];\n      UpdateMenuWidgetTransform(i, *item.x4_model_icon, item.x8_positioner);\n    }\n    UpdateMenuWidgetTransform(size_t(x8_selectedItem), *x24_model_ghost, x28_menuItems[x8_selectedItem].x8_positioner);\n  }\n\n  if (!x14_24_visibleDebug || !x14_25_visibleGame)\n    return;\n\n  x1c_basewidget_menutitle->SetVisibility(x1c_basewidget_menutitle->GetGeometryColor().a() != 0.f,\n                                          ETraversalMode::Children);\n\n  for (SMenuItem& item : x28_menuItems) {\n    item.x4_model_icon->SetIsVisible(item.x4_model_icon->GetGeometryColor().a() != 0.f);\n  }\n}\n\nvoid CHudVisorBeamMenu::UpdateHudAlpha(float alpha) {\n  zeus::CColor color = zeus::skWhite;\n  color.a() = g_GameState->GameOptions().GetHUDAlpha() / 255.f * alpha;\n  x18_basewidget_menu->SetColor(color);\n}\n\nvoid CHudVisorBeamMenu::SetIsVisibleGame(bool v) {\n  x14_25_visibleGame = v;\n  bool vis = x14_24_visibleDebug && x14_25_visibleGame;\n  x18_basewidget_menu->SetVisibility(vis, ETraversalMode::Children);\n  if (vis)\n    Update(0.f, true);\n}\n\nvoid CHudVisorBeamMenu::SetPlayerHas(const rstl::reserved_vector<bool, 4>& enables) {\n  for (size_t i = 0; i < x28_menuItems.size(); ++i) {\n    SMenuItem& item = x28_menuItems[i];\n    if (item.xc_opacity == 0.f && enables[i]) {\n      item.xc_opacity = FLT_EPSILON;\n    }\n    // Metaforce addition\n    else if (!enables[i]) {\n      item.xc_opacity = 0.f;\n    }\n  }\n}\n\nvoid CHudVisorBeamMenu::SetSelection(int selection, int pending, float interp) {\n  if (x8_selectedItem == selection && xc_pendingSelection == pending && x10_interp == interp)\n    return;\n\n  if (pending != selection) {\n    if (x6c_animPhase != EAnimPhase::SelectFlash) {\n      CSfxManager::SfxStart(SelectionSfxs[size_t(x4_type)], 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n    }\n    x6c_animPhase = EAnimPhase::SelectFlash;\n  } else if (interp < 1.f) {\n    x6c_animPhase = EAnimPhase::Animate;\n    x20_textpane_menu->TextSupport().SetText(\n        g_MainStringTable->GetString(MenuStringIdx[size_t(x4_type)][x8_selectedItem]));\n    x20_textpane_menu->TextSupport().SetTypeWriteEffectOptions(true, 0.1f, 16.f);\n  } else {\n    if (x6c_animPhase != EAnimPhase::Steady)\n      x78_textFader = x7c_animDur;\n    x6c_animPhase = EAnimPhase::Steady;\n  }\n\n  x14_26_dirty = true;\n  x8_selectedItem = selection;\n  xc_pendingSelection = pending;\n  x10_interp = interp;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CHudVisorBeamMenu.hpp",
    "content": "#pragma once\n\n#include <cfloat>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n\nnamespace metaforce {\nclass CGuiFrame;\nclass CGuiModel;\nclass CGuiTextPane;\nclass CGuiWidget;\n\nclass CHudVisorBeamMenu {\npublic:\n  enum class EHudVisorBeamMenu { Visor, Beam };\n\nprivate:\n  struct SMenuItem {\n    CGuiModel* x0_model_loz = nullptr;\n    CGuiModel* x4_model_icon = nullptr;\n    float x8_positioner = 0.f;\n    float xc_opacity = 0.f;\n  };\n\n  enum class EAnimPhase { None, Steady, SelectFlash, Animate };\n\n  CGuiFrame& x0_baseHud;\n  EHudVisorBeamMenu x4_type;\n  int x8_selectedItem = 0;\n  int xc_pendingSelection = 0;\n  float x10_interp = 1.f;\n  bool x14_24_visibleDebug : 1 = true;\n  bool x14_25_visibleGame : 1 = true;\n  bool x14_26_dirty : 1 = true;\n  CGuiWidget* x18_basewidget_menu;\n  CGuiWidget* x1c_basewidget_menutitle;\n  CGuiTextPane* x20_textpane_menu;\n  CGuiModel* x24_model_ghost;\n  rstl::reserved_vector<SMenuItem, 4> x28_menuItems;\n  EAnimPhase x6c_animPhase = EAnimPhase::Steady;\n  float x70_ = FLT_EPSILON;\n  float x74_ = FLT_EPSILON;\n  float x78_textFader = 0.f;\n  float x7c_animDur;\n  bool x80_24_swapBeamControls : 1;\n\n  void UpdateMenuWidgetTransform(size_t idx, CGuiWidget& w, float t);\n\npublic:\n  CHudVisorBeamMenu(CGuiFrame& baseHud, EHudVisorBeamMenu type, const rstl::reserved_vector<bool, 4>& enables);\n  void Update(float dt, bool init);\n  void UpdateHudAlpha(float alpha);\n  void SetIsVisibleGame(bool v);\n  void SetPlayerHas(const rstl::reserved_vector<bool, 4>& enables);\n  void SetSelection(int selection, int pending, float interp);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CInstruction.cpp",
    "content": "#include \"Runtime/GuiSys/CInstruction.hpp\"\n\n#include \"Runtime/Graphics/CTexture.hpp\"\n#include \"Runtime/GuiSys/CFontRenderState.hpp\"\n#include \"Runtime/GuiSys/CRasterFont.hpp\"\n#include \"Runtime/GuiSys/CTextRenderBuffer.hpp\"\n\nnamespace metaforce {\n\nvoid CInstruction::PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const {}\n\nvoid CInstruction::GetAssets(std::vector<CToken>& assetsOut) const {}\n\nsize_t CInstruction::GetAssetCount() const { return 0; }\n\nvoid CColorInstruction::Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const {\n  state.SetColor(x4_cType, x8_color);\n}\n\nvoid CColorInstruction::PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const { Invoke(state, buf); }\n\nvoid CColorOverrideInstruction::Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const {\n  state.x64_colorOverrides[x4_overrideIdx] = true;\n  zeus::CColor convCol = state.ConvertToTextureSpace(x8_color);\n  state.x0_drawStrOpts.x4_colors[x4_overrideIdx] = convCol;\n}\n\nvoid CColorOverrideInstruction::PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const {\n  Invoke(state, buf);\n}\n\nvoid CFontInstruction::Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const {\n  buf->AddFontChange(x4_font);\n  state.x48_font = x4_font;\n  state.RefreshPalette();\n}\n\nvoid CFontInstruction::PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const { Invoke(state, buf); }\n\nvoid CFontInstruction::GetAssets(std::vector<CToken>& assetsOut) const { assetsOut.push_back(x4_font); }\n\nsize_t CFontInstruction::GetAssetCount() const { return 1; }\n\nvoid CLineExtraSpaceInstruction::Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const {\n  state.x78_extraLineSpace = x4_extraSpace;\n}\n\nvoid CLineExtraSpaceInstruction::PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const {\n  Invoke(state, buf);\n}\n\nvoid CLineInstruction::TestLargestFont(s32 w, s32 h, s32 b) {\n  if (!x18_largestMonoBaseline)\n    x18_largestMonoBaseline = b;\n\n  if (x14_largestMonoWidth < w)\n    x14_largestMonoWidth = w;\n\n  if (x10_largestMonoHeight < h) {\n    x10_largestMonoHeight = h;\n    x18_largestMonoBaseline = b;\n  }\n}\n\nvoid CLineInstruction::TestLargestImage(s32 w, s32 h, s32 b) {\n  if (!x24_largestImageBaseline)\n    x24_largestImageBaseline = b;\n\n  if (x20_largestImageWidth < w)\n    x20_largestImageWidth = w;\n\n  if (x1c_largestImageHeight < h) {\n    x1c_largestImageHeight = h;\n    x24_largestImageBaseline = b;\n  }\n}\n\nvoid CLineInstruction::InvokeLTR(CFontRenderState& state) const {\n  switch (x28_just) {\n  case EJustification::Left:\n  case EJustification::Full:\n  case EJustification::NLeft:\n  case EJustification::LeftMono:\n    state.xd4_curX = state.x88_curBlock->x4_offsetX;\n    break;\n  case EJustification::Center:\n  case EJustification::CenterMono:\n    state.xd4_curX = state.x88_curBlock->x4_offsetX + state.x88_curBlock->xc_blockExtentX / 2 - x8_curX / 2;\n    break;\n  case EJustification::NCenter:\n    if (x4_wordCount == 1) {\n      state.xd4_curX = state.x88_curBlock->x4_offsetX + state.x88_curBlock->xc_blockExtentX / 2 - x8_curX / 2;\n    } else {\n      state.xd4_curX =\n          state.x88_curBlock->x4_offsetX + state.x88_curBlock->xc_blockExtentX / 2 - state.x88_curBlock->x2c_lineX / 2;\n    }\n    break;\n  case EJustification::Right:\n  case EJustification::RightMono:\n    state.xd4_curX = state.x88_curBlock->x4_offsetX + state.x88_curBlock->xc_blockExtentX - x8_curX;\n    break;\n  case EJustification::NRight:\n    state.xd4_curX =\n        state.x88_curBlock->x4_offsetX + state.x88_curBlock->xc_blockExtentX - state.x88_curBlock->x2c_lineX;\n    break;\n  default:\n    break;\n  }\n\n  if (state.xdc_currentLineInst) {\n    const CLineInstruction& inst = *state.xdc_currentLineInst;\n    s32 val = 0;\n    switch (state.x88_curBlock->x1c_vertJustification) {\n    case EVerticalJustification::Top:\n    case EVerticalJustification::Center:\n    case EVerticalJustification::Bottom:\n    case EVerticalJustification::NTop:\n    case EVerticalJustification::NCenter:\n    case EVerticalJustification::NBottom:\n      val = inst.xc_curY;\n      break;\n    case EVerticalJustification::Full:\n      val = state.x88_curBlock->x10_blockExtentY - state.x88_curBlock->x30_lineY;\n      if (state.x88_curBlock->x34_lineCount > 1)\n        val /= state.x88_curBlock->x34_lineCount - 1;\n      else\n        val = 0;\n      val += inst.xc_curY;\n      break;\n    case EVerticalJustification::TopMono:\n      val = state.x88_curBlock->x24_largestMonoH;\n      break;\n    case EVerticalJustification::CenterMono:\n      val = (inst.xc_curY - state.x88_curBlock->x24_largestMonoH) / 2 + state.x88_curBlock->x24_largestMonoH;\n      break;\n    case EVerticalJustification::RightMono:\n      val = state.x88_curBlock->x24_largestMonoH * 2 - inst.xc_curY;\n      break;\n    }\n\n    if (state.x88_curBlock->x1c_vertJustification != EVerticalJustification::Full)\n      val = val * state.x74_lineSpacing + state.x78_extraLineSpace;\n\n    state.xd8_curY += val;\n  }\n}\n\nvoid CLineInstruction::Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const {\n  InvokeLTR(state);\n  state.x108_lineInitialized = true;\n  state.xdc_currentLineInst = this;\n}\n\nvoid CLineInstruction::PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const {\n  if (!state.xdc_currentLineInst)\n    Invoke(state, buf);\n}\n\nvoid CLineSpacingInstruction::Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const {\n  state.x74_lineSpacing = x4_lineSpacing;\n}\n\nvoid CLineSpacingInstruction::PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const { Invoke(state, buf); }\n\nvoid CPopStateInstruction::Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const {\n  const auto& oldFont = state.GetFont();\n  state.PopState();\n  if (oldFont.GetObj() != state.GetFont().GetObj()) {\n    buf->AddFontChange(state.GetFont());\n  }\n}\n\nvoid CPopStateInstruction::PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const { Invoke(state, buf); }\n\nvoid CPushStateInstruction::Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const { state.PushState(); }\n\nvoid CPushStateInstruction::PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const { Invoke(state, buf); }\n\nvoid CRemoveColorOverrideInstruction::Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const {\n  state.x64_colorOverrides[x4_idx] = false;\n}\n\nvoid CRemoveColorOverrideInstruction::PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const {\n  Invoke(state, buf);\n}\n\nvoid CImageInstruction::Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const {\n  if (x4_image.IsLoaded() && x4_image.x4_texs.size()) {\n    const CTexture* tex = x4_image.x4_texs[0].GetObj();\n    if (state.x88_curBlock->x14_dir == ETextDirection::Horizontal) {\n      if (buf) {\n        int y = state.xd8_curY + state.xdc_currentLineInst->GetBaseline() - x4_image.CalculateBaseline();\n        zeus::CVector2i coords(state.xd4_curX, y);\n        buf->AddImage(coords, x4_image);\n      }\n      state.xd4_curX = state.xd4_curX + tex->GetWidth() * x4_image.x14_cropFactor.x();\n    } else {\n      int scale = state.xdc_currentLineInst->x8_curX - tex->GetWidth() * x4_image.x14_cropFactor.x();\n      if (buf) {\n        zeus::CVector2i coords(scale / 2 + state.xd4_curX, state.xd8_curY);\n        buf->AddImage(coords, x4_image);\n      }\n      state.xd8_curY += x4_image.CalculateHeight();\n    }\n  }\n}\n\nvoid CImageInstruction::GetAssets(std::vector<CToken>& assetsOut) const {\n  for (const CToken& tok : x4_image.x4_texs)\n    assetsOut.push_back(tok);\n}\n\nsize_t CImageInstruction::GetAssetCount() const { return x4_image.x4_texs.size(); }\n\nvoid CTextInstruction::Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const {\n  int xOut, yOut;\n  if (state.x88_curBlock->x14_dir == ETextDirection::Horizontal) {\n    state.x48_font->DrawString(state.x0_drawStrOpts, state.xd4_curX,\n                               state.xdc_currentLineInst->GetBaseline() + state.xd8_curY, xOut, yOut, buf,\n                               x4_str.c_str(), x4_str.size());\n    state.xd4_curX = xOut;\n  } else {\n    int scale = state.xdc_currentLineInst->x8_curX - state.x48_font->GetMonoWidth();\n    state.x48_font->DrawString(state.x0_drawStrOpts, scale / 2 + state.xd4_curX, state.xd8_curY, xOut, yOut, buf,\n                               x4_str.c_str(), x4_str.size());\n    state.xd8_curY = yOut;\n  }\n}\n\nvoid CBlockInstruction::TestLargestFont(s32 monoW, s32 monoH, s32 baseline) {\n  if (!x28_largestBaseline)\n    x28_largestBaseline = baseline;\n\n  if (x20_largestMonoW < monoW)\n    x20_largestMonoW = monoW;\n\n  if (x24_largestMonoH < monoH) {\n    x24_largestMonoH = monoH;\n    x28_largestBaseline = baseline;\n  }\n}\n\nvoid CBlockInstruction::SetupPositionLTR(CFontRenderState& state) const {\n  switch (x1c_vertJustification) {\n  case EVerticalJustification::Top:\n  case EVerticalJustification::Full:\n  case EVerticalJustification::NTop:\n  case EVerticalJustification::TopMono:\n    state.xd8_curY = x8_offsetY;\n    break;\n  case EVerticalJustification::Center:\n  case EVerticalJustification::NCenter:\n    state.xd8_curY = x8_offsetY + (x10_blockExtentY - x30_lineY) / 2;\n    break;\n  case EVerticalJustification::CenterMono:\n    state.xd8_curY = x8_offsetY + (x10_blockExtentY - x34_lineCount * x24_largestMonoH) / 2;\n    break;\n  case EVerticalJustification::Bottom:\n  case EVerticalJustification::NBottom:\n    state.xd8_curY = x8_offsetY + x10_blockExtentY - x30_lineY;\n    break;\n  case EVerticalJustification::RightMono:\n    state.xd8_curY = x8_offsetY + x10_blockExtentY - x34_lineCount * x24_largestMonoH;\n    break;\n  }\n}\n\nvoid CBlockInstruction::Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const {\n  state.x0_drawStrOpts.x0_direction = x14_dir;\n  state.x88_curBlock = const_cast<CBlockInstruction*>(this);\n  if (x14_dir == ETextDirection::Horizontal)\n    SetupPositionLTR(state);\n}\n\nvoid CBlockInstruction::PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const { Invoke(state, buf); }\n\nvoid CWordInstruction::InvokeLTR(CFontRenderState& state) const {\n  CRasterFont* font = state.x48_font.GetObj();\n  char16_t space = u' ';\n  int w, h;\n  font->GetSize(state.x0_drawStrOpts, w, h, &space, 1);\n\n  const CLineInstruction& inst = *state.xdc_currentLineInst;\n  switch (state.x88_curBlock->x18_justification) {\n  case EJustification::Full:\n    w += (state.x88_curBlock->xc_blockExtentX - inst.x8_curX) / (inst.x4_wordCount - 1);\n    break;\n  case EJustification::NLeft:\n  case EJustification::NCenter:\n  case EJustification::NRight:\n    w += (state.x88_curBlock->x2c_lineX - inst.x8_curX) / (inst.x4_wordCount - 1);\n    break;\n  default:\n    break;\n  }\n\n  int wOut = state.xd4_curX;\n  font->DrawSpace(state.x0_drawStrOpts, wOut, inst.xc_curY - font->GetMonoHeight() + state.xd8_curY, wOut, h, w);\n  state.xd4_curX = wOut;\n}\n\nvoid CWordInstruction::Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const {\n  if (state.x108_lineInitialized) {\n    state.x108_lineInitialized = false;\n    return;\n  }\n\n  if (state.x0_drawStrOpts.x0_direction == ETextDirection::Horizontal)\n    InvokeLTR(state);\n}\n\nvoid CWordInstruction::PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const {\n  state.x108_lineInitialized = false;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CInstruction.hpp",
    "content": "#pragma once\n\n#include <vector>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/GuiSys/CFontImageDef.hpp\"\n#include \"Runtime/GuiSys/CGuiTextSupport.hpp\"\n\nnamespace metaforce {\nclass CFontImageDef;\nclass CFontRenderState;\nclass CTextRenderBuffer;\n\nclass CInstruction {\npublic:\n  virtual ~CInstruction() = default;\n  virtual void Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const = 0;\n  virtual void PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const;\n  virtual void GetAssets(std::vector<CToken>& assetsOut) const;\n  virtual size_t GetAssetCount() const;\n};\n\nclass CColorInstruction : public CInstruction {\n  EColorType x4_cType;\n  CTextColor x8_color;\n\npublic:\n  CColorInstruction(EColorType tp, const CTextColor& color) : x4_cType(tp), x8_color(color) {}\n  void Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const override;\n  void PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const override;\n};\n\nclass CColorOverrideInstruction : public CInstruction {\n  int x4_overrideIdx;\n  CTextColor x8_color;\n\npublic:\n  CColorOverrideInstruction(int idx, const CTextColor& color) : x4_overrideIdx(idx), x8_color(color) {}\n  void Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const override;\n  void PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const override;\n};\n\nclass CFontInstruction : public CInstruction {\n  TLockedToken<CRasterFont> x4_font;\n\npublic:\n  explicit CFontInstruction(const TToken<CRasterFont>& font) : x4_font(font) {}\n  void Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const override;\n  void PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const override;\n  void GetAssets(std::vector<CToken>& assetsOut) const override;\n  size_t GetAssetCount() const override;\n};\n\nclass CLineExtraSpaceInstruction : public CInstruction {\n  s32 x4_extraSpace;\n\npublic:\n  explicit CLineExtraSpaceInstruction(s32 extraSpace) : x4_extraSpace(extraSpace) {}\n  void Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const override;\n  void PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const override;\n};\n\nclass CLineInstruction : public CInstruction {\n  friend class CTextExecuteBuffer;\n  friend class CTextInstruction;\n  friend class CImageInstruction;\n  friend class CWordInstruction;\n\n  s32 x4_wordCount = 0;\n  s32 x8_curX = 0;\n  s32 xc_curY = 0;\n  s32 x10_largestMonoHeight = 0;\n  s32 x14_largestMonoWidth = 0;\n  s32 x18_largestMonoBaseline = 0;\n  s32 x1c_largestImageHeight = 0;\n  s32 x20_largestImageWidth = 0;\n  s32 x24_largestImageBaseline = 0;\n  EJustification x28_just;\n  EVerticalJustification x2c_vjust;\n  bool x30_imageBaseline;\n\npublic:\n  CLineInstruction(EJustification just, EVerticalJustification vjust, bool imageBaseline)\n  : x28_just(just), x2c_vjust(vjust), x30_imageBaseline(imageBaseline) {}\n  void TestLargestFont(s32 w, s32 h, s32 b);\n  void TestLargestImage(s32 w, s32 h, s32 b);\n  void InvokeLTR(CFontRenderState& state) const;\n  void Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const override;\n  void PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const override;\n\n  s32 GetHeight() const {\n    if (x10_largestMonoHeight && !x30_imageBaseline)\n      return x10_largestMonoHeight;\n    else\n      return x1c_largestImageHeight;\n  }\n\n  s32 GetBaseline() const {\n    if (x10_largestMonoHeight && !x30_imageBaseline)\n      return x18_largestMonoBaseline;\n    else\n      return x24_largestImageBaseline;\n  }\n};\n\nclass CLineSpacingInstruction : public CInstruction {\n  float x4_lineSpacing;\n\npublic:\n  explicit CLineSpacingInstruction(float spacing) : x4_lineSpacing(spacing) {}\n  void Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const override;\n  void PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const override;\n};\n\nclass CPopStateInstruction : public CInstruction {\npublic:\n  void Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const override;\n  void PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const override;\n};\n\nclass CPushStateInstruction : public CInstruction {\npublic:\n  void Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const override;\n  void PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const override;\n};\n\nclass CRemoveColorOverrideInstruction : public CInstruction {\n  int x4_idx;\n\npublic:\n  explicit CRemoveColorOverrideInstruction(int idx) : x4_idx(idx) {}\n  void Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const override;\n  void PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const override;\n};\n\nclass CImageInstruction : public CInstruction {\n  CFontImageDef x4_image;\n\npublic:\n  explicit CImageInstruction(const CFontImageDef& image) : x4_image(image) {}\n  void Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const override;\n  void GetAssets(std::vector<CToken>& assetsOut) const override;\n  size_t GetAssetCount() const override;\n};\n\nclass CTextInstruction : public CInstruction {\n  std::u16string x4_str; /* used to be a placement-new sized allocation */\npublic:\n  CTextInstruction(const char16_t* str, int len) : x4_str(str, len) {}\n  void Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const override;\n};\n\nclass CBlockInstruction : public CInstruction {\n  friend class CTextExecuteBuffer;\n  friend class CLineInstruction;\n  friend class CImageInstruction;\n  friend class CTextInstruction;\n  friend class CWordInstruction;\n\n  s32 x4_offsetX;\n  s32 x8_offsetY;\n  s32 xc_blockExtentX;\n  s32 x10_blockExtentY;\n  ETextDirection x14_dir;\n  EJustification x18_justification;\n  EVerticalJustification x1c_vertJustification;\n  s32 x20_largestMonoW = 0;\n  s32 x24_largestMonoH = 0;\n  s32 x28_largestBaseline = 0;\n  s32 x2c_lineX = 0;\n  s32 x30_lineY = 0;\n  s32 x34_lineCount = 0;\n\npublic:\n  CBlockInstruction(s32 offX, s32 offY, s32 extX, s32 extY, ETextDirection dir, EJustification just,\n                    EVerticalJustification vjust)\n  : x4_offsetX(offX)\n  , x8_offsetY(offY)\n  , xc_blockExtentX(extX)\n  , x10_blockExtentY(extY)\n  , x14_dir(dir)\n  , x18_justification(just)\n  , x1c_vertJustification(vjust) {}\n  void TestLargestFont(s32 monoW, s32 monoH, s32 baseline);\n  void SetupPositionLTR(CFontRenderState& state) const;\n  void Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const override;\n  void PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const override;\n};\n\nclass CWordInstruction : public CInstruction {\npublic:\n  void InvokeLTR(CFontRenderState& state) const;\n  void Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const override;\n  void PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CMakeLists.txt",
    "content": "set(GUISYS_SOURCES\n        CSplashScreen.hpp CSplashScreen.cpp\n        CGuiObject.hpp CGuiObject.cpp\n        CConsoleOutputWindow.hpp CConsoleOutputWindow.cpp\n        CAuiEnergyBarT01.hpp CAuiEnergyBarT01.cpp\n        CAuiImagePane.hpp CAuiImagePane.cpp\n        CAuiMeter.hpp CAuiMeter.cpp\n        CConsoleOutputWindow.hpp CConsoleOutputWindow.cpp\n        CErrorOutputWindow.hpp CErrorOutputWindow.cpp\n        CGuiCamera.hpp CGuiCamera.cpp\n        CGuiFrame.hpp CGuiFrame.cpp\n        CGuiLight.hpp CGuiLight.cpp\n        CGuiModel.hpp CGuiModel.cpp\n        CGuiSliderGroup.hpp CGuiSliderGroup.cpp\n        CGuiSys.hpp CGuiSys.cpp\n        CGuiTableGroup.hpp CGuiTableGroup.cpp\n        CGuiTextPane.hpp CGuiTextPane.cpp\n        CGuiTextSupport.hpp CGuiTextSupport.cpp\n        CGuiWidget.hpp CGuiWidget.cpp\n        CGuiWidgetDrawParms.hpp\n        CSplashScreen.hpp CSplashScreen.cpp\n        CGuiCompoundWidget.hpp CGuiCompoundWidget.cpp\n        CSaveableState.hpp CSaveableState.cpp\n        CDrawStringOptions.hpp\n        CRasterFont.hpp CRasterFont.cpp\n        CGuiGroup.hpp CGuiGroup.cpp\n        CGuiWidgetIdDB.hpp CGuiWidgetIdDB.cpp\n        CGuiHeadWidget.hpp CGuiHeadWidget.cpp\n        CGuiPane.hpp CGuiPane.cpp\n        CFontRenderState.hpp CFontRenderState.cpp\n        CTextExecuteBuffer.hpp CTextExecuteBuffer.cpp\n        CTextRenderBuffer.hpp CTextRenderBuffer.cpp\n        CInstruction.hpp CInstruction.cpp\n        CTextParser.hpp CTextParser.cpp\n        CWordBreakTables.hpp CWordBreakTables.cpp\n        CFontImageDef.hpp CFontImageDef.cpp\n        CStringTable.hpp CStringTable.cpp\n        CTargetingManager.hpp CTargetingManager.cpp\n        CCompoundTargetReticle.hpp CCompoundTargetReticle.cpp\n        COrbitPointMarker.hpp COrbitPointMarker.cpp\n        CHudEnergyInterface.hpp CHudEnergyInterface.cpp\n        CHudBossEnergyInterface.hpp CHudBossEnergyInterface.cpp\n        CHudThreatInterface.hpp CHudThreatInterface.cpp\n        CHudMissileInterface.hpp CHudMissileInterface.cpp\n        CHudFreeLookInterface.hpp CHudFreeLookInterface.cpp\n        CHudDecoInterface.hpp CHudDecoInterface.cpp\n        CHudHelmetInterface.hpp CHudHelmetInterface.cpp\n        CHudVisorBeamMenu.hpp CHudVisorBeamMenu.cpp\n        CHudRadarInterface.hpp CHudRadarInterface.cpp\n        CHudBallInterface.hpp CHudBallInterface.cpp\n        CHudInterface.hpp\n        CScanDisplay.hpp CScanDisplay.cpp)\n\nruntime_add_list(GuiSys GUISYS_SOURCES)\n"
  },
  {
    "path": "Runtime/GuiSys/COrbitPointMarker.cpp",
    "content": "#include \"Runtime/GuiSys/COrbitPointMarker.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Camera/CGameCamera.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n\n#include <zeus/CEulerAngles.hpp>\n\nnamespace metaforce {\n\nCOrbitPointMarker::COrbitPointMarker() {\n  x0_zOffset = g_tweakTargeting->GetOrbitPointZOffset();\n  x28_orbitPointModel = g_SimplePool->GetObj(\"CMDL_OrbitPoint\");\n}\n\nbool COrbitPointMarker::CheckLoadComplete() const { return x28_orbitPointModel.IsLoaded(); }\n\nvoid COrbitPointMarker::Update(float dt, const CStateManager& mgr) {\n  x24_curTime += dt;\n  const CGameCamera* curCam = mgr.GetCameraManager()->GetCurrentCamera(mgr);\n  CPlayer::EPlayerOrbitState orbitState = mgr.GetPlayer().GetOrbitState();\n  bool freeOrbit = orbitState >= CPlayer::EPlayerOrbitState::OrbitPoint;\n  if (freeOrbit != x1c_lastFreeOrbit) {\n    if (orbitState == CPlayer::EPlayerOrbitState::OrbitPoint ||\n        orbitState == CPlayer::EPlayerOrbitState::OrbitCarcass) {\n      ResetInterpolationTimer(g_tweakTargeting->GetOrbitPointInTime());\n      zeus::CVector3f orbitTargetPosition = mgr.GetPlayer().GetHUDOrbitTargetPosition();\n      if (!x4_camRelZPos)\n        x10_lagTargetPos = orbitTargetPosition + zeus::CVector3f(0.f, 0.f, x0_zOffset);\n      else\n        x10_lagTargetPos = zeus::CVector3f(orbitTargetPosition.x(), orbitTargetPosition.y(),\n                                           curCam->GetTranslation().z() + x0_zOffset);\n      x8_lagAzimuth = zeus::CEulerAngles(zeus::CQuaternion(curCam->GetTransform().basis)).z() + zeus::degToRad(45.f);\n    } else {\n      ResetInterpolationTimer(g_tweakTargeting->GetOrbitPointOutTime());\n    }\n    x1c_lastFreeOrbit = !x1c_lastFreeOrbit;\n  }\n\n  if (x20_interpTimer > 0.f)\n    x20_interpTimer = std::max(0.f, x20_interpTimer - dt);\n\n  if (!x4_camRelZPos) {\n    zeus::CVector3f orbitTargetPosition = mgr.GetPlayer().GetHUDOrbitTargetPosition();\n    if ((orbitTargetPosition.z() + x0_zOffset) - x10_lagTargetPos.z() < 0.1f)\n      x10_lagTargetPos = orbitTargetPosition + zeus::CVector3f(0.f, 0.f, x0_zOffset);\n    else if ((orbitTargetPosition.z() + x0_zOffset) - x10_lagTargetPos.z() < 0.f)\n      x10_lagTargetPos = zeus::CVector3f(orbitTargetPosition.x(), orbitTargetPosition.y(), x10_lagTargetPos.z() - 0.1f);\n    else\n      x10_lagTargetPos = zeus::CVector3f(orbitTargetPosition.x(), orbitTargetPosition.y(), x10_lagTargetPos.z() + 0.1f);\n  } else {\n    zeus::CVector3f orbitTargetPosition = mgr.GetPlayer().GetHUDOrbitTargetPosition();\n    x10_lagTargetPos =\n        zeus::CVector3f(orbitTargetPosition.x(), orbitTargetPosition.y(), x0_zOffset + orbitTargetPosition.z());\n  }\n\n  if (x1c_lastFreeOrbit) {\n    float newAzimuth = zeus::CEulerAngles(zeus::CQuaternion(curCam->GetTransform().basis)).z() + zeus::degToRad(45.f);\n    float aziDelta = newAzimuth - xc_azimuth;\n    if (mgr.GetPlayer().IsInFreeLook())\n      x8_lagAzimuth += aziDelta;\n    xc_azimuth = newAzimuth;\n  }\n}\n\nvoid COrbitPointMarker::Draw(const CStateManager& mgr) {\n  if ((x1c_lastFreeOrbit || x20_interpTimer > 0.f) && g_tweakTargeting->DrawOrbitPoint() &&\n      x28_orbitPointModel.IsLoaded()) {\n    SCOPED_GRAPHICS_DEBUG_GROUP(\"COrbitPointMarker::Draw\", zeus::skCyan);\n    const CGameCamera* curCam = mgr.GetCameraManager()->GetCurrentCamera(mgr);\n    zeus::CTransform camXf = mgr.GetCameraManager()->GetCurrentCameraTransform(mgr);\n    CGraphics::SetViewPointMatrix(camXf);\n    zeus::CFrustum frustum = mgr.SetupDrawFrustum(CGraphics::mViewport);\n    frustum.updatePlanes(\n        camXf, zeus::SProjPersp(zeus::degToRad(curCam->GetFov()), CGraphics::GetViewportAspect(), 1.f, 100.f));\n    g_Renderer->SetClippingPlanes(frustum);\n    g_Renderer->SetPerspective(curCam->GetFov(), CGraphics::GetViewportWidth(), CGraphics::GetViewportHeight(),\n                               curCam->GetNearClipDistance(), curCam->GetFarClipDistance());\n    float scale;\n    if (x1c_lastFreeOrbit)\n      scale = 1.f - x20_interpTimer / g_tweakTargeting->GetOrbitPointInTime();\n    else\n      scale = x20_interpTimer / g_tweakTargeting->GetOrbitPointOutTime();\n    zeus::CTransform modelXf = zeus::CTransform::RotateZ(x8_lagAzimuth);\n    modelXf.scaleBy(scale);\n    modelXf.origin += x10_lagTargetPos;\n    CGraphics::SetModelMatrix(modelXf);\n    zeus::CColor color = g_tweakTargeting->GetOrbitPointColor();\n    color.a() *= scale;\n    CModelFlags flags(7, 0, 0, color);\n    x28_orbitPointModel->Draw(flags);\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/COrbitPointMarker.hpp",
    "content": "#pragma once\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Graphics/CModel.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CStateManager;\nclass COrbitPointMarker {\n  float x0_zOffset;\n  bool x4_camRelZPos = true;\n  float x8_lagAzimuth = 0.f;\n  float xc_azimuth = 0.f;\n  zeus::CVector3f x10_lagTargetPos;\n  bool x1c_lastFreeOrbit = false;\n  float x20_interpTimer = 0.f;\n  float x24_curTime = 0.f;\n  TLockedToken<CModel> x28_orbitPointModel;\n  void ResetInterpolationTimer(float time) { x20_interpTimer = time; }\n\npublic:\n  COrbitPointMarker();\n  bool CheckLoadComplete() const;\n  void Update(float dt, const CStateManager& mgr);\n  void Draw(const CStateManager& mgr);\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CRasterFont.cpp",
    "content": "#include \"Runtime/GuiSys/CRasterFont.hpp\"\n\n#include <algorithm>\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/Graphics/CGX.hpp\"\n#include \"Runtime/Graphics/CTexture.hpp\"\n#include \"Runtime/GuiSys/CDrawStringOptions.hpp\"\n#include \"Runtime/GuiSys/CTextRenderBuffer.hpp\"\n\nnamespace metaforce {\nCRasterFont::CRasterFont(metaforce::CInputStream& in, metaforce::IObjectStore& store) {\n  u32 magic = 0;\n  in.Get(reinterpret_cast<u8*>(&magic), 4);\n  if (magic != SBIG('FONT'))\n    return;\n\n  u32 version = in.ReadLong();\n  x4_monoWidth = in.ReadLong();\n  x8_monoHeight = in.ReadLong();\n\n  if (version >= 1)\n    x8c_baseline = in.ReadLong();\n  else\n    x8c_baseline = x8_monoHeight;\n\n  if (version >= 2)\n    x90_lineMargin = in.ReadLong();\n\n  bool tmp1 = in.ReadBool();\n  bool tmp2 = in.ReadBool();\n\n  u32 tmp3 = in.ReadLong();\n  u32 tmp4 = in.ReadLong();\n  std::string name = in.Get<std::string>();\n  u32 txtrId = (version == 5 ? in.ReadLongLong() : in.ReadLong());\n  x30_fontInfo = CFontInfo(tmp1, tmp2, tmp3, tmp4, name.c_str());\n  x80_texture = store.GetObj({FOURCC('TXTR'), txtrId});\n  x2c_mode = CTexture::EFontType(in.ReadLong());\n\n  u32 glyphCount = in.ReadLong();\n  xc_glyphs.reserve(glyphCount);\n\n  for (u32 i = 0; i < glyphCount; ++i) {\n    char16_t chr = in.ReadShort();\n    float startU = in.ReadFloat();\n    float startV = in.ReadFloat();\n    float endU = in.ReadFloat();\n    float endV = in.ReadFloat();\n    s32 layer = 0;\n    s32 leftPadding, advance, rightPadding, cellWidth, cellHeight, baseline, kernStart;\n    if (version < 4) {\n      leftPadding = in.ReadInt32();\n      advance = in.ReadInt32();\n      rightPadding = in.ReadInt32();\n      cellWidth = in.ReadInt32();\n      cellHeight = in.ReadInt32();\n      baseline = in.ReadInt32();\n      kernStart = in.ReadInt32();\n    } else {\n      layer = in.ReadInt8();\n      leftPadding = in.ReadInt8();\n      advance = in.ReadInt8();\n      rightPadding = in.ReadInt8();\n      cellWidth = in.ReadInt8();\n      cellHeight = in.ReadInt8();\n      baseline = in.ReadInt8();\n      kernStart = in.ReadInt16();\n    }\n    xc_glyphs.emplace_back(chr, CGlyph(leftPadding, advance, rightPadding, startU, startV, endU, endV, cellWidth,\n                                       cellHeight, baseline, kernStart, layer));\n  }\n\n  std::sort(xc_glyphs.begin(), xc_glyphs.end(), [=](auto& a, auto& b) -> bool { return a.first < b.first; });\n\n  u32 kernCount = in.ReadLong();\n  x1c_kerning.reserve(kernCount);\n\n  for (u32 i = 0; i < kernCount; ++i) {\n    char16_t first = in.ReadShort();\n    char16_t second = in.ReadShort();\n    s32 howMuch = in.ReadInt32();\n    x1c_kerning.emplace_back(first, second, howMuch);\n  }\n\n  if (magic == SBIG('FONT') && version <= 4)\n    x0_initialized = true;\n}\n\nconst CGlyph* CRasterFont::InternalGetGlyph(char16_t chr) const {\n  const auto iter =\n      std::find_if(xc_glyphs.cbegin(), xc_glyphs.cend(), [chr](const auto& entry) { return entry.first == chr; });\n\n  if (iter == xc_glyphs.cend()) {\n    return nullptr;\n  }\n\n  return &iter->second;\n}\n\nvoid CRasterFont::SinglePassDrawString(const CDrawStringOptions& opts, int x, int y, int& xout, int& yout,\n                                       CTextRenderBuffer* renderBuf, const char16_t* str, s32 length) const {\n  if (!x0_initialized)\n    return;\n\n  const char16_t* chr = str;\n  const CGlyph* prevGlyph = nullptr;\n  while (*chr != u'\\0') {\n    const CGlyph* glyph = GetGlyph(*chr);\n    if (glyph) {\n      if (opts.x0_direction == ETextDirection::Horizontal) {\n        x += glyph->GetLeftPadding();\n\n        if (prevGlyph != nullptr) {\n          x += KernLookup(x1c_kerning, prevGlyph->GetKernStart(), *chr);\n        }\n\n        int left = 0;\n        int top = 0;\n\n        if (renderBuf) {\n          left += x;\n          top += y - glyph->GetBaseline();\n          renderBuf->AddCharacter(zeus::CVector2i(left, top), *chr, opts.x4_colors[2]);\n        }\n        x += glyph->GetRightPadding() + glyph->GetAdvance();\n      }\n    }\n    prevGlyph = glyph;\n    chr++;\n    if (length == -1)\n      continue;\n\n    if ((chr - str) >= length)\n      break;\n  }\n\n  xout = x;\n  yout = y;\n}\n\nvoid CRasterFont::DrawSpace(const CDrawStringOptions& opts, int x, int y, int& xout, int& yout, int len) const {\n  if (opts.x0_direction != ETextDirection::Horizontal)\n    return;\n\n  xout = x + len;\n  yout = y;\n}\n\nvoid CRasterFont::DrawString(const CDrawStringOptions& opts, int x, int y, int& xout, int& yout,\n                             CTextRenderBuffer* renderBuf, const char16_t* str, int len) const {\n  if (!x0_initialized)\n    return;\n\n  if (renderBuf != nullptr) {\n    CGraphicsPalette pal(EPaletteFormat::RGB5A3, 4);\n    u16* data = pal.Lock();\n    data[0] = bswap16(zeus::CColor(0.f, 0.f, 0.f, 0.f).toRGB5A3());\n    data[1] = bswap16(opts.x4_colors[0].toRGB5A3());\n    data[2] = bswap16(opts.x4_colors[1].toRGB5A3());\n    data[3] = bswap16(zeus::CColor(0.f, 0.f, 0.f, 0.f).toRGB5A3());\n    pal.UnLock();\n    renderBuf->AddPaletteChange(pal);\n  }\n\n  SinglePassDrawString(opts, x, y, xout, yout, renderBuf, str, len);\n}\n\nvoid CRasterFont::GetSize(const CDrawStringOptions& opts, int& width, int& height, const char16_t* str, int len) const {\n  width = 0;\n  height = 0;\n\n  const char16_t* chr = str;\n  const CGlyph* prevGlyph = nullptr;\n  int prevWidth = 0;\n  while (*chr != u'\\0') {\n    const CGlyph* glyph = GetGlyph(*chr);\n\n    if (glyph) {\n      if (opts.x0_direction == ETextDirection::Horizontal) {\n        int advance = 0;\n        if (prevGlyph)\n          advance = KernLookup(x1c_kerning, prevGlyph->GetKernStart(), *chr);\n\n        int curWidth = prevWidth + (glyph->GetLeftPadding() + glyph->GetAdvance() + glyph->GetRightPadding() + advance);\n        int curHeight = glyph->GetBaseline() - (x8_monoHeight + glyph->GetCellHeight());\n\n        width = curWidth;\n        prevWidth = curWidth;\n\n        if (curHeight > height)\n          height = curHeight;\n      }\n    }\n\n    prevGlyph = glyph;\n    chr++;\n    if (len == -1)\n      continue;\n\n    if ((chr - str) >= len)\n      break;\n  }\n}\n\nbool CRasterFont::IsFinishedLoading() const {\n  if (!x80_texture || !x80_texture.IsLoaded())\n    return false;\n  return true;\n}\n\nvoid CRasterFont::SetupRenderState() {\n  constexpr std::array skDescList = {\n      GXVtxDescList{GX_VA_POS, GX_DIRECT},\n      GXVtxDescList{GX_VA_TEX0, GX_DIRECT},\n      GXVtxDescList{GX_VA_NULL, GX_NONE},\n  };\n\n  x80_texture->Load(GX_TEXMAP0, EClampMode::Clamp);\n  CGX::SetTevKAlphaSel(GX_TEVSTAGE0, GX_TEV_KASEL_K0_A);\n  CGX::SetTevKColorSel(GX_TEVSTAGE0, GX_TEV_KCSEL_K0);\n  CGX::SetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_TEXC, GX_CC_KONST, GX_CC_ZERO);\n  CGX::SetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_TEXA, GX_CA_KONST, GX_CA_ZERO);\n  CGX::SetStandardTevColorAlphaOp(GX_TEVSTAGE0);\n  CGX::SetTevDirect(GX_TEVSTAGE0);\n  CGX::SetVtxDescv(skDescList.data());\n  CGX::SetNumChans(0);\n  CGX::SetNumTexGens(1);\n  CGX::SetNumTevStages(1);\n  CGX::SetNumIndStages(0);\n  CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL);\n  CGX::SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY, GX_FALSE, GX_PTIDENTITY);\n}\nstd::unique_ptr<IObj> FRasterFontFactory([[maybe_unused]] const SObjectTag& tag, CInputStream& in,\n                                         const CVParamTransfer& vparms, [[maybe_unused]] CObjectReference* selfRef) {\n  CSimplePool* sp = vparms.GetOwnedObj<CSimplePool*>();\n  return TToken<CRasterFont>::GetIObjObjectFor(std::make_unique<CRasterFont>(in, *sp));\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CRasterFont.hpp",
    "content": "#pragma once\n\n#include <cstring>\n#include <memory>\n#include <vector>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/Graphics/CTexture.hpp\"\n\n#include <zeus/CVector2i.hpp>\n\nnamespace metaforce {\nclass CDrawStringOptions;\nclass CTextRenderBuffer;\nclass IObjectStore;\n\nenum class EColorType { Main, Outline, Geometry, Foreground, Background };\n\n/* NOTE: Is this a good place for CGlyph and CKernPair? */\nclass CGlyph {\nprivate:\n  s16 x0_leftPadding;\n  s16 x2_advance;\n  s16 x4_rightPadding;\n  float x8_startU;\n  float xc_startV;\n  float x10_endU;\n  float x14_endV;\n  s16 x18_cellWidth;\n  s16 x1a_cellHeight;\n  s16 x1c_baseline;\n  s16 x1e_kernStart;\n  s16 m_layer;\n\npublic:\n  CGlyph() = default;\n  CGlyph(s16 leftPadding, s16 advance, s32 rightPadding, float startU, float startV, float endU, float endV, s16 cellWidth, s16 cellHeight,\n         s16 baseline, s16 kernStart, s16 layer = 0)\n  : x0_leftPadding(leftPadding)\n  , x2_advance(advance)\n  , x4_rightPadding(rightPadding)\n  , x8_startU(startU)\n  , xc_startV(startV)\n  , x10_endU(endU)\n  , x14_endV(endV)\n  , x18_cellWidth(cellWidth)\n  , x1a_cellHeight(cellHeight)\n  , x1c_baseline(baseline)\n  , x1e_kernStart(kernStart)\n  , m_layer(layer) {}\n\n  s16 GetLeftPadding() const { return x0_leftPadding; }\n  s16 GetAdvance() const { return x2_advance; }\n  s16 GetRightPadding() const { return x4_rightPadding; }\n  float GetStartU() const { return x8_startU; }\n  float GetStartV() const { return xc_startV; }\n  float GetEndU() const { return x10_endU; }\n  float GetEndV() const { return x14_endV; }\n  s16 GetCellWidth() const { return x18_cellWidth; }\n  s16 GetCellHeight() const { return x1a_cellHeight; }\n  s16 GetBaseline() const { return x1c_baseline; }\n  s16 GetKernStart() const { return x1e_kernStart; }\n  s16 GetLayer() const { return m_layer; }\n};\n\nclass CKernPair {\nprivate:\n  char16_t x0_first;\n  char16_t x2_second;\n  s32 x4_howMuch;\n\npublic:\n  CKernPair() = default;\n  CKernPair(char16_t first, char16_t second, s32 howMuch) : x0_first(first), x2_second(second), x4_howMuch(howMuch) {}\n\n  char16_t GetFirst() const { return x0_first; }\n  char16_t GetSecond() const { return x2_second; }\n  s32 GetHowMuch() const { return x4_howMuch; }\n};\n\nclass CFontInfo {\n  bool x0_ = false;\n  bool x1_ = false;\n  s32 x4_ = 0;\n  s32 x8_fontSize = 0;\n  char xc_name[64] = \"\";\n\npublic:\n  CFontInfo() = default;\n  CFontInfo(bool a, bool b, s32 c, s32 fontSize, const char* name) : x0_(a), x1_(b), x4_(c), x8_fontSize(fontSize) {\n    std::strcpy(xc_name, name);\n  }\n};\n\nclass CRasterFont {\n  bool x0_initialized = false;\n  s32 x4_monoWidth = 16;\n  s32 x8_monoHeight = 16;\n  std::vector<std::pair<char16_t, CGlyph>> xc_glyphs;\n  std::vector<CKernPair> x1c_kerning;\n  CTexture::EFontType x2c_mode = CTexture::EFontType::OneLayer;\n  CFontInfo x30_fontInfo;\n  TLockedToken<CTexture> x80_texture;\n  s32 x8c_baseline;\n  s32 x90_lineMargin = 0;\n\n  const CGlyph* InternalGetGlyph(char16_t chr) const;\n\npublic:\n  CRasterFont(CInputStream& in, IObjectStore& store);\n\n  s32 GetMonoWidth() const { return x4_monoWidth; }\n  s32 GetMonoHeight() const { return x8_monoHeight; }\n  EColorType GetMode() const {\n    switch (x2c_mode) {\n    case CTexture::EFontType::OneLayer:\n    case CTexture::EFontType::TwoLayers:\n    case CTexture::EFontType::FourLayers:\n      return EColorType::Main;\n    case CTexture::EFontType::OneLayerOutline:\n    case CTexture::EFontType::TwoLayersOutlines:\n    case CTexture::EFontType::TwoLayersOutlines2:\n      return EColorType::Outline;\n    default:\n      return EColorType::Main;\n    }\n  }\n  s32 GetLineMargin() const { return x90_lineMargin; }\n  s32 GetCarriageAdvance() const { return GetLineMargin() + GetMonoHeight(); }\n\n  s32 GetBaseline() const { return x8c_baseline; }\n  static s32 KernLookup(const std::vector<CKernPair>& kernTable, s32 kernStart, char16_t chr) {\n    auto iter = kernTable.cbegin() + kernStart;\n    for (; iter != kernTable.cend() && iter->GetFirst() == kernTable[kernStart].GetFirst(); ++iter) {\n      if (iter->GetSecond() == chr)\n        return iter->GetHowMuch();\n    }\n\n    return 0;\n  }\n\n  void SinglePassDrawString(const CDrawStringOptions&, int x, int y, int& xout, int& yout, CTextRenderBuffer* renderBuf,\n                            const char16_t* str, s32 len) const;\n  void DrawSpace(const CDrawStringOptions& opts, int x, int y, int& xout, int& yout, int len) const;\n  void DrawString(const CDrawStringOptions& opts, int x, int y, int& xout, int& yout, CTextRenderBuffer* renderBuf,\n                  const char16_t* str, int len) const;\n  const CGlyph* GetGlyph(char16_t chr) const { return InternalGetGlyph(chr); }\n  void GetSize(const CDrawStringOptions& opts, int& width, int& height, const char16_t* str, int len) const;\n  CTexture& GetTexture() { return *x80_texture; }\n\n  bool IsFinishedLoading() const;\n\n  void SetupRenderState();\n};\n\nstd::unique_ptr<IObj> FRasterFontFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& vparms,\n                                         CObjectReference* selfRef);\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CSaveableState.cpp",
    "content": "#include \"Runtime/GuiSys/CSaveableState.hpp\"\n\n#include \"Runtime/GuiSys/CRasterFont.hpp\"\n\nnamespace metaforce {\n\nbool CSaveableState::IsFinishedLoading() const {\n  if (!x48_font || !x48_font.IsLoaded())\n    return false;\n  return x48_font->IsFinishedLoading();\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CSaveableState.hpp",
    "content": "#pragma once\n\n#include <vector>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/GuiSys/CDrawStringOptions.hpp\"\n#include \"Runtime/GuiSys/CGuiTextSupport.hpp\"\n#include \"Runtime/GuiSys/CRasterFont.hpp\"\n\n#include <zeus/CColor.hpp>\n\nnamespace metaforce {\nclass CSaveableState {\n  friend class CColorOverrideInstruction;\n  friend class CFontInstruction;\n  friend class CGuiTextSupport;\n  friend class CLineExtraSpaceInstruction;\n  friend class CLineSpacingInstruction;\n  friend class CRemoveColorOverrideInstruction;\n  friend class CTextExecuteBuffer;\n  friend class CTextInstruction;\n  friend class CWordInstruction;\n\nprotected:\n  CDrawStringOptions x0_drawStrOpts;\n  TLockedToken<CRasterFont> x48_font;\n  std::vector<CTextColor> x54_colors;\n  std::vector<bool> x64_colorOverrides;\n  float x74_lineSpacing = 1.f;\n  s32 x78_extraLineSpace = 0;\n  bool x7c_enableWordWrap = false;\n  EJustification x80_just = EJustification::Left;\n  EVerticalJustification x84_vjust = EVerticalJustification::Top;\n\npublic:\n  CSaveableState() : x54_colors(3, zeus::skBlack), x64_colorOverrides(16) {}\n  const TLockedToken<CRasterFont>& GetFont() const { return x48_font; }\n\n  bool IsFinishedLoading() const;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CScanDisplay.cpp",
    "content": "#include \"Runtime/GuiSys/CScanDisplay.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Audio/CSfxManager.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Graphics/CGraphics.hpp\"\n#include \"Runtime/GuiSys/CAuiImagePane.hpp\"\n#include \"Runtime/GuiSys/CGuiCamera.hpp\"\n#include \"Runtime/GuiSys/CGuiModel.hpp\"\n#include \"Runtime/GuiSys/CGuiTextPane.hpp\"\n#include \"Runtime/GuiSys/CGuiWidget.hpp\"\n#include \"Runtime/GuiSys/CStringTable.hpp\"\n#include \"Runtime/Input/CFinalInput.hpp\"\n#include \"Runtime/MP1/CPauseScreenBase.hpp\"\n\n#include <zeus/CTransform.hpp>\n\nnamespace metaforce {\nvoid CScanDisplay::CDataDot::Update(float dt) {\n  if (x20_remTime > 0.f) {\n    x20_remTime = std::max(0.f, x20_remTime - dt);\n    float d = 0.f;\n    if (x1c_transDur > 0.f)\n      d = x20_remTime / x1c_transDur;\n    xc_curPos = zeus::CVector2f::lerp(x14_targetPos, x4_startPos, d);\n  }\n\n  if (x24_alpha > x28_desiredAlpha) {\n    float tmp = x24_alpha - 2.f * dt;\n    x24_alpha = std::max(tmp, x28_desiredAlpha);\n  } else if (x24_alpha < x28_desiredAlpha) {\n    float tmp = 2.f * dt + x24_alpha;\n    x24_alpha = std::min(tmp, x28_desiredAlpha);\n  }\n}\n\nvoid CScanDisplay::CDataDot::Draw(const zeus::CColor& col, float radius) {\n  if (x24_alpha == 0.f || x0_dotState == EDotState::Hidden) {\n    return;\n  }\n\n  const zeus::CTransform xf = zeus::CTransform::Translate(xc_curPos.x(), 0.f, xc_curPos.y());\n  g_Renderer->SetModelMatrix(xf);\n  CGraphics::StreamBegin(ERglPrimitive::TriangleStrip);\n  zeus::CColor useColor = col;\n  useColor.a() *= x24_alpha;\n  CGraphics::StreamColor(useColor);\n  CGraphics::StreamTexcoord(0.f, 1.f);\n  CGraphics::StreamVertex(-radius, 0.f, radius);\n  CGraphics::StreamTexcoord(0.f, 0.f);\n  CGraphics::StreamVertex(-radius, 0.f, -radius);\n  CGraphics::StreamTexcoord(1.f, 1.f);\n  CGraphics::StreamVertex(radius, 0.f, radius);\n  CGraphics::StreamTexcoord(1.f, 0.f);\n  CGraphics::StreamVertex(radius, 0.f, -radius);\n  CGraphics::StreamEnd();\n}\n\nvoid CScanDisplay::CDataDot::StartTransitionTo(const zeus::CVector2f& vec, float dur) {\n  x20_remTime = dur;\n  x1c_transDur = dur;\n  x4_startPos = xc_curPos;\n  x14_targetPos = vec;\n}\n\nvoid CScanDisplay::CDataDot::SetDestPosition(const zeus::CVector2f& pos) {\n  if (x20_remTime <= 0.f)\n    xc_curPos = pos;\n  else\n    x14_targetPos = pos;\n}\n\nCScanDisplay::CScanDisplay(const CGuiFrame& selHud) : xa0_selHud(selHud) {\n  x0_dataDot = g_SimplePool->GetObj(\"TXTR_DataDot\");\n  for (size_t i = 0; i < xbc_dataDots.capacity(); ++i) {\n    xbc_dataDots.emplace_back(x0_dataDot);\n  }\n  x170_paneStates.resize(x170_paneStates.capacity());\n}\n\nvoid CScanDisplay::ProcessInput(const CFinalInput& input) {\n  if (xc_state == EScanState::Inactive || xc_state == EScanState::Done)\n    return;\n\n  if (xc_state == EScanState::DownloadComplete && x1a4_xAlpha == 0.f) {\n    if (input.PA() || input.PSpecialKey(ESpecialKey::Enter) || input.PMouseButton(EMouseButton::Primary)) {\n      if (xa8_message->TextSupport().GetCurTime() < xa8_message->TextSupport().GetTotalAnimationTime()) {\n        xa8_message->TextSupport().SetCurTime(xa8_message->TextSupport().GetTotalAnimationTime());\n      } else {\n        xc_state = EScanState::ViewingScan;\n        x1a4_xAlpha = 1.f;\n        CSfxManager::SfxStart(SFXui_scan_next_page, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n      }\n    }\n  } else if (xc_state == EScanState::ViewingScan) {\n    int oldCounter = x1ac_pageCounter;\n    int totalPages = xac_scrollMessage->TextSupport().GetTotalPageCount();\n    if ((input.PA() || input.PSpecialKey(ESpecialKey::Enter) || input.PMouseButton(EMouseButton::Primary)) &&\n        totalPages != -1) {\n      CGuiTextSupport& supp = !x1ac_pageCounter ? xa8_message->TextSupport() : xac_scrollMessage->TextSupport();\n      if (supp.GetCurTime() < supp.GetTotalAnimationTime())\n        supp.SetCurTime(supp.GetTotalAnimationTime());\n      else\n        x1ac_pageCounter = std::min(totalPages, x1ac_pageCounter + 1);\n    }\n    if (x1ac_pageCounter != oldCounter) {\n      CSfxManager::SfxStart(SFXui_scan_next_page, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n      if (x1ac_pageCounter == 0) {\n        xa8_message->SetIsVisible(true);\n        xac_scrollMessage->SetIsVisible(false);\n      } else {\n        if (oldCounter == 0) {\n          xa8_message->SetIsVisible(false);\n          xac_scrollMessage->SetIsVisible(true);\n        }\n        xac_scrollMessage->TextSupport().SetPage(x1ac_pageCounter - 1);\n        SetScanMessageTypeEffect(xac_scrollMessage, !x1b4_scanComplete);\n      }\n    }\n  }\n\n  float xAlpha = 0.f;\n  float aAlpha = 0.f;\n  float dashAlpha = 0.f;\n  if (xc_state == EScanState::DownloadComplete) {\n    xAlpha = std::min(2.f * x1a4_xAlpha, 1.f);\n    aAlpha = (1.f - xAlpha) * std::fabs(x1b0_aPulse);\n  } else if (xc_state == EScanState::ViewingScan) {\n    if (x1ac_pageCounter < xac_scrollMessage->TextSupport().GetTotalPageCount())\n      aAlpha = std::fabs(x1b0_aPulse);\n    else\n      dashAlpha = 1.f;\n  }\n\n  xb0_xmark->SetVisibility(xAlpha > 0.f, ETraversalMode::Children);\n  xb4_abutton->SetVisibility(aAlpha > 0.f, ETraversalMode::Children);\n  xb8_dash->SetVisibility(dashAlpha > 0.f, ETraversalMode::Children);\n\n  xb0_xmark->SetColor(zeus::CColor(1.f, xAlpha));\n  xb4_abutton->SetColor(zeus::CColor(1.f, aAlpha));\n  xb8_dash->SetColor(zeus::CColor(0.53f, 0.84f, 1.f, dashAlpha));\n}\n\nfloat CScanDisplay::GetDownloadStartTime(size_t idx) const {\n  if (!x14_scannableInfo) {\n    return 0.f;\n  }\n\n  const float nTime = x14_scannableInfo->GetBucket(idx).x4_appearanceRange;\n  float maxTime = 0.f;\n  for (size_t i = 0; i < CScannableObjectInfo::NumBuckets; ++i) {\n    const float iTime = x14_scannableInfo->GetBucket(i).x4_appearanceRange;\n    if (iTime < nTime) {\n      maxTime = std::max(iTime, maxTime);\n    }\n  }\n\n  return maxTime + g_tweakGui->GetScanAppearanceDuration();\n}\n\nfloat CScanDisplay::GetDownloadFraction(size_t idx, float scanningTime) const {\n  if (!x14_scannableInfo) {\n    return 0.f;\n  }\n\n  const float appearTime = GetDownloadStartTime(idx);\n  const float appearRange = x14_scannableInfo->GetBucket(idx).x4_appearanceRange;\n  if (appearTime == appearRange) {\n    return 1.f;\n  }\n\n  return zeus::clamp(0.f, (scanningTime - appearTime) / (appearRange - appearTime), 1.f);\n}\n\nvoid CScanDisplay::StartScan(TUniqueId id, const CScannableObjectInfo& scanInfo, CGuiTextPane* message,\n                             CGuiTextPane* scrollMessage, CGuiWidget* textGroup, CGuiModel* xmark, CGuiModel* abutton,\n                             CGuiModel* dash, float scanTime) {\n  x1b4_scanComplete = scanTime >= scanInfo.GetTotalDownloadTime();\n  x14_scannableInfo.emplace(scanInfo);\n  x10_objId = id;\n  xc_state = EScanState::Downloading;\n  x1ac_pageCounter = 0;\n  x1a4_xAlpha = 0.f;\n  xa8_message = message;\n  xac_scrollMessage = scrollMessage;\n  xa4_textGroup = textGroup;\n  xb0_xmark = xmark;\n  xb4_abutton = abutton;\n  xb8_dash = dash;\n  xa4_textGroup->SetVisibility(true, ETraversalMode::Children);\n  xa4_textGroup->SetColor(zeus::CColor(1.f, 0.f));\n  xa8_message->TextSupport().SetText(u\"\");\n  xac_scrollMessage->TextSupport().SetText(u\"\");\n\n  for (size_t i = 0; i < 20; ++i) {\n    auto* pane = static_cast<CAuiImagePane*>(xa0_selHud.FindWidget(MP1::CPauseScreenBase::GetImagePaneName(i)));\n    zeus::CColor color = g_tweakGuiColors->GetScanDisplayImagePaneColor();\n    color.a() = 0.f;\n    pane->SetColor(color);\n    pane->SetTextureID0({}, g_SimplePool);\n    pane->SetAnimationParms(zeus::skZero2f, 0.f, 0.f);\n    size_t pos = SIZE_MAX;\n    for (size_t j = 0; j < CScannableObjectInfo::NumBuckets; ++j) {\n      if (x14_scannableInfo->GetBucket(j).x8_imagePos == i) {\n        pos = j;\n        break;\n      }\n    }\n    if (pos != SIZE_MAX) {\n      x170_paneStates[pos].second = pane;\n    }\n  }\n\n  for (size_t i = 0; i < x170_paneStates.size(); ++i) {\n    std::pair<float, CAuiImagePane*>& state = x170_paneStates[i];\n    if (state.second) {\n      const CScannableObjectInfo::SBucket& bucket = x14_scannableInfo->GetBucket(i);\n      if (bucket.x14_interval > 0.f) {\n        state.second->SetAnimationParms(zeus::CVector2f(bucket.xc_size.x, bucket.xc_size.y), bucket.x14_interval,\n                                        bucket.x18_fadeDuration);\n      }\n      state.second->SetTextureID0(bucket.x0_texture, g_SimplePool);\n      state.second->SetFlashFactor(0.f);\n\n      const float startTime = GetDownloadStartTime(i);\n      if (scanTime >= startTime) {\n        x170_paneStates[i].first = 0.f;\n      } else {\n        x170_paneStates[i].first = -1.f;\n      }\n    }\n  }\n\n  const CAssetId strId = x14_scannableInfo->GetStringTableId();\n  if (strId.IsValid()) {\n    x194_scanStr = g_SimplePool->GetObj({FOURCC('STRG'), strId});\n  }\n\n  for (size_t i = 0; i < CScannableObjectInfo::NumBuckets; ++i) {\n    const u32 pos = x14_scannableInfo->GetBucket(i).x8_imagePos;\n    CDataDot& dot = xbc_dataDots[i];\n    if (pos != UINT32_MAX) {\n      if (GetDownloadStartTime(i) - g_tweakGui->GetScanAppearanceDuration() < scanTime) {\n        dot.SetAlpha(0.f);\n        dot.SetDotState(CDataDot::EDotState::Done);\n      } else {\n        dot.SetDesiredAlpha(1.f);\n        dot.SetDotState(CDataDot::EDotState::Seek);\n        dot.StartTransitionTo(zeus::skZero2f, FLT_EPSILON);\n        dot.Update(FLT_EPSILON);\n      }\n    } else {\n      dot.SetDotState(CDataDot::EDotState::Hidden);\n    }\n  }\n}\n\nvoid CScanDisplay::StopScan() {\n  if (xc_state == EScanState::Done || xc_state == EScanState::Inactive) {\n    return;\n  }\n\n  xc_state = EScanState::Done;\n  for (auto& dataDot : xbc_dataDots) {\n    dataDot.SetDesiredAlpha(0.f);\n  }\n}\n\nvoid CScanDisplay::SetScanMessageTypeEffect(CGuiTextPane* pane, bool type) {\n  if (type)\n    pane->TextSupport().SetTypeWriteEffectOptions(true, 0.1f, 60.f);\n  else\n    pane->TextSupport().SetTypeWriteEffectOptions(false, 0.f, 0.f);\n}\n\nvoid CScanDisplay::Update(float dt, float scanningTime) {\n  if (xc_state == EScanState::Inactive)\n    return;\n\n  bool active = false;\n  if (xc_state == EScanState::Done) {\n    x1a8_bodyAlpha = std::max(0.f, x1a8_bodyAlpha - 2.f * dt);\n    if (x1a8_bodyAlpha > 0.f)\n      active = true;\n  } else {\n    active = true;\n    x1a8_bodyAlpha = std::min(x1a8_bodyAlpha + 2.f * dt, 1.f);\n    if (xc_state == EScanState::DownloadComplete) {\n      if (xac_scrollMessage->TextSupport().GetIsTextSupportFinishedLoading())\n        x1a4_xAlpha = std::max(0.f, x1a4_xAlpha - dt);\n      if (x1a4_xAlpha < 0.5f) {\n        x1b0_aPulse += 2.f * dt;\n        if (x1b0_aPulse > 1.f)\n          x1b0_aPulse -= 2.f;\n      }\n    } else if (xc_state == EScanState::ViewingScan) {\n      x1b0_aPulse += 2.f * dt;\n      if (x1b0_aPulse > 1.f)\n        x1b0_aPulse -= 2.f;\n      if (x1a4_xAlpha == 1.f) {\n        xa8_message->TextSupport().SetText(x194_scanStr->GetString(0));\n        SetScanMessageTypeEffect(xa8_message, !x1b4_scanComplete);\n      }\n    } else if (xc_state == EScanState::Downloading && scanningTime >= x14_scannableInfo->GetTotalDownloadTime() &&\n               x194_scanStr.IsLoaded()) {\n      if (x1b4_scanComplete || x14_scannableInfo->GetCategory() == 0) {\n        xc_state = EScanState::ViewingScan;\n        x1b0_aPulse = x1a4_xAlpha = 1.f;\n        CSfxManager::SfxStart(SFXui_scan_complete, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n      } else {\n        xc_state = EScanState::DownloadComplete;\n        x1b0_aPulse = x1a4_xAlpha = 1.f;\n        xa8_message->TextSupport().SetText(std::u16string(g_MainStringTable->GetString(29)) +\n                                           g_MainStringTable->GetString(x14_scannableInfo->GetCategory() + 30) +\n                                           g_MainStringTable->GetString(30));\n        SetScanMessageTypeEffect(xa8_message, true);\n        CSfxManager::SfxStart(SFXui_new_scan_complete, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n      }\n\n      if (x194_scanStr->GetStringCount() > 2) {\n        xac_scrollMessage->TextSupport().SetText(x194_scanStr->GetString(2), true);\n        SetScanMessageTypeEffect(xac_scrollMessage, !x1b4_scanComplete);\n      }\n      xac_scrollMessage->SetIsVisible(false);\n    }\n  }\n\n  for (size_t i = 0; i < x170_paneStates.size(); ++i) {\n    if (x170_paneStates[i].second == nullptr) {\n      continue;\n    }\n\n    if (x170_paneStates[i].first > 0.f) {\n      x170_paneStates[i].first = std::max(0.f, x170_paneStates[i].first - dt);\n      float tmp;\n      if (x170_paneStates[i].first > g_tweakGui->GetScanPaneFadeOutTime()) {\n        tmp = 1.f -\n              (x170_paneStates[i].first - g_tweakGui->GetScanPaneFadeOutTime()) / g_tweakGui->GetScanPaneFadeInTime();\n      } else {\n        tmp = x170_paneStates[i].first / g_tweakGui->GetScanPaneFadeOutTime();\n      }\n      x170_paneStates[i].second->SetFlashFactor(tmp * g_tweakGui->GetScanPaneFlashFactor() * x1a8_bodyAlpha);\n    }\n\n    const float alphaMul =\n        ((xc_state == EScanState::Downloading) ? GetDownloadFraction(i, scanningTime) : 1.f) * x1a8_bodyAlpha;\n    zeus::CColor color = g_tweakGuiColors->GetScanDisplayImagePaneColor();\n    color.a() *= alphaMul;\n    x170_paneStates[i].second->SetColor(color);\n    x170_paneStates[i].second->SetDeResFactor(1.f - alphaMul);\n\n    if (GetDownloadStartTime(i) - g_tweakGui->GetScanAppearanceDuration() < scanningTime) {\n      CDataDot& dot = xbc_dataDots[i];\n      switch (dot.GetDotState()) {\n      case CDataDot::EDotState::Seek:\n      case CDataDot::EDotState::Hold:\n        dot.SetDotState(CDataDot::EDotState::RevealPane);\n        dot.StartTransitionTo(zeus::skZero2f, g_tweakGui->GetScanAppearanceDuration());\n        break;\n      case CDataDot::EDotState::RevealPane: {\n        const float tmp = dot.GetTransitionFactor();\n        if (tmp == 0.f) {\n          dot.SetDotState(CDataDot::EDotState::Done);\n          dot.SetDesiredAlpha(0.f);\n          CSfxManager::SfxStart(SFXui_scan_pane_reveal, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n          x170_paneStates[i].first = g_tweakGui->GetScanPaneFadeOutTime() + g_tweakGui->GetScanPaneFadeInTime();\n        }\n        break;\n      }\n      default:\n        break;\n      }\n    }\n  }\n\n  for (size_t i = 0; i < xbc_dataDots.size(); ++i) {\n    CDataDot& dot = xbc_dataDots[i];\n    switch (dot.GetDotState()) {\n    case CDataDot::EDotState::Hidden:\n      continue;\n    case CDataDot::EDotState::Seek:\n    case CDataDot::EDotState::Hold: {\n      float tmp = dot.GetTransitionFactor();\n      if (tmp == 0.f) {\n        float vpRatio = CGraphics::GetViewportHeight() / 480.f;\n        float posRand = g_tweakGui->GetScanDataDotPosRandMagnitude() * vpRatio;\n        float durMin = dot.GetDotState() == CDataDot::EDotState::Hold ? g_tweakGui->GetScanDataDotHoldDurationMin()\n                                                                      : g_tweakGui->GetScanDataDotSeekDurationMin();\n        float durMax = dot.GetDotState() == CDataDot::EDotState::Hold ? g_tweakGui->GetScanDataDotHoldDurationMax()\n                                                                      : g_tweakGui->GetScanDataDotSeekDurationMax();\n        zeus::CVector2f vec(\n            dot.GetDotState() == CDataDot::EDotState::Hold ? dot.GetCurrPosition().x()\n                                                           : (posRand * (rand() / float(RAND_MAX)) - 0.5f * posRand),\n            dot.GetDotState() == CDataDot::EDotState::Hold ? dot.GetCurrPosition().y()\n                                                           : (posRand * (rand() / float(RAND_MAX)) - 0.5f * posRand));\n        float dur = (durMax - durMin) * (rand() / float(RAND_MAX)) + durMin;\n        dot.StartTransitionTo(vec, dur);\n        dot.SetDotState(dot.GetDotState() == CDataDot::EDotState::Hold ? CDataDot::EDotState::Seek\n                                                                       : CDataDot::EDotState::Hold);\n      }\n      break;\n    }\n    case CDataDot::EDotState::RevealPane:\n    case CDataDot::EDotState::Done: {\n      const zeus::CVector3f& panePos = x170_paneStates[i].second->GetWorldPosition();\n      zeus::CVector3f screenPos = xa0_selHud.GetFrameCamera()->ConvertToScreenSpace(panePos);\n      zeus::CVector2f viewportCoords(screenPos.x() * CGraphics::GetViewportWidth() * 0.5f,\n                                     screenPos.y() * CGraphics::GetViewportHeight() * 0.5f);\n      dot.SetDestPosition(viewportCoords);\n      break;\n    }\n    default:\n      break;\n    }\n    dot.Update(dt);\n  }\n\n  if (!active) {\n    xc_state = EScanState::Inactive;\n    x10_objId = kInvalidUniqueId;\n    x14_scannableInfo = std::nullopt;\n    xa8_message->TextSupport().SetText(u\"\");\n    xac_scrollMessage->TextSupport().SetText(u\"\");\n    xa4_textGroup->SetVisibility(false, ETraversalMode::Children);\n    xb0_xmark->SetVisibility(false, ETraversalMode::Children);\n    xb4_abutton->SetVisibility(false, ETraversalMode::Children);\n    xb8_dash->SetVisibility(false, ETraversalMode::Children);\n    xa8_message = nullptr;\n    xac_scrollMessage = nullptr;\n    xa4_textGroup = nullptr;\n    xb0_xmark = nullptr;\n    xb4_abutton = nullptr;\n    xb8_dash = nullptr;\n    x170_paneStates.clear();\n    x170_paneStates.resize(4);\n    x194_scanStr = TLockedToken<CStringTable>();\n    x1ac_pageCounter = 0;\n    x1b4_scanComplete = false;\n  } else {\n    xa4_textGroup->SetColor(zeus::CColor(1.f, x1a8_bodyAlpha));\n  }\n}\n\nvoid CScanDisplay::Draw() {\n  if (!x0_dataDot.IsLoaded()) {\n    return;\n  }\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CScanDisplay::Draw\", zeus::skGreen);\n\n  g_Renderer->SetDepthReadWrite(false, false);\n  g_Renderer->SetViewportOrtho(true, -4096.f, 4096.f);\n  g_Renderer->SetBlendMode_AdditiveAlpha();\n  CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvModulate);\n  x0_dataDot->Load(GX_TEXMAP0, EClampMode::Repeat);\n\n  const float vpRatio = CGraphics::GetViewportHeight() / 480.f;\n  for (CDataDot& dot : xbc_dataDots) {\n    dot.Draw(g_tweakGuiColors->GetScanDataDotColor(), g_tweakGui->GetScanDataDotRadius() * vpRatio);\n  }\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CScanDisplay.hpp",
    "content": "#pragma once\n\n#include <optional>\n#include <utility>\n\n#include \"Runtime/CScannableObjectInfo.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Camera/CCameraFilter.hpp\"\n#include \"Runtime/Graphics/CTexture.hpp\"\n\n#include <zeus/CColor.hpp>\n#include <zeus/CQuaternion.hpp>\n#include <zeus/CVector2f.hpp>\n\nnamespace metaforce {\nclass CAuiImagePane;\nclass CGuiFrame;\nclass CGuiModel;\nclass CGuiTextPane;\nclass CGuiWidget;\nclass CStringTable;\nstruct CFinalInput;\n\nclass CScanDisplay {\n  friend class CHudDecoInterfaceScan;\n\npublic:\n  class CDataDot {\n  public:\n    enum class EDotState { Hidden, Seek, Hold, RevealPane, Done };\n\n  private:\n    EDotState x0_dotState = EDotState::Hidden;\n    zeus::CVector2f x4_startPos;\n    zeus::CVector2f xc_curPos;\n    zeus::CVector2f x14_targetPos;\n    float x1c_transDur = 0.f;\n    float x20_remTime = 0.f;\n    float x24_alpha = 0.f;\n    float x28_desiredAlpha = 0.f;\n\n  public:\n    explicit CDataDot(const TLockedToken<CTexture>& dataDotTex) {}\n    void Update(float dt);\n    void Draw(const zeus::CColor& color, float radius);\n    float GetTransitionFactor() const { return x1c_transDur > 0.f ? x20_remTime / x1c_transDur : 0.f; }\n    void StartTransitionTo(const zeus::CVector2f&, float);\n    void SetDestPosition(const zeus::CVector2f&);\n    void SetDesiredAlpha(float a) { x28_desiredAlpha = a; }\n    void SetDotState(EDotState s) { x0_dotState = s; }\n    void SetAlpha(float a) { x24_alpha = a; }\n    const zeus::CVector2f& GetCurrPosition() const { return xc_curPos; }\n    EDotState GetDotState() const { return x0_dotState; }\n  };\n\n  enum class EScanState { Inactive, Downloading, DownloadComplete, ViewingScan, Done };\n\nprivate:\n  TLockedToken<CTexture> x0_dataDot;\n  EScanState xc_state = EScanState::Inactive;\n  TUniqueId x10_objId = kInvalidUniqueId;\n  std::optional<CScannableObjectInfo> x14_scannableInfo;\n  const CGuiFrame& xa0_selHud;\n  CGuiWidget* xa4_textGroup = nullptr;\n  CGuiTextPane* xa8_message = nullptr;\n  CGuiTextPane* xac_scrollMessage = nullptr;\n  CGuiModel* xb0_xmark = nullptr;\n  CGuiModel* xb4_abutton = nullptr;\n  CGuiModel* xb8_dash = nullptr;\n  rstl::reserved_vector<CDataDot, 4> xbc_dataDots;\n  rstl::reserved_vector<std::pair<float, CAuiImagePane*>, 4> x170_paneStates;\n  TLockedToken<CStringTable> x194_scanStr; // Used to be optional\n  float x1a4_xAlpha = 0.f;\n  float x1a8_bodyAlpha = 0.f;\n  int x1ac_pageCounter = 0;\n  float x1b0_aPulse = 1.f;\n  bool x1b4_scanComplete = false;\n\n  float GetDownloadStartTime(size_t idx) const;\n  float GetDownloadFraction(size_t idx, float scanningTime) const;\n  static void SetScanMessageTypeEffect(CGuiTextPane* pane, bool type);\n\npublic:\n  explicit CScanDisplay(const CGuiFrame& selHud);\n  void ProcessInput(const CFinalInput& input);\n  void StartScan(TUniqueId id, const CScannableObjectInfo& scanInfo, CGuiTextPane* message, CGuiTextPane* scrollMessage,\n                 CGuiWidget* textGroup, CGuiModel* xmark, CGuiModel* abutton, CGuiModel* dash, float scanTime);\n  void StopScan();\n  void Update(float dt, float scanningTime);\n  void Draw();\n  EScanState GetScanState() const { return xc_state; }\n  TUniqueId ScanTarget() const { return x10_objId; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CSplashScreen.cpp",
    "content": "#include \"Runtime/GuiSys/CSplashScreen.hpp\"\n\n#include \"CArchitectureMessage.hpp\"\n#include \"CArchitectureQueue.hpp\"\n#include \"CSimplePool.hpp\"\n#include \"GameGlobalObjects.hpp\"\n#include \"Graphics/CCubeRenderer.hpp\"\n\nnamespace metaforce {\n\nconstexpr std::array SplashTextures{\"TXTR_NintendoLogo\"sv, \"TXTR_RetroLogo\"sv, \"TXTR_DolbyLogo\"sv};\n\nCSplashScreen::CSplashScreen(ESplashScreen which)\n: CIOWin(\"SplashScreen\"), x14_which(which), x28_texture(g_SimplePool->GetObj(SplashTextures[size_t(which)])) {}\n\nCIOWin::EMessageReturn CSplashScreen::OnMessage(const CArchitectureMessage& msg, CArchitectureQueue& queue) {\n  switch (msg.GetType()) {\n  case EArchMsgType::TimerTick: {\n    if (!x25_textureLoaded) {\n      if (!x28_texture.IsLoaded())\n        return EMessageReturn::Exit;\n      x25_textureLoaded = true;\n    }\n\n    float dt = MakeMsg::GetParmTimerTick(msg).x4_parm;\n    x18_splashTimeout -= dt;\n\n    if (x18_splashTimeout <= 0.f) {\n      /* HACK: If we're not compiling with Intel's IPP library we want to skip the Dolby Pro Logic II logo\n       * This is purely a URDE addition and does not reflect retro's intentions. - Phil\n       */\n#if INTEL_IPP\n      if (x14_which != ESplashScreen::Dolby)\n#else\n      if (x14_which != ESplashScreen::Retro)\n#endif\n        queue.Push(MakeMsg::CreateCreateIOWin(EArchMsgTarget::IOWinManager, 9999, 9999,\n                                              std::make_shared<CSplashScreen>(ESplashScreen(int(x14_which) + 1))));\n      return EMessageReturn::RemoveIOWinAndExit;\n    }\n    break;\n  }\n  default:\n    break;\n  }\n  return EMessageReturn::Exit;\n}\n\nvoid CSplashScreen::Draw() {\n  if (!x25_textureLoaded) {\n    return;\n  }\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CSplashScreen::Draw\", zeus::skGreen);\n\n  zeus::CColor color = zeus::skWhite;\n  if (x14_which == ESplashScreen::Nintendo) {\n    color = zeus::CColor{0.86f, 0.f, 0.f, 1.f};\n  }\n\n  if (x18_splashTimeout > 1.5f) {\n    color.a() = 1.f - (x18_splashTimeout - 1.5f) * 2.f;\n  } else if (x18_splashTimeout < 0.5f) {\n    color.a() = x18_splashTimeout * 2.f;\n  }\n\n  CGraphics::SetAlphaCompare(ERglAlphaFunc::Always, 0, ERglAlphaOp::And, ERglAlphaFunc::Always, 0);\n  g_Renderer->SetModelMatrix({});\n  CGraphics::SetViewPointMatrix({});\n  CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvModulate);\n  CGraphics::SetTevOp(ERglTevStage::Stage1, CTevCombiners::kEnvPassthru);\n  g_Renderer->SetBlendMode_AlphaBlended();\n  auto& tex = *x28_texture.GetObj();\n  const auto width = tex.GetWidth();\n  const auto height = tex.GetHeight();\n  tex.Load(GX_TEXMAP0, EClampMode::Clamp);\n  if (x14_which == ESplashScreen::Nintendo || x14_which == ESplashScreen::Retro) {\n    const auto x = static_cast<float>(133 - (width - 376) / 2);\n    const auto y = static_cast<float>(170 - (height - 104) / 2);\n    CGraphics::SetOrtho(-10.f, 650.f, -5.5f, 484.5f, -1.f, 1.f);\n    CGraphics::SetCullMode(ERglCullMode::None);\n    CGraphics::StreamBegin(ERglPrimitive::TriangleStrip);\n    CGraphics::StreamColor(color);\n    CGraphics::StreamTexcoord(0.f, 0.f);\n    CGraphics::StreamVertex({x, 0.f, y + static_cast<float>(height)});\n    CGraphics::StreamTexcoord(0.f, 1.f);\n    CGraphics::StreamVertex({x, 0.f, y});\n    CGraphics::StreamTexcoord(1.f, 0.f);\n    CGraphics::StreamVertex({x + static_cast<float>(width), 0.f, y + static_cast<float>(height)});\n    CGraphics::StreamTexcoord(1.f, 1.f);\n    CGraphics::StreamVertex({x + static_cast<float>(width), 0.f, y});\n    CGraphics::StreamEnd();\n    CGraphics::SetCullMode(ERglCullMode::Front);\n  } else {\n    // TODO originally uses CGraphics viewport, but Render2D needs scaling fix\n    CGraphics::Render2D(tex, 0, 0, 640, 448, color, true);\n  }\n\n  // Progressive scan options omitted\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CSplashScreen.hpp",
    "content": "#pragma once\n\n#include \"Runtime/CIOWin.hpp\"\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/Graphics/CTexture.hpp\"\n\nnamespace metaforce {\n\nclass CSplashScreen : public CIOWin {\npublic:\n  enum class ESplashScreen { Nintendo, Retro, Dolby };\n  enum class EProgressivePhase { Before, During, After };\n\nprivate:\n  ESplashScreen x14_which;\n  float x18_splashTimeout = 2.f;\n  // float x1c_progSelectionTimeout = 0.f;\n  // EProgressivePhase x20_progressivePhase = EProgressivePhase::Before;\n  // bool x24_progressiveSelection = true;\n  bool x25_textureLoaded = false;\n  TLockedToken<CTexture> x28_texture;\n\npublic:\n  explicit CSplashScreen(ESplashScreen);\n  EMessageReturn OnMessage(const CArchitectureMessage&, CArchitectureQueue&) override;\n  void Draw() override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CStringTable.cpp",
    "content": "#include \"Runtime/GuiSys/CStringTable.hpp\"\n\n#include \"Runtime/CBasics.hpp\"\n#include \"Runtime/Streams/CInputStream.hpp\"\n#include \"Runtime/CToken.hpp\"\n\n#include <array>\n\nnamespace metaforce {\nnamespace {\nconstexpr std::array languages{\n    FOURCC('ENGL'), FOURCC('FREN'), FOURCC('GERM'), FOURCC('SPAN'), FOURCC('ITAL'), FOURCC('DUTC'), FOURCC('JAPN'),\n};\n} // Anonymous namespace\n\nFourCC CStringTable::mCurrentLanguage = languages[0];\n\nCStringTable::CStringTable(CInputStream& in) { LoadStringTable(in); }\n\nvoid CStringTable::LoadStringTable(CInputStream& in) {\n  in.ReadLong();\n  in.ReadLong();\n  u32 langCount = in.ReadLong();\n  x0_stringCount = in.ReadLong();\n  std::vector<std::pair<FourCC, u32>> langOffsets;\n  for (u32 i = 0; i < langCount; ++i) {\n    FourCC fcc;\n    in.Get(reinterpret_cast<u8*>(&fcc), 4);\n    u32 off = in.ReadLong();\n    langOffsets.emplace_back(fcc, off);\n  }\n\n  u32 lang = 0;\n  u32 offset = 0;\n  while ((langCount--) > 0) {\n    if (langOffsets[lang].first == mCurrentLanguage) {\n      offset = langOffsets[lang].second;\n      break;\n    }\n\n    lang++;\n  }\n\n  /*\n   * If we fail to get a language, default to the first in the list\n   * This way we always display _something_\n   */\n  if (offset == UINT32_MAX)\n    offset = langOffsets[0].second;\n\n  for (u32 i = 0; i < offset; ++i) {\n    in.ReadChar();\n  }\n\n  u32 dataLen = in.ReadLong();\n  m_bufLen = dataLen;\n  x4_data.reset(new u8[dataLen]);\n  in.Get(x4_data.get(), dataLen);\n\n#if METAFORCE_TARGET_BYTE_ORDER == __ORDER_LITTLE_ENDIAN__\n  u32* off = reinterpret_cast<u32*>(x4_data.get());\n  for (u32 i = 0; i < x0_stringCount; ++i, ++off) {\n    *off = CBasics::SwapBytes(*off);\n  }\n\n  for (u32 i = x0_stringCount * 4; i < dataLen; i += 2) {\n    u16* chr = reinterpret_cast<u16*>(x4_data.get() + i);\n    *chr = CBasics::SwapBytes(*chr);\n  }\n#endif\n}\n\nconst char16_t* CStringTable::GetString(s32 str) const {\n  if (str < 0 || u32(str) >= x0_stringCount)\n    return u\"Invalid\";\n\n  u32 off = *reinterpret_cast<u32*>(x4_data.get() + str * 4);\n  return reinterpret_cast<char16_t*>(x4_data.get() + off);\n}\n\nvoid CStringTable::SetLanguage(s32 lang) { mCurrentLanguage = languages[lang]; }\n\nCFactoryFnReturn FStringTableFactory(const SObjectTag&, CInputStream& in, const CVParamTransfer&,\n                                     [[maybe_unused]] CObjectReference* selfRef) {\n  return TToken<CStringTable>::GetIObjObjectFor(std::make_unique<CStringTable>(in));\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CStringTable.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/IFactory.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n\nnamespace metaforce {\nclass CStringTable {\n  static FourCC mCurrentLanguage;\n  u32 x0_stringCount = 0;\n  std::unique_ptr<u8[]> x4_data;\n  u32 m_bufLen = 0;\n\npublic:\n  explicit CStringTable(CInputStream& in);\n  void LoadStringTable(CInputStream& in);\n\n  const char16_t* GetString(s32) const;\n  u32 GetStringCount() const { return x0_stringCount; }\n  static void SetLanguage(s32);\n};\n\nCFactoryFnReturn FStringTableFactory(const SObjectTag&, CInputStream&, const CVParamTransfer&,\n                                     CObjectReference* selfRef);\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CTargetingManager.cpp",
    "content": "#include \"Runtime/GuiSys/CTargetingManager.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Camera/CGameCamera.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n\nnamespace metaforce {\n\nCTargetingManager::CTargetingManager(const CStateManager& mgr) : x0_targetReticule(mgr) {}\n\nbool CTargetingManager::CheckLoadComplete() const {\n  return x0_targetReticule.CheckLoadComplete() && x21c_orbitPointMarker.CheckLoadComplete();\n}\n\nvoid CTargetingManager::Update(float dt, const CStateManager& stateMgr) {\n  x0_targetReticule.Update(dt, stateMgr);\n  x21c_orbitPointMarker.Update(dt, stateMgr);\n}\n\nvoid CTargetingManager::Draw(const CStateManager& mgr, bool hideLockon) {\n  CGraphics::SetAmbientColor(zeus::skWhite);\n  CGraphics::DisableAllLights();\n  x21c_orbitPointMarker.Draw(mgr);\n  const CGameCamera* curCam = mgr.GetCameraManager()->GetCurrentCamera(mgr);\n  zeus::CTransform camXf = mgr.GetCameraManager()->GetCurrentCameraTransform(mgr);\n  CGraphics::SetViewPointMatrix(camXf);\n  zeus::CFrustum frustum;\n  frustum.updatePlanes(camXf,\n                       zeus::SProjPersp(zeus::degToRad(curCam->GetFov()), CGraphics::GetViewportAspect(), 1.f, 100.f));\n  g_Renderer->SetClippingPlanes(frustum);\n  g_Renderer->SetPerspective(curCam->GetFov(), CGraphics::GetViewportWidth(), CGraphics::GetViewportHeight(),\n                             curCam->GetNearClipDistance(), curCam->GetFarClipDistance());\n  x0_targetReticule.Draw(mgr, hideLockon);\n}\n\nvoid CTargetingManager::Touch() { x0_targetReticule.Touch(); }\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CTargetingManager.hpp",
    "content": "#pragma once\n\n#include \"Runtime/GuiSys/CCompoundTargetReticle.hpp\"\n#include \"Runtime/GuiSys/COrbitPointMarker.hpp\"\n\nnamespace metaforce {\nclass CStateManager;\nclass CTargetingManager {\n  CCompoundTargetReticle x0_targetReticule;\n  COrbitPointMarker x21c_orbitPointMarker;\n\npublic:\n  explicit CTargetingManager(const CStateManager& stateMgr);\n  bool CheckLoadComplete() const;\n  void Update(float, const CStateManager& stateMgr);\n  void Draw(const CStateManager& stateMgr, bool hideLockon);\n  void Touch();\n  CCompoundTargetReticle& CompoundTargetReticle() { return x0_targetReticule; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CTextExecuteBuffer.cpp",
    "content": "#include \"Runtime/GuiSys/CTextExecuteBuffer.hpp\"\n\n#include \"Runtime/Graphics/CGraphicsPalette.hpp\"\n#include \"Runtime/Graphics/CTexture.hpp\"\n#include \"Runtime/GuiSys/CFontImageDef.hpp\"\n#include \"Runtime/GuiSys/CFontRenderState.hpp\"\n#include \"Runtime/GuiSys/CInstruction.hpp\"\n#include \"Runtime/GuiSys/CRasterFont.hpp\"\n#include \"Runtime/GuiSys/CTextRenderBuffer.hpp\"\n#include \"Runtime/GuiSys/CWordBreakTables.hpp\"\n\nnamespace metaforce {\n\nCTextRenderBuffer CTextExecuteBuffer::BuildRenderBuffer(CGuiWidget::EGuiModelDrawFlags df) const {\n  CTextRenderBuffer ret(CTextRenderBuffer::EMode::AllocTally);//, df);\n\n  {\n    CFontRenderState rendState;\n    for (const std::shared_ptr<CInstruction>& inst : x0_instList)\n      inst->Invoke(rendState, &ret);\n  }\n\n  ret.SetMode(CTextRenderBuffer::EMode::BufferFill);\n\n  {\n    CFontRenderState rendState;\n    for (const std::shared_ptr<CInstruction>& inst : x0_instList)\n      inst->Invoke(rendState, &ret);\n  }\n\n  return ret;\n}\n\nCTextRenderBuffer CTextExecuteBuffer::BuildRenderBufferPage(InstList::const_iterator start,\n                                                            InstList::const_iterator pgStart,\n                                                            InstList::const_iterator pgEnd,\n                                                            CGuiWidget::EGuiModelDrawFlags df) const {\n  CTextRenderBuffer ret(CTextRenderBuffer::EMode::AllocTally);//, df);\n\n  {\n    CFontRenderState rendState;\n    for (auto it = start; it != pgStart; ++it) {\n      const std::shared_ptr<CInstruction>& inst = *it;\n      inst->PageInvoke(rendState, &ret);\n    }\n    for (auto it = pgStart; it != pgEnd; ++it) {\n      const std::shared_ptr<CInstruction>& inst = *it;\n      inst->Invoke(rendState, &ret);\n    }\n  }\n\n  ret.SetMode(CTextRenderBuffer::EMode::BufferFill);\n\n  {\n    CFontRenderState rendState;\n    for (auto it = start; it != pgStart; ++it) {\n      const std::shared_ptr<CInstruction>& inst = *it;\n      inst->PageInvoke(rendState, &ret);\n    }\n    for (auto it = pgStart; it != pgEnd; ++it) {\n      const std::shared_ptr<CInstruction>& inst = *it;\n      inst->Invoke(rendState, &ret);\n    }\n  }\n\n  return ret;\n}\n\nstd::list<CTextRenderBuffer> CTextExecuteBuffer::BuildRenderBufferPages(const zeus::CVector2i& extent,\n                                                                        CGuiWidget::EGuiModelDrawFlags df) const {\n  std::list<CTextRenderBuffer> ret;\n\n  for (auto it = x0_instList.begin(); it != x0_instList.end();) {\n    CTextRenderBuffer rbuf(CTextRenderBuffer::EMode::AllocTally);//, df);\n\n    {\n      CFontRenderState rstate;\n      for (auto it2 = x0_instList.begin(); it2 != x0_instList.end(); ++it2) {\n        const std::shared_ptr<CInstruction>& inst2 = *it2;\n        inst2->Invoke(rstate, &rbuf);\n      }\n    }\n\n    rbuf.SetMode(CTextRenderBuffer::EMode::BufferFill);\n\n    InstList::const_iterator pageEnd = it;\n    {\n      CFontRenderState rstate;\n      bool seekingToPage = true;\n      for (auto it2 = x0_instList.begin(); it2 != x0_instList.end(); ++it2) {\n        const std::shared_ptr<CInstruction>& inst2 = *it2;\n        if (it2 == it)\n          seekingToPage = false;\n        if (seekingToPage) {\n          inst2->PageInvoke(rstate, &rbuf);\n        } else {\n          inst2->Invoke(rstate, &rbuf);\n          if (!rbuf.HasSpaceAvailable(zeus::CVector2i{}, extent))\n            break;\n          ++pageEnd;\n        }\n      }\n    }\n\n    ret.push_back(BuildRenderBufferPage(x0_instList.cbegin(), it, pageEnd, df));\n    it = pageEnd;\n  }\n\n  return ret;\n}\n\nstd::vector<CToken> CTextExecuteBuffer::GetAssets() const {\n  size_t totalAssets = 0;\n  for (const std::shared_ptr<CInstruction>& inst : x0_instList)\n    totalAssets += inst->GetAssetCount();\n\n  std::vector<CToken> ret;\n  ret.reserve(totalAssets);\n\n  for (const std::shared_ptr<CInstruction>& inst : x0_instList)\n    inst->GetAssets(ret);\n\n  return ret;\n}\n\nvoid CTextExecuteBuffer::AddString(const char16_t* str, int count) {\n  if (!xa4_curLine)\n    StartNewLine();\n\n  const char16_t* charCur = str;\n  const char16_t* wordCur = str;\n\n  for (int ac = 0; *charCur && (ac < count || count == -1); ++charCur, ++ac) {\n    if (*charCur == u'\\n' || *charCur == u' ') {\n      AddStringFragment(wordCur, charCur - wordCur);\n      wordCur = charCur + 1;\n      if (*charCur == u'\\n') {\n        StartNewLine();\n      } else {\n        StartNewWord();\n        int w, h;\n        char16_t space = u' ';\n        x18_textState.x48_font->GetSize(x18_textState.x0_drawStrOpts, w, h, &space, 1);\n        if (xa0_curBlock->x14_dir == ETextDirection::Horizontal) {\n          xa4_curLine->x8_curX += w;\n          xbc_spaceDistance = w;\n        } else {\n          xa4_curLine->xc_curY += h;\n          xbc_spaceDistance = h;\n        }\n      }\n    }\n  }\n\n  if (charCur > wordCur)\n    AddStringFragment(wordCur, charCur - wordCur);\n}\n\nvoid CTextExecuteBuffer::AddStringFragment(const char16_t* str, int len) {\n  if (xa0_curBlock->x14_dir == ETextDirection::Horizontal)\n    for (int i = 0; i < len;)\n      i += WrapOneLTR(str + i, len - i);\n}\n\nint CTextExecuteBuffer::WrapOneLTR(const char16_t* str, int len) {\n  if (!x18_textState.x48_font)\n    return len;\n\n  CRasterFont* font = x18_textState.x48_font.GetObj();\n  int rem = len;\n  int w, h;\n  x18_textState.x48_font->GetSize(x18_textState.x0_drawStrOpts, w, h, str, len);\n\n  if (x18_textState.x7c_enableWordWrap) {\n    if (w + xa4_curLine->x8_curX > xa0_curBlock->xc_blockExtentX && xa4_curLine->x4_wordCount >= 1 &&\n        xb0_curX + w < xa0_curBlock->xc_blockExtentX) {\n      MoveWordLTR();\n    }\n    if (w + xa4_curLine->x8_curX > xa0_curBlock->xc_blockExtentX && len > 1) {\n      const char16_t* strEnd = str + len;\n      int aRank = 5;\n\n      do {\n        --rem;\n        --strEnd;\n        int endRank = 4;\n        if (len > 2)\n          endRank = CWordBreakTables::GetEndRank(*(strEnd - 1));\n\n        int beginRank = CWordBreakTables::GetBeginRank(*strEnd);\n\n        if (endRank < aRank && endRank <= beginRank) {\n          aRank = endRank;\n        } else {\n          x18_textState.x48_font->GetSize(x18_textState.x0_drawStrOpts, w, h, str, rem);\n        }\n\n      } while (w + xa4_curLine->x8_curX > xa0_curBlock->xc_blockExtentX && rem > 1);\n    }\n  }\n\n  xac_curY = std::max(xac_curY, font->GetMonoHeight());\n\n  xa4_curLine->TestLargestFont(font->GetMonoWidth(), font->GetMonoHeight(), font->GetBaseline());\n\n  xa4_curLine->x8_curX += w;\n  xa0_curBlock->x2c_lineX = std::max(xa0_curBlock->x2c_lineX, xa4_curLine->x8_curX);\n  xb0_curX += w;\n\n  x0_instList.emplace(x0_instList.cend(), std::make_shared<CTextInstruction>(str, rem));\n\n  if (rem != len)\n    StartNewLine();\n\n  return rem;\n}\n\nvoid CTextExecuteBuffer::MoveWordLTR() {\n  xa4_curLine->x8_curX -= (xb0_curX + xbc_spaceDistance);\n  xa4_curLine->xc_curY = std::min(xa4_curLine->xc_curY, xb8_curWordY);\n  xbc_spaceDistance = 0;\n  --xa4_curLine->x4_wordCount;\n  TerminateLineLTR();\n\n  xa4_curLine = static_cast<CLineInstruction*>(\n      x0_instList\n          .emplace(xa8_curWordIt, std::make_shared<CLineInstruction>(x18_textState.x80_just, x18_textState.x84_vjust,\n                                                                     xc0_imageBaseline))\n          ->get());\n\n  // Dunno what's up with this in the original; seems fine without\n  x0_instList.emplace(xa8_curWordIt, std::make_shared<CWordInstruction>());\n\n  ++xa0_curBlock->x34_lineCount;\n}\n\nvoid CTextExecuteBuffer::StartNewLine() {\n  if (xa4_curLine)\n    TerminateLine();\n\n  xa8_curWordIt = x0_instList.emplace(\n      x0_instList.cend(),\n      std::make_shared<CLineInstruction>(x18_textState.x80_just, x18_textState.x84_vjust, xc0_imageBaseline));\n  xa4_curLine = static_cast<CLineInstruction*>(xa8_curWordIt->get());\n  xbc_spaceDistance = 0;\n\n  StartNewWord();\n  ++xa0_curBlock->x34_lineCount;\n}\n\nvoid CTextExecuteBuffer::StartNewWord() {\n  xa8_curWordIt = x0_instList.emplace(x0_instList.cend(), std::make_shared<CWordInstruction>());\n  xb0_curX = 0;\n  xac_curY = 0;\n  xb4_curWordX = xa4_curLine->x8_curX;\n  xb8_curWordY = xa4_curLine->xc_curY;\n  ++xa4_curLine->x4_wordCount;\n}\n\nvoid CTextExecuteBuffer::TerminateLine() {\n  if (xa0_curBlock->x14_dir == ETextDirection::Horizontal)\n    TerminateLineLTR();\n}\n\nvoid CTextExecuteBuffer::TerminateLineLTR() {\n  if (!xa4_curLine->xc_curY /*&& x18_textState.IsFinishedLoading()*/) {\n    xa4_curLine->xc_curY = std::max(xa4_curLine->GetHeight(), x18_textState.x48_font->GetCarriageAdvance());\n  }\n\n  if (xa0_curBlock->x1c_vertJustification == EVerticalJustification::Full) {\n    xa0_curBlock->x30_lineY += xa4_curLine->xc_curY;\n  } else {\n    xa0_curBlock->x30_lineY += x18_textState.x78_extraLineSpace + xa4_curLine->xc_curY * x18_textState.x74_lineSpacing;\n  }\n}\n\nvoid CTextExecuteBuffer::AddPopState() {\n  x0_instList.emplace(x0_instList.cend(), std::make_shared<CPopStateInstruction>());\n\n  x18_textState = xc4_stateStack.back();\n  xc4_stateStack.pop_back();\n\n  if (xa4_curLine->x8_curX == 0) {\n    xa4_curLine->x28_just = x18_textState.x80_just;\n    xa4_curLine->x2c_vjust = x18_textState.x84_vjust;\n  }\n}\n\nvoid CTextExecuteBuffer::AddPushState() {\n  x0_instList.emplace(x0_instList.cend(), std::make_shared<CPushStateInstruction>());\n  xc4_stateStack.push_back(x18_textState);\n}\n\nvoid CTextExecuteBuffer::AddVerticalJustification(EVerticalJustification vjust) {\n  x18_textState.x84_vjust = vjust;\n  if (!xa4_curLine)\n    return;\n  if (xa4_curLine->x8_curX)\n    return;\n  xa4_curLine->x2c_vjust = vjust;\n}\n\nvoid CTextExecuteBuffer::AddJustification(EJustification just) {\n  x18_textState.x80_just = just;\n  if (!xa4_curLine)\n    return;\n  if (xa4_curLine->x8_curX)\n    return;\n  xa4_curLine->x28_just = just;\n}\n\nvoid CTextExecuteBuffer::AddLineExtraSpace(s32 space) {\n  x0_instList.emplace(x0_instList.cend(), std::make_shared<CLineExtraSpaceInstruction>(space));\n  x18_textState.x78_extraLineSpace = space;\n}\n\nvoid CTextExecuteBuffer::AddLineSpacing(float spacing) {\n  x0_instList.emplace(x0_instList.cend(), std::make_shared<CLineSpacingInstruction>(spacing));\n  x18_textState.x74_lineSpacing = spacing;\n}\n\nvoid CTextExecuteBuffer::AddRemoveColorOverride(int idx) {\n  x0_instList.emplace(x0_instList.cend(), std::make_shared<CRemoveColorOverrideInstruction>(idx));\n}\n\nvoid CTextExecuteBuffer::AddColorOverride(int idx, const CTextColor& color) {\n  x0_instList.emplace(x0_instList.cend(), std::make_shared<CColorOverrideInstruction>(idx, color));\n}\n\nvoid CTextExecuteBuffer::AddColor(EColorType tp, const CTextColor& color) {\n  x0_instList.emplace(x0_instList.cend(), std::make_shared<CColorInstruction>(tp, color));\n}\n\nvoid CTextExecuteBuffer::AddImage(const CFontImageDef& image) {\n  if (!xa4_curLine)\n    StartNewLine();\n\n  if (xa0_curBlock) {\n    const CTexture* tex = image.x4_texs[0].GetObj();\n    int width = tex->GetWidth() * image.x14_cropFactor.x();\n    int height = tex->GetHeight() * image.x14_cropFactor.y();\n\n    if (x18_textState.x7c_enableWordWrap && xa4_curLine->x8_curX + width > xa0_curBlock->xc_blockExtentX &&\n        xa4_curLine->x4_wordCount > 1)\n      StartNewLine();\n\n    xa4_curLine->TestLargestImage(width, height, image.CalculateBaseline());\n\n    xa4_curLine->x8_curX += width;\n    if (xa4_curLine->x8_curX > width)\n      xa0_curBlock->x2c_lineX = xa4_curLine->x8_curX;\n  }\n\n  x0_instList.emplace(x0_instList.cend(), std::make_shared<CImageInstruction>(image));\n}\n\nvoid CTextExecuteBuffer::AddFont(const TToken<CRasterFont>& font) {\n  x0_instList.emplace(x0_instList.cend(), std::make_shared<CFontInstruction>(font));\n  x18_textState.x48_font = font;\n\n  if (xa0_curBlock)\n    xa0_curBlock->TestLargestFont(font->GetMonoWidth(), font->GetMonoHeight(), font->GetBaseline());\n\n  if (xa4_curLine)\n    xa4_curLine->TestLargestFont(font->GetMonoWidth(), font->GetMonoHeight(), font->GetBaseline());\n}\n\nvoid CTextExecuteBuffer::EndBlock() {\n  if (xa4_curLine)\n    TerminateLine();\n  xa4_curLine = nullptr;\n  xa0_curBlock = nullptr;\n}\n\nvoid CTextExecuteBuffer::BeginBlock(s32 offX, s32 offY, s32 extX, s32 extY, bool imageBaseline, ETextDirection dir,\n                                    EJustification just, EVerticalJustification vjust) {\n  xc0_imageBaseline = imageBaseline;\n  xa0_curBlock = static_cast<CBlockInstruction*>(\n      x0_instList\n          .emplace(x0_instList.cend(), std::make_shared<CBlockInstruction>(offX, offY, extX, extY, dir, just, vjust))\n          ->get());\n\n  if (x18_textState.x48_font) {\n    CRasterFont* font = x18_textState.x48_font.GetObj();\n    s32 baseline = font->GetBaseline();\n    s32 monoH = font->GetMonoHeight();\n    s32 monoW = font->GetMonoWidth();\n    xa0_curBlock->TestLargestFont(monoW, monoH, baseline);\n  }\n\n  x18_textState.x0_drawStrOpts.x0_direction = dir;\n  x18_textState.x80_just = just;\n  x18_textState.x84_vjust = vjust;\n}\n\nvoid CTextExecuteBuffer::Clear() {\n  x0_instList.clear();\n  x18_textState = CSaveableState();\n  xa0_curBlock = nullptr;\n  xa4_curLine = nullptr;\n  xa8_curWordIt = x0_instList.begin();\n  xb4_curWordX = 0;\n  xb8_curWordY = 0;\n  xbc_spaceDistance = 0;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CTextExecuteBuffer.hpp",
    "content": "#pragma once\n\n#include <list>\n#include <vector>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/GuiSys/CGuiTextSupport.hpp\"\n#include \"Runtime/GuiSys/CSaveableState.hpp\"\n\n#include <zeus/CVector2i.hpp>\n\nnamespace metaforce {\nclass CBlockInstruction;\nclass CFontImageDef;\nclass CInstruction;\nclass CLineInstruction;\nclass CTextRenderBuffer;\n\nclass CTextExecuteBuffer {\n  friend class CGuiTextSupport;\n  friend class CTextRenderBufferPages;\n  using InstList = std::list<std::shared_ptr<CInstruction>>;\n\n  InstList x0_instList;\n  u32 x14_ = 0;\n  CSaveableState x18_textState;\n  CBlockInstruction* xa0_curBlock = nullptr;\n  CLineInstruction* xa4_curLine = nullptr;\n  InstList::iterator xa8_curWordIt;\n  s32 xac_curY = 0;\n  s32 xb0_curX = 0;\n  s32 xb4_curWordX = 0;\n  s32 xb8_curWordY = 0;\n  s32 xbc_spaceDistance = 0;\n  bool xc0_imageBaseline = false;\n  std::list<CSaveableState> xc4_stateStack;\n  u32 xd8_ = 0;\n\npublic:\n  CTextExecuteBuffer() : xa8_curWordIt{x0_instList.begin()} {}\n\n  CTextRenderBuffer BuildRenderBuffer(CGuiWidget::EGuiModelDrawFlags df) const;\n  CTextRenderBuffer BuildRenderBufferPage(InstList::const_iterator start, InstList::const_iterator pgStart,\n                                          InstList::const_iterator pgEnd, CGuiWidget::EGuiModelDrawFlags df) const;\n  std::list<CTextRenderBuffer> BuildRenderBufferPages(const zeus::CVector2i& extent,\n                                                      CGuiWidget::EGuiModelDrawFlags df) const;\n  std::vector<CToken> GetAssets() const;\n  void AddString(const char16_t* str, int len);\n  void AddStringFragment(const char16_t* str, int len);\n  int WrapOneLTR(const char16_t* str, int len);\n  void MoveWordLTR();\n  void StartNewLine();\n  void StartNewWord();\n  void TerminateLine();\n  void TerminateLineLTR();\n  void AddPopState();\n  void AddPushState();\n  void AddVerticalJustification(EVerticalJustification vjust);\n  void AddJustification(EJustification just);\n  void AddLineExtraSpace(s32 space);\n  void AddLineSpacing(float spacing);\n  void AddRemoveColorOverride(int idx);\n  void AddColorOverride(int idx, const CTextColor& color);\n  void AddColor(EColorType, const CTextColor& color);\n  void AddImage(const CFontImageDef& image);\n  void AddFont(const TToken<CRasterFont>& font);\n  void EndBlock();\n  void BeginBlock(s32 offX, s32 offY, s32 extX, s32 extY, bool imageBaseline, ETextDirection dir, EJustification just,\n                  EVerticalJustification vjust);\n  void Clear();\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CTextParser.cpp",
    "content": "#include \"Runtime/GuiSys/CTextParser.hpp\"\n\n#include \"Runtime/GuiSys/CFontImageDef.hpp\"\n#include \"Runtime/GuiSys/CTextExecuteBuffer.hpp\"\n\nnamespace metaforce {\n\nstatic float u16stof(const char16_t* str) {\n  char cstr[16];\n  int i;\n  for (i = 0; i < 15 && str[i] != u'\\0'; ++i)\n    cstr[i] = str[i];\n  cstr[i] = '\\0';\n  return strtof(cstr, nullptr);\n}\n\nCTextColor CTextParser::ParseColor(const char16_t* str, int len) {\n  u8 r = GetColorValue(str + 1);\n  u8 g = GetColorValue(str + 3);\n  u8 b = GetColorValue(str + 5);\n  u8 a = 0xff;\n  if (len == 9)\n    a = GetColorValue(str + 7);\n  CTextColor ret;\n  ret.fromRGBA8(r, g, b, a);\n  return ret;\n}\n\nu8 CTextParser::GetColorValue(const char16_t* str) { return (FromHex(str[0]) << 4) + FromHex(str[1]); }\n\nu32 CTextParser::FromHex(char16_t ch) {\n  if (ch >= u'0' && ch <= u'9')\n    return ch - u'0';\n\n  if (ch >= u'A' && ch <= u'F')\n    return ch - u'A' + 10;\n\n  if (ch >= u'a' && ch <= u'f')\n    return ch - u'a' + 10;\n\n  return 0;\n}\n\ns32 CTextParser::ParseInt(const char16_t* str, int len, bool signVal) {\n  bool neg = false;\n  int procCur = 0;\n  if (signVal && len && *str == u'-') {\n    neg = true;\n    procCur = 1;\n  }\n\n  int val = 0;\n  while (len > procCur) {\n    val *= 10;\n    char16_t ch = str[procCur];\n    val += ch - u'0';\n    ++procCur;\n  }\n\n  return neg ? -val : val;\n}\n\nbool CTextParser::Equals(const char16_t* str, int len, const char16_t* other) {\n  for (int i = 0; *other && i < len; ++i, ++str, ++other) {\n    if (*str != *other)\n      return false;\n  }\n  return *other == u'\\0';\n}\n\nbool CTextParser::BeginsWith(const char16_t* str, int len, const char16_t* other) {\n  for (int i = 0; *other && i < len; ++i, ++str, ++other) {\n    if (*str != *other)\n      return false;\n  }\n  return true;\n}\n\nvoid CTextParser::ParseTag(CTextExecuteBuffer& out, const char16_t* str, int len,\n                           const std::vector<std::pair<CAssetId, CAssetId>>* txtrMap) {\n  if (BeginsWith(str, len, u\"font=\")) {\n    TToken<CRasterFont> font = GetFont(str + 5, len - 5);\n    out.AddFont(font);\n  } else if (BeginsWith(str, len, u\"image=\")) {\n    CFontImageDef image = GetImage(str + 6, len - 6, txtrMap);\n    out.AddImage(image);\n  } else if (BeginsWith(str, len, u\"fg-color=\")) {\n    CTextColor color = ParseColor(str + 9, len - 9);\n    out.AddColor(EColorType::Foreground, color);\n  } else if (BeginsWith(str, len, u\"main-color=\")) {\n    CTextColor color = ParseColor(str + 11, len - 11);\n    out.AddColor(EColorType::Main, color);\n  } else if (BeginsWith(str, len, u\"geometry-color=\")) {\n    CTextColor color = ParseColor(str + 15, len - 15);\n    out.AddColor(EColorType::Geometry, color);\n  } else if (BeginsWith(str, len, u\"outline-color=\")) {\n    CTextColor color = ParseColor(str + 14, len - 14);\n    out.AddColor(EColorType::Outline, color);\n  } else if (BeginsWith(str, len, u\"color\")) {\n    const char16_t* valCur = str + 7;\n    len -= 7;\n    int val = str[6] - u'0';\n    if (str[7] >= u'0' && str[7] <= u'9') {\n      ++valCur;\n      --len;\n      val *= 10;\n      val += str[7] - u'0';\n    }\n\n    if (Equals(valCur + 10, len - 10, u\"no\"))\n      out.AddRemoveColorOverride(val);\n    else {\n      CTextColor color = ParseColor(str + 10, len - 10);\n      out.AddColorOverride(val, color);\n    }\n  } else if (BeginsWith(str, len, u\"line-spacing=\")) {\n    out.AddLineSpacing(ParseInt(str + 13, len - 13, true) / 100.0f);\n  } else if (BeginsWith(str, len, u\"line-extra-space=\")) {\n    out.AddLineExtraSpace(ParseInt(str + 17, len - 17, true));\n  } else if (BeginsWith(str, len, u\"just=\")) {\n    if (Equals(str + 5, len - 5, u\"left\"))\n      out.AddJustification(EJustification::Left);\n    else if (Equals(str + 5, len - 5, u\"center\"))\n      out.AddJustification(EJustification::Center);\n    else if (Equals(str + 5, len - 5, u\"right\"))\n      out.AddJustification(EJustification::Right);\n    else if (Equals(str + 5, len - 5, u\"full\"))\n      out.AddJustification(EJustification::Full);\n    else if (Equals(str + 5, len - 5, u\"nleft\"))\n      out.AddJustification(EJustification::NLeft);\n    else if (Equals(str + 5, len - 5, u\"ncenter\"))\n      out.AddJustification(EJustification::NCenter);\n    else if (Equals(str + 5, len - 5, u\"nright\"))\n      out.AddJustification(EJustification::NRight);\n  } else if (BeginsWith(str, len, u\"vjust=\")) {\n    if (Equals(str + 6, len - 6, u\"top\"))\n      out.AddVerticalJustification(EVerticalJustification::Top);\n    else if (Equals(str + 6, len - 6, u\"center\"))\n      out.AddVerticalJustification(EVerticalJustification::Center);\n    else if (Equals(str + 6, len - 6, u\"bottom\"))\n      out.AddVerticalJustification(EVerticalJustification::Bottom);\n    else if (Equals(str + 6, len - 6, u\"full\"))\n      out.AddVerticalJustification(EVerticalJustification::Full);\n    else if (Equals(str + 6, len - 6, u\"ntop\"))\n      out.AddVerticalJustification(EVerticalJustification::NTop);\n    else if (Equals(str + 6, len - 6, u\"ncenter\"))\n      out.AddVerticalJustification(EVerticalJustification::NCenter);\n    else if (Equals(str + 6, len - 6, u\"nbottom\"))\n      out.AddVerticalJustification(EVerticalJustification::NBottom);\n  } else if (Equals(str, len, u\"push\")) {\n    out.AddPushState();\n  } else if (Equals(str, len, u\"pop\")) {\n    out.AddPopState();\n  }\n}\n\nCFontImageDef CTextParser::GetImage(const char16_t* str, int len,\n                                    const std::vector<std::pair<CAssetId, CAssetId>>* txtrMap) {\n  int commaCount = 0;\n  for (int i = 0; i < len; ++i)\n    if (str[i] == u',')\n      ++commaCount;\n\n  if (commaCount) {\n    std::u16string iterable(str, len);\n    size_t tokenPos;\n    size_t commaPos;\n    commaPos = iterable.find(u',');\n    iterable[commaPos] = u'\\0';\n    tokenPos = commaPos + 1;\n\n    auto AdvanceCommaPos = [&]() {\n      commaPos = iterable.find(u',', tokenPos);\n      if (commaPos == std::u16string::npos)\n        commaPos = iterable.size();\n      iterable[commaPos] = u'\\0';\n    };\n\n    auto AdvanceTokenPos = [&]() { tokenPos = commaPos + 1; };\n\n    if (BeginsWith(str, len, u\"A\")) {\n      /* Animated texture array */\n      AdvanceCommaPos();\n      float interval = u16stof(&iterable[tokenPos]);\n      AdvanceTokenPos();\n\n      std::vector<TToken<CTexture>> texs;\n      texs.reserve(commaCount - 1);\n      do {\n        AdvanceCommaPos();\n        texs.emplace_back(x0_store.GetObj({SBIG('TXTR'), GetAssetIdFromString(&iterable[tokenPos], len, txtrMap)}));\n        AdvanceTokenPos();\n      } while (commaPos != iterable.size());\n\n      return CFontImageDef(texs, interval, zeus::CVector2f(1.f, 1.f));\n    } else if (BeginsWith(str, len, u\"SA\")) {\n      /* Scaled and animated texture array */\n      AdvanceCommaPos();\n      float interval = u16stof(&iterable[tokenPos]);\n      AdvanceTokenPos();\n\n      AdvanceCommaPos();\n      float cropX = u16stof(&iterable[tokenPos]);\n      AdvanceTokenPos();\n\n      AdvanceCommaPos();\n      float cropY = u16stof(&iterable[tokenPos]);\n      AdvanceTokenPos();\n\n      std::vector<TToken<CTexture>> texs;\n      texs.reserve(commaCount - 3);\n      do {\n        AdvanceCommaPos();\n        texs.emplace_back(x0_store.GetObj({SBIG('TXTR'), GetAssetIdFromString(&iterable[tokenPos], len, txtrMap)}));\n        AdvanceTokenPos();\n      } while (commaPos != iterable.size());\n\n      return CFontImageDef(texs, interval, zeus::CVector2f(cropX, cropY));\n    } else if (BeginsWith(str, len, u\"SI\")) {\n      /* Scaled single texture */\n      AdvanceCommaPos();\n      float cropX = u16stof(&iterable[tokenPos]);\n      AdvanceTokenPos();\n\n      AdvanceCommaPos();\n      float cropY = u16stof(&iterable[tokenPos]);\n      AdvanceTokenPos();\n\n      AdvanceCommaPos();\n      TToken<CTexture> tex = x0_store.GetObj({SBIG('TXTR'), GetAssetIdFromString(&iterable[tokenPos], len, txtrMap)});\n      AdvanceTokenPos();\n\n      return CFontImageDef(tex, zeus::CVector2f(cropX, cropY));\n    }\n  }\n\n  TToken<CTexture> tex = x0_store.GetObj({SBIG('TXTR'), GetAssetIdFromString(str, len, txtrMap)});\n  return CFontImageDef(tex, zeus::CVector2f(1.f, 1.f));\n}\n\nCAssetId CTextParser::GetAssetIdFromString(const char16_t* str, int len,\n                                           const std::vector<std::pair<CAssetId, CAssetId>>* txtrMap) {\n  u8 r = GetColorValue(str);\n  u8 g = GetColorValue(str + 2);\n  u8 b = GetColorValue(str + 4);\n  u8 a = GetColorValue(str + 6);\n  CAssetId id = ((r << 24) | (g << 16) | (b << 8) | a) & 0xffffffff;\n\n  if (len == 16) {\n    r = GetColorValue(str + 8);\n    g = GetColorValue(str + 10);\n    b = GetColorValue(str + 12);\n    a = GetColorValue(str + 14);\n    id = (id.Value() << 32) | (((r << 24) | (g << 16) | (b << 8) | a) & 0xffffffff);\n  }\n\n  if (txtrMap) {\n    auto search = rstl::binary_find(txtrMap->begin(), txtrMap->end(), id,\n                                    [](const std::pair<CAssetId, CAssetId>& a) { return a.first; });\n    if (search != txtrMap->end())\n      id = search->second;\n  }\n\n  return id;\n}\n\nTToken<CRasterFont> CTextParser::GetFont(const char16_t* str, int len) {\n  return x0_store.GetObj({SBIG('FONT'), GetAssetIdFromString(str, len, nullptr)});\n}\n\nvoid CTextParser::ParseText(CTextExecuteBuffer& out, const char16_t* str, int len,\n                            const std::vector<std::pair<CAssetId, CAssetId>>* txtrMap) {\n  int b = 0, e = 0;\n  for (b = 0, e = 0; str[e] && (len == -1 || e < len);) {\n    if (str[e] != u'&') {\n      ++e;\n      continue;\n    }\n    if ((len == -1 || e + 1 < len) && str[e + 1] != u'&') {\n      if (e > b)\n        out.AddString(str + b, e - b);\n      ++e;\n      b = e;\n\n      while (str[e] && (len == -1 || e < len) && str[e] != u';')\n        ++e;\n\n      ParseTag(out, str + b, e - b, txtrMap);\n      b = e + 1;\n    } else {\n      out.AddString(str + b, e + 1 - b);\n      e += 2;\n      b = e;\n    }\n  }\n\n  if (e > b)\n    out.AddString(str + b, e - b);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CTextParser.hpp",
    "content": "#pragma once\n\n#include <utility>\n#include <vector>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/GuiSys/CGuiTextSupport.hpp\"\n\nnamespace metaforce {\nclass CFontImageDef;\nclass CTextExecuteBuffer;\n\nclass CTextParser {\n  IObjectStore& x0_store;\n\n  static CTextColor ParseColor(const char16_t* str, int len);\n  static u8 GetColorValue(const char16_t* str);\n  static u32 FromHex(char16_t ch);\n  static s32 ParseInt(const char16_t* str, int len, bool signVal);\n  static CAssetId GetAssetIdFromString(const char16_t* str, int len,\n                                       const std::vector<std::pair<CAssetId, CAssetId>>* txtrMap);\n  static bool Equals(const char16_t* str, int len, const char16_t* other);\n  static bool BeginsWith(const char16_t* str, int len, const char16_t* other);\n  void ParseTag(CTextExecuteBuffer& out, const char16_t* str, int len,\n                const std::vector<std::pair<CAssetId, CAssetId>>* txtrMap);\n  CFontImageDef GetImage(const char16_t* str, int len, const std::vector<std::pair<CAssetId, CAssetId>>* txtrMap);\n  TToken<CRasterFont> GetFont(const char16_t* str, int len);\n\npublic:\n  explicit CTextParser(IObjectStore& store) : x0_store(store) {}\n  void ParseText(CTextExecuteBuffer& out, const char16_t* str, int len,\n                 const std::vector<std::pair<CAssetId, CAssetId>>* txtrMap);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CTextRenderBuffer.cpp",
    "content": "#include \"Runtime/GuiSys/CTextRenderBuffer.hpp\"\n\n#include \"Runtime/Graphics/CGX.hpp\"\n#include \"Runtime/Graphics/CGraphics.hpp\"\n#include \"Runtime/Graphics/CGraphicsPalette.hpp\"\n#include \"Runtime/Graphics/CTexture.hpp\"\n#include \"Runtime/GuiSys/CFontImageDef.hpp\"\n#include \"Runtime/GuiSys/CFontRenderState.hpp\"\n#include \"Runtime/GuiSys/CInstruction.hpp\"\n#include \"Runtime/GuiSys/CRasterFont.hpp\"\n#include \"Runtime/GuiSys/CTextExecuteBuffer.hpp\"\n\nnamespace metaforce {\n\nCTextRenderBuffer::CTextRenderBuffer(CTextRenderBuffer&&) noexcept = default;\n\nCTextRenderBuffer::CTextRenderBuffer(EMode mode) : x0_mode(mode) {}\n\nCTextRenderBuffer::~CTextRenderBuffer() = default;\n\nCTextRenderBuffer& CTextRenderBuffer::operator=(CTextRenderBuffer&&) noexcept = default;\n\nvoid CTextRenderBuffer::SetPrimitive(const Primitive& prim, s32 idx) {\n  CMemoryStreamOut out(reinterpret_cast<u8*>(x34_bytecode.data() + x24_primOffsets[idx]),\n                       x44_blobSize - x24_primOffsets[idx]);\n  if (prim.x4_command == Command::ImageRender) {\n    out.WriteUint8(static_cast<u8>(Command::ImageRender));\n    out.Put(prim.x8_xPos);\n    out.Put(prim.xa_zPos);\n    out.Put(prim.xe_imageIndex);\n    out.Put(prim.x0_color1.toRGBA());\n  } else if (prim.x4_command == Command::CharacterRender) {\n    out.WriteUint8(static_cast<u8>(Command::CharacterRender));\n    out.Put(prim.x8_xPos);\n    out.Put(prim.xa_zPos);\n    out.Put(u16(prim.xc_glyph));\n    out.Put(prim.x0_color1.toRGBA());\n  }\n}\n\nCTextRenderBuffer::Primitive CTextRenderBuffer::GetPrimitive(s32 idx) const {\n  CMemoryInStream in(reinterpret_cast<const u8*>(x34_bytecode.data() + x24_primOffsets[idx]),\n                     x44_blobSize - x24_primOffsets[idx]);\n  auto cmd = Command(in.ReadChar());\n  if (cmd == Command::ImageRender) {\n    s16 xPos = in.ReadShort();\n    s16 zPos = in.ReadShort();\n    u8 imageIndex = in.ReadChar();\n    CTextColor color(in.ReadUint32());\n    return {color, Command::ImageRender, xPos, zPos, u'\\0', imageIndex};\n  }\n\n  if (cmd == Command::CharacterRender) {\n    s16 xPos = in.ReadShort();\n    s16 zPos = in.ReadShort();\n    char16_t glyph = in.ReadUint16();\n    CTextColor color(in.ReadUint32());\n\n    return {color, Command::CharacterRender, xPos, zPos, glyph, 0};\n  }\n\n  return {CTextColor(zeus::Comp32(0)), Command::Invalid, 0, 0, u'\\0', 0};\n}\n\nu8* CTextRenderBuffer::GetOutStream() {\n  VerifyBuffer();\n  return reinterpret_cast<u8*>(x34_bytecode.data()) + x48_curBytecodeOffset;\n}\n\nvoid CTextRenderBuffer::VerifyBuffer() {\n  if (x34_bytecode.empty()) {\n    x34_bytecode.resize(x44_blobSize);\n  }\n}\n\nvoid CTextRenderBuffer::SetMode(EMode mode) { x0_mode = mode; }\n\nint CTextRenderBuffer::GetMatchingPaletteIndex(const CGraphicsPalette& palette) {\n  for (int i = 0; i < x50_palettes.size(); ++i) {\n    if (memcmp(x50_palettes[i]->GetPaletteData(), palette.GetPaletteData(), 8) == 0) {\n      return i;\n    }\n  }\n\n  return -1;\n}\n\nCGraphicsPalette* CTextRenderBuffer::GetNextAvailablePalette() {\n  if (x254_nextPalette < 64) {\n    x50_palettes.push_back(std::make_unique<CGraphicsPalette>(EPaletteFormat::RGB5A3, 4));\n  } else {\n    x254_nextPalette = 0;\n  }\n  ++x254_nextPalette;\n  return x50_palettes[x254_nextPalette - 1].get();\n}\n\nu32 CTextRenderBuffer::GetCurLen() {\n  VerifyBuffer();\n  return x44_blobSize - x48_curBytecodeOffset;\n}\n\nvoid CTextRenderBuffer::Render(const zeus::CColor& color, float time) {\n  x4c_activeFont = -1;\n  x4d_activePalette = -1;\n  CMemoryInStream in(x34_bytecode.data(), x44_blobSize);\n  while (in.GetReadPosition() < x44_blobSize) {\n    auto cmd = static_cast<Command>(in.ReadChar());\n    if (cmd == Command::FontChange) {\n      x4c_activeFont = x4e_queuedFont = in.ReadChar();\n    } else if (cmd == Command::CharacterRender) {\n      if (x4e_queuedFont != -1) {\n        auto font = x4_fonts[x4e_queuedFont];\n        if (font) {\n          font->SetupRenderState();\n          x4e_queuedFont = -1;\n        }\n      }\n      if (x4f_queuedPalette != -1) {\n        x50_palettes[x4f_queuedPalette]->Load();\n        x4f_queuedPalette = -1;\n      }\n\n      s16 offX = in.ReadShort();\n      s16 offY = in.ReadShort();\n      char16_t chr = in.ReadShort();\n      zeus::CColor chrColor(static_cast<zeus::Comp32>(in.ReadLong()));\n      if (x4c_activeFont != -1) {\n        auto font = x4_fonts[x4c_activeFont];\n        if (font && font->GetGlyph(chr) != nullptr) {\n          const auto* glyph = font->GetGlyph(chr);\n          CGX::SetTevKColor(GX_KCOLOR0, chrColor * color);\n          CGX::Begin(GX_TRIANGLESTRIP, GX_VTXFMT0, 4);\n          {\n            GXPosition3f32(offX, 0.f, offY);\n            GXTexCoord2f32(glyph->GetStartU(), glyph->GetStartV());\n            GXPosition3f32(offX + glyph->GetCellWidth(), 0.f, offY);\n            GXTexCoord2f32(glyph->GetEndU(), glyph->GetStartV());\n            GXPosition3f32(offX, 0.f, offY + glyph->GetCellHeight());\n            GXTexCoord2f32(glyph->GetStartU(), glyph->GetEndV());\n            GXPosition3f32(offX + glyph->GetCellWidth(), 0.f, offY + glyph->GetCellHeight());\n            GXTexCoord2f32(glyph->GetEndU(), glyph->GetEndV());\n          }\n          CGX::End();\n        }\n      }\n    } else if (cmd == Command::ImageRender) {\n      s16 offX = in.ReadShort();\n      s16 offY = in.ReadShort();\n      u8 imageIdx = in.ReadChar();\n      zeus::CColor imageColor(static_cast<zeus::Comp32>(in.ReadLong()));\n      auto imageDef = x14_images[imageIdx];\n      auto tex = imageDef.x4_texs[static_cast<u32>(time * imageDef.x0_fps) % imageDef.x4_texs.size()];\n      if (tex) {\n        tex->Load(GX_TEXMAP0, EClampMode::Clamp);\n        float width = imageDef.x4_texs.front()->GetWidth() * imageDef.x14_cropFactor.x();\n        float height = imageDef.x4_texs.front()->GetHeight() * imageDef.x14_cropFactor.y();\n        float cropXHalf = imageDef.x14_cropFactor.x() * 0.5f;\n        float cropYHalf = imageDef.x14_cropFactor.y() * 0.5f;\n\n        CGX::SetTevKAlphaSel(GX_TEVSTAGE0, GX_TEV_KASEL_K0_A);\n        CGX::SetTevKColorSel(GX_TEVSTAGE0, GX_TEV_KCSEL_K0);\n        CGX::SetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_TEXC, GX_CC_KONST, GX_CC_ZERO);\n        CGX::SetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_TEXA, GX_CA_KONST, GX_CA_ZERO);\n        CGX::SetStandardTevColorAlphaOp(GX_TEVSTAGE0);\n        constexpr std::array skVtxDesc{\n            GXVtxDescList{GX_VA_POS, GX_DIRECT},\n            GXVtxDescList{GX_VA_TEX0, GX_DIRECT},\n            GXVtxDescList{GX_VA_NULL, GX_NONE},\n        };\n        CGX::SetVtxDescv(skVtxDesc.data());\n        CGX::SetNumChans(0);\n        CGX::SetNumTexGens(1);\n        CGX::SetNumTevStages(1);\n        CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL);\n        CGX::SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY, false, GX_PTIDENTITY);\n        CGX::SetTevKColor(GX_KCOLOR0, imageColor * color);\n        CGX::Begin(GX_TRIANGLESTRIP, GX_VTXFMT0, 4);\n        {\n          GXPosition3f32(offX, 0.f, offY);\n          GXTexCoord2f32(0.5f - cropXHalf, 0.5f + cropYHalf);\n          GXPosition3f32(offX + width, 0.f, offY);\n          GXTexCoord2f32(0.5f + cropXHalf, 0.5f + cropYHalf);\n          GXPosition3f32(offX, 0.f, offY + height);\n          GXTexCoord2f32(0.5f - cropXHalf, 0.5f - cropYHalf);\n          GXPosition3f32(offX + width, 0.f, offY + height);\n          GXTexCoord2f32(0.5f + cropXHalf, 0.5f - cropYHalf);\n        }\n        CGX::End();\n        x4e_queuedFont = x4c_activeFont;\n        x4f_queuedPalette = x4d_activePalette;\n      }\n    } else if (cmd == Command::PaletteChange) {\n      x4d_activePalette = x4f_queuedPalette = in.ReadChar();\n    }\n  }\n}\n\nvoid CTextRenderBuffer::AddPaletteChange(const CGraphicsPalette& palette) {\n  if (x0_mode == EMode::BufferFill) {\n    {\n      u8* buf = GetOutStream();\n      CMemoryStreamOut out(buf, GetCurLen());\n      s32 paletteIndex = GetMatchingPaletteIndex(palette);\n      if (paletteIndex == -1) {\n        GetNextAvailablePalette();\n        paletteIndex = x254_nextPalette - 1;\n        CGraphicsPalette* destPalette = x50_palettes[x254_nextPalette - 1].get();\n        u16* data = destPalette->Lock();\n        memcpy(data, palette.GetPaletteData(), 8);\n        destPalette->UnLock();\n      }\n      out.WriteUint8(static_cast<u8>(Command::PaletteChange));\n      out.WriteUint8(paletteIndex);\n      x48_curBytecodeOffset += out.GetNumWrites();\n    }\n  } else {\n    x44_blobSize += 2;\n  }\n}\n\nvoid CTextRenderBuffer::AddImage(const zeus::CVector2i& offset, const CFontImageDef& image) {\n  if (x0_mode == EMode::BufferFill) {\n    CMemoryStreamOut out(GetOutStream(), GetCurLen());\n    x24_primOffsets.reserve(x24_primOffsets.size() + 1);\n    u32 primCap = x24_primOffsets.capacity();\n    if (x24_primOffsets.capacity() <= x24_primOffsets.size()) {\n      x24_primOffsets.reserve(primCap != 0 ? primCap * 2 : 4);\n    }\n    x24_primOffsets.push_back(x48_curBytecodeOffset);\n    x14_images.reserve(x14_images.size() + 1);\n    u32 imgIdx = x14_images.size();\n    x14_images.push_back(image);\n    out.WriteUint8(static_cast<u8>(Command::ImageRender));\n    out.WriteShort(offset.x);\n    out.WriteShort(offset.y);\n    out.WriteUint8(imgIdx);\n    out.WriteLong(zeus::skWhite.toRGBA());\n    x48_curBytecodeOffset += out.GetNumWrites();\n  } else {\n    x44_blobSize += 10;\n  }\n}\n\nvoid CTextRenderBuffer::AddCharacter(const zeus::CVector2i& offset, char16_t ch, const CTextColor& color) {\n  if (x0_mode == EMode::BufferFill) {\n    CMemoryStreamOut out(GetOutStream(), GetCurLen());\n    x24_primOffsets.reserve(x24_primOffsets.size() + 1);\n    u32 primCap = x24_primOffsets.capacity();\n    if (x24_primOffsets.capacity() <= x24_primOffsets.size()) {\n      x24_primOffsets.reserve(primCap != 0 ? primCap * 2 : 4);\n    }\n    x24_primOffsets.push_back(x48_curBytecodeOffset);\n    out.WriteUint8(u32(Command::CharacterRender));\n    out.WriteShort(offset.x);\n    out.WriteShort(offset.y);\n    out.WriteShort(ch);\n    out.WriteUint32(color.toRGBA());\n    x48_curBytecodeOffset += out.GetNumWrites();\n  } else {\n    x44_blobSize += 11;\n  }\n}\n\nvoid CTextRenderBuffer::AddFontChange(const TToken<CRasterFont>& font) {\n  if (x0_mode == EMode::BufferFill) {\n    CMemoryStreamOut out(GetOutStream(), GetCurLen());\n    u32 fontCount = x4_fonts.size();\n    bool found = false;\n    u8 fontIndex = 0;\n    if (fontCount > 0) {\n      for (const auto& tok : x4_fonts) {\n        if (tok.GetObjectReference() == font.GetObjectReference()) {\n          out.WriteUint8(static_cast<u8>(Command::FontChange));\n          out.WriteUint8(fontIndex);\n          found = true;\n          break;\n        }\n        ++fontIndex;\n      }\n    }\n\n    if (!found) {\n      x4_fonts.reserve(x4_fonts.size() + 1);\n      u32 fontIdx = x4_fonts.size();\n      x4_fonts.push_back(font);\n      out.WriteUint8(static_cast<u8>(Command::FontChange));\n      out.WriteUint8(fontIdx);\n    }\n    x48_curBytecodeOffset += out.GetNumWrites();\n  } else {\n    x44_blobSize += 2;\n  }\n}\n\nbool CTextRenderBuffer::HasSpaceAvailable(const zeus::CVector2i& origin, const zeus::CVector2i& extent) {\n  std::pair<zeus::CVector2i, zeus::CVector2i> bounds = AccumulateTextBounds();\n  if (bounds.first.x > bounds.second.x) {\n    return true;\n  }\n\n  if (0 < origin.y) {\n    return false;\n  }\n\n  zeus::CVector2i size = bounds.second - bounds.first;\n  return size.y <= extent.y;\n}\n\nstd::pair<zeus::CVector2i, zeus::CVector2i> CTextRenderBuffer::AccumulateTextBounds() {\n  zeus::CVector2i min{INT_MAX, INT_MAX};\n  zeus::CVector2i max{INT_MIN, INT_MIN};\n  CMemoryInStream in(x34_bytecode.data(), x44_blobSize);\n\n  while (in.GetReadPosition() < x48_curBytecodeOffset) {\n    auto cmd = static_cast<Command>(in.ReadChar());\n    if (cmd == Command::FontChange) {\n      x4c_activeFont = in.ReadChar();\n    } else if (cmd == Command::CharacterRender) {\n      u16 offX = in.ReadShort();\n      u16 offY = in.ReadShort();\n      char16_t chr = in.ReadShort();\n      in.ReadLong();\n      if (x4c_activeFont != -1) {\n        auto font = x4_fonts[x4c_activeFont];\n        if (font) {\n          const auto* glyph = font->GetGlyph(chr);\n          if (glyph != nullptr) {\n            max.x = std::max(max.x, offX + glyph->GetCellWidth());\n            max.y = std::max(max.y, offY + glyph->GetCellHeight());\n            min.x = std::min<int>(min.x, offX);\n            min.y = std::min<int>(min.y, offY);\n          }\n        }\n      }\n    } else if (cmd == Command::ImageRender) {\n      u16 offX = in.ReadShort();\n      u16 offY = in.ReadShort();\n      u8 imageIdx = in.ReadChar();\n      in.ReadLong();\n      const auto& image = x14_images[imageIdx];\n      max.x = std::max(max.x, offX + static_cast<int>(static_cast<float>(image.x4_texs.front()->GetWidth()) *\n                                                      image.x14_cropFactor.x()));\n      max.y = std::max(max.y, offY + static_cast<int>(static_cast<float>(image.x4_texs.front()->GetHeight()) *\n                                                      image.x14_cropFactor.y()));\n      min.x = std::min<int>(min.x, offX);\n      min.y = std::min<int>(min.y, offY);\n    } else if (cmd == Command::PaletteChange) {\n      in.ReadChar();\n    }\n  }\n  return {min, max};\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CTextRenderBuffer.hpp",
    "content": "#pragma once\n\n#include <utility>\n#include <vector>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/GuiSys/CFontImageDef.hpp\"\n#include \"Runtime/GuiSys/CGuiWidget.hpp\"\n\n#include <zeus/CColor.hpp>\n#include <zeus/CMatrix4f.hpp>\n#include <zeus/CVector2f.hpp>\n#include <zeus/CVector2i.hpp>\n\nnamespace metaforce {\nclass CGlyph;\nclass CGraphicsPalette;\nclass CRasterFont;\nclass CTextExecuteBuffer;\n\nusing CTextColor = zeus::CColor;\n\nclass CTextRenderBuffer {\n  friend class CGuiTextSupport;\n  friend class CTextSupportShader;\n\npublic:\n  enum class Command { CharacterRender, ImageRender, FontChange, PaletteChange, Invalid = -1 };\n  struct Primitive {\n    CTextColor x0_color1;\n    Command x4_command;\n    s16 x8_xPos;\n    s16 xa_zPos;\n    char16_t xc_glyph;\n    u8 xe_imageIndex;\n  };\n  enum class EMode { AllocTally, BufferFill };\n\nprivate:\n  EMode x0_mode;\n  std::vector<TToken<CRasterFont>> x4_fonts;\n  std::vector<CFontImageDef> x14_images;\n  std::vector<int> x24_primOffsets;\n  std::vector<char> x34_bytecode;\n  u32 x44_blobSize = 0;\n  u32 x48_curBytecodeOffset = 0;\n  s8 x4c_activeFont = -1;\n  s8 x4d_activePalette = -1;\n  s8 x4e_queuedFont = -1;\n  s8 x4f_queuedPalette = -1;\n  rstl::reserved_vector<std::unique_ptr<CGraphicsPalette>, 64> x50_palettes;\n  s32 x254_nextPalette = 0;\n\npublic:\n  CTextRenderBuffer(CTextRenderBuffer&& other) noexcept;\n  CTextRenderBuffer(EMode mode);\n  ~CTextRenderBuffer();\n\n  CTextRenderBuffer& operator=(CTextRenderBuffer&& other) noexcept;\n\n  void SetPrimitive(const Primitive&, int);\n  [[nodiscard]] Primitive GetPrimitive(int) const;\n  [[nodiscard]] u32 GetPrimitiveCount() const { return x24_primOffsets.size(); }\n  [[nodiscard]] u8* GetOutStream();\n  [[nodiscard]] u32 GetCurLen();\n  void VerifyBuffer();\n  int GetMatchingPaletteIndex(const CGraphicsPalette& palette);\n  [[nodiscard]] CGraphicsPalette* GetNextAvailablePalette();\n  void AddPaletteChange(const CGraphicsPalette& palette);\n  void SetMode(EMode mode);\n  void Render(const CTextColor& col, float time);\n  void AddImage(const zeus::CVector2i& offset, const CFontImageDef& image);\n  void AddCharacter(const zeus::CVector2i& offset, char16_t ch, const CTextColor& color);\n  void AddFontChange(const TToken<CRasterFont>& font);\n\n  [[nodiscard]] bool HasSpaceAvailable(const zeus::CVector2i& origin, const zeus::CVector2i& extent);\n  [[nodiscard]] std::pair<zeus::CVector2i, zeus::CVector2i> AccumulateTextBounds();\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CWordBreakTables.cpp",
    "content": "#include \"Runtime/GuiSys/CWordBreakTables.hpp\"\n\n#include <array>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n\nnamespace metaforce {\nnamespace {\nstruct CCharacterIdentifier {\n  char16_t chr;\n  u32 rank;\n};\n\nconstexpr std::array<CCharacterIdentifier, 63> sCantBeginChars{{\n    {u'!', 1},   {u')', 1},   {u',', 1},   {u'-', 1},   {u'.', 1},   {u':', 1},   {u';', 1},   {u'?', 1},   {u']', 1},\n    {u'}', 1},   {0x92, 1},   {0x94, 1},   {0xBB, 1},   {0x3001, 1}, {0x3002, 1}, {0x3005, 1}, {0x300D, 1}, {0x300F, 1},\n    {0x3011, 1}, {0x3015, 1}, {0x3017, 1}, {0x3019, 1}, {0x301B, 1}, {0x301C, 3}, {0x301E, 1}, {0x302B, 3}, {0x3041, 2},\n    {0x3043, 2}, {0x3045, 2}, {0x3047, 2}, {0x3049, 2}, {0x3063, 2}, {0x3083, 2}, {0x3085, 2}, {0x3087, 2}, {0x308E, 2},\n    {0x309D, 3}, {0x309E, 3}, {0x30A1, 2}, {0x30A3, 2}, {0x30A5, 2}, {0x30A7, 2}, {0x30A9, 2}, {0x30C3, 2}, {0x30E3, 2},\n    {0x30E5, 2}, {0x30E7, 2}, {0x30EE, 2}, {0x30F5, 2}, {0x30F6, 2}, {0x30FC, 2}, {0x30FD, 3}, {0x30FE, 3}, {0xFF01, 1},\n    {0xFF05, 3}, {0xFF09, 1}, {0xFF0D, 1}, {0xFF3D, 1}, {0xFF5D, 1}, {0xFF61, 1}, {0xFF63, 1}, {0xFF64, 1}, {0xFF1F, 1},\n}};\n\nconstexpr std::array<CCharacterIdentifier, 87> sCantEndChars{{\n    {u'#', 2},   {u'$', 2},   {u'(', 1},   {u'@', 2},   {u'B', 4},   {u'C', 4},   {u'D', 4},   {u'E', 4},   {u'F', 4},\n    {u'G', 4},   {u'J', 4},   {u'K', 4},   {u'L', 4},   {u'M', 4},   {u'N', 4},   {u'P', 4},   {u'Q', 4},   {u'R', 4},\n    {u'S', 4},   {u'T', 4},   {u'V', 4},   {u'W', 4},   {u'X', 4},   {u'Y', 4},   {u'Z', 4},   {u'b', 4},   {u'c', 4},\n    {u'd', 4},   {u'f', 4},   {u'g', 4},   {u'h', 4},   {u'j', 4},   {u'k', 4},   {u'l', 4},   {u'm', 4},   {u'n', 4},\n    {u'p', 4},   {u'q', 4},   {u'r', 4},   {u's', 4},   {u't', 4},   {u'v', 4},   {u'w', 4},   {u'x', 4},   {u'y', 4},\n    {u'z', 4},   {0xD1, 4},   {0xF1, 4},   {u'[', 1},   {u'{', 1},   {0x91, 1},   {0x93, 1},   {0xA2, 2},   {0xA3, 2},\n    {0xA5, 2},   {0xA7, 2},   {0xA9, 2},   {0xAB, 1},   {0x20A0, 2}, {0x20A1, 2}, {0x20A2, 2}, {0x20A3, 2}, {0x20A4, 2},\n    {0x20A5, 2}, {0x20A6, 2}, {0x20A7, 2}, {0x20A8, 2}, {0x20A9, 2}, {0x20AA, 2}, {0x20AB, 2}, {0x20AC, 2}, {0x300C, 1},\n    {0x300E, 1}, {0x3010, 1}, {0x3012, 2}, {0x3014, 1}, {0x3016, 1}, {0x3018, 1}, {0x301A, 1}, {0xFF03, 2}, {0xFF04, 2},\n    {0xFF20, 2}, {0xFF3C, 1}, {0xFF5C, 1}, {0xFFE0, 2}, {0xFFE1, 2}, {0xFFEF, 2},\n}};\n} // Anonymous namespace\n\nint CWordBreakTables::GetBeginRank(char16_t ch) {\n  const auto search = rstl::binary_find(sCantBeginChars.cbegin(), sCantBeginChars.cend(), ch,\n                                        [](const CCharacterIdentifier& item) { return item.chr; });\n  if (search == sCantBeginChars.cend()) {\n    return 5;\n  }\n  return search->rank;\n}\n\nint CWordBreakTables::GetEndRank(char16_t ch) {\n  const auto search = rstl::binary_find(sCantEndChars.cbegin(), sCantEndChars.cend(), ch,\n                                        [](const CCharacterIdentifier& item) { return item.chr; });\n  if (search == sCantEndChars.cend()) {\n    return 5;\n  }\n  return search->rank;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/GuiSys/CWordBreakTables.hpp",
    "content": "#pragma once\n\nnamespace metaforce {\n\nclass CWordBreakTables {\npublic:\n  static int GetBeginRank(char16_t ch);\n  static int GetEndRank(char16_t ch);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/IFactory.hpp",
    "content": "#pragma once\n\n#include <chrono>\n#include <functional>\n#include <memory>\n#include <vector>\n\n#include \"Runtime/RetroTypes.hpp\"\n\nnamespace metaforce {\nclass CFactoryMgr;\nclass CObjectReference;\nclass CResLoader;\nclass CSimplePool;\nclass CVParamTransfer;\nclass IDvdRequest;\nclass IObj;\n\nusing CFactoryFnReturn = std::unique_ptr<IObj>;\nusing FFactoryFunc =\n    std::function<CFactoryFnReturn(const metaforce::SObjectTag& tag, metaforce::CInputStream& in,\n                                   const metaforce::CVParamTransfer& vparms, CObjectReference* selfRef)>;\nusing FMemFactoryFunc =\n    std::function<CFactoryFnReturn(const metaforce::SObjectTag& tag, std::unique_ptr<u8[]>&& in, u32 len,\n                                   const metaforce::CVParamTransfer& vparms, CObjectReference* selfRef)>;\n\nclass IFactory {\npublic:\n  virtual ~IFactory() = default;\n  virtual CFactoryFnReturn Build(const SObjectTag&, const CVParamTransfer&, CObjectReference*) = 0;\n  virtual void BuildAsync(const SObjectTag&, const CVParamTransfer&, std::unique_ptr<IObj>*, CObjectReference*) = 0;\n  virtual void CancelBuild(const SObjectTag&) = 0;\n  virtual bool CanBuild(const SObjectTag&) = 0;\n  virtual const SObjectTag* GetResourceIdByName(std::string_view) const = 0;\n  virtual FourCC GetResourceTypeById(CAssetId id) const = 0;\n  virtual void EnumerateResources(const std::function<bool(const SObjectTag&)>& lambda) const = 0;\n  virtual void\n  EnumerateNamedResources(const std::function<bool(std::string_view, const SObjectTag&)>& lambda) const = 0;\n  virtual CResLoader* GetResLoader() { return nullptr; }\n  virtual CFactoryMgr* GetFactoryMgr() { return nullptr; }\n  virtual bool AsyncIdle(std::chrono::nanoseconds target) { return false; }\n\n  /* Non-factory versions, replaces CResLoader */\n  virtual u32 ResourceSize(const metaforce::SObjectTag& tag) = 0;\n  virtual std::shared_ptr<IDvdRequest> LoadResourceAsync(const metaforce::SObjectTag& tag, void* target) = 0;\n  virtual std::shared_ptr<IDvdRequest> LoadResourcePartAsync(const metaforce::SObjectTag& tag, u32 off, u32 size,\n                                                             void* target) = 0;\n  virtual std::unique_ptr<u8[]> LoadResourceSync(const metaforce::SObjectTag& tag) = 0;\n  virtual std::unique_ptr<u8[]> LoadNewResourcePartSync(const metaforce::SObjectTag& tag, u32 off, u32 size) = 0;\n  virtual void GetTagListForFile(const char* pakName, std::vector<SObjectTag>& out) const {}\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/IMain.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/CMainFlowBase.hpp\"\n#include \"Runtime/ConsoleVariables/FileStoreManager.hpp\"\n\n//#include <amuse/amuse.hpp>\n//#include <boo/audiodev/IAudioVoiceEngine.hpp>\n\nnamespace metaforce {\nclass Console;\nclass CVarManager;\nenum class ERegion { USA, JPN, PAL, KOR };\nenum class EGame {\n  Invalid = 0,\n  MetroidPrime1,\n  MetroidPrime2,\n  MetroidPrime3,\n  MetroidPrimeTrilogy,\n};\nenum class EPlatform {\n  GameCube,\n  Wii,\n};\n\nstruct MetaforceVersionInfo {\n  std::string version;\n  ERegion region;\n  EGame game;\n  EPlatform platform;\n  std::string gameTitle;\n};\n\nclass CStopwatch;\nenum class EGameplayResult { None, Win, Lose, Playing };\n\nclass IMain {\npublic:\n  virtual ~IMain() = default;\n  virtual std::string Init(int argc, char** argv, const FileStoreManager& storeMgr, CVarManager* cvarMgr) = 0;\n  virtual void Draw() = 0;\n  virtual bool Proc(float dt) = 0;\n  virtual void Shutdown() = 0;\n  virtual EClientFlowStates GetFlowState() const = 0;\n  virtual void SetFlowState(EClientFlowStates) = 0;\n  virtual size_t GetExpectedIdSize() const = 0;\n  virtual EGame GetGame() const = 0;\n  virtual ERegion GetRegion() const = 0;\n  virtual bool IsPAL() const = 0;\n  virtual bool IsJapanese() const = 0;\n  virtual bool IsUSA() const = 0;\n  virtual bool IsKorean() const = 0;\n  virtual bool IsTrilogy() const = 0;\n  virtual std::string GetGameTitle() const = 0;\n  virtual std::string_view GetVersionString() const = 0;\n  virtual void Quit() = 0;\n  virtual bool IsPaused() const = 0;\n  virtual void SetPaused(bool b) = 0;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/IObj.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include \"Runtime/RetroTypes.hpp\"\n\nnamespace metaforce {\n\nclass IObj {\npublic:\n  virtual ~IObj() = default;\n};\n\nclass TObjOwnerDerivedFromIObjUntyped : public IObj {\nprotected:\n  void* m_objPtr;\n\npublic:\n  TObjOwnerDerivedFromIObjUntyped(void* objPtr) : m_objPtr(objPtr) {}\n};\n\ntemplate <class T>\nclass TObjOwnerDerivedFromIObj : public TObjOwnerDerivedFromIObjUntyped {\n  TObjOwnerDerivedFromIObj(T* objPtr) : TObjOwnerDerivedFromIObjUntyped(objPtr) {}\n\npublic:\n  static std::unique_ptr<TObjOwnerDerivedFromIObj<T>> GetNewDerivedObject(std::unique_ptr<T>&& obj) {\n    return std::unique_ptr<TObjOwnerDerivedFromIObj<T>>(new TObjOwnerDerivedFromIObj<T>(obj.release()));\n  }\n  ~TObjOwnerDerivedFromIObj() override { std::default_delete<T>()(static_cast<T*>(m_objPtr)); }\n  T* GetObj() { return static_cast<T*>(m_objPtr); }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/IObjFactory.hpp",
    "content": "#pragma once\n\nnamespace metaforce {\n\nclass IObjFactory {\npublic:\n  virtual ~IObjFactory() = default;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/IObjectStore.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\nnamespace metaforce {\nclass CToken;\nclass CVParamTransfer;\nclass IFactory;\nstruct SObjectTag;\n\nclass IObjectStore {\npublic:\n  virtual ~IObjectStore() = default;\n  virtual CToken GetObj(const SObjectTag&, const CVParamTransfer&) = 0;\n  virtual CToken GetObj(const SObjectTag&) = 0;\n  virtual CToken GetObj(std::string_view) = 0;\n  virtual CToken GetObj(std::string_view, const CVParamTransfer&) = 0;\n  virtual bool HasObject(const SObjectTag&) const = 0;\n  virtual bool ObjectIsLive(const SObjectTag&) const = 0;\n  virtual IFactory& GetFactory() const = 0;\n  virtual void Flush() = 0;\n  virtual void ObjectUnreferenced(const SObjectTag&) = 0;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/IRuntimeMain.hpp",
    "content": "#pragma once\n\nnamespace metaforce {\nstruct IRuntimeMain {\n  void init() = 0;\n  int proc() = 0;\n  void stop() = 0;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/IVParamObj.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include \"Runtime/IObj.hpp\"\n\nnamespace metaforce {\n\nclass IVParamObj : public IObj {\npublic:\n  ~IVParamObj() override = default;\n};\n\ntemplate <class T>\nclass TObjOwnerParam : public IVParamObj {\n  T m_param;\n\npublic:\n  TObjOwnerParam(T&& obj) : m_param(std::move(obj)) {}\n  T& GetParam() noexcept { return m_param; }\n  const T& GetParam() const noexcept { return m_param; }\n};\n\nclass CVParamTransfer {\n  std::shared_ptr<IVParamObj> m_ref;\n\npublic:\n  constexpr CVParamTransfer() noexcept = default;\n  CVParamTransfer(IVParamObj* obj) : m_ref(obj) {}\n\n  CVParamTransfer(const CVParamTransfer& other) noexcept = default;\n  CVParamTransfer& operator=(const CVParamTransfer&) noexcept = default;\n\n  CVParamTransfer(CVParamTransfer&&) noexcept = default;\n  CVParamTransfer& operator=(CVParamTransfer&&) noexcept = default;\n\n  IVParamObj* GetObj() const noexcept { return m_ref.get(); }\n  CVParamTransfer ShareTransferRef() const noexcept { return CVParamTransfer(*this); }\n\n  template <class T>\n  T& GetOwnedObj() const noexcept {\n    return static_cast<TObjOwnerParam<T>*>(GetObj())->GetParam();\n  }\n\n  static CVParamTransfer Null() noexcept { return CVParamTransfer(); }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/ImGuiConsole.cpp",
    "content": "#include <zeus/CVector2f.hpp>\n#include <array>\n#include <ranges>\n#define IM_VEC2_CLASS_EXTRA                                                                                            \\\n  ImVec2(const zeus::CVector2f& v) {                                                                                   \\\n    x = v.x();                                                                                                         \\\n    y = v.y();                                                                                                         \\\n  }                                                                                                                    \\\n  operator zeus::CVector2f() const { return zeus::CVector2f{x, y}; }\n\n#include \"ImGuiConsole.hpp\"\n\n#include \"../version.h\"\n#include \"MP1/MP1.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/ImGuiEntitySupport.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"ImGuiEngine.hpp\"\n#include \"Runtime/Logging.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\n#include <aurora/aurora.h>\n#include <aurora/gfx.h>\n#include <SDL3/SDL_dialog.h>\n#include <SDL3/SDL_error.h>\n#include <magic_enum.hpp>\n#include <imgui_internal.h>\n#include <zeus/CEulerAngles.hpp>\n\nnamespace ImGui {\n// Internal functions\nvoid ClearIniSettings();\n} // namespace ImGui\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nstd::array<ImGuiEntityEntry, kMaxEntities> ImGuiConsole::entities;\nstd::set<TUniqueId> ImGuiConsole::inspectingEntities;\nImGuiPlayerLoadouts ImGuiConsole::loadouts;\nextern SDL_Window* g_window;\n\nImGuiConsole::ImGuiConsole(CVarManager& cvarMgr, CVarCommons& cvarCommons)\n: m_cvarMgr(cvarMgr), m_cvarCommons(cvarCommons) {}\n\nvoid ImGuiStringViewText(std::string_view text) {\n  // begin()/end() do not work on MSVC\n  ImGui::TextUnformatted(text.data(), text.data() + text.size());\n}\n\nvoid ImGuiTextCenter(std::string_view text) {\n  ImGui::NewLine();\n  float fontSize = ImGui::CalcTextSize(text.data(), text.data() + text.size()).x;\n  ImGui::SameLine(ImGui::GetWindowSize().x / 2 - fontSize + fontSize / 2);\n  ImGuiStringViewText(text);\n}\n\nbool ImGuiButtonCenter(std::string_view text) {\n  ImGui::NewLine();\n  float fontSize = ImGui::CalcTextSize(text.data(), text.data() + text.size()).x;\n  fontSize += ImGui::GetStyle().FramePadding.x;\n  ImGui::SameLine(ImGui::GetWindowSize().x / 2 - fontSize + fontSize / 2);\n  return ImGui::Button(text.data());\n}\n\nstatic std::unordered_map<CAssetId, std::unique_ptr<CDummyWorld>> dummyWorlds;\nstatic std::unordered_map<CAssetId, TCachedToken<CStringTable>> stringTables;\n\nstd::string ImGuiLoadStringTable(CAssetId stringId, int idx) {\n  if (!stringId.IsValid()) {\n    return \"\"s;\n  }\n  if (!stringTables.contains(stringId)) {\n    stringTables[stringId] = g_SimplePool->GetObj(SObjectTag{SBIG('STRG'), stringId});\n  }\n  return CStringExtras::ConvertToUTF8(stringTables[stringId].GetObj()->GetString(idx));\n}\n\nstatic bool ContainsCaseInsensitive(std::string_view str, std::string_view val) {\n  return std::search(str.begin(), str.end(), val.begin(), val.end(),\n                     [](char ch1, char ch2) { return std::toupper(ch1) == std::toupper(ch2); }) != str.end();\n}\n\nstatic std::vector<std::pair<std::string, CAssetId>> ListWorlds() {\n  std::vector<std::pair<std::string, CAssetId>> worlds;\n  for (const auto& pak : g_ResFactory->GetResLoader()->GetPaks()) {\n    if (!pak->IsWorldPak()) {\n      continue;\n    }\n    CAssetId worldId = pak->GetMLVLId();\n    if (!dummyWorlds.contains(worldId)) {\n      dummyWorlds[worldId] = std::make_unique<CDummyWorld>(worldId, false);\n    }\n    auto& world = dummyWorlds[worldId];\n    bool complete = world->ICheckWorldComplete();\n    if (!complete) {\n      continue;\n    }\n    CAssetId stringId = world->IGetStringTableAssetId();\n    if (!stringId.IsValid()) {\n      continue;\n    }\n    worlds.emplace_back(ImGuiLoadStringTable(stringId, 0), worldId);\n  }\n  return worlds;\n}\n\nstatic std::vector<std::pair<std::string, TAreaId>> ListAreas(CAssetId worldId) {\n  std::vector<std::pair<std::string, TAreaId>> areas;\n  const auto& world = dummyWorlds[worldId];\n  for (TAreaId i = 0; i < world->IGetAreaCount(); ++i) {\n    const auto* area = world->IGetAreaAlways(i);\n    if (area == nullptr) {\n      continue;\n    }\n    CAssetId stringId = area->IGetStringTableAssetId();\n    if (!stringId.IsValid()) {\n      continue;\n    }\n    areas.emplace_back(ImGuiLoadStringTable(stringId, 0), i);\n  }\n  return areas;\n}\n\nstatic void Warp(const CAssetId worldId, TAreaId aId) {\n  g_GameState->SetCurrentWorldId(worldId);\n  g_GameState->GetWorldTransitionManager()->DisableTransition();\n  if (aId >= g_GameState->CurrentWorldState().GetLayerState()->GetAreaCount()) {\n    aId = 0;\n  }\n  g_GameState->CurrentWorldState().SetAreaId(aId);\n  g_Main->SetFlowState(EClientFlowStates::None);\n  if (g_StateManager != nullptr) {\n    g_StateManager->SetWarping(true);\n    g_StateManager->SetShouldQuitGame(true);\n  } else {\n    // TODO(encounter): warp from menu?\n  }\n}\n\nstatic inline float GetScale() { return 1.f; }\n\nvoid ImGuiConsole::ShowMenuGame() {\n  if (g_Main != nullptr) {\n    m_paused = g_Main->IsPaused();\n  }\n  if (ImGui::MenuItem(\"Paused\", \"F5\", &m_paused, g_Main != nullptr)) {\n    g_Main->SetPaused(m_paused);\n  }\n  if (ImGui::MenuItem(\"Step Frame\", \"F6\", &m_stepFrame, m_paused)) {\n    g_Main->SetPaused(false);\n  }\n  if (ImGui::BeginMenu(\"Warp\", m_cheats && g_StateManager != nullptr && g_ResFactory != nullptr &&\n                                   g_ResFactory->GetResLoader() != nullptr)) {\n    for (const auto& world : ListWorlds()) {\n      if (ImGui::BeginMenu(world.first.c_str())) {\n        for (const auto& area : ListAreas(world.second)) {\n          if (ImGui::MenuItem(area.first.c_str())) {\n            Warp(world.second, area.second);\n          }\n        }\n        ImGui::EndMenu();\n      }\n    }\n    ImGui::EndMenu();\n  }\n  if (ImGui::MenuItem(\"Quit\", \"Alt+F4\")) {\n    m_quitRequested = true;\n  }\n}\n\nvoid ImGuiConsole::LerpDebugColor(CActor* act) {\n  if (!act->m_debugSelected && !act->m_debugHovered) {\n    act->m_debugAddColorTime = 0.f;\n    act->m_debugAddColor = zeus::skClear;\n    return;\n  }\n  act->m_debugAddColorTime += ImGui::GetIO().DeltaTime;\n  float lerp = act->m_debugAddColorTime;\n  if (lerp > 2.f) {\n    lerp = 0.f;\n    act->m_debugAddColorTime = 0.f;\n  } else if (lerp > 1.f) {\n    lerp = 2.f - lerp;\n  }\n  act->m_debugAddColor = zeus::CColor::lerp(zeus::skClear, zeus::skBlue, lerp);\n}\n\nvoid ImGuiConsole::UpdateEntityEntries() {\n  CObjectList& list = g_StateManager->GetAllObjectList();\n  s16 uid = list.GetFirstObjectIndex();\n  while (uid != -1) {\n    ImGuiEntityEntry& entry = ImGuiConsole::entities[uid];\n    if (entry.uid == kInvalidUniqueId || entry.ent == nullptr) {\n      CEntity* ent = list.GetObjectByIndex(uid);\n      entry.uid = ent->GetUniqueId();\n      entry.ent = ent;\n      entry.type = ent->ImGuiType();\n      entry.name = ent->GetName();\n      entry.isActor = TCastToPtr<CActor>(ent).IsValid();\n    } else {\n      entry.active = entry.ent->GetActive();\n    }\n    if (entry.isActor) {\n      LerpDebugColor(entry.AsActor());\n    }\n    entry.ent->m_debugHovered = false;\n    uid = list.GetNextObjectIndex(uid);\n  }\n}\n\nvoid ImGuiConsole::BeginEntityRow(const ImGuiEntityEntry& entry) {\n  ImGui::PushID(entry.uid.Value());\n  ImGui::TableNextRow();\n  bool isActive = entry.active;\n\n  ImVec4 textColor = ImGui::GetStyleColorVec4(ImGuiCol_Text);\n  if (!isActive) {\n    textColor.w = 0.5f;\n  }\n  ImGui::PushStyleColor(ImGuiCol_Text, textColor);\n\n  if (ImGui::TableNextColumn()) {\n    auto text = fmt::format(\"0x{:04X}\", entry.uid.Value());\n    ImGui::Selectable(text.c_str(), &entry.ent->m_debugSelected,\n                      ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowItemOverlap);\n    if (ImGui::IsItemHovered()) {\n      entry.ent->m_debugHovered = true;\n    }\n\n    if (ImGui::BeginPopupContextItem(text.c_str())) {\n      ImGui::PopStyleColor();\n      if (ImGui::MenuItem(isActive ? \"Deactivate\" : \"Activate\")) {\n        entry.ent->SetActive(!isActive);\n      }\n      if (ImGui::MenuItem(\"Highlight\", nullptr, &entry.ent->m_debugSelected)) {\n        entry.ent->SetActive(!isActive);\n      }\n      // Only allow deletion if none of the objects are player related\n      // The player objects will always be in the first 6 slots\n      if (entry.uid.Value() > 6) {\n        ImGui::Separator();\n        ImGui::PushStyleColor(ImGuiCol_Text, ImVec4{0.77f, 0.12f, 0.23f, 1.f});\n        if (ImGui::MenuItem(\"Delete\")) {\n          g_StateManager->FreeScriptObject(entry.uid);\n        }\n        ImGui::PopStyleColor();\n      }\n      ImGui::EndPopup();\n      ImGui::PushStyleColor(ImGuiCol_Text, textColor);\n    }\n  }\n}\n\nvoid ImGuiConsole::EndEntityRow(const ImGuiEntityEntry& entry) {\n  ImGui::PopStyleColor();\n  if (ImGui::TableNextColumn()) {\n    if (ImGui::SmallButton(\"View\")) {\n      ImGuiConsole::inspectingEntities.insert(entry.uid);\n    }\n  }\n  ImGui::PopID();\n}\n\nstatic void RenderEntityColumns(const ImGuiEntityEntry& entry) {\n  ImGuiConsole::BeginEntityRow(entry);\n  if (ImGui::TableNextColumn()) {\n    ImGuiStringViewText(entry.type);\n  }\n  if (ImGui::TableNextColumn()) {\n    ImGuiStringViewText(entry.name);\n  }\n  ImGuiConsole::EndEntityRow(entry);\n}\n\nvoid ImGuiConsole::ShowInspectWindow(bool* isOpen) {\n  float initialWindowSize = 400.f * GetScale();\n  ImGui::SetNextWindowSize(ImVec2{initialWindowSize, initialWindowSize * 1.5f}, ImGuiCond_FirstUseEver);\n\n  if (ImGui::Begin(\"Inspect\", isOpen)) {\n    CObjectList& list = g_StateManager->GetAllObjectList();\n    ImGui::Text(\"Objects: %d / %d\", list.size(), kMaxEntities);\n    ImGui::SameLine();\n    if (ImGui::SmallButton(\"Deselect all\")) {\n      for (auto* const ent : list) {\n        ent->m_debugSelected = false;\n      }\n    }\n    if (ImGui::Button(\"Clear\")) {\n      m_inspectFilterText.clear();\n    }\n    ImGui::SameLine();\n    ImGui::InputText(\"Filter\", &m_inspectFilterText);\n    ImGui::Checkbox(\"Active\", &m_inspectActiveOnly);\n    ImGui::SameLine();\n    ImGui::Checkbox(\"Current area\", &m_inspectCurrentAreaOnly);\n\n    if (ImGui::BeginTable(\"Entities\", 4,\n                          ImGuiTableFlags_Resizable | ImGuiTableFlags_Sortable | ImGuiTableFlags_RowBg |\n                              ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_ScrollY)) {\n      ImGui::TableSetupColumn(\"ID\",\n                              ImGuiTableColumnFlags_PreferSortAscending | ImGuiTableColumnFlags_DefaultSort |\n                                  ImGuiTableColumnFlags_WidthFixed,\n                              0, 'id');\n      ImGui::TableSetupColumn(\"Type\", ImGuiTableColumnFlags_WidthFixed, 0, 'type');\n      ImGui::TableSetupColumn(\"Name\", ImGuiTableColumnFlags_WidthStretch, 0, 'name');\n      ImGui::TableSetupColumn(\"\", ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_WidthFixed |\n                                      ImGuiTableColumnFlags_NoResize);\n      ImGui::TableSetupScrollFreeze(0, 1);\n      ImGui::TableHeadersRow();\n\n      ImGuiTableSortSpecs* sortSpecs = ImGui::TableGetSortSpecs();\n      bool hasSortSpec = sortSpecs != nullptr &&\n                         // no multi-sort\n                         sortSpecs->SpecsCount == 1 &&\n                         // We can skip sorting if we just want uid ascending,\n                         // since that's how we iterate over CObjectList\n                         (sortSpecs->Specs[0].ColumnUserID != 'id' ||\n                          sortSpecs->Specs[0].SortDirection != ImGuiSortDirection_Ascending);\n      if (!m_inspectFilterText.empty() || m_inspectActiveOnly || m_inspectCurrentAreaOnly || hasSortSpec) {\n        std::vector<s16> sortedList;\n        sortedList.reserve(list.size());\n        s16 uid = list.GetFirstObjectIndex();\n\n        auto currAreaId = kInvalidAreaId;\n        CPlayer* player = nullptr;\n        if (m_inspectCurrentAreaOnly && (player = g_StateManager->Player()) != nullptr) {\n          currAreaId = player->GetAreaIdAlways();\n        }\n\n        while (uid != -1) {\n          ImGuiEntityEntry& entry = ImGuiConsole::entities[uid];\n          if ((!m_inspectActiveOnly || entry.active) &&\n              (!m_inspectCurrentAreaOnly || entry.ent->x4_areaId == currAreaId) &&\n              (m_inspectFilterText.empty() || ContainsCaseInsensitive(entry.type, m_inspectFilterText) ||\n               ContainsCaseInsensitive(entry.name, m_inspectFilterText))) {\n            sortedList.push_back(uid);\n          }\n          uid = list.GetNextObjectIndex(uid);\n        }\n        if (hasSortSpec) {\n          const auto& spec = sortSpecs->Specs[0];\n          if (spec.ColumnUserID == 'id') {\n            if (spec.SortDirection == ImGuiSortDirection_Ascending) {\n              // no-op\n            } else {\n              std::sort(sortedList.begin(), sortedList.end(), [&](s16 a, s16 b) { return a < b; });\n            }\n          } else if (spec.ColumnUserID == 'name') {\n            std::sort(sortedList.begin(), sortedList.end(), [&](s16 a, s16 b) {\n              int compare = ImGuiConsole::entities[a].name.compare(ImGuiConsole::entities[b].name);\n              return spec.SortDirection == ImGuiSortDirection_Ascending ? compare < 0 : compare > 0;\n            });\n          } else if (spec.ColumnUserID == 'type') {\n            std::sort(sortedList.begin(), sortedList.end(), [&](s16 a, s16 b) {\n              int compare = ImGuiConsole::entities[a].type.compare(ImGuiConsole::entities[b].type);\n              return spec.SortDirection == ImGuiSortDirection_Ascending ? compare < 0 : compare > 0;\n            });\n          }\n        }\n        for (const auto& item : sortedList) {\n          RenderEntityColumns(ImGuiConsole::entities[item]);\n        }\n      } else {\n        // Render uid ascending\n        s16 uid = list.GetFirstObjectIndex();\n        while (uid != -1) {\n          RenderEntityColumns(ImGuiConsole::entities[uid]);\n          uid = list.GetNextObjectIndex(uid);\n        }\n      }\n\n      ImGui::EndTable();\n    }\n  }\n  ImGui::End();\n}\n\nbool ImGuiConsole::ShowEntityInfoWindow(TUniqueId uid) {\n  bool open = true;\n  ImGuiEntityEntry& entry = ImGuiConsole::entities[uid.Value()];\n  if (entry.ent == nullptr) {\n    return false;\n  }\n  auto name = fmt::format(\"{}##0x{:04X}\", !entry.name.empty() ? entry.name : entry.type, uid.Value());\n  if (ImGui::Begin(name.c_str(), &open, ImGuiWindowFlags_AlwaysAutoResize)) {\n    ImGui::PushID(uid.Value());\n    entry.ent->ImGuiInspect();\n    ImGui::PopID();\n  }\n  ImGui::End();\n  return open;\n}\n\nvoid ImGuiConsole::ShowConsoleVariablesWindow() {\n  // For some reason the window shows up tiny without this\n  float initialWindowSize = 350.f * GetScale();\n  ImGui::SetNextWindowSize(ImVec2{initialWindowSize, initialWindowSize}, ImGuiCond_FirstUseEver);\n  if (ImGui::Begin(\"Console Variables\", &m_showConsoleVariablesWindow)) {\n    if (ImGui::Button(\"Clear\")) {\n      m_cvarFiltersText.clear();\n    }\n    ImGui::SameLine();\n    ImGui::InputText(\"Filter\", &m_cvarFiltersText);\n    auto cvars = m_cvarMgr.cvars(CVar::EFlags::Any & ~CVar::EFlags::Hidden);\n    if (ImGui::Button(\"Reset to defaults\")) {\n      for (auto* cv : cvars) {\n        if (cv->name() == \"developer\" || cv->name() == \"cheats\") {\n          // don't reset developer or cheats to default\n          continue;\n        }\n        CVarUnlocker l(cv);\n        cv->fromLiteralToType(cv->defaultValue());\n      }\n    }\n    if (ImGui::BeginTable(\"ConsoleVariables\", 2,\n                          ImGuiTableFlags_Resizable | ImGuiTableFlags_Sortable | ImGuiTableFlags_RowBg |\n                              ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_ScrollY)) {\n      ImGui::TableSetupColumn(\"Name\",\n                              ImGuiTableColumnFlags_PreferSortAscending | ImGuiTableColumnFlags_DefaultSort |\n                                  ImGuiTableColumnFlags_WidthFixed,\n                              0, 'name');\n      ImGui::TableSetupColumn(\"Value\", ImGuiTableColumnFlags_WidthStretch, 0, 'val');\n      ImGui::TableSetupScrollFreeze(0, 1);\n      ImGui::TableHeadersRow();\n\n      ImGuiTableSortSpecs* sortSpecs = ImGui::TableGetSortSpecs();\n      bool hasSortSpec = sortSpecs != nullptr &&\n                         // no multi-sort\n                         sortSpecs->SpecsCount == 1;\n      std::vector<CVar*> sortedList;\n      sortedList.reserve(cvars.size());\n\n      for (auto* cvar : cvars) {\n        if (cvar->isHidden()) {\n          continue;\n        }\n        if (!m_cvarFiltersText.empty()) {\n          if (ContainsCaseInsensitive(magic_enum::enum_name(cvar->type()), m_cvarFiltersText) ||\n              ContainsCaseInsensitive(cvar->name(), m_cvarFiltersText)) {\n            sortedList.push_back(cvar);\n          }\n        } else {\n          sortedList.push_back(cvar);\n        }\n      }\n\n      if (hasSortSpec) {\n        const auto& spec = sortSpecs->Specs[0];\n        if (spec.ColumnUserID == 'name') {\n          std::sort(sortedList.begin(), sortedList.end(), [&](CVar* a, CVar* b) {\n            int compare = a->name().compare(b->name());\n            return spec.SortDirection == ImGuiSortDirection_Ascending ? compare < 0 : compare > 0;\n          });\n        } else if (spec.ColumnUserID == 'val') {\n          std::sort(sortedList.begin(), sortedList.end(), [&](CVar* a, CVar* b) {\n            int compare = a->value().compare(b->value());\n            return spec.SortDirection == ImGuiSortDirection_Ascending ? compare < 0 : compare > 0;\n          });\n        }\n\n        for (auto* cv : sortedList) {\n          bool modified = cv->isModified();\n          ImGui::PushID(cv);\n          ImGui::TableNextRow();\n          // Name\n          if (ImGui::TableNextColumn()) {\n            ImGuiStringViewText(cv->name());\n            if (ImGui::IsItemHovered() && !cv->rawHelp().empty()) {\n              std::string sv(cv->rawHelp());\n              ImGui::SetTooltip(\"%s\", sv.c_str());\n            }\n          }\n          // Value\n          if (ImGui::TableNextColumn()) {\n            switch (cv->type()) {\n            case CVar::EType::Boolean: {\n              bool b = cv->toBoolean();\n              if (ImGui::Checkbox(\"\", &b)) {\n                cv->fromBoolean(b);\n                modified = true;\n              }\n              break;\n            }\n            case CVar::EType::Real: {\n              float f = cv->toReal();\n              if (ImGui::DragFloat(\"\", &f)) {\n                cv->fromReal(f);\n                modified = true;\n              }\n              break;\n            }\n            case CVar::EType::Signed: {\n              std::array<s32, 1> i{cv->toSigned()};\n              if (ImGui::DragScalar(\"\", ImGuiDataType_S32, i.data(), i.size())) {\n                cv->fromInteger(i[0]);\n                modified = true;\n              }\n              break;\n            }\n            case CVar::EType::Unsigned: {\n              std::array<u32, 1> i{cv->toUnsigned()};\n              if (ImGui::DragScalar(\"\", ImGuiDataType_U32, i.data(), i.size())) {\n                cv->fromInteger(i[0]);\n                modified = true;\n              }\n              break;\n            }\n            case CVar::EType::Literal: {\n              char buf[4096];\n              strcpy(buf, cv->value().c_str());\n              if (ImGui::InputText(\"\", buf, 4096, ImGuiInputTextFlags_EnterReturnsTrue)) {\n                cv->fromLiteral(buf);\n                modified = true;\n              }\n              break;\n            }\n            case CVar::EType::Vec2f: {\n              auto vec = cv->toVec2f();\n              std::array<float, 2> scalars = {vec.x(), vec.y()};\n              if (ImGui::DragScalarN(\"\", ImGuiDataType_Float, scalars.data(), scalars.size(), 0.1f)) {\n                vec.x() = scalars[0];\n                vec.y() = scalars[1];\n                cv->fromVec2f(vec);\n                modified = true;\n              }\n              break;\n            }\n            case CVar::EType::Vec2d: {\n              auto vec = cv->toVec2d();\n              std::array<double, 2> scalars = {vec.x(), vec.y()};\n              if (ImGui::DragScalarN(\"\", ImGuiDataType_Double, scalars.data(), scalars.size(), 0.1f)) {\n                vec.x() = scalars[0];\n                vec.y() = scalars[1];\n                cv->fromVec2d(vec);\n                modified = true;\n              }\n              break;\n            }\n            case CVar::EType::Vec3f: {\n              auto vec = cv->toVec3f();\n              std::array<float, 3> scalars = {vec.x(), vec.y(), vec.z()};\n              if (cv->isColor()) {\n                if (ImGui::ColorEdit3(\"\", scalars.data())) {\n                  vec.x() = scalars[0];\n                  vec.y() = scalars[1];\n                  vec.z() = scalars[2];\n                  cv->fromVec3f(vec);\n                  modified = true;\n                }\n              } else if (ImGui::DragScalarN(\"\", ImGuiDataType_Float, scalars.data(), scalars.size(), 0.1f)) {\n                vec.x() = scalars[0];\n                vec.y() = scalars[1];\n                vec.z() = scalars[2];\n                cv->fromVec3f(vec);\n                modified = true;\n              }\n              break;\n            }\n            case CVar::EType::Vec3d: {\n              auto vec = cv->toVec3d();\n              std::array<double, 3> scalars = {vec.x(), vec.y(), vec.z()};\n              if (cv->isColor()) {\n                std::array<float, 3> color{static_cast<float>(scalars[0]), static_cast<float>(scalars[1]),\n                                           static_cast<float>(scalars[2])};\n                if (ImGui::ColorEdit3(\"\", color.data())) {\n                  vec.x() = scalars[0];\n                  vec.y() = scalars[1];\n                  vec.z() = scalars[2];\n                  cv->fromVec3d(vec);\n                  modified = true;\n                }\n              } else if (ImGui::DragScalarN(\"\", ImGuiDataType_Double, scalars.data(), scalars.size(), 0.1f)) {\n                vec.x() = scalars[0];\n                vec.y() = scalars[1];\n                vec.z() = scalars[2];\n                cv->fromVec3d(vec);\n                modified = true;\n              }\n              break;\n            }\n            case CVar::EType::Vec4f: {\n              auto vec = cv->toVec4f();\n              std::array<float, 4> scalars = {vec.x(), vec.y(), vec.z(), vec.w()};\n              if (cv->isColor()) {\n                if (ImGui::ColorEdit4(\"\", scalars.data())) {\n                  vec.x() = scalars[0];\n                  vec.y() = scalars[1];\n                  vec.z() = scalars[2];\n                  vec.w() = scalars[2];\n                  cv->fromVec4f(vec);\n                  modified = true;\n                }\n              } else if (ImGui::DragScalarN(\"\", ImGuiDataType_Float, scalars.data(), scalars.size(), 0.1f)) {\n                vec.x() = scalars[0];\n                vec.y() = scalars[1];\n                vec.z() = scalars[2];\n                vec.w() = scalars[2];\n                cv->fromVec4f(vec);\n                modified = true;\n              }\n              break;\n            }\n            case CVar::EType::Vec4d: {\n              auto vec = cv->toVec4d();\n              std::array<double, 4> scalars = {vec.x(), vec.y(), vec.z(), vec.w()};\n              if (cv->isColor()) {\n                std::array<float, 4> color{static_cast<float>(scalars[0]), static_cast<float>(scalars[1]),\n                                           static_cast<float>(scalars[2]), static_cast<float>(scalars[3])};\n                if (ImGui::ColorEdit4(\"\", color.data())) {\n                  vec.x() = scalars[0];\n                  vec.y() = scalars[1];\n                  vec.z() = scalars[2];\n                  vec.w() = scalars[2];\n                  cv->fromVec4d(vec);\n                  modified = true;\n                }\n              } else if (ImGui::DragScalarN(\"\", ImGuiDataType_Double, scalars.data(), scalars.size(), 0.1f)) {\n                vec.x() = scalars[0];\n                vec.y() = scalars[1];\n                vec.z() = scalars[2];\n                vec.w() = scalars[2];\n                cv->fromVec4d(vec);\n                modified = true;\n              }\n              break;\n            }\n            default:\n              ImGui::Text(\"lawl wut? Please contact a developer, your copy of Metaforce is cursed!\");\n              break;\n            }\n            if (modified && cv->modificationRequiresRestart()) {\n              ImGui::Text(\"Restart required for value to take affect!\");\n            }\n            if (ImGui::IsItemHovered()) {\n              std::string sv(cv->defaultValue());\n              ImGui::SetTooltip(\"Default: %s\", sv.c_str());\n            }\n          }\n          ImGui::PopID();\n        }\n      }\n      ImGui::EndTable();\n    }\n  }\n  ImGui::End();\n}\n\nvoid fileDialogCallback(void* userdata, const char* const* filelist, [[maybe_unused]] int filter) {\n  auto* self = static_cast<ImGuiConsole*>(userdata);\n  if (filelist != nullptr) {\n    if (filelist[0] == nullptr) {\n      // Cancelled\n      self->m_gameDiscSelected.reset();\n    } else {\n      self->m_gameDiscSelected = filelist[0];\n    }\n  } else {\n    // Error occurred\n    self->m_gameDiscSelected.reset();\n    self->m_errorString = fmt::format(\"File dialog error: {}\", SDL_GetError());\n  }\n}\n\nstatic constexpr std::array<SDL_DialogFileFilter, 2> skGameDiscFileFilters{{\n    {\"Game Disc Images\", \"iso;gcm;ciso;gcz;nfs;rvz;wbfs;wia;tgc\"},\n    {\"All Files\", \"*\"},\n}};\n\nvoid ImGuiConsole::ShowAboutWindow(bool preLaunch) {\n  // Center window\n  ImVec2 center = ImGui::GetMainViewport()->GetCenter();\n  ImGui::SetNextWindowPos(center, preLaunch ? ImGuiCond_Always : ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));\n\n  ImVec4& windowBg = ImGui::GetStyle().Colors[ImGuiCol_WindowBg];\n  ImGui::PushStyleColor(ImGuiCol_TitleBg, windowBg);\n  ImGui::PushStyleColor(ImGuiCol_TitleBgActive, windowBg);\n\n  bool* open = nullptr;\n  ImGuiWindowFlags flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoNav |\n                           ImGuiWindowFlags_NoSavedSettings;\n  if (preLaunch) {\n    flags |= ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove;\n  } else {\n    open = &m_showAboutWindow;\n  }\n  if (ImGui::Begin(\"About\", open, flags)) {\n    float iconSize = 128.f * GetScale();\n    ImGui::SameLine(ImGui::GetWindowSize().x / 2 - iconSize + (iconSize / 2));\n    ImGui::Image(ImGuiEngine::metaforceIcon, ImVec2{iconSize, iconSize});\n    ImGui::PushFont(ImGuiEngine::fontLarge);\n    ImGuiTextCenter(\"Metaforce\");\n    ImGui::PopFont();\n    ImGuiTextCenter(METAFORCE_WC_DESCRIBE);\n    const ImVec2& padding = ImGui::GetStyle().WindowPadding;\n    ImGui::Dummy(padding);\n    if (preLaunch) {\n      if (ImGuiButtonCenter(\"Settings\")) {\n        m_showPreLaunchSettingsWindow = true;\n      }\n      ImGui::Dummy(padding);\n      if (ImGuiButtonCenter(\"Select Game\")) {\n        SDL_ShowOpenFileDialog(&fileDialogCallback, this, g_window, skGameDiscFileFilters.data(),\n                               int(skGameDiscFileFilters.size()), nullptr, false);\n      }\n#ifdef EMSCRIPTEN\n      if (ImGuiButtonCenter(\"Load Game\")) {\n        m_gameDiscSelected = \"game.iso\";\n      }\n#else\n      if (!m_lastDiscPath.empty()) {\n        if (ImGuiButtonCenter(\"Load Previous Game\")) {\n          m_gameDiscSelected = m_lastDiscPath;\n        }\n      }\n#endif\n      ImGui::Dummy(padding);\n    }\n    if (m_errorString) {\n      ImGui::PushStyleColor(ImGuiCol_Text, ImVec4{0.77f, 0.12f, 0.23f, 1.f});\n      ImGui::NewLine();\n      ImGui::PushTextWrapPos(0.f);\n      ImGuiStringViewText(*m_errorString);\n      ImGui::PopTextWrapPos();\n      ImGui::PopStyleColor();\n      ImGui::Dummy(padding);\n    }\n    ImGuiTextCenter(\"2015-2025\");\n    ImGui::BeginGroup();\n    ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 200));\n    ImGuiStringViewText(\"Development & Research\");\n    ImGui::PopStyleColor();\n    ImGuiStringViewText(\"Phillip Stephens (Antidote)\");\n    ImGuiStringViewText(\"Jack Andersen (jackoalan)\");\n    ImGuiStringViewText(\"Luke Street (encounter)\");\n    ImGuiStringViewText(\"Lioncache\");\n    ImGui::EndGroup();\n    ImGui::SameLine();\n    ImGui::BeginGroup();\n    ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 200));\n    ImGuiStringViewText(\"Testing\");\n    ImGui::PopStyleColor();\n    ImGuiStringViewText(\"Tom Lube\");\n    ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 200));\n    ImGuiStringViewText(\"Contributions\");\n    ImGui::PopStyleColor();\n    ImGuiStringViewText(\"Darkszero (Profiling)\");\n    ImGuiStringViewText(\"shio (Weapons)\");\n    ImGui::EndGroup();\n    ImGui::Dummy(padding);\n    ImGui::Separator();\n    if (ImGui::BeginTable(\"Version Info\", 2, ImGuiTableFlags_BordersInnerV)) {\n      ImGui::TableNextRow();\n      if (ImGui::TableNextColumn()) {\n        ImGuiStringViewText(\"Branch\");\n      }\n      if (ImGui::TableNextColumn()) {\n        ImGuiStringViewText(METAFORCE_WC_BRANCH);\n      }\n      ImGui::TableNextRow();\n      if (ImGui::TableNextColumn()) {\n        ImGuiStringViewText(\"Revision\");\n      }\n      if (ImGui::TableNextColumn()) {\n        ImGuiStringViewText(METAFORCE_WC_REVISION);\n      }\n      ImGui::TableNextRow();\n      if (ImGui::TableNextColumn()) {\n        ImGuiStringViewText(\"Build\");\n      }\n      if (ImGui::TableNextColumn()) {\n        ImGuiStringViewText(METAFORCE_DLPACKAGE);\n      }\n      ImGui::TableNextRow();\n      if (ImGui::TableNextColumn()) {\n        ImGuiStringViewText(\"Date\");\n      }\n      if (ImGui::TableNextColumn()) {\n        ImGuiStringViewText(METAFORCE_WC_DATE);\n      }\n      ImGui::TableNextRow();\n      if (ImGui::TableNextColumn()) {\n        ImGuiStringViewText(\"Type\");\n      }\n      if (ImGui::TableNextColumn()) {\n        ImGuiStringViewText(METAFORCE_BUILD_TYPE);\n      }\n      if (g_Main != nullptr) {\n        ImGui::TableNextRow();\n        if (ImGui::TableNextColumn()) {\n          ImGuiStringViewText(\"Game\");\n        }\n        if (ImGui::TableNextColumn()) {\n          ImGuiStringViewText(g_Main->GetVersionString());\n        }\n      }\n      ImGui::EndTable();\n    }\n  }\n  ImGui::End();\n  ImGui::PopStyleColor(2);\n}\n\nstatic std::string BytesToString(size_t bytes) {\n  constexpr std::array suffixes{\"B\"sv, \"KB\"sv, \"MB\"sv, \"GB\"sv, \"TB\"sv, \"PB\"sv, \"EB\"sv};\n  u32 s = 0;\n  auto count = static_cast<double>(bytes);\n  while (count >= 1024.0 && s < 7) {\n    s++;\n    count /= 1024.0;\n  }\n  if (count - floor(count) == 0.0) {\n    return fmt::format(\"{}{}\", static_cast<size_t>(count), suffixes[s]);\n  }\n  return fmt::format(\"{:.1f}{}\", count, suffixes[s]);\n}\n\nvoid ImGuiConsole::ShowDebugOverlay() {\n  const std::array flags{\n      m_frameCounter && (g_StateManager != nullptr),\n      m_frameRate,\n      m_inGameTime && (g_StateManager != nullptr),\n      m_roomTimer && (g_StateManager != nullptr),\n      m_playerInfo && (g_StateManager != nullptr) && (g_StateManager->Player() != nullptr),\n      m_worldInfo && (g_StateManager != nullptr) && m_developer,\n      m_areaInfo && (g_StateManager != nullptr) && m_developer,\n      m_layerInfo && (g_StateManager != nullptr) && m_developer,\n      m_randomStats && m_developer,\n      m_drawCallInfo && m_developer,\n      m_bufferInfo && m_developer,\n      m_pipelineInfo && m_developer,\n      m_resourceStats && (g_SimplePool != nullptr),\n  };\n\n  if (std::ranges::all_of(flags, [](const bool v) { return !v; })) {\n    return;\n  }\n\n  const auto* stats = aurora_get_stats();\n  ImGuiIO& io = ImGui::GetIO();\n  ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize |\n                                 ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav;\n  if (m_debugOverlayCorner != -1) {\n    SetOverlayWindowLocation(m_debugOverlayCorner);\n    windowFlags |= ImGuiWindowFlags_NoMove;\n  }\n  ImGui::SetNextWindowBgAlpha(0.65f);\n  if (ImGui::Begin(\"Debug Overlay\", nullptr, windowFlags)) {\n    bool hasPrevious = false;\n    if (m_frameCounter && g_StateManager != nullptr) {\n      ImGuiStringViewText(fmt::format(\"Frame: {}\\n\", g_StateManager->GetUpdateFrameIndex()));\n      hasPrevious = true;\n    }\n    if (m_frameRate) {\n      if (hasPrevious) {\n        ImGui::Separator();\n      }\n      hasPrevious = true;\n\n      ImGuiStringViewText(fmt::format(\"FPS: {:.1f}\\n\", io.Framerate));\n    }\n    if (m_inGameTime && g_GameState != nullptr) {\n      if (hasPrevious) {\n        ImGui::Separator();\n      }\n      hasPrevious = true;\n\n      double igt = g_GameState->GetTotalPlayTime();\n      u32 ms = u64(igt * 1000) % 1000;\n      auto pt = std::div(int(igt), 3600);\n      ImGuiStringViewText(\n          fmt::format(\"Play Time: {:02d}:{:02d}:{:02d}.{:03d}\\n\", pt.quot, pt.rem / 60, pt.rem % 60, ms));\n    }\n    if (m_roomTimer && g_StateManager != nullptr) {\n      if (hasPrevious) {\n        ImGui::Separator();\n      }\n      hasPrevious = true;\n\n      double igt = g_GameState->GetTotalPlayTime();\n      double currentRoomTime = igt - m_currentRoomStart;\n      u32 curFrames = u32(std::round(u32(currentRoomTime * 60)));\n      u32 lastFrames = u32(std::round(u32(m_lastRoomTime * 60)));\n      ImGuiStringViewText(fmt::format(\"Room Time: {:7.3f} / {:5d} | Last Room:{:7.3f} / {:5d}\\n\", currentRoomTime,\n                                      curFrames, m_lastRoomTime, lastFrames));\n    }\n    if (m_playerInfo && g_StateManager != nullptr && g_StateManager->Player() != nullptr && m_developer) {\n      if (hasPrevious) {\n        ImGui::Separator();\n      }\n      hasPrevious = true;\n\n      const CPlayer& pl = g_StateManager->GetPlayer();\n      const zeus::CQuaternion plQ = zeus::CQuaternion(pl.GetTransform().getRotation().buildMatrix3f());\n      const zeus::CTransform camXf = g_StateManager->GetCameraManager()->GetCurrentCameraTransform(*g_StateManager);\n      const zeus::CQuaternion camQ = zeus::CQuaternion(camXf.getRotation().buildMatrix3f());\n      ImGuiStringViewText(\n          fmt::format(\"Player Position x: {: .2f}, y: {: .2f}, z: {: .2f}\\n\"\n                      \"       Roll: {: .2f}, Pitch: {: .2f}, Yaw: {: .2f}\\n\"\n                      \"       Momentum x: {: .2f}, y: {: .2f}, z: {: .2f}\\n\"\n                      \"       Velocity x: {: .2f}, y: {: .2f}, z: {: .2f}\\n\"\n                      \"Camera Position x: {: .2f}, y: {: .2f}, z {: .2f}\\n\"\n                      \"       Roll: {: .2f}, Pitch: {: .2f}, Yaw: {: .2f}\\n\",\n                      pl.GetTranslation().x(), pl.GetTranslation().y(), pl.GetTranslation().z(),\n                      zeus::radToDeg(plQ.roll()), zeus::radToDeg(plQ.pitch()), zeus::radToDeg(plQ.yaw()),\n                      pl.GetMomentum().x(), pl.GetMomentum().y(), pl.GetMomentum().z(), pl.GetVelocity().x(),\n                      pl.GetVelocity().y(), pl.GetVelocity().z(), camXf.origin.x(), camXf.origin.y(), camXf.origin.z(),\n                      zeus::radToDeg(camQ.roll()), zeus::radToDeg(camQ.pitch()), zeus::radToDeg(camQ.yaw())));\n    }\n    if (m_worldInfo && g_StateManager != nullptr && m_developer) {\n      if (hasPrevious) {\n        ImGui::Separator();\n      }\n      hasPrevious = true;\n\n      const std::string name = ImGuiLoadStringTable(g_StateManager->GetWorld()->IGetStringTableAssetId(), 0);\n      ImGuiStringViewText(fmt::format(\"World Asset ID: 0x{}, Name: {}\\n\", g_GameState->CurrentWorldAssetId(), name));\n    }\n    if (m_areaInfo && g_StateManager != nullptr && m_developer) {\n      const metaforce::TAreaId aId = g_GameState->CurrentWorldState().GetCurrentAreaId();\n      if (g_StateManager->GetWorld() != nullptr && g_StateManager->GetWorld()->DoesAreaExist(aId)) {\n        if (hasPrevious) {\n          ImGui::Separator();\n        }\n        hasPrevious = true;\n\n        const auto& layerStates = g_GameState->CurrentWorldState().GetLayerState();\n        std::string layerBits;\n        u32 totalActive = 0;\n        for (int i = 0; i < layerStates->GetAreaLayerCount(aId); ++i) {\n          if (layerStates->IsLayerActive(aId, i)) {\n            ++totalActive;\n            layerBits += \"1\";\n          } else {\n            layerBits += \"0\";\n          }\n        }\n        CGameArea* pArea = g_StateManager->GetWorld()->GetArea(aId);\n        CAssetId stringId = pArea->IGetStringTableAssetId();\n        ImGuiStringViewText(fmt::format(\"Area Asset ID: 0x{}, Name: {}\\nArea ID: {}, Active Layer bits: {}\\n\",\n                                        pArea->GetAreaAssetId(), ImGuiLoadStringTable(stringId, 0), pArea->GetAreaId(),\n                                        layerBits));\n      }\n    }\n    if (m_layerInfo && g_StateManager != nullptr && m_developer) {\n      const metaforce::TAreaId aId = g_GameState->CurrentWorldState().GetCurrentAreaId();\n      const auto* world = g_StateManager->GetWorld();\n      if (world != nullptr && world->DoesAreaExist(aId) && world->GetWorldLayers()) {\n        if (hasPrevious) {\n          ImGui::Separator();\n        }\n        hasPrevious = true;\n\n        ImGuiStringViewText(\"Area Layers:\");\n\n        ImVec4 activeColor = ImGui::GetStyleColorVec4(ImGuiCol_Text);\n        ImVec4 inactiveColor = activeColor;\n        inactiveColor.w = 0.5f;\n\n        const CWorldLayers& layers = world->GetWorldLayers().value();\n        const auto& layerStates = g_GameState->CurrentWorldState().GetLayerState();\n        int layerCount = int(layerStates->GetAreaLayerCount(aId));\n        u32 startNameIdx = layers.m_areas[aId].m_startNameIdx;\n        if (startNameIdx + layerCount > layers.m_names.size()) {\n          ImGui::Text(\"Broken layer data, please re-package\");\n        } else {\n          for (int i = 0; i < layerCount; ++i) {\n            ImGui::PushStyleColor(ImGuiCol_Text, layerStates->IsLayerActive(aId, i) ? activeColor : inactiveColor);\n            ImGuiStringViewText(\"  \" + layers.m_names[startNameIdx + i]);\n            ImGui::PopStyleColor();\n          }\n        }\n      }\n    }\n    if (m_randomStats && m_developer) {\n      if (hasPrevious) {\n        ImGui::Separator();\n      }\n      hasPrevious = true;\n\n      ImGuiStringViewText(fmt::format(\"CRandom16::Next calls: {}\\n\", metaforce::CRandom16::GetNumNextCalls()));\n      ImGuiStringViewText(fmt::format(\"CRandom16::LastSeed: 0x{:08X}\\n\", CRandom16::GetLastSeed()));\n    }\n    if (m_resourceStats && g_SimplePool != nullptr) {\n      if (hasPrevious) {\n        ImGui::Separator();\n      }\n      hasPrevious = true;\n\n      ImGuiStringViewText(fmt::format(\"Resource Objects: {}\\n\", g_SimplePool->GetLiveObjects()));\n    }\n    if (m_pipelineInfo && m_developer) {\n      if (hasPrevious) {\n        ImGui::Separator();\n      }\n      hasPrevious = true;\n\n      ImGuiStringViewText(fmt::format(\"Queued pipelines:  {}\\n\", stats->queuedPipelines));\n      ImGuiStringViewText(fmt::format(\"Done pipelines:    {}\\n\", stats->createdPipelines));\n    }\n    if (m_drawCallInfo && m_developer) {\n      if (hasPrevious) {\n        ImGui::Separator();\n      }\n      hasPrevious = true;\n\n      ImGuiStringViewText(fmt::format(\"Draw call count:   {}\\n\", stats->drawCallCount));\n      ImGuiStringViewText(fmt::format(\"Merged draw calls: {}\\n\", stats->mergedDrawCallCount));\n    }\n    if (m_bufferInfo && m_developer) {\n      if (hasPrevious) {\n        ImGui::Separator();\n      }\n      hasPrevious = true;\n\n      ImGuiStringViewText(fmt::format(\"Vertex size:       {}\\n\", BytesToString(stats->lastVertSize)));\n      ImGuiStringViewText(fmt::format(\"Uniform size:      {}\\n\", BytesToString(stats->lastUniformSize)));\n      ImGuiStringViewText(fmt::format(\"Index size:        {}\\n\", BytesToString(stats->lastIndexSize)));\n      ImGuiStringViewText(fmt::format(\"Storage size:      {}\\n\", BytesToString(stats->lastStorageSize)));\n      ImGuiStringViewText(fmt::format(\"Tex upload size:   {}\\n\", BytesToString(stats->lastTextureUploadSize)));\n      ImGuiStringViewText(fmt::format(\n          \"Total:             {}\\n\", BytesToString(stats->lastVertSize + stats->lastUniformSize + stats->lastIndexSize +\n                                                   stats->lastStorageSize + stats->lastTextureUploadSize)));\n    }\n    if (ShowCornerContextMenu(m_debugOverlayCorner, m_inputOverlayCorner)) {\n      m_cvarCommons.m_debugOverlayCorner->fromInteger(m_debugOverlayCorner);\n    }\n  }\n  ImGui::End();\n}\nvoid TextCenter(const std::string& text) {\n  float font_size = ImGui::GetFontSize() * text.size() / 2;\n  ImGui::SameLine(ImGui::GetWindowSize().x / 2 - font_size + (font_size / 2));\n\n  ImGui::TextUnformatted(text.c_str());\n}\n\nvoid ImGuiConsole::ShowInputViewer() {\n  if (!m_showInput || g_InputGenerator == nullptr) {\n    return;\n  }\n  auto input = g_InputGenerator->GetLastInput();\n  if (input.ControllerIdx() != 0) {\n    return;\n  }\n\n  u32 thisWhich = input.ControllerIdx();\n  if (m_whichController != thisWhich) {\n    const char* name = PADGetName(thisWhich);\n    if (name != nullptr) {\n      m_controllerName = name;\n      m_whichController = thisWhich;\n    }\n  }\n\n  // Code -stolen- borrowed from Practice Mod\n  ImGuiIO& io = ImGui::GetIO();\n  ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize |\n                                 ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav;\n  if (m_inputOverlayCorner != -1) {\n    SetOverlayWindowLocation(m_inputOverlayCorner);\n    windowFlags |= ImGuiWindowFlags_NoMove;\n  }\n\n  ImGui::SetNextWindowBgAlpha(0.65f);\n  if (ImGui::Begin(\"Input Overlay\", nullptr, windowFlags)) {\n    float scale = GetScale();\n    if (!m_controllerName.empty()) {\n      TextCenter(m_controllerName);\n      ImGui::Separator();\n    }\n    ImDrawList* dl = ImGui::GetWindowDrawList();\n    zeus::CVector2f p = ImGui::GetCursorScreenPos();\n\n    float leftStickRadius = 30 * scale;\n    p = p + zeus::CVector2f{20, 20} * scale; // Pad p so we don't clip outside our rect\n    zeus::CVector2f leftStickCenter = p + zeus::CVector2f(30, 45) * scale;\n    float dpadRadius = 15 * scale;\n    float dpadWidth = 8 * scale;\n    zeus::CVector2f dpadCenter = p + zeus::CVector2f(80, 90) * scale;\n    float rightStickRadius = 20 * scale;\n    zeus::CVector2f rightStickCenter = p + zeus::CVector2f(160, 90) * scale;\n    float startButtonRadius = 8 * scale;\n    zeus::CVector2f startButtonCenter = p + zeus::CVector2f(120, 55) * scale;\n    float aButtonRadius = 16 * scale;\n    zeus::CVector2f aButtonCenter = p + zeus::CVector2f(210, 48) * scale;\n    float bButtonRadius = 8 * scale;\n    zeus::CVector2f bButtonCenter = aButtonCenter + zeus::CVector2f(-24, 16) * scale;\n    float xButtonRadius = 8 * scale;\n    zeus::CVector2f xButtonCenter = aButtonCenter + zeus::CVector2f(24, -16) * scale;\n    float yButtonRadius = 8 * scale;\n    zeus::CVector2f yButtonCenter = aButtonCenter + zeus::CVector2f(-12, -24) * scale;\n    float triggerWidth = leftStickRadius * 2;\n    float triggerHeight = 8 * scale;\n    zeus::CVector2f lCenter = leftStickCenter + zeus::CVector2f(0, -60) * scale;\n    zeus::CVector2f rCenter = zeus::CVector2f(aButtonCenter.x(), lCenter.y());\n    const auto zButtonCenter = rCenter + zeus::CVector2f{0, 24 * scale};\n    const float zButtonHalfWidth = triggerWidth / 2;\n    const float zButtonHalfHeight = 4 * scale;\n\n    constexpr ImU32 stickGray = IM_COL32(150, 150, 150, 255);\n    constexpr ImU32 darkGray = IM_COL32(60, 60, 60, 255);\n    constexpr ImU32 red = IM_COL32(255, 0, 0, 255);\n    constexpr ImU32 green = IM_COL32(0, 255, 0, 255);\n\n    // left stick\n    {\n      float x = input.ALeftX();\n      float y = -input.ALeftY();\n      dl->AddCircleFilled(leftStickCenter, leftStickRadius, stickGray, 8);\n      dl->AddLine(leftStickCenter, leftStickCenter + zeus::CVector2f(x * leftStickRadius, y * leftStickRadius),\n                  IM_COL32(255, 244, 0, 255), 1.5f);\n      dl->AddCircleFilled(leftStickCenter + (zeus::CVector2f{x, y} * leftStickRadius), leftStickRadius / 3, red);\n    }\n\n    // right stick\n    {\n      float x = input.ARightX();\n      float y = -input.ARightY();\n      dl->AddCircleFilled(rightStickCenter, rightStickRadius, stickGray, 8);\n      dl->AddLine(rightStickCenter, rightStickCenter + zeus::CVector2f(x * rightStickRadius, y * rightStickRadius),\n                  IM_COL32(255, 244, 0, 255), 1.5f);\n      dl->AddCircleFilled(rightStickCenter + (zeus::CVector2f{x, y} * rightStickRadius), rightStickRadius / 3, red);\n    }\n\n    // dpad\n    {\n      float halfWidth = dpadWidth / 2;\n      dl->AddRectFilled(dpadCenter + zeus::CVector2f(-halfWidth, -dpadRadius),\n                        dpadCenter + zeus::CVector2f(halfWidth, dpadRadius), stickGray);\n\n      dl->AddRectFilled(dpadCenter + zeus::CVector2f(-dpadRadius, -halfWidth),\n                        dpadCenter + zeus::CVector2f(dpadRadius, halfWidth), stickGray);\n\n      if (input.DDPUp()) {\n        dl->AddRectFilled(dpadCenter + zeus::CVector2f(-halfWidth, -dpadRadius),\n                          dpadCenter + zeus::CVector2f(halfWidth, -dpadRadius / 2), red);\n      }\n\n      if (input.DDPDown()) {\n        dl->AddRectFilled(dpadCenter + zeus::CVector2f(-halfWidth, dpadRadius),\n                          dpadCenter + zeus::CVector2f(halfWidth, dpadRadius / 2), red);\n      }\n\n      if (input.DDPLeft()) {\n        dl->AddRectFilled(dpadCenter + zeus::CVector2f(-dpadRadius, -halfWidth),\n                          dpadCenter + zeus::CVector2f(-dpadRadius / 2, halfWidth), red);\n      }\n\n      if (input.DDPRight()) {\n        dl->AddRectFilled(dpadCenter + zeus::CVector2f(dpadRadius, -halfWidth),\n                          dpadCenter + zeus::CVector2f(dpadRadius / 2, halfWidth), red);\n      }\n    }\n\n    // buttons\n    {\n      // start\n      dl->AddCircleFilled(startButtonCenter, startButtonRadius, input.DStart() ? red : stickGray);\n\n      // a\n      dl->AddCircleFilled(aButtonCenter, aButtonRadius, input.DA() ? green : stickGray);\n\n      // b\n      dl->AddCircleFilled(bButtonCenter, bButtonRadius, input.DB() ? red : stickGray);\n\n      // x\n      dl->AddCircleFilled(xButtonCenter, xButtonRadius, input.DX() ? red : stickGray);\n\n      // y\n      dl->AddCircleFilled(yButtonCenter, yButtonRadius, input.DY() ? red : stickGray);\n\n      // z\n      dl->AddRectFilled(zButtonCenter - zeus::CVector2f{zButtonHalfWidth, zButtonHalfHeight},\n                        zButtonCenter + zeus::CVector2f{zButtonHalfWidth, zButtonHalfHeight},\n                        input.DZ() ? IM_COL32(128, 0, 128, 255) : stickGray, 16);\n    }\n\n    // triggers\n    {\n      float halfTriggerWidth = triggerWidth / 2;\n      zeus::CVector2f lStart = lCenter - zeus::CVector2f(halfTriggerWidth, 0);\n      zeus::CVector2f lEnd = lCenter + zeus::CVector2f(halfTriggerWidth, triggerHeight);\n      float lValue = triggerWidth * std::min(1.f, input.ALTrigger());\n\n      dl->AddRectFilled(lStart, lStart + zeus::CVector2f(lValue, triggerHeight), input.DL() ? red : stickGray);\n      dl->AddRectFilled(lStart + zeus::CVector2f(lValue, 0), lEnd, darkGray);\n\n      zeus::CVector2f rStart = rCenter - zeus::CVector2f(halfTriggerWidth, 0);\n      zeus::CVector2f rEnd = rCenter + zeus::CVector2f(halfTriggerWidth, triggerHeight);\n      float rValue = triggerWidth * std::min(1.f, input.ARTrigger());\n\n      dl->AddRectFilled(rEnd - zeus::CVector2f(rValue, triggerHeight), rEnd, input.DR() ? red : stickGray);\n      dl->AddRectFilled(rStart, rEnd - zeus::CVector2f(rValue, 0), darkGray);\n    }\n\n    ImGui::Dummy(zeus::CVector2f(270, 130) * scale);\n    if (ShowCornerContextMenu(m_inputOverlayCorner, m_debugOverlayCorner)) {\n      m_cvarCommons.m_debugInputOverlayCorner->fromInteger(m_inputOverlayCorner);\n    }\n  }\n  ImGui::End();\n}\n\nbool ImGuiConsole::ShowCornerContextMenu(int& corner, int avoidCorner) const {\n  bool result = false;\n  if (ImGui::BeginPopupContextWindow()) {\n    if (ImGui::MenuItem(\"Custom\", nullptr, corner == -1)) {\n      corner = -1;\n      result = true;\n    }\n    if (ImGui::MenuItem(\"Top-left\", nullptr, corner == 0, avoidCorner != 0)) {\n      corner = 0;\n      result = true;\n    }\n    if (ImGui::MenuItem(\"Top-right\", nullptr, corner == 1, avoidCorner != 1)) {\n      corner = 1;\n      result = true;\n    }\n    if (ImGui::MenuItem(\"Bottom-left\", nullptr, corner == 2, avoidCorner != 2)) {\n      corner = 2;\n      result = true;\n    }\n    if (ImGui::MenuItem(\"Bottom-right\", nullptr, corner == 3, avoidCorner != 3)) {\n      corner = 3;\n      result = true;\n    }\n    ImGui::EndPopup();\n  }\n  return result;\n}\n\nvoid ImGuiConsole::SetOverlayWindowLocation(int corner) const {\n  const ImGuiViewport* viewport = ImGui::GetMainViewport();\n  ImVec2 workPos = viewport->WorkPos; // Use work area to avoid menu-bar/task-bar, if any!\n  ImVec2 workSize = viewport->WorkSize;\n  ImVec2 windowPos;\n  ImVec2 windowPosPivot;\n  constexpr float padding = 10.0f;\n  windowPos.x = (corner & 1) != 0 ? (workPos.x + workSize.x - padding) : (workPos.x + padding);\n  windowPos.y = (corner & 2) != 0 ? (workPos.y + workSize.y - padding) : (workPos.y + padding);\n  windowPosPivot.x = (corner & 1) != 0 ? 1.0f : 0.0f;\n  windowPosPivot.y = (corner & 2) != 0 ? 1.0f : 0.0f;\n  ImGui::SetNextWindowPos(windowPos, ImGuiCond_Always, windowPosPivot);\n}\n\nstatic void ImGuiCVarMenuItem(const char* name, CVar* cvar, bool& value) {\n  if (cvar == nullptr) {\n    return;\n  }\n  if (ImGui::MenuItem(name, nullptr, &value)) {\n    cvar->fromBoolean(value);\n  }\n  if (ImGui::IsItemHovered()) {\n    std::string tooltip{cvar->rawHelp()};\n    if (!tooltip.empty()) {\n      ImGui::SetTooltip(\"%s\", tooltip.c_str());\n    }\n  }\n}\n\nvoid ImGuiConsole::ShowAppMainMenuBar(bool canInspect, bool preLaunch) {\n  if (ImGui::BeginMainMenuBar()) {\n    if (ImGui::BeginMenu(\"Game\")) {\n      ShowMenuGame();\n      ImGui::EndMenu();\n    }\n    if (ImGui::BeginMenu(\"Tools\")) {\n      ImGui::MenuItem(\"Controller Config\", nullptr, &m_controllerConfigVisible);\n      ImGui::MenuItem(\"Items\", nullptr, &m_showItemsWindow, canInspect && m_cheats);\n      if (m_developer) {\n        ImGui::Separator();\n        ImGui::MenuItem(\"Console Variables\", nullptr, &m_showConsoleVariablesWindow);\n        ImGui::MenuItem(\"Inspect\", nullptr, &m_showInspectWindow, canInspect);\n        ImGui::MenuItem(\"Layers\", nullptr, &m_showLayersWindow, canInspect);\n        ImGui::MenuItem(\"Player Transform\", nullptr, &m_showPlayerTransformEditor, canInspect && m_cheats);\n      }\n      ImGui::EndMenu();\n    }\n    if (ImGui::BeginMenu(\"Overlays\")) {\n      ImGuiCVarMenuItem(\"Frame Counter\", m_cvarCommons.m_debugOverlayShowFrameCounter, m_frameCounter);\n      ImGuiCVarMenuItem(\"Frame Rate\", m_cvarCommons.m_debugOverlayShowFramerate, m_frameRate);\n      ImGuiCVarMenuItem(\"In-Game Time\", m_cvarCommons.m_debugOverlayShowInGameTime, m_inGameTime);\n      ImGuiCVarMenuItem(\"Room Timer\", m_cvarCommons.m_debugOverlayShowRoomTimer, m_roomTimer);\n      ImGuiCVarMenuItem(\"Player Info\", m_cvarCommons.m_debugOverlayPlayerInfo, m_playerInfo);\n      ImGuiCVarMenuItem(\"World Info\", m_cvarCommons.m_debugOverlayWorldInfo, m_worldInfo);\n      ImGuiCVarMenuItem(\"Area Info\", m_cvarCommons.m_debugOverlayAreaInfo, m_areaInfo);\n      ImGuiCVarMenuItem(\"Layer Info\", m_cvarCommons.m_debugOverlayLayerInfo, m_layerInfo);\n      ImGuiCVarMenuItem(\"Random Stats\", m_cvarCommons.m_debugOverlayShowRandomStats, m_randomStats);\n      ImGuiCVarMenuItem(\"Draw Call Info\", m_cvarCommons.m_debugOverlayDrawCallInfo, m_drawCallInfo);\n      ImGuiCVarMenuItem(\"Pipeline Info\", m_cvarCommons.m_debugOverlayPipelineInfo, m_pipelineInfo);\n      ImGuiCVarMenuItem(\"Buffer Info\", m_cvarCommons.m_debugOverlayBufferInfo, m_bufferInfo);\n      ImGuiCVarMenuItem(\"Resource Stats\", m_cvarCommons.m_debugOverlayShowResourceStats, m_resourceStats);\n      ImGuiCVarMenuItem(\"Show Input\", m_cvarCommons.m_debugOverlayShowInput, m_showInput);\n#if 0 // Currently unimplemented\n      ImGui::Separator();\n      ImGuiCVarMenuItem(\"Draw AI Paths\", m_cvarCommons.m_debugToolDrawAiPath, m_drawAiPath);\n      ImGuiCVarMenuItem(\"Draw Lighting\", m_cvarCommons.m_debugToolDrawLighting, m_drawLighting);\n      ImGuiCVarMenuItem(\"Draw Collision Actors\", m_cvarCommons.m_debugToolDrawCollisionActors, m_drawCollisionActors);\n      ImGuiCVarMenuItem(\"Draw Maze Path\", m_cvarCommons.m_debugToolDrawMazePath, m_drawMazePath);\n      ImGuiCVarMenuItem(\"Draw Platform Collision\", m_cvarCommons.m_debugToolDrawPlatformCollision,\n                        m_drawPlatformCollision);\n#endif\n      ImGui::EndMenu();\n    }\n    if (ImGui::BeginMenu(\"Help\")) {\n      ImGui::MenuItem(\"About\", nullptr, &m_showAboutWindow, !preLaunch);\n      if (m_developer) {\n        ImGui::Separator();\n        if (ImGui::BeginMenu(\"ImGui\")) {\n          if (ImGui::MenuItem(\"Clear Settings\")) {\n            ImGui::ClearIniSettings();\n          }\n#ifndef NDEBUG\n          ImGui::MenuItem(\"Show Demo\", nullptr, &m_showDemoWindow);\n#endif\n          ImGui::EndMenu();\n        }\n      }\n      ImGui::EndMenu();\n    }\n    ImGui::EndMainMenuBar();\n  }\n}\n\nvoid ImGuiConsole::ToggleVisible() {\n  if (g_Main != nullptr) {\n    m_isVisible ^= 1;\n  }\n}\n\nvoid ImGuiConsole::PreUpdate() {\n  // OPTICK_EVENT();\n  bool preLaunch = g_Main == nullptr;\n  if (!m_isInitialized) {\n    m_isInitialized = true;\n    m_cvarCommons.m_debugOverlayShowFrameCounter->addListener([this](CVar* c) { m_frameCounter = c->toBoolean(); });\n    m_cvarCommons.m_debugOverlayShowFramerate->addListener([this](CVar* c) { m_frameRate = c->toBoolean(); });\n    m_cvarCommons.m_debugOverlayShowInGameTime->addListener([this](CVar* c) { m_inGameTime = c->toBoolean(); });\n    m_cvarCommons.m_debugOverlayShowRoomTimer->addListener([this](CVar* c) { m_roomTimer = c->toBoolean(); });\n    m_cvarCommons.m_debugOverlayPlayerInfo->addListener([this](CVar* c) { m_playerInfo = c->toBoolean(); });\n    m_cvarCommons.m_debugOverlayWorldInfo->addListener([this](CVar* c) { m_worldInfo = c->toBoolean(); });\n    m_cvarCommons.m_debugOverlayAreaInfo->addListener([this](CVar* c) { m_areaInfo = c->toBoolean(); });\n    m_cvarCommons.m_debugOverlayLayerInfo->addListener([this](CVar* c) { m_layerInfo = c->toBoolean(); });\n    m_cvarCommons.m_debugOverlayShowRandomStats->addListener([this](CVar* c) { m_randomStats = c->toBoolean(); });\n    m_cvarCommons.m_debugOverlayShowResourceStats->addListener([this](CVar* c) { m_resourceStats = c->toBoolean(); });\n    m_cvarCommons.m_debugOverlayShowInput->addListener([this](CVar* c) { m_showInput = c->toBoolean(); });\n    m_cvarCommons.m_debugToolDrawAiPath->addListener([this](CVar* c) { m_drawAiPath = c->toBoolean(); });\n    m_cvarCommons.m_debugToolDrawCollisionActors->addListener(\n        [this](CVar* c) { m_drawCollisionActors = c->toBoolean(); });\n    m_cvarCommons.m_debugToolDrawPlatformCollision->addListener(\n        [this](CVar* c) { m_drawPlatformCollision = c->toBoolean(); });\n    m_cvarCommons.m_debugToolDrawMazePath->addListener([this](CVar* c) { m_drawMazePath = c->toBoolean(); });\n    m_cvarCommons.m_debugToolDrawLighting->addListener([this](CVar* c) { m_drawLighting = c->toBoolean(); });\n    m_cvarCommons.m_debugOverlayCorner->addListener([this](CVar* c) { m_debugOverlayCorner = c->toSigned(); });\n    m_cvarCommons.m_debugInputOverlayCorner->addListener([this](CVar* c) { m_inputOverlayCorner = c->toSigned(); });\n    m_cvarCommons.m_lastDiscPath->addListener([this](CVar* c) { m_lastDiscPath = c->toLiteral(); });\n    m_cvarMgr.findCVar(\"developer\")->addListener([this](CVar* c) { m_developer = c->toBoolean(); });\n    m_cvarMgr.findCVar(\"cheats\")->addListener([this](CVar* c) { m_cheats = c->toBoolean(); });\n  }\n  if (!preLaunch && !m_isLaunchInitialized) {\n    if (m_developer) {\n      m_toasts.emplace_back(\"Press Left Alt to toggle menu\"s, 5.f);\n    }\n    m_isLaunchInitialized = true;\n  }\n  // We need to make sure we have a valid CRandom16 at all times, so let's do that here\n  if (g_StateManager != nullptr && g_StateManager->GetActiveRandom() == nullptr) {\n    g_StateManager->SetActiveRandomToDefault();\n  }\n\n  if (!preLaunch) {\n    if (m_stepFrame) {\n      g_Main->SetPaused(true);\n      m_stepFrame = false;\n    }\n    if (m_paused && !m_stepFrame && ImGui::IsKeyPressed(ImGuiKey_F6)) {\n      g_Main->SetPaused(false);\n      m_stepFrame = true;\n    }\n    if (ImGui::IsKeyReleased(ImGuiKey_F5)) {\n      m_paused ^= 1;\n      g_Main->SetPaused(m_paused);\n    }\n  }\n  bool canInspect = g_StateManager != nullptr && g_StateManager->GetObjectList();\n  if (preLaunch || m_isVisible) {\n    ShowAppMainMenuBar(canInspect, preLaunch);\n  }\n  ShowToasts();\n  if (canInspect && (m_showInspectWindow || !inspectingEntities.empty())) {\n    UpdateEntityEntries();\n    if (m_showInspectWindow) {\n      ShowInspectWindow(&m_showInspectWindow);\n    }\n    auto iter = inspectingEntities.begin();\n    while (iter != inspectingEntities.end()) {\n      if (!ShowEntityInfoWindow(*iter)) {\n        iter = inspectingEntities.erase(iter);\n      } else {\n        iter++;\n      }\n    }\n  }\n  if (canInspect && m_showItemsWindow && m_cvarMgr.findCVar(\"cheats\")->toBoolean()) {\n    ShowItemsWindow();\n  }\n  if (canInspect && m_showLayersWindow) {\n    ShowLayersWindow();\n  }\n  if (preLaunch || m_showAboutWindow) {\n    ShowAboutWindow(preLaunch);\n  }\n  if (m_showDemoWindow) {\n    ImGui::ShowDemoWindow(&m_showDemoWindow);\n  }\n  if (m_showConsoleVariablesWindow) {\n    ShowConsoleVariablesWindow();\n  }\n  ShowPlayerTransformEditor();\n  m_controllerConfig.show(m_controllerConfigVisible);\n  if (preLaunch && m_showPreLaunchSettingsWindow) {\n    ShowPreLaunchSettingsWindow();\n  }\n}\n\nvoid ImGuiConsole::PostUpdate() {\n  // OPTICK_EVENT();\n  if (g_StateManager != nullptr && g_StateManager->GetObjectList()) {\n    // Clear deleted objects\n    CObjectList& list = g_StateManager->GetAllObjectList();\n    for (s16 uid = 0; uid < s16(entities.size()); uid++) {\n      ImGuiEntityEntry& item = entities[uid];\n      if (item.uid == kInvalidUniqueId) {\n        continue; // already cleared\n      }\n      CEntity* ent = list.GetObjectByIndex(uid);\n      if (ent == nullptr || ent != item.ent) {\n        // Remove inspect windows for deleted entities\n        inspectingEntities.erase(item.uid);\n        item.uid = kInvalidUniqueId;\n        item.ent = nullptr; // for safety\n      }\n    }\n  } else {\n    entities.fill(ImGuiEntityEntry{});\n    inspectingEntities.clear();\n  }\n\n  // Always calculate room time regardless of if the overlay is displayed, this allows us have an accurate display if\n  // the user chooses to display it later on during gameplay\n  if (g_StateManager != nullptr && m_currentRoom != g_StateManager->GetCurrentArea()) {\n    const double igt = g_GameState->GetTotalPlayTime();\n    m_currentRoom = static_cast<const void*>(g_StateManager->GetCurrentArea());\n    m_lastRoomTime = igt - m_currentRoomStart;\n    m_currentRoomStart = igt;\n  }\n}\n\nvoid ImGuiConsole::PostDraw() {\n  ShowDebugOverlay();\n  ShowInputViewer();\n  ShowPipelineProgress();\n}\n\nvoid ImGuiConsole::Shutdown() {\n  dummyWorlds.clear();\n  stringTables.clear();\n}\n\nstatic constexpr std::array GeneralItems{\n    CPlayerState::EItemType::EnergyTanks,    CPlayerState::EItemType::CombatVisor, CPlayerState::EItemType::ScanVisor,\n    CPlayerState::EItemType::ThermalVisor,   CPlayerState::EItemType::XRayVisor,   CPlayerState::EItemType::GrappleBeam,\n    CPlayerState::EItemType::SpaceJumpBoots, CPlayerState::EItemType::PowerSuit,   CPlayerState::EItemType::VariaSuit,\n    CPlayerState::EItemType::GravitySuit,    CPlayerState::EItemType::PhazonSuit,\n};\n\nstatic constexpr std::array WeaponItems{\n    CPlayerState::EItemType::Missiles,     CPlayerState::EItemType::PowerBeam,   CPlayerState::EItemType::IceBeam,\n    CPlayerState::EItemType::WaveBeam,     CPlayerState::EItemType::PlasmaBeam,  CPlayerState::EItemType::SuperMissile,\n    CPlayerState::EItemType::Flamethrower, CPlayerState::EItemType::IceSpreader, CPlayerState::EItemType::Wavebuster,\n    CPlayerState::EItemType::ChargeBeam,\n};\n\nstatic constexpr std::array MorphBallItems{\n    CPlayerState::EItemType::PowerBombs, CPlayerState::EItemType::MorphBall,  CPlayerState::EItemType::MorphBallBombs,\n    CPlayerState::EItemType::BoostBall,  CPlayerState::EItemType::SpiderBall,\n};\n\nstatic constexpr std::array ArtifactItems{\n    CPlayerState::EItemType::Truth, CPlayerState::EItemType::Strength,  CPlayerState::EItemType::Elder,\n    CPlayerState::EItemType::Wild,  CPlayerState::EItemType::Lifegiver, CPlayerState::EItemType::Warrior,\n    CPlayerState::EItemType::Chozo, CPlayerState::EItemType::Nature,    CPlayerState::EItemType::Sun,\n    CPlayerState::EItemType::World, CPlayerState::EItemType::Spirit,    CPlayerState::EItemType::Newborn,\n};\n\nstatic constexpr std::array ItemLoadout21Percent{\n    std::make_pair(CPlayerState::EItemType::PowerSuit, 1),      std::make_pair(CPlayerState::EItemType::CombatVisor, 1),\n    std::make_pair(CPlayerState::EItemType::ScanVisor, 1),      std::make_pair(CPlayerState::EItemType::PowerBeam, 1),\n    std::make_pair(CPlayerState::EItemType::WaveBeam, 1),       std::make_pair(CPlayerState::EItemType::IceBeam, 1),\n    std::make_pair(CPlayerState::EItemType::PlasmaBeam, 1),     std::make_pair(CPlayerState::EItemType::XRayVisor, 1),\n    std::make_pair(CPlayerState::EItemType::Missiles, 5),       std::make_pair(CPlayerState::EItemType::VariaSuit, 1),\n    std::make_pair(CPlayerState::EItemType::PhazonSuit, 1),     std::make_pair(CPlayerState::EItemType::MorphBall, 1),\n    std::make_pair(CPlayerState::EItemType::MorphBallBombs, 1), std::make_pair(CPlayerState::EItemType::PowerBombs, 4),\n};\n\nstatic constexpr std::array ItemLoadoutAnyPercent{\n    std::make_pair(CPlayerState::EItemType::PowerSuit, 1),      std::make_pair(CPlayerState::EItemType::CombatVisor, 1),\n    std::make_pair(CPlayerState::EItemType::ScanVisor, 1),      std::make_pair(CPlayerState::EItemType::EnergyTanks, 3),\n    std::make_pair(CPlayerState::EItemType::PowerBeam, 1),      std::make_pair(CPlayerState::EItemType::WaveBeam, 1),\n    std::make_pair(CPlayerState::EItemType::IceBeam, 1),        std::make_pair(CPlayerState::EItemType::PlasmaBeam, 1),\n    std::make_pair(CPlayerState::EItemType::ChargeBeam, 1),     std::make_pair(CPlayerState::EItemType::XRayVisor, 1),\n    std::make_pair(CPlayerState::EItemType::ThermalVisor, 1),   std::make_pair(CPlayerState::EItemType::Missiles, 25),\n    std::make_pair(CPlayerState::EItemType::VariaSuit, 1),      std::make_pair(CPlayerState::EItemType::PhazonSuit, 1),\n    std::make_pair(CPlayerState::EItemType::MorphBall, 1),      std::make_pair(CPlayerState::EItemType::BoostBall, 1),\n    std::make_pair(CPlayerState::EItemType::MorphBallBombs, 1), std::make_pair(CPlayerState::EItemType::PowerBombs, 4),\n    std::make_pair(CPlayerState::EItemType::SpaceJumpBoots, 1),\n};\n\nint roundMultiple(int value, int multiple) {\n  if (multiple == 0) {\n    return value;\n  }\n  return static_cast<int>(std::round(static_cast<double>(value) / static_cast<double>(multiple)) *\n                          static_cast<double>(multiple));\n}\n\nstatic void RenderItemType(CPlayerState& pState, CPlayerState::EItemType itemType) {\n  u32 maxValue = CPlayerState::GetPowerUpMaxValue(itemType);\n  std::string name{CPlayerState::ItemTypeToName(itemType)};\n  if (maxValue == 1) {\n    bool enabled = pState.GetItemCapacity(itemType) == 1;\n    if (ImGui::Checkbox(name.c_str(), &enabled)) {\n      if (enabled) {\n        pState.ReInitializePowerUp(itemType, 1);\n        pState.ResetAndIncrPickUp(itemType, 1);\n      } else {\n        pState.ReInitializePowerUp(itemType, 0);\n      }\n      if (itemType == CPlayerState::EItemType::VariaSuit || itemType == CPlayerState::EItemType::PowerSuit ||\n          itemType == CPlayerState::EItemType::GravitySuit || itemType == CPlayerState::EItemType::PhazonSuit) {\n        g_StateManager->Player()->AsyncLoadSuit(*g_StateManager);\n      }\n    }\n  } else if (maxValue > 1) {\n    int capacity = int(pState.GetItemCapacity(itemType));\n    int amount = int(pState.GetItemAmount(itemType));\n    if (ImGui::SliderInt((name + \" (Capacity)\").c_str(), &capacity, 0, int(maxValue), \"%d\",\n                         ImGuiSliderFlags_AlwaysClamp)) {\n      if (itemType == CPlayerState::EItemType::Missiles) {\n        capacity = roundMultiple(capacity, 5);\n      }\n      pState.ReInitializePowerUp(itemType, u32(capacity));\n      pState.ResetAndIncrPickUp(itemType, u32(capacity));\n    }\n    if (capacity > 0) {\n      if (ImGui::SliderInt((name + \" (Amount)\").c_str(), &amount, 0, capacity, \"%d\", ImGuiSliderFlags_AlwaysClamp)) {\n        if (itemType == CPlayerState::EItemType::Missiles) {\n          amount = roundMultiple(amount, 5);\n        }\n        pState.ResetAndIncrPickUp(itemType, u32(amount));\n      }\n    } else {\n      ImGui::Dummy(ImGui::GetItemRectSize());\n    }\n  }\n}\n\ntemplate <size_t N>\nstatic inline void RenderItemsDualColumn(CPlayerState& pState, const std::array<CPlayerState::EItemType, N>& items,\n                                         int start) {\n  ImGui::BeginGroup();\n  // Render left group\n  for (int i = start; i < items.size(); i += 2) {\n    RenderItemType(pState, items[i]);\n  }\n  ImGui::EndGroup();\n  ImGui::SameLine();\n  ImGui::BeginGroup();\n  // Render right group\n  for (int i = start + 1; i < items.size(); i += 2) {\n    RenderItemType(pState, items[i]);\n  }\n  ImGui::EndGroup();\n}\n\nvoid ImGuiConsole::ShowItemsWindow() {\n  CPlayerState& pState = *g_StateManager->GetPlayerState();\n  if (ImGui::Begin(\"Items\", &m_showItemsWindow, ImGuiWindowFlags_AlwaysAutoResize)) {\n    if (ImGui::Button(\"Refill\")) {\n      for (int i = 0; i < int(CPlayerState::EItemType::Max); ++i) {\n        auto itemType = static_cast<CPlayerState::EItemType>(i);\n        u32 maxValue = CPlayerState::GetPowerUpMaxValue(itemType);\n        pState.ResetAndIncrPickUp(itemType, maxValue);\n      }\n    }\n    auto& mapWorldInfo = *g_GameState->CurrentWorldState().MapWorldInfo();\n    ImGui::SameLine();\n    bool mapStationUsed = mapWorldInfo.GetMapStationUsed();\n    if (ImGui::Checkbox(\"Area map\", &mapStationUsed)) {\n      mapWorldInfo.SetMapStationUsed(mapStationUsed);\n    }\n    if (ImGui::Button(\"All\")) {\n      for (int i = 0; i < int(CPlayerState::EItemType::Max); ++i) {\n        auto itemType = static_cast<CPlayerState::EItemType>(i);\n        u32 maxValue = CPlayerState::GetPowerUpMaxValue(itemType);\n        pState.ReInitializePowerUp(itemType, maxValue);\n        pState.ResetAndIncrPickUp(itemType, maxValue);\n      }\n      mapWorldInfo.SetMapStationUsed(true);\n    }\n    ImGui::SameLine();\n    if (ImGui::Button(\"None\")) {\n      for (int i = 0; i < int(CPlayerState::EItemType::Max); ++i) {\n        auto itemType = static_cast<CPlayerState::EItemType>(i);\n        pState.ReInitializePowerUp(itemType, 0);\n      }\n      mapWorldInfo.SetMapStationUsed(false);\n    }\n    ImGui::SameLine();\n    if (ImGui::Button(\"21%\")) {\n      for (int i = 0; i < int(CPlayerState::EItemType::Max); ++i) {\n        auto itemType = static_cast<CPlayerState::EItemType>(i);\n        pState.ReInitializePowerUp(itemType, 0);\n      }\n      mapWorldInfo.SetMapStationUsed(false);\n      for (const auto& [item, count] : ItemLoadout21Percent) {\n        pState.ReInitializePowerUp(item, count);\n        pState.IncrPickup(item, count);\n      }\n      for (const auto& item : ArtifactItems) {\n        pState.ReInitializePowerUp(item, 1);\n      }\n    }\n    ImGui::SameLine();\n    if (ImGui::Button(\"Any%\")) {\n      for (int i = 0; i < int(CPlayerState::EItemType::Max); ++i) {\n        auto itemType = static_cast<CPlayerState::EItemType>(i);\n        pState.ReInitializePowerUp(itemType, 0);\n      }\n      mapWorldInfo.SetMapStationUsed(false);\n      for (const auto& [item, count] : ItemLoadoutAnyPercent) {\n        pState.ReInitializePowerUp(item, count);\n        pState.IncrPickup(item, count);\n      }\n      for (const auto& item : ArtifactItems) {\n        pState.ReInitializePowerUp(item, 1);\n      }\n    }\n\n    if (ImGui::BeginTabBar(\"Items\")) {\n      if (ImGui::BeginTabItem(\"General\")) {\n        RenderItemType(pState, GeneralItems[0]); // full width\n        RenderItemsDualColumn(pState, GeneralItems, 1);\n        ImGui::EndTabItem();\n      }\n      if (ImGui::BeginTabItem(\"Weapons\")) {\n        RenderItemType(pState, WeaponItems[0]); // full width\n        RenderItemsDualColumn(pState, WeaponItems, 1);\n        ImGui::EndTabItem();\n      }\n      if (ImGui::BeginTabItem(\"Morph Ball\")) {\n        RenderItemType(pState, MorphBallItems[0]); // full width\n        RenderItemsDualColumn(pState, MorphBallItems, 1);\n        ImGui::EndTabItem();\n      }\n      if (ImGui::BeginTabItem(\"Artifacts\")) {\n        ImGui::Text(\"NOTE: This doesn't affect Artifact Temple layers\");\n        ImGui::Text(\"Use the Layers window to set them for progression\");\n        RenderItemsDualColumn(pState, ArtifactItems, 0);\n        ImGui::EndTabItem();\n      }\n      ImGui::EndTabBar();\n    }\n  }\n  ImGui::End();\n}\n\nvoid ImGuiConsole::ShowLayersWindow() {\n  // For some reason the window shows up tiny without this\n  float initialWindowSize = 350.f * GetScale();\n  ImGui::SetNextWindowSize(ImVec2{initialWindowSize, initialWindowSize}, ImGuiCond_FirstUseEver);\n\n  if (ImGui::Begin(\"Layers\", &m_showLayersWindow)) {\n    if (ImGui::Button(\"Clear\")) {\n      m_layersFilterText.clear();\n    }\n    ImGui::SameLine();\n    ImGui::InputText(\"Filter\", &m_layersFilterText);\n    bool hasSearch = !m_layersFilterText.empty();\n    if (hasSearch) {\n      // kinda hacky way reset the tree state when search changes\n      ImGui::PushID(m_layersFilterText.c_str());\n    }\n    for (const auto& world : ListWorlds()) {\n      const auto& layers = dummyWorlds[world.second]->GetWorldLayers();\n      if (!layers) {\n        continue;\n      }\n\n      auto worldLayerState = g_GameState->StateForWorld(world.second).GetLayerState();\n      auto areas = ListAreas(world.second);\n      auto iter = areas.begin();\n      while (iter != areas.end()) {\n        if (hasSearch && !ContainsCaseInsensitive(iter->first, m_layersFilterText)) {\n          iter = areas.erase(iter);\n        } else {\n          iter++;\n        }\n      }\n      if (areas.empty()) {\n        continue;\n      }\n\n      if (ImGui::TreeNodeEx(world.first.c_str(), hasSearch ? ImGuiTreeNodeFlags_DefaultOpen : 0)) {\n        for (const auto& area : areas) {\n          u32 layerCount = worldLayerState->GetAreaLayerCount(area.second);\n          if (layerCount == 0) {\n            continue;\n          }\n          if (ImGui::TreeNode(area.first.c_str())) {\n            if (ImGui::Button(\"Warp here\")) {\n              Warp(world.second, area.second);\n            }\n            u32 startNameIdx = layers->m_areas[area.second].m_startNameIdx;\n            if (startNameIdx + layerCount > layers->m_names.size()) {\n              ImGui::Text(\"Broken layer data, please re-package\");\n            } else {\n              for (int layer = 0; layer < layerCount; ++layer) {\n                bool active = worldLayerState->IsLayerActive(area.second, layer);\n                if (ImGui::Checkbox(layers->m_names[startNameIdx + layer].c_str(), &active)) {\n                  worldLayerState->SetLayerActive(area.second, layer, active);\n                }\n              }\n            }\n            ImGui::TreePop();\n          }\n        }\n        ImGui::TreePop();\n      }\n    }\n    if (hasSearch) {\n      ImGui::PopID();\n    }\n  }\n  ImGui::End();\n}\n\nvoid ImGuiConsole::ShowToasts() {\n  if (m_toasts.empty()) {\n    return;\n  }\n  auto& toast = m_toasts.front();\n  const float dt = ImGui::GetIO().DeltaTime;\n  toast.remain -= dt;\n  toast.current += dt;\n\n  const ImGuiViewport* viewport = ImGui::GetMainViewport();\n  const ImVec2 workPos = viewport->WorkPos;\n  const ImVec2 workSize = viewport->WorkSize;\n  constexpr float padding = 10.0f;\n  const ImVec2 windowPos{workPos.x + workSize.x / 2, workPos.y + workSize.y - padding};\n  ImGui::SetNextWindowPos(windowPos, ImGuiCond_Always, ImVec2{0.5f, 1.f});\n\n  const float alpha = std::min({toast.remain, toast.current, 1.f});\n  ImGui::SetNextWindowBgAlpha(alpha * 0.65f);\n  ImVec4 textColor = ImGui::GetStyleColorVec4(ImGuiCol_Text);\n  textColor.w *= alpha;\n  ImVec4 borderColor = ImGui::GetStyleColorVec4(ImGuiCol_Border);\n  borderColor.w *= alpha;\n  ImGui::PushStyleColor(ImGuiCol_Text, textColor);\n  ImGui::PushStyleColor(ImGuiCol_Border, borderColor);\n  if (ImGui::Begin(\"Toast\", nullptr,\n                   ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize |\n                       ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav |\n                       ImGuiWindowFlags_NoMove)) {\n    ImGuiStringViewText(toast.message);\n  }\n  ImGui::End();\n  ImGui::PopStyleColor(2);\n\n  if (toast.remain <= 0.f) {\n    m_toasts.pop_front();\n  }\n}\n\nvoid ImGuiConsole::ShowPlayerTransformEditor() {\n  if (!m_showPlayerTransformEditor) {\n    return;\n  }\n\n  if (ImGui::Begin(\"Player Transform\", &m_showPlayerTransformEditor, ImGuiWindowFlags_AlwaysAutoResize)) {\n    if (ImGui::CollapsingHeader(\"Position\")) {\n      ImGui::PushID(\"player_position\");\n      zeus::CVector3f vec = g_StateManager->GetPlayer().GetTranslation();\n\n      if (ImGuiVector3fInput(\"Position\", vec)) {\n        g_StateManager->GetPlayer().SetTranslation(vec);\n      }\n\n      if (ImGui::Button(\"Save\")) {\n        m_savedLocation.emplace(vec);\n      }\n      ImGui::SameLine();\n      if (ImGui::Button(\"Load\") && m_savedLocation) {\n        g_StateManager->GetPlayer().SetTranslation(*m_savedLocation);\n      }\n      ImGui::SameLine();\n      if (ImGui::Button(\"Clear\") && m_savedLocation) {\n        m_savedLocation.reset();\n      }\n      if (m_savedLocation) {\n        ImGui::Text(\"Saved: %g, %g, %g\", float(m_savedLocation->x()), float(m_savedLocation->y()),\n                    float(m_savedLocation->z()));\n      }\n      ImGui::PopID();\n    }\n    if (ImGui::CollapsingHeader(\"Rotation\")) {\n      ImGui::PushID(\"player_rotation\");\n      zeus::CEulerAngles angles(g_StateManager->GetPlayer().GetTransform());\n      angles = zeus::CEulerAngles(angles * zeus::skRadToDegVec);\n      if (ImGuiVector3fInput(\"Rotation\", angles)) {\n        angles.x() = zeus::clamp(-179.999f, float(angles.x()), 179.999f);\n        angles.y() = zeus::clamp(-89.999f, float(angles.y()), 89.999f);\n        angles.z() = zeus::clamp(-179.999f, float(angles.z()), 179.999f);\n        auto xf = g_StateManager->GetPlayer().GetTransform();\n        xf.setRotation(zeus::CQuaternion(angles * zeus::skDegToRadVec).toTransform().buildMatrix3f());\n        g_StateManager->GetPlayer().SetTransform(xf);\n      }\n\n      if (ImGui::Button(\"Save\")) {\n        m_savedRotation.emplace(angles);\n      }\n      ImGui::SameLine();\n      if (ImGui::Button(\"Load\") && m_savedRotation) {\n        auto xf = g_StateManager->GetPlayer().GetTransform();\n        xf.setRotation(zeus::CQuaternion((*m_savedRotation) * zeus::skDegToRadVec).toTransform().buildMatrix3f());\n        g_StateManager->GetPlayer().SetTransform(xf);\n      }\n      ImGui::SameLine();\n      if (ImGui::Button(\"Clear\") && m_savedRotation) {\n        m_savedRotation.reset();\n      }\n\n      if (m_savedRotation) {\n        ImGui::Text(\"Saved: %g, %g, %g\", float(m_savedRotation->x()), float(m_savedRotation->y()),\n                    float(m_savedRotation->z()));\n      }\n      ImGui::PopID();\n    }\n  }\n  ImGui::End();\n}\n\nvoid ImGuiConsole::ShowPipelineProgress() {\n  const auto* stats = aurora_get_stats();\n  const u32 queuedPipelines = stats->queuedPipelines;\n  if (queuedPipelines == 0) {\n    return;\n  }\n  const u32 createdPipelines = stats->createdPipelines;\n  const u32 totalPipelines = queuedPipelines + createdPipelines;\n\n  const auto* viewport = ImGui::GetMainViewport();\n  const auto padding = viewport->WorkPos.y + 10.f;\n  const auto halfWidth = viewport->GetWorkCenter().x;\n  ImGui::SetNextWindowPos(ImVec2{halfWidth, padding}, ImGuiCond_Always, ImVec2{0.5f, 0.f});\n  ImGui::SetNextWindowSize(ImVec2{halfWidth, 0.f}, ImGuiCond_Always);\n  ImGui::SetNextWindowBgAlpha(0.65f);\n  ImGui::Begin(\"Pipelines\", nullptr,\n               ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoMove |\n                   ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing);\n  const auto percent = static_cast<float>(createdPipelines) / static_cast<float>(totalPipelines);\n  const auto progressStr = fmt::format(\"Processing pipelines: {} / {}\", createdPipelines, totalPipelines);\n  const auto textSize = ImGui::CalcTextSize(progressStr.data(), progressStr.data() + progressStr.size());\n  ImGui::NewLine();\n  ImGui::SameLine(ImGui::GetWindowWidth() / 2.f - textSize.x + textSize.x / 2.f);\n  ImGuiStringViewText(progressStr);\n  ImGui::ProgressBar(percent);\n  ImGui::End();\n}\n\nvoid ImGuiConsole::ControllerAdded(uint32_t idx) {\n  const char* name = PADGetName(idx);\n  if (name != nullptr) {\n    m_toasts.emplace_back(fmt::format(\"Controller {} ({}) connected\", idx, name), 5.f);\n  } else {\n    m_toasts.emplace_back(fmt::format(\"Controller {} connected\", idx), 5.f);\n  }\n}\n\nvoid ImGuiConsole::ControllerRemoved(uint32_t idx) {\n  m_toasts.emplace_back(fmt::format(\"Controller {} disconnected\", idx), 5.f);\n}\n\nstatic void ImGuiCVarCheckbox(CVarManager& mgr, std::string_view cvarName, const char* label, bool* ptr = nullptr) {\n  auto* cvar = mgr.findCVar(cvarName);\n  if (cvar != nullptr) {\n    bool value = cvar->toBoolean();\n    bool modified = false;\n    if (ptr == nullptr) {\n      modified = ImGui::Checkbox(label, &value);\n    } else {\n      modified = ImGui::Checkbox(label, ptr);\n      value = *ptr;\n    }\n    // Kinda useless for these tbh\n    // std::string tooltip{cvar->rawHelp()};\n    // if (!tooltip.empty() && ImGui::IsItemHovered()) {\n    //   ImGui::SetTooltip(\"%s\", tooltip.c_str());\n    // }\n    if (modified) {\n      cvar->unlock();\n      cvar->fromBoolean(value);\n      cvar->lock();\n    }\n  }\n}\n\nvoid ImGuiConsole::ShowPreLaunchSettingsWindow() {\n  if (ImGui::Begin(\"Settings\", &m_showPreLaunchSettingsWindow, ImGuiWindowFlags_AlwaysAutoResize)) {\n    if (ImGui::BeginTabBar(\"Settings\")) {\n      if (ImGui::BeginTabItem(\"Graphics\")) {\n        size_t backendCount = 0;\n        const auto* backends = aurora_get_available_backends(&backendCount);\n        ImGuiStringViewText(fmt::format(\"Current backend: {}\", backend_name(aurora_get_backend())));\n        auto desiredBackend = static_cast<int>(BACKEND_AUTO);\n        if (auto* cvar = m_cvarMgr.findCVar(\"graphicsApi\")) {\n          bool valid = false;\n          const auto name = cvar->toLiteral(&valid);\n          if (valid) {\n            desiredBackend = static_cast<int>(backend_from_string(name));\n          }\n        }\n        bool modified = false;\n        modified = ImGui::RadioButton(\"Auto\", &desiredBackend, static_cast<int>(BACKEND_AUTO));\n        for (size_t i = 0; i < backendCount; ++i, ++backends) {\n          const auto backend = *backends;\n          modified =\n              ImGui::RadioButton(backend_name(backend).data(), &desiredBackend, static_cast<int>(backend)) || modified;\n        }\n        if (modified) {\n          m_cvarCommons.m_graphicsApi->fromLiteral(backend_to_string(static_cast<AuroraBackend>(desiredBackend)));\n        }\n        ImGuiCVarCheckbox(m_cvarMgr, \"fullscreen\", \"Fullscreen\");\n        ImGui::EndTabItem();\n      }\n      if (ImGui::BeginTabItem(\"Game\")) {\n        ImGuiCVarCheckbox(m_cvarMgr, \"allowJoystickInBackground\", \"Enable Background Joystick Input\");\n        ImGuiCVarCheckbox(m_cvarMgr, \"tweak.game.SplashScreensDisabled\", \"Skip Splash Screens\");\n        ImGuiCVarCheckbox(m_cvarMgr, \"cheats\", \"Enable Cheats\", &m_cheats);\n        if (m_cheats) {\n          ImGuiCVarCheckbox(m_cvarMgr, \"developer\", \"Developer Mode\", &m_developer);\n        }\n        ImGui::EndTabItem();\n      }\n      if (ImGui::BeginTabItem(\"Experimental\")) {\n        ImGuiCVarCheckbox(m_cvarMgr, \"variableDt\", \"Variable Delta Time (broken)\");\n        ImGui::EndTabItem();\n      }\n      ImGui::EndTabBar();\n    }\n  }\n  ImGui::End();\n}\n\nstatic bool eq(std::string_view a, std::string_view b) {\n  if (a.size() != b.size()) {\n    return false;\n  }\n  return std::equal(a.begin(), a.end(), b.begin(), b.end(), [](char a, char b) { return tolower(a) == b; });\n}\n\nAuroraBackend backend_from_string(const std::string& str) {\n  if (eq(str, \"d3d12\"sv) || eq(str, \"d3d\"sv)) {\n    return BACKEND_D3D12;\n  }\n  if (eq(str, \"d3d11\")) {\n    return BACKEND_D3D11;\n  }\n  if (eq(str, \"metal\"sv)) {\n    return BACKEND_METAL;\n  }\n  if (eq(str, \"vulkan\"sv) || eq(str, \"vk\"sv)) {\n    return BACKEND_VULKAN;\n  }\n  if (eq(str, \"opengl\"sv) || eq(str, \"gl\"sv)) {\n    return BACKEND_OPENGL;\n  }\n  if (eq(str, \"opengles\"sv) || eq(str, \"gles\"sv)) {\n    return BACKEND_OPENGLES;\n  }\n  if (eq(str, \"webgpu\"sv) || eq(str, \"wgpu\"sv)) {\n    return BACKEND_WEBGPU;\n  }\n  if (eq(str, \"null\"sv) || eq(str, \"none\"sv)) {\n    return BACKEND_NULL;\n  }\n  return BACKEND_AUTO;\n}\n\nstd::string_view backend_to_string(AuroraBackend backend) {\n  switch (backend) {\n  default:\n    return \"auto\"sv;\n  case BACKEND_D3D12:\n    return \"d3d12\"sv;\n  case BACKEND_D3D11:\n    return \"d3d11\"sv;\n  case BACKEND_METAL:\n    return \"metal\"sv;\n  case BACKEND_VULKAN:\n    return \"vulkan\"sv;\n  case BACKEND_OPENGL:\n    return \"opengl\"sv;\n  case BACKEND_OPENGLES:\n    return \"opengles\"sv;\n  case BACKEND_WEBGPU:\n    return \"webgpu\"sv;\n  case BACKEND_NULL:\n    return \"null\"sv;\n  }\n}\n\nstd::string_view backend_name(AuroraBackend backend) {\n  switch (backend) {\n  default:\n    return \"Auto\"sv;\n  case BACKEND_D3D12:\n    return \"D3D12\"sv;\n  case BACKEND_D3D11:\n    return \"D3D11\"sv;\n  case BACKEND_METAL:\n    return \"Metal\"sv;\n  case BACKEND_VULKAN:\n    return \"Vulkan\"sv;\n  case BACKEND_OPENGL:\n    return \"OpenGL\"sv;\n  case BACKEND_OPENGLES:\n    return \"OpenGL ES\"sv;\n  case BACKEND_WEBGPU:\n    return \"WebGPU\"sv;\n  case BACKEND_NULL:\n    return \"Null\"sv;\n  }\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/ImGuiConsole.hpp",
    "content": "#pragma once\n\n#include <set>\n#include <string_view>\n#include <deque>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/World/CActor.hpp\"\n#include \"Runtime/World/CEntity.hpp\"\n#include \"Runtime/ImGuiPlayerLoadouts.hpp\"\n#include \"Runtime/ImGuiControllerConfig.hpp\"\n\n#include \"Runtime/ConsoleVariables/CVarCommons.hpp\"\n#include \"Runtime/ConsoleVariables/CVarManager.hpp\"\n\n#include <aurora/aurora.h>\n#include <zeus/CEulerAngles.hpp>\n\n#if __APPLE__\n#include <TargetConditionals.h>\n#endif\n\nnamespace metaforce {\nvoid ImGuiStringViewText(std::string_view text);\nvoid ImGuiTextCenter(std::string_view text);\nstd::string ImGuiLoadStringTable(CAssetId stringId, int idx);\n\nstruct ImGuiEntityEntry {\n  TUniqueId uid = kInvalidUniqueId;\n  CEntity* ent = nullptr;\n  std::string_view type;\n  std::string_view name;\n  bool active = false;\n  bool isActor = false;\n\n  ImGuiEntityEntry() = default;\n  ImGuiEntityEntry(TUniqueId uid, CEntity* ent, std::string_view type, std::string_view name, bool active)\n  : uid(uid), ent(ent), type(type), name(name), active(active) {}\n\n  [[nodiscard]] CActor* AsActor() const { return isActor ? static_cast<CActor*>(ent) : nullptr; }\n};\n\nstruct Toast {\n  std::string message;\n  float remain;\n  float current = 0.f;\n  Toast(std::string message, float duration) noexcept : message(std::move(message)), remain(duration) {}\n};\n\nclass ImGuiConsole {\npublic:\n  static std::set<TUniqueId> inspectingEntities;\n  static std::array<ImGuiEntityEntry, kMaxEntities> entities;\n  static ImGuiPlayerLoadouts loadouts;\n\n  ImGuiConsole(CVarManager& cvarMgr, CVarCommons& cvarCommons);\n  void PreUpdate();\n  void PostUpdate();\n  void PostDraw();\n  void Shutdown();\n\n  static void BeginEntityRow(const ImGuiEntityEntry& entry);\n  static void EndEntityRow(const ImGuiEntityEntry& entry);\n\n  void ControllerAdded(uint32_t idx);\n  void ControllerRemoved(uint32_t idx);\n  void ToggleVisible();\n\n  std::optional<std::string> m_errorString;\n  std::optional<std::string> m_gameDiscSelected;\n  bool m_quitRequested = false;\n\nprivate:\n  CVarManager& m_cvarMgr;\n  CVarCommons& m_cvarCommons;\n\n  bool m_showInspectWindow = false;\n  bool m_showDemoWindow = false;\n  bool m_showAboutWindow = false;\n  bool m_showItemsWindow = false;\n  bool m_showLayersWindow = false;\n  bool m_showConsoleVariablesWindow = false;\n  bool m_showPlayerTransformEditor = false;\n  bool m_showPreLaunchSettingsWindow = false;\n  std::optional<zeus::CVector3f> m_savedLocation;\n  std::optional<zeus::CEulerAngles> m_savedRotation;\n\n  bool m_paused = false;\n  bool m_stepFrame = false;\n  bool m_isVisible = false;\n\n  bool m_inspectActiveOnly = false;\n  bool m_inspectCurrentAreaOnly = false;\n\n  std::string m_inspectFilterText;\n  std::string m_layersFilterText;\n  std::string m_cvarFiltersText;\n  std::string m_lastDiscPath = m_cvarCommons.m_lastDiscPath->toLiteral();\n\n  // Debug overlays\n  bool m_frameCounter = m_cvarCommons.m_debugOverlayShowFrameCounter->toBoolean();\n#if TARGET_OS_TV\n  bool m_frameRate = true;\n#else\n  bool m_frameRate = m_cvarCommons.m_debugOverlayShowFramerate->toBoolean();\n#endif\n  bool m_inGameTime = m_cvarCommons.m_debugOverlayShowInGameTime->toBoolean();\n  bool m_roomTimer = m_cvarCommons.m_debugOverlayShowRoomTimer->toBoolean();\n  bool m_playerInfo = m_cvarCommons.m_debugOverlayPlayerInfo->toBoolean();\n  bool m_worldInfo = m_cvarCommons.m_debugOverlayWorldInfo->toBoolean();\n  bool m_areaInfo = m_cvarCommons.m_debugOverlayAreaInfo->toBoolean();\n  bool m_layerInfo = m_cvarCommons.m_debugOverlayLayerInfo->toBoolean();\n  bool m_randomStats = m_cvarCommons.m_debugOverlayShowRandomStats->toBoolean();\n  bool m_resourceStats = m_cvarCommons.m_debugOverlayShowResourceStats->toBoolean();\n  bool m_showInput = m_cvarCommons.m_debugOverlayShowInput->toBoolean();\n  bool m_drawAiPath = m_cvarCommons.m_debugToolDrawAiPath->toBoolean();\n  bool m_drawCollisionActors = m_cvarCommons.m_debugToolDrawCollisionActors->toBoolean();\n  bool m_drawPlatformCollision = m_cvarCommons.m_debugToolDrawPlatformCollision->toBoolean();\n  bool m_drawMazePath = m_cvarCommons.m_debugToolDrawMazePath->toBoolean();\n  bool m_drawLighting = m_cvarCommons.m_debugToolDrawLighting->toBoolean();\n#if TARGET_OS_IOS\n  bool m_pipelineInfo = false;\n  bool m_drawCallInfo = false;\n  bool m_bufferInfo = false;\n#else\n  bool m_pipelineInfo = m_cvarCommons.m_debugOverlayPipelineInfo->toBoolean(); // TODO cvar\n  bool m_drawCallInfo = m_cvarCommons.m_debugOverlayDrawCallInfo->toBoolean(); // TODO cvar\n  bool m_bufferInfo = m_cvarCommons.m_debugOverlayBufferInfo->toBoolean();     // TODO cvar\n#endif\n  bool m_developer = m_cvarMgr.findCVar(\"developer\")->toBoolean();\n  bool m_cheats = m_cvarMgr.findCVar(\"cheats\")->toBoolean();\n  bool m_isInitialized = false;\n  bool m_isLaunchInitialized = false;\n\n  int m_debugOverlayCorner = m_cvarCommons.m_debugOverlayCorner->toSigned();\n  int m_inputOverlayCorner = m_cvarCommons.m_debugInputOverlayCorner->toSigned();\n  const void* m_currentRoom = nullptr;\n  double m_lastRoomTime = 0.f;\n  double m_currentRoomStart = 0.f;\n  std::deque<Toast> m_toasts;\n  std::string m_controllerName;\n  u32 m_whichController = -1;\n\n  bool m_controllerConfigVisible = false;\n  ImGuiControllerConfig m_controllerConfig;\n\n  void ShowAboutWindow(bool preLaunch);\n  void ShowAppMainMenuBar(bool canInspect, bool preLaunch);\n  void ShowMenuGame();\n  bool ShowEntityInfoWindow(TUniqueId uid);\n  void ShowInspectWindow(bool* isOpen);\n  void LerpDebugColor(CActor* act);\n  void UpdateEntityEntries();\n  void ShowDebugOverlay();\n  void ShowItemsWindow();\n  void ShowLayersWindow();\n  void ShowConsoleVariablesWindow();\n  void ShowToasts();\n  void ShowInputViewer();\n  void SetOverlayWindowLocation(int corner) const;\n  bool ShowCornerContextMenu(int& corner, int avoidCorner) const;\n  void ShowPlayerTransformEditor();\n  void ShowPipelineProgress();\n  void ShowPreLaunchSettingsWindow();\n};\n\nAuroraBackend backend_from_string(const std::string& str);\nstd::string_view backend_to_string(AuroraBackend backend);\nstd::string_view backend_name(AuroraBackend backend);\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/ImGuiControllerConfig.cpp",
    "content": "#include \"Runtime/ImGuiControllerConfig.hpp\"\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Streams/CFileOutStream.hpp\"\n#include \"Runtime/Streams/ContainerReaders.hpp\"\n#include \"Runtime/Streams/ContainerWriters.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\n#include <imgui.h>\n\nnamespace metaforce {\nImGuiControllerConfig::Button::Button(CInputStream& in)\n: button(in.Get<s32>())\n, uvX(in.Get<u32>())\n, uvY(in.Get<u32>())\n, width(in.Get<u32>())\n, height(in.Get<u32>())\n, offX(in.Get<float>())\n, offY(in.Get<float>()) {}\n\nvoid ImGuiControllerConfig::Button::PutTo(COutputStream& out) const {\n  out.Put(button);\n  out.Put(uvX);\n  out.Put(uvY);\n  out.Put(width);\n  out.Put(height);\n  out.Put(offX);\n  out.Put(offY);\n}\n\nImGuiControllerConfig::ControllerAtlas::ControllerAtlas(CInputStream& in) : name(in.Get<std::string>()) {\n  u32 vidPidCount = in.Get<u32>();\n  vidPids.reserve(vidPidCount);\n\n  for (u32 i = 0; i < vidPidCount; ++i) {\n    u16 vid = static_cast<u16>(in.Get<s16>());\n    u16 pid = static_cast<u16>(in.Get<s16>());\n    vidPids.emplace_back(vid, pid);\n  }\n\n  atlasFile = in.Get<std::string>();\n  read_vector(buttons, in);\n};\n\nvoid ImGuiControllerConfig::ControllerAtlas::PutTo(COutputStream& out) const {\n  out.Put(name);\n  out.Put(static_cast<u32>(vidPids.size()));\n  for (const auto& vidPid : vidPids) {\n    out.Put(vidPid.first);\n    out.Put(vidPid.second);\n  }\n\n  write_vector(buttons, out);\n}\n\nvoid ImGuiControllerConfig::show(bool& visible) {\n\n  /** TODO:\n   * - Implement multiple controllers\n   * - Implement setting controller ports (except for the GameCube adapter, which is hard coded)\n   * - Implement fancy graphical UI\n   */\n\n  if (!visible) {\n    return;\n  }\n\n  if (m_pendingMapping != nullptr) {\n    s32 nativeButton = PADGetNativeButtonPressed(m_pendingPort);\n    if (nativeButton != -1) {\n      m_pendingMapping->nativeButton = nativeButton;\n      m_pendingMapping = nullptr;\n      m_pendingPort = -1;\n      PADBlockInput(false);\n    }\n  }\n\n  std::vector<std::string> controllers;\n  controllers.push_back(\"None\");\n  for (u32 i = 0; i < PADCount(); ++i) {\n    controllers.push_back(fmt::format(\"{}-{}\", PADGetNameForControllerIndex(i), i));\n  }\n\n  m_pendingValid = false;\n  if (ImGui::Begin(\"Controller Config\", &visible)) {\n    if (ImGui::CollapsingHeader(\"Ports\")) {\n      for (u32 i = 0; i < 4; ++i) {\n        ImGui::PushID(fmt::format(\"PortConf-{}\", i).c_str());\n        s32 index = PADGetIndexForPort(i);\n        int sel = 0;\n        std::string name = \"None\";\n        const char* tmpName = PADGetName(i);\n        bool changed = false;\n        if (tmpName != nullptr) {\n          name = fmt::format(\"{}-{}\", tmpName, index);\n        }\n        if (ImGui::BeginCombo(fmt::format(\"Port {}\", i + 1).c_str(), name.c_str())) {\n          for (u32 j = 0; const auto& s : controllers) {\n            if (ImGui::Selectable(s.c_str(), name == s)) {\n              sel = j;\n              changed = true;\n            }\n            ++j;\n          }\n          ImGui::EndCombo();\n        }\n\n        if (changed) {\n          if (sel > 0) {\n            PADSetPortForIndex(sel - 1, i);\n          } else if (sel == 0) {\n            PADClearPort(i);\n          }\n        }\n        ImGui::PopID();\n      }\n    }\n    if (ImGui::BeginTabBar(\"Controllers\")) {\n      for (u32 i = 0; i < 4; ++i) {\n        if (ImGui::BeginTabItem(fmt::format(\"Port {}\", i + 1).c_str())) {\n          ImGui::PushID(fmt::format(\"Port_{}\", i + 1).c_str());\n          /* If the tab is changed while pending for input, cancel the pending port */\n          if (m_pendingMapping != nullptr && m_pendingPort != i) {\n            m_pendingMapping = nullptr;\n            m_pendingValid = false;\n            m_pendingPort = -1;\n            PADBlockInput(false);\n          }\n          u32 vid, pid;\n          PADGetVidPid(i, &vid, &pid);\n          if (vid == 0 && pid == 0) {\n            ImGui::EndTabItem();\n            ImGui::PopID();\n            continue;\n          }\n          ImGui::Text(\"%s\", PADGetName(i));\n          u32 buttonCount = 0;\n          PADButtonMapping* mapping = PADGetButtonMappings(i, &buttonCount);\n          if (mapping != nullptr) {\n            for (u32 m = 0; m < buttonCount; ++m) {\n              const char* padName = PADGetButtonName(mapping[m].padButton);\n              if (padName == nullptr) {\n                continue;\n              }\n              ImGui::PushID(padName);\n              bool pressed = ImGui::Button(padName);\n              ImGui::SameLine();\n              ImGui::Text(\"%s\", PADGetNativeButtonName(mapping[m].nativeButton));\n\n              if (pressed && m_pendingMapping == nullptr) {\n                m_pendingMapping = &mapping[m];\n                m_pendingPort = i;\n                PADBlockInput(true);\n              }\n\n              if (m_pendingMapping == &mapping[m]) {\n                m_pendingValid = true;\n                ImGui::SameLine();\n                ImGui::Text(\" - Waiting for button...\");\n              }\n              ImGui::PopID();\n            }\n          }\n\n          if (ImGui::CollapsingHeader(\"Dead-zones\")) {\n            PADDeadZones* deadZones = PADGetDeadZones(i);\n            ImGui::Checkbox(\"Use Dead-zones\", &deadZones->useDeadzones);\n            float tmp = static_cast<float>(deadZones->stickDeadZone * 100.f) / 32767.f;\n            if (ImGui::DragFloat(\"Left Stick\", &tmp, 0.5f, 0.f, 100.f, \"%.3f%%\")) {\n              deadZones->stickDeadZone = static_cast<u16>((tmp / 100.f) * 32767);\n            }\n            tmp = static_cast<float>(deadZones->substickDeadZone * 100.f) / 32767.f;\n            if (ImGui::DragFloat(\"Right Stick\", &tmp, 0.5f, 0.f, 100.f, \"%.3f%%\")) {\n              deadZones->substickDeadZone = static_cast<u16>((tmp / 100.f) * 32767);\n            }\n            ImGui::Checkbox(\"Emulate Triggers\", &deadZones->emulateTriggers);\n            tmp = static_cast<float>(deadZones->leftTriggerActivationZone * 100.f) / 32767.f;\n            if (ImGui::DragFloat(\"Left Trigger Activation\", &tmp, 0.5f, 0.f, 100.f, \"%.3f%%\")) {\n              deadZones->leftTriggerActivationZone = static_cast<u16>((tmp / 100.f) * 32767);\n            }\n            tmp = static_cast<float>(deadZones->rightTriggerActivationZone * 100.f) / 32767.f;\n            if (ImGui::DragFloat(\"Right Trigger Activation\", &tmp, 0.5f, 0.f, 100.f, \"%.3f%%\")) {\n              deadZones->rightTriggerActivationZone = static_cast<u16>((tmp / 100.f) * 32767);\n            }\n          }\n          ImGui::PopID();\n          ImGui::EndTabItem();\n        }\n      }\n      ImGui::EndTabBar();\n    }\n\n    ImGui::Separator();\n    if (ImGui::Button(\"Display Editor\")) {\n      m_editorVisible = true;\n    }\n    ImGui::SameLine();\n    if (ImGui::Button(\"Save Mappings\")) {\n      PADSerializeMappings();\n    }\n    ImGui::SameLine();\n    if (ImGui::Button(\"Restore Defaults\")) {\n      for (u32 i = 0; i < 4; ++i) {\n        PADRestoreDefaultMapping(i);\n      }\n    }\n  }\n  ImGui::End();\n\n  showEditor(m_editorVisible);\n}\n\nvoid ImGuiControllerConfig::showEditor(bool& visible) {\n  if (!visible) {\n    return;\n  }\n\n  if (ImGui::Begin(\"Controller Atlas Editor\", &visible)) {\n    /* TODO: Atlas editor */\n    ImGui::Separator();\n    if (ImGui::Button(\"Save Controller Database\")) {\n      CFileOutStream out(\"ControllerAtlases.ctrdb\");\n      out.WriteUint32(SLITTLE('CTDB'));\n      out.WriteUint32(1); // Version\n      write_vector(m_controllerAtlases, out);\n    }\n    ImGui::SameLine();\n    if (ImGui::Button(\"Export\") && m_currentAtlas != nullptr) {\n      CFileOutStream out(\"test.ctratlas\");\n      out.Put(SLITTLE('CTRA'));\n      out.Put(1); // Version\n      out.Put(*m_currentAtlas);\n    }\n\n    /* TODO: Import logic */\n  }\n  ImGui::End();\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/ImGuiControllerConfig.hpp",
    "content": "#pragma once\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/Streams/CInputStream.hpp\"\n#include \"Runtime/Streams/COutputStream.hpp\"\n\n#include \"imgui.h\"\n#include <dolphin/pad.h>\n\n#include <array>\n#include <chrono>\n#include <string>\n#include <vector>\n\nnamespace metaforce {\nclass ImGuiControllerConfig {\n  struct Button {\n    s32 button = -1; // the SDL button this entry corresponds to\n    u32 uvX = 0;    // Offset if icon image in atlas from left (in pixels)\n    u32 uvY = 0;    // Offset if icon image in atlas from top (in pixels)\n    u32 width = 32;  // Width of button image (in pixels)\n    u32 height = 32; // Height of button image (in pixels)\n    float offX = 0.f; // Offset from left of config window\n    float offY = 0.f; // Offset from top of config window\n\n    Button() = default;\n    explicit Button(CInputStream& in);\n    void PutTo(COutputStream& in) const;\n  };\n\n  struct ControllerAtlas {\n    std::string name;\n    std::vector<std::pair<u16, u16>> vidPids;\n    std::string atlasFile; // Path to atlas relative to controller definition\n    std::vector<Button> buttons;\n    ImTextureID atlasId;\n    ControllerAtlas() = default;\n    explicit ControllerAtlas(CInputStream& in);\n    void PutTo(COutputStream& out) const;\n  };\n\npublic:\n  void show(bool& visible);\n\nprivate:\n  void showEditor(bool& visible);\n\n  PADButtonMapping* m_pendingMapping = nullptr;\n  s32 m_pendingPort = 0;\n  bool m_pendingValid = false;\n  bool m_editorVisible = false;\n\n\n  ControllerAtlas* m_currentAtlas = nullptr;\n  std::vector<ControllerAtlas> m_controllerAtlases;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/ImGuiEntitySupport.cpp",
    "content": "#include \"Runtime/ImGuiEntitySupport.hpp\"\n\n#include \"Runtime/World/CActor.hpp\"\n#include \"Runtime/World/CAi.hpp\"\n#include \"Runtime/World/CAmbientAI.hpp\"\n#include \"Runtime/World/CDestroyableRock.hpp\"\n#include \"Runtime/World/CEffect.hpp\"\n#include \"Runtime/World/CEntity.hpp\"\n#include \"Runtime/World/CExplosion.hpp\"\n#include \"Runtime/World/CFire.hpp\"\n#include \"Runtime/World/CFishCloud.hpp\"\n#include \"Runtime/World/CFishCloudModifier.hpp\"\n#include \"Runtime/World/CGameLight.hpp\"\n#include \"Runtime/World/CHUDBillboardEffect.hpp\"\n#include \"Runtime/World/CIceImpact.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CRepulsor.hpp\"\n#include \"Runtime/World/CScriptActor.hpp\"\n#include \"Runtime/World/CScriptActorKeyframe.hpp\"\n#include \"Runtime/World/CScriptActorRotate.hpp\"\n#include \"Runtime/World/CScriptAiJumpPoint.hpp\"\n#include \"Runtime/World/CScriptAreaAttributes.hpp\"\n#include \"Runtime/World/CScriptBallTrigger.hpp\"\n#include \"Runtime/World/CScriptBeam.hpp\"\n#include \"Runtime/World/CScriptCameraBlurKeyframe.hpp\"\n#include \"Runtime/World/CScriptCameraFilterKeyframe.hpp\"\n#include \"Runtime/World/CScriptCameraHint.hpp\"\n#include \"Runtime/World/CScriptCameraHintTrigger.hpp\"\n#include \"Runtime/World/CScriptCameraPitchVolume.hpp\"\n#include \"Runtime/World/CScriptCameraShaker.hpp\"\n#include \"Runtime/World/CScriptCameraWaypoint.hpp\"\n#include \"Runtime/World/CScriptColorModulate.hpp\"\n#include \"Runtime/World/CScriptControllerAction.hpp\"\n#include \"Runtime/World/CScriptCounter.hpp\"\n#include \"Runtime/World/CScriptCoverPoint.hpp\"\n#include \"Runtime/World/CScriptDamageableTrigger.hpp\"\n#include \"Runtime/World/CScriptDebris.hpp\"\n#include \"Runtime/World/CScriptDebugCameraWaypoint.hpp\"\n#include \"Runtime/World/CScriptDistanceFog.hpp\"\n#include \"Runtime/World/CScriptDock.hpp\"\n#include \"Runtime/World/CScriptDockAreaChange.hpp\"\n#include \"Runtime/World/CScriptDoor.hpp\"\n#include \"Runtime/World/CScriptEMPulse.hpp\"\n#include \"Runtime/World/CScriptEffect.hpp\"\n#include \"Runtime/World/CScriptGenerator.hpp\"\n#include \"Runtime/World/CScriptGrapplePoint.hpp\"\n#include \"Runtime/World/CScriptGunTurret.hpp\"\n#include \"Runtime/World/CScriptHUDMemo.hpp\"\n#include \"Runtime/World/CScriptMazeNode.hpp\"\n#include \"Runtime/World/CScriptMemoryRelay.hpp\"\n#include \"Runtime/World/CScriptMidi.hpp\"\n#include \"Runtime/World/CScriptPickup.hpp\"\n#include \"Runtime/World/CScriptPickupGenerator.hpp\"\n#include \"Runtime/World/CScriptPlatform.hpp\"\n#include \"Runtime/World/CScriptPlayerActor.hpp\"\n#include \"Runtime/World/CScriptPlayerHint.hpp\"\n#include \"Runtime/World/CScriptPlayerStateChange.hpp\"\n#include \"Runtime/World/CScriptPointOfInterest.hpp\"\n#include \"Runtime/World/CScriptRandomRelay.hpp\"\n#include \"Runtime/World/CScriptRelay.hpp\"\n#include \"Runtime/World/CScriptRipple.hpp\"\n#include \"Runtime/World/CScriptRoomAcoustics.hpp\"\n#include \"Runtime/World/CScriptShadowProjector.hpp\"\n#include \"Runtime/World/CScriptSound.hpp\"\n#include \"Runtime/World/CScriptSpawnPoint.hpp\"\n#include \"Runtime/World/CScriptSpecialFunction.hpp\"\n#include \"Runtime/World/CScriptSpiderBallAttractionSurface.hpp\"\n#include \"Runtime/World/CScriptSpiderBallWaypoint.hpp\"\n#include \"Runtime/World/CScriptSpindleCamera.hpp\"\n#include \"Runtime/World/CScriptSteam.hpp\"\n#include \"Runtime/World/CScriptStreamedMusic.hpp\"\n#include \"Runtime/World/CScriptSwitch.hpp\"\n#include \"Runtime/World/CScriptTargetingPoint.hpp\"\n#include \"Runtime/World/CScriptTimer.hpp\"\n#include \"Runtime/World/CScriptTrigger.hpp\"\n#include \"Runtime/World/CScriptVisorFlare.hpp\"\n#include \"Runtime/World/CScriptVisorGoo.hpp\"\n#include \"Runtime/World/CScriptWater.hpp\"\n#include \"Runtime/World/CScriptWaypoint.hpp\"\n#include \"Runtime/World/CScriptWorldTeleporter.hpp\"\n#include \"Runtime/World/CSnakeWeedSwarm.hpp\"\n#include \"Runtime/World/CTeamAiMgr.hpp\"\n#include \"Runtime/World/CWallCrawlerSwarm.hpp\"\n#include \"Runtime/World/CWallWalker.hpp\"\n\n#include \"Runtime/Camera/CGameCamera.hpp\"\n#include \"Runtime/Camera/CCinematicCamera.hpp\"\n#include \"Runtime/Camera/CFirstPersonCamera.hpp\"\n#include \"Runtime/Camera/CInterpolationCamera.hpp\"\n#include \"Runtime/Camera/CPathCamera.hpp\"\n\n#include \"Runtime/Collision/CCollisionActor.hpp\"\n\n#include \"Runtime/Weapon/CWeapon.hpp\"\n#include \"Runtime/Weapon/CBeamProjectile.hpp\"\n#include \"Runtime/Weapon/CBomb.hpp\"\n#include \"Runtime/Weapon/CElectricBeamProjectile.hpp\"\n#include \"Runtime/Weapon/CFlameThrower.hpp\"\n#include \"Runtime/Weapon/CGameProjectile.hpp\"\n#include \"Runtime/Weapon/CNewFlameThrower.hpp\"\n#include \"Runtime/Weapon/CPlasmaProjectile.hpp\"\n#include \"Runtime/Weapon/CPowerBomb.hpp\"\n#include \"Runtime/Weapon/CTargetableProjectile.hpp\"\n#include \"Runtime/Weapon/CWaveBuster.hpp\"\n\n#include \"Runtime/MP1/World/CScriptContraption.hpp\"\n#include \"Runtime/MP1/World/CAtomicAlpha.hpp\"\n#include \"Runtime/MP1/World/CAtomicBeta.hpp\"\n#include \"Runtime/MP1/World/CBabygoth.hpp\"\n#include \"Runtime/MP1/World/CBeetle.hpp\"\n#include \"Runtime/MP1/World/CBloodFlower.hpp\"\n#include \"Runtime/MP1/World/CBouncyGrenade.hpp\"\n#include \"Runtime/MP1/World/CBurrower.hpp\"\n#include \"Runtime/MP1/World/CChozoGhost.hpp\"\n#include \"Runtime/MP1/World/CDrone.hpp\"\n#include \"Runtime/MP1/World/CDroneLaser.hpp\"\n#include \"Runtime/MP1/World/CElitePirate.hpp\"\n#include \"Runtime/MP1/World/CEnergyBall.hpp\"\n#include \"Runtime/MP1/World/CEyeball.hpp\"\n#include \"Runtime/MP1/World/CFireFlea.hpp\"\n#include \"Runtime/MP1/World/CFlaahgra.hpp\"\n#include \"Runtime/MP1/World/CFlaahgraProjectile.hpp\"\n#include \"Runtime/MP1/World/CFlaahgraTentacle.hpp\"\n#include \"Runtime/MP1/World/CFlickerBat.hpp\"\n#include \"Runtime/MP1/World/CFlyingPirate.hpp\"\n#include \"Runtime/MP1/World/CGrenadeLauncher.hpp\"\n#include \"Runtime/MP1/World/CIceAttackProjectile.hpp\"\n#include \"Runtime/MP1/World/CIceSheegoth.hpp\"\n#include \"Runtime/MP1/World/CJellyZap.hpp\"\n#include \"Runtime/MP1/World/CMagdolite.hpp\"\n#include \"Runtime/MP1/World/CMetaree.hpp\"\n#include \"Runtime/MP1/World/CMetroid.hpp\"\n#include \"Runtime/MP1/World/CMetroidBeta.hpp\"\n#include \"Runtime/MP1/World/CMetroidPrimeStage2.hpp\"\n#include \"Runtime/MP1/World/CMetroidPrime.hpp\"\n#include \"Runtime/MP1/World/CMetroidPrimeProjectile.hpp\"\n#include \"Runtime/MP1/World/CMetroidPrimeRelay.hpp\"\n#include \"Runtime/MP1/World/CNewIntroBoss.hpp\"\n#include \"Runtime/MP1/World/COmegaPirate.hpp\"\n#include \"Runtime/MP1/World/CParasite.hpp\"\n#include \"Runtime/MP1/World/CPhazonHealingNodule.hpp\"\n#include \"Runtime/MP1/World/CPhazonPool.hpp\"\n#include \"Runtime/MP1/World/CPuddleSpore.hpp\"\n#include \"Runtime/MP1/World/CPuddleToadGamma.hpp\"\n#include \"Runtime/MP1/World/CPuffer.hpp\"\n#include \"Runtime/MP1/World/CRidley.hpp\"\n#include \"Runtime/MP1/World/CRipper.hpp\"\n#include \"Runtime/MP1/World/CSeedling.hpp\"\n#include \"Runtime/MP1/World/CShockWave.hpp\"\n#include \"Runtime/MP1/World/CSpacePirate.hpp\"\n#include \"Runtime/MP1/World/CSpankWeed.hpp\"\n#include \"Runtime/MP1/World/CThardus.hpp\"\n#include \"Runtime/MP1/World/CThardusRockProjectile.hpp\"\n#include \"Runtime/MP1/World/CTryclops.hpp\"\n#include \"Runtime/MP1/World/CWarWasp.hpp\"\n\n#include \"Runtime/World/CWorld.hpp\"\n#include \"Runtime/World/CGameArea.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\n#include \"ImGuiConsole.hpp\"\n#include \"imgui.h\"\n#include \"magic_enum.hpp\"\n\n#include <cinttypes>\n\n#define IMGUI_ENTITY_INSPECT(CLS, PARENT, NAME, ...)                                                                   \\\n  std::string_view CLS::ImGuiType() { return #NAME; }                                                                  \\\n  void CLS::ImGuiInspect() {                                                                                           \\\n    PARENT::ImGuiInspect();                                                                                            \\\n    if (ImGui::CollapsingHeader(#NAME))                                                                                \\\n      __VA_ARGS__                                                                                                      \\\n  }\n\n#define BITFIELD_CHECKBOX(label, bf, ...)                                                                              \\\n  {                                                                                                                    \\\n    bool b = (bf);                                                                                                     \\\n    if (ImGui::Checkbox(label, &b)) {                                                                                  \\\n      (bf) = b;                                                                                                        \\\n      __VA_ARGS__                                                                                                      \\\n    }                                                                                                                  \\\n  }\n\nnamespace metaforce {\n\nbool ImGuiVector3fInput(const char* label, zeus::CVector3f& vec) {\n  std::array<float, 3> arr{vec.x(), vec.y(), vec.z()};\n  if (ImGui::DragFloat3(label, arr.data(), 0.1f)) {\n    vec.assign(arr[0], arr[1], arr[2]);\n    return true;\n  }\n  return false;\n}\n\nstatic bool ImGuiColorInput(const char* label, zeus::CColor& col) {\n  std::array<float, 4> arr{col.r(), col.g(), col.b(), col.a()};\n  if (ImGui::ColorEdit4(label, arr.data())) {\n    col = zeus::CColor{arr[0], arr[1], arr[2], arr[3]};\n    return true;\n  }\n  return false;\n}\n\ntemplate <typename E>\nstatic constexpr bool ImGuiEnumInput(const char* label, E& val) {\n  constexpr auto& entries = magic_enum::enum_entries<E>();\n  bool changed = false;\n  if (ImGui::BeginCombo(label, magic_enum::enum_name(val).data())) {\n    for (const auto& [item, name] : entries) {\n      if (ImGui::Selectable(name.data(), item == val)) {\n        val = item;\n        changed = true;\n      }\n    }\n    ImGui::EndCombo();\n  }\n  return changed;\n}\n\nvoid ImGuiAnimRes(const char* label, metaforce::CAnimRes& res) {\n  if (res.GetId().IsValid()) {\n    ImGui::Text(\"Model: 0x%08\" PRIX64, res.GetId().Value());\n  } else {\n    ImGui::Text(\"Model: [none]\");\n  }\n  // TODO: More\n}\n\nvoid CDamageVulnerability::ImGuiEditWindow(const char* title, bool& open) {\n  if (!open) {\n    return;\n  }\n  if (ImGui::Begin(title, &open, ImGuiWindowFlags_AlwaysAutoResize)) {\n    ImGuiEnumInput(\"Deflected\", x5c_deflected);\n    if (ImGui::CollapsingHeader(\"Normal\")) {\n      constexpr size_t max = std::tuple_size_v<decltype(x0_normal)>;\n      constexpr std::array<const char*, max> names{\n          \"Power\",  \"Ice\", \"Wave\",         \"Plasma\", \"Bomb\", \"Power Bomb\", \"Missile\",      \"Boost Ball\",\n          \"Phazon\", \"AI\",  \"Poison Water\", \"Lava\",   \"Heat\", \"(Unused)\",   \"Orange Phazon\"};\n      for (int i = 0; i < max; ++i) {\n        ImGuiEnumInput(names[i], x0_normal[i]);\n      }\n    }\n    if (ImGui::CollapsingHeader(\"Charged\")) {\n      constexpr size_t max = std::tuple_size_v<decltype(x3c_charged)>;\n      constexpr std::array<const char*, max> names{\"Power\", \"Ice\", \"Wave\", \"Plasma\"};\n      for (int i = 0; i < max; ++i) {\n        ImGuiEnumInput(names[i], x3c_charged[i]);\n      }\n    }\n    if (ImGui::CollapsingHeader(\"Combo\")) {\n      constexpr size_t max = std::tuple_size_v<decltype(x4c_combo)>;\n      constexpr std::array<const char*, max> names{\"Super Missile\", \"Ice Spreader\", \"Wavebuster\", \"Flamethrower\"};\n      for (int i = 0; i < max; ++i) {\n        ImGuiEnumInput(names[i], x4c_combo[i]);\n      }\n    }\n  }\n  ImGui::End();\n}\n\nvoid ImGuiUniqueId(const char* label, TUniqueId uid) {\n  ImGui::PushID(uid.Value());\n  if (uid != kInvalidUniqueId && ImGuiConsole::entities[uid.Value()].ent != nullptr) {\n    ImGui::Text(\"%s: 0x%04\" PRIX16, label, uid.Value());\n    ImGui::SameLine();\n    if (ImGui::SmallButton(\"View\")) {\n      ImGuiConsole::inspectingEntities.insert(uid);\n    }\n  } else {\n    ImGui::Text(\"%s: [none]\", label);\n  }\n  ImGui::PopID();\n}\n\nstd::string_view CEntity::ImGuiType() { return \"Entity\"; }\n\nvoid CEntity::ImGuiInspect() {\n  if (!x20_conns.empty() && ImGui::CollapsingHeader(\"Outgoing Connections\")) {\n    if (ImGui::BeginTable(\"Outgoing Connections\", 6,\n                          ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV)) {\n      ImGui::TableSetupColumn(\"ID\", ImGuiTableColumnFlags_WidthFixed, 0, 'id');\n      ImGui::TableSetupColumn(\"Type\", ImGuiTableColumnFlags_WidthFixed, 0, 'type');\n      ImGui::TableSetupColumn(\"Name\", ImGuiTableColumnFlags_WidthStretch, 0, 'name');\n      ImGui::TableSetupColumn(\"State\", ImGuiTableColumnFlags_WidthFixed, 0, 'stat');\n      ImGui::TableSetupColumn(\"Message\", ImGuiTableColumnFlags_WidthFixed, 0, 'msg');\n      ImGui::TableSetupColumn(\"\", ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_WidthFixed |\n                                      ImGuiTableColumnFlags_NoResize);\n      ImGui::TableSetupScrollFreeze(0, 1);\n      ImGui::TableHeadersRow();\n      for (const auto& item : x20_conns) {\n        const auto search = g_StateManager->GetIdListForScript(item.x8_objId);\n        for (auto it = search.first; it != search.second; ++it) {\n          const auto uid = it->second;\n          if (uid == kInvalidUniqueId) {\n            continue;\n          }\n          ImGuiEntityEntry& entry = ImGuiConsole::entities[uid.Value()];\n          if (entry.uid == kInvalidUniqueId) {\n            continue;\n          }\n          ImGuiConsole::BeginEntityRow(entry);\n          if (ImGui::TableNextColumn()) {\n            ImGuiStringViewText(entry.type);\n          }\n          if (ImGui::TableNextColumn()) {\n            ImGuiStringViewText(entry.name);\n          }\n          if (ImGui::TableNextColumn()) {\n            ImGuiStringViewText(ScriptObjectStateToStr(item.x0_state));\n          }\n          if (ImGui::TableNextColumn()) {\n            ImGuiStringViewText(ScriptObjectMessageToStr(item.x4_msg));\n          }\n          ImGuiConsole::EndEntityRow(entry);\n        }\n      }\n      ImGui::EndTable();\n    }\n  }\n  if (m_incomingConnections != nullptr && ImGui::CollapsingHeader(\"Incoming Connections\")) {\n    if (ImGui::BeginTable(\"Incoming Connections\", 6,\n                          ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV)) {\n      ImGui::TableSetupColumn(\"ID\", ImGuiTableColumnFlags_WidthFixed, 0, 'id');\n      ImGui::TableSetupColumn(\"Type\", ImGuiTableColumnFlags_WidthFixed, 0, 'type');\n      ImGui::TableSetupColumn(\"Name\", ImGuiTableColumnFlags_WidthStretch, 0, 'name');\n      ImGui::TableSetupColumn(\"State\", ImGuiTableColumnFlags_WidthFixed, 0, 'stat');\n      ImGui::TableSetupColumn(\"Message\", ImGuiTableColumnFlags_WidthFixed, 0, 'msg');\n      ImGui::TableSetupColumn(\"\", ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_WidthFixed |\n                                      ImGuiTableColumnFlags_NoResize);\n      ImGui::TableSetupScrollFreeze(0, 1);\n      ImGui::TableHeadersRow();\n      for (const auto& item : *m_incomingConnections) {\n        const auto search = g_StateManager->GetIdListForScript(item.x8_objId);\n        for (auto it = search.first; it != search.second; ++it) {\n          auto uid = it->second;\n          if (uid == kInvalidUniqueId) {\n            continue;\n          }\n          ImGuiEntityEntry& entry = ImGuiConsole::entities[uid.Value()];\n          if (entry.uid == kInvalidUniqueId) {\n            continue;\n          }\n          ImGuiConsole::BeginEntityRow(entry);\n          if (ImGui::TableNextColumn()) {\n            ImGuiStringViewText(entry.type);\n          }\n          if (ImGui::TableNextColumn()) {\n            ImGuiStringViewText(entry.name);\n          }\n          if (ImGui::TableNextColumn()) {\n            ImGuiStringViewText(ScriptObjectStateToStr(item.x0_state));\n          }\n          if (ImGui::TableNextColumn()) {\n            ImGuiStringViewText(ScriptObjectMessageToStr(item.x4_msg));\n          }\n          ImGuiConsole::EndEntityRow(entry);\n        }\n      }\n      ImGui::EndTable();\n    }\n  }\n  if (ImGui::CollapsingHeader(\"Entity\", ImGuiTreeNodeFlags_DefaultOpen)) {\n    ImGui::Text(\"ID: 0x%04\" PRIX16, x8_uid.Value());\n    ImGui::Text(\"Editor ID: 0x%08\" PRIX32, xc_editorId.id);\n    ImGui::Text(\"Area: %i\", x4_areaId);\n    ImGui::Text(\"Name: %s\", x10_name.c_str());\n    BITFIELD_CHECKBOX(\"Active\", x30_24_active);\n    ImGui::SameLine();\n    ImGui::Checkbox(\"Highlight\", &m_debugSelected);\n  }\n}\n\nstruct EulerAngles {\n  float roll, pitch, yaw;\n};\n\nstatic EulerAngles ToEulerAngles(const zeus::CQuaternion& q) {\n  EulerAngles angles;\n\n  // roll (x-axis rotation)\n  float sinr_cosp = 2.f * (q.w() * q.x() + q.y() * q.z());\n  float cosr_cosp = 1.f - 2.f * (q.x() * q.x() + q.y() * q.y());\n  angles.roll = atan2f(sinr_cosp, cosr_cosp);\n\n  // pitch (y-axis rotation)\n  float sinp = 2.f * (q.w() * q.y() - q.z() * q.x());\n  if (std::abs(sinp) >= 1.f) {\n    angles.pitch = std::copysign(M_PI / 2, sinp); // use 90 degrees if out of range\n  } else {\n    angles.pitch = std::asin(sinp);\n  }\n\n  // yaw (z-axis rotation)\n  float siny_cosp = 2.f * (q.w() * q.z() + q.x() * q.y());\n  float cosy_cosp = 1.f - 2.f * (q.y() * q.y() + q.z() * q.z());\n  angles.yaw = atan2f(siny_cosp, cosy_cosp);\n\n  return angles;\n}\n\n// <- CEntity\nIMGUI_ENTITY_INSPECT(CActor, CEntity, Actor, {\n  if (ImGuiVector3fInput(\"Position\", x34_transform.origin)) {\n    SetTranslation(x34_transform.origin);\n  }\n  EulerAngles angles = ToEulerAngles(zeus::CQuaternion(GetTransform().getRotation().buildMatrix3f()));\n  zeus::CVector3f rotation = zeus::CVector3f(angles.roll, angles.pitch, angles.yaw) * zeus::skRadToDegVec;\n  if (ImGuiVector3fInput(\"Rotation\", rotation)) {\n    rotation.x() = zeus::clamp(-179.999f, float(rotation.x()), 179.999f);\n    rotation.y() = zeus::clamp(-89.999f, float(rotation.y()), 89.999f);\n    rotation.z() = zeus::clamp(-179.999f, float(rotation.z()), 179.999f);\n    x34_transform.setRotation(zeus::CQuaternion(rotation * zeus::skDegToRadVec).toTransform().buildMatrix3f());\n    SetTransform(x34_transform);\n  }\n  {\n    int thermalVisorFlags = xe6_27_thermalVisorFlags;\n    if (ImGui::Combo(\"Thermal Visor Flags\", &thermalVisorFlags, \"None\\0Cold\\0Hot\", 3)) {\n      xe6_27_thermalVisorFlags = u8(thermalVisorFlags);\n    }\n  }\n})\nIMGUI_ENTITY_INSPECT(MP1::CFireFlea::CDeathCameraEffect, CEntity, FireFleaDeathCameraEffect, {})\nIMGUI_ENTITY_INSPECT(MP1::CMetroidPrimeRelay, CEntity, MetroidPrimeRelay, {})\nIMGUI_ENTITY_INSPECT(CScriptActorKeyframe, CEntity, ScriptActorKeyframe, {})\nIMGUI_ENTITY_INSPECT(CScriptActorRotate, CEntity, ScriptActorRotate, {})\n\nIMGUI_ENTITY_INSPECT(CScriptAreaAttributes, CEntity, ScriptAreaAttributes, {\n  BITFIELD_CHECKBOX(\"Show Skybox\", x34_24_showSkybox);\n  ImGui::SameLine();\n  ImGui::Text(\"(Asset: 0x%08X)\", int(x4c_skybox.Value()));\n  ImGuiEnumInput(\"Env FX Type\", x38_envFx);\n  if (ImGui::SliderFloat(\"Env FX Density\", &x3c_envFxDensity, 0.f, 1.f)) {\n    g_StateManager->GetEnvFxManager()->SetFxDensity(500, x3c_envFxDensity);\n  }\n  if (ImGui::SliderFloat(\"Thermal Heat\", &x40_thermalHeat, 0.f, 1.f)) {\n    CGameArea* area = g_StateManager->GetWorld()->GetArea(x4_areaId);\n    if (area != nullptr && area->IsPostConstructed()) {\n      area->GetPostConstructed()->x111c_thermalCurrent = x40_thermalHeat;\n    }\n  }\n  ImGui::SliderFloat(\"X-Ray Fog Distance\", &x44_xrayFogDistance, 0.f, 1.f);\n  if (ImGui::SliderFloat(\"World Lighting Level\", &x48_worldLightingLevel, 0.f, 1.f)) {\n    CGameArea* area = g_StateManager->GetWorld()->GetArea(x4_areaId);\n    if (area != nullptr && area->IsPostConstructed()) {\n      area->GetPostConstructed()->x1128_worldLightingLevel = x48_worldLightingLevel;\n    }\n  }\n  ImGuiEnumInput(\"Phazon Type\", x50_phazon);\n})\nIMGUI_ENTITY_INSPECT(CScriptCameraBlurKeyframe, CEntity, ScriptCameraBlurKeyframe, {})\nIMGUI_ENTITY_INSPECT(CScriptCameraFilterKeyframe, CEntity, ScriptCameraFilterKeyframe, {})\nIMGUI_ENTITY_INSPECT(CScriptCameraShaker, CEntity, ScriptCameraShaker, {})\nIMGUI_ENTITY_INSPECT(CScriptColorModulate, CEntity, ScriptColorModulate, {})\nIMGUI_ENTITY_INSPECT(CScriptControllerAction, CEntity, ScriptControllerAction, {})\nIMGUI_ENTITY_INSPECT(CScriptCounter, CEntity, ScriptCounter, {})\nIMGUI_ENTITY_INSPECT(CScriptDistanceFog, CEntity, ScriptDistanceFog, {})\nIMGUI_ENTITY_INSPECT(CScriptDockAreaChange, CEntity, ScriptDockAreaChange, {})\nIMGUI_ENTITY_INSPECT(CScriptGenerator, CEntity, ScriptGenerator, {\n  int count = x34_spawnCount;\n  if (ImGui::SliderInt(\"Spawn Count\", &count, 0, kMaxEntities)) {\n    x34_spawnCount = count;\n  }\n  BITFIELD_CHECKBOX(\"Don't Reuse Followers\", x38_24_noReuseFollowers);\n  BITFIELD_CHECKBOX(\"Don't Inherit Transform\", x38_25_noInheritTransform);\n  ImGuiVector3fInput(\"Offset\", x3c_offset);\n  ImGui::DragFloat(\"Minimum Scale\", &x48_minScale);\n  ImGui::DragFloat(\"Maximum Scale\", &x4c_maxScale);\n})\nIMGUI_ENTITY_INSPECT(CScriptHUDMemo, CEntity, ScriptHUDMemo, {})\nIMGUI_ENTITY_INSPECT(CScriptMemoryRelay, CEntity, ScriptMemoryRelay, {})\nIMGUI_ENTITY_INSPECT(CScriptMidi, CEntity, ScriptMidi, {})\nIMGUI_ENTITY_INSPECT(CScriptPickupGenerator, CEntity, ScriptPickupGenerator, {\n  ImGuiVector3fInput(\"Position\", x34_position);\n  ImGui::DragFloat(\"Frequency\", &x40_frequency);\n  ImGui::DragFloat(\"Delay Timer\", &x44_delayTimer);\n})\nIMGUI_ENTITY_INSPECT(CScriptPlayerStateChange, CEntity, ScriptPlayerStateChange, {})\nIMGUI_ENTITY_INSPECT(CScriptRandomRelay, CEntity, ScriptRandomRelay, {})\nIMGUI_ENTITY_INSPECT(CScriptRelay, CEntity, ScriptRelay, {})\nIMGUI_ENTITY_INSPECT(CScriptRipple, CEntity, ScripleRipple, {})\nIMGUI_ENTITY_INSPECT(CScriptRoomAcoustics, CEntity, ScriptRoomAcoustics, {})\nIMGUI_ENTITY_INSPECT(CScriptSpawnPoint, CEntity, ScriptSpawnPoint, {})\nIMGUI_ENTITY_INSPECT(CScriptStreamedMusic, CEntity, ScriptStreamedMusic, {})\nIMGUI_ENTITY_INSPECT(CScriptSwitch, CEntity, ScriptSwitch, {\n  ImGui::Checkbox(\"Is Open\", &x34_opened);\n  ImGui::Checkbox(\"Close On Opened\", &x35_closeOnOpened);\n  if (ImGui::Button(\"Open\")) {\n    g_StateManager->SendScriptMsg(this, x8_uid, EScriptObjectMessage::Open);\n  }\n  ImGui::SameLine();\n  if (ImGui::Button(\"Close\")) {\n    g_StateManager->SendScriptMsg(this, x8_uid, EScriptObjectMessage::Close);\n  }\n  ImGui::SameLine();\n  if (ImGui::Button(\"Toggle\")) {\n    g_StateManager->SendScriptMsg(this, x8_uid, EScriptObjectMessage::SetToZero);\n  }\n})\n\nIMGUI_ENTITY_INSPECT(CScriptTimer, CEntity, ScriptTimer, {\n  ImGui::DragFloat(\"Time\", &x34_time);\n  ImGui::DragFloat(\"Start Time\", &x38_startTime);\n  ImGui::DragFloat(\"Max Random Delay\", &x3c_maxRandDelay);\n  ImGui::Checkbox(\"Loop\", &x40_loop);\n  ImGui::Checkbox(\"Auto Start\", &x41_autoStart);\n  ImGui::Checkbox(\"Is Timing\", &x42_isTiming);\n  if (ImGui::Button(\"Reset\")) {\n    g_StateManager->SendScriptMsg(this, x8_uid, EScriptObjectMessage::ResetAndStart);\n  }\n})\nIMGUI_ENTITY_INSPECT(CScriptWorldTeleporter, CEntity, ScriptWorldTeleporter, {})\nIMGUI_ENTITY_INSPECT(CTeamAiMgr, CEntity, TeamAiMgr, {\n  // TODO x34_data\n  // TODO x58_roles\n  if (!x68_meleeAttackers.empty() && ImGui::CollapsingHeader(\"Melee Attackers\")) {\n    if (ImGui::BeginTable(\"Melee Attackers\", 4,\n                          ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV)) {\n      ImGui::TableSetupColumn(\"ID\", ImGuiTableColumnFlags_WidthFixed, 0, 'id');\n      ImGui::TableSetupColumn(\"Type\", ImGuiTableColumnFlags_WidthFixed, 0, 'type');\n      ImGui::TableSetupColumn(\"Name\", ImGuiTableColumnFlags_WidthStretch, 0, 'name');\n      ImGui::TableSetupColumn(\"\", ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_WidthFixed |\n                                      ImGuiTableColumnFlags_NoResize);\n      ImGui::TableSetupScrollFreeze(0, 1);\n      ImGui::TableHeadersRow();\n      for (auto uid : x68_meleeAttackers) {\n        if (uid == kInvalidUniqueId) {\n          continue;\n        }\n        ImGuiEntityEntry& entry = ImGuiConsole::entities[uid.Value()];\n        if (entry.uid == kInvalidUniqueId) {\n          continue;\n        }\n        ImGuiConsole::BeginEntityRow(entry);\n        if (ImGui::TableNextColumn()) {\n          ImGuiStringViewText(entry.type);\n        }\n        if (ImGui::TableNextColumn()) {\n          ImGuiStringViewText(entry.name);\n        }\n        ImGuiConsole::EndEntityRow(entry);\n      }\n    }\n    ImGui::EndTable();\n  }\n  if (!x78_rangedAttackers.empty() && ImGui::CollapsingHeader(\"Ranged Attackers\")) {\n    if (ImGui::BeginTable(\"Ranged Attackers\", 4,\n                          ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV)) {\n      ImGui::TableSetupColumn(\"ID\", ImGuiTableColumnFlags_WidthFixed, 0, 'id');\n      ImGui::TableSetupColumn(\"Type\", ImGuiTableColumnFlags_WidthFixed, 0, 'type');\n      ImGui::TableSetupColumn(\"Name\", ImGuiTableColumnFlags_WidthStretch, 0, 'name');\n      ImGui::TableSetupColumn(\"\", ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_WidthFixed |\n                                      ImGuiTableColumnFlags_NoResize);\n      ImGui::TableSetupScrollFreeze(0, 1);\n      ImGui::TableHeadersRow();\n      for (auto uid : x78_rangedAttackers) {\n        if (uid == kInvalidUniqueId) {\n          continue;\n        }\n        ImGuiEntityEntry& entry = ImGuiConsole::entities[uid.Value()];\n        if (entry.uid == kInvalidUniqueId) {\n          continue;\n        }\n        ImGuiConsole::BeginEntityRow(entry);\n        if (ImGui::TableNextColumn()) {\n          ImGuiStringViewText(entry.type);\n        }\n        if (ImGui::TableNextColumn()) {\n          ImGuiStringViewText(entry.name);\n        }\n        ImGuiConsole::EndEntityRow(entry);\n      }\n    }\n    ImGui::EndTable();\n  }\n  ImGui::InputFloat(\"Time Dirty\", &x88_timeDirty);\n  ImGuiUniqueId(\"Team Captain\", x8c_teamCaptainId);\n  ImGui::InputFloat(\"Time Since Melee\", &x90_timeSinceMelee);\n  ImGui::InputFloat(\"Time Since Ranged\", &x94_timeSinceRanged);\n})\n\n// <- CActor\nIMGUI_ENTITY_INSPECT(CPhysicsActor, CActor, PhysicsActor, {\n  float mass = xe8_mass;\n  if (ImGui::InputFloat(\"Mass\", &mass)) {\n    SetMass(mass);\n  }\n  float inertiaTensor = xf0_inertiaTensor;\n  if (ImGui::InputFloat(\"Inertia tensor\", &inertiaTensor)) {\n    SetInertiaTensorScalar(inertiaTensor);\n  }\n  if (ImGuiVector3fInput(\"Velocity\", x138_velocity)) {\n    SetVelocityWR(x138_velocity);\n  }\n  if (ImGuiVector3fInput(\"Momentum\", x150_momentum)) {\n    SetMomentumWR(x150_momentum);\n  }\n  zeus::CVector3f force = x15c_force;\n  if (ImGuiVector3fInput(\"Force\", force)) {\n    ApplyForceWR(force - x15c_force, zeus::CAxisAngle());\n  }\n  zeus::CVector3f impulse = x168_impulse;\n  if (ImGuiVector3fInput(\"Impulse\", x168_impulse)) {\n    ApplyImpulseWR(impulse - x168_impulse, zeus::CAxisAngle());\n  }\n  BITFIELD_CHECKBOX(\"Movable\", xf8_24_movable);\n  BITFIELD_CHECKBOX(\"Angular enabled\", xf8_25_angularEnabled);\n  BITFIELD_CHECKBOX(\"Standard collider\", xf9_standardCollider);\n})\nIMGUI_ENTITY_INSPECT(MP1::CDroneLaser, CActor, DroneLaser, {})\nIMGUI_ENTITY_INSPECT(CEffect, CActor, Effect, {})\nIMGUI_ENTITY_INSPECT(CFire, CActor, Fire, {})\nIMGUI_ENTITY_INSPECT(CFishCloud, CActor, FishCloud, {})\nIMGUI_ENTITY_INSPECT(CFishCloudModifier, CActor, FishCloudModifier, {})\nIMGUI_ENTITY_INSPECT(MP1::CFlaahgraPlants, CActor, FlaahgraPlants, {})\nIMGUI_ENTITY_INSPECT(MP1::CFlaahgraRenderer, CActor, FlaahgraRenderer, {})\nIMGUI_ENTITY_INSPECT(MP1::COmegaPirate::CFlash, CActor, OmegaPirateFlash, {})\nIMGUI_ENTITY_INSPECT(CGameCamera, CActor, GameCamera, {})\nIMGUI_ENTITY_INSPECT(CGameLight, CActor, GameLight, {\n  ImGuiVector3fInput(\"Position\", xec_light.x0_pos);\n  ImGuiVector3fInput(\"Direction\", xec_light.xc_dir);\n  if (ImGuiColorInput(\"Color\", xec_light.x18_color)) {\n    xec_light.SetColor(xec_light.x18_color); // set dirty flags\n  }\n  ImGuiEnumInput(\"Type\", xec_light.x1c_type);\n  ImGui::DragFloat(\"Spot cutoff\", &xec_light.x20_spotCutoff, 0.1f);\n  {\n    std::array<float, 3> att{xec_light.x24_distC, xec_light.x28_distL, xec_light.x2c_distQ};\n    if (ImGui::DragFloat3(\"Attenuation\", att.data())) {\n      xec_light.SetAttenuation(att[0], att[1], att[2]);\n    }\n    ImGui::SameLine();\n    ImGui::TextUnformatted(\"(?)\");\n    if (ImGui::IsItemHovered()) {\n      ImGui::SetTooltip(\"Constant | Linear | Quadratic\");\n    }\n  }\n  {\n    std::array<float, 3> angleAtt{xec_light.x30_angleC, xec_light.x34_angleL, xec_light.x38_angleQ};\n    if (ImGui::DragFloat3(\"Angle Atten\", angleAtt.data())) {\n      xec_light.SetAngleAttenuation(angleAtt[0], angleAtt[1], angleAtt[2]);\n    }\n    ImGui::SameLine();\n    ImGui::TextUnformatted(\"(?)\");\n    if (ImGui::IsItemHovered()) {\n      ImGui::SetTooltip(\"Constant | Linear | Quadratic\");\n    }\n  }\n  ImGui::Text(\"Calculated radius: %.3f\", xec_light.GetRadius());\n  ImGui::Text(\"Calculated intensity: %.3f\", xec_light.GetIntensity());\n})\nIMGUI_ENTITY_INSPECT(MP1::CIceAttackProjectile, CActor, IceAttackProjectile, {})\nIMGUI_ENTITY_INSPECT(CRepulsor, CActor, Repulsor, {})\nIMGUI_ENTITY_INSPECT(CScriptAiJumpPoint, CActor, ScriptAIJumpPoint, {})\nIMGUI_ENTITY_INSPECT(CScriptBeam, CActor, ScriptBeam, {})\nIMGUI_ENTITY_INSPECT(CScriptCameraHint, CActor, ScriptCameraHint, {})\nIMGUI_ENTITY_INSPECT(CScriptCameraHintTrigger, CActor, ScriptCameraHintTrigger, {})\nIMGUI_ENTITY_INSPECT(CScriptCameraPitchVolume, CActor, ScriptCameraPitchVolume, {})\nIMGUI_ENTITY_INSPECT(CScriptCameraWaypoint, CActor, ScriptCameraWaypoint, {})\nIMGUI_ENTITY_INSPECT(CScriptCoverPoint, CActor, ScriptCoverPoint, {})\nIMGUI_ENTITY_INSPECT(CScriptDamageableTrigger, CActor, ScriptDamageableTrigger, {})\nIMGUI_ENTITY_INSPECT(CScriptDebugCameraWaypoint, CActor, ScriptDebugCameraWaypoint, {})\nIMGUI_ENTITY_INSPECT(CScriptEffect, CActor, ScriptEffect, {\n  BITFIELD_CHECKBOX(\"Enabled\", x110_24_enable);\n  BITFIELD_CHECKBOX(\"No Timer Unless Area Occluded\", x110_25_noTimerUnlessAreaOccluded);\n  BITFIELD_CHECKBOX(\"Rebuild Systems On Activate\", x110_26_rebuildSystemsOnActivate);\n  BITFIELD_CHECKBOX(\"Use Rate Inverse Camera Distance\", x110_27_useRateInverseCamDist);\n  BITFIELD_CHECKBOX(\"Combat Visor Visible\", x110_28_combatVisorVisible);\n  BITFIELD_CHECKBOX(\"Thermal Visor Visible\", x110_29_thermalVisorVisible);\n  BITFIELD_CHECKBOX(\"X-Ray Visor Visible\", x110_30_xrayVisorVisible);\n  BITFIELD_CHECKBOX(\"Any Visor Visible\", x110_31_anyVisorVisible);\n  BITFIELD_CHECKBOX(\"Use Rate Camera Distance Range\", x111_24_useRateCamDistRange);\n  BITFIELD_CHECKBOX(\"Die When Systems Done\", x111_25_dieWhenSystemsDone);\n  BITFIELD_CHECKBOX(\"Can Render\", x111_26_canRender);\n  if (ImGui::DragFloat(\"Rate Inverse Camera Distance\", &x114_rateInverseCamDist, 0.1f)) {\n    x118_rateInverseCamDistSq = x114_rateInverseCamDist * x114_rateInverseCamDist;\n  }\n  ImGui::DragFloat(\"Rate Inverse Camera Distance Rate\", &x11c_rateInverseCamDistRate, 0.1f);\n  ImGui::DragFloat(\"Rate Camera Distance Range Min\", &x120_rateCamDistRangeMin, 0.1f);\n  ImGui::DragFloat(\"Rate Camera Distance Range Max\", &x124_rateCamDistRangeMax, 0.1f);\n  ImGui::DragFloat(\"Rate Camera Distance Range Far Rate\", &x128_rateCamDistRangeFarRate, 0.1f);\n  ImGui::DragFloat(\"Remaining Time\", &x12c_remTime, 0.1f);\n  ImGui::DragFloat(\"Duration\", &x130_duration, 0.1f);\n  ImGui::DragFloat(\"Duration Reset While Visible\", &x134_durationResetWhileVisible, 0.1f);\n  ImGuiUniqueId(\"Trigger ID\", x13c_triggerId);\n  ImGui::DragFloat(\"Destroy Delay Timer\", &x140_destroyDelayTimer, 0.1f);\n})\nIMGUI_ENTITY_INSPECT(CScriptEMPulse, CActor, ScriptEMPulse, {})\nIMGUI_ENTITY_INSPECT(CScriptGrapplePoint, CActor, ScriptGrapplePoint, {})\nIMGUI_ENTITY_INSPECT(CScriptMazeNode, CActor, ScriptMazeNode, {})\nIMGUI_ENTITY_INSPECT(CScriptPlayerHint, CActor, ScriptPlayerHint, {})\nIMGUI_ENTITY_INSPECT(CScriptPointOfInterest, CActor, ScriptPointOfInterest, {})\nIMGUI_ENTITY_INSPECT(CScriptShadowProjector, CActor, ScriptShadowProjector, {})\nIMGUI_ENTITY_INSPECT(CScriptSound, CActor, ScriptSound, {})\nIMGUI_ENTITY_INSPECT(CScriptSpecialFunction, CActor, ScriptSpecialFunction, {})\nIMGUI_ENTITY_INSPECT(CScriptSpiderBallAttractionSurface, CActor, ScriptSpiderballAttractionSurface, {})\nIMGUI_ENTITY_INSPECT(CScriptSpiderBallWaypoint, CActor, ScriptSpiderBallWaypoint, {})\nIMGUI_ENTITY_INSPECT(CScriptTargetingPoint, CActor, ScriptTargetingPoint, {})\nIMGUI_ENTITY_INSPECT(CScriptTrigger, CActor, ScriptTrigger, {})\nIMGUI_ENTITY_INSPECT(CScriptVisorFlare, CActor, ScriptVisorFlare, {})\nIMGUI_ENTITY_INSPECT(CScriptVisorGoo, CActor, ScriptVisorGoo, {})\nIMGUI_ENTITY_INSPECT(CScriptWaypoint, CActor, ScriptWaypoint, {})\nIMGUI_ENTITY_INSPECT(MP1::CShockWave, CActor, ShockWave, {})\nIMGUI_ENTITY_INSPECT(CSnakeWeedSwarm, CActor, SnakeWeedSwarm, {})\nIMGUI_ENTITY_INSPECT(CWallCrawlerSwarm, CActor, WallCrawlerSwarm, {})\nIMGUI_ENTITY_INSPECT(CWeapon, CActor, Weapon, {})\n\n// <- CEffect\nIMGUI_ENTITY_INSPECT(CExplosion, CEffect, Explosion, {})\nIMGUI_ENTITY_INSPECT(CHUDBillboardEffect, CEffect, HUDBillboardEffect, {})\nIMGUI_ENTITY_INSPECT(CIceImpact, CEffect, IceImpact, {})\n\n// <- CGameCamera\nIMGUI_ENTITY_INSPECT(CBallCamera, CGameCamera, BallCamera, {})\nIMGUI_ENTITY_INSPECT(CCinematicCamera, CGameCamera, CinematicCamera, {})\nIMGUI_ENTITY_INSPECT(CFirstPersonCamera, CGameCamera, FirstPersonCamera, {})\nIMGUI_ENTITY_INSPECT(CInterpolationCamera, CGameCamera, InterpolationCamera, {})\nIMGUI_ENTITY_INSPECT(CPathCamera, CGameCamera, PathCamera, {})\nIMGUI_ENTITY_INSPECT(CScriptSpindleCamera, CGameCamera, ScriptSpindleCamera, {})\n\n// <- CScriptTrigger\nIMGUI_ENTITY_INSPECT(MP1::CPhazonPool, CScriptTrigger, PhazonPool, {})\nIMGUI_ENTITY_INSPECT(CScriptBallTrigger, CScriptTrigger, ScriptBallTrigger, {})\nIMGUI_ENTITY_INSPECT(CScriptSteam, CScriptTrigger, ScriptSteam, {})\nIMGUI_ENTITY_INSPECT(CScriptWater, CScriptTrigger, ScriptWater, {})\n\n// <- CWeapon\nIMGUI_ENTITY_INSPECT(CBomb, CWeapon, Bomb, {})\nIMGUI_ENTITY_INSPECT(CGameProjectile, CWeapon, GameProjectile, {})\nIMGUI_ENTITY_INSPECT(CPowerBomb, CWeapon, PowerBomb, {})\n\n// <- CGameProjectile\nIMGUI_ENTITY_INSPECT(CBeamProjectile, CGameProjectile, BeamProjectile, {})\nIMGUI_ENTITY_INSPECT(CEnergyProjectile, CGameProjectile, EnergyProjectile, {})\nIMGUI_ENTITY_INSPECT(CFlameThrower, CGameProjectile, FlameThrower, {})\nIMGUI_ENTITY_INSPECT(CNewFlameThrower, CGameProjectile, NewFlameThrower, {})\nIMGUI_ENTITY_INSPECT(CWaveBuster, CGameProjectile, WaveBuster, {})\n\n// <- CBeamProjectile\nIMGUI_ENTITY_INSPECT(CElectricBeamProjectile, CBeamProjectile, ElectricBeamProjectile, {})\nIMGUI_ENTITY_INSPECT(CPlasmaProjectile, CBeamProjectile, PlasmaProjectile, {})\n\n// <- CEnergyProjectile\nIMGUI_ENTITY_INSPECT(MP1::CFlaahgraProjectile, CEnergyProjectile, FlaahgraProjectile, {})\nIMGUI_ENTITY_INSPECT(MP1::CMetroidPrimeProjectile, CEnergyProjectile, MetroidPrimeProjectile, {})\nIMGUI_ENTITY_INSPECT(CTargetableProjectile, CEnergyProjectile, TargetableProjectile, {})\n\n// <- CPhysicsActor\nIMGUI_ENTITY_INSPECT(CAi, CPhysicsActor, AI, {})\nIMGUI_ENTITY_INSPECT(CAmbientAI, CPhysicsActor, AmbientAI, {})\nIMGUI_ENTITY_INSPECT(MP1::CBouncyGrenade, CPhysicsActor, BouncyGrenade, {})\nIMGUI_ENTITY_INSPECT(CCollisionActor, CPhysicsActor, CollisionActor, {})\nIMGUI_ENTITY_INSPECT(MP1::CGrenadeLauncher, CPhysicsActor, GrenadeLauncher, {})\nIMGUI_ENTITY_INSPECT(MP1::CMetroidPrime::CPhysicsDummy, CPhysicsActor, MetroidPrimeExoPhysicsDummy, {})\nIMGUI_ENTITY_INSPECT(CPlayer, CPhysicsActor, Player, {\n  if (ImGui::CollapsingHeader(\"Player Gun\")) {\n    auto* gun = GetPlayerGun();\n    ImGui::Text(\"Last Fire Button States: 0x%08X\", gun->x2ec_lastFireButtonStates);\n    ImGui::Text(\"Pressed Fire Button States: 0x%08X\", gun->x2f0_pressedFireButtonStates);\n    ImGui::Text(\"Fire Button States: 0x%08X\", gun->x2f4_fireButtonStates);\n    ImGui::Text(\"State Flags: 0x%08X\", gun->x2f8_stateFlags);\n    ImGui::Text(\"Fidget Anim Bits: 0x%08X\", gun->x2fc_fidgetAnimBits);\n    ImGui::Text(\"Remaining Missiles: %i\", gun->x300_remainingMissiles);\n    ImGui::Text(\"Bomb Count: %i\", gun->x308_bombCount);\n    ImGui::Text(\"Current Beam: %s\", magic_enum::enum_name(gun->x310_currentBeam).data());\n    ImGui::Text(\"Next Beam: %s\", magic_enum::enum_name(gun->x314_nextBeam).data());\n  }\n})\nIMGUI_ENTITY_INSPECT(CScriptActor, CPhysicsActor, ScriptActor, {\n  if (ImGui::Button(\"Edit Damage Vulnerability\")) {\n    m_editingDamageVulnerability = true;\n  }\n  x268_damageVulnerability.ImGuiEditWindow(\"Damage Vulnerability - Script Actor\", m_editingDamageVulnerability);\n\n  bool modelFlagsChanged = false;\n  ImGui::DragFloat(\"Fade In Time\", &x2d0_fadeInTime, 0.1f);\n  ImGui::DragFloat(\"Fade Out Time\", &x2d4_fadeOutTime, 0.1f);\n  ImGui::SliderFloat(\"X-Ray Alpha\", &x2dc_xrayAlpha, 0.0f, 1.f);\n  ImGui::SameLine();\n  BITFIELD_CHECKBOX(\"Enabled\", x2e2_27_xrayAlphaEnabled, { modelFlagsChanged = true; });\n  BITFIELD_CHECKBOX(\"Disable Thermal Hot Z-test\", x2e2_24_noThermalHotZ, { modelFlagsChanged = true; });\n  BITFIELD_CHECKBOX(\"Dead\", x2e2_25_dead); // onclick -> EScriptObjectMessage::Reset?\n  BITFIELD_CHECKBOX(\"Animating\", x2e2_26_animating);\n  BITFIELD_CHECKBOX(\"Scale Advancement Delta\", x2e2_30_scaleAdvancementDelta);\n  BITFIELD_CHECKBOX(\"Material Flag 54\", x2e2_31_materialFlag54);\n  BITFIELD_CHECKBOX(\"Is Player Actor\", x2e3_24_isPlayerActor);\n  ImGuiUniqueId(\"Trigger ID\", x2e0_triggerId);\n  x2e2_29_processModelFlags =\n      modelFlagsChanged || x2e2_27_xrayAlphaEnabled || x2e2_24_noThermalHotZ || x2d8_shaderIdx != 0;\n})\nIMGUI_ENTITY_INSPECT(CScriptDebris, CPhysicsActor, ScriptDebris, {})\nIMGUI_ENTITY_INSPECT(CScriptDock, CPhysicsActor, ScriptDock, {\n  if (x260_area != kInvalidAreaId) {\n    const auto* dock = g_StateManager->GetWorld()->GetArea(x260_area)->GetDock(x25c_dock);\n    if (dock != nullptr) {\n      auto areaId = dock->GetConnectedAreaId(dock->GetReferenceCount());\n      if (areaId != kInvalidAreaId) {\n        CAssetId stringId = g_StateManager->GetWorld()->GetArea(areaId)->IGetStringTableAssetId();\n        ImGuiStringViewText(fmt::format(\"Connected Area: {}\", ImGuiLoadStringTable(stringId, 0)));\n      }\n    }\n  }\n  ImGuiEnumInput(\"Dock State\", x264_dockState);\n  BITFIELD_CHECKBOX(\"Dock Referenced\", x268_24_dockReferenced);\n  BITFIELD_CHECKBOX(\"Load Connected\", x268_25_loadConnected);\n  BITFIELD_CHECKBOX(\"Area Post Constructed\", x268_26_areaPostConstructed);\n});\nIMGUI_ENTITY_INSPECT(CScriptDoor, CPhysicsActor, ScriptDoor, {\n  ImGui::DragFloat(\"Animation Length\", &x258_animLen, 0.1f);\n  ImGui::DragFloat(\"Animation Time\", &x25c_animTime, 0.1f);\n  ImGuiEnumInput(\"Door State\", x260_doorAnimState);\n  // TODO: AABox\n  ImGuiUniqueId(\"Partner1\", x27c_partner1);\n  ImGuiUniqueId(\"Partner2\", x27e_partner2);\n  ImGuiUniqueId(\"Previous Door\", x280_prevDoor);\n  ImGuiUniqueId(\"Dock\", x282_dockId);\n  // TODO: model aabox\n  ImGuiVector3fInput(\"Orbit Position\", x29c_orbitPos);\n  BITFIELD_CHECKBOX(\"Closing\", x2a8_24_closing);\n  BITFIELD_CHECKBOX(\"Was Open\", x2a8_25_wasOpen);\n  BITFIELD_CHECKBOX(\"Is Open\", x2a8_26_isOpen);\n  BITFIELD_CHECKBOX(\"Open Conditions Met\", x2a8_27_conditionsMet);\n  BITFIELD_CHECKBOX(\"Projectiles Can Collide\", x2a8_28_projectilesCollide);\n  BITFIELD_CHECKBOX(\"Is Ball Door\", x2a8_29_ballDoor);\n  BITFIELD_CHECKBOX(\"Will Close\", x2a8_30_doClose);\n})\nIMGUI_ENTITY_INSPECT(CScriptGunTurret, CPhysicsActor, ScriptGunTurret, {})\nIMGUI_ENTITY_INSPECT(CScriptPickup, CPhysicsActor, ScriptPickup, {\n  ImGuiEnumInput(\"Item Type\", x258_itemType);\n  ImGui::DragInt(\"Amount\", &x25c_amount);\n  ImGui::DragInt(\"Capacity\", &x260_capacity);\n  ImGui::SliderFloat(\"Possibility\", &x264_possibility, 0.f, 100.f);\n  ImGui::DragFloat(\"Fade In Time\", &x268_fadeInTime);\n  ImGui::DragFloat(\"Lifetime\", &x26c_lifeTime);\n  ImGui::DragFloat(\"Current Time\", &x270_curTime);\n  ImGui::DragFloat(\"Tractor Time\", &x274_tractorTime);\n  ImGui::DragFloat(\"Delay Timer\", &x278_delayTimer);\n})\nIMGUI_ENTITY_INSPECT(CScriptPlatform, CPhysicsActor, ScriptPlatform, {\n  ImGuiUniqueId(\"Current Waypoint\", x258_currentWaypoint);\n  ImGuiUniqueId(\"Target Waypoint\", x25a_targetWaypoint);\n})\n\n// <- CScriptActor\nIMGUI_ENTITY_INSPECT(MP1::CScriptContraption, CScriptActor, ActorContraption, {})\nIMGUI_ENTITY_INSPECT(CScriptPlayerActor, CScriptActor, PlayerActor, {\n  ImGuiAnimRes(\"Suit Resource\", x2e8_suitRes);\n  ImGuiEnumInput(\"Beam\", x304_beam);\n  ImGuiEnumInput(\"Suit\", x308_suit);\n})\n\n// <- CScriptPlatform\nIMGUI_ENTITY_INSPECT(MP1::CRipperControlledPlatform, CScriptPlatform, RipperControlledPlatform, {})\n\n// <- CAi\nIMGUI_ENTITY_INSPECT(CDestroyableRock, CAi, DestroyableRock, {})\nIMGUI_ENTITY_INSPECT(CPatterned, CAi, Patterned, {\n  BITFIELD_CHECKBOX(\"Enable state machine\", x403_25_enableStateMachine);\n  ImGui::Text(\"Body state:\");\n  ImGui::SameLine();\n  ImGuiStringViewText(pas::AnimationStateToStr(x450_bodyController->GetCurrentStateId()));\n  if (ImGui::Button(\"Burn\")) {\n    Burn(1.f, 1.f);\n  }\n  if (ImGui::Button(\"Shock\")) {\n    Shock(*g_StateManager, 1.f, 1.f);\n  }\n  if (ImGui::Button(\"Freeze\")) {\n    Freeze(*g_StateManager, GetTranslation(), zeus::skZero3f, 1.f);\n  }\n})\n\n// <- CPatterned\nIMGUI_ENTITY_INSPECT(MP1::CAtomicAlpha, CPatterned, AtomicAlpha, {})\nIMGUI_ENTITY_INSPECT(MP1::CAtomicBeta, CPatterned, AtomicBeta, {})\nIMGUI_ENTITY_INSPECT(MP1::CBabygoth, CPatterned, Babygoth, {})\nIMGUI_ENTITY_INSPECT(MP1::CBeetle, CPatterned, Beetle, {})\nIMGUI_ENTITY_INSPECT(MP1::CBloodFlower, CPatterned, BloodFlower, {})\nIMGUI_ENTITY_INSPECT(MP1::CBurrower, CPatterned, Burrower, {})\nIMGUI_ENTITY_INSPECT(MP1::CChozoGhost, CPatterned, ChozoGhost, {})\nIMGUI_ENTITY_INSPECT(MP1::CDrone, CPatterned, Drone, {})\nIMGUI_ENTITY_INSPECT(MP1::CElitePirate, CPatterned, ElitePirate, {})\nIMGUI_ENTITY_INSPECT(MP1::CEnergyBall, CPatterned, EnergyBall, {})\nIMGUI_ENTITY_INSPECT(MP1::CEyeball, CPatterned, EyeBall, {})\nIMGUI_ENTITY_INSPECT(MP1::CFireFlea, CPatterned, FireFlea, {})\nIMGUI_ENTITY_INSPECT(MP1::CFlaahgra, CPatterned, Flaahgra, {})\nIMGUI_ENTITY_INSPECT(MP1::CFlaahgraTentacle, CPatterned, FlaahgraTentacle, {})\nIMGUI_ENTITY_INSPECT(MP1::CFlickerBat, CPatterned, FlickerBat, {})\nIMGUI_ENTITY_INSPECT(MP1::CFlyingPirate, CPatterned, FlyingPirate, {})\nIMGUI_ENTITY_INSPECT(MP1::CIceSheegoth, CPatterned, IceSheegoth, {})\nIMGUI_ENTITY_INSPECT(MP1::CJellyZap, CPatterned, JellyZap, {})\nIMGUI_ENTITY_INSPECT(MP1::CMagdolite, CPatterned, Magdolite, {})\nIMGUI_ENTITY_INSPECT(MP1::CMetaree, CPatterned, Metaree, {})\nIMGUI_ENTITY_INSPECT(MP1::CMetroid, CPatterned, Metroid, {})\nIMGUI_ENTITY_INSPECT(MP1::CMetroidBeta, CPatterned, MetroidBeta, {})\nIMGUI_ENTITY_INSPECT(MP1::CMetroidPrimeStage2, CPatterned, MetroidPrimeEssence, {})\nIMGUI_ENTITY_INSPECT(MP1::CMetroidPrime, CPatterned, MetroidPrimeExo, {})\nIMGUI_ENTITY_INSPECT(MP1::CNewIntroBoss, CPatterned, NewIntroBoss, {})\nIMGUI_ENTITY_INSPECT(MP1::CPhazonHealingNodule, CPatterned, PhazonHealingNodule, {})\nIMGUI_ENTITY_INSPECT(MP1::CPuddleSpore, CPatterned, PuddleSpore, {})\nIMGUI_ENTITY_INSPECT(MP1::CPuddleToadGamma, CPatterned, PuddleToadGamma, {})\nIMGUI_ENTITY_INSPECT(MP1::CPuffer, CPatterned, Puffer, {})\nIMGUI_ENTITY_INSPECT(MP1::CRidley, CPatterned, Ridley, {})\nIMGUI_ENTITY_INSPECT(MP1::CRipper, CPatterned, Ripper, {})\nIMGUI_ENTITY_INSPECT(MP1::CSpacePirate, CPatterned, SpacePirate, {})\nIMGUI_ENTITY_INSPECT(MP1::CSpankWeed, CPatterned, SpankWeed, {})\nIMGUI_ENTITY_INSPECT(MP1::CThardus, CPatterned, Thardus, {})\nIMGUI_ENTITY_INSPECT(MP1::CThardusRockProjectile, CPatterned, ThardusRockProjectile, {})\nIMGUI_ENTITY_INSPECT(MP1::CTryclops, CPatterned, Tryclops, {})\nIMGUI_ENTITY_INSPECT(CWallWalker, CPatterned, WallWalker, {})\nIMGUI_ENTITY_INSPECT(MP1::CParasite, CWallWalker, Parasite, {})\nIMGUI_ENTITY_INSPECT(MP1::CSeedling, CWallWalker, Seedling, {})\nIMGUI_ENTITY_INSPECT(MP1::CWarWasp, CPatterned, WarWasp, {})\nIMGUI_ENTITY_INSPECT(MP1::COmegaPirate, MP1::CElitePirate, OmegaPirate, {})\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/ImGuiEntitySupport.hpp",
    "content": "#pragma once\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nbool ImGuiVector3fInput(const char* label, zeus::CVector3f& vec);\n}"
  },
  {
    "path": "Runtime/ImGuiPlayerLoadouts.cpp",
    "content": "#include \"Runtime/ImGuiPlayerLoadouts.hpp\"\n#include \"Runtime/Streams/ContainerReaders.hpp\"\n#include \"Runtime/Streams/ContainerWriters.hpp\"\n#include \"Runtime/Logging.hpp\"\n\n#include \"magic_enum.hpp\"\n\nnamespace metaforce {\nnamespace {\nconstexpr u32 CurrentVersion = 1;\n} // namespace\n\nImGuiPlayerLoadouts::Item::Item(CInputStream& in)\n: type(magic_enum::enum_cast<CPlayerState::EItemType>(in.Get<std::string>()).value()), amount(in.ReadLong()) {}\n\nvoid ImGuiPlayerLoadouts::Item::PutTo(COutputStream& out) const {\n  out.Put(magic_enum::enum_name<CPlayerState::EItemType>(type));\n  out.Put(amount);\n}\nImGuiPlayerLoadouts::LoadOut::LoadOut(CInputStream& in) : name(in.Get<std::string>()) { read_vector(items, in); }\n\nvoid ImGuiPlayerLoadouts::LoadOut::PutTo(COutputStream& out) const {\n  out.Put(name);\n  write_vector(items, out);\n}\n\nImGuiPlayerLoadouts::ImGuiPlayerLoadouts(CInputStream& in) {\n  FourCC magic;\n  in.Get(reinterpret_cast<u8*>(&magic), 4);\n  auto version = in.ReadLong();\n  if (magic != FOURCC('LOAD') && version != CurrentVersion) {\n    spdlog::error(\"Incorrect loadout version, expected {} got {}\", CurrentVersion, version);\n    return;\n  }\n  read_vector(loadouts, in);\n}\nvoid ImGuiPlayerLoadouts::PutTo(COutputStream& out) const {\n  auto magic = FOURCC('LOAD');\n  out.Put(reinterpret_cast<const u8*>(&magic), 4);\n  out.Put(CurrentVersion);\n  write_vector(loadouts, out);\n}\n} // namespace metaforce"
  },
  {
    "path": "Runtime/ImGuiPlayerLoadouts.hpp",
    "content": "#pragma once\n\n#include \"Runtime/CPlayerState.hpp\"\n\nnamespace metaforce {\nstruct ImGuiPlayerLoadouts {\n  struct Item {\n    CPlayerState::EItemType type = CPlayerState::EItemType::Invalid;\n    u32 amount = 0;\n    Item() = default;\n    explicit Item(CInputStream& in);\n    void PutTo(COutputStream& out) const;\n  };\n  struct LoadOut{\n    std::string name;\n    std::vector<Item> items;\n    LoadOut() = default;\n    explicit LoadOut(CInputStream& in);\n    void PutTo(COutputStream& out) const;\n  };\n  std::vector<LoadOut> loadouts;\n\n  ImGuiPlayerLoadouts() = default;\n  explicit ImGuiPlayerLoadouts(CInputStream& in);\n  void PutTo(COutputStream& out) const;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Input/CControllerAxis.hpp",
    "content": "#pragma once\n\nnamespace metaforce {\nenum class EJoyAxis {\n  LeftX,\n  LeftY,\n  RightX,\n  RightY,\n  MAX\n};\n\nclass CControllerAxis {\n  float x0_relative = 0.f;\n  float x4_absolute = 0.f;\n\npublic:\n  void SetRelativeValue(float val) { x0_relative = val; }\n  float GetRelativeValue() const { return x0_relative; }\n  void SetAbsoluteValue(float val) { x4_absolute = val; }\n  float GetAbsoluteValue() const { return x4_absolute; }\n};\n} // namespace metaforce"
  },
  {
    "path": "Runtime/Input/CControllerButton.hpp",
    "content": "#pragma once\n\n#include \"Runtime/GCNTypes.hpp\"\n\nnamespace metaforce {\nenum class EButton {\n  A,\n  B,\n  X,\n  Y,\n  Start,\n  Z,\n  Up,\n  Right,\n  Down,\n  Left,\n  L,\n  R,\n  MAX,\n};\n\nenum class EAnalogButton {\n  Left,\n  Right\n};\n\nclass CControllerButton {\n  bool x0_pressed = false;\n  bool x1_pressEvent = false;\n  bool x2_releaseEvent = false;\n\npublic:\n  void SetIsPressed(bool pressed) { x0_pressed = pressed; }\n  [[nodiscard]] bool GetIsPressed() const { return x0_pressed; }\n  void SetPressEvent(bool press){ x1_pressEvent = press; }\n  [[nodiscard]] bool GetPressEvent() const{ return x1_pressEvent; }\n  void SetReleaseEvent(bool release) { x2_releaseEvent = release;};\n  [[nodiscard]] bool GetReleaseEvent() const { return x2_releaseEvent; }\n};\n} // namespace metaforce"
  },
  {
    "path": "Runtime/Input/CControllerGamepadData.cpp",
    "content": "#include \"Runtime/Input/CControllerGamepadData.hpp\"\n\nnamespace metaforce {\n}\n"
  },
  {
    "path": "Runtime/Input/CControllerGamepadData.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Input/CControllerAxis.hpp\"\n#include \"Runtime/Input/CControllerButton.hpp\"\n\n#include <array>\n\nnamespace metaforce {\nclass CControllerGamepadData {\n  bool x0_present;\n  std::array<CControllerAxis, 4> x4_axes;\n  std::array<CControllerAxis, 2> x24_triggers;\n  std::array<CControllerButton, 12> x34_buttons;\n\npublic:\n  void SetDeviceIsPresent(bool present) { x0_present = present; }\n  [[nodiscard]] bool DeviceIsPresent() const { return x0_present; }\n\n  [[nodiscard]] const CControllerAxis& GetAxis(EJoyAxis axis) const { return x4_axes[u32(axis)]; }\n  [[nodiscard]] CControllerAxis& GetAxis(EJoyAxis axis) { return x4_axes[u32(axis)]; }\n\n  [[nodiscard]] const CControllerButton& GetButton(EButton button) const { return x34_buttons[u32(button)]; }\n  [[nodiscard]] CControllerButton& GetButton(EButton button) { return x34_buttons[u32(button)]; }\n\n  [[nodiscard]] const CControllerAxis& GetAnalogButton(EAnalogButton button) const { return x24_triggers[u32(button)]; }\n  [[nodiscard]] CControllerAxis& GetAnalogButton(EAnalogButton button) { return x24_triggers[u32(button)]; }\n};\n} // namespace metaforce"
  },
  {
    "path": "Runtime/Input/CDolphinController.cpp",
    "content": "#include \"Runtime/Input/CDolphinController.hpp\"\n\n#include <dolphin/si.h>\n#include <zeus/Math.hpp>\n\n#include <cstring>\n\nnamespace metaforce {\nCDolphinController::CDolphinController() {\n  static bool sIsInitialized = false;\n  if (!sIsInitialized) {\n    PADSetSpec(5);\n    PADInit();\n    sIsInitialized = true;\n  }\n}\n\nvoid CDolphinController::Poll() {\n  ReadDevices();\n  ProcessInputData();\n}\nvoid CDolphinController::SetMotorState(EIOPort port, EMotorState state) { x194_motorStates[u32(port)] = state; }\n\nfloat CDolphinController::GetAnalogStickMaxValue(EJoyAxis axis) {\n  if (axis >= EJoyAxis::LeftX && axis <= EJoyAxis::LeftY) {\n    return 72.f;\n  }\n\n  if (axis >= EJoyAxis::RightX && axis <= EJoyAxis::RightY) {\n    return 59.f;\n  }\n\n  return 0.f;\n}\n\nvoid CDolphinController::ReadDevices() {\n  std::array<PADStatus, 4> status{};\n  PADRead(status.data());\n  if (status[0].err == PAD_ERR_NONE) {\n    PADClamp(status.data());\n    x4_status = status;\n  } else {\n    x4_status[0].err = status[0].err;\n    x4_status[1].err = status[1].err;\n    x4_status[2].err = status[2].err;\n    x4_status[3].err = status[3].err;\n  }\n\n  for (u32 i = 0; i < 4; ++i) {\n    if (x4_status[i].err != PAD_ERR_NOT_READY) {\n      if (x4_status[i].err == PAD_ERR_NONE) {\n        x34_gamepadStates[i].SetDeviceIsPresent(true);\n      } else if (x4_status[i].err == PAD_ERR_NO_CONTROLLER) {\n        x1c8_invalidControllers |= PAD_CHAN0_BIT >> i;\n        x34_gamepadStates[i].SetDeviceIsPresent(false);\n      }\n    }\n\n    if (x1b4_controllerTypePollTime[i] == 0) {\n      const auto type = SIProbe(i);\n      if ((type & (SI_ERROR_NO_RESPONSE | SI_ERROR_UNKNOWN | SI_ERROR_BUSY)) == 0) {\n        x1b4_controllerTypePollTime[i] = 0x3c;\n        if (type == SI_GC_WIRELESS) {\n          x1a4_controllerTypes[i] = skTypeWavebird;\n        } else if (type == SI_GBA) { /* here for completeness, the GameCube adapter does not support GBA */\n          x1a4_controllerTypes[i] = skTypeGBA;\n        } else if (type == SI_GC_STANDARD) {\n          x1a4_controllerTypes[i] = skTypeStandard;\n        }\n      } else {\n        x1a4_controllerTypes[i] = skTypeUnknown;\n      }\n    } else {\n      --x1b4_controllerTypePollTime[i];\n    }\n  }\n\n  if (x1c8_invalidControllers != 0 && PADReset(x1c8_invalidControllers)) {\n    x1c8_invalidControllers = 0;\n  }\n}\n\nvoid CDolphinController::ProcessInputData() {\n  for (u32 i = 0; i < 4; ++i) {\n    if (!x34_gamepadStates[i].DeviceIsPresent()) {\n      continue;\n    }\n    ProcessAxis(i, EJoyAxis::LeftX);\n    ProcessAxis(i, EJoyAxis::LeftY);\n    ProcessAxis(i, EJoyAxis::RightX);\n    ProcessAxis(i, EJoyAxis::RightY);\n    ProcessButtons(i);\n  }\n}\n\nvoid CDolphinController::ProcessAxis(u32 controller, EJoyAxis axis) {\n  const auto maxAxisValue = GetAnalogStickMaxValue(axis);\n  auto& data = x34_gamepadStates[controller].GetAxis(axis);\n\n  float axisValue = 0.f;\n  if (axis == EJoyAxis::LeftX) {\n    axisValue = x4_status[controller].stickX;\n  } else if (axis == EJoyAxis::LeftY) {\n    axisValue = x4_status[controller].stickY;\n  } else if (axis == EJoyAxis::RightX) {\n    axisValue = x4_status[controller].substickX;\n  } else if (axis == EJoyAxis::RightY) {\n    axisValue = x4_status[controller].substickY;\n  }\n  axisValue *= 1.f / maxAxisValue;\n  float absolute = zeus::clamp(kAbsoluteMinimum, axisValue, kAbsoluteMaximum);\n  float relativeValue = zeus::clamp(kRelativeMinimum, absolute - data.GetAbsoluteValue(), kRelativeMaximum);\n  data.SetAbsoluteValue(absolute);\n  data.SetRelativeValue(relativeValue);\n}\n\nstatic constexpr std::array<u16, size_t(EButton::MAX)> mButtonMapping{\n    PAD_BUTTON_A,  PAD_BUTTON_B,     PAD_BUTTON_X,    PAD_BUTTON_Y,    PAD_BUTTON_START, PAD_TRIGGER_Z,\n    PAD_BUTTON_UP, PAD_BUTTON_RIGHT, PAD_BUTTON_DOWN, PAD_BUTTON_LEFT, PAD_TRIGGER_L,    PAD_TRIGGER_R,\n};\n\nvoid CDolphinController::ProcessButtons(u32 controller) {\n  for (u32 i = 0; i < u32(EButton::MAX); ++i) {\n    ProcessDigitalButton(controller, x34_gamepadStates[controller].GetButton(EButton(i)), mButtonMapping[i]);\n  }\n\n  ProcessAnalogButton(x4_status[controller].triggerLeft,\n                      x34_gamepadStates[controller].GetAnalogButton(EAnalogButton::Left));\n  ProcessAnalogButton(x4_status[controller].triggerRight,\n                      x34_gamepadStates[controller].GetAnalogButton(EAnalogButton::Right));\n}\n\nvoid CDolphinController::ProcessDigitalButton(u32 controller, CControllerButton& button, u16 mapping) {\n  bool btnPressed = (x4_status[controller].button & mapping) != 0;\n  button.SetPressEvent(PADButtonDown(button.GetIsPressed(), btnPressed));\n  button.SetReleaseEvent(PADButtonUp(button.GetIsPressed(), btnPressed));\n  button.SetIsPressed(btnPressed);\n}\n\nvoid CDolphinController::ProcessAnalogButton(float value, CControllerAxis& axis) {\n  float absolute = value * (1 / 150.f);\n  if (value * (1 / 150.f) > kAbsoluteMaximum) {\n    absolute = kAbsoluteMaximum;\n  }\n\n  float relative = absolute - axis.GetAbsoluteValue();\n  if (relative > kRelativeMaximum) {\n    relative = kRelativeMaximum;\n  }\n\n  axis.SetRelativeValue(relative);\n  axis.SetAbsoluteValue(absolute);\n}\n\nbool CDolphinController::Initialize() {\n  // GBAInit();\n  memset(x4_status.data(), 0, sizeof(PADStatus) * x4_status.size());\n  x34_gamepadStates[0].SetDeviceIsPresent(false);\n  x194_motorStates[0] = EMotorState::StopHard;\n  x1b4_controllerTypePollTime[0] = 0;\n  x1a4_controllerTypes[0] = skTypeUnknown;\n  x34_gamepadStates[1].SetDeviceIsPresent(false);\n  x194_motorStates[1] = EMotorState::StopHard;\n  x1b4_controllerTypePollTime[1] = 0;\n  x1a4_controllerTypes[0] = skTypeUnknown;\n  x34_gamepadStates[2].SetDeviceIsPresent(false);\n  x194_motorStates[2] = EMotorState::StopHard;\n  x1b4_controllerTypePollTime[2] = 0;\n  x1a4_controllerTypes[0] = skTypeUnknown;\n  x34_gamepadStates[3].SetDeviceIsPresent(false);\n  x194_motorStates[3] = EMotorState::StopHard;\n  x1b4_controllerTypePollTime[3] = 0;\n  x1a4_controllerTypes[0] = skTypeUnknown;\n  PADControlAllMotors(reinterpret_cast<const u32*>(x194_motorStates.data()));\n  Poll();\n  return true;\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Input/CDolphinController.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Input/IController.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n\nnamespace metaforce {\n\nclass CDolphinController : public IController {\n  static constexpr u32 skTypeUnknown = SBIG('UNKN');\n  static constexpr u32 skTypeStandard = SBIG('STND');\n  static constexpr u32 skTypeGBA = SBIG('GBA_');\n  static constexpr u32 skTypeWavebird = SBIG('WAVE');\n\n  std::array<PADStatus, 4> x4_status{};\n  std::array<CControllerGamepadData, 4> x34_gamepadStates{};\n  std::array<EMotorState, 4> x194_motorStates{};\n  std::array<u32, 4> x1a4_controllerTypes{};\n  std::array<u32, 4> x1b4_controllerTypePollTime{};\n  u32 x1c4_ = 0xf0000000;\n  u32 x1c8_invalidControllers = 0;\n  u32 x1cc_ = 0;\n\npublic:\n  CDolphinController();\n  void Poll() override;\n  [[nodiscard]] u32 GetDeviceCount() const override { return 4; };\n  [[nodiscard]] CControllerGamepadData& GetGamepadData(u32 controller) override {\n    return x34_gamepadStates[controller];\n  };\n  [[nodiscard]] u32 GetControllerType(u32 controller) const override { return x1a4_controllerTypes[controller]; }\n  void SetMotorState(EIOPort port, EMotorState state) override;\n  float GetAnalogStickMaxValue(EJoyAxis axis);\n  bool Initialize();\n\nprivate:\n  void ReadDevices();\n  void ProcessInputData();\n  void ProcessAxis(u32 controller, EJoyAxis axis);\n  void ProcessButtons(u32 controller);\n  void ProcessDigitalButton(u32 controller, CControllerButton& button, u16 mapping);\n  void ProcessAnalogButton(float value, CControllerAxis& axis);\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Input/CFinalInput.cpp",
    "content": "#include \"Runtime/Input/CFinalInput.hpp\"\n\n#include <zeus/Math.hpp>\n\nnamespace metaforce {\n\nCFinalInput::CFinalInput() = default;\n\nCFinalInput::CFinalInput(int cIdx, float dt, const CControllerGamepadData& data, float leftDiv, float rightDiv)\n: x0_dt(dt)\n, x4_controllerIdx(cIdx)\n, x8_anaLeftX(data.GetAxis(EJoyAxis::LeftX).GetAbsoluteValue())\n, xc_anaLeftY(data.GetAxis(EJoyAxis::LeftY).GetAbsoluteValue())\n, x10_anaRightX(data.GetAxis(EJoyAxis::RightX).GetAbsoluteValue())\n, x14_anaRightY(data.GetAxis(EJoyAxis::RightY).GetAbsoluteValue())\n, x18_anaLeftTrigger(data.GetAnalogButton(EAnalogButton::Left).GetAbsoluteValue())\n, x1c_anaRightTrigger(data.GetAnalogButton(EAnalogButton::Right).GetAbsoluteValue())\n, x24_anaLeftTriggerP(data.GetAnalogButton(EAnalogButton::Left).GetRelativeValue())\n, x28_anaRightTriggerP(data.GetAnalogButton(EAnalogButton::Right).GetRelativeValue())\n, x2c_b24_A(data.GetButton(EButton::A).GetIsPressed())\n, x2c_b25_B(data.GetButton(EButton::B).GetIsPressed())\n, x2c_b26_X(data.GetButton(EButton::X).GetIsPressed())\n, x2c_b27_Y(data.GetButton(EButton::Y).GetIsPressed())\n, x2c_b28_Z(data.GetButton(EButton::Z).GetIsPressed())\n, x2c_b29_L(data.GetButton(EButton::L).GetIsPressed())\n, x2c_b30_R(data.GetButton(EButton::R).GetIsPressed())\n, x2c_b31_DPUp(data.GetButton(EButton::Up).GetIsPressed())\n, x2d_b24_DPRight(data.GetButton(EButton::Right).GetIsPressed())\n, x2d_b25_DPDown(data.GetButton(EButton::Down).GetIsPressed())\n, x2d_b26_DPLeft(data.GetButton(EButton::Left).GetIsPressed())\n, x2d_b27_Start(data.GetButton(EButton::Start).GetIsPressed())\n, x2d_b28_PA(data.GetButton(EButton::A).GetPressEvent())\n, x2d_b29_PB(data.GetButton(EButton::B).GetPressEvent())\n, x2d_b30_PX(data.GetButton(EButton::X).GetPressEvent())\n, x2d_b31_PY(data.GetButton(EButton::Y).GetPressEvent())\n, x2e_b24_PZ(data.GetButton(EButton::Z).GetPressEvent())\n, x2e_b25_PL(data.GetButton(EButton::L).GetPressEvent())\n, x2e_b26_PR(data.GetButton(EButton::R).GetPressEvent())\n, x2e_b27_PDPUp(data.GetButton(EButton::Up).GetPressEvent())\n, x2e_b28_PDPRight(data.GetButton(EButton::Right).GetPressEvent())\n, x2e_b29_PDPDown(data.GetButton(EButton::Down).GetPressEvent())\n, x2e_b30_PDPLeft(data.GetButton(EButton::Left).GetPressEvent())\n, x2e_b31_PStart(data.GetButton(EButton::Start).GetPressEvent()) {\n  InitializeAnalog(leftDiv, rightDiv);\n}\n\n#if 0\nCFinalInput::CFinalInput(int cIdx, float dt, const CKeyboardMouseControllerData& data, const CFinalInput& prevInput)\n: x0_dt(dt)\n, x4_controllerIdx(cIdx)\n, x18_anaLeftTrigger(false)\n, x1c_anaRightTrigger(false)\n, x20_enableAnaLeftXP(DLARight() && !prevInput.DLARight())\n, x21_enableAnaLeftYP(DLAUp() && !prevInput.DLAUp())\n, x22_enableAnaRightXP(DRARight() && !prevInput.DRARight())\n, x23_enableAnaRightYP(DRAUp() && !prevInput.DRAUp())\n, x24_anaLeftTriggerP(DLTrigger() && !prevInput.DLTrigger())\n, x28_anaRightTriggerP(DRTrigger() && !prevInput.DRTrigger())\n, x2c_b31_DPUp(data.m_specialKeys[size_t(ESpecialKey::Up)])\n, x2d_b24_DPRight(data.m_specialKeys[size_t(ESpecialKey::Right)])\n, x2d_b25_DPDown(data.m_specialKeys[size_t(ESpecialKey::Down)])\n, x2d_b26_DPLeft(data.m_specialKeys[size_t(ESpecialKey::Left)])\n, x2d_b28_PA(DA() && !prevInput.DA())\n, x2d_b29_PB(DB() && !prevInput.DB())\n, x2d_b30_PX(DX() && !prevInput.DX())\n, x2d_b31_PY(DY() && !prevInput.DY())\n, x2e_b24_PZ(DZ() && !prevInput.DZ())\n, x2e_b25_PL(DL() && !prevInput.DL())\n, x2e_b26_PR(DR() && !prevInput.DR())\n, x2e_b27_PDPUp(DDPUp() && !prevInput.DDPUp())\n, x2e_b28_PDPRight(DDPRight() && !prevInput.DDPRight())\n, x2e_b29_PDPDown(DDPDown() && !prevInput.DDPDown())\n, x2e_b30_PDPLeft(DDPLeft() && !prevInput.DDPLeft())\n, x2e_b31_PStart(DStart() && !prevInput.DStart())\n, m_kbm(data) {\n  if (prevInput.m_kbm) {\n    for (size_t i = 0; i < m_PCharKeys.size(); ++i) {\n      m_PCharKeys[i] = data.m_charKeys[i] && !prevInput.m_kbm->m_charKeys[i];\n    }\n    for (size_t i = 0; i < m_PSpecialKeys.size(); ++i) {\n      m_PSpecialKeys[i] = data.m_specialKeys[i] && !prevInput.m_kbm->m_specialKeys[i];\n    }\n    for (size_t i = 0; i < m_PMouseButtons.size(); ++i) {\n      m_PMouseButtons[i] = data.m_mouseButtons[i] && !prevInput.m_kbm->m_mouseButtons[i];\n    }\n  }\n}\n\nCFinalInput& CFinalInput::operator|=(const CFinalInput& other) {\n  if (std::fabs(other.x8_anaLeftX) > std::fabs(x8_anaLeftX))\n    x8_anaLeftX = other.x8_anaLeftX;\n  if (std::fabs(other.xc_anaLeftY) > std::fabs(xc_anaLeftY))\n    xc_anaLeftY = other.xc_anaLeftY;\n  if (std::fabs(other.x10_anaRightX) > std::fabs(x10_anaRightX))\n    x10_anaRightX = other.x10_anaRightX;\n  if (std::fabs(other.x14_anaRightY) > std::fabs(x14_anaRightY))\n    x14_anaRightY = other.x14_anaRightY;\n  if (std::fabs(other.x18_anaLeftTrigger) > std::fabs(x18_anaLeftTrigger))\n    x18_anaLeftTrigger = other.x18_anaLeftTrigger;\n  if (std::fabs(other.x1c_anaRightTrigger) > std::fabs(x1c_anaRightTrigger))\n    x1c_anaRightTrigger = other.x1c_anaRightTrigger;\n  x20_enableAnaLeftXP |= other.x20_enableAnaLeftXP;\n  x21_enableAnaLeftYP |= other.x21_enableAnaLeftYP;\n  x22_enableAnaRightXP |= other.x22_enableAnaRightXP;\n  x23_enableAnaRightYP |= other.x23_enableAnaRightYP;\n  x24_anaLeftTriggerP = other.x24_anaLeftTriggerP;\n  x28_anaRightTriggerP = other.x28_anaRightTriggerP;\n  x2c_b24_A |= other.x2c_b24_A;\n  x2c_b25_B |= other.x2c_b25_B;\n  x2c_b26_X |= other.x2c_b26_X;\n  x2c_b27_Y |= other.x2c_b27_Y;\n  x2c_b28_Z |= other.x2c_b28_Z;\n  x2c_b29_L |= other.x2c_b29_L;\n  x2c_b30_R |= other.x2c_b30_R;\n  x2c_b31_DPUp |= other.x2c_b31_DPUp;\n  x2d_b24_DPRight |= other.x2d_b24_DPRight;\n  x2d_b25_DPDown |= other.x2d_b25_DPDown;\n  x2d_b26_DPLeft |= other.x2d_b26_DPLeft;\n  x2d_b27_Start |= other.x2d_b27_Start;\n  x2d_b28_PA |= other.x2d_b28_PA;\n  x2d_b29_PB |= other.x2d_b29_PB;\n  x2d_b30_PX |= other.x2d_b30_PX;\n  x2d_b31_PY |= other.x2d_b31_PY;\n  x2e_b24_PZ |= other.x2e_b24_PZ;\n  x2e_b25_PL |= other.x2e_b25_PL;\n  x2e_b26_PR |= other.x2e_b26_PR;\n  x2e_b27_PDPUp |= other.x2e_b27_PDPUp;\n  x2e_b28_PDPRight |= other.x2e_b28_PDPRight;\n  x2e_b29_PDPDown |= other.x2e_b29_PDPDown;\n  x2e_b30_PDPLeft |= other.x2e_b30_PDPLeft;\n  x2e_b31_PStart |= other.x2e_b31_PStart;\n  if (other.m_kbm) {\n    m_kbm = other.m_kbm;\n    m_PCharKeys = other.m_PCharKeys;\n    m_PSpecialKeys = other.m_PSpecialKeys;\n    m_PMouseButtons = other.m_PMouseButtons;\n  }\n  m_which = other.m_which;\n  return *this;\n}\n#endif\n\nCFinalInput CFinalInput::ScaleAnalogueSticks(float leftDiv, float rightDiv) const {\n  CFinalInput ret = *this;\n  ret.x8_anaLeftX = zeus::clamp(-1.f, x8_anaLeftX / leftDiv, 1.f);\n  ret.xc_anaLeftY = zeus::clamp(-1.f, xc_anaLeftY / leftDiv, 1.f);\n  ret.x10_anaRightX = zeus::clamp(-1.f, x10_anaRightX / rightDiv, 1.f);\n  ret.x14_anaRightY = zeus::clamp(-1.f, x14_anaRightY / rightDiv, 1.f);\n  return ret;\n}\n\nstatic std::array<std::array<bool, 4>, 4> sIsAnalogPressed{};\n\nvoid CFinalInput::InitializeAnalog(float leftDiv, float rightDiv) {\n  x8_anaLeftX = zeus::clamp(-1.f, x8_anaLeftX / leftDiv, 1.f);\n  xc_anaLeftY = zeus::clamp(-1.f, xc_anaLeftY / leftDiv, 1.f);\n  x10_anaRightX = zeus::clamp(-1.f, x10_anaRightX / rightDiv, 1.f);\n  x14_anaRightY = zeus::clamp(-1.f, x14_anaRightY / rightDiv, 1.f);\n\n  if (xc_anaLeftY > 0.7f && !sIsAnalogPressed[x4_controllerIdx][0]) {\n    sIsAnalogPressed[x4_controllerIdx][0] = true;\n    x21_enableAnaLeftYP = true;\n  } else if (xc_anaLeftY > 0.7f && sIsAnalogPressed[x4_controllerIdx][0]) {\n    x21_enableAnaLeftYP = false;\n  } else if (xc_anaLeftY < -0.7f && !sIsAnalogPressed[x4_controllerIdx][0]) {\n    x21_enableAnaLeftYP = true;\n    sIsAnalogPressed[x4_controllerIdx][0] = true;\n  } else if (xc_anaLeftY < -0.7f && sIsAnalogPressed[x4_controllerIdx][0]) {\n    x21_enableAnaLeftYP = false;\n  } else if (std::fabs(xc_anaLeftY) < 0.7f) {\n    x21_enableAnaLeftYP = false;\n    sIsAnalogPressed[x4_controllerIdx][0] = false;\n  }\n\n  if (x8_anaLeftX > 0.7f && !sIsAnalogPressed[x4_controllerIdx][1]) {\n    sIsAnalogPressed[x4_controllerIdx][1] = true;\n    x20_enableAnaLeftXP = true;\n  } else if (x8_anaLeftX > 0.7f && sIsAnalogPressed[x4_controllerIdx][1]) {\n    x20_enableAnaLeftXP = false;\n  } else if (x8_anaLeftX < -0.7f && !sIsAnalogPressed[x4_controllerIdx][1]) {\n    x20_enableAnaLeftXP = true;\n    sIsAnalogPressed[x4_controllerIdx][1] = true;\n  } else if (x8_anaLeftX < -0.7f && sIsAnalogPressed[x4_controllerIdx][1]) {\n    x20_enableAnaLeftXP = false;\n  } else if (std::fabs(x8_anaLeftX) < 0.7f) {\n    x20_enableAnaLeftXP = false;\n    sIsAnalogPressed[x4_controllerIdx][1] = false;\n  }\n\n  if (x14_anaRightY > 0.7f && !sIsAnalogPressed[x4_controllerIdx][2]) {\n    sIsAnalogPressed[x4_controllerIdx][2] = true;\n    x23_enableAnaRightYP = true;\n  } else if (x14_anaRightY > 0.7f && sIsAnalogPressed[x4_controllerIdx][2]) {\n    x23_enableAnaRightYP = false;\n  } else if (x14_anaRightY < -0.7f && !sIsAnalogPressed[x4_controllerIdx][2]) {\n    x23_enableAnaRightYP = true;\n    sIsAnalogPressed[x4_controllerIdx][2] = true;\n  } else if (x14_anaRightY < -0.7f && sIsAnalogPressed[x4_controllerIdx][2]) {\n    x23_enableAnaRightYP = false;\n  } else if (std::fabs(x14_anaRightY) < 0.7f) {\n    x23_enableAnaRightYP = false;\n    sIsAnalogPressed[x4_controllerIdx][2] = false;\n  }\n\n  if (x10_anaRightX > 0.7f && !sIsAnalogPressed[x4_controllerIdx][3]) {\n    sIsAnalogPressed[x4_controllerIdx][3] = true;\n    x22_enableAnaRightXP = true;\n  } else if (x10_anaRightX > 0.7f && sIsAnalogPressed[x4_controllerIdx][3]) {\n    x22_enableAnaRightXP = false;\n  } else if (x10_anaRightX < -0.7f && !sIsAnalogPressed[x4_controllerIdx][3]) {\n    x22_enableAnaRightXP = true;\n    sIsAnalogPressed[x4_controllerIdx][3] = true;\n  } else if (x10_anaRightX < -0.7f && sIsAnalogPressed[x4_controllerIdx][3]) {\n    x22_enableAnaRightXP = false;\n  } else if (std::fabs(x10_anaRightX) < 0.7f) {\n    x22_enableAnaRightXP = false;\n    sIsAnalogPressed[x4_controllerIdx][3] = false;\n  }\n}\n\n/* The following code is derived from pad.c in libogc\n *\n *  Copyright (C) 2004 - 2009\n *  Michael Wiedenbauer (shagkur)\n *  Dave Murphy (WinterMute)\n *\n * This software is provided 'as-is', without any express or implied\n * warranty.  In no event will the authors be held liable for any\n * damages arising from the use of this software.\n *\n * Permission is granted to anyone to use this software for any\n * purpose, including commercial applications, and to alter it and\n * redistribute it freely, subject to the following restrictions:\n *\n * 1. The origin of this software must not be misrepresented; you\n *    must not claim that you wrote the original software. If you use\n *    this software in a product, an acknowledgment in the product\n *    documentation would be appreciated but is not required.\n * 2. Altered source versions must be plainly marked as such, and\n *    must not be misrepresented as being the original software.\n * 3. This notice may not be removed or altered from any source\n *    distribution.\n */\n\nconstexpr std::array<int16_t, 8> pad_clampregion{\n    30, 180, 15, 72, 40, 15, 59, 31,\n};\n\nstatic void pad_clampstick(int16_t& px, int16_t& py, int16_t max, int16_t xy, int16_t min) {\n  int x = px;\n  int y = py;\n\n  int signX;\n  if (x > 0) {\n    signX = 1;\n  } else {\n    signX = -1;\n    x = -x;\n  }\n\n  int signY;\n  if (y > 0) {\n    signY = 1;\n  } else {\n    signY = -1;\n    y = -y;\n  }\n\n  if (x <= min) {\n    x = 0;\n  } else {\n    x -= min;\n  }\n\n  if (y <= min) {\n    y = 0;\n  } else {\n    y -= min;\n  }\n\n  if (x == 0 && y == 0) {\n    px = py = 0;\n    return;\n  }\n\n  if (xy * y <= xy * x) {\n    const int d = xy * x + (max - xy) * y;\n    if (xy * max < d) {\n      x = int16_t(xy * max * x / d);\n      y = int16_t(xy * max * y / d);\n    }\n  } else {\n    const int d = xy * y + (max - xy) * x;\n    if (xy * max < d) {\n      x = int16_t(xy * max * x / d);\n      y = int16_t(xy * max * y / d);\n    }\n  }\n\n  px = int16_t(signX * x);\n  py = int16_t(signY * y);\n}\n\nstatic void pad_clamptrigger(int16_t& trigger) {\n  const int16_t min = pad_clampregion[0];\n  const int16_t max = pad_clampregion[1];\n\n  if (trigger <= min) {\n    trigger = 0;\n  } else {\n    if (max < trigger) {\n      trigger = max;\n    }\n    trigger -= min;\n  }\n}\n\nvoid SAuroraControllerState::clamp() {\n  pad_clampstick(m_axes[size_t(EControllerAxis::LeftX)], m_axes[size_t(EControllerAxis::LeftY)],\n                 pad_clampregion[3], pad_clampregion[4], pad_clampregion[2]);\n  pad_clampstick(m_axes[size_t(EControllerAxis::RightX)], m_axes[size_t(EControllerAxis::RightY)],\n                 pad_clampregion[6], pad_clampregion[7], pad_clampregion[5]);\n  pad_clamptrigger(m_axes[size_t(EControllerAxis::TriggerLeft)]);\n  pad_clamptrigger(m_axes[size_t(EControllerAxis::TriggerRight)]);\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Input/CFinalInput.hpp",
    "content": "#pragma once\n\n#include <array>\n#include <cstring>\n#include <bitset>\n\n#include \"Runtime/Input/CKeyboardMouseController.hpp\"\n#include \"Runtime/Input/CControllerGamepadData.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n\nnamespace metaforce {\n\nstruct SAuroraControllerState {\n  u32 m_which = -1;\n  bool m_isGamecube = false;\n  bool m_hasRumble = false;\n  std::array<int16_t, size_t(EControllerAxis::MAX)> m_axes{};\n  std::bitset<size_t(EControllerButton::MAX)> m_btns{};\n\n  SAuroraControllerState() = default;\n  SAuroraControllerState(uint32_t which, bool isGamecube, bool hasRumble)\n  : m_which(which), m_isGamecube(isGamecube), m_hasRumble(hasRumble) {}\n  void clamp();\n};\n\nstruct CFinalInput {\n  float x0_dt = 0.0f;\n  u32 x4_controllerIdx = 0;\n  float x8_anaLeftX = 0.0f;\n  float xc_anaLeftY = 0.0f;\n  float x10_anaRightX = 0.0f;\n  float x14_anaRightY = 0.0f;\n  float x18_anaLeftTrigger = 0.0f;\n  float x1c_anaRightTrigger = 0.0f;\n\n  bool x20_enableAnaLeftXP  = false;\n  bool x21_enableAnaLeftYP  = false;\n  bool x22_enableAnaRightXP = false;\n  bool x23_enableAnaRightYP = false;\n\n  float x24_anaLeftTriggerP = 0.f;\n  float x28_anaRightTriggerP = 0.f;\n\n  bool x2c_b24_A : 1 = false;\n  bool x2c_b25_B : 1 = false;\n  bool x2c_b26_X : 1 = false;\n  bool x2c_b27_Y : 1 = false;\n  bool x2c_b28_Z : 1 = false;\n  bool x2c_b29_L : 1 = false;\n  bool x2c_b30_R : 1 = false;\n  bool x2c_b31_DPUp : 1 = false;\n  bool x2d_b24_DPRight : 1 = false;\n  bool x2d_b25_DPDown : 1 = false;\n  bool x2d_b26_DPLeft : 1 = false;\n  bool x2d_b27_Start : 1 = false;\n\n  bool x2d_b28_PA : 1 = false;\n  bool x2d_b29_PB : 1 = false;\n  bool x2d_b30_PX : 1 = false;\n  bool x2d_b31_PY : 1 = false;\n  bool x2e_b24_PZ : 1 = false;\n  bool x2e_b25_PL : 1 = false;\n  bool x2e_b26_PR : 1 = false;\n  bool x2e_b27_PDPUp : 1 = false;\n  bool x2e_b28_PDPRight : 1 = false;\n  bool x2e_b29_PDPDown : 1 = false;\n  bool x2e_b30_PDPLeft : 1 = false;\n  bool x2e_b31_PStart : 1 = false;\n\n  std::optional<CKeyboardMouseControllerData> m_kbm;\n\n  std::array<bool, 256> m_PCharKeys{};\n  std::array<bool, size_t(ESpecialKey::MAX)> m_PSpecialKeys{};\n  std::array<bool, 6> m_PMouseButtons{};\n\n\n  CFinalInput();\n  CFinalInput(int cIdx, float dt, const CControllerGamepadData& data, float leftDiv, float rightDiv);\n  CFinalInput(int cIdx, float dt, const CKeyboardMouseControllerData& data, const CFinalInput& prevInput);\n\n  //CFinalInput& operator|=(const CFinalInput& other);\n\n  bool operator==(const CFinalInput& other) const { return memcmp(this, &other, sizeof(CFinalInput)) == 0; }\n  bool operator!=(const CFinalInput& other) const { return !operator==(other); }\n\n  float DeltaTime() const { return x0_dt; }\n  u32 ControllerIdx() const { return x4_controllerIdx; }\n\n  bool PStart() const { return x2e_b31_PStart; }\n  bool PR() const { return x2e_b26_PR; }\n  bool PL() const { return x2e_b25_PL; }\n  bool PZ() const { return x2e_b24_PZ; }\n  bool PY() const { return x2d_b31_PY; }\n  bool PX() const { return x2d_b30_PX; }\n  bool PB() const { return x2d_b29_PB; }\n  bool PA() const { return x2d_b28_PA; }\n  bool PDPRight() const { return x2e_b28_PDPRight; }\n  bool PDPLeft() const { return x2e_b30_PDPLeft; }\n  bool PDPDown() const { return x2e_b29_PDPDown; }\n  bool PDPUp() const { return x2e_b27_PDPUp; }\n  bool PRTrigger() const { return x28_anaRightTriggerP > 0.05f; }\n  bool PLTrigger() const { return x24_anaLeftTriggerP > 0.05f; }\n  bool PRARight() const { return x10_anaRightX > 0.7f && x22_enableAnaRightXP; }\n  bool PRALeft() const { return x10_anaRightX < -0.7f && x22_enableAnaRightXP; }\n  bool PRADown() const { return x14_anaRightY < -0.7f && x23_enableAnaRightYP; }\n  bool PRAUp() const { return x14_anaRightY > 0.7f && x23_enableAnaRightYP; }\n  bool PLARight() const { return x8_anaLeftX > 0.7f && x20_enableAnaLeftXP; }\n  bool PLALeft() const { return x8_anaLeftX < -0.7f && x20_enableAnaLeftXP; }\n  bool PLADown() const { return xc_anaLeftY < -0.7f && x21_enableAnaLeftYP; }\n  bool PLAUp() const { return xc_anaLeftY > 0.7f && x21_enableAnaLeftYP; }\n  bool DStart() const { return x2d_b27_Start; }\n  bool DR() const { return x2c_b30_R; }\n  bool DL() const { return x2c_b29_L; }\n  bool DZ() const { return x2c_b28_Z; }\n  bool DY() const { return x2c_b27_Y; }\n  bool DX() const { return x2c_b26_X; }\n  bool DB() const { return x2c_b25_B; }\n  bool DA() const { return x2c_b24_A; }\n  bool DDPRight() const { return x2d_b24_DPRight; }\n  bool DDPLeft() const { return x2d_b26_DPLeft; }\n  bool DDPDown() const { return x2d_b25_DPDown; }\n  bool DDPUp() const { return x2c_b31_DPUp; }\n  bool DRTrigger() const { return x1c_anaRightTrigger > 0.05f; }\n  bool DLTrigger() const { return x18_anaLeftTrigger > 0.05f; }\n  bool DRARight() const { return x10_anaRightX > 0.7f; }\n  bool DRALeft() const { return x10_anaRightX < -0.7f; }\n  bool DRADown() const { return x14_anaRightY < -0.7f; }\n  bool DRAUp() const { return x14_anaRightY > 0.7f; }\n  bool DLARight() const { return x8_anaLeftX > 0.7f; }\n  bool DLALeft() const { return x8_anaLeftX < -0.7f; }\n  bool DLADown() const { return xc_anaLeftY < -0.7f; }\n  bool DLAUp() const { return xc_anaLeftY > 0.7f; }\n  float AStart() const { return x2d_b27_Start ? 1.f : 0.f; }\n  float AR() const { return x2c_b30_R ? 1.f : 0.f; }\n  float AL() const { return x2c_b29_L ? 1.f : 0.f; }\n  float AZ() const { return x2c_b28_Z ? 1.f : 0.f; }\n  float AY() const { return x2c_b27_Y ? 1.f : 0.f; }\n  float AX() const { return x2c_b26_X ? 1.f : 0.f; }\n  float AB() const { return x2c_b25_B ? 1.f : 0.f; }\n  float AA() const { return x2c_b24_A ? 1.f : 0.f; }\n  float ADPRight() const { return x2d_b24_DPRight ? 1.f : 0.f; }\n  float ADPLeft() const { return x2d_b26_DPLeft ? 1.f : 0.f; }\n  float ADPDown() const { return x2d_b25_DPDown ? 1.f : 0.f; }\n  float ADPUp() const { return x2c_b31_DPUp ? 1.f : 0.f; }\n  float ARTrigger() const { return x1c_anaRightTrigger; }\n  float ALTrigger() const { return x18_anaLeftTrigger; }\n  float ARARight() const { return x10_anaRightX > 0.f ? x10_anaRightX : 0.f; }\n  float ARALeft() const { return x10_anaRightX < 0.f ? -x10_anaRightX : 0.f; }\n  float ARADown() const { return x14_anaRightY < 0.f ? -x14_anaRightY : 0.f; }\n  float ARAUp() const { return x14_anaRightY > 0.f ? x14_anaRightY : 0.f; }\n  float ALARight() const { return x8_anaLeftX > 0.f ? x8_anaLeftX : 0.f; }\n  float ALALeft() const { return x8_anaLeftX < 0.f ? -x8_anaLeftX : 0.f; }\n  float ALADown() const { return xc_anaLeftY < 0.f ? -xc_anaLeftY : 0.f; }\n  float ALAUp() const { return xc_anaLeftY > 0.f ? xc_anaLeftY : 0.f; }\n\n  float ALeftX() const { return x8_anaLeftX; }\n  float ALeftY() const { return xc_anaLeftY; }\n  float ARightX() const { return x10_anaRightX; }\n  float ARightY() const { return x14_anaRightY; }\n  float ALeftTrigger() const { return x18_anaLeftTrigger; }\n  float ARightTrigger() const { return x1c_anaRightTrigger; }\n\n  CFinalInput ScaleAnalogueSticks(float leftDiv, float rightDiv) const;\n  void InitializeAnalog(float leftDiv, float rightDiv);\n\n  bool PKey(char k) const { return m_kbm && m_PCharKeys[size_t(k)]; }\n  bool PSpecialKey(ESpecialKey k) const { return m_kbm && m_PSpecialKeys[size_t(k)]; }\n  bool PMouseButton(EMouseButton k) const { return m_kbm && m_PMouseButtons[size_t(k)]; }\n  bool DKey(char k) const { return m_kbm && m_kbm->m_charKeys[size_t(k)]; }\n  bool DSpecialKey(ESpecialKey k) const { return m_kbm && m_kbm->m_specialKeys[size_t(k)]; }\n  bool DMouseButton(EMouseButton k) const { return m_kbm && m_kbm->m_mouseButtons[size_t(k)]; }\n  float AKey(char k) const { return DKey(k) ? 1.f : 0.f; }\n  float ASpecialKey(ESpecialKey k) const { return DSpecialKey(k) ? 1.f : 0.f; }\n  float AMouseButton(EMouseButton k) const { return DMouseButton(k) ? 1.f : 0.f; }\n\n  const std::optional<CKeyboardMouseControllerData>& GetKBM() const { return m_kbm; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Input/CInputGenerator.cpp",
    "content": "#include \"Runtime/Input/CInputGenerator.hpp\"\n\n#include \"Runtime/Input/IController.hpp\"\n#include \"Runtime/Input/CFinalInput.hpp\"\n\n#include \"Runtime/CArchitectureQueue.hpp\"\n\nnamespace metaforce {\nCInputGenerator::CInputGenerator(/*COsContext& context, */ float leftDiv, float rightDiv)\n/*:  x0_context(context) */ {\n  x4_controller.reset(IController::Create());\n  xc_leftDiv = leftDiv;\n  x10_rightDiv = rightDiv;\n}\n\nvoid CInputGenerator::Update(float dt, CArchitectureQueue& queue) {\n#if 0\n  if (!x0_context.Update()) {\n    return;\n  }\n#endif\n\n  u32 availSlot = 0;\n  bool firstController = false;\n  if (x4_controller) {\n    x4_controller->Poll();\n    for (u32 i = 0; i < x4_controller->GetDeviceCount(); ++i) {\n      auto cont = x4_controller->GetGamepadData(i);\n      if (cont.DeviceIsPresent()) {\n        if (i == 0) {\n          firstController = true;\n        }\n        auto tmp = CFinalInput(i, dt, cont, xc_leftDiv, x10_rightDiv);\n        if (i == 0) {\n          m_lastInput = tmp;\n        }\n        queue.Push(MakeMsg::CreateUserInput(EArchMsgTarget::Game, tmp));\n        ++availSlot;\n      }\n\n      if (x8_connectedControllers[i] != cont.DeviceIsPresent()) {\n        queue.Push(MakeMsg::CreateControllerStatus(EArchMsgTarget::Game, i, cont.DeviceIsPresent()));\n        x8_connectedControllers[i] = cont.DeviceIsPresent();\n      }\n    }\n  }\n#if 0\n  if (firstController) {\n    queue.Push(MakeMsg::CreateUserInput(EArchMsgTarget::Game, CFinalInput(availSlot, dt, x0_osContext)));\n  } else {\n    queue.Push(MakeMsg::CreateUserInput(EArchMsgTarget::Game, CFinalInput(0, dt, x0_osContext)));\n  }\n#endif\n}\n} // namespace metaforce::WIP\n"
  },
  {
    "path": "Runtime/Input/CInputGenerator.cpp.old",
    "content": "#include \"Runtime/Input/CInputGenerator.hpp\"\n\n#include \"Runtime/CArchitectureMessage.hpp\"\n#include \"Runtime/CArchitectureQueue.hpp\"\n\n#include <magic_enum.hpp>\n\nnamespace metaforce {\nstatic logvisor::Module Log(\"CInputGenerator\");\n\nvoid CInputGenerator::Update(float dt, CArchitectureQueue& queue) {\n  if (m_firstFrame) {\n    m_firstFrame = false;\n    return;\n  }\n\n  const CFinalInput& kbInput = getFinalInput(0, dt);\n  queue.Push(MakeMsg::CreateUserInput(EArchMsgTarget::Game, kbInput));\n\n  /* Dolphin controllers next */\n  //  for (int i = 0; i < 4; ++i) {\n  //    bool connected;\n  //    EStatusChange change = m_dolphinCb.getStatusChange(i, connected);\n  //    if (change != EStatusChange::NoChange)\n  //      queue.Push(MakeMsg::CreateControllerStatus(EArchMsgTarget::Game, i, connected));\n  //    if (connected) {\n  //      CFinalInput input = m_dolphinCb.getFinalInput(i, dt, m_leftDiv, m_rightDiv);\n  //      if (i == 0) /* Merge KB input with first controller */\n  //      {\n  //        input |= kbInput;\n  //        kbUsed = true;\n  //      }\n  //      m_lastUpdate = input;\n  //      queue.Push(MakeMsg::CreateUserInput(EArchMsgTarget::Game, input));\n  //    }\n  //  }\n\n  //  /* Send straight keyboard input if no first controller present */\n  //  if (!kbUsed) {\n  //    m_lastUpdate = kbInput;\n  //  }\n}\n\nvoid CInputGenerator::controllerAdded(uint32_t which) noexcept {\n  s32 player = aurora::get_controller_player_index(which);\n  if (player < 0) {\n    player = 0;\n    aurora::set_controller_player_index(which, 0);\n  }\n\n  if (m_state[player].m_hasRumble && m_state[player].m_isGamecube) {\n    /* The GameCube controller can get stuck in a state where it's always rumbling if the game crashes\n     * (this can actually happen on hardware in certain cases)\n     * so lets toggle the motors to ensure they're off, this happens so quickly the player doesn't notice\n     */\n    aurora::controller_rumble(which, 1, 1);\n    aurora::controller_rumble(which, 0, 0);\n  }\n  m_state[player] =\n      SAuroraControllerState(which, aurora::is_controller_gamecube(which), aurora::controller_has_rumble(which));\n}\n\nvoid CInputGenerator::controllerRemoved(uint32_t which) noexcept {\n  auto it = std::find_if(m_state.begin(), m_state.end(), [&which](const auto& s) { return s.m_which == which; });\n  if (it == m_state.end()) {\n    return;\n  }\n\n  (*it) = SAuroraControllerState();\n}\n\nvoid CInputGenerator::controllerButton(uint32_t which, EControllerButton button, bool pressed) noexcept {\n  s32 player = aurora::get_controller_player_index(which);\n  if (player < 0) {\n    return;\n  }\n  m_state[player].m_btns.set(size_t(button), pressed);\n}\n\nvoid CInputGenerator::controllerAxis(uint32_t which, EControllerAxis axis, int16_t value) noexcept {\n  s32 player = aurora::get_controller_player_index(which);\n  if (player < 0) {\n    return;\n  }\n\n  switch (axis) {\n  case EControllerAxis::LeftY:\n  case EControllerAxis::RightY:\n    /* Value is inverted compared to what we expect on the Y axis */\n    value = int16_t(-(value + 1));\n    [[fallthrough]];\n  case EControllerAxis::LeftX:\n  case EControllerAxis::RightX:\n    value /= int16_t(256);\n    break;\n  case EControllerAxis::TriggerLeft:\n  case EControllerAxis::TriggerRight:\n    value /= int16_t(128);\n    break;\n  default:\n    break;\n  }\n\n  m_state[player].m_axes[size_t(axis)] = value;\n}\n\nvoid CInputGenerator::SetMotorState(EIOPort port, EMotorState state) {\n  if (m_state[size_t(port)].m_hasRumble && m_state[size_t(port)].m_isGamecube) {\n    if (state == EMotorState::Rumble) {\n      aurora::controller_rumble(m_state[size_t(port)].m_which, 1, 1);\n    } else if (state == EMotorState::Stop) {\n      aurora::controller_rumble(m_state[size_t(port)].m_which, 0, 1);\n    } else if (state == EMotorState::StopHard) {\n      aurora::controller_rumble(m_state[size_t(port)].m_which, 0, 0);\n    }\n  } // TODO: Figure out good intensity values for generic controllers with rumble, support HAPTIC?\n}\n\nconst CFinalInput& CInputGenerator::getFinalInput(unsigned int idx, float dt) {\n#if 0\n  auto input = CFinalInput(idx, dt, m_data, m_lastUpdate);\n  // Merge controller input with kb/m input\n  auto state = m_state[idx];\n  state.clamp();\n  input |= CFinalInput(idx, dt, state, m_lastUpdate, m_leftDiv, m_rightDiv);\n  m_lastUpdate = input;\n#endif\n  return m_lastUpdate;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Input/CInputGenerator.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/Input/IController.hpp\"\n#include \"Runtime/Input/CFinalInput.hpp\"\n\nnamespace metaforce {\nstruct COsContext {\n  bool GetOsKeyState(int key) { return false; }\n};\n\nclass CArchitectureQueue;\n\nclass CInputGenerator {\n  // COsContext& x0_context;\n  std::unique_ptr<IController> x4_controller;\n  std::array<bool, 4> x8_connectedControllers{};\n  float xc_leftDiv;\n  float x10_rightDiv;\n\n  CFinalInput m_lastInput;\n\npublic:\n  CInputGenerator(/*COsContext& context, */ float leftDiv, float rightDiv);\n\n  void Update(float dt, CArchitectureQueue& queue);\n\n  IController* GetController() const { return x4_controller.get(); }\n  CFinalInput GetLastInput() const { return m_lastInput; }\n};\n} // namespace metaforce"
  },
  {
    "path": "Runtime/Input/CInputGenerator.hpp.old",
    "content": "#pragma once\n\n#include <array>\n#include <atomic>\n#include <mutex>\n\n#include \"Runtime/Input/InputTypes.hpp\"\n#include \"Runtime/Input/CFinalInput.hpp\"\n#include \"Runtime/Input/CKeyboardMouseController.hpp\"\n\nnamespace metaforce {\nclass CArchitectureQueue;\n\nclass CInputGenerator /*: public boo::DeviceFinder*/ {\n  enum class EStatusChange { NoChange = 0, Connected = 1, Disconnected = 2 };\n\n  /* When the sticks are used as logical (digital) input,\n   * these thresholds determine the vector magnitude indicating\n   * the logical state */\n  float m_leftDiv;\n  float m_rightDiv;\n  CKeyboardMouseControllerData m_data;\n  std::array<SAuroraControllerState, 4> m_state;\n\n  CFinalInput m_lastUpdate;\n  const CFinalInput& getFinalInput(unsigned idx, float dt);\n\n  bool m_firstFrame = true;\n\npublic:\n  CInputGenerator(float leftDiv, float rightDiv)\n  : /*boo::DeviceFinder({dev_typeid(DolphinSmashAdapter)}),*/ m_leftDiv(leftDiv), m_rightDiv(rightDiv) {}\n\n//  ~CInputGenerator() override {\n//    if (smashAdapter) {\n//      smashAdapter->setCallback(nullptr);\n//      smashAdapter->closeDevice();\n//    }\n//  }\n\n  void controllerAdded(uint32_t which) noexcept;\n\n  void controllerRemoved(uint32_t which) noexcept;\n\n  void controllerButton(uint32_t which, EControllerButton button, bool pressed) noexcept;\n\n  void controllerAxis(uint32_t which, EControllerAxis axis, int16_t value) noexcept;\n\n\n  /* Keyboard and mouse events are delivered on the main game\n   * thread as part of the app's main event loop. The OS is responsible\n   * for buffering events in its own way, then boo flushes the buffer\n   * at the start of each frame, invoking these methods. No atomic locking\n   * is necessary, only absolute state tracking. */\n\n  void mouseDown(const SWindowCoord&, EMouseButton button, EModifierKey) {\n    m_data.m_mouseButtons[size_t(button)] = true;\n  }\n  void mouseUp(const SWindowCoord&, EMouseButton button, EModifierKey) {\n    m_data.m_mouseButtons[size_t(button)] = false;\n  }\n  void mouseMove(const SWindowCoord& coord) { m_data.m_mouseCoord = coord; }\n  void scroll(const SWindowCoord&, const SScrollDelta& scroll) { m_data.m_accumScroll += scroll; }\n\n  void charKeyDown(uint8_t charCode, aurora::ModifierKey, bool) {\n    charCode = tolower(charCode);\n    if (charCode > 255)\n      return;\n    m_data.m_charKeys[charCode] = true;\n  }\n  void charKeyUp(uint8_t charCode, aurora::ModifierKey mods) {\n    charCode = tolower(charCode);\n    if (charCode > 255)\n      return;\n    m_data.m_charKeys[charCode] = false;\n  }\n  void specialKeyDown(ESpecialKey key, aurora::ModifierKey, bool) { m_data.m_specialKeys[size_t(key)] = true; }\n  void specialKeyUp(ESpecialKey key, aurora::ModifierKey) { m_data.m_specialKeys[size_t(key)] = false; }\n  void modKeyDown(aurora::ModifierKey mod, bool) { m_data.m_modMask = m_data.m_modMask | mod; }\n  void modKeyUp(aurora::ModifierKey mod) { m_data.m_modMask = m_data.m_modMask & ~mod; }\n\n  void reset() { m_data.m_accumScroll.zeroOut(); }\n\n//  /* Input via the smash adapter is received asynchronously on a USB\n//   * report thread. This class atomically exchanges that data to the\n//   * game thread as needed */\n//  struct DolphinSmashAdapterCallback : boo::IDolphinSmashAdapterCallback {\n//    std::array<std::atomic<EStatusChange>, 4> m_statusChanges;\n//    std::array<bool, 4> m_connected{};\n//    std::array<boo::DolphinControllerState, 4> m_states;\n//    std::mutex m_stateLock;\n//    void controllerConnected(unsigned idx, boo::EDolphinControllerType) override {\n//      /* Controller thread */\n//      m_statusChanges[idx].store(EStatusChange::Connected);\n//    }\n//    void controllerDisconnected(unsigned idx) override {\n//      /* Controller thread */\n//      std::unique_lock lk{m_stateLock};\n//      m_statusChanges[idx].store(EStatusChange::Disconnected);\n//      m_states[idx].reset();\n//    }\n//    void controllerUpdate(unsigned idx, boo::EDolphinControllerType,\n//                          const boo::DolphinControllerState& state) override {\n//      /* Controller thread */\n//      std::unique_lock lk{m_stateLock};\n//      m_states[idx] = state;\n//    }\n//\n//    std::array<CFinalInput, 4> m_lastUpdates;\n//    const CFinalInput& getFinalInput(unsigned idx, float dt, float leftDiv, float rightDiv) {\n//      /* Game thread */\n//      std::unique_lock lk{m_stateLock};\n//      boo::DolphinControllerState state = m_states[idx];\n//      lk.unlock();\n//      state.clamp(); /* PADClamp equivalent */\n//      m_lastUpdates[idx] = CFinalInput(idx, dt, state, m_lastUpdates[idx], leftDiv, rightDiv);\n//      return m_lastUpdates[idx];\n//    }\n//    EStatusChange getStatusChange(unsigned idx, bool& connected) {\n//      /* Game thread */\n//      EStatusChange ch = m_statusChanges[idx].exchange(EStatusChange::NoChange);\n//      if (ch == EStatusChange::Connected)\n//        m_connected[idx] = true;\n//      else if (ch == EStatusChange::Disconnected)\n//        m_connected[idx] = false;\n//      connected = m_connected[idx];\n//      return ch;\n//    }\n//  } m_dolphinCb;\n\n//  /* Device connection/disconnection events are handled on a separate thread\n//   * using the relevant OS API. This thread blocks in a loop until an event is\n//   * received. Device pointers should only be manipulated by this thread using\n//   * the deviceConnected() and deviceDisconnected() callbacks. */\n//  std::shared_ptr<boo::DolphinSmashAdapter> smashAdapter;\n//  void deviceConnected(boo::DeviceToken& tok) override {\n//    /* Device listener thread */\n//    if (!smashAdapter) {\n//      auto dev = tok.openAndGetDevice();\n//      if (dev && dev->getTypeHash() == dev_typeid(DolphinSmashAdapter)) {\n//        smashAdapter = std::static_pointer_cast<boo::DolphinSmashAdapter>(tok.openAndGetDevice());\n//        smashAdapter->setCallback(&m_dolphinCb);\n//      }\n//    }\n//  }\n//  void deviceDisconnected(boo::DeviceToken&, boo::DeviceBase* device) override {\n//    if (smashAdapter.get() == device)\n//      smashAdapter.reset();\n//  }\n  void SetMotorState(EIOPort port, EMotorState state);\n  void ControlAllMotors(const std::array<EMotorState, 4>& states) {\n    for (u32 i = 0; i <= size_t(EIOPort::Player4); ++i ) {\n      SetMotorState(EIOPort(i), states[i]);\n    }\n  }\n\n  /* This is where the game thread enters */\n  void Update(float dt, CArchitectureQueue& queue);\n  CFinalInput GetLastInput() const { return m_lastUpdate; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Input/CKeyboardMouseController.hpp",
    "content": "#pragma once\n\n#include <array>\n\n#include <aurora/aurora.h>\n#include \"GCNTypes.hpp\"\n\nnamespace metaforce {\n// TODO: copied from boo; should be rewritten\nenum class EControllerButton : uint8_t {\n  A,\n  B,\n  X,\n  Y,\n  Back,\n  Guide,\n  Start,\n  LeftStick,\n  RightStick,\n  LeftShoulder,\n  RightShoulder,\n  DPadUp,\n  DPadDown,\n  DPadLeft,\n  DPadRight,\n  Other,\n  MAX,\n};\nenum class EControllerAxis : uint8_t {\n  LeftX,\n  LeftY,\n  RightX,\n  RightY,\n  TriggerLeft,\n  TriggerRight,\n  MAX,\n};\nenum class EMouseButton { None = 0, Primary = 1, Secondary = 2, Middle = 3, Aux1 = 4, Aux2 = 5 };\nenum class ESpecialKey : uint8_t {\n  None = 0,\n  F1 = 1,\n  F2 = 2,\n  F3 = 3,\n  F4 = 4,\n  F5 = 5,\n  F6 = 6,\n  F7 = 7,\n  F8 = 8,\n  F9 = 9,\n  F10 = 10,\n  F11 = 11,\n  F12 = 12,\n  F13 = 13,\n  F14 = 14,\n  F15 = 15,\n  F16 = 16,\n  F17 = 17,\n  F18 = 18,\n  F19 = 19,\n  F20 = 20,\n  F21 = 21,\n  F22 = 22,\n  F23 = 23,\n  F24 = 24,\n  Esc = 25,\n  Enter = 26,\n  Backspace = 27,\n  Insert = 28,\n  Delete = 29,\n  Home = 30,\n  End = 31,\n  PgUp = 32,\n  PgDown = 33,\n  Left = 34,\n  Right = 35,\n  Up = 36,\n  Down = 37,\n  Tab = 38,\n  PrintScreen = 39,\n  ScrollLock = 40,\n  Pause = 41,\n  NumLockClear = 42,\n  KpDivide = 43,\n  KpMultiply = 44,\n  KpMinus = 45,\n  KpPlus = 46,\n  KpEnter = 47,\n  KpNum0 = 48,\n  KpNum1 = 49,\n  KpNum2 = 50,\n  KpNum3 = 51,\n  KpNum4 = 52,\n  KpNum5 = 53,\n  KpNum6 = 54,\n  KpNum7 = 55,\n  KpNum8 = 56,\n  KpNum9 = 57,\n  KpPercent = 58,\n  KpPeriod = 59,\n  KpComma = 60,\n  KpEquals = 61,\n  Application = 62,\n  Power = 63,\n  Execute = 64,\n  Help = 65,\n  Menu = 66,\n  Select = 67,\n  Stop = 68,\n  Again = 69,\n  Undo = 70,\n  Cut = 71,\n  Paste = 72,\n  Find = 73,\n  VolumeUp = 74,\n  VolumeDown = 75,\n  MAX,\n};\nenum class EModifierKey {\n  None = 0,\n  Ctrl = 1 << 0,\n  Alt = 1 << 2,\n  Shift = 1 << 3,\n  Command = 1 << 4,\n  CtrlCommand = EModifierKey::Ctrl | EModifierKey::Command\n};\nENABLE_BITWISE_ENUM(EModifierKey)\nstruct SWindowCoord {\n  std::array<int, 2> pixel;\n  std::array<int, 2> virtualPixel;\n  std::array<float, 2> norm;\n};\nstruct SScrollDelta {\n  std::array<double, 2> delta{};\n  bool isFine = false;        /* Use system-scale fine-scroll (for scrollable-trackpads) */\n  bool isAccelerated = false; /* System performs acceleration computation */\n\n  constexpr SScrollDelta operator+(const SScrollDelta& other) const noexcept {\n    SScrollDelta ret;\n    ret.delta[0] = delta[0] + other.delta[0];\n    ret.delta[1] = delta[1] + other.delta[1];\n    ret.isFine = isFine || other.isFine;\n    ret.isAccelerated = isAccelerated || other.isAccelerated;\n    return ret;\n  }\n  constexpr SScrollDelta operator-(const SScrollDelta& other) const noexcept {\n    SScrollDelta ret;\n    ret.delta[0] = delta[0] - other.delta[0];\n    ret.delta[1] = delta[1] - other.delta[1];\n    ret.isFine = isFine || other.isFine;\n    ret.isAccelerated = isAccelerated || other.isAccelerated;\n    return ret;\n  }\n  constexpr SScrollDelta& operator+=(const SScrollDelta& other) noexcept {\n    delta[0] += other.delta[0];\n    delta[1] += other.delta[1];\n    isFine |= other.isFine;\n    isAccelerated |= other.isAccelerated;\n    return *this;\n  }\n  constexpr void zeroOut() noexcept { delta = {}; }\n  constexpr bool isZero() const noexcept { return delta[0] == 0.0 && delta[1] == 0.0; }\n};\n\nstruct CKeyboardMouseControllerData {\n  std::array<bool, 256> m_charKeys{};\n  std::array<bool, static_cast<size_t>(ESpecialKey::MAX)> m_specialKeys{};\n  std::array<bool, 6> m_mouseButtons{};\n  EModifierKey m_modMask = EModifierKey::None;\n  SWindowCoord m_mouseCoord;\n  SScrollDelta m_accumScroll;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Input/CMakeLists.txt",
    "content": "set(INPUT_SOURCES\n        IController.hpp DolphinIController.cpp\n        CControllerAxis.hpp\n        CControllerButton.hpp\n        CControllerGamepadData.hpp CControllerGamepadData.cpp\n        CDolphinController.hpp CDolphinController.cpp\n        CKeyboardMouseController.hpp\n        ControlMapper.hpp ControlMapper.cpp\n        CInputGenerator.hpp CInputGenerator.cpp\n        CFinalInput.hpp CFinalInput.cpp\n        CRumbleManager.hpp CRumbleManager.cpp\n        CRumbleGenerator.hpp CRumbleGenerator.cpp\n        CRumbleVoice.hpp CRumbleVoice.cpp\n        RumbleFxTable.hpp RumbleFxTable.cpp)\n\nruntime_add_list(Input INPUT_SOURCES)\n"
  },
  {
    "path": "Runtime/Input/CRumbleGenerator.cpp",
    "content": "#include \"Runtime/Input/CRumbleGenerator.hpp\"\n\n#include \"Runtime/GameGlobalObjects.hpp\"\n\nnamespace metaforce {\n\nCRumbleGenerator::CRumbleGenerator() { HardStopAll(); }\n\nCRumbleGenerator::~CRumbleGenerator() { HardStopAll(); }\n\n#define PWM_MONITOR 0\n#if PWM_MONITOR\nstatic bool b_tp = false;\nstatic std::chrono::steady_clock::time_point s_tp;\n#endif\n\nvoid CRumbleGenerator::Update(float dt) {\n#if PWM_MONITOR\n  std::chrono::milliseconds::rep ms = 0;\n  std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();\n  if (!b_tp) {\n    b_tp = true;\n    s_tp = now;\n  } else {\n    ms = std::chrono::duration_cast<std::chrono::milliseconds>(now - s_tp).count();\n  }\n#endif\n\n  if (!xf0_24_disabled) {\n    bool updated = false;\n    for (size_t i = 0; i < x0_voices.size(); ++i) {\n      const float intensity = x0_voices[i].GetIntensity();\n      if (!x0_voices[i].Update(dt) || intensity <= 0.f) {\n        xc0_periodTime[i] = 0.f;\n        xd0_onTime[i] = 0.f;\n        if (xe0_commandArray[i] != EMotorState::Stop) {\n#if PWM_MONITOR\n          s_tp = now;\n          spdlog::debug(\"{}ms ON\\n\", ms);\n#endif\n          xe0_commandArray[i] = EMotorState::Stop;\n          updated = true;\n        }\n      } else {\n        xc0_periodTime[i] += dt;\n        if (xc0_periodTime[i] >= 1.f / (30.f * intensity)) {\n          xc0_periodTime[i] = 0.f;\n          if (xe0_commandArray[i] != EMotorState::Rumble) {\n#if PWM_MONITOR\n            s_tp = now;\n            spdlog::debug(\"{}ms Off\\n\", ms);\n#endif\n            xe0_commandArray[i] = EMotorState::Rumble;\n            updated = true;\n          }\n        } else {\n          xd0_onTime[i] += dt;\n          if (xd0_onTime[i] >= (1.f / 30.f)) {\n            xd0_onTime[i] = 0.f;\n            if (xe0_commandArray[i] != EMotorState::Stop) {\n#if PWM_MONITOR\n              s_tp = now;\n              spdlog::debug(\"{}ms ON\\n\", ms);\n#endif\n              xe0_commandArray[i] = EMotorState::Stop;\n              updated = true;\n            }\n          }\n        }\n      }\n    }\n    if (updated) {\n      PADControlAllMotors(reinterpret_cast<const u32*>(xe0_commandArray.data()));\n    }\n  }\n}\n\nvoid CRumbleGenerator::HardStopAll() {\n  static constexpr std::array HardStopCommands{\n      (u32)EMotorState::StopHard,\n      (u32)EMotorState::StopHard,\n      (u32)EMotorState::StopHard,\n      (u32)EMotorState::StopHard,\n  };\n\n  xc0_periodTime.fill(0.0f);\n  xd0_onTime.fill(0.0f);\n  xe0_commandArray.fill(EMotorState::Stop);\n  for (auto& voice : x0_voices) {\n    voice.HardReset();\n  }\n\n  // TODO(phil): switch this to g_InputGenerator->GetContoller()->SetMotorState?\n  PADControlAllMotors(static_cast<const u32*>(HardStopCommands.data()));\n}\n\ns16 CRumbleGenerator::Rumble(const SAdsrData& adsr, float gain, ERumblePriority prio, EIOPort port) {\n  CRumbleVoice& vox = x0_voices[size_t(port)];\n  const s16 freeChan = vox.GetFreeChannel();\n  if (prio >= vox.GetPriority(freeChan)) {\n    xc0_periodTime[size_t(port)] = 0.f;\n    return vox.Activate(adsr, freeChan, gain, prio);\n  }\n  return -1;\n}\n\nvoid CRumbleGenerator::Stop(s16 id, EIOPort port) {\n  CRumbleVoice& vox = x0_voices[size_t(port)];\n  vox.Deactivate(id, false);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Input/CRumbleGenerator.hpp",
    "content": "#pragma once\n\n#include <array>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/Input/CInputGenerator.hpp\"\n#include \"Runtime/Input/CRumbleVoice.hpp\"\n\nnamespace metaforce {\nclass CRumbleGenerator {\n  std::array<CRumbleVoice, 4> x0_voices;\n  std::array<float, 4> xc0_periodTime;\n  std::array<float, 4> xd0_onTime;\n  std::array<EMotorState, 4> xe0_commandArray;\n  bool xf0_24_disabled : 1 = false;\n\npublic:\n  CRumbleGenerator();\n  ~CRumbleGenerator();\n  void Update(float dt);\n  void HardStopAll();\n  s16 Rumble(const SAdsrData& adsr, float, ERumblePriority prio, EIOPort port);\n  void Stop(s16 id, EIOPort port);\n  bool IsDisabled() const { return xf0_24_disabled; }\n  void SetDisabled(bool disabled) { xf0_24_disabled = disabled; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Input/CRumbleManager.cpp",
    "content": "#include \"Runtime/Input/CRumbleManager.hpp\"\n\n#include \"Runtime/CGameState.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Input/RumbleFxTable.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n\nnamespace metaforce {\n\ns16 CRumbleManager::Rumble(CStateManager& mgr, const zeus::CVector3f& pos, ERumbleFxId fx, float dist,\n                           ERumblePriority priority) {\n  if (zeus::close_enough(dist, 0.f))\n    return -1;\n  zeus::CVector3f delta = mgr.GetPlayer().GetTranslation() - pos;\n  if (delta.magSquared() < dist * dist)\n    return Rumble(mgr, fx, 1.f - delta.magnitude() / dist, priority);\n  return -1;\n}\n\ns16 CRumbleManager::Rumble(CStateManager& mgr, ERumbleFxId fx, float gain, ERumblePriority priority) {\n  if (g_GameState->GameOptions().GetIsRumbleEnabled())\n    return x0_rumbleGenerator.Rumble(RumbleFxTable[size_t(fx)], gain, priority, EIOPort::Player1);\n  return -1;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Input/CRumbleManager.hpp",
    "content": "#pragma once\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/Input/CRumbleGenerator.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CStateManager;\nclass CRumbleManager {\n  CRumbleGenerator x0_rumbleGenerator;\n\npublic:\n  bool IsDisabled() const { return x0_rumbleGenerator.IsDisabled(); }\n  void SetDisabled(bool disabled) { x0_rumbleGenerator.SetDisabled(disabled); }\n  void Update(float dt) { x0_rumbleGenerator.Update(dt); }\n  void StopRumble(s16 id) {\n    if (id == -1)\n      return;\n    x0_rumbleGenerator.Stop(id, EIOPort::Player1);\n  }\n  void HardStopAll() { x0_rumbleGenerator.HardStopAll(); }\n  s16 Rumble(CStateManager& mgr, const zeus::CVector3f& pos, ERumbleFxId fx, float dist, ERumblePriority priority);\n  s16 Rumble(CStateManager& mgr, ERumbleFxId fx, float gain, ERumblePriority priority);\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Input/CRumbleVoice.cpp",
    "content": "#include \"Runtime/Input/CRumbleVoice.hpp\"\n\nnamespace metaforce {\n\nCRumbleVoice::CRumbleVoice() : x0_datas(4), x10_deltas(4, SAdsrDelta::Stopped()) { x20_handleIds.resize(4); }\n\ns16 CRumbleVoice::CreateRumbleHandle(s16 idx) {\n  ++x2e_lastId;\n  if (x2e_lastId == 0)\n    x2e_lastId = 1;\n  x20_handleIds[idx] = x2e_lastId;\n  return (x2e_lastId << 8) | idx;\n}\n\nbool CRumbleVoice::OwnsSustained(s16 handle) const {\n  int idx = handle & 0xf;\n  if (idx < 4)\n    return x20_handleIds[idx] == ((handle >> 8) & 0xff);\n  return false;\n}\n\ns16 CRumbleVoice::GetFreeChannel() const {\n  for (s16 i = 0; i < 4; ++i) {\n    if (!((1 << i) & x2c_usedChannels)) {\n      return i;\n    }\n  }\n  return 0;\n}\n\nfloat CRumbleVoice::GetIntensity() const {\n  return std::min(2.f, std::max({x10_deltas[0].x0_curIntensity, x10_deltas[1].x0_curIntensity,\n                                 x10_deltas[2].x0_curIntensity, x10_deltas[3].x0_curIntensity}));\n}\n\nbool CRumbleVoice::UpdateChannel(SAdsrDelta& delta, const SAdsrData& data, float dt) {\n  switch (delta.x20_phase) {\n  case SAdsrDelta::EPhase::PrePulse:\n    if (delta.x4_attackTime < (1.f / 30.f)) {\n      delta.x4_attackTime += dt;\n    } else {\n      delta.x20_phase = SAdsrDelta::EPhase::Attack;\n      delta.x0_curIntensity = 0.f;\n      delta.x4_attackTime = 0.f;\n    }\n    break;\n  case SAdsrDelta::EPhase::Attack:\n    if (delta.x4_attackTime < data.x8_attackDur) {\n      float t = delta.x4_attackTime / data.x8_attackDur;\n      delta.x0_curIntensity = t * delta.x14_attackIntensity;\n      delta.x4_attackTime += dt;\n    } else {\n      delta.x0_curIntensity = delta.x14_attackIntensity;\n      delta.x20_phase = SAdsrDelta::EPhase::Decay;\n    }\n    break;\n  case SAdsrDelta::EPhase::Decay:\n    if (data.x18_24_hasSustain) {\n      if (delta.x8_decayTime < data.xc_decayDur) {\n        float t = delta.x8_decayTime / data.xc_decayDur;\n        delta.x0_curIntensity = (1.f - t) * delta.x14_attackIntensity + t * delta.x18_sustainIntensity;\n        delta.x8_decayTime += dt;\n      } else {\n        delta.x0_curIntensity = delta.x18_sustainIntensity;\n        delta.x20_phase = SAdsrDelta::EPhase::Sustain;\n      }\n    } else {\n      if (delta.x8_decayTime < data.xc_decayDur) {\n        float t = delta.x8_decayTime / data.xc_decayDur;\n        delta.x0_curIntensity = (1.f - t) * delta.x14_attackIntensity;\n        delta.x8_decayTime += dt;\n      } else {\n        delta.x0_curIntensity = 0.f;\n        delta.x20_phase = SAdsrDelta::EPhase::Stop;\n      }\n      if (delta.x20_phase != SAdsrDelta::EPhase::Decay) {\n        delta.x20_phase = SAdsrDelta::EPhase::Stop;\n        return true;\n      }\n    }\n    break;\n  case SAdsrDelta::EPhase::Release: {\n    float a = data.x18_24_hasSustain ? delta.x18_sustainIntensity : 0.f;\n    if (delta.xc_releaseTime < data.x14_releaseDur) {\n      float t = delta.xc_releaseTime / data.x14_releaseDur;\n      delta.x0_curIntensity = (1.f - t) * a;\n      delta.xc_releaseTime += dt;\n    } else {\n      delta.x0_curIntensity = 0.f;\n      delta.x20_phase = SAdsrDelta::EPhase::Stop;\n    }\n    if (delta.x20_phase != SAdsrDelta::EPhase::Release) {\n      delta.x20_phase = SAdsrDelta::EPhase::Stop;\n      return true;\n    }\n  } break;\n  default:\n    break;\n  }\n\n  if (data.x18_25_autoRelease) {\n    if (delta.x10_autoReleaseTime < data.x4_autoReleaseDur)\n      delta.x10_autoReleaseTime += dt;\n    else if (delta.x20_phase == SAdsrDelta::EPhase::Sustain)\n      delta.x20_phase = SAdsrDelta::EPhase::Release;\n  }\n\n  return false;\n}\n\nbool CRumbleVoice::Update(float dt) {\n  if (x2c_usedChannels != 0) {\n    for (s16 i = 0; i < 4; ++i) {\n      if ((1 << i) & x2c_usedChannels) {\n        if (UpdateChannel(x10_deltas[i], x0_datas[i], dt)) {\n          x2c_usedChannels &= ~(1 << i);\n          x10_deltas[i] = SAdsrDelta::Stopped();\n        }\n      }\n    }\n    return true;\n  }\n  return false;\n}\n\nvoid CRumbleVoice::HardReset() {\n  x2c_usedChannels = 0;\n  for (s16 i = 0; i < 4; ++i) {\n    x10_deltas[i] = SAdsrDelta::Stopped();\n    x20_handleIds[i] = 0;\n  }\n}\n\ns16 CRumbleVoice::Activate(const SAdsrData& data, s16 idx, float gain, ERumblePriority prio) {\n  if (gain > 0.f) {\n    x0_datas[idx] = data;\n    x10_deltas[idx] = SAdsrDelta::Start(prio, x2c_usedChannels == 0);\n    x10_deltas[idx].x14_attackIntensity = gain * x0_datas[idx].x0_attackGain;\n    x10_deltas[idx].x18_sustainIntensity = gain * x0_datas[idx].x10_sustainGain;\n    x2c_usedChannels |= 1 << idx;\n    if (data.x18_24_hasSustain)\n      return CreateRumbleHandle(idx);\n  }\n  return -1;\n}\n\nvoid CRumbleVoice::Deactivate(s16 id, bool b1) {\n  if (id == -1)\n    return;\n  if (OwnsSustained(id)) {\n    int handle = (id & 0xf);\n    if (x2c_usedChannels & (1 << handle))\n      x10_deltas[handle].x20_phase = SAdsrDelta::EPhase::Release;\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Input/CRumbleVoice.hpp",
    "content": "#pragma once\n\n#include <vector>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n\nnamespace metaforce {\nenum class ERumbleFxId {\n  Zero = 0,\n  One = 1,\n  CameraShake = 6,\n  EscapeSequenceShake = 7,\n  PlayerBump = 11,\n  PlayerGunCharge = 12,\n  PlayerMissileFire = 13,\n  PlayerGrappleFire = 14,\n  PlayerLand = 15,\n  PlayerGrappleSwoosh = 17,\n  IntroBossProjectile = 19,\n  Twenty = 20,\n  TwentyOne = 21,\n  TwentyTwo = 22,\n  TwentyThree = 23\n};\nenum class ERumblePriority { None = 0, One = 1, Two = 2, Three = 3 };\n\nstruct SAdsrData {\n  float x0_attackGain = 0.f;\n  float x4_autoReleaseDur = 0.f;\n  float x8_attackDur = 0.f;\n  float xc_decayDur = 0.f;\n  float x10_sustainGain = 0.f;\n  float x14_releaseDur = 0.f;\n  bool x18_24_hasSustain : 1 = false;\n  bool x18_25_autoRelease : 1 = false;\n\n  constexpr SAdsrData() noexcept = default;\n  constexpr SAdsrData(float attackGain, float autoReleaseDur, float attackDur, float decayDur, float sustainGain,\n                      float releaseDur, bool hasSustain, bool autoRelease) noexcept\n  : x0_attackGain(attackGain)\n  , x4_autoReleaseDur(autoReleaseDur)\n  , x8_attackDur(attackDur)\n  , xc_decayDur(decayDur)\n  , x10_sustainGain(sustainGain)\n  , x14_releaseDur(releaseDur)\n  , x18_24_hasSustain(hasSustain)\n  , x18_25_autoRelease(autoRelease) {}\n};\n\nstruct SAdsrDelta {\n  enum class EPhase { Stop, PrePulse, Attack, Decay, Sustain, Release };\n\n  float x0_curIntensity = 0.f;\n  float x4_attackTime = 0.f;\n  float x8_decayTime = 0.f;\n  float xc_releaseTime = 0.f;\n  float x10_autoReleaseTime = 0.f;\n  float x14_attackIntensity = 0.f;\n  float x18_sustainIntensity = 0.f;\n  ERumblePriority x1c_priority;\n  EPhase x20_phase;\n\n  constexpr SAdsrDelta(EPhase phase, ERumblePriority priority) noexcept\n  : x0_curIntensity(phase == EPhase::PrePulse ? 2.f : 0.f), x1c_priority(priority), x20_phase(phase) {}\n  constexpr SAdsrDelta(EPhase phase) noexcept : x1c_priority(ERumblePriority::None), x20_phase(phase) {}\n\n  static constexpr SAdsrDelta Stopped() noexcept { return SAdsrDelta(EPhase::Stop); }\n  static constexpr SAdsrDelta Start(ERumblePriority priority, bool prePulse) noexcept {\n    return SAdsrDelta(prePulse ? EPhase::PrePulse : EPhase::Attack, priority);\n  }\n};\n\nclass CRumbleVoice {\n  std::vector<SAdsrData> x0_datas;\n  std::vector<SAdsrDelta> x10_deltas;\n  rstl::reserved_vector<s16, 4> x20_handleIds;\n  s16 x2c_usedChannels = 0;\n  u8 x2e_lastId = 0;\n  bool UpdateChannel(SAdsrDelta& delta, const SAdsrData& data, float dt);\n\npublic:\n  CRumbleVoice();\n  s16 CreateRumbleHandle(s16 idx);\n  bool OwnsSustained(s16 id) const;\n  s16 GetFreeChannel() const;\n  float GetIntensity() const;\n  bool Update(float dt);\n  void HardReset();\n  s16 Activate(const SAdsrData& data, s16 idx, float gain, ERumblePriority prio);\n  void Deactivate(s16 id, bool b1);\n  ERumblePriority GetPriority(s16 idx) const { return x10_deltas[idx].x1c_priority; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Input/ControlMapper.cpp",
    "content": "#include \"Runtime/Input/ControlMapper.hpp\"\n\n#include <array>\n\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Input/CFinalInput.hpp\"\n\nnamespace metaforce {\nnamespace {\nconstexpr std::array skCommandDescs{\n    \"Forward\",           \"Backward\",        \"Turn Left\",\n    \"Turn Right\",        \"Strafe Left\",     \"Strafe Right\",\n    \"Look Left\",         \"Look Right\",      \"Look Up\",\n    \"Look Down\",         \"Jump/Boost\",      \"Fire/Bomb\",\n    \"Missile/PowerBomb\", \"Morph\",           \"Aim Up\",\n    \"Aim Down\",          \"Cycle Beam Up\",   \"Cycle Beam Down\",\n    \"Cycle Item\",        \"Power Beam\",      \"Ice Beam\",\n    \"Wave Beam\",         \"Plasma Beam\",     \"Toggle Holster\",\n    \"Orbit Close\",       \"Orbit Far\",       \"Orbit Object\",\n    \"Orbit Select\",      \"Orbit Confirm\",   \"Orbit Left\",\n    \"Orbit Right\",       \"Orbit Up\",        \"Orbit Down\",\n    \"Look Hold1\",        \"Look Hold2\",      \"Look Zoom In\",\n    \"Look Zoom Out\",     \"Aim Hold\",        \"Map Circle Up\",\n    \"Map Circle Down\",   \"Map Circle Left\", \"Map Circle Right\",\n    \"Map Move Forward\",  \"Map Move Back\",   \"Map Move Left\",\n    \"Map Move Right\",    \"Map Zoom In\",     \"Map Zoom Out\",\n    \"SpiderBall\",        \"Chase Camera\",    \"XRay Visor\",\n    \"Thermo Visor\",      \"Enviro Visor\",    \"No Visor\",\n    \"Visor Menu\",        \"Visor Up\",        \"Visor Down\",\n    \"UNKNOWN\",           \"UNKNOWN\",         \"Use Shield\",\n    \"Scan Item\",         \"UNKNOWN\",         \"UNKNOWN\",\n    \"UNKNOWN\",           \"UNKNOWN\",         \"Previous Pause Screen\",\n    \"Next Pause Screen\", \"UNKNOWN\",         \"None\",\n};\n\nconstexpr std::array skFunctionDescs{\n    \"None\",\n    \"Left Stick Up\",\n    \"Left Stick Down\",\n    \"Left Stick Left\",\n    \"Left Stick Right\",\n    \"Right Stick Up\",\n    \"Right Stick Down\",\n    \"Right Stick Left\",\n    \"Right Stick Right\",\n    \"Left Trigger\",\n    \"Right Trigger\",\n    \"D-Pad Up   \",\n    \"D-Pad Down \",\n    \"D-Pad Left \",\n    \"D-Pad Right\",\n    \"A Button\",\n    \"B Button\",\n    \"X Button\",\n    \"Y Button\",\n    \"Z Button\",\n    \"Left Trigger Press\",\n    \"Right Trigger Press\",\n    \"Start\",\n};\n\nusing BoolReturnFn = bool (CFinalInput::*)() const;\nusing FloatReturnFn = float (CFinalInput::*)() const;\n\nconstexpr std::array<BoolReturnFn, 24> skPressFuncs{\n    nullptr,\n    &CFinalInput::PLAUp,\n    &CFinalInput::PLADown,\n    &CFinalInput::PLALeft,\n    &CFinalInput::PLARight,\n    &CFinalInput::PRAUp,\n    &CFinalInput::PRADown,\n    &CFinalInput::PRALeft,\n    &CFinalInput::PRARight,\n    &CFinalInput::PLTrigger,\n    &CFinalInput::PRTrigger,\n    &CFinalInput::PDPUp,\n    &CFinalInput::PDPDown,\n    &CFinalInput::PDPLeft,\n    &CFinalInput::PDPRight,\n    &CFinalInput::PA,\n    &CFinalInput::PB,\n    &CFinalInput::PX,\n    &CFinalInput::PY,\n    &CFinalInput::PZ,\n    &CFinalInput::PL,\n    &CFinalInput::PR,\n    &CFinalInput::PStart,\n    nullptr,\n};\n\nconstexpr std::array<BoolReturnFn, 24> skDigitalFuncs{\n    nullptr,\n    &CFinalInput::DLAUp,\n    &CFinalInput::DLADown,\n    &CFinalInput::DLALeft,\n    &CFinalInput::DLARight,\n    &CFinalInput::DRAUp,\n    &CFinalInput::DRADown,\n    &CFinalInput::DRALeft,\n    &CFinalInput::DRARight,\n    &CFinalInput::DLTrigger,\n    &CFinalInput::DRTrigger,\n    &CFinalInput::DDPUp,\n    &CFinalInput::DDPDown,\n    &CFinalInput::DDPLeft,\n    &CFinalInput::DDPRight,\n    &CFinalInput::DA,\n    &CFinalInput::DB,\n    &CFinalInput::DX,\n    &CFinalInput::DY,\n    &CFinalInput::DZ,\n    &CFinalInput::DL,\n    &CFinalInput::DR,\n    &CFinalInput::DStart,\n    nullptr,\n};\n\nconstexpr std::array<FloatReturnFn, 24> skAnalogFuncs{\n    nullptr,\n    &CFinalInput::ALAUp,\n    &CFinalInput::ALADown,\n    &CFinalInput::ALALeft,\n    &CFinalInput::ALARight,\n    &CFinalInput::ARAUp,\n    &CFinalInput::ARADown,\n    &CFinalInput::ARALeft,\n    &CFinalInput::ARARight,\n    &CFinalInput::ALTrigger,\n    &CFinalInput::ARTrigger,\n    &CFinalInput::ADPUp,\n    &CFinalInput::ADPDown,\n    &CFinalInput::ADPLeft,\n    &CFinalInput::ADPRight,\n    &CFinalInput::AA,\n    &CFinalInput::AB,\n    &CFinalInput::AX,\n    &CFinalInput::AY,\n    &CFinalInput::AZ,\n    &CFinalInput::AL,\n    &CFinalInput::AR,\n    &CFinalInput::AStart,\n    nullptr,\n};\n\nconstexpr std::array<ControlMapper::EKBMFunctionList, 70> skKBMMapping{\n    ControlMapper::EKBMFunctionList::KeyPress + 'w',                       // Forward,\n    ControlMapper::EKBMFunctionList::KeyPress + 's',                       // Backward,\n    ControlMapper::EKBMFunctionList::KeyPress + 'a',                       // TurnLeft,\n    ControlMapper::EKBMFunctionList::KeyPress + 'd',                       // TurnRight,\n    ControlMapper::EKBMFunctionList::KeyPress + 'a',                       // StrafeLeft,\n    ControlMapper::EKBMFunctionList::KeyPress + 'd',                       // StrafeRight,\n    ControlMapper::EKBMFunctionList::KeyPress + 'a',                       // LookLeft,\n    ControlMapper::EKBMFunctionList::KeyPress + 'd',                       // LookRight,\n    ControlMapper::EKBMFunctionList::KeyPress + 's',                       // LookUp,\n    ControlMapper::EKBMFunctionList::KeyPress + 'w',                       // LookDown,\n    ControlMapper::EKBMFunctionList::KeyPress + ' ',                       // JumpOrBoost = 10,\n    ControlMapper::EKBMFunctionList::MousePress + EMouseButton::Primary,   // FireOrBomb = 11,\n    ControlMapper::EKBMFunctionList::MousePress + EMouseButton::Secondary, // MissileOrPowerBomb = 12,\n    ControlMapper::EKBMFunctionList::KeyPress + 'c',                       // Morph,\n    ControlMapper::EKBMFunctionList::None,                                 // AimUp,\n    ControlMapper::EKBMFunctionList::None,                                 // AimDown,\n    ControlMapper::EKBMFunctionList::None,                                 // CycleBeamUp,\n    ControlMapper::EKBMFunctionList::None,                                 // CycleBeamDown,\n    ControlMapper::EKBMFunctionList::None,                                 // CycleItem,\n    ControlMapper::EKBMFunctionList::KeyPress + '1',                       // PowerBeam,\n    ControlMapper::EKBMFunctionList::KeyPress + '3',                       // IceBeam,\n    ControlMapper::EKBMFunctionList::KeyPress + '2',                       // WaveBeam,\n    ControlMapper::EKBMFunctionList::KeyPress + '4',                       // PlasmaBeam,\n    ControlMapper::EKBMFunctionList::None,                                 // ToggleHolster = 23,\n    ControlMapper::EKBMFunctionList::None,                                 // OrbitClose,\n    ControlMapper::EKBMFunctionList::KeyPress + 'q',                       // OrbitFar,\n    ControlMapper::EKBMFunctionList::KeyPress + 'q',                       // OrbitObject,\n    ControlMapper::EKBMFunctionList::None,                                 // OrbitSelect,\n    ControlMapper::EKBMFunctionList::None,                                 // OrbitConfirm,\n    ControlMapper::EKBMFunctionList::KeyPress + 'a',                       // OrbitLeft,\n    ControlMapper::EKBMFunctionList::KeyPress + 'd',                       // OrbitRight,\n    ControlMapper::EKBMFunctionList::KeyPress + 'w',                       // OrbitUp,\n    ControlMapper::EKBMFunctionList::KeyPress + 's',                       // OrbitDown,\n    ControlMapper::EKBMFunctionList::KeyPress + 'e',                       // LookHold1,\n    ControlMapper::EKBMFunctionList::None,                                 // LookHold2,\n    ControlMapper::EKBMFunctionList::None,                                 // LookZoomIn,\n    ControlMapper::EKBMFunctionList::None,                                 // LookZoomOut,\n    ControlMapper::EKBMFunctionList::None,                                 // AimHold,\n    ControlMapper::EKBMFunctionList::KeyPress + 's',                       // MapCircleUp,\n    ControlMapper::EKBMFunctionList::KeyPress + 'w',                       // MapCircleDown,\n    ControlMapper::EKBMFunctionList::KeyPress + 'a',                       // MapCircleLeft,\n    ControlMapper::EKBMFunctionList::KeyPress + 'd',                       // MapCircleRight,\n    ControlMapper::EKBMFunctionList::SpecialKeyPress + ESpecialKey::Up,    // MapMoveForward,\n    ControlMapper::EKBMFunctionList::SpecialKeyPress + ESpecialKey::Down,  // MapMoveBack,\n    ControlMapper::EKBMFunctionList::SpecialKeyPress + ESpecialKey::Left,  // MapMoveLeft,\n    ControlMapper::EKBMFunctionList::SpecialKeyPress + ESpecialKey::Right, // MapMoveRight,\n    ControlMapper::EKBMFunctionList::KeyPress + 'e',                       // MapZoomIn,\n    ControlMapper::EKBMFunctionList::KeyPress + 'q',                       // MapZoomOut,\n    ControlMapper::EKBMFunctionList::KeyPress + 'e',                       // SpiderBall,\n    ControlMapper::EKBMFunctionList::KeyPress + 'q',                       // ChaseCamera,\n    ControlMapper::EKBMFunctionList::SpecialKeyPress + ESpecialKey::Right, // XrayVisor = 50,\n    ControlMapper::EKBMFunctionList::SpecialKeyPress + ESpecialKey::Down,  // ThermoVisor = 51,\n    ControlMapper::EKBMFunctionList::SpecialKeyPress + ESpecialKey::Left,  // InviroVisor = 52,\n    ControlMapper::EKBMFunctionList::SpecialKeyPress + ESpecialKey::Up,    // NoVisor = 53,\n    ControlMapper::EKBMFunctionList::None,                                 // VisorMenu,\n    ControlMapper::EKBMFunctionList::None,                                 // VisorUp,\n    ControlMapper::EKBMFunctionList::None,                                 // VisorDown,\n    ControlMapper::EKBMFunctionList::KeyPress + 'e',                       // ShowCrosshairs,\n    ControlMapper::EKBMFunctionList::None,                                 // UNKNOWN\n    ControlMapper::EKBMFunctionList::None,                                 // UseSheild = 0x3B,\n    ControlMapper::EKBMFunctionList::KeyPress + 'q',                       // ScanItem = 0x3C,\n    ControlMapper::EKBMFunctionList::None,                                 // UNKNOWN\n    ControlMapper::EKBMFunctionList::None,                                 // UNKNOWN\n    ControlMapper::EKBMFunctionList::None,                                 // UNKNOWN\n    ControlMapper::EKBMFunctionList::None,                                 // UNKNOWN\n    ControlMapper::EKBMFunctionList::KeyPress + 'q',                       // PreviousPauseScreen = 0x41,\n    ControlMapper::EKBMFunctionList::KeyPress + 'e',                       // NextPauseScreen = 0x42,\n    ControlMapper::EKBMFunctionList::None,                                 // UNKNOWN,\n    ControlMapper::EKBMFunctionList::None,                                 // None,\n    ControlMapper::EKBMFunctionList::None,\n};\n\nstd::array<bool, 67> skCommandFilterFlag{true};\n} // Anonymous namespace\n\nvoid ControlMapper::SetCommandFiltered(ECommands cmd, bool filtered) { skCommandFilterFlag[size_t(cmd)] = filtered; }\n\nvoid ControlMapper::ResetCommandFilters() { skCommandFilterFlag.fill(true); }\n\nbool ControlMapper::GetPressInput(ECommands cmd, const CFinalInput& input) {\n  if (!skCommandFilterFlag[size_t(cmd)]) {\n    return false;\n  }\n\n  bool ret = false;\n  const auto func = EFunctionList(g_currentPlayerControl->GetMapping(u32(cmd)));\n  if (func < EFunctionList::MAX) {\n    if (BoolReturnFn fn = skPressFuncs[size_t(func)]) {\n      ret = (input.*fn)();\n    }\n  }\n  if (const auto& kbm = input.GetKBM()) {\n    const EKBMFunctionList kbmfunc = skKBMMapping[size_t(cmd)];\n    if (kbmfunc < EKBMFunctionList::MAX) {\n      if (kbmfunc >= EKBMFunctionList::MousePress) {\n        ret |= input.m_PMouseButtons[size_t(kbmfunc) - size_t(EKBMFunctionList::MousePress)];\n      } else if (kbmfunc >= EKBMFunctionList::SpecialKeyPress) {\n        ret |= input.m_PSpecialKeys[size_t(kbmfunc) - size_t(EKBMFunctionList::SpecialKeyPress)];\n      } else if (kbmfunc >= EKBMFunctionList::KeyPress) {\n        ret |= input.m_PCharKeys[size_t(kbmfunc) - size_t(EKBMFunctionList::KeyPress)];\n      }\n    }\n  }\n  return ret;\n}\n\nbool ControlMapper::GetDigitalInput(ECommands cmd, const CFinalInput& input) {\n  if (!skCommandFilterFlag[size_t(cmd)]) {\n    return false;\n  }\n\n  bool ret = false;\n  const auto func = EFunctionList(g_currentPlayerControl->GetMapping(u32(cmd)));\n  if (func < EFunctionList::MAX) {\n    if (BoolReturnFn fn = skDigitalFuncs[size_t(func)])\n      ret = (input.*fn)();\n  }\n  if (const auto& kbm = input.GetKBM()) {\n    EKBMFunctionList kbmfunc = skKBMMapping[size_t(cmd)];\n    if (kbmfunc < EKBMFunctionList::MAX) {\n      if (kbmfunc >= EKBMFunctionList::MousePress) {\n        ret |= kbm->m_mouseButtons[size_t(kbmfunc) - size_t(EKBMFunctionList::MousePress)];\n      } else if (kbmfunc >= EKBMFunctionList::SpecialKeyPress) {\n        ret |= kbm->m_specialKeys[size_t(kbmfunc) - size_t(EKBMFunctionList::SpecialKeyPress)];\n      } else if (kbmfunc >= EKBMFunctionList::KeyPress) {\n        ret |= kbm->m_charKeys[size_t(kbmfunc) - size_t(EKBMFunctionList::KeyPress)];\n      }\n    }\n  }\n  return ret;\n}\n\nstatic float KBToWASDX(const CKeyboardMouseControllerData& data) {\n  float retval = 0.0;\n  if (data.m_charKeys[size_t('a')]) {\n    retval -= 1.0;\n  }\n  if (data.m_charKeys[size_t('d')]) {\n    retval += 1.0;\n  }\n  if (data.m_charKeys[size_t('w')] ^ data.m_charKeys[size_t('s')]) {\n    retval *= 0.555f;\n  }\n  return retval;\n}\n\nstatic float KBToWASDY(const CKeyboardMouseControllerData& data) {\n  float retval = 0.0;\n  if (data.m_charKeys[size_t('s')]) {\n    retval -= 1.0;\n  }\n  if (data.m_charKeys[size_t('w')]) {\n    retval += 1.0;\n  }\n  if (data.m_charKeys[size_t('a')] ^ data.m_charKeys[size_t('d')]) {\n    retval *= 0.555f;\n  }\n  return retval;\n}\n\nstatic float KBToArrowsX(const CKeyboardMouseControllerData& data) {\n  float retval = 0.0;\n  if (data.m_specialKeys[size_t(ESpecialKey::Left)]) {\n    retval -= 1.0;\n  }\n  if (data.m_specialKeys[size_t(ESpecialKey::Right)]) {\n    retval += 1.0;\n  }\n  return retval;\n}\n\nstatic float KBToArrowsY(const CKeyboardMouseControllerData& data) {\n  float retval = 0.0;\n  if (data.m_specialKeys[size_t(ESpecialKey::Down)]) {\n    retval -= 1.0;\n  }\n  if (data.m_specialKeys[size_t(ESpecialKey::Up)]) {\n    retval += 1.0;\n  }\n  return retval;\n}\n\nfloat ControlMapper::GetAnalogInput(ECommands cmd, const CFinalInput& input) {\n  if (!skCommandFilterFlag[size_t(cmd)]) {\n    return 0.f;\n  }\n\n  float ret = 0.f;\n  const auto func = EFunctionList(g_currentPlayerControl->GetMapping(u32(cmd)));\n  if (func < EFunctionList::MAX) {\n    if (FloatReturnFn fn = skAnalogFuncs[size_t(func)]) {\n      ret = (input.*fn)();\n    }\n  }\n#if 0 // TODO: reimplement this\n  if (const auto& kbm = input.GetKBM()) {\n    switch (cmd) {\n    case ECommands::Forward:\n    case ECommands::LookDown:\n    case ECommands::OrbitUp:\n    case ECommands::MapCircleDown:\n      ret = std::max(ret, zeus::clamp(-1.f, KBToWASDY(*kbm) * input.m_leftMul, 1.f));\n      break;\n    case ECommands::Backward:\n    case ECommands::LookUp:\n    case ECommands::OrbitDown:\n    case ECommands::MapCircleUp:\n      ret = std::max(ret, zeus::clamp(-1.f, -KBToWASDY(*kbm) * input.m_leftMul, 1.f));\n      break;\n    case ECommands::TurnLeft:\n    case ECommands::StrafeLeft:\n    case ECommands::LookLeft:\n    case ECommands::OrbitLeft:\n    case ECommands::MapCircleLeft:\n      ret = std::max(ret, zeus::clamp(-1.f, -KBToWASDX(*kbm) * input.m_leftMul, 1.f));\n      break;\n    case ECommands::TurnRight:\n    case ECommands::StrafeRight:\n    case ECommands::LookRight:\n    case ECommands::OrbitRight:\n    case ECommands::MapCircleRight:\n      ret = std::max(ret, zeus::clamp(-1.f, KBToWASDX(*kbm) * input.m_leftMul, 1.f));\n      break;\n    case ECommands::MapMoveForward:\n      ret = std::max(ret, KBToArrowsY(*kbm));\n      break;\n    case ECommands::MapMoveBack:\n      ret = std::max(ret, -KBToArrowsY(*kbm));\n      break;\n    case ECommands::MapMoveLeft:\n      ret = std::max(ret, -KBToArrowsX(*kbm));\n      break;\n    case ECommands::MapMoveRight:\n      ret = std::max(ret, KBToArrowsX(*kbm));\n      break;\n    default: {\n      const EKBMFunctionList kbmfunc = skKBMMapping[size_t(cmd)];\n      if (kbmfunc < EKBMFunctionList::MAX) {\n        if (kbmfunc >= EKBMFunctionList::MousePress) {\n          ret = std::max(ret, kbm->m_mouseButtons[size_t(kbmfunc) - size_t(EKBMFunctionList::MousePress)] ? 1.f : 0.f);\n        } else if (kbmfunc >= EKBMFunctionList::SpecialKeyPress) {\n          ret = std::max(ret,\n                         kbm->m_specialKeys[size_t(kbmfunc) - size_t(EKBMFunctionList::SpecialKeyPress)] ? 1.f : 0.f);\n        } else if (kbmfunc >= EKBMFunctionList::KeyPress) {\n          ret = std::max(ret, kbm->m_charKeys[size_t(kbmfunc) - size_t(EKBMFunctionList::KeyPress)] ? 1.f : 0.f);\n        }\n      }\n      break;\n    }\n    }\n  }\n#endif\n  return ret;\n}\n\nconst char* ControlMapper::GetDescriptionForCommand(ECommands cmd) {\n  if (cmd >= ECommands::MAX) {\n    return nullptr;\n  }\n  return skCommandDescs[size_t(cmd)];\n}\n\nconst char* ControlMapper::GetDescriptionForFunction(EFunctionList func) {\n  if (func >= EFunctionList::MAX) {\n    return nullptr;\n  }\n  return skFunctionDescs[size_t(func)];\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Input/ControlMapper.hpp",
    "content": "#pragma once\n\n#include <type_traits>\n\n#include \"Input/CKeyboardMouseController.hpp\"\n\nnamespace metaforce {\nstruct CFinalInput;\n\nclass ControlMapper {\npublic:\n  enum class ECommands {\n    Forward,\n    Backward,\n    TurnLeft,\n    TurnRight,\n    StrafeLeft,\n    StrafeRight,\n    LookLeft,\n    LookRight,\n    LookUp,\n    LookDown,\n    JumpOrBoost = 10,\n    FireOrBomb = 11,\n    MissileOrPowerBomb = 12,\n    Morph,\n    AimUp,\n    AimDown,\n    CycleBeamUp,\n    CycleBeamDown,\n    CycleItem,\n    PowerBeam,\n    IceBeam,\n    WaveBeam,\n    PlasmaBeam,\n    ToggleHolster = 23,\n    OrbitClose,\n    OrbitFar,\n    OrbitObject,\n    OrbitSelect,\n    OrbitConfirm,\n    OrbitLeft,\n    OrbitRight,\n    OrbitUp,\n    OrbitDown,\n    LookHold1,\n    LookHold2,\n    LookZoomIn,\n    LookZoomOut,\n    AimHold,\n    MapCircleUp,\n    MapCircleDown,\n    MapCircleLeft,\n    MapCircleRight,\n    MapMoveForward,\n    MapMoveBack,\n    MapMoveLeft,\n    MapMoveRight,\n    MapZoomIn,\n    MapZoomOut,\n    SpiderBall,\n    ChaseCamera,\n    XrayVisor = 50,\n    ThermoVisor = 51,\n    InviroVisor = 52,\n    NoVisor = 53,\n    VisorMenu,\n    VisorUp,\n    VisorDown,\n    ShowCrosshairs,\n    UseSheild = 0x3B,\n    ScanItem = 0x3C,\n    PreviousPauseScreen = 0x41,\n    NextPauseScreen = 0x42,\n    UNKNOWN,\n    None,\n    MAX\n  };\n\n  enum class EFunctionList {\n    None,\n    LeftStickUp,\n    LeftStickDown,\n    LeftStickLeft,\n    LeftStickRight,\n    RightStickUp,\n    RightStickDown,\n    RightStickLeft,\n    RightStickRight,\n    LeftTrigger,\n    RightTrigger,\n    DPadUp,\n    DPadDown,\n    DPadLeft,\n    DPadRight,\n    AButton,\n    BButton,\n    XButton,\n    YButton,\n    ZButton,\n    LeftTriggerPress,\n    RightTriggerPress,\n    Start,\n    MAX // default case\n  };\n\n  enum class EKBMFunctionList {\n    None,\n    KeyPress,\n    SpecialKeyPress = 259,\n    MousePress = 285,\n    MAX = 291 /* Provide space for keys/buttons within base actions */\n  };\n\n  static void SetCommandFiltered(ECommands cmd, bool filtered);\n  static void ResetCommandFilters();\n  static bool GetPressInput(ECommands cmd, const CFinalInput& input);\n  static bool GetDigitalInput(ECommands cmd, const CFinalInput& input);\n  static float GetAnalogInput(ECommands cmd, const CFinalInput& input);\n  static const char* GetDescriptionForCommand(ECommands cmd);\n  static const char* GetDescriptionForFunction(EFunctionList func);\n};\n\nconstexpr ControlMapper::EKBMFunctionList operator+(ControlMapper::EKBMFunctionList a, char b) {\n  using T = std::underlying_type_t<ControlMapper::EKBMFunctionList>;\n  return ControlMapper::EKBMFunctionList(static_cast<T>(a) + static_cast<T>(b));\n}\n\nconstexpr ControlMapper::EKBMFunctionList operator+(ControlMapper::EKBMFunctionList a, ESpecialKey b) {\n  using T = std::underlying_type_t<ControlMapper::EKBMFunctionList>;\n  return ControlMapper::EKBMFunctionList(static_cast<T>(a) + static_cast<T>(b));\n}\n\nconstexpr ControlMapper::EKBMFunctionList operator+(ControlMapper::EKBMFunctionList a, EMouseButton b) {\n  using T = std::underlying_type_t<ControlMapper::EKBMFunctionList>;\n  return ControlMapper::EKBMFunctionList(static_cast<T>(a) + static_cast<T>(b));\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Input/DolphinIController.cpp",
    "content": "#include \"Runtime/Input/CDolphinController.hpp\"\n\nnamespace metaforce {\nIController* IController::Create() {\n  CDolphinController* cont = new CDolphinController();\n  cont->Initialize();\n  return cont;\n}\n}"
  },
  {
    "path": "Runtime/Input/IController.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Input/InputTypes.hpp\"\n#include \"Runtime/Input/CControllerGamepadData.hpp\"\n\n#include \"Runtime/GCNTypes.hpp\"\n\nnamespace metaforce {\n\nclass IController {\nprotected:\n  static constexpr float kAbsoluteMinimum = -1.f;\n  static constexpr float kAbsoluteMaximum = 1.f;\n  static constexpr float kRelativeMinimum = -1.f;\n  static constexpr float kRelativeMaximum = 1.f;\npublic:\n  virtual ~IController() = default;\n  virtual void Poll() = 0;\n  virtual u32 GetDeviceCount() const = 0;\n  virtual CControllerGamepadData& GetGamepadData(u32 controller) = 0;\n  virtual u32 GetControllerType(u32 controller) const = 0;\n  virtual void SetMotorState(EIOPort port, EMotorState state) = 0;\n\n  static IController* Create();\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Input/InputTypes.hpp",
    "content": "#pragma once\n#include <dolphin/pad.h>\n\nnamespace metaforce {\nenum class EIOPort {\n  Player1 = PAD_CHAN0,\n  Player2 = PAD_CHAN1,\n  Player3 = PAD_CHAN2,\n  Player4 = PAD_CHAN3,\n};\n\nenum class EMotorState {\n  Stop = PAD_MOTOR_STOP,\n  Rumble = PAD_MOTOR_RUMBLE,\n  StopHard = PAD_MOTOR_STOP_HARD,\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Input/RumbleFxTable.cpp",
    "content": "#include \"Runtime/Input/RumbleFxTable.hpp\"\n\nnamespace metaforce {\n\nconstexpr RumbleFXTable RumbleFxTable{{\n    /* attackGain, autoReleaseDur, attackDur, decayDur, sustainGain, releaseDur, hasSustain, autoRelease */\n    {0.48f, 0.f, 0.3f, 0.125f, 0.1f, 0.5f, false, false},\n    {0.66f, 0.f, 0.11f, 0.175f, 0.42f, 0.375f, false, false},\n    {0.42f, 0.f, 0.1f, 0.225f, 0.225f, 0.f, false, false},\n    {1.5f, 0.f, 0.1f, 0.225f, 1.025f, 0.4f, false, false},\n    {0.786f, 0.f, 0.1f, 0.16f, 0.655f, 0.255f, false, false},\n    {1.2f, 0.f, 0.4f, 0.1f, 1.f, 0.055f, false, false},\n    {1.2f, 0.f, 0.05f, 0.3f, 0.4f, 1.1f, false, false},      // CameraShake\n    {1.02f, 0.f, 0.065f, 0.175f, 0.85f, 0.9f, false, false}, // EscapeSequenceShake\n    {0.48f, 0.f, 0.065f, 0.175f, 0.4f, 0.0f, false, false},\n    {0.72f, 0.f, 0.01f, 0.01f, 0.6f, 0.1f, false, false},\n    {0.24f, 0.f, 0.01f, 0.525f, 0.2f, 0.2f, false, false},\n    {2.4f, 0.f, 0.01f, 0.466f, 0.f, 0.f, false, false},      // PlayerBump\n    {0.5532f, 0.f, 0.f, 1.345f, 0.f, 1.756f, false, false},  // PlayerGunCharge\n    {2.4f, 0.f, 0.01f, 0.125f, 0.25f, 0.5f, false, false},   // PlayerMissileFire\n    {0.84f, 0.f, 0.1f, 0.125f, 0.35f, 1.0f, false, false},   // PlayerGrappleFire\n    {2.4f, 0.f, 0.1f, 0.225f, 0.38f, 0.3f, false, false},    // PlayerLand\n    {0.48f, 0.f, 0.065f, 0.175f, 0.4f, 0.f, false, false},   // IntroBossProjectile (??)\n    {0.3024f, 0.f, 0.1f, 1.345f, 0.f, 1.756f, false, false}, // PlayerGrappleSwoosh\n    {0.72f, 0.f, 0.01f, 0.01f, 0.6f, 0.1f, false, false},\n    {1.1904f, 0.f, 0.f, 0.125f, 0.683f, 0.5f, true, false},\n    {1.2f, 0.f, 0.01f, 0.621f, 0.f, 0.f, false, false},\n    {0.5268f, 0.f, 0.114f, 1.008f, 0.f, 0.325f, false, false},\n    {0.6828f, 0.f, 0.f, 0.821f, 0.f, 0.f, false, false},\n    {1.8f, 0.f, 0.5f, 0.425f, 0.35f, 0.5f, false, false},\n}};\n\n}\n"
  },
  {
    "path": "Runtime/Input/RumbleFxTable.hpp",
    "content": "#pragma once\n\n#include <array>\n#include \"Runtime/Input/CRumbleVoice.hpp\"\n\nnamespace metaforce {\n\nusing RumbleFXTable = std::array<SAdsrData, 24>;\n\nextern const RumbleFXTable RumbleFxTable;\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Logging.hpp",
    "content": "#pragma once\n\n#include <spdlog/spdlog.h> // IWYU pragma: export\n\nnamespace spdlog {\ntemplate <typename... Args>\n[[noreturn]] inline void fatal(format_string_t<Args...> fmt, Args&&... args) {\n  default_logger_raw()->critical(fmt, std::forward<Args>(args)...);\n  default_logger_raw()->flush();\n  std::terminate();\n}\n} // namespace spdlog\n"
  },
  {
    "path": "Runtime/MP1/CArtifactDoll.cpp",
    "content": "#include \"Runtime/MP1/CArtifactDoll.hpp\"\n\n#include <algorithm>\n#include <array>\n#include <cmath>\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Graphics/CGraphics.hpp\"\n\n#include <zeus/CColor.hpp>\n#include <zeus/CTransform.hpp>\n\nnamespace metaforce::MP1 {\nnamespace {\nconstexpr std::array ArtifactPieceModels{\n    \"CMDL_Piece1\",  // Truth\n    \"CMDL_Piece2\",  // Strength\n    \"CMDL_Piece3\",  // Elder\n    \"CMDL_Piece4\",  // Wild\n    \"CMDL_Piece5\",  // Lifegiver\n    \"CMDL_Piece6\",  // Warrior\n    \"CMDL_Piece7\",  // Chozo\n    \"CMDL_Piece8\",  // Nature\n    \"CMDL_Piece9\",  // Sun\n    \"CMDL_Piece10\", // World\n    \"CMDL_Piece11\", // Spirit\n    \"CMDL_Piece12\"  // Newborn\n};\n\nconstexpr std::array<CAssetId, 12> ArtifactHeadScans{\n    0x32C9DDCEu, // Truth\n    0xB45DAF60u, // Strength\n    0x7F017CC5u, // Elder\n    0x62044C7Du, // Wild\n    0xA9589FD8u, // Lifegiver\n    0x2FCCED76u, // Warrior\n    0xE4903ED3u, // Chozo\n    0x15C68C06u, // Nature\n    0xDE9A5FA3u, // Sun\n    0xFBBE9D9Au, // World\n    0x30E24E3Fu, // Spirit\n    0xB6763C91u  // Newborn\n};\n\nconstexpr zeus::CColor ArtifactPreColor{0.4f, 0.68f, 0.88f, 0.8f};\nconstexpr zeus::CColor ArtifactPostColor{1.f, 0.63f, 0.02f, 1.f};\n} // Anonymous namespace\n\nCArtifactDoll::CArtifactDoll() {\n  x10_lights.resize(2, CLight::BuildDirectional(zeus::skForward, zeus::skWhite));\n  x20_actorLights = std::make_unique<CActorLights>(8, zeus::skZero3f, 4, 4, false, false, false, 0.1f);\n  x0_models.reserve(ArtifactPieceModels.size());\n  for (const char* const model : ArtifactPieceModels) {\n    x0_models.emplace_back(g_SimplePool->GetObj(model));\n  }\n}\n\nint CArtifactDoll::GetArtifactHeadScanIndex(CAssetId scanId) {\n  for (size_t i = 0; i < ArtifactHeadScans.size(); ++i) {\n    if (ArtifactHeadScans[i] == scanId) {\n      return int(i);\n    }\n  }\n\n  return -1;\n}\n\nCAssetId CArtifactDoll::GetArtifactHeadScanFromItemType(CPlayerState::EItemType item) {\n  if (item < CPlayerState::EItemType::Truth || item > CPlayerState::EItemType::Newborn) {\n    return {};\n  }\n\n  return ArtifactHeadScans[size_t(item) - 29];\n}\n\nvoid CArtifactDoll::UpdateArtifactHeadScan(const CStateManager& mgr, float delta) {\n  CPlayerState& playerState = *mgr.GetPlayerState();\n  for (size_t i = 0; i < ArtifactHeadScans.size(); ++i) {\n    if (mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType(i + 29))) {\n      const CAssetId id = ArtifactHeadScans[i];\n      playerState.SetScanTime(id, std::min(playerState.GetScanTime(id) + delta, 1.f));\n    }\n  }\n}\n\nvoid CArtifactDoll::CompleteArtifactHeadScan(const CStateManager& mgr) { UpdateArtifactHeadScan(mgr, 1.f); }\n\nvoid CArtifactDoll::Draw(float alpha, const CStateManager& mgr, bool inArtifactCategory, int selectedArtifact) {\n  if (!IsLoaded())\n    return;\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CArtifactDoll::Draw\", zeus::skPurple);\n\n  alpha *= x24_fader;\n  g_Renderer->SetPerspective(55.f, CGraphics::GetViewportWidth(), CGraphics::GetViewportHeight(), 0.2f, 4096.f);\n  CGraphics::SetViewPointMatrix(zeus::CTransform::Translate(0.f, -10.f, 0.f));\n\n  float angle = CGraphics::GetSecondsMod900() * 2.f * M_PIF * 0.25f;\n  CGraphics::SetModelMatrix(zeus::CTransform::RotateX(zeus::degToRad(std::sin(angle) * 8.f)) *\n                            zeus::CTransform::RotateZ(zeus::degToRad(std::cos(angle) * 8.f)) *\n                            zeus::CTransform::RotateX(M_PIF / 2.f) * zeus::CTransform::Scale(0.2f));\n\n  CPlayerState& playerState = *mgr.GetPlayerState();\n  for (size_t i = 0; i < x0_models.size(); ++i) {\n    TLockedToken<CModel>& model = x0_models[i];\n    zeus::CColor color = ArtifactPreColor;\n    if (playerState.HasPowerUp(CPlayerState::EItemType(i + 29))) {\n      if (ArtifactHeadScans[i].IsValid()) {\n        float interp = (playerState.GetScanTime(ArtifactHeadScans[i]) - 0.5f) * 2.f;\n        if (interp < 0.5f)\n          color = zeus::CColor::lerp(ArtifactPreColor, zeus::skWhite, 2.f * interp);\n        else\n          color = zeus::CColor::lerp(zeus::skWhite, ArtifactPostColor, 2.f * (interp - 0.5f));\n      } else {\n        color = ArtifactPostColor;\n      }\n    }\n\n    if (inArtifactCategory && i == size_t(selectedArtifact)) {\n      float interp = (std::sin(CGraphics::GetSecondsMod900() * 2.f * M_PIF) + 1.f) * 0.5f;\n      color = zeus::CColor::lerp(zeus::skWhite, color, interp);\n      color.a() *= zeus::clamp(0.f, 1.25f - interp, 1.f);\n    }\n\n    CModelFlags flags(7, 0, 3, zeus::CColor(1.f, 0.f));\n    // flags.m_extendedShader = EExtendedShader::SolidColorFrontfaceCullLEqualAlphaOnly;\n    x20_actorLights->ActivateLights();\n    model->Draw(flags);\n\n    flags.x4_color = color;\n    flags.x4_color.a() *= alpha;\n    // flags.m_extendedShader = EExtendedShader::ForcedAdditive;\n    model->Draw({8, 0, 1, flags.x4_color});\n  }\n}\n\nvoid CArtifactDoll::UpdateActorLights() {\n  x10_lights[0] = CLight::BuildDirectional((zeus::skForward + 0.25f * zeus::skRight + 0.1f * zeus::skDown).normalized(),\n                                           zeus::skWhite);\n  x10_lights[1] = CLight::BuildDirectional(-zeus::skForward, zeus::skBlack);\n  x20_actorLights->BuildFakeLightList(x10_lights, zeus::CColor(0.25f, 1.f));\n}\n\nvoid CArtifactDoll::Update(float dt, const CStateManager& mgr) {\n  if (!CheckLoadComplete())\n    return;\n\n  x24_fader = std::min(x24_fader + 2.f * dt, 1.f);\n  if (std::fabs(x24_fader - 1.f) < 0.00001f)\n    UpdateArtifactHeadScan(mgr, 0.5f * dt * 0.5f);\n  UpdateActorLights();\n}\n\nvoid CArtifactDoll::Touch() {\n  if (!CheckLoadComplete())\n    return;\n\n  for (TLockedToken<CModel>& model : x0_models)\n    model->Touch(0);\n}\n\nbool CArtifactDoll::CheckLoadComplete() {\n  if (IsLoaded()) {\n    return true;\n  }\n\n  const bool allLoaded =\n      std::all_of(x0_models.cbegin(), x0_models.cend(), [](const auto& model) { return model.IsLoaded(); });\n  if (!allLoaded) {\n    return false;\n  }\n\n  x28_24_loaded = true;\n  return true;\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/CArtifactDoll.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <vector>\n\n#include \"Runtime/CPlayerState.hpp\"\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Character/CActorLights.hpp\"\n\nnamespace metaforce {\nclass CModel;\nnamespace MP1 {\n\nclass CArtifactDoll {\n  std::vector<TLockedToken<CModel>> x0_models;\n  std::vector<CLight> x10_lights;\n  std::unique_ptr<CActorLights> x20_actorLights;\n  float x24_fader = 0.f;\n  bool x28_24_loaded : 1 = false;\n  void UpdateActorLights();\n\npublic:\n  CArtifactDoll();\n  static int GetArtifactHeadScanIndex(CAssetId scanId);\n  static CAssetId GetArtifactHeadScanFromItemType(CPlayerState::EItemType item);\n  static void UpdateArtifactHeadScan(const CStateManager& mgr, float delta);\n  static void CompleteArtifactHeadScan(const CStateManager& mgr);\n  void Draw(float alpha, const CStateManager& mgr, bool inArtifactCategory, int selectedArtifact);\n  void Update(float dt, const CStateManager& mgr);\n  void Touch();\n  bool CheckLoadComplete();\n  bool IsLoaded() const { return x28_24_loaded; }\n};\n\n} // namespace MP1\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/MP1/CAudioStateWin.cpp",
    "content": "#include \"CAudioStateWin.hpp\"\n\n#include \"Runtime/CArchitectureMessage.hpp\"\n#include \"Runtime/CArchitectureQueue.hpp\"\n#include \"Runtime/CGameState.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Audio/CSfxManager.hpp\"\n#include \"Runtime/MP1/MP1.hpp\"\n\nnamespace metaforce::MP1 {\n\nCIOWin::EMessageReturn CAudioStateWin::OnMessage(const CArchitectureMessage& msg, CArchitectureQueue& queue) {\n  CMain* m = static_cast<CMain*>(g_Main);\n\n  const EArchMsgType msgType = msg.GetType();\n  if (msgType == EArchMsgType::SetGameState) {\n    CSfxManager::KillAll(CSfxManager::ESfxChannels::Game);\n    CSfxManager::TurnOnChannel(CSfxManager::ESfxChannels::Game);\n  } else if (msgType == EArchMsgType::QuitGameplay) {\n    if (g_GameState->GetWorldTransitionManager()->GetTransType() == CWorldTransManager::ETransType::Disabled ||\n        m->GetFlowState() != EClientFlowStates::None) {\n      CSfxManager::SetChannel(CSfxManager::ESfxChannels::Default);\n      CSfxManager::KillAll(CSfxManager::ESfxChannels::Game);\n    }\n  }\n  return EMessageReturn::Normal;\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/CAudioStateWin.hpp",
    "content": "#pragma once\n\n#include \"Runtime/CIOWin.hpp\"\n\nnamespace metaforce::MP1 {\nclass CAudioStateWin : public CIOWin {\npublic:\n  CAudioStateWin() : CIOWin(\"CAudioStateWin\") {}\n  CIOWin::EMessageReturn OnMessage(const CArchitectureMessage& msg, CArchitectureQueue& queue) override;\n};\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/CAutoSave.cpp",
    "content": "#include \"CAutoSave.hpp\"\n#include \"CSaveGameScreen.hpp\"\n#include \"Runtime/MP1/MP1.hpp\"\n\nnamespace metaforce::MP1 {\nCAutoSave::CAutoSave()\n: CIOWin(\"AutoSave\"sv), x14_savegameScreen(new CSaveGameScreen(ESaveContext::InGame, g_GameState->GetCardSerial())) {\n  static_cast<MP1::CMain*>(g_Main)->RefreshGameState();\n}\nvoid CAutoSave::Draw() { x14_savegameScreen->Draw(); }\nCIOWin::EMessageReturn CAutoSave::OnMessage(const CArchitectureMessage& msg, CArchitectureQueue& queue) {\n  if (g_GameState->GetCardSerial() == 0ull) {\n    return EMessageReturn ::RemoveIOWinAndExit;\n  }\n\n  if (msg.GetType() == EArchMsgType::UserInput) {\n    x14_savegameScreen->ProcessUserInput(MakeMsg::GetParmUserInput(msg).x4_parm);\n  } else if (msg.GetType() == EArchMsgType::TimerTick) {\n    auto ret = x14_savegameScreen->Update(MakeMsg::GetParmTimerTick(msg).x4_parm);\n    if (ret != EMessageReturn::Exit) {\n      return EMessageReturn::RemoveIOWinAndExit;\n    }\n  }\n\n  return EMessageReturn::Exit;\n}\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/CAutoSave.hpp",
    "content": "#pragma once\n\n#include \"Runtime/CIOWin.hpp\"\n\nnamespace metaforce::MP1 {\nclass CSaveGameScreen;\nclass CAutoSave : CIOWin {\n  std::unique_ptr<CSaveGameScreen> x14_savegameScreen;\n\npublic:\n  CAutoSave();\n\n  void Draw() override;\n  bool GetIsContinueDraw() const override { return false; }\n  EMessageReturn OnMessage(const CArchitectureMessage& msg, CArchitectureQueue& queue) override;\n};\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/CCredits.cpp",
    "content": "#include \"Runtime/MP1/CCredits.hpp\"\n\n#include \"Runtime/CArchitectureMessage.hpp\"\n#include \"Runtime/CArchitectureQueue.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Graphics/CGraphics.hpp\"\n#include \"Runtime/Graphics/CMoviePlayer.hpp\"\n#include \"Runtime/GuiSys/CGuiTextSupport.hpp\"\n#include \"Runtime/GuiSys/CStringTable.hpp\"\n#include \"Runtime/Input/CFinalInput.hpp\"\n#include \"Runtime/MP1/CPlayMovie.hpp\"\n\nnamespace metaforce::MP1 {\nCCredits::CCredits()\n: CIOWin(\"Credits\")\n, x18_creditsTable(g_SimplePool->GetObj(g_tweakGui->GetCreditsTable()))\n, x20_creditsFont(g_SimplePool->GetObj(g_tweakGui->GetJapaneseCreditsFont()))\n, x54_(g_tweakGui->x30c_) {\n  x18_creditsTable.Lock();\n  x20_creditsFont.Lock();\n}\n\nCIOWin::EMessageReturn CCredits::OnMessage(const CArchitectureMessage& msg, CArchitectureQueue& queue) {\n  switch (msg.GetType()) {\n  case EArchMsgType::UserInput: {\n    return ProcessUserInput(MakeMsg::GetParmUserInput(msg).x4_parm);\n    break;\n  case EArchMsgType::TimerTick: {\n    return Update(MakeMsg::GetParmTimerTick(msg).x4_parm, queue);\n  }\n  default:\n    break;\n  }\n  }\n  return EMessageReturn::Normal;\n}\n\nvoid CCredits::Draw() {\n  if (x14_ != 3) {\n    return;\n  }\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CCredits::Draw\", zeus::skGreen);\n  DrawVideo();\n  DrawText();\n}\n\nCIOWin::EMessageReturn CCredits::Update(float dt, CArchitectureQueue& queue) {\n  switch (x14_) {\n  case 0: {\n    if (!x18_creditsTable || !x20_creditsFont) {\n      return EMessageReturn::Exit;\n    }\n    if (x30_text.empty()) {\n      for (size_t i = 0; i < x18_creditsTable->GetStringCount(); ++i) {\n        x30_text.emplace_back(std::make_unique<CGuiTextSupport>(\n                                  g_ResFactory->GetResourceIdByName(g_tweakGui->GetCreditsFont())->id,\n                                  CGuiTextProperties(true, true, EJustification::Center, EVerticalJustification::Top),\n                                  g_tweakGui->GetCreditsTextFontColor(), g_tweakGui->GetCreditsTextBorderColor(),\n                                  zeus::skWhite, CGraphics::GetViewportWidth() - 64, 0, g_SimplePool,\n                                  CGuiWidget::EGuiModelDrawFlags::Alpha),\n                              zeus::CVector2i(0, 0));\n        x30_text.back().first->SetText(x18_creditsTable->GetString(i));\n      }\n\n      //      auto tmp = std::make_pair(std::make_unique<CGuiTextSupport>(\n      //                                    g_ResFactory->GetResourceIdByName(g_tweakGui->GetCreditsFont())->id,\n      //                                    CGuiTextProperties(true, true, EJustification::Center,\n      //                                    EVerticalJustification::Top), g_tweakGui->GetCreditsTextFontColor(),\n      //                                    g_tweakGui->GetCreditsTextBorderColor(), zeus::skWhite, g_Viewport.x8_width\n      //                                    - 64, 0, g_SimplePool, CGuiWidget::EGuiModelDrawFlags::Alpha),\n      //                                zeus::CVector2i(0, 0));\n      //      tmp.first->SetText(\n      //          \"\\n&push;&font=C29C51F1;&main-color=#89D6FF;URDE DEVELOPMENT TEAM&pop;\\n\"\n      //          \"&push;&main-color=#89D6FF;LEAD REVERSE ENGINEERING TEAM&pop\\n;\"\n      //          \"Jack \\\"Cirrus\\\" Andersen\\n\"\n      //          \"Phillip \\\"Antidote\\\" Stephens\\n\"\n      //          \"Luke \\\"encounter\\\" Street\\n\\n\"\n      //          \"&push;&main-color=#89D6FF;C++ COMPLIANCE & CLEANUP&pop;\\n\"\n      //          \"Lioncache\\n\");\n      //      x30_text.insert(x30_text.end() - 1, std::move(tmp));\n      //      x30_text.back().first->SetOutlineColor(g_tweakGui->GetCreditsTextBorderColor());\n    }\n\n    for (const auto& [text, offset] : x30_text) {\n      if (!text->GetIsTextSupportFinishedLoading()) {\n        return EMessageReturn::Exit;\n      }\n    }\n\n    int scaleY = 0;\n    for (auto& [text, offset] : x30_text) {\n      auto bounds = text->GetBounds();\n      offset.y = (bounds.second.y - bounds.first.y);\n      offset.x = scaleY;\n      text->SetExtentX(CGraphics::GetViewportWidth() - 1280);\n      text->SetExtentY((bounds.second.y - bounds.first.y));\n      scaleY += (bounds.second.y - bounds.first.y);\n    }\n\n    x4c_ = float(scaleY + CGraphics::GetViewportHeight() - 896); // * 0.5f;\n    const float divVal = std::max(g_tweakGui->x310_, g_tweakGui->x30c_);\n    x50_ = x4c_ / (g_tweakGui->x308_ - divVal);\n    x14_ = 1;\n    break;\n  }\n  case 1: {\n    if (!x28_) {\n      //x28_ = std::make_unique<CMoviePlayer>(\"Video/creditBG.thp\", 0.f, true, true);\n    }\n    x14_ = 2;\n    break;\n  }\n  case 2: {\n    if (!x2c_) {\n      //x2c_ = std::make_unique<CStaticAudioPlayer>(\"Audio/ending3.rsf\", 0, 0x5d7c00);\n    }\n    if (!x2c_->IsReady()) {\n      return EMessageReturn::Exit;\n    }\n    x2c_->SetVolume(1.f);\n    //x2c_->StartMixing();\n    x14_ = 3;\n  }\n    [[fallthrough]];\n  case 3: {\n    // if (!x28_->PumpIndexLoad())\n    //    break;\n    x28_->Update(dt);\n    if (x5c_24_) {\n      x5c_28_ = true;\n      if (x5c_27_) {\n        x5c_27_ = false;\n        x58_ = g_tweakGui->x310_ - x58_;\n      }\n    }\n    if (x5c_27_ || x5c_28_) {\n      x58_ = zeus::clamp(0.f, x58_ + dt, g_tweakGui->x310_);\n      if (x58_ == g_tweakGui->x310_) {\n        if (x5c_27_) {\n          x5c_27_ = false;\n          x58_ = 0.f;\n        } else if (x5c_28_) {\n          x5c_25_ = true;\n        }\n      }\n\n      if (x58_ != 0.f && x5c_28_) {\n        const float volume = zeus::clamp(0.f, 1.f - x58_ / g_tweakGui->x310_, 1.f);\n        x2c_->SetVolume(volume);\n      }\n    }\n    x48_ = std::min(x4c_, (dt * x50_) + x48_);\n\n    if (x48_ == x4c_ || x5c_24_) {\n      x5c_24_ = true;\n      x54_ = std::max(0.f, x54_ - dt);\n      const float alpha = x54_ / g_tweakGui->x30c_;\n      for (const auto& [text, offset] : x30_text) {\n        zeus::CColor col = zeus::skWhite;\n        col.a() *= alpha;\n        text->SetGeometryColor(col);\n      }\n      if (x54_ <= 0.f) {\n        x5c_26_ = true;\n      }\n    }\n\n    if (x5c_26_ && x5c_25_) {\n      queue.Push(MakeMsg::CreateCreateIOWin(EArchMsgTarget::IOWinManager, 12, 11,\n                                            std::make_shared<CPlayMovie>(CPlayMovie::EWhichMovie::AfterCredits)));\n      return EMessageReturn::RemoveIOWinAndExit;\n    }\n    break;\n  }\n  default:\n    break;\n  }\n  return EMessageReturn::Exit;\n}\n\nCIOWin::EMessageReturn CCredits::ProcessUserInput(const CFinalInput& input) {\n  if (input.DA()) {\n    x48_ = zeus::clamp(0.f, x48_ - ((x50_ * input.DeltaTime())), x4c_);\n  } else {\n    float leftY = input.ALeftY();\n    float offset = 0.f;\n    if (leftY < 0.f) {\n      offset = -leftY;\n      leftY = 0.f;\n    }\n    x48_ = zeus::clamp(0.f, x48_ - (leftY - offset) * 10.f * x50_ * input.DeltaTime(), x4c_);\n  }\n  return EMessageReturn::Exit;\n}\n\nvoid CCredits::DrawVideo() {\n  /* Render movie */\n  if (x28_ && x28_->DrawVideo() && (x5c_27_ || x5c_28_)) {\n    float alpha = x58_ / g_tweakGui->x310_;\n    if (x5c_27_) {\n      alpha = 1.f - alpha;\n    }\n\n    alpha = zeus::clamp(0.f, alpha, 1.f);\n    zeus::CColor filterCol = zeus::skBlack;\n    filterCol.a() = alpha;\n    CCameraFilterPass::DrawFilter(EFilterType::Blend, EFilterShape::Fullscreen, filterCol, nullptr, 1.f);\n  }\n}\n\nvoid CCredits::DrawText() {\n  float width = 896.f * CGraphics::GetViewportAspect();\n  CGraphics::SetOrtho(0.f, width, 896.f, 0.f, -4096.f, 4096.f);\n  auto region = std::make_pair(zeus::CVector2f{0.f, 0.f}, zeus::CVector2f{width, 896.f});\n  CGraphics::SetViewPointMatrix(zeus::CTransform());\n  CGraphics::SetModelMatrix(zeus::CTransform::Translate((width - 1280.f) / 2.f, 0.f, 896.f));\n  float dVar5 = (x48_ - (region.second.y() - region.first.y()));\n  for (const auto& [text, offset] : x30_text) {\n    if (offset.y + offset.x >= dVar5 && offset.x <= x48_) {\n      DrawText(*text, {0.5f * (region.second.x() - text->GetExtentX()), 0.f, x48_ - offset.x});\n    }\n  }\n  CCameraFilterPass::DrawFilter(EFilterType::Multiply, EFilterShape::CinemaBars, zeus::skBlack, nullptr, 1.f);\n}\n\nvoid CCredits::DrawText(CGuiTextSupport& text, const zeus::CVector3f& translation) {\n  CGraphics::SetCullMode(ERglCullMode::None);\n  g_Renderer->SetViewportOrtho(true, -4096.f, 4096.f);\n  g_Renderer->SetModelMatrix(zeus::CTransform::Translate(translation));\n  g_Renderer->SetDepthReadWrite(false, false);\n  text.Render();\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/CCredits.hpp",
    "content": "#pragma once\n\n#include \"Runtime/CIOWin.hpp\"\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/Audio/CStaticAudioPlayer.hpp\"\n#include \"Runtime/Camera/CCameraFilter.hpp\"\n\n#include \"zeus/CVector2i.hpp\"\n\nnamespace metaforce {\nclass CGuiTextSupport;\nclass CStringTable;\nclass CRasterFont;\nclass CMoviePlayer;\nstruct CFinalInput;\n\nnamespace MP1 {\nclass CCredits : public CIOWin {\n  u32 x14_ = 0;\n  TLockedToken<CStringTable> x18_creditsTable;\n  TLockedToken<CRasterFont> x20_creditsFont;\n  std::unique_ptr<CMoviePlayer> x28_;\n  std::unique_ptr<CStaticAudioPlayer> x2c_;\n  std::vector<std::pair<std::unique_ptr<CGuiTextSupport>, zeus::CVector2i>> x30_text;\n  int x44_textSupport = 0;\n  float x48_ = 0.f;\n  float x4c_ = 0.f;\n  float x50_ = 8.f;\n  float x54_;\n  float x58_ = 0.f;\n  bool x5c_24_ : 1 = false;\n  bool x5c_25_ : 1 = false;\n  bool x5c_26_ : 1 = false;\n  bool x5c_27_ : 1 = true;\n  bool x5c_28_ : 1 = false;\n  void DrawVideo();\n  void DrawText();\n  static void DrawText(CGuiTextSupport&, const zeus::CVector3f& translation);\n\npublic:\n  CCredits();\n  EMessageReturn OnMessage(const CArchitectureMessage&, CArchitectureQueue&) override;\n  bool GetIsContinueDraw() const override { return false; }\n  void Draw() override;\n\n  EMessageReturn Update(float, CArchitectureQueue& queue);\n  EMessageReturn ProcessUserInput(const CFinalInput& input);\n};\n} // namespace MP1\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/MP1/CFaceplateDecoration.cpp",
    "content": "#include \"Runtime/MP1/CFaceplateDecoration.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Particle/CGenDescription.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n\nnamespace metaforce::MP1 {\n\nCFaceplateDecoration::CFaceplateDecoration(CStateManager& stateMgr) {}\n\nvoid CFaceplateDecoration::Update(float dt, CStateManager& stateMgr) {\n  CAssetId txtrId = stateMgr.GetPlayer().GetVisorSteam().GetTextureId();\n  if (!txtrId.IsValid()) {\n    if (xc_ready) {\n      x4_tex.Unlock();\n      x0_id = txtrId;\n    }\n  }\n\n  if (x0_id != txtrId && txtrId.IsValid()) {\n    x0_id = txtrId;\n    x4_tex = g_SimplePool->GetObj(SObjectTag{FOURCC('TXTR'), txtrId});\n    xc_ready = true;\n    x4_tex.Lock();\n  }\n}\n\nvoid CFaceplateDecoration::Draw(CStateManager& stateMgr) {\n  if (xc_ready && x4_tex) {\n    SCOPED_GRAPHICS_DEBUG_GROUP(\"CFaceplateDecoration::Draw\", zeus::skPurple);\n    float alpha = stateMgr.GetPlayer().GetVisorSteam().GetAlpha();\n    if (!zeus::close_enough(alpha, 0.f)) {\n      zeus::CColor color = zeus::skWhite;\n      color.a() = alpha;\n      CCameraFilterPass::DrawFilter(EFilterType::Blend, EFilterShape::FullscreenQuarters, color, x4_tex.GetObj(), 1.f);\n    }\n  }\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/CFaceplateDecoration.hpp",
    "content": "#pragma once\n\n#include <optional>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Graphics/CTexture.hpp\"\n\nnamespace metaforce {\nclass CStateManager;\n\nnamespace MP1 {\n\nclass CFaceplateDecoration {\n  CAssetId x0_id;\n  TToken<CTexture> x4_tex;\n  bool xc_ready = false;\n\npublic:\n  explicit CFaceplateDecoration(CStateManager& stateMgr);\n  void Update(float dt, CStateManager& stateMgr);\n  void Draw(CStateManager& stateMgr);\n};\n\n} // namespace MP1\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/MP1/CFrontEndUI.cpp",
    "content": "#include \"Runtime/MP1/CFrontEndUI.hpp\"\n\n#include <algorithm>\n#include <array>\n#include <ctime>\n#include <fmt/xchar.h>\n\n#include \"NESEmulator/CNESEmulator.hpp\"\n\n#include \"Runtime/CArchitectureMessage.hpp\"\n#include \"Runtime/CArchitectureQueue.hpp\"\n#include \"Runtime/CDependencyGroup.hpp\"\n#include \"Runtime/CDvdFile.hpp\"\n#include \"Runtime/CGameState.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Audio/CAudioGroupSet.hpp\"\n#include \"Runtime/Audio/CSfxManager.hpp\"\n#include \"Runtime/Graphics/CMoviePlayer.hpp\"\n#include \"Runtime/GuiSys/CGuiFrame.hpp\"\n#include \"Runtime/GuiSys/CGuiModel.hpp\"\n#include \"Runtime/GuiSys/CGuiSliderGroup.hpp\"\n#include \"Runtime/GuiSys/CGuiTableGroup.hpp\"\n#include \"Runtime/GuiSys/CGuiTextPane.hpp\"\n#include \"Runtime/GuiSys/CGuiWidgetDrawParms.hpp\"\n#include \"Runtime/GuiSys/CStringTable.hpp\"\n#include \"Runtime/Input/RumbleFxTable.hpp\"\n#include \"Runtime/MP1/CQuitGameScreen.hpp\"\n#include \"Runtime/MP1/CSaveGameScreen.hpp\"\n#include \"Runtime/MP1/CSlideShow.hpp\"\n#include \"Runtime/MP1/MP1.hpp\"\n\nnamespace metaforce::MP1 {\nnamespace {\n#define FE_USE_SECONDS_IN_ELAPSED 1\n\n/* Music volume constants */\nconstexpr float FE1_VOL = 0.7421875f;\nconstexpr float FE2_VOL = 0.7421875f;\n\n/* L/R Stereo transition cues */\nconstexpr std::array<std::array<u16, 2>, 3> FETransitionBackSFX{{\n    {SFXfnt_transfore_00L, SFXfnt_transfore_00R},\n    {SFXfnt_transfore_01L, SFXfnt_transfore_01R},\n    {SFXfnt_transfore_02L, SFXfnt_transfore_02R},\n}};\n\nconstexpr std::array<std::array<u16, 2>, 3> FETransitionForwardSFX{{\n    {SFXfnt_transback_00L, SFXfnt_transback_00R},\n    {SFXfnt_transback_01L, SFXfnt_transback_01R},\n    {SFXfnt_transback_02L, SFXfnt_transback_02R},\n}};\n\nstruct FEMovie {\n  const char* path;\n  bool loop;\n};\n\nconstexpr std::array<FEMovie, 8> FEMovies{{\n    {\"Video/00_first_start.thp\", false},\n    {\"Video/01_startloop.thp\", true},\n    {\"Video/02_start_fileselect_A.thp\", false},\n    {\"Video/03_fileselectloop.thp\", true},\n    {\"Video/04_fileselect_playgame_A.thp\", false},\n    {\"Video/06_fileselect_GBA.thp\", false},\n    {\"Video/07_GBAloop.thp\", true},\n    {\"Video/08_GBA_fileselect.thp\", false},\n}};\n\nconstexpr SObjectTag g_DefaultWorldTag = {FOURCC('MLVL'), 0x158efe17u};\n\nconstexpr std::array<float, 3> AudioFadeTimeA{\n    0.44f,\n    5.41f,\n    3.41f,\n};\n\nconstexpr std::array<float, 3> AudioFadeTimeB{\n    4.2f,\n    6.1f,\n    6.1f,\n};\n\nconstexpr std::array<CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType, 10> NextLinkUI{\n    CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::ConnectSocket,\n    CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::PressStartAndSelect,\n    CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::BeginLink,\n    CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::Linking,\n    CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::Empty,\n    CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::TurnOffGBA,\n    CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::Complete,\n    CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::InsertPak,\n    CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::Empty,\n    CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::Empty,\n};\n\nconstexpr std::array<CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType, 10> PrevLinkUI{\n    CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::Cancelled,\n    CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::Cancelled,\n    CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::Cancelled,\n    CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::Cancelled,\n    CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::Empty,\n    CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::Cancelled,\n    CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::Empty,\n    CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::Cancelled,\n    CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::Empty,\n    CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::Empty,\n};\n} // Anonymous namespace\n\nvoid CFrontEndUI::PlayAdvanceSfx() {\n  CSfxManager::SfxStart(SFXfnt_advance_L, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n  CSfxManager::SfxStart(SFXfnt_advance_R, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n}\n\nCFrontEndUI::SNewFileSelectFrame::SNewFileSelectFrame(CSaveGameScreen* sui, u32 rnd) : x0_rnd(rnd), x4_saveUI(sui) {\n  x10_frme = g_SimplePool->GetObj(\"FRME_NewFileSelect\");\n}\n\nvoid CFrontEndUI::SNewFileSelectFrame::FinishedLoading() {\n  x1c_loadedFrame->Reset();\n  x1c_loadedFrame->SetAspectConstraint(1.78f);\n\n  x20_tablegroup_fileselect = static_cast<CGuiTableGroup*>(x1c_loadedFrame->FindWidget(\"tablegroup_fileselect\"));\n  x24_model_erase = static_cast<CGuiModel*>(x1c_loadedFrame->FindWidget(\"model_erase\"));\n  xf8_model_erase_position = x24_model_erase->GetLocalPosition();\n\n  // TODO: Implement language menu\n  auto langPair = FindTextPanePair(x1c_loadedFrame, \"textpane_lang\");\n  if (langPair.x0_panes[0] != nullptr) {\n    langPair.x0_panes[0]->SetIsSelectable(false);\n    langPair.x0_panes[0]->SetIsVisible(false);\n    langPair.x0_panes[1]->SetIsSelectable(false);\n    langPair.x0_panes[1]->SetIsVisible(false);\n  }\n\n  x28_textpane_erase = FindTextPanePair(x1c_loadedFrame, \"textpane_erase\");\n  x38_textpane_gba = FindTextPanePair(x1c_loadedFrame, \"textpane_gba\");\n  x30_textpane_cheats = FindTextPanePair(x1c_loadedFrame, \"textpane_cheats\");\n  x48_textpane_popupadvance = FindTextPanePair(x1c_loadedFrame, \"textpane_popupadvance\");\n  x50_textpane_popupcancel = FindTextPanePair(x1c_loadedFrame, \"textpane_popupcancel\");\n  x58_textpane_popupextra = FindTextPanePair(x1c_loadedFrame, \"textpane_popupextra\");\n  x40_tablegroup_popup = static_cast<CGuiTableGroup*>(x1c_loadedFrame->FindWidget(\"tablegroup_popup\"));\n  x44_model_dash7 = static_cast<CGuiModel*>(x1c_loadedFrame->FindWidget(\"model_dash7\"));\n  x60_textpane_cancel = static_cast<CGuiTextPane*>(x1c_loadedFrame->FindWidget(\"textpane_cancel\"));\n  FindAndSetPairText(x1c_loadedFrame, \"textpane_title\",\n                     g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 97 : 92));\n  CGuiTextPane* proceed = static_cast<CGuiTextPane*>(x1c_loadedFrame->FindWidget(\"textpane_proceed\"));\n  if (proceed)\n    proceed->TextSupport().SetText(g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 85 : 79));\n  x40_tablegroup_popup->SetIsVisible(false);\n  x40_tablegroup_popup->SetIsActive(false);\n  x40_tablegroup_popup->SetVertical(false);\n  CGuiWidget* worker = x40_tablegroup_popup->GetWorkerWidget(2);\n  worker->SetIsSelectable(false);\n  worker->SetVisibility(false, ETraversalMode::Children);\n\n  x20_tablegroup_fileselect->SetMenuAdvanceCallback([this](CGuiTableGroup* caller) { DoFileMenuAdvance(caller); });\n  x20_tablegroup_fileselect->SetMenuSelectionChangeCallback(\n      [this](CGuiTableGroup* caller, int oldSel) { DoSelectionChange(caller, oldSel); });\n  x20_tablegroup_fileselect->SetMenuCancelCallback([this](CGuiTableGroup* caller) { DoFileMenuCancel(caller); });\n\n  x40_tablegroup_popup->SetMenuAdvanceCallback([this](CGuiTableGroup* caller) { DoPopupAdvance(caller); });\n  x40_tablegroup_popup->SetMenuSelectionChangeCallback(\n      [this](CGuiTableGroup* caller, int oldSel) { DoSelectionChange(caller, oldSel); });\n  x40_tablegroup_popup->SetMenuCancelCallback([this](CGuiTableGroup* caller) { DoPopupCancel(caller); });\n\n  for (size_t i = 0; i < x64_fileSelections.size(); ++i) {\n    x64_fileSelections[i] = FindFileSelectOption(x1c_loadedFrame, int(i));\n  }\n\n  x104_rowPitch =\n      (x64_fileSelections[1].x0_base->GetLocalPosition() - x64_fileSelections[0].x0_base->GetLocalPosition()).z();\n}\n\nbool CFrontEndUI::SNewFileSelectFrame::PumpLoad() {\n  if (x1c_loadedFrame)\n    return true;\n  if (x10_frme.IsLoaded()) {\n    if (x10_frme->GetIsFinishedLoading()) {\n      x1c_loadedFrame = x10_frme.GetObj();\n      FinishedLoading();\n      return true;\n    }\n  }\n  return false;\n}\n\nbool CFrontEndUI::SNewFileSelectFrame::IsTextDoneAnimating() const {\n  if (x64_fileSelections[0].x28_curField != 4)\n    return false;\n  if (x64_fileSelections[1].x28_curField != 4)\n    return false;\n  if (x64_fileSelections[2].x28_curField != 4)\n    return false;\n  if (!x28_textpane_erase.x0_panes[0]->GetTextSupport().IsAnimationDone())\n    return false;\n  return x38_textpane_gba.x0_panes[0]->GetTextSupport().IsAnimationDone();\n}\n\nvoid CFrontEndUI::SNewFileSelectFrame::Update(float dt) {\n  bool saveReady = x4_saveUI->GetUIType() == CSaveGameScreen::EUIType::SaveReady;\n  if (saveReady != x10c_saveReady) {\n    if (saveReady) {\n      ClearFrameContents();\n    } else if (x8_subMenu != ESubMenu::Root) {\n      ResetFrame();\n      DeactivateEraseGamePopup();\n      DeactivateNewGamePopup();\n      x8_subMenu = ESubMenu::Root;\n    }\n    x10c_saveReady = saveReady;\n  }\n  if (x10c_saveReady)\n    SetupFrameContents();\n\n  x1c_loadedFrame->Update(dt);\n}\n\nCFrontEndUI::SNewFileSelectFrame::EAction CFrontEndUI::SNewFileSelectFrame::ProcessUserInput(const CFinalInput& input) {\n  xc_action = EAction::None;\n\n  if (x8_subMenu != ESubMenu::EraseGamePopup)\n    x4_saveUI->ProcessUserInput(input);\n\n  if (IsTextDoneAnimating())\n    x108_curTime = std::min(0.5f, x108_curTime + input.DeltaTime());\n\n  if (x108_curTime < 0.5f)\n    return xc_action;\n\n  if (x10c_saveReady) {\n    x1c_loadedFrame->ProcessUserInput(input);\n  }\n\n  if (x10d_needsEraseToggle) {\n    if (x40_tablegroup_popup->GetIsActive())\n      DeactivateEraseGamePopup();\n    else\n      ActivateEraseGamePopup();\n    x10d_needsEraseToggle = false;\n  }\n\n  if (x10e_needsNewToggle) {\n    if (x40_tablegroup_popup->GetIsActive())\n      DeactivateNewGamePopup();\n    else\n      ActivateNewGamePopup();\n    x10e_needsNewToggle = false;\n  }\n\n  return xc_action;\n}\n\nvoid CFrontEndUI::SNewFileSelectFrame::Draw() const {\n  if (x1c_loadedFrame && x10c_saveReady)\n    x1c_loadedFrame->Draw(CGuiWidgetDrawParms::Default());\n}\n\nvoid CFrontEndUI::SNewFileSelectFrame::HandleActiveChange(CGuiTableGroup* active) {\n  if (!active)\n    return;\n\n  active->SetColors(zeus::skWhite, zeus::CColor{0.627450f, 0.627450f, 0.627450f, 0.784313f});\n\n  if (active == x20_tablegroup_fileselect) {\n    x24_model_erase->SetLocalTransform(zeus::CTransform::Translate(\n        zeus::CVector3f{0.f, 0.f, active->GetUserSelection() * x104_rowPitch} + xf8_model_erase_position));\n  }\n\n  if (x8_subMenu == ESubMenu::Root || x8_subMenu == ESubMenu::NewGamePopup)\n    x24_model_erase->SetIsVisible(false);\n  else\n    x24_model_erase->SetIsVisible(true);\n}\n\nvoid CFrontEndUI::SNewFileSelectFrame::DeactivateEraseGamePopup() {\n  x40_tablegroup_popup->SetIsActive(false);\n  x40_tablegroup_popup->SetIsVisible(false);\n  x20_tablegroup_fileselect->SetIsActive(true);\n\n  HandleActiveChange(x20_tablegroup_fileselect);\n\n  x64_fileSelections[x20_tablegroup_fileselect->GetUserSelection()].x0_base->SetColor(zeus::skWhite);\n}\n\nvoid CFrontEndUI::SNewFileSelectFrame::ActivateEraseGamePopup() {\n  x40_tablegroup_popup->SetIsActive(true);\n  x40_tablegroup_popup->SetIsVisible(true);\n  x40_tablegroup_popup->SetLocalTransform(\n      zeus::CTransform::Translate(0.f, 0.f, x20_tablegroup_fileselect->GetUserSelection() * x104_rowPitch) *\n      x40_tablegroup_popup->GetTransform());\n  x20_tablegroup_fileselect->SetIsActive(false);\n\n  x8_subMenu = ESubMenu::EraseGamePopup;\n  HandleActiveChange(x40_tablegroup_popup);\n\n  x48_textpane_popupadvance.SetPairText(\n      g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 95 : 89));\n  x50_textpane_popupcancel.SetPairText(\n      g_MainStringTable->GetString(38)); // This string is unmodified in PAL/NTSCJ/Trilogy\n\n  x64_fileSelections[x20_tablegroup_fileselect->GetUserSelection()].x0_base->SetColor(zeus::CColor{1.f, 1.f, 1.f, 0.f});\n  x44_model_dash7->SetVisibility(false, ETraversalMode::Children);\n}\n\nvoid CFrontEndUI::SNewFileSelectFrame::DeactivateNewGamePopup() {\n  x40_tablegroup_popup->SetIsActive(false);\n  x40_tablegroup_popup->SetIsVisible(false);\n  x20_tablegroup_fileselect->SetIsActive(true);\n\n  CGuiWidget* worker = x40_tablegroup_popup->GetWorkerWidget(2);\n  worker->SetIsSelectable(false);\n  worker->SetVisibility(false, ETraversalMode::Children);\n\n  x44_model_dash7->SetVisibility(false, ETraversalMode::Children);\n\n  HandleActiveChange(x20_tablegroup_fileselect);\n\n  x64_fileSelections[x20_tablegroup_fileselect->GetUserSelection()].x0_base->SetColor(zeus::skWhite);\n  x60_textpane_cancel->TextSupport().SetText(u\"\");\n}\n\nvoid CFrontEndUI::SNewFileSelectFrame::ActivateNewGamePopup() {\n  x40_tablegroup_popup->SetIsActive(true);\n  x40_tablegroup_popup->SetIsVisible(true);\n  x40_tablegroup_popup->SetUserSelection(0);\n  x40_tablegroup_popup->SetLocalTransform(\n      zeus::CTransform::Translate(0.f, 0.f, x20_tablegroup_fileselect->GetUserSelection() * x104_rowPitch) *\n      x40_tablegroup_popup->GetTransform());\n  x20_tablegroup_fileselect->SetIsActive(false);\n\n  x8_subMenu = ESubMenu::NewGamePopup;\n  HandleActiveChange(x40_tablegroup_popup);\n  x64_fileSelections[x20_tablegroup_fileselect->GetUserSelection()].x0_base->SetColor(zeus::CColor{1.f, 1.f, 1.f, 0.f});\n\n  PlayAdvanceSfx();\n\n  if (g_GameState->SystemOptions().GetPlayerBeatNormalMode()) {\n    x48_textpane_popupadvance.SetPairText(\n        g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 102 : 96));\n    x50_textpane_popupcancel.SetPairText(\n        g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 94 : 88));\n    x58_textpane_popupextra.SetPairText(\n        g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 101 : 95));\n    CGuiWidget* worker = x40_tablegroup_popup->GetWorkerWidget(2);\n    worker->SetIsSelectable(true);\n    worker->SetVisibility(true, ETraversalMode::Children);\n    x44_model_dash7->SetVisibility(true, ETraversalMode::Children);\n  } else {\n    x48_textpane_popupadvance.SetPairText(\n        g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 67 : 61));\n    x50_textpane_popupcancel.SetPairText(\n        g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 94 : 88));\n    x44_model_dash7->SetVisibility(false, ETraversalMode::Children);\n  }\n  x60_textpane_cancel->TextSupport().SetText(\n      g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 82 : 76));\n}\n\nvoid CFrontEndUI::SNewFileSelectFrame::ResetFrame() {\n  x8_subMenu = ESubMenu::Root;\n\n  x38_textpane_gba.x0_panes[0]->SetIsSelectable(true);\n  x38_textpane_gba.x0_panes[0]->TextSupport().SetFontColor(zeus::skWhite);\n\n  x30_textpane_cheats.x0_panes[0]->SetIsSelectable(true);\n  x30_textpane_cheats.x0_panes[0]->TextSupport().SetFontColor(zeus::skWhite);\n\n  ClearFrameContents();\n\n  for (int i = 2; i >= 0; --i)\n    x20_tablegroup_fileselect->GetWorkerWidget(i)->SetIsSelectable(true);\n  x60_textpane_cancel->TextSupport().SetText(u\"\");\n}\n\nvoid CFrontEndUI::SNewFileSelectFrame::ActivateErase() {\n  x8_subMenu = ESubMenu::EraseGame;\n  x28_textpane_erase.x0_panes[0]->SetIsSelectable(false);\n  zeus::CColor color = zeus::skGrey;\n  color.a() = 0.5f;\n  x28_textpane_erase.x0_panes[0]->TextSupport().SetFontColor(color);\n  x38_textpane_gba.x0_panes[0]->TextSupport().SetFontColor(color);\n  x30_textpane_cheats.x0_panes[0]->TextSupport().SetFontColor(color);\n  x38_textpane_gba.x0_panes[0]->SetIsSelectable(false);\n  x30_textpane_cheats.x0_panes[0]->SetIsSelectable(false);\n\n  for (int i = 2; i >= 0; --i) {\n    SFileMenuOption& fileOpt = x64_fileSelections[i];\n    if (x4_saveUI->GetGameData(i)) {\n      fileOpt.x0_base->SetIsSelectable(true);\n      x20_tablegroup_fileselect->SetUserSelection(i);\n    } else {\n      fileOpt.x0_base->SetIsSelectable(false);\n    }\n  }\n\n  x60_textpane_cancel->TextSupport().SetText(\n      g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 82 : 76));\n  HandleActiveChange(x20_tablegroup_fileselect);\n}\n\nvoid CFrontEndUI::SNewFileSelectFrame::ClearFrameContents() {\n  x108_curTime = 0.f;\n  bool hasSave = false;\n  for (size_t i = 0; i < x64_fileSelections.size(); ++i) {\n    if (x4_saveUI->GetGameData(int(i))) {\n      hasSave = true;\n    }\n\n    SFileMenuOption& option = x64_fileSelections[i];\n    option.x2c_chRate = SFileMenuOption::ComputeRandom();\n    option.x28_curField = -1;\n    for (auto& panePair : option.x4_textpanes) {\n      panePair.SetPairText(u\"\");\n    }\n  }\n\n  StartTextAnimating(x28_textpane_erase.x0_panes[0], g_MainStringTable->GetString(38),\n                     60.f); // This string is unmodified in PAL/NTSCJ/Trilogy\n  StartTextAnimating(x38_textpane_gba.x0_panes[0], g_MainStringTable->GetString(37),\n                     60.f); // This string is unmodified in PAL/NTSCJ/Trilogy\n  StartTextAnimating(x30_textpane_cheats.x0_panes[0],\n                     g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 96 : 90), 60.f);\n\n  StartTextAnimating(x28_textpane_erase.x0_panes[1], g_MainStringTable->GetString(38),\n                     60.f); // This string is unmodified in PAL/NTSCJ/Trilogy\n  StartTextAnimating(x38_textpane_gba.x0_panes[1], g_MainStringTable->GetString(37),\n                     60.f); // This string is unmodified in PAL/NTSCJ/Trilogy\n  StartTextAnimating(x30_textpane_cheats.x0_panes[1],\n                     g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 96 : 90), 60.f);\n\n  if (hasSave) {\n    x28_textpane_erase.x0_panes[0]->SetIsSelectable(true);\n    x28_textpane_erase.x0_panes[0]->TextSupport().SetFontColor(zeus::skWhite);\n  } else {\n    x28_textpane_erase.x0_panes[0]->SetIsSelectable(false);\n    zeus::CColor color = zeus::skGrey;\n    color.a() = 0.5f;\n    x28_textpane_erase.x0_panes[0]->TextSupport().SetFontColor(color);\n  }\n\n  x20_tablegroup_fileselect->SetUserSelection(0);\n  CGuiTextPane* cheats = static_cast<CGuiTextPane*>(x20_tablegroup_fileselect->GetWorkerWidget(5));\n  if (CSlideShow::SlideShowGalleryFlags()) {\n    cheats->SetIsSelectable(true);\n    x30_textpane_cheats.x0_panes[0]->TextSupport().SetFontColor(zeus::skWhite);\n  } else {\n    cheats->SetIsSelectable(false);\n    zeus::CColor color = zeus::skGrey;\n    color.a() = 0.5f;\n    x30_textpane_cheats.x0_panes[0]->TextSupport().SetFontColor(color);\n  }\n\n  HandleActiveChange(x20_tablegroup_fileselect);\n}\n\nstd::u16string GetTimeString(const CGameState::GameFileStateInfo* data) {\n  if (data) {\n    auto pt = std::div(data->x0_playTime, 3600);\n#if FE_USE_SECONDS_IN_ELAPSED\n    return fmt::format(u\"{:02d}:{:02d}:{:02d}\", pt.quot, pt.rem / 60, pt.rem % 60);\n#else\n    return fmt::format(u\"{:02d}:{:02d}\", pt.quot, pt.rem / 60);\n#endif\n  }\n  if (!g_Main->IsUSA() || g_Main->IsTrilogy())\n    return g_MainStringTable->GetString(53);\n  return g_MainStringTable->GetString(52);\n}\n\nstd::u16string GetElapsedString(const CGameState::GameFileStateInfo* data) {\n  if (!g_Main->IsUSA() || g_Main->IsTrilogy())\n    return g_MainStringTable->GetString(data ? 55 : 54);\n\n  return std::u16string(g_MainStringTable->GetString(data ? 54 : 53));\n}\n\nvoid CFrontEndUI::SNewFileSelectFrame::SetupFrameContents() {\n  for (size_t i = 0; i < x64_fileSelections.size(); ++i) {\n    SFileMenuOption& option = x64_fileSelections[i];\n    if (option.x28_curField == 4) {\n      continue;\n    }\n\n    SGuiTextPair* pair = (option.x28_curField == UINT32_MAX) ? nullptr : &option.x4_textpanes[option.x28_curField];\n    if (!pair || pair->x0_panes[0]->GetTextSupport().GetNumCharsPrinted() >=\n                     pair->x0_panes[0]->GetTextSupport().GetNumCharsTotal()) {\n      if (++option.x28_curField < 4) {\n        std::u16string str;\n        SGuiTextPair& populatePair = option.x4_textpanes[option.x28_curField];\n        const CGameState::GameFileStateInfo* data = x4_saveUI->GetGameData(int(i));\n\n        switch (option.x28_curField) {\n        case 0:\n          // Completion percent\n          if (data) {\n            int strIdx = (data->x20_hardMode ? 106 : 39);\n            if ((!g_Main->IsUSA() || g_Main->IsTrilogy()))\n              strIdx = (strIdx == 106 ? 100 : 40);\n\n            std::u16string fileStr = g_MainStringTable->GetString(strIdx + int(i));\n            str = fileStr + fmt::format(u\"  {:02d}%\", data->x18_itemPercent);\n            break;\n          }\n          str = g_MainStringTable->GetString(36);\n          break;\n\n        case 1:\n          // World name\n          if (data) {\n            if (g_MemoryCardSys->HasSaveWorldMemory(data->x8_mlvlId)) {\n              const CSaveWorldMemory& wldMem = g_MemoryCardSys->GetSaveWorldMemory(data->x8_mlvlId);\n              str = wldMem.GetFrontEndName();\n            }\n            break;\n          }\n          str = g_MainStringTable->GetString(51 + int(!g_Main->IsUSA() || g_Main->IsTrilogy()));\n          break;\n        case 2:\n#if FE_USE_SECONDS_IN_ELAPSED\n          if (!g_Main->IsUSA() || g_Main->IsTrilogy()) {\n            str = GetElapsedString(data);\n          } else {\n            str = GetTimeString(data);\n          }\n#else\n          if (!g_Main->IsUSA() || g_Main->IsTrilogy()) {\n            str = GetElapsedString(data);\n          } else {\n            str = GetTimeString(data);\n          }\n#endif\n          break;\n        case 3:\n#if FE_USE_SECONDS_IN_ELAPSED\n          if (!g_Main->IsUSA() || g_Main->IsTrilogy()) {\n            str = GetTimeString(data);\n          } else {\n            str = std::u16string(u\"    \") + GetElapsedString(data);\n          }\n#else\n          if (!g_Main->IsUSA() || g_Main->IsTrilogy()) {\n            str = GetTimeString(data);\n          } else {\n            str = std::u16string(u\"    \") + GetElapsedString(data);\n          }\n#endif\n          break;\n\n        default:\n          break;\n        }\n\n        StartTextAnimating(populatePair.x0_panes[0], str, option.x2c_chRate);\n        StartTextAnimating(populatePair.x0_panes[1], str, option.x2c_chRate);\n      }\n    }\n  }\n}\n\nvoid CFrontEndUI::SNewFileSelectFrame::DoPopupCancel(CGuiTableGroup* caller) {\n  if (x8_subMenu == ESubMenu::EraseGamePopup) {\n    CSfxManager::SfxStart(SFXfnt_back, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n    x8_subMenu = ESubMenu::EraseGame;\n    x10d_needsEraseToggle = true;\n  } else {\n    CSfxManager::SfxStart(SFXfnt_back, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n    x8_subMenu = ESubMenu::Root;\n    x10e_needsNewToggle = true;\n  }\n}\n\nvoid CFrontEndUI::SNewFileSelectFrame::DoPopupAdvance(CGuiTableGroup* caller) {\n  if (x8_subMenu == ESubMenu::EraseGamePopup) {\n    if (x40_tablegroup_popup->GetUserSelection() == 1) {\n      x4_saveUI->EraseGame(x20_tablegroup_fileselect->GetUserSelection());\n      ResetFrame();\n    } else\n      x8_subMenu = ESubMenu::EraseGame;\n    x10d_needsEraseToggle = true;\n  } else {\n    if (g_GameState->SystemOptions().GetPlayerBeatNormalMode()) {\n      if (x40_tablegroup_popup->GetUserSelection() == 1) {\n        PlayAdvanceSfx();\n        xc_action = EAction::GameOptions;\n        return;\n      }\n      g_GameState->SetHardMode(!x40_tablegroup_popup->GetUserSelection());\n      x4_saveUI->StartGame(x20_tablegroup_fileselect->GetUserSelection());\n    } else {\n      if (x40_tablegroup_popup->GetUserSelection() == 1) {\n        PlayAdvanceSfx();\n        xc_action = EAction::GameOptions;\n        return;\n      }\n      x4_saveUI->StartGame(x20_tablegroup_fileselect->GetUserSelection());\n    }\n  }\n}\n\nvoid CFrontEndUI::SNewFileSelectFrame::DoFileMenuCancel(CGuiTableGroup* caller) {\n  if (x8_subMenu == ESubMenu::EraseGame) {\n    CSfxManager::SfxStart(SFXfnt_back, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n    ResetFrame();\n  }\n}\n\nvoid CFrontEndUI::SNewFileSelectFrame::DoSelectionChange(CGuiTableGroup* caller, int oldSel) {\n  HandleActiveChange(caller);\n  CSfxManager::SfxStart(SFXfnt_selection_change, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n}\n\nvoid CFrontEndUI::SNewFileSelectFrame::DoFileMenuAdvance(CGuiTableGroup* caller) {\n  int userSel = x20_tablegroup_fileselect->GetUserSelection();\n  if (userSel < 3) {\n    if (x8_subMenu == ESubMenu::EraseGame) {\n      if (x4_saveUI->GetGameData(userSel)) {\n        PlayAdvanceSfx();\n        x10d_needsEraseToggle = true;\n      }\n    } else {\n      if (x4_saveUI->GetGameData(userSel)) {\n        x4_saveUI->StartGame(userSel);\n      } else\n        x10e_needsNewToggle = true;\n    }\n  } else if (userSel == 3) {\n    PlayAdvanceSfx();\n    ActivateErase();\n  } else if (userSel == 4) {\n    xc_action = EAction::FusionBonus;\n  } else if (userSel == 5) {\n    xc_action = EAction::SlideShow;\n  }\n}\n\nCFrontEndUI::SFileMenuOption CFrontEndUI::SNewFileSelectFrame::FindFileSelectOption(CGuiFrame* frame, int idx) {\n  SFileMenuOption ret;\n  ret.x0_base = frame->FindWidget(fmt::format(\"basewidget_file{}\", idx));\n  ret.x4_textpanes[0] = FindTextPanePair(frame, fmt::format(\"textpane_filename{}\", idx));\n  ret.x4_textpanes[1] = FindTextPanePair(frame, fmt::format(\"textpane_world{}\", idx));\n  ret.x4_textpanes[2] = FindTextPanePair(frame, fmt::format(\"textpane_playtime{}\", idx));\n  ret.x4_textpanes[3] = FindTextPanePair(frame, fmt::format(\"textpane_date{}\", idx));\n  return ret;\n}\n\nvoid CFrontEndUI::SNewFileSelectFrame::StartTextAnimating(CGuiTextPane* text, std::u16string_view str, float chRate) {\n  text->TextSupport().SetText(str);\n  text->TextSupport().SetTypeWriteEffectOptions(true, 0.1f, chRate);\n}\n\nCFrontEndUI::SFusionBonusFrame::SFusionBonusFrame() {\n  if (!g_Main->IsTrilogy()) {\n    x4_gbaSupport = std::make_unique<CGBASupport>();\n    xc_gbaScreen = g_SimplePool->GetObj(\"FRME_GBAScreen\");\n    x18_gbaLink = g_SimplePool->GetObj(\"FRME_GBALink\");\n  }\n}\n\nvoid CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::SetUIText(EUIType tp) {\n  int instructions = -1;\n  int yes = -1;\n  int no = -1;\n\n  bool cableVisible = false;\n  bool circleGcVisible = false;\n  bool circleGbaVisible = false;\n  bool circleStartVisible = false;\n  bool pakoutVisible = false;\n  bool gbaScreenVisible = false;\n  bool connectVisible = false;\n\n  switch (tp) {\n  case EUIType::InsertPak:\n    instructions = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 73 : 67; // Insert Game Pak\n    no = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 82 : 76;\n    yes = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 83 : 77;\n    pakoutVisible = true;\n    circleGbaVisible = true;\n    break;\n  case EUIType::ConnectSocket:\n    instructions = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 68 : 62; // Connect socket\n    no = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 82 : 76;\n    yes = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 83 : 77;\n    cableVisible = true;\n    circleGcVisible = true;\n    circleGbaVisible = true;\n    break;\n  case EUIType::PressStartAndSelect:\n    instructions = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 74 : 68; // Hold start and select\n    no = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 82 : 76;\n    yes = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 83 : 77;\n    cableVisible = true;\n    circleStartVisible = true;\n    gbaScreenVisible = true;\n    break;\n  case EUIType::BeginLink:\n    instructions = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 75 : 69; // Begin link?\n    no = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 82 : 76;\n    yes = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 83 : 77;\n    cableVisible = true;\n    gbaScreenVisible = true;\n    break;\n  case EUIType::TurnOffGBA:\n    instructions = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 76 : 70; // Turn off GBA\n    no = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 82 : 76;\n    yes = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 83 : 77;\n    cableVisible = true;\n    gbaScreenVisible = true;\n    circleStartVisible = true;\n    break;\n  case EUIType::Linking:\n    x4_gbaSupport->StartLink();\n    instructions = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 72 : 66; // Linking\n    cableVisible = true;\n    gbaScreenVisible = true;\n    connectVisible = true;\n    break;\n  case EUIType::LinkFailed:\n    instructions = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 69 : 63; // Link failed\n    no = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 82 : 76;\n    yes = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 83 : 77;\n    cableVisible = true;\n    circleGcVisible = true;\n    circleGbaVisible = true;\n    circleStartVisible = true;\n    gbaScreenVisible = true;\n    break;\n  case EUIType::LinkCompleteOrLinking:\n    yes = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 83 : 77;\n    instructions = x40_linkInProgress + (g_Main->IsUSA() && !g_Main->IsTrilogy() ? 71 : 65); // Complete or linking\n    cableVisible = true;\n    gbaScreenVisible = true;\n    break;\n  case EUIType::Complete:\n  case EUIType::Cancelled:\n  default:\n    break;\n  }\n\n  std::u16string instructionsStr;\n  if (instructions != -1)\n    instructionsStr = g_MainStringTable->GetString(instructions);\n  xc_textpane_instructions.SetPairText(instructionsStr);\n\n  std::u16string yesStr;\n  if (yes != -1)\n    yesStr = g_MainStringTable->GetString(yes);\n  x14_textpane_yes->TextSupport().SetText(yesStr);\n\n  std::u16string noStr;\n  if (no != -1)\n    noStr = g_MainStringTable->GetString(no);\n  x18_textpane_no->TextSupport().SetText(noStr);\n\n  x1c_model_gc->SetVisibility(true, ETraversalMode::Children);\n  x20_model_gba->SetVisibility(true, ETraversalMode::Children);\n  x24_model_cable->SetVisibility(cableVisible, ETraversalMode::Children);\n  x28_model_circlegcport->SetVisibility(circleGcVisible, ETraversalMode::Children);\n  x2c_model_circlegbaport->SetVisibility(circleGbaVisible, ETraversalMode::Children);\n  x30_model_circlestartselect->SetVisibility(circleStartVisible, ETraversalMode::Children);\n  x34_model_pakout->SetVisibility(pakoutVisible, ETraversalMode::Children);\n  x38_model_gbascreen->SetVisibility(gbaScreenVisible, ETraversalMode::Children);\n  x3c_model_connect->SetVisibility(connectVisible, ETraversalMode::Children);\n\n  x0_uiType = tp;\n}\n\nCFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EAction\nCFrontEndUI::SFusionBonusFrame::SGBALinkFrame::ProcessUserInput(const CFinalInput& input, bool linkInProgress) {\n  if (linkInProgress != x40_linkInProgress) {\n    x40_linkInProgress = linkInProgress;\n    SetUIText(x0_uiType);\n  }\n\n  switch (x0_uiType) {\n  case EUIType::InsertPak:\n  case EUIType::ConnectSocket:\n  case EUIType::PressStartAndSelect:\n  case EUIType::BeginLink:\n  case EUIType::LinkFailed:\n  case EUIType::LinkCompleteOrLinking:\n  case EUIType::TurnOffGBA:\n    if (input.PA() || input.PSpecialKey(ESpecialKey::Enter) || input.PMouseButton(EMouseButton::Primary)) {\n      PlayAdvanceSfx();\n      SetUIText(NextLinkUI[size_t(x0_uiType)]);\n    } else if (input.PB() || input.PSpecialKey(ESpecialKey::Esc)) {\n      const EUIType prevUi = PrevLinkUI[size_t(x0_uiType)];\n      if (prevUi == EUIType::Empty) {\n        break;\n      }\n      CSfxManager::SfxStart(SFXfnt_back, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n      SetUIText(prevUi);\n    }\n    break;\n  case EUIType::Linking:\n    if (x4_gbaSupport->GetPhase() == CGBASupport::EPhase::Complete) {\n      if (x4_gbaSupport->IsFusionLinked())\n        g_GameState->SystemOptions().SetPlayerLinkedFusion(true);\n      if (x4_gbaSupport->IsFusionBeat())\n        g_GameState->SystemOptions().SetPlayerBeatFusion(true);\n      if (x4_gbaSupport->IsFusionLinked()) {\n        PlayAdvanceSfx();\n        SetUIText(EUIType::LinkCompleteOrLinking);\n      } else {\n        CSfxManager::SfxStart(SFXfnt_back, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n        SetUIText(EUIType::LinkFailed);\n      }\n    } else if (x4_gbaSupport->GetPhase() == CGBASupport::EPhase::Failed) {\n      CSfxManager::SfxStart(SFXfnt_back, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n      SetUIText(EUIType::LinkFailed);\n    }\n    break;\n  case EUIType::Complete:\n    return EAction::Complete;\n  case EUIType::Cancelled:\n    return EAction::Cancelled;\n  default:\n    break;\n  }\n\n  return EAction::None;\n}\n\nvoid CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::Update(float dt) {\n  x4_gbaSupport->Update(dt);\n  x8_frme->Update(dt);\n}\n\nvoid CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::FinishedLoading() {\n  x8_frme->SetAspectConstraint(1.78f);\n\n  xc_textpane_instructions = FindTextPanePair(x8_frme, \"textpane_instructions\");\n  x14_textpane_yes = static_cast<CGuiTextPane*>(x8_frme->FindWidget(\"textpane_yes\"));\n  x18_textpane_no = static_cast<CGuiTextPane*>(x8_frme->FindWidget(\"textpane_no\"));\n  x1c_model_gc = static_cast<CGuiModel*>(x8_frme->FindWidget(\"model_gc\"));\n  x20_model_gba = static_cast<CGuiModel*>(x8_frme->FindWidget(\"model_gba\"));\n  x24_model_cable = static_cast<CGuiModel*>(x8_frme->FindWidget(\"model_cable\"));\n  x28_model_circlegcport = static_cast<CGuiModel*>(x8_frme->FindWidget(\"model_circlegcport\"));\n  x2c_model_circlegbaport = static_cast<CGuiModel*>(x8_frme->FindWidget(\"model_circlegbaport\"));\n  x30_model_circlestartselect = static_cast<CGuiModel*>(x8_frme->FindWidget(\"model_circlestartselect\"));\n  x34_model_pakout = static_cast<CGuiModel*>(x8_frme->FindWidget(\"model_pakout\"));\n  x38_model_gbascreen = static_cast<CGuiModel*>(x8_frme->FindWidget(\"model_gbascreen\"));\n  x3c_model_connect = static_cast<CGuiModel*>(x8_frme->FindWidget(\"model_connect\"));\n  SetUIText(EUIType::InsertPak);\n}\n\nvoid CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::Draw() { x8_frme->Draw(CGuiWidgetDrawParms::Default()); }\n\nCFrontEndUI::SFusionBonusFrame::SGBALinkFrame::SGBALinkFrame(CGuiFrame* linkFrame, CGBASupport* support,\n                                                             bool linkInProgress)\n: x4_gbaSupport(support), x8_frme(linkFrame), x40_linkInProgress(linkInProgress) {\n  support->InitializeSupport();\n  FinishedLoading();\n}\n\nvoid CFrontEndUI::SFusionBonusFrame::FinishedLoading() {\n  x24_loadedFrame->SetAspectConstraint(1.78f);\n\n  x28_tablegroup_options = static_cast<CGuiTableGroup*>(x24_loadedFrame->FindWidget(\"tablegroup_options\"));\n  x2c_tablegroup_fusionsuit = static_cast<CGuiTableGroup*>(x24_loadedFrame->FindWidget(\"tablegroup_fusionsuit\"));\n  x30_textpane_instructions = FindTextPanePair(x24_loadedFrame, \"textpane_instructions\");\n\n  FindAndSetPairText(x24_loadedFrame, \"textpane_nes\",\n                     g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 66 : 60));\n  FindAndSetPairText(x24_loadedFrame, \"textpane_fusionsuit\",\n                     g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 63 : 57));\n  FindAndSetPairText(x24_loadedFrame, \"textpane_fusionsuitno\",\n                     g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 65 : 59));\n  FindAndSetPairText(x24_loadedFrame, \"textpane_fusionsuityes\",\n                     g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 64 : 58));\n  FindAndSetPairText(x24_loadedFrame, \"textpane_title\",\n                     g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 100 : 94));\n\n  static_cast<CGuiTextPane*>(x24_loadedFrame->FindWidget(\"textpane_proceed\"))\n      ->TextSupport()\n      .SetText(g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 85 : 79));\n  static_cast<CGuiTextPane*>(x24_loadedFrame->FindWidget(\"textpane_cancel\"))\n      ->TextSupport()\n      .SetText(g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 82 : 76));\n\n  x2c_tablegroup_fusionsuit->SetIsActive(false);\n  x2c_tablegroup_fusionsuit->SetIsVisible(false);\n  x2c_tablegroup_fusionsuit->SetVertical(false);\n  x2c_tablegroup_fusionsuit->SetUserSelection(g_GameState->SystemOptions().GetPlayerFusionSuitActive());\n\n  SetTableColors(x28_tablegroup_options);\n  SetTableColors(x2c_tablegroup_fusionsuit);\n\n  x28_tablegroup_options->SetMenuAdvanceCallback([this](CGuiTableGroup* caller) { DoAdvance(caller); });\n  x28_tablegroup_options->SetMenuSelectionChangeCallback(\n      [this](CGuiTableGroup* caller, int oldSel) { DoSelectionChange(caller, oldSel); });\n  x28_tablegroup_options->SetMenuCancelCallback([this](CGuiTableGroup* caller) { DoCancel(caller); });\n  x2c_tablegroup_fusionsuit->SetMenuSelectionChangeCallback(\n      [this](CGuiTableGroup* caller, int oldSel) { DoSelectionChange(caller, oldSel); });\n}\n\nbool CFrontEndUI::SFusionBonusFrame::PumpLoad() {\n  if (x24_loadedFrame)\n    return true;\n  if (!xc_gbaScreen.IsLoaded())\n    return false;\n  if (!x18_gbaLink.IsLoaded())\n    return false;\n  if (!x4_gbaSupport->IsReady())\n    return false;\n  if (!xc_gbaScreen->GetIsFinishedLoading())\n    return false;\n  x24_loadedFrame = xc_gbaScreen.GetObj();\n  FinishedLoading();\n  return true;\n}\n\nvoid CFrontEndUI::SFusionBonusFrame::SetTableColors(CGuiTableGroup* tbgp) const {\n  tbgp->SetColors(zeus::skWhite, zeus::CColor{0.627450f, 0.627450f, 0.627450f, 0.784313f});\n}\n\nvoid CFrontEndUI::SFusionBonusFrame::Update(float dt, CSaveGameScreen* saveUI) {\n  bool doDraw = !saveUI || saveUI->GetUIType() == CSaveGameScreen::EUIType::SaveReady;\n\n  if (doDraw != x38_lastDoDraw) {\n    x38_lastDoDraw = doDraw;\n    ResetCompletionFlags();\n  }\n\n  if (x0_gbaLinkFrame)\n    x0_gbaLinkFrame->Update(dt);\n  else if (x24_loadedFrame)\n    x24_loadedFrame->Update(dt);\n\n  bool showFusionSuit = (g_GameState->SystemOptions().GetPlayerLinkedFusion() &&\n                         g_GameState->SystemOptions().GetPlayerBeatNormalMode()) ||\n                        m_gbaOverride;\n  bool showFusionSuitProceed = showFusionSuit && x28_tablegroup_options->GetUserSelection() == 1;\n  x2c_tablegroup_fusionsuit->SetIsActive(showFusionSuitProceed);\n  x2c_tablegroup_fusionsuit->SetIsVisible(showFusionSuitProceed);\n  x24_loadedFrame->FindWidget(\"textpane_proceed\")->SetIsVisible(showFusionSuitProceed);\n\n  std::u16string instructionStr;\n  x30_textpane_instructions.x0_panes[0]->TextSupport().SetFontColor(zeus::skWhite);\n  if (x28_tablegroup_options->GetUserSelection() == 1) {\n    /* Fusion Suit */\n    if (x3a_mpNotComplete)\n      instructionStr =\n          g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 80 : 74); // MP not complete\n    else if (!showFusionSuit)\n      instructionStr =\n          g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 78 : 72); // To enable fusion suit\n  } else {\n    /* NES Metroid */\n    if (x39_fusionNotComplete)\n      instructionStr = g_MainStringTable->GetString(\n          (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 79 : 73); // You have not completed fusion\n    else if (!g_GameState->SystemOptions().GetPlayerBeatFusion())\n      instructionStr =\n          g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 77 : 71); // To play NES Metroid\n  }\n\n  x30_textpane_instructions.SetPairText(instructionStr);\n}\n\nCFrontEndUI::SFusionBonusFrame::EAction CFrontEndUI::SFusionBonusFrame::ProcessUserInput(const CFinalInput& input,\n                                                                                         CSaveGameScreen* sui) {\n  x8_action = EAction::None;\n\n  if (sui)\n    sui->ProcessUserInput(input);\n\n  if (x38_lastDoDraw) {\n    if (x0_gbaLinkFrame) {\n      SGBALinkFrame::EAction action = x0_gbaLinkFrame->ProcessUserInput(input, sui);\n      if (action != SGBALinkFrame::EAction::None) {\n        x0_gbaLinkFrame.reset();\n        if (action == SGBALinkFrame::EAction::Complete) {\n          if (x28_tablegroup_options->GetUserSelection() == 0 && !g_GameState->SystemOptions().GetPlayerBeatFusion())\n            x39_fusionNotComplete = true;\n          else if (sui)\n            sui->SaveNESState();\n        }\n      }\n    } else if (x24_loadedFrame) {\n      CFinalInput useInput = input;\n      if (input.PZ() || input.PSpecialKey(ESpecialKey::Tab)) {\n        useInput.x2d_b28_PA = true;\n        m_gbaOverride = true;\n      }\n      x24_loadedFrame->ProcessUserInput(useInput);\n    }\n  }\n\n  return x8_action;\n}\n\nvoid CFrontEndUI::SFusionBonusFrame::Draw() const {\n  if (!x38_lastDoDraw)\n    return;\n  if (x0_gbaLinkFrame)\n    x0_gbaLinkFrame->Draw();\n  else if (x24_loadedFrame)\n    x24_loadedFrame->Draw(CGuiWidgetDrawParms::Default());\n}\n\nvoid CFrontEndUI::SFusionBonusFrame::DoCancel(CGuiTableGroup* caller) {\n  x8_action = EAction::GoBack;\n  x28_tablegroup_options->SetUserSelection(0);\n  x2c_tablegroup_fusionsuit->SetIsActive(false);\n  x30_textpane_instructions.SetPairText(u\"\");\n  SetTableColors(x28_tablegroup_options);\n}\n\nvoid CFrontEndUI::SFusionBonusFrame::DoSelectionChange(CGuiTableGroup* caller, int oldSel) {\n  if (caller == x28_tablegroup_options) {\n    CSfxManager::SfxStart(SFXfnt_selection_change, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n    ResetCompletionFlags();\n  } else {\n    CSfxManager::SfxStart(SFXfnt_enum_change, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n    bool fusionActive = x2c_tablegroup_fusionsuit->GetUserSelection() == 1;\n    g_GameState->SystemOptions().SetPlayerFusionSuitActive(fusionActive);\n    g_GameState->GetPlayerState()->SetIsFusionEnabled(fusionActive);\n  }\n  SetTableColors(caller);\n}\n\nvoid CFrontEndUI::SFusionBonusFrame::DoAdvance(CGuiTableGroup* caller) {\n  switch (x28_tablegroup_options->GetUserSelection()) {\n  case 1:\n    /* Fusion Suit */\n    if (x3a_mpNotComplete || m_gbaOverride) {\n      x3a_mpNotComplete = false;\n      PlayAdvanceSfx();\n    } else if (g_GameState->SystemOptions().GetPlayerBeatNormalMode()) {\n      if (g_GameState->SystemOptions().GetPlayerLinkedFusion())\n        return;\n      x0_gbaLinkFrame = std::make_unique<SGBALinkFrame>(x18_gbaLink.GetObj(), x4_gbaSupport.get(), false);\n      PlayAdvanceSfx();\n    } else {\n      x3a_mpNotComplete = true;\n      CSfxManager::SfxStart(SFXfnt_back, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n    }\n    break;\n  case 0:\n    /* NES Metroid */\n    if (x39_fusionNotComplete && !m_gbaOverride) {\n      x39_fusionNotComplete = false;\n      PlayAdvanceSfx();\n    } else if (g_GameState->SystemOptions().GetPlayerBeatFusion() || m_gbaOverride) {\n      // x8_action = EAction::None;\n      CSfxManager::SfxStart(SFXfnt_back, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n      x8_action = EAction::PlayNESMetroid;\n    } else {\n      x0_gbaLinkFrame = std::make_unique<SGBALinkFrame>(x18_gbaLink.GetObj(), x4_gbaSupport.get(), false);\n      PlayAdvanceSfx();\n    }\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CFrontEndUI::SGuiTextPair::SetPairText(std::u16string_view str) {\n  x0_panes[0]->TextSupport().SetText(str);\n  x0_panes[1]->TextSupport().SetText(str);\n}\n\nCFrontEndUI::SGuiTextPair CFrontEndUI::FindTextPanePair(CGuiFrame* frame, std::string_view name) {\n  SGuiTextPair ret;\n  ret.x0_panes[0] = static_cast<CGuiTextPane*>(frame->FindWidget(name));\n  ret.x0_panes[1] = static_cast<CGuiTextPane*>(frame->FindWidget(fmt::format(\"{}b\", name)));\n  return ret;\n}\n\nvoid CFrontEndUI::FindAndSetPairText(CGuiFrame* frame, std::string_view name, std::u16string_view str) {\n  CGuiTextPane* w1 = static_cast<CGuiTextPane*>(frame->FindWidget(name));\n  w1->TextSupport().SetText(str);\n  CGuiTextPane* w2 = static_cast<CGuiTextPane*>(frame->FindWidget(fmt::format(\"{}b\", name)));\n  w2->TextSupport().SetText(str);\n}\n\nvoid CFrontEndUI::SFrontEndFrame::FinishedLoading() {\n  x14_loadedFrme->SetAspectConstraint(1.78f);\n\n  x18_tablegroup_mainmenu = static_cast<CGuiTableGroup*>(x14_loadedFrme->FindWidget(\"tablegroup_mainmenu\"));\n  // TODO: HACK: Implement language menu so this isn't necessary\n  if (g_Main->IsUSA() && !g_Main->IsTrilogy()) {\n    x1c_gbaPair = FindTextPanePair(x14_loadedFrme, \"textpane_gba\");\n  } else {\n    x1c_gbaPair = FindTextPanePair(x14_loadedFrme, \"textpane_lang\");\n  }\n  x1c_gbaPair.SetPairText(g_MainStringTable->GetString(37));\n  // TODO: HACK: Implement language menu so this isn't necessary\n  if (g_Main->IsUSA() && !g_Main->IsTrilogy()) {\n    x24_cheatPair = FindTextPanePair(x14_loadedFrme, \"textpane_cheats\");\n  } else {\n    x24_cheatPair = FindTextPanePair(x14_loadedFrme, \"textpane_options\");\n  }\n  x24_cheatPair.SetPairText(g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 96 : 90));\n\n  FindAndSetPairText(x14_loadedFrme, \"textpane_start\",\n                     g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 67 : 61));\n  // TODO: HACK: Implement language menu so this isn't necessary\n  FindAndSetPairText(x14_loadedFrme, (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? \"textpane_options\" : \"textpane_gba\",\n                     g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 94 : 88));\n  FindAndSetPairText(x14_loadedFrme, \"textpane_title\",\n                     g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 98 : 92));\n\n  CGuiTextPane* proceed = static_cast<CGuiTextPane*>(x14_loadedFrme->FindWidget(\"textpane_proceed\"));\n  if (proceed)\n    proceed->TextSupport().SetText(g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 85 : 79));\n\n  x18_tablegroup_mainmenu->SetMenuAdvanceCallback([this](CGuiTableGroup* caller) { DoAdvance(caller); });\n  x18_tablegroup_mainmenu->SetMenuSelectionChangeCallback(\n      [this](CGuiTableGroup* caller, int oldSel) { DoSelectionChange(caller, oldSel); });\n  x18_tablegroup_mainmenu->SetMenuCancelCallback([this](CGuiTableGroup* caller) { DoCancel(caller); });\n\n  HandleActiveChange(x18_tablegroup_mainmenu);\n}\n\nbool CFrontEndUI::SFrontEndFrame::PumpLoad() {\n  if (x14_loadedFrme)\n    return true;\n  if (x8_frme.IsLoaded()) {\n    if (CGuiFrame* frme = x8_frme.GetObj()) {\n      if (frme->GetIsFinishedLoading()) {\n        x14_loadedFrme = frme;\n        FinishedLoading();\n        return true;\n      }\n    }\n  }\n  return false;\n}\n\nvoid CFrontEndUI::SFrontEndFrame::Update(float dt) {\n  CGuiTextPane* imageGallery = static_cast<CGuiTextPane*>(x18_tablegroup_mainmenu->GetWorkerWidget(3));\n\n  if (CSlideShow::SlideShowGalleryFlags()) {\n    imageGallery->SetIsSelectable(true);\n    x24_cheatPair.x0_panes[0]->TextSupport().SetFontColor(zeus::skWhite);\n  } else {\n    imageGallery->SetIsSelectable(false);\n    zeus::CColor color = zeus::skGrey;\n    color.a() = 0.5f;\n    x24_cheatPair.x0_panes[0]->TextSupport().SetFontColor(color);\n  }\n\n  x14_loadedFrme->Update(dt);\n}\n\nCFrontEndUI::SFrontEndFrame::EAction CFrontEndUI::SFrontEndFrame::ProcessUserInput(const CFinalInput& input) {\n  x4_action = EAction::None;\n  x14_loadedFrme->ProcessUserInput(input);\n  return x4_action;\n}\n\nvoid CFrontEndUI::SFrontEndFrame::Draw() const { x14_loadedFrme->Draw(CGuiWidgetDrawParms::Default()); }\n\nvoid CFrontEndUI::SFrontEndFrame::HandleActiveChange(CGuiTableGroup* active) {\n  active->SetColors(zeus::skWhite, zeus::CColor{0.627450f, 0.627450f, 0.627450f, 0.784313f});\n}\n\nvoid CFrontEndUI::SFrontEndFrame::DoCancel(CGuiTableGroup* caller) { /* Intentionally empty */ }\n\nvoid CFrontEndUI::SFrontEndFrame::DoSelectionChange(CGuiTableGroup* caller, int oldSel) {\n  CSfxManager::SfxStart(SFXfnt_selection_change, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n  HandleActiveChange(caller);\n}\n\nvoid CFrontEndUI::SFrontEndFrame::DoAdvance(CGuiTableGroup* caller) {\n  switch (x18_tablegroup_mainmenu->GetUserSelection()) {\n  case 0:\n    CSfxManager::SfxStart(FETransitionForwardSFX[x0_rnd][0], 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n    CSfxManager::SfxStart(FETransitionForwardSFX[x0_rnd][1], 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n    x4_action = EAction::StartGame;\n    break;\n  case 1:\n    x4_action = EAction::FusionBonus;\n    break;\n  case 2:\n    PlayAdvanceSfx();\n    x4_action = EAction::GameOptions;\n    break;\n  case 3:\n    PlayAdvanceSfx();\n    x4_action = EAction::SlideShow;\n    break;\n  default:\n    break;\n  }\n}\n\nCFrontEndUI::SFrontEndFrame::SFrontEndFrame(u32 rnd) : x0_rnd(rnd) {\n  x8_frme = g_SimplePool->GetObj(\"FRME_FrontEndPL\");\n}\n\nCFrontEndUI::SNesEmulatorFrame::SNesEmulatorFrame() {\n  x4_nesEmu = std::make_unique<CNESEmulator>();\n\n  const SObjectTag* deface = g_ResFactory->GetResourceIdByName(\"FONT_Deface14B\");\n  CGuiTextProperties props(false, true, EJustification::Left, EVerticalJustification::Center);\n  xc_textSupport = std::make_unique<CGuiTextSupport>(deface->id, props, zeus::skWhite, zeus::skBlack, zeus::skWhite, 0,\n                                                     0, g_SimplePool, CGuiWidget::EGuiModelDrawFlags::Alpha);\n  xc_textSupport->SetText(g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 103 : 97));\n  xc_textSupport->AutoSetExtent();\n  xc_textSupport->ClearRenderBuffer();\n}\n\nvoid CFrontEndUI::SNesEmulatorFrame::SetMode(EMode mode) {\n  switch (mode) {\n  case EMode::Emulator:\n    x8_quitScreen.reset();\n    break;\n  case EMode::SaveProgress:\n    x8_quitScreen = std::make_unique<CQuitGameScreen>(EQuitType::SaveProgress);\n    break;\n  case EMode::ContinuePlaying:\n    x8_quitScreen = std::make_unique<CQuitGameScreen>(EQuitType::ContinuePlaying);\n    break;\n  case EMode::QuitNESMetroid:\n    x8_quitScreen = std::make_unique<CQuitGameScreen>(EQuitType::QuitNESMetroid);\n    break;\n  default:\n    break;\n  }\n  x0_mode = mode;\n}\n\nvoid CFrontEndUI::SNesEmulatorFrame::ProcessUserInput(const CFinalInput& input, CSaveGameScreen* sui) {\n  bool processInput = true;\n  if (sui && sui->GetUIType() != CSaveGameScreen::EUIType::SaveReady)\n    processInput = false;\n  if (sui)\n    sui->ProcessUserInput(input);\n  if (!processInput)\n    return;\n\n  switch (x0_mode) {\n  case EMode::Emulator:\n    x4_nesEmu->ProcessUserInput(input, 4);\n    if ((input.ControllerIdx() == 0 && input.PL()) || input.PSpecialKey(ESpecialKey::Esc))\n      SetMode(EMode::QuitNESMetroid);\n    break;\n  case EMode::SaveProgress:\n  case EMode::ContinuePlaying:\n  case EMode::QuitNESMetroid:\n    x8_quitScreen->ProcessUserInput(input);\n    break;\n  default:\n    break;\n  }\n}\n\nbool CFrontEndUI::SNesEmulatorFrame::Update(float dt, CSaveGameScreen* saveUi) {\n  bool doUpdate = (saveUi && saveUi->GetUIType() != CSaveGameScreen::EUIType::SaveReady) ? false : true;\n  x10_remTime = std::max(x10_remTime - dt, 0.f);\n\n  zeus::CColor geomCol(zeus::skWhite);\n  geomCol.a() = std::min(x10_remTime, 1.f);\n  xc_textSupport->SetGeometryColor(geomCol);\n  if (xc_textSupport->GetIsTextSupportFinishedLoading()) {\n    xc_textSupport->AutoSetExtent();\n    xc_textSupport->ClearRenderBuffer();\n  }\n\n  if (!doUpdate)\n    return false;\n\n  switch (x0_mode) {\n  case EMode::Emulator: {\n    x4_nesEmu->Update();\n    if (!x4_nesEmu->IsGameOver())\n      x14_emulationSuspended = false;\n    if (x4_nesEmu->IsGameOver() && !x14_emulationSuspended) {\n      x14_emulationSuspended = true;\n      if (saveUi && !saveUi->IsSavingDisabled()) {\n        SetMode(EMode::SaveProgress);\n        break;\n      }\n      SetMode(EMode::ContinuePlaying);\n      break;\n    }\n    if (x4_nesEmu->GetPasswordEntryState() == CNESEmulator::EPasswordEntryState::NotEntered && saveUi)\n      x4_nesEmu->LoadPassword(g_GameState->SystemOptions().GetNESState());\n    break;\n  }\n\n  case EMode::SaveProgress: {\n    if (saveUi) {\n      EQuitAction action = x8_quitScreen->Update(dt);\n      if (action == EQuitAction::Yes) {\n        memmove(g_GameState->SystemOptions().GetNESState(), x4_nesEmu->GetPassword(), 18);\n        saveUi->SaveNESState();\n        SetMode(EMode::ContinuePlaying);\n      } else if (action == EQuitAction::No)\n        SetMode(EMode::ContinuePlaying);\n    } else\n      SetMode(EMode::ContinuePlaying);\n    break;\n  }\n\n  case EMode::ContinuePlaying: {\n    EQuitAction action = x8_quitScreen->Update(dt);\n    if (action == EQuitAction::Yes)\n      SetMode(EMode::Emulator);\n    else if (action == EQuitAction::No)\n      return true;\n    break;\n  }\n\n  case EMode::QuitNESMetroid: {\n    EQuitAction action = x8_quitScreen->Update(dt);\n    if (action == EQuitAction::Yes)\n      return true;\n    else if (action == EQuitAction::No)\n      SetMode(EMode::Emulator);\n    break;\n  }\n\n  default:\n    break;\n  }\n\n  return false;\n}\n\nvoid CFrontEndUI::SNesEmulatorFrame::Draw(CSaveGameScreen* saveUi) const {\n  zeus::CColor mulColor = zeus::skWhite;\n  bool blackout = saveUi && saveUi->GetUIType() != CSaveGameScreen::EUIType::SaveReady;\n\n  if (blackout)\n    mulColor = zeus::skBlack;\n  else if (x8_quitScreen)\n    mulColor = zeus::CColor{0.376470f, 0.376470f, 0.376470f, 1.f};\n\n  x4_nesEmu->Draw(mulColor, x15_enableFiltering);\n  if (!blackout && x8_quitScreen)\n    x8_quitScreen->Draw();\n\n  if (x10_remTime >= 7.5f)\n    return;\n  if (x10_remTime <= 0.f)\n    return;\n  if (xc_textSupport->GetIsTextSupportFinishedLoading()) {\n    float aspect = CGraphics::GetViewportAspect() / 1.33f;\n    CGraphics::SetOrtho(-320.f * aspect, 320.f * aspect, 240.f, -240.f, -4096.f, 4096.f);\n    CGraphics::SetViewPointMatrix(zeus::CTransform());\n    CGraphics::SetModelMatrix(zeus::CTransform::Translate(-220.f, 0.f, -200.f));\n    xc_textSupport->Render();\n  }\n}\n\nCFrontEndUI::SOptionsFrontEndFrame::SOptionsFrontEndFrame() {\n  x4_frme = g_SimplePool->GetObj(\"FRME_OptionsFrontEnd\");\n  x10_pauseScreen = g_SimplePool->GetObj(\"STRG_PauseScreen\");\n}\n\nvoid CFrontEndUI::SOptionsFrontEndFrame::DoSliderChange(CGuiSliderGroup* caller, float value) {\n  if (x28_tablegroup_rightmenu->GetIsActive()) {\n    int leftSel = x24_tablegroup_leftmenu->GetUserSelection();\n    int rightSel = x28_tablegroup_rightmenu->GetUserSelection();\n    const auto& optionCategory = GameOptionsRegistry[leftSel];\n    const SGameOption& option = optionCategory.second[rightSel];\n    CGameOptions::SetOption(option.option, caller->GetGurVal());\n  }\n}\n\nvoid CFrontEndUI::SOptionsFrontEndFrame::DoMenuCancel(CGuiTableGroup* caller) {\n  if (x28_tablegroup_rightmenu == caller) {\n    DeactivateRightMenu();\n    x24_tablegroup_leftmenu->SetIsActive(true);\n    x28_tablegroup_rightmenu->SetIsActive(false);\n    x28_tablegroup_rightmenu->SetUserSelection(0);\n    SetTableColors(x28_tablegroup_rightmenu);\n    CSfxManager::SfxStart(SFXfnt_back, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n  }\n}\n\nvoid CFrontEndUI::SOptionsFrontEndFrame::DoMenuSelectionChange(CGuiTableGroup* caller, int oldSel) {\n  SetTableColors(caller);\n  if (x24_tablegroup_leftmenu == caller) {\n    SetRightUIText();\n    CSfxManager::SfxStart(SFXfnt_selection_change, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n  } else if (x28_tablegroup_rightmenu == caller) {\n    HandleRightSelectionChange();\n    CSfxManager::SfxStart(SFXfnt_selection_change, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n  } else if (x2c_tablegroup_double == caller || x30_tablegroup_triple == caller) {\n    if (x28_tablegroup_rightmenu->GetIsActive()) {\n      int leftSel = x24_tablegroup_leftmenu->GetUserSelection();\n      int rightSel = x28_tablegroup_rightmenu->GetUserSelection();\n      const auto& optionCategory = GameOptionsRegistry[leftSel];\n      const SGameOption& option = optionCategory.second[rightSel];\n      CGameOptions::SetOption(option.option, caller->GetUserSelection());\n      CSfxManager::SfxStart(SFXfnt_enum_change, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n\n      if (option.option == EGameOption::Rumble && caller->GetUserSelection() > 0) {\n        x40_rumbleGen.HardStopAll();\n        x40_rumbleGen.Rumble(RumbleFxTable[size_t(ERumbleFxId::PlayerBump)], 1.f, ERumblePriority::One,\n                             EIOPort::Player1);\n      }\n    }\n  }\n}\n\nvoid CFrontEndUI::SOptionsFrontEndFrame::DoLeftMenuAdvance(CGuiTableGroup* caller) {\n  if (caller == x24_tablegroup_leftmenu) {\n    HandleRightSelectionChange();\n    x28_tablegroup_rightmenu->SetUserSelection(0);\n    x24_tablegroup_leftmenu->SetIsActive(false);\n    x28_tablegroup_rightmenu->SetIsActive(true);\n    CSfxManager::SfxStart(SFXfnt_advance_L, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n    CSfxManager::SfxStart(SFXfnt_advance_R, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n  }\n}\n\nvoid CFrontEndUI::SOptionsFrontEndFrame::DeactivateRightMenu() {\n  x2c_tablegroup_double->SetIsActive(false);\n  x30_tablegroup_triple->SetIsActive(false);\n  x34_slidergroup_slider->SetIsActive(false);\n  x2c_tablegroup_double->SetVisibility(false, ETraversalMode::Children);\n  x30_tablegroup_triple->SetVisibility(false, ETraversalMode::Children);\n  x34_slidergroup_slider->SetVisibility(false, ETraversalMode::Children);\n}\n\nvoid CFrontEndUI::SOptionsFrontEndFrame::HandleRightSelectionChange() {\n  DeactivateRightMenu();\n  int leftSel = x24_tablegroup_leftmenu->GetUserSelection();\n  int rightSel = x28_tablegroup_rightmenu->GetUserSelection();\n  const auto& optionCategory = GameOptionsRegistry[leftSel];\n  const SGameOption& option = optionCategory.second[rightSel];\n\n  switch (option.type) {\n  case EOptionType::Float:\n    x34_slidergroup_slider->SetIsActive(true);\n    x34_slidergroup_slider->SetVisibility(true, ETraversalMode::Children);\n    x34_slidergroup_slider->SetMinVal(option.minVal);\n    x34_slidergroup_slider->SetMaxVal(option.maxVal);\n    x34_slidergroup_slider->SetIncrement(option.increment);\n    x34_slidergroup_slider->SetCurVal(CGameOptions::GetOption(option.option));\n    x34_slidergroup_slider->SetLocalTransform(zeus::CTransform::Translate(0.f, 0.f, rightSel * x38_rowPitch) *\n                                              x34_slidergroup_slider->GetTransform());\n    break;\n\n  case EOptionType::DoubleEnum:\n    x2c_tablegroup_double->SetUserSelection(CGameOptions::GetOption(option.option));\n    x2c_tablegroup_double->SetIsVisible(true);\n    x2c_tablegroup_double->SetIsActive(true);\n    x2c_tablegroup_double->SetLocalTransform(zeus::CTransform::Translate(0.f, 0.f, rightSel * x38_rowPitch) *\n                                             x2c_tablegroup_double->GetTransform());\n    SetTableColors(x2c_tablegroup_double);\n    break;\n\n  case EOptionType::TripleEnum:\n    x30_tablegroup_triple->SetUserSelection(CGameOptions::GetOption(option.option));\n    x30_tablegroup_triple->SetIsVisible(true);\n    x30_tablegroup_triple->SetIsActive(true);\n    x30_tablegroup_triple->SetLocalTransform(zeus::CTransform::Translate(0.f, 0.f, rightSel * x38_rowPitch) *\n                                             x30_tablegroup_triple->GetTransform());\n    SetTableColors(x30_tablegroup_triple);\n    break;\n\n  default:\n    break;\n  }\n}\n\nvoid CFrontEndUI::SOptionsFrontEndFrame::SetRightUIText() {\n  const int userSel = x24_tablegroup_leftmenu->GetUserSelection();\n  const auto& options =\n      (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? GameOptionsRegistry[userSel] : GameOptionsRegistryNew[userSel];\n\n  for (int i = 0; i < 5; ++i) {\n    std::string name = fmt::format(\"textpane_right{}\", i);\n    if (i < static_cast<int>(options.first)) {\n      FindTextPanePair(x1c_loadedFrame, name).SetPairText(x20_loadedPauseStrg->GetString(options.second[i].stringId));\n      x28_tablegroup_rightmenu->GetWorkerWidget(i)->SetIsSelectable(true);\n    } else {\n      FindTextPanePair(x1c_loadedFrame, name).SetPairText(u\"\");\n      x28_tablegroup_rightmenu->GetWorkerWidget(i)->SetIsSelectable(false);\n    }\n  }\n}\n\nvoid CFrontEndUI::SOptionsFrontEndFrame::SetTableColors(CGuiTableGroup* tbgp) const {\n  tbgp->SetColors(zeus::skWhite, zeus::CColor{0.627450f, 0.627450f, 0.627450f, 0.784313f});\n}\n\nvoid CFrontEndUI::SOptionsFrontEndFrame::FinishedLoading() {\n  x1c_loadedFrame->SetAspectConstraint(1.78f);\n\n  x24_tablegroup_leftmenu = static_cast<CGuiTableGroup*>(x1c_loadedFrame->FindWidget(\"tablegroup_leftmenu\"));\n  x28_tablegroup_rightmenu = static_cast<CGuiTableGroup*>(x1c_loadedFrame->FindWidget(\"tablegroup_rightmenu\"));\n  x2c_tablegroup_double = static_cast<CGuiTableGroup*>(x1c_loadedFrame->FindWidget(\"tablegroup_double\"));\n  x30_tablegroup_triple = static_cast<CGuiTableGroup*>(x1c_loadedFrame->FindWidget(\"tablegroup_triple\"));\n  x34_slidergroup_slider = static_cast<CGuiSliderGroup*>(x1c_loadedFrame->FindWidget(\"slidergroup_slider\"));\n\n  x24_tablegroup_leftmenu->SetMenuAdvanceCallback([this](CGuiTableGroup* caller) { DoLeftMenuAdvance(caller); });\n  x24_tablegroup_leftmenu->SetMenuSelectionChangeCallback(\n      [this](CGuiTableGroup* caller, int oldSel) { DoMenuSelectionChange(caller, oldSel); });\n\n  x38_rowPitch = x24_tablegroup_leftmenu->GetWorkerWidget(1)->GetIdlePosition().z() -\n                 x24_tablegroup_leftmenu->GetWorkerWidget(0)->GetIdlePosition().z();\n\n  x28_tablegroup_rightmenu->SetMenuSelectionChangeCallback(\n      [this](CGuiTableGroup* caller, int oldSel) { DoMenuSelectionChange(caller, oldSel); });\n  x28_tablegroup_rightmenu->SetMenuCancelCallback([this](CGuiTableGroup* caller) { DoMenuCancel(caller); });\n\n  x2c_tablegroup_double->SetMenuSelectionChangeCallback(\n      [this](CGuiTableGroup* caller, int oldSel) { DoMenuSelectionChange(caller, oldSel); });\n  x2c_tablegroup_double->SetMenuCancelCallback([this](CGuiTableGroup* caller) { DoMenuCancel(caller); });\n\n  x30_tablegroup_triple->SetMenuSelectionChangeCallback(\n      [this](CGuiTableGroup* caller, int oldSel) { DoMenuSelectionChange(caller, oldSel); });\n  x30_tablegroup_triple->SetMenuCancelCallback([this](CGuiTableGroup* caller) { DoMenuCancel(caller); });\n\n  x34_slidergroup_slider->SetSelectionChangedCallback(\n      [this](CGuiSliderGroup* caller, float value) { DoSliderChange(caller, value); });\n\n  FindTextPanePair(x1c_loadedFrame, \"textpane_double0\")\n      .SetPairText(x20_loadedPauseStrg->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 95 : 98)); // Off\n  FindTextPanePair(x1c_loadedFrame, \"textpane_double1\")\n      .SetPairText(x20_loadedPauseStrg->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 94 : 97)); // On\n  FindTextPanePair(x1c_loadedFrame, \"textpane_triple0\")\n      .SetPairText(x20_loadedPauseStrg->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 96 : 99)); // Mono\n  FindTextPanePair(x1c_loadedFrame, \"textpane_triple1\")\n      .SetPairText(x20_loadedPauseStrg->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 97 : 100)); // Stereo\n  FindTextPanePair(x1c_loadedFrame, \"textpane_triple2\")\n      .SetPairText(x20_loadedPauseStrg->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 98 : 101)); // Dolby\n\n  FindTextPanePair(x1c_loadedFrame, \"textpane_title\")\n      .SetPairText(g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 99 : 93)); // OPTIONS\n\n  if (CGuiTextPane* proceed = static_cast<CGuiTextPane*>(x1c_loadedFrame->FindWidget(\"textpane_proceed\")))\n    proceed->TextSupport().SetText(g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 85 : 79));\n\n  if (CGuiTextPane* cancel = static_cast<CGuiTextPane*>(x1c_loadedFrame->FindWidget(\"textpane_cancel\")))\n    cancel->TextSupport().SetText(g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 82 : 76));\n\n  // Visor, Display, Sound, Controller\n  for (int i = 0; i < 4; ++i) {\n    const std::string name = fmt::format(\"textpane_filename{}\", i);\n    FindTextPanePair(x1c_loadedFrame, name)\n        .SetPairText(x20_loadedPauseStrg->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy() ? 16 : 18) + i));\n  }\n\n  x2c_tablegroup_double->SetVertical(false);\n  x30_tablegroup_triple->SetVertical(false);\n\n  x24_tablegroup_leftmenu->SetIsActive(true);\n  x28_tablegroup_rightmenu->SetIsActive(false);\n\n  SetTableColors(x24_tablegroup_leftmenu);\n  SetTableColors(x28_tablegroup_rightmenu);\n  SetTableColors(x2c_tablegroup_double);\n  SetTableColors(x30_tablegroup_triple);\n\n  SetRightUIText();\n  DeactivateRightMenu();\n}\n\nbool CFrontEndUI::SOptionsFrontEndFrame::PumpLoad() {\n  if (x1c_loadedFrame)\n    return true;\n  if (!x4_frme.IsLoaded())\n    return false;\n  if (!x10_pauseScreen.IsLoaded())\n    return false;\n  if (!x4_frme->GetIsFinishedLoading())\n    return false;\n  x1c_loadedFrame = x4_frme.GetObj();\n  x20_loadedPauseStrg = x10_pauseScreen.GetObj();\n  FinishedLoading();\n  return true;\n}\n\nbool CFrontEndUI::SOptionsFrontEndFrame::ProcessUserInput(const CFinalInput& input, CSaveGameScreen* sui) {\n  x134_25_exitOptions = false;\n  if (sui)\n    sui->ProcessUserInput(input);\n  if (x1c_loadedFrame && x134_24_visible) {\n    if ((input.PB() || input.PSpecialKey(ESpecialKey::Esc)) && x24_tablegroup_leftmenu->GetIsActive()) {\n      x134_25_exitOptions = true;\n      CSfxManager::SfxStart(SFXfnt_back, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n    } else {\n      x1c_loadedFrame->ProcessUserInput(input);\n      int leftSel = x24_tablegroup_leftmenu->GetUserSelection();\n      int rightSel = x28_tablegroup_rightmenu->GetUserSelection();\n      CGameOptions::TryRestoreDefaults(input, leftSel, rightSel, true, false);\n    }\n  }\n  return !x134_25_exitOptions;\n}\n\nvoid CFrontEndUI::SOptionsFrontEndFrame::Update(float dt, CSaveGameScreen* sui) {\n  x40_rumbleGen.Update(dt);\n  x134_24_visible = !sui || sui->GetUIType() == CSaveGameScreen::EUIType::SaveReady;\n\n  if (!PumpLoad())\n    return;\n\n  x0_uiAlpha = std::min(1.f, x0_uiAlpha + dt);\n  x1c_loadedFrame->Update(dt);\n\n  bool isSliding = x34_slidergroup_slider->GetState() != CGuiSliderGroup::EState::None;\n  if (x3c_sliderSfx.operator bool() != isSliding) {\n    if (isSliding) {\n      x3c_sliderSfx =\n          CSfxManager::SfxStart(SFXui_frontend_options_slider_change_lp, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n    } else {\n      CSfxManager::SfxStop(x3c_sliderSfx);\n      x3c_sliderSfx.reset();\n    }\n  }\n}\n\nvoid CFrontEndUI::SOptionsFrontEndFrame::Draw() const {\n  if (x1c_loadedFrame && x134_24_visible) {\n    CGuiWidgetDrawParms params(x0_uiAlpha, zeus::skZero3f);\n    x1c_loadedFrame->Draw(params);\n  }\n}\n\nCFrontEndUI::CFrontEndUI() : CIOWin(\"FrontEndUI\") {\n  CMain* m = static_cast<CMain*>(g_Main);\n\n  CRandom16 r(time(nullptr));\n  x18_rndA = r.Range(0, 2);\n  x1c_rndB = r.Range(0, 2);\n\n  x20_depsGroup = g_SimplePool->GetObj(\"FrontEnd_DGRP\");\n  x38_pressStart = g_SimplePool->GetObj(\"TXTR_PressStart\");\n  x44_frontendAudioGrp = g_SimplePool->GetObj(\"FrontEnd_AGSC\");\n\n  xdc_saveUI = std::make_unique<CSaveGameScreen>(ESaveContext::FrontEnd, g_GameState->GetCardSerial());\n\n  m->ResetGameState();\n  g_GameState->SetCurrentWorldId(g_DefaultWorldTag.id);\n  g_GameState->GameOptions().ResetToDefaults();\n  g_GameState->WriteBackupBuf();\n\n  for (int i = 0; CDvdFile::FileExists(GetAttractMovieFileName(i)); ++i)\n    ++xc0_attractCount;\n}\n\nCFrontEndUI::~CFrontEndUI() {\n  if (x14_phase >= EPhase::DisplayFrontEnd) {\n    // CAudioSys::RemoveAudioGroup(x44_frontendAudioGrp->GetAudioGroupData());\n  }\n}\n\nvoid CFrontEndUI::StartSlideShow(CArchitectureQueue& queue) {\n  // xf4_curAudio->StopMixing();\n  queue.Push(MakeMsg::CreateCreateIOWin(EArchMsgTarget::IOWinManager, 12, 11, std::make_shared<CSlideShow>()));\n}\n\nstd::string CFrontEndUI::GetAttractMovieFileName(int idx) { return fmt::format(\"Video/attract{}.thp\", idx); }\n\nstd::string CFrontEndUI::GetNextAttractMovieFileName() {\n  std::string ret = GetAttractMovieFileName(xbc_nextAttract);\n  xbc_nextAttract = (xbc_nextAttract + 1) % xc0_attractCount;\n  return ret;\n}\n\nvoid CFrontEndUI::SetCurrentMovie(EMenuMovie movie) {\n  if (xb8_curMovie == movie) {\n    return;\n  }\n\n  StopAttractMovie();\n  if (!g_Main->IsTrilogy()) {\n    if (xb8_curMovie != EMenuMovie::Stopped) {\n      xcc_curMoviePtr->SetPlayMode(CMoviePlayer::EPlayMode::Stopped);\n      xcc_curMoviePtr->Rewind();\n    }\n\n    xb8_curMovie = movie;\n\n    if (xb8_curMovie != EMenuMovie::Stopped) {\n      xcc_curMoviePtr = x70_menuMovies[size_t(xb8_curMovie)].get();\n      xcc_curMoviePtr->SetPlayMode(CMoviePlayer::EPlayMode::Playing);\n    } else {\n      xcc_curMoviePtr = nullptr;\n    }\n  }\n}\n\nvoid CFrontEndUI::StopAttractMovie() {\n  if (!xc4_attractMovie)\n    return;\n  xc4_attractMovie.reset();\n  xcc_curMoviePtr = nullptr;\n}\n\nvoid CFrontEndUI::StartAttractMovie() {\n  if (xc4_attractMovie)\n    return;\n  SetCurrentMovie(EMenuMovie::Stopped);\n  xc4_attractMovie = std::make_unique<CMoviePlayer>(GetNextAttractMovieFileName().c_str(), 0.f, false, true);\n  xcc_curMoviePtr = xc4_attractMovie.get();\n}\n\nvoid CFrontEndUI::StartStateTransition(EScreen screen) {\n  switch (x50_curScreen) {\n  case EScreen::Title:\n    if (screen != EScreen::FileSelect)\n      break;\n    SetCurrentMovie(EMenuMovie::StartFileSelectA);\n    SetFadeBlackTimer(!g_Main->IsTrilogy() ? xcc_curMoviePtr->GetTotalSeconds() : 5);\n    break;\n  case EScreen::FileSelect:\n    if (screen == EScreen::ToPlayGame) {\n      SetCurrentMovie(EMenuMovie::FileSelectPlayGameA);\n      SetFadeBlackTimer(!g_Main->IsTrilogy() ? xcc_curMoviePtr->GetTotalSeconds() : 5);\n    } else if (screen == EScreen::FusionBonus) {\n      SetCurrentMovie(EMenuMovie::FileSelectGBA);\n      SetFadeBlackTimer(!g_Main->IsTrilogy() ? xcc_curMoviePtr->GetTotalSeconds() : 5);\n      CSfxManager::SfxStart(SFXfnt_tofusion_L, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n      CSfxManager::SfxStart(SFXfnt_tofusion_R, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n    }\n    break;\n  case EScreen::FusionBonus:\n    if (screen == EScreen::ToPlayGame) {\n      SetCurrentMovie(EMenuMovie::GBAFileSelectB);\n      SetFadeBlackTimer(!g_Main->IsTrilogy() ? xcc_curMoviePtr->GetTotalSeconds() : 5);\n    } else if (screen == EScreen::FileSelect) {\n      SetCurrentMovie(EMenuMovie::GBAFileSelectA);\n      SetFadeBlackTimer(!g_Main->IsTrilogy() ? xcc_curMoviePtr->GetTotalSeconds() : 5);\n      CSfxManager::SfxStart(SFXfnt_fromfusion_L, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n      CSfxManager::SfxStart(SFXfnt_fromfusion_R, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n    }\n    break;\n  default:\n    break;\n  }\n\n  switch (screen) {\n  case EScreen::OpenCredits:\n  case EScreen::Title:\n    SetCurrentMovie(EMenuMovie::FirstStart);\n    SetFadeBlackTimer(!g_Main->IsTrilogy() ? xcc_curMoviePtr->GetTotalSeconds() : 5);\n    break;\n  case EScreen::AttractMovie:\n    StartAttractMovie();\n    SetFadeBlackWithMovie();\n    break;\n  default:\n    break;\n  }\n\n  x54_nextScreen = screen;\n}\n\nvoid CFrontEndUI::CompleteStateTransition() {\n  EScreen oldScreen = x50_curScreen;\n  x50_curScreen = x54_nextScreen;\n\n  switch (x50_curScreen) {\n  case EScreen::AttractMovie:\n    x54_nextScreen = EScreen::OpenCredits;\n    x50_curScreen = EScreen::OpenCredits;\n    xd0_playerSkipToTitle = false;\n    StartStateTransition(EScreen::Title);\n    break;\n\n  case EScreen::Title:\n    SetCurrentMovie(EMenuMovie::StartLoop);\n    SetFadeBlackTimer(30.f);\n    break;\n\n  case EScreen::FileSelect:\n    SetCurrentMovie(EMenuMovie::FileSelectLoop);\n    if (oldScreen == EScreen::Title) {\n      // xf4_curAudio->StopMixing();\n      // xf4_curAudio = xd8_audio2.get();\n      // xf4_curAudio->StartMixing();\n    }\n    if (xdc_saveUI)\n      xdc_saveUI->ResetCardDriver();\n    break;\n\n  case EScreen::FusionBonus:\n    SetCurrentMovie(EMenuMovie::GBALoop);\n    break;\n\n  case EScreen::ToPlayGame:\n    x14_phase = EPhase::ExitFrontEnd;\n    break;\n\n  default:\n    break;\n  }\n}\n\nvoid CFrontEndUI::HandleDebugMenuReturnValue(CGameDebug::EReturnValue val, CArchitectureQueue& queue) {}\n\nvoid CFrontEndUI::Draw() {\n  if (x14_phase < EPhase::DisplayFrontEnd) {\n    return;\n  }\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CFrontEndUI::Draw\", zeus::skGreen);\n\n  if (xec_emuFrme) {\n    xec_emuFrme->Draw(xdc_saveUI.get());\n  } else {\n    if ((xcc_curMoviePtr != nullptr)) {\n      /* Render movie */\n      xcc_curMoviePtr->DrawVideo();\n    }\n\n    if (x50_curScreen == EScreen::FileSelect && x54_nextScreen == EScreen::FileSelect) {\n      /* Render active FileSelect UI */\n      if (xf0_optionsFrme)\n        xf0_optionsFrme->Draw();\n      else if (xe0_frontendCardFrme)\n        xe0_frontendCardFrme->Draw();\n      else\n        xe8_frontendNoCardFrme->Draw();\n    } else if (x50_curScreen == EScreen::FusionBonus && x54_nextScreen == EScreen::FusionBonus) {\n      /* Render Fusion bonus UI */\n      xe4_fusionBonusFrme->Draw();\n    }\n\n    if (x64_pressStartAlpha > 0.f && x38_pressStart.IsLoaded()) {\n      /* Render \"Press Start\" */\n      CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvModulate);\n      CGraphics::SetTevOp(ERglTevStage::Stage1, CTevCombiners::kEnvPassthru);\n      g_Renderer->SetBlendMode_AdditiveAlpha();\n      g_Renderer->SetDepthReadWrite(false, false);\n      const int width = x38_pressStart->GetWidth();\n      const int height = x38_pressStart->GetHeight();\n      CGraphics::Render2D(*x38_pressStart, 320 - (width / 2), 72 - (height / 2), width,\n                          height, zeus::CColor{1.f, x64_pressStartAlpha}, true);\n    }\n\n    if (xc0_attractCount > 0) {\n      /* Render fade-to-black into attract movie */\n      if (((x50_curScreen == EScreen::Title && x54_nextScreen == EScreen::Title) ||\n           x54_nextScreen == EScreen::AttractMovie) &&\n          x58_fadeBlackTimer < 1.f) {\n        /* To black */\n        zeus::CColor color = zeus::skBlack;\n        color.a() = 1.f - x58_fadeBlackTimer;\n        CCameraFilterPass::DrawFilter(EFilterType::Blend, EFilterShape::Fullscreen, color, nullptr, 1.f);\n      }\n    }\n\n    if (xd0_playerSkipToTitle) {\n      /* Render fade-through-black into title if player skips */\n      if (x50_curScreen == EScreen::OpenCredits && x54_nextScreen == EScreen::Title) {\n        /* To black */\n        zeus::CColor color = zeus::skBlack;\n        color.a() = zeus::clamp(0.f, 1.f - x58_fadeBlackTimer, 1.f);\n        CCameraFilterPass::DrawFilter(EFilterType::Blend, EFilterShape::Fullscreen, color, nullptr, 1.f);\n      } else if (x50_curScreen == EScreen::Title && x54_nextScreen == EScreen::Title) {\n        /* From black with 30-sec skip to title */\n        zeus::CColor color = zeus::skBlack;\n        color.a() = 1.f - zeus::clamp(0.f, 30.f - x58_fadeBlackTimer, 1.f);\n        CCameraFilterPass::DrawFilter(EFilterType::Blend, EFilterShape::Fullscreen, color, nullptr, 1.f);\n      }\n    }\n\n    if (xdc_saveUI) {\n      /* Render memory card feedback strings */\n      if ((CanShowSaveUI() && !xdc_saveUI->IsHiddenFromFrontEnd()) ||\n          ((x50_curScreen == EScreen::FileSelect && x54_nextScreen == EScreen::FileSelect) ||\n           (x50_curScreen == EScreen::FusionBonus && x54_nextScreen == EScreen::FusionBonus)))\n        xdc_saveUI->Draw();\n    }\n  }\n}\n\nvoid CFrontEndUI::UpdateMovies(float dt) {\n  if (xcc_curMoviePtr && x5c_fadeBlackWithMovie) {\n    /* Set fade-to-black timer to match attract movie */\n    x5c_fadeBlackWithMovie = false;\n    x58_fadeBlackTimer = xcc_curMoviePtr->GetTotalSeconds();\n  }\n\n  /* Advance playing menu movies */\n  for (auto& movie : x70_menuMovies)\n    if (movie)\n      movie->Update(dt);\n\n  /* Advance attract movie */\n  if (xc4_attractMovie)\n    xc4_attractMovie->Update(dt);\n}\n\nvoid CFrontEndUI::FinishedLoadingDepsGroup() {\n  /* Transfer DGRP tokens into FrontEnd and lock */\n  const CDependencyGroup* dgrp = x20_depsGroup.GetObj();\n  x2c_deps.reserve(dgrp->GetObjectTagVector().size());\n  for (const SObjectTag& tag : dgrp->GetObjectTagVector()) {\n    x2c_deps.push_back(g_SimplePool->GetObj(tag));\n    x2c_deps.back().Lock();\n  }\n  x44_frontendAudioGrp.Lock();\n}\n\nbool CFrontEndUI::PumpLoad() {\n  /* Poll all tokens for load completion */\n  for (CToken& tok : x2c_deps)\n    if (!tok.IsLoaded())\n      return false;\n  if (!x44_frontendAudioGrp.IsLoaded())\n    return false;\n\n  return true;\n}\n\nbool CFrontEndUI::PumpMovieLoad() {\n  /* Prepare all FrontEnd movies and pause each */\n  if (xd1_moviesLoaded) {\n    return true;\n  }\n  if (g_Main->IsTrilogy()) {\n    return true;\n  }\n\n  for (size_t i = 0; i < x70_menuMovies.size(); ++i) {\n    if (x70_menuMovies[i] != nullptr) {\n      continue;\n    }\n\n    const FEMovie& movie = FEMovies[i];\n    std::string path = movie.path;\n    if (i == size_t(EMenuMovie::StartFileSelectA)) {\n      const auto pos = path.find(\"A.thp\");\n      if (pos != std::string::npos) {\n        path[pos] = 'A' + x18_rndA;\n      }\n    } else if (i == size_t(EMenuMovie::FileSelectPlayGameA)) {\n      const auto pos = path.find(\"A.thp\");\n      if (pos != std::string::npos) {\n        path[pos] = 'A' + x1c_rndB;\n      }\n    }\n\n    if (CDvdFile::FileExists(path)) {\n      x70_menuMovies[i] = std::make_unique<CMoviePlayer>(path.c_str(), 0.05f, movie.loop, false);\n      x70_menuMovies[i]->SetPlayMode(CMoviePlayer::EPlayMode::Stopped);\n    }\n    return false;\n  }\n\n  xd1_moviesLoaded = true;\n  return true;\n}\n\nvoid CFrontEndUI::ProcessUserInput(const CFinalInput& input, CArchitectureQueue& queue) {\n  if (static_cast<CMain*>(g_Main)->GetCardBusy())\n    return;\n  if (input.ControllerIdx() > 1)\n    return;\n\n  if (xec_emuFrme) {\n    /* NES emulator pre-empts user input if active */\n    xec_emuFrme->ProcessUserInput(input, xdc_saveUI.get());\n    return;\n  }\n\n  /* Controllers other than first shall not pass */\n  if (x14_phase != EPhase::DisplayFrontEnd || input.ControllerIdx() != 0)\n    return;\n\n  if (x50_curScreen != x54_nextScreen) {\n    if (x54_nextScreen == EScreen::AttractMovie &&\n        (input.PStart() || input.PA() || input.PSpecialKey(ESpecialKey::Esc) || input.PSpecialKey(ESpecialKey::Enter) ||\n         input.PMouseButton(EMouseButton::Primary))) {\n      /* Player wants to return to opening credits from attract movie */\n      SetFadeBlackTimer(std::min(1.f, x58_fadeBlackTimer));\n      PlayAdvanceSfx();\n      return;\n    }\n\n    if (input.PA() || input.PStart() || input.PSpecialKey(ESpecialKey::Esc) || input.PSpecialKey(ESpecialKey::Enter) ||\n        input.PMouseButton(EMouseButton::Primary)) {\n      if (x50_curScreen == EScreen::OpenCredits && x54_nextScreen == EScreen::Title && x58_fadeBlackTimer > 1.f) {\n        /* Player is too impatient to view opening credits */\n        xd0_playerSkipToTitle = true;\n        SetFadeBlackTimer(1.f);\n        return;\n      }\n    }\n  } else {\n    if (x50_curScreen == EScreen::Title) {\n      if (input.PStart() || input.PA() || input.PSpecialKey(ESpecialKey::Esc) ||\n          input.PSpecialKey(ESpecialKey::Enter) || input.PMouseButton(EMouseButton::Primary)) {\n        if (x58_fadeBlackTimer < 30.f - g_tweakGame->GetPressStartDelay()) {\n          /* Proceed to file select UI */\n          CSfxManager::SfxStart(FETransitionBackSFX[x18_rndA][0], 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n          CSfxManager::SfxStart(FETransitionBackSFX[x18_rndA][1], 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n          StartStateTransition(EScreen::FileSelect);\n          return;\n        }\n      }\n    } else if (x50_curScreen == EScreen::FileSelect && x54_nextScreen == EScreen::FileSelect) {\n      if (xf0_optionsFrme) {\n        /* Control options UI */\n        if (xf0_optionsFrme->ProcessUserInput(input, xdc_saveUI.get()))\n          return;\n        /* Exit options UI */\n        xf0_optionsFrme.reset();\n        return;\n      } else if (xe0_frontendCardFrme) {\n        /* Control FrontEnd with memory card */\n        switch (xe0_frontendCardFrme->ProcessUserInput(input)) {\n        case SNewFileSelectFrame::EAction::FusionBonus:\n          StartStateTransition(EScreen::FusionBonus);\n          return;\n        case SNewFileSelectFrame::EAction::GameOptions:\n          xf0_optionsFrme = std::make_unique<SOptionsFrontEndFrame>();\n          return;\n        case SNewFileSelectFrame::EAction::SlideShow:\n          xd2_deferSlideShow = true;\n          StartSlideShow(queue);\n          return;\n        default:\n          return;\n        }\n      } else {\n        /* Control FrontEnd without memory card */\n        switch (xe8_frontendNoCardFrme->ProcessUserInput(input)) {\n        case SFrontEndFrame::EAction::FusionBonus:\n          StartStateTransition(EScreen::FusionBonus);\n          return;\n        case SFrontEndFrame::EAction::GameOptions:\n          xf0_optionsFrme = std::make_unique<SOptionsFrontEndFrame>();\n          return;\n        case SFrontEndFrame::EAction::StartGame:\n          TransitionToGame();\n          return;\n        case SFrontEndFrame::EAction::SlideShow:\n          xd2_deferSlideShow = true;\n          StartSlideShow(queue);\n          return;\n        default:\n          return;\n        }\n      }\n    } else if (x50_curScreen == EScreen::FusionBonus && x54_nextScreen == EScreen::FusionBonus) {\n      /* Control Fusion bonus UI */\n      switch (xe4_fusionBonusFrme->ProcessUserInput(input, xdc_saveUI.get())) {\n      case SFusionBonusFrame::EAction::GoBack:\n        StartStateTransition(EScreen::FileSelect);\n        return;\n      case SFusionBonusFrame::EAction::PlayNESMetroid:\n        // xf4_curAudio->StopMixing();\n        xec_emuFrme = std::make_unique<SNesEmulatorFrame>();\n        if (xdc_saveUI)\n          xdc_saveUI->SetInGame(true);\n        return;\n      default:\n        return;\n      }\n    }\n  }\n}\n\nvoid CFrontEndUI::TransitionToGame() {\n  if (x14_phase >= EPhase::ToPlayGame) {\n    return;\n  }\n\n  const auto sfx = FETransitionForwardSFX[x1c_rndB];\n  CSfxManager::SfxStart(sfx[0], 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n  CSfxManager::SfxStart(sfx[1], 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n\n  x14_phase = EPhase::ToPlayGame;\n  StartStateTransition(EScreen::ToPlayGame);\n}\n\nvoid CFrontEndUI::UpdateMusicVolume() {\n  float volMul = (xf4_curAudio == xd4_audio1.get()) ? FE1_VOL : FE2_VOL;\n  if (xf4_curAudio) {\n    float vol = volMul * x68_musicVol * (g_GameState->GameOptions().GetMusicVolume() / 127.f);\n    xf4_curAudio->SetVolume(vol);\n  }\n}\n\nCIOWin::EMessageReturn CFrontEndUI::Update(float dt, CArchitectureQueue& queue) {\n  if (xdc_saveUI && x50_curScreen >= EScreen::FileSelect) {\n    switch (xdc_saveUI->Update(dt)) {\n    case EMessageReturn::Exit:\n      /* Memory card operation complete, transition to game */\n      TransitionToGame();\n      break;\n    case EMessageReturn::RemoveIOWinAndExit:\n    case EMessageReturn::RemoveIOWin:\n      /* No memory card available, fallback to non-save UI */\n      xe0_frontendCardFrme.reset();\n      xdc_saveUI.reset();\n      break;\n    default:\n      break;\n    }\n  }\n\n  /* Set music fade volume */\n  UpdateMusicVolume();\n\n  switch (x14_phase) {\n  case EPhase::LoadDepsGroup:\n    /* Poll DGRP load */\n    if (!x20_depsGroup.IsLoaded())\n      return EMessageReturn::Exit;\n    FinishedLoadingDepsGroup();\n    x20_depsGroup.Unlock();\n    x14_phase = EPhase::LoadDeps;\n    [[fallthrough]];\n\n  case EPhase::LoadDeps:\n    /* Poll loading DGRP resources */\n    if (PumpLoad()) {\n      xe0_frontendCardFrme = std::make_unique<SNewFileSelectFrame>(xdc_saveUI.get(), x1c_rndB);\n      if (!g_Main->IsTrilogy()) {\n        xe4_fusionBonusFrme = std::make_unique<SFusionBonusFrame>();\n      }\n      xe8_frontendNoCardFrme = std::make_unique<SFrontEndFrame>(x1c_rndB);\n      x38_pressStart.GetObj();\n      // CAudioSys::AddAudioGroup(x44_frontendAudioGrp->GetAudioGroupData());\n      //      xd4_audio1 = std::make_unique<CStaticAudioPlayer>(\"Audio/frontend_1.rsf\", 416480, 1973664);\n      //      xd8_audio2 = std::make_unique<CStaticAudioPlayer>(\"Audio/frontend_2.rsf\", 273556, 1636980);\n      x14_phase = EPhase::LoadFrames;\n    }\n    if (x14_phase == EPhase::LoadDeps)\n      return EMessageReturn::Exit;\n    [[fallthrough]];\n\n  case EPhase::LoadFrames:\n    if (!g_Main->IsTrilogy() && !xe4_fusionBonusFrme->PumpLoad()) {\n      return EMessageReturn::Exit;\n    }\n    /* Poll loading music and FRME resources */\n    if (/*!xd4_audio1->IsReady() || !xd8_audio2->IsReady() || */ !xe0_frontendCardFrme->PumpLoad() ||\n        !xe8_frontendNoCardFrme->PumpLoad() || !xdc_saveUI->PumpLoad())\n      return EMessageReturn::Exit;\n    //    xf4_curAudio = xd4_audio1.get();\n    //    xf4_curAudio->StartMixing();\n    x14_phase = EPhase::LoadMovies;\n    [[fallthrough]];\n\n  case EPhase::LoadMovies: {\n    /* Poll loading movies */\n    bool moviesReady = true;\n    if (PumpMovieLoad()) {\n      /* Prime first frame of movies */\n      UpdateMovies(dt);\n\n      if (!g_Main->IsTrilogy()) {\n        moviesReady = std::all_of(x70_menuMovies.cbegin(), x70_menuMovies.cend(),\n                                  [](const auto& movie) { return movie->GetIsFullyCached(); });\n      } else {\n        moviesReady = true;\n      }\n    } else {\n      moviesReady = false;\n    }\n\n    if (moviesReady) {\n      /* Ready to display FrontEnd */\n      x14_phase = EPhase::DisplayFrontEnd;\n      StartStateTransition(EScreen::Title);\n    } else {\n      return EMessageReturn::Exit;\n    }\n    [[fallthrough]];\n  }\n\n  case EPhase::DisplayFrontEnd:\n  case EPhase::ToPlayGame:\n    /* Displaying frontend to user */\n    if (xec_emuFrme) {\n      /* Update just the emulator if active */\n      if (xec_emuFrme->Update(dt, xdc_saveUI.get())) {\n        /* Exit emulator */\n        xec_emuFrme.reset();\n        if (xdc_saveUI)\n          xdc_saveUI->SetInGame(false);\n        // xf4_curAudio->StartMixing();\n      }\n      break;\n    }\n\n    if (xd2_deferSlideShow) {\n      /* Start mixing slideshow music */\n      xd2_deferSlideShow = false;\n      // xf4_curAudio->StartMixing();\n      if (xdc_saveUI)\n        xdc_saveUI->ResetCardDriver();\n    }\n\n    if (x50_curScreen == EScreen::FileSelect && x54_nextScreen == EScreen::FileSelect) {\n      /* Main FrontEnd UI tree active */\n      if (xf0_optionsFrme) {\n        bool optionsActive = true;\n        if (xdc_saveUI && xdc_saveUI->GetUIType() != CSaveGameScreen::EUIType::SaveReady)\n          optionsActive = false;\n\n        if (optionsActive) {\n          /* Update options UI */\n          xf0_optionsFrme->Update(dt, xdc_saveUI.get());\n        } else {\n          /* Save triggered; exit options UI here */\n          xf0_optionsFrme.reset();\n        }\n      } else if (xe0_frontendCardFrme) {\n        /* Update FrontEnd with memory card UI */\n        xe0_frontendCardFrme->Update(dt);\n      } else {\n        /* Update FrontEnd without memory card UI */\n        xe8_frontendNoCardFrme->Update(dt);\n      }\n    } else if (x50_curScreen == EScreen::FusionBonus && x54_nextScreen == EScreen::FusionBonus) {\n      /* Update Fusion bonus UI */\n      xe4_fusionBonusFrme->Update(dt, xdc_saveUI.get());\n    }\n\n    if (x50_curScreen != x54_nextScreen && xcc_curMoviePtr &&\n        (xcc_curMoviePtr->GetIsMovieFinishedPlaying() || xcc_curMoviePtr->IsLooping())) {\n      /* Movie-based transition complete */\n      CompleteStateTransition();\n    }\n\n    if (x58_fadeBlackTimer > 0.f && !x5c_fadeBlackWithMovie) {\n      SetFadeBlackTimer(std::max(0.f, x58_fadeBlackTimer - dt));\n      if (x58_fadeBlackTimer == 0.f) {\n        if (x50_curScreen == EScreen::Title && x54_nextScreen == EScreen::Title) {\n          if (xc0_attractCount > 0) {\n            /* Screen black, start attract movie */\n            StartStateTransition(EScreen::AttractMovie);\n          }\n        } else if (x54_nextScreen == EScreen::AttractMovie) {\n          /* Attract movie done, play open credits again */\n          CompleteStateTransition();\n        } else if (x50_curScreen != x54_nextScreen) {\n          /* Fade-based transition complete */\n          CompleteStateTransition();\n        }\n      }\n    }\n\n    /* Advance active movies */\n    UpdateMovies(dt);\n\n    if (x50_curScreen == EScreen::Title && x54_nextScreen == EScreen::Title) {\n      /* Update press-start pulsing */\n      if (x58_fadeBlackTimer < 30.f - g_tweakGame->GetPressStartDelay()) {\n        x60_pressStartTime = std::fmod(x60_pressStartTime + dt, 1.f);\n        if (x60_pressStartTime < 0.5f)\n          x64_pressStartAlpha = x60_pressStartTime / 0.5f;\n        else\n          x64_pressStartAlpha = (1.f - x60_pressStartTime) / 0.5f;\n      }\n    } else {\n      /* Clear press-start pulsing */\n      x60_pressStartTime = 0.f;\n      x64_pressStartAlpha = 0.f;\n    }\n\n    if (x50_curScreen == EScreen::Title && x54_nextScreen == EScreen::FileSelect) {\n      /* Fade out title music */\n      if (!g_Main->IsTrilogy()) {\n        x68_musicVol =\n            1.f - zeus::clamp(0.f, (xcc_curMoviePtr->GetPlayedSeconds() - AudioFadeTimeA[x18_rndA]) / 2.5f, 1.f);\n      } else {\n        x68_musicVol -= dt;\n      }\n    } else if (x54_nextScreen == EScreen::ToPlayGame) {\n      /* Fade out menu music */\n      if (!g_Main->IsTrilogy()) {\n        float delay = AudioFadeTimeB[x1c_rndB];\n        x68_musicVol =\n            1.f -\n            zeus::clamp(\n                0.f, (xcc_curMoviePtr->GetPlayedSeconds() - delay) / (xcc_curMoviePtr->GetTotalSeconds() - delay), 1.f);\n      } else {\n        x68_musicVol -= dt;\n      }\n    } else {\n      /* Full music volume */\n      x68_musicVol = 1.f;\n    }\n\n    return EMessageReturn::Exit;\n\n  case EPhase::ExitFrontEnd:\n    /* Remove FrontEnd IOWin and begin updating next IOWin */\n    return EMessageReturn::RemoveIOWin;\n\n  default:\n    break;\n  }\n\n  return EMessageReturn::Exit;\n}\n\nCIOWin::EMessageReturn CFrontEndUI::OnMessage(const CArchitectureMessage& msg, CArchitectureQueue& queue) {\n  switch (msg.GetType()) {\n  case EArchMsgType::UserInput: {\n    /* Forward user events */\n    const CArchMsgParmUserInput& input = MakeMsg::GetParmUserInput(msg);\n    ProcessUserInput(input.x4_parm, queue);\n    break;\n  }\n  case EArchMsgType::TimerTick: {\n    /* Forward frame events */\n    float dt = MakeMsg::GetParmTimerTick(msg).x4_parm;\n    return Update(dt, queue);\n  }\n  case EArchMsgType::QuitGameplay: {\n    /* Immediately exit FrontEnd */\n    x14_phase = EPhase::ExitFrontEnd;\n    break;\n  }\n  default:\n    break;\n  }\n  return EMessageReturn::Normal;\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/CFrontEndUI.hpp",
    "content": "#pragma once\n\n#include <array>\n#include <memory>\n#include <optional>\n\n#include \"Runtime/CGameDebug.hpp\"\n#include \"Runtime/CIOWin.hpp\"\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Audio/CSfxManager.hpp\"\n#include \"Runtime/Audio/CStaticAudioPlayer.hpp\"\n#include \"Runtime/Camera/CCameraFilter.hpp\"\n#include \"Runtime/GuiSys/CGuiTextSupport.hpp\"\n#include \"Runtime/Input/CRumbleGenerator.hpp\"\n#include \"Runtime/MP1/CGBASupport.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CAudioGroupSet;\nclass CDependencyGroup;\nclass CGuiFrame;\nclass CGuiModel;\nclass CGuiSliderGroup;\nclass CGuiTableGroup;\nclass CGuiTableGroup;\nclass CGuiTextPane;\nclass CGuiWidget;\nclass CMoviePlayer;\nclass CWorldSaveGameInfo;\nclass CStringTable;\nclass CTexture;\nstruct SObjectTag;\n\nnamespace MP1 {\nclass CNESEmulator;\nclass CSaveGameScreen;\nclass CQuitGameScreen;\n\nclass CFrontEndUI : public CIOWin {\npublic:\n  enum class EPhase { LoadDepsGroup, LoadDeps, LoadFrames, LoadMovies, DisplayFrontEnd, ToPlayGame, ExitFrontEnd };\n  enum class EScreen { OpenCredits, Title, AttractMovie, FileSelect, FusionBonus, ToPlayGame };\n  enum class EMenuMovie {\n    Stopped = -1,\n    FirstStart = 0,\n    StartLoop,\n    StartFileSelectA,\n    FileSelectLoop,\n    FileSelectPlayGameA,\n    FileSelectGBA,\n    GBALoop,\n    GBAFileSelectA,\n    GBAFileSelectB\n  };\n\n  static void PlayAdvanceSfx();\n\n  struct SGuiTextPair {\n    std::array<CGuiTextPane*, 2> x0_panes{};\n    void SetPairText(std::u16string_view str);\n  };\n  static SGuiTextPair FindTextPanePair(CGuiFrame* frame, std::string_view name);\n  static void FindAndSetPairText(CGuiFrame* frame, std::string_view name, std::u16string_view str);\n\n  struct SFileMenuOption {\n    CGuiWidget* x0_base;\n\n    /* filename, world, playtime, date */\n    std::array<SGuiTextPair, 4> x4_textpanes;\n\n    u32 x28_curField = 0;\n    float x2c_chRate = ComputeRandom();\n\n    static float ComputeRandom() { return rand() / float(RAND_MAX) * 30.f + 30.f; }\n  };\n\n  struct SNewFileSelectFrame {\n    enum class ESubMenu { Root, EraseGame, EraseGamePopup, NewGamePopup };\n\n    enum class EAction { None, GameOptions, FusionBonus, SlideShow };\n\n    u32 x0_rnd;\n    CSaveGameScreen* x4_saveUI;\n    ESubMenu x8_subMenu = ESubMenu::Root;\n    EAction xc_action = EAction::None;\n    TLockedToken<CGuiFrame> x10_frme;\n    CGuiFrame* x1c_loadedFrame = nullptr;\n    CGuiTableGroup* x20_tablegroup_fileselect = nullptr;\n    CGuiModel* x24_model_erase = nullptr;\n    SGuiTextPair x28_textpane_erase;\n    SGuiTextPair x30_textpane_cheats;\n    SGuiTextPair x38_textpane_gba;\n    CGuiTableGroup* x40_tablegroup_popup = nullptr;\n    CGuiModel* x44_model_dash7 = nullptr;\n    SGuiTextPair x48_textpane_popupadvance;\n    SGuiTextPair x50_textpane_popupcancel;\n    SGuiTextPair x58_textpane_popupextra;\n    CGuiTextPane* x60_textpane_cancel = nullptr;\n    std::array<SFileMenuOption, 3> x64_fileSelections;\n    zeus::CVector3f xf8_model_erase_position;\n    float x104_rowPitch = 0.f;\n    float x108_curTime = 0.f;\n    bool x10c_saveReady = false;\n    bool x10d_needsEraseToggle = false;\n    bool x10e_needsNewToggle = false;\n\n    SNewFileSelectFrame(CSaveGameScreen* sui, u32 rnd);\n    void FinishedLoading();\n    bool PumpLoad();\n    bool IsTextDoneAnimating() const;\n    void Update(float dt);\n    EAction ProcessUserInput(const CFinalInput& input);\n    void Draw() const;\n\n    void HandleActiveChange(CGuiTableGroup* active);\n    void DeactivateEraseGamePopup();\n    void ActivateEraseGamePopup();\n    void DeactivateNewGamePopup();\n    void ActivateNewGamePopup();\n\n    void ResetFrame();\n    void ActivateErase();\n    void ClearFrameContents();\n    void SetupFrameContents();\n\n    void DoPopupCancel(CGuiTableGroup* caller);\n    void DoPopupAdvance(CGuiTableGroup* caller);\n    void DoFileMenuCancel(CGuiTableGroup* caller);\n    void DoSelectionChange(CGuiTableGroup* caller, int oldSel);\n    void DoFileMenuAdvance(CGuiTableGroup* caller);\n\n    static SFileMenuOption FindFileSelectOption(CGuiFrame* frame, int idx);\n    static void StartTextAnimating(CGuiTextPane* text, std::u16string_view str, float chRate);\n  };\n\n  struct SFusionBonusFrame {\n    struct SGBALinkFrame {\n      enum class EUIType {\n        Empty = -1,\n        InsertPak = 0,\n        ConnectSocket = 1,\n        PressStartAndSelect = 2,\n        BeginLink = 3,\n        Linking = 4,\n        LinkFailed = 5,\n        LinkCompleteOrLinking = 6,\n        TurnOffGBA = 7,\n        Complete = 8,\n        Cancelled = 9\n      };\n\n      enum class EAction { None = 0, Complete = 1, Cancelled = 2 };\n\n      EUIType x0_uiType;\n      CGBASupport* x4_gbaSupport;\n      CGuiFrame* x8_frme;\n      SGuiTextPair xc_textpane_instructions;\n      CGuiTextPane* x14_textpane_yes = nullptr;\n      CGuiTextPane* x18_textpane_no = nullptr;\n      CGuiModel* x1c_model_gc = nullptr;\n      CGuiModel* x20_model_gba = nullptr;\n      CGuiModel* x24_model_cable = nullptr;\n      CGuiModel* x28_model_circlegcport = nullptr;\n      CGuiModel* x2c_model_circlegbaport = nullptr;\n      CGuiModel* x30_model_circlestartselect = nullptr;\n      CGuiModel* x34_model_pakout = nullptr;\n      CGuiModel* x38_model_gbascreen = nullptr;\n      CGuiModel* x3c_model_connect = nullptr;\n      bool x40_linkInProgress;\n\n      void SetUIText(EUIType tp);\n      EAction ProcessUserInput(const CFinalInput& input, bool linkInProgress);\n      void Update(float dt);\n      void FinishedLoading();\n      void Draw();\n      SGBALinkFrame(CGuiFrame* linkFrame, CGBASupport* support, bool linkInProgress);\n    };\n\n    enum class EAction { None, GoBack, PlayNESMetroid };\n\n    std::unique_ptr<SGBALinkFrame> x0_gbaLinkFrame;\n    std::unique_ptr<CGBASupport> x4_gbaSupport;\n    EAction x8_action = EAction::None;\n    TLockedToken<CGuiFrame> xc_gbaScreen;\n    TLockedToken<CGuiFrame> x18_gbaLink;\n    CGuiFrame* x24_loadedFrame = nullptr;\n    CGuiTableGroup* x28_tablegroup_options = nullptr;\n    CGuiTableGroup* x2c_tablegroup_fusionsuit = nullptr;\n    SGuiTextPair x30_textpane_instructions;\n    bool x38_lastDoDraw = false;\n    bool x39_fusionNotComplete = false;\n    bool x3a_mpNotComplete = false;\n\n    bool m_gbaOverride = false;\n\n    explicit SFusionBonusFrame();\n    void FinishedLoading();\n    bool PumpLoad();\n    void SetTableColors(CGuiTableGroup* tbgp) const;\n    void Update(float dt, CSaveGameScreen* saveUI);\n    EAction ProcessUserInput(const CFinalInput& input, CSaveGameScreen* sui);\n    void Draw() const;\n\n    void ResetCompletionFlags() {\n      x39_fusionNotComplete = false;\n      x3a_mpNotComplete = false;\n    }\n\n    void DoCancel(CGuiTableGroup* caller);\n    void DoSelectionChange(CGuiTableGroup* caller, int oldSel);\n    void DoAdvance(CGuiTableGroup* caller);\n  };\n\n  struct SFrontEndFrame {\n    enum class EAction { None, StartGame, FusionBonus, GameOptions, SlideShow };\n\n    u32 x0_rnd;\n    EAction x4_action = EAction::None;\n    TLockedToken<CGuiFrame> x8_frme;\n    CGuiFrame* x14_loadedFrme = nullptr;\n    CGuiTableGroup* x18_tablegroup_mainmenu = nullptr;\n    SGuiTextPair x1c_gbaPair;\n    SGuiTextPair x24_cheatPair;\n\n    SFrontEndFrame(u32 rnd);\n    void FinishedLoading();\n    bool PumpLoad();\n    void Update(float dt);\n    EAction ProcessUserInput(const CFinalInput& input);\n    void Draw() const;\n    void HandleActiveChange(CGuiTableGroup* active);\n\n    void DoCancel(CGuiTableGroup* caller);\n    void DoSelectionChange(CGuiTableGroup* caller, int oldSel);\n    void DoAdvance(CGuiTableGroup* caller);\n  };\n\n  struct SNesEmulatorFrame {\n    enum class EMode { Emulator, SaveProgress, ContinuePlaying, QuitNESMetroid };\n\n    EMode x0_mode = EMode::Emulator;\n    std::unique_ptr<CNESEmulator> x4_nesEmu;\n    std::unique_ptr<CQuitGameScreen> x8_quitScreen;\n    std::unique_ptr<CGuiTextSupport> xc_textSupport;\n    float x10_remTime = 8.f;\n    bool x14_emulationSuspended = false;\n    bool x15_enableFiltering = true;\n\n    SNesEmulatorFrame();\n    void SetMode(EMode mode);\n    void ProcessUserInput(const CFinalInput& input, CSaveGameScreen* sui);\n    bool Update(float dt, CSaveGameScreen* saveUi);\n    void Draw(CSaveGameScreen* saveUi) const;\n  };\n\n  struct SOptionsFrontEndFrame {\n    float x0_uiAlpha = 0.f;\n    TLockedToken<CGuiFrame> x4_frme;\n    TLockedToken<CStringTable> x10_pauseScreen;\n    CGuiFrame* x1c_loadedFrame = nullptr;\n    CStringTable* x20_loadedPauseStrg = nullptr;\n    CGuiTableGroup* x24_tablegroup_leftmenu = nullptr;\n    CGuiTableGroup* x28_tablegroup_rightmenu = nullptr;\n    CGuiTableGroup* x2c_tablegroup_double = nullptr;\n    CGuiTableGroup* x30_tablegroup_triple = nullptr;\n    CGuiSliderGroup* x34_slidergroup_slider = nullptr;\n    float x38_rowPitch = 0.f;\n    CSfxHandle x3c_sliderSfx;\n    CRumbleGenerator x40_rumbleGen;\n    bool x134_24_visible : 1 = true;\n    bool x134_25_exitOptions : 1 = false;\n\n    SOptionsFrontEndFrame();\n\n    void DoSliderChange(CGuiSliderGroup* caller, float value);\n    void DoMenuCancel(CGuiTableGroup* caller);\n    void DoMenuSelectionChange(CGuiTableGroup* caller, int oldSel);\n    void DoLeftMenuAdvance(CGuiTableGroup* caller);\n\n    void DeactivateRightMenu();\n    void HandleRightSelectionChange();\n\n    void SetRightUIText();\n    void SetTableColors(CGuiTableGroup* tbgp) const;\n    void FinishedLoading();\n    bool PumpLoad();\n\n    bool ProcessUserInput(const CFinalInput& input, CSaveGameScreen* sui);\n    void Update(float dt, CSaveGameScreen* saveUi);\n    void Draw() const;\n  };\n\n  bool CanShowSaveUI() const {\n    if (x50_curScreen != EScreen::FileSelect && x50_curScreen != EScreen::FusionBonus)\n      return false;\n    if (x54_nextScreen != EScreen::FileSelect && x54_nextScreen != EScreen::FusionBonus)\n      return false;\n    return true;\n  }\n\nprivate:\n  EPhase x14_phase = EPhase::LoadDepsGroup;\n  u32 x18_rndA;\n  u32 x1c_rndB;\n  TLockedToken<CDependencyGroup> x20_depsGroup;\n  std::vector<CToken> x2c_deps;\n  TLockedToken<CTexture> x38_pressStart;\n  TLockedToken<CAudioGroupSet> x44_frontendAudioGrp;\n  EScreen x50_curScreen = EScreen::OpenCredits;\n  EScreen x54_nextScreen = EScreen::OpenCredits;\n  float x58_fadeBlackTimer = 0.f;\n  bool x5c_fadeBlackWithMovie = false;\n  float x60_pressStartTime = 0.f;\n  float x64_pressStartAlpha = 0.f;\n  float x68_musicVol = 1.f;\n  u32 x6c_;\n  std::array<std::unique_ptr<CMoviePlayer>, 8> x70_menuMovies;\n  EMenuMovie xb8_curMovie = EMenuMovie::Stopped;\n  int xbc_nextAttract = 0;\n  int xc0_attractCount = 0;\n  std::unique_ptr<CMoviePlayer> xc4_attractMovie;\n  CMoviePlayer* xcc_curMoviePtr = nullptr;\n  bool xd0_playerSkipToTitle = false;\n  bool xd1_moviesLoaded = false;\n  bool xd2_deferSlideShow = false;\n  std::unique_ptr<CStaticAudioPlayer> xd4_audio1;\n  std::unique_ptr<CStaticAudioPlayer> xd8_audio2;\n  std::unique_ptr<CSaveGameScreen> xdc_saveUI;\n  std::unique_ptr<SNewFileSelectFrame> xe0_frontendCardFrme;\n  std::unique_ptr<SFusionBonusFrame> xe4_fusionBonusFrme;\n  std::unique_ptr<SFrontEndFrame> xe8_frontendNoCardFrme;\n  std::unique_ptr<SNesEmulatorFrame> xec_emuFrme;\n  std::unique_ptr<SOptionsFrontEndFrame> xf0_optionsFrme;\n  CStaticAudioPlayer* xf4_curAudio = nullptr;\n\n  void SetFadeBlackWithMovie() {\n    x58_fadeBlackTimer = 1000000.f;\n    x5c_fadeBlackWithMovie = true;\n  }\n\n  void SetFadeBlackTimer(float seconds) {\n    x58_fadeBlackTimer = seconds;\n    x5c_fadeBlackWithMovie = false;\n  }\n\n  void TransitionToGame();\n  void UpdateMusicVolume();\n  void FinishedLoadingDepsGroup();\n  bool PumpLoad();\n\npublic:\n  CFrontEndUI();\n  ~CFrontEndUI() override;\n  void StartSlideShow(CArchitectureQueue& queue);\n  std::string GetAttractMovieFileName(int idx);\n  std::string GetNextAttractMovieFileName();\n  void SetCurrentMovie(EMenuMovie movie);\n  void StopAttractMovie();\n  void StartAttractMovie();\n  void StartStateTransition(EScreen screen);\n  void CompleteStateTransition();\n  void HandleDebugMenuReturnValue(CGameDebug::EReturnValue val, CArchitectureQueue& queue);\n  void Draw() override;\n  void UpdateMovies(float dt);\n  bool PumpMovieLoad();\n  void ProcessUserInput(const CFinalInput& input, CArchitectureQueue& queue);\n  EMessageReturn Update(float dt, CArchitectureQueue& queue);\n  EMessageReturn OnMessage(const CArchitectureMessage& msg, CArchitectureQueue& queue) override;\n};\n\n} // namespace MP1\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/MP1/CGBASupport.cpp",
    "content": "#include \"Runtime/MP1/CGBASupport.hpp\"\n\n#include <cstring>\n\n#include \"Runtime/CBasics.hpp\"\n#include \"Runtime/CDvdRequest.hpp\"\n\n#ifndef EMSCRIPTEN\n#define ENABLE_JBUS\n#endif\n\n#ifdef ENABLE_JBUS\n#include <jbus/Endpoint.hpp>\n#include <jbus/Listener.hpp>\n#endif\n\nnamespace metaforce::MP1 {\n\n#ifdef ENABLE_JBUS\nstatic jbus::Listener g_JbusListener;\nstatic std::unique_ptr<jbus::Endpoint> g_JbusEndpoint;\n#endif\n\nvoid CGBASupport::Initialize() {\n#ifdef ENABLE_JBUS\n  jbus::Initialize();\n  g_JbusListener.start();\n#endif\n}\n\nvoid CGBASupport::GlobalPoll() {\n#ifdef ENABLE_JBUS\n  if (g_JbusEndpoint && !g_JbusEndpoint->connected())\n    g_JbusEndpoint.reset();\n  if (!g_JbusEndpoint) {\n    g_JbusEndpoint = g_JbusListener.accept();\n    if (g_JbusEndpoint)\n      g_JbusEndpoint->setChan(3);\n  }\n#endif\n}\n\nCGBASupport::CGBASupport() : CDvdFile(\"client_pad.bin\") {\n  x28_fileSize = ROUND_UP_32(Length());\n  x2c_buffer.reset(new u8[x28_fileSize]);\n  x30_dvdReq = AsyncRead(x2c_buffer.get(), x28_fileSize);\n}\n\nCGBASupport::~CGBASupport() {\n  if (x30_dvdReq)\n    x30_dvdReq->PostCancelRequest();\n}\n\nu8 CGBASupport::CalculateFusionJBusChecksum(const u8* data, size_t len) {\n  u32 sum = -1;\n  for (size_t i = 0; i < len; ++i) {\n    u8 ch = *data++;\n    sum ^= ch;\n    for (int j = 0; j < 8; ++j) {\n      if ((sum & 1)) {\n        sum >>= 1;\n        sum ^= 0xb010;\n      } else\n        sum >>= 1;\n    }\n  }\n  return sum;\n}\n\nbool CGBASupport::PollResponse() {\n#ifdef ENABLE_JBUS\n  if (!g_JbusEndpoint)\n    return false;\n\n  u8 status;\n  if (g_JbusEndpoint->GBAReset(&status) == jbus::GBA_NOT_READY)\n    if (g_JbusEndpoint->GBAReset(&status) == jbus::GBA_NOT_READY)\n      return false;\n\n  if (g_JbusEndpoint->GBAGetStatus(&status) == jbus::GBA_NOT_READY)\n    return false;\n  if (status != (jbus::GBA_JSTAT_PSF1 | jbus::GBA_JSTAT_SEND))\n    return false;\n\n  jbus::ReadWriteBuffer bytes;\n  if (g_JbusEndpoint->GBARead(bytes, &status) == jbus::GBA_NOT_READY) {\n    return false;\n  }\n\n  u32 bytesU32;\n  std::memcpy(&bytesU32, bytes.data(), sizeof(bytes));\n  if (bytesU32 != SBIG('AMTE')) {\n    return false;\n  }\n\n  if (g_JbusEndpoint->GBAGetStatus(&status) == jbus::GBA_NOT_READY) {\n    return false;\n  }\n  if (status != jbus::GBA_JSTAT_PSF1) {\n    return false;\n  }\n\n  if (g_JbusEndpoint->GBAWrite({'A', 'M', 'T', 'E'}, &status) == jbus::GBA_NOT_READY) {\n    return false;\n  }\n\n  if (g_JbusEndpoint->GBAGetStatus(&status) == jbus::GBA_NOT_READY)\n    return false;\n  if ((status & jbus::GBA_JSTAT_FLAGS_MASK) != jbus::GBA_JSTAT_FLAGS_MASK)\n    return false;\n\n  u64 profStart = jbus::GetGCTicks();\n  const u64 timeToSpin = jbus::GetGCTicksPerSec() / 8000;\n  for (;;) {\n    u64 curTime = jbus::GetGCTicks();\n    if (curTime - profStart > timeToSpin)\n      return true;\n\n    if (g_JbusEndpoint->GBAGetStatus(&status) == jbus::GBA_NOT_READY)\n      continue;\n    if (!(status & jbus::GBA_JSTAT_SEND))\n      continue;\n\n    if (g_JbusEndpoint->GBAGetStatus(&status) == jbus::GBA_NOT_READY)\n      continue;\n    if (status != (jbus::GBA_JSTAT_FLAGS_MASK | jbus::GBA_JSTAT_SEND))\n      continue;\n    break;\n  }\n\n  if (g_JbusEndpoint->GBARead(bytes, &status) != jbus::GBA_READY) {\n    return false;\n  }\n\n  if (bytes[3] != CalculateFusionJBusChecksum(bytes.data(), 3)) {\n    return false;\n  }\n\n  x44_fusionLinked = (bytes[2] & 0x2) == 0;\n  if (x44_fusionLinked && (bytes[2] & 0x1) != 0)\n    x45_fusionBeat = true;\n\n#endif\n  return true;\n}\n\n#ifdef ENABLE_JBUS\nstatic void JoyBootDone(jbus::ThreadLocalEndpoint& endpoint, jbus::EJoyReturn status) {}\n#endif\n\nvoid CGBASupport::Update(float dt) {\n  switch (x34_phase) {\n  case EPhase::LoadClientPad:\n    IsReady();\n    break;\n\n  case EPhase::StartProbeTimeout:\n    x38_timeout = 4.f;\n    x34_phase = EPhase::PollProbe;\n    [[fallthrough]];\n\n  case EPhase::PollProbe:\n#ifdef ENABLE_JBUS\n    /* SIProbe poll normally occurs here with 4 second timeout */\n    if (!g_JbusEndpoint) {\n      x34_phase = EPhase::Failed;\n      break;\n    }\n    x40_siChan = g_JbusEndpoint->getChan();\n    x34_phase = EPhase::StartJoyBusBoot;\n#endif\n    [[fallthrough]];\n\n  case EPhase::StartJoyBusBoot:\n#ifdef ENABLE_JBUS\n    x34_phase = EPhase::PollJoyBusBoot;\n    if (!g_JbusEndpoint || g_JbusEndpoint->GBAJoyBootAsync(x40_siChan * 2, 2, x2c_buffer.get(), x28_fileSize,\n                                                           &x3c_status, JoyBootDone) != jbus::GBA_READY) {\n      x34_phase = EPhase::Failed;\n    }\n#else\n    x34_phase = EPhase::Failed;\n#endif\n    break;\n\n  case EPhase::PollJoyBusBoot:\n#ifdef ENABLE_JBUS\n    u8 percent;\n    if (g_JbusEndpoint && g_JbusEndpoint->GBAGetProcessStatus(percent) == jbus::GBA_BUSY)\n      break;\n    if (!g_JbusEndpoint || g_JbusEndpoint->GBAGetStatus(&x3c_status) == jbus::GBA_NOT_READY) {\n      x34_phase = EPhase::Failed;\n      break;\n    }\n    x38_timeout = 4.f;\n    x34_phase = EPhase::DataTransfer;\n#else\n    x34_phase = EPhase::Failed;\n#endif\n    break;\n\n  case EPhase::DataTransfer:\n#ifdef ENABLE_JBUS\n    if (PollResponse()) {\n      x34_phase = EPhase::Complete;\n      break;\n    }\n    x38_timeout = std::max(0.f, x38_timeout - dt);\n    if (x38_timeout == 0.f)\n      x34_phase = EPhase::Failed;\n#else\n    x34_phase = EPhase::Failed;\n#endif\n    break;\n\n  default:\n    break;\n  }\n}\n\nbool CGBASupport::IsReady() {\n  if (x34_phase != EPhase::LoadClientPad)\n    return true;\n\n  if (x30_dvdReq->IsComplete()) {\n    x30_dvdReq.reset();\n    x34_phase = EPhase::Standby;\n    /* Conveniently already little-endian */\n    reinterpret_cast<u32&>(x2c_buffer[0xc8]) = u32(CBasics::GetGCTicks());\n    x2c_buffer[0xaf] = 'E';\n    x2c_buffer[0xbd] = 0xc9;\n    return true;\n  }\n  return false;\n}\n\nvoid CGBASupport::InitializeSupport() {\n  x34_phase = EPhase::Standby;\n  x38_timeout = 0.f;\n  x3c_status = false;\n  x40_siChan = -1;\n  x44_fusionLinked = false;\n  x45_fusionBeat = false;\n}\n\nvoid CGBASupport::StartLink() {\n  x34_phase = EPhase::StartProbeTimeout;\n  x40_siChan = -1;\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/CGBASupport.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/CDvdFile.hpp\"\n\nnamespace metaforce::MP1 {\n\nclass CGBASupport : public CDvdFile {\npublic:\n  enum class EPhase {\n    LoadClientPad,\n    Standby,\n    StartProbeTimeout,\n    PollProbe,\n    StartJoyBusBoot,\n    PollJoyBusBoot,\n    DataTransfer,\n    Complete,\n    Failed\n  };\n\nprivate:\n  u32 x28_fileSize;\n  std::unique_ptr<u8[]> x2c_buffer;\n  EPhase x34_phase = EPhase::LoadClientPad;\n  float x38_timeout = 0.f;\n  std::shared_ptr<IDvdRequest> x30_dvdReq;\n  u8 x3c_status = 0;\n  u32 x40_siChan = -1;\n  bool x44_fusionLinked = false;\n  bool x45_fusionBeat = false;\n\n  static u8 CalculateFusionJBusChecksum(const u8* data, size_t len);\n\npublic:\n  static void Initialize();\n  static void GlobalPoll();\n\n  CGBASupport();\n  ~CGBASupport();\n  bool PollResponse();\n  void Update(float dt);\n  bool IsReady();\n  void InitializeSupport();\n  void StartLink();\n  EPhase GetPhase() const { return x34_phase; }\n  bool IsFusionLinked() const { return x44_fusionLinked; }\n  bool IsFusionBeat() const { return x45_fusionBeat; }\n};\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/CGameCubeDoll.cpp",
    "content": "#include \"Runtime/MP1/CGameCubeDoll.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CModel.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n\nnamespace metaforce::MP1 {\n\nCGameCubeDoll::CGameCubeDoll() {\n  x0_model = g_SimplePool->GetObj(\"CMDL_GameCube\");\n  x8_lights.push_back(CLight::BuildDirectional(zeus::skForward, zeus::skWhite));\n  x18_actorLights = std::make_unique<CActorLights>(8, zeus::skZero3f, 4, 4, false, false, false, 0.1f);\n}\n\nvoid CGameCubeDoll::UpdateActorLights() {\n  // Game calculates that and does nothing\n  // (zeus::skForward + zeus::skRight * 0.25f + zeus::skDown * 0.1f).normalized();\n\n  x8_lights[0] = CLight::BuildDirectional(zeus::skForward, zeus::skWhite);\n  x18_actorLights->BuildFakeLightList(x8_lights, zeus::CColor(0.25f, 1.f));\n}\n\nvoid CGameCubeDoll::Update(float dt) {\n  if (!CheckLoadComplete())\n    return;\n  x1c_fader = std::min(2.f * dt + x1c_fader, 1.f);\n  UpdateActorLights();\n}\n\nvoid CGameCubeDoll::Draw(float alpha) {\n  if (!IsLoaded())\n    return;\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CGameCubeDoll::Draw\", zeus::skPurple);\n\n  g_Renderer->SetPerspective(55.f, CGraphics::GetViewportWidth(), CGraphics::GetViewportHeight(), 0.2f, 4096.f);\n  CGraphics::SetViewPointMatrix(zeus::CTransform::Translate(0.f, -2.f, 0.f));\n  x18_actorLights->ActivateLights();\n  CGraphics::SetModelMatrix(zeus::CTransform::RotateZ(zeus::degToRad(360.f * CGraphics::GetSecondsMod900() * -0.25f)) *\n                            zeus::CTransform::Scale(0.2f));\n  CModelFlags flags(5, 0, 3, zeus::CColor(1.f, alpha * x1c_fader));\n  x0_model->Draw(flags);\n  CGraphics::DisableAllLights();\n}\n\nvoid CGameCubeDoll::Touch() {\n  if (!CheckLoadComplete())\n    return;\n  x0_model->Touch(0);\n}\n\nbool CGameCubeDoll::CheckLoadComplete() {\n  if (IsLoaded())\n    return true;\n  if (x0_model.IsLoaded()) {\n    x20_24_loaded = true;\n    return true;\n  }\n  return false;\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/CGameCubeDoll.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <vector>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Character/CActorLights.hpp\"\n\nnamespace metaforce {\nclass CModel;\nnamespace MP1 {\n\nclass CGameCubeDoll {\n  TLockedToken<CModel> x0_model;\n  std::vector<CLight> x8_lights;\n  std::unique_ptr<CActorLights> x18_actorLights;\n  float x1c_fader = 0.f;\n  bool x20_24_loaded : 1 = false;\n  void UpdateActorLights();\n\npublic:\n  CGameCubeDoll();\n  void Update(float dt);\n  void Draw(float alpha);\n  void Touch();\n  bool CheckLoadComplete();\n  bool IsLoaded() const { return x20_24_loaded; }\n};\n\n} // namespace MP1\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/MP1/CInGameGuiManager.cpp",
    "content": "#include \"Runtime/MP1/CInGameGuiManager.hpp\"\n\n#include <algorithm>\n#include <array>\n\n#include \"Runtime/CDependencyGroup.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Audio/CSfxManager.hpp\"\n#include \"Runtime/AutoMapper/CAutoMapper.hpp\"\n#include \"Runtime/Camera/CFirstPersonCamera.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/GuiSys/CGuiCamera.hpp\"\n#include \"Runtime/GuiSys/CGuiFrame.hpp\"\n#include \"Runtime/GuiSys/CGuiModel.hpp\"\n#include \"Runtime/GuiSys/CGuiWidgetDrawParms.hpp\"\n#include \"Runtime/Input/CInputGenerator.hpp\"\n#include \"Runtime/Input/IController.hpp\"\n#include \"Runtime/MP1/CSamusHud.hpp\"\n#include \"Runtime/Particle/CGenDescription.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce::MP1 {\n\nconstexpr std::array InGameGuiDGRPs{\n    \"InGameGui_DGRP\"sv, \"Ice_DGRP\"sv,         \"Phazon_DGRP\"sv,         \"Plasma_DGRP\"sv,\n    \"Power_DGRP\"sv,     \"Wave_DGRP\"sv,        \"BallTransition_DGRP\"sv, \"GravitySuit_DGRP\"sv,\n    \"Ice_Anim_DGRP\"sv,  \"Plasma_Anim_DGRP\"sv, \"PowerSuit_DGRP\"sv,      \"Power_Anim_DGRP\"sv,\n    \"VariaSuit_DGRP\"sv, \"Wave_Anim_DGRP\"sv,\n};\n\nconstexpr std::array PauseScreenDGRPs{\n    \"InventorySuitPower_DGRP\"sv,         \"InventorySuitVaria_DGRP\"sv,        \"InventorySuitGravity_DGRP\"sv,\n    \"InventorySuitPhazon_DGRP\"sv,        \"InventorySuitFusionPower_DGRP\"sv,  \"InventorySuitFusionVaria_DGRP\"sv,\n    \"InventorySuitFusionGravity_DGRP\"sv, \"InventorySuitFusionPhazon_DGRP\"sv, \"SamusBallANCS_DGRP\"sv,\n    \"SamusSpiderBallANCS_DGRP\"sv,        \"PauseScreenDontDump_DGRP\"sv,       \"PauseScreenDontDump_NoARAM_DGRP\"sv,\n    \"PauseScreenTokens_DGRP\"sv,\n};\n\nstd::vector<TLockedToken<CDependencyGroup>> CInGameGuiManager::LockPauseScreenDependencies() {\n  std::vector<TLockedToken<CDependencyGroup>> ret;\n  ret.reserve(PauseScreenDGRPs.size());\n  for (const auto& dgrp : PauseScreenDGRPs) {\n    ret.emplace_back(g_SimplePool->GetObj(dgrp));\n  }\n  return ret;\n}\n\nbool CInGameGuiManager::CheckDGRPLoadComplete() const {\n  const auto isLoaded = [](const auto& entry) { return entry.IsLoaded(); };\n\n  return std::all_of(x5c_pauseScreenDGRPs.cbegin(), x5c_pauseScreenDGRPs.cend(), isLoaded) &&\n         std::all_of(xc8_inGameGuiDGRPs.cbegin(), xc8_inGameGuiDGRPs.cend(), isLoaded);\n}\n\nvoid CInGameGuiManager::BeginStateTransition(EInGameGuiState state, CStateManager& stateMgr) {\n  if (x1c0_nextState == state)\n    return;\n\n  x1bc_prevState = x1c0_nextState;\n  x1c0_nextState = state;\n\n  switch (state) {\n  case EInGameGuiState::InGame: {\n    CSfxManager::SetChannel(CSfxManager::ESfxChannels::Game);\n    x4c_saveUI.reset();\n    x38_autoMapper->UnmuteAllLoopedSounds();\n    break;\n  }\n  case EInGameGuiState::PauseHUDMessage: {\n    x44_messageScreen = std::make_unique<CMessageScreen>(x124_pauseGameHudMessage, x128_pauseGameHudTime);\n    break;\n  }\n  case EInGameGuiState::PauseSaveGame: {\n    x4c_saveUI = std::make_unique<CSaveGameScreen>(ESaveContext::InGame, g_GameState->GetCardSerial());\n    break;\n  }\n  default: {\n    if (x1bc_prevState >= EInGameGuiState::Zero && x1bc_prevState <= EInGameGuiState::InGame)\n      x1f8_26_deferTransition = true;\n    break;\n  }\n  }\n\n  x3c_pauseScreenBlur->OnNewInGameGuiState(state, stateMgr);\n  if (!x1f8_26_deferTransition)\n    DoStateTransition(stateMgr);\n}\n\nvoid CInGameGuiManager::EnsureStates(CStateManager& stateMgr) {\n  if (x1f8_26_deferTransition) {\n    if (!x3c_pauseScreenBlur->IsGameDraw()) {\n      DestroyAreaTextures(stateMgr);\n      x1f8_26_deferTransition = false;\n      DoStateTransition(stateMgr);\n    }\n  }\n}\n\nvoid CInGameGuiManager::DoStateTransition(CStateManager& stateMgr) {\n  x34_samusHud->OnNewInGameGuiState(x1c0_nextState, stateMgr);\n  x38_autoMapper->OnNewInGameGuiState(x1c0_nextState, stateMgr);\n\n  bool needsLock;\n  switch (x1c0_nextState) {\n  case EInGameGuiState::PauseGame:\n  case EInGameGuiState::PauseLogBook:\n    if (!x48_pauseScreen) {\n      const auto& pState = stateMgr.GetPlayerState();\n      const CPlayerState::EPlayerSuit suit = pState->GetCurrentSuitRaw();\n      int suitResIdx;\n      if (pState->IsFusionEnabled()) {\n        switch (suit) {\n        case CPlayerState::EPlayerSuit::Phazon:\n          suitResIdx = 7;\n          break;\n        case CPlayerState::EPlayerSuit::Gravity:\n          suitResIdx = 6;\n          break;\n        case CPlayerState::EPlayerSuit::Varia:\n          suitResIdx = 5;\n          break;\n        default:\n          suitResIdx = 4;\n          break;\n        }\n      } else {\n        switch (suit) {\n        case CPlayerState::EPlayerSuit::Phazon:\n          suitResIdx = 3;\n          break;\n        case CPlayerState::EPlayerSuit::Gravity:\n          suitResIdx = 2;\n          break;\n        case CPlayerState::EPlayerSuit::Varia:\n          suitResIdx = 1;\n          break;\n        default:\n          suitResIdx = 0;\n          break;\n        }\n      }\n\n      CPauseScreen::ESubScreen screen = x1c0_nextState == EInGameGuiState::PauseLogBook\n                                            ? CPauseScreen::ESubScreen::LogBook\n                                            : CPauseScreen::ESubScreen::Inventory;\n      CDependencyGroup* suitGrp = x5c_pauseScreenDGRPs[suitResIdx].GetObj();\n      x48_pauseScreen = std::make_unique<CPauseScreen>(screen, *suitGrp, *suitGrp);\n    }\n    [[fallthrough]];\n\n  case EInGameGuiState::MapScreen:\n  case EInGameGuiState::PauseSaveGame:\n  case EInGameGuiState::PauseHUDMessage:\n    needsLock = true;\n    break;\n  default:\n    needsLock = false;\n    break;\n  }\n\n  for (CToken& tok : xe8_pauseResources) {\n    if (needsLock)\n      tok.Lock();\n    else\n      tok.Unlock();\n  }\n}\n\nvoid CInGameGuiManager::DestroyAreaTextures(CStateManager& stateMgr) {\n  // TODO\n  CModel::DisableTextureTimeout();\n}\n\nvoid CInGameGuiManager::TryReloadAreaTextures() {}\n\nCInGameGuiManager::CInGameGuiManager(CStateManager& stateMgr, CArchitectureQueue& archQueue)\n: x0_iggmPreLoad(g_SimplePool->GetObj(\"PreLoadIGGM_DGRP\"))\n, x1c_rand(1234)\n, x20_faceplateDecor(stateMgr)\n, x50_deathDot(g_SimplePool->GetObj(\"TXTR_DeathDot\"))\n, x5c_pauseScreenDGRPs(LockPauseScreenDependencies()) {\n  x1e0_helmetVisMode = g_tweakGui->GetHelmetVisMode();\n  x1e4_enableTargetingManager = g_tweakGui->GetEnableTargetingManager();\n  x1e8_enableAutoMapper = g_tweakGui->GetEnableAutoMapper();\n  x1ec_hudVisMode = g_tweakGui->GetHudVisMode();\n  x1f0_enablePlayerVisor = g_tweakGui->GetEnablePlayerVisor();\n\n  x1f4_visorStaticAlpha = stateMgr.GetPlayer().GetVisorStaticAlpha();\n\n  xc8_inGameGuiDGRPs.reserve(InGameGuiDGRPs.size());\n  for (const auto& dgrp : InGameGuiDGRPs) {\n    xc8_inGameGuiDGRPs.emplace_back(g_SimplePool->GetObj(dgrp));\n  }\n}\n\nbool CInGameGuiManager::CheckLoadComplete(CStateManager& stateMgr) {\n  switch (x18_loadPhase) {\n  case ELoadPhase::LoadDepsGroup: {\n    if (!x0_iggmPreLoad.IsLoaded())\n      return false;\n    const auto& tags = x0_iggmPreLoad->GetObjectTagVector();\n    x8_preLoadDeps.reserve(tags.size());\n    for (const SObjectTag& tag : tags) {\n      x8_preLoadDeps.push_back(g_SimplePool->GetObj(tag));\n      x8_preLoadDeps.back().Lock();\n    }\n    x0_iggmPreLoad.Unlock();\n    x18_loadPhase = ELoadPhase::PreLoadDeps;\n    [[fallthrough]];\n  }\n  case ELoadPhase::PreLoadDeps: {\n    for (CToken& tok : x8_preLoadDeps)\n      if (!tok.IsLoaded())\n        return false;\n    x18_loadPhase = ELoadPhase::LoadDeps;\n    x30_playerVisor = std::make_unique<CPlayerVisor>(stateMgr);\n    x34_samusHud = std::make_unique<CSamusHud>(stateMgr);\n    x38_autoMapper = std::make_unique<CAutoMapper>(stateMgr);\n    x3c_pauseScreenBlur = std::make_unique<CPauseScreenBlur>();\n    x40_samusReflection = std::make_unique<CSamusFaceReflection>(stateMgr);\n    [[fallthrough]];\n  }\n  case ELoadPhase::LoadDeps: {\n    if (!x38_autoMapper->CheckLoadComplete())\n      return false;\n    if (!x34_samusHud->CheckLoadComplete(stateMgr))\n      return false;\n    if (!x50_deathDot.IsLoaded())\n      return false;\n    if (!CheckDGRPLoadComplete())\n      return false;\n    x8_preLoadDeps.clear();\n\n    CGuiFrame& baseHud = *x34_samusHud->x274_loadedFrmeBaseHud;\n    x144_basewidget_automapper = baseHud.FindWidget(\"BaseWidget_AutoMapper\");\n    x148_model_automapper = static_cast<CGuiModel*>(baseHud.FindWidget(\"Model_AutoMapper\"));\n    x14c_basehud_camera = baseHud.GetFrameCamera();\n    x150_basewidget_functional = baseHud.FindWidget(\"BaseWidget_Functional\");\n\n    x154_automapperRotate = zeus::CQuaternion(x144_basewidget_automapper->GetWorldTransform().basis);\n    x164_automapperOffset = x144_basewidget_automapper->GetLocalPosition();\n    x170_camRotate = zeus::CQuaternion();\n    x180_camOffset =\n        x14c_basehud_camera->GetLocalPosition() + zeus::CVector3f(0.f, 2.f, g_tweakAutoMapper->GetCamVerticalOffset());\n\n    zeus::CMatrix3f mtx(x170_camRotate);\n    x18c_mapCamXf = zeus::CTransform(mtx, x180_camOffset);\n\n    BeginStateTransition(EInGameGuiState::InGame, stateMgr);\n    x18_loadPhase = ELoadPhase::Done;\n    [[fallthrough]];\n  }\n  case ELoadPhase::Done: {\n    x34_samusHud->Touch();\n    return true;\n  }\n  default:\n    return false;\n  }\n}\n\nvoid CInGameGuiManager::RefreshHudOptions() { x34_samusHud->RefreshHudOptions(); }\n\nvoid CInGameGuiManager::OnNewPauseScreenState(CArchitectureQueue& archQueue) {\n  if (x1c0_nextState != EInGameGuiState::PauseGame && x1c0_nextState != EInGameGuiState::PauseLogBook) {\n    if (x48_pauseScreen && x48_pauseScreen->IsTransitioning())\n      return;\n    x48_pauseScreen.reset();\n  }\n\n  if (x1c0_nextState >= EInGameGuiState::Zero && x1c0_nextState <= EInGameGuiState::InGame) {\n    if (x44_messageScreen)\n      x44_messageScreen.reset();\n    CModel::EnableTextureTimeout();\n    RefreshHudOptions();\n  }\n  x1bc_prevState = x1c0_nextState;\n}\n\nvoid CInGameGuiManager::UpdateAutoMapper(float dt, CStateManager& stateMgr) {\n  x38_autoMapper->Update(dt, stateMgr);\n  zeus::CTransform xf =\n      x148_model_automapper->GetParent()->GetWorldTransform() * x144_basewidget_automapper->GetTransform();\n  x154_automapperRotate = zeus::CQuaternion(xf.basis);\n  x164_automapperOffset = xf.origin;\n\n  x170_camRotate = zeus::CQuaternion(x14c_basehud_camera->GetWorldTransform().basis);\n  x180_camOffset = x14c_basehud_camera->GetWorldTransform().basis[1] * 2.f + x14c_basehud_camera->GetWorldPosition() +\n                   x14c_basehud_camera->GetWorldTransform().basis[2] * g_tweakAutoMapper->GetCamVerticalOffset();\n\n  float frameLength = std::tan(zeus::degToRad(x14c_basehud_camera->GetProjection().xbc_fov) / 2.f) / 0.7f;\n  float scaleX = frameLength * g_tweakAutoMapper->GetAutomapperScaleX();\n  float scaleZ = frameLength * g_tweakAutoMapper->GetAutomapperScaleZ();\n  if (x38_autoMapper->IsFullyOutOfMiniMapState()) {\n    x148_model_automapper->SetO2WTransform(zeus::CTransform(x170_camRotate, x180_camOffset) *\n                                           zeus::CTransform::Scale(scaleX, 1.f, scaleZ));\n    x18c_mapCamXf =\n        zeus::CTransform(x170_camRotate, x180_camOffset) * zeus::CTransform::Scale(frameLength, 1.f, frameLength);\n    x148_model_automapper->SetColor(g_tweakAutoMapper->GetAutomapperWidgetColor());\n  } else if (x38_autoMapper->IsFullyInMiniMapState()) {\n    x148_model_automapper->SetO2WTransform(zeus::CTransform(x154_automapperRotate, x164_automapperOffset));\n    x18c_mapCamXf = x148_model_automapper->GetWorldTransform();\n    x148_model_automapper->SetColor(g_tweakAutoMapper->GetAutomapperWidgetMiniColor());\n  } else {\n    float t;\n    if (x38_autoMapper->GetNextState() != CAutoMapper::EAutoMapperState::MiniMap)\n      t = x38_autoMapper->GetInterp();\n    else\n      t = 1.f - x38_autoMapper->GetInterp();\n    float st = t * (frameLength - 1.f) + 1.f;\n    zeus::CQuaternion rotate = zeus::CQuaternion::slerp(x154_automapperRotate, x170_camRotate, t);\n    zeus::CVector3f offset = x164_automapperOffset * (1.f - t) + x180_camOffset * t;\n    x18c_mapCamXf = zeus::CTransform(rotate, offset) * zeus::CTransform::Scale(st, 1.f, st);\n    x148_model_automapper->SetO2WTransform(\n        zeus::CTransform(rotate, offset) *\n        zeus::CTransform::Scale(t * (scaleX - 1.f) + 1.f, 1.f, t * (scaleZ - 1.f) + 1.f));\n    x148_model_automapper->SetColor(zeus::CColor::lerp(g_tweakAutoMapper->GetAutomapperWidgetMiniColor(),\n                                                       g_tweakAutoMapper->GetAutomapperWidgetColor(), t));\n  }\n}\n\nvoid CInGameGuiManager::Update(CStateManager& stateMgr, float dt, CArchitectureQueue& archQueue, bool useHud) {\n  EnsureStates(stateMgr);\n\n  if (x1d8_onScreenTexAlpha == 0.f)\n    x1dc_onScreenTexTok = TLockedToken<CTexture>();\n\n  if (x1c4_onScreenTex.x0_id != stateMgr.GetPendingScreenTex().x0_id) {\n    if (!x1dc_onScreenTexTok) {\n      x1c4_onScreenTex = stateMgr.GetPendingScreenTex();\n      if (x1c4_onScreenTex.x0_id.IsValid()) {\n        x1dc_onScreenTexTok = g_SimplePool->GetObj({FOURCC('TXTR'), x1c4_onScreenTex.x0_id});\n        x1d8_onScreenTexAlpha = FLT_EPSILON;\n      }\n    } else {\n      if (!stateMgr.GetPendingScreenTex().x0_id.IsValid() &&\n          stateMgr.GetPendingScreenTex().x4_extent == zeus::CVector2i(0, 0)) {\n        x1c4_onScreenTex.x4_extent = stateMgr.GetPendingScreenTex().x4_extent;\n        x1c4_onScreenTex.x0_id = {};\n        x1d8_onScreenTexAlpha = 0.f;\n      } else {\n        x1d8_onScreenTexAlpha = std::max(0.f, x1d8_onScreenTexAlpha - dt);\n      }\n    }\n  } else if (x1c4_onScreenTex.x0_id.IsValid() && x1dc_onScreenTexTok.IsLoaded()) {\n    x1d8_onScreenTexAlpha = std::min(x1d8_onScreenTexAlpha + dt, 1.f);\n  }\n\n  if (useHud) {\n    if (stateMgr.GetPlayer().GetVisorStaticAlpha() != x1f4_visorStaticAlpha) {\n      if (TCastToPtr<CFirstPersonCamera>(stateMgr.GetCameraManager()->GetCurrentCamera(stateMgr))) {\n        if (std::fabs(stateMgr.GetPlayer().GetVisorStaticAlpha() - x1f4_visorStaticAlpha) < 0.5f) {\n          if (x1f4_visorStaticAlpha == 0.f)\n            CSfxManager::SfxStart(SFXui_hud_reboot, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n          else if (x1f4_visorStaticAlpha == 1.f)\n            CSfxManager::SfxStart(SFXui_hud_shutdown, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n        }\n      }\n    }\n    x1f4_visorStaticAlpha = stateMgr.GetPlayer().GetVisorStaticAlpha();\n    x20_faceplateDecor.Update(dt, stateMgr);\n    x40_samusReflection->Update(dt, stateMgr, x1c_rand);\n    if (x1f0_enablePlayerVisor)\n      x30_playerVisor->Update(dt, stateMgr);\n    if (x1f8_25_playerAlive)\n      x34_samusHud->Update(dt, stateMgr, x1e0_helmetVisMode, x1ec_hudVisMode != EHudVisMode::Zero,\n                           x1e4_enableTargetingManager);\n  }\n\n  if (x1e8_enableAutoMapper)\n    UpdateAutoMapper(dt, stateMgr);\n\n  x3c_pauseScreenBlur->Update(dt, stateMgr, !x140_);\n\n  if (x4c_saveUI) {\n    CIOWin::EMessageReturn ret = x4c_saveUI->Update(dt);\n    if (ret != CIOWin::EMessageReturn::Normal) {\n      x1f8_27_exitSaveUI = ret == CIOWin::EMessageReturn::Exit;\n      BeginStateTransition(EInGameGuiState::InGame, stateMgr);\n    }\n  } else if (x44_messageScreen) {\n    if (!x44_messageScreen->Update(dt, x3c_pauseScreenBlur->GetBlurAmt()))\n      BeginStateTransition(EInGameGuiState::InGame, stateMgr);\n  }\n\n  if (x48_pauseScreen) {\n    x48_pauseScreen->Update(dt, stateMgr, x1c_rand, archQueue);\n    if (x1bc_prevState == x1c0_nextState) {\n      if (x48_pauseScreen->ShouldSwitchToMapScreen())\n        BeginStateTransition(EInGameGuiState::MapScreen, stateMgr);\n      else if (x48_pauseScreen->ShouldSwitchToInGame())\n        BeginStateTransition(EInGameGuiState::InGame, stateMgr);\n    }\n  }\n\n  x34_samusHud->Touch();\n  x30_playerVisor->Touch();\n  x34_samusHud->GetTargetingManager().Touch();\n\n  if (x1bc_prevState != x1c0_nextState) {\n    if (x1c0_nextState == EInGameGuiState::Zero || x1c0_nextState == EInGameGuiState::InGame)\n      TryReloadAreaTextures();\n    if ((!x38_autoMapper->IsInMapperStateTransition() || !x1e8_enableAutoMapper) &&\n        x3c_pauseScreenBlur->IsNotTransitioning())\n      OnNewPauseScreenState(archQueue);\n  }\n\n  xf8_camFilter.Update(dt);\n  if (stateMgr.GetCameraManager()->IsInCinematicCamera()) {\n    stateMgr.SetViewportScale(zeus::CVector2f(1.f, 1.f));\n  } else {\n    stateMgr.SetViewportScale(zeus::CVector2f(\n        std::min(x30_playerVisor->GetDesiredViewportScaleX(stateMgr), x34_samusHud->GetViewportScale().x()),\n        std::min(x30_playerVisor->GetDesiredViewportScaleY(stateMgr), x34_samusHud->GetViewportScale().y())));\n  }\n\n  x1f8_25_playerAlive = stateMgr.GetPlayerState()->IsPlayerAlive();\n}\n\nbool CInGameGuiManager::IsInGameStateNotTransitioning() const {\n  return (x1bc_prevState >= EInGameGuiState::Zero && x1bc_prevState <= EInGameGuiState::InGame &&\n          x1c0_nextState >= EInGameGuiState::Zero && x1c0_nextState <= EInGameGuiState::InGame);\n}\n\nbool CInGameGuiManager::IsInPausedStateNotTransitioning() const {\n  return (x1bc_prevState >= EInGameGuiState::MapScreen && x1bc_prevState <= EInGameGuiState::PauseHUDMessage &&\n          x1c0_nextState >= EInGameGuiState::MapScreen && x1c0_nextState <= EInGameGuiState::PauseHUDMessage);\n}\n\nvoid CInGameGuiManager::ProcessControllerInput(CStateManager& stateMgr, const CFinalInput& input,\n                                               CArchitectureQueue& archQueue) {\n  if (input.ControllerIdx() == 0) {\n    if (!IsInGameStateNotTransitioning()) {\n      if (IsInPausedStateNotTransitioning()) {\n        if (x1bc_prevState == EInGameGuiState::MapScreen) {\n          if (x38_autoMapper->IsInMapperState(CAutoMapper::EAutoMapperState::MapScreen) ||\n              x38_autoMapper->IsInMapperState(CAutoMapper::EAutoMapperState::MapScreenUniverse)) {\n            x38_autoMapper->ProcessControllerInput(input, stateMgr);\n            if (x38_autoMapper->CanLeaveMapScreen(stateMgr))\n              BeginStateTransition(EInGameGuiState::InGame, stateMgr);\n          }\n          return;\n        }\n        if (x1bc_prevState == EInGameGuiState::PauseSaveGame) {\n          x4c_saveUI->ProcessUserInput(input);\n          return;\n        }\n        if (x1bc_prevState == EInGameGuiState::PauseHUDMessage) {\n          x44_messageScreen->ProcessControllerInput(input);\n          return;\n        }\n        if (x48_pauseScreen)\n          x48_pauseScreen->ProcessControllerInput(stateMgr, input);\n      }\n    } else {\n      x34_samusHud->ProcessControllerInput(input);\n    }\n  }\n}\n\nvoid CInGameGuiManager::PreDraw(CStateManager& stateMgr, bool cameraActive) {\n  if (x48_pauseScreen)\n    x48_pauseScreen->PreDraw();\n  if (cameraActive)\n    x40_samusReflection->PreDraw(stateMgr);\n}\n\nvoid CInGameGuiManager::Draw(CStateManager& stateMgr) {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CInGameGuiManager::Draw\", zeus::skBlue);\n  // if (!GetIsGameDraw())\n  //    g_Renderer->x318_26_requestRGBA6 = true;\n  if (x1d8_onScreenTexAlpha > 0.f && x1dc_onScreenTexTok.IsLoaded()) {\n    g_Renderer->SetDepthReadWrite(false, false);\n    g_Renderer->SetBlendMode_AlphaBlended();\n    CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvModulate);\n    CGraphics::SetTevOp(ERglTevStage::Stage1, CTevCombiners::kEnvPassthru);\n    int w = x1c4_onScreenTex.x4_extent.x;\n    int h = x1c4_onScreenTex.x4_extent.y;\n    int x = (640 - w) / 2 + x1c4_onScreenTex.xc_origin.x;\n    int y = (448 - h) / 2 - x1c4_onScreenTex.xc_origin.y;\n    CGraphics::Render2D(*x1dc_onScreenTexTok, x, y, w, h, zeus::CColor{1.f, x1d8_onScreenTexAlpha}, true);\n  }\n\n  float staticAlpha = 0.f;\n  if (stateMgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Unmorphed &&\n      stateMgr.GetPlayer().GetDeathTime() > 0.f)\n    staticAlpha = zeus::clamp(0.f, stateMgr.GetPlayer().GetDeathTime() / (0.3f * 2.5f), 1.f);\n\n  bool notInCine = !stateMgr.GetCameraManager()->IsInCinematicCamera();\n  bool drawVisor = false;\n  /* Let's always draw the HUD except in cinematic mode */\n  if (notInCine /* && (x1bc_prevState == EInGameGuiState::InGame || x1c0_nextState == EInGameGuiState::InGame) */)\n    drawVisor = true;\n\n  // if (x3c_pauseScreenBlur->IsGameDraw())\n  {\n    x34_samusHud->GetTargetingManager().Draw(stateMgr, true);\n    CGraphics::SetDepthRange(DEPTH_SCREEN_ACTORS, DEPTH_GUN);\n    bool scanVisor = stateMgr.GetPlayerState()->GetActiveVisor(stateMgr) == CPlayerState::EPlayerVisor::Scan;\n    if (drawVisor && (x1f0_enablePlayerVisor != 0u)) {\n      if (stateMgr.GetPlayer().GetCameraState() == CPlayer::EPlayerCameraState::FirstPerson)\n        x20_faceplateDecor.Draw(stateMgr);\n      CTargetingManager* tgtMgr = nullptr;\n      if (scanVisor && x1e4_enableTargetingManager)\n        tgtMgr = &x34_samusHud->GetTargetingManager();\n      x30_playerVisor->Draw(stateMgr, tgtMgr);\n    }\n    x40_samusReflection->Draw(stateMgr);\n    if (drawVisor) {\n      CGraphics::SetDepthRange(DEPTH_HUD, DEPTH_SCREEN_ACTORS);\n      if (staticAlpha > 0.f) {\n        CCameraFilterPass::DrawFilter(EFilterType::Blend, EFilterShape::RandomStatic, zeus::CColor{1.f, staticAlpha},\n                                      nullptr, 1.f);\n      }\n      x34_samusHud->Draw(stateMgr, x1f4_visorStaticAlpha * (1.f - staticAlpha), x1e0_helmetVisMode,\n                         x1ec_hudVisMode != EHudVisMode::Zero, x1e4_enableTargetingManager && !scanVisor);\n    }\n  }\n\n  bool preDrawBlur = true;\n  if (x1bc_prevState >= EInGameGuiState::Zero && x1bc_prevState <= EInGameGuiState::InGame)\n    if (x1bc_prevState != EInGameGuiState::MapScreen && x1c0_nextState != EInGameGuiState::MapScreen)\n      preDrawBlur = false;\n  if (preDrawBlur)\n    x3c_pauseScreenBlur->Draw(stateMgr);\n\n  if (notInCine && x1e8_enableAutoMapper &&\n      (x3c_pauseScreenBlur->IsGameDraw() || x1bc_prevState == EInGameGuiState::MapScreen ||\n       x1c0_nextState == EInGameGuiState::MapScreen)) {\n    float t;\n    if (stateMgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::Combat)\n      t = stateMgr.GetPlayerState()->GetVisorTransitionFactor();\n    else\n      t = 0.f;\n\n    float mapAlpha;\n    if (g_tweakGui->GetShowAutomapperInMorphball())\n      mapAlpha = 1.f;\n    else if (stateMgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Unmorphed)\n      mapAlpha = 1.f;\n    else\n      mapAlpha = 0.f;\n\n    x34_samusHud->GetBaseHudFrame()->GetFrameCamera()->Draw(CGuiWidgetDrawParms(0.f, zeus::skZero3f));\n    CGraphics::SetDepthRange(DEPTH_NEAR, DEPTH_HUD);\n    x148_model_automapper->SetIsVisible(true);\n    x148_model_automapper->Draw(CGuiWidgetDrawParms(1.f, zeus::skZero3f));\n    CGraphics::SetDepthWriteMode(true, ERglEnum::GEqual, false);\n    x38_autoMapper->Draw(stateMgr, zeus::CTransform::Translate(0.f, 0.02f, 0.f) * x18c_mapCamXf,\n                         mapAlpha * x1f4_visorStaticAlpha * t);\n    CGraphics::SetDepthWriteMode(true, ERglEnum::LEqual, true);\n    x148_model_automapper->SetIsVisible(false);\n  }\n\n  if (!preDrawBlur)\n    x3c_pauseScreenBlur->Draw(stateMgr);\n\n  if (x1e0_helmetVisMode != EHelmetVisMode::ReducedUpdate && notInCine) {\n    float camYOff;\n    if (!x48_pauseScreen)\n      camYOff = 0.f;\n    else\n      camYOff = x48_pauseScreen->GetHelmetCamYOff();\n    x34_samusHud->DrawHelmet(stateMgr, camYOff);\n  }\n\n  if (x4c_saveUI)\n    x4c_saveUI->Draw();\n\n  if (x44_messageScreen)\n    x44_messageScreen->Draw();\n\n  if (x48_pauseScreen)\n    x48_pauseScreen->Draw();\n\n  xf8_camFilter.Draw();\n\n  if (stateMgr.GetPlayer().GetDeathTime() > 0.f) {\n    float dieDur;\n    if (stateMgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Unmorphed)\n      dieDur = 2.5f;\n    else\n      dieDur = 6.f;\n\n    float alpha = zeus::clamp(0.f, stateMgr.GetPlayer().GetDeathTime() / dieDur, 1.f);\n    CCameraFilterPass::DrawFilter(EFilterType::Blend, EFilterShape::Fullscreen, zeus::CColor{1.f, alpha}, nullptr, 1.f);\n\n    float zStart = dieDur - 0.5f - 0.5f - 1.f;\n    float xStart = 0.5f - zStart;\n    float colStart = 0.5f - xStart;\n    if (stateMgr.GetPlayer().GetDeathTime() > zStart) {\n      float zT = 1.f - zeus::clamp(0.f, (stateMgr.GetPlayer().GetDeathTime() - zStart) / 0.5f, 1.f);\n      float xT = 1.f - zeus::clamp(0.f, (stateMgr.GetPlayer().GetDeathTime() - xStart) / 0.5f, 1.f);\n      float colT = 1.f - zeus::clamp(0.f, (stateMgr.GetPlayer().GetDeathTime() - colStart) / 0.5f, 1.f);\n//      SClipScreenRect rect(CGraphics::g_Viewport);\n//      CGraphics::ResolveSpareTexture(rect, 0, GX_TF_RGB565);\n      CCameraFilterPass::DrawFilter(EFilterType::Blend, EFilterShape::Fullscreen, zeus::skBlack, nullptr, 1.f);\n      float z = 0.5f * (zT * zT * zT * zT * zT * (CGraphics::GetViewportHeight() - 12.f) + 12.f);\n      float x = 0.5f * (xT * (CGraphics::GetViewportWidth() - 12.f) + 12.f);\n\n      // TODO\n      //      const std::array<CTexturedQuadFilter::Vert, 4> verts{{\n      //          {{-x, 0.f, z}, {0.f, 0.f}},\n      //          {{-x, 0.f, -z}, {0.f, 1.f}},\n      //          {{x, 0.f, z}, {1.f, 0.f}},\n      //          {{x, 0.f, -z}, {1.f, 1.f}},\n      //      }};\n\n      //      if (!m_deathRenderTexQuad)\n      //        m_deathRenderTexQuad.emplace(EFilterType::Blend, CGraphics::g_SpareTexture.get());\n      //      m_deathRenderTexQuad->drawVerts(zeus::CColor(1.f, colT), verts);\n\n      //      if (!m_deathDotQuad)\n      //        m_deathDotQuad.emplace(EFilterType::Multiply, x50_deathDot);\n      //      m_deathDotQuad->drawVerts(zeus::CColor(1.f, colT), verts);\n    }\n  }\n}\n\nvoid CInGameGuiManager::ShowPauseGameHudMessage(CStateManager& stateMgr, CAssetId pauseMsg, float time) {\n  x124_pauseGameHudMessage = pauseMsg;\n  x128_pauseGameHudTime = time;\n  PauseGame(stateMgr, EInGameGuiState::PauseHUDMessage);\n}\n\nvoid CInGameGuiManager::PauseGame(CStateManager& stateMgr, EInGameGuiState state) {\n  g_Controller->SetMotorState(EIOPort::Player1, EMotorState::Stop);\n  CSfxManager::SetChannel(CSfxManager::ESfxChannels::PauseScreen);\n  BeginStateTransition(state, stateMgr);\n}\n\nvoid CInGameGuiManager::StartFadeIn() {\n  xf8_camFilter.SetFilter(EFilterType::Multiply, EFilterShape::Fullscreen, 0.f, zeus::skBlack, {});\n  xf8_camFilter.DisableFilter(0.5f);\n}\n\nbool CInGameGuiManager::GetIsGameDraw() const {\n  // Always draw world for URDE, even while paused\n  return true;\n  // return x3c_pauseScreenBlur->IsGameDraw();\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/CInGameGuiManager.hpp",
    "content": "#pragma once\n\n#include <list>\n#include <memory>\n#include <optional>\n#include <vector>\n\n#include \"Runtime/Tweaks/ITweakGui.hpp\"\n\n#include \"Runtime/CDependencyGroup.hpp\"\n#include \"Runtime/CRandom16.hpp\"\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Camera/CCameraFilter.hpp\"\n#include \"Runtime/MP1/CFaceplateDecoration.hpp\"\n#include \"Runtime/MP1/CInGameGuiManagerCommon.hpp\"\n#include \"Runtime/MP1/CInventoryScreen.hpp\"\n#include \"Runtime/MP1/CMessageScreen.hpp\"\n#include \"Runtime/MP1/CPauseScreen.hpp\"\n#include \"Runtime/MP1/CPauseScreenBlur.hpp\"\n#include \"Runtime/MP1/CPlayerVisor.hpp\"\n#include \"Runtime/MP1/CSamusFaceReflection.hpp\"\n#include \"Runtime/MP1/CSaveGameScreen.hpp\"\n\n#include <zeus/CQuaternion.hpp>\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CActorLights;\nclass CArchitectureQueue;\nclass CAutoMapper;\nclass CGuiCamera;\nclass CGuiModel;\nclass CModelData;\nclass CStateManager;\n\nnamespace MP1 {\nclass CPauseScreen;\nclass CPauseScreenBlur;\nclass CSamusHud;\n\nclass CInGameGuiManager {\npublic:\n  using EHelmetVisMode = Tweaks::ITweakGui::EHelmetVisMode;\n  using EHudVisMode = Tweaks::ITweakGui::EHudVisMode;\n\nprivate:\n  enum class ELoadPhase { LoadDepsGroup = 0, PreLoadDeps, LoadDeps, Done };\n\n  struct SGuiProfileInfo {};\n\n  TLockedToken<CDependencyGroup> x0_iggmPreLoad;\n  std::vector<CToken> x8_preLoadDeps;\n  ELoadPhase x18_loadPhase = ELoadPhase::LoadDepsGroup;\n  CRandom16 x1c_rand;\n  CFaceplateDecoration x20_faceplateDecor;\n  std::unique_ptr<CPlayerVisor> x30_playerVisor;\n  std::unique_ptr<CSamusHud> x34_samusHud;\n  std::unique_ptr<CAutoMapper> x38_autoMapper;\n  std::unique_ptr<CPauseScreenBlur> x3c_pauseScreenBlur;\n  std::unique_ptr<CSamusFaceReflection> x40_samusReflection;\n  std::unique_ptr<CMessageScreen> x44_messageScreen;\n  std::unique_ptr<CPauseScreen> x48_pauseScreen;\n  std::unique_ptr<CSaveGameScreen> x4c_saveUI;\n  TLockedToken<CTexture> x50_deathDot;\n  std::vector<TLockedToken<CDependencyGroup>> x5c_pauseScreenDGRPs;\n  std::vector<TLockedToken<CDependencyGroup>> xc8_inGameGuiDGRPs;\n  std::vector<u32> xd8_;\n  std::vector<CToken> xe8_pauseResources;\n  CCameraFilterPass xf8_camFilter;\n  CAssetId x124_pauseGameHudMessage;\n  float x128_pauseGameHudTime = 0.f;\n  std::list<CToken> x12c_;\n  u32 x140_ = 0;\n  CGuiWidget* x144_basewidget_automapper = nullptr;\n  CGuiModel* x148_model_automapper = nullptr;\n  CGuiCamera* x14c_basehud_camera = nullptr;\n  CGuiWidget* x150_basewidget_functional = nullptr;\n  zeus::CQuaternion x154_automapperRotate;\n  zeus::CVector3f x164_automapperOffset;\n  zeus::CQuaternion x170_camRotate;\n  zeus::CVector3f x180_camOffset;\n  zeus::CTransform x18c_mapCamXf;\n  EInGameGuiState x1bc_prevState = EInGameGuiState::Zero;\n  EInGameGuiState x1c0_nextState = EInGameGuiState::Zero;\n  SOnScreenTex x1c4_onScreenTex;\n  float x1d8_onScreenTexAlpha = 0.f;\n  TLockedToken<CTexture> x1dc_onScreenTexTok; // Used to be heap-allocated\n  EHelmetVisMode x1e0_helmetVisMode;\n  bool x1e4_enableTargetingManager;\n  bool x1e8_enableAutoMapper;\n  EHudVisMode x1ec_hudVisMode;\n  u32 x1f0_enablePlayerVisor;\n  float x1f4_visorStaticAlpha;\n  bool x1f8_24_ : 1 = false;\n  bool x1f8_25_playerAlive : 1 = true;\n  bool x1f8_26_deferTransition : 1 = false;\n  bool x1f8_27_exitSaveUI : 1 = true;\n\n  static std::vector<TLockedToken<CDependencyGroup>> LockPauseScreenDependencies();\n  bool CheckDGRPLoadComplete() const;\n  void BeginStateTransition(EInGameGuiState state, CStateManager& stateMgr);\n  void EnsureStates(CStateManager& stateMgr);\n  void DoStateTransition(CStateManager& stateMgr);\n  void DestroyAreaTextures(CStateManager& stateMgr);\n  void TryReloadAreaTextures();\n  bool IsInGameStateNotTransitioning() const;\n  bool IsInPausedStateNotTransitioning() const;\n  void UpdateAutoMapper(float dt, CStateManager& stateMgr);\n  void OnNewPauseScreenState(CArchitectureQueue& archQueue);\n  void RefreshHudOptions();\n\npublic:\n  explicit CInGameGuiManager(CStateManager& stateMgr, CArchitectureQueue& archQueue);\n  bool CheckLoadComplete(CStateManager& stateMgr);\n  void Update(CStateManager& stateMgr, float dt, CArchitectureQueue& archQueue, bool useHud);\n  void ProcessControllerInput(CStateManager& stateMgr, const CFinalInput& input, CArchitectureQueue& archQueue);\n  void PreDraw(CStateManager& stateMgr, bool cameraActive);\n  void Draw(CStateManager& stateMgr);\n  void ShowPauseGameHudMessage(CStateManager& stateMgr, CAssetId pauseMsg, float time);\n  void PauseGame(CStateManager& stateMgr, EInGameGuiState state);\n  void StartFadeIn();\n  bool WasInGame() const {\n    return x1bc_prevState >= EInGameGuiState::Zero && x1bc_prevState <= EInGameGuiState::InGame;\n  }\n  bool IsInGame() const { return x1c0_nextState >= EInGameGuiState::Zero && x1c0_nextState <= EInGameGuiState::InGame; }\n  bool IsInSaveUI() const { return x1f8_27_exitSaveUI; }\n  bool GetIsGameDraw() const;\n};\n\n} // namespace MP1\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/MP1/CInGameGuiManagerCommon.hpp",
    "content": "#pragma once\n\nnamespace metaforce::MP1 {\n\nenum class EInGameGuiState { Zero, InGame, MapScreen, PauseGame, PauseLogBook, PauseSaveGame, PauseHUDMessage };\n\n}\n"
  },
  {
    "path": "Runtime/MP1/CInGameTweakManager.hpp",
    "content": "#pragma once\n\n#include \"Runtime/CInGameTweakManagerBase.hpp\"\n\nnamespace metaforce::MP1 {\n\nclass CInGameTweakManager : public CInGameTweakManagerBase {\npublic:\n};\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/CInventoryScreen.cpp",
    "content": "#include \"Runtime/MP1/CInventoryScreen.hpp\"\n\n#include <array>\n\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/GuiSys/CGuiModel.hpp\"\n#include \"Runtime/GuiSys/CGuiTableGroup.hpp\"\n#include \"Runtime/GuiSys/CGuiTextPane.hpp\"\n#include \"Runtime/Input/ControlMapper.hpp\"\n#include \"Runtime/IMain.hpp\"\n\nnamespace metaforce::MP1 {\nnamespace {\nstruct SInventoryItem {\n  u32 idx;\n  u32 nameStrIdx;\n  u32 entryStrIdx;\n};\n\nconstexpr std::array<SInventoryItem, 5> ArmCannonItems{{\n    {0, 0x24, 0x46}, // Power Beam\n    {1, 0x25, 0x48}, // Ice Beam\n    {2, 0x26, 0x4a}, // Wave Beam\n    {3, 0x27, 0x4c}, // Plasma Beam\n    {4, 0x28, 0x4e}, // Phazon Beam\n}};\n\nconstexpr std::array<SInventoryItem, 5> MorphballItems{{\n    {5, 0x2e, 0x57}, // Morph Ball\n    {6, 0x2f, 0x58}, // Boost Ball\n    {7, 0x30, 0x59}, // Spider Ball\n    {8, 0x31, 0x5a}, // Morph Ball Bomb\n    {9, 0x32, 0x5b}, // Power Bomb\n}};\n\nconstexpr std::array<SInventoryItem, 5> SuitItems{{\n    {10, 0x33, 0x52}, // Power Suit\n    {11, 0x34, 0x53}, // Varia Suit\n    {12, 0x35, 0x54}, // Gravity Suit\n    {13, 0x36, 0x55}, // Phazon Suit\n    {14, 0x37, 0x56}, // Energy Tank\n}};\n\nconstexpr std::array<SInventoryItem, 4> VisorItems{{\n    {15, 0x38, 0x42}, // Combat Visor\n    {16, 0x39, 0x43}, // Scan Visor\n    {17, 0x3a, 0x44}, // X-Ray Visor\n    {18, 0x3b, 0x45}, // Thermal Visor\n}};\n\nconstexpr std::array<SInventoryItem, 5> SecondaryItems{{\n    {19, 0x3c, 0x4f}, // Space Jump Boots\n    {20, 0x3d, 0x50}, // Grapple Beam\n    {21, 0x3e, 0x51}, // Missile Launcher\n    {22, 0x3f, 0x5c}, // Charge Beam\n    {23, 0x40, 0x5d}, // Beam Combo\n}};\n\nconstexpr std::array<std::pair<size_t, const SInventoryItem*>, 5> InventoryRegistry{{\n    {ArmCannonItems.size(), ArmCannonItems.data()},\n    {MorphballItems.size(), MorphballItems.data()},\n    {SuitItems.size(), SuitItems.data()},\n    {VisorItems.size(), VisorItems.data()},\n    {SecondaryItems.size(), SecondaryItems.data()},\n}};\n} // Anonymous namespace\n\nCInventoryScreen::CInventoryScreen(const CStateManager& mgr, CGuiFrame& frame, const CStringTable& pauseStrg,\n                                   const CDependencyGroup& suitDgrp, const CDependencyGroup& ballDgrp)\n: CPauseScreenBase(mgr, frame, pauseStrg) {\n  CPlayerState& playerState = *mgr.GetPlayerState();\n  x19c_samusDoll = std::make_unique<CSamusDoll>(\n      suitDgrp, ballDgrp,\n      CPlayerState::EPlayerSuit(int(playerState.GetCurrentSuitRaw()) + (playerState.IsFusionEnabled() * 4)),\n      playerState.GetCurrentBeam(), playerState.HasPowerUp(CPlayerState::EItemType::SpiderBall),\n      playerState.HasPowerUp(CPlayerState::EItemType::GrappleBeam));\n}\n\nCInventoryScreen::~CInventoryScreen() {\n  for (int i = 0; i < 5; ++i) {\n    xd8_textpane_titles[i]->TextSupport().SetFontColor(zeus::skWhite);\n    x15c_model_righttitledecos[i]->SetColor(zeus::skWhite);\n    x144_model_titles[i]->SetColor(zeus::skWhite);\n  }\n  x8c_model_righthighlight->SetColor(zeus::skWhite);\n}\n\nbool CInventoryScreen::InputDisabled() const {\n  return std::fabs(x19c_samusDoll->GetViewInterpolation()) > 0 || x1a8_state == EState::Leaving;\n}\n\nvoid CInventoryScreen::TransitioningAway() { x1a8_state = EState::Leaving; }\n\nvoid CInventoryScreen::Update(float dt, CRandom16& rand, CArchitectureQueue& archQueue) {\n  CPauseScreenBase::Update(dt, rand, archQueue);\n  x19c_samusDoll->Update(dt, rand);\n\n  if (x10_mode == EMode::TextScroll) {\n    if (x1ad_textViewing)\n      x1a4_textBodyAlpha = std::min(4.f * dt + x1a4_textBodyAlpha, 1.f);\n    else\n      x1a4_textBodyAlpha = std::max(0.f, x1a4_textBodyAlpha - 4.f * dt);\n    if (x1a4_textBodyAlpha == 0.f && x1a8_state == EState::Active)\n      ChangeMode(EMode::RightTable);\n  } else {\n    x1a4_textBodyAlpha = std::max(0.f, x1a4_textBodyAlpha - 4.f * dt);\n  }\n  x174_textpane_body->SetColor(zeus::CColor(1.f, x1a4_textBodyAlpha));\n  x180_basewidget_yicon->SetColor(zeus::CColor(1.f, 1.f - x1a4_textBodyAlpha));\n\n  x19c_samusDoll->SetInMorphball(x70_tablegroup_leftlog->GetUserSelection() == 1 && x10_mode != EMode::LeftTable);\n  UpdateSamusDollPulses();\n  if (x1a8_state == EState::Leaving && x1a4_textBodyAlpha == 0.f)\n    x1a8_state = EState::Inactive;\n}\n\nvoid CInventoryScreen::Touch() {\n  CPauseScreenBase::Touch();\n  x19c_samusDoll->Touch();\n}\n\nvoid CInventoryScreen::ProcessControllerInput(const CFinalInput& input) {\n  float viewInterp = x19c_samusDoll->GetViewInterpolation();\n  if (x1a8_state == EState::Inactive || (viewInterp != 0.f && viewInterp != 1.f))\n    return;\n\n  float absViewInterp = std::fabs(viewInterp);\n  if ((input.PY() || ((m_bodyUpClicked || m_bodyClicked) && absViewInterp == 0.f)) && x19c_samusDoll->IsLoaded() &&\n      (absViewInterp > 0.f || x10_mode != EMode::TextScroll)) {\n    x19c_samusDoll->BeginViewInterpolate(absViewInterp == 0.f);\n    if (absViewInterp == 0.f) {\n      if (const auto& kbm = input.GetKBM()) {\n        m_lastMouseCoord = zeus::CVector2f(kbm->m_mouseCoord.norm[0], kbm->m_mouseCoord.norm[1]);\n        m_lastAccumScroll = kbm->m_accumScroll;\n        m_dollScroll = SScrollDelta();\n      }\n    }\n  }\n\n  if (absViewInterp == 1.f) {\n    if (input.PStart()) {\n      x19c_samusDoll->BeginViewInterpolate(false);\n      x198_26_exitPauseScreen = true;\n    } else if (input.PB() || input.PSpecialKey(ESpecialKey::Esc)) {\n      x19c_samusDoll->BeginViewInterpolate(false);\n    }\n  }\n\n  if (std::fabs(x19c_samusDoll->GetViewInterpolation()) > 0.f) {\n    CPauseScreenBase::ResetMouseState();\n\n    float motionAmt = input.DeltaTime() * 6.f;\n    float circleUp = ControlMapper::GetAnalogInput(ControlMapper::ECommands::MapCircleUp, input);\n    float circleDown = ControlMapper::GetAnalogInput(ControlMapper::ECommands::MapCircleDown, input);\n    float circleLeft = ControlMapper::GetAnalogInput(ControlMapper::ECommands::MapCircleLeft, input);\n    float circleRight = ControlMapper::GetAnalogInput(ControlMapper::ECommands::MapCircleRight, input);\n    float moveForward = ControlMapper::GetAnalogInput(ControlMapper::ECommands::MapMoveForward, input);\n    float moveBack = ControlMapper::GetAnalogInput(ControlMapper::ECommands::MapMoveBack, input);\n    float moveLeft = ControlMapper::GetAnalogInput(ControlMapper::ECommands::MapMoveLeft, input);\n    float moveRight = ControlMapper::GetAnalogInput(ControlMapper::ECommands::MapMoveRight, input);\n    float zoomIn = ControlMapper::GetAnalogInput(ControlMapper::ECommands::MapZoomIn, input);\n    float zoomOut = ControlMapper::GetAnalogInput(ControlMapper::ECommands::MapZoomOut, input);\n\n    if (const auto& kbm = input.GetKBM()) {\n      zeus::CVector2f mouseCoord = zeus::CVector2f(kbm->m_mouseCoord.norm[0], kbm->m_mouseCoord.norm[1]);\n      zeus::CVector2f mouseDelta = mouseCoord - m_lastMouseCoord;\n      m_lastMouseCoord = mouseCoord;\n      mouseDelta.x() *= CGraphics::GetViewportAspect();\n      mouseDelta *= 100.f;\n      if (kbm->m_mouseButtons[size_t(EMouseButton::Middle)] ||\n          kbm->m_mouseButtons[size_t(EMouseButton::Secondary)]) {\n        if (float(mouseDelta.x()) < 0.f)\n          moveRight += -mouseDelta.x();\n        else if (float(mouseDelta.x()) > 0.f)\n          moveLeft += mouseDelta.x();\n        if (float(mouseDelta.y()) < 0.f)\n          moveForward += -mouseDelta.y();\n        else if (float(mouseDelta.y()) > 0.f)\n          moveBack += mouseDelta.y();\n      }\n      if (kbm->m_mouseButtons[size_t(EMouseButton::Primary)]) {\n        if (float(mouseDelta.x()) < 0.f)\n          circleRight += -mouseDelta.x();\n        else if (float(mouseDelta.x()) > 0.f)\n          circleLeft += mouseDelta.x();\n        if (float(mouseDelta.y()) < 0.f)\n          circleUp += -mouseDelta.y();\n        else if (float(mouseDelta.y()) > 0.f)\n          circleDown += mouseDelta.y();\n      }\n\n      m_dollScroll += kbm->m_accumScroll - m_lastAccumScroll;\n      m_lastAccumScroll = kbm->m_accumScroll;\n      if (m_dollScroll.delta[1] > 0.0) {\n        zoomIn = 1.f;\n        m_dollScroll.delta[1] = std::max(0.0, m_dollScroll.delta[1] - (15.0 / 60.0));\n      } else if (m_dollScroll.delta[1] < 0.0) {\n        if (x19c_samusDoll->IsZoomedOut()) {\n          x19c_samusDoll->BeginViewInterpolate(false);\n          return;\n        }\n        zoomOut = 1.f;\n        m_dollScroll.delta[1] = std::min(0.0, m_dollScroll.delta[1] + (15.0 / 60.0));\n      }\n    }\n\n    zeus::CVector3f moveVec = {(moveRight - moveLeft) * 0.25f * motionAmt, (zoomIn - zoomOut) * 0.5f * motionAmt,\n                               (moveForward - moveBack) * 0.25f * motionAmt};\n    x19c_samusDoll->SetOffset(moveVec, input.DeltaTime());\n    x19c_samusDoll->SetRotation(0.5f * motionAmt * (circleDown - circleUp),\n                                0.5f * motionAmt * (circleRight - circleLeft), input.DeltaTime());\n  } else {\n    x1ad_textViewing = false;\n    if (x10_mode == EMode::TextScroll) {\n      int oldPage = x174_textpane_body->TextSupport().GetPageCounter();\n      int newPage = oldPage;\n      int totalCount = x174_textpane_body->TextSupport().GetTotalPageCount();\n      bool lastPage = totalCount - 1 == oldPage;\n      if (totalCount != -1) {\n        if (input.PLAUp() || m_bodyUpClicked)\n          newPage = std::max(oldPage - 1, 0);\n        else if (input.PLADown() || m_bodyDownClicked ||\n                 ((input.PA() || input.PSpecialKey(ESpecialKey::Enter) || m_bodyClicked) && !lastPage))\n          newPage = std::min(oldPage + 1, totalCount - 1);\n        x174_textpane_body->TextSupport().SetPage(newPage);\n        if (oldPage != newPage)\n          CSfxManager::SfxStart(SFXui_pause_screen_next_page, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n        x198_28_pulseTextArrowTop = newPage > 0;\n        x198_29_pulseTextArrowBottom = !lastPage;\n      } else {\n        x198_29_pulseTextArrowBottom = false;\n        x198_28_pulseTextArrowTop = false;\n      }\n      if (!x1ac_textLeaveRequested)\n        x1ac_textLeaveRequested =\n            input.PB() || input.PSpecialKey(ESpecialKey::Esc) ||\n            ((input.PA() || m_bodyClicked || input.PSpecialKey(ESpecialKey::Enter)) && lastPage);\n      x1ad_textViewing = !x1ac_textLeaveRequested;\n    } else {\n      x198_29_pulseTextArrowBottom = false;\n      x198_28_pulseTextArrowTop = false;\n    }\n\n    if (x1a8_state != EState::Active)\n      x1ad_textViewing = false;\n\n    CPauseScreenBase::ProcessMouseInput(input, absViewInterp);\n    CPauseScreenBase::ProcessControllerInput(input);\n  }\n}\n\nvoid CInventoryScreen::Draw(float transInterp, float totalAlpha, float yOff) {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CInventoryScreen::Draw\", zeus::skPurple);\n  CPauseScreenBase::Draw(transInterp, totalAlpha, std::fabs(x19c_samusDoll->GetViewInterpolation()));\n  x19c_samusDoll->Draw(x4_mgr, transInterp * (1.f - x1a4_textBodyAlpha));\n}\n\nfloat CInventoryScreen::GetCameraYBias() const { return std::fabs(x19c_samusDoll->GetViewInterpolation()); }\n\nbool CInventoryScreen::VReady() const { return true; }\n\nbool CInventoryScreen::HasLeftInventoryItem(int idx) const {\n  CPlayerState& playerState = *x4_mgr.GetPlayerState();\n  switch (idx) {\n  case 0: // Arm Cannon\n    return true;\n  case 1: // Morphball\n    return playerState.HasPowerUp(CPlayerState::EItemType::MorphBall);\n  case 2: // Suit\n    return true;\n  case 3: // Visor\n    return true;\n  case 4: // Secondary\n    return playerState.HasPowerUp(CPlayerState::EItemType::SpaceJumpBoots) ||\n           playerState.HasPowerUp(CPlayerState::EItemType::GrappleBeam) ||\n           playerState.HasPowerUp(CPlayerState::EItemType::Missiles) ||\n           playerState.HasPowerUp(CPlayerState::EItemType::ChargeBeam) ||\n           playerState.HasPowerUp(CPlayerState::EItemType::SuperMissile) ||\n           playerState.HasPowerUp(CPlayerState::EItemType::IceSpreader) ||\n           playerState.HasPowerUp(CPlayerState::EItemType::Wavebuster) ||\n           playerState.HasPowerUp(CPlayerState::EItemType::Flamethrower);\n  default:\n    return false;\n  }\n}\n\nvoid CInventoryScreen::VActivate() {\n  for (int i = 0; i < 5; ++i) {\n    if (HasLeftInventoryItem(i)) {\n      if (g_Main->IsUSA() && !g_Main->IsTrilogy()) {\n        xa8_textpane_categories[i]->TextSupport().SetText(xc_pauseStrg.GetString(i + 10));\n      } else {\n        xa8_textpane_categories[i]->TextSupport().SetText(xc_pauseStrg.GetString(i + 12));\n      }\n    } else {\n      xa8_textpane_categories[i]->TextSupport().SetText(u\"??????\");\n      x70_tablegroup_leftlog->GetWorkerWidget(i)->SetIsSelectable(false);\n    }\n  }\n\n  if (g_Main->IsUSA() && !g_Main->IsTrilogy()) {\n    x178_textpane_title->TextSupport().SetText(xc_pauseStrg.GetString(9));\n  } else {\n    x178_textpane_title->TextSupport().SetText(xc_pauseStrg.GetString(11));\n  }\n  x180_basewidget_yicon->SetVisibility(true, ETraversalMode::Children);\n}\n\nvoid CInventoryScreen::RightTableSelectionChanged(int oldSel, int newSel) {}\n\nvoid CInventoryScreen::UpdateTextBody() {\n  x1ac_textLeaveRequested = false;\n\n  const SInventoryItem& sel = InventoryRegistry[x70_tablegroup_leftlog->GetUserSelection()].second[x1c_rightSel];\n  std::u16string entryText =\n      xc_pauseStrg.GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? sel.entryStrIdx : sel.entryStrIdx + 3);\n\n  if (sel.idx == 23) // Beam combo\n  {\n    CPlayerState& playerState = *x4_mgr.GetPlayerState();\n    if (g_Main->IsUSA() && !g_Main->IsTrilogy()) {\n      entryText += xc_pauseStrg.GetString(playerState.HasPowerUp(CPlayerState::EItemType::SuperMissile) ? 71 : 65);\n      entryText += xc_pauseStrg.GetString(playerState.HasPowerUp(CPlayerState::EItemType::IceSpreader) ? 73 : 65);\n      entryText += xc_pauseStrg.GetString(playerState.HasPowerUp(CPlayerState::EItemType::Wavebuster) ? 75 : 65);\n      entryText += xc_pauseStrg.GetString(playerState.HasPowerUp(CPlayerState::EItemType::Flamethrower) ? 77 : 65);\n    } else {\n      entryText += xc_pauseStrg.GetString(playerState.HasPowerUp(CPlayerState::EItemType::SuperMissile) ? 73 : 68);\n      entryText += xc_pauseStrg.GetString(playerState.HasPowerUp(CPlayerState::EItemType::IceSpreader) ? 75 : 68);\n      entryText += xc_pauseStrg.GetString(playerState.HasPowerUp(CPlayerState::EItemType::Wavebuster) ? 78 : 68);\n      entryText += xc_pauseStrg.GetString(playerState.HasPowerUp(CPlayerState::EItemType::Flamethrower) ? 79 : 68);\n    }\n  }\n\n  x174_textpane_body->TextSupport().SetText(entryText, true);\n  x174_textpane_body->TextSupport().SetPage(0);\n}\n\nvoid CInventoryScreen::ChangedMode(EMode oldMode) {\n  if (x10_mode == EMode::TextScroll) {\n    x1ad_textViewing = true;\n    UpdateTextBody();\n  }\n}\n\nbool CInventoryScreen::HasRightInventoryItem(int idx) const {\n  CPlayerState& playerState = *x4_mgr.GetPlayerState();\n  switch (idx) {\n  case 0: // Power Beam\n    return true;\n  case 1: // Ice Beam\n    return playerState.HasPowerUp(CPlayerState::EItemType::IceBeam);\n  case 2: // Wave Beam\n    return playerState.HasPowerUp(CPlayerState::EItemType::WaveBeam);\n  case 3: // Plasma Beam\n    return playerState.HasPowerUp(CPlayerState::EItemType::PlasmaBeam);\n  case 4: // Phazon Beam\n    return playerState.HasPowerUp(CPlayerState::EItemType::PhazonSuit);\n  case 5: // Morph Ball\n    return playerState.HasPowerUp(CPlayerState::EItemType::MorphBall);\n  case 6: // Boost Ball\n    return playerState.HasPowerUp(CPlayerState::EItemType::BoostBall);\n  case 7: // Spider Ball\n    return playerState.HasPowerUp(CPlayerState::EItemType::SpiderBall);\n  case 8: // Morph Ball Bomb\n    return playerState.HasPowerUp(CPlayerState::EItemType::MorphBallBombs);\n  case 9: // Power Bomb\n    return playerState.HasPowerUp(CPlayerState::EItemType::PowerBombs);\n  case 10: // Power Suit\n    return true;\n  case 11: // Varia Suit\n    return playerState.HasPowerUp(CPlayerState::EItemType::VariaSuit);\n  case 12: // Gravity Suit\n    return playerState.HasPowerUp(CPlayerState::EItemType::GravitySuit);\n  case 13: // Phazon Suit\n    return playerState.HasPowerUp(CPlayerState::EItemType::PhazonSuit);\n  case 14: // Energy Tank\n    return playerState.HasPowerUp(CPlayerState::EItemType::EnergyTanks);\n  case 15: // Combat Visor\n    return true;\n  case 16: // Scan Visor\n    return playerState.HasPowerUp(CPlayerState::EItemType::ScanVisor);\n  case 17: // X-Ray Visor\n    return playerState.HasPowerUp(CPlayerState::EItemType::XRayVisor);\n  case 18: // Thermal Visor\n    return playerState.HasPowerUp(CPlayerState::EItemType::ThermalVisor);\n  case 19: // Space Jump Boots\n    return playerState.HasPowerUp(CPlayerState::EItemType::SpaceJumpBoots);\n  case 20: // Grapple Beam\n    return playerState.HasPowerUp(CPlayerState::EItemType::GrappleBeam);\n  case 21: // Missile Launcher\n    return playerState.HasPowerUp(CPlayerState::EItemType::Missiles);\n  case 22: // Charge Beam\n    return playerState.HasPowerUp(CPlayerState::EItemType::ChargeBeam);\n  case 23: // Beam Combo\n    return playerState.HasPowerUp(CPlayerState::EItemType::SuperMissile) ||\n           playerState.HasPowerUp(CPlayerState::EItemType::IceSpreader) ||\n           playerState.HasPowerUp(CPlayerState::EItemType::Wavebuster) ||\n           playerState.HasPowerUp(CPlayerState::EItemType::Flamethrower);\n  default:\n    return false;\n  }\n}\n\nbool CInventoryScreen::IsRightInventoryItemEquipped(int idx) const {\n  CPlayerState& playerState = *x4_mgr.GetPlayerState();\n  switch (idx) {\n  case 0: // Power Beam\n    return playerState.GetCurrentBeam() == CPlayerState::EBeamId::Power;\n  case 1: // Ice Beam\n    return playerState.GetCurrentBeam() == CPlayerState::EBeamId::Ice;\n  case 2: // Wave Beam\n    return playerState.GetCurrentBeam() == CPlayerState::EBeamId::Wave;\n  case 3: // Plasma Beam\n    return playerState.GetCurrentBeam() == CPlayerState::EBeamId::Plasma;\n  case 4: // Phazon Beam\n    return playerState.GetCurrentBeam() == CPlayerState::EBeamId::Phazon2;\n  case 5: // Morph Ball\n    return playerState.HasPowerUp(CPlayerState::EItemType::MorphBall);\n  case 6: // Boost Ball\n    return playerState.HasPowerUp(CPlayerState::EItemType::BoostBall);\n  case 7: // Spider Ball\n    return playerState.HasPowerUp(CPlayerState::EItemType::SpiderBall);\n  case 8: // Morph Ball Bomb\n    return playerState.HasPowerUp(CPlayerState::EItemType::MorphBallBombs);\n  case 9: // Power Bomb\n    return playerState.HasPowerUp(CPlayerState::EItemType::PowerBombs);\n  case 10: // Power Suit\n    return playerState.GetCurrentSuit() == CPlayerState::EPlayerSuit::Power;\n  case 11: // Varia Suit\n    return playerState.GetCurrentSuit() == CPlayerState::EPlayerSuit::Varia;\n  case 12: // Gravity Suit\n    return playerState.GetCurrentSuit() == CPlayerState::EPlayerSuit::Gravity;\n  case 13: // Phazon Suit\n    return playerState.GetCurrentSuit() == CPlayerState::EPlayerSuit::Phazon;\n  case 14: // Energy Tank\n    return playerState.HasPowerUp(CPlayerState::EItemType::EnergyTanks);\n  case 15: // Combat Visor\n    return playerState.GetCurrentVisor() == CPlayerState::EPlayerVisor::Combat;\n  case 16: // Scan Visor\n    return playerState.GetCurrentVisor() == CPlayerState::EPlayerVisor::Scan;\n  case 17: // X-Ray Visor\n    return playerState.GetCurrentVisor() == CPlayerState::EPlayerVisor::XRay;\n  case 18: // Thermal Visor\n    return playerState.GetCurrentVisor() == CPlayerState::EPlayerVisor::Thermal;\n  case 19: // Space Jump Boots\n    return playerState.HasPowerUp(CPlayerState::EItemType::SpaceJumpBoots);\n  case 20: // Grapple Beam\n    return playerState.HasPowerUp(CPlayerState::EItemType::GrappleBeam);\n  case 21: // Missile Launcher\n    return playerState.HasPowerUp(CPlayerState::EItemType::Missiles);\n  case 22: // Charge Beam\n    return playerState.HasPowerUp(CPlayerState::EItemType::ChargeBeam);\n  case 23: // Beam Combo\n    return playerState.HasPowerUp(CPlayerState::EItemType::SuperMissile) ||\n           playerState.HasPowerUp(CPlayerState::EItemType::IceSpreader) ||\n           playerState.HasPowerUp(CPlayerState::EItemType::Wavebuster) ||\n           playerState.HasPowerUp(CPlayerState::EItemType::Flamethrower);\n  default:\n    return false;\n  }\n}\n\nvoid CInventoryScreen::UpdateRightTable() {\n  CPauseScreenBase::UpdateRightTable();\n  const auto& [size, data] = InventoryRegistry[x70_tablegroup_leftlog->GetUserSelection()];\n\n  int minSel = INT_MAX;\n  for (int i = 0; i < 5; ++i) {\n    CGuiTextPane* title = xd8_textpane_titles[i];\n    if (i < int(size)) {\n      if (HasRightInventoryItem(data[i].idx)) {\n        title->TextSupport().SetText(xc_pauseStrg.GetString(\n            (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? data[i].nameStrIdx : data[i].nameStrIdx + 3));\n        x84_tablegroup_rightlog->GetWorkerWidget(i + 1)->SetIsSelectable(true);\n        if (i < minSel)\n          minSel = i;\n      } else {\n        title->TextSupport().SetText(u\"??????\");\n        x84_tablegroup_rightlog->GetWorkerWidget(i + 1)->SetIsSelectable(false);\n      }\n    } else {\n      title->TextSupport().SetText(u\"??????\");\n    }\n  }\n\n  if (minSel != INT_MAX) {\n    x1c_rightSel = minSel;\n    SetRightTableSelection(x1c_rightSel, x1c_rightSel);\n  }\n\n  x84_tablegroup_rightlog->GetWorkerWidget(0)->SetIsSelectable(false);\n  x84_tablegroup_rightlog->GetWorkerWidget(x84_tablegroup_rightlog->GetElementCount() - 1)->SetIsSelectable(false);\n  zeus::CColor inactiveColor = g_tweakGuiColors->GetPauseItemAmberColor();\n  inactiveColor.a() = 0.5f;\n  UpdateRightLogColors(false, g_tweakGuiColors->GetPauseItemAmberColor(), inactiveColor);\n}\n\nbool CInventoryScreen::ShouldLeftTableAdvance() const { return x19c_samusDoll->IsLoaded(); }\n\nbool CInventoryScreen::ShouldRightTableAdvance() const {\n  return std::fabs(x19c_samusDoll->GetViewInterpolation()) == 0.f;\n}\n\nu32 CInventoryScreen::GetRightTableCount() const {\n  return u32(InventoryRegistry[x70_tablegroup_leftlog->GetUserSelection()].first);\n}\n\nbool CInventoryScreen::IsRightLogDynamic() const { return true; }\n\nvoid CInventoryScreen::UpdateRightLogColors(bool active, const zeus::CColor& activeColor,\n                                            const zeus::CColor& inactiveColor) {\n  x80_basewidget_rightlog->SetColor(active ? zeus::skWhite : zeus::CColor(1.f, 0.71f));\n  const auto& [size, data] = InventoryRegistry[x70_tablegroup_leftlog->GetUserSelection()];\n  for (u32 i = 0; i < 5; ++i) {\n    if (i < size && IsRightInventoryItemEquipped(data[i].idx)) {\n      x15c_model_righttitledecos[i]->SetColor(g_tweakGuiColors->GetPauseItemBlueColor());\n      xd8_textpane_titles[i]->TextSupport().SetFontColor(g_tweakGuiColors->GetPauseItemBlueColor());\n    } else {\n      x15c_model_righttitledecos[i]->SetColor(activeColor);\n      xd8_textpane_titles[i]->TextSupport().SetFontColor(activeColor);\n    }\n  }\n}\n\nvoid CInventoryScreen::UpdateRightLogHighlight(bool active, int idx, const zeus::CColor& activeColor,\n                                               const zeus::CColor& inactiveColor) {\n  const zeus::CColor actColor = g_tweakGuiColors->GetPauseItemAmberColor() * activeColor;\n  const zeus::CColor inactColor = g_tweakGuiColors->GetPauseItemAmberColor() * inactiveColor;\n\n  const auto& [size, data] = InventoryRegistry[x70_tablegroup_leftlog->GetUserSelection()];\n  for (s32 i = 0; i < 5; ++i) {\n    const bool act = i == idx && active;\n    if (i < int(size) && IsRightInventoryItemEquipped(data[i].idx) && act) {\n      x8c_model_righthighlight->SetColor(g_tweakGuiColors->GetPauseItemBlueColor());\n    } else if (act) {\n      x8c_model_righthighlight->SetColor(actColor);\n    }\n    x144_model_titles[i]->SetColor(act ? actColor : inactColor);\n  }\n}\n\nvoid CInventoryScreen::UpdateSamusDollPulses() {\n  bool pulseSuit = false;\n  bool pulseBeam = false;\n  bool pulseGrapple = false;\n  bool pulseBoots = false;\n  bool pulseVisor = false;\n  int userSel = x70_tablegroup_leftlog->GetUserSelection();\n\n  if (x10_mode == EMode::RightTable) {\n    if (userSel == 2)\n      pulseSuit = true;\n    else if (userSel == 0)\n      pulseBeam = true;\n    else if (userSel == 3)\n      pulseVisor = true;\n    else if (userSel == 4) {\n      pulseGrapple = SecondaryItems[x1c_rightSel].idx == 20;\n      pulseBoots = SecondaryItems[x1c_rightSel].idx == 19;\n      if (SecondaryItems[x1c_rightSel].idx == 21)\n        pulseBeam = true;\n    }\n  }\n\n  x19c_samusDoll->SetPulseSuit(pulseSuit);\n  x19c_samusDoll->SetPulseBeam(pulseBeam);\n  x19c_samusDoll->SetPulseGrapple(pulseGrapple);\n  x19c_samusDoll->SetPulseBoots(pulseBoots);\n  x19c_samusDoll->SetPulseVisor(pulseVisor);\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/CInventoryScreen.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/MP1/CInGameGuiManager.hpp\"\n#include \"Runtime/MP1/CPauseScreenBase.hpp\"\n#include \"Runtime/MP1/CSamusDoll.hpp\"\n\n#include <zeus/CVector2f.hpp>\n\nnamespace metaforce {\nclass CDependencyGroup;\n\nnamespace MP1 {\n\nclass CInventoryScreen : public CPauseScreenBase {\n  enum class EState { Active, Leaving, Inactive };\n\n  std::unique_ptr<CSamusDoll> x19c_samusDoll;\n  float x1a0_ = 0.f;\n  float x1a4_textBodyAlpha = 0.f;\n  EState x1a8_state = EState::Active;\n  bool x1ac_textLeaveRequested = false;\n  bool x1ad_textViewing;\n\n  zeus::CVector2f m_lastMouseCoord;\n  SScrollDelta m_lastAccumScroll;\n  SScrollDelta m_dollScroll;\n\n  void UpdateSamusDollPulses();\n  bool HasLeftInventoryItem(int idx) const;\n  bool HasRightInventoryItem(int idx) const;\n  bool IsRightInventoryItemEquipped(int idx) const;\n  void UpdateTextBody();\n\npublic:\n  CInventoryScreen(const CStateManager& mgr, CGuiFrame& frame, const CStringTable& pauseStrg,\n                   const CDependencyGroup& suitDgrp, const CDependencyGroup& ballDgrp);\n  ~CInventoryScreen() override;\n\n  bool InputDisabled() const override;\n  void TransitioningAway() override;\n  void Update(float dt, CRandom16& rand, CArchitectureQueue& archQueue) override;\n  void Touch() override;\n  void ProcessControllerInput(const CFinalInput& input) override;\n  void Draw(float transInterp, float totalAlpha, float yOff) override;\n  float GetCameraYBias() const override;\n  bool VReady() const override;\n  void VActivate() override;\n  void RightTableSelectionChanged(int oldSel, int newSel) override;\n  void ChangedMode(EMode oldMode) override;\n  void UpdateRightTable() override;\n  bool ShouldLeftTableAdvance() const override;\n  bool ShouldRightTableAdvance() const override;\n  u32 GetRightTableCount() const override;\n  bool IsRightLogDynamic() const override;\n  void UpdateRightLogColors(bool active, const zeus::CColor& activeColor, const zeus::CColor& inactiveColor) override;\n  void UpdateRightLogHighlight(bool active, int idx, const zeus::CColor& activeColor,\n                               const zeus::CColor& inactiveColor) override;\n};\n\n} // namespace MP1\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/MP1/CLogBookScreen.cpp",
    "content": "#include \"Runtime/MP1/CLogBookScreen.hpp\"\n\n#include <algorithm>\n\n#include \"Runtime/CPlayerState.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GuiSys/CAuiImagePane.hpp\"\n#include \"Runtime/GuiSys/CGuiModel.hpp\"\n#include \"Runtime/GuiSys/CGuiTableGroup.hpp\"\n#include \"Runtime/GuiSys/CGuiTextPane.hpp\"\n#include \"Runtime/MP1/CArtifactDoll.hpp\"\n#include \"Runtime/MP1/MP1.hpp\"\n\nnamespace metaforce::MP1 {\n\nCLogBookScreen::CLogBookScreen(const CStateManager& mgr, CGuiFrame& frame, const CStringTable& pauseStrg)\n: CPauseScreenBase(mgr, frame, pauseStrg, true) {\n  x19c_scanCompletes.resize(5);\n  x200_viewScans.resize(5);\n  x258_artifactDoll = std::make_unique<CArtifactDoll>();\n  CMain::EnsureWorldPaksReady();\n  InitializeLogBook();\n}\n\nCLogBookScreen::~CLogBookScreen() {\n  CArtifactDoll::CompleteArtifactHeadScan(x4_mgr);\n  for (CGuiModel* model : x144_model_titles)\n    model->SetLocalTransform(model->GetTransform());\n  CMain::EnsureWorldPakReady(g_GameState->CurrentWorldAssetId());\n}\n\nbool CLogBookScreen::IsScanComplete(CWorldSaveGameInfo::EScanCategory category, CAssetId scan,\n                                    const CPlayerState& playerState) {\n  const float time = playerState.GetScanTime(scan);\n  if (category == CWorldSaveGameInfo::EScanCategory::Artifact) {\n    return time >= 0.5f;\n  } else {\n    return time >= 1.f;\n  }\n}\n\nvoid CLogBookScreen::InitializeLogBook() {\n  for (size_t i = 0; i < x19c_scanCompletes.size(); ++i) {\n    x19c_scanCompletes[i].reserve(g_MemoryCardSys->GetScanCategoryCount(CWorldSaveGameInfo::EScanCategory(i + 1)));\n  }\n\n  CPlayerState& playerState = *x4_mgr.GetPlayerState();\n  for (const auto& [scanId, scanCategory] : g_MemoryCardSys->GetScanStates()) {\n    if (scanCategory == CWorldSaveGameInfo::EScanCategory::None) {\n      continue;\n    }\n\n    const bool complete = IsScanComplete(scanCategory, scanId, playerState);\n    x19c_scanCompletes[int(scanCategory) - 1].emplace_back(scanId, complete);\n  }\n\n  std::sort(x19c_scanCompletes[4].begin(), x19c_scanCompletes[4].end(),\n            [](const std::pair<CAssetId, bool>& a, std::pair<CAssetId, bool>& b) {\n              return CArtifactDoll::GetArtifactHeadScanIndex(a.first) <\n                     CArtifactDoll::GetArtifactHeadScanIndex(b.first);\n            });\n\n  auto viewIt = x200_viewScans.begin();\n  for (const std::vector<std::pair<CAssetId, bool>>& category : x19c_scanCompletes) {\n    const size_t viewScanCount = std::min(category.size(), size_t(5));\n    auto& viewScans = *viewIt++;\n    viewScans.reserve(viewScanCount);\n\n    for (size_t i = 0; i < viewScanCount; ++i) {\n      viewScans.emplace_back(g_SimplePool->GetObj({FOURCC('SCAN'), category[i].first}), TLockedToken<CStringTable>{});\n    }\n  }\n}\n\nvoid CLogBookScreen::UpdateRightTitles() {\n  const std::vector<std::pair<CAssetId, bool>>& category =\n      x19c_scanCompletes[x70_tablegroup_leftlog->GetUserSelection()];\n  for (size_t i = 0; i < xd8_textpane_titles.size(); ++i) {\n    std::u16string string;\n    const auto scanIndex = size_t(x18_firstViewRightSel) + i;\n\n    if (scanIndex < x1f0_curViewScans.size()) {\n      const auto& scan = x1f0_curViewScans[scanIndex];\n\n      if (scan.second && scan.second.IsLoaded()) {\n        if (category[scanIndex].second) {\n          if (scan.second->GetStringCount() > 1) {\n            string = scan.second->GetString(1);\n          } else {\n            string = u\"No Title!\";\n          }\n        } else {\n          string = u\"??????\";\n        }\n      }\n\n      if (string.empty()) {\n        string = u\"........\";\n      }\n    }\n\n    xd8_textpane_titles[i]->TextSupport().SetText(string);\n  }\n\n  const int rightSelMod = x18_firstViewRightSel % 5;\n  const int rightSelRem = 5 - rightSelMod;\n  for (size_t i = 0; i < x144_model_titles.size(); ++i) {\n    const float zOff = float(((int(i) >= rightSelMod) ? rightSelRem - 5 : rightSelRem)) * x38_highlightPitch;\n    x144_model_titles[i]->SetLocalTransform(zeus::CTransform::Translate(0.f, 0.f, zOff) *\n                                            x144_model_titles[i]->GetTransform());\n  }\n}\n\nvoid CLogBookScreen::PumpArticleLoad() {\n  x260_24_loaded = true;\n  for (auto& category : x200_viewScans) {\n    for (auto& [scanInfo, stringTable] : category) {\n      if (scanInfo.IsLoaded()) {\n        if (!stringTable) {\n          stringTable = g_SimplePool->GetObj({FOURCC('STRG'), scanInfo->GetStringTableId()});\n          x260_24_loaded = false;\n        }\n      } else {\n        x260_24_loaded = false;\n      }\n    }\n  }\n\n  int rem = 6;\n  for (auto& [scanInfo, stringTable] : x1f0_curViewScans) {\n    if (scanInfo.IsLoaded()) {\n      if (!stringTable) {\n        stringTable = g_SimplePool->GetObj({FOURCC('STRG'), scanInfo->GetStringTableId()});\n        stringTable.Lock();\n        --rem;\n      }\n    } else if (scanInfo.IsLocked()) {\n      --rem;\n    }\n    if (rem == 0)\n      break;\n  }\n\n  if (x1f0_curViewScans.size()) {\n    int articleIdx = x18_firstViewRightSel;\n    while (rem > 0) {\n      x1f0_curViewScans[articleIdx].first.Lock();\n      articleIdx = NextSurroundingArticleIndex(articleIdx);\n      if (articleIdx == -1)\n        break;\n      --rem;\n    }\n  }\n\n  for (const auto& [scanInfo, stringTable] : x1f0_curViewScans) {\n    if (!scanInfo.IsLoaded()) {\n      continue;\n    }\n    if (!stringTable || !stringTable.IsLoaded()) {\n      continue;\n    }\n\n    UpdateRightTitles();\n    UpdateBodyText();\n  }\n}\n\nbool CLogBookScreen::IsScanCategoryReady(CWorldSaveGameInfo::EScanCategory category) const {\n  const CPlayerState& playerState = *x4_mgr.GetPlayerState();\n  const auto& scanState = g_MemoryCardSys->GetScanStates();\n\n  return std::any_of(scanState.cbegin(), scanState.cend(), [category, &playerState](const auto& state) {\n    if (state.second != category) {\n      return false;\n    }\n\n    return IsScanComplete(state.second, state.first, playerState);\n  });\n}\n\nvoid CLogBookScreen::UpdateBodyText() {\n  if (x10_mode != EMode::TextScroll) {\n    x174_textpane_body->TextSupport().SetText(u\"\");\n    return;\n  }\n\n  const TCachedToken<CStringTable>& str = x1f0_curViewScans[x1c_rightSel].second;\n  if (!str || !str.IsLoaded()) {\n    return;\n  }\n\n  std::u16string accumStr = str->GetString(0);\n  if (str->GetStringCount() > 2) {\n    accumStr += u\"\\n\\n\";\n    accumStr += str->GetString(2);\n  }\n\n  if (IsArtifactCategorySelected()) {\n    const int headIdx = GetSelectedArtifactHeadScanIndex();\n    if (headIdx >= 0 && g_GameState->GetPlayerState()->HasPowerUp(CPlayerState::EItemType(headIdx + 29))) {\n      if (g_Main->IsUSA() && !g_Main->IsTrilogy()) {\n        accumStr = std::u16string(u\"\\n\\n\\n\\n\\n\\n\").append(g_MainStringTable->GetString(105));\n      } else {\n        accumStr = std::u16string(u\"\\n\\n\\n\\n\\n\\n\").append(g_MainStringTable->GetString(107));\n      }\n    }\n  }\n\n  x174_textpane_body->TextSupport().SetText(accumStr, true);\n}\n\nvoid CLogBookScreen::UpdateBodyImagesAndText() {\n  const CScannableObjectInfo* scan = x1f0_curViewScans[x1c_rightSel].first.GetObj();\n  for (CAuiImagePane* pane : xf0_imagePanes) {\n    pane->SetTextureID0({}, g_SimplePool);\n    pane->SetAnimationParms(zeus::skZero2f, 0.f, 0.f);\n  }\n\n  for (size_t i = 0; i < CScannableObjectInfo::NumBuckets; ++i) {\n    const CScannableObjectInfo::SBucket& bucket = scan->GetBucket(i);\n    if (bucket.x8_imagePos == UINT32_MAX) {\n      continue;\n    }\n    CAuiImagePane* pane = xf0_imagePanes[bucket.x8_imagePos];\n    if (bucket.x14_interval > 0.f) {\n      pane->SetAnimationParms(zeus::CVector2f(bucket.xc_size.x, bucket.xc_size.y), bucket.x14_interval,\n                              bucket.x18_fadeDuration);\n    }\n    pane->SetTextureID0(bucket.x0_texture, g_SimplePool);\n    pane->SetFlashFactor(0.f);\n  }\n\n  x260_26_exitTextScroll = false;\n  UpdateBodyText();\n}\n\nint CLogBookScreen::NextSurroundingArticleIndex(int cur) const {\n  if (cur < x18_firstViewRightSel) {\n    const int tmp = x18_firstViewRightSel + (x18_firstViewRightSel - cur + 6);\n\n    if (tmp >= int(x1f0_curViewScans.size())) {\n      return cur - 1;\n    }\n\n    return tmp;\n  }\n\n  if (cur < x18_firstViewRightSel + 6) {\n    if (cur + 1 < int(x1f0_curViewScans.size())) {\n      return cur + 1;\n    }\n\n    if (x18_firstViewRightSel == 0) {\n      return -1;\n    }\n\n    return x18_firstViewRightSel - 1;\n  }\n\n  const int tmp = x18_firstViewRightSel - (cur - (x18_firstViewRightSel + 5));\n  if (tmp >= 0) {\n    return tmp;\n  }\n\n  if (cur >= int(x1f0_curViewScans.size()) - 1) {\n    return -1;\n  }\n\n  return cur + 1;\n}\n\nbool CLogBookScreen::IsArtifactCategorySelected() const { return x70_tablegroup_leftlog->GetUserSelection() == 4; }\n\nint CLogBookScreen::GetSelectedArtifactHeadScanIndex() const {\n  const auto& category = x19c_scanCompletes[x70_tablegroup_leftlog->GetUserSelection()];\n\n  if (x1c_rightSel < int(category.size())) {\n    return CArtifactDoll::GetArtifactHeadScanIndex(category[x1c_rightSel].first);\n  }\n\n  return -1;\n}\n\nbool CLogBookScreen::InputDisabled() const { return x25c_leavePauseState == ELeavePauseState::LeavingPause; }\n\nvoid CLogBookScreen::TransitioningAway() { x25c_leavePauseState = ELeavePauseState::LeavingPause; }\n\nvoid CLogBookScreen::Update(float dt, CRandom16& rand, CArchitectureQueue& archQueue) {\n  CPauseScreenBase::Update(dt, rand, archQueue);\n  x258_artifactDoll->Update(dt, x4_mgr);\n  PumpArticleLoad();\n\n  if (x10_mode == EMode::TextScroll) {\n    if (x260_25_inTextScroll)\n      x254_viewInterp = std::min(x254_viewInterp + 4.f * dt, 1.f);\n    else\n      x254_viewInterp = std::max(0.f, x254_viewInterp - 4.f * dt);\n\n    if (x254_viewInterp == 0.f && x25c_leavePauseState == ELeavePauseState::InPause)\n      ChangeMode(EMode::RightTable);\n  } else {\n    x254_viewInterp = std::max(0.f, x254_viewInterp - 4.f * dt);\n  }\n\n  zeus::CColor color(1.f, x254_viewInterp);\n  x74_basewidget_leftguages->SetColor(color);\n  x88_basewidget_rightguages->SetColor(color);\n\n  zeus::CColor invColor(1.f, 1.f - x254_viewInterp);\n  x70_tablegroup_leftlog->SetColor(invColor);\n  x84_tablegroup_rightlog->SetColor(invColor);\n  x17c_model_textalpha->SetColor(invColor);\n  x174_textpane_body->SetColor(color);\n\n  for (CAuiImagePane* pane : xf0_imagePanes)\n    pane->SetDeResFactor(1.f - x254_viewInterp);\n\n  if (x25c_leavePauseState == ELeavePauseState::LeavingPause && x254_viewInterp == 0.f)\n    x25c_leavePauseState = ELeavePauseState::LeftPause;\n}\n\nvoid CLogBookScreen::Touch() {\n  CPauseScreenBase::Touch();\n  x258_artifactDoll->Touch();\n}\n\nvoid CLogBookScreen::ProcessControllerInput(const CFinalInput& input) {\n  x260_25_inTextScroll = false;\n  if (x25c_leavePauseState == ELeavePauseState::LeftPause)\n    return;\n\n  if (x10_mode == EMode::TextScroll) {\n    int oldPage = x174_textpane_body->TextSupport().GetPageCounter();\n    int newPage = oldPage;\n    int pageCount = x174_textpane_body->TextSupport().GetTotalPageCount();\n    bool lastPage = (pageCount - 1) == oldPage;\n    if (pageCount != -1) {\n      if (input.PLAUp() || m_bodyUpClicked)\n        newPage = std::max(oldPage - 1, 0);\n      else if (input.PLADown() || m_bodyDownClicked ||\n               ((input.PA() || input.PSpecialKey(ESpecialKey::Enter) || m_bodyClicked) && !lastPage))\n        newPage = std::min(oldPage + 1, pageCount - 1);\n      x174_textpane_body->TextSupport().SetPage(newPage);\n      if (oldPage != newPage)\n        CSfxManager::SfxStart(SFXui_pause_screen_next_page, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n      x198_28_pulseTextArrowTop = newPage > 0;\n      x198_29_pulseTextArrowBottom = !lastPage;\n    } else {\n      x198_29_pulseTextArrowBottom = false;\n      x198_28_pulseTextArrowTop = false;\n    }\n\n    if (!x260_26_exitTextScroll)\n      x260_26_exitTextScroll =\n          input.PB() || input.PSpecialKey(ESpecialKey::Esc) ||\n          ((input.PA() || input.PSpecialKey(ESpecialKey::Enter) || m_bodyClicked) && lastPage);\n\n    if (g_tweakGui->GetLatchArticleText())\n      x260_25_inTextScroll = !x260_26_exitTextScroll;\n    else\n      x260_25_inTextScroll = input.DA();\n  } else {\n    x198_29_pulseTextArrowBottom = false;\n    x198_28_pulseTextArrowTop = false;\n  }\n\n  if (x25c_leavePauseState == ELeavePauseState::LeavingPause)\n    x260_25_inTextScroll = false;\n\n  CPauseScreenBase::ProcessMouseInput(input, 0.f);\n  CPauseScreenBase::ProcessControllerInput(input);\n}\n\nvoid CLogBookScreen::Draw(float transInterp, float totalAlpha, float yOff) {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CInventoryScreen::Draw\", zeus::skPurple);\n  CPauseScreenBase::Draw(transInterp, totalAlpha, yOff);\n  bool artifactSel = x10_mode == EMode::RightTable && IsArtifactCategorySelected();\n  x258_artifactDoll->Draw(transInterp * (1.f - x254_viewInterp), x4_mgr, artifactSel,\n                          GetSelectedArtifactHeadScanIndex());\n}\n\nbool CLogBookScreen::VReady() const { return true; }\n\nvoid CLogBookScreen::VActivate() {\n  for (int i = 0; i < int(xa8_textpane_categories.size()); ++i) {\n    if (IsScanCategoryReady(CWorldSaveGameInfo::EScanCategory(i + 1))) {\n      xa8_textpane_categories[i]->TextSupport().SetText(xc_pauseStrg.GetString(i + 1));\n    } else {\n      xa8_textpane_categories[i]->TextSupport().SetText(u\"??????\");\n      x70_tablegroup_leftlog->GetWorkerWidget(i)->SetIsSelectable(false);\n    }\n  }\n\n  x178_textpane_title->TextSupport().SetText(xc_pauseStrg.GetString(0));\n\n#if 0\n    for (int i=5 ; i<5 ; ++i)\n        x70_tablegroup_leftlog->GetWorkerWidget(i)->SetIsSelectable(false);\n#endif\n}\n\nvoid CLogBookScreen::RightTableSelectionChanged(int oldSel, int newSel) { UpdateRightTitles(); }\n\nvoid CLogBookScreen::ChangedMode(EMode oldMode) {\n  if (oldMode == EMode::TextScroll) {\n    x74_basewidget_leftguages->SetVisibility(false, ETraversalMode::Children);\n    x88_basewidget_rightguages->SetVisibility(false, ETraversalMode::Children);\n    UpdateBodyText();\n    x174_textpane_body->TextSupport().SetPage(0);\n  } else if (x10_mode == EMode::TextScroll) {\n    x74_basewidget_leftguages->SetVisibility(true, ETraversalMode::Children);\n    x88_basewidget_rightguages->SetVisibility(true, ETraversalMode::Children);\n    x260_25_inTextScroll = true;\n    UpdateBodyImagesAndText();\n  }\n}\n\nvoid CLogBookScreen::UpdateRightTable() {\n  CPauseScreenBase::UpdateRightTable();\n\n  const auto& category = x19c_scanCompletes[x70_tablegroup_leftlog->GetUserSelection()];\n  x1f0_curViewScans.clear();\n  x1f0_curViewScans.reserve(category.size());\n  for (const std::pair<CAssetId, bool>& scan : category) {\n    x1f0_curViewScans.emplace_back(g_SimplePool->GetObj({FOURCC('SCAN'), scan.first}), TLockedToken<CStringTable>{});\n  }\n\n  PumpArticleLoad();\n  UpdateRightTitles();\n}\n\nbool CLogBookScreen::ShouldLeftTableAdvance() const {\n  if (!x260_24_loaded || x1f0_curViewScans.empty())\n    return false;\n  return IsScanCategoryReady(CWorldSaveGameInfo::EScanCategory(x70_tablegroup_leftlog->GetUserSelection() + 1));\n}\n\nbool CLogBookScreen::ShouldRightTableAdvance() const {\n  const auto& [info, stringTable] = x1f0_curViewScans[x1c_rightSel];\n  return info.IsLoaded() && stringTable.IsLoaded();\n}\n\nu32 CLogBookScreen::GetRightTableCount() const { return x1f0_curViewScans.size(); }\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/CLogBookScreen.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <utility>\n#include <vector>\n\n#include \"Runtime/CWorldSaveGameInfo.hpp\"\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/MP1/CPauseScreenBase.hpp\"\n\nnamespace metaforce {\nclass CPlayerState;\nclass CScannableObjectInfo;\nclass CStringTable;\n} // namespace metaforce\n\nnamespace metaforce::MP1 {\nclass CArtifactDoll;\n\nclass CLogBookScreen : public CPauseScreenBase {\n  rstl::reserved_vector<std::vector<std::pair<CAssetId, bool>>, 5> x19c_scanCompletes;\n  std::vector<std::pair<TCachedToken<CScannableObjectInfo>, TCachedToken<CStringTable>>> x1f0_curViewScans;\n  rstl::reserved_vector<std::vector<std::pair<TLockedToken<CScannableObjectInfo>, TLockedToken<CStringTable>>>, 5>\n      x200_viewScans;\n  float x254_viewInterp = 0.f;\n  std::unique_ptr<CArtifactDoll> x258_artifactDoll;\n  enum class ELeavePauseState {\n    InPause = 0,\n    LeavingPause = 1,\n    LeftPause = 2\n  } x25c_leavePauseState = ELeavePauseState::InPause;\n  bool x260_24_loaded : 1 = false;\n  bool x260_25_inTextScroll : 1 = false;\n  bool x260_26_exitTextScroll : 1 = false;\n\n  void InitializeLogBook();\n  void UpdateRightTitles();\n  void PumpArticleLoad();\n  bool IsScanCategoryReady(CWorldSaveGameInfo::EScanCategory category) const;\n  void UpdateBodyText();\n  void UpdateBodyImagesAndText();\n  int NextSurroundingArticleIndex(int cur) const;\n  bool IsArtifactCategorySelected() const;\n  int GetSelectedArtifactHeadScanIndex() const;\n  static bool IsScanComplete(CWorldSaveGameInfo::EScanCategory category, CAssetId scan,\n                             const CPlayerState& playerState);\n\npublic:\n  CLogBookScreen(const CStateManager& mgr, CGuiFrame& frame, const CStringTable& pauseStrg);\n  ~CLogBookScreen() override;\n\n  bool InputDisabled() const override;\n  void TransitioningAway() override;\n  void Update(float dt, CRandom16& rand, CArchitectureQueue& archQueue) override;\n  void Touch() override;\n  void ProcessControllerInput(const CFinalInput& input) override;\n  void Draw(float transInterp, float totalAlpha, float yOff) override;\n  bool VReady() const override;\n  void VActivate() override;\n  void RightTableSelectionChanged(int oldSel, int newSel) override;\n  void ChangedMode(EMode oldMode) override;\n  void UpdateRightTable() override;\n  bool ShouldLeftTableAdvance() const override;\n  bool ShouldRightTableAdvance() const override;\n  u32 GetRightTableCount() const override;\n};\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/CMFGame.cpp",
    "content": "#include \"Runtime/MP1/CMFGame.hpp\"\n\n#include <array>\n\n#include \"Runtime/CArchitectureQueue.hpp\"\n#include \"Runtime/MP1/CSamusHud.hpp\"\n#include \"Runtime/MP1/MP1.hpp\"\n#include \"Runtime/Audio/CMidiManager.hpp\"\n#include \"Runtime/AutoMapper/CAutoMapper.hpp\"\n#include \"Runtime/Camera/CCinematicCamera.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce::MP1 {\n\nCMFGame::CMFGame(const std::weak_ptr<CStateManager>& stateMgr, const std::weak_ptr<CInGameGuiManager>& guiMgr,\n                 const CArchitectureQueue&)\n: CMFGameBase(\"CMFGame\"), x14_stateManager(stateMgr.lock()), x18_guiManager(guiMgr.lock()) {\n  static_cast<CMain&>(*g_Main).SetMFGameBuilt(true);\n}\n\nCMFGame::~CMFGame() {\n  auto& main = static_cast<CMain&>(*g_Main);\n  main.SetMFGameBuilt(false);\n  main.SetScreenFading(false);\n  CDecalManager::Reinitialize();\n}\n\nCIOWin::EMessageReturn CMFGame::OnMessage(const CArchitectureMessage& msg, CArchitectureQueue& queue) {\n  switch (msg.GetType()) {\n  case EArchMsgType::FrameBegin:\n    x14_stateManager->FrameBegin(msg.GetParm<CArchMsgParmInt32>()->x4_parm);\n    break;\n  case EArchMsgType::TimerTick: {\n    bool wasInitialized = x2a_24_initialized;\n    x2a_24_initialized = true;\n    float dt = MakeMsg::GetParmTimerTick(msg).x4_parm;\n\n    /* URDE addition: this is continuously updated for animated UVs even when game paused */\n    x14_stateManager->UpdateGraphicsTiming(dt);\n\n    switch (x1c_flowState) {\n    case EGameFlowState::CinematicSkip: {\n      x20_cineSkipTime += dt;\n      const CEntity* cam = x14_stateManager->GetCameraManager()->GetCurrentCamera(*x14_stateManager);\n      TCastToConstPtr<CCinematicCamera> cineCam = cam;\n      if ((x20_cineSkipTime >= 1.f && x14_stateManager->SpecialSkipCinematic()) || !cineCam ||\n          (cineCam->GetFlags() & 0x10 && x28_skippedCineCam != cineCam->GetUniqueId())) {\n        static_cast<CMain&>(*g_Main).SetScreenFading(false);\n        x1c_flowState = EGameFlowState::InGame;\n        x18_guiManager->StartFadeIn();\n        x28_skippedCineCam = kInvalidUniqueId;\n        break;\n      }\n      [[fallthrough]];\n    }\n    case EGameFlowState::InGame: {\n      x14_stateManager->SetActiveRandomToDefault();\n      switch (x14_stateManager->GetDeferredStateTransition()) {\n      case EStateManagerTransition::InGame:\n        x14_stateManager->Update(dt);\n        if (!x14_stateManager->ShouldQuitGame())\n          break;\n        // CGraphics::SetIsBeginSceneClearFb();\n        break;\n      case EStateManagerTransition::MapScreen:\n        EnterMapScreen();\n        break;\n      case EStateManagerTransition::PauseGame:\n        PauseGame();\n        break;\n      case EStateManagerTransition::LogBook:\n        EnterLogBook();\n        break;\n      case EStateManagerTransition::SaveGame:\n        SaveGame();\n        break;\n      case EStateManagerTransition::MessageScreen:\n        EnterMessageScreen(x14_stateManager->GetHUDMessageTime());\n        break;\n      }\n\n      if (x2a_25_samusAlive && !x14_stateManager->GetPlayerState()->IsPlayerAlive()) {\n        PlayerDied();\n      }\n      x14_stateManager->ClearActiveRandom();\n      break;\n    }\n    case EGameFlowState::Paused: {\n      if (x18_guiManager->WasInGame() && x18_guiManager->IsInGame()) {\n        x14_stateManager->SetInSaveUI(x18_guiManager->IsInSaveUI());\n        UnpauseGame();\n        if (x14_stateManager->GetPauseHUDMessage().IsValid())\n          x14_stateManager->IncrementHUDMessageFrameCounter();\n      }\n      break;\n    }\n    case EGameFlowState::SamusDied: {\n      if (x14_stateManager->GetPlayer().IsPlayerDeadEnough()) {\n        static_cast<CMain&>(*g_Main).SetFlowState(EClientFlowStates::LoseGame);\n        queue.Push(MakeMsg::CreateQuitGameplay(EArchMsgTarget::Game));\n      } else {\n        x14_stateManager->SetActiveRandomToDefault();\n        x14_stateManager->Update(dt);\n        x14_stateManager->ClearActiveRandom();\n      }\n      break;\n    }\n    default:\n      break;\n    }\n\n    x18_guiManager->Update(*x14_stateManager, dt, queue, IsCameraActiveFlow());\n    if (!wasInitialized)\n      g_GameState->GetWorldTransitionManager()->EndTransition();\n\n    return EMessageReturn::Exit;\n  }\n  case EArchMsgType::UserInput: {\n    if (!x2a_24_initialized)\n      break;\n    const CFinalInput& input = MakeMsg::GetParmUserInput(msg).x4_parm;\n    if (x1c_flowState == EGameFlowState::InGame) {\n      if (input.ControllerIdx() == 0) {\n        const CEntity* cam = x14_stateManager->GetCameraManager()->GetCurrentCamera(*x14_stateManager);\n        TCastToConstPtr<CCinematicCamera> cineCam = cam;\n        if (input.PStart() || input.PSpecialKey(ESpecialKey::Esc)) {\n          if (cineCam && x14_stateManager->GetSkipCinematicSpecialFunction() != kInvalidUniqueId) {\n            CMidiManager::StopAll();\n            x28_skippedCineCam = cineCam->GetUniqueId();\n            x1c_flowState = EGameFlowState::CinematicSkip;\n            x20_cineSkipTime = 0.f;\n          } else if (!cineCam) {\n            x14_stateManager->DeferStateTransition(EStateManagerTransition::PauseGame);\n          }\n        } else if ((input.PZ() || input.PSpecialKey(ESpecialKey::Tab)) && !cineCam &&\n                   x14_stateManager->CanShowMapScreen()) {\n          x14_stateManager->DeferStateTransition(EStateManagerTransition::MapScreen);\n        }\n      }\n\n      x14_stateManager->SetActiveRandomToDefault();\n      x14_stateManager->ProcessInput(input);\n      x14_stateManager->ClearActiveRandom();\n    }\n    x18_guiManager->ProcessControllerInput(*x14_stateManager, input, queue);\n    break;\n  }\n  case EArchMsgType::FrameEnd: {\n    x14_stateManager->FrameEnd();\n    if (x14_stateManager->ShouldQuitGame())\n      queue.Push(MakeMsg::CreateQuitGameplay(EArchMsgTarget::Game));\n    break;\n  }\n  case EArchMsgType::QuitGameplay:\n    return EMessageReturn::RemoveIOWin;\n  default:\n    break;\n  }\n\n  return EMessageReturn::Normal;\n}\n\nvoid CMFGame::Touch() {\n  x14_stateManager->TouchSky();\n  x14_stateManager->TouchPlayerActor();\n\n  bool gunVisible = false;\n  bool ballVisible = false;\n  bool samusVisible = false;\n  CPlayer& player = x14_stateManager->GetPlayer();\n  switch (player.GetMorphballTransitionState()) {\n  case CPlayer::EPlayerMorphBallState::Unmorphed:\n    gunVisible = true;\n    break;\n  case CPlayer::EPlayerMorphBallState::Morphed:\n    ballVisible = true;\n    break;\n  case CPlayer::EPlayerMorphBallState::Morphing:\n    ballVisible = true;\n    samusVisible = true;\n    break;\n  case CPlayer::EPlayerMorphBallState::Unmorphing:\n    gunVisible = true;\n    samusVisible = true;\n    break;\n  }\n\n  if (gunVisible)\n    player.GetPlayerGun()->TouchModel(*x14_stateManager);\n  if (samusVisible)\n    player.GetModelData()->Touch(*x14_stateManager, 0);\n  if (ballVisible)\n    player.GetMorphBall()->TouchModel(*x14_stateManager);\n}\n\nvoid CMFGame::Draw() {\n  if (!x2a_24_initialized) {\n    return;\n  }\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CMFGame::Draw\", zeus::skGreen);\n\n  Touch();\n  if (x18_guiManager->GetIsGameDraw()) {\n    static_cast<CMain&>(*g_Main).SetGameFrameDrawn();\n    x14_stateManager->PreRender();\n    x14_stateManager->DrawWorld();\n    x14_stateManager->GetPlayer().IsPlayerDeadEnough();\n  }\n\n  x18_guiManager->PreDraw(*x14_stateManager, IsCameraActiveFlow());\n  x18_guiManager->Draw(*x14_stateManager);\n\n  if (x1c_flowState == EGameFlowState::CinematicSkip) {\n    const float c = std::min(1.f, 1.f - x20_cineSkipTime);\n    CCameraFilterPass::DrawFilter(EFilterType::Multiply, EFilterShape::Fullscreen, zeus::CColor{c, c, c, c}, nullptr,\n                                  1.f);\n  }\n}\n\nvoid CMFGame::PlayerDied() {\n  x1c_flowState = EGameFlowState::SamusDied;\n  x2a_25_samusAlive = false;\n}\n\nvoid CMFGame::UnpauseGame() {\n  x1c_flowState = EGameFlowState::InGame;\n  CSfxManager::SetChannel(CSfxManager::ESfxChannels::Game);\n  x14_stateManager->DeferStateTransition(EStateManagerTransition::InGame);\n}\n\nvoid CMFGame::EnterMessageScreen(float time) {\n  x1c_flowState = EGameFlowState::Paused;\n  x18_guiManager->ShowPauseGameHudMessage(*x14_stateManager, x14_stateManager->GetPauseHUDMessage(), time);\n}\n\nvoid CMFGame::SaveGame() {\n  x1c_flowState = EGameFlowState::Paused;\n  x18_guiManager->PauseGame(*x14_stateManager, EInGameGuiState::PauseSaveGame);\n}\n\nvoid CMFGame::EnterLogBook() {\n  x1c_flowState = EGameFlowState::Paused;\n  x18_guiManager->PauseGame(*x14_stateManager, EInGameGuiState::PauseLogBook);\n}\n\nvoid CMFGame::PauseGame() {\n  x1c_flowState = EGameFlowState::Paused;\n  x18_guiManager->PauseGame(*x14_stateManager, EInGameGuiState::PauseGame);\n}\n\nvoid CMFGame::EnterMapScreen() {\n  x1c_flowState = EGameFlowState::Paused;\n  x18_guiManager->PauseGame(*x14_stateManager, EInGameGuiState::MapScreen);\n  x14_stateManager->SetInMapScreen(true);\n}\n\nCMFGameLoader::CMFGameLoader() : CMFGameLoaderBase(\"CMFGameLoader\") {\n  CModel::DisableTextureTimeout();\n  auto* m = static_cast<CMain*>(g_Main);\n  switch (m->GetFlowState()) {\n  case EClientFlowStates::Default:\n  case EClientFlowStates::StateSetter: {\n    CAssetId mlvlId = g_GameState->CurrentWorldAssetId();\n    if (g_MemoryCardSys->HasSaveWorldMemory(mlvlId)) {\n      const CSaveWorldMemory& savwMem = g_MemoryCardSys->GetSaveWorldMemory(mlvlId);\n      if (savwMem.GetWorldNameId().IsValid()) {\n        g_GameState->GetWorldTransitionManager()->EnableTransition(0xB7BBD0B4, savwMem.GetWorldNameId(), 1, false, 0.1f,\n                                                                   16.f, 1.f);\n      }\n    }\n    break;\n  }\n  default:\n    break;\n  }\n\n  if (g_GameState->CurrentWorldAssetId() == 0x158EFE17u && g_GameState->CurrentWorldState().GetCurrentAreaId() == 0) {\n    const SObjectTag* strgTag = g_ResFactory->GetResourceIdByName(\"STRG_IntroLevelLoad\");\n    if (strgTag)\n      g_GameState->GetWorldTransitionManager()->EnableTransition({}, strgTag->id, 0, false, 0.1f, 16.f, 1.f);\n  }\n}\n\nCMFGameLoader::~CMFGameLoader() = default;\n\nvoid CMFGameLoader::MakeLoadDependencyList() {\n  static constexpr std::array loadDepPAKs{\"TestAnim\", \"SamusGun\", \"SamGunFx\"};\n\n  std::vector<SObjectTag> tags;\n  for (const auto* const pak : loadDepPAKs) {\n    g_ResFactory->GetTagListForFile(pak, tags);\n  }\n\n  x1c_loadList.reserve(tags.size());\n  for (const SObjectTag& tag : tags) {\n    x1c_loadList.push_back(g_SimplePool->GetObj(tag));\n  }\n}\n\nCIOWin::EMessageReturn CMFGameLoader::OnMessage(const CArchitectureMessage& msg, CArchitectureQueue& queue) {\n  std::shared_ptr<CWorldTransManager> wtMgr = g_GameState->GetWorldTransitionManager();\n\n  switch (msg.GetType()) {\n  case EArchMsgType::TimerTick: {\n    const CArchMsgParmReal32& tick = MakeMsg::GetParmTimerTick(msg);\n    float dt = tick.x4_parm;\n    if (!x2c_24_initialized) {\n      if (x1c_loadList.empty()) {\n        MakeLoadDependencyList();\n        wtMgr->StartTransition();\n        return EMessageReturn::Exit;\n      }\n      u32 loadingCount = 0;\n      for (CToken& tok : x1c_loadList) {\n        tok.Lock();\n        if (!tok.IsLoaded())\n          ++loadingCount;\n      }\n      wtMgr->Update(dt);\n      if (loadingCount)\n        return EMessageReturn::Exit;\n      x2c_24_initialized = true;\n    } else {\n      wtMgr->Update(dt);\n    }\n\n    if (!x14_stateMgr) {\n      CWorldTransManager::WaitForModelsAndTextures();\n      CWorldState& wldState = g_GameState->CurrentWorldState();\n      x14_stateMgr = std::make_shared<CStateManager>(wldState.Mailbox(), wldState.MapWorldInfo(),\n                                                     g_GameState->GetPlayerState(), wtMgr, wldState.GetLayerState());\n    }\n\n    if (x14_stateMgr->xb3c_initPhase != CStateManager::EInitPhase::Done) {\n      CWorldState& wldState = g_GameState->CurrentWorldState();\n      x14_stateMgr->InitializeState(wldState.GetWorldAssetId(), wldState.GetCurrentAreaId(),\n                                    wldState.GetDesiredAreaAssetId());\n      return EMessageReturn::Exit;\n    }\n\n    if (!x18_guiMgr) {\n      g_GameState->CurrentWorldState().SetDesiredAreaAssetId(CAssetId());\n      x18_guiMgr = std::make_shared<CInGameGuiManager>(*x14_stateMgr, queue);\n    }\n    if (!x18_guiMgr->CheckLoadComplete(*x14_stateMgr))\n      return EMessageReturn::Exit;\n\n    x1c_loadList.clear();\n\n    //    if (!CGraphics::g_BooFactory->areShadersReady())\n    //      return EMessageReturn::Exit;\n\n    wtMgr->StartTextFadeOut();\n    x2c_25_transitionFinished = wtMgr->IsTransitionFinished();\n    return EMessageReturn::Exit;\n  }\n  case EArchMsgType::FrameEnd: {\n    if (x2c_25_transitionFinished) {\n      queue.Push(MakeMsg::CreateCreateIOWin(EArchMsgTarget::IOWinManager, 10, 1000,\n                                            std::make_shared<CMFGame>(x14_stateMgr, x18_guiMgr, queue)));\n      CModel::EnableTextureTimeout();\n      return EMessageReturn::RemoveIOWinAndExit;\n    }\n    break;\n  }\n  default:\n    break;\n  }\n  return EMessageReturn::Exit;\n}\n\nvoid CMFGameLoader::Draw() {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CMFGameLoader::Draw\", zeus::skGreen);\n  g_GameState->GetWorldTransitionManager()->Draw();\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/CMFGame.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <vector>\n\n#include \"Runtime/CMFGameBase.hpp\"\n#include \"Runtime/Camera/CCameraFilter.hpp\"\n#include \"Runtime/MP1/CInGameGuiManager.hpp\"\n\nnamespace metaforce {\nclass CStateManager;\nclass CToken;\n\nnamespace MP1 {\n\nenum class EGameFlowState { InGame = 0, Paused, SamusDied, CinematicSkip };\n\nclass CMFGame : public CMFGameBase {\n  std::shared_ptr<CStateManager> x14_stateManager;\n  std::shared_ptr<CInGameGuiManager> x18_guiManager;\n  EGameFlowState x1c_flowState = EGameFlowState::InGame;\n  float x20_cineSkipTime;\n  u32 x24_ = 0;\n  TUniqueId x28_skippedCineCam = kInvalidUniqueId;\n  bool x2a_24_initialized : 1 = false;\n  bool x2a_25_samusAlive : 1 = true;\n\n  bool IsCameraActiveFlow() const {\n    return (x1c_flowState == EGameFlowState::InGame || x1c_flowState == EGameFlowState::SamusDied);\n  }\n\npublic:\n  CMFGame(const std::weak_ptr<CStateManager>& stateMgr, const std::weak_ptr<CInGameGuiManager>& guiMgr,\n          const CArchitectureQueue&);\n  ~CMFGame() override;\n  CIOWin::EMessageReturn OnMessage(const CArchitectureMessage& msg, CArchitectureQueue& queue) override;\n  void Touch();\n  void Draw() override;\n  void PlayerDied();\n  void UnpauseGame();\n  void EnterMessageScreen(float time);\n  void SaveGame();\n  void EnterLogBook();\n  void PauseGame();\n  void EnterMapScreen();\n};\n\nclass CMFGameLoader : public CMFGameLoaderBase {\n  std::shared_ptr<CStateManager> x14_stateMgr;\n  std::shared_ptr<CInGameGuiManager> x18_guiMgr;\n  std::vector<CToken> x1c_loadList;\n  bool x2c_24_initialized : 1 = false;\n  bool x2c_25_transitionFinished : 1 = false;\n\n  void MakeLoadDependencyList();\n\npublic:\n  CMFGameLoader();\n  ~CMFGameLoader() override;\n  EMessageReturn OnMessage(const CArchitectureMessage& msg, CArchitectureQueue& queue) override;\n  void Draw() override;\n};\n\n} // namespace MP1\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/MP1/CMainFlow.cpp",
    "content": "#include \"Runtime/MP1/CMainFlow.hpp\"\n\n#include \"NESEmulator/CNESEmulator.hpp\"\n\n#include \"Runtime/CArchitectureQueue.hpp\"\n#include \"Runtime/CResFactory.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Character/CCharLayoutInfo.hpp\"\n#include \"Runtime/MP1/CCredits.hpp\"\n#include \"Runtime/MP1/CFrontEndUI.hpp\"\n#include \"Runtime/MP1/CMFGame.hpp\"\n#include \"Runtime/MP1/CPlayMovie.hpp\"\n#include \"Runtime/MP1/CPreFrontEnd.hpp\"\n#include \"Runtime/MP1/CQuitGameScreen.hpp\"\n#include \"Runtime/MP1/CSaveGameScreen.hpp\"\n#include \"Runtime/MP1/CStateSetterFlow.hpp\"\n#include \"Runtime/MP1/MP1.hpp\"\n\nnamespace metaforce::MP1 {\n\nvoid CMainFlow::AdvanceGameState(CArchitectureQueue& queue) {\n  switch (x14_gameState) {\n  case EClientFlowStates::Game:\n    SetGameState(EClientFlowStates::GameExit, queue);\n    break;\n  case EClientFlowStates::PreFrontEnd:\n    SetGameState(EClientFlowStates::FrontEnd, queue);\n    break;\n  case EClientFlowStates::FrontEnd:\n    SetGameState(EClientFlowStates::Game, queue);\n    break;\n  case EClientFlowStates::GameExit: {\n    MP1::CMain* main = static_cast<MP1::CMain*>(g_Main);\n    if (main->GetFlowState() != EClientFlowStates::None && main->GetFlowState() != EClientFlowStates::StateSetter)\n      main->SetX30(true);\n    [[fallthrough]];\n  }\n  case EClientFlowStates::Unspecified:\n    SetGameState(EClientFlowStates::PreFrontEnd, queue);\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CMainFlow::SetGameState(EClientFlowStates state, CArchitectureQueue& queue) {\n  x14_gameState = state;\n  MP1::CMain* main = static_cast<MP1::CMain*>(g_Main);\n\n  switch (state) {\n  case EClientFlowStates::GameExit: {\n    switch (main->GetFlowState()) {\n    case EClientFlowStates::WinBad:\n    case EClientFlowStates::WinGood:\n    case EClientFlowStates::WinBest:\n      queue.Push(MakeMsg::CreateCreateIOWin(EArchMsgTarget::IOWinManager, 12, 11, std::make_shared<CCredits>()));\n      break;\n    case EClientFlowStates::LoseGame:\n      queue.Push(MakeMsg::CreateCreateIOWin(EArchMsgTarget::IOWinManager, 12, 11,\n                                            std::make_shared<CPlayMovie>(CPlayMovie::EWhichMovie::LoseGame)));\n      break;\n    default:\n      break;\n    }\n    break;\n  }\n  case EClientFlowStates::PreFrontEnd: {\n    if (main->GetFlowState() == EClientFlowStates::None)\n      return;\n    queue.Push(MakeMsg::CreateCreateIOWin(EArchMsgTarget::IOWinManager, 12, 11, std::make_shared<CPreFrontEnd>()));\n    break;\n  }\n  case EClientFlowStates::FrontEnd: {\n    std::shared_ptr<CIOWin> nextIOWin;\n    switch (main->GetFlowState()) {\n    case EClientFlowStates::StateSetter:\n      nextIOWin = std::make_shared<CStateSetterFlow>();\n      break;\n    case EClientFlowStates::WinBad:\n    case EClientFlowStates::WinGood:\n    case EClientFlowStates::WinBest:\n    case EClientFlowStates::LoseGame:\n    case EClientFlowStates::Default:\n      nextIOWin = std::make_shared<CFrontEndUI>();\n      break;\n    default:\n      return;\n    }\n\n    queue.Push(MakeMsg::CreateCreateIOWin(EArchMsgTarget::IOWinManager, 12, 11, std::move(nextIOWin)));\n    break;\n  }\n  case EClientFlowStates::Game: {\n    g_GameState->GameOptions().EnsureSettings();\n    auto gameLoader = std::make_shared<CMFGameLoader>();\n    main->SetFlowState(EClientFlowStates::Default);\n    queue.Push(MakeMsg::CreateCreateIOWin(EArchMsgTarget::IOWinManager, 10, 1000, std::move(gameLoader)));\n    break;\n  }\n  default:\n    break;\n  }\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/CMainFlow.hpp",
    "content": "#pragma once\n\n#include \"Runtime/CMainFlowBase.hpp\"\n\nnamespace metaforce {\nclass CArchitectureMessage;\nclass CArchitectureQueue;\n\nnamespace MP1 {\n\nclass CMainFlow : public CMainFlowBase {\npublic:\n  CMainFlow() : CMainFlowBase(\"CMainFlow\") {}\n  void AdvanceGameState(CArchitectureQueue& queue) override;\n  void SetGameState(EClientFlowStates state, CArchitectureQueue& queue) override;\n  bool GetIsContinueDraw() const override { return false; }\n  void Draw() override {}\n};\n\n} // namespace MP1\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/MP1/CMakeLists.txt",
    "content": "add_subdirectory(World)\n\nset(MP1_SOURCES\n        Tweaks/CTweakAutoMapper.hpp Tweaks/CTweakAutoMapper.cpp\n        Tweaks/CTweakBall.hpp Tweaks/CTweakBall.cpp\n        Tweaks/CTweakGame.hpp Tweaks/CTweakGame.cpp\n        Tweaks/CTweakGui.hpp Tweaks/CTweakGui.cpp\n        Tweaks/CTweakGuiColors.hpp Tweaks/CTweakGuiColors.cpp\n        Tweaks/CTweakGunRes.hpp Tweaks/CTweakGunRes.cpp\n        Tweaks/CTweakParticle.hpp Tweaks/CTweakParticle.cpp\n        Tweaks/CTweakPlayer.hpp Tweaks/CTweakPlayer.cpp\n        Tweaks/CTweakPlayerControl.hpp Tweaks/CTweakPlayerControl.cpp\n        Tweaks/CTweakPlayerGun.hpp Tweaks/CTweakPlayerGun.cpp\n        Tweaks/CTweakPlayerRes.hpp Tweaks/CTweakPlayerRes.cpp\n        Tweaks/CTweakSlideShow.hpp Tweaks/CTweakSlideShow.cpp\n        Tweaks/CTweakTargeting.hpp Tweaks/CTweakTargeting.cpp\n        CTweaks.hpp CTweaks.cpp\n        CInGameTweakManager.hpp\n        CGBASupport.hpp CGBASupport.cpp\n        CMainFlow.hpp CMainFlow.cpp\n        CMFGame.hpp CMFGame.cpp\n        CPlayMovie.hpp CPlayMovie.cpp\n        CFrontEndUI.hpp CFrontEndUI.cpp\n        CPreFrontEnd.hpp CPreFrontEnd.cpp\n        CSlideShow.hpp CSlideShow.cpp\n        CSaveGameScreen.hpp CSaveGameScreen.cpp\n        CMemoryCardDriver.hpp CMemoryCardDriver.cpp\n        CQuitGameScreen.hpp CQuitGameScreen.cpp\n        CMessageScreen.hpp CMessageScreen.cpp\n        CCredits.hpp CCredits.cpp\n        CStateSetterFlow.hpp CStateSetterFlow.cpp\n        CAudioStateWin.hpp CAudioStateWin.cpp\n        CInGameGuiManager.hpp CInGameGuiManager.cpp\n        CInGameGuiManagerCommon.hpp\n        CSamusFaceReflection.hpp CSamusFaceReflection.cpp\n        CPlayerVisor.hpp CPlayerVisor.cpp\n        CSamusHud.hpp CSamusHud.cpp\n        CPauseScreenBlur.hpp CPauseScreenBlur.cpp\n        CPauseScreen.hpp CPauseScreen.cpp\n        CPauseScreenBase.hpp CPauseScreenBase.cpp\n        CFaceplateDecoration.hpp CFaceplateDecoration.cpp\n        CInventoryScreen.hpp CInventoryScreen.cpp\n        CLogBookScreen.hpp CLogBookScreen.cpp\n        COptionsScreen.hpp COptionsScreen.cpp\n        CSamusDoll.hpp CSamusDoll.cpp\n        CGameCubeDoll.hpp CGameCubeDoll.cpp\n        CArtifactDoll.hpp CArtifactDoll.cpp\n        CAutoSave.hpp CAutoSave.cpp\n        MP1.hpp MP1.cpp\n        ${MP1_PLAT_SOURCES}\n        ${MP1_WORLD_SOURCES})\n\nruntime_add_list(MP1 MP1_SOURCES)\n"
  },
  {
    "path": "Runtime/MP1/CMemoryCardDriver.cpp",
    "content": "#include \"Runtime/MP1/CMemoryCardDriver.hpp\"\n\n#include <array>\n\n#include \"Runtime/CCRC32.hpp\"\n#include \"Runtime/MP1/MP1.hpp\"\n\nnamespace metaforce::MP1 {\n\nconstexpr std::array SaveFileNames{\"MetroidPrime A\", \"MetroidPrime B\"};\n\nusing ECardResult = kabufuda::ECardResult;\nusing ECardSlot = kabufuda::ECardSlot;\n\nECardResult CMemoryCardDriver::SFileInfo::Open() {\n  return CMemoryCardSys::OpenFile(GetFileCardPort(), x14_name.c_str(), x0_fileInfo);\n}\n\nECardResult CMemoryCardDriver::SFileInfo::Close() { return CMemoryCardSys::CloseFile(x0_fileInfo); }\n\nECardResult CMemoryCardDriver::SFileInfo::StartRead() {\n  CMemoryCardSys::CardStat stat = {};\n  ECardResult result = CMemoryCardSys::GetStatus(x0_fileInfo.slot, x0_fileInfo.getFileNo(), stat);\n  if (result != ECardResult::READY)\n    return result;\n\n  u32 length = stat.GetFileLength();\n  x34_saveData.clear();\n  x24_saveFileData.resize(length);\n  return CMemoryCardSys::ReadFile(x0_fileInfo, x24_saveFileData.data(), length, 0);\n}\n\nECardResult CMemoryCardDriver::SFileInfo::TryFileRead() {\n  ECardResult res = CMemoryCardSys::GetResultCode(GetFileCardPort());\n  if (res == ECardResult::READY) {\n    res = FileRead();\n  }\n  return res;\n}\n\nECardResult CMemoryCardDriver::SFileInfo::FileRead() {\n  x34_saveData.clear();\n  if (x24_saveFileData.empty()) {\n    return ECardResult::CRC_MISMATCH;\n  }\n  u32 existingCrc = CBasics::SwapBytes(*reinterpret_cast<u32*>(x24_saveFileData.data()));\n  u32 newCrc = CCRC32::Calculate(x24_saveFileData.data() + 4, x24_saveFileData.size() - 4);\n  if (existingCrc == newCrc) {\n    u32 saveDataOff;\n    ECardResult result = GetSaveDataOffset(saveDataOff);\n    if (result != ECardResult::READY) {\n      x24_saveFileData.clear();\n      return result;\n    }\n\n    u32 saveSize = x24_saveFileData.size() - saveDataOff;\n    x34_saveData.resize(saveSize);\n    memmove(x34_saveData.data(), x24_saveFileData.data() + saveDataOff, saveSize);\n    x24_saveFileData.clear();\n    return ECardResult::READY;\n  } else {\n    x24_saveFileData.clear();\n    return ECardResult::CRC_MISMATCH;\n  }\n}\n\nECardResult CMemoryCardDriver::SFileInfo::GetSaveDataOffset(u32& offOut) const {\n  CMemoryCardSys::CardStat stat = {};\n  ECardResult result = CMemoryCardSys::GetStatus(x0_fileInfo.slot, x0_fileInfo.getFileNo(), stat);\n  if (result != ECardResult::READY) {\n    offOut = -1;\n    return result;\n  }\n\n  offOut = 4;\n  offOut += 64;\n  switch (stat.GetBannerFormat()) {\n  case kabufuda::EImageFormat::C8:\n    offOut += 3584;\n    break;\n  case kabufuda::EImageFormat::RGB5A3:\n    offOut += 6144;\n    break;\n  default:\n    break;\n  }\n\n  int idx = 0;\n  bool paletteIcon = false;\n  for (kabufuda::EImageFormat fmt = stat.GetIconFormat(idx); fmt != kabufuda::EImageFormat::None;\n       fmt = stat.GetIconFormat(idx)) {\n    if (fmt == kabufuda::EImageFormat::C8) {\n      paletteIcon = true;\n      offOut += 1024;\n    } else\n      offOut += 2048;\n    ++idx;\n  }\n\n  if (paletteIcon)\n    offOut += 512;\n\n  return ECardResult::READY;\n}\n\nCMemoryCardDriver::SGameFileSlot::SGameFileSlot() { InitializeFromGameState(); }\n\nCMemoryCardDriver::SGameFileSlot::SGameFileSlot(CMemoryInStream& in) {\n  in.ReadBytes(reinterpret_cast<char*>(x0_saveBuffer.data()), x0_saveBuffer.size());\n  x944_fileInfo = CGameState::LoadGameFileState(x0_saveBuffer.data());\n}\n\nvoid CMemoryCardDriver::SGameFileSlot::InitializeFromGameState() {\n  CMemoryStreamOut w(x0_saveBuffer.data(), x0_saveBuffer.size());\n  g_GameState->PutTo(w);\n  w.Flush();\n  x944_fileInfo = CGameState::LoadGameFileState(x0_saveBuffer.data());\n}\n\nvoid CMemoryCardDriver::SGameFileSlot::LoadGameState(u32 idx) {\n  CMemoryInStream r(x0_saveBuffer.data(), x0_saveBuffer.size(), CMemoryInStream::EOwnerShip::NotOwned);\n  static_cast<MP1::CMain*>(g_Main)->StreamNewGameState(r, idx);\n}\n\nCMemoryCardDriver::SFileInfo::SFileInfo(kabufuda::ECardSlot port, std::string_view name)\n: x0_fileInfo(port), x14_name(name) {}\n\nCMemoryCardDriver::CMemoryCardDriver(kabufuda::ECardSlot cardPort, CAssetId saveBanner, CAssetId saveIcon0,\n                                     CAssetId saveIcon1, bool importPersistent)\n: x0_cardPort(cardPort)\n, x4_saveBanner(saveBanner)\n, x8_saveIcon0(saveIcon0)\n, xc_saveIcon1(saveIcon1)\n, x19d_importPersistent(importPersistent) {\n  x100_mcFileInfos.reserve(2);\n  x100_mcFileInfos.emplace_back(EFileState::Unknown, SFileInfo(x0_cardPort, SaveFileNames[0]));\n  x100_mcFileInfos.emplace_back(EFileState::Unknown, SFileInfo(x0_cardPort, SaveFileNames[1]));\n}\n\nvoid CMemoryCardDriver::NoCardFound() {\n  x10_state = EState::NoCard;\n  static_cast<CMain*>(g_Main)->SetCardBusy(false);\n}\n\nconst CGameState::GameFileStateInfo* CMemoryCardDriver::GetGameFileStateInfo(int idx) const {\n  SGameFileSlot* slot = xe4_fileSlots[idx].get();\n  if (!slot)\n    return nullptr;\n  return &slot->x944_fileInfo;\n}\n\nCMemoryCardDriver::SSaveHeader CMemoryCardDriver::LoadSaveHeader(CMemoryInStream& in) {\n  SSaveHeader ret;\n  ret.x0_version = in.ReadLong();\n  for (bool& present : ret.x4_savePresent) {\n    present = in.ReadBool();\n  }\n  return ret;\n}\n\nstd::unique_ptr<CMemoryCardDriver::SGameFileSlot> CMemoryCardDriver::LoadSaveFile(CMemoryInStream& in) {\n  return std::make_unique<CMemoryCardDriver::SGameFileSlot>(in);\n}\n\nvoid CMemoryCardDriver::ReadFinished() {\n  CMemoryCardSys::CardStat stat = {};\n  SFileInfo& fileInfo = x100_mcFileInfos[x194_fileIdx].second;\n  if (CMemoryCardSys::GetStatus(fileInfo.x0_fileInfo.slot, fileInfo.x0_fileInfo.getFileNo(), stat) !=\n      ECardResult::READY) {\n    NoCardFound();\n    return;\n  }\n\n  x20_fileTime = stat.GetTime();\n  CMemoryInStream r(fileInfo.x34_saveData.data(), 3004);\n  SSaveHeader header = LoadSaveHeader(r);\n  r.ReadBytes(reinterpret_cast<char*>(x30_systemData.data()), x30_systemData.size());\n\n  for (size_t i = 0; i < xe4_fileSlots.size(); ++i) {\n    if (header.x4_savePresent[i]) {\n      xe4_fileSlots[i] = LoadSaveFile(r);\n    }\n  }\n\n  if (x19d_importPersistent) {\n    ImportPersistentOptions();\n  }\n}\n\nvoid CMemoryCardDriver::ImportPersistentOptions() {\n  CMemoryInStream r(x30_systemData.data(), x30_systemData.size(), CMemoryInStream::EOwnerShip::NotOwned);\n  CPersistentOptions opts(r);\n  g_GameState->ImportPersistentOptions(opts);\n}\n\nvoid CMemoryCardDriver::ExportPersistentOptions() {\n  CMemoryInStream r(x30_systemData.data(), x30_systemData.size(), CMemoryInStream::EOwnerShip::NotOwned);\n  CPersistentOptions opts(r);\n  g_GameState->ExportPersistentOptions(opts);\n  CMemoryStreamOut w(x30_systemData.data(), x30_systemData.size());\n  w.Put(opts);\n}\n\nvoid CMemoryCardDriver::CheckCardCapacity() {\n  if (x18_cardFreeBytes < 0x2000 || !x1c_cardFreeFiles)\n    x14_error = EError::CardStillFull;\n}\n\nvoid CMemoryCardDriver::IndexFiles() {\n  x14_error = EError::OK;\n  for (std::pair<EFileState, SFileInfo>& info : x100_mcFileInfos) {\n    if (info.first == EFileState::Unknown) {\n      ECardResult result = info.second.Open();\n      if (result == ECardResult::NOFILE) {\n        info.first = EFileState::NoFile;\n        continue;\n      } else if (result == ECardResult::READY) {\n        CMemoryCardSys::CardStat stat = {};\n        if (CMemoryCardSys::GetStatus(info.second.x0_fileInfo.slot, info.second.x0_fileInfo.getFileNo(), stat) ==\n            ECardResult::READY) {\n          u32 comment = stat.GetCommentAddr();\n          if (comment == UINT32_MAX)\n            info.first = EFileState::BadFile;\n          else\n            info.first = EFileState::File;\n        } else {\n          NoCardFound();\n          return;\n        }\n        if (info.second.Close() == ECardResult::NOCARD) {\n          NoCardFound();\n          return;\n        }\n      } else {\n        NoCardFound();\n        return;\n      }\n    }\n  }\n\n  if (x100_mcFileInfos[0].first == EFileState::File) {\n    if (x100_mcFileInfos[1].first == EFileState::File) {\n      CMemoryCardSys::CardStat stat = {};\n      if (CMemoryCardSys::GetStatus(x100_mcFileInfos[0].second.x0_fileInfo.slot,\n                                    x100_mcFileInfos[0].second.x0_fileInfo.getFileNo(), stat) == ECardResult::READY) {\n        u32 timeA = stat.GetTime();\n        if (CMemoryCardSys::GetStatus(x100_mcFileInfos[1].second.x0_fileInfo.slot,\n                                      x100_mcFileInfos[1].second.x0_fileInfo.getFileNo(), stat) == ECardResult::READY) {\n          u32 timeB = stat.GetTime();\n          if (timeA > timeB)\n            x194_fileIdx = 0;\n          else\n            x194_fileIdx = 1;\n          StartFileRead();\n          return;\n        }\n        NoCardFound();\n        return;\n      }\n      NoCardFound();\n      return;\n    }\n    x194_fileIdx = 0;\n    StartFileRead();\n    return;\n  }\n\n  if (x100_mcFileInfos[1].first == EFileState::File) {\n    x194_fileIdx = 1;\n    StartFileRead();\n    return;\n  }\n\n  if (x100_mcFileInfos[0].first == EFileState::BadFile || x100_mcFileInfos[1].first == EFileState::BadFile) {\n    x10_state = EState::FileBad;\n    x14_error = EError::FileCorrupted;\n  } else {\n    x10_state = EState::FileBad;\n    x14_error = EError::FileMissing;\n  }\n}\n\nvoid CMemoryCardDriver::StartCardProbe() {\n  x10_state = EState::CardProbe;\n  x14_error = EError::OK;\n  UpdateCardProbe();\n}\n\nvoid CMemoryCardDriver::StartMountCard() {\n  x10_state = EState::CardMount;\n  x14_error = EError::OK;\n  ECardResult result = CMemoryCardSys::MountCard(x0_cardPort);\n  if (result != ECardResult::READY)\n    UpdateMountCard(result);\n}\n\nvoid CMemoryCardDriver::StartCardCheck() {\n  x14_error = EError::OK;\n  x10_state = EState::CardCheck;\n  ECardResult result = CMemoryCardSys::CheckCard(x0_cardPort);\n  if (result != ECardResult::READY)\n    UpdateCardCheck(result);\n}\n\nvoid CMemoryCardDriver::StartFileDeleteBad() {\n  x14_error = EError::OK;\n  x10_state = EState::FileDeleteBad;\n  int idx = 0;\n  for (std::pair<EFileState, SFileInfo>& info : x100_mcFileInfos) {\n    if (info.first == EFileState::BadFile) {\n      x194_fileIdx = idx;\n      ECardResult result = CMemoryCardSys::FastDeleteFile(x0_cardPort, info.second.GetFileNo());\n      if (result != ECardResult::READY) {\n        UpdateFileDeleteBad(result);\n        return;\n      }\n    }\n    ++idx;\n  }\n}\n\nvoid CMemoryCardDriver::StartFileRead() {\n  x14_error = EError::OK;\n  x10_state = EState::FileRead;\n  ECardResult result = x100_mcFileInfos[x194_fileIdx].second.Open();\n  if (result != ECardResult::READY) {\n    UpdateFileRead(result);\n    return;\n  }\n\n  result = x100_mcFileInfos[x194_fileIdx].second.StartRead();\n  if (result != ECardResult::READY)\n    UpdateFileRead(result);\n}\n\nvoid CMemoryCardDriver::StartFileDeleteAlt() {\n  x14_error = EError::OK;\n  x10_state = EState::FileDeleteAlt;\n  SFileInfo& fileInfo = x100_mcFileInfos[!bool(x194_fileIdx)].second;\n  ECardResult result = CMemoryCardSys::FastDeleteFile(x0_cardPort, fileInfo.GetFileNo());\n  if (result != ECardResult::READY)\n    UpdateFileDeleteAlt(result);\n}\n\nvoid CMemoryCardDriver::StartFileCreate() {\n  x14_error = EError::OK;\n  x10_state = EState::FileCreate;\n  if (x18_cardFreeBytes < 8192 || x1c_cardFreeFiles < 2) {\n    x10_state = EState::FileCreateFailed;\n    x14_error = EError::CardFull;\n    return;\n  }\n\n  x194_fileIdx = 0;\n  x198_fileInfo = std::make_unique<CMemoryCardSys::CCardFileInfo>(x0_cardPort, SaveFileNames[x194_fileIdx]);\n  InitializeFileInfo();\n  ECardResult result = x198_fileInfo->CreateFile();\n  if (result != ECardResult::READY)\n    UpdateFileCreate(result);\n}\n\nvoid CMemoryCardDriver::StartFileWrite() {\n  x14_error = EError::OK;\n  x10_state = EState::FileWrite;\n  ECardResult result = x198_fileInfo->WriteFile();\n  if (result != ECardResult::READY)\n    UpdateFileWrite(result);\n}\n\nvoid CMemoryCardDriver::StartFileCreateTransactional() {\n  x14_error = EError::OK;\n  x10_state = EState::FileCreateTransactional;\n  ClearFileInfo();\n  if (x18_cardFreeBytes < 8192 || !x1c_cardFreeFiles) {\n    x10_state = EState::FileCreateTransactionalFailed;\n    x14_error = EError::CardFull;\n    return;\n  }\n\n  x194_fileIdx = !bool(x194_fileIdx);\n  x198_fileInfo = std::make_unique<CMemoryCardSys::CCardFileInfo>(x0_cardPort, SaveFileNames[x194_fileIdx]);\n  InitializeFileInfo();\n  ECardResult result = x198_fileInfo->CreateFile();\n  if (result != ECardResult::READY)\n    UpdateFileCreateTransactional(result);\n}\n\nvoid CMemoryCardDriver::StartFileWriteTransactional() {\n  x14_error = EError::OK;\n  x10_state = EState::FileWriteTransactional;\n  ECardResult result = x198_fileInfo->WriteFile();\n  if (result != ECardResult::READY)\n    UpdateFileWriteTransactional(result);\n}\n\nvoid CMemoryCardDriver::StartFileDeleteAltTransactional() {\n  x14_error = EError::OK;\n  x10_state = EState::FileDeleteAltTransactional;\n  ECardResult result = CMemoryCardSys::DeleteFile(x0_cardPort, SaveFileNames[!bool(x194_fileIdx)]);\n  if (result != ECardResult::READY)\n    UpdateFileDeleteAltTransactional(result);\n}\n\nvoid CMemoryCardDriver::StartFileRenameBtoA() {\n  if (x194_fileIdx == 1) {\n    /* Rename B file to A file (ideally the card is always left with 'A' only) */\n    x14_error = EError::OK;\n    x10_state = EState::FileRenameBtoA;\n    ECardResult result =\n        CMemoryCardSys::Rename(x0_cardPort, SaveFileNames[x194_fileIdx], SaveFileNames[!bool(x194_fileIdx)]);\n    if (result != ECardResult::READY)\n      UpdateFileRenameBtoA(result);\n  } else {\n    x10_state = EState::DriverClosed;\n    WriteBackupBuf();\n  }\n}\n\nvoid CMemoryCardDriver::StartCardFormat() {\n  x14_error = EError::OK;\n  x10_state = EState::CardFormat;\n  ECardResult result = CMemoryCardSys::FormatCard(x0_cardPort);\n  if (result != ECardResult::READY)\n    UpdateCardFormat(result);\n}\n\nvoid CMemoryCardDriver::UpdateMountCard(ECardResult result) {\n  switch (result) {\n  case ECardResult::READY:\n    x10_state = EState::CardMountDone;\n    StartCardCheck();\n    break;\n  case ECardResult::BROKEN:\n    x10_state = EState::CardMountDone;\n    x14_error = EError::CardBroken;\n    // StartCardCheck();\n    break;\n  default:\n    HandleCardError(result, EState::CardMountFailed);\n    break;\n  }\n}\n\nvoid CMemoryCardDriver::UpdateCardProbe() {\n  auto result = CMemoryCardSys::CardProbe(x0_cardPort);\n  switch (result.x0_error) {\n  case ECardResult::READY:\n    if (result.x8_sectorSize != 0x2000) {\n      x10_state = EState::CardProbeFailed;\n      x14_error = EError::CardNon8KSectors;\n      return;\n    }\n    x10_state = EState::CardProbeDone;\n    StartMountCard();\n    break;\n  case ECardResult::BUSY:\n    break;\n  case ECardResult::WRONGDEVICE:\n    x10_state = EState::CardProbeFailed;\n    x14_error = EError::CardWrongDevice;\n    break;\n  default:\n    NoCardFound();\n    break;\n  }\n}\n\nvoid CMemoryCardDriver::UpdateCardCheck(ECardResult result) {\n  switch (result) {\n  case ECardResult::READY:\n    x10_state = EState::CardCheckDone;\n    if (!GetCardFreeBytes())\n      return;\n    if (CMemoryCardSys::GetSerialNo(x0_cardPort, x28_cardSerial) == ECardResult::READY)\n      return;\n    NoCardFound();\n    break;\n  case ECardResult::BROKEN:\n    x10_state = EState::CardCheckFailed;\n    x14_error = EError::CardBroken;\n    break;\n  default:\n    HandleCardError(result, EState::CardCheckFailed);\n  }\n}\n\nvoid CMemoryCardDriver::UpdateFileDeleteBad(ECardResult result) {\n  if (result == ECardResult::READY) {\n    x100_mcFileInfos[x194_fileIdx].first = EFileState::NoFile;\n    if (x100_mcFileInfos[!bool(x194_fileIdx)].first == EFileState::BadFile) {\n      x10_state = EState::FileBad;\n      StartFileDeleteBad();\n    } else {\n      x10_state = EState::CardCheckDone;\n      if (!GetCardFreeBytes())\n        return;\n      IndexFiles();\n    }\n  } else\n    HandleCardError(result, EState::FileDeleteBadFailed);\n}\n\nvoid CMemoryCardDriver::UpdateFileRead(ECardResult result) {\n  if (result == ECardResult::READY) {\n    auto& fileInfo = x100_mcFileInfos[x194_fileIdx];\n    ECardResult readRes = fileInfo.second.TryFileRead();\n    if (fileInfo.second.Close() != ECardResult::READY) {\n      NoCardFound();\n      return;\n    }\n\n    u32 altFileIdx = !bool(x194_fileIdx);\n    if (readRes == ECardResult::READY) {\n      x10_state = EState::Ready;\n      ReadFinished();\n      EFileState fileSt = x100_mcFileInfos[altFileIdx].first;\n      if (fileSt == EFileState::NoFile)\n        CheckCardCapacity();\n      else\n        StartFileDeleteAlt();\n      return;\n    }\n\n    if (readRes == ECardResult::CRC_MISMATCH) {\n      x100_mcFileInfos[x194_fileIdx].first = EFileState::BadFile;\n      if (x100_mcFileInfos[altFileIdx].first == EFileState::File) {\n        x10_state = EState::CardCheckDone;\n        IndexFiles();\n      } else {\n        x10_state = EState::FileBad;\n        x14_error = EError::FileCorrupted;\n      }\n    }\n  } else\n    HandleCardError(result, EState::FileBad);\n}\n\nvoid CMemoryCardDriver::UpdateFileDeleteAlt(ECardResult result) {\n  if (result == ECardResult::READY) {\n    x10_state = EState::Ready;\n    if (GetCardFreeBytes())\n      CheckCardCapacity();\n  } else\n    HandleCardError(result, EState::FileDeleteAltFailed);\n}\n\nvoid CMemoryCardDriver::UpdateFileCreate(ECardResult result) {\n  if (result == ECardResult::READY) {\n    x10_state = EState::FileCreateDone;\n    StartFileWrite();\n  } else\n    HandleCardError(result, EState::FileCreateFailed);\n}\n\nvoid CMemoryCardDriver::UpdateFileWrite(ECardResult result) {\n  if (result == ECardResult::READY) {\n    ECardResult xferResult = x198_fileInfo->PumpCardTransfer();\n    if (xferResult == ECardResult::READY) {\n      x10_state = EState::Ready;\n      if (x198_fileInfo->CloseFile() == ECardResult::READY) {\n        CMemoryCardSys::CommitToDisk(x0_cardPort);\n        return;\n      }\n      NoCardFound();\n      return;\n    }\n    if (xferResult == ECardResult::BUSY)\n      return;\n    if (xferResult == ECardResult::IOERROR) {\n      x10_state = EState::FileWriteFailed;\n      x14_error = EError::CardIOError;\n      return;\n    }\n    NoCardFound();\n  } else\n    HandleCardError(result, EState::FileWriteFailed);\n}\n\nvoid CMemoryCardDriver::UpdateFileCreateTransactional(ECardResult result) {\n  if (result == ECardResult::READY) {\n    x10_state = EState::FileCreateTransactionalDone;\n    StartFileWriteTransactional();\n  } else\n    HandleCardError(result, EState::FileCreateTransactionalFailed);\n}\n\nvoid CMemoryCardDriver::UpdateFileWriteTransactional(ECardResult result) {\n  if (result == ECardResult::READY) {\n    ECardResult xferResult = x198_fileInfo->PumpCardTransfer();\n    if (xferResult == ECardResult::READY) {\n      x10_state = EState::FileWriteTransactionalDone;\n      if (x198_fileInfo->CloseFile() != ECardResult::READY) {\n        NoCardFound();\n        return;\n      }\n      StartFileDeleteAltTransactional();\n      return;\n    }\n    if (xferResult == ECardResult::BUSY)\n      return;\n    if (xferResult == ECardResult::IOERROR) {\n      x10_state = EState::FileWriteTransactionalFailed;\n      x14_error = EError::CardIOError;\n      return;\n    }\n    NoCardFound();\n  } else\n    HandleCardError(result, EState::FileWriteTransactionalFailed);\n}\n\nvoid CMemoryCardDriver::UpdateFileDeleteAltTransactional(ECardResult result) {\n  if (result == ECardResult::READY) {\n    x10_state = EState::FileDeleteAltTransactionalDone;\n    if (GetCardFreeBytes())\n      StartFileRenameBtoA();\n  } else\n    HandleCardError(result, EState::FileDeleteAltTransactionalFailed);\n}\n\nvoid CMemoryCardDriver::UpdateFileRenameBtoA(ECardResult result) {\n  if (result == ECardResult::READY) {\n    x10_state = EState::DriverClosed;\n    CMemoryCardSys::CommitToDisk(x0_cardPort);\n    WriteBackupBuf();\n  } else\n    HandleCardError(result, EState::FileRenameBtoAFailed);\n}\n\nvoid CMemoryCardDriver::UpdateCardFormat(ECardResult result) {\n  if (result == ECardResult::READY)\n    x10_state = EState::CardFormatted;\n  else if (result == ECardResult::BROKEN) {\n    x10_state = EState::CardFormatFailed;\n    x14_error = EError::CardIOError;\n  } else\n    HandleCardError(result, EState::CardFormatFailed);\n}\n\nvoid CMemoryCardDriver::BuildNewFileSlot(u32 saveIdx) {\n  g_GameState->SetFileIdx(saveIdx);\n  bool fusionBackup = g_GameState->SystemOptions().GetPlayerFusionSuitActive();\n\n  std::unique_ptr<SGameFileSlot>& slot = xe4_fileSlots[saveIdx];\n  if (!slot)\n    slot = std::make_unique<SGameFileSlot>();\n  slot->LoadGameState(saveIdx);\n\n  CMemoryInStream r(x30_systemData.data(), x30_systemData.size());\n  g_GameState->ReadPersistentOptions(r);\n  ImportPersistentOptions();\n  g_GameState->SetCardSerial(x28_cardSerial);\n  g_GameState->SystemOptions().SetPlayerFusionSuitActive(fusionBackup);\n}\n\nvoid CMemoryCardDriver::EraseFileSlot(u32 saveIdx) { xe4_fileSlots[saveIdx].reset(); }\n\nvoid CMemoryCardDriver::BuildExistingFileSlot(u32 saveIdx) {\n  g_GameState->SetFileIdx(saveIdx);\n\n  std::unique_ptr<SGameFileSlot>& slot = xe4_fileSlots[saveIdx];\n  if (!slot)\n    slot = std::make_unique<SGameFileSlot>();\n  else\n    slot->InitializeFromGameState();\n\n  CMemoryStreamOut w(x30_systemData.data(), x30_systemData.size());\n  g_GameState->PutTo(w);\n}\n\nvoid CMemoryCardDriver::InitializeFileInfo() {\n  ExportPersistentOptions();\n\n  OSCalendarTime time = CBasics::ToCalendarTime(std::chrono::system_clock::now());\n  std::string timeString = fmt::format(\"{:02d}.{:02d}.{:02d}  {:02d}:{:02d}\", time.x10_mon + 1,\n                                       time.xc_mday, time.x14_year % 100, time.x8_hour, time.x4_min);\n  std::string comment(\"Metroid Prime                   \");\n  comment += timeString;\n  x198_fileInfo->SetComment(comment);\n\n  x198_fileInfo->LockBannerToken(x4_saveBanner, *g_SimplePool);\n  x198_fileInfo->LockIconToken(x8_saveIcon0, kabufuda::EAnimationSpeed::Middle, *g_SimplePool);\n\n  CMemoryStreamOut w = x198_fileInfo->BeginMemoryOut(3004);\n\n  SSaveHeader header;\n  for (size_t i = 0; i < xe4_fileSlots.size(); ++i) {\n    header.x4_savePresent[i] = xe4_fileSlots[i] != nullptr;\n  }\n  header.DoPut(w);\n\n  w.Put(x30_systemData.data(), x30_systemData.size());\n\n  for (auto& fileSlot : xe4_fileSlots) {\n    if (fileSlot) {\n      fileSlot->DoPut(w);\n    }\n  }\n}\n\nvoid CMemoryCardDriver::WriteBackupBuf() {\n  g_GameState->WriteBackupBuf();\n  g_GameState->SetCardSerial(x28_cardSerial);\n}\n\nbool CMemoryCardDriver::GetCardFreeBytes() {\n  ECardResult result = CMemoryCardSys::GetNumFreeBytes(x0_cardPort, x18_cardFreeBytes, x1c_cardFreeFiles);\n  if (result == ECardResult::READY)\n    return true;\n  NoCardFound();\n  return false;\n}\n\nvoid CMemoryCardDriver::HandleCardError(ECardResult result, EState state) {\n  switch (result) {\n  case ECardResult::WRONGDEVICE:\n    x10_state = state;\n    x14_error = EError::CardWrongDevice;\n    break;\n  case ECardResult::NOCARD:\n    NoCardFound();\n    break;\n  case ECardResult::IOERROR:\n    x10_state = state;\n    x14_error = EError::CardIOError;\n    break;\n  case ECardResult::ENCODING:\n    x10_state = state;\n    x14_error = EError::CardWrongCharacterSet;\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CMemoryCardDriver::Update() {\n  kabufuda::ProbeResults result = CMemoryCardSys::CardProbe(x0_cardPort);\n\n  if (result.x0_error == ECardResult::NOCARD) {\n    if (x10_state != EState::NoCard)\n      NoCardFound();\n    static_cast<CMain*>(g_Main)->SetCardBusy(false);\n    return;\n  }\n\n  if (x10_state == EState::CardProbe) {\n    UpdateCardProbe();\n    static_cast<CMain*>(g_Main)->SetCardBusy(false);\n    return;\n  }\n\n  ECardResult resultCode = CMemoryCardSys::GetResultCode(x0_cardPort);\n  bool cardBusy = false;\n\n  if (IsCardBusy(x10_state)) {\n    cardBusy = true;\n\n    switch (x10_state) {\n    case EState::CardMount:\n      UpdateMountCard(resultCode);\n      break;\n    case EState::CardCheck:\n      UpdateCardCheck(resultCode);\n      break;\n    case EState::FileDeleteBad:\n      UpdateFileDeleteBad(resultCode);\n      break;\n    case EState::FileRead:\n      UpdateFileRead(resultCode);\n      break;\n    case EState::FileDeleteAlt:\n      UpdateFileDeleteAlt(resultCode);\n      break;\n    case EState::FileCreate:\n      UpdateFileCreate(resultCode);\n      break;\n    case EState::FileWrite:\n      UpdateFileWrite(resultCode);\n      break;\n    case EState::FileCreateTransactional:\n      UpdateFileCreateTransactional(resultCode);\n      break;\n    case EState::FileWriteTransactional:\n      UpdateFileWriteTransactional(resultCode);\n      break;\n    case EState::FileDeleteAltTransactional:\n      UpdateFileDeleteAltTransactional(resultCode);\n      break;\n    case EState::FileRenameBtoA:\n      UpdateFileRenameBtoA(resultCode);\n      break;\n    case EState::CardFormat:\n      UpdateCardFormat(resultCode);\n      break;\n    default:\n      break;\n    }\n  }\n\n  static_cast<CMain*>(g_Main)->SetCardBusy(cardBusy);\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/CMemoryCardDriver.hpp",
    "content": "#pragma once\n\n#include <array>\n#include <memory>\n#include <string>\n#include <vector>\n\n#include \"Runtime/CGameState.hpp\"\n#include \"Runtime/CMemoryCardSys.hpp\"\n\nnamespace metaforce::MP1 {\n\nclass CMemoryCardDriver {\n  friend class CSaveGameScreen;\n\npublic:\n  enum class EState {\n    Initial = 0,\n    Ready = 1,\n    NoCard = 2,\n    DriverClosed = 3,\n    CardFormatted = 4,\n    CardProbeDone = 5,\n    CardMountDone = 6,\n    CardCheckDone = 7,\n    FileCreateDone = 8,\n    FileCreateTransactionalDone = 9,\n    FileWriteTransactionalDone = 10,\n    FileDeleteAltTransactionalDone = 11,\n    CardProbeFailed = 12,\n    CardMountFailed = 13,\n    CardCheckFailed = 14,\n    FileDeleteBadFailed = 15,\n    FileDeleteAltFailed = 16,\n    FileBad = 17,\n    FileCreateFailed = 18,\n    FileWriteFailed = 19,\n    FileCreateTransactionalFailed = 20,\n    FileWriteTransactionalFailed = 21,\n    FileDeleteAltTransactionalFailed = 22,\n    FileRenameBtoAFailed = 23,\n    CardFormatFailed = 24,\n    CardProbe = 25,\n    CardMount = 26,\n    CardCheck = 27,\n    FileDeleteBad = 28,\n    FileRead = 29,\n    FileDeleteAlt = 30,\n    FileCreate = 31,\n    FileWrite = 32,\n    FileCreateTransactional = 33,\n    FileWriteTransactional = 34,\n    FileDeleteAltTransactional = 35,\n    FileRenameBtoA = 36,\n    CardFormat = 37\n  };\n\n  enum class EError {\n    OK,\n    CardBroken,\n    CardWrongCharacterSet,\n    CardIOError,\n    CardWrongDevice,\n    CardFull,\n    CardStillFull, /* After attempting alt-delete (if needed) */\n    CardNon8KSectors,\n    FileMissing,\n    FileCorrupted\n  };\n\nprivate:\n  struct SFileInfo {\n    CMemoryCardSys::CardFileHandle x0_fileInfo;\n\n    std::string x14_name;\n    std::vector<u8> x24_saveFileData;\n    std::vector<u8> x34_saveData;\n    SFileInfo(kabufuda::ECardSlot cardPort, std::string_view name);\n    kabufuda::ECardResult Open();\n    kabufuda::ECardResult Close();\n    kabufuda::ECardSlot GetFileCardPort() const { return x0_fileInfo.slot; }\n    int GetFileNo() const { return x0_fileInfo.getFileNo(); }\n    kabufuda::ECardResult StartRead();\n    kabufuda::ECardResult TryFileRead();\n    kabufuda::ECardResult FileRead();\n    kabufuda::ECardResult GetSaveDataOffset(u32& offOut) const;\n  };\n\n  struct SSaveHeader {\n    u32 x0_version = 0;\n    std::array<bool, 3> x4_savePresent{};\n\n    void DoPut(CMemoryStreamOut& out) const {\n      out.WriteLong(x0_version);\n      for (const bool savePresent : x4_savePresent) {\n        out.Put(savePresent);\n      }\n    }\n  };\n\n  struct SGameFileSlot {\n    std::array<u8, 940> x0_saveBuffer{};\n    CGameState::GameFileStateInfo x944_fileInfo;\n\n    SGameFileSlot();\n    explicit SGameFileSlot(CMemoryInStream& in);\n    void InitializeFromGameState();\n    void LoadGameState(u32 idx);\n    void DoPut(CMemoryStreamOut& w) const { w.Put(x0_saveBuffer.data(), x0_saveBuffer.size()); }\n  };\n\n  enum class EFileState { Unknown, NoFile, File, BadFile };\n\n  kabufuda::ECardSlot x0_cardPort;\n  CAssetId x4_saveBanner;\n  CAssetId x8_saveIcon0;\n  CAssetId xc_saveIcon1;\n  EState x10_state = EState::Initial;\n  EError x14_error = EError::OK;\n  s32 x18_cardFreeBytes = 0;\n  s32 x1c_cardFreeFiles = 0;\n  u32 x20_fileTime = 0;\n  u64 x28_cardSerial = 0;\n  std::array<u8, 174> x30_systemData{};\n  std::array<std::unique_ptr<SGameFileSlot>, 3> xe4_fileSlots;\n  std::vector<std::pair<EFileState, SFileInfo>> x100_mcFileInfos;\n  u32 x194_fileIdx = -1;\n  std::unique_ptr<CMemoryCardSys::CCardFileInfo> x198_fileInfo;\n  bool x19c_ = false;\n  bool x19d_importPersistent;\n\npublic:\n  CMemoryCardDriver(kabufuda::ECardSlot cardPort, CAssetId saveBanner, CAssetId saveIcon0, CAssetId saveIcon1,\n                    bool importPersistent);\n\n  void NoCardFound();\n  const CGameState::GameFileStateInfo* GetGameFileStateInfo(int idx) const;\n  static SSaveHeader LoadSaveHeader(CMemoryInStream& in);\n  static std::unique_ptr<SGameFileSlot> LoadSaveFile(CMemoryInStream& in);\n  void ReadFinished();\n  void ImportPersistentOptions();\n  void ExportPersistentOptions();\n  void CheckCardCapacity();\n  void IndexFiles();\n\n  void StartCardProbe();                  // 25\n  void StartMountCard();                  // 26\n  void StartCardCheck();                  // 27\n  void StartFileDeleteBad();              // 28\n  void StartFileRead();                   // 29\n  void StartFileDeleteAlt();              // 30\n  void StartFileCreate();                 // 31\n  void StartFileWrite();                  // 32\n  void StartFileCreateTransactional();    // 33\n  void StartFileWriteTransactional();     // 34\n  void StartFileDeleteAltTransactional(); // 35\n  void StartFileRenameBtoA();             // 36\n  void StartCardFormat();                 // 37\n\n  void UpdateCardProbe();                                              // 25\n  void UpdateMountCard(kabufuda::ECardResult result);                  // 26\n  void UpdateCardCheck(kabufuda::ECardResult result);                  // 27\n  void UpdateFileDeleteBad(kabufuda::ECardResult result);              // 28\n  void UpdateFileRead(kabufuda::ECardResult result);                   // 29\n  void UpdateFileDeleteAlt(kabufuda::ECardResult result);              // 30\n  void UpdateFileCreate(kabufuda::ECardResult result);                 // 31\n  void UpdateFileWrite(kabufuda::ECardResult result);                  // 32\n  void UpdateFileCreateTransactional(kabufuda::ECardResult result);    // 33\n  void UpdateFileWriteTransactional(kabufuda::ECardResult result);     // 34\n  void UpdateFileDeleteAltTransactional(kabufuda::ECardResult result); // 35\n  void UpdateFileRenameBtoA(kabufuda::ECardResult result);             // 36\n  void UpdateCardFormat(kabufuda::ECardResult result);                 // 37\n\n  void ClearFileInfo() { x198_fileInfo.reset(); }\n  void BuildNewFileSlot(u32 saveIdx);\n  void EraseFileSlot(u32 saveIdx);\n  void BuildExistingFileSlot(u32 saveIdx);\n  void InitializeFileInfo();\n  void WriteBackupBuf();\n  bool GetCardFreeBytes();\n  void HandleCardError(kabufuda::ECardResult result, EState state);\n  void Update();\n  void ClearError() { x14_error = EError::OK; }\n\n  static bool IsCardBusy(EState v) { return v >= EState::CardMount && v <= EState::CardFormat; }\n\n  static bool IsCardWriting(EState v) {\n    if (v < EState::CardProbe)\n      return false;\n    if (v == EState::CardCheck)\n      return false;\n    if (v == EState::FileRead)\n      return false;\n    return true;\n  }\n};\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/CMessageScreen.cpp",
    "content": "#include \"Runtime/MP1/CMessageScreen.hpp\"\n\n#include \"Runtime/CGameState.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Audio/CSfxManager.hpp\"\n#include \"Runtime/GuiSys/CGuiModel.hpp\"\n#include \"Runtime/GuiSys/CGuiTextPane.hpp\"\n#include \"Runtime/GuiSys/CGuiWidgetDrawParms.hpp\"\n#include \"Runtime/Input/CFinalInput.hpp\"\n\nnamespace metaforce::MP1 {\n\nCMessageScreen::CMessageScreen(CAssetId msg, float delayTime) : x74_delayTime(delayTime) {\n  x0_msg = g_SimplePool->GetObj({FOURCC('STRG'), msg});\n  xc_msgScreen = g_SimplePool->GetObj(\"FRME_MsgScreen\");\n}\n\nvoid CMessageScreen::ProcessControllerInput(const CFinalInput& input) {\n  if (!x18_loadedMsgScreen || x74_delayTime > 0.f ||\n      !(input.PA() || input.PSpecialKey(ESpecialKey::Enter) || input.PMouseButton(EMouseButton::Primary)))\n    return;\n\n  if (x1c_textpane_message->TextSupport().GetCurTime() < x1c_textpane_message->TextSupport().GetTotalAnimationTime()) {\n    x1c_textpane_message->TextSupport().SetCurTime(x1c_textpane_message->TextSupport().GetTotalAnimationTime());\n    return;\n  }\n\n  x6c_page += 1;\n\n  if (x6c_page >= x0_msg->GetStringCount()) {\n    x78_24_exit = true;\n    return;\n  }\n\n  x1c_textpane_message->TextSupport().SetTypeWriteEffectOptions(false, 0.1f, 30.f);\n  x1c_textpane_message->TextSupport().SetText(x0_msg->GetString(x6c_page));\n\n  CSfxManager::SfxStart(SFXui_message_screen_key, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n\n  x74_delayTime = 0.8f;\n}\n\nbool CMessageScreen::Update(float dt, float blurAmt) {\n  x70_blurAmt = blurAmt;\n  if (!x18_loadedMsgScreen && xc_msgScreen.IsLoaded() && x0_msg.IsLoaded()) {\n    x18_loadedMsgScreen = xc_msgScreen.GetObj();\n    x18_loadedMsgScreen->Reset();\n    x1c_textpane_message = static_cast<CGuiTextPane*>(x18_loadedMsgScreen->FindWidget(\"textpane_message\"));\n    x20_basewidget_top = x18_loadedMsgScreen->FindWidget(\"basewidget_top\");\n    x24_basewidget_center = x18_loadedMsgScreen->FindWidget(\"basewidget_center\");\n    x28_basewidget_bottom = x18_loadedMsgScreen->FindWidget(\"basewidget_bottom\");\n    x2c_model_abutton = static_cast<CGuiModel*>(x18_loadedMsgScreen->FindWidget(\"model_abutton\"));\n    x30_model_top = static_cast<CGuiModel*>(x18_loadedMsgScreen->FindWidget(\"model_top\"));\n    x38_model_bottom = static_cast<CGuiModel*>(x18_loadedMsgScreen->FindWidget(\"model_bottom\"));\n    x34_model_center = static_cast<CGuiModel*>(x18_loadedMsgScreen->FindWidget(\"model_center\"));\n    x3c_model_bg = static_cast<CGuiModel*>(x18_loadedMsgScreen->FindWidget(\"model_bg\"));\n    x40_model_videoband = static_cast<CGuiModel*>(x18_loadedMsgScreen->FindWidget(\"model_videoband\"));\n    x44_topPos = x20_basewidget_top->GetLocalPosition();\n    x50_bottomPos = x28_basewidget_bottom->GetLocalPosition();\n    x5c_videoBandPos = x40_model_videoband->GetLocalPosition();\n\n    if (CGuiWidget* w = x18_loadedMsgScreen->FindWidget(\"basewidget_top\"))\n      w->SetColor(g_tweakGuiColors->GetHudFrameColor());\n    if (CGuiWidget* w = x18_loadedMsgScreen->FindWidget(\"basewidget_centerdeco\"))\n      w->SetColor(g_tweakGuiColors->GetHudFrameColor());\n    if (CGuiWidget* w = x18_loadedMsgScreen->FindWidget(\"model_bottom\"))\n      w->SetColor(g_tweakGuiColors->GetHudFrameColor());\n\n    x40_model_videoband->SetDepthGreater(true);\n    x30_model_top->SetDepthWrite(true);\n    x38_model_bottom->SetDepthWrite(true);\n    x34_model_center->SetDepthWrite(true);\n    x3c_model_bg->SetDepthWrite(true);\n\n    if (x0_msg->GetStringCount()) {\n      x1c_textpane_message->TextSupport().SetTypeWriteEffectOptions(false, 0.1f, 30.f);\n      x1c_textpane_message->TextSupport().SetText(x0_msg->GetString(0));\n      x1c_textpane_message->TextSupport().SetFontColor(g_tweakGuiColors->GetHudMessageFill());\n      x1c_textpane_message->TextSupport().SetControlTXTRMap(&g_GameState->GameOptions().GetControlTXTRMap());\n    }\n  }\n\n  if (x18_loadedMsgScreen) {\n    if (x74_delayTime > 0.f)\n      x74_delayTime -= dt;\n\n    float xT = std::max(0.f, (x70_blurAmt - 0.5f) / 0.5f);\n    float scaleX;\n    if (xT < 0.7f)\n      scaleX = xT / 0.7f;\n    else if (xT < 0.85f)\n      scaleX = 0.1f * (1.f - (xT - 0.7f) / 0.15f) + 0.9f;\n    else\n      scaleX = 0.1f * ((xT - 0.7f - 0.15f) / 0.3f) + 0.9f;\n\n    x24_basewidget_center->SetLocalTransform(zeus::CTransform::Scale(scaleX, 1.f, 1.f));\n    x20_basewidget_top->SetLocalTransform(zeus::CTransform::Translate(0.f, 0.f, 12.f * (1.f - xT)));\n    x28_basewidget_bottom->SetLocalTransform(zeus::CTransform::Translate(0.f, 0.f, -12.f * (1.f - xT)));\n\n    float alpha = std::max(0.f, (x70_blurAmt - 0.7f) / 0.3f);\n    zeus::CColor color = g_tweakGuiColors->GetHudFrameColor();\n    color.a() *= alpha;\n    x20_basewidget_top->SetColor(color);\n    x28_basewidget_bottom->SetColor(zeus::CColor(1.f, alpha));\n\n    float pulse = 0.f;\n    if (x74_delayTime <= 0.f)\n      pulse = zeus::clamp(0.f, 0.5f * (1.f + std::sin(5.f * CGraphics::GetSecondsMod900() - M_PIF / 2.f)), 1.f);\n    x2c_model_abutton->SetColor(zeus::CColor(1.f, pulse));\n\n    x68_videoBandOffset += 12.f * dt;\n    if (x68_videoBandOffset > 10.f)\n      x68_videoBandOffset -= 20.f;\n\n    x40_model_videoband->SetColor(zeus::CColor(1.f, 0.04f * (rand() / float(RAND_MAX)) + 0.08f));\n    x40_model_videoband->SetLocalTransform(\n        zeus::CTransform::Translate(x5c_videoBandPos + zeus::CVector3f(0.f, 0.f, x68_videoBandOffset)));\n\n    x18_loadedMsgScreen->Update(dt);\n  }\n\n  return !x78_24_exit;\n}\n\nvoid CMessageScreen::Draw() const {\n  if (!x18_loadedMsgScreen)\n    return;\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CMessageScreen::Draw\", zeus::skPurple);\n\n  x18_loadedMsgScreen->Draw(CGuiWidgetDrawParms(x70_blurAmt, zeus::skZero3f));\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/CMessageScreen.hpp",
    "content": "#pragma once\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/GuiSys/CGuiFrame.hpp\"\n#include \"Runtime/GuiSys/CStringTable.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nstruct CFinalInput;\nclass CGuiModel;\nclass CGuiTextPane;\nclass CGuiWidget;\n\nnamespace MP1 {\n\nclass CMessageScreen {\n  TLockedToken<CStringTable> x0_msg;\n  TLockedToken<CGuiFrame> xc_msgScreen;\n  CGuiFrame* x18_loadedMsgScreen = nullptr;\n  CGuiTextPane* x1c_textpane_message = nullptr;\n  CGuiWidget* x20_basewidget_top = nullptr;\n  CGuiWidget* x24_basewidget_center = nullptr;\n  CGuiWidget* x28_basewidget_bottom = nullptr;\n  CGuiModel* x2c_model_abutton = nullptr;\n  CGuiModel* x30_model_top = nullptr;\n  CGuiModel* x34_model_center = nullptr;\n  CGuiModel* x38_model_bottom = nullptr;\n  CGuiModel* x3c_model_bg = nullptr;\n  CGuiModel* x40_model_videoband = nullptr;\n  zeus::CVector3f x44_topPos;\n  zeus::CVector3f x50_bottomPos;\n  zeus::CVector3f x5c_videoBandPos;\n  float x68_videoBandOffset = 10.f;\n  u32 x6c_page = 0;\n  float x70_blurAmt = 0.f;\n  float x74_delayTime;\n  bool x78_24_exit : 1 = false;\n\npublic:\n  explicit CMessageScreen(CAssetId msg, float time);\n  void ProcessControllerInput(const CFinalInput& input);\n  bool Update(float dt, float blurAmt);\n  void Draw() const;\n};\n\n} // namespace MP1\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/MP1/COptionsScreen.cpp",
    "content": "#include \"Runtime/MP1/COptionsScreen.hpp\"\n\n#include \"Runtime/IMain.hpp\"\n#include \"Runtime/CArchitectureQueue.hpp\"\n#include \"Runtime/GuiSys/CGuiSliderGroup.hpp\"\n#include \"Runtime/GuiSys/CGuiTableGroup.hpp\"\n#include \"Runtime/GuiSys/CGuiTextPane.hpp\"\n#include \"Runtime/Input/RumbleFxTable.hpp\"\n\nnamespace metaforce::MP1 {\n\nCOptionsScreen::COptionsScreen(const CStateManager& mgr, CGuiFrame& frame, const CStringTable& pauseStrg)\n: CPauseScreenBase(mgr, frame, pauseStrg), x1a0_gameCube{std::make_unique<CGameCubeDoll>()} {}\n\nCOptionsScreen::~COptionsScreen() { CSfxManager::SfxStop(x1a4_sliderSfx); }\n\nvoid COptionsScreen::UpdateOptionView() {\n  ResetOptionWidgetVisibility();\n\n  const std::pair<int, const SGameOption*>& category = GameOptionsRegistry[x70_tablegroup_leftlog->GetUserSelection()];\n  if (category.first == 0)\n    return;\n\n  float zOff = x38_highlightPitch * x1c_rightSel;\n  const SGameOption& opt = category.second[x1c_rightSel];\n  switch (opt.type) {\n  case EOptionType::Float:\n    x18c_slidergroup_slider->SetIsActive(true);\n    x18c_slidergroup_slider->SetVisibility(true, ETraversalMode::Children);\n    x18c_slidergroup_slider->SetMinVal(opt.minVal);\n    x18c_slidergroup_slider->SetMaxVal(opt.maxVal);\n    x18c_slidergroup_slider->SetIncrement(opt.increment);\n    x18c_slidergroup_slider->SetCurVal(CGameOptions::GetOption(opt.option));\n    x18c_slidergroup_slider->SetLocalPosition(x3c_sliderStart + zeus::CVector3f(0.f, 0.f, zOff));\n    break;\n  case EOptionType::DoubleEnum:\n    x190_tablegroup_double->SetUserSelection(CGameOptions::GetOption(opt.option));\n    x190_tablegroup_double->SetIsVisible(true);\n    x190_tablegroup_double->SetIsActive(true);\n    x190_tablegroup_double->SetWorkersMouseActive(true);\n    UpdateSideTable(x190_tablegroup_double);\n    x190_tablegroup_double->SetLocalPosition(x48_tableDoubleStart + zeus::CVector3f(0.f, 0.f, zOff));\n    break;\n  case EOptionType::TripleEnum:\n    x194_tablegroup_triple->SetUserSelection(CGameOptions::GetOption(opt.option));\n    x194_tablegroup_triple->SetIsVisible(true);\n    x194_tablegroup_triple->SetIsActive(true);\n    x194_tablegroup_triple->SetWorkersMouseActive(true);\n    UpdateSideTable(x194_tablegroup_triple);\n    x194_tablegroup_triple->SetLocalPosition(x54_tableTripleStart + zeus::CVector3f(0.f, 0.f, zOff));\n    break;\n  default:\n    break;\n  }\n\n  x174_textpane_body->SetMouseActive(false);\n}\n\nvoid COptionsScreen::ResetOptionWidgetVisibility() {\n  x18c_slidergroup_slider->SetIsActive(false);\n  x18c_slidergroup_slider->SetVisibility(false, ETraversalMode::Children);\n  x190_tablegroup_double->SetIsVisible(false);\n  x190_tablegroup_double->SetIsActive(false);\n  x190_tablegroup_double->SetWorkersMouseActive(false);\n  x194_tablegroup_triple->SetIsActive(false);\n  x194_tablegroup_triple->SetIsVisible(false);\n  x194_tablegroup_triple->SetWorkersMouseActive(false);\n  x174_textpane_body->SetMouseActive(true);\n}\n\nvoid COptionsScreen::OnSliderChanged(CGuiSliderGroup* caller, float val) {\n  if (x10_mode != EMode::RightTable)\n    return;\n\n  EGameOption opt = GameOptionsRegistry[x70_tablegroup_leftlog->GetUserSelection()].second[x1c_rightSel].option;\n  CGameOptions::SetOption(opt, caller->GetGurVal());\n}\n\nvoid COptionsScreen::OnEnumChanged(CGuiTableGroup* caller, int oldSel) {\n  if (x10_mode != EMode::RightTable)\n    return;\n\n  EGameOption opt = GameOptionsRegistry[x70_tablegroup_leftlog->GetUserSelection()].second[x1c_rightSel].option;\n  CGameOptions::SetOption(opt, caller->GetUserSelection());\n\n  if (opt == EGameOption::Rumble && caller->GetUserSelection() > 0) {\n    x1a8_rumble.HardStopAll();\n    x1a8_rumble.Rumble(RumbleFxTable[size_t(ERumbleFxId::PlayerBump)], 1.f, ERumblePriority::One, EIOPort::Player1);\n  }\n\n  CPauseScreenBase::UpdateSideTable(caller);\n  CSfxManager::SfxStart(SFXui_option_enum_change, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n}\n\nbool COptionsScreen::InputDisabled() const { return x19c_quitGame.operator bool(); }\n\nvoid COptionsScreen::Update(float dt, CRandom16& rand, CArchitectureQueue& archQueue) {\n  x1a8_rumble.Update(dt);\n  CPauseScreenBase::Update(dt, rand, archQueue);\n\n  if (x1a4_sliderSfx.operator bool() != (x18c_slidergroup_slider->GetState() != CGuiSliderGroup::EState::None)) {\n    if (x18c_slidergroup_slider->GetState() != CGuiSliderGroup::EState::None) {\n      x1a4_sliderSfx =\n          CSfxManager::SfxStart(SFXui_options_slider_change_lp, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n    } else {\n      CSfxManager::SfxStop(x1a4_sliderSfx);\n      x1a4_sliderSfx.reset();\n    }\n  }\n\n  if (x2a0_24_inOptionBody)\n    x29c_optionAlpha = std::min(x29c_optionAlpha + 4.f * dt, 1.f);\n  else\n    x29c_optionAlpha = std::max(x29c_optionAlpha - 4.f * dt, 0.f);\n\n  if (std::fabs(x29c_optionAlpha) < 0.00001f) {\n    ResetOptionWidgetVisibility();\n    x174_textpane_body->SetIsVisible(false);\n  }\n\n  zeus::CColor color = g_tweakGuiColors->GetPauseItemAmberColor();\n  color.a() = x29c_optionAlpha;\n  x18c_slidergroup_slider->SetColor(color);\n  x190_tablegroup_double->SetColor(color);\n  x194_tablegroup_triple->SetColor(color);\n\n  if (x19c_quitGame) {\n    EQuitAction action = x19c_quitGame->Update(dt);\n    if (action == EQuitAction::Yes) {\n      archQueue.Push(MakeMsg::CreateQuitGameplay(EArchMsgTarget::Game));\n      CSfxManager::SetChannel(CSfxManager::ESfxChannels::Default);\n      CSfxManager::SfxStart(SFXui_options_quit_accept, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n    } else if (action == EQuitAction::No) {\n      CSfxManager::SfxStart(SFXui_options_quit_reject, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n      x19c_quitGame.reset();\n    }\n  }\n\n  x1a0_gameCube->Update(dt);\n}\n\nvoid COptionsScreen::Touch() {\n  CPauseScreenBase::Touch();\n  x1a0_gameCube->Touch();\n}\n\nvoid COptionsScreen::ProcessControllerInput(const CFinalInput& input) {\n  if (!x19c_quitGame) {\n    bool leftClicked = m_leftClicked;\n    bool rightClicked = m_rightClicked;\n    CPauseScreenBase::ProcessMouseInput(input, 0.f);\n    CPauseScreenBase::ProcessControllerInput(input);\n    CGameOptions::TryRestoreDefaults(input, x70_tablegroup_leftlog->GetUserSelection(), x1c_rightSel, false,\n                                     rightClicked);\n    if (x70_tablegroup_leftlog->GetUserSelection() == 4 &&\n        (input.PA() || leftClicked || input.PSpecialKey(ESpecialKey::Enter)))\n      x19c_quitGame = std::make_unique<CQuitGameScreen>(EQuitType::QuitGame);\n  } else {\n    CPauseScreenBase::ResetMouseState();\n    x19c_quitGame->ProcessUserInput(input);\n  }\n}\n\nvoid COptionsScreen::Draw(float transInterp, float totalAlpha, float yOff) {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"COptionsScreen::Draw\", zeus::skPurple);\n  CPauseScreenBase::Draw(transInterp, totalAlpha, yOff);\n  x1a0_gameCube->Draw(transInterp * (1.f - x29c_optionAlpha));\n  if (x19c_quitGame) {\n    CGraphics::SetDepthRange(DEPTH_NEAR, 0.001f);\n    x19c_quitGame->Draw();\n    CGraphics::SetDepthRange(DEPTH_NEAR, DEPTH_FAR);\n  }\n}\n\nbool COptionsScreen::VReady() const { return true; }\n\nvoid COptionsScreen::VActivate() {\n  for (int i = 0; i < 5; ++i) {\n    if (g_Main->IsUSA() && !g_Main->IsTrilogy()) {\n      xa8_textpane_categories[i]->TextSupport().SetText(xc_pauseStrg.GetString(i + 16));\n    } else {\n      xa8_textpane_categories[i]->TextSupport().SetText(xc_pauseStrg.GetString(i + 18));\n    }\n  }\n\n  x178_textpane_title->TextSupport().SetText(\n      xc_pauseStrg.GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 15 : 17));\n\n#if 0\n    for (int i=5 ; i<5 ; ++i)\n        x70_tablegroup_leftlog->GetWorkerWidget(i)->SetIsSelectable(false);\n#endif\n\n  x174_textpane_body->TextSupport().SetJustification(EJustification::Center);\n  x174_textpane_body->TextSupport().SetVerticalJustification(EVerticalJustification::Bottom);\n\n  int stringOffset = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 0 : 3;\n  static_cast<CGuiTextPane*>(x190_tablegroup_double->GetWorkerWidget(0))\n      ->TextSupport()\n      .SetText(xc_pauseStrg.GetString(95 + stringOffset));\n  static_cast<CGuiTextPane*>(x190_tablegroup_double->GetWorkerWidget(1))\n      ->TextSupport()\n      .SetText(xc_pauseStrg.GetString(94 + stringOffset));\n\n  static_cast<CGuiTextPane*>(x194_tablegroup_triple->GetWorkerWidget(0))\n      ->TextSupport()\n      .SetText(xc_pauseStrg.GetString(96 + stringOffset));\n  static_cast<CGuiTextPane*>(x194_tablegroup_triple->GetWorkerWidget(1))\n      ->TextSupport()\n      .SetText(xc_pauseStrg.GetString(97 + stringOffset));\n  static_cast<CGuiTextPane*>(x194_tablegroup_triple->GetWorkerWidget(2))\n      ->TextSupport()\n      .SetText(xc_pauseStrg.GetString(98 + stringOffset));\n\n  x18c_slidergroup_slider->SetSelectionChangedCallback(\n      [this](CGuiSliderGroup* caller, float value) { OnSliderChanged(caller, value); });\n  x190_tablegroup_double->SetMenuSelectionChangeCallback(\n      [this](CGuiTableGroup* caller, int oldSel) { OnEnumChanged(caller, oldSel); });\n  x194_tablegroup_triple->SetMenuSelectionChangeCallback(\n      [this](CGuiTableGroup* caller, int oldSel) { OnEnumChanged(caller, oldSel); });\n}\n\nvoid COptionsScreen::RightTableSelectionChanged(int oldSel, int newSel) { UpdateOptionView(); }\n\nvoid COptionsScreen::ChangedMode(EMode oldMode) {\n  if (x10_mode == EMode::RightTable) {\n    x174_textpane_body->SetIsVisible(true);\n    UpdateOptionView();\n    x2a0_24_inOptionBody = true;\n  } else {\n    x2a0_24_inOptionBody = false;\n  }\n}\n\nvoid COptionsScreen::UpdateRightTable() {\n  CPauseScreenBase::UpdateRightTable();\n  const std::pair<int, const SGameOption*>& category =\n      (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? GameOptionsRegistry[x70_tablegroup_leftlog->GetUserSelection()]\n                                                : GameOptionsRegistryNew[x70_tablegroup_leftlog->GetUserSelection()];\n  for (int i = 0; i < 5; ++i) {\n    if (i < category.first) {\n      xd8_textpane_titles[i]->TextSupport().SetText(xc_pauseStrg.GetString(category.second[i].stringId));\n    } else {\n      xd8_textpane_titles[i]->TextSupport().SetText(u\"\");\n    }\n  }\n}\n\nbool COptionsScreen::ShouldLeftTableAdvance() const { return x70_tablegroup_leftlog->GetUserSelection() != 4; }\n\nbool COptionsScreen::ShouldRightTableAdvance() const { return false; }\n\nu32 COptionsScreen::GetRightTableCount() const {\n  return GameOptionsRegistry[x70_tablegroup_leftlog->GetUserSelection()].first;\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/COptionsScreen.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/MP1/CGameCubeDoll.hpp\"\n#include \"Runtime/MP1/CInGameGuiManager.hpp\"\n#include \"Runtime/MP1/CPauseScreenBase.hpp\"\n#include \"Runtime/MP1/CQuitGameScreen.hpp\"\n\nnamespace metaforce::MP1 {\n\nclass COptionsScreen : public CPauseScreenBase {\n  std::unique_ptr<CQuitGameScreen> x19c_quitGame;\n  std::unique_ptr<CGameCubeDoll> x1a0_gameCube;\n  CSfxHandle x1a4_sliderSfx;\n  CRumbleGenerator x1a8_rumble;\n  float x29c_optionAlpha = 0.f;\n  bool x2a0_24_inOptionBody : 1 = false;\n\n  void UpdateOptionView();\n  void ResetOptionWidgetVisibility();\n  void OnSliderChanged(CGuiSliderGroup* caller, float val);\n  void OnEnumChanged(CGuiTableGroup* caller, int oldSel);\n\npublic:\n  COptionsScreen(const CStateManager& mgr, CGuiFrame& frame, const CStringTable& pauseStrg);\n  ~COptionsScreen() override;\n\n  bool InputDisabled() const override;\n  void Update(float dt, CRandom16& rand, CArchitectureQueue& archQueue) override;\n  void Touch() override;\n  void ProcessControllerInput(const CFinalInput& input) override;\n  void Draw(float transInterp, float totalAlpha, float yOff) override;\n  bool VReady() const override;\n  void VActivate() override;\n  void RightTableSelectionChanged(int oldSel, int newSel) override;\n  void ChangedMode(EMode oldMode) override;\n  void UpdateRightTable() override;\n  bool ShouldLeftTableAdvance() const override;\n  bool ShouldRightTableAdvance() const override;\n  u32 GetRightTableCount() const override;\n};\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/CPauseScreen.cpp",
    "content": "#include \"Runtime/MP1/CPauseScreen.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Audio/CSfxManager.hpp\"\n#include \"Runtime/GuiSys/CGuiSys.hpp\"\n#include \"Runtime/GuiSys/CGuiTextPane.hpp\"\n#include \"Runtime/GuiSys/CGuiWidgetDrawParms.hpp\"\n#include \"Runtime/Input/ControlMapper.hpp\"\n#include \"Runtime/MP1/CLogBookScreen.hpp\"\n#include \"Runtime/MP1/COptionsScreen.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\nnamespace metaforce::MP1 {\n\nCPauseScreen::CPauseScreen(ESubScreen subscreen, const CDependencyGroup& suitDgrp, const CDependencyGroup& ballDgrp)\n: x0_initialSubScreen(subscreen)\n, x14_strgPauseScreen(g_SimplePool->GetObj(\"STRG_PauseScreen\"))\n, x20_suitDgrp(suitDgrp)\n, x24_ballDgrp(ballDgrp)\n, x28_pauseScreenInstructions(g_SimplePool->GetObj(\"FRME_PauseScreenInstructions\"))\n, x54_frmePauseScreenId(g_ResFactory->GetResourceIdByName(\"FRME_PauseScreen\")->id) {\n  SObjectTag frmeTag(FOURCC('FRME'), x54_frmePauseScreenId);\n  x58_frmePauseScreenBufSz = g_ResFactory->ResourceSize(frmeTag);\n  x5c_frmePauseScreenBuf.reset(new u8[x58_frmePauseScreenBufSz]);\n  x60_loadTok = g_ResFactory->LoadResourceAsync(frmeTag, x5c_frmePauseScreenBuf.get());\n  CSfxManager::SfxStart(SFXui_pause_screen_enter, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n  x7c_screens.resize(2);\n}\n\nCPauseScreen::~CPauseScreen() {\n  if (x60_loadTok)\n    x60_loadTok->PostCancelRequest();\n}\n\nstd::unique_ptr<CPauseScreenBase> CPauseScreen::BuildPauseSubScreen(ESubScreen subscreen, const CStateManager& mgr,\n                                                                    CGuiFrame& frame) const {\n  switch (subscreen) {\n  case ESubScreen::LogBook:\n    return std::make_unique<CLogBookScreen>(mgr, frame, *x14_strgPauseScreen);\n  case ESubScreen::Options:\n    return std::make_unique<COptionsScreen>(mgr, frame, *x14_strgPauseScreen);\n  case ESubScreen::Inventory:\n    return std::make_unique<CInventoryScreen>(mgr, frame, *x14_strgPauseScreen, x20_suitDgrp, x24_ballDgrp);\n  default:\n    return {};\n  }\n}\n\nvoid CPauseScreen::InitializeFrameGlue() {\n  x38_textpane_l1 = static_cast<CGuiTextPane*>(x34_loadedPauseScreenInstructions->FindWidget(\"textpane_l1\"));\n  x38_textpane_l1->SetMouseActive(true);\n  x3c_textpane_r = static_cast<CGuiTextPane*>(x34_loadedPauseScreenInstructions->FindWidget(\"textpane_r\"));\n  x3c_textpane_r->SetMouseActive(true);\n  x40_textpane_a = static_cast<CGuiTextPane*>(x34_loadedPauseScreenInstructions->FindWidget(\"textpane_a\"));\n  x40_textpane_a->SetMouseActive(true);\n  x44_textpane_b = static_cast<CGuiTextPane*>(x34_loadedPauseScreenInstructions->FindWidget(\"textpane_b\"));\n  x44_textpane_b->SetMouseActive(true);\n  x48_textpane_return = static_cast<CGuiTextPane*>(x34_loadedPauseScreenInstructions->FindWidget(\"textpane_return\"));\n  x48_textpane_return->SetMouseActive(true);\n  x4c_textpane_next = static_cast<CGuiTextPane*>(x34_loadedPauseScreenInstructions->FindWidget(\"textpane_next\"));\n  x4c_textpane_next->SetMouseActive(true);\n  x50_textpane_back = static_cast<CGuiTextPane*>(x34_loadedPauseScreenInstructions->FindWidget(\"textpane_back\"));\n  x50_textpane_back->SetMouseActive(true);\n\n  x40_textpane_a->TextSupport().SetText(x14_strgPauseScreen->GetString(7)); // OPTIONS\n  x40_textpane_a->TextSupport().SetFontColor(g_tweakGuiColors->GetPauseItemAmberColor());\n  x44_textpane_b->TextSupport().SetText(x14_strgPauseScreen->GetString(6)); // LOG BOOK\n  x44_textpane_b->TextSupport().SetFontColor(g_tweakGuiColors->GetPauseItemAmberColor());\n  x40_textpane_a->SetColor(zeus::skClear);\n  x44_textpane_b->SetColor(zeus::skClear);\n\n  if (CGuiWidget* deco = x34_loadedPauseScreenInstructions->FindWidget(\"basewidget_deco\")) {\n    zeus::CColor color = g_tweakGuiColors->GetPauseItemAmberColor();\n    color.a() *= 0.75f;\n    deco->SetColor(color);\n  }\n\n  x34_loadedPauseScreenInstructions->SetMouseDownCallback(\n      [this](CGuiWidget* caller, bool resume) { OnWidgetMouseDown(caller, resume); });\n  x34_loadedPauseScreenInstructions->SetMouseUpCallback(\n      [this](CGuiWidget* caller, bool cancel) { OnWidgetMouseUp(caller, cancel); });\n}\n\nbool CPauseScreen::CheckLoadComplete(const CStateManager& mgr) {\n  if (x90_resourcesLoaded)\n    return true;\n  if (!x14_strgPauseScreen.IsLoaded())\n    return false;\n  if (!x34_loadedPauseScreenInstructions) {\n    if (!x28_pauseScreenInstructions.IsLoaded())\n      return false;\n    if (!x28_pauseScreenInstructions->GetIsFinishedLoading())\n      return false;\n    x34_loadedPauseScreenInstructions = x28_pauseScreenInstructions.GetObj();\n    x34_loadedPauseScreenInstructions->SetMaxAspect(1.77f);\n    InitializeFrameGlue();\n  }\n  if (x60_loadTok) {\n    if (!x60_loadTok->IsComplete())\n      return false;\n    for (int i = 0; i < 2; ++i) {\n      CMemoryInStream s(x5c_frmePauseScreenBuf.get(), x58_frmePauseScreenBufSz);\n      x64_frameInsts.push_back(CGuiFrame::CreateFrame(x54_frmePauseScreenId, *g_GuiSys, s, g_SimplePool));\n      x64_frameInsts.back()->SetMaxAspect(1.77f);\n    }\n    x5c_frmePauseScreenBuf.reset();\n    x60_loadTok.reset();\n  }\n  if (!x64_frameInsts[0]->GetIsFinishedLoading() || !x64_frameInsts[1]->GetIsFinishedLoading())\n    return false;\n  x90_resourcesLoaded = true;\n  StartTransition(FLT_EPSILON, mgr, x0_initialSubScreen, 2);\n  x91_initialTransition = true;\n  return true;\n}\n\nvoid CPauseScreen::StartTransition(float time, const CStateManager& mgr, ESubScreen subscreen, int b) {\n  if (subscreen == xc_nextSubscreen)\n    return;\n  xc_nextSubscreen = subscreen;\n  x4_ = b;\n  x10_alphaInterp = time;\n  std::unique_ptr<CPauseScreenBase>& newScreenSlot = x7c_screens[1 - x78_activeIdx];\n  std::unique_ptr<CGuiFrame>& newScreenInst = x64_frameInsts[1 - x78_activeIdx];\n  newScreenSlot = BuildPauseSubScreen(xc_nextSubscreen, mgr, *newScreenInst);\n  if (x7c_screens[x78_activeIdx])\n    x7c_screens[x78_activeIdx]->TransitioningAway();\n  x91_initialTransition = false;\n}\n\nbool CPauseScreen::InputEnabled() const {\n  if (xc_nextSubscreen != x8_curSubscreen)\n    return false;\n\n  if (const std::unique_ptr<CPauseScreenBase>& screenSlot = x7c_screens[x78_activeIdx])\n    if (screenSlot->InputDisabled())\n      return false;\n\n  if (const std::unique_ptr<CPauseScreenBase>& screenSlot = x7c_screens[1 - x78_activeIdx])\n    if (screenSlot->InputDisabled())\n      return false;\n\n  return true;\n}\n\nCPauseScreen::ESubScreen CPauseScreen::GetPreviousSubscreen(ESubScreen screen) {\n  switch (screen) {\n  case ESubScreen::Inventory:\n    return ESubScreen::Options;\n  case ESubScreen::Options:\n    return ESubScreen::LogBook;\n  case ESubScreen::LogBook:\n    return ESubScreen::Inventory;\n  default:\n    return ESubScreen::ToGame;\n  }\n}\n\nCPauseScreen::ESubScreen CPauseScreen::GetNextSubscreen(ESubScreen screen) {\n  switch (screen) {\n  case ESubScreen::Inventory:\n    return ESubScreen::LogBook;\n  case ESubScreen::Options:\n    return ESubScreen::Inventory;\n  case ESubScreen::LogBook:\n    return ESubScreen::Options;\n  default:\n    return ESubScreen::ToGame;\n  }\n}\n\nvoid CPauseScreen::ProcessControllerInput(const CStateManager& mgr, const CFinalInput& input) {\n  if (!IsLoaded())\n    return;\n\n  if (x8_curSubscreen == ESubScreen::ToGame)\n    return;\n\n  m_returnClicked = false;\n  m_nextClicked = false;\n  m_backClicked = false;\n  m_lClicked = false;\n  m_rClicked = false;\n\n  CFinalInput useInput = input;\n\n  bool bExits = false;\n  if (std::unique_ptr<CPauseScreenBase>& curScreen = x7c_screens[x78_activeIdx]) {\n    float yOff = 0.f;\n    if (curScreen->CanDraw())\n      yOff = curScreen->GetCameraYBias();\n    CGuiWidgetDrawParms parms(1.f, zeus::CVector3f{0.f, 15.f * yOff, 0.f});\n    x34_loadedPauseScreenInstructions->ProcessMouseInput(useInput, parms);\n    useInput.x2e_b31_PStart |= m_returnClicked;\n    useInput.x2d_b28_PA |= m_nextClicked;\n    useInput.x2d_b29_PB |= m_backClicked;\n\n    if (curScreen->GetMode() == CPauseScreenBase::EMode::LeftTable)\n      bExits = true;\n    curScreen->ProcessControllerInput(useInput);\n  }\n\n  if (InputEnabled()) {\n    bool invalid = x8_curSubscreen == ESubScreen::ToGame;\n    if (useInput.PStart() || ((useInput.PB() || useInput.PSpecialKey(ESpecialKey::Esc)) && bExits) ||\n        (x7c_screens[x78_activeIdx] && x7c_screens[x78_activeIdx]->ShouldExitPauseScreen())) {\n      CSfxManager::SfxStart(SFXui_pause_screen_exit, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n      StartTransition(0.5f, mgr, ESubScreen::ToGame, 2);\n    } else {\n      if (ControlMapper::GetPressInput(ControlMapper::ECommands::PreviousPauseScreen, useInput) || m_lClicked) {\n        CSfxManager::SfxStart(SFXui_pause_screen_change, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n        StartTransition(0.5f, mgr, GetPreviousSubscreen(x8_curSubscreen), invalid ? 2 : 0);\n      } else if (ControlMapper::GetPressInput(ControlMapper::ECommands::NextPauseScreen, useInput) || m_rClicked) {\n        CSfxManager::SfxStart(SFXui_pause_screen_change, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n        StartTransition(0.5f, mgr, GetNextSubscreen(x8_curSubscreen), invalid ? 2 : 1);\n      }\n    }\n  }\n\n  x38_textpane_l1->TextSupport().SetText(fmt::format(\n      \"&image={};\",\n      g_tweakPlayerRes\n          ->x74_lTrigger[ControlMapper::GetDigitalInput(ControlMapper::ECommands::PreviousPauseScreen, useInput) ||\n                         m_lDown]));\n  x3c_textpane_r->TextSupport().SetText(fmt::format(\n      \"&image={};\",\n      g_tweakPlayerRes\n          ->x80_rTrigger[ControlMapper::GetDigitalInput(ControlMapper::ECommands::NextPauseScreen, useInput) ||\n                         m_rDown]));\n  x48_textpane_return->TextSupport().SetText(\n      fmt::format(\"&image={};\", g_tweakPlayerRes->x8c_startButton[useInput.DStart() || m_returnDown]));\n  x50_textpane_back->TextSupport().SetText(\n      fmt::format(\"&image={};\", g_tweakPlayerRes->x98_aButton[useInput.DA() || m_backDown]));\n  x4c_textpane_next->TextSupport().SetText(\n      fmt::format(\"&image={};\", g_tweakPlayerRes->xa4_bButton[useInput.DB() || m_nextDown]));\n}\n\nvoid CPauseScreen::TransitionComplete() {\n  std::unique_ptr<CPauseScreenBase>& curScreen = x7c_screens[x78_activeIdx];\n  curScreen.reset();\n  x78_activeIdx = 1 - x78_activeIdx;\n  x8_curSubscreen = xc_nextSubscreen;\n  x40_textpane_a->TextSupport().SetText(x14_strgPauseScreen->GetString(int(GetPreviousSubscreen(x8_curSubscreen)) + 6));\n  x44_textpane_b->TextSupport().SetText(x14_strgPauseScreen->GetString(int(GetNextSubscreen(x8_curSubscreen)) + 6));\n}\n\nvoid CPauseScreen::OnWidgetMouseDown(CGuiWidget* widget, bool resume) {\n  if (widget == x48_textpane_return)\n    m_returnDown = true;\n  else if (widget == x4c_textpane_next)\n    m_backDown = true;\n  else if (widget == x50_textpane_back)\n    m_nextDown = true;\n  else if (widget == x38_textpane_l1 || widget == x40_textpane_a)\n    m_lDown = true;\n  else if (widget == x3c_textpane_r || widget == x44_textpane_b)\n    m_rDown = true;\n}\n\nvoid CPauseScreen::OnWidgetMouseUp(CGuiWidget* widget, bool cancel) {\n  if (widget == x48_textpane_return)\n    m_returnDown = false;\n  else if (widget == x4c_textpane_next)\n    m_backDown = false;\n  else if (widget == x50_textpane_back)\n    m_nextDown = false;\n  else if (widget == x38_textpane_l1 || widget == x40_textpane_a)\n    m_lDown = false;\n  else if (widget == x3c_textpane_r || widget == x44_textpane_b)\n    m_rDown = false;\n  if (cancel)\n    return;\n  if (widget == x48_textpane_return)\n    m_returnClicked = true;\n  else if (widget == x4c_textpane_next)\n    m_backClicked = true;\n  else if (widget == x50_textpane_back)\n    m_nextClicked = true;\n  else if (widget == x38_textpane_l1 || widget == x40_textpane_a)\n    m_lClicked = true;\n  else if (widget == x3c_textpane_r || widget == x44_textpane_b)\n    m_rClicked = true;\n}\n\nvoid CPauseScreen::Update(float dt, const CStateManager& mgr, CRandom16& rand, CArchitectureQueue& archQueue) {\n  if (!CheckLoadComplete(mgr))\n    return;\n\n  std::unique_ptr<CPauseScreenBase>& curScreen = x7c_screens[x78_activeIdx];\n  std::unique_ptr<CPauseScreenBase>& otherScreen = x7c_screens[1 - x78_activeIdx];\n\n  if (x8_curSubscreen != xc_nextSubscreen) {\n    x10_alphaInterp = std::max(0.f, x10_alphaInterp - dt);\n    if (!curScreen || !curScreen->InputDisabled()) {\n      if (!otherScreen || otherScreen->IsReady()) {\n        if (x10_alphaInterp == 0.f)\n          TransitionComplete();\n      }\n    }\n  }\n\n  if (std::unique_ptr<CPauseScreenBase>& curScreen = x7c_screens[x78_activeIdx]) {\n    curScreen->Update(dt, rand, archQueue);\n    zeus::CColor color = zeus::skWhite;\n    color.a() = std::min(curScreen->GetAlpha(), x8_curSubscreen != xc_nextSubscreen ? x10_alphaInterp / 0.5f : 1.f);\n    x40_textpane_a->SetColor(color);\n    x44_textpane_b->SetColor(color);\n  }\n}\n\nvoid CPauseScreen::PreDraw() {\n  if (!IsLoaded())\n    return;\n  if (std::unique_ptr<CPauseScreenBase>& curScreen = x7c_screens[x78_activeIdx])\n    if (curScreen->CanDraw())\n      curScreen->Touch();\n}\n\nvoid CPauseScreen::Draw() {\n  if (!IsLoaded())\n    return;\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CPauseScreen::Draw\", zeus::skPurple);\n\n  float totalAlpha = 0.f;\n  float yOff = 0.f;\n  std::unique_ptr<CPauseScreenBase>& curScreen = x7c_screens[x78_activeIdx];\n  if (curScreen && curScreen->CanDraw()) {\n    float useInterp = x10_alphaInterp == 0.f ? 1.f : x10_alphaInterp / 0.5f;\n    float initInterp = std::min(curScreen->GetAlpha(), useInterp);\n    if (xc_nextSubscreen == ESubScreen::ToGame)\n      totalAlpha = useInterp;\n    else if (x91_initialTransition)\n      totalAlpha = initInterp;\n    else\n      totalAlpha = 1.f;\n\n    curScreen->Draw(x8_curSubscreen != xc_nextSubscreen ? useInterp : 1.f, totalAlpha, 0.f);\n    yOff = curScreen->GetCameraYBias();\n  }\n\n  CGuiWidgetDrawParms parms(totalAlpha, zeus::CVector3f{0.f, 15.f * yOff, 0.f});\n  x34_loadedPauseScreenInstructions->Draw(parms);\n}\n\nbool CPauseScreen::ShouldSwitchToMapScreen() const {\n  return IsLoaded() && x8_curSubscreen == ESubScreen::ToMap && xc_nextSubscreen == ESubScreen::ToMap;\n}\n\nbool CPauseScreen::ShouldSwitchToInGame() const {\n  return IsLoaded() && x8_curSubscreen == ESubScreen::ToGame && xc_nextSubscreen == ESubScreen::ToGame;\n}\n\nfloat CPauseScreen::GetHelmetCamYOff() const {\n  CPauseScreenBase* screen = x7c_screens[x78_activeIdx].get();\n  if (screen)\n    return screen->GetCameraYBias();\n  return 0.f;\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/CPauseScreen.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/MP1/CInGameGuiManager.hpp\"\n#include \"Runtime/MP1/CPauseScreenBase.hpp\"\n\nnamespace metaforce {\nclass CDependencyGroup;\n\nnamespace MP1 {\n\nclass CPauseScreen {\npublic:\n  enum class ESubScreen { LogBook, Options, Inventory, ToGame, ToMap };\n\nprivate:\n  ESubScreen x0_initialSubScreen;\n  u32 x4_ = 2;\n  ESubScreen x8_curSubscreen = ESubScreen::ToGame;\n  ESubScreen xc_nextSubscreen = ESubScreen::ToGame;\n  float x10_alphaInterp = 0.f;\n  TLockedToken<CStringTable> x14_strgPauseScreen;\n  const CDependencyGroup& x20_suitDgrp;\n  const CDependencyGroup& x24_ballDgrp;\n  TLockedToken<CGuiFrame> x28_pauseScreenInstructions;\n  CGuiFrame* x34_loadedPauseScreenInstructions = nullptr;\n  CGuiTextPane* x38_textpane_l1 = nullptr;\n  CGuiTextPane* x3c_textpane_r = nullptr;\n  CGuiTextPane* x40_textpane_a = nullptr;\n  CGuiTextPane* x44_textpane_b = nullptr;\n  CGuiTextPane* x48_textpane_return = nullptr;\n  CGuiTextPane* x4c_textpane_next = nullptr;\n  CGuiTextPane* x50_textpane_back = nullptr;\n  CAssetId x54_frmePauseScreenId;\n  u32 x58_frmePauseScreenBufSz;\n  std::unique_ptr<u8[]> x5c_frmePauseScreenBuf;\n  std::shared_ptr<IDvdRequest> x60_loadTok;\n  rstl::reserved_vector<std::unique_ptr<CGuiFrame>, 2> x64_frameInsts;\n  u32 x78_activeIdx = 0;\n  rstl::reserved_vector<std::unique_ptr<CPauseScreenBase>, 2> x7c_screens;\n  bool x90_resourcesLoaded = false;\n  bool x91_initialTransition = true;\n\n  bool m_returnClicked : 1 = false;\n  bool m_nextClicked : 1 = false;\n  bool m_backClicked : 1 = false;\n  bool m_lClicked : 1 = false;\n  bool m_rClicked : 1 = false;\n  bool m_returnDown : 1 = false;\n  bool m_nextDown : 1 = false;\n  bool m_backDown : 1 = false;\n  bool m_lDown : 1 = false;\n  bool m_rDown : 1 = false;\n\n  std::unique_ptr<CPauseScreenBase> BuildPauseSubScreen(ESubScreen subscreen, const CStateManager& mgr,\n                                                        CGuiFrame& frame) const;\n  void StartTransition(float time, const CStateManager& mgr, ESubScreen subscreen, int);\n  bool CheckLoadComplete(const CStateManager& mgr);\n  void InitializeFrameGlue();\n  bool InputEnabled() const;\n  static ESubScreen GetPreviousSubscreen(ESubScreen screen);\n  static ESubScreen GetNextSubscreen(ESubScreen screen);\n  void TransitionComplete();\n\n  void OnWidgetMouseDown(CGuiWidget* widget, bool resume);\n  void OnWidgetMouseUp(CGuiWidget* widget, bool cancel);\n\npublic:\n  CPauseScreen(ESubScreen subscreen, const CDependencyGroup& suitDgrp, const CDependencyGroup& ballDgrp);\n  ~CPauseScreen();\n  void ProcessControllerInput(const CStateManager& mgr, const CFinalInput& input);\n  void Update(float dt, const CStateManager& mgr, CRandom16& rand, CArchitectureQueue& archQueue);\n  void PreDraw();\n  void Draw();\n  bool IsLoaded() const { return x90_resourcesLoaded; }\n  bool ShouldSwitchToMapScreen() const;\n  bool ShouldSwitchToInGame() const;\n  bool IsTransitioning() const { return x8_curSubscreen != xc_nextSubscreen; }\n  float GetHelmetCamYOff() const;\n};\n\n} // namespace MP1\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/MP1/CPauseScreenBase.cpp",
    "content": "#include \"Runtime/MP1/CPauseScreenBase.hpp\"\n\n#include <array>\n\n#include \"Runtime/CGameState.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Audio/CSfxManager.hpp\"\n#include \"Runtime/GuiSys/CAuiImagePane.hpp\"\n#include \"Runtime/GuiSys/CGuiFrame.hpp\"\n#include \"Runtime/GuiSys/CGuiModel.hpp\"\n#include \"Runtime/GuiSys/CGuiSliderGroup.hpp\"\n#include \"Runtime/GuiSys/CGuiTableGroup.hpp\"\n#include \"Runtime/GuiSys/CGuiTextPane.hpp\"\n#include \"Runtime/GuiSys/CGuiWidgetDrawParms.hpp\"\n#include \"Runtime/GuiSys/CStringTable.hpp\"\n#include \"Runtime/IMain.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\nnamespace metaforce::MP1 {\n\nCPauseScreenBase::CPauseScreenBase(const CStateManager& mgr, CGuiFrame& frame, const CStringTable& pauseStrg,\n                                   bool isLogBook)\n: x4_mgr(mgr), x8_frame(frame), xc_pauseStrg(pauseStrg), m_isLogBook(isLogBook) {\n  InitializeFrameGlue();\n}\n\nvoid CPauseScreenBase::InitializeFrameGlue() {\n  x60_basewidget_pivot = x8_frame.FindWidget(\"basewidget_pivot\");\n  x64_basewidget_bgframe = x8_frame.FindWidget(\"basewidget_bgframe\");\n  x68_basewidget_leftside = x8_frame.FindWidget(\"basewidget_leftside\");\n  x6c_basewidget_leftlog = x8_frame.FindWidget(\"basewidget_leftlog\");\n  x70_tablegroup_leftlog = static_cast<CGuiTableGroup*>(x8_frame.FindWidget(\"tablegroup_leftlog\"));\n  x74_basewidget_leftguages = x8_frame.FindWidget(\"basewidget_leftguages\");\n  x74_basewidget_leftguages->SetColor(zeus::CColor(1.f, 0.f));\n  x78_model_lefthighlight = static_cast<CGuiModel*>(x8_frame.FindWidget(\"model_lefthighlight\"));\n  x7c_basewidget_rightside = x8_frame.FindWidget(\"basewidget_rightside\");\n  x80_basewidget_rightlog = x8_frame.FindWidget(\"basewidget_rightlog\");\n  x84_tablegroup_rightlog = static_cast<CGuiTableGroup*>(x8_frame.FindWidget(\"tablegroup_rightlog\"));\n  x88_basewidget_rightguages = x8_frame.FindWidget(\"basewidget_rightguages\");\n  x88_basewidget_rightguages->SetColor(zeus::CColor(1.f, 0.f));\n  x8c_model_righthighlight = static_cast<CGuiModel*>(x8_frame.FindWidget(\"model_righthighlight\"));\n  x90_model_textarrowtop = static_cast<CGuiModel*>(x8_frame.FindWidget(\"model_textarrowtop\"));\n  x90_model_textarrowtop->SetMouseActive(true);\n  x94_model_textarrowbottom = static_cast<CGuiModel*>(x8_frame.FindWidget(\"model_textarrowbottom\"));\n  x94_model_textarrowbottom->SetMouseActive(true);\n  x98_model_scrollleftup = static_cast<CGuiModel*>(x8_frame.FindWidget(\"model_scrollleftup\"));\n  x9c_model_scrollleftdown = static_cast<CGuiModel*>(x8_frame.FindWidget(\"model_scrollleftdown\"));\n  xa0_model_scrollrightup = static_cast<CGuiModel*>(x8_frame.FindWidget(\"model_scrollrightup\"));\n  xa0_model_scrollrightup->SetMouseActive(true);\n  xa4_model_scrollrightdown = static_cast<CGuiModel*>(x8_frame.FindWidget(\"model_scrollrightdown\"));\n  xa4_model_scrollrightdown->SetMouseActive(true);\n  x178_textpane_title = static_cast<CGuiTextPane*>(x8_frame.FindWidget(\"textpane_title\"));\n  x178_textpane_title->TextSupport().SetFontColor(g_tweakGuiColors->GetPauseItemAmberColor());\n  x174_textpane_body = static_cast<CGuiTextPane*>(x8_frame.FindWidget(\"textpane_body\"));\n  x174_textpane_body->SetColor(zeus::CColor(1.f, 0.f));\n  x174_textpane_body->SetIsVisible(true);\n  x174_textpane_body->TextSupport().SetFontColor(g_tweakGuiColors->GetPauseItemAmberColor());\n  x174_textpane_body->TextSupport().SetPage(0);\n  x174_textpane_body->TextSupport().SetText(u\"\");\n  x174_textpane_body->TextSupport().SetJustification(EJustification::Left);\n  x174_textpane_body->TextSupport().SetVerticalJustification(EVerticalJustification::Top);\n  x174_textpane_body->TextSupport().SetControlTXTRMap(&g_GameState->GameOptions().GetControlTXTRMap());\n  x174_textpane_body->SetMouseActive(true);\n  x180_basewidget_yicon = x8_frame.FindWidget(\"basewidget_yicon\");\n  x180_basewidget_yicon->SetVisibility(false, ETraversalMode::Children);\n  x17c_model_textalpha = static_cast<CGuiModel*>(x8_frame.FindWidget(\"model_textalpha\"));\n  x184_textpane_yicon = static_cast<CGuiTextPane*>(x8_frame.FindWidget(\"textpane_yicon\"));\n  x188_textpane_ytext = static_cast<CGuiTextPane*>(x8_frame.FindWidget(\"textpane_ytext\"));\n  x184_textpane_yicon->TextSupport().SetText(fmt::format(u\"&image={};\", g_tweakPlayerRes->xbc_yButton[0]));\n  x188_textpane_ytext->TextSupport().SetText(\n      xc_pauseStrg.GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 99 : 102));\n  x188_textpane_ytext->SetColor(g_tweakGuiColors->GetPauseItemAmberColor());\n  x18c_slidergroup_slider = static_cast<CGuiSliderGroup*>(x8_frame.FindWidget(\"slidergroup_slider\"));\n  x18c_slidergroup_slider->SetMouseActive(true);\n  x190_tablegroup_double = static_cast<CGuiTableGroup*>(x8_frame.FindWidget(\"tablegroup_double\"));\n  x194_tablegroup_triple = static_cast<CGuiTableGroup*>(x8_frame.FindWidget(\"tablegroup_triple\"));\n\n  x2c_rightTableStart = x84_tablegroup_rightlog->GetWorkerWidget(0)->GetIdlePosition();\n  x38_highlightPitch = x84_tablegroup_rightlog->GetWorkerWidget(1)->GetIdlePosition().z() - x2c_rightTableStart.z();\n  x3c_sliderStart = x18c_slidergroup_slider->GetIdlePosition();\n  x48_tableDoubleStart = x190_tablegroup_double->GetIdlePosition();\n  x54_tableTripleStart = x194_tablegroup_triple->GetIdlePosition();\n\n  for (int i = 0; i < 5; ++i)\n    x70_tablegroup_leftlog->GetWorkerWidget(i)->SetIsSelectable(true);\n\n  for (int i = 0; i < x84_tablegroup_rightlog->GetElementCount(); ++i) {\n    CGuiWidget* w = x84_tablegroup_rightlog->GetWorkerWidget(i);\n    w->SetLocalTransform(\n        zeus::CTransform::Translate(x2c_rightTableStart + zeus::CVector3f(0.f, 0.f, x38_highlightPitch * i)));\n    w->SetIsSelectable(true);\n  }\n\n  for (int i = 0; i < 5; ++i) {\n    xd8_textpane_titles.push_back(\n        static_cast<CGuiTextPane*>(x8_frame.FindWidget(fmt::format(\"textpane_title{}\", i + 1))));\n    xd8_textpane_titles.back()->TextSupport().SetText(u\"\");\n    x144_model_titles.push_back(static_cast<CGuiModel*>(x8_frame.FindWidget(fmt::format(\"model_title{}\", i + 1))));\n    m_model_lefttitledecos.push_back(\n        static_cast<CGuiModel*>(x8_frame.FindWidget(fmt::format(\"model_lefttitledeco{}\", i))));\n    m_model_lefttitledecos.back()->SetMouseActive(true);\n    x15c_model_righttitledecos.push_back(\n        static_cast<CGuiModel*>(x8_frame.FindWidget(fmt::format(\"model_righttitledeco{}\", i + 1))));\n    x15c_model_righttitledecos.back()->SetMouseActive(true);\n    xa8_textpane_categories.push_back(\n        static_cast<CGuiTextPane*>(x8_frame.FindWidget(fmt::format(\"textpane_category{}\", i))));\n    xc0_model_categories.push_back(static_cast<CGuiModel*>(x8_frame.FindWidget(fmt::format(\"model_category{}\", i))));\n  }\n\n  for (int i = 0; i < 20; ++i)\n    xf0_imagePanes.push_back(static_cast<CAuiImagePane*>(x8_frame.FindWidget(GetImagePaneName(i))));\n\n  x70_tablegroup_leftlog->SetUserSelection(0);\n  x84_tablegroup_rightlog->SetUserSelection(1);\n\n  x74_basewidget_leftguages->SetVisibility(false, ETraversalMode::Children);\n  x88_basewidget_rightguages->SetVisibility(false, ETraversalMode::Children);\n  x6c_basewidget_leftlog->SetColor(g_tweakGuiColors->GetPauseItemAmberColor());\n\n  if (IsRightLogDynamic()) {\n    zeus::CColor color = g_tweakGuiColors->GetPauseItemAmberColor();\n    color.a() = 0.5f;\n    UpdateRightLogColors(false, g_tweakGuiColors->GetPauseItemAmberColor(), color);\n  } else {\n    x80_basewidget_rightlog->SetColor(g_tweakGuiColors->GetPauseItemAmberColor());\n  }\n\n  for (CGuiObject* obj = x64_basewidget_bgframe->GetChildObject(); obj; obj = obj->GetNextSibling())\n    static_cast<CGuiWidget*>(obj)->SetColor(g_tweakGuiColors->GetPauseItemAmberColor());\n\n  zeus::CColor dimColor = g_tweakGuiColors->GetPauseItemAmberColor();\n  dimColor.a() = 0.2f;\n  x98_model_scrollleftup->SetColor(dimColor);\n  x9c_model_scrollleftdown->SetColor(dimColor);\n  xa0_model_scrollrightup->SetColor(dimColor);\n  xa4_model_scrollrightdown->SetColor(dimColor);\n  x90_model_textarrowtop->SetColor(dimColor);\n  x94_model_textarrowbottom->SetColor(dimColor);\n\n  x18c_slidergroup_slider->SetColor(g_tweakGuiColors->GetPauseItemAmberColor());\n  x190_tablegroup_double->SetColor(g_tweakGuiColors->GetPauseItemAmberColor());\n  x194_tablegroup_triple->SetColor(g_tweakGuiColors->GetPauseItemAmberColor());\n\n  UpdateSideTable(x190_tablegroup_double);\n  UpdateSideTable(x194_tablegroup_triple);\n  UpdateSideTable(x70_tablegroup_leftlog);\n  UpdateSideTable(x84_tablegroup_rightlog);\n\n  x18c_slidergroup_slider->SetVisibility(false, ETraversalMode::Children);\n\n  x190_tablegroup_double->SetIsVisible(false);\n  x194_tablegroup_triple->SetIsVisible(false);\n  x190_tablegroup_double->SetVertical(false);\n  x194_tablegroup_triple->SetVertical(false);\n  x190_tablegroup_double->SetWorkersMouseActive(false);\n  x194_tablegroup_triple->SetWorkersMouseActive(false);\n\n  x70_tablegroup_leftlog->SetMenuAdvanceCallback([this](CGuiTableGroup* caller) { OnLeftTableAdvance(caller); });\n  x70_tablegroup_leftlog->SetMenuSelectionChangeCallback(\n      [this](CGuiTableGroup* caller, int oldSel) { OnTableSelectionChange(caller, oldSel); });\n  x84_tablegroup_rightlog->SetMenuAdvanceCallback([this](CGuiTableGroup* caller) { OnRightTableAdvance(caller); });\n  x84_tablegroup_rightlog->SetMenuSelectionChangeCallback(\n      [this](CGuiTableGroup* caller, int oldSel) { OnTableSelectionChange(caller, oldSel); });\n  x84_tablegroup_rightlog->SetMenuCancelCallback([this](CGuiTableGroup* caller) { OnRightTableCancel(caller); });\n  x18c_slidergroup_slider->SetSelectionChangedCallback({});\n  x190_tablegroup_double->SetMenuSelectionChangeCallback({});\n  x194_tablegroup_triple->SetMenuSelectionChangeCallback({});\n\n  x8_frame.SetMouseUpCallback([this](CGuiWidget* widget, bool cancel) { OnWidgetMouseUp(widget, cancel); });\n  x8_frame.SetMouseScrollCallback([this](CGuiWidget* widget, const SScrollDelta& delta, int accumX, int accumY) {\n    OnWidgetScroll(widget, delta, accumX, accumY);\n  });\n}\n\nbool CPauseScreenBase::IsReady() {\n  if (x198_24_ready)\n    return true;\n  x198_24_ready = VReady();\n  if (x198_24_ready) {\n    VActivate();\n    ChangeMode(EMode::LeftTable);\n    UpdateSideTable(x70_tablegroup_leftlog);\n    UpdateRightTable();\n    return true;\n  }\n  return false;\n}\n\nvoid CPauseScreenBase::ChangeMode(EMode mode, bool playSfx) {\n  if (x10_mode == mode)\n    return;\n\n  EMode oldMode = x10_mode;\n  zeus::CColor color = g_tweakGuiColors->GetPauseItemAmberColor();\n  zeus::CColor colorDim = color;\n  colorDim.a() = 0.5f;\n\n  switch (x10_mode) {\n  case EMode::LeftTable:\n    x6c_basewidget_leftlog->SetColor(colorDim);\n    x70_tablegroup_leftlog->SetIsActive(false);\n    break;\n  case EMode::Invalid:\n  case EMode::RightTable:\n    if (IsRightLogDynamic())\n      UpdateRightLogColors(false, color, colorDim);\n    else\n      x80_basewidget_rightlog->SetColor(colorDim);\n    x84_tablegroup_rightlog->SetIsActive(false);\n    break;\n  case EMode::TextScroll:\n    if (playSfx)\n      CSfxManager::SfxStart(SFXui_table_change_mode, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n    break;\n  default:\n    break;\n  }\n\n  x10_mode = mode;\n\n  switch (x10_mode) {\n  case EMode::LeftTable:\n    if (playSfx && oldMode == EMode::RightTable)\n      CSfxManager::SfxStart(SFXui_table_change_mode, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n    x6c_basewidget_leftlog->SetColor(color);\n    x70_tablegroup_leftlog->SetIsActive(true);\n    UpdateSideTable(x70_tablegroup_leftlog);\n    x18_firstViewRightSel = 0;\n    x1c_rightSel = 0;\n    x84_tablegroup_rightlog->SetUserSelection(1);\n    UpdateSideTable(x84_tablegroup_rightlog);\n    break;\n  case EMode::RightTable:\n    if (IsRightLogDynamic())\n      UpdateRightLogColors(true, color, colorDim);\n    else\n      x80_basewidget_rightlog->SetColor(colorDim);\n    x84_tablegroup_rightlog->SetIsActive(true);\n    UpdateSideTable(x84_tablegroup_rightlog);\n    break;\n  case EMode::TextScroll:\n    x6c_basewidget_leftlog->SetColor(colorDim);\n    if (IsRightLogDynamic())\n      UpdateRightLogColors(true, color, colorDim);\n    else\n      x80_basewidget_rightlog->SetColor(colorDim);\n    x70_tablegroup_leftlog->SetIsActive(false);\n    x84_tablegroup_rightlog->SetIsActive(false);\n    break;\n  default:\n    break;\n  }\n\n  ChangedMode(oldMode);\n}\n\nvoid CPauseScreenBase::UpdateSideTable(CGuiTableGroup* table) {\n  if (!table) {\n    return;\n  }\n\n  constexpr zeus::CColor selColor = zeus::skWhite;\n  constexpr zeus::CColor deselColor = {1.f, 1.f, 1.f, 0.5f};\n\n  bool tableActive = true;\n  if (table == x84_tablegroup_rightlog && x10_mode != EMode::RightTable)\n    tableActive = false;\n\n  table->SetColors(selColor, deselColor);\n\n  if (table == x84_tablegroup_rightlog) {\n    int sel = x1c_rightSel - x18_firstViewRightSel;\n    x8c_model_righthighlight->SetLocalTransform(x8c_model_righthighlight->GetTransform() *\n                                                zeus::CTransform::Translate(0.f, 0.f, x38_highlightPitch * sel));\n    x8c_model_righthighlight->SetVisibility(x10_mode == EMode::RightTable, ETraversalMode::Children);\n    int selInView = x1c_rightSel % 5;\n    if (IsRightLogDynamic()) {\n      UpdateRightLogHighlight(tableActive, selInView, selColor, deselColor);\n    } else {\n      for (size_t i = 0; i < x144_model_titles.size(); ++i)\n        x144_model_titles[i]->SetColor((i == selInView && tableActive) ? selColor : deselColor);\n    }\n  } else {\n    int sel = x70_tablegroup_leftlog->GetUserSelection();\n    x78_model_lefthighlight->SetLocalTransform(x78_model_lefthighlight->GetTransform() *\n                                               zeus::CTransform::Translate(0.f, 0.f, x38_highlightPitch * sel));\n    for (size_t i = 0; i < xc0_model_categories.size(); ++i)\n      xc0_model_categories[i]->SetColor(i == sel ? selColor : deselColor);\n  }\n}\n\nvoid CPauseScreenBase::Update(float dt, CRandom16& rand, CArchitectureQueue& archQueue) {\n  x198_27_canDraw = true;\n  x8_frame.Update(dt);\n  x14_alpha = std::min(2.f * dt + x14_alpha, 1.f);\n\n  int rightCount = GetRightTableCount();\n  bool pulseRightUp = x10_mode == EMode::RightTable && x18_firstViewRightSel > 0;\n  bool pulseRightDown = x10_mode == EMode::RightTable && x18_firstViewRightSel + 5 < rightCount;\n  float rightUpT = pulseRightUp ? CGraphics::GetSecondsMod900() : 0.f;\n  float rightDownT = pulseRightDown ? CGraphics::GetSecondsMod900() : 0.f;\n\n  zeus::CColor lowC = g_tweakGuiColors->GetPauseItemAmberColor();\n  lowC.a() = 0.2f;\n  xa0_model_scrollrightup->SetColor(\n      zeus::CColor::lerp(lowC, g_tweakGuiColors->GetPauseItemAmberColor(),\n                         zeus::clamp(0.f, (std::sin(5.f * rightUpT - M_PIF / 2.f) + 1.f) * 0.5f, 1.f)));\n  xa4_model_scrollrightdown->SetColor(\n      zeus::CColor::lerp(lowC, g_tweakGuiColors->GetPauseItemAmberColor(),\n                         zeus::clamp(0.f, (std::sin(5.f * rightDownT - M_PIF / 2.f) + 1.f) * 0.5f, 1.f)));\n\n  float textUpT = x198_28_pulseTextArrowTop ? CGraphics::GetSecondsMod900() : 0.f;\n  float textDownT = x198_29_pulseTextArrowBottom ? CGraphics::GetSecondsMod900() : 0.f;\n\n  x90_model_textarrowtop->SetColor(\n      zeus::CColor::lerp(lowC, g_tweakGuiColors->GetPauseItemAmberColor(),\n                         zeus::clamp(0.f, (std::sin(5.f * textUpT - M_PIF / 2.f) + 1.f) * 0.5f, 1.f)));\n  x94_model_textarrowbottom->SetColor(\n      zeus::CColor::lerp(lowC, g_tweakGuiColors->GetPauseItemAmberColor(),\n                         zeus::clamp(0.f, (std::sin(5.f * textDownT - M_PIF / 2.f) + 1.f) * 0.5f, 1.f)));\n}\n\nvoid CPauseScreenBase::ProcessControllerInput(const CFinalInput& input) {\n  x198_25_handledInput = false;\n  x8_frame.ProcessUserInput(input);\n}\n\nbool CPauseScreenBase::ProcessMouseInput(const CFinalInput& input, float yOff) {\n  m_bodyUpClicked = false;\n  m_bodyDownClicked = false;\n  m_bodyClicked = false;\n  m_leftClicked = false;\n  m_rightClicked = false;\n  CGuiWidgetDrawParms parms(1.f, zeus::CVector3f{0.f, 15.f * yOff, 0.f});\n  return x8_frame.ProcessMouseInput(input, parms);\n}\n\nvoid CPauseScreenBase::ResetMouseState() {\n  m_bodyUpClicked = false;\n  m_bodyDownClicked = false;\n  m_bodyClicked = false;\n  m_leftClicked = false;\n  m_rightClicked = false;\n  x8_frame.ResetMouseState();\n}\n\nvoid CPauseScreenBase::Draw(float mainAlpha, float frameAlpha, float yOff) {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CPauseScreenBase::Draw\", zeus::skBlue);\n\n  zeus::CColor color = zeus::skWhite;\n  color.a() = mainAlpha * x14_alpha;\n  x60_basewidget_pivot->SetColor(color);\n  color.a() = frameAlpha;\n  x64_basewidget_bgframe->SetColor(color);\n\n  CGuiWidgetDrawParms parms(1.f, zeus::CVector3f{0.f, 15.f * yOff, 0.f});\n  x8_frame.Draw(parms);\n}\n\nvoid CPauseScreenBase::UpdateRightTable() {\n  x18_firstViewRightSel = 0;\n  x1c_rightSel = 0;\n  x84_tablegroup_rightlog->SetUserSelection(1);\n  UpdateSideTable(x84_tablegroup_rightlog);\n}\n\nvoid CPauseScreenBase::SetRightTableSelection(int oldSel, int newSel) {\n  int oldRightSel = x1c_rightSel;\n  x1c_rightSel = zeus::clamp(0, x1c_rightSel + (newSel - oldSel), int(GetRightTableCount()) - 1);\n  if (m_playRightTableSfx && oldRightSel != x1c_rightSel)\n    CSfxManager::SfxStart(SFXui_table_selection_change, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n\n  if (x1c_rightSel < x18_firstViewRightSel)\n    x18_firstViewRightSel = x1c_rightSel;\n  else if (x1c_rightSel >= x18_firstViewRightSel + 5)\n    x18_firstViewRightSel = x1c_rightSel - 4;\n\n  x84_tablegroup_rightlog->SetUserSelection(x1c_rightSel + 1 - x18_firstViewRightSel);\n  UpdateSideTable(x84_tablegroup_rightlog);\n  RightTableSelectionChanged(oldSel, newSel);\n}\n\nvoid CPauseScreenBase::OnLeftTableAdvance(CGuiTableGroup* caller) {\n  if (ShouldLeftTableAdvance()) {\n    ChangeMode(EMode::RightTable);\n    x198_25_handledInput = true;\n    CSfxManager::SfxStart(SFXui_advance, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n  }\n}\n\nvoid CPauseScreenBase::OnRightTableAdvance(CGuiTableGroup* caller) {\n  if (ShouldRightTableAdvance() && !x198_25_handledInput) {\n    ChangeMode(EMode::TextScroll);\n    CSfxManager::SfxStart(SFXui_advance, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n  }\n}\n\nvoid CPauseScreenBase::OnTableSelectionChange(CGuiTableGroup* caller, int oldSel) {\n  UpdateSideTable(caller);\n  if (x70_tablegroup_leftlog == caller) {\n    CSfxManager::SfxStart(SFXui_table_selection_change, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n    UpdateRightTable();\n  } else {\n    SetRightTableSelection(oldSel, x84_tablegroup_rightlog->GetUserSelection());\n  }\n}\n\nvoid CPauseScreenBase::OnRightTableCancel(CGuiTableGroup* caller) { ChangeMode(EMode::LeftTable); }\n\nvoid CPauseScreenBase::OnWidgetMouseUp(CGuiWidget* widget, bool cancel) {\n  if (cancel || !widget)\n    return;\n  if (widget->GetParent() == x70_tablegroup_leftlog) {\n    if (m_isLogBook && x10_mode == EMode::TextScroll)\n      return;\n    int idx = int(std::find(m_model_lefttitledecos.begin(), m_model_lefttitledecos.end(), widget) -\n                  m_model_lefttitledecos.begin());\n    if (x70_tablegroup_leftlog->IsWorkerSelectable(idx)) {\n      /* Simulate change to left table */\n      if (x10_mode == EMode::TextScroll)\n        ChangeMode(EMode::RightTable, false);\n      if (x10_mode == EMode::RightTable)\n        ChangeMode(EMode::LeftTable, false);\n      /* Simulate selection change */\n      int oldSel = x70_tablegroup_leftlog->GetUserSelection();\n      x70_tablegroup_leftlog->SelectWorker(idx);\n      OnTableSelectionChange(x70_tablegroup_leftlog, oldSel);\n      /* Simulate change to right table if able */\n      if (ShouldLeftTableAdvance())\n        ChangeMode(EMode::RightTable, false);\n      m_leftClicked = true;\n    }\n  } else if (widget->GetParent() == x84_tablegroup_rightlog) {\n    if (m_isLogBook && x10_mode == EMode::TextScroll)\n      return;\n    int idx = int(std::find(x15c_model_righttitledecos.begin(), x15c_model_righttitledecos.end(), widget) -\n                  x15c_model_righttitledecos.begin()) +\n              1;\n    if (x10_mode == EMode::LeftTable) {\n      if (ShouldLeftTableAdvance())\n        ChangeMode(EMode::RightTable, false);\n      else\n        return;\n    }\n    if (x84_tablegroup_rightlog->IsWorkerSelectable(idx)) {\n      /* Simulate change to right table */\n      if (x10_mode == EMode::TextScroll)\n        ChangeMode(EMode::RightTable, false);\n      /* Simulate selection change */\n      int oldSel = x84_tablegroup_rightlog->GetUserSelection();\n      x84_tablegroup_rightlog->SelectWorker(idx);\n      m_playRightTableSfx = !ShouldRightTableAdvance();\n      OnTableSelectionChange(x84_tablegroup_rightlog, oldSel);\n      m_playRightTableSfx = true;\n      /* Simulate change to text scroll if able */\n      OnRightTableAdvance(nullptr);\n      m_rightClicked = true;\n    }\n  } else if (widget == x174_textpane_body) {\n    m_bodyClicked = true;\n  } else if (widget == x90_model_textarrowtop) {\n    m_bodyUpClicked = true;\n  } else if (widget == x94_model_textarrowbottom) {\n    m_bodyDownClicked = true;\n  } else if (widget == xa0_model_scrollrightup) {\n    if (x10_mode == EMode::LeftTable) {\n      if (ShouldLeftTableAdvance())\n        ChangeMode(EMode::RightTable, false);\n      else\n        return;\n    }\n    if (x10_mode == EMode::RightTable && x18_firstViewRightSel > 0) {\n      /* Simulate selection change */\n      int oldSel = x84_tablegroup_rightlog->GetUserSelection();\n      x84_tablegroup_rightlog->SelectWorker(0);\n      OnTableSelectionChange(x84_tablegroup_rightlog, oldSel);\n    }\n  } else if (widget == xa4_model_scrollrightdown) {\n    if (x10_mode == EMode::LeftTable) {\n      if (ShouldLeftTableAdvance())\n        ChangeMode(EMode::RightTable, false);\n      else\n        return;\n    }\n    if (x10_mode == EMode::RightTable && x18_firstViewRightSel + 5 < GetRightTableCount()) {\n      /* Simulate selection change */\n      int oldSel = x84_tablegroup_rightlog->GetUserSelection();\n      x84_tablegroup_rightlog->SelectWorker(6);\n      OnTableSelectionChange(x84_tablegroup_rightlog, oldSel);\n    }\n  }\n}\n\nvoid CPauseScreenBase::OnWidgetScroll(CGuiWidget* widget, const SScrollDelta& delta, int accumX, int accumY) {\n  if (!widget || accumY == 0)\n    return;\n  if (widget->GetParent() == x84_tablegroup_rightlog) {\n    if (x10_mode == EMode::LeftTable) {\n      if (ShouldLeftTableAdvance())\n        ChangeMode(EMode::RightTable, false);\n      else\n        return;\n    }\n    if (accumY < 0)\n      do {\n        if (x10_mode == EMode::RightTable && x18_firstViewRightSel + 5 < GetRightTableCount()) {\n          /* Simulate selection change */\n          int oldSel = x84_tablegroup_rightlog->GetUserSelection();\n          x84_tablegroup_rightlog->SelectWorker(6);\n          OnTableSelectionChange(x84_tablegroup_rightlog, oldSel);\n        }\n      } while (++accumY < 0);\n    else if (accumY > 0)\n      do {\n        if (x10_mode == EMode::RightTable && x18_firstViewRightSel > 0) {\n          /* Simulate selection change */\n          int oldSel = x84_tablegroup_rightlog->GetUserSelection();\n          x84_tablegroup_rightlog->SelectWorker(0);\n          OnTableSelectionChange(x84_tablegroup_rightlog, oldSel);\n        }\n      } while (--accumY > 0);\n  } else if (widget == x174_textpane_body) {\n    if (accumY < 0)\n      m_bodyDownClicked = true;\n    else if (accumY > 0)\n      m_bodyUpClicked = true;\n  }\n}\n\nstd::string CPauseScreenBase::GetImagePaneName(size_t i) {\n  static constexpr std::array PaneSuffixes{\n      \"0\", \"1\", \"2\", \"3\", \"01\", \"12\", \"23\", \"012\", \"123\", \"0123\",\n      \"4\", \"5\", \"6\", \"7\", \"45\", \"56\", \"67\", \"456\", \"567\", \"4567\",\n  };\n\n  return fmt::format(\"imagepane_pane{}\", PaneSuffixes[i]);\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/CPauseScreenBase.hpp",
    "content": "#pragma once\n\n#include <string>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/GuiSys/CGuiFrame.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CArchitectureQueue;\nclass CAuiImagePane;\nclass CGuiModel;\nclass CGuiSliderGroup;\nclass CGuiTableGroup;\nclass CGuiTextPane;\nclass CGuiWidget;\nclass CRandom16;\nclass CStateManager;\nclass CStringTable;\n\nnamespace MP1 {\n\nclass CPauseScreenBase {\npublic:\n  enum class EMode { Invalid = -1, LeftTable = 0, RightTable = 1, TextScroll = 2 };\n\nprotected:\n  const CStateManager& x4_mgr;\n  CGuiFrame& x8_frame;\n  const CStringTable& xc_pauseStrg;\n  EMode x10_mode = EMode::Invalid;\n  float x14_alpha = 0.f;\n  int x18_firstViewRightSel = 0;\n  int x1c_rightSel = 0;\n  zeus::CVector3f x20_;\n  zeus::CVector3f x2c_rightTableStart;\n  float x38_highlightPitch = 0.f;\n  zeus::CVector3f x3c_sliderStart;\n  zeus::CVector3f x48_tableDoubleStart;\n  zeus::CVector3f x54_tableTripleStart;\n  CGuiWidget* x60_basewidget_pivot;\n  CGuiWidget* x64_basewidget_bgframe;\n  CGuiWidget* x68_basewidget_leftside = nullptr;\n  CGuiWidget* x6c_basewidget_leftlog = nullptr;\n  CGuiTableGroup* x70_tablegroup_leftlog = nullptr;\n  CGuiWidget* x74_basewidget_leftguages = nullptr;\n  CGuiModel* x78_model_lefthighlight = nullptr;\n  CGuiWidget* x7c_basewidget_rightside = nullptr;\n  CGuiWidget* x80_basewidget_rightlog = nullptr;\n  CGuiTableGroup* x84_tablegroup_rightlog = nullptr;\n  CGuiWidget* x88_basewidget_rightguages = nullptr;\n  CGuiModel* x8c_model_righthighlight = nullptr;\n  CGuiModel* x90_model_textarrowtop;\n  CGuiModel* x94_model_textarrowbottom;\n  CGuiModel* x98_model_scrollleftup;\n  CGuiModel* x9c_model_scrollleftdown;\n  CGuiModel* xa0_model_scrollrightup;\n  CGuiModel* xa4_model_scrollrightdown;\n  rstl::reserved_vector<CGuiTextPane*, 5> xa8_textpane_categories;\n  rstl::reserved_vector<CGuiModel*, 5> xc0_model_categories;\n  rstl::reserved_vector<CGuiTextPane*, 5> xd8_textpane_titles;\n  rstl::reserved_vector<CAuiImagePane*, 20> xf0_imagePanes;\n  rstl::reserved_vector<CGuiModel*, 5> x144_model_titles;\n  rstl::reserved_vector<CGuiModel*, 5> m_model_lefttitledecos;\n  rstl::reserved_vector<CGuiModel*, 5> x15c_model_righttitledecos;\n  CGuiTextPane* x174_textpane_body = nullptr;\n  CGuiTextPane* x178_textpane_title = nullptr;\n  CGuiModel* x17c_model_textalpha;\n  CGuiWidget* x180_basewidget_yicon = nullptr;\n  CGuiTextPane* x184_textpane_yicon = nullptr;\n  CGuiTextPane* x188_textpane_ytext = nullptr;\n  CGuiSliderGroup* x18c_slidergroup_slider = nullptr;\n  CGuiTableGroup* x190_tablegroup_double = nullptr;\n  CGuiTableGroup* x194_tablegroup_triple = nullptr;\n  bool x198_24_ready : 1 = false;\n  bool x198_25_handledInput : 1 = false;\n  bool x198_26_exitPauseScreen : 1 = false;\n  bool x198_27_canDraw : 1 = false;\n  bool x198_28_pulseTextArrowTop : 1 = false;\n  bool x198_29_pulseTextArrowBottom : 1 = false;\n  bool m_isLogBook : 1;\n  bool m_bodyUpClicked : 1 = false;\n  bool m_bodyDownClicked : 1 = false;\n  bool m_bodyClicked : 1 = false;\n  bool m_leftClicked : 1 = false;\n  bool m_rightClicked : 1 = false;\n  bool m_playRightTableSfx : 1 = true;\n\n  void InitializeFrameGlue();\n  void ChangeMode(EMode mode, bool playSfx = true);\n  void UpdateSideTable(CGuiTableGroup* table);\n  void SetRightTableSelection(int oldSel, int newSel);\n\n  void OnLeftTableAdvance(CGuiTableGroup* caller);\n  void OnRightTableAdvance(CGuiTableGroup* caller);\n  void OnTableSelectionChange(CGuiTableGroup* caller, int oldSel);\n  void OnRightTableCancel(CGuiTableGroup* caller);\n\n  void OnWidgetMouseUp(CGuiWidget* widget, bool cancel);\n  void OnWidgetScroll(CGuiWidget* widget, const SScrollDelta& delta, int accumX, int accumY);\n\npublic:\n  static std::string GetImagePaneName(size_t i);\n\n  CPauseScreenBase(const CStateManager& mgr, CGuiFrame& frame, const CStringTable& pauseStrg, bool isLogBook = false);\n\n  bool ShouldExitPauseScreen() const { return x198_26_exitPauseScreen; }\n  bool IsReady();\n  bool CanDraw() const { return x198_27_canDraw; }\n  EMode GetMode() const { return x10_mode; }\n  float GetAlpha() const { return x14_alpha; }\n\n  virtual ~CPauseScreenBase() = default;\n  virtual bool InputDisabled() const { return false; }\n  virtual void TransitioningAway() {}\n  virtual void Update(float dt, CRandom16& rand, CArchitectureQueue& archQueue);\n  virtual void Touch() {}\n  virtual void ProcessControllerInput(const CFinalInput& input);\n  bool ProcessMouseInput(const CFinalInput& input, float yOff);\n  void ResetMouseState();\n  virtual void Draw(float transInterp, float totalAlpha, float yOff);\n  virtual float GetCameraYBias() const { return 0.f; }\n  virtual bool VReady() const = 0;\n  virtual void VActivate() = 0;\n  virtual void RightTableSelectionChanged(int oldSel, int newSel) {}\n  virtual void ChangedMode(EMode oldMode) {}\n  virtual void UpdateRightTable();\n  virtual bool ShouldLeftTableAdvance() const { return true; }\n  virtual bool ShouldRightTableAdvance() const { return true; }\n  virtual u32 GetRightTableCount() const = 0;\n  virtual bool IsRightLogDynamic() const { return false; }\n  virtual void UpdateRightLogColors(bool active, const zeus::CColor& activeColor, const zeus::CColor& inactiveColor) {}\n  virtual void UpdateRightLogHighlight(bool active, int idx, const zeus::CColor& activeColor,\n                                       const zeus::CColor& inactiveColor) {}\n};\n\n} // namespace MP1\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/MP1/CPauseScreenBlur.cpp",
    "content": "#include \"Runtime/MP1/CPauseScreenBlur.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Audio/CSfxManager.hpp\"\n\nnamespace metaforce::MP1 {\n\nCPauseScreenBlur::CPauseScreenBlur() : x4_mapLightQuarter(g_SimplePool->GetObj(\"TXTR_MapLightQuarter\")) {}\n\nvoid CPauseScreenBlur::OnNewInGameGuiState(EInGameGuiState state, CStateManager& stateMgr) {\n  switch (state) {\n  case EInGameGuiState::Zero:\n  case EInGameGuiState::InGame:\n    SetState(EState::InGame);\n    break;\n  case EInGameGuiState::MapScreen:\n    SetState(EState::MapScreen);\n    break;\n  case EInGameGuiState::PauseSaveGame:\n    SetState(EState::SaveGame);\n    break;\n  case EInGameGuiState::PauseHUDMessage:\n    SetState(EState::HUDMessage);\n    break;\n  case EInGameGuiState::PauseGame:\n  case EInGameGuiState::PauseLogBook:\n    SetState(EState::Pause);\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CPauseScreenBlur::SetState(EState state) {\n  if (x10_prevState == EState::InGame && state != EState::InGame) {\n    CSfxManager::SetChannel(CSfxManager::ESfxChannels::PauseScreen);\n    if (state == EState::HUDMessage)\n      CSfxManager::SfxStart(SFXui_into_hud_message, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n    else if (state == EState::MapScreen)\n      CSfxManager::SfxStart(SFXui_into_map_screen, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n    x18_blurAmt = FLT_EPSILON;\n  }\n\n  if (state == EState::InGame && (x10_prevState != EState::InGame || x14_nextState != EState::InGame)) {\n    CSfxManager::SetChannel(CSfxManager::ESfxChannels::Game);\n    if (x10_prevState == EState::HUDMessage)\n      CSfxManager::SfxStart(SFXui_outof_hud_message, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n    else if (x10_prevState == EState::MapScreen)\n      CSfxManager::SfxStart(SFXui_outof_map_screen, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n    x18_blurAmt = -1.f;\n  }\n\n  x14_nextState = state;\n}\n\nvoid CPauseScreenBlur::OnBlurComplete(bool b) {\n  if (x14_nextState == EState::InGame && !b)\n    return;\n  x10_prevState = x14_nextState;\n  if (x10_prevState == EState::InGame)\n    x50_25_gameDraw = true;\n}\n\nvoid CPauseScreenBlur::Update(float dt, const CStateManager& stateMgr, bool b) {\n  if (x10_prevState == x14_nextState)\n    return;\n\n  if (x18_blurAmt < 0.f)\n    x18_blurAmt = std::min(0.f, 2.f * dt + x18_blurAmt);\n  else\n    x18_blurAmt = std::min(1.f, 2.f * dt + x18_blurAmt);\n\n  if (x18_blurAmt == 0.f || x18_blurAmt == 1.f)\n    OnBlurComplete(b);\n\n  if (x18_blurAmt == 0.f && b) {\n    x1c_camBlur.DisableBlur(0.f);\n  } else {\n    x1c_camBlur.SetBlur(EBlurType::HiBlur, g_tweakGui->GetPauseBlurFactor() * std::fabs(x18_blurAmt), 0.f, true);\n    x50_24_blurring = true;\n  }\n}\n\nvoid CPauseScreenBlur::Draw(const CStateManager&) {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CPauseScreenBlur::Draw\", zeus::skPurple);\n  x1c_camBlur.Draw(true);\n  CGraphics::DisableAllLights();\n  CGraphics::SetAmbientColor(zeus::skWhite);\n  const float t = std::fabs(x18_blurAmt);\n  if (x1c_camBlur.GetCurrType() != EBlurType::NoBlur) {\n    const auto filterColor = zeus::CColor::lerp(zeus::skWhite, g_tweakGuiColors->GetPauseBlurFilterColor(), t);\n    CCameraFilterPass::DrawFilter(EFilterType::Multiply, EFilterShape::FullscreenQuarters, filterColor,\n                                  x4_mapLightQuarter.GetObj(), t);\n    const auto scanLinesColor = zeus::CColor::lerp(zeus::skWhite, zeus::CColor(0.75f, 1.f), t);\n    CCameraFilterPass::DrawFilter(EFilterType::Multiply, EFilterShape::ScanLinesEven, scanLinesColor, nullptr, t);\n  }\n\n  if (x50_24_blurring /*&& x1c_camBlur.x2d_noPersistentCopy*/) {\n    x50_24_blurring = false;\n    x50_25_gameDraw = false;\n  }\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/CPauseScreenBlur.hpp",
    "content": "#pragma once\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/Camera/CCameraFilter.hpp\"\n#include \"Runtime/Graphics/CTexture.hpp\"\n#include \"Runtime/MP1/CInGameGuiManagerCommon.hpp\"\n\nnamespace metaforce {\nclass CStateManager;\n\nnamespace MP1 {\n\nclass CPauseScreenBlur {\n  enum class EState { InGame, MapScreen, SaveGame, HUDMessage, Pause };\n\n  TLockedToken<CTexture> x4_mapLightQuarter;\n  EState x10_prevState = EState::InGame;\n  EState x14_nextState = EState::InGame;\n  float x18_blurAmt = 0.f;\n  CCameraBlurPass x1c_camBlur;\n  bool x50_24_blurring : 1 = false;\n  bool x50_25_gameDraw : 1 = true;\n\n  void OnBlurComplete(bool);\n  void SetState(EState state);\n\npublic:\n  CPauseScreenBlur();\n  void OnNewInGameGuiState(EInGameGuiState state, CStateManager& stateMgr);\n  bool IsGameDraw() const { return x50_25_gameDraw; }\n  void Update(float dt, const CStateManager& stateMgr, bool);\n  void Draw(const CStateManager& stateMgr);\n  float GetBlurAmt() const { return std::fabs(x18_blurAmt); }\n  bool IsNotTransitioning() const { return x10_prevState == x14_nextState; }\n};\n\n} // namespace MP1\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/MP1/CPlayMovie.cpp",
    "content": "#include \"Runtime/MP1/CPlayMovie.hpp\"\n\n#include \"Runtime/GuiSys/CGuiFrame.hpp\"\n\nnamespace metaforce::MP1 {\n\nconst char* kMovies[] = {\n    \"Video/wingame.thp\",       \"Video/wingame_good.thp\", \"Video/wingame_best.thp\",  \"Video/losegame.thp\",\n    \"Video/05_tallonText.thp\", \"Video/AfterCredits.thp\", \"Video/SpecialEnding.thp\", \"Video/creditBG.thp\",\n};\n\nbool CPlayMovie::IsResultsScreen(EWhichMovie which) { return int(which) <= 2; }\n\nCPlayMovie::CPlayMovie(EWhichMovie which) : CIOWin(\"CPlayMovie\"), x18_which(which) {}\n\nCIOWin::EMessageReturn CPlayMovie::OnMessage(const CArchitectureMessage& msg, CArchitectureQueue& queue) {\n  return EMessageReturn::RemoveIOWinAndExit;\n}\n\nvoid CPlayMovie::Draw() {\n  if (x14_ != 3) {\n    return;\n  }\n\n  DrawVideo();\n  if (x78_27_) {\n    x40_quitScreen->Draw();\n  } else if (x78_26_resultsScreen) {\n    DrawText();\n  }\n}\n\nvoid CPlayMovie::DrawVideo() {\n  if (x38_moviePlayer) {\n    x38_moviePlayer->DrawVideo();\n  }\n}\n\nvoid CPlayMovie::DrawText() {\n  // TODO\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/CPlayMovie.hpp",
    "content": "#pragma once\n\n#include \"Runtime/CIOWin.hpp\"\n#include \"Runtime/Graphics/CMoviePlayer.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/MP1/CQuitGameScreen.hpp\"\n\nnamespace metaforce::MP1 {\n\nclass CPlayMovie : public CIOWin {\npublic:\n  enum class EWhichMovie {\n    WinGameBad,\n    WinGameGood,\n    WinGameBest,\n    LoseGame,\n    TalonText,\n    AfterCredits,\n    SpecialEnding,\n    CreditBG\n  };\n\nprivate:\n  s32 x14_ = 0;\n  EWhichMovie x18_which;\n  std::unique_ptr<CMoviePlayer> x38_moviePlayer;\n  std::unique_ptr<CQuitGameScreen> x40_quitScreen;\n  bool x78_24_ : 1 = false;\n  bool x78_25_ : 1 = false;\n  bool x78_26_resultsScreen : 1 = false;\n  bool x78_27_ : 1 = false;\n\n  static bool IsResultsScreen(EWhichMovie which);\n\n  void DrawVideo();\n  void DrawText();\n\npublic:\n  explicit CPlayMovie(EWhichMovie which);\n  EMessageReturn OnMessage(const CArchitectureMessage&, CArchitectureQueue&) override;\n  void Draw() override;\n  bool GetIsContinueDraw() const override { return false; }\n};\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/CPlayerVisor.cpp",
    "content": "#include \"Runtime/MP1/CPlayerVisor.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Camera/CGameCamera.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Graphics/CModel.hpp\"\n#include \"Runtime/GuiSys/CCompoundTargetReticle.hpp\"\n#include \"Runtime/GuiSys/CTargetingManager.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce::MP1 {\n\nCPlayerVisor::CPlayerVisor(CStateManager&) {\n  xcc_scanFrameCorner = g_SimplePool->GetObj(\"CMDL_ScanFrameCorner\");\n  xd8_scanFrameCenterSide = g_SimplePool->GetObj(\"CMDL_ScanFrameCenterSide\");\n  xe4_scanFrameCenterTop = g_SimplePool->GetObj(\"CMDL_ScanFrameCenterTop\");\n  xf0_scanFrameStretchSide = g_SimplePool->GetObj(\"CMDL_ScanFrameStretchSide\");\n  xfc_scanFrameStretchTop = g_SimplePool->GetObj(\"CMDL_ScanFrameStretchTop\");\n  x108_newScanPane = g_SimplePool->GetObj(\"CMDL_NewScanPane\");\n  x114_scanShield = g_SimplePool->GetObj(\"CMDL_ScanShield\");\n  x124_scanIconNoncritical = g_SimplePool->GetObj(\"CMDL_ScanIconNoncritical\");\n  x130_scanIconCritical = g_SimplePool->GetObj(\"CMDL_ScanIconCritical\");\n  x13c_scanTargets.resize(64);\n  x540_xrayPalette = g_SimplePool->GetObj(\"TXTR_XRayPalette\");\n  x0_scanWindowSizes.push_back({});\n  x0_scanWindowSizes.push_back({g_tweakGui->GetScanWindowIdleWidth(), g_tweakGui->GetScanWindowIdleHeight()});\n  x0_scanWindowSizes.push_back({g_tweakGui->GetScanWindowActiveWidth(), g_tweakGui->GetScanWindowActiveHeight()});\n}\n\nCPlayerVisor::~CPlayerVisor() {\n  CSfxManager::SfxStop(x5c_visorLoopSfx);\n  CSfxManager::SfxStop(x60_scanningLoopSfx);\n}\n\nint CPlayerVisor::FindEmptyInactiveScanTarget() const {\n  for (size_t i = 0; i < x13c_scanTargets.size(); ++i) {\n    const SScanTarget& tgt = x13c_scanTargets[i];\n    if (tgt.x4_timer == 0.f)\n      return i;\n  }\n  return -1;\n}\n\nint CPlayerVisor::FindCachedInactiveScanTarget(TUniqueId uid) const {\n  for (size_t i = 0; i < x13c_scanTargets.size(); ++i) {\n    const SScanTarget& tgt = x13c_scanTargets[i];\n    if (tgt.x0_objId == uid && tgt.x4_timer > 0.f)\n      return i;\n  }\n  return -1;\n}\n\nbool CPlayerVisor::DrawScanObjectIndicators(const CStateManager& mgr) {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CPlayerVisor::DrawScanObjectIndicators\", zeus::skMagenta);\n  if (!x124_scanIconNoncritical.IsLoaded() || !x130_scanIconCritical.IsLoaded())\n    return false;\n  if (!x114_scanShield.IsLoaded())\n    return false;\n\n  CGraphics::SetDepthRange(DEPTH_WORLD, DEPTH_FAR);\n  g_Renderer->SetViewportOrtho(true, 0.f, 4096.f);\n\n  float vpScale = CGraphics::GetViewportHeight() / 448.f;\n  CGraphics::SetModelMatrix(zeus::CTransform::Scale(x48_interpWindowDims.x() * 17.f * vpScale, 1.f,\n                                                    x48_interpWindowDims.y() * 17.f * vpScale));\n\n  x114_scanShield->Draw(CModelFlags(5, 0, 3, zeus::skClear));\n\n  const CGameCamera* cam = mgr.GetCameraManager()->GetCurrentCamera(mgr);\n  zeus::CTransform camMtx = mgr.GetCameraManager()->GetCurrentCameraTransform(mgr);\n  CGraphics::SetViewPointMatrix(camMtx);\n  zeus::CFrustum frustum;\n  frustum.updatePlanes(\n      camMtx, zeus::CProjection(zeus::SProjPersp(\n                  cam->GetFov(), CGraphics::GetViewportWidth() / float(CGraphics::GetViewportHeight()), 1.f, 100.f)));\n  g_Renderer->SetClippingPlanes(frustum);\n  g_Renderer->SetPerspective(cam->GetFov(), CGraphics::GetViewportWidth(), CGraphics::GetViewportHeight(),\n                             cam->GetNearClipDistance(), cam->GetFarClipDistance());\n\n  for (const SScanTarget& tgt : x13c_scanTargets) {\n    if (tgt.x4_timer == 0.f)\n      continue;\n    if (TCastToConstPtr<CActor> act = mgr.GetObjectById(tgt.x0_objId)) {\n      if (!act->GetMaterialList().HasMaterial(EMaterialTypes::Scannable))\n        continue;\n      const CScannableObjectInfo* scanInfo = act->GetScannableObjectInfo();\n      CModel* useModel;\n      const zeus::CColor* useColor;\n      const zeus::CColor* useDimColor;\n      if (scanInfo->IsImportant()) {\n        useModel = x130_scanIconCritical.GetObj();\n        useColor = &g_tweakGuiColors->GetScanIconCriticalColor();\n        useDimColor = &g_tweakGuiColors->GetScanIconCriticalDimColor();\n      } else {\n        useModel = x124_scanIconNoncritical.GetObj();\n        useColor = &g_tweakGuiColors->GetScanIconNoncriticalColor();\n        useDimColor = &g_tweakGuiColors->GetScanIconNoncriticalDimColor();\n      }\n\n      zeus::CVector3f scanPos = act->GetScanObjectIndicatorPosition(mgr);\n      float scale = CCompoundTargetReticle::CalculateClampedScale(\n          scanPos, 1.f, g_tweakTargeting->GetScanTargetClampMin(), g_tweakTargeting->GetScanTargetClampMax(), mgr);\n      zeus::CTransform xf(zeus::CMatrix3f(scale) * camMtx.basis, scanPos);\n\n      float scanRange = g_tweakPlayer->GetScanningRange();\n      float farRange = g_tweakPlayer->GetScanMaxLockDistance() - scanRange;\n      float farT;\n      if (farRange <= 0.f)\n        farT = 1.f;\n      else\n        farT = zeus::clamp(0.f, 1.f - ((scanPos - camMtx.origin).magnitude() - scanRange) / farRange, 1.f);\n\n      zeus::CColor iconColor = zeus::CColor::lerp(*useColor, *useDimColor, tgt.x8_inRangeTimer);\n      float iconAlpha;\n      if (mgr.GetPlayerState()->GetScanTime(scanInfo->GetScannableObjectId()) == 1.f) {\n        iconAlpha = tgt.x4_timer * 0.25f;\n      } else {\n        float tmp = 1.f;\n        if (mgr.GetPlayer().GetOrbitTargetId() == tgt.x0_objId)\n          tmp = 0.75f * x2c_scanDimInterp + 0.25f;\n        iconAlpha = tgt.x4_timer * tmp;\n      }\n\n      CGraphics::SetModelMatrix(xf);\n      iconColor.a() *= iconAlpha * farT;\n      useModel->Draw(CModelFlags(7, 0, 1, iconColor));\n    }\n  }\n\n  CGraphics::SetDepthRange(DEPTH_SCREEN_ACTORS, DEPTH_GUN);\n  return true;\n}\n\nvoid CPlayerVisor::UpdateScanObjectIndicators(const CStateManager& mgr, float dt) {\n  bool inBoxExists = false;\n  float dt2 = dt * 2.f;\n\n  for (SScanTarget& tgt : x13c_scanTargets) {\n    tgt.x4_timer = std::max(0.f, tgt.x4_timer - dt);\n    if (mgr.GetPlayer().ObjectInScanningRange(tgt.x0_objId, mgr))\n      tgt.x8_inRangeTimer = std::max(0.f, tgt.x8_inRangeTimer - dt2);\n    else\n      tgt.x8_inRangeTimer = std::min(1.f, tgt.x8_inRangeTimer + dt2);\n\n    if (TCastToConstPtr<CActor> act = mgr.GetObjectById(tgt.x0_objId)) {\n      const CGameCamera* cam = mgr.GetCameraManager()->GetCurrentCamera(mgr);\n      zeus::CVector3f orbitPos = act->GetOrbitPosition(mgr);\n      orbitPos = cam->ConvertToScreenSpace(orbitPos);\n      orbitPos.x() = orbitPos.x() * CGraphics::GetViewportWidth() / 2.f + CGraphics::GetViewportWidth() / 2.f;\n      orbitPos.y() = orbitPos.y() * CGraphics::GetViewportHeight() / 2.f + CGraphics::GetViewportHeight() / 2.f;\n      bool inBox = mgr.GetPlayer().WithinOrbitScreenBox(orbitPos, mgr.GetPlayer().GetOrbitZone(),\n                                                        mgr.GetPlayer().GetOrbitType());\n      if (inBox != tgt.xc_inBox) {\n        tgt.xc_inBox = inBox;\n        if (inBox)\n          x550_scanFrameColorImpulseInterp = 1.f;\n      }\n      inBoxExists |= inBox;\n    }\n  }\n\n  if (inBoxExists)\n    x54c_scanFrameColorInterp = std::min(x54c_scanFrameColorInterp + dt2, 1.f);\n  else\n    x54c_scanFrameColorInterp = std::max(0.f, x54c_scanFrameColorInterp - dt2);\n\n  x550_scanFrameColorImpulseInterp = std::max(0.f, x550_scanFrameColorImpulseInterp - dt);\n  dt += FLT_EPSILON;\n\n  TAreaId playerArea = mgr.GetPlayer().GetAreaIdAlways();\n  for (TUniqueId id : mgr.GetPlayer().GetNearbyOrbitObjects()) {\n    if (TCastToConstPtr<CActor> act = mgr.GetObjectById(id)) {\n      if (act->GetAreaIdAlways() != playerArea)\n        continue;\n      if (!act->GetMaterialList().HasMaterial(EMaterialTypes::Scannable))\n        continue;\n      int target = FindCachedInactiveScanTarget(id);\n      if (target != -1) {\n        SScanTarget& sTarget = x13c_scanTargets[target];\n        sTarget.x4_timer = std::min(sTarget.x4_timer + dt2, 1.f);\n        continue;\n      }\n      target = FindEmptyInactiveScanTarget();\n      if (target != -1) {\n        SScanTarget& sTarget = x13c_scanTargets[target];\n        sTarget.x0_objId = id;\n        sTarget.x4_timer = dt;\n        sTarget.x8_inRangeTimer = 1.f;\n        sTarget.xc_inBox = false;\n      }\n    }\n  }\n}\n\nvoid CPlayerVisor::UpdateScanWindow(float dt, const CStateManager& mgr) {\n  UpdateScanObjectIndicators(mgr, dt);\n  if (mgr.GetPlayer().GetScanningState() == CPlayer::EPlayerScanState::Scanning) {\n    if (!x60_scanningLoopSfx)\n      x60_scanningLoopSfx =\n          CSfxManager::SfxStart(SFXui_scanning_lp, x24_visorSfxVol, 0.f, false, 0x7f, true, kInvalidAreaId);\n  } else {\n    CSfxManager::SfxStop(x60_scanningLoopSfx);\n    x60_scanningLoopSfx.reset();\n  }\n\n  EScanWindowState desiredState = GetDesiredScanWindowState(mgr);\n  switch (x34_nextState) {\n  case EScanWindowState::NotInScanVisor:\n    if (desiredState != EScanWindowState::NotInScanVisor) {\n      if (x30_prevState == EScanWindowState::NotInScanVisor)\n        x48_interpWindowDims = x0_scanWindowSizes[int(desiredState)];\n      x50_nextWindowDims = x0_scanWindowSizes[int(desiredState)];\n      x40_prevWindowDims = x48_interpWindowDims;\n      x30_prevState = x34_nextState;\n      x34_nextState = desiredState;\n      x38_windowInterpDuration =\n          (desiredState == EScanWindowState::Scan) ? g_tweakGui->GetScanSidesEndTime() - x3c_windowInterpTimer : 0.f;\n      x3c_windowInterpTimer = x38_windowInterpDuration;\n    }\n    break;\n  case EScanWindowState::Idle:\n    if (desiredState != EScanWindowState::Idle) {\n      x50_nextWindowDims = (desiredState == EScanWindowState::NotInScanVisor) ? x48_interpWindowDims\n                                                                              : x0_scanWindowSizes[int(desiredState)];\n      x40_prevWindowDims = x48_interpWindowDims;\n      x30_prevState = x34_nextState;\n      x34_nextState = desiredState;\n      x38_windowInterpDuration =\n          (desiredState == EScanWindowState::Scan) ? g_tweakGui->GetScanSidesEndTime() - x3c_windowInterpTimer : 0.f;\n      x3c_windowInterpTimer = x38_windowInterpDuration;\n      if (desiredState == EScanWindowState::Scan)\n        CSfxManager::SfxStart(SFXui_into_scan_window, x24_visorSfxVol, 0.f, false, 0x7f, false, kInvalidAreaId);\n    }\n    break;\n  case EScanWindowState::Scan:\n    if (desiredState != EScanWindowState::Scan) {\n      x50_nextWindowDims = (desiredState == EScanWindowState::NotInScanVisor) ? x48_interpWindowDims\n                                                                              : x0_scanWindowSizes[int(desiredState)];\n      x40_prevWindowDims = x48_interpWindowDims;\n      x30_prevState = x34_nextState;\n      x34_nextState = desiredState;\n      x38_windowInterpDuration =\n          (desiredState == EScanWindowState::Idle) ? g_tweakGui->GetScanSidesEndTime() - x3c_windowInterpTimer : 0.f;\n      x3c_windowInterpTimer = x38_windowInterpDuration;\n      if (mgr.GetPlayerState()->GetVisorTransitionFactor() == 1.f)\n        CSfxManager::SfxStart(SFXui_outof_scan_window, x24_visorSfxVol, 0.f, false, 0x7f, false, kInvalidAreaId);\n    }\n    break;\n  default:\n    break;\n  }\n\n  if (x30_prevState != x34_nextState) {\n    x3c_windowInterpTimer = std::max(0.f, x3c_windowInterpTimer - dt);\n    if (x3c_windowInterpTimer == 0.f)\n      x30_prevState = x34_nextState;\n\n    float t = 0.f;\n    if (x38_windowInterpDuration > 0.f) {\n      float scanSidesDuration = g_tweakGui->GetScanSidesDuration();\n      float scanSidesStart = g_tweakGui->GetScanSidesStartTime();\n      if (x34_nextState == EScanWindowState::Scan)\n        t = (x3c_windowInterpTimer < scanSidesDuration) ? 0.f\n                                                        : (x3c_windowInterpTimer - scanSidesDuration) / scanSidesStart;\n      else\n        t = (x3c_windowInterpTimer > scanSidesStart) ? 1.f : x3c_windowInterpTimer / scanSidesStart;\n    }\n\n    x48_interpWindowDims = x50_nextWindowDims * (1.f - t) + x40_prevWindowDims * t;\n  }\n}\n\nCPlayerVisor::EScanWindowState CPlayerVisor::GetDesiredScanWindowState(const CStateManager& mgr) const {\n  if (mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::Scan) {\n    switch (mgr.GetPlayer().GetScanningState()) {\n    case CPlayer::EPlayerScanState::Scanning:\n    case CPlayer::EPlayerScanState::ScanComplete:\n      return EScanWindowState::Scan;\n    default:\n      return EScanWindowState::Idle;\n    }\n  }\n  return EScanWindowState::NotInScanVisor;\n}\n\nvoid CPlayerVisor::LockUnlockAssets() {\n  if (x1c_curVisor == CPlayerState::EPlayerVisor::Scan) {\n    x120_assetLockCountdown = 2;\n  } else if (x120_assetLockCountdown > 0) {\n    --x120_assetLockCountdown;\n  }\n\n  if (x120_assetLockCountdown > 0) {\n    xcc_scanFrameCorner.Lock();\n    xd8_scanFrameCenterSide.Lock();\n    xe4_scanFrameCenterTop.Lock();\n    xf0_scanFrameStretchSide.Lock();\n    xfc_scanFrameStretchTop.Lock();\n    x108_newScanPane.Lock();\n    x114_scanShield.Lock();\n    x124_scanIconNoncritical.Lock();\n    x130_scanIconCritical.Lock();\n  } else {\n    xcc_scanFrameCorner.Unlock();\n    xd8_scanFrameCenterSide.Unlock();\n    xe4_scanFrameCenterTop.Unlock();\n    xf0_scanFrameStretchSide.Unlock();\n    xfc_scanFrameStretchTop.Unlock();\n    x108_newScanPane.Unlock();\n    x114_scanShield.Unlock();\n    x124_scanIconNoncritical.Unlock();\n    x130_scanIconCritical.Unlock();\n  }\n}\n\nvoid CPlayerVisor::DrawScanEffect(const CStateManager& mgr, CTargetingManager* tgtMgr) {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CPlayerVisor::DrawScanEffect\", zeus::skMagenta);\n\n  const bool indicatorsDrawn = DrawScanObjectIndicators(mgr);\n  if (tgtMgr && indicatorsDrawn) {\n    CGraphics::SetDepthRange(DEPTH_TARGET_MANAGER, DEPTH_TARGET_MANAGER);\n    tgtMgr->Draw(mgr, false);\n    CGraphics::SetDepthRange(DEPTH_SCREEN_ACTORS, DEPTH_GUN);\n  }\n\n  const float transFactor = mgr.GetPlayerState()->GetVisorTransitionFactor();\n  const float scanSidesDuration = g_tweakGui->GetScanSidesDuration();\n  const float scanSidesStart = g_tweakGui->GetScanSidesStartTime();\n\n  float t;\n  if (x34_nextState == EScanWindowState::Scan) {\n    t = 1.f - ((x3c_windowInterpTimer < scanSidesDuration)\n                   ? 0.f\n                   : (x3c_windowInterpTimer - scanSidesDuration) / scanSidesStart);\n  } else {\n    t = (x3c_windowInterpTimer > scanSidesStart) ? 1.f : x3c_windowInterpTimer / scanSidesStart;\n  }\n\n  const float vpScale = CGraphics::GetViewportHeight() / 448.f;\n  float divisor = (transFactor * ((1.f - t) * x58_scanMagInterp + t * g_tweakGui->GetScanWindowScanningAspect()) +\n                   (1.f - transFactor));\n  divisor = 1.f / divisor;\n  float vpW = 169.218f * x48_interpWindowDims.x() * divisor;\n  vpW = zeus::clamp(0.f, vpW, 640.f) * vpScale;\n  float vpH = 152.218f * x48_interpWindowDims.y() * divisor;\n  vpH = zeus::clamp(0.f, vpH, 448.f) * vpScale;\n\n  GXSetTexCopySrc(int((CGraphics::GetViewportWidth() - vpW) / 2.f), int((CGraphics::GetViewportHeight() - vpH) / 2.f),\n                  vpW, vpH);\n  GXSetTexCopyDst(vpW, vpH, GX_TF_RGB565, false);\n  GXCopyTex(CGraphics::mpSpareBuffer, false);\n  GXPixModeSync();\n\n  {\n    SCOPED_GRAPHICS_DEBUG_GROUP(\"x64_scanDim Draw\", zeus::skMagenta);\n    x64_scanDim.Draw();\n  }\n\n  g_Renderer->SetViewportOrtho(true, -1.f, 1.f);\n\n  const zeus::CTransform windowScale = zeus::CTransform::Scale(x48_interpWindowDims.x(), 1.f, x48_interpWindowDims.y());\n  const zeus::CTransform seventeenScale = zeus::CTransform::Scale(17.f * vpScale, 1.f, 17.f * vpScale);\n  const zeus::CTransform mm = seventeenScale * windowScale;\n  g_Renderer->SetModelMatrix(mm);\n  CGraphics::LoadDolphinSpareTexture(vpW, vpH, GX_TF_RGB565, CGraphics::mpSpareBuffer, GX_TEXMAP0);\n\n  if (x108_newScanPane) {\n    SCOPED_GRAPHICS_DEBUG_GROUP(\"x108_newScanPane Draw\", zeus::skMagenta);\n    x108_newScanPane->Draw(CModelFlags{5, 0, 7, zeus::CColor{1.f, transFactor}});\n  }\n\n  CGraphics::SetCullMode(ERglCullMode::None);\n\n  zeus::CColor frameColor = zeus::CColor::lerp(g_tweakGuiColors->GetScanFrameInactiveColor(),\n                                               g_tweakGuiColors->GetScanFrameActiveColor(), x54c_scanFrameColorInterp);\n  frameColor.a() = transFactor;\n\n  CModelFlags flags(5, 0, 0,\n                    frameColor + g_tweakGuiColors->GetScanFrameImpulseColor() *\n                                     zeus::CColor(x550_scanFrameColorImpulseInterp, x550_scanFrameColorImpulseInterp));\n\n  const zeus::CTransform verticalFlip = zeus::CTransform::Scale(1.f, 1.f, -1.f);\n  const zeus::CTransform horizontalFlip = zeus::CTransform::Scale(-1.f, 1.f, 1.f);\n\n  if (xe4_scanFrameCenterTop.IsLoaded()) {\n    const zeus::CTransform modelXf =\n        seventeenScale * zeus::CTransform::Translate(windowScale * zeus::CVector3f(0.f, 0.f, 4.553f));\n    CGraphics::SetModelMatrix(modelXf);\n    xe4_scanFrameCenterTop->Draw(flags);\n    CGraphics::SetModelMatrix(verticalFlip * modelXf);\n    xe4_scanFrameCenterTop->Draw(flags);\n  }\n\n  if (xd8_scanFrameCenterSide.IsLoaded()) {\n    const zeus::CTransform modelXf =\n        seventeenScale * zeus::CTransform::Translate(windowScale * zeus::CVector3f(-5.f, 0.f, 0.f));\n    CGraphics::SetModelMatrix(modelXf);\n    xd8_scanFrameCenterSide->Draw(flags);\n    CGraphics::SetModelMatrix(horizontalFlip * modelXf);\n    xd8_scanFrameCenterSide->Draw(flags);\n  }\n\n  if (xcc_scanFrameCorner.IsLoaded()) {\n    const zeus::CTransform modelXf =\n        seventeenScale * zeus::CTransform::Translate(windowScale * zeus::CVector3f(-5.f, 0.f, 4.553f));\n    CGraphics::SetModelMatrix(modelXf);\n    xcc_scanFrameCorner->Draw(flags);\n    CGraphics::SetModelMatrix(horizontalFlip * modelXf);\n    xcc_scanFrameCorner->Draw(flags);\n    CGraphics::SetModelMatrix(verticalFlip * modelXf);\n    xcc_scanFrameCorner->Draw(flags);\n    CGraphics::SetModelMatrix(verticalFlip * horizontalFlip * modelXf);\n    xcc_scanFrameCorner->Draw(flags);\n  }\n\n  if (xfc_scanFrameStretchTop.IsLoaded()) {\n    const zeus::CTransform modelXf = seventeenScale *\n                                     zeus::CTransform::Translate(-1.f, 0.f, 4.553f * windowScale.basis[2][2]) *\n                                     zeus::CTransform::Scale(5.f * windowScale.basis[0][0] - 1.f - 1.884f, 1.f, 1.f);\n    CGraphics::SetModelMatrix(modelXf);\n    xfc_scanFrameStretchTop->Draw(flags);\n    CGraphics::SetModelMatrix(horizontalFlip * modelXf);\n    xfc_scanFrameStretchTop->Draw(flags);\n    CGraphics::SetModelMatrix(verticalFlip * modelXf);\n    xfc_scanFrameStretchTop->Draw(flags);\n    CGraphics::SetModelMatrix(verticalFlip * horizontalFlip * modelXf);\n    xfc_scanFrameStretchTop->Draw(flags);\n  }\n\n  if (xf0_scanFrameStretchSide.IsLoaded()) {\n    const zeus::CTransform modelXf = seventeenScale *\n                                     zeus::CTransform::Translate(-5.f * windowScale.basis[0][0], 0.f, 1.f) *\n                                     zeus::CTransform::Scale(1.f, 1.f, 4.553f * windowScale.basis[2][2] - 1.f - 1.886f);\n    CGraphics::SetModelMatrix(modelXf);\n    xf0_scanFrameStretchSide->Draw(flags);\n    CGraphics::SetModelMatrix(horizontalFlip * modelXf);\n    xf0_scanFrameStretchSide->Draw(flags);\n    CGraphics::SetModelMatrix(verticalFlip * modelXf);\n    xf0_scanFrameStretchSide->Draw(flags);\n    CGraphics::SetModelMatrix(verticalFlip * horizontalFlip * modelXf);\n    xf0_scanFrameStretchSide->Draw(flags);\n  }\n\n  CGraphics::SetCullMode(ERglCullMode::Front);\n}\n\nvoid CPlayerVisor::DrawXRayEffect(const CStateManager&) {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CPlayerVisor::DrawXRayEffect\", zeus::skMagenta);\n  x90_xrayBlur.Draw();\n}\n\nvoid CPlayerVisor::DrawThermalEffect(const CStateManager&) {\n  // Empty\n}\n\nvoid CPlayerVisor::UpdateCurrentVisor(float transFactor) {\n  switch (x1c_curVisor) {\n  case CPlayerState::EPlayerVisor::XRay:\n    x90_xrayBlur.SetBlur(EBlurType::Xray, 36.f * transFactor, 0.f, false);\n    break;\n  case CPlayerState::EPlayerVisor::Scan: {\n    zeus::CColor dimColor =\n        zeus::CColor::lerp(g_tweakGuiColors->GetScanVisorHudLightMultiply(), zeus::skWhite, 1.f - transFactor);\n    x64_scanDim.SetFilter(EFilterType::Multiply, EFilterShape::Fullscreen, 0.f, dimColor, CAssetId());\n    break;\n  }\n  default:\n    break;\n  }\n}\n\nvoid CPlayerVisor::FinishTransitionIn() {\n  switch (x1c_curVisor) {\n  case CPlayerState::EPlayerVisor::Combat:\n    x90_xrayBlur.DisableBlur(0.f);\n    break;\n  case CPlayerState::EPlayerVisor::XRay:\n    x90_xrayBlur.SetBlur(EBlurType::Xray, 36.f, 0.f, false);\n    if (!x5c_visorLoopSfx)\n      x5c_visorLoopSfx =\n          CSfxManager::SfxStart(SFXui_visor_xray_lp, x24_visorSfxVol, 0.f, false, 0x7f, true, kInvalidAreaId);\n    break;\n  case CPlayerState::EPlayerVisor::Scan: {\n    zeus::CColor dimColor = zeus::CColor::lerp(g_tweakGuiColors->GetScanVisorScreenDimColor(),\n                                               g_tweakGuiColors->GetScanVisorHudLightMultiply(), x2c_scanDimInterp);\n    x64_scanDim.SetFilter(EFilterType::Multiply, EFilterShape::Fullscreen, 0.f, dimColor, {});\n    if (!x5c_visorLoopSfx)\n      x5c_visorLoopSfx =\n          CSfxManager::SfxStart(SFXui_visor_scan_lp, x24_visorSfxVol, 0.f, false, 0x7f, true, kInvalidAreaId);\n    break;\n  }\n  case CPlayerState::EPlayerVisor::Thermal:\n    if (!x5c_visorLoopSfx)\n      x5c_visorLoopSfx =\n          CSfxManager::SfxStart(SFXui_visor_thermal_lp, x24_visorSfxVol, 0.f, false, 0x7f, true, kInvalidAreaId);\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CPlayerVisor::BeginTransitionIn(const CStateManager&) {\n  switch (x1c_curVisor) {\n  case CPlayerState::EPlayerVisor::XRay:\n    x90_xrayBlur.SetBlur(EBlurType::Xray, 0.f, 0.f, false);\n    xc4_vpScaleX = 0.9f;\n    xc8_vpScaleY = 0.9f;\n    CSfxManager::SfxStart(SFXui_into_visor, x24_visorSfxVol, 0.f, false, 0x7f, false, kInvalidAreaId);\n    break;\n  case CPlayerState::EPlayerVisor::Scan:\n    CSfxManager::SfxStart(SFXui_into_visor, x24_visorSfxVol, 0.f, false, 0x7f, false, kInvalidAreaId);\n    x64_scanDim.SetFilter(EFilterType::Multiply, EFilterShape::Fullscreen, 0.f, zeus::skWhite, {});\n    break;\n  case CPlayerState::EPlayerVisor::Thermal:\n    CSfxManager::SfxStart(SFXui_into_visor, x24_visorSfxVol, 0.f, false, 0x7f, false, kInvalidAreaId);\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CPlayerVisor::FinishTransitionOut(const CStateManager&) {\n  switch (x1c_curVisor) {\n  case CPlayerState::EPlayerVisor::XRay:\n    x90_xrayBlur.DisableBlur(0.f);\n    xc4_vpScaleX = 1.f;\n    xc8_vpScaleY = 1.f;\n    break;\n  case CPlayerState::EPlayerVisor::Scan:\n    x64_scanDim.DisableFilter(0.f);\n    x34_nextState = EScanWindowState::NotInScanVisor;\n    x30_prevState = EScanWindowState::NotInScanVisor;\n    break;\n  case CPlayerState::EPlayerVisor::Thermal:\n    x90_xrayBlur.DisableBlur(0.f);\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CPlayerVisor::BeginTransitionOut() {\n  if (x5c_visorLoopSfx) {\n    CSfxManager::SfxStop(x5c_visorLoopSfx);\n    x5c_visorLoopSfx.reset();\n  }\n\n  switch (x1c_curVisor) {\n  case CPlayerState::EPlayerVisor::XRay:\n    CSfxManager::SfxStart(SFXui_outof_visor, x24_visorSfxVol, 0.f, false, 0x7f, false, kInvalidAreaId);\n    break;\n  case CPlayerState::EPlayerVisor::Scan:\n    if (x60_scanningLoopSfx) {\n      CSfxManager::SfxStop(x60_scanningLoopSfx);\n      x60_scanningLoopSfx.reset();\n    }\n    CSfxManager::SfxStart(SFXui_outof_visor, x24_visorSfxVol, 0.f, false, 0x7f, false, kInvalidAreaId);\n    break;\n  case CPlayerState::EPlayerVisor::Thermal:\n    CSfxManager::SfxStart(SFXui_outof_visor, x24_visorSfxVol, 0.f, false, 0x7f, false, kInvalidAreaId);\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CPlayerVisor::Update(float dt, const CStateManager& mgr) {\n  x90_xrayBlur.Update(dt);\n\n  CPlayerState& playerState = *mgr.GetPlayerState();\n  CPlayerState::EPlayerVisor activeVisor = playerState.GetActiveVisor(mgr);\n  CPlayerState::EPlayerVisor curVisor = playerState.GetCurrentVisor();\n  CPlayerState::EPlayerVisor transVisor = playerState.GetTransitioningVisor();\n  bool visorTransitioning = playerState.GetIsVisorTransitioning();\n\n  UpdateScanWindow(dt, mgr);\n\n  if (x20_nextVisor != transVisor)\n    x20_nextVisor = transVisor;\n\n  LockUnlockAssets();\n\n  if (mgr.GetPlayer().GetScanningState() == CPlayer::EPlayerScanState::ScanComplete)\n    x2c_scanDimInterp = std::max(0.f, x2c_scanDimInterp - 2.f * dt);\n  else\n    x2c_scanDimInterp = std::min(x2c_scanDimInterp + 2.f * dt, 1.f);\n\n  if (visorTransitioning) {\n    if (!x25_24_visorTransitioning)\n      BeginTransitionOut();\n    if (x1c_curVisor != curVisor) {\n      FinishTransitionOut(mgr);\n      x1c_curVisor = curVisor;\n      BeginTransitionIn(mgr);\n    }\n    UpdateCurrentVisor(playerState.GetVisorTransitionFactor());\n  } else {\n    if (x25_24_visorTransitioning) {\n      FinishTransitionIn();\n    } else if (curVisor == CPlayerState::EPlayerVisor::Scan) {\n      zeus::CColor dimColor = zeus::CColor::lerp(g_tweakGuiColors->GetScanVisorScreenDimColor(),\n                                                 g_tweakGuiColors->GetScanVisorHudLightMultiply(), x2c_scanDimInterp);\n      x64_scanDim.SetFilter(EFilterType::Multiply, EFilterShape::Fullscreen, 0.f, dimColor, {});\n    }\n  }\n\n  x25_24_visorTransitioning = visorTransitioning;\n\n  if (x1c_curVisor != activeVisor) {\n    if (x24_visorSfxVol != 0.f) {\n      x24_visorSfxVol = 0.f;\n      CSfxManager::SfxVolume(x5c_visorLoopSfx, x24_visorSfxVol);\n      CSfxManager::SfxVolume(x60_scanningLoopSfx, x24_visorSfxVol);\n    }\n  } else {\n    if (x24_visorSfxVol != 1.f) {\n      x24_visorSfxVol = 1.f;\n      CSfxManager::SfxVolume(x5c_visorLoopSfx, x24_visorSfxVol);\n      CSfxManager::SfxVolume(x60_scanningLoopSfx, x24_visorSfxVol);\n    }\n  }\n\n  float scanMag = g_tweakGui->GetScanWindowMagnification();\n  if (x58_scanMagInterp < scanMag)\n    x58_scanMagInterp = std::min(x58_scanMagInterp + 2.f * dt, scanMag);\n  else\n    x58_scanMagInterp = std::max(x58_scanMagInterp - 2.f * dt, scanMag);\n}\n\nvoid CPlayerVisor::Draw(const CStateManager& mgr, CTargetingManager* tgtManager) {\n  CGraphics::SetAmbientColor(zeus::skWhite);\n  CGraphics::DisableAllLights();\n  switch (mgr.GetPlayerState()->GetActiveVisor(mgr)) {\n  case CPlayerState::EPlayerVisor::XRay:\n    DrawXRayEffect(mgr);\n    break;\n  case CPlayerState::EPlayerVisor::Thermal:\n    DrawThermalEffect(mgr);\n    break;\n  case CPlayerState::EPlayerVisor::Scan:\n    DrawScanEffect(mgr, tgtManager);\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CPlayerVisor::Touch() {\n  if (x124_scanIconNoncritical.IsLoaded())\n    x124_scanIconNoncritical->Touch(0);\n  if (x130_scanIconCritical.IsLoaded())\n    x130_scanIconCritical->Touch(0);\n}\n\nfloat CPlayerVisor::GetDesiredViewportScaleX(const CStateManager& mgr) const {\n  return mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::Combat ? 1.f : xc4_vpScaleX;\n}\n\nfloat CPlayerVisor::GetDesiredViewportScaleY(const CStateManager& mgr) const {\n  return mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::Combat ? 1.f : xc8_vpScaleY;\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/CPlayerVisor.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Audio/CSfxManager.hpp\"\n#include \"Runtime/CPlayerState.hpp\"\n#include \"Runtime/Camera/CCameraFilter.hpp\"\n#include \"Runtime/Graphics/CModel.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n\n#include <zeus/CVector2f.hpp>\n\nnamespace metaforce {\nclass CStateManager;\nclass CTargetingManager;\n\nnamespace MP1 {\n\nclass CPlayerVisor {\n  struct SScanTarget {\n    TUniqueId x0_objId = kInvalidUniqueId;\n    float x4_timer = 0.f;\n    float x8_inRangeTimer = 0.f;\n    bool xc_inBox = false;\n  };\n\n  enum class EScanWindowState { NotInScanVisor, Idle, Scan };\n\n  rstl::reserved_vector<zeus::CVector2f, 3> x0_scanWindowSizes;\n  CPlayerState::EPlayerVisor x1c_curVisor = CPlayerState::EPlayerVisor::Combat;\n  CPlayerState::EPlayerVisor x20_nextVisor = CPlayerState::EPlayerVisor::Combat;\n  float x24_visorSfxVol = 1.f; // used to be u8\n  bool x25_24_visorTransitioning : 1 = false;\n  bool x25_25_ : 1 = false;\n  float x28_ = 0.f;\n  float x2c_scanDimInterp = 1.f;\n  EScanWindowState x30_prevState = EScanWindowState::NotInScanVisor;\n  EScanWindowState x34_nextState = EScanWindowState::NotInScanVisor;\n  float x38_windowInterpDuration = 0.f;\n  float x3c_windowInterpTimer = 0.f;\n  zeus::CVector2f x40_prevWindowDims;\n  zeus::CVector2f x48_interpWindowDims;\n  zeus::CVector2f x50_nextWindowDims;\n  float x58_scanMagInterp = 1.f;\n  CSfxHandle x5c_visorLoopSfx;\n  CSfxHandle x60_scanningLoopSfx;\n  CCameraFilterPass x64_scanDim;\n  CCameraBlurPass x90_xrayBlur;\n  float xc4_vpScaleX = 1.f;\n  float xc8_vpScaleY = 1.f;\n  TCachedToken<CModel> xcc_scanFrameCorner;\n  TCachedToken<CModel> xd8_scanFrameCenterSide;\n  TCachedToken<CModel> xe4_scanFrameCenterTop;\n  TCachedToken<CModel> xf0_scanFrameStretchSide;\n  TCachedToken<CModel> xfc_scanFrameStretchTop;\n  TCachedToken<CModel> x108_newScanPane;\n  TCachedToken<CModel> x114_scanShield;\n  int x120_assetLockCountdown = 0;\n  TCachedToken<CModel> x124_scanIconNoncritical;\n  TCachedToken<CModel> x130_scanIconCritical;\n  rstl::reserved_vector<SScanTarget, 64> x13c_scanTargets;\n  TLockedToken<CTexture> x540_xrayPalette;\n  float x54c_scanFrameColorInterp = 0.f;\n  float x550_scanFrameColorImpulseInterp = 0.f;\n\n  int FindEmptyInactiveScanTarget() const;\n  int FindCachedInactiveScanTarget(TUniqueId uid) const;\n  bool DrawScanObjectIndicators(const CStateManager& mgr);\n  void UpdateScanObjectIndicators(const CStateManager& mgr, float dt);\n  void UpdateScanWindow(float dt, const CStateManager& mgr);\n  EScanWindowState GetDesiredScanWindowState(const CStateManager& mgr) const;\n  void LockUnlockAssets();\n  void DrawScanEffect(const CStateManager& mgr, CTargetingManager* tgtMgr);\n  void DrawXRayEffect(const CStateManager& mgr);\n  void DrawThermalEffect(const CStateManager& mgr);\n  void UpdateCurrentVisor(float transFactor);\n  void FinishTransitionIn();\n  void BeginTransitionIn(const CStateManager& mgr);\n  void FinishTransitionOut(const CStateManager& mgr);\n  void BeginTransitionOut();\n\npublic:\n  explicit CPlayerVisor(CStateManager& stateMgr);\n  ~CPlayerVisor();\n  void Update(float dt, const CStateManager& stateMgr);\n  void Draw(const CStateManager& stateMgr, CTargetingManager* tgtManager);\n  void Touch();\n  float GetDesiredViewportScaleX(const CStateManager& stateMgr) const;\n  float GetDesiredViewportScaleY(const CStateManager& stateMgr) const;\n};\n\n} // namespace MP1\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/MP1/CPreFrontEnd.cpp",
    "content": "#include \"Runtime/MP1/CPreFrontEnd.hpp\"\n\n#include \"Runtime/CResLoader.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n\n#include \"Runtime/MP1/MP1.hpp\"\n\nnamespace metaforce::MP1 {\n\nCPreFrontEnd::CPreFrontEnd() : CIOWin(\"Pre front-end window\") {}\n\nCIOWin::EMessageReturn CPreFrontEnd::OnMessage(const CArchitectureMessage& msg, CArchitectureQueue&) {\n  if (msg.GetType() != EArchMsgType::TimerTick)\n    return EMessageReturn::Normal;\n\n  CMain* m = static_cast<CMain*>(g_Main);\n  if (CResLoader* loader = g_ResFactory->GetResLoader())\n    if (!loader->AreAllPaksLoaded())\n      return EMessageReturn::Exit;\n  if (!x14_resourceTweaksRegistered) {\n    m->RegisterResourceTweaks();\n    x14_resourceTweaksRegistered = true;\n  }\n  m->MemoryCardInitializePump();\n  if (!g_MemoryCardSys)\n    return EMessageReturn::Exit;\n  if (!m->LoadAudio())\n    return EMessageReturn::Exit;\n  return EMessageReturn::RemoveIOWinAndExit;\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/CPreFrontEnd.hpp",
    "content": "#pragma once\n\n#include \"Runtime/CIOWin.hpp\"\n\nnamespace metaforce::MP1 {\n\nclass CPreFrontEnd : public CIOWin {\n  bool x14_resourceTweaksRegistered = false;\n\npublic:\n  CPreFrontEnd();\n  EMessageReturn OnMessage(const CArchitectureMessage&, CArchitectureQueue&) override;\n};\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/CQuitGameScreen.cpp",
    "content": "#include \"Runtime/MP1/CQuitGameScreen.hpp\"\n\n#include <array>\n\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/Audio/CSfxManager.hpp\"\n#include \"Runtime/Camera/CCameraFilter.hpp\"\n#include \"Runtime/Input/CFinalInput.hpp\"\n#include \"Runtime/Graphics/CGraphics.hpp\"\n#include \"Runtime/GuiSys/CGuiFrame.hpp\"\n#include \"Runtime/GuiSys/CGuiTableGroup.hpp\"\n#include \"Runtime/GuiSys/CGuiTextPane.hpp\"\n#include \"Runtime/GuiSys/CGuiWidgetDrawParms.hpp\"\n#include \"Runtime/GuiSys/CStringTable.hpp\"\n\nnamespace metaforce::MP1 {\n\nconstexpr std::array Titles{24, 25, 26, 27, 28};\n\nconstexpr std::array DefaultSelections{1, 0, 1, 1, 0};\n\nconstexpr std::array VerticalOffsets{0.f, 1.6f, 1.f, 0.f, 1.f};\n\nvoid CQuitGameScreen::SetColors() {\n  x14_tablegroup_quitgame->SetColors(zeus::CColor{0.784313f, 0.784313f, 0.784313f, 1.f},\n                                     zeus::CColor{0.196078f, 0.196078f, 0.196078f, 1.f});\n}\n\nvoid CQuitGameScreen::FinishedLoading() {\n  x10_loadedFrame = x4_frame.GetObj();\n  x10_loadedFrame->SetMaxAspect(1.33f);\n\n  x14_tablegroup_quitgame = static_cast<CGuiTableGroup*>(x10_loadedFrame->FindWidget(\"tablegroup_quitgame\"));\n  x14_tablegroup_quitgame->SetVertical(false);\n  x14_tablegroup_quitgame->SetMenuAdvanceCallback([this](CGuiTableGroup* caller) { DoAdvance(caller); });\n  x14_tablegroup_quitgame->SetMenuSelectionChangeCallback(\n      [this](CGuiTableGroup* caller, int oldSel) { DoSelectionChange(caller, oldSel); });\n\n  static_cast<CGuiTextPane*>(x10_loadedFrame->FindWidget(\"textpane_title\"))\n      ->TextSupport()\n      .SetText(g_MainStringTable->GetString(Titles[size_t(x0_type)]));\n\n  static_cast<CGuiTextPane*>(x10_loadedFrame->FindWidget(\"textpane_yes\"))\n      ->TextSupport()\n      .SetText(g_MainStringTable->GetString(22));\n  static_cast<CGuiTextPane*>(x10_loadedFrame->FindWidget(\"textpane_no\"))\n      ->TextSupport()\n      .SetText(g_MainStringTable->GetString(23));\n\n  x14_tablegroup_quitgame->SetUserSelection(DefaultSelections[size_t(x0_type)]);\n  x14_tablegroup_quitgame->SetWorkersMouseActive(true);\n  x10_loadedFrame->SetMouseUpCallback([this](CGuiWidget* widget, bool cancel) { OnWidgetMouseUp(widget, cancel); });\n  SetColors();\n}\n\nvoid CQuitGameScreen::OnWidgetMouseUp(CGuiWidget* widget, bool cancel) {\n  if (!widget || cancel)\n    return;\n  DoAdvance(static_cast<CGuiTableGroup*>(widget->GetParent()));\n}\n\nvoid CQuitGameScreen::DoSelectionChange(CGuiTableGroup* caller, int oldSel) {\n  SetColors();\n  CSfxManager::SfxStart(SFXui_quit_change, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n}\n\nvoid CQuitGameScreen::DoAdvance(CGuiTableGroup* caller) {\n  if (caller->GetUserSelection() == 0) {\n    /* Yes */\n    CSfxManager::SfxStart(SFXui_advance, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n    x18_action = EQuitAction::Yes;\n  } else {\n    /* No */\n    CSfxManager::SfxStart(SFXui_table_change_mode, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n    x18_action = EQuitAction::No;\n  }\n}\n\nEQuitAction CQuitGameScreen::Update(float dt) {\n  if (!x10_loadedFrame && x4_frame.IsLoaded())\n    FinishedLoading();\n  return x18_action;\n}\n\nvoid CQuitGameScreen::Draw() {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CQuitGameScreen::Draw\", zeus::skPurple);\n  if (x0_type == EQuitType::QuitGame) {\n    CCameraFilterPass::DrawFilter(EFilterType::Blend, EFilterShape::Fullscreen, zeus::CColor{0.f, 0.5f}, nullptr, 1.f);\n  }\n\n  if (x10_loadedFrame) {\n    x10_loadedFrame->Draw(CGuiWidgetDrawParms{1.f, zeus::CVector3f{0.f, 0.f, VerticalOffsets[size_t(x0_type)]}});\n  }\n}\n\nvoid CQuitGameScreen::ProcessUserInput(const CFinalInput& input) {\n  if (input.ControllerIdx() != 0) {\n    return;\n  }\n\n  if (!x10_loadedFrame) {\n    return;\n  }\n\n  x10_loadedFrame->ProcessMouseInput(\n      input, CGuiWidgetDrawParms{1.f, zeus::CVector3f{0.f, 0.f, VerticalOffsets[size_t(x0_type)]}});\n  x10_loadedFrame->ProcessUserInput(input);\n  if ((input.PB() || input.PSpecialKey(ESpecialKey::Esc)) && x0_type != EQuitType::ContinueFromLastSave) {\n    x18_action = EQuitAction::No;\n  }\n}\n\nCQuitGameScreen::CQuitGameScreen(EQuitType tp) : x0_type(tp) { x4_frame = g_SimplePool->GetObj(\"FRME_QuitScreen\"); }\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/CQuitGameScreen.hpp",
    "content": "#pragma once\n\n#include <optional>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/GuiSys/CGuiFrame.hpp\"\n\nnamespace metaforce {\nstruct CFinalInput;\nclass CGuiTableGroup;\nclass CGuiTextPane;\nclass CGuiWidget;\n\nnamespace MP1 {\n\nenum class EQuitType { QuitGame, ContinueFromLastSave, SaveProgress, QuitNESMetroid, ContinuePlaying };\n\nenum class EQuitAction { None, Yes, No };\n\nclass CQuitGameScreen {\n  EQuitType x0_type;\n  TLockedToken<CGuiFrame> x4_frame;\n  CGuiFrame* x10_loadedFrame = nullptr;\n  CGuiTableGroup* x14_tablegroup_quitgame = nullptr;\n  EQuitAction x18_action = EQuitAction::None;\n\n  void SetColors();\n\npublic:\n  void FinishedLoading();\n  void OnWidgetMouseUp(CGuiWidget* widget, bool cancel);\n  void DoSelectionChange(CGuiTableGroup* caller, int oldSel);\n  void DoAdvance(CGuiTableGroup* caller);\n  EQuitAction Update(float dt);\n  void Draw();\n  void ProcessUserInput(const CFinalInput& input);\n  explicit CQuitGameScreen(EQuitType type);\n};\n\n} // namespace MP1\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/MP1/CSamusDoll.cpp",
    "content": "#include \"Runtime/MP1/CSamusDoll.hpp\"\n\n#include \"Runtime/CDependencyGroup.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/Collision/CollisionUtil.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/World/CMorphBall.hpp\"\n\n#include <array>\n#include <cfloat>\n#include <cmath>\n#include <memory>\n\n#include <zeus/CColor.hpp>\n#include <zeus/CEulerAngles.hpp>\n\nnamespace metaforce::MP1 {\nnamespace {\nconstexpr std::array<std::pair<const char*, u32>, 8> SpiderBallGlassModels{{\n    {\"SamusSpiderBallGlassCMDL\", 0},\n    {\"SamusSpiderBallGlassCMDL\", 0},\n    {\"SamusSpiderBallGlassCMDL\", 1},\n    {\"SamusPhazonBallGlassCMDL\", 0},\n    {\"SamusSpiderBallGlassCMDL\", 0},\n    {\"SamusSpiderBallGlassCMDL\", 0},\n    {\"SamusSpiderBallGlassCMDL\", 1},\n    {\"SamusPhazonBallGlassCMDL\", 0},\n}};\n\nconstexpr std::array<std::pair<const char*, u32>, 8> SpiderBallCharacters{{\n    {\"SamusSpiderBallANCS\", 0},\n    {\"SamusSpiderBallANCS\", 0},\n    {\"SamusSpiderBallANCS\", 1},\n    {\"SamusPhazonBallANCS\", 0},\n    {\"SamusFusionBallANCS\", 0},\n    {\"SamusFusionBallANCS\", 2},\n    {\"SamusFusionBallANCS\", 1},\n    {\"SamusFusionBallANCS\", 3},\n}};\n\nconstexpr std::array<std::pair<const char*, u32>, 8> BallCharacters{{\n    {\"SamusBallANCS\", 0},\n    {\"SamusBallANCS\", 0},\n    {\"SamusBallANCS\", 1},\n    {\"SamusBallANCS\", 0},\n    {\"SamusFusionBallANCS\", 0},\n    {\"SamusFusionBallANCS\", 2},\n    {\"SamusFusionBallANCS\", 1},\n    {\"SamusFusionBallANCS\", 3},\n}};\n\nconstexpr std::array<u32, 8> SpiderBallGlowColorIdxs{\n    3, 3, 2, 4, 5, 7, 6, 8,\n};\n\nconstexpr std::array<u32, 8> BallGlowColorIdxs{\n    0, 0, 1, 0, 5, 7, 6, 8,\n};\n\nconstexpr std::array BeamModels{\n    \"CMDL_InvPowerBeam\", \"CMDL_InvIceBeam\", \"CMDL_InvWaveBeam\", \"CMDL_InvPlasmaBeam\", \"CMDL_InvPowerBeam\",\n};\n\nconstexpr std::array VisorModels{\n    \"CMDL_InvVisor\",       \"CMDL_InvGravityVisor\", \"CMDL_InvVisor\",       \"CMDL_InvPhazonVisor\",\n    \"CMDL_InvFusionVisor\", \"CMDL_InvFusionVisor\",  \"CMDL_InvFusionVisor\", \"CMDL_InvFusionVisor\",\n};\n\nconstexpr std::array FinModels{\n    \"CMDL_InvPowerFins\", \"CMDL_InvPowerFins\", \"CMDL_InvPowerFins\",   \"CMDL_InvPowerFins\",\n    \"CMDL_InvPowerFins\", \"CMDL_InvVariaFins\", \"CMDL_InvGravityFins\", \"CMDL_InvPhazonFins\",\n};\n\nconstexpr std::array<u32, 8> Character1Idxs{\n    0, 6, 2, 10, 16, 24, 20, 28,\n};\n\nconstexpr std::array<u32, 8> CharacterBootsIdxs{\n    1, 7, 3, 11, 17, 25, 21, 29,\n};\n\nconstexpr std::array<std::array<u32, 2>, 8> Character2and3Idxs{{\n    {14, 15},\n    {8, 9},\n    {4, 5},\n    {12, 13},\n    {18, 19},\n    {26, 27},\n    {22, 23},\n    {30, 31},\n}};\n} // Anonymous namespace\n\nCSamusDoll::CSamusDoll(const CDependencyGroup& suitDgrp, const CDependencyGroup& ballDgrp,\n                       CPlayerState::EPlayerSuit suit, CPlayerState::EBeamId beam, bool hasSpiderBall,\n                       bool hasGrappleBeam)\n: x10_ballXf(zeus::CTransform::Translate(0.f, 0.f, 0.625f * g_tweakPlayer->GetPlayerBallHalfExtent()))\n, x44_suit(suit)\n, x48_beam(beam)\n, x270_24_hasSpiderBall(hasSpiderBall)\n, x270_25_hasGrappleBeam(hasGrappleBeam) {\n  x70_fixedRot.rotateZ(M_PIF);\n  x90_userInterpRot = xb0_userRot = x70_fixedRot;\n  x1d4_spiderBallGlass = g_SimplePool->GetObj(SpiderBallGlassModels[size_t(suit)].first);\n  x1e0_ballMatIdx = hasSpiderBall ? SpiderBallCharacters[size_t(suit)].second : BallCharacters[size_t(suit)].second;\n  x1e4_glassMatIdx = SpiderBallGlassModels[size_t(suit)].second;\n  x1e8_ballGlowColorIdx = hasSpiderBall ? SpiderBallGlowColorIdxs[size_t(suit)] : BallGlowColorIdxs[size_t(suit)];\n  x1ec_itemScreenSamus = g_SimplePool->GetObj(\"ANCS_ItemScreenSamus\");\n  x1f4_invBeam = g_SimplePool->GetObj(BeamModels[size_t(beam)]);\n  x200_invVisor = g_SimplePool->GetObj(VisorModels[size_t(suit)]);\n  x20c_invGrappleBeam = g_SimplePool->GetObj(\"CMDL_InvGrappleBeam\");\n  x218_invFins = g_SimplePool->GetObj(FinModels[size_t(suit)]);\n  x224_ballInnerGlow = g_SimplePool->GetObj(\"BallInnerGlow\");\n  x22c_ballInnerGlowGen = std::make_unique<CElementGen>(x224_ballInnerGlow);\n  x230_ballTransitionFlash = g_SimplePool->GetObj(\"MorphBallTransitionFlash\");\n  x23c_lights.push_back(CLight::BuildDirectional(zeus::skForward, zeus::skWhite));\n  x24c_actorLights = std::make_unique<CActorLights>(8, zeus::skZero3f, 4, 4, false, false, false, 0.1f);\n  x22c_ballInnerGlowGen->SetGlobalScale(zeus::CVector3f(0.625f));\n  x0_depToks.reserve(suitDgrp.GetObjectTagVector().size() + ballDgrp.GetObjectTagVector().size());\n  for (const SObjectTag& tag : suitDgrp.GetObjectTagVector()) {\n    x0_depToks.push_back(g_SimplePool->GetObj(tag));\n    x0_depToks.back().Lock();\n  }\n  for (const SObjectTag& tag : ballDgrp.GetObjectTagVector()) {\n    x0_depToks.push_back(g_SimplePool->GetObj(tag));\n    x0_depToks.back().Lock();\n  }\n}\n\nbool CSamusDoll::IsLoaded() const {\n  if (x270_31_loaded)\n    return true;\n  if (!x1ec_itemScreenSamus.IsLoaded())\n    return false;\n  if (!x1f4_invBeam.IsLoaded())\n    return false;\n  if (!x200_invVisor.IsLoaded())\n    return false;\n  if (!x20c_invGrappleBeam.IsLoaded())\n    return false;\n  if (!x1d4_spiderBallGlass.IsLoaded())\n    return false;\n  if (x218_invFins && !x218_invFins.IsLoaded())\n    return false;\n  return xc8_suitModel0.operator bool();\n}\n\nCModelData CSamusDoll::BuildSuitModelData1(CPlayerState::EPlayerSuit suit) {\n  CModelData ret(CAnimRes(g_ResFactory->GetResourceIdByName(\"ANCS_ItemScreenSamus\")->id, Character1Idxs[size_t(suit)],\n                          zeus::skOne3f, 2, true));\n  constexpr CAnimPlaybackParms parms(2, -1, 1.f, true);\n  ret.GetAnimationData()->SetAnimation(parms, false);\n  return ret;\n}\n\nCModelData CSamusDoll::BuildSuitModelDataBoots(CPlayerState::EPlayerSuit suit) {\n  CModelData ret(CAnimRes(g_ResFactory->GetResourceIdByName(\"ANCS_ItemScreenSamus\")->id,\n                          CharacterBootsIdxs[size_t(suit)], zeus::skOne3f, 2, true));\n  constexpr CAnimPlaybackParms parms(2, -1, 1.f, true);\n  ret.GetAnimationData()->SetAnimation(parms, false);\n  return ret;\n}\n\nbool CSamusDoll::CheckLoadComplete() {\n  if (IsLoaded())\n    return true;\n\n  for (const CToken& tok : x0_depToks)\n    if (!tok.IsLoaded())\n      return false;\n\n  xc8_suitModel0.emplace(BuildSuitModelData1(x44_suit));\n  for (int i = 0; i < 2; ++i) {\n    CAnimRes res(g_ResFactory->GetResourceIdByName(\"ANCS_ItemScreenSamus\")->id, Character2and3Idxs[size_t(x44_suit)][i],\n                 zeus::skOne3f, 2, true);\n    CModelData mData(res);\n    x118_suitModel1and2.push_back(mData.GetAnimationData()->GetModelData());\n    x118_suitModel1and2.back().Lock();\n  }\n  x134_suitModelBoots.emplace(BuildSuitModelDataBoots(x44_suit));\n\n  CAnimRes res(g_ResFactory\n                   ->GetResourceIdByName(x270_24_hasSpiderBall ? SpiderBallCharacters[size_t(x44_suit)].first\n                                                               : BallCharacters[size_t(x44_suit)].first)\n                   ->id,\n               0, zeus::skOne3f, 0, true);\n  x184_ballModelData.emplace(res);\n  x1e0_ballMatIdx =\n      x270_24_hasSpiderBall ? SpiderBallCharacters[size_t(x44_suit)].second : BallCharacters[size_t(x44_suit)].second;\n  x270_31_loaded = true;\n  return true;\n}\n\nvoid CSamusDoll::Update(float dt, CRandom16& rand) {\n  if (x1f4_invBeam.IsLoaded())\n    x1f4_invBeam->Touch(0);\n  if (x200_invVisor.IsLoaded())\n    x200_invVisor->Touch(0);\n  if (x20c_invGrappleBeam.IsLoaded())\n    x20c_invGrappleBeam->Touch(0);\n  if (x1d4_spiderBallGlass.IsLoaded())\n    x1d4_spiderBallGlass->Touch(0);\n  if (x218_invFins.IsLoaded())\n    x218_invFins->Touch(0);\n\n  if (!CheckLoadComplete())\n    return;\n\n  x40_alphaIn = std::min(x40_alphaIn + 2.f * dt, 1.f);\n  if (x54_remTransitionTime > 0.f) {\n    float oldRemTransTime = x54_remTransitionTime;\n    x54_remTransitionTime = std::max(0.f, x54_remTransitionTime - dt);\n    if (!x4c_completedMorphball && x4d_selectedMorphball && oldRemTransTime >= x50_totalTransitionTime - 0.5f &&\n        x54_remTransitionTime < x50_totalTransitionTime - 0.5f) {\n      x238_ballTransitionFlashGen = std::make_unique<CElementGen>(x230_ballTransitionFlash);\n      x238_ballTransitionFlashGen->SetGlobalScale(zeus::CVector3f(0.625f));\n    }\n\n    if (x54_remTransitionTime == 0.f) {\n      x4c_completedMorphball = x4d_selectedMorphball;\n      if (!x4d_selectedMorphball) {\n        xc8_suitModel0->GetAnimationData()->SetAnimation(CAnimPlaybackParms(2, -1, 1.f, true), false);\n        x134_suitModelBoots->GetAnimationData()->SetAnimation(CAnimPlaybackParms(2, -1, 1.f, true), false);\n      }\n    }\n  }\n\n  if (x270_26_pulseSuit)\n    x58_suitPulseFactor = std::min(x58_suitPulseFactor + 2.f * dt, 1.f);\n  else\n    x58_suitPulseFactor = std::max(x58_suitPulseFactor - 2.f * dt, 0.f);\n\n  if (x270_27_pulseBeam)\n    x5c_beamPulseFactor = std::min(x5c_beamPulseFactor + 2.f * dt, 1.f);\n  else\n    x5c_beamPulseFactor = std::max(x5c_beamPulseFactor - 2.f * dt, 0.f);\n\n  if (x270_28_pulseGrapple)\n    x60_grapplePulseFactor = std::min(x60_grapplePulseFactor + 2.f * dt, 1.f);\n  else\n    x60_grapplePulseFactor = std::max(x60_grapplePulseFactor - 2.f * dt, 0.f);\n\n  if (x270_29_pulseBoots)\n    x64_bootsPulseFactor = std::min(x64_bootsPulseFactor + 2.f * dt, 1.f);\n  else\n    x64_bootsPulseFactor = std::max(x64_bootsPulseFactor - 2.f * dt, 0.f);\n\n  if (x270_30_pulseVisor)\n    x68_visorPulseFactor = std::min(x68_visorPulseFactor + 2.f * dt, 1.f);\n  else\n    x68_visorPulseFactor = std::max(x68_visorPulseFactor - 2.f * dt, 0.f);\n\n  if (x4c_completedMorphball)\n    x6c_ballPulseFactor = std::min(x6c_ballPulseFactor + 2.f * dt, 1.f);\n  else\n    x6c_ballPulseFactor = std::max(x6c_ballPulseFactor - 2.f * dt, 0.f);\n\n  if (x44_suit == CPlayerState::EPlayerSuit::Phazon) {\n    if (!x250_phazonIndirectTexture)\n      x250_phazonIndirectTexture = g_SimplePool->GetObj(\"PhazonIndirectTexture\");\n  } else {\n    if (x250_phazonIndirectTexture)\n      x250_phazonIndirectTexture = TLockedToken<CTexture>();\n  }\n\n  if (x250_phazonIndirectTexture) {\n    x260_phazonOffsetAngle += 0.03f;\n    x260_phazonOffsetAngle.makeRel();\n    g_Renderer->AllocatePhazonSuitMaskTexture();\n  }\n\n  xc8_suitModel0->AdvanceAnimationIgnoreParticles(dt, rand, true);\n  x134_suitModelBoots->AdvanceAnimationIgnoreParticles(dt, rand, true);\n  x184_ballModelData->AdvanceAnimationIgnoreParticles(dt, rand, true);\n\n  SetupLights();\n\n  x22c_ballInnerGlowGen->SetGlobalTranslation(x10_ballXf.origin);\n  x22c_ballInnerGlowGen->Update(dt);\n\n  if (x238_ballTransitionFlashGen) {\n    if (x238_ballTransitionFlashGen->IsSystemDeletable())\n      x238_ballTransitionFlashGen.reset();\n    if (x238_ballTransitionFlashGen) {\n      x22c_ballInnerGlowGen->SetGlobalTranslation(x10_ballXf.origin);\n      x22c_ballInnerGlowGen->Update(dt);\n    }\n  }\n\n  if (xc4_viewInterp != 0.f && xc4_viewInterp != 1.f) {\n    if (xc4_viewInterp < 0.f)\n      xc4_viewInterp = std::min(xc4_viewInterp + 3.f * dt, 0.f);\n    else\n      xc4_viewInterp = std::min(xc4_viewInterp + 3.f * dt, 1.f);\n\n    float interp = std::fabs(xc4_viewInterp);\n    float oneMinusInterp = 1.f - interp;\n    xa4_offset = x84_interpStartOffset * interp + skInitialOffset * oneMinusInterp;\n\n    xb0_userRot = zeus::CQuaternion::slerpShort(x70_fixedRot, x90_userInterpRot, interp);\n\n    if (xc4_viewInterp <= 0.f) // Zoom out\n      xc0_userZoom = x80_fixedZoom * oneMinusInterp + xa0_userInterpZoom * interp;\n    else // Zoom in\n      xc0_userZoom = x80_fixedZoom * interp + xa0_userInterpZoom * oneMinusInterp;\n  }\n}\n\nvoid CSamusDoll::Draw(const CStateManager& mgr, float alpha) {\n  if (!IsLoaded())\n    return;\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CSamusDoll::Draw\", zeus::skPurple);\n\n  alpha *= x40_alphaIn;\n\n  float itemPulse = zeus::clamp(0.f, (std::sin(5.f * CGraphics::GetSecondsMod900()) + 1.f) * 0.5f, 1.f) *\n                    (1.f - std::fabs(xc4_viewInterp));\n\n  g_Renderer->SetPerspective(55.f, CGraphics::GetViewportWidth(), CGraphics::GetViewportHeight(), 0.2f, 4096.f);\n\n  CGraphics::SetViewPointMatrix(zeus::CTransform(xb0_userRot, xa4_offset) *\n                                zeus::CTransform::Translate(0.f, xc0_userZoom, 0.f));\n\n  zeus::CTransform gunXf = xc8_suitModel0->GetScaledLocatorTransform(\"GUN_LCTR\");\n  zeus::CTransform visorXf = xc8_suitModel0->GetScaledLocatorTransform(\"VISOR_LCTR\");\n  zeus::CTransform grappleXf = xc8_suitModel0->GetScaledLocatorTransform(\"GRAPPLE_LCTR\");\n\n  if (!x4c_completedMorphball || !x4d_selectedMorphball) {\n    float suitPulse = itemPulse * x58_suitPulseFactor;\n    float bootsPulse = std::max(suitPulse, itemPulse * x64_bootsPulseFactor);\n\n    bool phazonSuit = x44_suit == CPlayerState::EPlayerSuit::Phazon;\n    if (phazonSuit) {\n      GXSetDstAlpha(true, 255);\n    }\n\n    for (size_t i = 0; i <= x118_suitModel1and2.size(); ++i) {\n      TCachedToken<CSkinnedModel> backupModelData = xc8_suitModel0->GetAnimationData()->GetModelData();\n      if (i < x118_suitModel1and2.size())\n        xc8_suitModel0->GetAnimationData()->SubstituteModelData(x118_suitModel1and2[i]);\n      xc8_suitModel0->MultiLightingDraw(CModelData::EWhichModel::Normal, zeus::CTransform(), x24c_actorLights.get(),\n                                        zeus::CColor(1.f, alpha), zeus::CColor(1.f, alpha * suitPulse));\n      xc8_suitModel0->GetAnimationData()->SubstituteModelData(backupModelData);\n    }\n\n    x134_suitModelBoots->MultiLightingDraw(CModelData::EWhichModel::Normal, zeus::CTransform(), x24c_actorLights.get(),\n                                           zeus::CColor(1.f, alpha), zeus::CColor(1.f, alpha * bootsPulse));\n\n    {\n      CGraphics::LoadLight(0, x23c_lights[0]);\n      CGraphics::EnableLight(0);\n      CGraphics::SetAmbientColor(zeus::skClear);\n      CGraphics::SetModelMatrix(gunXf);\n      x1f4_invBeam->Draw({5, 0, 3, zeus::CColor{1.f, alpha}});\n      x1f4_invBeam->Draw({7, 0, 1, zeus::CColor(1.f, alpha * itemPulse * x5c_beamPulseFactor)});\n    }\n\n    {\n      CGraphics::SetModelMatrix(visorXf);\n\n      float visorT = std::fmod(CGraphics::GetSecondsMod900(), 1.f) * (1.f - std::fabs(xc4_viewInterp));\n      float alphaBlend = (visorT < 0.25f) ? 1.f - 2.f * visorT : (visorT < 0.5f) ? 2.f * (visorT - 0.25f) + 0.5f : 1.f;\n      float addBlend = (visorT > 0.75f) ? 1.f - 4.f * (visorT - 0.75f) : (visorT > 0.5f) ? 4.f * (visorT - 0.5f) : 0.f;\n\n      const auto c1 =\n          zeus::CColor::lerp(zeus::CColor{1.f, alpha}, zeus::CColor{alphaBlend, alpha}, x68_visorPulseFactor);\n      x200_invVisor->Draw({5, 0, 3, c1});\n      const zeus::CColor c2{1.f, alpha * addBlend * x68_visorPulseFactor};\n      x200_invVisor->Draw({7, 0, 1, c2});\n    }\n\n    if (x270_25_hasGrappleBeam) {\n      CGraphics::SetModelMatrix(grappleXf);\n      x20c_invGrappleBeam->Draw({5, 0, 3, zeus::CColor(1.f, alpha)});\n      x20c_invGrappleBeam->Draw({7, 0, 1, zeus::CColor(1.f, alpha * itemPulse * x60_grapplePulseFactor)});\n    } else if (x44_suit >= CPlayerState::EPlayerSuit::FusionPower) {\n      CGraphics::SetModelMatrix(grappleXf);\n      x218_invFins->Draw({5, 0, 3, zeus::CColor(1.f, alpha)});\n      x218_invFins->Draw({7, 0, 1, zeus::CColor(1.f, alpha * suitPulse)});\n    }\n\n    if (x54_remTransitionTime > 0.f) {\n      float ballT = 1.f - x54_remTransitionTime / x50_totalTransitionTime;\n\n      float ballAlpha = 0.f;\n      if (x4d_selectedMorphball)\n        ballAlpha = 1.f - std::min(x54_remTransitionTime / 0.25f, 1.f);\n      else if (x4c_completedMorphball)\n        ballAlpha = std::max(0.f, (x54_remTransitionTime - (x50_totalTransitionTime - 0.25f)) / 0.25f);\n\n      const auto ballMatIdx = static_cast<u8>(x1e0_ballMatIdx);\n      const float combinedBallAlpha = alpha * ballAlpha;\n      if (ballAlpha > 0.f) {\n        const std::array flags{\n            CModelFlags{5, ballMatIdx, 3, zeus::CColor{1.f, 0.f}},\n            CModelFlags{5, ballMatIdx, 3, zeus::CColor{1.f, combinedBallAlpha}},\n        };\n        x184_ballModelData->MultiPassDraw(CModelData::EWhichModel::Normal, x10_ballXf, x24c_actorLights.get(),\n                                          flags.data(), flags.size());\n        x184_ballModelData->Render(\n            mgr, x10_ballXf, x24c_actorLights.get(),\n            CModelFlags{7, ballMatIdx, 3, zeus::CColor{1.f, x6c_ballPulseFactor * combinedBallAlpha * itemPulse}});\n      }\n\n      if (x4d_selectedMorphball && ballT > 0.5f) {\n        float ballEndT = (ballT - 0.5f) / 0.5f;\n        float oneMinusBallEndT = 1.f - ballEndT;\n\n        float spinScale = 0.75f * oneMinusBallEndT + 1.f;\n        float spinAlpha;\n        if (ballEndT < 0.1f)\n          spinAlpha = 0.f;\n        else if (ballEndT < 0.2f)\n          spinAlpha = (ballEndT - 0.1f) / 0.1f;\n        else if (ballEndT < 0.9f)\n          spinAlpha = 1.f;\n        else\n          spinAlpha = 1.f - (ballT - 0.9f) / 0.1f;\n\n        zeus::CRelAngle spinAngle = zeus::degToRad(360.f * oneMinusBallEndT);\n        spinAlpha *= 0.5f;\n        if (spinAlpha > 0.f) {\n          const CModelFlags flags{7, ballMatIdx, 1, zeus::CColor{1.f, spinAlpha * alpha}};\n          x184_ballModelData->Render(\n              mgr, x10_ballXf * zeus::CTransform::RotateZ(spinAngle) * zeus::CTransform::Scale(spinScale),\n              x24c_actorLights.get(), flags);\n        }\n      }\n\n      if (x270_24_hasSpiderBall && ballAlpha > 0.f) {\n        CGraphics::SetModelMatrix(x10_ballXf);\n        const auto glassMatIdx = static_cast<u8>(x1e4_glassMatIdx);\n        x1d4_spiderBallGlass->Draw({5, glassMatIdx, 3, zeus::CColor{1.f, 0.f}});\n        x1d4_spiderBallGlass->Draw({5, glassMatIdx, 3, zeus::CColor{1.f, combinedBallAlpha}});\n        x1d4_spiderBallGlass->Draw(\n            {7, glassMatIdx, 3, zeus::CColor{1.f, x6c_ballPulseFactor * itemPulse * combinedBallAlpha}});\n      }\n    }\n\n    if (phazonSuit && alpha > 0.1f) {\n      float radius = zeus::clamp(0.2f, (10.f - (xc0_userZoom >= 0.f ? xc0_userZoom : -xc0_userZoom)) / 20.f, 1.f);\n      float offset = std::sin(x260_phazonOffsetAngle);\n      zeus::CColor color = g_tweakGuiColors->GetPauseBlurFilterColor();\n      color.a() = alpha;\n      g_Renderer->DrawPhazonSuitIndirectEffect(zeus::CColor(0.1f, alpha), x250_phazonIndirectTexture, color, radius,\n                                               0.1f, offset, offset);\n    }\n  } else {\n    const auto ballMatIdx = static_cast<u8>(x1e0_ballMatIdx);\n    const std::array flags{\n        CModelFlags{5, ballMatIdx, 3, zeus::CColor{1.f, 0.f}},\n        CModelFlags{5, ballMatIdx, 3, zeus::CColor{1.f, alpha}},\n    };\n    x184_ballModelData->MultiPassDraw(CModelData::EWhichModel::Normal, x10_ballXf, x24c_actorLights.get(), flags.data(),\n                                      flags.size());\n    x184_ballModelData->Render(\n        mgr, x10_ballXf, nullptr,\n        CModelFlags{7, ballMatIdx, 3, zeus::CColor{1.f, x6c_ballPulseFactor * alpha * itemPulse}});\n\n    const CMorphBall::ColorArray ballGlowColorData = CMorphBall::BallGlowColors[x1e8_ballGlowColorIdx];\n    const zeus::CColor ballGlowColor{\n        float(ballGlowColorData[0]) / 255.f,\n        float(ballGlowColorData[1]) / 255.f,\n        float(ballGlowColorData[2]) / 255.f,\n        alpha,\n    };\n    x22c_ballInnerGlowGen->SetModulationColor(ballGlowColor);\n\n    if (alpha > 0.f) {\n      if (x22c_ballInnerGlowGen->GetNumActiveChildParticles() > 0) {\n        const CMorphBall::ColorArray transFlashColorData = CMorphBall::BallTransFlashColors[x1e8_ballGlowColorIdx];\n        const zeus::CColor transFlashColor{\n            float(transFlashColorData[0]) / 255.f,\n            float(transFlashColorData[1]) / 255.f,\n            float(transFlashColorData[2]) / 255.f,\n            alpha,\n        };\n        x22c_ballInnerGlowGen->GetActiveChildParticle(0).SetModulationColor(transFlashColor);\n\n        if (x22c_ballInnerGlowGen->GetNumActiveChildParticles() > 1) {\n          const CMorphBall::ColorArray auxColorData = CMorphBall::BallAuxGlowColors[x1e8_ballGlowColorIdx];\n          const zeus::CColor auxColor{\n              float(auxColorData[0]) / 255.f,\n              float(auxColorData[1]) / 255.f,\n              float(auxColorData[2]) / 255.f,\n              alpha,\n          };\n          x22c_ballInnerGlowGen->GetActiveChildParticle(1).SetModulationColor(auxColor);\n        }\n      }\n      x22c_ballInnerGlowGen->Render();\n    }\n\n    if (x270_24_hasSpiderBall) {\n      const auto glassMatIdx = static_cast<u8>(x1e4_glassMatIdx);\n      CGraphics::SetModelMatrix(x10_ballXf);\n      x1d4_spiderBallGlass->Draw({5, 0, 3, zeus::CColor{1.f, 0.f}});\n      x1d4_spiderBallGlass->Draw({5, glassMatIdx, 3, zeus::CColor{1.f, alpha}});\n      x1d4_spiderBallGlass->Draw({7, glassMatIdx, 3, zeus::CColor{1.f, x6c_ballPulseFactor * alpha * itemPulse}});\n    }\n  }\n\n  if (x238_ballTransitionFlashGen) {\n    const CMorphBall::ColorArray c = CMorphBall::BallTransFlashColors[x1e8_ballGlowColorIdx];\n    const zeus::CColor color{\n        float(c[0]) / 255.f,\n        float(c[1]) / 255.f,\n        float(c[2]) / 255.f,\n        1.f,\n    };\n    x238_ballTransitionFlashGen->SetModulationColor(color);\n    x238_ballTransitionFlashGen->Render();\n  }\n\n  CGraphics::DisableAllLights();\n}\n\nvoid CSamusDoll::Touch() {\n  if (!CheckLoadComplete())\n    return;\n  xc8_suitModel0->GetAnimationData()->PreRender();\n  x134_suitModelBoots->GetAnimationData()->PreRender();\n  x184_ballModelData->GetAnimationData()->PreRender();\n  xc8_suitModel0->Touch(CModelData::EWhichModel::Normal, 0);\n  x134_suitModelBoots->Touch(CModelData::EWhichModel::Normal, 0);\n  x184_ballModelData->Touch(CModelData::EWhichModel::Normal, 0);\n}\n\nvoid CSamusDoll::SetupLights() {\n  x23c_lights[0] = CLight::BuildDirectional(xb0_userRot.toTransform().basis[1], zeus::CColor(0.75f, 1.f));\n  x24c_actorLights->BuildFakeLightList(x23c_lights, zeus::skBlack);\n}\n\nvoid CSamusDoll::SetInMorphball(bool morphball) {\n  if (x54_remTransitionTime > 0.f)\n    return;\n  if (x4d_selectedMorphball == morphball)\n    return;\n  x4d_selectedMorphball = morphball;\n  SetTransitionAnimation();\n}\n\nvoid CSamusDoll::SetTransitionAnimation() {\n  if (!x4c_completedMorphball) {\n    /* Into morphball */\n    xc8_suitModel0->GetAnimationData()->SetAnimation(CAnimPlaybackParms{0, -1, 1.f, true}, false);\n    x134_suitModelBoots->GetAnimationData()->SetAnimation(CAnimPlaybackParms{0, -1, 1.f, true}, false);\n    x50_totalTransitionTime = x54_remTransitionTime = xc8_suitModel0->GetAnimationData()->GetAnimationDuration(0);\n  } else if (!x4d_selectedMorphball) {\n    /* Outta morphball */\n    xc8_suitModel0->GetAnimationData()->SetAnimation(CAnimPlaybackParms{1, -1, 1.f, true}, false);\n    x134_suitModelBoots->GetAnimationData()->SetAnimation(CAnimPlaybackParms{1, -1, 1.f, true}, false);\n    x50_totalTransitionTime = x54_remTransitionTime = xc8_suitModel0->GetAnimationData()->GetAnimationDuration(1);\n  }\n}\n\nvoid CSamusDoll::SetRotationSfxPlaying(bool playing) {\n  if (playing) {\n    if (x268_rotateSfx)\n      return;\n    x268_rotateSfx = CSfxManager::SfxStart(SFXui_map_rotate, 1.f, 0.f, false, 0x7f, true, kInvalidAreaId);\n  } else {\n    CSfxManager::SfxStop(x268_rotateSfx);\n    x268_rotateSfx.reset();\n  }\n}\n\nvoid CSamusDoll::SetOffsetSfxPlaying(bool playing) {\n  if (playing) {\n    if (x264_offsetSfx)\n      return;\n    x264_offsetSfx = CSfxManager::SfxStart(SFXui_map_pan, 1.f, 0.f, false, 0x7f, true, kInvalidAreaId);\n  } else {\n    CSfxManager::SfxStop(x264_offsetSfx);\n    x264_offsetSfx.reset();\n  }\n}\n\nvoid CSamusDoll::SetZoomSfxPlaying(bool playing) {\n  if (playing) {\n    if (x26c_zoomSfx)\n      return;\n    x26c_zoomSfx = CSfxManager::SfxStart(SFXui_map_zoom, 1.f, 0.f, false, 0x7f, true, kInvalidAreaId);\n  } else {\n    CSfxManager::SfxStop(x26c_zoomSfx);\n    x26c_zoomSfx.reset();\n  }\n}\n\nvoid CSamusDoll::SetRotation(float xDelta, float zDelta, float dt) {\n  if (xc4_viewInterp != 0.f && xc4_viewInterp != 1.f)\n    return;\n  SetRotationSfxPlaying(xDelta != 0.f || zDelta != 0.f);\n  zeus::CEulerAngles angles(xb0_userRot);\n\n  zeus::CRelAngle angX(angles.x());\n  angX.makeRel();\n  zeus::CRelAngle angZ(angles.z());\n  angZ.makeRel();\n\n  angX += xDelta;\n  angX.makeRel();\n  angZ += zDelta;\n  angZ.makeRel();\n\n  float angXCenter = angX;\n  if (angXCenter > M_PIF)\n    angXCenter -= 2.f * M_PIF;\n  angXCenter = zeus::clamp(-1.555f, angXCenter, 1.555f);\n\n  zeus::CQuaternion quat;\n  quat.rotateZ(angZ);\n  quat.rotateX(zeus::CRelAngle(angXCenter).asRel());\n  xb0_userRot = quat;\n}\n\nvoid CSamusDoll::SetOffset(const zeus::CVector3f& offset, float dt) {\n  if (xc4_viewInterp != 0.f && xc4_viewInterp != 1.f)\n    return;\n  zeus::CVector3f oldOffset = xa4_offset;\n  zeus::CMatrix3f rotMtx = xb0_userRot.toTransform().basis;\n  xa4_offset += rotMtx * zeus::CVector3f(offset.x(), 0.f, offset.z());\n  SetOffsetSfxPlaying((oldOffset - xa4_offset).magnitude() > dt);\n  float oldZoom = xc0_userZoom;\n  xc0_userZoom = zeus::clamp(-4.f, xc0_userZoom + offset.y(), -2.2f);\n  bool zoomSfx = std::fabs(xc0_userZoom - oldZoom) > dt;\n  float zoomDelta = offset.y() - (xc0_userZoom - oldZoom);\n  zeus::CVector3f newOffset = rotMtx[1] * zoomDelta + xa4_offset;\n  zeus::CVector3f delta = newOffset - xa4_offset;\n  oldOffset = xa4_offset;\n  if (!delta.isZero()) {\n    zeus::CSphere sphere(skInitialOffset, 1.f);\n    float T;\n    zeus::CVector3f point;\n    if (CollisionUtil::RaySphereIntersection(sphere, xa4_offset, delta.normalized(), 0.f, T, point)) {\n      if ((point - xa4_offset).magnitude() < std::fabs(zoomDelta))\n        xa4_offset = point;\n      else\n        xa4_offset = newOffset;\n    } else {\n      xa4_offset = newOffset;\n    }\n  }\n  if ((oldOffset - xa4_offset).magnitude() > dt)\n    zoomSfx = true;\n  SetZoomSfxPlaying(zoomSfx);\n  delta = xa4_offset - skInitialOffset;\n  if (delta.magnitude() > 1.f)\n    xa4_offset = delta.normalized() + skInitialOffset;\n}\n\nvoid CSamusDoll::BeginViewInterpolate(bool zoomIn) {\n  if (xc4_viewInterp == 0.f) {\n    CSfxManager::SfxStart(SFXui_samus_doll_enter, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n  } else {\n    SetOffsetSfxPlaying(false);\n    SetZoomSfxPlaying(false);\n    SetRotationSfxPlaying(false);\n    CSfxManager::SfxStart(SFXui_samus_doll_exit, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n  }\n\n  xc4_viewInterp = zoomIn ? FLT_EPSILON : (-1.f + FLT_EPSILON);\n  x84_interpStartOffset = xa4_offset;\n  x90_userInterpRot = xb0_userRot;\n  xa0_userInterpZoom = xc0_userZoom;\n  x80_fixedZoom = zoomIn ? -2.2f : -3.6f;\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/CSamusDoll.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <optional>\n#include <vector>\n\n#include \"Runtime/CPlayerState.hpp\"\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Audio/CSfxManager.hpp\"\n#include \"Runtime/Character/CActorLights.hpp\"\n#include \"Runtime/Character/CAnimCharacterSet.hpp\"\n#include \"Runtime/Character/CModelData.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n\n#include <zeus/CRelAngle.hpp>\n#include <zeus/CQuaternion.hpp>\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CDependencyGroup;\nnamespace MP1 {\n\nclass CSamusDoll {\n  std::vector<CToken> x0_depToks;\n  zeus::CTransform x10_ballXf;\n  float x40_alphaIn = 0.f;\n  CPlayerState::EPlayerSuit x44_suit;\n  CPlayerState::EBeamId x48_beam;\n  bool x4c_completedMorphball = false;\n  bool x4d_selectedMorphball = false;\n  float x50_totalTransitionTime = 1.f;\n  float x54_remTransitionTime = 0.f;\n  float x58_suitPulseFactor = 0.f;\n  float x5c_beamPulseFactor = 0.f;\n  float x60_grapplePulseFactor = 0.f;\n  float x64_bootsPulseFactor = 0.f;\n  float x68_visorPulseFactor = 0.f;\n  float x6c_ballPulseFactor = 0.f;\n  zeus::CQuaternion x70_fixedRot;\n  float x80_fixedZoom = -3.6f;\n  zeus::CVector3f x84_interpStartOffset = skInitialOffset;\n  zeus::CQuaternion x90_userInterpRot;\n  float xa0_userInterpZoom = -3.6f;\n  zeus::CVector3f xa4_offset = skInitialOffset;\n  zeus::CQuaternion xb0_userRot;\n  float xc0_userZoom = -3.6f;\n  float xc4_viewInterp = 0.f;\n  std::optional<CModelData> xc8_suitModel0;\n  rstl::reserved_vector<TCachedToken<CSkinnedModel>, 2> x118_suitModel1and2;\n  std::optional<CModelData> x134_suitModelBoots;\n  std::optional<CModelData> x184_ballModelData;\n  TLockedToken<CModel> x1d4_spiderBallGlass;\n  u32 x1e0_ballMatIdx;\n  u32 x1e4_glassMatIdx;\n  u32 x1e8_ballGlowColorIdx;\n  TLockedToken<CAnimCharacterSet> x1ec_itemScreenSamus;\n  TLockedToken<CModel> x1f4_invBeam;\n  TLockedToken<CModel> x200_invVisor;\n  TLockedToken<CModel> x20c_invGrappleBeam;\n  TLockedToken<CModel> x218_invFins;\n  TLockedToken<CGenDescription> x224_ballInnerGlow;\n  std::unique_ptr<CElementGen> x22c_ballInnerGlowGen;\n  TLockedToken<CGenDescription> x230_ballTransitionFlash;\n  std::unique_ptr<CElementGen> x238_ballTransitionFlashGen;\n  std::vector<CLight> x23c_lights;\n  std::unique_ptr<CActorLights> x24c_actorLights;\n  TLockedToken<CTexture> x250_phazonIndirectTexture; // Used to be optional\n  zeus::CRelAngle x260_phazonOffsetAngle;\n  CSfxHandle x264_offsetSfx;\n  CSfxHandle x268_rotateSfx;\n  CSfxHandle x26c_zoomSfx;\n  bool x270_24_hasSpiderBall : 1;\n  bool x270_25_hasGrappleBeam : 1;\n  bool x270_26_pulseSuit : 1 = false;\n  bool x270_27_pulseBeam : 1 = false;\n  bool x270_28_pulseGrapple : 1 = false;\n  bool x270_29_pulseBoots : 1 = false;\n  bool x270_30_pulseVisor : 1 = false;\n  bool x270_31_loaded : 1 = false;\n\n  static constexpr zeus::CVector3f skInitialOffset{0.0f, 0.0f, 0.8f};\n  static CModelData BuildSuitModelData1(CPlayerState::EPlayerSuit suit);\n  static CModelData BuildSuitModelDataBoots(CPlayerState::EPlayerSuit suit);\n  void SetupLights();\n  void SetTransitionAnimation();\n  void SetRotationSfxPlaying(bool playing);\n  void SetOffsetSfxPlaying(bool playing);\n  void SetZoomSfxPlaying(bool playing);\n\npublic:\n  CSamusDoll(const CDependencyGroup& suitDgrp, const CDependencyGroup& ballDgrp, CPlayerState::EPlayerSuit suit,\n             CPlayerState::EBeamId beam, bool hasSpiderBall, bool hasGrappleBeam);\n  bool IsLoaded() const;\n  bool CheckLoadComplete();\n  void Update(float dt, CRandom16& rand);\n  void Draw(const CStateManager& mgr, float alpha);\n  void Touch();\n  void SetInMorphball(bool morphballComplete);\n  void SetRotation(float xDelta, float zDelta, float dt);\n  void SetOffset(const zeus::CVector3f& offset, float dt);\n  void BeginViewInterpolate(bool zoomOut);\n  void SetPulseSuit(bool b) { x270_26_pulseSuit = b; }\n  void SetPulseVisor(bool b) { x270_30_pulseVisor = b; }\n  void SetPulseBoots(bool b) { x270_29_pulseBoots = b; }\n  void SetPulseGrapple(bool b) { x270_28_pulseGrapple = b; }\n  void SetPulseBeam(bool b) { x270_27_pulseBeam = b; }\n  float GetViewInterpolation() const { return xc4_viewInterp; }\n  bool IsZoomedOut() const { return xc0_userZoom == -4.f; }\n};\n\n} // namespace MP1\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/MP1/CSamusFaceReflection.cpp",
    "content": "#include \"Runtime/MP1/CSamusFaceReflection.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Camera/CFirstPersonCamera.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce::MP1 {\n\nstatic const zeus::CTransform PreXf = zeus::CTransform::Scale(0.3f) * zeus::CTransform::Translate(0.f, 0.5f, 0.f);\n\nCSamusFaceReflection::CSamusFaceReflection(CStateManager& stateMgr)\n: x0_modelData(CAnimRes(g_ResFactory->GetResourceIdByName(\"ACS_SamusFace\")->id, 0, zeus::skOne3f, 0, true))\n, x4c_lights(std::make_unique<CActorLights>(8, zeus::skZero3f, 4, 4, false, false, false, 0.1f)) {\n  x60_lookDir = zeus::skForward;\n  constexpr CAnimPlaybackParms parms(0, -1, 1.f, true);\n  x0_modelData.GetAnimationData()->SetAnimation(parms, false);\n}\n\nvoid CSamusFaceReflection::PreDraw(const CStateManager& mgr) {\n  if (x6c_ != 2 && (x4c_lights->GetActiveLightCount() >= 1 || (x6c_ != 0 && x6c_ != 3))) {\n    if (!TCastToConstPtr<CFirstPersonCamera>(mgr.GetCameraManager()->GetCurrentCamera(mgr))) {\n      x70_hidden = true;\n    } else {\n      x70_hidden = false;\n      x0_modelData.GetAnimationData()->PreRender();\n    }\n  }\n}\n\nvoid CSamusFaceReflection::Draw(const CStateManager& mgr) {\n  if (x70_hidden)\n    return;\n\n  if (TCastToConstPtr<CFirstPersonCamera> fpCam = (mgr.GetCameraManager()->GetCurrentCamera(mgr))) {\n    SCOPED_GRAPHICS_DEBUG_GROUP(\"CSamusFaceReflection::Draw\", zeus::skBlue);\n    zeus::CQuaternion camRot(fpCam->GetTransform().basis);\n    float dist = ITweakGui::FaceReflectionDistanceDebugValueToActualValue(g_tweakGui->GetFaceReflectionDistance());\n    float height = ITweakGui::FaceReflectionHeightDebugValueToActualValue(g_tweakGui->GetFaceReflectionHeight());\n    float aspect = ITweakGui::FaceReflectionAspectDebugValueToActualValue(g_tweakGui->GetFaceReflectionAspect());\n    float orthoWidth =\n        ITweakGui::FaceReflectionOrthoWidthDebugValueToActualValue(g_tweakGui->GetFaceReflectionOrthoWidth());\n    float orthoHeight =\n        ITweakGui::FaceReflectionOrthoHeightDebugValueToActualValue(g_tweakGui->GetFaceReflectionOrthoHeight());\n\n    zeus::CTransform modelXf =\n        zeus::CTransform(camRot * x50_lookRot, fpCam->GetTransform().basis[1] * dist + fpCam->GetTransform().origin +\n                                                   fpCam->GetTransform().basis[2] * height) *\n        PreXf;\n\n    CGraphics::SetViewPointMatrix(fpCam->GetTransform());\n    CGraphics::SetOrtho(aspect * -orthoWidth, aspect * orthoWidth, orthoHeight, -orthoHeight, -10.f, 10.f);\n\n    CActorLights* lights = x6c_ == 1 ? nullptr : x4c_lights.get();\n    if (x6c_ == 3) {\n      x0_modelData.Render(mgr, modelXf, lights, CModelFlags(0, 0, 3, zeus::skWhite));\n    } else {\n      float transFactor;\n      if (mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::Combat)\n        transFactor = mgr.GetPlayerState()->GetVisorTransitionFactor();\n      else\n        transFactor = 0.f;\n      if (transFactor > 0.f) {\n        x0_modelData.Render(mgr, modelXf, nullptr, CModelFlags(7, 0, 3, zeus::skBlack));\n        x0_modelData.Render(mgr, modelXf, lights, CModelFlags(7, 0, 1, zeus::CColor(1.f, transFactor)));\n      }\n    }\n  }\n}\n\nvoid CSamusFaceReflection::Update(float dt, const CStateManager& mgr, CRandom16& rand) {\n  if (TCastToConstPtr<CFirstPersonCamera> fpCam = (mgr.GetCameraManager()->GetCurrentCamera(mgr))) {\n    x0_modelData.AdvanceAnimationIgnoreParticles(dt, rand, true);\n    x4c_lights->SetFindShadowLight(false);\n    TAreaId areaId = mgr.GetPlayer().GetAreaIdAlways();\n    if (areaId == kInvalidAreaId)\n      return;\n\n    zeus::CAABox aabb(fpCam->GetTranslation() - 0.125f, fpCam->GetTranslation() + 0.125f);\n    const CGameArea* area = mgr.GetWorld()->GetAreaAlways(areaId);\n    x4c_lights->BuildFaceLightList(mgr, *area, aabb);\n\n    zeus::CUnitVector3f lookDir(fpCam->GetTransform().basis[1]);\n    zeus::CUnitVector3f xfLook =\n        zeus::CQuaternion::lookAt(lookDir, zeus::skForward, 2.f * M_PIF).transform(x60_lookDir);\n    zeus::CQuaternion xfLook2 = zeus::CQuaternion::lookAt(zeus::skForward, xfLook, 2.f * M_PIF);\n    xfLook2 *= xfLook2;\n    zeus::CMatrix3f newXf(xfLook2);\n    zeus::CMatrix3f prevXf(x50_lookRot);\n    float lookDot = prevXf[1].dot(newXf[1]);\n    if (std::fabs(lookDot) > 1.f)\n      lookDot = lookDot > 0.f ? 1.f : -1.f;\n    float lookAng = std::acos(lookDot);\n    x50_lookRot = zeus::CQuaternion::slerp(\n        x50_lookRot, xfLook2,\n        zeus::clamp(0.f, 18.f * dt * ((lookAng > 0.f) ? 0.5f * dt * g_tweakPlayer->GetFreeLookSpeed() / lookAng : 0.f),\n                    1.f));\n    x60_lookDir = lookDir;\n  }\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/CSamusFaceReflection.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/Character/CActorLights.hpp\"\n#include \"Runtime/Character/CModelData.hpp\"\n\n#include <zeus/CQuaternion.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce::MP1 {\n\nclass CSamusFaceReflection {\n  CModelData x0_modelData;\n  std::unique_ptr<CActorLights> x4c_lights;\n  zeus::CQuaternion x50_lookRot;\n  zeus::CVector3f x60_lookDir;\n  u32 x6c_ = 0;\n  bool x70_hidden = true;\n\npublic:\n  explicit CSamusFaceReflection(CStateManager& stateMgr);\n  void PreDraw(const CStateManager& stateMgr);\n  void Draw(const CStateManager& stateMgr);\n  void Update(float dt, const CStateManager& stateMgr, CRandom16& rand);\n};\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/CSamusHud.cpp",
    "content": "#include \"Runtime/MP1/CSamusHud.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Camera/CFirstPersonCamera.hpp\"\n#include \"Runtime/GuiSys/CGuiCamera.hpp\"\n#include \"Runtime/GuiSys/CGuiFrame.hpp\"\n#include \"Runtime/GuiSys/CGuiLight.hpp\"\n#include \"Runtime/GuiSys/CGuiModel.hpp\"\n#include \"Runtime/GuiSys/CGuiTextPane.hpp\"\n#include \"Runtime/GuiSys/CGuiWidgetDrawParms.hpp\"\n#include \"Runtime/World/CGameLight.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CScriptTrigger.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce::MP1 {\n\nCSamusHud* CSamusHud::g_SamusHud = nullptr;\n\nCSamusHud::CSamusHud(CStateManager& stateMgr)\n: x8_targetingMgr(stateMgr)\n, x258_frmeHelmet(g_SimplePool->GetObj(\"FRME_Helmet\"))\n, x268_frmeBaseHud(g_SimplePool->GetObj(\"FRME_BaseHud\"))\n, x2e0_27_energyLow(stateMgr.GetPlayer().IsEnergyLow(stateMgr)) {\n  x33c_lights = std::make_unique<CActorLights>(8, zeus::skZero3f, 4, 1, true, 0, 0, 0.1f);\n  x340_hudLights.resize(3, SCachedHudLight(zeus::skZero3f, zeus::skWhite, 0.f, 0.f, 0.f, 0.f));\n  x46c_.resize(3);\n  x568_fpCamDir = stateMgr.GetCameraManager()->GetFirstPersonCamera()->GetTransform().basis[1];\n  x5a4_videoBands.resize(4);\n  x5d8_guiLights.resize(4);\n  x7ac_.resize(15);\n  UpdateStateTransition(1.f, stateMgr);\n  g_SamusHud = this;\n\n  for (size_t i = 0; i < x5ec_camFovTweaks.size(); ++i) {\n    x5ec_camFovTweaks[i] = 5.f * float(i) + 40.f;\n  }\n  for (size_t i = 0; i < x62c_camYTweaks.size(); ++i) {\n    x62c_camYTweaks[i] = -0.5f * float(i);\n  }\n  for (size_t i = 0; i < x72c_camZTweaks.size(); ++i) {\n    x72c_camZTweaks[i] = 0.5f * float(i) - 8.f;\n  }\n\n  x264_loadedFrmeHelmet = x258_frmeHelmet.GetObj();\n  x264_loadedFrmeHelmet->Reset();\n  x264_loadedFrmeHelmet->SetMaxAspect(1.78f);\n  x274_loadedFrmeBaseHud = x268_frmeBaseHud.GetObj();\n  x274_loadedFrmeBaseHud->Reset();\n  x274_loadedFrmeBaseHud->SetMaxAspect(1.78f);\n  x2a0_helmetIntf = std::make_unique<CHudHelmetInterface>(*x264_loadedFrmeHelmet);\n\n  rstl::reserved_vector<bool, 4> hasVisors = BuildPlayerHasVisors(stateMgr);\n  x2a4_visorMenu = std::make_unique<CHudVisorBeamMenu>(*x274_loadedFrmeBaseHud,\n                                                       CHudVisorBeamMenu::EHudVisorBeamMenu::Visor, hasVisors);\n\n  rstl::reserved_vector<bool, 4> hasBeams = BuildPlayerHasBeams(stateMgr);\n  x2a8_beamMenu = std::make_unique<CHudVisorBeamMenu>(*x274_loadedFrmeBaseHud,\n                                                      CHudVisorBeamMenu::EHudVisorBeamMenu::Beam, hasBeams);\n\n  x2ac_radarIntf = std::make_unique<CHudRadarInterface>(*x274_loadedFrmeBaseHud, stateMgr);\n\n  InitializeFrameGluePermanent(stateMgr);\n  UpdateEnergy(0.f, stateMgr, true);\n  UpdateMissile(0.f, stateMgr, true);\n  UpdateBallMode(stateMgr, true);\n}\n\nCSamusHud::~CSamusHud() {\n  if (x3a4_damageSfx)\n    CSfxManager::RemoveEmitter(x3a4_damageSfx);\n  g_SamusHud = nullptr;\n}\n\nrstl::reserved_vector<bool, 4> CSamusHud::BuildPlayerHasVisors(const CStateManager& mgr) {\n  rstl::reserved_vector<bool, 4> ret;\n  ret.push_back(mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::CombatVisor));\n  ret.push_back(mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::XRayVisor));\n  ret.push_back(mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::ScanVisor));\n  ret.push_back(mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::ThermalVisor));\n  return ret;\n}\n\nrstl::reserved_vector<bool, 4> CSamusHud::BuildPlayerHasBeams(const CStateManager& mgr) {\n  rstl::reserved_vector<bool, 4> ret;\n  ret.push_back(mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::PowerBeam));\n  ret.push_back(mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::IceBeam));\n  ret.push_back(mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::WaveBeam));\n  ret.push_back(mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::PlasmaBeam));\n  return ret;\n}\n\nvoid CSamusHud::InitializeFrameGluePermanent(const CStateManager& mgr) {\n  x588_base_basewidget_pivot = x274_loadedFrmeBaseHud->FindWidget(\"basewidget_pivot\");\n  x58c_helmet_BaseWidget_Pivot = x264_loadedFrmeHelmet->FindWidget(\"BaseWidget_Pivot\");\n  x590_base_Model_AutoMapper = static_cast<CGuiModel*>(x274_loadedFrmeBaseHud->FindWidget(\"Model_AutoMapper\"));\n  x594_base_textpane_counter = static_cast<CGuiTextPane*>(x274_loadedFrmeBaseHud->FindWidget(\"textpane_counter\"));\n  x594_base_textpane_counter->TextSupport().SetFontColor(g_tweakGuiColors->GetHudCounterFill());\n  x594_base_textpane_counter->TextSupport().SetOutlineColor(g_tweakGuiColors->GetHudCounterOutline());\n  x598_base_basewidget_message = x274_loadedFrmeBaseHud->FindWidget(\"basewidget_message\");\n  for (CGuiWidget* child = static_cast<CGuiWidget*>(x598_base_basewidget_message->GetChildObject()); child;\n       child = static_cast<CGuiWidget*>(child->GetNextSibling()))\n    child->SetDepthTest(false);\n  x59c_base_textpane_message = static_cast<CGuiTextPane*>(x274_loadedFrmeBaseHud->FindWidget(\"textpane_message\"));\n  x5a0_base_model_abutton = static_cast<CGuiModel*>(x274_loadedFrmeBaseHud->FindWidget(\"model_abutton\"));\n  for (size_t i = 0; i < x5d8_guiLights.size(); ++i) {\n    x5d8_guiLights[i] = x264_loadedFrmeHelmet->GetFrameLight(s32(i));\n  }\n  x5d8_guiLights[3]->SetColor(zeus::skBlack);\n  for (size_t i = 0; i < x5a4_videoBands.size(); ++i) {\n    SVideoBand& band = x5a4_videoBands[i];\n    band.x0_videoband =\n        static_cast<CGuiModel*>(x274_loadedFrmeBaseHud->FindWidget(fmt::format(\"model_videoband{}\", i)));\n    band.x4_randA = 6 + (std::rand() % ((66 - 6) + 1));\n    band.x8_randB = 16 + (std::rand() % ((256 - 16) + 1));\n  }\n  x59c_base_textpane_message->SetDepthTest(false);\n  x598_base_basewidget_message->SetVisibility(false, ETraversalMode::Children);\n  x59c_base_textpane_message->TextSupport().SetFontColor(g_tweakGuiColors->GetHudMessageFill());\n  x59c_base_textpane_message->TextSupport().SetOutlineColor(g_tweakGuiColors->GetHudMessageOutline());\n  x59c_base_textpane_message->TextSupport().SetControlTXTRMap(&g_GameState->GameOptions().GetControlTXTRMap());\n  x590_base_Model_AutoMapper->SetDepthWrite(true);\n  x304_basewidgetIdlePos = x588_base_basewidget_pivot->GetIdlePosition();\n  x310_cameraPos = x274_loadedFrmeBaseHud->GetFrameCamera()->GetLocalPosition();\n  RefreshHudOptions();\n}\n\nvoid CSamusHud::InitializeFrameGlueMutable(const CStateManager& mgr) {\n  float lastTankEnergy = std::fmod(x2d0_playerHealth, CPlayerState::GetEnergyTankCapacity());\n  u32 tanksFilled = x2d0_playerHealth / CPlayerState::GetEnergyTankCapacity();\n\n  CPlayer& player = mgr.GetPlayer();\n  CPlayerState& playerState = *mgr.GetPlayerState();\n  CPlayerGun& gun = *player.GetPlayerGun();\n  float chargeFactor = gun.IsCharging() ? gun.GetChargeBeamFactor() : 0.f;\n  bool missilesActive = gun.GetMissleMode() == CPlayerGun::EMissileMode::Active;\n  bool lockedOnObj = player.GetOrbitTargetId() != kInvalidUniqueId;\n\n  switch (x2bc_nextState) {\n  case EHudState::Combat: {\n    x2b4_bossEnergyIntf = std::make_unique<CHudBossEnergyInterface>(*x288_loadedSelectedHud);\n\n    x28c_energyIntf =\n        std::make_unique<CHudEnergyInterface>(*x288_loadedSelectedHud, lastTankEnergy, x2d4_totalEnergyTanks,\n                                              tanksFilled, bool(x2e0_27_energyLow), EHudType::Combat);\n\n    if (!x290_threatIntf)\n      x290_threatIntf = std::make_unique<CHudThreatInterface>(*x288_loadedSelectedHud, EHudType::Combat, 9999.f);\n    else\n      x290_threatIntf->SetIsVisibleGame(true);\n\n    if (!x294_missileIntf)\n      x294_missileIntf =\n          std::make_unique<CHudMissileInterface>(*x288_loadedSelectedHud, x2dc_missileCapacity, x2d8_missileAmount,\n                                                 chargeFactor, missilesActive, EHudType::Combat, mgr);\n    else\n      x294_missileIntf->SetIsVisibleGame(true, mgr);\n\n    if (!x298_freeLookIntf)\n      x298_freeLookIntf =\n          std::make_unique<CHudFreeLookInterface>(*x288_loadedSelectedHud, EHudType::Combat, bool(x2e0_24_inFreeLook),\n                                                  bool(x2e0_25_lookControlHeld), lockedOnObj);\n    else\n      x298_freeLookIntf->SetIsVisibleGame(true);\n\n    if (!x29c_decoIntf)\n      x29c_decoIntf = std::make_unique<CHudDecoInterfaceCombat>(*x288_loadedSelectedHud);\n    else\n      x29c_decoIntf->SetIsVisibleGame(true);\n\n    x2ac_radarIntf->SetIsVisibleGame(true);\n    x2a4_visorMenu->SetIsVisibleGame(true);\n    x2a8_beamMenu->SetIsVisibleGame(true);\n    InitializeDamageLight();\n    UpdateEnergy(0.f, mgr, true);\n    break;\n  }\n  case EHudState::Ball: {\n    u32 numPBs = playerState.GetItemAmount(CPlayerState::EItemType::PowerBombs);\n    u32 pbCap = playerState.GetItemCapacity(CPlayerState::EItemType::PowerBombs);\n    u32 bombsAvailable;\n    if (gun.IsBombReady())\n      bombsAvailable = gun.GetBombCount();\n    else\n      bombsAvailable = 0;\n    x2b0_ballIntf = std::make_unique<CHudBallInterface>(\n        *x288_loadedSelectedHud, numPBs, pbCap, bombsAvailable,\n        gun.IsPowerBombReady() && player.GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed,\n        playerState.HasPowerUp(CPlayerState::EItemType::MorphBallBombs));\n\n    x28c_energyIntf =\n        std::make_unique<CHudEnergyInterface>(*x288_loadedSelectedHud, lastTankEnergy, x2d4_totalEnergyTanks,\n                                              tanksFilled, bool(x2e0_27_energyLow), EHudType::Ball);\n\n    x290_threatIntf.reset();\n    x294_missileIntf.reset();\n    x298_freeLookIntf.reset();\n    x29c_decoIntf.reset();\n    x3d4_damageLight = nullptr;\n\n    x2ac_radarIntf->SetIsVisibleGame(false);\n    x2a4_visorMenu->SetIsVisibleGame(false);\n    x2a8_beamMenu->SetIsVisibleGame(false);\n    UpdateEnergy(0.f, mgr, true);\n    break;\n  }\n  case EHudState::Scan: {\n    x2b4_bossEnergyIntf.reset();\n\n    x28c_energyIntf =\n        std::make_unique<CHudEnergyInterface>(*x288_loadedSelectedHud, lastTankEnergy, x2d4_totalEnergyTanks,\n                                              tanksFilled, bool(x2e0_27_energyLow), EHudType::Scan);\n\n    x290_threatIntf.reset();\n    x294_missileIntf.reset();\n\n    x298_freeLookIntf = std::make_unique<CHudFreeLookInterface>(\n        *x288_loadedSelectedHud, EHudType::Scan, bool(x2e0_24_inFreeLook), bool(x2e0_25_lookControlHeld), lockedOnObj);\n\n    x29c_decoIntf = std::make_unique<CHudDecoInterfaceScan>(*x288_loadedSelectedHud);\n    InitializeDamageLight();\n    UpdateEnergy(0.f, mgr, true);\n    break;\n  }\n  case EHudState::XRay: {\n    x2b4_bossEnergyIntf = std::make_unique<CHudBossEnergyInterface>(*x288_loadedSelectedHud);\n\n    x28c_energyIntf =\n        std::make_unique<CHudEnergyInterface>(*x288_loadedSelectedHud, lastTankEnergy, x2d4_totalEnergyTanks,\n                                              tanksFilled, bool(x2e0_27_energyLow), EHudType::XRay);\n\n    x290_threatIntf = std::make_unique<CHudThreatInterface>(*x288_loadedSelectedHud, EHudType::XRay, 9999.f);\n\n    x294_missileIntf =\n        std::make_unique<CHudMissileInterface>(*x288_loadedSelectedHud, x2dc_missileCapacity, x2d8_missileAmount,\n                                               chargeFactor, missilesActive, EHudType::XRay, mgr);\n\n    x298_freeLookIntf = std::make_unique<CHudFreeLookInterfaceXRay>(*x288_loadedSelectedHud, bool(x2e0_24_inFreeLook),\n                                                                    bool(x2e0_25_lookControlHeld), lockedOnObj);\n\n    x29c_decoIntf = std::make_unique<CHudDecoInterfaceXRay>(*x288_loadedSelectedHud);\n    InitializeDamageLight();\n    x2a4_visorMenu->SetIsVisibleGame(true);\n    x2a8_beamMenu->SetIsVisibleGame(true);\n    UpdateEnergy(0.f, mgr, true);\n    break;\n  }\n  case EHudState::Thermal: {\n    x2b4_bossEnergyIntf = std::make_unique<CHudBossEnergyInterface>(*x288_loadedSelectedHud);\n\n    x28c_energyIntf =\n        std::make_unique<CHudEnergyInterface>(*x288_loadedSelectedHud, lastTankEnergy, x2d4_totalEnergyTanks,\n                                              tanksFilled, bool(x2e0_27_energyLow), EHudType::Thermal);\n\n    x290_threatIntf = std::make_unique<CHudThreatInterface>(*x288_loadedSelectedHud, EHudType::Thermal, 9999.f);\n\n    x294_missileIntf =\n        std::make_unique<CHudMissileInterface>(*x288_loadedSelectedHud, x2dc_missileCapacity, x2d8_missileAmount,\n                                               chargeFactor, missilesActive, EHudType::Thermal, mgr);\n\n    x298_freeLookIntf =\n        std::make_unique<CHudFreeLookInterface>(*x288_loadedSelectedHud, EHudType::Thermal, bool(x2e0_24_inFreeLook),\n                                                bool(x2e0_25_lookControlHeld), lockedOnObj);\n\n    x29c_decoIntf = std::make_unique<CHudDecoInterfaceThermal>(*x288_loadedSelectedHud);\n    InitializeDamageLight();\n    x2a4_visorMenu->SetIsVisibleGame(true);\n    x2a8_beamMenu->SetIsVisibleGame(true);\n    UpdateEnergy(0.f, mgr, true);\n    break;\n  }\n  case EHudState::None:\n    UninitializeFrameGlueMutable();\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CSamusHud::UninitializeFrameGlueMutable() {\n  x2b4_bossEnergyIntf.reset();\n  x28c_energyIntf.reset();\n  x29c_decoIntf.reset();\n  x290_threatIntf.reset();\n  x294_missileIntf.reset();\n  x298_freeLookIntf.reset();\n  x2b0_ballIntf.reset();\n  x3d4_damageLight = nullptr;\n}\n\nvoid CSamusHud::InitializeDamageLight() {\n  s16 lightId = x288_loadedSelectedHud->GetWidgetIdDB().AddWidget(\"DamageSpotLight\");\n  s16 parentId = x288_loadedSelectedHud->FindWidget(\"basewidget_pivot\")->GetSelfId();\n  CGuiWidget::CGuiWidgetParms parms(x288_loadedSelectedHud, false, lightId, parentId, true, true, false,\n                                    g_tweakGuiColors->GetHudDamageLightColor(), CGuiWidget::EGuiModelDrawFlags::Alpha,\n                                    false, false, \"DamageSpotLight\"s);\n\n  std::shared_ptr<CGuiLight> light =\n      std::make_shared<CGuiLight>(parms, CLight::BuildSpot(zeus::skZero3f, zeus::skForward, zeus::skWhite,\n                                                           g_tweakGui->GetHudDamageLightSpotAngle()));\n  x3d4_damageLight = light.get();\n  x3d4_damageLight->SetColor(zeus::skBlack);\n\n  zeus::CColor lightColor = g_tweakGuiColors->GetHudFrameColor();\n  lightColor *= lightColor.a();\n  lightColor.a() = 1.f;\n  x3d4_damageLight->SetAmbientLightColor(lightColor);\n\n  x3d4_damageLight->SetDistC(1.f);\n  x3d4_damageLight->SetDistL(0.f);\n  x3d4_damageLight->SetAngleC(g_tweakGui->GetDamageLightAngleC());\n  x3d4_damageLight->SetAngleL(g_tweakGui->GetDamageLightAngleL());\n  x3d4_damageLight->SetAngleQ(g_tweakGui->GetDamageLightAngleQ());\n  x3d4_damageLight->SetLightId(4);\n\n  x3d4_damageLight->SetLocalTransform(zeus::CTransform());\n\n  x288_loadedSelectedHud->RegisterLight(std::move(light));\n  x288_loadedSelectedHud->FindWidget(parentId)->AddChildWidget(x3d4_damageLight, false, true);\n  x288_loadedSelectedHud->AddLight(x3d4_damageLight);\n\n  zeus::CTransform lightXf = zeus::CTransform::Translate(g_tweakGui->GetDamageLightPreTranslate());\n\n  x3d8_lightTransforms.clear();\n  x3d8_lightTransforms.reserve(10);\n\n  zeus::CTransform negX = zeus::CTransform::RotateX(zeus::degToRad(-g_tweakGui->GetDamageLightXfXAngle()));\n  zeus::CTransform posX = zeus::CTransform::RotateX(zeus::degToRad(g_tweakGui->GetDamageLightXfXAngle()));\n  zeus::CTransform negZ = zeus::CTransform::RotateZ(zeus::degToRad(-g_tweakGui->GetDamageLightXfZAngle()));\n  zeus::CTransform posZ = zeus::CTransform::RotateZ(zeus::degToRad(g_tweakGui->GetDamageLightXfZAngle()));\n\n  x3d8_lightTransforms.push_back(lightXf);\n  x3d8_lightTransforms.push_back(zeus::CTransform::Translate(g_tweakGui->GetDamageLightCenterTranslate()) * lightXf);\n  x3d8_lightTransforms.push_back(posX * lightXf);\n  x3d8_lightTransforms.push_back(posX * negZ * lightXf);\n  x3d8_lightTransforms.push_back(negZ * lightXf);\n  x3d8_lightTransforms.push_back(negX * negZ * lightXf);\n  x3d8_lightTransforms.push_back(negX * lightXf);\n  x3d8_lightTransforms.push_back(negX * posZ * lightXf);\n  x3d8_lightTransforms.push_back(posZ * lightXf);\n  x3d8_lightTransforms.push_back(posX * posZ * lightXf);\n}\n\nvoid CSamusHud::UpdateEnergy(float dt, const CStateManager& mgr, bool init) {\n  CPlayer& player = mgr.GetPlayer();\n  CPlayerState& playerState = *mgr.GetPlayerState();\n  float energy = std::max(0.f, std::ceil(playerState.GetHealthInfo().GetHP()));\n\n  u32 numEnergyTanks = playerState.GetItemCapacity(CPlayerState::EItemType::EnergyTanks);\n  x2e0_27_energyLow = player.IsEnergyLow(mgr);\n\n  if (init || energy != x2d0_playerHealth || numEnergyTanks != x2d4_totalEnergyTanks) {\n    float lastTankEnergy = energy;\n    u32 filledTanks = 0;\n    while (lastTankEnergy > CPlayerState::GetBaseHealthCapacity()) {\n      ++filledTanks;\n      lastTankEnergy -= CPlayerState::GetEnergyTankCapacity();\n    }\n\n    if (x2bc_nextState != EHudState::None) {\n      if (x28c_energyIntf) {\n        float curLastTankEnergy = x2d0_playerHealth;\n        while (curLastTankEnergy > CPlayerState::GetBaseHealthCapacity())\n          curLastTankEnergy -= CPlayerState::GetEnergyTankCapacity();\n        x28c_energyIntf->SetCurrEnergy(lastTankEnergy,\n                                       (curLastTankEnergy > lastTankEnergy) != (x2d0_playerHealth > energy));\n      }\n      x2d0_playerHealth = energy;\n      if (x28c_energyIntf) {\n        x28c_energyIntf->SetNumTotalEnergyTanks(numEnergyTanks);\n        x28c_energyIntf->SetNumFilledEnergyTanks(filledTanks);\n        x28c_energyIntf->SetEnergyLow(x2e0_27_energyLow);\n      }\n      x2d4_totalEnergyTanks = numEnergyTanks;\n    }\n  }\n\n  if (x2b4_bossEnergyIntf) {\n    const CEntity* bossEnt = mgr.GetObjectById(mgr.GetBossId());\n    if (TCastToConstPtr<CActor> act = bossEnt) {\n      if (const CHealthInfo* hInfo = act->GetHealthInfo(mgr)) {\n        float bossEnergy = std::ceil(hInfo->GetHP());\n        x2b4_bossEnergyIntf->SetBossParams(true, g_MainStringTable->GetString(mgr.GetBossStringIdx()), bossEnergy,\n                                           mgr.GetTotalBossEnergy());\n      } else {\n        x2b4_bossEnergyIntf->SetBossParams(false, u\"\", 0.f, 0.f);\n      }\n    } else {\n      x2b4_bossEnergyIntf->SetBossParams(false, u\"\", 0.f, 0.f);\n    }\n  }\n}\n\nvoid CSamusHud::UpdateFreeLook(float dt, const CStateManager& mgr) {\n  TCastToConstPtr<CFirstPersonCamera> fpCam = mgr.GetCameraManager()->GetCurrentCamera(mgr);\n  CPlayer& player = mgr.GetPlayer();\n  bool inFreeLook = player.IsInFreeLook() && fpCam;\n  bool lookControlHeld = player.GetFreeLookStickState();\n  if (x2e0_24_inFreeLook != inFreeLook) {\n    if (inFreeLook)\n      CSfxManager::SfxStart(SFXui_into_freelook, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n    else\n      CSfxManager::SfxStart(SFXui_outof_freelook, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n    x2e0_24_inFreeLook = inFreeLook;\n  }\n\n  float deltaFrames = 60.f * dt * 0.999999f;\n  float oldLookDeltaDot = x574_lookDeltaDot;\n  zeus::CVector3f fpCamDir;\n  if (!fpCam)\n    fpCamDir = x568_fpCamDir;\n  else\n    fpCamDir = fpCam->GetTransform().basis[1];\n\n  bool lookAdj = inFreeLook ? lookControlHeld : false;\n\n  if (lookAdj) {\n    x574_lookDeltaDot = fpCamDir.dot(x568_fpCamDir);\n    if (std::fabs(x574_lookDeltaDot) > 1.f)\n      x574_lookDeltaDot = (x574_lookDeltaDot >= 0.f) ? 1.f : -1.f;\n  } else {\n    x574_lookDeltaDot = 1.f;\n  }\n\n  x568_fpCamDir = fpCamDir;\n\n  if ((oldLookDeltaDot >= deltaFrames && x574_lookDeltaDot < deltaFrames) ||\n      (oldLookDeltaDot < deltaFrames && x574_lookDeltaDot >= deltaFrames)) {\n    x578_freeLookSfxCycleTimer = 0.f;\n  } else if (x578_freeLookSfxCycleTimer < 0.05f) {\n    x578_freeLookSfxCycleTimer = std::min(x578_freeLookSfxCycleTimer + dt, 0.05f);\n    if (x578_freeLookSfxCycleTimer == 0.05f) {\n      if (x574_lookDeltaDot < deltaFrames) {\n        if (!x564_freeLookSfx)\n          x564_freeLookSfx = CSfxManager::SfxStart(SFXui_freelook_move_lp, 1.f, 0.f, true, 0x7f, true, kInvalidAreaId);\n      } else {\n        CSfxManager::SfxStop(x564_freeLookSfx);\n        x564_freeLookSfx.reset();\n      }\n    }\n  }\n\n  if (fpCam) {\n    zeus::CMatrix3f camRot = fpCam->GetTransform().buildMatrix3f();\n    zeus::CVector3f camDir(camRot[1]);\n    zeus::CUnitVector3f camDirNoZ = camDir;\n    camDirNoZ.z() = 0.f;\n    float offHorizonDot = camDir.dot(camDirNoZ);\n    if (std::fabs(offHorizonDot) > 1.f)\n      offHorizonDot = (offHorizonDot >= 0.f) ? 1.f : -1.f;\n    float offHorizonAngle = std::fabs(std::acos(offHorizonDot));\n    if (camDir.z() < 0.f)\n      offHorizonAngle = -offHorizonAngle;\n\n    if (x298_freeLookIntf)\n      x298_freeLookIntf->SetFreeLookState(inFreeLook, lookControlHeld, player.GetOrbitTargetId() != kInvalidUniqueId,\n                                          offHorizonAngle);\n\n    if (x564_freeLookSfx) {\n      float pitch = offHorizonAngle * (g_tweakGui->GetFreeLookSfxPitchScale() / 8192.f) / (M_PIF / 2.f);\n      if (!g_tweakGui->GetNoAbsoluteFreeLookSfxPitch())\n        pitch = std::fabs(pitch);\n      CSfxManager::PitchBend(x564_freeLookSfx, pitch);\n    }\n  }\n}\n\nvoid CSamusHud::UpdateMissile(float dt, const CStateManager& mgr, bool init) {\n  CPlayerGun& gun = *mgr.GetPlayer().GetPlayerGun();\n  CPlayerState& playerState = *mgr.GetPlayerState();\n\n  u32 numMissles = playerState.GetItemAmount(CPlayerState::EItemType::Missiles);\n  u32 missileCap = playerState.GetItemCapacity(CPlayerState::EItemType::Missiles);\n  CPlayerGun::EMissileMode missileMode = gun.GetMissleMode();\n  float chargeFactor = gun.IsCharging() ? gun.GetChargeBeamFactor() : 0.f;\n\n  if (x294_missileIntf)\n    x294_missileIntf->SetChargeBeamFactor(chargeFactor);\n\n  if (init || numMissles != x2d8_missileAmount || missileMode != x2ec_missileMode ||\n      missileCap != x2dc_missileCapacity) {\n    if (x294_missileIntf) {\n      if (missileCap != x2dc_missileCapacity)\n        x294_missileIntf->SetMissileCapacity(missileCap);\n      if (numMissles != x2d8_missileAmount)\n        x294_missileIntf->SetNumMissiles(numMissles, mgr);\n      if (missileMode != x2ec_missileMode)\n        x294_missileIntf->SetIsMissilesActive(missileMode == CPlayerGun::EMissileMode::Active);\n    }\n    x2d8_missileAmount = numMissles;\n    x2ec_missileMode = missileMode;\n    x2dc_missileCapacity = missileCap;\n  }\n}\n\nvoid CSamusHud::UpdateVideoBands(float dt, const CStateManager& mgr) {\n  for (auto& videoBand : x5a4_videoBands) {\n    if (videoBand.x0_videoband) {\n      videoBand.x0_videoband->SetIsVisible(false);\n    }\n  }\n}\n\nvoid CSamusHud::UpdateBallMode(const CStateManager& mgr, bool init) {\n  if (!x2b0_ballIntf)\n    return;\n\n  CPlayer& player = mgr.GetPlayer();\n  CPlayerGun& gun = *player.GetPlayerGun();\n  CPlayerState& playerState = *mgr.GetPlayerState();\n  u32 numPbs = playerState.GetItemAmount(CPlayerState::EItemType::PowerBombs);\n  u32 pbCap = playerState.GetItemCapacity(CPlayerState::EItemType::PowerBombs);\n  u32 bombCount = gun.IsBombReady() ? gun.GetBombCount() : 0;\n  bool hasBombs = playerState.HasPowerUp(CPlayerState::EItemType::MorphBallBombs);\n  bool pbReady =\n      gun.IsPowerBombReady() && player.GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed;\n\n  x2b0_ballIntf->SetBombParams(numPbs, pbCap, bombCount, hasBombs, pbReady, false);\n}\n\nvoid CSamusHud::UpdateThreatAssessment(float dt, const CStateManager& mgr) {\n  CMaterialFilter filter(CMaterialList(EMaterialTypes::Trigger), CMaterialList(),\n                         CMaterialFilter::EFilterType::Include);\n\n  CPlayer& player = mgr.GetPlayer();\n  zeus::CAABox playerAABB = zeus::skNullBox;\n  if (std::optional<zeus::CAABox> aabb = player.GetTouchBounds())\n    playerAABB = *aabb;\n\n  zeus::CAABox aabb;\n  aabb.accumulateBounds(player.GetTranslation() - g_tweakGui->GetThreatRange());\n  aabb.accumulateBounds(player.GetTranslation() + g_tweakGui->GetThreatRange());\n  EntityList nearList;\n  mgr.BuildNearList(nearList, aabb, filter, nullptr);\n\n  float threatDist = 9999.f;\n  for (TUniqueId id : nearList) {\n    const CEntity* ent = mgr.GetObjectById(id);\n    if (TCastToConstPtr<CScriptTrigger> trigger = ent) {\n      if (!bool(trigger->GetTriggerFlags() & ETriggerFlags::DetectPlayer))\n        continue;\n      if (trigger->GetDamageInfo().GetDamage() == 0.f)\n        continue;\n      if (std::optional<zeus::CAABox> aabb = trigger->GetTouchBounds()) {\n        float dist = playerAABB.distanceBetween(*aabb);\n        if (dist < threatDist)\n          threatDist = dist;\n      }\n    }\n  }\n\n  if (player.GetThreatOverride() > 0.f)\n    threatDist = std::min((1.f - player.GetThreatOverride()) * g_tweakGui->GetThreatRange(), threatDist);\n\n  if (mgr.IsFullThreat())\n    threatDist = 0.f;\n  if (x290_threatIntf)\n    x290_threatIntf->SetThreatDistance(threatDist);\n}\n\nvoid CSamusHud::UpdateVisorAndBeamMenus(float dt, const CStateManager& mgr) {\n  CPlayer& player = mgr.GetPlayer();\n  CPlayerGun& gun = *player.GetPlayerGun();\n  CPlayerState& playerState = *mgr.GetPlayerState();\n\n  float beamInterp = zeus::clamp(0.f, gun.GetHoloTransitionFactor(), 1.f);\n  float visorInterp = playerState.GetVisorTransitionFactor();\n\n  if (x2a8_beamMenu) {\n    x2a8_beamMenu->SetSelection(int(gun.GetCurrentBeam()), int(gun.GetNextBeam()), beamInterp);\n    x2a8_beamMenu->SetPlayerHas(BuildPlayerHasBeams(mgr));\n  }\n\n  if (x2a4_visorMenu) {\n    x2a4_visorMenu->SetSelection(int(playerState.GetCurrentVisor()), int(playerState.GetTransitioningVisor()),\n                                 visorInterp);\n    x2a4_visorMenu->SetPlayerHas(BuildPlayerHasVisors(mgr));\n  }\n}\n\nvoid CSamusHud::UpdateCameraDebugSettings() {\n  const float fov = x5ec_camFovTweaks[g_tweakGui->GetHudCamFovTweak()];\n  const float y = x62c_camYTweaks[g_tweakGui->GetHudCamYTweak()];\n  const float z = x72c_camZTweaks[g_tweakGui->GetHudCamZTweak()];\n  if (x2a0_helmetIntf) {\n    x2a0_helmetIntf->UpdateCameraDebugSettings(fov, y, z);\n  }\n  if (x29c_decoIntf) {\n    x29c_decoIntf->UpdateCameraDebugSettings(fov, y, z);\n  }\n  x274_loadedFrmeBaseHud->GetFrameCamera()->SetFov(fov);\n  x310_cameraPos.y() = y;\n  x310_cameraPos.z() = z;\n}\n\nvoid CSamusHud::UpdateEnergyLow(float dt, const CStateManager& mgr) {\n  const bool cineCam = TCastToConstPtr<CCinematicCamera>(mgr.GetCameraManager()->GetCurrentCamera(mgr)).IsValid();\n  float oldTimer = x57c_energyLowTimer;\n\n  x57c_energyLowTimer = std::fmod(x57c_energyLowTimer + dt, 0.5f);\n  if (x57c_energyLowTimer < 0.25f)\n    x580_energyLowPulse = x57c_energyLowTimer / 0.25f;\n  else\n    x580_energyLowPulse = (0.5f - x57c_energyLowTimer) / 0.25f;\n\n  if (!cineCam && x2e0_27_energyLow && x57c_energyLowTimer < oldTimer)\n    CSfxManager::SfxStart(SFXui_energy_low, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n}\n\nvoid CSamusHud::ApplyClassicLag(const zeus::CUnitVector3f& lookDir, zeus::CQuaternion& rot, const CStateManager& mgr,\n                                float dt, bool invert) {\n  zeus::CQuaternion lookRot = zeus::CQuaternion::lookAt(lookDir, zeus::skForward, 2.f * M_PIF);\n  zeus::CQuaternion lookRot2;\n  if (invert) {\n    zeus::CUnitVector3f v1(lookRot.transform(x2f8_fpCamDir));\n    lookRot2 = zeus::CQuaternion::lookAt(v1, zeus::skForward, 2.f * M_PIF);\n  } else {\n    zeus::CUnitVector3f v1(lookRot.transform(x2f8_fpCamDir));\n    lookRot2 = zeus::CQuaternion::lookAt(zeus::skForward, v1, 2.f * M_PIF);\n  }\n\n  zeus::CQuaternion doubleRot = lookRot2 * lookRot2;\n  float dot = doubleRot.toTransform().basis[1].dot(rot.toTransform().basis[1]);\n  if (std::fabs(dot) > 1.f)\n    dot = (dot >= 0.f) ? 1.f : -1.f;\n\n  float angle = std::acos(dot);\n  float tmp = 0.f;\n  if (angle > 0.f)\n    tmp = 0.5f * dt * g_tweakPlayer->GetFreeLookSpeed() / angle;\n\n  float t = zeus::clamp(0.f, 18.f * dt * tmp, 1.f);\n  rot = zeus::CQuaternion::slerp(rot, doubleRot, t);\n}\n\nvoid CSamusHud::UpdateHudLag(float dt, const CStateManager& mgr) {\n  CPlayer& player = mgr.GetPlayer();\n\n  if (x29c_decoIntf)\n    x29c_decoIntf->SetDecoRotation(player.GetYaw());\n\n  if (!g_GameState->GameOptions().GetHUDLag()) {\n    if (x2a0_helmetIntf) {\n      x2a0_helmetIntf->SetHudLagRotation(zeus::CMatrix3f());\n      x2a0_helmetIntf->SetHudLagOffset(zeus::skZero3f);\n    }\n    if (x29c_decoIntf) {\n      x29c_decoIntf->SetReticuleTransform(zeus::CMatrix3f());\n      x29c_decoIntf->SetHudRotation(zeus::CQuaternion());\n      x29c_decoIntf->SetHudOffset(zeus::skZero3f);\n    }\n    x588_base_basewidget_pivot->SetTransform(\n        zeus::CTransform::Translate(x588_base_basewidget_pivot->GetWorldPosition()));\n    x274_loadedFrmeBaseHud->GetFrameCamera()->SetO2WTransform(\n        BuildFinalCameraTransform(zeus::CQuaternion(), x304_basewidgetIdlePos, x310_cameraPos));\n    x8_targetingMgr.CompoundTargetReticle().SetLeadingOrientation(zeus::CQuaternion());\n  } else {\n    zeus::CVector3f fpCamDir = x2f8_fpCamDir;\n    if (TCastToConstPtr<CFirstPersonCamera> fpCam = mgr.GetCameraManager()->GetCurrentCamera(mgr))\n      fpCamDir = fpCam->GetTransform().buildMatrix3f()[1];\n\n    ApplyClassicLag(fpCamDir, x31c_hudLag, mgr, dt, false);\n    ApplyClassicLag(fpCamDir, x32c_invHudLag, mgr, dt, true);\n\n    zeus::CQuaternion rot = zeus::CQuaternion::lookAt(zeus::CUnitVector3f(x2f8_fpCamDir), fpCamDir, 2.f * M_PIF);\n    rot *= rot;\n    rot *= rot;\n    x8_targetingMgr.CompoundTargetReticle().SetLeadingOrientation(rot);\n\n    zeus::CVector3f bobTranslation = player.GetCameraBob()->GetHelmetBobTranslation();\n\n    zeus::CQuaternion lagRot = x44c_hudLagShakeRot * x31c_hudLag;\n    zeus::CVector3f lagOff = x41c_decoShakeTranslate * g_tweakGui->GetHudLagOffsetScale();\n    lagOff.z() += bobTranslation.z();\n    if (x2a0_helmetIntf) {\n      x2a0_helmetIntf->SetHudLagRotation(lagRot);\n      x2a0_helmetIntf->SetHudLagOffset(lagOff);\n    }\n    if (x29c_decoIntf) {\n      x29c_decoIntf->SetReticuleTransform(x32c_invHudLag);\n      x29c_decoIntf->SetHudRotation(lagRot);\n      x29c_decoIntf->SetHudOffset(lagOff);\n    }\n\n    x274_loadedFrmeBaseHud->GetFrameCamera()->SetO2WTransform(\n        BuildFinalCameraTransform(lagRot, x304_basewidgetIdlePos + lagOff, x310_cameraPos));\n    x2f8_fpCamDir = fpCamDir;\n  }\n}\n\nbool CSamusHud::IsCachedLightInAreaLights(const SCachedHudLight& light, const CActorLights& areaLights) const {\n  for (const CLight& l : areaLights.GetAreaLights()) {\n    if (l.GetColor() != light.xc_color || l.GetPosition() != light.x0_pos)\n      continue;\n    return true;\n  }\n  return false;\n}\n\nbool CSamusHud::IsAreaLightInCachedLights(const CLight& light) const {\n  for (const SCachedHudLight& l : x340_hudLights) {\n    if (l.x1c_fader == 0.f)\n      continue;\n    if (l.xc_color != light.GetColor() || l.x0_pos != light.GetPosition())\n      continue;\n    return true;\n  }\n  return false;\n}\n\nint CSamusHud::FindEmptyHudLightSlot(const CLight& light) const {\n  for (size_t i = 0; i < x340_hudLights.size(); ++i) {\n    if (x340_hudLights[i].x1c_fader == 0.f) {\n      return int(i);\n    }\n  }\n  return -1;\n}\n\nzeus::CColor CSamusHud::GetVisorHudLightColor(const zeus::CColor& color, const CStateManager& mgr) const {\n  zeus::CColor ret = color;\n  const CPlayerState& playerState = *mgr.GetPlayerState();\n  float t = playerState.GetVisorTransitionFactor();\n  switch (playerState.GetCurrentVisor()) {\n  case CPlayerState::EPlayerVisor::Scan:\n    ret *= zeus::CColor::lerp(zeus::skWhite, g_tweakGuiColors->GetScanVisorHudLightMultiply(), t);\n    break;\n  case CPlayerState::EPlayerVisor::Thermal:\n    ret *= g_tweakGuiColors->GetThermalVisorHudLightMultiply();\n    break;\n  case CPlayerState::EPlayerVisor::XRay:\n    ret = zeus::CColor(zeus::CColor(0.3f, 0.6f, 0.1f).rgbDot(ret));\n    break;\n  default:\n    break;\n  }\n  return ret;\n}\n\nvoid CSamusHud::UpdateHudDynamicLights(float dt, const CStateManager& mgr) {\n  if (TCastToConstPtr<CFirstPersonCamera> fpCam = mgr.GetCameraManager()->GetCurrentCamera(mgr)) {\n    zeus::CVector3f lookDir = fpCam->GetTransform().basis[1];\n    zeus::CAABox camAABB(fpCam->GetTranslation() - 0.125f, fpCam->GetTranslation() + 0.125f);\n    TAreaId playerArea = mgr.GetPlayer().GetAreaIdAlways();\n    if (playerArea == kInvalidAreaId)\n      return;\n    x33c_lights->BuildAreaLightList(mgr, *mgr.GetWorld()->GetAreaAlways(playerArea), camAABB);\n    for (SCachedHudLight& light : x340_hudLights)\n      if (light.x1c_fader > 0.f && ((light.x0_pos - fpCam->GetTranslation()).normalized().dot(lookDir) <= 0.15707964f ||\n                                    !IsCachedLightInAreaLights(light, *x33c_lights)))\n        light.x1c_fader *= -1.f;\n    int negCount = 0;\n    for (SCachedHudLight& light : x340_hudLights)\n      if (light.x1c_fader <= 0.f)\n        ++negCount;\n    --negCount;\n    for (const CLight& light : x33c_lights->GetAreaLights()) {\n      if (negCount < 1)\n        break;\n      if (IsAreaLightInCachedLights(light))\n        continue;\n      if ((light.GetPosition() - fpCam->GetTranslation()).normalized().dot(lookDir) > 0.15707964f) {\n        int slot = FindEmptyHudLightSlot(light);\n        if (slot == -1)\n          continue;\n        --negCount;\n        SCachedHudLight& cachedLight = x340_hudLights[slot];\n        cachedLight.x0_pos = light.GetPosition();\n        cachedLight.xc_color = light.GetColor();\n        cachedLight.x10_distC = light.GetAttenuationConstant();\n        cachedLight.x14_distL = light.GetAttenuationLinear();\n        cachedLight.x18_distQ = light.GetAttenuationQuadratic();\n        cachedLight.x1c_fader = 0.001f;\n      }\n    }\n\n    float dt2 = 2.f * dt;\n    for (SCachedHudLight& light : x340_hudLights) {\n      if (light.x1c_fader < 0.f)\n        light.x1c_fader = std::min(0.f, light.x1c_fader + dt2);\n      else if (light.x1c_fader < 1.f && light.x1c_fader != 0.f)\n        light.x1c_fader = std::min(light.x1c_fader + dt2, 1.f);\n    }\n\n    CPlayerState& playerState = *mgr.GetPlayerState();\n    CPlayerState::EPlayerVisor visor = playerState.GetCurrentVisor();\n    float visorT = playerState.GetVisorTransitionFactor();\n    zeus::CColor lightAdd =\n        zeus::CColor::lerp(g_tweakGui->GetVisorHudLightAdd(0), g_tweakGui->GetVisorHudLightAdd(int(visor)), visorT);\n    zeus::CColor lightMul = zeus::CColor::lerp(g_tweakGui->GetVisorHudLightMultiply(0),\n                                               g_tweakGui->GetVisorHudLightMultiply(int(visor)), visorT);\n\n    auto lightIt = x5d8_guiLights.begin();\n    float maxIntensity = 0.f;\n    int maxIntensityIdx = 0;\n    for (size_t i = 0; i < x340_hudLights.size(); ++i) {\n      SCachedHudLight& light = x340_hudLights[i];\n      CGuiLight* lightWidget = *lightIt++;\n      zeus::CVector3f lightToCam = fpCam->GetTranslation() - light.x0_pos;\n      zeus::CVector3f lightNormal = fpCam->GetTransform().buildMatrix3f() * lightToCam.normalized();\n      float dist = std::max(lightToCam.magnitude(), FLT_EPSILON);\n      float falloffMul = 1.f / (dist * dist * light.x18_distQ * g_tweakGui->GetHudLightAttMulQuadratic() +\n                                dist * light.x14_distL * g_tweakGui->GetHudLightAttMulLinear() +\n                                light.x10_distC * g_tweakGui->GetHudLightAttMulConstant());\n      falloffMul = std::min(falloffMul, 1.f);\n      lightWidget->SetO2WTransform(zeus::lookAt(zeus::skZero3f, lightNormal));\n      float fadedFalloff = falloffMul * std::fabs(light.x1c_fader);\n      zeus::CColor lightColor = GetVisorHudLightColor(light.xc_color * zeus::CColor(fadedFalloff), mgr);\n      lightWidget->SetColor(lightColor);\n      lightAdd += lightColor * lightMul;\n      float greyscale =\n          fadedFalloff * zeus::skForward.dot(-lightNormal) * lightAdd.rgbDot(zeus::CColor(0.3f, 0.6f, 0.1f));\n      if (greyscale > maxIntensity) {\n        maxIntensity = greyscale;\n        maxIntensityIdx = int(i);\n      }\n    }\n\n    CLight brightestGameLight = CLight::BuildPoint(zeus::skZero3f, zeus::skBlack);\n    for (CEntity* ent : mgr.GetLightObjectList()) {\n      if (!ent || !ent->GetActive())\n        continue;\n      CGameLight& gameLight = static_cast<CGameLight&>(*ent);\n      if (TCastToConstPtr<CGameProjectile>(mgr.GetObjectById(gameLight.GetParentId())))\n        continue;\n      CLight thisLight = gameLight.GetLight();\n      if (thisLight.GetIntensity() > brightestGameLight.GetIntensity()) {\n        zeus::CSphere sphere(thisLight.GetPosition(), thisLight.GetRadius());\n        if (camAABB.intersects(sphere))\n          brightestGameLight = thisLight;\n      }\n    }\n\n    if (brightestGameLight.GetIntensity() > FLT_EPSILON) {\n      zeus::CVector3f lightToCam = fpCam->GetTranslation() - brightestGameLight.GetPosition();\n      float dist = std::max(lightToCam.magnitude(), FLT_EPSILON);\n      float falloffMul =\n          1.f / (dist * dist * brightestGameLight.GetAttenuationQuadratic() * g_tweakGui->GetHudLightAttMulQuadratic() +\n                 dist * brightestGameLight.GetAttenuationLinear() * g_tweakGui->GetHudLightAttMulLinear() +\n                 brightestGameLight.GetAttenuationConstant() * g_tweakGui->GetHudLightAttMulConstant());\n      falloffMul = std::min(falloffMul, 1.f);\n      zeus::CColor falloffColor = brightestGameLight.GetColor() * zeus::CColor(falloffMul);\n      falloffColor = GetVisorHudLightColor(falloffColor, mgr);\n      if (brightestGameLight.GetType() == ELightType::Spot) {\n        float quarterCicleFactor = zeus::clamp(\n            0.f,\n            std::asin(std::max(0.f, fpCam->GetTransform().basis[1].dot(brightestGameLight.GetDirection()))) *\n                (M_PIF / 2.f),\n            1.f);\n        falloffColor *= zeus::CColor(quarterCicleFactor);\n      }\n      lightAdd += falloffColor;\n    }\n\n    const CGuiLight& brightestLight = *x5d8_guiLights[maxIntensityIdx];\n    lightAdd += x33c_lights->GetAmbientColor() * zeus::CColor(0.25f, 1.f);\n    zeus::CVector3f revDir = -brightestLight.GetWorldTransform().basis[1];\n    float foreDot = revDir.dot(zeus::skForward);\n    x5d8_guiLights[3]->SetO2WTransform(zeus::lookAt(zeus::skZero3f, zeus::skForward * 2.f * foreDot - revDir));\n    x5d8_guiLights[3]->SetColor(g_tweakGui->GetHudReflectivityLightColor() * brightestLight.GetIntermediateColor());\n    x5d8_guiLights[3]->SetAmbientLightColor(lightAdd);\n  }\n}\n\nvoid CSamusHud::UpdateHudDamage(float dt, const CStateManager& mgr, Tweaks::ITweakGui::EHelmetVisMode helmetVis) {\n  CPlayer& player = mgr.GetPlayer();\n  if (player.WasDamaged() && mgr.GetGameState() == CStateManager::EGameState::Running)\n    x3e8_damageTIme += dt;\n  else\n    x3e8_damageTIme = 0.f;\n\n  float pulseDur = g_tweakGui->GetHudDamagePulseDuration();\n  float pulseTime = std::fabs(std::fmod(x3e8_damageTIme, pulseDur));\n  if (pulseTime < 0.5f * pulseDur)\n    x3ec_damageLightPulser = pulseTime / (0.5f * pulseDur);\n  else\n    x3ec_damageLightPulser = (pulseDur - pulseTime) / (0.5f * pulseDur);\n\n  x3ec_damageLightPulser = zeus::clamp(\n      0.f, g_tweakGui->GetHudDamageColorGain() * x3ec_damageLightPulser * std::min(0.5f, player.GetDamageAmount()),\n      1.f);\n  zeus::CColor damageAmbColor = g_tweakGuiColors->GetHudFrameColor();\n  damageAmbColor *= damageAmbColor.a();\n  damageAmbColor += zeus::CColor(x3ec_damageLightPulser);\n  damageAmbColor.a() = 1.f;\n\n  if (x3d4_damageLight)\n    x3d4_damageLight->SetAmbientLightColor(damageAmbColor);\n\n  if (x3f4_damageFilterAmt > 0.f) {\n    x3f4_damageFilterAmt = std::max(0.f, x3f4_damageFilterAmt - dt);\n    if (x3f4_damageFilterAmt == 0.f) {\n      CSfxManager::RemoveEmitter(x3a4_damageSfx);\n      x3a4_damageSfx.reset();\n    }\n  }\n\n  float tmp = x3f0_damageFilterAmtInit * g_tweakGui->GetHudDamagePeakFactor();\n  float colorGain;\n  if (x3f4_damageFilterAmt > tmp)\n    colorGain = (x3f0_damageFilterAmtInit - x3f4_damageFilterAmt) / (x3f0_damageFilterAmtInit - tmp);\n  else\n    colorGain = x3f4_damageFilterAmt / tmp;\n\n  colorGain = zeus::clamp(0.f, colorGain * x3f8_damageFilterAmtGain, 1.f);\n  zeus::CColor color0 = g_tweakGuiColors->GetDamageAmbientColor();\n  color0.a() *= colorGain;\n\n  zeus::CColor color1 = g_tweakGuiColors->GetDamageAmbientPulseColor();\n  color1.a() *= x3ec_damageLightPulser;\n  zeus::CColor color2 = color0 + color1;\n\n  if (color2.a()) {\n    if (player.GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Unmorphed)\n      color2.a() *= 0.75f;\n    x3a8_camFilter.SetFilter(EFilterType::Add, EFilterShape::Fullscreen, 0.f, color2, {});\n  } else {\n    x3a8_camFilter.DisableFilter(0.f);\n  }\n\n  if (x3a4_damageSfx)\n    CSfxManager::UpdateEmitter(x3a4_damageSfx, player.GetTranslation(), player.GetTransform().basis[1], 1.f);\n\n  if (x400_hudDamagePracticals > 0.f) {\n    x400_hudDamagePracticals = std::max(0.f, x400_hudDamagePracticals - dt);\n    float practicals = x400_hudDamagePracticals / x3fc_hudDamagePracticalsInit;\n    if (x28c_energyIntf)\n      x28c_energyIntf->SetFlashMagnitude(practicals);\n    practicals = std::min(practicals * x404_hudDamagePracticalsGain, 1.f);\n    x2a0_helmetIntf->AddHelmetLightValue(practicals);\n    if (x29c_decoIntf) {\n      x29c_decoIntf->SetFrameColorValue(practicals);\n      if (practicals > 0.f) {\n        x3d4_damageLight->SetColor(g_tweakGuiColors->GetHudDamageLightColor() * zeus::CColor(practicals));\n        x3d4_damageLight->SetIsVisible(true);\n      } else {\n        x3d4_damageLight->SetIsVisible(false);\n      }\n    }\n  }\n\n  bool transformUpdate = false;\n  if (x414_decoShakeTranslateAmt > 0.f) {\n    x418_decoShakeTranslateAmtVel -= g_tweakGui->GetDecoDamageShakeDeceleration() * 60.f * dt;\n    x414_decoShakeTranslateAmt = std::max(0.f, x414_decoShakeTranslateAmt + x418_decoShakeTranslateAmtVel);\n    transformUpdate = true;\n  }\n  if (x460_decoShakeAmt > 0.f) {\n    x460_decoShakeAmt = std::max(0.f, x460_decoShakeAmt - dt);\n    x44c_hudLagShakeRot = zeus::CQuaternion();\n    float rotMul = std::min(g_tweakGui->GetMaxDecoDamageShakeRotate(),\n                            x460_decoShakeAmt / x45c_decoShakeAmtInit * x464_decoShakeAmtGain);\n    float rotAng = rotMul * (2.f * M_PIF / 10.f);\n    x44c_hudLagShakeRot.rotateX(rand() / float(RAND_MAX) * rotAng);\n    x44c_hudLagShakeRot.rotateZ(rand() / float(RAND_MAX) * rotAng);\n    std::array<zeus::CVector3f, 3> vecs{zeus::skRight, zeus::skForward, zeus::skUp};\n    for (int i = 0; i < 4; ++i) {\n      const int sel = rand() % 9;\n      vecs[sel % 3][sel / 3] += (rand() / float(RAND_MAX) - dt) * rotMul;\n    }\n    x428_decoShakeRotate = zeus::CMatrix3f(vecs[0], vecs[1], vecs[2]).transposed();\n    transformUpdate = true;\n  }\n\n  if (transformUpdate) {\n    x41c_decoShakeTranslate =\n        x408_damagerToPlayerNorm * std::min(g_tweakGui->GetMaxDecoDamageShakeTranslate(), x414_decoShakeTranslateAmt);\n    if (x29c_decoIntf)\n      x29c_decoIntf->SetDamageTransform(x428_decoShakeRotate,\n                                        x41c_decoShakeTranslate * g_tweakGui->GetHudDecoShakeTranslateGain());\n  }\n}\n\nvoid CSamusHud::UpdateStaticSfx(CSfxHandle& handle, float& cycleTimer, u16 sfxId, float dt, float oldStaticInterp,\n                                float staticThreshold) {\n  if ((oldStaticInterp > staticThreshold && x510_staticInterp <= staticThreshold) ||\n      (oldStaticInterp <= staticThreshold && x510_staticInterp > staticThreshold)) {\n    cycleTimer = 0.f;\n  } else {\n    if (cycleTimer < 0.1f)\n      cycleTimer = std::min(cycleTimer + dt, 0.1f);\n    if (cycleTimer == 0.1f) {\n      if (x510_staticInterp > staticThreshold) {\n        if (!handle)\n          handle = CSfxManager::SfxStart(sfxId, 1.f, 0.f, false, 0x7f, true, kInvalidAreaId);\n      } else {\n        CSfxManager::SfxStop(handle);\n        handle.reset();\n      }\n    }\n  }\n}\n\nvoid CSamusHud::UpdateStaticInterference(float dt, const CStateManager& mgr) {\n  float intf = mgr.GetPlayerState()->GetStaticInterference().GetTotalInterference();\n  float oldStaticInterp = x510_staticInterp;\n  if (x510_staticInterp < intf)\n    x510_staticInterp = std::min(x510_staticInterp + dt, intf);\n  else\n    x510_staticInterp = std::max(intf, x510_staticInterp - dt);\n\n  UpdateStaticSfx(x508_staticSfxHi, x514_staticCycleTimerHi, SFXui_static_hi, dt, oldStaticInterp, 0.1f);\n  UpdateStaticSfx(x50c_staticSfxLo, x518_staticCycleTimerLo, SFXui_static_lo, dt, oldStaticInterp, 0.5f);\n\n  if (x510_staticInterp > 0.f) {\n    zeus::CColor color = zeus::skWhite;\n    color.a() = x510_staticInterp;\n    x51c_camFilter2.SetFilter(EFilterType::Blend, EFilterShape::RandomStatic, 0.f, color, {});\n  } else {\n    x51c_camFilter2.DisableFilter(0.f);\n  }\n}\n\nint CSamusHud::GetRelativeDirection(const zeus::CVector3f& position, const CStateManager& mgr) {\n  TCastToConstPtr<CFirstPersonCamera> fpCam = mgr.GetCameraManager()->GetCurrentCamera(mgr);\n  if (!fpCam)\n    return 0;\n  zeus::CVector3f camToPosLocal = fpCam->GetTransform().transposeRotate(position - fpCam->GetTranslation());\n  if (camToPosLocal == position)\n    return 0;\n  float y = std::cos(2.f * M_PIF * 0.0027777778f * 0.5f * fpCam->GetFov());\n  float x = std::cos(2.f * M_PIF * 0.0027777778f * 0.5f * fpCam->GetFov() * fpCam->GetAspectRatio());\n  zeus::CVector2f camToPosXY = zeus::CVector2f(camToPosLocal.x(), camToPosLocal.y()).normalized();\n  zeus::CVector2f camToPosYZ = zeus::CVector2f(camToPosLocal.y(), camToPosLocal.z()).normalized();\n  if (camToPosXY.dot(zeus::CVector2f(0.f, 1.f)) > x && camToPosYZ.dot(zeus::CVector2f(1.f, 0.f)) > y)\n    return 0;\n  if (camToPosXY.dot(zeus::CVector2f(0.f, -1.f)) > x && camToPosYZ.dot(zeus::CVector2f(-1.f, 0.f)) > y)\n    return 1;\n  zeus::CVector3f camToPosNorm = camToPosLocal.normalized();\n  zeus::CQuaternion quat;\n  quat.rotateY(2.f * M_PIF / 8.f);\n  zeus::CVector3f vec = zeus::skUp;\n  float maxDot = -1.f;\n  int ret = -1;\n  for (int i = 0; i < 8; ++i) {\n    float dot = camToPosNorm.dot(vec);\n    if (dot > maxDot) {\n      maxDot = dot;\n      ret = i + 2;\n    }\n    vec = quat.transform(vec);\n  }\n  return ret;\n}\n\nvoid CSamusHud::ShowDamage(const zeus::CVector3f& position, float dam, float prevDam, const CStateManager& mgr) {\n  CPlayer& player = mgr.GetPlayer();\n  int dir = GetRelativeDirection(position, mgr);\n  TCastToConstPtr<CFirstPersonCamera> fpCam = mgr.GetCameraManager()->GetCurrentCamera(mgr);\n  x404_hudDamagePracticalsGain =\n      g_tweakGui->GetHudDamagePracticalsGainLinear() * dam + g_tweakGui->GetHudDamagePracticalsGainConstant();\n  x3fc_hudDamagePracticalsInit = std::max(FLT_EPSILON, g_tweakGui->GetHudDamagePracticalsInitLinear() * dam +\n                                                           g_tweakGui->GetHudDamagePracticalsInitConstant());\n  x400_hudDamagePracticals = x3fc_hudDamagePracticalsInit;\n  if (x3d4_damageLight)\n    x3d4_damageLight->SetLocalTransform(x3d8_lightTransforms[dir]);\n  x3f8_damageFilterAmtGain =\n      g_tweakGui->GetHudDamageFilterGainLinear() * dam + g_tweakGui->GetHudDamageFilterGainConstant();\n  x3f0_damageFilterAmtInit =\n      g_tweakGui->GetHudDamageFilterInitLinear() * dam + g_tweakGui->GetHudDamageFilterInitConstant();\n  x3f4_damageFilterAmt = x3f0_damageFilterAmtInit;\n  if (!x3a4_damageSfx) {\n    x3a4_damageSfx = CSfxManager::AddEmitter(SFXui_damage_lp, player.GetTranslation(), player.GetTransform().basis[1],\n                                             0.f, false, true, 0xff, kInvalidAreaId);\n  }\n  if (fpCam) {\n    x418_decoShakeTranslateAmtVel =\n        g_tweakGui->GetHudDecoShakeTranslateVelLinear() * prevDam + g_tweakGui->GetHudDecoShakeTranslateVelConstant();\n    x414_decoShakeTranslateAmt = x418_decoShakeTranslateAmtVel;\n    x408_damagerToPlayerNorm = -(fpCam->GetTransform().inverse() * position).normalized();\n    x464_decoShakeAmtGain = g_tweakGui->GetDecoShakeGainLinear() * prevDam + g_tweakGui->GetDecoShakeGainConstant();\n    x45c_decoShakeAmtInit = g_tweakGui->GetDecoShakeInitLinear() * prevDam + g_tweakGui->GetDecoShakeInitConstant();\n    x460_decoShakeAmt = x45c_decoShakeAmtInit;\n  }\n}\n\nvoid CSamusHud::EnterFirstPerson(const CStateManager& mgr) {\n  CSfxManager::SfxVolume(x508_staticSfxHi, 1.f);\n  CSfxManager::SfxVolume(x50c_staticSfxLo, 1.f);\n}\n\nvoid CSamusHud::LeaveFirstPerson(const CStateManager& mgr) {\n  CSfxManager::SfxVolume(x508_staticSfxHi, 0.f);\n  CSfxManager::SfxVolume(x50c_staticSfxLo, 0.f);\n}\n\nEHudState CSamusHud::GetDesiredHudState(const CStateManager& mgr) {\n  if (mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed ||\n      mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphing ||\n      mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Unmorphing)\n    return EHudState::Ball;\n\n  switch (mgr.GetPlayerState()->GetTransitioningVisor()) {\n  case CPlayerState::EPlayerVisor::Combat:\n    return EHudState::Combat;\n  case CPlayerState::EPlayerVisor::XRay:\n    return EHudState::XRay;\n  case CPlayerState::EPlayerVisor::Scan:\n    return EHudState::Scan;\n  case CPlayerState::EPlayerVisor::Thermal:\n    return EHudState::Thermal;\n  default:\n    return EHudState::None;\n  }\n}\n\nvoid CSamusHud::Update(float dt, const CStateManager& mgr, CInGameGuiManager::EHelmetVisMode helmetVis, bool hudVis,\n                       bool targetingManager) {\n  CPlayer& player = mgr.GetPlayer();\n  UpdateStateTransition(dt, mgr);\n  bool firstPerson = player.GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Unmorphed &&\n                     !mgr.GetCameraManager()->IsInCinematicCamera();\n  if (firstPerson != x2e0_26_latestFirstPerson) {\n    if (firstPerson)\n      EnterFirstPerson(mgr);\n    else\n      LeaveFirstPerson(mgr);\n    x2e0_26_latestFirstPerson = firstPerson;\n  }\n\n  float morphT = 0.f;\n  switch (player.GetMorphballTransitionState()) {\n  case CPlayer::EPlayerMorphBallState::Morphed:\n    morphT = 1.f;\n    break;\n  case CPlayer::EPlayerMorphBallState::Morphing:\n    morphT = player.GetMorphFactor();\n    break;\n  case CPlayer::EPlayerMorphBallState::Unmorphing:\n    morphT = 1.f - player.GetMorphFactor();\n    break;\n  default:\n    break;\n  }\n\n  float scaleMul = 1.f - zeus::clamp(0.f, (CGraphics::GetViewportAspect() - 1.33f) / (1.77f - 1.33f), 1.f);\n  x500_viewportScale.y() = 1.f - scaleMul * morphT * g_tweakGui->GetBallViewportYReduction() * 1.2f;\n  if (x2b0_ballIntf)\n    x2b0_ballIntf->SetBallModeFactor(morphT);\n\n  bool helmetVisible = false;\n  bool glowVisible = false;\n  bool decoVisible = false;\n  if (player.GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Morphed) {\n    switch (helmetVis) {\n    case CInGameGuiManager::EHelmetVisMode::HelmetOnly:\n      helmetVisible = true;\n      break;\n    case CInGameGuiManager::EHelmetVisMode::GlowHelmetDeco:\n      glowVisible = true;\n      [[fallthrough]];\n    case CInGameGuiManager::EHelmetVisMode::HelmetDeco:\n      helmetVisible = true;\n      [[fallthrough]];\n    case CInGameGuiManager::EHelmetVisMode::Deco:\n      decoVisible = true;\n      break;\n    default:\n      break;\n    }\n  }\n\n  if (x29c_decoIntf)\n    x29c_decoIntf->SetIsVisibleDebug(decoVisible);\n  if (x2a0_helmetIntf)\n    x2a0_helmetIntf->SetIsVisibleDebug(helmetVisible, glowVisible);\n\n  x590_base_Model_AutoMapper->SetIsVisible(false);\n\n  UpdateEnergyLow(dt, mgr);\n\n  for (auto& entry : x7ac_) {\n    entry.x0_ = 0;\n    entry.x4_ = 0;\n  }\n\n  if (x2ac_radarIntf)\n    x2ac_radarIntf->Update(dt, mgr);\n\n  UpdateHudLag(dt, mgr);\n\n  UpdateHudDynamicLights(dt, mgr);\n\n  if (targetingManager)\n    x8_targetingMgr.Update(dt, mgr);\n\n  UpdateHudDamage(dt, mgr, helmetVis);\n\n  UpdateStaticInterference(dt, mgr);\n\n  if (helmetVis != Tweaks::ITweakGui::EHelmetVisMode::ReducedUpdate) {\n    if (x2bc_nextState != EHudState::None) {\n      UpdateEnergy(dt, mgr, false);\n      UpdateFreeLook(dt, mgr);\n    }\n\n    if (x2bc_nextState == EHudState::Ball) {\n      UpdateBallMode(mgr, false);\n    } else if (x2bc_nextState >= EHudState::Combat && x2bc_nextState <= EHudState::Scan) {\n      UpdateThreatAssessment(dt, mgr);\n      UpdateMissile(dt, mgr, false);\n      UpdateVideoBands(dt, mgr);\n    }\n\n    UpdateVisorAndBeamMenus(dt, mgr);\n\n    if (player.WasDamaged() && mgr.GetGameState() == CStateManager::EGameState::Running)\n      ShowDamage(player.GetDamageLocationWR(), player.GetDamageAmount(), player.GetPrevDamageAmount(), mgr);\n  }\n\n  float oldAPulse = x584_abuttonPulse;\n  if (!x554_hudMemoIdx) {\n    x584_abuttonPulse += 2.f * dt;\n    if (x584_abuttonPulse > 1.f)\n      x584_abuttonPulse -= 2.f;\n  }\n\n  zeus::CColor abuttonColor = zeus::skWhite;\n  abuttonColor.a() = std::fabs(x584_abuttonPulse);\n  x5a0_base_model_abutton->SetColor(abuttonColor);\n\n  if (!mgr.GetCameraManager()->IsInCinematicCamera() && oldAPulse < 0.f && x584_abuttonPulse >= 0.f &&\n      x598_base_basewidget_message->GetIsVisible() && (x558_messageTextTime == 0.f || x558_messageTextTime >= 1.f)) {\n    CSfxManager::SfxStart(SFXui_hud_memo_a_pulse, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n  }\n\n  float allTextAlpha;\n  if (x29c_decoIntf)\n    allTextAlpha = x29c_decoIntf->GetHudTextAlpha();\n  else\n    allTextAlpha = 1.f;\n\n  float messageTextAlpha = 1.f;\n  if (x558_messageTextTime > 0.f)\n    messageTextAlpha = std::min(x558_messageTextTime, 1.f);\n  else if (!x59c_base_textpane_message->GetIsVisible() && !x598_base_basewidget_message->GetIsVisible())\n    messageTextAlpha = 0.f;\n\n  if (x2b4_bossEnergyIntf)\n    x2b4_bossEnergyIntf->SetAlpha(1.f - messageTextAlpha);\n\n  if (x550_hudMemoString && x550_hudMemoString.IsLoaded()) {\n    SetMessage(x550_hudMemoString->GetString(x554_hudMemoIdx), x548_hudMemoParms);\n    x550_hudMemoString = TLockedToken<CStringTable>();\n  }\n\n  if (x558_messageTextTime > 0.f) {\n    x558_messageTextTime = std::max(0.f, x558_messageTextTime - dt);\n    if (x558_messageTextTime == 0.f) {\n      x59c_base_textpane_message->TextSupport().SetTypeWriteEffectOptions(false, 0.f, 1.f);\n      x598_base_basewidget_message->SetVisibility(false, ETraversalMode::Children);\n    }\n  }\n\n  CGuiWidget* messageWidget;\n  if (x598_base_basewidget_message->GetIsVisible())\n    messageWidget = x598_base_basewidget_message;\n  else\n    messageWidget = x59c_base_textpane_message;\n\n  zeus::CColor messageColor = zeus::skWhite;\n  float textScale = 1.f;\n  messageColor.a() = std::min(allTextAlpha, messageTextAlpha);\n  messageWidget->SetColor(messageColor);\n\n  if (messageWidget == x598_base_basewidget_message) {\n    if (x558_messageTextTime > 0.f)\n      x560_messageTextScale = std::min(x558_messageTextTime, 1.f);\n    else\n      x560_messageTextScale = std::min(x560_messageTextScale + dt, 1.f);\n\n    float textScaleT = std::max(0.f, (x560_messageTextScale - 0.75f) / 0.25f);\n    if (textScaleT != 1.f) {\n      if (textScaleT < 0.7f)\n        textScale = textScaleT / 0.7f;\n      else if (textScaleT < 0.85f)\n        textScale = 0.1f * (1.f - (textScaleT - 0.7f) / 0.15f) + 0.9f;\n      else\n        textScale = 0.1f * ((textScaleT - 0.7f) - 0.15f) / 0.3f + 0.9f;\n    }\n\n    x598_base_basewidget_message->SetLocalTransform(x598_base_basewidget_message->GetTransform() *\n                                                    zeus::CTransform::Scale(textScale, 1.f, 1.f));\n  }\n\n  float nextSfxChars = x55c_lastSfxChars + g_tweakGui->GetWorldTransManagerCharsPerSfx();\n  if (x59c_base_textpane_message->TextSupport().GetNumCharsPrinted() >= nextSfxChars) {\n    x55c_lastSfxChars = nextSfxChars;\n    if (!x598_base_basewidget_message->GetIsVisible() || textScale == 1.f)\n      CSfxManager::SfxStart(SFXui_hud_memo_type, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n  }\n\n  if (mgr.GetEscapeSequenceTimer() > 0.f) {\n    int minutes = mgr.GetEscapeSequenceTimer() / 60.f;\n    int seconds = std::fmod(mgr.GetEscapeSequenceTimer(), 60.f);\n    int hundredths = std::fmod(mgr.GetEscapeSequenceTimer() * 100.f, 100.f);\n    std::string timeStr = fmt::format(\"{:02d}:{:02d}:{:02d}\", int(minutes), int(seconds), int(hundredths));\n    x594_base_textpane_counter->TextSupport().SetText(timeStr);\n    x594_base_textpane_counter->SetIsVisible(true);\n\n    zeus::CColor counterColor = zeus::skWhite;\n    counterColor.a() = zeus::clamp(0.f, std::min(1.f - std::min(x558_messageTextTime, 1.f), allTextAlpha), 1.f);\n    x594_base_textpane_counter->SetColor(counterColor);\n  } else {\n    x594_base_textpane_counter->SetIsVisible(false);\n  }\n\n  x274_loadedFrmeBaseHud->Update(dt);\n  if (x288_loadedSelectedHud)\n    x288_loadedSelectedHud->Update(dt);\n  if (x2b4_bossEnergyIntf)\n    x2b4_bossEnergyIntf->Update(dt);\n  if (x28c_energyIntf)\n    x28c_energyIntf->Update(dt, x580_energyLowPulse);\n  if (x290_threatIntf)\n    x290_threatIntf->Update(dt);\n  if (x294_missileIntf)\n    x294_missileIntf->Update(dt, mgr);\n  if (x298_freeLookIntf)\n    x298_freeLookIntf->Update(dt);\n  if (x2a0_helmetIntf)\n    x2a0_helmetIntf->Update(dt);\n\n  if (player.GetScanningState() == CPlayer::EPlayerScanState::NotScanning)\n    x2f0_visorBeamMenuAlpha = std::min(x2f0_visorBeamMenuAlpha + 2.f * dt, 1.f);\n  else\n    x2f0_visorBeamMenuAlpha = std::max(0.f, x2f0_visorBeamMenuAlpha - 2.f * dt);\n\n  CPlayerState::EPlayerVisor curVisor = mgr.GetPlayerState()->GetCurrentVisor();\n  CPlayerState::EPlayerVisor transVisor = mgr.GetPlayerState()->GetTransitioningVisor();\n  float transFactor = 0.f;\n  if (curVisor != CPlayerState::EPlayerVisor::Scan) {\n    if (transVisor == CPlayerState::EPlayerVisor::Scan)\n      transFactor = mgr.GetPlayerState()->GetVisorTransitionFactor();\n    else\n      transFactor = 1.f;\n  }\n\n  if (x2a4_visorMenu) {\n    float hudAlpha;\n    if (g_GameState->GameOptions().GetSwapBeamControls())\n      hudAlpha = transFactor;\n    else\n      hudAlpha = x2f0_visorBeamMenuAlpha;\n    x2a4_visorMenu->UpdateHudAlpha(hudAlpha);\n    x2a4_visorMenu->Update(dt, false);\n  }\n\n  if (x2a8_beamMenu) {\n    float hudAlpha;\n    if (g_GameState->GameOptions().GetSwapBeamControls())\n      hudAlpha = x2f0_visorBeamMenuAlpha;\n    else\n      hudAlpha = transFactor;\n    x2a8_beamMenu->UpdateHudAlpha(hudAlpha);\n    x2a8_beamMenu->Update(dt, false);\n  }\n\n  UpdateCameraDebugSettings();\n\n  if (x29c_decoIntf)\n    x29c_decoIntf->Update(dt, mgr);\n}\n\nvoid CSamusHud::DrawAttachedEnemyEffect(const CStateManager& mgr) {\n  const float drainTime = mgr.GetPlayer().GetEnergyDrain().GetEnergyDrainTime();\n  if (drainTime <= 0.f) {\n    return;\n  }\n\n  const float modPeriod = g_tweakGui->GetEnergyDrainModPeriod();\n  float alpha;\n  if (g_tweakGui->GetEnergyDrainSinusoidalPulse()) {\n    alpha = (std::sin(-0.25f * modPeriod + 2.f * M_PIF * drainTime / modPeriod) + 1.f) * 0.5f;\n  } else {\n    const float halfModPeriod = 0.5f * modPeriod;\n    const float tmp = std::fabs(std::fmod(drainTime, modPeriod));\n    if (tmp < halfModPeriod) {\n      alpha = tmp / halfModPeriod;\n    } else {\n      alpha = (modPeriod - tmp) / halfModPeriod;\n    }\n  }\n\n  zeus::CColor filterColor = g_tweakGuiColors->GetEnergyDrainFilterColor();\n  filterColor.a() *= alpha;\n  EFilterType filterType = g_tweakGui->GetEnergyDrainFilterAdditive() ? EFilterType::Add : EFilterType::Blend;\n  CCameraFilterPass::DrawFilter(filterType, EFilterShape::Fullscreen, filterColor, nullptr, 1.f);\n}\n\nvoid CSamusHud::Draw(const CStateManager& mgr, float alpha, CInGameGuiManager::EHelmetVisMode helmetVis, bool hudVis,\n                     bool targetingManager) {\n  if (x2bc_nextState == EHudState::None) {\n    return;\n  }\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CSamusHud::Draw\", zeus::skBlue);\n  x3a8_camFilter.Draw();\n  if (mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Unmorphed) {\n    DrawAttachedEnemyEffect(mgr);\n    x51c_camFilter2.Draw();\n    if (targetingManager) {\n      x8_targetingMgr.Draw(mgr, false);\n    }\n  }\n\n  if (helmetVis != CInGameGuiManager::EHelmetVisMode::ReducedUpdate &&\n      helmetVis < CInGameGuiManager::EHelmetVisMode::HelmetOnly) {\n    if (alpha < 1.f) {\n      CCameraFilterPass::DrawFilter(EFilterType::NoColor, EFilterShape::CookieCutterDepthRandomStatic, zeus::skWhite,\n                                    nullptr, 1.f - alpha);\n    }\n\n    if (x288_loadedSelectedHud) {\n      if (mgr.GetPlayer().GetDeathTime() > 0.f) {\n        if (mgr.GetPlayer().GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Unmorphed) {\n          const CGuiWidgetDrawParms parms(\n              x2c8_transT * zeus::clamp(0.f, 1.f - mgr.GetPlayer().GetDeathTime() / 6.f, 1.f), zeus::skZero3f);\n          x288_loadedSelectedHud->Draw(parms);\n        } else {\n          const CGuiWidgetDrawParms parms(x2c8_transT, zeus::skZero3f);\n          x288_loadedSelectedHud->Draw(parms);\n        }\n      } else {\n        const CGuiWidgetDrawParms parms(x2c8_transT, zeus::skZero3f);\n        x288_loadedSelectedHud->Draw(parms);\n      }\n    }\n\n    if (x274_loadedFrmeBaseHud) {\n      x274_loadedFrmeBaseHud->Draw(CGuiWidgetDrawParms::Default());\n    }\n  }\n\n  if (x29c_decoIntf && !x2cc_preLoadCountdown) {\n    x29c_decoIntf->Draw();\n  }\n\n  if (x2bc_nextState >= EHudState::Combat && x2bc_nextState <= EHudState::Scan) {\n    if (hudVis && helmetVis != CInGameGuiManager::EHelmetVisMode::ReducedUpdate &&\n        helmetVis < CInGameGuiManager::EHelmetVisMode::HelmetOnly) {\n      float t;\n      if (mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::Combat) {\n        t = mgr.GetPlayerState()->GetVisorTransitionFactor();\n      } else {\n        t = 0.f;\n      }\n      x2ac_radarIntf->Draw(mgr, t * alpha);\n    }\n    g_Renderer->SetDepthReadWrite(true, true);\n  }\n\n  // TODO timer stuff?\n}\n\nvoid CSamusHud::DrawHelmet(const CStateManager& mgr, float camYOff) {\n  if (!x264_loadedFrmeHelmet ||\n      mgr.GetPlayer().GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Unmorphed ||\n      x2bc_nextState == EHudState::Ball) {\n    return;\n  }\n\n  float t;\n  if (x2c4_activeTransState == ETransitionState::Transitioning && x2b8_curState == EHudState::Ball) {\n    t = x2c8_transT;\n  } else {\n    t = 1.f;\n  }\n\n  x264_loadedFrmeHelmet->Draw(CGuiWidgetDrawParms(t, zeus::CVector3f(0.f, 15.f * camYOff, 0.f)));\n}\n\nvoid CSamusHud::ProcessControllerInput(const CFinalInput& input) {\n  if (x29c_decoIntf)\n    x29c_decoIntf->ProcessInput(input);\n}\n\nvoid CSamusHud::UpdateStateTransition(float dt, const CStateManager& mgr) {\n  if (x2cc_preLoadCountdown == 0) {\n    EHudState desiredState = GetDesiredHudState(mgr);\n    if (desiredState != x2c0_setState) {\n      x2c0_setState = desiredState;\n      if (desiredState == EHudState::Ball || x2bc_nextState == EHudState::Ball)\n        x2c8_transT = FLT_EPSILON;\n      x2c4_activeTransState = ETransitionState::Countdown;\n    }\n  }\n\n  switch (x2c4_activeTransState) {\n  case ETransitionState::Countdown:\n    if (x2cc_preLoadCountdown == 0) {\n      x2c8_transT = std::max(x2c8_transT - 5.f * dt, 0.f);\n      if (x2c8_transT == 0.f) {\n        x2cc_preLoadCountdown = 2;\n        x288_loadedSelectedHud = nullptr;\n      }\n    } else {\n      x2cc_preLoadCountdown -= 1;\n      if (x2cc_preLoadCountdown == 0) {\n        UninitializeFrameGlueMutable();\n        x278_selectedHud = TLockedToken<CGuiFrame>();\n        switch (x2c0_setState) {\n        case EHudState::Thermal:\n          x278_selectedHud = g_SimplePool->GetObj(\"FRME_ThermalHud\");\n          break;\n        case EHudState::Combat:\n          x278_selectedHud = g_SimplePool->GetObj(\"FRME_CombatHud\");\n          break;\n        case EHudState::Scan:\n          x278_selectedHud = g_SimplePool->GetObj(\"FRME_ScanHud\");\n          break;\n        case EHudState::XRay:\n          x278_selectedHud = g_SimplePool->GetObj(\"FRME_XRayHudNew\");\n          break;\n        case EHudState::Ball:\n          x278_selectedHud = g_SimplePool->GetObj(\"FRME_BallHud\");\n          break;\n        default:\n          break;\n        }\n        x2c4_activeTransState = ETransitionState::Loading;\n      }\n    }\n    if (x2c4_activeTransState != ETransitionState::Loading)\n      return;\n    [[fallthrough]];\n  case ETransitionState::Loading:\n    if (x278_selectedHud) {\n      if (!x278_selectedHud.IsLoaded() || !x278_selectedHud->GetIsFinishedLoading())\n        return;\n      x288_loadedSelectedHud = x278_selectedHud.GetObj();\n      x288_loadedSelectedHud->Reset();\n      x288_loadedSelectedHud->SetMaxAspect(1.78f);\n      x2b8_curState = x2bc_nextState;\n      x2bc_nextState = x2c0_setState;\n      InitializeFrameGlueMutable(mgr);\n      x2c4_activeTransState = ETransitionState::Transitioning;\n      UpdateCameraDebugSettings();\n    } else {\n      x2b8_curState = x2bc_nextState;\n      x2bc_nextState = x2c0_setState;\n      x2c4_activeTransState = ETransitionState::NotTransitioning;\n    }\n    break;\n  case ETransitionState::Transitioning:\n    x2c8_transT = std::min(1.f, 5.f * dt + x2c8_transT);\n    if (x2c8_transT == 1.f)\n      x2c4_activeTransState = ETransitionState::NotTransitioning;\n    break;\n  default:\n    break;\n  }\n}\n\nbool CSamusHud::CheckLoadComplete(CStateManager& stateMgr) {\n  switch (x4_loadPhase) {\n  case ELoadPhase::Zero:\n    if (!x8_targetingMgr.CheckLoadComplete())\n      return false;\n    x4_loadPhase = ELoadPhase::One;\n    [[fallthrough]];\n  case ELoadPhase::One:\n    UpdateStateTransition(1.f, stateMgr);\n    if (x2bc_nextState != x2c0_setState)\n      return false;\n    x4_loadPhase = ELoadPhase::Two;\n    [[fallthrough]];\n  case ELoadPhase::Two:\n    if (!x264_loadedFrmeHelmet->GetIsFinishedLoading())\n      return false;\n    if (!x274_loadedFrmeBaseHud->GetIsFinishedLoading())\n      return false;\n    x4_loadPhase = ELoadPhase::Three;\n    [[fallthrough]];\n  case ELoadPhase::Three:\n    return true;\n  default:\n    break;\n  }\n\n  return false;\n}\n\nvoid CSamusHud::OnNewInGameGuiState(EInGameGuiState state, CStateManager& stateMgr) {\n  // Empty\n}\n\nvoid CSamusHud::RefreshHudOptions() {\n  if (x29c_decoIntf)\n    x29c_decoIntf->UpdateHudAlpha();\n  if (x2a0_helmetIntf)\n    x2a0_helmetIntf->UpdateHelmetAlpha();\n}\n\nvoid CSamusHud::Touch() {\n  if (x264_loadedFrmeHelmet)\n    x264_loadedFrmeHelmet->Touch();\n  if (x274_loadedFrmeBaseHud)\n    x274_loadedFrmeBaseHud->Touch();\n  if (x288_loadedSelectedHud)\n    x288_loadedSelectedHud->Touch();\n}\n\nzeus::CTransform CSamusHud::BuildFinalCameraTransform(const zeus::CQuaternion& rot, const zeus::CVector3f& pos,\n                                                      const zeus::CVector3f& camPos) {\n  zeus::CQuaternion invRot = rot.inverse();\n  return zeus::CTransform(invRot, invRot.transform(camPos - pos) + pos);\n}\n\nvoid CSamusHud::SetMessage(std::u16string_view text, const CHUDMemoParms& info) {\n  bool isWidgetVisible = x598_base_basewidget_message->GetIsVisible();\n  if (!isWidgetVisible || info.IsHintMemo()) {\n    if (info.IsFadeOutOnly()) {\n      x558_messageTextTime = 1.f;\n      if (!info.IsHintMemo() || !isWidgetVisible)\n        return;\n      CSfxManager::SfxStart(SFXui_hide_hint_memo, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n      return;\n    }\n    x598_base_basewidget_message->SetColor(zeus::skWhite);\n    x598_base_basewidget_message->SetVisibility(false, ETraversalMode::Children);\n    CGuiWidget* pane = info.IsHintMemo() ? x598_base_basewidget_message : x59c_base_textpane_message;\n    pane->SetVisibility(true, ETraversalMode::Children);\n    x59c_base_textpane_message->TextSupport().SetTypeWriteEffectOptions(true, 0.1f, 40.f);\n    if (info.IsClearMemoWindow()) {\n      x55c_lastSfxChars = 0.f;\n      x59c_base_textpane_message->TextSupport().SetCurTime(0.f);\n      x59c_base_textpane_message->TextSupport().SetText(text);\n    } else if (x59c_base_textpane_message->TextSupport().GetString().empty()) {\n      x55c_lastSfxChars = 0.f;\n      x59c_base_textpane_message->TextSupport().AddText(text);\n    } else {\n      x59c_base_textpane_message->TextSupport().AddText(std::u16string(u\"\\n\") + text.data());\n    }\n\n    x59c_base_textpane_message->SetColor(zeus::skWhite);\n    x598_base_basewidget_message->SetColor(zeus::skWhite);\n    x558_messageTextTime = info.GetDisplayTime();\n    if (info.IsHintMemo()) {\n      if (!isWidgetVisible) {\n        x584_abuttonPulse = 0.f;\n        x560_messageTextScale = 0.f;\n        CSfxManager::SfxStart(SFXui_show_hint_memo, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n      }\n    } else {\n      x598_base_basewidget_message->SetLocalTransform(x598_base_basewidget_message->GetTransform());\n    }\n  }\n}\n\nvoid CSamusHud::InternalDeferHintMemo(CAssetId strg, u32 strgIdx, const CHUDMemoParms& info) {\n  x548_hudMemoParms = info;\n  x550_hudMemoString = g_SimplePool->GetObj(SObjectTag{FOURCC('STRG'), strg});\n  x554_hudMemoIdx = strgIdx;\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/CSamusHud.hpp",
    "content": "#pragma once\n\n#include <array>\n#include <memory>\n#include <string_view>\n#include <vector>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Camera/CCameraFilter.hpp\"\n#include \"Runtime/GuiSys/CHudBallInterface.hpp\"\n#include \"Runtime/GuiSys/CHudBossEnergyInterface.hpp\"\n#include \"Runtime/GuiSys/CHudDecoInterface.hpp\"\n#include \"Runtime/GuiSys/CHudEnergyInterface.hpp\"\n#include \"Runtime/GuiSys/CHudFreeLookInterface.hpp\"\n#include \"Runtime/GuiSys/CHudHelmetInterface.hpp\"\n#include \"Runtime/GuiSys/CHudMissileInterface.hpp\"\n#include \"Runtime/GuiSys/CHudRadarInterface.hpp\"\n#include \"Runtime/GuiSys/CHudThreatInterface.hpp\"\n#include \"Runtime/GuiSys/CHudVisorBeamMenu.hpp\"\n#include \"Runtime/GuiSys/CTargetingManager.hpp\"\n#include \"Runtime/MP1/CInGameGuiManager.hpp\"\n#include \"Runtime/World/CHUDMemoParms.hpp\"\n\n#include <zeus/CColor.hpp>\n#include <zeus/CMatrix3f.hpp>\n#include <zeus/CQuaternion.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CGuiFrame;\nclass CGuiLight;\nclass CStateManager;\n\nenum class EHudState { Combat, XRay, Thermal, Scan, Ball, None };\n\nnamespace MP1 {\n\nclass CSamusHud {\n  enum class ELoadPhase { Zero, One, Two, Three };\n\n  enum class ETransitionState { NotTransitioning, Countdown, Loading, Transitioning };\n\n  struct SCachedHudLight {\n    zeus::CVector3f x0_pos;\n    zeus::CColor xc_color;\n    float x10_distC = 0.f;\n    float x14_distL = 0.f;\n    float x18_distQ = 0.f;\n    float x1c_fader = 0.f;\n    SCachedHudLight(const zeus::CVector3f& pos, const zeus::CColor& color, float f1, float f2, float f3, float f4)\n    : x0_pos(pos), xc_color(color), x10_distC(f1), x14_distL(f2), x18_distQ(f3), x1c_fader(f4) {}\n  };\n\n  struct SVideoBand {\n    CGuiModel* x0_videoband = nullptr;\n    float x4_randA = 0.f;\n    float x8_randB = 0.f;\n  };\n\n  struct SProfileInfo {\n    u32 x0_ = 0;\n    u32 x4_ = 0;\n    u64 x8_profUsec = 0;\n  };\n\n  friend class CInGameGuiManager;\n  ELoadPhase x4_loadPhase = ELoadPhase::Zero;\n  CTargetingManager x8_targetingMgr;\n  TLockedToken<CGuiFrame> x258_frmeHelmet;\n  CGuiFrame* x264_loadedFrmeHelmet = nullptr;\n  TLockedToken<CGuiFrame> x268_frmeBaseHud;\n  CGuiFrame* x274_loadedFrmeBaseHud = nullptr;\n  TLockedToken<CGuiFrame> x278_selectedHud; // used to be optional\n  CGuiFrame* x288_loadedSelectedHud = nullptr;\n  std::unique_ptr<CHudEnergyInterface> x28c_energyIntf;\n  std::unique_ptr<CHudThreatInterface> x290_threatIntf;\n  std::unique_ptr<CHudMissileInterface> x294_missileIntf;\n  std::unique_ptr<IFreeLookInterface> x298_freeLookIntf;\n  std::unique_ptr<IHudDecoInterface> x29c_decoIntf;\n  std::unique_ptr<CHudHelmetInterface> x2a0_helmetIntf;\n  std::unique_ptr<CHudVisorBeamMenu> x2a4_visorMenu;\n  std::unique_ptr<CHudVisorBeamMenu> x2a8_beamMenu;\n  std::unique_ptr<CHudRadarInterface> x2ac_radarIntf;\n  std::unique_ptr<CHudBallInterface> x2b0_ballIntf;\n  std::unique_ptr<CHudBossEnergyInterface> x2b4_bossEnergyIntf;\n  EHudState x2b8_curState = EHudState::None;\n  EHudState x2bc_nextState = EHudState::None;\n  EHudState x2c0_setState = EHudState::None;\n  ETransitionState x2c4_activeTransState = ETransitionState::NotTransitioning;\n  float x2c8_transT = 1.f;\n  u32 x2cc_preLoadCountdown = 0;\n  float x2d0_playerHealth = 0.f;\n  u32 x2d4_totalEnergyTanks = 0;\n  u32 x2d8_missileAmount = 0;\n  u32 x2dc_missileCapacity = 0;\n  bool x2e0_24_inFreeLook : 1 = false;\n  bool x2e0_25_lookControlHeld : 1 = false;\n  bool x2e0_26_latestFirstPerson : 1 = true;\n  bool x2e0_27_energyLow : 1;\n  u32 x2e4_ = 0;\n  u32 x2e8_ = 0;\n  CPlayerGun::EMissileMode x2ec_missileMode = CPlayerGun::EMissileMode::Inactive;\n  float x2f0_visorBeamMenuAlpha = 1.f;\n  zeus::CVector3f x2f8_fpCamDir;\n  zeus::CVector3f x304_basewidgetIdlePos;\n  zeus::CVector3f x310_cameraPos;\n  zeus::CQuaternion x31c_hudLag;\n  zeus::CQuaternion x32c_invHudLag;\n  std::unique_ptr<CActorLights> x33c_lights;\n  rstl::reserved_vector<SCachedHudLight, 3> x340_hudLights;\n  CSfxHandle x3a4_damageSfx;\n  CCameraFilterPass x3a8_camFilter;\n  CGuiLight* x3d4_damageLight = nullptr;\n  std::vector<zeus::CTransform> x3d8_lightTransforms;\n  float x3e8_damageTIme = 0.f;\n  float x3ec_damageLightPulser = 0.f;\n  float x3f0_damageFilterAmtInit = 1.f;\n  float x3f4_damageFilterAmt = 0.f;\n  float x3f8_damageFilterAmtGain = 0.f;\n  float x3fc_hudDamagePracticalsInit = 0.f;\n  float x400_hudDamagePracticals = 0.f;\n  float x404_hudDamagePracticalsGain = 0.f;\n  zeus::CVector3f x408_damagerToPlayerNorm;\n  float x414_decoShakeTranslateAmt = 0.f;\n  float x418_decoShakeTranslateAmtVel = 0.f;\n  zeus::CVector3f x41c_decoShakeTranslate;\n  zeus::CMatrix3f x428_decoShakeRotate;\n  zeus::CQuaternion x44c_hudLagShakeRot;\n  float x45c_decoShakeAmtInit = 0.f;\n  float x460_decoShakeAmt = 0.f;\n  float x464_decoShakeAmtGain = 0.f;\n  rstl::reserved_vector<zeus::CTransform, 3> x46c_;\n  zeus::CVector2f x500_viewportScale = {1.f, 1.f};\n  CSfxHandle x508_staticSfxHi;\n  CSfxHandle x50c_staticSfxLo;\n  float x510_staticInterp = 0.f;\n  float x514_staticCycleTimerHi = 0.f;\n  float x518_staticCycleTimerLo = 0.f;\n  CCameraFilterPass x51c_camFilter2;\n  CHUDMemoParms x548_hudMemoParms;\n  TLockedToken<CStringTable> x550_hudMemoString;\n  u32 x554_hudMemoIdx = 0;\n  float x558_messageTextTime = 0.f;\n  float x55c_lastSfxChars = 0.f;\n  float x560_messageTextScale = 0.f;\n  CSfxHandle x564_freeLookSfx;\n  zeus::CVector3f x568_fpCamDir;\n  float x574_lookDeltaDot = 1.f;\n  float x578_freeLookSfxCycleTimer = 0.f;\n  float x57c_energyLowTimer = 0.f;\n  float x580_energyLowPulse = 0.f;\n  float x584_abuttonPulse = 0.f;\n  CGuiWidget* x588_base_basewidget_pivot;\n  CGuiWidget* x58c_helmet_BaseWidget_Pivot;\n  CGuiModel* x590_base_Model_AutoMapper;\n  CGuiTextPane* x594_base_textpane_counter;\n  CGuiWidget* x598_base_basewidget_message;\n  CGuiTextPane* x59c_base_textpane_message;\n  CGuiModel* x5a0_base_model_abutton;\n  rstl::reserved_vector<SVideoBand, 4> x5a4_videoBands;\n  rstl::reserved_vector<CGuiLight*, 4> x5d8_guiLights;\n  std::array<float, 16> x5ec_camFovTweaks;\n  std::array<float, 64> x62c_camYTweaks;\n  std::array<float, 32> x72c_camZTweaks;\n  rstl::reserved_vector<SProfileInfo, 15> x7ac_;\n\n  static CSamusHud* g_SamusHud;\n  static rstl::reserved_vector<bool, 4> BuildPlayerHasVisors(const CStateManager& mgr);\n  static rstl::reserved_vector<bool, 4> BuildPlayerHasBeams(const CStateManager& mgr);\n  void InitializeFrameGluePermanent(const CStateManager& mgr);\n  void InitializeFrameGlueMutable(const CStateManager& mgr);\n  void UninitializeFrameGlueMutable();\n  void InitializeDamageLight();\n  void UpdateEnergy(float dt, const CStateManager& mgr, bool init);\n  void UpdateFreeLook(float dt, const CStateManager& mgr);\n  void UpdateMissile(float dt, const CStateManager& mgr, bool init);\n  void UpdateVideoBands(float dt, const CStateManager& mgr);\n  void UpdateBallMode(const CStateManager& mgr, bool init);\n  void UpdateThreatAssessment(float dt, const CStateManager& mgr);\n  void UpdateVisorAndBeamMenus(float dt, const CStateManager& mgr);\n  void UpdateCameraDebugSettings();\n  void UpdateEnergyLow(float dt, const CStateManager& mgr);\n  void ApplyClassicLag(const zeus::CUnitVector3f& lookDir, zeus::CQuaternion& rot, const CStateManager& mgr, float dt,\n                       bool invert);\n  void UpdateHudLag(float dt, const CStateManager& mgr);\n  bool IsCachedLightInAreaLights(const SCachedHudLight& light, const CActorLights& areaLights) const;\n  bool IsAreaLightInCachedLights(const CLight& light) const;\n  int FindEmptyHudLightSlot(const CLight& light) const;\n  zeus::CColor GetVisorHudLightColor(const zeus::CColor& color, const CStateManager& mgr) const;\n  void UpdateHudDynamicLights(float dt, const CStateManager& mgr);\n  void UpdateHudDamage(float dt, const CStateManager& mgr, Tweaks::ITweakGui::EHelmetVisMode helmetVis);\n  void UpdateStaticSfx(CSfxHandle& handle, float& cycleTimer, u16 sfxId, float dt, float oldStaticInterp,\n                       float staticThreshold);\n  void UpdateStaticInterference(float dt, const CStateManager& mgr);\n  int GetRelativeDirection(const zeus::CVector3f& position, const CStateManager& mgr);\n  void ShowDamage(const zeus::CVector3f& position, float dam, float prevDam, const CStateManager& mgr);\n  void EnterFirstPerson(const CStateManager& mgr);\n  void LeaveFirstPerson(const CStateManager& mgr);\n  void DrawAttachedEnemyEffect(const CStateManager& mgr);\n  static EHudState GetDesiredHudState(const CStateManager& mgr);\n\npublic:\n  explicit CSamusHud(CStateManager& stateMgr);\n  ~CSamusHud();\n  void Update(float dt, const CStateManager& mgr, CInGameGuiManager::EHelmetVisMode helmetVis, bool hudVis,\n              bool targetingManager);\n  void Draw(const CStateManager& mgr, float alpha, CInGameGuiManager::EHelmetVisMode helmetVis, bool hudVis,\n            bool targetingManager);\n  void DrawHelmet(const CStateManager& mgr, float camYOff);\n  void ProcessControllerInput(const CFinalInput& input);\n  void UpdateStateTransition(float time, const CStateManager& mgr);\n  bool CheckLoadComplete(CStateManager& stateMgr);\n  void OnNewInGameGuiState(EInGameGuiState state, CStateManager& stateMgr);\n  void RefreshHudOptions();\n  void Touch();\n  CTargetingManager& GetTargetingManager() { return x8_targetingMgr; }\n  const zeus::CVector2f& GetViewportScale() const { return x500_viewportScale; }\n  static zeus::CTransform BuildFinalCameraTransform(const zeus::CQuaternion& rot, const zeus::CVector3f& pos,\n                                                    const zeus::CVector3f& camPos);\n  static void DisplayHudMemo(std::u16string_view text, const CHUDMemoParms& info) {\n    if (g_SamusHud)\n      g_SamusHud->InternalDisplayHudMemo(text, info);\n  }\n  void InternalDisplayHudMemo(std::u16string_view text, const CHUDMemoParms& info) { SetMessage(text, info); }\n  void SetMessage(std::u16string_view text, const CHUDMemoParms& info);\n  static void DeferHintMemo(CAssetId strg, u32 strgIdx, const CHUDMemoParms& info) {\n    if (g_SamusHud)\n      g_SamusHud->InternalDeferHintMemo(strg, strgIdx, info);\n  }\n  void InternalDeferHintMemo(CAssetId strg, u32 strgIdx, const CHUDMemoParms& info);\n  CGuiFrame* GetBaseHudFrame() const { return x274_loadedFrmeBaseHud; }\n};\n\n} // namespace MP1\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/MP1/CSaveGameScreen.cpp",
    "content": "#include \"Runtime/MP1/CSaveGameScreen.hpp\"\n\n#include \"Runtime/Audio/CSfxManager.hpp\"\n#include \"Runtime/CMemoryCardSys.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/GuiSys/CGuiFrame.hpp\"\n#include \"Runtime/GuiSys/CGuiTableGroup.hpp\"\n#include \"Runtime/GuiSys/CGuiTextPane.hpp\"\n#include \"Runtime/GuiSys/CGuiWidgetDrawParms.hpp\"\n#include \"Runtime/MP1/MP1.hpp\"\n\nnamespace metaforce::MP1 {\n\nusing EState = CMemoryCardDriver::EState;\nusing EError = CMemoryCardDriver::EError;\n\nvoid CSaveGameScreen::ResetCardDriver() {\n  x92_savingDisabled = false;\n  x6c_cardDriver.reset();\n  bool importState = (x0_saveCtx == ESaveContext::FrontEnd && !x90_needsDriverReset);\n  x6c_cardDriver = ConstructCardDriver(importState);\n  x6c_cardDriver->StartCardProbe();\n  x10_uiType = EUIType::Empty;\n  SetUIText();\n}\n\nCIOWin::EMessageReturn CSaveGameScreen::Update(float dt) {\n  if (!PumpLoad()) {\n    return CIOWin::EMessageReturn::Normal;\n  }\n\n  x50_loadedFrame->Update(dt);\n  x6c_cardDriver->Update();\n\n  if (x6c_cardDriver->x10_state == EState::DriverClosed) {\n    if (x90_needsDriverReset) {\n      ResetCardDriver();\n      x90_needsDriverReset = false;\n    } else {\n      x80_iowRet = CIOWin::EMessageReturn::Exit;\n    }\n  } else if (x6c_cardDriver->x10_state == EState::CardCheckDone && x10_uiType != EUIType::NotOriginalCard) {\n    if (x6c_cardDriver->x28_cardSerial != x8_serial) {\n      if (x93_inGame) {\n        x10_uiType = EUIType::NotOriginalCard;\n        x91_uiTextDirty = true;\n      } else {\n        x8_serial = x6c_cardDriver->x28_cardSerial;\n        x6c_cardDriver->IndexFiles();\n      }\n    } else {\n      x6c_cardDriver->IndexFiles();\n    }\n  } else if (x6c_cardDriver->x10_state == EState::Ready) {\n    if (x90_needsDriverReset) {\n      x6c_cardDriver->StartFileCreateTransactional();\n    }\n  }\n\n  if (x80_iowRet != CIOWin::EMessageReturn::Normal) {\n    return x80_iowRet;\n  }\n\n  EUIType oldTp = x10_uiType;\n  x10_uiType = SelectUIType();\n  if (oldTp != x10_uiType || x91_uiTextDirty) {\n    SetUIText();\n  }\n\n  if (x6c_cardDriver->x10_state == EState::NoCard) {\n    auto res = CMemoryCardSys::CardProbe(kabufuda::ECardSlot::SlotA);\n    if (res.x0_error == CMemoryCardSys::ECardResult::READY ||\n        res.x0_error == CMemoryCardSys::ECardResult::WRONGDEVICE) {\n      ResetCardDriver();\n    }\n  } else if (x6c_cardDriver->x10_state == EState::CardFormatted) {\n    ResetCardDriver();\n  } else if (x6c_cardDriver->x10_state == EState::FileBad && x6c_cardDriver->x14_error == EError::FileMissing) {\n    x6c_cardDriver->StartFileCreate();\n  }\n\n  return CIOWin::EMessageReturn::Normal;\n}\n\nbool CSaveGameScreen::PumpLoad() {\n  if (x50_loadedFrame != nullptr) {\n    return true;\n  }\n  if (!x14_txtrSaveBanner.IsLoaded()) {\n    return false;\n  }\n  if (!x20_txtrSaveIcon0.IsLoaded()) {\n    return false;\n  }\n  if (!x2c_txtrSaveIcon1.IsLoaded()) {\n    return false;\n  }\n  if (!x38_strgMemoryCard.IsLoaded()) {\n    return false;\n  }\n  for (TLockedToken<CWorldSaveGameInfo>& savw : x70_saveWorlds) {\n    if (!savw.IsLoaded()) {\n      return false;\n    }\n  }\n  if (!x44_frmeGenericMenu.IsLoaded()) {\n    return false;\n  }\n\n  x50_loadedFrame = x44_frmeGenericMenu.GetObj();\n  x50_loadedFrame->SetAspectConstraint(1.33f);\n  x54_textpane_message = static_cast<CGuiTextPane*>(x50_loadedFrame->FindWidget(\"textpane_message\"));\n  x58_tablegroup_choices = static_cast<CGuiTableGroup*>(x50_loadedFrame->FindWidget(\"tablegroup_choices\"));\n  x5c_textpane_choice0 = static_cast<CGuiTextPane*>(x50_loadedFrame->FindWidget(\"textpane_choice0\"));\n  x60_textpane_choice1 = static_cast<CGuiTextPane*>(x50_loadedFrame->FindWidget(\"textpane_choice1\"));\n  x64_textpane_choice2 = static_cast<CGuiTextPane*>(x50_loadedFrame->FindWidget(\"textpane_choice2\"));\n  x68_textpane_choice3 = static_cast<CGuiTextPane*>(x50_loadedFrame->FindWidget(\"textpane_choice3\"));\n\n  x58_tablegroup_choices->SetMenuAdvanceCallback([this](CGuiTableGroup* caller) { DoAdvance(caller); });\n  x58_tablegroup_choices->SetMenuSelectionChangeCallback(\n      [this](CGuiTableGroup* caller, int oldSel) { DoSelectionChange(caller, oldSel); });\n\n  if (x0_saveCtx == ESaveContext::InGame) {\n    x6c_cardDriver->StartCardProbe();\n  }\n\n  x10_uiType = SelectUIType();\n  SetUIText();\n  return true;\n}\n\nCSaveGameScreen::EUIType CSaveGameScreen::SelectUIType() const {\n  if (x10_uiType == EUIType::CreateDolphinCardFailed) {\n    return x10_uiType;\n  }\n  if (x6c_cardDriver->x10_state == EState::NoCard) {\n    return EUIType::NoCardFound;\n  }\n\n  switch (x10_uiType) {\n  case EUIType::ProgressWillBeLost:\n  case EUIType::NotOriginalCard:\n  case EUIType::AllDataWillBeLost:\n    return x10_uiType;\n  default:\n    break;\n  }\n\n  if (CMemoryCardDriver::IsCardBusy(x6c_cardDriver->x10_state)) {\n    if (CMemoryCardDriver::IsCardWriting(x6c_cardDriver->x10_state)) {\n      return EUIType::BusyWriting;\n    }\n    return EUIType::BusyReading;\n  }\n\n  if (x6c_cardDriver->x10_state == EState::Ready) {\n    if (x6c_cardDriver->x14_error == CMemoryCardDriver::EError::CardStillFull) {\n      return EUIType::StillInsufficientSpace;\n    }\n    return EUIType::SaveReady;\n  }\n\n  if (x6c_cardDriver->x14_error == CMemoryCardDriver::EError::CardBroken) {\n    return EUIType::NeedsFormatBroken;\n  }\n\n  if (x6c_cardDriver->x14_error == CMemoryCardDriver::EError::CardWrongCharacterSet) {\n    return EUIType::NeedsFormatEncoding;\n  }\n\n  if (x6c_cardDriver->x14_error == CMemoryCardDriver::EError::CardWrongDevice) {\n    return EUIType::WrongDevice;\n  }\n\n  if (x6c_cardDriver->x14_error == CMemoryCardDriver::EError::CardFull) {\n    if (x6c_cardDriver->x10_state == EState::CardCheckFailed) {\n      return EUIType::InsufficientSpaceBadCheck;\n    }\n    return EUIType::InsufficientSpaceOKCheck;\n  }\n\n  if (x6c_cardDriver->x14_error == CMemoryCardDriver::EError::CardNon8KSectors) {\n    return EUIType::IncompatibleCard;\n  }\n\n  if (x6c_cardDriver->x14_error == CMemoryCardDriver::EError::FileCorrupted) {\n    return EUIType::SaveCorrupt;\n  }\n\n  if (x6c_cardDriver->x14_error == CMemoryCardDriver::EError::CardIOError) {\n    return EUIType::CardDamaged;\n  }\n\n  return EUIType::Empty;\n}\n\nvoid CSaveGameScreen::SetUIText() {\n  x91_uiTextDirty = false;\n\n  s32 msgA = -1;\n  s32 msgB = -1;\n  s32 opt0 = -1;\n  s32 opt1 = -1;\n  s32 opt2 = -1;\n\n  std::u16string msgAStr;\n  std::u16string msgBStr;\n  std::u16string opt0Str;\n  std::u16string opt1Str;\n  std::u16string opt2Str;\n  std::u16string opt3Str;\n\n  switch (x10_uiType) {\n  case EUIType::BusyReading:\n    msgB = 24; // Reading\n    break;\n  case EUIType::BusyWriting:\n    msgB = 25; // Writing\n    break;\n  case EUIType::NoCardFound:\n    if (g_Main->IsUSA() && !g_Main->IsTrilogy()) {\n      msgB = 0;  // No card found\n      opt0 = 17; // Continue without saving\n      opt1 = 18; // Retry\n      opt2 = -2;\n      opt2Str = u\"Create Dolphin Card\";\n    } else {\n      msgAStr = u\"This version of Metroid Prime\\nhas a currently unsupported save format.\\n\";\n      msgBStr = u\"&push;&main-color=$ff0000ff;Saving has been disabled.&pop;\\n\";\n      opt0 = -2;\n      opt0Str = u\"Press &image=SI,1.0,0.68,05AF9CAA; to proceed.\\n\";\n    }\n    break;\n  case EUIType::NeedsFormatBroken:\n    msgB = 1;  // Needs format (card broken)\n    opt0 = 17; // Continue without saving\n    opt1 = 18; // Retry\n    opt2 = 20; // Format\n    break;\n  case EUIType::NeedsFormatEncoding:\n    msgB = 2;  // Needs format (wrong char set)\n    opt0 = 17; // Continue without saving\n    opt1 = 18; // Retry\n    opt2 = 20; // Format\n    break;\n  case EUIType::CardDamaged:\n    msgB = 3;  // Damaged\n    opt0 = 17; // Continue without saving\n    opt1 = 18; // Retry\n    break;\n  case EUIType::WrongDevice:\n    msgB = 5;  // Invalid device\n    opt0 = 17; // Continue without saving\n    opt1 = 18; // Retry\n    break;\n  case EUIType::InsufficientSpaceOKCheck:\n    msgB = 6;  // Insufficient space (completely filled)\n    opt0 = 17; // Continue without saving\n    opt1 = 18; // Retry\n    opt2 = 19; // Manage memory card\n    break;\n  case EUIType::InsufficientSpaceBadCheck:\n    msgB = static_cast<int>(bool(x0_saveCtx)) + 9; // Insufficient space A or B\n    opt0 = 17;                                     // Continue without saving\n    opt1 = 18;                                     // Retry\n    opt2 = 19;                                     // Manage memory card\n    break;\n  case EUIType::IncompatibleCard:\n    msgB = 7;  // Incompatible card\n    opt0 = 17; // Continue without saving\n    opt1 = 18; // Retry\n    break;\n  case EUIType::SaveCorrupt:\n    msgB = 4;  // Save corrupt\n    opt0 = 22; // Delete corrupt file\n    opt1 = 17; // Continue without saving\n    opt2 = 18; // Retry\n    break;\n  case EUIType::StillInsufficientSpace:\n    if (x0_saveCtx == ESaveContext::InGame) {\n      msgB = 10; // Insufficient space B\n      opt0 = 17; // Continue without saving\n      opt1 = 18; // Retry\n      opt2 = 19; // Manage memory card\n    } else {\n      msgB = 9;  // Insufficient space A\n      opt0 = 17; // Continue without saving\n      opt1 = 18; // Retry\n      opt2 = 19; // Manage memory card\n    }\n    break;\n  case EUIType::ProgressWillBeLost:\n    msgA = 28; // Warning\n    msgB = 11; // Progress will be lost\n    opt0 = 21; // Cancel\n    opt1 = 16; // Continue\n    break;\n  case EUIType::NotOriginalCard:\n    msgA = 28;                                           // Warning\n    msgB = 12;                                           // Not the original card\n    opt0 = x0_saveCtx == ESaveContext::InGame ? 21 : 17; // Cancel : continue without saving\n    opt1 = 16;                                           // Continue\n    break;\n  case EUIType::AllDataWillBeLost:\n    msgA = 28; // Warning\n    msgB = 13; // All card data will be erased\n    opt0 = 16; // Continue\n    opt1 = 21; // Cancel\n    break;\n  case EUIType::SaveReady:\n    if (x0_saveCtx == ESaveContext::InGame) {\n      msgB = 8;  // Save progress?\n      opt0 = 14; // Yes\n      opt1 = 15; // No\n    }\n    break;\n  case EUIType::CreateDolphinCardFailed:\n    msgAStr = u\"Attempt to create Dolphin Card failed!\\n\";\n    msgBStr = u\"CVars have been reset to defaults.\\nMetaforce will use Dolphin's memory cards if available\\n\";\n    opt0 = -2;\n    opt0Str = u\"Press &image=SI,1.0,0.68,05AF9CAA; to proceed.\\n\";\n    break;\n  default:\n    break;\n  }\n\n  if (msgA > -1) {\n    msgAStr = x38_strgMemoryCard->GetString(msgA);\n  }\n  if (msgB > -1) {\n    msgBStr = x38_strgMemoryCard->GetString(msgB);\n  }\n  x54_textpane_message->TextSupport().SetText(msgAStr + msgBStr);\n\n  if (opt0 > -1) {\n    opt0Str = x38_strgMemoryCard->GetString(opt0);\n  }\n  x5c_textpane_choice0->TextSupport().SetText(opt0Str);\n\n  if (opt1 > -1) {\n    opt1Str = x38_strgMemoryCard->GetString(opt1);\n  }\n  x60_textpane_choice1->TextSupport().SetText(opt1Str);\n\n  if (opt2 > -1) {\n    opt2Str = x38_strgMemoryCard->GetString(opt2);\n  }\n  x64_textpane_choice2->TextSupport().SetText(opt2Str);\n\n  x68_textpane_choice3->TextSupport().SetText(opt3Str);\n\n  x5c_textpane_choice0->SetIsSelectable(opt0 != -1);\n  x60_textpane_choice1->SetIsSelectable(opt1 != -1);\n  x64_textpane_choice2->SetIsSelectable(opt2 != -1);\n  x68_textpane_choice3->SetIsSelectable(false);\n\n  x58_tablegroup_choices->SetIsActive(opt0 != -1 || opt1 != -1 || opt2 != -1);\n  x58_tablegroup_choices->SetUserSelection(0);\n  SetUIColors();\n}\n\nvoid CSaveGameScreen::SetUIColors() {\n  x58_tablegroup_choices->SetColors(zeus::skWhite, zeus::CColor{0.627450f, 0.627450f, 0.627450f, 0.784313f});\n}\n\nvoid CSaveGameScreen::Draw() const {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CSaveGameScreen::Draw\", zeus::skPurple);\n  if (x50_loadedFrame != nullptr) {\n    x50_loadedFrame->Draw(CGuiWidgetDrawParms::Default());\n  }\n}\n\nvoid CSaveGameScreen::ContinueWithoutSaving() {\n  x80_iowRet = CIOWin::EMessageReturn::RemoveIOWin;\n  g_GameState->SetCardSerial(0);\n}\n\nvoid CSaveGameScreen::DoAdvance([[maybe_unused]] CGuiTableGroup* caller) {\n  int userSel = x58_tablegroup_choices->GetUserSelection();\n  int sfx = -1;\n\n  switch (x10_uiType) {\n  case EUIType::NoCardFound:\n  case EUIType::CardDamaged:\n  case EUIType::WrongDevice:\n  case EUIType::IncompatibleCard:\n    if (userSel == 0) {\n      /* Continue without saving */\n      if (x0_saveCtx == ESaveContext::InGame) {\n        x80_iowRet = CIOWin::EMessageReturn::RemoveIOWinAndExit;\n      } else {\n        ContinueWithoutSaving();\n      }\n      sfx = x8c_navBackSfx;\n    } else if (userSel == 1) {\n      /* Retry */\n      ResetCardDriver();\n      sfx = x84_navConfirmSfx;\n    } else if (userSel == 2 && x10_uiType == EUIType::NoCardFound) {\n      /* Create Dolphin Card */\n      if (!CMemoryCardSys::CreateDolphinCard(kabufuda::ECardSlot::SlotA)) {\n        x10_uiType = EUIType::CreateDolphinCardFailed;\n        sfx = x8c_navBackSfx;\n        x91_uiTextDirty = true;\n        break;\n      }\n      ResetCardDriver();\n      sfx = x84_navConfirmSfx;\n    }\n    break;\n\n  case EUIType::NeedsFormatBroken:\n  case EUIType::NeedsFormatEncoding:\n    if (userSel == 0) {\n      /* Continue without saving */\n      if (x0_saveCtx == ESaveContext::InGame) {\n        x80_iowRet = CIOWin::EMessageReturn::RemoveIOWinAndExit;\n      } else {\n        ContinueWithoutSaving();\n      }\n      sfx = x8c_navBackSfx;\n    } else if (userSel == 1) {\n      /* Retry */\n      ResetCardDriver();\n      sfx = x84_navConfirmSfx;\n    } else if (userSel == 2) {\n      /* Format */\n      x10_uiType = EUIType::AllDataWillBeLost;\n      x91_uiTextDirty = true;\n      sfx = x84_navConfirmSfx;\n    }\n    break;\n\n  case EUIType::InsufficientSpaceBadCheck:\n  case EUIType::InsufficientSpaceOKCheck:\n    if (userSel == 0) {\n      /* Continue without saving */\n      if (x0_saveCtx == ESaveContext::InGame) {\n        x80_iowRet = CIOWin::EMessageReturn::RemoveIOWinAndExit;\n      } else {\n        ContinueWithoutSaving();\n      }\n      sfx = x8c_navBackSfx;\n    } else if (userSel == 1) {\n      /* Retry */\n      ResetCardDriver();\n      sfx = x84_navConfirmSfx;\n    } else if (userSel == 2) {\n      /* Manage memory card */\n      if (x0_saveCtx == ESaveContext::InGame) {\n        x10_uiType = EUIType::ProgressWillBeLost;\n        x91_uiTextDirty = true;\n        sfx = x84_navConfirmSfx;\n      } else {\n        static_cast<MP1::CMain*>(g_Main)->SetManageCard(true);\n      }\n    }\n    break;\n\n  case EUIType::SaveCorrupt:\n    if (userSel == 0) {\n      /* Delete corrupt file */\n      x6c_cardDriver->StartFileDeleteBad();\n      sfx = x84_navConfirmSfx;\n    } else if (userSel == 1) {\n      /* Continue without saving */\n      if (x0_saveCtx == ESaveContext::InGame) {\n        x80_iowRet = CIOWin::EMessageReturn::RemoveIOWinAndExit;\n      } else {\n        ContinueWithoutSaving();\n      }\n      sfx = x8c_navBackSfx;\n    } else if (userSel == 2) {\n      /* Retry */\n      ResetCardDriver();\n      sfx = x84_navConfirmSfx;\n    }\n    break;\n\n  case EUIType::StillInsufficientSpace:\n    if (x0_saveCtx == ESaveContext::InGame) {\n      if (userSel == 0) {\n        /* Continue without saving */\n        x80_iowRet = CIOWin::EMessageReturn::RemoveIOWinAndExit;\n        sfx = x8c_navBackSfx;\n      } else if (userSel == 1) {\n        /* Retry */\n        ResetCardDriver();\n        sfx = x84_navConfirmSfx;\n      } else if (userSel == 2) {\n        /* Manage memory card */\n        x10_uiType = EUIType::ProgressWillBeLost;\n        x91_uiTextDirty = true;\n        sfx = x84_navConfirmSfx;\n      }\n    } else {\n      if (userSel == 0) {\n        /* Continue without saving */\n        if (x93_inGame) {\n          x80_iowRet = CIOWin::EMessageReturn::RemoveIOWinAndExit;\n          sfx = x8c_navBackSfx;\n        } else {\n          x6c_cardDriver->ClearError();\n          x92_savingDisabled = true;\n          sfx = x84_navConfirmSfx;\n        }\n      } else if (userSel == 1) {\n        /* Retry */\n        ResetCardDriver();\n        sfx = x84_navConfirmSfx;\n      } else if (userSel == 2) {\n        /* Manage memory card */\n        static_cast<MP1::CMain*>(g_Main)->SetManageCard(true);\n      }\n    }\n    break;\n\n  case EUIType::ProgressWillBeLost:\n    if (userSel == 1) {\n      /* Continue */\n      static_cast<MP1::CMain*>(g_Main)->SetManageCard(true);\n    } else if (userSel == 0) {\n      /* Cancel */\n      x80_iowRet = CIOWin::EMessageReturn::RemoveIOWinAndExit;\n      sfx = x8c_navBackSfx;\n    }\n    break;\n\n  case EUIType::NotOriginalCard:\n    if (userSel == 1) {\n      /* Continue */\n      x8_serial = x6c_cardDriver->x28_cardSerial;\n      x10_uiType = EUIType::Empty;\n      x6c_cardDriver->IndexFiles();\n      sfx = x84_navConfirmSfx;\n    } else if (userSel == 0) {\n      /* Cancel */\n      x80_iowRet = CIOWin::EMessageReturn::RemoveIOWinAndExit;\n      sfx = x8c_navBackSfx;\n    }\n    break;\n\n  case EUIType::AllDataWillBeLost:\n    if (userSel == 0) {\n      /* Continue */\n      x6c_cardDriver->StartCardFormat();\n      x10_uiType = EUIType::Empty;\n      sfx = x84_navConfirmSfx;\n    } else if (userSel == 1) {\n      /* Cancel */\n      ResetCardDriver();\n      sfx = x8c_navBackSfx;\n    }\n    break;\n\n  case EUIType::SaveReady:\n    if (x0_saveCtx == ESaveContext::InGame) {\n      if (userSel == 0) {\n        /* Yes */\n        x6c_cardDriver->BuildExistingFileSlot(g_GameState->GetFileIdx());\n        x6c_cardDriver->StartFileCreateTransactional();\n        sfx = x84_navConfirmSfx;\n      } else if (userSel == 1) {\n        /* No */\n        x80_iowRet = CIOWin::EMessageReturn::RemoveIOWinAndExit;\n        sfx = x8c_navBackSfx;\n      }\n    }\n    break;\n  case EUIType::CreateDolphinCardFailed: {\n    if (userSel == 0) {\n      CMemoryCardSys::_ResetCVar(kabufuda::ECardSlot::SlotA);\n      CMemoryCardSys::ResolveDolphinCardPath(kabufuda::ECardSlot::SlotA);\n      ResetCardDriver();\n      x10_uiType = EUIType::Empty;\n      sfx = x84_navConfirmSfx;\n    }\n  } break;\n\n  default:\n    break;\n  }\n\n  if (sfx >= 0) {\n    CSfxManager::SfxStart(sfx, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n  }\n}\n\nvoid CSaveGameScreen::DoSelectionChange([[maybe_unused]] CGuiTableGroup* caller, [[maybe_unused]] int oldSel) {\n  SetUIColors();\n  CSfxManager::SfxStart(x88_navMoveSfx, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n}\n\nvoid CSaveGameScreen::ProcessUserInput(const CFinalInput& input) {\n  if (x50_loadedFrame != nullptr) {\n    x50_loadedFrame->ProcessUserInput(input);\n  }\n}\n\nvoid CSaveGameScreen::StartGame(int idx) {\n  const CGameState::GameFileStateInfo* info = x6c_cardDriver->GetGameFileStateInfo(idx);\n  x6c_cardDriver->ExportPersistentOptions();\n  x6c_cardDriver->BuildNewFileSlot(idx);\n  if (info == nullptr) {\n    x6c_cardDriver->StartFileCreateTransactional();\n  } else {\n    x80_iowRet = CIOWin::EMessageReturn::Exit;\n  }\n}\n\nvoid CSaveGameScreen::SaveNESState() {\n  if (!x92_savingDisabled) {\n    x90_needsDriverReset = true;\n    x8_serial = x6c_cardDriver->x28_cardSerial;\n    x6c_cardDriver->StartFileCreateTransactional();\n  }\n}\n\nvoid CSaveGameScreen::EraseGame(int idx) {\n  x6c_cardDriver->EraseFileSlot(idx);\n  x90_needsDriverReset = true;\n  x6c_cardDriver->StartFileCreateTransactional();\n}\n\nconst CGameState::GameFileStateInfo* CSaveGameScreen::GetGameData(int idx) const {\n  return x6c_cardDriver->GetGameFileStateInfo(idx);\n}\n\nCSaveGameScreen::CSaveGameScreen(ESaveContext saveCtx, u64 serial)\n: x0_saveCtx(saveCtx), x8_serial(serial) {\n  x14_txtrSaveBanner = g_SimplePool->GetObj(\"TXTR_SaveBanner\");\n  x20_txtrSaveIcon0 = g_SimplePool->GetObj(\"TXTR_SaveIcon0\");\n  x2c_txtrSaveIcon1 = g_SimplePool->GetObj(\"TXTR_SaveIcon1\");\n  x38_strgMemoryCard = g_SimplePool->GetObj(\"STRG_MemoryCard\");\n  x44_frmeGenericMenu = g_SimplePool->GetObj(\"FRME_GenericMenu\");\n\n  x6c_cardDriver = ConstructCardDriver(x0_saveCtx == ESaveContext::FrontEnd);\n\n  if (saveCtx == ESaveContext::InGame) {\n    x84_navConfirmSfx = SFXui_advance;\n    x88_navMoveSfx = SFXui_table_selection_change;\n    x8c_navBackSfx = SFXui_table_change_mode;\n  }\n  x93_inGame = bool(saveCtx);\n\n  x70_saveWorlds.reserve(g_MemoryCardSys->GetMemoryWorlds().size());\n  for (const std::pair<CAssetId, CSaveWorldMemory>& wld : g_MemoryCardSys->GetMemoryWorlds()) {\n    x70_saveWorlds.emplace_back(g_SimplePool->GetObj(SObjectTag{FOURCC('SAVW'), wld.second.GetSaveWorldAssetId()}));\n  }\n}\n\nstd::unique_ptr<CMemoryCardDriver> CSaveGameScreen::ConstructCardDriver(bool importPersistent) {\n  return std::make_unique<CMemoryCardDriver>(kabufuda::ECardSlot::SlotA,\n                                             g_ResFactory->GetResourceIdByName(\"TXTR_SaveBanner\")->id,\n                                             g_ResFactory->GetResourceIdByName(\"TXTR_SaveIcon0\")->id,\n                                             g_ResFactory->GetResourceIdByName(\"TXTR_SaveIcon1\")->id, importPersistent);\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/CSaveGameScreen.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <vector>\n\n#include \"Runtime/CIOWin.hpp\"\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/GuiSys/CGuiFrame.hpp\"\n#include \"Runtime/MP1/CMemoryCardDriver.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n\nnamespace metaforce {\nclass CGuiTableGroup;\nclass CGuiTextPane;\nclass CWorldSaveGameInfo;\nclass CStringTable;\nclass CTexture;\nstruct CFinalInput;\n\nnamespace MP1 {\n\nenum class ESaveContext { FrontEnd, InGame };\n\nclass CSaveGameScreen {\npublic:\n  enum class EUIType {\n    Empty = 0,\n    BusyReading = 1,\n    BusyWriting = 2,\n    NoCardFound = 3,\n    NeedsFormatBroken = 4,\n    NeedsFormatEncoding = 5,\n    CardDamaged = 6,\n    WrongDevice = 7,\n    InsufficientSpaceBadCheck = 8,\n    InsufficientSpaceOKCheck = 9,\n    IncompatibleCard = 10,\n    SaveCorrupt = 11,\n    StillInsufficientSpace = 12,\n    ProgressWillBeLost = 13,\n    NotOriginalCard = 14,\n    AllDataWillBeLost = 15,\n    SaveReady = 16,\n    // Metaforce Addition\n    CreateDolphinCardFailed\n  };\n\n  bool IsHiddenFromFrontEnd() const {\n    switch (x10_uiType) {\n    case EUIType::SaveReady:\n    case EUIType::Empty:\n    case EUIType::BusyReading:\n    case EUIType::BusyWriting:\n      return false;\n    default:\n      return true;\n    }\n  }\n\nprivate:\n  ESaveContext x0_saveCtx;\n  u64 x8_serial;\n  EUIType x10_uiType = EUIType::Empty;\n  TLockedToken<CTexture> x14_txtrSaveBanner;\n  TLockedToken<CTexture> x20_txtrSaveIcon0;\n  TLockedToken<CTexture> x2c_txtrSaveIcon1;\n  TLockedToken<CStringTable> x38_strgMemoryCard;\n  TLockedToken<CGuiFrame> x44_frmeGenericMenu;\n  CGuiFrame* x50_loadedFrame = nullptr;\n  CGuiTextPane* x54_textpane_message{};\n  CGuiTableGroup* x58_tablegroup_choices{};\n  CGuiTextPane* x5c_textpane_choice0{};\n  CGuiTextPane* x60_textpane_choice1{};\n  CGuiTextPane* x64_textpane_choice2{};\n  CGuiTextPane* x68_textpane_choice3{};\n  std::unique_ptr<CMemoryCardDriver> x6c_cardDriver;\n  std::vector<TLockedToken<CWorldSaveGameInfo>> x70_saveWorlds;\n  CIOWin::EMessageReturn x80_iowRet = CIOWin::EMessageReturn::Normal;\n  u32 x84_navConfirmSfx = SFXui_frontend_save_confirm;\n  u32 x88_navMoveSfx = SFXui_frontend_save_move;\n  u32 x8c_navBackSfx = SFXui_frontend_save_back;\n  bool x90_needsDriverReset = false;\n  bool x91_uiTextDirty = false;\n  bool x92_savingDisabled = false;\n  bool x93_inGame;\n\n  void ContinueWithoutSaving();\n\npublic:\n  static std::unique_ptr<CMemoryCardDriver> ConstructCardDriver(bool inGame);\n  void ResetCardDriver();\n  CIOWin::EMessageReturn Update(float dt);\n  void SetInGame(bool v) { x93_inGame = v; }\n  bool PumpLoad();\n  [[nodiscard]] EUIType SelectUIType() const;\n  void SetUIText();\n  void SetUIColors();\n  void Draw() const;\n\n  void DoAdvance(CGuiTableGroup* caller);\n  void DoSelectionChange(CGuiTableGroup* caller, int oldSel);\n\n  void ProcessUserInput(const CFinalInput& input);\n  void StartGame(int idx);\n  void SaveNESState();\n  void EraseGame(int idx);\n  [[nodiscard]] const CGameState::GameFileStateInfo* GetGameData(int idx) const;\n  [[nodiscard]] EUIType GetUIType() const { return x10_uiType; }\n  [[nodiscard]] bool IsSavingDisabled() const { return x92_savingDisabled; }\n  explicit CSaveGameScreen(ESaveContext saveCtx, u64 serial);\n};\n\n} // namespace MP1\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/MP1/CSlideShow.cpp",
    "content": "#include \"Runtime/MP1/CSlideShow.hpp\"\n\n#include <algorithm>\n\n#include \"Runtime/CArchitectureMessage.hpp\"\n#include \"Runtime/CGameState.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\nnamespace metaforce {\nnamespace {\nbool AreAllDepsLoaded(const std::vector<TLockedToken<CDependencyGroup>>& deps) {\n  return std::all_of(deps.cbegin(), deps.cend(), [](const auto& dep) { return dep.IsLoaded(); });\n}\n} // Anonymous namespace\n\nCSlideShow::CSlideShow() : CIOWin(\"SlideShow\"), x130_(g_tweakSlideShow->GetX54()) {\n  const SObjectTag* font = g_ResFactory->GetResourceIdByName(g_tweakSlideShow->GetFont());\n  if (font) {\n    CGuiTextProperties propsA(false, true, EJustification::Center, EVerticalJustification::Bottom);\n    xc4_textA = std::make_unique<CGuiTextSupport>(font->id, propsA, g_tweakSlideShow->GetFontColor(),\n                                                  g_tweakSlideShow->GetOutlineColor(), zeus::skWhite, 640, 480,\n                                                  g_SimplePool, CGuiWidget::EGuiModelDrawFlags::Alpha);\n\n    CGuiTextProperties propsB(false, true, EJustification::Right, EVerticalJustification::Bottom);\n    xc8_textB = std::make_unique<CGuiTextSupport>(font->id, propsB, g_tweakSlideShow->GetFontColor(),\n                                                  g_tweakSlideShow->GetOutlineColor(), zeus::skWhite, 640, 480,\n                                                  g_SimplePool, CGuiWidget::EGuiModelDrawFlags::Alpha);\n  }\n\n  xf8_stickTextures.reserve(18);\n  x108_buttonTextures.reserve(8);\n  SObjectTag txtrTag(FOURCC('TXTR'), CAssetId());\n  for (const auto& lStickId : g_tweakPlayerRes->x24_lStick) {\n    txtrTag.id = lStickId;\n    xf8_stickTextures.emplace_back(g_SimplePool->GetObj(txtrTag));\n  }\n  for (const auto& cStickId : g_tweakPlayerRes->x4c_cStick) {\n    txtrTag.id = cStickId;\n    xf8_stickTextures.emplace_back(g_SimplePool->GetObj(txtrTag));\n  }\n  for (const auto& lTriggerId : g_tweakPlayerRes->x74_lTrigger) {\n    txtrTag.id = lTriggerId;\n    xf8_stickTextures.emplace_back(g_SimplePool->GetObj(txtrTag));\n  }\n  for (const auto& rTriggerId : g_tweakPlayerRes->x80_rTrigger) {\n    txtrTag.id = rTriggerId;\n    xf8_stickTextures.emplace_back(g_SimplePool->GetObj(txtrTag));\n  }\n  for (const auto& bButtonId : g_tweakPlayerRes->xa4_bButton) {\n    txtrTag.id = bButtonId;\n    xf8_stickTextures.emplace_back(g_SimplePool->GetObj(txtrTag));\n  }\n  for (const auto& yButtonId : g_tweakPlayerRes->xbc_yButton) {\n    txtrTag.id = yButtonId;\n    xf8_stickTextures.emplace_back(g_SimplePool->GetObj(txtrTag));\n  }\n}\n\nbool CSlideShow::LoadTXTRDep(std::string_view name) {\n  const SObjectTag* dgrpTag = g_ResFactory->GetResourceIdByName(name);\n  if (dgrpTag && dgrpTag->type == FOURCC('DGRP')) {\n    x18_galleryTXTRDeps.emplace_back(g_SimplePool->GetObj(*dgrpTag));\n    return true;\n  }\n  return false;\n}\n\nCIOWin::EMessageReturn CSlideShow::OnMessage(const CArchitectureMessage& msg, CArchitectureQueue& queue) {\n  switch (msg.GetType()) {\n  case EArchMsgType::TimerTick: {\n    if (x134_29_)\n      return EMessageReturn::RemoveIOWinAndExit;\n\n    // float dt = MakeMsg::GetParmTimerTick(msg).x4_parm;\n\n    switch (x14_phase) {\n    case Phase::Zero: {\n      // if (!g_resLoader->AreAllPaksLoaded())\n      //{\n      //    g_resLoader->AsyncIdlePakLoading();\n      //    return EMessageReturn::Exit;\n      //}\n      x14_phase = Phase::One;\n      [[fallthrough]];\n    }\n    case Phase::One: {\n      if (x18_galleryTXTRDeps.empty()) {\n        x18_galleryTXTRDeps.reserve(5);\n        for (int i = 1; true; ++i) {\n          std::string depResName = fmt::format(\"Gallery{:02d}_DGRP\", i);\n          if (!LoadTXTRDep(depResName))\n            break;\n        }\n        LoadTXTRDep(\"GalleryAssets_DGRP\");\n      }\n      if (!AreAllDepsLoaded(x18_galleryTXTRDeps))\n        return EMessageReturn::Exit;\n\n      x14_phase = Phase::Three;\n      [[fallthrough]];\n    }\n    case Phase::Two:\n    case Phase::Three:\n    case Phase::Four:\n    case Phase::Five:\n      break;\n    default:\n      break;\n    }\n\n    break;\n  }\n  case EArchMsgType::UserInput:\n  default:\n    break;\n  }\n\n  return EMessageReturn::Exit;\n}\n\nvoid CSlideShow::SSlideData::Draw() {\n  // TODO\n//  if (!IsLoaded()) {\n//    return;\n//  }\n//\n//  const zeus::CRectangle rect;\n//  m_texQuad->draw(x30_mulColor, 1.f, rect);\n//\n//  const zeus::CVector2f centeredOffset((x28_canvasSize.x() - m_texQuad->GetTex()->GetWidth()) * 0.5f,\n//                                       (x28_canvasSize.y() - m_texQuad->GetTex()->GetHeight()) * 0.5f);\n}\n\nvoid CSlideShow::Draw() {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CSlideShow::Draw\", zeus::skGreen);\n  if (x14_phase == Phase::Five) {\n    x5c_slideA.Draw();\n    x90_slideB.Draw();\n  }\n}\n\nu32 CSlideShow::SlideShowGalleryFlags() {\n  u32 ret = 0;\n  if (!g_GameState)\n    return ret;\n  if (g_GameState->SystemOptions().GetLogScanPercent() >= 50)\n    ret |= 1;\n  if (g_GameState->SystemOptions().GetLogScanPercent() == 100)\n    ret |= 2;\n  if (g_GameState->SystemOptions().GetPlayerBeatHardMode())\n    ret |= 4;\n  if (g_GameState->SystemOptions().GetAllItemsCollected())\n    ret |= 8;\n  return ret;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/MP1/CSlideShow.hpp",
    "content": "#pragma once\n\n#include <optional>\n#include <string>\n#include <vector>\n\n#include \"Runtime/CDependencyGroup.hpp\"\n#include \"Runtime/CIOWin.hpp\"\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Audio/CSfxManager.hpp\"\n#include \"Runtime/Camera/CCameraFilter.hpp\"\n#include \"Runtime/GuiSys/CGuiTextSupport.hpp\"\n\n#include <zeus/CColor.hpp>\n#include <zeus/CVector2f.hpp>\n\nnamespace metaforce {\nclass CTexture;\n\nclass CSlideShow : public CIOWin {\npublic:\n  enum class Phase { Zero, One, Two, Three, Four, Five };\n  struct SSlideData {\n    CSlideShow& x0_parent;\n    u32 x4_ = -1;\n    u32 x8_ = -1;\n\n    zeus::CVector2f x18_vpOffset;\n    zeus::CVector2f x20_vpSize;\n    zeus::CVector2f x28_canvasSize;\n    zeus::CColor x30_mulColor = zeus::skWhite;\n\n    explicit SSlideData(CSlideShow& parent) : x0_parent(parent) { x30_mulColor.a() = 0.f; }\n\n    void Draw();\n  };\n\nprivate:\n  Phase x14_phase = Phase::Zero;\n  std::vector<TLockedToken<CDependencyGroup>> x18_galleryTXTRDeps;\n  /*\n  u32 x2c_ = 0;\n  u32 x30_ = 0;\n  u32 x34_ = 0;\n  u32 x38_ = 0;\n  u32 x3c_ = 0;\n  u32 x40_ = 0;\n  u32 x44_ = 0;\n  u32 x48_ = -1;\n  float x4c_ = 0.f;\n  float x50_ = 0.f;\n  float x54_ = 0.f;\n  float x58_ = 0.f;\n  */\n\n  SSlideData x5c_slideA{*this};\n  SSlideData x90_slideB{*this};\n\n  std::unique_ptr<CGuiTextSupport> xc4_textA;\n  std::unique_ptr<CGuiTextSupport> xc8_textB;\n  /*\n  u32 xcc_ = 0;\n  u32 xd4_ = 0;\n  u32 xd8_ = 0;\n  u32 xdc_ = 0;\n  u32 xe0_ = 0;\n  */\n  CSfxHandle xe4_;\n  /*\n  u32 xe8_ = 0;\n  u32 xec_ = 0;\n  u32 xf0_ = 0;\n  u32 xf4_ = 0;\n  */\n  std::vector<TLockedToken<CTexture>> xf8_stickTextures; /* (9 LStick, 9 CStick) */\n  std::vector<CToken> x108_buttonTextures;               /* (2L, 2R, 2B, 2Y) */\n  /*\n  u32 x11c_ = 0;\n  u32 x120_ = 0;\n  u32 x124_ = 0;\n  float x128_ = 32.f;\n  float x12c_ = 32.f;\n  */\n  float x130_;\n  bool x134_24_ : 1 = true;\n  bool x134_25_ : 1 = false;\n  bool x134_26_ : 1 = false;\n  bool x134_27_ : 1 = false;\n  bool x134_28_disableInput : 1 = false;\n  bool x134_29_ : 1 = false;\n  bool x134_30_ : 1 = true;\n  bool x134_31_ : 1 = false;\n  bool x135_24_ : 1 = true;\n\n  bool LoadTXTRDep(std::string_view name);\n\npublic:\n  CSlideShow();\n  EMessageReturn OnMessage(const CArchitectureMessage&, CArchitectureQueue&) override;\n  bool GetIsContinueDraw() const override { return false; }\n  void Draw() override;\n\n  static u32 SlideShowGalleryFlags();\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/MP1/CSplashScreen.cpp",
    "content": "#include \"CSplashScreen.hpp\"\n#include \"Graphics/CGraphics.hpp\"\n#include \"CSimplePool.hpp\"\n#include \"CArchitectureQueue.hpp\"\n#include \"CArchitectureMessage.hpp\"\n\n#include \"zeus/CColor.hpp\"\nnamespace metaforce {\nextern CSimplePool* g_simplePool;\nnamespace MP1 {\nstatic const char* SplashTextures[] = {\"TXTR_NintendLogo\", \"TXTR_RetroLogo\", \"TXTR_DolbyLogo\"};\n\nCSplashScreen::CSplashScreen(ESplashScreen splash) : CIOWin(\"SplashScreen\"), x14_splashScreen(splash) {\n  x28_texture = g_simplePool->GetObj(SplashTextures[(u32)splash]);\n}\n\nCIOWin::EMessageReturn CSplashScreen::OnMessage(const CArchitectureMessage& msg, CArchitectureQueue& queue) {\n  if (msg.GetType() == EArchMsgType::UserInput && x25_) {\n    CFinalInput input = MakeMsg::GetParmUserInput(msg).x4_parm;\n    if (x20_phase == 1) {\n      if (input.x8_anaLeftX <= -0.7f || input.x2d_b26_DPLeft) {\n        x24_progressiveEnabled = true;\n        x1c_ = 10.f;\n      } else if (input.x8_anaLeftX >= 0.7f || input.x2d_b24_DPRight) {\n        x24_progressiveEnabled = false;\n        x1c_ = 10.f;\n      } else if (input.x2d_b28_PA || input.x2d_b27_Start) {\n        // CGraphics::SetProgressiveMode(x24_progressiveEnabled);\n        x1c_ = 5.f;\n        x20_phase = 2;\n      }\n    } else if (x20_phase == 2) {\n      if (input.x2d_b28_PA || input.x2e_b31_PStart)\n        x1c_ = 0.f;\n    }\n  } else if (msg.GetType() == EArchMsgType::TimerTick) {\n    if (!x25_) {\n      if (x28_texture)\n        x25_ = true;\n    } else {\n    }\n  }\n\n  return EMessageReturn::Exit;\n}\n\nvoid CSplashScreen::Draw() const {\n  zeus::CColor col;\n  if (x14_splashScreen == ESplashScreen::Nintendo)\n    col = zeus::CColor(0.86f, 0.f, 0.f);\n\n  float tmp = x18_;\n  if (tmp <= 1.5f) {\n    if (x18_ <= 0.5f)\n      tmp /= 0.5f;\n    else\n      tmp = 1.0f;\n  } else {\n    tmp -= 1.5f;\n    tmp = 1.0f - (tmp / 1.5f);\n  }\n\n  CGraphics::SetAlphaCompare(ERglAlphaFunc::Always, 0, ERglAlphaOp::And, ERglAlphaFunc::Always, 0);\n}\n\n} // namespace MP1\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/MP1/CSplashScreen.hpp",
    "content": "#pragma once\n\n#include \"CIOWin.hpp\"\n#include \"CToken.hpp\"\n\nnamespace metaforce {\nclass CTexture;\nnamespace MP1 {\nclass CSplashScreen : public CIOWin {\npublic:\n  enum ESplashScreen { NintendoLogo, RetroLogo, DolbyLogo };\n\nprivate:\n  ESplashScreen x14_splashScreen;\n  float x18_ = 2.0;\n  float x1c_ = 0.0;\n  u32 x20_phase = 0;\n  bool x24_progressiveEnabled = true;\n  bool x25_ = false;\n  TLockedToken<CTexture> x28_texture;\n\npublic:\n  CSplashScreen(ESplashScreen splash);\n  EMessageReturn OnMessage(const CArchitectureMessage&, CArchitectureQueue&);\n  void Draw() const;\n};\n} // namespace MP1\n"
  },
  {
    "path": "Runtime/MP1/CStateSetterFlow.cpp",
    "content": "#include \"Runtime/MP1/CStateSetterFlow.hpp\"\n\n#include \"Runtime/CArchitectureMessage.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/MP1/MP1.hpp\"\n\nnamespace metaforce::MP1 {\n\nCStateSetterFlow::CStateSetterFlow() : CIOWin(\"\") {}\n\nCIOWin::EMessageReturn CStateSetterFlow::OnMessage(const CArchitectureMessage& msg, CArchitectureQueue& queue) {\n  if (msg.GetType() == EArchMsgType::TimerTick) {\n    MP1::CMain* m = static_cast<MP1::CMain*>(g_Main);\n\n    if (m->m_warpWorldIdx > -1) {\n      CResLoader* loader = g_ResFactory->GetResLoader();\n      CAssetId worldId;\n      for (const auto& pak : loader->GetPaks()) {\n        if (*(pak->GetPath().end() - 5) == '0' + m->m_warpWorldIdx) {\n          worldId = pak->GetMLVLId();\n          break;\n        }\n      }\n\n      if (worldId.IsValid()) {\n        m->ResetGameState();\n\n        g_GameState->SetCurrentWorldId(worldId);\n        CWorldState& ws = g_GameState->StateForWorld(worldId);\n        CScriptLayerManager& layers = *ws.GetLayerState();\n        if (m->m_warpAreaId < layers.GetAreaCount()) {\n          ws.SetAreaId(m->m_warpAreaId);\n          if (m->m_warpLayerBits) {\n            for (u32 i = 0; i < layers.GetAreaLayerCount(m->m_warpAreaId); ++i)\n              layers.SetLayerActive(m->m_warpAreaId, i, ((m->m_warpLayerBits >> i) & 1) != 0);\n          }\n          CScriptMailbox& relays = *ws.Mailbox();\n          for (const auto& r : m->m_warpMemoryRelays)\n            relays.AddMsg(r);\n        }\n        g_GameState->GameOptions().ResetToDefaults();\n        g_GameState->WriteBackupBuf();\n        return EMessageReturn::RemoveIOWinAndExit;\n      }\n    }\n\n    m->RefreshGameState();\n    return EMessageReturn::RemoveIOWinAndExit;\n  }\n  return EMessageReturn::Exit;\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/CStateSetterFlow.hpp",
    "content": "#pragma once\n\n#include \"Runtime/CIOWin.hpp\"\n\nnamespace metaforce::MP1 {\n\nclass CStateSetterFlow : public CIOWin {\npublic:\n  CStateSetterFlow();\n  EMessageReturn OnMessage(const CArchitectureMessage&, CArchitectureQueue&) override;\n};\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/CTweaks.cpp",
    "content": "#include \"Runtime/MP1/CTweaks.hpp\"\n\n#include \"Runtime/MP1/Tweaks/CTweakGame.hpp\"\n#include \"Runtime/MP1/Tweaks/CTweakPlayer.hpp\"\n#include \"Runtime/MP1/Tweaks/CTweakPlayerControl.hpp\"\n#include \"Runtime/MP1/Tweaks/CTweakGunRes.hpp\"\n#include \"Runtime/MP1/Tweaks/CTweakPlayerRes.hpp\"\n#include \"Runtime/MP1/Tweaks/CTweakSlideShow.hpp\"\n#include \"Runtime/MP1/Tweaks/CTweakAutoMapper.hpp\"\n#include \"Runtime/MP1/Tweaks/CTweakTargeting.hpp\"\n#include \"Runtime/MP1/Tweaks/CTweakGui.hpp\"\n#include \"Runtime/MP1/Tweaks/CTweakParticle.hpp\"\n#include \"Runtime/MP1/Tweaks/CTweakBall.hpp\"\n#include \"Runtime/MP1/Tweaks/CTweakGuiColors.hpp\"\n#include \"Runtime/MP1/Tweaks/CTweakPlayerGun.hpp\"\n\n#include \"Runtime/IMain.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/AutoMapper/CMappableObject.hpp\"\n#include \"Runtime/World/CPlayerCameraBob.hpp\"\n#include \"Runtime/Logging.hpp\"\n\nnamespace metaforce::MP1 {\n\nvoid CTweaks::RegisterTweaks(CVarManager* cvarMgr) {\n  std::optional<CMemoryInStream> strm;\n  const SObjectTag* tag;\n\n  /* Particle */\n  tag = g_ResFactory->GetResourceIdByName(\"Particle\");\n  strm.emplace(g_ResFactory->LoadResourceSync(*tag).release(), g_ResFactory->ResourceSize(*tag),\n               CMemoryInStream::EOwnerShip::Owned);\n  g_tweakParticle = new MP1::CTweakParticle(*strm);\n  g_tweakParticle->initCVars(cvarMgr);\n\n  /* Player */\n  tag = g_ResFactory->GetResourceIdByName(\"Player\");\n  strm.emplace(g_ResFactory->LoadResourceSync(*tag).release(), g_ResFactory->ResourceSize(*tag),\n               CMemoryInStream::EOwnerShip::Owned);\n  g_tweakPlayer = new MP1::CTweakPlayer(*strm);\n  g_tweakPlayer->initCVars(cvarMgr);\n\n  /* CameraBob */\n  tag = g_ResFactory->GetResourceIdByName(\"CameraBob\");\n  strm.emplace(g_ResFactory->LoadResourceSync(*tag).release(), g_ResFactory->ResourceSize(*tag),\n               CMemoryInStream::EOwnerShip::Owned);\n  CPlayerCameraBob::ReadTweaks(*strm);\n\n  /* Ball */\n  tag = g_ResFactory->GetResourceIdByName(\"Ball\");\n  strm.emplace(g_ResFactory->LoadResourceSync(*tag).release(), g_ResFactory->ResourceSize(*tag),\n               CMemoryInStream::EOwnerShip::Owned);\n  g_tweakBall = new MP1::CTweakBall(*strm);\n  g_tweakBall->initCVars(cvarMgr);\n\n  /* PlayerGun */\n  tag = g_ResFactory->GetResourceIdByName(\"PlayerGun\");\n  strm.emplace(g_ResFactory->LoadResourceSync(*tag).release(), g_ResFactory->ResourceSize(*tag),\n               CMemoryInStream::EOwnerShip::Owned);\n  g_tweakPlayerGun = new MP1::CTweakPlayerGun(*strm);\n  g_tweakPlayerGun->initCVars(cvarMgr);\n\n  /* Targeting */\n  tag = g_ResFactory->GetResourceIdByName(\"Targeting\");\n  u8* Args = g_ResFactory->LoadResourceSync(*tag).release();\n  u32 size = g_ResFactory->ResourceSize(*tag);\n  strm.emplace(Args, size, CMemoryInStream::EOwnerShip::Owned);\n  g_tweakTargeting = new MP1::CTweakTargeting(*strm);\n  g_tweakTargeting->initCVars(cvarMgr);\n  /* Game */\n  tag = g_ResFactory->GetResourceIdByName(\"Game\");\n  strm.emplace(g_ResFactory->LoadResourceSync(*tag).release(), g_ResFactory->ResourceSize(*tag),\n               CMemoryInStream::EOwnerShip::Owned);\n  g_tweakGame = new MP1::CTweakGame(*strm);\n  g_tweakGame->initCVars(cvarMgr);\n\n  /* GuiColors */\n  tag = g_ResFactory->GetResourceIdByName(\"GuiColors\");\n  strm.emplace(g_ResFactory->LoadResourceSync(*tag).release(), g_ResFactory->ResourceSize(*tag),\n               CMemoryInStream::EOwnerShip::Owned);\n  g_tweakGuiColors = new MP1::CTweakGuiColors(*strm);\n  g_tweakGuiColors->initCVars(cvarMgr);\n\n  /* AutoMapper */\n  tag = g_ResFactory->GetResourceIdByName(\"AutoMapper\");\n  strm.emplace(g_ResFactory->LoadResourceSync(*tag).release(), g_ResFactory->ResourceSize(*tag),\n               CMemoryInStream::EOwnerShip::Owned);\n  g_tweakAutoMapper = new MP1::CTweakAutoMapper(*strm);\n  CMappableObject::ReadAutoMapperTweaks(*g_tweakAutoMapper);\n  g_tweakAutoMapper->initCVars(cvarMgr);\n\n  /* Gui */\n  tag = g_ResFactory->GetResourceIdByName(\"Gui\");\n  strm.emplace(g_ResFactory->LoadResourceSync(*tag).release(), g_ResFactory->ResourceSize(*tag),\n               CMemoryInStream::EOwnerShip::Owned);\n  g_tweakGui = new MP1::CTweakGui(*strm);\n  g_tweakPlayerGun->initCVars(cvarMgr);\n\n  /* PlayerControls */\n  tag = g_ResFactory->GetResourceIdByName(\"PlayerControls\");\n  strm.emplace(g_ResFactory->LoadResourceSync(*tag).release(), g_ResFactory->ResourceSize(*tag),\n               CMemoryInStream::EOwnerShip::Owned);\n  g_tweakPlayerControl = new MP1::CTweakPlayerControl(*strm);\n\n  /* PlayerControls2 */\n  tag = g_ResFactory->GetResourceIdByName(\"PlayerControls2\");\n  strm.emplace(g_ResFactory->LoadResourceSync(*tag).release(), g_ResFactory->ResourceSize(*tag),\n               CMemoryInStream::EOwnerShip::Owned);\n  g_tweakPlayerControlAlt = new MP1::CTweakPlayerControl(*strm);\n\n  g_currentPlayerControl = g_tweakPlayerControl;\n\n  /* SlideShow */\n  tag = g_ResFactory->GetResourceIdByName(\"SlideShow\");\n  strm.emplace(g_ResFactory->LoadResourceSync(*tag).release(), g_ResFactory->ResourceSize(*tag),\n               CMemoryInStream::EOwnerShip::Owned);\n  g_tweakSlideShow = new MP1::CTweakSlideShow(*strm);\n  g_tweakSlideShow->initCVars(cvarMgr);\n}\n\nvoid CTweaks::RegisterResourceTweaks(CVarManager* cvarMgr) {\n  std::optional<CMemoryInStream> strm;\n\n  const SObjectTag* tag = g_ResFactory->GetResourceIdByName(\"GunRes\");\n  strm.emplace(g_ResFactory->LoadResourceSync(*tag).release(), g_ResFactory->ResourceSize(*tag),\n               CMemoryInStream::EOwnerShip::Owned);\n  g_tweakGunRes = new MP1::CTweakGunRes(*strm);\n  g_tweakGunRes->ResolveResources(*g_ResFactory);\n  g_tweakGunRes->initCVars(cvarMgr);\n\n  tag = g_ResFactory->GetResourceIdByName(\"PlayerRes\");\n  strm.emplace(g_ResFactory->LoadResourceSync(*tag).release(), g_ResFactory->ResourceSize(*tag),\n               CMemoryInStream::EOwnerShip::Owned);\n  g_tweakPlayerRes = new MP1::CTweakPlayerRes(*strm, g_Main->IsTrilogy() || g_Main->IsPAL() || g_Main->IsJapanese());\n  g_tweakPlayerRes->ResolveResources(*g_ResFactory);\n  g_tweakPlayerRes->initCVars(cvarMgr);\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/CTweaks.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n\nnamespace metaforce {\nclass CVarManager;\n\nnamespace MP1 {\n\nclass CTweaks {\npublic:\n  void RegisterTweaks(CVarManager* cvarMgr);\n  void RegisterResourceTweaks(CVarManager* cvarMgr);\n};\n\n} // namespace MP1\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/MP1/MP1.cpp",
    "content": "#include \"Runtime/MP1/MP1.hpp\"\n\n#include <array>\n\n#include \"Runtime/CDependencyGroup.hpp\"\n#include \"Runtime/CGameHintInfo.hpp\"\n#include \"Runtime/CWorldSaveGameInfo.hpp\"\n#include \"Runtime/CScannableObjectInfo.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/CStopwatch.hpp\"\n#include \"Runtime/CTextureCache.hpp\"\n#include \"Runtime/Audio/CAudioGroupSet.hpp\"\n#include \"Runtime/Audio/CMidiManager.hpp\"\n#include \"Runtime/Audio/CSfxManager.hpp\"\n#include \"Runtime/Audio/CStreamAudioManager.hpp\"\n#include \"Runtime/AutoMapper/CMapArea.hpp\"\n#include \"Runtime/AutoMapper/CMapUniverse.hpp\"\n#include \"Runtime/AutoMapper/CMapWorld.hpp\"\n#include \"Runtime/Character/CAllFormatsAnimSource.hpp\"\n#include \"Runtime/Character/CAnimCharacterSet.hpp\"\n#include \"Runtime/Character/CAnimPOIData.hpp\"\n#include \"Runtime/Character/CCharLayoutInfo.hpp\"\n#include \"Runtime/Character/CSkinRules.hpp\"\n#include \"Runtime/Collision/CCollidableOBBTreeGroup.hpp\"\n#include \"Runtime/Collision/CCollisionResponseData.hpp\"\n#include \"Runtime/Graphics/CFont.hpp\"\n#include \"Runtime/Graphics/CModel.hpp\"\n#include \"Runtime/Graphics/CTexture.hpp\"\n#include \"Runtime/GuiSys/CGuiFrame.hpp\"\n#include \"Runtime/GuiSys/CRasterFont.hpp\"\n#include \"Runtime/GuiSys/CStringTable.hpp\"\n#include \"Runtime/MP1/CGBASupport.hpp\"\n#include \"Runtime/Particle/CDecalDataFactory.hpp\"\n#include \"Runtime/Particle/CParticleDataFactory.hpp\"\n#include \"Runtime/Particle/CParticleElectricDataFactory.hpp\"\n#include \"Runtime/Particle/CParticleSwooshDataFactory.hpp\"\n#include \"Runtime/Particle/CProjectileWeaponDataFactory.hpp\"\n#include \"Runtime/Particle/CWeaponDescription.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CStateMachine.hpp\"\n#include \"Runtime/World/CScriptMazeNode.hpp\"\n#include \"Runtime/Logging.hpp\"\n\n#include \"Runtime/MP1/CCredits.hpp\"\n\n#include <magic_enum.hpp>\n\n#ifdef ENABLE_DISCORD\n#include <discord_rpc.h>\n#endif\n\n#if _WIN32\ninline void* memmem(const void* haystack, size_t hlen, const void* needle, size_t nlen) {\n  int needle_first;\n  const uint8_t* p = static_cast<const uint8_t*>(haystack);\n  size_t plen = hlen;\n\n  if (!nlen)\n    return NULL;\n\n  needle_first = *(unsigned char*)needle;\n\n  while (plen >= nlen && (p = static_cast<const uint8_t*>(memchr(p, needle_first, plen - nlen + 1)))) {\n    if (!memcmp(p, needle, nlen))\n      return (void*)p;\n\n    p++;\n    plen = hlen - (p - static_cast<const uint8_t*>(haystack));\n  }\n\n  return NULL;\n}\n#endif\n\nnamespace metaforce::MP1 {\nnamespace {\nstruct AudioGroupInfo {\n  const char* name;\n  u32 id;\n};\n\nconstexpr std::array<AudioGroupInfo, 5> StaticAudioGroups{{\n    {\"Misc_AGSC\", GRPMisc},\n    {\"MiscSamus_AGSC\", GRPMiscSamus},\n    {\"UI_AGSC\", GRPUI},\n    {\"Weapons_AGSC\", GRPWeapons},\n    {\"ZZZ_AGSC\", GRPZZZ},\n}};\n} // Anonymous namespace\n\nCGameArchitectureSupport::CGameArchitectureSupport(CMain& parent)\n: m_parent(parent)\n, x0_audioSys(0, 0, 0, 0, 0)\n, x30_inputGenerator(/*osCtx, */ g_tweakPlayer->GetLeftLogicalThreshold(), g_tweakPlayer->GetRightLogicalThreshold())\n, x44_guiSys(*g_ResFactory, *g_SimplePool, CGuiSys::EUsageMode::Zero) {\n  auto* m = static_cast<CMain*>(g_Main);\n\n  g_InputGenerator = &x30_inputGenerator;\n  g_Controller = x30_inputGenerator.GetController();\n\n  CAudioSys::SysSetVolume(0x7f);\n  CAudioSys::SetDefaultVolumeScale(0x75);\n  CAudioSys::SetVolumeScale(CAudioSys::GetDefaultVolumeScale());\n  CStreamAudioManager::Initialize();\n  CStreamAudioManager::SetMusicVolume(0x7f);\n  m->ResetGameState();\n\n  if (!g_tweakGame->GetSplashScreensDisabled()) {\n    std::shared_ptr<CIOWin> splash = std::make_shared<CSplashScreen>(CSplashScreen::ESplashScreen::Nintendo);\n    x58_ioWinManager.AddIOWin(splash, 1000, 10000);\n  }\n\n  std::shared_ptr<CIOWin> mf = std::make_shared<CMainFlow>();\n  x58_ioWinManager.AddIOWin(mf, 0, 0);\n\n  std::shared_ptr<CIOWin> console = std::make_shared<CConsoleOutputWindow>(8, 5.f, 0.75f);\n  x58_ioWinManager.AddIOWin(console, 100, 0);\n\n  std::shared_ptr<CIOWin> audState = std::make_shared<CAudioStateWin>();\n  x58_ioWinManager.AddIOWin(audState, 100, -1);\n\n  std::shared_ptr<CIOWin> errWin = std::make_shared<CErrorOutputWindow>(false);\n  x58_ioWinManager.AddIOWin(errWin, 10000, 100000);\n\n  g_GuiSys = &x44_guiSys;\n  g_GameState->GameOptions().EnsureSettings();\n}\n\nvoid CGameArchitectureSupport::UpdateTicks(float dt) {\n  x4_archQueue.Push(MakeMsg::CreateFrameBegin(EArchMsgTarget::Game, x78_gameFrameCount));\n  x4_archQueue.Push(MakeMsg::CreateTimerTick(EArchMsgTarget::Game, dt));\n}\n\nvoid CGameArchitectureSupport::Update(float dt) {\n  g_GameState->GetWorldTransitionManager()->TouchModels();\n  x30_inputGenerator.Update(dt, x4_archQueue);\n  x4_archQueue.Push(MakeMsg::CreateFrameEnd(EArchMsgTarget::Game, x78_gameFrameCount));\n  x58_ioWinManager.PumpMessages(x4_archQueue);\n}\n\nbool CGameArchitectureSupport::LoadAudio() {\n  if (x88_audioLoadStatus == EAudioLoadStatus::Loaded) {\n    return true;\n  }\n\n  for (int i = 0; i < 5; ++i) {\n    TToken<CAudioGroupSet>& tok = x8c_pendingAudioGroups[i];\n    if (tok.IsLocked()) {\n      if (tok.IsLoaded()) {\n        CAudioGroupSet* set = tok.GetObj();\n        if (!CAudioSys::SysIsGroupSetLoaded(set->GetName())) {\n          CAudioSys::SysLoadGroupSet(tok, set->GetName(), tok.GetObjectTag()->id);\n          CAudioSys::SysAddGroupIntoAmuse(set->GetName());\n        }\n      } else {\n        return false;\n      }\n    } else {\n      /* Lock next pending group */\n      tok.Lock();\n      return false;\n    }\n  }\n\n  CSfxManager::LoadTranslationTable(g_SimplePool, g_ResFactory->GetResourceIdByName(\"sound_lookup\"));\n  x8c_pendingAudioGroups = std::vector<TToken<CAudioGroupSet>>();\n  x88_audioLoadStatus = EAudioLoadStatus::Loaded;\n\n  return true;\n}\n\nvoid CGameArchitectureSupport::PreloadAudio() {\n  if (x88_audioLoadStatus != EAudioLoadStatus::Uninitialized) {\n    return;\n  }\n\n  x8c_pendingAudioGroups.clear();\n  x8c_pendingAudioGroups.reserve(5);\n\n  for (size_t i = 0; i < StaticAudioGroups.size(); ++i) {\n    const AudioGroupInfo& info = StaticAudioGroups[i];\n    CToken grp = g_SimplePool->GetObj(info.name);\n\n    // Lock first group in sequence\n    if (i == 0) {\n      grp.Lock();\n    }\n\n    x8c_pendingAudioGroups.emplace_back(std::move(grp));\n  }\n\n  x88_audioLoadStatus = EAudioLoadStatus::Loading;\n}\n\nvoid CGameArchitectureSupport::UnloadAudio() {\n  for (const AudioGroupInfo& info : StaticAudioGroups) {\n    const SObjectTag* tag = g_ResFactory->GetResourceIdByName(info.name);\n    auto name = CAudioSys::SysGetGroupSetName(tag->id);\n    CAudioSys::SysRemoveGroupFromAmuse(name);\n    CAudioSys::SysUnloadAudioGroupSet(name);\n  }\n\n  x8c_pendingAudioGroups = std::vector<TToken<CAudioGroupSet>>();\n  x88_audioLoadStatus = EAudioLoadStatus::Uninitialized;\n}\n\nvoid CGameArchitectureSupport::Draw() {\n  x58_ioWinManager.Draw();\n  if (m_parent.x161_24_gameFrameDrawn) {\n    ++x78_gameFrameCount;\n    m_parent.x161_24_gameFrameDrawn = false;\n  }\n}\n\nCGameArchitectureSupport::~CGameArchitectureSupport() {\n  x58_ioWinManager.RemoveAllIOWins();\n  UnloadAudio();\n  CSfxManager::Shutdown();\n  CStreamAudioManager::Shutdown();\n}\n\nCMain::CMain(IFactory* resFactory, CSimplePool* resStore)\n: xe4_gameplayResult(EGameplayResult::Playing)\n, x128_globalObjects(std::make_unique<CGameGlobalObjects>(resFactory, resStore)) {\n  g_Main = this;\n}\n\nCMain::~CMain() { g_Main = nullptr; }\n\nvoid CMain::RegisterResourceTweaks() {}\n\nvoid CGameGlobalObjects::AddPaksAndFactories() {\n  CGraphics::SetViewPointMatrix(zeus::CTransform());\n  CGraphics::SetModelMatrix(zeus::CTransform());\n  if (CResLoader* loader = g_ResFactory->GetResLoader()) {\n    loader->AddPakFileAsync(\"Tweaks\", false, false);\n    loader->AddPakFileAsync(\"NoARAM\", false, false);\n    loader->AddPakFileAsync(\"AudioGrp\", false, false);\n    loader->AddPakFileAsync(\"MiscData\", false, false);\n    loader->AddPakFileAsync(\"SamusGun\", true, false);\n    loader->AddPakFileAsync(\"TestAnim\", true, false);\n    loader->AddPakFileAsync(\"SamGunFx\", true, false);\n    loader->AddPakFileAsync(\"MidiData\", false, false);\n    loader->AddPakFileAsync(\"GGuiSys\", false, false);\n    loader->WaitForPakFileLoadingComplete();\n  }\n\n  if (CFactoryMgr* fmgr = g_ResFactory->GetFactoryMgr()) {\n    fmgr->AddFactory(FOURCC('TXTR'), FFactoryFunc(FTextureFactory));\n    fmgr->AddFactory(FOURCC('PART'), FFactoryFunc(FParticleFactory));\n    fmgr->AddFactory(FOURCC('FRME'), FFactoryFunc(RGuiFrameFactoryInGame));\n    fmgr->AddFactory(FOURCC('FONT'), FFactoryFunc(FRasterFontFactory));\n    fmgr->AddFactory(FOURCC('CMDL'), FMemFactoryFunc(FModelFactory));\n    fmgr->AddFactory(FOURCC('CINF'), FFactoryFunc(FCharLayoutInfo));\n    fmgr->AddFactory(FOURCC('CSKR'), FFactoryFunc(FSkinRulesFactory));\n    fmgr->AddFactory(FOURCC('ANCS'), FFactoryFunc(FAnimCharacterSet));\n    fmgr->AddFactory(FOURCC('ANIM'), FFactoryFunc(AnimSourceFactory));\n    fmgr->AddFactory(FOURCC('EVNT'), FFactoryFunc(AnimPOIDataFactory));\n    fmgr->AddFactory(FOURCC('DCLN'), FFactoryFunc(FCollidableOBBTreeGroupFactory));\n    fmgr->AddFactory(FOURCC('DGRP'), FFactoryFunc(FDependencyGroupFactory));\n    fmgr->AddFactory(FOURCC('AGSC'), FMemFactoryFunc(FAudioGroupSetDataFactory));\n    fmgr->AddFactory(FOURCC('CSNG'), FFactoryFunc(FMidiDataFactory));\n    fmgr->AddFactory(FOURCC('ATBL'), FFactoryFunc(FAudioTranslationTableFactory));\n    fmgr->AddFactory(FOURCC('STRG'), FFactoryFunc(FStringTableFactory));\n    fmgr->AddFactory(FOURCC('HINT'), FFactoryFunc(FHintFactory));\n    fmgr->AddFactory(FOURCC('SAVW'), FFactoryFunc(FWorldSaveGameInfoFactory));\n    fmgr->AddFactory(FOURCC('MAPW'), FFactoryFunc(FMapWorldFactory));\n    fmgr->AddFactory(FOURCC('SCAN'), FFactoryFunc(FScannableObjectInfoFactory));\n    fmgr->AddFactory(FOURCC('CRSC'), FFactoryFunc(FCollisionResponseDataFactory));\n    fmgr->AddFactory(FOURCC('SWHC'), FFactoryFunc(FParticleSwooshDataFactory));\n    fmgr->AddFactory(FOURCC('ELSC'), FFactoryFunc(FParticleElectricDataFactory));\n    fmgr->AddFactory(FOURCC('WPSC'), FFactoryFunc(FProjectileWeaponDataFactory));\n    fmgr->AddFactory(FOURCC('DPSC'), FFactoryFunc(FDecalDataFactory));\n    fmgr->AddFactory(FOURCC('MAPA'), FFactoryFunc(FMapAreaFactory));\n    fmgr->AddFactory(FOURCC('MAPU'), FFactoryFunc(FMapUniverseFactory));\n    fmgr->AddFactory(FOURCC('AFSM'), FFactoryFunc(FAiFiniteStateMachineFactory));\n    fmgr->AddFactory(FOURCC('PATH'), FMemFactoryFunc(FPathFindAreaFactory));\n    fmgr->AddFactory(FOURCC('TMET'), FFactoryFunc(FTextureCacheFactory));\n  }\n}\n\nCGameGlobalObjects::~CGameGlobalObjects() {\n  g_ResFactory = nullptr;\n  g_SimplePool = nullptr;\n  g_CharFactoryBuilder = nullptr;\n  g_AiFuncMap = nullptr;\n  g_GameState = nullptr;\n  g_TweakManager = nullptr;\n}\nvoid CGameGlobalObjects::PostInitialize() {\n  AddPaksAndFactories();\n  LoadTextureCache();\n  LoadStringTable();\n  m_renderer.reset(AllocateRenderer(*xcc_simplePool, *x4_resFactory));\n  CEnvFxManager::Initialize();\n  CScriptMazeNode::LoadMazeSeeds();\n}\n\nvoid CMain::AddWorldPaks() {\n  CResLoader* loader = g_ResFactory->GetResLoader();\n  if (loader == nullptr) {\n    return;\n  }\n\n  auto pakPrefix = g_tweakGame->GetWorldPrefix();\n  for (int i = 0; i < 10; ++i) {\n    std::string path(pakPrefix);\n\n    if (i != 0) {\n      path += '0' + char(i);\n    }\n\n    if (CDvdFile::FileExists(path + \".pak\")) {\n      loader->AddPakFileAsync(path, false, true);\n    }\n  }\n  loader->WaitForPakFileLoadingComplete();\n}\n\nvoid CMain::AddOverridePaks() {\n  CResLoader* loader = g_ResFactory->GetResLoader();\n  if (loader == nullptr) {\n    return;\n  }\n\n  /* Inversely load each pak starting at 999, to ensure proper priority order\n   * the higher the number the higer the priority, e.g: Override0 has less priority than Override1 etc.\n   */\n  for (size_t i = 9; i > 0; --i) {\n    const std::string path = fmt::format(\"Override{}\", i);\n    if (CDvdFile::FileExists(path + \".pak\")) {\n      loader->AddPakFileAsync(path, false, false, true);\n    }\n  }\n  /* Make sure all Override paks are ready before attempting to load URDE.pak */\n  loader->WaitForPakFileLoadingComplete();\n\n  /* Load Trilogy PAKs */\n  if (CDvdFile::FileExists(\"RS5.pak\")) {\n    loader->AddPakFile(\"RS5\", false, false, true);\n  }\n  if (CDvdFile::FileExists(\"Strings.pak\")) {\n    loader->AddPakFile(\"Strings\", false, false, true);\n  }\n\n  /* Attempt to load URDE.pak\n   * NOTE(phil): Should we fatal here if it's not found?\n   */\n  if (CDvdFile::FileExists(\"URDE.pak\")) {\n    loader->AddPakFile(\"URDE\", false, false, true);\n  }\n}\n\nvoid CMain::ResetGameState() {\n  CPersistentOptions sysOpts = g_GameState->SystemOptions();\n  CGameOptions gameOpts = g_GameState->GameOptions();\n  x128_globalObjects->ResetGameState();\n  g_GameState->ImportPersistentOptions(sysOpts);\n  g_GameState->SetGameOptions(gameOpts);\n  g_GameState->GetPlayerState()->SetIsFusionEnabled(g_GameState->SystemOptions().GetPlayerFusionSuitActive());\n}\n\nvoid CMain::InitializeSubsystems() {\n  CBasics::Initialize();\n  CElementGen::Initialize();\n  CAnimData::InitializeCache();\n  CDecalManager::Initialize();\n  CGBASupport::Initialize();\n  CPatterned::Initialize();\n  // Metaforce additions\n  CMoviePlayer::Initialize();\n}\n\nvoid CMain::MemoryCardInitializePump() {\n  if (g_MemoryCardSys != nullptr) {\n    return;\n  }\n\n  std::unique_ptr<CMemoryCardSys>& memSys = x128_globalObjects->x0_memoryCardSys;\n  if (!memSys) {\n    memSys = std::make_unique<CMemoryCardSys>();\n  }\n  if (memSys->InitializePump()) {\n    g_MemoryCardSys = memSys.get();\n    g_GameState->InitializeMemoryStates();\n  }\n}\n\nvoid CMain::FillInAssetIDs() {\n  if (const SObjectTag* tag = g_ResFactory->GetResourceIdByName(g_tweakGame->GetDefaultRoom())) {\n    g_GameState->SetCurrentWorldId(tag->id);\n  }\n}\n\nbool CMain::LoadAudio() {\n  if (x164_archSupport) {\n    return x164_archSupport->LoadAudio();\n  }\n  return true;\n}\n\nvoid CMain::EnsureWorldPaksReady() {}\n\nvoid CMain::EnsureWorldPakReady(CAssetId mlvl) { /* TODO: Schedule resource list load for World Pak containing mlvl */ }\n\nvoid CMain::StreamNewGameState(CInputStream& r, u32 idx) {\n  bool fusionBackup = g_GameState->SystemOptions().GetPlayerFusionSuitActive();\n  x128_globalObjects->x134_gameState = std::make_unique<CGameState>(r, idx);\n  g_GameState = x128_globalObjects->x134_gameState.get();\n  g_GameState->SystemOptions().SetPlayerFusionSuitActive(fusionBackup);\n  g_GameState->GetPlayerState()->SetIsFusionEnabled(fusionBackup);\n  g_GameState->HintOptions().SetNextHintTime();\n}\n\nvoid CMain::RefreshGameState() {\n  CPersistentOptions sysOpts = g_GameState->SystemOptions();\n  u64 cardSerial = g_GameState->GetCardSerial();\n  std::vector<u8> saveData = g_GameState->BackupBuf();\n  CGameOptions gameOpts = g_GameState->GameOptions();\n  CMemoryInStream r(saveData.data(), saveData.size(), CMemoryInStream::EOwnerShip::NotOwned);\n  x128_globalObjects->StreamInGameState(r, g_GameState->GetFileIdx());\n  g_GameState->SetPersistentOptions(sysOpts);\n  g_GameState->SetGameOptions(gameOpts);\n  g_GameState->GameOptions().EnsureSettings();\n  g_GameState->SetCardSerial(cardSerial);\n  g_GameState->GetPlayerState()->SetIsFusionEnabled(g_GameState->SystemOptions().GetPlayerFusionSuitActive());\n}\n\nstatic const char* DISCORD_APPLICATION_ID = \"402571593815031819\";\nstatic int64_t DiscordStartTime;\nstatic CAssetId DiscordWorldSTRG;\nstatic TLockedToken<CStringTable> DiscordWorldSTRGObj;\nstatic std::string DiscordWorldName;\nstatic u32 DiscordItemPercent = 0xffffffff;\nstatic std::string DiscordState;\n\nvoid CMain::InitializeDiscord() {\n#ifdef ENABLE_DISCORD\n  DiscordStartTime = std::time(nullptr);\n  DiscordEventHandlers handlers = {};\n  handlers.ready = HandleDiscordReady;\n  handlers.disconnected = HandleDiscordDisconnected;\n  handlers.errored = HandleDiscordErrored;\n  Discord_Initialize(DISCORD_APPLICATION_ID, &handlers, 1, nullptr);\n#endif\n}\n\nvoid CMain::ShutdownDiscord() {\n#ifdef ENABLE_DISCORD\n  DiscordWorldSTRGObj = TLockedToken<CStringTable>();\n  Discord_Shutdown();\n#endif\n}\n\nvoid CMain::UpdateDiscordPresence(CAssetId worldSTRG) {\n#ifdef ENABLE_DISCORD\n  bool updated = false;\n\n  if (worldSTRG != DiscordWorldSTRG) {\n    DiscordWorldSTRG = worldSTRG;\n    DiscordWorldSTRGObj = g_SimplePool->GetObj(SObjectTag{FOURCC('STRG'), worldSTRG});\n  }\n  if (DiscordWorldSTRGObj.IsLoaded()) {\n    DiscordWorldName = hecl::Char16ToUTF8(DiscordWorldSTRGObj->GetString(0));\n    DiscordWorldSTRGObj = TLockedToken<CStringTable>();\n    updated = true;\n  }\n\n  if (g_GameState != nullptr) {\n    if (const CPlayerState* pState = g_GameState->GetPlayerState().get()) {\n      const u32 itemPercent = pState->CalculateItemCollectionRate() * 100 / pState->GetPickupTotal();\n      if (DiscordItemPercent != itemPercent) {\n        DiscordItemPercent = itemPercent;\n        DiscordState = fmt::format(\"{}%\", itemPercent);\n        updated = true;\n      }\n    }\n  }\n\n  if (updated) {\n    DiscordRichPresence discordPresence = {};\n    discordPresence.state = DiscordState.c_str();\n    discordPresence.details = DiscordWorldName.c_str();\n    discordPresence.largeImageKey = \"default\";\n    discordPresence.startTimestamp = DiscordStartTime;\n    Discord_UpdatePresence(&discordPresence);\n  }\n#endif\n}\n\nvoid CMain::HandleDiscordReady(const DiscordUser* request) { spdlog::info(\"Discord Ready\"); }\n\nvoid CMain::HandleDiscordDisconnected(int errorCode, const char* message) {\n  spdlog::warn(\"Discord Disconnected: {}\", message);\n}\n\nvoid CMain::HandleDiscordErrored(int errorCode, const char* message) { spdlog::error(\"Discord Error: {}\", message); }\n\nstd::string CMain::Init(int argc, char** argv, const FileStoreManager& storeMgr, CVarManager* cvarMgr) {\n  m_cvarMgr = cvarMgr;\n\n  {\n    auto discInfo = CDvdFile::DiscInfo();\n    if (discInfo.gameId[4] != '0' || discInfo.gameId[5] != '1') {\n      return fmt::format(\"Unknown game ID {}\", std::string_view{discInfo.gameId.data(), 6});\n    }\n    if (strncmp(discInfo.gameId.data(), \"GM8\", 3) == 0) {\n      m_version.game = EGame::MetroidPrime1;\n      m_version.platform = EPlatform::GameCube;\n    } else if (strncmp(discInfo.gameId.data(), \"R3I\", 3) == 0) {\n      m_version.game = EGame::MetroidPrime1;\n      m_version.platform = EPlatform::Wii;\n    } else if (strncmp(discInfo.gameId.data(), \"G2M\", 3) == 0) {\n      m_version.game = EGame::MetroidPrime2;\n      m_version.platform = EPlatform::GameCube;\n    } else if (strncmp(discInfo.gameId.data(), \"R32\", 3) == 0) {\n      m_version.game = EGame::MetroidPrime2;\n      m_version.platform = EPlatform::Wii;\n    } else if (strncmp(discInfo.gameId.data(), \"RM3\", 3) == 0) {\n      m_version.game = EGame::MetroidPrime3;\n      m_version.platform = EPlatform::Wii;\n    } else if (strncmp(discInfo.gameId.data(), \"R3M\", 3) == 0) {\n      m_version.game = EGame::MetroidPrimeTrilogy;\n      m_version.platform = EPlatform::Wii;\n    } else {\n      return fmt::format(\"Unknown game ID {}\", std::string_view{discInfo.gameId.data(), 6});\n    }\n    switch (discInfo.gameId[3]) {\n    case 'E':\n      if (m_version.game == EGame::MetroidPrime1 && discInfo.version == 48) {\n        m_version.region = ERegion::KOR;\n      } else {\n        m_version.region = ERegion::USA;\n      }\n      break;\n    case 'J':\n      m_version.region = ERegion::JPN;\n      break;\n    case 'P':\n      m_version.region = ERegion::PAL;\n      break;\n    default:\n      return fmt::format(\"Unknown region {}\", discInfo.gameId[3]);\n    }\n    m_version.gameTitle = std::move(discInfo.gameTitle);\n  }\n\n  if (m_version.game != EGame::MetroidPrime1 && m_version.game != EGame::MetroidPrimeTrilogy) {\n    return fmt::format(\"Unsupported game {}\", magic_enum::enum_name(m_version.game));\n  }\n\n  {\n    auto dolFile = \"default.dol\"sv;\n    if (m_version.game == EGame::MetroidPrimeTrilogy) {\n      dolFile = \"rs5mp1_p.dol\"sv;\n    } else if (m_version.platform == EPlatform::Wii) {\n      dolFile = \"rs5mp1jpn_p.dol\"sv;\n    }\n    CDvdFile file(dolFile);\n    if (!file) {\n      return fmt::format(\"Failed to open {}\", dolFile);\n    }\n    std::unique_ptr<u8[]> dolBuf = std::make_unique<u8[]>(file.Length());\n    size_t dolLen = file.SyncRead(dolBuf.get(), file.Length());\n    if (dolLen < 16) {\n      return fmt::format(\"Failed to read {}\", dolFile);\n    }\n\n    const void* buildInfoPos = memmem(dolBuf.get(), dolLen, \"MetroidBuildInfo\", 16);\n    if (buildInfoPos == nullptr) {\n      return fmt::format(\"Failed to locate MetroidBuildInfo\");\n    }\n    const char* buildInfo = static_cast<const char*>(buildInfoPos) + 19;\n    const auto maxRemaining = size_t(dolBuf.get() + dolLen - reinterpret_cast<const u8*>(buildInfo));\n    const char* buildInfoEnd = static_cast<const char*>(memchr(buildInfo, '\\0', maxRemaining));\n    if (buildInfoEnd == nullptr) {\n      return fmt::format(\"Failed to parse MetroidBuildInfo\");\n    }\n    m_version.version.assign(buildInfo, buildInfoEnd);\n  }\n  spdlog::info(\"Loading data from {} {} ({})\", GetGameTitle(), magic_enum::enum_name(GetRegion()), GetVersionString());\n\n  InitializeDiscord();\n  if (m_version.game == EGame::MetroidPrimeTrilogy) {\n    CDvdFile::SetRootDirectory(\"MP1\");\n  } else if (m_version.platform == EPlatform::Wii) {\n    CDvdFile::SetRootDirectory(\"MP1JPN\");\n  }\n  InitializeSubsystems();\n  AddOverridePaks();\n  x128_globalObjects->PostInitialize();\n  x70_tweaks.RegisterTweaks(m_cvarMgr);\n  x70_tweaks.RegisterResourceTweaks(m_cvarMgr);\n  AddWorldPaks();\n\n  for (int i = 1; i < argc; ++i) {\n    std::string arg = argv[i];\n    if (arg == \"--warp\" && i < argc - 2) {\n      const char* worldIdxStr = argv[i + 1];\n      const char* areaIdxStr = argv[i + 2];\n\n      char* endptr = nullptr;\n      m_warpWorldIdx = TAreaId(strtoul(worldIdxStr, &endptr, 0));\n      if (endptr == worldIdxStr) {\n        m_warpWorldIdx = 0;\n      }\n      m_warpAreaId = TAreaId(strtoul(areaIdxStr, &endptr, 0));\n      if (endptr == areaIdxStr) {\n        m_warpAreaId = 0;\n      }\n\n      bool found = false;\n      for (const auto& pak : g_ResFactory->GetResLoader()->GetPaks()) {\n        if (*(pak->GetPath().end() - 5) == '0' + m_warpWorldIdx) {\n          found = true;\n          break;\n        }\n      }\n\n      if (!found) {\n        m_warpWorldIdx = -1;\n        break;\n      }\n\n      while (i < argc - 3) {\n        const char* layerStr = argv[i + 3];\n        if (!(layerStr[0] == '0' && layerStr[1] == 'x') && (layerStr[0] == '0' || layerStr[0] == '1')) {\n          for (const auto* cur = layerStr; *cur != '\\0'; ++cur)\n            if (*cur == '1')\n              m_warpLayerBits |= u64(1) << (cur - layerStr);\n        } else if (layerStr[0] == '0' && layerStr[1] == 'x') {\n          m_warpMemoryRelays.emplace_back(TAreaId(strtoul(layerStr + 2, nullptr, 16)));\n        }\n        ++i;\n      }\n\n      SetFlowState(EClientFlowStates::StateSetter);\n      break;\n    }\n  }\n\n  FillInAssetIDs();\n  x164_archSupport = std::make_unique<CGameArchitectureSupport>(*this);\n  g_archSupport = x164_archSupport.get();\n  x164_archSupport->PreloadAudio();\n  std::srand(static_cast<u32>(CBasics::GetTime()));\n  // g_TweakManager->ReadFromMemoryCard(\"AudioTweaks\");\n  return {};\n}\n\nbool CMain::Proc(float dt) {\n  CRandom16::ResetNumNextCalls();\n  if (!m_loadedPersistentResources) {\n    x128_globalObjects->m_gameResFactory->LoadPersistentResources(*g_SimplePool);\n    m_loadedPersistentResources = true;\n  }\n\n  if (!m_paused) {\n    CGBASupport::GlobalPoll();\n    x164_archSupport->UpdateTicks(dt);\n    x164_archSupport->Update(dt);\n    CSfxManager::Update(dt);\n    CStreamAudioManager::Update(dt);\n  }\n\n  if (x164_archSupport->GetIOWinManager().IsEmpty() || CheckReset()) {\n    CStreamAudioManager::StopAll();\n    /*\n    x164_archSupport.reset();\n    g_archSupport = x164_archSupport.get();\n    x164_archSupport->PreloadAudio();\n    */\n    x160_24_finished = true;\n  }\n\n#ifdef ENABLE_DISCORD\n  Discord_RunCallbacks();\n#endif\n\n  return x160_24_finished;\n}\n\nvoid CMain::Draw() { x164_archSupport->Draw(); }\n\nvoid CMain::ShutdownSubsystems() {\n  CDecalManager::Shutdown();\n  CElementGen::Shutdown();\n  CAnimData::FreeCache();\n  CMemoryCardSys::Shutdown();\n  // Metaforce additions\n  CMoviePlayer::Shutdown();\n  CFont::Shutdown();\n  CFluidPlaneManager::Shutdown();\n}\n\nvoid CMain::Shutdown() {\n  x128_globalObjects->m_gameResFactory->UnloadPersistentResources();\n  x164_archSupport.reset();\n  ShutdownSubsystems();\n  //  CBooModel::Shutdown();\n  //  CGraphics::ShutdownBoo();\n  ShutdownDiscord();\n}\n\n#if 0\nint CMain::RsMain(int argc, char** argv, boo::IAudioVoiceEngine* voiceEngine,\n                  amuse::IBackendVoiceAllocator& backend) {\n  // PPCSetFpIEEEMode();\n  // uVar21 = OSGetTime();\n  // LCEnable();\n  x128_globalObjects = std::make_unique<CGameGlobalObjects>(nullptr, nullptr);\n  xf0_.resize(4, 0.3f);\n  x104_.resize(4, 0.2f);\n  x118_ = 0.3f;\n  x11c_ = 0.2f;\n  InitializeSubsystems();\n  x128_globalObjects->PostInitialize(); // COsContext*, CMemorySys*\n  x70_tweaks.RegisterTweaks(m_cvarMgr);\n  AddWorldPaks();\n\n  std::string msg;\n  if (!g_TweakManager->ReadFromMemoryCard(\"AudioTweaks\"sv)) {\n    msg = \"Loaded audio tweaks from memory card\\n\"s;\n  } else {\n    msg = \"FAILED to load audio tweaks from memory card\\n\";\n  }\n\n  FillInAssetIDs();\n  x164_archSupport = std::make_unique<CGameArchitectureSupport>(*this, voiceEngine, backend);\n  x164_archSupport->PreloadAudio();\n\n  return 0;\n}\n#endif\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/MP1.hpp",
    "content": "#pragma once\n\n#include \"Runtime/IMain.hpp\"\n#include \"Runtime/MP1/CTweaks.hpp\"\n#include \"Runtime/MP1/CPlayMovie.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/CBasics.hpp\"\n#include \"Runtime/CMemoryCardSys.hpp\"\n#include \"Runtime/CResFactory.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/Character/CAssetFactory.hpp\"\n#include \"Runtime/World/CAi.hpp\"\n#include \"Runtime/CGameState.hpp\"\n#include \"Runtime/ImGuiConsole.hpp\"\n#include \"Runtime/MP1/CInGameTweakManager.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/Character/CAnimData.hpp\"\n#include \"Runtime/Particle/CDecalManager.hpp\"\n#include \"Runtime/Particle/CGenDescription.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Audio/CAudioSys.hpp\"\n#include \"Runtime/Input/CInputGenerator.hpp\"\n#include \"Runtime/GuiSys/CGuiSys.hpp\"\n#include \"Runtime/CIOWinManager.hpp\"\n#include \"Runtime/GuiSys/CSplashScreen.hpp\"\n#include \"Runtime/MP1/CMainFlow.hpp\"\n#include \"Runtime/GuiSys/CConsoleOutputWindow.hpp\"\n#include \"Runtime/GuiSys/CErrorOutputWindow.hpp\"\n#include \"Runtime/GuiSys/CTextParser.hpp\"\n#include \"Runtime/MP1/CAudioStateWin.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/CArchitectureQueue.hpp\"\n#include \"Runtime/CTimeProvider.hpp\"\n#include \"Runtime/GuiSys/CTextExecuteBuffer.hpp\"\n#include \"Runtime/MP1/Tweaks/CTweakPlayer.hpp\"\n#include \"Runtime/MP1/Tweaks/CTweakGame.hpp\"\n#include \"Runtime/ConsoleVariables/CVarCommons.hpp\"\n\nstruct DiscordUser;\n\nnamespace metaforce {\nclass IFactory;\nclass IObjectStore;\n\nnamespace MP1 {\nclass CMain;\n\nclass CGameGlobalObjects {\n  friend class CMain;\n\n  std::unique_ptr<CSimplePool> m_gameSimplePool;\n  std::unique_ptr<CResFactory> m_gameResFactory;\n  std::unique_ptr<CMemoryCardSys> x0_memoryCardSys;\n  IFactory* x4_resFactory;\n  CSimplePool* xcc_simplePool;\n  CCharacterFactoryBuilder xec_charFactoryBuilder;\n  CAiFuncMap x110_aiFuncMap;\n  std::unique_ptr<CGameState> x134_gameState;\n  TLockedToken<CStringTable> x13c_mainStringTable;\n  CInGameTweakManager x150_tweakManager;\n  std::unique_ptr<IRenderer> m_renderer;\n  TLockedToken<CTextureCache> m_textureCache;\n\n  void LoadStringTable() {\n    x13c_mainStringTable = g_SimplePool->GetObj(\"STRG_Main\");\n    g_MainStringTable = x13c_mainStringTable.GetObj();\n  }\n  void LoadTextureCache() {\n    m_textureCache = g_SimplePool->GetObj(\"TextureCache\"sv);\n    g_TextureCache = m_textureCache.GetObj();\n  }\n  void AddPaksAndFactories();\n  static IRenderer* AllocateRenderer(IObjectStore& store, IFactory& resFactory) {\n    g_Renderer = new CCubeRenderer(store, resFactory);\n    return g_Renderer;\n  }\n\npublic:\n  CGameGlobalObjects(IFactory* resFactory, CSimplePool* objStore)\n  : x4_resFactory(resFactory), xcc_simplePool(objStore) {\n    if (!x4_resFactory) {\n      m_gameResFactory = std::make_unique<CResFactory>();\n      x4_resFactory = m_gameResFactory.get();\n    }\n    if (!xcc_simplePool) {\n      m_gameSimplePool = std::make_unique<CSimplePool>(*x4_resFactory);\n      xcc_simplePool = m_gameSimplePool.get();\n    }\n    g_ResFactory = x4_resFactory;\n    g_SimplePool = xcc_simplePool;\n    g_CharFactoryBuilder = &xec_charFactoryBuilder;\n    g_AiFuncMap = &x110_aiFuncMap;\n    CGraphics::Startup(); // TODO CGraphicsSys\n    x134_gameState = std::make_unique<CGameState>();\n    g_GameState = x134_gameState.get();\n    g_TweakManager = &x150_tweakManager;\n  }\n\n  ~CGameGlobalObjects();\n\n  void PostInitialize();\n\n  void ResetGameState() {\n    x134_gameState = std::make_unique<CGameState>();\n    g_GameState = x134_gameState.get();\n  }\n\n  void StreamInGameState(CInputStream& stream, u32 saveIdx) {\n    x134_gameState = std::make_unique<CGameState>(stream, saveIdx);\n    g_GameState = x134_gameState.get();\n  }\n};\n\nclass CGameArchitectureSupport {\n  friend class CMain;\n  CMain& m_parent;\n  CArchitectureQueue x4_archQueue;\n  CAudioSys x0_audioSys;\n  CInputGenerator x30_inputGenerator;\n  CGuiSys x44_guiSys;\n  CIOWinManager x58_ioWinManager;\n  s32 x78_gameFrameCount = 0;\n\n  enum class EAudioLoadStatus { Loading, Loaded, Uninitialized };\n  EAudioLoadStatus x88_audioLoadStatus = EAudioLoadStatus::Uninitialized;\n  std::vector<TToken<CAudioGroupSet>> x8c_pendingAudioGroups;\n\n  void destroyed() { x4_archQueue.Push(MakeMsg::CreateRemoveAllIOWins(EArchMsgTarget::IOWinManager)); }\n\npublic:\n  CGameArchitectureSupport(CMain& parent);\n  ~CGameArchitectureSupport();\n\n  void PreloadAudio();\n  bool LoadAudio();\n  void UnloadAudio();\n  void UpdateTicks(float dt);\n  void Update(float dt);\n  void Draw();\n\n  CIOWinManager& GetIOWinManager() { return x58_ioWinManager; }\n};\n\nclass CMain : public IMain {\n  friend class CGameArchitectureSupport;\n\nprivate:\n  // COsContext x0_osContext;\n  // CMemorySys x6c_memSys;\n  CTweaks x70_tweaks;\n  EGameplayResult xe4_gameplayResult;\n  double xe8_;\n  rstl::reserved_vector<float, 4> xf0_;\n  rstl::reserved_vector<float, 4> x104_;\n  float x118_;\n  float x11c_;\n  float x120_;\n  float x124_;\n  std::unique_ptr<CGameGlobalObjects> x128_globalObjects;\n  EClientFlowStates x12c_flowState = EClientFlowStates::Default;\n  rstl::reserved_vector<u32, 10> x130_{{\n      1000000,\n      1000000,\n      1000000,\n      1000000,\n      1000000,\n      1000000,\n      1000000,\n      1000000,\n      1000000,\n      1000000,\n  }};\n  // u32 x15c_ = 0;\n  bool x160_24_finished : 1 = false;\n  bool x160_25_mfGameBuilt : 1 = false;\n  bool x160_26_screenFading : 1 = false;\n  bool x160_27_ : 1 = false;\n  bool x160_28_manageCard : 1 = false;\n  bool x160_29_ : 1 = false;\n  bool x160_30_ : 1 = false;\n  bool x160_31_cardBusy : 1 = false;\n  bool x161_24_gameFrameDrawn : 1 = false;\n  std::unique_ptr<CGameArchitectureSupport> x164_archSupport;\n\n  CVarManager* m_cvarMgr = nullptr;\n  bool m_loadedPersistentResources = false;\n  bool m_doQuit = false;\n  bool m_paused = false;\n  MetaforceVersionInfo m_version;\n\n  void InitializeSubsystems();\n  static void InitializeDiscord();\n  static void ShutdownDiscord();\n  static void HandleDiscordReady(const DiscordUser* request);\n  static void HandleDiscordDisconnected(int errorCode, const char* message);\n  static void HandleDiscordErrored(int errorCode, const char* message);\n\npublic:\n  CMain(IFactory* resFactory, CSimplePool* resStore);\n  ~CMain();\n  void RegisterResourceTweaks();\n  void AddWorldPaks();\n  void AddOverridePaks();\n  void ResetGameState();\n  void StreamNewGameState(CInputStream&, u32 idx);\n  void RefreshGameState();\n  void CheckTweakManagerDebugOptions() {}\n  void SetMFGameBuilt(bool b) { x160_25_mfGameBuilt = b; }\n  void SetScreenFading(bool b) { x160_26_screenFading = b; }\n  bool GetScreenFading() const { return x160_26_screenFading; }\n\n  static void UpdateDiscordPresence(CAssetId worldSTRG = {});\n\n  // int RsMain(int argc, char** argv, boo::IAudioVoiceEngine* voiceEngine, amuse::IBackendVoiceAllocator&\n  // backend);\n  std::string Init(int argc, char** argv, const FileStoreManager& storeMgr, CVarManager* cvarManager) override;\n  bool Proc(float dt) override;\n  void Draw() override;\n  void Shutdown() override;\n\n  void MemoryCardInitializePump();\n\n  bool CheckReset() const { return m_doQuit; }\n  bool CheckTerminate() const { return m_doQuit; }\n  void DrawDebugMetrics(double, CStopwatch&) {}\n  void DoPredrawMetrics() {}\n  void FillInAssetIDs();\n  bool LoadAudio();\n  void ShutdownSubsystems();\n  EGameplayResult GetGameplayResult() const { return xe4_gameplayResult; }\n  void SetGameplayResult(EGameplayResult wl) { xe4_gameplayResult = wl; }\n  void SetManageCard(bool v) { x160_28_manageCard = v; }\n  bool GetCardBusy() const { return x160_31_cardBusy; }\n  void SetCardBusy(bool v) { x160_31_cardBusy = v; }\n  void SetGameFrameDrawn() { x161_24_gameFrameDrawn = true; }\n  static void EnsureWorldPaksReady();\n  static void EnsureWorldPakReady(CAssetId mlvl);\n\n  EClientFlowStates GetFlowState() const override { return x12c_flowState; }\n  void SetFlowState(EClientFlowStates s) override { x12c_flowState = s; }\n\n  void SetX30(bool v) { x160_30_ = v; }\n\n  CGameArchitectureSupport* GetArchSupport() const { return x164_archSupport.get(); }\n\n  size_t GetExpectedIdSize() const override { return sizeof(u32); }\n  bool IsPAL() const override { return m_version.region == ERegion::PAL; }\n  bool IsJapanese() const override { return m_version.region == ERegion::JPN; }\n  bool IsUSA() const override { return m_version.region == ERegion::USA; }\n  bool IsKorean() const override { return m_version.region == ERegion::KOR; }\n  bool IsTrilogy() const override { return m_version.game == EGame::MetroidPrimeTrilogy; }\n  ERegion GetRegion() const override { return m_version.region; }\n  EGame GetGame() const override { return m_version.game; }\n  std::string GetGameTitle() const override { return m_version.gameTitle; }\n  std::string_view GetVersionString() const override { return m_version.version; }\n  void Quit() override { m_doQuit = true; }\n  bool IsPaused() const override { return m_paused; }\n  void SetPaused(bool b) override { m_paused = b; }\n\n  int m_warpWorldIdx = -1;\n  TAreaId m_warpAreaId = 0;\n  u64 m_warpLayerBits = 0;\n  std::vector<TEditorId> m_warpMemoryRelays;\n};\n\n} // namespace MP1\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/MP1/Tweaks/CTweakAutoMapper.cpp",
    "content": "#include \"Runtime/MP1/Tweaks/CTweakAutoMapper.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n\n#include \"Runtime/ConsoleVariables/CVar.hpp\"\n#include \"Runtime/ConsoleVariables/CVarManager.hpp\"\n\n#define PREFIX(v) std::string_view(\"tweak.automap.\" #v)\nnamespace metaforce::MP1 {\nnamespace {\nconstexpr std::string_view skShowOneMiniMapArea = PREFIX(ShowOneMiniMapArea);\nconstexpr std::string_view skScaleMoveSpeedWithCamDist = PREFIX(ScaleMoveSpeedWithCamDist);\nconstexpr std::string_view skCamDist = PREFIX(CamDist);\nconstexpr std::string_view skMinCameraDist = PREFIX(MinCamDist);\nconstexpr std::string_view skMaxCamDist = PREFIX(MaxCamDist);\nconstexpr std::string_view skMinCamRotX = PREFIX(MinCamRotX);\nconstexpr std::string_view skMaxCamRotX = PREFIX(MaxCamRotX);\nconstexpr std::string_view skCamAngle = PREFIX(CamAngle);\nconstexpr std::string_view skWidgetColor = PREFIX(WidgetColor);\nconstexpr std::string_view skMiniCamDist = PREFIX(MiniCamDist);\nconstexpr std::string_view skMiniCamXAngle = PREFIX(MiniCamXAngle);\nconstexpr std::string_view skMiniCamAngle = PREFIX(MiniCamAngle);\nconstexpr std::string_view skVisitedSurfaceColor = PREFIX(VisitedSurfaceColor);\nconstexpr std::string_view skVisitedOutlineColor = PREFIX(VisitedOutlineColor);\nconstexpr std::string_view skUnvisitedSurfaceColor = PREFIX(UnvisitedSurfaceColor);\nconstexpr std::string_view skUnvisitedOutlineColor = PREFIX(UnvisitedOutlineColor);\nconstexpr std::string_view skSelectedVisitedSurfaceColor = PREFIX(SelectedVisitedSurfaceColor);\nconstexpr std::string_view skSelectedVisitedOutlineColor = PREFIX(SelectedVisitedOutlineColor);\nconstexpr std::string_view skMapSurfaceNormalColorLinear = PREFIX(MapSurfaceNormalColorLinear);\nconstexpr std::string_view skMapSurfaceNormalColorConstant = PREFIX(MapSurfaceNormalColorConstant);\nCVar* tw_showOneMiniMapArea = nullptr;\nCVar* tw_scaleMoveSpeedWithCamDist = nullptr;\nCVar* tw_camDist = nullptr;\nCVar* tw_minCamDist = nullptr;\nCVar* tw_maxCamDist = nullptr;\nCVar* tw_minCamRotX = nullptr;\nCVar* tw_maxCamRotX = nullptr;\nCVar* tw_camAngle = nullptr;\nCVar* tw_widgetColor = nullptr;\nCVar* tw_miniCamDist = nullptr;\nCVar* tw_miniCamXAngle = nullptr;\nCVar* tw_miniCamAngle = nullptr;\nCVar* tw_visitedsurfaceColor = nullptr;\nCVar* tw_visitedOutlineColor = nullptr;\nCVar* tw_unvisitedSurfaceColor = nullptr;\nCVar* tw_unvisitedOutlineColor = nullptr;\nCVar* tw_selectedVisitedSurfaceColor = nullptr;\nCVar* tw_selectedVisitedOutlineColor = nullptr;\nCVar* tw_mapSurfaceNormColorLinear = nullptr;\nCVar* tw_mapSurfaceNormColorConstant = nullptr;\n} // namespace\n\nCTweakAutoMapper::CTweakAutoMapper(CInputStream& in) {\n  x4_24_showOneMiniMapArea = in.ReadBool();\n  x4_25_ = in.ReadBool();\n  x4_26_scaleMoveSpeedWithCamDist = in.ReadBool();\n  x8_camDist = in.ReadFloat();\n  xc_minCamDist = in.ReadFloat();\n  x10_maxCamDist = in.ReadFloat();\n  x14_minCamRotateX = in.ReadFloat();\n  x18_maxCamRotateX = in.ReadFloat();\n  x1c_camAngle = in.ReadFloat();\n  x20_ = in.ReadFloat();\n  x24_automapperWidgetColor = in.Get<zeus::CColor>();\n  x28_miniCamDist = in.ReadFloat();\n  x2c_miniCamXAngle = in.ReadFloat();\n  x30_miniCamAngle = in.ReadFloat();\n  x34_ = in.ReadFloat();\n  x38_automapperWidgetMiniColor = in.Get<zeus::CColor>();\n  x3c_surfColorVisited = in.Get<zeus::CColor>();\n  x40_outlineColorVisited = in.Get<zeus::CColor>();\n  x44_surfColorUnvisited = in.Get<zeus::CColor>();\n  x48_outlineColorUnvisited = in.Get<zeus::CColor>();\n  x4c_surfaceSelectColorVisited = in.Get<zeus::CColor>();\n  x50_outlineSelectColorVisited = in.Get<zeus::CColor>();\n  x54_mapSurfaceNormColorLinear = in.ReadFloat();\n  x58_mapSurfaceNormColorConstant = in.ReadFloat();\n  x5c_ = in.ReadFloat();\n  x64_openMapScreenTime = in.ReadFloat();\n  x68_closeMapScreenTime = in.ReadFloat();\n  x6c_hintPanTime = in.ReadFloat();\n  x70_zoomUnitsPerFrame = in.ReadFloat();\n  x74_rotateDegPerFrame = in.ReadFloat();\n  x78_baseMapScreenCameraMoveSpeed = in.ReadFloat();\n  x7c_surfaceSelectColorUnvisited = in.Get<zeus::CColor>();\n  x80_outlineSelectColorUnvisited = in.Get<zeus::CColor>();\n  x84_miniAlphaSurfaceVisited = in.ReadFloat();\n  x88_alphaSurfaceVisited = in.ReadFloat();\n  x8c_miniAlphaOutlineVisited = in.ReadFloat();\n  x90_alphaOutlineVisited = in.ReadFloat();\n  x94_miniAlphaSurfaceUnvisited = in.ReadFloat();\n  x98_alphaSurfaceUnvisited = in.ReadFloat();\n  x9c_miniAlphaOutlineUnvisited = in.ReadFloat();\n  xa0_alphaOutlineUnvisited = in.ReadFloat();\n  xa4_doorCenterA = in.ReadFloat();\n  xa8_doorCenterB = in.ReadFloat();\n  xac_doorCenterC = in.ReadFloat();\n  xb0_ = in.ReadFloat();\n  xb4_ = in.ReadFloat();\n  xb8_miniMapViewportWidth = in.ReadFloat();\n  xbc_miniMapViewportHeight = in.ReadFloat();\n  xc0_miniMapCamDistScale = in.ReadFloat();\n  xc4_mapPlaneScaleX = in.ReadFloat();\n  xc8_mapPlaneScaleZ = in.ReadFloat();\n  xcc_ = in.ReadBool();\n  xd0_universeCamDist = in.ReadFloat();\n  xd4_minUniverseCamDist = in.ReadFloat();\n  xd8_maxUniverseCamDist = in.ReadFloat();\n  xdc_switchToFromUniverseTime = in.ReadFloat();\n  xe0_camPanUnitsPerFrame = in.ReadFloat();\n  xe4_automapperScaleX = in.ReadFloat();\n  xe8_automapperScaleZ = in.ReadFloat();\n  xec_camVerticalOffset = in.ReadFloat();\n  xf0_miniMapSamusModColor = in.Get<zeus::CColor>();\n  xf4_areaFlashPulseColor = in.Get<zeus::CColor>();\n  xf8_ = in.Get<zeus::CColor>();\n  xfc_ = in.Get<zeus::CColor>();\n  read_reserved_vector(x100_doorColors, in);\n  x118_doorBorderColor = in.Get<zeus::CColor>();\n  x11c_openDoorColor = in.Get<zeus::CColor>();\n}\nvoid CTweakAutoMapper::_tweakListener(CVar* cv) {\n  if (cv == tw_showOneMiniMapArea) {\n    x4_24_showOneMiniMapArea = cv->toBoolean();\n  } else if (cv == tw_scaleMoveSpeedWithCamDist) {\n    x4_26_scaleMoveSpeedWithCamDist = cv->toBoolean();\n  } else if (cv == tw_camDist) {\n    x8_camDist = cv->toReal();\n  } else if (cv == tw_minCamDist) {\n    xc_minCamDist = cv->toReal();\n  } else if (cv == tw_maxCamDist) {\n    x10_maxCamDist = cv->toReal();\n  } else if (cv == tw_minCamRotX) {\n    x14_minCamRotateX = cv->toReal();\n  } else if (cv == tw_maxCamRotX) {\n    x18_maxCamRotateX = cv->toReal();\n  } else if (cv == tw_camAngle) {\n    x1c_camAngle = cv->toReal();\n  } else if (cv == tw_widgetColor) {\n    x24_automapperWidgetColor = zeus::CColor(cv->toVec4f());\n  } else if (cv == tw_miniCamDist) {\n    x28_miniCamDist = cv->toReal();\n  } else if (cv == tw_miniCamXAngle) {\n    x2c_miniCamXAngle = tw_miniCamXAngle->toReal();\n  } else if (cv == tw_miniCamAngle) {\n    x30_miniCamAngle = tw_miniCamAngle->toReal();\n  } else if (cv == tw_visitedsurfaceColor) {\n    x3c_surfColorVisited = zeus::CColor(tw_visitedsurfaceColor->toVec4f());\n  } else if (cv == tw_visitedOutlineColor) {\n    x40_outlineColorVisited = zeus::CColor(tw_visitedOutlineColor->toVec4f());\n  } else if (cv == tw_unvisitedSurfaceColor) {\n    x44_surfColorUnvisited = zeus::CColor(tw_unvisitedSurfaceColor->toVec4f());\n  } else if (cv == tw_unvisitedOutlineColor) {\n    x48_outlineColorUnvisited = zeus::CColor(tw_unvisitedOutlineColor->toVec4f());\n  } else if (cv == tw_selectedVisitedSurfaceColor) {\n    x4c_surfaceSelectColorVisited = zeus::CColor(tw_selectedVisitedSurfaceColor->toVec4f());\n  } else if (cv == tw_selectedVisitedOutlineColor) {\n    x50_outlineSelectColorVisited = zeus::CColor(tw_selectedVisitedOutlineColor->toVec4f());\n  } else if (cv == tw_mapSurfaceNormColorLinear) {\n    x54_mapSurfaceNormColorLinear = tw_mapSurfaceNormColorLinear->toReal();\n  } else if (cv == tw_mapSurfaceNormColorConstant) {\n    x58_mapSurfaceNormColorConstant = tw_mapSurfaceNormColorConstant->toReal();\n  }\n}\n\nvoid CTweakAutoMapper::initCVars(CVarManager* mgr) {\n  auto assignBool = [this, mgr](std::string_view name, std::string_view desc, bool& v, CVar::EFlags flags) {\n    CVar* cv = mgr->findOrMakeCVar(name, desc, v, flags);\n    // Check if the CVar was deserialized, this avoid an unnecessary conversion\n    if (cv->wasDeserialized())\n      v = cv->toBoolean();\n    cv->addListener([this](CVar* cv) { _tweakListener(cv); });\n    return cv;\n  };\n\n  auto assignRealValue = [this, mgr](std::string_view name, std::string_view desc, float& v, CVar::EFlags flags) {\n    CVar* cv = mgr->findOrMakeCVar(name, desc, v, flags);\n    // Check if the CVar was deserialized, this avoid an unnecessary conversion\n    if (cv->wasDeserialized())\n      v = cv->toReal();\n    cv->addListener([this](CVar* cv) { _tweakListener(cv); });\n    return cv;\n  };\n\n  auto assignColorValue = [this, mgr](std::string_view name, std::string_view desc, zeus::CColor& v,\n                                      CVar::EFlags flags) {\n    zeus::CVector4f vec = v;\n    CVar* cv = mgr->findOrMakeCVar(name, desc, vec, flags | CVar::EFlags::Color);\n    // Check if the CVar was deserialized, this avoid an unnecessary conversion\n    if (cv->wasDeserialized())\n      v = zeus::CColor(cv->toVec4f());\n    cv->addListener([this](CVar* cv) { _tweakListener(cv); });\n    return cv;\n  };\n\n  tw_showOneMiniMapArea = assignBool(skShowOneMiniMapArea, \"\", x4_24_showOneMiniMapArea,\n                                     CVar::EFlags::Game | CVar::EFlags::Gui | CVar::EFlags::Archive);\n  tw_scaleMoveSpeedWithCamDist = assignBool(skScaleMoveSpeedWithCamDist, \"\", x4_26_scaleMoveSpeedWithCamDist,\n                                            CVar::EFlags::Game | CVar::EFlags::Gui | CVar::EFlags::Archive);\n  tw_camDist =\n      assignRealValue(skCamDist, \"\", x8_camDist, CVar::EFlags::Game | CVar::EFlags::Gui | CVar::EFlags::Archive);\n  tw_minCamDist = assignRealValue(skMinCameraDist, \"\", xc_minCamDist,\n                                  CVar::EFlags::Game | CVar::EFlags::Gui | CVar::EFlags::Archive);\n  tw_maxCamDist =\n      assignRealValue(skMaxCamDist, \"\", x10_maxCamDist, CVar::EFlags::Game | CVar::EFlags::Gui | CVar::EFlags::Archive);\n  tw_minCamRotX = assignRealValue(skMinCamRotX, \"\", x14_minCamRotateX,\n                                  CVar::EFlags::Game | CVar::EFlags::Gui | CVar::EFlags::Archive);\n  tw_maxCamRotX = assignRealValue(skMaxCamRotX, \"\", x18_maxCamRotateX,\n                                  CVar::EFlags::Game | CVar::EFlags::Gui | CVar::EFlags::Archive);\n  tw_camAngle =\n      assignRealValue(skCamAngle, \"\", x1c_camAngle, CVar::EFlags::Game | CVar::EFlags::Gui | CVar::EFlags::Archive);\n  tw_widgetColor = assignColorValue(skWidgetColor, \"\", x24_automapperWidgetColor,\n                                    CVar::EFlags::Game | CVar::EFlags::Gui | CVar::EFlags::Archive);\n  tw_miniCamDist = assignRealValue(skMiniCamDist, \"\", x28_miniCamDist,\n                                   CVar::EFlags::Game | CVar::EFlags::Gui | CVar::EFlags::Archive);\n  tw_miniCamXAngle = assignRealValue(skMiniCamXAngle, \"\", x2c_miniCamXAngle,\n                                     CVar::EFlags::Game | CVar::EFlags::Gui | CVar::EFlags::Archive);\n  tw_miniCamAngle = assignRealValue(skMiniCamAngle, \"\", x30_miniCamAngle,\n                                    CVar::EFlags::Game | CVar::EFlags::Gui | CVar::EFlags::Archive);\n  tw_widgetColor = assignColorValue(skWidgetColor, \"\", x38_automapperWidgetMiniColor,\n                                    CVar::EFlags::Game | CVar::EFlags::Gui | CVar::EFlags::Archive);\n  tw_visitedsurfaceColor =\n      assignColorValue(skVisitedSurfaceColor, \"\", x3c_surfColorVisited,\n                       CVar::EFlags::Game | CVar::EFlags::Color | CVar::EFlags::Gui | CVar::EFlags::Archive);\n  tw_visitedOutlineColor =\n      assignColorValue(skVisitedOutlineColor, \"\", x40_outlineColorVisited,\n                       CVar::EFlags::Game | CVar::EFlags::Color | CVar::EFlags::Gui | CVar::EFlags::Archive);\n  tw_unvisitedSurfaceColor =\n      assignColorValue(skUnvisitedSurfaceColor, \"\", x44_surfColorUnvisited,\n                       CVar::EFlags::Game | CVar::EFlags::Color | CVar::EFlags::Gui | CVar::EFlags::Archive);\n  tw_unvisitedOutlineColor = assignColorValue(skUnvisitedOutlineColor, \"\", x48_outlineColorUnvisited,\n                                              CVar::EFlags::Game | CVar::EFlags::Gui | CVar::EFlags::Archive);\n  tw_selectedVisitedSurfaceColor =\n      assignColorValue(skSelectedVisitedSurfaceColor, \"\", x4c_surfaceSelectColorVisited,\n                       CVar::EFlags::Game | CVar::EFlags::Color | CVar::EFlags::Gui | CVar::EFlags::Archive);\n  tw_selectedVisitedOutlineColor =\n      assignColorValue(skSelectedVisitedOutlineColor, \"\", x50_outlineSelectColorVisited,\n                       CVar::EFlags::Game | CVar::EFlags::Color | CVar::EFlags::Gui | CVar::EFlags::Archive);\n  tw_mapSurfaceNormColorLinear =\n      assignRealValue(skMapSurfaceNormalColorLinear, \"\", x54_mapSurfaceNormColorLinear,\n                      CVar::EFlags::Game | CVar::EFlags::Color | CVar::EFlags::Gui | CVar::EFlags::Archive);\n  tw_mapSurfaceNormColorConstant =\n      assignRealValue(skMapSurfaceNormalColorConstant, \"\", x58_mapSurfaceNormColorConstant,\n                      CVar::EFlags::Game | CVar::EFlags::Color | CVar::EFlags::Gui | CVar::EFlags::Archive);\n}\n} // namespace metaforce::MP1"
  },
  {
    "path": "Runtime/MP1/Tweaks/CTweakAutoMapper.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Tweaks/ITweakAutoMapper.hpp\"\n\n#include \"Runtime/rstl.hpp\"\n\nnamespace metaforce::MP1 {\nstruct CTweakAutoMapper final : public Tweaks::ITweakAutoMapper {\n  bool x4_24_showOneMiniMapArea;        // : 1;\n  bool x4_25_;                          // : 1;\n  bool x4_26_scaleMoveSpeedWithCamDist; // : 1;\n  float x8_camDist;\n  float xc_minCamDist;\n  float x10_maxCamDist;\n  float x14_minCamRotateX;\n  float x18_maxCamRotateX;\n  float x1c_camAngle;\n  float x20_;\n  zeus::CColor x24_automapperWidgetColor;\n  float x28_miniCamDist;\n  float x2c_miniCamXAngle;\n  float x30_miniCamAngle;\n  float x34_;\n  zeus::CColor x38_automapperWidgetMiniColor;\n  zeus::CColor x3c_surfColorVisited;\n  zeus::CColor x40_outlineColorVisited;\n  zeus::CColor x44_surfColorUnvisited;\n  zeus::CColor x48_outlineColorUnvisited;\n  zeus::CColor x4c_surfaceSelectColorVisited;\n  zeus::CColor x50_outlineSelectColorVisited;\n  float x54_mapSurfaceNormColorLinear;\n  float x58_mapSurfaceNormColorConstant;\n  float x5c_;\n  float x60_ = 0.4f;\n  float x64_openMapScreenTime;\n  float x68_closeMapScreenTime;\n  float x6c_hintPanTime;\n  float x70_zoomUnitsPerFrame;\n  float x74_rotateDegPerFrame;\n  float x78_baseMapScreenCameraMoveSpeed;\n  zeus::CColor x7c_surfaceSelectColorUnvisited;\n  zeus::CColor x80_outlineSelectColorUnvisited;\n  float x84_miniAlphaSurfaceVisited;\n  float x88_alphaSurfaceVisited;\n  float x8c_miniAlphaOutlineVisited;\n  float x90_alphaOutlineVisited;\n  float x94_miniAlphaSurfaceUnvisited;\n  float x98_alphaSurfaceUnvisited;\n  float x9c_miniAlphaOutlineUnvisited;\n  float xa0_alphaOutlineUnvisited;\n  float xa4_doorCenterA;\n  float xa8_doorCenterB;\n  float xac_doorCenterC;\n  float xb0_;\n  float xb4_;\n  float xb8_miniMapViewportWidth;\n  float xbc_miniMapViewportHeight;\n  float xc0_miniMapCamDistScale;\n  float xc4_mapPlaneScaleX;\n  float xc8_mapPlaneScaleZ;\n  bool xcc_;\n  float xd0_universeCamDist;\n  float xd4_minUniverseCamDist;\n  float xd8_maxUniverseCamDist;\n  float xdc_switchToFromUniverseTime;\n  float xe0_camPanUnitsPerFrame;\n  float xe4_automapperScaleX;\n  float xe8_automapperScaleZ;\n  float xec_camVerticalOffset;\n  zeus::CColor xf0_miniMapSamusModColor;\n  zeus::CColor xf4_areaFlashPulseColor;\n  zeus::CColor xf8_;\n  zeus::CColor xfc_;\n  rstl::reserved_vector<zeus::CColor, 5> x100_doorColors;\n  zeus::CColor x118_doorBorderColor;\n  zeus::CColor x11c_openDoorColor;\n\n  CTweakAutoMapper() = default;\n  CTweakAutoMapper(CInputStream& r);\n  bool GetShowOneMiniMapArea() const override { return x4_24_showOneMiniMapArea; }\n  bool GetScaleMoveSpeedWithCamDist() const override { return x4_26_scaleMoveSpeedWithCamDist; }\n  float GetCamDist() const override { return x8_camDist; }\n  float GetMinCamDist() const override { return xc_minCamDist; }\n  float GetMaxCamDist() const override { return x10_maxCamDist; }\n  float GetMinCamRotateX() const override { return x14_minCamRotateX; }\n  float GetMaxCamRotateX() const override { return x18_maxCamRotateX; }\n  float GetCamAngle() const override { return x1c_camAngle; }\n  const zeus::CColor& GetAutomapperWidgetColor() const override { return x24_automapperWidgetColor; }\n  float GetMiniCamDist() const override { return x28_miniCamDist; }\n  float GetMiniCamXAngle() const override { return x2c_miniCamXAngle; }\n  float GetMiniCamAngle() const override { return x30_miniCamAngle; }\n  const zeus::CColor& GetAutomapperWidgetMiniColor() const override { return x38_automapperWidgetMiniColor; }\n  const zeus::CColor& GetSurfaceVisitedColor() const override { return x3c_surfColorVisited; }\n  const zeus::CColor& GetOutlineVisitedColor() const override { return x40_outlineColorVisited; }\n  const zeus::CColor& GetSurfaceUnvisitedColor() const override { return x44_surfColorUnvisited; }\n  const zeus::CColor& GetOutlineUnvisitedColor() const override { return x48_outlineColorUnvisited; }\n  const zeus::CColor& GetSurfaceSelectVisitedColor() const override { return x4c_surfaceSelectColorVisited; }\n  const zeus::CColor& GetOutlineSelectVisitedColor() const override { return x50_outlineSelectColorVisited; }\n  float GetMapSurfaceNormColorLinear() const override { return x54_mapSurfaceNormColorLinear; }\n  float GetMapSurfaceNormColorConstant() const override { return x58_mapSurfaceNormColorConstant; }\n  float GetOpenMapScreenTime() const override { return x64_openMapScreenTime; }\n  float GetCloseMapScreenTime() const override { return x68_closeMapScreenTime; }\n  float GetHintPanTime() const override { return x6c_hintPanTime; }\n  float GetCamZoomUnitsPerFrame() const override { return x70_zoomUnitsPerFrame; }\n  float GetCamRotateDegreesPerFrame() const override { return x74_rotateDegPerFrame; }\n  float GetBaseMapScreenCameraMoveSpeed() const override { return x78_baseMapScreenCameraMoveSpeed; }\n  const zeus::CColor& GetSurfaceSelectUnvisitedColor() const override { return x7c_surfaceSelectColorUnvisited; }\n  const zeus::CColor& GetOutlineSelectUnvisitedColor() const override { return x80_outlineSelectColorUnvisited; }\n  float GetMiniAlphaSurfaceVisited() const override { return x84_miniAlphaSurfaceVisited; }\n  float GetAlphaSurfaceVisited() const override { return x88_alphaSurfaceVisited; }\n  float GetMiniAlphaOutlineVisited() const override { return x8c_miniAlphaOutlineVisited; }\n  float GetAlphaOutlineVisited() const override { return x90_alphaOutlineVisited; }\n  float GetMiniAlphaSurfaceUnvisited() const override { return x94_miniAlphaSurfaceUnvisited; }\n  float GetAlphaSurfaceUnvisited() const override { return x98_alphaSurfaceUnvisited; }\n  float GetMiniAlphaOutlineUnvisited() const override { return x9c_miniAlphaOutlineUnvisited; }\n  float GetAlphaOutlineUnvisited() const override { return xa0_alphaOutlineUnvisited; }\n  float GetMiniMapViewportWidth() const override { return xb8_miniMapViewportWidth; }\n  float GetMiniMapViewportHeight() const override { return xbc_miniMapViewportHeight; }\n  float GetMiniMapCamDistScale() const override { return xc0_miniMapCamDistScale; }\n  float GetMapPlaneScaleX() const override { return xc4_mapPlaneScaleX; }\n  float GetMapPlaneScaleZ() const override { return xc8_mapPlaneScaleZ; }\n  float GetUniverseCamDist() const override { return xd0_universeCamDist; }\n  float GetMinUniverseCamDist() const override { return xd4_minUniverseCamDist; }\n  float GetMaxUniverseCamDist() const override { return xd8_maxUniverseCamDist; }\n  float GetSwitchToFromUniverseTime() const override { return xdc_switchToFromUniverseTime; }\n  float GetCamPanUnitsPerFrame() const override { return xe0_camPanUnitsPerFrame; }\n  float GetAutomapperScaleX() const override { return xe4_automapperScaleX; }\n  float GetAutomapperScaleZ() const override { return xe8_automapperScaleZ; }\n  float GetCamVerticalOffset() const override { return xec_camVerticalOffset; }\n  const zeus::CColor& GetMiniMapSamusModColor() const override { return xf0_miniMapSamusModColor; }\n  const zeus::CColor& GetAreaFlashPulseColor() const override { return xf4_areaFlashPulseColor; }\n  const zeus::CColor& GetDoorColor(int idx) const override { return x100_doorColors[idx]; }\n  const zeus::CColor& GetOpenDoorColor() const override { return x11c_openDoorColor; }\n  void initCVars(CVarManager*) override;\n\nprivate:\n  void _tweakListener(CVar* cv);\n};\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/Tweaks/CTweakBall.cpp",
    "content": "#include \"Runtime/MP1/Tweaks/CTweakBall.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n\nnamespace metaforce::MP1 {\nCTweakBall::CTweakBall(CInputStream& in) {\n  LoadTweaks(in);\n  x6c_ = -x6c_;\n  x70_ = -x70_;\n  x74_ballCameraAnglePerSecond = zeus::degToRad(x74_ballCameraAnglePerSecond);\n  x90_ = zeus::degToRad(x90_);\n  xe4_ballGravity = -xe4_ballGravity;\n  xe8_ballWaterGravity = -xe8_ballWaterGravity;\n  x15c_ = zeus::degToRad(x15c_);\n  x16c_ = zeus::degToRad(x16c_);\n  x174_ = zeus::degToRad(x174_);\n  x17c_ballCameraChaseDampenAngle = zeus::degToRad(x17c_ballCameraChaseDampenAngle);\n  x184_ballCameraChaseYawSpeed = zeus::degToRad(x184_ballCameraChaseYawSpeed);\n  x188_ballCameraChaseAnglePerSecond = zeus::degToRad(x188_ballCameraChaseAnglePerSecond);\n  x1a8_ballCameraBoostDampenAngle = zeus::degToRad(x1a8_ballCameraBoostDampenAngle);\n  x1b0_ballCameraBoostYawSpeed = zeus::degToRad(x1b0_ballCameraBoostYawSpeed);\n  x1b4_ballCameraBoostAnglePerSecond = zeus::degToRad(x1b4_ballCameraBoostAnglePerSecond);\n  x1ec_maxLeanAngle = zeus::degToRad(x1ec_maxLeanAngle);\n}\n\nvoid CTweakBall::LoadTweaks(CInputStream& in) {\n  /* x4_maxTranslationAcceleration[0] */\n  x4_maxTranslationAcceleration[0] = in.ReadFloat();\n  /* x4_maxTranslationAcceleration[1] */\n  x4_maxTranslationAcceleration[1] = in.ReadFloat();\n  /* x4_maxTranslationAcceleration[2] */\n  x4_maxTranslationAcceleration[2] = in.ReadFloat();\n  /* x4_maxTranslationAcceleration[3] */\n  x4_maxTranslationAcceleration[3] = in.ReadFloat();\n  /* x4_maxTranslationAcceleration[4] */\n  x4_maxTranslationAcceleration[4] = in.ReadFloat();\n  /* x4_maxTranslationAcceleration[5] */\n  x4_maxTranslationAcceleration[5] = in.ReadFloat();\n  /* x4_maxTranslationAcceleration[6] */\n  x4_maxTranslationAcceleration[6] = in.ReadFloat();\n  /* x4_maxTranslationAcceleration[7] */\n  x4_maxTranslationAcceleration[7] = in.ReadFloat();\n  /* x24_translationFriction[0] */\n  x24_translationFriction[0] = in.ReadFloat();\n  /* x24_translationFriction[1] */\n  x24_translationFriction[1] = in.ReadFloat();\n  /* x24_translationFriction[2] */\n  x24_translationFriction[2] = in.ReadFloat();\n  /* x24_translationFriction[3] */\n  x24_translationFriction[3] = in.ReadFloat();\n  /* x24_translationFriction[4] */\n  x24_translationFriction[4] = in.ReadFloat();\n  /* x24_translationFriction[5] */\n  x24_translationFriction[5] = in.ReadFloat();\n  /* x24_translationFriction[6] */\n  x24_translationFriction[6] = in.ReadFloat();\n  /* x24_translationFriction[7] */\n  x24_translationFriction[7] = in.ReadFloat();\n  /* x44_translationMaxSpeed[0] */\n  x44_translationMaxSpeed[0] = in.ReadFloat();\n  /* x44_translationMaxSpeed[1] */\n  x44_translationMaxSpeed[1] = in.ReadFloat();\n  /* x44_translationMaxSpeed[2] */\n  x44_translationMaxSpeed[2] = in.ReadFloat();\n  /* x44_translationMaxSpeed[3] */\n  x44_translationMaxSpeed[3] = in.ReadFloat();\n  /* x44_translationMaxSpeed[4] */\n  x44_translationMaxSpeed[4] = in.ReadFloat();\n  /* x44_translationMaxSpeed[5] */\n  x44_translationMaxSpeed[5] = in.ReadFloat();\n  /* x44_translationMaxSpeed[6] */\n  x44_translationMaxSpeed[6] = in.ReadFloat();\n  /* x44_translationMaxSpeed[7] */\n  x44_translationMaxSpeed[7] = in.ReadFloat();\n  /* x64_ */\n  x64_ = in.ReadFloat();\n  /* x68_ */\n  x68_ = in.ReadFloat();\n  /* x6c_ */\n  x6c_ = in.ReadFloat();\n  /* x70_ */\n  x70_ = in.ReadFloat();\n  /* xc4_ballForwardBrakingAcceleration[0] */\n  xc4_ballForwardBrakingAcceleration[0] = in.ReadFloat();\n  /* xc4_ballForwardBrakingAcceleration[1] */\n  xc4_ballForwardBrakingAcceleration[1] = in.ReadFloat();\n  /* xc4_ballForwardBrakingAcceleration[2] */\n  xc4_ballForwardBrakingAcceleration[2] = in.ReadFloat();\n  /* xc4_ballForwardBrakingAcceleration[3] */\n  xc4_ballForwardBrakingAcceleration[3] = in.ReadFloat();\n  /* xc4_ballForwardBrakingAcceleration[4] */\n  xc4_ballForwardBrakingAcceleration[4] = in.ReadFloat();\n  /* xc4_ballForwardBrakingAcceleration[5] */\n  xc4_ballForwardBrakingAcceleration[5] = in.ReadFloat();\n  /* xc4_ballForwardBrakingAcceleration[6] */\n  xc4_ballForwardBrakingAcceleration[6] = in.ReadFloat();\n  /* xc4_ballForwardBrakingAcceleration[7] */\n  xc4_ballForwardBrakingAcceleration[7] = in.ReadFloat();\n  /* xe4_ballGravity */\n  xe4_ballGravity = in.ReadFloat();\n  /* xe8_ballWaterGravity */\n  xe8_ballWaterGravity = in.ReadFloat();\n  /* x14c_ */\n  x14c_ = in.ReadFloat();\n  /* x150_ */\n  x150_ = in.ReadFloat();\n  /* x158_ */\n  x158_ = in.ReadFloat();\n  /* x1dc_minimumAlignmentSpeed */\n  x1dc_minimumAlignmentSpeed = in.ReadFloat();\n  /* x1e0_tireness */\n  x1e0_tireness = in.ReadFloat();\n  /* x1ec_maxLeanAngle */\n  x1ec_maxLeanAngle = in.ReadFloat();\n  /* x1f0_tireToMarbleThresholdSpeed */\n  x1f0_tireToMarbleThresholdSpeed = in.ReadFloat();\n  /* x1f4_marbleToTireThresholdSpeed */\n  x1f4_marbleToTireThresholdSpeed = in.ReadFloat();\n  /* x1f8_forceToLeanGain */\n  x1f8_forceToLeanGain = in.ReadFloat();\n  /* x1fc_leanTrackingGain */\n  x1fc_leanTrackingGain = in.ReadFloat();\n  /* x74_ballCameraAnglePerSecond */\n  x74_ballCameraAnglePerSecond = in.ReadFloat();\n  /* x78_ballCameraOffset */\n  x78_ballCameraOffset = in.Get<zeus::CVector3f>();\n  /* x84_ballCameraMinSpeedDistance */\n  x84_ballCameraMinSpeedDistance = in.ReadFloat();\n  /* x88_ballCameraMaxSpeedDistance */\n  x88_ballCameraMaxSpeedDistance = in.ReadFloat();\n  /* x8c_ballCameraBackwardsDistance */\n  x8c_ballCameraBackwardsDistance = in.ReadFloat();\n  /* x90_ */\n  x90_ = in.ReadFloat();\n  /* x94_ballCameraSpringConstant */\n  x94_ballCameraSpringConstant = in.ReadFloat();\n  /* x98_ballCameraSpringMax */\n  x98_ballCameraSpringMax = in.ReadFloat();\n  /* x9c_ballCameraSpringTardis */\n  x9c_ballCameraSpringTardis = in.ReadFloat();\n  /* xa0_ballCameraCentroidSpringConstant */\n  xa0_ballCameraCentroidSpringConstant = in.ReadFloat();\n  /* xa4_ballCameraCentroidSpringMax */\n  xa4_ballCameraCentroidSpringMax = in.ReadFloat();\n  /* xa8_ballCameraCentroidSpringTardis */\n  xa8_ballCameraCentroidSpringTardis = in.ReadFloat();\n  /* xac_ballCameraCentroidDistanceSpringConstant */\n  xac_ballCameraCentroidDistanceSpringConstant = in.ReadFloat();\n  /* xb0_ballCameraCentroidDistanceSpringMax */\n  xb0_ballCameraCentroidDistanceSpringMax = in.ReadFloat();\n  /* xb4_ballCameraCentroidDistanceSpringTardis */\n  xb4_ballCameraCentroidDistanceSpringTardis = in.ReadFloat();\n  /* xb8_ballCameraLookAtSpringConstant */\n  xb8_ballCameraLookAtSpringConstant = in.ReadFloat();\n  /* xbc_ballCameraLookAtSpringMax */\n  xbc_ballCameraLookAtSpringMax = in.ReadFloat();\n  /* xc0_ballCameraLookAtSpringTardis */\n  xc0_ballCameraLookAtSpringTardis = in.ReadFloat();\n  /* x154_ */\n  x154_ = in.ReadFloat();\n  /* x15c_ */\n  x15c_ = in.ReadFloat();\n  /* x160_ */\n  x160_ = in.ReadFloat();\n  /* x164_ */\n  x164_ = in.ReadFloat();\n  /* x168_ */\n  x168_ = in.ReadFloat();\n  /* x16c_ */\n  x16c_ = in.ReadFloat();\n  /* x170_conservativeDoorCamDistance */\n  x170_conservativeDoorCamDistance = in.ReadFloat();\n  /* x174_ */\n  x174_ = in.ReadFloat();\n  /* x178_ballCameraChaseElevation */\n  x178_ballCameraChaseElevation = in.ReadFloat();\n  /* x17c_ballCameraChaseDampenAngle */\n  x17c_ballCameraChaseDampenAngle = in.ReadFloat();\n  /* x180_ballCameraChaseDistance */\n  x180_ballCameraChaseDistance = in.ReadFloat();\n  /* x184_ballCameraChaseYawSpeed */\n  x184_ballCameraChaseYawSpeed = in.ReadFloat();\n  /* x188_ballCameraChaseAnglePerSecond */\n  x188_ballCameraChaseAnglePerSecond = in.ReadFloat();\n  /* x18c_ballCameraChaseLookAtOffset */\n  x18c_ballCameraChaseLookAtOffset = in.Get<zeus::CVector3f>();\n  /* x198_ballCameraChaseSpringConstant */\n  x198_ballCameraChaseSpringConstant = in.ReadFloat();\n  /* x19c_ballCameraChaseSpringMax */\n  x19c_ballCameraChaseSpringMax = in.ReadFloat();\n  /* x1a0_ballCameraChaseSpringTardis */\n  x1a0_ballCameraChaseSpringTardis = in.ReadFloat();\n  /* x1a4_ballCameraBoostElevation */\n  x1a4_ballCameraBoostElevation = in.ReadFloat();\n  /* x1a8_ballCameraBoostDampenAngle */\n  x1a8_ballCameraBoostDampenAngle = in.ReadFloat();\n  /* x1ac_ballCameraBoostDistance */\n  x1ac_ballCameraBoostDistance = in.ReadFloat();\n  /* x1b0_ballCameraBoostYawSpeed */\n  x1b0_ballCameraBoostYawSpeed = in.ReadFloat();\n  /* x1b4_ballCameraBoostAnglePerSecond */\n  x1b4_ballCameraBoostAnglePerSecond = in.ReadFloat();\n  /* x1b8_ballCameraBoostLookAtOffset */\n  x1b8_ballCameraBoostLookAtOffset = in.Get<zeus::CVector3f>();\n  /* x1c4_ballCameraBoostSpringConstant */\n  x1c4_ballCameraBoostSpringConstant = in.ReadFloat();\n  /* x1c8_ballCameraBoostSpringMax */\n  x1c8_ballCameraBoostSpringMax = in.ReadFloat();\n  /* x1cc_ballCameraBoostSpringTardis */\n  x1cc_ballCameraBoostSpringTardis = in.ReadFloat();\n  /* x1d0_ballCameraControlDistance */\n  x1d0_ballCameraControlDistance = in.ReadFloat();\n  /* x1d4_ */\n  x1d4_ = in.ReadFloat();\n  /* x1d8_ */\n  x1d8_ = in.ReadFloat();\n  /* x1e4_leftStickDivisor */\n  x1e4_leftStickDivisor = in.ReadFloat();\n  /* x1e8_rightStickDivisor */\n  x1e8_rightStickDivisor = in.ReadFloat();\n  /* x200_ */\n  x200_ = in.ReadFloat();\n  /* x204_ballTouchRadius */\n  x204_ballTouchRadius = in.ReadFloat();\n  /* x20c_boostBallDrainTime */\n  x20c_boostBallDrainTime = in.ReadFloat();\n  /* x218_boostBallMinChargeTime */\n  x218_boostBallMinChargeTime = in.ReadFloat();\n  /* x21c_boostBallMinRelativeSpeedForDamage */\n  x21c_boostBallMinRelativeSpeedForDamage = in.ReadFloat();\n  /* x220_boostBallChargeTime0 */\n  x220_boostBallChargeTime0 = in.ReadFloat();\n  /* x224_boostBallChargeTime1 */\n  x224_boostBallChargeTime1 = in.ReadFloat();\n  /* x210_boostBallMaxChargeTime */\n  x228_boostBallChargeTime2 = x210_boostBallMaxChargeTime = in.ReadFloat();\n  /* x22c_boostBallIncrementalSpeed0 */\n  x22c_boostBallIncrementalSpeed0 = in.ReadFloat();\n  /* x230_boostBallIncrementalSpeed1 */\n  x230_boostBallIncrementalSpeed1 = in.ReadFloat();\n  /* x234_boostBallIncrementalSpeed2 */\n  x234_boostBallIncrementalSpeed2 = in.ReadFloat();\n}\n\nvoid CTweakBall::PutTo(COutputStream& out) {\n  /* x4_maxTranslationAcceleration[0] */\n  out.Put(x4_maxTranslationAcceleration[0]);\n  /* x4_maxTranslationAcceleration[1] */\n  out.Put(x4_maxTranslationAcceleration[1]);\n  /* x4_maxTranslationAcceleration[2] */\n  out.Put(x4_maxTranslationAcceleration[2]);\n  /* x4_maxTranslationAcceleration[3] */\n  out.Put(x4_maxTranslationAcceleration[3]);\n  /* x4_maxTranslationAcceleration[4] */\n  out.Put(x4_maxTranslationAcceleration[4]);\n  /* x4_maxTranslationAcceleration[5] */\n  out.Put(x4_maxTranslationAcceleration[5]);\n  /* x4_maxTranslationAcceleration[6] */\n  out.Put(x4_maxTranslationAcceleration[6]);\n  /* x4_maxTranslationAcceleration[7] */\n  out.Put(x4_maxTranslationAcceleration[7]);\n  /* x24_translationFriction[0] */\n  out.Put(x24_translationFriction[0]);\n  /* x24_translationFriction[1] */\n  out.Put(x24_translationFriction[1]);\n  /* x24_translationFriction[2] */\n  out.Put(x24_translationFriction[2]);\n  /* x24_translationFriction[3] */\n  out.Put(x24_translationFriction[3]);\n  /* x24_translationFriction[4] */\n  out.Put(x24_translationFriction[4]);\n  /* x24_translationFriction[5] */\n  out.Put(x24_translationFriction[5]);\n  /* x24_translationFriction[6] */\n  out.Put(x24_translationFriction[6]);\n  /* x24_translationFriction[7] */\n  out.Put(x24_translationFriction[7]);\n  /* x44_translationMaxSpeed[0] */\n  out.Put(x44_translationMaxSpeed[0]);\n  /* x44_translationMaxSpeed[1] */\n  out.Put(x44_translationMaxSpeed[1]);\n  /* x44_translationMaxSpeed[2] */\n  out.Put(x44_translationMaxSpeed[2]);\n  /* x44_translationMaxSpeed[3] */\n  out.Put(x44_translationMaxSpeed[3]);\n  /* x44_translationMaxSpeed[4] */\n  out.Put(x44_translationMaxSpeed[4]);\n  /* x44_translationMaxSpeed[5] */\n  out.Put(x44_translationMaxSpeed[5]);\n  /* x44_translationMaxSpeed[6] */\n  out.Put(x44_translationMaxSpeed[6]);\n  /* x44_translationMaxSpeed[7] */\n  out.Put(x44_translationMaxSpeed[7]);\n  /* x64_ */\n  out.Put(x64_);\n  /* x68_ */\n  out.Put(x68_);\n  /* x6c_ */\n  out.Put(x6c_);\n  /* x70_ */\n  out.Put(x70_);\n  /* xc4_ballForwardBrakingAcceleration[0] */\n  out.Put(xc4_ballForwardBrakingAcceleration[0]);\n  /* xc4_ballForwardBrakingAcceleration[1] */\n  out.Put(xc4_ballForwardBrakingAcceleration[1]);\n  /* xc4_ballForwardBrakingAcceleration[2] */\n  out.Put(xc4_ballForwardBrakingAcceleration[2]);\n  /* xc4_ballForwardBrakingAcceleration[3] */\n  out.Put(xc4_ballForwardBrakingAcceleration[3]);\n  /* xc4_ballForwardBrakingAcceleration[4] */\n  out.Put(xc4_ballForwardBrakingAcceleration[4]);\n  /* xc4_ballForwardBrakingAcceleration[5] */\n  out.Put(xc4_ballForwardBrakingAcceleration[5]);\n  /* xc4_ballForwardBrakingAcceleration[6] */\n  out.Put(xc4_ballForwardBrakingAcceleration[6]);\n  /* xc4_ballForwardBrakingAcceleration[7] */\n  out.Put(xc4_ballForwardBrakingAcceleration[7]);\n  /* xe4_ballGravity */\n  out.Put(xe4_ballGravity);\n  /* xe8_ballWaterGravity */\n  out.Put(xe8_ballWaterGravity);\n  /* x14c_ */\n  out.Put(x14c_);\n  /* x150_ */\n  out.Put(x150_);\n  /* x158_ */\n  out.Put(x158_);\n  /* x1dc_minimumAlignmentSpeed */\n  out.Put(x1dc_minimumAlignmentSpeed);\n  /* x1e0_tireness */\n  out.Put(x1e0_tireness);\n  /* x1ec_maxLeanAngle */\n  out.Put(x1ec_maxLeanAngle);\n  /* x1f0_tireToMarbleThresholdSpeed */\n  out.Put(x1f0_tireToMarbleThresholdSpeed);\n  /* x1f4_marbleToTireThresholdSpeed */\n  out.Put(x1f4_marbleToTireThresholdSpeed);\n  /* x1f8_forceToLeanGain */\n  out.Put(x1f8_forceToLeanGain);\n  /* x1fc_leanTrackingGain */\n  out.Put(x1fc_leanTrackingGain);\n  /* x74_ballCameraAnglePerSecond */\n  out.Put(x74_ballCameraAnglePerSecond);\n  /* x78_ballCameraOffset */\n  out.Put(x78_ballCameraOffset);\n  /* x84_ballCameraMinSpeedDistance */\n  out.Put(x84_ballCameraMinSpeedDistance);\n  /* x88_ballCameraMaxSpeedDistance */\n  out.Put(x88_ballCameraMaxSpeedDistance);\n  /* x8c_ballCameraBackwardsDistance */\n  out.Put(x8c_ballCameraBackwardsDistance);\n  /* x90_ */\n  out.Put(x90_);\n  /* x94_ballCameraSpringConstant */\n  out.Put(x94_ballCameraSpringConstant);\n  /* x98_ballCameraSpringMax */\n  out.Put(x98_ballCameraSpringMax);\n  /* x9c_ballCameraSpringTardis */\n  out.Put(x9c_ballCameraSpringTardis);\n  /* xa0_ballCameraCentroidSpringConstant */\n  out.Put(xa0_ballCameraCentroidSpringConstant);\n  /* xa4_ballCameraCentroidSpringMax */\n  out.Put(xa4_ballCameraCentroidSpringMax);\n  /* xa8_ballCameraCentroidSpringTardis */\n  out.Put(xa8_ballCameraCentroidSpringTardis);\n  /* xac_ballCameraCentroidDistanceSpringConstant */\n  out.Put(xac_ballCameraCentroidDistanceSpringConstant);\n  /* xb0_ballCameraCentroidDistanceSpringMax */\n  out.Put(xb0_ballCameraCentroidDistanceSpringMax);\n  /* xb4_ballCameraCentroidDistanceSpringTardis */\n  out.Put(xb4_ballCameraCentroidDistanceSpringTardis);\n  /* xb8_ballCameraLookAtSpringConstant */\n  out.Put(xb8_ballCameraLookAtSpringConstant);\n  /* xbc_ballCameraLookAtSpringMax */\n  out.Put(xbc_ballCameraLookAtSpringMax);\n  /* xc0_ballCameraLookAtSpringTardis */\n  out.Put(xc0_ballCameraLookAtSpringTardis);\n  /* x154_ */\n  out.Put(x154_);\n  /* x15c_ */\n  out.Put(x15c_);\n  /* x160_ */\n  out.Put(x160_);\n  /* x164_ */\n  out.Put(x164_);\n  /* x168_ */\n  out.Put(x168_);\n  /* x16c_ */\n  out.Put(x16c_);\n  /* x170_conservativeDoorCamDistance */\n  out.Put(x170_conservativeDoorCamDistance);\n  /* x174_ */\n  out.Put(x174_);\n  /* x178_ballCameraChaseElevation */\n  out.Put(x178_ballCameraChaseElevation);\n  /* x17c_ballCameraChaseDampenAngle */\n  out.Put(x17c_ballCameraChaseDampenAngle);\n  /* x180_ballCameraChaseDistance */\n  out.Put(x180_ballCameraChaseDistance);\n  /* x184_ballCameraChaseYawSpeed */\n  out.Put(x184_ballCameraChaseYawSpeed);\n  /* x188_ballCameraChaseAnglePerSecond */\n  out.Put(x188_ballCameraChaseAnglePerSecond);\n  /* x18c_ballCameraChaseLookAtOffset */\n  out.Put(x18c_ballCameraChaseLookAtOffset);\n  /* x198_ballCameraChaseSpringConstant */\n  out.Put(x198_ballCameraChaseSpringConstant);\n  /* x19c_ballCameraChaseSpringMax */\n  out.Put(x19c_ballCameraChaseSpringMax);\n  /* x1a0_ballCameraChaseSpringTardis */\n  out.Put(x1a0_ballCameraChaseSpringTardis);\n  /* x1a4_ballCameraBoostElevation */\n  out.Put(x1a4_ballCameraBoostElevation);\n  /* x1a8_ballCameraBoostDampenAngle */\n  out.Put(x1a8_ballCameraBoostDampenAngle);\n  /* x1ac_ballCameraBoostDistance */\n  out.Put(x1ac_ballCameraBoostDistance);\n  /* x1b0_ballCameraBoostYawSpeed */\n  out.Put(x1b0_ballCameraBoostYawSpeed);\n  /* x1b4_ballCameraBoostAnglePerSecond */\n  out.Put(x1b4_ballCameraBoostAnglePerSecond);\n  /* x1b8_ballCameraBoostLookAtOffset */\n  out.Put(x1b8_ballCameraBoostLookAtOffset);\n  /* x1c4_ballCameraBoostSpringConstant */\n  out.Put(x1c4_ballCameraBoostSpringConstant);\n  /* x1c8_ballCameraBoostSpringMax */\n  out.Put(x1c8_ballCameraBoostSpringMax);\n  /* x1cc_ballCameraBoostSpringTardis */\n  out.Put(x1cc_ballCameraBoostSpringTardis);\n  /* x1d0_ballCameraControlDistance */\n  out.Put(x1d0_ballCameraControlDistance);\n  /* x1d4_ */\n  out.Put(x1d4_);\n  /* x1d8_ */\n  out.Put(x1d8_);\n  /* x1e4_leftStickDivisor */\n  out.Put(x1e4_leftStickDivisor);\n  /* x1e8_rightStickDivisor */\n  out.Put(x1e8_rightStickDivisor);\n  /* x200_ */\n  out.Put(x200_);\n  /* x204_ballTouchRadius */\n  out.Put(x204_ballTouchRadius);\n  /* x20c_boostBallDrainTime */\n  out.Put(x20c_boostBallDrainTime);\n  /* x218_boostBallMinChargeTime */\n  out.Put(x218_boostBallMinChargeTime);\n  /* x21c_boostBallMinRelativeSpeedForDamage */\n  out.Put(x21c_boostBallMinRelativeSpeedForDamage);\n  /* x220_boostBallChargeTime0 */\n  out.Put(x220_boostBallChargeTime0);\n  /* x224_boostBallChargeTime1 */\n  out.Put(x224_boostBallChargeTime1);\n  /* x210_boostBallMaxChargeTime */\n  out.Put(x210_boostBallMaxChargeTime);\n  /* x22c_boostBallIncrementalSpeed0 */\n  out.Put(x22c_boostBallIncrementalSpeed0);\n  /* x230_boostBallIncrementalSpeed1 */\n  out.Put(x230_boostBallIncrementalSpeed1);\n  /* x234_boostBallIncrementalSpeed2 */\n  out.Put(x234_boostBallIncrementalSpeed2);\n}\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/Tweaks/CTweakBall.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Tweaks/ITweakBall.hpp\"\n#include \"zeus/CVector3f.hpp\"\n\nnamespace metaforce::MP1 {\nstruct CTweakBall final : public Tweaks::ITweakBall {\n  float x4_maxTranslationAcceleration[8];\n  float x24_translationFriction[8];\n  float x44_translationMaxSpeed[8];\n  float x64_;\n  float x68_;\n  float x6c_;\n  float x70_;\n  float x74_ballCameraAnglePerSecond;\n  zeus::CVector3f x78_ballCameraOffset;\n  float x84_ballCameraMinSpeedDistance;\n  float x88_ballCameraMaxSpeedDistance;\n  float x8c_ballCameraBackwardsDistance;\n  float x90_;\n  float x94_ballCameraSpringConstant;\n  float x98_ballCameraSpringMax;\n  float x9c_ballCameraSpringTardis;\n  float xa0_ballCameraCentroidSpringConstant;\n  float xa4_ballCameraCentroidSpringMax;\n  float xa8_ballCameraCentroidSpringTardis;\n  float xac_ballCameraCentroidDistanceSpringConstant;\n  float xb0_ballCameraCentroidDistanceSpringMax;\n  float xb4_ballCameraCentroidDistanceSpringTardis;\n  float xb8_ballCameraLookAtSpringConstant;\n  float xbc_ballCameraLookAtSpringMax;\n  float xc0_ballCameraLookAtSpringTardis;\n  float xc4_ballForwardBrakingAcceleration[8];\n  float xe4_ballGravity;\n  float xe8_ballWaterGravity;\n  float xec_ = 10000.f;\n  float xf0_ = 1000.f;\n  float xf4_ = 40000.f;\n  float xf8_ = 40000.f;\n  float xfc_ = 40000.f;\n  float x100_ = 40000.f;\n  float x104_ = 40000.f;\n  float x108_ = 40000.f;\n  float x10c_ = 10000.f;\n  float x110_ = 1000.f;\n  float x114_ = 40000.f;\n  float x118_ = 40000.f;\n  float x11c_ = 40000.f;\n  float x120_ = 40000.f;\n  float x124_ = 40000.f;\n  float x128_ = 40000.f;\n  float x12c_ballSlipFactor[8] = {10000.f, 10000.f, 1000.f, 10000.f, 2000.f, 2000.f, 2000.f, 2000.f};\n  float x14c_;\n  float x150_;\n  float x158_;\n  float x154_;\n  float x15c_;\n  float x160_;\n  float x164_;\n  float x168_;\n  float x16c_;\n  float x170_conservativeDoorCamDistance;\n  float x174_;\n  float x178_ballCameraChaseElevation;\n  float x17c_ballCameraChaseDampenAngle;\n  float x180_ballCameraChaseDistance;\n  float x184_ballCameraChaseYawSpeed;\n  float x188_ballCameraChaseAnglePerSecond;\n  zeus::CVector3f x18c_ballCameraChaseLookAtOffset;\n  float x198_ballCameraChaseSpringConstant;\n  float x19c_ballCameraChaseSpringMax;\n  float x1a0_ballCameraChaseSpringTardis;\n  float x1a4_ballCameraBoostElevation;\n  float x1a8_ballCameraBoostDampenAngle;\n  float x1ac_ballCameraBoostDistance;\n  float x1b0_ballCameraBoostYawSpeed;\n  float x1b4_ballCameraBoostAnglePerSecond;\n  zeus::CVector3f x1b8_ballCameraBoostLookAtOffset;\n  float x1c4_ballCameraBoostSpringConstant;\n  float x1c8_ballCameraBoostSpringMax;\n  float x1cc_ballCameraBoostSpringTardis;\n  float x1d0_ballCameraControlDistance;\n  float x1d4_;\n  float x1d8_;\n  float x1dc_minimumAlignmentSpeed;\n  float x1e0_tireness;\n  float x1ec_maxLeanAngle;\n  float x1f0_tireToMarbleThresholdSpeed;\n  float x1f4_marbleToTireThresholdSpeed;\n  float x1f8_forceToLeanGain;\n  float x1fc_leanTrackingGain;\n  float x1e4_leftStickDivisor;\n  float x1e8_rightStickDivisor;\n  float x200_;\n  float x204_ballTouchRadius;\n  float x208_;\n  float x20c_boostBallDrainTime;\n  float x218_boostBallMinChargeTime;\n  float x21c_boostBallMinRelativeSpeedForDamage;\n  float x220_boostBallChargeTime0;\n  float x224_boostBallChargeTime1;\n  float x228_boostBallChargeTime2;\n  float x210_boostBallMaxChargeTime;\n  float x22c_boostBallIncrementalSpeed0;\n  float x230_boostBallIncrementalSpeed1;\n  float x234_boostBallIncrementalSpeed2;\n\n  CTweakBall() = default;\n  CTweakBall(CInputStream& r);\n  float GetMaxBallTranslationAcceleration(int s) const override { return x4_maxTranslationAcceleration[s]; }\n  float GetBallTranslationFriction(int s) const override { return x24_translationFriction[s]; }\n  float GetBallTranslationMaxSpeed(int s) const override { return x44_translationMaxSpeed[s]; }\n  float GetBallCameraElevation() const override { return 2.736f; }\n  float GetBallCameraAnglePerSecond() const override { return x74_ballCameraAnglePerSecond; }\n  const zeus::CVector3f& GetBallCameraOffset() const override { return x78_ballCameraOffset; }\n  float GetBallCameraMinSpeedDistance() const override { return x84_ballCameraMinSpeedDistance; }\n  float GetBallCameraMaxSpeedDistance() const override { return x88_ballCameraMaxSpeedDistance; }\n  float GetBallCameraBackwardsDistance() const override { return x8c_ballCameraBackwardsDistance; }\n  float GetBallCameraSpringConstant() const override { return x94_ballCameraSpringConstant; }\n  float GetBallCameraSpringMax() const override { return x98_ballCameraSpringMax; }\n  float GetBallCameraSpringTardis() const override { return x9c_ballCameraSpringTardis; }\n  float GetBallCameraCentroidSpringConstant() const override { return xa0_ballCameraCentroidSpringConstant; }\n  float GetBallCameraCentroidSpringMax() const override { return xa4_ballCameraCentroidSpringMax; }\n  float GetBallCameraCentroidSpringTardis() const override { return xa8_ballCameraCentroidSpringTardis; }\n  float GetBallCameraCentroidDistanceSpringConstant() const override {\n    return xac_ballCameraCentroidDistanceSpringConstant;\n  }\n  float GetBallCameraCentroidDistanceSpringMax() const override { return xb0_ballCameraCentroidDistanceSpringMax; }\n  float GetBallCameraCentroidDistanceSpringTardis() const override {\n    return xb4_ballCameraCentroidDistanceSpringTardis;\n  }\n  float GetBallCameraLookAtSpringConstant() const override { return xb8_ballCameraLookAtSpringConstant; }\n  float GetBallCameraLookAtSpringMax() const override { return xbc_ballCameraLookAtSpringMax; }\n  float GetBallCameraLookAtSpringTardis() const override { return xc0_ballCameraLookAtSpringTardis; }\n  float GetBallForwardBrakingAcceleration(int s) const override { return xc4_ballForwardBrakingAcceleration[s]; }\n  float GetBallGravity() const override { return xe4_ballGravity; }\n  float GetBallWaterGravity() const override { return xe8_ballWaterGravity; }\n  float GetBallSlipFactor(int s) const override { return x12c_ballSlipFactor[s]; }\n  float GetConservativeDoorCameraDistance() const override { return x170_conservativeDoorCamDistance; }\n  float GetBallCameraChaseElevation() const override { return x178_ballCameraChaseElevation; }\n  float GetBallCameraChaseDampenAngle() const override { return x17c_ballCameraChaseDampenAngle; }\n  float GetBallCameraChaseDistance() const override { return x180_ballCameraChaseDistance; }\n  float GetBallCameraChaseYawSpeed() const override { return x184_ballCameraChaseYawSpeed; }\n  float GetBallCameraChaseAnglePerSecond() const override { return x188_ballCameraChaseAnglePerSecond; }\n  const zeus::CVector3f& GetBallCameraChaseLookAtOffset() const override { return x18c_ballCameraChaseLookAtOffset; }\n  float GetBallCameraChaseSpringConstant() const override { return x198_ballCameraChaseSpringConstant; }\n  float GetBallCameraChaseSpringMax() const override { return x19c_ballCameraChaseSpringMax; }\n  float GetBallCameraChaseSpringTardis() const override { return x1a0_ballCameraChaseSpringTardis; }\n  float GetBallCameraBoostElevation() const override { return x1a4_ballCameraBoostElevation; }\n  float GetBallCameraBoostDampenAngle() const override { return x1a8_ballCameraBoostDampenAngle; }\n  float GetBallCameraBoostDistance() const override { return x1ac_ballCameraBoostDistance; }\n  float GetBallCameraBoostYawSpeed() const override { return x1b0_ballCameraBoostYawSpeed; }\n  float GetBallCameraBoostAnglePerSecond() const override { return x1b4_ballCameraBoostAnglePerSecond; }\n  const zeus::CVector3f& GetBallCameraBoostLookAtOffset() const override { return x1b8_ballCameraBoostLookAtOffset; }\n  float GetBallCameraBoostSpringConstant() const override { return x1c4_ballCameraBoostSpringConstant; }\n  float GetBallCameraBoostSpringMax() const override { return x1c8_ballCameraBoostSpringMax; }\n  float GetBallCameraBoostSpringTardis() const override { return x1cc_ballCameraBoostSpringTardis; }\n  float GetBallCameraControlDistance() const override { return x1d0_ballCameraControlDistance; }\n  float GetMinimumAlignmentSpeed() const override { return x1dc_minimumAlignmentSpeed; }\n  float GetTireness() const override { return x1e0_tireness; }\n  float GetMaxLeanAngle() const override { return x1ec_maxLeanAngle; }\n  float GetTireToMarbleThresholdSpeed() const override { return x1f0_tireToMarbleThresholdSpeed; }\n  float GetMarbleToTireThresholdSpeed() const override { return x1f4_marbleToTireThresholdSpeed; }\n  float GetForceToLeanGain() const override { return x1f8_forceToLeanGain; }\n  float GetLeanTrackingGain() const override { return x1fc_leanTrackingGain; }\n  float GetLeftStickDivisor() const override { return x1e4_leftStickDivisor; }\n  float GetRightStickDivisor() const override { return x1e8_rightStickDivisor; }\n  float GetBallTouchRadius() const override { return x204_ballTouchRadius; }\n  float GetBoostBallDrainTime() const override { return x20c_boostBallDrainTime; }\n  float GetBoostBallMaxChargeTime() const override { return x210_boostBallMaxChargeTime; }\n  float GetBoostBallMinChargeTime() const override { return x218_boostBallMinChargeTime; }\n  float GetBoostBallMinRelativeSpeedForDamage() const override { return x21c_boostBallMinRelativeSpeedForDamage; }\n  float GetBoostBallChargeTimeTable(int i) const override {\n    switch (i) {\n    default:\n    case 0:\n      return x220_boostBallChargeTime0;\n    case 1:\n      return x224_boostBallChargeTime1;\n    case 2:\n      return x228_boostBallChargeTime2;\n    }\n  }\n  float GetBoostBallIncrementalSpeedTable(int i) const override {\n    switch (i) {\n    default:\n    case 0:\n      return x22c_boostBallIncrementalSpeed0;\n    case 1:\n      return x230_boostBallIncrementalSpeed1;\n    case 2:\n      return x234_boostBallIncrementalSpeed2;\n    }\n  }\n\n  void LoadTweaks(CInputStream& in);\n  void PutTo(COutputStream& out);\n};\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/Tweaks/CTweakGame.cpp",
    "content": "#include \"Runtime/MP1/Tweaks/CTweakGame.hpp\"\n#include \"Runtime/Camera/CCameraManager.hpp\"\n\n#include \"Runtime/Streams/CInputStream.hpp\"\n\n#include \"ConsoleVariables/CVar.hpp\"\n#include \"ConsoleVariables/CVarManager.hpp\"\n\n#define DEFINE_CVAR_GLOBAL(name)                                                                                       \\\n  constexpr std::string_view sk##name = std::string_view(\"tweak.game.\" #name);                                         \\\n  CVar* tw_##name = nullptr;\n\n#define CREATE_CVAR(name, help, value, flags)                                                                          \\\n  tw_##name = mgr->findOrMakeCVar(sk##name, help, value, flags);                                                       \\\n  if (tw_##name->wasDeserialized()) {                                                                                  \\\n    tw_##name->toValue(value);                                                                                         \\\n  }                                                                                                                    \\\n  tw_##name->addListener([this](CVar* cv) { _tweakListener(cv); });\n\n#define CREATE_CVAR_BITFIELD(name, help, value, flags)                                                                 \\\n  {                                                                                                                    \\\n    bool tmp = value;                                                                                                  \\\n    CREATE_CVAR(name, help, tmp, flags)                                                                                \\\n  }\n\n#define UPDATE_CVAR(name, cv, value)                                                                                   \\\n  if ((cv) == tw_##name) {                                                                                             \\\n    (cv)->toValue(value);                                                                                              \\\n    return;                                                                                                            \\\n  }\n\n#define UPDATE_CVAR_BITFIELD(name, cv, value)                                                                          \\\n  {                                                                                                                    \\\n    bool tmp = value;                                                                                                  \\\n    UPDATE_CVAR(name, cv, tmp)                                                                                         \\\n    (value) = tmp;                                                                                                     \\\n  }\n\nnamespace metaforce::MP1 {\n\nCTweakGame::CTweakGame(CInputStream& in) {\n  x4_worldPrefix = in.Get<std::string>();\n  x14_defaultRoom = in.Get<std::string>();\n  x24_fov = in.ReadFloat();\n  x28_unknown1 = in.ReadBool();\n  x29_unknown2 = in.ReadBool();\n  x2a_unknown3 = in.ReadBool();\n  x2b_splashScreensDisabled = in.ReadBool();\n  x2c_unknown5 = in.ReadFloat();\n  x30_pressStartDelay = in.ReadFloat();\n  x34_wavecapIntensityNormal = in.ReadFloat();\n  x38_wavecapIntensityPoison = in.ReadFloat();\n  x3c_wavecapIntensityLava = in.ReadFloat();\n  x40_rippleIntensityNormal = in.ReadFloat();\n  x44_rippleIntensityPoison = in.ReadFloat();\n  x48_rippleIntensityLava = in.ReadFloat();\n  x4c_fluidEnvBumpScale = in.ReadFloat();\n  x50_waterFogDistanceBase = in.ReadFloat();\n  x54_waterFogDistanceRange = in.ReadFloat();\n  x58_gravityWaterFogDistanceBase = in.ReadFloat();\n  x5c_gravityWaterFogDistanceRange = in.ReadFloat();\n  x60_hardmodeDamageMult = in.ReadFloat();\n  x64_hardmodeWeaponMult = in.ReadFloat();\n#ifdef NDEBUG\n  x2b_splashScreensDisabled = false;\n#endif\n}\n\nDEFINE_CVAR_GLOBAL(WorldPrefix);\nDEFINE_CVAR_GLOBAL(FieldOfView);\nDEFINE_CVAR_GLOBAL(SplashScreensDisabled);\nDEFINE_CVAR_GLOBAL(PressStartDelay);\nDEFINE_CVAR_GLOBAL(WavecapIntensityNormal);\nDEFINE_CVAR_GLOBAL(WavecapIntensityPoison);\nDEFINE_CVAR_GLOBAL(WavecapIntensityLava);\nDEFINE_CVAR_GLOBAL(RippleIntensityNormal);\nDEFINE_CVAR_GLOBAL(RippleIntensityPoison);\nDEFINE_CVAR_GLOBAL(RippleIntensityLava);\nDEFINE_CVAR_GLOBAL(FluidEnvBumpScale);\nDEFINE_CVAR_GLOBAL(WaterFogDistanceBase);\nDEFINE_CVAR_GLOBAL(WaterFogDistanceRange);\nDEFINE_CVAR_GLOBAL(GravityWaterFogDistanceBase);\nDEFINE_CVAR_GLOBAL(GravityWaterFogDistanceRange);\nDEFINE_CVAR_GLOBAL(HardModeDamageMult);\nDEFINE_CVAR_GLOBAL(HardModeWeaponMult);\n\nvoid CTweakGame::_tweakListener(CVar* cv) {\n  UPDATE_CVAR(WorldPrefix, cv, x4_worldPrefix);\n  UPDATE_CVAR(FieldOfView, cv, x24_fov);\n  UPDATE_CVAR(SplashScreensDisabled, cv, x2b_splashScreensDisabled);\n  UPDATE_CVAR(PressStartDelay, cv, x30_pressStartDelay);\n  UPDATE_CVAR(WavecapIntensityNormal, cv, x34_wavecapIntensityNormal);\n  UPDATE_CVAR(WavecapIntensityPoison, cv, x38_wavecapIntensityPoison);\n  UPDATE_CVAR(WavecapIntensityLava, cv, x3c_wavecapIntensityLava);\n  UPDATE_CVAR(RippleIntensityNormal, cv, x40_rippleIntensityNormal);\n  UPDATE_CVAR(RippleIntensityPoison, cv, x44_rippleIntensityPoison);\n  UPDATE_CVAR(RippleIntensityLava, cv, x48_rippleIntensityLava);\n  UPDATE_CVAR(FluidEnvBumpScale, cv, x4c_fluidEnvBumpScale);\n  UPDATE_CVAR(WaterFogDistanceBase, cv, x50_waterFogDistanceBase);\n  UPDATE_CVAR(WaterFogDistanceRange, cv, x54_waterFogDistanceRange);\n  UPDATE_CVAR(GravityWaterFogDistanceBase, cv, x58_gravityWaterFogDistanceBase);\n  UPDATE_CVAR(GravityWaterFogDistanceRange, cv, x5c_gravityWaterFogDistanceRange);\n  UPDATE_CVAR(HardModeDamageMult, cv, x60_hardmodeDamageMult);\n  UPDATE_CVAR(HardModeWeaponMult, cv, x64_hardmodeWeaponMult);\n}\n\nvoid CTweakGame::initCVars(CVarManager* mgr) {\n  constexpr CVar::EFlags skDefaultFlags = CVar::EFlags::Game | CVar::EFlags::Archive;\n  CREATE_CVAR(WorldPrefix, \"\", x4_worldPrefix, skDefaultFlags);\n  CREATE_CVAR(FieldOfView, \"\", x24_fov, skDefaultFlags);\n  CREATE_CVAR(SplashScreensDisabled, \"\", x2b_splashScreensDisabled, skDefaultFlags);\n  CREATE_CVAR(PressStartDelay, \"\", x30_pressStartDelay, skDefaultFlags);\n  CREATE_CVAR(WavecapIntensityNormal, \"\", x34_wavecapIntensityNormal, skDefaultFlags);\n  CREATE_CVAR(WavecapIntensityPoison, \"\", x38_wavecapIntensityPoison, skDefaultFlags);\n  CREATE_CVAR(WavecapIntensityLava, \"\", x3c_wavecapIntensityLava, skDefaultFlags);\n  CREATE_CVAR(RippleIntensityNormal, \"\", x40_rippleIntensityNormal, skDefaultFlags);\n  CREATE_CVAR(RippleIntensityPoison, \"\", x44_rippleIntensityPoison, skDefaultFlags);\n  CREATE_CVAR(RippleIntensityLava, \"\", x48_rippleIntensityLava, skDefaultFlags);\n  CREATE_CVAR(FluidEnvBumpScale, \"\", x4c_fluidEnvBumpScale, skDefaultFlags);\n  CREATE_CVAR(WaterFogDistanceBase, \"\", x50_waterFogDistanceBase, skDefaultFlags);\n  CREATE_CVAR(WaterFogDistanceRange, \"\", x54_waterFogDistanceRange, skDefaultFlags);\n  CREATE_CVAR(GravityWaterFogDistanceBase, \"\", x58_gravityWaterFogDistanceBase, skDefaultFlags);\n  CREATE_CVAR(GravityWaterFogDistanceRange, \"\", x5c_gravityWaterFogDistanceRange, skDefaultFlags);\n  CREATE_CVAR(HardModeDamageMult, \"\", x60_hardmodeDamageMult, skDefaultFlags);\n  CREATE_CVAR(HardModeWeaponMult, \"\", x64_hardmodeWeaponMult, skDefaultFlags);\n}\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/Tweaks/CTweakGame.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Tweaks/ITweakGame.hpp\"\n\nnamespace hecl {\nclass CVar;\n} // namespace hecl\n\nnamespace metaforce {\nclass CInputStream;\nnamespace MP1 {\n\n#define DEFINE_CVAR_GLOBAL(name) extern CVar* tw_##name;\n\nDEFINE_CVAR_GLOBAL(WorldPrefix);\nDEFINE_CVAR_GLOBAL(FieldOfView);\nDEFINE_CVAR_GLOBAL(SplashScreensDisabled);\nDEFINE_CVAR_GLOBAL(PressStartDelay);\nDEFINE_CVAR_GLOBAL(WavecapIntensityNormal);\nDEFINE_CVAR_GLOBAL(WavecapIntensityPoison);\nDEFINE_CVAR_GLOBAL(WavecapIntensityLava);\nDEFINE_CVAR_GLOBAL(RippleIntensityNormal);\nDEFINE_CVAR_GLOBAL(RippleIntensityPoison);\nDEFINE_CVAR_GLOBAL(RippleIntensityLava);\nDEFINE_CVAR_GLOBAL(FluidEnvBumpScale);\nDEFINE_CVAR_GLOBAL(WaterFogDistanceBase);\nDEFINE_CVAR_GLOBAL(WaterFogDistanceRange);\nDEFINE_CVAR_GLOBAL(GravityWaterFogDistanceBase);\nDEFINE_CVAR_GLOBAL(GravityWaterFogDistanceRange);\nDEFINE_CVAR_GLOBAL(HardModeDamageMult);\nDEFINE_CVAR_GLOBAL(HardModeWeaponMult);\n\n#undef DEFINE_CVAR_GLOBAL\n\nstruct CTweakGame final : Tweaks::ITweakGame {\n  std::string x4_worldPrefix;\n  std::string x14_defaultRoom;\n  float x24_fov{};\n  bool x28_unknown1{};\n  bool x29_unknown2{};\n  bool x2a_unknown3{};\n  bool x2b_splashScreensDisabled{};\n  float x2c_unknown5{};\n  float x30_pressStartDelay{};\n  float x34_wavecapIntensityNormal{};\n  float x38_wavecapIntensityPoison{};\n  float x3c_wavecapIntensityLava{};\n  float x40_rippleIntensityNormal{};\n  float x44_rippleIntensityPoison{};\n  float x48_rippleIntensityLava{};\n  float x4c_fluidEnvBumpScale{};\n  float x50_waterFogDistanceBase{};\n  float x54_waterFogDistanceRange{};\n  float x58_gravityWaterFogDistanceBase{};\n  float x5c_gravityWaterFogDistanceRange{};\n  float x60_hardmodeDamageMult{};\n  float x64_hardmodeWeaponMult{};\n\n  std::string_view GetWorldPrefix() const override { return x4_worldPrefix; }\n  std::string_view GetDefaultRoom() const { return x14_defaultRoom; }\n  bool GetSplashScreensDisabled() const override { return x2b_splashScreensDisabled; }\n  float GetFirstPersonFOV() const override { return x24_fov; }\n  float GetPressStartDelay() const override { return x30_pressStartDelay; }\n  float GetWavecapIntensityNormal() const override { return x34_wavecapIntensityNormal; }\n  float GetWavecapIntensityPoison() const override { return x38_wavecapIntensityPoison; }\n  float GetWavecapIntensityLava() const override { return x3c_wavecapIntensityLava; }\n  float GetRippleIntensityNormal() const override { return x40_rippleIntensityNormal; }\n  float GetRippleIntensityPoison() const override { return x44_rippleIntensityPoison; }\n  float GetRippleIntensityLava() const override { return x48_rippleIntensityLava; }\n  float GetFluidEnvBumpScale() const override { return x4c_fluidEnvBumpScale; }\n  float GetWaterFogDistanceBase() const override { return x50_waterFogDistanceBase; }\n  float GetWaterFogDistanceRange() const override { return x54_waterFogDistanceRange; }\n  float GetGravityWaterFogDistanceBase() const override { return x58_gravityWaterFogDistanceBase; }\n  float GetGravityWaterFogDistanceRange() const override { return x5c_gravityWaterFogDistanceRange; }\n  float GetHardModeDamageMultiplier() const override { return x60_hardmodeDamageMult; }\n  float GetHardModeWeaponMultiplier() const override { return x64_hardmodeWeaponMult; }\n  CTweakGame() = default;\n  CTweakGame(CInputStream& in);\n\n  void initCVars(CVarManager* mgr) override;\n\nprivate:\n  void _tweakListener(CVar* cv);\n};\n} // namespace MP1\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/MP1/Tweaks/CTweakGui.cpp",
    "content": "#include \"Runtime/MP1/Tweaks/CTweakGui.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n\nnamespace metaforce::MP1 {\nCTweakGui::CTweakGui(CInputStream& in)\n: x4_(in.ReadBool())\n, x8_mapAlphaInterp(in.ReadFloat())\n, xc_pauseBlurFactor(in.ReadFloat())\n, x10_radarXYRadius(in.ReadFloat())\n, x14_(in.ReadFloat())\n, x18_(in.ReadFloat())\n, x1c_(in.ReadFloat())\n, x20_(in.ReadFloat())\n, x24_radarZRadius(in.ReadFloat())\n, x28_radarZCloseRadius(in.ReadFloat())\n, x30_(in.ReadFloat())\n, x34_energyBarFilledSpeed(in.ReadFloat())\n, x38_energyBarShadowSpeed(in.ReadFloat())\n, x3c_energyBarDrainDelay(in.ReadFloat())\n, x40_energyBarAlwaysResetDelay(in.ReadBool())\n, x44_hudDamagePracticalsGainConstant(in.ReadFloat())\n, x48_hudDamagePracticalsGainLinear(in.ReadFloat())\n, x4c_hudDamagePracticalsInitConstant(in.ReadFloat())\n, x50_hudDamagePracticalsInitLinear(in.ReadFloat())\n, x54_hudDamageLightSpotAngle(in.ReadFloat())\n, x58_damageLightAngleC(in.ReadFloat())\n, x5c_damageLightAngleL(in.ReadFloat())\n, x60_damageLightAngleQ(in.ReadFloat())\n, x64_damageLightPreTranslate(in.Get<zeus::CVector3f>())\n, x70_damageLightCenterTranslate(in.Get<zeus::CVector3f>())\n, x7c_damageLightXfXAngle(in.ReadFloat())\n, x80_damageLightXfZAngle(in.ReadFloat())\n, x84_hudDecoShakeTranslateVelConstant(in.ReadFloat())\n, x88_hudDecoShakeTranslateVelLinear(in.ReadFloat())\n, x8c_maxDecoDamageShakeTranslate(in.ReadFloat())\n, x90_decoDamageShakeDeceleration(in.ReadFloat())\n, x94_decoShakeGainConstant(in.ReadFloat())\n, x98_decoShakeGainLinear(in.ReadFloat())\n, x9c_decoShakeInitConstant(in.ReadFloat())\n, xa0_decoShakeInitLinear(in.ReadFloat())\n, xa4_maxDecoDamageShakeRotate(in.ReadFloat())\n, xa8_hudCamFovTweak(in.ReadLong())\n, xac_hudCamYTweak(in.ReadLong())\n, xb0_hudCamZTweak(in.ReadLong())\n, xb4_(in.ReadFloat())\n, xb8_(in.ReadFloat())\n, xbc_(in.ReadFloat())\n, xc0_beamVisorMenuAnimTime(in.ReadFloat())\n, xc4_visorBeamMenuItemActiveScale(in.ReadFloat())\n, xc8_visorBeamMenuItemInactiveScale(in.ReadFloat())\n, xcc_visorBeamMenuItemTranslate(in.ReadFloat())\n, xd0_(in.ReadFloat())\n, xd4_(in.ReadLong())\n, xd8_(in.ReadFloat())\n, xdc_(in.ReadFloat())\n, xe0_(in.ReadFloat())\n, xe4_threatRange(in.ReadFloat())\n, xe8_radarScopeCoordRadius(in.ReadFloat())\n, xec_radarPlayerPaintRadius(in.ReadFloat())\n, xf0_radarEnemyPaintRadius(in.ReadFloat())\n, xf4_missileArrowVisTime(in.ReadFloat())\n, xf8_hudVisMode(EHudVisMode(in.ReadLong()))\n, xfc_helmetVisMode(EHelmetVisMode(in.ReadLong()))\n, x100_enableAutoMapper(in.ReadLong())\n, x104_(in.ReadLong())\n, x108_enableTargetingManager(in.ReadLong())\n, x10c_enablePlayerVisor(in.ReadLong())\n, x110_threatWarningFraction(in.ReadFloat())\n, x114_missileWarningFraction(in.ReadFloat())\n, x118_freeLookFadeTime(in.ReadFloat())\n, x11c_(in.ReadFloat())\n, x120_(in.ReadFloat())\n, x124_(in.ReadFloat())\n, x128_(in.ReadFloat())\n, x12c_freeLookSfxPitchScale(in.ReadFloat())\n, x130_noAbsoluteFreeLookSfxPitch(in.ReadBool())\n, x134_(in.ReadFloat())\n, x138_(in.ReadFloat())\n, x13c_faceReflectionOrthoWidth(in.ReadLong())\n, x140_faceReflectionOrthoHeight(in.ReadLong())\n, x144_faceReflectionDistance(in.ReadLong())\n, x148_faceReflectionHeight(in.ReadLong())\n, x14c_faceReflectionAspect(in.ReadLong())\n, x150_(in.Get<std::string>())\n, x160_(in.Get<std::string>())\n, x170_(in.Get<std::string>())\n, x180_(in.Get<std::string>())\n, x190_(in.Get<std::string>())\n, x1a0_missileWarningPulseTime(in.ReadFloat())\n, x1a4_explosionLightFalloffMultConstant(in.ReadFloat())\n, x1a8_explosionLightFalloffMultLinear(in.ReadFloat())\n, x1ac_explosionLightFalloffMultQuadratic(in.ReadFloat())\n, x1b0_(in.ReadFloat())\n, x1b4_hudDamagePeakFactor(in.ReadFloat())\n, x1b8_hudDamageFilterGainConstant(in.ReadFloat())\n, x1bc_hudDamageFilterGainLinear(in.ReadFloat())\n, x1c0_hudDamageFilterInitConstant(in.ReadFloat())\n, x1c4_hudDamageFilterInitLinear(in.ReadFloat())\n, x1c8_energyDrainModPeriod(in.ReadFloat())\n, x1cc_energyDrainSinusoidalPulse(in.ReadBool())\n, x1cd_energyDrainFilterAdditive(in.ReadBool())\n, x1d0_hudDamagePulseDuration(in.ReadFloat())\n, x1d4_hudDamageColorGain(in.ReadFloat())\n, x1d8_hudDecoShakeTranslateGain(in.ReadFloat())\n, x1dc_hudLagOffsetScale(in.ReadFloat())\n, x1e0_(in.ReadFloat())\n, x1e4_(in.ReadFloat())\n, x1e8_(in.ReadFloat())\n, x1ec_(in.ReadFloat())\n, x1f0_(in.ReadFloat())\n, x1f4_(in.ReadFloat())\n, x1f8_(in.ReadFloat())\n, x1fc_(in.ReadFloat())\n, x20c_(in.ReadFloat())\n, x210_scanSidesAngle(in.ReadFloat())\n, x214_scanSidesXScale(in.ReadFloat())\n, x218_scanSidesPositionEnd(in.ReadFloat())\n, x21c_(in.ReadFloat())\n, x220_scanSidesDuration(in.ReadFloat())\n, x224_scanSidesStartTime(in.ReadFloat())\n, x22c_scanDataDotRadius(in.ReadFloat())\n, x230_scanDataDotPosRandMag(in.ReadFloat())\n, x234_scanDataDotSeekDurationMin(in.ReadFloat())\n, x238_scanDataDotSeekDurationMax(in.ReadFloat())\n, x23c_scanDataDotHoldDurationMin(in.ReadFloat())\n, x240_scanDataDotHoldDurationMax(in.ReadFloat())\n, x244_scanAppearanceDuration(in.ReadFloat())\n, x248_scanPaneFlashFactor(in.ReadFloat())\n, x24c_scanPaneFadeInTime(in.ReadFloat())\n, x250_scanPaneFadeOutTime(in.ReadFloat())\n, x254_ballViewportYReduction(in.ReadFloat())\n, x258_scanWindowIdleW(in.ReadFloat())\n, x25c_scanWindowIdleH(in.ReadFloat())\n, x260_scanWindowActiveW(in.ReadFloat())\n, x264_scanWindowActiveH(in.ReadFloat())\n, x268_scanWindowMagnification(in.ReadFloat())\n, x26c_scanWindowScanningAspect(in.ReadFloat())\n, x270_scanSidesPositionStart(in.ReadFloat())\n, x274_showAutomapperInMorphball(in.ReadBool())\n, x278_wtMgrCharsPerSfx(in.ReadFloat())\n, x27c_xrayFogMode(in.ReadLong())\n, x280_xrayFogNearZ(in.ReadFloat())\n, x284_xrayFogFarZ(in.ReadFloat())\n, x288_xrayFogColor(in.Get<zeus::CColor>())\n, x28c_thermalVisorLevel(in.ReadFloat())\n, x290_thermalVisorColor(in.Get<zeus::CColor>()) {\n  for (u32 i = 0; i < 4; ++i) {\n    x294_hudLightAddPerVisor[i] = in.Get<zeus::CColor>();\n  }\n  for (u32 i = 0; i < 4; ++i) {\n    x2a4_hudLightMultiplyPerVisor[i] = in.Get<zeus::CColor>();\n  }\n\n  x2b4_hudReflectivityLightColor = in.Get<zeus::CColor>();\n  x2b8_hudLightAttMulConstant = in.ReadFloat();\n  x2bc_hudLightAttMulLinear = in.ReadFloat();\n  x2c0_hudLightAttMulQuadratic = in.ReadFloat();\n  read_reserved_vector(x2c4_scanSpeeds, in);\n  x2d0_creditsTable = in.Get<std::string>();\n  x2e0_creditsFont = in.Get<std::string>();\n  x2f0_japaneseCreditsFont = in.Get<std::string>();\n  x300_ = in.Get<zeus::CColor>();\n  x304_ = in.Get<zeus::CColor>();\n  x308_ = in.ReadFloat();\n  x30c_ = in.ReadFloat();\n  x310_ = in.ReadFloat();\n  x314_ = in.Get<std::string>();\n  x324_ = in.Get<std::string>();\n  x334_ = in.Get<std::string>();\n  x344_ = in.Get<zeus::CColor>();\n  x348_ = in.Get<zeus::CColor>();\n  x34c_ = in.Get<zeus::CColor>();\n  x350_ = in.Get<zeus::CColor>();\n  x354_ = in.Get<zeus::CColor>();\n  x358_ = in.Get<zeus::CColor>();\n  x35c_ = in.ReadFloat();\n  x360_ = in.ReadFloat();\n  x364_ = in.ReadFloat();\n\n  FixupValues();\n}\n\n} // namespace metaforce::MP1"
  },
  {
    "path": "Runtime/MP1/Tweaks/CTweakGui.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Tweaks/ITweakGui.hpp\"\n#include \"rstl.hpp\"\n\nnamespace metaforce::MP1 {\nstruct CTweakGui final : Tweaks::ITweakGui {\n  bool x4_{};\n  float x8_mapAlphaInterp{};\n  float xc_pauseBlurFactor{};\n  float x10_radarXYRadius{};\n  float x14_{};\n  float x18_{};\n  float x1c_{};\n  float x20_{};\n  float x24_radarZRadius{};\n  float x28_radarZCloseRadius{};\n  u32 x2c_ = 0;\n  float x30_{};\n  float x34_energyBarFilledSpeed{};\n  float x38_energyBarShadowSpeed{};\n  float x3c_energyBarDrainDelay{};\n  bool x40_energyBarAlwaysResetDelay{};\n  float x44_hudDamagePracticalsGainConstant{};\n  float x48_hudDamagePracticalsGainLinear{};\n  float x4c_hudDamagePracticalsInitConstant{};\n  float x50_hudDamagePracticalsInitLinear{};\n  float x54_hudDamageLightSpotAngle{};\n  float x58_damageLightAngleC{};\n  float x5c_damageLightAngleL{};\n  float x60_damageLightAngleQ{};\n  zeus::CVector3f x64_damageLightPreTranslate;\n  zeus::CVector3f x70_damageLightCenterTranslate;\n  float x7c_damageLightXfXAngle{};\n  float x80_damageLightXfZAngle{};\n  float x84_hudDecoShakeTranslateVelConstant{};\n  float x88_hudDecoShakeTranslateVelLinear{};\n  float x8c_maxDecoDamageShakeTranslate{};\n  float x90_decoDamageShakeDeceleration{};\n  float x94_decoShakeGainConstant{};\n  float x98_decoShakeGainLinear{};\n  float x9c_decoShakeInitConstant{};\n  float xa0_decoShakeInitLinear{};\n  float xa4_maxDecoDamageShakeRotate{};\n  u32 xa8_hudCamFovTweak{};\n  u32 xac_hudCamYTweak{};\n  u32 xb0_hudCamZTweak{};\n  float xb4_{};\n  float xb8_{};\n  float xbc_{};\n  float xc0_beamVisorMenuAnimTime{};\n  float xc4_visorBeamMenuItemActiveScale{};\n  float xc8_visorBeamMenuItemInactiveScale{};\n  float xcc_visorBeamMenuItemTranslate{};\n  float xd0_{};\n  u32 xd4_{};\n  float xd8_{};\n  float xdc_{};\n  float xe0_{};\n  float xe4_threatRange{};\n  float xe8_radarScopeCoordRadius{};\n  float xec_radarPlayerPaintRadius{};\n  float xf0_radarEnemyPaintRadius{};\n  float xf4_missileArrowVisTime{};\n  EHudVisMode xf8_hudVisMode;\n  EHelmetVisMode xfc_helmetVisMode;\n  u32 x100_enableAutoMapper{};\n  u32 x104_{};\n  u32 x108_enableTargetingManager{};\n  u32 x10c_enablePlayerVisor{};\n  float x110_threatWarningFraction{};\n  float x114_missileWarningFraction{};\n  float x118_freeLookFadeTime{};\n  float x11c_{};\n  float x120_{};\n  float x124_{};\n  float x128_{};\n  float x12c_freeLookSfxPitchScale{};\n  bool x130_noAbsoluteFreeLookSfxPitch{};\n  float x134_{};\n  float x138_{};\n  u32 x13c_faceReflectionOrthoWidth{};\n  u32 x140_faceReflectionOrthoHeight{};\n  u32 x144_faceReflectionDistance{};\n  u32 x148_faceReflectionHeight{};\n  u32 x14c_faceReflectionAspect{};\n  std::string x150_;\n  std::string x160_;\n  std::string x170_;\n  std::string x180_;\n  std::string x190_;\n  float x1a0_missileWarningPulseTime{};\n  float x1a4_explosionLightFalloffMultConstant{};\n  float x1a8_explosionLightFalloffMultLinear{};\n  float x1ac_explosionLightFalloffMultQuadratic{};\n  float x1b0_{};\n  float x1b4_hudDamagePeakFactor{};\n  float x1b8_hudDamageFilterGainConstant{};\n  float x1bc_hudDamageFilterGainLinear{};\n  float x1c0_hudDamageFilterInitConstant{};\n  float x1c4_hudDamageFilterInitLinear{};\n  float x1c8_energyDrainModPeriod{};\n  bool x1cc_energyDrainSinusoidalPulse{};\n  bool x1cd_energyDrainFilterAdditive{};\n  float x1d0_hudDamagePulseDuration{};\n  float x1d4_hudDamageColorGain{};\n  float x1d8_hudDecoShakeTranslateGain{};\n  float x1dc_hudLagOffsetScale{};\n  float x1e0_{};\n  float x1e4_{};\n  float x1e8_{};\n  float x1ec_{};\n  float x1f0_{};\n  float x1f4_{};\n  float x1f8_{};\n  float x1fc_{};\n  zeus::CColor x200_;\n  float x204_xrayBlurScaleLinear = 0.0014f;\n  float x208_xrayBlurScaleQuadratic = 0.0000525f;\n  float x20c_{};\n  float x210_scanSidesAngle{};\n  float x214_scanSidesXScale{};\n  float x218_scanSidesPositionEnd{};\n  float x21c_{};\n  float x220_scanSidesDuration{};\n  float x224_scanSidesStartTime{};\n  float x228_scanSidesEndTime{};\n  float x22c_scanDataDotRadius{};\n  float x230_scanDataDotPosRandMag{};\n  float x234_scanDataDotSeekDurationMin{};\n  float x238_scanDataDotSeekDurationMax{};\n  float x23c_scanDataDotHoldDurationMin{};\n  float x240_scanDataDotHoldDurationMax{};\n  float x244_scanAppearanceDuration{};\n  float x248_scanPaneFlashFactor{};\n  float x24c_scanPaneFadeInTime{};\n  float x250_scanPaneFadeOutTime{};\n  float x254_ballViewportYReduction{};\n  float x258_scanWindowIdleW{};\n  float x25c_scanWindowIdleH{};\n  float x260_scanWindowActiveW{};\n  float x264_scanWindowActiveH{};\n  float x268_scanWindowMagnification{};\n  float x26c_scanWindowScanningAspect{};\n  float x270_scanSidesPositionStart{};\n  bool x274_showAutomapperInMorphball{};\n  bool x275_latchArticleText = true;\n  float x278_wtMgrCharsPerSfx{};\n  u32 x27c_xrayFogMode{};\n  float x280_xrayFogNearZ{};\n  float x284_xrayFogFarZ{};\n  zeus::CColor x288_xrayFogColor;\n  float x28c_thermalVisorLevel{};\n  zeus::CColor x290_thermalVisorColor;\n  std::array<zeus::CColor, 4> x294_hudLightAddPerVisor;\n  std::array<zeus::CColor, 4> x2a4_hudLightMultiplyPerVisor;\n  zeus::CColor x2b4_hudReflectivityLightColor;\n  float x2b8_hudLightAttMulConstant{};\n  float x2bc_hudLightAttMulLinear{};\n  float x2c0_hudLightAttMulQuadratic{};\n  rstl::reserved_vector<float, 2> x2c4_scanSpeeds;\n  std::string x2d0_creditsTable;\n  std::string x2e0_creditsFont;\n  std::string x2f0_japaneseCreditsFont;\n  zeus::CColor x300_;\n  zeus::CColor x304_;\n  float x308_{};\n  float x30c_{};\n  float x310_{};\n  std::string x314_;\n  std::string x324_;\n  std::string x334_;\n  zeus::CColor x344_;\n  zeus::CColor x348_;\n  zeus::CColor x34c_;\n  zeus::CColor x350_;\n  zeus::CColor x354_;\n  zeus::CColor x358_;\n  float x35c_{};\n  float x360_{};\n  float x364_{};\n\n  CTweakGui() = default;\n  CTweakGui(CInputStream& r);\n  float GetMapAlphaInterpolant() const override { return x8_mapAlphaInterp; }\n  float GetPauseBlurFactor() const override { return xc_pauseBlurFactor; }\n  float GetRadarXYRadius() const override { return x10_radarXYRadius; }\n  float GetRadarZRadius() const override { return x24_radarZRadius; }\n  float GetRadarZCloseRadius() const override { return x28_radarZCloseRadius; }\n  float GetEnergyBarFilledSpeed() const override { return x34_energyBarFilledSpeed; }\n  float GetEnergyBarShadowSpeed() const override { return x38_energyBarShadowSpeed; }\n  float GetEnergyBarDrainDelay() const override { return x3c_energyBarDrainDelay; }\n  bool GetEnergyBarAlwaysResetDelay() const override { return x40_energyBarAlwaysResetDelay; }\n  float GetHudDamagePracticalsGainConstant() const override { return x44_hudDamagePracticalsGainConstant; }\n  float GetHudDamagePracticalsGainLinear() const override { return x48_hudDamagePracticalsGainLinear; }\n  float GetHudDamagePracticalsInitConstant() const override { return x4c_hudDamagePracticalsInitConstant; }\n  float GetHudDamagePracticalsInitLinear() const override { return x50_hudDamagePracticalsInitLinear; }\n  float GetHudDamageLightSpotAngle() const override { return x54_hudDamageLightSpotAngle; }\n  float GetDamageLightAngleC() const override { return x58_damageLightAngleC; }\n  float GetDamageLightAngleL() const override { return x5c_damageLightAngleL; }\n  float GetDamageLightAngleQ() const override { return x60_damageLightAngleQ; }\n  zeus::CVector3f GetDamageLightPreTranslate() const override { return x64_damageLightPreTranslate; }\n  zeus::CVector3f GetDamageLightCenterTranslate() const override { return x70_damageLightCenterTranslate; }\n  float GetDamageLightXfXAngle() const override { return x7c_damageLightXfXAngle; }\n  float GetDamageLightXfZAngle() const override { return x80_damageLightXfZAngle; }\n  float GetHudDecoShakeTranslateVelConstant() const override { return x84_hudDecoShakeTranslateVelConstant; }\n  float GetHudDecoShakeTranslateVelLinear() const override { return x88_hudDecoShakeTranslateVelLinear; }\n  float GetMaxDecoDamageShakeTranslate() const override { return x8c_maxDecoDamageShakeTranslate; }\n  float GetDecoDamageShakeDeceleration() const override { return x90_decoDamageShakeDeceleration; }\n  float GetDecoShakeGainConstant() const override { return x94_decoShakeGainConstant; }\n  float GetDecoShakeGainLinear() const override { return x98_decoShakeGainLinear; }\n  float GetDecoShakeInitConstant() const override { return x9c_decoShakeInitConstant; }\n  float GetDecoShakeInitLinear() const override { return xa0_decoShakeInitLinear; }\n  float GetMaxDecoDamageShakeRotate() const override { return xa4_maxDecoDamageShakeRotate; }\n  u32 GetHudCamFovTweak() const override { return xa8_hudCamFovTweak; }\n  u32 GetHudCamYTweak() const override { return xac_hudCamYTweak; }\n  u32 GetHudCamZTweak() const override { return xb0_hudCamZTweak; }\n  float GetBeamVisorMenuAnimTime() const override { return xc0_beamVisorMenuAnimTime; }\n  float GetVisorBeamMenuItemActiveScale() const override { return xc4_visorBeamMenuItemActiveScale; }\n  float GetVisorBeamMenuItemInactiveScale() const override { return xc8_visorBeamMenuItemInactiveScale; }\n  float GetVisorBeamMenuItemTranslate() const override { return xcc_visorBeamMenuItemTranslate; }\n  float GetThreatRange() const override { return xe4_threatRange; }\n  float GetRadarScopeCoordRadius() const override { return xe8_radarScopeCoordRadius; }\n  float GetRadarPlayerPaintRadius() const override { return xec_radarPlayerPaintRadius; }\n  float GetRadarEnemyPaintRadius() const override { return xf0_radarEnemyPaintRadius; }\n  float GetMissileArrowVisTime() const override { return xf4_missileArrowVisTime; }\n  EHudVisMode GetHudVisMode() const override { return xf8_hudVisMode; }\n  EHelmetVisMode GetHelmetVisMode() const override { return xfc_helmetVisMode; }\n  u32 GetEnableAutoMapper() const override { return x100_enableAutoMapper; }\n  u32 GetEnableTargetingManager() const override { return x108_enableTargetingManager; }\n  u32 GetEnablePlayerVisor() const override { return x10c_enablePlayerVisor; }\n  float GetThreatWarningFraction() const override { return x110_threatWarningFraction; }\n  float GetMissileWarningFraction() const override { return x114_missileWarningFraction; }\n  float GetFreeLookFadeTime() const override { return x118_freeLookFadeTime; }\n  float GetFreeLookSfxPitchScale() const override { return x12c_freeLookSfxPitchScale; }\n  bool GetNoAbsoluteFreeLookSfxPitch() const override { return x130_noAbsoluteFreeLookSfxPitch; }\n  float GetFaceReflectionOrthoWidth() const override { return x13c_faceReflectionOrthoWidth; }\n  float GetFaceReflectionOrthoHeight() const override { return x140_faceReflectionOrthoHeight; }\n  float GetFaceReflectionDistance() const override { return x144_faceReflectionDistance; }\n  float GetFaceReflectionHeight() const override { return x148_faceReflectionHeight; }\n  float GetFaceReflectionAspect() const override { return x14c_faceReflectionAspect; }\n  float GetMissileWarningPulseTime() const override { return x1a0_missileWarningPulseTime; }\n  float GetExplosionLightFalloffMultConstant() const override { return x1a4_explosionLightFalloffMultConstant; }\n  float GetExplosionLightFalloffMultLinear() const override { return x1a8_explosionLightFalloffMultLinear; }\n  float GetExplosionLightFalloffMultQuadratic() const override { return x1ac_explosionLightFalloffMultQuadratic; }\n  float GetHudDamagePeakFactor() const override { return x1b4_hudDamagePeakFactor; }\n  float GetHudDamageFilterGainConstant() const override { return x1b8_hudDamageFilterGainConstant; }\n  float GetHudDamageFilterGainLinear() const override { return x1bc_hudDamageFilterGainLinear; }\n  float GetHudDamageFilterInitConstant() const override { return x1c0_hudDamageFilterInitConstant; }\n  float GetHudDamageFilterInitLinear() const override { return x1c4_hudDamageFilterInitLinear; }\n  float GetEnergyDrainModPeriod() const override { return x1c8_energyDrainModPeriod; }\n  bool GetEnergyDrainSinusoidalPulse() const override { return x1cc_energyDrainSinusoidalPulse; }\n  bool GetEnergyDrainFilterAdditive() const override { return x1cd_energyDrainFilterAdditive; }\n  float GetHudDamagePulseDuration() const override { return x1d0_hudDamagePulseDuration; }\n  float GetHudDamageColorGain() const override { return x1d4_hudDamageColorGain; }\n  float GetHudDecoShakeTranslateGain() const override { return x1d8_hudDecoShakeTranslateGain; }\n  float GetHudLagOffsetScale() const override { return x1dc_hudLagOffsetScale; }\n  float GetXrayBlurScaleLinear() const override { return x204_xrayBlurScaleLinear; }\n  float GetXrayBlurScaleQuadratic() const override { return x208_xrayBlurScaleQuadratic; }\n  float GetScanSidesAngle() const override { return x210_scanSidesAngle; }\n  float GetScanSidesXScale() const override { return x214_scanSidesXScale; }\n  float GetScanSidesPositionEnd() const override { return x218_scanSidesPositionEnd; }\n  float GetScanSidesDuration() const override { return x220_scanSidesDuration; }\n  float GetScanSidesStartTime() const override { return x224_scanSidesStartTime; }\n  float GetScanSidesEndTime() const override { return x228_scanSidesEndTime; }\n  float GetScanDataDotRadius() const override { return x22c_scanDataDotRadius; }\n  float GetScanDataDotPosRandMagnitude() const override { return x230_scanDataDotPosRandMag; }\n  float GetScanDataDotSeekDurationMin() const override { return x234_scanDataDotSeekDurationMin; }\n  float GetScanDataDotSeekDurationMax() const override { return x238_scanDataDotSeekDurationMax; }\n  float GetScanDataDotHoldDurationMin() const override { return x23c_scanDataDotHoldDurationMin; }\n  float GetScanDataDotHoldDurationMax() const override { return x240_scanDataDotHoldDurationMax; }\n  float GetScanAppearanceDuration() const override { return x244_scanAppearanceDuration; }\n  float GetScanPaneFlashFactor() const override { return x248_scanPaneFlashFactor; }\n  float GetScanPaneFadeInTime() const override { return x24c_scanPaneFadeInTime; }\n  float GetScanPaneFadeOutTime() const override { return x250_scanPaneFadeOutTime; }\n  float GetBallViewportYReduction() const override { return x254_ballViewportYReduction; }\n  float GetScanWindowIdleWidth() const override { return x258_scanWindowIdleW; }\n  float GetScanWindowIdleHeight() const override { return x25c_scanWindowIdleH; }\n  float GetScanWindowActiveWidth() const override { return x260_scanWindowActiveW; }\n  float GetScanWindowActiveHeight() const override { return x264_scanWindowActiveH; }\n  float GetScanWindowMagnification() const override { return x268_scanWindowMagnification; }\n  float GetScanWindowScanningAspect() const override { return x26c_scanWindowScanningAspect; }\n  float GetScanSidesPositionStart() const override { return x270_scanSidesPositionStart; }\n  bool GetShowAutomapperInMorphball() const override { return x274_showAutomapperInMorphball; }\n  bool GetLatchArticleText() const override { return x275_latchArticleText; }\n  float GetWorldTransManagerCharsPerSfx() const override { return x278_wtMgrCharsPerSfx; }\n  u32 GetXRayFogMode() const override { return x27c_xrayFogMode; }\n  float GetXRayFogNearZ() const override { return x280_xrayFogNearZ; }\n  float GetXRayFogFarZ() const override { return x284_xrayFogFarZ; }\n  const zeus::CColor& GetXRayFogColor() const override { return x288_xrayFogColor; }\n  float GetThermalVisorLevel() const override { return x28c_thermalVisorLevel; }\n  const zeus::CColor& GetThermalVisorColor() const override { return x290_thermalVisorColor; }\n  const zeus::CColor& GetVisorHudLightAdd(int v) const override { return x294_hudLightAddPerVisor[v]; }\n  const zeus::CColor& GetVisorHudLightMultiply(int v) const override { return x2a4_hudLightMultiplyPerVisor[v]; }\n  const zeus::CColor& GetHudReflectivityLightColor() const override { return x2b4_hudReflectivityLightColor; }\n  float GetHudLightAttMulConstant() const override { return x2b8_hudLightAttMulConstant; }\n  float GetHudLightAttMulLinear() const override { return x2bc_hudLightAttMulLinear; }\n  float GetHudLightAttMulQuadratic() const override { return x2c0_hudLightAttMulQuadratic; }\n  std::string_view GetCreditsTable() const override { return x2d0_creditsTable; }\n  std::string_view GetCreditsFont() const override { return x2e0_creditsFont; }\n  std::string_view GetJapaneseCreditsFont() const override { return x2f0_japaneseCreditsFont; }\n  const zeus::CColor& GetCreditsTextFontColor() const override { return x300_; }\n  const zeus::CColor& GetCreditsTextBorderColor() const override { return x304_; }\n\n  float GetScanSpeed(int idx) const override {\n    if (idx < 0 || size_t(idx) >= x2c4_scanSpeeds.size())\n      return 0.f;\n    return x2c4_scanSpeeds[idx];\n  }\n\n  void FixupValues() {\n    xd8_ = zeus::degToRad(xd8_);\n    xdc_ = zeus::degToRad(xdc_);\n\n    x200_ = zeus::CColor(x1f4_ * 0.25f, x1f8_ * 0.25f, x1fc_ * 0.25f, 1.f);\n\n    x210_scanSidesAngle = zeus::degToRad(x210_scanSidesAngle);\n    x228_scanSidesEndTime = x220_scanSidesDuration + x224_scanSidesStartTime;\n\n    if (x27c_xrayFogMode == 1)\n      x27c_xrayFogMode = 2;\n    else if (x27c_xrayFogMode == 2)\n      x27c_xrayFogMode = 4;\n    else if (x27c_xrayFogMode == 3)\n      x27c_xrayFogMode = 5;\n    else\n      x27c_xrayFogMode = 0;\n\n    x84_hudDecoShakeTranslateVelConstant *= 2.0f;\n  }\n};\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/Tweaks/CTweakGuiColors.cpp",
    "content": "#include \"Runtime/MP1/Tweaks/CTweakGuiColors.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n\n\nnamespace metaforce::MP1 {\n\nCTweakGuiColors::SPerVisorColors::SPerVisorColors(CInputStream& in) {\n  x0_energyBarFilled = in.Get<zeus::CColor>();\n  x4_energyBarEmpty = in.Get<zeus::CColor>();\n  x8_energyBarShadow = in.Get<zeus::CColor>();\n  xc_energyTankFilled = in.Get<zeus::CColor>();\n  x10_energyTankEmpty = in.Get<zeus::CColor>();\n  x14_energyDigitsFont = in.Get<zeus::CColor>();\n  x18_energyDigitsOutline = in.Get<zeus::CColor>();\n}\n\nCTweakGuiColors::CTweakGuiColors(CInputStream& in) {\n   x4_pauseBlurFilterColor = in.Get<zeus::CColor>();\n   x8_radarStuffColor = in.Get<zeus::CColor>();\n   xc_radarPlayerPaintColor = in.Get<zeus::CColor>();\n   x10_radarEnemyPaintColor = in.Get<zeus::CColor>();\n   x14_hudMessageFill = in.Get<zeus::CColor>();\n   x18_hudMessageOutline = in.Get<zeus::CColor>();\n   x1c_hudFrameColor = in.Get<zeus::CColor>();\n   x20_ = in.Get<zeus::CColor>();\n   x24_ = in.Get<zeus::CColor>();\n   x28_missileIconColorActive = in.Get<zeus::CColor>();\n   x2c_visorBeamMenuItemActive = in.Get<zeus::CColor>();\n   x30_visorBeamMenuColorInactive = in.Get<zeus::CColor>();\n   x34_energyBarFilledLowEnergy = in.Get<zeus::CColor>();\n   x38_energyBarShadowLowEnergy = in.Get<zeus::CColor>();\n   x3c_energyBarEmptyLowEnergy = in.Get<zeus::CColor>();\n   x40_hudDamageLightColor = in.Get<zeus::CColor>();\n   x44_ = in.Get<zeus::CColor>();\n   x48_ = in.Get<zeus::CColor>();\n   x4c_visorMenuTextFont = in.Get<zeus::CColor>();\n   x50_visorMenuTextOutline = in.Get<zeus::CColor>();\n   x54_beamMenuTextFont = in.Get<zeus::CColor>();\n   x58_beamMenuTextOutline = in.Get<zeus::CColor>();\n   x5c_energyWarningFont = in.Get<zeus::CColor>();\n   x60_threatWarningFont = in.Get<zeus::CColor>();\n   x64_missileWarningFont = in.Get<zeus::CColor>();\n   x68_threatBarFilled = in.Get<zeus::CColor>();\n   x6c_threatBarShadow = in.Get<zeus::CColor>();\n   x70_threatBarEmpty = in.Get<zeus::CColor>();\n   x74_missileBarFilled = in.Get<zeus::CColor>();\n   x78_missileBarShadow = in.Get<zeus::CColor>();\n   x7c_missileBarEmpty = in.Get<zeus::CColor>();\n   x80_threatIconColor = in.Get<zeus::CColor>();\n   x84_ = in.Get<zeus::CColor>();\n   x88_tickDecoColor = in.Get<zeus::CColor>();\n   x8c_helmetLightColor = in.Get<zeus::CColor>();\n   x90_threatIconSafeColor = in.Get<zeus::CColor>();\n   x94_missileIconColorInactive = in.Get<zeus::CColor>();\n   x98_missileIconColorChargedCanAlt = in.Get<zeus::CColor>();\n   x9c_missileIconColorChargedNoAlt = in.Get<zeus::CColor>();\n   xa0_missileIconColorDepleteAlt = in.Get<zeus::CColor>();\n   xa4_ = in.Get<zeus::CColor>();\n   xa8_ = in.Get<zeus::CColor>();\n   xac_ = in.Get<zeus::CColor>();\n   xb0_visorBeamMenuLozColor = in.Get<zeus::CColor>();\n   xb4_energyWarningOutline = in.Get<zeus::CColor>();\n   xb8_threatWarningOutline = in.Get<zeus::CColor>();\n   xbc_missileWarningOutline = in.Get<zeus::CColor>();\n   xc0_ = in.Get<zeus::CColor>();\n   xc4_damageAmbientColor = in.Get<zeus::CColor>();\n   xc8_scanFrameInactiveColor = in.Get<zeus::CColor>();\n   xcc_scanFrameActiveColor = in.Get<zeus::CColor>();\n   xd0_scanFrameImpulseColor = in.Get<zeus::CColor>();\n   xd4_scanVisorHudLightMultiply = in.Get<zeus::CColor>();\n   xd8_scanVisorScreenDimColor = in.Get<zeus::CColor>();\n   xdc_thermalVisorHudLightMultiply = in.Get<zeus::CColor>();\n   xe0_energyDrainFilterColor = in.Get<zeus::CColor>();\n   xe4_damageAmbientPulseColor = in.Get<zeus::CColor>();\n   xe8_energyBarFlashColor = in.Get<zeus::CColor>();\n   xec_ = in.Get<zeus::CColor>();\n   xf0_ = in.Get<zeus::CColor>();\n   xf4_ = in.Get<zeus::CColor>();\n   xf8_ = in.Get<zeus::CColor>();\n   xfc_ = in.Get<zeus::CColor>();\n   x100_xrayEnergyDecoColor = in.Get<zeus::CColor>();\n   x104_ = in.Get<zeus::CColor>();\n   x108_ = in.Get<zeus::CColor>();\n   x10c_ = in.Get<zeus::CColor>();\n   x110_ = in.Get<zeus::CColor>();\n   x114_ = in.Get<zeus::CColor>();\n   x118_ = in.Get<zeus::CColor>();\n   x11c_ = in.Get<zeus::CColor>();\n   x120_ = in.Get<zeus::CColor>();\n   x124_ = in.Get<zeus::CColor>();\n   x128_ = in.Get<zeus::CColor>();\n   x12c_ = in.Get<zeus::CColor>();\n   x130_ = in.Get<zeus::CColor>();\n   x134_ = in.Get<zeus::CColor>();\n   x138_scanDataDotColor = in.Get<zeus::CColor>();\n   x13c_powerBombDigitAvailableFont = in.Get<zeus::CColor>();\n   x140_powerBombDigitAvailableOutline = in.Get<zeus::CColor>();\n   x144_ = in.Get<zeus::CColor>();\n   x148_ballBombFilled = in.Get<zeus::CColor>();\n   x14c_ballBombEmpty = in.Get<zeus::CColor>();\n   x150_powerBombIconAvailable = in.Get<zeus::CColor>();\n   x154_ = in.Get<zeus::CColor>();\n   x158_ballEnergyDeco = in.Get<zeus::CColor>();\n   x15c_ballBombDeco = in.Get<zeus::CColor>();\n   x160_powerBombDigitDepletedFont = in.Get<zeus::CColor>();\n   x164_powerBombDigitDepletedOutline = in.Get<zeus::CColor>();\n   x168_powerBombIconUnavailable = in.Get<zeus::CColor>();\n   x16c_ = in.Get<zeus::CColor>();\n   x170_ = in.Get<zeus::CColor>();\n   x174_scanDisplayImagePaneColor = in.Get<zeus::CColor>();\n   x178_ = in.Get<zeus::CColor>();\n   x17c_threatIconWarningColor = in.Get<zeus::CColor>();\n   x180_hudCounterFill = in.Get<zeus::CColor>();\n   x184_hudCounterOutline = in.Get<zeus::CColor>();\n   x188_scanIconCriticalColor = in.Get<zeus::CColor>();\n   x18c_scanIconCriticalDimColor = in.Get<zeus::CColor>();\n   x190_scanIconNoncriticalColor = in.Get<zeus::CColor>();\n   x194_scanIconNoncriticalDimColor = in.Get<zeus::CColor>();\n   x198_scanReticuleColor = in.Get<zeus::CColor>();\n   x19c_threatDigitsFont = in.Get<zeus::CColor>();\n   x1a0_threatDigitsOutline = in.Get<zeus::CColor>();\n   x1a4_missileDigitsFont = in.Get<zeus::CColor>();\n   x1a8_missileDigitsOutline = in.Get<zeus::CColor>();\n   x1ac_thermalDecoColor = in.Get<zeus::CColor>();\n   x1b0_thermalOutlinesColor = in.Get<zeus::CColor>();\n   x1b4_ = in.Get<zeus::CColor>();\n   x1b8_thermalLockColor = in.Get<zeus::CColor>();\n   x1bc_pauseItemAmber = in.Get<zeus::CColor>();\n   x1c0_pauseItemBlue = in.Get<zeus::CColor>();\n   read_reserved_vector(x1c4_perVisorColors, in);\n}\n}"
  },
  {
    "path": "Runtime/MP1/Tweaks/CTweakGuiColors.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Tweaks/ITweakGuiColors.hpp\"\n#include \"Runtime/rstl.hpp\"\n\nnamespace metaforce::MP1 {\nstruct CTweakGuiColors final : public Tweaks::ITweakGuiColors {\n  zeus::CColor x4_pauseBlurFilterColor;\n  zeus::CColor x8_radarStuffColor;\n  zeus::CColor xc_radarPlayerPaintColor;\n  zeus::CColor x10_radarEnemyPaintColor;\n  zeus::CColor x14_hudMessageFill;\n  zeus::CColor x18_hudMessageOutline;\n  zeus::CColor x1c_hudFrameColor;\n  zeus::CColor x20_;\n  zeus::CColor x24_;\n  zeus::CColor x28_missileIconColorActive;\n  zeus::CColor x2c_visorBeamMenuItemActive;\n  zeus::CColor x30_visorBeamMenuColorInactive;\n  zeus::CColor x34_energyBarFilledLowEnergy;\n  zeus::CColor x38_energyBarShadowLowEnergy;\n  zeus::CColor x3c_energyBarEmptyLowEnergy;\n  zeus::CColor x40_hudDamageLightColor;\n  zeus::CColor x44_;\n  zeus::CColor x48_;\n  zeus::CColor x4c_visorMenuTextFont;\n  zeus::CColor x50_visorMenuTextOutline;\n  zeus::CColor x54_beamMenuTextFont;\n  zeus::CColor x58_beamMenuTextOutline;\n  zeus::CColor x5c_energyWarningFont;\n  zeus::CColor x60_threatWarningFont;\n  zeus::CColor x64_missileWarningFont;\n  zeus::CColor x68_threatBarFilled;\n  zeus::CColor x6c_threatBarShadow;\n  zeus::CColor x70_threatBarEmpty;\n  zeus::CColor x74_missileBarFilled;\n  zeus::CColor x78_missileBarShadow;\n  zeus::CColor x7c_missileBarEmpty;\n  zeus::CColor x80_threatIconColor;\n  zeus::CColor x84_;\n  zeus::CColor x88_tickDecoColor;\n  zeus::CColor x8c_helmetLightColor;\n  zeus::CColor x90_threatIconSafeColor;\n  zeus::CColor x94_missileIconColorInactive;\n  zeus::CColor x98_missileIconColorChargedCanAlt;\n  zeus::CColor x9c_missileIconColorChargedNoAlt;\n  zeus::CColor xa0_missileIconColorDepleteAlt;\n  zeus::CColor xa4_;\n  zeus::CColor xa8_;\n  zeus::CColor xac_;\n  zeus::CColor xb0_visorBeamMenuLozColor;\n  zeus::CColor xb4_energyWarningOutline;\n  zeus::CColor xb8_threatWarningOutline;\n  zeus::CColor xbc_missileWarningOutline;\n  zeus::CColor xc0_;\n  zeus::CColor xc4_damageAmbientColor;\n  zeus::CColor xc8_scanFrameInactiveColor;\n  zeus::CColor xcc_scanFrameActiveColor;\n  zeus::CColor xd0_scanFrameImpulseColor;\n  zeus::CColor xd4_scanVisorHudLightMultiply;\n  zeus::CColor xd8_scanVisorScreenDimColor;\n  zeus::CColor xdc_thermalVisorHudLightMultiply;\n  zeus::CColor xe0_energyDrainFilterColor;\n  zeus::CColor xe4_damageAmbientPulseColor;\n  zeus::CColor xe8_energyBarFlashColor;\n  zeus::CColor xec_;\n  zeus::CColor xf0_;\n  zeus::CColor xf4_;\n  zeus::CColor xf8_;\n  zeus::CColor xfc_;\n  zeus::CColor x100_xrayEnergyDecoColor;\n  zeus::CColor x104_;\n  zeus::CColor x108_;\n  zeus::CColor x10c_;\n  zeus::CColor x110_;\n  zeus::CColor x114_;\n  zeus::CColor x118_;\n  zeus::CColor x11c_;\n  zeus::CColor x120_;\n  zeus::CColor x124_;\n  zeus::CColor x128_;\n  zeus::CColor x12c_;\n  zeus::CColor x130_;\n  zeus::CColor x134_;\n  zeus::CColor x138_scanDataDotColor;\n  zeus::CColor x13c_powerBombDigitAvailableFont;\n  zeus::CColor x140_powerBombDigitAvailableOutline;\n  zeus::CColor x144_;\n  zeus::CColor x148_ballBombFilled;\n  zeus::CColor x14c_ballBombEmpty;\n  zeus::CColor x150_powerBombIconAvailable;\n  zeus::CColor x154_;\n  zeus::CColor x158_ballEnergyDeco;\n  zeus::CColor x15c_ballBombDeco;\n  zeus::CColor x160_powerBombDigitDepletedFont;\n  zeus::CColor x164_powerBombDigitDepletedOutline;\n  zeus::CColor x168_powerBombIconUnavailable;\n  zeus::CColor x16c_;\n  zeus::CColor x170_;\n  zeus::CColor x174_scanDisplayImagePaneColor;\n  zeus::CColor x178_;\n  zeus::CColor x17c_threatIconWarningColor;\n  zeus::CColor x180_hudCounterFill;\n  zeus::CColor x184_hudCounterOutline;\n  zeus::CColor x188_scanIconCriticalColor;\n  zeus::CColor x18c_scanIconCriticalDimColor;\n  zeus::CColor x190_scanIconNoncriticalColor;\n  zeus::CColor x194_scanIconNoncriticalDimColor;\n  zeus::CColor x198_scanReticuleColor;\n  zeus::CColor x19c_threatDigitsFont;\n  zeus::CColor x1a0_threatDigitsOutline;\n  zeus::CColor x1a4_missileDigitsFont;\n  zeus::CColor x1a8_missileDigitsOutline;\n  zeus::CColor x1ac_thermalDecoColor;\n  zeus::CColor x1b0_thermalOutlinesColor;\n  zeus::CColor x1b4_;\n  zeus::CColor x1b8_thermalLockColor;\n  zeus::CColor x1bc_pauseItemAmber;\n  zeus::CColor x1c0_pauseItemBlue;\n  struct SPerVisorColors {\n    zeus::CColor x0_energyBarFilled;\n    zeus::CColor x4_energyBarEmpty;\n    zeus::CColor x8_energyBarShadow;\n    zeus::CColor xc_energyTankFilled;\n    zeus::CColor x10_energyTankEmpty;\n    zeus::CColor x14_energyDigitsFont;\n    zeus::CColor x18_energyDigitsOutline;\n    explicit SPerVisorColors() = default;\n    explicit SPerVisorColors(CInputStream& in);\n  };\n  /* Combat, Scan, XRay, Thermal, Ball */\n  rstl::reserved_vector<SPerVisorColors, 5> x1c4_perVisorColors{};\n\n  CTweakGuiColors() = default;\n  CTweakGuiColors(CInputStream& r);\n  const zeus::CColor& GetPauseBlurFilterColor() const override { return x4_pauseBlurFilterColor; }\n  const zeus::CColor& GetRadarStuffColor() const override { return x8_radarStuffColor; }\n  const zeus::CColor& GetRadarPlayerPaintColor() const override { return xc_radarPlayerPaintColor; }\n  const zeus::CColor& GetRadarEnemyPaintColor() const override { return x10_radarEnemyPaintColor; }\n  const zeus::CColor& GetHudMessageFill() const override { return x14_hudMessageFill; }\n  const zeus::CColor& GetHudMessageOutline() const override { return x18_hudMessageOutline; }\n  const zeus::CColor& GetHudFrameColor() const override { return x1c_hudFrameColor; }\n  const zeus::CColor& GetMissileIconColorActive() const override { return x28_missileIconColorActive; }\n  const zeus::CColor& GetVisorBeamMenuItemActive() const override { return x2c_visorBeamMenuItemActive; }\n  const zeus::CColor& GetVisorBeamMenuItemInactive() const override { return x30_visorBeamMenuColorInactive; }\n  const zeus::CColor& GetEnergyBarFilledLowEnergy() const override { return x34_energyBarFilledLowEnergy; }\n  const zeus::CColor& GetEnergyBarShadowLowEnergy() const override { return x38_energyBarShadowLowEnergy; }\n  const zeus::CColor& GetEnergyBarEmptyLowEnergy() const override { return x3c_energyBarEmptyLowEnergy; }\n  const zeus::CColor& GetHudDamageLightColor() const override { return x40_hudDamageLightColor; }\n  const zeus::CColor& GetVisorMenuTextFont() const override { return x4c_visorMenuTextFont; }\n  const zeus::CColor& GetVisorMenuTextOutline() const override { return x50_visorMenuTextOutline; }\n  const zeus::CColor& GetBeamMenuTextFont() const override { return x54_beamMenuTextFont; }\n  const zeus::CColor& GetBeamMenuTextOutline() const override { return x58_beamMenuTextOutline; }\n  const zeus::CColor& GetEnergyWarningFont() const override { return x5c_energyWarningFont; }\n  const zeus::CColor& GetThreatWarningFont() const override { return x60_threatWarningFont; }\n  const zeus::CColor& GetMissileWarningFont() const override { return x64_missileWarningFont; }\n  const zeus::CColor& GetThreatBarFilled() const override { return x68_threatBarFilled; }\n  const zeus::CColor& GetThreatBarShadow() const override { return x6c_threatBarShadow; }\n  const zeus::CColor& GetThreatBarEmpty() const override { return x70_threatBarEmpty; }\n  const zeus::CColor& GetMissileBarFilled() const override { return x74_missileBarFilled; }\n  const zeus::CColor& GetMissileBarShadow() const override { return x78_missileBarShadow; }\n  const zeus::CColor& GetMissileBarEmpty() const override { return x7c_missileBarEmpty; }\n  const zeus::CColor& GetThreatIconColor() const override { return x80_threatIconColor; }\n  const zeus::CColor& GetTickDecoColor() const override { return x88_tickDecoColor; }\n  const zeus::CColor& GetHelmetLightColor() const override { return x8c_helmetLightColor; }\n  const zeus::CColor& GetThreatIconSafeColor() const override { return x90_threatIconSafeColor; }\n  const zeus::CColor& GetMissileIconColorInactive() const override { return x94_missileIconColorInactive; }\n  const zeus::CColor& GetMissileIconColorChargedCanAlt() const override { return x98_missileIconColorChargedCanAlt; }\n  const zeus::CColor& GetMissileIconColorChargedNoAlt() const override { return x9c_missileIconColorChargedNoAlt; }\n  const zeus::CColor& GetMissileIconColorDepleteAlt() const override { return xa0_missileIconColorDepleteAlt; }\n  const zeus::CColor& GetVisorBeamMenuLozColor() const override { return xb0_visorBeamMenuLozColor; }\n  const zeus::CColor& GetEnergyWarningOutline() const override { return xb4_energyWarningOutline; }\n  const zeus::CColor& GetThreatWarningOutline() const override { return xb8_threatWarningOutline; }\n  const zeus::CColor& GetMissileWarningOutline() const override { return xbc_missileWarningOutline; }\n  const zeus::CColor& GetDamageAmbientColor() const override { return xc4_damageAmbientColor; }\n  const zeus::CColor& GetScanFrameInactiveColor() const override { return xc8_scanFrameInactiveColor; }\n  const zeus::CColor& GetScanFrameActiveColor() const override { return xcc_scanFrameActiveColor; }\n  const zeus::CColor& GetScanFrameImpulseColor() const override { return xd0_scanFrameImpulseColor; }\n  const zeus::CColor& GetScanVisorHudLightMultiply() const override { return xd4_scanVisorHudLightMultiply; }\n  const zeus::CColor& GetScanVisorScreenDimColor() const override { return xd8_scanVisorScreenDimColor; }\n  const zeus::CColor& GetThermalVisorHudLightMultiply() const override { return xdc_thermalVisorHudLightMultiply; }\n  const zeus::CColor& GetEnergyDrainFilterColor() const override { return xe0_energyDrainFilterColor; }\n  const zeus::CColor& GetDamageAmbientPulseColor() const override { return xe4_damageAmbientPulseColor; }\n  const zeus::CColor& GetEnergyBarFlashColor() const override { return xe8_energyBarFlashColor; }\n  const zeus::CColor& GetXRayEnergyDecoColor() const override { return x100_xrayEnergyDecoColor; }\n  const zeus::CColor& GetScanDataDotColor() const override { return x138_scanDataDotColor; }\n  const zeus::CColor& GetPowerBombDigitAvailableFont() const override { return x13c_powerBombDigitAvailableFont; }\n  const zeus::CColor& GetPowerBombDigitAvailableOutline() const override { return x140_powerBombDigitAvailableOutline; }\n  const zeus::CColor& GetBallBombFilledColor() const override { return x148_ballBombFilled; }\n  const zeus::CColor& GetBallBombEmptyColor() const override { return x14c_ballBombEmpty; }\n  const zeus::CColor& GetPowerBombIconAvailableColor() const override { return x150_powerBombIconAvailable; }\n  const zeus::CColor& GetBallBombEnergyColor() const override { return x158_ballEnergyDeco; }\n  const zeus::CColor& GetBallBombDecoColor() const override { return x15c_ballBombDeco; }\n  const zeus::CColor& GetPowerBombDigitDelpetedFont() const override { return x160_powerBombDigitDepletedFont; }\n  const zeus::CColor& GetPowerBombDigitDelpetedOutline() const override { return x164_powerBombDigitDepletedOutline; }\n  const zeus::CColor& GetPowerBombIconDepletedColor() const override { return x168_powerBombIconUnavailable; }\n  const zeus::CColor& GetScanDisplayImagePaneColor() const override { return x174_scanDisplayImagePaneColor; }\n  const zeus::CColor& GetThreatIconWarningColor() const override { return x17c_threatIconWarningColor; }\n  const zeus::CColor& GetHudCounterFill() const override { return x180_hudCounterFill; }\n  const zeus::CColor& GetHudCounterOutline() const override { return x184_hudCounterOutline; }\n  const zeus::CColor& GetScanIconCriticalColor() const override { return x188_scanIconCriticalColor; }\n  const zeus::CColor& GetScanIconCriticalDimColor() const override { return x18c_scanIconCriticalDimColor; }\n  const zeus::CColor& GetScanIconNoncriticalColor() const override { return x190_scanIconNoncriticalColor; }\n  const zeus::CColor& GetScanIconNoncriticalDimColor() const override { return x194_scanIconNoncriticalDimColor; }\n  const zeus::CColor& GetScanReticuleColor() const override { return x198_scanReticuleColor; }\n  const zeus::CColor& GetThreatDigitsFont() const override { return x19c_threatDigitsFont; }\n  const zeus::CColor& GetThreatDigitsOutline() const override { return x1a0_threatDigitsOutline; }\n  const zeus::CColor& GetMissileDigitsFont() const override { return x1a4_missileDigitsFont; }\n  const zeus::CColor& GetMissileDigitsOutline() const override { return x1a8_missileDigitsOutline; }\n  const zeus::CColor& GetThermalDecoColor() const override { return x1ac_thermalDecoColor; }\n  const zeus::CColor& GetThermalOutlinesColor() const override { return x1b0_thermalOutlinesColor; }\n  const zeus::CColor& GetThermalLockColor() const override { return x1b8_thermalLockColor; }\n  const zeus::CColor& GetPauseItemAmberColor() const override { return x1bc_pauseItemAmber; }\n  const zeus::CColor& GetPauseItemBlueColor() const override { return x1c0_pauseItemBlue; }\n\n  SVisorEnergyInitColors GetVisorEnergyInitColors(int idx) const override {\n    const SPerVisorColors& colors = x1c4_perVisorColors[idx];\n    return {colors.xc_energyTankFilled, colors.x10_energyTankEmpty, colors.x14_energyDigitsFont,\n            colors.x18_energyDigitsOutline};\n  }\n  SVisorEnergyBarColors GetVisorEnergyBarColors(int idx) const override {\n    const SPerVisorColors& colors = x1c4_perVisorColors[idx];\n    return {colors.x0_energyBarFilled, colors.x4_energyBarEmpty, colors.x8_energyBarShadow};\n  }\n};\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/Tweaks/CTweakGunRes.cpp",
    "content": "#include \"Runtime/MP1/Tweaks/CTweakGunRes.hpp\"\n\nnamespace metaforce::MP1 {\nCTweakGunRes::CTweakGunRes(CInputStream& in) {\n  m_gunMotion = in.Get<std::string>();\n  m_grappleArm = in.Get<std::string>();\n  m_rightHand = in.Get<std::string>();\n  m_powerBeam = in.Get<std::string>();\n  m_iceBeam = in.Get<std::string>();\n  m_waveBeam = in.Get<std::string>();\n  m_plasmaBeam = in.Get<std::string>();\n  m_phazonBeam = in.Get<std::string>();\n  m_holoTransition = in.Get<std::string>();\n  m_bombSet = in.Get<std::string>();\n  m_bombExplode = in.Get<std::string>();\n  m_powerBombExplode = in.Get<std::string>();\n  m_powerBeamWp = in.Get<std::string>();\n  m_powerBallWp = in.Get<std::string>();\n  m_iceBeamWp = in.Get<std::string>();\n  m_iceBallWp = in.Get<std::string>();\n  m_waveBeamWp = in.Get<std::string>();\n  m_waveBallWp = in.Get<std::string>();\n  m_plasmaBeamWp = in.Get<std::string>();\n  m_plasmaBallWp = in.Get<std::string>();\n  m_phazonBeamWp = in.Get<std::string>();\n  m_phazonBallWp = in.Get<std::string>();\n  m_powerMuzzle = in.Get<std::string>();\n  m_iceMuzzle = in.Get<std::string>();\n  m_waveMuzzle = in.Get<std::string>();\n  m_plasmaMuzzle = in.Get<std::string>();\n  m_phazonMuzzle = in.Get<std::string>();\n  m_powerCharge = in.Get<std::string>();\n  m_iceCharge = in.Get<std::string>();\n  m_waveCharge = in.Get<std::string>();\n  m_plasmaCharge = in.Get<std::string>();\n  m_phazonCharge = in.Get<std::string>();\n  m_powerAuxMuzzle = in.Get<std::string>();\n  m_iceAuxMuzzle = in.Get<std::string>();\n  m_waveAuxMuzzle = in.Get<std::string>();\n  m_plasmaAuxMuzzle = in.Get<std::string>();\n  m_phazonAuxMuzzle = in.Get<std::string>();\n  m_grappleSegment = in.Get<std::string>();\n  m_grappleClaw = in.Get<std::string>();\n  m_grappleHit = in.Get<std::string>();\n  m_grappleMuzzle = in.Get<std::string>();\n  m_grappleSwoosh = in.Get<std::string>();\n}\n} // namespace metaforce::MP1"
  },
  {
    "path": "Runtime/MP1/Tweaks/CTweakGunRes.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Tweaks/ITweakGunRes.hpp\"\n\nnamespace metaforce::MP1 {\n\nstruct CTweakGunRes final : Tweaks::ITweakGunRes {\n  std::string m_gunMotion;\n  std::string m_grappleArm;\n  std::string m_rightHand;\n\n  std::string m_powerBeam;\n  std::string m_iceBeam;\n  std::string m_waveBeam;\n  std::string m_plasmaBeam;\n  std::string m_phazonBeam;\n\n  std::string m_holoTransition;\n\n  std::string m_bombSet;\n  std::string m_bombExplode;\n  std::string m_powerBombExplode;\n\n  std::string m_powerBeamWp;\n  std::string m_powerBallWp;\n  std::string m_iceBeamWp;\n  std::string m_iceBallWp;\n  std::string m_waveBeamWp;\n  std::string m_waveBallWp;\n  std::string m_plasmaBeamWp;\n  std::string m_plasmaBallWp;\n  std::string m_phazonBeamWp;\n  std::string m_phazonBallWp;\n\n  std::string m_powerMuzzle;\n  std::string m_iceMuzzle;\n  std::string m_waveMuzzle;\n  std::string m_plasmaMuzzle;\n  std::string m_phazonMuzzle;\n\n  std::string m_powerCharge;\n  std::string m_iceCharge;\n  std::string m_waveCharge;\n  std::string m_plasmaCharge;\n  std::string m_phazonCharge;\n\n  std::string m_powerAuxMuzzle;\n  std::string m_iceAuxMuzzle;\n  std::string m_waveAuxMuzzle;\n  std::string m_plasmaAuxMuzzle;\n  std::string m_phazonAuxMuzzle;\n\n  std::string m_grappleSegment;\n  std::string m_grappleClaw;\n  std::string m_grappleHit;\n  std::string m_grappleMuzzle;\n  std::string m_grappleSwoosh;\n\n  const std::string& GetGunMotion() const override { return m_gunMotion; }\n  const std::string& GetGrappleArm() const override { return m_grappleArm; }\n  const std::string& GetRightHand() const override { return m_rightHand; }\n\n  const std::string& GetPowerBeam() const override { return m_powerBeam; }\n  const std::string& GetIceBeam() const override { return m_iceBeam; }\n  const std::string& GetWaveBeam() const override { return m_waveBeam; }\n  const std::string& GetPlasmaBeam() const override { return m_plasmaBeam; }\n  const std::string& GetPhazonBeam() const override { return m_phazonBeam; }\n\n  const std::string& GetHoloTransition() const override { return m_holoTransition; }\n\n  const std::string& GetBombSet() const override { return m_bombSet; }\n  const std::string& GetBombExplode() const override { return m_bombExplode; }\n  const std::string& GetPowerBombExplode() const override { return m_powerBombExplode; }\n\n  const std::string& GetWeapon(size_t idx, bool ball) const override { return (&m_powerBeamWp)[idx * 2 + ball]; }\n  const std::string& GetMuzzleParticle(size_t idx) const override { return (&m_powerMuzzle)[idx]; }\n  const std::string& GetChargeParticle(size_t idx) const override { return (&m_powerCharge)[idx]; }\n  const std::string& GetAuxMuzzleParticle(size_t idx) const override { return (&m_powerAuxMuzzle)[idx]; }\n\n  const std::string& GetGrappleSegmentParticle() const override { return m_grappleSegment; }\n  const std::string& GetGrappleClawParticle() const override { return m_grappleClaw; }\n  const std::string& GetGrappleHitParticle() const override { return m_grappleHit; }\n  const std::string& GetGrappleMuzzleParticle() const override { return m_grappleMuzzle; }\n  const std::string& GetGrappleSwooshParticle() const override { return m_grappleSwoosh; }\n\n  CTweakGunRes() = default;\n  CTweakGunRes(CInputStream& in);\n};\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/Tweaks/CTweakParticle.cpp",
    "content": "#include \"Runtime/MP1/Tweaks/CTweakParticle.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n\nnamespace metaforce::MP1 {\nCTweakParticle::CTweakParticle(CInputStream& in) {\n  m_particle = in.Get<std::string>();\n  m_powerBeam = in.Get<std::string>();\n  m_genThrust = in.Get<std::string>();\n}\n} // namespace metaforce::MP1"
  },
  {
    "path": "Runtime/MP1/Tweaks/CTweakParticle.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Tweaks/ITweakParticle.hpp\"\n\nnamespace metaforce::MP1 {\n\nstruct CTweakParticle final : Tweaks::ITweakParticle {\n  std::string m_particle;\n  std::string m_powerBeam;\n  std::string m_genThrust;\n\n  CTweakParticle() = default;\n  CTweakParticle(CInputStream& reader);\n};\n\n} // namespace DataSpec::DNAMP1\n"
  },
  {
    "path": "Runtime/MP1/Tweaks/CTweakPlayer.cpp",
    "content": "#include \"Runtime/MP1/Tweaks/CTweakPlayer.hpp\"\n\n#include \"Runtime/Streams/IOStreams.hpp\"\n\n#include \"zeus/Math.hpp\"\n\n#include \"Runtime/ConsoleVariables/CVar.hpp\"\n#include \"Runtime/ConsoleVariables/CVarManager.hpp\"\n\n#define DEFINE_CVAR_GLOBAL(name)                                                                                       \\\n  constexpr std::string_view sk##name = std::string_view(\"tweak.player.\" #name);                                       \\\n  CVar* tw_##name = nullptr;\n\n#define CREATE_CVAR(name, help, value, flags)                                                                          \\\n  tw_##name = mgr->findOrMakeCVar(sk##name, help, value, flags);                                                       \\\n  if (tw_##name->wasDeserialized()) {                                                                                  \\\n    tw_##name->toValue(value);                                                                                         \\\n  }                                                                                                                    \\\n  tw_##name->addListener([this](CVar* cv) { _tweakListener(cv); });\n\n#define CREATE_CVAR_BITFIELD(name, help, value, flags)                                                                 \\\n  {                                                                                                                    \\\n    bool tmp = value;                                                                                                  \\\n    CREATE_CVAR(name, help, tmp, flags)                                                                                \\\n  }\n\n#define UPDATE_CVAR(name, cv, value)                                                                                   \\\n  if ((cv) == tw_##name) {                                                                                             \\\n    (cv)->toValue(value);                                                                                              \\\n    return;                                                                                                            \\\n  }\n\n#define UPDATE_CVAR_BITFIELD(name, cv, value)                                                                          \\\n  {                                                                                                                    \\\n    bool tmp = value;                                                                                                  \\\n    UPDATE_CVAR(name, cv, tmp)                                                                                         \\\n    (value) = tmp;                                                                                                     \\\n  }\n\nnamespace metaforce::MP1 {\nnamespace {\nstatic constexpr CVar::EFlags skDefaultFlags = CVar::EFlags::Game | CVar::EFlags::Cheat | CVar::EFlags::Archive;\nDEFINE_CVAR_GLOBAL(MaxTranslationAccelerationNormal);\nDEFINE_CVAR_GLOBAL(MaxTranslationAccelerationAir);\nDEFINE_CVAR_GLOBAL(MaxTranslationAccelerationIce);\nDEFINE_CVAR_GLOBAL(MaxTranslationAccelerationOrganic);\nDEFINE_CVAR_GLOBAL(MaxTranslationAccelerationWater);\nDEFINE_CVAR_GLOBAL(MaxTranslationAccelerationLava);\nDEFINE_CVAR_GLOBAL(MaxTranslationAccelerationPhazon);\nDEFINE_CVAR_GLOBAL(MaxTranslationAccelerationShrubbery);\nDEFINE_CVAR_GLOBAL(MaxRotationAccelerationNormal);\nDEFINE_CVAR_GLOBAL(MaxRotationAccelerationAir);\nDEFINE_CVAR_GLOBAL(MaxRotationAccelerationIce);\nDEFINE_CVAR_GLOBAL(MaxRotationAccelerationOrganic);\nDEFINE_CVAR_GLOBAL(MaxRotationAccelerationWater);\nDEFINE_CVAR_GLOBAL(MaxRotationAccelerationLava);\nDEFINE_CVAR_GLOBAL(MaxRotationAccelerationPhazon);\nDEFINE_CVAR_GLOBAL(MaxRotationAccelerationShrubbery);\nDEFINE_CVAR_GLOBAL(TranslationFrictionNormal);\nDEFINE_CVAR_GLOBAL(TranslationFrictionAir);\nDEFINE_CVAR_GLOBAL(TranslationFrictionIce);\nDEFINE_CVAR_GLOBAL(TranslationFrictionOrganic);\nDEFINE_CVAR_GLOBAL(TranslationFrictionWater);\nDEFINE_CVAR_GLOBAL(TranslationFrictionLava);\nDEFINE_CVAR_GLOBAL(TranslationFrictionPhazon);\nDEFINE_CVAR_GLOBAL(TranslationFrictionShrubbery);\nDEFINE_CVAR_GLOBAL(RotationFrictionNormal);\nDEFINE_CVAR_GLOBAL(RotationFrictionAir);\nDEFINE_CVAR_GLOBAL(RotationFrictionIce);\nDEFINE_CVAR_GLOBAL(RotationFrictionOrganic);\nDEFINE_CVAR_GLOBAL(RotationFrictionWater);\nDEFINE_CVAR_GLOBAL(RotationFrictionLava);\nDEFINE_CVAR_GLOBAL(RotationFrictionPhazon);\nDEFINE_CVAR_GLOBAL(RotationFrictionShrubbery);\nDEFINE_CVAR_GLOBAL(RotationMaxSpeedNormal);\nDEFINE_CVAR_GLOBAL(RotationMaxSpeedAir);\nDEFINE_CVAR_GLOBAL(RotationMaxSpeedIce);\nDEFINE_CVAR_GLOBAL(RotationMaxSpeedOrganic);\nDEFINE_CVAR_GLOBAL(RotationMaxSpeedWater);\nDEFINE_CVAR_GLOBAL(RotationMaxSpeedLava);\nDEFINE_CVAR_GLOBAL(RotationMaxSpeedPhazon);\nDEFINE_CVAR_GLOBAL(RotationMaxSpeedShrubbery);\nDEFINE_CVAR_GLOBAL(TranslationMaxSpeedNormal);\nDEFINE_CVAR_GLOBAL(TranslationMaxSpeedAir);\nDEFINE_CVAR_GLOBAL(TranslationMaxSpeedIce);\nDEFINE_CVAR_GLOBAL(TranslationMaxSpeedOrganic);\nDEFINE_CVAR_GLOBAL(TranslationMaxSpeedWater);\nDEFINE_CVAR_GLOBAL(TranslationMaxSpeedLava);\nDEFINE_CVAR_GLOBAL(TranslationMaxSpeedPhazon);\nDEFINE_CVAR_GLOBAL(TranslationMaxSpeedShrubbery);\nDEFINE_CVAR_GLOBAL(NormalGravityAcceleration);\nDEFINE_CVAR_GLOBAL(FluidGravityAcceleration);\nDEFINE_CVAR_GLOBAL(VerticalJumpAcceleration);\nDEFINE_CVAR_GLOBAL(HorizontalJumpAcceleration);\nDEFINE_CVAR_GLOBAL(VerticalDoubleJumpAcceleration);\nDEFINE_CVAR_GLOBAL(HorizontalDoubleJumpAcceleration);\nDEFINE_CVAR_GLOBAL(WaterJumpFactor);\nDEFINE_CVAR_GLOBAL(WaterBallJumpFactor);\nDEFINE_CVAR_GLOBAL(LavaJumpFactor);\nDEFINE_CVAR_GLOBAL(LavaBallJumpFactor);\nDEFINE_CVAR_GLOBAL(PhazonJumpFactor);\nDEFINE_CVAR_GLOBAL(PhazonBallJumpFactor);\nDEFINE_CVAR_GLOBAL(AllowedJumpTime);\nDEFINE_CVAR_GLOBAL(AllowedDoubleJumpTime);\nDEFINE_CVAR_GLOBAL(MinDoubleJumpWindow);\nDEFINE_CVAR_GLOBAL(MaxDoubleJumpWindow)\n// DEFINE_CVAR_GLOBAL(); // x104_\nDEFINE_CVAR_GLOBAL(MinJumpTime);\nDEFINE_CVAR_GLOBAL(MinDoubleJumpTime);\nDEFINE_CVAR_GLOBAL(AllowedLedgeTime);\nDEFINE_CVAR_GLOBAL(DoubleJumpImpulse);\nDEFINE_CVAR_GLOBAL(BackwardsForceMultiplier);\nDEFINE_CVAR_GLOBAL(BombJumpRadius);\nDEFINE_CVAR_GLOBAL(BombJumpHeight);\nDEFINE_CVAR_GLOBAL(EyeOffset);\nDEFINE_CVAR_GLOBAL(TurnSpeedMultiplier);\nDEFINE_CVAR_GLOBAL(FreeLookTurnSpeedMultiplier);\nDEFINE_CVAR_GLOBAL(HorizontalFreeLookAngleVelocity);\nDEFINE_CVAR_GLOBAL(VerticalFreeLookAngleVelocity);\nDEFINE_CVAR_GLOBAL(FreeLookSpeed);\nDEFINE_CVAR_GLOBAL(FreeLookSnapSpeed);\n// DEFINE_CVAR_GLOBAL(); // x140_\nDEFINE_CVAR_GLOBAL(FreeLookCenteredThresholdAngle);\nDEFINE_CVAR_GLOBAL(FreeLookCenteredTime);\nDEFINE_CVAR_GLOBAL(FreeLookDampenFactor);\nDEFINE_CVAR_GLOBAL(LeftDivisor);\nDEFINE_CVAR_GLOBAL(RightDivisor);\nDEFINE_CVAR_GLOBAL(OrbitMinDistanceClose);\nDEFINE_CVAR_GLOBAL(OrbitMinDistanceFar);\nDEFINE_CVAR_GLOBAL(OrbitMinDistanceDefault);\nDEFINE_CVAR_GLOBAL(OrbitNormalDistanceClose);\nDEFINE_CVAR_GLOBAL(OrbitNormalDistanceFar);\nDEFINE_CVAR_GLOBAL(OrbitNormalDistanceDefault);\nDEFINE_CVAR_GLOBAL(OrbitMaxDistanceClose);\nDEFINE_CVAR_GLOBAL(OrbitMaxDistanceFar);\nDEFINE_CVAR_GLOBAL(OrbitMaxDistanceDefault);\n// DEFINE_CVAR_GLOBAL(); // x17c_\nDEFINE_CVAR_GLOBAL(OrbitmodeTimer);\nDEFINE_CVAR_GLOBAL(OrbitCameraSpeed);\nDEFINE_CVAR_GLOBAL(OrbitUpperAngle);\nDEFINE_CVAR_GLOBAL(OrbitLowerAngle);\nDEFINE_CVAR_GLOBAL(OrbitHorizontalAngle);\n// DEFINE_CVAR_GLOBAL(); // x194_\n// DEFINE_CVAR_GLOBAL(); // x198_\nDEFINE_CVAR_GLOBAL(OrbitMaxTargetDistance);\nDEFINE_CVAR_GLOBAL(OrbitMaxLockDistance);\nDEFINE_CVAR_GLOBAL(OrbitDistanceThreshold);\nDEFINE_CVAR_GLOBAL(OrbitScreenTargetingBoxHalfExtentX);\nDEFINE_CVAR_GLOBAL(OrbitScreenScanBoxHalfExtentX);\nDEFINE_CVAR_GLOBAL(OrbitScreenTargetingBoxHalfExtentY);\nDEFINE_CVAR_GLOBAL(OrbitScreenScanBoxHalfExtentY);\nDEFINE_CVAR_GLOBAL(OrbitScreenTargetingBoxCenterX);\nDEFINE_CVAR_GLOBAL(OrbitScreenScanBoxCenterX);\nDEFINE_CVAR_GLOBAL(OrbitScreenTargetingBoxCenterY);\nDEFINE_CVAR_GLOBAL(OrbitScreenScanBoxCenterY);\nDEFINE_CVAR_GLOBAL(OrbitZoneTargetingIdealX);\nDEFINE_CVAR_GLOBAL(OrbitZoneScanIdealX);\nDEFINE_CVAR_GLOBAL(OrbitZoneTargetingIdealY);\nDEFINE_CVAR_GLOBAL(OrbitZoneScanIdealY);\nDEFINE_CVAR_GLOBAL(OrbitNearX);\nDEFINE_CVAR_GLOBAL(OrbitNearZ);\n// DEFINE_CVAR_GLOBAL(); // x1e0_\n// DEFINE_CVAR_GLOBAL(); // x1e4_\nDEFINE_CVAR_GLOBAL(OrbitFixedOffsetZDiff);\nDEFINE_CVAR_GLOBAL(OrbitZRange);\n// DEFINE_CVAR_GLOBAL(); // x1f0_\n// DEFINE_CVAR_GLOBAL(); // x1f4_\n// DEFINE_CVAR_GLOBAL(); // x1f8_\nDEFINE_CVAR_GLOBAL(OrbitPreventionTime);\nDEFINE_CVAR_GLOBAL(DashEnabled);\nDEFINE_CVAR_GLOBAL(DashOnButtonRelease);\nDEFINE_CVAR_GLOBAL(DashButtonHoldCancelTime);\nDEFINE_CVAR_GLOBAL(DashStrafeInputThreshold);\nDEFINE_CVAR_GLOBAL(SidewaysDoubleJumpImpulse);\nDEFINE_CVAR_GLOBAL(SidewaysVerticalDoubleJumpAccel);\nDEFINE_CVAR_GLOBAL(SidewaysHorizontalDoubleJumpAccel);\nDEFINE_CVAR_GLOBAL(ScanningRange);\nDEFINE_CVAR_GLOBAL(ScanRetention);\nDEFINE_CVAR_GLOBAL(ScanFreezesGame);\nDEFINE_CVAR_GLOBAL(OrbitWhileScanning);\nDEFINE_CVAR_GLOBAL(ScanMaxTargetDistance);\nDEFINE_CVAR_GLOBAL(ScanMaxLockDistance)\nDEFINE_CVAR_GLOBAL(FreeLookTurnsPlayer);\n// DEFINE_CVAR_GLOBAL(); // x228_25_\n// DEFINE_CVAR_GLOBAL(); // x228_26_\nDEFINE_CVAR_GLOBAL(MoveDuringFreelook);\nDEFINE_CVAR_GLOBAL(HoldButtonsForFreeLook);\n// DEFINE_CVAR_GLOBAL(); // x228_30_\n// DEFINE_CVAR_GLOBAL(); // x228_31_\n// DEFINE_CVAR_GLOBAL(); // x229_24_\nDEFINE_CVAR_GLOBAL(AimWhenOrbitingPoint);\nDEFINE_CVAR_GLOBAL(StayInFreeLookWhileFiring);\n// DEFINE_CVAR_GLOBAL(); // x229_27_\n// DEFINE_CVAR_GLOBAL(); // x229_28_\nDEFINE_CVAR_GLOBAL(OrbitFixedOffset);\nDEFINE_CVAR_GLOBAL(GunButtonTogglesHolster);\nDEFINE_CVAR_GLOBAL(GunNotFiringHolstersGun);\nDEFINE_CVAR_GLOBAL(FallingDoubleJump);\nDEFINE_CVAR_GLOBAL(ImpulseDoubleJump);\nDEFINE_CVAR_GLOBAL(FiringCancelsCameraPitch);\nDEFINE_CVAR_GLOBAL(AssistedAimingIgnoreHorizontal);\nDEFINE_CVAR_GLOBAL(AssistedAimingIgnoreVertical);\n// DEFINE_CVAR_GLOBAL(); // x22c\n// DEFINE_CVAR_GLOBAL(); // x230_\nDEFINE_CVAR_GLOBAL(AimMaxDistance);\n// DEFINE_CVAR_GLOBAL(); // x238_\n// DEFINE_CVAR_GLOBAL(); // x23c_\n// DEFINE_CVAR_GLOBAL(); // x240_\n// DEFINE_CVAR_GLOBAL(); // x244_\n// DEFINE_CVAR_GLOBAL(); // x248_\nDEFINE_CVAR_GLOBAL(AimThresholdDistance);\n// DEFINE_CVAR_GLOBAL(); // x250_\n// DEFINE_CVAR_GLOBAL(); // x254_\nDEFINE_CVAR_GLOBAL(AimBoxWidth);\nDEFINE_CVAR_GLOBAL(AimBoxHeight);\nDEFINE_CVAR_GLOBAL(AimTargetTimer);\nDEFINE_CVAR_GLOBAL(AimAssistHorizontalAngle);\nDEFINE_CVAR_GLOBAL(AimAssistVerticalAngle);\nDEFINE_CVAR_GLOBAL(PlayerHeight);\nDEFINE_CVAR_GLOBAL(PlayerXYHalfExtent);\nDEFINE_CVAR_GLOBAL(StepUpHeight);\nDEFINE_CVAR_GLOBAL(StepDownHeight);\nDEFINE_CVAR_GLOBAL(PlayerBallHalfExtent);\nDEFINE_CVAR_GLOBAL(FirstPersonCameraSpeed);\n// DEFINE_CVAR_GLOBAL(); // x284_\nDEFINE_CVAR_GLOBAL(JumpCameraPitchDownStart);\nDEFINE_CVAR_GLOBAL(JumpCameraPitchDownFull);\nDEFINE_CVAR_GLOBAL(JumpCameraPitchDownAngle);\nDEFINE_CVAR_GLOBAL(FallCameraPitchDownStart);\nDEFINE_CVAR_GLOBAL(FallCameraPitchDownFull);\nDEFINE_CVAR_GLOBAL(FallCameraPitchDownAngle);\nDEFINE_CVAR_GLOBAL(OrbitDistanceMax);\nDEFINE_CVAR_GLOBAL(GrappleSwingLength);\nDEFINE_CVAR_GLOBAL(GrappleSwingPeriod);\nDEFINE_CVAR_GLOBAL(GrapplePullSpeedMin);\nDEFINE_CVAR_GLOBAL(GrappleCameraSpeed);\nDEFINE_CVAR_GLOBAL(MaxGrappleLockedTurnAlignDistance);\nDEFINE_CVAR_GLOBAL(GrapplePullSpeedProportion);\nDEFINE_CVAR_GLOBAL(GrapplePullSpeedMax);\nDEFINE_CVAR_GLOBAL(GrappleLookCenterSpeed);\nDEFINE_CVAR_GLOBAL(MaxGrappleTurnSpeed);\nDEFINE_CVAR_GLOBAL(GrappleJumpForce);\nDEFINE_CVAR_GLOBAL(GrappleReleaseTime);\nDEFINE_CVAR_GLOBAL(GrappleJumpMode);\nDEFINE_CVAR_GLOBAL(OrbitReleaseBreaksGrapple);\nDEFINE_CVAR_GLOBAL(InvertGrappleTurn);\nDEFINE_CVAR_GLOBAL(GrappleBeamSpeed);\nDEFINE_CVAR_GLOBAL(GrappleBeamXWaveAmplitude);\nDEFINE_CVAR_GLOBAL(GrappleBeamZWaveAmplitude);\nDEFINE_CVAR_GLOBAL(GrappleBeamAnglePhaseDelta);\n// DEFINE_CVAR_GLOBAL(); // x2e8_\n// DEFINE_CVAR_GLOBAL(); // x2ec_\n// DEFINE_CVAR_GLOBAL(); // x2f0_\n// DEFINE_CVAR_GLOBAL(); // x2f4_\nDEFINE_CVAR_GLOBAL(FrozenTimeout);\nDEFINE_CVAR_GLOBAL(IceBreakJumpCount);\nDEFINE_CVAR_GLOBAL(VariaDamageReduction);\nDEFINE_CVAR_GLOBAL(GravityDamageReduction);\nDEFINE_CVAR_GLOBAL(PhazonDamageReduction);\n} // namespace\n\nCTweakPlayer::CTweakPlayer(CInputStream& in) {\n  /* x4_maxTranslationalAcceleration[0] */\n  x4_maxTranslationalAcceleration[0] = in.ReadFloat();\n  /* x4_maxTranslationalAcceleration[1] */\n  x4_maxTranslationalAcceleration[1] = in.ReadFloat();\n  /* x4_maxTranslationalAcceleration[2] */\n  x4_maxTranslationalAcceleration[2] = in.ReadFloat();\n  /* x4_maxTranslationalAcceleration[3] */\n  x4_maxTranslationalAcceleration[3] = in.ReadFloat();\n  /* x4_maxTranslationalAcceleration[4] */\n  x4_maxTranslationalAcceleration[4] = in.ReadFloat();\n  /* x4_maxTranslationalAcceleration[5] */\n  x4_maxTranslationalAcceleration[5] = in.ReadFloat();\n  /* x4_maxTranslationalAcceleration[6] */\n  x4_maxTranslationalAcceleration[6] = in.ReadFloat();\n  /* x4_maxTranslationalAcceleration[7] */\n  x4_maxTranslationalAcceleration[7] = in.ReadFloat();\n  /* x24_maxRotationalAcceleration[0] */\n  x24_maxRotationalAcceleration[0] = in.ReadFloat();\n  /* x24_maxRotationalAcceleration[1] */\n  x24_maxRotationalAcceleration[1] = in.ReadFloat();\n  /* x24_maxRotationalAcceleration[2] */\n  x24_maxRotationalAcceleration[2] = in.ReadFloat();\n  /* x24_maxRotationalAcceleration[3] */\n  x24_maxRotationalAcceleration[3] = in.ReadFloat();\n  /* x24_maxRotationalAcceleration[4] */\n  x24_maxRotationalAcceleration[4] = in.ReadFloat();\n  /* x24_maxRotationalAcceleration[5] */\n  x24_maxRotationalAcceleration[5] = in.ReadFloat();\n  /* x24_maxRotationalAcceleration[6] */\n  x24_maxRotationalAcceleration[6] = in.ReadFloat();\n  /* x24_maxRotationalAcceleration[7] */\n  x24_maxRotationalAcceleration[7] = in.ReadFloat();\n  /* x44_translationFriction[0] */\n  x44_translationFriction[0] = in.ReadFloat();\n  /* x44_translationFriction[1] */\n  x44_translationFriction[1] = in.ReadFloat();\n  /* x44_translationFriction[2] */\n  x44_translationFriction[2] = in.ReadFloat();\n  /* x44_translationFriction[3] */\n  x44_translationFriction[3] = in.ReadFloat();\n  /* x44_translationFriction[4] */\n  x44_translationFriction[4] = in.ReadFloat();\n  /* x44_translationFriction[5] */\n  x44_translationFriction[5] = in.ReadFloat();\n  /* x44_translationFriction[6] */\n  x44_translationFriction[6] = in.ReadFloat();\n  /* x44_translationFriction[7] */\n  x44_translationFriction[7] = in.ReadFloat();\n  /* x64_rotationFriction[0] */\n  x64_rotationFriction[0] = in.ReadFloat();\n  /* x64_rotationFriction[1] */\n  x64_rotationFriction[1] = in.ReadFloat();\n  /* x64_rotationFriction[2] */\n  x64_rotationFriction[2] = in.ReadFloat();\n  /* x64_rotationFriction[3] */\n  x64_rotationFriction[3] = in.ReadFloat();\n  /* x64_rotationFriction[4] */\n  x64_rotationFriction[4] = in.ReadFloat();\n  /* x64_rotationFriction[5] */\n  x64_rotationFriction[5] = in.ReadFloat();\n  /* x64_rotationFriction[6] */\n  x64_rotationFriction[6] = in.ReadFloat();\n  /* x64_rotationFriction[7] */\n  x64_rotationFriction[7] = in.ReadFloat();\n  /* x84_rotationMaxSpeed[0] */\n  x84_rotationMaxSpeed[0] = in.ReadFloat();\n  /* x84_rotationMaxSpeed[1] */\n  x84_rotationMaxSpeed[1] = in.ReadFloat();\n  /* x84_rotationMaxSpeed[2] */\n  x84_rotationMaxSpeed[2] = in.ReadFloat();\n  /* x84_rotationMaxSpeed[3] */\n  x84_rotationMaxSpeed[3] = in.ReadFloat();\n  /* x84_rotationMaxSpeed[4] */\n  x84_rotationMaxSpeed[4] = in.ReadFloat();\n  /* x84_rotationMaxSpeed[5] */\n  x84_rotationMaxSpeed[5] = in.ReadFloat();\n  /* x84_rotationMaxSpeed[6] */\n  x84_rotationMaxSpeed[6] = in.ReadFloat();\n  /* x84_rotationMaxSpeed[7] */\n  x84_rotationMaxSpeed[7] = in.ReadFloat();\n  /* xa4_translationMaxSpeed[0] */\n  xa4_translationMaxSpeed[0] = in.ReadFloat();\n  /* xa4_translationMaxSpeed[1] */\n  xa4_translationMaxSpeed[1] = in.ReadFloat();\n  /* xa4_translationMaxSpeed[2] */\n  xa4_translationMaxSpeed[2] = in.ReadFloat();\n  /* xa4_translationMaxSpeed[3] */\n  xa4_translationMaxSpeed[3] = in.ReadFloat();\n  /* xa4_translationMaxSpeed[4] */\n  xa4_translationMaxSpeed[4] = in.ReadFloat();\n  /* xa4_translationMaxSpeed[5] */\n  xa4_translationMaxSpeed[5] = in.ReadFloat();\n  /* xa4_translationMaxSpeed[6] */\n  xa4_translationMaxSpeed[6] = in.ReadFloat();\n  /* xa4_translationMaxSpeed[7] */\n  xa4_translationMaxSpeed[7] = in.ReadFloat();\n  /* xc4_normalGravAccel */\n  xc4_normalGravAccel = in.ReadFloat();\n  /* xc8_fluidGravAccel */\n  xc8_fluidGravAccel = in.ReadFloat();\n  /* xcc_verticalJumpAccel */\n  xcc_verticalJumpAccel = in.ReadFloat();\n  /* xd0_horizontalJumpAccel */\n  xd0_horizontalJumpAccel = in.ReadFloat();\n  /* xd4_verticalDoubleJumpAccel */\n  xd4_verticalDoubleJumpAccel = in.ReadFloat();\n  /* xd8_horizontalDoubleJumpAccel */\n  xd8_horizontalDoubleJumpAccel = in.ReadFloat();\n  /* xdc_waterJumpFactor */\n  xdc_waterJumpFactor = in.ReadFloat();\n  /* xe0_waterBallJumpFactor */\n  xe0_waterBallJumpFactor = in.ReadFloat();\n  /* xe4_lavaJumpFactor */\n  xe4_lavaJumpFactor = in.ReadFloat();\n  /* xe8_lavaBallJumpFactor */\n  xe8_lavaBallJumpFactor = in.ReadFloat();\n  /* xec_phazonJumpFactor */\n  xec_phazonJumpFactor = in.ReadFloat();\n  /* xf0_phazonBallJumpFactor */\n  xf0_phazonBallJumpFactor = in.ReadFloat();\n  /* xf4_allowedJumpTime */\n  xf4_allowedJumpTime = in.ReadFloat();\n  /* xf8_allowedDoubleJumpTime */\n  xf8_allowedDoubleJumpTime = in.ReadFloat();\n  /* xfc_minDoubleJumpWindow */\n  xfc_minDoubleJumpWindow = in.ReadFloat();\n  /* x100_maxDoubleJumpWindow */\n  x100_maxDoubleJumpWindow = in.ReadFloat();\n  /* x104_ */\n  x104_ = in.ReadFloat();\n  /* x108_minJumpTime */\n  x108_minJumpTime = in.ReadFloat();\n  /* x10c_minDoubleJumpTime */\n  x10c_minDoubleJumpTime = in.ReadFloat();\n  /* x110_allowedLedgeTime */\n  x110_allowedLedgeTime = in.ReadFloat();\n  /* x114_doubleJumpImpulse */\n  x114_doubleJumpImpulse = in.ReadFloat();\n  /* x118_backwardsForceMultiplier */\n  x118_backwardsForceMultiplier = in.ReadFloat();\n  /* x11c_bombJumpRadius */\n  x11c_bombJumpRadius = in.ReadFloat();\n  /* x120_bombJumpHeight */\n  x120_bombJumpHeight = in.ReadFloat();\n  /* x124_eyeOffset */\n  x124_eyeOffset = in.ReadFloat();\n  /* x128_turnSpeedMultiplier */\n  x128_turnSpeedMultiplier = in.ReadFloat();\n  /* x12c_freeLookTurnSpeedMultiplier */\n  x12c_freeLookTurnSpeedMultiplier = in.ReadFloat();\n  /* x130_horizontalFreeLookAngleVel */\n  x130_horizontalFreeLookAngleVel = in.ReadFloat();\n  /* x134_verticalFreeLookAngleVel */\n  x134_verticalFreeLookAngleVel = in.ReadFloat();\n  /* x138_freeLookSpeed */\n  x138_freeLookSpeed = in.ReadFloat();\n  /* x13c_freeLookSnapSpeed */\n  x13c_freeLookSnapSpeed = in.ReadFloat();\n  /* x140_ */\n  x140_ = in.ReadFloat();\n  /* x144_freeLookCenteredThresholdAngle */\n  x144_freeLookCenteredThresholdAngle = in.ReadFloat();\n  /* x148_freeLookCenteredTime */\n  x148_freeLookCenteredTime = in.ReadFloat();\n  /* x14c_freeLookDampenFactor */\n  x14c_freeLookDampenFactor = in.ReadFloat();\n  /* x150_leftDiv */\n  x150_leftDiv = in.ReadFloat();\n  /* x154_rightDiv */\n  x154_rightDiv = in.ReadFloat();\n  /* x228_24_freelookTurnsPlayer */\n  x228_24_freelookTurnsPlayer = in.ReadBool();\n  /* x228_25_ */\n  x228_25_ = in.ReadBool();\n  /* x228_26_ */\n  x228_26_ = in.ReadBool();\n  /* x228_27_moveDuringFreeLook */\n  x228_27_moveDuringFreeLook = in.ReadBool();\n  /* x228_28_holdButtonsForFreeLook */\n  x228_28_holdButtonsForFreeLook = in.ReadBool();\n  /* x228_29_twoButtonsForFreeLook */\n  x228_29_twoButtonsForFreeLook = in.ReadBool();\n  /* x228_30_ */\n  x228_30_ = in.ReadBool();\n  /* x228_31_ */\n  x228_31_ = in.ReadBool();\n  /* x229_24_ */\n  x229_24_ = in.ReadBool();\n  /* x229_25_aimWhenOrbitingPoint */\n  x229_25_aimWhenOrbitingPoint = in.ReadBool();\n  /* x229_26_stayInFreeLookWhileFiring */\n  x229_26_stayInFreeLookWhileFiring = in.ReadBool();\n  /* x229_27_ */\n  x229_27_ = in.ReadBool();\n  /* x229_28_ */\n  x229_28_ = in.ReadBool();\n  /* x229_29_orbitFixedOffset */\n  x229_29_orbitFixedOffset = in.ReadBool();\n  /* x229_30_gunButtonTogglesHolster */\n  x229_30_gunButtonTogglesHolster = in.ReadBool();\n  /* x229_31_gunNotFiringHolstersGun */\n  x229_31_gunNotFiringHolstersGun = in.ReadBool();\n  /* x22a_24_fallingDoubleJump */\n  x22a_24_fallingDoubleJump = in.ReadBool();\n  /* x22a_25_impulseDoubleJump */\n  x22a_25_impulseDoubleJump = in.ReadBool();\n  /* x22a_26_firingCancelsCameraPitch */\n  x22a_26_firingCancelsCameraPitch = in.ReadBool();\n  /* x22a_27_assistedAimingIgnoreHorizontal */\n  x22a_27_assistedAimingIgnoreHorizontal = in.ReadBool();\n  /* x22a_28_assistedAimingIgnoreVertical */\n  x22a_28_assistedAimingIgnoreVertical = in.ReadBool();\n  /* x22c_ */\n  x22c_ = in.ReadFloat();\n  /* x230_ */\n  x230_ = in.ReadFloat();\n  /* x234_aimMaxDistance */\n  x234_aimMaxDistance = in.ReadFloat();\n  /* x238_ */\n  x238_ = in.ReadFloat();\n  /* x23c_ */\n  x23c_ = in.ReadFloat();\n  /* x240_ */\n  x240_ = in.ReadFloat();\n  /* x244_ */\n  x244_ = in.ReadFloat();\n  /* x248_ */\n  x248_ = in.ReadFloat();\n  /* x24c_aimThresholdDistance */\n  x24c_aimThresholdDistance = in.ReadFloat();\n  /* x250_ */\n  x250_ = in.ReadFloat();\n  /* x254_ */\n  x254_ = in.ReadFloat();\n  /* x258_aimBoxWidth */\n  x258_aimBoxWidth = in.ReadFloat();\n  /* x25c_aimBoxHeight */\n  x25c_aimBoxHeight = in.ReadFloat();\n  /* x260_aimTargetTimer */\n  x260_aimTargetTimer = in.ReadFloat();\n  /* x264_aimAssistHorizontalAngle */\n  x264_aimAssistHorizontalAngle = in.ReadFloat();\n  /* x268_aimAssistVerticalAngle */\n  x268_aimAssistVerticalAngle = in.ReadFloat();\n  /* x158_orbitMinDistance[0] */\n  x158_orbitMinDistance[0] = in.ReadFloat();\n  /* x164_orbitNormalDistance[0] */\n  x164_orbitNormalDistance[0] = in.ReadFloat();\n  /* x170_orbitMaxDistance[0] */\n  x170_orbitMaxDistance[0] = in.ReadFloat();\n  /* x158_orbitMinDistance[1] */\n  x158_orbitMinDistance[1] = in.ReadFloat();\n  /* x164_orbitNormalDistance[1] */\n  x164_orbitNormalDistance[1] = in.ReadFloat();\n  /* x170_orbitMaxDistance[1] */\n  x170_orbitMaxDistance[1] = in.ReadFloat();\n  /* x158_orbitMinDistance[2] */\n  x158_orbitMinDistance[2] = in.ReadFloat();\n  /* x164_orbitNormalDistance[2] */\n  x164_orbitNormalDistance[2] = in.ReadFloat();\n  /* x170_orbitMaxDistance[2] */\n  x170_orbitMaxDistance[2] = in.ReadFloat();\n  /* x17c_ */\n  x17c_ = in.ReadFloat();\n  /* x180_orbitModeTimer */\n  x180_orbitModeTimer = in.ReadFloat();\n  /* x184_orbitCameraSpeed */\n  x184_orbitCameraSpeed = in.ReadFloat();\n  /* x188_orbitUpperAngle */\n  x188_orbitUpperAngle = in.ReadFloat();\n  /* x18c_orbitLowerAngle */\n  x18c_orbitLowerAngle = in.ReadFloat();\n  /* x190_orbitHorizAngle */\n  x190_orbitHorizAngle = in.ReadFloat();\n  /* x194_ */\n  x194_ = in.ReadFloat();\n  /* x198_ */\n  x198_ = in.ReadFloat();\n  /* x19c_orbitMaxTargetDistance */\n  x19c_orbitMaxTargetDistance = in.ReadFloat();\n  /* x1a0_orbitMaxLockDistance */\n  x1a0_orbitMaxLockDistance = in.ReadFloat();\n  /* x1a4_orbitDistanceThreshold */\n  x1a4_orbitDistanceThreshold = in.ReadFloat();\n  /* x1a8_orbitScreenBoxHalfExtentX[0] */\n  x1a8_orbitScreenBoxHalfExtentX[0] = in.ReadLong();\n  /* x1b0_orbitScreenBoxHalfExtentY[0] */\n  x1b0_orbitScreenBoxHalfExtentY[0] = in.ReadLong();\n  /* x1b8_orbitScreenBoxCenterX[0] */\n  x1b8_orbitScreenBoxCenterX[0] = in.ReadLong();\n  /* x1c0_orbitScreenBoxCenterY[0] */\n  x1c0_orbitScreenBoxCenterY[0] = in.ReadLong();\n  /* x1c8_orbitZoneIdealX[0] */\n  x1c8_orbitZoneIdealX[0] = in.ReadLong();\n  /* x1d0_orbitZoneIdealY[0] */\n  x1d0_orbitZoneIdealY[0] = in.ReadLong();\n  /* x1a8_orbitScreenBoxHalfExtentX[1] */\n  x1a8_orbitScreenBoxHalfExtentX[1] = in.ReadLong();\n  /* x1b0_orbitScreenBoxHalfExtentY[1] */\n  x1b0_orbitScreenBoxHalfExtentY[1] = in.ReadLong();\n  /* x1b8_orbitScreenBoxCenterX[1] */\n  x1b8_orbitScreenBoxCenterX[1] = in.ReadLong();\n  /* x1c0_orbitScreenBoxCenterY[1] */\n  x1c0_orbitScreenBoxCenterY[1] = in.ReadLong();\n  /* x1c8_orbitZoneIdealX[1] */\n  x1c8_orbitZoneIdealX[1] = in.ReadLong();\n  /* x1d0_orbitZoneIdealY[1] */\n  x1d0_orbitZoneIdealY[1] = in.ReadLong();\n  /* x1d8_orbitNearX */\n  x1d8_orbitNearX = in.ReadFloat();\n  /* x1dc_orbitNearZ */\n  x1dc_orbitNearZ = in.ReadFloat();\n  /* x1e0_ */\n  x1e0_ = in.ReadFloat();\n  /* x1e4_ */\n  x1e4_ = in.ReadFloat();\n  /* x1e8_orbitFixedOffsetZDiff */\n  x1e8_orbitFixedOffsetZDiff = in.ReadFloat();\n  /* x1ec_orbitZRange */\n  x1ec_orbitZRange = in.ReadFloat();\n  /* x1f0_ */\n  x1f0_ = in.ReadFloat();\n  /* x1f4_ */\n  x1f4_ = in.ReadFloat();\n  /* x1f8_ */\n  x1f8_ = in.ReadFloat();\n  /* x1fc_orbitPreventionTime */\n  x1fc_orbitPreventionTime = in.ReadFloat();\n  /* x200_24_dashEnabled */\n  x200_24_dashEnabled = in.ReadBool();\n  /* x200_25_dashOnButtonRelease */\n  x200_25_dashOnButtonRelease = in.ReadBool();\n  /* x204_dashButtonHoldCancelTime */\n  x204_dashButtonHoldCancelTime = in.ReadFloat();\n  /* x208_dashStrafeInputThreshold */\n  x208_dashStrafeInputThreshold = in.ReadFloat();\n  /* x20c_sidewaysDoubleJumpImpulse */\n  x20c_sidewaysDoubleJumpImpulse = in.ReadFloat();\n  /* x210_sidewaysVerticalDoubleJumpAccel */\n  x210_sidewaysVerticalDoubleJumpAccel = in.ReadFloat();\n  /* x214_sidewaysHorizontalDoubleJumpAccel */\n  x214_sidewaysHorizontalDoubleJumpAccel = in.ReadFloat();\n  /* x218_scanningRange */\n  x218_scanningRange = in.ReadFloat();\n  /* x21c_24_scanRetention */\n  x21c_24_scanRetention = in.ReadBool();\n  /* x21c_25_scanFreezesGame */\n  x21c_25_scanFreezesGame = in.ReadBool();\n  /* x21c_26_orbitWhileScanning */\n  x21c_26_orbitWhileScanning = in.ReadBool();\n  /* x220_scanMaxTargetDistance */\n  x220_scanMaxTargetDistance = in.ReadFloat();\n  /* x224_scanMaxLockDistance */\n  x224_scanMaxLockDistance = in.ReadFloat();\n  /* x2a0_orbitDistanceMax */\n  x2a0_orbitDistanceMax = in.ReadFloat();\n  /* x2a4_grappleSwingLength */\n  x2a4_grappleSwingLength = in.ReadFloat();\n  /* x2a8_grappleSwingPeriod */\n  x2a8_grappleSwingPeriod = in.ReadFloat();\n  /* x2ac_grapplePullSpeedMin */\n  x2ac_grapplePullSpeedMin = in.ReadFloat();\n  /* x2b0_grappleCameraSpeed */\n  x2b0_grappleCameraSpeed = in.ReadFloat();\n  /* x2b4_maxGrappleLockedTurnAlignDistance */\n  x2b4_maxGrappleLockedTurnAlignDistance = in.ReadFloat();\n  /* x2b8_grapplePullSpeedProportion */\n  x2b8_grapplePullSpeedProportion = in.ReadFloat();\n  /* x2bc_grapplePullSpeedMax */\n  x2bc_grapplePullSpeedMax = in.ReadFloat();\n  /* x2c0_grappleLookCenterSpeed */\n  x2c0_grappleLookCenterSpeed = in.ReadFloat();\n  /* x2c4_maxGrappleTurnSpeed */\n  x2c4_maxGrappleTurnSpeed = in.ReadFloat();\n  /* x2c8_grappleJumpForce */\n  x2c8_grappleJumpForce = in.ReadFloat();\n  /* x2cc_grappleReleaseTime */\n  x2cc_grappleReleaseTime = in.ReadFloat();\n  /* x2d0_grappleJumpMode */\n  x2d0_grappleJumpMode = in.ReadLong();\n  /* x2d4_orbitReleaseBreaksGrapple */\n  x2d4_orbitReleaseBreaksGrapple = in.ReadBool();\n  /* x2d5_invertGrappleTurn */\n  x2d5_invertGrappleTurn = in.ReadBool();\n  /* x2d8_grappleBeamSpeed */\n  x2d8_grappleBeamSpeed = in.ReadFloat();\n  /* x2dc_grappleBeamXWaveAmplitude */\n  x2dc_grappleBeamXWaveAmplitude = in.ReadFloat();\n  /* x2e0_grappleBeamZWaveAmplitude */\n  x2e0_grappleBeamZWaveAmplitude = in.ReadFloat();\n  /* x2e4_grappleBeamAnglePhaseDelta */\n  x2e4_grappleBeamAnglePhaseDelta = in.ReadFloat();\n  /* x26c_playerHeight */\n  x26c_playerHeight = in.ReadFloat();\n  /* x270_playerXYHalfExtent */\n  x270_playerXYHalfExtent = in.ReadFloat();\n  /* x274_stepUpHeight */\n  x274_stepUpHeight = in.ReadFloat();\n  /* x278_stepDownHeight */\n  x278_stepDownHeight = in.ReadFloat();\n  /* x27c_playerBallHalfExtent */\n  x27c_playerBallHalfExtent = in.ReadFloat();\n  /* x280_ */\n  x280_firstPersonCameraSpeed = in.ReadFloat();\n  /* x284_ */\n  x284_ = in.ReadFloat();\n  /* x288_jumpCameraPitchDownStart */\n  x288_jumpCameraPitchDownStart = in.ReadFloat();\n  /* x28c_jumpCameraPitchDownFull */\n  x28c_jumpCameraPitchDownFull = in.ReadFloat();\n  /* x290_jumpCameraPitchDownAngle */\n  x290_jumpCameraPitchDownAngle = in.ReadFloat();\n  /* x294_fallCameraPitchDownStart */\n  x294_fallCameraPitchDownStart = in.ReadFloat();\n  /* x298_fallCameraPitchDownFull */\n  x298_fallCameraPitchDownFull = in.ReadFloat();\n  /* x29c_fallCameraPitchDownAngle */\n  x29c_fallCameraPitchDownAngle = in.ReadFloat();\n  /* x2e8_ */\n  x2e8_ = in.ReadFloat();\n  /* x2ec_ */\n  x2ec_ = in.ReadFloat();\n  /* x2f0_ */\n  x2f0_ = in.ReadFloat();\n  /* x2f4_ */\n  x2f4_ = in.ReadBool();\n  /* x2f8_frozenTimeout */\n  x2f8_frozenTimeout = in.ReadFloat();\n  /* x2fc_iceBreakJumpCount */\n  x2fc_iceBreakJumpCount = in.ReadLong();\n  /* x300_variaDamageReduction */\n  x300_variaDamageReduction = in.ReadFloat();\n  /* x304_gravityDamageReduction */\n  x304_gravityDamageReduction = in.ReadFloat();\n  /* x308_phazonDamageReduction */\n  x308_phazonDamageReduction = in.ReadFloat();\n\n  FixupValues();\n}\n\nvoid CTweakPlayer::PutTo(COutputStream& out) {\n  /* x4_maxTranslationalAcceleration[0] */\n  out.Put(x4_maxTranslationalAcceleration[0]);\n  /* x4_maxTranslationalAcceleration[1] */\n  out.Put(x4_maxTranslationalAcceleration[1]);\n  /* x4_maxTranslationalAcceleration[2] */\n  out.Put(x4_maxTranslationalAcceleration[2]);\n  /* x4_maxTranslationalAcceleration[3] */\n  out.Put(x4_maxTranslationalAcceleration[3]);\n  /* x4_maxTranslationalAcceleration[4] */\n  out.Put(x4_maxTranslationalAcceleration[4]);\n  /* x4_maxTranslationalAcceleration[5] */\n  out.Put(x4_maxTranslationalAcceleration[5]);\n  /* x4_maxTranslationalAcceleration[6] */\n  out.Put(x4_maxTranslationalAcceleration[6]);\n  /* x4_maxTranslationalAcceleration[7] */\n  out.Put(x4_maxTranslationalAcceleration[7]);\n  /* x24_maxRotationalAcceleration[0] */\n  out.Put(x24_maxRotationalAcceleration[0]);\n  /* x24_maxRotationalAcceleration[1] */\n  out.Put(x24_maxRotationalAcceleration[1]);\n  /* x24_maxRotationalAcceleration[2] */\n  out.Put(x24_maxRotationalAcceleration[2]);\n  /* x24_maxRotationalAcceleration[3] */\n  out.Put(x24_maxRotationalAcceleration[3]);\n  /* x24_maxRotationalAcceleration[4] */\n  out.Put(x24_maxRotationalAcceleration[4]);\n  /* x24_maxRotationalAcceleration[5] */\n  out.Put(x24_maxRotationalAcceleration[5]);\n  /* x24_maxRotationalAcceleration[6] */\n  out.Put(x24_maxRotationalAcceleration[6]);\n  /* x24_maxRotationalAcceleration[7] */\n  out.Put(x24_maxRotationalAcceleration[7]);\n  /* x44_translationFriction[0] */\n  out.Put(x44_translationFriction[0]);\n  /* x44_translationFriction[1] */\n  out.Put(x44_translationFriction[1]);\n  /* x44_translationFriction[2] */\n  out.Put(x44_translationFriction[2]);\n  /* x44_translationFriction[3] */\n  out.Put(x44_translationFriction[3]);\n  /* x44_translationFriction[4] */\n  out.Put(x44_translationFriction[4]);\n  /* x44_translationFriction[5] */\n  out.Put(x44_translationFriction[5]);\n  /* x44_translationFriction[6] */\n  out.Put(x44_translationFriction[6]);\n  /* x44_translationFriction[7] */\n  out.Put(x44_translationFriction[7]);\n  /* x64_rotationFriction[0] */\n  out.Put(x64_rotationFriction[0]);\n  /* x64_rotationFriction[1] */\n  out.Put(x64_rotationFriction[1]);\n  /* x64_rotationFriction[2] */\n  out.Put(x64_rotationFriction[2]);\n  /* x64_rotationFriction[3] */\n  out.Put(x64_rotationFriction[3]);\n  /* x64_rotationFriction[4] */\n  out.Put(x64_rotationFriction[4]);\n  /* x64_rotationFriction[5] */\n  out.Put(x64_rotationFriction[5]);\n  /* x64_rotationFriction[6] */\n  out.Put(x64_rotationFriction[6]);\n  /* x64_rotationFriction[7] */\n  out.Put(x64_rotationFriction[7]);\n  /* x84_rotationMaxSpeed[0] */\n  out.Put(x84_rotationMaxSpeed[0]);\n  /* x84_rotationMaxSpeed[1] */\n  out.Put(x84_rotationMaxSpeed[1]);\n  /* x84_rotationMaxSpeed[2] */\n  out.Put(x84_rotationMaxSpeed[2]);\n  /* x84_rotationMaxSpeed[3] */\n  out.Put(x84_rotationMaxSpeed[3]);\n  /* x84_rotationMaxSpeed[4] */\n  out.Put(x84_rotationMaxSpeed[4]);\n  /* x84_rotationMaxSpeed[5] */\n  out.Put(x84_rotationMaxSpeed[5]);\n  /* x84_rotationMaxSpeed[6] */\n  out.Put(x84_rotationMaxSpeed[6]);\n  /* x84_rotationMaxSpeed[7] */\n  out.Put(x84_rotationMaxSpeed[7]);\n  /* xa4_translationMaxSpeed[0] */\n  out.Put(xa4_translationMaxSpeed[0]);\n  /* xa4_translationMaxSpeed[1] */\n  out.Put(xa4_translationMaxSpeed[1]);\n  /* xa4_translationMaxSpeed[2] */\n  out.Put(xa4_translationMaxSpeed[2]);\n  /* xa4_translationMaxSpeed[3] */\n  out.Put(xa4_translationMaxSpeed[3]);\n  /* xa4_translationMaxSpeed[4] */\n  out.Put(xa4_translationMaxSpeed[4]);\n  /* xa4_translationMaxSpeed[5] */\n  out.Put(xa4_translationMaxSpeed[5]);\n  /* xa4_translationMaxSpeed[6] */\n  out.Put(xa4_translationMaxSpeed[6]);\n  /* xa4_translationMaxSpeed[7] */\n  out.Put(xa4_translationMaxSpeed[7]);\n  /* xc4_normalGravAccel */\n  out.Put(xc4_normalGravAccel);\n  /* xc8_fluidGravAccel */\n  out.Put(xc8_fluidGravAccel);\n  /* xcc_verticalJumpAccel */\n  out.Put(xcc_verticalJumpAccel);\n  /* xd0_horizontalJumpAccel */\n  out.Put(xd0_horizontalJumpAccel);\n  /* xd4_verticalDoubleJumpAccel */\n  out.Put(xd4_verticalDoubleJumpAccel);\n  /* xd8_horizontalDoubleJumpAccel */\n  out.Put(xd8_horizontalDoubleJumpAccel);\n  /* xdc_waterJumpFactor */\n  out.Put(xdc_waterJumpFactor);\n  /* xe0_waterBallJumpFactor */\n  out.Put(xe0_waterBallJumpFactor);\n  /* xe4_lavaJumpFactor */\n  out.Put(xe4_lavaJumpFactor);\n  /* xe8_lavaBallJumpFactor */\n  out.Put(xe8_lavaBallJumpFactor);\n  /* xec_phazonJumpFactor */\n  out.Put(xec_phazonJumpFactor);\n  /* xf0_phazonBallJumpFactor */\n  out.Put(xf0_phazonBallJumpFactor);\n  /* xf4_allowedJumpTime */\n  out.Put(xf4_allowedJumpTime);\n  /* xf8_allowedDoubleJumpTime */\n  out.Put(xf8_allowedDoubleJumpTime);\n  /* xfc_minDoubleJumpWindow */\n  out.Put(xfc_minDoubleJumpWindow);\n  /* x100_maxDoubleJumpWindow */\n  out.Put(x100_maxDoubleJumpWindow);\n  /* x104_ */\n  out.Put(x104_);\n  /* x108_minJumpTime */\n  out.Put(x108_minJumpTime);\n  /* x10c_minDoubleJumpTime */\n  out.Put(x10c_minDoubleJumpTime);\n  /* x110_allowedLedgeTime */\n  out.Put(x110_allowedLedgeTime);\n  /* x114_doubleJumpImpulse */\n  out.Put(x114_doubleJumpImpulse);\n  /* x118_backwardsForceMultiplier */\n  out.Put(x118_backwardsForceMultiplier);\n  /* x11c_bombJumpRadius */\n  out.Put(x11c_bombJumpRadius);\n  /* x120_bombJumpHeight */\n  out.Put(x120_bombJumpHeight);\n  /* x124_eyeOffset */\n  out.Put(x124_eyeOffset);\n  /* x128_turnSpeedMultiplier */\n  out.Put(x128_turnSpeedMultiplier);\n  /* x12c_freeLookTurnSpeedMultiplier */\n  out.Put(x12c_freeLookTurnSpeedMultiplier);\n  /* x130_horizontalFreeLookAngleVel */\n  out.Put(x130_horizontalFreeLookAngleVel);\n  /* x134_verticalFreeLookAngleVel */\n  out.Put(x134_verticalFreeLookAngleVel);\n  /* x138_freeLookSpeed */\n  out.Put(x138_freeLookSpeed);\n  /* x13c_freeLookSnapSpeed */\n  out.Put(x13c_freeLookSnapSpeed);\n  /* x140_ */\n  out.Put(x140_);\n  /* x144_freeLookCenteredThresholdAngle */\n  out.Put(x144_freeLookCenteredThresholdAngle);\n  /* x148_freeLookCenteredTime */\n  out.Put(x148_freeLookCenteredTime);\n  /* x14c_freeLookDampenFactor */\n  out.Put(x14c_freeLookDampenFactor);\n  /* x150_leftDiv */\n  out.Put(x150_leftDiv);\n  /* x154_rightDiv */\n  out.Put(x154_rightDiv);\n  /* x228_24_freelookTurnsPlayer */\n  out.Put(x228_24_freelookTurnsPlayer);\n  /* x228_25_ */\n  out.Put(x228_25_);\n  /* x228_26_ */\n  out.Put(x228_26_);\n  /* x228_27_moveDuringFreeLook */\n  out.Put(x228_27_moveDuringFreeLook);\n  /* x228_28_holdButtonsForFreeLook */\n  out.Put(x228_28_holdButtonsForFreeLook);\n  /* x228_29_twoButtonsForFreeLook */\n  out.Put(x228_29_twoButtonsForFreeLook);\n  /* x228_30_ */\n  out.Put(x228_30_);\n  /* x228_31_ */\n  out.Put(x228_31_);\n  /* x229_24_ */\n  out.Put(x229_24_);\n  /* x229_25_aimWhenOrbitingPoint */\n  out.Put(x229_25_aimWhenOrbitingPoint);\n  /* x229_26_stayInFreeLookWhileFiring */\n  out.Put(x229_26_stayInFreeLookWhileFiring);\n  /* x229_27_ */\n  out.Put(x229_27_);\n  /* x229_28_ */\n  out.Put(x229_28_);\n  /* x229_29_orbitFixedOffset */\n  out.Put(x229_29_orbitFixedOffset);\n  /* x229_30_gunButtonTogglesHolster */\n  out.Put(x229_30_gunButtonTogglesHolster);\n  /* x229_31_gunNotFiringHolstersGun */\n  out.Put(x229_31_gunNotFiringHolstersGun);\n  /* x22a_24_fallingDoubleJump */\n  out.Put(x22a_24_fallingDoubleJump);\n  /* x22a_25_impulseDoubleJump */\n  out.Put(x22a_25_impulseDoubleJump);\n  /* x22a_26_firingCancelsCameraPitch */\n  out.Put(x22a_26_firingCancelsCameraPitch);\n  /* x22a_27_assistedAimingIgnoreHorizontal */\n  out.Put(x22a_27_assistedAimingIgnoreHorizontal);\n  /* x22a_28_assistedAimingIgnoreVertical */\n  out.Put(x22a_28_assistedAimingIgnoreVertical);\n  /* x22c_ */\n  out.Put(x22c_);\n  /* x230_ */\n  out.Put(x230_);\n  /* x234_aimMaxDistance */\n  out.Put(x234_aimMaxDistance);\n  /* x238_ */\n  out.Put(x238_);\n  /* x23c_ */\n  out.Put(x23c_);\n  /* x240_ */\n  out.Put(x240_);\n  /* x244_ */\n  out.Put(x244_);\n  /* x248_ */\n  out.Put(x248_);\n  /* x24c_aimThresholdDistance */\n  out.Put(x24c_aimThresholdDistance);\n  /* x250_ */\n  out.Put(x250_);\n  /* x254_ */\n  out.Put(x254_);\n  /* x258_aimBoxWidth */\n  out.Put(x258_aimBoxWidth);\n  /* x25c_aimBoxHeight */\n  out.Put(x25c_aimBoxHeight);\n  /* x260_aimTargetTimer */\n  out.Put(x260_aimTargetTimer);\n  /* x264_aimAssistHorizontalAngle */\n  out.Put(x264_aimAssistHorizontalAngle);\n  /* x268_aimAssistVerticalAngle */\n  out.Put(x268_aimAssistVerticalAngle);\n  /* x158_orbitMinDistance[0] */\n  out.Put(x158_orbitMinDistance[0]);\n  /* x164_orbitNormalDistance[0] */\n  out.Put(x164_orbitNormalDistance[0]);\n  /* x170_orbitMaxDistance[0] */\n  out.Put(x170_orbitMaxDistance[0]);\n  /* x158_orbitMinDistance[1] */\n  out.Put(x158_orbitMinDistance[1]);\n  /* x164_orbitNormalDistance[1] */\n  out.Put(x164_orbitNormalDistance[1]);\n  /* x170_orbitMaxDistance[1] */\n  out.Put(x170_orbitMaxDistance[1]);\n  /* x158_orbitMinDistance[2] */\n  out.Put(x158_orbitMinDistance[2]);\n  /* x164_orbitNormalDistance[2] */\n  out.Put(x164_orbitNormalDistance[2]);\n  /* x170_orbitMaxDistance[2] */\n  out.Put(x170_orbitMaxDistance[2]);\n  /* x17c_ */\n  out.Put(x17c_);\n  /* x180_orbitModeTimer */\n  out.Put(x180_orbitModeTimer);\n  /* x184_orbitCameraSpeed */\n  out.Put(x184_orbitCameraSpeed);\n  /* x188_orbitUpperAngle */\n  out.Put(x188_orbitUpperAngle);\n  /* x18c_orbitLowerAngle */\n  out.Put(x18c_orbitLowerAngle);\n  /* x190_orbitHorizAngle */\n  out.Put(x190_orbitHorizAngle);\n  /* x194_ */\n  out.Put(x194_);\n  /* x198_ */\n  out.Put(x198_);\n  /* x19c_orbitMaxTargetDistance */\n  out.Put(x19c_orbitMaxTargetDistance);\n  /* x1a0_orbitMaxLockDistance */\n  out.Put(x1a0_orbitMaxLockDistance);\n  /* x1a4_orbitDistanceThreshold */\n  out.Put(x1a4_orbitDistanceThreshold);\n  /* x1a8_orbitScreenBoxHalfExtentX[0] */\n  out.Put(x1a8_orbitScreenBoxHalfExtentX[0]);\n  /* x1b0_orbitScreenBoxHalfExtentY[0] */\n  out.Put(x1b0_orbitScreenBoxHalfExtentY[0]);\n  /* x1b8_orbitScreenBoxCenterX[0] */\n  out.Put(x1b8_orbitScreenBoxCenterX[0]);\n  /* x1c0_orbitScreenBoxCenterY[0] */\n  out.Put(x1c0_orbitScreenBoxCenterY[0]);\n  /* x1c8_orbitZoneIdealX[0] */\n  out.Put(x1c8_orbitZoneIdealX[0]);\n  /* x1d0_orbitZoneIdealY[0] */\n  out.Put(x1d0_orbitZoneIdealY[0]);\n  /* x1a8_orbitScreenBoxHalfExtentX[1] */\n  out.Put(x1a8_orbitScreenBoxHalfExtentX[1]);\n  /* x1b0_orbitScreenBoxHalfExtentY[1] */\n  out.Put(x1b0_orbitScreenBoxHalfExtentY[1]);\n  /* x1b8_orbitScreenBoxCenterX[1] */\n  out.Put(x1b8_orbitScreenBoxCenterX[1]);\n  /* x1c0_orbitScreenBoxCenterY[1] */\n  out.Put(x1c0_orbitScreenBoxCenterY[1]);\n  /* x1c8_orbitZoneIdealX[1] */\n  out.Put(x1c8_orbitZoneIdealX[1]);\n  /* x1d0_orbitZoneIdealY[1] */\n  out.Put(x1d0_orbitZoneIdealY[1]);\n  /* x1d8_orbitNearX */\n  out.Put(x1d8_orbitNearX);\n  /* x1dc_orbitNearZ */\n  out.Put(x1dc_orbitNearZ);\n  /* x1e0_ */\n  out.Put(x1e0_);\n  /* x1e4_ */\n  out.Put(x1e4_);\n  /* x1e8_orbitFixedOffsetZDiff */\n  out.Put(x1e8_orbitFixedOffsetZDiff);\n  /* x1ec_orbitZRange */\n  out.Put(x1ec_orbitZRange);\n  /* x1f0_ */\n  out.Put(x1f0_);\n  /* x1f4_ */\n  out.Put(x1f4_);\n  /* x1f8_ */\n  out.Put(x1f8_);\n  /* x1fc_orbitPreventionTime */\n  out.Put(x1fc_orbitPreventionTime);\n  /* x200_24_dashEnabled */\n  out.Put(x200_24_dashEnabled);\n  /* x200_25_dashOnButtonRelease */\n  out.Put(x200_25_dashOnButtonRelease);\n  /* x204_dashButtonHoldCancelTime */\n  out.Put(x204_dashButtonHoldCancelTime);\n  /* x208_dashStrafeInputThreshold */\n  out.Put(x208_dashStrafeInputThreshold);\n  /* x20c_sidewaysDoubleJumpImpulse */\n  out.Put(x20c_sidewaysDoubleJumpImpulse);\n  /* x210_sidewaysVerticalDoubleJumpAccel */\n  out.Put(x210_sidewaysVerticalDoubleJumpAccel);\n  /* x214_sidewaysHorizontalDoubleJumpAccel */\n  out.Put(x214_sidewaysHorizontalDoubleJumpAccel);\n  /* x218_scanningRange */\n  out.Put(x218_scanningRange);\n  /* x21c_24_scanRetention */\n  out.Put(x21c_24_scanRetention);\n  /* x21c_25_scanFreezesGame */\n  out.Put(x21c_25_scanFreezesGame);\n  /* x21c_26_orbitWhileScanning */\n  out.Put(x21c_26_orbitWhileScanning);\n  /* x220_scanMaxTargetDistance */\n  out.Put(x220_scanMaxTargetDistance);\n  /* x224_scanMaxLockDistance */\n  out.Put(x224_scanMaxLockDistance);\n  /* x2a0_orbitDistanceMax */\n  out.Put(x2a0_orbitDistanceMax);\n  /* x2a4_grappleSwingLength */\n  out.Put(x2a4_grappleSwingLength);\n  /* x2a8_grappleSwingPeriod */\n  out.Put(x2a8_grappleSwingPeriod);\n  /* x2ac_grapplePullSpeedMin */\n  out.Put(x2ac_grapplePullSpeedMin);\n  /* x2b0_grappleCameraSpeed */\n  out.Put(x2b0_grappleCameraSpeed);\n  /* x2b4_maxGrappleLockedTurnAlignDistance */\n  out.Put(x2b4_maxGrappleLockedTurnAlignDistance);\n  /* x2b8_grapplePullSpeedProportion */\n  out.Put(x2b8_grapplePullSpeedProportion);\n  /* x2bc_grapplePullSpeedMax */\n  out.Put(x2bc_grapplePullSpeedMax);\n  /* x2c0_grappleLookCenterSpeed */\n  out.Put(x2c0_grappleLookCenterSpeed);\n  /* x2c4_maxGrappleTurnSpeed */\n  out.Put(x2c4_maxGrappleTurnSpeed);\n  /* x2c8_grappleJumpForce */\n  out.Put(x2c8_grappleJumpForce);\n  /* x2cc_grappleReleaseTime */\n  out.Put(x2cc_grappleReleaseTime);\n  /* x2d0_grappleJumpMode */\n  out.Put(x2d0_grappleJumpMode);\n  /* x2d4_orbitReleaseBreaksGrapple */\n  out.Put(x2d4_orbitReleaseBreaksGrapple);\n  /* x2d5_invertGrappleTurn */\n  out.Put(x2d5_invertGrappleTurn);\n  /* x2d8_grappleBeamSpeed */\n  out.Put(x2d8_grappleBeamSpeed);\n  /* x2dc_grappleBeamXWaveAmplitude */\n  out.Put(x2dc_grappleBeamXWaveAmplitude);\n  /* x2e0_grappleBeamZWaveAmplitude */\n  out.Put(x2e0_grappleBeamZWaveAmplitude);\n  /* x2e4_grappleBeamAnglePhaseDelta */\n  out.Put(x2e4_grappleBeamAnglePhaseDelta);\n  /* x26c_playerHeight */\n  out.Put(x26c_playerHeight);\n  /* x270_playerXYHalfExtent */\n  out.Put(x270_playerXYHalfExtent);\n  /* x274_stepUpHeight */\n  out.Put(x274_stepUpHeight);\n  /* x278_stepDownHeight */\n  out.Put(x278_stepDownHeight);\n  /* x27c_playerBallHalfExtent */\n  out.Put(x27c_playerBallHalfExtent);\n  /* x280_ */\n  out.Put(x280_firstPersonCameraSpeed);\n  /* x284_ */\n  out.Put(x284_);\n  /* x288_jumpCameraPitchDownStart */\n  out.Put(x288_jumpCameraPitchDownStart);\n  /* x28c_jumpCameraPitchDownFull */\n  out.Put(x28c_jumpCameraPitchDownFull);\n  /* x290_jumpCameraPitchDownAngle */\n  out.Put(x290_jumpCameraPitchDownAngle);\n  /* x294_fallCameraPitchDownStart */\n  out.Put(x294_fallCameraPitchDownStart);\n  /* x298_fallCameraPitchDownFull */\n  out.Put(x298_fallCameraPitchDownFull);\n  /* x29c_fallCameraPitchDownAngle */\n  out.Put(x29c_fallCameraPitchDownAngle);\n  /* x2e8_ */\n  out.Put(x2e8_);\n  /* x2ec_ */\n  out.Put(x2ec_);\n  /* x2f0_ */\n  out.Put(x2f0_);\n  /* x2f4_ */\n  out.Put(x2f4_);\n  /* x2f8_frozenTimeout */\n  out.Put(x2f8_frozenTimeout);\n  /* x2fc_iceBreakJumpCount */\n  out.Put(x2fc_iceBreakJumpCount);\n  /* x300_variaDamageReduction */\n  out.Put(x300_variaDamageReduction);\n  /* x304_gravityDamageReduction */\n  out.Put(x304_gravityDamageReduction);\n  /* x308_phazonDamageReduction */\n  out.Put(x308_phazonDamageReduction);\n}\n\nvoid CTweakPlayer::FixupValues() {\n  x130_horizontalFreeLookAngleVel = zeus::degToRad(x130_horizontalFreeLookAngleVel);\n  x134_verticalFreeLookAngleVel = zeus::degToRad(x134_verticalFreeLookAngleVel);\n  x138_freeLookSpeed = zeus::degToRad(x138_freeLookSpeed);\n  x13c_freeLookSnapSpeed = zeus::degToRad(x13c_freeLookSnapSpeed);\n  x140_ = zeus::degToRad(x140_);\n  x144_freeLookCenteredThresholdAngle = zeus::degToRad(x144_freeLookCenteredThresholdAngle);\n  x23c_ = zeus::degToRad(x23c_);\n  x240_ = zeus::degToRad(x240_);\n  x244_ = zeus::degToRad(x244_);\n  x248_ = zeus::degToRad(x248_);\n  x250_ = zeus::degToRad(x250_);\n  x264_aimAssistHorizontalAngle = zeus::degToRad(x264_aimAssistHorizontalAngle);\n  x268_aimAssistVerticalAngle = zeus::degToRad(x268_aimAssistVerticalAngle);\n  x17c_ = zeus::degToRad(x17c_);\n  x184_orbitCameraSpeed = zeus::degToRad(x184_orbitCameraSpeed);\n  x188_orbitUpperAngle = zeus::degToRad(x188_orbitUpperAngle);\n  x18c_orbitLowerAngle = zeus::degToRad(x18c_orbitLowerAngle);\n  x190_orbitHorizAngle = zeus::degToRad(x190_orbitHorizAngle);\n  x194_ = zeus::degToRad(x194_);\n  x198_ = zeus::degToRad(x198_);\n  x1f0_ = zeus::degToRad(x1f0_);\n  x1f4_ = zeus::degToRad(x1f4_);\n  x2b0_grappleCameraSpeed = zeus::degToRad(x2b0_grappleCameraSpeed);\n  x2c0_grappleLookCenterSpeed = zeus::degToRad(x2c0_grappleLookCenterSpeed);\n  x280_firstPersonCameraSpeed = zeus::degToRad(x280_firstPersonCameraSpeed);\n  x284_ = zeus::degToRad(x284_);\n  x290_jumpCameraPitchDownAngle = zeus::degToRad(x290_jumpCameraPitchDownAngle);\n  x29c_fallCameraPitchDownAngle = zeus::degToRad(x29c_fallCameraPitchDownAngle);\n}\n\nvoid CTweakPlayer::_tweakListener(CVar* cv) {\n  UPDATE_CVAR(MaxTranslationAccelerationNormal, cv, x4_maxTranslationalAcceleration[0]);\n  UPDATE_CVAR(MaxTranslationAccelerationAir, cv, x4_maxTranslationalAcceleration[1]);\n  UPDATE_CVAR(MaxTranslationAccelerationIce, cv, x4_maxTranslationalAcceleration[2]);\n  UPDATE_CVAR(MaxTranslationAccelerationOrganic, cv, x4_maxTranslationalAcceleration[3]);\n  UPDATE_CVAR(MaxTranslationAccelerationWater, cv, x4_maxTranslationalAcceleration[4]);\n  UPDATE_CVAR(MaxTranslationAccelerationLava, cv, x4_maxTranslationalAcceleration[5]);\n  UPDATE_CVAR(MaxTranslationAccelerationPhazon, cv, x4_maxTranslationalAcceleration[6]);\n  UPDATE_CVAR(MaxRotationAccelerationShrubbery, cv, x24_maxRotationalAcceleration[7]);\n  UPDATE_CVAR(MaxRotationAccelerationNormal, cv, x24_maxRotationalAcceleration[0]);\n  UPDATE_CVAR(MaxRotationAccelerationAir, cv, x24_maxRotationalAcceleration[1]);\n  UPDATE_CVAR(MaxRotationAccelerationIce, cv, x24_maxRotationalAcceleration[2]);\n  UPDATE_CVAR(MaxRotationAccelerationOrganic, cv, x24_maxRotationalAcceleration[3]);\n  UPDATE_CVAR(MaxRotationAccelerationWater, cv, x24_maxRotationalAcceleration[4]);\n  UPDATE_CVAR(MaxRotationAccelerationLava, cv, x24_maxRotationalAcceleration[5]);\n  UPDATE_CVAR(MaxRotationAccelerationPhazon, cv, x24_maxRotationalAcceleration[6]);\n  UPDATE_CVAR(MaxRotationAccelerationShrubbery, cv, x24_maxRotationalAcceleration[7]);\n  UPDATE_CVAR(TranslationFrictionNormal, cv, x44_translationFriction[0]);\n  UPDATE_CVAR(TranslationFrictionAir, cv, x44_translationFriction[1]);\n  UPDATE_CVAR(TranslationFrictionIce, cv, x44_translationFriction[2]);\n  UPDATE_CVAR(TranslationFrictionOrganic, cv, x44_translationFriction[3]);\n  UPDATE_CVAR(TranslationFrictionWater, cv, x44_translationFriction[4]);\n  UPDATE_CVAR(TranslationFrictionLava, cv, x44_translationFriction[5]);\n  UPDATE_CVAR(TranslationFrictionPhazon, cv, x44_translationFriction[6]);\n  UPDATE_CVAR(TranslationFrictionShrubbery, cv, x44_translationFriction[7]);\n  UPDATE_CVAR(RotationFrictionNormal, cv, x44_translationFriction[2]);\n  UPDATE_CVAR(RotationFrictionIce, cv, x44_translationFriction[2]);\n  UPDATE_CVAR(RotationFrictionOrganic, cv, x44_translationFriction[3]);\n  UPDATE_CVAR(RotationFrictionWater, cv, x44_translationFriction[4]);\n  UPDATE_CVAR(RotationFrictionLava, cv, x44_translationFriction[5]);\n  UPDATE_CVAR(RotationFrictionPhazon, cv, x44_translationFriction[6]);\n  UPDATE_CVAR(RotationFrictionShrubbery, cv, x44_translationFriction[7]);\n  UPDATE_CVAR(RotationMaxSpeedNormal, cv, x84_rotationMaxSpeed[2]);\n  UPDATE_CVAR(RotationMaxSpeedIce, cv, x84_rotationMaxSpeed[2]);\n  UPDATE_CVAR(RotationMaxSpeedOrganic, cv, x84_rotationMaxSpeed[3]);\n  UPDATE_CVAR(RotationMaxSpeedWater, cv, x84_rotationMaxSpeed[4]);\n  UPDATE_CVAR(RotationMaxSpeedLava, cv, x84_rotationMaxSpeed[5]);\n  UPDATE_CVAR(RotationMaxSpeedPhazon, cv, x84_rotationMaxSpeed[6]);\n  UPDATE_CVAR(RotationMaxSpeedShrubbery, cv, x84_rotationMaxSpeed[7]);\n  UPDATE_CVAR(TranslationMaxSpeedNormal, cv, xa4_translationMaxSpeed[2]);\n  UPDATE_CVAR(TranslationMaxSpeedIce, cv, xa4_translationMaxSpeed[2]);\n  UPDATE_CVAR(TranslationMaxSpeedOrganic, cv, xa4_translationMaxSpeed[3]);\n  UPDATE_CVAR(TranslationMaxSpeedWater, cv, xa4_translationMaxSpeed[4]);\n  UPDATE_CVAR(TranslationMaxSpeedLava, cv, xa4_translationMaxSpeed[5]);\n  UPDATE_CVAR(TranslationMaxSpeedPhazon, cv, xa4_translationMaxSpeed[6]);\n  UPDATE_CVAR(TranslationMaxSpeedShrubbery, cv, xa4_translationMaxSpeed[7]);\n  UPDATE_CVAR(NormalGravityAcceleration, cv, xc4_normalGravAccel);\n  UPDATE_CVAR(FluidGravityAcceleration, cv, xc8_fluidGravAccel);\n  UPDATE_CVAR(VerticalJumpAcceleration, cv, xcc_verticalJumpAccel);\n  UPDATE_CVAR(HorizontalJumpAcceleration, cv, xd0_horizontalJumpAccel);\n  UPDATE_CVAR(VerticalDoubleJumpAcceleration, cv, xd4_verticalDoubleJumpAccel);\n  UPDATE_CVAR(HorizontalDoubleJumpAcceleration, cv, xd8_horizontalDoubleJumpAccel);\n  UPDATE_CVAR(WaterJumpFactor, cv, xdc_waterJumpFactor);\n  UPDATE_CVAR(WaterBallJumpFactor, cv, xe0_waterBallJumpFactor);\n  UPDATE_CVAR(LavaJumpFactor, cv, xe4_lavaJumpFactor);\n  UPDATE_CVAR(LavaBallJumpFactor, cv, xe8_lavaBallJumpFactor);\n  UPDATE_CVAR(PhazonJumpFactor, cv, xec_phazonJumpFactor);\n  UPDATE_CVAR(PhazonBallJumpFactor, cv, xf0_phazonBallJumpFactor);\n  UPDATE_CVAR(AllowedJumpTime, cv, xf4_allowedJumpTime);\n  UPDATE_CVAR(AllowedDoubleJumpTime, cv, xf8_allowedDoubleJumpTime);\n  UPDATE_CVAR(MinDoubleJumpWindow, cv, xfc_minDoubleJumpWindow);\n  UPDATE_CVAR(MaxDoubleJumpWindow, cv, x100_maxDoubleJumpWindow);\n  // UPDATE_CVAR(); // x104_\n  UPDATE_CVAR(MinJumpTime, cv, x108_minJumpTime);\n  UPDATE_CVAR(MinDoubleJumpTime, cv, x10c_minDoubleJumpTime);\n  UPDATE_CVAR(AllowedLedgeTime, cv, x110_allowedLedgeTime);\n  UPDATE_CVAR(DoubleJumpImpulse, cv, x114_doubleJumpImpulse);\n  UPDATE_CVAR(BackwardsForceMultiplier, cv, x118_backwardsForceMultiplier);\n  UPDATE_CVAR(BombJumpRadius, cv, x11c_bombJumpRadius);\n  UPDATE_CVAR(BombJumpHeight, cv, x120_bombJumpHeight);\n  UPDATE_CVAR(EyeOffset, cv, x124_eyeOffset);\n  UPDATE_CVAR(TurnSpeedMultiplier, cv, x128_turnSpeedMultiplier);\n  UPDATE_CVAR(FreeLookTurnSpeedMultiplier, cv, x12c_freeLookTurnSpeedMultiplier);\n  UPDATE_CVAR(HorizontalFreeLookAngleVelocity, cv, x130_horizontalFreeLookAngleVel);\n  UPDATE_CVAR(VerticalFreeLookAngleVelocity, cv, x134_verticalFreeLookAngleVel);\n  UPDATE_CVAR(FreeLookSpeed, cv, x138_freeLookSpeed);\n  UPDATE_CVAR(FreeLookSnapSpeed, cv, x13c_freeLookSnapSpeed);\n  // UPDATE_CVAR(); // x140_\n  UPDATE_CVAR(FreeLookCenteredThresholdAngle, cv, x144_freeLookCenteredThresholdAngle);\n  UPDATE_CVAR(FreeLookCenteredTime, cv, x148_freeLookCenteredTime);\n  UPDATE_CVAR(FreeLookDampenFactor, cv, x14c_freeLookDampenFactor);\n  UPDATE_CVAR(LeftDivisor, cv, x150_leftDiv);\n  UPDATE_CVAR(RightDivisor, cv, x154_rightDiv);\n  UPDATE_CVAR(OrbitMinDistanceClose, cv, x158_orbitMinDistance[0]);\n  UPDATE_CVAR(OrbitMinDistanceFar, cv, x158_orbitMinDistance[1]);\n  UPDATE_CVAR(OrbitMinDistanceDefault, cv, x158_orbitMinDistance[2]);\n  UPDATE_CVAR(OrbitNormalDistanceClose, cv, x164_orbitNormalDistance[0]);\n  UPDATE_CVAR(OrbitNormalDistanceFar, cv, x164_orbitNormalDistance[1]);\n  UPDATE_CVAR(OrbitNormalDistanceDefault, cv, x164_orbitNormalDistance[2]);\n  UPDATE_CVAR(OrbitMaxDistanceClose, cv, x170_orbitMaxDistance[0]);\n  UPDATE_CVAR(OrbitMaxDistanceFar, cv, x170_orbitMaxDistance[1]);\n  UPDATE_CVAR(OrbitMaxDistanceDefault, cv, x170_orbitMaxDistance[2]);\n  // UPDATE_CVAR(); // x17c_\n  UPDATE_CVAR(OrbitmodeTimer, cv, x180_orbitModeTimer);\n  UPDATE_CVAR(OrbitCameraSpeed, cv, x184_orbitCameraSpeed);\n  UPDATE_CVAR(OrbitUpperAngle, cv, x188_orbitUpperAngle);\n  UPDATE_CVAR(OrbitLowerAngle, cv, x18c_orbitLowerAngle);\n  UPDATE_CVAR(OrbitHorizontalAngle, cv, x190_orbitHorizAngle);\n  // UPDATE_CVAR(); // x194_\n  // UPDATE_CVAR(); // x198_\n  UPDATE_CVAR(OrbitMaxTargetDistance, cv, x19c_orbitMaxTargetDistance);\n  UPDATE_CVAR(OrbitMaxLockDistance, cv, x1a0_orbitMaxLockDistance);\n  UPDATE_CVAR(OrbitDistanceThreshold, cv, x1a4_orbitDistanceThreshold);\n  UPDATE_CVAR(OrbitScreenTargetingBoxHalfExtentX, cv, x1a8_orbitScreenBoxHalfExtentX[0]);\n  UPDATE_CVAR(OrbitScreenScanBoxHalfExtentX, cv, x1a8_orbitScreenBoxHalfExtentX[1]);\n  UPDATE_CVAR(OrbitScreenTargetingBoxHalfExtentY, cv, x1b0_orbitScreenBoxHalfExtentY[0]);\n  UPDATE_CVAR(OrbitScreenScanBoxHalfExtentY, cv, x1b0_orbitScreenBoxHalfExtentY[1]);\n  UPDATE_CVAR(OrbitScreenTargetingBoxCenterX, cv, x1b8_orbitScreenBoxCenterX[0]);\n  UPDATE_CVAR(OrbitScreenScanBoxCenterX, cv, x1b8_orbitScreenBoxCenterX[1]);\n  UPDATE_CVAR(OrbitScreenTargetingBoxCenterY, cv, x1c0_orbitScreenBoxCenterY[0]);\n  UPDATE_CVAR(OrbitScreenScanBoxCenterY, cv, x1c0_orbitScreenBoxCenterY[1]);\n  UPDATE_CVAR(OrbitZoneTargetingIdealX, cv, x1c8_orbitZoneIdealX[0]);\n  UPDATE_CVAR(OrbitZoneScanIdealX, cv, x1c8_orbitZoneIdealX[1]);\n  UPDATE_CVAR(OrbitZoneTargetingIdealY, cv, x1d0_orbitZoneIdealY[0]);\n  UPDATE_CVAR(OrbitZoneScanIdealY, cv, x1d0_orbitZoneIdealY[1]);\n  UPDATE_CVAR(OrbitNearX, cv, x1d8_orbitNearX);\n  UPDATE_CVAR(OrbitNearZ, cv, x1dc_orbitNearZ);\n  // UPDATE_CVAR(); // x1e0_\n  // UPDATE_CVAR(); // x1e4_\n  UPDATE_CVAR(OrbitFixedOffsetZDiff, cv, x1e8_orbitFixedOffsetZDiff);\n  UPDATE_CVAR(OrbitZRange, cv, x1ec_orbitZRange);\n  // UPDATE_CVAR(); // x1f0_\n  // UPDATE_CVAR(); // x1f4_\n  // UPDATE_CVAR(); // x1f8_\n  UPDATE_CVAR(OrbitPreventionTime, cv, x1fc_orbitPreventionTime);\n  UPDATE_CVAR_BITFIELD(DashEnabled, cv, x200_24_dashEnabled);\n  UPDATE_CVAR_BITFIELD(DashOnButtonRelease, cv, x200_25_dashOnButtonRelease);\n  UPDATE_CVAR(DashButtonHoldCancelTime, cv, x204_dashButtonHoldCancelTime);\n  UPDATE_CVAR(DashStrafeInputThreshold, cv, x208_dashStrafeInputThreshold);\n  UPDATE_CVAR(SidewaysDoubleJumpImpulse, cv, x20c_sidewaysDoubleJumpImpulse);\n  UPDATE_CVAR(SidewaysVerticalDoubleJumpAccel, cv, x210_sidewaysVerticalDoubleJumpAccel);\n  UPDATE_CVAR(SidewaysHorizontalDoubleJumpAccel, cv, x214_sidewaysHorizontalDoubleJumpAccel);\n  UPDATE_CVAR(ScanningRange, cv, x218_scanningRange);\n  UPDATE_CVAR_BITFIELD(ScanRetention, cv, x21c_24_scanRetention);\n  UPDATE_CVAR_BITFIELD(ScanFreezesGame, cv, x21c_25_scanFreezesGame);\n  UPDATE_CVAR_BITFIELD(OrbitWhileScanning, cv, x21c_26_orbitWhileScanning);\n  UPDATE_CVAR(ScanMaxTargetDistance, cv, x220_scanMaxTargetDistance);\n  UPDATE_CVAR(ScanMaxLockDistance, cv, x224_scanMaxLockDistance);\n  UPDATE_CVAR_BITFIELD(FreeLookTurnsPlayer, cv, x228_24_freelookTurnsPlayer);\n  // UPDATE_CVAR_BITFIELD(); // x228_25_\n  // UPDATE_CVAR_BITFIELD(); // x228_26_\n  UPDATE_CVAR_BITFIELD(MoveDuringFreelook, cv, x228_27_moveDuringFreeLook);\n  UPDATE_CVAR_BITFIELD(HoldButtonsForFreeLook, cv, x228_28_holdButtonsForFreeLook);\n  // UPDATE_CVAR_BITFIELD(); // x228_30_\n  // UPDATE_CVAR_BITFIELD(); // x228_31_\n  // UPDATE_CVAR_BITFIELD(); // x229_24_\n  UPDATE_CVAR_BITFIELD(AimWhenOrbitingPoint, cv, x229_25_aimWhenOrbitingPoint);\n  UPDATE_CVAR_BITFIELD(StayInFreeLookWhileFiring, cv, x229_26_stayInFreeLookWhileFiring);\n  // UPDATE_CVAR_BITFIELD(); // x229_27_\n  // UPDATE_CVAR_BITFIELD(); // x229_28_\n  UPDATE_CVAR_BITFIELD(OrbitFixedOffset, cv, x229_29_orbitFixedOffset);\n  UPDATE_CVAR_BITFIELD(GunButtonTogglesHolster, cv, x229_30_gunButtonTogglesHolster);\n  UPDATE_CVAR_BITFIELD(GunNotFiringHolstersGun, cv, x229_31_gunNotFiringHolstersGun);\n  UPDATE_CVAR_BITFIELD(FallingDoubleJump, cv, x22a_24_fallingDoubleJump);\n  UPDATE_CVAR_BITFIELD(ImpulseDoubleJump, cv, x22a_25_impulseDoubleJump);\n  UPDATE_CVAR_BITFIELD(FiringCancelsCameraPitch, cv, x22a_26_firingCancelsCameraPitch);\n  UPDATE_CVAR_BITFIELD(AssistedAimingIgnoreHorizontal, cv, x22a_27_assistedAimingIgnoreHorizontal);\n  UPDATE_CVAR_BITFIELD(AssistedAimingIgnoreVertical, cv, x22a_28_assistedAimingIgnoreVertical);\n  // UPDATE_CVAR(); // x22c\n  // UPDATE_CVAR(); // x230_\n  UPDATE_CVAR(AimMaxDistance, cv, x234_aimMaxDistance);\n  // UPDATE_CVAR(); // x238_\n  // UPDATE_CVAR(); // x23c_\n  // UPDATE_CVAR(); // x240_\n  // UPDATE_CVAR(); // x244_\n  // UPDATE_CVAR(); // x248_\n  UPDATE_CVAR(AimThresholdDistance, cv, x24c_aimThresholdDistance);\n  // UPDATE_CVAR(); // x250_\n  // UPDATE_CVAR(); // x254_\n  UPDATE_CVAR(AimBoxWidth, cv, x258_aimBoxWidth);\n  UPDATE_CVAR(AimBoxHeight, cv, x25c_aimBoxHeight);\n  UPDATE_CVAR(AimTargetTimer, cv, x260_aimTargetTimer);\n  UPDATE_CVAR(AimAssistHorizontalAngle, cv, x264_aimAssistHorizontalAngle);\n  UPDATE_CVAR(AimAssistVerticalAngle, cv, x268_aimAssistVerticalAngle);\n  UPDATE_CVAR(PlayerHeight, cv, x26c_playerHeight);\n  UPDATE_CVAR(PlayerXYHalfExtent, cv, x270_playerXYHalfExtent);\n  UPDATE_CVAR(StepUpHeight, cv, x274_stepUpHeight);\n  UPDATE_CVAR(StepDownHeight, cv, x278_stepDownHeight);\n  UPDATE_CVAR(PlayerBallHalfExtent, cv, x27c_playerBallHalfExtent);\n  UPDATE_CVAR(FirstPersonCameraSpeed, cv, x280_firstPersonCameraSpeed);\n  // UPDATE_CVAR(); // x284_\n  UPDATE_CVAR(JumpCameraPitchDownStart, cv, x288_jumpCameraPitchDownStart);\n  UPDATE_CVAR(JumpCameraPitchDownFull, cv, x28c_jumpCameraPitchDownFull);\n  UPDATE_CVAR(JumpCameraPitchDownAngle, cv, x290_jumpCameraPitchDownAngle);\n  UPDATE_CVAR(FallCameraPitchDownStart, cv, x294_fallCameraPitchDownStart);\n  UPDATE_CVAR(FallCameraPitchDownFull, cv, x298_fallCameraPitchDownFull);\n  UPDATE_CVAR(FallCameraPitchDownAngle, cv, x29c_fallCameraPitchDownAngle);\n  UPDATE_CVAR(OrbitDistanceMax, cv, x2a0_orbitDistanceMax);\n  UPDATE_CVAR(GrappleSwingLength, cv, x2a4_grappleSwingLength);\n  UPDATE_CVAR(GrappleSwingPeriod, cv, x2a8_grappleSwingPeriod);\n  UPDATE_CVAR(GrapplePullSpeedMin, cv, x2ac_grapplePullSpeedMin);\n  UPDATE_CVAR(GrappleCameraSpeed, cv, x2b0_grappleCameraSpeed);\n  UPDATE_CVAR(MaxGrappleLockedTurnAlignDistance, cv, x2b4_maxGrappleLockedTurnAlignDistance);\n  UPDATE_CVAR(GrapplePullSpeedProportion, cv, x2b8_grapplePullSpeedProportion);\n  UPDATE_CVAR(GrapplePullSpeedMax, cv, x2bc_grapplePullSpeedMax);\n  UPDATE_CVAR(GrappleLookCenterSpeed, cv, x2c0_grappleLookCenterSpeed);\n  UPDATE_CVAR(MaxGrappleTurnSpeed, cv, x2c4_maxGrappleTurnSpeed);\n  UPDATE_CVAR(GrappleJumpForce, cv, x2c8_grappleJumpForce);\n  UPDATE_CVAR(GrappleReleaseTime, cv, x2cc_grappleReleaseTime);\n  UPDATE_CVAR(GrappleJumpMode, cv, x2d0_grappleJumpMode);\n  UPDATE_CVAR(OrbitReleaseBreaksGrapple, cv, x2d4_orbitReleaseBreaksGrapple);\n  UPDATE_CVAR(InvertGrappleTurn, cv, x2d5_invertGrappleTurn);\n  UPDATE_CVAR(GrappleBeamSpeed, cv, x2d8_grappleBeamSpeed);\n  UPDATE_CVAR(GrappleBeamXWaveAmplitude, cv, x2dc_grappleBeamXWaveAmplitude);\n  UPDATE_CVAR(GrappleBeamZWaveAmplitude, cv, x2e0_grappleBeamZWaveAmplitude);\n  UPDATE_CVAR(GrappleBeamAnglePhaseDelta, cv, x2e4_grappleBeamAnglePhaseDelta);\n  // UPDATE_CVAR(); // x2e8_\n  // UPDATE_CVAR(); // x2ec_\n  // UPDATE_CVAR(); // x2f0_\n  // UPDATE_CVAR(); // x2f4_\n  UPDATE_CVAR(FrozenTimeout, cv, x2f8_frozenTimeout);\n  UPDATE_CVAR(IceBreakJumpCount, cv, x2fc_iceBreakJumpCount);\n  UPDATE_CVAR(VariaDamageReduction, cv, x300_variaDamageReduction);\n  UPDATE_CVAR(GravityDamageReduction, cv, x304_gravityDamageReduction);\n  UPDATE_CVAR(PhazonDamageReduction, cv, x308_phazonDamageReduction);\n}\n\nvoid CTweakPlayer::initCVars(CVarManager* mgr) {\n  CREATE_CVAR(MaxTranslationAccelerationNormal,\n              \"Max translation acceleration allowed to the player under normal circumstances\",\n              x4_maxTranslationalAcceleration[0], skDefaultFlags);\n  CREATE_CVAR(MaxTranslationAccelerationAir, \"Max translation acceleration allowed to the player while in air\",\n              x4_maxTranslationalAcceleration[1], skDefaultFlags);\n  CREATE_CVAR(MaxTranslationAccelerationIce, \"Max translation acceleration allowed to the player while on ice surfaces\",\n              x4_maxTranslationalAcceleration[2], skDefaultFlags);\n  CREATE_CVAR(MaxTranslationAccelerationOrganic,\n              \"Max translation acceleration allowed to the player while on organic surfaces\",\n              x4_maxTranslationalAcceleration[3], skDefaultFlags);\n  CREATE_CVAR(MaxTranslationAccelerationWater, \"Max translation acceleration allowed to the player while in water\",\n              x4_maxTranslationalAcceleration[4], skDefaultFlags);\n  CREATE_CVAR(MaxTranslationAccelerationLava, \"Max translation acceleration allowed to the player while in lava\",\n              x4_maxTranslationalAcceleration[5], skDefaultFlags);\n  CREATE_CVAR(MaxTranslationAccelerationPhazon, \"Max translation acceleration allowed to the player while in phazon\",\n              x4_maxTranslationalAcceleration[6], skDefaultFlags);\n  CREATE_CVAR(MaxTranslationAccelerationShrubbery,\n              \"Max translation acceleration allowed to the player while in shrubbery\",\n              x4_maxTranslationalAcceleration[7], skDefaultFlags);\n  CREATE_CVAR(MaxRotationAccelerationNormal,\n              \"Max rotation acceleration allowed to the player under normal circumstances\",\n              x24_maxRotationalAcceleration[0], skDefaultFlags);\n  CREATE_CVAR(MaxRotationAccelerationAir, \"Max rotation acceleration allowed to the player while in air\",\n              x24_maxRotationalAcceleration[1], skDefaultFlags);\n  CREATE_CVAR(MaxRotationAccelerationIce, \"Max rotation acceleration allowed to the player while on ice surfaces\",\n              x24_maxRotationalAcceleration[2], skDefaultFlags);\n  CREATE_CVAR(MaxRotationAccelerationOrganic,\n              \"Max rotation acceleration allowed to the player while on organic surfaces\",\n              x24_maxRotationalAcceleration[3], skDefaultFlags);\n  CREATE_CVAR(MaxRotationAccelerationWater, \"Max rotation acceleration allowed to the player while in water\",\n              x24_maxRotationalAcceleration[4], skDefaultFlags);\n  CREATE_CVAR(MaxRotationAccelerationLava, \"Max rotation acceleration allowed to the player while in lava\",\n              x24_maxRotationalAcceleration[5], skDefaultFlags);\n  CREATE_CVAR(MaxRotationAccelerationPhazon, \"Max rotation acceleration allowed to the player while in phazon\",\n              x24_maxRotationalAcceleration[6], skDefaultFlags);\n  CREATE_CVAR(MaxRotationAccelerationShrubbery, \"Max rotation acceleration allowed to the player while in shrubbery\",\n              x24_maxRotationalAcceleration[7], skDefaultFlags);\n  CREATE_CVAR(TranslationFrictionNormal, \"Translation friction allowed to the player under normal circumstances\",\n              x44_translationFriction[0], skDefaultFlags);\n  CREATE_CVAR(TranslationFrictionAir, \"Translation friction allowed to the player while in air\",\n              x44_translationFriction[1], skDefaultFlags);\n  CREATE_CVAR(TranslationFrictionIce, \"Translation friction allowed to the player while on ice surfaces\",\n              x44_translationFriction[2], skDefaultFlags);\n  CREATE_CVAR(TranslationFrictionOrganic, \"Translation friction allowed to the player while on organic surfaces\",\n              x44_translationFriction[3], skDefaultFlags);\n  CREATE_CVAR(TranslationFrictionWater, \"Translation friction allowed to the player while in water\",\n              x44_translationFriction[4], skDefaultFlags);\n  CREATE_CVAR(TranslationFrictionLava, \"Translation friction allowed to the player while in lava\",\n              x44_translationFriction[5], skDefaultFlags);\n  CREATE_CVAR(TranslationFrictionPhazon, \"Translation friction allowed to the player while in phazon\",\n              x44_translationFriction[6], skDefaultFlags);\n  CREATE_CVAR(TranslationFrictionShrubbery, \"Translation friction allowed to the player while in shrubbery\",\n              x44_translationFriction[7], skDefaultFlags);\n  CREATE_CVAR(RotationFrictionNormal, \"Rotation friction allowed to the player under normal circumstances\",\n              x44_translationFriction[0], skDefaultFlags);\n  CREATE_CVAR(RotationFrictionAir, \"Rotation friction allowed to the player while in air\", x44_translationFriction[1],\n              skDefaultFlags);\n  CREATE_CVAR(RotationFrictionIce, \"Rotation friction allowed to the player while on ice surfaces\",\n              x44_translationFriction[2], skDefaultFlags);\n  CREATE_CVAR(RotationFrictionOrganic, \"Rotation friction allowed to the player while on organic surfaces\",\n              x44_translationFriction[3], skDefaultFlags);\n  CREATE_CVAR(RotationFrictionWater, \"Rotation friction allowed to the player while in water\",\n              x44_translationFriction[4], skDefaultFlags);\n  CREATE_CVAR(RotationFrictionLava, \"Rotation friction allowed to the player while in lava\", x44_translationFriction[5],\n              skDefaultFlags);\n  CREATE_CVAR(RotationFrictionPhazon, \"Rotation friction allowed to the player while in phazon\",\n              x44_translationFriction[6], skDefaultFlags);\n  CREATE_CVAR(RotationFrictionShrubbery, \"Rotation friction allowed to the player while in shrubbery\",\n              x44_translationFriction[7], skDefaultFlags);\n  CREATE_CVAR(RotationMaxSpeedNormal, \"Rotation max speed allowed to the player under normal circumstances\",\n              x84_rotationMaxSpeed[0], skDefaultFlags);\n  CREATE_CVAR(RotationMaxSpeedAir, \"Rotation max speed allowed to the player while in air\", x84_rotationMaxSpeed[1],\n              skDefaultFlags);\n  CREATE_CVAR(RotationMaxSpeedIce, \"Rotation max speed allowed to the player while on ice surfaces\",\n              x84_rotationMaxSpeed[2], skDefaultFlags);\n  CREATE_CVAR(RotationMaxSpeedOrganic, \"Rotation max speed allowed to the player while on organic surfaces\",\n              x84_rotationMaxSpeed[3], skDefaultFlags);\n  CREATE_CVAR(RotationMaxSpeedWater, \"Rotation max speed allowed to the player while in water\", x84_rotationMaxSpeed[4],\n              skDefaultFlags);\n  CREATE_CVAR(RotationMaxSpeedLava, \"Rotation max speed allowed to the player while in lava\", x84_rotationMaxSpeed[5],\n              skDefaultFlags);\n  CREATE_CVAR(RotationMaxSpeedPhazon, \"Rotation max speed allowed to the player while in phazon\",\n              x84_rotationMaxSpeed[6], skDefaultFlags);\n  CREATE_CVAR(RotationMaxSpeedShrubbery, \"Rotation max speed allowed to the player while in shrubbery\",\n              x84_rotationMaxSpeed[7], skDefaultFlags);\n  CREATE_CVAR(TranslationMaxSpeedNormal, \"Translation max speed allowed to the player under normal circumstances\",\n              xa4_translationMaxSpeed[0], skDefaultFlags);\n  CREATE_CVAR(TranslationMaxSpeedNormal, \"Translation max speed allowed to the player under normal circumstances\",\n              xa4_translationMaxSpeed[1], skDefaultFlags);\n  CREATE_CVAR(TranslationMaxSpeedIce, \"Translation max speed allowed to the player while on ice surfaces\",\n              xa4_translationMaxSpeed[2], skDefaultFlags);\n  CREATE_CVAR(TranslationMaxSpeedOrganic, \"Translation max speed allowed to the player while on organic surfaces\",\n              xa4_translationMaxSpeed[3], skDefaultFlags);\n  CREATE_CVAR(TranslationMaxSpeedWater, \"Translation max speed allowed to the player while in water\",\n              xa4_translationMaxSpeed[4], skDefaultFlags);\n  CREATE_CVAR(TranslationMaxSpeedLava, \"Translation max speed allowed to the player while in lava\",\n              xa4_translationMaxSpeed[5], skDefaultFlags);\n  CREATE_CVAR(TranslationMaxSpeedPhazon, \"Translation max speed allowed to the player while in phazon\",\n              xa4_translationMaxSpeed[6], skDefaultFlags);\n  CREATE_CVAR(TranslationMaxSpeedShrubbery, \"Translation max speed allowed to the player while in shrubbery\",\n              xa4_translationMaxSpeed[7], skDefaultFlags);\n  CREATE_CVAR(NormalGravityAcceleration, \"Gravity applied to the player under normal circumstances\",\n              xc4_normalGravAccel, skDefaultFlags);\n  CREATE_CVAR(FluidGravityAcceleration, \"Gravity applied to the player while in water\", xc8_fluidGravAccel,\n              skDefaultFlags);\n  CREATE_CVAR(VerticalJumpAcceleration, \"Vertical acceleration applied while jumping\", xcc_verticalJumpAccel,\n              skDefaultFlags);\n  CREATE_CVAR(HorizontalJumpAcceleration, \"Horizontal acceleration while jumping\", xd0_horizontalJumpAccel,\n              skDefaultFlags);\n  CREATE_CVAR(VerticalDoubleJumpAcceleration, \"Vertical acceleration while double jumping\", xd4_verticalDoubleJumpAccel,\n              skDefaultFlags);\n  CREATE_CVAR(HorizontalDoubleJumpAcceleration, \"Horizontal acceleration while double jumping\",\n              xd8_horizontalDoubleJumpAccel, skDefaultFlags);\n  CREATE_CVAR(WaterJumpFactor, \"Jump Factor while in water\", xdc_waterJumpFactor, skDefaultFlags);\n  CREATE_CVAR(WaterBallJumpFactor, \"Jump Factor while morphed in water\", xe0_waterBallJumpFactor, skDefaultFlags);\n  CREATE_CVAR(LavaJumpFactor, \"Jump Factor while in lava\", xe4_lavaJumpFactor, skDefaultFlags);\n  CREATE_CVAR(LavaBallJumpFactor, \"Jump Factor while morphed in lava\", xe8_lavaBallJumpFactor, skDefaultFlags);\n  CREATE_CVAR(PhazonJumpFactor, \"Jump Factor while in phazon\", xec_phazonJumpFactor, skDefaultFlags);\n  CREATE_CVAR(PhazonBallJumpFactor, \"Jump Factor while morphed in phazon\", xf0_phazonBallJumpFactor, skDefaultFlags);\n  CREATE_CVAR(AllowedJumpTime, \"\", xf4_allowedJumpTime, skDefaultFlags);\n  CREATE_CVAR(AllowedDoubleJumpTime, \"\", xf8_allowedDoubleJumpTime, skDefaultFlags);\n  CREATE_CVAR(MinDoubleJumpWindow, \"\", xfc_minDoubleJumpWindow, skDefaultFlags);\n  CREATE_CVAR(MaxDoubleJumpWindow, \"\", x100_maxDoubleJumpWindow, skDefaultFlags);\n  // CREATE_CVAR(); // x104_\n  CREATE_CVAR(MinJumpTime, \"\", x108_minJumpTime, skDefaultFlags);\n  CREATE_CVAR(MinDoubleJumpTime, \"\", x10c_minDoubleJumpTime, skDefaultFlags);\n  CREATE_CVAR(AllowedLedgeTime, \"\", x110_allowedLedgeTime, skDefaultFlags);\n  CREATE_CVAR(DoubleJumpImpulse, \"\", x114_doubleJumpImpulse, skDefaultFlags);\n  CREATE_CVAR(BackwardsForceMultiplier, \"\", x118_backwardsForceMultiplier, skDefaultFlags);\n  CREATE_CVAR(BombJumpRadius, \"\", x11c_bombJumpRadius, skDefaultFlags);\n  CREATE_CVAR(BombJumpHeight, \"\", x120_bombJumpHeight, skDefaultFlags);\n  CREATE_CVAR(EyeOffset, \"\", x124_eyeOffset, skDefaultFlags);\n  CREATE_CVAR(TurnSpeedMultiplier, \"\", x128_turnSpeedMultiplier, skDefaultFlags);\n  CREATE_CVAR(FreeLookTurnSpeedMultiplier, \"\", x12c_freeLookTurnSpeedMultiplier, skDefaultFlags);\n  CREATE_CVAR(HorizontalFreeLookAngleVelocity, \"\", x130_horizontalFreeLookAngleVel, skDefaultFlags);\n  CREATE_CVAR(VerticalFreeLookAngleVelocity, \"\", x134_verticalFreeLookAngleVel, skDefaultFlags);\n  CREATE_CVAR(FreeLookSpeed, \"\", x138_freeLookSpeed, skDefaultFlags);\n  CREATE_CVAR(FreeLookSnapSpeed, \"\", x13c_freeLookSnapSpeed, skDefaultFlags);\n  // CREATE_CVAR(); // x140_\n  CREATE_CVAR(FreeLookCenteredThresholdAngle, \"\", x144_freeLookCenteredThresholdAngle, skDefaultFlags);\n  CREATE_CVAR(FreeLookCenteredTime, \"\", x148_freeLookCenteredTime, skDefaultFlags);\n  CREATE_CVAR(FreeLookDampenFactor, \"\", x14c_freeLookDampenFactor, skDefaultFlags);\n  CREATE_CVAR(LeftDivisor, \"\", x150_leftDiv, skDefaultFlags);\n  CREATE_CVAR(RightDivisor, \"\", x154_rightDiv, skDefaultFlags);\n  CREATE_CVAR(OrbitMinDistanceClose, \"\", x158_orbitMinDistance[0], skDefaultFlags);\n  CREATE_CVAR(OrbitMinDistanceFar, \"\", x158_orbitMinDistance[1], skDefaultFlags);\n  CREATE_CVAR(OrbitMinDistanceDefault, \"\", x158_orbitMinDistance[2], skDefaultFlags);\n  CREATE_CVAR(OrbitNormalDistanceClose, \"\", x164_orbitNormalDistance[0], skDefaultFlags);\n  CREATE_CVAR(OrbitNormalDistanceFar, \"\", x164_orbitNormalDistance[1], skDefaultFlags);\n  CREATE_CVAR(OrbitNormalDistanceDefault, \"\", x164_orbitNormalDistance[2], skDefaultFlags);\n  CREATE_CVAR(OrbitMaxDistanceClose, \"\", x170_orbitMaxDistance[0], skDefaultFlags);\n  CREATE_CVAR(OrbitMaxDistanceFar, \"\", x170_orbitMaxDistance[1], skDefaultFlags);\n  CREATE_CVAR(OrbitMaxDistanceDefault, \"\", x170_orbitMaxDistance[2], skDefaultFlags);\n  // CREATE_CVAR(); // x17c_\n  CREATE_CVAR(OrbitmodeTimer, \"\", x180_orbitModeTimer, skDefaultFlags);\n  CREATE_CVAR(OrbitCameraSpeed, \"\", x184_orbitCameraSpeed, skDefaultFlags);\n  CREATE_CVAR(OrbitUpperAngle, \"\", x184_orbitCameraSpeed, skDefaultFlags);\n  CREATE_CVAR(OrbitLowerAngle, \"\", x184_orbitCameraSpeed, skDefaultFlags);\n  CREATE_CVAR(OrbitHorizontalAngle, \"\", x184_orbitCameraSpeed, skDefaultFlags);\n  // CREATE_CVAR(); // x194_\n  // CREATE_CVAR(); // x198_\n  CREATE_CVAR(OrbitMaxTargetDistance, \"\", x19c_orbitMaxTargetDistance, skDefaultFlags);\n  CREATE_CVAR(OrbitMaxLockDistance, \"\", x1a0_orbitMaxLockDistance, skDefaultFlags);\n  CREATE_CVAR(OrbitDistanceThreshold, \"\", x1a4_orbitDistanceThreshold, skDefaultFlags);\n  CREATE_CVAR(OrbitScreenTargetingBoxHalfExtentX, \"\", x1a8_orbitScreenBoxHalfExtentX[0], skDefaultFlags);\n  CREATE_CVAR(OrbitScreenScanBoxHalfExtentX, \"\", x1a8_orbitScreenBoxHalfExtentX[1], skDefaultFlags);\n  CREATE_CVAR(OrbitScreenTargetingBoxHalfExtentY, \"\", x1b0_orbitScreenBoxHalfExtentY[0], skDefaultFlags);\n  CREATE_CVAR(OrbitScreenScanBoxHalfExtentY, \"\", x1b0_orbitScreenBoxHalfExtentY[1], skDefaultFlags);\n  CREATE_CVAR(OrbitScreenTargetingBoxCenterX, \"\", x1b8_orbitScreenBoxCenterX[0], skDefaultFlags);\n  CREATE_CVAR(OrbitScreenScanBoxCenterX, \"\", x1b8_orbitScreenBoxCenterX[1], skDefaultFlags);\n  CREATE_CVAR(OrbitScreenTargetingBoxCenterY, \"\", x1c0_orbitScreenBoxCenterY[0], skDefaultFlags);\n  CREATE_CVAR(OrbitScreenScanBoxCenterY, \"\", x1c0_orbitScreenBoxCenterY[1], skDefaultFlags);\n  CREATE_CVAR(OrbitZoneTargetingIdealX, \"\", x1c8_orbitZoneIdealX[0], skDefaultFlags);\n  CREATE_CVAR(OrbitZoneScanIdealX, \"\", x1c8_orbitZoneIdealX[1], skDefaultFlags);\n  CREATE_CVAR(OrbitZoneTargetingIdealY, \"\", x1d0_orbitZoneIdealY[0], skDefaultFlags);\n  CREATE_CVAR(OrbitZoneScanIdealY, \"\", x1d0_orbitZoneIdealY[1], skDefaultFlags);\n  CREATE_CVAR(OrbitNearX, \"\", x1d8_orbitNearX, skDefaultFlags);\n  CREATE_CVAR(OrbitNearZ, \"\", x1dc_orbitNearZ, skDefaultFlags);\n  // CREATE_CVAR(); // x1e0_\n  // CREATE_CVAR(); // x1e4_\n  CREATE_CVAR(OrbitFixedOffsetZDiff, \"\", x1e8_orbitFixedOffsetZDiff, skDefaultFlags);\n  CREATE_CVAR(OrbitZRange, \"\", x1ec_orbitZRange, skDefaultFlags);\n  // CREATE_CVAR(); // x1f0_\n  // CREATE_CVAR(); // x1f4_\n  // CREATE_CVAR(); // x1f8_\n  CREATE_CVAR(OrbitPreventionTime, \"\", x1fc_orbitPreventionTime, skDefaultFlags);\n  CREATE_CVAR_BITFIELD(DashEnabled, \"\", x200_24_dashEnabled, skDefaultFlags);\n  CREATE_CVAR_BITFIELD(DashOnButtonRelease, \"\", x200_25_dashOnButtonRelease, skDefaultFlags);\n  CREATE_CVAR(DashButtonHoldCancelTime, \"\", x204_dashButtonHoldCancelTime, skDefaultFlags);\n  CREATE_CVAR(DashStrafeInputThreshold, \"\", x208_dashStrafeInputThreshold, skDefaultFlags);\n  CREATE_CVAR(SidewaysDoubleJumpImpulse, \"\", x20c_sidewaysDoubleJumpImpulse, skDefaultFlags);\n  CREATE_CVAR(SidewaysVerticalDoubleJumpAccel, \"\", x210_sidewaysVerticalDoubleJumpAccel, skDefaultFlags);\n  CREATE_CVAR(SidewaysHorizontalDoubleJumpAccel, \"\", x214_sidewaysHorizontalDoubleJumpAccel, skDefaultFlags);\n  CREATE_CVAR(ScanningRange, \"\", x218_scanningRange, skDefaultFlags);\n  CREATE_CVAR_BITFIELD(ScanRetention, \"\", x21c_24_scanRetention, skDefaultFlags);\n  CREATE_CVAR_BITFIELD(ScanFreezesGame, \"\", x21c_25_scanFreezesGame, skDefaultFlags);\n  CREATE_CVAR_BITFIELD(OrbitWhileScanning, \"\", x21c_26_orbitWhileScanning, skDefaultFlags);\n  CREATE_CVAR(ScanMaxTargetDistance, \"\", x220_scanMaxTargetDistance, skDefaultFlags);\n  CREATE_CVAR(ScanMaxLockDistance, \"\", x224_scanMaxLockDistance, skDefaultFlags);\n  CREATE_CVAR_BITFIELD(FreeLookTurnsPlayer, \"\", x228_24_freelookTurnsPlayer, skDefaultFlags);\n  // CREATE_CVAR_BITFIELD(); // x228_25_\n  // CREATE_CVAR_BITFIELD(); // x228_26_\n  CREATE_CVAR_BITFIELD(MoveDuringFreelook, \"\", x228_27_moveDuringFreeLook, skDefaultFlags);\n  CREATE_CVAR_BITFIELD(HoldButtonsForFreeLook, \"\", x228_28_holdButtonsForFreeLook, skDefaultFlags);\n  // CREATE_CVAR_BITFIELD(); // x228_30_\n  // CREATE_CVAR_BITFIELD(); // x228_31_\n  // CREATE_CVAR(); // x229_24_\n  CREATE_CVAR_BITFIELD(AimWhenOrbitingPoint, \"\", x229_25_aimWhenOrbitingPoint, skDefaultFlags);\n  CREATE_CVAR_BITFIELD(StayInFreeLookWhileFiring, \"\", x229_26_stayInFreeLookWhileFiring, skDefaultFlags);\n  // CREATE_CVAR_BITFIELD(); // x229_27_\n  // CREATE_CVAR_BITFIELD(); // x229_28_\n  CREATE_CVAR_BITFIELD(OrbitFixedOffset, \"\", x229_29_orbitFixedOffset, skDefaultFlags);\n  CREATE_CVAR_BITFIELD(GunButtonTogglesHolster, \"\", x229_30_gunButtonTogglesHolster, skDefaultFlags);\n  CREATE_CVAR_BITFIELD(GunNotFiringHolstersGun, \"\", x229_31_gunNotFiringHolstersGun, skDefaultFlags);\n  CREATE_CVAR_BITFIELD(FallingDoubleJump, \"\", x22a_24_fallingDoubleJump, skDefaultFlags);\n  CREATE_CVAR_BITFIELD(ImpulseDoubleJump, \"\", x22a_25_impulseDoubleJump, skDefaultFlags);\n  CREATE_CVAR_BITFIELD(FiringCancelsCameraPitch, \"\", x22a_26_firingCancelsCameraPitch, skDefaultFlags);\n  CREATE_CVAR_BITFIELD(AssistedAimingIgnoreHorizontal, \"\", x22a_27_assistedAimingIgnoreHorizontal, skDefaultFlags);\n  CREATE_CVAR_BITFIELD(AssistedAimingIgnoreVertical, \"\", x22a_28_assistedAimingIgnoreVertical, skDefaultFlags);\n  // CREATE_CVAR(); // x22c\n  // CREATE_CVAR(); // x230_\n  CREATE_CVAR(AimMaxDistance, \"\", x234_aimMaxDistance, skDefaultFlags);\n  // CREATE_CVAR(); // x238_\n  // CREATE_CVAR(); // x23c_\n  // CREATE_CVAR(); // x240_\n  // CREATE_CVAR(); // x244_\n  // CREATE_CVAR(); // x248_\n  CREATE_CVAR(AimThresholdDistance, \"\", x24c_aimThresholdDistance, skDefaultFlags);\n  // CREATE_CVAR(); // x250_\n  // CREATE_CVAR(); // x254_\n  CREATE_CVAR(AimBoxWidth, \"\", x258_aimBoxWidth, skDefaultFlags);\n  CREATE_CVAR(AimBoxHeight, \"\", x25c_aimBoxHeight, skDefaultFlags);\n  CREATE_CVAR(AimTargetTimer, \"\", x260_aimTargetTimer, skDefaultFlags);\n  CREATE_CVAR(AimAssistHorizontalAngle, \"\", x264_aimAssistHorizontalAngle, skDefaultFlags);\n  CREATE_CVAR(AimAssistVerticalAngle, \"\", x268_aimAssistVerticalAngle, skDefaultFlags);\n  CREATE_CVAR(PlayerHeight, \"\", x26c_playerHeight, skDefaultFlags);\n  CREATE_CVAR(PlayerXYHalfExtent, \"\", x270_playerXYHalfExtent, skDefaultFlags);\n  CREATE_CVAR(StepUpHeight, \"\", x274_stepUpHeight, skDefaultFlags);\n  CREATE_CVAR(StepDownHeight, \"\", x278_stepDownHeight, skDefaultFlags);\n  CREATE_CVAR(PlayerBallHalfExtent, \"\", x27c_playerBallHalfExtent, skDefaultFlags);\n  CREATE_CVAR(FirstPersonCameraSpeed, \"\", x280_firstPersonCameraSpeed, skDefaultFlags);\n  // CREATE_CVAR(); // x284_\n  CREATE_CVAR(JumpCameraPitchDownStart, \"\", x288_jumpCameraPitchDownStart, skDefaultFlags);\n  CREATE_CVAR(JumpCameraPitchDownFull, \"\", x28c_jumpCameraPitchDownFull, skDefaultFlags);\n  CREATE_CVAR(JumpCameraPitchDownAngle, \"\", x290_jumpCameraPitchDownAngle, skDefaultFlags);\n  CREATE_CVAR(FallCameraPitchDownStart, \"\", x294_fallCameraPitchDownStart, skDefaultFlags);\n  CREATE_CVAR(FallCameraPitchDownFull, \"\", x298_fallCameraPitchDownFull, skDefaultFlags);\n  CREATE_CVAR(FallCameraPitchDownAngle, \"\", x29c_fallCameraPitchDownAngle, skDefaultFlags);\n  CREATE_CVAR(OrbitDistanceMax, \"\", x2a0_orbitDistanceMax, skDefaultFlags);\n  CREATE_CVAR(GrappleSwingLength, \"\", x2a4_grappleSwingLength, skDefaultFlags);\n  CREATE_CVAR(GrappleSwingPeriod, \"\", x2a8_grappleSwingPeriod, skDefaultFlags);\n  CREATE_CVAR(GrapplePullSpeedMin, \"\", x2ac_grapplePullSpeedMin, skDefaultFlags);\n  CREATE_CVAR(GrappleCameraSpeed, \"\", x2b0_grappleCameraSpeed, skDefaultFlags);\n  CREATE_CVAR(MaxGrappleLockedTurnAlignDistance, \"\", x2b4_maxGrappleLockedTurnAlignDistance, skDefaultFlags);\n  CREATE_CVAR(GrapplePullSpeedProportion, \"\", x2b8_grapplePullSpeedProportion, skDefaultFlags);\n  CREATE_CVAR(GrapplePullSpeedMax, \"\", x2bc_grapplePullSpeedMax, skDefaultFlags);\n  CREATE_CVAR(GrappleLookCenterSpeed, \"\", x2c0_grappleLookCenterSpeed, skDefaultFlags);\n  CREATE_CVAR(MaxGrappleTurnSpeed, \"\", x2c4_maxGrappleTurnSpeed, skDefaultFlags);\n  CREATE_CVAR(GrappleJumpForce, \"\", x2c8_grappleJumpForce, skDefaultFlags);\n  CREATE_CVAR(GrappleReleaseTime, \"\", x2cc_grappleReleaseTime, skDefaultFlags);\n  CREATE_CVAR(GrappleJumpMode, \"\", x2d0_grappleJumpMode, skDefaultFlags);\n  CREATE_CVAR(OrbitReleaseBreaksGrapple, \"\", x2d4_orbitReleaseBreaksGrapple, skDefaultFlags);\n  CREATE_CVAR(InvertGrappleTurn, \"\", x2d5_invertGrappleTurn, skDefaultFlags);\n  CREATE_CVAR(GrappleBeamSpeed, \"\", x2d8_grappleBeamSpeed, skDefaultFlags);\n  CREATE_CVAR(GrappleBeamXWaveAmplitude, \"\", x2dc_grappleBeamXWaveAmplitude, skDefaultFlags);\n  CREATE_CVAR(GrappleBeamZWaveAmplitude, \"\", x2e0_grappleBeamZWaveAmplitude, skDefaultFlags);\n  CREATE_CVAR(GrappleBeamAnglePhaseDelta, \"\", x2e4_grappleBeamAnglePhaseDelta, skDefaultFlags);\n  // CREATE_CVAR(); // x2e8_\n  // CREATE_CVAR(); // x2ec_\n  // CREATE_CVAR(); // x2f0_\n  // CREATE_CVAR(); // x2f4_\n  CREATE_CVAR(FrozenTimeout, \"\", x2f8_frozenTimeout, skDefaultFlags);\n  CREATE_CVAR(IceBreakJumpCount, \"\", x2fc_iceBreakJumpCount, skDefaultFlags);\n  CREATE_CVAR(VariaDamageReduction, \"\", x300_variaDamageReduction, skDefaultFlags);\n  CREATE_CVAR(GravityDamageReduction, \"\", x304_gravityDamageReduction, skDefaultFlags);\n  CREATE_CVAR(PhazonDamageReduction, \"\", x308_phazonDamageReduction, skDefaultFlags);\n}\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/Tweaks/CTweakPlayer.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Tweaks/ITweakPlayer.hpp\"\n\nnamespace metaforce::MP1 {\n\nstruct CTweakPlayer final : Tweaks::ITweakPlayer {\n  float x4_maxTranslationalAcceleration[8]{};\n  float x24_maxRotationalAcceleration[8]{};\n  float x44_translationFriction[8]{};\n  float x64_rotationFriction[8]{};\n  float x84_rotationMaxSpeed[8]{};\n  float xa4_translationMaxSpeed[8]{};\n  float xc4_normalGravAccel{};\n  float xc8_fluidGravAccel{};\n  float xcc_verticalJumpAccel{};\n  float xd0_horizontalJumpAccel{};\n  float xd4_verticalDoubleJumpAccel{};\n  float xd8_horizontalDoubleJumpAccel{};\n  float xdc_waterJumpFactor{};\n  float xe0_waterBallJumpFactor{};\n  float xe4_lavaJumpFactor{};\n  float xe8_lavaBallJumpFactor{};\n  float xec_phazonJumpFactor{};\n  float xf0_phazonBallJumpFactor{};\n  float xf4_allowedJumpTime{};\n  float xf8_allowedDoubleJumpTime{};\n  float xfc_minDoubleJumpWindow{};\n  float x100_maxDoubleJumpWindow{};\n  float x104_{};\n  float x108_minJumpTime{};\n  float x10c_minDoubleJumpTime{};\n  float x110_allowedLedgeTime{};\n  float x114_doubleJumpImpulse{};\n  float x118_backwardsForceMultiplier{};\n  float x11c_bombJumpRadius{};\n  float x120_bombJumpHeight{};\n  float x124_eyeOffset{};\n  float x128_turnSpeedMultiplier{};\n  float x12c_freeLookTurnSpeedMultiplier{};\n  float x130_horizontalFreeLookAngleVel{};\n  float x134_verticalFreeLookAngleVel{};\n  float x138_freeLookSpeed{};\n  float x13c_freeLookSnapSpeed{};\n  float x140_{};\n  float x144_freeLookCenteredThresholdAngle{};\n  float x148_freeLookCenteredTime{};\n  float x14c_freeLookDampenFactor{};\n  float x150_leftDiv{};\n  float x154_rightDiv{};\n  float x158_orbitMinDistance[3]{};\n  float x164_orbitNormalDistance[3]{};\n  float x170_orbitMaxDistance[3]{};\n  float x17c_{};\n  float x180_orbitModeTimer{};\n  float x184_orbitCameraSpeed{};\n  float x188_orbitUpperAngle{};\n  float x18c_orbitLowerAngle{};\n  float x190_orbitHorizAngle{};\n  float x194_{};\n  float x198_{};\n  float x19c_orbitMaxTargetDistance{};\n  float x1a0_orbitMaxLockDistance{};\n  float x1a4_orbitDistanceThreshold{};\n  u32 x1a8_orbitScreenBoxHalfExtentX[2]{};\n  u32 x1b0_orbitScreenBoxHalfExtentY[2]{};\n  u32 x1b8_orbitScreenBoxCenterX[2]{};\n  u32 x1c0_orbitScreenBoxCenterY[2]{};\n  u32 x1c8_orbitZoneIdealX[2]{};\n  u32 x1d0_orbitZoneIdealY[2]{};\n  float x1d8_orbitNearX{};\n  float x1dc_orbitNearZ{};\n  float x1e0_{};\n  float x1e4_{};\n  float x1e8_orbitFixedOffsetZDiff{};\n  float x1ec_orbitZRange{};\n  float x1f0_{};\n  float x1f4_{};\n  float x1f8_{};\n  float x1fc_orbitPreventionTime{};\n  bool x200_24_dashEnabled : 1{};\n  bool x200_25_dashOnButtonRelease : 1{};\n  float x204_dashButtonHoldCancelTime{};\n  float x208_dashStrafeInputThreshold{};\n  float x20c_sidewaysDoubleJumpImpulse{};\n  float x210_sidewaysVerticalDoubleJumpAccel{};\n  float x214_sidewaysHorizontalDoubleJumpAccel{};\n  float x218_scanningRange{};\n  bool x21c_24_scanRetention : 1{};\n  bool x21c_25_scanFreezesGame : 1{};\n  bool x21c_26_orbitWhileScanning : 1{};\n  float x220_scanMaxTargetDistance{};\n  float x224_scanMaxLockDistance{};\n  bool x228_24_freelookTurnsPlayer : 1{};\n  bool x228_25_ : 1{};\n  bool x228_26_ : 1{};\n  bool x228_27_moveDuringFreeLook : 1{};\n  bool x228_28_holdButtonsForFreeLook : 1{};\n  bool x228_29_twoButtonsForFreeLook : 1{};\n  bool x228_30_ : 1{};\n  bool x228_31_ : 1{};\n  bool x229_24_ : 1{};\n  bool x229_25_aimWhenOrbitingPoint : 1{};\n  bool x229_26_stayInFreeLookWhileFiring : 1{};\n  bool x229_27_ : 1{};\n  bool x229_28_ : 1{};\n  bool x229_29_orbitFixedOffset : 1{};\n  bool x229_30_gunButtonTogglesHolster : 1{};\n  bool x229_31_gunNotFiringHolstersGun : 1{};\n  bool x22a_24_fallingDoubleJump : 1{};\n  bool x22a_25_impulseDoubleJump : 1{};\n  bool x22a_26_firingCancelsCameraPitch : 1{};\n  bool x22a_27_assistedAimingIgnoreHorizontal : 1{};\n  bool x22a_28_assistedAimingIgnoreVertical : 1{};\n  float x22c_{};\n  float x230_{};\n  float x234_aimMaxDistance{};\n  float x238_{};\n  float x23c_{};\n  float x240_{};\n  float x244_{};\n  float x248_{};\n  float x24c_aimThresholdDistance{};\n  float x250_{};\n  float x254_{};\n  float x258_aimBoxWidth{};\n  float x25c_aimBoxHeight{};\n  float x260_aimTargetTimer{};\n  float x264_aimAssistHorizontalAngle{};\n  float x268_aimAssistVerticalAngle{};\n  float x26c_playerHeight{};\n  float x270_playerXYHalfExtent{};\n  float x274_stepUpHeight{};\n  float x278_stepDownHeight{};\n  float x27c_playerBallHalfExtent{};\n  float x280_firstPersonCameraSpeed{};\n  float x284_{};\n  float x288_jumpCameraPitchDownStart{};\n  float x28c_jumpCameraPitchDownFull{};\n  float x290_jumpCameraPitchDownAngle{};\n  float x294_fallCameraPitchDownStart{};\n  float x298_fallCameraPitchDownFull{};\n  float x29c_fallCameraPitchDownAngle{};\n  float x2a0_orbitDistanceMax{};\n  float x2a4_grappleSwingLength{};\n  float x2a8_grappleSwingPeriod{};\n  float x2ac_grapplePullSpeedMin{};\n  float x2b0_grappleCameraSpeed{};\n  float x2b4_maxGrappleLockedTurnAlignDistance{};\n  float x2b8_grapplePullSpeedProportion{};\n  float x2bc_grapplePullSpeedMax{};\n  float x2c0_grappleLookCenterSpeed{};\n  float x2c4_maxGrappleTurnSpeed{};\n  float x2c8_grappleJumpForce{};\n  float x2cc_grappleReleaseTime{};\n  u32 x2d0_grappleJumpMode{};\n  bool x2d4_orbitReleaseBreaksGrapple{};\n  bool x2d5_invertGrappleTurn{};\n  float x2d8_grappleBeamSpeed{};\n  float x2dc_grappleBeamXWaveAmplitude{};\n  float x2e0_grappleBeamZWaveAmplitude{};\n  float x2e4_grappleBeamAnglePhaseDelta{};\n  float x2e8_{};\n  float x2ec_{};\n  float x2f0_{};\n  bool x2f4_{};\n  float x2f8_frozenTimeout{};\n  u32 x2fc_iceBreakJumpCount{};\n  float x300_variaDamageReduction{};\n  float x304_gravityDamageReduction{};\n  float x308_phazonDamageReduction{};\n  float GetMaxTranslationalAcceleration(int s) const override { return x4_maxTranslationalAcceleration[s]; }\n  float GetMaxRotationalAcceleration(int s) const override { return x24_maxRotationalAcceleration[s]; }\n  float GetPlayerTranslationFriction(int s) const override { return x44_translationFriction[s]; }\n  float GetPlayerRotationFriction(int s) const override { return x64_rotationFriction[s]; }\n  float GetPlayerRotationMaxSpeed(int s) const override { return x84_rotationMaxSpeed[s]; }\n  float GetPlayerTranslationMaxSpeed(int s) const override { return xa4_translationMaxSpeed[s]; }\n  float GetNormalGravAccel() const override { return xc4_normalGravAccel; }\n  float GetFluidGravAccel() const override { return xc8_fluidGravAccel; }\n  float GetVerticalJumpAccel() const override { return xcc_verticalJumpAccel; }\n  float GetHorizontalJumpAccel() const override { return xd0_horizontalJumpAccel; }\n  float GetVerticalDoubleJumpAccel() const override { return xd4_verticalDoubleJumpAccel; }\n  float GetHorizontalDoubleJumpAccel() const override { return xd8_horizontalDoubleJumpAccel; }\n  float GetWaterJumpFactor() const override { return xdc_waterJumpFactor; }\n  float GetWaterBallJumpFactor() const override { return xe0_waterBallJumpFactor; }\n  float GetLavaJumpFactor() const override { return xe4_lavaJumpFactor; }\n  float GetLavaBallJumpFactor() const override { return xe8_lavaBallJumpFactor; }\n  float GetPhazonJumpFactor() const override { return xec_phazonJumpFactor; }\n  float GetPhazonBallJumpFactor() const override { return xf0_phazonBallJumpFactor; }\n  float GetAllowedJumpTime() const override { return xf4_allowedJumpTime; }\n  float GetAllowedDoubleJumpTime() const override { return xf8_allowedDoubleJumpTime; }\n  float GetMinDoubleJumpWindow() const override { return xfc_minDoubleJumpWindow; }\n  float GetMaxDoubleJumpWindow() const override { return x100_maxDoubleJumpWindow; }\n  float GetMinJumpTime() const override { return x108_minJumpTime; }\n  float GetMinDoubleJumpTime() const override { return x10c_minDoubleJumpTime; }\n  float GetAllowedLedgeTime() const override { return x110_allowedLedgeTime; }\n  float GetDoubleJumpImpulse() const override { return x114_doubleJumpImpulse; }\n  float GetBackwardsForceMultiplier() const override { return x118_backwardsForceMultiplier; }\n  float GetBombJumpRadius() const override { return x11c_bombJumpRadius; }\n  float GetBombJumpHeight() const override { return x120_bombJumpHeight; }\n  float GetEyeOffset() const override { return x124_eyeOffset; }\n  float GetTurnSpeedMultiplier() const override { return x128_turnSpeedMultiplier; }\n  float GetFreeLookTurnSpeedMultiplier() const override { return x12c_freeLookTurnSpeedMultiplier; }\n  float GetFreeLookSpeed() const override { return x138_freeLookSpeed; }\n  float GetFreeLookSnapSpeed() const override { return x13c_freeLookSnapSpeed; }\n  float GetFreeLookCenteredThresholdAngle() const override { return x144_freeLookCenteredThresholdAngle; }\n  float GetFreeLookCenteredTime() const override { return x148_freeLookCenteredTime; }\n  float GetOrbitModeTimer() const override { return x180_orbitModeTimer; }\n  float GetOrbitUpperAngle() const override { return x188_orbitUpperAngle; }\n  float GetOrbitLowerAngle() const override { return x18c_orbitLowerAngle; }\n  float GetOrbitHorizAngle() const override { return x190_orbitHorizAngle; }\n  float GetOrbitMaxTargetDistance() const override { return x19c_orbitMaxTargetDistance; }\n  float GetOrbitMaxLockDistance() const override { return x1a0_orbitMaxLockDistance; }\n  float GetOrbitDistanceThreshold() const override { return x1a4_orbitDistanceThreshold; }\n  uint32_t GetOrbitScreenBoxHalfExtentX(int zone) const override { return x1a8_orbitScreenBoxHalfExtentX[zone]; }\n  uint32_t GetOrbitScreenBoxHalfExtentY(int zone) const override { return x1b0_orbitScreenBoxHalfExtentY[zone]; }\n  uint32_t GetOrbitScreenBoxCenterX(int zone) const override { return x1b8_orbitScreenBoxCenterX[zone]; }\n  uint32_t GetOrbitScreenBoxCenterY(int zone) const override { return x1c0_orbitScreenBoxCenterY[zone]; }\n  uint32_t GetOrbitZoneIdealX(int zone) const override { return x1c8_orbitZoneIdealX[zone]; }\n  uint32_t GetOrbitZoneIdealY(int zone) const override { return x1d0_orbitZoneIdealY[zone]; }\n  float GetOrbitNearX() const override { return x1d8_orbitNearX; }\n  float GetOrbitNearZ() const override { return x1dc_orbitNearZ; }\n  float GetOrbitFixedOffsetZDiff() const override { return x1e8_orbitFixedOffsetZDiff; }\n  float GetOrbitZRange() const override { return x1ec_orbitZRange; }\n  bool GetDashEnabled() const override { return x200_24_dashEnabled; }\n  bool GetDashOnButtonRelease() const override { return x200_25_dashOnButtonRelease; }\n  float GetDashButtonHoldCancelTime() const override { return x204_dashButtonHoldCancelTime; }\n  float GetDashStrafeInputThreshold() const override { return x208_dashStrafeInputThreshold; }\n  float GetSidewaysDoubleJumpImpulse() const override { return x20c_sidewaysDoubleJumpImpulse; }\n  float GetSidewaysVerticalDoubleJumpAccel() const override { return x210_sidewaysVerticalDoubleJumpAccel; }\n  float GetSidewaysHorizontalDoubleJumpAccel() const override { return x214_sidewaysHorizontalDoubleJumpAccel; }\n  float GetScanningRange() const override { return x218_scanningRange; }\n  bool GetScanRetention() const override { return x21c_24_scanRetention; }\n  bool GetScanFreezesGame() const override { return x21c_25_scanFreezesGame; }\n  bool GetOrbitWhileScanning() const override { return x21c_26_orbitWhileScanning; }\n  float GetScanMaxTargetDistance() const override { return x220_scanMaxTargetDistance; }\n  float GetScanMaxLockDistance() const override { return x224_scanMaxLockDistance; }\n  bool GetMoveDuringFreeLook() const override { return x228_27_moveDuringFreeLook; }\n  bool GetHoldButtonsForFreeLook() const override { return x228_28_holdButtonsForFreeLook; }\n  bool GetTwoButtonsForFreeLook() const override { return x228_29_twoButtonsForFreeLook; }\n  bool GetAimWhenOrbitingPoint() const override { return x229_25_aimWhenOrbitingPoint; }\n  bool GetStayInFreeLookWhileFiring() const override { return x229_26_stayInFreeLookWhileFiring; }\n  bool GetOrbitFixedOffset() const override { return x229_29_orbitFixedOffset; }\n  bool GetGunButtonTogglesHolster() const override { return x229_30_gunButtonTogglesHolster; }\n  bool GetGunNotFiringHolstersGun() const override { return x229_31_gunNotFiringHolstersGun; }\n  bool GetFallingDoubleJump() const override { return x22a_24_fallingDoubleJump; }\n  bool GetImpulseDoubleJump() const override { return x22a_25_impulseDoubleJump; }\n  bool GetFiringCancelsCameraPitch() const override { return x22a_26_firingCancelsCameraPitch; }\n  bool GetAssistedAimingIgnoreHorizontal() const override { return x22a_27_assistedAimingIgnoreHorizontal; }\n  bool GetAssistedAimingIgnoreVertical() const override { return x22a_28_assistedAimingIgnoreVertical; }\n  float GetAimMaxDistance() const override { return x234_aimMaxDistance; }\n  float GetAimThresholdDistance() const override { return x24c_aimThresholdDistance; }\n  float GetAimBoxWidth() const override { return x258_aimBoxWidth; }\n  float GetAimBoxHeight() const override { return x25c_aimBoxHeight; }\n  float GetAimTargetTimer() const override { return x260_aimTargetTimer; }\n  float GetAimAssistHorizontalAngle() const override { return x264_aimAssistHorizontalAngle; }\n  float GetAimAssistVerticalAngle() const override { return x268_aimAssistVerticalAngle; }\n  float GetPlayerHeight() const override { return x26c_playerHeight; }\n  float GetPlayerXYHalfExtent() const override { return x270_playerXYHalfExtent; }\n  float GetStepUpHeight() const override { return x274_stepUpHeight; }\n  float GetStepDownHeight() const override { return x278_stepDownHeight; }\n  float GetPlayerBallHalfExtent() const override { return x27c_playerBallHalfExtent; }\n  float GetOrbitDistanceMax() const override { return x2a0_orbitDistanceMax; }\n  float GetGrappleSwingLength() const override { return x2a4_grappleSwingLength; }\n  float GetGrappleSwingPeriod() const override { return x2a8_grappleSwingPeriod; }\n  float GetGrapplePullSpeedMin() const override { return x2ac_grapplePullSpeedMin; }\n  float GetMaxGrappleLockedTurnAlignDistance() const override { return x2b4_maxGrappleLockedTurnAlignDistance; }\n  float GetGrapplePullSpeedProportion() const override { return x2b8_grapplePullSpeedProportion; }\n  float GetGrapplePullSpeedMax() const override { return x2bc_grapplePullSpeedMax; }\n  float GetGrappleLookCenterSpeed() const override { return x2c0_grappleLookCenterSpeed; }\n  float GetMaxGrappleTurnSpeed() const override { return x2c4_maxGrappleTurnSpeed; }\n  float GetGrappleJumpForce() const override { return x2c8_grappleJumpForce; }\n  float GetGrappleReleaseTime() const override { return x2cc_grappleReleaseTime; }\n  uint32_t GetGrappleJumpMode() const override { return x2d0_grappleJumpMode; }\n  bool GetOrbitReleaseBreaksGrapple() const override { return x2d4_orbitReleaseBreaksGrapple; }\n  bool GetInvertGrappleTurn() const override { return x2d5_invertGrappleTurn; }\n  float GetGrappleBeamSpeed() const override { return x2d8_grappleBeamSpeed; }\n  float GetGrappleBeamXWaveAmplitude() const override { return x2dc_grappleBeamXWaveAmplitude; }\n  float GetGrappleBeamZWaveAmplitude() const override { return x2e0_grappleBeamZWaveAmplitude; }\n  float GetGrappleBeamAnglePhaseDelta() const override { return x2e4_grappleBeamAnglePhaseDelta; }\n  float GetHorizontalFreeLookAngleVel() const override { return x130_horizontalFreeLookAngleVel; }\n  float GetVerticalFreeLookAngleVel() const override { return x134_verticalFreeLookAngleVel; }\n  float GetOrbitCameraSpeed() const override { return x184_orbitCameraSpeed; }\n  float GetOrbitPreventionTime() const override { return x1fc_orbitPreventionTime; }\n  bool GetFreeLookTurnsPlayer() const override { return x228_24_freelookTurnsPlayer; }\n  float GetJumpCameraPitchDownStart() const override { return x288_jumpCameraPitchDownStart; }\n  float GetJumpCameraPitchDownFull() const override { return x28c_jumpCameraPitchDownFull; }\n  float GetJumpCameraPitchDownAngle() const override { return x290_jumpCameraPitchDownAngle; }\n  float GetFallCameraPitchDownStart() const override { return x294_fallCameraPitchDownStart; }\n  float GetFallCameraPitchDownFull() const override { return x298_fallCameraPitchDownFull; }\n  float GetFallCameraPitchDownAngle() const override { return x29c_fallCameraPitchDownAngle; }\n  float GetFirstPersonCameraSpeed() const override { return x280_firstPersonCameraSpeed; }\n  float GetGrappleCameraSpeed() const override { return x2b0_grappleCameraSpeed; }\n  float GetFreeLookDampenFactor() const override { return x14c_freeLookDampenFactor; }\n  float GetLeftLogicalThreshold() const override { return x150_leftDiv; }\n  float GetRightLogicalThreshold() const override { return x154_rightDiv; }\n  float GetOrbitMinDistance(int type) const override { return x158_orbitMinDistance[type]; }\n  float GetOrbitNormalDistance(int type) const override { return x164_orbitNormalDistance[type]; }\n  float GetOrbitMaxDistance(int type) const override { return x170_orbitMaxDistance[type]; }\n  float GetFrozenTimeout() const override { return x2f8_frozenTimeout; }\n  uint32_t GetIceBreakJumpCount() const override { return x2fc_iceBreakJumpCount; }\n  float GetVariaDamageReduction() const override { return x300_variaDamageReduction; }\n  float GetGravityDamageReduction() const override { return x304_gravityDamageReduction; }\n  float GetPhazonDamageReduction() const override { return x308_phazonDamageReduction; }\n  CTweakPlayer() = default;\n  CTweakPlayer(CInputStream& in);\n  void PutTo(COutputStream& out);\n  void FixupValues();\n  void initCVars(CVarManager* mgr) override;\n  void _tweakListener(CVar* cv);\n};\n\n} // namespace DataSpec::DNAMP1\n"
  },
  {
    "path": "Runtime/MP1/Tweaks/CTweakPlayerControl.cpp",
    "content": "#include \"Runtime/MP1/Tweaks/CTweakPlayerControl.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n\nnamespace metaforce::MP1 {\nCTweakPlayerControl::CTweakPlayerControl(CInputStream& in) {\n  for (u32 i = 0; i < m_mappings.size(); ++i) {\n    m_mappings[i] = ControlMapper::EFunctionList(in.ReadLong());\n  }\n}\n}"
  },
  {
    "path": "Runtime/MP1/Tweaks/CTweakPlayerControl.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Tweaks/ITweakPlayerControl.hpp\"\n\nnamespace metaforce::MP1 {\nstruct CTweakPlayerControl final : Tweaks::ITweakPlayerControl {\n  std::array<ControlMapper::EFunctionList, 67> m_mappings;\n  [[nodiscard]] ControlMapper::EFunctionList GetMapping(u32 command) const override { return m_mappings[command]; }\n  CTweakPlayerControl() = default;\n  CTweakPlayerControl(CInputStream& reader);\n};\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/Tweaks/CTweakPlayerGun.cpp",
    "content": "#include \"Runtime/MP1/Tweaks/CTweakPlayerGun.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n\nnamespace metaforce::MP1 {\nCTweakPlayerGun::CTweakPlayerGun(CInputStream& in) {\n  x4_upLookAngle = in.ReadFloat();\n  x8_downLookAngle = in.ReadFloat();\n  xc_verticalSpread = in.ReadFloat();\n  x10_horizontalSpread = in.ReadFloat();\n  x14_highVerticalSpread = in.ReadFloat();\n  x18_highHorizontalSpread = in.ReadFloat();\n  x1c_lowVerticalSpread = in.ReadFloat();\n  x20_lowHorizontalSpread = in.ReadFloat();\n  x24_aimVerticalSpeed = in.ReadFloat();\n  x28_aimHorizontalSpeed = in.ReadFloat();\n  x2c_bombFuseTime = in.ReadFloat();\n  x30_bombDropDelayTime = in.ReadFloat();\n  x34_holoHoldTime = in.ReadFloat();\n  x38_gunTransformTime = in.ReadFloat();\n  x3c_gunHolsterTime = in.ReadFloat();\n  x40_gunNotFiringTime = in.ReadFloat();\n  x44_fixedVerticalAim = in.ReadFloat();\n  x48_gunExtendDistance = in.ReadFloat();\n  x4c_gunPosition = in.Get<zeus::CVector3f>();\n  x58_ = in.Get<zeus::CVector3f>();\n  x64_grapplingArmPosition = in.Get<zeus::CVector3f>();\n  x70_bomb = in.Get<SShotParam>();\n  x8c_powerBomb = in.Get<SShotParam>();\n  x1d4_missile = in.Get<SShotParam>();\n  for (auto& beam : xa8_beams) {\n    beam = in.Get<SWeaponInfo>();\n  }\n  for (auto& combo : x1f0_combos) {\n    combo = in.Get<SComboShotParam>();\n  }\n\n  for (float& r : x280_ricochetData) {\n    r = in.ReadFloat();\n  }\n  x44_fixedVerticalAim = zeus::degToRad(x44_fixedVerticalAim);\n}\n} // namespace metaforce::MP1"
  },
  {
    "path": "Runtime/MP1/Tweaks/CTweakPlayerGun.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Tweaks/ITweakPlayerGun.hpp\"\n\nnamespace metaforce::MP1 {\nstruct CTweakPlayerGun final : Tweaks::ITweakPlayerGun {\n  float x4_upLookAngle;\n  float x8_downLookAngle;\n  float xc_verticalSpread;\n  float x10_horizontalSpread;\n  float x14_highVerticalSpread;\n  float x18_highHorizontalSpread;\n  float x1c_lowVerticalSpread;\n  float x20_lowHorizontalSpread;\n  float x24_aimVerticalSpeed;\n  float x28_aimHorizontalSpeed;\n  float x2c_bombFuseTime;\n  float x30_bombDropDelayTime;\n  float x34_holoHoldTime;\n  float x38_gunTransformTime;\n  float x3c_gunHolsterTime;\n  float x40_gunNotFiringTime;\n  float x44_fixedVerticalAim;\n  float x48_gunExtendDistance;\n  zeus::CVector3f x4c_gunPosition;\n  zeus::CVector3f x58_;\n  zeus::CVector3f x64_grapplingArmPosition;\n  SShotParam x70_bomb;\n  SShotParam x8c_powerBomb;\n  SShotParam x1d4_missile;\n  SWeaponInfo xa8_beams[5];\n  SComboShotParam x1f0_combos[5];    // Originally rstl::reserved_vector<SShotParam,5>\n  float x280_ricochetData[6]; // Originally rstl::reserved_vector<float,5>, extended to 6 to capture\n                                     // PhazonBeam's value\n  CTweakPlayerGun() = default;\n  CTweakPlayerGun(CInputStream& in);\n  float GetUpLookAngle() const override { return x4_upLookAngle; }\n  float GetDownLookAngle() const override { return x8_downLookAngle; }\n  float GetVerticalSpread() const override { return xc_verticalSpread; }\n  float GetHorizontalSpread() const override { return x10_horizontalSpread; }\n  float GetHighVerticalSpread() const override { return x14_highVerticalSpread; }\n  float GetHighHorizontalSpread() const override { return x18_highHorizontalSpread; }\n  float GetLowVerticalSpread() const override { return x1c_lowVerticalSpread; }\n  float GetLowHorizontalSpread() const override { return x20_lowHorizontalSpread; }\n  float GetAimVerticalSpeed() const override { return x24_aimVerticalSpeed; }\n  float GetAimHorizontalSpeed() const override { return x28_aimHorizontalSpeed; }\n  float GetBombFuseTime() const override { return x2c_bombFuseTime; }\n  float GetBombDropDelayTime() const override { return x30_bombDropDelayTime; }\n  float GetHoloHoldTime() const override { return x34_holoHoldTime; }\n  float GetGunTransformTime() const override { return x38_gunTransformTime; }\n  float GetGunHolsterTime() const override { return x3c_gunHolsterTime; }\n  float GetGunNotFiringTime() const override { return x40_gunNotFiringTime; }\n  float GetFixedVerticalAim() const override { return x44_fixedVerticalAim; }\n  float GetGunExtendDistance() const override { return x48_gunExtendDistance; }\n  const zeus::CVector3f& GetGunPosition() const override { return x4c_gunPosition; }\n  const zeus::CVector3f& GetGrapplingArmPosition() const override { return x64_grapplingArmPosition; }\n  float GetRichochetDamage(u32 type) const override {\n    switch (type) {\n    case 0: // Power\n      return x280_ricochetData[0];\n    case 1: // Ice\n      return x280_ricochetData[1];\n    case 2: // Wave\n      return x280_ricochetData[2];\n    case 3: // Plasma\n      return x280_ricochetData[3];\n    case 6: // Missile\n      return x280_ricochetData[4];\n    case 8: // Phazon\n            /* Note: In order to return the same value as retail we have to do a bit of a hack\n             * Retro accidentally forgot to load in PhazonBeam's richochet value, as a result, it loads the\n             * pointer to CTweakParticle's vtable.\n             */\n#if MP_v1088\n\n      return float(0x803D9CC4);\n#else\n      return x280_ricochetData[5];\n#endif\n    default:\n      return 1.f;\n    }\n  }\n\n  const SWeaponInfo& GetBeamInfo(s32 beam) const override {\n    if (beam < 0 || beam >= 5) {\n      return xa8_beams[0];\n    }\n    return xa8_beams[beam];\n  }\n\n  const SComboShotParam& GetComboShotInfo(s32 beam) const override {\n    if (beam < 0 || beam >= 5) {\n      return x1f0_combos[0];\n    }\n    return x1f0_combos[beam];\n  }\n\n  const SShotParam& GetBombInfo() const override { return x70_bomb; }\n  const SShotParam& GetPowerBombInfo() const override { return x8c_powerBomb; }\n  const SShotParam& GetMissileInfo() const { return x1d4_missile; }\n};\n} // namespace DataSpec::DNAMP1\n"
  },
  {
    "path": "Runtime/MP1/Tweaks/CTweakPlayerRes.cpp",
    "content": "#include \"Runtime/MP1/Tweaks/CTweakPlayerRes.hpp\"\n\nnamespace metaforce::MP1 {\nCTweakPlayerRes::CTweakPlayerRes(CInputStream& in, bool hasNewFields) {\n  m_saveStationIcon = in.Get<std::string>();\n  m_missileStationIcon = in.Get<std::string>();\n  m_elevatorIcon = in.Get<std::string>();\n\n  m_minesBreakFirstTopIcon = in.Get<std::string>();\n  m_minesBreakFirstBottomIcon = in.Get<std::string>();\n  m_minesBreakSecondTopIcon = in.Get<std::string>();\n  m_minesBreakSecondBottomIcon = in.Get<std::string>();\n\n  /* ADDED IN JP/PAL/TRILOGY */\n  if (hasNewFields) {\n    m_mapArrowDown = in.Get<std::string>();\n    m_mapArrowUp = in.Get<std::string>();\n  }\n  /* END */\n\n  m_lStickN = in.Get<std::string>();\n  m_lStickU = in.Get<std::string>();\n  m_lStickUL = in.Get<std::string>();\n  m_lStickL = in.Get<std::string>();\n  m_lStickDL = in.Get<std::string>();\n  m_lStickD = in.Get<std::string>();\n  m_lStickDR = in.Get<std::string>();\n  m_lStickR = in.Get<std::string>();\n  m_lStickUR = in.Get<std::string>();\n\n  m_cStickN = in.Get<std::string>();\n  m_cStickU = in.Get<std::string>();\n  m_cStickUL = in.Get<std::string>();\n  m_cStickL = in.Get<std::string>();\n  m_cStickDL = in.Get<std::string>();\n  m_cStickD = in.Get<std::string>();\n  m_cStickDR = in.Get<std::string>();\n  m_cStickR = in.Get<std::string>();\n  m_cStickUR = in.Get<std::string>();\n\n  m_lTriggerOut = in.Get<std::string>();\n  m_lTriggerIn = in.Get<std::string>();\n  m_rTriggerOut = in.Get<std::string>();\n  m_rTriggerIn = in.Get<std::string>();\n\n  m_startButtonOut = in.Get<std::string>();\n  m_startButtonIn = in.Get<std::string>();\n  m_aButtonOut = in.Get<std::string>();\n  m_aButtonIn = in.Get<std::string>();\n  m_bButtonOut = in.Get<std::string>();\n  m_bButtonIn = in.Get<std::string>();\n  m_xButtonOut = in.Get<std::string>();\n  m_xButtonIn = in.Get<std::string>();\n  m_yButtonOut = in.Get<std::string>();\n  m_yButtonIn = in.Get<std::string>();\n\n  m_ballTransitionsANCS = in.Get<std::string>();\n  m_ballTransitionsPower = in.Get<std::string>();\n  m_ballTransitionsIce = in.Get<std::string>();\n  m_ballTransitionsWave = in.Get<std::string>();\n  m_ballTransitionsPlasma = in.Get<std::string>();\n  m_ballTransitionsPhazon = in.Get<std::string>();\n\n  m_cinePower = in.Get<std::string>();\n  m_cineIce = in.Get<std::string>();\n  m_cineWave = in.Get<std::string>();\n  m_cinePlasma = in.Get<std::string>();\n  m_cinePhazon = in.Get<std::string>();\n\n  m_cinematicMoveOutofIntoPlayerDistance = in.ReadFloat();\n}\n} // namespace metaforce::MP1"
  },
  {
    "path": "Runtime/MP1/Tweaks/CTweakPlayerRes.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Tweaks/ITweakPlayerRes.hpp\"\n\nnamespace metaforce::MP1 {\n\nstruct CTweakPlayerRes final : Tweaks::ITweakPlayerRes {\n  std::string m_saveStationIcon;\n  std::string m_missileStationIcon;\n  std::string m_elevatorIcon;\n\n  std::string m_minesBreakFirstTopIcon;\n  std::string m_minesBreakFirstBottomIcon;\n  std::string m_minesBreakSecondTopIcon;\n  std::string m_minesBreakSecondBottomIcon;\n\n  /* ADDED IN PAL/TRILOGY */\n  std::string m_mapArrowDown;\n  std::string m_mapArrowUp;\n  /* END */\n\n  std::string m_lStickN;\n  std::string m_lStickU;\n  std::string m_lStickUL;\n  std::string m_lStickL;\n  std::string m_lStickDL;\n  std::string m_lStickD;\n  std::string m_lStickDR;\n  std::string m_lStickR;\n  std::string m_lStickUR;\n\n  std::string m_cStickN;\n  std::string m_cStickU;\n  std::string m_cStickUL;\n  std::string m_cStickL;\n  std::string m_cStickDL;\n  std::string m_cStickD;\n  std::string m_cStickDR;\n  std::string m_cStickR;\n  std::string m_cStickUR;\n\n  std::string m_lTriggerOut;\n  std::string m_lTriggerIn;\n  std::string m_rTriggerOut;\n  std::string m_rTriggerIn;\n\n  std::string m_startButtonOut;\n  std::string m_startButtonIn;\n  std::string m_aButtonOut;\n  std::string m_aButtonIn;\n  std::string m_bButtonOut;\n  std::string m_bButtonIn;\n  std::string m_xButtonOut;\n  std::string m_xButtonIn;\n  std::string m_yButtonOut;\n  std::string m_yButtonIn;\n\n  std::string m_ballTransitionsANCS;\n  std::string m_ballTransitionsPower;\n  std::string m_ballTransitionsIce;\n  std::string m_ballTransitionsWave;\n  std::string m_ballTransitionsPlasma;\n  std::string m_ballTransitionsPhazon;\n\n  std::string m_cinePower;\n  std::string m_cineIce;\n  std::string m_cineWave;\n  std::string m_cinePlasma;\n  std::string m_cinePhazon;\n\n  float m_cinematicMoveOutofIntoPlayerDistance;\n\n  std::string_view _GetSaveStationIcon() const override { return m_saveStationIcon; }\n  std::string_view _GetMissileStationIcon() const override { return m_missileStationIcon; }\n  std::string_view _GetElevatorIcon() const override { return m_elevatorIcon; }\n\n  std::string_view _GetMinesBreakFirstTopIcon() const override { return m_minesBreakFirstTopIcon; }\n  std::string_view _GetMinesBreakFirstBottomIcon() const override { return m_minesBreakFirstBottomIcon; }\n  std::string_view _GetMinesBreakSecondTopIcon() const override { return m_minesBreakSecondTopIcon; }\n  std::string_view _GetMinesBreakSecondBottomIcon() const override { return m_minesBreakSecondBottomIcon; }\n\n  std::string_view _GetLStick(size_t idx) const override { return (&m_lStickN)[idx]; }\n  std::string_view _GetCStick(size_t idx) const override { return (&m_cStickN)[idx]; }\n\n  std::string_view _GetLTrigger(size_t idx) const override { return (&m_lTriggerOut)[idx]; }\n  std::string_view _GetRTrigger(size_t idx) const override { return (&m_rTriggerOut)[idx]; }\n  std::string_view _GetStartButton(size_t idx) const override { return (&m_startButtonOut)[idx]; }\n  std::string_view _GetAButton(size_t idx) const override { return (&m_aButtonOut)[idx]; }\n  std::string_view _GetBButton(size_t idx) const override { return (&m_bButtonOut)[idx]; }\n  std::string_view _GetXButton(size_t idx) const override { return (&m_xButtonOut)[idx]; }\n  std::string_view _GetYButton(size_t idx) const override { return (&m_yButtonOut)[idx]; }\n\n  std::string_view _GetBallTransitionsANCS() const override { return m_ballTransitionsANCS; }\n\n  std::string_view _GetBallTransitionBeamRes(size_t idx) const override { return (&m_ballTransitionsPower)[idx]; }\n  std::string_view _GetBeamCineModel(size_t idx) const override { return (&m_cinePower)[idx]; }\n\n  float _GetCinematicMoveOutofIntoPlayerDistance() const override { return m_cinematicMoveOutofIntoPlayerDistance; }\n\n  CTweakPlayerRes() = default;\n  CTweakPlayerRes(CInputStream& in, bool hasNewFields);\n};\n\n} // namespace DataSpec::DNAMP1\n"
  },
  {
    "path": "Runtime/MP1/Tweaks/CTweakSlideShow.cpp",
    "content": "#include \"Runtime/MP1/Tweaks/CTweakSlideShow.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n\nnamespace metaforce::MP1 {\nCTweakSlideShow::CTweakSlideShow(CInputStream& in) {\n  x4_pakName = in.Get<std::string>();\n  x14_fontAssetName = in.Get<std::string>();\n  x24_fontColor = in.Get<zeus::CColor>();\n  x28_outlineColor = in.Get<zeus::CColor>();\n  x2c_scanPercentInterval = in.ReadFloat();\n  x30_ = in.ReadFloat();\n  x34_ = in.ReadFloat();\n  x38_ = in.ReadFloat();\n  x3c_ = in.ReadFloat();\n  x40_ = in.Get<zeus::CColor>();\n  x44_ = in.ReadFloat();\n  x48_ = in.ReadFloat();\n  x4c_ = in.ReadFloat();\n  x50_ = in.ReadFloat();\n  x54_ = in.ReadFloat();\n  x58_ = in.ReadFloat();\n}\n} // namespace metaforce::MP1"
  },
  {
    "path": "Runtime/MP1/Tweaks/CTweakSlideShow.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Tweaks/ITweakSlideShow.hpp\"\n#include \"zeus/CColor.hpp\"\n\nnamespace metaforce::MP1 {\n\nstruct CTweakSlideShow final : Tweaks::ITweakSlideShow {\n  std::string x4_pakName;\n  std::string x14_fontAssetName;\n  zeus::CColor x24_fontColor;\n  zeus::CColor x28_outlineColor;\n  float x2c_scanPercentInterval;\n  float x30_;\n  float x34_;\n  float x38_;\n  float x3c_;\n  zeus::CColor x40_;\n  float x44_;\n  float x48_;\n  float x4c_;\n  float x50_;\n  float x54_;\n  float x58_;\n\n  CTweakSlideShow() = default;\n  CTweakSlideShow(CInputStream& in);\n\n  std::string_view GetFont() const override { return x14_fontAssetName; }\n  const zeus::CColor& GetFontColor() const override { return x24_fontColor; }\n  const zeus::CColor& GetOutlineColor() const override { return x28_outlineColor; }\n  float GetScanPercentInterval() const override { return x2c_scanPercentInterval; }\n  float GetX54() const override { return x54_; }\n};\n\n} // namespace DataSpec::DNAMP1\n"
  },
  {
    "path": "Runtime/MP1/Tweaks/CTweakTargeting.cpp",
    "content": "#include \"Runtime/MP1/Tweaks/CTweakTargeting.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n\nnamespace metaforce::MP1 {\nCTweakTargeting::CTweakTargeting(CInputStream& in) {\n  x4_targetRadiusMode = in.ReadLong();\n  x8_currLockOnExitDuration = in.ReadFloat();\n  xc_currLockOnEnterDuration = in.ReadFloat();\n  x10_currLockOnSwitchDuration = in.ReadFloat();\n  x14_lockConfirmScale = in.ReadFloat();\n  x18_nextLockOnEnterDuration = in.ReadFloat();\n  x1c_nextLockOnExitDuration = in.ReadFloat();\n  x20_nextLockOnSwitchDuration = in.ReadFloat();\n  x24_seekerScale = in.ReadFloat();\n  x28_seekerAngleSpeed = in.ReadFloat();\n  x2c_xrayRetAngleSpeed = in.ReadFloat();\n  x30_ = in.Get<zeus::CVector3f>();\n  x3c_ = in.Get<zeus::CVector3f>();\n  x48_ = in.ReadFloat();\n  x4c_ = in.ReadFloat();\n  x50_orbitPointZOffset = in.ReadFloat();\n  x54_orbitPointInTime = in.ReadFloat();\n  x58_orbitPointOutTime = in.ReadFloat();\n  x5c_ = in.ReadFloat();\n  x60_ = in.Get<zeus::CVector3f>();\n  x6c_ = in.Get<zeus::CVector3f>();\n  x78_ = in.Get<zeus::CVector3f>();\n  x84_ = in.Get<zeus::CVector3f>();\n  x90_ = in.ReadFloat();\n  x94_ = in.ReadFloat();\n  x98_ = in.ReadFloat();\n  x9c_ = in.ReadFloat();\n  xa0_ = in.ReadFloat();\n  xa4_ = in.ReadFloat();\n  xa8_ = in.ReadFloat();\n  xac_ = in.ReadFloat();\n  xb0_thermalReticuleColor = in.Get<zeus::CColor>();\n  xb4_targetFlowerScale = in.ReadFloat();\n  xb8_targetFlowerColor = in.Get<zeus::CColor>();\n  xbc_missileBracketDuration = in.ReadFloat();\n  xc0_missileBracketScaleStart = in.ReadFloat();\n  xc4_missileBracketScaleEnd = in.ReadFloat();\n  xc8_missileBracketScaleDuration = in.ReadFloat();\n  xcc_missileBracketColor = in.Get<zeus::CColor>();\n  xd0_LockonDuration = in.ReadFloat();\n  xd4_innerBeamScale = in.ReadFloat();\n  xd8_innerBeamColorPower = in.Get<zeus::CColor>();\n  xdc_innerBeamColorIce = in.Get<zeus::CColor>();\n  xe0_innerBeamColorWave = in.Get<zeus::CColor>();\n  xe4_innerBeamColorPlasma = in.Get<zeus::CColor>();\n  xe8_chargeGaugeOvershootOffset = in.ReadFloat();\n  xec_chargeGaugeOvershootDuration = in.ReadFloat();\n  xf0_outerBeamSquaresScale = in.ReadFloat();\n  xf4_outerBeamSquareColor = in.Get<zeus::CColor>();\n  u32 outerBeamCount = in.ReadLong();\n  xf8_outerBeamSquareAngles.resize(outerBeamCount);\n  for (u32 i = 0; i < outerBeamCount; ++i) {\n    read_reserved_vector(xf8_outerBeamSquareAngles[i], in);\n  }\n  read_reserved_vector(x108_chargeGaugeAngles, in);\n  x118_chargeGaugeScale = in.ReadFloat();\n  x11c_chargeGaugeNonFullColor = in.Get<zeus::CColor>();\n  x120_chargeTickCount = in.ReadLong();\n  x124_chargeTickAnglePitch = in.ReadFloat();\n  x128_lockFireScale = in.ReadFloat();\n  x12c_lockFireDuration = in.ReadFloat();\n  x130_lockFireColor = in.Get<zeus::CColor>();\n  x134_lockDaggerScaleStart = in.ReadFloat();\n  x138_lockDaggerScaleEnd = in.ReadFloat();\n  x13c_lockDaggerColor = in.Get<zeus::CColor>();\n  x140_lockDaggerAngle0 = in.ReadFloat();\n  x144_lockDaggerAngle1 = in.ReadFloat();\n  x148_lockDaggerAngle2 = in.ReadFloat();\n  x14c_lockConfirmColor = in.Get<zeus::CColor>();\n  x150_seekerColor = in.Get<zeus::CColor>();\n  x154_lockConfirmClampMin = in.ReadFloat();\n  x158_lockConfirmClampMax = in.ReadFloat();\n  x15c_targetFlowerClampMin = in.ReadFloat();\n  x160_targetFlowerClampMax = in.ReadFloat();\n  x164_seekerClampMin = in.ReadFloat();\n  x168_seekerClampMax = in.ReadFloat();\n  x16c_missileBracketClampMin = in.ReadFloat();\n  x170_missileBracketClampMax = in.ReadFloat();\n  x174_innerBeamClampMin = in.ReadFloat();\n  x178_innerBeamClampMax = in.ReadFloat();\n  x17c_chargeGaugeClampMin = in.ReadFloat();\n  x180_chargeGaugeClampMax = in.ReadFloat();\n  x184_lockFireClampMin = in.ReadFloat();\n  x188_lockFireClampMax = in.ReadFloat();\n  x18c_lockDaggerClampMin = in.ReadFloat();\n  x190_lockDaggerClampMax = in.ReadFloat();\n  x194_grappleSelectScale = in.ReadFloat();\n  x198_grappleScale = in.ReadFloat();\n  x19c_grappleClampMin = in.ReadFloat();\n  x1a0_grappleClampMax = in.ReadFloat();\n  x1a4_grapplePointSelectColor = in.Get<zeus::CColor>();\n  x1a8_grapplePointColor = in.Get<zeus::CColor>();\n  x1ac_lockedGrapplePointSelectColor = in.Get<zeus::CColor>();\n  x1b0_grappleMinClampScale = in.ReadFloat();\n  x1b4_chargeGaugePulseColorHigh = in.Get<zeus::CColor>();\n  x1b8_fullChargeFadeDuration = in.ReadFloat();\n  x1bc_orbitPointColor = in.Get<zeus::CColor>();\n  x1c0_crosshairsColor = in.Get<zeus::CColor>();\n  x1c4_crosshairsScaleDur = in.ReadFloat();\n  x1c8_drawOrbitPoint = in.ReadBool();\n  x1cc_chargeGaugePulseColorLow = in.Get<zeus::CColor>();\n  x1d0_chargeGaugePulsePeriod = in.ReadFloat();\n  x1d4_ = in.Get<zeus::CColor>();\n  x1d8_ = in.Get<zeus::CColor>();\n  x1dc_ = in.Get<zeus::CColor>();\n  x1e0_ = in.ReadFloat();\n  x1e4_ = in.ReadFloat();\n  x1e8_ = in.ReadFloat();\n  x1ec_ = in.ReadFloat();\n  x1f0_ = in.ReadFloat();\n  x1f4_ = in.ReadFloat();\n  x1f8_ = in.ReadFloat();\n  x1fc_ = in.ReadFloat();\n  x200_ = in.ReadFloat();\n  x204_ = in.ReadFloat();\n  x208_ = in.ReadFloat();\n  x20c_reticuleClampMin = in.ReadFloat();\n  x210_reticuleClampMax = in.ReadFloat();\n  x214_xrayRetRingColor = in.Get<zeus::CColor>();\n  x218_reticuleScale = in.ReadFloat();\n  x21c_scanTargetClampMin = in.ReadFloat();\n  x220_scanTargetClampMax = in.ReadFloat();\n  x224_angularLagSpeed = in.ReadFloat();\n\n  x124_chargeTickAnglePitch = -zeus::degToRad(x124_chargeTickAnglePitch);\n  x140_lockDaggerAngle0 = zeus::degToRad(x140_lockDaggerAngle0);\n  x144_lockDaggerAngle1 = zeus::degToRad(x144_lockDaggerAngle1);\n  x148_lockDaggerAngle2 = zeus::degToRad(x148_lockDaggerAngle2);\n  x208_ = zeus::degToRad(x208_);\n  for (int i = 0; i < 4; ++i) {\n    for (float& f : xf8_outerBeamSquareAngles[i]) {\n      f = zeus::degToRad(f);\n    }\n  }\n  for (int i = 0; i < 4; ++i) {\n    x108_chargeGaugeAngles[i] = zeus::degToRad(x108_chargeGaugeAngles[i]);\n  }\n}\n\n} // namespace metaforce::MP1"
  },
  {
    "path": "Runtime/MP1/Tweaks/CTweakTargeting.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Tweaks/ITweakTargeting.hpp\"\n#include \"Runtime/rstl.hpp\"\n\nnamespace metaforce::MP1 {\nstruct CTweakTargeting final : public Tweaks::ITweakTargeting {\n  u32 x4_targetRadiusMode{};\n  float x8_currLockOnExitDuration{};\n  float xc_currLockOnEnterDuration{};\n  float x10_currLockOnSwitchDuration{};\n  float x14_lockConfirmScale{};\n  float x18_nextLockOnEnterDuration{};\n  float x1c_nextLockOnExitDuration{};\n  float x20_nextLockOnSwitchDuration{};\n  float x24_seekerScale{};\n  float x28_seekerAngleSpeed{};\n  float x2c_xrayRetAngleSpeed{};\n  zeus::CVector3f x30_;\n  zeus::CVector3f x3c_;\n  float x48_{};\n  float x4c_{};\n  float x50_orbitPointZOffset{};\n  float x54_orbitPointInTime{};\n  float x58_orbitPointOutTime{};\n  float x5c_{};\n  zeus::CVector3f x60_;\n  zeus::CVector3f x6c_;\n  zeus::CVector3f x78_;\n  zeus::CVector3f x84_;\n  float x90_{};\n  float x94_{};\n  float x98_{};\n  float x9c_{};\n  float xa0_{};\n  float xa4_{};\n  float xa8_{};\n  float xac_{};\n  zeus::CColor xb0_thermalReticuleColor;\n  float xb4_targetFlowerScale{};\n  zeus::CColor xb8_targetFlowerColor;\n  float xbc_missileBracketDuration{};\n  float xc0_missileBracketScaleStart{};\n  float xc4_missileBracketScaleEnd{};\n  float xc8_missileBracketScaleDuration{};\n  zeus::CColor xcc_missileBracketColor;\n  float xd0_LockonDuration{};\n  float xd4_innerBeamScale{};\n  zeus::CColor xd8_innerBeamColorPower;\n  zeus::CColor xdc_innerBeamColorIce;\n  zeus::CColor xe0_innerBeamColorWave;\n  zeus::CColor xe4_innerBeamColorPlasma;\n  float xe8_chargeGaugeOvershootOffset{};\n  float xec_chargeGaugeOvershootDuration{};\n  float xf0_outerBeamSquaresScale{};\n  zeus::CColor xf4_outerBeamSquareColor;\n  rstl::reserved_vector<rstl::reserved_vector<float, 9>, 4> xf8_outerBeamSquareAngles;\n  rstl::reserved_vector<float, 4> x108_chargeGaugeAngles{};\n  float x118_chargeGaugeScale{};\n  zeus::CColor x11c_chargeGaugeNonFullColor;\n  u32 x120_chargeTickCount{};\n  float x124_chargeTickAnglePitch{};\n  float x128_lockFireScale{};\n  float x12c_lockFireDuration{};\n  zeus::CColor x130_lockFireColor;\n  float x134_lockDaggerScaleStart{};\n  float x138_lockDaggerScaleEnd{};\n  zeus::CColor x13c_lockDaggerColor;\n  float x140_lockDaggerAngle0{};\n  float x144_lockDaggerAngle1{};\n  float x148_lockDaggerAngle2{};\n  zeus::CColor x14c_lockConfirmColor;\n  zeus::CColor x150_seekerColor;\n  float x154_lockConfirmClampMin{};\n  float x158_lockConfirmClampMax{};\n  float x15c_targetFlowerClampMin{};\n  float x160_targetFlowerClampMax{};\n  float x164_seekerClampMin{};\n  float x168_seekerClampMax{};\n  float x16c_missileBracketClampMin{};\n  float x170_missileBracketClampMax{};\n  float x174_innerBeamClampMin{};\n  float x178_innerBeamClampMax{};\n  float x17c_chargeGaugeClampMin{};\n  float x180_chargeGaugeClampMax{};\n  float x184_lockFireClampMin{};\n  float x188_lockFireClampMax{};\n  float x18c_lockDaggerClampMin{};\n  float x190_lockDaggerClampMax{};\n  float x194_grappleSelectScale{};\n  float x198_grappleScale{};\n  float x19c_grappleClampMin{};\n  float x1a0_grappleClampMax{};\n  zeus::CColor x1a4_grapplePointSelectColor;\n  zeus::CColor x1a8_grapplePointColor;\n  zeus::CColor x1ac_lockedGrapplePointSelectColor;\n  float x1b0_grappleMinClampScale{};\n  zeus::CColor x1b4_chargeGaugePulseColorHigh;\n  float x1b8_fullChargeFadeDuration{};\n  zeus::CColor x1bc_orbitPointColor;\n  zeus::CColor x1c0_crosshairsColor;\n  float x1c4_crosshairsScaleDur{};\n  bool x1c8_drawOrbitPoint{};\n  zeus::CColor x1cc_chargeGaugePulseColorLow;\n  float x1d0_chargeGaugePulsePeriod{};\n  zeus::CColor x1d4_;\n  zeus::CColor x1d8_;\n  zeus::CColor x1dc_;\n  float x1e0_{};\n  float x1e4_{};\n  float x1e8_{};\n  float x1ec_{};\n  float x1f0_{};\n  float x1f4_{};\n  float x1f8_{};\n  float x1fc_{};\n  float x200_{};\n  float x204_{};\n  float x208_{};\n  float x20c_reticuleClampMin{};\n  float x210_reticuleClampMax{};\n  zeus::CColor x214_xrayRetRingColor;\n  float x218_reticuleScale{};\n  float x21c_scanTargetClampMin{};\n  float x220_scanTargetClampMax{};\n  float x224_angularLagSpeed{};\n\n  bool x224_ = true;\n  bool x225_ = false;\n  bool x226_ = true;\n  bool x227_ = true;\n  bool x22c_ = true;\n  bool x22d_ = false;\n  bool x22e_ = true;\n  bool x22f_ = true;\n  bool x234_ = true;\n  bool x235_ = false;\n  bool x236_ = true;\n  bool x237_ = true;\n  zeus::CVector3f x23c_ = zeus::skZero3f;\n\n  float x2c8_ = 0.25f;\n  float x2cc_ = 0.35f;\n  zeus::CColor x2d0_ = (zeus::Comp32)0xb6e6ffff;\n  float x2d4_ = 0.39215687f;\n  zeus::CColor x2d8_ = (zeus::Comp32)0xa82a00ff;\n  float x2dc_ = 0.78431374f;\n  zeus::CVector3f x2e0_ = zeus::CVector3f(0.f, 0.f, 0.46f);\n  float x2ec_ = 0.25f;\n  float x2f0_ = 0.25f;\n  float x2f4_ = 120.f;\n  float x2f8_ = 0.25f;\n  float x2fc_ = 3.5f;\n  float x300_ = 0.35f;\n  zeus::CColor x304_ = (zeus::Comp32)0xa82a00ff;\n  float x308_ = 0.78431374f;\n  zeus::CColor x30c_ = (zeus::Comp32)0x89d6ffff;\n  float x310_ = 0.5019608f;\n  float x314_ = 11.25f;\n  float x318_ = 0.25f;\n  float x31c_ = 0.125f;\n  zeus::CColor x320_ = (zeus::Comp32)0xffca28ff;\n  float x324_ = 0.78431374f;\n  zeus::CColor x328_ = (zeus::Comp32)0x89d6ffff;\n  float x32c_ = 0.19607843f;\n  float x330_ = 0.f;\n  float x334_ = 0.25f;\n  float x338_ = 3.f;\n  float x33c_ = 0.25f;\n  float x340_ = 0.25f;\n  float x344_ = 0.25f;\n  float x348_ = 0.25f;\n  float x34c_ = 45.f;\n  float x350_ = 0.5f;\n  float x354_ = 0.65f;\n  float x358_ = 1.5f;\n  float x35c_ = 0.18f;\n  float x360_ = 0.15f;\n  float x364_ = 0.25f;\n  zeus::CColor x368_ = static_cast<zeus::Comp32>(0x56c1fb9f);\n  zeus::CColor x36c_ = static_cast<zeus::Comp32>(0x49c3f6a0);\n  zeus::CColor x370_ = static_cast<zeus::Comp32>(0x49c3f631);\n  zeus::CColor x374_ = static_cast<zeus::Comp32>(0xff8930ff);\n  zeus::CColor x378_ = static_cast<zeus::Comp32>(0xff2f28ff);\n  zeus::CColor x37c_ = static_cast<zeus::Comp32>(0x93e9ffff);\n  zeus::CColor x380_ = static_cast<zeus::Comp32>(0xff6b60ff);\n\n  CTweakTargeting() = default;\n  CTweakTargeting(CInputStream& r);\n  u32 GetTargetRadiusMode() const override { return x4_targetRadiusMode; }\n  float GetCurrLockOnExitDuration() const override { return x8_currLockOnExitDuration; }\n  float GetCurrLockOnEnterDuration() const override { return xc_currLockOnEnterDuration; }\n  float GetCurrLockOnSwitchDuration() const override { return x10_currLockOnSwitchDuration; }\n  float GetLockConfirmScale() const override { return x14_lockConfirmScale; }\n  float GetNextLockOnEnterDuration() const override { return x18_nextLockOnEnterDuration; }\n  float GetNextLockOnExitDuration() const override { return x1c_nextLockOnExitDuration; }\n  float GetNextLockOnSwitchDuration() const override { return x20_nextLockOnSwitchDuration; }\n  float GetSeekerScale() const override { return x24_seekerScale; }\n  float GetSeekerAngleSpeed() const override { return x28_seekerAngleSpeed; }\n  float GetXRayRetAngleSpeed() const override { return x2c_xrayRetAngleSpeed; }\n  float GetOrbitPointZOffset() const override { return x50_orbitPointZOffset; }\n  float GetOrbitPointInTime() const override { return x54_orbitPointInTime; }\n  float GetOrbitPointOutTime() const override { return x58_orbitPointOutTime; }\n  const zeus::CColor& GetThermalReticuleColor() const override { return xb0_thermalReticuleColor; }\n  float GetTargetFlowerScale() const override { return xb4_targetFlowerScale; }\n  const zeus::CColor& GetTargetFlowerColor() const override { return xb8_targetFlowerColor; }\n  float GetMissileBracketDuration() const override { return xbc_missileBracketDuration; }\n  float GetMissileBracketScaleStart() const override { return xc0_missileBracketScaleStart; }\n  float GetMissileBracketScaleEnd() const override { return xc4_missileBracketScaleEnd; }\n  float GetMissileBracketScaleDuration() const override { return xc8_missileBracketScaleDuration; }\n  const zeus::CColor& GetMissileBracketColor() const override { return xcc_missileBracketColor; }\n  float GetChargeGaugeOvershootOffset() const override { return xe8_chargeGaugeOvershootOffset; }\n  float GetChargeGaugeOvershootDuration() const override { return xec_chargeGaugeOvershootDuration; }\n  float GetOuterBeamSquaresScale() const override { return xf0_outerBeamSquaresScale; }\n  const zeus::CColor& GetOuterBeamSquareColor() const override { return xf4_outerBeamSquareColor; }\n  float GetLockonDuration() const override { return xd0_LockonDuration; }\n  float GetInnerBeamScale() const override { return xd4_innerBeamScale; }\n  const zeus::CColor& GetInnerBeamColorPower() const override { return xd8_innerBeamColorPower; }\n  const zeus::CColor& GetInnerBeamColorIce() const override { return xdc_innerBeamColorIce; }\n  const zeus::CColor& GetInnerBeamColorWave() const override { return xe0_innerBeamColorWave; }\n  const zeus::CColor& GetInnerBeamColorPlasma() const override { return xe4_innerBeamColorPlasma; }\n  const float* GetOuterBeamSquareAngles(int i) const override { return xf8_outerBeamSquareAngles[i].data(); }\n  float GetChargeGaugeAngle(int i) const override { return x108_chargeGaugeAngles[i]; }\n  float GetChargeGaugeScale() const override { return x118_chargeGaugeScale; }\n  const zeus::CColor& GetChargeGaugeNonFullColor() const override { return x11c_chargeGaugeNonFullColor; }\n  u32 GetChargeTickCount() const override { return x120_chargeTickCount; }\n  float GetChargeTickAnglePitch() const override { return x124_chargeTickAnglePitch; }\n  float GetLockFireScale() const override { return x128_lockFireScale; }\n  float GetLockFireDuration() const override { return x12c_lockFireDuration; }\n  const zeus::CColor& GetLockFireColor() const override { return x130_lockFireColor; }\n  float GetLockDaggerScaleStart() const override { return x134_lockDaggerScaleStart; }\n  float GetLockDaggerScaleEnd() const override { return x138_lockDaggerScaleEnd; }\n  const zeus::CColor& GetLockDaggerColor() const override { return x13c_lockDaggerColor; }\n  float GetLockDaggerAngle0() const override { return x140_lockDaggerAngle0; }\n  float GetLockDaggerAngle1() const override { return x144_lockDaggerAngle1; }\n  float GetLockDaggerAngle2() const override { return x148_lockDaggerAngle2; }\n  const zeus::CColor& GetLockConfirmColor() const override { return x14c_lockConfirmColor; }\n  const zeus::CColor& GetSeekerColor() const override { return x150_seekerColor; }\n  float GetLockConfirmClampMin() const override { return x154_lockConfirmClampMin; }\n  float GetLockConfirmClampMax() const override { return x158_lockConfirmClampMax; }\n  float GetTargetFlowerClampMin() const override { return x15c_targetFlowerClampMin; }\n  float GetTargetFlowerClampMax() const override { return x160_targetFlowerClampMax; }\n  float GetSeekerClampMin() const override { return x164_seekerClampMin; }\n  float GetSeekerClampMax() const override { return x168_seekerClampMax; }\n  float GetMissileBracketClampMin() const override { return x16c_missileBracketClampMin; }\n  float GetMissileBracketClampMax() const override { return x170_missileBracketClampMax; }\n  float GetInnerBeamClampMin() const override { return x174_innerBeamClampMin; }\n  float GetInnerBeamClampMax() const override { return x178_innerBeamClampMax; }\n  float GetChargeGaugeClampMin() const override { return x17c_chargeGaugeClampMin; }\n  float GetChargeGaugeClampMax() const override { return x180_chargeGaugeClampMax; }\n  float GetLockFireClampMin() const override { return x184_lockFireClampMin; }\n  float GetLockFireClampMax() const override { return x188_lockFireClampMax; }\n  float GetLockDaggerClampMin() const override { return x18c_lockDaggerClampMin; }\n  float GetLockDaggerClampMax() const override { return x190_lockDaggerClampMax; }\n  float GetGrappleSelectScale() const override { return x194_grappleSelectScale; }\n  float GetGrappleScale() const override { return x198_grappleScale; }\n  float GetGrappleClampMin() const override { return x19c_grappleClampMin; }\n  float GetGrappleClampMax() const override { return x1a0_grappleClampMax; }\n  const zeus::CColor& GetGrapplePointSelectColor() const override { return x1a4_grapplePointSelectColor; }\n  const zeus::CColor& GetGrapplePointColor() const override { return x1a8_grapplePointColor; }\n  const zeus::CColor& GetLockedGrapplePointSelectColor() const override { return x1ac_lockedGrapplePointSelectColor; }\n  float GetGrappleMinClampScale() const override { return x1b0_grappleMinClampScale; }\n  const zeus::CColor& GetChargeGaugePulseColorHigh() const override { return x1b4_chargeGaugePulseColorHigh; }\n  float GetFullChargeFadeDuration() const override { return x1b8_fullChargeFadeDuration; }\n  const zeus::CColor& GetOrbitPointColor() const override { return x1bc_orbitPointColor; }\n  const zeus::CColor& GetCrosshairsColor() const override { return x1c0_crosshairsColor; }\n  float GetCrosshairsScaleDuration() const override { return x1c4_crosshairsScaleDur; }\n  bool DrawOrbitPoint() const override { return x1c8_drawOrbitPoint; }\n  const zeus::CColor& GetChargeGaugePulseColorLow() const override { return x1cc_chargeGaugePulseColorLow; }\n  float GetChargeGaugePulsePeriod() const override { return x1d0_chargeGaugePulsePeriod; }\n  float GetReticuleClampMin() const override { return x20c_reticuleClampMin; }\n  float GetReticuleClampMax() const override { return x210_reticuleClampMax; }\n  const zeus::CColor& GetXRayRetRingColor() const override { return x214_xrayRetRingColor; }\n  float GetReticuleScale() const override { return x218_reticuleScale; }\n  float GetScanTargetClampMin() const override { return x21c_scanTargetClampMin; }\n  float GetScanTargetClampMax() const override { return x220_scanTargetClampMax; }\n  float GetAngularLagSpeed() const override { return x224_angularLagSpeed; }\n};\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CAtomicAlpha.cpp",
    "content": "#include \"Runtime/MP1/World/CAtomicAlpha.hpp\"\n\n#include <array>\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Weapon/CPlayerGun.hpp\"\n#include \"Runtime/World/CGameArea.hpp\"\n#include \"Runtime/World/CPatternedInfo.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\nnamespace metaforce::MP1 {\nconstexpr std::array skBombLocators{\n    \"bomb1_LCTR\"sv,\n    \"bomb2_LCTR\"sv,\n    \"bomb3_LCTR\"sv,\n    \"bomb4_LCTR\"sv,\n};\n\nCAtomicAlpha::CAtomicAlpha(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                           CModelData&& mData, const CActorParameters& actParms, const CPatternedInfo& pInfo,\n                           CAssetId bombWeapon, const CDamageInfo& bombDamage, float bombDropDelay, float f2, float f3,\n                           CAssetId cmdl, bool invisible, bool b2)\n: CPatterned(ECharacter::AtomicAlpha, uid, name, EFlavorType::Zero, info, xf, std::move(mData), pInfo,\n             EMovementType::Flyer, EColliderType::One, EBodyType::Flyer, actParms, EKnockBackVariant::Medium)\n, x568_25_invisible(invisible)\n, x568_26_applyBeamAttraction(b2)\n, x56c_bombDropDelay(bombDropDelay)\n, x570_bombReappearDelay(f2)\n, x574_bombRappearTime(f3)\n, x580_pathFind(nullptr, 3, pInfo.GetPathfindingIndex(), 1.f, 1.f)\n, x668_bombProjectile(bombWeapon, bombDamage)\n, x690_bombModel(CStaticRes(cmdl, GetModelData()->GetScale())) {\n  x668_bombProjectile.Token().Lock();\n  for (u32 i = 0; i < skBombCount; ++i) {\n    x6dc_bombLocators.emplace_back(skBombLocators[i], pas::ELocomotionType(u32(pas::ELocomotionType::Internal10) + i));\n  }\n}\n\nvoid CAtomicAlpha::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  CPatterned::AcceptScriptMsg(msg, uid, mgr);\n  if (msg == EScriptObjectMessage::InitializedInArea) {\n    x580_pathFind.SetArea(mgr.GetWorld()->GetAreaAlways(GetAreaIdAlways())->GetPostConstructed()->x10bc_pathArea);\n  } else if (msg == EScriptObjectMessage::Registered) {\n    x450_bodyController->Activate(mgr);\n  } else if (msg == EScriptObjectMessage::AddSplashInhabitant) {\n    if (x400_25_alive)\n      x401_30_pendingDeath = true;\n  }\n}\n\nvoid CAtomicAlpha::Render(CStateManager& mgr) {\n  if (mgr.GetPlayerState()->GetActiveVisor(mgr) != CPlayerState::EPlayerVisor::XRay && x568_25_invisible)\n    return;\n\n  CPatterned::Render(mgr);\n  for (const SBomb& bomb : x6dc_bombLocators) {\n    zeus::CTransform locatorXf =\n        GetTransform() * GetScaledLocatorTransform(bomb.x0_locatorName) *\n        zeus::CTransform::Scale(\n            std::min(1.f, std::max(0.f, bomb.x14_scaleTime - x570_bombReappearDelay) / x570_bombReappearDelay));\n    CModelFlags flags{0, 0, 3, zeus::skWhite};\n    x690_bombModel.Render(mgr, locatorXf, x90_actorLights.get(), flags);\n  }\n}\n\nvoid CAtomicAlpha::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) {\n  if (mgr.GetPlayerState()->GetActiveVisor(mgr) != CPlayerState::EPlayerVisor::XRay && x568_25_invisible) {\n    return;\n  }\n  CPatterned::AddToRenderer(frustum, mgr);\n}\n\nvoid CAtomicAlpha::Think(float dt, CStateManager& mgr) {\n  CPatterned::Think(dt, mgr);\n  if (!GetActive())\n    return;\n\n  x578_bombTime += dt;\n\n  for (SBomb& bomb : x6dc_bombLocators) {\n    bomb.x14_scaleTime += dt;\n  }\n}\n\nvoid CAtomicAlpha::DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) {\n  if (type == EUserEventType::Projectile) {\n    zeus::CVector3f origin = GetLctrTransform(node.GetLocatorName()).origin;\n    zeus::CTransform xf = zeus::lookAt(origin, origin + zeus::skDown, zeus::skUp);\n    LaunchProjectile(xf, mgr, 4, EProjectileAttrib::None, false, {}, 0xFFFF, false, zeus::skOne3f);\n    x578_bombTime = 0.f;\n    x6dc_bombLocators[x57c_curBomb].x14_scaleTime = 0.f;\n    x57c_curBomb = (x57c_curBomb + 1) % x6dc_bombLocators.size();\n  } else\n    CPatterned::DoUserAnimEvent(mgr, node, type, dt);\n}\n\nbool CAtomicAlpha::Leash(CStateManager& mgr, float) {\n  return (mgr.GetPlayer().GetTranslation() - GetTranslation()).magSquared() >\n             x3cc_playerLeashRadius * x3cc_playerLeashRadius &&\n         x3d4_curPlayerLeashTime > x3d0_playerLeashTime;\n}\n\nbool CAtomicAlpha::AggressionCheck(CStateManager& mgr, float) {\n  if (!x568_26_applyBeamAttraction) {\n    return false;\n  }\n  float factor = 0.f;\n  const CPlayerGun* playerGun = mgr.GetPlayer().GetPlayerGun();\n  if (playerGun->IsCharging()) {\n    factor = playerGun->GetChargeBeamFactor();\n  }\n  return factor > 0.1f;\n}\n\nvoid CAtomicAlpha::CollidedWith(TUniqueId uid, const CCollisionInfoList& list, CStateManager& mgr) {\n  if (IsAlive()) {\n    if (TCastToConstPtr<CPlayer> pl = mgr.GetObjectById(uid)) {\n      if (x420_curDamageRemTime <= 0.f) {\n        mgr.GetPlayerState()->GetStaticInterference().AddSource(GetUniqueId(), 0.5f, 0.25f);\n        for (SBomb& bomb : x6dc_bombLocators) {\n          bomb.x14_scaleTime = 0.f;\n        }\n      }\n    }\n  }\n  CPatterned::CollidedWith(uid, list, mgr);\n}\n\nvoid CAtomicAlpha::Patrol(CStateManager& mgr, EStateMsg msg, float arg) {\n  CPatterned::Patrol(mgr, msg, arg);\n  if (msg == EStateMsg::Activate) {\n    x578_bombTime = 0.f;\n  } else if (msg == EStateMsg::Update) {\n    if (x568_24_inRange) {\n      if (x578_bombTime >= x56c_bombDropDelay &&\n          x6dc_bombLocators[0].x14_scaleTime > (x570_bombReappearDelay + x574_bombRappearTime)) {\n        x450_bodyController->SetLocomotionType(x6dc_bombLocators[x57c_curBomb].x10_locomotionType);\n      } else {\n        x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n      }\n      if (Leash(mgr, arg))\n        x568_24_inRange = false;\n    } else {\n      x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n      if (InMaxRange(mgr, arg))\n        x568_24_inRange = true;\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x568_24_inRange = false;\n  }\n}\n\nvoid CAtomicAlpha::Attack(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Internal8);\n  } else if (msg == EStateMsg::Update) {\n    zeus::CVector3f seekVec = x664_steeringBehaviors.Seek(*this, mgr.GetPlayer().GetEyePosition());\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(seekVec, {}, 1.f));\n  } else if (msg == EStateMsg::Deactivate) {\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n  }\n}\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CAtomicAlpha.hpp",
    "content": "#pragma once\n\n#include <string>\n\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Weapon/CProjectileInfo.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n#include \"Runtime/World/CPathFindSearch.hpp\"\n\nnamespace metaforce::MP1 {\nclass CAtomicAlpha : public CPatterned {\n  static constexpr u32 skBombCount = 4;\n  struct SBomb {\n    std::string x0_locatorName;\n    pas::ELocomotionType x10_locomotionType;\n    float x14_scaleTime = FLT_MAX;\n    SBomb(const std::string_view locator, pas::ELocomotionType locomotionType)\n    : x0_locatorName(locator), x10_locomotionType(locomotionType) {}\n  };\n  bool x568_24_inRange : 1 = false;\n  bool x568_25_invisible : 1;\n  bool x568_26_applyBeamAttraction : 1;\n  float x56c_bombDropDelay;\n  float x570_bombReappearDelay;\n  float x574_bombRappearTime;\n  float x578_bombTime = 0.f;\n  u32 x57c_curBomb = 0;\n  CPathFindSearch x580_pathFind;\n  CSteeringBehaviors x664_steeringBehaviors;\n  CProjectileInfo x668_bombProjectile;\n  CModelData x690_bombModel;\n  rstl::reserved_vector<SBomb, skBombCount> x6dc_bombLocators;\n\npublic:\n  DEFINE_PATTERNED(AtomicAlpha);\n\n  CAtomicAlpha(TUniqueId, std::string_view, const CEntityInfo&, const zeus::CTransform&, CModelData&&,\n               const CActorParameters&, const CPatternedInfo&, CAssetId, const CDamageInfo&, float, float, float,\n               CAssetId, bool, bool);\n\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void Render(CStateManager&) override;\n  void AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) override;\n  void Think(float, CStateManager&) override;\n  void DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) override;\n\n  CPathFindSearch* GetSearchPath() override { return &x580_pathFind; }\n\n  EWeaponCollisionResponseTypes GetCollisionResponseType(const zeus::CVector3f&, const zeus::CVector3f&,\n                                                         const CWeaponMode& wMode, EProjectileAttrib) const override {\n    return GetDamageVulnerability()->WeaponHits(wMode, false) ? EWeaponCollisionResponseTypes::AtomicAlpha\n                                                              : EWeaponCollisionResponseTypes::AtomicAlphaReflect;\n  }\n\n  bool Leash(CStateManager& mgr, float) override;\n  bool AggressionCheck(CStateManager&, float) override;\n  void CollidedWith(TUniqueId, const CCollisionInfoList&, CStateManager&) override;\n  void Patrol(CStateManager&, EStateMsg, float) override;\n  void Attack(CStateManager&, EStateMsg, float) override;\n\n  CProjectileInfo* GetProjectileInfo() override { return &x668_bombProjectile; }\n};\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CAtomicBeta.cpp",
    "content": "#include \"Runtime/MP1/World/CAtomicBeta.hpp\"\n\n#include <array>\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Particle/CWeaponDescription.hpp\"\n#include \"Runtime/Weapon/CElectricBeamProjectile.hpp\"\n#include \"Runtime/Weapon/CPlayerGun.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n\nnamespace metaforce::MP1 {\nconstexpr std::array skBombLocators{\n    \"bomb2_LCTR\"sv,\n    \"bomb3_LCTR\"sv,\n    \"bomb4_LCTR\"sv,\n};\n\nCAtomicBeta::CAtomicBeta(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                         CModelData&& mData, const CActorParameters& actParms, const CPatternedInfo& pInfo,\n                         CAssetId electricId, CAssetId weaponId, const CDamageInfo& dInfo, CAssetId particleId,\n                         float f1, float beamRadius, float f3, const CDamageVulnerability& dVuln, float f4, float f5,\n                         float f6, s16 sId1, s16 sId2, s16 sId3, float f7)\n: CPatterned(ECharacter::AtomicBeta, uid, name, EFlavorType::Zero, info, xf, std::move(mData), pInfo,\n             EMovementType::Flyer, EColliderType::One, EBodyType::RestrictedFlyer, actParms, EKnockBackVariant::Small)\n, x578_minSpeed(f5)\n, x57c_maxSpeed(f6)\n, x580_speedStep(f7)\n, x584_currentSpeed(x578_minSpeed)\n, x588_frozenDamage(dVuln)\n, x5f0_moveSpeed(f4)\n, x5f4_(xf.basis[1])\n, x600_electricWeapon(g_SimplePool->GetObj({SBIG('ELSC'), electricId}))\n, x608_(g_SimplePool->GetObj({SBIG('WPSC'), weaponId}))\n, x610_projectileDamage(dInfo)\n, x62c_beamParticle(particleId)\n, x630_beamFadeSpeed(f1)\n, x634_beamRadius(beamRadius)\n, x638_beamDamageInterval(f3)\n, x644_(CSfxManager::TranslateSFXID(sId1))\n, x646_(CSfxManager::TranslateSFXID(sId2))\n, x648_(CSfxManager::TranslateSFXID(sId3)) {\n  x460_knockBackController.SetAutoResetImpulse(false);\n  x460_knockBackController.SetEnableFreeze(false);\n  x460_knockBackController.SetX82_24(false);\n}\n\nvoid CAtomicBeta::CreateBeams(CStateManager& mgr) {\n  const SElectricBeamInfo beamInfo{x600_electricWeapon, 50.f, x634_beamRadius, 10.f, x62c_beamParticle,\n                                   x630_beamFadeSpeed, x638_beamDamageInterval};\n\n  for (size_t i = 0; i < kBombCount; ++i) {\n    const TUniqueId id = mgr.AllocateUniqueId();\n    x568_projectileIds.push_back(id);\n    mgr.AddObject(new CElectricBeamProjectile(x608_, EWeaponType::AI, beamInfo, {}, EMaterialTypes::Character,\n                                              x610_projectileDamage, id, GetAreaIdAlways(), GetUniqueId(),\n                                              EProjectileAttrib::None));\n  }\n}\n\nvoid CAtomicBeta::UpdateBeams(CStateManager& mgr, bool fireBeam) {\n  if (x574_beamFired == fireBeam) {\n    return;\n  }\n\n  for (size_t i = 0; i < x568_projectileIds.size(); ++i) {\n    // zeus::CTransform xf = GetTransform() * GetScaledLocatorTransform(skBombLocators[i]);\n    // zeus::CTransform newXf = zeus::lookAt(xf.origin, xf.origin + xf.basis[1], zeus::skUp);\n    if (auto* const proj = static_cast<CElectricBeamProjectile*>(mgr.ObjectById(x568_projectileIds[i]))) {\n      if (fireBeam) {\n        proj->Fire(GetTransform() * GetScaledLocatorTransform(skBombLocators[i]), mgr, false);\n      } else {\n        proj->ResetBeam(mgr, false);\n      }\n    }\n  }\n  x574_beamFired = fireBeam;\n}\n\nvoid CAtomicBeta::FreeBeams(CStateManager& mgr) {\n  for (TUniqueId uid : x568_projectileIds) {\n    mgr.FreeScriptObject(uid);\n  }\n  x568_projectileIds.clear();\n}\n\nvoid CAtomicBeta::UpdateOrCreateEmitter(CSfxHandle& handle, u16 id, const zeus::CVector3f& pos, float maxVol) {\n  if (handle)\n    CSfxManager::UpdateEmitter(handle, pos, {}, maxVol);\n  else\n    handle = CSfxManager::AddEmitter(id, pos, {}, maxVol, true, true, 0x7F, GetAreaIdAlways());\n}\n\nvoid CAtomicBeta::DestroyEmitter(CSfxHandle& handle) {\n  if (handle) {\n    CSfxManager::RemoveEmitter(handle);\n    handle.reset();\n  }\n}\n\nvoid CAtomicBeta::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  if (msg == EScriptObjectMessage::Registered) {\n    x450_bodyController->Activate(mgr);\n    CreateBeams(mgr);\n  } else if (msg == EScriptObjectMessage::Deactivate) {\n    UpdateBeams(mgr, false);\n    DestroyEmitter(x650_);\n    DestroyEmitter(x654_);\n    DestroyEmitter(x64c_);\n  } else if (msg == EScriptObjectMessage::Deleted) {\n    FreeBeams(mgr);\n  }\n  CPatterned::AcceptScriptMsg(msg, uid, mgr);\n}\n\nvoid CAtomicBeta::Think(float dt, CStateManager& mgr) {\n  CPatterned::Think(dt, mgr);\n  zeus::CVector3f movementVec = x450_bodyController->GetCommandMgr().GetMoveVector();\n  x450_bodyController->GetCommandMgr().ClearLocomotionCmds();\n  if (!movementVec.isZero())\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(movementVec, x5f4_, 1.f));\n\n  const float mag =\n      x63c_ * std::max(1.f - (mgr.GetPlayer().GetTranslation() - GetTranslation()).magSquared() / (x640_ * x640_), 0.f);\n  if (!zeus::close_enough(mag, 0.f))\n    mgr.GetPlayerState()->GetStaticInterference().AddSource(GetUniqueId(), mag, 0.5f);\n\n  if (InMaxRange(mgr, dt)) {\n    UpdateBeams(mgr, true);\n    UpdateOrCreateEmitter(x650_, x646_, GetTranslation(), 96 / 127.f);\n    UpdateOrCreateEmitter(x654_, x648_, GetTranslation(), 96 / 127.f);\n    DestroyEmitter(x64c_);\n  } else {\n    UpdateBeams(mgr, false);\n    DestroyEmitter(x650_);\n    DestroyEmitter(x654_);\n    UpdateOrCreateEmitter(x64c_, x644_, GetTranslation(), 96 / 127.f);\n  }\n\n  // was hardcoded to 3 (kBombCount), but that segfaults after FreeBeams\n  for (size_t i = 0; i < x568_projectileIds.size(); ++i) {\n    if (auto* const proj = static_cast<CElectricBeamProjectile*>(mgr.ObjectById(x568_projectileIds[i]))) {\n      if (!proj->GetActive()) {\n        continue;\n      }\n      const zeus::CTransform xf = GetTransform() * GetScaledLocatorTransform(skBombLocators[i]);\n      proj->UpdateFx(zeus::lookAt(xf.origin, xf.origin + xf.frontVector(), zeus::skUp), dt, mgr);\n    }\n  }\n\n  float speed = x580_speedStep * (dt * (IsPlayerBeamChargedEnough(mgr) ? 1.f : -1.f)) + x584_currentSpeed;\n  x584_currentSpeed = zeus::clamp(x578_minSpeed, speed, x57c_maxSpeed);\n  x3b4_speed = x584_currentSpeed;\n  x450_bodyController->SetRestrictedFlyerMoveSpeed(x5f0_moveSpeed * x584_currentSpeed);\n}\n\nconst CDamageVulnerability* CAtomicBeta::GetDamageVulnerability() const {\n  if (zeus::close_enough(x450_bodyController->GetPercentageFrozen(), 0.f))\n    return CPatterned::GetDamageVulnerability();\n  return &x588_frozenDamage;\n}\n\nvoid CAtomicBeta::Death(CStateManager& mgr, const zeus::CVector3f& dir, EScriptObjectState state) {\n  UpdateBeams(mgr, false);\n  DestroyEmitter(x650_);\n  DestroyEmitter(x654_);\n  DestroyEmitter(x64c_);\n  CPatterned::Death(mgr, dir, state);\n}\n\nbool CAtomicBeta::IsPlayerBeamChargedEnough(const CStateManager& mgr) {\n  const CPlayerGun* gun = mgr.GetPlayer().GetPlayerGun();\n  return (gun->IsCharging() ? gun->GetChargeBeamFactor() : 0.f) > 0.1f;\n}\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CAtomicBeta.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CWeaponDescription;\n}\n\nnamespace metaforce::MP1 {\nclass CAtomicBeta final : public CPatterned {\n  static constexpr u32 kBombCount = 3;\n  rstl::reserved_vector<TUniqueId, kBombCount> x568_projectileIds;\n  bool x574_beamFired = false;\n  float x578_minSpeed;\n  float x57c_maxSpeed;\n  float x580_speedStep;\n  float x584_currentSpeed;\n  CDamageVulnerability x588_frozenDamage;\n  float x5f0_moveSpeed;\n  zeus::CVector3f x5f4_;\n  TToken<CElectricDescription> x600_electricWeapon;\n  TToken<CWeaponDescription> x608_;\n  CDamageInfo x610_projectileDamage;\n  CAssetId x62c_beamParticle;\n  float x630_beamFadeSpeed;\n  float x634_beamRadius;\n  float x638_beamDamageInterval;\n  float x63c_ = 1.f;\n  float x640_ = 10.f;\n  u16 x644_;\n  u16 x646_;\n  u16 x648_;\n  CSfxHandle x64c_;\n  CSfxHandle x650_;\n  CSfxHandle x654_;\n\n  void CreateBeams(CStateManager&);\n  void UpdateBeams(CStateManager&, bool);\n  void FreeBeams(CStateManager&);\n  void UpdateOrCreateEmitter(CSfxHandle&, u16, const zeus::CVector3f&, float);\n  void DestroyEmitter(CSfxHandle&);\n\npublic:\n  DEFINE_PATTERNED(AtomicBeta);\n  CAtomicBeta(TUniqueId, std::string_view, const CEntityInfo&, const zeus::CTransform&, CModelData&&,\n              const CActorParameters&, const CPatternedInfo&, CAssetId, CAssetId, const CDamageInfo&, CAssetId, float,\n              float, float, const CDamageVulnerability&, float, float, float, s16, s16, s16, float);\n\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n\n  void Think(float, CStateManager&) override;\n  const CDamageVulnerability* GetDamageVulnerability() const override;\n  EWeaponCollisionResponseTypes GetCollisionResponseType(const zeus::CVector3f&, const zeus::CVector3f&,\n                                                         const CWeaponMode& mode, EProjectileAttrib) const override {\n    return GetDamageVulnerability()->WeaponHits(mode, false) ? EWeaponCollisionResponseTypes::AtomicBeta\n                                                             : EWeaponCollisionResponseTypes::AtomicBetaReflect;\n  }\n  void Death(CStateManager&, const zeus::CVector3f&, EScriptObjectState) override;\n  static bool IsPlayerBeamChargedEnough(const CStateManager& mgr);\n};\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CBabygoth.cpp",
    "content": "#include \"Runtime/MP1/World/CBabygoth.hpp\"\n\n#include <array>\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Character/CPASAnimParmData.hpp\"\n#include \"Runtime/Collision/CCollisionActor.hpp\"\n#include \"Runtime/Collision/CCollisionActorManager.hpp\"\n#include \"Runtime/Graphics/CSkinnedModel.hpp\"\n#include \"Runtime/Particle/CWeaponDescription.hpp\"\n#include \"Runtime/Weapon/CFlameInfo.hpp\"\n#include \"Runtime/Weapon/CFlameThrower.hpp\"\n#include \"Runtime/Weapon/CWeapon.hpp\"\n#include \"Runtime/World/CExplosion.hpp\"\n#include \"Runtime/World/CGameArea.hpp\"\n#include \"Runtime/World/CPatternedInfo.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CTeamAiMgr.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce::MP1 {\nconstexpr std::string_view skpMouthDamageJoint = \"LCTR_SHEMOUTH\"sv;\n\nconstexpr std::array<SSphereJointInfo, 5> skSphereJointList{{\n    {\"L_knee\", 1.2f},\n    {\"R_knee\", 1.2f},\n    {\"LCTR_SHEMOUTH\", 1.7f},\n    {\"Pelvis\", 1.2f},\n    {\"butt_LCTR\", 0.9f},\n}};\n\nCBabygothData::CBabygothData(CInputStream& in)\n: x0_fireballAttackTime(in.ReadFloat())\n, x4_fireballAttackTimeVariance(in.ReadFloat())\n, x8_fireballWeapon(in)\n, xc_fireballDamage(in)\n, x28_attackContactDamage(in)\n, x44_fireBreathWeapon(in)\n, x48_fireBreathRes(in)\n, x4c_fireBreathDamage(in)\n, x68_mouthVulnerabilities(in)\n, xd0_shellVulnerabilities(in)\n, x138_noShellModel(in)\n, x13c_noShellSkin(in)\n, x140_shellHitPoints(in.ReadFloat())\n, x144_shellCrackSfx(CSfxManager::TranslateSFXID(in.ReadLong()))\n, x148_intermediateCrackParticle(in)\n, x14c_crackOneParticle(in)\n, x150_crackTwoParticle(in)\n, x154_destroyShellParticle(in)\n, x158_crackOneSfx(CSfxManager::TranslateSFXID(in.ReadLong()))\n, x15a_crackTwoSfx(CSfxManager::TranslateSFXID(in.ReadLong()))\n, x15c_destroyShellSfx(CSfxManager::TranslateSFXID(in.ReadLong()))\n, x160_timeUntilAttack(in.ReadFloat())\n, x164_attackCooldownTime(in.ReadFloat())\n, x168_interestTime(in.ReadFloat())\n, x16c_flamePlayerSteamTxtr(in)\n, x170_flamePlayerHitSfx(CSfxManager::TranslateSFXID(in.ReadLong()))\n, x174_flamePlayerIceTxtr(in) {}\n\nCBabygoth::CBabygoth(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                     CModelData&& mData, const CPatternedInfo& pInfo, const CActorParameters& actParms,\n                     const CBabygothData& babyData)\n: CPatterned(ECharacter::Babygoth, uid, name, EFlavorType::Zero, info, xf, std::move(mData), pInfo,\n             EMovementType::Ground, EColliderType::One, EBodyType::BiPedal, actParms, EKnockBackVariant::Medium)\n, x570_babyData(babyData)\n, x6ec_pathSearch(nullptr, 1, pInfo.GetPathfindingIndex(), 1.f, 1.f)\n, x7d0_approachPathSearch(nullptr, 1, pInfo.GetPathfindingIndex(), 1.f, 1.f)\n, x8d0_initialSpeed(x3b4_speed)\n, x8f0_boneTracking(*GetModelData()->GetAnimationData(), \"Head_1\"sv, zeus::degToRad(80.f), zeus::degToRad(180.f),\n                    EBoneTrackingFlags::None)\n, x930_aabox(GetBoundingBox(), GetMaterialList())\n, x958_iceProjectile(babyData.x8_fireballWeapon, babyData.xc_fireballDamage)\n, x984_flameThrowerDesc(babyData.x44_fireBreathWeapon.IsValid()\n                            ? g_SimplePool->GetObj({SBIG('WPSC'), babyData.x44_fireBreathWeapon})\n                            : g_SimplePool->GetObj(\"FlameThrower\"sv))\n, x98c_dVuln(pInfo.GetDamageVulnerability())\n, xa00_shellHitPoints(babyData.GetShellHitPoints()) {\n  TLockedToken<CModel> model = g_SimplePool->GetObj({SBIG('CMDL'), babyData.x138_noShellModel});\n  TLockedToken<CSkinRules> skin = g_SimplePool->GetObj({SBIG('CSKR'), babyData.x13c_noShellSkin});\n  xa08_noShellModel =\n      CToken(TObjOwnerDerivedFromIObj<CSkinnedModel>::GetNewDerivedObject(std::make_unique<CSkinnedModel>(\n          model, skin, x64_modelData->GetAnimationData()->GetModelData()->GetLayoutInfo())));\n  xa14_crackOneParticle = g_SimplePool->GetObj({SBIG('PART'), babyData.x14c_crackOneParticle});\n  xa20_crackTwoParticle = g_SimplePool->GetObj({SBIG('PART'), babyData.x150_crackTwoParticle});\n  xa2c_destroyShellParticle = g_SimplePool->GetObj({SBIG('PART'), babyData.x154_destroyShellParticle});\n  if (x570_babyData.x148_intermediateCrackParticle.IsValid())\n    xa38_intermediateCrackParticle = g_SimplePool->GetObj({SBIG('PART'), babyData.x148_intermediateCrackParticle});\n  x958_iceProjectile.Token().Lock();\n  UpdateTouchBounds();\n  x460_knockBackController.SetEnableFreeze(false);\n  x460_knockBackController.SetAutoResetImpulse(true);\n  x460_knockBackController.SetEnableShock(true);\n  x460_knockBackController.SetEnableExplodeDeath(true);\n  x8d4_stepBackwardDist = GetAnimationDistance(CPASAnimParmData(pas::EAnimationState::Step, CPASAnimParm::FromEnum(1),\n                                                                CPASAnimParm::FromEnum(0))) *\n                          GetModelData()->GetScale().y(); // B_backward_sheegoth\n  xa08_noShellModel->SetLayoutInfo(GetModelData()->GetAnimationData()->GetModelData()->GetLayoutInfo());\n  MakeThermalColdAndHot();\n}\n\nvoid CBabygoth::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  switch (msg) {\n  case EScriptObjectMessage::Registered: {\n    if (!HasPatrolPath(mgr, 0.f))\n      x450_bodyController->SetLocomotionType(pas::ELocomotionType::Crouch);\n    x450_bodyController->Activate(mgr);\n    SetupCollisionManager(mgr);\n    CreateFlameThrower(mgr);\n    const CBodyStateInfo& bStateInfo = x450_bodyController->GetBodyStateInfo();\n    float maxSpeed = bStateInfo.GetMaxSpeed();\n    x450_bodyController->GetCommandMgr().SetSteeringSpeedRange(\n        0.f, maxSpeed <= 0.f ? 1.f : bStateInfo.GetLocomotionSpeed(pas::ELocomotionAnim::Walk) / maxSpeed);\n    x9f4_mouthLocator = GetModelData()->GetAnimationData()->GetLocatorSegId(skpMouthDamageJoint);\n    break;\n  }\n  case EScriptObjectMessage::Activate:\n    x928_colActMgr->SetActive(mgr, true);\n    break;\n  case EScriptObjectMessage::Deactivate: {\n    x928_colActMgr->SetActive(mgr, false);\n    xa49_29_objectSpaceCollision = false;\n    RemoveFromTeam(mgr);\n    break;\n  }\n  case EScriptObjectMessage::Deleted: {\n    x928_colActMgr->Destroy(mgr);\n    if (x980_flameThrower != kInvalidUniqueId) {\n      mgr.FreeScriptObject(x980_flameThrower);\n      x980_flameThrower = kInvalidUniqueId;\n    }\n    RemoveFromTeam(mgr);\n    break;\n  }\n  case EScriptObjectMessage::Falling: {\n    if (!x450_bodyController->IsFrozen()) {\n      x150_momentum = {0.f, 0.f, -(GetGravityConstant() * GetMass())};\n      x328_27_onGround = false;\n      RemoveMaterial(EMaterialTypes::GroundCollider, mgr);\n    }\n    return;\n  }\n  case EScriptObjectMessage::OnFloor: {\n    x328_27_onGround = true;\n    x150_momentum.zeroOut();\n    AddMaterial(EMaterialTypes::GroundCollider, mgr);\n    return;\n  }\n  case EScriptObjectMessage::Alert: {\n    xa48_24_isAlert = true;\n    break;\n  }\n  case EScriptObjectMessage::InitializedInArea: {\n    x6ec_pathSearch.SetArea(mgr.GetWorld()->GetAreaAlways(GetAreaIdAlways())->GetPostConstructed()->x10bc_pathArea);\n    x7d0_approachPathSearch.SetArea(\n        mgr.GetWorld()->GetAreaAlways(GetAreaIdAlways())->GetPostConstructed()->x10bc_pathArea);\n    if (x6e8_teamMgr == kInvalidUniqueId)\n      x6e8_teamMgr = CTeamAiMgr::GetTeamAiMgr(*this, mgr);\n    break;\n  }\n  case EScriptObjectMessage::Touched: {\n    ApplyContactDamage(uid, mgr);\n    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(uid))\n      if (TCastToConstPtr<CWeapon> wp = mgr.GetObjectById(colAct->GetLastTouchedObject()))\n        if (wp->GetOwnerId() == mgr.GetPlayer().GetUniqueId())\n          xa48_24_isAlert = true;\n    break;\n  }\n  case EScriptObjectMessage::Damage: {\n    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(uid)) {\n      if (TCastToConstPtr<CWeapon> wp = mgr.GetObjectById(colAct->GetLastTouchedObject())) {\n        if (wp->GetOwnerId() == mgr.GetPlayer().GetUniqueId()) {\n          if (IsMouthCollisionActor(uid)) {\n            TakeDamage({}, 0.f);\n          } else if (IsShell(uid)) {\n            TakeDamage({}, 0.f);\n            if (x56c_shellState != EShellState::Destroyed && x56c_shellState != EShellState::Default) {\n              zeus::CTransform xf = wp->GetTransform();\n              xf.rotateLocalZ(zeus::degToRad(180.f));\n              CrackShell(mgr, xa38_intermediateCrackParticle, xf, x570_babyData.GetShellCrackSfx(), false);\n            }\n          }\n          KnockBack(GetTransform().frontVector(), mgr, wp->GetDamageInfo(), EKnockBackType::Direct, false,\n                    wp->GetDamageInfo().GetKnockBackPower());\n        }\n      }\n      xa48_24_isAlert = true;\n      x8e8_interestTimer = 0.f;\n      mgr.InformListeners(GetTranslation(), EListenNoiseType::PlayerFire);\n    } else\n      ApplyDamage(mgr, uid);\n    x400_24_hitByPlayerProjectile = true;\n    break;\n  }\n  case EScriptObjectMessage::InvulnDamage: {\n    mgr.InformListeners(GetTranslation(), EListenNoiseType::PlayerFire);\n    x400_24_hitByPlayerProjectile = true;\n    xa48_24_isAlert = true;\n    x8e8_interestTimer = 0.f;\n    if (!TCastToPtr<CCollisionActor>(mgr.ObjectById(uid)))\n      ApplyDamage(mgr, uid);\n    break;\n  }\n  case EScriptObjectMessage::SuspendedMove: {\n    if (x928_colActMgr)\n      x928_colActMgr->SetMovable(mgr, false);\n    break;\n  }\n  default:\n    break;\n  }\n  CPatterned::AcceptScriptMsg(msg, uid, mgr);\n}\n\nvoid CBabygoth::Think(float dt, CStateManager& mgr) {\n  if (!GetActive())\n    return;\n\n  AvoidPlayerCollision(dt, mgr);\n  if (xa49_26_readyForTeam) {\n    if (!CTeamAiMgr::GetTeamAiRole(mgr, x6e8_teamMgr, GetUniqueId()))\n      AddToTeam(mgr);\n  }\n\n  CPatterned::Think(dt, mgr);\n  if (x450_bodyController->IsElectrocuting())\n    x8f0_boneTracking.SetActive(false);\n  UpdateTimers(dt);\n  GetModelData()->GetAnimationData()->PreRender();\n  x8f0_boneTracking.Update(dt);\n  x8f0_boneTracking.PreRender(mgr, *GetModelData()->GetAnimationData(), GetTransform(), GetModelData()->GetScale(),\n                              *x450_bodyController);\n  x928_colActMgr->Update(dt, mgr, CCollisionActorManager::EUpdateOptions(!xa49_29_objectSpaceCollision));\n  xa49_29_objectSpaceCollision = true;\n  UpdateHealth(mgr);\n  UpdateParticleEffects(dt, mgr);\n  TryToGetUp(mgr);\n  CheckShouldWakeUp(mgr, dt);\n  if (!x400_25_alive && x450_bodyController->GetBodyStateInfo().GetCurrentState()->IsDying())\n    SetProjectilePasshtrough(mgr);\n}\n\nvoid CBabygoth::DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) {\n  switch (type) {\n  case EUserEventType::Projectile: {\n    zeus::CTransform xf = GetLctrTransform(node.GetLocatorName());\n    zeus::CVector3f plAimPos = mgr.GetPlayer().GetAimPosition(mgr, 0.f);\n    zeus::CVector3f pos = GetProjectileInfo()->PredictInterceptPos(xf.origin, plAimPos, mgr.GetPlayer(), false, dt);\n    zeus::CVector3f delta = pos - xf.origin;\n    if (zeus::CVector3f::getAngleDiff(GetTransform().basis[1], delta) > zeus::degToRad(30.f)) {\n      if (delta.canBeNormalized()) {\n        pos = zeus::CVector3f::slerp(GetTransform().basis[1], delta.normalized(), zeus::degToRad(30.f)) *\n                  delta.magnitude() +\n              xf.origin;\n      } else {\n        pos = xf.basis[1] * delta.magnitude() + xf.origin;\n      }\n    }\n    LaunchProjectile(zeus::lookAt(xf.origin, pos), mgr, 4, EProjectileAttrib::None, false, {}, 0xffff, false,\n                     zeus::skOne3f);\n    return;\n  }\n  case EUserEventType::DamageOn: {\n    if (xa48_26_inProjectileAttack) {\n      if (CFlameThrower* flame = static_cast<CFlameThrower*>(mgr.ObjectById(x980_flameThrower)))\n        flame->Fire(GetTransform(), mgr, false);\n    }\n    break;\n  }\n  case EUserEventType::DamageOff: {\n    if (xa48_26_inProjectileAttack) {\n      if (CFlameThrower* flame = static_cast<CFlameThrower*>(mgr.ObjectById(x980_flameThrower)))\n        flame->Reset(mgr, false);\n    }\n    break;\n  }\n  case EUserEventType::BeginAction: {\n    if (xa48_26_inProjectileAttack) {\n      x8f0_boneTracking.SetTarget(mgr.GetPlayer().GetUniqueId());\n      x8f0_boneTracking.SetActive(true);\n    }\n    break;\n  }\n  case EUserEventType::ScreenShake:\n    return;\n  case EUserEventType::BecomeShootThrough:\n    SetProjectilePasshtrough(mgr);\n    return;\n  default:\n    break;\n  }\n  CPatterned::DoUserAnimEvent(mgr, node, type, dt);\n}\n\nvoid CBabygoth::AddSphereCollisionList(const SSphereJointInfo* sphereJointInfo, size_t jointCount,\n                                       std::vector<CJointCollisionDescription>& jointList) {\n  for (size_t i = 0; i < jointCount; ++i) {\n    const CSegId seg = GetModelData()->GetAnimationData()->GetLocatorSegId(sphereJointInfo[i].name);\n    jointList.push_back(\n        CJointCollisionDescription::SphereCollision(seg, sphereJointInfo[i].radius, sphereJointInfo[i].name, 1000.f));\n  }\n}\n\nvoid CBabygoth::SetupCollisionManager(CStateManager& mgr) {\n  std::vector<CJointCollisionDescription> joints;\n  AddSphereCollisionList(skSphereJointList.data(), skSphereJointList.size(), joints);\n  x928_colActMgr = std::make_unique<CCollisionActorManager>(mgr, GetUniqueId(), GetAreaIdAlways(), joints, false);\n  x928_colActMgr->SetActive(mgr, GetActive());\n\n  for (u32 i = 0; i < x928_colActMgr->GetNumCollisionActors(); ++i) {\n    const CJointCollisionDescription& desc = x928_colActMgr->GetCollisionDescFromIndex(i);\n    TUniqueId id = desc.GetCollisionActorId();\n    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(id)) {\n      colAct->SetDamageVulnerability(x570_babyData.x68_mouthVulnerabilities);\n      if (desc.GetName().find(skpMouthDamageJoint) == 0)\n        x9f6_mouthCollisionActor = id;\n      else if (desc.GetName().find(\"Pelvis\"sv) == 0 || desc.GetName().find(\"butt_LCTR\"sv) == 0) {\n        x9f8_shellIds.push_back(id);\n        x300_maxAttackRange = 66;\n      }\n    }\n  }\n\n  SetupHealthInfo(mgr);\n\n  SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(\n      {EMaterialTypes::Solid},\n      {EMaterialTypes::CollisionActor, EMaterialTypes::AIPassthrough, EMaterialTypes::Player}));\n  AddMaterial(EMaterialTypes::ProjectilePassthrough, mgr);\n  x928_colActMgr->AddMaterial(mgr, {EMaterialTypes::AIJoint, EMaterialTypes::CameraPassthrough});\n}\n\nvoid CBabygoth::SetupHealthInfo(CStateManager& mgr) {\n  CHealthInfo* thisHealth = HealthInfo(mgr);\n  x8ec_bodyHP = thisHealth->GetHP();\n\n  if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(x9f6_mouthCollisionActor))\n    colAct->HealthInfo(mgr)->SetHP(x570_babyData.GetShellHitPoints());\n\n  for (const TUniqueId& uid : x9f8_shellIds) {\n    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(uid)) {\n      CHealthInfo* colHealth = colAct->HealthInfo(mgr);\n      colHealth->SetHP(x570_babyData.GetShellHitPoints());\n      colHealth->SetKnockbackResistance(thisHealth->GetKnockbackResistance());\n      colAct->SetDamageVulnerability(x570_babyData.GetShellDamageVulnerability());\n    }\n  }\n\n  xa00_shellHitPoints = x570_babyData.GetShellHitPoints();\n}\n\nvoid CBabygoth::CreateFlameThrower(CStateManager& mgr) {\n  if (x980_flameThrower != kInvalidUniqueId)\n    return;\n\n  x980_flameThrower = mgr.AllocateUniqueId();\n  mgr.AddObject(new CFlameThrower(x984_flameThrowerDesc, \"IceSheegoth_Flame\"sv, EWeaponType::Plasma,\n                                  CFlameInfo(6, 4, x570_babyData.GetFireBreathResId(), 15, 0.0625f, 20.f, 1.f), {},\n                                  EMaterialTypes::CollisionActor, x570_babyData.GetFireBreathDamage(),\n                                  x980_flameThrower, GetAreaIdAlways(), GetUniqueId(), EProjectileAttrib::None,\n                                  x570_babyData.x16c_flamePlayerSteamTxtr, x570_babyData.x170_flamePlayerHitSfx,\n                                  x570_babyData.x174_flamePlayerIceTxtr));\n}\n\nvoid CBabygoth::ApplyContactDamage(TUniqueId uid, CStateManager& mgr) {\n  if (TCastToConstPtr<CCollisionActor> colAct = mgr.GetObjectById(uid)) {\n    if (colAct->GetHealthInfo(mgr)->GetHP() > 0.f && colAct->GetLastTouchedObject() == mgr.GetPlayer().GetUniqueId()) {\n      if (xa48_28_pendingAttackContactDamage) {\n        mgr.ApplyDamage(GetUniqueId(), mgr.GetPlayer().GetUniqueId(), GetUniqueId(),\n                        x570_babyData.x28_attackContactDamage,\n                        CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), {});\n        xa48_28_pendingAttackContactDamage = false;\n        x420_curDamageRemTime = x424_damageWaitTime;\n      } else if (x420_curDamageRemTime <= 0.f) {\n        mgr.ApplyDamage(GetUniqueId(), mgr.GetPlayer().GetUniqueId(), GetUniqueId(), GetContactDamage(),\n                        CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), {});\n        x420_curDamageRemTime = x424_damageWaitTime;\n      }\n    }\n  }\n}\n\nvoid CBabygoth::RemoveFromTeam(CStateManager& mgr) {\n  if (x6e8_teamMgr == kInvalidUniqueId)\n    return;\n\n  if (TCastToPtr<CTeamAiMgr> teamMgr = mgr.ObjectById(x6e8_teamMgr)) {\n    if (teamMgr->IsPartOfTeam(GetUniqueId()))\n      teamMgr->RemoveTeamAiRole(GetUniqueId());\n  }\n}\n\nvoid CBabygoth::ApplySeparationBehavior(CStateManager& mgr) {\n  for (CEntity* ent : mgr.GetListeningAiObjectList()) {\n    if (TCastToPtr<CPatterned> ai = ent) {\n      if (ai.GetPtr() != this && GetAreaIdAlways() == ai->GetAreaIdAlways()) {\n        zeus::CVector3f sep = x45c_steeringBehaviors.Separation(*this, ai->GetTranslation(), 15.f);\n        if (!sep.isZero()) {\n          x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(sep, {}, 1.f));\n        }\n      }\n    }\n  }\n}\n\nvoid CBabygoth::ApplyDamage(CStateManager& mgr, TUniqueId uid) {\n  if (TCastToConstPtr<CWeapon> weap = mgr.GetObjectById(uid)) {\n    if (x9f8_shellIds.empty())\n      return;\n\n    mgr.ApplyDamage(uid, x9f8_shellIds[0], weap->GetOwnerId(), weap->GetDamageInfo(),\n                    CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), {});\n  }\n}\n\nzeus::CVector3f CBabygoth::GetAimPosition(const CStateManager& mgr, float dt) const {\n  if (x450_bodyController->GetLocomotionType() != pas::ELocomotionType::Crouch && !x9f8_shellIds.empty()) {\n    zeus::CVector3f shellAvgPos;\n    for (TUniqueId id : x9f8_shellIds) {\n      if (TCastToConstPtr<CCollisionActor> cact = mgr.GetObjectById(id))\n        shellAvgPos += cact->GetSphereRadius() * zeus::skUp + cact->GetTranslation();\n    }\n    if (!shellAvgPos.isZero())\n      shellAvgPos = shellAvgPos / float(x9f8_shellIds.size());\n    float mouthZ = shellAvgPos.z();\n    if (TCastToConstPtr<CCollisionActor> cact = mgr.GetObjectById(x9f6_mouthCollisionActor))\n      mouthZ = cact->GetTranslation().z();\n    float t = zeus::CVector2f::getAngleDiff(GetTransform().basis[1].toVec2f(),\n                                            (mgr.GetPlayer().GetTranslation() - GetTranslation()).toVec2f()) /\n              M_PIF;\n    return zeus::CVector3f::lerp(zeus::CVector3f(shellAvgPos.toVec2f(), mouthZ),\n                                 zeus::CVector3f(shellAvgPos.toVec2f(), shellAvgPos.z()), t);\n  } else {\n    return CPatterned::GetAimPosition(mgr, 0.f);\n  }\n}\n\nzeus::CVector3f CBabygoth::GetOrigin(const CStateManager& mgr, const CTeamAiRole& role,\n                                     const zeus::CVector3f& aimPos) const {\n  zeus::CVector3f ret = GetTranslation() - aimPos;\n  if (ret.canBeNormalized())\n    ret = ret.normalized() * x2fc_minAttackRange + aimPos;\n  return ret;\n}\n\nvoid CBabygoth::KnockBack(const zeus::CVector3f& backVec, CStateManager& mgr, const CDamageInfo& info,\n                          EKnockBackType type, bool inDeferred, float magnitude) {\n  x460_knockBackController.SetAvailableState(EKnockBackAnimationState::Hurled,\n                                             x56c_shellState == EShellState::Destroyed);\n  CPatterned::KnockBack(backVec, mgr, info, type, inDeferred, magnitude);\n  if (x400_25_alive && x460_knockBackController.GetActiveParms().x0_animState == EKnockBackAnimationState::Hurled)\n    x330_stateMachineState.SetState(mgr, *this, GetStateMachine(), \"GetUp\"sv);\n}\n\nvoid CBabygoth::Shock(CStateManager& mgr, float duration, float damage) {\n  if (x9f8_shellIds.empty())\n    return;\n  if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(x9f8_shellIds[0])) {\n    EVulnerability vuln = colAct->GetDamageVulnerability()->GetVulnerability(CWeaponMode::Wave(), false);\n    if (vuln == EVulnerability::Weak) {\n      x450_bodyController->SetElectrocuting(1.5f * duration);\n      x3f0_pendingShockDamage = 1.5f * damage;\n    } else if (vuln == EVulnerability::Normal) {\n      x450_bodyController->SetElectrocuting(duration);\n      x3f0_pendingShockDamage = damage;\n    }\n  }\n}\n\nvoid CBabygoth::UpdateTouchBounds() {\n  zeus::CAABox bounds({-1.5f, -1.5f, 0.f}, {1.5f, 1.5f, 2.f});\n  SetBoundingBox(bounds);\n  x930_aabox.Box() = bounds;\n}\n\nvoid CBabygoth::UpdateAttackPosition(CStateManager& mgr, zeus::CVector3f& attackPos) {\n  attackPos = GetTranslation();\n  if (x8d8_attackTimeLeft > 0.f)\n    return;\n  attackPos = mgr.GetPlayer().GetTranslation();\n  zeus::CVector3f distVec = GetTranslation() - attackPos;\n  if (distVec.canBeNormalized())\n    attackPos += x2fc_minAttackRange * distVec.normalized();\n}\n\nvoid CBabygoth::UpdateShellHealth(CStateManager& mgr) {\n  if (xa00_shellHitPoints <= 0.f)\n    return;\n\n  float dam = 0.f;\n  if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(x9f6_mouthCollisionActor))\n    dam = zeus::max(dam, x570_babyData.GetShellHitPoints() - colAct->GetHealthInfo(mgr)->GetHP());\n\n  for (TUniqueId uid : x9f8_shellIds) {\n    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(uid)) {\n      dam = zeus::max(dam, x570_babyData.GetShellHitPoints() - colAct->GetHealthInfo(mgr)->GetHP());\n    }\n  }\n\n  xa00_shellHitPoints -= dam;\n  if (xa00_shellHitPoints <= 0.f) {\n    x56c_shellState = EShellState::Destroyed;\n    DestroyShell(mgr);\n    CrackShell(mgr, xa2c_destroyShellParticle, x34_transform, x570_babyData.x15c_destroyShellSfx, false);\n    UpdateHealthInfo(mgr);\n  } else {\n    if (xa00_shellHitPoints < CalculateShellCrackHP(EShellState::CrackTwo)) {\n      if (x56c_shellState != EShellState::CrackTwo) {\n        CrackShell(mgr, xa20_crackTwoParticle, x34_transform, x570_babyData.x15a_crackTwoSfx, false);\n        x56c_shellState = EShellState::CrackTwo;\n        xa04_drawMaterialIdx = 2;\n      }\n    } else if (xa00_shellHitPoints < CalculateShellCrackHP(EShellState::CrackOne)) {\n      if (x56c_shellState != EShellState::CrackOne) {\n        CrackShell(mgr, xa14_crackOneParticle, x34_transform, x570_babyData.x158_crackOneSfx, false);\n        x56c_shellState = EShellState::CrackOne;\n        xa04_drawMaterialIdx = 1;\n      }\n    }\n  }\n\n  float hp = (x56c_shellState == EShellState::Destroyed ? x8ec_bodyHP : x570_babyData.GetShellHitPoints());\n\n  if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(x9f6_mouthCollisionActor)) {\n    colAct->HealthInfo(mgr)->SetHP(hp);\n  }\n\n  for (TUniqueId uid : x9f8_shellIds) {\n    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(uid)) {\n      colAct->HealthInfo(mgr)->SetHP(hp);\n    }\n  }\n}\n\nvoid CBabygoth::AvoidPlayerCollision(float dt, CStateManager& mgr) {\n  if (x450_bodyController->GetLocomotionType() == pas::ELocomotionType::Crouch ||\n      x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::Step)\n    return;\n\n  auto dev = x928_colActMgr->GetDeviation(mgr, x9f4_mouthLocator);\n\n  if (dev) {\n    const CPlayer& pl = mgr.GetPlayer();\n    if (GetModelData()->GetBounds().intersects(pl.GetBoundingBox())) {\n      if (dev->magnitude() > 0.9f * GetModelData()->GetScale().y()) {\n        zeus::CVector3f posDiff = GetTranslation() - pl.GetTranslation();\n        zeus::CVector3f xpos = GetTransform().transposeRotate(\n            (posDiff.dot(dev->magnitude() - 0.9f * GetModelData()->GetScale().y() * (*dev).normalized()) /\n             posDiff.magSquared()) *\n            posDiff);\n\n        ApplyImpulseWR(GetMoveToORImpulseWR(xpos, dt), zeus::CAxisAngle());\n      }\n    }\n  }\n}\n\nvoid CBabygoth::AddToTeam(CStateManager& mgr) {\n  if (x6e8_teamMgr == kInvalidUniqueId)\n    return;\n  if (TCastToPtr<CTeamAiMgr> aiMgr = mgr.ObjectById(x6e8_teamMgr)) {\n    if (!aiMgr->IsPartOfTeam(GetUniqueId()))\n      aiMgr->AssignTeamAiRole(*this, CTeamAiRole::ETeamAiRole::Melee, CTeamAiRole::ETeamAiRole::Ranged,\n                              CTeamAiRole::ETeamAiRole::Invalid);\n  }\n}\n\nvoid CBabygoth::UpdateTimers(float dt) {\n  if (x8d8_attackTimeLeft > 0.f)\n    x8d8_attackTimeLeft -= dt * (xa49_28_onApproachPath ? 2.f : 1.f);\n  if (x8e4_fireballAttackTimeLeft > 0.f)\n    x8e4_fireballAttackTimeLeft -= dt * (xa49_28_onApproachPath ? 2.f : 1.f);\n  if (x8e0_attackCooldownTimeLeft > 0.f)\n    x8e0_attackCooldownTimeLeft -= dt;\n  if (x8e8_interestTimer < x570_babyData.x168_interestTime)\n    x8e8_interestTimer += dt;\n}\n\nvoid CBabygoth::UpdateHealth(CStateManager& mgr) {\n  if (!x400_25_alive)\n    return;\n\n  if (x56c_shellState == EShellState::Destroyed) {\n    float dam = 0.f;\n    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(x9f6_mouthCollisionActor)) {\n      dam = zeus::max(dam, x8ec_bodyHP - colAct->GetHealthInfo(mgr)->GetHP());\n    }\n\n    for (TUniqueId uid : x9f8_shellIds) {\n      if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(uid)) {\n        dam = zeus::max(dam, x8ec_bodyHP - colAct->GetHealthInfo(mgr)->GetHP());\n      }\n    }\n\n    HealthInfo(mgr)->SetHP(HealthInfo(mgr)->GetHP() - dam);\n    if (HealthInfo(mgr)->GetHP() <= 0.f) {\n      Death(mgr, {}, EScriptObjectState::DeathRattle);\n      xa48_26_inProjectileAttack = true;\n      xa49_26_readyForTeam = false;\n      RemoveFromTeam(mgr);\n      RemoveMaterial(EMaterialTypes::Orbit, EMaterialTypes::Target, mgr);\n    } else {\n      if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(x9f6_mouthCollisionActor))\n        colAct->HealthInfo(mgr)->SetHP(x8ec_bodyHP);\n\n      for (TUniqueId uid : x9f8_shellIds) {\n        if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(uid)) {\n          colAct->HealthInfo(mgr)->SetHP(x8ec_bodyHP);\n        }\n      }\n    }\n  } else {\n    UpdateShellHealth(mgr);\n  }\n}\n\nvoid CBabygoth::UpdateParticleEffects(float dt, CStateManager& mgr) {\n  if (CFlameThrower* flame = static_cast<CFlameThrower*>(mgr.ObjectById(x980_flameThrower))) {\n    if (!flame->GetActive())\n      return;\n    flame->SetTransform(GetLctrTransform(skpMouthDamageJoint), dt);\n  }\n}\n\nvoid CBabygoth::TryToGetUp(CStateManager& mgr) {\n  if (!x400_25_alive || x450_bodyController->GetFallState() == pas::EFallState::Zero || xa49_24_gettingUp)\n    return;\n\n  x330_stateMachineState.SetState(mgr, *this, GetStateMachine(), \"GetUp\"sv);\n}\n\nbool CBabygoth::CheckShouldWakeUp(CStateManager& mgr, float dt) {\n  if (!xa48_30_heardPlayerFire)\n    return false;\n\n  xa48_24_isAlert = x400_24_hitByPlayerProjectile && mgr.GetActiveRandom()->Float() < (0.5f * dt);\n  return xa48_24_isAlert;\n}\n\nvoid CBabygoth::SetProjectilePasshtrough(CStateManager& mgr) {\n  for (u32 i = 0; i < x928_colActMgr->GetNumCollisionActors(); ++i) {\n    const auto& desc = x928_colActMgr->GetCollisionDescFromIndex(i);\n    if (TCastToPtr<CCollisionActor> act = mgr.ObjectById(desc.GetCollisionActorId())) {\n      act->AddMaterial(EMaterialTypes::ProjectilePassthrough, mgr);\n    }\n  }\n}\n\nvoid CBabygoth::TurnAround(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x8f0_boneTracking.SetTarget(mgr.GetPlayer().GetUniqueId());\n    x8f0_boneTracking.SetActive(true);\n    UpdateAttackPosition(mgr, x2e0_destPos);\n    SetPathFindMode(EPathFindMode::Normal);\n    CPatterned::PathFind(mgr, EStateMsg::Activate, arg);\n    x450_bodyController->GetCommandMgr().ClearLocomotionCmds();\n  } else if (msg == EStateMsg::Update) {\n    if (ShouldTurn(mgr, arg)) {\n      float speed = (GetModelData()->GetAnimationData()->GetSpeedScale() > 0.f\n                         ? 1.f / GetModelData()->GetAnimationData()->GetSpeedScale()\n                         : 0.f);\n      zeus::CVector3f aimPos = mgr.GetPlayer().GetAimPosition(mgr, speed) - GetTranslation();\n      if (aimPos.canBeNormalized())\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd({}, aimPos.normalized(), 1.f));\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x8f0_boneTracking.SetActive(false);\n  }\n}\n\nvoid CBabygoth::GetUp(CStateManager&, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x568_stateProg = 0;\n    xa49_24_gettingUp = true;\n  } else if (msg == EStateMsg::Update) {\n    if (x568_stateProg == 0) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::Getup)\n        x568_stateProg = 3;\n      else\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCGetupCmd(pas::EGetupType::Zero));\n    } else if (x568_stateProg == 3 &&\n               x450_bodyController->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::Getup)\n      x568_stateProg = 4;\n  } else if (msg == EStateMsg::Deactivate) {\n    xa49_24_gettingUp = false;\n  }\n}\n\nvoid CBabygoth::Enraged(CStateManager&, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    xa48_29_hasBeenEnraged = true;\n    x568_stateProg = 0;\n  } else if (msg == EStateMsg::Update) {\n    if (x568_stateProg == 0) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::Generate)\n        x568_stateProg = 3;\n      else\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCGenerateCmd(pas::EGenerateType::Three));\n    } else if (x568_stateProg == 3 &&\n               x450_bodyController->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::Generate)\n      x568_stateProg = 4;\n  }\n}\n\nvoid CBabygoth::FollowPattern(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x568_stateProg = (xa49_25_shouldStepBackwards ? 0 : 4);\n    xa49_25_shouldStepBackwards = false;\n    x8f0_boneTracking.SetTarget(mgr.GetPlayer().GetUniqueId());\n    x8f0_boneTracking.SetActive(true);\n  } else if (msg == EStateMsg::Update) {\n    if (x568_stateProg == 0) {\n      x450_bodyController->GetCommandMgr().DeliverCmd(\n          CBCStepCmd(pas::EStepDirection::Backward, pas::EStepType::Normal));\n    } else if (x568_stateProg == 3) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::Step)\n        x450_bodyController->GetCommandMgr().DeliverTargetVector(mgr.GetPlayer().GetTranslation() - GetTranslation());\n      else\n        x568_stateProg = 4;\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x8f0_boneTracking.SetActive(false);\n    SetPathFindMode(EPathFindMode::Normal);\n  }\n}\n\nvoid CBabygoth::Taunt(CStateManager&, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBCTauntCmd(pas::ETauntType::One));\n  } else if (msg == EStateMsg::Update &&\n             x450_bodyController->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::Taunt) {\n    x568_stateProg = 4;\n  }\n}\n\nvoid CBabygoth::Crouch(CStateManager& mgr, const EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    RemoveMaterial(EMaterialTypes::Orbit, EMaterialTypes::Target, mgr);\n    mgr.GetPlayer().TryToBreakOrbit(GetUniqueId(), CPlayer::EPlayerOrbitRequest::ActivateOrbitSource, mgr);\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Crouch);\n    xa48_30_heardPlayerFire = xa48_24_isAlert = false;\n    x400_24_hitByPlayerProjectile = false;\n    x8e8_interestTimer = 0.f;\n  } else if (msg == EStateMsg::Deactivate) {\n    AddMaterial(EMaterialTypes::Orbit, EMaterialTypes::Target, mgr);\n  }\n}\n\nvoid CBabygoth::Deactivate(CStateManager&, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x568_stateProg = 1;\n  } else if (msg == EStateMsg::Update) {\n    if (x568_stateProg == 0) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::Generate) {\n        x568_stateProg = 3;\n        x450_bodyController->SetLocomotionType(pas::ELocomotionType::Crouch);\n      }\n    } else if (x568_stateProg == 1) {\n      if ((x3a0_latestLeashPosition - GetTranslation()).magSquared() > 1.f)\n        x568_stateProg = 2;\n      else {\n        zeus::CVector3f arrivalVec = x45c_steeringBehaviors.Arrival(*this, x3a0_latestLeashPosition, 15.f);\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(arrivalVec, {}, 1.f));\n      }\n    } else if (x568_stateProg == 2) {\n      float angle = zeus::CVector3f::getAngleDiff(GetTranslation(), x8c4_initialFaceDir);\n      if (angle > zeus::degToRad(5.f)) {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd({}, x8c4_initialFaceDir, 1.f));\n        return;\n      }\n      x568_stateProg = 0;\n    } else if (x568_stateProg == 3 &&\n               x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::Generate) {\n      x568_stateProg = 4;\n    }\n  }\n}\n\nvoid CBabygoth::Generate(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x568_stateProg = 0;\n    x8c4_initialFaceDir = GetTransform().basis[1];\n  } else if (msg == EStateMsg::Update) {\n    if (x568_stateProg == 0) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::Generate) {\n        x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n        x568_stateProg = 3;\n        SendScriptMsgs(EScriptObjectState::Attack, mgr, EScriptObjectMessage::None);\n      } else {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCGenerateCmd(pas::EGenerateType::Zero));\n      }\n    } else if (x568_stateProg == 3 &&\n               x450_bodyController->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::Generate)\n      x568_stateProg = 4;\n  }\n}\n\nvoid CBabygoth::TargetPatrol(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    xa49_26_readyForTeam = false;\n    RemoveFromTeam(mgr);\n    x400_24_hitByPlayerProjectile = false;\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n    if (HasPatrolPath(mgr, 0.f)) {\n      Patrol(mgr, msg, arg);\n      UpdateDest(mgr);\n    } else {\n      SetDestPos(x3a0_latestLeashPosition);\n    }\n    x8b8_backupDestPos = x2e0_destPos;\n    if (GetSearchPath()) {\n      SetPathFindMode(EPathFindMode::Normal);\n      CPatterned::PathFind(mgr, msg, arg);\n    }\n  } else if (msg == EStateMsg::Update) {\n    if (GetSearchPath() && !PathShagged(mgr, 0.f)) {\n      SetPathFindMode(EPathFindMode::Normal);\n      CPatterned::PathFind(mgr, msg, arg);\n      ApplySeparationBehavior(mgr);\n    } else {\n      zeus::CVector3f arrivalVec = x45c_steeringBehaviors.Arrival(*this, x8b8_backupDestPos, 9.f);\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(arrivalVec, {}, 1.f));\n    }\n  }\n}\n\nvoid CBabygoth::Patrol(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Update)\n    ApplySeparationBehavior(mgr);\n\n  CPatterned::Patrol(mgr, msg, arg);\n}\n\nvoid CBabygoth::Approach(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n    x8f0_boneTracking.SetTarget(mgr.GetPlayer().GetUniqueId());\n    x8f0_boneTracking.SetActive(true);\n    UpdateAttackPosition(mgr, x2e0_destPos);\n    SetPathFindMode(EPathFindMode::Normal);\n    CPatterned::PathFind(mgr, EStateMsg::Activate, arg);\n    if (!xa49_27_locomotionValid)\n      x450_bodyController->GetCommandMgr().ClearLocomotionCmds();\n    xa48_31_approachNeedsPathSearch = true;\n    xa49_28_onApproachPath = true;\n  } else if (msg == EStateMsg::Update) {\n    SetPathFindMode(EPathFindMode::Approach);\n    if (xa48_31_approachNeedsPathSearch) {\n      x2e0_destPos = x8b8_backupDestPos;\n      if (x7d0_approachPathSearch.FindClosestReachablePoint(GetTranslation(), x2e0_destPos) ==\n          CPathFindSearch::EResult::Success) {\n        if ((x2e0_destPos - GetTranslation()).magSquared() < 10.f)\n          x2e0_destPos = GetTranslation();\n        x8b8_backupDestPos = x2e0_destPos;\n        CPatterned::PathFind(mgr, EStateMsg::Activate, arg);\n        if (!xa49_27_locomotionValid)\n          x450_bodyController->GetCommandMgr().ClearLocomotionCmds();\n      }\n      xa48_31_approachNeedsPathSearch = false;\n    }\n\n    xa49_27_locomotionValid &= !IsDestinationObstructed(mgr);\n    if (xa49_27_locomotionValid && GetSearchPath() && !PathShagged(mgr, 0.f) &&\n        (x7d0_approachPathSearch.GetCurrentWaypoint() < x7d0_approachPathSearch.GetWaypoints().size() - 1)) {\n      CPatterned::PathFind(mgr, msg, arg);\n      ApplySeparationBehavior(mgr);\n    } else if (ShouldTurn(mgr, 0.f)) {\n      zeus::CVector3f direction = mgr.GetPlayer().GetTranslation() - GetTranslation();\n      if (direction.canBeNormalized()) {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(direction.normalized(), {}, 1.f));\n      }\n    }\n    xa49_27_locomotionValid = true;\n  } else if (msg == EStateMsg::Deactivate) {\n    x8f0_boneTracking.SetActive(false);\n    SetPathFindMode(EPathFindMode::Normal);\n  }\n}\n\nvoid CBabygoth::PathFind(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    xa49_28_onApproachPath = false;\n    xa49_26_readyForTeam = true;\n    xa48_24_isAlert = false;\n    x8e8_interestTimer = 0.f;\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n    x8f0_boneTracking.SetTarget(mgr.GetPlayer().GetUniqueId());\n    x8f0_boneTracking.SetActive(true);\n    UpdateAttackPosition(mgr, x2e0_destPos);\n    x8b8_backupDestPos = x2e0_destPos;\n    SetPathFindMode(EPathFindMode::Normal);\n    CPatterned::PathFind(mgr, msg, arg);\n  } else if (msg == EStateMsg::Update) {\n    SetPathFindMode(EPathFindMode::Normal);\n    if (GetSearchPath() && !PathShagged(mgr, 0.f) &&\n        x6ec_pathSearch.GetCurrentWaypoint() < x6ec_pathSearch.GetWaypoints().size() - 1) {\n      CPatterned::PathFind(mgr, msg, arg);\n      x8e8_interestTimer = 0.f;\n      zeus::CVector3f move = x450_bodyController->GetCommandMgr().GetMoveVector();\n      if (move.canBeNormalized()) {\n        float mag = x45c_steeringBehaviors.Arrival(*this, mgr.GetPlayer().GetTranslation(), 15.f).magnitude();\n        x450_bodyController->GetCommandMgr().ClearLocomotionCmds();\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(mag * move.normalized(), {}, 10.f));\n      }\n      ApplySeparationBehavior(mgr);\n      if (GetTransform().basis[1].magSquared() < 0.f && move.canBeNormalized()) {\n        x450_bodyController->GetCommandMgr().ClearLocomotionCmds();\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd({}, move.normalized(), 1.f));\n      }\n    } else {\n      zeus::CVector3f diffPos = mgr.GetPlayer().GetTranslation() - GetTranslation();\n      if (diffPos.canBeNormalized()) {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd({}, diffPos.normalized(), 1.f));\n      }\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x8f0_boneTracking.SetActive(false);\n  }\n}\n\nvoid CBabygoth::UpdateAttackTimeLeft(CStateManager& mgr) {\n  float mult = x56c_shellState == EShellState::Destroyed ? 0.6f : 1.f;\n  x8d8_attackTimeLeft = (mgr.GetActiveRandom()->Float() * x308_attackTimeVariation + x304_averageAttackTime) * mult;\n  x8e4_fireballAttackTimeLeft = (mgr.GetActiveRandom()->Float() * x570_babyData.GetFireballAttackVariance() +\n                                 x570_babyData.GetFireballAttackTime()) *\n                                mult;\n}\n\nvoid CBabygoth::SpecialAttack(CStateManager& mgr, EStateMsg state, float) {\n  if (state == EStateMsg::Activate) {\n    xa48_27_ = true;\n    x8e8_interestTimer = 0.f;\n    xa49_27_locomotionValid = false;\n    TCastToPtr<CTeamAiMgr> aimgr = mgr.ObjectById(x6e8_teamMgr);\n    if (aimgr && aimgr->HasTeamAiRole(GetUniqueId()))\n      x568_stateProg = aimgr->AddRangedAttacker(GetUniqueId()) ? 0 : 4;\n    else\n      x568_stateProg = 0;\n  } else if (state == EStateMsg::Update) {\n    switch (x568_stateProg) {\n    case 0:\n      if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::ProjectileAttack) {\n        x568_stateProg = 3;\n        x3b4_speed = 2.f * x8d0_initialSpeed;\n      } else {\n        x450_bodyController->GetCommandMgr().DeliverCmd(\n            CBCProjectileAttackCmd(pas::ESeverity::One, mgr.GetPlayer().GetTranslation(), false));\n      }\n      break;\n    case 3:\n      if (x450_bodyController->GetCurrentStateId() != pas::EAnimationState::ProjectileAttack)\n        x568_stateProg = 4;\n      break;\n    default:\n      break;\n    }\n  } else if (state == EStateMsg::Deactivate) {\n    UpdateAttackTimeLeft(mgr);\n    xa48_27_ = false;\n    x3b4_speed = x8d0_initialSpeed;\n    if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::ProjectileAttack)\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBodyStateCmd(EBodyStateCmd::NextState));\n    CTeamAiMgr::ResetTeamAiRole(CTeamAiMgr::EAttackType::Ranged, mgr, x6e8_teamMgr, GetUniqueId(), false);\n  }\n}\n\nvoid CBabygoth::ExtendCollisionActorTouchBounds(CStateManager& mgr, const zeus::CVector3f& extents) {\n  for (u32 i = 0; i < x928_colActMgr->GetNumCollisionActors(); ++i) {\n    const CJointCollisionDescription& desc = x928_colActMgr->GetCollisionDescFromIndex(i);\n    if (TCastToPtr<CCollisionActor> cact = mgr.ObjectById(desc.GetCollisionActorId()))\n      cact->SetExtendedTouchBounds(extents);\n  }\n}\n\nvoid CBabygoth::UpdateAttack(CStateManager& mgr, float dt) {\n  switch (x568_stateProg) {\n  case 0:\n    x8dc_attackTimer += dt;\n    if (x8dc_attackTimer < x570_babyData.x160_timeUntilAttack) {\n      if (!xa48_28_pendingAttackContactDamage) {\n        x568_stateProg = 3;\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCMeleeAttackCmd(pas::ESeverity::One));\n      } else {\n        zeus::CTransform mouthXf = GetLctrTransform(x9f4_mouthLocator);\n        if ((mgr.GetPlayer().GetTranslation() - mouthXf.origin).dot(GetTransform().basis[1]) > 0.f) {\n          SetPathFindMode(EPathFindMode::Normal);\n          auto* path = GetSearchPath();\n          if (path && !PathShagged(mgr, 0.f)) {\n            CPatterned::PathFind(mgr, EStateMsg::Update, dt);\n            ApplySeparationBehavior(mgr);\n          } else {\n            x568_stateProg = 4;\n          }\n        } else {\n          x568_stateProg = 4;\n        }\n      }\n    } else {\n      x568_stateProg = 4;\n    }\n    break;\n  case 3:\n    if (x450_bodyController->GetCurrentStateId() != pas::EAnimationState::MeleeAttack) {\n      x568_stateProg = 4;\n    } else {\n      xa49_25_shouldStepBackwards = true;\n    }\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CBabygoth::Attack(CStateManager& mgr, EStateMsg state, float dt) {\n  if (state == EStateMsg::Activate) {\n    x568_stateProg = 0;\n    xa48_25_ = xa48_28_pendingAttackContactDamage = true;\n    x8e8_interestTimer = 0.f;\n    xa49_25_shouldStepBackwards = false;\n    x2e0_destPos = mgr.GetPlayer().GetAimPosition(mgr, 0.f);\n    SetPathFindMode(EPathFindMode::Normal);\n    CPatterned::PathFind(mgr, state, dt);\n    x8dc_attackTimer = 0.f;\n    x450_bodyController->GetCommandMgr().SetSteeringBlendMode(ESteeringBlendMode::FullSpeed);\n    x450_bodyController->GetCommandMgr().SetSteeringSpeedRange(1.f, 1.f);\n    ExtendCollisionActorTouchBounds(mgr, zeus::CVector3f(0.2f));\n  } else if (state == EStateMsg::Update) {\n    UpdateAttack(mgr, dt);\n  } else if (state == EStateMsg::Deactivate) {\n    UpdateAttackTimeLeft(mgr);\n    xa48_25_ = xa48_28_pendingAttackContactDamage = false;\n    x8e0_attackCooldownTimeLeft = x570_babyData.x164_attackCooldownTime;\n    x8dc_attackTimer = 0.f;\n    float maxSpeed = x450_bodyController->GetBodyStateInfo().GetMaxSpeed();\n    x450_bodyController->GetCommandMgr().SetSteeringBlendMode(ESteeringBlendMode::Clamped);\n    x450_bodyController->GetCommandMgr().SetSteeringSpeedRange(\n        0.f, (maxSpeed > 0.f)\n                 ? x450_bodyController->GetBodyStateInfo().GetLocomotionSpeed(pas::ELocomotionAnim::Walk) / maxSpeed\n                 : 1.f);\n    ExtendCollisionActorTouchBounds(mgr, zeus::skZero3f);\n    CTeamAiMgr::ResetTeamAiRole(CTeamAiMgr::EAttackType::Melee, mgr, x6e8_teamMgr, GetUniqueId(), true);\n  }\n}\n\nvoid CBabygoth::ProjectileAttack(CStateManager& mgr, EStateMsg state, float) {\n  if (state == EStateMsg::Activate) {\n    xa48_26_inProjectileAttack = true;\n    x8e8_interestTimer = 0.f;\n    xa49_27_locomotionValid = false;\n    TCastToPtr<CTeamAiMgr> aimgr = mgr.ObjectById(x6e8_teamMgr);\n    if (aimgr && aimgr->HasTeamAiRole(GetUniqueId()))\n      x568_stateProg = aimgr->AddRangedAttacker(GetUniqueId()) ? 0 : 4;\n    else\n      x568_stateProg = 0;\n  } else if (state == EStateMsg::Update) {\n    zeus::CVector3f aimPos = mgr.GetPlayer().GetAimPosition(mgr, 0.f);\n    switch (x568_stateProg) {\n    case 0:\n      if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::ProjectileAttack) {\n        x568_stateProg = 3;\n        x3b4_speed = 2.f * x8d0_initialSpeed;\n      } else {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCProjectileAttackCmd(pas::ESeverity::Two, aimPos, false));\n      }\n      break;\n    case 3:\n      if (x450_bodyController->GetCurrentStateId() != pas::EAnimationState::ProjectileAttack) {\n        x568_stateProg = 4;\n      } else if (!mgr.ObjectById(x980_flameThrower)) {\n        x450_bodyController->GetCommandMgr().DeliverTargetVector(aimPos - GetTranslation());\n        x8f0_boneTracking.UnsetTarget();\n        x8f0_boneTracking.SetTargetPosition(aimPos);\n      }\n    }\n  } else if (state == EStateMsg::Deactivate) {\n    UpdateAttackTimeLeft(mgr);\n    if (auto* fthrower = static_cast<CFlameThrower*>(mgr.ObjectById(x980_flameThrower)))\n      fthrower->Reset(mgr, false);\n    xa48_26_inProjectileAttack = false;\n    if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::ProjectileAttack)\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBodyStateCmd(EBodyStateCmd::NextState));\n    x8f0_boneTracking.SetActive(false);\n    x3b4_speed = x8d0_initialSpeed;\n    CTeamAiMgr::ResetTeamAiRole(CTeamAiMgr::EAttackType::Ranged, mgr, x6e8_teamMgr, GetUniqueId(), false);\n  }\n}\n\nbool CBabygoth::Leash(CStateManager& mgr, float) {\n  if ((x3a0_latestLeashPosition - GetTranslation()).magSquared() > x3c8_leashRadius * x3c8_leashRadius)\n    return (mgr.GetPlayer().GetTranslation() - GetTranslation()).magSquared() >\n               x3cc_playerLeashRadius * x3cc_playerLeashRadius &&\n           x3d4_curPlayerLeashTime > x3d0_playerLeashTime;\n  return false;\n}\n\nbool CBabygoth::IsDestinationObstructed(const CStateManager& mgr) const {\n  for (const CEntity* obj : mgr.GetListeningAiObjectList()) {\n    if (const TCastToConstPtr<CPatterned> ai = obj) {\n      if (ai->GetAreaIdAlways() == GetAreaIdAlways()) {\n        if ((x8b8_backupDestPos - ai->GetTranslation()).magSquared() <= 10.f) {\n          return true;\n        }\n      }\n    }\n  }\n  return false;\n}\n\nvoid CBabygoth::DestroyShell(CStateManager& mgr) {\n  GetModelData()->GetAnimationData()->SubstituteModelData(xa08_noShellModel);\n\n  for (TUniqueId uid : x9f8_shellIds) {\n    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(uid)) {\n      colAct->SetWeaponCollisionResponseType(EWeaponCollisionResponseTypes::Unknown41);\n    }\n  }\n  xa04_drawMaterialIdx = 0;\n}\n\nvoid CBabygoth::CrackShell(CStateManager& mgr, const TLockedToken<CGenDescription>& desc, const zeus::CTransform& xf,\n                           u16 sfx, bool nonEmitterSfx) {\n  mgr.AddObject(new CExplosion(desc, mgr.AllocateUniqueId(), true,\n                               CEntityInfo(GetAreaIdAlways(), CEntity::NullConnectionList), \"Babygoth Shell Crack Fx\"sv,\n                               xf, 0, GetModelData()->GetScale(), zeus::skWhite));\n\n  if (nonEmitterSfx)\n    CSfxManager::SfxStart(sfx, 0x7f, 64 / 127.f, false, 0x7f, false, -1);\n  else\n    CSfxManager::AddEmitter(sfx, GetTranslation(), zeus::skUp, false, false, 0x7f, GetAreaIdAlways());\n}\n\nvoid CBabygoth::UpdateHealthInfo(CStateManager& mgr) {\n  CHealthInfo* hInfo = HealthInfo(mgr);\n  if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(x9f6_mouthCollisionActor)) {\n    (*colAct->HealthInfo(mgr)) = *hInfo;\n    colAct->SetDamageVulnerability(x98c_dVuln);\n  }\n\n  for (TUniqueId uid : x9f8_shellIds) {\n    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(uid)) {\n      (*colAct->HealthInfo(mgr)) = *hInfo;\n      colAct->SetDamageVulnerability(x98c_dVuln);\n    }\n  }\n}\n\nfloat CBabygoth::CalculateShellCrackHP(EShellState state) const {\n  if (state == EShellState::Default)\n    return x570_babyData.GetShellHitPoints();\n  else if (state == EShellState::CrackOne)\n    return (2.f / 3.f) * x570_babyData.GetShellHitPoints();\n  else if (state == EShellState::CrackTwo)\n    return (1.f / 3.f) * x570_babyData.GetShellHitPoints();\n\n  return 0.f;\n}\n\nbool CBabygoth::ShouldTurn(CStateManager& mgr, float arg) {\n  const float speedScale = GetModelData()->GetAnimationData()->GetSpeedScale();\n  zeus::CVector3f aimPos = mgr.GetPlayer().GetAimPosition(mgr, (speedScale > 0.f ? 1.f / speedScale : 0.f));\n  return zeus::CVector2f::getAngleDiff(GetTransform().basis[1].toVec2f(), (aimPos - GetTranslation()).toVec2f()) >\n         (arg == 0.f ? zeus::degToRad(45.f) : arg);\n}\n\nbool CBabygoth::ShouldAttack(CStateManager& mgr, float arg) {\n  if (mgr.GetPlayer().GetAreaIdAlways() == GetAreaIdAlways() && !mgr.GetPlayer().GetFrozenState() &&\n      x8d8_attackTimeLeft <= 0.f && x8e0_attackCooldownTimeLeft <= 0.f) {\n    zeus::CVector3f aimPos = mgr.GetPlayer().GetAimPosition(mgr, 0.f);\n    if (x450_bodyController->GetBodyStateInfo().GetMaxSpeed() * x570_babyData.x160_timeUntilAttack >\n            (aimPos - GetTranslation()).magnitude() &&\n        !ShouldTurn(mgr, zeus::degToRad(25.f)) &&\n        !IsPatternObstructed(mgr, GetLctrTransform(x9f4_mouthLocator).origin, aimPos)) {\n      if (TCastToPtr<CTeamAiMgr> aimgr = mgr.ObjectById(x6e8_teamMgr)) {\n        if (aimgr->IsPartOfTeam(GetUniqueId()))\n          return aimgr->AddMeleeAttacker(GetUniqueId());\n      }\n      return true;\n    }\n  }\n  return false;\n}\n\nbool CBabygoth::ShouldSpecialAttack(CStateManager& mgr, float arg) {\n  if (mgr.GetPlayer().GetAreaIdAlways() == GetAreaIdAlways() && x8e4_fireballAttackTimeLeft <= 0.f) {\n    zeus::CVector3f aimPos = mgr.GetPlayer().GetAimPosition(mgr, 0.f);\n    zeus::CVector3f mouthPos = GetLctrTransform(x9f4_mouthLocator).origin;\n    return (aimPos - mouthPos).magSquared() >= x300_maxAttackRange * x300_maxAttackRange &&\n           !ShouldTurn(mgr, zeus::degToRad(25.f)) && !IsPatternObstructed(mgr, mouthPos, aimPos);\n  }\n  return false;\n}\n\nbool CBabygoth::ShouldFire(CStateManager& mgr, float arg) {\n  if (mgr.GetPlayer().GetAreaIdAlways() == GetAreaIdAlways() && x8d8_attackTimeLeft <= 0.f) {\n    zeus::CVector3f aimPos = mgr.GetPlayer().GetAimPosition(mgr, 0.f);\n    zeus::CVector3f mouthPos = GetLctrTransform(x9f4_mouthLocator).origin;\n    return (aimPos - mouthPos).magSquared() <= x300_maxAttackRange * x300_maxAttackRange &&\n           !ShouldTurn(mgr, zeus::degToRad(30.f)) && !IsPatternObstructed(mgr, mouthPos, aimPos);\n  }\n  return false;\n}\n\nbool CBabygoth::TooClose(CStateManager& mgr, float arg) {\n  if (x930_aabox.CalculateAABox(GetTransform()).intersects(mgr.GetPlayer().GetBoundingBox())) {\n    xa49_25_shouldStepBackwards = true;\n    return true;\n  }\n  return false;\n}\n\nbool CBabygoth::LineOfSight(CStateManager& mgr, float arg) {\n  zeus::CVector3f aimPos = mgr.GetPlayer().GetAimPosition(mgr, 0.f);\n  zeus::CVector3f mouthPos = GetLctrTransform(x9f4_mouthLocator).origin;\n  return !IsPatternObstructed(mgr, mouthPos, aimPos);\n}\n\nbool CBabygoth::LostInterest(CStateManager& mgr, float arg) {\n  return x8e8_interestTimer >= x570_babyData.x168_interestTime &&\n         x6ec_pathSearch.OnPath(GetTranslation()) == CPathFindSearch::EResult::Success;\n}\n\nbool CBabygoth::InMaxRange(CStateManager& mgr, float) {\n  return (GetTranslation() - mgr.GetPlayer().GetTranslation()).magSquared() < (1.5f * x300_maxAttackRange);\n}\n\nbool CBabygoth::InDetectionRange(CStateManager& mgr, float arg) {\n  if (xa48_24_isAlert)\n    return true;\n  float range = (arg > 0.f ? arg : 1.f) * x3bc_detectionRange;\n  zeus::CVector3f delta = mgr.GetPlayer().GetTranslation() - GetTranslation();\n  if (delta.magSquared() < range * range) {\n    if (x3c0_detectionHeightRange > 0.f)\n      return delta.z() * delta.z() < x3c0_detectionHeightRange * x3c0_detectionHeightRange;\n    return true;\n  }\n  return false;\n}\n\nbool CBabygoth::Listen(const zeus::CVector3f& origin, EListenNoiseType noiseType) {\n  if (!x400_25_alive || noiseType != EListenNoiseType::PlayerFire || (origin - GetTranslation()).magSquared() >= 1600.f)\n    return false;\n\n  xa48_30_heardPlayerFire = true;\n  return true;\n}\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CBabygoth.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Character/CBoneTracking.hpp\"\n#include \"Runtime/Collision/CJointCollisionDescription.hpp\"\n#include \"Runtime/Weapon/CProjectileInfo.hpp\"\n#include \"Runtime/World/CPathFindSearch.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n\nnamespace metaforce {\nclass CCollisionActorManager;\nclass CWeaponDescription;\n} // namespace metaforce\n\nnamespace metaforce::MP1 {\nstruct CBabygothData {\n  float x0_fireballAttackTime;\n  float x4_fireballAttackTimeVariance;\n  CAssetId x8_fireballWeapon;\n  CDamageInfo xc_fireballDamage;\n  CDamageInfo x28_attackContactDamage;\n  CAssetId x44_fireBreathWeapon;\n  CAssetId x48_fireBreathRes;\n  CDamageInfo x4c_fireBreathDamage;\n  CDamageVulnerability x68_mouthVulnerabilities;\n  CDamageVulnerability xd0_shellVulnerabilities;\n  CAssetId x138_noShellModel;\n  CAssetId x13c_noShellSkin;\n  float x140_shellHitPoints;\n  s16 x144_shellCrackSfx;\n  CAssetId x148_intermediateCrackParticle;\n  CAssetId x14c_crackOneParticle;\n  CAssetId x150_crackTwoParticle;\n  CAssetId x154_destroyShellParticle;\n  s16 x158_crackOneSfx;\n  s16 x15a_crackTwoSfx;\n  s16 x15c_destroyShellSfx;\n  float x160_timeUntilAttack;\n  float x164_attackCooldownTime;\n  float x168_interestTime;\n  CAssetId x16c_flamePlayerSteamTxtr;\n  s16 x170_flamePlayerHitSfx;\n  CAssetId x174_flamePlayerIceTxtr;\n\npublic:\n  explicit CBabygothData(CInputStream&);\n  const CDamageInfo& GetFireballDamage() const { return xc_fireballDamage; }\n  CAssetId GetFireballResID() const { return x8_fireballWeapon; }\n  float GetFireballAttackVariance() const { return x4_fireballAttackTimeVariance; }\n  float GetFireballAttackTime() const { return x0_fireballAttackTime; }\n  CAssetId GetFireBreathResId() const { return x48_fireBreathRes; }\n  const CDamageInfo& GetFireBreathDamage() const { return x4c_fireBreathDamage; }\n  const CDamageVulnerability& GetShellDamageVulnerability() const { return xd0_shellVulnerabilities; }\n  float GetShellHitPoints() const { return x140_shellHitPoints; }\n  s16 GetShellCrackSfx() const { return x144_shellCrackSfx; }\n};\n\nclass CBabygoth final : public CPatterned {\npublic:\n  enum class EPathFindMode { Normal, Approach };\n  enum class EShellState { Default, CrackOne, CrackTwo, Destroyed };\n\nprivate:\n  s32 x568_stateProg = -1;\n  EShellState x56c_shellState = EShellState::Default;\n  CBabygothData x570_babyData;\n  TUniqueId x6e8_teamMgr = kInvalidUniqueId;\n  CPathFindSearch x6ec_pathSearch;\n  CPathFindSearch x7d0_approachPathSearch;\n  EPathFindMode x8b4_pathFindMode;\n  zeus::CVector3f x8b8_backupDestPos;\n  zeus::CVector3f x8c4_initialFaceDir;\n  float x8d0_initialSpeed;\n  float x8d4_stepBackwardDist = 0.f;\n  float x8d8_attackTimeLeft = 0.f;\n  float x8dc_attackTimer = 0.f;\n  float x8e0_attackCooldownTimeLeft = 0.f;\n  float x8e4_fireballAttackTimeLeft = 0.f;\n  float x8e8_interestTimer = 0.f;\n  float x8ec_bodyHP = 0.f;\n  CBoneTracking x8f0_boneTracking;\n  std::unique_ptr<CCollisionActorManager> x928_colActMgr;\n  CCollidableAABox x930_aabox;\n  CProjectileInfo x958_iceProjectile;\n  TUniqueId x980_flameThrower = kInvalidUniqueId;\n  TToken<CWeaponDescription> x984_flameThrowerDesc;\n  CDamageVulnerability x98c_dVuln;\n  CSegId x9f4_mouthLocator;\n  TUniqueId x9f6_mouthCollisionActor = kInvalidUniqueId;\n  rstl::reserved_vector<TUniqueId, 2> x9f8_shellIds;\n  float xa00_shellHitPoints;\n  u32 xa04_drawMaterialIdx = 0;\n  TLockedToken<CSkinnedModel> xa08_noShellModel;\n  TToken<CGenDescription> xa14_crackOneParticle;\n  TToken<CGenDescription> xa20_crackTwoParticle;\n  TToken<CGenDescription> xa2c_destroyShellParticle;\n  TLockedToken<CGenDescription> xa38_intermediateCrackParticle; // Used to be an optional, not necessary in URDE\n  bool xa48_24_isAlert : 1 = false;\n  bool xa48_25_ : 1 = false;\n  bool xa48_26_inProjectileAttack : 1 = false;\n  bool xa48_27_ : 1 = false;\n  bool xa48_28_pendingAttackContactDamage : 1 = false;\n  bool xa48_29_hasBeenEnraged : 1 = false;\n  bool xa48_30_heardPlayerFire : 1 = false;\n  bool xa48_31_approachNeedsPathSearch : 1 = true;\n  bool xa49_24_gettingUp : 1 = false;\n  bool xa49_25_shouldStepBackwards : 1 = false;\n  bool xa49_26_readyForTeam : 1 = false;\n  bool xa49_27_locomotionValid : 1 = false;\n  bool xa49_28_onApproachPath : 1 = false;\n  bool xa49_29_objectSpaceCollision : 1 = false;\n\n  void AddSphereCollisionList(const SSphereJointInfo*, size_t, std::vector<CJointCollisionDescription>&);\n\n  void SetupCollisionManager(CStateManager&);\n\n  void SetupHealthInfo(CStateManager&);\n\n  void CreateFlameThrower(CStateManager&);\n\n  void ApplyContactDamage(TUniqueId, CStateManager&);\n\n  void RemoveFromTeam(CStateManager&);\n\n  void ApplySeparationBehavior(CStateManager&);\n\n  bool IsMouthCollisionActor(TUniqueId uid) const { return x9f6_mouthCollisionActor == uid; }\n\n  bool IsShell(TUniqueId uid) const {\n    for (TUniqueId shellId : x9f8_shellIds) {\n      if (shellId == uid)\n        return true;\n    }\n    return false;\n  }\n\n  void ApplyDamage(CStateManager& mgr, TUniqueId uid);\n\n  void AvoidPlayerCollision(float, CStateManager&);\n\n  void AddToTeam(CStateManager& mgr);\n\n  void UpdateTimers(float);\n\n  void UpdateHealthInfo(CStateManager& mgr);\n\n  void UpdateParticleEffects(float, CStateManager&);\n\n  void TryToGetUp(CStateManager& mgr);\n\n  bool CheckShouldWakeUp(CStateManager&, float);\n\n  void SetProjectilePasshtrough(CStateManager&);\n\n  void UpdateTouchBounds();\n\n  void UpdateAttackPosition(CStateManager&, zeus::CVector3f&);\n\n  void UpdateShellHealth(CStateManager&);\n\n  bool IsDestinationObstructed(const CStateManager& mgr) const;\n\n  void DestroyShell(CStateManager& mgr);\n\n  void CrackShell(CStateManager&, const TLockedToken<CGenDescription>&, const zeus::CTransform&, u16, bool);\n\n  void UpdateHealth(CStateManager&);\n\n  float CalculateShellCrackHP(EShellState state) const;\n\n  void UpdateAttackTimeLeft(CStateManager& mgr);\n\n  void ExtendCollisionActorTouchBounds(CStateManager& mgr, const zeus::CVector3f& extents);\n\n  void UpdateAttack(CStateManager& mgr, float dt);\n\npublic:\n  DEFINE_PATTERNED(Babygoth);\n\n  CBabygoth(TUniqueId, std::string_view, const CEntityInfo&, const zeus::CTransform&, CModelData&&,\n            const CPatternedInfo&, const CActorParameters&, const CBabygothData&);\n\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) override;\n\n  void PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) override {\n    CPatterned::PreRender(mgr, frustum);\n    xb4_drawFlags.x1_matSetIdx = u8(xa04_drawMaterialIdx);\n  }\n\n  void Think(float, CStateManager&) override;\n\n  void DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) override;\n\n  float GetGravityConstant() const override { return 10.f * CPhysicsActor::GravityConstant(); }\n\n  void SetPathFindMode(EPathFindMode mode) { x8b4_pathFindMode = mode; }\n\n  const CCollisionPrimitive* GetCollisionPrimitive() const override { return &x930_aabox; }\n\n  EWeaponCollisionResponseTypes GetCollisionResponseType(const zeus::CVector3f& v1, const zeus::CVector3f& v2,\n                                                         const CWeaponMode& wMode,\n                                                         EProjectileAttrib attrib) const override {\n    if (wMode.GetType() == EWeaponType::Ice)\n      return EWeaponCollisionResponseTypes::None;\n    if (x56c_shellState != EShellState::Destroyed)\n      return EWeaponCollisionResponseTypes::Unknown66;\n    return CPatterned::GetCollisionResponseType(v1, v2, wMode, attrib);\n  }\n\n  const CDamageVulnerability* GetDamageVulnerability() const override {\n    return &CDamageVulnerability::ReflectVulnerabilty();\n  }\n\n  const CDamageVulnerability* GetDamageVulnerability(const zeus::CVector3f&, const zeus::CVector3f&,\n                                                     const CDamageInfo&) const override {\n    return &CDamageVulnerability::ReflectVulnerabilty();\n  }\n\n  zeus::CVector3f GetAimPosition(const CStateManager& mgr, float dt) const override;\n\n  zeus::CVector3f GetOrigin(const CStateManager& mgr, const CTeamAiRole& role,\n                            const zeus::CVector3f& aimPos) const override;\n\n  void TakeDamage(const zeus::CVector3f&, float) override {\n    if (x400_25_alive)\n      x428_damageCooldownTimer = 0.33f;\n  }\n\n  bool IsListening() const override { return true; }\n\n  void KnockBack(const zeus::CVector3f&, CStateManager&, const CDamageInfo& info, EKnockBackType type, bool inDeferred,\n                 float magnitude) override;\n\n  void Shock(CStateManager&, float, float) override;\n\n  void TurnAround(CStateManager&, EStateMsg, float) override;\n\n  void GetUp(CStateManager&, EStateMsg, float) override;\n\n  void Enraged(CStateManager&, EStateMsg, float) override;\n\n  void FollowPattern(CStateManager&, EStateMsg, float) override;\n\n  void Taunt(CStateManager&, EStateMsg, float) override;\n\n  void Crouch(CStateManager&, EStateMsg, float) override;\n\n  void Deactivate(CStateManager&, EStateMsg, float) override;\n\n  void Generate(CStateManager&, EStateMsg, float) override;\n\n  void TargetPatrol(CStateManager&, EStateMsg, float) override;\n\n  void Patrol(CStateManager&, EStateMsg, float) override;\n\n  void Approach(CStateManager&, EStateMsg, float) override;\n\n  void PathFind(CStateManager&, EStateMsg, float) override;\n\n  void SpecialAttack(CStateManager&, EStateMsg, float) override;\n\n  void Attack(CStateManager&, EStateMsg, float) override;\n\n  void ProjectileAttack(CStateManager&, EStateMsg, float) override;\n\n  bool Leash(CStateManager&, float) override;\n\n  bool AnimOver(CStateManager&, float) override { return x568_stateProg == 4; }\n\n  bool SpotPlayer(CStateManager& mgr, float arg) override {\n    if (xa48_24_isAlert)\n      return true;\n    return CPatterned::SpotPlayer(mgr, arg);\n  }\n\n  bool InPosition(CStateManager&, float) override { return (x8b8_backupDestPos - GetTranslation()).magSquared() < 9.f; }\n\n  bool InMaxRange(CStateManager&, float) override;\n\n  bool InDetectionRange(CStateManager&, float) override;\n\n  bool ShotAt(CStateManager&, float) override { return x400_24_hitByPlayerProjectile; }\n\n  bool OffLine(CStateManager& mgr, float arg) override {\n    SetPathFindMode(EPathFindMode::Normal);\n    return PathShagged(mgr, arg);\n  }\n\n  bool ShouldTurn(CStateManager& mgr, float arg) override;\n\n  bool ShouldAttack(CStateManager& mgr, float arg) override;\n\n  bool ShouldSpecialAttack(CStateManager& mgr, float arg) override;\n\n  bool ShouldFire(CStateManager& mgr, float arg) override;\n\n  bool TooClose(CStateManager& mgr, float arg) override;\n\n  bool LineOfSight(CStateManager& mgr, float arg) override;\n\n  bool AggressionCheck(CStateManager& mgr, float arg) override {\n    return x400_25_alive && !xa48_29_hasBeenEnraged && x56c_shellState == EShellState::Destroyed;\n  }\n\n  bool LostInterest(CStateManager& mgr, float arg) override;\n\n  bool Listen(const zeus::CVector3f&, EListenNoiseType) override;\n\n  CPathFindSearch* GetSearchPath() override {\n    return x8b4_pathFindMode == EPathFindMode::Normal ? &x6ec_pathSearch : &x7d0_approachPathSearch;\n  }\n\n  CProjectileInfo* GetProjectileInfo() override { return &x958_iceProjectile; }\n};\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CBeetle.cpp",
    "content": "#include \"Runtime/MP1/World/CBeetle.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Character/CCharLayoutInfo.hpp\"\n#include \"Runtime/Character/CPASAnimParmData.hpp\"\n#include \"Runtime/World/CDamageInfo.hpp\"\n#include \"Runtime/World/CPatternedInfo.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CScriptWaypoint.hpp\"\n#include \"Runtime/World/CTeamAiMgr.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce::MP1 {\n\nCBeetle::CBeetle(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                 CModelData&& mData, const CPatternedInfo& pInfo, CPatterned::EFlavorType flavor,\n                 CBeetle::EEntranceType entranceType, const CDamageInfo& touchDamage,\n                 const CDamageVulnerability& platingVuln, const zeus::CVector3f& tailAimReference,\n                 float initialAttackDelay, float retreatTime, float f3, const CDamageVulnerability& tailVuln,\n                 const CActorParameters& aParams, const std::optional<CStaticRes>& tailModel)\n: CPatterned(ECharacter::Beetle, uid, name, flavor, info, xf, std::move(mData), pInfo, EMovementType::Ground,\n             EColliderType::One, EBodyType::BiPedal, aParams, EKnockBackVariant(flavor))\n, x56c_entranceType(entranceType)\n, x574_tailAimReference(tailAimReference)\n, x580_f3(f3)\n, x584_touchDamage(touchDamage)\n, x5ac_tailModel(tailModel ? std::optional<CModelData>(CModelData(*tailModel)) : std::nullopt)\n, x5fc_pathFindSearch(nullptr, 1, pInfo.GetPathfindingIndex(), 1.f, 1.f)\n, x744_platingVuln(platingVuln)\n, x7ac_tailVuln(tailVuln)\n, x814_attackDelayTimer(initialAttackDelay)\n, x834_retreatTime(retreatTime) {\n  x5a0_headbuttDist = GetAnimationDistance(\n      CPASAnimParmData(pas::EAnimationState::MeleeAttack, CPASAnimParm::FromEnum(0), CPASAnimParm::FromEnum(1)));\n  x5a4_jumpBackwardDist = x64_modelData->GetScale().y() *\n                          GetAnimationDistance(CPASAnimParmData(pas::EAnimationState::Step, CPASAnimParm::FromEnum(1),\n                                                                CPASAnimParm::FromEnum(0)));\n  MakeThermalColdAndHot();\n  if (x3fc_flavor == EFlavorType::One)\n    x460_knockBackController.SetLocomotionDuringElectrocution(true);\n}\n\nvoid CBeetle::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CBeetle::SquadAdd(CStateManager& mgr) {\n  if (x570_aiMgr != kInvalidUniqueId)\n    if (TCastToPtr<CTeamAiMgr> aimgr = mgr.ObjectById(x570_aiMgr))\n      aimgr->AssignTeamAiRole(*this, CTeamAiRole::ETeamAiRole::Melee, CTeamAiRole::ETeamAiRole::Unknown,\n                              CTeamAiRole::ETeamAiRole::Invalid);\n}\n\nvoid CBeetle::SquadRemove(CStateManager& mgr) {\n  if (x570_aiMgr != kInvalidUniqueId)\n    if (TCastToPtr<CTeamAiMgr> aimgr = mgr.ObjectById(x570_aiMgr))\n      if (aimgr->IsPartOfTeam(GetUniqueId()))\n        aimgr->RemoveTeamAiRole(GetUniqueId());\n}\n\nvoid CBeetle::Think(float dt, CStateManager& mgr) {\n  if (!GetActive())\n    return;\n  if (CTeamAiRole* role = CTeamAiMgr::GetTeamAiRole(mgr, x570_aiMgr, GetUniqueId())) {\n    x450_bodyController->SetLocomotionType(role->GetTeamAiRole() == CTeamAiRole::ETeamAiRole::Melee\n                                               ? pas::ELocomotionType::Lurk\n                                               : pas::ELocomotionType::Relaxed);\n  } else {\n    SquadAdd(mgr);\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Lurk);\n  }\n  x460_knockBackController.SetAutoResetImpulse(IsOnGround());\n  if (x814_attackDelayTimer > 0.f)\n    x814_attackDelayTimer -= dt;\n  if ((x824_predictPos - GetTranslation()).toVec2f().magSquared() > 0.1f * dt)\n    x820_posDeviationCounter += 1;\n  else\n    x820_posDeviationCounter = 0;\n  CPatterned::Think(dt, mgr);\n  x824_predictPos = x138_velocity * dt + GetTranslation();\n}\n\nvoid CBeetle::SetupRetreatPoints(CStateManager& mgr) {\n  for (const auto& conn : GetConnectionList()) {\n    if (conn.x0_state == EScriptObjectState::Retreat && conn.x4_msg == EScriptObjectMessage::Follow) {\n      if (TCastToConstPtr<CScriptWaypoint> wp = mgr.GetObjectById(mgr.GetIdForScript(conn.x8_objId))) {\n        x6e0_retreatPoints.push_back(wp->GetTranslation());\n        if (x6e0_retreatPoints.size() == 8)\n          break;\n      }\n    }\n  }\n}\n\nvoid CBeetle::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) {\n  bool forward = true;\n  switch (msg) {\n  case EScriptObjectMessage::Activate:\n    SquadAdd(mgr);\n    break;\n  case EScriptObjectMessage::Deactivate:\n  case EScriptObjectMessage::Deleted:\n    SquadRemove(mgr);\n    break;\n  case EScriptObjectMessage::OnFloor:\n    forward = false;\n    SetMomentumWR(zeus::skZero3f);\n    x328_27_onGround = true;\n    break;\n  case EScriptObjectMessage::Falling:\n    if (!x450_bodyController->IsFrozen()) {\n      SetMomentumWR({0.f, 0.f, -(GetGravityConstant() * xe8_mass)});\n      x328_27_onGround = false;\n    }\n    forward = false;\n    break;\n  case EScriptObjectMessage::InitializedInArea:\n    if (x570_aiMgr == kInvalidUniqueId) {\n      x570_aiMgr = CTeamAiMgr::GetTeamAiMgr(*this, mgr);\n      if (GetActive())\n        SquadAdd(mgr);\n    }\n    SetupRetreatPoints(mgr);\n    x5fc_pathFindSearch.SetArea(mgr.GetWorld()->GetAreaAlways(GetAreaIdAlways())->GetPostConstructed()->x10bc_pathArea);\n    break;\n  default:\n    break;\n  }\n  if (forward)\n    CPatterned::AcceptScriptMsg(msg, sender, mgr);\n}\n\nvoid CBeetle::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) {\n  if (x400_25_alive) {\n    switch (mgr.GetPlayerState()->GetActiveVisor(mgr)) {\n    case CPlayerState::EPlayerVisor::XRay:\n      x42c_color.a() = 76.5f / 255.f;\n      break;\n    case CPlayerState::EPlayerVisor::Thermal:\n      if (x838_25_burrowing)\n        x42c_color.a() = x830_intoGroundFactor;\n      else\n        x42c_color.a() = 1.f;\n      break;\n    default:\n      x42c_color.a() = 1.f;\n      break;\n    }\n  }\n  CPatterned::PreRender(mgr, frustum);\n}\n\nvoid CBeetle::Render(CStateManager& mgr) {\n  if (x3fc_flavor == EFlavorType::One && x400_25_alive) {\n    zeus::CTransform tailXf = GetLctrTransform(\"Target_Tail\"sv);\n    if (x428_damageCooldownTimer >= 0.f && x42c_color.a() == 1.f) {\n      if (x5ac_tailModel) {\n        CModelFlags flags(2, 0, 3, zeus::skWhite);\n        // TODO flags.addColor = x42c_color;\n        x5ac_tailModel->Render(mgr, tailXf, x90_actorLights.get(), flags);\n      }\n    } else if (x5ac_tailModel) {\n      constexpr CModelFlags flags(0, 0, 3, zeus::skWhite);\n      x5ac_tailModel->Render(mgr, tailXf, x90_actorLights.get(), flags);\n    }\n  }\n  CPatterned::Render(mgr);\n}\n\nconst CDamageVulnerability* CBeetle::GetDamageVulnerability() const {\n  if (x838_25_burrowing)\n    return &CDamageVulnerability::PassThroughVulnerabilty();\n  if (x3fc_flavor == EFlavorType::One)\n    return x450_bodyController->IsOnFire() ? &x7ac_tailVuln : &x744_platingVuln;\n  return CAi::GetDamageVulnerability();\n}\n\nconst CDamageVulnerability* CBeetle::GetDamageVulnerability(const zeus::CVector3f& pos, const zeus::CVector3f& dir,\n                                                            const CDamageInfo& dInfo) const {\n  if (x838_25_burrowing)\n    return &CDamageVulnerability::PassThroughVulnerabilty();\n  if (x3fc_flavor == EFlavorType::One) {\n    if (dInfo.GetWeaponMode().IsComboed() && dInfo.GetWeaponMode().GetType() == EWeaponType::Wave)\n      return &x7ac_tailVuln;\n    if (GetTransform().basis[1].dot(dir) > 0.f &&\n        GetTransform().basis[1].dot(zeus::CUnitVector3f(pos - GetBoundingBox().center())) < -0.5f)\n      return &x7ac_tailVuln;\n    else\n      return &x744_platingVuln;\n  }\n  return GetDamageVulnerability();\n}\n\nzeus::CVector3f CBeetle::GetOrbitPosition(const CStateManager& mgr) const {\n  zeus::CVector3f ret = CPatterned::GetOrbitPosition(mgr);\n  ret.z() = float(GetBoundingBox().center().z());\n  return ret;\n}\n\nzeus::CVector3f CBeetle::GetAimPosition(const CStateManager& mgr, float dt) const {\n  if (x3fc_flavor == EFlavorType::One || x3fc_flavor == EFlavorType::Two) {\n    zeus::CTransform tailXf = GetLctrTransform(\"Target_Tail\"sv);\n    zeus::CVector3f scaleRange = tailXf * x574_tailAimReference - GetTranslation();\n    zeus::CAABox aabb = GetBoundingBox();\n    float minFactor = 10.f;\n    for (int i = 0; i < 3; ++i) {\n      if (scaleRange[i] < 0.f) {\n        float factor = (aabb.min[i] - GetTranslation()[i]) / scaleRange[i];\n        if (factor < minFactor)\n          minFactor = factor;\n      } else if (scaleRange[i] > 0.f) {\n        float factor = (aabb.max[i] - GetTranslation()[i]) / scaleRange[i];\n        if (factor < minFactor)\n          minFactor = factor;\n      }\n    }\n    return scaleRange * minFactor + GetTranslation();\n  } else {\n    return CPhysicsActor::GetAimPosition(mgr, dt);\n  }\n}\n\nEWeaponCollisionResponseTypes CBeetle::GetCollisionResponseType(const zeus::CVector3f& pos, const zeus::CVector3f& dir,\n                                                                const CWeaponMode& wMode,\n                                                                EProjectileAttrib attribs) const {\n  if (x450_bodyController->IsFrozen() && wMode.GetType() == EWeaponType::Ice)\n    return EWeaponCollisionResponseTypes::None;\n  if (x838_25_burrowing)\n    return EWeaponCollisionResponseTypes::Unknown69;\n  if (x3fc_flavor == EFlavorType::One) {\n    if (GetTransform().basis[1].dot(dir) > 0.f &&\n        GetTransform().basis[1].dot(zeus::CUnitVector3f(pos - GetBoundingBox().center())) < -0.5f)\n      return EWeaponCollisionResponseTypes::Unknown44;\n    if (!x744_platingVuln.WeaponHurts(wMode, false))\n      return EWeaponCollisionResponseTypes::Unknown69;\n  }\n  return EWeaponCollisionResponseTypes::Unknown19;\n}\n\nvoid CBeetle::DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) {\n  bool handled = false;\n  switch (type) {\n  case EUserEventType::ChangeMaterial:\n    AddMaterial(EMaterialTypes::Character, EMaterialTypes::Solid, mgr);\n    x328_25_verticalMovement = true;\n    RemoveMaterial(EMaterialTypes::GroundCollider, mgr);\n    handled = true;\n    break;\n  case EUserEventType::GenerateEnd:\n    x328_25_verticalMovement = false;\n    AddMaterial(EMaterialTypes::GroundCollider, mgr);\n    handled = true;\n    break;\n  case EUserEventType::DamageOn: {\n    zeus::CVector3f center =\n        GetTransform() * (x64_modelData->GetScale() * GetLocatorTransform(\"LCTR_GARMOUTH\"sv).origin);\n    zeus::CVector3f extent = x64_modelData->GetScale() * zeus::CVector3f(2.f, 2.f, 0.5f);\n    if (zeus::CAABox(center - extent, center + extent).intersects(mgr.GetPlayer().GetBoundingBox())) {\n      mgr.ApplyDamage(GetUniqueId(), mgr.GetPlayer().GetUniqueId(), GetUniqueId(), x584_touchDamage,\n                      CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), zeus::skZero3f);\n    }\n    handled = true;\n    break;\n  }\n  case EUserEventType::Delete:\n    handled = true;\n    break;\n  default:\n    break;\n  }\n  if (!handled)\n    CPatterned::DoUserAnimEvent(mgr, node, type, dt);\n}\n\nvoid CBeetle::CollidedWith(TUniqueId uid, const CCollisionInfoList& list, CStateManager& mgr) {\n  static CMaterialList envList(EMaterialTypes::Ceiling, EMaterialTypes::Wall);\n  for (const auto& c : list) {\n    if (c.GetMaterialLeft().Intersection(envList) != 0 &&\n        x450_bodyController->GetCurrentStateId() == pas::EAnimationState::MeleeAttack) {\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBodyStateCmd(EBodyStateCmd::NextState));\n      SetVelocityWR(zeus::skZero3f);\n    }\n  }\n  CPatterned::CollidedWith(uid, list, mgr);\n}\n\nvoid CBeetle::Death(CStateManager& mgr, const zeus::CVector3f& direction, EScriptObjectState state) {\n  if (x400_25_alive) {\n    if (x3fc_flavor == EFlavorType::One) {\n      zeus::CTransform backupXf = GetTransform();\n      SetTransform(GetLctrTransform(\"Target_Tail\"sv));\n      SendScriptMsgs(EScriptObjectState::DeathRattle, mgr, EScriptObjectMessage::None);\n      SetTransform(backupXf);\n    }\n    CPatterned::Death(mgr, direction, state);\n  }\n}\n\nvoid CBeetle::TakeDamage(const zeus::CVector3f& direction, float magnitude) { x428_damageCooldownTimer = 0.33f; }\n\nbool CBeetle::IsListening() const { return true; }\n\nzeus::CVector3f CBeetle::GetOrigin(const CStateManager& mgr, const CTeamAiRole& role,\n                                   const zeus::CVector3f& aimPos) const {\n  float midRange = (x2fc_minAttackRange + x300_maxAttackRange) * 0.5f;\n  zeus::CVector3f playerToThis = GetTranslation() - aimPos;\n  if (playerToThis.canBeNormalized())\n    return aimPos + playerToThis.normalized() * midRange;\n  else\n    return aimPos + GetTransform().basis[1] * midRange;\n}\n\nvoid CBeetle::FollowPattern(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x568_stateProg = 1;\n    break;\n  case EStateMsg::Update:\n    switch (x568_stateProg) {\n    case 1:\n      if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::Step) {\n        x568_stateProg = 3;\n      } else if (IsOnGround()) {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCStepCmd(pas::EStepDirection::Left, pas::EStepType::Normal));\n      }\n      break;\n    case 3:\n      if (x450_bodyController->GetCurrentStateId() != pas::EAnimationState::Step && IsOnGround()) {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCStepCmd(pas::EStepDirection::Right, pas::EStepType::Normal));\n        x568_stateProg = 2;\n      }\n      break;\n    case 2:\n      if (x450_bodyController->GetCurrentStateId() != pas::EAnimationState::Step)\n        x568_stateProg = x814_attackDelayTimer <= 0.f ? 4 : 1;\n      break;\n    default:\n      break;\n    }\n    x450_bodyController->GetCommandMgr().DeliverTargetVector(mgr.GetPlayer().GetTranslation() - GetTranslation());\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CBeetle::RefinePathFindDest(CStateManager& mgr, zeus::CVector3f& dest) {\n  dest = mgr.GetPlayer().GetTranslation();\n  if (CTeamAiRole* role = CTeamAiMgr::GetTeamAiRole(mgr, x570_aiMgr, GetUniqueId())) {\n    dest = role->GetTeamPosition();\n  } else {\n    zeus::CVector3f thisToDest = dest - GetTranslation();\n    dest += (thisToDest.canBeNormalized() ? thisToDest.normalized() : GetTransform().basis[1]) *\n            (0.5f * (x2fc_minAttackRange + x300_maxAttackRange));\n  }\n}\n\nvoid CBeetle::SeparateFromMelees(CStateManager& mgr) {\n  for (CEntity* ent : mgr.GetListeningAiObjectList()) {\n    if (TCastToPtr<CPatterned> ai = ent) {\n      if (ai.GetPtr() != this && ai->GetAreaIdAlways() == GetAreaIdAlways()) {\n        float dist = 4.f;\n        if (CTeamAiRole* role = CTeamAiMgr::GetTeamAiRole(mgr, x570_aiMgr, ai->GetUniqueId()))\n          if (role->GetTeamAiRole() == CTeamAiRole::ETeamAiRole::Melee)\n            dist *= 2.f;\n        zeus::CVector3f move = x45c_steeringBehaviors.Separation(*this, ai->GetTranslation(), dist);\n        if (!move.isZero())\n          x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(move, zeus::skZero3f, 1.f));\n      }\n    }\n  }\n}\n\nvoid CBeetle::PathFind(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    if (GetSearchPath()) {\n      RefinePathFindDest(mgr, x2e0_destPos);\n      CPatterned::PathFind(mgr, msg, dt);\n    }\n    break;\n  case EStateMsg::Update: {\n    zeus::CVector3f dest = mgr.GetPlayer().GetTranslation();\n    RefinePathFindDest(mgr, dest);\n    zeus::CVector3f delta = dest - GetTranslation();\n    zeus::CVector3f move;\n    if (!PathShagged(mgr, 0.f) && !x5fc_pathFindSearch.IsOver()) {\n      CPatterned::PathFind(mgr, msg, dt);\n      move = x450_bodyController->GetCommandMgr().GetMoveVector();\n    } else {\n      move = x45c_steeringBehaviors.Arrival(*this, dest, 5.f);\n    }\n    if (GetTransform().basis[1].dot(move) >= 0.f || GetTransform().basis[1].dot(delta) <= 0.f) {\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(move, zeus::skZero3f, 1.f));\n    } else {\n      zeus::CVector3f face = delta.canBeNormalized() ? delta.normalized() : GetTransform().basis[1];\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(move, face, 1.f));\n    }\n    SeparateFromMelees(mgr);\n    break;\n  }\n  default:\n    break;\n  }\n}\n\nvoid CBeetle::TargetPlayer(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    if (CTeamAiRole* role = CTeamAiMgr::GetTeamAiRole(mgr, x570_aiMgr, GetUniqueId()))\n      x2e0_destPos = role->GetTeamPosition();\n    else\n      x2e0_destPos = mgr.GetPlayer().GetTranslation();\n    x2dc_destObj = mgr.GetPlayer().GetUniqueId();\n    x2ec_reflectedDestPos = GetTranslation();\n    x328_24_inPosition = false;\n  }\n}\n\nvoid CBeetle::Generate(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    if (x56c_entranceType == EEntranceType::FacePlayer || x450_bodyController->GetActive()) {\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBCGenerateCmd(pas::EGenerateType::Zero));\n      SetTransform(zeus::lookAt(GetTranslation(), mgr.GetPlayer().GetTranslation()));\n    } else {\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBCGenerateCmd(pas::EGenerateType::Zero, x388_anim));\n    }\n    if (!x450_bodyController->GetActive())\n      x450_bodyController->Activate(mgr);\n    RemoveMaterial(EMaterialTypes::Character, EMaterialTypes::Solid, mgr);\n    x568_stateProg = 0;\n    break;\n  case EStateMsg::Update:\n    switch (x568_stateProg) {\n    case 0:\n      if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::Generate) {\n        x568_stateProg = 2;\n        x5a8_animTimeRem = x450_bodyController->GetAnimTimeRemaining();\n      } else {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCGenerateCmd(pas::EGenerateType::Zero));\n      }\n      break;\n    case 2:\n      if (x450_bodyController->GetCurrentStateId() != pas::EAnimationState::Generate) {\n        x568_stateProg = 4;\n      } else if (x68_material.HasMaterial(EMaterialTypes::Solid) && x5a8_animTimeRem > 0.f) {\n        EntityList nearList;\n        mgr.BuildNearList(nearList, zeus::CAABox(GetTranslation() - 5.f, GetTranslation() + 5.f),\n                          CMaterialFilter::MakeInclude({EMaterialTypes::Solid}), this);\n        if (!nearList.empty()) {\n          zeus::CVector3f totalSep;\n          float sepFactor = 5.f * dt / x5a8_animTimeRem;\n          for (TUniqueId id : nearList) {\n            if (CActor* act = static_cast<CActor*>(mgr.ObjectById(id))) {\n              zeus::CVector3f sep = x45c_steeringBehaviors.Separation(*this, act->GetTranslation(), 5.f);\n              sep.z() = std::max(0.f, float(sep.z()));\n              totalSep += sep * sepFactor;\n            }\n          }\n          SetTranslation(GetTranslation() + totalSep);\n        }\n      }\n      break;\n    default:\n      break;\n    }\n    break;\n  case EStateMsg::Deactivate:\n    AddMaterial(EMaterialTypes::Character, EMaterialTypes::Solid, EMaterialTypes::GroundCollider, mgr);\n    x328_25_verticalMovement = false;\n    x838_25_burrowing = false;\n    if (x328_26_solidCollision)\n      DeathDelete(mgr);\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CBeetle::Deactivate(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x568_stateProg = 0;\n    SquadRemove(mgr);\n    x818_stateFinishTimer = 0.f;\n    break;\n  case EStateMsg::Update:\n    switch (x568_stateProg) {\n    case 0:\n      if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::Generate) {\n        RemoveMaterial(EMaterialTypes::Character, EMaterialTypes::Solid, EMaterialTypes::Target, EMaterialTypes::Orbit,\n                       mgr);\n        mgr.GetPlayer().TryToBreakOrbit(GetUniqueId(), CPlayer::EPlayerOrbitRequest::ActivateOrbitSource, mgr);\n        x838_25_burrowing = true;\n        x5a8_animTimeRem = x450_bodyController->GetAnimTimeRemaining();\n        x568_stateProg = 2;\n      } else if (IsOnGround()) {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCGenerateCmd(pas::EGenerateType::One));\n      } else {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(\n            x45c_steeringBehaviors.Seek(*this, mgr.GetPlayer().GetTranslation()), zeus::skZero3f, 1.f));\n      }\n      break;\n    case 2:\n      if (x450_bodyController->GetCurrentStateId() != pas::EAnimationState::Generate) {\n        x568_stateProg = 3;\n        x830_intoGroundFactor = 0.f;\n        auto aabb = GetBoundingBox();\n        SetTranslation((aabb.max.z() - aabb.min.z()) * 3.f * zeus::skDown + GetTranslation());\n      } else {\n        float remTime = x450_bodyController->GetAnimTimeRemaining();\n        x830_intoGroundFactor = x5a8_animTimeRem > 0.f ? remTime / x5a8_animTimeRem : 0.f;\n      }\n      break;\n    case 3:\n      x818_stateFinishTimer += dt;\n      if (x818_stateFinishTimer >= 0.75f) {\n        SendScriptMsgs(EScriptObjectState::DeactivateState, mgr, EScriptObjectMessage::None);\n        mgr.FreeScriptObject(GetUniqueId());\n        x568_stateProg = 4;\n        x830_intoGroundFactor = 0.f;\n      } else {\n        auto aabb = GetBoundingBox();\n        SetTranslation((aabb.max.z() - aabb.min.z()) * 4.f * zeus::skDown * dt + GetTranslation());\n      }\n      break;\n    default:\n      break;\n    }\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CBeetle::Attack(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x838_26_canSkid = false;\n    x838_24_hitSomething = false;\n    x2e0_destPos = mgr.GetPlayer().GetTranslation();\n    x2e0_destPos.z() = GetTranslation().z();\n    x568_stateProg = 0;\n    break;\n  case EStateMsg::Update:\n    switch (x568_stateProg) {\n    case 0:\n      if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::MeleeAttack) {\n        x568_stateProg = 1;\n        x838_26_canSkid = true;\n      } else if (IsOnGround()) {\n        zeus::CVector3f aimPos = mgr.GetPlayer().GetAimPosition(mgr, 0.f);\n        aimPos.z() = GetTranslation().z();\n        zeus::CVector3f aimDelta = aimPos - GetTranslation();\n        aimDelta.z() = 0.f;\n        if (aimDelta.canBeNormalized())\n          aimPos += aimDelta.normalized() * 5.f;\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCMeleeAttackCmd(pas::ESeverity::One, aimPos));\n      }\n      break;\n    case 1:\n      if (x450_bodyController->GetCurrentStateId() != pas::EAnimationState::MeleeAttack) {\n        x568_stateProg = 4;\n      } else if (IsOnGround()) {\n        x450_bodyController->GetCommandMgr().DeliverTargetVector(x2e0_destPos - GetTranslation());\n      } else {\n        x568_stateProg = 2;\n      }\n      break;\n    case 2:\n      if (x450_bodyController->GetCurrentStateId() != pas::EAnimationState::MeleeAttack)\n        x568_stateProg = 4;\n      break;\n    default:\n      break;\n    }\n    if (x328_26_solidCollision)\n      x838_24_hitSomething = true;\n    break;\n  case EStateMsg::Deactivate:\n    CTeamAiMgr::ResetTeamAiRole(CTeamAiMgr::EAttackType::Melee, mgr, x570_aiMgr, GetUniqueId(), true);\n    x814_attackDelayTimer = mgr.GetActiveRandom()->Float() * x308_attackTimeVariation + x304_averageAttackTime;\n    break;\n  }\n}\n\nvoid CBeetle::JumpBack(CStateManager&, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x568_stateProg = 0;\n    break;\n  case EStateMsg::Update:\n    switch (x568_stateProg) {\n    case 0:\n      if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::Step) {\n        x568_stateProg = 2;\n      } else if (IsOnGround()) {\n        x450_bodyController->GetCommandMgr().DeliverCmd(\n            CBCStepCmd(pas::EStepDirection::Backward, pas::EStepType::Normal));\n      }\n      break;\n    case 2:\n      if (x450_bodyController->GetCurrentStateId() != pas::EAnimationState::Step)\n        x568_stateProg = 4;\n      break;\n    default:\n      break;\n    }\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CBeetle::DoubleSnap(CStateManager&, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x568_stateProg = 0;\n    break;\n  case EStateMsg::Update:\n    switch (x568_stateProg) {\n    case 0:\n      if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::MeleeAttack) {\n        x568_stateProg = 2;\n      } else {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCMeleeAttackCmd(pas::ESeverity::Zero));\n      }\n      break;\n    case 2:\n      if (x450_bodyController->GetCurrentStateId() != pas::EAnimationState::MeleeAttack)\n        x568_stateProg = 4;\n      else\n        x450_bodyController->GetCommandMgr().DeliverTargetVector(x2e0_destPos - GetTranslation());\n      break;\n    default:\n      break;\n    }\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CBeetle::Shuffle(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x328_24_inPosition = false;\n    break;\n  case EStateMsg::Update: {\n    zeus::CVector3f playerPos = mgr.GetPlayer().GetTranslation();\n    zeus::CVector3f playerToThis = GetTranslation() - playerPos;\n    float midRange = 0.5f * (x2fc_minAttackRange + x300_maxAttackRange);\n    zeus::CVector3f playerLimit;\n    if (playerToThis.canBeNormalized())\n      playerLimit = playerToThis.normalized() * midRange + playerPos;\n    else\n      playerLimit = GetTransform().basis[1] * midRange + playerPos;\n    if ((GetTranslation() - playerLimit).magSquared() > 4.f) {\n      pas::EStepDirection dir = GetStepDirection(-playerToThis);\n      switch (dir) {\n      case pas::EStepDirection::Forward:\n      case pas::EStepDirection::Backward: {\n        zeus::CVector3f move = x45c_steeringBehaviors.Arrival(*this, x2e0_destPos, x300_maxAttackRange);\n        if (GetTransform().basis[1].dot(move) >= 0.f || GetTransform().basis[1].dot(playerToThis) >= 0.f) {\n          x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(move, zeus::skZero3f, 1.f));\n        } else {\n          x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(\n              move, playerToThis.canBeNormalized() ? -playerToThis.normalized() : GetTransform().basis[1], 1.f));\n        }\n        break;\n      }\n      case pas::EStepDirection::Left:\n      case pas::EStepDirection::Right: {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCStepCmd(dir, pas::EStepType::Normal));\n        break;\n      }\n      default:\n        break;\n      }\n      x450_bodyController->GetCommandMgr().DeliverTargetVector(-playerToThis);\n    } else {\n      x328_24_inPosition = true;\n    }\n    break;\n  }\n  default:\n    break;\n  }\n}\n\nvoid CBeetle::TurnAround(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x568_stateProg = 0;\n    break;\n  case EStateMsg::Update:\n    switch (x568_stateProg) {\n    case 0:\n      if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::Turn) {\n        x568_stateProg = 2;\n      } else {\n        zeus::CVector3f thisToPlayer = mgr.GetPlayer().GetTranslation() - GetTranslation();\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(\n            zeus::skZero3f, (thisToPlayer.magnitude() > FLT_EPSILON) ? thisToPlayer.normalized() : zeus::skZero3f,\n            1.f));\n      }\n      break;\n    case 2:\n      if (x450_bodyController->GetCurrentStateId() != pas::EAnimationState::Turn)\n        x568_stateProg = 4;\n      break;\n    default:\n      break;\n    }\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CBeetle::Skid(CStateManager&, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    if (IsOnGround() && x838_26_canSkid) {\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBCSlideCmd(pas::ESlideType::Zero, GetTransform().basis[1]));\n      x568_stateProg = 2;\n    }\n    break;\n  case EStateMsg::Update:\n    if (x450_bodyController->GetCurrentStateId() != pas::EAnimationState::Slide)\n      x568_stateProg = 4;\n    break;\n  case EStateMsg::Deactivate:\n    x838_26_canSkid = false;\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CBeetle::Taunt(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x450_bodyController->GetCommandMgr().DeliverCmd(\n        CBCTauntCmd(mgr.GetActiveRandom()->Float() < 0.75f ? pas::ETauntType::One : pas::ETauntType::Zero));\n    x568_stateProg = 2;\n    break;\n  case EStateMsg::Update:\n    if (x450_bodyController->GetCurrentStateId() != pas::EAnimationState::Taunt)\n      x568_stateProg = 4;\n    else\n      x450_bodyController->GetCommandMgr().DeliverTargetVector(mgr.GetPlayer().GetTranslation() - GetTranslation());\n    break;\n  default:\n    break;\n  }\n}\n\ns32 CBeetle::FindFurthestRetreatPoint(CStateManager& mgr) const {\n  s32 ret = -1;\n  if (x6e0_retreatPoints.empty())\n    return ret;\n  zeus::CVector2f playerPos = mgr.GetPlayer().GetTranslation().toVec2f();\n  ret = mgr.GetActiveRandom()->Range(0, s32(x6e0_retreatPoints.size() - 1));\n  float maxDist = (playerPos - x6e0_retreatPoints[ret].toVec2f()).magSquared();\n  if (maxDist < 100.f) {\n    s32 i = 0;\n    for (const auto& v : x6e0_retreatPoints) {\n      float dist = (playerPos - v.toVec2f()).magSquared();\n      if (dist > maxDist) {\n        maxDist = dist;\n        ret = i;\n      }\n      ++i;\n    }\n  }\n  return ret;\n}\n\nvoid CBeetle::Retreat(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x568_stateProg = 0;\n    SquadRemove(mgr);\n    x818_stateFinishTimer = 0.f;\n    x3b4_speed = 2.f * x81c_;\n    break;\n  case EStateMsg::Update:\n    switch (x568_stateProg) {\n    case 0:\n      if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::Generate) {\n        RemoveMaterial(EMaterialTypes::Character, EMaterialTypes::Solid, EMaterialTypes::Target, EMaterialTypes::Orbit,\n                       mgr);\n        mgr.GetPlayer().TryToBreakOrbit(GetUniqueId(), CPlayer::EPlayerOrbitRequest::ActivateOrbitSource, mgr);\n        x838_25_burrowing = true;\n        x5a8_animTimeRem = x450_bodyController->GetAnimTimeRemaining();\n        x568_stateProg = 2;\n      } else if (IsOnGround()) {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCGenerateCmd(pas::EGenerateType::One));\n      } else {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(\n            x45c_steeringBehaviors.Seek(*this, mgr.GetPlayer().GetTranslation()), zeus::skZero3f, 1.f));\n      }\n      break;\n    case 2:\n      if (x450_bodyController->GetCurrentStateId() != pas::EAnimationState::Generate) {\n        x568_stateProg = 3;\n        x830_intoGroundFactor = 0.f;\n        auto aabb = GetBoundingBox();\n        SetTranslation((aabb.max.z() - aabb.min.z()) * 3.f * zeus::skDown + GetTranslation());\n      } else {\n        float remTime = x450_bodyController->GetAnimTimeRemaining();\n        x830_intoGroundFactor = (x5a8_animTimeRem > 0.f) ? remTime / x5a8_animTimeRem : 0.f;\n      }\n      break;\n    case 3:\n      x818_stateFinishTimer += dt;\n      if (x818_stateFinishTimer >= x834_retreatTime) {\n        x568_stateProg = 4;\n        x830_intoGroundFactor = 0.f;\n      } else {\n        auto aabb = GetBoundingBox();\n        zeus::CVector3f downVec = (aabb.max.z() - aabb.min.z()) * 3.f * zeus::skDown;\n        SetTranslation(((x834_retreatTime > 0.f) ? (1.f / x834_retreatTime) * downVec : downVec) * dt +\n                       GetTranslation());\n      }\n      break;\n    default:\n      break;\n    }\n    break;\n  case EStateMsg::Deactivate: {\n    s32 idx = FindFurthestRetreatPoint(mgr);\n    if (idx != -1) {\n      SetTranslation(x6e0_retreatPoints[idx]);\n      AddMaterial(EMaterialTypes::Character, EMaterialTypes::Solid, EMaterialTypes::Target, EMaterialTypes::Orbit, mgr);\n    } else {\n      SendScriptMsgs(EScriptObjectState::DeactivateState, mgr, EScriptObjectMessage::None);\n      mgr.FreeScriptObject(GetUniqueId());\n    }\n    x400_24_hitByPlayerProjectile = false;\n    x3b4_speed = x81c_;\n    x838_25_burrowing = false;\n    break;\n  }\n  }\n}\n\nbool CBeetle::InAttackPosition(CStateManager& mgr, float arg) {\n  float distSq = (mgr.GetPlayer().GetTranslation() - GetTranslation()).magSquared();\n  if (distSq > x2fc_minAttackRange * x2fc_minAttackRange && distSq < x300_maxAttackRange * x300_maxAttackRange)\n    if (SpotPlayer(mgr, x3c8_leashRadius))\n      return true;\n  return false;\n}\n\nbool CBeetle::PathShagged(CStateManager&, float arg) { return false; }\n\nbool CBeetle::InRange(CStateManager& mgr, float arg) {\n  zeus::CVector3f targetPos = mgr.GetPlayer().GetTranslation();\n  if (CTeamAiRole* role = CTeamAiMgr::GetTeamAiRole(mgr, x570_aiMgr, GetUniqueId())) {\n    if (role->GetTeamAiRole() == CTeamAiRole::ETeamAiRole::Melee)\n      targetPos = role->GetTeamPosition();\n  }\n  return (targetPos - GetTranslation()).magSquared() < 100.f;\n}\n\nbool CBeetle::PatternOver(CStateManager& mgr, float arg) { return AnimOver(mgr, arg); }\n\nbool CBeetle::HasAttackPattern(CStateManager&, float arg) { return true; }\n\nbool CBeetle::AnimOver(CStateManager&, float arg) { return x568_stateProg == 4; }\n\nbool CBeetle::ShouldAttack(CStateManager& mgr, float arg) {\n  if (x814_attackDelayTimer <= 0.f) {\n    if (TCastToPtr<CTeamAiMgr> tmgr = mgr.ObjectById(x570_aiMgr)) {\n      if (tmgr->HasTeamAiRole(GetUniqueId()))\n        return tmgr->AddMeleeAttacker(GetUniqueId());\n    }\n    return true;\n  }\n  return false;\n}\n\nbool CBeetle::ShouldDoubleSnap(CStateManager& mgr, float arg) {\n  if (!GetSearchPath() && IsOnGround()) {\n    zeus::CVector3f targetPos = mgr.GetPlayer().GetTranslation();\n    float dist = x5a0_headbuttDist + x300_maxAttackRange;\n    if (CTeamAiRole* role = CTeamAiMgr::GetTeamAiRole(mgr, x570_aiMgr, GetUniqueId()))\n      targetPos = role->GetTeamPosition();\n    zeus::CVector3f delta = targetPos - GetTranslation();\n    if (delta.magSquared() > dist * dist && GetTransform().basis[1].dot(delta.normalized()) > 0.98f) {\n      EntityList nearList;\n      mgr.BuildNearList(nearList, GetTranslation(), GetTransform().basis[1], x5a0_headbuttDist,\n                        CMaterialFilter::MakeInclude({EMaterialTypes::Character}), this);\n      TUniqueId bestId = kInvalidUniqueId;\n      CRayCastResult res =\n          mgr.RayWorldIntersection(bestId, GetTranslation(), GetTransform().basis[1], x5a0_headbuttDist,\n                                   CMaterialFilter::MakeInclude({EMaterialTypes::Solid}), nearList);\n      if (res.IsInvalid())\n        return true;\n    }\n  }\n  return false;\n}\n\nbool CBeetle::ShouldTurn(CStateManager& mgr, float arg) {\n  return zeus::CVector2f::getAngleDiff(GetTransform().basis[1].toVec2f(),\n                                       (mgr.GetPlayer().GetTranslation() - GetTranslation()).toVec2f()) >\n         zeus::degToRad(30.f);\n}\n\nbool CBeetle::HitSomething(CStateManager&, float arg) { return x838_24_hitSomething; }\n\nbool CBeetle::ShouldJumpBack(CStateManager& mgr, float arg) {\n  zeus::CVector3f backDir = -GetTransform().basis[1];\n  const auto& aabb = GetBaseBoundingBox();\n  zeus::CVector3f pos = GetTranslation() + zeus::CVector3f(0.f, 0.f, (aabb.max.z() - aabb.min.z()) * 0.5f);\n  EntityList nearList;\n  mgr.BuildNearList(nearList, pos, backDir, x5a4_jumpBackwardDist,\n                    CMaterialFilter::MakeInclude({EMaterialTypes::Character}), this);\n  TUniqueId bestId = kInvalidUniqueId;\n  CRayCastResult res = mgr.RayWorldIntersection(bestId, pos, backDir, x5a4_jumpBackwardDist,\n                                                CMaterialFilter::MakeInclude({EMaterialTypes::Solid}), nearList);\n  return res.IsInvalid();\n}\n\nbool CBeetle::Stuck(CStateManager&, float arg) { return x820_posDeviationCounter > 30; }\n\nbool CBeetle::NoPathNodes(CStateManager&, float arg) { return false; }\n\nbool CBeetle::ShouldTaunt(CStateManager& mgr, float arg) {\n  if (CTeamAiRole* role = CTeamAiMgr::GetTeamAiRole(mgr, x570_aiMgr, GetUniqueId())) {\n    if (role->GetTeamAiRole() == CTeamAiRole::ETeamAiRole::Unknown ||\n        role->GetTeamAiRole() == CTeamAiRole::ETeamAiRole::Unassigned) {\n      return (role->GetTeamPosition() - GetTranslation()).magSquared() < 100.f;\n    }\n  }\n  return false;\n}\n\nbool CBeetle::ShotAt(CStateManager&, float arg) {\n  if (x3fc_flavor == EFlavorType::Two && x6e0_retreatPoints.size() > 0)\n    return x400_24_hitByPlayerProjectile;\n  return false;\n}\n\nvoid CBeetle::Burn(float duration, float damage) {\n  CDamageVulnerability dVuln = *GetDamageVulnerability();\n  if (!x838_25_burrowing && x3fc_flavor == EFlavorType::One)\n    dVuln = x7ac_tailVuln;\n  switch (dVuln.GetVulnerability(CWeaponMode(EWeaponType::Wave), false)) {\n  case EVulnerability::Weak:\n    x450_bodyController->SetOnFire(1.5f * duration);\n    x3ec_pendingFireDamage = 1.5f * damage;\n    break;\n  case EVulnerability::Normal:\n    x450_bodyController->SetOnFire(duration);\n    x3ec_pendingFireDamage = damage;\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CBeetle::Shock(CStateManager& mgr, float duration, float damage) {\n  CDamageVulnerability dVuln = *GetDamageVulnerability();\n  if (!x838_25_burrowing && x3fc_flavor == EFlavorType::One)\n    dVuln = x7ac_tailVuln;\n  switch (dVuln.GetVulnerability(CWeaponMode(EWeaponType::Wave), false)) {\n  case EVulnerability::Weak:\n    x450_bodyController->SetElectrocuting(1.5f * duration);\n    x3f0_pendingShockDamage = 1.5f * damage;\n    break;\n  case EVulnerability::Normal:\n    x450_bodyController->SetElectrocuting(duration);\n    x3f0_pendingShockDamage = damage;\n    break;\n  default:\n    break;\n  }\n}\n\nCPathFindSearch* CBeetle::GetSearchPath() { return &x5fc_pathFindSearch; }\n\nfloat CBeetle::GetGravityConstant() const { return 4.f * CPhysicsActor::GravityConstant(); }\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CBeetle.hpp",
    "content": "#pragma once\n\n#include <optional>\n\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/World/CDamageInfo.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n#include \"Runtime/World/CPathFindSearch.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\n\nnamespace MP1 {\nclass CBeetle : public CPatterned {\npublic:\n  enum class EEntranceType : u32 { FacePlayer, UseOrientation };\n\nprivate:\n  s32 x568_stateProg = -1;\n  EEntranceType x56c_entranceType;\n  TUniqueId x570_aiMgr = kInvalidUniqueId;\n  zeus::CVector3f x574_tailAimReference;\n  float x580_f3;\n  CDamageInfo x584_touchDamage;\n  float x5a0_headbuttDist = FLT_MAX;\n  float x5a4_jumpBackwardDist = FLT_MAX;\n  float x5a8_animTimeRem = 0.f;\n  std::optional<CModelData> x5ac_tailModel;\n  CPathFindSearch x5fc_pathFindSearch;\n  rstl::reserved_vector<zeus::CVector3f, 8> x6e0_retreatPoints;\n  CDamageVulnerability x744_platingVuln;\n  CDamageVulnerability x7ac_tailVuln;\n  float x814_attackDelayTimer;\n  float x818_stateFinishTimer = FLT_MAX;\n  float x81c_ = x3b4_speed;\n  u32 x820_posDeviationCounter = 0;\n  zeus::CVector3f x824_predictPos;\n  float x830_intoGroundFactor = 1.f;\n  float x834_retreatTime;\n  bool x838_24_hitSomething : 1 = false;\n  bool x838_25_burrowing : 1 = false;\n  bool x838_26_canSkid : 1 = false;\n\n  void SquadAdd(CStateManager& mgr);\n  void SquadRemove(CStateManager& mgr);\n  void RefinePathFindDest(CStateManager& mgr, zeus::CVector3f& dest);\n  void SeparateFromMelees(CStateManager& mgr);\n  void SetupRetreatPoints(CStateManager& mgr);\n  s32 FindFurthestRetreatPoint(CStateManager& mgr) const;\n\npublic:\n  DEFINE_PATTERNED(Beetle);\n  CBeetle(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf, CModelData&& mData,\n          const CPatternedInfo& pInfo, CPatterned::EFlavorType flavor, CBeetle::EEntranceType entranceType,\n          const CDamageInfo& touchDamage, const CDamageVulnerability& platingVuln,\n          const zeus::CVector3f& tailAimReference, float initialAttackDelay, float retreatTime, float f3,\n          const CDamageVulnerability& tailVuln, const CActorParameters& aParams,\n          const std::optional<CStaticRes>& tailModel);\n\n  void Accept(IVisitor& visitor) override;\n  void Think(float dt, CStateManager& mgr) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) override;\n  void PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) override;\n  void Render(CStateManager& mgr) override;\n\n  const CDamageVulnerability* GetDamageVulnerability() const override;\n  const CDamageVulnerability* GetDamageVulnerability(const zeus::CVector3f& pos, const zeus::CVector3f& dir,\n                                                     const CDamageInfo& dInfo) const override;\n  zeus::CVector3f GetOrbitPosition(const CStateManager&) const override;\n  zeus::CVector3f GetAimPosition(const CStateManager& mgr, float) const override;\n  EWeaponCollisionResponseTypes GetCollisionResponseType(const zeus::CVector3f& pos, const zeus::CVector3f& dir,\n                                                         const CWeaponMode& wMode,\n                                                         EProjectileAttrib attribs) const override;\n  void DoUserAnimEvent(CStateManager&, const CInt32POINode&, EUserEventType, float dt) override;\n  void CollidedWith(TUniqueId, const CCollisionInfoList&, CStateManager& mgr) override;\n  void Death(CStateManager& mgr, const zeus::CVector3f& direction, EScriptObjectState state) override;\n  void TakeDamage(const zeus::CVector3f& direction, float magnitude) override;\n  bool IsListening() const override;\n  zeus::CVector3f GetOrigin(const CStateManager& mgr, const CTeamAiRole& role,\n                            const zeus::CVector3f& aimPos) const override;\n\n  void FollowPattern(CStateManager&, EStateMsg msg, float dt) override;\n  void PathFind(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void TargetPlayer(CStateManager&, EStateMsg msg, float dt) override;\n  void Generate(CStateManager&, EStateMsg msg, float dt) override;\n  void Deactivate(CStateManager&, EStateMsg msg, float dt) override;\n  void Attack(CStateManager&, EStateMsg msg, float dt) override;\n  void JumpBack(CStateManager&, EStateMsg msg, float dt) override;\n  void DoubleSnap(CStateManager&, EStateMsg msg, float dt) override;\n  void Shuffle(CStateManager&, EStateMsg msg, float dt) override;\n  void TurnAround(CStateManager&, EStateMsg msg, float dt) override;\n  void Skid(CStateManager&, EStateMsg msg, float dt) override;\n  void Taunt(CStateManager&, EStateMsg msg, float dt) override;\n  void Retreat(CStateManager&, EStateMsg msg, float dt) override;\n\n  bool InAttackPosition(CStateManager&, float arg) override;\n  bool PathShagged(CStateManager&, float arg) override;\n  bool InRange(CStateManager&, float arg) override;\n  bool PatternOver(CStateManager&, float arg) override;\n  bool HasAttackPattern(CStateManager&, float arg) override;\n  bool AnimOver(CStateManager&, float arg) override;\n  bool ShouldAttack(CStateManager&, float arg) override;\n  bool ShouldDoubleSnap(CStateManager&, float arg) override;\n  bool ShouldTurn(CStateManager&, float arg) override;\n  bool HitSomething(CStateManager&, float arg) override;\n  bool ShouldJumpBack(CStateManager&, float arg) override;\n  bool Stuck(CStateManager&, float arg) override;\n  bool NoPathNodes(CStateManager&, float arg) override;\n  bool ShouldTaunt(CStateManager&, float arg) override;\n  bool ShotAt(CStateManager&, float arg) override;\n\n  void Burn(float duration, float damage) override;\n  void Shock(CStateManager& mgr, float duration, float damage) override;\n\n  CPathFindSearch* GetSearchPath() override;\n  float GetGravityConstant() const override;\n};\n} // namespace MP1\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/MP1/World/CBloodFlower.cpp",
    "content": "#include \"Runtime/MP1/World/CBloodFlower.hpp\"\n\n#include <array>\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/Particle/CGenDescription.hpp\"\n#include \"Runtime/Weapon/CProjectileWeapon.hpp\"\n#include \"Runtime/Weapon/CTargetableProjectile.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CScriptTrigger.hpp\"\n\nnamespace metaforce::MP1 {\nCBloodFlower::CBloodFlower(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                           CModelData&& mData, const CPatternedInfo& pInfo, CAssetId partId1, CAssetId wpscId1,\n                           const CActorParameters& actParms, CAssetId wpscId2, const CDamageInfo& dInfo1,\n                           const CDamageInfo& dInfo2, const CDamageInfo& dInfo3, CAssetId partId2, CAssetId partId3,\n                           CAssetId partId4, float f1, CAssetId partId5, u32 soundId)\n: CPatterned(ECharacter::BloodFlower, uid, name, EFlavorType::Zero, info, xf, std::move(mData), pInfo,\n             EMovementType::Ground, EColliderType::One, EBodyType::Restricted, actParms, EKnockBackVariant::Medium)\n, x568_podEffectDesc(g_SimplePool->GetObj({FOURCC('PART'), partId1}))\n, x574_podEffect(std::make_unique<CElementGen>(x568_podEffectDesc))\n, x578_projectileDesc(g_SimplePool->GetObj({FOURCC('WPSC'), wpscId1}))\n, x590_projectileInfo(wpscId2, dInfo1)\n, x5d4_visorSfx(CSfxManager::TranslateSFXID(soundId))\n, x5dc_projectileDamage(dInfo2)\n, x5f8_podDamage(dInfo3)\n, x614_(f1)\n, x618_(partId2)\n, x61c_(partId3)\n, x620_(partId4) {\n  x588_projectileOffset = GetModelData()->GetScale().z() * GetLocatorTransform(\"LCTR_FLOFLOWER\"sv).origin.z();\n  x574_podEffect->SetParticleEmission(false);\n  x574_podEffect->SetOrientation(xf.getRotation());\n  x574_podEffect->SetGlobalTranslation(xf.origin);\n  x574_podEffect->SetGlobalScale(GetModelData()->GetScale());\n  x590_projectileInfo.Token().Lock();\n  x460_knockBackController.SetAutoResetImpulse(false);\n  if (partId5.IsValid()) {\n    x5c4_visorParticle = g_SimplePool->GetObj({FOURCC('PART'), partId5});\n  }\n}\n\nvoid CBloodFlower::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  CPatterned::AcceptScriptMsg(msg, uid, mgr);\n  if (msg == EScriptObjectMessage::Registered) {\n    x450_bodyController->Activate(mgr);\n    x5b8_ = GetHealthInfo(mgr)->GetHP();\n  } else if (msg == EScriptObjectMessage::Damage) {\n    if (x450_bodyController->IsFrozen()) {\n      x450_bodyController->FrozenBreakout();\n    }\n    CalculateAttackTime(mgr);\n    UpdateFire(mgr);\n  }\n}\n\nvoid CBloodFlower::CalculateAttackTime(CStateManager& mgr) {\n  x584_curAttackTime = x308_attackTimeVariation * -mgr.GetActiveRandom()->Float();\n}\n\nvoid CBloodFlower::UpdateFire(CStateManager& mgr) {\n  if (x5d8_effectState == 0) {\n    TurnEffectsOn(0, mgr);\n  } else if (x5d8_effectState == 1) {\n    TurnEffectsOff(0, mgr);\n    TurnEffectsOn(1, mgr);\n  } else if (x5d8_effectState == 2) {\n    TurnEffectsOff(1, mgr);\n    TurnEffectsOn(2, mgr);\n  }\n\n  ++x5d8_effectState;\n}\n\nconstexpr std::array sFireEffects{\n    \"Fire1\"sv,\n    \"Fire2\"sv,\n    \"Fire3\"sv,\n};\n\nvoid CBloodFlower::TurnEffectsOn(u32 effect, CStateManager& mgr) {\n  GetModelData()->GetAnimationData()->SetParticleEffectState(sFireEffects[effect], true, mgr);\n}\n\nvoid CBloodFlower::TurnEffectsOff(u32 effect, CStateManager& mgr) {\n  GetModelData()->GetAnimationData()->SetParticleEffectState(sFireEffects[effect], false, mgr);\n}\n\nvoid CBloodFlower::Think(float dt, CStateManager& mgr) {\n  if (!GetActive())\n    return;\n\n  CPatterned::Think(dt, mgr);\n  x574_podEffect->Update(dt);\n  if (x5bc_projectileDelay > 0.f)\n    x5bc_projectileDelay -= dt;\n\n  x5c0_ += dt;\n}\n\nvoid CBloodFlower::DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) {\n  if (type == EUserEventType::Projectile) {\n    if (x58c_projectileState == 1 && x5bc_projectileDelay <= 0.f) {\n      LaunchPollenProjectile(GetLctrTransform(node.GetLocatorName()), mgr, x614_, 5);\n      x58c_projectileState = 0;\n      x5bc_projectileDelay = 0.5f;\n    }\n    return;\n  } else if (type == EUserEventType::Delete) {\n    if (x5d8_effectState > 0)\n      TurnEffectsOff((x5d8_effectState > 3 ? x5d8_effectState - 1 : 2), mgr);\n  }\n  CPatterned::DoUserAnimEvent(mgr, node, type, dt);\n}\n\nvoid CBloodFlower::LaunchPollenProjectile(const zeus::CTransform& xf, CStateManager& mgr, float var_f1,\n                                          s32 maxProjectiles) {\n  CProjectileInfo* proj = GetProjectileInfo();\n  TLockedToken<CWeaponDescription> projToken = proj->Token();\n\n  if (!projToken || !mgr.CanCreateProjectile(GetUniqueId(), EWeaponType::AI, maxProjectiles))\n    return;\n\n  zeus::CVector3f aimPos = mgr.GetPlayer().GetAimPosition(mgr, 0.f);\n\n  float zDiff = xf.origin.z() - aimPos.z();\n  float f2 = (zDiff > 0.f ? var_f1 : -zDiff + var_f1);\n  if (zDiff > 0.f)\n    var_f1 = zDiff + var_f1;\n  float f7 = std::sqrt(2.f * f2 / 4.905f) + std::sqrt(2.f * var_f1 / 4.905f);\n  float f4 = 1.f / f7;\n  zeus::CVector3f vel{f4 * (aimPos.x() - xf.origin.x()), f4 * (aimPos.y() - xf.origin.y()),\n                      2.4525f * f7 + (-zDiff / f7)};\n  if (CTargetableProjectile* targProj =\n          CreateArcProjectile(mgr, GetProjectileInfo()->Token(), zeus::CTransform::Translate(xf.origin),\n                              GetProjectileInfo()->GetDamage(), kInvalidUniqueId)) {\n    targProj->ProjectileWeapon().SetVelocity(CProjectileWeapon::GetTickPeriod() * vel);\n    targProj->ProjectileWeapon().SetGravity(CProjectileWeapon::GetTickPeriod() * zeus::CVector3f(0.f, 0.f, -4.905f));\n    mgr.AddObject(targProj);\n  }\n}\n\nvoid CBloodFlower::Render(CStateManager& mgr) {\n  CPatterned::Render(mgr);\n  x574_podEffect->Render();\n}\n\nEWeaponCollisionResponseTypes CBloodFlower::GetCollisionResponseType(const zeus::CVector3f&, const zeus::CVector3f&,\n                                                                     const CWeaponMode& weaponMode,\n                                                                     EProjectileAttrib) const {\n  const auto* const damageVulnerability = GetDamageVulnerability();\n\n  if (damageVulnerability->WeaponHurts(weaponMode, false)) {\n    return EWeaponCollisionResponseTypes::Unknown28;\n  }\n\n  return EWeaponCollisionResponseTypes::Unknown78;\n}\n\nbool CBloodFlower::ShouldAttack(CStateManager& mgr, float arg) {\n  if (TooClose(mgr, 0.f))\n    return false;\n\n  if (x584_curAttackTime <= x304_averageAttackTime)\n    return false;\n\n  return (mgr.GetPlayer().GetTranslation().z() + mgr.GetPlayer().GetEyeHeight() <\n          x588_projectileOffset + x614_ + GetTranslation().z());\n}\n\nbool CBloodFlower::ShouldTurn(CStateManager& mgr, float) {\n  if (TooClose(mgr, 0.f))\n    return false;\n\n  zeus::CVector3f frontVec = GetTransform().basis[1];\n  frontVec.z() = 0.f;\n  frontVec.normalize();\n  zeus::CVector3f posDiff = mgr.GetPlayer().GetTranslation() - GetTranslation();\n  posDiff.z() = 0.f;\n  posDiff.normalize();\n\n  return posDiff.dot(frontVec) < 0.99599999f;\n}\n\nvoid CBloodFlower::Active(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n    CalculateAttackTime(mgr);\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::LoopReaction, &CPatterned::TryLoopReaction, 0);\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBCAdditiveAimCmd());\n    x584_curAttackTime += arg;\n    zeus::CVector3f targetPos = GetTransform().transposeRotate(mgr.GetPlayer().GetTranslation() - GetTranslation());\n    const float y = targetPos.y();\n    targetPos.y() = static_cast<float>(targetPos.z());\n    targetPos.z() = y;\n    x450_bodyController->GetCommandMgr().DeliverAdditiveTargetVector(targetPos);\n  } else if (msg == EStateMsg::Deactivate) {\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBodyStateCmd(EBodyStateCmd::ExitState));\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBodyStateCmd(EBodyStateCmd::AdditiveIdle));\n  }\n}\n\nvoid CBloodFlower::InActive(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n    x400_24_hitByPlayerProjectile = false;\n  } else if (msg == EStateMsg::Deactivate) {\n    x5c0_ = 0.f;\n  }\n}\n\nvoid CBloodFlower::BulbAttack(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x450_bodyController->GetCommandMgr().DeliverCmd(\n        CBCProjectileAttackCmd(pas::ESeverity::Zero, mgr.GetPlayer().GetTranslation(), true));\n    x58c_projectileState = 1;\n  }\n}\n\nvoid CBloodFlower::PodAttack(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBCMeleeAttackCmd(pas::ESeverity::Zero));\n    x574_podEffect->SetParticleEmission(true);\n    TriggerPodSteam(mgr, true);\n  } else if (msg == EStateMsg::Update) {\n    if (!TooClose(mgr, 0.f)) {\n      return;\n    }\n\n    mgr.ApplyDamage(GetUniqueId(), mgr.GetPlayer().GetUniqueId(), GetUniqueId(), x5f8_podDamage,\n                    CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), {});\n  } else if (msg == EStateMsg::Deactivate) {\n    x574_podEffect->SetParticleEmission(false);\n    TriggerPodSteam(mgr, false);\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBCKnockBackCmd({}, pas::ESeverity::One));\n  }\n}\n\nvoid CBloodFlower::TriggerPodSteam(CStateManager& mgr, bool activate) {\n  for (const SConnection& conn : GetConnectionList()) {\n    auto search = mgr.GetIdListForScript(conn.x8_objId);\n    if (search.first != search.second) {\n      if (TCastToPtr<CScriptTrigger> trigger = mgr.ObjectById(search.second->second)) {\n        mgr.SendScriptMsg(trigger, GetUniqueId(),\n                          (activate ? EScriptObjectMessage::Activate : EScriptObjectMessage::Deactivate));\n      }\n    }\n  }\n}\n\nCTargetableProjectile* CBloodFlower::CreateArcProjectile(CStateManager& mgr, const TToken<CWeaponDescription>& desc,\n                                                         const zeus::CTransform& xf, const CDamageInfo& damage,\n                                                         TUniqueId uid) {\n\n  if (!x578_projectileDesc) {\n    return nullptr;\n  }\n\n  TUniqueId projId = mgr.AllocateUniqueId();\n  auto* targProj = new CTargetableProjectile(\n      desc, EWeaponType::AI, xf, EMaterialTypes::Character, damage, x5dc_projectileDamage, projId, GetAreaIdAlways(),\n      GetUniqueId(), x578_projectileDesc, uid, EProjectileAttrib::None, {x5c4_visorParticle}, x5d4_visorSfx, false);\n  if (mgr.GetPlayer().GetOrbitTargetId() == GetUniqueId()) {\n    mgr.GetPlayer().ResetAimTargetPrediction(projId);\n    mgr.GetPlayer().SetOrbitTargetId(projId, mgr);\n  }\n\n  return targProj;\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CBloodFlower.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/Weapon/CProjectileInfo.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n\nnamespace metaforce {\nclass CGenDescription;\nclass CElementGen;\nclass CWeaponDescription;\nclass CTargetableProjectile;\n} // namespace metaforce\n\nnamespace metaforce::MP1 {\nclass CBloodFlower : public CPatterned {\n  TLockedToken<CGenDescription> x568_podEffectDesc;\n  std::unique_ptr<CElementGen> x574_podEffect;\n  TLockedToken<CWeaponDescription> x578_projectileDesc;\n  float x584_curAttackTime = 0.f;\n  float x588_projectileOffset = 0.f;\n  u32 x58c_projectileState = 0;\n  CProjectileInfo x590_projectileInfo;\n  float x5b8_ = 0.f;\n  float x5bc_projectileDelay = 0.f;\n  float x5c0_ = 0.f;\n  TLockedToken<CGenDescription> x5c4_visorParticle;\n  s16 x5d4_visorSfx;\n  u32 x5d8_effectState = 0;\n  CDamageInfo x5dc_projectileDamage;\n  CDamageInfo x5f8_podDamage;\n  float x614_;\n  CAssetId x618_;\n  CAssetId x61c_;\n  CAssetId x620_;\n\n  void TriggerPodSteam(CStateManager& mgr, bool activate);\n  void CalculateAttackTime(CStateManager&);\n  void UpdateFire(CStateManager& mgr);\n  void TurnEffectsOn(u32, CStateManager&);\n  void TurnEffectsOff(u32, CStateManager&);\n  void LaunchPollenProjectile(const zeus::CTransform&, CStateManager&, float, s32);\n  CTargetableProjectile* CreateArcProjectile(CStateManager&, const TToken<CWeaponDescription>&, const zeus::CTransform&,\n                                             const CDamageInfo&, TUniqueId);\n\npublic:\n  DEFINE_PATTERNED(BloodFlower);\n\n  CBloodFlower(TUniqueId, std::string_view, const CEntityInfo&, const zeus::CTransform&, CModelData&&,\n               const CPatternedInfo&, CAssetId, CAssetId, const CActorParameters&, CAssetId, const CDamageInfo&,\n               const CDamageInfo&, const CDamageInfo&, CAssetId, CAssetId, CAssetId, float, CAssetId, u32);\n\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void Think(float dt, CStateManager& mgr) override;\n  void DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) override;\n  void Render(CStateManager& mgr) override;\n  void Touch(CActor&, CStateManager&) override {}\n  EWeaponCollisionResponseTypes GetCollisionResponseType(const zeus::CVector3f& v1, const zeus::CVector3f& v2,\n                                                         const CWeaponMode& weaponMode,\n                                                         EProjectileAttrib attribute) const override;\n  CProjectileInfo* GetProjectileInfo() override { return &x590_projectileInfo; }\n\n  bool ShouldAttack(CStateManager&, float) override;\n  bool ShouldTurn(CStateManager&, float) override;\n  bool Leash(CStateManager&, float) override { return x5c0_ < x3d0_playerLeashTime; }\n  void Active(CStateManager&, EStateMsg, float) override;\n  void InActive(CStateManager&, EStateMsg, float) override;\n  void BulbAttack(CStateManager&, EStateMsg, float) override;\n  void PodAttack(CStateManager&, EStateMsg, float) override;\n};\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CBouncyGrenade.cpp",
    "content": "#include \"Runtime/MP1/World/CBouncyGrenade.hpp\"\n\n#include \"Runtime/CPlayerState.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/Collision/CCollisionActor.hpp\"\n\nnamespace metaforce::MP1 {\nCBouncyGrenade::CBouncyGrenade(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                               const zeus::CTransform& xf, CModelData&& mData, const CActorParameters& actParams,\n                               TUniqueId parentId, const SBouncyGrenadeData& data, float velocity,\n                               float explodePlayerDistance)\n: CPhysicsActor(uid, true, name, info, xf, std::move(mData), {EMaterialTypes::Projectile, EMaterialTypes::Solid},\n                mData.GetBounds(), SMoverData{data.GetUnkStruct().GetMass()}, actParams, 0.3f, 0.1f)\n, x258_data(data)\n, x294_numBounces(data.GetNumBounces())\n, x298_parentId(parentId)\n, x2a0_elementGenCombat(std::make_unique<CElementGen>(g_SimplePool->GetObj({'PART', data.GetElementGenId1()})))\n, x2a4_elementGenXRay(std::make_unique<CElementGen>(g_SimplePool->GetObj({'PART', data.GetElementGenId2()})))\n, x2a8_elementGenThermal(std::make_unique<CElementGen>(g_SimplePool->GetObj({'PART', data.GetElementGenId3()})))\n, x2ac_elementGen4(std::make_unique<CElementGen>(g_SimplePool->GetObj({'PART', data.GetElementGenId4()})))\n, x2b0_explodePlayerDistance(explodePlayerDistance) {\n  SetMomentumWR({0.f, 0.f, -GravityConstant() * GetMass()});\n  SetVelocityWR(velocity * xf.frontVector());\n  x2a0_elementGenCombat->SetParticleEmission(false);\n  x2a4_elementGenXRay->SetParticleEmission(false);\n  x2a8_elementGenThermal->SetParticleEmission(false);\n  x2ac_elementGen4->SetParticleEmission(true);\n  CMaterialFilter filter = GetMaterialFilter();\n  filter.ExcludeList().Add(EMaterialTypes::Character);\n  SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(filter.IncludeList(), filter.ExcludeList()));\n}\n\nvoid CBouncyGrenade::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) {\n  CActor::AddToRenderer(frustum, mgr);\n  if (!x2b4_24_exploded) {\n    g_Renderer->AddParticleGen(*x2ac_elementGen4);\n    return;\n  }\n  const auto visor = mgr.GetPlayerState()->GetActiveVisor(mgr);\n  if (visor == CPlayerState::EPlayerVisor::Combat || visor == CPlayerState::EPlayerVisor::Scan) {\n    g_Renderer->AddParticleGen(*x2a0_elementGenCombat);\n  } else if (visor == CPlayerState::EPlayerVisor::XRay || visor == CPlayerState::EPlayerVisor::Thermal) {\n    g_Renderer->AddParticleGen(*x2a8_elementGenThermal);\n  }\n}\n\nvoid CBouncyGrenade::CollidedWith(TUniqueId id, const CCollisionInfoList& list, CStateManager& mgr) {\n  constexpr auto matList = CMaterialList{\n      EMaterialTypes::Solid,\n      EMaterialTypes::Ceiling,\n      EMaterialTypes::Floor,\n      EMaterialTypes::Character,\n  };\n\n  bool shouldExplode = false;\n  if (x298_parentId != id) {\n    const CEntity* const entity = mgr.GetObjectById(id);\n    if (entity != nullptr) {\n      if (TCastToConstPtr<CCollisionActor> actor = entity) {\n        shouldExplode = actor->GetOwnerId() != x298_parentId;\n      } else {\n        shouldExplode = true;\n      }\n    }\n  }\n  if (shouldExplode) {\n    Explode(mgr, id);\n  } else {\n    for (const auto& info : list) {\n      if (info.GetMaterialLeft().SharesMaterials(matList)) {\n        if (x294_numBounces == 0) {\n          Explode(mgr, kInvalidUniqueId);\n        } else {\n          const zeus::CVector3f* normal = &info.GetNormalLeft();\n          if (GetVelocity().dot(info.GetNormalLeft()) > 0.f) {\n            normal = &info.GetNormalRight();\n          }\n          const zeus::CVector3f impulse =\n              (x258_data.GetUnkStruct().GetSpeed() * GetConstantForce().magnitude()) * *normal;\n          const zeus::CVector3f angle = -x258_data.GetUnkStruct().GetSpeed() * GetAngularMomentum();\n          ApplyImpulseWR(impulse, angle);\n          CSfxManager::AddEmitter(x258_data.GetBounceSfx(), GetTranslation(), zeus::skUp, false, false, 0x7f,\n                                  GetAreaIdAlways());\n          --x294_numBounces;\n        }\n        break;\n      }\n    }\n  }\n  CPhysicsActor::CollidedWith(id, list, mgr);\n}\n\nstd::optional<zeus::CAABox> CBouncyGrenade::GetTouchBounds() const { return GetModelData()->GetBounds(GetTransform()); }\n\nvoid CBouncyGrenade::Render(CStateManager& mgr) {\n  if (!x2b4_24_exploded) {\n    GetModelData()->Render(mgr, GetTransform(), nullptr, {0, 0, 3, zeus::skWhite});\n  } else if (mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::XRay) {\n    CElementGen::SetSubtractBlend(true);\n    CElementGen::SetMoveRedToAlphaBuffer(true);\n    CGraphics::SetFog(ERglFogMode::PerspLin, 0.f, 75.f, zeus::skBlack);\n    x2a4_elementGenXRay->Render();\n    mgr.SetupFogForArea(GetAreaIdAlways());\n    CElementGen::SetSubtractBlend(false);\n    CElementGen::SetMoveRedToAlphaBuffer(false);\n  }\n}\n\nvoid CBouncyGrenade::Think(float dt, CStateManager& mgr) {\n  if (GetActive()) {\n    const zeus::CTransform& orientation = GetTransform().getRotation();\n    const zeus::CVector3f& translation = GetTranslation();\n    const zeus::CVector3f& scale = GetModelData()->GetScale();\n    auto UpdateElementGen = [orientation, translation, scale, dt](CElementGen& gen) {\n      gen.SetOrientation(orientation);\n      gen.SetGlobalTranslation(translation);\n      gen.SetGlobalScale(scale);\n      gen.Update(dt);\n    };\n    if (x2b4_24_exploded) {\n      Stop();\n      UpdateElementGen(*x2a0_elementGenCombat);\n      UpdateElementGen(*x2a4_elementGenXRay);\n      UpdateElementGen(*x2a8_elementGenThermal);\n    } else {\n      UpdateElementGen(*x2ac_elementGen4);\n    }\n    x29c_ += dt;\n    if (x29c_ > 0.3f) {\n      x2b4_25_ = true;\n    }\n    const zeus::CVector3f& playerDistance = mgr.GetPlayer().GetTranslation() +\n                                            zeus::CVector3f{0.f, 0.f, 0.5f * mgr.GetPlayer().GetEyeHeight()} -\n                                            translation;\n    if (playerDistance.magSquared() < x2b0_explodePlayerDistance * x2b0_explodePlayerDistance) {\n      Explode(mgr, kInvalidUniqueId);\n    }\n  }\n  if (x2a0_elementGenCombat->IsSystemDeletable() && x2a4_elementGenXRay->IsSystemDeletable() &&\n      x2a8_elementGenThermal->IsSystemDeletable()) {\n    mgr.FreeScriptObject(GetUniqueId());\n  }\n}\n\nvoid CBouncyGrenade::Touch(CActor& act, CStateManager& mgr) { CActor::Touch(act, mgr); }\n\nvoid CBouncyGrenade::Explode(CStateManager& mgr, TUniqueId uid) {\n  if (x2b4_24_exploded) {\n    return;\n  }\n\n  x2b4_24_exploded = true;\n  CSfxManager::AddEmitter(x258_data.GetExplodeSfx(), GetTranslation(), zeus::skUp, false, false, 0x7f,\n                          GetAreaIdAlways());\n  x2a0_elementGenCombat->SetParticleEmission(true);\n  x2a4_elementGenXRay->SetParticleEmission(true);\n  x2a8_elementGenThermal->SetParticleEmission(true);\n  x2ac_elementGen4->SetParticleEmission(false);\n\n  const CDamageInfo& dInfo = x258_data.GetDamageInfo();\n  {\n    bool isParent = x298_parentId == uid;\n    if (TCastToConstPtr<CCollisionActor> actor = mgr.GetObjectById(uid)) {\n      isParent = x298_parentId == actor->GetOwnerId();\n    }\n    if (uid != kInvalidUniqueId && !isParent) {\n      mgr.ApplyDamage(GetUniqueId(), uid, GetUniqueId(), dInfo, CMaterialFilter::MakeInclude({EMaterialTypes::Solid}),\n                      zeus::skZero3f);\n    }\n  }\n\n  const float radius = dInfo.GetRadius();\n  if (radius > 1.f) {\n    const zeus::CVector3f& pos = GetTranslation();\n    const CMaterialFilter filter = CMaterialFilter::MakeInclude({EMaterialTypes::Player, EMaterialTypes::Character});\n    EntityList nearList;\n    mgr.BuildNearList(nearList, {pos - radius, pos + radius}, filter, nullptr);\n\n    for (const auto& id : nearList) {\n      bool isParent = x298_parentId == id;\n      if (TCastToConstPtr<CCollisionActor> cActor = mgr.GetObjectById(id)) {\n        isParent = x298_parentId == cActor->GetOwnerId();\n      }\n      if (isParent) {\n        continue;\n      }\n\n      const auto* actor = static_cast<const CActor*>(mgr.GetObjectById(id));\n      if (actor == nullptr) {\n        continue;\n      }\n\n      const float magnitude = (actor->GetTranslation() - GetTranslation()).magnitude();\n      if (radius <= magnitude) {\n        continue;\n      }\n\n      float scale = (radius - magnitude) / radius;\n      const CDamageInfo info{dInfo.GetWeaponMode(), scale * dInfo.GetDamage(), radius,\n                             scale * dInfo.GetKnockBackPower()};\n      mgr.ApplyDamage(GetUniqueId(), id, GetUniqueId(), info, CMaterialFilter::MakeInclude({EMaterialTypes::Solid}),\n                      zeus::skZero3f);\n    }\n  }\n}\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CBouncyGrenade.hpp",
    "content": "#pragma once\n\n#include \"Runtime/World/CPhysicsActor.hpp\"\n#include \"Runtime/World/CDamageInfo.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\n#include <zeus/CTransform.hpp>\n\nnamespace metaforce::MP1 {\nstruct SGrenadeVelocityInfo {\nprivate:\n  float x0_mass;\n  float x4_speed;\n\npublic:\n  explicit SGrenadeVelocityInfo(CInputStream& in) : x0_mass(in.ReadFloat()), x4_speed(in.ReadFloat()) {}\n\n  [[nodiscard]] float GetMass() const { return x0_mass; }\n  [[nodiscard]] float GetSpeed() const { return x4_speed; }\n};\n\nstruct SBouncyGrenadeData {\nprivate:\n  SGrenadeVelocityInfo x0_velocityInfo;\n  CDamageInfo x8_damageInfo;\n  CAssetId x24_elementGenId1;\n  CAssetId x28_elementGenId2;\n  CAssetId x2c_elementGenId3;\n  CAssetId x30_elementGenId4;\n  u32 x34_numBounces;\n  u16 x38_bounceSfx;\n  u16 x3a_explodeSfx;\n\npublic:\n  SBouncyGrenadeData(const SGrenadeVelocityInfo& velocityInfo, const CDamageInfo& damageInfo, CAssetId elementGenId1,\n                     CAssetId elementGenId2, CAssetId elementGenId3, CAssetId elementGenId4, u32 numBounces,\n                     u16 bounceSfxId, u16 explodeSfxId)\n  : x0_velocityInfo(velocityInfo)\n  , x8_damageInfo(damageInfo)\n  , x24_elementGenId1(elementGenId1)\n  , x28_elementGenId2(elementGenId2)\n  , x2c_elementGenId3(elementGenId3)\n  , x30_elementGenId4(elementGenId4)\n  , x34_numBounces(numBounces)\n  , x38_bounceSfx(bounceSfxId)\n  , x3a_explodeSfx(explodeSfxId) {}\n\n  [[nodiscard]] const SGrenadeVelocityInfo& GetUnkStruct() const { return x0_velocityInfo; }\n  [[nodiscard]] const CDamageInfo& GetDamageInfo() const { return x8_damageInfo; }\n  [[nodiscard]] CAssetId GetElementGenId1() const { return x24_elementGenId1; }\n  [[nodiscard]] CAssetId GetElementGenId2() const { return x28_elementGenId2; }\n  [[nodiscard]] CAssetId GetElementGenId3() const { return x2c_elementGenId3; }\n  [[nodiscard]] CAssetId GetElementGenId4() const { return x30_elementGenId4; }\n  [[nodiscard]] u32 GetNumBounces() const { return x34_numBounces; }\n  [[nodiscard]] u16 GetBounceSfx() const { return x38_bounceSfx; }\n  [[nodiscard]] u16 GetExplodeSfx() const { return x3a_explodeSfx; }\n};\n\nclass CBouncyGrenade : public CPhysicsActor {\nprivate:\n  SBouncyGrenadeData x258_data;\n  u32 x294_numBounces;\n  TUniqueId x298_parentId;\n  float x29c_ = 0.f;\n  std::unique_ptr<CElementGen> x2a0_elementGenCombat;\n  std::unique_ptr<CElementGen> x2a4_elementGenXRay;\n  std::unique_ptr<CElementGen> x2a8_elementGenThermal;\n  std::unique_ptr<CElementGen> x2ac_elementGen4;\n  float x2b0_explodePlayerDistance;\n  bool x2b4_24_exploded : 1 = false;\n  bool x2b4_25_ : 1 = false;\n\npublic:\n  DEFINE_ENTITY\n  CBouncyGrenade(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                 CModelData&& mData, const CActorParameters& actParams, TUniqueId parentId,\n                 const SBouncyGrenadeData& data, float velocity, float explodePlayerDistance);\n\n  void Accept(IVisitor& visitor) override { visitor.Visit(this); }\n  void AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) override;\n  void CollidedWith(TUniqueId id, const CCollisionInfoList& list, CStateManager& mgr) override;\n  [[nodiscard]] std::optional<zeus::CAABox> GetTouchBounds() const override;\n  void Render(CStateManager& mgr) override;\n  void Think(float dt, CStateManager& mgr) override;\n  void Touch(CActor& act, CStateManager& mgr) override;\n\nprivate:\n  void Explode(CStateManager& mgr, TUniqueId uid);\n};\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CBurrower.cpp",
    "content": "#include \"Runtime/MP1/World/CBurrower.hpp\"\n\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/World/CGameArea.hpp\"\n#include \"Runtime/World/CPatternedInfo.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce::MP1 {\nnamespace {\nconstexpr CDamageVulnerability skVulnerability{\n    EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect,\n    EVulnerability::Normal,  EVulnerability::Normal,  EVulnerability::Deflect, EVulnerability::Deflect,\n    EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect,\n    EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EDeflectType::None};\n} // namespace\n\nCBurrower::CBurrower(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                     CModelData&& mData, const CPatternedInfo& pInfo, const CActorParameters& actParms, CAssetId aId1,\n                     CAssetId aId2, CAssetId aId3, const CDamageInfo& damageInfo, CAssetId aId4, u32 sfxId,\n                     CAssetId aId5)\n: CPatterned(ECharacter::Burrower, uid, name, EFlavorType::Zero, info, xf, std::move(mData), pInfo,\n             EMovementType::Ground, EColliderType::One, EBodyType::BiPedal, actParms, EKnockBackVariant::Small)\n, x568_pathFindSearch(nullptr, 1, pInfo.GetPathfindingIndex(), 1.f, 1.f)\n, x64c_projectileInfo(aId3, damageInfo)\n, x6aa_visorSfx(CSfxManager::TranslateSFXID(sfxId)) {\n  CreateShadow(false);\n  MakeThermalColdAndHot();\n\n  x64c_projectileInfo.Token().Lock();\n  if (aId1.IsValid()) {\n    x674_jumpParticle =\n        std::make_unique<CElementGen>(g_SimplePool->GetObj({SBIG('PART'), aId1}),\n                                      CElementGen::EModelOrientationType::One, CElementGen::EOptionalSystemFlags::One);\n    x674_jumpParticle->SetGlobalScale(GetModelData()->GetScale());\n    x674_jumpParticle->SetParticleEmission(false);\n  }\n\n  if (aId2.IsValid()) {\n    x678_trailParticle =\n        std::make_unique<CElementGen>(g_SimplePool->GetObj({SBIG('PART'), aId2}),\n                                      CElementGen::EModelOrientationType::One, CElementGen::EOptionalSystemFlags::One);\n    x678_trailParticle->SetGlobalScale(GetModelData()->GetScale());\n    x678_trailParticle->SetParticleEmission(false);\n  }\n\n  if (aId4.IsValid()) {\n    x67c_visorParticle.emplace(g_SimplePool->GetObj({SBIG('PART'), aId4}));\n  }\n\n  if (aId5.IsValid()) {\n    x68c_deathExplosionParticle.emplace(g_SimplePool->GetObj({SBIG('PART'), aId5}));\n  }\n}\n\nvoid CBurrower::Think(float dt, CStateManager& mgr) {\n  if (!GetActive()) {\n    return;\n  }\n  CPatterned::Think(dt, mgr);\n  if (x6a4_invulnDamageTime > 0.f) {\n    x6a4_invulnDamageTime -= dt;\n  }\n\n  if (x6ac_24_doFacePlayer) {\n    zeus::CVector3f front = GetTransform().frontVector().normalized();\n    zeus::CVector3f diffPos = (mgr.GetPlayer().GetTranslation() - GetTranslation()).normalized();\n    if (front.dot(diffPos) < 0.9993) {\n      zeus::CQuaternion q = zeus::CQuaternion::lookAt(front, diffPos, zeus::degToRad(360.f * dt));\n      q.setImaginary(GetTransform().transposeRotate(q.getImaginary()));\n      RotateToOR(q, dt);\n    }\n  } else if (x69c_attackTime > 0.f) {\n    x69c_attackTime -= dt;\n  }\n\n  if (x674_jumpParticle) {\n    x6a0_lurkTimer -= dt;\n    if (!x6ac_25_inAir && x6a0_lurkTimer <= 0.f) {\n      if (IsAlive()) {\n        x674_jumpParticle->SetParticleEmission(true);\n        x674_jumpParticle->SetOrientation(GetTransform().getRotation());\n        x674_jumpParticle->SetTranslation(GetTranslation());\n        x674_jumpParticle->ForceParticleCreation(1);\n        x674_jumpParticle->SetOrientation({});\n        x674_jumpParticle->SetParticleEmission(false);\n      }\n      x6a0_lurkTimer = 0.f;\n    }\n    x674_jumpParticle->Update(dt);\n  }\n\n  if (x678_trailParticle) {\n    if (IsAlive() && !x6ac_25_inAir) {\n      x678_trailParticle->SetTranslation(GetTranslation());\n    }\n    x678_trailParticle->Update(dt);\n  }\n}\n\nvoid CBurrower::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  CPatterned::AcceptScriptMsg(msg, uid, mgr);\n  if (msg == EScriptObjectMessage::Registered) {\n    x450_bodyController->Activate(mgr);\n  } else if (msg == EScriptObjectMessage::InitializedInArea) {\n    x568_pathFindSearch.SetArea(mgr.GetWorld()->GetAreaAlways(GetAreaIdAlways())->GetPostConstructed()->x10bc_pathArea);\n    if (!HasPatrolPath(mgr, 0.f)) {\n      x678_trailParticle.reset();\n    }\n  } else if (msg == EScriptObjectMessage::InvulnDamage) {\n    x6a4_invulnDamageTime = 1.f;\n  }\n}\n\nvoid CBurrower::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) {\n  if (GetActive() && x678_trailParticle) {\n    g_Renderer->AddParticleGen(*x678_trailParticle);\n  }\n  CPatterned::AddToRenderer(frustum, mgr);\n}\n\nvoid CBurrower::Render(CStateManager& mgr) {\n  auto* lights = GetActorLights();\n  if (lights != nullptr && x674_jumpParticle) {\n    lights->ActivateLights();\n    x674_jumpParticle->Render();\n  }\n  CPatterned::Render(mgr);\n}\n\nconst CDamageVulnerability* CBurrower::GetDamageVulnerability() const {\n  if (x6ac_25_inAir) {\n    return CAi::GetDamageVulnerability();\n  }\n  return &skVulnerability;\n}\n\nconst CDamageVulnerability* CBurrower::GetDamageVulnerability(const zeus::CVector3f& v1, const zeus::CVector3f& v2,\n                                                              const CDamageInfo& damageInfo) const {\n  if (x6ac_25_inAir) {\n    return CAi::GetDamageVulnerability();\n  }\n  return &skVulnerability;\n}\n\nvoid CBurrower::DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) {\n  if (type == EUserEventType::Landing) {\n    x328_25_verticalMovement = false;\n    AddMaterial(EMaterialTypes::GroundCollider, mgr);\n    x55c_moveScale.splat(1.f);\n    return;\n  }\n\n  if (type == EUserEventType::Projectile) {\n    const zeus::CVector3f aimPos = mgr.GetPlayer().GetAimPosition(mgr, 0.f);\n    const zeus::CVector3f gunPos = GetLctrTransform(node.GetLocatorName()).origin;\n    const zeus::CVector3f interPos =\n        GetProjectileInfo()->PredictInterceptPos(gunPos, aimPos, mgr.GetPlayer(), true, dt);\n    const zeus::CTransform gunXf = zeus::lookAt(gunPos, interPos);\n    LaunchProjectile(gunXf, mgr, 1, EProjectileAttrib::None, false, x67c_visorParticle, x6aa_visorSfx, false,\n                     GetModelData()->GetScale());\n    return;\n  }\n\n  if (type == EUserEventType::TakeOff) {\n    RemoveMaterial(EMaterialTypes::GroundCollider, mgr);\n    x328_25_verticalMovement = true;\n    x55c_moveScale = GetModelData()->GetScale();\n    return;\n  }\n\n  CPatterned::DoUserAnimEvent(mgr, node, type, dt);\n}\n\nvoid CBurrower::Death(CStateManager& mgr, const zeus::CVector3f& direction, EScriptObjectState state) {\n  if (!IsAlive()) {\n    return;\n  }\n\n  CPatterned::Death(mgr, direction, state);\n  if (x678_trailParticle) {\n    x678_trailParticle->SetParticleEmission(false);\n  }\n}\n\nvoid CBurrower::Patrol(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x6ac_25_inAir = false;\n    if (x678_trailParticle) {\n      x678_trailParticle->SetParticleEmission(true);\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x6a8_lastDestObj = x2dc_destObj;\n  }\n\n  CPatterned::Patrol(mgr, msg, dt);\n}\n\nvoid CBurrower::TargetPatrol(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg != EStateMsg::Activate) {\n    return;\n  }\n\n  x2dc_destObj = x6a8_lastDestObj != kInvalidUniqueId\n                     ? x6a8_lastDestObj\n                     : GetWaypointForState(mgr, EScriptObjectState::Patrol, EScriptObjectMessage::Follow);\n\n  if (const TCastToConstPtr<CActor> act = mgr.GetObjectById(x2dc_destObj)) {\n    x2e0_destPos = act->GetTranslation();\n    x328_24_inPosition = false;\n    x2ec_reflectedDestPos = GetTranslation();\n  }\n}\n\nvoid CBurrower::TurnAround(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg != EStateMsg::Activate) {\n    return;\n  }\n  GetBodyController()->GetCommandMgr().DeliverCmd(\n      CBCLocomotionCmd{-GetTransform().frontVector(), GetTransform().frontVector(), 1.f});\n}\n\nvoid CBurrower::Active(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x6ac_24_doFacePlayer = true;\n    x32c_animState = EAnimState::Ready;\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::Generate, &CPatterned::TryGenerateNoXf, 0);\n  } else if (msg == EStateMsg::Deactivate) {\n    x6ac_24_doFacePlayer = false;\n    x6ac_25_inAir = true;\n    x32c_animState = EAnimState::NotReady;\n  }\n}\n\nvoid CBurrower::Lurk(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg != EStateMsg::Activate) {\n    return;\n  }\n  x6ac_25_inAir = false;\n  x6a0_lurkTimer = 0.1875f;\n}\n\nvoid CBurrower::ProjectileAttack(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x6ac_24_doFacePlayer = true;\n    x6ac_25_inAir = true;\n    if (x678_trailParticle) {\n      x678_trailParticle->SetParticleEmission(false);\n    }\n    x32c_animState = EAnimState::Ready;\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::ProjectileAttack, &CPatterned::TryProjectileAttack, 0);\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n    x69c_attackTime = x308_attackTimeVariation * mgr.GetActiveRandom()->Float() + x304_averageAttackTime;\n    x328_25_verticalMovement = false;\n    AddMaterial(EMaterialTypes::GroundCollider, mgr);\n    x55c_moveScale.splat(1.f);\n  }\n}\n\nvoid CBurrower::Retreat(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::Generate, &CPatterned::TryGenerateNoXf, 1);\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n    if (x678_trailParticle) {\n      x678_trailParticle->SetParticleEmission(true);\n    }\n  }\n}\n\nbool CBurrower::PathShagged(CStateManager& mgr, float arg) {\n  return x568_pathFindSearch.OnPath(GetTranslation()) == CPathFindSearch::EResult::InvalidArea;\n}\n\nbool CBurrower::ShouldAttack(CStateManager& mgr, float arg) {\n  if (x6a4_invulnDamageTime > 0.f) {\n    return false;\n  }\n\n  return mgr.CanCreateProjectile(GetUniqueId(), EWeaponType::AI, 1);\n}\n\nconst std::optional<TLockedToken<CGenDescription>>& CBurrower::GetDeathExplosionParticle() const {\n  if (x6ac_25_inAir) {\n    return x68c_deathExplosionParticle;\n  }\n  return x520_deathExplosionParticle;\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CBurrower.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Weapon/CProjectileInfo.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n#include \"Runtime/World/CPathFindSearch.hpp\"\n\nnamespace metaforce {\nclass CElementGen;\nnamespace MP1 {\n\nclass CBurrower : public CPatterned {\n  CPathFindSearch x568_pathFindSearch;\n  CProjectileInfo x64c_projectileInfo;\n  std::unique_ptr<CElementGen> x674_jumpParticle;\n  std::unique_ptr<CElementGen> x678_trailParticle;\n  std::optional<TLockedToken<CGenDescription>> x67c_visorParticle;\n  std::optional<TLockedToken<CGenDescription>> x68c_deathExplosionParticle;\n  float x69c_attackTime = 0.f;\n  float x6a0_lurkTimer = 0.f;\n  float x6a4_invulnDamageTime = 0.f;\n  TUniqueId x6a8_lastDestObj = kInvalidUniqueId;\n  s16 x6aa_visorSfx;\n  bool x6ac_24_doFacePlayer : 1 = false;\n  bool x6ac_25_inAir : 1 = false;\n\npublic:\n  DEFINE_PATTERNED(Burrower);\n  CBurrower(TUniqueId, std::string_view, const CEntityInfo&, const zeus::CTransform&, CModelData&&,\n            const CPatternedInfo&, const CActorParameters&, CAssetId, CAssetId, CAssetId, const CDamageInfo&, CAssetId,\n            u32, CAssetId);\n\n  void Think(float, CStateManager&) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void AddToRenderer(const zeus::CFrustum&, CStateManager&) override;\n  void Render(CStateManager& mgr) override;\n  const CDamageVulnerability* GetDamageVulnerability() const override;\n  const CDamageVulnerability* GetDamageVulnerability(const zeus::CVector3f&, const zeus::CVector3f&,\n                                                     const CDamageInfo&) const override;\n  void DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) override;\n  void Death(CStateManager& mgr, const zeus::CVector3f& direction, EScriptObjectState state) override;\n  void Patrol(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void TargetPatrol(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void TurnAround(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Active(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Lurk(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void ProjectileAttack(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Retreat(CStateManager& mgr, EStateMsg msg, float dt) override;\n  bool PathShagged(CStateManager& mgr, float arg) override;\n  bool ShouldAttack(CStateManager& mgr, float arg) override;\n  CPathFindSearch* GetSearchPath() override { return &x568_pathFindSearch; }\n  CProjectileInfo* GetProjectileInfo() override { return &x64c_projectileInfo; }\n  const std::optional<TLockedToken<CGenDescription>>& GetDeathExplosionParticle() const override;\n};\n} // namespace MP1\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/MP1/World/CChozoGhost.cpp",
    "content": "#include \"Runtime/MP1/World/CChozoGhost.hpp\"\n\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CRandom16.hpp\"\n#include \"Runtime/Character/CPASAnimParmData.hpp\"\n#include \"Runtime/Weapon/CGameProjectile.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CScriptCoverPoint.hpp\"\n#include \"Runtime/World/CScriptWaypoint.hpp\"\n#include \"Runtime/World/CTeamAiMgr.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce::MP1 {\nCChozoGhost::CBehaveChance::CBehaveChance(CInputStream& in)\n: x0_propertyCount(in.ReadLong())\n, x4_lurk(in.ReadFloat())\n, x8_taunt(in.ReadFloat())\n, xc_attack(in.ReadFloat())\n, x10_move(in.ReadFloat())\n, x14_lurkTime(in.ReadFloat())\n, x18_chargeAttack(x0_propertyCount < 6 ? 0.5f : in.ReadFloat() * .01f)\n, x1c_numBolts(x0_propertyCount < 7 ? 2 : in.ReadLong()) {\n  float f2 = 1.f / (x10_move + xc_attack + x4_lurk + x8_taunt);\n  x4_lurk *= f2;\n  x8_taunt *= f2;\n  xc_attack *= f2;\n  x10_move *= f2;\n}\n\nEBehaveType CChozoGhost::CBehaveChance::GetBehave(EBehaveType type, CStateManager& mgr) const {\n  float lurkChance = x4_lurk;\n  float tauntChance = x8_taunt;\n  float attackChance = xc_attack;\n  if (type == EBehaveType::Lurk) {\n    float delta = lurkChance / 3.f;\n    lurkChance = 0.f;\n    tauntChance += delta;\n    attackChance += delta;\n  } else if (type == EBehaveType::Taunt) {\n    float delta = tauntChance / 3.f;\n    tauntChance = 0.f;\n    lurkChance += delta;\n    attackChance += delta;\n  } else if (type == EBehaveType::Attack) {\n    float delta = attackChance / 3.f;\n    attackChance = 0.f;\n    lurkChance += delta;\n    tauntChance += delta;\n  } else if (type == EBehaveType::Move) {\n    float delta = x10_move / 3.f;\n    lurkChance += delta;\n    tauntChance += delta;\n    attackChance += delta;\n  }\n\n  float rnd = mgr.GetActiveRandom()->Float();\n  if (lurkChance > rnd)\n    return EBehaveType::Lurk;\n  else if (tauntChance > rnd - lurkChance)\n    return EBehaveType::Taunt;\n  else if (attackChance > rnd - lurkChance - tauntChance)\n    return EBehaveType::Attack;\n  return EBehaveType::Move;\n}\n\nCChozoGhost::CChozoGhost(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                         CModelData&& mData, const CActorParameters& actParms, const CPatternedInfo& pInfo,\n                         float hearingRadius, float fadeOutDelay, float attackDelay, float freezeTime, CAssetId wpsc1,\n                         const CDamageInfo& dInfo1, CAssetId wpsc2, const CDamageInfo& dInfo2,\n                         const CBehaveChance& chance1, const CBehaveChance& chance2, const CBehaveChance& chance3,\n                         u16 soundImpact, float f5, u16 sId2, u16 sId3, u32 w1, float f6, u32 w2, float hurlRecoverTime,\n                         CAssetId projectileVisorEffect, s16 soundProjectileVisor, float f8, float f9, u32 nearChance,\n                         u32 midChance)\n: CPatterned(ECharacter::ChozoGhost, uid, name, EFlavorType::Zero, info, xf, std::move(mData), pInfo,\n             EMovementType::Flyer, EColliderType::Zero, EBodyType::BiPedal, actParms, EKnockBackVariant::Medium)\n, x568_hearingRadius(hearingRadius)\n, x56c_fadeOutDelay(fadeOutDelay)\n, x570_attackDelay(attackDelay)\n, x574_freezeTime(freezeTime)\n, x578_(wpsc1, dInfo1)\n, x5a0_(wpsc2, dInfo2)\n, x5c8_(chance1)\n, x5e8_(chance2)\n, x608_(chance3)\n, x628_soundImpact(soundImpact)\n, x62c_(f5)\n, x630_sfxFadeIn(sId2)\n, x632_sfxFadeOut(sId3)\n, x634_(f6)\n, x638_hurlRecoverTime(hurlRecoverTime)\n, x63c_(w2)\n, x650_sound_ProjectileVisor(soundProjectileVisor)\n, x654_(f8)\n, x658_(f9)\n, x65c_nearChance(nearChance)\n, x660_midChance(midChance)\n, x664_24_behaviorEnabled(w1)\n, x664_25_flinch(!w1)\n, x680_behaveType(x664_24_behaviorEnabled ? EBehaveType::Attack : EBehaveType::None)\n, x68c_boneTracking(*GetModelData()->GetAnimationData(), \"Head_1\"sv, zeus::degToRad(80.f), zeus::degToRad(180.f),\n                    EBoneTrackingFlags::None) {\n  x578_.Token().Lock();\n  x5a0_.Token().Lock();\n  x668_ = GetModelData()->GetScale().z() *\n          GetAnimationDistance(\n              CPASAnimParmData(pas::EAnimationState::Jump, CPASAnimParm::FromEnum(3), CPASAnimParm::FromEnum(0)));\n  x66c_ = GetModelData()->GetScale().z() *\n          GetAnimationDistance(\n              CPASAnimParmData(pas::EAnimationState::Slide, CPASAnimParm::FromEnum(1), CPASAnimParm::FromReal32(90.f)));\n  x670_ = GetModelData()->GetScale().z() *\n          GetAnimationDistance(CPASAnimParmData(pas::EAnimationState::MeleeAttack, CPASAnimParm::FromEnum(2),\n                                                CPASAnimParm::FromEnum(1)));\n\n  if (projectileVisorEffect.IsValid())\n    x640_projectileVisor = g_SimplePool->GetObj({SBIG('PART'), projectileVisorEffect});\n  x460_knockBackController.SetEnableBurn(false);\n  x460_knockBackController.SetEnableLaggedBurnDeath(false);\n  x460_knockBackController.SetEnableShock(false);\n  x460_knockBackController.SetEnableFreeze(false);\n  CreateShadow(false);\n  MakeThermalColdAndHot();\n}\n\nvoid CChozoGhost::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  CPatterned::AcceptScriptMsg(msg, uid, mgr);\n\n  switch (msg) {\n  case EScriptObjectMessage::Activate:\n    AddToTeam(mgr);\n    break;\n  case EScriptObjectMessage::Deactivate:\n  case EScriptObjectMessage::Deleted:\n    RemoveFromTeam(mgr);\n    break;\n  case EScriptObjectMessage::Action:\n    if (x664_25_flinch)\n      x665_29_aggressive = true;\n    break;\n  case EScriptObjectMessage::Alert:\n    if (x664_26_alert)\n      break;\n    x664_26_alert = true;\n    x400_24_hitByPlayerProjectile = true;\n    break;\n  case EScriptObjectMessage::Falling:\n  case EScriptObjectMessage::Jumped: {\n    if (!x328_25_verticalMovement)\n      x150_momentum = {0.f, 0.f, -(GetGravityConstant() * GetMass())};\n    break;\n  }\n  case EScriptObjectMessage::InitializedInArea:\n    if (GetActive())\n      AddToTeam(mgr);\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CChozoGhost::Think(float dt, CStateManager& mgr) {\n  if (!GetActive())\n    return;\n\n  CPatterned::Think(dt, mgr);\n  UpdateThermalFrozenState(false);\n  x68c_boneTracking.Update(dt);\n  x6c8_spaceWarpTime = std::max(0.f, x6c8_spaceWarpTime - dt);\n  xe7_31_targetable = IsVisibleEnough(mgr);\n}\n\nvoid CChozoGhost::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) {\n  x402_29_drawParticles = mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::XRay;\n  if (mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::Thermal) {\n    SetCalculateLighting(false);\n    x90_actorLights->BuildConstantAmbientLighting(zeus::skWhite);\n  } else {\n    SetCalculateLighting(true);\n  }\n\n  u8 alpha = GetModelAlphau8(mgr);\n  u8 r, g, b, a;\n  x42c_color.toRGBA8(r, g, b, a);\n  if (alpha < 255 || r == 0) {\n    zeus::CColor col;\n    if (r == 0) {\n      col = zeus::skWhite;\n    } else {\n      float v = std::max<s8>(0, r - 255) * (1.f / 255.f);\n      col.r() = 1.f;\n      col.g() = v;\n      col.b() = v;\n    }\n    col.a() = alpha * (1.f / 255.f);\n    xb4_drawFlags = CModelFlags(5, 0, 3, col);\n  } else {\n    xb4_drawFlags = CModelFlags(0, 0, 3, zeus::skWhite);\n  }\n  CPatterned::PreRender(mgr, frustum);\n  x68c_boneTracking.PreRender(mgr, *GetModelData()->GetAnimationData(), GetTransform(), GetModelData()->GetScale(),\n                              *GetBodyController());\n}\n\nvoid CChozoGhost::Render(CStateManager& mgr) {\n  if (x6c8_spaceWarpTime > 0.f)\n    mgr.DrawSpaceWarp(x6cc_spaceWarpPosition, std::sin((M_PIF * x6c8_spaceWarpTime) / x56c_fadeOutDelay));\n\n  if (mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::XRay) {\n    CElementGen::SetSubtractBlend(true);\n    CElementGen::SetMoveRedToAlphaBuffer(true);\n    CGraphics::SetFog(ERglFogMode::PerspLin, 0.f, 75.f, zeus::skBlack);\n    GetModelData()->GetAnimationData()->GetParticleDB().RenderSystemsToBeDrawnFirst();\n    mgr.SetupFogForArea3XRange(GetAreaIdAlways());\n  }\n\n  CPatterned::Render(mgr);\n\n  if (mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::XRay) {\n    CGraphics::SetFog(ERglFogMode::PerspLin, 0.f, 75.f, zeus::skBlack);\n    GetModelData()->GetAnimationData()->GetParticleDB().RenderSystemsToBeDrawnLast();\n    mgr.SetupFogForArea(GetAreaIdAlways());\n    CElementGen::SetSubtractBlend(false);\n    CElementGen::SetMoveRedToAlphaBuffer(false);\n  }\n}\n\nvoid CChozoGhost::Touch(CActor& act, CStateManager& mgr) {\n  if (IsVisibleEnough(mgr)) {\n    if (TCastToPtr<CPlayer> pl = act) {\n      if (x420_curDamageRemTime <= 0.f) {\n        mgr.ApplyDamage(GetUniqueId(), pl->GetUniqueId(), GetUniqueId(), GetContactDamage(),\n                        CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), {});\n        x420_curDamageRemTime = x424_damageWaitTime;\n      }\n    }\n  }\n  CPatterned::Touch(act, mgr);\n}\n\nEWeaponCollisionResponseTypes CChozoGhost::GetCollisionResponseType(const zeus::CVector3f& pos,\n                                                                    const zeus::CVector3f& dir, const CWeaponMode& mode,\n                                                                    EProjectileAttrib attrib) const {\n  return EWeaponCollisionResponseTypes::ChozoGhost;\n}\n\nvoid CChozoGhost::DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) {\n\n  if (type == EUserEventType::FadeIn) {\n    if (x664_30_fadedOut) {\n      x3e8_alphaDelta = 2.f;\n      CSfxManager::AddEmitter(x630_sfxFadeIn, GetTranslation(), {}, false, false, 127, -1);\n    }\n    AddMaterial(EMaterialTypes::Target, mgr);\n    x664_30_fadedOut = false;\n    x664_29_fadedIn = true;\n    return;\n  } else if (type == EUserEventType::Projectile) {\n    const zeus::CTransform& xf =\n        zeus::lookAt(GetLctrTransform(node.GetLocatorName()).origin, mgr.GetPlayer().GetAimPosition(mgr, 0.f));\n    if (x67c_attackType == 2) {\n      CGameProjectile* proj =\n          LaunchProjectile(xf, mgr, 2, EProjectileAttrib::BigStrike | EProjectileAttrib::StaticInterference, true,\n                           x640_projectileVisor, x650_sound_ProjectileVisor, false, zeus::skOne3f);\n      if (proj) {\n        proj->AddAttrib(EProjectileAttrib::BigStrike);\n        proj->SetDamageDuration(x62c_);\n        proj->AddAttrib(EProjectileAttrib::StaticInterference);\n        proj->SetInterferenceDuration(x62c_);\n        proj->SetMinHomingDistance(x634_);\n      }\n    } else {\n      CGameProjectile* proj =\n          LaunchProjectile(xf, mgr, 5, EProjectileAttrib::DamageFalloff | EProjectileAttrib::StaticInterference, true,\n                           {x640_projectileVisor}, x650_sound_ProjectileVisor, false, zeus::skOne3f);\n      if (proj) {\n        if (GetProjectileInfo()->GetProjectileSpeed() > 0.f) {\n          proj->SetDamageFalloffSpeed(80.f / GetProjectileInfo()->GetProjectileSpeed());\n        }\n        proj->AddAttrib(EProjectileAttrib::BigStrike);\n        proj->SetDamageDuration(x62c_);\n        proj->AddAttrib(EProjectileAttrib::StaticInterference);\n        proj->SetInterferenceDuration(x62c_);\n        proj->SetMinHomingDistance(x634_);\n      }\n    }\n    return;\n  } else if (type == EUserEventType::FadeOut) {\n    if (x664_29_fadedIn) {\n      x3e8_alphaDelta = -2.f;\n      CSfxManager::AddEmitter(x632_sfxFadeOut, GetTranslation(), {}, false, false, 127, -1);\n    }\n    RemoveMaterial(EMaterialTypes::Target, mgr);\n    x664_29_fadedIn = false;\n    x664_30_fadedOut = true;\n    x665_26_shouldSwoosh = true;\n    return;\n  }\n\n  CPatterned::DoUserAnimEvent(mgr, node, type, dt);\n\n  if (type == EUserEventType::Delete)\n    x3e8_alphaDelta = -1.f;\n}\n\nvoid CChozoGhost::KnockBack(const zeus::CVector3f& dir, CStateManager& mgr, const CDamageInfo& info,\n                            EKnockBackType type, bool inDeferred, float magnitude) {\n  if (!IsAlive()) {\n    x460_knockBackController.SetAvailableState(EKnockBackAnimationState::Hurled, false);\n  } else if (!x460_knockBackController.TestAvailableState(EKnockBackAnimationState::KnockBack) &&\n             info.GetWeaponMode().IsCharged()) {\n    x460_knockBackController.SetAnimationStateRange(EKnockBackAnimationState::Hurled, EKnockBackAnimationState::Fall);\n  }\n\n  CPatterned::KnockBack(dir, mgr, info, type, inDeferred, magnitude);\n  x460_knockBackController.SetAnimationStateRange(EKnockBackAnimationState::Flinch, EKnockBackAnimationState::Fall);\n  if (!IsAlive()) {\n    Stop();\n    x150_momentum.zeroOut();\n  } else if (x460_knockBackController.GetActiveParms().x0_animState == EKnockBackAnimationState::Hurled) {\n    x330_stateMachineState.SetState(mgr, *this, GetStateMachine(), \"Hurled\"sv);\n  }\n}\n\nbool CChozoGhost::CanBeShot(const CStateManager& mgr, int) { return IsVisibleEnough(mgr); }\n\nvoid CChozoGhost::Dead(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    ReleaseCoverPoint(mgr, x674_coverPoint);\n    x3e8_alphaDelta = 4.f;\n    x664_30_fadedOut = false;\n    x664_29_fadedIn = false;\n    x68c_boneTracking.SetActive(false);\n    Stop();\n  }\n}\n\nvoid CChozoGhost::SelectTarget(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate)\n    FindBestAnchor(mgr);\n}\n\nvoid CChozoGhost::Run(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    GetBodyController()->SetLocomotionType(pas::ELocomotionType::Lurk);\n    x400_24_hitByPlayerProjectile = false;\n    x460_knockBackController.SetAvailableState(EKnockBackAnimationState::KnockBack, false);\n    x665_28_inRange = false;\n  } else if (msg == EStateMsg::Update) {\n    GetBodyController()->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(x688_.Seek(*this, x2e0_destPos), {}, 1.f));\n    if (x665_26_shouldSwoosh) {\n      x678_floorLevel = x2e0_destPos.z();\n      FloatToLevel(x678_floorLevel, dt);\n      GetModelData()->GetAnimationData()->SetParticleEffectState(\"SpeedSwoosh\", true, mgr);\n      x665_24_ = false;\n      if (!x665_28_inRange) {\n        const float range = 2.5f * (dt * x138_velocity.magnitude()) + x66c_;\n        x665_28_inRange = (GetTranslation() - x2e0_destPos).magSquared() < range * range;\n      }\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    GetBodyController()->SetLocomotionType(pas::ELocomotionType::Crouch);\n    SetDestPos(mgr.GetPlayer().GetTranslation());\n    GetModelData()->GetAnimationData()->SetParticleEffectState(\"SpeedSwoosh\", false, mgr);\n    x460_knockBackController.SetAvailableState(EKnockBackAnimationState::KnockBack, true);\n    x665_28_inRange = false;\n  }\n}\n\nvoid CChozoGhost::Generate(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x330_stateMachineState.SetDelay(x56c_fadeOutDelay);\n    x32c_animState = EAnimState::Ready;\n    x664_27_onGround = false;\n    const CRayCastResult& res = mgr.RayStaticIntersection(GetTranslation(), zeus::skDown, 100.f,\n                                                          CMaterialFilter::MakeInclude({EMaterialTypes::Floor}));\n    if (res.IsInvalid()) {\n      x678_floorLevel = mgr.GetPlayer().GetTranslation().z();\n    } else {\n      x678_floorLevel = res.GetPoint().z();\n    }\n    x3e8_alphaDelta = 1.f;\n    x664_29_fadedIn = true;\n    if (x56c_fadeOutDelay > 0.f) {\n      x6c8_spaceWarpTime = x56c_fadeOutDelay;\n      FindSpaceWarpPosition(mgr, zeus::skDown);\n    }\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::Jump, &CPatterned::TryJump, 0);\n    if (x32c_animState == EAnimState::Over) {\n      x68c_boneTracking.SetActive(true);\n      x68c_boneTracking.SetTarget(mgr.GetPlayer().GetUniqueId());\n      FloatToLevel(x678_floorLevel, dt);\n    } else if (x32c_animState == EAnimState::Repeat) {\n      x450_bodyController->SetLocomotionType(pas::ELocomotionType::Crouch);\n      if (!x664_27_onGround) {\n        const zeus::CVector3f& pos = GetTranslation();\n        SetTranslation({pos.x(), pos.y(), x678_floorLevel + x668_});\n        x664_27_onGround = true;\n      }\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n    x665_24_ = false;\n    x664_27_onGround = false;\n  }\n}\n\nvoid CChozoGhost::Deactivate(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x68c_boneTracking.SetActive(false);\n    ReleaseCoverPoint(mgr, x674_coverPoint);\n    x32c_animState = EAnimState::Ready;\n    x665_24_ = true;\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::Generate, &CPatterned::TryGenerateNoXf, 1);\n    if (x32c_animState == EAnimState::Repeat)\n      GetBodyController()->SetLocomotionType(pas::ELocomotionType::Relaxed);\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n  }\n}\n\nvoid CChozoGhost::Attack(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    CTeamAiMgr::AddAttacker(CTeamAiMgr::EAttackType::Melee, mgr, x6c4_teamMgr, GetUniqueId());\n    x32c_animState = EAnimState::Ready;\n    if (x6d8_ == 1)\n      x67c_attackType = 3;\n    else if (x6d8_ == 2)\n      x67c_attackType = 4;\n    else if (x6d8_ == 3)\n      x67c_attackType = 5;\n\n    const CRayCastResult& res = mgr.RayStaticIntersection(GetTranslation() + (zeus::skUp * 0.5f), zeus::skUp, x670_,\n                                                          CMaterialFilter::MakeInclude({EMaterialTypes::Solid}));\n    if (x665_26_shouldSwoosh && res.IsValid()) {\n      x67c_attackType = 2;\n      x460_knockBackController.SetAvailableState(EKnockBackAnimationState::KnockBack, false);\n    }\n    x150_momentum.zeroOut();\n    xfc_constantForce.zeroOut();\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::MeleeAttack, &CPatterned::TryMeleeAttack, x67c_attackType);\n    GetBodyController()->GetCommandMgr().DeliverTargetVector(mgr.GetPlayer().GetTranslation() - GetTranslation());\n    if (x67c_attackType != 2)\n      FloatToLevel(x678_floorLevel, dt);\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n    x665_26_shouldSwoosh = false;\n    x460_knockBackController.SetAvailableState(EKnockBackAnimationState::KnockBack, true);\n    CTeamAiMgr::ResetTeamAiRole(CTeamAiMgr::EAttackType::Ranged, mgr, x6c4_teamMgr, GetUniqueId(), true);\n  }\n}\n\nvoid CChozoGhost::Shuffle(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg != EStateMsg::Activate)\n    return;\n\n  const CBehaveChance& chance1 = ChooseBehaveChanceRange(mgr);\n  CTeamAiRole* role = CTeamAiMgr::GetTeamAiRole(mgr, x6c4_teamMgr, GetUniqueId());\n  if (role && role->GetTeamAiRole() == CTeamAiRole::ETeamAiRole::Ranged &&\n      !CTeamAiMgr::CanAcceptAttacker(CTeamAiMgr::EAttackType::Ranged, mgr, x6c4_teamMgr, GetUniqueId())) {\n    x680_behaveType = EBehaveType::Attack;\n  }\n\n  const CBehaveChance& chance2 = ChooseBehaveChanceRange(mgr);\n  x680_behaveType = chance2.GetBehave(x680_behaveType, mgr);\n  if (x680_behaveType == EBehaveType::Lurk)\n    x684_lurkDelay = chance1.GetLurkTime();\n  else if (x680_behaveType == EBehaveType::Attack) {\n    x665_25_ = mgr.GetActiveRandom()->Float() < chance1.GetChargeAttack();\n    const int rnd = mgr.GetActiveRandom()->Next();\n    x6d8_ = (rnd - (rnd / chance1.GetNumBolts()) * chance1.GetNumBolts()) + 1;\n  }\n  x664_31_ = false;\n  x665_27_playerInLeashRange = false;\n}\n\nvoid CChozoGhost::InActive(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    if (!x450_bodyController->GetActive())\n      x450_bodyController->Activate(mgr);\n\n    if (x63c_ == 3) {\n      x450_bodyController->SetLocomotionType(pas::ELocomotionType::Crouch);\n      x42c_color.a() = 1.f;\n    } else {\n      x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n      x42c_color.a() = 0.f;\n    }\n\n    RemoveMaterial(EMaterialTypes::Solid, mgr);\n    x150_momentum.zeroOut();\n    x665_24_ = true;\n  }\n}\n\nvoid CChozoGhost::Taunt(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::Taunt, &CPatterned::TryTaunt, 0);\n    FloatToLevel(x678_floorLevel, dt);\n  } else {\n    x32c_animState = EAnimState::NotReady;\n    x665_26_shouldSwoosh = false;\n  }\n}\n\nvoid CChozoGhost::Hurled(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x328_25_verticalMovement = false;\n    x664_27_onGround = false;\n    x665_24_ = true;\n  } else if (msg == EStateMsg::Update) {\n    x3e8_alphaDelta = 2.f;\n    if (x664_27_onGround)\n      return;\n\n    if (x138_velocity.z() < 0.f) {\n      const CRayCastResult& res = mgr.RayStaticIntersection(GetTranslation() + zeus::skUp, zeus::skDown, 2.f,\n                                                            CMaterialFilter::MakeInclude({EMaterialTypes::Floor}));\n      if (res.IsValid()) {\n        x664_27_onGround = true;\n        x150_momentum.zeroOut();\n        SetVelocityWR({x138_velocity.x(), x138_velocity.y(), 0.f});\n        x678_floorLevel = res.GetPoint().z();\n        x330_stateMachineState.SetCodeTrigger();\n      }\n    }\n    if (!x664_27_onGround && x638_hurlRecoverTime < x330_stateMachineState.GetTime()) {\n      GetBodyController()->GetCommandMgr().DeliverCmd(CBodyStateCmd(EBodyStateCmd::ExitState));\n      GetBodyController()->SetLocomotionType(pas::ELocomotionType::Lurk);\n      x330_stateMachineState.SetCodeTrigger();\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x328_25_verticalMovement = true;\n    x150_momentum.zeroOut();\n  }\n}\n\nvoid CChozoGhost::WallDetach(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x330_stateMachineState.SetDelay(x56c_fadeOutDelay);\n    x3e8_alphaDelta = 0.f;\n    x664_29_fadedIn = false;\n    if (x56c_fadeOutDelay > 0.f) {\n      x6c8_spaceWarpTime = x56c_fadeOutDelay;\n      FindSpaceWarpPosition(mgr, GetTransform().basis[1]);\n    }\n    TUniqueId wpId = GetWaypointForState(mgr, EScriptObjectState::Attack, EScriptObjectMessage::Follow);\n    TCastToConstPtr<CScriptWaypoint> wp;\n    if (wpId != kInvalidUniqueId) {\n      wp = TCastToConstPtr<CScriptWaypoint>(mgr.GetObjectById(wpId));\n    }\n    if (wp)\n      SetDestPos(wp->GetTranslation());\n    else\n      SetDestPos(GetTranslation() + (2.f * x66c_) * GetTranslation());\n\n    SendScriptMsgs(EScriptObjectState::Attack, mgr, EScriptObjectMessage::Follow);\n  } else if (msg == EStateMsg::Deactivate) {\n    x68c_boneTracking.SetActive(true);\n    x68c_boneTracking.SetTarget(mgr.GetPlayer().GetUniqueId());\n    x665_24_ = false;\n    x680_behaveType = EBehaveType::Move;\n  }\n}\n\nvoid CChozoGhost::Growth(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x330_stateMachineState.SetDelay(x56c_fadeOutDelay);\n    GetBodyController()->SetLocomotionType(pas::ELocomotionType::Crouch);\n    x3e8_alphaDelta = 1.f;\n    x664_29_fadedIn = true;\n    if (x56c_fadeOutDelay > 0.f) {\n      x6c8_spaceWarpTime = x56c_fadeOutDelay;\n      FindSpaceWarpPosition(mgr, zeus::skUp);\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x665_24_ = false;\n    x68c_boneTracking.SetActive(false);\n    x68c_boneTracking.SetTarget(mgr.GetPlayer().GetUniqueId());\n  }\n}\n\nvoid CChozoGhost::Land(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Update) {\n    FloatToLevel(x678_floorLevel, dt);\n    if (std::fabs(x678_floorLevel - GetTranslation().z()) < 0.05f) {\n      x330_stateMachineState.SetCodeTrigger();\n    }\n  }\n}\n\nvoid CChozoGhost::Lurk(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x330_stateMachineState.SetDelay(x684_lurkDelay);\n  } else if (msg == EStateMsg::Update) {\n    FloatToLevel(x678_floorLevel, dt);\n  }\n}\n\nbool CChozoGhost::Leash(CStateManager& mgr, float arg) {\n  return x665_27_playerInLeashRange || CPatterned::Leash(mgr, arg);\n}\n\nbool CChozoGhost::InRange(CStateManager& mgr, float arg) { return x665_28_inRange; }\n\nbool CChozoGhost::ShouldAttack(CStateManager& mgr, float arg) { return x680_behaveType == EBehaveType::Attack; }\n\nbool CChozoGhost::AggressionCheck(CStateManager& mgr, float arg) { return x665_29_aggressive; }\n\nbool CChozoGhost::ShouldTaunt(CStateManager& mgr, float arg) { return x680_behaveType == EBehaveType::Taunt; }\n\nbool CChozoGhost::ShouldFlinch(CStateManager& mgr, float arg) { return x664_25_flinch; }\n\nbool CChozoGhost::ShouldMove(CStateManager& mgr, float arg) { return x680_behaveType == EBehaveType::Move; }\n\nbool CChozoGhost::AIStage(CStateManager& mgr, float arg) { return arg == x63c_; }\n\nu8 CChozoGhost::GetModelAlphau8(const CStateManager& mgr) const {\n  if (mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::XRay && IsAlive())\n    return 255;\n  return u8(x42c_color.a() * 255);\n}\n\nbool CChozoGhost::IsOnGround() const { return x664_27_onGround; }\n\nCProjectileInfo* CChozoGhost::GetProjectileInfo() { return x67c_attackType == 2 ? &x578_ : &x5a0_; }\n\nvoid CChozoGhost::AddToTeam(CStateManager& mgr) {\n  if (x6c4_teamMgr == kInvalidUniqueId)\n    x6c4_teamMgr = CTeamAiMgr::GetTeamAiMgr(*this, mgr);\n\n  if (x6c4_teamMgr == kInvalidUniqueId)\n    return;\n\n  if (TCastToPtr<CTeamAiMgr> teamMgr = mgr.ObjectById(x6c4_teamMgr))\n    teamMgr->AssignTeamAiRole(*this, CTeamAiRole::ETeamAiRole::Ranged, CTeamAiRole::ETeamAiRole::Unknown,\n                              CTeamAiRole::ETeamAiRole::Invalid);\n}\n\nvoid CChozoGhost::RemoveFromTeam(CStateManager& mgr) {\n  if (x6c4_teamMgr == kInvalidUniqueId)\n    return;\n\n  if (TCastToPtr<CTeamAiMgr> teamMgr = mgr.ObjectById(x6c4_teamMgr)) {\n    if (teamMgr->IsPartOfTeam(GetUniqueId())) {\n      teamMgr->RemoveTeamAiRole(GetUniqueId());\n      x6c4_teamMgr = kInvalidUniqueId;\n    }\n  }\n}\n\nvoid CChozoGhost::FloatToLevel(float level, float dt) {\n  const zeus::CVector3f& pos = GetTranslation();\n  SetTranslation({pos.x(), pos.y(), 4.f * (level - pos.z()) * dt + pos.z()});\n}\n\nconst CChozoGhost::CBehaveChance& CChozoGhost::ChooseBehaveChanceRange(CStateManager& mgr) {\n  const float dist = (GetTranslation() - mgr.GetPlayer().GetTranslation()).magnitude();\n  if (x654_ <= dist && x658_ > dist)\n    return x5e8_;\n  else if (x658_ <= dist)\n    return x608_;\n  else\n    return x5c8_;\n}\n\nvoid CChozoGhost::FindSpaceWarpPosition(CStateManager& mgr, const zeus::CVector3f& dir) {\n  const zeus::CVector3f& center = GetBoundingBox().center();\n  const CRayCastResult& res =\n      mgr.RayStaticIntersection(center + (dir * 8.f), -dir, 8.f, CMaterialFilter::MakeInclude({EMaterialTypes::Solid}));\n  if (res.IsInvalid()) {\n    x6cc_spaceWarpPosition = center + dir;\n  } else {\n    x6cc_spaceWarpPosition = res.GetPoint();\n  }\n}\n\nvoid CChozoGhost::FindBestAnchor(CStateManager& mgr) {\n  x665_27_playerInLeashRange = false;\n  u32 chance = mgr.GetActiveRandom()->Next() % 100;\n  chance = chance < x65c_nearChance ? 0 : (chance < (x65c_nearChance + x660_midChance)) + 2;\n  float dVar15 = 10.f * x658_;\n  float dVar14 = dVar15;\n  float dVar13 = dVar15;\n  if (chance == 0) {\n    dVar13 = dVar15 * 10.f;\n    dVar14 = dVar15 * 5.f;\n  } else if (chance == 1) {\n    dVar13 = dVar15 * 5.f;\n    dVar15 *= 10.f;\n  } else if (chance == 2) {\n    dVar14 = dVar15 * 5.f;\n    dVar15 *= 10.f;\n  }\n\n  float prevDist = FLT_MAX;\n  CScriptCoverPoint* target = nullptr;\n  for (CEntity* ent : mgr.GetAiWaypointObjectList()) {\n    if (TCastToPtr<CScriptCoverPoint> cover = ent) {\n      if (cover->GetActive() && !cover->GetInUse(kInvalidUniqueId) && cover->GetAreaIdAlways() == GetAreaId()) {\n        float fVar17 = (cover->GetTranslation() - GetTranslation()).magnitude();\n        if (2.f * x66c_ <= fVar17) {\n          float dist = std::max(0.f, x654_ - fVar17);\n          zeus::CVector3f diff = cover->GetTranslation() - mgr.GetPlayer().GetTranslation();\n          fVar17 = diff.magnitude();\n          if (x2fc_minAttackRange <= fVar17) {\n            if (std::fabs(diff.z()) / fVar17 < 0.2f) {\n              dist = (20.f * x658_) * ((std::fabs(diff.z()) / fVar17) - 0.2f) + dist;\n            }\n            if (x654_ <= fVar17) {\n              if (x658_ <= fVar17) {\n                dist = (dist + dVar13);\n              } else {\n                dist = (dist + dVar14);\n                if (dist < prevDist) {\n                  fVar17 = 1.f / fVar17;\n                  diff = diff * fVar17;\n                  dist += (10.f * x658_) * (1.f - mgr.GetPlayer().GetTransform().basis[1].dot(diff));\n                }\n              }\n            } else {\n              dist += dVar15;\n              if (dist < prevDist) {\n                fVar17 = 1.f / fVar17;\n                diff = diff * fVar17;\n                dist += (10.f * x658_) * (1.f - mgr.GetPlayer().GetTransform().basis[1].dot(diff));\n              }\n            }\n            if (dist < prevDist) {\n              dist += x658_ * mgr.GetActiveRandom()->Float();\n              if (dist < prevDist) {\n                x665_27_playerInLeashRange = false;\n                target = cover;\n                prevDist = dist;\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n\n  if (target) {\n    x2dc_destObj = target->GetUniqueId();\n    SetDestPos(target->GetTranslation());\n    ReleaseCoverPoint(mgr, x674_coverPoint);\n    SetCoverPoint(target, x674_coverPoint);\n  } else if (mgr.GetPlayer().GetAreaIdAlways() == GetAreaIdAlways()) {\n    x2dc_destObj = mgr.GetPlayer().GetUniqueId();\n    zeus::CVector3f destPos =\n        mgr.GetPlayer().GetTranslation() - x654_ * (mgr.GetPlayer().GetTranslation() - GetTranslation()).normalized();\n    const CRayCastResult& res =\n        mgr.RayStaticIntersection(destPos, zeus::skDown, 8.f, CMaterialFilter::MakeInclude(EMaterialTypes::Floor));\n    if (res.IsValid())\n      destPos = res.GetPoint();\n    SetDestPos(destPos);\n  } else {\n    x2dc_destObj = kInvalidUniqueId;\n    x2e0_destPos = GetTranslation();\n  }\n}\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CChozoGhost.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/Character/CBoneTracking.hpp\"\n#include \"Runtime/Weapon/CProjectileInfo.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce::MP1 {\nenum class EBehaveType { Lurk, Taunt, Attack, Move, None };\n\nclass CChozoGhost : public CPatterned {\npublic:\n  class CBehaveChance {\n    u32 x0_propertyCount;\n    float x4_lurk;\n    float x8_taunt;\n    float xc_attack;\n    float x10_move;\n    float x14_lurkTime;\n    float x18_chargeAttack;\n    u32 x1c_numBolts;\n\n  public:\n    explicit CBehaveChance(CInputStream&);\n\n    EBehaveType GetBehave(EBehaveType type, CStateManager& mgr) const;\n    float GetLurk() const { return x4_lurk; }\n    float GetTaunt() const { return x8_taunt; }\n    float GetAttack() const { return xc_attack; }\n    float GetMove() const { return x10_move; }\n    float GetLurkTime() const { return x14_lurkTime; }\n    float GetChargeAttack() const { return x18_chargeAttack; }\n    u32 GetNumBolts() const { return x1c_numBolts; }\n  };\n\nprivate:\n  float x568_hearingRadius;\n  float x56c_fadeOutDelay;\n  float x570_attackDelay;\n  float x574_freezeTime;\n  CProjectileInfo x578_;\n  CProjectileInfo x5a0_;\n  CBehaveChance x5c8_;\n  CBehaveChance x5e8_;\n  CBehaveChance x608_;\n  s16 x628_soundImpact;\n  float x62c_;\n  s16 x630_sfxFadeIn;\n  s16 x632_sfxFadeOut;\n  float x634_;\n  float x638_hurlRecoverTime;\n  u32 x63c_;\n  std::optional<TLockedToken<CGenDescription>> x640_projectileVisor;\n  s16 x650_sound_ProjectileVisor;\n  float x654_;\n  float x658_;\n  u32 x65c_nearChance;\n  u32 x660_midChance;\n  bool x664_24_behaviorEnabled : 1;\n  bool x664_25_flinch : 1;\n  bool x664_26_alert : 1 = false;\n  bool x664_27_onGround : 1 = false;\n  bool x664_28_ : 1 = false;\n  bool x664_29_fadedIn : 1 = false;\n  bool x664_30_fadedOut : 1 = false;\n  bool x664_31_ : 1 = false;\n  bool x665_24_ : 1 = true;\n  bool x665_25_ : 1 = false;\n  bool x665_26_shouldSwoosh : 1 = false;\n  bool x665_27_playerInLeashRange : 1 = false;\n  bool x665_28_inRange : 1 = false;\n  bool x665_29_aggressive : 1 = false;\n  float x668_ = 0.f;\n  float x66c_ = 0.f;\n  float x670_ = 0.f;\n  TUniqueId x674_coverPoint = kInvalidUniqueId;\n  float x678_floorLevel = 0.f;\n  u32 x67c_attackType = -1;\n  EBehaveType x680_behaveType = EBehaveType::Lurk;\n  float x684_lurkDelay = 1.f;\n  CSteeringBehaviors x688_;\n  CBoneTracking x68c_boneTracking;\n  TUniqueId x6c4_teamMgr = kInvalidUniqueId;\n  float x6c8_spaceWarpTime = 0.f;\n  zeus::CVector3f x6cc_spaceWarpPosition;\n  u32 x6d8_ = 1;\n\n  void AddToTeam(CStateManager& mgr);\n  void RemoveFromTeam(CStateManager& mgr);\n  void FloatToLevel(float f1, float dt);\n  const CBehaveChance& ChooseBehaveChanceRange(CStateManager& mgr);\n  bool IsVisibleEnough(const CStateManager& mgr) const { return GetModelAlphau8(mgr) > 31; }\n  void FindSpaceWarpPosition(CStateManager& mgr, const zeus::CVector3f& dir);\n  void FindBestAnchor(CStateManager& mgr);\n\npublic:\n  DEFINE_PATTERNED(ChozoGhost);\n\n  CChozoGhost(TUniqueId, std::string_view, const CEntityInfo&, const zeus::CTransform&, CModelData&&,\n              const CActorParameters&, const CPatternedInfo&, float, float, float, float, CAssetId, const CDamageInfo&,\n              CAssetId, const CDamageInfo&, const CChozoGhost::CBehaveChance&, const CChozoGhost::CBehaveChance&,\n              const CBehaveChance&, u16, float, u16, u16, u32, float, u32, float, CAssetId, s16, float, float, u32,\n              u32);\n\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) override;\n  void Think(float dt, CStateManager&) override;\n  void PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) override;\n  void Render(CStateManager& mgr) override;\n  void Touch(CActor& act, CStateManager& mgr) override;\n  EWeaponCollisionResponseTypes GetCollisionResponseType(const zeus::CVector3f& pos, const zeus::CVector3f& dir,\n                                                         const CWeaponMode& mode,\n                                                         EProjectileAttrib attrib) const override;\n  void DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) override;\n  void KnockBack(const zeus::CVector3f& dir, CStateManager& mgr, const CDamageInfo& info, EKnockBackType type,\n                 bool inDeferred, float magnitude) override;\n  bool CanBeShot(const CStateManager& mgr, int w1) override;\n  zeus::CVector3f GetOrigin(const CStateManager& mgr, const CTeamAiRole& role,\n                            const zeus::CVector3f& aimPos) const override {\n    return x34_transform.origin;\n  }\n  void Dead(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void SelectTarget(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Run(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Generate(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Deactivate(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Attack(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Shuffle(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void InActive(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Taunt(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Hurled(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void WallDetach(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Growth(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Land(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Lurk(CStateManager& mgr, EStateMsg msg, float dt) override;\n  bool Leash(CStateManager& mgr, float arg) override;\n  bool InRange(CStateManager& mgr, float arg) override;\n  bool ShouldAttack(CStateManager& mgr, float arg) override;\n  bool AggressionCheck(CStateManager& mgr, float arg) override;\n  bool ShouldTaunt(CStateManager& mgr, float arg) override;\n  bool ShouldFlinch(CStateManager& mgr, float arg) override;\n  bool ShouldMove(CStateManager& mgr, float arg) override;\n  bool AIStage(CStateManager& mgr, float arg) override;\n  u8 GetModelAlphau8(const CStateManager&) const override;\n  bool IsOnGround() const override;\n  float GetGravityConstant() const override { return 60.f; }\n  CProjectileInfo* GetProjectileInfo() override;\n};\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CDrone.cpp",
    "content": "#include \"Runtime/MP1/World/CDrone.hpp\"\n\n#include \"Runtime/Audio/CSfxManager.hpp\"\n#include \"Runtime/Collision/CGameCollision.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/MP1/World/CDroneLaser.hpp\"\n#include \"Runtime/Particle/CWeaponDescription.hpp\"\n#include \"Runtime/Weapon/CGameProjectile.hpp\"\n#include \"Runtime/Weapon/CWeapon.hpp\"\n#include \"Runtime/World/CGameLight.hpp\"\n#include \"Runtime/World/CPatternedInfo.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CScriptVisorFlare.hpp\"\n#include \"Runtime/World/CScriptWater.hpp\"\n#include \"Runtime/World/CTeamAiMgr.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\n#include \"Audio/SFX/Drones.h\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\n#include <algorithm>\n\nnamespace metaforce::MP1 {\nCDrone::CDrone(TUniqueId uid, std::string_view name, EFlavorType flavor, const CEntityInfo& info,\n               const zeus::CTransform& xf, float f1, CModelData&& mData, const CPatternedInfo& pInfo,\n               const CActorParameters& actParms, EMovementType movement, EColliderType colliderType, EBodyType bodyType,\n               const CDamageInfo& dInfo1, CAssetId aId1, const CDamageInfo& dInfo2, CAssetId aId2,\n               std::vector<CVisorFlare::CFlareDef> flares, float f2, float f3, float f4, float f5, float f6, float f7,\n               float f8, float f9, float f10, float f11, float f12, float f13, float f14, float f15, float f16,\n               float f17, float f18, float f19, float f20, CAssetId crscId, float f21, float f22, float f23, float f24,\n               s32 sId, bool b1)\n: CPatterned(ECharacter::Drone, uid, name, flavor, info, xf, std::move(mData), pInfo, movement, colliderType, bodyType,\n             actParms, flavor == EFlavorType::Zero ? EKnockBackVariant::Medium : EKnockBackVariant::Large)\n, x568_laserParticlesId(aId1)\n, x56c_(g_SimplePool->GetObj({SBIG('CRSC'), crscId}))\n, x57c_flares(std::move(flares))\n, x590_(dInfo1)\n, x5ac_(dInfo2)\n, x5e4_(f23)\n, x5ec_turnSpeed(f1)\n, x5f0_(f2)\n, x5f4_(f3)\n, x5f8_(f4)\n, x5fc_(f5)\n, x600_(f11)\n, x608_(f6)\n, x60c_(f7)\n, x610_(f8)\n, x614_(f9)\n, x618_(f10)\n, x61c_(f12)\n, x620_(f20)\n, x63c_(f13)\n, x640_(f14)\n, x648_(f15)\n, x64c_(f16)\n, x650_(f17)\n, x654_(f18)\n, x658_(f19)\n, x65c_(f21)\n, x660_(f22)\n, x664_(f24)\n, x690_colSphere(zeus::CSphere({0.f, 0.f, 1.8f}, 1.1f), CActor::GetMaterialList())\n, x6b0_pathFind(nullptr, 3 + int(b1) /* TODO double check */, pInfo.GetPathfindingIndex(), 1.f, 2.4f)\n, x7cc_laserSfx(CSfxManager::TranslateSFXID(sId))\n, x82c_shieldModel(std::make_unique<CModelData>(CStaticRes{aId2, zeus::skOne3f}))\n, x835_25_(b1) {\n  UpdateTouchBounds(pInfo.GetHalfExtent());\n  x460_knockBackController.SetEnableShock(true);\n  x460_knockBackController.SetAvailableState(EKnockBackAnimationState::Hurled, false);\n  x460_knockBackController.SetLocomotionDuringElectrocution(true);\n  MakeThermalColdAndHot();\n  CreateShadow(flavor != EFlavorType::One);\n}\n\nvoid CDrone::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CDrone::Think(float dt, CStateManager& mgr) {\n  if (x3fc_flavor == EFlavorType::One) {\n    if (mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::XRay) {\n      x42c_color.a() = 1.f;\n    } else {\n      x42c_color.a() = std::max(0.f, x428_damageCooldownTimer / 0.33f);\n    }\n  }\n\n  x403_25_enableStateMachine = !GetBodyController()->IsElectrocuting();\n  if (GetBodyController()->IsElectrocuting() && (x824_activeLasers[0] || x824_activeLasers[1])) {\n    x824_activeLasers[0] = false;\n    x824_activeLasers[1] = false;\n    UpdateLaser(mgr, 0, false);\n    UpdateLaser(mgr, 1, false);\n    SetVisorFlareEnabled(mgr, false);\n  }\n  CPatterned::Think(dt, mgr);\n\n  if (!GetActive())\n    return;\n\n  x5c8_ -= dt;\n  if (x7c4_ > 0.f) {\n    x7c4_ -= dt;\n  }\n\n  if (x5d0_ > 0.f) {\n    x5d0_ -= (mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed ? 3.f * dt : dt);\n  }\n\n  if (x624_ > 0.f) {\n    x624_ -= dt;\n  }\n\n  if (x644_ > 0.f) {\n    x644_ -= dt;\n  }\n\n  if (x824_activeLasers[0] || (x824_activeLasers[1] && IsAlive())) {\n    UpdateLasers(mgr, dt);\n    UpdateVisorFlare(mgr);\n  }\n\n  if (x834_25_ && IsAlive()) {\n    UpdateScanner(mgr, dt);\n  }\n\n  const float dist = (mgr.GetPlayer().GetTranslation() - GetTranslation()).magSquared();\n  if (x834_28_ && dist < x60c_ * x60c_) {\n    mgr.GetPlayerState()->GetStaticInterference().RemoveSource(GetUniqueId());\n    mgr.GetPlayerState()->GetStaticInterference().AddSource(\n        GetUniqueId(), std::max(0.f, x608_ - mgr.GetPlayerState()->GetStaticInterference().GetTotalInterference()),\n        0.2f);\n  }\n\n  if (!x834_28_ && dist < x614_ * x614_) {\n    mgr.GetPlayerState()->GetStaticInterference().RemoveSource(GetUniqueId());\n    mgr.GetPlayerState()->GetStaticInterference().AddSource(\n        GetUniqueId(), std::max(0.f, x610_ - mgr.GetPlayerState()->GetStaticInterference().GetTotalInterference()),\n        0.2f);\n  }\n\n  if (!x834_28_ && IsAlive() && !x835_25_) {\n    x5e0_ -= dt;\n    if (x5e0_ < 0.f) {\n      sub_801633a8(mgr);\n      x5e0_ = 0.1f;\n    }\n  }\n\n  const float healthDiff = x604_ - HealthInfo(mgr)->GetHP();\n  if (!zeus::close_enough(x600_, 0.f)) {\n    x5d0_ -= healthDiff / x600_;\n    x624_ -= healthDiff / x600_;\n  }\n  x604_ = HealthInfo(mgr)->GetHP();\n  if (x3fc_flavor == EFlavorType::One) {\n    if (!x834_30_visible) {\n      x5dc_ = zeus::max(0.f, x5dc_ - (3.f * dt));\n    } else {\n      x5dc_ = zeus::min(1.f, x5dc_ + (3.f * dt));\n    }\n    x5e8_shieldTime = zeus::max(0.f, x5e8_shieldTime - dt);\n\n    if (zeus::close_enough(x5dc_, 0.f)) {\n      if (x7d0_) {\n        CSfxManager::RemoveEmitter(x7d0_);\n        x7d0_.reset();\n      }\n    } else if (!x7d0_ && IsAlive()) {\n      x7d0_ = CSfxManager::AddEmitter(SFXsfx00DD, GetTranslation(), zeus::skZero3f, true, true, 127, GetAreaIdAlways());\n    }\n  }\n  sub_8015f25c(dt, mgr);\n  sub_8015f158(dt);\n\n  if (!x835_25_) {\n    CGameCollision::AvoidStaticCollisionWithinRadius(mgr, *this, 8, dt, 0.25f, 3.5f * GetModelData()->GetScale().y(),\n                                                     3000.f, 0.5f);\n  }\n  if (x66c_ > 0.f) {\n    x66c_ -= dt;\n  } else {\n    x668_elevation = mgr.RayStaticIntersection(GetTranslation(), zeus::skDown, 10000.f,\n                                               CMaterialFilter::MakeInclude({EMaterialTypes::Solid}))\n                         .GetT();\n    x66c_ = 0.f;\n  }\n\n  if (IsAlive() && x835_25_) {\n    zeus::CAABox box = GetBoundingBox();\n    box.accumulateBounds(GetTranslation() + 20.f * zeus::skDown);\n    EntityList nearList;\n    mgr.BuildNearList(nearList, GetBoundingBox(), CMaterialFilter::MakeInclude({EMaterialTypes::Trigger}), this);\n    for (TUniqueId id : nearList) {\n      if (const TCastToConstPtr<CScriptWater> water = mgr.GetObjectById(id)) {\n        zeus::CAABox waterBox = water->GetTriggerBoundsWR();\n        if (waterBox.max.z() - GetTranslation().z() < 3.f) {\n          float z = 20.f;\n          if (waterBox.max.z() - GetTranslation().z() < 1.5f) {\n            z = 60.f;\n          }\n          ApplyImpulseWR(GetMoveToORImpulseWR(GetTransform().transposeRotate(z * (dt * zeus::skDown)), dt),\n                         zeus::CAxisAngle());\n        }\n      }\n    }\n  }\n  if (IsAlive() && x668_elevation < x664_) {\n    ApplyImpulseWR(GetMoveToORImpulseWR(GetTransform().transposeRotate(dt * (1.f * zeus::skUp)), dt),\n                   zeus::CAxisAngle());\n  }\n  xe7_31_targetable = IsAlive();\n}\n\nvoid CDrone::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) {\n  CPatterned::AcceptScriptMsg(msg, sender, mgr);\n\n  switch (msg) {\n  case EScriptObjectMessage::Activate: {\n    SetLightEnabled(mgr, true);\n    AddToTeam(mgr);\n    break;\n  }\n  case EScriptObjectMessage::Deactivate:\n  case EScriptObjectMessage::Deleted: {\n    for (TUniqueId& unkId : x7d8_laserIds) {\n      if (unkId != kInvalidUniqueId) {\n        mgr.FreeScriptObject(unkId);\n        unkId = kInvalidUniqueId;\n      }\n    }\n    RemoveFromTeam(mgr);\n    mgr.GetPlayerState()->GetStaticInterference().RemoveSource(GetUniqueId());\n    if (x578_lightId != kInvalidUniqueId) {\n      mgr.FreeScriptObject(x578_lightId);\n      x578_lightId = kInvalidUniqueId;\n    }\n    if (x57a_visorFlareId != kInvalidUniqueId) {\n      mgr.FreeScriptObject(x57a_visorFlareId);\n      x57a_visorFlareId = kInvalidUniqueId;\n    }\n\n    if (x7d0_) {\n      CSfxManager::RemoveEmitter(x7d0_);\n      x7d0_.reset();\n    }\n    break;\n  }\n  case EScriptObjectMessage::Alert:\n    x834_29_codeTrigger = true;\n    break;\n  case EScriptObjectMessage::OnFloor:\n    if (!x835_26_ && x834_24_waveHit && !IsAlive()) {\n      x835_26_ = true;\n      MassiveFrozenDeath(mgr);\n    }\n    break;\n  case EScriptObjectMessage::Registered:\n    x450_bodyController->Activate(mgr);\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Lurk);\n    x450_bodyController->BodyStateInfo().SetMaximumPitch(0.f);\n    x5cc_ = 0.f;\n    x460_knockBackController.SetEnableFreeze(false);\n    AddMaterial(EMaterialTypes::AIJoint, mgr);\n    x578_lightId = mgr.AllocateUniqueId();\n    mgr.AddObject(new CGameLight(x578_lightId, GetAreaIdAlways(), GetActive(), \"LaserLight\"sv, {}, GetUniqueId(),\n                                 CLight::BuildPoint(zeus::skZero3f, zeus::skRed), 0, 0, 0.f));\n    break;\n  case EScriptObjectMessage::InitializedInArea: {\n    x6b0_pathFind.SetArea(mgr.GetWorld()->GetAreaAlways(GetAreaIdAlways())->GetPostConstructed()->x10bc_pathArea);\n    if (x688_teamMgr == kInvalidUniqueId) {\n      x688_teamMgr = CTeamAiMgr::GetTeamAiMgr(*this, mgr);\n      if (GetActive()) {\n        AddToTeam(mgr);\n      }\n    }\n\n    x604_ = HealthInfo(mgr)->GetHP();\n    x55c_moveScale = 1.f / GetModelData()->GetScale();\n    if (x835_25_)\n      SetSoundEventPitchBend(0);\n    break;\n  }\n  default:\n    break;\n  }\n}\n\nvoid CDrone::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) {\n  CPatterned::AddToRenderer(frustum, mgr);\n}\n\nvoid CDrone::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) {\n  CPatterned::PreRender(mgr, frustum);\n  if (x3fc_flavor == EFlavorType::One) {\n    if (HasModelData() && GetModelData()->HasAnimData() && GetModelData()->HasNormalModel()) {\n      if (GetModelAlphau8(mgr) == 0)\n        GetModelData()->GetAnimationData()->BuildPose();\n    }\n  }\n}\n\nvoid CDrone::Render(CStateManager& mgr) {\n  bool isOne = x3fc_flavor == EFlavorType::One;\n  if (!isOne || GetModelAlphau8(mgr) != 0) {\n    if (isOne && mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::XRay) {\n      CElementGen::SetSubtractBlend(true);\n      CElementGen::SetMoveRedToAlphaBuffer(true);\n      CGraphics::SetFog(ERglFogMode::PerspLin, 0.f, 75.f, zeus::skBlack);\n      GetModelData()->GetAnimationData()->GetParticleDB().RenderSystemsToBeDrawnFirst();\n      mgr.SetupFogForArea3XRange(GetAreaIdAlways());\n    }\n    CPatterned::Render(mgr);\n    if (isOne && mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::XRay) {\n      CGraphics::SetFog(ERglFogMode::PerspLin, 0.f, 75.f, zeus::skBlack);\n      GetModelData()->GetAnimationData()->GetParticleDB().RenderSystemsToBeDrawnLast();\n      mgr.SetupFogForArea(GetAreaIdAlways());\n      CElementGen::SetSubtractBlend(false);\n      CElementGen::SetMoveRedToAlphaBuffer(false);\n    }\n\n    if (isOne && !zeus::close_enough(x5dc_, 0)) {\n      x82c_shieldModel->Render(\n          mgr, GetLctrTransform(\"Shield_LCTR\"sv), GetActorLights(),\n          CModelFlags{8, 0, 3, zeus::CColor::lerp({1.f, 1.f, 1.f, x5dc_}, {1.f, 0.f, 0.f, 1.f}, x5e8_shieldTime)});\n    }\n  }\n}\n\nbool CDrone::CanRenderUnsorted(const CStateManager& mgr) const {\n  if (!zeus::close_enough(x5dc_, 0.f))\n    return false;\n  return CPatterned::CanRenderUnsorted(mgr);\n}\n\nconst CDamageVulnerability* CDrone::GetDamageVulnerability(const zeus::CVector3f&, const zeus::CVector3f& dir,\n                                                           const CDamageInfo&) const {\n  if (x3fc_flavor == EFlavorType::One && HitShield(-dir)) {\n    x5e8_shieldTime = 1.f;\n    return &CDamageVulnerability::ReflectVulnerabilty();\n  }\n  return CAi::GetDamageVulnerability();\n}\n\nvoid CDrone::Touch(CActor& act, CStateManager& mgr) {\n  CPatterned::Touch(act, mgr);\n  if (TCastToPtr<CWeapon> weapon = act) {\n    if (IsAlive()) {\n      x834_24_waveHit = weapon->GetType() == EWeaponType::Wave;\n      if (x3fc_flavor == CPatterned::EFlavorType::One && HitShield(weapon->GetTranslation() - GetTranslation())) {\n        x5e8_shieldTime = 1.f;\n      }\n    }\n  }\n}\n\nEWeaponCollisionResponseTypes CDrone::GetCollisionResponseType(const zeus::CVector3f&, const zeus::CVector3f& dir,\n                                                               const CWeaponMode&, EProjectileAttrib) const {\n  if (x3fc_flavor == EFlavorType::One && HitShield(-dir)) {\n    x5e8_shieldTime = 1.f;\n    return EWeaponCollisionResponseTypes::Unknown86;\n  }\n  return EWeaponCollisionResponseTypes::Unknown36;\n}\n\nvoid CDrone::DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) {\n  switch (type) {\n  case EUserEventType::Projectile:\n    sub_80165984(mgr, GetLctrTransform(node.GetLocatorName()));\n    return;\n  case EUserEventType::Delete:\n    if (x7d0_) {\n      CSfxManager::RemoveEmitter(x7d0_);\n      x7d0_.reset();\n    }\n    MassiveDeath(mgr);\n    break;\n  case EUserEventType::DamageOn: {\n    if (IsAlive() && x835_24_) {\n      if (!x824_activeLasers[0]) {\n        UpdateLaser(mgr, 0, true);\n        x824_activeLasers[0] = true;\n        SetVisorFlareEnabled(mgr, true);\n      } else if (x3fc_flavor == EFlavorType::One) {\n        UpdateLaser(mgr, 1, true);\n        x824_activeLasers[1] = true;\n      }\n    }\n    return;\n  }\n  case EUserEventType::DamageOff: {\n    if (x824_activeLasers[0]) {\n      UpdateLaser(mgr, 0, false);\n      x824_activeLasers[0] = false;\n      SetVisorFlareEnabled(mgr, false);\n    } else if (x3fc_flavor == EFlavorType::One) {\n      UpdateLaser(mgr, 1, false);\n      x824_activeLasers[1] = false;\n    }\n    return;\n  }\n  case EUserEventType::FadeIn: {\n    if (x3fc_flavor == EFlavorType::One)\n      x834_30_visible = true;\n    return;\n  }\n  case EUserEventType::FadeOut: {\n    if (x3fc_flavor == EFlavorType::One)\n      x834_30_visible = false;\n    return;\n  }\n  default:\n    break;\n  }\n  CPatterned::DoUserAnimEvent(mgr, node, type, dt);\n}\n\nconst CCollisionPrimitive* CDrone::GetCollisionPrimitive() const {\n  if (!x834_28_)\n    return &x690_colSphere;\n  return CPatterned::GetCollisionPrimitive();\n}\n\nvoid CDrone::Death(CStateManager& mgr, const zeus::CVector3f& direction, EScriptObjectState state) {\n  if (!IsAlive())\n    return;\n\n  x824_activeLasers[0] = false;\n  x824_activeLasers[1] = false;\n  UpdateLaser(mgr, 0, false);\n  UpdateLaser(mgr, 1, false);\n  SetVisorFlareEnabled(mgr, false);\n\n  if (x3e4_lastHP - HealthInfo(mgr)->GetHP() < x3d8_xDamageThreshold || x834_24_waveHit) {\n    x330_stateMachineState.SetState(mgr, *this, GetStateMachine(), \"Dead\"sv);\n  } else {\n    x834_28_ = true;\n    if (x3e0_xDamageDelay <= 0.f) {\n      SetTransform(zeus::lookAt(GetTranslation(), GetTranslation() - direction) *\n                   zeus::CTransform::RotateX(zeus::degToRad(45.f)));\n    }\n  }\n\n  if (x450_bodyController->GetPercentageFrozen() > 0.f) {\n    x450_bodyController->UnFreeze();\n  }\n\n  x400_25_alive = false;\n  SendScriptMsgs(state, mgr, EScriptObjectMessage::None);\n}\n\nvoid CDrone::KnockBack(const zeus::CVector3f& backVec, CStateManager& mgr, const CDamageInfo& info, EKnockBackType type,\n                       bool inDeferred, float magnitude) {\n  if (!IsAlive())\n    return;\n  CPatterned::KnockBack(backVec, mgr, info, type, inDeferred, magnitude);\n  if (GetKnockBackController().GetActiveParms().x0_animState == EKnockBackAnimationState::Invalid)\n    return;\n  x630_ = 0.5f;\n  x634_ = 1.f;\n}\n\nvoid CDrone::Patrol(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Lurk);\n    SetLightEnabled(mgr, true);\n    x834_25_ = true;\n  } else if (msg == EStateMsg::Update) {\n    EntityList nearList;\n    BuildNearList(EMaterialTypes::Character, EMaterialTypes::Player, nearList, 5.f, mgr);\n    if (!nearList.empty()) {\n      zeus::CVector3f sep = x45c_steeringBehaviors.Separation(\n          *this, static_cast<const CActor*>(mgr.GetObjectById(nearList[0]))->GetTranslation(), 5.f);\n      if (!sep.isZero()) {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(sep, zeus::skZero3f, 0.5f));\n      }\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    SetLightEnabled(mgr, false);\n    x834_25_ = false;\n  }\n  CPatterned::Patrol(mgr, msg, dt);\n}\n\nvoid CDrone::PathFind(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    auto searchOff = GetDestPos();\n    CPathFindSearch::EResult res = GetSearchPath()->Search(GetTranslation(), searchOff);\n    if (res != CPathFindSearch::EResult::Success &&\n        (res == CPathFindSearch::EResult::NoDestPoint || res == CPathFindSearch::EResult::NoPath)) {\n      if (GetSearchPath()->FindClosestReachablePoint(GetTranslation(), searchOff) ==\n          CPathFindSearch::EResult::Success) {\n        GetSearchPath()->Search(GetTranslation(), searchOff);\n        SetDestPos(searchOff);\n      }\n    }\n    if (x3fc_flavor == CPatterned::EFlavorType::One) {\n      x834_30_visible = true;\n    }\n  } else if (msg == EStateMsg::Update) {\n    CPatterned::PathFind(mgr, msg, dt);\n    x450_bodyController->GetCommandMgr().BlendSteeringCmds();\n    zeus::CVector3f moveVec = x450_bodyController->GetCommandMgr().GetMoveVector();\n    if (moveVec.canBeNormalized()) {\n      moveVec.normalize();\n      x450_bodyController->GetCommandMgr().ClearLocomotionCmds();\n      ApplyImpulseWR(GetMass() * (x5e4_ * moveVec), {});\n      const auto target = (mgr.GetPlayer().GetAimPosition(mgr, 0.f) - GetTranslation()).normalized();\n      x450_bodyController->GetCommandMgr().DeliverCmd(\n          CBCLocomotionCmd(FLT_EPSILON * GetTransform().frontVector(), target, 1.f));\n      x450_bodyController->GetCommandMgr().DeliverTargetVector(target);\n      StrafeFromCompanions(mgr);\n      if (x630_ <= 0.f) {\n        x634_ = 0.333333f;\n      }\n    } else if (x630_ <= 0.f) {\n      x634_ = 0.f;\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    CPatterned::PathFind(mgr, msg, dt);\n  }\n}\n\nvoid CDrone::TargetPlayer(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x3b8_turnSpeed = x5ec_turnSpeed;\n    x450_bodyController->SetTurnSpeed(x5ec_turnSpeed);\n    if (x450_bodyController->GetLocomotionType() != pas::ELocomotionType::Combat)\n      x450_bodyController->SetLocomotionType(pas::ELocomotionType::Combat);\n    x450_bodyController->BodyStateInfo().SetMaximumPitch(zeus::degToRad(60.f));\n    SetDestPos(mgr.GetPlayer().GetAimPosition(mgr, 0.f));\n    x400_24_hitByPlayerProjectile = false;\n    if (x3fc_flavor == EFlavorType::One)\n      x834_30_visible = true;\n    x330_stateMachineState.SetDelay(std::max(0.3f, x624_));\n  } else if (msg == EStateMsg::Update) {\n    zeus::CVector3f target = (mgr.GetPlayer().GetAimPosition(mgr, 0.f) - GetTranslation()).normalized();\n    x450_bodyController->GetCommandMgr().DeliverCmd(\n        CBCLocomotionCmd(FLT_EPSILON * GetTransform().frontVector(), target, 1.f));\n    x450_bodyController->GetCommandMgr().DeliverTargetVector(target);\n    StrafeFromCompanions(mgr);\n    if (x630_ <= 0.f)\n      x634_ = 0.f;\n  } else if (msg == EStateMsg::Deactivate) {\n    SetDestPos(mgr.GetPlayer().GetTranslation() + zeus::CVector3f{0.f, 0.f, x664_});\n  }\n}\n\nvoid CDrone::TargetCover(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg != EStateMsg::Update) {\n    return;\n  }\n\n  // Don't ask I have no idea....\n  const zeus::CVector3f vec{1.f * x5e4_ * 0.f, 1.f * x5e4_ * 0.f, 1.f * x5e4_ * 1.f};\n  ApplyImpulseWR(GetMoveToORImpulseWR(GetTransform().transposeRotate(vec), 1.f), {});\n}\n\nvoid CDrone::Deactivate(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg != EStateMsg::Activate)\n    return;\n  DeathDelete(mgr);\n}\n\nvoid CDrone::Attack(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x7c8_ = 0;\n    x834_31_attackOver = false;\n    const auto playerAimPos = mgr.GetPlayer().GetAimPosition(mgr, 0.f);\n    const auto& xf = GetTransform();\n    const auto frontVec = xf.frontVector();\n    zeus::CVector3f out;\n    if ((playerAimPos - GetTranslation()).normalized().dot(frontVec) >= 0.8f) {\n      out = playerAimPos;\n    } else {\n      out = GetTranslation() + 10.f * frontVec;\n    }\n    s32 state = mgr.GetActiveRandom()->Next() % 4;\n    if (state == 0) {\n      x7e0_lasersStart[0] = out + (3.f * xf.rightVector()) - (4.f * xf.upVector());\n      x7fc_lasersEnd[0] = out - (3.f * xf.rightVector()) + (4.f * xf.upVector());\n      x7e0_lasersStart[1] = out - (3.f * xf.rightVector()) - (4.f * xf.upVector());\n      x7fc_lasersEnd[1] = out + (3.f * xf.rightVector()) + (4.f * xf.upVector());\n    } else if (state == 1) {\n      x7e0_lasersStart[0] = out + (3.f * xf.rightVector()) + (4.f * xf.upVector());\n      x7fc_lasersEnd[0] = out - (3.f * xf.rightVector()) - (4.f * xf.upVector());\n      x7e0_lasersStart[1] = out - (3.f * xf.rightVector()) + (4.f * xf.upVector());\n      x7fc_lasersEnd[1] = out + (3.f * xf.rightVector()) - (4.f * xf.upVector());\n    } else if (state == 2) {\n      x7e0_lasersStart[0] = out - (4.f * xf.rightVector()) - (3.f * xf.upVector());\n      x7fc_lasersEnd[0] = out + (4.f * xf.rightVector()) + (3.f * xf.upVector());\n      x7e0_lasersStart[1] = out + (4.f * xf.rightVector()) - (3.f * xf.upVector());\n      x7fc_lasersEnd[1] = out - (4.f * xf.rightVector()) + (3.f * xf.upVector());\n    } else if (state == 3) {\n      x7e0_lasersStart[0] = out - (4.f * xf.rightVector()) + (3.f * xf.upVector());\n      x7fc_lasersEnd[0] = out + (4.f * xf.rightVector()) - (3.f * xf.upVector());\n      x7e0_lasersStart[1] = out + (4.f * xf.rightVector()) + (3.f * xf.upVector());\n      x7fc_lasersEnd[1] = out - (4.f * xf.rightVector()) - (3.f * xf.upVector());\n    }\n    x818_lasersTime[0] = 0.f;\n    x818_lasersTime[1] = 0.f;\n    x835_24_ = true;\n  } else if (msg == EStateMsg::Update) {\n    if (x7c8_ == 0) {\n      if (GetBodyController()->GetCurrentStateId() == pas::EAnimationState::ProjectileAttack) {\n        x7c8_ = 1;\n      } else {\n        GetBodyController()->GetCommandMgr().DeliverCmd(\n            CBCProjectileAttackCmd(pas::ESeverity::Two, mgr.GetPlayer().GetTranslation(), false));\n      }\n    } else if (x7c8_ == 1) {\n      if (GetBodyController()->GetCurrentStateId() != pas::EAnimationState::ProjectileAttack) {\n        x7c8_ = 2;\n      }\n    }\n    if (x630_ <= 0.f) {\n      x634_ = 0.f;\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x824_activeLasers[0] = false;\n    x824_activeLasers[1] = false;\n    UpdateLaser(mgr, 0, false);\n    UpdateLaser(mgr, 1, false);\n    SetVisorFlareEnabled(mgr, false);\n    x5d0_ = x5f4_;\n    x835_24_ = false;\n  }\n}\n\nvoid CDrone::Active(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x330_stateMachineState.SetDelay(x5f0_);\n    GetBodyController()->SetLocomotionType(pas::ELocomotionType::Relaxed);\n  } else if (msg == EStateMsg::Deactivate) {\n    x5d0_ = x5f8_;\n  }\n}\n\nvoid CDrone::Flee(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x7c8_ = 0;\n    x832_b = 0;\n    if (mgr.RayStaticIntersection(GetTranslation(), -GetTransform().frontVector(), 4.f,\n                                  CMaterialFilter::MakeInclude({EMaterialTypes::Solid}))\n            .IsValid()) {\n      x832_b = mgr.GetActiveRandom()->Float() >= 0.5f ? 1 : 2;\n    }\n  } else if (msg == EStateMsg::Update) {\n    if (x7c8_ == 0) {\n      if (GetBodyController()->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::Step) {\n        x7c8_ = 1;\n      } else if (x832_b == 0) {\n        GetBodyController()->GetCommandMgr().DeliverCmd(\n            CBCStepCmd(pas::EStepDirection::Backward, pas::EStepType::BreakDodge));\n      } else if (x832_b == 1) {\n        GetBodyController()->GetCommandMgr().DeliverCmd(CBCStepCmd(pas::EStepDirection::Left, pas::EStepType::Normal));\n      } else if (x832_b == 2) {\n        GetBodyController()->GetCommandMgr().DeliverCmd(CBCStepCmd(pas::EStepDirection::Right, pas::EStepType::Normal));\n      }\n    } else if (x7c8_ == 1 &&\n               GetBodyController()->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::Step) {\n      x7c8_ = 2;\n    }\n    GetBodyController()->GetCommandMgr().DeliverTargetVector(\n        (mgr.GetPlayer().GetTranslation() - GetTranslation()).normalized());\n  }\n}\n\nvoid CDrone::ProjectileAttack(CStateManager& mgr, EStateMsg msg, float dt) {\n  // TODO: Finish\n}\n\nvoid CDrone::TelegraphAttack(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x7c8_ = 0;\n  } else if (msg == EStateMsg::Update) {\n    if (x7c8_ == 1 && x450_bodyController->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::Taunt) {\n      x7c8_ = 2;\n    } else if (x7c8_ == 0) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::Taunt) {\n        x7c8_ = 1;\n      } else {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCTauntCmd(pas::ETauntType::One));\n      }\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    SendScriptMsgs(EScriptObjectState::Zero, mgr, EScriptObjectMessage::None);\n  }\n}\n\nvoid CDrone::Dodge(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x7c8_ = 0;\n    x630_ = 0.5f;\n    x634_ = 1.f;\n    if (x3fc_flavor == EFlavorType::One)\n      x834_30_visible = true;\n  } else if (msg == EStateMsg::Update) {\n    if (x7c8_ == 0) {\n      GetBodyController()->GetCommandMgr().DeliverCmd(CBodyStateCmd(EBodyStateCmd::NextState));\n      if (x58c_prevDodgeDir == pas::EStepDirection::Down) {\n        GetBodyController()->GetCommandMgr().DeliverCmd(CBCStepCmd(pas::EStepDirection::Left, pas::EStepType::Dodge));\n        x58c_prevDodgeDir = pas::EStepDirection::Left;\n      } else if (x58c_prevDodgeDir == pas::EStepDirection::Up) {\n        GetBodyController()->GetCommandMgr().DeliverCmd(CBCStepCmd(pas::EStepDirection::Down, pas::EStepType::Dodge));\n        x58c_prevDodgeDir = pas::EStepDirection::Down;\n      } else if (x58c_prevDodgeDir == pas::EStepDirection::Right) {\n        GetBodyController()->GetCommandMgr().DeliverCmd(CBCStepCmd(pas::EStepDirection::Up, pas::EStepType::Dodge));\n        x58c_prevDodgeDir = pas::EStepDirection::Up;\n      } else if (x58c_prevDodgeDir == pas::EStepDirection::Left) {\n        GetBodyController()->GetCommandMgr().DeliverCmd(CBCStepCmd(pas::EStepDirection::Right, pas::EStepType::Dodge));\n        x58c_prevDodgeDir = pas::EStepDirection::Right;\n      }\n      x7c8_ = 1;\n    } else if (x7c8_ == 1 &&\n               GetBodyController()->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::Step) {\n      x7c8_ = 2;\n    }\n    GetBodyController()->GetCommandMgr().DeliverTargetVector(\n        (mgr.GetPlayer().GetTranslation() + zeus::CVector3f{0.f, 0.f, 1.f}) - GetTranslation());\n  }\n}\n\nvoid CDrone::Retreat(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x7c8_ = 0;\n    if (x3fc_flavor == EFlavorType::One) {\n      x834_30_visible = true;\n    }\n    x330_stateMachineState.SetDelay(x65c_);\n  } else if (msg == EStateMsg::Update) {\n    if (x7c8_ == 0) {\n      if (GetBodyController()->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::Step) {\n        x7c8_ = 1;\n      } else {\n        GetBodyController()->GetCommandMgr().DeliverCmd(\n            CBCStepCmd(pas::EStepDirection::Backward, pas::EStepType::Normal));\n      }\n    } else if (x7c8_ == 1 &&\n               GetBodyController()->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::Step) {\n      x7c8_ = 2;\n    } else if (x7c8_ == 2) {\n      x7c8_ = 0;\n    }\n\n    GetBodyController()->GetCommandMgr().DeliverTargetVector(\n        (mgr.GetPlayer().GetTranslation() - GetTranslation()).normalized());\n    if (x630_ <= 0.f) {\n      x634_ = 0.333333; // 1/3\n    }\n  }\n}\n\nvoid CDrone::Cover(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x67c_ = zeus::skZero3f;\n    x670_ = GetTranslation();\n    for (int i = 0; i < 4; ++i) {\n      float dVar11 = (x64c_ - x648_) * mgr.GetActiveRandom()->Float() + x648_;\n      int v = mgr.GetActiveRandom()->Next();\n      float angle = 0.f;\n      if (((v >> 3) & 1) == 0) {\n        const float angleMin = 270.f - x654_;\n        const float angleMax = 270.f + x650_;\n        angle = zeus::degToRad((angleMax - angleMin) * x648_ + angleMin);\n      } else {\n        const float angleMin = 90.f - x654_;\n        const float angleMax = 90.f + x650_;\n        angle = zeus::degToRad((angleMax - angleMin) * x648_ + angleMin);\n      }\n      zeus::CQuaternion quat;\n      quat.rotateZ(angle);\n      const zeus::CVector3f end =\n          GetTranslation() +\n          quat.transform((dVar11 * (mgr.GetPlayer().GetAimPosition(mgr, 0.f) - GetTranslation()).normalized()));\n      if (mgr.RayCollideWorld(GetTranslation(), end, CMaterialFilter::MakeInclude({EMaterialTypes::Solid}), this)) {\n        x670_ = end;\n        x67c_ = end - GetTranslation();\n        if (x67c_.canBeNormalized())\n          x67c_.normalize();\n      }\n    }\n  } else if (msg == EStateMsg::Update) {\n    ApplyImpulseWR(GetMoveToORImpulseWR(GetTransform().transposeRotate(dt * (x658_ * x67c_)), dt), zeus::CAxisAngle());\n    x450_bodyController->GetCommandMgr().DeliverCmd(\n        CBCLocomotionCmd(FLT_EPSILON * GetTransform().basis[1],\n                         (mgr.GetPlayer().GetAimPosition(mgr, 0.f) - GetTranslation()).normalized(), 1.f));\n  } else if (msg == EStateMsg::Deactivate) {\n    x644_ = (x640_ - x63c_) * mgr.GetActiveRandom()->Float() + x63c_;\n  }\n}\n\nvoid CDrone::SpecialAttack(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    if (x3fc_flavor == EFlavorType::One) {\n      x834_30_visible = true;\n    }\n    x330_stateMachineState.SetDelay(x660_);\n    GetBodyController()->SetLocomotionType(pas::ELocomotionType::Internal10);\n  } else if (msg == EStateMsg::Update) {\n    GetBodyController()->GetCommandMgr().DeliverCmd(\n        CBCLocomotionCmd(GetTransform().frontVector(), zeus::skZero3f, 1.f));\n    zeus::CVector3f local_74 =\n        0.5f * (mgr.GetPlayer().GetAimPosition(mgr, 0.f) + mgr.GetPlayer().GetTranslation()) - GetTranslation();\n    if (((x668_elevation < x664_ && local_74.z() > 0.f) || (x668_elevation > x664_)) && local_74.canBeNormalized()) {\n      ApplyImpulseWR(GetMoveToORImpulseWR(GetTransform().transposeRotate(dt * (x5e4_ * local_74.normalized())), dt),\n                     zeus::CAxisAngle());\n    }\n  }\n}\n\nvoid CDrone::PathFindEx(CStateManager& mgr, EStateMsg msg, float dt) {\n  CPatterned::PathFind(mgr, msg, dt);\n  if (msg == EStateMsg::Activate) {\n    auto searchOff = mgr.GetPlayer().GetTranslation() + zeus::CVector3f{0.f, 0.f, x664_};\n    CPathFindSearch::EResult res = GetSearchPath()->Search(GetTranslation(), searchOff);\n    if (res != CPathFindSearch::EResult::Success &&\n        (res == CPathFindSearch::EResult::NoDestPoint || res == CPathFindSearch::EResult::NoPath)) {\n      if (GetSearchPath()->FindClosestReachablePoint(GetTranslation(), searchOff) ==\n          CPathFindSearch::EResult::Success) {\n        GetSearchPath()->Search(GetTranslation(), searchOff);\n        SetDestPos(searchOff);\n      }\n    }\n  }\n}\n\nbool CDrone::Leash(CStateManager& mgr, float arg) {\n  return (mgr.GetPlayer().GetTranslation() - GetTranslation()).magSquared() < x3c8_leashRadius * x3c8_leashRadius;\n}\n\nbool CDrone::InRange(CStateManager& mgr, float arg) {\n  float mag = (mgr.GetPlayer().GetTranslation() - GetTranslation()).magSquared();\n  return mag > x2fc_minAttackRange * x2fc_minAttackRange && mag < x300_maxAttackRange * x300_maxAttackRange;\n}\n\nbool CDrone::SpotPlayer(CStateManager& mgr, float arg) {\n  if ((mgr.GetPlayer().GetTranslation() - GetTranslation()).magSquared() > x3bc_detectionRange * x3bc_detectionRange)\n    return false;\n\n  if (!LineOfSight(mgr, arg))\n    return false;\n\n  return (GetTransform().frontVector() + x5cc_ * GetTransform().rightVector())\n             .normalized()\n             .dot((mgr.GetPlayer().GetAimPosition(mgr, 0.f) - GetTranslation()).normalized()) > 0.5f;\n}\n\nbool CDrone::AnimOver(CStateManager& mgr, float arg) { return x7c8_ == 2; }\n\nbool CDrone::AttackOver(CStateManager& mgr, float arg) { return x834_31_attackOver; }\n\nbool CDrone::ShouldAttack(CStateManager& mgr, float arg) {\n  if (x5d0_ > 0.f)\n    return false;\n  if (TCastToPtr<CTeamAiMgr> teamMgr = mgr.ObjectById(x688_teamMgr)) {\n    if (teamMgr->HasTeamAiRole(GetUniqueId()))\n      return teamMgr->AddRangedAttacker(GetUniqueId());\n  }\n  return true;\n}\n\nbool CDrone::ShouldFire(CStateManager& mgr, float arg) {\n  if (mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed || x624_ > 0.f) {\n    return false;\n  }\n  const zeus::CVector3f playerAimPos = mgr.GetPlayer().GetAimPosition(mgr, 0.f);\n  constexpr auto matFilter =\n      CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid, EMaterialTypes::Character},\n                                          {EMaterialTypes::Player, EMaterialTypes::ProjectilePassthrough});\n  bool result = mgr.RayCollideWorld(GetLctrTransform(\"R_GUN_TOP_LCTR\"sv).origin, playerAimPos, matFilter, this);\n  if (!result) {\n    return false;\n  }\n  return mgr.RayCollideWorld(GetLctrTransform(\"L_GUN_TOP_LCTR\"sv).origin, playerAimPos, matFilter, this);\n}\n\nbool CDrone::HearShot(CStateManager& mgr, float arg) {\n  EntityList nearList;\n  BuildNearList(EMaterialTypes::Projectile, EMaterialTypes::Player, nearList, 10.f, mgr);\n  return std::any_of(nearList.begin(), nearList.end(), [&mgr](TUniqueId uid) {\n    if (TCastToConstPtr<CWeapon> wp = mgr.GetObjectById(uid))\n      return wp->GetType() != EWeaponType::AI;\n    return false;\n  });\n}\n\nbool CDrone::CoverCheck(CStateManager& mgr, float arg) {\n  if (!zeus::close_enough(x67c_, zeus::skZero3f)) {\n    const zeus::CVector3f diff = x670_ - GetTranslation();\n    return x67c_.dot(diff) < 0.0f || diff.magSquared() < 0.25f;\n  }\n\n  return true;\n}\nbool CDrone::LineOfSight(CStateManager& mgr, float arg) {\n  return mgr.RayCollideWorld(\n      GetTranslation(), mgr.GetPlayer().GetAimPosition(mgr, 0.f),\n      CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid, EMaterialTypes::Character},\n                                          {EMaterialTypes::Player, EMaterialTypes::ProjectilePassthrough}),\n      this);\n}\n\nbool CDrone::ShouldMove(CStateManager& mgr, float arg) { return x644_ <= 0.f; }\n\nbool CDrone::CodeTrigger(CStateManager& mgr, float arg) { return x834_29_codeTrigger; }\n\nvoid CDrone::Burn(float duration, float damage) {\n  // Intentionally empty\n}\n\nCPathFindSearch* CDrone::GetSearchPath() { return &x6b0_pathFind; }\n\nvoid CDrone::BuildNearList(EMaterialTypes includeMat, EMaterialTypes excludeMat, EntityList& listOut, float radius,\n                           CStateManager& mgr) {\n  const zeus::CVector3f pos = GetTranslation();\n  mgr.BuildNearList(listOut, zeus::CAABox(pos - radius, pos + radius),\n                    CMaterialFilter::MakeIncludeExclude({includeMat}, {excludeMat}), nullptr);\n}\n\nvoid CDrone::SetLightEnabled(CStateManager& mgr, bool activate) {\n  mgr.SendScriptMsgAlways(x578_lightId, GetUniqueId(),\n                          activate ? EScriptObjectMessage::Activate : EScriptObjectMessage::Deactivate);\n}\n\nvoid CDrone::SetVisorFlareEnabled(CStateManager& mgr, bool activate) {\n  if (!IsAlive()) {\n    return;\n  }\n  CScriptVisorFlare* flare = TCastToPtr<CScriptVisorFlare>{mgr.ObjectById(x57a_visorFlareId)};\n  if (flare == nullptr && activate) {\n    x57a_visorFlareId = mgr.AllocateUniqueId();\n    flare = new CScriptVisorFlare(x57a_visorFlareId, \"DroneVisorFlare\"sv,\n                                  CEntityInfo{GetAreaIdAlways(), CEntity::NullConnectionList}, activate,\n                                  GetLctrTransform(\"Beacon_LCTR\"sv).origin, CVisorFlare::EBlendMode::Additive, true,\n                                  0.1f, 1.f, 2.f, 0, 0, x57c_flares);\n    mgr.AddObject(flare);\n  }\n  mgr.SendScriptMsg(flare, GetUniqueId(), activate ? EScriptObjectMessage::Activate : EScriptObjectMessage::Deactivate);\n}\n\nvoid CDrone::UpdateVisorFlare(CStateManager& mgr) {\n  TCastToPtr<CScriptVisorFlare> flare = mgr.ObjectById(x57a_visorFlareId);\n  SetVisorFlareEnabled(\n      mgr, (mgr.GetPlayer().GetTranslation() - GetTranslation()).normalized().dot(GetTransform().frontVector()) > 0.f);\n  if (flare) {\n    const auto beaconXf = GetLctrTransform(\"Beacon_LCTR\"sv);\n    flare->SetTranslation(beaconXf.origin + (0.1f * beaconXf.frontVector()));\n  }\n}\n\nvoid CDrone::UpdateTouchBounds(float radius) {\n  const zeus::CTransform xf = GetLctrTransform(\"Skeleton_Root\"sv);\n  const zeus::CVector3f diff = xf.origin - GetTranslation();\n  x690_colSphere.SetSphereCenter(diff);\n  x690_colSphere.SetSphereRadius(radius);\n  SetBoundingBox(zeus::CAABox{diff - radius, diff + radius});\n  x6b0_pathFind.SetCharacterRadius(0.25f + radius);\n}\n\nbool CDrone::HitShield(const zeus::CVector3f& dir) const {\n  if (x3fc_flavor == EFlavorType::One && !zeus::close_enough(x5dc_, 0.f)) {\n    return GetLctrTransform(\"Shield_LCTR\"sv).frontVector().dot(dir.normalized()) > 0.85f;\n  }\n\n  return false;\n}\n\nvoid CDrone::AddToTeam(CStateManager& mgr) const {\n  if (x688_teamMgr == kInvalidUniqueId) {\n    return;\n  }\n\n  if (TCastToPtr<CTeamAiMgr> teamMgr = mgr.ObjectById(x688_teamMgr)) {\n    teamMgr->AssignTeamAiRole(*this, CTeamAiRole::ETeamAiRole::Ranged, CTeamAiRole::ETeamAiRole::Melee,\n                              CTeamAiRole::ETeamAiRole::Invalid);\n  }\n}\n\nvoid CDrone::RemoveFromTeam(CStateManager& mgr) const {\n  if (TCastToPtr<CTeamAiMgr> teamMgr = mgr.ObjectById(x688_teamMgr)) {\n    if (teamMgr->IsPartOfTeam(GetUniqueId())) {\n      teamMgr->RemoveTeamAiRole(GetUniqueId());\n    }\n  }\n}\n\nvoid CDrone::UpdateLaser(CStateManager& mgr, u32 laserIdx, bool active) {\n  if (active && x7d8_laserIds[laserIdx] == kInvalidUniqueId) {\n    x7d8_laserIds[laserIdx] = mgr.AllocateUniqueId();\n    mgr.AddObject(new CDroneLaser(x7d8_laserIds[laserIdx], GetAreaIdAlways(), GetTransform(), x568_laserParticlesId));\n  }\n  if (CEntity* ent = mgr.ObjectById(x7d8_laserIds[laserIdx])) {\n    mgr.SendScriptMsg(ent, GetUniqueId(), active ? EScriptObjectMessage::Activate : EScriptObjectMessage::Deactivate);\n  }\n}\n\nvoid CDrone::FireProjectile(CStateManager& mgr, const zeus::CTransform& xf, const TToken<CWeaponDescription>& weapon) {\n  // TODO implement\n}\n\nvoid CDrone::StrafeFromCompanions(CStateManager& mgr) {\n  if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::Step)\n    return;\n  EntityList nearList;\n  BuildNearList(EMaterialTypes::Character, EMaterialTypes::Player, nearList, x61c_, mgr);\n  if (nearList.empty())\n    return;\n\n  float minDist = FLT_MAX;\n  zeus::CVector3f nearestPos;\n  for (TUniqueId uid : nearList) {\n    if (const CActor* act = static_cast<const CActor*>(mgr.GetObjectById(uid))) {\n      const float dist = (act->GetTranslation() - GetTranslation()).magSquared();\n      if (uid != GetUniqueId() && dist < minDist) {\n        minDist = dist;\n        nearestPos = act->GetTranslation();\n      }\n    }\n  }\n\n  if (nearestPos.isZero() || minDist > x61c_ * x61c_)\n    return;\n\n  const auto off = nearestPos - GetTranslation();\n  const float rightOff = GetTransform().rightVector().dot(off);\n  if (rightOff < -0.2f) {\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBCStepCmd(pas::EStepDirection::Right, pas::EStepType::Normal));\n  } else if (rightOff > 0.2f) {\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBCStepCmd(pas::EStepDirection::Left, pas::EStepType::Normal));\n  }\n}\n\nvoid CDrone::UpdateScanner(CStateManager& mgr, float dt) {\n  constexpr float deg360 = zeus::degToRad(360.f);\n  x5d4_ = 1.2f * dt + x5d4_;\n  if (x5d4_ > deg360) {\n    x5d4_ -= deg360;\n  }\n  if (x5d4_ < 0.f) {\n    x5d4_ = 0.f;\n  }\n  if (x5d8_ > deg360) {\n    x5d8_ -= deg360;\n  }\n  if (x5d8_ < 0.f) {\n    x5d8_ = 0.f;\n  }\n  float angle = zeus::clamp(0.f, 0.5f * (1.f + std::sin(x5d4_)), 1.f);\n  if (std::fpclassify(angle) != FP_SUBNORMAL)\n    x5d8_ += 0.03f * std::pow(angle, 5.f);\n  zeus::CVector3f vec =\n      GetTransform().rotate(zeus::CVector3f(0.5f * std::cos(x5d8_), 1.f, 0.5f * std::sin(2.05f * x5d8_)).normalized());\n  TUniqueId id;\n  EntityList nearList;\n  nearList.push_back(mgr.GetPlayer().GetUniqueId());\n  auto res = mgr.RayWorldIntersection(\n      id, GetLctrTransform(\"Beacon_LCTR\"sv).origin + (0.2f * vec), vec, 10000.f,\n      CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {EMaterialTypes::ProjectilePassthrough}), nearList);\n  if (res.IsValid() && x578_lightId != kInvalidUniqueId) {\n    if (TCastToPtr<CGameLight> light = mgr.ObjectById(x578_lightId)) {\n      light->SetTranslation(res.GetPoint());\n      x7ac_lightPos = res.GetPoint();\n    }\n  }\n}\n\nvoid CDrone::UpdateLasers(CStateManager& mgr, float dt) {\n  constexpr auto matFilter =\n      CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {EMaterialTypes::ProjectilePassthrough});\n  const auto beaconXf = GetLctrTransform(\"Beacon_LCTR\"sv);\n  for (size_t i = 0; i < x818_lasersTime.size(); ++i) {\n    if (x818_lasersTime[i] >= 1.f || !x824_activeLasers[i]) {\n      continue;\n    }\n    x818_lasersTime[i] += dt;\n    const auto vec =\n        (x7e0_lasersStart[i] * (1.f - x818_lasersTime[i]) + (x7fc_lasersEnd[i] * x818_lasersTime[i]) - beaconXf.origin)\n            .normalized();\n    auto box = zeus::skInvertedBox;\n    box.accumulateBounds(GetTranslation() + 1000.f * vec);\n    box.accumulateBounds(GetTranslation());\n    EntityList nearList;\n    mgr.BuildNearList(nearList, box, matFilter, nullptr);\n    TUniqueId id;\n    const auto result = mgr.RayWorldIntersection(id, beaconXf.origin + 2.f * vec, vec, 10000.f, matFilter, nearList);\n    if (result.IsInvalid()) {\n      continue;\n    }\n    if (x7d8_laserIds[i] != kInvalidUniqueId) {\n      if (auto* laser = static_cast<CDroneLaser*>(mgr.ObjectById(x7d8_laserIds[i]))) {\n        laser->SetTransform(beaconXf);\n        laser->sub_80167754(mgr, result.GetPoint(), result.GetPlane().normal());\n      }\n    }\n    if (TCastToPtr<CPlayer> player = mgr.ObjectById(id)) {\n      if (x420_curDamageRemTime <= 0.f) {\n        mgr.ApplyDamage(GetUniqueId(), player->GetUniqueId(), GetUniqueId(), GetContactDamage(),\n                        CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), zeus::skZero3f);\n        x420_curDamageRemTime = x424_damageWaitTime;\n        mgr.GetPlayerState()->GetStaticInterference().AddSource(GetUniqueId(), 0.3f, 1.f);\n        CSfxManager::AddEmitter(x7cc_laserSfx, result.GetPoint(), zeus::skZero3f, true, false, 127, GetAreaIdAlways());\n      }\n    }\n    if (id != GetUniqueId() && TCastToPtr<CPatterned>{mgr.ObjectById(id)}) {\n      x834_31_attackOver = true;\n      float rem = GetModelData()->GetAnimationData()->GetAnimTimeRemaining(\"Whole Body\"sv);\n      UpdateAnimation(rem, mgr, true);\n    }\n  }\n}\n\nvoid CDrone::sub_801633a8(CStateManager& mgr) {\n  // TODO implement\n}\n\nvoid CDrone::sub_8015f25c(float dt, CStateManager& mgr) {\n  // TODO implement\n}\n\nvoid CDrone::sub_8015f158(float dt) {\n  // TODO implement\n}\n\nvoid CDrone::sub_80165984(CStateManager& mgr, const zeus::CTransform& xf) {\n  /*constexpr*/ float sin60 = std::sqrt(3.f) / 2.f;\n  const auto playerAimPos = mgr.GetPlayer().GetAimPosition(mgr, 0.f);\n  const auto distNorm = (playerAimPos - xf.origin).normalized();\n  if (distNorm.dot(xf.frontVector()) <= sin60) {\n    sub_801656d4(xf, mgr);\n  } else {\n    zeus::CVector3f vec;\n    if (mgr.GetActiveRandom()->Float() > 0.2f) {\n      const auto lookAt = zeus::lookAt(xf.origin, playerAimPos);\n      vec = zeus::CQuaternion::fromAxisAngle(lookAt.frontVector(), mgr.GetActiveRandom()->Range(0.f, M_PIF))\n                .transform(4.f * lookAt.rightVector());\n    }\n    sub_801656d4(zeus::lookAt(xf.origin, playerAimPos + vec), mgr);\n  }\n}\n\nvoid CDrone::sub_801656d4(const zeus::CTransform& xf, CStateManager& mgr) {\n  constexpr auto matFilter =\n      CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {EMaterialTypes::ProjectilePassthrough});\n  EntityList nearList;\n  mgr.BuildNearList(nearList, xf.origin, xf.frontVector(), 100000.f, matFilter, this);\n  TUniqueId id;\n  const auto result = mgr.RayWorldIntersection(id, xf.origin, xf.frontVector(), 100000.f, matFilter, nearList);\n  if (result.IsInvalid()) {\n    return;\n  }\n  if (id == mgr.GetPlayer().GetUniqueId()) {\n    mgr.ApplyDamage(GetUniqueId(), id, GetUniqueId(), x5ac_,\n                    CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), zeus::skZero3f);\n  }\n  mgr.sub_80044098(*x56c_.GetObj(), result, id, x5ac_.GetWeaponMode(), 1, xe6_27_thermalVisorFlags);\n}\n\nvoid CDrone::Dead(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x460_knockBackController.SetAutoResetImpulse(false);\n    if (x834_24_waveHit) {\n      SetMomentumWR({0.f, 0.f, -GetWeight()});\n    } else {\n      Stop();\n      SetVelocityWR(zeus::skZero3f);\n      SetMomentumWR(zeus::skZero3f);\n    }\n    x401_26_disableMove = true;\n    x5c8_ = 0.f;\n    SetVisorFlareEnabled(mgr, false);\n    x7c8_ = 0;\n  } else if (msg == EStateMsg::Update && x7c0_ == 0) {\n    if (x834_24_waveHit) {\n      GetBodyController()->GetCommandMgr().DeliverCmd(CBCHurledCmd());\n      x7c8_ = 1;\n    } else {\n      GetBodyController()->GetCommandMgr().DeliverCmd(CBCKnockDownCmd(zeus::skZero3f, pas::ESeverity::Zero));\n      x7c8_ = 1;\n      Stop();\n    }\n  }\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CDrone.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Collision/CCollidableSphere.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n#include \"Runtime/World/CPathFindSearch.hpp\"\n#include \"Runtime/World/CVisorFlare.hpp\"\n\nnamespace metaforce {\nclass CWeaponDescription;\nnamespace MP1 {\nclass CDrone : public CPatterned {\n  CAssetId x568_laserParticlesId;\n  TLockedToken<CCollisionResponseData> x56c_;\n  TUniqueId x578_lightId = kInvalidUniqueId;\n  TUniqueId x57a_visorFlareId = kInvalidUniqueId;\n  std::vector<CVisorFlare::CFlareDef> x57c_flares;\n  pas::EStepDirection x58c_prevDodgeDir = pas::EStepDirection::Left;\n  CDamageInfo x590_;\n  CDamageInfo x5ac_;\n  float x5c8_ = 0.f;\n  float x5cc_ = 0.f;\n  float x5d0_ = 0.f;\n  float x5d4_ = 0.f;\n  float x5d8_ = 0.f;\n  float x5dc_ = 0.f;\n  float x5e0_ = 0.f;\n  float x5e4_;\n  mutable float x5e8_shieldTime = 0.f;\n  float x5ec_turnSpeed;\n  float x5f0_;\n  float x5f4_;\n  float x5f8_;\n  float x5fc_;\n  float x600_;\n  float x604_ = 0.f;\n  float x608_;\n  float x60c_;\n  float x610_;\n  float x614_;\n  float x618_;\n  float x61c_;\n  float x620_;\n  float x624_ = 0.f;\n  float x628_ = 0.f;\n  float x62c_ = 0.f;\n  float x630_ = 0.f;\n  float x634_ = 0.f;\n  float x638_ = 0.f;\n  float x63c_;\n  float x640_;\n  float x644_ = 0.f;\n  float x648_;\n  float x64c_;\n  float x650_;\n  float x654_;\n  float x658_;\n  float x65c_;\n  float x660_;\n  float x664_;\n  float x668_elevation = 0.f;\n  float x66c_ = 0.f;\n  zeus::CVector3f x670_;\n  zeus::CVector3f x67c_;\n  TUniqueId x688_teamMgr = kInvalidUniqueId;\n  CCollidableSphere x690_colSphere;\n  CPathFindSearch x6b0_pathFind;\n  zeus::CAxisAngle x794_;\n  zeus::CVector3f x7a0_;\n  zeus::CVector3f x7ac_lightPos;\n  float x7b8_ = 0.f;\n  float x7bc_ = 0.f;\n  float x7c0_ = 0.f;\n  float x7c4_ = 0.f;\n  s32 x7c8_ = 0;\n  u16 x7cc_laserSfx;\n  CSfxHandle x7d0_;\n  rstl::reserved_vector<TUniqueId, 2> x7d8_laserIds = {{kInvalidUniqueId, kInvalidUniqueId}};\n  rstl::reserved_vector<zeus::CVector3f, 2> x7e0_lasersStart = {{zeus::skZero3f, zeus::skZero3f}};\n  rstl::reserved_vector<zeus::CVector3f, 2> x7fc_lasersEnd = {{zeus::skZero3f, zeus::skZero3f}};\n  rstl::reserved_vector<float, 2> x818_lasersTime = {{0.f, 0.f}};\n  rstl::reserved_vector<bool, 2> x824_activeLasers = {{false, false}};\n  std::unique_ptr<CModelData> x82c_shieldModel;\n  u8 x832_a : 3 = 0;\n  u8 x832_b : 3 = 0;\n  bool x834_24_waveHit : 1 = false;\n  bool x834_25_ : 1 = false;\n  bool x834_26_ : 1 = false;\n  bool x834_27_ : 1 = false;\n  bool x834_28_ : 1 = false;\n  bool x834_29_codeTrigger : 1 = false;\n  bool x834_30_visible : 1 = false;\n  bool x834_31_attackOver : 1 = false;\n  bool x835_24_ : 1 = false;\n  bool x835_25_ : 1;\n  bool x835_26_ : 1 = false;\n\n  void UpdateTouchBounds(float radius);\n  bool HitShield(const zeus::CVector3f& dir) const;\n  void AddToTeam(CStateManager& mgr) const;\n  void RemoveFromTeam(CStateManager& mgr) const;\n  void UpdateLaser(CStateManager& mgr, u32 laserIdx, bool active);\n  void FireProjectile(CStateManager& mgr, const zeus::CTransform& xf, const TToken<CWeaponDescription>& weapon);\n  void StrafeFromCompanions(CStateManager& mgr);\n  void UpdateScanner(CStateManager& mgr, float dt);\n\n  void UpdateLasers(CStateManager& mgr, float dt);\n  void sub_801633a8(CStateManager& mgr);\n  void sub_8015f25c(float dt, CStateManager& mgr);\n  void sub_8015f158(float dt);\n  void sub_80165984(CStateManager& mgr, const zeus::CTransform& xf);\n  void sub_801656d4(const zeus::CTransform& xf, CStateManager& mgr);\n\npublic:\n  DEFINE_PATTERNED(Drone);\n  CDrone(TUniqueId uid, std::string_view name, EFlavorType flavor, const CEntityInfo& info, const zeus::CTransform& xf,\n         float f1, CModelData&& mData, const CPatternedInfo& pInfo, const CActorParameters& actParms,\n         EMovementType movement, EColliderType colliderType, EBodyType bodyType, const CDamageInfo& dInfo1,\n         CAssetId aId1, const CDamageInfo& dInfo2, CAssetId aId2, std::vector<CVisorFlare::CFlareDef> flares, float f2,\n         float f3, float f4, float f5, float f6, float f7, float f8, float f9, float f10, float f11, float f12,\n         float f13, float f14, float f15, float f16, float f17, float f18, float f19, float f20, CAssetId crscId,\n         float f21, float f22, float f23, float f24, s32 w3, bool b1);\n\n  void Accept(IVisitor& visitor) override;\n  void Think(float dt, CStateManager& mgr) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) override;\n  void AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) override;\n  void PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) override;\n  void Render(CStateManager& mgr) override;\n  bool CanRenderUnsorted(const CStateManager& mgr) const override;\n  const CDamageVulnerability* GetDamageVulnerability() const override { return CAi::GetDamageVulnerability(); }\n  const CDamageVulnerability* GetDamageVulnerability(const zeus::CVector3f&, const zeus::CVector3f&,\n                                                     const CDamageInfo&) const override;\n  void Touch(CActor& act, CStateManager& mgr) override;\n  EWeaponCollisionResponseTypes GetCollisionResponseType(const zeus::CVector3f&, const zeus::CVector3f&,\n                                                         const CWeaponMode&, EProjectileAttrib) const override;\n  void DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) override;\n  const CCollisionPrimitive* GetCollisionPrimitive() const override;\n  void Death(CStateManager& mgr, const zeus::CVector3f& direction, EScriptObjectState state) override;\n  void KnockBack(const zeus::CVector3f&, CStateManager&, const CDamageInfo& info, EKnockBackType type, bool inDeferred,\n                 float magnitude) override;\n  void Patrol(CStateManager&, EStateMsg msg, float dt) override;\n  void PathFind(CStateManager&, EStateMsg msg, float dt) override;\n  void TargetPlayer(CStateManager&, EStateMsg msg, float dt) override;\n  void TargetCover(CStateManager&, EStateMsg msg, float dt) override;\n  void Deactivate(CStateManager&, EStateMsg msg, float dt) override;\n  void Attack(CStateManager&, EStateMsg msg, float dt) override;\n  void Active(CStateManager&, EStateMsg msg, float dt) override;\n  void Flee(CStateManager&, EStateMsg msg, float dt) override;\n  void ProjectileAttack(CStateManager&, EStateMsg msg, float dt) override;\n  void TelegraphAttack(CStateManager&, EStateMsg msg, float dt) override;\n  void Dodge(CStateManager&, EStateMsg msg, float dt) override;\n  void Retreat(CStateManager&, EStateMsg msg, float dt) override;\n  void Cover(CStateManager&, EStateMsg msg, float dt) override;\n  void SpecialAttack(CStateManager&, EStateMsg msg, float dt) override;\n  void PathFindEx(CStateManager&, EStateMsg msg, float dt) override;\n  bool Leash(CStateManager&, float arg) override;\n  bool InRange(CStateManager&, float arg) override;\n  bool SpotPlayer(CStateManager&, float arg) override;\n  bool AnimOver(CStateManager&, float arg) override;\n  bool AttackOver(CStateManager& mgr, float arg) override;\n  bool ShouldAttack(CStateManager&, float arg) override;\n  bool ShouldFire(CStateManager& mgr, float arg) override;\n  bool HearShot(CStateManager&, float arg) override;\n  bool CoverCheck(CStateManager&, float arg) override;\n  bool LineOfSight(CStateManager&, float arg) override;\n  bool ShouldMove(CStateManager&, float arg) override;\n  bool CodeTrigger(CStateManager&, float arg) override;\n  void Burn(float duration, float damage) override;\n  void Dead(CStateManager& mgr, EStateMsg msg, float arg) override;\n  CPathFindSearch* GetSearchPath() override;\n  virtual void BuildNearList(EMaterialTypes includeMat, EMaterialTypes excludeMat, EntityList& listOut, float radius,\n                             CStateManager& mgr);\n  virtual void SetLightEnabled(CStateManager& mgr, bool activate);\n  virtual void SetVisorFlareEnabled(CStateManager& mgr, bool activate);\n  virtual void UpdateVisorFlare(CStateManager& mgr);\n  virtual int sub_8015f150() { return 3; }\n};\n} // namespace MP1\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/MP1/World/CDroneLaser.cpp",
    "content": "#include \"Runtime/MP1/World/CDroneLaser.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/World/CGameLight.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce::MP1 {\nCDroneLaser::CDroneLaser(TUniqueId uid, TAreaId aId, const zeus::CTransform& xf, CAssetId particle)\n: CActor(uid, true, \"DroneLaser\"sv, CEntityInfo(aId, CEntity::NullConnectionList), xf, CModelData::CModelDataNull(),\n         CMaterialList(EMaterialTypes::NoStepLogic), CActorParameters::None().HotInThermal(true), kInvalidUniqueId)\n, xf8_beamDesc(g_SimplePool->GetObj({SBIG('PART'), particle}))\n, x104_beamParticle(std::make_unique<CElementGen>(xf8_beamDesc, CElementGen::EModelOrientationType::Normal,\n                                                  CElementGen::EOptionalSystemFlags::One)) {}\n\nvoid CDroneLaser::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CDroneLaser::Think(float dt, CStateManager& mgr) { x104_beamParticle->Update(dt); }\n\nvoid CDroneLaser::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) {\n  CActor::AcceptScriptMsg(msg, sender, mgr);\n\n  if (msg == EScriptObjectMessage::Deactivate) {\n    SetScannerLightActive(mgr, false);\n    x104_beamParticle->SetParticleEmission(false);\n  } else if (msg == EScriptObjectMessage::Activate) {\n    SetScannerLightActive(mgr, true);\n    x104_beamParticle->SetParticleEmission(true);\n  } else if (msg == EScriptObjectMessage::Deleted) {\n    if (xf4_scannerLight != kInvalidUniqueId) {\n      mgr.FreeScriptObject(xf4_scannerLight);\n      xf4_scannerLight = kInvalidUniqueId;\n    }\n  } else if (msg == EScriptObjectMessage::Registered) {\n    xf4_scannerLight = mgr.AllocateUniqueId();\n    mgr.AddObject(new CGameLight(xf4_scannerLight, GetAreaIdAlways(), GetActive(), \"LaserScanner\"sv, zeus::CTransform(),\n                                 GetUniqueId(), CLight::BuildPoint(zeus::skZero3f, zeus::skRed), 0, 0, 0.f));\n  }\n}\n\nvoid CDroneLaser::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) {\n  g_Renderer->AddParticleGen(*x104_beamParticle);\n  EnsureRendered(mgr);\n}\n\nvoid CDroneLaser::Render(CStateManager& mgr) {\n  g_Renderer->SetDepthReadWrite(true, true);\n  RenderBeam(4, 0.01f, zeus::CColor(1.f, .9f, .9f, 1.f), true);\n  g_Renderer->SetDepthReadWrite(true, false);\n  RenderBeam(5, 0.06f, zeus::CColor(.4f, .0f, .0f, .5f), true);\n  RenderBeam(7, 0.06f, zeus::CColor(.4f, .2f, .2f, .1f), true);\n}\n\nvoid CDroneLaser::CalculateRenderBounds() {\n  zeus::CAABox box = zeus::skInvertedBox;\n  const zeus::CVector3f diff = xe8_ - GetTranslation();\n  const float mag1 = 0.2f * diff.magnitude();\n  box.accumulateBounds(diff);\n  box.accumulateBounds(xe8_ + (mag1 * GetTransform().basis[2]));\n  box.accumulateBounds(xe8_ - (mag1 * GetTransform().basis[2]));\n  x9c_renderBounds = box;\n}\n\nvoid CDroneLaser::SetScannerLightActive(CStateManager& mgr, bool activate) {\n  mgr.SendScriptMsgAlways(xf4_scannerLight, GetUniqueId(),\n                          activate ? EScriptObjectMessage::Activate : EScriptObjectMessage::Deactivate);\n}\n\nvoid CDroneLaser::RenderBeam(u32 w, float f, const zeus::CColor& col, bool) const {\n  // TODO\n}\n\nvoid CDroneLaser::sub_80167754(CStateManager& mgr, const zeus::CVector3f& pos, const zeus::CVector3f& look) {\n  xe8_ = pos;\n  if (xf4_scannerLight != kInvalidUniqueId) {\n    if (TCastToPtr<CGameLight> light = mgr.ObjectById(xf4_scannerLight)) {\n      light->SetTranslation(pos - 0.5f * (pos - GetTranslation()));\n    }\n  }\n  x104_beamParticle->SetOrientation(zeus::lookAt(zeus::skZero3f, look));\n  x104_beamParticle->SetTranslation(pos);\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CDroneLaser.hpp",
    "content": "#pragma once\n#include \"Runtime/World/CActor.hpp\"\n\nnamespace metaforce {\nclass CElementGen;\nnamespace MP1 {\nclass CDroneLaser : public CActor {\n  zeus::CVector3f xe8_ = zeus::skZero3f;\n  TUniqueId xf4_scannerLight = kInvalidUniqueId;\n  TLockedToken<CGenDescription> xf8_beamDesc;\n  std::unique_ptr<CElementGen> x104_beamParticle;\n\n  void SetScannerLightActive(CStateManager& mgr, bool activate);\n  void RenderBeam(u32 w, float f, const zeus::CColor& col, bool) const;\n\npublic:\n  DEFINE_ENTITY\n  CDroneLaser(TUniqueId uid, TAreaId aId, const zeus::CTransform& xf, CAssetId particle);\n  void Accept(IVisitor& visitor) override;\n  void Think(float dt, CStateManager& mgr) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) override;\n  void AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) override;\n  void Render(CStateManager& mgr) override;\n  void CalculateRenderBounds() override;\n\n  void sub_80167754(CStateManager& mgr, const zeus::CVector3f& pos, const zeus::CVector3f& look);\n};\n} // namespace MP1\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/MP1/World/CElitePirate.cpp",
    "content": "#include \"Runtime/MP1/World/CElitePirate.hpp\"\n\n#include <algorithm>\n#include <array>\n\n#include \"Runtime/Camera/CFirstPersonCamera.hpp\"\n#include \"Runtime/Collision/CCollisionActor.hpp\"\n#include \"Runtime/Collision/CCollisionActorManager.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/MP1/World/CGrenadeLauncher.hpp\"\n#include \"Runtime/Weapon/CGameProjectile.hpp\"\n#include \"Runtime/World/CExplosion.hpp\"\n#include \"Runtime/World/CPatternedInfo.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n#include \"Runtime/World/ScriptLoader.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce::MP1 {\nnamespace {\nconstexpr std::array<SJointInfo, 3> skLeftArmJointList{{\n    {\"L_shoulder\", \"L_elbow\", 1.f, 1.5f},\n    {\"L_wrist\", \"L_elbow\", 0.9f, 1.3f},\n    {\"L_knee\", \"L_ankle\", 0.9f, 1.3f},\n}};\n\nconstexpr std::array<SJointInfo, 3> skRightArmJointList{{\n    {\"R_shoulder\", \"R_elbow\", 1.f, 1.5f},\n    {\"R_wrist\", \"R_elbow\", 0.9f, 1.3f},\n    {\"R_knee\", \"R_ankle\", 0.9f, 1.3f},\n}};\n\nconstexpr std::array<SSphereJointInfo, 7> skSphereJointList{{\n    {\"Head_1\", 1.2f},\n    {\"L_Palm_LCTR\", 1.5f},\n    {\"R_Palm_LCTR\", 1.5f},\n    {\"Spine_1\", 1.5f},\n    {\"Collar\", 1.2f},\n    {\"L_Ball\", 0.8f},\n    {\"R_Ball\", 0.8f},\n}};\n\n// The following used to be member functions, but are made internal as\n// they alter no internal state.\n\n// Used to be a member function with a pointer and size in GM8Ev0\nbool IsArmClawCollider(std::string_view name, std::string_view locator, const std::array<SJointInfo, 3>& info) {\n  if (name == locator) {\n    return true;\n  }\n  return std::any_of(info.cbegin(), info.cend(), [&name](const auto& entry) { return entry.from == name; });\n}\n\nbool IsArmClawCollider(TUniqueId uid, const rstl::reserved_vector<TUniqueId, 7>& vec) {\n  return std::find(vec.cbegin(), vec.cend(), uid) != vec.cend();\n}\n} // Anonymous namespace\n\nCElitePirateData::CElitePirateData(CInputStream& in, u32 propCount)\n: x0_tauntInterval(in.ReadFloat())\n, x4_tauntVariance(in.ReadFloat())\n, x8_(in.ReadFloat())\n, xc_(in.ReadFloat())\n, x10_attackChance(in.ReadFloat())\n, x14_shotAtTime(in.ReadFloat())\n, x18_shotAtTimeVariance(in.ReadFloat())\n, x1c_projectileAttractionRadius(in.ReadFloat())\n, x20_energyAbsorbParticleDescId(in)\n, x24_energyAbsorbSfxId(CSfxManager::TranslateSFXID(in.ReadLong()))\n, x28_launcherActParams(ScriptLoader::LoadActorParameters(in))\n, x90_launcherAnimParams(ScriptLoader::LoadAnimationParameters(in))\n, x9c_launcherParticleGenDescId(in)\n, xa0_launcherSfxId(CSfxManager::TranslateSFXID(in.ReadLong()))\n, xa4_grenadeModelId(in)\n, xa8_grenadeDamageInfo(in)\n, xc4_launcherHp(in.ReadFloat())\n, xc8_grenadeElementGenDescId1(in)\n, xcc_grenadeElementGenDescId2(in)\n, xd0_grenadeElementGenDescId3(in)\n, xd4_grenadeElementGenDescId4(in)\n, xd8_grenadeVelocityInfo(in)\n, xe0_grenadeTrajectoryInfo(in)\n, xf0_grenadeNumBounces(in.ReadLong())\n, xf4_grenadeBounceSfxId(CSfxManager::TranslateSFXID(in.ReadLong()))\n, xf6_grenadeExplodeSfxId(CSfxManager::TranslateSFXID(in.ReadLong()))\n, xf8_shockwaveParticleDescId(in)\n, xfc_shockwaveDamageInfo(in)\n, x118_shockwaveWeaponDescId(in)\n, x11c_shockwaveElectrocuteSfxId(CSfxManager::TranslateSFXID(in.ReadLong()))\n, x11e_canCallForBackup(in.ReadBool())\n, x11f_fastWhenAttractingEnergy(propCount < 42 ? true : in.ReadBool()) {}\n\nCElitePirate::CElitePirate(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                           CModelData&& mData, const CPatternedInfo& pInfo, const CActorParameters& actParms,\n                           CElitePirateData data)\n: CPatterned(ECharacter::ElitePirate, uid, name, EFlavorType::Zero, info, xf, std::move(mData), pInfo,\n             EMovementType::Ground, EColliderType::One, EBodyType::BiPedal, actParms, EKnockBackVariant::Large)\n, x56c_vulnerability(pInfo.GetDamageVulnerability())\n, x5d8_data(std::move(data))\n, x6f8_boneTracking(*GetModelData()->GetAnimationData(), \"Head_1\", zeus::degToRad(80.f), zeus::degToRad(180.f),\n                    EBoneTrackingFlags::None)\n, x738_collisionAabb(GetBoundingBox(), GetMaterialList())\n, x7a0_initialSpeed(x3b4_speed)\n, x7d0_pathFindSearch(nullptr, 1, pInfo.GetPathfindingIndex(), 1.f, 1.f) {\n  if (x5d8_data.GetEnergyAbsorbParticleDescId().IsValid()) {\n    x760_energyAbsorbDesc = g_SimplePool->GetObj({SBIG('PART'), x5d8_data.GetEnergyAbsorbParticleDescId()});\n  }\n\n  x460_knockBackController.SetEnableFreeze(false);\n  x460_knockBackController.SetAutoResetImpulse(false);\n  x460_knockBackController.SetEnableBurn(false);\n  x460_knockBackController.SetEnableExplodeDeath(false);\n  x460_knockBackController.SetEnableLaggedBurnDeath(false);\n  SetupPathFindSearch();\n}\n\nvoid CElitePirate::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CElitePirate::Think(float dt, CStateManager& mgr) {\n  if (GetActive()) {\n    CPatterned::Think(dt, mgr);\n    x6f8_boneTracking.Update(dt);\n    if (HasWeakPointHead()) {\n      x730_collisionActorMgrHead->Update(dt, mgr, CCollisionActorManager::EUpdateOptions::ObjectSpace);\n    }\n    x5d4_collisionActorMgr->Update(dt, mgr, CCollisionActorManager::EUpdateOptions::ObjectSpace);\n    if (IsAttractingEnergy() && x5d8_data.IsFastWhenAttractingEnergy()) {\n      x3b4_speed = 2.f * x7a0_initialSpeed;\n    } else {\n      x3b4_speed = x7a0_initialSpeed;\n    }\n    UpdateTimers(dt);\n    UpdatePositionHistory();\n    UpdateActorTransform(mgr, x772_launcherId, \"grenadeLauncher_LCTR\"sv);\n    UpdateHealthInfo(mgr);\n    x328_31_energyAttractor = IsAttractingEnergy();\n  }\n}\n\nvoid CElitePirate::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  bool shouldPass = true;\n\n  switch (msg) {\n  case EScriptObjectMessage::Activate: {\n    if (HasWeakPointHead()) {\n      x730_collisionActorMgrHead->SetActive(mgr, true);\n    }\n    if (CEntity* ent = mgr.ObjectById(x772_launcherId)) {\n      ent->SetActive(true);\n    }\n    break;\n  }\n  case EScriptObjectMessage::Deactivate: {\n    if (HasWeakPointHead()) {\n      x730_collisionActorMgrHead->SetActive(mgr, false);\n    }\n    x5d4_collisionActorMgr->SetActive(mgr, false);\n    if (CEntity* ent = mgr.ObjectById(x772_launcherId)) {\n      ent->SetActive(false);\n    }\n    break;\n  }\n  case EScriptObjectMessage::Alert:\n    x988_28_alert = true;\n    break;\n  case EScriptObjectMessage::Touched: {\n    if (HealthInfo(mgr)->GetHP() <= 0.f) {\n      break;\n    }\n    const TCastToConstPtr<CCollisionActor> actor = mgr.ObjectById(uid);\n    if (!actor) {\n      if (uid == x772_launcherId && x772_launcherId != kInvalidUniqueId) {\n        SetShotAt(true, mgr);\n      }\n      break;\n    }\n    const TUniqueId touchedUid = actor->GetLastTouchedObject();\n    if (touchedUid != mgr.GetPlayer().GetUniqueId()) {\n      if (TCastToConstPtr<CGameProjectile>(mgr.ObjectById(touchedUid))) {\n        SetShotAt(true, mgr);\n      }\n      break;\n    }\n    if (!x988_24_damageOn) {\n      if (x420_curDamageRemTime <= 0.f) {\n        CDamageInfo info = GetContactDamage();\n        info.SetDamage(0.5f * info.GetDamage());\n        mgr.ApplyDamage(GetUniqueId(), mgr.GetPlayer().GetUniqueId(), GetUniqueId(), info,\n                        CMaterialFilter::MakeInclude({EMaterialTypes::Solid}), zeus::skZero3f);\n        x420_curDamageRemTime = x424_damageWaitTime;\n      }\n      break;\n    }\n    if ((!x988_25_attackingRightClaw || !IsArmClawCollider(uid, x774_collisionRJointIds)) &&\n        (!x988_26_attackingLeftClaw || !IsArmClawCollider(uid, x788_collisionLJointIds))) {\n      break;\n    }\n    mgr.ApplyDamage(GetUniqueId(), mgr.GetPlayer().GetUniqueId(), GetUniqueId(), GetContactDamage(),\n                    CMaterialFilter::MakeInclude({EMaterialTypes::Solid}), zeus::skZero3f);\n    x420_curDamageRemTime = x424_damageWaitTime;\n    x988_24_damageOn = false;\n    break;\n  }\n  case EScriptObjectMessage::Registered: {\n    x450_bodyController->Activate(mgr);\n    SetupCollisionManager(mgr);\n    x772_launcherId = mgr.AllocateUniqueId();\n    CreateGrenadeLauncher(mgr, x772_launcherId);\n    const auto& bodyStateInfo = x450_bodyController->GetBodyStateInfo();\n    if (bodyStateInfo.GetMaxSpeed() > 0.f) {\n      x7a4_steeringSpeed =\n          (0.99f * bodyStateInfo.GetLocomotionSpeed(pas::ELocomotionAnim::Walk)) / bodyStateInfo.GetMaxSpeed();\n    }\n    x450_bodyController->GetCommandMgr().SetSteeringBlendMode(ESteeringBlendMode::FullSpeed);\n    x450_bodyController->GetCommandMgr().SetSteeringSpeedRange(x7a4_steeringSpeed, x7a4_steeringSpeed);\n    break;\n  }\n  case EScriptObjectMessage::Deleted:\n    if (HasWeakPointHead()) {\n      x730_collisionActorMgrHead->Destroy(mgr);\n    }\n    x5d4_collisionActorMgr->Destroy(mgr);\n    mgr.FreeScriptObject(x772_launcherId);\n    break;\n  case EScriptObjectMessage::InitializedInArea:\n    x7d0_pathFindSearch.SetArea(mgr.GetWorld()->GetAreaAlways(GetAreaIdAlways())->GetPostConstructed()->x10bc_pathArea);\n    break;\n  case EScriptObjectMessage::Damage:\n    shouldPass = false;\n    if (const TCastToConstPtr<CCollisionActor> actor = mgr.ObjectById(uid)) {\n      if (const TCastToConstPtr<CGameProjectile> projectile = mgr.ObjectById(actor->GetLastTouchedObject())) {\n        if (uid == x770_collisionHeadId) {\n          x428_damageCooldownTimer = 0.33f;\n          const auto& damageInfo = projectile->GetDamageInfo();\n          KnockBack(projectile->GetTranslation() - projectile->GetPreviousPos(), mgr, damageInfo,\n                    EKnockBackType::Direct, false, damageInfo.GetKnockBackPower());\n          CPatterned::AcceptScriptMsg(msg, uid, mgr);\n        } else if (uid == x79c_energyAttractorId && x760_energyAbsorbDesc->IsLoaded()) {\n          CreateEnergyAbsorb(mgr, projectile->GetTransform());\n        }\n        SetShotAt(true, mgr);\n      }\n    } else if (uid == x772_launcherId && x772_launcherId != kInvalidUniqueId) {\n      x450_bodyController->GetCommandMgr().DeliverCmd(\n          CBCKnockBackCmd(GetTransform().frontVector(), pas::ESeverity::Eight));\n    } else {\n      ApplyDamageToHead(mgr, uid);\n    }\n    break;\n  case EScriptObjectMessage::InvulnDamage: {\n    SetShotAt(true, mgr);\n    if (!TCastToConstPtr<CCollisionActor>(mgr.ObjectById(uid))) {\n      ApplyDamageToHead(mgr, uid);\n    }\n    break;\n  }\n  default:\n    break;\n  }\n\n  if (shouldPass) {\n    CPatterned::AcceptScriptMsg(msg, uid, mgr);\n  }\n}\n\nvoid CElitePirate::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) {\n  CPatterned::PreRender(mgr, frustum);\n  auto* modelData = GetModelData();\n  x6f8_boneTracking.PreRender(mgr, *modelData->GetAnimationData(), GetTransform(), modelData->GetScale(),\n                              *x450_bodyController);\n  const auto numMaterialSets = modelData->GetNumMaterialSets();\n  xb4_drawFlags.x1_matSetIdx =\n      numMaterialSets - 1 < x7cc_activeMaterialSet ? numMaterialSets - 1 : x7cc_activeMaterialSet;\n}\n\nconst CDamageVulnerability* CElitePirate::GetDamageVulnerability() const {\n  return &CDamageVulnerability::PassThroughVulnerabilty();\n}\n\nconst CDamageVulnerability* CElitePirate::GetDamageVulnerability(const zeus::CVector3f& pos, const zeus::CVector3f& dir,\n                                                                 const CDamageInfo& dInfo) const {\n  return &CDamageVulnerability::PassThroughVulnerabilty();\n}\n\nzeus::CVector3f CElitePirate::GetOrbitPosition(const CStateManager& mgr) const {\n  if (x772_launcherId != kInvalidUniqueId &&\n      mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::Thermal) {\n    if (const auto* actor = static_cast<const CActor*>(mgr.GetObjectById(x772_launcherId))) {\n      return GetLockOnPosition(actor);\n    }\n  }\n  if (HasWeakPointHead()) {\n    if (const TCastToConstPtr<CCollisionActor> actor = mgr.GetObjectById(x770_collisionHeadId)) {\n      return actor->GetTranslation();\n    }\n  }\n  return GetLctrTransform(\"lockon_target_LCTR\").origin;\n}\n\nzeus::CVector3f CElitePirate::GetAimPosition(const CStateManager& mgr, float) const {\n  const std::shared_ptr<CPlayerState>& playerState = mgr.GetPlayerState();\n  if (x5d4_collisionActorMgr->GetActive() && playerState->IsFiringComboBeam() &&\n      playerState->GetCurrentBeam() == CPlayerState::EBeamId::Wave) {\n    if (const TCastToConstPtr<CCollisionActor> actor = mgr.GetObjectById(x79c_energyAttractorId)) {\n      return actor->GetTranslation();\n    }\n  }\n  return GetOrbitPosition(mgr);\n}\n\nvoid CElitePirate::DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) {\n  switch (type) {\n  case EUserEventType::Projectile:\n    if (x772_launcherId != kInvalidUniqueId) {\n      CEntity* launcher = mgr.ObjectById(x772_launcherId);\n      mgr.SendScriptMsg(launcher, GetUniqueId(), EScriptObjectMessage::Action);\n    }\n    return;\n  case EUserEventType::DamageOn:\n    x988_24_damageOn = true;\n    return;\n  case EUserEventType::DamageOff:\n    x988_24_damageOn = false;\n    return;\n  case EUserEventType::ScreenShake:\n    ShakeCamera(mgr);\n    return;\n  case EUserEventType::BeginAction: {\n    const zeus::CVector3f origin = GetTranslation();\n    const zeus::CVector3f front = GetTransform().frontVector();\n    const float dot = (GetLctrTransform(node.GetLocatorName()).origin - origin).dot(front);\n    const zeus::CTransform xf = zeus::CTransform::Translate({\n        origin.x() + dot * front.x(),\n        origin.y() + dot * front.y(),\n        origin.z(),\n    });\n    mgr.AddObject(new CShockWave(mgr.AllocateUniqueId(), \"Shock Wave\", {GetAreaIdAlways(), CEntity::NullConnectionList},\n                                 xf, GetUniqueId(), GetShockWaveData(), IsElitePirate() ? 2.f : 1.3f,\n                                 IsElitePirate() ? 0.4f : 0.5f));\n    return;\n  }\n  case EUserEventType::BecomeShootThrough:\n    if (HasWeakPointHead()) {\n      const u32 numCollisionActors = x730_collisionActorMgrHead->GetNumCollisionActors();\n      for (u32 i = 0; i < numCollisionActors; ++i) {\n        const auto& description = x730_collisionActorMgrHead->GetCollisionDescFromIndex(i);\n        if (TCastToPtr<CCollisionActor> actor = mgr.ObjectById(description.GetCollisionActorId())) {\n          actor->AddMaterial(EMaterialTypes::ProjectilePassthrough, mgr);\n        }\n      }\n    }\n    return;\n  default:\n    break;\n  }\n  CPatterned::DoUserAnimEvent(mgr, node, type, dt);\n}\n\nconst CCollisionPrimitive* CElitePirate::GetCollisionPrimitive() const { return &x738_collisionAabb; }\n\nvoid CElitePirate::KnockBack(const zeus::CVector3f& pos, CStateManager& mgr, const CDamageInfo& info,\n                             EKnockBackType type, bool inDeferred, float magnitude) {\n  if (!CanKnockBack(info)) {\n    return;\n  }\n  CPatterned::KnockBack(pos, mgr, info, type, inDeferred, magnitude);\n  if (info.GetWeaponMode().IsComboed() && info.GetWeaponMode().GetType() == EWeaponType::Ice) {\n    Freeze(mgr, zeus::skZero3f, GetTransform().transposeRotate(pos), 1.5f);\n  }\n}\n\nvoid CElitePirate::TakeDamage(const zeus::CVector3f& pos, float) {}\n\nvoid CElitePirate::Patrol(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n    x400_24_hitByPlayerProjectile = false;\n    x989_24_onPath = false;\n  }\n  CPatterned::Patrol(mgr, msg, dt);\n}\n\nvoid CElitePirate::PathFind(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x989_24_onPath = true;\n    x988_28_alert = false;\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n    x6f8_boneTracking.SetTarget(mgr.GetPlayer().GetUniqueId());\n    x6f8_boneTracking.SetActive(true);\n    UpdateDestPos(mgr);\n    CPatterned::PathFind(mgr, msg, dt);\n    x7bc_tauntTimer = x5d8_data.GetTauntVariance() * mgr.GetActiveRandom()->Float() + x5d8_data.GetTauntInterval();\n    if (TooClose(mgr, 0.f)) {\n      x450_bodyController->GetCommandMgr().ClearLocomotionCmds();\n    }\n  } else if (msg == EStateMsg::Update) {\n    if (x7bc_tauntTimer > 0.f) {\n      x7bc_tauntTimer -= dt;\n    }\n    if (!TooClose(mgr, 0.f) && !PathShagged(mgr, 0.f)) {\n      CPatterned::PathFind(mgr, msg, dt);\n    } else if (PathShagged(mgr, 0.f)) {\n      const auto move = x8c0_positionHistory.GetValue(GetTranslation(), GetTransform().frontVector());\n      if (move != zeus::skZero3f) {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(move, zeus::skZero3f, 1.f));\n      }\n    } else if (ShouldTurn(mgr, 0.f)) {\n      const zeus::CVector3f aim =\n          mgr.GetPlayer().GetAimPosition(mgr, 0.5f * GetModelData()->GetAnimationData()->GetSpeedScale());\n      const zeus::CVector3f face = aim - GetTranslation();\n      if (face.canBeNormalized()) {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(zeus::skZero3f, face.normalized(), 1.f));\n      }\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x6f8_boneTracking.SetActive(false);\n  }\n}\n\nvoid CElitePirate::TargetPatrol(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n    if (HasPatrolPath(mgr, 0.f)) {\n      CPatterned::Patrol(mgr, msg, dt);\n      UpdateDest(mgr);\n    } else {\n      SetDestPos(x3a0_latestLeashPosition);\n    }\n    x8b4_targetDestPos = x2e0_destPos;\n    if (GetSearchPath() != nullptr) {\n      CPatterned::PathFind(mgr, msg, dt);\n    }\n  } else if (msg == EStateMsg::Update) {\n    if (PathShagged(mgr, 0.f)) {\n      const zeus::CVector3f move = x45c_steeringBehaviors.Arrival(*this, x8b4_targetDestPos, 25.f);\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(move, zeus::skZero3f, 1.f));\n    } else {\n      CPatterned::PathFind(mgr, msg, dt);\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x988_28_alert = false;\n  }\n}\n\nvoid CElitePirate::Halt(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Lurk);\n    x989_24_onPath = false;\n    CMaterialFilter filter = GetMaterialFilter();\n    filter.ExcludeList().Add(\n        {EMaterialTypes::Wall, EMaterialTypes::Ceiling, EMaterialTypes::AIBlock, EMaterialTypes::Character});\n    SetMaterialFilter(filter);\n  } else if (msg == EStateMsg::Deactivate) {\n    CMaterialFilter filter = GetMaterialFilter();\n    filter.ExcludeList().Remove(\n        {EMaterialTypes::Wall, EMaterialTypes::Ceiling, EMaterialTypes::AIBlock, EMaterialTypes::Character});\n    SetMaterialFilter(filter);\n  }\n}\n\nvoid CElitePirate::Run(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x988_31_running = true;\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n    x450_bodyController->GetCommandMgr().SetSteeringSpeedRange(1.f, 1.f);\n    x6f8_boneTracking.SetTarget(mgr.GetPlayer().GetUniqueId());\n    x6f8_boneTracking.SetActive(true);\n    UpdateDestPos(mgr);\n    CPatterned::PathFind(mgr, msg, dt);\n  } else if (msg == EStateMsg::Update) {\n    if (PathShagged(mgr, 0.f)) {\n      const auto move = x8c0_positionHistory.GetValue(GetTranslation(), GetTransform().frontVector());\n      if (move != zeus::skZero3f) {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(move, zeus::skZero3f, 1.f));\n      } else if (ShouldTurn(mgr, 0.f)) {\n        const zeus::CVector3f aim =\n            mgr.GetPlayer().GetAimPosition(mgr, 0.5f * GetModelData()->GetAnimationData()->GetSpeedScale());\n        const auto face = aim - GetTranslation();\n        if (face.canBeNormalized()) {\n          x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(zeus::skZero3f, face.normalized(), 1.f));\n        }\n      }\n    } else {\n      CPatterned::PathFind(mgr, msg, dt);\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x988_31_running = false;\n    x6f8_boneTracking.SetActive(false);\n    x450_bodyController->GetCommandMgr().SetSteeringSpeedRange(x7a4_steeringSpeed, x7a4_steeringSpeed);\n  }\n}\n\nvoid CElitePirate::Generate(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x568_state = EState::One;\n  } else if (msg == EStateMsg::Update) {\n    if (x568_state == EState::Zero) {\n      if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::Generate) {\n        x568_state = EState::Two;\n      } else {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCGenerateCmd(pas::EGenerateType::Zero));\n      }\n    } else if (x568_state == EState::One) {\n      if (ShouldTurn(mgr, 0.f)) {\n        const auto face = mgr.GetPlayer().GetTranslation() - GetTranslation();\n        if (face.canBeNormalized()) {\n          x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(zeus::skZero3f, face.normalized(), 1.f));\n        }\n      } else {\n        x568_state = EState::Zero;\n      }\n    } else if (x568_state == EState::Two &&\n               x450_bodyController->GetCurrentStateId() != pas::EAnimationState::Generate) {\n      x568_state = EState::Over;\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    SetShotAt(false, mgr);\n    SetLaunchersActive(mgr, true);\n  }\n}\n\nvoid CElitePirate::Attack(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x568_state = EState::Zero;\n    ExtendTouchBounds(mgr, x774_collisionRJointIds, zeus::CVector3f(2.f));\n    if (x64_modelData->GetNumMaterialSets() > 1) {\n      x7cc_activeMaterialSet = 1;\n    }\n  } else if (msg == EStateMsg::Update) {\n    if (x568_state == EState::Zero) {\n      if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::MeleeAttack) {\n        x568_state = EState::One;\n        x988_25_attackingRightClaw = true;\n        x7c8_currAnimId = x450_bodyController->GetCurrentAnimId();\n      } else {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCMeleeAttackCmd(pas::ESeverity::One));\n      }\n    } else if (x568_state == EState::One) {\n      if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::MeleeAttack) {\n        if (x7c8_currAnimId == x450_bodyController->GetCurrentAnimId()) {\n          x450_bodyController->GetCommandMgr().DeliverTargetVector(mgr.GetPlayer().GetTranslation() - GetTranslation());\n          if (ShouldAttack(mgr, 0.f)) {\n            x450_bodyController->GetCommandMgr().DeliverCmd(CBCMeleeAttackCmd(pas::ESeverity::Two));\n          }\n        } else {\n          x568_state = EState::Two;\n          x988_25_attackingRightClaw = false;\n          x988_26_attackingLeftClaw = true;\n          ExtendTouchBounds(mgr, x774_collisionRJointIds, zeus::skZero3f);\n          ExtendTouchBounds(mgr, x788_collisionLJointIds, zeus::CVector3f(2.f));\n        }\n      } else {\n        x568_state = EState::Over;\n      }\n    } else if (x568_state == EState::Two) {\n      if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::MeleeAttack) {\n        x450_bodyController->GetCommandMgr().DeliverTargetVector(mgr.GetPlayer().GetTranslation() - GetTranslation());\n      } else {\n        x568_state = EState::Over;\n      }\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    CheckAttackChance(mgr);\n    x988_24_damageOn = false;\n    x988_26_attackingLeftClaw = false;\n    x988_25_attackingRightClaw = false;\n    x7c8_currAnimId = -1;\n    ExtendTouchBounds(mgr, x774_collisionRJointIds, zeus::skZero3f);\n    ExtendTouchBounds(mgr, x788_collisionLJointIds, zeus::skZero3f);\n    x7cc_activeMaterialSet = 0;\n  }\n}\n\nvoid CElitePirate::Taunt(CStateManager& mgr, EStateMsg msg, float dt) { CAi::Taunt(mgr, msg, dt); }\n\nvoid CElitePirate::ProjectileAttack(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x568_state = EState::Zero;\n  } else if (msg == EStateMsg::Update) {\n    const zeus::CVector3f& playerPos = mgr.GetPlayer().GetTranslation();\n    if (x568_state == EState::Zero) {\n      if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::ProjectileAttack) {\n        x568_state = EState::Two;\n      } else {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCProjectileAttackCmd(pas::ESeverity::One, playerPos, false));\n      }\n    } else if (x568_state == EState::Two) {\n      if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::ProjectileAttack) {\n        x450_bodyController->GetCommandMgr().DeliverTargetVector(playerPos - GetTranslation());\n      } else {\n        x568_state = EState::Over;\n      }\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    CheckAttackChance(mgr);\n  }\n}\n\nvoid CElitePirate::SpecialAttack(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x568_state = EState::Zero;\n  } else if (msg == EStateMsg::Update) {\n    if (x568_state == EState::Zero) {\n      if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::ProjectileAttack) {\n        x568_state = EState::Two;\n        x988_29_shockWaveAnim = true;\n      } else {\n        x450_bodyController->GetCommandMgr().DeliverCmd(\n            CBCProjectileAttackCmd(pas::ESeverity::Two, mgr.GetPlayer().GetTranslation(), false));\n      }\n    } else if (x568_state == EState::Two) {\n      if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::ProjectileAttack) {\n        x450_bodyController->GetCommandMgr().DeliverTargetVector(mgr.GetPlayer().GetTranslation() - GetTranslation());\n      } else {\n        x568_state = EState::Over;\n      }\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    CheckAttackChance(mgr);\n    x988_29_shockWaveAnim = false;\n  }\n}\n\nvoid CElitePirate::CallForBackup(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x568_state = EState::Zero;\n    x988_30_calledForBackup = true;\n    SetShotAt(false, mgr);\n  } else if (msg == EStateMsg::Update) {\n    if (x568_state == EState::Zero) {\n      if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::Generate) {\n        x568_state = EState::Two;\n      } else {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCGenerateCmd(pas::EGenerateType::Five, zeus::skZero3f));\n      }\n    } else if (x568_state == EState::Two) {\n      if (x450_bodyController->GetCurrentStateId() != pas::EAnimationState::Generate) {\n        x568_state = EState::Over;\n      }\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    SendScriptMsgs(EScriptObjectState::Zero, mgr, EScriptObjectMessage::None);\n  }\n}\n\nvoid CElitePirate::Cover(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Crouch);\n    if (HasWeakPointHead()) {\n      if (TCastToPtr<CCollisionActor> actor = mgr.ObjectById(x770_collisionHeadId)) {\n        actor->SetDamageVulnerability(CDamageVulnerability::ImmuneVulnerabilty());\n      }\n    }\n    x5d4_collisionActorMgr->SetActive(mgr, true);\n    x6f8_boneTracking.SetTarget(mgr.GetPlayer().GetUniqueId());\n    x6f8_boneTracking.SetActive(true);\n    UpdateDestPos(mgr);\n    CPatterned::PathFind(mgr, msg, dt);\n    if (TooClose(mgr, 0.f)) {\n      x450_bodyController->GetCommandMgr().ClearLocomotionCmds();\n    }\n  } else if (msg == EStateMsg::Update) {\n    if (x988_27_shotAt) {\n      x7c0_shotAtTimer -= dt;\n      if (x7c0_shotAtTimer <= 0.f) {\n        x988_27_shotAt = false;\n      }\n    }\n    x7a8_pathShaggedTime = PathShagged(mgr, 0.f) ? x7a8_pathShaggedTime + dt : 0.f;\n    if (!TooClose(mgr, 0.f) && !PathShagged(mgr, 0.f)) {\n      CPatterned::PathFind(mgr, msg, dt);\n    } else if (PathShagged(mgr, 0.f)) {\n      const auto move = x8c0_positionHistory.GetValue(GetTranslation(), GetTransform().frontVector());\n      if (move != zeus::skZero3f) {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(move, zeus::skZero3f, 1.f));\n      }\n    } else if (ShouldTurn(mgr, 0.f)) {\n      const zeus::CVector3f aim =\n          mgr.GetPlayer().GetAimPosition(mgr, 0.5f * GetModelData()->GetAnimationData()->GetSpeedScale());\n      const zeus::CVector3f face = aim - GetTranslation();\n      if (face.canBeNormalized()) {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(zeus::skZero3f, face.normalized(), 1.f));\n      }\n    }\n    AttractProjectiles(mgr);\n    UpdateAbsorbBodyState(mgr, dt);\n  } else if (msg == EStateMsg::Deactivate) {\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n    x6f8_boneTracking.SetActive(false);\n    if (HasWeakPointHead()) {\n      if (TCastToPtr<CCollisionActor> actor = mgr.ObjectById(x770_collisionHeadId)) {\n        actor->SetDamageVulnerability(x56c_vulnerability);\n      }\n    }\n    x5d4_collisionActorMgr->SetActive(mgr, false);\n  }\n}\n\nbool CElitePirate::TooClose(CStateManager& mgr, float) {\n  return x2fc_minAttackRange * x2fc_minAttackRange > (GetTranslation() - mgr.GetPlayer().GetTranslation()).magSquared();\n}\n\nbool CElitePirate::InDetectionRange(CStateManager& mgr, float arg) {\n  return x988_28_alert ? true : CPatterned::InDetectionRange(mgr, arg);\n}\n\nbool CElitePirate::SpotPlayer(CStateManager& mgr, float arg) {\n  return x988_28_alert ? true : CPatterned::SpotPlayer(mgr, arg);\n}\n\nbool CElitePirate::AnimOver(CStateManager& mgr, float) { return x568_state == EState::Over; }\n\nbool CElitePirate::ShouldAttack(CStateManager& mgr, float) {\n  if ((mgr.GetPlayer().GetTranslation() - GetTranslation()).magSquared() > x2fc_minAttackRange * x2fc_minAttackRange) {\n    return false;\n  }\n  return !ShouldTurn(mgr, 0.f);\n}\n\nbool CElitePirate::InPosition(CStateManager& mgr, float) {\n  return (x8b4_targetDestPos - GetTranslation()).magSquared() < 25.f;\n}\n\nbool CElitePirate::ShouldTurn(CStateManager& mgr, float) {\n  return zeus::CVector2f::getAngleDiff((mgr.GetPlayer().GetTranslation() - GetTranslation()).toVec2f(),\n                                       GetTransform().frontVector().toVec2f()) > zeus::degToRad(15.f);\n}\n\nbool CElitePirate::AggressionCheck(CStateManager& mgr, float arg) {\n  if (x772_launcherId == kInvalidUniqueId && !PathShagged(mgr, arg)) {\n    if (x988_31_running) {\n      return true;\n    }\n    return 4.f * x300_maxAttackRange * x300_maxAttackRange <\n           (mgr.GetPlayer().GetTranslation() - GetTranslation()).magSquared();\n  }\n  return false;\n}\n\nbool CElitePirate::ShouldTaunt(CStateManager& mgr, float) { return x7bc_tauntTimer <= 0.f; }\n\nbool CElitePirate::ShouldFire(CStateManager& mgr, float) { return ShouldFireFromLauncher(mgr, x772_launcherId); }\n\nbool CElitePirate::ShotAt(CStateManager& mgr, float) { return x988_27_shotAt; }\n\nbool CElitePirate::ShouldSpecialAttack(CStateManager& mgr, float) {\n  if (x7b8_attackTimer <= 0.f && GetAreaIdAlways() == mgr.GetPlayer().GetAreaIdAlways()) {\n    const zeus::CVector3f dist = mgr.GetPlayer().GetAimPosition(mgr, 0.f) - GetTranslation();\n    const float magSquared = dist.magSquared();\n    if (x2fc_minAttackRange * x2fc_minAttackRange <= magSquared &&\n        magSquared <= x300_maxAttackRange * x300_maxAttackRange) {\n      return std::abs(dist.z()) < 3.f;\n    }\n  }\n  return false;\n}\n\nbool CElitePirate::ShouldCallForBackup(CStateManager& mgr, float) {\n  return ShouldCallForBackupFromLauncher(mgr, x772_launcherId);\n}\n\nCPathFindSearch* CElitePirate::GetSearchPath() { return &x7d0_pathFindSearch; }\n\nvoid CElitePirate::SetupHealthInfo(CStateManager& mgr) {\n  const CHealthInfo* const health = HealthInfo(mgr);\n  x7b4_hp = health->GetHP();\n  if (HasWeakPointHead()) {\n    if (TCastToPtr<CCollisionActor> actor = mgr.ObjectById(x770_collisionHeadId)) {\n      auto* actHealth = actor->HealthInfo(mgr);\n      actHealth->SetHP(health->GetHP());\n      actHealth->SetKnockbackResistance(health->GetKnockbackResistance());\n      actor->SetDamageVulnerability(x56c_vulnerability);\n    }\n  }\n  SetupLauncherHealthInfo(mgr, x772_launcherId);\n}\n\nvoid CElitePirate::SetLaunchersActive(CStateManager& mgr, bool val) { SetLauncherActive(mgr, val, x772_launcherId); }\n\nvoid CElitePirate::SetupPathFindSearch() {\n  const float scale = 1.5f * GetModelData()->GetScale().y();\n  const float fVar1 = IsElitePirate() ? 5.f : 1.f;\n  const zeus::CAABox box{{-scale, -scale, 0.f}, {scale, scale, fVar1 * scale}};\n  SetBoundingBox(box);\n  x738_collisionAabb.SetBox(box);\n  x7d0_pathFindSearch.SetCharacterRadius(scale);\n  x7d0_pathFindSearch.SetCharacterHeight(3.f * scale);\n}\n\nvoid CElitePirate::SetShotAt(bool val, CStateManager& mgr) {\n  if (!IsElitePirate() || x7b4_hp <= 0.f || !val) {\n    x988_27_shotAt = val;\n  } else if (HealthInfo(mgr)->GetHP() / x7b4_hp <= x7b0_) {\n    x7b0_ -= 0.2f;\n    x988_27_shotAt = true;\n  }\n  if (x988_27_shotAt) {\n    x7c0_shotAtTimer = mgr.GetActiveRandom()->Float() * x5d8_data.GetShotAtTimeVariance() + x5d8_data.GetShotAtTime();\n  } else {\n    x7c0_shotAtTimer = 0.f;\n  }\n}\n\nvoid CElitePirate::AddCollisionList(const SJointInfo* joints, size_t count,\n                                    std::vector<CJointCollisionDescription>& outJoints) const {\n  const CAnimData* animData = GetModelData()->GetAnimationData();\n  for (size_t i = 0; i < count; ++i) {\n    const auto& joint = joints[i];\n    const CSegId from = animData->GetLocatorSegId(joint.from);\n    const CSegId to = animData->GetLocatorSegId(joint.to);\n    if (to.IsInvalid() || from.IsInvalid()) {\n      continue;\n    }\n    outJoints.emplace_back(CJointCollisionDescription::SphereSubdivideCollision(\n        to, from, joint.radius, joint.separation, CJointCollisionDescription::EOrientationType::One, joint.from, 10.f));\n  }\n}\n\nvoid CElitePirate::AddSphereCollisionList(const SSphereJointInfo* joints, size_t count,\n                                          std::vector<CJointCollisionDescription>& outJoints) const {\n  const CAnimData* animData = GetModelData()->GetAnimationData();\n  for (size_t i = 0; i < count; ++i) {\n    const auto& joint = joints[i];\n    const CSegId seg = animData->GetLocatorSegId(joint.name);\n    if (seg.IsInvalid()) {\n      continue;\n    }\n    outJoints.emplace_back(CJointCollisionDescription::SphereCollision(seg, joint.radius, joint.name, 10.f));\n  }\n}\n\nvoid CElitePirate::SetupCollisionManager(CStateManager& mgr) {\n  constexpr size_t jointInfoCount = skLeftArmJointList.size() + skRightArmJointList.size() + skSphereJointList.size();\n  std::vector<CJointCollisionDescription> joints;\n  joints.reserve(jointInfoCount);\n  AddCollisionList(skLeftArmJointList.data(), skLeftArmJointList.size(), joints);\n  AddCollisionList(skRightArmJointList.data(), skLeftArmJointList.size(), joints);\n  AddSphereCollisionList(skSphereJointList.data(), skSphereJointList.size(), joints);\n  if (HasWeakPointHead()) {\n    x730_collisionActorMgrHead =\n        std::make_unique<CCollisionActorManager>(mgr, GetUniqueId(), GetAreaIdAlways(), joints, true);\n    x730_collisionActorMgrHead->SetActive(mgr, GetActive());\n  }\n  x774_collisionRJointIds.clear();\n  x788_collisionLJointIds.clear();\n\n  const CAnimData* animData = GetModelData()->GetAnimationData();\n  constexpr zeus::CVector3f bounds{4.f, 4.f, 2.f};\n  joints.emplace_back(CJointCollisionDescription::OBBCollision(animData->GetLocatorSegId(\"L_Palm_LCTR\"sv), bounds,\n                                                               zeus::skZero3f, \"Shield\"sv, 10.f));\n  x5d4_collisionActorMgr =\n      std::make_unique<CCollisionActorManager>(mgr, GetUniqueId(), GetAreaIdAlways(), joints, false);\n\n  SetupCollisionActorInfo(mgr);\n  SetupHealthInfo(mgr);\n\n  SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(\n      {EMaterialTypes::Solid},\n      {EMaterialTypes::CollisionActor, EMaterialTypes::AIPassthrough, EMaterialTypes::Player}));\n  AddMaterial(EMaterialTypes::ProjectilePassthrough, mgr);\n}\n\nvoid CElitePirate::SetupCollisionActorInfo(CStateManager& mgr) {\n  if (HasWeakPointHead()) {\n    for (size_t i = 0; i < x730_collisionActorMgrHead->GetNumCollisionActors(); ++i) {\n      const auto& colDesc = x730_collisionActorMgrHead->GetCollisionDescFromIndex(i);\n      const TUniqueId uid = colDesc.GetCollisionActorId();\n      if (TCastToPtr<CCollisionActor> act = mgr.ObjectById(uid)) {\n        if (colDesc.GetName() == \"Head_1\"sv) {\n          x770_collisionHeadId = uid;\n        } else if (IsArmClawCollider(colDesc.GetName(), \"R_Palm_LCTR\"sv, skRightArmJointList)) {\n          x774_collisionRJointIds.push_back(uid);\n        } else if (IsArmClawCollider(colDesc.GetName(), \"L_Palm_LCTR\"sv, skLeftArmJointList)) {\n          x788_collisionLJointIds.push_back(uid);\n        }\n        if (uid != x770_collisionHeadId) {\n          act->SetDamageVulnerability(CDamageVulnerability::ReflectVulnerabilty());\n        }\n      }\n    }\n    x730_collisionActorMgrHead->AddMaterial(\n        mgr, {EMaterialTypes::AIJoint, EMaterialTypes::CameraPassthrough, EMaterialTypes::Immovable});\n  }\n\n  const CJointCollisionDescription& description = x5d4_collisionActorMgr->GetCollisionDescFromIndex(0);\n  x79c_energyAttractorId = description.GetCollisionActorId();\n  if (TCastToPtr<CCollisionActor> act = mgr.ObjectById(x79c_energyAttractorId)) {\n    act->SetWeaponCollisionResponseType(EWeaponCollisionResponseTypes::None);\n  }\n  x5d4_collisionActorMgr->AddMaterial(mgr, {EMaterialTypes::AIJoint, EMaterialTypes::CameraPassthrough});\n}\n\nvoid CElitePirate::CreateGrenadeLauncher(CStateManager& mgr, TUniqueId uid) {\n  const CAnimationParameters& params = x5d8_data.GetLauncherAnimParams();\n  if (!params.GetACSFile().IsValid()) {\n    return;\n  }\n  CModelData mData(CAnimRes(params.GetACSFile(), params.GetCharacter(), GetModelData()->GetScale(),\n                            params.GetInitialAnimation(), true));\n  const zeus::CAABox bounds = mData.GetBounds(GetTransform().getRotation());\n  mgr.AddObject(\n      new CGrenadeLauncher(uid, \"Grenade Launcher\", {GetAreaIdAlways(), CEntity::NullConnectionList}, GetTransform(),\n                           std::move(mData), bounds, CHealthInfo(x5d8_data.GetLauncherHP(), 10.f), x56c_vulnerability,\n                           x5d8_data.GetLauncherActParams(), GetUniqueId(), x5d8_data.GetGrenadeLauncherData(), 0.f));\n}\n\nvoid CElitePirate::ApplyDamageToHead(CStateManager& mgr, TUniqueId uid) {\n  if (!HasWeakPointHead()) {\n    return;\n  }\n  if (const TCastToConstPtr<CWeapon> weapon = mgr.ObjectById(uid)) {\n    CDamageInfo damageInfo = weapon->GetDamageInfo();\n    damageInfo.SetRadius(0.f);\n    mgr.ApplyDamage(uid, x770_collisionHeadId, weapon->GetOwnerId(), damageInfo,\n                    CMaterialFilter::MakeInclude({EMaterialTypes::Solid}), zeus::skZero3f);\n  }\n}\n\nvoid CElitePirate::CreateEnergyAbsorb(CStateManager& mgr, const zeus::CTransform& xf) {\n  if (x7ac_energyAbsorbCooldown > 0.f) {\n    return;\n  }\n  mgr.AddObject(new CExplosion(*x760_energyAbsorbDesc, mgr.AllocateUniqueId(), true,\n                               {GetAreaIdAlways(), CEntity::NullConnectionList}, \"Absorb energy Fx\", xf, 0,\n                               GetModelData()->GetScale(), zeus::skWhite));\n  CSfxManager::AddEmitter(x5d8_data.GetEnergyAbsorbSfxId(), GetTranslation(), zeus::skUp, false, false, 0x7f,\n                          GetAreaIdAlways());\n  x7ac_energyAbsorbCooldown = 0.25f;\n}\n\nvoid CElitePirate::SetupLauncherHealthInfo(CStateManager& mgr, TUniqueId uid) {\n  const CHealthInfo* const health = HealthInfo(mgr);\n  if (uid == kInvalidUniqueId) {\n    return;\n  }\n\n  if (const TCastToPtr<CCollisionActor> actor = mgr.ObjectById(uid)) {\n    auto* actHealth = actor->HealthInfo(mgr);\n    actHealth->SetHP(x5d8_data.GetLauncherHP());\n    actHealth->SetKnockbackResistance(health->GetKnockbackResistance());\n    actor->SetDamageVulnerability(x56c_vulnerability);\n  }\n}\n\nvoid CElitePirate::SetLauncherActive(CStateManager& mgr, bool val, TUniqueId uid) {\n  if (uid == kInvalidUniqueId) {\n    return;\n  }\n  if (auto* entity = mgr.ObjectById(uid)) {\n    mgr.SendScriptMsg(entity, GetUniqueId(), val ? EScriptObjectMessage::Start : EScriptObjectMessage::Stop);\n  }\n}\n\nzeus::CVector3f CElitePirate::GetLockOnPosition(const CActor* actor) const {\n  const zeus::CTransform targetTransform = actor->GetLocatorTransform(\"lockon_target_LCTR\"sv);\n  return actor->GetTranslation() + actor->GetTransform().rotate(targetTransform.origin);\n}\n\nbool CElitePirate::CanKnockBack(const CDamageInfo& info) const {\n  return !x400_25_alive || info.GetWeaponMode().IsComboed() || info.GetWeaponMode().GetType() != EWeaponType::Plasma;\n}\n\nvoid CElitePirate::UpdateDestPos(CStateManager& mgr) {\n  x8b4_targetDestPos = GetTranslation();\n  const zeus::CVector3f playerPos = mgr.GetPlayer().GetTranslation();\n  const zeus::CVector3f dist = GetTranslation() - playerPos;\n  if (dist.canBeNormalized() && dist.magSquared() > x2fc_minAttackRange * x2fc_minAttackRange) {\n    x2e0_destPos = playerPos + (x2fc_minAttackRange * dist.normalized());\n    x8b4_targetDestPos = x2e0_destPos;\n  }\n}\n\nvoid CElitePirate::CheckAttackChance(CStateManager& mgr) {\n  if (mgr.GetActiveRandom()->Float() > x5d8_data.GetAttackChance()) {\n    x7b8_attackTimer = x308_attackTimeVariation * mgr.GetActiveRandom()->Float() + x304_averageAttackTime;\n  }\n}\n\nvoid CElitePirate::AttractProjectiles(CStateManager& mgr) {\n  if (!IsAlive()) {\n    return;\n  }\n  const TCastToConstPtr<CCollisionActor> actor = mgr.GetObjectById(x79c_energyAttractorId);\n  if (!actor) {\n    return;\n  }\n\n  float radius = x5d8_data.GetProjectileAttractionRadius();\n  const zeus::CVector3f actorPos = actor->GetTranslation();\n  const zeus::CVector3f pos = GetTranslation();\n  EntityList projNearList;\n  const zeus::CAABox aabb{pos - radius, pos + radius};\n  mgr.BuildNearList(projNearList, aabb, CMaterialFilter::MakeInclude({EMaterialTypes::Projectile}), nullptr);\n  if (projNearList.empty()) {\n    return;\n  }\n\n  EntityList charNearList;\n  mgr.BuildNearList(charNearList, aabb, CMaterialFilter::MakeInclude({EMaterialTypes::Character}), nullptr);\n  for (const auto& projId : projNearList) {\n    TCastToPtr<CGameProjectile> projectile = mgr.ObjectById(projId);\n    if (!projectile || projectile->GetType() == EWeaponType::Missile ||\n        projectile->GetOwnerId() != mgr.GetPlayer().GetUniqueId() ||\n        projectile->GetAreaIdAlways() != GetAreaIdAlways()) {\n      continue;\n    }\n\n    const zeus::CVector3f projectilePos = projectile->GetTranslation();\n    const zeus::CVector3f actorProjDist = actorPos - projectilePos;\n    if (GetTransform().frontVector().dot(actorProjDist) < 0.f) {\n      const zeus::CVector3f projectileDir = projectilePos - projectile->GetPreviousPos();\n      if (projectileDir.canBeNormalized() && IsClosestEnergyAttractor(mgr, charNearList, projectilePos)) {\n        const float actorProjMag = actorProjDist.magnitude();\n        const zeus::CVector3f b = projectilePos + ((0.5f * actorProjMag) * projectileDir.normalized());\n        const zeus::CVector3f c = actorPos + zeus::CVector3f{0.f, 0.f, 0.4f * 0.4f * actorProjMag};\n        const zeus::CVector3f p1 = zeus::getBezierPoint(projectilePos, b, c, actorPos, 0.333f);\n        const zeus::CVector3f p2 = zeus::getBezierPoint(projectilePos, b, c, actorPos, 0.666f);\n\n        const float magAdd = (p2 - p1).magnitude() + (p1 - projectilePos).magnitude() + (actorPos - p2).magnitude();\n        const zeus::CVector3f p3 =\n            zeus::getBezierPoint(projectilePos, b, c, actorPos, projectileDir.magnitude() / magAdd);\n\n        const zeus::CVector3f look = p3 - projectilePos;\n        if (look.canBeNormalized()) {\n          zeus::CTransform xf = zeus::lookAt(zeus::skZero3f, look);\n          xf.orthonormalize();\n          CProjectileWeapon& weapon = projectile->ProjectileWeapon();\n          weapon.SetWorldSpaceOrientation(xf);\n          const zeus::CVector3f scaledVelocity = 0.8f * weapon.GetVelocity().normalized();\n          weapon.SetVelocity(weapon.GetVelocity() * 0.39999998f + (scaledVelocity * 0.6f));\n        }\n      }\n    }\n    SetShotAt(true, mgr);\n  }\n}\n\nvoid CElitePirate::UpdateAbsorbBodyState(CStateManager& mgr, float dt) {\n  if (!x988_27_shotAt || x450_bodyController->IsFrozen()) {\n    return;\n  }\n  x7c4_absorbUpdateTimer += dt;\n  if (x7c4_absorbUpdateTimer < 3.f) {\n    return;\n  }\n  if (x450_bodyController->GetCurrentStateId() != pas::EAnimationState::Turn &&\n      x450_bodyController->GetBodyStateInfo().GetCurrentState()->IsMoving()) {\n    x450_bodyController->GetCommandMgr().DeliverCmd(\n        CBCAdditiveReactionCmd(pas::EAdditiveReactionType::Six, 1.f, false));\n  } else {\n    bool b = false;\n    if (HasWeakPointHead()) {\n      if (const TCastToConstPtr<CCollisionActor> actor = mgr.GetObjectById(x770_collisionHeadId)) {\n        const float z = actor->GetTranslation().z();\n        b = z - 0.5f * (z - GetTranslation().z()) <= mgr.GetPlayer().GetTranslation().z();\n      }\n    }\n    b = b || TooClose(mgr, 0.f);\n    x450_bodyController->GetCommandMgr().DeliverCmd(\n        CBCAdditiveReactionCmd(b ? pas::EAdditiveReactionType::Seven : pas::EAdditiveReactionType::Five, 1.f, false));\n  }\n  x7c4_absorbUpdateTimer = 0.f;\n}\n\nbool CElitePirate::IsAttractingEnergy() const {\n  if (x450_bodyController->GetLocomotionType() == pas::ELocomotionType::Crouch) {\n    const auto state = x450_bodyController->GetCurrentStateId();\n    return state == pas::EAnimationState::Locomotion || state == pas::EAnimationState::Turn;\n  }\n  return false;\n}\n\nvoid CElitePirate::UpdateTimers(float dt) {\n  if (x7b8_attackTimer > 0.f) {\n    x7b8_attackTimer -= dt;\n  }\n  if (x7ac_energyAbsorbCooldown > 0.f) {\n    x7ac_energyAbsorbCooldown -= dt;\n  }\n}\n\nvoid CElitePirate::UpdatePositionHistory() {\n  const zeus::CVector3f pos = GetTranslation();\n  if (x7d0_pathFindSearch.OnPath(pos) == CPathFindSearch::EResult::Success) {\n    x8c0_positionHistory.Clear();\n  }\n  x8c0_positionHistory.AddValue(pos);\n}\n\nvoid CElitePirate::UpdateActorTransform(CStateManager& mgr, TUniqueId& uid, std::string_view name) {\n  if (uid == kInvalidUniqueId) {\n    return;\n  }\n  auto* actor = static_cast<CActor*>(mgr.ObjectById(uid));\n  if (actor == nullptr) {\n    uid = kInvalidUniqueId;\n    return;\n  }\n  actor->SetTransform(GetLctrTransform(name));\n}\n\nvoid CElitePirate::UpdateHealthInfo(CStateManager& mgr) {\n  const float hp = HealthInfo(mgr)->GetHP();\n  if (HasWeakPointHead()) {\n    if (TCastToPtr<CCollisionActor> actor = mgr.ObjectById(x770_collisionHeadId)) {\n      const float headHp = actor->HealthInfo(mgr)->GetHP();\n      HealthInfo(mgr)->SetHP(hp - (hp - headHp));\n      *actor->HealthInfo(mgr) = *HealthInfo(mgr);\n    }\n  }\n  if (HealthInfo(mgr)->GetHP() <= 0.f) {\n    Death(mgr, zeus::skZero3f, EScriptObjectState::DeathRattle);\n    RemoveMaterial(EMaterialTypes::Orbit, EMaterialTypes::Target, mgr);\n  }\n}\n\nvoid CElitePirate::ExtendTouchBounds(const CStateManager& mgr, const rstl::reserved_vector<TUniqueId, 7>& uids,\n                                     const zeus::CVector3f& vec) const {\n  for (const auto& uid : uids) {\n    if (TCastToPtr<CCollisionActor> actor = mgr.ObjectById(uid)) {\n      actor->SetExtendedTouchBounds(vec);\n    }\n  }\n}\n\nbool CElitePirate::ShouldFireFromLauncher(CStateManager& mgr, TUniqueId launcherId) {\n  if (x7b8_attackTimer > 0.f || launcherId == kInvalidUniqueId) {\n    return false;\n  }\n  const auto* launcher = static_cast<const CActor*>(mgr.GetObjectById(launcherId));\n  if (launcher == nullptr) {\n    return false;\n  }\n  const zeus::CVector3f aim = mgr.GetPlayer().GetAimPosition(mgr, 0.f);\n  if (x300_maxAttackRange * x300_maxAttackRange > (aim - GetTranslation()).magSquared() || ShouldTurn(mgr, 0.f)) {\n    return false;\n  }\n  const zeus::CVector3f origin = GetLockOnPosition(launcher);\n  if (IsPatternObstructed(mgr, origin, aim)) {\n    return false;\n  }\n  const zeus::CVector3f target = CGrenadeLauncher::GrenadeTarget(mgr);\n  float angleOut = x5d8_data.GetGrenadeTrajectoryInfo().GetAngleMin();\n  float velocityOut = x5d8_data.GetGrenadeTrajectoryInfo().GetVelocityMin();\n  CGrenadeLauncher::CalculateGrenadeTrajectory(target, origin, x5d8_data.GetGrenadeTrajectoryInfo(), angleOut,\n                                               velocityOut);\n  const zeus::CVector3f rot = GetTransform().rotate({0.f, std::cos(angleOut), std::sin(angleOut)});\n  return !CPatterned::IsPatternObstructed(mgr, target, target + (7.5f * rot));\n}\n\nbool CElitePirate::ShouldCallForBackupFromLauncher(const CStateManager& mgr, TUniqueId uid) const {\n  if (x988_30_calledForBackup || uid != kInvalidUniqueId || !x5d8_data.CanCallForBackup()) {\n    return false;\n  }\n  return x7a8_pathShaggedTime >= 3.f;\n}\n\nbool CElitePirate::IsClosestEnergyAttractor(const CStateManager& mgr, const EntityList& charNearList,\n                                            const zeus::CVector3f& projectilePos) const {\n  const float distance = (projectilePos - GetTranslation()).magSquared();\n  for (const auto& id : charNearList) {\n    if (const TCastToConstPtr<CPatterned> actor = mgr.GetObjectById(id)) {\n      if (actor->GetUniqueId() != GetUniqueId() && actor->IsEnergyAttractor() &&\n          (projectilePos - actor->GetTranslation()).magSquared() < distance) {\n        return false;\n      }\n    }\n  }\n  return true;\n}\n\nvoid CElitePirate::ShakeCamera(CStateManager& mgr) {\n  CPlayer& player = mgr.GetPlayer();\n  const float distance = (GetTranslation() - player.GetTranslation()).magnitude();\n  const float scale = x988_29_shockWaveAnim ? 1.f : 0.25f;\n  const float magnitude = (scale * GetModelData()->GetScale().magnitude()) - (0.005f * distance);\n  if (magnitude <= 0.f || player.GetSurfaceRestraint() == CPlayer::ESurfaceRestraints::Air ||\n      player.IsInWaterMovement()) {\n    return;\n  }\n  if (player.GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed) {\n    const float intensity = x988_29_shockWaveAnim ? 20.f : 10.f;\n    player.ApplyImpulseWR(player.GetMass() * intensity * zeus::skUp, zeus::CAxisAngle{});\n    player.SetMoveState(CPlayer::EPlayerMovementState::ApplyJump, mgr);\n  } else if (mgr.GetCameraManager()->GetCurrentCameraId() ==\n             mgr.GetCameraManager()->GetFirstPersonCamera()->GetUniqueId()) {\n    mgr.GetCameraManager()->AddCameraShaker(CCameraShakeData{0.5f, magnitude}, true);\n  }\n}\n\nzeus::CVector3f CElitePirate::SPositionHistory::GetValue(const zeus::CVector3f& pos, const zeus::CVector3f& face) {\n  while (!x4_values.empty()) {\n    const zeus::CVector3f v = x4_values.back() - pos;\n    if (v.dot(face) > 0.f && v.isMagnitudeSafe()) {\n      return v.normalized();\n    }\n    x4_values.pop_back();\n  }\n  return zeus::skZero3f;\n}\n\nvoid CElitePirate::SPositionHistory::AddValue(const zeus::CVector3f& pos) {\n  if (x4_values.size() > 15) {\n    return;\n  }\n  if (x4_values.empty()) {\n    x4_values.emplace_back(pos);\n    return;\n  }\n  if (x4_values.back().magSquared() > x0_magSquared) {\n    x4_values.emplace_back(pos);\n  }\n}\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CElitePirate.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Character/CBoneTracking.hpp\"\n#include \"Runtime/Collision/CCollisionActorManager.hpp\"\n#include \"Runtime/Collision/CJointCollisionDescription.hpp\"\n#include \"Runtime/MP1/World/CGrenadeLauncher.hpp\"\n#include \"Runtime/MP1/World/CShockWave.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/World/CAnimationParameters.hpp\"\n#include \"Runtime/World/CPathFindSearch.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n\nnamespace metaforce::MP1 {\nclass CElitePirateData {\nprivate:\n  float x0_tauntInterval;\n  float x4_tauntVariance;\n  float x8_;\n  float xc_;\n  float x10_attackChance;\n  float x14_shotAtTime;\n  float x18_shotAtTimeVariance;\n  float x1c_projectileAttractionRadius;\n  CAssetId x20_energyAbsorbParticleDescId;\n  u16 x24_energyAbsorbSfxId;\n  CActorParameters x28_launcherActParams;\n  CAnimationParameters x90_launcherAnimParams;\n  CAssetId x9c_launcherParticleGenDescId;\n  u16 xa0_launcherSfxId;\n  CAssetId xa4_grenadeModelId;\n  CDamageInfo xa8_grenadeDamageInfo;\n  float xc4_launcherHp;\n  CAssetId xc8_grenadeElementGenDescId1;\n  CAssetId xcc_grenadeElementGenDescId2;\n  CAssetId xd0_grenadeElementGenDescId3;\n  CAssetId xd4_grenadeElementGenDescId4;\n  SGrenadeVelocityInfo xd8_grenadeVelocityInfo;\n  SGrenadeTrajectoryInfo xe0_grenadeTrajectoryInfo;\n  u32 xf0_grenadeNumBounces;\n  u16 xf4_grenadeBounceSfxId;\n  u16 xf6_grenadeExplodeSfxId;\n  CAssetId xf8_shockwaveParticleDescId;\n  CDamageInfo xfc_shockwaveDamageInfo;\n  CAssetId x118_shockwaveWeaponDescId;\n  u16 x11c_shockwaveElectrocuteSfxId;\n  bool x11e_canCallForBackup;\n  bool x11f_fastWhenAttractingEnergy;\n\npublic:\n  CElitePirateData(CInputStream&, u32 propCount);\n\n  [[nodiscard]] float GetTauntInterval() const { return x0_tauntInterval; }\n  [[nodiscard]] float GetTauntVariance() const { return x4_tauntVariance; }\n  [[nodiscard]] float GetAttackChance() const { return x10_attackChance; }\n  [[nodiscard]] float GetShotAtTime() const { return x14_shotAtTime; }\n  [[nodiscard]] float GetShotAtTimeVariance() const { return x18_shotAtTimeVariance; }\n  [[nodiscard]] float GetProjectileAttractionRadius() const { return x1c_projectileAttractionRadius; }\n  [[nodiscard]] CAssetId GetEnergyAbsorbParticleDescId() const { return x20_energyAbsorbParticleDescId; }\n  [[nodiscard]] u16 GetEnergyAbsorbSfxId() const { return x24_energyAbsorbSfxId; }\n  [[nodiscard]] const CActorParameters& GetLauncherActParams() const { return x28_launcherActParams; }\n  [[nodiscard]] const CAnimationParameters& GetLauncherAnimParams() const { return x90_launcherAnimParams; }\n  [[nodiscard]] float GetLauncherHP() const { return xc4_launcherHp; }\n  [[nodiscard]] const SGrenadeTrajectoryInfo& GetGrenadeTrajectoryInfo() const { return xe0_grenadeTrajectoryInfo; }\n  [[nodiscard]] CAssetId GetShockwaveParticleDescId() const { return xf8_shockwaveParticleDescId; }\n  [[nodiscard]] const CDamageInfo& GetShockwaveDamageInfo() const { return xfc_shockwaveDamageInfo; }\n  [[nodiscard]] CAssetId GetShockwaveWeaponDescId() const { return x118_shockwaveWeaponDescId; }\n  [[nodiscard]] u16 GetShockwaveElectrocuteSfxId() const { return x11c_shockwaveElectrocuteSfxId; }\n  [[nodiscard]] bool CanCallForBackup() const { return x11e_canCallForBackup; }\n  [[nodiscard]] bool IsFastWhenAttractingEnergy() const { return x11f_fastWhenAttractingEnergy; }\n\n  [[nodiscard]] SBouncyGrenadeData GetBouncyGrenadeData() const {\n    return {\n        xd8_grenadeVelocityInfo,      xa8_grenadeDamageInfo,        xc8_grenadeElementGenDescId1,\n        xcc_grenadeElementGenDescId2, xd0_grenadeElementGenDescId3, xd4_grenadeElementGenDescId4,\n        xf0_grenadeNumBounces,        xf4_grenadeBounceSfxId,       xf6_grenadeExplodeSfxId,\n    };\n  }\n  [[nodiscard]] SGrenadeLauncherData GetGrenadeLauncherData() const {\n    return {\n        GetBouncyGrenadeData(), xa4_grenadeModelId,        x9c_launcherParticleGenDescId,\n        xa0_launcherSfxId,      xe0_grenadeTrajectoryInfo,\n    };\n  }\n};\n\nclass CElitePirate : public CPatterned {\nprotected:\n  enum class EState {\n    Invalid = -1,\n    Zero = 0,\n    One = 1,\n    Two = 2,\n    Over = 3,\n  };\n\nprivate:\n  struct SPositionHistory {\n  private:\n    float x0_magSquared;\n    rstl::reserved_vector<zeus::CVector3f, 16> x4_values;\n\n  public:\n    explicit SPositionHistory(float mag) : x0_magSquared(mag * mag) {}\n    zeus::CVector3f GetValue(const zeus::CVector3f& pos, const zeus::CVector3f& face);\n    void AddValue(const zeus::CVector3f& pos);\n    void Clear() { x4_values.clear(); }\n  };\n\n  EState x568_state = EState::Invalid;\n  CDamageVulnerability x56c_vulnerability;\n  std::unique_ptr<CCollisionActorManager> x5d4_collisionActorMgr;\n  CElitePirateData x5d8_data;\n  CBoneTracking x6f8_boneTracking;\n  std::unique_ptr<CCollisionActorManager> x730_collisionActorMgrHead;\n  // s32 x734_;\n  CCollidableAABox x738_collisionAabb;\n  std::optional<TLockedToken<CGenDescription>> x760_energyAbsorbDesc;\n  TUniqueId x770_collisionHeadId = kInvalidUniqueId;\n  TUniqueId x772_launcherId = kInvalidUniqueId;\n  rstl::reserved_vector<TUniqueId, 7> x774_collisionRJointIds;\n  rstl::reserved_vector<TUniqueId, 7> x788_collisionLJointIds;\n  TUniqueId x79c_energyAttractorId = kInvalidUniqueId;\n  float x7a0_initialSpeed;\n  float x7a4_steeringSpeed = 1.f;\n  float x7a8_pathShaggedTime = 0.f;\n  float x7ac_energyAbsorbCooldown = 0.f;\n  float x7b0_ = 1.f;\n  float x7b4_hp = 0.f;\n  float x7b8_attackTimer = 0.f;\n  float x7bc_tauntTimer = 0.f;\n  float x7c0_shotAtTimer = 0.f;\n  float x7c4_absorbUpdateTimer = 0.f;\n  s32 x7c8_currAnimId = -1;\n  u32 x7cc_activeMaterialSet = 0;\n  CPathFindSearch x7d0_pathFindSearch;\n  zeus::CVector3f x8b4_targetDestPos;\n  SPositionHistory x8c0_positionHistory{5.0f};\n  bool x988_24_damageOn : 1 = false;\n  bool x988_25_attackingRightClaw : 1 = false;\n  bool x988_26_attackingLeftClaw : 1 = false;\n  bool x988_27_shotAt : 1 = false;\n  bool x988_28_alert : 1 = false;\n  bool x988_29_shockWaveAnim : 1 = false;\n  bool x988_30_calledForBackup : 1 = false;\n  bool x988_31_running : 1 = false;\n  bool x989_24_onPath : 1 = false;\n\npublic:\n  DEFINE_PATTERNED(ElitePirate);\n\n  CElitePirate(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n               CModelData&& mData, const CPatternedInfo& pInfo, const CActorParameters& actParms,\n               CElitePirateData data);\n\n  void Accept(IVisitor& visitor) override;\n  void Think(float dt, CStateManager& mgr) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) override;\n  void PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) override;\n  const CDamageVulnerability* GetDamageVulnerability() const override;\n  const CDamageVulnerability* GetDamageVulnerability(const zeus::CVector3f& pos, const zeus::CVector3f& dir,\n                                                     const CDamageInfo& dInfo) const override;\n  zeus::CVector3f GetOrbitPosition(const CStateManager& mgr) const override;\n  zeus::CVector3f GetAimPosition(const CStateManager& mgr, float) const override;\n  void DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) override;\n  const CCollisionPrimitive* GetCollisionPrimitive() const override;\n  void KnockBack(const zeus::CVector3f&, CStateManager& mgr, const CDamageInfo& info, EKnockBackType type,\n                 bool inDeferred, float magnitude) override;\n  void TakeDamage(const zeus::CVector3f&, float arg) override;\n  void Patrol(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void PathFind(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void TargetPatrol(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Halt(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Run(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Generate(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Attack(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Taunt(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void ProjectileAttack(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Cover(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void SpecialAttack(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void CallForBackup(CStateManager& mgr, EStateMsg msg, float dt) override;\n  bool TooClose(CStateManager& mgr, float arg) override;\n  bool InDetectionRange(CStateManager& mgr, float arg) override;\n  bool SpotPlayer(CStateManager& mgr, float arg) override;\n  bool AnimOver(CStateManager& mgr, float arg) override;\n  bool ShouldAttack(CStateManager& mgr, float arg) override;\n  bool InPosition(CStateManager& mgr, float arg) override;\n  bool ShouldTurn(CStateManager& mgr, float arg) override;\n  bool AggressionCheck(CStateManager& mgr, float arg) override;\n  bool ShouldTaunt(CStateManager& mgr, float arg) override;\n  bool ShouldFire(CStateManager& mgr, float arg) override;\n  bool ShotAt(CStateManager& mgr, float arg) override;\n  bool ShouldSpecialAttack(CStateManager& mgr, float arg) override;\n  bool ShouldCallForBackup(CStateManager& mgr, float arg) override;\n  CPathFindSearch* GetSearchPath() override;\n  virtual bool HasWeakPointHead() const { return true; }\n  virtual bool IsElitePirate() const { return true; }\n  virtual void SetupHealthInfo(CStateManager& mgr);\n  virtual void SetLaunchersActive(CStateManager& mgr, bool val);\n  virtual CShockWaveInfo GetShockWaveData() const {\n    return {x5d8_data.GetShockwaveParticleDescId(), x5d8_data.GetShockwaveDamageInfo(), 16.5217f,\n            x5d8_data.GetShockwaveWeaponDescId(), x5d8_data.GetShockwaveElectrocuteSfxId()};\n  }\n\nprotected:\n  void SetShotAt(bool val, CStateManager& mgr);\n  void CreateGrenadeLauncher(CStateManager& mgr, TUniqueId uid);\n  zeus::CVector3f GetLockOnPosition(const CActor* actor) const;\n  bool ShouldFireFromLauncher(CStateManager& mgr, TUniqueId launcherId);\n  bool ShouldCallForBackupFromLauncher(const CStateManager& mgr, TUniqueId uid) const;\n  void SetupLauncherHealthInfo(CStateManager& mgr, TUniqueId uid);\n  void SetLauncherActive(CStateManager& mgr, bool val, TUniqueId uid);\n  void SetupPathFindSearch();\n  void UpdateActorTransform(CStateManager& mgr, TUniqueId& uid, std::string_view name);\n\n  const CElitePirateData& GetData() const { return x5d8_data; }\n  EState GetState() const { return x568_state; }\n  void SetState(EState state) { x568_state = state; }\n  TUniqueId GetLauncherId() const { return x772_launcherId; }\n  void SetAlert(bool val) { x988_28_alert = val; }\n  const CCollisionActorManager& GetCollisionActorManager() const { return *x5d4_collisionActorMgr; }\n\nprivate:\n  void AddSphereCollisionList(const SSphereJointInfo* joints, size_t count,\n                              std::vector<CJointCollisionDescription>& outJoints) const;\n  void AddCollisionList(const SJointInfo* joints, size_t count,\n                        std::vector<CJointCollisionDescription>& outJoints) const;\n  void SetupCollisionManager(CStateManager& mgr);\n  void SetupCollisionActorInfo(CStateManager& mgr);\n  void ApplyDamageToHead(CStateManager& mgr, TUniqueId uid);\n  void CreateEnergyAbsorb(CStateManager& mgr, const zeus::CTransform& xf);\n  bool CanKnockBack(const CDamageInfo& info) const;\n  void UpdateDestPos(CStateManager& mgr);\n  void CheckAttackChance(CStateManager& mgr);\n  void AttractProjectiles(CStateManager& mgr);\n  void UpdateAbsorbBodyState(CStateManager& mgr, float dt);\n  bool IsAttractingEnergy() const;\n  void UpdateTimers(float dt);\n  void UpdatePositionHistory();\n  void UpdateHealthInfo(CStateManager& mgr);\n  void ExtendTouchBounds(const CStateManager& mgr, const rstl::reserved_vector<TUniqueId, 7>& uids,\n                         const zeus::CVector3f& vec) const;\n  bool IsClosestEnergyAttractor(const CStateManager& mgr, const EntityList& charNearList,\n                                const zeus::CVector3f& projectilePos) const;\n  void ShakeCamera(CStateManager& mgr);\n};\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CEnergyBall.cpp",
    "content": "#include \"Runtime/MP1/World/CEnergyBall.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/World/CPatternedInfo.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n\nnamespace metaforce::MP1 {\nCEnergyBall::CEnergyBall(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                         CModelData&& mData, const CActorParameters& actParms, const CPatternedInfo& pInfo, s32 w1,\n                         float f1, const CDamageInfo& dInfo1, float f2, const CAssetId& a1, s16 sfxId1,\n                         const CAssetId& a2, const CAssetId& a3, s16 sfxId2, float f3, float f4, const CAssetId& a4,\n                         const CDamageInfo& dInfo2, float f5)\n: CPatterned(ECharacter::EnergyBall, uid, name, EFlavorType::Zero, info, xf, std::move(mData), pInfo,\n             EMovementType::Flyer, EColliderType::One, EBodyType::NewFlyer, actParms, EKnockBackVariant::Medium)\n, x570_(w1)\n, x574_(f1)\n, x578_(dInfo1)\n, x594_initialTurnSpeed(pInfo.GetTurnSpeed())\n, x598_(f2)\n, x59c_(a1)\n, x5a0_(sfxId1)\n, x5a4_(a2)\n, x5a8_(g_SimplePool->GetObj({FOURCC('ELSC'), a3}))\n, x5b4_(sfxId2)\n, x5b8_(f3)\n, x5bc_(f4)\n, x5c0_(g_SimplePool->GetObj({FOURCC('PART'), a4}))\n, x5cc_(dInfo2)\n, x5e8_(f5) {\n  x460_knockBackController.SetEnableExplodeDeath(false);\n  x460_knockBackController.SetAutoResetImpulse(false);\n  x460_knockBackController.SetEnableBurnDeath(false);\n  x460_knockBackController.SetX82_24(false);\n  x460_knockBackController.SetEnableBurn(false);\n  x460_knockBackController.SetEnableLaggedBurnDeath(false);\n  x460_knockBackController.SetEnableShock(false);\n  x460_knockBackController.SetEnableFreeze(false);\n  x460_knockBackController.SetX81_31(false);\n}\n\nvoid CEnergyBall::Think(float dt, CStateManager& mgr) {\n  float newTurnSpeed = x594_initialTurnSpeed * zeus::clamp(0.f, (x56c_ - 2.5f) * 0.125f, 1.f);\n  x3b8_turnSpeed = newTurnSpeed;\n  x450_bodyController->SetTurnSpeed(newTurnSpeed);\n  CPatterned::Think(dt, mgr);\n  GetModelData()->GetAnimationData()->GetParticleDB().SetModulationColorAllActiveEffects(\n      zeus::CColor::lerp(zeus::skWhite, zeus::skRed, zeus::clamp(0.f, x428_damageCooldownTimer / 0.33f, 1.f)));\n\n  bool r27 = false;\n  if (GetActive() && IsAlive()) {\n    x56c_ -= dt;\n    if (x56c_ > x574_)\n      r27 = true;\n    if (!InMaxRange(mgr, dt))\n      r27 = true;\n  }\n\n  if (!r27)\n    Detonate(mgr);\n}\n\nvoid CEnergyBall::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  if (msg == EScriptObjectMessage::Registered) {\n    SetMaterialFilter(CMaterialFilter::MakeInclude({EMaterialTypes::Player}));\n    RemoveMaterial(EMaterialTypes::Solid, mgr);\n  }\n  CPatterned::AcceptScriptMsg(msg, uid, mgr);\n}\n\nvoid CEnergyBall::Death(CStateManager& mgr, const zeus::CVector3f& direction, EScriptObjectState /* state */) {\n  CHealthInfo* hInfo = HealthInfo(mgr);\n  if (hInfo && hInfo->GetHP() > 0.f) {\n    CPatterned::Death(mgr, direction, EScriptObjectState::Any);\n  } else {\n    CPatterned::Death(mgr, direction, EScriptObjectState::DeathRattle);\n  }\n}\n\nvoid CEnergyBall::Generate(CStateManager& mgr, EStateMsg msg, float /*arg*/) {\n  if (msg == EStateMsg::Activate || msg == EStateMsg::Update) {\n    if (msg == EStateMsg::Activate)\n      x32c_animState = EAnimState::Ready;\n\n    TryGenerateDeactivate(mgr, 0);\n\n    if (!x450_bodyController->GetActive())\n      x450_bodyController->Activate(mgr);\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n  }\n}\n\nvoid CEnergyBall::Attack(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Update) {\n    zeus::CVector3f seekPos = x568_steeringBehaviors.Seek(*this, mgr.GetPlayer().GetEyePosition());\n    x450_bodyController->FaceDirection3D(seekPos, GetTransform().basis[1], arg);\n  }\n}\nvoid CEnergyBall::Detonate(CStateManager& mgr) {}\n} // namespace metaforce::MP1"
  },
  {
    "path": "Runtime/MP1/World/CEnergyBall.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/Character/CSteeringBehaviors.hpp\"\n#include \"Runtime/World/CDamageInfo.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n\nnamespace metaforce::MP1 {\nclass CEnergyBall : public CPatterned {\n  CSteeringBehaviors x568_steeringBehaviors;\n  float x56c_ = 0.f;\n  s32 x570_;\n  float x574_;\n  CDamageInfo x578_;\n  float x594_initialTurnSpeed;\n  float x598_;\n  CAssetId x59c_;\n  s16 x5a0_;\n  CAssetId x5a4_;\n  TToken<CElectricDescription> x5a8_; // originally an rstl::optional_object\n  s16 x5b4_;\n  float x5b8_;\n  float x5bc_;\n  TToken<CGenDescription> x5c0_;\n  CDamageInfo x5cc_;\n  float x5e8_;\n\n  void Detonate(CStateManager& mgr);\n\npublic:\n  DEFINE_PATTERNED(EnergyBall);\n  CEnergyBall(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n              CModelData&& mData, const CActorParameters& actParms, const CPatternedInfo& pInfo, s32 w1, float f1,\n              const CDamageInfo& dInfo1, float f2, const CAssetId& a1, s16 sfxId1, const CAssetId& a2,\n              const CAssetId& a3, s16 sfxId2, float f3, float f4, const CAssetId& a4, const CDamageInfo& dInfo2,\n              float f5);\n\n  void Think(float dt, CStateManager& mgr) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) override;\n  void Death(CStateManager& mgr, const zeus::CVector3f& direction, EScriptObjectState state) override;\n  void Generate(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Attack(CStateManager& mgr, EStateMsg msg, float arg) override;\n};\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CEyeball.cpp",
    "content": "#include \"Runtime/MP1/World/CEyeball.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Weapon/CBeamInfo.hpp\"\n#include \"Runtime/Weapon/CGameProjectile.hpp\"\n#include \"Runtime/Weapon/CPlasmaProjectile.hpp\"\n#include \"Runtime/World/CGameArea.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce::MP1 {\nconstexpr std::string_view skEyeLocator = \"Laser_LCTR\"sv;\n\nCEyeball::CEyeball(TUniqueId uid, std::string_view name, CPatterned::EFlavorType flavor, const CEntityInfo& info,\n                   const zeus::CTransform& xf, CModelData&& mData, const CPatternedInfo& pInfo, float attackDelay,\n                   float attackStartTime, CAssetId wpscId, const CDamageInfo& dInfo, CAssetId beamContactFxId,\n                   CAssetId beamPulseFxId, CAssetId beamTextureId, CAssetId beamGlowTextureId, u32 anim0, u32 anim1,\n                   u32 anim2, u32 anim3, u32 beamSfx, bool attackDisabled, const CActorParameters& actParms)\n: CPatterned(ECharacter::EyeBall, uid, name, flavor, info, xf, std::move(mData), pInfo, EMovementType::Flyer,\n             EColliderType::Zero, EBodyType::Restricted, actParms, EKnockBackVariant::Medium)\n, x568_attackDelay(attackDelay)\n, x56c_attackStartTime(attackStartTime)\n, x570_boneTracking(*GetModelData()->GetAnimationData(), \"Eye\"sv, zeus::degToRad(45.f), zeus::degToRad(180.f),\n                    EBoneTrackingFlags::NoParent)\n, x5b4_projectileInfo(wpscId, dInfo)\n, x5dc_beamContactFxId(beamContactFxId)\n, x5e0_beamPulseFxId(beamPulseFxId)\n, x5e4_beamTextureId(beamTextureId)\n, x5e8_beamGlowTextureId(beamGlowTextureId)\n, x5f4_animIdxs{static_cast<s32>(anim0), static_cast<s32>(anim1), static_cast<s32>(anim2), static_cast<s32>(anim3)}\n, x604_beamSfxId(CSfxManager::TranslateSFXID(beamSfx))\n, x60c_27_attackDisabled(attackDisabled) {\n  x460_knockBackController.SetAutoResetImpulse(false);\n}\n\nvoid CEyeball::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CEyeball::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  bool skipForward = false;\n  switch (msg) {\n  case EScriptObjectMessage::Damage: {\n    if (TCastToConstPtr<CGameProjectile> const proj = mgr.GetObjectById(uid)) {\n      if (proj->GetOwnerId() == mgr.GetPlayer().GetUniqueId())\n        if (static_cast<CActor*>(this)->GetDamageVulnerability()->GetVulnerability(\n                proj->GetDamageInfo().GetWeaponMode(), false) != EVulnerability::Deflect)\n          x400_24_hitByPlayerProjectile = true;\n    }\n    skipForward = true;\n    break;\n  }\n  case EScriptObjectMessage::InvulnDamage: {\n    if (TCastToConstPtr<CGameProjectile> const proj = mgr.GetObjectById(uid)) {\n      if (proj->GetOwnerId() == mgr.GetPlayer().GetUniqueId())\n        if (static_cast<CActor*>(this)->GetDamageVulnerability()->GetVulnerability(\n                proj->GetDamageInfo().GetWeaponMode(), false) != EVulnerability::Deflect)\n          x400_24_hitByPlayerProjectile = true;\n    }\n    skipForward = true;\n    break;\n  }\n  case EScriptObjectMessage::Alert:\n    x60c_26_alert = true;\n    break;\n  case EScriptObjectMessage::Registered: {\n    RemoveMaterial(EMaterialTypes::Orbit, EMaterialTypes::Target, mgr);\n    x450_bodyController->Activate(mgr);\n    x330_stateMachineState.SetDelay(0.f);\n    CreateShadow(false);\n    CreateBeam(mgr);\n    break;\n  }\n  case EScriptObjectMessage::Deleted: {\n    if (x5ec_projectileId != kInvalidUniqueId) {\n      mgr.FreeScriptObject(x5ec_projectileId);\n      if (x608_beamSfx) {\n        CSfxManager::RemoveEmitter(x608_beamSfx);\n        x608_beamSfx.reset();\n      }\n    }\n    x5ec_projectileId = kInvalidUniqueId;\n    break;\n  }\n  default:\n    break;\n  }\n  if (!skipForward) {\n    CPatterned::AcceptScriptMsg(msg, uid, mgr);\n  }\n}\n\nvoid CEyeball::Think(float dt, CStateManager& mgr) {\n  CPatterned::Think(dt, mgr);\n\n  if (!GetActive()) {\n    return;\n  }\n\n  const CPlayer& player = mgr.GetPlayer();\n  const zeus::CVector3f direction = (player.GetTranslation() - GetTranslation()).normalized();\n\n  // Used to be directly calculated as std::cos(zeus::degToRad(45.f));\n  // but was converted into the exact constant to avoid unnecessary runtime initialization.\n  constexpr float minAngle = 0.707106769f;\n\n  x60c_25_playerInRange = (player.GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed &&\n                           direction.dot(GetTransform().frontVector()) > minAngle);\n\n  if (x60c_25_playerInRange) {\n    x570_boneTracking.SetActive(true);\n    x5a8_targetPosition = player.GetTranslation() - (0.5f * player.GetVelocity());\n    x570_boneTracking.SetTargetPosition(x5a8_targetPosition);\n    x570_boneTracking.Update(dt);\n    GetModelData()->GetAnimationData()->PreRender();\n    x570_boneTracking.PreRender(mgr, *GetModelData()->GetAnimationData(), GetTransform(), GetModelData()->GetScale(),\n                                *x450_bodyController);\n  } else {\n    x570_boneTracking.SetActive(false);\n  }\n\n  if (GetActive()) {\n    CPlasmaProjectile* projectile = static_cast<CPlasmaProjectile*>(mgr.ObjectById(x5ec_projectileId));\n    if (projectile && projectile->GetActive())\n      projectile->UpdateFx(GetLctrTransform(skEyeLocator), dt, mgr);\n  }\n\n  if (x60c_28_firingBeam) {\n    const CGameArea* area = mgr.GetWorld()->GetAreaAlways(GetAreaIdAlways());\n    if (area->GetActive() && area->IsPostConstructed() &&\n        area->GetPostConstructed()->x10dc_occlusionState == CGameArea::EOcclusionState::Occluded)\n      ResetBeamState(mgr);\n  }\n}\n\nvoid CEyeball::CreateBeam(CStateManager& mgr) {\n  if (x5ec_projectileId != kInvalidUniqueId)\n    return;\n\n  CBeamInfo beamInfo(3, x5dc_beamContactFxId, x5e0_beamPulseFxId, x5e4_beamTextureId, x5e8_beamGlowTextureId, 50, 0.05f,\n                     1.f, 2.f, 20.f, 1.f, 1.f, 2.f, zeus::CColor(1.f, 1.f, 1.f, 0.f), zeus::CColor(0.f, 1.f, 0.5f, 0.f),\n                     150.f);\n  x5ec_projectileId = mgr.AllocateUniqueId();\n  mgr.AddObject(new CPlasmaProjectile(x5b4_projectileInfo.Token(), \"EyeBall_Beam\"sv, EWeaponType::AI, beamInfo,\n                                      zeus::CTransform(), EMaterialTypes::Character, x5b4_projectileInfo.GetDamage(),\n                                      x5ec_projectileId, GetAreaIdAlways(), GetUniqueId(), {}, false,\n                                      EProjectileAttrib::KeepInCinematic));\n}\n\nvoid CEyeball::InActive(CStateManager&, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate)\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n}\n\nvoid CEyeball::Cover(CStateManager&, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Lurk);\n    x60c_24_canAttack = false;\n    x330_stateMachineState.SetDelay(x568_attackDelay);\n  }\n}\n\nvoid CEyeball::Flinch(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n    x330_stateMachineState.SetDelay(x568_attackDelay);\n  } else if (msg == EStateMsg::Update)\n    TryCommand(mgr, pas::EAnimationState::KnockBack, CPatternedTryFunc(&CEyeball::TryFlinch), 0);\n  else if (msg == EStateMsg::Deactivate)\n    x32c_animState = EAnimState::NotReady;\n}\n\nvoid CEyeball::TryFlinch(CStateManager&, int arg) {\n  x450_bodyController->GetCommandMgr().DeliverCmd(CBCKnockBackCmd(GetTransform().basis[0], pas::ESeverity(arg)));\n}\n\nvoid CEyeball::Active(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x400_24_hitByPlayerProjectile = 0;\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Combat);\n    x60c_24_canAttack = false;\n  } else if (msg == EStateMsg::Update) {\n    if (x330_stateMachineState.GetTime() > x56c_attackStartTime)\n      x60c_24_canAttack = true;\n\n    UpdateAnimation();\n  } else if (msg == EStateMsg::Deactivate) {\n    x330_stateMachineState.SetDelay(x568_attackDelay);\n    if (CPlasmaProjectile* proj = static_cast<CPlasmaProjectile*>(mgr.ObjectById(x5ec_projectileId)))\n      proj->ResetBeam(mgr, true);\n\n    x60c_24_canAttack = false;\n\n    CSfxManager::RemoveEmitter(x608_beamSfx);\n    x608_beamSfx.reset();\n  }\n}\n\nvoid CEyeball::UpdateAnimation() {\n  if (std::fabs(GetModelData()->GetAnimationData()->GetAnimTimeRemaining(\"Whole Body\"sv) - 0.f) >= 0.00001f) {\n    return;\n  }\n\n  x5f0_currentAnim = (x5f0_currentAnim + 1) & 3;\n  for (size_t i = 0; i < x5f4_animIdxs.size(); ++i) {\n    if (x5f4_animIdxs[x5f0_currentAnim] != -1) {\n      break;\n    }\n\n    x5f0_currentAnim = (x5f0_currentAnim + 1) & 3;\n  }\n  const s32 animIdx = x5f4_animIdxs[x5f0_currentAnim];\n  if (animIdx != -1) {\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBCScriptedCmd(animIdx, false, false, 0.f));\n  }\n}\n\nvoid CEyeball::ResetBeamState(CStateManager& mgr) {\n  if (CPlasmaProjectile* projectile = static_cast<CPlasmaProjectile*>(mgr.ObjectById(x5ec_projectileId)))\n    projectile->ResetBeam(mgr, true);\n\n  x60c_28_firingBeam = false;\n  if (x608_beamSfx) {\n    CSfxManager::RemoveEmitter(x608_beamSfx);\n    x608_beamSfx.reset();\n  }\n}\n\nvoid CEyeball::DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) {\n  if (type == EUserEventType::DamageOff)\n    ResetBeamState(mgr);\n  else if (type == EUserEventType::DamageOn && x60c_24_canAttack)\n    FireBeam(mgr, GetLctrTransform(node.GetLocatorName()));\n  else\n    CPatterned::DoUserAnimEvent(mgr, node, type, dt);\n}\n\nvoid CEyeball::FireBeam(CStateManager& mgr, const zeus::CTransform& xf) {\n  if (CPlasmaProjectile* projectile = static_cast<CPlasmaProjectile*>(mgr.ObjectById(x5ec_projectileId))) {\n    if (!projectile->GetActive()) {\n      projectile->Fire(xf, mgr, false);\n      x60c_28_firingBeam = true;\n      if (!x608_beamSfx) {\n        CAudioSys::C3DEmitterParmData parmData{\n            GetTranslation(), {}, 50.f, 0.1f, 0x1, x604_beamSfxId, 1.f /* 127 */, 0.15f /* 20 / 127 */, false, 127};\n        x608_beamSfx = CSfxManager::AddEmitter(parmData, true, 127, true, GetAreaIdAlways());\n      }\n    }\n  }\n}\n\nvoid CEyeball::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) {\n  CPatterned::PreRender(mgr, frustum);\n  x570_boneTracking.PreRender(mgr, *GetModelData()->GetAnimationData(), GetTransform(), GetModelData()->GetScale(),\n                              *x450_bodyController);\n}\n\nvoid CEyeball::Death(CStateManager& mgr, const zeus::CVector3f& pos, EScriptObjectState state) {\n  zeus::CTransform oldXf = GetTransform();\n  CPatterned::Death(mgr, pos, state);\n  SetTransform(oldXf);\n}\n} // namespace metaforce::MP1"
  },
  {
    "path": "Runtime/MP1/World/CEyeball.hpp",
    "content": "#pragma once\n\n#include <array>\n#include <string_view>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Character/CBoneTracking.hpp\"\n#include \"Runtime/Weapon/CProjectileInfo.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce::MP1 {\nclass CEyeball : public CPatterned {\n  float x568_attackDelay;\n  float x56c_attackStartTime;\n  CBoneTracking x570_boneTracking;\n  zeus::CVector3f x5a8_targetPosition;\n  CProjectileInfo x5b4_projectileInfo;\n  CAssetId x5dc_beamContactFxId;\n  CAssetId x5e0_beamPulseFxId;\n  CAssetId x5e4_beamTextureId;\n  CAssetId x5e8_beamGlowTextureId;\n  TUniqueId x5ec_projectileId = kInvalidUniqueId;\n  u32 x5f0_currentAnim = 0;\n  std::array<s32, 4> x5f4_animIdxs;\n  u16 x604_beamSfxId;\n  CSfxHandle x608_beamSfx;\n  bool x60c_24_canAttack : 1 = false;\n  bool x60c_25_playerInRange : 1 = false;\n  bool x60c_26_alert : 1 = false;\n  bool x60c_27_attackDisabled : 1;\n  bool x60c_28_firingBeam : 1 = false;\n\n  void CreateBeam(CStateManager&);\n  void FireBeam(CStateManager&, const zeus::CTransform&);\n  void TryFlinch(CStateManager&, int);\n  void UpdateAnimation();\n  void ResetBeamState(CStateManager&);\n\npublic:\n  DEFINE_PATTERNED(EyeBall);\n\n  CEyeball(TUniqueId uid, std::string_view name, CPatterned::EFlavorType flavor, const CEntityInfo& info,\n           const zeus::CTransform& xf, CModelData&& mData, const CPatternedInfo& pInfo, float attackDelay,\n           float attackStartTime, CAssetId wpscId, const CDamageInfo& dInfo, CAssetId beamContactFxId,\n           CAssetId beamPulseFxId, CAssetId beamTextureId, CAssetId beamGlowTextureId, u32 anim0, u32 anim1, u32 anim2,\n           u32 anim3, u32 beamSfx, bool attackDisabled, const CActorParameters& actParms);\n\n  void Accept(IVisitor& visitor) override;\n  void PreRender(CStateManager&, const zeus::CFrustum&) override;\n  void Touch(CActor&, CStateManager&) override {}\n  void Death(CStateManager&, const zeus::CVector3f&, EScriptObjectState) override;\n\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) override;\n  void DoUserAnimEvent(CStateManager&, const CInt32POINode&, EUserEventType, float) override;\n  void Think(float, CStateManager&) override;\n  void Flinch(CStateManager&, EStateMsg, float) override;\n  void Active(CStateManager&, EStateMsg, float) override;\n  void InActive(CStateManager&, EStateMsg, float) override;\n\n  void Cover(CStateManager&, EStateMsg, float) override;\n\n  bool ShouldAttack(CStateManager&, float) override { return x60c_26_alert; }\n  bool ShouldFire(CStateManager&, float) override { return !x60c_27_attackDisabled; }\n};\n} // namespace metaforce::MP1"
  },
  {
    "path": "Runtime/MP1/World/CFireFlea.cpp",
    "content": "#include \"Runtime/MP1/World/CFireFlea.hpp\"\n\n#include \"Runtime/CPlayerState.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CPatternedInfo.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce::MP1 {\nnamespace {\nconstexpr zeus::CColor skEndFadeColor{1.f, 1.f, 0.5f, 1.f};\nconstexpr zeus::CColor skStartFadeColor{1.f, 0.f, 0.f, 0.f};\n} // Anonymous namespace\n\n// region Fire Flea Death Camera\n\nzeus::CColor CFireFlea::CDeathCameraEffect::sCurrentFadeColor = zeus::skClear;\n\nCFireFlea::CDeathCameraEffect::CDeathCameraEffect(TUniqueId uid, TAreaId areaId, std::string_view name)\n: CEntity(uid, CEntityInfo(areaId, CEntity::NullConnectionList), true, name) {}\n\nvoid CFireFlea::CDeathCameraEffect::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CFireFlea::CDeathCameraEffect::PreThink(float dt, CStateManager& mgr) {\n  auto& filterPass = mgr.GetCameraFilterPass(5);\n  u32 r5 = x34_ + x38_;\n  u32 r8 = r5 + x3c_;\n  u32 r31 = r8 + x40_;\n  if (x44_ >= x34_ && x44_ <= r5) {\n    sCurrentFadeColor += zeus::CColor::lerp(skStartFadeColor, skEndFadeColor, x34_ - x44_);\n    filterPass.SetFilter(EFilterType::Blend, EFilterShape::Fullscreen, 0.f, sCurrentFadeColor, CAssetId());\n  } else if (x44_ >= r8 && x44_ <= r31) {\n    sCurrentFadeColor += zeus::CColor::lerp(skEndFadeColor, skStartFadeColor, r8 - x44_);\n    filterPass.SetFilter(EFilterType::Blend, EFilterShape::Fullscreen, 0.f, sCurrentFadeColor, CAssetId());\n  } else if (x44_ > r5) {\n    sCurrentFadeColor = skEndFadeColor;\n    filterPass.SetFilter(EFilterType::Blend, EFilterShape::Fullscreen, 0.f, sCurrentFadeColor, CAssetId());\n  }\n\n  if (r31 == x44_) {\n    filterPass.DisableFilter(0.f);\n    mgr.FreeScriptObject(GetUniqueId());\n    x44_ = 0;\n  } else\n    x44_++;\n\n  if (mgr.GetPlayerState()->GetActiveVisor(mgr) != CPlayerState::EPlayerVisor::Thermal)\n    filterPass.DisableFilter(0.f);\n}\n\nvoid CFireFlea::CDeathCameraEffect::Think(float dt, CStateManager& mgr) { sCurrentFadeColor = zeus::skClear; }\n\n// endregion\n\ns32 CFireFlea::sLightIdx = 0;\n\nCFireFlea::CFireFlea(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                     CModelData&& mData, const CActorParameters& actParms, const CPatternedInfo& pInfo, float f1)\n: CPatterned(ECharacter::FireFlea, uid, name, EFlavorType::Zero, info, xf, std::move(mData), pInfo,\n             EMovementType::Flyer, EColliderType::One, EBodyType::Flyer, actParms, EKnockBackVariant::Small)\n, x56c_(f1)\n, xd8c_pathFind(nullptr, 3, pInfo.GetPathfindingIndex(), 1.f, 1.f) {\n  CMaterialFilter filter = GetMaterialFilter();\n  filter.ExcludeList().Add(EMaterialTypes::Character);\n  SetMaterialFilter(filter);\n\n  GetModelData()->GetAnimationData()->SetParticleLightIdx(sLightIdx);\n  ++sLightIdx;\n}\n\nvoid CFireFlea::Dead(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg != EStateMsg::Activate || mgr.GetPlayerState()->GetActiveVisor(mgr) != CPlayerState::EPlayerVisor::Thermal)\n    return;\n\n  mgr.AddObject(new CDeathCameraEffect(mgr.AllocateUniqueId(), GetAreaIdAlways(), \"\"sv));\n}\n\nbool CFireFlea::Delay(CStateManager&, float arg) { return x330_stateMachineState.GetTime() > 0.5f; }\n\nvoid CFireFlea::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CFireFlea::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  CPatterned::AcceptScriptMsg(msg, uid, mgr);\n\n  if (msg == EScriptObjectMessage::Registered) {\n    x450_bodyController->Activate(mgr);\n  } else if (msg == EScriptObjectMessage::InitializedInArea) {\n    xd8c_pathFind.SetArea(mgr.GetWorld()->GetAreaAlways(GetAreaIdAlways())->GetPostConstructed()->x10bc_pathArea);\n    xd8c_pathFind.SetPadding(50.0f);\n  }\n}\n\nbool CFireFlea::InPosition(CStateManager& mgr, float dt) {\n  if (x2dc_destObj == kInvalidUniqueId)\n    return false;\n  return (xd80_targetPos - GetTranslation()).magnitude() < 25.f;\n}\n\nbool CFireFlea::HearShot(metaforce::CStateManager& mgr, float arg) {\n  x570_nearList.clear();\n  mgr.BuildNearList(x570_nearList, zeus::CAABox(GetTranslation() - 10.f, GetTranslation() + 10.f),\n                    CMaterialFilter::MakeInclude({EMaterialTypes::Projectile}), nullptr);\n\n  return !x570_nearList.empty();\n}\n\nvoid CFireFlea::TargetPatrol(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    CPatterned::Patrol(mgr, msg, arg);\n    UpdateDest(mgr);\n    xd80_targetPos = x2e0_destPos;\n  } else if (msg == EStateMsg::Update) {\n    if (auto* pathFind = GetSearchPath()) {\n      if (pathFind->GetResult() != CPathFindSearch::EResult::Success) {\n        zeus::CVector3f closestPoint = zeus::skZero3f;\n        if (pathFind->FindClosestReachablePoint(GetTranslation(), closestPoint) == CPathFindSearch::EResult::Success) {\n          zeus::CVector3f delta = AdjustMovementVec(mgr, x45c_steeringBehaviors.Arrival(*this, xd80_targetPos, 5.f));\n          x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(delta, {}, 1.f));\n        }\n      } else {\n        PathFind(mgr, msg, arg);\n      }\n    } else {\n      x450_bodyController->GetCommandMgr().DeliverCmd(\n          CBCLocomotionCmd(x45c_steeringBehaviors.Arrival(*this, xd80_targetPos, 5.f), {}, 1.f));\n    }\n  }\n}\n\nzeus::CVector3f CFireFlea::AdjustMovementVec(CStateManager& mgr, const zeus::CVector3f& forward) const {\n  const float mag = forward.magnitude();\n  if (mag <= 0.f) {\n    return {};\n  }\n\n  const CRayCastResult res = mgr.RayStaticIntersection(GetTranslation(), forward.normalized(), 1.f,\n                                                       CMaterialFilter::MakeInclude({EMaterialTypes::Solid}));\n  if (res.IsInvalid() && !MoveTooCloseToWater(mgr, forward.normalized())) {\n    return forward;\n  }\n\n  const zeus::CVector3f right = forward.normalized().cross(zeus::skUp).normalized();\n  const CRayCastResult res1 =\n      mgr.RayStaticIntersection(GetTranslation(), right, 1.f, CMaterialFilter::MakeInclude({EMaterialTypes::Solid}));\n  if (res1.IsInvalid()) {\n    return mag * right;\n  }\n\n  const zeus::CVector3f left = -right;\n  const CRayCastResult res2 =\n      mgr.RayStaticIntersection(GetTranslation(), left, 1.f, CMaterialFilter::MakeInclude({EMaterialTypes::Solid}));\n  if (res2.IsInvalid()) {\n    return mag * left;\n  }\n\n  const zeus::CVector3f up = right.cross(forward.normalized());\n  const CRayCastResult res3 =\n      mgr.RayStaticIntersection(GetTranslation(), up, 1.f, CMaterialFilter::MakeInclude({EMaterialTypes::Solid}));\n  if (res3.IsInvalid()) {\n    return mag * up;\n  }\n\n  const zeus::CVector3f down = -up;\n  const CRayCastResult res4 =\n      mgr.RayStaticIntersection(GetTranslation(), down, 1.f, CMaterialFilter::MakeInclude({EMaterialTypes::Solid}));\n  if (res4.IsInvalid()) {\n    return mag * down;\n  }\n\n  return -forward;\n}\n\nbool CFireFlea::MoveTooCloseToWater(const CStateManager& mgr, const zeus::CVector3f& dir) const {\n  EntityList nearList;\n  mgr.BuildNearList(nearList, GetTranslation(), dir, 2.f, CMaterialFilter::skPassEverything, nullptr);\n\n  for (const auto& id : nearList) {\n    if (TCastToConstPtr<CScriptWater>(mgr.GetObjectById(id)))\n      return true;\n  }\n\n  return false;\n}\n\nvoid CFireFlea::Patrol(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (!zeus::close_enough(x310_moveVec, {}))\n    x310_moveVec.normalize();\n\n  x310_moveVec = AdjustMovementVec(mgr, x310_moveVec);\n  CPatterned::Patrol(mgr, msg, arg);\n  if (x2d8_patrolState == EPatrolState::Done)\n    mgr.FreeScriptObject(GetUniqueId());\n}\n\nvoid CFireFlea::Flee(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Lurk);\n  } else if (msg == EStateMsg::Update) {\n    if (x570_nearList.empty()) {\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(AdjustMovementVec(mgr, xd74_), {}, 1.f));\n    } else {\n      for (TUniqueId id : x570_nearList) {\n        if (const CActor* act = static_cast<const CActor*>(mgr.GetObjectById(id))) {\n          zeus::CVector3f fleeDirection = x45c_steeringBehaviors.Flee(*this, act->GetTranslation());\n          x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(AdjustMovementVec(mgr, fleeDirection), {}, 1.f));\n        }\n      }\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n  }\n}\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CFireFlea.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/World/CPathFindSearch.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n\n#include <zeus/CColor.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce::MP1 {\nclass CFireFlea : public CPatterned {\n  class CDeathCameraEffect : public CEntity {\n    u32 x34_ = 13;\n    u32 x38_ = 5;\n    u32 x3c_ = 60;\n    u32 x40_ = 190;\n    u32 x44_ = 0;\n\n  public:\n    DEFINE_ENTITY\n    static zeus::CColor sCurrentFadeColor;\n    CDeathCameraEffect(TUniqueId, TAreaId, std::string_view);\n\n    void Accept(IVisitor&) override;\n    void PreThink(float, CStateManager&) override;\n    void Think(float, CStateManager&) override;\n  };\n  float x568_ = 1.f;\n  float x56c_;\n  EntityList x570_nearList;\n  zeus::CVector3f xd74_;\n  zeus::CVector3f xd80_targetPos;\n  CPathFindSearch xd8c_pathFind;\n\n  static s32 sLightIdx;\n  zeus::CVector3f AdjustMovementVec(CStateManager& mgr, const zeus::CVector3f& forward) const;\n  bool MoveTooCloseToWater(const CStateManager&, const zeus::CVector3f& dir) const;\n\npublic:\n  DEFINE_PATTERNED(FireFlea);\n\n  CFireFlea(TUniqueId, std::string_view, const CEntityInfo&, const zeus::CTransform&, CModelData&&,\n            const CActorParameters&, const CPatternedInfo&, float);\n\n  void Accept(IVisitor&) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) override;\n  void Dead(CStateManager&, EStateMsg msg, float dt) override;\n  bool Delay(CStateManager&, float arg) override;\n  bool InPosition(CStateManager& mgr, float dt) override;\n  bool HearShot(CStateManager&, float) override;\n  void TargetPatrol(CStateManager&, EStateMsg, float) override;\n  void Patrol(CStateManager&, EStateMsg, float) override;\n  void Flee(CStateManager&, EStateMsg, float) override;\n  CPathFindSearch* GetSearchPath() override { return &xd8c_pathFind; }\n};\n} // namespace metaforce::MP1"
  },
  {
    "path": "Runtime/MP1/World/CFlaahgra.cpp",
    "content": "#include \"Runtime/MP1/World/CFlaahgra.hpp\"\n\n#include \"Runtime/CDependencyGroup.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Camera/CFirstPersonCamera.hpp\"\n#include \"Runtime/Character/CBoneTracking.hpp\"\n#include \"Runtime/Collision/CCollisionActor.hpp\"\n#include \"Runtime/Collision/CCollisionActorManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/MP1/World/CFlaahgraProjectile.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CScriptWaypoint.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n#include \"Runtime/World/ScriptLoader.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce::MP1 {\nconstexpr zeus::CColor skFlaahgraDamageColor{0.5f, 0.5f, 0.f, 1.f};\nconstexpr zeus::CColor skUnkColor{0.5f, 0.f, 0.f, 1.f};\nconstexpr zeus::CVector3f skUnkVec1{0.5f, 7.f, 0.f};\nconstexpr zeus::CVector3f skUnkVec2{12.f, 12.f, 12.f};\n\nconstexpr std::array<SJointInfo, 3> skLeftArmJointList{{\n    {\"L_elbow\", \"L_blade\", 0.6f, 1.f},\n    {\"L_blade\", \"L_CLAW_LCTR\", 0.6f, 1.f},\n    {\"L_CLAW_LCTR\", \"L_CLAW_END_LCTR\", 0.6f, 1.f},\n}};\n\nconstexpr std::array<SJointInfo, 3> skRightArmJointList{{\n    {\"R_elbow\", \"R_blade\", 0.6f, 1.f},\n    {\"R_blade\", \"R_CLAW_LCTR\", 0.6f, 1.f},\n    {\"R_CLAW_LCTR\", \"R_CLAW_END_LCTR\", 0.6f, 1.f},\n}};\n\nconstexpr std::array<SSphereJointInfo, 5> skSphereJointList{{\n    {\"Head_1\", 1.5f},\n    {\"Spine_2\", 1.5f},\n    {\"Spine_4\", 1.5f},\n    {\"Spine_6\", 1.5f},\n    {\"Collar\", 1.5f},\n}};\n\nCFlaahgraData::CFlaahgraData(CInputStream& in)\n: x0_(in.ReadFloat())\n, x4_(in.ReadFloat())\n, x8_(in.ReadFloat())\n, xc_faintDuration(in.ReadFloat())\n, x10_(in)\n, x78_(in)\n, x7c_(in)\n, x98_(in)\n, x9c_(in)\n, xb8_plantsParticleGenDescId(in)\n, xbc_(in)\n, xd8_(ScriptLoader::LoadActorParameters(in))\n, x140_(in.ReadFloat())\n, x144_(in.ReadFloat())\n, x148_(in.ReadFloat())\n, x14c_animationParameters(ScriptLoader::LoadAnimationParameters(in))\n, x158_(in) {}\n\nCFlaahgraRenderer::CFlaahgraRenderer(TUniqueId uid, TUniqueId owner, std::string_view name, const CEntityInfo& info,\n                                     const zeus::CTransform& xf)\n: CActor(uid, true, name, info, xf, CModelData::CModelDataNull(), CMaterialList(EMaterialTypes::Character),\n         CActorParameters::None(), kInvalidUniqueId)\n, xe8_owner(owner) {}\n\nvoid CFlaahgraRenderer::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) {\n  if (const auto* act = static_cast<const CActor*>(mgr.GetObjectById(xe8_owner))) {\n    if (act->HasModelData() && (act->GetModelData()->HasAnimData() || act->GetModelData()->HasNormalModel())) {\n      act->GetModelData()->RenderParticles(frustum);\n    }\n  }\n}\n\nvoid CFlaahgraRenderer::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nCFlaahgra::CFlaahgra(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                     const CAnimRes& animRes, const CPatternedInfo& pInfo, const CActorParameters& actParms,\n                     CFlaahgraData flaahgraData)\n: CPatterned(ECharacter::Flaahgra, uid, name, EFlavorType::Zero, info, xf, CModelData::CModelDataNull(), pInfo,\n             EMovementType::Flyer, EColliderType::One, EBodyType::Restricted, actParms, EKnockBackVariant::Large)\n, x56c_data(std::move(flaahgraData))\n, x6d4_plantsParticleGenDesc(g_SimplePool->GetObj({SBIG('PART'), x56c_data.xb8_plantsParticleGenDescId}))\n, x6dc_normalProjectileInfo(x56c_data.x78_, x56c_data.x7c_)\n, x704_bigStrikeProjectileInfo(x56c_data.x98_, x56c_data.x9c_)\n, x7dc_halfContactDamage(x404_contactDamage)\n, x820_aimPosition(xf.origin)\n, x8a0_(xf.frontVector())\n, x8ac_(animRes) {\n  xe7_30_doTargetDistanceTest = false;\n  x6dc_normalProjectileInfo.Token().Lock();\n  x704_bigStrikeProjectileInfo.Token().Lock();\n  x7dc_halfContactDamage.SetDamage(0.5f * x7dc_halfContactDamage.GetDamage());\n  SetActorLights(actParms.GetLightParameters().MakeActorLights());\n  x90_actorLights->SetCastShadows(false);\n  x90_actorLights->SetMaxAreaLights(2);\n  x90_actorLights->SetHasAreaLights(x90_actorLights->GetMaxAreaLights() > 0);\n  x90_actorLights->SetMaxDynamicLights(1);\n  x460_knockBackController.SetAutoResetImpulse(false);\n  x460_knockBackController.SetEnableLaggedBurnDeath(false);\n  x430_damageColor = skFlaahgraDamageColor;\n  LoadDependencies(x56c_data.x158_);\n\n  float curAngle = zeus::degToRad(17.5f);\n  while (curAngle < zeus::degToRad(360.f)) {\n    x82c_.push_back(GetTransform().rotate(zeus::CVector3f(std::cos(curAngle), std::sin(curAngle), 0.f)));\n    x860_.push_back(GetTransform().rotate(\n        zeus::CVector3f(std::cos(curAngle + zeus::degToRad(45.f)), std::sin(curAngle + zeus::degToRad(45.f)), 0.f)));\n\n    curAngle += zeus::degToRad(90.f);\n  }\n}\n\nvoid CFlaahgra::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CFlaahgra::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  switch (msg) {\n  case EScriptObjectMessage::InitializedInArea: {\n    if (!x8e4_25_loading && !x8e4_24_loaded) {\n      mgr.GetWorld()->GetArea(GetAreaIdAlways())->GetPostConstructed()->x113c_playerActorsLoading++;\n      x8e4_25_loading = true;\n    }\n\n    GetMirrorWaypoints(mgr);\n    break;\n  }\n  case EScriptObjectMessage::Activate: {\n    GatherAssets(mgr);\n    if (x8e5_27_) {\n      break;\n    }\n\n    SetupCollisionManagers(mgr);\n    x6d0_rendererId = mgr.AllocateUniqueId();\n    mgr.AddObject(new CFlaahgraRenderer(x6d0_rendererId, GetUniqueId(), \"Flaahgra Renderer\"sv,\n                                        CEntityInfo(GetAreaIdAlways(), NullConnectionList), GetTransform()));\n\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Crouch);\n    x450_bodyController->Activate(mgr);\n    x8e5_27_ = true;\n    break;\n  }\n  case EScriptObjectMessage::Deleted: {\n    if (!x8e5_27_) {\n      break;\n    }\n\n    x79c_leftArmCollision->Destroy(mgr);\n    x7a0_rightArmCollision->Destroy(mgr);\n    x7a4_sphereCollision->Destroy(mgr);\n    mgr.FreeScriptObject(x6d0_rendererId);\n    x6d0_rendererId = kInvalidUniqueId;\n    x8e5_27_ = false;\n    break;\n  }\n  case EScriptObjectMessage::Touched: {\n    if (HealthInfo(mgr)->GetHP() <= 0.f) {\n      break;\n    }\n\n    if (TCastToConstPtr<CCollisionActor> colAct = mgr.ObjectById(uid)) {\n      if (colAct->GetLastTouchedObject() == mgr.GetPlayer().GetUniqueId() && x420_curDamageRemTime <= 0.f) {\n        CDamageInfo contactDamage = GetContactDamage();\n        if (x7a8_ == 4) {\n          contactDamage = x7dc_halfContactDamage;\n        } else if (!sub801ae670()) {\n          contactDamage.SetDamage(0.5f * contactDamage.GetDamage());\n        }\n\n        if (x788_stage >= 2) {\n          contactDamage.SetDamage(1.33f * contactDamage.GetDamage());\n        }\n\n        mgr.ApplyDamage(GetUniqueId(), mgr.GetPlayer().GetUniqueId(), GetUniqueId(), contactDamage,\n                        CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), {});\n        x420_curDamageRemTime = x424_damageWaitTime;\n      }\n    }\n    break;\n  }\n  case EScriptObjectMessage::Damage: {\n    if (HealthInfo(mgr)->GetHP() <= 0.f) {\n      break;\n    }\n\n    if (!IsSphereCollider(uid)) {\n      break;\n    }\n\n    if (TCastToConstPtr<CCollisionActor> colAct = mgr.GetObjectById(uid)) {\n      if (TCastToConstPtr<CGameProjectile> proj = mgr.GetObjectById(colAct->GetLastTouchedObject())) {\n        if (x780_ != 3) {\n          break;\n        }\n        if (!IsDizzy(mgr, 0.f) && x450_bodyController->HasBodyState(pas::EAnimationState::LoopReaction)) {\n          TakeDamage({}, 0.f);\n\n          if (x56c_data.x140_ - proj->GetDamageInfo().GetDamage() <= x810_) {\n            x450_bodyController->GetCommandMgr().DeliverCmd(CBCLoopHitReactionCmd(pas::EReactionType::One));\n          } else if (uid == x80c_headActor &&\n                     (proj->GetDamageInfo().GetWeaponMode().IsCharged() ||\n                      proj->GetDamageInfo().GetWeaponMode().IsComboed() ||\n                      proj->GetDamageInfo().GetWeaponMode().GetType() == EWeaponType::Missile)) {\n            x450_bodyController->GetCommandMgr().DeliverCmd(\n                CBCKnockBackCmd(-GetTransform().frontVector(), pas::ESeverity::One));\n          }\n        } else {\n          if (x8e5_30_) {\n            TakeDamage({}, 0.f);\n          }\n\n          if (uid == x80c_headActor &&\n              (proj->GetDamageInfo().GetWeaponMode().IsCharged() || proj->GetDamageInfo().GetWeaponMode().IsComboed() ||\n               proj->GetDamageInfo().GetWeaponMode().GetType() == EWeaponType::Missile)) {\n            x450_bodyController->GetCommandMgr().DeliverCmd(CBCAdditiveFlinchCmd(1.f));\n          }\n        }\n      }\n    }\n    break;\n  }\n  case EScriptObjectMessage::Decrement: {\n    x780_ = 0;\n    break;\n  }\n  case EScriptObjectMessage::Close: {\n    sub801ae980(mgr);\n    break;\n  }\n  case EScriptObjectMessage::Start: {\n    x8e4_31_ = false;\n    break;\n  }\n  case EScriptObjectMessage::Stop: {\n    x8e4_31_ = true;\n    break;\n  }\n  case EScriptObjectMessage::Play: {\n    x7d0_hitSomethingTime = 3.f;\n    x8e5_24_ = true;\n    break;\n  }\n  case EScriptObjectMessage::Action: {\n    if (TCastToConstPtr<CGameProjectile>(mgr.GetObjectById(uid))) {\n      x7f8_ = x788_stage;\n    }\n    break;\n  }\n  case EScriptObjectMessage::SetToMax: {\n    x7d4_faintTime = 0.f;\n    break;\n  }\n  case EScriptObjectMessage::Reset: {\n    x8e5_28_ = true;\n    break;\n  }\n  default:\n    break;\n  }\n  CPatterned::AcceptScriptMsg(msg, uid, mgr);\n}\n\nvoid CFlaahgra::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) {\n  if ((!GetModelData()->HasAnimData() && !GetModelData()->HasNormalModel()) || xe4_30_outOfFrustum) {\n    return;\n  }\n\n  if (CanRenderUnsorted(mgr)) {\n    Render(mgr);\n  } else {\n    EnsureRendered(mgr);\n  }\n}\n\nvoid CFlaahgra::Death(CStateManager& mgr, const zeus::CVector3f& dir, EScriptObjectState state) {\n  if (!x400_25_alive) {\n    return;\n  }\n\n  x330_stateMachineState.SetState(mgr, *this, GetStateMachine(), \"Dead\"sv);\n  if (x450_bodyController->GetPercentageFrozen() > 0.f) {\n    x450_bodyController->UnFreeze();\n  }\n\n  x400_25_alive = false;\n}\n\nvoid CFlaahgra::DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) {\n  switch (type) {\n  case EUserEventType::Projectile: {\n    const auto lctrXf = GetLctrTransform(node.GetLocatorName());\n    const auto attackTargetPos = GetAttackTargetPos(mgr);\n    if (x7b4_ == 0 || x7b4_ == 1) {\n      if (x72c_projectilesCreated == 0) {\n        x730_projectileDirs.clear();\n        auto& player = mgr.GetPlayer();\n        const auto interceptPos =\n            GetProjectileInfo()->PredictInterceptPos(lctrXf.origin, attackTargetPos, player, false, dt);\n        x730_projectileDirs.push_back(interceptPos);\n        const auto& xf = GetTransform();\n        auto basis = xf.basis;\n        const auto rot = zeus::CMatrix3f::RotateZ(zeus::degToRad(x7b4_ == 1 ? -4.f : 4.f));\n        for (int i = 1; i < x730_projectileDirs.capacity(); ++i) {\n          basis = basis * rot;\n          const auto vec = basis * xf.transposeRotate(interceptPos - xf.origin);\n          x730_projectileDirs.push_back(\n              zeus::CVector3f{xf.origin.x() + vec.x(), xf.origin.y() + vec.y(), interceptPos.z()});\n        }\n        if (x72c_projectilesCreated > -1 && x730_projectileDirs.size() > x72c_projectilesCreated) {\n          CreateProjectile(zeus::lookAt(lctrXf.origin, x730_projectileDirs[x72c_projectilesCreated]), mgr);\n          x72c_projectilesCreated++;\n        }\n      }\n    } else {\n      CPlayer& player = mgr.GetPlayer();\n      const auto interceptPos =\n          GetProjectileInfo()->PredictInterceptPos(lctrXf.origin, attackTargetPos, player, false, dt);\n      auto target = interceptPos;\n      auto dir = interceptPos - lctrXf.origin;\n      dir.z() = 0.f;\n      const auto frontVec = GetTransform().frontVector();\n      if (zeus::CVector3f::getAngleDiff(frontVec, dir) > zeus::degToRad(45.f)) {\n        if (dir.canBeNormalized()) {\n          target = lctrXf.origin +\n                   (dir.magnitude() * zeus::CVector3f::slerp(frontVec, dir.normalized(), zeus::degToRad(45.f)));\n        } else {\n          target = lctrXf.origin + dir.magnitude() * lctrXf.frontVector();\n        }\n      }\n      CreateProjectile(zeus::lookAt(lctrXf.origin, target), mgr);\n    }\n    return;\n  }\n  case EUserEventType::BeginAction: {\n    x8e4_26_ = true;\n    x7c4_actionDuration = GetEndActionTime();\n    break;\n  }\n  case EUserEventType::ScreenShake: {\n    RattlePlayer(mgr, GetLctrTransform(node.GetLocatorName()).origin);\n    return;\n  }\n  case EUserEventType::AlignTargetRot: {\n    if (x77c_targetMirrorWaypointId == kInvalidUniqueId) {\n      break;\n    }\n    if (TCastToPtr<CScriptWaypoint> wp = mgr.ObjectById(x77c_targetMirrorWaypointId)) {\n      mgr.SendScriptMsg(wp, GetUniqueId(), EScriptObjectMessage::Arrived);\n      if (x7f8_ > 0) {\n        --x7f8_;\n      }\n    }\n    break;\n  }\n  case EUserEventType::GenerateEnd: {\n    zeus::CVector3f boneOrigin = GetLctrTransform(node.GetLocatorName()).origin;\n    zeus::CVector3f aimPos = mgr.GetPlayer().GetAimPosition(mgr, 0.f);\n    boneOrigin.z() = zeus::max<float>(aimPos.z(), boneOrigin.z());\n    zeus::CTransform xf = GetTransform();\n    xf.origin = boneOrigin;\n\n    CRayCastResult res = mgr.RayStaticIntersection(xf.origin, zeus::skDown, 100.f,\n                                                   CMaterialFilter::MakeInclude({EMaterialTypes::Floor}));\n\n    if (res.IsValid()) {\n      xf.origin = res.GetPoint();\n      auto* plants = new CFlaahgraPlants(x6d4_plantsParticleGenDesc, x56c_data.xd8_, mgr.AllocateUniqueId(),\n                                         GetAreaIdAlways(), GetUniqueId(), xf, x56c_data.xbc_, {5.f, 10.f, 5.f});\n      mgr.AddObject(plants);\n      mgr.SetActorAreaId(*plants, GetAreaIdAlways());\n      x7cc_generateEndCooldown = 8.f;\n    }\n    x8e4_27_ = true;\n    break;\n  }\n  case EUserEventType::ObjectDrop: {\n    SendScriptMsgs(EScriptObjectState::Modify, mgr, EScriptObjectMessage::None);\n    break;\n  }\n  default:\n    break;\n  }\n\n  CPatterned::DoUserAnimEvent(mgr, node, type, dt);\n}\n\nvoid CFlaahgra::LoadDependencies(CAssetId dgrpId) {\n  if (!dgrpId.IsValid()) {\n    ResetModelDataAndBodyController();\n    x8e4_24_loaded = true;\n    return;\n  }\n\n  x8c8_depGroup = {g_SimplePool->GetObj({SBIG('DGRP'), dgrpId})};\n  x8c8_depGroup->Lock();\n}\n\nvoid CFlaahgra::ResetModelDataAndBodyController() {\n  SetModelData(std::make_unique<CModelData>(x8ac_));\n  _CreateShadow();\n  CreateShadow(true);\n  x94_simpleShadow->SetAlwaysCalculateRadius(false);\n  BuildBodyController(EBodyType::Restricted);\n  x6cc_boneTracking =\n      std::make_unique<CBoneTracking>(*GetModelData()->GetAnimationData(), \"Head_1\"sv, zeus::degToRad(80.f),\n                                      zeus::degToRad(180.f), EBoneTrackingFlags::None);\n}\n\nvoid CFlaahgra::GatherAssets(CStateManager& mgr) {\n  if (x8e4_24_loaded) {\n    return;\n  }\n\n  x8c8_depGroup->GetObj();\n  LoadTokens(mgr);\n\n  if (x8e4_24_loaded) {\n    return;\n  }\n\n  for (const CToken& tok : x8d4_tokens) {\n    tok.GetObj();\n  }\n\n  FinalizeLoad(mgr);\n}\n\nvoid CFlaahgra::LoadTokens(CStateManager& mgr) {\n  if (!x8d4_tokens.empty()) {\n    for (const CToken& tok : x8d4_tokens) {\n      if (!tok.IsLoaded()) {\n        return;\n      }\n    }\n\n    FinalizeLoad(mgr);\n  }\n\n  if (!x8c8_depGroup) {\n    return;\n  }\n\n  TToken<CDependencyGroup> depGroup = *x8c8_depGroup;\n  if (depGroup->GetObjectTagVector().empty()) {\n    FinalizeLoad(mgr);\n    return;\n  }\n\n  if (x8d4_tokens.empty()) {\n    x8d4_tokens.reserve(depGroup->GetObjectTagVector().size());\n\n    for (const auto& tag : depGroup->GetObjectTagVector()) {\n      CToken token = g_SimplePool->GetObj({tag.type, tag.id});\n      token.Lock();\n      x8d4_tokens.push_back(token);\n    }\n  }\n}\n\nvoid CFlaahgra::FinalizeLoad(CStateManager& mgr) {\n  x8e4_24_loaded = true;\n  if (x8e4_25_loading) {\n    mgr.GetWorld()->GetArea(GetAreaIdAlways())->GetPostConstructed()->x113c_playerActorsLoading--;\n    x8e4_25_loading = false;\n  }\n\n  ResetModelDataAndBodyController();\n}\n\nvoid CFlaahgra::Think(float dt, CStateManager& mgr) {\n  if (!GetActive()) {\n    return;\n  }\n\n  CPatterned::Think(dt, mgr);\n  x6cc_boneTracking->Update(dt);\n  UpdateCollisionManagers(dt, mgr);\n  x6cc_boneTracking->PreRender(mgr, *GetModelData()->GetAnimationData(), GetTransform(), GetModelData()->GetScale(),\n                               *x450_bodyController);\n  UpdateSmallScaleReGrowth(dt);\n  UpdateHealthInfo(mgr);\n  UpdateAimPosition(mgr, dt);\n  x15c_force = {};\n  x168_impulse = {};\n}\n\nvoid CFlaahgra::PreThink(float dt, CStateManager& mgr) {\n  if (!x8e4_24_loaded) {\n    LoadTokens(mgr);\n  }\n\n  CPatterned::PreThink(dt, mgr);\n}\n\nvoid CFlaahgra::GetMirrorWaypoints(CStateManager& mgr) {\n  x770_mirrorWaypoints.clear();\n\n  for (const SConnection& conn : x20_conns) {\n    if (conn.x0_state != EScriptObjectState::Modify || conn.x4_msg != EScriptObjectMessage::Follow) {\n      continue;\n    }\n    TUniqueId uid = mgr.GetIdForScript(conn.x8_objId);\n    if (TCastToConstPtr<CScriptWaypoint>(mgr.GetObjectById(uid))) {\n      x770_mirrorWaypoints.push_back(uid);\n    }\n  }\n}\n\nvoid CFlaahgra::AddCollisionList(const SJointInfo* joints, size_t count,\n                                 std::vector<CJointCollisionDescription>& outJoints) {\n  const CAnimData* animData = GetModelData()->GetAnimationData();\n\n  for (size_t i = 0; i < count; ++i) {\n    const auto& joint = joints[i];\n    const CSegId from = animData->GetLocatorSegId(joint.from);\n    const CSegId to = animData->GetLocatorSegId(joint.to);\n\n    if (to.IsInvalid() || from.IsInvalid()) {\n      continue;\n    }\n\n    outJoints.push_back(CJointCollisionDescription::SphereSubdivideCollision(\n        to, from, joint.radius, joint.separation, CJointCollisionDescription::EOrientationType::One, joint.from, 10.f));\n  }\n}\n\nvoid CFlaahgra::AddSphereCollisionList(const SSphereJointInfo* joints, size_t count,\n                                       std::vector<CJointCollisionDescription>& outJoints) {\n  const CAnimData* animData = GetModelData()->GetAnimationData();\n\n  for (size_t i = 0; i < count; ++i) {\n    const auto& joint = joints[i];\n    const CSegId seg = animData->GetLocatorSegId(joint.name);\n\n    if (seg.IsInvalid()) {\n      continue;\n    }\n\n    outJoints.push_back(CJointCollisionDescription::SphereCollision(seg, joint.radius, joint.name, 10.f));\n  }\n}\n\nvoid CFlaahgra::SetupHealthInfo(CStateManager& mgr) {\n  x7fc_sphereColliders.clear();\n\n  for (u32 i = 0; i < x7a4_sphereCollision->GetNumCollisionActors(); ++i) {\n    const auto& desc = x7a4_sphereCollision->GetCollisionDescFromIndex(i);\n    TUniqueId uid = desc.GetCollisionActorId();\n    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(uid)) {\n      *colAct->HealthInfo(mgr) = *HealthInfo(mgr);\n      if (desc.GetName() == \"Head_1\"sv) {\n        x80c_headActor = uid;\n      }\n\n      x7fc_sphereColliders.push_back(uid);\n    }\n  }\n\n  x818_curHp = HealthInfo(mgr)->GetHP();\n}\n\nvoid CFlaahgra::SetupCollisionManagers(CStateManager& mgr) {\n  zeus::CVector3f oldScale = GetModelData()->GetScale();\n  GetModelData()->SetScale(zeus::CVector3f{x56c_data.x4_ * 1.f});\n\n  std::vector<CJointCollisionDescription> leftArmJointList;\n  leftArmJointList.reserve(skLeftArmJointList.size());\n  AddCollisionList(skLeftArmJointList.data(), skLeftArmJointList.size(), leftArmJointList);\n  x79c_leftArmCollision =\n      std::make_unique<CCollisionActorManager>(mgr, GetUniqueId(), GetAreaIdAlways(), leftArmJointList, true);\n  SetMaterialProperties(x79c_leftArmCollision, mgr);\n\n  std::vector<CJointCollisionDescription> rightArmJointList;\n  rightArmJointList.reserve(skRightArmJointList.size());\n  AddCollisionList(skRightArmJointList.data(), skRightArmJointList.size(), rightArmJointList);\n  x7a0_rightArmCollision =\n      std::make_unique<CCollisionActorManager>(mgr, GetUniqueId(), GetAreaIdAlways(), rightArmJointList, true);\n  SetMaterialProperties(x7a0_rightArmCollision, mgr);\n\n  std::vector<CJointCollisionDescription> sphereJointList;\n  sphereJointList.reserve(skSphereJointList.size());\n  AddSphereCollisionList(skSphereJointList.data(), skSphereJointList.size(), sphereJointList);\n  x7a4_sphereCollision =\n      std::make_unique<CCollisionActorManager>(mgr, GetUniqueId(), GetAreaIdAlways(), sphereJointList, true);\n  SetMaterialProperties(x7a4_sphereCollision, mgr);\n\n  SetupHealthInfo(mgr);\n  SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(\n      {EMaterialTypes::Solid},\n      {EMaterialTypes::CollisionActor, EMaterialTypes::AIPassthrough, EMaterialTypes::Player}));\n  AddMaterial(EMaterialTypes::ProjectilePassthrough, EMaterialTypes::Target, EMaterialTypes::Orbit, mgr);\n  RemoveMaterial(EMaterialTypes::Solid, mgr);\n  GetModelData()->SetScale(oldScale);\n  x7a4_sphereCollision->AddMaterial(mgr, {EMaterialTypes::AIJoint, EMaterialTypes::CameraPassthrough});\n  x79c_leftArmCollision->AddMaterial(mgr, {EMaterialTypes::AIJoint, EMaterialTypes::CameraPassthrough});\n  x7a0_rightArmCollision->AddMaterial(mgr, {EMaterialTypes::AIJoint, EMaterialTypes::CameraPassthrough});\n}\n\nvoid CFlaahgra::sub801ae980(CStateManager& mgr) {\n  HealthInfo(mgr)->SetHP(HealthInfo(mgr)->GetHP() - x56c_data.x8_);\n  x7d4_faintTime = x56c_data.xc_faintDuration;\n  x8e4_29_getup = true;\n  x7d8_ = 0.f;\n  x430_damageColor = skUnkColor;\n  ++x788_stage;\n}\n\nvoid CFlaahgra::CalculateFallDirection() {\n  const auto front = GetTransform().frontVector();\n  const auto right = x7ac_ ? GetTransform().rightVector() : -GetTransform().rightVector();\n  x894_fallDirection = right;\n\n  const rstl::reserved_vector<zeus::CVector3f, 4>& vec = x7ac_ ? x82c_ : x860_;\n\n  float curDist = FLT_MIN;\n  for (const zeus::CVector3f& v : vec) {\n    if (right.dot(v) < 0.f) {\n      continue;\n    }\n\n    float dist = front.dot(v);\n    if (dist > curDist) {\n      x894_fallDirection = v;\n      curDist = dist;\n    }\n  }\n}\n\nbool CFlaahgra::ShouldAttack(CStateManager& mgr, float /*unused*/) {\n  CPlayer& player = mgr.GetPlayer();\n  if (x788_stage <= 0 || x788_stage > 3 || x7c0_ > 0.f || player.IsInWaterMovement() || x8e4_31_) {\n    return false;\n  }\n\n  zeus::CVector2f diff = player.GetTranslation().toVec2f() - GetTranslation().toVec2f();\n\n  float dist = diff.magSquared();\n\n  float minSq = x2fc_minAttackRange * x2fc_minAttackRange;\n  float maxSq = x300_maxAttackRange * x300_maxAttackRange;\n\n  if ((player.GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Morphed && minSq <= dist &&\n       dist <= maxSq) ||\n      (x7cc_generateEndCooldown <= 0.f && player.GetVelocity().magSquared() > 25.f)) {\n    return zeus::CVector2f::getAngleDiff(GetTransform().frontVector().toVec2f(), diff) < zeus::degToRad(45.f);\n  }\n  return false;\n}\n\nvoid CFlaahgra::UpdateHeadDamageVulnerability(CStateManager& mgr, bool b) {\n  if (TCastToPtr<CCollisionActor> head = mgr.ObjectById(x80c_headActor)) {\n    head->SetDamageVulnerability(b ? x56c_data.x10_ : *GetDamageVulnerability());\n  }\n}\n\nvoid CFlaahgra::FadeIn(CStateManager& mgr, EStateMsg msg, float /*unused*/) {\n  if (msg != EStateMsg::Activate) {\n    return;\n  }\n\n  if (HealthInfo(mgr)->GetHP() > 0.f) {\n    SendScriptMsgs(EScriptObjectState::Exited, mgr, EScriptObjectMessage::None);\n  }\n\n  if (!x8e4_29_getup) {\n    SendScriptMsgs(EScriptObjectState::CloseIn, mgr, EScriptObjectMessage::None);\n  }\n}\n\nvoid CFlaahgra::FadeOut(CStateManager& mgr, EStateMsg msg, float /*unused*/) {\n  if (msg != EStateMsg::Activate) {\n    return;\n  }\n\n  x7a4_sphereCollision->SetActive(mgr, true);\n  x79c_leftArmCollision->SetActive(mgr, true);\n  x7a0_rightArmCollision->SetActive(mgr, true);\n  x784_ = x780_;\n  x81c_ = GetModelData()->GetScale().z();\n  UpdateScale(1.f, x81c_, x56c_data.x4_);\n  x8e4_26_ = false;\n  x7c0_ = 2.f;\n  x780_ = 3;\n  x8e4_29_getup = false;\n  x430_damageColor = skFlaahgraDamageColor;\n  x450_bodyController->GetCommandMgr().DeliverCmd(CBodyStateCmd(EBodyStateCmd::NextState));\n  x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n  x8e5_29_ = false;\n}\n\nvoid CFlaahgra::UpdateCollisionManagers(float dt, CStateManager& mgr) {\n  x7a4_sphereCollision->Update(dt, mgr, CCollisionActorManager::EUpdateOptions::ObjectSpace);\n  x79c_leftArmCollision->Update(dt, mgr, CCollisionActorManager::EUpdateOptions::ObjectSpace);\n  x7a0_rightArmCollision->Update(dt, mgr, CCollisionActorManager::EUpdateOptions::ObjectSpace);\n}\n\nvoid CFlaahgra::UpdateSmallScaleReGrowth(float dt) {\n  if (x7c0_ > 0.f) {\n    x7c0_ -= (x788_stage < 2 ? dt : 1.25f * dt);\n  }\n\n  if (x7bc_ > 0.f) {\n    x7bc_ -= dt;\n  }\n\n  if (x7d0_hitSomethingTime > 0.f) {\n    x7d0_hitSomethingTime -= dt;\n  }\n\n  if (x7cc_generateEndCooldown > 0.f) {\n    x7cc_generateEndCooldown -= dt;\n  }\n\n  if (!x8e4_29_getup || x7d8_ > 6.f) {\n    return;\n  }\n\n  x430_damageColor = zeus::CColor::lerp(zeus::skBlack, skUnkColor, std::fabs(M_PIF * std::cos(x7d8_)));\n  TakeDamage({}, 0.f);\n  x7d8_ += dt;\n}\n\nvoid CFlaahgra::UpdateHealthInfo(CStateManager& mgr) {\n  float tmp = 0.f;\n  for (const TUniqueId& uid : x7fc_sphereColliders) {\n    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(uid)) {\n      CHealthInfo* inf = colAct->HealthInfo(mgr);\n\n      tmp = zeus::max(tmp, x818_curHp - inf->GetHP());\n    }\n  }\n\n  if (x780_ == 3) {\n    if (IsDizzy(mgr, 0.f)) {\n      x814_ += tmp;\n    } else {\n      x810_ += tmp;\n    }\n  } else {\n    x814_ = 0.f;\n    x810_ = 0.f;\n  }\n\n  CHealthInfo* hInfo = HealthInfo(mgr);\n  if (hInfo->GetHP() <= 0.f) {\n    Death(mgr, {}, EScriptObjectState::DeathRattle);\n    RemoveMaterial(EMaterialTypes::Orbit, mgr);\n    return;\n  }\n  for (const TUniqueId& uid : x7fc_sphereColliders) {\n    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(uid)) {\n      colAct->HealthInfo(mgr)->SetHP(x818_curHp);\n    }\n  }\n}\n\nvoid CFlaahgra::UpdateAimPosition(CStateManager& mgr, float dt) {\n  if (TCastToConstPtr<CCollisionActor> head = mgr.GetObjectById(x80c_headActor)) {\n    pas::EAnimationState animState = x450_bodyController->GetBodyStateInfo().GetCurrentStateId();\n    if (animState == pas::EAnimationState::GroundHit || animState == pas::EAnimationState::LieOnGround) {\n      return;\n    }\n\n    zeus::CVector3f vec;\n    if (x780_ == 0 || x8e4_28_ || sub_801ae638() || sub801ae650()) {\n      vec = head->GetTranslation();\n    } else {\n      vec = GetTranslation() + zeus::CVector3f(0.f, 0.f, 3.7675f) +\n            (zeus::CVector3f(0.f, 0.f, 4.155f) * GetModelData()->GetScale());\n    }\n\n    zeus::CVector3f diff = vec - x820_aimPosition;\n    if (diff.canBeNormalized()) {\n      if (diff.magnitude() > (125.f * dt)) {\n        x820_aimPosition += (125.f * dt) * (1.f / diff.magnitude()) * diff;\n      } else {\n        x820_aimPosition = vec;\n      }\n    }\n  }\n}\n\nvoid CFlaahgra::SetMaterialProperties(const std::unique_ptr<CCollisionActorManager>& actMgr, CStateManager& mgr) {\n  for (u32 i = 0; i < actMgr->GetNumCollisionActors(); ++i) {\n    TUniqueId uid = actMgr->GetCollisionDescFromIndex(i).GetCollisionActorId();\n    if (auto* colAct = static_cast<CCollisionActor*>(mgr.ObjectById(uid))) {\n      colAct->SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(\n          {EMaterialTypes::Player}, {EMaterialTypes::Trigger, EMaterialTypes::CollisionActor,\n                                     EMaterialTypes::NoStaticCollision, EMaterialTypes::Immovable}));\n      colAct->AddMaterial(EMaterialTypes::Trigger, EMaterialTypes::ScanPassthrough, mgr);\n      colAct->SetDamageVulnerability(*GetDamageVulnerability());\n    }\n  }\n}\n\nbool CFlaahgra::ShouldTurn(CStateManager& mgr, float /*arg*/) {\n  zeus::CVector2f posDiff = mgr.GetPlayer().GetTranslation().toVec2f() - GetTranslation().toVec2f();\n  zeus::CVector2f frontVec = x34_transform.frontVector().toVec2f();\n  return zeus::CVector2f::getAngleDiff(frontVec, posDiff) > zeus::degToRad(15.f);\n}\n\nvoid CFlaahgra::TurnAround(CStateManager& mgr, EStateMsg msg, float /*arg*/) {\n  if (msg == EStateMsg::Activate) {\n    x6cc_boneTracking->SetTarget(mgr.GetPlayer().GetUniqueId());\n    x6cc_boneTracking->SetActive(true);\n    x8e5_29_ = false;\n  } else if (msg == EStateMsg::Update) {\n    if (!ShouldTurn(mgr, 0.f)) {\n      return;\n    }\n\n    float dt = 0.f;\n    if (GetModelData()->GetAnimationData()->GetSpeedScale() > 0.f) {\n      dt = 1.5f / GetModelData()->GetAnimationData()->GetSpeedScale();\n    }\n\n    zeus::CVector3f offset = mgr.GetPlayer().GetAimPosition(mgr, dt) - GetTranslation();\n    if (offset.canBeNormalized()) {\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd({}, offset.normalized(), 1.f));\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x6cc_boneTracking->SetActive(false);\n  }\n}\n\nbool CFlaahgra::IsSphereCollider(TUniqueId uid) const {\n  const auto it = std::find(x7fc_sphereColliders.cbegin(), x7fc_sphereColliders.cend(), uid);\n  return it != x7fc_sphereColliders.end();\n}\n\nvoid CFlaahgra::GetUp(CStateManager& mgr, EStateMsg msg, float /*arg*/) {\n  if (msg == EStateMsg::Activate) {\n    x568_state = EState::Zero;\n    x784_ = x780_;\n    x8e4_28_ = true;\n  } else if (msg == EStateMsg::Update) {\n    if (x568_state == EState::Zero) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::Getup) {\n        x568_state = EState::Two;\n        x7a8_ = (!x8e4_29_getup ? 4 : -1);\n        SetCollisionActorBounds(mgr, x79c_leftArmCollision, skUnkVec1);\n        SetCollisionActorBounds(mgr, x7a0_rightArmCollision, skUnkVec1);\n      } else {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCGetupCmd(pas::EGetupType(!x8e4_29_getup)));\n      }\n    } else if (x568_state == EState::Two &&\n               x450_bodyController->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::Getup) {\n      x568_state = EState::Four;\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x7c0_ = (x8e4_29_getup ? 5.f : 0.f);\n    x7a8_ = -1;\n    x8e4_28_ = false;\n    x8e4_29_getup = false;\n    SetCollisionActorBounds(mgr, x79c_leftArmCollision, {});\n    SetCollisionActorBounds(mgr, x7a0_rightArmCollision, {});\n    x430_damageColor = skFlaahgraDamageColor;\n  }\n}\n\nvoid CFlaahgra::Growth(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x568_state = EState::Zero;\n    x8e4_26_ = false;\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBCGenerateCmd(pas::EGenerateType::Two));\n    x784_ = x780_;\n    x81c_ = GetModelData()->GetScale().z();\n  } else if (msg == EStateMsg::Update) {\n    if (x568_state == EState::Zero) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::Generate) {\n        x568_state = EState::Two;\n      } else {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCGenerateCmd(pas::EGenerateType::Two));\n      }\n    } else if (x568_state == EState::Two) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::Generate) {\n        x568_state = EState::Four;\n      } else if (x8e4_26_) {\n        UpdateScale((x7c4_actionDuration > 0.f ? 1.f - (GetEndActionTime() / x7c4_actionDuration) : 1.f), x81c_,\n                    x56c_data.x4_);\n      }\n\n      x450_bodyController->GetCommandMgr().DeliverTargetVector(mgr.GetPlayer().GetTranslation() - GetTranslation());\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    UpdateScale(1.f, x81c_, x56c_data.x4_);\n    x8e4_26_ = false;\n    x780_ = 3;\n    x79c_leftArmCollision->SetActive(mgr, true);\n    x7a0_rightArmCollision->SetActive(mgr, true);\n    x8e4_29_getup = false;\n    x430_damageColor = skFlaahgraDamageColor;\n  }\n}\n\nvoid CFlaahgra::SetCollisionActorBounds(CStateManager& mgr, const std::unique_ptr<CCollisionActorManager>& colMgr,\n                                        const zeus::CVector3f& extendedBounds) {\n  for (u32 i = 0; i < colMgr->GetNumCollisionActors(); ++i) {\n    const CJointCollisionDescription& jointDesc = colMgr->GetCollisionDescFromIndex(i);\n    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(jointDesc.GetCollisionActorId())) {\n      colAct->SetExtendedTouchBounds(extendedBounds);\n    }\n  }\n}\n\nvoid CFlaahgra::UpdateScale(float t, float min, float max) {\n  float scale = (t * (max - min) + min);\n  GetModelData()->SetScale(zeus::skOne3f * scale);\n}\n\nfloat CFlaahgra::GetEndActionTime() const {\n  CCharAnimTime eventTime =\n      GetModelData()->GetAnimationData()->GetTimeOfUserEvent(EUserEventType::EndAction, CCharAnimTime::Infinity());\n  if (eventTime == CCharAnimTime::Infinity()) {\n    return 0.f;\n  }\n\n  return eventTime.GetSeconds();\n}\n\nvoid CFlaahgra::Generate(CStateManager& mgr, EStateMsg msg, float /*arg*/) {\n  if (msg == EStateMsg::Activate) {\n    x568_state = EState::Zero;\n  } else if (msg == EStateMsg::Update) {\n    if (x568_state == EState::Zero) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::Generate) {\n        x568_state = EState::Two;\n        x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n      } else {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCGenerateCmd(pas::EGenerateType::Zero));\n      }\n    } else if (x568_state == EState::Two &&\n               x450_bodyController->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::Generate) {\n      x568_state = EState::Four;\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x7a4_sphereCollision->SetActive(mgr, true);\n    x7c0_ = 11.f;\n  }\n}\n\nzeus::CVector3f CFlaahgra::GetAttackTargetPos(const CStateManager& mgr) const {\n  if (mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed) {\n    return mgr.GetPlayer().GetMorphBall()->GetBallToWorld().origin;\n  }\n\n  return mgr.GetPlayer().GetTranslation() + zeus::CVector3f(0.f, 0.f, -.5f + mgr.GetPlayer().GetEyeHeight());\n}\n\nvoid CFlaahgra::RattlePlayer(CStateManager& mgr, const zeus::CVector3f& vec) {\n  CPlayer& player = mgr.GetPlayer();\n  /*\n   zeus::CVector3f direction = vec - mgr.GetPlayer().GetTranslation();\n   float dir = direction.magnitude(); Unused\n  */\n\n  if (player.GetSurfaceRestraint() == CPlayer::ESurfaceRestraints::Air || player.IsInWaterMovement()) {\n    return;\n  }\n\n  if (player.GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Morphed) {\n    if (mgr.GetCameraManager()->GetFirstPersonCamera()->GetUniqueId() != mgr.GetCameraManager()->GetCurrentCameraId()) {\n      return;\n    }\n\n    mgr.GetCameraManager()->AddCameraShaker(CCameraShakeData(2.f, 0.75f), true);\n  } else {\n    player.ApplyImpulseWR(mgr.GetPlayer().GetMass() * 0.75f * 25.f * zeus::skUp, {});\n    player.SetMoveState(CPlayer::EPlayerMovementState::ApplyJump, mgr);\n  }\n}\n\nvoid CFlaahgra::Faint(CStateManager& mgr, EStateMsg msg, float arg) {\n  static constexpr std::array kSeverities{pas::ESeverity::Zero, pas::ESeverity::One};\n\n  if (msg == EStateMsg::Activate) {\n    x568_state = EState::Zero;\n    x7d4_faintTime = 0.f;\n    x8e5_24_ = false;\n    x7f8_ = 0;\n    SendScriptMsgs(EScriptObjectState::Entered, mgr, EScriptObjectMessage::None);\n    SendScriptMsgs(EScriptObjectState::Retreat, mgr, EScriptObjectMessage::None);\n    x450_bodyController->GetCommandMgr().DeliverCmd(\n        CBCKnockDownCmd(-GetTransform().frontVector(), kSeverities[size_t(x7ac_)]));\n  } else if (msg == EStateMsg::Update) {\n    if (x568_state == EState::Zero) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::Fall) {\n        x568_state = EState::Two;\n        CalculateFallDirection();\n        UpdateHeadDamageVulnerability(mgr, true);\n      } else {\n        x450_bodyController->GetCommandMgr().DeliverCmd(\n            CBCKnockDownCmd(-GetTransform().frontVector(), kSeverities[size_t(x7ac_)]));\n      }\n    } else if (x568_state == EState::Two) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::LieOnGround) {\n        x7d4_faintTime += arg;\n        if (x7d4_faintTime >= x56c_data.xc_faintDuration) {\n          x568_state = EState::Four;\n        }\n      } else {\n        x450_bodyController->FaceDirection(x894_fallDirection, arg);\n      }\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x8e4_27_ = false;\n    x7ac_ = !x7ac_;\n    x780_ = 3;\n    UpdateHeadDamageVulnerability(mgr, false);\n  }\n}\n\nvoid CFlaahgra::Dead(CStateManager& mgr, EStateMsg msg, float /*arg*/) {\n  if (msg == EStateMsg::Activate) {\n    x568_state = (x450_bodyController->GetFallState() != pas::EFallState::Zero ||\n                  x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::Fall)\n                     ? EState::One\n                     : EState::Zero;\n    SendScriptMsgs(EScriptObjectState::CloseIn, mgr, EScriptObjectMessage::None);\n  } else if (msg == EStateMsg::Update) {\n    if (x568_state == EState::Zero) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::Getup) {\n        return;\n      }\n\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::Fall) {\n        x450_bodyController->GetCommandMgr().DeliverCmd(\n            CBCKnockDownCmd(-GetTransform().frontVector(), pas::ESeverity::Two));\n      } else {\n        x568_state = EState::Two;\n        zeus::CTransform xf = zeus::lookAt(GetTranslation(), GetTranslation() + x8a0_, zeus::skUp);\n        xf.origin = GetTranslation();\n        SetTransform(xf);\n        SendScriptMsgs(EScriptObjectState::Dead, mgr, EScriptObjectMessage::None);\n        x8e5_26_ = true;\n      }\n    } else if (x568_state == EState::One) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::Getup) {\n        x568_state = EState::Zero;\n        return;\n      }\n\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBCGetupCmd(pas::EGetupType::Two));\n    } else if (x568_state == EState::Two &&\n               x450_bodyController->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::Fall) {\n      mgr.FreeScriptObject(GetUniqueId());\n    }\n  }\n}\n\nvoid CFlaahgra::Attack(CStateManager& mgr, EStateMsg msg, float arg) {\n  static constexpr std::array kStates1{\n      -1, -1, -1, 2, -1,\n  };\n  static constexpr std::array kSeverity{\n      pas::ESeverity::Three, pas::ESeverity::Four, pas::ESeverity::One, pas::ESeverity::Zero, pas::ESeverity::Invalid,\n  };\n\n  if (msg == EStateMsg::Activate) {\n    x568_state = EState::Zero;\n    x7a8_ = sub801ae828(mgr);\n    SendScriptMsgs(EScriptObjectState::Attack, mgr, EScriptObjectMessage::None);\n  } else if (msg == EStateMsg::Update) {\n    if (x568_state == EState::Zero) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::MeleeAttack) {\n        x568_state = kStates1[x7a8_] != -1 ? EState::One : EState::Two;\n\n        if (sub801ae670()) {\n          SetCollisionActorBounds(mgr, x79c_leftArmCollision, skUnkVec2);\n          SetCollisionActorBounds(mgr, x7a0_rightArmCollision, skUnkVec2);\n        }\n\n        x78c_ = sub801ae754(mgr);\n        x798_meleeInitialAnimState = x450_bodyController->GetCurrentAnimId();\n      } else {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCMeleeAttackCmd(kSeverity[x7a8_]));\n      }\n    } else if (x568_state == EState::One) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::MeleeAttack) {\n        if (x798_meleeInitialAnimState == x450_bodyController->GetCurrentAnimId()) {\n          x450_bodyController->GetCommandMgr().DeliverTargetVector(x78c_);\n          if (ShouldAttack(mgr, 0.f)) {\n            x450_bodyController->GetCommandMgr().DeliverCmd(CBCMeleeAttackCmd(kSeverity[kStates1[x7a8_]]));\n          }\n        } else {\n          x568_state = EState::Two;\n        }\n      } else {\n        x568_state = EState::Four;\n      }\n    } else if (x568_state == EState::Two) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::MeleeAttack) {\n        x568_state = EState::Four;\n      } else {\n        x450_bodyController->GetCommandMgr().DeliverTargetVector(x78c_);\n      }\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    SetCollisionActorBounds(mgr, x79c_leftArmCollision, {});\n    SetCollisionActorBounds(mgr, x7a0_rightArmCollision, {});\n\n    if (sub801ae670()) {\n      x7c0_ = (x308_attackTimeVariation * mgr.GetActiveRandom()->Float() + x304_averageAttackTime) / (1.f + x788_stage);\n    }\n\n    x7a8_ = -1;\n\n    if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::MeleeAttack) {\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBodyStateCmd(EBodyStateCmd::NextState));\n    }\n  }\n}\n\nu32 CFlaahgra::sub801ae828(const CStateManager& mgr) const {\n  const CPlayer& player = mgr.GetPlayer();\n  if (player.GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Morphed &&\n      (x7cc_generateEndCooldown > 0.f || player.GetVelocity().magSquared() < 25.f)) {\n    return 3;\n  }\n\n  if (GetTransform().basis[0].dot(player.GetVelocity()) > 0.f) {\n    return 1;\n  }\n\n  return 0;\n}\n\nzeus::CVector3f CFlaahgra::sub801ae754(const CStateManager& mgr) const {\n  float dt = (sub801ae650() && mgr.GetPlayer().GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Morphed\n                  ? 0.75f\n                  : 0.5f);\n  return GetAimPosition(mgr, dt * x450_bodyController->GetAnimTimeRemaining()) - GetTranslation();\n}\n\nvoid CFlaahgra::Dizzy(CStateManager& /*unused*/, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x7b8_dizzyTime = 0.f;\n    x814_ = 0.f;\n    x8e5_30_ = false;\n  } else if (msg == EStateMsg::Update) {\n    x7b8_dizzyTime += arg;\n    if (x7b8_dizzyTime >= (x788_stage < 2 ? x56c_data.x144_ : -1.5f + x56c_data.x144_)) {\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBodyStateCmd(EBodyStateCmd::ExitState));\n      x8e5_30_ = true;\n    } else {\n      x814_ = 0.f;\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x8e5_30_ = false;\n    x810_ = x814_;\n    x7bc_ = x56c_data.x148_;\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBodyStateCmd(EBodyStateCmd::ExitState));\n  }\n}\n\nvoid CFlaahgra::Suck(CStateManager& mgr, EStateMsg msg, float /*arg*/) {\n  if (msg == EStateMsg::Activate) {\n    x568_state = EState::Zero;\n    x8e4_26_ = false;\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBCGenerateCmd(pas::EGenerateType::Two));\n    x784_ = x780_;\n  } else if (msg == EStateMsg::Update) {\n    if (x568_state == EState::Zero) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::Getup) {\n        x568_state = EState::Two;\n      } else {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCGetupCmd(pas::EGetupType::Zero));\n      }\n    } else if (x568_state == EState::Two) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::Getup) {\n        x568_state = EState::Four;\n      } else if (x8e4_26_) {\n        UpdateScale(x7c4_actionDuration > 0.0f ? 1.f - (GetEndActionTime() / x7c4_actionDuration) : 1.f, x56c_data.x4_,\n                    x56c_data.x0_);\n      }\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x8e4_26_ = false;\n    x780_ = 2;\n    x79c_leftArmCollision->SetActive(mgr, false);\n    x7a0_rightArmCollision->SetActive(mgr, false);\n  }\n}\n\nvoid CFlaahgra::ProjectileAttack(CStateManager& mgr, EStateMsg msg, float /*arg*/) {\n  if (msg == EStateMsg::Activate) {\n    x568_state = EState::Zero;\n    SendScriptMsgs(EScriptObjectState::Attack, mgr, EScriptObjectMessage::None);\n  } else if (msg == EStateMsg::Update) {\n    if (x568_state == EState::Zero) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::ProjectileAttack) {\n        x568_state = EState::Two;\n      } else {\n        x7b4_ = 3;\n        pas::ESeverity severity;\n        if (x8e4_31_) {\n          x7b4_ = 2;\n          severity = pas::ESeverity::Six;\n        } else if (mgr.GetPlayer().GetVelocity().magSquared() > 100.f) {\n          if (mgr.GetPlayer().GetTransform().basis[0].magSquared() < 0.f) {\n            x7b4_ = 1;\n            severity = pas::ESeverity::Four;\n            x72c_projectilesCreated = 0;\n          } else {\n            severity = pas::ESeverity::Three;\n            x7b4_ = x72c_projectilesCreated = 0;\n          }\n        } else {\n          severity = pas::ESeverity::Seven;\n        }\n\n        x450_bodyController->GetCommandMgr().DeliverCmd(\n            CBCProjectileAttackCmd(severity, mgr.GetPlayer().GetTranslation(), false));\n      }\n    } else if (x568_state == EState::Two) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::ProjectileAttack) {\n        x568_state = EState::Four;\n      } else {\n        x450_bodyController->GetCommandMgr().DeliverTargetVector(mgr.GetPlayer().GetTranslation() - GetTranslation());\n      }\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x7c0_ = (x308_attackTimeVariation * mgr.GetActiveRandom()->Float() + x304_averageAttackTime) / (1.f + x788_stage);\n    x7b4_ = -1;\n    x72c_projectilesCreated = -1;\n    if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::ProjectileAttack) {\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBodyStateCmd(EBodyStateCmd::NextState));\n    }\n  }\n}\n\nvoid CFlaahgra::Cover(CStateManager& mgr, EStateMsg msg, float /*arg*/) {\n  static constexpr std::array severities{pas::ESeverity::Eight, pas::ESeverity::Seven};\n  if (msg == EStateMsg::Activate) {\n    x77c_targetMirrorWaypointId = GetMirrorNearestPlayer(mgr);\n    x568_state = (x77c_targetMirrorWaypointId == kInvalidUniqueId ? EState::Four : EState::One);\n    x6cc_boneTracking->SetTarget(mgr.GetPlayer().GetUniqueId());\n    x6cc_boneTracking->SetActive(true);\n  } else if (msg == EStateMsg::Update) {\n    if (x568_state == EState::Zero) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::MeleeAttack) {\n        x568_state = EState::Two;\n      } else {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCMeleeAttackCmd(severities[x7b0_]));\n      }\n    } else if (x568_state == EState::One) {\n      if (TCastToConstPtr<CActor> wp = mgr.GetObjectById(x77c_targetMirrorWaypointId)) {\n        zeus::CVector3f direction = wp->GetTranslation() - GetTranslation();\n        if (zeus::CVector2f::getAngleDiff(GetTransform().basis[1].toVec2f(), direction.toVec2f()) >\n                zeus::degToRad(15.f) &&\n            direction.canBeNormalized()) {\n          x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd({}, direction.normalized(), 1.f));\n        } else {\n          x568_state = EState::Zero;\n          x6cc_boneTracking->SetActive(false);\n        }\n      } else {\n        x568_state = EState::Four;\n      }\n    } else if (x568_state == EState::Two) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::MeleeAttack) {\n        x568_state = EState::Four;\n      }\n\n      else if (TCastToConstPtr<CActor> wp = mgr.GetObjectById(x77c_targetMirrorWaypointId)) {\n        x450_bodyController->GetCommandMgr().DeliverTargetVector(wp->GetTranslation() - GetTranslation());\n      }\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::MeleeAttack) {\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBodyStateCmd(EBodyStateCmd::NextState));\n    }\n\n    x77c_targetMirrorWaypointId = kInvalidUniqueId;\n    x7bc_ = x56c_data.x148_;\n    x7b0_ ^= 1;\n  }\n}\n\nvoid CFlaahgra::SpecialAttack(CStateManager& mgr, EStateMsg msg, float /*arg*/) {\n  if (msg == EStateMsg::Activate) {\n    x568_state = EState::Zero;\n    x8e5_24_ = false;\n    x7b4_ = 3;\n    SendScriptMsgs(EScriptObjectState::Attack, mgr, EScriptObjectMessage::None);\n  } else if (msg == EStateMsg::Update) {\n    if (x568_state == EState::Zero) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::ProjectileAttack) {\n        x568_state = EState::Two;\n        x8e4_30_bigStrike = true;\n      } else {\n        x450_bodyController->GetCommandMgr().DeliverCmd(\n            CBCProjectileAttackCmd(pas::ESeverity::Eight, mgr.GetPlayer().GetTranslation(), false));\n      }\n    } else if (x568_state == EState::Two) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::ProjectileAttack) {\n        x450_bodyController->GetCommandMgr().DeliverTargetVector(mgr.GetPlayer().GetTranslation() - GetTranslation());\n      } else {\n        x568_state = EState::Four;\n      }\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x7c0_ = (x308_attackTimeVariation * mgr.GetActiveRandom()->Float() + x304_averageAttackTime) / (1.f + x788_stage);\n    x8e4_30_bigStrike = false;\n    x7b4_ = -1;\n    if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::ProjectileAttack) {\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBodyStateCmd(EBodyStateCmd::NextState));\n    }\n  }\n}\n\nbool CFlaahgra::CoverCheck(CStateManager& mgr, float /*arg*/) {\n  if (x7f8_ <= 0 && x7bc_ > 0.f) {\n    return false;\n  }\n\n  return std::any_of(x770_mirrorWaypoints.cbegin(), x770_mirrorWaypoints.cend(), [&mgr](TUniqueId id) {\n    if (const CEntity* ent = mgr.GetObjectById(id)) {\n      if (ent->GetActive()) {\n        return true;\n      }\n    }\n    return false;\n  });\n}\n\nTUniqueId CFlaahgra::GetMirrorNearestPlayer(const CStateManager& mgr) const {\n  zeus::CVector3f playerPos = mgr.GetPlayer().GetTranslation();\n\n  TUniqueId nearId = kInvalidUniqueId;\n  float prevMag = -1.f;\n  for (TUniqueId id : x770_mirrorWaypoints) {\n    if (TCastToConstPtr<CActor> wp = mgr.GetObjectById(id)) {\n      if (!wp->GetActive()) {\n        continue;\n      }\n      const float mag = (wp->GetTranslation() - playerPos).magSquared();\n      if (mag > prevMag) {\n        nearId = id;\n        prevMag = mag;\n      }\n    }\n  }\n\n  return nearId;\n}\n\nvoid CFlaahgra::Enraged(CStateManager& /*mgr*/, EStateMsg msg, float /*arg*/) {\n  if (msg == EStateMsg::Activate) {\n    x568_state = EState::Zero;\n  } else if (msg == EStateMsg::Update) {\n    if (x568_state == EState::Zero) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::Taunt) {\n        x568_state = EState::Two;\n      } else {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCTauntCmd(pas::ETauntType::Zero));\n      }\n    } else if (x568_state == EState::Two &&\n               x450_bodyController->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::Taunt) {\n      x568_state = EState::Four;\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x7d0_hitSomethingTime = 0.f;\n  }\n}\n\nCFlaahgraProjectile* CFlaahgra::CreateProjectile(const zeus::CTransform& xf, CStateManager& mgr) {\n  CProjectileInfo* projectileInfo = GetProjectileInfo();\n  if (!projectileInfo->Token() || !mgr.CanCreateProjectile(GetUniqueId(), EWeaponType::AI, 6)) {\n    return nullptr;\n  }\n  CDamageInfo damageInfo = projectileInfo->GetDamage();\n  if (x788_stage > 1) {\n    damageInfo.SetDamage(damageInfo.GetDamage() * 1.33f);\n  }\n  auto* projectile = new CFlaahgraProjectile(x8e4_30_bigStrike, projectileInfo->Token(), xf, damageInfo,\n                                             mgr.AllocateUniqueId(), GetAreaIdAlways(), GetUniqueId());\n  mgr.AddObject(projectile);\n  return projectile;\n}\n\nbool CFlaahgra::sub_801ae638() {\n  return GetBodyController()->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::ProjectileAttack;\n}\n\nbool CFlaahgra::ShouldSpecialAttack(CStateManager& mgr, float arg) {\n  return !(x788_stage < 0 || x788_stage > 3 || !ShouldFire(mgr, arg) || x788_stage < 2) && x8e5_24_;\n}\n\nbool CFlaahgra::ShouldFire(CStateManager& mgr, float arg) {\n  CPlayer& player = mgr.GetPlayer();\n  if (x7c0_ > 0.f || player.IsInWaterMovement()) {\n    return false;\n  }\n  const auto dir = player.GetTranslation().toVec2f() - GetTranslation().toVec2f();\n  return zeus::CVector2f::getAngleDiff(GetTransform().frontVector().toVec2f(), dir) < zeus::degToRad(45.f);\n}\n\nCFlaahgraPlants::CFlaahgraPlants(const TToken<CGenDescription>& genDesc, const CActorParameters& actParms,\n                                 TUniqueId uid, TAreaId aId, TUniqueId owner, const zeus::CTransform& xf,\n                                 const CDamageInfo& dInfo, const zeus::CVector3f& extents)\n: CActor(uid, true, \"Flaahgra Plants\"sv, CEntityInfo(aId, NullConnectionList), xf, CModelData::CModelDataNull(),\n         CMaterialList(EMaterialTypes::Projectile), actParms, kInvalidUniqueId)\n, xe8_elementGen(std::make_unique<CElementGen>(genDesc))\n, xf0_ownerId(owner)\n, xf4_damageInfo(dInfo)\n, x130_obbox(xf, extents) {\n  xe8_elementGen->SetOrientation(xf.getRotation());\n  xe8_elementGen->SetTranslation(xf.origin);\n  xe8_elementGen->SetModelsUseLights(true);\n  x110_aabox = {x130_obbox.calculateAABox()};\n}\n\nvoid CFlaahgraPlants::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CFlaahgraPlants::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  CActor::AcceptScriptMsg(msg, uid, mgr);\n\n  if (msg == EScriptObjectMessage::Registered) {\n    xe8_elementGen->SetParticleEmission(true);\n    SetActive(true);\n    if (x16c_colAct == kInvalidUniqueId) {\n      x16c_colAct = mgr.AllocateUniqueId();\n      auto* colAct = new CCollisionActor(x16c_colAct, GetAreaIdAlways(), GetUniqueId(),\n                                         x130_obbox.extents + zeus::CVector3f(0.f, 5.f, 10.f), {}, true, 0.001f,\n                                         \"Flaahgra Plants\"sv);\n\n      colAct->SetTransform(GetTransform());\n      colAct->SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(\n          {EMaterialTypes::Player}, {EMaterialTypes::Trigger, EMaterialTypes::CollisionActor,\n                                     EMaterialTypes::NoStaticCollision, EMaterialTypes::Character}));\n      CMaterialList materialList = colAct->GetMaterialList();\n      materialList.Add(EMaterialTypes::ProjectilePassthrough);\n      materialList.Add(EMaterialTypes::Immovable);\n      colAct->SetMaterialList(materialList);\n      mgr.AddObject(colAct);\n      mgr.SetActorAreaId(*colAct, GetAreaIdAlways());\n    }\n  } else if (msg == EScriptObjectMessage::Deleted && x16c_colAct != kInvalidUniqueId) {\n    mgr.FreeScriptObject(x16c_colAct);\n  }\n}\n\nvoid CFlaahgraPlants::Think(float dt, CStateManager& mgr) {\n  if (GetActive()) {\n    xe8_elementGen->Update(dt);\n    x12c_lastDt = dt;\n  }\n\n  if (xe8_elementGen->IsSystemDeletable()) {\n    mgr.FreeScriptObject(GetUniqueId());\n  }\n}\n\nvoid CFlaahgraPlants::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) {\n  g_Renderer->AddParticleGen(*xe8_elementGen);\n  CActor::AddToRenderer(frustum, mgr);\n}\n\nvoid CFlaahgraPlants::Touch(CActor& act, CStateManager& mgr) {\n  if (act.GetUniqueId() != mgr.GetPlayer().GetUniqueId() || !x110_aabox) {\n    return;\n  }\n\n  zeus::COBBox plObb = zeus::COBBox::FromAABox(mgr.GetPlayer().GetBoundingBox(), {});\n\n  if (!x130_obbox.OBBIntersectsBox(plObb)) {\n    return;\n  }\n\n  CDamageInfo dInfo = xf4_damageInfo;\n  float newDamage = x12c_lastDt * xf4_damageInfo.GetDamage();\n  dInfo.SetDamage(newDamage);\n  dInfo.SetRadiusDamage(newDamage);\n  dInfo.SetNoImmunity(true);\n\n  zeus::CVector3f diffVec = mgr.GetPlayer().GetTranslation() - GetTranslation();\n  mgr.ApplyDamage(GetUniqueId(), act.GetUniqueId(), GetUniqueId(), dInfo,\n                  CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}),\n                  diffVec.dot(GetTransform().basis[0]) > 0.f ? GetTransform().basis[0] : -GetTransform().basis[0]);\n}\n\nstd::optional<zeus::CAABox> CFlaahgraPlants::GetTouchBounds() const {\n  if (!GetActive()) {\n    return {};\n  }\n  return x110_aabox;\n}\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CFlaahgra.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <optional>\n#include <vector>\n\n#include \"Runtime/CDependencyGroup.hpp\"\n#include \"Runtime/Collision/CJointCollisionDescription.hpp\"\n#include \"Runtime/MP1/World/CFlaahgraProjectile.hpp\"\n#include \"Runtime/Weapon/CProjectileInfo.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/World/CAnimationParameters.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n#include \"Runtime/rstl.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CColor.hpp>\n#include <zeus/COBBox.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CBoneTracking;\nclass CCollisionActorManager;\nclass CDependencyGroup;\nclass CElementGen;\nclass CGenDescription;\n} // namespace metaforce\n\nnamespace metaforce::MP1 {\nclass CFlaahgraData {\n  friend class CFlaahgra;\n  float x0_;\n  float x4_;\n  float x8_;\n  float xc_faintDuration;\n  CDamageVulnerability x10_;\n  CAssetId x78_;\n  CDamageInfo x7c_;\n  CAssetId x98_;\n  CDamageInfo x9c_;\n  CAssetId xb8_plantsParticleGenDescId;\n  CDamageInfo xbc_;\n  CActorParameters xd8_;\n  float x140_;\n  float x144_;\n  float x148_;\n  CAnimationParameters x14c_animationParameters;\n  CAssetId x158_;\n\npublic:\n  static constexpr u32 GetNumProperties() { return 23; }\n  explicit CFlaahgraData(CInputStream&);\n\n  [[nodiscard]] const CAnimationParameters& GetAnimationParameters() const { return x14c_animationParameters; }\n};\n\nclass CFlaahgraRenderer : public CActor {\n  TUniqueId xe8_owner;\n\npublic:\n  DEFINE_ENTITY\n  CFlaahgraRenderer(TUniqueId, TUniqueId, std::string_view, const CEntityInfo&, const zeus::CTransform&);\n\n  void AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) override;\n  void Accept(IVisitor& visitor) override;\n  [[nodiscard]] std::optional<zeus::CAABox> GetTouchBounds() const override { return {}; }\n};\n\nclass CFlaahgraPlants : public CActor {\n  std::unique_ptr<CElementGen> xe8_elementGen;\n  TUniqueId xf0_ownerId;\n  CDamageInfo xf4_damageInfo;\n  std::optional<zeus::CAABox> x110_aabox;\n  float x12c_lastDt = 0.f;\n  zeus::COBBox x130_obbox;\n  TUniqueId x16c_colAct = kInvalidUniqueId;\n\npublic:\n  DEFINE_ENTITY\n  CFlaahgraPlants(const TToken<CGenDescription>&, const CActorParameters&, TUniqueId, TAreaId, TUniqueId,\n                  const zeus::CTransform&, const CDamageInfo&, const zeus::CVector3f&);\n\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) override;\n  void Think(float dt, CStateManager& mgr) override;\n  void AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) override;\n  [[nodiscard]] std::optional<zeus::CAABox> GetTouchBounds() const override;\n  void Touch(CActor& actor, CStateManager& mgr) override;\n};\n\nclass CFlaahgra : public CPatterned {\n  enum class EState { Invalid = -1, Zero, One, Two, Three, Four } x568_state = EState::Invalid;\n  CFlaahgraData x56c_data;\n  std::unique_ptr<CBoneTracking> x6cc_boneTracking; // Used to be an rstl::optional_object<CBoneTracking*>\n  TUniqueId x6d0_rendererId = kInvalidUniqueId;\n  TToken<CGenDescription> x6d4_plantsParticleGenDesc;\n  CProjectileInfo x6dc_normalProjectileInfo;\n  CProjectileInfo x704_bigStrikeProjectileInfo;\n  s32 x72c_projectilesCreated = -1;\n  rstl::reserved_vector<zeus::CVector3f, 5> x730_projectileDirs;\n  rstl::reserved_vector<TUniqueId, 4> x770_mirrorWaypoints;\n  TUniqueId x77c_targetMirrorWaypointId = kInvalidUniqueId;\n  u32 x780_ = 1;\n  u32 x784_ = 1;\n  u32 x788_stage = 0;\n  zeus::CVector3f x78c_;\n  s32 x798_meleeInitialAnimState = -1;\n  std::unique_ptr<CCollisionActorManager> x79c_leftArmCollision;\n  std::unique_ptr<CCollisionActorManager> x7a0_rightArmCollision;\n  std::unique_ptr<CCollisionActorManager> x7a4_sphereCollision;\n  s32 x7a8_ = -1;\n  bool x7ac_ = true; // Was an enum\n  u32 x7b0_ = 1;\n  s32 x7b4_ = -1;\n  float x7b8_dizzyTime = 0.f;\n  float x7bc_ = 0.f;\n  float x7c0_ = 0.f;\n  float x7c4_actionDuration = 0.f;\n  // float x7c8_ = -4.f;\n  float x7cc_generateEndCooldown = 0.f;\n  float x7d0_hitSomethingTime = 0.f;\n  float x7d4_faintTime = 0.f;\n  float x7d8_ = 0.f;\n  CDamageInfo x7dc_halfContactDamage;\n  u32 x7f8_ = 0;\n  rstl::reserved_vector<TUniqueId, 8> x7fc_sphereColliders;\n  TUniqueId x80c_headActor = kInvalidUniqueId;\n  float x810_ = 0.f;\n  float x814_ = 0.f;\n  float x818_curHp = 0.f;\n  float x81c_ = 0.f;\n  zeus::CVector3f x820_aimPosition;\n  rstl::reserved_vector<zeus::CVector3f, 4> x82c_;\n  rstl::reserved_vector<zeus::CVector3f, 4> x860_;\n  zeus::CVector3f x894_fallDirection;\n  zeus::CVector3f x8a0_;\n  CAnimRes x8ac_;\n  std::optional<TToken<CDependencyGroup>> x8c8_depGroup;\n  std::vector<CToken> x8d4_tokens;\n  bool x8e4_24_loaded : 1 = false;\n  bool x8e4_25_loading : 1 = false;\n  bool x8e4_26_ : 1 = false;\n  bool x8e4_27_ : 1 = false;\n  bool x8e4_28_ : 1 = false;\n  bool x8e4_29_getup : 1 = false;\n  bool x8e4_30_bigStrike : 1 = false;\n  bool x8e4_31_ : 1 = false;\n  bool x8e5_24_ : 1 = false;\n  bool x8e5_25_ : 1 = false;\n  bool x8e5_26_ : 1 = false;\n  bool x8e5_27_ : 1 = false;\n  bool x8e5_28_ : 1 = false;\n  bool x8e5_29_ : 1 = true;\n  bool x8e5_30_ : 1 = false;\n\n  void LoadDependencies(CAssetId);\n  void ResetModelDataAndBodyController();\n  void GatherAssets(CStateManager& mgr);\n  void LoadTokens(CStateManager& mgr);\n  void FinalizeLoad(CStateManager& mgr);\n  void GetMirrorWaypoints(CStateManager& mgr);\n  void AddCollisionList(const SJointInfo*, size_t, std::vector<CJointCollisionDescription>&);\n  void AddSphereCollisionList(const SSphereJointInfo*, size_t, std::vector<CJointCollisionDescription>&);\n  void SetupCollisionManagers(CStateManager&);\n  void sub801ae980(CStateManager&);\n  void UpdateCollisionManagers(float, CStateManager&);\n  void UpdateSmallScaleReGrowth(float);\n  void UpdateHealthInfo(CStateManager&);\n  void UpdateAimPosition(CStateManager&, float);\n  void SetMaterialProperties(const std::unique_ptr<CCollisionActorManager>&, CStateManager&);\n  [[nodiscard]] bool sub801ae650() const { return x7a8_ == 0 || x7a8_ == 1; }\n  [[nodiscard]] bool sub801ae670() const { return (x7a8_ == 2 || x7a8_ == 3 || x7a8_ == 4); }\n  [[nodiscard]] bool IsSphereCollider(TUniqueId) const;\n  void SetCollisionActorBounds(CStateManager& mgr, const std::unique_ptr<CCollisionActorManager>& colMgr,\n                               const zeus::CVector3f& extendedBounds);\n\n  void UpdateScale(float, float, float);\n  [[nodiscard]] float GetEndActionTime() const;\n  void SetupHealthInfo(CStateManager&);\n  [[nodiscard]] zeus::CVector3f GetAttackTargetPos(const CStateManager& mgr) const;\n  void RattlePlayer(CStateManager& mgr, const zeus::CVector3f& vec);\n  void CalculateFallDirection();\n  void UpdateHeadDamageVulnerability(CStateManager&, bool);\n\n  [[nodiscard]] u32 sub801ae828(const CStateManager&) const;\n  [[nodiscard]] zeus::CVector3f sub801ae754(const CStateManager&) const;\n  CFlaahgraProjectile* CreateProjectile(const zeus::CTransform& xf, CStateManager& mgr);\n\n  [[nodiscard]] TUniqueId GetMirrorNearestPlayer(const CStateManager&) const;\n  bool sub_801ae638();\n\npublic:\n  DEFINE_PATTERNED(Flaahgra);\n  CFlaahgra(TUniqueId, std::string_view, const CEntityInfo&, const zeus::CTransform&, const CAnimRes&,\n            const CPatternedInfo&, const CActorParameters&, CFlaahgraData);\n\n  void Accept(IVisitor& visitor) override;\n  void Think(float dt, CStateManager& mgr) override;\n  void PreThink(float dt, CStateManager& mgr) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) override;\n  void AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) override;\n  [[nodiscard]] bool CanRenderUnsorted(const CStateManager& mgr) const override { return true; }\n  [[nodiscard]] zeus::CVector3f GetAimPosition(const CStateManager& mgr, float dt) const override {\n    return x820_aimPosition;\n  }\n  void Death(CStateManager& mgr, const zeus::CVector3f& direction, EScriptObjectState state) override;\n  void DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) override;\n\n  CProjectileInfo* GetProjectileInfo() override {\n    return x8e4_30_bigStrike ? &x704_bigStrikeProjectileInfo : &x6dc_normalProjectileInfo;\n  }\n\n  bool AnimOver(CStateManager& mgr, float arg) override { return x568_state == EState::Four; }\n  bool AIStage(CStateManager& mgr, float arg) override { return x780_ == u32(arg); }\n  bool HitSomething(CStateManager& mgr, float arg) override { return x7d0_hitSomethingTime > 0.f; }\n  bool OffLine(CStateManager& mgr, float arg) override { return (x8e5_29_ && x8e5_28_); }\n  bool ShouldTurn(CStateManager& mgr, float arg) override;\n  bool ShouldAttack(CStateManager& mgr, float arg) override;\n  bool BreakAttack(CStateManager& mgr, float arg) override {\n    return x7d4_faintTime >= x56c_data.xc_faintDuration && !x8e4_29_getup;\n  }\n  bool IsDizzy(CStateManager& mgr, float arg) override {\n    return x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::LoopReaction;\n  }\n  bool CoverCheck(CStateManager& mgr, float arg) override;\n  bool ShouldSpecialAttack(CStateManager& mgr, float arg) override;\n  bool ShouldFire(CStateManager& mgr, float arg) override;\n\n  void FadeIn(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void FadeOut(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void TurnAround(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void GetUp(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Growth(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Generate(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Faint(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Dead(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Attack(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Dizzy(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Suck(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void ProjectileAttack(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Cover(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void SpecialAttack(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Enraged(CStateManager& mgr, EStateMsg msg, float arg) override;\n};\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CFlaahgraProjectile.cpp",
    "content": "#include \"Runtime/MP1/World/CFlaahgraProjectile.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n\nnamespace metaforce::MP1 {\n\nCFlaahgraProjectile::CFlaahgraProjectile(bool bigStrike, const TToken<CWeaponDescription>& desc,\n                                         const zeus::CTransform& xf, const CDamageInfo& damage, TUniqueId uid,\n                                         TAreaId aid, TUniqueId owner)\n: CEnergyProjectile(true, desc, EWeaponType::AI, xf, EMaterialTypes::Character, damage, uid, aid, owner,\n                    kInvalidUniqueId, EProjectileAttrib::BigProjectile, false, zeus::skOne3f, {}, 0xffff, false)\n, x3d8_bigStrike(bigStrike) {\n  if (x3d8_bigStrike) {\n    xe8_projectileAttribs |= EProjectileAttrib::BigStrike;\n    x150_damageDuration = 2.f;\n  }\n}\n\nvoid CFlaahgraProjectile::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) {\n  CEnergyProjectile::AcceptScriptMsg(msg, sender, mgr);\n  if (x3d8_bigStrike && msg == EScriptObjectMessage::Deleted && mgr.GetPlayer().GetUniqueId() == x2c2_lastResolvedObj) {\n    if (auto* ent = mgr.ObjectById(GetOwnerId())) {\n      mgr.SendScriptMsg(ent, GetUniqueId(), EScriptObjectMessage::Action);\n    }\n  }\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CFlaahgraProjectile.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Weapon/CEnergyProjectile.hpp\"\n\nnamespace metaforce::MP1 {\n\nclass CFlaahgraProjectile : public CEnergyProjectile {\n  bool x3d8_bigStrike;\n\npublic:\n  DEFINE_ENTITY\n  CFlaahgraProjectile(bool bigStrike, const TToken<CWeaponDescription>& desc, const zeus::CTransform& xf,\n                      const CDamageInfo& damage, TUniqueId uid, TAreaId aid, TUniqueId owner);\n\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) override;\n};\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CFlaahgraTentacle.cpp",
    "content": "#include \"Runtime/MP1/World/CFlaahgraTentacle.hpp\"\n\n#include <array>\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Collision/CCollisionActor.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CScriptTrigger.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce::MP1 {\nnamespace {\nconstexpr std::string_view skpTentacleTip = \"Arm_12\"sv;\nconstexpr std::array<SSphereJointInfo, 3> skJointList{{\n    {\"Arm_8\", 2.f},\n    {\"Arm_10\", 1.2f},\n    {\"Arm_12\", 1.2f},\n}};\n} // Anonymous namespace\n\nCFlaahgraTentacle::CFlaahgraTentacle(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                                     const zeus::CTransform& xf, CModelData&& mData, const CPatternedInfo& pInfo,\n                                     const CActorParameters& actParms)\n: CPatterned(ECharacter::FlaahgraTentacle, uid, name, EFlavorType::Zero, info, xf, std::move(mData), pInfo,\n             EMovementType::Flyer, EColliderType::One, EBodyType::Restricted, actParms, EKnockBackVariant::Large) {\n  GetActorLights()->SetCastShadows(false);\n  x460_knockBackController.SetAutoResetImpulse(false);\n  CreateShadow(false);\n}\n\nvoid CFlaahgraTentacle::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CFlaahgraTentacle::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  switch (msg) {\n  case EScriptObjectMessage::Registered: {\n    x450_bodyController->Activate(mgr);\n    SetupCollisionManager(mgr);\n    break;\n  }\n  case EScriptObjectMessage::Deleted: {\n    x56c_collisionManager->Destroy(mgr);\n    if (const TCastToPtr<CScriptTrigger> trigger = mgr.ObjectById(x58c_triggerId)) {\n      trigger->SetForceVector(x580_forceVector);\n    }\n    break;\n  }\n  case EScriptObjectMessage::Touched: {\n    if (const TCastToConstPtr<CCollisionActor> colAct = mgr.GetObjectById(uid)) {\n      if (colAct->GetLastTouchedObject() == mgr.GetPlayer().GetUniqueId() && x420_curDamageRemTime <= 0.f) {\n        mgr.ApplyDamage(GetUniqueId(), mgr.GetPlayer().GetUniqueId(), GetUniqueId(), GetContactDamage(),\n                        CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), {});\n        x420_curDamageRemTime = x424_damageWaitTime;\n      }\n    }\n    break;\n  }\n  case EScriptObjectMessage::Play: {\n    x578_ = 0.04f;\n    break;\n  }\n  case EScriptObjectMessage::Deactivate: {\n    DeathDelete(mgr);\n    break;\n  }\n  case EScriptObjectMessage::Open: {\n    ExtractTentacle(mgr);\n    break;\n  }\n  case EScriptObjectMessage::Close: {\n    RetractTentacle(mgr);\n    break;\n  }\n  case EScriptObjectMessage::InitializedInArea: {\n    SaveBombSlotInfo(mgr);\n    break;\n  }\n  default:\n    break;\n  }\n\n  CPatterned::AcceptScriptMsg(msg, uid, mgr);\n}\n\nvoid CFlaahgraTentacle::Think(float dt, CStateManager& mgr) {\n  if (!GetActive()) {\n    return;\n  }\n\n  CPatterned::Think(dt, mgr);\n  x56c_collisionManager->Update(dt, mgr, CCollisionActorManager::EUpdateOptions::ObjectSpace);\n\n  if (x574_ > 0.f) {\n    x574_ -= dt;\n  }\n\n  if (x578_ > 0.f) {\n    x578_ -= dt;\n  }\n}\n\nvoid CFlaahgraTentacle::AddSphereCollisionList(const SSphereJointInfo* sphereJoints, size_t jointCount,\n                                               std::vector<CJointCollisionDescription>& outJoints) {\n  const CAnimData* animData = GetModelData()->GetAnimationData();\n\n  for (size_t i = 0; i < jointCount; ++i) {\n    const SSphereJointInfo& sphereJoint = sphereJoints[i];\n    const CSegId segId = animData->GetLocatorSegId(sphereJoint.name);\n\n    if (segId.IsInvalid()) {\n      continue;\n    }\n\n    outJoints.push_back(CJointCollisionDescription::SphereCollision(segId, sphereJoint.radius, sphereJoint.name, 10.f));\n  }\n}\n\nvoid CFlaahgraTentacle::SetupCollisionManager(CStateManager& mgr) {\n  std::vector<CJointCollisionDescription> jointList;\n  AddSphereCollisionList(skJointList.data(), skJointList.size(), jointList);\n  x56c_collisionManager =\n      std::make_unique<CCollisionActorManager>(mgr, GetUniqueId(), GetAreaIdAlways(), jointList, true);\n\n  for (u32 i = 0; i < x56c_collisionManager->GetNumCollisionActors(); ++i) {\n    const CJointCollisionDescription& desc = x56c_collisionManager->GetCollisionDescFromIndex(i);\n    if (const TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(desc.GetCollisionActorId())) {\n      colAct->SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(\n          {EMaterialTypes::Player}, {EMaterialTypes::Character, EMaterialTypes::CollisionActor,\n                                     EMaterialTypes::NoStaticCollision, EMaterialTypes::NoPlatformCollision}));\n      colAct->AddMaterial(EMaterialTypes::ScanPassthrough);\n      colAct->SetDamageVulnerability(*GetDamageVulnerability());\n\n      if (x57c_tentacleTipAct == kInvalidUniqueId && desc.GetName() == skpTentacleTip) {\n        x57c_tentacleTipAct = desc.GetCollisionActorId();\n      }\n    }\n  }\n\n  RemoveMaterial(EMaterialTypes::Solid, EMaterialTypes::Target, EMaterialTypes::Orbit, mgr);\n  AddMaterial(EMaterialTypes::Scannable, mgr);\n}\n\nzeus::CVector3f CFlaahgraTentacle::GetAimPosition(const CStateManager& mgr, float dt) const {\n  if (const TCastToConstPtr<CCollisionActor> colAct = mgr.GetObjectById(x57c_tentacleTipAct)) {\n    return colAct->GetTranslation();\n  }\n\n  return CPatterned::GetAimPosition(mgr, dt);\n}\n\nvoid CFlaahgraTentacle::ExtractTentacle(CStateManager& mgr) {\n  if (!Inside(mgr, 0.f)) {\n    return;\n  }\n\n  x58e_24_ = true;\n\n  if (const TCastToPtr<CScriptTrigger> trigger = mgr.ObjectById(x58c_triggerId)) {\n    trigger->SetForceVector(x580_forceVector);\n  }\n}\n\nvoid CFlaahgraTentacle::RetractTentacle(CStateManager& mgr) {\n  x450_bodyController->SetLocomotionType(pas::ELocomotionType::Crouch);\n  if (const TCastToPtr<CScriptTrigger> trigger = mgr.ObjectById(x58c_triggerId)) {\n    trigger->SetForceVector({});\n  }\n}\n\nvoid CFlaahgraTentacle::SaveBombSlotInfo(CStateManager& mgr) {\n  for (const SConnection& conn : GetConnectionList()) {\n    if (conn.x0_state != EScriptObjectState::Modify || conn.x4_msg != EScriptObjectMessage::ToggleActive) {\n      continue;\n    }\n\n    const TUniqueId uid = mgr.GetIdForScript(conn.x8_objId);\n    if (const TCastToConstPtr<CScriptTrigger> trigger = mgr.GetObjectById(uid)) {\n      x58c_triggerId = uid;\n      x580_forceVector = trigger->GetForceVector();\n      return;\n    }\n  }\n}\n\nbool CFlaahgraTentacle::ShouldAttack(CStateManager& mgr, float) {\n  if (x578_ > 0.f) {\n    return true;\n  }\n\n  if (x574_ > 0.f || mgr.GetPlayer().IsInWaterMovement()) {\n    return false;\n  }\n\n  if (const TCastToConstPtr<CCollisionActor> colAct = mgr.GetObjectById(x57c_tentacleTipAct)) {\n    const float mag = (mgr.GetPlayer().GetTranslation().toVec2f() - colAct->GetTranslation().toVec2f()).magSquared();\n    return mag >= (x2fc_minAttackRange * x2fc_minAttackRange) && mag <= (x300_maxAttackRange * x300_maxAttackRange);\n  }\n\n  return false;\n}\n\nvoid CFlaahgraTentacle::Attack(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x568_ = 0;\n  } else if (msg == EStateMsg::Update) {\n    if (x568_ == 0) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::MeleeAttack) {\n        x568_ = 2;\n      } else {\n        x450_bodyController->GetCommandMgr().DeliverCmd(\n            CBCMeleeAttackCmd((x578_ > 0.f ? pas::ESeverity::Zero : pas::ESeverity::One), {}));\n      }\n    } else if (x568_ == 2 &&\n               x450_bodyController->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::MeleeAttack) {\n      x568_ = 3;\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x574_ = (x308_attackTimeVariation * mgr.GetActiveRandom()->Float()) + x304_averageAttackTime;\n    x578_ = 0.f;\n  }\n}\n\nvoid CFlaahgraTentacle::Retreat(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Update) {\n    if (!x58e_24_) {\n      return;\n    }\n\n    if (x330_stateMachineState.GetTime() <= 1.f) {\n      return;\n    }\n\n    if (const TCastToConstPtr<CScriptTrigger> trigger = mgr.ObjectById(x58c_triggerId)) {\n      if (!trigger->IsPlayerTriggerProc()) {\n        x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n      }\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x58e_24_ = false;\n  }\n}\n\nvoid CFlaahgraTentacle::InActive(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x570_ = 0.f;\n  } else if (msg == EStateMsg::Update) {\n    if (Inside(mgr, 0.f)) {\n      return;\n    }\n\n    if (const TCastToConstPtr<CScriptTrigger> trigger = mgr.ObjectById(x58c_triggerId)) {\n      if (trigger->IsPlayerTriggerProc()) {\n        if (x570_ > 1.f) {\n          RetractTentacle(mgr);\n          ExtractTentacle(mgr);\n        } else {\n          x570_ += arg;\n        }\n      }\n    }\n  }\n}\n\nvoid CFlaahgraTentacle::Death(CStateManager& mgr, const zeus::CVector3f& direction, EScriptObjectState state) {\n  if (!IsAlive()) {\n    return;\n  }\n  x330_stateMachineState.SetState(mgr, *this, GetStateMachine(), \"Dead\");\n  if (GetBodyController()->GetPercentageFrozen() > 0.f) {\n    GetBodyController()->UnFreeze();\n  }\n  x400_25_alive = false;\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CFlaahgraTentacle.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <string_view>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Collision/CCollisionActorManager.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce::MP1 {\nclass CFlaahgraTentacle : public CPatterned {\n  s32 x568_ = -1;\n  std::unique_ptr<CCollisionActorManager> x56c_collisionManager;\n  float x570_ = 0.f;\n  float x574_ = 0.f;\n  float x578_ = 0.f;\n  TUniqueId x57c_tentacleTipAct = kInvalidUniqueId;\n  zeus::CVector3f x580_forceVector;\n  TUniqueId x58c_triggerId = kInvalidUniqueId;\n  bool x58e_24_ : 1 = false;\n\n  void AddSphereCollisionList(const SSphereJointInfo* sphereJoints, size_t jointCount,\n                              std::vector<CJointCollisionDescription>& outJoints);\n  void SetupCollisionManager(CStateManager&);\n  void ExtractTentacle(CStateManager&);\n  void RetractTentacle(CStateManager&);\n  void SaveBombSlotInfo(CStateManager&);\n\npublic:\n  DEFINE_PATTERNED(FlaahgraTentacle);\n  CFlaahgraTentacle(TUniqueId, std::string_view, const CEntityInfo&, const zeus::CTransform&, CModelData&&,\n                    const CPatternedInfo&, const CActorParameters&);\n\n  void Accept(IVisitor&) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void Think(float, CStateManager&) override;\n\n  zeus::CVector3f GetAimPosition(const CStateManager&, float) const override;\n\n  bool Inside(CStateManager&, float) override {\n    return x450_bodyController->GetLocomotionType() == pas::ELocomotionType::Crouch;\n  }\n  bool AnimOver(CStateManager&, float) override { return x568_ == 3; }\n  bool ShouldAttack(CStateManager&, float) override;\n\n  void Dead(CStateManager&, EStateMsg, float) override {\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Crouch);\n  }\n  void Attack(CStateManager&, EStateMsg, float) override;\n  void Retreat(CStateManager&, EStateMsg, float) override;\n  void InActive(CStateManager&, EStateMsg, float) override;\n  void Death(CStateManager& mgr, const zeus::CVector3f& direction, EScriptObjectState state) override;\n};\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CFlickerBat.cpp",
    "content": "#include \"Runtime/MP1/World/CFlickerBat.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Camera/CCameraManager.hpp\"\n#include \"Runtime/Camera/CGameCamera.hpp\"\n#include \"Runtime/Collision/CGameCollision.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce::MP1 {\n\nCFlickerBat::CFlickerBat(TUniqueId uid, std::string_view name, CPatterned::EFlavorType flavor, const CEntityInfo& info,\n                         const zeus::CTransform& xf, CModelData&& mData, const CPatternedInfo& pInfo,\n                         EColliderType colType, bool startsHidden, const CActorParameters& actParms,\n                         bool enableLineOfSight)\n: CPatterned(ECharacter::FlickerBat, uid, name, flavor, info, xf, std::move(mData), pInfo, EMovementType::Flyer,\n             colType, EBodyType::Pitchable, actParms, EKnockBackVariant::Small)\n, x574_state(EFlickerBatState(startsHidden))\n, x580_27_enableLOSCheck(enableLineOfSight) {\n  SetupPlayerCollision(startsHidden);\n  x3d8_xDamageThreshold = 0.f;\n  x402_27_noXrayModel = false;\n}\n\nvoid CFlickerBat::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CFlickerBat::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  CPatterned::AcceptScriptMsg(msg, uid, mgr);\n\n  if (msg == EScriptObjectMessage::Registered) {\n    RemoveMaterial(EMaterialTypes::Solid, mgr);\n    mgr.GetActiveFlickerBats().push_back(GetUniqueId());\n    x450_bodyController->Activate(mgr);\n    x450_bodyController->BodyStateInfo().SetMaximumPitch(zeus::degToRad(60.f));\n  } else if (msg == EScriptObjectMessage::Deleted) {\n    mgr.GetActiveFlickerBats().remove(GetUniqueId());\n  }\n}\n\nvoid CFlickerBat::Think(float dt, CStateManager& mgr) {\n  if (!GetActive())\n    return;\n\n  x402_29_drawParticles = mgr.GetPlayerState()->GetActiveVisor(mgr) != CPlayerState::EPlayerVisor::XRay;\n\n  if (GetFlickerBatState() == EFlickerBatState::FadeIn || GetFlickerBatState() == EFlickerBatState::FadeOut) {\n    x578_fadeRemTime -= dt;\n    if (x578_fadeRemTime <= 0.f) {\n      if (GetFlickerBatState() == EFlickerBatState::FadeIn)\n        SetFlickerBatState(EFlickerBatState::Visible, mgr);\n      else\n        SetFlickerBatState(EFlickerBatState::Hidden, mgr);\n    }\n  }\n\n  bool inXray = mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::XRay;\n  if (inXray != x580_24_wasInXray) {\n    if (inXray) {\n      if (GetFlickerBatState() == EFlickerBatState::Hidden) {\n        AddMaterial(EMaterialTypes::Target, EMaterialTypes::Orbit, mgr);\n        SetMuted(false);\n      }\n      CreateShadow(false);\n    } else {\n      if (GetFlickerBatState() == EFlickerBatState::Hidden) {\n        RemoveMaterial(EMaterialTypes::Target, EMaterialTypes::Orbit, mgr);\n        SetMuted(true);\n      }\n      CreateShadow(true);\n    }\n    x580_24_wasInXray = inXray;\n  }\n\n  float alpha = 0.f;\n  if (!x580_24_wasInXray) {\n    if (GetFlickerBatState() == EFlickerBatState::Visible)\n      alpha = 1.f;\n    else if (GetFlickerBatState() == EFlickerBatState::FadeIn || GetFlickerBatState() == EFlickerBatState::FadeOut) {\n      alpha = x578_fadeRemTime * x57c_ooFadeDur;\n      if (GetFlickerBatState() == EFlickerBatState::FadeIn)\n        alpha = 1.f - alpha;\n    }\n  } else\n    alpha = 1.f;\n\n  x42c_color.a() = alpha;\n  x94_simpleShadow->SetUserAlpha(alpha);\n\n  xe7_31_targetable = (mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::XRay\n                           ? true\n                           : x574_state == EFlickerBatState::Visible || x574_state == EFlickerBatState::FadeIn);\n  CPatterned::Think(dt, mgr);\n}\n\nvoid CFlickerBat::Render(CStateManager& mgr) {\n  if (!x580_24_wasInXray && x580_26_inLOS &&\n      (GetFlickerBatState() == EFlickerBatState::FadeIn || GetFlickerBatState() == EFlickerBatState::FadeOut)) {\n    float strength = 0.f;\n    if (GetFlickerBatState() == EFlickerBatState::FadeIn) {\n      strength = 4.f * (x578_fadeRemTime - .75f);\n    } else if (GetFlickerBatState() == EFlickerBatState::FadeOut) {\n      strength = 4.f * x578_fadeRemTime;\n    }\n    if (strength > 0.f && strength < 1.f)\n      mgr.DrawSpaceWarp(GetTranslation(), 0.3f * std::sin(M_PIF * strength));\n  }\n\n  if (x580_24_wasInXray) {\n    mgr.SetupFogForAreaNonCurrent(GetAreaIdAlways());\n    CPatterned::Render(mgr);\n    mgr.SetupFogForArea(GetAreaIdAlways());\n  } else\n    CPatterned::Render(mgr);\n}\n\nvoid CFlickerBat::Touch(CActor& act, CStateManager& mgr) {\n  if (TCastToPtr<CPlayer> pl = act) {\n    if (x420_curDamageRemTime <= 0.f) {\n      mgr.ApplyDamage(GetUniqueId(), pl->GetUniqueId(), GetUniqueId(), GetContactDamage(),\n                      CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), {});\n      x420_curDamageRemTime = x424_damageWaitTime;\n    }\n  }\n  CPatterned::Touch(act, mgr);\n}\n\nvoid CFlickerBat::DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) {\n  if (type == EUserEventType::FadeIn)\n    ToggleVisible(mgr);\n  else\n    CPatterned::DoUserAnimEvent(mgr, node, type, dt);\n}\n\nvoid CFlickerBat::Death(CStateManager& mgr, const zeus::CVector3f& direction, EScriptObjectState state) {\n  SetFlickerBatState(EFlickerBatState::Visible, mgr);\n  SetMuted(false);\n  CPatterned::Death(mgr, direction, state);\n}\n\nbool CFlickerBat::CanBeShot(const CStateManager& mgr, int) {\n  return (GetFlickerBatState() == EFlickerBatState::Visible || GetFlickerBatState() == EFlickerBatState::FadeIn ||\n          mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::XRay);\n}\n\nvoid CFlickerBat::Patrol(CStateManager& mgr, EStateMsg state, float dt) {\n  CPatterned::Patrol(mgr, state, dt);\n  x450_bodyController->GetCommandMgr().DeliverFaceVector((x2e0_destPos - GetTranslation()).normalized());\n}\n\nvoid CFlickerBat::Attack(CStateManager&, EStateMsg msg, float) {\n  if (msg == EStateMsg::Update) {\n    x450_bodyController->GetCommandMgr().DeliverCmd(\n        CBCLocomotionCmd((x2e0_destPos - GetTranslation()).normalized(), {}, 1.f));\n  }\n}\n\nvoid CFlickerBat::Shuffle(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    CRandom16* rnd = mgr.GetActiveRandom();\n    SetDestPos(GetTranslation() +\n               zeus::CVector3f(100.f * rnd->Float() - 50.f, 100.f * rnd->Float() - 50.f, 100.f * rnd->Float() - 50.f));\n  } else if (msg == EStateMsg::Update) {\n    ApproachDest(mgr);\n  }\n}\n\nvoid CFlickerBat::Taunt(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    NotifyNeighbors(mgr);\n    x400_24_hitByPlayerProjectile = false;\n  }\n}\n\nbool CFlickerBat::InPosition(CStateManager& mgr, float arg) {\n  return GetTransform().frontVector().dot(mgr.GetPlayer().GetAimPosition(mgr, 0.f) - GetTranslation()) > 0.f;\n}\n\nbool CFlickerBat::HearShot(CStateManager&, float) {\n  if (x580_25_heardShot) {\n    x580_25_heardShot = false;\n    return true;\n  }\n\n  return false;\n}\n\nvoid CFlickerBat::SetFlickerBatState(EFlickerBatState state, CStateManager& mgr) {\n  if (state == x574_state)\n    return;\n\n  FlickerBatStateChanged(state, mgr);\n  x574_state = state;\n}\n\nvoid CFlickerBat::FlickerBatStateChanged(EFlickerBatState state, CStateManager& mgr) {\n  if (state == EFlickerBatState::Visible) {\n    if (mgr.GetPlayerState()->GetCurrentVisor() != CPlayerState::EPlayerVisor::XRay)\n      CreateShadow(true);\n\n    AddMaterial(EMaterialTypes::Target, mgr);\n  } else if (state == EFlickerBatState::Hidden) {\n    SetMuted(true);\n    RemoveMaterial(EMaterialTypes::Target, mgr);\n  } else if (state == EFlickerBatState::FadeIn) {\n    if (mgr.GetPlayerState()->GetCurrentVisor() != CPlayerState::EPlayerVisor::XRay) {\n      CreateShadow(true);\n      SetMuted(false);\n    }\n\n    CheckFadeEffect(mgr);\n    SetupPlayerCollision(true);\n  } else if (state == EFlickerBatState::FadeOut) {\n    if (mgr.GetPlayerState()->GetCurrentVisor() != CPlayerState::EPlayerVisor::XRay) {\n      CreateShadow(false);\n    }\n\n    CheckFadeEffect(mgr);\n    SetupPlayerCollision(false);\n  }\n}\n\nvoid CFlickerBat::CheckFadeEffect(CStateManager& mgr) {\n  if (!x580_27_enableLOSCheck) {\n    x580_26_inLOS = false;\n    return;\n  }\n\n  zeus::CVector3f camPos = mgr.GetCameraManager()->GetCurrentCamera(mgr)->GetTranslation();\n  zeus::CVector3f diff = GetBoundingBox().center() - camPos;\n  float mag = diff.magnitude();\n  diff *= zeus::CVector3f(1.f / mag);\n  x580_26_inLOS = CGameCollision::RayStaticIntersectionBool(mgr, camPos, diff, mag,\n                                                            CMaterialFilter::MakeExclude({EMaterialTypes::SeeThrough}));\n}\n\nvoid CFlickerBat::NotifyNeighbors(CStateManager& mgr) {\n  for (TUniqueId uid : mgr.GetActiveFlickerBats()) {\n    if (CFlickerBat* flick = CPatterned::CastTo<CFlickerBat>(mgr.ObjectById(uid)))\n      if ((GetTranslation() - flick->GetTranslation()).magnitude() < 100.f)\n        flick->SetHeardShot(true);\n  }\n}\n\nvoid CFlickerBat::ToggleVisible(CStateManager& mgr) {\n  if (GetFlickerBatState() == EFlickerBatState::Visible || GetFlickerBatState() == EFlickerBatState::FadeIn)\n    SetFlickerBatState(EFlickerBatState::FadeOut, mgr);\n  else\n    SetFlickerBatState(EFlickerBatState::FadeIn, mgr);\n\n  x578_fadeRemTime = 1.f;\n  x57c_ooFadeDur = 1.f / x578_fadeRemTime;\n}\n} // namespace metaforce::MP1"
  },
  {
    "path": "Runtime/MP1/World/CFlickerBat.hpp",
    "content": "#pragma once\n\n#include \"Runtime/World/CPatterned.hpp\"\n\nnamespace metaforce::MP1 {\nclass CFlickerBat final : public CPatterned {\npublic:\n  enum class EFlickerBatState { Visible, Hidden, FadeIn, FadeOut };\n\nprivate:\n  float x568_ = 0.f;\n  float x56c_ = 0.f;\n  float x570_ = 0.f;\n  EFlickerBatState x574_state;\n  float x578_fadeRemTime = 1.f;\n  float x57c_ooFadeDur = 0.f;\n  bool x580_24_wasInXray : 1 = false;\n  bool x580_25_heardShot : 1 = false;\n  bool x580_26_inLOS : 1 = false;\n  bool x580_27_enableLOSCheck : 1;\n\n  void NotifyNeighbors(CStateManager&);\n  void ToggleVisible(CStateManager&);\n  void SetHeardShot(bool heardShot) { x580_25_heardShot = heardShot; }\n\npublic:\n  DEFINE_PATTERNED(FlickerBat);\n  CFlickerBat(TUniqueId, std::string_view name, EFlavorType, const CEntityInfo&, const zeus::CTransform&, CModelData&&,\n              const CPatternedInfo&, EColliderType, bool, const CActorParameters&, bool);\n\n  void Accept(IVisitor&) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void Think(float, CStateManager&) override;\n  void Render(CStateManager&) override;\n  void Touch(CActor&, CStateManager&) override;\n  void DoUserAnimEvent(CStateManager&, const CInt32POINode&, EUserEventType, float dt) override;\n  void Death(CStateManager& mgr, const zeus::CVector3f& direction, EScriptObjectState state) override;\n  bool CanBeShot(const CStateManager&, int) override;\n  void Patrol(CStateManager&, EStateMsg, float) override;\n  void Attack(CStateManager&, EStateMsg, float) override;\n  void Shuffle(CStateManager&, EStateMsg, float) override;\n  void Taunt(CStateManager&, EStateMsg, float) override;\n  bool InPosition(CStateManager&, float) override;\n  bool HearShot(CStateManager&, float) override;\n\n  EFlickerBatState GetFlickerBatState() const { return x574_state; }\n  void SetFlickerBatState(EFlickerBatState state, CStateManager&);\n  void FlickerBatStateChanged(EFlickerBatState, CStateManager&);\n  void CheckFadeEffect(CStateManager&);\n};\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CFlyingPirate.cpp",
    "content": "#include \"Runtime/MP1/World/CFlyingPirate.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Character/CPASAnimParmData.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/MP1/World/CSpacePirate.hpp\"\n#include \"Runtime/Weapon/CEnergyProjectile.hpp\"\n#include \"Runtime/Weapon/CGameProjectile.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/World/CExplosion.hpp\"\n#include \"Runtime/World/CPatternedInfo.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CScriptCoverPoint.hpp\"\n#include \"Runtime/World/CScriptWater.hpp\"\n#include \"Runtime/World/CScriptWaypoint.hpp\"\n#include \"Runtime/World/CTeamAiMgr.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\n#include <zeus/CColor.hpp>\n\nnamespace metaforce::MP1 {\nnamespace {\nconstexpr std::array<SBurst, 6> skBurstsFlying{{\n    {4, {3, 4, 11, 12, -1, 0, 0, 0}, 0.1f, 0.05f},\n    {20, {2, 3, 4, 5, -1, 0, 0, 0}, 0.1f, 0.05f},\n    {20, {10, 11, 12, 13, -1, 0, 0, 0}, 0.1f, 0.05f},\n    {25, {15, 16, 1, 2, -1, 0, 0, 0}, 0.1f, 0.05f},\n    {25, {5, 6, 7, 8, -1, 0, 0, 0}, 0.1f, 0.05f},\n    {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0.000000, 0.000000},\n}};\n\nconstexpr std::array<SBurst, 6> skBurstsFlyingOutOfView{{\n    {5, {3, 4, 8, 12, -1, 0, 0, 0}, 0.1f, 0.05f},\n    {10, {2, 3, 4, 5, -1, 0, 0, 0}, 0.1f, 0.05f},\n    {10, {10, 11, 12, 13, -1, 0, 0, 0}, 0.1f, 0.05f},\n    {40, {15, 16, 1, 2, -1, 0, 0, 0}, 0.1f, 0.05f},\n    {35, {5, 6, 7, 8, -1, 0, 0, 0}, 0.1f, 0.05f},\n    {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0.000000, 0.000000},\n}};\n\nconstexpr std::array<SBurst, 5> skBurstsLanded{{\n    {30, {3, 4, 5, 11, 12, 4, -1, 0}, 0.1f, 0.05f},\n    {20, {2, 3, 4, 5, 4, 3, -1, 0}, 0.1f, 0.05f},\n    {20, {5, 4, 3, 13, 12, 11, -1, 0}, 0.1f, 0.05f},\n    {30, {1, 2, 3, 4, 5, 6, -1, 0}, 0.1f, 0.05f},\n    {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0.000000, 0.000000},\n}};\n\nconstexpr std::array<SBurst, 5> skBurstsLandedOutOfView{{\n    {10, {6, 5, 4, 14, 13, 12, -1, 0}, 0.1f, 0.05f},\n    {20, {14, 13, 12, 11, 10, 9, -1, 0}, 0.1f, 0.05f},\n    {20, {14, 15, 16, 11, 10, 9, -1, 0}, 0.1f, 0.05f},\n    {50, {11, 10, 9, 8, 7, 6, -1, 0}, 0.1f, 0.05f},\n    {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0.000000, 0.000000},\n}};\n\nconstexpr std::array<const SBurst*, 5> skBursts{\n    skBurstsFlying.data(),\n    skBurstsFlyingOutOfView.data(),\n    skBurstsLanded.data(),\n    skBurstsLandedOutOfView.data(),\n    nullptr,\n};\n\nconstexpr std::array<std::string_view, 15> skParts{\n    \"Collar\"sv, \"Head_1\"sv, \"R_shoulder\"sv, \"R_elbow\"sv, \"R_wrist\"sv, \"L_shoulder\"sv, \"L_elbow\"sv,     \"L_wrist\"sv,\n    \"R_hip\"sv,  \"R_knee\"sv, \"R_ankle\"sv,    \"L_hip\"sv,   \"L_knee\"sv,  \"L_ankle\"sv,    \"rocket_LCTR\"sv,\n};\n\nconstexpr std::array<float, 15> skRadii{\n    0.45f, 0.52f, 0.35f, 0.1f, 0.15f, 0.35f, 0.1f, 0.15f, 0.15f, 0.15f, 0.15f, 0.15f, 0.15f, 0.15f, 0.35f,\n};\n} // namespace\n\nCFlyingPirate::CFlyingPirateData::CFlyingPirateData(CInputStream& in, u32 propCount)\n: x0_maxCoverDistance(in.ReadFloat())\n, x4_hearingDistance(in.ReadFloat())\n, x8_type(EFlyingPirateType(in.ReadLong()))\n, xc_gunProjectileInfo(in)\n, x34_gunSfx(CSfxManager::TranslateSFXID(in.ReadLong()))\n, x38_altProjectileInfo1(in)\n, x60_altProjectileInfo2(CAssetId(in), {})\n, x88_knockBackDelay(in.ReadFloat())\n, x8c_flyingHeight(in.ReadFloat())\n, x90_particleGenDesc(g_SimplePool->GetObj({SBIG('PART'), CAssetId(in)}))\n, x9c_dInfo(in)\n, xb8_(in.ReadFloat())\n, xbc_(in.ReadFloat())\n, xc0_(in.ReadFloat())\n, xc4_(in.ReadFloat())\n, xc8_ragDollSfx1(CSfxManager::TranslateSFXID(in.ReadLong()))\n, xca_ragDollSfx2(CSfxManager::TranslateSFXID(in.ReadLong()))\n, xcc_coverCheckChance(in.ReadFloat())\n, xd0_(in.ReadFloat())\n, xd4_(in.ReadFloat())\n, xd8_particleGen1(in)\n, xdc_particleGen2(in)\n, xe0_particleGen3(in)\n, xe4_knockBackSfx(CSfxManager::TranslateSFXID(in.ReadLong()))\n, xe6_deathSfx(CSfxManager::TranslateSFXID(in.ReadLong()))\n, xe8_aggressionChance(in.ReadFloat())\n, xec_(in.ReadFloat())\n, xf0_projectileHomingDistance(propCount < 36 ? 0.f : in.ReadFloat()) {\n  xc_gunProjectileInfo.Token().Lock();\n  x38_altProjectileInfo1.Token().Lock();\n  x60_altProjectileInfo2.Token().Lock();\n}\n\nCFlyingPirate::CFlyingPirateRagDoll::CFlyingPirateRagDoll(CStateManager& mgr, CFlyingPirate* actor, u16 w1, u16 w2)\n: CRagDoll(-actor->GetGravityConstant(), 3.f, 8.f, 0)\n, x6c_actor(actor)\n, x88_sfx(w1)\n, x9c_(w2)\n, xa4_(actor->GetDestPos() - actor->GetTranslation()) {\n  actor->RemoveMaterial(EMaterialTypes::Solid, EMaterialTypes::AIBlock, EMaterialTypes::GroundCollider, mgr);\n  actor->HealthInfo(mgr)->SetHP(-1.f);\n  SetNumParticles(skParts.size());\n  SetNumLengthConstraints(45);\n  SetNumJointConstraints(4);\n  CModelData* modelData = actor->GetModelData();\n  CAnimData* animData = modelData->GetAnimationData();\n  const zeus::CVector3f& scale = modelData->GetScale();\n  animData->BuildPose();\n  const zeus::CVector3f center = actor->GetBoundingBox().center();\n  for (size_t i = 0; i < skParts.size(); ++i) {\n    const CSegId id = animData->GetLocatorSegId(skParts[i]);\n    AddParticle(id, center, actor->GetTransform() * (scale * animData->GetPose().GetOffset(id)),\n                skRadii[i] * scale.z());\n  }\n  SatisfyWorldConstraintsOnConstruction(mgr);\n  AddLengthConstraint(0, 1);\n  AddLengthConstraint(0, 2);\n  AddLengthConstraint(0, 8);\n  AddLengthConstraint(0, 11);\n  AddLengthConstraint(0, 5);\n  AddLengthConstraint(2, 3);\n  AddLengthConstraint(3, 4);\n  AddLengthConstraint(5, 6);\n  AddLengthConstraint(6, 7);\n  AddLengthConstraint(2, 5);\n  AddLengthConstraint(2, 8);\n  AddLengthConstraint(2, 11);\n  AddLengthConstraint(5, 8);\n  AddLengthConstraint(5, 11);\n  AddLengthConstraint(8, 11);\n  AddLengthConstraint(8, 9);\n  AddLengthConstraint(9, 10);\n  AddLengthConstraint(11, 12);\n  AddLengthConstraint(12, 13);\n  AddLengthConstraint(14, 0);\n  AddLengthConstraint(14, 2);\n  AddLengthConstraint(14, 5);\n  AddLengthConstraint(14, 8);\n  AddLengthConstraint(14, 11);\n  AddMinLengthConstraint(1, 8, x14_lengthConstraints[2].GetLength());\n  AddMinLengthConstraint(1, 11, x14_lengthConstraints[3].GetLength());\n  AddMinLengthConstraint(4, 2, x14_lengthConstraints[5].GetLength());\n  AddMinLengthConstraint(7, 5, x14_lengthConstraints[7].GetLength());\n  AddMinLengthConstraint(3, 5, 0.5f * x14_lengthConstraints[5].GetLength() + x14_lengthConstraints[9].GetLength());\n  AddMinLengthConstraint(6, 2, 0.5f * x14_lengthConstraints[7].GetLength() + x14_lengthConstraints[9].GetLength());\n  AddMinLengthConstraint(4, 5, 0.5f * x14_lengthConstraints[5].GetLength() + x14_lengthConstraints[9].GetLength());\n  AddMinLengthConstraint(7, 2, 0.5f * x14_lengthConstraints[7].GetLength() + x14_lengthConstraints[9].GetLength());\n  AddMinLengthConstraint(10, 8, x14_lengthConstraints[15].GetLength());\n  AddMinLengthConstraint(13, 11, x14_lengthConstraints[17].GetLength());\n  AddMinLengthConstraint(9, 2, 0.707f * x14_lengthConstraints[15].GetLength() + x14_lengthConstraints[10].GetLength());\n  AddMinLengthConstraint(12, 5, 0.707f * x14_lengthConstraints[17].GetLength() + x14_lengthConstraints[13].GetLength());\n  AddMinLengthConstraint(9, 11, x14_lengthConstraints[15].GetLength());\n  AddMinLengthConstraint(12, 8, x14_lengthConstraints[17].GetLength());\n  AddMinLengthConstraint(10, 0, x14_lengthConstraints[2].GetLength() + x14_lengthConstraints[15].GetLength());\n  AddMinLengthConstraint(13, 0, x14_lengthConstraints[3].GetLength() + x14_lengthConstraints[17].GetLength());\n  AddMinLengthConstraint(10, 13, x14_lengthConstraints[14].GetLength());\n  AddMinLengthConstraint(9, 12, 0.5f * x14_lengthConstraints[14].GetLength());\n  AddMinLengthConstraint(10, 12, 0.5f * x14_lengthConstraints[14].GetLength());\n  AddMinLengthConstraint(13, 9, 0.5f * x14_lengthConstraints[14].GetLength());\n  AddMinLengthConstraint(10, 13, 0.5f * x14_lengthConstraints[14].GetLength());\n  AddJointConstraint(8, 2, 5, 8, 9, 10);\n  AddJointConstraint(11, 2, 5, 11, 12, 13);\n  AddJointConstraint(2, 11, 5, 2, 3, 4);\n  AddJointConstraint(5, 2, 8, 5, 6, 7);\n}\n\nvoid CFlyingPirate::CFlyingPirateRagDoll::PreRender(const zeus::CVector3f& v, CModelData& mData) {\n  if (!x68_25_over) {\n    CAnimData* const animData = mData.GetAnimationData();\n    const CCharLayoutInfo& layout = animData->GetCharLayoutInfo();\n    CHierarchyPoseBuilder& poseBuilder = animData->PoseBuilder();\n    for (const auto& id : layout.GetSegIdList().GetList()) {\n      if (layout.GetRootNode()->GetBoneMap()[id].x10_children.size() > 1) {\n        poseBuilder.GetTreeMap()[id].x4_rotation = zeus::CQuaternion();\n      }\n    }\n\n    CHierarchyPoseBuilder::CTreeNode& skeletonRoot =\n        poseBuilder.GetTreeMap()[animData->GetLocatorSegId(\"Skeleton_Root\"sv)];\n    const zeus::CVector3f& rHipPos = x4_particles[8].GetPosition();      // R_hip\n    const zeus::CVector3f& lHipPos = x4_particles[11].GetPosition();     // L_hip\n    const zeus::CVector3f& rShoulderPos = x4_particles[2].GetPosition(); // R_shoulder\n    const zeus::CVector3f& lShoulderPos = x4_particles[5].GetPosition(); // L_shoulder\n    const zeus::CVector3f& collarPos = x4_particles[0].GetPosition();    // Collar\n    skeletonRoot.x14_offset = (0.5f * (rHipPos + lHipPos) - v) / mData.GetScale();\n\n    const zeus::CVector3f& rootRight = rShoulderPos - lShoulderPos;\n    const zeus::CVector3f& rootUp = (collarPos - (rHipPos + lHipPos) * 0.5f).normalized();\n    const zeus::CVector3f& rootFore = rootUp.cross(rootRight).normalized();\n    const zeus::CQuaternion& rootRot = zeus::CMatrix3f(rootFore.cross(rootUp), rootFore, rootUp);\n    skeletonRoot.x4_rotation = rootRot;\n\n    const CRagDollParticle& head = x4_particles[1]; // Head_1\n    const zeus::CVector3f& headRestVec = layout.GetFromParentUnrotated(head.GetBone());\n    poseBuilder.GetTreeMap()[head.GetBone()].x4_rotation = zeus::CQuaternion::shortestRotationArc(\n        headRestVec, rootRot.inverse().transform(head.GetPosition() - collarPos));\n\n    BoneAlign(poseBuilder, layout, 3, 4, rootRot * BoneAlign(poseBuilder, layout, 2, 3, rootRot));\n    BoneAlign(poseBuilder, layout, 6, 7, rootRot * BoneAlign(poseBuilder, layout, 5, 6, rootRot));\n    BoneAlign(poseBuilder, layout, 9, 10, rootRot * BoneAlign(poseBuilder, layout, 8, 9, rootRot));\n    BoneAlign(poseBuilder, layout, 12, 13, rootRot * BoneAlign(poseBuilder, layout, 11, 12, rootRot));\n\n    animData->MarkPoseDirty();\n  }\n}\n\nvoid CFlyingPirate::CFlyingPirateRagDoll::Prime(CStateManager& mgr, const zeus::CTransform& xf, CModelData& mData) {\n  if (x6c_actor->x6a1_30_spinToDeath) {\n    xa0_ = CSfxManager::AddEmitter(x9c_, x6c_actor->GetTranslation(), zeus::skZero3f, true, true, 0x7f, kInvalidAreaId);\n  }\n  CRagDoll::Prime(mgr, xf, mData);\n}\n\nvoid CFlyingPirate::CFlyingPirateRagDoll::Update(CStateManager& mgr, float dt, float waterTop) {\n  if (!x68_25_over) {\n    if (x6c_actor->x6a1_30_spinToDeath) {\n      x84_ -= dt;\n      const zeus::CVector3f v9c = (x6c_actor->x2e0_destPos - x4_particles[14].GetPosition()).normalized();\n      x74_ = zeus::CVector3f::slerp(x74_, v9c, zeus::degToRad(360.f * dt));\n      x70_ = 25.f;\n      zeus::CVector3f mul = x70_ * x74_;\n      if (x84_ <= 0.f) {\n        x4_particles[14].Velocity() += 11.f * mul;\n      } else {\n        x4_particles[14].Velocity() += 25.f * mul;\n      }\n      zeus::CVector3f inv = -4 * mul;\n      x4_particles[4].Velocity() += -mul;\n      x4_particles[7].Velocity() += -mul;\n      x4_particles[10].Velocity() += inv;\n      x4_particles[10].Velocity() += inv;\n      x4_particles[1].Velocity() += -mul;\n\n      x80_ = std::min(1000.f * dt + x80_, 1000.f);\n\n      zeus::CVector3f vc0 = ((x4_particles[5].Position() - x4_particles[2].Position())\n                                 .cross(x4_particles[8].Position() - x4_particles[2].Position()) +\n                             0.25f * (x4_particles[2].Position() - x4_particles[5].Position()))\n                                .normalized() *\n                            x80_;\n      x4_particles[2].Velocity() += vc0;\n      x4_particles[5].Velocity() += -vc0;\n\n      x44_normalGravity = 0.f;\n      CSfxManager::UpdateEmitter(xa0_, x6c_actor->GetTranslation(), x58_averageVel, 1.f);\n    }\n\n    // Collar-hips weighted center\n    zeus::CVector3f oldTorsoCenter = x4_particles[8].GetPosition() * 0.25f + x4_particles[11].GetPosition() * 0.25f +\n                                     x4_particles[0].GetPosition() * 0.5f;\n    oldTorsoCenter.z() = std::min({x4_particles[0].GetPosition().z() - x4_particles[0].GetRadius(),\n                                   x4_particles[8].GetPosition().z() - x4_particles[8].GetRadius(),\n                                   x4_particles[11].GetPosition().z() - x4_particles[11].GetRadius()});\n\n    CRagDoll::Update(mgr, dt, waterTop);\n\n    // Collar-hips weighted center\n    zeus::CVector3f newTorsoCenter = x4_particles[8].GetPosition() * 0.25f + x4_particles[11].GetPosition() * 0.25f +\n                                     x4_particles[0].GetPosition() * 0.5f;\n    newTorsoCenter.z() = std::min({x4_particles[0].GetPosition().z() - x4_particles[0].GetRadius(),\n                                   x4_particles[8].GetPosition().z() - x4_particles[8].GetRadius(),\n                                   x4_particles[11].GetPosition().z() - x4_particles[11].GetRadius()});\n    x6c_actor->SetTransform({zeus::CMatrix3f(false), newTorsoCenter});\n    x6c_actor->SetVelocityWR((newTorsoCenter - oldTorsoCenter) * (1.f / dt));\n\n    if (x6c_actor->x6a1_30_spinToDeath) {\n      if ((newTorsoCenter - x6c_actor->GetDestPos()).magSquared() > 0.f) {\n        x6c_actor->x88c_ragDollTimer = 0.5f * dt;\n      }\n    }\n\n    x8c_ -= dt;\n    if (x54_impactVel > 2.f && x8c_ < 0.f) {\n      if (xb0_24_ || (x6c_actor->GetTranslation() - x90_).magSquared() > 0.1f) {\n        float vol = std::min(10.f * x54_impactVel, 127.f) / 127.f;\n        CSfxManager::AddEmitter(x88_sfx, x6c_actor->GetTranslation(), zeus::skZero3f, vol, true, false, 0x7f,\n                                kInvalidAreaId);\n        x8c_ = 0.222f * mgr.GetActiveRandom()->Float() + 0.222f;\n        xb0_24_ = false;\n        x90_ = x6c_actor->GetTranslation();\n      }\n    }\n  } else {\n    x6c_actor->SetMomentumWR(zeus::skZero3f);\n    x6c_actor->Stop();\n  }\n}\n\nCFlyingPirate::CFlyingPirate(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                             CModelData&& mData, const CActorParameters& actParms, const CPatternedInfo& pInfo,\n                             CInputStream& in, u32 propCount)\n: CPatterned(ECharacter::FlyingPirate, uid, name, EFlavorType::Zero, info, xf, std::move(mData), pInfo,\n             EMovementType::Ground, EColliderType::One, EBodyType::NewFlyer, actParms, EKnockBackVariant::Medium)\n, x568_data(in, propCount)\n, x6a0_24_isFlyingPirate(x568_data.x8_type == EFlyingPirateType::FlyingPirate)\n, x6a0_25_isAquaPirate(x568_data.x8_type == EFlyingPirateType::AquaPirate)\n, x6a8_pathFindSearch(nullptr, x6a0_25_isAquaPirate ? 2 : 3, pInfo.GetHalfExtent(), pInfo.GetHeight(),\n                      pInfo.GetPathfindingIndex())\n, x794_initialHealth(pInfo.GetHealthInfo().GetHP())\n, x7a0_boneTracking(*GetModelData()->GetAnimationData(), \"Head_1\"sv, zeus::degToRad(80.f), zeus::degToRad(180.f),\n                    EBoneTrackingFlags::None)\n, x7ec_burstFire(skBursts.data(), 0) {\n  const CModelData* modelData = GetModelData();\n  const CAnimData* animData = modelData->GetAnimationData();\n  x798_headSegId = animData->GetLocatorSegId(\"Head_1\"sv);\n  x7e0_gunSegId = animData->GetLocatorSegId(\"L_gun_LCTR\"sv);\n  x864_missileSegments.push_back(animData->GetLocatorSegId(\"L_Missile_LCTR\"sv));\n  x864_missileSegments.push_back(animData->GetLocatorSegId(\"R_Missile_LCTR\"sv));\n  x850_height = modelData->GetScale().x() *\n                GetAnimationDistance(\n                    CPASAnimParmData{pas::EAnimationState::Step, CPASAnimParm::FromEnum(3), CPASAnimParm::FromEnum(1)});\n  if (x568_data.xd8_particleGen1.IsValid() && x568_data.xdc_particleGen2.IsValid() &&\n      x568_data.xe0_particleGen3.IsValid()) {\n    x65c_particleGenDescs.push_back(g_SimplePool->GetObj({SBIG('PART'), x568_data.xd8_particleGen1}));\n    x65c_particleGenDescs.push_back(g_SimplePool->GetObj({SBIG('PART'), x568_data.xdc_particleGen2}));\n    x65c_particleGenDescs.push_back(g_SimplePool->GetObj({SBIG('PART'), x568_data.xe0_particleGen3}));\n    for (const auto& desc : x65c_particleGenDescs) {\n      x684_particleGens.push_back(std::make_unique<CElementGen>(desc));\n    }\n  }\n  x460_knockBackController.SetLocomotionDuringElectrocution(true);\n}\n\nvoid CFlyingPirate::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  if (msg == EScriptObjectMessage::Alert) {\n    if (GetActive()) {\n      x400_24_hitByPlayerProjectile = true;\n    }\n  } else if (msg == EScriptObjectMessage::Activate) {\n    AddToTeam(mgr);\n  } else if (msg == EScriptObjectMessage::Deleted) {\n    RemoveFromTeam(mgr);\n  }\n  CPatterned::AcceptScriptMsg(msg, uid, mgr);\n  switch (msg) {\n  case EScriptObjectMessage::SetToZero:\n    x6a2_28_ = true;\n    break;\n  case EScriptObjectMessage::Start:\n    x6a1_31_stopped = false;\n    break;\n  case EScriptObjectMessage::Stop:\n    x6a1_31_stopped = true;\n    break;\n  case EScriptObjectMessage::OnFloor:\n    x7ec_burstFire.SetBurstType(2);\n    break;\n  case EScriptObjectMessage::Falling:\n    if (x450_bodyController->GetPercentageFrozen() == 0.f && !x400_28_pendingMassiveDeath && !x6a1_30_spinToDeath) {\n      SetMomentumWR({0.f, 0.f, -GetGravityConstant() * xe8_mass});\n    }\n    x7ec_burstFire.SetBurstType(0);\n    break;\n  case EScriptObjectMessage::Registered:\n    x86c_ = x568_data.xc0_ * mgr.GetActiveRandom()->Float() + x568_data.xbc_;\n    break;\n  case EScriptObjectMessage::InitializedInArea:\n    for (const auto& conn : x20_conns) {\n      if (conn.x0_state == EScriptObjectState::Retreat) {\n        if (TCastToPtr<CScriptCoverPoint> cover = mgr.ObjectById(mgr.GetIdForScript(conn.x8_objId))) {\n          cover->Reserve(x8_uid);\n        }\n      } else if (conn.x0_state == EScriptObjectState::Patrol && conn.x4_msg == EScriptObjectMessage::Follow) {\n        x6a0_27_canPatrol = true;\n      } else if (conn.x0_state == EScriptObjectState::Attack && conn.x4_msg == EScriptObjectMessage::Action) {\n        x85c_attackObjectId = mgr.GetIdForScript(conn.x8_objId);\n      }\n    }\n    x6a8_pathFindSearch.SetArea(mgr.GetWorld()->GetAreaAlways(x4_areaId)->GetPostConstructed()->x10bc_pathArea);\n    if (x30_24_active) {\n      AddToTeam(mgr);\n    }\n    UpdateParticleEffects(mgr, 0.f, x6a0_24_isFlyingPirate);\n    GetModelData()->GetAnimationData()->SetParticleEffectState(\"Eyes\"sv, true, mgr);\n    break;\n  case EScriptObjectMessage::Jumped:\n    if (CScriptCoverPoint* cover = GetCoverPoint(mgr, x6a4_currentCoverPoint)) {\n      x328_25_verticalMovement = false;\n      SetMomentumWR({0.f, 0.f, -xe8_mass * GetGravityConstant()});\n      AddMaterial(EMaterialTypes::GroundCollider, mgr);\n      SetDestPos(cover->GetTranslation());\n      const zeus::CVector3f dist = cover->GetTranslation() - GetTranslation();\n      if (dist.z() < 0.f) {\n        zeus::CVector3f velocity = GetVelocity();\n        const float gravity = GetGravityConstant();\n        float fVar2 = -(2.f * gravity) * dist.z() - (velocity.z() * velocity.z());\n        float fVar3 = fVar2 != 0.f ? fVar2 * (1.f / std::sqrt(fVar2)) : 0.f;\n        float dVar9 = (-velocity.z() + fVar3) / gravity;\n        if (0.f < dVar9) {\n          zeus::CVector2f dist2f(dist.x(), dist.y());\n          const zeus::CVector2f normal = dist2f.normalized();\n          const float mag = dist2f.magnitude();\n          velocity.x() = (mag / dVar9) * normal.x();\n          velocity.y() = (mag / dVar9) * normal.y();\n          SetVelocityWR(velocity);\n\n          x870_.zeroOut();\n          x87c_.zeroOut();\n          x898_ = 1.f;\n        }\n      }\n    }\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CFlyingPirate::AddToTeam(CStateManager& mgr) {\n  if (x890_teamAiMgr == kInvalidUniqueId) {\n    x890_teamAiMgr = CTeamAiMgr::GetTeamAiMgr(*this, mgr);\n  }\n  if (x890_teamAiMgr != kInvalidUniqueId) {\n    if (TCastToPtr<CTeamAiMgr> team = mgr.ObjectById(x890_teamAiMgr)) {\n      team->AssignTeamAiRole(*this, CTeamAiRole::ETeamAiRole::Melee, CTeamAiRole::ETeamAiRole::Ranged,\n                             CTeamAiRole::ETeamAiRole::Invalid);\n    }\n  }\n}\n\nvoid CFlyingPirate::RemoveFromTeam(CStateManager& mgr) {\n  if (x890_teamAiMgr != kInvalidUniqueId) {\n    if (TCastToPtr<CTeamAiMgr> team = mgr.ObjectById(x890_teamAiMgr)) {\n      if (team->IsPartOfTeam(GetUniqueId())) {\n        team->RemoveTeamAiRole(GetUniqueId());\n        x890_teamAiMgr = kInvalidUniqueId;\n      }\n    }\n  }\n}\n\nvoid CFlyingPirate::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) {\n  for (const auto& gen : x684_particleGens) {\n    if (frustum.aabbFrustumTest(GetBoundingBox())) {\n      g_Renderer->AddParticleGen(*gen);\n    }\n  }\n  CPatterned::AddToRenderer(frustum, mgr);\n}\n\nbool CFlyingPirate::AggressionCheck(CStateManager& mgr, float) { return x6a2_24_aggressive; }\n\nbool CFlyingPirate::AnimOver(CStateManager& mgr, float arg) {\n  if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::Death) {\n    return true;\n  }\n  return CPatterned::AnimOver(mgr, arg);\n}\n\nvoid CFlyingPirate::Attack(CStateManager& mgr, EStateMsg msg, float arg) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x32c_animState = EAnimState::Ready;\n    if (!x6a2_25_aggressionChecked) {\n      float fVar1;\n      if (3.f <= x858_) {\n        fVar1 = x568_data.xe8_aggressionChance;\n      } else {\n        fVar1 = 2.f * x568_data.xe8_aggressionChance;\n      }\n      x6a2_24_aggressive = mgr.GetActiveRandom()->Range(0.f, 100.f) < fVar1;\n      x6a2_25_aggressionChecked = true;\n    }\n    break;\n  case EStateMsg::Update:\n    TryCommand(mgr, pas::EAnimationState::ProjectileAttack, &CPatterned::TryProjectileAttack, 1);\n    x450_bodyController->FaceDirection((mgr.GetPlayer().GetTranslation() - GetTranslation()).normalized(), arg);\n    DeliverGetUp();\n    UpdateCanSeePlayer(mgr);\n    break;\n  case EStateMsg::Deactivate:\n    x32c_animState = EAnimState::NotReady;\n    x6a2_24_aggressive = false;\n    break;\n  default:\n    break;\n  }\n}\n\nbool CFlyingPirate::Attacked(CStateManager& mgr, float arg) { return x854_ < (arg != 0.f ? arg : 0.5f); }\n\nzeus::CVector3f CFlyingPirate::AvoidActors(CStateManager& mgr) {\n  const zeus::CVector3f& origin = GetTranslation();\n  const zeus::CAABox box(origin - 8.f, origin + 8.f);\n  EntityList nearList;\n  mgr.BuildNearList(nearList, box, CMaterialFilter::MakeInclude(EMaterialTypes::Character), this);\n\n  zeus::CVector3f ret;\n  for (const auto& id : nearList) {\n    if (TCastToConstPtr<CActor> actor = mgr.GetObjectById(id)) {\n      ret += x45c_steeringBehaviors.Separation(*this, actor->GetTranslation(), 10.f);\n    }\n  }\n  const zeus::CVector3f& playerPos = mgr.GetPlayer().GetTranslation();\n  return ret + x45c_steeringBehaviors.Separation(*this, {playerPos.x(), playerPos.y(), origin.z()}, 20.f);\n}\n\nvoid CFlyingPirate::Bounce(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    CTeamAiMgr::ResetTeamAiRole(CTeamAiMgr::EAttackType::Ranged, mgr, x8_uid, x890_teamAiMgr, true);\n  } else if (msg == EStateMsg::Update) {\n    switch (x450_bodyController->GetCurrentStateId()) {\n    case pas::EAnimationState::Locomotion:\n      x330_stateMachineState.SetCodeTrigger();\n      break;\n    case pas::EAnimationState::LieOnGround:\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBCGetupCmd(pas::EGetupType::Zero));\n      break;\n    case pas::EAnimationState::Hurled:\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBodyStateCmd(EBodyStateCmd::ExitState));\n      x328_25_verticalMovement = true;\n      break;\n    default:\n      break;\n    }\n  }\n}\n\nvoid CFlyingPirate::CalculateRenderBounds() {\n  if (!x89c_ragDoll || !x89c_ragDoll->IsPrimed()) {\n    CActor::CalculateRenderBounds();\n  } else {\n    const zeus::CAABox bounds = x89c_ragDoll->CalculateRenderBounds();\n    const zeus::CVector3f scale = 0.25f * GetModelData()->GetScale();\n    x9c_renderBounds = {bounds.min - scale, bounds.max + scale};\n  }\n}\n\nbool CFlyingPirate::CanFireMissiles(CStateManager& mgr) {\n  for (const auto& seg : x864_missileSegments) {\n    const zeus::CTransform xf = GetLctrTransform(seg);\n    const zeus::CVector3f dir = xf.origin + (3.f * xf.frontVector());\n    CMaterialList matList(EMaterialTypes::Player, EMaterialTypes::ProjectilePassthrough);\n    if (!LineOfSightTest(mgr, xf.origin, dir, matList) || !LineOfSightTest(mgr, dir, GetTargetPos(mgr), matList)) {\n      x6a1_28_ = true;\n      return false;\n    }\n  }\n  return true;\n}\n\nvoid CFlyingPirate::CheckForProjectiles(CStateManager& mgr) {\n  if (!x6a0_29_checkForProjectiles) {\n    return;\n  }\n\n  const zeus::CVector3f& playerPos = mgr.GetPlayer().GetTranslation();\n  const zeus::CAABox box(playerPos - 5.f, playerPos + 5.f);\n  x6a0_30_ = false;\n\n  EntityList nearList;\n  mgr.BuildNearList(nearList, box, CMaterialFilter::MakeInclude(EMaterialTypes::Projectile), this);\n  for (const auto& id : nearList) {\n    if (TCastToConstPtr<CGameProjectile> proj = mgr.GetObjectById(id)) {\n      zeus::CVector3f dist = GetBoundingBox().center() - proj->GetTranslation();\n      if (dist.isMagnitudeSafe()) {\n        if (GetTranslation().dot(dist) < 0.f) {\n          dist.normalize();\n          zeus::CVector3f nv = proj->GetTranslation() - proj->GetPreviousPos();\n          if (!nv.isMagnitudeSafe()) {\n            nv.normalize();\n            if (0.939f < nv.dot(dist)) {\n              x6a0_30_ = true;\n            }\n          }\n        }\n      } else {\n        x6a0_30_ = true;\n      }\n      if (x6a0_30_) {\n        break;\n      }\n    }\n  }\n  x6a0_29_checkForProjectiles = false;\n}\n\nbool CFlyingPirate::CoverCheck(CStateManager& mgr, float) {\n  if (0.f < x888_) {\n    return false;\n  }\n  x888_ = 10.f;\n  return mgr.GetActiveRandom()->Range(0.f, 100.f) < x568_data.xcc_coverCheckChance;\n}\n\nbool CFlyingPirate::CoverFind(CStateManager& mgr, float) {\n  float closestMag = x568_data.x0_maxCoverDistance * x568_data.x0_maxCoverDistance;\n  CScriptCoverPoint* closest = nullptr;\n  for (auto* const entity : *mgr.ObjectListById(EGameObjectList::PlatformAndDoor)) {\n    if (TCastToPtr<CScriptCoverPoint> cover = entity) {\n      if (cover->GetActive() && cover->ShouldLandHere() && !cover->GetInUse(x8_uid) &&\n          cover->GetAreaIdAlways() == x4_areaId) {\n        float mag = (GetTranslation() - cover->GetTranslation()).magSquared();\n        if (mag < closestMag) {\n          closest = cover;\n          closestMag = mag;\n        }\n      }\n    }\n  }\n  if (closest != nullptr) {\n    ReleaseCoverPoint(mgr, x6a4_currentCoverPoint);\n    SetCoverPoint(closest, x6a4_currentCoverPoint);\n    x6a6_id2 = x6a4_currentCoverPoint;\n    return true;\n  }\n  return false;\n}\n\nvoid CFlyingPirate::Deactivate(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x401_30_pendingDeath = true;\n  }\n}\n\nvoid CFlyingPirate::Dead(CStateManager& mgr, EStateMsg msg, float arg) {\n  CPatterned::Dead(mgr, msg, arg);\n  if (msg == EStateMsg::Activate) {\n    x7a0_boneTracking.SetActive(false);\n    GetModelData()->GetAnimationData()->SetParticleEffectState(\"Eyes\"sv, false, mgr);\n    CTeamAiMgr::ResetTeamAiRole(CTeamAiMgr::EAttackType::Ranged, mgr, x890_teamAiMgr, x8_uid, true);\n  }\n}\n\nvoid CFlyingPirate::Dodge(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n    if ((x84c_dodgeDirection = GetDodgeDirection(mgr, x850_height)) == pas::EStepDirection::Invalid) {\n      x84c_dodgeDirection =\n          (mgr.GetActiveRandom()->Next() & 0x4000) == 0 ? pas::EStepDirection::Right : pas::EStepDirection::Left;\n    }\n    UpdateParticleEffects(mgr, 1.f, true);\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::Step, &CPatterned::TryDodge, static_cast<int>(x84c_dodgeDirection));\n    UpdateCanSeePlayer(mgr);\n    x898_ = std::max(1.f, 2.f - x330_stateMachineState.GetTime());\n    DeliverGetUp();\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n    x6a1_28_ = false;\n  }\n}\n\nvoid CFlyingPirate::DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) {\n  if (type == EUserEventType::DeGenerate || type == EUserEventType::BecomeRagDoll) {\n    if (!x89c_ragDoll && HealthInfo(mgr)->GetHP() <= 0.f) {\n      x89c_ragDoll =\n          std::make_unique<CFlyingPirateRagDoll>(mgr, this, x568_data.xc8_ragDollSfx1, x568_data.xca_ragDollSfx2);\n    }\n  } else if (type == EUserEventType::Projectile) {\n    CProjectileInfo& pInfo =\n        x6a1_26_isAttackingObject ? x568_data.x60_altProjectileInfo2 : x568_data.x38_altProjectileInfo1;\n    if (pInfo.Token().IsLoaded() && mgr.CanCreateProjectile(x8_uid, EWeaponType::AI, 16)) {\n      const zeus::CTransform xf = GetLctrTransform(node.GetLocatorName());\n      TUniqueId target = x6a1_26_isAttackingObject ? x85c_attackObjectId : mgr.GetPlayer().GetUniqueId();\n      auto* projectile = new CEnergyProjectile(true, pInfo.Token(), EWeaponType::AI, xf, EMaterialTypes::Character,\n                                               pInfo.GetDamage(), mgr.AllocateUniqueId(), x4_areaId, x8_uid, target,\n                                               EProjectileAttrib::None, false, zeus::skOne3f, std::nullopt, -1, false);\n      mgr.AddObject(projectile);\n      if (!x6a1_26_isAttackingObject) {\n        projectile->SetCameraShake(\n            CCameraShakeData::BuildPatternedExplodeShakeData(projectile->GetTranslation(), 0.3f, 0.2f, 50.f));\n        if (x6a0_25_isAquaPirate) {\n          projectile->SetMinHomingDistance(x568_data.xf0_projectileHomingDistance);\n        }\n      }\n    }\n  } else {\n    CPatterned::DoUserAnimEvent(mgr, node, type, dt);\n  }\n}\n\nvoid CFlyingPirate::Enraged(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg != EStateMsg::Update) {\n    return;\n  }\n  x87c_ = (arg * arg * x568_data.xc4_) * zeus::skUp;\n  x898_ = 1.f;\n  x870_ += x87c_;\n  x450_bodyController->GetCommandMgr().DeliverCmd(\n      CBCLocomotionCmd(zeus::skUp, (GetTargetPos(mgr) - GetTranslation()).normalized(), 1.f));\n}\n\nvoid CFlyingPirate::Explode(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    RemoveMaterial(EMaterialTypes::Target, EMaterialTypes::Orbit, EMaterialTypes::GroundCollider, EMaterialTypes::Solid,\n                   mgr);\n    x150_momentum.zeroOut();\n    if (!x400_27_fadeToDeath) {\n      MassiveDeath(mgr);\n    }\n  } else if (msg == EStateMsg::Update) {\n    if (x330_stateMachineState.GetTime() > 0.1f) {\n      DeathDelete(mgr);\n    }\n  }\n}\n\nvoid CFlyingPirate::MassiveDeath(CStateManager& mgr) {\n  auto* explosion = new CExplosion(static_cast<const TLockedToken<CGenDescription>>(x568_data.x90_particleGenDesc),\n                                   mgr.AllocateUniqueId(), true, {x4_areaId, CEntity::NullConnectionList}, \"\",\n                                   GetTransform(), 0, zeus::CVector3f(1.5f), zeus::skWhite);\n  mgr.AddObject(explosion);\n  mgr.ApplyDamageToWorld(x8_uid, *this, GetTranslation(), x568_data.x9c_dInfo,\n                         CMaterialFilter::MakeInclude({EMaterialTypes::Solid}));\n  mgr.GetCameraManager()->AddCameraShaker(CCameraShakeData::BuildPatternedExplodeShakeData(0.5f, 0.3f), true);\n  CPatterned::MassiveDeath(mgr);\n}\n\nvoid CFlyingPirate::FireProjectile(CStateManager& mgr, float dt) {\n  bool projectileFired = false;\n  const zeus::CTransform xf = GetLctrTransform(x7e0_gunSegId);\n  if (!x400_25_alive) {\n    LaunchProjectile(xf, mgr, 8, EProjectileAttrib::None, false, std::nullopt, -1, false, zeus::skOne3f);\n    projectileFired = true;\n  } else {\n    if (TCastToPtr<CActor> actor = mgr.ObjectById(x7e8_targetId)) {\n      zeus::CVector3f origin = actor->GetTranslation();\n      const CPlayer& player = mgr.GetPlayer();\n      if (x7e8_targetId == player.GetUniqueId()) {\n        origin = GetProjectileInfo()->PredictInterceptPos(xf.origin, player.GetAimPosition(mgr, 0.f), player, true, dt);\n      }\n      zeus::CVector3f dist = origin - xf.origin;\n      float mag = dist.magnitude();\n      float fVar13 = xf.frontVector().dot(dist * (1.f / mag));\n      if (0.707f < fVar13 || (mag < 6.f && 0.5f < fVar13)) {\n        if (LineOfSightTest(mgr, xf.origin, origin, {EMaterialTypes::Player, EMaterialTypes::ProjectilePassthrough})) {\n          origin += GetTransform().rotate(x7ec_burstFire.GetDistanceCompensatedError(mag, 6.f));\n          LaunchProjectile(zeus::lookAt(xf.origin, origin, zeus::skUp), mgr, 8, EProjectileAttrib::None, false,\n                           std::nullopt, -1, false, zeus::skOne3f);\n          projectileFired = true;\n        }\n      }\n    }\n  }\n  if (projectileFired) {\n    const std::pair<float, s32> anim = x450_bodyController->GetPASDatabase().FindBestAnimation(\n        CPASAnimParmData{pas::EAnimationState::AdditiveReaction, CPASAnimParm::FromEnum(2)}, *mgr.GetActiveRandom(),\n        -1);\n    if (anim.first > 0.f) {\n      GetModelData()->GetAnimationData()->AddAdditiveAnimation(anim.second, 1.f, false, true);\n    }\n    CSfxManager::AddEmitter(x568_data.x34_gunSfx, GetTranslation(), zeus::skZero3f, true, false, 0x7f, kInvalidAreaId);\n  }\n}\n\npas::EStepDirection CFlyingPirate::GetDodgeDirection(CStateManager& mgr, float arg) {\n  float argSquared = arg * arg;\n  bool canDodgeLeft = true;\n  bool canDodgeRight = true;\n  bool canDodgeUp = true;\n  bool canDodgeDown = true;\n  pas::EStepDirection direction = pas::EStepDirection::Invalid;\n  for (auto* const entity : *mgr.ObjectListById(EGameObjectList::AiWaypoint)) {\n    if (entity == this) {\n      continue;\n    }\n    if (TCastToPtr<CPhysicsActor> actor = entity) {\n      const zeus::CVector3f dist = actor->GetTranslation() - GetTranslation();\n      float distMagSquared = dist.magSquared();\n      if (distMagSquared < argSquared) {\n        float rightVecMag = GetTransform().rightVector().magSquared();\n        if ((0.866f * distMagSquared) < rightVecMag || (0.f < rightVecMag && distMagSquared < 3.f)) {\n          canDodgeRight = false;\n        } else if (rightVecMag < 0.866f * -distMagSquared || (rightVecMag < 0.f && distMagSquared < 3.f)) {\n          canDodgeLeft = false;\n        }\n        float upVecMag = GetTransform().upVector().magSquared();\n        if ((0.866f * distMagSquared) < upVecMag || (0.f < upVecMag && distMagSquared < 3.f)) {\n          canDodgeUp = false;\n        } else if (upVecMag < 0.866f * -distMagSquared || (0.f < upVecMag && distMagSquared < 3.f)) {\n          canDodgeDown = false;\n        }\n      }\n    }\n  }\n\n  const zeus::CVector3f center = GetBoundingBox().center();\n  if (canDodgeRight) {\n    canDodgeRight = LineOfSightTest(mgr, center, center + (arg * GetTransform().rightVector()), {});\n  }\n  if (canDodgeLeft) {\n    canDodgeLeft = LineOfSightTest(mgr, center, center - (arg * GetTransform().rightVector()), {});\n  }\n  if (canDodgeUp) {\n    canDodgeUp = LineOfSightTest(mgr, center, center + (arg * GetTransform().upVector()), {});\n  }\n  if (canDodgeDown) {\n    canDodgeDown = LineOfSightTest(mgr, center, center - (arg * GetTransform().upVector()), {});\n  }\n\n  if ((canDodgeLeft || canDodgeRight) && (canDodgeUp || canDodgeDown)) {\n    if ((mgr.GetActiveRandom()->Next() & 0x4000) == 0) {\n      canDodgeUp = false;\n      canDodgeDown = false;\n    } else {\n      canDodgeLeft = false;\n      canDodgeRight = false;\n    }\n  }\n  if (canDodgeLeft && canDodgeRight) {\n    if ((mgr.GetActiveRandom()->Next() & 0x4000) == 0) {\n      canDodgeRight = false;\n    } else {\n      canDodgeLeft = false;\n    }\n  }\n  if (canDodgeUp && canDodgeDown) {\n    const zeus::CVector3f target = GetTargetPos(mgr);\n    if (target.z() - (GetTranslation().z() + x568_data.x8c_flyingHeight) <= 0.f) {\n      canDodgeUp = false;\n    } else {\n      canDodgeDown = false;\n    }\n  }\n\n  if (canDodgeUp) {\n    direction = pas::EStepDirection::Up;\n  } else if (canDodgeDown) {\n    direction = pas::EStepDirection::Down;\n  } else if (canDodgeLeft) {\n    direction = pas::EStepDirection::Left;\n  } else if (canDodgeRight) {\n    direction = pas::EStepDirection::Right;\n  }\n  return direction;\n}\n\nzeus::CVector3f CFlyingPirate::GetTargetPos(CStateManager& mgr) {\n  const CPlayer& player = mgr.GetPlayer();\n  const TUniqueId playerUid = player.GetUniqueId();\n  if (x7e8_targetId != playerUid) {\n    if (TCastToPtr<CActor> actor = mgr.ObjectById(x7e8_targetId)) {\n      if (actor->GetActive()) {\n        return actor->GetTranslation();\n      }\n    }\n    x7a0_boneTracking.SetTarget(playerUid);\n    x7e8_targetId = playerUid;\n  }\n  return player.GetAimPosition(mgr, 0.f);\n}\n\nvoid CFlyingPirate::GetUp(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n    CTeamAiMgr::ResetTeamAiRole(CTeamAiMgr::EAttackType::Ranged, mgr, x890_teamAiMgr, x8_uid, true);\n  } else if (msg == EStateMsg::Update) {\n    if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::LieOnGround) {\n      // will always return Success?\n      CPathFindSearch::EResult result = x6a8_pathFindSearch.Search(GetTranslation(), GetTranslation());\n      if (result == CPathFindSearch::EResult::NoSourcePoint) {\n        x401_30_pendingDeath = true;\n        return;\n      }\n    }\n    TryCommand(mgr, pas::EAnimationState::Getup, &CPatterned::TryGetUp, 0);\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n  }\n}\n\nbool CFlyingPirate::HearPlayer(CStateManager& mgr, float) {\n  const CPlayer& player = mgr.GetPlayer();\n  const float hearingDist = x568_data.x4_hearingDistance * x568_data.x4_hearingDistance;\n  return player.GetVelocity().magSquared() > 0.1f &&\n         (player.GetTranslation() - GetTranslation()).magSquared() < hearingDist;\n}\n\nbool CFlyingPirate::HearShot(CStateManager& mgr, float) { return x6a0_26_hearShot; }\n\nbool CFlyingPirate::InPosition(CStateManager& mgr, float) {\n  CScriptCoverPoint* const cover = GetCoverPoint(mgr, x6a4_currentCoverPoint);\n  if (cover == nullptr) {\n    return true;\n  }\n  const zeus::CVector3f dist = cover->GetTranslation() - GetTranslation();\n  return dist.z() < 0.f && dist.magnitude() < 4.f;\n}\n\nbool CFlyingPirate::InRange(CStateManager& mgr, float) {\n  const CPlayer& player = mgr.GetPlayer();\n  const zeus::CVector3f& playerPos = player.GetTranslation();\n  return std::abs(playerPos.z()) < x2fc_minAttackRange &&\n         playerPos.magSquared() < x300_maxAttackRange * x300_maxAttackRange;\n}\n\nvoid CFlyingPirate::Jump(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Combat);\n    x328_25_verticalMovement = true;\n    RemoveMaterial(EMaterialTypes::GroundCollider, mgr);\n    x150_momentum.zeroOut();\n    x888_ = 10.f;\n    UpdateParticleEffects(mgr, 1.f, true);\n    UpdateLandingSmoke(mgr, true);\n    x6a2_24_aggressive = x568_data.xec_ > mgr.GetActiveRandom()->Range(0.f, 100.f);\n  } else if (msg == EStateMsg::Deactivate) {\n    UpdateParticleEffects(mgr, 0.5f, true);\n    UpdateLandingSmoke(mgr, false);\n    x6a2_24_aggressive = false;\n  }\n}\n\nvoid CFlyingPirate::KnockBack(const zeus::CVector3f& pos, CStateManager& mgr, const CDamageInfo& info,\n                              EKnockBackType type, bool inDeferred, float magnitude) {\n  if (x400_25_alive) {\n    x460_knockBackController.SetSeverity(x328_25_verticalMovement ? pas::ESeverity::Zero : pas::ESeverity::One);\n  } else if (!IsOnGround()) {\n    const float rand = mgr.GetActiveRandom()->Range(0.f, 100.f);\n    if (x568_data.xb8_ <= rand) {\n      UpdateParticleEffects(mgr, 0.f, false);\n      SetMomentumWR({0.f, 0.f, -GetGravityConstant() * xe8_mass});\n    } else {\n      x6a1_30_spinToDeath = true;\n      x150_momentum.zeroOut();\n    }\n    x460_knockBackController.SetAnimationStateRange(EKnockBackAnimationState::Hurled, EKnockBackAnimationState::Hurled);\n    x328_25_verticalMovement = false;\n    // const TUniqueId& waypointId = GetWaypointForState(mgr, EScriptObjectState::Retreat, EScriptObjectMessage::Next);\n    // if (waypointId != kInvalidUniqueId) {\n    //   casts and then does nothing?\n    // }\n    const zeus::CVector3f homingPosition = mgr.GetPlayer().GetHomingPosition(mgr, 0.f);\n    const zeus::CVector3f homingDist = homingPosition - GetTranslation();\n    zeus::CVector3f cross = homingDist.cross(zeus::skUp);\n    if (zeus::close_enough(cross, zeus::skZero3f, 1.0E-4f)) {\n      cross = homingDist.cross(zeus::skForward);\n    }\n    SetDestPos(homingPosition + (homingDist.y() * cross.normalized()));\n    x7a0_boneTracking.SetActive(false);\n  }\n  CPatterned::KnockBack(pos, mgr, info, type, inDeferred, magnitude);\n  if (x460_knockBackController.GetActiveParms().x0_animState == EKnockBackAnimationState::Hurled) {\n    if (x400_25_alive) {\n      if (!x450_bodyController->IsFrozen()) {\n        x330_stateMachineState.SetState(mgr, *this, GetStateMachine(), \"GetUpNow\"sv);\n        x330_stateMachineState.SetDelay(x568_data.x88_knockBackDelay);\n      }\n      x6a1_28_ = false;\n      x328_25_verticalMovement = false;\n      CSfxManager::AddEmitter(x568_data.xe4_knockBackSfx, GetTranslation(), zeus::skZero3f, 1.f, true, false, 0x7f,\n                              kInvalidAreaId);\n    } else {\n      CSfxManager::AddEmitter(x568_data.xe6_deathSfx, GetTranslation(), zeus::skZero3f, 1.f, true, false, 0x7f,\n                              kInvalidAreaId);\n      if (x400_27_fadeToDeath) {\n        x6a1_30_spinToDeath = false;\n        UpdateParticleEffects(mgr, 0.f, false);\n        SetMomentumWR({0.f, 0.f, -GetGravityConstant() * xe8_mass});\n      }\n    }\n  }\n}\n\nvoid CFlyingPirate::Land(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n    UpdateLandingSmoke(mgr, true);\n    UpdateParticleEffects(mgr, 1.f, true);\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::Jump, &CPatterned::TryJump, 0);\n    if (x32c_animState == EAnimState::Repeat) {\n      x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n    UpdateLandingSmoke(mgr, false);\n    UpdateParticleEffects(mgr, 0.f, false);\n  }\n}\n\nbool CFlyingPirate::Landed(CStateManager& mgr, float) {\n  return x450_bodyController->GetCurrentStateId() == pas::EAnimationState::LieOnGround;\n}\n\nbool CFlyingPirate::LineOfSight(CStateManager& mgr, float) { return !x6a0_31_canSeePlayer; }\n\nbool CFlyingPirate::LineOfSightTest(CStateManager& mgr, const zeus::CVector3f& start, const zeus::CVector3f& end,\n                                    CMaterialList exclude) {\n  return mgr.RayCollideWorld(start, end, CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, exclude), this);\n}\n\nbool CFlyingPirate::Listen(const zeus::CVector3f& pos, EListenNoiseType type) {\n  bool ret = false;\n  if (x400_25_alive) {\n    float x4Squared = x568_data.x4_hearingDistance * x568_data.x4_hearingDistance;\n    const zeus::CVector3f dist = pos - GetTranslation();\n    if (dist.magSquared() < x4Squared && (x3c0_detectionHeightRange == 0.f || (dist.z() * dist.z() < x4Squared))) {\n      ret = true;\n      x6a0_26_hearShot = true;\n    }\n    if (type == EListenNoiseType::PlayerFire) {\n      x6a0_29_checkForProjectiles = true;\n    }\n  }\n  return ret;\n}\n\nvoid CFlyingPirate::Lurk(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    ReleaseCoverPoint(mgr, x6a4_currentCoverPoint);\n    x6a0_31_canSeePlayer = true;\n    x7d8_ = 0.f;\n    x7dc_ = 0;\n    CTeamAiMgr::ResetTeamAiRole(CTeamAiMgr::EAttackType::Ranged, mgr, x890_teamAiMgr, x8_uid, true);\n    x330_stateMachineState.SetDelay(3.f);\n    UpdateParticleEffects(mgr, 0.f, true);\n    x6a2_25_aggressionChecked = false;\n  } else if (msg == EStateMsg::Update) {\n    UpdateCanSeePlayer(mgr);\n    if (x32c_animState != EAnimState::NotReady) {\n      TryCommand(mgr, pas::EAnimationState::Turn, &CPatterned::TryTurn, 0);\n    }\n    if (x32c_animState != EAnimState::Repeat) {\n      x2e0_destPos = GetTargetPos(mgr);\n      zeus::CVector3f dist = x2e0_destPos - GetTranslation();\n      dist.z() = 0.f;\n      if (GetTransform().frontVector().dot(dist.normalized()) < 0.8f) {\n        x32c_animState = EAnimState::Ready;\n      }\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x6a1_25_ = false;\n    x6a1_28_ = false;\n    x32c_animState = EAnimState::NotReady;\n  }\n}\n\nvoid CFlyingPirate::PathFind(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    zeus::CVector3f target = mgr.GetPlayer().GetAimPosition(mgr, 0.f);\n    if (!x6a1_29_isMoving) {\n      if (const CScriptCoverPoint* const cover = GetCoverPoint(mgr, x6a4_currentCoverPoint)) {\n        target = cover->GetTranslation();\n      }\n    } else {\n      target = x2e0_destPos;\n    }\n    CPathFindSearch* search = GetSearchPath();\n    CPathFindSearch::EResult result = search->Search(GetTranslation(), target);\n    if (result != CPathFindSearch::EResult::Success &&\n        (result == CPathFindSearch::EResult::NoDestPoint || result == CPathFindSearch::EResult::NoPath)) {\n      result = search->FindClosestReachablePoint(GetTranslation(), target);\n      if (result == CPathFindSearch::EResult::Success) {\n        search->Search(GetTranslation(), target);\n      }\n    }\n    UpdateParticleEffects(mgr, 0.5f, true);\n  } else if (msg == EStateMsg::Update) {\n    zeus::CVector3f move = zeus::skZero3f;\n    CPathFindSearch* search = GetSearchPath();\n    if (search->GetResult() == CPathFindSearch::EResult::Success &&\n        search->GetCurrentWaypoint() < search->GetWaypoints().size() - 1) {\n      zeus::CVector3f out = GetTranslation();\n      const zeus::CVector3f front = out + GetTransform().frontVector();\n      search->GetSplinePointWithLookahead(out, front, 3.f);\n      if (search->SegmentOver(out)) {\n        search->SetCurrentWaypoint(search->GetCurrentWaypoint() + 1);\n      }\n      move = front - GetTranslation();\n      if (move.canBeNormalized()) {\n        move.normalize();\n      }\n    }\n    move += 3.f * AvoidActors(mgr);\n    if (move.canBeNormalized()) {\n      move.normalize();\n    }\n\n    float fVar1 = 1.f;\n    if (x858_ < 2.f) {\n      fVar1 = 4.f;\n    }\n    float fVar2 = 1.5f * fVar1;\n    fVar1 = arg * (arg * (fVar1 * x568_data.xc4_));\n    x87c_ = fVar1 * move;\n    x898_ = fVar2;\n    x870_ += x87c_;\n\n    const zeus::CVector3f face = (GetTargetPos(mgr) - GetTranslation()).normalized();\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(move, face, 1.f));\n    UpdateCanSeePlayer(mgr);\n  } else if (msg == EStateMsg::Deactivate) {\n    x6a1_29_isMoving = false;\n  }\n}\n\nvoid CFlyingPirate::Patrol(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (!x6a0_27_canPatrol) {\n    return;\n  }\n\n  CPatterned::Patrol(mgr, msg, arg);\n  if (msg == EStateMsg::Activate) {\n    x450_bodyController->GetCommandMgr().SetSteeringBlendMode(ESteeringBlendMode::FullSpeed);\n    x8a0_patrolTarget = x2dc_destObj;\n    x8a4_ = 0.f;\n  } else if (msg == EStateMsg::Update) {\n    if (x8a0_patrolTarget != x2dc_destObj) {\n      x8a0_patrolTarget = x2dc_destObj;\n      x8a4_ = 0.f;\n    }\n    if (x2d8_patrolState == EPatrolState::Patrol) {\n      float f78 = x3b0_moveSpeed * x568_data.xc4_;\n      x8a4_ = std::min(f78, arg * f78 + x8a4_);\n      x87c_ = (arg * x8a4_ * arg) * (x2e0_destPos - GetTranslation()).normalized();\n      x898_ = 1.5f * x3b0_moveSpeed;\n      x870_ += x87c_;\n    }\n    if (x30c_behaviourOrient == EBehaviourOrient::Constant) {\n      x450_bodyController->GetCommandMgr().DeliverTargetVector(GetTargetPos(mgr) - GetTranslation());\n    }\n    UpdateCanSeePlayer(mgr);\n  } else if (msg == EStateMsg::Deactivate) {\n    x450_bodyController->GetCommandMgr().SetSteeringBlendMode(ESteeringBlendMode::Normal);\n  }\n}\n\nbool CFlyingPirate::PatternOver(CStateManager& mgr, float) { return x2dc_destObj == kInvalidUniqueId; }\n\nvoid CFlyingPirate::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) {\n  CModelData* modelData = GetModelData();\n  if (x89c_ragDoll && x89c_ragDoll->IsPrimed()) {\n    x89c_ragDoll->PreRender(GetTranslation(), *modelData);\n  }\n  CPatterned::PreRender(mgr, frustum);\n  x7a0_boneTracking.PreRender(mgr, *modelData->GetAnimationData(), GetTransform(), modelData->GetScale(),\n                              *x450_bodyController);\n}\n\nvoid CFlyingPirate::ProjectileAttack(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x6a1_26_isAttackingObject = true;\n    x32c_animState = EAnimState::Ready;\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::ProjectileAttack, &CPatterned::TryProjectileAttack, 0);\n    DeliverGetUp();\n  } else if (msg == EStateMsg::Deactivate) {\n    x6a1_26_isAttackingObject = false;\n    x32c_animState = EAnimState::NotReady;\n  }\n}\n\nvoid CFlyingPirate::Retreat(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    const zeus::CVector3f& origin = GetTranslation();\n    const zeus::CVector3f& playerOrigin = mgr.GetPlayer().GetTranslation();\n    const zeus::CVector3f dist = (playerOrigin - origin).normalized();\n    zeus::CVector3f target{\n        origin.x() - x2fc_minAttackRange * dist.x(),\n        origin.y() - x2fc_minAttackRange * dist.y(),\n        playerOrigin.z() + x568_data.x8c_flyingHeight,\n    };\n    CPathFindSearch* const search = GetSearchPath();\n    if (search->OnPath(target) == CPathFindSearch::EResult::NoSourcePoint) {\n      search->FindClosestReachablePoint(origin, target);\n      target.z() += x568_data.x8c_flyingHeight;\n      if ((playerOrigin - target).magSquared() < 0.25f * x2fc_minAttackRange * x2fc_minAttackRange) {\n        target.z() -= x568_data.x8c_flyingHeight; // can just subtract instead of recreating dist/target\n        if (search->OnPath(target) == CPathFindSearch::EResult::NoSourcePoint) {\n          search->FindClosestReachablePoint(origin, target);\n          target.z() += x568_data.x8c_flyingHeight;\n        }\n      }\n    }\n    search->Search(origin, target);\n    UpdateParticleEffects(mgr, 0.5f, true);\n  } else if (msg == EStateMsg::Update) {\n    zeus::CVector3f move = zeus::skZero3f;\n    CPathFindSearch* const search = GetSearchPath();\n    if (search->GetCurrentWaypoint() < search->GetWaypoints().size() - 1) {\n      const zeus::CVector3f& origin = GetTranslation();\n      zeus::CVector3f out = origin + GetTransform().frontVector();\n      search->GetSplinePointWithLookahead(out, origin, 3.f);\n      if (search->SegmentOver(out)) {\n        search->SetCurrentWaypoint(search->GetCurrentWaypoint() + 1);\n      }\n      move = out - origin;\n      if (move.canBeNormalized()) {\n        move.normalize();\n      }\n    }\n    move += 3.f * AvoidActors(mgr);\n    if (move.canBeNormalized()) {\n      move.normalize();\n    }\n\n    float fVar1 = 1.f;\n    if (x858_ < 2.f) {\n      fVar1 = 4.f;\n    }\n    float fVar2 = 1.5f * fVar1;\n    fVar1 = arg * (arg * (fVar1 * x568_data.xc4_));\n    x87c_ = fVar1 * move;\n    x898_ = fVar2;\n    x870_ += x87c_;\n\n    const zeus::CVector3f face = (GetTargetPos(mgr) - GetTranslation()).normalized();\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(move, face, 1.f));\n    UpdateCanSeePlayer(mgr);\n  }\n}\n\nbool CFlyingPirate::ShotAt(CStateManager& mgr, float arg) { return x858_ < (arg != 0.f ? arg : 0.5f); }\n\nbool CFlyingPirate::ShouldAttack(CStateManager& mgr, float) {\n  CTeamAiRole* const role = CTeamAiMgr::GetTeamAiRole(mgr, x890_teamAiMgr, x8_uid);\n  const CPlayer& player = mgr.GetPlayer();\n  if ((role == nullptr || role->GetTeamAiRole() == CTeamAiRole::ETeamAiRole::Ranged) &&\n      x7e8_targetId == player.GetUniqueId() && (x86c_ <= 0.f || x854_ < 1.f) && CanFireMissiles(mgr)) {\n    const zeus::CVector3f& dist = player.GetTranslation() - GetTranslation();\n    if (dist.z() * dist.z() < dist.y() * dist.y() + dist.x() * dist.x()) {\n      if (x890_teamAiMgr != kInvalidUniqueId) {\n        if (!CTeamAiMgr::AddAttacker(CTeamAiMgr::EAttackType::Ranged, mgr, x890_teamAiMgr, x8_uid)) {\n          return false;\n        }\n      }\n      x86c_ = x568_data.xc0_ * mgr.GetActiveRandom()->Float() + x568_data.xbc_;\n      return true;\n    }\n  }\n  return false;\n}\n\nbool CFlyingPirate::ShouldDodge(CStateManager& mgr, float) {\n  if (x6a1_28_ || x6a1_25_) {\n    return false;\n  }\n  return 0.f < (GetTargetPos(mgr) - GetTranslation()).dot(GetTransform().frontVector()) &&\n         (x854_ < 0.33f || x858_ < 0.33f) && x7d8_ < 0.5f;\n}\n\nbool CFlyingPirate::ShouldMove(CStateManager& mgr, float) {\n  const CPlayer& player = mgr.GetPlayer();\n  const zeus::CVector3f& origin = GetTranslation();\n  const zeus::CVector3f& playerOrigin = player.GetTranslation();\n  const zeus::CVector3f dist = origin - playerOrigin;\n\n  CRandom16* activeRandom = mgr.GetActiveRandom();\n  float rand = activeRandom->Float();\n  if (0.5f <= rand) {\n    rand = activeRandom->Range(15.f, 25.f);\n  } else {\n    rand = activeRandom->Range(-25.f, -15.f);\n  }\n\n  const zeus::CVector3f cross = dist.cross(zeus::skUp).normalized();\n  SetDestPos({\n      origin.x() + (rand * cross.x()),\n      origin.y() + (rand * cross.y()),\n      playerOrigin.z() + x568_data.x8c_flyingHeight,\n  });\n  x6a1_29_isMoving = true;\n  return true;\n}\n\nbool CFlyingPirate::ShouldRetreat(CStateManager& mgr, float) {\n  if (!x6a2_28_) {\n    return false;\n  }\n\n  TUniqueId id = GetWaypointForState(mgr, EScriptObjectState::Patrol, EScriptObjectMessage::Follow);\n  TCastToPtr<CScriptWaypoint> waypoint = mgr.ObjectById(id);\n  if (!waypoint) {\n    id = GetWaypointForState(mgr, EScriptObjectState::Retreat, EScriptObjectMessage::Follow);\n    waypoint = mgr.ObjectById(id);\n  }\n  if (waypoint) {\n    x6a2_28_ = false;\n    x2dc_destObj = id;\n    SetDestPos(waypoint->GetTranslation());\n    x2ec_reflectedDestPos = GetTranslation();\n    x328_24_inPosition = false;\n    x6a1_29_isMoving = true;\n    x6a0_26_hearShot = false;\n    x6a0_28_ = false;\n    x400_24_hitByPlayerProjectile = false;\n    return true;\n  }\n  return false;\n}\n\nbool CFlyingPirate::ShouldSpecialAttack(CStateManager& mgr, float) {\n  if (x3fc_flavor != EFlavorType::One || x85c_attackObjectId == kInvalidUniqueId || x860_ > 0.f) {\n    return false;\n  }\n\n  x860_ = 15.f * mgr.GetActiveRandom()->Float() + 15.f;\n  if (!mgr.GetPlayer().CheckOrbitDisableSourceList()) {\n    if (TCastToPtr<CActor> actor = mgr.ObjectById(x85c_attackObjectId)) {\n      if (x890_teamAiMgr != kInvalidUniqueId) {\n        if (!CTeamAiMgr::AddAttacker(CTeamAiMgr::EAttackType::Ranged, mgr, x890_teamAiMgr, x8_uid)) {\n          return false;\n        }\n      }\n      SetDestPos(actor->GetTranslation() + (15.f * zeus::skDown));\n      x6a1_29_isMoving = true;\n      return true;\n    }\n  }\n  return false;\n}\n\nbool CFlyingPirate::SpotPlayer(CStateManager& mgr, float) {\n  const zeus::CVector3f dir = mgr.GetPlayer().GetAimPosition(mgr, 0.f) - GetGunEyePos();\n  return dir.magnitude() * x3c4_detectionAngle < dir.dot(GetTransform().frontVector());\n}\n\nbool CFlyingPirate::Stuck(CStateManager& mgr, float arg) {\n  if (x330_stateMachineState.GetTime() < 0.5f)\n    return false;\n  return CPatterned::Stuck(mgr, arg) || GetSearchPath()->GetResult() != CPathFindSearch::EResult::Success;\n}\n\nvoid CFlyingPirate::UpdateLandingSmoke(CStateManager& mgr, bool active) {\n  if (active) {\n    if (!x684_particleGens.empty()) {\n      const zeus::CVector3f& origin = GetTranslation();\n      float particleLevel = origin.z() - 5.f;\n      CScriptCoverPoint* const cover = GetCoverPoint(mgr, x6a4_currentCoverPoint);\n      if (cover != nullptr) {\n        particleLevel = cover->GetTranslation().z() - 1.f;\n      }\n      const CRayCastResult result = mgr.RayStaticIntersection(origin, zeus::skDown, origin.z() - particleLevel,\n                                                              CMaterialFilter::MakeInclude({EMaterialTypes::Solid}));\n      int idx = 1;\n      if (result.IsValid()) {\n        const CMaterialList& list = result.GetMaterial();\n        if (!list.HasMaterial(EMaterialTypes::Ice) && !list.HasMaterial(EMaterialTypes::Snow)) {\n          if (list.HasMaterial(EMaterialTypes::Dirt) || list.HasMaterial(EMaterialTypes::MudSlow) ||\n              list.HasMaterial(EMaterialTypes::Sand)) {\n            idx = 0;\n          }\n        } else {\n          idx = 2;\n        }\n        particleLevel = origin.z() - result.GetT();\n      }\n      x684_particleGens[idx]->SetParticleEmission(true);\n      x684_particleGens[idx]->SetTranslation({origin.x(), origin.y(), particleLevel});\n    }\n    GetModelData()->GetAnimationData()->SetParticleEffectState(\"LandingSmoke\"sv, true, mgr);\n  } else {\n    for (const auto& gen : x684_particleGens) {\n      gen->SetParticleEmission(false);\n    }\n    GetModelData()->GetAnimationData()->SetParticleEffectState(\"LandingSmoke\"sv, false, mgr);\n  }\n}\n\nvoid CFlyingPirate::UpdateParticleEffects(CStateManager& mgr, float intensity, bool active) {\n  CAnimData* const animData = GetModelData()->GetAnimationData();\n  std::string_view name = x6a0_25_isAquaPirate ? \"ScubaGear\"sv : \"JetPack\"sv;\n  if (active != x6a2_26_jetpackActive) {\n    animData->SetParticleEffectState(name, active, mgr);\n    if (x6a0_25_isAquaPirate) {\n      animData->SetParticleEffectState(\"ScubaBubbles\"sv, active, mgr);\n    }\n    x6a2_26_jetpackActive = active;\n  }\n  if (active) {\n    animData->SetParticleCEXTValue(name, 0, 0.75f * intensity + 2.25f);\n    animData->SetParticleCEXTValue(name, 1, -0.13f * intensity + -0.1f);\n  }\n  if (!x6a0_25_isAquaPirate) {\n    const bool sparksActive = active && intensity > 0.8f;\n    if (sparksActive != x6a2_27_sparksActive) {\n      animData->SetParticleEffectState(\"Sparks\"sv, sparksActive, mgr);\n      x6a2_27_sparksActive = sparksActive;\n    }\n  }\n}\n\nvoid CFlyingPirate::DeliverGetUp() {\n  if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::LieOnGround) {\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBCGetupCmd(pas::EGetupType::Zero));\n  }\n}\n\nvoid CFlyingPirate::UpdateCanSeePlayer(CStateManager& mgr) {\n  if (x7dc_ % 7 == 0) {\n    bool bVar4 = true;\n    const zeus::CVector3f start = GetGunEyePos() - GetTransform().rightVector();\n    const zeus::CVector3f end = GetAimPosition(mgr, 0.f);\n    const CMaterialList matList(EMaterialTypes::Player, EMaterialTypes::ProjectilePassthrough);\n    if (LineOfSightTest(mgr, start, end, matList)) {\n      bVar4 = !LineOfSightTest(mgr, start, end, matList);\n    }\n    x6a0_31_canSeePlayer = bVar4;\n  }\n  x7dc_++;\n}\n\nvoid CFlyingPirate::TargetPatrol(CStateManager& mgr, EStateMsg msg, float arg) {\n  CPatterned::Patrol(mgr, msg, arg);\n\n  if (msg == EStateMsg::Activate) {\n    x450_bodyController->GetCommandMgr().SetSteeringBlendMode(ESteeringBlendMode::Normal);\n    x2dc_destObj = GetWaypointForState(mgr, EScriptObjectState::Attack, EScriptObjectMessage::Follow);\n    if (x2dc_destObj != kInvalidUniqueId) {\n      if (TCastToPtr<CScriptWaypoint> waypoint = mgr.ObjectById(x2dc_destObj)) {\n        x30c_behaviourOrient = static_cast<EBehaviourOrient>(waypoint->GetBehaviourOrient());\n        x3b0_moveSpeed = waypoint->GetSpeed();\n      }\n    }\n    x8a0_patrolTarget = x2dc_destObj;\n    x8a4_ = 0.f;\n  } else if (msg == EStateMsg::Update) {\n    if (x2dc_destObj != x8a0_patrolTarget) {\n      x8a0_patrolTarget = x2dc_destObj;\n      x8a4_ = 0.f;\n    }\n    if (x2d8_patrolState == EPatrolState::Patrol) {\n      float f80 = x3b0_moveSpeed * x568_data.xc4_;\n      x8a4_ = std::min(arg * f80 + x8a4_, f80);\n      x87c_ = (arg * x8a4_ * arg) * (x2e0_destPos - GetTranslation()).normalized();\n      x898_ = 1.5f * x3b0_moveSpeed;\n      x870_ += x87c_;\n    }\n    if (x30c_behaviourOrient == EBehaviourOrient::Constant) {\n      x450_bodyController->GetCommandMgr().DeliverTargetVector(GetTargetPos(mgr) - GetTranslation());\n    }\n    UpdateCanSeePlayer(mgr);\n  }\n}\n\nvoid CFlyingPirate::Taunt(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x6a0_28_ = true;\n    x7a0_boneTracking.SetActive(true);\n    const TUniqueId playerUid = mgr.GetPlayer().GetUniqueId();\n    x7a0_boneTracking.SetTarget(playerUid);\n    bool foundPirate = false;\n    for (auto* const obj : *mgr.ObjectListById(EGameObjectList::AiWaypoint)) {\n      if (const CSpacePirate* const pirate = CPatterned::CastTo<CSpacePirate>(obj)) {\n        if (pirate->GetEnableAim() && pirate->IsAlive() && pirate->GetAreaIdAlways() == x4_areaId &&\n            (pirate->GetTranslation() - GetTranslation()).magSquared() <\n                x568_data.x4_hearingDistance * x568_data.x4_hearingDistance) {\n          foundPirate = true;\n        }\n      }\n    }\n    x79c_ = foundPirate ? 0 : 1;\n    if (x7e8_targetId == kInvalidUniqueId) {\n      x7e8_targetId = playerUid;\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    if (x79c_ == 0) {\n      mgr.InformListeners(GetTranslation(), EListenNoiseType::PlayerFire);\n    }\n  }\n}\n\nvoid CFlyingPirate::TurnAround(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x2e0_destPos = GetTargetPos(mgr);\n    zeus::CVector3f dist = x2e0_destPos - GetTranslation();\n    dist.z() = 0.f;\n    if (GetTransform().frontVector().dot(dist.normalized()) < 0.8f) {\n      x32c_animState = EAnimState::Ready;\n    }\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::Turn, &CPatterned::TryTurn, 0);\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n  }\n}\n\nvoid CFlyingPirate::Walk(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    UpdateParticleEffects(mgr, 0.f, false);\n  } else if (msg == EStateMsg::Update) {\n    if (x32c_animState != EAnimState::NotReady) {\n      TryCommand(mgr, pas::EAnimationState::Turn, &CPatterned::TryTurn, 0);\n    }\n    if (x32c_animState != EAnimState::Repeat) {\n      x2e0_destPos = GetTargetPos(mgr);\n      zeus::CVector3f dist = x2e0_destPos - GetTranslation();\n      dist.z() = 0.f;\n      if (GetTransform().frontVector().dot(dist.normalized()) < 0.8f) {\n        x32c_animState = EAnimState::Ready;\n      }\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Combat);\n    x328_25_verticalMovement = true;\n    x150_momentum.zeroOut();\n  }\n}\n\nvoid CFlyingPirate::Think(float dt, CStateManager& mgr) {\n  if (!x30_24_active)\n    return;\n\n  if (!x450_bodyController->GetActive()) {\n    x450_bodyController->Activate(mgr);\n    if (x6a0_24_isFlyingPirate) {\n      x450_bodyController->SetLocomotionType(pas::ELocomotionType::Combat);\n      x328_25_verticalMovement = true;\n    }\n  }\n\n  bool inCineCam = mgr.GetCameraManager()->IsInCinematicCamera();\n  if (inCineCam && !x6a1_24_prevInCineCam) {\n    RemoveMaterial(EMaterialTypes::AIBlock, mgr);\n    CMaterialFilter filter = GetMaterialFilter();\n    filter.IncludeList().Remove(EMaterialTypes::AIBlock);\n    SetMaterialFilter(filter);\n  } else if (!inCineCam && x6a1_24_prevInCineCam) {\n    AddMaterial(EMaterialTypes::AIBlock, mgr);\n    CMaterialFilter filter = GetMaterialFilter();\n    filter.IncludeList().Add(EMaterialTypes::AIBlock);\n    SetMaterialFilter(filter);\n  }\n  x6a1_24_prevInCineCam = inCineCam;\n\n  for (const auto& gen : x684_particleGens) {\n    gen->Update(dt);\n  }\n\n  x78c_ = std::max(0.f, x78c_ - dt);\n  if (x400_25_alive) {\n    if (x6a0_30_) {\n      x858_ = 0.f;\n      x6a0_30_ = false;\n    } else {\n      x858_ += dt;\n    }\n    if (x400_24_hitByPlayerProjectile) {\n      x854_ = 0.f;\n      x400_24_hitByPlayerProjectile = false;\n    } else {\n      x854_ += dt;\n    }\n    if (!x6a0_25_isAquaPirate && xc4_fluidId != kInvalidUniqueId) {\n      if (TCastToPtr<CScriptWater> water = mgr.ObjectById(xc4_fluidId)) {\n        const zeus::CAABox box = water->GetTriggerBoundsWR();\n        if (2.f + GetTranslation().z() < box.max.z()) {\n          x401_30_pendingDeath = true;\n        }\n      }\n    }\n  }\n\n  if (x450_bodyController->GetPercentageFrozen() == 0.f) {\n    x86c_ = std::max(0.f, x86c_ - dt);\n    x860_ = std::max(0.f, x860_ - dt);\n    x888_ = std::max(0.f, x888_ - dt);\n    if (x6a0_31_canSeePlayer) {\n      x7d8_ += dt;\n    } else {\n      x7d8_ = 0.f;\n    }\n    if (x400_25_alive) {\n      CheckForProjectiles(mgr);\n    }\n    if (!x6a0_25_isAquaPirate &&\n        (!x400_25_alive || !(!x450_bodyController->GetBodyStateInfo().GetCurrentState()->CanShoot() || !x6a0_28_ ||\n                             x450_bodyController->GetCurrentStateId() == pas::EAnimationState::ProjectileAttack ||\n                             x6a1_31_stopped || x450_bodyController->IsElectrocuting()))) {\n      if (x7ec_burstFire.GetBurstType() != -1) {\n        x7e4_ -= dt;\n        if (x7e4_ < 0.f) {\n          s32 newType = x7ec_burstFire.GetBurstType() & ~1;\n          if (!PlayerSpot(mgr, 0.f)) {\n            newType += 1;\n          }\n          x7ec_burstFire.SetBurstType(newType);\n          x7ec_burstFire.Start(mgr);\n          if (x400_25_alive) {\n            x7e4_ = x308_attackTimeVariation * mgr.GetActiveRandom()->Float() + x304_averageAttackTime;\n            const zeus::CVector3f dist =\n                (GetBoundingBox().center() - mgr.GetPlayer().GetAimPosition(mgr, 0.f)).normalized();\n            if (dist.magSquared() < 0.9f) {\n              for (auto* const obj : *mgr.ObjectListById(EGameObjectList::AiWaypoint)) {\n                if (const auto* pirate = CPatterned::CastTo<const CSpacePirate>(obj)) {\n                  if (pirate->GetEnableAim() && pirate->GetAreaIdAlways() == x4_areaId) {\n                    x7e4_ += 0.2f;\n                  }\n                }\n              }\n            }\n          } else {\n            x7e4_ = 22050.f;\n          }\n        }\n\n        x7ec_burstFire.Update(mgr, dt);\n        if (x7ec_burstFire.ShouldFire()) {\n          FireProjectile(mgr, dt);\n          if (0.f < x7ec_burstFire.GetTimeToNextShot()) {\n            x7ec_burstFire.SetTimeToNextShot(x568_data.xd4_ * (mgr.GetActiveRandom()->Float() - 0.5f) + x568_data.xd0_);\n          }\n        }\n      }\n    }\n  }\n\n  if (x89c_ragDoll && x89c_ragDoll->IsPrimed()) {\n    UpdateAlphaDelta(dt, mgr);\n    UpdateDamageColor(dt);\n  } else {\n    if (!x400_25_alive || x450_bodyController->IsFrozen() || x450_bodyController->IsElectrocuting() || !x6a0_28_ ||\n        x89c_ragDoll || x6a0_25_isAquaPirate) {\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBodyStateCmd(EBodyStateCmd::AdditiveIdle));\n    } else {\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBCAdditiveAimCmd());\n      x450_bodyController->GetCommandMgr().DeliverAdditiveTargetVector(\n          GetTransform().transposeRotate(GetTargetPos(mgr) - GetTranslation()));\n    }\n    if (0.f < x870_.magSquared()) {\n      float mag = x870_.magnitude();\n      float fVar5 = 0.2f;\n      if (0.f == x87c_.magSquared()) {\n        fVar5 = 0.2f * 3.f;\n      }\n      float mul = -(dt * mag * fVar5 * mag - mag);\n      x870_ = mul * (1.f / mag) * x870_;\n    }\n    pas::EAnimationState state = x450_bodyController->GetCurrentStateId();\n    if (!x400_25_alive || state == pas::EAnimationState::LoopReaction || state == pas::EAnimationState::Hurled ||\n        state == pas::EAnimationState::LieOnGround || state == pas::EAnimationState::Getup) {\n      x870_.zeroOut();\n      x87c_.zeroOut();\n    } else {\n      ApplyImpulseWR(xe8_mass * x870_, {});\n    }\n\n    if (const auto& handle = GetSfxHandle()) {\n      x898_ = std::clamp(x898_, 1.f, 1.999f);\n      x894_pitchBend += std::clamp(x898_ - x894_pitchBend, -dt, dt);\n      CSfxManager::PitchBend(handle, x894_pitchBend);\n    }\n\n    x87c_.zeroOut();\n    x898_ = 1.f;\n    CPatterned::Think(dt, mgr);\n\n    zeus::CVector3f vf8 = x87c_;\n    if (vf8.canBeNormalized()) {\n      vf8.normalize();\n    }\n    zeus::CVector3f v1d0 = std::min(0.333f * x87c_.magnitude(), 0.333f) * vf8;\n    const zeus::CVector3f v104 = (zeus::skUp + v1d0).normalized();\n    const zeus::CVector3f v110 = GetTransform().upVector();\n    float f26c = std::abs(zeus::CVector3f::getAngleDiff(v110, v104));\n    if (f26c > 0.f) {\n      float f1f4 = std::min(f26c, 30.f * zeus::degToRad(dt)); // ?\n      float f200 = f26c - f1f4;\n      zeus::CVector3f v1dc = (f1f4 * v104 + (f200 * v110)).normalized();\n      zeus::CVector3f v128 = GetTransform().frontVector().cross(v1dc);\n      zeus::CVector3f v20c = v1dc.cross(v128).normalized();\n      zeus::CVector3f v128_2 = v20c.cross(v1dc);\n      SetTransform({v128_2, v20c, v1dc, GetTranslation()});\n    }\n\n    if (!x450_bodyController->IsFrozen()) {\n      x7a0_boneTracking.Update(dt);\n    }\n  }\n\n  if (x89c_ragDoll) {\n    if (x89c_ragDoll->IsPrimed()) {\n      float waterTop = -FLT_MAX;\n      if (xc4_fluidId != kInvalidUniqueId) {\n        if (TCastToPtr<CScriptWater> water = mgr.ObjectById(xc4_fluidId)) {\n          waterTop = water->GetTriggerBoundsWR().max.z();\n        }\n      }\n      x89c_ragDoll->Update(mgr, dt * CalcDyingThinkRate(), waterTop);\n      x64_modelData->AdvanceParticles(GetTransform(), dt, mgr);\n    } else {\n      // SetMuted(true); ??\n      SetMuted(false);\n      x89c_ragDoll->Prime(mgr, GetTransform(), *x64_modelData);\n      SetTransform(zeus::CTransform::Translate(GetTranslation()));\n      x450_bodyController->SetPlaybackRate(0.f);\n    }\n\n    if (x89c_ragDoll->IsOver() && !x400_27_fadeToDeath) {\n      x400_27_fadeToDeath = true;\n      x3e8_alphaDelta = -1.f / 3.f;\n      SetVelocityWR(zeus::skZero3f);\n      x150_momentum.zeroOut();\n      x870_.zeroOut();\n    }\n\n    bool wasGtZero = x88c_ragDollTimer > 0.f;\n    x88c_ragDollTimer -= dt;\n    if (x88c_ragDollTimer < 2.f) {\n      if (x89c_ragDoll->GetImpactCount() > 2) {\n        x88c_ragDollTimer = std::min(0.1f, x88c_ragDollTimer);\n      }\n      if (wasGtZero && x88c_ragDollTimer <= 0.f) {\n        x330_stateMachineState.SetState(mgr, *this, GetStateMachine(), \"Explode\");\n      }\n    }\n  }\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CFlyingPirate.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Character/CBoneTracking.hpp\"\n#include \"Runtime/Character/CRagDoll.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/Weapon/CBurstFire.hpp\"\n#include \"Runtime/Weapon/CProjectileInfo.hpp\"\n#include \"Runtime/World/CAi.hpp\"\n#include \"Runtime/World/CPathFindSearch.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n\nnamespace metaforce::MP1 {\nenum class EFlyingPirateType : u32 {\n  FlyingPirate = 1,\n  AquaPirate = 2,\n};\n\nclass CFlyingPirate : public CPatterned {\npublic:\n  DEFINE_PATTERNED(FlyingPirate);\n\nprivate:\n  class CFlyingPirateData {\n    friend class CFlyingPirate;\n    float x0_maxCoverDistance;\n    float x4_hearingDistance;\n    EFlyingPirateType x8_type;\n    CProjectileInfo xc_gunProjectileInfo;\n    u16 x34_gunSfx;\n    CProjectileInfo x38_altProjectileInfo1;\n    CProjectileInfo x60_altProjectileInfo2;\n    float x88_knockBackDelay;\n    float x8c_flyingHeight;\n    TCachedToken<CGenDescription> x90_particleGenDesc;\n    CDamageInfo x9c_dInfo;\n    float xb8_;\n    float xbc_;\n    float xc0_;\n    float xc4_;\n    u16 xc8_ragDollSfx1;\n    u16 xca_ragDollSfx2;\n    float xcc_coverCheckChance;\n    float xd0_;\n    float xd4_;\n    CAssetId xd8_particleGen1;\n    CAssetId xdc_particleGen2;\n    CAssetId xe0_particleGen3;\n    u16 xe4_knockBackSfx;\n    u16 xe6_deathSfx;\n    float xe8_aggressionChance;\n    float xec_;\n    float xf0_projectileHomingDistance;\n\n  public:\n    CFlyingPirateData(CInputStream& in, u32 propCount);\n  };\n\n  class CFlyingPirateRagDoll : public CRagDoll {\n  private:\n    CFlyingPirate* x6c_actor;\n    float x70_ = 0.f;\n    zeus::CVector3f x74_ = zeus::skUp;\n    float x80_ = 0.f;\n    float x84_ = 5.f;\n    u16 x88_sfx;\n    float x8c_ = 0.f;\n    zeus::CVector3f x90_ = zeus::skZero3f;\n    u16 x9c_;\n    CSfxHandle xa0_;\n    zeus::CVector3f xa4_;\n    bool xb0_24_ : 1 = false;\n\n  public:\n    CFlyingPirateRagDoll(CStateManager& mgr, CFlyingPirate* actor, u16 w1, u16 w2);\n\n    void PreRender(const zeus::CVector3f& v, CModelData& mData) override;\n    void Prime(CStateManager& mgr, const zeus::CTransform& xf, CModelData& mData) override;\n    void Update(CStateManager& mgr, float dt, float waterTop) override;\n  };\n\npublic:\n  CFlyingPirate(TUniqueId, std::string_view, const CEntityInfo&, const zeus::CTransform&, CModelData&&,\n                const CActorParameters&, const CPatternedInfo&, CInputStream&, u32);\n\n  void Accept(IVisitor& visitor) override { visitor.Visit(this); }\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) override;\n  void AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) override;\n  bool AnimOver(CStateManager& mgr, float arg) override;\n  void CalculateRenderBounds() override;\n  void DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) override;\n  void MassiveDeath(CStateManager& mgr) override;\n  float GetGravityConstant() const override { return x6a0_25_isAquaPirate ? 5.f : 50.f; }\n  CPathFindSearch* GetSearchPath() override { return &x6a8_pathFindSearch; }\n  bool IsListening() const override { return true; }\n  bool KnockbackWhenFrozen() const override { return false; }\n  bool Listen(const zeus::CVector3f& pos, EListenNoiseType type) override;\n  void PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) override;\n  CProjectileInfo* GetProjectileInfo() override { return &x568_data.xc_gunProjectileInfo; }\n  void Think(float dt, CStateManager& mgr) override;\n\n  void Attack(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Bounce(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Deactivate(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Dead(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Dodge(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Enraged(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Explode(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void GetUp(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Jump(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void KnockBack(const zeus::CVector3f& pos, CStateManager& mgr, const CDamageInfo& info, EKnockBackType type,\n                 bool inDeferred, float magnitude) override;\n  void Land(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Lurk(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void PathFind(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Patrol(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void ProjectileAttack(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Retreat(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void TargetPatrol(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Taunt(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void TurnAround(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Walk(CStateManager& mgr, EStateMsg msg, float arg) override;\n\n  bool AggressionCheck(CStateManager& mgr, float arg) override;\n  bool Attacked(CStateManager& mgr, float arg) override;\n  bool CoverCheck(CStateManager& mgr, float arg) override;\n  bool CoverFind(CStateManager& mgr, float arg) override;\n  bool HearPlayer(CStateManager& mgr, float arg) override;\n  bool HearShot(CStateManager& mgr, float arg) override;\n  bool InPosition(CStateManager& mgr, float arg) override;\n  bool InRange(CStateManager& mgr, float arg) override;\n  bool Landed(CStateManager& mgr, float arg) override;\n  bool LineOfSight(CStateManager& mgr, float arg) override;\n  bool PatternOver(CStateManager& mgr, float arg) override;\n  bool ShotAt(CStateManager& mgr, float arg) override;\n  bool ShouldAttack(CStateManager& mgr, float arg) override;\n  bool ShouldDodge(CStateManager& mgr, float arg) override;\n  bool ShouldMove(CStateManager& mgr, float arg) override;\n  bool ShouldRetreat(CStateManager& mgr, float arg) override;\n  bool ShouldSpecialAttack(CStateManager& mgr, float arg) override;\n  bool SpotPlayer(CStateManager& mgr, float arg) override;\n  bool Stuck(CStateManager& mgr, float arg) override;\n\nprivate:\n  CFlyingPirateData x568_data;\n  rstl::reserved_vector<TCachedToken<CGenDescription>, 3> x65c_particleGenDescs;\n  // was rstl::reserved_vector<rstl::optional_object<CElementGen *>, 3>\n  rstl::reserved_vector<std::unique_ptr<CElementGen>, 3> x684_particleGens;\n  bool x6a0_24_isFlyingPirate : 1 = false;\n  bool x6a0_25_isAquaPirate : 1 = false;\n  bool x6a0_26_hearShot : 1 = false;\n  bool x6a0_27_canPatrol : 1 = false;\n  bool x6a0_28_ : 1 = false;\n  bool x6a0_29_checkForProjectiles : 1 = false;\n  bool x6a0_30_ : 1 = false;\n  bool x6a0_31_canSeePlayer : 1 = true;\n  bool x6a1_24_prevInCineCam : 1 = false;\n  bool x6a1_25_ : 1 = false;\n  bool x6a1_26_isAttackingObject : 1 = false;\n  bool x6a1_27_ : 1 = false;\n  bool x6a1_28_ : 1 = false;\n  bool x6a1_29_isMoving : 1 = false;\n  bool x6a1_30_spinToDeath : 1 = false;\n  bool x6a1_31_stopped : 1 = false;\n  bool x6a2_24_aggressive : 1 = false;\n  bool x6a2_25_aggressionChecked : 1 = false;\n  bool x6a2_26_jetpackActive : 1 = false;\n  bool x6a2_27_sparksActive : 1 = false;\n  bool x6a2_28_ : 1 = false;\n  TUniqueId x6a4_currentCoverPoint = kInvalidUniqueId;\n  TUniqueId x6a6_id2 = kInvalidUniqueId;\n  CPathFindSearch x6a8_pathFindSearch;\n  float x78c_ = 0.f; // not initialized in constructor?\n  int x790_ = 0;\n  float x794_initialHealth;\n  CSegId x798_headSegId;\n  int x79c_ = -1;\n  CBoneTracking x7a0_boneTracking;\n  float x7d8_ = 0.f;\n  int x7dc_ = 0;\n  CSegId x7e0_gunSegId;\n  float x7e4_ = 1.f;\n  TUniqueId x7e8_targetId = kInvalidUniqueId;\n  CBurstFire x7ec_burstFire;\n  pas::EStepDirection x84c_dodgeDirection = pas::EStepDirection::Invalid;\n  float x850_height = 3.f;\n  float x854_ = FLT_MAX;\n  float x858_ = FLT_MAX;\n  TUniqueId x85c_attackObjectId = kInvalidUniqueId;\n  float x860_ = 15.f;\n  rstl::reserved_vector<CSegId, 4> x864_missileSegments;\n  float x86c_ = 0.f;\n  zeus::CVector3f x870_ = zeus::skZero3f;\n  zeus::CVector3f x87c_ = zeus::skZero3f;\n  float x888_ = 10.f;\n  float x88c_ragDollTimer = 3.f;\n  TUniqueId x890_teamAiMgr = kInvalidUniqueId;\n  float x894_pitchBend = 1.f;\n  float x898_ = 1.f;\n  std::unique_ptr<CFlyingPirateRagDoll> x89c_ragDoll;\n  TUniqueId x8a0_patrolTarget = kInvalidUniqueId;\n  float x8a4_ = 0.f;\n\n  zeus::CVector3f AvoidActors(CStateManager& mgr);\n  bool CanFireMissiles(CStateManager& mgr);\n  void CheckForProjectiles(CStateManager& mgr);\n  void FireProjectile(CStateManager& mgr, float dt);\n  pas::EStepDirection GetDodgeDirection(CStateManager& mgr, float arg);\n  zeus::CVector3f GetTargetPos(CStateManager& mgr);\n  bool LineOfSightTest(CStateManager& mgr, const zeus::CVector3f& start, const zeus::CVector3f& end,\n                       CMaterialList exclude);\n  void UpdateLandingSmoke(CStateManager& mgr, bool active);\n  void UpdateParticleEffects(CStateManager& mgr, float intensity, bool active);\n  void DeliverGetUp();\n  void UpdateCanSeePlayer(CStateManager& mgr);\n  void AddToTeam(CStateManager& mgr);\n  void RemoveFromTeam(CStateManager& mgr);\n};\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CGrenadeLauncher.cpp",
    "content": "#include \"Runtime/MP1/World/CGrenadeLauncher.hpp\"\n\n#include \"Runtime/Character/CPASAnimParm.hpp\"\n#include \"Runtime/Character/CPASAnimParmData.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Weapon/CGameProjectile.hpp\"\n#include \"Runtime/World/CExplosion.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n\nnamespace metaforce::MP1 {\nCGrenadeLauncher::CGrenadeLauncher(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                                   const zeus::CTransform& xf, CModelData&& mData, const zeus::CAABox& bounds,\n                                   const CHealthInfo& healthInfo, const CDamageVulnerability& vulnerability,\n                                   const CActorParameters& actParams, TUniqueId parentId,\n                                   const SGrenadeLauncherData& data, float f1)\n: CPhysicsActor(uid, true, name, info, xf, std::move(mData), {EMaterialTypes::Character, EMaterialTypes::Solid}, bounds,\n                SMoverData{1000.f}, actParams, 0.3f, 0.1f)\n, x25c_healthInfo(healthInfo)\n, x264_vulnerability(vulnerability)\n, x2cc_parentId(parentId)\n, x2d0_data(data)\n, x328_cSphere({{}, mData.GetScale().z()}, {EMaterialTypes::Character, EMaterialTypes::Solid})\n, x350_grenadeActorParams(actParams)\n, x3e8_thermalMag(actParams.GetThermalMag())\n, x3f8_explodePlayerDistance(f1) {\n  if (data.GetShootParticleGenDescId().IsValid()) {\n    x3b8_particleGenDesc = g_SimplePool->GetObj({SBIG('PART'), data.GetShootParticleGenDescId()});\n  }\n  GetModelData()->EnableLooping(true);\n  const CPASDatabase& pasDatabase = GetModelData()->GetAnimationData()->GetCharacterInfo().GetPASDatabase();\n  for (size_t i = 0; i < x3c8_animIds.size(); ++i) {\n    const auto result = pasDatabase.FindBestAnimation(\n        CPASAnimParmData{pas::EAnimationState::AdditiveAim, CPASAnimParm::FromEnum(int(i))}, -1);\n    x3c8_animIds[i] = result.second;\n  }\n}\n\nzeus::CVector3f CGrenadeLauncher::GrenadeTarget(const CStateManager& mgr) {\n  const zeus::CVector3f aim = mgr.GetPlayer().GetAimPosition(mgr, 1.f);\n  if (mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Unmorphed) {\n    return aim - zeus::CVector3f{0.f, 0.f, 0.5f * mgr.GetPlayer().GetEyeHeight()};\n  }\n  return aim;\n}\n\nvoid CGrenadeLauncher::CalculateGrenadeTrajectory(const zeus::CVector3f& target, const zeus::CVector3f& origin,\n                                                  const SGrenadeTrajectoryInfo& info, float& angleOut,\n                                                  float& velocityOut) {\n  float angle = info.GetAngleMin();\n  float velocity = info.GetVelocityMin();\n  float delta = std::max(0.01f, 0.1f * (info.GetAngleMax() - info.GetAngleMin()));\n  zeus::CVector3f dist = target - origin;\n  float distXYMag = dist.toVec2f().magnitude();\n  float velocityMinSq = info.GetVelocityMin() * info.GetVelocityMin();\n  float velocityMaxSq = info.GetVelocityMax() * info.GetVelocityMax();\n  float gravAdj = distXYMag * ((0.5f * CPhysicsActor::GravityConstant()) * distXYMag);\n  float currAngle = info.GetAngleMin();\n  float leastResult = FLT_MAX;\n  while (info.GetAngleMax() >= currAngle) {\n    float cos = std::cos(currAngle);\n    float sin = std::sin(currAngle);\n    float result = (distXYMag * (cos * sin) - (dist.z() * (cos * cos)));\n    if (result > FLT_EPSILON) {\n      float div = gravAdj / result;\n      if (velocityMinSq <= result && result <= velocityMaxSq) {\n        angle = currAngle;\n        velocity = std::sqrt(div);\n        break;\n      }\n      if (result <= velocityMaxSq) {\n        result = velocityMinSq - result;\n      } else {\n        result = result - velocityMaxSq;\n      }\n      if (result < leastResult) {\n        angle = currAngle;\n        velocity = std::sqrt(div);\n        leastResult = result;\n      }\n    }\n    currAngle += delta;\n  }\n  angleOut = angle;\n  velocityOut = velocity;\n}\n\nvoid CGrenadeLauncher::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  CActor::AcceptScriptMsg(msg, uid, mgr);\n  switch (msg) {\n  case EScriptObjectMessage::Start:\n    if (x2cc_parentId == uid && x258_started != 1) {\n      x258_started = 1;\n      UpdateStartAnimation();\n    }\n    break;\n  case EScriptObjectMessage::Stop:\n    if (x2cc_parentId == uid && x258_started != 0) {\n      x258_started = 0;\n      UpdateStartAnimation();\n    }\n    break;\n  case EScriptObjectMessage::Action:\n    if (x2cc_parentId == uid && x258_started == 1) {\n      x3fc_launchGrenade = true;\n    }\n    break;\n  case EScriptObjectMessage::Registered:\n    UpdateStartAnimation();\n    break;\n  case EScriptObjectMessage::Damage:\n    x3ec_damageTimer = 0.33f;\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CGrenadeLauncher::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) {\n  CActor::AddToRenderer(frustum, mgr);\n}\n\nstd::optional<zeus::CAABox> CGrenadeLauncher::GetTouchBounds() const {\n  return x328_cSphere.CalculateAABox(GetTransform());\n}\n\nvoid CGrenadeLauncher::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) {\n  if (x3f4_damageAddColor.a() == 1.f) {\n    // Original code redundantly sets a() = 1.f\n    xb4_drawFlags = CModelFlags{2, 0, 3, x3f4_damageAddColor};\n  } else {\n    xb4_drawFlags = CModelFlags{5, 0, 3, x3f4_damageAddColor};\n  }\n  CActor::PreRender(mgr, frustum);\n}\n\nvoid CGrenadeLauncher::Render(CStateManager& mgr) {\n  if (x3fd_visible) {\n    CPhysicsActor::Render(mgr);\n  }\n}\n\nvoid CGrenadeLauncher::Think(float dt, CStateManager& mgr) {\n  if (GetActive()) {\n    if (x3fc_launchGrenade) {\n      LaunchGrenade(mgr);\n      x3fc_launchGrenade = false;\n    }\n\n    UpdateCollision();\n    UpdateColor(dt);\n    UpdateFollowPlayer(mgr, dt);\n    UpdateDamageTime(dt);\n\n    const SAdvancementDeltas& deltas = CActor::UpdateAnimation(dt, mgr, true);\n    MoveToOR(deltas.x0_posDelta, dt);\n    RotateToOR(deltas.xc_rotDelta, dt);\n\n    TCastToPtr<CPatterned> parent = mgr.ObjectById(x2cc_parentId);\n    if (parent == nullptr || !parent->IsAlive() || parent->HealthInfo(mgr)->GetHP() <= 0.f) {\n      mgr.SendScriptMsg(parent, GetUniqueId(), EScriptObjectMessage::Damage);\n      CreateExplosion(mgr);\n      mgr.FreeScriptObject(GetUniqueId());\n    }\n  }\n}\n\nvoid CGrenadeLauncher::Touch(CActor& act, CStateManager& mgr) {\n  if (TCastToPtr<CGameProjectile> projectile = act) {\n    if (projectile->GetOwnerId() == mgr.GetPlayer().GetUniqueId() &&\n        GetDamageVulnerability()->WeaponHurts(CWeaponMode{projectile->GetType()}, false)) {\n      x348_shotTimer = 0.5f;\n      CEntity* parent = mgr.ObjectById(x2cc_parentId);\n      if (parent != nullptr) {\n        mgr.SendScriptMsg(parent, GetUniqueId(), EScriptObjectMessage::Touched);\n      }\n    }\n  }\n}\n\nvoid CGrenadeLauncher::UpdateCollision() {\n  x328_cSphere.SetSphereCenter(GetLocatorTransform(\"lockon_target_LCTR\"sv).origin);\n}\n\nvoid CGrenadeLauncher::UpdateColor(float arg) {\n  if (x348_shotTimer > 0.f) {\n    x348_shotTimer = std::max(0.f, x348_shotTimer - arg);\n    x34c_color1 = zeus::CColor::lerp(zeus::skWhite, zeus::skRed, x348_shotTimer);\n  }\n}\n\nvoid CGrenadeLauncher::UpdateDamageTime(float arg) {\n  if (x3ec_damageTimer <= 0.f) {\n    xd0_damageMag = x3e8_thermalMag;\n  } else {\n    x3ec_damageTimer = std::max(0.f, x3ec_damageTimer - arg);\n    x3f4_damageAddColor =\n        zeus::CColor::lerp(zeus::skBlack, x3f0_color2, std::clamp(x3ec_damageTimer / 0.33f, 0.f, 1.f));\n    xd0_damageMag = 5.f * x3ec_damageTimer + x3e8_thermalMag;\n  }\n}\n\nvoid CGrenadeLauncher::CreateExplosion(CStateManager& mgr) {\n  if (!x3b8_particleGenDesc) {\n    return;\n  }\n  mgr.AddObject(new CExplosion(*x3b8_particleGenDesc, mgr.AllocateUniqueId(), true,\n                               {GetAreaIdAlways(), CEntity::NullConnectionList}, \"Grenade Launcher Explode Fx\"sv,\n                               GetTransform(), 0, GetModelData()->GetScale(), zeus::skWhite));\n  CSfxManager::SfxStart(x2d0_data.GetShootSfxId(), 1.f, 1.f, false, 0x7f, false, kInvalidAreaId);\n}\n\nvoid CGrenadeLauncher::UpdateFollowPlayer(CStateManager& mgr, float dt) {\n  CModelData* modelData = GetModelData();\n  CAnimData* animData = nullptr;\n\n  if (modelData != nullptr && (animData = modelData->GetAnimationData()) != nullptr && x258_started == 1 &&\n      x3fe_followPlayer) {\n    const zeus::CVector3f target = mgr.GetPlayer().GetAimPosition(mgr, 0.f) - GetTranslation();\n    const zeus::CVector3f rot = GetTransform().transposeRotate({target.x(), target.y(), 0.f});\n\n    if (rot.canBeNormalized()) {\n      constexpr float p36d = zeus::degToRad(36.476f);\n      constexpr float n45d = zeus::degToRad(-45.f);\n      constexpr float p45d = zeus::degToRad(45.f);\n\n      float l84 = p36d * std::clamp(std::atan2(rot.x(), rot.y()), n45d, p45d);\n      float l88 = std::clamp((0.25f * (l84 - x3d8_)) / dt, -3.f, 3.f);\n      float l8c = std::clamp((l88 - x3dc_) / dt, -10.f, 10.f);\n      x3dc_ += dt * l8c;\n\n      float l90 = p36d * std::clamp(std::atan2(rot.z(), rot.toVec2f().magnitude()), n45d, p45d);\n      l88 = std::clamp((0.25f * (l90 - x3e0_)) / dt, -3.f, 3.f);\n      l8c = std::clamp((l88 - x3e4_) / dt, -10.f, 10.f);\n      x3e4_ += dt * l8c;\n\n      float dVar7 = std::clamp(dt * x3dc_ + x3d8_, -0.5f, 0.5f);\n      float dVar8 = std::clamp(dt * x3e4_ + x3e0_, -0.5f, 0.5f);\n\n      if (dVar7 != x3d8_) {\n        if (std::abs(x3d8_) > 0.f && x3d8_ * dVar7 <= 0.f) {\n          animData->DelAdditiveAnimation(x3c8_animIds[x3d8_ >= 0.f ? 1 : 0]);\n        }\n        float weight = std::abs(dVar7);\n        if (weight > 0.f) {\n          animData->AddAdditiveAnimation(x3c8_animIds[dVar7 >= 0.f ? 1 : 0], weight, false, false);\n        }\n      }\n      if (dVar8 != x3e0_) {\n        if (std::abs(x3e0_) > 0.f && x3e0_ * dVar8 <= 0.f) {\n          animData->DelAdditiveAnimation(x3c8_animIds[x3e0_ <= 0.f ? 3 : 2]);\n        }\n        float weight = std::abs(dVar8);\n        if (weight > 0.f) {\n          animData->AddAdditiveAnimation(x3c8_animIds[dVar8 <= 0.f ? 3 : 2], weight, false, false);\n        }\n      }\n      x3d8_ = dVar7;\n      x3e0_ = dVar8;\n    }\n  } else {\n    if (x3d8_ != 0.f) {\n      if (animData != nullptr) { // Original code does not check\n        animData->DelAdditiveAnimation(x3c8_animIds[x3d8_ >= 0.f ? 1 : 0]);\n      }\n      x3d8_ = 0.f;\n    }\n    if (x3e0_ != 0.f) {\n      if (animData != nullptr) { // Original code does not check\n        animData->DelAdditiveAnimation(x3c8_animIds[x3e0_ <= 0.f ? 3 : 2]);\n      }\n      x3e0_ = 0.f;\n    }\n  }\n}\n\nvoid CGrenadeLauncher::UpdateStartAnimation() {\n  CModelData* modelData = GetModelData();\n  CAnimData* animData;\n  if (modelData == nullptr || (animData = modelData->GetAnimationData()) == nullptr || x258_started < 0 ||\n      x258_started > 1) {\n    return;\n  }\n\n  constexpr std::array arr{0, 3};\n  const auto anim = animData->GetCharacterInfo().GetPASDatabase().FindBestAnimation(\n      CPASAnimParmData{pas::EAnimationState::Locomotion, CPASAnimParm::FromEnum(0),\n                       CPASAnimParm::FromEnum(arr[x258_started])},\n      -1);\n  if (anim.first > 0.f) {\n    animData->SetAnimation({anim.second, -1, 1.f, true}, false);\n    modelData->EnableLooping(true);\n  }\n}\n\nvoid CGrenadeLauncher::LaunchGrenade(CStateManager& mgr) {\n  CModelData* modelData = GetModelData();\n  CAnimData* animData;\n  if (modelData == nullptr || (animData = modelData->GetAnimationData()) == nullptr) {\n    return;\n  }\n\n  const auto& anim = animData->GetCharacterInfo().GetPASDatabase().FindBestAnimation(\n      CPASAnimParmData{pas::EAnimationState::AdditiveFlinch}, -1);\n  if (anim.first > 0.f) {\n    animData->AddAdditiveAnimation(anim.second, 1.f, false, true);\n    const zeus::CVector3f origin =\n        GetTranslation() + GetTransform().rotate(GetLocatorTransform(\"grenade_LCTR\"sv).origin);\n    const zeus::CVector3f target = GrenadeTarget(mgr);\n    float angleOut = x2d0_data.GetGrenadeTrajectoryInfo().GetAngleMin();\n    float velocityOut = x2d0_data.GetGrenadeTrajectoryInfo().GetVelocityMin();\n    CalculateGrenadeTrajectory(target, origin, x2d0_data.GetGrenadeTrajectoryInfo(), angleOut, velocityOut);\n\n    zeus::CVector3f dist = target - origin;\n    dist.z() = 0.f;\n    const zeus::CVector3f front = GetTransform().frontVector();\n    if (dist.canBeNormalized()) {\n      dist.normalize();\n    } else {\n      dist = front;\n    }\n\n    constexpr float maxAngle = zeus::degToRad(45.f);\n    if (zeus::CVector3f::getAngleDiff(front, dist) > maxAngle) {\n      dist = zeus::CVector3f::slerp(front, dist, maxAngle);\n    }\n\n    const zeus::CVector3f look = zeus::CVector3f::slerp(dist, zeus::skUp, angleOut);\n    const zeus::CTransform xf = zeus::lookAt(origin, origin + look, zeus::skUp);\n    CModelData mData{CStaticRes{x2d0_data.GetGrenadeModelId(), GetModelData()->GetScale()}};\n    mgr.AddObject(new CBouncyGrenade(mgr.AllocateUniqueId(), \"Bouncy Grenade\"sv,\n                                     {GetAreaIdAlways(), CEntity::NullConnectionList}, xf, std::move(mData),\n                                     x350_grenadeActorParams, x2cc_parentId, x2d0_data.GetGrenadeData(), velocityOut,\n                                     x3f8_explodePlayerDistance));\n  }\n}\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CGrenadeLauncher.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Character/CModelData.hpp\"\n#include \"Runtime/Collision/CCollidableSphere.hpp\"\n#include \"Runtime/MP1/World/CBouncyGrenade.hpp\"\n#include \"Runtime/Particle/CGenDescription.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/World/CDamageInfo.hpp\"\n#include \"Runtime/World/CDamageVulnerability.hpp\"\n#include \"Runtime/World/CEntityInfo.hpp\"\n#include \"Runtime/World/CHealthInfo.hpp\"\n#include \"Runtime/World/CPhysicsActor.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\n#include <array>\n#include <string_view>\n#include <zeus/CColor.hpp>\n#include <zeus/CTransform.hpp>\n#include <zeus/CQuaternion.hpp>\n\nnamespace metaforce::MP1 {\nstruct SGrenadeTrajectoryInfo {\nprivate:\n  float x0_velocityMin;\n  float x4_velocityMax;\n  float x8_angleMin;\n  float xc_angleMax;\n\npublic:\n  explicit SGrenadeTrajectoryInfo(CInputStream& in)\n  : x0_velocityMin(in.ReadFloat())\n  , x4_velocityMax(in.ReadFloat())\n  , x8_angleMin(zeus::degToRad(in.ReadFloat()))\n  , xc_angleMax(zeus::degToRad(in.ReadFloat())) {}\n\n  [[nodiscard]] float GetVelocityMin() const { return x0_velocityMin; }\n  [[nodiscard]] float GetVelocityMax() const { return x4_velocityMax; }\n  [[nodiscard]] float GetAngleMin() const { return x8_angleMin; }\n  [[nodiscard]] float GetAngleMax() const { return xc_angleMax; }\n};\n\nstruct SGrenadeLauncherData {\nprivate:\n  SBouncyGrenadeData x0_grenadeData;\n  CAssetId x3c_grenadeModelId;\n  CAssetId x40_shootParticleGenDescId;\n  u16 x44_shootSfxId;\n  SGrenadeTrajectoryInfo x48_grenadeTrajectoryInfo;\n\npublic:\n  SGrenadeLauncherData(const SBouncyGrenadeData& data, CAssetId grenadeModelId, CAssetId shootParticleGenDescId,\n                       u16 shootSfxId, const SGrenadeTrajectoryInfo& grenadeTrajectoryInfo)\n  : x0_grenadeData(data)\n  , x3c_grenadeModelId(grenadeModelId)\n  , x40_shootParticleGenDescId(shootParticleGenDescId)\n  , x44_shootSfxId(shootSfxId)\n  , x48_grenadeTrajectoryInfo(grenadeTrajectoryInfo) {}\n\n  [[nodiscard]] const SBouncyGrenadeData& GetGrenadeData() const { return x0_grenadeData; }\n  [[nodiscard]] CAssetId GetGrenadeModelId() const { return x3c_grenadeModelId; }\n  [[nodiscard]] CAssetId GetShootParticleGenDescId() const { return x40_shootParticleGenDescId; }\n  [[nodiscard]] u16 GetShootSfxId() const { return x44_shootSfxId; }\n  [[nodiscard]] const SGrenadeTrajectoryInfo& GetGrenadeTrajectoryInfo() const { return x48_grenadeTrajectoryInfo; }\n};\n\nclass CGrenadeLauncher : public CPhysicsActor {\nprivate:\n  int x258_started = 0;\n  CHealthInfo x25c_healthInfo;\n  CDamageVulnerability x264_vulnerability;\n  TUniqueId x2cc_parentId;\n  SGrenadeLauncherData x2d0_data;\n  CCollidableSphere x328_cSphere;\n  float x348_shotTimer = -1.f;\n  zeus::CColor x34c_color1{1.f};\n  CActorParameters x350_grenadeActorParams;\n  std::optional<TLockedToken<CGenDescription>> x3b8_particleGenDesc;\n  std::array<s32, 4> x3c8_animIds{};\n  float x3d8_ = 0.f;\n  float x3dc_ = 0.f;\n  float x3e0_ = 0.f;\n  float x3e4_ = 0.f;\n  float x3e8_thermalMag;\n  float x3ec_damageTimer = 0.f;\n  zeus::CColor x3f0_color2{0.5f, 0.f, 0.f};\n  zeus::CColor x3f4_damageAddColor{0.f};\n  float x3f8_explodePlayerDistance;\n  bool x3fc_launchGrenade = false;\n  bool x3fd_visible = true;\n  bool x3fe_followPlayer = true;\n\npublic:\n  DEFINE_ENTITY\n  CGrenadeLauncher(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                   CModelData&& mData, const zeus::CAABox& bounds, const CHealthInfo& healthInfo,\n                   const CDamageVulnerability& vulnerability, const CActorParameters& actParams, TUniqueId parentId,\n                   const SGrenadeLauncherData& data, float f1);\n\n  void Accept(IVisitor& visitor) override { visitor.Visit(this); }\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) override;\n  void AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) override;\n  [[nodiscard]] const CCollisionPrimitive* GetCollisionPrimitive() const override { return &x328_cSphere; }\n  [[nodiscard]] const CDamageVulnerability* GetDamageVulnerability() const override { return &x264_vulnerability; }\n  [[nodiscard]] std::optional<zeus::CAABox> GetTouchBounds() const override;\n  CHealthInfo* HealthInfo(CStateManager& mgr) override { return &x25c_healthInfo; }\n  void PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) override;\n  void Render(CStateManager& mgr) override;\n  void Think(float dt, CStateManager& mgr) override;\n  void Touch(CActor& act, CStateManager& mgr) override;\n\n  void SetAddColor(const zeus::CColor& color) { x3f4_damageAddColor = color; }\n  void SetVisible(bool val) { x3fd_visible = val; }\n  void SetFollowPlayer(bool val) { x3fe_followPlayer = val; }\n\n  static zeus::CVector3f GrenadeTarget(const CStateManager& mgr);\n  static void CalculateGrenadeTrajectory(const zeus::CVector3f& target, const zeus::CVector3f& origin,\n                                         const SGrenadeTrajectoryInfo& info, float& angleOut, float& velocityOut);\n\nprivate:\n  void UpdateCollision();\n  void UpdateColor(float arg);\n  void UpdateDamageTime(float arg);\n  void CreateExplosion(CStateManager& mgr);\n  void UpdateFollowPlayer(CStateManager& mgr, float dt);\n  void UpdateStartAnimation();\n  void LaunchGrenade(CStateManager& mgr);\n};\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CIceAttackProjectile.cpp",
    "content": "#include \"Runtime/MP1/World/CIceAttackProjectile.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce::MP1 {\n\nCIceAttackProjectile::CIceAttackProjectile(const TToken<CGenDescription>& gen1, const TToken<CGenDescription>& gen2,\n                                           const TToken<CGenDescription>& gen3, TUniqueId uid, TAreaId areaId,\n                                           TUniqueId owner, bool active, const zeus::CTransform& xf,\n                                           const CDamageInfo& dInfo, const zeus::CAABox& bounds, float f1, float f2,\n                                           CAssetId unkInt1, u16 unkShort1, u16 unkShort2, CAssetId unkInt2)\n: CActor(uid, active, \"IceAttackProjectile\"sv, CEntityInfo(areaId, NullConnectionList), xf,\n         CModelData::CModelDataNull(), CMaterialList(EMaterialTypes::Projectile, EMaterialTypes::CameraPassthrough),\n         CActorParameters::None(), kInvalidUniqueId)\n, xe8_(gen1)\n, xf0_(gen2)\n, xf8_(gen3)\n, x118_owner(owner)\n, x11c_(dInfo)\n, x138_(dInfo)\n, x154_(bounds)\n, x170_(f1)\n, x174_(f2)\n, x184_(unkInt1)\n, x188_(unkShort1)\n, x18a_(unkShort2)\n, x18c_(unkInt2) {\n\n  zeus::CVector3f m0 = zeus::CVector3f{0.f, 1.f, 0.f}.cross(xf.frontVector()).normalized();\n  zeus::CVector3f m1 = m0.cross(zeus::CVector3f{0.f, 0.f, 1.f}).normalized();\n  SetTransform(zeus::CTransform(m0, m1, zeus::CVector3f{0.f, 0.f, 1.f}, GetTranslation()));\n  x100_ = std::make_unique<CElementGen>(xf8_, CElementGen::EModelOrientationType::Normal,\n                                        CElementGen::EOptionalSystemFlags::One);\n}\n\nvoid CIceAttackProjectile::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CIceAttackProjectile::Think(float dt, CStateManager& mgr) {\n  if (!GetActive()) {\n    return;\n  }\n\n  if (!x190_) {}\n  CEntity::Think(dt, mgr);\n}\n\nvoid CIceAttackProjectile::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId other, CStateManager& mgr) {\n  CActor::AcceptScriptMsg(msg, other, mgr);\n}\n\nvoid CIceAttackProjectile::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) {\n  mgr.AddDrawableActor(*this, -1000.f * CGraphics::mViewMatrix.frontVector(), x9c_renderBounds);\n}\n\nvoid CIceAttackProjectile::Render(CStateManager& mgr) {}\n\nvoid CIceAttackProjectile::Touch(CActor& act, CStateManager& mgr) {}\n} // namespace metaforce::MP1"
  },
  {
    "path": "Runtime/MP1/World/CIceAttackProjectile.hpp",
    "content": "#pragma once\n\n#include \"Runtime/World/CActor.hpp\"\n#include \"Runtime/World/CDamageInfo.hpp\"\n\nnamespace metaforce {\nclass CElementGen;\nnamespace MP1 {\nclass CIceAttackProjectile : public CActor {\n  class CTrailObject {\n    std::unique_ptr<CElementGen> x0_gen1;\n    std::unique_ptr<CElementGen> x8_explosion;\n    TUniqueId x10_collisionObj;\n    float x14_ = 0.f;\n    CActorLights x18_ = CActorLights(1, zeus::CVector3f(0.f, 0.f, 1.f), 4, 4, false, false, false, 0.1f);\n    zeus::CVector3f x2f8_;\n    zeus::CVector3f x304_;\n    zeus::CVector3f x310_;\n    int x31c_ = 0;\n    u8 x320_ = 0;\n\n  public:\n    CTrailObject(CElementGen* gen, TUniqueId uid, const zeus::CVector3f& vec1, const zeus::CVector3f& vec2,\n                 const zeus::CVector3f& vec3)\n    : x0_gen1(gen), x10_collisionObj(uid), x2f8_(vec1), x304_(vec2), x310_(vec3) {}\n  };\n  TToken<CGenDescription> xe8_;\n  TToken<CGenDescription> xf0_;\n  TToken<CGenDescription> xf8_;\n  std::unique_ptr<CElementGen> x100_;\n  std::vector<TUniqueId> x108_trailObjects;\n  TUniqueId x118_owner;\n  CDamageInfo x11c_;\n  CDamageInfo x138_;\n  std::optional<zeus::CAABox> x154_;\n  float x170_;\n  float x174_;\n  float x178_ = 0.f;\n  float x17c_ = 0.f;\n  s32 x180_ = 0;\n  CAssetId x184_;\n  u16 x188_;\n  u16 x18a_;\n  CAssetId x18c_;\n  bool x190_ = false;\n  bool x191_ = false;\n  bool x192_ = false;\n  int x194_ = 0;\n\npublic:\n  DEFINE_ENTITY\n  CIceAttackProjectile(const TToken<CGenDescription>& gen1, const TToken<CGenDescription>& gen2,\n                       const TToken<CGenDescription>& gen3, TUniqueId uid, TAreaId areaId, TUniqueId owner, bool active,\n                       const zeus::CTransform& xf, const CDamageInfo& dInfo, const zeus::CAABox& bounds, float f1,\n                       float f2, CAssetId unkInt1, u16 unkShort1, u16 unkShort2, CAssetId unkInt2);\n\n  void Accept(IVisitor& visitor) override;\n  void Think(float dt, CStateManager& mgr) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId other, CStateManager& mgr) override;\n  void AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) override;\n  void Render(CStateManager& mgr) override;\n\n  [[nodiscard]] std::optional<zeus::CAABox> GetTouchBounds() const override {\n    if (!GetActive()) {\n      return std::nullopt;\n    }\n    return x154_->getTransformedAABox(GetTransform());\n  }\n\n  void Touch(CActor& act, CStateManager& mgr) override;\n};\n} // namespace MP1\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/MP1/World/CIceSheegoth.cpp",
    "content": "#include \"Runtime/MP1/World/CIceSheegoth.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Camera/CCameraManager.hpp\"\n#include \"Runtime/Camera/CCameraShakeData.hpp\"\n#include \"Runtime/Camera/CFirstPersonCamera.hpp\"\n#include \"Runtime/Character/CPASAnimParmData.hpp\"\n#include \"Runtime/Collision/CCollisionActor.hpp\"\n#include \"Runtime/Collision/CCollisionActorManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/Particle/CParticleElectric.hpp\"\n#include \"Runtime/Particle/CParticleSwoosh.hpp\"\n#include \"Runtime/Weapon/CFlameInfo.hpp\"\n#include \"Runtime/Weapon/CFlameThrower.hpp\"\n#include \"Runtime/World/CGameArea.hpp\"\n#include \"Runtime/World/CPatternedInfo.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce::MP1 {\nnamespace {\nconstexpr std::array<SSphereJointInfo, 7> skSphereJointList = {{\n    {\"Jaw_end_LCTR\", 0.55f},\n    {\"Pelvis\", 1.1f},\n    {\"LCTR_SHEMOUTH\", 1.1f},\n    {\"butt_LCTR\", 0.7f},\n    {\"Ice_Shards_LCTR\", 1.2f},\n    {\"GillL_LCTR\", 0.6f},\n    {\"GillR_LCTR\", 0.6f},\n}};\n\nconstexpr std::array<SJointInfo, 2> skLeftLegJointList = {{\n    {\"L_hip\", \"L_knee\", 0.4f, 0.75f},\n    {\"L_ankle\", \"L_Toe_3\", 0.4f, 0.75f},\n}};\n\nconstexpr std::array<SJointInfo, 2> skRightLegJointList = {{\n    {\"R_hip\", \"R_knee\", 0.4f, 0.75f},\n    {\"R_ankle\", \"R_Toe_3\", 0.4f, 0.75f},\n}};\n} // namespace\n\nCIceSheegothData::CIceSheegothData(CInputStream& in, [[maybe_unused]] s32 propertyCount)\n: x0_(zeus::degToRad(in.ReadFloat()))\n, x4_(zeus::degToRad(in.ReadFloat()))\n, x8_(in.Get<zeus::CVector3f>())\n, x14_(in.ReadFloat())\n, x18_(in)\n, x80_(in)\n, xe8_(in)\n, x150_(in)\n, x154_(in)\n, x170_(in.ReadFloat())\n, x174_(in.ReadFloat())\n, x178_(in)\n, x17c_fireBreathResId(in)\n, x180_fireBreathDamage(in)\n, x19c_(in)\n, x1a0_(in)\n, x1a4_(in)\n, x1a8_(in)\n, x1ac_(in)\n, x1b0_(in.ReadFloat())\n, x1b4_(in.ReadFloat())\n, x1b8_(in)\n, x1d4_(CSfxManager::TranslateSFXID(in.ReadLong()))\n, x1d8_(in.ReadFloat())\n, x1dc_(in.ReadFloat())\n, x1e0_maxInterestTime(in.ReadFloat())\n, x1e4_(in)\n, x1e8_(CSfxManager::TranslateSFXID(in.ReadLong()))\n, x1ec_(in)\n, x1f0_24_(in.ReadBool())\n, x1f0_25_(in.ReadBool()) {}\n\nCIceSheegoth::CIceSheegoth(TUniqueId uid, std::string_view name, const CEntityInfo& info, zeus::CTransform& xf,\n                           CModelData&& mData, const CPatternedInfo& pInfo, const CActorParameters& actParms,\n                           const CIceSheegothData& sheegothData)\n: CPatterned(ECharacter::IceSheeegoth, uid, name, EFlavorType::Zero, info, xf, std::move(mData), pInfo,\n             EMovementType::Ground, EColliderType::One, EBodyType::BiPedal, actParms, EKnockBackVariant::Large)\n, x56c_sheegothData(sheegothData)\n, x760_pathSearch(nullptr, 1, pInfo.GetPathfindingIndex(), 1.f, 1.f)\n, x844_approachSearch(nullptr, 1, pInfo.GetPathfindingIndex(), 1.f, 1.f)\n, x94c_(x3b4_speed)\n, x974_(sheegothData.Get_x174())\n, x98c_mouthVulnerability(pInfo.GetDamageVulnerability())\n, x9f4_boneTracking(*GetModelData()->GetAnimationData(), \"Head_1\"sv, zeus::degToRad(80.f), zeus::degToRad(180.f),\n                    EBoneTrackingFlags::None)\n, xa30_(GetBoundingBox(), GetMaterialList())\n, xa58_(sheegothData.Get_x150(), sheegothData.Get_x154())\n, xa84_(sheegothData.Get_x178().IsValid() ? g_SimplePool->GetObj({SBIG('WPSC'), sheegothData.Get_x178()})\n                                          : g_SimplePool->GetObj(\"FlameThrower\"sv))\n, xa8c_(g_SimplePool->GetObj({SBIG('PART'), sheegothData.Get_x1a0()}))\n, xa9c_(std::make_unique<CElementGen>(xa8c_, CElementGen::EModelOrientationType::Normal,\n                                      CElementGen::EOptionalSystemFlags::One))\n, xaa0_(g_SimplePool->GetObj({SBIG('PART'), sheegothData.Get_x1a4()}))\n, xab0_(std::make_unique<CElementGen>(xaa0_, CElementGen::EModelOrientationType::Normal,\n                                      CElementGen::EOptionalSystemFlags::One))\n, xab4_(g_SimplePool->GetObj({SBIG('PART'), sheegothData.Get_x1a4()}))\n, xac4_(std::make_unique<CElementGen>(xaa0_, CElementGen::EModelOrientationType::Normal,\n                                      CElementGen::EOptionalSystemFlags::One))\n, xac8_(g_SimplePool->GetObj({SBIG('ELSC'), sheegothData.Get_x1ac()}))\n, xad8_(std::make_unique<CParticleElectric>(xac8_))\n, xadc_(g_SimplePool->GetObj({SBIG('PART'), sheegothData.Get_x19c()})) {\n  UpdateTouchBounds();\n  x460_knockBackController.SetEnableFreeze(false);\n  x460_knockBackController.SetX82_24(false);\n  x460_knockBackController.SetEnableLaggedBurnDeath(false);\n  x460_knockBackController.SetEnableExplodeDeath(false);\n  x950_ = GetAnimationDistance(\n              CPASAnimParmData(pas::EAnimationState::Step, CPASAnimParm::FromEnum(1), CPASAnimParm::FromEnum(0))) *\n          GetModelData()->GetScale().y();\n  xa9c_->SetGlobalScale(GetModelData()->GetScale());\n  xab0_->SetGlobalScale(GetModelData()->GetScale());\n  xac4_->SetGlobalScale(GetModelData()->GetScale());\n  xad8_->SetGlobalScale(GetModelData()->GetScale());\n  x450_bodyController->BodyStateInfo().SetLocoAnimChangeAtEndOfAnimOnly(true);\n  MakeThermalColdAndHot();\n  x328_31_energyAttractor = true;\n}\n\nvoid CIceSheegoth::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CIceSheegoth::Think(float dt, CStateManager& mgr) {\n  if (!GetActive()) {\n    return;\n  }\n\n  CPatterned::Think(dt, mgr);\n  AttractProjectiles(mgr);\n  UpdateTimers(dt);\n  UpdateScanState(mgr);\n  if (!IsAlive()) {\n    x974_ = std::max(0.f, x974_ - (dt * x56c_sheegothData.Get_x170()));\n    if (GetBodyController()->GetBodyStateInfo().GetCurrentState()->IsDying()) {\n      SetPassthroughVulnerability(mgr);\n    }\n  }\n  x96c_ -= dt;\n  if (x96c_ < 0.f) {\n    GetBodyController()->GetCommandMgr().DeliverCmd(\n        CBCAdditiveReactionCmd(pas::EAdditiveReactionType::Four, 1.f, false));\n    x96c_ = 3.f * mgr.GetActiveRandom()->Float() + 2.f;\n  }\n  GetModelData()->GetAnimationData()->PreRender();\n  x9f4_boneTracking.Update(dt);\n  x9f4_boneTracking.PreRender(mgr, *GetModelData()->GetAnimationData(), GetTransform(), GetModelData()->GetScale(),\n                              *GetBodyController());\n  xa2c_collisionManager->Update(dt, mgr, CCollisionActorManager::EUpdateOptions::ObjectSpace);\n  PreventWorldCollisions(dt, mgr);\n  UpdateHealthInfo(mgr);\n  SetSteeringSpeed(dt, mgr);\n  UpdateParticleEffects(dt, mgr);\n  UpdateThermalFrozenState(x428_damageCooldownTimer > 0.f);\n}\n\nvoid CIceSheegoth::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) {\n  switch (msg) {\n  case EScriptObjectMessage::Activate: {\n    xa2c_collisionManager->SetActive(mgr, true);\n    break;\n  }\n  case EScriptObjectMessage::Deactivate: {\n    xa2c_collisionManager->SetActive(mgr, false);\n    break;\n  }\n  case EScriptObjectMessage::Start: {\n    xb28_24_shotAt = true;\n    break;\n  }\n  case EScriptObjectMessage::Touched: {\n    ApplyContactDamage(sender, mgr);\n    if (const TCastToConstPtr<CCollisionActor> colAct = mgr.ObjectById(sender)) {\n      if (const TCastToConstPtr<CWeapon> wp = mgr.GetObjectById(colAct->GetLastTouchedObject())) {\n        if (wp->GetOwnerId() == mgr.GetPlayer().GetUniqueId()) {\n          xb28_24_shotAt = true;\n        }\n      }\n    }\n    break;\n  }\n  case EScriptObjectMessage::Registered: {\n    if (!HasPatrolPath(mgr, 0.f)) {\n      x450_bodyController->SetLocomotionType(pas::ELocomotionType::Crouch);\n    }\n    x450_bodyController->Activate(mgr);\n    SetupCollisionActorManager(mgr);\n    CreateFlameThrower(mgr);\n    if (x450_bodyController->GetBodyStateInfo().GetMaxSpeed() > 0.f) {\n      x944_ = x948_ = (0.9f * x450_bodyController->GetBodyStateInfo().GetLocomotionSpeed(pas::ELocomotionAnim::Walk)) /\n                      x450_bodyController->GetBodyStateInfo().GetMaxSpeed();\n    }\n    GetBodyController()->GetCommandMgr().SetSteeringBlendMode(ESteeringBlendMode::FullSpeed);\n    xaf4_mouthLocator = GetModelData()->GetAnimationData()->GetLocatorSegId(\"LCTR_SHEMOUTH\"sv);\n    break;\n  }\n  case EScriptObjectMessage::Deleted: {\n    xa2c_collisionManager->Destroy(mgr);\n    if (xa80_flameThrowerId != kInvalidUniqueId) {\n      mgr.FreeScriptObject(xa80_flameThrowerId);\n      xa80_flameThrowerId = kInvalidUniqueId;\n    }\n    if (xaf0_crackleSfx) {\n      CSfxManager::RemoveEmitter(xaf0_crackleSfx);\n      xaf0_crackleSfx.reset();\n    }\n    break;\n  }\n  case EScriptObjectMessage::InitializedInArea: {\n    x760_pathSearch.SetArea(mgr.GetWorld()->GetArea(GetAreaIdAlways())->GetPostConstructed()->x10bc_pathArea);\n    x844_approachSearch.SetArea(mgr.GetWorld()->GetArea(GetAreaIdAlways())->GetPostConstructed()->x10bc_pathArea);\n    break;\n  }\n  case EScriptObjectMessage::Damage: {\n    if (const TCastToConstPtr<CCollisionActor> colAct = mgr.ObjectById(sender)) {\n      if (const TCastToConstPtr<CWeapon> wp = mgr.GetObjectById(colAct->GetLastTouchedObject())) {\n        if (sender == xaf6_iceShardsCollider && !xb28_27_) {\n          sub_8019ebf0(mgr, wp->GetDamageInfo().GetDamage());\n          if (!xaec_ || xaec_->IsSystemDeletable()) {\n            xaec_ = std::make_unique<CElementGen>(xadc_, CElementGen::EModelOrientationType::Normal,\n                                                  CElementGen::EOptionalSystemFlags::One);\n          }\n        } else {\n          TakeDamage(zeus::skZero3f, 0.f);\n          if (IsGillCollider(colAct.GetPtr())) {\n            x97c_ = 0.2f;\n            x980_ = GetTransform().basis[1];\n          }\n        }\n      }\n    } else {\n      ApplyWeaponDamage(mgr, sender);\n    }\n    xb28_24_shotAt = true;\n    x968_interestTimer = 0.f;\n    mgr.InformListeners(GetTranslation(), EListenNoiseType::PlayerFire);\n    break;\n  }\n  case EScriptObjectMessage::InvulnDamage: {\n    if (sender == xaf6_iceShardsCollider && !xb28_27_) {\n      if (const TCastToConstPtr<CCollisionActor> colAct = mgr.ObjectById(sender)) {\n        if (const TCastToConstPtr<CWeapon> wp = mgr.GetObjectById(colAct->GetLastTouchedObject())) {\n          sub_8019ebf0(mgr, wp->GetDamageInfo().GetDamage());\n          if (!xaec_ || xaec_->IsSystemDeletable()) {\n            xaec_ = std::make_unique<CElementGen>(xadc_, CElementGen::EModelOrientationType::Normal,\n                                                  CElementGen::EOptionalSystemFlags::One);\n          }\n        }\n      }\n    }\n    mgr.InformListeners(GetTranslation(), EListenNoiseType::PlayerFire);\n    xb28_24_shotAt = true;\n    x968_interestTimer = 0.f;\n    break;\n  }\n  case EScriptObjectMessage::SuspendedMove: {\n    if (xa2c_collisionManager != nullptr) {\n      xa2c_collisionManager->SetMovable(mgr, false);\n    }\n    break;\n  }\n  default:\n    break;\n  }\n  CPatterned::AcceptScriptMsg(msg, sender, mgr);\n}\n\nvoid CIceSheegoth::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) {\n  CPatterned::AddToRenderer(frustum, mgr);\n\n  if (mgr.GetThermalDrawFlag() == EThermalDrawFlag::Hot) {\n    if (x428_damageCooldownTimer == 0.f && xb29_25_ &&\n        (HasModelData() && (GetModelData()->HasAnimData() || GetModelData()->HasNormalModel()))) {\n      GetModelData()->RenderParticles(frustum);\n    }\n  } else {\n    std::optional<zeus::CAABox> bounds1;\n    if (xaec_) {\n      bounds1 = xaec_->GetBounds();\n    }\n    std::optional<zeus::CAABox> bounds2;\n    if (xa9c_) {\n      bounds2 = xa9c_->GetBounds();\n    }\n\n    std::optional<zeus::CAABox> bounds3;\n    if (xab0_) {\n      bounds3 = xab0_->GetBounds();\n    }\n\n    std::optional<zeus::CAABox> bounds4;\n    if (xac4_) {\n      bounds4 = xac4_->GetBounds();\n    }\n\n    std::optional<zeus::CAABox> bounds5;\n    if (xad8_) {\n      bounds5 = xad8_->GetBounds();\n    }\n\n    /* Lawl retro\n    std::optional<zeus::CAABox> bounds6;\n    if (xad8_) {\n      bounds6 = xad8_->GetBounds();\n    }\n   */\n\n    zeus::CAABox accumulatedBounds = zeus::skInvertedBox;\n    if (bounds1) {\n      accumulatedBounds.accumulateBounds(*bounds1);\n    }\n    /* Lawl retro\n    if (bounds6) {\n      accumulatedBounds.accumulateBounds(*bounds6);\n    }\n    */\n    if (bounds2) {\n      accumulatedBounds.accumulateBounds(*bounds2);\n    }\n    if (bounds3) {\n      accumulatedBounds.accumulateBounds(*bounds3);\n    }\n    if (bounds4) {\n      accumulatedBounds.accumulateBounds(*bounds4);\n    }\n    if (bounds5) {\n      accumulatedBounds.accumulateBounds(*bounds5);\n    }\n\n    if (frustum.aabbFrustumTest(accumulatedBounds)) {\n      if (xaec_) {\n        g_Renderer->AddParticleGen(*xaec_);\n      }\n\n      g_Renderer->AddParticleGen(*xa9c_);\n      g_Renderer->AddParticleGen(*xab0_);\n      g_Renderer->AddParticleGen(*xac4_);\n      g_Renderer->AddParticleGen(*xad8_);\n    }\n  }\n}\n\nzeus::CVector3f CIceSheegoth::GetAimPosition(const CStateManager& mgr, float dt) const {\n  if (GetBodyController()->GetLocomotionType() != pas::ELocomotionType::Crouch) {\n    if (mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::Thermal) {\n      zeus::CVector3f pos = zeus::skZero3f;\n      for (TUniqueId uid : xafc_gillColliders) {\n        if (const TCastToConstPtr<CCollisionActor> colAct = mgr.GetObjectById(uid)) {\n          pos += colAct->GetTranslation();\n        }\n      }\n      if (!pos.isZero()) {\n        return pos * (1.f / static_cast<float>(xafc_gillColliders.size()));\n      }\n    } else if (!xb29_29_scanned && mgr.GetPlayerState()->GetActiveVisor(mgr) != CPlayerState::EPlayerVisor::Scan) {\n      if (const TCastToConstPtr<CCollisionActor> colAct = mgr.GetObjectById(xaf6_iceShardsCollider)) {\n        return colAct->GetTranslation();\n      }\n    } else if (const TCastToConstPtr<CCollisionActor> colAct = mgr.GetObjectById(xaf8_mouthCollider)) {\n      return colAct->GetTranslation();\n    }\n  }\n\n  return CPatterned::GetAimPosition(mgr, dt);\n}\n\nEWeaponCollisionResponseTypes CIceSheegoth::GetCollisionResponseType(const zeus::CVector3f& v1,\n                                                                     const zeus::CVector3f& v2, const CWeaponMode& mode,\n                                                                     EProjectileAttrib attrib) const {\n  return mode.GetType() == EWeaponType::Ice ? EWeaponCollisionResponseTypes::None\n                                            : CPatterned::GetCollisionResponseType(v1, v2, mode, attrib);\n}\n\nzeus::CAABox CIceSheegoth::GetSortingBounds(const CStateManager& mgr) const {\n  const zeus::CAABox box = CActor::GetSortingBounds(mgr);\n  const zeus::CVector3f offset = 0.125f * (box.max - box.min);\n  const zeus::CVector3f center = box.center();\n  return {center - offset, center + offset};\n}\n\nvoid CIceSheegoth::DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) {\n  switch (type) {\n  case EUserEventType::Projectile: {\n    zeus::CTransform gunPos = GetLctrTransform(node.GetLocatorName());\n    zeus::CVector3f interceptPos = GetProjectileInfo()->PredictInterceptPos(\n        gunPos.origin, mgr.GetPlayer().GetAimPosition(mgr, 0.f), mgr.GetPlayer(), true, dt);\n    zeus::CVector3f predictOffset = interceptPos - gunPos.origin;\n    zeus::CVector3f lookPos;\n    if (zeus::CVector3f::getAngleDiff(GetTransform().frontVector(), predictOffset) > zeus::degToRad(60.f)) {\n      if (!predictOffset.canBeNormalized()) {\n        lookPos = gunPos.origin + predictOffset.magnitude() * gunPos.frontVector();\n      } else {\n        lookPos = gunPos.origin + (predictOffset.magnitude() *\n                                   zeus::CVector3f::slerp(GetTransform().frontVector(), predictOffset.normalized(),\n                                                          zeus::CRelAngle::FromDegrees(60.f)));\n      }\n    }\n\n    LaunchProjectile(zeus::lookAt(gunPos.origin, lookPos), mgr, 4, EProjectileAttrib::None, false, std::nullopt, -1,\n                     false, zeus::skOne3f);\n    x974_ = std::max(0.f, x974_ - 0.3333f * x56c_sheegothData.Get_x170());\n    if (xb28_27_ && !ShouldSpecialAttack(mgr, 0.f)) {\n      GetBodyController()->GetCommandMgr().DeliverCmd(CBodyStateCmd{EBodyStateCmd::ExitState});\n    }\n    return;\n  }\n  case EUserEventType::DamageOn:\n    if (!xb28_26_) {\n      break;\n    }\n\n    if (CFlameThrower* fl = static_cast<CFlameThrower*>(mgr.ObjectById(xa80_flameThrowerId))) {\n      fl->Fire(GetTransform(), mgr, false);\n    }\n    break;\n  case EUserEventType::DamageOff:\n    if (!xb28_26_) {\n      break;\n    }\n\n    if (CFlameThrower* fl = static_cast<CFlameThrower*>(mgr.ObjectById(xa80_flameThrowerId))) {\n      fl->Reset(mgr, false);\n    }\n    break;\n  case EUserEventType::ScreenShake:\n    ShakePlayer(mgr);\n    return;\n  case EUserEventType::BecomeShootThrough:\n    SetPassthroughVulnerability(mgr);\n    return;\n  default:\n    break;\n  }\n  CPatterned::DoUserAnimEvent(mgr, node, type, dt);\n}\n\nvoid CIceSheegoth::PathFind(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    xb28_24_shotAt = false;\n    xb29_28_ = false;\n    x968_interestTimer = 0.f;\n    GetBodyController()->SetLocomotionType(pas::ELocomotionType::Relaxed);\n    x9f4_boneTracking.SetTarget(mgr.GetPlayer().GetUniqueId());\n    x9f4_boneTracking.SetActive(true);\n    UpdateAttackPosition(mgr, x2e0_destPos);\n    SetPathFindMode(EPathFindMode::Normal);\n    if (!x56c_sheegothData.Get_x1f0_24()) {\n      CPatterned::PathFind(mgr, msg, dt);\n    }\n  } else if (msg == EStateMsg::Update) {\n    SetPathFindMode(EPathFindMode::Normal);\n    if (x56c_sheegothData.Get_x1f0_24() && GetSearchPath() != nullptr && !PathShagged(mgr, 0.f) &&\n        x760_pathSearch.GetCurrentWaypoint() < x760_pathSearch.GetWaypoints().size() - 1) {\n      CPatterned::PathFind(mgr, EStateMsg::Update, dt);\n      x968_interestTimer = 0.f;\n      zeus::CVector3f moveVec = GetBodyController()->GetCommandMgr().GetMoveVector();\n      if (GetTransform().basis[1].dot(moveVec) < 0.f && moveVec.canBeNormalized()) {\n        GetBodyController()->GetCommandMgr().ClearLocomotionCmds();\n        GetBodyController()->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(zeus::skZero3f, moveVec.normalized(), 1.f));\n      }\n    } else {\n      const zeus::CVector3f posDiff = mgr.GetPlayer().GetTranslation() - GetTranslation();\n      if (sub_8019ecdc(mgr, zeus::degToRad(15.f)) && posDiff.canBeNormalized()) {\n        GetBodyController()->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(zeus::skZero3f, posDiff.normalized(), 1.f));\n      }\n    }\n\n    x3b4_speed =\n        (GetBodyController()->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::Turn ? 2.f : 1.f) * x94c_;\n  } else if (msg == EStateMsg::Deactivate) {\n    x9f4_boneTracking.SetActive(false);\n    x3b4_speed = x94c_;\n  }\n}\n\nvoid CIceSheegoth::TargetPatrol(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    GetBodyController()->SetLocomotionType(pas::ELocomotionType::Relaxed);\n\n    if (HasPatrolPath(mgr, 0.f)) {\n      Patrol(mgr, msg, dt);\n      UpdateDest(mgr);\n    } else {\n      SetDestPos(x3a0_latestLeashPosition);\n    }\n\n    const zeus::CVector3f arrivalPos = x45c_steeringBehaviors.Arrival(*this, x92c_lastDest, 15.f);\n    x92c_lastDest = x2e0_destPos;\n\n    if (GetSearchPath() != nullptr) {\n      SetPathFindMode(EPathFindMode::Normal);\n      CPatterned::PathFind(mgr, msg, dt);\n      const zeus::CVector3f moveVec = GetBodyController()->GetCommandMgr().GetMoveVector();\n      if (moveVec.canBeNormalized()) {\n        GetBodyController()->GetCommandMgr().ClearLocomotionCmds();\n        GetBodyController()->GetCommandMgr().DeliverCmd(\n            CBCLocomotionCmd{arrivalPos.magnitude() * moveVec.normalized(), zeus::skZero3f, 1.f});\n      } else {\n        GetBodyController()->GetCommandMgr().DeliverCmd(CBCLocomotionCmd{arrivalPos, zeus::skZero3f, 1.f});\n      }\n    }\n  } else if (msg == EStateMsg::Update) {\n    const zeus::CVector3f arrivalPos = x45c_steeringBehaviors.Arrival(*this, x92c_lastDest, 15.f);\n\n    if (GetSearchPath() && PathShagged(mgr, dt)) {\n      SetPathFindMode(EPathFindMode::Normal);\n      CPatterned::PathFind(mgr, EStateMsg::Update, dt);\n      const zeus::CVector3f moveVec = GetBodyController()->GetCommandMgr().GetMoveVector();\n      if (moveVec.canBeNormalized()) {\n        GetBodyController()->GetCommandMgr().ClearLocomotionCmds();\n        GetBodyController()->GetCommandMgr().DeliverCmd(\n            CBCLocomotionCmd{arrivalPos.magnitude() * moveVec.normalized(), zeus::skZero3f, 1.f});\n      } else {\n        GetBodyController()->GetCommandMgr().DeliverCmd(CBCLocomotionCmd{arrivalPos, zeus::skZero3f, 1.f});\n      }\n    }\n  }\n}\n\nvoid CIceSheegoth::Generate(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x568_ = 0;\n    x938_ = GetTransform().basis[1];\n  } else if (msg == EStateMsg::Update) {\n    if (x568_ == 0) {\n      if (GetBodyController()->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::Generate) {\n        GetBodyController()->SetLocomotionType(pas::ELocomotionType::Relaxed);\n        x568_ = 3;\n        SendScriptMsgs(EScriptObjectState::Attack, mgr, EScriptObjectMessage::None);\n      } else {\n        GetBodyController()->GetCommandMgr().DeliverCmd(\n            CBCGenerateCmd{x56c_sheegothData.Get_x1f0_25() ? pas::EGenerateType::Eight : pas::EGenerateType::Zero});\n      }\n    } else if (x568_ == 3 &&\n               GetBodyController()->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::Generate) {\n      x568_ = 4;\n    }\n  }\n}\n\nvoid CIceSheegoth::Deactivate(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x568_ = 1;\n  } else if (msg == EStateMsg::Update) {\n    if (x568_ == 0) {\n      if (GetBodyController()->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::Generate) {\n        x568_ = 3;\n      } else {\n        GetBodyController()->GetCommandMgr().DeliverCmd(CBCGenerateCmd{pas::EGenerateType::One});\n      }\n    } else if (x568_ == 1) {\n      if ((x3a0_latestLeashPosition - GetTranslation()).magSquared() > 1.f) {\n        GetBodyController()->GetCommandMgr().DeliverCmd(CBCLocomotionCmd{\n            x45c_steeringBehaviors.Arrival(*this, x3a0_latestLeashPosition, 15.f), zeus::skZero3f, 1.f});\n      } else {\n        x568_ = 2;\n      }\n    } else if (x568_ == 2) {\n      const float angleDiff = zeus::CVector3f::getAngleDiff(GetTransform().frontVector(), x938_);\n      if (angleDiff <= zeus::degToRad(5.f)) {\n        x568_ = 0;\n      } else {\n        GetBodyController()->GetCommandMgr().DeliverCmd(CBCLocomotionCmd{zeus::skZero3f, x938_, 1.f});\n      }\n    } else if (x568_ == 3) {\n      x568_ = 4;\n    }\n  }\n}\n\nvoid CIceSheegoth::Attack(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x568_ = 0;\n    xb28_29_ = true;\n    xb28_25_ = true;\n    x2e0_destPos = mgr.GetPlayer().GetAimPosition(mgr, 0.f);\n    SetPathFindMode(EPathFindMode::Normal);\n    CPatterned::PathFind(mgr, msg, dt);\n    SetCollisionActorExtendedTouchBounds(mgr, zeus::CVector3f{2.f});\n    x95c_ = 0.f;\n  } else if (msg == EStateMsg::Update) {\n    if (x568_ == 0) {\n      x95c_ += dt;\n      if (x95c_ >= x56c_sheegothData.Get_x1d8()) {\n        x568_ = 4;\n      } else if (!xb28_29_) {\n        x568_ = 3;\n        xb28_29_ = true;\n        const bool isPlayerMorphed =\n            mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed;\n        GetBodyController()->GetCommandMgr().DeliverCmd(\n            CBCMeleeAttackCmd{isPlayerMorphed ? pas::ESeverity::Two : pas::ESeverity::One});\n      } else if (xb28_29_) {\n        const zeus::CTransform mouthXf = GetLctrTransform(xaf4_mouthLocator);\n        if (GetTransform().frontVector().dot(mgr.GetPlayer().GetTranslation() - mouthXf.origin) > 0.f) {\n          SetPathFindMode(EPathFindMode::Normal);\n          if (GetSearchPath() != nullptr && !PathShagged(mgr, 0.f)) {\n            CPatterned::PathFind(mgr, EStateMsg::Update, dt);\n          } else {\n            x568_ = 4;\n          }\n        } else {\n          x568_ = 4;\n        }\n      }\n    } else if (x568_ == 3 &&\n               GetBodyController()->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::MeleeAttack) {\n      x568_ = 4;\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    if (!sub_801a1794(mgr)) {\n      x954_attackTimeLeft = x308_attackTimeVariation * mgr.GetActiveRandom()->Float() + x304_averageAttackTime;\n    }\n\n    SetCollisionActorExtendedTouchBounds(mgr, zeus::skZero3f);\n    xb28_29_ = false;\n    xb28_25_ = false;\n    x960_ = x56c_sheegothData.Get_x1dc();\n    x95c_ = 0.f;\n  }\n}\n\nvoid CIceSheegoth::DoubleSnap(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x568_ = 0;\n    xb28_29_ = true;\n    xb28_28_ = true;\n  } else if (msg == EStateMsg::Update) {\n    if (x568_ == 0) {\n      if (GetBodyController()->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::MeleeAttack) {\n        x568_ = 3;\n      } else if (IsOnGround()) {\n        GetBodyController()->GetCommandMgr().DeliverCmd(CBCMeleeAttackCmd{pas::ESeverity::Zero});\n      }\n    } else if (x568_ == 3 &&\n               GetBodyController()->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::MeleeAttack) {\n      x568_ = 4;\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    if (!sub_801a1794(mgr) || !TooClose(mgr, dt)) {\n      x958_ = x308_attackTimeVariation * mgr.GetActiveRandom()->Float() + x304_averageAttackTime;\n    }\n    xb28_29_ = false;\n    xb28_28_ = false;\n  }\n}\n\nvoid CIceSheegoth::TurnAround(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x9f4_boneTracking.SetTarget(mgr.GetPlayer().GetUniqueId());\n    x9f4_boneTracking.SetActive(true);\n    UpdateAttackPosition(mgr, x2e0_destPos);\n    SetPathFindMode(EPathFindMode::Normal);\n    CPatterned::PathFind(mgr, EStateMsg::Activate, dt);\n    GetBodyController()->GetCommandMgr().ClearLocomotionCmds();\n  } else if (msg == EStateMsg::Update) {\n    if (!sub_8019ecdc(mgr, zeus::degToRad(15.f))) {\n      const float speedScale = GetModelData()->GetAnimationData()->GetSpeedScale();\n      const zeus::CVector3f aimPos =\n          (mgr.GetPlayer().GetAimPosition(mgr, speedScale > 0.f ? 1.25f / speedScale : 0.f).toVec2f() -\n           GetTranslation().toVec2f());\n      if (aimPos.canBeNormalized()) {\n        GetBodyController()->GetCommandMgr().DeliverCmd(CBCLocomotionCmd{zeus::skZero3f, aimPos.normalized(), 1.f});\n      }\n    }\n    x3b4_speed =\n        (GetBodyController()->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::Turn ? 2.f : 1.f) * x94c_;\n  } else if (msg == EStateMsg::Deactivate) {\n    x9f4_boneTracking.SetActive(false);\n    x3b4_speed = x94c_;\n  }\n}\n\nvoid CIceSheegoth::Crouch(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    RemoveMaterial(EMaterialTypes::Orbit, EMaterialTypes::Target, mgr);\n    mgr.GetPlayer().TryToBreakOrbit(GetUniqueId(), CPlayer::EPlayerOrbitRequest::ActivateOrbitSource, mgr);\n    GetBodyController()->SetLocomotionType(pas::ELocomotionType::Crouch);\n    x968_interestTimer = x56c_sheegothData.GetMaxInterestTime();\n    x400_24_hitByPlayerProjectile = false;\n  } else if (msg == EStateMsg::Deactivate) {\n    AddMaterial(EMaterialTypes::Orbit, EMaterialTypes::Target, mgr);\n  }\n}\n\nvoid CIceSheegoth::Taunt(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x568_ = 0;\n    xb29_25_ = true;\n    SetMouthVulnerability(mgr, true);\n  } else if (msg == EStateMsg::Update) {\n    if (x568_ == 0) {\n      if (GetBodyController()->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::Taunt) {\n        x568_ = 3;\n      } else {\n        GetBodyController()->GetCommandMgr().DeliverCmd(CBCTauntCmd{pas::ETauntType::Zero});\n      }\n    } else if (x568_ == 3 &&\n               GetBodyController()->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::Taunt) {\n      x568_ = 4;\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    SetMouthVulnerability(mgr, false);\n    xb29_25_ = false;\n  }\n}\n\nvoid CIceSheegoth::ProjectileAttack(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x568_ = 0;\n    xb28_26_ = true;\n    xb29_25_ = true;\n    xb29_27_ = false;\n    x968_interestTimer = 0.f;\n    SetGillVulnerability(mgr, true);\n  } else if (msg == EStateMsg::Update) {\n    if (x568_ == 0) {\n      if (GetBodyController()->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::ProjectileAttack) {\n        GetBodyController()->GetCommandMgr().DeliverCmd(\n            CBCProjectileAttackCmd{pas::ESeverity::Two, mgr.GetPlayer().GetTranslation(), false});\n      } else {\n        x568_ = 3;\n        x3b4_speed = 2.f * x94c_;\n      }\n    } else if (x568_ == 3 &&\n               GetBodyController()->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::ProjectileAttack) {\n      x568_ = 4;\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x3b4_speed = x94c_;\n    if (!sub_801a1794(mgr)) {\n      x954_attackTimeLeft = x308_attackTimeVariation * mgr.GetActiveRandom()->Float() + x304_averageAttackTime;\n    }\n\n    if (CFlameThrower* fl = static_cast<CFlameThrower*>(mgr.ObjectById(xa80_flameThrowerId))) {\n      fl->Reset(mgr, false);\n    }\n    SetGillVulnerability(mgr, false);\n    if (GetBodyController()->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::ProjectileAttack) {\n      GetBodyController()->GetCommandMgr().DeliverCmd(CBodyStateCmd{EBodyStateCmd::NextState});\n    }\n    xb29_25_ = false;\n    xb28_26_ = false;\n  }\n}\n\nvoid CIceSheegoth::Flinch(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x568_ = 0;\n    xb29_25_ = true;\n    SetGillVulnerability(mgr, true);\n    SetMouthVulnerability(mgr, true);\n  } else if (msg == EStateMsg::Update) {\n    if (x568_ == 0) {\n      if (GetBodyController()->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::KnockBack) {\n        GetBodyController()->GetCommandMgr().DeliverCmd(CBCKnockBackCmd{x980_, pas::ESeverity::One});\n      } else {\n        x568_ = 4;\n      }\n    } else if (x568_ == 3 &&\n               GetBodyController()->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::KnockBack) {\n      x568_ = 4;\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    SetGillVulnerability(mgr, true);\n    SetMouthVulnerability(mgr, true);\n    xb29_25_ = false;\n  }\n}\n\nvoid CIceSheegoth::Approach(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    GetBodyController()->SetLocomotionType(pas::ELocomotionType::Relaxed);\n    x9f4_boneTracking.SetTarget(mgr.GetPlayer().GetUniqueId());\n    UpdateAttackPosition(mgr, x2e0_destPos);\n    SetPathFindMode(EPathFindMode::Normal);\n    if (!xb29_27_) {\n      GetBodyController()->GetCommandMgr().ClearLocomotionCmds();\n    }\n    xb29_24_ = !xb29_24_;\n    xb29_28_ = true;\n    x92c_lastDest = mgr.GetPlayer().GetTranslation();\n  } else if (msg == EStateMsg::Update) {\n    SetPathFindMode(EPathFindMode::Approach);\n    if (xb29_24_) {\n      x2e0_destPos = x92c_lastDest;\n      if (x844_approachSearch.FindClosestReachablePoint(GetTranslation(), x2e0_destPos) ==\n          CPathFindSearch::EResult::Success) {\n        if ((GetTranslation() - x2e0_destPos).magSquared() < 81.f) {\n          x2e0_destPos = GetTranslation();\n        }\n        x92c_lastDest = x2e0_destPos;\n        CPatterned::PathFind(mgr, EStateMsg::Activate, dt);\n        if (!xb29_27_) {\n          GetBodyController()->GetCommandMgr().ClearLocomotionCmds();\n        }\n      }\n      xb29_24_ = false;\n    }\n\n    if (x56c_sheegothData.Get_x1f0_24() && GetSearchPath() != nullptr && !PathShagged(mgr, 0.f) &&\n        x760_pathSearch.GetCurrentWaypoint() < x760_pathSearch.GetWaypoints().size() - 1) {\n      CPatterned::PathFind(mgr, EStateMsg::Update, dt);\n    } else {\n      const zeus::CVector3f posDiff = mgr.GetPlayer().GetTranslation() - GetTranslation();\n      if (sub_8019ecdc(mgr, zeus::degToRad(15.f)) && posDiff.canBeNormalized()) {\n        GetBodyController()->GetCommandMgr().DeliverCmd(CBCLocomotionCmd{zeus::skZero3f, posDiff.normalized(), 1.f});\n      }\n    }\n\n    xb29_27_ = true;\n    x3b4_speed =\n        (GetBodyController()->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::Turn ? 2.f : 1.f) * x94c_;\n  } else if (msg == EStateMsg::Deactivate) {\n    x9f4_boneTracking.SetActive(false);\n    SetPathFindMode(EPathFindMode::Normal);\n    x3b4_speed = x94c_;\n  }\n}\n\nvoid CIceSheegoth::Enraged(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x568_ = 0;\n    xb28_30_ = true;\n  } else if (msg == EStateMsg::Update) {\n    if (x568_ == 0) {\n      if (GetBodyController()->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::Generate) {\n        GetBodyController()->GetCommandMgr().DeliverCmd(CBCGenerateCmd(pas::EGenerateType::Three));\n      } else {\n        x568_ = 3;\n      }\n    } else if (x568_ == 3 &&\n               GetBodyController()->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::Generate) {\n      x568_ = 4;\n    }\n  }\n}\n\nvoid CIceSheegoth::SpecialAttack(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x568_ = 0;\n    x968_interestTimer = 0.f;\n    xb28_27_ = true;\n    xb29_27_ = false;\n  } else if (msg == EStateMsg::Update) {\n    if (x568_ == 0) {\n      if (GetBodyController()->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::LoopAttack) {\n        GetBodyController()->GetCommandMgr().DeliverCmd(CBCLoopAttackCmd{pas::ELoopAttackType::Zero, true});\n      } else {\n        x568_ = 4;\n        x3b4_speed = 2.f * x94c_;\n      }\n    } else if (x568_ == 3 &&\n               GetBodyController()->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::LoopAttack) {\n      x568_ = 4;\n    }\n  }\n}\n\nbool CIceSheegoth::Leash(CStateManager& mgr, float arg) {\n  const zeus::CVector3f posDiff = mgr.GetPlayer().GetTranslation() - GetTranslation();\n  if ((x3a0_latestLeashPosition - posDiff).magSquared() <= x3c8_leashRadius * x3c8_leashRadius) {\n    return false;\n  }\n\n  return posDiff.magSquared() > x3cc_playerLeashRadius && x3d4_curPlayerLeashTime > x3d0_playerLeashTime;\n}\n\nbool CIceSheegoth::OffLine(CStateManager& mgr, float arg) {\n  SetPathFindMode(EPathFindMode::Normal);\n  return PathShagged(mgr, arg);\n}\n\nbool CIceSheegoth::TooClose(CStateManager& mgr, float arg) {\n  if (mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed) {\n    const zeus::CVector3f posDiff = mgr.GetPlayer().GetTranslation() - GetTranslation();\n    if (posDiff.z() < 0.f && posDiff.magSquared() < x978_) {\n      return true;\n    }\n  }\n  return false;\n}\n\nbool CIceSheegoth::InMaxRange(CStateManager& mgr, float arg) {\n  if (x56c_sheegothData.Get_x1f0_24()) {\n    return true;\n  }\n\n  return CPatterned::InMaxRange(mgr, arg);\n}\n\nbool CIceSheegoth::InDetectionRange(CStateManager& mgr, float arg) {\n  if (xb28_24_shotAt) {\n    return true;\n  }\n\n  zeus::CVector3f posDiff = mgr.GetPlayer().GetTranslation() - GetTranslation();\n  float range = std::min(1.f, arg) * x3bc_detectionRange;\n  if (posDiff.magSquared() < range * range) {\n    if (x3c0_detectionHeightRange <= 0.f) {\n      return true;\n    }\n    return (posDiff.z() * posDiff.z()) < x3c0_detectionHeightRange * x3c0_detectionHeightRange;\n  }\n  return false;\n}\n\nbool CIceSheegoth::SpotPlayer(CStateManager& mgr, float arg) {\n  return xb28_24_shotAt || CPatterned::SpotPlayer(mgr, arg);\n}\n\nbool CIceSheegoth::AnimOver(CStateManager& mgr, float arg) { return x568_ == 4; }\n\nbool CIceSheegoth::ShouldAttack(CStateManager& mgr, float arg) {\n  if (GetAreaIdAlways() == mgr.GetPlayer().GetAreaId() && x954_attackTimeLeft <= 0.f &&\n      x974_ >= 0.3333f * x56c_sheegothData.Get_x170() && sub_8019ecbc()) {\n    const zeus::CVector3f aimPos = mgr.GetPlayer().GetAimPosition(mgr, 0.f);\n    const zeus::CTransform lctrXf = GetLctrTransform(xaf4_mouthLocator);\n    if ((aimPos - lctrXf.origin).magSquared() > x2fc_minAttackRange * x2fc_minAttackRange &&\n        !ShouldTurn(mgr, zeus::degToRad(15.f))) {\n      return !IsPatternObstructed(mgr, lctrXf.origin, aimPos);\n    }\n  }\n\n  return false;\n}\n\nbool CIceSheegoth::ShouldDoubleSnap(CStateManager& mgr, float arg) {\n  if (GetAreaIdAlways() != mgr.GetPlayer().GetAreaIdAlways() || x958_ > 0.f) {\n    return false;\n  }\n  SetPathFindMode(EPathFindMode::Approach);\n  return (!PathShagged(mgr, 0.f) && x760_pathSearch.GetCurrentWaypoint() < x760_pathSearch.GetWaypoints().size() - 1) ||\n         (x92c_lastDest - GetTranslation()).magSquared() < 81.f;\n}\n\nbool CIceSheegoth::InPosition(CStateManager& mgr, float arg) {\n  return (x92c_lastDest - GetTranslation()).magSquared() < 81.f;\n}\n\nbool CIceSheegoth::ShouldTurn(CStateManager& mgr, float arg) {\n  if (arg == 0.f) {\n    arg = zeus::degToRad(45.f);\n  }\n  return zeus::CVector2f::getAngleDiff(GetTransform().frontVector().toVec2f(),\n                                       mgr.GetPlayer().GetTranslation().toVec2f() - GetTranslation().toVec2f()) > arg;\n}\n\nbool CIceSheegoth::AggressionCheck(CStateManager& mgr, float arg) {\n  return !(!IsAlive() || xb28_30_ || !sub_801a1794(mgr));\n}\n\nbool CIceSheegoth::ShouldFire(CStateManager& mgr, float arg) {\n  if (GetAreaIdAlways() != mgr.GetPlayer().GetAreaIdAlways()) {\n    return false;\n  }\n  zeus::CVector3f pos = mgr.GetPlayer().GetTranslation();\n  zeus::CTransform lctrXf = GetLctrTransform(xaf4_mouthLocator);\n  if ((pos - lctrXf.origin).magSquared() <= x300_maxAttackRange * x300_maxAttackRange &&\n      std::fabs(pos.z() - GetTranslation().z()) < (lctrXf.origin.z() - GetTranslation().z()) &&\n      !ShouldTurn(mgr, 0.2617994f)) {\n    return !IsPatternObstructed(mgr, lctrXf.origin, pos);\n  }\n  return false;\n}\n\nbool CIceSheegoth::ShouldFlinch(CStateManager& mgr, float arg) { return xb29_25_ && x97c_ > 0.f; }\n\nbool CIceSheegoth::ShotAt(CStateManager& mgr, float arg) { return x400_24_hitByPlayerProjectile; }\n\nbool CIceSheegoth::ShouldSpecialAttack(CStateManager& mgr, float arg) {\n  if (GetAreaIdAlways() == mgr.GetPlayer().GetAreaId() && x954_attackTimeLeft <= 0.f &&\n      x974_ >= 0.3333f * x56c_sheegothData.Get_x170() && sub_8019ecbc()) {\n    const zeus::CVector3f aimPos = mgr.GetPlayer().GetAimPosition(mgr, 0.f);\n    const zeus::CTransform lctrXf = GetLctrTransform(xaf4_mouthLocator);\n    if ((aimPos - lctrXf.origin).magSquared() > x2fc_minAttackRange * x2fc_minAttackRange &&\n        !ShouldTurn(mgr, zeus::degToRad(15.f))) {\n      return !IsPatternObstructed(mgr, lctrXf.origin, aimPos);\n    }\n  }\n\n  return false;\n}\n\nbool CIceSheegoth::LostInterest(CStateManager& mgr, float arg) {\n  return x968_interestTimer >= x56c_sheegothData.GetMaxInterestTime();\n}\n\nvoid CIceSheegoth::UpdateTouchBounds() {\n  x978_ = 1.75f * GetModelData()->GetScale().y();\n  zeus::CAABox box{-x978_, -x978_, 0.f, x978_, x978_, 2.f * x978_};\n  SetBoundingBox(box);\n  xa30_.SetBox(box);\n  x760_pathSearch.SetCharacterRadius(x978_);\n  x760_pathSearch.SetCharacterHeight(x978_);\n  x760_pathSearch.SetPadding(20.f);\n  x844_approachSearch.SetCharacterRadius(x978_);\n  x844_approachSearch.SetCharacterHeight(x978_);\n  x844_approachSearch.SetPadding(20.f);\n}\n\nvoid CIceSheegoth::ApplyContactDamage(TUniqueId sender, CStateManager& mgr) {\n  if (const TCastToConstPtr<CCollisionActor> colAct = mgr.GetObjectById(sender)) {\n    if (colAct->GetHealthInfo(mgr)->GetHP() <= 0.f) {\n      return;\n    }\n    bool bite = (xb28_29_ && !xb28_25_ && xb28_28_) ? true : IsMouthCollider(sender);\n\n    if (colAct->GetLastTouchedObject() == mgr.GetPlayer().GetUniqueId()) {\n      if (!bite) {\n        if (x420_curDamageRemTime <= 0.f) {\n          mgr.ApplyDamage(GetUniqueId(), mgr.GetPlayer().GetUniqueId(), GetUniqueId(), GetContactDamage(),\n                          CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), zeus::skZero3f);\n        }\n      } else {\n        mgr.ApplyDamage(GetUniqueId(), mgr.GetPlayer().GetUniqueId(), GetUniqueId(), x56c_sheegothData.Get_x1b8(),\n                        CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), zeus::skZero3f);\n      }\n    }\n  }\n}\n\nvoid CIceSheegoth::AddSphereCollisionList(const SSphereJointInfo* info, size_t count,\n                                          std::vector<CJointCollisionDescription>& vecOut) {\n  for (size_t i = 0; i < count; ++i) {\n    const auto& joint = info[i];\n    CSegId id = GetModelData()->GetAnimationData()->GetLocatorSegId(joint.name);\n    if (id.IsInvalid()) {\n      continue;\n    }\n    vecOut.push_back(CJointCollisionDescription::SphereCollision(id, joint.radius, joint.name, 1000.f));\n    xb1c_.push_back(id);\n  }\n}\n\nvoid CIceSheegoth::AddCollisionList(const SJointInfo* info, size_t count,\n                                    std::vector<CJointCollisionDescription>& vecOut) {\n  for (size_t i = 0; i < count; ++i) {\n    const auto& joint = info[i];\n    CSegId fromId = GetModelData()->GetAnimationData()->GetLocatorSegId(joint.from);\n    CSegId toId = GetModelData()->GetAnimationData()->GetLocatorSegId(joint.to);\n    if (fromId.IsInvalid() || toId.IsInvalid()) {\n      continue;\n    }\n    vecOut.push_back(CJointCollisionDescription::SphereSubdivideCollision(\n        fromId, toId, joint.radius, joint.separation, CJointCollisionDescription::EOrientationType::One, joint.from,\n        1000.f));\n  }\n}\n\nvoid CIceSheegoth::SetupCollisionActorManager(CStateManager& mgr) {\n  std::vector<CJointCollisionDescription> joints;\n  joints.reserve(7);\n  AddSphereCollisionList(skSphereJointList.data(), skSphereJointList.size(), joints);\n  AddCollisionList(skLeftLegJointList.data(), skLeftLegJointList.size(), joints);\n  AddCollisionList(skRightLegJointList.data(), skRightLegJointList.size(), joints);\n  xa2c_collisionManager = std::make_unique<CCollisionActorManager>(mgr, GetUniqueId(), GetAreaIdAlways(), joints, true);\n  xa2c_collisionManager->SetActive(mgr, GetActive());\n  xa2c_collisionManager->AddMaterial(mgr, EMaterialTypes::CameraPassthrough);\n  xb04_.clear();\n  xafc_gillColliders.clear();\n\n  for (size_t i = 0; i < xa2c_collisionManager->GetNumCollisionActors(); ++i) {\n    const auto& desc = xa2c_collisionManager->GetCollisionDescFromIndex(i);\n    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(desc.GetCollisionActorId())) {\n      colAct->SetDamageVulnerability(CDamageVulnerability::ImmuneVulnerabilty());\n      if (desc.GetName() == \"LCTR_SHEMOUTH\"sv) {\n        xaf8_mouthCollider = desc.GetCollisionActorId();\n        colAct->SetDamageVulnerability(x98c_mouthVulnerability);\n      } else if (desc.GetName() == \"Jaw_end_LCTR\"sv) {\n        colAct->SetDamageVulnerability(CDamageVulnerability::PassThroughVulnerabilty());\n      } else if (desc.GetName() == \"Ice_Shards_LCTR\"sv) {\n        xaf6_iceShardsCollider = desc.GetCollisionActorId();\n        colAct->SetWeaponCollisionResponseType(EWeaponCollisionResponseTypes::None);\n      } else if (desc.GetName() == \"GillL_LCTR\"sv || desc.GetName() == \"GillR_LCTR\"sv) {\n        xafc_gillColliders.emplace_back(desc.GetCollisionActorId());\n      } else {\n        xb04_.emplace_back(desc.GetCollisionActorId());\n        colAct->SetDamageVulnerability(x56c_sheegothData.Get_x80());\n      }\n    }\n  }\n\n  SetupHealthInfo(mgr);\n  SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(\n      {EMaterialTypes::Solid},\n      {EMaterialTypes::Player, EMaterialTypes::CollisionActor, EMaterialTypes::AIPassthrough}));\n  AddMaterial(EMaterialTypes::ProjectilePassthrough, mgr);\n  xa2c_collisionManager->AddMaterial(mgr, {EMaterialTypes::AIJoint, EMaterialTypes::CameraPassthrough});\n}\n\nvoid CIceSheegoth::SetupHealthInfo(CStateManager& mgr) {\n  CHealthInfo* thisHealth = HealthInfo(mgr);\n  x970_maxHp = thisHealth->GetHP();\n  for (auto& id : xafc_gillColliders) {\n    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(id)) {\n      *colAct->HealthInfo(mgr) = *thisHealth;\n    }\n  }\n\n  if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(xaf8_mouthCollider)) {\n    *colAct->HealthInfo(mgr) = *thisHealth;\n  }\n\n  for (auto& id : xb04_) {\n    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(id)) {\n      *colAct->HealthInfo(mgr) = *thisHealth;\n    }\n  }\n}\n\nvoid CIceSheegoth::sub_8019ebf0(CStateManager& mgr, float damage) {\n  x974_ = zeus::clamp(0.f, x974_ + damage, x56c_sheegothData.Get_x170());\n  float fVar1 = (1.f / 3.f) * x56c_sheegothData.Get_x170();\n  if (fVar1 <= 0.f) {\n    xb29_26_ = true;\n  } else {\n    xb29_26_ = mgr.GetActiveRandom()->Float() <= (x974_ / (3.f * fVar1));\n  }\n}\n\nvoid CIceSheegoth::ApplyWeaponDamage(CStateManager& mgr, TUniqueId sender) {\n  if (const TCastToConstPtr<CWeapon> wp = mgr.GetObjectById(sender)) {\n    mgr.ApplyDamage(sender, xaf8_mouthCollider, wp->GetOwnerId(), wp->GetDamageInfo(),\n                    CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), zeus::skZero3f);\n  }\n}\n\nvoid CIceSheegoth::CreateFlameThrower(CStateManager& mgr) {\n  if (xa80_flameThrowerId != kInvalidUniqueId) {\n    return;\n  }\n\n  xa80_flameThrowerId = mgr.AllocateUniqueId();\n  mgr.AddObject(new CFlameThrower(xa84_, \"IceSheegoth_Flame\"sv, EWeaponType::Plasma,\n                                  CFlameInfo{6, 4, x56c_sheegothData.GetFireBreathResId(), 15, 0.0625f, 20.f, 1.f}, {},\n                                  EMaterialTypes::CollisionActor, x56c_sheegothData.GetFireBreathDamage(),\n                                  xa80_flameThrowerId, GetAreaIdAlways(), GetUniqueId(), EProjectileAttrib::None,\n                                  x56c_sheegothData.Get_x1e4(), x56c_sheegothData.Get_x1e8(),\n                                  x56c_sheegothData.Get_x1ec()));\n}\nvoid CIceSheegoth::AttractProjectiles(CStateManager& mgr) {\n  if (!IsAlive())\n    return;\n\n  EntityList nearProjectiles;\n  zeus::CAABox attractionBounds =\n      zeus::CAABox{GetTranslation() - x56c_sheegothData.Get_x14(), GetTranslation() + x56c_sheegothData.Get_x14()};\n  mgr.BuildNearList(nearProjectiles, attractionBounds, CMaterialFilter::MakeInclude({EMaterialTypes::Projectile}),\n                    nullptr);\n\n  if (nearProjectiles.empty())\n    return;\n\n  zeus::CVector3f attractionPos = GetEnergyAttractionPos(mgr);\n  EntityList nearCharacters;\n  mgr.BuildNearList(nearCharacters, attractionBounds, CMaterialFilter::MakeInclude({EMaterialTypes::Character}),\n                    nullptr);\n\n  for (TUniqueId projectileId : nearProjectiles) {\n    if (TCastToPtr<CGameProjectile> proj = mgr.ObjectById(projectileId)) {\n      if (!ShouldAttractProjectile(*proj, mgr))\n        continue;\n\n      const zeus::CVector3f projPos = (attractionPos - proj->GetTranslation()) - proj->GetPreviousPos();\n      if (!projPos.canBeNormalized() || !IsClosestSheegoth(mgr, nearCharacters, projPos))\n        continue;\n\n      const float mag = (attractionPos - proj->GetTranslation()).magnitude();\n      const zeus::CVector3f b =\n          proj->GetTranslation() + (0.5f * mag) * (proj->GetTranslation() - proj->GetPreviousPos()).normalized();\n      const zeus::CVector3f c = attractionPos + zeus::CVector3f{0.f, 0.f, 0.4f * (0.4f * mag)};\n      const zeus::CVector3f point1 = zeus::getBezierPoint(proj->GetTranslation(), b, c, attractionPos, 0.333f);\n      const zeus::CVector3f point2 = zeus::getBezierPoint(proj->GetTranslation(), b, c, attractionPos, 0.666f);\n      const float t = (point2 - point1).magnitude() + (point1 - proj->GetTranslation()).magnitude() +\n                      (attractionPos - point2).magnitude();\n      const zeus::CVector3f point3 =\n          zeus::getBezierPoint(proj->GetTranslation(), b, c, attractionPos,\n                               t / (proj->GetTranslation() - proj->GetPreviousPos()).magnitude());\n      zeus::CVector3f lookPos = point3 - proj->GetTranslation();\n      if (!lookPos.canBeNormalized()) {\n        return;\n      }\n\n      zeus::CTransform xf = zeus::lookAt(zeus::skZero3f, lookPos);\n      xf.orthonormalize();\n      proj->ProjectileWeapon().SetWorldSpaceOrientation(xf);\n      zeus::CVector3f velocity = 0.8f * proj->GetProjectileWeapon().GetVelocity().normalized();\n      proj->ProjectileWeapon().SetVelocity(proj->ProjectileWeapon().GetVelocity() * (0.3999f + (velocity * 0.6f)));\n    }\n  }\n}\n\nvoid CIceSheegoth::UpdateTimers(float dt) {\n  if (x954_attackTimeLeft > 0.f) {\n    x954_attackTimeLeft -= (xb29_27_ ? 2.f : 1.f) * dt;\n  }\n  if (x960_ > 0.f) {\n    x960_ -= dt;\n  }\n  if (x97c_ > 0.f) {\n    x97c_ -= dt;\n  }\n  if (x958_ > 0.f) {\n    x958_ -= dt;\n  }\n  if (x968_interestTimer < x56c_sheegothData.GetMaxInterestTime()) {\n    x968_interestTimer += dt;\n  }\n}\n\nvoid CIceSheegoth::UpdateScanState(CStateManager& mgr) {\n  if (!xb29_29_scanned && GetScannableObjectInfo() != nullptr &&\n      zeus::close_enough(1.f, mgr.GetPlayerState()->GetScanTime(GetScannableObjectInfo()->GetScannableObjectId()))) {\n    xb29_29_scanned = true;\n  }\n}\n\nvoid CIceSheegoth::SetPassthroughVulnerability(CStateManager& mgr) {\n  for (size_t i = 0; i < xa2c_collisionManager->GetNumCollisionActors(); ++i) {\n    const auto& jInfo = xa2c_collisionManager->GetCollisionDescFromIndex(i);\n    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(jInfo.GetCollisionActorId())) {\n      colAct->AddMaterial(EMaterialTypes::ProjectilePassthrough);\n    }\n  }\n}\n\nvoid CIceSheegoth::PreventWorldCollisions(float dt, CStateManager& mgr) {\n  if (GetBodyController()->GetLocomotionType() == pas::ELocomotionType::Crouch || !IsOnGround() ||\n      mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed) {\n    return;\n  }\n\n  zeus::CAABox ourBox = GetModelData()->GetBounds(GetTransform());\n  zeus::CAABox plBox = mgr.GetPlayer().GetBoundingBox();\n  if (!ourBox.intersects(plBox)) {\n    return;\n  }\n\n  zeus::CVector3f maxDeviation = zeus::skZero3f;\n  float lastMag = 0.f;\n  zeus::CVector3f predictedTrans = PredictMotion(dt).x0_translation;\n  for (CSegId uid : xb1c_) {\n    auto deviation = xa2c_collisionManager->GetDeviation(mgr, uid);\n    if (!deviation) {\n      continue;\n    }\n    const zeus::CVector3f curDeviation = ((*deviation) + predictedTrans);\n    if (curDeviation.magnitude() > lastMag) {\n      maxDeviation = curDeviation;\n      lastMag = curDeviation.magnitude();\n    }\n  }\n\n  if (lastMag <= (0.6f * GetModelData()->GetScale().y())) {\n    return;\n  }\n\n  zeus::CVector3f posDiff = GetTranslation() - mgr.GetPlayer().GetTranslation();\n  const zeus::CVector3f direction = GetTransform().transposeRotate(\n      (posDiff.dot(posDiff * maxDeviation.normalized()) / posDiff.magSquared()) * posDiff);\n  ApplyImpulseOR(GetMoveToORImpulseWR(direction, dt), zeus::CAxisAngle{});\n}\n\nvoid CIceSheegoth::UpdateHealthInfo(CStateManager& mgr) {\n  if (!IsAlive()) {\n    return;\n  }\n\n  float hpDelta = 0.f;\n  if (const TCastToConstPtr<CCollisionActor> colAct = mgr.ObjectById(xaf8_mouthCollider)) {\n    hpDelta = std::max(hpDelta, x970_maxHp - colAct->GetHealthInfo(mgr)->GetHP());\n  }\n\n  for (const auto& uid : xafc_gillColliders) {\n    if (const TCastToConstPtr<CCollisionActor> colAct = mgr.ObjectById(uid)) {\n      hpDelta = std::max(hpDelta, x970_maxHp - colAct->GetHealthInfo(mgr)->GetHP());\n    }\n  }\n\n  for (const auto& uid : xb04_) {\n    if (const TCastToConstPtr<CCollisionActor> colAct = mgr.ObjectById(uid)) {\n      hpDelta = std::max(hpDelta, x970_maxHp - colAct->GetHealthInfo(mgr)->GetHP());\n    }\n  }\n  HealthInfo(mgr)->SetHP(HealthInfo(mgr)->GetHP() - hpDelta);\n  if (GetHealthInfo(mgr)->GetHP() > 0.f) {\n    if (const TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(xaf8_mouthCollider)) {\n      colAct->HealthInfo(mgr)->SetHP(x970_maxHp);\n    }\n    for (const auto& uid : xafc_gillColliders) {\n      if (const TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(uid)) {\n        colAct->HealthInfo(mgr)->SetHP(x970_maxHp);\n      }\n    }\n\n    for (const auto& uid : xb04_) {\n      if (const TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(uid)) {\n        colAct->HealthInfo(mgr)->SetHP(x970_maxHp);\n      }\n    }\n  } else {\n    Death(mgr, zeus::skZero3f, EScriptObjectState::DeathRattle);\n  }\n}\n\nvoid CIceSheegoth::SetSteeringSpeed(float dt, CStateManager& mgr) {\n  const float steeringSpeedRange = x95c_ > 0.f ? 1.f : x944_;\n\n  if (steeringSpeedRange > x948_) {\n    x948_ = std::max(steeringSpeedRange, x948_ - (2.f * dt));\n  } else if (steeringSpeedRange < x948_) {\n    x948_ = std::min(steeringSpeedRange, x948_ + (2.f * dt));\n  }\n\n  GetBodyController()->GetCommandMgr().SetSteeringSpeedRange(x948_, x948_);\n}\n\nvoid CIceSheegoth::UpdateParticleEffects(float dt, CStateManager& mgr) {\n  if (auto* fl = static_cast<CFlameThrower*>(mgr.ObjectById(xa80_flameThrowerId))) {\n    if (fl->GetActive()) {\n      fl->SetTransform(GetLctrTransform(\"LCTR_SHEMOUTH\"sv), dt);\n    }\n  }\n\n  if (const TCastToConstPtr<CCollisionActor> colAct = mgr.GetObjectById(xaf6_iceShardsCollider)) {\n    const float time = 0.333f * x56c_sheegothData.Get_x170();\n    if (x974_ >= 3.f * time) {\n      xa9c_->SetParticleEmission(false);\n      xab0_->SetParticleEmission(false);\n      xac4_->SetParticleEmission(true);\n      xad8_->SetParticleEmission(true);\n    } else if (x974_ >= 2.f * time) {\n      xa9c_->SetParticleEmission(false);\n      xab0_->SetParticleEmission(true);\n      xac4_->SetParticleEmission(false);\n      xad8_->SetParticleEmission(false);\n    } else {\n      xa9c_->SetParticleEmission(x974_ > 0.f);\n      xab0_->SetParticleEmission(false);\n      xac4_->SetParticleEmission(false);\n      xad8_->SetParticleEmission(false);\n    }\n\n    const zeus::CTransform rotation = GetTransform().getRotation();\n    const zeus::CVector3f pos = colAct->GetTranslation();\n    xa9c_->SetOrientation(rotation);\n    xa9c_->SetGlobalTranslation(pos);\n    xab0_->SetOrientation(rotation);\n    xab0_->SetGlobalTranslation(pos);\n    xac4_->SetOrientation(rotation);\n    xac4_->SetGlobalTranslation(pos);\n    xad8_->SetOrientation(rotation);\n    xad8_->SetGlobalTranslation(pos);\n    if (xaec_) {\n      xaec_->SetParticleEmission(true);\n      xaec_->SetOrientation(rotation);\n      xaec_->SetGlobalTranslation(pos);\n      xaec_->SetGlobalScale(GetModelData()->GetScale());\n      xaec_->Update(dt);\n    }\n    xa9c_->Update(dt);\n    xab0_->Update(dt);\n    xac4_->Update(dt);\n    xad8_->Update(dt);\n    if (x974_ < 2.f * time && xaf0_crackleSfx) {\n      CSfxManager::RemoveEmitter(xaf0_crackleSfx);\n    } else {\n      xaf0_crackleSfx = CSfxManager::AddEmitter(x56c_sheegothData.Get_x1d4(), GetTranslation(), zeus::skZero3f, false,\n                                                true, 127, kInvalidAreaId);\n    }\n  } else {\n    xa9c_->SetParticleEmission(false);\n    xab0_->SetParticleEmission(false);\n    xac4_->SetParticleEmission(false);\n    xad8_->SetParticleEmission(false);\n  }\n}\n\nvoid CIceSheegoth::UpdateAttackPosition(CStateManager& mgr, zeus::CVector3f& attackPos) {\n  attackPos = GetTranslation();\n  if (x954_attackTimeLeft > 0.f) {\n    return;\n  }\n\n  attackPos = mgr.GetPlayer().GetTranslation();\n  zeus::CVector3f distVec = GetTranslation() - attackPos;\n  if (distVec.canBeNormalized()) {\n    attackPos += x2fc_minAttackRange * distVec.normalized();\n  }\n}\n\nbool CIceSheegoth::sub_8019ecdc(CStateManager& mgr, float minAngle) {\n  const zeus::CVector3f plAimPos =\n      mgr.GetPlayer().GetAimPosition(mgr, GetModelData()->GetAnimationData()->GetSpeedScale() > 0.f\n                                              ? 1.25f / GetModelData()->GetAnimationData()->GetSpeedScale()\n                                              : 0.f);\n  return zeus::CVector2f::getAngleDiff(GetTransform().basis[1].toVec2f(),\n                                       plAimPos.toVec2f() - GetTranslation().toVec2f()) > minAngle;\n}\n\nvoid CIceSheegoth::SetMouthVulnerability(CStateManager& mgr, bool isVulnerable) {\n  if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(xaf8_mouthCollider)) {\n    colAct->SetDamageVulnerability(isVulnerable ? x56c_sheegothData.Get_xe8() : x98c_mouthVulnerability);\n  }\n}\n\nvoid CIceSheegoth::SetGillVulnerability(CStateManager& mgr, bool isVulnerable) {\n  for (TUniqueId uid : xafc_gillColliders) {\n    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(uid)) {\n      colAct->SetDamageVulnerability(isVulnerable ? x56c_sheegothData.Get_x18()\n                                                  : CDamageVulnerability::ImmuneVulnerabilty());\n    }\n  }\n}\n\nvoid CIceSheegoth::SetCollisionActorExtendedTouchBounds(CStateManager& mgr, const zeus::CVector3f& extents) {\n  for (size_t i = 0; i < xa2c_collisionManager->GetNumCollisionActors(); ++i) {\n    if (TCastToPtr<CCollisionActor> colAct =\n            mgr.ObjectById(xa2c_collisionManager->GetCollisionDescFromIndex(i).GetCollisionActorId())) {\n      colAct->SetExtendedTouchBounds(extents);\n    }\n  }\n}\n\nvoid CIceSheegoth::ShakePlayer(CStateManager& mgr) {\n  const zeus::CVector3f posDiff = mgr.GetPlayer().GetTranslation() - GetTranslation();\n  const float magnitude = (0.5f - (0.01f * posDiff.magnitude()));\n  if (magnitude > 0.f) {\n    if (mgr.GetPlayer().GetSurfaceRestraint() != CPlayer::ESurfaceRestraints::Air &&\n        !mgr.GetPlayer().IsInWaterMovement()) {\n      bool applyImpulseToPlayer = true;\n      if (mgr.GetPlayer().GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Morphed) {\n        if (mgr.GetCameraManager()->GetCurrentCameraId() ==\n            mgr.GetCameraManager()->GetFirstPersonCamera()->GetUniqueId()) {\n          mgr.GetCameraManager()->AddCameraShaker(\n              CCameraShakeData::BuildPatternedExplodeShakeData(GetTranslation(), 0.5f, magnitude, 50.f), true);\n        }\n        applyImpulseToPlayer = xb28_27_;\n      }\n\n      if (applyImpulseToPlayer) {\n        const zeus::CVector3f direction = magnitude * ((xb28_27_ ? 40.f : 25.f) * zeus::skUp);\n        zeus::CVector3f impulse = zeus::skZero3f;\n        if (x978_ < posDiff.magnitude() && xb28_27_ && posDiff.toVec2f().canBeNormalized()) {\n          impulse = magnitude * (12.5f * posDiff.toVec2f().normalized());\n        }\n        mgr.GetPlayer().ApplyImpulseWR(mgr.GetPlayer().GetMass() * (direction + impulse), zeus::CAxisAngle{});\n        mgr.GetPlayer().SetMoveState(CPlayer::EPlayerMovementState::ApplyJump, mgr);\n      }\n    }\n  }\n\n  if (xb28_28_) {\n    sub_8019ebf0(mgr, 0.25f * x56c_sheegothData.Get_x170());\n  }\n}\n\nzeus::CVector3f CIceSheegoth::GetEnergyAttractionPos(CStateManager& mgr) const {\n  if (const TCastToConstPtr<CCollisionActor> colAct = mgr.GetObjectById(xaf6_iceShardsCollider)) {\n    return colAct->GetTranslation();\n  }\n\n  return GetTranslation();\n}\n\nbool CIceSheegoth::ShouldAttractProjectile(const CGameProjectile& proj, CStateManager& mgr) const {\n  if (proj.GetType() != EWeaponType::Missile && proj.GetType() != EWeaponType::Plasma &&\n      (!proj.GetDamageInfo().GetWeaponMode().IsComboed() || proj.GetType() != EWeaponType::Power)) {\n    const CActor* ent = static_cast<const CActor*>(mgr.GetObjectById(proj.GetOwnerId()));\n    if (ent != nullptr) {\n      if (CPatterned::CastTo<CIceSheegoth>(ent) == nullptr && ent->GetAreaIdAlways() == GetAreaIdAlways()) {\n        zeus::CVector3f r = GetTransform().rotate(x56c_sheegothData.Get_x8());\n        zeus::CVector3f posDiff = proj.GetTranslation() - ent->GetTranslation();\n        if (proj.GetType() == EWeaponType::Wave && !proj.GetDamageInfo().GetWeaponMode().IsCharged() &&\n            !proj.GetDamageInfo().GetWeaponMode().IsComboed() && posDiff.magSquared() < 100.f) {\n          return false;\n        }\n\n        zeus::CVector3f xyPos = posDiff.toVec2f() - GetTranslation().toVec2f() + r.toVec2f();\n        if (zeus::CVector3f::getAngleDiff(GetTransform().frontVector(), xyPos) <= x56c_sheegothData.Get_x4()) {\n          if (zeus::CVector3f::getAngleDiff(GetTransform().frontVector(), -xyPos) >= x56c_sheegothData.Get_x0()) {\n            return false;\n          }\n        }\n        return true;\n      }\n    }\n  }\n\n  return false;\n}\n\nbool CIceSheegoth::IsClosestSheegoth(CStateManager& mgr, const EntityList& nearList,\n                                     const zeus::CVector3f& projectileOffset) const {\n  zeus::CVector3f diff = projectileOffset - GetTranslation();\n  const float diffMag = diff.magSquared();\n  for (const auto& uid : nearList) {\n    const CIceSheegoth* goth = CPatterned::CastTo<CIceSheegoth>(mgr.GetObjectById(uid));\n    if (!goth || goth->GetUniqueId() == GetUniqueId())\n      continue;\n\n    diff = projectileOffset - goth->GetTranslation();\n    if (diff.magSquared() < diffMag) {\n      return false;\n    }\n  }\n  return true;\n}\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CIceSheegoth.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Collision/CJointCollisionDescription.hpp\"\n#include \"Runtime/Character/CBoneTracking.hpp\"\n#include \"Runtime/Weapon/CProjectileInfo.hpp\"\n#include \"Runtime/World/CPathFindSearch.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n\nnamespace metaforce {\nclass CCollisionActorManager;\nclass CElementGen;\nclass CParticleElectric;\nnamespace MP1 {\nclass CIceSheegothData {\n  float x0_;\n  float x4_;\n  zeus::CVector3f x8_;\n  float x14_;\n  CDamageVulnerability x18_;\n  CDamageVulnerability x80_;\n  CDamageVulnerability xe8_;\n  CAssetId x150_;\n  CDamageInfo x154_;\n  float x170_;\n  float x174_;\n  CAssetId x178_;\n  CAssetId x17c_fireBreathResId;\n  CDamageInfo x180_fireBreathDamage;\n  CAssetId x19c_;\n  CAssetId x1a0_;\n  CAssetId x1a4_;\n  CAssetId x1a8_;\n  CAssetId x1ac_;\n  float x1b0_;\n  float x1b4_;\n  CDamageInfo x1b8_;\n  s16 x1d4_;\n  float x1d8_;\n  float x1dc_;\n  float x1e0_maxInterestTime;\n  CAssetId x1e4_;\n  s16 x1e8_;\n  CAssetId x1ec_;\n  bool x1f0_24_ : 1;\n  bool x1f0_25_ : 1;\n\npublic:\n  CIceSheegothData(CInputStream& in, s32 propertyCount);\n  [[nodiscard]] float Get_x0() const { return x0_; }\n  [[nodiscard]] float Get_x4() const { return x4_; }\n  [[nodiscard]] zeus::CVector3f Get_x8() const { return x8_; }\n  [[nodiscard]] float Get_x14() const { return x14_; }\n  [[nodiscard]] CDamageVulnerability Get_x18() const { return x18_; }\n  [[nodiscard]] CDamageVulnerability Get_x80() const { return x80_; }\n  [[nodiscard]] CDamageVulnerability Get_xe8() const { return xe8_; }\n  [[nodiscard]] CAssetId Get_x150() const { return x150_; }\n  [[nodiscard]] CDamageInfo Get_x154() const { return x154_; }\n  [[nodiscard]] float Get_x170() const { return x170_; }\n  [[nodiscard]] float Get_x174() const { return x174_; }\n  [[nodiscard]] CAssetId Get_x178() const { return x178_; }\n  [[nodiscard]] CAssetId GetFireBreathResId() const { return x17c_fireBreathResId; }\n  [[nodiscard]] CDamageInfo GetFireBreathDamage() const { return x180_fireBreathDamage; }\n  [[nodiscard]] CAssetId Get_x19c() const { return x19c_; }\n  [[nodiscard]] CAssetId Get_x1a0() const { return x1a0_; }\n  [[nodiscard]] CAssetId Get_x1a4() const { return x1a4_; }\n  [[nodiscard]] CAssetId Get_x1a8() const { return x1a8_; }\n  [[nodiscard]] CAssetId Get_x1ac() const { return x1ac_; }\n  [[nodiscard]] float Get_x1b0() const { return x1b0_; }\n  [[nodiscard]] CDamageInfo Get_x1b8() const { return x1b8_; }\n  [[nodiscard]] s16 Get_x1d4() const { return x1d4_; }\n  [[nodiscard]] float Get_x1d8() const { return x1d8_; }\n  [[nodiscard]] float Get_x1dc() const { return x1dc_; }\n  [[nodiscard]] float GetMaxInterestTime() const { return x1e0_maxInterestTime; }\n  [[nodiscard]] CAssetId Get_x1e4() const { return x1e4_; }\n  [[nodiscard]] s16 Get_x1e8() const { return x1e8_; }\n  [[nodiscard]] CAssetId Get_x1ec() const { return x1ec_; }\n  [[nodiscard]] bool Get_x1f0_24() const { return x1f0_24_; }\n  [[nodiscard]] bool Get_x1f0_25() const { return x1f0_25_; }\n};\n\nclass CIceSheegoth : public CPatterned {\n  enum class EPathFindMode { Normal, Approach };\n  s32 x568_ = -1;\n  CIceSheegothData x56c_sheegothData;\n  CPathFindSearch x760_pathSearch;\n  CPathFindSearch x844_approachSearch;\n  EPathFindMode x928_pathFindMode = EPathFindMode::Normal;\n  zeus::CVector3f x92c_lastDest = zeus::skZero3f;\n  zeus::CVector3f x938_ = zeus::skZero3f;\n  float x944_ = 1.f;\n  float x948_ = 1.f;\n  float x94c_;\n  float x950_ = 0.f;\n  float x954_attackTimeLeft = 0.f;\n  float x958_ = 0.f;\n  float x95c_ = 0.f;\n  float x960_ = 0.f;\n  /* x964_ */\n  float x968_interestTimer = 0.f;\n  float x96c_ = 2.f;\n  float x970_maxHp = 0.f;\n  float x974_;\n  float x978_ = 0.f;\n  float x97c_ = 0.f;\n  zeus::CVector3f x980_ = zeus::skZero3f;\n  CDamageVulnerability x98c_mouthVulnerability;\n  CBoneTracking x9f4_boneTracking;\n  std::unique_ptr<CCollisionActorManager> xa2c_collisionManager;\n  CCollidableAABox xa30_;\n  CProjectileInfo xa58_;\n  TUniqueId xa80_flameThrowerId = kInvalidUniqueId;\n  TToken<CWeaponDescription> xa84_;\n  TCachedToken<CGenDescription> xa8c_;\n  // bool xa98_;\n  std::unique_ptr<CElementGen> xa9c_;\n  TCachedToken<CGenDescription> xaa0_;\n  // bool xaac_;\n  std::unique_ptr<CElementGen> xab0_;\n  TCachedToken<CGenDescription> xab4_;\n  // bool xac0_;\n  std::unique_ptr<CElementGen> xac4_;\n  TCachedToken<CElectricDescription> xac8_;\n  // bool xad4_;\n  std::unique_ptr<CParticleElectric> xad8_;\n  TCachedToken<CGenDescription> xadc_;\n  // bool xae8_;\n  std::unique_ptr<CElementGen> xaec_;\n  CSfxHandle xaf0_crackleSfx;\n  CSegId xaf4_mouthLocator;\n  TUniqueId xaf6_iceShardsCollider = kInvalidUniqueId;\n  TUniqueId xaf8_mouthCollider = kInvalidUniqueId;\n  rstl::reserved_vector<TUniqueId, 2> xafc_gillColliders;\n  rstl::reserved_vector<TUniqueId, 10> xb04_;\n  rstl::reserved_vector<CSegId, 7> xb1c_;\n  bool xb28_24_shotAt : 1 = false;\n  bool xb28_25_ : 1 = false;\n  bool xb28_26_ : 1 = false;\n  bool xb28_27_ : 1 = false;\n  bool xb28_28_ : 1 = false;\n  bool xb28_29_ : 1 = false;\n  bool xb28_30_ : 1 = false;\n  bool xb28_31_ : 1 = false;\n  bool xb29_24_ : 1 = false;\n  bool xb29_25_ : 1 = false;\n  bool xb29_26_ : 1 = false;\n  bool xb29_27_ : 1 = false;\n  bool xb29_28_ : 1 = false;\n  bool xb29_29_scanned : 1 = false;\n\n  void UpdateTouchBounds();\n  [[nodiscard]] bool IsMouthCollider(TUniqueId uid) const { return xaf8_mouthCollider == uid; }\n  [[nodiscard]] bool IsGillCollider(const CEntity* ent) const {\n    return std::find_if(xafc_gillColliders.cbegin(), xafc_gillColliders.cend(),\n                        [&ent](TUniqueId uid) { return uid == ent->GetUniqueId(); }) != xafc_gillColliders.cend();\n  }\n  void sub_8019ebf0(CStateManager& mgr, float damage);\n  void ApplyWeaponDamage(CStateManager& mgr, TUniqueId sender);\n  void CreateFlameThrower(CStateManager& mgr);\n  void ApplyContactDamage(TUniqueId sender, CStateManager& mgr);\n  void AddSphereCollisionList(const SSphereJointInfo* info, size_t count,\n                              std::vector<CJointCollisionDescription>& vecOut);\n  void AddCollisionList(const SJointInfo* info, size_t count, std::vector<CJointCollisionDescription>& vecOut);\n  void SetupCollisionActorManager(CStateManager& mgr);\n  void SetupHealthInfo(CStateManager& mgr);\n  void AttractProjectiles(CStateManager& mgr);\n  void UpdateTimers(float dt);\n  void UpdateScanState(CStateManager& mgr);\n  void SetPassthroughVulnerability(CStateManager& mgr);\n  void PreventWorldCollisions(float dt, CStateManager& mgr);\n  void UpdateHealthInfo(CStateManager& mgr);\n  void SetSteeringSpeed(float dt, CStateManager& mgr);\n  void SetPathFindMode(EPathFindMode mode) { x928_pathFindMode = mode; }\n  void UpdateParticleEffects(float dt, CStateManager& mgr);\n  bool sub_801a1794(CStateManager& mgr) const {\n    const CHealthInfo* hInfo = GetHealthInfo(mgr);\n    return hInfo != nullptr && hInfo->GetHP() < 0.3f * x970_maxHp;\n  }\n\n  bool sub_8019ecbc() const { return xb28_27_ || xb29_26_; }\n  bool sub_8019ecdc(CStateManager& mgr, float minAngle);\n  void SetMouthVulnerability(CStateManager& mgr, bool isVulnerable);\n  void SetGillVulnerability(CStateManager& mgr, bool isVulnerable);\n  void ShakePlayer(CStateManager& mgr);\n  void SetCollisionActorExtendedTouchBounds(CStateManager& mgr, const zeus::CVector3f& extents);\n\n  void UpdateAttackPosition(CStateManager& mgr, zeus::CVector3f& attackPos);\n  zeus::CVector3f GetEnergyAttractionPos(CStateManager& mgr) const;\n  bool ShouldAttractProjectile(const CGameProjectile& proj, CStateManager& mgr) const;\n  bool IsClosestSheegoth(CStateManager& mgr, const EntityList& nearList, const zeus::CVector3f& projectileOffset) const;\n\npublic:\n  DEFINE_PATTERNED(IceSheeegoth);\n  CIceSheegoth(TUniqueId uid, std::string_view name, const CEntityInfo& info, zeus::CTransform& xf, CModelData&& mData,\n               const CPatternedInfo& pInfo, const CActorParameters& actorParameters,\n               const CIceSheegothData& sheegothData);\n\n  void Accept(IVisitor& visitor) override;\n  void Think(float dt, CStateManager& mgr) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) override;\n  void AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) override;\n  [[nodiscard]] const CDamageVulnerability* GetDamageVulnerability() const override {\n    return &CDamageVulnerability::PassThroughVulnerabilty();\n  }\n  [[nodiscard]] const CDamageVulnerability* GetDamageVulnerability(const zeus::CVector3f&, const zeus::CVector3f&,\n                                                                   const CDamageInfo&) const override {\n    return &CDamageVulnerability::PassThroughVulnerabilty();\n  }\n\n  [[nodiscard]] zeus::CVector3f GetAimPosition(const CStateManager& mgr, float dt) const override;\n  [[nodiscard]] EWeaponCollisionResponseTypes GetCollisionResponseType(const zeus::CVector3f&, const zeus::CVector3f&,\n                                                                       const CWeaponMode&,\n                                                                       EProjectileAttrib) const override;\n  [[nodiscard]] zeus::CAABox GetSortingBounds(const CStateManager& mgr) const override;\n  void DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) override;\n  [[nodiscard]] const CCollisionPrimitive* GetCollisionPrimitive() const override { return &xa30_; }\n  void PathFind(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void TargetPatrol(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Generate(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Deactivate(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Attack(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void DoubleSnap(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void TurnAround(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Crouch(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Taunt(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void ProjectileAttack(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Flinch(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Approach(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Enraged(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void SpecialAttack(CStateManager& mgr, EStateMsg msg, float dt) override;\n  bool Leash(CStateManager& mgr, float arg) override;\n  bool OffLine(CStateManager& mgr, float arg) override;\n  bool TooClose(CStateManager& mgr, float arg) override;\n  bool InMaxRange(CStateManager& mgr, float arg) override;\n  bool InDetectionRange(CStateManager& mgr, float arg) override;\n  bool SpotPlayer(CStateManager& mgr, float arg) override;\n  bool AnimOver(CStateManager& mgr, float arg) override;\n  bool ShouldAttack(CStateManager& mgr, float arg) override;\n  bool ShouldDoubleSnap(CStateManager& mgr, float arg) override;\n  bool InPosition(CStateManager& mgr, float arg) override;\n  bool ShouldTurn(CStateManager& mgr, float arg) override;\n  bool AggressionCheck(CStateManager& mgr, float arg) override;\n  bool ShouldFire(CStateManager& mgr, float arg) override;\n  bool ShouldFlinch(CStateManager& mgr, float arg) override;\n  bool ShotAt(CStateManager& mgr, float arg) override;\n  bool ShouldSpecialAttack(CStateManager& mgr, float arg) override;\n  bool LostInterest(CStateManager& mgr, float arg) override;\n  CPathFindSearch* GetSearchPath() override {\n    return x928_pathFindMode == EPathFindMode::Normal ? &x760_pathSearch : &x844_approachSearch;\n  }\n  [[nodiscard]] float GetGravityConstant() const override { return 10.f * 24.525f; }\n  CProjectileInfo* GetProjectileInfo() override { return &xa58_; }\n};\n} // namespace MP1\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/MP1/World/CJellyZap.cpp",
    "content": "#include \"Runtime/MP1/World/CJellyZap.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CFishCloud.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce::MP1 {\nconstexpr CMaterialFilter kPlayerFilter = CMaterialFilter::MakeInclude({EMaterialTypes::Player});\n\nCJellyZap::CJellyZap(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                     CModelData&& mData, const CDamageInfo& attackDamage, bool b1, float attackRadius, float f2,\n                     float f3, float f4, float attackDelay, float f6, float f7, float f8, float priority,\n                     float repulseRadius, float attractRadius, float f12, const CPatternedInfo& pInfo,\n                     const CActorParameters& actParms)\n: CPatterned(ECharacter::JellyZap, uid, name, EFlavorType::Zero, info, xf, std::move(mData), pInfo,\n             EMovementType::Flyer, EColliderType::One, EBodyType::BiPedal, actParms, EKnockBackVariant::Medium)\n, x56c_attackDamage(attackDamage)\n, x588_attackRadius(attackRadius)\n, x58c_(f2)\n, x590_(f4)\n, x594_(f3)\n, x598_(f8)\n, x59c_priority(priority)\n, x5a0_repulseRadius(repulseRadius)\n, x5a4_attractRadius(attractRadius)\n, x5a8_attackDelay(attackDelay)\n, x5ac_(f6)\n, x5b0_(f7)\n, x5b4_(f12)\n, x5b8_26_(b1) {\n  UpdateThermalFrozenState(true);\n  x50c_baseDamageMag = 0.f;\n}\n\nvoid CJellyZap::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CJellyZap::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  CPatterned::AcceptScriptMsg(msg, uid, mgr);\n  if (msg == EScriptObjectMessage::Registered) {\n    x450_bodyController->Activate(mgr);\n  } else if (msg == EScriptObjectMessage::Activate) {\n    AddAttractor(mgr);\n  } else if (msg == EScriptObjectMessage::Deleted || msg == EScriptObjectMessage::Deactivate) {\n    RemoveAllAttractors(mgr);\n  }\n}\n\nvoid CJellyZap::Think(float dt, CStateManager& mgr) {\n  CPatterned::Think(dt, mgr);\n  if (!GetActive()) {\n    return;\n  }\n  if (x5b8_24_) {\n    x450_bodyController->FaceDirection(mgr.GetPlayer().GetTranslation() - GetTranslation(), dt);\n  }\n\n  float damage = x50c_baseDamageMag;\n\n  if (x5b8_25_ && GetBodyController()->GetPercentageFrozen() == 0.f) {\n    damage += dt / 0.3f;\n  } else {\n    damage -= dt / 0.75f;\n  }\n  x50c_baseDamageMag = zeus::clamp(0.f, damage, 1.f);\n}\n\nvoid CJellyZap::DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) {\n  if (type == EUserEventType::DamageOn) {\n    mgr.ApplyDamageToWorld(GetUniqueId(), *this, GetTranslation(), x56c_attackDamage, kPlayerFilter);\n    return;\n  }\n  CPatterned::DoUserAnimEvent(mgr, node, type, dt);\n}\n\nvoid CJellyZap::KnockBack(const zeus::CVector3f& pos, CStateManager& mgr, const CDamageInfo& info, EKnockBackType type,\n                          bool inDeferred, float magnitude) {\n  if (info.GetWeaponMode().GetType() == EWeaponType::Ice) {\n    Freeze(mgr, {}, GetTransform().transposeRotate(pos), x4fc_freezeDur);\n  }\n}\n\nvoid CJellyZap::Attack(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n    AddRepulsor(mgr);\n    x5b8_25_ = true;\n    float dist = (mgr.GetPlayer().GetTranslation() - GetTranslation()).magnitude();\n    if (dist < x56c_attackDamage.GetRadius()) {\n      float staticTimer = 3.f * (1.f - dist / x56c_attackDamage.GetRadius()) + 2.f;\n      if (staticTimer > mgr.GetPlayer().GetStaticTimer()) {\n        mgr.GetPlayer().SetHudDisable(staticTimer, 0.5f, 2.5f);\n        mgr.GetPlayer().TryToBreakOrbit(mgr.GetPlayer().GetOrbitTargetId(),\n                                        CPlayer::EPlayerOrbitRequest::ActivateOrbitSource, mgr);\n      }\n\n      mgr.GetPlayerState()->GetStaticInterference().AddSource(GetUniqueId(), 0.5f, 0.5f);\n    }\n    x330_stateMachineState.SetDelay(x5ac_);\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::MeleeAttack, &CPatterned::TryMeleeAttack, 1);\n  } else if (msg == EStateMsg::Deactivate) {\n    RemoveAllAttractors(mgr);\n    x32c_animState = EAnimState::NotReady;\n    x5b8_25_ = false;\n  }\n}\n\nvoid CJellyZap::Suck(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n    RemoveAllAttractors(mgr);\n    x568_ = 1;\n    x400_24_hitByPlayerProjectile = false;\n    x5b8_24_ = true;\n    x5b8_25_ = true;\n  } else if (msg == EStateMsg::Update) {\n    auto curSuit = mgr.GetPlayerState()->GetCurrentSuit();\n    TryCommand(mgr, pas::EAnimationState::LoopReaction, &CPatterned::TryLoopReaction, 0);\n    x450_bodyController->GetCommandMgr().DeliverTargetVector(\n        (mgr.GetPlayer().GetTranslation() + zeus::CVector3f(0.f, 0.f, 1.f)) - GetTranslation());\n\n    float intensity = mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::GravitySuit) ? 0.1f : 1.f;\n    zeus::CVector3f posDiff = (mgr.GetPlayer().GetTranslation() - GetTranslation());\n    float mag = 1.f / posDiff.magnitude();\n    float massScale = mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed ? x594_\n                      : curSuit == CPlayerState::EPlayerSuit::Gravity                                          ? x590_\n                                                                                                               : x58c_;\n    mgr.GetPlayer().ApplyImpulseWR(\n        arg * ((5.f * massScale * mgr.GetPlayer().GetMass()) * (intensity * (mag * -posDiff))), {});\n    mgr.GetPlayer().UseCollisionImpulses();\n    mgr.GetPlayer().SetAccelerationChangeTimer(2.f * arg);\n    mgr.GetPlayerState()->GetStaticInterference().AddSource(GetUniqueId(), 0.1f, 0.1f);\n  } else if (msg == EStateMsg::Deactivate) {\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBodyStateCmd(EBodyStateCmd::ExitState));\n    mgr.GetPlayerState()->GetStaticInterference().RemoveSource(GetUniqueId());\n    x32c_animState = EAnimState::NotReady;\n    x5b8_24_ = false;\n    x5b8_25_ = false;\n  }\n}\n\nvoid CJellyZap::Active(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x5b8_24_ = true;\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Lurk);\n    x568_ = 0;\n    x330_stateMachineState.SetDelay(x3d0_playerLeashTime);\n  } else if (msg == EStateMsg::Update) {\n    zeus::CVector3f targetVector =\n        (mgr.GetPlayer().GetTranslation() + zeus::CVector3f(0.f, 0.f, 1.f)) - GetTranslation();\n    x450_bodyController->GetCommandMgr().DeliverTargetVector(targetVector);\n    if (x5b8_26_) {\n      const zeus::CVector3f tmpMove = arg * (x598_ * zeus::CVector3f(0.f, 1.f, 0.f));\n      const zeus::CVector3f moveToImpulse = GetMoveToORImpulseWR(GetTransform().transposeRotate(tmpMove), arg);\n      ApplyImpulseOR(moveToImpulse, zeus::CAxisAngle());\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x5b8_24_ = false;\n  }\n}\n\nvoid CJellyZap::InActive(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg != EStateMsg::Activate) {\n    return;\n  }\n  x400_24_hitByPlayerProjectile = false;\n  x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n  AddAttractor(mgr);\n  x568_ = 0;\n}\n\nvoid CJellyZap::Flinch(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x400_24_hitByPlayerProjectile = false;\n    x32c_animState = EAnimState::Ready;\n    x330_stateMachineState.SetDelay(x5b0_);\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::KnockBack, &CPatterned::TryKnockBack, 0);\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n  }\n}\n\nbool CJellyZap::InAttackPosition(CStateManager& mgr, float arg) {\n  if (mgr.GetPlayer().GetFluidCounter() == 0) {\n    return false;\n  }\n\n  return (mgr.GetPlayer().GetTranslation() - GetTranslation()).magSquared() < x588_attackRadius * x588_attackRadius;\n}\n\nbool CJellyZap::InDetectionRange(CStateManager& mgr, float arg) {\n  return (mgr.GetPlayer().GetFluidCounter() != 0 ? CPatterned::InDetectionRange(mgr, arg) : false);\n}\n\nvoid CJellyZap::AddSelfToFishCloud(CStateManager& mgr, float radius, float priority, bool repulsor) {\n  for (const SConnection& conn : x20_conns) {\n    if (conn.x0_state != EScriptObjectState::Modify || conn.x4_msg != EScriptObjectMessage::Follow) {\n      continue;\n    }\n    if (TCastToPtr<CFishCloud> cloud = mgr.ObjectById(mgr.GetIdForScript(conn.x8_objId))) {\n      if (repulsor) {\n        cloud->AddRepulsor(GetUniqueId(), false, radius, priority);\n      } else {\n        cloud->AddAttractor(GetUniqueId(), false, radius, priority);\n      }\n    }\n  }\n}\n\nvoid CJellyZap::AddRepulsor(CStateManager& mgr) { AddSelfToFishCloud(mgr, x5a0_repulseRadius, x59c_priority, true); }\n\nvoid CJellyZap::AddAttractor(CStateManager& mgr) {\n  AddSelfToFishCloud(mgr, x5a0_repulseRadius, x59c_priority, true);\n  AddSelfToFishCloud(mgr, x5a4_attractRadius, x59c_priority, false);\n}\n\nvoid CJellyZap::RemoveSelfFromFishCloud(CStateManager& mgr) {\n  for (const SConnection& conn : x20_conns) {\n    if (conn.x0_state != EScriptObjectState::ScanStart || conn.x4_msg != EScriptObjectMessage::Follow) {\n      continue;\n    }\n\n    if (TCastToPtr<CFishCloud> cloud = mgr.ObjectById(mgr.GetIdForScript(conn.x8_objId))) {\n      cloud->RemoveAttractor(GetUniqueId());\n      cloud->RemoveRepulsor(GetUniqueId());\n    }\n  }\n}\n\nvoid CJellyZap::RemoveAllAttractors(CStateManager& mgr) { RemoveSelfFromFishCloud(mgr); }\n\nbool CJellyZap::ClosestToPlayer(const CStateManager& mgr) const {\n  const zeus::CVector3f playerPos = mgr.GetPlayer().GetTranslation();\n  const float ourDistance = (playerPos - GetTranslation()).magnitude();\n  float closestDistance = ourDistance;\n  for (CEntity* ent : mgr.GetPhysicsActorObjectList()) {\n    if (auto* zap = CPatterned::CastTo<CJellyZap>(ent)) {\n      if (zap->GetAreaIdAlways() != GetAreaIdAlways() || zap == this) {\n        continue;\n      }\n\n      const float tmpDist = (playerPos - zap->GetTranslation()).magnitude();\n      if (tmpDist < closestDistance) {\n        closestDistance = tmpDist;\n      }\n\n      if (zap->x5b8_25_) {\n        return false;\n      }\n    }\n  }\n  return zeus::close_enough(closestDistance, ourDistance);\n}\nconst CDamageVulnerability* CJellyZap::GetDamageVulnerability(const zeus::CVector3f& pos, const zeus::CVector3f& dir,\n                                                              const CDamageInfo& info) const {\n  if (!HitShell()) {\n    return GetDamageVulnerability();\n  }\n\n  return &CDamageVulnerability::ReflectVulnerabilty();\n}\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CJellyZap.hpp",
    "content": "#pragma once\n\n#include \"Runtime/World/CPatterned.hpp\"\n\nnamespace metaforce::MP1 {\nclass CJellyZap : public CPatterned {\n  u32 x568_ = 0;\n  CDamageInfo x56c_attackDamage;\n  float x588_attackRadius;\n  float x58c_;\n  float x590_;\n  float x594_;\n  float x598_;\n  float x59c_priority;\n  float x5a0_repulseRadius;\n  float x5a4_attractRadius;\n  float x5a8_attackDelay;\n  float x5ac_;\n  float x5b0_;\n  float x5b4_;\n  bool x5b8_24_ : 1 = false;\n  bool x5b8_25_ : 1 = false;\n  bool x5b8_26_ : 1;\n\n  void AddSelfToFishCloud(CStateManager&, float, float, bool);\n  void AddRepulsor(CStateManager&);\n  void AddAttractor(CStateManager&);\n  void RemoveSelfFromFishCloud(CStateManager&);\n  void RemoveAllAttractors(CStateManager&);\n  bool ClosestToPlayer(const CStateManager&) const;\n  bool HitShell() const { return x568_ != 1; }\n\npublic:\n  DEFINE_PATTERNED(JellyZap);\n\n  CJellyZap(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n            CModelData&& mData, const CDamageInfo& attackDamage, bool b1, float attackRadius, float f2, float f3,\n            float f4, float attackDelay, float f6, float f7, float f8, float priority, float repulseRadius,\n            float attractRadius, float f12, const CPatternedInfo& pInfo, const CActorParameters& actParms);\n\n  void Accept(IVisitor&) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void Think(float, CStateManager&) override;\n  void DoUserAnimEvent(CStateManager&, const CInt32POINode&, EUserEventType, float dt) override;\n  void KnockBack(const zeus::CVector3f&, CStateManager&, const CDamageInfo& info, EKnockBackType type, bool inDeferred,\n                 float magnitude) override;\n  const CDamageVulnerability* GetDamageVulnerability() const override { return CAi::GetDamageVulnerability(); }\n  const CDamageVulnerability* GetDamageVulnerability(const zeus::CVector3f& pos, const zeus::CVector3f& dir,\n                                                     const CDamageInfo& info) const override;\n  EWeaponCollisionResponseTypes GetCollisionResponseType(const zeus::CVector3f&, const zeus::CVector3f&,\n                                                         const CWeaponMode&, EProjectileAttrib) const override {\n    return HitShell() ? EWeaponCollisionResponseTypes::Unknown89 : EWeaponCollisionResponseTypes::Unknown39;\n  }\n  void Attack(CStateManager&, EStateMsg, float) override;\n  void Suck(CStateManager&, EStateMsg, float) override;\n  void Active(CStateManager&, EStateMsg, float) override;\n  void InActive(CStateManager&, EStateMsg, float) override;\n  void Flinch(CStateManager&, EStateMsg, float) override;\n  bool ShouldAttack(CStateManager&, float) override { return x330_stateMachineState.GetTime() > x5a8_attackDelay; }\n  bool ShouldSpecialAttack(CStateManager& mgr, float) override { return ClosestToPlayer(mgr); }\n  bool InAttackPosition(CStateManager&, float) override;\n  bool InDetectionRange(CStateManager&, float) override;\n};\n} // namespace metaforce::MP1"
  },
  {
    "path": "Runtime/MP1/World/CMagdolite.cpp",
    "content": "#include \"Runtime/MP1/World/CMagdolite.hpp\"\n\n#include \"Runtime/Collision/CCollisionActor.hpp\"\n#include \"Runtime/Collision/CCollisionActorManager.hpp\"\n#include \"Runtime/Collision/CJointCollisionDescription.hpp\"\n#include \"Runtime/Graphics/CSkinnedModel.hpp\"\n#include \"Runtime/Weapon/CFlameThrower.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CScriptWater.hpp\"\n#include \"Runtime/World/CScriptWaypoint.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\n#include <array>\n\nnamespace metaforce::MP1 {\n\nnamespace {\nconstexpr std::array<SSphereJointInfo, 5> skSpineJoints{{\n    {\"spine1\", 0.75f},\n    {\"spine3\", 0.75f},\n    {\"spine5\", 0.75f},\n    {\"spine7\", 0.75f},\n    {\"spine9\", 0.75f},\n}};\n\nconstexpr std::array<SOBBJointInfo, 2> skHeadJoints{{\n    {\"head\", \"Top_LCTR\", {1.f, 0.15f, 0.5f}},\n    {\"head\", \"Bottom_LCTR\", {0.75f, 0.15f, 0.25f}},\n}};\n} // namespace\n\nCMagdolite::CMagdolite(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                       CModelData&& mData, const CPatternedInfo& pInfo, const CActorParameters& actParms, float f1,\n                       float f2, const CDamageInfo& dInfo1, const CDamageInfo& dInfo2,\n                       const CDamageVulnerability& dVuln1, const CDamageVulnerability& dVuln2, CAssetId modelId,\n                       CAssetId skinId, float f3, float f4, float f5, float f6, const CFlameInfo& magData, float f7,\n                       float f8, float f9)\n: CPatterned(ECharacter::Magdolite, uid, name, EFlavorType::Zero, info, xf, std::move(mData), pInfo,\n             EMovementType::Flyer, EColliderType::One, EBodyType::BiPedal, actParms, EKnockBackVariant::Large)\n, x568_initialDelay(f4)\n, x56c_minDelay(f5)\n, x570_maxDelay(f6)\n, x574_minHp(f3)\n, x578_losMaxDistance(std::cos(zeus::degToRad(f2)))\n, x57c_(f1)\n, x584_boneTracker(*GetModelData()->GetAnimationData(), \"head\"sv, zeus::degToRad(f1), zeus::degToRad(90.f),\n                   EBoneTrackingFlags::ParentIk)\n, x5bc_instaKillVulnerability(dVuln1)\n, x624_normalVulnerability(dVuln2)\n, x690_headlessModel(\n      CToken(TObjOwnerDerivedFromIObj<CSkinnedModel>::GetNewDerivedObject(std::make_unique<CSkinnedModel>(\n          g_SimplePool->GetObj({SBIG('CMDL'), modelId}), g_SimplePool->GetObj({SBIG('CSKR'), skinId}),\n          x64_modelData->GetAnimationData()->GetModelData()->GetLayoutInfo()))))\n, x6a8_flameInfo(magData)\n, x6cc_flameThrowerDesc(g_SimplePool->GetObj(\"FlameThrower\"sv))\n, x6d4_flameThrowerDamage(dInfo1)\n, x6f0_headContactDamage(dInfo2)\n, x71c_attackTarget(xf.origin)\n, x744_(f7)\n, x748_(f8)\n, x74c_(f9) {\n  x460_knockBackController.SetAutoResetImpulse(false);\n  x460_knockBackController.SetEnableBurn(false);\n  // redundant\n  // x690_headlessModel->SetLayoutInfo(GetModelData()->GetAnimationData()->GetModelData()->GetLayoutInfo());\n}\n\nvoid CMagdolite::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  switch (msg) {\n  case EScriptObjectMessage::Touched:\n    ApplyContactDamage(uid, mgr);\n    break;\n  case EScriptObjectMessage::Damage:\n  case EScriptObjectMessage::InvulnDamage:\n    if (TCastToConstPtr<CGameProjectile> proj = mgr.GetObjectById(uid)) {\n      if (proj->GetOwnerId() == mgr.GetPlayer().GetUniqueId()) {\n        if (GetBodyController()->GetPercentageFrozen() <= 0.f ||\n            x5bc_instaKillVulnerability.GetVulnerability(proj->GetDamageInfo().GetWeaponMode(), false) ==\n                EVulnerability::Deflect) {\n          float hp = HealthInfo(mgr)->GetHP();\n          if (x70c_curHealth - hp <= x574_minHp) {\n            if (x624_normalVulnerability.GetVulnerability(proj->GetDamageInfo().GetWeaponMode(), false) !=\n                EVulnerability::Deflect) {\n              x400_24_hitByPlayerProjectile = true;\n            }\n          } else {\n            x70c_curHealth = hp;\n            x754_24_retreat = true;\n          }\n        } else if (x400_24_hitByPlayerProjectile) {\n          x754_26_lostMyHead = true;\n          x401_30_pendingDeath = true;\n        }\n      }\n    }\n    return;\n  case EScriptObjectMessage::SuspendedMove:\n    if (x580_collisionManager) {\n      x580_collisionManager->SetMovable(mgr, false);\n    }\n    break;\n  case EScriptObjectMessage::Registered: {\n    x450_bodyController->Activate(mgr);\n    RemoveMaterial(EMaterialTypes::Solid, mgr);\n    AddMaterial(EMaterialTypes::NonSolidDamageable, mgr);\n    x584_boneTracker.SetActive(false);\n    CreateShadow(false);\n    const zeus::CVector3f boxExtents = GetBoundingBox().extents();\n    SetBoundingBox({-boxExtents, boxExtents});\n    SetupCollisionActors(mgr);\n    x330_stateMachineState.SetDelay(0.f);\n    CreateFlameThrower(mgr);\n    x70c_curHealth = GetHealthInfo(mgr)->GetHP();\n    break;\n  }\n  case EScriptObjectMessage::Deleted:\n    x580_collisionManager->Destroy(mgr);\n    if (x6c8_flameThrowerId != kInvalidUniqueId) {\n      mgr.FreeScriptObject(x6c8_flameThrowerId);\n      x6c8_flameThrowerId = kInvalidUniqueId;\n    }\n    break;\n  default:\n    break;\n  }\n  CPatterned::AcceptScriptMsg(msg, uid, mgr);\n}\n\nvoid CMagdolite::ApplyContactDamage(TUniqueId uid, CStateManager& mgr) {\n  if (TCastToConstPtr<CCollisionActor> colAct = mgr.GetObjectById(uid)) {\n    if (!IsAlive()) {\n      return;\n    }\n    CDamageInfo dInfo = GetContactDamage();\n\n    for (TUniqueId testId : x69c_) {\n      if (testId == colAct->GetUniqueId()) {\n        dInfo = x6f0_headContactDamage;\n        break;\n      }\n    }\n\n    if (colAct->GetLastTouchedObject() == mgr.GetPlayer().GetUniqueId() && x420_curDamageRemTime <= 0.f) {\n      mgr.ApplyDamage(GetUniqueId(), mgr.GetPlayer().GetUniqueId(), GetUniqueId(), dInfo,\n                      CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), {});\n      x420_curDamageRemTime = x424_damageWaitTime;\n    }\n  }\n}\n\nvoid CMagdolite::SetupCollisionActors(CStateManager& mgr) {\n  std::vector<CJointCollisionDescription> joints;\n  joints.reserve(8);\n  const CAnimData* animData = GetModelData()->GetAnimationData();\n  for (const auto& info : skSpineJoints) {\n    joints.push_back(CJointCollisionDescription::SphereCollision(animData->GetLocatorSegId(info.name), info.radius,\n                                                                 info.name, 200.f));\n  }\n\n  for (const auto& info : skHeadJoints) {\n    joints.push_back(CJointCollisionDescription::OBBAutoSizeCollision(\n        animData->GetLocatorSegId(info.from), animData->GetLocatorSegId(info.to), info.bounds,\n        CJointCollisionDescription::EOrientationType::One, info.to, 200.f));\n  }\n  x580_collisionManager =\n      std::make_unique<CCollisionActorManager>(mgr, GetUniqueId(), GetAreaIdAlways(), joints, GetActive());\n\n  for (int i = 0; i < x580_collisionManager->GetNumCollisionActors(); ++i) {\n    const auto& desc = x580_collisionManager->GetCollisionDescFromIndex(i);\n    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(desc.GetCollisionActorId())) {\n      colAct->AddMaterial(EMaterialTypes::AIJoint, mgr);\n    }\n  }\n\n  for (int i = 0; i < x580_collisionManager->GetNumCollisionActors(); ++i) {\n    const auto& desc = x580_collisionManager->GetCollisionDescFromIndex(i);\n    if (desc.GetName() == \"Top_LCTR\"sv || desc.GetName() == \"Bottom_LCTR\"sv) {\n      if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(desc.GetCollisionActorId())) {\n        colAct->AddMaterial(EMaterialTypes::ProjectilePassthrough, mgr);\n        x69c_.push_back(colAct->GetUniqueId());\n      }\n    }\n  }\n}\n\nvoid CMagdolite::CreateFlameThrower(CStateManager& mgr) {\n  if (x6c8_flameThrowerId != kInvalidUniqueId) {\n    return;\n  }\n\n  x6c8_flameThrowerId = mgr.AllocateUniqueId();\n  mgr.AddObject(new CFlameThrower(x6cc_flameThrowerDesc, \"Magdolite_Flame\"sv, EWeaponType::Plasma, x6a8_flameInfo, {},\n                                  EMaterialTypes::CollisionActor, x6d4_flameThrowerDamage, x6c8_flameThrowerId,\n                                  GetAreaIdAlways(), GetUniqueId(), EProjectileAttrib::None, CAssetId(), -1,\n                                  CAssetId()));\n}\n\nvoid CMagdolite::LaunchFlameThrower(CStateManager& mgr, bool fire) {\n  if ((!fire || x754_30_inProjectileAttack) && IsAlive()) {\n    if (auto* fl = static_cast<CFlameThrower*>(mgr.ObjectById(x6c8_flameThrowerId))) {\n      if (!fire) {\n        fl->Reset(mgr, false);\n      } else {\n        fl->Fire(GetTransform(), mgr, false);\n      }\n    }\n    x754_27_flameThrowerActive = fire;\n  }\n}\n\nvoid CMagdolite::Think(float dt, CStateManager& mgr) {\n  CPatterned::Think(dt, mgr);\n  if (!GetActive()) {\n    return;\n  }\n\n  x758_ += dt;\n  if (GetBodyController()->GetPercentageFrozen() > 0.f && x754_27_flameThrowerActive) {\n    LaunchFlameThrower(mgr, false);\n  }\n\n  if (!IsAlive()) {\n    if (auto* fl = static_cast<CFlameThrower*>(mgr.ObjectById(x6c8_flameThrowerId))) {\n      fl->SetTransform(GetLctrTransform(\"LCTR_MAGMOUTH\"sv), dt);\n    }\n  }\n\n  zeus::CVector3f plPos = mgr.GetPlayer().GetTranslation();\n  zeus::CVector3f aimPos = mgr.GetPlayer().GetAimPosition(mgr, 0.f);\n  zeus::CTransform xf = GetLctrTransform(\"LCTR_MAGMOUTH\"sv);\n  zeus::CVector3f aimDir = (aimPos - xf.origin).normalized();\n  float angleDiff = zeus::CVector3f::getAngleDiff(xf.frontVector(), aimDir);\n  float dVar7 = std::min(angleDiff, zeus::degToRad(x57c_));\n  if (xf.upVector().dot(aimDir) < 0.f) {\n    dVar7 = -dVar7;\n  }\n\n  float dVar8 = dVar7 + zeus::degToRad(x57c_) / 2.f * zeus::degToRad(x57c_);\n  float dVar2 = 1.f - dVar8;\n  x710_attackOffset = plPos * dVar2 + (aimPos * dVar8);\n\n  if (GetActive()) { // lol tautologies\n    if (IsAlive()) {\n      GetModelData()->GetAnimationData()->PreRender();\n      x584_boneTracker.Update(dt);\n      x584_boneTracker.PreRender(mgr, *GetModelData()->GetAnimationData(), GetTransform(), GetModelData()->GetScale(),\n                                 *GetBodyController());\n\n      if (auto* fl = static_cast<CFlameThrower*>(mgr.ObjectById(x6c8_flameThrowerId))) {\n        fl->SetTransform(GetLctrTransform(\"LCTR_MAGMOUTH\"sv), dt);\n      }\n    }\n    x580_collisionManager->Update(dt, mgr, CCollisionActorManager::EUpdateOptions::ObjectSpace);\n    zeus::CTransform headXf = GetLocatorTransform(\"head\"sv);\n    MoveCollisionPrimitive(GetTransform().rotate(GetModelData()->GetScale() * headXf.origin));\n    xe4_27_notInSortedLists = true;\n  }\n\n  if (x750_aiStage == 2) {\n    if (x738_ * 0.5f <= x734_) {\n      x740_ -= x73c_ * dt;\n    } else {\n      x740_ += x73c_ * dt;\n    }\n    x734_ += x740_ * dt;\n    SetTranslation(GetTranslation() - zeus::CVector3f{0.f, 0.f, x740_ * dt});\n    if (GetTranslation().z() < x728_cachedTarget.z()) {\n      SetTranslation(x728_cachedTarget);\n      x750_aiStage = 0;\n    }\n  } else if (x750_aiStage == 1) {\n    if (x738_ * 0.5f <= x734_) {\n      x740_ -= x73c_ * dt;\n    } else {\n      x740_ += x73c_ * dt;\n    }\n    x734_ += x740_ * dt;\n\n    SetTranslation(GetTranslation() + zeus::CVector3f{0.f, 0.f, x740_ * dt});\n    if (GetTranslation().z() > x728_cachedTarget.z()) {\n      SetTranslation(x728_cachedTarget);\n      x750_aiStage = 0;\n    }\n  }\n}\n\nvoid CMagdolite::DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) {\n  if (type == EUserEventType::DamageOff) {\n    LaunchFlameThrower(mgr, false);\n  } else if (type == EUserEventType::DamageOn) {\n    LaunchFlameThrower(mgr, true);\n  } else if (type == EUserEventType::BreakLockOn) {\n    RemoveMaterial(EMaterialTypes::Target, EMaterialTypes::Orbit, mgr);\n    mgr.GetPlayer().TryToBreakOrbit(GetUniqueId(), CPlayer::EPlayerOrbitRequest::ActivateOrbitSource, mgr);\n    return;\n  }\n  CPatterned::DoUserAnimEvent(mgr, node, type, dt);\n}\n\nvoid CMagdolite::Death(CStateManager& mgr, const zeus::CVector3f& direction, EScriptObjectState state) {\n  if (!IsAlive()) {\n    return;\n  }\n  zeus::CTransform tmpXf = GetTransform();\n  SetTransform(GetLctrTransform(\"head\"sv));\n  SendScriptMsgs(EScriptObjectState::MassiveDeath, mgr, EScriptObjectMessage::None);\n  GenerateDeathExplosion(mgr);\n  SetTransform(tmpXf);\n  GetModelData()->GetAnimationData()->SubstituteModelData(x690_headlessModel);\n  x460_knockBackController.SetEnableFreeze(false);\n  GetBodyController()->UnFreeze();\n  if (!x754_26_lostMyHead) {\n    x460_knockBackController.SetSeverity(pas::ESeverity::Two);\n  } else {\n    GetModelData()->GetAnimationData()->SubstituteModelData(x690_headlessModel);\n  }\n  CPatterned::Death(mgr, direction, state);\n}\n\nvoid CMagdolite::FluidFXThink(EFluidState state, CScriptWater& water, CStateManager& mgr) {\n  if (x754_28_alert && state == EFluidState::InFluid) {\n    CFluidPlaneManager* flMgr = mgr.GetFluidPlaneManager();\n    if (flMgr->GetLastRippleDeltaTime(GetUniqueId()) >= 2.3f) {\n      zeus::CVector3f center = GetTranslation();\n      center.z() = float(water.GetTriggerBoundsWR().max.z());\n      water.GetFluidPlane().AddRipple(3.f, GetUniqueId(), center, water, mgr);\n    }\n  }\n}\n\nvoid CMagdolite::SelectTarget(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Update) {\n    TUniqueId targetId = FindSuitableTarget(mgr, EScriptObjectState::Attack, EScriptObjectMessage::Follow);\n    if (targetId != kInvalidUniqueId) {\n      if (TCastToConstPtr<CScriptWaypoint> wp = mgr.GetObjectById(targetId)) {\n        SetTransform(wp->GetTransform());\n        x71c_attackTarget = GetTranslation();\n      }\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    UpdateOrientation(mgr);\n  }\n}\n\nvoid CMagdolite::Generate(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n    x754_24_retreat = false;\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::Generate, &CPatterned::TryGenerate, 0);\n    if (x32c_animState == EAnimState::Repeat) {\n      GetBodyController()->SetLocomotionType(pas::ELocomotionType::Relaxed);\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n  }\n}\n\nvoid CMagdolite::Deactivate(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n    x754_24_retreat = false;\n    x584_boneTracker.SetActive(false);\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::Step, &CPatterned::TryStep, int(pas::EStepDirection::Down));\n    if (x32c_animState == EAnimState::Repeat) {\n      GetBodyController()->SetLocomotionType(pas::ELocomotionType::Internal7);\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n  }\n}\n\nvoid CMagdolite::Attack(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n    x584_boneTracker.SetActive(false);\n    zeus::CVector3f plPos = mgr.GetPlayer().GetTranslation();\n    zeus::CTransform headXf = GetLctrTransform(\"head\"sv);\n    float zDiff = headXf.origin.z() - plPos.z();\n\n    float fVar1 = (zDiff - x74c_);\n    float fVar2 = 0.f;\n    if (fVar1 >= 0.f) {\n      fVar2 = fVar1;\n      if (x748_ < fVar1) {\n        fVar2 = x748_;\n      }\n    }\n    x728_cachedTarget = x71c_attackTarget - zeus::CVector3f(0.f, 0.f, fVar1 - fVar2);\n    x740_ = 0.f;\n    x734_ = 0.f;\n    x738_ = fVar2;\n    x73c_ = (2.f * x738_) / (x744_ * x744_);\n    if (GetTranslation().z() <= x728_cachedTarget.z()) {\n      x750_aiStage = 1;\n    } else {\n      x750_aiStage = 2;\n    }\n    x754_30_inProjectileAttack = true;\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::MeleeAttack, &CPatterned::TryMeleeAttack, 1);\n    zeus::CVector3f direction = (mgr.GetPlayer().GetTranslation().toVec2f() - GetTranslation().toVec2f()).normalized();\n    float angle = zeus::CVector3f::getAngleDiff(GetTransform().frontVector(), direction);\n    if (GetTransform().rightVector().dot(direction) > 0.f) {\n      angle *= -1.f;\n    }\n\n    if ((57.29f * angle) <= x3b8_turnSpeed && (57.29f * angle) < -x3b8_turnSpeed) {\n      angle = zeus::degToRad(-x3b8_turnSpeed);\n    } else {\n      angle = zeus::degToRad(x3b8_turnSpeed);\n    }\n\n    RotateInOneFrameOR(zeus::CQuaternion::fromAxisAngle({0.f, 0.f, 1.f}, angle), arg);\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n    LaunchFlameThrower(mgr, false);\n    x750_aiStage = 1;\n    x728_cachedTarget = x71c_attackTarget;\n    x740_ = 0.f;\n    x744_ = 0.f;\n    x738_ = x728_cachedTarget.z() - GetTranslation().z();\n    x73c_ = (2.f * x738_) / (x744_ * x744_);\n    x754_30_inProjectileAttack = false;\n  }\n}\n\nvoid CMagdolite::Active(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    GetBodyController()->SetLocomotionType(pas::ELocomotionType::Lurk);\n    x584_boneTracker.SetActive(true);\n    x584_boneTracker.SetTarget(mgr.GetPlayer().GetUniqueId());\n    x400_24_hitByPlayerProjectile = false;\n    x754_29_useDetectionRange = false;\n    x330_stateMachineState.SetDelay(x568_initialDelay);\n  } else if (msg == EStateMsg::Update) {\n    zeus::CVector3f posDiff = (mgr.GetPlayer().GetTranslation().toVec2f() - GetTranslation().toVec2f());\n    if (posDiff.canBeNormalized()) {\n      posDiff.normalize();\n      if (GetTransform().frontVector().dot(posDiff) < x578_losMaxDistance) {\n        GetBodyController()->GetCommandMgr().DeliverCmd(CBCLocomotionCmd({}, posDiff, 1.f));\n      }\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x330_stateMachineState.SetDelay(((x570_maxDelay - x56c_minDelay) * mgr.GetActiveRandom()->Float()) + x56c_minDelay);\n  }\n}\n\nvoid CMagdolite::InActive(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    xe7_27_enableRender = false;\n    GetBodyController()->SetLocomotionType(pas::ELocomotionType::Internal7);\n    RemoveMaterial(EMaterialTypes::Orbit, EMaterialTypes::Target, mgr);\n  } else if (msg == EStateMsg::Deactivate) {\n    AddMaterial(EMaterialTypes::Orbit, EMaterialTypes::Target, mgr);\n    x754_25_up = false;\n    UpdateOrientation(mgr);\n    xe7_27_enableRender = true;\n  }\n}\n\nvoid CMagdolite::GetUp(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n    x754_24_retreat = false;\n    x754_28_alert = true;\n    AddMaterial(EMaterialTypes::Orbit, EMaterialTypes::Target, mgr);\n  } else if (msg == EStateMsg::Update) {\n    if (!x754_25_up) {\n      TryCommand(mgr, pas::EAnimationState::Step, &CPatterned::TryStep, int(pas::EStepDirection::Forward));\n    } else {\n      TryCommand(mgr, pas::EAnimationState::Step, &CPatterned::TryStep, int(pas::EStepDirection::Up));\n    }\n    if (x32c_animState == EAnimState::Repeat) {\n      GetBodyController()->SetLocomotionType(pas::ELocomotionType::Lurk);\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n    x754_28_alert = false;\n  }\n}\n\nvoid CMagdolite::Taunt(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n    x584_boneTracker.SetActive(true);\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::Taunt, &CPatterned::TryTaunt, 1);\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n    x584_boneTracker.SetActive(false);\n  }\n}\n\nvoid CMagdolite::Lurk(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    GetBodyController()->SetLocomotionType(pas::ELocomotionType::Relaxed);\n    x584_boneTracker.SetActive(false);\n    x330_stateMachineState.SetDelay(0.f);\n    AddMaterial(EMaterialTypes::Target, EMaterialTypes::Orbit, mgr);\n  } else if (msg == EStateMsg::Deactivate) {\n    x754_25_up = true;\n  }\n}\n\nvoid CMagdolite::ProjectileAttack(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n    LaunchFlameThrower(mgr, true);\n    x584_boneTracker.SetActive(true);\n    x584_boneTracker.SetNoHorizontalAim(true);\n    x584_boneTracker.SetTarget(kInvalidUniqueId);\n    x754_30_inProjectileAttack = true;\n  } else if (msg == EStateMsg::Update) {\n    if (TooClose(mgr, 0.f)) {\n      TryCommand(mgr, pas::EAnimationState::ProjectileAttack, &CPatterned::TryProjectileAttack, 0);\n    } else {\n      TryCommand(mgr, pas::EAnimationState::ProjectileAttack, &CPatterned::TryProjectileAttack, 1);\n    }\n    x584_boneTracker.SetTargetPosition(x710_attackOffset);\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n    LaunchFlameThrower(mgr, false);\n    x584_boneTracker.SetActive(false);\n    x584_boneTracker.SetNoHorizontalAim(false);\n    x754_30_inProjectileAttack = false;\n    x584_boneTracker.SetTarget(mgr.GetPlayer().GetUniqueId());\n  }\n}\n\nvoid CMagdolite::Flinch(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n    x400_24_hitByPlayerProjectile = false;\n    x584_boneTracker.SetActive(false);\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::KnockBack, &CPatterned::TryKnockBack, 0);\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n  }\n}\n\nvoid CMagdolite::Retreat(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n    x754_24_retreat = false;\n    x584_boneTracker.SetActive(false);\n    GetBodyController()->GetCommandMgr().DeliverCmd(CBodyStateCmd(EBodyStateCmd::NextState));\n    x754_28_alert = true;\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::Generate, &CPatterned::TryGenerateNoXf, 1);\n    if (x32c_animState == EAnimState::Repeat) {\n      GetBodyController()->SetLocomotionType(pas::ELocomotionType::Internal7);\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x754_28_alert = false;\n    x32c_animState = EAnimState::NotReady;\n  }\n}\n\nbool CMagdolite::InAttackPosition(CStateManager& mgr, float arg) {\n  zeus::CTransform xf = GetLctrTransform(\"head\"sv);\n  zeus::CVector3f plAimPos = mgr.GetPlayer().GetAimPosition(mgr, 0.f);\n  zeus::CVector3f diff = (plAimPos - GetTranslation());\n  if (GetTranslation().z() < plAimPos.z() && plAimPos.z() < xf.origin.z()) {\n    diff.z() = 0.f;\n  }\n\n  if (std::fabs(diff.magnitude()) >= FLT_EPSILON) {\n    return ((1.f / diff.magnitude()) * diff).dot(GetTransform().frontVector()) < x578_losMaxDistance;\n  }\n\n  return false;\n}\n\nbool CMagdolite::Leash(CStateManager& mgr, float arg) {\n  float dist = (GetTranslation() - mgr.GetPlayer().GetTranslation()).magSquared();\n  bool ret = dist < x3c8_leashRadius * x3c8_leashRadius;\n  return ret;\n}\n\nbool CMagdolite::HasAttackPattern(CStateManager& mgr, float arg) {\n  return FindSuitableTarget(mgr, EScriptObjectState::Attack, EScriptObjectMessage::Follow) != kInvalidUniqueId;\n}\n\nbool CMagdolite::LineOfSight(CStateManager& mgr, float arg) {\n  zeus::CTransform mouthXf = GetLctrTransform(\"LCTR_MAGMOUTH\"sv);\n  zeus::CVector3f diff = x710_attackOffset - mouthXf.origin;\n  if (diff.canBeNormalized()) {\n    diff.normalize();\n    if (diff.dot(GetTransform().frontVector()) < x578_losMaxDistance) {\n      return false;\n    }\n  }\n\n  bool ret = mgr.RayCollideWorld(\n      mouthXf.origin, x710_attackOffset,\n      CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid, EMaterialTypes::Character},\n                                          {EMaterialTypes::Player, EMaterialTypes::ProjectilePassthrough}),\n      this);\n  return ret;\n}\n\nbool CMagdolite::ShouldRetreat(CStateManager& mgr, float arg) { return x754_24_retreat; }\n\nvoid CMagdolite::UpdateOrientation(CStateManager& mgr) {\n  zeus::CVector3f plDiff = (mgr.GetPlayer().GetTranslation().toVec2f() - GetTranslation().toVec2f());\n  plDiff = plDiff.normalized();\n  float angle = zeus::CVector3f::getAngleDiff(GetTransform().frontVector(), plDiff);\n  if (GetTransform().rightVector().dot(plDiff) > 0.f) {\n    angle *= -1.f;\n  }\n  zeus::CQuaternion q = GetTransform().basis;\n  q.rotateZ(angle);\n  SetTransform(q.toTransform(GetTranslation()));\n}\n\nTUniqueId CMagdolite::FindSuitableTarget(CStateManager& mgr, EScriptObjectState state, EScriptObjectMessage msg) {\n  float lastDistance = FLT_MAX;\n  int wpCount = 0;\n  float maxDistanceSq =\n      x754_29_useDetectionRange ? x3bc_detectionRange * x3bc_detectionRange : x300_maxAttackRange * x300_maxAttackRange;\n\n  TUniqueId tmpId = kInvalidUniqueId;\n  for (const auto& conn : x20_conns) {\n    if (conn.x0_state != state || conn.x4_msg != msg) {\n      continue;\n    }\n    TUniqueId uid = mgr.GetIdForScript(conn.x8_objId);\n    if (const CEntity* ent = mgr.GetObjectById(uid)) {\n      if (!ent->GetActive()) {\n        continue;\n      }\n\n      if (TCastToConstPtr<CScriptWaypoint> wp = ent) {\n        ++wpCount;\n        const float dist = (wp->GetTranslation() - mgr.GetPlayer().GetTranslation()).magSquared();\n        if (dist < maxDistanceSq) {\n          if (dist < lastDistance) {\n            lastDistance = dist;\n            tmpId = uid;\n          }\n        }\n      }\n    }\n  }\n\n  if (!x754_29_useDetectionRange) {\n    int skipCount = mgr.GetActiveRandom()->Next();\n    skipCount = skipCount - (skipCount / wpCount) * wpCount;\n    for (const auto& conn : x20_conns) {\n      if (conn.x0_state != state || conn.x4_msg != msg) {\n        continue;\n      }\n      TUniqueId uid = mgr.GetIdForScript(conn.x8_objId);\n      if (const CEntity* ent = mgr.GetObjectById(uid)) {\n        if (!ent->GetActive()) {\n          continue;\n        }\n\n        if (TCastToConstPtr<CScriptWaypoint>(ent)) {\n          tmpId = uid;\n          if (skipCount == 0) {\n            break;\n          }\n          --skipCount;\n        }\n      }\n    }\n  }\n\n  return tmpId;\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CMagdolite.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Character/CBoneTracking.hpp\"\n#include \"Runtime/Weapon/CFlameInfo.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n\nnamespace metaforce {\nclass CCollisionActorManager;\nclass CSkinnedModel;\nclass CWeaponDescription;\n\nnamespace MP1 {\nclass CMagdolite : public CPatterned {\nprivate:\n  float x568_initialDelay;\n  float x56c_minDelay;\n  float x570_maxDelay;\n  float x574_minHp;\n  float x578_losMaxDistance;\n  float x57c_;\n  std::unique_ptr<CCollisionActorManager> x580_collisionManager;\n  CBoneTracking x584_boneTracker;\n  CDamageVulnerability x5bc_instaKillVulnerability;\n  CDamageVulnerability x624_normalVulnerability;\n  // CRefData* x68c_;\n  TLockedToken<CSkinnedModel> x690_headlessModel;\n  rstl::reserved_vector<TUniqueId, 4> x69c_;\n  CFlameInfo x6a8_flameInfo;\n  TUniqueId x6c8_flameThrowerId = kInvalidUniqueId;\n  TLockedToken<CWeaponDescription> x6cc_flameThrowerDesc; // was TToken<CWeaponDescription>\n  CDamageInfo x6d4_flameThrowerDamage;\n  CDamageInfo x6f0_headContactDamage;\n  float x70c_curHealth = 0.f; // not init in ctr\n  zeus::CVector3f x710_attackOffset;\n  zeus::CVector3f x71c_attackTarget;\n  zeus::CVector3f x728_cachedTarget;\n  float x734_ = 0.f;\n  float x738_ = 0.f;\n  float x73c_ = 0.f;\n  float x740_ = 0.f;\n  float x744_;\n  float x748_;\n  float x74c_;\n  u32 x750_aiStage = 0;\n  bool x754_24_retreat : 1 = false;\n  bool x754_25_up : 1 = false;\n  bool x754_26_lostMyHead : 1 = false;\n  bool x754_27_flameThrowerActive : 1 = false;\n  bool x754_28_alert : 1 = false;\n  bool x754_29_useDetectionRange : 1 = true;\n  bool x754_30_inProjectileAttack : 1 = false;\n  float x758_ = 0.f;\n\n  void ApplyContactDamage(TUniqueId uid, CStateManager& mgr);\n  void SetupCollisionActors(CStateManager& mgr);\n  void CreateFlameThrower(CStateManager& mgr);\n  void LaunchFlameThrower(CStateManager& mgr, bool fire);\n  void UpdateOrientation(CStateManager& mgr);\n  TUniqueId FindSuitableTarget(CStateManager& mgr, EScriptObjectState state, EScriptObjectMessage msg);\n\npublic:\n  DEFINE_PATTERNED(Magdolite);\n\n  CMagdolite(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n             CModelData&& mData, const CPatternedInfo& pInfo, const CActorParameters& actParms, float f1, float f2,\n             const CDamageInfo& dInfo1, const CDamageInfo& dInfo2, const CDamageVulnerability& dVuln1,\n             const CDamageVulnerability& dVuln2, CAssetId modelId, CAssetId skinId, float f3, float f4, float f5,\n             float f6, const CFlameInfo& magData, float f7, float f8, float f9);\n\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) override;\n  void Think(float dt, CStateManager& mgr) override;\n  void Touch(CActor& actor, CStateManager& mgr) override {}\n  const CDamageVulnerability* GetDamageVulnerability() const override {\n    return x400_25_alive ? CAi::GetDamageVulnerability() : &CDamageVulnerability::ImmuneVulnerabilty();\n  }\n  const CDamageVulnerability* GetDamageVulnerability(const zeus::CVector3f&, const zeus::CVector3f&,\n                                                     const CDamageInfo&) const override {\n    return GetDamageVulnerability();\n  }\n\n  void DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) override;\n  void Death(CStateManager& mgr, const zeus::CVector3f& direction, EScriptObjectState state) override;\n  void FluidFXThink(EFluidState state, CScriptWater& water, CStateManager& mgr) override;\n  void SelectTarget(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Generate(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Deactivate(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Attack(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Active(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void InActive(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void GetUp(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Taunt(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Lurk(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void ProjectileAttack(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Flinch(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Retreat(CStateManager& mgr, EStateMsg msg, float arg) override;\n  bool InAttackPosition(CStateManager& mgr, float arg) override;\n  bool Leash(CStateManager& mgr, float arg) override;\n  bool HasAttackPattern(CStateManager& mgr, float arg) override;\n  bool LineOfSight(CStateManager& mgr, float arg) override;\n  bool ShouldRetreat(CStateManager& mgr, float arg) override;\n};\n} // namespace MP1\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/MP1/World/CMakeLists.txt",
    "content": "set(MP1_WORLD_SOURCES\n        CScriptContraption.hpp CScriptContraption.cpp\n        CAtomicAlpha.hpp CAtomicAlpha.cpp\n        CAtomicBeta.hpp CAtomicBeta.cpp\n        CBabygoth.hpp CBabygoth.cpp\n        CBeetle.hpp CBeetle.cpp\n        CBloodFlower.hpp CBloodFlower.cpp\n        CBouncyGrenade.hpp CBouncyGrenade.cpp\n        CBurrower.hpp CBurrower.cpp\n        CChozoGhost.hpp CChozoGhost.cpp\n        CElitePirate.hpp CElitePirate.cpp\n        CDrone.hpp CDrone.cpp\n        CDroneLaser.hpp CDroneLaser.cpp\n        CSpacePirate.hpp CSpacePirate.cpp\n        CParasite.hpp CParasite.cpp\n        CBabygoth.hpp CBabygoth.cpp\n        CTryclops.hpp CTryclops.cpp\n        CEnergyBall.hpp CEnergyBall.cpp\n        CEyeball.hpp CEyeball.cpp\n        CFireFlea.hpp CFireFlea.cpp\n        CFlaahgra.hpp CFlaahgra.cpp\n        CFlaahgraProjectile.hpp CFlaahgraProjectile.cpp\n        CFlaahgraTentacle.hpp CFlaahgraTentacle.cpp\n        CFlickerBat.hpp CFlickerBat.cpp\n        CFlyingPirate.hpp CFlyingPirate.cpp\n        CGrenadeLauncher.hpp CGrenadeLauncher.cpp\n        CJellyZap.hpp CJellyZap.cpp\n        CMagdolite.hpp CMagdolite.cpp\n        CMetaree.hpp CMetaree.cpp\n        CMetroid.hpp CMetroid.cpp\n        CMetroidBeta.hpp CMetroidBeta.cpp\n        CMetroidPrime.hpp CMetroidPrime.cpp\n        CMetroidPrimeProjectile.hpp CMetroidPrimeProjectile.cpp\n        CMetroidPrimeRelay.hpp CMetroidPrimeRelay.cpp\n        CMetroidPrimeStage2.hpp CMetroidPrimeStage2.cpp\n        CNewIntroBoss.hpp CNewIntroBoss.cpp\n        COmegaPirate.hpp COmegaPirate.cpp\n        CParasite.hpp CParasite.cpp\n        CPhazonHealingNodule.hpp CPhazonHealingNodule.cpp\n        CPhazonPool.hpp CPhazonPool.cpp\n        CPuddleSpore.hpp CPuddleSpore.cpp\n        CPuddleToadGamma.hpp CPuddleToadGamma.cpp\n        CPuffer.hpp CPuffer.cpp\n        CRidley.hpp CRidley.cpp\n        CRipper.hpp CRipper.cpp\n        CSeedling.hpp CSeedling.cpp\n        CShockWave.hpp CShockWave.cpp\n        CSpacePirate.hpp CSpacePirate.cpp\n        CSpankWeed.hpp CSpankWeed.cpp\n        CThardus.hpp CThardus.cpp\n        CThardusRockProjectile.hpp CThardusRockProjectile.cpp\n        CTryclops.hpp CTryclops.cpp\n        CIceSheegoth.hpp CIceSheegoth.cpp\n        CWarWasp.hpp CWarWasp.cpp\n        CIceAttackProjectile.hpp CIceAttackProjectile.cpp\n        )\n\nruntime_add_list(World MP1_WORLD_SOURCES)\n"
  },
  {
    "path": "Runtime/MP1/World/CMetaree.cpp",
    "content": "#include \"Runtime/MP1/World/CMetaree.hpp\"\n\n#include \"Runtime/CPlayerState.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Weapon/CGameProjectile.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce::MP1 {\nCMetaree::CMetaree(TUniqueId uid, std::string_view name, EFlavorType flavor, const CEntityInfo& info,\n                   const zeus::CTransform& xf, CModelData&& mData, const CPatternedInfo& pInfo,\n                   const CDamageInfo& dInfo, float f1, const zeus::CVector3f& v1, float f2, EBodyType bodyType,\n                   float f3, float f4, const CActorParameters& aParms)\n: CPatterned(ECharacter::Metaree, uid, name, flavor, info, xf, std::move(mData), pInfo, EMovementType::Flyer,\n             EColliderType::Zero, bodyType, aParms, EKnockBackVariant::Small)\n, x568_delay(f3)\n, x56c_haltDelay(f4)\n, x570_dropHeight(f1)\n, x574_offset(v1)\n, x580_attackSpeed(f2)\n, x5ac_damageInfo(dInfo)\n, x5ca_24_(true)\n, x5ca_25_started(false)\n, x5ca_26_deactivated(false) {}\n\nvoid CMetaree::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CMetaree::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  CPatterned::AcceptScriptMsg(msg, uid, mgr);\n\n  if (msg == EScriptObjectMessage::Start) {\n    x5ca_25_started = true;\n  } else if (msg == EScriptObjectMessage::Registered) {\n    x450_bodyController->Activate(mgr);\n  }\n}\n\nvoid CMetaree::Think(float dt, CStateManager& mgr) {\n  bool target = true;\n  if (mgr.GetPlayerState()->GetCurrentVisor() != CPlayerState::EPlayerVisor::Thermal &&\n      mgr.GetPlayerState()->GetCurrentVisor() != CPlayerState::EPlayerVisor::Scan) {\n    target = x5ca_26_deactivated;\n  }\n  xe7_31_targetable = target;\n  CPatterned::Think(dt, mgr);\n}\n\nvoid CMetaree::Explode(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg != EStateMsg::Activate) {\n    return;\n  }\n\n  mgr.ApplyDamage(GetUniqueId(), mgr.GetPlayer().GetUniqueId(), GetUniqueId(), x5ac_damageInfo,\n                  CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), {});\n  MassiveDeath(mgr);\n}\n\nvoid CMetaree::Touch(CActor& act, CStateManager& mgr) {\n  if (!x400_25_alive) {\n    return;\n  }\n\n  if (TCastToPtr<CGameProjectile> projectile = act) {\n    if (projectile->GetOwnerId() != mgr.GetPlayer().GetUniqueId()) {\n      return;\n    }\n\n    x400_24_hitByPlayerProjectile = true;\n    x590_projectileDelta = projectile->GetTranslation() - projectile->GetPreviousPos();\n  }\n}\n\nvoid CMetaree::CollidedWith(TUniqueId id, const CCollisionInfoList& colList, CStateManager& mgr) {\n  if (!x400_25_alive || colList.GetCount() <= 0) {\n    return;\n  }\n\n  mgr.ApplyDamageToWorld(GetUniqueId(), *this, GetTranslation(), x5ac_damageInfo,\n                         CMaterialFilter::MakeInclude({EMaterialTypes::Player}));\n  SendScriptMsgs(EScriptObjectState::Arrived, mgr, EScriptObjectMessage::None);\n  MassiveDeath(mgr);\n}\n\nvoid CMetaree::Flee(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    ApplyImpulseWR(5.f * (GetMass() * (x590_projectileDelta * zeus::CVector3f{1.f, 1.f, 0.f})), zeus::CAxisAngle());\n\n    SetMomentumWR({0.f, 0.f, -GetGravityConstant() * GetMass()});\n    SetTransform(zeus::CTransform::Translate(GetTranslation()));\n    x5a8_ = 0;\n  } else if (msg == EStateMsg::Update) {\n    if (x5a8_ != 0) {\n      return;\n    }\n\n    if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::LieOnGround) {\n      x5a8_ = 1;\n      return;\n    }\n\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBCKnockDownCmd({0.f, 1.f, 0.f}, pas::ESeverity::Zero));\n  }\n}\n\nvoid CMetaree::Dead(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg != EStateMsg::Activate) {\n    return;\n  }\n\n  mgr.ApplyDamageToWorld(GetUniqueId(), *this, GetTranslation(), x5ac_damageInfo,\n                         CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Player}, {}));\n  DeathDelete(mgr);\n}\n\nvoid CMetaree::Attack(CStateManager&, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x5a8_ = 0;\n    zeus::CVector3f dir = (x584_lookPos - GetTranslation()).normalized();\n    SetVelocityWR(x580_attackSpeed * dir);\n    CSfxManager::AddEmitter(x5c8_attackSfx, GetTranslation(), {}, true, false, 127, kInvalidAreaId);\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Combat);\n    x59c_velocity = x580_attackSpeed * dir;\n  } else if (msg == EStateMsg::Update) {\n    if (x450_bodyController->GetPercentageFrozen() == 0.f) {\n      SetVelocityWR(x59c_velocity);\n    } else {\n      Stop();\n      SetVelocityWR({});\n    }\n  }\n}\n\nvoid CMetaree::Halt(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg != EStateMsg::Activate) {\n    return;\n  }\n\n  Stop();\n  SetVelocityWR(zeus::skZero3f);\n  SetMomentumWR(zeus::skZero3f);\n  x450_bodyController->SetLocomotionType(pas::ELocomotionType::Lurk);\n  x584_lookPos = x574_offset + mgr.GetPlayer().GetTranslation();\n  SetTransform(zeus::lookAt(GetTranslation(), x584_lookPos));\n  x330_stateMachineState.SetDelay(x56c_haltDelay);\n}\n\nvoid CMetaree::Active(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x400_24_hitByPlayerProjectile = false;\n    x584_lookPos = GetTranslation() - zeus::CVector3f{0.f, 0.f, x570_dropHeight};\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBCGenerateCmd(pas::EGenerateType::Zero, x584_lookPos));\n    SetMomentumWR({0.f, 0.f, -GetGravityConstant() * GetMass()});\n  } else if (msg == EStateMsg::Update) {\n    x450_bodyController->GetCommandMgr().DeliverTargetVector(\n        (mgr.GetPlayer().GetTranslation() - GetTranslation()).normalized());\n  } else if (msg == EStateMsg::Deactivate) {\n    SetMomentumWR({});\n  }\n}\n\nvoid CMetaree::InActive(CStateManager&, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    const auto locomotionType = x5ca_26_deactivated ? pas::ELocomotionType::Crouch : pas::ELocomotionType::Relaxed;\n    x450_bodyController->SetLocomotionType(locomotionType);\n  } else if (msg == EStateMsg::Deactivate) {\n    x5ca_26_deactivated = true;\n  }\n}\n\nbool CMetaree::InRange(CStateManager& mgr, float arg) {\n  if (x5ca_25_started) {\n    return true;\n  }\n\n  return CPatterned::InRange(mgr, arg);\n}\n\nbool CMetaree::ShouldAttack(CStateManager&, float) { return GetTranslation().z() < x584_lookPos.z(); }\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CMetaree.hpp",
    "content": "#pragma once\n\n#include \"Audio/SFX/Metaree.h\"\n\n#include \"Runtime/World/CDamageInfo.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce::MP1 {\nclass CMetaree : public CPatterned {\n  float x568_delay;\n  float x56c_haltDelay;\n  float x570_dropHeight;\n  zeus::CVector3f x574_offset;\n  float x580_attackSpeed;\n  zeus::CVector3f x584_lookPos;\n  zeus::CVector3f x590_projectileDelta;\n  zeus::CVector3f x59c_velocity;\n  u32 x5a8_ = 0;\n  CDamageInfo x5ac_damageInfo;\n  u16 x5c8_attackSfx = SFXsfx0225;\n  bool x5ca_24_ : 1;\n  bool x5ca_25_started : 1;\n  bool x5ca_26_deactivated : 1;\n  u32 x5cc_;\n\npublic:\n  DEFINE_PATTERNED(Metaree);\n  CMetaree(TUniqueId, std::string_view, EFlavorType, const CEntityInfo&, const zeus::CTransform&, CModelData&&,\n           const CPatternedInfo&, const CDamageInfo&, float, const zeus::CVector3f&, float, EBodyType, float, float,\n           const CActorParameters&);\n\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void Think(float, CStateManager&) override;\n\n  void Touch(CActor&, CStateManager&) override;\n  void CollidedWith(TUniqueId, const CCollisionInfoList&, CStateManager&) override;\n  void ThinkAboutMove(float) override {}\n  bool Delay(CStateManager&, float) override { return x330_stateMachineState.GetTime() > x568_delay; }\n  void Explode(CStateManager&, EStateMsg, float) override;\n  void Flee(CStateManager&, EStateMsg, float) override;\n  void Dead(CStateManager&, EStateMsg, float) override;\n  void Attack(CStateManager&, EStateMsg, float) override;\n  void Halt(CStateManager&, EStateMsg, float) override;\n  void Active(CStateManager&, EStateMsg, float) override;\n  void InActive(CStateManager&, EStateMsg, float) override;\n  bool InRange(CStateManager&, float) override;\n  bool ShouldAttack(CStateManager&, float) override;\n};\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CMetroid.cpp",
    "content": "#include \"Runtime/MP1/World/CMetroid.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Camera/CFirstPersonCamera.hpp\"\n#include \"Runtime/Character/CPASAnimParmData.hpp\"\n#include \"Runtime/Collision/CGameCollision.hpp\"\n#include \"Runtime/Weapon/CGameProjectile.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CScriptWater.hpp\"\n#include \"Runtime/World/CTeamAiMgr.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n#include \"Runtime/World/ScriptLoader.hpp\"\n#include \"Runtime/MP1/World/CMetroidBeta.hpp\"\n\nnamespace metaforce::MP1 {\nnamespace {\nconstexpr CDamageVulnerability skGammaRedDamageVulnerability{\n    EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Immune,\n    EVulnerability::Deflect, EVulnerability::Normal,  EVulnerability::Deflect, EVulnerability::Deflect,\n    EVulnerability::Normal,  EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect,\n    EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EDeflectType::None,\n};\n\nconstexpr CDamageVulnerability skGammaWhiteDamageVulnerability{\n    EVulnerability::Deflect, EVulnerability::Immune,  EVulnerability::Deflect, EVulnerability::Deflect,\n    EVulnerability::Deflect, EVulnerability::Normal,  EVulnerability::Deflect, EVulnerability::Deflect,\n    EVulnerability::Normal,  EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect,\n    EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EDeflectType::None,\n};\n\nconstexpr CDamageVulnerability skGammaPurpleDamageVulnerability{\n    EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Immune,  EVulnerability::Deflect,\n    EVulnerability::Deflect, EVulnerability::Normal,  EVulnerability::Deflect, EVulnerability::Deflect,\n    EVulnerability::Normal,  EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect,\n    EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EDeflectType::None,\n};\n\nconstexpr CDamageVulnerability skGammaOrangeDamageVulnerability{\n    EVulnerability::Immune,  EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect,\n    EVulnerability::Deflect, EVulnerability::Normal,  EVulnerability::Deflect, EVulnerability::Deflect,\n    EVulnerability::Normal,  EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect,\n    EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EDeflectType::None,\n};\n\nconstexpr CDamageVulnerability skNormalDamageVulnerability{\n    EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect,\n    EVulnerability::Deflect, EVulnerability::Normal,  EVulnerability::Deflect, EVulnerability::Deflect,\n    EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect,\n    EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EDeflectType::None,\n};\n\nconstexpr auto skPirateSuckJoint = \"Head_1\"sv;\n\nconstexpr std::array skJointNameList = {\n    \"Head_1\"sv,        \"L_ankle\"sv,    \"L_elbow\"sv,       \"L_hip\"sv,   \"L_knee\"sv,  \"L_shoulder\"sv,\n    \"L_varias2_SDK\"sv, \"L_wrist\"sv,    \"Pelvis\"sv,        \"R_ankle\"sv, \"R_elbow\"sv, \"R_hip\"sv,\n    \"R_knee\"sv,        \"R_shoulder\"sv, \"R_varias2_SDK\"sv, \"Spine_1\"sv, \"Spine_2\"sv,\n};\n} // namespace\n\nCMetroidData::CMetroidData(CInputStream& in)\n: x0_frozenVulnerability(in)\n, x68_energyDrainVulnerability(in)\n, xd0_energyDrainPerSec(in.ReadFloat())\n, xd4_maxEnergyDrainAllowed(in.ReadFloat())\n, xd8_telegraphAttackTime(in.ReadFloat())\n, xdc_stage2GrowthScale(in.ReadFloat())\n, xe0_stage2GrowthEnergy(in.ReadFloat())\n, xe4_explosionGrowthEnergy(in.ReadFloat()) {\n  xe8_animParms1 = ScriptLoader::LoadAnimationParameters(in);\n  xf8_animParms2 = ScriptLoader::LoadAnimationParameters(in);\n  x108_animParms3 = ScriptLoader::LoadAnimationParameters(in);\n  x118_animParms4 = ScriptLoader::LoadAnimationParameters(in);\n  x128_24_startsInWall = in.ReadBool();\n}\n\nCMetroid::CMetroid(TUniqueId uid, std::string_view name, EFlavorType flavor, const CEntityInfo& info,\n                   const zeus::CTransform& xf, CModelData&& mData, const CPatternedInfo& pInfo,\n                   const CActorParameters& aParms, const CMetroidData& metroidData, TUniqueId other)\n: CPatterned(ECharacter::Metroid, uid, name, flavor, info, xf, std::move(mData), pInfo, EMovementType::Flyer,\n             EColliderType::One, EBodyType::Flyer, aParms, EKnockBackVariant::Medium)\n, x56c_data(metroidData)\n, x6a0_collisionPrimitive(zeus::CSphere{zeus::skZero3f, 0.9f * GetModelData()->GetScale().y()}, GetMaterialList())\n, x6c0_pathFindSearch(nullptr, 3, pInfo.GetPathfindingIndex(), 1.f, 1.f)\n, x7cc_gammaType(flavor == EFlavorType::Two ? EGammaType::Red : EGammaType::Normal)\n, x7d0_scale1(GetModelData()->GetScale())\n, x7dc_scale2(GetModelData()->GetScale())\n, x7e8_scale3(GetModelData()->GetScale())\n, x81c_patternedInfo(pInfo)\n, x954_actParams(aParms)\n, x9bc_parent(other) {\n  x808_loopAttackDistance = GetAnimationDistance(\n      CPASAnimParmData{pas::EAnimationState::LoopAttack, CPASAnimParm::FromEnum(2), CPASAnimParm::FromEnum(3)});\n  UpdateTouchBounds();\n  SetCoefficientOfRestitutionModifier(0.9f);\n  x460_knockBackController.SetX82_24(false);\n  x460_knockBackController.SetEnableBurn(false);\n  x460_knockBackController.SetEnableBurnDeath(false);\n  x460_knockBackController.SetEnableShock(false);\n  if (flavor == CPatterned::EFlavorType::Two) {\n    x460_knockBackController.SetEnableFreeze(false);\n  }\n  x81c_patternedInfo.SetActive(true);\n}\n\nvoid CMetroid::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  CPatterned::AcceptScriptMsg(msg, uid, mgr);\n  switch (msg) {\n  case EScriptObjectMessage::Registered:\n    x450_bodyController->Activate(mgr);\n    UpdateVolume();\n    break;\n  case EScriptObjectMessage::Alert:\n    x9bf_24_alert = true;\n    break;\n  case EScriptObjectMessage::Deactivate:\n    SwarmRemove(mgr);\n    break;\n  case EScriptObjectMessage::Damage:\n  case EScriptObjectMessage::InvulnDamage:\n    if (TCastToConstPtr<CGameProjectile> projectile = mgr.GetObjectById(uid)) {\n      const CDamageInfo& damageInfo = projectile->GetDamageInfo();\n      if (GetDamageVulnerability()->WeaponHits(damageInfo.GetWeaponMode(), false)) {\n        ApplyGrowth(damageInfo.GetDamage());\n      }\n    }\n    x9bf_24_alert = true;\n    break;\n  case EScriptObjectMessage::InitializedInArea:\n    if (x698_teamAiMgrId == kInvalidUniqueId) {\n      x698_teamAiMgrId = CTeamAiMgr::GetTeamAiMgr(*this, mgr);\n    }\n    x6c0_pathFindSearch.SetArea(mgr.GetWorld()->GetAreaAlways(GetAreaIdAlways())->GetPostConstructed()->x10bc_pathArea);\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CMetroid::Think(float dt, CStateManager& mgr) {\n  if (!GetActive()) {\n    return;\n  }\n  if (CTeamAiMgr::GetTeamAiRole(mgr, x698_teamAiMgrId, GetUniqueId()) == nullptr) {\n    SwarmAdd(mgr);\n  }\n  UpdateAttackChance(mgr, dt);\n  SuckEnergyFromTarget(mgr, dt);\n  PreventWorldCollisions(mgr, dt);\n  UpdateTouchBounds();\n  RestoreSolidCollision(mgr);\n  CPatterned::Think(dt, mgr);\n}\n\nvoid CMetroid::DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType eType, float dt) {\n  if (eType == EUserEventType::GenerateEnd) {\n    AddMaterial(EMaterialTypes::Solid, mgr);\n  } else {\n    CPatterned::DoUserAnimEvent(mgr, node, eType, dt);\n  }\n}\n\nEWeaponCollisionResponseTypes CMetroid::GetCollisionResponseType(const zeus::CVector3f& vec1,\n                                                                 const zeus::CVector3f& vec2, const CWeaponMode& mode,\n                                                                 EProjectileAttrib attribute) const {\n  return !GetDamageVulnerability()->WeaponHurts(mode, false) && x450_bodyController->GetPercentageFrozen() <= 0.f\n             ? EWeaponCollisionResponseTypes::Unknown58\n             : EWeaponCollisionResponseTypes::Unknown33;\n}\n\nconst CDamageVulnerability* CMetroid::GetDamageVulnerability() const {\n  if (IsSuckingEnergy()) {\n    if (x9c0_24_isEnergyDrainVulnerable) {\n      return &x56c_data.GetEnergyDrainVulnerability();\n    }\n    return &skNormalDamageVulnerability;\n  }\n  if (x9bf_25_growing && !x450_bodyController->IsFrozen()) {\n    return &x56c_data.GetEnergyDrainVulnerability();\n  }\n  if (x450_bodyController->GetPercentageFrozen() > 0.f) {\n    return &x56c_data.GetFrozenVulnerability();\n  }\n  if (x3fc_flavor == CPatterned::EFlavorType::Two) {\n    if (x7cc_gammaType == EGammaType::Red) {\n      return &skGammaRedDamageVulnerability;\n    }\n    if (x7cc_gammaType == EGammaType::White) {\n      return &skGammaWhiteDamageVulnerability;\n    }\n    if (x7cc_gammaType == EGammaType::Purple) {\n      return &skGammaPurpleDamageVulnerability;\n    }\n    if (x7cc_gammaType == EGammaType::Orange) {\n      return &skGammaOrangeDamageVulnerability;\n    }\n  }\n  return CAi::GetDamageVulnerability();\n}\n\nzeus::CVector3f CMetroid::GetOrigin(const CStateManager& mgr, const CTeamAiRole& role,\n                                    const zeus::CVector3f& aimPos) const {\n  CPlayer& player = mgr.GetPlayer();\n  float range = 0.5f * (x2fc_minAttackRange + x300_maxAttackRange);\n  const zeus::CVector3f& pos = GetTranslation();\n  TUniqueId target = x7b0_attackTarget;\n  if (target == player.GetUniqueId()) {\n    const zeus::CVector3f& playerPos = player.GetTranslation();\n    const zeus::CVector3f& playerFace = player.GetTransform().frontVector();\n    if (player.GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed) {\n      zeus::CVector3f face = pos - playerPos;\n      face.z() = 0.f;\n      if (face.canBeNormalized()) {\n        face.normalize();\n      } else {\n        face = playerFace;\n      }\n      return playerPos + zeus::CVector3f{range * face.x(), range * face.y(), 0.5f};\n    }\n    return aimPos + zeus::CVector3f{range * playerFace.x(), range * playerFace.y(), 0.5f};\n  }\n  if (TCastToConstPtr<CActor> actor = mgr.GetObjectById(target)) {\n    const zeus::CVector3f& actorPos = actor->GetTranslation();\n    zeus::CVector3f face = pos - actorPos;\n    face.z() = 0.f;\n    if (face.canBeNormalized()) {\n      face.normalize();\n    } else {\n      face = actor->GetTransform().frontVector();\n    }\n    return actorPos + zeus::CVector3f{range * face.x(), range * face.y(), 0.5f};\n  }\n  return pos;\n}\n\nvoid CMetroid::SelectTarget(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x568_state = x9bf_27_ ? EState::Over : EState::One;\n    if (x7b0_attackTarget == kInvalidUniqueId) {\n      CPlayer& player = mgr.GetPlayer();\n      const zeus::CVector3f& pos = GetTranslation();\n      float playerDistSq = (player.GetTranslation() - pos).magSquared();\n      x7b0_attackTarget = player.GetUniqueId();\n      if (!x450_bodyController->HasBeenFrozen()) {\n        float range = std::max(x3bc_detectionRange, std::sqrt(playerDistSq));\n        EntityList nearList;\n        mgr.BuildNearList(nearList, zeus::CAABox{pos - range, pos + range},\n                          CMaterialFilter::MakeInclude({EMaterialTypes::Character}), nullptr);\n        CSpacePirate* closestPirate = nullptr;\n        for (const auto& id : nearList) {\n          if (auto* pirate = CPatterned::CastTo<CSpacePirate>(mgr.ObjectById(id))) {\n            if (IsPirateValidTarget(pirate, mgr)) {\n              float distSq = (pirate->GetTranslation() - pos).magSquared();\n              if (distSq < playerDistSq) {\n                closestPirate = pirate;\n                playerDistSq = distSq;\n              }\n            }\n          }\n        }\n        if (closestPirate != nullptr) {\n          x7b0_attackTarget = closestPirate->GetUniqueId();\n          closestPirate->SetAttackTarget(GetUniqueId());\n        }\n      }\n    }\n    if (auto* pirate = CPatterned::CastTo<CSpacePirate>(mgr.ObjectById(x7b0_attackTarget))) {\n      mgr.SendScriptMsg(pirate, GetUniqueId(), EScriptObjectMessage::Alert);\n    }\n  } else if (msg == EStateMsg::Update) {\n    if (x568_state == EState::One) {\n      if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::Generate) {\n        x568_state = EState::Two;\n      } else {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCGenerateCmd(pas::EGenerateType::Three, zeus::skZero3f));\n      }\n    } else if (x568_state == EState::Two) {\n      if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::Generate) {\n        if (x7b0_attackTarget != kInvalidUniqueId) {\n          if (TCastToConstPtr<CActor> actor = mgr.GetObjectById(x7b0_attackTarget)) {\n            x450_bodyController->GetCommandMgr().DeliverTargetVector(actor->GetTranslation() - GetTranslation());\n          }\n        }\n      } else {\n        x568_state = EState::Over;\n      }\n    }\n  }\n}\n\nvoid CMetroid::Touch(CActor& act, CStateManager& mgr) {\n  if (IsAlive()) {\n    if (TCastToPtr<CGameProjectile> proj = act) {\n      if (proj->GetOwnerId() == mgr.GetPlayer().GetUniqueId() && x3fc_flavor != CPatterned::EFlavorType::Two &&\n          proj->HasAttrib(EProjectileAttrib::Ice)) {\n        if (GetDamageVulnerability()->WeaponHits(CWeaponMode{EWeaponType::Ice, false}, false)) {\n          float timeScale = proj->HasAttrib(EProjectileAttrib::Charged) ? 2.f : 1.f;\n          const zeus::CVector3f& projPos = proj->GetTranslation();\n          Freeze(mgr, projPos - GetTranslation(), x34_transform.transposeRotate(projPos - proj->GetPreviousPos()),\n                 timeScale * x4fc_freezeDur);\n        }\n      }\n      x9bf_26_shotAt = true;\n    }\n    CPatterned::Touch(act, mgr);\n  }\n}\n\nbool CMetroid::IsPirateValidTarget(const CSpacePirate* target, CStateManager& mgr) {\n  if (target->GetAttachedActor() == kInvalidUniqueId && target->IsTrooper()) {\n    const CHealthInfo* healthInfo = target->GetHealthInfo(mgr);\n    if (healthInfo != nullptr && healthInfo->GetHP() > 0.f) {\n      return true;\n    }\n  }\n  return false;\n}\n\nvoid CMetroid::UpdateAttackChance(CStateManager& mgr, float dt) {\n  if (IsAttackInProgress(mgr)) {\n    x7b4_attackChance = x308_attackTimeVariation * mgr.GetActiveRandom()->Float() + x304_averageAttackTime;\n  } else if (x7b4_attackChance > 0.f) {\n    float delta = dt;\n    if (mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed) {\n      delta = 2.f * dt;\n    }\n    x7b4_attackChance -= delta;\n  }\n}\n\nbool CMetroid::IsAttackInProgress(CStateManager& mgr) {\n  if (TCastToConstPtr<CActor> actor = mgr.GetObjectById(x7b0_attackTarget)) {\n    if (x7b0_attackTarget == mgr.GetPlayer().GetUniqueId()) {\n      const TUniqueId attachedActor = mgr.GetPlayer().GetAttachedActor();\n      if (attachedActor != kInvalidUniqueId && attachedActor != GetUniqueId()) {\n        return true;\n      }\n    } else if (const auto* pirate = CPatterned::CastTo<CSpacePirate>(actor.GetPtr())) {\n      const TUniqueId attachedActor = pirate->GetAttachedActor();\n      if (attachedActor != kInvalidUniqueId && attachedActor != GetUniqueId()) {\n        return true;\n      }\n    }\n  }\n  return false;\n}\n\nvoid CMetroid::SuckEnergyFromTarget(CStateManager& mgr, float dt) {\n  x9c0_24_isEnergyDrainVulnerable = false;\n  if (x7b0_attackTarget == kInvalidUniqueId) {\n    return;\n  }\n  if (x7c8_attackState == EAttackState::Attached) {\n    InterpolateToPosRot(mgr, 0.4f);\n    CPlayer& player = mgr.GetPlayer();\n    if (x7b0_attackTarget == player.GetUniqueId()) {\n      x402_28_isMakingBigStrike = true;\n      x504_damageDur = 0.2f;\n      mgr.SendScriptMsg(&player, GetUniqueId(), EScriptObjectMessage::Damage);\n    }\n    x7c0_energyDrainTime = 0.f;\n  } else if (x7c8_attackState == EAttackState::Draining) {\n    CPlayer& player = mgr.GetPlayer();\n    if (TCastToPtr<CActor> actor = mgr.ObjectById(x7b0_attackTarget)) {\n      CHealthInfo* healthInfo = actor->HealthInfo(mgr);\n      if (healthInfo != nullptr) {\n        const float damage = dt * x56c_data.GetEnergyDrainPerSec() * GetDamageMultiplier();\n        x7bc_energyDrained += damage;\n        if (x7b0_attackTarget == player.GetUniqueId()) {\n          player.SetNoDamageLoopSfx(true);\n          constexpr auto filter = CMaterialFilter::MakeInclude({EMaterialTypes::Solid});\n          constexpr CWeaponMode mode{EWeaponType::PoisonWater};\n          CDamageInfo info{mode, damage, 0.f, 0.f};\n          info.SetNoImmunity(true);\n          mgr.ApplyDamage(GetUniqueId(), x7b0_attackTarget, GetUniqueId(), info, filter, zeus::skZero3f);\n          player.SetNoDamageLoopSfx(false);\n          x9c0_24_isEnergyDrainVulnerable =\n              player.GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed;\n        } else {\n          x9c0_24_isEnergyDrainVulnerable = true;\n          constexpr auto filter = CMaterialFilter::MakeInclude({EMaterialTypes::Solid});\n          constexpr CWeaponMode mode{EWeaponType::Power};\n          CDamageInfo info{mode, damage, 0.f, 0.f};\n          info.SetNoImmunity(true);\n          mgr.ApplyDamage(GetUniqueId(), x7b0_attackTarget, GetUniqueId(), info, filter, zeus::skZero3f);\n        }\n        if (GetGrowthStage() >= 2.f) {\n          TakeDamage(zeus::skZero3f, 0.f);\n        } else {\n          ApplyGrowth(damage);\n        }\n      }\n    }\n    float arg = 0.95f;\n    if (x7b0_attackTarget == player.GetUniqueId()) {\n      auto morphBallState = player.GetMorphballTransitionState();\n      if (morphBallState != CPlayer::EPlayerMorphBallState::Unmorphed &&\n          morphBallState != CPlayer::EPlayerMorphBallState::Morphed) {\n        arg = 0.4f;\n      }\n      if (morphBallState == CPlayer::EPlayerMorphBallState::Unmorphed) {\n        const float magnitude = std::clamp(std::abs(std::sin(zeus::degToRad(90.f) * x7c0_energyDrainTime)), 0.f, 1.f);\n        mgr.GetPlayerState()->GetStaticInterference().AddSource(GetUniqueId(), magnitude, 0.2f);\n        if (player.GetStaticTimer() < 0.2f) {\n          player.SetHudDisable(0.2f, 0.5f, 2.5f);\n        }\n      }\n      x402_28_isMakingBigStrike = true;\n      x504_damageDur = 0.2f;\n    }\n    InterpolateToPosRot(mgr, arg);\n    x7c0_energyDrainTime += dt;\n  } else if (x7c8_attackState == EAttackState::Over) {\n    const zeus::CQuaternion zRot = zeus::CQuaternion::fromAxisAngle({0.0f, 0.0f, 1.0f}, GetYaw());\n    const zeus::CQuaternion rot = zeus::CQuaternion::slerpShort(GetTransform().basis, zRot, 0.95f);\n    SetRotation(rot.normalized());\n  }\n}\n\nvoid CMetroid::RestoreSolidCollision(CStateManager& mgr) {\n  constexpr auto filter = CMaterialFilter::MakeInclude({EMaterialTypes::Solid, EMaterialTypes::AIBlock});\n  const zeus::CVector3f& pos = GetTranslation();\n  if (x9bf_30_restoreSolidCollision &&\n      !CGameCollision::DetectStaticCollisionBoolean(mgr, x6a0_collisionPrimitive, GetTransform(), filter)) {\n    bool add = true;\n    if (!x80c_detachPos.isZero()) {\n      const zeus::CVector3f dir = pos - x80c_detachPos;\n      float mag = dir.magnitude();\n      if (mag > 0.f) {\n        add = mgr.RayStaticIntersection(x80c_detachPos, (1.f / mag) * dir, mag, filter).IsInvalid();\n      }\n    }\n    if (add) {\n      AddMaterial(EMaterialTypes::Solid, mgr);\n      x9bf_30_restoreSolidCollision = false;\n    }\n  }\n  if (x9bf_31_restoreCharacterCollision) {\n    constexpr auto nearFilter =\n        CMaterialFilter::MakeInclude({EMaterialTypes::Solid, EMaterialTypes::Player, EMaterialTypes::Character});\n    float radius = x808_loopAttackDistance * GetModelData()->GetScale().y();\n    const zeus::CAABox box{pos - radius, pos + radius};\n    EntityList nearList;\n    mgr.BuildNearList(nearList, box, nearFilter, this);\n    if (!CGameCollision::DetectDynamicCollisionBoolean(x6a0_collisionPrimitive, GetTransform(), nearList, mgr)) {\n      x9bf_31_restoreCharacterCollision = false;\n      CMaterialFilter matFilter = GetMaterialFilter();\n      matFilter.ExcludeList().Remove({EMaterialTypes::Character, EMaterialTypes::Player});\n      SetMaterialFilter(matFilter);\n    }\n  }\n}\n\nvoid CMetroid::PreventWorldCollisions(CStateManager& mgr, float dt) {\n  float size = 2.f * x6a0_collisionPrimitive.GetSphere().radius;\n  if (IsSuckingEnergy()) {\n    if (x7b0_attackTarget == mgr.GetPlayer().GetUniqueId()) {\n      if (TCastToPtr<CPhysicsActor> actor = mgr.ObjectById(x7b0_attackTarget)) {\n        // why not use mgr.GetPlayer()? :thonking:\n        float mass = 300.f;\n        if (mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Unmorphed) {\n          float scale = std::min(1.f, 1.33f * x7c0_energyDrainTime);\n          mass = 300.f * (1.f - scale) + 7500.f * scale;\n        }\n        CGameCollision::AvoidStaticCollisionWithinRadius(mgr, *actor, 8, dt, 0.25f, size, mass, 0.5f);\n      }\n    }\n    x7c4_ = 0.f;\n  } else if (!x9bf_30_restoreSolidCollision && !x9bf_31_restoreCharacterCollision) {\n    x7c4_ = 0.f;\n  } else {\n    x7c4_ += dt;\n    if (x7c4_ <= 6.f) {\n      if (x9bf_30_restoreSolidCollision && 0.25f < x7c4_) {\n        RemoveMaterial(EMaterialTypes::Solid, mgr);\n      }\n    } else {\n      MassiveDeath(mgr);\n    }\n    CGameCollision::AvoidStaticCollisionWithinRadius(mgr, *this, 8, dt, 0.25f, size, 15000.f, 0.5f);\n  }\n}\n\nvoid CMetroid::SwarmRemove(CStateManager& mgr) {\n  if (x698_teamAiMgrId == kInvalidUniqueId) {\n    return;\n  }\n  if (TCastToPtr<CTeamAiMgr> aiMgr = mgr.ObjectById(x698_teamAiMgrId)) {\n    if (aiMgr->IsPartOfTeam(GetUniqueId())) {\n      aiMgr->RemoveTeamAiRole(GetUniqueId());\n    }\n  }\n}\n\nvoid CMetroid::SwarmAdd(CStateManager& mgr) {\n  if (x698_teamAiMgrId == kInvalidUniqueId) {\n    return;\n  }\n  if (TCastToPtr<CTeamAiMgr> aiMgr = mgr.ObjectById(x698_teamAiMgrId)) {\n    if (!aiMgr->IsPartOfTeam(GetUniqueId())) {\n      aiMgr->AssignTeamAiRole(*this, CTeamAiRole::ETeamAiRole::Melee, CTeamAiRole::ETeamAiRole::Invalid,\n                              CTeamAiRole::ETeamAiRole::Invalid);\n    }\n  }\n}\n\nvoid CMetroid::ApplyGrowth(float arg) {\n  x7f8_growthEnergy += arg;\n  const float energy = std::clamp(x7f8_growthEnergy / x56c_data.GetStage2GrowthEnergy(), 0.f, 1.f);\n  const float scale = x56c_data.GetStage2GrowthScale() - x7e8_scale3.y();\n  x7d0_scale1 = zeus::CVector3f{energy * scale + x7e8_scale3.y()};\n  TakeDamage(zeus::skZero3f, 0.f);\n}\n\nbool CMetroid::IsSuckingEnergy() const {\n  return x7c8_attackState == EAttackState::Draining && !x450_bodyController->IsFrozen();\n}\n\nvoid CMetroid::UpdateVolume() { SetVolume(0.25f * std::clamp(GetGrowthStage() - 1.f, 0.f, 1.f) + 0.75f); }\n\nvoid CMetroid::UpdateTouchBounds() {\n  const zeus::CTransform locXf = GetLocatorTransform(\"lockon_target_LCTR\"sv);\n  x6a0_collisionPrimitive.SetSphereCenter(locXf * GetModelData()->GetScale());\n}\n\nvoid CMetroid::Attack(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    if (AttachToTarget(mgr)) {\n      x568_state = EState::One;\n      x7bc_energyDrained = 0.f;\n      x7c8_attackState = EAttackState::Attached;\n      x9bf_29_isAttacking = true;\n      RemoveMaterial(EMaterialTypes::Orbit, EMaterialTypes::Target, mgr);\n      mgr.GetPlayer().TryToBreakOrbit(GetUniqueId(), CPlayer::EPlayerOrbitRequest::ActivateOrbitSource, mgr);\n      DisableSolidCollision(this);\n      AddMaterial(EMaterialTypes::Trigger, mgr);\n    } else {\n      x568_state = EState::Over;\n    }\n  } else if (msg == EStateMsg::Update) {\n    if (x568_state == EState::One) {\n      if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::LoopAttack) {\n        x568_state = EState::Two;\n      } else {\n        GetBodyController()->GetCommandMgr().DeliverCmd(CBCLoopAttackCmd(pas::ELoopAttackType::Three));\n      }\n    } else if (x568_state == EState::Two) {\n      if (GetModelData()->GetAnimationData()->GetIsLoop()) {\n        x7c8_attackState = EAttackState::Draining;\n      }\n      const CPlayer& player = mgr.GetPlayer();\n      if (x7b0_attackTarget == player.GetUniqueId() && player.GetAttachedActor() == GetUniqueId() &&\n          player.GetAreaIdAlways() != GetAreaIdAlways()) {\n        DetachFromTarget(mgr);\n        x401_30_pendingDeath = true;\n      } else if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::LoopAttack) {\n        if (ShouldReleaseFromTarget(mgr)) {\n          GetBodyController()->GetCommandMgr().DeliverCmd(CBodyStateCmd(EBodyStateCmd::ExitState));\n          DetachFromTarget(mgr);\n          x7c8_attackState = EAttackState::Over;\n        }\n      } else {\n        x568_state = EState::Over;\n      }\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    CTeamAiMgr::ResetTeamAiRole(CTeamAiMgr::EAttackType::Melee, mgr, x698_teamAiMgrId, GetUniqueId(), false);\n    x7b4_attackChance = x308_attackTimeVariation * mgr.GetActiveRandom()->Float() + x304_averageAttackTime;\n    x7c8_attackState = EAttackState::None;\n    DetachFromTarget(mgr);\n    x9bf_29_isAttacking = false;\n    SetTransform({zeus::CQuaternion::fromAxisAngle({0.f, 0.f, 1.f}, GetYaw()), GetTranslation()});\n    AddMaterial(EMaterialTypes::Orbit, EMaterialTypes::Target, mgr);\n    RemoveMaterial(EMaterialTypes::Trigger, mgr);\n  }\n}\n\nbool CMetroid::AttachToTarget(CStateManager& mgr) {\n  CPlayer& player = mgr.GetPlayer();\n  if (x7b0_attackTarget == player.GetUniqueId()) {\n    if (player.AttachActorToPlayer(GetUniqueId(), false)) {\n      player.GetEnergyDrain().AddEnergyDrainSource(GetUniqueId(), 1.f);\n      return true;\n    }\n    return false;\n  }\n  return PreDamageSpacePirate(mgr);\n}\n\nvoid CMetroid::DetachFromTarget(CStateManager& mgr) {\n  CPlayer& player = mgr.GetPlayer();\n  CActor* target = nullptr;\n  zeus::CVector3f vec;\n  zeus::CTransform xf;\n  if (x7b0_attackTarget == player.GetUniqueId()) {\n    if (player.GetAttachedActor() == GetUniqueId()) {\n      player.DetachActorFromPlayer();\n      if (player.GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed) {\n        const auto q1 = zeus::CQuaternion::fromAxisAngle({0.f, 0.f, 1.f}, M_PIF);\n        const auto q2 = zeus::CQuaternion::fromAxisAngle({0.f, 0.f, 1.f}, GetYaw());\n        const auto mat = zeus::CMatrix3f{q2 * q1};\n        vec = mat * zeus::skForward;\n        xf = zeus::CTransform{mat, player.GetTranslation()};\n      } else {\n        vec = player.GetTransform().frontVector();\n        xf = player.GetTransform();\n      }\n      mgr.GetPlayer().GetEnergyDrain().RemoveEnergyDrainSource(GetUniqueId());\n      x80c_detachPos = player.GetAimPosition(mgr, 0.f);\n      target = &player;\n    }\n  } else if (x7b0_attackTarget != kInvalidUniqueId) {\n    if (auto* pirate = CPatterned::CastTo<CSpacePirate>(mgr.ObjectById(x7b0_attackTarget))) {\n      if (pirate->GetAttachedActor() == GetUniqueId()) {\n        pirate->DetachActorFromPirate();\n        vec = pirate->GetTransform().frontVector();\n        xf = pirate->GetTransform();\n        x80c_detachPos = GetTranslation();\n        target = pirate;\n      }\n    }\n  }\n  SetupExitFaceHugDirection(target, mgr, vec, xf);\n  x9bf_31_restoreCharacterCollision = true;\n  x9bf_30_restoreSolidCollision = true;\n}\n\nbool CMetroid::ShouldReleaseFromTarget(CStateManager& mgr) {\n  if (x450_bodyController->IsFrozen()) {\n    return true;\n  }\n  CPlayer& player = mgr.GetPlayer();\n  if (x7b0_attackTarget == player.GetUniqueId()) {\n    if (x7bc_energyDrained >= x56c_data.GetMaxEnergyDrainAllowed() * GetDamageMultiplier() || IsPlayerUnderwater(mgr)) {\n      return true;\n    }\n    if (player.GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Unmorphed) {\n      return IsHunterAttacking(mgr);\n    }\n  } else if (x7b0_attackTarget != kInvalidUniqueId) {\n    if (const auto* pirate = CPatterned::CastTo<CSpacePirate>(mgr.GetObjectById(x7b0_attackTarget))) {\n      if (!pirate->AllEnergyDrained() && !pirate->GetBodyController()->GetBodyStateInfo().GetCurrentState()->IsDead()) {\n        return false;\n      }\n    }\n    return true;\n  }\n  return false;\n}\n\nvoid CMetroid::DisableSolidCollision(CMetroid* target) {\n  CMaterialFilter filter = target->GetMaterialFilter();\n  filter.ExcludeList().Add({EMaterialTypes::Character, EMaterialTypes::Player});\n  target->SetMaterialFilter(filter);\n}\n\nvoid CMetroid::SetupExitFaceHugDirection(CActor* actor, CStateManager& mgr, const zeus::CVector3f& vec,\n                                         const zeus::CTransform& xf) {\n  if (actor == nullptr || x7c8_attackState == EAttackState::Over) {\n    return;\n  }\n  // TODO\n}\n\nbool CMetroid::PreDamageSpacePirate(CStateManager& mgr) {\n  // TODO\n  return false;\n}\n\nbool CMetroid::IsPlayerUnderwater(CStateManager& mgr) {\n  CPlayer& player = mgr.GetPlayer();\n  if (player.GetFluidCounter() != 0) {\n    const TUniqueId fluidId = player.GetFluidId();\n    if (fluidId != kInvalidUniqueId) {\n      const zeus::CVector3f aimPos = player.GetAimPosition(mgr, 0.f);\n      if (TCastToConstPtr<CScriptWater> water = mgr.GetObjectById(fluidId)) {\n        const zeus::CAABox triggerBounds = water->GetTriggerBoundsWR();\n        return aimPos.z() < triggerBounds.max.z();\n      }\n    }\n    return true;\n  }\n  return false;\n}\n\nbool CMetroid::IsHunterAttacking(CStateManager& mgr) {\n  if (TCastToConstPtr<CTeamAiMgr> aiMgr = mgr.GetObjectById(x698_teamAiMgrId)) {\n    if (!aiMgr->HasRangedAttackers()) {\n      return false;\n    }\n    for (const auto& id : aiMgr->GetRangedAttackers()) {\n      if (CPatterned::CastTo<CMetroidBeta>(mgr.GetObjectById(id)) != nullptr) {\n        return true;\n      }\n    }\n  }\n  return false;\n}\n\nfloat CMetroid::GetGrowthStage() {\n  const float energy = x7f8_growthEnergy;\n  const float stage2GrowthEnergy = x56c_data.GetStage2GrowthEnergy();\n  if (energy < stage2GrowthEnergy) {\n    return 1.f + energy / stage2GrowthEnergy;\n  }\n  const float explosionGrowthEnergy = x56c_data.GetExplosionGrowthEnergy();\n  if (energy < explosionGrowthEnergy) {\n    return 2.f + (energy - stage2GrowthEnergy) / (explosionGrowthEnergy - stage2GrowthEnergy);\n  }\n  return 3.f;\n}\n\nbool CMetroid::ShouldAttack(CStateManager& mgr, float arg) {\n  if (!CanAttack(mgr)) {\n    return false;\n  }\n  if (TCastToPtr<CTeamAiMgr> aiMgr = mgr.ObjectById(x698_teamAiMgrId)) {\n    return aiMgr->AddMeleeAttacker(GetUniqueId());\n  }\n  return true;\n}\n\nbool CMetroid::CanAttack(CStateManager& mgr) {\n  if (x7b4_attackChance <= 0.f) {\n    CPlayer& player = mgr.GetPlayer();\n    if (x7b0_attackTarget == player.GetUniqueId()) {\n      if (IsPlayerUnderwater(mgr) ||\n          (player.GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed &&\n           player.GetMorphBall()->GetSpiderBallState() == CMorphBall::ESpiderBallState::Active)) {\n        return false;\n      }\n      if (player.GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Morphed && IsHunterAttacking(mgr)) {\n        return false;\n      }\n    }\n    const CEntity* target = mgr.GetObjectById(x7b0_attackTarget);\n    if (target != nullptr && target->GetAreaIdAlways() == GetAreaIdAlways()) {\n      return !IsAttackInProgress(mgr);\n    }\n  }\n  return false;\n}\n\nvoid CMetroid::PathFind(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    GetBodyController()->SetLocomotionType(pas::ELocomotionType::Lurk);\n    GetBodyController()->GetCommandMgr().SetSteeringBlendMode(ESteeringBlendMode::FullSpeed);\n    GetBodyController()->GetCommandMgr().SetSteeringSpeedRange(1.f, 1.f);\n    SetUpPathFindBehavior(mgr);\n  } else if (msg == EStateMsg::Update) {\n    const auto* searchPath = GetSearchPath();\n    if (searchPath == nullptr || PathShagged(mgr, 0.f) || PathOver(mgr, 0.f)) {\n      const auto* aiRole = CTeamAiMgr::GetTeamAiRole(mgr, x698_teamAiMgrId, GetUniqueId());\n      if (aiRole == nullptr) {\n        x7a4_ = GetOrigin(mgr, CTeamAiRole{GetUniqueId()}, GetAttackTargetPos(mgr));\n      } else {\n        x7a4_ = aiRole->GetTeamPosition();\n      }\n      ApplyForwardSteering(mgr, x7a4_);\n    } else {\n      CPatterned::PathFind(mgr, msg, arg);\n    }\n    ApplySeparationBehavior(mgr, 9.f);\n  } else if (msg == EStateMsg::Deactivate) {\n    GetBodyController()->GetCommandMgr().SetSteeringBlendMode(ESteeringBlendMode::Normal);\n  }\n}\n\nvoid CMetroid::ApplyForwardSteering(CStateManager& mgr, const zeus::CVector3f& vec) {\n  if ((vec - GetTranslation()).magSquared() <= 4.f) {\n    if (ShouldTurn(mgr, 0.f) && x7b0_attackTarget != kInvalidUniqueId) {\n      if (TCastToConstPtr<CActor> actor = mgr.GetObjectById(x7b0_attackTarget)) {\n        const zeus::CVector3f dir = actor->GetTranslation() - GetTranslation();\n        if (dir.canBeNormalized()) {\n          GetBodyController()->GetCommandMgr().DeliverCmd(CBCLocomotionCmd{zeus::skZero3f, dir.normalized(), 1.f});\n        }\n      }\n    }\n  } else {\n    const zeus::CVector3f arrival = x45c_steeringBehaviors.Arrival(*this, GetTranslation().toVec2f(), 0.5f);\n    if (arrival.magSquared() > 0.01f) {\n      GetBodyController()->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(arrival, zeus::skZero3f, 3.f));\n    }\n    if (TCastToConstPtr<CPhysicsActor> actor = mgr.GetObjectById(x7b0_attackTarget)) {\n      const auto move = x45c_steeringBehaviors.Pursuit(*this, vec, actor->GetVelocity());\n      GetBodyController()->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(move, zeus::skZero3f, 1.f));\n    } else {\n      const auto move = x45c_steeringBehaviors.Seek(*this, vec);\n      GetBodyController()->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(move, zeus::skZero3f, 1.f));\n    }\n  }\n}\n\nvoid CMetroid::ApplySeparationBehavior(CStateManager& mgr, float arg) {\n  // TODO\n}\n\nvoid CMetroid::SetUpPathFindBehavior(CStateManager& mgr) {\n  x9bf_28_ = false;\n  if (GetSearchPath() == nullptr) {\n    return;\n  }\n  if (const auto* role = CTeamAiMgr::GetTeamAiRole(mgr, x698_teamAiMgrId, GetUniqueId())) {\n    SetDestPos(role->GetTeamPosition());\n  } else {\n    SetDestPos(GetOrigin(mgr, CTeamAiRole{GetUniqueId()}, mgr.GetPlayer().GetTranslation()));\n  }\n  const zeus::CVector3f targetPos = GetAttackTargetPos(mgr);\n  const zeus::CVector3f dir = GetDestPos() - targetPos;\n  if (dir.canBeNormalized()) {\n    constexpr auto filter = CMaterialFilter::MakeInclude({EMaterialTypes::Solid, EMaterialTypes::AIBlock});\n    const float length = dir.magnitude();\n    const zeus::CVector3f dirScaled = (1.f / length) * dir;\n    const auto result = mgr.RayStaticIntersection(targetPos, dirScaled, length, filter);\n    if (result.IsValid()) {\n      SetDestPos(targetPos + 0.5f * result.GetT() * dirScaled);\n      x9bf_28_ = true;\n    }\n  }\n  x7a4_ = GetDestPos();\n  CPatterned::PathFind(mgr, EStateMsg::Activate, 0.f);\n}\n\nzeus::CVector3f CMetroid::GetAttackTargetPos(CStateManager& mgr) {\n  const TUniqueId targetId = x7b0_attackTarget;\n  if (targetId != kInvalidUniqueId) {\n    CPlayer& player = mgr.GetPlayer();\n    if (targetId == player.GetUniqueId()) {\n      if (player.GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed) {\n        return player.GetMorphBall()->GetBallToWorld().origin;\n      }\n      return player.GetTranslation() + zeus::CVector3f{0.f, 0.f, -0.6f + player.GetEyeHeight()};\n    }\n    if (TCastToConstPtr<CActor> actor = mgr.GetObjectById(targetId)) {\n      const auto locXf = actor->GetLocatorTransform(skPirateSuckJoint);\n      return actor->GetTranslation() +\n             zeus::CVector3f{0.f, 0.f, locXf.origin.z() * actor->GetModelData()->GetScale().z() + 0.4f};\n    }\n  }\n  return mgr.GetPlayer().GetAimPosition(mgr, 0.f);\n}\n\nbool CMetroid::AggressionCheck(CStateManager& mgr, float arg) {\n  if (x7b0_attackTarget != kInvalidUniqueId) {\n    CEntity* target = mgr.ObjectById(x7b0_attackTarget);\n    if (auto* pirate = CPatterned::CastTo<CSpacePirate>(target)) {\n      if (!IsPirateValidTarget(pirate, mgr)) {\n        x7b0_attackTarget = kInvalidUniqueId;\n        return false;\n      }\n    }\n    if (TCastToPtr<CActor> actor = target) {\n      const zeus::CVector3f dir = actor->GetTranslation() - GetTranslation();\n      if (x3bc_detectionRange * x3bc_detectionRange > dir.magSquared()) {\n        if (x3c0_detectionHeightRange > 0.f) {\n          return dir.z() * dir.z() < x3c0_detectionHeightRange * x3c0_detectionHeightRange;\n        }\n        return true;\n      }\n    } else {\n      x7b0_attackTarget = kInvalidUniqueId;\n    }\n  }\n  return false;\n}\n\nvoid CMetroid::Dodge(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    if (x818_dodgeDirection != pas::EStepDirection::Invalid) {\n      GetBodyController()->GetCommandMgr().DeliverCmd(CBCStepCmd(x818_dodgeDirection, pas::EStepType::Dodge));\n    }\n  } else if (msg == EStateMsg::Update) {\n    if (GetBodyController()->GetCurrentStateId() == pas::EAnimationState::Step) {\n      if (x7b0_attackTarget != kInvalidUniqueId) {\n        if (TCastToConstPtr<CActor> actor = mgr.GetObjectById(x7b0_attackTarget)) {\n          GetBodyController()->GetCommandMgr().DeliverTargetVector(actor->GetTranslation() - GetTranslation());\n        }\n      }\n    } else {\n      x568_state = EState::Over;\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x818_dodgeDirection = pas::EStepDirection::Invalid;\n  }\n}\n\nvoid CMetroid::Death(CStateManager& mgr, const zeus::CVector3f& direction, EScriptObjectState state) {\n  CPatterned::Death(mgr, direction, state);\n  x328_25_verticalMovement = false;\n  SetMuted(true);\n  SwarmRemove(mgr);\n}\n\nvoid CMetroid::Generate(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    if (ShouldSpawnGammaMetroid()) {\n      SpawnGammaMetroid(mgr);\n    } else if (x7f8_growthEnergy >= x56c_data.GetExplosionGrowthEnergy()) {\n      MassiveDeath(mgr);\n    }\n    x568_state = EState::One;\n    x7dc_scale2 = GetModelData()->GetScale();\n    x9bf_25_growing = true;\n  } else if (msg == EStateMsg::Update) {\n    CBodyController* bodyController = GetBodyController();\n    if (x568_state == EState::One) {\n      if (bodyController->GetCurrentStateId() == pas::EAnimationState::Generate) {\n        x7f4_growthDuration = bodyController->GetAnimTimeRemaining();\n        x568_state = x7f4_growthDuration > 0.f ? EState::Two : EState::Over;\n      } else if (Attacked(mgr, 0.f)) {\n        bodyController->GetCommandMgr().DeliverCmd(CBCGenerateCmd(pas::EGenerateType::Two));\n      } else {\n        bodyController->GetCommandMgr().DeliverCmd(CBCGenerateCmd(pas::EGenerateType::Seven));\n      }\n    } else if (x568_state == EState::Two) {\n      if (bodyController->GetCurrentStateId() == pas::EAnimationState::Generate) {\n        bool inside = false;\n        if (!bodyController->IsFrozen() && ((inside = Inside(mgr, 3.f)) || Attacked(mgr, 0.f))) {\n          float timeRem = bodyController->GetAnimTimeRemaining();\n          float clamp = std::clamp(1.f - (timeRem / x7f4_growthDuration), 0.f, 1.f);\n          zeus::CVector3f scale;\n          if (0.25f <= clamp) {\n            float dVar13 = 0.75f * x7f4_growthDuration;\n            const zeus::CVector3f v = 0.5f * x7dc_scale2;\n            scale = v + (dVar13 - timeRem) * (1.f / dVar13) * (x7d0_scale1 - v);\n          } else {\n            scale = std::clamp(1.f - 0.5f * (clamp / 0.25f), 0.f, 1.f) * x7dc_scale2;\n          }\n          if (inside) {\n            ApplySplitGammas(mgr, arg);\n          }\n          GetModelData()->SetScale(scale);\n          UpdateVolume();\n        }\n      } else {\n        x568_state = EState::Over;\n      }\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x7f4_growthDuration = 0.f;\n    x9bf_25_growing = false;\n    if (Attacked(mgr, 0.f)) {\n      x7fc_lastGrowthEnergy = x7f8_growthEnergy;\n      GetModelData()->SetScale(x7d0_scale1);\n    }\n    UpdateVolume();\n  }\n}\n\nbool CMetroid::ShouldSpawnGammaMetroid() {\n  // TODO\n  return false;\n}\n\nvoid CMetroid::SpawnGammaMetroid(CStateManager& mgr) {\n  // TODO\n}\n\nvoid CMetroid::ApplySplitGammas(CStateManager& mgr, float arg) {\n  auto* metroid = CPatterned::CastTo<CMetroid>(mgr.ObjectById(x9bc_parent));\n  if (metroid == nullptr) {\n    return;\n  }\n  // TODO\n}\n\nvoid CMetroid::KnockBack(const zeus::CVector3f& dir, CStateManager& mgr, const CDamageInfo& info, EKnockBackType type,\n                         bool inDeferred, float magnitude) {\n  if (!IsAlive()) {\n    return;\n  }\n  const CDamageVulnerability* vulnerability = GetDamageVulnerability();\n  float percentFrozen = GetBodyController()->GetPercentageFrozen();\n  const CWeaponMode& mode = info.GetWeaponMode();\n  if (x7c8_attackState == EAttackState::Draining) {\n    if (vulnerability->WeaponHits(mode, false)) {\n      x7bc_energyDrained = x56c_data.GetMaxEnergyDrainAllowed() * GetDamageMultiplier();\n    }\n  } else if (vulnerability->WeaponHurts(mode, false)) {\n    x7b4_attackChance = x308_attackTimeVariation * mgr.GetActiveRandom()->Float() + x304_averageAttackTime;\n    if (percentFrozen > 0.f) {\n      GetBodyController()->UnFreeze();\n    }\n    CPatterned::KnockBack(dir, mgr, info, type, inDeferred, magnitude);\n  } else if (percentFrozen <= 0.f && vulnerability->WeaponHits(mode, false) &&\n             (mode.IsCharged() || mode.IsComboed() || mode.GetType() == EWeaponType::Missile) &&\n             !ShouldSpawnGammaMetroid()) {\n    CPatterned::KnockBack(dir, mgr, info, type, inDeferred, magnitude);\n    x800_seekTime = x804_maxSeekTime;\n  }\n}\n\nbool CMetroid::Attacked(CStateManager& mgr, float arg) {\n  if (x7f8_growthEnergy - x7fc_lastGrowthEnergy > 0.f) {\n    if (x7fc_lastGrowthEnergy < x56c_data.GetStage2GrowthEnergy()) {\n      return x56c_data.GetStage2GrowthEnergy() <= x7f8_growthEnergy;\n    }\n    if (x56c_data.GetExplosionGrowthEnergy() <= x7f8_growthEnergy) {\n      return true;\n    }\n  }\n  return false;\n}\n\nbool CMetroid::AttackOver(CStateManager& mgr, float arg) {\n  if (x568_state != EState::Two || GetBodyController()->IsFrozen()) {\n    return false;\n  }\n  float posZ = GetTranslation().z();\n  const zeus::CVector3f targetPos = GetAttackTargetPos(mgr);\n  float scale = 0.8f * GetModelData()->GetScale().y();\n  if (zeus::close_enough(targetPos.z(), posZ, scale)) {\n    if (TCastToConstPtr<CPhysicsActor> actor = mgr.GetObjectById(x7b0_attackTarget)) {\n      const zeus::CAABox collisionBox = x6a0_collisionPrimitive.CalculateAABox(x34_transform);\n      const zeus::CAABox scaledBox = zeus::CAABox{collisionBox.min - scale, collisionBox.max + scale};\n      return scaledBox.intersects(actor->GetBoundingBox());\n    }\n  }\n  return false;\n}\n\nbool CMetroid::InAttackPosition(CStateManager& mgr, float arg) {\n  if (x7b0_attackTarget == kInvalidUniqueId) {\n    return false;\n  }\n  const auto* actor = static_cast<const CActor*>(mgr.GetObjectById(x7b0_attackTarget));\n  if (actor == nullptr || actor->GetAreaIdAlways() != GetAreaIdAlways()) {\n    return false;\n  }\n  CPlayer& player = mgr.GetPlayer();\n  const zeus::CVector3f& actorPos = actor->GetTranslation();\n  const zeus::CVector3f& pos = GetTranslation();\n  const zeus::CVector3f dir = pos - actorPos;\n  const zeus::CVector2f actorFrontXY = actor->GetTransform().frontVector().toVec2f();\n  float maxAngle = M_PIF;\n  if (x7b0_attackTarget == player.GetUniqueId()) {\n    if (IsPlayerUnderwater(mgr)) {\n      return false;\n    }\n    if (player.GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Morphed && !x9bf_29_isAttacking) {\n      maxAngle = zeus::degToRad(45.f);\n    }\n  }\n  if (zeus::CVector2f::getAngleDiff(dir.toVec2f(), actorFrontXY) < maxAngle &&\n      dir.dot(GetTransform().frontVector()) < 0.f) {\n    const zeus::CVector3f dir2 = x7a4_ - pos;\n    if (dir2.magSquared() < x300_maxAttackRange * x300_maxAttackRange && actorPos.z() < pos.z() &&\n        pos.z() < 0.5f + x7a4_.z()) {\n      zeus::CVector3f attackDir = GetAttackTargetPos(mgr) - pos;\n      if (attackDir.canBeNormalized()) {\n        constexpr auto filter = CMaterialFilter::MakeInclude({EMaterialTypes::Solid, EMaterialTypes::AIBlock});\n        float mag = attackDir.magnitude();\n        return mgr.RayStaticIntersection(pos, (1.f / mag) * attackDir, mag, filter).IsInvalid();\n      }\n    }\n  }\n  return false;\n}\n\nbool CMetroid::InDetectionRange(CStateManager& mgr, float arg) {\n  if (x7b0_attackTarget == kInvalidUniqueId) {\n    if ((x9bf_24_alert || CPatterned::InDetectionRange(mgr, arg)) && !IsPlayerUnderwater(mgr) &&\n        mgr.GetPlayer().GetAreaIdAlways() == GetAreaIdAlways()) {\n      return true;\n    }\n    // TODO attack pirates\n  } else if (x7b0_attackTarget != mgr.GetPlayer().GetUniqueId() ||\n             !(IsPlayerUnderwater(mgr) || mgr.GetPlayer().GetAreaIdAlways() != GetAreaIdAlways())) {\n    if (TCastToConstPtr<CActor> actor = mgr.GetObjectById(x7b0_attackTarget)) {\n      const zeus::CVector3f dir = actor->GetTranslation() - GetTranslation();\n      if (dir.magSquared() < x3bc_detectionRange * x3bc_detectionRange && x3c0_detectionHeightRange > 0.f) {\n        return x3c0_detectionHeightRange * x3c0_detectionHeightRange > dir.z() * dir.z();\n      }\n    }\n  }\n  return false;\n}\n\nvoid CMetroid::TelegraphAttack(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x568_state = EState::Zero;\n    x7b8_telegraphAttackTime = x56c_data.GetTelegraphAttackTime();\n    x800_seekTime = 0.f;\n    GetBodyController()->GetCommandMgr().ClearLocomotionCmds();\n    GetBodyController()->SetLocomotionType(pas::ELocomotionType::Combat);\n  } else if (msg == EStateMsg::Update) {\n    if (x568_state == EState::Zero) {\n      x7b8_telegraphAttackTime -= dt;\n      if (x7b8_telegraphAttackTime >= 0.f) {\n        if (TCastToConstPtr<CActor> actor = mgr.GetObjectById(x7b0_attackTarget)) {\n          const zeus::CVector3f face = actor->GetTranslation() - GetTranslation();\n          if (face.canBeNormalized()) {\n            GetBodyController()->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(zeus::skZero3f, face.normalized(), 1.f));\n          }\n        }\n      } else {\n        x568_state = EState::Two;\n        const float distance = 1.25f * (GetAttackTargetPos(mgr) - GetTranslation()).magnitude();\n        const float speed = x3b4_speed > 0.f ? 1.15f / x3b4_speed : 0.f;\n        x804_maxSeekTime = speed + (distance / GetBodyController()->GetBodyStateInfo().GetMaxSpeed());\n        GetBodyController()->SetTurnSpeed(x3b4_speed > 0.f ? 20.f / x3b4_speed : 20.f);\n      }\n    } else if (x568_state == EState::Two) {\n      x800_seekTime += dt;\n      const zeus::CVector3f move = x45c_steeringBehaviors.Seek(*this, GetAttackTargetPos(mgr));\n      GetBodyController()->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(move, zeus::skZero3f, 1.f));\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    GetBodyController()->SetTurnSpeed(x3b8_turnSpeed);\n    if (Attacked(mgr, 0.f) || PatternShagged(mgr, 0.f)) {\n      CTeamAiMgr::ResetTeamAiRole(CTeamAiMgr::EAttackType::Melee, mgr, x698_teamAiMgrId, GetUniqueId(), false);\n    }\n  }\n}\n\nvoid CMetroid::InterpolateToPosRot(CStateManager& mgr, float dt) {\n  zeus::CVector3f pos;\n  zeus::CQuaternion rot;\n  ComputeSuckTargetPosRot(mgr, pos, rot);\n  const float oneMinusDt = 1.f - dt;\n  const auto posInterp = GetTranslation() * oneMinusDt + pos * dt;\n  const auto quatInterp = zeus::CQuaternion::slerpShort(GetTransform().basis, rot, dt);\n  SetTransform(quatInterp.toTransform(posInterp));\n}\n\nvoid CMetroid::ComputeSuckTargetPosRot(CStateManager& mgr, zeus::CVector3f& outPos, zeus::CQuaternion& outRot) {\n  const auto& xf = GetTransform();\n  outPos = xf.origin;\n  outRot = xf.basis;\n  if (x7b0_attackTarget == mgr.GetPlayer().GetUniqueId()) {\n    ComputeSuckPlayerPosRot(mgr, outPos, outRot);\n  } else {\n    ComputeSuckPiratePosRot(mgr, outPos, outRot);\n  }\n}\n\nvoid CMetroid::ComputeSuckPlayerPosRot(CStateManager& mgr, zeus::CVector3f& outPos, zeus::CQuaternion& outRot) {\n  CPlayer& player = mgr.GetPlayer();\n  const auto& playerXf = player.GetTransform();\n  outPos = playerXf.origin;\n  const auto& scale = GetModelData()->GetScale();\n  const auto morphBallState = player.GetMorphballTransitionState();\n  if (morphBallState == CPlayer::EPlayerMorphBallState::Morphing) {\n    outPos += zeus::CVector3f{0.f, 0.f, 0.4f + ComputeMorphingPlayerSuckZPos(player)};\n    outPos += 0.5f * playerXf.frontVector() - player.GetMorphBall()->GetBallRadius() * GetTransform().upVector();\n    const auto xRot = zeus::CQuaternion::fromAxisAngle(zeus::skRight, zeus::degToRad(-90.f));\n    const auto yRot = zeus::CQuaternion::fromAxisAngle(zeus::skForward, 0.f);\n    const auto zRot = zeus::CQuaternion::fromAxisAngle(zeus::skUp, M_PIF);\n    outRot = zeus::CQuaternion{playerXf.basis} * (zRot * xRot * yRot);\n  } else if (morphBallState == CPlayer::EPlayerMorphBallState::Unmorphed) {\n    const zeus::CQuaternion camRot = mgr.GetCameraManager()->GetFirstPersonCamera()->GetTransform().basis;\n    outRot = camRot * zeus::CQuaternion::fromAxisAngle(zeus::skUp, M_PIF);\n    const zeus::CMatrix3f camMtx = camRot.toTransform().basis;\n    const zeus::CVector3f forward = camMtx * zeus::skForward;\n    const zeus::CVector3f up = (-0.6f * scale.y()) * (camMtx * zeus::skUp);\n    outPos += zeus::CVector3f{0.f, 0.f, player.GetEyeHeight()} + up + forward;\n  } else if (morphBallState == CPlayer::EPlayerMorphBallState::Morphed) {\n    const float ballRadius = player.GetMorphBall()->GetBallRadius();\n    outPos += (2.f * ballRadius + 0.25f) * zeus::skUp;\n    outPos -= ballRadius * (scale.y() * GetTransform().upVector());\n    const auto xRot = zeus::CQuaternion::fromAxisAngle(zeus::skRight, zeus::degToRad(-90.f));\n    const auto yRot = zeus::CQuaternion::fromAxisAngle(zeus::skForward, 0.f);\n    const auto zRot = zeus::CQuaternion::fromAxisAngle(zeus::skUp, GetYaw());\n    outRot = zRot * xRot * yRot;\n  } else if (morphBallState == CPlayer::EPlayerMorphBallState::Unmorphing) {\n    outPos += zeus::CVector3f{0.f, 0.f, 0.4f + ComputeMorphingPlayerSuckZPos(player)};\n    outPos += 0.5f * playerXf.frontVector() - player.GetMorphBall()->GetBallRadius() * GetTransform().upVector();\n    const auto xRot = zeus::CQuaternion::fromAxisAngle(zeus::skRight, zeus::degToRad(-90.f));\n    const auto yRot = zeus::CQuaternion::fromAxisAngle(zeus::skForward, 0.f);\n    const auto zRot = zeus::CQuaternion::fromAxisAngle(zeus::skUp, M_PIF);\n    outRot = zeus::CQuaternion{playerXf.basis} * (zRot * xRot * yRot);\n\n    float morphT = 0.f;\n    if (player.GetMorphDuration() != 0.f) {\n      morphT = std::clamp(player.GetMorphTime() / player.GetMorphDuration(), 0.f, 1.f);\n    }\n    if (morphT > 0.75f) {\n      const zeus::CQuaternion camRot = mgr.GetCameraManager()->GetFirstPersonCamera()->GetTransform().basis;\n      const zeus::CQuaternion rot = camRot * zeus::CQuaternion::fromAxisAngle(zeus::skUp, M_PIF);\n      const zeus::CMatrix3f camMtx = camRot.toTransform().basis;\n      const zeus::CVector3f forward = camMtx * zeus::skForward;\n      const zeus::CVector3f up = (-0.6f * scale.y()) * (camMtx * zeus::skUp);\n      const zeus::CVector3f pos = playerXf.origin + zeus::CVector3f{0.f, 0.f, player.GetEyeHeight()} + up + forward;\n      const float t = (morphT - 0.75f) / 0.25f;\n      outRot = zeus::CQuaternion::slerpShort(outRot, rot, t);\n      outPos = zeus::CVector3f::lerp(outPos, pos, t); // outPos * (1.f - t) + (pos * t);\n    }\n  }\n}\n\nvoid CMetroid::ComputeSuckPiratePosRot(CStateManager& mgr, zeus::CVector3f& outPos, zeus::CQuaternion& outRot) {\n  // TODO\n}\n\nfloat CMetroid::ComputeMorphingPlayerSuckZPos(const CPlayer& player) const {\n  float ret = 0.f;\n  const CModelData* modelData = player.GetModelData();\n  const float scaleZ = modelData->GetScale().z();\n  if (modelData != nullptr && modelData->GetAnimationData() != nullptr) { // && modelData->GetNormalModel() ?\n    for (const auto& joint : skJointNameList) {\n      const zeus::CTransform xf = player.GetLocatorTransform(joint);\n      const float z = xf.origin.z() * scaleZ;\n      if (z > ret) {\n        ret = z;\n      }\n    }\n  }\n  return ret;\n}\n\nbool CMetroid::InPosition(CStateManager& mgr, float arg) {\n  CPathFindSearch* searchPath = GetSearchPath();\n  if (searchPath != nullptr) {\n    return searchPath->GetCurrentWaypoint() < searchPath->GetWaypoints().size() - 1;\n  }\n  return (x7a4_ - GetTranslation()).magSquared() < 4.f;\n}\n\nbool CMetroid::InRange(CStateManager& mgr, float arg) {\n  if (x7b0_attackTarget == kInvalidUniqueId) {\n    return false;\n  }\n  if (TCastToConstPtr<CActor> actor = mgr.GetObjectById(x7b0_attackTarget)) {\n    if (const auto* pirate = CPatterned::CastTo<CSpacePirate>(actor.GetPtr())) {\n      if (!IsPirateValidTarget(pirate, mgr)) {\n        return false;\n      }\n    }\n    return (actor->GetTranslation() - GetTranslation()).magSquared() < x300_maxAttackRange * x300_maxAttackRange;\n  }\n  return false;\n}\n\nbool CMetroid::Inside(CStateManager& mgr, float arg) {\n  if (x9bc_parent == kInvalidUniqueId) {\n    return false;\n  }\n  if (const auto* other = CPatterned::CastTo<CMetroid>(mgr.GetObjectById(x9bc_parent))) {\n    float radius = x6a0_collisionPrimitive.GetSphere().radius;\n    if (arg > 0.f) {\n      radius *= arg;\n    }\n    return (other->GetTranslation() - GetTranslation()).magSquared() < radius * radius;\n  }\n  return false;\n}\n\nbool CMetroid::Leash(CStateManager& mgr, float arg) {\n  if (x7b0_attackTarget == mgr.GetPlayer().GetUniqueId() && IsPlayerUnderwater(mgr)) {\n    return true;\n  }\n  if ((x3a0_latestLeashPosition - GetTranslation()).magSquared() <= x3c8_leashRadius * x3c8_leashRadius) {\n    return false;\n  }\n  if (x7b0_attackTarget != kInvalidUniqueId) {\n    if (TCastToConstPtr<CActor> actor = mgr.GetObjectById(x7b0_attackTarget)) {\n      if ((actor->GetTranslation() - GetTranslation()).magSquared() <=\n          x3cc_playerLeashRadius * x3cc_playerLeashRadius) {\n        return false;\n      }\n      if (x3d4_curPlayerLeashTime <= x3d0_playerLeashTime) {\n        return false;\n      }\n    }\n  }\n  return true;\n}\n\nbool CMetroid::LostInterest(CStateManager& mgr, float arg) {\n  if (x7b0_attackTarget == kInvalidUniqueId) {\n    return true;\n  }\n  if (TCastToConstPtr<CActor> actor = mgr.GetObjectById(x7b0_attackTarget)) {\n    if (const auto* pirate = CPatterned::CastTo<CSpacePirate>(actor.GetPtr())) {\n      return pirate->GetAttachedActor() != kInvalidUniqueId || GetBodyController()->HasBeenFrozen();\n    }\n    return x7b0_attackTarget == mgr.GetPlayer().GetUniqueId() &&\n           (IsPlayerUnderwater(mgr) || mgr.GetPlayer().GetAreaIdAlways() != GetAreaIdAlways());\n  }\n  return true;\n}\n\nbool CMetroid::PatternShagged(CStateManager& mgr, float arg) {\n  if (x7b0_attackTarget == kInvalidUniqueId) {\n    return true;\n  }\n  if (const auto* pirate = CPatterned::CastTo<CSpacePirate>(mgr.GetObjectById(x7b0_attackTarget))) {\n    if (!pirate->IsAlive()) {\n      return true;\n    }\n  }\n  if (!CanAttack(mgr)) {\n    return true;\n  }\n  if (x568_state == EState::Two) {\n    return x800_seekTime >= x804_maxSeekTime;\n  }\n  return false;\n}\n\nbool CMetroid::ShouldDodge(CStateManager& mgr, float arg) {\n  CPlayer& player = mgr.GetPlayer();\n  if (x3fc_flavor == CPatterned::EFlavorType::Two || x7b0_attackTarget != player.GetUniqueId() ||\n      GetAreaIdAlways() != player.GetAreaIdAlways()) {\n    return false;\n  }\n  const CTeamAiRole* const aiRole = CTeamAiMgr::GetTeamAiRole(mgr, x698_teamAiMgrId, GetUniqueId());\n  if (aiRole == nullptr || aiRole->GetTeamAiRole() != CTeamAiRole::ETeamAiRole::Melee) {\n    return false;\n  }\n  const auto& xf = GetTransform();\n  EntityList nearList;\n  mgr.BuildNearList(nearList, zeus::CAABox{xf.origin - 9.f, xf.origin + 9.f},\n                    CMaterialFilter::MakeInclude({EMaterialTypes::Projectile}), nullptr);\n  if (nearList.empty()) {\n    return false;\n  }\n  const auto front = xf.frontVector();\n  for (const auto& id : nearList) {\n    if (TCastToConstPtr<CGameProjectile> projectile = mgr.GetObjectById(id)) {\n      if (!projectile->HasAttrib(EProjectileAttrib::Ice)) {\n        continue;\n      }\n      const auto dir = projectile->GetTranslation() - xf.origin;\n      if (zeus::CVector3f::getAngleDiff(front, dir) >= zeus::degToRad(10.f)) {\n        continue;\n      }\n      pas::EStepDirection dodgeDirection = pas::EStepDirection::Right;\n      if (xf.rightVector().dot(dir) <= 0.f) {\n        dodgeDirection = pas::EStepDirection::Left;\n      }\n      x818_dodgeDirection = dodgeDirection;\n      return true;\n    }\n  }\n  return false;\n}\n\nbool CMetroid::ShouldTurn(CStateManager& mgr, float arg) {\n  if (x7b0_attackTarget == kInvalidUniqueId) {\n    return false;\n  }\n  if (TCastToConstPtr<CActor> actor = mgr.GetObjectById(x7b0_attackTarget)) {\n    const zeus::CTransform& xf = GetTransform();\n    const zeus::CVector2f dir = (actor->GetTranslation() - xf.origin).toVec2f();\n    const float diff = zeus::CVector2f::getAngleDiff(xf.frontVector().toVec2f(), dir);\n    return diff > zeus::degToRad(15.f);\n  }\n  return false;\n}\n\nbool CMetroid::SpotPlayer(CStateManager& mgr, float arg) {\n  CPlayer& player = mgr.GetPlayer();\n  if (IsPlayerUnderwater(mgr) || player.GetAreaIdAlways() != GetAreaIdAlways()) {\n    return false;\n  }\n  const TUniqueId playerUid = player.GetUniqueId();\n  if (x7b0_attackTarget != kInvalidUniqueId) {\n    return x7b0_attackTarget == playerUid;\n  }\n  if (TCastToPtr<CTeamAiMgr> aiMgr = mgr.ObjectById(x698_teamAiMgrId)) {\n    const float range = x3bc_detectionRange * x3bc_detectionRange;\n    for (const auto& role : aiMgr->GetRoles()) {\n      const TUniqueId uid = role.GetOwnerId();\n      if (uid != GetUniqueId()) {\n        if (const auto* other = CPatterned::CastTo<CMetroid>(mgr.GetObjectById(uid))) {\n          if (other->x7b0_attackTarget == playerUid &&\n              (other->GetTranslation() - GetTranslation()).magSquared() < range) {\n            return true;\n          }\n        }\n      }\n    }\n  }\n  return false;\n}\n\nvoid CMetroid::Patrol(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    GetBodyController()->SetLocomotionType(pas::ELocomotionType::Lurk);\n    x7b0_attackTarget = kInvalidUniqueId;\n    x9bf_26_shotAt = false;\n  }\n  CPatterned::Patrol(mgr, msg, arg);\n}\n\nvoid CMetroid::TargetPatrol(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    GetBodyController()->SetLocomotionType(pas::ELocomotionType::Relaxed);\n    x7b0_attackTarget = kInvalidUniqueId;\n    x9bf_26_shotAt = false;\n    if (HasPatrolPath(mgr, 0.f)) {\n      CPatterned::Patrol(mgr, msg, dt);\n      CPatterned::UpdateDest(mgr);\n    } else {\n      CPatterned::SetDestPos(x3a0_latestLeashPosition);\n    }\n    x7a4_ = GetDestPos();\n    if (GetSearchPath() != nullptr) {\n      CPatterned::PathFind(mgr, msg, dt);\n    }\n  } else if (msg == EStateMsg::Update) {\n    if (GetSearchPath() == nullptr || PathShagged(mgr, 0.f)) {\n      CPatterned::Patrol(mgr, msg, dt);\n    } else {\n      CPatterned::PathFind(mgr, msg, dt);\n    }\n    ApplySeparationBehavior(mgr, 9.f);\n  }\n}\n\nvoid CMetroid::TurnAround(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg != EStateMsg::Update || x7b0_attackTarget == kInvalidUniqueId) {\n    return;\n  }\n  if (TCastToConstPtr<CActor> actor = mgr.GetObjectById(x7b0_attackTarget)) {\n    const zeus::CVector3f face = actor->GetTranslation() - GetTranslation();\n    if (ShouldTurn(mgr, 0.f) && face.canBeNormalized()) {\n      GetBodyController()->GetCommandMgr().DeliverCmd(CBCLocomotionCmd{zeus::skZero3f, face.normalized(), 1.f});\n    }\n  }\n}\n\nvoid CMetroid::WallHang(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    GetBodyController()->SetLocomotionType(pas::ELocomotionType::Crouch);\n    x568_state = EState::Zero;\n    RemoveMaterial(EMaterialTypes::Solid, mgr);\n    x9bf_30_restoreSolidCollision = false;\n  } else if (msg == EStateMsg::Update) {\n    if (x568_state == EState::Zero) {\n      if (x9bf_24_alert) {\n        x568_state = EState::One;\n        x9bf_30_restoreSolidCollision = true;\n        x80c_detachPos.zeroOut();\n      }\n    } else if (x568_state == EState::One) {\n      if (GetBodyController()->GetCurrentStateId() == pas::EAnimationState::Generate) {\n        x568_state = EState::Two;\n      } else {\n        GetBodyController()->GetCommandMgr().DeliverCmd(CBCGenerateCmd(pas::EGenerateType::Zero, zeus::skZero3f));\n      }\n    } else if (x568_state == EState::Two) {\n      if (GetBodyController()->GetCurrentStateId() != pas::EAnimationState::Generate) {\n        x568_state = EState::Over;\n      }\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    GetBodyController()->SetLocomotionType(pas::ELocomotionType::Relaxed);\n    x9bf_27_ = true;\n  }\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CMetroid.hpp",
    "content": "#pragma once\n\n#include <optional>\n#include <string_view>\n\n#include \"Runtime/Collision/CCollidableSphere.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/World/CAnimationParameters.hpp\"\n#include \"Runtime/World/CPathFindSearch.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n#include \"Runtime/World/CPatternedInfo.hpp\"\n#include \"Runtime/MP1/World/CSpacePirate.hpp\"\n\nnamespace metaforce::MP1 {\n\nclass CMetroidData {\nprivate:\n  static constexpr u32 skNumProperties = 20;\n  CDamageVulnerability x0_frozenVulnerability;\n  CDamageVulnerability x68_energyDrainVulnerability;\n  float xd0_energyDrainPerSec;\n  float xd4_maxEnergyDrainAllowed;\n  float xd8_telegraphAttackTime;\n  float xdc_stage2GrowthScale;\n  float xe0_stage2GrowthEnergy;\n  float xe4_explosionGrowthEnergy;\n  std::optional<CAnimationParameters> xe8_animParms1;\n  std::optional<CAnimationParameters> xf8_animParms2;\n  std::optional<CAnimationParameters> x108_animParms3;\n  std::optional<CAnimationParameters> x118_animParms4;\n  bool x128_24_startsInWall : 1;\n\npublic:\n  explicit CMetroidData(CInputStream& in);\n  static u32 GetNumProperties() { return skNumProperties; }\n  const CDamageVulnerability& GetFrozenVulnerability() const { return x0_frozenVulnerability; }\n  const CDamageVulnerability& GetEnergyDrainVulnerability() const { return x68_energyDrainVulnerability; }\n  float GetEnergyDrainPerSec() const { return xd0_energyDrainPerSec; }\n  float GetMaxEnergyDrainAllowed() const { return xd4_maxEnergyDrainAllowed; }\n  float GetTelegraphAttackTime() const { return xd8_telegraphAttackTime; }\n  float GetStage2GrowthScale() const { return xdc_stage2GrowthScale; }\n  float GetStage2GrowthEnergy() const { return xe0_stage2GrowthEnergy; }\n  float GetExplosionGrowthEnergy() const { return xe4_explosionGrowthEnergy; }\n  bool GetStartsInWall() const { return x128_24_startsInWall; }\n};\n\nclass CMetroid : public CPatterned {\nprivate:\n  enum class EState {\n    Invalid = -1,\n    Zero,\n    One,\n    Two,\n    Over,\n  } x568_state = EState::Invalid;\n  CMetroidData x56c_data;\n  TUniqueId x698_teamAiMgrId = kInvalidUniqueId;\n  CCollidableSphere x6a0_collisionPrimitive;\n  CPathFindSearch x6c0_pathFindSearch;\n  zeus::CVector3f x7a4_;\n  TUniqueId x7b0_attackTarget = kInvalidUniqueId;\n  float x7b4_attackChance = 0.f;\n  float x7b8_telegraphAttackTime = 0.f;\n  float x7bc_energyDrained = 0.f;\n  float x7c0_energyDrainTime = 0.f;\n  float x7c4_ = 0.f;\n  enum class EAttackState {\n    None,\n    Attached,\n    Draining,\n    Over,\n  } x7c8_attackState = EAttackState::None;\n  enum class EGammaType {\n    Invalid = -1,\n    Normal,\n    Red,\n    White,\n    Purple,\n    Orange,\n  } x7cc_gammaType;\n  zeus::CVector3f x7d0_scale1;\n  zeus::CVector3f x7dc_scale2;\n  zeus::CVector3f x7e8_scale3;\n  float x7f4_growthDuration = 0.f;\n  float x7f8_growthEnergy = 0.f;\n  float x7fc_lastGrowthEnergy = 0.f;\n  float x800_seekTime = 0.f;\n  float x804_maxSeekTime = 0.f;\n  float x808_loopAttackDistance = 0.f;\n  zeus::CVector3f x80c_detachPos;\n  pas::EStepDirection x818_dodgeDirection = pas::EStepDirection::Invalid;\n  CPatternedInfo x81c_patternedInfo;\n  CActorParameters x954_actParams;\n  TUniqueId x9bc_parent;\n  u8 x9be_ = 0;\n  bool x9bf_24_alert : 1 = false;\n  bool x9bf_25_growing : 1 = false;\n  bool x9bf_26_shotAt : 1 = false;\n  bool x9bf_27_ : 1 = false;\n  bool x9bf_28_ : 1 = false;\n  bool x9bf_29_isAttacking : 1 = false;\n  bool x9bf_30_restoreSolidCollision : 1 = false;\n  bool x9bf_31_restoreCharacterCollision : 1 = false;\n  bool x9c0_24_isEnergyDrainVulnerable : 1 = false;\n\npublic:\n  DEFINE_PATTERNED(Metroid);\n  CMetroid(TUniqueId uid, std::string_view name, EFlavorType flavor, const CEntityInfo& info,\n           const zeus::CTransform& xf, CModelData&& mData, const CPatternedInfo& pInfo, const CActorParameters& aParms,\n           const CMetroidData& metroidData, TUniqueId);\n\n  void Accept(IVisitor& visitor) override { visitor.Visit(this); }\n  void Think(float dt, CStateManager& mgr) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) override;\n  void DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType eType, float dt) override;\n  const CCollisionPrimitive* GetCollisionPrimitive() const override { return &x6a0_collisionPrimitive; }\n  EWeaponCollisionResponseTypes GetCollisionResponseType(const zeus::CVector3f& vec1, const zeus::CVector3f& vec2,\n                                                         const CWeaponMode& mode,\n                                                         EProjectileAttrib attribute) const override;\n  const CDamageVulnerability* GetDamageVulnerability() const override;\n  const CDamageVulnerability* GetDamageVulnerability(const zeus::CVector3f& vec1, const zeus::CVector3f& vec2,\n                                                     const CDamageInfo& dInfo) const override {\n    return GetDamageVulnerability();\n  }\n  zeus::CVector3f GetOrigin(const CStateManager& mgr, const CTeamAiRole& role,\n                            const zeus::CVector3f& aimPos) const override;\n  CPathFindSearch* GetSearchPath() override { return &x6c0_pathFindSearch; }\n  std::optional<zeus::CAABox> GetTouchBounds() const override {\n    return x6a0_collisionPrimitive.CalculateAABox(GetTransform());\n  }\n  bool IsListening() const override { return true; }\n  void Render(CStateManager& mgr) override { return CPatterned::Render(mgr); }\n  void SelectTarget(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Touch(CActor& act, CStateManager& mgr) override;\n\n  void Attack(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Dodge(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Death(CStateManager& mgr, const zeus::CVector3f& direction, EScriptObjectState state) override;\n  void Generate(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void KnockBack(const zeus::CVector3f& dir, CStateManager& mgr, const CDamageInfo& info, EKnockBackType type,\n                 bool inDeferred, float magnitude) override;\n  void PathFind(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Patrol(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void TargetPatrol(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void TelegraphAttack(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void TurnAround(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void WallHang(CStateManager& mgr, EStateMsg msg, float dt) override;\n\n  bool AnimOver(CStateManager&, float arg) override { return x568_state == EState::Over; }\n  bool AggressionCheck(CStateManager& mgr, float arg) override;\n  bool Attacked(CStateManager& mgr, float arg) override;\n  bool AttackOver(CStateManager& mgr, float arg) override;\n  bool InAttackPosition(CStateManager& mgr, float arg) override;\n  bool InDetectionRange(CStateManager& mgr, float arg) override;\n  bool InPosition(CStateManager& mgr, float arg) override;\n  bool InRange(CStateManager& mgr, float arg) override;\n  bool Inside(CStateManager& mgr, float arg) override;\n  bool Leash(CStateManager& mgr, float arg) override;\n  bool LostInterest(CStateManager& mgr, float arg) override;\n  bool PatternShagged(CStateManager& mgr, float arg) override;\n  bool ShotAt(CStateManager& mgr, float arg) override { return x9bf_26_shotAt; }\n  bool ShouldAttack(CStateManager& mgr, float arg) override;\n  bool ShouldDodge(CStateManager& mgr, float arg) override;\n  bool ShouldTurn(CStateManager& mgr, float arg) override;\n  bool ShouldWallHang(CStateManager& mgr, float arg) override { return x56c_data.GetStartsInWall(); }\n  bool SpotPlayer(CStateManager& mgr, float arg) override;\n\n  bool IsAttacking() const { return x9bf_29_isAttacking; }\n\nprivate:\n  float ComputeMorphingPlayerSuckZPos(const CPlayer& player) const;\n  bool IsPirateValidTarget(const CSpacePirate* target, CStateManager& mgr);\n  bool CanAttack(CStateManager& mgr);\n  void UpdateAttackChance(CStateManager& mgr, float dt);\n  bool IsPlayerUnderwater(CStateManager& mgr);\n  bool IsHunterAttacking(CStateManager& mgr);\n  bool IsAttackInProgress(CStateManager& mgr);\n  void ComputeSuckPiratePosRot(CStateManager& mgr, zeus::CVector3f& outPos, zeus::CQuaternion& outRot);\n  EGammaType GetRandomGammaType(CStateManager& mgr, EGammaType previous);\n  void SpawnGammaMetroid(CStateManager& mgr);\n  bool ShouldSpawnGammaMetroid();\n  void ComputeSuckPlayerPosRot(CStateManager& mgr, zeus::CVector3f& outPos, zeus::CQuaternion& outRot);\n  void ComputeSuckTargetPosRot(CStateManager& mgr, zeus::CVector3f& outPos, zeus::CQuaternion& outRot);\n  void InterpolateToPosRot(CStateManager& mgr, float dt);\n  void SuckEnergyFromTarget(CStateManager& mgr, float dt);\n  bool ShouldReleaseFromTarget(CStateManager& mgr);\n  void DisableSolidCollision(CMetroid* target);\n  void RestoreSolidCollision(CStateManager& mgr);\n  void PreventWorldCollisions(CStateManager& mgr, float dt);\n  void SetupExitFaceHugDirection(CActor* actor, CStateManager& mgr, const zeus::CVector3f& vec,\n                                 const zeus::CTransform& xf);\n  void DetachFromTarget(CStateManager& mgr);\n  bool AttachToTarget(CStateManager& mgr);\n  void SwarmRemove(CStateManager& mgr);\n  void SwarmAdd(CStateManager& mgr);\n  void ApplySplitGammas(CStateManager& mgr, float arg);\n  void ApplyForwardSteering(CStateManager& mgr, const zeus::CVector3f& vec);\n  void ApplySeparationBehavior(CStateManager& mgr, float arg);\n  void SetUpPathFindBehavior(CStateManager& mgr);\n  void ApplyGrowth(float arg);\n  bool PreDamageSpacePirate(CStateManager& mgr);\n  float GetDamageMultiplier() { return 0.5f * (GetGrowthStage() - 1.f) + 1.f; }\n  float GetGrowthStage();\n  zeus::CVector3f GetAttackTargetPos(CStateManager& mgr);\n  bool IsSuckingEnergy() const;\n  void UpdateVolume();\n  void UpdateTouchBounds();\n};\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CMetroidBeta.cpp",
    "content": "#include \"Runtime/MP1/World/CMetroidBeta.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Collision/CCollisionActor.hpp\"\n#include \"Runtime/Collision/CCollisionActorManager.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/Particle/CParticleSwoosh.hpp\"\n#include \"Runtime/Weapon/CGameProjectile.hpp\"\n#include \"Runtime/World/CGameArea.hpp\"\n#include \"Runtime/World/CPatternedInfo.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CTeamAiMgr.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n#include \"Runtime/World/ScriptLoader.hpp\"\n\nnamespace metaforce::MP1 {\n\nCMetroidBetaData::CMetroidBetaData(CInputStream& in)\n: x0_(in)\n, x68_(in)\n, xd0_(in.ReadFloat())\n, xd4_(in.ReadFloat())\n, xd8_(in.ReadFloat())\n, xdc_(in.ReadFloat())\n, xe0_(in.ReadFloat())\n, xe4_(in.ReadFloat())\n, xe8_(in.ReadFloat())\n, xec_(in.ReadFloat())\n, xf0_(in.ReadFloat())\n, xf4_(in)\n, xf8_(in)\n, xfc_(in)\n, x100_(in)\n, x104_(in)\n, x108_24_(in.ReadBool()) {}\n\nCMetroidBeta::CMetroidBeta(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                           CModelData&& mData, const CPatternedInfo& pInfo, const CActorParameters& aParms,\n                           const CMetroidBetaData& metroidData)\n: CPatterned(ECharacter::MetroidBeta, uid, name, EFlavorType::One, info, xf, std::move(mData), pInfo,\n             EMovementType::Flyer, EColliderType::One, EBodyType::Flyer, aParms, EKnockBackVariant::Large)\n, x56c_metroidBetaData(metroidData)\n, x67c_pathFind(nullptr, 3, pInfo.GetPathfindingIndex(), 1.f, 1.f)\n, x768_colPrim(GetBoundingBox(), GetMaterialList())\n, x7b4_(GetModelData()->GetScale())\n, x7c0_(GetModelData()->GetScale())\n, x7cc_(GetModelData()->GetScale())\n, x7e4_(g_SimplePool->GetObj({FOURCC('PART'), metroidData.xf4_}))\n, x7f0_(g_SimplePool->GetObj({FOURCC('SWHC'), metroidData.xf8_}))\n, x7fc_(g_SimplePool->GetObj({FOURCC('PART'), metroidData.xfc_}))\n, x808_(g_SimplePool->GetObj({FOURCC('PART'), metroidData.x100_}))\n, x814_(g_SimplePool->GetObj({FOURCC('PART'), metroidData.x104_}))\n, x820_(std::make_unique<CElementGen>(x7e4_))\n, x824_(std::make_unique<CParticleSwoosh>(x7f0_, 0))\n, x828_(std::make_unique<CElementGen>(x7fc_))\n, x82c_(std::make_unique<CElementGen>(x808_))\n, x830_(std::make_unique<CElementGen>(x814_)) {\n  x820_->SetParticleEmission(false);\n  x828_->SetParticleEmission(false);\n  x82c_->SetParticleEmission(false);\n  x824_->DoElectricWarmup();\n  const float scale = 0.75f * GetModelData()->GetScale().y();\n  const zeus::CVector3f scaleVec(scale, scale, 2.f * scale);\n  zeus::CAABox box = {-scaleVec, scaleVec};\n  SetBoundingBox(box);\n  x768_colPrim.SetBox(box);\n}\n\nvoid CMetroidBeta::Think(float dt, CStateManager& mgr) {\n  if (CTeamAiMgr::GetTeamAiRole(mgr, x678_teamMgr, GetUniqueId())) {\n    AddToTeam(mgr);\n  }\n\n  CPatterned::Think(dt, mgr);\n  x764_collisionManager->Update(dt, mgr, CCollisionActorManager::EUpdateOptions::ObjectSpace);\n  // sub801c1928(mgr);\n  // sub801c0da4(dt, mgr);\n  // sub801c21b4(dt, mgr);\n}\n\nvoid CMetroidBeta::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  CPatterned::AcceptScriptMsg(msg, uid, mgr);\n  switch (msg) {\n  case EScriptObjectMessage::Registered: {\n    x450_bodyController->Activate(mgr);\n    CreateCollisionActorManager(mgr);\n    // sub801c13d4();\n    x760_ = GetModelData()->GetAnimationData()->GetLocatorSegId(\"L_Claw_1\"sv);\n    x761_ = GetModelData()->GetAnimationData()->GetLocatorSegId(\"R_Claw_1\"sv);\n    break;\n  }\n  case EScriptObjectMessage::Activate: {\n    x764_collisionManager->SetActive(mgr, true);\n    break;\n  }\n  case EScriptObjectMessage::Deactivate: {\n    x764_collisionManager->SetActive(mgr, false);\n    break;\n  }\n  case EScriptObjectMessage::Deleted: {\n    x764_collisionManager->Destroy(mgr);\n    RemoveFromTeam(mgr);\n    break;\n  }\n  case EScriptObjectMessage::Damage:\n  case EScriptObjectMessage::InvulnDamage: {\n    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(uid)) {\n      if (TCastToConstPtr<CGameProjectile> proj = mgr.GetObjectById(colAct->GetLastTouchedObject())) {\n        if (proj->GetOwnerId() != mgr.GetPlayer().GetUniqueId())\n          break;\n        // sub801c14b4(proj->GetDamageInfo().GetDamage(), mgr);\n        const CDamageInfo& dInfo = proj->GetDamageInfo();\n        if (colAct->GetDamageVulnerability()->WeaponHits(dInfo.GetWeaponMode(), false)) {\n          if (dInfo.GetWeaponMode().IsCharged() || dInfo.GetWeaponMode().IsComboed() ||\n              dInfo.GetWeaponMode().GetType() == EWeaponType::Missile) {\n            x840_31_ = true;\n            x83c_ += 1.f;\n          }\n          KnockBack(proj->GetTranslation() - proj->GetPreviousPos(), mgr, dInfo, EKnockBackType::Direct, false,\n                    dInfo.GetKnockBackPower());\n        }\n        if (x840_25_)\n          x83c_ += 0.1f;\n        x840_26_ = true;\n      }\n    } else if (TCastToConstPtr<CWeapon> weap = mgr.GetObjectById(uid)) {\n      CDamageInfo info = weap->GetDamageInfo();\n      info.SetRadius(0.f);\n      mgr.ApplyDamage(uid, x790_, weap->GetOwnerId(), info,\n                      CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), {});\n    }\n    break;\n  }\n  case EScriptObjectMessage::Alert: {\n    x840_26_ = true;\n    break;\n  }\n  case EScriptObjectMessage::Touched: {\n    if (TCastToConstPtr<CCollisionActor> colAct = mgr.GetObjectById(uid)) {\n      if (HealthInfo(mgr)->GetHP() > 0.f && colAct->GetLastTouchedObject() == mgr.GetPlayer().GetUniqueId() &&\n          x420_curDamageRemTime <= 0.f) {\n        CDamageInfo dInfo = GetContactDamage();\n        dInfo.SetDamage(0.5f * dInfo.GetDamage());\n        if (x840_29_ && x840_30_)\n          dInfo = GetContactDamage();\n\n        mgr.ApplyDamage(GetUniqueId(), mgr.GetPlayer().GetUniqueId(), GetUniqueId(), dInfo,\n                        CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), {});\n\n        x420_curDamageRemTime = x424_damageWaitTime;\n        x840_30_ = false;\n      }\n    }\n    break;\n  }\n  case EScriptObjectMessage::InitializedInArea: {\n    if (x678_teamMgr == kInvalidUniqueId)\n      x678_teamMgr = CTeamAiMgr::GetTeamAiMgr(*this, mgr);\n\n    x67c_pathFind.SetArea(mgr.GetWorld()->GetAreaAlways(GetAreaId())->GetPostConstructed()->x10bc_pathArea);\n    break;\n  }\n  case EScriptObjectMessage::SuspendedMove: {\n    if (x764_collisionManager)\n      x764_collisionManager->SetMovable(mgr, false);\n    break;\n  }\n  default:\n    break;\n  }\n}\n\nvoid CMetroidBeta::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) {\n  CPatterned::AddToRenderer(frustum, mgr);\n}\n\nvoid CMetroidBeta::Render(CStateManager& mgr) { CPatterned::Render(mgr); }\n\nconst CDamageVulnerability* CMetroidBeta::GetDamageVulnerability() const { return CAi::GetDamageVulnerability(); }\n\nconst CDamageVulnerability* CMetroidBeta::GetDamageVulnerability(const zeus::CVector3f& vec1,\n                                                                 const zeus::CVector3f& vec2,\n                                                                 const CDamageInfo& dInfo) const {\n  return CActor::GetDamageVulnerability(vec1, vec2, dInfo);\n}\n\nvoid CMetroidBeta::Touch(CActor& act, CStateManager& mgr) { CPatterned::Touch(act, mgr); }\n\nzeus::CVector3f CMetroidBeta::GetAimPosition(const CStateManager& mgr, float dt) const {\n  return CPatterned::GetAimPosition(mgr, dt);\n}\n\nvoid CMetroidBeta::DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType eType, float dt) {\n  CPatterned::DoUserAnimEvent(mgr, node, eType, dt);\n}\n\nconst CCollisionPrimitive* CMetroidBeta::GetCollisionPrimitive() const {\n  return CPhysicsActor::GetCollisionPrimitive();\n}\n\nvoid CMetroidBeta::CollidedWith(TUniqueId collidee, const CCollisionInfoList& info, CStateManager& mgr) {\n  CPatterned::CollidedWith(collidee, info, mgr);\n}\n\nzeus::CVector3f CMetroidBeta::GetOrigin(const CStateManager& mgr, const CTeamAiRole& role,\n                                        const zeus::CVector3f& aimPos) const {\n  return CAi::GetOrigin(mgr, role, aimPos);\n}\n\nvoid CMetroidBeta::Patrol(CStateManager& mgr, EStateMsg msg, float arg) { CPatterned::Patrol(mgr, msg, arg); }\n\nvoid CMetroidBeta::PathFind(CStateManager& mgr, EStateMsg msg, float arg) { CPatterned::PathFind(mgr, msg, arg); }\n\nvoid CMetroidBeta::SelectTarget(CStateManager& mgr, EStateMsg msg, float arg) { CAi::SelectTarget(mgr, msg, arg); }\n\nvoid CMetroidBeta::TargetPatrol(CStateManager& mgr, EStateMsg msg, float arg) {\n  CPatterned::TargetPatrol(mgr, msg, arg);\n}\n\nvoid CMetroidBeta::Generate(CStateManager& mgr, EStateMsg msg, float arg) { CAi::Generate(mgr, msg, arg); }\n\nvoid CMetroidBeta::Attack(CStateManager& mgr, EStateMsg msg, float arg) { CAi::Attack(mgr, msg, arg); }\n\nvoid CMetroidBeta::TurnAround(CStateManager& mgr, EStateMsg msg, float arg) { CAi::TurnAround(mgr, msg, arg); }\n\nvoid CMetroidBeta::TelegraphAttack(CStateManager& mgr, EStateMsg msg, float arg) {\n  CAi::TelegraphAttack(mgr, msg, arg);\n}\n\nvoid CMetroidBeta::WallHang(CStateManager& mgr, EStateMsg msg, float arg) { CAi::WallHang(mgr, msg, arg); }\n\nvoid CMetroidBeta::SpecialAttack(CStateManager& mgr, EStateMsg msg, float arg) { CAi::SpecialAttack(mgr, msg, arg); }\n\nbool CMetroidBeta::InAttackPosition(CStateManager& mgr, float arg) { return CAi::InAttackPosition(mgr, arg); }\n\nbool CMetroidBeta::Attacked(CStateManager& mgr, float arg) { return CPatterned::Attacked(mgr, arg); }\n\nbool CMetroidBeta::PathShagged(CStateManager& mgr, float arg) { return CPatterned::PathShagged(mgr, arg); }\n\nbool CMetroidBeta::InDetectionRange(CStateManager& mgr, float arg) { return CPatterned::InDetectionRange(mgr, arg); }\n\nbool CMetroidBeta::AnimOver(CStateManager& mgr, float arg) { return CPatterned::AnimOver(mgr, arg); }\n\nbool CMetroidBeta::ShouldAttack(CStateManager& mgr, float arg) { return CAi::ShouldAttack(mgr, arg); }\n\nbool CMetroidBeta::InPosition(CStateManager& mgr, float arg) { return CPatterned::InPosition(mgr, arg); }\n\nbool CMetroidBeta::ShouldTurn(CStateManager& mgr, float arg) { return CAi::ShouldTurn(mgr, arg); }\n\nbool CMetroidBeta::AttackOver(CStateManager& mgr, float arg) { return CAi::AttackOver(mgr, arg); }\n\nbool CMetroidBeta::ShotAt(CStateManager& mgr, float arg) { return CAi::ShotAt(mgr, arg); }\n\nbool CMetroidBeta::ShouldWallHang(CStateManager& mgr, float arg) { return CAi::ShouldWallHang(mgr, arg); }\n\nbool CMetroidBeta::StartAttack(CStateManager& mgr, float arg) { return CAi::StartAttack(mgr, arg); }\n\nbool CMetroidBeta::BreakAttack(CStateManager& mgr, float arg) { return CAi::BreakAttack(mgr, arg); }\n\nbool CMetroidBeta::ShouldSpecialAttack(CStateManager& mgr, float arg) { return CAi::ShouldSpecialAttack(mgr, arg); }\n\nvoid CMetroidBeta::RenderHitGunEffect() const {}\n\nvoid CMetroidBeta::RenderHitBallEffect() const {}\n\nstatic SSphereJointInfo skPelvisInfo[1]{\n    {\"Pelvis\", 1.5f},\n};\n\nvoid CMetroidBeta::CreateCollisionActorManager(CStateManager& mgr) {\n  std::vector<CJointCollisionDescription> joints;\n  AddSphereJoints(skPelvisInfo, 1, joints);\n\n  x764_collisionManager =\n      std::make_unique<CCollisionActorManager>(mgr, GetUniqueId(), GetAreaIdAlways(), joints, false);\n  x764_collisionManager->SetActive(mgr, GetActive());\n\n  for (u32 i = 0; i < x764_collisionManager->GetNumCollisionActors(); ++i) {\n    const CJointCollisionDescription& desc = x764_collisionManager->GetCollisionDescFromIndex(i);\n    if (TCastToPtr<CCollisionActor>(mgr.ObjectById(desc.GetCollisionActorId()))) {\n      if (desc.GetName() == \"Pelvis\"sv)\n        x790_ = desc.GetCollisionActorId();\n    }\n  }\n\n  SetCollisionActorHealthAndVulnerability(mgr);\n  SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(\n      {EMaterialTypes::Solid, EMaterialTypes::Wall, EMaterialTypes::Floor, EMaterialTypes::Ceiling},\n      {EMaterialTypes::CollisionActor, EMaterialTypes::Player, EMaterialTypes::Character}));\n  AddMaterial(EMaterialTypes::ProjectilePassthrough, mgr);\n  x764_collisionManager->AddMaterial(mgr, CMaterialList(EMaterialTypes::AIJoint, EMaterialTypes::CameraPassthrough));\n}\n\nvoid CMetroidBeta::AddSphereJoints(SSphereJointInfo* sphereJoints, s32 count,\n                                   std::vector<CJointCollisionDescription>& joints) {\n\n  for (s32 i = 0; i < count; ++i) {\n    const auto& sphereJoint = sphereJoints[i];\n    const CSegId id = GetModelData()->GetAnimationData()->GetLocatorSegId(sphereJoint.name);\n\n    if (id.IsInvalid()) {\n      continue;\n    }\n\n    joints.push_back(CJointCollisionDescription::SphereCollision(id, sphereJoint.radius, sphereJoint.name, 1000.0f));\n  }\n}\n\nvoid CMetroidBeta::SetCollisionActorHealthAndVulnerability(CStateManager& mgr) {\n  CHealthInfo* hInfo = HealthInfo(mgr);\n  if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(x790_)) {\n    *colAct->HealthInfo(mgr) = *hInfo;\n    colAct->SetDamageVulnerability(*GetDamageVulnerability());\n  }\n}\n\nvoid CMetroidBeta::RemoveFromTeam(CStateManager& mgr) {\n  if (x678_teamMgr == kInvalidUniqueId)\n    return;\n\n  if (TCastToPtr<CTeamAiMgr> teamMgr = mgr.ObjectById(x678_teamMgr)) {\n    if (teamMgr->IsPartOfTeam(GetUniqueId()))\n      teamMgr->RemoveTeamAiRole(GetUniqueId());\n  }\n}\n\nvoid CMetroidBeta::AddToTeam(CStateManager& mgr) {\n  if (x678_teamMgr == kInvalidUniqueId)\n    return;\n\n  if (TCastToPtr<CTeamAiMgr> teamMgr = mgr.ObjectById(x678_teamMgr)) {\n    if (!teamMgr->IsPartOfTeam(GetUniqueId()))\n      teamMgr->AssignTeamAiRole(*this, CTeamAiRole::ETeamAiRole::Ranged, CTeamAiRole::ETeamAiRole::Invalid,\n                                CTeamAiRole::ETeamAiRole::Invalid);\n  }\n}\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CMetroidBeta.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/CRandom16.hpp\"\n#include \"Runtime/Collision/CJointCollisionDescription.hpp\"\n#include \"Runtime/World/CPathFindSearch.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CCollisionActorManager;\nclass CElementGen;\nclass CParticleSwoosh;\n} // namespace metaforce\n\nnamespace metaforce::MP1 {\n\nclass CMetroidBetaData {\n  friend class CMetroidBeta;\n  CDamageVulnerability x0_;\n  CDamageVulnerability x68_;\n  float xd0_;\n  float xd4_;\n  float xd8_;\n  float xdc_;\n  float xe0_;\n  float xe4_;\n  float xe8_;\n  float xec_;\n  float xf0_;\n  CAssetId xf4_;\n  CAssetId xf8_;\n  CAssetId xfc_;\n  CAssetId x100_;\n  CAssetId x104_;\n  bool x108_24_ : 1;\n\npublic:\n  explicit CMetroidBetaData(CInputStream&);\n};\n\nclass CMetroidBeta : public CPatterned {\n  s32 x568_progState = -1;\n  CMetroidBetaData x56c_metroidBetaData;\n  TUniqueId x678_teamMgr = kInvalidUniqueId;\n  CPathFindSearch x67c_pathFind;\n  u8 x760_ = 0xFF;\n  u8 x761_ = 0xFF;\n  std::unique_ptr<CCollisionActorManager> x764_collisionManager;\n  CCollidableAABox x768_colPrim;\n  TUniqueId x790_ = kInvalidUniqueId;\n  float x794_ = 0.f;\n  float x798_ = 0.f;\n  float x79c_ = 0.f;\n  float x7a0_ = 0.f;\n  float x7a4_ = 0.f;\n  zeus::CVector3f x7a8_;\n  zeus::CVector3f x7b4_;\n  zeus::CVector3f x7c0_;\n  zeus::CVector3f x7cc_;\n  float x7d8_ = 0.f;\n  float x7dc_ = 0.f;\n  float x7e0_ = 0.f;\n  TToken<CGenDescription> x7e4_;\n  TToken<CSwooshDescription> x7f0_;\n  TToken<CGenDescription> x7fc_;\n  TToken<CGenDescription> x808_;\n  TToken<CGenDescription> x814_;\n  std::unique_ptr<CElementGen> x820_;\n  std::unique_ptr<CParticleSwoosh> x824_;\n  std::unique_ptr<CElementGen> x828_;\n  std::unique_ptr<CElementGen> x82c_;\n  std::unique_ptr<CElementGen> x830_;\n  float x834_ = 0.f;\n  CRandom16 x838_ = CRandom16(1469);\n  float x83c_;\n  bool x840_24_ : 1 = false;\n  bool x840_25_ : 1 = false;\n  bool x840_26_ : 1 = false;\n  bool x840_27_ : 1 = false;\n  bool x840_28_ : 1 = false;\n  bool x840_29_ : 1 = false;\n  bool x840_30_ : 1 = false;\n  bool x840_31_ : 1 = false;\n\n  void CreateCollisionActorManager(CStateManager& mgr);\n  void AddSphereJoints(SSphereJointInfo* sphereJoints, s32 count, std::vector<CJointCollisionDescription>& joints);\n  void SetCollisionActorHealthAndVulnerability(CStateManager& mgr);\n  void RemoveFromTeam(CStateManager& mgr);\n  void AddToTeam(CStateManager& mgr);\n\npublic:\n  DEFINE_PATTERNED(MetroidBeta);\n  CMetroidBeta(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n               CModelData&& mData, const CPatternedInfo& pInfo, const CActorParameters& aParms,\n               const CMetroidBetaData& metroidData);\n\n  void Accept(IVisitor& visitor) override { visitor.Visit(this); }\n  void Think(float dt, CStateManager& mgr) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) override;\n  void AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) override;\n  void Render(CStateManager& mgr) override;\n  const CDamageVulnerability* GetDamageVulnerability() const override;\n  const CDamageVulnerability* GetDamageVulnerability(const zeus::CVector3f& vec1, const zeus::CVector3f& vec2,\n                                                     const CDamageInfo& dInfo) const override;\n  void Touch(CActor& act, CStateManager& mgr) override;\n  zeus::CVector3f GetAimPosition(const CStateManager& mgr, float dt) const override;\n  void DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType eType, float dt) override;\n  const CCollisionPrimitive* GetCollisionPrimitive() const override;\n  void CollidedWith(TUniqueId collidee, const CCollisionInfoList& info, CStateManager& mgr) override;\n  bool IsListening() const override { return true; }\n  zeus::CVector3f GetOrigin(const CStateManager& mgr, const CTeamAiRole& role,\n                            const zeus::CVector3f& aimPos) const override;\n  void Patrol(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void PathFind(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void SelectTarget(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void TargetPatrol(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Generate(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Attack(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void TurnAround(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void TelegraphAttack(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void WallHang(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void SpecialAttack(CStateManager& mgr, EStateMsg msg, float arg) override;\n  bool InAttackPosition(CStateManager& mgr, float arg) override;\n  bool Attacked(CStateManager& mgr, float arg) override;\n  bool PathShagged(CStateManager& mgr, float arg) override;\n  bool InDetectionRange(CStateManager& mgr, float arg) override;\n  bool AnimOver(CStateManager& mgr, float arg) override;\n  bool ShouldAttack(CStateManager& mgr, float arg) override;\n  bool InPosition(CStateManager& mgr, float arg) override;\n  bool ShouldTurn(CStateManager& mgr, float arg) override;\n  bool AttackOver(CStateManager& mgr, float arg) override;\n  bool ShotAt(CStateManager& mgr, float arg) override;\n  bool ShouldWallHang(CStateManager& mgr, float arg) override;\n  bool StartAttack(CStateManager& mgr, float arg) override;\n  bool BreakAttack(CStateManager& mgr, float arg) override;\n  bool ShouldSpecialAttack(CStateManager& mgr, float arg) override;\n  CPathFindSearch* GetSearchPath() override { return &x67c_pathFind; }\n\n  void RenderHitGunEffect() const;\n  void RenderHitBallEffect() const;\n};\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CMetroidPrime.cpp",
    "content": "#include \"Runtime/MP1/World/CMetroidPrime.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Collision/CCollisionActor.hpp\"\n#include \"Runtime/Collision/CCollisionActorManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/MP1/World/CEnergyBall.hpp\"\n#include \"Runtime/MP1/World/CIceAttackProjectile.hpp\"\n#include \"Runtime/MP1/World/CMetroidPrimeRelay.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/Particle/CParticleElectric.hpp\"\n#include \"Runtime/Particle/CParticleSwoosh.hpp\"\n#include \"Runtime/Weapon/CPlasmaProjectile.hpp\"\n#include \"Runtime/World/CHUDBillboardEffect.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CProjectedShadow.hpp\"\n#include \"Runtime/World/CScriptWaypoint.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n#include \"Runtime/World/ScriptLoader.hpp\"\n\n#include \"Runtime/Streams/IOStreams.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce::MP1 {\n\nnamespace {\nstd::array<pas::ELocomotionType, 3> skLocomotions{{\n    pas::ELocomotionType::Internal10,\n    pas::ELocomotionType::Internal11,\n    pas::ELocomotionType::Internal12,\n}};\nstd::array<pas::ETauntType, 3> skTaunts{{\n    pas::ETauntType::One,\n    pas::ETauntType::Two,\n    pas::ETauntType::Zero,\n}};\n\nstd::array<SSphereJointInfo, 3> skSphereJoints{{\n    {\"Sphere_LCTR\", 1.5f},\n    {\"Skeleton_Root\", 2.3f},\n    {\"Head_LockON_SDK\", 0.92f},\n}};\n\nstd::array<SOBBJointInfo, 23> skBodyJoints{{\n    {\"R_shoulder\", \"R_elbow\", {0.6, 0.6, 0.6}},          {\"R_elbow\", \"R_wrist\", {0.3f, 0.3f, 0.3f}},\n    {\"R_wrist\", \"R_hand_LCTR\", {0.3f, 0.3f, 0.3f}},      {\"R_hand_LCTR\", \"R_leg_LCTR\", {0.4f, 1.2f, 0.4f}},\n    {\"R_front_1\", \"R_front_2\", {0.2f, 0.2f, 0.2f}},      {\"R_front_2\", \"R_front_3\", {0.2f, 0.2f, 0.2f}},\n    {\"R_front_3\", \"F_R_leg_LCTR\", {0.2f, 0.2f, 0.7f}},   {\"R_stinger_1\", \"R_stinger_2\", {0.2f, 0.2f, 0.2f}},\n    {\"R_stinger_2\", \"R_spike_LCTR\", {0.2f, 0.2f, 0.2f}}, {\"L_shoulder\", \"L_elbow\", {0.6, 0.6, 0.6}},\n    {\"L_elbow\", \"L_wrist\", {0.3f, 0.3f, 0.3f}},          {\"L_wrist\", \"L_hand_LCTR\", {0.3f, 0.3f, 0.3f}},\n    {\"L_hand_LCTR\", \"L_leg_LCTR\", {0.4f, 1.2f, 0.4f}},   {\"L_front_1\", \"L_front_2\", {0.2f, 0.2f, 0.2f}},\n    {\"L_front_2\", \"L_front_3\", {0.2f, 0.2f, 0.2f}},      {\"L_front_3\", \"F_L_leg_LCTR\", {0.2f, 0.2f, 0.7f}},\n    {\"L_stinger_1\", \"L_stinger_2\", {0.4f, 0.4f, 0.4f}},  {\"L_stinger_2\", \"L_spike_LCTR\", {0.2f, 0.2f, 0.2f}},\n    {\"B_shoulder\", \"B_elbow\", {0.8f, 0.8f, 0.8f}},       {\"B_elbow\", \"B_wrist\", {0.7f, 0.7f, 0.7f}},\n    {\"B_wrist\", \"B_leg_LCTR\", {0.6f, 0.1f, 0.6f}},       {\"Head_LCTR\", \"Horn_LCTR\", {0.8f, 0.1f, 0.6f}},\n    {\"Jaw_1\", \"C_bottomtooth\", {2.f, 0.2f, 0.5f}},\n}};\n\nstd::array<float, 4> skHealthConstants{{\n    2420.f,\n    1760.f,\n    880.f,\n    0.f,\n}};\n\nstd::array<std::string_view, 2> skDrillerLocators{{\"driller_LCTR1\"sv, \"driller_LCTR2\"sv}};\n\nstd::array<std::string_view, 4> skEffectNames{{\n    \"Flame_Head\"sv,\n    \"Flame_HeadLockOn\"sv,\n    \"Flame_Lshoulder\"sv,\n    \"Flame_Rshoulder\"sv,\n}};\n\nstd::array<std::string_view, 20> skLegLocators{{\n    \"R_front_2\"sv,\n    \"L_front_2\"sv,\n    \"R_front_1\"sv,\n    \"L_front_1\"sv,\n    \"\"sv,\n    \"\"sv,\n    \"R_elbow\"sv,\n    \"L_elbow\"sv,\n    \"\"sv,\n    \"\"sv,\n    \"Head\"sv,\n    \"Head_LCTR\"sv,\n    \"\"sv,\n    \"\"sv,\n    \"R_shoulder\"sv,\n    \"L_shoulder\"sv,\n    \"R_stinger_2\"sv,\n    \"L_stinger_2\"sv,\n    \"R_spike_LCTR\"sv,\n    \"L_spike_LCTR\"sv,\n}};\n\nstd::array<std::string_view, 6> skBoneTrackingNames{{\n    \"L_eye_1\"sv,\n    \"L_eye_2\"sv,\n    \"L_eye_3\"sv,\n    \"R_eye_1\"sv,\n    \"R_eye_2\"sv,\n    \"R_eye_3\"sv,\n}};\n\nstd::array<std::array<s32, 3>, 14> skSomeMeleeValues{{\n    {{3, 3, 3}},\n    {{-1, -1, -1}},\n    {{2, 2, 2}},\n    {{5, 5, 5}},\n    {{8, 8, 8}},\n    {{11, 11, 11}},\n    {{1, 1, 1}},\n    {{4, 4, 4}},\n    {{7, 7, 7}},\n    {{4, 7, 1}},\n    {{-1, -1, -1}},\n    {{-1, 2, -1}},\n    {{-1, -1, -1}},\n    {{0, 0, 0}},\n}};\n\nstd::array<s32, 17> skSomeValues1{{7, 5, 18, 18, 18, 18, 18, 18, 18, 7, 5, 7, 17, 18, 9, 2, 11}};\n\nstd::array<std::array<pas::ELocomotionType, 3>, 14> skSomeValues2{{\n    {{pas::ELocomotionType::Invalid, pas::ELocomotionType::Invalid, pas::ELocomotionType::Invalid}},\n    {{pas::ELocomotionType::Internal10, pas::ELocomotionType::Internal11, pas::ELocomotionType::Internal12}},\n    {{pas::ELocomotionType::Invalid, pas::ELocomotionType::Invalid, pas::ELocomotionType::Invalid}},\n    {{pas::ELocomotionType::Invalid, pas::ELocomotionType::Invalid, pas::ELocomotionType::Invalid}},\n    {{pas::ELocomotionType::Invalid, pas::ELocomotionType::Invalid, pas::ELocomotionType::Invalid}},\n    {{pas::ELocomotionType::Invalid, pas::ELocomotionType::Invalid, pas::ELocomotionType::Invalid}},\n    {{pas::ELocomotionType::Invalid, pas::ELocomotionType::Invalid, pas::ELocomotionType::Invalid}},\n    {{pas::ELocomotionType::Invalid, pas::ELocomotionType::Invalid, pas::ELocomotionType::Invalid}},\n    {{pas::ELocomotionType::Invalid, pas::ELocomotionType::Invalid, pas::ELocomotionType::Invalid}},\n    {{pas::ELocomotionType::Invalid, pas::ELocomotionType::Invalid, pas::ELocomotionType::Invalid}},\n    {{pas::ELocomotionType::Internal8, pas::ELocomotionType::Internal8, pas::ELocomotionType::Internal8}},\n    {{pas::ELocomotionType::Invalid, pas::ELocomotionType::Invalid, pas::ELocomotionType::Invalid}},\n    {{pas::ELocomotionType::Invalid, pas::ELocomotionType::Invalid, pas::ELocomotionType::Invalid}},\n    {{pas::ELocomotionType::Invalid, pas::ELocomotionType::Invalid, pas::ELocomotionType::Invalid}},\n}};\n} // namespace\nSPrimeStruct2B::SPrimeStruct2B(CInputStream& in)\n: x0_propertyCount(in.ReadLong())\n, x4_particle1(in.Get<CAssetId>())\n, x8_particle2(in.Get<CAssetId>())\n, xc_particle3(in.Get<CAssetId>())\n, x10_dInfo(in)\n, x2c_(in.ReadFloat())\n, x30_(in.ReadFloat())\n, x34_texture(in.Get<CAssetId>())\n, x38_(CSfxManager::TranslateSFXID(u16(in.ReadLong())))\n, x3a_(CSfxManager::TranslateSFXID(u16(in.ReadLong()))) {}\n\nSPrimeStruct4::SPrimeStruct4(CInputStream& in)\n: x0_beamInfo(in)\n, x44_(in.ReadLong())\n, x48_dInfo1(in)\n, x64_struct5(CPlasmaProjectile::LoadPlayerEffectResources(in))\n, x88_(in.ReadFloat())\n, x8c_dInfo2(in) {}\n\nSPrimeStruct6::SPrimeStruct6(CInputStream& in)\n: x0_propertyCount(in.ReadLong()), x4_damageVulnerability(in), x6c_color(in.Get<zeus::CColor>()) {\n  x70_[0] = in.ReadLong();\n  x70_[1] = in.ReadLong();\n}\n\nstatic CPatternedInfo LoadPatternedInfo(CInputStream& in) {\n  std::pair<bool, u32> pcount = CPatternedInfo::HasCorrectParameterCount(in);\n  return CPatternedInfo(in, pcount.second);\n}\n\nstruct SExoCameraShakePoint {\n  float x0_attackTime{};\n  float x4_sustainTime{};\n  float x8_duration{};\n  float xc_magnitude{};\n  SExoCameraShakePoint() = default;\n  SExoCameraShakePoint(CInputStream& in)\n  : x0_attackTime(in.ReadFloat())\n  , x4_sustainTime(in.ReadFloat())\n  , x8_duration(in.ReadFloat())\n  , xc_magnitude(in.ReadFloat()) {}\n};\n\nstruct SExoCameraShakerComponent {\n  bool x0_useModulation{};\n  SExoCameraShakePoint x4_am{};\n  SExoCameraShakePoint x14_fm{};\n  SExoCameraShakerComponent() = default;\n  explicit SExoCameraShakerComponent(CInputStream& in)\n  : x0_useModulation(in.ReadBool()), x4_am(in.Get<SExoCameraShakePoint>()), x14_fm(in.Get<SExoCameraShakePoint>()) {}\n};\n\nstruct SExoCameraShakeData {\n  bool x0_useSfx{};\n  float x4_duration{};\n  float x8_sfxDist{};\n  std::array<SExoCameraShakerComponent, 3> xc_components{};\n  SExoCameraShakeData() = default;\n  explicit SExoCameraShakeData(CInputStream& in)\n  : x0_useSfx(in.ReadBool()), x4_duration(in.ReadFloat()), x8_sfxDist(in.ReadFloat()) {\n    for (auto& component : xc_components) {\n      component = in.Get<SExoCameraShakerComponent>();\n    }\n  }\n};\n\nstatic SCameraShakePoint BuildCameraShakePoint(SExoCameraShakePoint& sp) {\n  return {false, sp.x0_attackTime, sp.x4_sustainTime, sp.x8_duration, sp.xc_magnitude};\n}\n\nstatic CCameraShakerComponent BuildCameraShakerComponent(SExoCameraShakerComponent& comp) {\n  return {comp.x0_useModulation, BuildCameraShakePoint(comp.x4_am), BuildCameraShakePoint(comp.x14_fm)};\n}\n\nstatic CCameraShakeData LoadCameraShakeData(CInputStream& in) {\n  auto shakeData = in.Get<SExoCameraShakeData>();\n  return {shakeData.x4_duration,\n          shakeData.x8_sfxDist,\n          u32(shakeData.x0_useSfx),\n          zeus::skZero3f,\n          BuildCameraShakerComponent(shakeData.xc_components[0]),\n          BuildCameraShakerComponent(shakeData.xc_components[1]),\n          BuildCameraShakerComponent(shakeData.xc_components[2])};\n}\n\nstatic rstl::reserved_vector<SPrimeStruct4, 4> LoadPrimeStruct4s(CInputStream& in) {\n  rstl::reserved_vector<SPrimeStruct4, 4> ret;\n  for (int i = 0; i < 4; ++i) {\n    ret.emplace_back(in);\n  }\n  return ret;\n}\n\nstatic rstl::reserved_vector<SPrimeStruct6, 4> LoadPrimeStruct6s(CInputStream& in) {\n  rstl::reserved_vector<SPrimeStruct6, 4> ret;\n  for (int i = 0; i < 4; ++i) {\n    ret.emplace_back(in);\n  }\n  return ret;\n}\n\nCMetroidPrimeData::CMetroidPrimeData(CInputStream& in)\n: x0_propertyCount(in.ReadLong())\n, x4_patternedInfo(LoadPatternedInfo(in))\n, x13c_actorParms(ScriptLoader::LoadActorParameters(in))\n, x1a4_(in.ReadLong())\n, x1a8_(LoadCameraShakeData(in))\n, x27c_(LoadCameraShakeData(in))\n, x350_(LoadCameraShakeData(in))\n, x424_(in)\n, x460_particle1(in.Get<CAssetId>())\n, x464_(LoadPrimeStruct4s(in))\n, x708_wpsc1(in.Get<CAssetId>())\n, x70c_dInfo1(in)\n, x728_shakeData1(LoadCameraShakeData(in))\n, x7fc_wpsc2(in.Get<CAssetId>())\n, x800_dInfo2(in)\n, x81c_shakeData2(LoadCameraShakeData(in))\n, x8f0_(in)\n, x92c_(in)\n, x948_(LoadCameraShakeData(in))\n, xa1c_particle2(in.Get<CAssetId>())\n, xa20_swoosh(in.Get<CAssetId>())\n, xa24_particle3(in.Get<CAssetId>())\n, xa28_particle4(in.Get<CAssetId>())\n, xa2c_(LoadPrimeStruct6s(in)) {}\n\nCMetroidPrimeAttackWeights::CMetroidPrimeAttackWeights(CInputStream& in) {\n  u32 propCount = std::min(14, in.ReadLong());\n  for (u32 i = 0; i < propCount; ++i) {\n    x0_.push_back(in.ReadFloat());\n  }\n}\n\nCMetroidPrime::CMetroidPrime(\n    TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf, CModelData&& mData,\n    const CPatternedInfo& pInfo, const CActorParameters& aParms, u32 pw1, const CCameraShakeData& shakeData1,\n    const CCameraShakeData& shakeData2, const CCameraShakeData& shakeData3, const SPrimeStruct2B& struct2b,\n    CAssetId particle1, const rstl::reserved_vector<SPrimeStruct4, 4>& struct4s, CAssetId wpsc1,\n    const CDamageInfo& dInfo1, const CCameraShakeData& shakeData4, CAssetId wpsc2, const CDamageInfo& dInfo2,\n    const CCameraShakeData& shakeData5, const SPrimeProjectileInfo& projectileInfo, const CDamageInfo& dInfo3,\n    const CCameraShakeData& shakeData6, CAssetId particle2, CAssetId swoosh, CAssetId particle3, CAssetId particle4,\n    const rstl::reserved_vector<SPrimeStruct6, 4>& struct6s)\n: CPatterned(ECharacter::MetroidPrimeExo, uid, name, EFlavorType::Zero, info, xf, std::move(mData), pInfo,\n             EMovementType::Flyer, EColliderType::One, EBodyType::Flyer, aParms, EKnockBackVariant::Large)\n, x588_(struct6s)\n, x8e8_headUpAdditiveBodyAnimIndex(\n      GetModelData()->GetAnimationData()->GetCharacterInfo().GetAnimationIndex(\"B_headup_additive_body\"sv))\n, x91c_(pw1)\n, x930_(struct2b)\n, xc48_(g_SimplePool->GetObj({FOURCC('PART'), particle1}))\n, xc50_(std::make_unique<CElementGen>(xc48_, CElementGen::EModelOrientationType::Normal,\n                                      CElementGen::EOptionalSystemFlags::One))\n, xc78_(wpsc1, dInfo1)\n, xca0_(shakeData4)\n, xd74_(wpsc2, dInfo2)\n, xd9c_(shakeData5)\n, xe70_(projectileInfo)\n, xeb4_(dInfo3)\n, xed0_(shakeData6)\n, xfa4_(g_SimplePool->GetObj(\"Effect_Electric\"sv))\n, xfac_(std::make_unique<CParticleElectric>(xfa4_))\n, x1014_(g_SimplePool->GetObj({FOURCC('PART'), particle3}))\n, x101c_(g_SimplePool->GetObj({FOURCC('PART'), particle4}))\n, x1024_(std::make_unique<CElementGen>(x1014_, CElementGen::EModelOrientationType::Normal,\n                                       CElementGen::EOptionalSystemFlags::One))\n, x108c_(shakeData1)\n, x1294_(shakeData2)\n, x1368_(shakeData3)\n, x143c_(std::make_unique<CProjectedShadow>(128, 128, true)) {\n  for (const auto& struct4 : struct4s) {\n    x96c_.emplace_back(struct4.x0_beamInfo);\n    xb30_.emplace_back(struct4.x64_struct5);\n    xbc4_.emplace_back(struct4.x8c_dInfo2);\n    xa80_.emplace_back(struct4.x44_, struct4.x48_dInfo1);\n  }\n\n  x460_knockBackController.SetAutoResetImpulse(false);\n  x460_knockBackController.SetEnableBurn(false);\n  x460_knockBackController.SetEnableFreeze(false);\n  xc78_.Token().Lock();\n  xd74_.Token().Lock();\n  xfc4_.emplace_back(g_SimplePool->GetObj({FOURCC('PART'), particle2}));\n  xfc4_.emplace_back(g_SimplePool->GetObj({FOURCC('PART'), particle2}));\n  xfd8_.emplace_back(g_SimplePool->GetObj({FOURCC('SWSH'), swoosh}));\n  xfd8_.emplace_back(g_SimplePool->GetObj({FOURCC('SWSH'), swoosh}));\n  xfec_.emplace_back(std::make_unique<CElementGen>(xfc4_[0]));\n  xfec_.emplace_back(std::make_unique<CElementGen>(xfc4_[1]));\n  x1000_.emplace_back(std::make_unique<CParticleSwoosh>(xfd8_[0], 0));\n  x1000_.emplace_back(std::make_unique<CParticleSwoosh>(xfd8_[1], 0));\n  x102c_.push_back(0.3f);\n  x102c_.push_back(0.3f);\n  x1038_.push_back(0.f);\n  x1038_.push_back(2.f);\n}\n\nvoid CMetroidPrime::PreThink(float dt, CStateManager& mgr) {\n  CPatterned::PreThink(dt, mgr);\n  if (!GetActive()) {\n    return;\n  }\n\n  if (TCastToConstPtr<CCollisionActor> colAct = mgr.GetObjectById(x8cc_headColActor)) {\n    x8c8_ = colAct->GetHealthInfo(mgr)->GetHP();\n  }\n}\n\nvoid CMetroidPrime::Think(float dt, CStateManager& mgr) {\n  CPatterned::Think(dt, mgr);\n  if (!GetActive()) {\n    return;\n  }\n  UpdateAreaId(mgr);\n  UpdateBoneTracking(dt, mgr);\n  UpdateCollision(dt, mgr);\n  UpdateHealthInfo(mgr);\n  UpdateColorChange(dt, mgr);\n  UpdateHeadAnimation(dt);\n  UpdatePlasmaProjectile(dt, mgr);\n  UpdateParticles(dt, mgr);\n  UpdateEnergyBalls(dt, mgr);\n  UpdatePhysicsDummy(mgr);\n  UpdateContactDamage(mgr);\n  UpdateTimers(dt);\n  UpdateSfxEmitter(dt, mgr);\n  UpdateElectricEffect(dt, mgr);\n}\n\nvoid CMetroidPrime::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId other, CStateManager& mgr) {\n  switch (msg) {\n  case EScriptObjectMessage::Activate:\n    x56c_collisionManager->SetActive(mgr, true);\n    break;\n  case EScriptObjectMessage::Deactivate:\n    x56c_collisionManager->SetActive(mgr, false);\n    break;\n  case EScriptObjectMessage::Start:\n    x1444_24_ = true;\n    break;\n  case EScriptObjectMessage::Touched:\n    DoContactDamage(other, mgr);\n    break;\n  case EScriptObjectMessage::Registered:\n    CreateShadow(false);\n    x450_bodyController->Activate(mgr);\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Internal11);\n    SetupBoneTracking();\n    SetupCollisionActorManager(mgr);\n    SetEyesParticleEffectState(mgr, true);\n    SetBoneTrackingTarget(mgr, true);\n    CreatePlasmaProjectiles(mgr);\n    CreatePhysicsDummy(mgr);\n    EnableParticles(mgr, true);\n    CreateHUDBillBoard(mgr);\n    mgr.GetPlayer().SetFrozenTimeoutBias(2.f);\n    break;\n  case EScriptObjectMessage::Deleted: {\n    x56c_collisionManager->Destroy(mgr);\n    FreePlasmaProjectiles(mgr);\n    sub802740cc(mgr);\n    FreeBillboard(mgr);\n    mgr.GetPlayer().SetFrozenTimeoutBias(0.f);\n    break;\n  }\n  case EScriptObjectMessage::InitializedInArea:\n    RemoveMaterial(EMaterialTypes::AIBlock, mgr);\n    UpdateRelay(mgr, GetAreaIdAlways());\n    if (GetAreaIdAlways() == mgr.GetWorld()->GetCurrentAreaId()) {\n      SendStateToRelay(EScriptObjectState::MaxReached, mgr);\n    }\n\n    if (xfac_) {\n      xfac_->SetParticleEmission(false);\n    }\n    break;\n  case EScriptObjectMessage::Damage:\n    sub8027827c(other, mgr);\n    [[fallthrough]];\n  case EScriptObjectMessage::InvulnDamage:\n    return;\n  default:\n    break;\n  }\n  CPatterned::AcceptScriptMsg(msg, other, mgr);\n}\n\nvoid CMetroidPrime::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) {\n  CPatterned::PreRender(mgr, frustum);\n  x143c_->RenderShadowBuffer(mgr, *GetModelData(), GetTransform(), 1, zeus::skZero3f, 1.f, 5.f);\n  x143c_->Set_x98(0.8f);\n}\n\nvoid CMetroidPrime::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) {\n  CPatterned::AddToRenderer(frustum, mgr);\n\n  if (frustum.aabbFrustumTest(*xc50_->GetBounds())) {\n    g_Renderer->AddParticleGen(*xc50_);\n  }\n\n  if (frustum.aabbFrustumTest(*xfac_->GetBounds())) {\n    g_Renderer->AddParticleGen(*xfac_);\n  }\n\n  if (frustum.aabbFrustumTest(*x1024_->GetBounds())) {\n    g_Renderer->AddParticleGen(*x1024_);\n  }\n\n  for (size_t i = 0; i < 2; ++i) {\n    if (frustum.aabbFrustumTest(*xfec_[i]->GetBounds())) {\n      g_Renderer->AddParticleGen(*xfec_[i]);\n    }\n\n    if (x1054_24_) {\n      g_Renderer->AddParticleGen(*x1000_[i]);\n    }\n  }\n}\n\nvoid CMetroidPrime::Render(CStateManager& mgr) {\n  g_Renderer->SetGXRegister1Color(x8d8_beamColor);\n  CPatterned::Render(mgr);\n}\n\nbool CMetroidPrime::CanRenderUnsorted(const CStateManager& mgr) const {\n  return mgr.GetPlayerState()->GetCurrentVisor() != CPlayerState::EPlayerVisor::XRay;\n}\n\nvoid CMetroidPrime::Touch(CActor& act, CStateManager& mgr) {\n  // Empty\n}\n\nvoid CMetroidPrime::DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) {\n  if (type == EUserEventType::ScreenShake) {\n    // TODO: Implement\n    return;\n  }\n  if (type == EUserEventType::EffectOff) {\n    xc50_->SetParticleEmission(false);\n    return;\n  }\n  if (type == EUserEventType::EffectOn && x92c_ == 7) {\n    xc50_->SetParticleEmission(true);\n    if (auto* ent = static_cast<CPlasmaProjectile*>(mgr.ObjectById(xb24_plasmaProjectileIds[x570_]))) {\n      zeus::CColor col = ent->GetInnerColor();\n      col.a() = 1.f;\n      xc50_->SetModulationColor(col);\n    }\n    return;\n  }\n  if (type == EUserEventType::DamageOn) {\n    if (x92c_ == 11) {\n      EnableParticles(mgr, true);\n      x1054_26_ = true;\n    } else if (x92c_ == 7) {\n      FirePlasmaProjectile(mgr, true);\n    }\n  } else if (type == EUserEventType::DamageOff) {\n    if (x92c_ == 1) {\n      EnableParticles(mgr, false);\n    } else if (x92c_ == 7) {\n      FirePlasmaProjectile(mgr, false);\n    }\n    return;\n  }\n  if (type == EUserEventType::Projectile) {\n    if (x92c_ == 6) {\n      auto xf = zeus::lookAt(GetLctrTransform(node.GetLocatorName()).origin + zeus::CVector3f{0.f, 0.f, 1.5f},\n                             mgr.GetPlayer().GetTranslation());\n      float randAngle = zeus::degToRad(mgr.GetActiveRandom()->Range(-20.f, 20.f));\n      xf.rotateLocalZ(randAngle);\n      TUniqueId uid = mgr.AllocateUniqueId();\n      auto gen1 = g_SimplePool->GetObj(SObjectTag{SBIG('PART'), x930_.x4_particle1});\n      auto gen2 = g_SimplePool->GetObj(SObjectTag{SBIG('PART'), x930_.x8_particle2});\n      auto gen3 = g_SimplePool->GetObj(SObjectTag{SBIG('PART'), x930_.xc_particle3});\n      mgr.AddObject(new CIceAttackProjectile(gen1, gen2, gen3, uid, GetAreaIdAlways(), GetUniqueId(), true, xf,\n                                             x930_.x10_dInfo, {-1.f, 1.f}, x930_.x2c_, zeus::degToRad(x930_.x30_),\n                                             x930_.x34_texture, x930_.x38_, x930_.x3a_, {}));\n    }\n  }\n  CPatterned::DoUserAnimEvent(mgr, node, type, dt);\n}\n\nvoid CMetroidPrime::SelectTarget(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x330_stateMachineState.SetDelay(zeus::clamp(0.2f, x924_ * 0.25f, 1.f));\n  } else if (msg == EStateMsg::Update) {\n    sub80275800(mgr);\n  } else if (msg == EStateMsg::Deactivate) {\n    sub802738d4(mgr);\n    x1054_27_ = false;\n  }\n}\n\nvoid CMetroidPrime::Run(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x92c_ = 10;\n    x1084_ = 1.9666666f;\n    TUniqueId wpId = GetNextAttackWaypoint(mgr, true);\n    if (TCastToConstPtr<CScriptWaypoint> wp = mgr.GetObjectById(wpId)) {\n      GetBodyController()->SetLocomotionType(sub80275e14(1));\n      x2dc_destObj = wpId;\n      SetDestPos(wp->GetTranslation());\n      x2ec_reflectedDestPos = GetTranslation();\n      x328_24_inPosition = false;\n    }\n    SetEyesParticleEffectState(mgr, false);\n  } else if (msg == EStateMsg::Update) {\n    ApproachDest(mgr);\n  } else if (msg == EStateMsg::Deactivate) {\n    x92c_ = 0;\n    x1078_ = 1;\n    GetBodyController()->SetLocomotionType(skLocomotions[x1078_]);\n    SetEyesParticleEffectState(mgr, true);\n    sub802738d4(mgr);\n  }\n}\n\nvoid CMetroidPrime::Attack(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n    x92c_ = 6;\n    x1084_ = 0.2f;\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::MeleeAttack, &CPatterned::TryMeleeAttack, sub80275e34(9));\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n    x92c_ = 0;\n    sub802738d4(mgr);\n    x1254_ = 2;\n  }\n}\n\nvoid CMetroidPrime::TurnAround(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x92c_ = 9;\n    x32c_animState = EAnimState::Ready;\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::Step, &CPatterned::TryStep, 3);\n    x450_bodyController->SetLocomotionType(skLocomotions[x1078_]);\n    x1078_ = 1;\n    zeus::CVector3f vec = sub8027464c(mgr);\n    if ((vec - GetTranslation()).normalized().dot(mgr.GetPlayer().GetTranslation() - GetTranslation()) < 15.f) {\n      sub802747b8(arg, mgr, vec - GetTranslation());\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x92c_ = 0;\n    x32c_animState = EAnimState::NotReady;\n  }\n}\n\nvoid CMetroidPrime::Active(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x107c_ = 0.4f;\n    x1084_ = x1088_;\n    x3b4_speed = 1.f;\n  } else if (msg == EStateMsg::Update) {\n    if ((x570_ != 0 || x1078_ != 1) && x107c_ < 0.f && x1084_ < 0.f) {\n      x107c_ = x1080_;\n      x1084_ = 0.9f;\n      x1078_ = mgr.GetActiveRandom()->Next() % 3;\n      GetBodyController()->SetLocomotionType(skLocomotions[x1078_]);\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x1088_ = 0.2f;\n  }\n}\n\nvoid CMetroidPrime::InActive(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x1084_ = x1080_;\n    x1084_ = 0.2f;\n    x400_24_hitByPlayerProjectile = false;\n    x914_24_ = true;\n    SetEyesParticleEffectState(mgr, false);\n    x1078_ = 1;\n    GetBodyController()->SetLocomotionType(skLocomotions[x1078_]);\n    UpdateHeadHealthInfo(mgr);\n    x3b4_speed = 1.f;\n  } else if (msg == EStateMsg::Update) {\n    if (x107c_ < 0.f && x1084_ < 0.f) {\n      x107c_ = x1080_;\n      x1084_ = 0.9f;\n      x1078_ = mgr.GetActiveRandom()->Next() % 3;\n      GetBodyController()->SetLocomotionType(skLocomotions[x1078_]);\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x914_24_ = false;\n    x1444_24_ = false;\n    SetEyesParticleEffectState(mgr, true);\n    sub802738d4(mgr);\n    x1084_ = x1080_;\n    x1088_ = x1084_;\n    mgr.SetBossParams(GetUniqueId(), 2860.0f, 91);\n    x8d0_ = x8d4_;\n  }\n}\n\nvoid CMetroidPrime::CoverAttack(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n    x92c_ = 1;\n    x1084_ = 1.9666666f;\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::MeleeAttack, &CPatterned::TryMeleeAttack, sub80275e34(0));\n    zeus::CVector3f vec = (16.f * GetTransform().frontVector()) + GetTranslation();\n    zeus::CVector3f direction = (vec - GetTranslation()).normalized();\n    if (direction.dot(mgr.GetPlayer().GetTranslation() - GetTranslation()) < 15.f) {\n      sub802747b8(arg, mgr, vec - mgr.GetPlayer().GetTranslation());\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n    x92c_ = 0;\n    x1078_ = 1;\n    GetBodyController()->SetLocomotionType(skLocomotions[x1078_]);\n  }\n}\n\nvoid CMetroidPrime::Crouch(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::MeleeAttack, &CPatterned::TryMeleeAttack, 5);\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n    sub802738d4(mgr);\n  }\n}\n\nvoid CMetroidPrime::Taunt(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::Taunt, &CPatterned::TryTaunt, u32(skTaunts[x1078_]));\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n  }\n}\n\nvoid CMetroidPrime::Suck(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    GetBodyController()->SetLocomotionType(sub80275e14(10));\n    x92c_ = 11;\n    x1054_25_ = false;\n    x1084_ = 0.8f;\n    mgr.GetPlayer().AttachActorToPlayer(GetUniqueId(), false);\n  } else if (msg == EStateMsg::Deactivate) {\n    mgr.GetPlayer().DetachActorFromPlayer();\n    x1078_ = 1;\n    GetBodyController()->SetLocomotionType(skLocomotions[x1078_]);\n    x92c_ = 0;\n    EnableParticles(mgr, false);\n    sub802738d4(mgr);\n    x1088_ = 0.6f;\n    if (mgr.GetPlayer().GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Morphed) {\n      mgr.GetPlayer().TryToBreakOrbit(GetUniqueId(), CPlayer::EPlayerOrbitRequest::ActivateOrbitSource, mgr);\n      x402_28_isMakingBigStrike = true;\n      x504_damageDur = 0.35f;\n      mgr.SendScriptMsgAlways(mgr.GetPlayer().GetUniqueId(), GetUniqueId(), EScriptObjectMessage::Damage);\n      mgr.GetPlayer().ApplyImpulseWR(\n          60.f * (mgr.GetPlayer().GetMass() * (mgr.GetPlayer().GetTranslation() - GetTranslation()).normalized()), {});\n    }\n    x1054_27_ = true;\n  }\n}\n\nvoid CMetroidPrime::ProjectileAttack(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n    x92c_ = 7;\n    x1088_ = 1.0999999f;\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::ProjectileAttack, &CPatterned::TryProjectileAttack, sub80275e34(x1254_));\n    if (x32c_animState == EAnimState::Repeat) {\n      x1078_ = 1;\n      GetBodyController()->SetLocomotionType(skLocomotions[x1078_]);\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n    x92c_ = 0;\n    sub802738d4(mgr);\n    x1088_ = 1.2166667f;\n    xc50_->SetParticleEmission(false);\n    FirePlasmaProjectile(mgr, false);\n    x1254_ = 2;\n  }\n}\n\nvoid CMetroidPrime::Flinch(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n    SetEyesParticleEffectState(mgr, false);\n    DisableHeadOrbitAndTarget(mgr);\n    x8f4_28_ = false;\n    x8f4_27_ = false;\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::KnockBack, &CPatterned::TryKnockBack_Front, 5);\n    if (x428_damageCooldownTimer < 0.25f * 0.33f) {\n      x428_damageCooldownTimer = 0.33f;\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n    SetEyesParticleEffectState(mgr, true);\n    x1078_ = 1;\n    GetBodyController()->SetLocomotionType(skLocomotions[x1078_]);\n    EnableHeadOrbitAndTarget(mgr);\n  }\n}\n\nvoid CMetroidPrime::Dodge(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n    x1078_ = 1;\n    GetBodyController()->SetLocomotionType(skLocomotions[x1078_]);\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::Step, &CPatterned::TryStep, 0);\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n  }\n}\n\nvoid CMetroidPrime::Retreat(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n    SendStateToRelay(EScriptObjectState::Zero, mgr);\n    if (TCastToConstPtr<CScriptWaypoint> wp =\n            mgr.GetObjectById(GetWaypointForBehavior(mgr, EScriptObjectState::CloseIn, EScriptObjectMessage::Follow))) {\n      SetTransform(wp->GetTransform());\n    }\n    x1078_ = 1;\n    GetBodyController()->SetLocomotionType(skLocomotions[x1078_]);\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::Scripted, &CPatterned::TryScripted, x918_);\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n    if (TCastToConstPtr<CScriptWaypoint> wp =\n            mgr.GetObjectById(GetWaypointForBehavior(mgr, EScriptObjectState::Retreat, EScriptObjectMessage::Follow))) {\n      SetTransform(wp->GetTransform());\n    }\n    ++x91c_;\n  }\n}\n\nvoid CMetroidPrime::Cover(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n    x92c_ = 12;\n    x1084_ = 1.2666667f;\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::ProjectileAttack, &CPatterned::TryProjectileAttack, sub80275e34(13));\n    if (x32c_animState == EAnimState::Repeat) {\n      x1078_ = 1;\n      GetBodyController()->SetLocomotionType(skLocomotions[x1078_]);\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n    x92c_ = 0;\n    sub802738d4(mgr);\n    x1254_ = 2;\n  }\n}\n\nvoid CMetroidPrime::Approach(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::MeleeAttack, &CPatterned::TryMeleeAttack, 2);\n    if (x32c_animState == EAnimState::Repeat) {\n      x1078_ = 1;\n      GetBodyController()->SetLocomotionType(skLocomotions[x1078_]);\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n    SetEyesParticleEffectState(mgr, true);\n    sub802738d4(mgr);\n  }\n}\n\nvoid CMetroidPrime::Enraged(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg != EStateMsg::Activate) {\n    return;\n  }\n  sub802786fc(mgr);\n}\n\nvoid CMetroidPrime::SpecialAttack(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n    if (x1254_ == 2) {\n      x92c_ = 2;\n    } else if (x1254_ == 3) {\n      x92c_ = 3;\n    } else if (x1254_ == 4) {\n      x92c_ = 4;\n    } else if (x1254_ == 5) {\n      x92c_ = 5;\n    }\n    x1084_ = 1.2666667f;\n    sub80274054(mgr);\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::ProjectileAttack, &CPatterned::TryProjectileAttack, sub80275e34(x1254_));\n    if (x32c_animState == EAnimState::Repeat) {\n      x1078_ = 1;\n      GetBodyController()->SetLocomotionType(skLocomotions[x1078_]);\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n    x92c_ = 0;\n    sub802738d4(mgr);\n  }\n}\n\nvoid CMetroidPrime::Growth(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg != EStateMsg::Activate) {\n    return;\n  }\n  x3b4_speed = 1.4f;\n}\n\nvoid CMetroidPrime::Land(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg != EStateMsg::Activate) {\n    return;\n  }\n\n  sub802786fc(mgr);\n}\n\nbool CMetroidPrime::TooClose(CStateManager& mgr, float arg) { return CPatterned::TooClose(mgr, arg); }\n\nbool CMetroidPrime::InMaxRange(CStateManager& mgr, float arg) {\n  return CPatterned::InMaxRange(mgr, arg) || (sub80277224(3.f, mgr) && sub80277224(7.f, mgr) && sub80277224(15.f, mgr));\n}\n\nbool CMetroidPrime::PlayerSpot(CStateManager& mgr, float arg) { return mgr.GetPlayer().GetFrozenState(); }\n\nbool CMetroidPrime::ShouldAttack(CStateManager& mgr, float arg) { return x1254_ == 9; }\n\nbool CMetroidPrime::ShouldDoubleSnap(CStateManager& mgr, float arg) {\n  return !(!x328_24_inPosition && x2dc_destObj == kInvalidUniqueId && sub80277224(11.f, mgr));\n}\n\nbool CMetroidPrime::InPosition(CStateManager& mgr, float arg) { return x1084_ <= 0.f; }\n\nbool CMetroidPrime::ShouldTurn(CStateManager& mgr, float arg) {\n  return GetTransform().frontVector().dot(mgr.GetPlayer().GetTranslation() - GetTranslation()) < 0.f;\n}\n\nbool CMetroidPrime::CoverCheck(CStateManager& mgr, float arg) { return sub80277224(-8.f, mgr); }\n\nbool CMetroidPrime::CoverFind(CStateManager& mgr, float arg) { return x1254_ == 12; }\n\nbool CMetroidPrime::CoveringFire(CStateManager& mgr, float arg) { return x1254_ == 13; }\n\nbool CMetroidPrime::AggressionCheck(CStateManager& mgr, float arg) {\n  return (mgr.GetPlayer().GetTranslation() - GetLctrTransform(\"Jaw_1\"sv).origin).magSquared() < 324.f;\n}\n\nbool CMetroidPrime::AttackOver(CStateManager& mgr, float arg) { return x8f4_28_ || x8f4_27_ || x1054_25_; }\n\nbool CMetroidPrime::ShouldFire(CStateManager& mgr, float arg) { return x1254_ == 6 || x1254_ == 7 || x1254_ == 8; }\n\nbool CMetroidPrime::ShouldFlinch(CStateManager& mgr, float arg) { return x8f4_27_; }\n\nbool CMetroidPrime::ShouldRetreat(CStateManager& mgr, float arg) { return x8f4_28_; }\n\nbool CMetroidPrime::ShouldCrouch(CStateManager& mgr, float arg) { return x1254_ == 10; }\n\nbool CMetroidPrime::ShouldMove(CStateManager& mgr, float arg) { return x1254_ == 1; }\n\nbool CMetroidPrime::AIStage(CStateManager& mgr, float arg) {\n  return (arg < 0.25f && x1078_ == 0) || (arg >= 0.75f && x1078_ == 2) || (x1078_ == 1 && arg > 0.25f && arg <= 0.75f);\n}\n\nbool CMetroidPrime::StartAttack(CStateManager& mgr, float arg) { return x920_ <= 0.f; }\n\nbool CMetroidPrime::ShouldSpecialAttack(CStateManager& mgr, float arg) {\n  return x1254_ == 2 || x1254_ == 3 || x1254_ == 4 || x1254_ == 5;\n}\n\nbool CMetroidPrime::CodeTrigger(CStateManager& mgr, float arg) { return x1444_24_; }\n\nCProjectileInfo* CMetroidPrime::GetProjectileInfo() {\n  if (x92c_ == 5) {\n    return &xd74_;\n  }\n  if (x92c_ < 5 && x92c_ > 1) {\n    return &xc78_;\n  }\n\n  return nullptr;\n}\n\nvoid CMetroidPrime::sub802738d4(CStateManager& mgr) { x920_ = mgr.GetActiveRandom()->Range(x924_, x928_); }\n\nvoid CMetroidPrime::UpdateEnergyBalls(float dt, CStateManager& mgr) {\n  if (x1074_ > 0.f) {\n    for (size_t i = 0; i < x106c_energyBallIds.size(); ++i) {\n      if (auto* ball = CPatterned::CastTo<CEnergyBall>(mgr.ObjectById(x106c_energyBallIds[i]))) {\n        ball->SetTransform(GetLctrTransform(skDrillerLocators[i]));\n      }\n    }\n  } else {\n    x106c_energyBallIds.clear();\n  }\n}\n\nu32 CMetroidPrime::CountEnergyBalls(CStateManager& mgr) {\n  u32 ret = 0;\n  for (auto* ent : mgr.GetPhysicsActorObjectList()) {\n    if (CPatterned::CastTo<CEnergyBall>(ent) != nullptr && ent->GetAreaIdAlways() == GetAreaIdAlways() &&\n        ent->GetActive()) {\n      ++ret;\n    }\n  }\n  return ret;\n}\n\nvoid CMetroidPrime::DeactivatePatrolObjects(CStateManager& mgr) {\n  const TCastToConstPtr<CMetroidPrimeRelay> relay = mgr.GetObjectById(x568_relayId);\n  x1058_.clear();\n  if (relay) {\n    for (const auto& conn : relay->GetConnectionList()) {\n      if (conn.x0_state != EScriptObjectState::Patrol) {\n        continue;\n      }\n      if (auto* ent = mgr.ObjectById(mgr.GetIdForScript(conn.x8_objId))) {\n        ent->AcceptScriptMsg(EScriptObjectMessage::Deactivate, GetUniqueId(), mgr);\n        x1058_.push_back(conn.x8_objId);\n        if (x1058_.size() >= 4) {\n          break;\n        }\n      }\n    }\n  }\n}\n\nvoid CMetroidPrime::UpdatePhysicsDummy(CStateManager& mgr) {\n  if (TCastToPtr<CPhysicsActor> physAct = mgr.ObjectById(xeac_)) {\n    zeus::CVector3f diffVec = mgr.GetPlayer().GetTranslation() - GetTranslation();\n    if (!zeus::close_enough(diffVec, zeus::skZero3f)) {\n      zeus::CVector3f direction = diffVec.normalized();\n      physAct->SetVelocityWR((direction.dot(GetTransform().frontVector()) > 0.f ? 7.5f : 5.f) * direction);\n    } else {\n      physAct->SetVelocityWR(zeus::skZero3f);\n    }\n  }\n}\n\nvoid CMetroidPrime::sub80274054(CStateManager& mgr) {\n  if (TCastToPtr<CActor> act = mgr.ObjectById(xeac_)) {\n    act->SetTranslation(mgr.GetPlayer().GetTranslation());\n  }\n}\n\nvoid CMetroidPrime::sub802740cc(CStateManager& mgr) {}\n\nvoid CMetroidPrime::CreatePhysicsDummy(CStateManager& mgr) {\n  xeac_ = mgr.AllocateUniqueId();\n  mgr.AddObject(new CPhysicsDummy(xeac_, true, \"\"sv, CEntityInfo(GetAreaIdAlways(), NullConnectionList)));\n}\n\nvoid CMetroidPrime::SetBillboardEmission(CStateManager& mgr, bool emission) {\n  if (TCastToPtr<CHUDBillboardEffect> billboard = mgr.ObjectById(x1044_billboardId)) {\n    billboard->GetParticleGen()->SetParticleEmission(emission);\n  }\n}\n\nvoid CMetroidPrime::FreeBillboard(CStateManager& mgr) { mgr.FreeScriptObject(x1044_billboardId); }\n\nzeus::CVector3f CMetroidPrime::sub8027464c(CStateManager& mgr) {\n  TUniqueId uid = GetWaypointForBehavior(mgr, EScriptObjectState::Attack, EScriptObjectMessage::Follow);\n\n  float dVar4 = 0.f;\n  zeus::CVector3f tmpVec;\n  for (; uid != kInvalidUniqueId;) {\n    auto wp = TCastToConstPtr<CScriptWaypoint>(mgr.GetObjectById(uid));\n    if (wp) {\n      tmpVec += wp->GetTranslation();\n      dVar4 += 1.f;\n    }\n    uid = wp->NextWaypoint(mgr);\n  }\n\n  if (dVar4 <= 0.f) {\n    return zeus::skZero3f;\n  }\n\n  return (1.f / dVar4) * tmpVec;\n}\n\nvoid CMetroidPrime::CreateHUDBillBoard(CStateManager& mgr) {\n  x1044_billboardId = mgr.AllocateUniqueId();\n  mgr.AddObject(new CHUDBillboardEffect(\n      {x101c_}, std::nullopt, x1044_billboardId, true, \"\"sv, CHUDBillboardEffect::GetNearClipDistance(mgr),\n      CHUDBillboardEffect::GetScaleForPOV(mgr), zeus::skWhite, zeus::skOne3f, zeus::skZero3f));\n}\n\nvoid CMetroidPrime::sub802747b8(float f1, CStateManager& mgr, const zeus::CVector3f& vec) {\n  if (mgr.RayCollideWorld(mgr.GetPlayer().GetTranslation(), mgr.GetPlayer().GetTranslation() + (0.2f * zeus::skDown),\n                          CMaterialFilter::MakeInclude({EMaterialTypes::Floor, EMaterialTypes::Platform}), this)) {\n    mgr.GetPlayer().ApplyImpulseWR((10.f * mgr.GetPlayer().GetMass()) * zeus::skUp, {});\n    mgr.GetPlayer().SetMoveState(CPlayer::EPlayerMovementState::ApplyJump, mgr);\n  }\n  zeus::CVector3f vec2 = vec;\n  vec2.z() = 0.f;\n  if (!zeus::close_enough(vec2, zeus::skZero3f)) {\n    mgr.GetPlayer().ApplyImpulseWR(f1 * (mgr.GetPlayer().GetMass() * (120.f * vec2)), {});\n    mgr.GetPlayer().UseCollisionImpulses();\n    mgr.GetPlayer().SetAccelerationChangeTimer(2.f * f1);\n  }\n}\n\nvoid CMetroidPrime::sub802749e8(float f1, float f2, float f3, const zeus::CVector3f& vec1,\n                                   const zeus::CVector3f& vec2, s32 idx) {\n  auto diffVec = vec2 - vec1;\n  const float dist = diffVec.magnitude();\n  const auto& elemGen = xfec_[idx];\n  const auto& swooshGen = x1000_[idx];\n\n  if (!zeus::close_enough(diffVec, zeus::skZero3f)) {\n    zeus::CVector3f v1 = vec1;\n    auto lookAtXf = zeus::lookAt(zeus::skZero3f, diffVec);\n    elemGen->SetParticleEmission(true);\n    s32 count = static_cast<s32>(2.f * dist + 1.f);\n    for (s32 i = 0; i < count; ++i) {\n      float dVar14 = i * static_cast<int>(1.f / static_cast<float>(count));\n      float dVar11 = f1 * (dVar14 * std::cos(static_cast<float>(i) + f3));\n      float fVar15 = static_cast<float>(std::sin(i));\n      const auto v = (i < 1) ? zeus::skZero3f : lookAtXf * zeus::CVector3f{dVar11, 0.f, f2 * (dVar14 * fVar15)};\n      elemGen->SetTranslation(v1 + v);\n      elemGen->ForceParticleCreation(1);\n      v1 += v1 + diffVec;\n    }\n\n    elemGen->SetParticleEmission(false);\n\n    auto& swooshes = swooshGen->GetSwooshes();\n    count = swooshes.size() - 1;\n    v1 = vec1;\n    float fVar14 = vec1.x();\n    float fVar1 = vec1.y();\n    float fVar2 = vec1.z();\n    float fVar3 = swooshes[count].x30_irot;\n    float dVar12 = 1.f / static_cast<float>(count);\n    diffVec = diffVec * dVar12;\n    for (s32 i = 0; i <= count; ++i) {\n      float vec1Z = fVar3;\n      float dVar13 = fVar2;\n      float vec1X = fVar1;\n      float vec1Y = fVar14;\n      float dVar9 = static_cast<float>(i) * dVar12;\n      fVar14 = std::cos(static_cast<float>(i) + f3);\n      float dVar11 = f1 * (dVar9 * fVar14);\n      fVar14 = std::sin(i);\n      const auto v = (i < 1) ? zeus::skZero3f : lookAtXf * zeus::CVector3f{dVar11, 0.f, f2 * (dVar9 * fVar14)};\n\n      swooshes[i].xc_translation = {vec1Y + v.x(), vec1X + fVar14, dVar13 + fVar1};\n      swooshes[i].x38_orientation = lookAtXf;\n      fVar3 = swooshes[i].x30_irot;\n      fVar14 = vec1Y + diffVec.x();\n      fVar1 = vec1X + diffVec.y();\n      swooshes[i].x30_irot = vec1Z;\n      fVar2 = dVar13 + diffVec.z();\n    }\n  }\n}\n\nvoid CMetroidPrime::UpdateParticles(float f1, CStateManager& mgr) {\n  if (GetBodyController()->GetPercentageFrozen() > 0.f && x1054_24_) {\n    EnableParticles(mgr, false);\n    x1054_25_ = true;\n  }\n\n  constexpr std::array<std::string_view, 2> EyeLocators{{\n      \"L_eye_3\"sv,\n      \"R_eye_3\"sv,\n  }};\n  float dVar9 = 0.f;\n  for (size_t i = 0; i < 2; ++i) {\n    x102c_[i] -= f1;\n    x1038_[i] += f1;\n    zeus::CTransform xf = GetLctrTransform(EyeLocators[i]);\n    zeus::CVector3f off =\n        (mgr.GetPlayer().GetTranslation() + (g_tweakPlayer->GetPlayerBallHalfExtent() * zeus::skUp) +\n         (g_tweakPlayer->GetPlayerBallHalfExtent() * xf.origin - mgr.GetPlayer().GetTranslation()).normalized());\n    if (x1054_24_) {\n      float dVar13 = zeus::clamp(0.f, 2.f * (x102c_[i] / 0.3f) - 1.f, 1.f);\n      float dVar12 = 1.f - dVar13;\n      float tmp = zeus::clamp(0.f, 0.5f + (x102c_[i] / 0.3f), 1.f);\n      zeus::CVector3f local_154 = off * dVar12 + (xf.origin * dVar13);\n      sub802749e8(2.f * tmp, 0.5f * tmp, x1038_[i], xf.origin, local_154, i);\n      if (tmp <= 0.f) {\n        if (mgr.RayCollideWorld(xf.origin, local_154,\n                                CMaterialFilter::MakeIncludeExclude(\n                                    {EMaterialTypes::Solid}, {EMaterialTypes::Player, EMaterialTypes::CollisionActor}),\n                                this)) {\n          x1054_25_ = true;\n        }\n      } else if (dVar13 <= 0.f) {\n        dVar9 += 1.f - tmp;\n      }\n      x1024_->SetTranslation(local_154);\n    } else if (CParticleSwoosh::GetAliveParticleSystemCount() != 0) {\n      float tmp1 = zeus::clamp(0.f, 3.f * (x102c_[i] / 0.3f) - 2.f, 1.f);\n      float tmp2 = zeus::clamp(0.f, 0.5f + (x102c_[i] / 0.3f), 1.f);\n      sub802749e8(tmp2, tmp2, x1038_[i], xf.origin, xf.origin * (1.f - tmp1) + (off * tmp1), i);\n      x1000_[i]->Update(f1);\n    }\n    xfec_[i]->Update(f1);\n  }\n  x1024_->Update(f1);\n  if ((0.5f * dVar9) > 0.9f && x1054_24_) {\n    x1024_->SetParticleEmission(true);\n    SetBillboardEmission(mgr, true);\n  }\n\n  if (zeus::close_enough(0.f, dVar9)) {\n    return;\n  }\n\n  if (mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed) {\n    if (mgr.GetPlayer().GetMorphBall()->IsBoosting()) {\n      x1054_25_ = true;\n    }\n    if (mgr.GetPlayer().GetAttachedActorStruggle() == 1.f) {\n      x1054_25_ = true;\n    }\n  }\n  zeus::CTransform jawXf = GetLctrTransform(\"Jaw_1\"sv);\n  zeus::CVector3f direction = (jawXf.origin - mgr.GetPlayer().GetTranslation()).normalized();\n\n  float offX = (mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed ? 31.49f\n                : mgr.GetPlayer().GetOrbitState() == CPlayer::EPlayerOrbitState::NoOrbit                 ? 136.5f\n                                                                                                         : 20.4749f);\n  if ((x1054_26_ && dVar9 > 0.75f) || x1048_ > 0.f) {\n    if (!x1054_26_) {\n      x1048_ -= f1;\n    } else {\n      x1054_26_ = false;\n      x1048_ = std::sqrt(1.5f / GravityConstant());\n    }\n    mgr.GetPlayer().ApplyImpulseWR(f1 * (mgr.GetPlayer().GetMass() * std::sqrt(1.5f * GravityConstant()) * zeus::skUp),\n                                   {});\n    mgr.GetPlayer().SetMoveState(CPlayer::EPlayerMovementState::ApplyJump, mgr);\n  }\n\n  mgr.GetPlayer().ApplyImpulseWR(f1 * ((0.5f * dVar9) * offX + direction), {});\n  mgr.GetPlayer().UseCollisionImpulses();\n  mgr.GetPlayer().SetAccelerationChangeTimer(2.f * f1);\n}\n\nvoid CMetroidPrime::EnableParticles(CStateManager& mgr, bool b1) {\n  for (size_t i = 0; i < 2; ++i) {\n    x1000_[i]->SetParticleEmission(b1);\n    if (!b1) {\n      x1024_->SetParticleEmission(false);\n      SetBillboardEmission(mgr, false);\n    } else if (x1054_24_ != b1) {\n      x102c_[i] = 0.3f;\n      for (size_t j = 0; j < x1000_[i]->GetSwooshes().size(); ++j) {\n        x1000_[i]->ForceOneUpdate(0.f);\n      }\n    }\n  }\n  x1054_24_ = b1;\n}\n\nvoid CMetroidPrime::EnableHeadOrbitAndTarget(CStateManager& mgr) {\n  if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(x8cc_headColActor)) {\n    colAct->AddMaterial(EMaterialTypes::Target, EMaterialTypes::Orbit, mgr);\n  }\n}\n\nvoid CMetroidPrime::DisableHeadOrbitAndTarget(CStateManager& mgr) {\n  if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(x8cc_headColActor)) {\n    colAct->RemoveMaterial(EMaterialTypes::Target, EMaterialTypes::Orbit, mgr);\n  }\n}\n\nvoid CMetroidPrime::UpdateTimers(float dt) {\n  if (GetBodyController()->GetPercentageFrozen() != 0.f) {\n    return;\n  }\n\n  x107c_ -= dt;\n  x1084_ -= dt * GetModelData()->GetAnimationData()->GetSpeedScale();\n  x920_ -= dt;\n}\n\nvoid CMetroidPrime::sub80275800(CStateManager& mgr) {\n  TUniqueId tmpId = GetNextAttackWaypoint(mgr, true);\n\n  u32 flags = 0x13c1;\n  if (tmpId != kInvalidUniqueId) {\n    if (TCastToConstPtr<CScriptWaypoint> wp = mgr.GetObjectById(tmpId)) {\n      if ((wp->GetTranslation() - GetTranslation()).magSquared() > 16.f) {\n        flags = 0x13c3;\n      }\n    }\n  }\n\n  if (!x1054_27_) {\n    flags |= 0x400;\n  }\n  if (GetTransform().frontVector().dot(mgr.GetPlayer().GetTranslation() - GetTranslation()) > 30.f) {\n    flags |= 0x3c;\n  }\n\n  if (CountEnergyBalls(mgr) == 0) {\n    flags |= 0x2000;\n  }\n\n  if (sub80277224(7.f, mgr)) {\n    flags |= 0x800;\n  }\n\n  sub802759a8(mgr, flags);\n}\n\nvoid CMetroidPrime::sub802759a8(CStateManager& mgr, u32 w1) {\n  float dVar7 = 0.f;\n  const auto& parms = x1160_[x570_];\n  for (size_t i = 0; i < 14; ++i) {\n    if (((w1 & (1 << i)) != 0u) && sub80275d68(i)) {\n      dVar7 += sub80275b04(parms, i);\n    }\n  }\n\n  if (dVar7 > 30000.0f) {\n    sub80275b68();\n  }\n\n  dVar7 = mgr.GetActiveRandom()->Range(0.f, dVar7);\n  x1254_ = -1;\n  float dVar5 = 0.f;\n  for (size_t i = 0; i < 13; ++i) {\n    if (((w1 & (1 << i)) != 0u) && sub80275d68(i)) {\n      float dVar6 = sub80275b04(parms, i);\n      if ((dVar5 < dVar7) && (dVar7 < (dVar5 + dVar6))) {\n        x1254_ = i;\n        x1258_[i] += 3.f;\n        break;\n      }\n\n      dVar5 += dVar6;\n    }\n  }\n}\n\nfloat CMetroidPrime::sub80275b04(const CMetroidPrimeAttackWeights& roomParms, int w2) {\n  float dVar1 = 0.f;\n  if (!zeus::close_enough(0.f, x1258_[w2])) {\n    const float tmpFloat = roomParms.GetFloatValue(w2);\n    dVar1 = (tmpFloat * tmpFloat) / x1258_[w2];\n  }\n\n  return dVar1;\n}\n\nvoid CMetroidPrime::sub80275b68() {\n  float fVar9 = 0.f;\n  for (float f : x1258_) {\n    fVar9 += f;\n  }\n\n  if (zeus::close_enough(fVar9, 0.f)) {\n    return;\n  }\n\n  for (float& f : x1258_) {\n    f /= fVar9;\n  }\n}\n\nvoid CMetroidPrime::sub80275c60(CStateManager& mgr, int w1) {\n  if (x570_ == -1) {\n    return;\n  }\n\n  x1258_.clear();\n\n  for (size_t i = 0; i < 14; ++i) {\n    x1258_.push_back(x1160_[x570_].GetFloatValue(i));\n  }\n\n  if (x1078_ == -1) {\n    return;\n  }\n\n  for (size_t i = 0; i < 40; ++i) {\n    sub802759a8(mgr, -1);\n  }\n}\n\nbool CMetroidPrime::sub80275d68(int w1) {\n  // TODO(antidote): Simplify expressions and rename globals\n  const s32 iVar1 = skSomeValues1[w1];\n  if (iVar1 == 7 || iVar1 == 18) {\n    return (-1 - ((skSomeMeleeValues[w1][x1078_] >> 24) | (((skSomeMeleeValues[w1][x1078_] + 1) >> 24) >> 7))) != 0;\n  }\n  if (iVar1 == 5) {\n    return (-1 - ((int(skSomeValues2[w1][x1078_]) >> 24) | (((int(skSomeValues2[w1][x1078_]) + 1) >> 24) >> 7))) != 0;\n  }\n\n  return iVar1 == 17;\n}\n\npas::ELocomotionType CMetroidPrime::sub80275e14(int w1) { return skSomeValues2[w1][x1078_]; }\n\nu32 CMetroidPrime::sub80275e34(int w1) const { return skSomeMeleeValues[w1][x1078_]; }\n\nvoid CMetroidPrime::UpdateElectricEffect(float dt, CStateManager& mgr) {\n  if (!xfac_) {\n    return;\n  }\n\n  xfac_->SetGlobalOrientation(GetTransform().getRotation());\n  xfac_->SetGlobalTranslation(GetTranslation());\n  xfac_->SetGlobalScale(GetModelData()->GetScale());\n  if (xfc0_) {\n    CSfxManager::UpdateEmitter(xfbc_, GetTranslation(), zeus::skZero3f, 1.f);\n    xfac_->SetParticleEmission(true);\n    const auto* animData = GetModelData()->GetAnimationData();\n    for (size_t i = 0; i < 4; ++i) {\n      xfac_->SetOverrideIPos(\n          animData\n              ->GetLocatorTransform(animData->GetLocatorSegId(skLegLocators[mgr.GetActiveRandom()->Range(0, 19)]),\n                                    nullptr)\n              .origin);\n      xfac_->SetOverrideFPos(\n          animData\n              ->GetLocatorTransform(animData->GetLocatorSegId(skLegLocators[mgr.GetActiveRandom()->Range(0, 19)]),\n                                    nullptr)\n              .origin);\n      xfac_->ForceParticleCreation(1);\n    }\n\n    xfac_->SetParticleEmission(false);\n    xfb4_ -= dt;\n    if (xfb4_ <= 0.f) {\n      sub80276204(mgr, false);\n    }\n  }\n  xfac_->Update(dt);\n}\n\nvoid CMetroidPrime::UpdateSfxEmitter(float f1, CStateManager& mgr) {\n  if (!xfc1_) {\n    return;\n  }\n\n  xfb8_ -= f1;\n  if (xfb8_ <= 0.f) {\n    sub8027639c(mgr, false);\n  }\n\n  if (xfbc_) {\n    CSfxManager::UpdateEmitter(xfbc_, GetTranslation(), zeus::skZero3f, 1.f);\n  }\n}\n\nvoid CMetroidPrime::sub80276204(CStateManager& mgr, bool b1) {\n  if (b1 && xfc1_) {\n    sub8027639c(mgr, false);\n  }\n\n  xfc0_ = b1;\n  if (!b1) {\n    CSfxManager::RemoveEmitter(xfbc_);\n    xfbc_.reset();\n    GetBodyController()->GetCommandMgr().DeliverCmd(CBodyStateCmd{EBodyStateCmd::StopReaction});\n  } else {\n    if (xfbc_) {\n      xfbc_.reset();\n    }\n    xfbc_ = CSfxManager::AddEmitter(SFXsfx0519, GetTranslation(), zeus::skZero3f, 1.f, true, true, 127, kInvalidAreaId);\n    GetBodyController()->GetCommandMgr().DeliverCmd(\n        CBCAdditiveReactionCmd(pas::EAdditiveReactionType::Electrocution, 1.f, true));\n  }\n}\n\nvoid CMetroidPrime::sub8027639c(CStateManager& mgr, bool b1) {\n  if (b1 && xfc0_) {\n    sub80276204(mgr, false);\n  }\n\n  for (size_t i = 0; i < 4; ++i) {\n    GetModelData()->GetAnimationData()->SetParticleEffectState(skEffectNames[i], b1, mgr);\n  }\n\n  xfc1_ = b1;\n  if (!b1) {\n    CSfxManager::RemoveEmitter(xfbc_);\n    xfbc_.reset();\n  } else {\n    if (xfbc_) {\n      xfbc_.reset();\n    }\n    xfbc_ = CSfxManager::AddEmitter(SFXsfx051A, GetTranslation(), zeus::skZero3f, 1.f, true, true, 127, kInvalidAreaId);\n  }\n}\n\nvoid CMetroidPrime::SetActorAreaId(CStateManager& mgr, TUniqueId uid, TAreaId aid) {\n  if (auto* act = static_cast<CActor*>(mgr.ObjectById(uid))) {\n    mgr.SetActorAreaId(*act, aid);\n  }\n}\n\nvoid CMetroidPrime::UpdateAreaId(CStateManager& mgr) {\n  if (!x914_24_) {\n    return;\n  }\n\n  TAreaId curAreaId = mgr.GetWorld()->GetCurrentAreaId();\n  if (GetAreaIdAlways() == curAreaId) {\n    if (x1444_25_) {\n      x1444_25_ = false;\n      SendStateToRelay(EScriptObjectState::MaxReached, mgr);\n    }\n  } else if (!IsRelayValid(mgr, curAreaId)) {\n    x1444_25_ = true;\n  } else {\n    SetActorAreaId(mgr, GetUniqueId(), curAreaId);\n\n    for (size_t i = 0; i < x56c_collisionManager->GetNumCollisionActors(); ++i) {\n      SetActorAreaId(mgr, x56c_collisionManager->GetCollisionDescFromIndex(i).GetCollisionActorId(), curAreaId);\n    }\n\n    for (const auto& uid : xb24_plasmaProjectileIds) {\n      SetActorAreaId(mgr, uid, curAreaId);\n    }\n\n    SetActorAreaId(mgr, xeac_, curAreaId);\n    UpdateRelay(mgr, GetAreaIdAlways());\n    SendStateToRelay(EScriptObjectState::MaxReached, mgr);\n  }\n}\n\nvoid CMetroidPrime::SendStateToRelay(EScriptObjectState state, CStateManager& mgr) {\n  if (TCastToPtr<CMetroidPrimeRelay> relay = mgr.ObjectById(x568_relayId)) {\n    relay->SendScriptMsgs(state, mgr, EScriptObjectMessage::None);\n  }\n}\n\nvoid CMetroidPrime::GetRelayState(CStateManager& mgr) {\n  x1160_.clear();\n  if (TCastToConstPtr<CMetroidPrimeRelay> relay = mgr.GetObjectById(x568_relayId)) {\n    x1160_ = relay->GetRoomParameters();\n    x8c0_ = relay->GetHealthInfo1();\n    x924_ = relay->Get_xc84();\n    x928_ = relay->Get_xc88();\n    x1080_ = relay->Get_xc8c();\n    x1440_ = relay->Get_xc90();\n    x918_ = relay->Get_xcac();\n    x584_ = relay->Get_xc94();\n    x574_ = relay->Get_xc98();\n    x8d4_ = relay->Get_xcb0();\n    x57c_ = relay->Get_xcb4();\n    sub80275c60(mgr, relay->Get_xcb4());\n  }\n}\n\nTUniqueId CMetroidPrime::GetNextAttackWaypoint(CStateManager& mgr, bool b1) {\n  TUniqueId uid = GetWaypointForBehavior(mgr, EScriptObjectState::Attack, EScriptObjectMessage::Follow);\n  float lastDot = 0.f;\n  TUniqueId lastUid = kInvalidUniqueId;\n  while (uid != kInvalidUniqueId) {\n    if (TCastToConstPtr<CScriptWaypoint> wp = mgr.GetObjectById(uid)) {\n      float dot = GetTransform().frontVector().dot(wp->GetTranslation() - GetTranslation());\n\n      if ((b1 && dot > 0.f && dot > lastDot) || (!b1 && dot < 0.f && dot < lastDot)) {\n        lastUid = uid;\n        lastDot = dot;\n      }\n      uid = wp->NextWaypoint(mgr);\n    } else {\n      uid = kInvalidUniqueId;\n    }\n  }\n\n  return lastUid;\n}\n\nTUniqueId CMetroidPrime::GetWaypointForBehavior(CStateManager& mgr, EScriptObjectState state,\n                                                   EScriptObjectMessage msg) {\n  if (TCastToConstPtr<CMetroidPrimeRelay> relay = mgr.GetObjectById(x568_relayId)) {\n    rstl::reserved_vector<TUniqueId, 8> uids;\n    for (const auto& conn : relay->GetConnectionList()) {\n      if (conn.x0_state != state || conn.x4_msg != msg) {\n        continue;\n      }\n\n      TUniqueId uid = mgr.GetIdForScript(conn.x8_objId);\n      const auto* ent = mgr.GetObjectById(uid);\n      if (ent != nullptr && ent->GetActive()) {\n        uids.push_back(uid);\n        if (uids.size() >= 8) {\n          break;\n        }\n      }\n    }\n\n    if (!uids.empty()) {\n      return uids[mgr.GetActiveRandom()->Next() % uids.size()];\n    }\n  }\n  return kInvalidUniqueId;\n}\n\nvoid CMetroidPrime::UpdateRelay(CStateManager& mgr, TAreaId areaId) {\n  if (x568_relayId != kInvalidUniqueId) {\n    if (TCastToPtr<CMetroidPrimeRelay> relay = mgr.ObjectById(x568_relayId)) {\n      relay->SetMetroidPrimeExoId(kInvalidUniqueId);\n    }\n  }\n\n  TEditorId tmpEditorId = kInvalidEditorId;\n  for (auto* ent : mgr.GetAllObjectList()) {\n    if (TCastToPtr<CMetroidPrimeRelay> relay = ent) {\n      if (relay->GetActive() && relay->GetAreaIdAlways() == areaId) {\n        tmpEditorId = relay->GetEditorId();\n      }\n    }\n  }\n\n  x568_relayId = kInvalidUniqueId;\n  if (tmpEditorId != kInvalidEditorId) {\n    TUniqueId uid = mgr.GetIdForScript(tmpEditorId);\n    x568_relayId = uid;\n    if (TCastToPtr<CMetroidPrimeRelay> relay = mgr.ObjectById(uid)) {\n      relay->SetMetroidPrimeExoId(GetUniqueId());\n    }\n  }\n\n  GetRelayState(mgr);\n  DeactivatePatrolObjects(mgr);\n}\n\nbool CMetroidPrime::IsRelayValid(CStateManager& mgr, TAreaId aid) {\n  TEditorId tmpId = kInvalidEditorId;\n\n  for (const auto* ent : mgr.GetAllObjectList()) {\n    if (TCastToConstPtr<CMetroidPrimeRelay> relay = ent) {\n      if (relay->GetAreaIdAlways() == aid) {\n        tmpId = relay->GetEditorId();\n      }\n    }\n  }\n\n  return tmpId != kInvalidEditorId;\n}\n\nbool CMetroidPrime::sub80277224(float f1, CStateManager& mgr) {\n  TUniqueId uid = GetNextAttackWaypoint(mgr, f1 >= 0.f);\n\n  if (TCastToConstPtr<CScriptWaypoint> wp = mgr.GetObjectById(uid)) {\n    const float scaleMag = f1 * (0.57735026 * GetModelData()->GetScale().magnitude());\n    const float dist = GetTransform().frontVector().dot(wp->GetTranslation() - GetTranslation());\n    return f1 < 0.f ? dist < scaleMag : dist > scaleMag;\n  }\n  return false;\n}\n\nvoid CMetroidPrime::FirePlasmaProjectile(CStateManager& mgr, bool b1) {\n  if (b1) {\n    xc58_curPlasmaProjectile = x570_;\n    if (auto* proj =\n            static_cast<CPlasmaProjectile*>(mgr.ObjectById(xb24_plasmaProjectileIds[xc58_curPlasmaProjectile]))) {\n      if (proj->GetDamageInfo().GetWeaponMode().GetType() == EWeaponType::Power) {\n        proj->AddAttrib(EProjectileAttrib::BigStrike);\n        proj->SetDamageDuration(1.4f);\n      }\n\n      xc60_ = GetTargetVector(mgr);\n      xc6c_ = xc60_;\n      xc5c_ = 0.f;\n      proj->Fire(zeus::lookAt(GetLctrTransform(\"Jaw_1\"sv).origin, xc60_), mgr, false);\n    }\n  } else {\n    for (const auto& plasmaId : xb24_plasmaProjectileIds) {\n      if (auto* proj = static_cast<CPlasmaProjectile*>(mgr.ObjectById(plasmaId))) {\n        if (proj->IsFiring()) {\n          proj->ResetBeam(mgr, false);\n        }\n      }\n    }\n  }\n}\n\nvoid CMetroidPrime::UpdatePlasmaProjectile(float dt, CStateManager& mgr) {\n  zeus::CTransform jawXf = GetLctrTransform(\"Jaw_1\"sv);\n  xc50_->SetTranslation(jawXf.origin);\n  xc50_->SetOrientation(jawXf.getRotation());\n  xc50_->Update(dt);\n  if (xc58_curPlasmaProjectile >= 0 && xc58_curPlasmaProjectile < 4) {\n    if (auto* ent =\n            static_cast<CPlasmaProjectile*>(mgr.ObjectById(xb24_plasmaProjectileIds[xc58_curPlasmaProjectile]))) {\n      if (!ent->GetActive()) {\n        return;\n      }\n\n      if (GetBodyController()->GetPercentageFrozen() > 0.f) {\n        FirePlasmaProjectile(mgr, false);\n      }\n\n      zeus::CTransform xf;\n      xc5c_ = zeus::clamp(0.f, xc5c_ + dt, 1.4f);\n      const float fVar1 = xc5c_ / 1.f;\n      const float fVar2 = 1.f - fVar1;\n      zeus::CVector3f vec1 = xc60_ * fVar2 + xc6c_ * fVar1;\n      zeus::CVector3f vec2 = vec1 - jawXf.origin;\n      if (vec2.normalized().dot(GetTransform().frontVector()) <= zeus::degToRad(40.f)) {\n        xf = (zeus::CQuaternion::lookAt(GetTransform().frontVector(), vec2.normalized(), zeus::degToRad(45.f)) *\n              zeus::CQuaternion(GetTransform().buildMatrix3f()))\n                 .toTransform();\n      } else {\n        xf = zeus::lookAt(jawXf.origin, vec1);\n      }\n\n      ent->UpdateFx(xf, dt, mgr);\n    }\n  }\n}\n\nzeus::CVector3f CMetroidPrime::GetTargetVector(CStateManager& mgr) {\n  constexpr auto MatFilter = CMaterialFilter::MakeIncludeExclude(\n      {EMaterialTypes::Solid}, {EMaterialTypes::Character, EMaterialTypes::Player, EMaterialTypes::Projectile});\n  EntityList nearList;\n  mgr.BuildNearList(nearList, GetTranslation(), zeus::skDown, 150.f, MatFilter, this);\n\n  TUniqueId uid = kInvalidUniqueId;\n  CRayCastResult res =\n      mgr.RayWorldIntersection(uid, mgr.GetPlayer().GetTranslation(), zeus::skDown, 150.f, MatFilter, nearList);\n  const auto pos = 0.5f * (mgr.GetPlayer().GetAimPosition(mgr, 0.f) + mgr.GetPlayer().GetTranslation());\n  if (res.IsInvalid()) {\n    return pos;\n  }\n\n  return res.GetPoint() + zeus::CVector3f{0.f, 0.f, pos.z() - mgr.GetPlayer().GetTranslation().z()};\n}\n\nvoid CMetroidPrime::FreePlasmaProjectiles(CStateManager& mgr) {\n  for (auto& uid : xb24_plasmaProjectileIds) {\n    mgr.FreeScriptObject(uid);\n    uid = kInvalidUniqueId;\n  }\n}\n\nvoid CMetroidPrime::CreatePlasmaProjectiles(CStateManager& mgr) {\n  xc50_->SetParticleEmission(false);\n  xc50_->SetGlobalScale(GetModelData()->GetScale());\n\n  for (size_t i = 0; i < x96c_.size(); ++i) {\n    xb24_plasmaProjectileIds[i] = mgr.AllocateUniqueId();\n    CDamageInfo dInfo = xa80_[i].GetDamage();\n    dInfo.SetWeaponMode(CWeaponMode(EWeaponType::PoisonWater));\n    EProjectileAttrib flags = EProjectileAttrib::PlayerUnFreeze;\n    if (xa80_[i].GetDamage().GetWeaponMode().GetType() == EWeaponType::Ice) {\n      flags = EProjectileAttrib::None;\n    }\n    mgr.AddObject(new CPlasmaProjectile(xa80_[i].Token(), \"\"sv, xa80_[i].GetDamage().GetWeaponMode().GetType(),\n                                        x96c_[i], {}, EMaterialTypes::Character, dInfo, xb24_plasmaProjectileIds[i],\n                                        GetAreaIdAlways(), GetUniqueId(), xb30_[i], true, flags));\n  }\n}\n\nvoid CMetroidPrime::UpdateContactDamage(CStateManager& mgr) {\n  const auto* mData = GetModelData();\n  zeus::CVector3f max = GetTranslation() + zeus::CVector3f{(0.57735026f * mData->GetScale().magnitude()) * 6.f,\n                                                           (0.57735026f * mData->GetScale().magnitude()) * 6.f,\n                                                           (0.57735026f * mData->GetScale().magnitude()) * 5.5f};\n  zeus::CVector3f min = GetTranslation() + zeus::CVector3f{(0.57735026f * mData->GetScale().magnitude()) * -6.f,\n                                                           (0.57735026f * mData->GetScale().magnitude()) * -6.f,\n                                                           (0.57735026f * mData->GetScale().magnitude()) * 2.f};\n  x8f8_ = zeus::CAABox{min, max};\n\n  if (mgr.GetPlayer().GetTouchBounds()->intersects(x8f8_) && x420_curDamageRemTime <= 0.f) {\n    mgr.ApplyDamage(GetUniqueId(), mgr.GetPlayer().GetUniqueId(), GetUniqueId(), GetContactDamage(),\n                    CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), zeus::skZero3f);\n    x420_curDamageRemTime = x424_damageWaitTime;\n  }\n}\n\nvoid CMetroidPrime::UpdateColorChange(float f1, CStateManager& mgr) {\n  if (!x8f4_24_) {\n    return;\n  }\n\n  if (x8e4_ >= 1.f) {\n    x8d8_beamColor = x8e0_;\n    x8f4_24_ = false;\n    GetModelData()->GetAnimationData()->SetParticleEffectState(\"ColorChange\"sv, false, mgr);\n  } else {\n    x8e4_ = std::max(1.f, x8e4_ + f1 / 0.3f);\n    x8d8_beamColor = zeus::CColor::lerp(x8dc_, x8e0_, x8e4_);\n  }\n}\n\nvoid CMetroidPrime::sub80278130(const zeus::CColor& col) {\n  x8e4_ = 0.f;\n  x8f4_24_ = true;\n  x8e0_ = col;\n  x8dc_ = x8d8_beamColor;\n}\n\nvoid CMetroidPrime::UpdateHeadAnimation(float f1) {\n  if (x8e8_headUpAdditiveBodyAnimIndex == -1) {\n    return;\n  }\n  if (!x8f4_25_) {\n    if (x8ec_ > 0.f) {\n      x8ec_ = std::min(0.f, x8ec_ - f1 / 0.1f);\n    }\n  } else if (x8ec_ < 1.f) {\n    x8ec_ = std::max(1.f, x8ec_ + f1 / 0.5f);\n  }\n\n  if (x8ec_ > 0.f || x8f4_26_) {\n    if (x8ec_ <= FLT_EPSILON) {\n      GetModelData()->GetAnimationData()->DelAdditiveAnimation(x8e8_headUpAdditiveBodyAnimIndex);\n      x8f4_26_ = false;\n    } else {\n      GetModelData()->GetAnimationData()->AddAdditiveAnimation(x8e8_headUpAdditiveBodyAnimIndex, x8ec_, true, false);\n      x8f4_26_ = true;\n    }\n  }\n}\n\nvoid CMetroidPrime::sub8027827c(TUniqueId uid, CStateManager& mgr) {\n  if (uid == x8cc_headColActor) {\n    if (TCastToConstPtr<CCollisionActor> colAct = mgr.GetObjectById(uid)) {\n      if (!IsAlive()) {\n        return;\n      }\n\n      if (TCastToConstPtr<CWeapon> wp = mgr.GetObjectById(colAct->GetLastTouchedObject())) {\n        if (colAct->GetDamageVulnerability()->WeaponHurts(wp->GetDamageInfo().GetWeaponMode(), false)) {\n          x428_damageCooldownTimer = 0.33f;\n          if (wp->GetDamageInfo().GetWeaponMode().GetType() == EWeaponType::Ice) {\n            if (TCastToPtr<CCollisionActor> wat = mgr.ObjectById(uid)) {\n              wat->HealthInfo(mgr)->SetHP(wat->HealthInfo(mgr)->GetHP() -\n                                          (0.5f * (x8c8_ - wat->GetHealthInfo(mgr)->GetHP())));\n            }\n          }\n          if (wp->GetDamageInfo().GetWeaponMode().GetType() == EWeaponType::Wave &&\n              (wp->GetDamageInfo().GetWeaponMode().IsCharged() || wp->GetDamageInfo().GetWeaponMode().IsComboed())) {\n            xfb4_ = 1.5f;\n            sub80276204(mgr, true);\n          }\n\n          if (wp->GetDamageInfo().GetWeaponMode().GetType() == EWeaponType::Plasma &&\n              (wp->GetDamageInfo().GetWeaponMode().IsCharged() || wp->GetDamageInfo().GetWeaponMode().IsComboed())) {\n            xfb8_ = 1.5f;\n            sub8027639c(mgr, true);\n            xfc1_ = true;\n          }\n\n          if (wp->GetDamageInfo().GetWeaponMode().GetType() == EWeaponType::Ice &&\n              wp->GetDamageInfo().GetWeaponMode().IsComboed()) {\n            Freeze(mgr, zeus::skZero3f, colAct->GetTranslation() - wp->GetTranslation(), 2.f);\n          }\n        }\n      }\n    }\n  }\n}\n\nvoid CMetroidPrime::sub80278508(CStateManager& mgr, int w1, bool b1) {\n  if (x570_ != w1) {\n    GetModelData()->GetAnimationData()->SetParticleEffectState(\"ColorChange\", true, mgr);\n\n    CAudioSys::C3DEmitterParmData emitterData{\n        GetTranslation(), zeus::skZero3f, 1000.f, 0.1f, 1, SFXsfx0B9A, 1.f, 0.16f, false, 127};\n    CSfxManager::AddEmitter(emitterData, true, 127, false, GetAreaIdAlways());\n  }\n  x570_ = w1;\n  sub80278130(x588_[x570_].x6c_color);\n  if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(x8cc_headColActor)) {\n    if (!b1) {\n      colAct->SetDamageVulnerability(CDamageVulnerability::ImmuneVulnerabilty());\n      mgr.GetPlayer().TryToBreakOrbit(GetUniqueId(), CPlayer::EPlayerOrbitRequest::ActivateOrbitSource, mgr);\n      colAct->RemoveMaterial(EMaterialTypes::Target, EMaterialTypes::Orbit, mgr);\n    } else {\n      colAct->SetDamageVulnerability(x588_[x570_].x4_damageVulnerability);\n      colAct->AddMaterial(EMaterialTypes::Target, EMaterialTypes::Orbit, mgr);\n    }\n  }\n}\n\nvoid CMetroidPrime::sub802786fc(CStateManager& mgr) {\n  int w1 = 0;\n  if (!x584_) {\n    w1 = x570_;\n    u32 iVar2 = 0;\n    do {\n      ++iVar2;\n      if (iVar2 < 10) {\n        w1 = x588_[w1].x70_[mgr.GetActiveRandom()->Next() & 1];\n      } else if (iVar2 < 19) {\n        w1 = x588_[w1].x70_[1];\n      } else {\n        break;\n      }\n    } while (((x57c_ & (1 << w1)) != 0u) || ((x580_ & (1 << w1)) != 0));\n    x580_ |= 1 << w1;\n  } else {\n    w1 = x588_[x570_].x70_[mgr.GetActiveRandom()->Next() & 1];\n  }\n\n  sub80278508(mgr, w1, x8f4_25_);\n  sub80275c60(mgr, w1);\n}\n\nvoid CMetroidPrime::SetEyesParticleEffectState(CStateManager& mgr, bool b) {\n  x8f4_25_ = b;\n  sub80278508(mgr, x570_, b);\n  GetModelData()->GetAnimationData()->SetParticleEffectState(\"Eyes\"sv, b, mgr);\n\n  if (!b) {\n    sub80278130(zeus::skBlack);\n  } else {\n    sub80278130(x588_[x570_].x6c_color);\n  }\n}\n\nvoid CMetroidPrime::UpdateHeadHealthInfo(CStateManager& mgr) {\n  if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(x8cc_headColActor)) {\n    *colAct->HealthInfo(mgr) = x8c0_;\n  }\n}\n\nvoid CMetroidPrime::UpdateHealthInfo(CStateManager& mgr) {\n  if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(x8cc_headColActor)) {\n    auto* hInfo = colAct->HealthInfo(mgr);\n    if (hInfo->GetHP() <= 0.f && !x8f4_28_) {\n      x8f4_28_ = true;\n      --x8d0_;\n      if (x8d0_ == 0) {\n        x400_24_hitByPlayerProjectile = true;\n      }\n    }\n\n    if (x8f4_28_) {\n      UpdateHeadHealthInfo(mgr);\n    }\n\n    if (x91c_ > -1 && x91c_ < 4) {\n      if (x914_24_) {\n        HealthInfo(mgr)->SetHP(skHealthConstants[std::max(0, x91c_ - 1)]);\n      } else {\n        HealthInfo(mgr)->SetHP(std::max(0.f, hInfo->GetHP()) + skHealthConstants[x91c_] +\n                               (static_cast<float>(x8d0_ - 1) * x8c0_.GetHP()));\n      }\n    }\n  }\n}\n\nvoid CMetroidPrime::SetBoneTrackingTarget(CStateManager& mgr, bool active) {\n  for (auto& boneTracking : x76c_) {\n    boneTracking.SetActive(active);\n    boneTracking.SetTarget(mgr.GetPlayer().GetUniqueId());\n  }\n}\n\nvoid CMetroidPrime::UpdateBoneTracking(float dt, CStateManager& mgr) {\n  CAnimData* animData = GetModelData()->GetAnimationData();\n  animData->PreRender();\n  for (auto tracking : x76c_) {\n    tracking.Update(dt);\n    tracking.PreRender(mgr, *animData, GetTransform(), GetModelData()->GetScale(), *GetBodyController());\n  }\n\n  if (xe4_30_outOfFrustum) {\n    xe4_27_notInSortedLists = !x1054_24_;\n  }\n}\n\nvoid CMetroidPrime::DoContactDamage(TUniqueId uid, CStateManager& mgr) {\n  if (!IsAlive()) {\n    return;\n  }\n\n  if (TCastToConstPtr<CCollisionActor> colAct = mgr.GetObjectById(uid)) {\n    if (colAct->GetLastTouchedObject() == mgr.GetPlayer().GetUniqueId()) {\n      if (mgr.GetPlayer().GetFrozenState()) {\n        mgr.GetPlayer().UnFreeze(mgr);\n      }\n\n      if (x420_curDamageRemTime <= 0.f) {\n        mgr.ApplyDamage(GetUniqueId(), colAct->GetLastTouchedObject(), GetUniqueId(), GetContactDamage(),\n                        CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), zeus::skZero3f);\n        x420_curDamageRemTime = x424_damageWaitTime;\n      }\n    } else if (TCastToConstPtr<CActor> act = mgr.GetObjectById(colAct->GetLastTouchedObject())) {\n      if (!act->GetMaterialList().HasMaterial(EMaterialTypes::Platform)) {\n        return;\n      }\n\n      mgr.ApplyDamage(GetUniqueId(), colAct->GetLastTouchedObject(), GetUniqueId(), GetContactDamage(),\n                      CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), zeus::skZero3f);\n    }\n  }\n}\n\nvoid CMetroidPrime::UpdateCollision(float dt, CStateManager& mgr) {\n  x56c_collisionManager->Update(dt, mgr, CCollisionActorManager::EUpdateOptions::ObjectSpace);\n  zeus::CTransform xf = GetLocatorTransform(\"Skeleton_Root\"sv);\n  MoveCollisionPrimitive(GetTransform().rotate(GetModelData()->GetScale() * xf.origin));\n}\n\nvoid CMetroidPrime::SetupBoneTracking() {\n  for (size_t i = 0; i < 6; ++i) {\n    x76c_.emplace_back(*GetModelData()->GetAnimationData(), skBoneTrackingNames[i], zeus::degToRad(80.f),\n                       zeus::degToRad(180.f), EBoneTrackingFlags::NoParentOrigin);\n  }\n}\n\nvoid CMetroidPrime::SetupCollisionActorManager(CStateManager& mgr) {\n  std::vector<CJointCollisionDescription> joints;\n  joints.reserve(skBodyJoints.size() + skSphereJoints.size());\n  for (auto& skBodyJoint : skBodyJoints) {\n    CSegId to = GetModelData()->GetAnimationData()->GetLocatorSegId(skBodyJoint.to);\n    CSegId from = GetModelData()->GetAnimationData()->GetLocatorSegId(skBodyJoint.from);\n    joints.push_back(CJointCollisionDescription::OBBAutoSizeCollision(\n        to, from, skBodyJoint.bounds, CJointCollisionDescription::EOrientationType::One,\n        std::string(skBodyJoint.to) + std::string(skBodyJoint.from), 200.f));\n  }\n\n  for (auto& skSphereJoint : skSphereJoints) {\n    joints.push_back(CJointCollisionDescription::SphereCollision(\n        GetModelData()->GetAnimationData()->GetLocatorSegId(skSphereJoint.name), skSphereJoint.radius,\n        skSphereJoint.name, 200.f));\n  }\n\n  x56c_collisionManager =\n      std::make_unique<CCollisionActorManager>(mgr, GetUniqueId(), GetAreaIdAlways(), joints, GetActive());\n\n  for (size_t i = 0; i < x56c_collisionManager->GetNumCollisionActors(); ++i) {\n    const auto& jInfo = x56c_collisionManager->GetCollisionDescFromIndex(i);\n    if (jInfo.GetName() == \"Head_LockON_SDK\"sv) {\n      x8cc_headColActor = jInfo.GetCollisionActorId();\n    }\n\n    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(jInfo.GetCollisionActorId())) {\n      if (jInfo.GetCollisionActorId() != x1046_) {\n        colAct->SetDamageVulnerability(*GetDamageVulnerability());\n      }\n    }\n  }\n\n  x56c_collisionManager->AddMaterial(mgr, CMaterialList(EMaterialTypes::AIJoint, EMaterialTypes::CameraPassthrough));\n  SetMaterialFilter(CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {EMaterialTypes::Player}));\n  AddMaterial(EMaterialTypes::ProjectilePassthrough, mgr);\n  RemoveMaterial(EMaterialTypes::Solid, EMaterialTypes::Orbit, EMaterialTypes::Solid, mgr);\n  UpdateHeadHealthInfo(mgr);\n}\n\nvoid CMetroidPrime::CPhysicsDummy::Accept(IVisitor& visitor) { visitor.Visit(this); }\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CMetroidPrime.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Camera/CCameraShakeData.hpp\"\n#include \"Runtime/Character/CBoneTracking.hpp\"\n#include \"Runtime/MP1/World/CMetroidPrimeProjectile.hpp\"\n#include \"Runtime/Weapon/CBeamInfo.hpp\"\n#include \"Runtime/Weapon/CPlasmaProjectile.hpp\"\n#include \"Runtime/Weapon/CProjectileInfo.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n#include \"Runtime/World/CPatternedInfo.hpp\"\n#include \"Runtime/rstl.hpp\"\n\n#include <zeus/CColor.hpp>\n\nnamespace metaforce {\nclass CCameraShakeData;\nclass CCollisionActorManager;\nclass CGenDescription;\nclass CElementGen;\nclass CProjectedShadow;\n\nnamespace MP1 {\n\nstruct SPrimeStruct2B {\n  u32 x0_propertyCount;\n  CAssetId x4_particle1;\n  CAssetId x8_particle2;\n  CAssetId xc_particle3;\n  CDamageInfo x10_dInfo;\n  float x2c_;\n  float x30_;\n  CAssetId x34_texture;\n  u16 x38_;\n  u16 x3a_;\n  explicit SPrimeStruct2B(CInputStream& in);\n};\n\nstruct SPrimeStruct4 {\n  CBeamInfo x0_beamInfo;\n  u32 x44_;\n  CDamageInfo x48_dInfo1;\n  CPlasmaProjectile::PlayerEffectResources x64_struct5;\n  float x88_;\n  CDamageInfo x8c_dInfo2;\n  explicit SPrimeStruct4(CInputStream& in);\n};\n\nstruct SPrimeStruct6 {\n  u32 x0_propertyCount;\n  CDamageVulnerability x4_damageVulnerability;\n  zeus::CColor x6c_color;\n  std::array<u32, 2> x70_;\n  explicit SPrimeStruct6(CInputStream& in);\n};\n\nstruct CMetroidPrimeData {\n  u32 x0_propertyCount;\n  CPatternedInfo x4_patternedInfo;\n  CActorParameters x13c_actorParms;\n  u32 x1a4_;\n  CCameraShakeData x1a8_;\n  CCameraShakeData x27c_;\n  CCameraShakeData x350_;\n  SPrimeStruct2B x424_;\n  CAssetId x460_particle1;\n  rstl::reserved_vector<SPrimeStruct4, 4> x464_;\n  CAssetId x708_wpsc1;\n  CDamageInfo x70c_dInfo1;\n  CCameraShakeData x728_shakeData1;\n  CAssetId x7fc_wpsc2;\n  CDamageInfo x800_dInfo2;\n  CCameraShakeData x81c_shakeData2;\n  SPrimeProjectileInfo x8f0_;\n  CDamageInfo x92c_;\n  CCameraShakeData x948_;\n  CAssetId xa1c_particle2;\n  CAssetId xa20_swoosh;\n  CAssetId xa24_particle3;\n  CAssetId xa28_particle4;\n  rstl::reserved_vector<SPrimeStruct6, 4> xa2c_;\n  explicit CMetroidPrimeData(CInputStream& in);\n};\n\nstruct CMetroidPrimeAttackWeights {\n  rstl::reserved_vector<float, 14> x0_;\n  explicit CMetroidPrimeAttackWeights(CInputStream& in);\n\n  float GetFloatValue(size_t idx) const { return x0_[idx]; }\n};\n\nclass CMetroidPrime : public CPatterned {\n  class CPhysicsDummy : public CPhysicsActor {\n  public:\n    DEFINE_ENTITY\n    CPhysicsDummy(TUniqueId uid, bool active, std::string_view name, const CEntityInfo& info)\n    : CPhysicsActor(uid, active, name, info, {}, CModelData::CModelDataNull(),\n                    CMaterialList(EMaterialTypes::Target, EMaterialTypes::ExcludeFromRadar), zeus::CAABox{-1.f, 1.f},\n                    SMoverData(1.f), CActorParameters::None(), 0.3f, 0.1f) {}\n    void Accept(IVisitor& visitor) override;\n  };\n  TUniqueId x568_relayId = kInvalidUniqueId;\n  std::unique_ptr<CCollisionActorManager> x56c_collisionManager;\n  s32 x570_ = 1;\n  u32 x574_ = 1;\n  u32 x578_ = 0;\n  u32 x57c_ = 0;\n  u32 x580_ = 0;\n  bool x584_ = false;\n  rstl::reserved_vector<SPrimeStruct6, 4> x588_;\n  rstl::reserved_vector<CBoneTracking, 6> x76c_;\n  CHealthInfo x8c0_ = CHealthInfo(150.f, 0.f);\n  float x8c8_ = 0.f;\n  TUniqueId x8cc_headColActor = kInvalidUniqueId;\n  u32 x8d0_ = 3;\n  u32 x8d4_ = 3;\n  zeus::CColor x8d8_beamColor = zeus::skBlack;\n  zeus::CColor x8dc_ = zeus::skBlack;\n  zeus::CColor x8e0_ = zeus::skBlack;\n  float x8e4_ = 0.f;\n  s32 x8e8_headUpAdditiveBodyAnimIndex;\n  float x8ec_ = 0.f;\n  float x8f0_ = 0.f;\n  bool x8f4_24_ : 1 = false;\n  bool x8f4_25_ : 1 = false;\n  bool x8f4_26_ : 1 = false;\n  bool x8f4_27_ : 1 = false;\n  bool x8f4_28_ : 1 = false;\n  zeus::CAABox x8f8_;\n  float x910_ = 5.f;\n  bool x914_24_ : 1 = false;\n  s32 x918_ = -1;\n  s32 x91c_;\n  float x920_ = 0.f;\n  float x924_ = 4.f;\n  float x928_ = 5.f;\n  u32 x92c_ = 0;\n  SPrimeStruct2B x930_;\n  rstl::reserved_vector<CBeamInfo, 4> x96c_;\n  rstl::reserved_vector<CProjectileInfo, 4> xa80_;\n  rstl::reserved_vector<TUniqueId, 4> xb24_plasmaProjectileIds = {\n      {kInvalidUniqueId, kInvalidUniqueId, kInvalidUniqueId, kInvalidUniqueId}};\n  rstl::reserved_vector<CPlasmaProjectile::PlayerEffectResources, 4> xb30_;\n  rstl::reserved_vector<CDamageInfo, 4> xbc4_;\n  TLockedToken<CGenDescription> xc48_;\n  std::unique_ptr<CElementGen> xc50_;\n  s32 xc58_curPlasmaProjectile = -1;\n  float xc5c_ = 0.f;\n  zeus::CVector3f xc60_;\n  zeus::CVector3f xc6c_;\n  CProjectileInfo xc78_;\n  CCameraShakeData xca0_;\n  CProjectileInfo xd74_;\n  CCameraShakeData xd9c_;\n  SPrimeProjectileInfo xe70_;\n  TUniqueId xeac_ = kInvalidUniqueId;\n  u32 xeb0_ = 0;\n  CDamageInfo xeb4_;\n  CCameraShakeData xed0_;\n  TLockedToken<CElectricDescription> xfa4_;\n  std::unique_ptr<CParticleElectric> xfac_;\n  float xfb4_ = 0.f;\n  float xfb8_ = 0.f;\n  CSfxHandle xfbc_;\n  bool xfc0_ = false;\n  bool xfc1_ = false;\n  rstl::reserved_vector<TToken<CGenDescription>, 2> xfc4_;\n  rstl::reserved_vector<TToken<CSwooshDescription>, 2> xfd8_;\n  rstl::reserved_vector<std::unique_ptr<CElementGen>, 2> xfec_;\n  rstl::reserved_vector<std::unique_ptr<CParticleSwoosh>, 2> x1000_;\n  TToken<CGenDescription> x1014_;\n  TToken<CGenDescription> x101c_;\n  std::unique_ptr<CElementGen> x1024_;\n  rstl::reserved_vector<float, 2> x102c_;\n  rstl::reserved_vector<float, 2> x1038_;\n  TUniqueId x1044_billboardId = kInvalidUniqueId;\n  TUniqueId x1046_ = kInvalidUniqueId;\n  float x1048_ = 0.f;\n  float x104c_ = 75.f;\n  float x1050_ = 0.f;\n  bool x1054_24_ : 1 = false;\n  bool x1054_25_ : 1 = false;\n  bool x1054_26_ : 1 = false;\n  bool x1054_27_ : 1 = false;\n  rstl::reserved_vector<TEditorId, 4> x1058_;\n  rstl::reserved_vector<TUniqueId, 2> x106c_energyBallIds;\n  float x1074_ = 0.f;\n  s32 x1078_ = -1;\n  float x107c_ = 0.f;\n  float x1080_;\n  float x1084_ = 0.f;\n  float x1088_ = 0.f;\n  CCameraShakeData x108c_;\n  rstl::reserved_vector<CMetroidPrimeAttackWeights, 4> x1160_;\n  s32 x1254_ = -1;\n  rstl::reserved_vector<float, 14> x1258_;\n  CCameraShakeData x1294_;\n  CCameraShakeData x1368_;\n  std::unique_ptr<CProjectedShadow> x143c_;\n  s32 x1440_ = 0;\n  bool x1444_24_ : 1 = false;\n  bool x1444_25_ : 1 = false;\n\n  void sub802738d4(CStateManager& mgr);\n  void UpdateEnergyBalls(float dt, CStateManager& mgr);\n  u32 CountEnergyBalls(CStateManager& mgr);\n  void DeactivatePatrolObjects(CStateManager& mgr);\n  void UpdatePhysicsDummy(CStateManager& mgr);\n  void sub80274054(CStateManager& mgr);\n  void sub802740cc(CStateManager& mgr);\n  void CreatePhysicsDummy(CStateManager& mgr);\n  void SetBillboardEmission(CStateManager& mgr, bool emission);\n  void FreeBillboard(CStateManager& mgr);\n  zeus::CVector3f sub8027464c(CStateManager& mgr);\n  void CreateHUDBillBoard(CStateManager& mgr);\n  void sub802747b8(float f1, CStateManager& mgr, const zeus::CVector3f& vec);\n  void sub802749e8(float f1, float f2, float f3, const zeus::CVector3f& vec1, const zeus::CVector3f& vec2, s32 idx);\n  void UpdateParticles(float f1, CStateManager& mgr);\n  void EnableParticles(CStateManager& mgr, bool b1);\n  void EnableHeadOrbitAndTarget(CStateManager& mgr);\n  void DisableHeadOrbitAndTarget(CStateManager& mgr);\n  void UpdateTimers(float mgr);\n  void sub80275800(CStateManager& mgr);\n  void sub802759a8(CStateManager& mgr, u32 w1);\n  float sub80275b04(const CMetroidPrimeAttackWeights& roomParms, int w2);\n  void sub80275b68();\n  void sub80275c60(CStateManager& mgr, int w1);\n  bool sub80275d68(int w1);\n  pas::ELocomotionType sub80275e14(int w1);\n  u32 sub80275e34(int w1) const;\n  void UpdateElectricEffect(float dt, CStateManager& mgr);\n  void UpdateSfxEmitter(float f1, CStateManager& mgr);\n  void sub80276204(CStateManager& mgr, bool b1);\n  void sub8027639c(CStateManager& mgr, bool b1);\n  void SetActorAreaId(CStateManager& mgr, TUniqueId uid, TAreaId aid);\n  void UpdateAreaId(CStateManager& mgr);\n  void SendStateToRelay(EScriptObjectState state, CStateManager& mgr);\n  void GetRelayState(CStateManager& mgr);\n  TUniqueId GetNextAttackWaypoint(CStateManager& mgr, bool b1);\n  TUniqueId GetWaypointForBehavior(CStateManager& mgr, EScriptObjectState state, EScriptObjectMessage msg);\n  void UpdateRelay(CStateManager& mgr, TAreaId areaId);\n  bool IsRelayValid(CStateManager& mgr, TAreaId w2);\n  bool sub80277224(float f1, CStateManager& mgr);\n  void FirePlasmaProjectile(CStateManager& mgr, bool b1);\n  void UpdatePlasmaProjectile(float dt, CStateManager& mgr);\n  zeus::CVector3f GetTargetVector(CStateManager& mgr);\n  void FreePlasmaProjectiles(CStateManager& mgr);\n  void CreatePlasmaProjectiles(CStateManager& mgr);\n  void UpdateContactDamage(CStateManager& mgr);\n  void UpdateColorChange(float f1, CStateManager& mgr);\n  void sub80278130(const zeus::CColor& col);\n  void UpdateHeadAnimation(float f1);\n  void sub8027827c(TUniqueId uid, CStateManager& mgr);\n  void sub80278508(CStateManager& mgr, int w1, bool b1);\n  void sub802786fc(CStateManager& mgr);\n  void SetEyesParticleEffectState(CStateManager& mgr, bool b);\n  void UpdateHeadHealthInfo(CStateManager& mgr);\n  void UpdateHealthInfo(CStateManager& mgr);\n  void SetBoneTrackingTarget(CStateManager& mgr, bool active);\n  void UpdateBoneTracking(float f1, CStateManager& mgr);\n  void DoContactDamage(TUniqueId uid, CStateManager& mgr);\n  void UpdateCollision(float dt, CStateManager& mgr);\n  void SetupBoneTracking();\n  void SetupCollisionActorManager(CStateManager& mgr);\n\npublic:\n  DEFINE_PATTERNED(MetroidPrimeExo);\n  CMetroidPrime(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                   CModelData&& mData, const CPatternedInfo& pInfo, const CActorParameters& aParms, u32 pw1,\n                   const CCameraShakeData& shakeData1, const CCameraShakeData& shakeData2,\n                   const CCameraShakeData& shakeData3, const SPrimeStruct2B& struct2b, CAssetId particle1,\n                   const rstl::reserved_vector<SPrimeStruct4, 4>& struct4s, CAssetId wpsc1, const CDamageInfo& dInfo1,\n                   const CCameraShakeData& shakeData4, CAssetId wpsc2, const CDamageInfo& dInfo2,\n                   const CCameraShakeData& shakeData5, const SPrimeProjectileInfo& projectileInfo,\n                   const CDamageInfo& dInfo3, const CCameraShakeData& shakeData6, CAssetId particle2, CAssetId swoosh,\n                   CAssetId particle3, CAssetId particle4, const rstl::reserved_vector<SPrimeStruct6, 4>& struct6s);\n\n  void PreThink(float dt, CStateManager& mgr) override;\n  void Think(float dt, CStateManager& mgr) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId other, CStateManager& mgr) override;\n  void PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) override;\n  void AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) override;\n  void Render(CStateManager& mgr) override;\n  bool CanRenderUnsorted(const CStateManager& mgr) const override;\n  void Touch(CActor& act, CStateManager& mgr) override;\n  void DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) override;\n  void SelectTarget(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Run(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Attack(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void TurnAround(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Active(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void InActive(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void CoverAttack(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Crouch(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Taunt(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Suck(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void ProjectileAttack(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Flinch(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Dodge(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Retreat(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Cover(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Approach(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Enraged(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void SpecialAttack(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Growth(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Land(CStateManager& mgr, EStateMsg msg, float arg) override;\n  bool TooClose(CStateManager& mgr, float arg) override;\n  bool InMaxRange(CStateManager& mgr, float arg) override;\n  bool PlayerSpot(CStateManager& mgr, float arg) override;\n  bool ShouldAttack(CStateManager& mgr, float arg) override;\n  bool ShouldDoubleSnap(CStateManager& mgr, float arg) override;\n  bool InPosition(CStateManager& mgr, float arg) override;\n  bool ShouldTurn(CStateManager& mgr, float arg) override;\n  bool ShouldJumpBack(CStateManager& mgr, float arg) override { return x1254_ == 11; }\n  bool CoverCheck(CStateManager& mgr, float arg) override;\n  bool CoverFind(CStateManager& mgr, float arg) override;\n  bool CoveringFire(CStateManager& mgr, float arg) override;\n  bool AggressionCheck(CStateManager& mgr, float arg) override;\n  bool AttackOver(CStateManager& mgr, float arg) override;\n  bool ShouldFire(CStateManager& mgr, float arg) override;\n  bool ShouldFlinch(CStateManager& mgr, float arg) override;\n  bool ShouldRetreat(CStateManager& mgr, float arg) override;\n  bool ShouldCrouch(CStateManager& mgr, float arg) override;\n  bool ShouldMove(CStateManager& mgr, float arg) override;\n  bool AIStage(CStateManager& mgr, float arg) override;\n  bool StartAttack(CStateManager& mgr, float arg) override;\n  bool ShouldSpecialAttack(CStateManager& mgr, float arg) override;\n  bool CodeTrigger(CStateManager& mgr, float arg) override;\n  CProjectileInfo* GetProjectileInfo() override;\n};\n\n} // namespace MP1\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/MP1/World/CMetroidPrimeProjectile.cpp",
    "content": "#include \"Runtime/MP1/World/CMetroidPrimeProjectile.hpp\"\n#include \"Runtime/World/CFire.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n\nnamespace metaforce::MP1 {\n\nSPrimeProjectileInfo::SPrimeProjectileInfo(CInputStream& in)\n: x0_propertyCount(in.ReadLong())\n, x4_particle(g_SimplePool->GetObj(SObjectTag{FOURCC('PART'), CAssetId(in)}))\n, xc_dInfo(in)\n, x28_(in.ReadFloat())\n, x2c_(in.ReadFloat())\n, x30_(in.ReadFloat())\n, x34_texture(in) {\n  x38_24_ = in.ReadBool();\n  x38_25_ = in.ReadBool();\n  x38_26_ = in.ReadBool();\n  x38_27_ = in.ReadBool();\n}\n\nCMetroidPrimeProjectile::CMetroidPrimeProjectile(bool active, const TToken<CWeaponDescription>& desc, EWeaponType type,\n                                                 const zeus::CTransform& xf, EMaterialTypes materials,\n                                                 const CDamageInfo& damage, TUniqueId uid, TAreaId aid, TUniqueId owner,\n                                                 const SPrimeProjectileInfo& auxData, TUniqueId homingTarget,\n                                                 EProjectileAttrib attribs, const zeus::CVector3f& scale,\n                                                 const std::optional<TLockedToken<CGenDescription>>& visorParticle,\n                                                 u16 visorSfx, bool sendCollideMsg)\n: CEnergyProjectile(active, desc, type, xf, materials, damage, uid, aid, owner, homingTarget, attribs, false, scale,\n                    visorParticle, visorSfx, sendCollideMsg)\n, x3d8_auxData(auxData) {}\n\nbool CMetroidPrimeProjectile::Explode(const zeus::CVector3f& pos, const zeus::CVector3f& normal,\n                                      const EWeaponCollisionResponseTypes type, CStateManager& mgr,\n                                      const CDamageVulnerability& dVuln, TUniqueId hitActor) {\n  bool result = CEnergyProjectile::Explode(pos, normal, type, mgr, dVuln, hitActor);\n  if (!x2e4_24_active) {\n    TUniqueId newId(mgr.AllocateUniqueId());\n\n    zeus::CAABox box = zeus::CAABox(zeus::CVector3f(-1.f, -1.f, -1.f), zeus::CVector3f(1.f, 1.f, 1.f))\n            .getTransformedAABox(GetTransform() *\n                                 zeus::CTransform::Scale(x3d8_auxData.GetDamageInfo().GetRadius()));\n\n    CFire* fire = new CFire(x3d8_auxData.x4_particle, newId, GetAreaIdAlways(), true, GetUniqueId(), GetTransform(),\n                            x3d8_auxData.GetDamageInfo(), box, zeus::CVector3f(1.f, 1.f, 1.f),\n                            x3d8_auxData.GetFlag_27(), x3d8_auxData.GetTexture(), x3d8_auxData.GetFlag_24(),\n                            x3d8_auxData.GetFlag_25(), x3d8_auxData.GetFlag_26(), 1.0, x3d8_auxData.Get_0x28(),\n                            x3d8_auxData.Get_0x2c(), x3d8_auxData.Get_0x30());\n    if (fire) {\n      mgr.AddObject(fire);\n    }\n  }\n  return result;\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CMetroidPrimeProjectile.hpp",
    "content": "#pragma once\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/Weapon/CEnergyProjectile.hpp\"\n\nnamespace metaforce::MP1 {\n\nstruct SPrimeProjectileInfo {\n  u32 x0_propertyCount;\n  TToken<CGenDescription> x4_particle;\n  CDamageInfo xc_dInfo;\n  float x28_;\n  float x2c_;\n  float x30_;\n  CAssetId x34_texture;\n  bool x38_24_ : 1;\n  bool x38_25_ : 1;\n  bool x38_26_ : 1;\n  bool x38_27_ : 1;\n  explicit SPrimeProjectileInfo(CInputStream& in);\n  const CDamageInfo& GetDamageInfo() const { return xc_dInfo; }\n  float Get_0x28() const { return x28_; }\n  float Get_0x2c() const { return x2c_; }\n  float Get_0x30() const { return x30_; }\n  CAssetId GetTexture() const { return x34_texture; }\n  const bool GetFlag_24() const { return x38_24_; }\n  const bool GetFlag_25() const { return x38_25_; }\n  const bool GetFlag_26() const { return x38_26_; }\n  const bool GetFlag_27() const { return x38_27_; }\n};\n\nclass CMetroidPrimeProjectile : public CEnergyProjectile {\n  SPrimeProjectileInfo x3d8_auxData;\n\npublic:\n  DEFINE_ENTITY\n  CMetroidPrimeProjectile(bool active, const TToken<CWeaponDescription>& desc, EWeaponType type,\n                          const zeus::CTransform& xf, EMaterialTypes materials, const CDamageInfo& damage,\n                          TUniqueId uid, TAreaId aid, TUniqueId owner, const SPrimeProjectileInfo& auxData,\n                          TUniqueId homingTarget, EProjectileAttrib attribs, const zeus::CVector3f& scale,\n                          const std::optional<TLockedToken<CGenDescription>>& visorParticle, u16 visorSfx,\n                          bool sendCollideMsg);\n\n  bool Explode(const zeus::CVector3f& pos, const zeus::CVector3f& normal, const EWeaponCollisionResponseTypes type,\n               CStateManager& mgr, const CDamageVulnerability& dVuln, TUniqueId hitActor) override;\n};\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CMetroidPrimeRelay.cpp",
    "content": "#include \"Runtime/MP1/World/CMetroidPrimeRelay.hpp\"\n\n#include \"Runtime/MP1/World/CMetroidPrime.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce::MP1 {\n\nCMetroidPrimeRelay::CMetroidPrimeRelay(TUniqueId uid, std::string_view name, const CEntityInfo& info, bool active,\n                                       const zeus::CTransform& xf, const zeus::CVector3f& scale,\n                                       CMetroidPrimeData&& parms, float f1, float f2, float f3, u32 w1, bool b1,\n                                       u32 w2, const CHealthInfo& hInfo1, const CHealthInfo& hInfo2, u32 w3, u32 w4,\n                                       u32 w5, rstl::reserved_vector<CMetroidPrimeAttackWeights, 4>&& roomParms)\n: CEntity(uid, info, active, name)\n, x38_xf(xf)\n, x68_scale(scale)\n, x74_parms(std::move(parms))\n, xc84_f1(f1)\n, xc88_f2(f2)\n, xc8c_f3(f3)\n, xc90_w1(w1)\n, xc94_b1(b1)\n, xc98_w2(w2)\n, xc9c_hInfo1(hInfo1)\n, xca4_hInfo2(hInfo2)\n, xcac_w3(w3)\n, xcb0_w4(w4)\n, xcb4_w5(w5)\n, xcb8_roomParms(std::move(roomParms)) {}\n\nvoid CMetroidPrimeRelay::Accept(IVisitor& visitor) { visitor.Visit(this); }\nvoid CMetroidPrimeRelay::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& mgr) {\n  if (x34_mpUid != objId) {\n    ForwardMessageToMetroidPrimeExo(msg, mgr);\n  }\n  if (msg == EScriptObjectMessage::InitializedInArea) {\n    GetOrBuildMetroidPrimeExo(mgr);\n  }\n}\n\nvoid CMetroidPrimeRelay::ForwardMessageToMetroidPrimeExo(EScriptObjectMessage msg, CStateManager& mgr) {\n  if (auto* exo = CPatterned::CastTo<CMetroidPrime>(mgr.ObjectById(x34_mpUid))) {\n    mgr.SendScriptMsg(exo, GetUniqueId(), msg);\n  }\n}\n\nvoid CMetroidPrimeRelay::GetOrBuildMetroidPrimeExo(CStateManager& mgr) {\n  if (!GetActive()) {\n    return;\n  }\n\n  for (const auto act : mgr.GetPhysicsActorObjectList()) {\n    if (CPatterned::CastTo<CMetroidPrime>(act) != nullptr) {\n      return;\n    }\n  }\n\n  const auto& animParms = x74_parms.x4_patternedInfo.GetAnimationParameters();\n  CModelData mData(\n      CAnimRes(animParms.GetACSFile(), animParms.GetCharacter(), x68_scale, animParms.GetInitialAnimation(), true));\n  auto* exo = new CMetroidPrime(\n      mgr.AllocateUniqueId(), \"Metroid Prime! (Stage 1)\"sv, CEntityInfo(GetAreaId(), NullConnectionList), x38_xf,\n      std::move(mData), x74_parms.x4_patternedInfo, x74_parms.x13c_actorParms, x74_parms.x1a4_, x74_parms.x1a8_,\n      x74_parms.x27c_, x74_parms.x350_, x74_parms.x424_, x74_parms.x460_particle1, x74_parms.x464_,\n      x74_parms.x708_wpsc1, x74_parms.x70c_dInfo1, x74_parms.x728_shakeData1, x74_parms.x7fc_wpsc2,\n      x74_parms.x800_dInfo2, x74_parms.x81c_shakeData2, x74_parms.x8f0_, x74_parms.x92c_, x74_parms.x948_,\n      x74_parms.xa1c_particle2, x74_parms.xa20_swoosh, x74_parms.xa24_particle3, x74_parms.xa28_particle4,\n      x74_parms.xa2c_);\n  mgr.AddObject(exo);\n  mgr.SendScriptMsg(exo, kInvalidUniqueId, EScriptObjectMessage::InitializedInArea);\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CMetroidPrimeRelay.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/MP1/World/CMetroidPrime.hpp\"\n#include \"Runtime/World/CEntity.hpp\"\n\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce::MP1 {\n\nclass CMetroidPrimeRelay : public CEntity {\n  TUniqueId x34_mpUid = kInvalidUniqueId;\n  zeus::CTransform x38_xf;\n  zeus::CVector3f x68_scale;\n  CMetroidPrimeData x74_parms;\n  float xc84_f1;\n  float xc88_f2;\n  float xc8c_f3;\n  u32 xc90_w1;\n  bool xc94_b1;\n  u32 xc98_w2;\n  CHealthInfo xc9c_hInfo1;\n  CHealthInfo xca4_hInfo2;\n  u32 xcac_w3;\n  u32 xcb0_w4;\n  u32 xcb4_w5;\n  rstl::reserved_vector<CMetroidPrimeAttackWeights, 4> xcb8_roomParms;\n\n  void ForwardMessageToMetroidPrimeExo(EScriptObjectMessage msg, CStateManager& mgr);\n  void GetOrBuildMetroidPrimeExo(CStateManager& mgr);\n\npublic:\n  DEFINE_ENTITY\n  CMetroidPrimeRelay(TUniqueId uid, std::string_view name, const CEntityInfo& info, bool active,\n                     const zeus::CTransform& xf, const zeus::CVector3f& scale, CMetroidPrimeData&& parms, float f1,\n                     float f2, float f3, u32 w1, bool b1, u32 w2, const CHealthInfo& hInfo1, const CHealthInfo& hInfo2,\n                     u32 w3, u32 w4, u32 w5, rstl::reserved_vector<CMetroidPrimeAttackWeights, 4>&& roomParms);\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) override;\n  [[nodiscard]] TUniqueId GetMetroidPrimeExoId() const { return x34_mpUid; }\n  void SetMetroidPrimeExoId(TUniqueId uid) { x34_mpUid = uid; }\n  [[nodiscard]] float Get_xc84() const { return xc84_f1; }\n  [[nodiscard]] float Get_xc88() const { return xc88_f2; }\n  [[nodiscard]] float Get_xc8c() const { return xc8c_f3; }\n  [[nodiscard]] u32 Get_xc90() const { return xc90_w1; }\n  [[nodiscard]] bool Get_xc94() const { return xc94_b1; }\n  [[nodiscard]] u32 Get_xc98() const { return xc98_w2; }\n  [[nodiscard]] CHealthInfo GetHealthInfo1() const { return xc9c_hInfo1; }\n  [[nodiscard]] CHealthInfo GetHealthInfo2() const { return xca4_hInfo2; }\n  [[nodiscard]] u32 Get_xcac() const { return xcac_w3; }\n  [[nodiscard]] u32 Get_xcb0() const { return xcb0_w4; }\n  [[nodiscard]] u32 Get_xcb4() const { return xcb4_w5; }\n  [[nodiscard]] rstl::reserved_vector<CMetroidPrimeAttackWeights, 4> GetRoomParameters() const { return xcb8_roomParms; }\n};\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CMetroidPrimeStage2.cpp",
    "content": "#include \"Runtime/MP1/World/CMetroidPrimeStage2.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Collision/CCollisionActor.hpp\"\n#include \"Runtime/Collision/CCollisionActorManager.hpp\"\n#include \"Runtime/Collision/CGameCollision.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Weapon/CGameProjectile.hpp\"\n#include \"Runtime/World/CGameArea.hpp\"\n#include \"Runtime/World/CPatternedInfo.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CScriptWaypoint.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\n#include \"Audio/SFX/MetroidPrime.h\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce::MP1 {\nnamespace {\nstd::array<SSphereJointInfo, 1> skJointInfo{{\n    {\"lockon_target_LCTR\", 1.5f},\n}};\n\nstd::array<u32, 4> skUnkInts1{{0, 1, 0, 2}};\nstd::array<u32, 3> skUnkInts2{{1, 2, 3}};\n\n} // namespace\nCMetroidPrimeStage2::CMetroidPrimeStage2(metaforce::TUniqueId uid, std::string_view name,\n                                           const metaforce::CEntityInfo& info, const zeus::CTransform& xf,\n                                           metaforce::CModelData&& mData, const metaforce::CPatternedInfo& pInfo,\n                                           const metaforce::CActorParameters& actParms, metaforce::CAssetId particle1,\n                                           const metaforce::CDamageInfo& dInfo, float f1, metaforce::CAssetId electric,\n                                           u32 w1, metaforce::CAssetId particle2)\n: CPatterned(ECharacter::MetroidPrimeEssence, uid, name, EFlavorType::Zero, info, xf, std::move(mData), pInfo,\n             EMovementType::Flyer, EColliderType::One, EBodyType::Flyer, actParms, EKnockBackVariant::Medium)\n, x568_(g_SimplePool->GetObj({FOURCC('PART'), particle2}))\n, x574_searchPath(nullptr, 3, pInfo.GetPathfindingIndex(), 1.f, 1.f)\n, x660_(particle1)\n, x664_(electric)\n, x698_(dInfo)\n, x6b4_(xf.origin)\n, x70c_(CSfxManager::TranslateSFXID(w1)) {\n  CreateShadow(false);\n  MakeThermalColdAndHot();\n}\n\nvoid CMetroidPrimeStage2::Think(float dt, CStateManager& mgr) {\n  if (!GetActive()) {\n    return;\n  }\n\n  CPatterned::Think(dt, mgr);\n  if (IsAlive()) {\n    UpdatePhase(dt, mgr);\n  }\n\n  x450_bodyController->FaceDirection((mgr.GetPlayer().GetTranslation() - GetTranslation()).normalized(), dt);\n  x658_collisionManager->Update(dt, mgr, CCollisionActorManager::EUpdateOptions::ObjectSpace);\n  UpdateHealth(mgr);\n  CountListeningAi(mgr);\n  if (x70e_30_) {\n    x6d4_ = 2.f * dt + x6d4_;\n    if (x6d4_ >= 1.f) {\n      x6d4_ = 0.f;\n    }\n\n    sub8027ce5c(-4.f * x6d4_ * (x6d4_ - 1.f));\n  }\n}\n\nvoid CMetroidPrimeStage2::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId other, CStateManager& mgr) {\n  CPatterned::AcceptScriptMsg(msg, other, mgr);\n\n  switch (msg) {\n  case EScriptObjectMessage::Activate:\n    x658_collisionManager->SetActive(mgr, true);\n    break;\n  case EScriptObjectMessage::Deactivate:\n    x658_collisionManager->SetActive(mgr, false);\n    break;\n  case EScriptObjectMessage::Start:\n    x70e_25_ = true;\n    break;\n  case EScriptObjectMessage::Stop:\n    x70e_25_ = false;\n    break;\n  case EScriptObjectMessage::Touched: {\n    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(other)) {\n      if (colAct->GetLastTouchedObject() == mgr.GetPlayer().GetUniqueId() && x420_curDamageRemTime <= 0.f) {\n        mgr.ApplyDamage(GetUniqueId(), mgr.GetPlayer().GetUniqueId(), GetUniqueId(), GetContactDamage(),\n                        CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), zeus::skZero3f);\n        x420_curDamageRemTime = x424_damageWaitTime;\n      }\n    }\n    break;\n  }\n  case EScriptObjectMessage::Registered: {\n    SetupCollisionActorManager(mgr);\n    x658_collisionManager->SetActive(mgr, true);\n    x6cc_ = GetModelData()->GetScale().x();\n    x6d0_ = 0.9f * x6cc_ + x6cc_;\n    x55c_moveScale.splat(1.f / (0.625f * x6cc_));\n    const float hp = GetHealthInfo(mgr)->GetHP();\n    x6c0_ = 0.3f * hp;\n    if (hp > 0.f) {\n      x6c4_ = 1.f / hp;\n    }\n    x450_bodyController->Activate(mgr);\n    break;\n  }\n  case EScriptObjectMessage::Deleted: {\n    x658_collisionManager->Destroy(mgr);\n    mgr.SetBossParams(kInvalidUniqueId, 0.f, 0);\n    break;\n  }\n  case EScriptObjectMessage::InitializedInArea: {\n    x574_searchPath.SetArea(mgr.GetWorld()->GetArea(GetAreaIdAlways())->GetPostConstructed()->x10bc_pathArea);\n    x704_bossUtilityWaypointId = GetWaypointForState(mgr, EScriptObjectState::Play, EScriptObjectMessage::Activate);\n    break;\n  }\n  case EScriptObjectMessage::Damage: {\n    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(other)) {\n      if (TCastToConstPtr<CGameProjectile> proj = mgr.GetObjectById(colAct->GetLastTouchedObject())) {\n        if (proj->GetOwnerId() == mgr.GetPlayer().GetUniqueId()) {\n          if (colAct->GetDamageVulnerability()->WeaponHits(proj->GetDamageInfo().GetWeaponMode(), false) &&\n              proj->GetDamageInfo().GetWeaponMode().GetType() == EWeaponType::Phazon) {\n            sub8027cee0(mgr);\n            TakeDamage(zeus::skForward, 1.f);\n            if (!x70e_24_ && !x70e_26_) {\n              GetBodyController()->GetCommandMgr().DeliverCmd(\n                  CBCKnockBackCmd{GetTransform().frontVector(), pas::ESeverity::One});\n              sub8027cce0(mgr);\n            }\n          }\n        }\n      }\n    } else if (TCastToConstPtr<CGameProjectile> proj = mgr.GetObjectById(other)) {\n      mgr.ApplyDamage(other, x706_lockOnTargetCollider, proj->GetOwnerId(), proj->GetDamageInfo(),\n                      CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), zeus::skZero3f);\n    }\n    break;\n  }\n  default:\n    break;\n  }\n}\n\nvoid CMetroidPrimeStage2::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) {\n  CPatterned::PreRender(mgr, frustum);\n}\n\nvoid CMetroidPrimeStage2::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) {\n\n  if (GetActive() && x65c_) {\n    g_Renderer->AddParticleGen(*x65c_);\n  }\n  CPatterned::AddToRenderer(frustum, mgr);\n}\n\nvoid CMetroidPrimeStage2::Render(CStateManager& mgr) {\n  if (x70e_27_) {\n    mgr.DrawSpaceWarp(x6b4_, 1.f);\n  }\n  CPatterned::Render(mgr);\n}\n\nzeus::CVector3f CMetroidPrimeStage2::GetAimPosition(const CStateManager& mgr, float dt) const {\n  if (TCastToConstPtr<CCollisionActor> colAct = mgr.GetObjectById(x706_lockOnTargetCollider)) {\n    return colAct->GetTranslation();\n  }\n  return CPatterned::GetAimPosition(mgr, dt);\n}\n\nvoid CMetroidPrimeStage2::DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type,\n                                           float dt) {\n\n  switch (type) {\n  case EUserEventType::EggLay: {\n    if (x70e_29_ && x6d8_ != 0 && x6e4_spawnedAiCount < x6f8_maxSpawnedCount) {\n      const float ang1 = zeus::degToRad(22.5f) * mgr.GetActiveRandom()->Range(-1, 1);\n      const float ang2 = zeus::degToRad(45.0f) * mgr.GetActiveRandom()->Range(-1, 1);\n      zeus::CVector3f pos =\n          x668_ * zeus::CVector3f{2.f * -std::sin(ang1), (2.f * (2.f * std::cos(ang1)) * std::sin(ang2)),\n                                  2.f * ((2.f * std::cos(ang1)) * std::cos(ang2))};\n      if (TCastToPtr<CScriptWaypoint> wp = mgr.ObjectById(x704_bossUtilityWaypointId)) {\n        wp->SetTransform(zeus::lookAt(pos, mgr.GetPlayer().GetAimPosition(mgr, 0.f)));\n        if (sub8027e870(wp->GetTransform(), mgr)) {\n          SendScriptMsgs(EScriptObjectState::Zero, mgr, EScriptObjectMessage::None);\n          x6b4_ = wp->GetTranslation();\n        }\n      }\n    }\n    return;\n  }\n  case EUserEventType::EventStart: {\n    if (!x70e_31_) {\n      SendScriptMsgs(EScriptObjectState::CameraTarget, mgr, EScriptObjectMessage::None);\n      x70e_31_ = true;\n    }\n    return;\n  }\n  case EUserEventType::BeginAction: {\n    CShockWaveInfo data(x660_, x698_, 2.f, x664_, x70c_);\n    data.SetSpeedIncrease(180.f);\n    CreateShockWave(mgr, data);\n    ShakeCamera(mgr, 1.f);\n    return;\n  }\n  case EUserEventType::Activate: {\n    sub8027d824(mgr);\n    return;\n  }\n  case EUserEventType::Deactivate:\n    x70e_27_ = false;\n    [[fallthrough]];\n  default:\n    break;\n  }\n  CPatterned::DoUserAnimEvent(mgr, node, type, dt);\n}\n\nvoid CMetroidPrimeStage2::Death(CStateManager& mgr, const zeus::CVector3f& direction, EScriptObjectState state) {\n  if (!IsAlive()) {\n    return;\n  }\n\n  KillAiInArea(mgr);\n  SetParticleEffectState(mgr, false);\n  if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(x706_lockOnTargetCollider)) {\n    colAct->AddMaterial(EMaterialTypes::ProjectilePassthrough, mgr);\n  }\n  CPatterned::Death(mgr, direction, state);\n}\n\nvoid CMetroidPrimeStage2::Dead(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg != EStateMsg::Update || GetModelData()->GetAnimationData()->IsAnimTimeRemaining(dt, \"Whole Body\"sv)) {\n    return;\n  }\n\n  DeathDelete(mgr);\n}\n\nvoid CMetroidPrimeStage2::PathFind(CStateManager& mgr, EStateMsg msg, float dt) {\n  CPatterned::PathFind(mgr, msg, dt);\n  if (msg == EStateMsg::Update) {\n    sub8027cb40(mgr.GetPlayer().GetTranslation());\n  }\n}\n\nvoid CMetroidPrimeStage2::Halt(CStateManager& mgr, EStateMsg msg, float dt) {\n  // Intentionally empty\n}\n\nvoid CMetroidPrimeStage2::Generate(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    zeus::CVector3f lookPos = mgr.GetPlayer().GetTranslation();\n    lookPos.z() = GetTranslation().z();\n    zeus::CTransform xf = zeus::lookAt(GetTranslation(), lookPos);\n    xf.origin = GetTranslation();\n    SetTransform(xf);\n  } else if (msg == EStateMsg::Deactivate) {\n    mgr.SetBossParams(GetUniqueId(), GetHealthInfo(mgr)->GetHP(), 91);\n    SetParticleEffectState(mgr, true);\n  }\n}\n\nvoid CMetroidPrimeStage2::JumpBack(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n    x700_ = sub8027cfd4(mgr, true);\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::Step, &CPatterned::TryStep, x700_);\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n  }\n}\n\nvoid CMetroidPrimeStage2::Skid(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::Step, &CPatterned::TryStep, 5);\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n  }\n}\n\nvoid CMetroidPrimeStage2::FadeIn(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x6f8_maxSpawnedCount = GetMaxSpawnCount(mgr);\n    x32c_animState = EAnimState::Ready;\n    x70e_24_ = true;\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::ProjectileAttack, &CPatterned::TryProjectileAttack, 0);\n  } else if (msg == EStateMsg::Deactivate) {\n    x70e_24_ = false;\n    x70e_27_ = false;\n    x70e_29_ = false;\n    x70e_30_ = false;\n    x32c_animState = EAnimState::NotReady;\n  }\n}\n\nvoid CMetroidPrimeStage2::FadeOut(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg != EStateMsg::Activate) {\n    return;\n  }\n\n  DoPhaseTransition(mgr);\n}\n\nvoid CMetroidPrimeStage2::Taunt(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::Taunt, &CPatterned::TryTaunt, 2);\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n  }\n}\n\nvoid CMetroidPrimeStage2::TelegraphAttack(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n    x70e_30_ = true;\n  } else if (msg == EStateMsg::Update) {\n    if (!x70e_31_) {\n      TryCommand(mgr, pas::EAnimationState::MeleeAttack, &CPatterned::TryMeleeAttack, 2);\n    } else {\n      TryCommand(mgr, pas::EAnimationState::ProjectileAttack, &CPatterned::TryProjectileAttack, 5);\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n    x70e_30_ = false;\n    sub8027ce5c(dt);\n  }\n}\n\nvoid CMetroidPrimeStage2::Dodge(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n    x700_ = sub8027cfd4(mgr, false);\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::Step, &CPatterned::TryStep, x700_);\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n  }\n}\n\nvoid CMetroidPrimeStage2::PathFindEx(CStateManager& mgr, EStateMsg msg, float dt) {\n  CPatterned::PathFind(mgr, msg, dt);\n  if (msg == EStateMsg::Activate) {\n    x70e_24_ = true;\n  } else if (msg == EStateMsg::Update) {\n    sub8027cb40(x2e0_destPos);\n  } else if (msg == EStateMsg::Deactivate) {\n    x70e_24_ = false;\n  }\n}\n\nbool CMetroidPrimeStage2::HasPatrolPath(CStateManager& mgr, float dt) {\n  return !x70e_31_ && CPatterned::HasPatrolPath(mgr, dt);\n}\n\nbool CMetroidPrimeStage2::ShouldAttack(CStateManager& mgr, float dt) {\n  if (x70e_31_) {\n    return x70e_25_;\n  }\n  return true;\n}\n\nbool CMetroidPrimeStage2::InPosition(CStateManager& mgr, float dt) {\n  return (GetTranslation().z() - mgr.GetPlayer().GetTranslation().z()) > 0.25f;\n}\n\nbool CMetroidPrimeStage2::CoverFind(CStateManager& mgr, float dt) {\n  return (x2e0_destPos - GetTranslation()).magSquared() < 90.f;\n}\n\nbool CMetroidPrimeStage2::ShouldTaunt(CStateManager& mgr, float dt) {\n  const CHealthInfo* info = GetHealthInfo(mgr);\n  if (!info || info->GetHP() <= x6c0_) {\n    return false;\n  }\n\n  return mgr.GetActiveRandom()->Next() % 100 < 50;\n}\n\nbool CMetroidPrimeStage2::ShouldCrouch(CStateManager& mgr, float dt) {\n  if (x6f0_ < x6f4_) {\n    ++x6f0_;\n    return false;\n  }\n\n  x6f4_ = std::min(x6e8_ + static_cast<u32>(3.f * (1.f - x6c4_ * GetHealthInfo(mgr)->GetHP())), x6ec_);\n  x6f0_ = 0;\n  return true;\n}\n\nbool CMetroidPrimeStage2::ShouldMove(CStateManager& mgr, float dt) { return x70e_31_; }\n\nCPathFindSearch* CMetroidPrimeStage2::GetSearchPath() { return &x574_searchPath; }\n\nvoid CMetroidPrimeStage2::sub8027cb40(const zeus::CVector3f& vec) {\n  pas::EStepDirection stepDir = GetStepDirection(GetBodyController()->GetCommandMgr().GetMoveVector());\n  GetBodyController()->GetCommandMgr().ClearLocomotionCmds();\n  if (stepDir == pas::EStepDirection::Forward &&\n      GetTransform().frontVector().normalized().dot((x2e0_destPos - GetTranslation()).normalized()) <\n          zeus::degToRad(-15.f)) {\n    stepDir = pas::EStepDirection::Backward;\n  }\n\n  GetBodyController()->GetCommandMgr().DeliverCmd(CBCStepCmd(stepDir, pas::EStepType::Normal));\n  GetBodyController()->GetCommandMgr().DeliverTargetVector(vec - GetTranslation());\n}\n\nvoid CMetroidPrimeStage2::sub8027cce0(CStateManager& mgr) {\n  if (CSfxManager::IsPlaying(x708_)) {\n    return;\n  }\n  CAudioSys::C3DEmitterParmData emitterData{zeus::skZero3f, zeus::skZero3f, 1000.f, 0.1f, 1, SFXsfx0B67, 1.f,\n                                            0.16f,          false,          127};\n  emitterData.x0_pos = GetTargetTransform(mgr).origin;\n  x708_ = CSfxManager::AddEmitter(emitterData, true, 127, false, GetAreaIdAlways());\n}\n\nzeus::CTransform CMetroidPrimeStage2::GetTargetTransform(CStateManager& mgr) {\n  if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(x706_lockOnTargetCollider)) {\n    return colAct->GetTransform();\n  }\n\n  return GetTransform();\n}\n\nvoid CMetroidPrimeStage2::sub8027ce5c(float dt) {\n  const auto matCount = static_cast<float>(GetModelData()->GetNumMaterialSets() - 2);\n  u32 iVar1 = matCount - (matCount * dt);\n  if (x6fc_ != iVar1) {\n    x6fc_ = iVar1;\n  }\n}\n\nvoid CMetroidPrimeStage2::sub8027cee0(CStateManager& mgr) {\n  const float hp = x6c4_ * GetHealthInfo(mgr)->GetHP();\n  if (hp <= 0.f) {\n    return;\n  }\n\n  bool sendMsg = false;\n  if (x6d8_ == 0 && hp < 0.75f) {\n    x6d8_ = 1;\n  } else if (x6d8_ == 1 && hp < 0.5f) {\n    sendMsg = true;\n    x6d8_ = 2;\n  } else if (x6d8_ == 2 && hp < 0.25f) {\n    sendMsg = true;\n    x6d8_ = 3;\n  }\n\n  if (sendMsg) {\n    SendScriptMsgs(EScriptObjectState::DeactivateState, mgr, EScriptObjectMessage::None);\n  }\n}\n\nu32 CMetroidPrimeStage2::sub8027cfd4(CStateManager& mgr, bool w1) {\n  auto startIndex = static_cast<size_t>(!w1);\n  zeus::CTransform xf = GetTargetTransform(mgr);\n  std::array<zeus::CVector3f, 3> directions;\n  directions[0] = -xf.frontVector();\n  directions[2] = xf.rightVector();\n  directions[1] = -directions[2];\n  u32 uVar5 = 1 << size_t(startIndex);\n  for (auto i = size_t(startIndex); i < 3; ++i) {\n    CRayCastResult res = mgr.RayStaticIntersection(xf.origin, directions[i], 20.f, CMaterialFilter::skPassEverything);\n    if (res.IsInvalid()) {\n      uVar5 |= 1 << i;\n    }\n  }\n\n  u32 uVar3 = 0;\n  if (uVar5 < 8) {\n    u32 numBits = zeus::PopCount(uVar5);\n    if (numBits == 2) {\n      u32 uVar1_ = mgr.GetActiveRandom()->Next();\n      if ((uVar1_ & 1) == false) {\n        uVar3 = (uVar5 & 1) ^ 1;\n      } else {\n        uVar3 = ((uVar5 >> 2) & 1) + 1;\n      }\n    } else if (numBits < 2) {\n      uVar3 = uVar5 >> 1;\n    } else if (numBits == 3) {\n      uVar3 = mgr.GetActiveRandom()->Range(startIndex, 2);\n    }\n  }\n\n  return skUnkInts2[uVar3];\n}\n\nvoid CMetroidPrimeStage2::DoPhaseTransition(CStateManager& mgr) {\n  x330_stateMachineState.SetDelay(2.f);\n  x70e_26_ = true;\n  x70e_27_ = true;\n  x6c8_ = 1.f;\n  x70e_29_ = false;\n\n  bool uVar3 = x6dc_ == skUnkInts1[size_t(mgr.GetPlayerState()->GetCurrentVisor())];\n  if (skUnkInts1[size_t(mgr.GetPlayerState()->GetCurrentVisor())] == x6dc_) {\n    x65c_ = std::make_unique<CElementGen>(x568_, CElementGen::EModelOrientationType::Normal,\n                                          CElementGen::EOptionalSystemFlags::One);\n\n    if (x65c_) {\n      zeus::CTransform xf = GetTargetTransform(mgr);\n      x65c_->SetGlobalScale(GetModelData()->GetScale());\n      x65c_->SetGlobalOrientation(xf.getRotation());\n      x65c_->SetGlobalTranslation(xf.origin);\n    }\n  }\n\n  CSfxManager::AddEmitter(SFXsfx0B7D + uVar3, GetTranslation(), zeus::skZero3f, true, false, 127, kInvalidAreaId);\n  x6e0_ = x6dc_;\n  ++x6dc_;\n  if (x6dc_ > 2) {\n    x6dc_ = 0;\n  }\n}\n\nvoid CMetroidPrimeStage2::ShakeCamera(CStateManager& mgr, float f1) {\n  float mag = 0.5f - (0.01f * (GetTranslation() - mgr.GetPlayer().GetTranslation()).magnitude());\n  if (mag < 0.f || mgr.GetPlayer().GetSurfaceRestraint() == CPlayer::ESurfaceRestraints::Air) {\n    return;\n  }\n\n  mgr.GetCameraManager()->AddCameraShaker(CCameraShakeData(0.5f, mag), true);\n}\n\nvoid CMetroidPrimeStage2::CreateShockWave(CStateManager& mgr, const CShockWaveInfo& shockWaveData) {\n  CRayCastResult res = RayStaticIntersection(mgr);\n  if (res.IsInvalid()) {\n    return;\n  }\n\n  mgr.AddObject(new CShockWave(mgr.AllocateUniqueId(), \"Shockwave\", CEntityInfo(GetAreaIdAlways(), NullConnectionList),\n                               zeus::CTransform::Translate(res.GetPoint()), GetUniqueId(), shockWaveData, 1.5f, 0.5f));\n}\n\nCRayCastResult CMetroidPrimeStage2::RayStaticIntersection(CStateManager& mgr) {\n  return mgr.RayStaticIntersection(GetTranslation(), -zeus::skUp, 30.f, CMaterialFilter::skPassEverything);\n}\n\nvoid CMetroidPrimeStage2::SetParticleEffectState(CStateManager& mgr, bool active) {\n  GetModelData()->GetAnimationData()->SetParticleEffectState(\"Eyes\"sv, active, mgr);\n  GetModelData()->GetAnimationData()->SetParticleEffectState(\"Head\"sv, active, mgr);\n}\n\nvoid CMetroidPrimeStage2::sub8027d824(CStateManager& mgr) {\n  CRayCastResult res = RayStaticIntersection(mgr);\n  if (res.IsInvalid()) {\n    return;\n  }\n\n  x668_ = zeus::CTransform::Translate(res.GetPoint());\n  if (TCastToPtr<CScriptWaypoint> wp = mgr.ObjectById(x704_bossUtilityWaypointId)) {\n    wp->SetTransform(x668_);\n    SendScriptMsgs(EScriptObjectState::AboutToMassivelyDie, mgr, EScriptObjectMessage::None);\n    x70e_29_ = true;\n  }\n}\n\nbool CMetroidPrimeStage2::sub8027e870(const zeus::CTransform& xf, CStateManager& mgr) {\n  EntityList nearList;\n  mgr.BuildNearList(nearList, {xf.origin - 2.f, xf.origin + 2.f}, CMaterialFilter::MakeInclude(EMaterialTypes::AIBlock),\n                    this);\n\n  CCollidableSphere sphere({zeus::skZero3f, 2.f}, CMaterialList(EMaterialTypes::Solid, EMaterialTypes::AIBlock));\n  CCollisionInfoList infoList;\n  TUniqueId tmpId = kInvalidUniqueId;\n  CGameCollision::DetectCollision(\n      mgr, sphere, xf,\n      CMaterialFilter::MakeIncludeExclude(\n          {EMaterialTypes ::Solid, EMaterialTypes ::Player, EMaterialTypes ::Character, EMaterialTypes ::AIBlock},\n          {EMaterialTypes ::ProjectilePassthrough}),\n      nearList, tmpId, infoList);\n\n  if (infoList.GetCount() >= 1) {\n    return false;\n  }\n\n  if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(x706_lockOnTargetCollider)) {\n    zeus::CVector3f direction = (xf.origin - colAct->GetTranslation()).normalized();\n    CRayCastResult res =\n        mgr.RayStaticIntersection(colAct->GetTranslation(), direction, direction.magnitude(),\n                                  CMaterialFilter::MakeExclude({EMaterialTypes::ProjectilePassthrough}));\n    if (res.IsInvalid()) {\n      return true;\n    }\n  }\n  return false;\n}\n\nvoid CMetroidPrimeStage2::KillAiInArea(CStateManager& mgr) {\n  for (auto* ent : mgr.GetListeningAiObjectList()) {\n    if (TCastToPtr<CPatterned> ai = ent) {\n      if (ai != this && ai->GetActive() && ai->GetAreaIdAlways() == GetAreaIdAlways()) {\n        ai->MassiveDeath(mgr);\n      }\n    }\n  }\n}\n\nvoid CMetroidPrimeStage2::CountListeningAi(CStateManager& mgr) {\n  x6e0_ = 0;\n  for (auto* ent : mgr.GetListeningAiObjectList()) {\n    if (TCastToPtr<CPatterned> ai = ent) {\n      if (ai != this && ai->GetActive() && ai->GetAreaIdAlways() == GetAreaIdAlways()) {\n        ++x6e4_spawnedAiCount;\n      }\n    }\n  }\n}\n\nvoid CMetroidPrimeStage2::UpdatePhase(float dt, CStateManager& mgr) {\n  if (skUnkInts1[size_t(mgr.GetPlayerState()->GetCurrentVisor())] == x6dc_) {\n    x42c_color.a() = 1.f - x6c8_;\n    GetModelData()->SetScale(zeus::CVector3f((x6cc_ - x6d0_) + x6d0_));\n    if (!x70e_28_) {\n      AddMaterial(EMaterialTypes::Orbit, EMaterialTypes::Target, mgr);\n      SetParticleEffectState(mgr, true);\n      x70e_28_ = true;\n    }\n  } else {\n    x42c_color.a() = skUnkInts1[size_t(mgr.GetPlayerState()->GetCurrentVisor())] == x6e0_ ? x6c8_ : 0.f;\n    GetModelData()->SetScale(zeus::CVector3f((x6cc_ - x6d0_) + x6d0_));\n    if (x70e_28_) {\n      RemoveMaterial(EMaterialTypes::Orbit, EMaterialTypes::Target, mgr);\n      SetParticleEffectState(mgr, false);\n      x70e_28_ = false;\n    }\n  }\n\n  zeus::CTransform xf = GetTargetTransform(mgr);\n  if (x70e_26_) {\n    x6c8_ -= 0.5f * dt;\n    x6b4_ = xf.origin;\n    if (x6c8_ < 0.f) {\n      x6c8_ = 0.f;\n      x70e_26_ = false;\n      x70e_27_ = false;\n    }\n  }\n\n  if (!x65c_) {\n    return;\n  }\n\n  if (!x65c_->IsSystemDeletable()) {\n    x65c_->SetGlobalOrientation(xf.getRotation());\n    x65c_->SetGlobalTranslation(xf.origin);\n    x65c_->Update(dt);\n  } else {\n    x65c_.reset();\n  }\n}\n\nvoid CMetroidPrimeStage2::UpdateHealth(CStateManager& mgr) {\n  if (!IsAlive()) {\n    return;\n  }\n\n  if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(x706_lockOnTargetCollider)) {\n    colAct->SetDamageVulnerability(*GetDamageVulnerability());\n    HealthInfo(mgr)->SetHP(colAct->GetHealthInfo(mgr)->GetHP());\n  }\n\n  if (GetHealthInfo(mgr)->GetHP() <= 0.f) {\n    Death(mgr, zeus::skZero3f, EScriptObjectState::DeathRattle);\n    RemoveMaterial(EMaterialTypes::Orbit, EMaterialTypes::Target, mgr);\n  }\n}\n\nvoid CMetroidPrimeStage2::SetLockOnTargetHealthAndDamageVulns(CStateManager& mgr) {\n  if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(x706_lockOnTargetCollider)) {\n    *colAct->HealthInfo(mgr) = *HealthInfo(mgr);\n    colAct->SetDamageVulnerability(*GetDamageVulnerability());\n  }\n}\n\nvoid CMetroidPrimeStage2::AddSphereCollisions(SSphereJointInfo* info, size_t count,\n                                               std::vector<CJointCollisionDescription>& vecOut) {\n  const CAnimData* animData = GetModelData()->GetAnimationData();\n  for (size_t i = 0; i < count; ++i) {\n    CSegId segId = animData->GetLocatorSegId(info[i].name);\n    if (segId.IsInvalid()) {\n      continue;\n    }\n    vecOut.push_back(CJointCollisionDescription::SphereCollision(segId, info[i].radius, info[i].name, 1000.f));\n  }\n}\n\nvoid CMetroidPrimeStage2::SetupCollisionActorManager(CStateManager& mgr) {\n  std::vector<CJointCollisionDescription> joints;\n  AddSphereCollisions(skJointInfo.data(), skJointInfo.size(), joints);\n  x658_collisionManager =\n      std::make_unique<CCollisionActorManager>(mgr, GetUniqueId(), GetAreaIdAlways(), joints, false);\n\n  for (size_t i = 0; i < x658_collisionManager->GetNumCollisionActors(); ++i) {\n    const auto& info = x658_collisionManager->GetCollisionDescFromIndex(i);\n    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(info.GetCollisionActorId())) {\n      if (info.GetName() == \"lockon_target_LCTR\"sv) {\n        x706_lockOnTargetCollider = info.GetCollisionActorId();\n      }\n    }\n  }\n\n  SetLockOnTargetHealthAndDamageVulns(mgr);\n  SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(\n      {EMaterialTypes::Solid}, {EMaterialTypes::CollisionActor, EMaterialTypes::Player, EMaterialTypes::Character}));\n  AddMaterial(EMaterialTypes::ProjectilePassthrough, mgr);\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CMetroidPrimeStage2.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Collision/CJointCollisionDescription.hpp\"\n#include \"Runtime/MP1/World/CShockWave.hpp\"\n#include \"Runtime/World/CPathFindSearch.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n\nnamespace metaforce {\nclass CCollisionActorManager;\nnamespace MP1 {\nclass CMetroidPrimeStage2 : public CPatterned {\n  TCachedToken<CGenDescription> x568_;\n  CPathFindSearch x574_searchPath;\n  std::unique_ptr<CCollisionActorManager> x658_collisionManager;\n  std::unique_ptr<CElementGen> x65c_;\n  CAssetId x660_;\n  CAssetId x664_;\n  zeus::CTransform x668_;\n  CDamageInfo x698_;\n  zeus::CVector3f x6b4_;\n  float x6c0_ = 0.f;\n  float x6c4_ = 0.f;\n  float x6c8_ = 0.f;\n  float x6cc_ = 4.f;\n  float x6d0_ = 0.9f * x6cc_ + x6cc_;\n  float x6d4_ = 0.f;\n  u32 x6d8_ = 0;\n  u32 x6dc_ = 0;\n  u32 x6e0_ = x6dc_;\n  u32 x6e4_spawnedAiCount = 0;\n  u32 x6e8_ = 2;\n  u32 x6ec_ = 4;\n  u32 x6f0_ = 0;\n  u32 x6f4_ = x6e8_ - 1;\n  u32 x6f8_maxSpawnedCount = 2;\n  u32 x6fc_ = 0;\n  u32 x700_ = 1;\n  TUniqueId x704_bossUtilityWaypointId = kInvalidUniqueId;\n  TUniqueId x706_lockOnTargetCollider = kInvalidUniqueId;\n  CSfxHandle x708_;\n  s16 x70c_;\n  bool x70e_24_ : 1 = false;\n  bool x70e_25_ : 1 = true;\n  bool x70e_26_ : 1 = false;\n  bool x70e_27_ : 1 = false;\n  bool x70e_28_ : 1 = true;\n  bool x70e_29_ : 1 = false;\n  bool x70e_30_ : 1 = false;\n  bool x70e_31_ : 1 = false;\n\n  void sub8027cb40(const zeus::CVector3f& vec);\n  void sub8027cce0(CStateManager& mgr);\n  zeus::CTransform GetTargetTransform(CStateManager& mgr);\n  void sub8027ce5c(float f1);\n  void sub8027cee0(CStateManager& mgr);\n  u32 sub8027cfd4(CStateManager& mgr, bool w1);\n  void DoPhaseTransition(CStateManager& mgr);\n  u32 GetMaxSpawnCount(CStateManager& mgr) { return 2;  }\n  void ShakeCamera(CStateManager& mgr, float f1);\n  void CreateShockWave(CStateManager& mgr, const CShockWaveInfo& shockWaveData);\n  CRayCastResult RayStaticIntersection(CStateManager& mgr);\n  void SetParticleEffectState(CStateManager& mgr, bool active);\n  void sub8027d824(CStateManager& mgr);\n  bool sub8027e870(const zeus::CTransform& xf, CStateManager& mgr);\n  void KillAiInArea(CStateManager& mgr);\n  void CountListeningAi(CStateManager& mgr);\n  void UpdatePhase(float dt, CStateManager& mgr);\n  void UpdateHealth(CStateManager& mgr);\n  void SetLockOnTargetHealthAndDamageVulns(CStateManager& mgr);\n  void AddSphereCollisions(SSphereJointInfo* info, size_t count, std::vector<CJointCollisionDescription>& vecOut);\n  void SetupCollisionActorManager(CStateManager& mgr);\n\npublic:\n  DEFINE_PATTERNED(MetroidPrimeEssence);\n\n  CMetroidPrimeStage2(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                       CModelData&& mData, const CPatternedInfo& pInfo, const CActorParameters& actParms,\n                       CAssetId particle1, const CDamageInfo& dInfo, float f1, CAssetId electric, u32 w1,\n                       CAssetId particle2);\n\n  void Think(float dt, CStateManager& mgr) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId other, CStateManager& mgr) override;\n  void PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) override;\n  void AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) override;\n  void Render(CStateManager& mgr) override;\n  zeus::CVector3f GetAimPosition(const CStateManager& mgr, float dt) const override;\n  void DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) override;\n  void Death(CStateManager& mgr, const zeus::CVector3f& direction, EScriptObjectState state) override;\n  void Dead(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void PathFind(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Halt(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Generate(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void JumpBack(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Skid(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void FadeIn(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void FadeOut(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Taunt(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void TelegraphAttack(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Dodge(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void PathFindEx(CStateManager& mgr, EStateMsg msg, float dt) override;\n  bool HasPatrolPath(CStateManager& mgr, float dt) override;\n  bool ShouldAttack(CStateManager& mgr, float dt) override;\n  bool InPosition(CStateManager& mgr, float dt) override;\n  bool CoverFind(CStateManager& mgr, float dt) override;\n  bool ShouldTaunt(CStateManager& mgr, float dt) override;\n  bool ShouldCrouch(CStateManager& mgr, float dt) override;\n  bool ShouldMove(CStateManager& mgr, float dt) override;\n  CPathFindSearch* GetSearchPath() override;\n};\n} // namespace MP1\n} // namespace metaforce"
  },
  {
    "path": "Runtime/MP1/World/CNewIntroBoss.cpp",
    "content": "#include \"Runtime/MP1/World/CNewIntroBoss.hpp\"\n\n#include <array>\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Character/CCharLayoutInfo.hpp\"\n#include \"Runtime/Collision/CCollisionActor.hpp\"\n#include \"Runtime/Collision/CCollisionActorManager.hpp\"\n#include \"Runtime/Collision/CGameCollision.hpp\"\n#include \"Runtime/Collision/CJointCollisionDescription.hpp\"\n#include \"Runtime/Weapon/CPlasmaProjectile.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce::MP1 {\n\nconstexpr std::array<SSphereJointInfo, 2> skSphereJoints{{\n    {\"Head_1\", 1.5f},\n    {\"Tail_5\", 1.5f},\n}};\n\nconstexpr std::array<SOBBJointInfo, 13> skOBBJoints{{\n    {\"Pelvis\", \"Spine_3\", {4.f, 1.f, 4.f}},\n    {\"Spine_3\", \"Tail_1\", {2.f, 1.f, 2.f}},\n    {\"Tail_1\", \"Tail_2\", {1.f, 1.f, 1.f}},\n    {\"Tail_2\", \"Tail_3\", {1.f, 1.f, 1.f}},\n    {\"Tail_3\", \"Tail_4\", {1.f, 1.f, 1.f}},\n    {\"R_shoulder_front\", \"R_elbow_front\", {.5f, .5f, .5f}},\n    {\"R_elbow_front\", \"R_wrist_front\", {.5f, .5f, .5f}},\n    {\"L_shoulder_front\", \"L_elbow_front\", {.5f, .5f, .5f}},\n    {\"L_elbow_front\", \"L_wrist_front\", {.5f, .5f, .5f}},\n    {\"R_shoulder_back\", \"R_elbow_back\", {.5f, .5f, .5f}},\n    {\"R_elbow_back\", \"R_wrist_back\", {.5f, .5f, .5f}},\n    {\"L_shoulder_back\", \"L_elbow_back\", {.5f, .5f, .5f}},\n    {\"L_elbow_back\", \"L_wrist_back\", {.5f, .5f, .5f}},\n}};\n\nCNewIntroBoss::CNewIntroBoss(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                             CModelData&& mData, const CPatternedInfo& pInfo, const CActorParameters& actParms,\n                             float minTurnAngle, CAssetId projectile, const CDamageInfo& dInfo,\n                             CAssetId beamContactFxId, CAssetId beamPulseFxId, CAssetId beamTextureId,\n                             CAssetId beamGlowTextureId)\n: CPatterned(ECharacter::NewIntroBoss, uid, name, EFlavorType::Zero, info, xf, std::move(mData), pInfo,\n             EMovementType::Flyer, EColliderType::One, EBodyType::Restricted, actParms, EKnockBackVariant::Medium)\n, x570_minTurnAngle(minTurnAngle)\n, x574_boneTracking(*GetModelData()->GetAnimationData(), \"Head_1\"sv, zeus::degToRad(80.f), zeus::degToRad(180.f),\n                    EBoneTrackingFlags::None)\n, x5ac_projectileInfo(projectile, dInfo)\n, x5f0_beamContactFxId(beamContactFxId)\n, x5f4_beamPulseFxId(beamPulseFxId)\n, x5f8_beamTextureId(beamTextureId)\n, x5fc_beamGlowTextureId(beamGlowTextureId)\n, x644_initialXf(xf) {\n  x5ac_projectileInfo.Token().Lock();\n  x574_boneTracking.SetActive(true);\n}\n\nvoid CNewIntroBoss::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CNewIntroBoss::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  if (msg == EScriptObjectMessage::Registered) {\n    RemoveMaterial(EMaterialTypes::Solid, mgr);\n    RemoveMaterial(EMaterialTypes::Target, mgr);\n    RemoveMaterial(EMaterialTypes::Orbit, mgr);\n    RemoveMaterial(EMaterialTypes::Occluder, mgr);\n    x450_bodyController->Activate(mgr);\n\n    if (x5d4_stage1Projectile == kInvalidUniqueId) {\n      CBeamInfo stage1BeamInfo(3, x5f0_beamContactFxId, x5f4_beamPulseFxId, x5f8_beamTextureId, x5fc_beamGlowTextureId,\n                               50, 1.f, 1.f, 1.5f, 20.f, 1.f, 4.f, 8.f, zeus::skYellow,\n                               zeus::CColor(0.1098f, 0.5764f, 0.1592f), 150.f);\n      CBeamInfo stage2BeamInfo(3, x5f0_beamContactFxId, x5f4_beamPulseFxId, x5f8_beamTextureId, x5fc_beamGlowTextureId,\n                               50, 1.f, 1.f, 2.f, 20.f, 1.f, 4.f, 8.f, zeus::skYellow,\n                               zeus::CColor(0.1098f, 0.5764f, 0.1592f), 150.f);\n\n      x5d4_stage1Projectile = mgr.AllocateUniqueId();\n      x5d6_stage2Projectile = mgr.AllocateUniqueId();\n      x5d8_stage3Projectile = mgr.AllocateUniqueId();\n      CPlasmaProjectile* stage1Projectile =\n          new CPlasmaProjectile(x5ac_projectileInfo.Token(), \"IntroBoss_Beam\"sv, EWeaponType::AI, stage1BeamInfo, {},\n                                EMaterialTypes::Character, x5ac_projectileInfo.GetDamage(), x5d4_stage1Projectile,\n                                GetAreaIdAlways(), GetUniqueId(), {}, true, EProjectileAttrib::KeepInCinematic);\n      CPlasmaProjectile* stage2Projectile =\n          new CPlasmaProjectile(x5ac_projectileInfo.Token(), \"IntroBoss_Beam_Stage2\"sv, EWeaponType::AI, stage2BeamInfo,\n                                {}, EMaterialTypes::Character, x5ac_projectileInfo.GetDamage(), x5d6_stage2Projectile,\n                                GetAreaIdAlways(), GetUniqueId(), {}, true, EProjectileAttrib::KeepInCinematic);\n      CPlasmaProjectile* stage3Projectile =\n          new CPlasmaProjectile(x5ac_projectileInfo.Token(), \"IntroBoss_Beam_Stage2\"sv, EWeaponType::AI, stage2BeamInfo,\n                                {}, EMaterialTypes::Character, x5ac_projectileInfo.GetDamage(), x5d8_stage3Projectile,\n                                GetAreaIdAlways(), GetUniqueId(), {}, true, EProjectileAttrib::KeepInCinematic);\n      mgr.AddObject(stage1Projectile);\n      mgr.AddObject(stage2Projectile);\n      mgr.AddObject(stage3Projectile);\n      x676_curProjectile = x5d4_stage1Projectile;\n    }\n\n    std::vector<CJointCollisionDescription> jointCollisions;\n    jointCollisions.reserve(15);\n\n    const CAnimData* animData = GetModelData()->GetAnimationData();\n    for (const SSphereJointInfo& joint : skSphereJoints) {\n      CSegId seg = animData->GetLocatorSegId(joint.name);\n      jointCollisions.push_back(CJointCollisionDescription::SphereCollision(seg, joint.radius, joint.name, 0.001f));\n    }\n\n    for (const SOBBJointInfo& joint : skOBBJoints) {\n      CSegId from = animData->GetLocatorSegId(joint.from);\n      CSegId to = animData->GetLocatorSegId(joint.to);\n      jointCollisions.push_back(CJointCollisionDescription::OBBAutoSizeCollision(\n          from, to, joint.bounds, CJointCollisionDescription::EOrientationType::One, joint.from, 0.001f));\n    }\n\n    x5ec_collisionManager.reset(\n        new CCollisionActorManager(mgr, GetUniqueId(), GetAreaIdAlways(), jointCollisions, GetActive()));\n    x640_initialHp = GetHealthInfo(mgr)->GetHP();\n\n    for (u32 i = 0; i < x5ec_collisionManager->GetNumCollisionActors(); ++i) {\n      const CJointCollisionDescription& desc = x5ec_collisionManager->GetCollisionDescFromIndex(i);\n      TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(desc.GetCollisionActorId());\n      if (desc.GetName() == skSphereJoints[0].name) {\n        x600_headActor = desc.GetCollisionActorId();\n        if (colAct) {\n          CHealthInfo* thisHealthInfo = HealthInfo(mgr);\n          CHealthInfo* colHealthInfo = colAct->HealthInfo(mgr);\n          *colHealthInfo = *thisHealthInfo;\n          colAct->SetDamageVulnerability(*GetDamageVulnerability());\n          colAct->RemoveMaterial(EMaterialTypes::Orbit, mgr);\n        }\n      } else if (desc.GetName() == skOBBJoints[0].from) {\n        x602_pelvisActor = desc.GetCollisionActorId();\n        if (colAct) {\n          CHealthInfo* thisHealthInfo = HealthInfo(mgr);\n          CHealthInfo* colHealthInfo = colAct->HealthInfo(mgr);\n          *colHealthInfo = *thisHealthInfo;\n          colAct->SetDamageVulnerability(CDamageVulnerability::NormalVulnerabilty());\n          colAct->AddMaterial(EMaterialTypes::Orbit, mgr);\n          MoveScannableObjectInfoToActor(colAct, mgr);\n        }\n      } else\n        colAct->RemoveMaterial(EMaterialTypes::Orbit, mgr);\n    }\n  } else if (msg == EScriptObjectMessage::Deleted) {\n    DeleteBeam(mgr);\n    x5ec_collisionManager->Destroy(mgr);\n  } else if (msg == EScriptObjectMessage::Damage) {\n    if (uid == x600_headActor || uid == x602_pelvisActor)\n      TakeDamage({}, 0.f);\n  }\n\n  bool active = GetActive();\n  CPatterned::AcceptScriptMsg(msg, uid, mgr);\n  if (active == GetActive())\n    return;\n\n  if (x5ec_collisionManager)\n    x5ec_collisionManager->SetActive(mgr, GetActive());\n  x63c_attackTime = 8.f;\n}\n\npas::ELocomotionType CNewIntroBoss::GetLocoForHealth(const CStateManager& mgr) const {\n  float hp = GetHealthInfo(mgr)->GetHP();\n\n  if (hp > .66f * x640_initialHp)\n    return pas::ELocomotionType::Relaxed;\n  else if (hp > .33f * x640_initialHp)\n    return pas::ELocomotionType::Lurk;\n\n  return pas::ELocomotionType::Combat;\n}\n\nvoid CNewIntroBoss::OnScanStateChanged(EScanState state, CStateManager& mgr) {\n  CPatterned::OnScanStateChanged(state, mgr);\n  if (state != EScanState::Done)\n    return;\n\n  TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(x600_headActor);\n  if (colAct)\n    colAct->AddMaterial(EMaterialTypes::Orbit, mgr);\n  colAct = mgr.ObjectById(x602_pelvisActor);\n  if (colAct)\n    colAct->RemoveMaterial(EMaterialTypes::Orbit, mgr);\n}\n\nvoid CNewIntroBoss::DeleteBeam(CStateManager& mgr) {\n  if (x5d4_stage1Projectile != kInvalidUniqueId) {\n    mgr.FreeScriptObject(x5d4_stage1Projectile);\n    x5d4_stage1Projectile = kInvalidUniqueId;\n  }\n  if (x5d6_stage2Projectile != kInvalidUniqueId) {\n    mgr.FreeScriptObject(x5d6_stage2Projectile);\n    x5d6_stage2Projectile = kInvalidUniqueId;\n  }\n  if (x5d8_stage3Projectile != kInvalidUniqueId) {\n    mgr.FreeScriptObject(x5d8_stage3Projectile);\n    x5d8_stage3Projectile = kInvalidUniqueId;\n  }\n\n  StopRumble(mgr);\n}\n\nvoid CNewIntroBoss::StopRumble(CStateManager& mgr) {\n  if (x674_rumbleVoice == -1)\n    return;\n\n  mgr.GetRumbleManager().StopRumble(x674_rumbleVoice);\n  x674_rumbleVoice = -1;\n}\n\nvoid CNewIntroBoss::Think(float dt, CStateManager& mgr) {\n  CPatterned::Think(dt, mgr);\n  if (x638_ < 0.2f)\n    x638_ += dt;\n\n  if (x400_25_alive) {\n    x574_boneTracking.SetTargetPosition(x62c_targetPos + zeus::CVector3f{0.f, 0.f, 10.f});\n    x574_boneTracking.Update(dt);\n  }\n\n  if (x63c_attackTime > 0.f)\n    x63c_attackTime -= dt;\n\n  GetModelData()->GetAnimationData()->PreRender();\n\n  if (x400_25_alive)\n    x574_boneTracking.PreRender(mgr, *GetModelData()->GetAnimationData(), x34_transform, GetModelData()->GetScale(),\n                                *x450_bodyController);\n\n  x5ec_collisionManager->Update(dt, mgr, CCollisionActorManager::EUpdateOptions::ObjectSpace);\n\n  CPlasmaProjectile* curProjectile = static_cast<CPlasmaProjectile*>(mgr.ObjectById(x676_curProjectile));\n  if (curProjectile && curProjectile->GetActive()) {\n    x628_firingTime += dt;\n    zeus::CTransform xf = GetLctrTransform(x5dc_damageLocator);\n\n    if (x400_25_alive) {\n      zeus::CQuaternion clampedQuat = zeus::CQuaternion::clampedRotateTo(\n          xf.frontVector(),\n          (x610_lookPos + (zeus::min(x628_firingTime / 1.5f, 1.f) * (x61c_startPlayerPos - x610_lookPos))) - xf.origin,\n          zeus::CRelAngle::FromDegrees(30.f));\n      zeus::CTransform newXf = clampedQuat.toTransform() * xf.getRotation();\n      newXf.origin = xf.origin;\n      curProjectile->UpdateFx(newXf, dt, mgr);\n    } else\n      curProjectile->UpdateFx(xf, dt, mgr);\n  }\n\n  TCastToPtr<CCollisionActor> headAct = mgr.ObjectById(x600_headActor);\n  TCastToPtr<CCollisionActor> pelvisAct = mgr.ObjectById(x602_pelvisActor);\n\n  if (headAct && pelvisAct) {\n    CHealthInfo* pelvisHealth = pelvisAct->HealthInfo(mgr);\n    CHealthInfo* headHealth = headAct->HealthInfo(mgr);\n    if (headHealth->GetHP() < pelvisHealth->GetHP()) {\n      *HealthInfo(mgr) = *headHealth;\n      *pelvisAct->HealthInfo(mgr) = *headHealth;\n    } else {\n      *HealthInfo(mgr) = *pelvisHealth;\n      *headAct->HealthInfo(mgr) = *pelvisHealth;\n    }\n  }\n\n  if (HealthInfo(mgr)->GetHP() <= 0.f && x400_25_alive) {\n    if (curProjectile)\n      curProjectile->ResetBeam(mgr, true);\n\n    x450_bodyController->SetPlaybackRate(1.f);\n    SetTransform(x644_initialXf);\n    StopRumble(mgr);\n    Death(mgr, GetTransform().frontVector(), EScriptObjectState::DeathRattle);\n  }\n}\n\nvoid CNewIntroBoss::DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType event, float dt) {\n  if (event == EUserEventType::DamageOn) {\n    x5dc_damageLocator = node.GetLocatorName();\n    zeus::CTransform xf = GetLctrTransform(x5dc_damageLocator);\n    zeus::CVector3f playerPos = PlayerPos(mgr);\n    x604_predictedPlayerPos = x610_lookPos = x62c_targetPos = playerPos;\n    x61c_startPlayerPos = playerPos;\n    x628_firingTime = 0.f;\n    if (GetLocoForHealth(mgr) == pas::ELocomotionType::Combat)\n      x676_curProjectile = x5d8_stage3Projectile;\n    else if (GetLocoForHealth(mgr) == pas::ELocomotionType::Lurk)\n      x676_curProjectile = x5d6_stage2Projectile;\n    else\n      x676_curProjectile = x5d4_stage1Projectile;\n\n    if (CPlasmaProjectile* projectile = static_cast<CPlasmaProjectile*>(mgr.ObjectById(x676_curProjectile))) {\n      if (!projectile->GetActive()) {\n        projectile->Fire(zeus::lookAt(xf.origin, x610_lookPos), mgr, false);\n\n        if (x674_rumbleVoice == -1)\n          x674_rumbleVoice =\n              mgr.GetRumbleManager().Rumble(mgr, ERumbleFxId::IntroBossProjectile, 1.f, ERumblePriority::Two);\n      }\n    }\n\n  } else if (event == EUserEventType::DamageOff) {\n    if (CPlasmaProjectile* projectile = static_cast<CPlasmaProjectile*>(mgr.ObjectById(x676_curProjectile)))\n      projectile->ResetBeam(mgr, false);\n\n    StopRumble(mgr);\n    x63c_attackTime = GetNextAttackTime(mgr);\n    SendScriptMsgs(EScriptObjectState::Attack, mgr, EScriptObjectMessage::None);\n  } else {\n    CPatterned::DoUserAnimEvent(mgr, node, event, dt);\n  }\n}\n\nvoid CNewIntroBoss::AddToRenderer(const zeus::CFrustum&, CStateManager& mgr) { EnsureRendered(mgr); }\n\nfloat CNewIntroBoss::GetNextAttackTime(CStateManager& mgr) const {\n  float attackTime = 2.f * mgr.GetActiveRandom()->Float() + 6.f;\n  float hp = GetHealthInfo(mgr)->GetHP();\n\n  if (hp > .66 * x640_initialHp)\n    return attackTime;\n  else if (hp > .33 * x640_initialHp)\n    return attackTime - (0.4125f * attackTime);\n  return attackTime - (0.825f * attackTime);\n}\n\nzeus::CVector3f CNewIntroBoss::PlayerPos(const CStateManager& mgr) const {\n  CRayCastResult result = CGameCollision::RayStaticIntersection(\n      mgr, mgr.GetPlayer().GetTranslation() + zeus::CVector3f{0.f, 0.f, mgr.GetPlayer().GetEyeHeight() * 0.5f},\n      zeus::skDown, 30.f, CMaterialFilter::MakeInclude({EMaterialTypes::Solid}));\n  if (result.IsValid())\n    return result.GetPoint() + zeus::CVector3f{0.f, 0.f, (mgr.GetPlayer().GetEyeHeight() * 0.5f) + 0.2f};\n\n  return mgr.GetPlayer().GetTranslation() + zeus::CVector3f{0.f, 0.f, (mgr.GetPlayer().GetEyeHeight() * 0.5f) + 0.2f};\n}\n\nbool CNewIntroBoss::ShouldTurn(CStateManager& mgr, float dt) {\n  zeus::CVector3f playerVel = 1.f * mgr.GetPlayer().GetVelocity();\n  zeus::CVector3f playerPos = PlayerPos(mgr);\n  x604_predictedPlayerPos = playerPos + playerVel;\n\n  zeus::CVector2f diffPos = (x604_predictedPlayerPos - GetTranslation()).toVec2f();\n\n  return zeus::CVector2f::getAngleDiff(GetTransform().frontVector().toVec2f(), diffPos) >\n         zeus::degToRad(x570_minTurnAngle);\n}\n\nbool CNewIntroBoss::ShouldAttack(CStateManager& mgr, float dt) {\n  if (x63c_attackTime > 0.f)\n    return false;\n\n  if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::Turn)\n    return false;\n\n  return !ShouldTurn(mgr, dt);\n}\n\nbool CNewIntroBoss::AIStage(CStateManager& mgr, float) { return x568_locomotion != GetLocoForHealth(mgr); }\n\nbool CNewIntroBoss::AnimOver(metaforce::CStateManager&, float) { return x56c_stateProg == 3; }\n\nvoid CNewIntroBoss::Generate(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x56c_stateProg = 0;\n    x568_locomotion = GetLocoForHealth(mgr);\n    SendScriptMsgs(EScriptObjectState::Entered, mgr, EScriptObjectMessage::None);\n  } else if (msg == EStateMsg::Update) {\n    if (x56c_stateProg == 0) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::Generate) {\n        x56c_stateProg = 2;\n        return;\n      }\n\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBCGenerateCmd(GetGenerateForHealth(mgr)));\n    } else if (x56c_stateProg == 2) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::Generate) {\n        x56c_stateProg = 3;\n        SendScriptMsgs(EScriptObjectState::Exited, mgr, EScriptObjectMessage::None);\n      }\n    }\n  }\n}\n\npas::EGenerateType CNewIntroBoss::GetGenerateForHealth(const CStateManager& mgr) const {\n  return GetHealthInfo(mgr)->GetHP() > 0.33f * x640_initialHp ? pas::EGenerateType::Three : pas::EGenerateType::Four;\n}\n\nbool CNewIntroBoss::InAttackPosition(CStateManager& mgr, float dt) {\n  return x330_stateMachineState.GetTime() > 0.25f && x678_ &&\n         x450_bodyController->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::Turn &&\n         !ShouldTurn(mgr, dt);\n}\n\nvoid CNewIntroBoss::Attack(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate)\n    x56c_stateProg = 0;\n  else if (msg == EStateMsg::Update) {\n    if (x56c_stateProg == 0) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::ProjectileAttack)\n        x56c_stateProg = 2;\n      else {\n        x450_bodyController->GetCommandMgr().DeliverCmd(\n            CBCProjectileAttackCmd(pas::ESeverity::One, mgr.GetPlayer().GetTranslation(), false));\n      }\n    } else if (x56c_stateProg == 2) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::ProjectileAttack) {\n        x56c_stateProg = 3;\n        x638_ = 0.f;\n      }\n\n      if (const CPlasmaProjectile* proj =\n              static_cast<const CPlasmaProjectile*>(mgr.GetObjectById(x676_curProjectile))) {\n        if (!proj->GetActive())\n          x62c_targetPos = mgr.GetPlayer().GetTranslation();\n      }\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    if (GetLocoForHealth(mgr) == pas::ELocomotionType::Lurk || GetLocoForHealth(mgr) == pas::ELocomotionType::Combat)\n      x678_ ^= 1;\n    else\n      x678_ = false;\n  }\n}\n\nvoid CNewIntroBoss::Patrol(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate || msg == EStateMsg::Update) {\n    x450_bodyController->SetLocomotionType(x568_locomotion);\n    if (x638_ > 0.2f)\n      x62c_targetPos = PlayerPos(mgr);\n    else\n      x62c_targetPos = x610_lookPos + ((x638_ / 0.2f) * (PlayerPos(mgr) - x610_lookPos));\n\n    if (ShouldTurn(mgr, 0.f)) {\n      x450_bodyController->GetCommandMgr().DeliverCmd(\n          CBCLocomotionCmd({}, (x604_predictedPlayerPos - GetTranslation()).normalized(), 1.f));\n    }\n  }\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CNewIntroBoss.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <string>\n\n#include \"Runtime/Character/CBoneTracking.hpp\"\n#include \"Runtime/Weapon/CProjectileInfo.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CCollisionActorManager;\nclass CDamageInfo;\n\nnamespace MP1 {\nclass CNewIntroBoss : public CPatterned {\n  pas::ELocomotionType x568_locomotion = pas::ELocomotionType::Relaxed;\n  u32 x56c_stateProg = 0;\n  float x570_minTurnAngle;\n  CBoneTracking x574_boneTracking;\n  CProjectileInfo x5ac_projectileInfo;\n  TUniqueId x5d4_stage1Projectile = kInvalidUniqueId;\n  TUniqueId x5d6_stage2Projectile = kInvalidUniqueId;\n  TUniqueId x5d8_stage3Projectile = kInvalidUniqueId;\n  std::string x5dc_damageLocator; // ???\n  std::unique_ptr<CCollisionActorManager> x5ec_collisionManager;\n  CAssetId x5f0_beamContactFxId;\n  CAssetId x5f4_beamPulseFxId;\n  CAssetId x5f8_beamTextureId;\n  CAssetId x5fc_beamGlowTextureId;\n  TUniqueId x600_headActor = kInvalidUniqueId;\n  TUniqueId x602_pelvisActor = kInvalidUniqueId;\n  zeus::CVector3f x604_predictedPlayerPos;\n  zeus::CVector3f x610_lookPos;\n  zeus::CVector3f x61c_startPlayerPos;\n  float x628_firingTime = 0.f;\n  zeus::CVector3f x62c_targetPos;\n  float x638_ = 0.2f;\n  float x63c_attackTime = 8.f;\n  float x640_initialHp = 0.f;\n  zeus::CTransform x644_initialXf;\n  s16 x674_rumbleVoice = -1;\n  TUniqueId x676_curProjectile = kInvalidUniqueId;\n  bool x678_ = false;\n  pas::ELocomotionType GetLocoForHealth(const CStateManager&) const;\n  pas::EGenerateType GetGenerateForHealth(const CStateManager&) const;\n  float GetNextAttackTime(CStateManager&) const;\n  zeus::CVector3f PlayerPos(const CStateManager&) const;\n  void DeleteBeam(CStateManager&);\n  void StopRumble(CStateManager&);\n\npublic:\n  DEFINE_PATTERNED(NewIntroBoss);\n  CNewIntroBoss(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                CModelData&& mData, const CPatternedInfo& pInfo, const CActorParameters& actParms, float minTurnAngle,\n                CAssetId projectile, const CDamageInfo& dInfo, CAssetId beamContactFxId, CAssetId beamPulseFxId,\n                CAssetId beamTextureId, CAssetId beamGlowTextureId);\n\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager&) override;\n  void Think(float dt, CStateManager& mgr) override;\n  void AddToRenderer(const zeus::CFrustum&, CStateManager&) override;\n  void OnScanStateChanged(EScanState, CStateManager&) override;\n  CProjectileInfo* GetProjectileInfo() override { return &x5ac_projectileInfo; }\n  zeus::CAABox GetSortingBounds(const CStateManager&) const override {\n    zeus::CAABox box = GetModelData()->GetBounds();\n    return zeus::CAABox({-0.5f, -0.5f, box.min.z()}, {0.5f, 0.5f, box.max.z()}).getTransformedAABox(x34_transform);\n  }\n\n  std::optional<zeus::CAABox> GetTouchBounds() const override { return {}; }\n  void DoUserAnimEvent(CStateManager&, const CInt32POINode&, EUserEventType, float dt) override;\n  void Generate(CStateManager&, EStateMsg, float) override;\n  void Attack(CStateManager&, EStateMsg, float) override;\n  void Patrol(CStateManager&, EStateMsg, float) override;\n  bool ShouldTurn(CStateManager&, float) override;\n  bool ShouldAttack(CStateManager&, float) override;\n  bool AIStage(CStateManager&, float) override;\n  bool AnimOver(CStateManager&, float) override;\n  bool InAttackPosition(CStateManager&, float) override;\n};\n\n} // namespace MP1\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/MP1/World/COmegaPirate.cpp",
    "content": "#include \"Runtime/MP1/World/COmegaPirate.hpp\"\n\n#include \"Runtime/Camera/CGameCamera.hpp\"\n#include \"Runtime/Collision/CCollisionActor.hpp\"\n#include \"Runtime/Collision/CCollisionActorManager.hpp\"\n#include \"Runtime/Collision/CGameCollision.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Graphics/CGX.hpp\"\n#include \"Runtime/Weapon/CGameProjectile.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CScriptEffect.hpp\"\n#include \"Runtime/World/CScriptPlatform.hpp\"\n#include \"Runtime/World/CScriptSound.hpp\"\n#include \"Runtime/World/CScriptWaypoint.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce::MP1 {\nnamespace {\nconstexpr std::array<SSphereJointInfo, 1> skSphereJoints{{\n    {\"lockon_target_LCTR\", 1.f},\n}};\n\nconstexpr std::array<SOBBJointInfo, 11> skObbJoints{{\n    {\"Spine_2\", \"Collar\", zeus::skOne3f},\n    {\"R_toe_1\", \"R_ankle\", zeus::skOne3f},\n    {\"L_toe_1\", \"L_ankle\", zeus::skOne3f},\n    {\"R_knee\", \"R_ankle\", zeus::skOne3f},\n    {\"L_knee\", \"L_ankle\", zeus::skOne3f},\n    {\"R_elbow\", \"R_wrist\", zeus::skOne3f},\n    {\"L_elbow\", \"L_wrist\", zeus::skOne3f},\n    {\"R_wrist\", \"R_index_1\", zeus::skOne3f},\n    {\"L_wrist\", \"L_index_1\", zeus::skOne3f},\n    {\"R_index_1\", \"R_index_3_SDK\", zeus::CVector3f{2.f}},\n    {\"L_index_1\", \"L_index_3_SDK\", zeus::CVector3f{2.f}},\n}};\n} // namespace\n\nCOmegaPirate::CFlash::CFlash(TUniqueId uid, const CEntityInfo& info, const zeus::CVector3f& pos,\n                             TLockedToken<CTexture>& thermalSpot, float delay)\n: CActor(uid, true, \"Omega Pirate Flash\", info, zeus::CTransform::Translate(pos), CModelData::CModelDataNull(), {},\n         CActorParameters::None(), kInvalidUniqueId)\n, xf4_delay(delay) {}\n\nvoid COmegaPirate::CFlash::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid COmegaPirate::CFlash::Think(float dt, CStateManager& mgr) {\n  CEntity::Think(dt, mgr);\n  xf4_delay -= dt;\n  if (xf4_delay > 0.f) {\n    return;\n  }\n\n  xf8_time += dt;\n  float intensity = xf8_time;\n  if (intensity <= 0.75f) {\n    intensity /= 0.75f;\n  } else {\n    intensity = 1.f - (intensity - 0.75f) / 0.25f;\n  }\n  const CGameCamera* const camera = mgr.GetCameraManager()->GetCurrentCamera(mgr);\n  const zeus::CVector3f dist = (GetTranslation() - camera->GetTranslation()).normalized();\n  float dot = dist.dot(camera->GetTransform().frontVector());\n  float dVar4 = 0.f;\n  if (dot >= 0.f) {\n    dVar4 = dot * dot;\n  }\n  xfc_size = dVar4 * intensity;\n  if (xf8_time > 1.f) {\n    mgr.FreeScriptObject(GetUniqueId());\n  }\n}\n\nvoid COmegaPirate::CFlash::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) {\n  mgr.RenderLast(GetUniqueId());\n  if (xf0_thermalSpot == nullptr && xe8_thermalSpotToken.IsLocked() && xe8_thermalSpotToken.HasReference()) {\n    xf0_thermalSpot = xe8_thermalSpotToken.GetObj();\n  }\n}\n\nvoid COmegaPirate::CFlash::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) {}\n\nvoid COmegaPirate::CFlash::Render(CStateManager& mgr) {\n  const CPlayerState::EPlayerVisor visor = mgr.GetPlayerState()->GetActiveVisor(mgr);\n  if (visor == CPlayerState::EPlayerVisor::Thermal) {\n    return;\n  }\n  if (xf0_thermalSpot == nullptr || !xe8_thermalSpotToken) {\n    return;\n  }\n  xf0_thermalSpot->Load(GX_TEXMAP0, EClampMode::Repeat);\n\n  float sizeMul = 35.f;\n  if (visor == CPlayerState::EPlayerVisor::XRay) {\n    CGX::SetBlendMode(GX_BM_SUBTRACT, GX_BL_ONE, GX_BL_ZERO, GX_LO_CLEAR);\n    sizeMul = 60.f;\n  } else {\n    CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::One, ERglLogicOp::Clear);\n  }\n\n  float size = xfc_size * sizeMul;\n  const auto rightVec = size * CGraphics::mViewMatrix.rightVector();\n  const auto upVec = size * CGraphics::mViewMatrix.upVector();\n  const auto rvS = GetTranslation() - rightVec;\n  const auto rvP = GetTranslation() + rightVec;\n  CGraphics::SetModelMatrix(zeus::CTransform());\n  CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvModulate);\n  CGraphics::SetTevOp(ERglTevStage::Stage1, CTevCombiners::kEnvPassthru);\n  CGraphics::SetDepthWriteMode(false, ERglEnum::Always, false);\n  CGraphics::StreamColor(zeus::CColor{1.f, std::min(1.f, size)});\n  CGraphics::StreamBegin(ERglPrimitive::TriangleFan);\n  CGraphics::StreamTexcoord(0.f, 0.f);\n  CGraphics::StreamVertex(rvS + upVec);\n  CGraphics::StreamTexcoord(1.f, 0.f);\n  CGraphics::StreamVertex(rvP + upVec);\n  CGraphics::StreamTexcoord(1.f, 1.f);\n  CGraphics::StreamVertex(rvP - upVec);\n  CGraphics::StreamTexcoord(0.f, 1.f);\n  CGraphics::StreamVertex(rvS - upVec);\n  CGraphics::StreamEnd();\n}\n\nCOmegaPirate::COmegaPirate(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                           CModelData&& mData, const CPatternedInfo& pInfo, const CActorParameters& actParms,\n                           CElitePirateData data, CAssetId skeletonModelId, CAssetId skeletonSkinRulesId,\n                           CAssetId skeletonLayoutInfoId)\n: CElitePirate(uid, name, info, xf, std::move(mData), pInfo, actParms, data)\n, x9d0_initialScale(GetModelData()->GetScale())\n, x9f0_skeletonModel(*g_SimplePool, skeletonModelId, skeletonSkinRulesId, skeletonLayoutInfoId)\n, xb70_thermalSpot(g_SimplePool->GetObj(\"Thermal_Spot_2\"sv)) {\n  x9a4_scriptWaypointPlatforms.reserve(3);\n  x9b8_scriptEffects.reserve(24);\n  x9dc_scriptPlatforms.reserve(4);\n  xaa0_scriptSounds.reserve(4);\n  xab4_.reserve(3);\n  xb7c_.resize(4, 0);\n\n  SetMass(100000.f);\n\n  CMaterialFilter filter = GetMaterialFilter();\n  filter.ExcludeList().Add(\n      CMaterialList{EMaterialTypes::Character, EMaterialTypes::CollisionActor, EMaterialTypes::Platform});\n  SetMaterialFilter(filter);\n\n  GetSearchPath()->SetPadding(20.f);\n}\n\nvoid COmegaPirate::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  switch (msg) {\n  default:\n    CElitePirate::AcceptScriptMsg(msg, uid, mgr);\n    break;\n  case EScriptObjectMessage::Activate:\n    CElitePirate::AcceptScriptMsg(msg, uid, mgr);\n    xa38_collisionActorMgr1->SetActive(mgr, true);\n    xa9c_collisionActorMgr2->SetActive(mgr, true);\n    GetKnockBackController().SetAutoResetImpulse(false);\n    if (auto* entity = mgr.ObjectById(x990_launcherId2)) {\n      entity->SetActive(true);\n    }\n    break;\n  case EScriptObjectMessage::Deactivate:\n    CElitePirate::AcceptScriptMsg(msg, uid, mgr);\n    xa38_collisionActorMgr1->SetActive(mgr, false);\n    xa9c_collisionActorMgr2->SetActive(mgr, false);\n    if (auto* entity = mgr.ObjectById(x990_launcherId2)) {\n      entity->SetActive(false);\n    }\n    break;\n  case EScriptObjectMessage::Decrement:\n    x9ec_decrement = true;\n    break;\n  case EScriptObjectMessage::Increment:\n    SetShotAt(true, mgr);\n    break;\n  case EScriptObjectMessage::Open:\n    xb7c_[3] -= xb7c_[3] == 0 ? 0 : 1;\n    break;\n  case EScriptObjectMessage::Reset:\n    xb78_codeTrigger = true;\n    break;\n  case EScriptObjectMessage::SetToMax:\n    xa3c_hearPlayer = true;\n    break;\n  case EScriptObjectMessage::SetToZero:\n    xb7c_[2] -= xb7c_[2] == 0 ? 0 : 1;\n    break;\n  case EScriptObjectMessage::Start:\n    x3b4_speed = 1.f;\n    ++xade_armorPiecesDestroyed;\n    if (xade_armorPiecesDestroyed < 4) {\n      GetBodyController()->GetCommandMgr().DeliverCmd(CBCKnockBackCmd(zeus::skLeft, pas::ESeverity::One));\n    }\n    break;\n  case EScriptObjectMessage::Stop:\n    DeathDestroy(mgr);\n    break;\n  case EScriptObjectMessage::StopAndReset:\n    xb7c_[1] -= xb7c_[1] == 0 ? 0 : 1;\n    break;\n  case EScriptObjectMessage::UNKM18:\n    xb7c_[0] -= xb7c_[0] == 0 ? 0 : 1;\n    break;\n  case EScriptObjectMessage::Action:\n    x3b4_speed = 1.f;\n    ++xade_armorPiecesDestroyed;\n    if (xade_armorPiecesDestroyed < 4) {\n      GetBodyController()->GetCommandMgr().DeliverCmd(CBCKnockBackCmd(zeus::skRight, pas::ESeverity::One));\n    }\n    break;\n  case EScriptObjectMessage::Alert:\n    CElitePirate::AcceptScriptMsg(msg, uid, mgr);\n    break;\n  case EScriptObjectMessage::Touched:\n    CElitePirate::AcceptScriptMsg(msg, uid, mgr);\n    if (uid == x990_launcherId2 && x990_launcherId2 != kInvalidUniqueId) {\n      SetShotAt(true, mgr);\n    }\n    if (const TCastToConstPtr<CCollisionActor> actor = mgr.ObjectById(uid)) {\n      if (const TCastToConstPtr<CPlayer> player = mgr.ObjectById(actor->GetLastTouchedObject())) {\n        if (x420_curDamageRemTime <= 0.f) {\n          mgr.ApplyDamage(GetUniqueId(), player->GetUniqueId(), GetUniqueId(), GetContactDamage(),\n                          CMaterialFilter::MakeInclude({EMaterialTypes::Solid}), zeus::skZero3f);\n          x420_curDamageRemTime = x424_damageWaitTime;\n        }\n      }\n    }\n    break;\n  case EScriptObjectMessage::Registered:\n    CElitePirate::AcceptScriptMsg(msg, uid, mgr);\n    x990_launcherId2 = mgr.AllocateUniqueId();\n    CreateGrenadeLauncher(mgr, x990_launcherId2);\n    SetupCollisionManager(mgr);\n    GetBodyController()->SetLocomotionType(pas::ELocomotionType::Internal8);\n    x402_27_noXrayModel = false;\n    xa4c_initialXf = x34_transform;\n    xa98_maxEnergy = HealthInfo(mgr)->GetHP();\n    if (auto* actor = static_cast<CActor*>(mgr.ObjectById(GetLauncherId()))) {\n      actor->RemoveMaterial(EMaterialTypes::Scannable, mgr);\n    }\n    if (auto* actor = static_cast<CActor*>(mgr.ObjectById(x990_launcherId2))) {\n      actor->RemoveMaterial(EMaterialTypes::Scannable, mgr);\n    }\n    GetKnockBackController().SetAutoResetImpulse(false);\n    SetupPathFindSearch();\n    break;\n  case EScriptObjectMessage::Deleted:\n    CElitePirate::AcceptScriptMsg(msg, uid, mgr);\n    xa38_collisionActorMgr1->Destroy(mgr);\n    xa9c_collisionActorMgr2->Destroy(mgr);\n    mgr.FreeScriptObject(x990_launcherId2);\n    break;\n  case EScriptObjectMessage::InitializedInArea:\n    CElitePirate::AcceptScriptMsg(msg, uid, mgr);\n\n    for (const SConnection& conn : GetConnectionList()) {\n      TUniqueId connId = mgr.GetIdForScript(conn.x8_objId);\n      if (connId == kInvalidUniqueId || conn.x0_state != EScriptObjectState::Attack) {\n        continue;\n      }\n\n      if (conn.x4_msg == EScriptObjectMessage::Activate) {\n        if (const TCastToConstPtr<CScriptEffect> effect = mgr.ObjectById(connId)) {\n          x9b8_scriptEffects.emplace_back(connId, effect->GetName());\n        } else if (TCastToPtr<CScriptPlatform> platform = mgr.ObjectById(connId)) {\n          x9dc_scriptPlatforms.emplace_back(connId, platform->GetName());\n          platform->AddMaterial(EMaterialTypes::Target, EMaterialTypes::Orbit, EMaterialTypes::Character, mgr);\n          platform->RemoveMaterial(EMaterialTypes::Scannable, mgr);\n          CMaterialList excludes = platform->GetMaterialFilter().GetExcludeList();\n          excludes.Add({EMaterialTypes::Player, EMaterialTypes::Character, EMaterialTypes::CollisionActor});\n          CMaterialList includes = GetMaterialFilter().GetIncludeList();\n          platform->SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(includes, excludes));\n          xae4_platformVuln = *platform->GetDamageVulnerability();\n          xb54_platformColor = platform->GetDrawFlags().x4_color;\n        } else if (const TCastToConstPtr<CScriptSound> sound = mgr.ObjectById(connId)) {\n          xaa0_scriptSounds.emplace_back(connId, sound->GetName());\n        }\n      } else if (conn.x4_msg == EScriptObjectMessage::Follow) {\n        if (const TCastToConstPtr<CScriptWaypoint> waypoint = mgr.ObjectById(connId)) {\n          std::vector<TUniqueId> waypointPlatformIds;\n          waypointPlatformIds.reserve(3);\n          for (const SConnection& waypointConn : waypoint->GetConnectionList()) {\n            auto waypointConnId = mgr.GetIdForScript(waypointConn.x8_objId);\n            if (TCastToPtr<CScriptPlatform> platform = mgr.ObjectById(waypointConnId)) {\n              platform->AddMaterial(EMaterialTypes::Target, EMaterialTypes::Orbit, mgr);\n              waypointPlatformIds.push_back(waypointConnId);\n            }\n          }\n          x9a4_scriptWaypointPlatforms.emplace_back(connId, waypointPlatformIds);\n        }\n      }\n    }\n    break;\n  case EScriptObjectMessage::Damage:\n    if (uid == x990_launcherId2 && x990_launcherId2 != kInvalidUniqueId) {\n      GetBodyController()->GetCommandMgr().DeliverCmd(\n          CBCKnockBackCmd(GetTransform().frontVector(), pas::ESeverity::Eight));\n    }\n    CElitePirate::AcceptScriptMsg(msg, uid, mgr);\n    if (uid == xa46_ && xa7c_xrayAlphaState == EXRayFadeState::WaitForTrigger) {\n      xa7c_xrayAlphaState = EXRayFadeState::FadeOut;\n      xa84_xrayAlphaStateTime = 0.f;\n    }\n    break;\n  case EScriptObjectMessage::InvulnDamage:\n    if (const TCastToConstPtr<CGameProjectile> projectile = mgr.GetObjectById(uid)) {\n      if (xa4a_heartVisible) {\n        mgr.ApplyDamage(uid, xa46_, projectile->GetOwnerId(), projectile->GetDamageInfo(),\n                        CMaterialFilter::MakeInclude({EMaterialTypes::Solid}), zeus::skZero3f);\n      }\n    }\n    SetShotAt(true, mgr);\n  }\n}\n\nbool COmegaPirate::AggressionCheck(CStateManager& mgr, float arg) {\n  return x990_launcherId2 == kInvalidUniqueId && CElitePirate::AggressionCheck(mgr, arg);\n}\n\nvoid COmegaPirate::Attack(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x402_28_isMakingBigStrike = true;\n    x504_damageDur = 1.f;\n  } else if (msg == EStateMsg::Deactivate) {\n    x402_28_isMakingBigStrike = false;\n    x504_damageDur = 0.f;\n  }\n  CElitePirate::Attack(mgr, msg, dt);\n}\n\nbool COmegaPirate::CodeTrigger(CStateManager&, float) { return xb78_codeTrigger; }\n\nvoid COmegaPirate::Cover(CStateManager& mgr, EStateMsg msg, float dt) {\n  CElitePirate::Cover(mgr, msg, dt);\n  if (msg == EStateMsg::Activate) {\n    xad4_cachedSpeed = x3b4_speed;\n    xad8_cover = true;\n  } else if (msg == EStateMsg::Deactivate) {\n    xad8_cover = false;\n  }\n}\n\nbool COmegaPirate::CoverBlown(CStateManager&, float) {\n  if (x9b4_lostAllHp) {\n    x9b4_lostAllHp = false;\n    xb5c_hpLost = 0.f;\n    return true;\n  }\n  return false;\n}\n\nvoid COmegaPirate::Dizzy(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    xa44_targetable = true;\n  } else if (msg == EStateMsg::Update) {\n    GetBodyController()->GetCommandMgr().DeliverCmd(CBCLoopReactionCmd(pas::EReactionType::Two));\n  } else if (msg == EStateMsg::Deactivate) {\n    GetBodyController()->GetCommandMgr().DeliverCmd(CBodyStateCmd(EBodyStateCmd::ExitState));\n  }\n}\n\nvoid COmegaPirate::DoubleSnap(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    SendScriptMsgs(EScriptObjectState::MaxReached, mgr, EScriptObjectMessage::None);\n    SetShotAt(false, mgr);\n    SetState(EState::Zero);\n    xa44_targetable = false;\n    xa4a_heartVisible = false;\n    xa88_xrayFadeInTrigger = false;\n    xa8c_xrayFadeOutTime = 3.f;\n    for (auto& entry : x9dc_scriptPlatforms) {\n      if (auto* platform = static_cast<CScriptPlatform*>(mgr.ObjectById(entry.first))) {\n        platform->SetActive(true);\n        platform->SetDamageVulnerability(xae4_platformVuln);\n        platform->AddMaterial(EMaterialTypes::Orbit, EMaterialTypes::Target, mgr);\n        platform->SetDisableXRayAlpha(false);\n        platform->SetXRayFog(true);\n      }\n    }\n    xb64_stateTime = 17.f;\n    AddMaterial(EMaterialTypes::Scannable, mgr);\n  } else if (msg == EStateMsg::Update) {\n    if (GetState() == EState::Zero) {\n      if (GetBodyController()->GetCurrentStateId() == pas::EAnimationState::Step) {\n        SetState(EState::Two);\n      } else {\n        GetBodyController()->GetCommandMgr().DeliverCmd(\n            CBCStepCmd(pas::EStepDirection::Backward, pas::EStepType::BreakDodge));\n      }\n    } else if (GetState() == EState::Two && GetBodyController()->GetCurrentStateId() != pas::EAnimationState::Step) {\n      SetState(EState::Over);\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    if (auto* launcher = static_cast<CGrenadeLauncher*>(mgr.ObjectById(GetLauncherId()))) {\n      launcher->SetFollowPlayer(true);\n    }\n    if (auto* launcher = static_cast<CGrenadeLauncher*>(mgr.ObjectById(x990_launcherId2))) {\n      launcher->SetFollowPlayer(true);\n    }\n  }\n}\n\nvoid COmegaPirate::DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) {\n  switch (type) {\n  case EUserEventType::EggLay:\n    if (x990_launcherId2 != kInvalidUniqueId) {\n      if (auto* entity = mgr.ObjectById(x990_launcherId2)) {\n        mgr.SendScriptMsg(entity, GetUniqueId(), EScriptObjectMessage::Action);\n      }\n    }\n    break;\n  case EUserEventType::FadeIn:\n    x9a1_fadeIn = true;\n    break;\n  case EUserEventType::FadeOut:\n    if (x994_normalFadeState != ENormalFadeState::Two && x9a1_fadeIn) {\n      x994_normalFadeState = ENormalFadeState::One;\n      xa30_skeletonFadeState = ESkeletonFadeState::FadeIn;\n    }\n    break;\n  case EUserEventType::ObjectPickUp:\n    xab4_.clear();\n    xac8_ = 0;\n    ++xacc_;\n    if (xac4_ == 0) {\n      sub_8028cbec(2, mgr);\n    } else if (xac4_ == 1) {\n      sub_8028cbec(1, mgr);\n      sub_8028cbec(1, mgr);\n    } else if (xac4_ == 2) {\n      sub_8028cbec(2, mgr);\n      sub_8028cbec(1, mgr);\n    } else if (xac4_ == 3) {\n      sub_8028cbec(1, mgr);\n      sub_8028cbec(1, mgr);\n      sub_8028cbec(1, mgr);\n    }\n    SendScriptMsgs(EScriptObjectState::Arrived, mgr, EScriptObjectMessage::None);\n    break;\n  case EUserEventType::Projectile:\n  case EUserEventType::DamageOn:\n  case EUserEventType::DamageOff:\n  case EUserEventType::ScreenShake:\n  case EUserEventType::BeginAction:\n  case EUserEventType::BecomeShootThrough:\n  default:\n    CElitePirate::DoUserAnimEvent(mgr, node, type, dt);\n  }\n}\n\nvoid COmegaPirate::Enraged(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    SetState(EState::Zero);\n  } else if (msg == EStateMsg::Update) {\n    if (GetState() == EState::Zero) {\n      if (GetBodyController()->GetCurrentStateId() == pas::EAnimationState::Taunt) {\n        SetState(EState::Two);\n      } else {\n        GetBodyController()->GetCommandMgr().DeliverCmd(CBCTauntCmd(pas::ETauntType::Zero));\n      }\n    } else if (GetState() == EState::Two && GetBodyController()->GetCurrentStateId() != pas::EAnimationState::Taunt) {\n      SetState(EState::Over);\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    xadf_launcher1FollowPlayer = true;\n    xae0_launcher2FollowPlayer = true;\n  }\n}\n\nvoid COmegaPirate::Explode(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    SetState(EState::Zero);\n    xad0_scaleUpTrigger = false;\n  } else if (msg == EStateMsg::Update) {\n    if (GetState() == EState::Zero) {\n      if (GetBodyController()->GetCurrentStateId() == pas::EAnimationState::Step) {\n        SetState(EState::Two);\n      } else {\n        GetBodyController()->GetCommandMgr().DeliverCmd(\n            CBCStepCmd(pas::EStepDirection::Forward, pas::EStepType::Dodge));\n      }\n    } else if (GetState() == EState::Two && GetBodyController()->GetCurrentStateId() != pas::EAnimationState::Step) {\n      SetState(EState::Over);\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    GetBodyController()->SetLocomotionType(xa40_locomotionType);\n  }\n}\n\nvoid COmegaPirate::Faint(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    GetBodyController()->GetCommandMgr().DeliverCmd(CBCLoopReactionCmd(pas::EReactionType::Zero));\n    xa44_targetable = true;\n    xa4a_heartVisible = true;\n    if (xa7c_xrayAlphaState == EXRayFadeState::WaitForTrigger) {\n      xa8c_xrayFadeOutTime = 0.333f;\n    }\n    for (const auto& entry : x9dc_scriptPlatforms) {\n      if (auto* platform = static_cast<CScriptPlatform*>(mgr.ObjectById(entry.first))) {\n        platform->SetActive(true);\n      }\n    }\n  } else if (msg == EStateMsg::Update) {\n    if (xb4c_armorPiecesHealed < 4 && x9c8_scaleState == EScaleState::None && xb58_healTime >= 2.5f) {\n      float alpha = std::min(xb50_armorPieceHealTime, 1.f);\n      float invAlpha = 1.f - alpha;\n      size_t idx = 0;\n      for (const auto& entry : x9dc_scriptPlatforms) {\n        if (auto* platform = static_cast<CScriptPlatform*>(mgr.ObjectById(entry.first))) {\n          if (mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::XRay) {\n            if (xb4c_armorPiecesHealed > idx) {\n              constexpr CModelFlags flags{5, 0, 3, zeus::skBlack};\n              platform->SetDrawFlags(flags);\n            } else if (xb4c_armorPiecesHealed == idx) {\n              if (!xb6e_armorPieceActivated) {\n                SendScriptMsgs(EScriptObjectState::Entered, mgr, EScriptObjectMessage::None);\n                xb6e_armorPieceActivated = true;\n              }\n              const CModelFlags flags{5, 0, 3, zeus::CColor{invAlpha, alpha}};\n              platform->SetDrawFlags(flags);\n            }\n          } else {\n            constexpr CModelFlags flags{5, 0, 3, zeus::CColor{1.f, 0.f}};\n            platform->SetDrawFlags(flags);\n          }\n        }\n        ++idx;\n      }\n      if (xb50_armorPieceHealTime > 1.f) {\n        ++xb4c_armorPiecesHealed;\n        xb50_armorPieceHealTime = 0.f;\n        xb58_healTime = 0.f;\n        xb6e_armorPieceActivated = false;\n      }\n      xb50_armorPieceHealTime += dt;\n    }\n    xb58_healTime += dt;\n    GetBodyController()->GetCommandMgr().DeliverCmd(CBCLoopReactionCmd(pas::EReactionType::Zero));\n  } else if (msg == EStateMsg::Deactivate) {\n    GetBodyController()->GetCommandMgr().DeliverCmd(CBodyStateCmd(EBodyStateCmd::ExitState));\n    if (xb58_healTime >= 2.5f) {\n      ++xb4c_armorPiecesHealed;\n    }\n  }\n}\n\nvoid COmegaPirate::Growth(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x9c8_scaleState = EScaleState::ScaleDownY;\n    xad0_scaleUpTrigger = false;\n    RemoveMaterial(EMaterialTypes::RadarObject, EMaterialTypes::Scannable, mgr);\n    xb6c_exit1Sent = false;\n    xb6d_exit2Sent = false;\n    ProcessSoundEvent(SFXsfx0B27, 1.f, 0, 0.1f, 1000.f, 0.16f, 1.f, zeus::skZero3f, GetTranslation(),\n                      mgr.GetNextAreaId(), mgr, false);\n  } else if (msg == EStateMsg::Update) {\n    if (xb68_ == 0) {\n      if (x330_stateMachineState.GetTime() > 0.3f * xb64_stateTime && !xb6c_exit1Sent) {\n        SendScriptMsgs(EScriptObjectState::Exited, mgr, EScriptObjectMessage::None);\n        xb6c_exit1Sent = true;\n      }\n      if (x330_stateMachineState.GetTime() > 0.6f * xb64_stateTime && !xb6d_exit2Sent) {\n        SendScriptMsgs(EScriptObjectState::Exited, mgr, EScriptObjectMessage::None);\n        xb6d_exit2Sent = true;\n      }\n    } else if (x330_stateMachineState.GetTime() > 0.5f * xb64_stateTime && !xb6c_exit1Sent) {\n      SendScriptMsgs(EScriptObjectState::Exited, mgr, EScriptObjectMessage::None);\n      xb6c_exit1Sent = true;\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    TeleportToFurthestPlatform(mgr);\n    xad0_scaleUpTrigger = true;\n    AddMaterial(EMaterialTypes::RadarObject, mgr);\n    ProcessSoundEvent(SFXsfx0B28, 1.f, 0, 0.1f, 1000.f, 0.16f, 1.f, zeus::skZero3f, GetTranslation(),\n                      mgr.GetNextAreaId(), mgr, false);\n  }\n}\n\nvoid COmegaPirate::JumpBack(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    SetShotAt(false, mgr);\n    SetState(EState::Two);\n    xade_armorPiecesDestroyed = 0;\n    xadf_launcher1FollowPlayer = false;\n    xae0_launcher2FollowPlayer = false;\n    xb68_ = 0;\n    xa40_locomotionType = GetBodyController()->GetLocomotionType();\n    GetBodyController()->SetLocomotionType(pas::ELocomotionType::Internal5);\n    GetBodyController()->GetCommandMgr().DeliverCmd(\n        CBCKnockBackCmd(GetTransform().frontVector(), pas::ESeverity::Five));\n    for (const auto& entry : x9dc_scriptPlatforms) {\n      if (auto* platform = static_cast<CScriptPlatform*>(mgr.ObjectById(entry.first))) {\n        platform->SetActive(false);\n      }\n    }\n  } else if (msg == EStateMsg::Update) {\n    if (GetState() == EState::Two && GetBodyController()->GetCurrentStateId() != pas::EAnimationState::KnockBack) {\n      SetState(EState::Over);\n    }\n  }\n}\n\nbool COmegaPirate::Landed(CStateManager& mgr, float arg) { return xb4c_armorPiecesHealed == 4; }\n\nzeus::CVector3f COmegaPirate::GetOrbitPosition(const CStateManager& mgr) const {\n  if (x990_launcherId2 != kInvalidUniqueId &&\n      mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::Thermal) {\n    if (const auto* actor = static_cast<const CActor*>(mgr.GetObjectById(x990_launcherId2))) {\n      return GetLockOnPosition(actor);\n    }\n  }\n  return CElitePirate::GetOrbitPosition(mgr);\n}\n\nbool COmegaPirate::HearPlayer(CStateManager& mgr, float arg) { return xa3c_hearPlayer; }\n\nvoid COmegaPirate::PathFind(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    xad4_cachedSpeed = x3b4_speed;\n    x3b4_speed = 1.4f * xad4_cachedSpeed;\n  } else if (msg == EStateMsg::Update) {\n    if (GetBodyController()->GetCurrentStateId() == pas::EAnimationState::KnockBack) {\n      x3b4_speed = xad4_cachedSpeed;\n    } else if (xad4_cachedSpeed == x3b4_speed) {\n      x3b4_speed = 1.4f * xad4_cachedSpeed;\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x3b4_speed = xad4_cachedSpeed;\n  }\n  CElitePirate::PathFind(mgr, msg, dt);\n}\n\nvoid COmegaPirate::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) {\n  CElitePirate::PreRender(mgr, frustum);\n  if (mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::XRay) {\n    xb4_drawFlags = CModelFlags{1, 0, 3, zeus::CColor{xa80_xrayAlpha}};\n  }\n}\n\nvoid COmegaPirate::Render(CStateManager& mgr) {\n  auto* mData = GetModelData();\n  auto* animData = mData->GetAnimationData();\n\n  CGraphics::SetModelMatrix(GetTransform() * zeus::CTransform::Scale(mData->GetScale()));\n\n  if (mgr.GetPlayerState()->GetCurrentVisor() != CPlayerState::EPlayerVisor::XRay && xa2c_skeletonAlpha > 0.f) {\n    const CModelFlags flags{5, 0, 3, zeus::CColor{1.f, xa2c_skeletonAlpha}};\n    animData->Render(x9f0_skeletonModel, flags, nullptr, {});\n  }\n  if (x9a0_visible) {\n    bool isXRay = mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::XRay;\n    if (isXRay) {\n      g_Renderer->SetWorldFog(ERglFogMode::None, 0.f, 1.f, zeus::skBlack);\n      const CModelFlags flags{5, 0, 1, zeus::CColor{1.f, 0.2f}};\n      auto& model = *animData->GetModelData().GetObj();\n      animData->Render(model, flags, nullptr, {});\n    }\n    CPatterned::Render(mgr);\n    if (isXRay) {\n      mgr.SetupFogForArea(GetAreaIdAlways());\n    }\n  }\n}\n\nvoid COmegaPirate::Retreat(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    SetShotAt(false, mgr);\n    SetState(EState::Zero);\n    SendScriptMsgs(EScriptObjectState::Inside, mgr, EScriptObjectMessage::None);\n    xad0_scaleUpTrigger = false;\n    xa44_targetable = false;\n    xa4a_heartVisible = false;\n    xb5c_hpLost = 0.f;\n    xb60_hpLostInPhase = 0.f;\n    xb64_stateTime = 5.f;\n    ++xb68_;\n  } else if (msg == EStateMsg::Update) {\n    if (GetState() == EState::Zero) {\n      if (GetBodyController()->GetCurrentStateId() == pas::EAnimationState::Step) {\n        SetState(EState::Two);\n      } else {\n        GetBodyController()->GetCommandMgr().DeliverCmd(\n            CBCStepCmd(pas::EStepDirection::Forward, pas::EStepType::BreakDodge));\n      }\n    } else if (GetState() == EState::Two && GetBodyController()->GetCurrentStateId() != pas::EAnimationState::Step) {\n      SetState(EState::Over);\n    }\n  }\n}\n\nvoid COmegaPirate::Run(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    xad4_cachedSpeed = x3b4_speed;\n    x3b4_speed = 1.4f * xad4_cachedSpeed;\n  } else if (msg == EStateMsg::Update) {\n    if (GetBodyController()->GetCurrentStateId() == pas::EAnimationState::KnockBack) {\n      x3b4_speed = xad4_cachedSpeed;\n    } else if (xad4_cachedSpeed == x3b4_speed) {\n      x3b4_speed = 1.4f * xad4_cachedSpeed;\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x3b4_speed = xad4_cachedSpeed;\n  }\n  CElitePirate::Run(mgr, msg, dt);\n}\n\nbool COmegaPirate::ShotAt(CStateManager& mgr, float arg) { return CElitePirate::ShotAt(mgr, arg); }\n\nbool COmegaPirate::ShouldCallForBackup(CStateManager& mgr, float arg) {\n  if (CElitePirate::ShouldCallForBackup(mgr, arg)) {\n    return ShouldCallForBackupFromLauncher(mgr, x990_launcherId2);\n  }\n  return false;\n}\n\nbool COmegaPirate::ShouldFire(CStateManager& mgr, float arg) {\n  if (CElitePirate::ShouldFire(mgr, arg)) {\n    return ShouldFireFromLauncher(mgr, x990_launcherId2);\n  }\n  return false;\n}\n\nbool COmegaPirate::ShouldMove(CStateManager& mgr, float arg) {\n  return xb64_stateTime < x330_stateMachineState.GetTime();\n}\n\nvoid COmegaPirate::Shuffle(CStateManager& mgr, EStateMsg msg, float dt) {}\n\nvoid COmegaPirate::Skid(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    SetState(EState::Zero);\n  } else if (msg == EStateMsg::Update) {\n    if (GetState() == EState::Zero) {\n      if (GetBodyController()->GetCurrentStateId() == pas::EAnimationState::Step) {\n        SetState(EState::Two);\n      } else {\n        GetBodyController()->GetCommandMgr().DeliverCmd(\n            CBCStepCmd(pas::EStepDirection::Forward, pas::EStepType::Normal));\n      }\n    } else if (GetState() == EState::Two && GetBodyController()->GetCurrentStateId() != pas::EAnimationState::Step) {\n      SetState(EState::Over);\n    }\n  }\n}\n\nvoid COmegaPirate::Suck(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    SetState(EState::Zero);\n    xa7c_xrayAlphaState = EXRayFadeState::FadeOut;\n    xa88_xrayFadeInTrigger = true;\n  } else if (msg == EStateMsg::Update) {\n    if (GetState() == EState::Zero) {\n      if (GetBodyController()->GetCurrentStateId() == pas::EAnimationState::Step) {\n        SetState(EState::Two);\n      } else {\n        GetBodyController()->GetCommandMgr().DeliverCmd(\n            CBCStepCmd(pas::EStepDirection::Backward, pas::EStepType::Normal));\n        GetBodyController()->SetLocomotionType(pas::ELocomotionType::Relaxed);\n      }\n    } else if (GetState() == EState::Two && GetBodyController()->GetCurrentStateId() != pas::EAnimationState::Step) {\n      SetState(EState::Over);\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    for (const auto& entry : x9dc_scriptPlatforms) {\n      if (auto* platform = static_cast<CScriptPlatform*>(mgr.ObjectById(entry.first))) {\n        platform->SetDamageVulnerability(CDamageVulnerability::ImmuneVulnerabilty());\n        platform->RemoveMaterial(EMaterialTypes::Target, EMaterialTypes::Orbit, mgr);\n        platform->SetDisableXRayAlpha(true);\n        CModelFlags flags{5, 0, 3, zeus::CColor{1.f, 0.f}};\n        platform->SetDrawFlags(flags);\n        platform->SetXRayFog(false);\n      }\n    }\n    xb50_armorPieceHealTime = 0.f;\n    xb58_healTime = 2.5f;\n    xb4c_armorPiecesHealed = 0;\n  }\n}\n\nvoid COmegaPirate::TargetPatrol(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    xad4_cachedSpeed = x3b4_speed;\n    x3b4_speed = 1.4f * xad4_cachedSpeed;\n  } else if (msg == EStateMsg::Update) {\n    if (GetBodyController()->GetCurrentStateId() == pas::EAnimationState::KnockBack) {\n      x3b4_speed = xad4_cachedSpeed;\n    } else if (xad4_cachedSpeed == x3b4_speed) {\n      x3b4_speed = 1.4f * xad4_cachedSpeed;\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x3b4_speed = xad4_cachedSpeed;\n  }\n  CElitePirate::TargetPatrol(mgr, msg, dt);\n}\n\nvoid COmegaPirate::Think(float dt, CStateManager& mgr) {\n  if (!GetActive()) {\n    return;\n  }\n\n  SetAlert(true);\n  CElitePirate::Think(dt, mgr);\n\n  {\n    float maxHealth = xa98_maxEnergy;\n    CHealthInfo* healthInfo = HealthInfo(mgr);\n    if (healthInfo->GetHP() > 0.2f * maxHealth) {\n      if (healthInfo->GetHP() > 0.7f * maxHealth) {\n        if (xacc_ > 4) {\n          xac4_ = 1;\n        }\n      } else {\n        xac4_ = 2;\n      }\n    } else {\n      xac4_ = 3;\n    }\n  }\n\n  UpdateActorTransform(mgr, x990_launcherId2, \"grenadeLauncher2_LCTR\"sv);\n\n  UpdateNormalAlpha(mgr, dt);\n  UpdateSkeletonAlpha(mgr, dt);\n  UpdateXRayAlpha(mgr, dt);\n  if ((!x9a1_fadeIn || xa4a_heartVisible) &&\n      mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::XRay && xa44_targetable) {\n    AddMaterial(EMaterialTypes::Target, EMaterialTypes::Orbit, mgr);\n    if (x9c8_scaleState == EScaleState::WaitForTrigger) {\n      xa38_collisionActorMgr1->SetActive(mgr, false);\n      xa9c_collisionActorMgr2->SetActive(mgr, false);\n    } else {\n      xa38_collisionActorMgr1->SetActive(mgr, true);\n      xa9c_collisionActorMgr2->SetActive(mgr, true);\n      if (auto* entity = mgr.ObjectById(xa48_)) {\n        entity->SetActive(false);\n      }\n    }\n  } else {\n    RemoveMaterial(EMaterialTypes::Target, EMaterialTypes::Orbit, mgr);\n    xa38_collisionActorMgr1->SetActive(mgr, false);\n    if (x9a1_fadeIn) {\n      xa9c_collisionActorMgr2->SetActive(mgr, true);\n      if (auto* entity = mgr.ObjectById(xa48_)) {\n        entity->SetActive(true);\n      }\n    } else {\n      xa9c_collisionActorMgr2->SetActive(mgr, false);\n      if (auto* entity = mgr.ObjectById(xa48_)) {\n        entity->SetActive(false);\n      }\n    }\n  }\n\n  UpdateScale(mgr, dt);\n  xa38_collisionActorMgr1->Update(dt, mgr, CCollisionActorManager::EUpdateOptions::ObjectSpace);\n  xa9c_collisionActorMgr2->Update(dt, mgr, CCollisionActorManager::EUpdateOptions::ObjectSpace);\n\n  if (auto* entity = static_cast<CActor*>(mgr.ObjectById(xa46_))) {\n    float hp = GetHealthInfo(mgr)->GetHP();\n    *HealthInfo(mgr) = *entity->GetHealthInfo(mgr);\n    float hpChange = hp - GetHealthInfo(mgr)->GetHP();\n    xb5c_hpLost += hpChange;\n    xb60_hpLostInPhase += hpChange;\n  }\n\n  if (GetHealthInfo(mgr)->GetHP() > 0.f) {\n    if (xb5c_hpLost > 100.f) {\n      x9b4_lostAllHp = true;\n    } else {\n      if (xb60_hpLostInPhase > 20.f) {\n        GetBodyController()->GetCommandMgr().DeliverCmd(\n            CBCAdditiveReactionCmd(pas::EAdditiveReactionType::One, 1.f, false));\n        xb60_hpLostInPhase = 0.f;\n      }\n    }\n  } else {\n    DeathDestroy(mgr);\n  }\n\n  sub_8028c704(mgr, dt);\n\n  for (auto& entry : x9dc_scriptPlatforms) {\n    auto* platform = static_cast<CScriptPlatform*>(mgr.ObjectById(entry.first));\n    if ((!xb78_codeTrigger && !xb79_bossPhaseActive) || xa4a_heartVisible) {\n      platform->RemoveMaterial(EMaterialTypes::Target, EMaterialTypes::Orbit, mgr);\n    } else {\n      platform->AddMaterial(EMaterialTypes::Target, EMaterialTypes::Orbit, mgr);\n    }\n  }\n\n  {\n    const CPlayerState& playerState = *mgr.GetPlayerState();\n    CPlayer& player = mgr.GetPlayer();\n    if (GetCollisionActorManager().GetActive() && playerState.IsFiringComboBeam() &&\n        playerState.GetCurrentBeam() == CPlayerState::EBeamId::Wave && xad8_cover) {\n      AddMaterial(EMaterialTypes::Target, mgr);\n      player.ResetAimTargetPrediction(GetUniqueId());\n      for (auto& entry : x9dc_scriptPlatforms) {\n        if (auto* platform = static_cast<CScriptPlatform*>(mgr.ObjectById(entry.first))) {\n          platform->RemoveMaterial(EMaterialTypes::Target, mgr);\n        }\n      }\n      player.GetPlayerGun()->GetAuxWeapon().SetNewTarget(GetUniqueId(), mgr);\n    } else if (!xa4a_heartVisible) {\n      RemoveMaterial(EMaterialTypes::Target, mgr);\n      for (auto& entry : x9dc_scriptPlatforms) {\n        if (auto* platform = static_cast<CScriptPlatform*>(mgr.ObjectById(entry.first))) {\n          platform->AddMaterial(EMaterialTypes::Target, mgr);\n        }\n      }\n      CAuxWeapon& weapon = player.GetPlayerGun()->GetAuxWeapon();\n      if (weapon.HasTarget(mgr) == GetUniqueId()) {\n        if (player.ValidateOrbitTargetId(player.GetOrbitTargetId(), mgr) == CPlayer::EOrbitValidationResult::OK) {\n          weapon.SetNewTarget(player.GetOrbitTargetId(), mgr);\n        } else {\n          weapon.SetNewTarget(kInvalidUniqueId, mgr);\n        }\n      }\n    }\n  }\n\n  if (auto* launcher = static_cast<CGrenadeLauncher*>(mgr.ObjectById(GetLauncherId()))) {\n    launcher->SetFollowPlayer(xadf_launcher1FollowPlayer);\n  }\n  if (auto* launcher = static_cast<CGrenadeLauncher*>(mgr.ObjectById(x990_launcherId2))) {\n    launcher->SetFollowPlayer(xae0_launcher2FollowPlayer);\n  }\n\n  if (x9ec_decrement) {\n    x9ec_decrement = false;\n    x330_stateMachineState.SetState(mgr, *this, GetStateMachine(), \"JumpBack\"sv);\n  }\n\n  if (xb68_ > 1) {\n    DoUserAnimEvent(mgr, CInt32POINode{}, EUserEventType::ObjectPickUp, dt);\n    xb68_ = 0;\n  }\n\n  if (xb8c_avoidStaticCollisionTime > 0.f) {\n    const zeus::CAABox& box = GetBoundingBox();\n    CGameCollision::AvoidStaticCollisionWithinRadius(mgr, *this, 8, dt, 1.f, 1.5f * (box.max.x() - box.min.x()),\n                                                     10000.f, 0.25f);\n    xb8c_avoidStaticCollisionTime = 0.f;\n  }\n  xb8c_avoidStaticCollisionTime += dt;\n}\n\nvoid COmegaPirate::WallDetach(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    SetState(EState::Zero);\n  } else if (msg == EStateMsg::Update) {\n    if (GetState() == EState::Zero) {\n      if (GetBodyController()->GetCurrentStateId() == pas::EAnimationState::Step) {\n        SetState(EState::Two);\n      } else {\n        GetBodyController()->GetCommandMgr().DeliverCmd(CBCStepCmd(pas::EStepDirection::Left, pas::EStepType::Dodge));\n      }\n    } else if (GetState() == EState::Two && GetBodyController()->GetCurrentStateId() != pas::EAnimationState::Step) {\n      SetState(EState::Over);\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    mgr.SetBossParams(GetUniqueId(), xa98_maxEnergy, 89);\n    xb79_bossPhaseActive = true;\n  }\n}\n\nvoid COmegaPirate::WallHang(CStateManager& mgr, EStateMsg msg, float dt) {}\n\nvoid COmegaPirate::SetupHealthInfo(CStateManager& mgr) {\n  CElitePirate::SetupHealthInfo(mgr);\n  SetupLauncherHealthInfo(mgr, x990_launcherId2);\n}\n\nvoid COmegaPirate::SetLaunchersActive(CStateManager& mgr, bool val) {\n  CElitePirate::SetLaunchersActive(mgr, val);\n  SetLauncherActive(mgr, val, x990_launcherId2);\n}\n\nvoid COmegaPirate::CreateFlash(CStateManager& mgr, float arg) {\n  mgr.AddObject(new CFlash(mgr.AllocateUniqueId(), CEntityInfo{GetAreaIdAlways(), CEntity::NullConnectionList},\n                           GetRenderBounds().center(), xb70_thermalSpot, arg));\n}\n\nvoid COmegaPirate::SetupCollisionManager(CStateManager& mgr) {\n  std::vector<CJointCollisionDescription> sphereJoints;\n  sphereJoints.reserve(skSphereJoints.size());\n  AddSphereCollisionList(skSphereJoints.data(), skSphereJoints.size(), sphereJoints);\n  xa38_collisionActorMgr1 =\n      std::make_unique<CCollisionActorManager>(mgr, GetUniqueId(), GetAreaIdAlways(), sphereJoints, true);\n  SetupCollisionActorInfo1(xa38_collisionActorMgr1, mgr);\n  xa46_ = xa38_collisionActorMgr1->GetCollisionDescFromIndex(0).GetCollisionActorId();\n  if (auto* actor = static_cast<CActor*>(mgr.ObjectById(xa46_))) {\n    *actor->HealthInfo(mgr) = *HealthInfo(mgr);\n  }\n\n  std::vector<CJointCollisionDescription> obbJoints;\n  obbJoints.reserve(skObbJoints.size());\n  AddOBBAutoSizeCollisionList(skObbJoints.data(), skObbJoints.size(), obbJoints);\n  xa9c_collisionActorMgr2 =\n      std::make_unique<CCollisionActorManager>(mgr, GetUniqueId(), GetAreaIdAlways(), obbJoints, true);\n  SetupCollisionActorInfo2(xa9c_collisionActorMgr2, mgr);\n  xa48_ = xa9c_collisionActorMgr2->GetCollisionDescFromIndex(0).GetCollisionActorId();\n}\n\nvoid COmegaPirate::AddSphereCollisionList(const SSphereJointInfo* joints, size_t count,\n                                          std::vector<CJointCollisionDescription>& outJoints) const {\n  const CAnimData* animData = GetModelData()->GetAnimationData();\n  for (size_t i = 0; i < count; ++i) {\n    const auto& joint = joints[i];\n    const CSegId seg = animData->GetLocatorSegId(joint.name);\n    if (seg.IsInvalid()) {\n      continue;\n    }\n    outJoints.emplace_back(CJointCollisionDescription::SphereCollision(seg, joint.radius, joint.name, 0.001f));\n  }\n}\n\nvoid COmegaPirate::AddOBBAutoSizeCollisionList(const SOBBJointInfo* joints, size_t count,\n                                               std::vector<CJointCollisionDescription>& outJoints) const {\n  const CAnimData* animData = GetModelData()->GetAnimationData();\n  for (size_t i = 0; i < count; ++i) {\n    const auto& joint = joints[i];\n    const CSegId from = animData->GetLocatorSegId(joint.from);\n    const CSegId to = animData->GetLocatorSegId(joint.to);\n    if (to.IsInvalid() || from.IsInvalid()) {\n      continue;\n    }\n    outJoints.emplace_back(CJointCollisionDescription::OBBAutoSizeCollision(\n        from, to, joint.bounds, CJointCollisionDescription::EOrientationType::One,\n        \"Omega_Pirate_OBB_\"s + std::to_string(i), 0.001f));\n  }\n}\n\nvoid COmegaPirate::SetupCollisionActorInfo1(const std::unique_ptr<CCollisionActorManager>& actMgr, CStateManager& mgr) {\n  for (size_t i = 0; i < actMgr->GetNumCollisionActors(); ++i) {\n    const auto& colDesc = actMgr->GetCollisionDescFromIndex(i);\n    const TUniqueId uid = colDesc.GetCollisionActorId();\n    if (TCastToPtr<CCollisionActor> act = mgr.ObjectById(uid)) {\n      act->AddMaterial(EMaterialTypes::ScanPassthrough, EMaterialTypes::CameraPassthrough, EMaterialTypes::AIJoint,\n                       EMaterialTypes::Immovable, mgr);\n      const CMaterialFilter& selfFilter = GetMaterialFilter();\n      const CMaterialFilter& actFilter = act->GetMaterialFilter();\n      CMaterialFilter filter =\n          CMaterialFilter::MakeIncludeExclude(selfFilter.GetIncludeList(), selfFilter.GetExcludeList());\n      filter.IncludeList().Add(actFilter.GetIncludeList());\n      filter.ExcludeList().Add(actFilter.GetExcludeList());\n      filter.ExcludeList().Add(EMaterialTypes::Platform);\n      act->SetMaterialFilter(filter);\n      act->RemoveMaterial(EMaterialTypes::ProjectilePassthrough, mgr);\n    }\n  }\n}\n\nvoid COmegaPirate::SetupCollisionActorInfo2(const std::unique_ptr<CCollisionActorManager>& actMgr, CStateManager& mgr) {\n  for (size_t i = 0; i < actMgr->GetNumCollisionActors(); ++i) {\n    const auto& colDesc = actMgr->GetCollisionDescFromIndex(i);\n    const TUniqueId uid = colDesc.GetCollisionActorId();\n    if (TCastToPtr<CCollisionActor> act = mgr.ObjectById(uid)) {\n      act->AddMaterial(EMaterialTypes::ScanPassthrough, EMaterialTypes::CameraPassthrough, EMaterialTypes::AIJoint,\n                       EMaterialTypes::Immovable, mgr);\n      const CMaterialFilter& selfFilter = GetMaterialFilter();\n      const CMaterialFilter& actFilter = act->GetMaterialFilter();\n      CMaterialFilter filter =\n          CMaterialFilter::MakeIncludeExclude(selfFilter.GetIncludeList(), selfFilter.GetExcludeList());\n      filter.IncludeList().Add(actFilter.GetIncludeList());\n      filter.IncludeList().Add(EMaterialTypes::Player);\n      filter.ExcludeList().Add(actFilter.GetExcludeList());\n      filter.ExcludeList().Remove(EMaterialTypes::Player);\n      filter.ExcludeList().Add(EMaterialTypes::Platform);\n      act->SetMaterialFilter(filter);\n      act->RemoveMaterial(EMaterialTypes::ProjectilePassthrough, mgr);\n      act->SetDamageVulnerability(CDamageVulnerability::ReflectVulnerabilty());\n    }\n  }\n}\n\nu8 COmegaPirate::sub_8028bfac() const {\n  std::array<u8, 4> arr{0, 0, 0, 0};\n  for (const auto i : xab4_) {\n    ++arr[i];\n  }\n  u8 ret = 0;\n  for (size_t i = 0; i < arr.size(); ++i) {\n    if (xb7c_[i] != 0 || arr[i] != 0) {\n      ++ret;\n    }\n  }\n  return ret;\n}\n\nvoid COmegaPirate::sub_8028cbec(u32 arg, CStateManager& mgr) {\n  int i = mgr.GetActiveRandom()->Next() % 4;\n  u32 v = 3 - (xab4_.size() + sub_8028c230());\n  u32 ct = std::min(arg, v);\n\n  if (sub_8028bfac() < 2) {\n    for (u32 n = 0; n < ct; ++n) {\n      xab4_.push_back(i);\n    }\n  } else {\n    sub_8028c840(ct, mgr);\n  }\n}\n\nu8 COmegaPirate::sub_8028c230() const { return xb7c_[0] + xb7c_[1] + xb7c_[2] + xb7c_[3]; }\n\nvoid COmegaPirate::sub_8028c840(u32 arg, CStateManager& mgr) {\n  std::array<u8, 4> arr{0, 0, 0, 0};\n  for (const auto i : xab4_) {\n    ++arr[i];\n  }\n  std::vector<u8> vec;\n  for (size_t i = 0; i < arr.size(); ++i) {\n    if (xb7c_[i] != 0 || arr[i] != 0) {\n      vec.push_back(i);\n    }\n  }\n  if (vec.empty()) {\n    sub_8028cbec(arg, mgr);\n  } else {\n    s32 rand = mgr.GetActiveRandom()->Next();\n    int sz = vec.size();\n    int val = vec[rand - (rand / sz) * sz];\n    u32 v = 3 - (xab4_.size() + sub_8028c230());\n    u32 ct = std::min(arg, v);\n    for (u32 n = 0; n < ct; ++n) {\n      xab4_.push_back(val);\n    }\n  }\n}\n\nvoid COmegaPirate::TeleportToFurthestPlatform(CStateManager& mgr) {\n  size_t waypointIdx = 0;\n  float maxDist = 0.f;\n  zeus::CVector3f pos;\n  for (size_t i = 0; i < x9a4_scriptWaypointPlatforms.size(); ++i) {\n    const auto& entry = x9a4_scriptWaypointPlatforms[i];\n    if (const TCastToConstPtr<CScriptWaypoint> waypoint = mgr.GetObjectById(entry.first)) {\n      const auto waypointPos = waypoint->GetTranslation();\n      const float dist = (mgr.GetPlayer().GetTranslation() - waypointPos).magnitude();\n      if (dist > maxDist && waypoint->GetUniqueId() != xada_lastWaypointId) {\n        waypointIdx = i;\n        maxDist = dist;\n        pos = waypointPos;\n      }\n    }\n  }\n  SetTranslation(FindGround(pos, mgr));\n\n  auto waypointId = x9a4_scriptWaypointPlatforms[waypointIdx].first;\n  xada_lastWaypointId = waypointId;\n  if (TCastToPtr<CScriptWaypoint> waypoint = mgr.ObjectById(waypointId)) {\n    waypoint->SendScriptMsgs(EScriptObjectState::Arrived, mgr, EScriptObjectMessage::None);\n  }\n\n  const zeus::CVector2f distXY = (mgr.GetPlayer().GetTranslation().toVec2f() - GetTranslation().toVec2f()).normalized();\n  const zeus::CVector2f frontVecXY = GetTransform().frontVector().toVec2f().normalized();\n  const zeus::CQuaternion quat =\n      zeus::CQuaternion::shortestRotationArc(zeus::CVector3f{frontVecXY, 0.f}, zeus::CVector3f{distXY, 0.f});\n  SetTransform(zeus::CTransform{GetTransform().basis * zeus::CMatrix3f{quat}, GetTranslation()});\n}\n\nzeus::CVector3f COmegaPirate::FindGround(const zeus::CVector3f& pos, CStateManager& mgr) const {\n  auto result = mgr.RayStaticIntersection(pos, zeus::skDown, 30.f, CMaterialFilter::MakeInclude(EMaterialTypes::Solid));\n  return result.IsValid() ? result.GetPoint() : pos;\n}\n\nvoid COmegaPirate::UpdateNormalAlpha(CStateManager& mgr, float dt) {\n  if (x994_normalFadeState == ENormalFadeState::One) {\n    x99c_normalAlpha = 1.f - std::min(x998_normalFadeTime, 1.25f) / 1.25f;\n    x42c_color.a() = x99c_normalAlpha;\n    if (x998_normalFadeTime > 1.25f) {\n      x994_normalFadeState = ENormalFadeState::Two;\n      x9a1_fadeIn = false;\n      x998_normalFadeTime = 0.f;\n    }\n    x998_normalFadeTime += dt;\n    x9a0_visible = true;\n  } else if (x994_normalFadeState == ENormalFadeState::Two) {\n    x99c_normalAlpha = 0.f;\n    if (x998_normalFadeTime > 1.5f && x9a1_fadeIn) {\n      CreateFlash(mgr, 0.f);\n      x994_normalFadeState = ENormalFadeState::Three;\n      x998_normalFadeTime = 0.f;\n    }\n    x998_normalFadeTime += dt;\n    x9a0_visible = false;\n  } else if (x994_normalFadeState == ENormalFadeState::Three) {\n    x99c_normalAlpha = std::min(x998_normalFadeTime, 1.f) / 1.25f;\n    if (x998_normalFadeTime > 1.f) {\n      x994_normalFadeState = ENormalFadeState::Zero;\n      x998_normalFadeTime = 0.f;\n    }\n    x998_normalFadeTime += dt;\n    x9a0_visible = true;\n  } else {\n    x99c_normalAlpha = 1.f;\n    x9a0_visible = true;\n  }\n\n  float alpha = x99c_normalAlpha;\n  if (mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::XRay) {\n    alpha = 0.f;\n    x99c_normalAlpha = 1.f;\n    x9a0_visible = true;\n  }\n  x42c_color.a() = x99c_normalAlpha;\n\n  if (alpha >= 1.f) {\n    if (auto* launcher = static_cast<CGrenadeLauncher*>(mgr.ObjectById(GetLauncherId()))) {\n      launcher->SetVisible(true);\n      launcher->SetAddColor(zeus::CColor{0.f});\n    }\n    if (auto* launcher = static_cast<CGrenadeLauncher*>(mgr.ObjectById(x990_launcherId2))) {\n      launcher->SetVisible(true);\n      launcher->SetAddColor(zeus::CColor{0.f});\n    }\n  } else {\n    if (auto* launcher = static_cast<CGrenadeLauncher*>(mgr.ObjectById(GetLauncherId()))) {\n      launcher->SetAddColor(zeus::CColor{1.f, alpha});\n      launcher->SetVisible(alpha != 0.f);\n    }\n    if (auto* launcher = static_cast<CGrenadeLauncher*>(mgr.ObjectById(x990_launcherId2))) {\n      launcher->SetAddColor(zeus::CColor{1.f, alpha});\n      launcher->SetVisible(alpha != 0.f);\n    }\n  }\n}\n\nvoid COmegaPirate::sub_8028c704(CStateManager& mgr, float dt) {\n  int idx = xac8_;\n  if (idx >= xab4_.size()) {\n    return;\n  }\n\n  if (xab0_ <= 0.f) {\n    ++xac8_;\n    int val = xab4_[idx];\n    if (val == 0) {\n      SendScriptMsgs(EScriptObjectState::Closed, mgr, EScriptObjectMessage::None);\n      xb7c_[0]++;\n    } else if (val == 1) {\n      SendScriptMsgs(EScriptObjectState::Open, mgr, EScriptObjectMessage::None);\n      xb7c_[1]++;\n    } else if (val == 2) {\n      SendScriptMsgs(EScriptObjectState::CloseIn, mgr, EScriptObjectMessage::None);\n      xb7c_[2]++;\n    } else if (val == 3) {\n      SendScriptMsgs(EScriptObjectState::Modify, mgr, EScriptObjectMessage::None);\n      xb7c_[3]++;\n    }\n    xab0_ = 1.5f;\n  }\n  xab0_ -= dt;\n}\n\nvoid COmegaPirate::UpdateXRayAlpha(CStateManager& mgr, float dt) {\n  if (xa7c_xrayAlphaState == EXRayFadeState::FadeIn) {\n    xa80_xrayAlpha = std::min(xa84_xrayAlphaStateTime, xa90_xrayFadeInTime) / xa90_xrayFadeInTime;\n    if (xa84_xrayAlphaStateTime > xa90_xrayFadeInTime) {\n      xa7c_xrayAlphaState = EXRayFadeState::None;\n      xa84_xrayAlphaStateTime = 0.f;\n    }\n    xa84_xrayAlphaStateTime += dt;\n  } else if (xa7c_xrayAlphaState == EXRayFadeState::WaitForTrigger) {\n    xa80_xrayAlpha = 0.f;\n    if ((xa94_xrayFadeTriggerTime < xa84_xrayAlphaStateTime) && !xa88_xrayFadeInTrigger) {\n      xa7c_xrayAlphaState = EXRayFadeState::FadeIn;\n      xa84_xrayAlphaStateTime = 0.f;\n    }\n    xa84_xrayAlphaStateTime += dt;\n  } else if (xa7c_xrayAlphaState == EXRayFadeState::FadeOut) {\n    xa80_xrayAlpha = 1.f - std::min(xa84_xrayAlphaStateTime, xa8c_xrayFadeOutTime) / xa8c_xrayFadeOutTime;\n    if (xa84_xrayAlphaStateTime > xa8c_xrayFadeOutTime) {\n      xa7c_xrayAlphaState = EXRayFadeState::WaitForTrigger;\n      xa84_xrayAlphaStateTime = 0.f;\n    }\n    xa84_xrayAlphaStateTime += dt;\n  } else {\n    xa80_xrayAlpha = 1.f;\n  }\n}\n\nvoid COmegaPirate::UpdateScale(CStateManager& mgr, float dt) {\n  auto* modelData = GetModelData();\n  zeus::CVector3f scale = modelData->GetScale();\n  switch (x9c8_scaleState) {\n  case EScaleState::None:\n  default:\n    return;\n  case EScaleState::ScaleDownX:\n    scale.x() = x9d0_initialScale.x() * std::min(1.f, 0.005f + (1.f - std::min(x9cc_scaleTime, 0.25f) / 0.25f));\n    if (x9cc_scaleTime > 0.25f) {\n      x9c8_scaleState = EScaleState::ScaleDownZ;\n      x9cc_scaleTime = 0.f;\n    }\n    x9cc_scaleTime += dt;\n    break;\n  case EScaleState::ScaleDownY:\n    scale.y() = x9d0_initialScale.y() * std::min(1.f, 0.005f + (1.f - std::min(x9cc_scaleTime, 0.25f) / 0.25f));\n    if (x9cc_scaleTime > 0.25f) {\n      x9c8_scaleState = EScaleState::ScaleDownX;\n      x9cc_scaleTime = 0.f;\n    }\n    x9cc_scaleTime += dt;\n    break;\n  case EScaleState::ScaleDownZ:\n    scale.z() = x9d0_initialScale.z() * std::min(1.f, 0.005f + (1.f - std::min(x9cc_scaleTime, 0.25f) / 0.25f));\n    if (x9cc_scaleTime > 0.25f) {\n      x9c8_scaleState = EScaleState::WaitForTrigger;\n      x9cc_scaleTime = 0.f;\n    }\n    x9cc_scaleTime += dt;\n    break;\n  case EScaleState::WaitForTrigger:\n    if (x9cc_scaleTime > 0.1f && xad0_scaleUpTrigger) {\n      x9c8_scaleState = EScaleState::ScaleUpZ;\n      x9cc_scaleTime = 0.f;\n    }\n    x9cc_scaleTime += dt;\n    break;\n  case EScaleState::ScaleUpX:\n    scale.x() = x9d0_initialScale.x() * std::min(1.f, 0.005f + std::min(x9cc_scaleTime, 0.25f) / 0.25f);\n    if (x9cc_scaleTime > 0.25f) {\n      x9c8_scaleState = EScaleState::ScaleUpY;\n      x9cc_scaleTime = 0.f;\n    }\n    x9cc_scaleTime += dt;\n    break;\n  case EScaleState::ScaleUpY:\n    scale.y() = x9d0_initialScale.y() * std::min(1.f, 0.005f + std::min(x9cc_scaleTime, 0.25f) / 0.25f);\n    if (x9cc_scaleTime > 0.25f) {\n      x9c8_scaleState = EScaleState::None;\n      x9cc_scaleTime = 0.f;\n    }\n    x9cc_scaleTime += dt;\n    break;\n  case EScaleState::ScaleUpZ:\n    scale.z() = x9d0_initialScale.z() * std::min(1.f, 0.005f + std::min(x9cc_scaleTime, 0.25f) / 0.25f);\n    if (x9cc_scaleTime > 0.25f) {\n      x9c8_scaleState = EScaleState::ScaleUpX;\n      x9cc_scaleTime = 0.f;\n    }\n    x9cc_scaleTime += dt;\n    break;\n  }\n\n  modelData->SetScale(scale);\n  if (auto* launcher = static_cast<CGrenadeLauncher*>(mgr.ObjectById(GetLauncherId()))) {\n    launcher->GetModelData()->SetScale(scale);\n  }\n  if (auto* launcher = static_cast<CGrenadeLauncher*>(mgr.ObjectById(x990_launcherId2))) {\n    launcher->GetModelData()->SetScale(scale);\n  }\n  for (const auto& entry : x9dc_scriptPlatforms) {\n    if (auto* platform = static_cast<CScriptPlatform*>(mgr.ObjectById(entry.first))) {\n      platform->GetModelData()->SetScale(scale);\n    }\n  }\n}\n\nvoid COmegaPirate::UpdateSkeletonAlpha(CStateManager& mgr, float dt) {\n  if (xa30_skeletonFadeState == ESkeletonFadeState::FadeOut) {\n    xa2c_skeletonAlpha = 1.f - std::min(xa34_skeletonStateTime, 1.f);\n    if (xa34_skeletonStateTime > 1.f) {\n      xa30_skeletonFadeState = ESkeletonFadeState::None;\n      xa34_skeletonStateTime = 0.f;\n    }\n    xa34_skeletonStateTime += dt;\n  } else if (xa30_skeletonFadeState == ESkeletonFadeState::Flash) {\n    xa2c_skeletonAlpha = 1.f;\n    if (xa34_skeletonStateTime > 1.f) {\n      xa30_skeletonFadeState = ESkeletonFadeState::FadeOut;\n      xa34_skeletonStateTime = 0.f;\n      CreateFlash(mgr, 0.75f);\n    }\n    xa34_skeletonStateTime += dt;\n  } else if (xa30_skeletonFadeState == ESkeletonFadeState::FadeIn) {\n    xa2c_skeletonAlpha = std::min(xa34_skeletonStateTime, 1.f);\n    if (xa34_skeletonStateTime > 1.f) {\n      xa30_skeletonFadeState = ESkeletonFadeState::Flash;\n      xa34_skeletonStateTime = 0.f;\n    }\n    xa34_skeletonStateTime += dt;\n  } else {\n    xa2c_skeletonAlpha = 0.f;\n  }\n}\n\nvoid COmegaPirate::DeathDestroy(CStateManager& mgr) {\n  RemoveEmitter();\n  SetTransform(xa4c_initialXf);\n  x9a1_fadeIn = true;\n  xa4a_heartVisible = false;\n  SendScriptMsgs(EScriptObjectState::DeathRattle, mgr, EScriptObjectMessage::None);\n  SendScriptMsgs(EScriptObjectState::Dead, mgr, EScriptObjectMessage::None);\n  SendScriptMsgs(EScriptObjectState::Inside, mgr, EScriptObjectMessage::None);\n  for (auto& entry : x9dc_scriptPlatforms) {\n    if (auto* platform = static_cast<CScriptPlatform*>(mgr.ObjectById(entry.first))) {\n      platform->SetActive(false);\n      platform->RemoveMaterial(EMaterialTypes::Target, EMaterialTypes::Orbit, mgr);\n      mgr.FreeScriptObject(entry.first);\n    }\n  }\n  x9dc_scriptPlatforms.clear();\n\n  if (auto* launcher = static_cast<CGrenadeLauncher*>(mgr.ObjectById(GetLauncherId()))) {\n    launcher->SetActive(false);\n  }\n  if (auto* launcher = static_cast<CGrenadeLauncher*>(mgr.ObjectById(x990_launcherId2))) {\n    launcher->SetActive(false);\n  }\n  SetActive(false);\n  mgr.SetBossParams(kInvalidUniqueId, 0.f, 89);\n  xa38_collisionActorMgr1->SetActive(mgr, false);\n  xa9c_collisionActorMgr2->SetActive(mgr, false);\n}\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/COmegaPirate.hpp",
    "content": "#pragma once\n\n#include \"Runtime/MP1/World/CElitePirate.hpp\"\n\nnamespace metaforce::MP1 {\nclass COmegaPirate : public CElitePirate {\nprivate:\n  class CFlash : public CActor {\n  private:\n    TToken<CTexture> xe8_thermalSpotToken;\n    CTexture* xf0_thermalSpot = nullptr;\n    float xf4_delay;\n    float xf8_time = 0.f;\n    float xfc_size = 0.f;\n\n  public:\n    DEFINE_ENTITY\n    CFlash(TUniqueId uid, const CEntityInfo& info, const zeus::CVector3f& pos, TLockedToken<CTexture>& thermalSpot,\n           float delay);\n\n    void Accept(IVisitor& visitor) override;\n    void Think(float dt, CStateManager& mgr) override;\n    void PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) override;\n    void AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) override;\n    void Render(CStateManager& mgr) override;\n  };\n\n  TUniqueId x990_launcherId2 = kInvalidUniqueId;\n  enum class ENormalFadeState {\n    Zero,\n    One,\n    Two,\n    Three,\n  } x994_normalFadeState = ENormalFadeState::Zero;\n  float x998_normalFadeTime = 0.f;\n  float x99c_normalAlpha = 1.f;\n  bool x9a0_visible = true;\n  bool x9a1_fadeIn = true;\n  std::vector<std::pair<TUniqueId, std::vector<TUniqueId>>> x9a4_scriptWaypointPlatforms;\n  bool x9b4_lostAllHp = false;\n  std::vector<std::pair<TUniqueId, std::string_view>> x9b8_scriptEffects;\n  enum class EScaleState {\n    None,\n    ScaleDownX,\n    ScaleDownY,\n    ScaleDownZ,\n    WaitForTrigger,\n    ScaleUpX,\n    ScaleUpY,\n    ScaleUpZ,\n  } x9c8_scaleState = EScaleState::None;\n  float x9cc_scaleTime = 0.f;\n  zeus::CVector3f x9d0_initialScale;\n  std::vector<std::pair<TUniqueId, std::string_view>> x9dc_scriptPlatforms;\n  bool x9ec_decrement = false;\n  CSkinnedModel x9f0_skeletonModel;\n  float xa2c_skeletonAlpha = 0.f;\n  enum class ESkeletonFadeState {\n    None,\n    FadeOut,\n    Flash,\n    FadeIn,\n  } xa30_skeletonFadeState = ESkeletonFadeState::None;\n  float xa34_skeletonStateTime = 0.f;\n  std::unique_ptr<CCollisionActorManager> xa38_collisionActorMgr1;\n  bool xa3c_hearPlayer = false;\n  pas::ELocomotionType xa40_locomotionType = pas::ELocomotionType::Relaxed;\n  bool xa44_targetable = false;\n  TUniqueId xa46_ = kInvalidUniqueId;\n  TUniqueId xa48_ = kInvalidUniqueId;\n  bool xa4a_heartVisible = false;\n  zeus::CTransform xa4c_initialXf;\n  enum class EXRayFadeState {\n    None,\n    FadeIn,\n    WaitForTrigger,\n    FadeOut,\n  } xa7c_xrayAlphaState = EXRayFadeState::None;\n  float xa80_xrayAlpha = 1.f;\n  float xa84_xrayAlphaStateTime = 0.f;\n  bool xa88_xrayFadeInTrigger = false;\n  float xa8c_xrayFadeOutTime = 3.f;\n  float xa90_xrayFadeInTime = 1.f;\n  float xa94_xrayFadeTriggerTime = 1.f;\n  float xa98_maxEnergy = 0.f;\n  std::unique_ptr<CCollisionActorManager> xa9c_collisionActorMgr2;\n  std::vector<std::pair<TUniqueId, std::string_view>> xaa0_scriptSounds;\n  float xab0_ = 0.f;\n  std::vector<u32> xab4_;\n  int xac4_ = 0;\n  int xac8_ = 0;\n  int xacc_ = 0;\n  bool xad0_scaleUpTrigger = false;\n  float xad4_cachedSpeed = 1.f;\n  bool xad8_cover = false;\n  TUniqueId xada_lastWaypointId = kInvalidUniqueId;\n  // bool xadc_ = false;\n  // bool xadd_ = false;\n  u8 xade_armorPiecesDestroyed = 0;\n  bool xadf_launcher1FollowPlayer = true;\n  bool xae0_launcher2FollowPlayer = true;\n  CDamageVulnerability xae4_platformVuln = CDamageVulnerability::NormalVulnerabilty();\n  int xb4c_armorPiecesHealed = 0;\n  float xb50_armorPieceHealTime = 0.f;\n  zeus::CColor xb54_platformColor = zeus::skWhite;\n  float xb58_healTime = 2.5f;\n  float xb5c_hpLost = 0.f;\n  float xb60_hpLostInPhase = 0.f;\n  float xb64_stateTime = 17.f;\n  int xb68_ = 0;\n  bool xb6c_exit1Sent = false;\n  bool xb6d_exit2Sent = false;\n  bool xb6e_armorPieceActivated = false;\n  TLockedToken<CTexture> xb70_thermalSpot; // was TToken<CTexture>\n  bool xb78_codeTrigger = false;\n  bool xb79_bossPhaseActive = false;\n  std::vector<u8> xb7c_;\n  float xb8c_avoidStaticCollisionTime = 0.f; // not initialized in ctr\n\npublic:\n  DEFINE_ENTITY\n  COmegaPirate(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n               CModelData&& mData, const CPatternedInfo& pInfo, const CActorParameters& actParms, CElitePirateData data,\n               CAssetId skeletonModelId, CAssetId skeletonSkinRulesId, CAssetId skeletonLayoutInfoId);\n\n  void Think(float dt, CStateManager& mgr) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) override;\n  void PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) override;\n  void Render(CStateManager& mgr) override;\n  zeus::CVector3f GetOrbitPosition(const CStateManager& mgr) const override;\n  void DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) override;\n  void PathFind(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void TargetPatrol(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Run(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Attack(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void JumpBack(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void DoubleSnap(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Shuffle(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Skid(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Suck(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Explode(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Retreat(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Cover(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void WallHang(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void WallDetach(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Enraged(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Growth(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Faint(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Dizzy(CStateManager& mgr, EStateMsg msg, float dt) override;\n\n  bool Landed(CStateManager& mgr, float arg) override;\n  bool HearPlayer(CStateManager& mgr, float arg) override;\n  bool CoverBlown(CStateManager& mgr, float arg) override;\n  bool AggressionCheck(CStateManager& mgr, float arg) override;\n  bool ShouldFire(CStateManager& mgr, float arg) override;\n  bool ShouldMove(CStateManager& mgr, float arg) override;\n  bool ShotAt(CStateManager& mgr, float arg) override;\n  bool CodeTrigger(CStateManager& mgr, float arg) override;\n  bool ShouldCallForBackup(CStateManager& mgr, float arg) override;\n  bool HasWeakPointHead() const override { return false; }\n  bool IsElitePirate() const override { return false; }\n  void SetupHealthInfo(CStateManager& mgr) override;\n  void SetLaunchersActive(CStateManager& mgr, bool val) override;\n  CShockWaveInfo GetShockWaveData() const override {\n    return {GetData().GetShockwaveParticleDescId(), GetData().GetShockwaveDamageInfo(), 24.78255f,\n            GetData().GetShockwaveWeaponDescId(), GetData().GetShockwaveElectrocuteSfxId()};\n  }\n\nprivate:\n  void CreateFlash(CStateManager& mgr, float arg);\n  void SetupCollisionManager(CStateManager& mgr);\n  void AddSphereCollisionList(const SSphereJointInfo* joints, size_t count,\n                              std::vector<CJointCollisionDescription>& outJoints) const;\n  void AddOBBAutoSizeCollisionList(const SOBBJointInfo* joints, size_t count,\n                                   std::vector<CJointCollisionDescription>& outJoints) const;\n  void SetupCollisionActorInfo1(const std::unique_ptr<CCollisionActorManager>& actMgr, CStateManager& mgr);\n  void SetupCollisionActorInfo2(const std::unique_ptr<CCollisionActorManager>& actMgr, CStateManager& mgr);\n  void sub_8028cbec(u32 arg, CStateManager& mgr);\n  u8 sub_8028c230() const;\n  u8 sub_8028bfac() const;\n  void TeleportToFurthestPlatform(CStateManager& mgr);\n  void UpdateNormalAlpha(CStateManager& mgr, float dt);\n  void sub_8028c704(CStateManager& mgr, float dt);\n  void UpdateXRayAlpha(CStateManager& mgr, float dt);\n  void UpdateScale(CStateManager& mgr, float dt);\n  void UpdateSkeletonAlpha(CStateManager& mgr, float dt);\n  void DeathDestroy(CStateManager& mgr);\n  void sub_8028c840(u32 arg, CStateManager& mgr);\n  zeus::CVector3f FindGround(const zeus::CVector3f& pos, CStateManager& mgr) const;\n};\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CParasite.cpp",
    "content": "#include \"Runtime/MP1/World/CParasite.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Character/CModelData.hpp\"\n#include \"Runtime/Collision/CCollisionActor.hpp\"\n#include \"Runtime/Collision/CGameCollision.hpp\"\n#include \"Runtime/Graphics/CSkinnedModel.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/World/CGameArea.hpp\"\n#include \"Runtime/World/CPatternedInfo.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CScriptDoor.hpp\"\n#include \"Runtime/World/CScriptWaypoint.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce::MP1 {\n\nconst float CParasite::flt_805A8FB0 = 2.f * std::sqrt(2.5f / CPhysicsActor::GravityConstant());\nconst float CParasite::skAttackVelocity = 15.f / 2.f * (std::sqrt(2.5f / CPhysicsActor::GravityConstant()));\nshort CParasite::word_805A8FC0 = 0;\nconst float CParasite::flt_805A8FB8 = 2.f * std::sqrt(2.5f / CPhysicsActor::GravityConstant());\nconst float CParasite::skRetreatVelocity = 3.f / 2.f * std::sqrt(2.5f / CPhysicsActor::GravityConstant());\n\nCParasite::CParasite(TUniqueId uid, std::string_view name, EFlavorType flavor, const CEntityInfo& info,\n                     const zeus::CTransform& xf, CModelData&& mData, const CPatternedInfo& pInfo, EBodyType bodyType,\n                     float maxTelegraphReactDist, float advanceWpRadius, float f3, float alignAngVel, float f5,\n                     float stuckTimeThreshold, float collisionCloseMargin, float parasiteSearchRadius,\n                     float parasiteSeparationDist, float parasiteSeparationWeight, float parasiteAlignmentWeight,\n                     float parasiteCohesionWeight, float destinationSeekWeight, float forwardMoveWeight,\n                     float playerSeparationDist, float playerSeparationWeight, float playerObstructionMinDist,\n                     float haltDelay, bool disableMove, EWalkerType wType, const CDamageVulnerability& dVuln,\n                     const CDamageInfo& parInfo, u16 haltSfx, u16 getUpSfx, u16 crouchSfx, CAssetId modelRes,\n                     CAssetId skinRes, float iceZoomerJointHP, const CActorParameters& aParams)\n: CWallWalker(ECharacter::Parasite, uid, name, flavor, info, xf, std::move(mData), pInfo, EMovementType::Flyer,\n              EColliderType::Zero, bodyType, aParams, collisionCloseMargin, alignAngVel, EKnockBackVariant::Small,\n              advanceWpRadius, wType, playerObstructionMinDist, disableMove)\n, x64c_oculusHaltDVuln(dVuln)\n, x6b4_oculusHaltDInfo(parInfo)\n, x6d0_maxTelegraphReactDist(maxTelegraphReactDist)\n, x6d4_(f3)\n, x6dc_(f5)\n, x6e0_stuckTimeThreshold(stuckTimeThreshold)\n, x6e4_parasiteSearchRadius(parasiteSearchRadius)\n, x6e8_parasiteSeparationDist(parasiteSeparationDist)\n, x6ec_parasiteSeparationWeight(parasiteSeparationWeight)\n, x6f0_parasiteAlignmentWeight(parasiteAlignmentWeight)\n, x6f4_parasiteCohesionWeight(parasiteCohesionWeight)\n, x6f8_destinationSeekWeight(destinationSeekWeight)\n, x6fc_forwardMoveWeight(forwardMoveWeight)\n, x700_playerSeparationDist(playerSeparationDist)\n, x704_playerSeparationWeight(playerSeparationWeight)\n, x708_unmorphedRadius(pInfo.GetHeight() * 0.5f)\n, x710_haltDelay(haltDelay)\n, x714_iceZoomerJointHP(iceZoomerJointHP)\n, x73c_haltSfx(CSfxManager::TranslateSFXID(haltSfx))\n, x73e_getUpSfx(CSfxManager::TranslateSFXID(getUpSfx))\n, x740_crouchSfx(CSfxManager::TranslateSFXID(crouchSfx)) {\n  switch (x5d0_walkerType) {\n  case EWalkerType::Geemer:\n    x460_knockBackController.SetEnableFreeze(false);\n    [[fallthrough]];\n  case EWalkerType::Oculus:\n    x460_knockBackController.SetAutoResetImpulse(false);\n    break;\n  case EWalkerType::IceZoomer: {\n    TLockedToken<CModel> model = g_SimplePool->GetObj({FOURCC('CMDL'), modelRes});\n    TLockedToken<CModel> skin = g_SimplePool->GetObj({FOURCC('CSKR'), skinRes});\n    x624_extraModel =\n        CToken(TObjOwnerDerivedFromIObj<CSkinnedModel>::GetNewDerivedObject(std::make_unique<CSkinnedModel>(\n            model, skin, x64_modelData->GetAnimationData()->GetModelData()->GetLayoutInfo())));\n    break;\n  }\n  default:\n    break;\n  }\n  if (x5d0_walkerType == EWalkerType::Oculus) {\n    x460_knockBackController.SetEnableShock(false);\n    x460_knockBackController.SetEnableBurn(false);\n    x460_knockBackController.SetEnableBurnDeath(false);\n    x460_knockBackController.SetEnableExplodeDeath(false);\n    x460_knockBackController.SetX82_24(false);\n  }\n}\n\nvoid CParasite::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CParasite::SetupIceZoomerCollision(CStateManager& mgr) {\n  std::vector<CJointCollisionDescription> descs;\n  descs.reserve(2);\n  descs.push_back(CJointCollisionDescription::SphereCollision(\n      x64_modelData->GetAnimationData()->GetLocatorSegId(\"Ice_LCTR\"sv), 0.4f, \"Ice_LCTR\"sv, 0.001f));\n  RemoveMaterial(EMaterialTypes::Solid, mgr);\n  AddMaterial(EMaterialTypes::ProjectilePassthrough, mgr);\n  x620_collisionActorManager =\n      std::make_unique<CCollisionActorManager>(mgr, GetUniqueId(), GetAreaIdAlways(), descs, GetActive());\n}\n\nvoid CParasite::SetupIceZoomerVulnerability(CStateManager& mgr, const CDamageVulnerability& dVuln,\n                                            const CHealthInfo& hInfo) {\n  for (u32 i = 0; i < x620_collisionActorManager->GetNumCollisionActors(); ++i) {\n    const CJointCollisionDescription& cDesc = x620_collisionActorManager->GetCollisionDescFromIndex(i);\n    if (TCastToPtr<CCollisionActor> act = mgr.ObjectById(cDesc.GetCollisionActorId())) {\n      act->SetDamageVulnerability(dVuln);\n      *act->HealthInfo(mgr) = hInfo;\n    }\n  }\n}\n\nvoid CParasite::AddDoorRepulsors(CStateManager& mgr) {\n  u32 doorCount = 0;\n  for (CEntity* ent : mgr.GetPhysicsActorObjectList())\n    if (TCastToPtr<CScriptDoor> door = ent)\n      if (door->GetAreaIdAlways() == GetAreaIdAlways())\n        ++doorCount;\n  x5d8_doorRepulsors.reserve(doorCount);\n  for (CEntity* ent : mgr.GetPhysicsActorObjectList())\n    if (TCastToPtr<CScriptDoor> door = ent)\n      if (door->GetAreaIdAlways() == GetAreaIdAlways()) {\n        if (auto tb = door->GetTouchBounds()) {\n          float diagMag = (tb->min - tb->max).magnitude() * 0.75f;\n          x5d8_doorRepulsors.emplace_back(tb->center(), diagMag);\n        }\n      }\n}\n\nstatic TUniqueId lastParasite = kInvalidUniqueId;\n\nvoid CParasite::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  CPatterned::AcceptScriptMsg(msg, uid, mgr);\n  switch (msg) {\n  case EScriptObjectMessage::Registered:\n    x450_bodyController->Activate(mgr);\n    mgr.GetActiveParasites().push_back(GetUniqueId());\n    CActor::CreateShadow(false);\n    x604_activeSpeed = x3b4_speed;\n    CPhysicsActor::SetBoundingBox(zeus::CAABox(zeus::CVector3f(-x590_colSphere.GetSphere().radius),\n                                               zeus::CVector3f(x590_colSphere.GetSphere().radius)));\n    lastParasite = GetUniqueId();\n    AddDoorRepulsors(mgr);\n    if (x5d0_walkerType == EWalkerType::IceZoomer) {\n      SetupIceZoomerCollision(mgr);\n      SetupIceZoomerVulnerability(mgr, x64c_oculusHaltDVuln,\n                                  CHealthInfo(x714_iceZoomerJointHP, HealthInfo(mgr)->GetKnockbackResistance()));\n    }\n    break;\n  case EScriptObjectMessage::Deleted:\n    mgr.GetActiveParasites().remove(GetUniqueId());\n    if (x5d0_walkerType == EWalkerType::IceZoomer)\n      DestroyActorManager(mgr);\n    break;\n  case EScriptObjectMessage::Jumped:\n    if (x742_25_jumpVelDirty) {\n      UpdateJumpVelocity();\n      x742_25_jumpVelDirty = false;\n    }\n    break;\n  case EScriptObjectMessage::Activate:\n    x5d6_27_disableMove = false;\n    if (x5d0_walkerType == EWalkerType::Parasite)\n      x450_bodyController->SetLocomotionType(pas::ELocomotionType::Lurk);\n    break;\n  case EScriptObjectMessage::InvulnDamage:\n    if (x5d0_walkerType == EWalkerType::Oculus) {\n      if (TCastToConstPtr<CActor> act = mgr.GetObjectById(uid)) {\n        float distSq = (act->GetTranslation() - GetTranslation()).magSquared();\n        auto tb = GetTouchBounds();\n        float maxComp =\n            std::max(std::max(tb->max.y() - tb->min.y(), tb->max.z() - tb->min.z()), tb->max.x() - tb->min.x());\n        float maxCompSq = maxComp * maxComp + 1.f;\n        if (distSq < maxCompSq * maxCompSq)\n          x743_26_oculusShotAt = true;\n      }\n    }\n    break;\n  case EScriptObjectMessage::SuspendedMove:\n    if (x620_collisionActorManager)\n      x620_collisionActorManager->SetMovable(mgr, false);\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CParasite::PreThink(float dt, CStateManager& mgr) {\n  CWallWalker::PreThink(dt, mgr);\n  x743_26_oculusShotAt = false;\n}\n\nvoid CParasite::UpdateCollisionActors(float dt, CStateManager& mgr) {\n  x620_collisionActorManager->Update(dt, mgr, CCollisionActorManager::EUpdateOptions::ObjectSpace);\n  if (!x743_25_vulnerable) {\n    float totalHP = 0.f;\n    for (u32 i = 0; i < x620_collisionActorManager->GetNumCollisionActors(); ++i) {\n      const CJointCollisionDescription& cDesc = x620_collisionActorManager->GetCollisionDescFromIndex(i);\n      if (TCastToPtr<CCollisionActor> cact = mgr.ObjectById(cDesc.GetCollisionActorId()))\n        totalHP += cact->HealthInfo(mgr)->GetHP();\n    }\n    if (totalHP <= 0.f) {\n      x743_25_vulnerable = true;\n      AddMaterial(EMaterialTypes::Solid, mgr);\n      RemoveMaterial(EMaterialTypes::ProjectilePassthrough, mgr);\n      DestroyActorManager(mgr);\n      x64_modelData->GetAnimationData()->SubstituteModelData(x624_extraModel);\n    }\n  }\n}\n\nvoid CParasite::Think(float dt, CStateManager& mgr) {\n  if (!GetActive())\n    return;\n\n  ++x5d4_thinkCounter;\n  if (x5d0_walkerType == EWalkerType::IceZoomer)\n    UpdateCollisionActors(dt, mgr);\n\n  x5d6_26_playerObstructed = false;\n  CGameArea* area = mgr.GetWorld()->GetArea(GetAreaIdAlways());\n\n  CGameArea::EOcclusionState r6 = CGameArea::EOcclusionState::Occluded;\n  if (area->IsPostConstructed())\n    r6 = area->GetPostConstructed()->x10dc_occlusionState;\n  if (r6 != CGameArea::EOcclusionState::Visible)\n    x5d6_26_playerObstructed = true;\n\n  if (!x5d6_26_playerObstructed) {\n    zeus::CVector3f plVec = mgr.GetPlayer().GetTranslation();\n    float distance = (GetTranslation() - plVec).magnitude();\n\n    if (distance > x5c4_playerObstructionMinDist) {\n      CRayCastResult res = mgr.RayStaticIntersection(plVec, (GetTranslation() - plVec).normalized(), distance,\n                                                     CMaterialFilter::skPassEverything);\n      if (res.IsValid())\n        x5d6_26_playerObstructed = true;\n    }\n  }\n\n  if (x5d6_26_playerObstructed) {\n    xf8_24_movable = false;\n    return;\n  }\n\n  xf8_24_movable = !x5d6_24_alignToFloor;\n\n  if (!x5d6_27_disableMove) {\n    if (x450_bodyController->IsFrozen()) {\n      if ((GetTranslation() - x614_lastStuckPos).magSquared() < 0.3f /* <- Used to be a static variable */ * dt)\n        x60c_stuckTime += dt;\n      else\n        x60c_stuckTime = 0.f;\n\n      x614_lastStuckPos = GetTranslation();\n      if (x608_telegraphRemTime > 0.f)\n        x608_telegraphRemTime -= dt;\n      else\n        x608_telegraphRemTime = 0.f;\n    }\n  }\n\n  if (x400_25_alive) {\n    CPlayer* pl = mgr.Player();\n    float radius;\n    if (pl->GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed && !x742_30_attackOver)\n      radius = x590_colSphere.GetSphere().radius;\n    else\n      radius = x708_unmorphedRadius;\n\n    zeus::CAABox aabox{GetTranslation() - radius, GetTranslation() + radius};\n    auto plBox = pl->GetTouchBounds();\n\n    if (plBox && plBox->intersects(aabox)) {\n      if (!x742_30_attackOver) {\n        x742_30_attackOver = true;\n        x742_27_landed = false;\n      }\n\n      if (x420_curDamageRemTime <= 0.f) {\n        mgr.ApplyDamage(GetUniqueId(), pl->GetUniqueId(), GetUniqueId(), GetContactDamage(),\n                        CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), {});\n        x420_curDamageRemTime = x424_damageWaitTime;\n      }\n    }\n  }\n\n  CWallWalker::Think(dt, mgr);\n\n  if (x5d6_27_disableMove)\n    return;\n\n  if (x450_bodyController->IsFrozen())\n    return;\n\n  x3b4_speed = x604_activeSpeed;\n  if (x5d6_24_alignToFloor)\n    AlignToFloor(mgr, x590_colSphere.GetSphere().radius, GetTranslation() + 2.f * dt * x138_velocity, dt);\n\n  x742_27_landed = false;\n}\n\nvoid CParasite::Render(CStateManager& mgr) { CWallWalker::Render(mgr); }\n\nconst CDamageVulnerability* CParasite::GetDamageVulnerability() const {\n  switch (x5d0_walkerType) {\n  case EWalkerType::Oculus:\n    if (x743_24_halted)\n      return &x64c_oculusHaltDVuln;\n    break;\n  case EWalkerType::IceZoomer:\n    if (!x743_25_vulnerable)\n      return &CDamageVulnerability::ImmuneVulnerabilty();\n    break;\n  default:\n    break;\n  }\n  return CAi::GetDamageVulnerability();\n}\n\nCDamageInfo CParasite::GetContactDamage() const {\n  if (x5d0_walkerType == EWalkerType::Oculus && x743_24_halted)\n    return x6b4_oculusHaltDInfo;\n  return CPatterned::GetContactDamage();\n}\n\nvoid CParasite::Touch(CActor& actor, CStateManager& mgr) { CPatterned::Touch(actor, mgr); }\n\nzeus::CVector3f CParasite::GetAimPosition(const CStateManager&, float) const { return GetTranslation(); }\n\nvoid CParasite::CollidedWith(TUniqueId uid, const CCollisionInfoList& list, CStateManager&) {\n  static constexpr CMaterialList testList(EMaterialTypes::Character, EMaterialTypes::Player);\n  if (x743_27_inJump) {\n    for (const auto& info : list) {\n      if (!x5d6_24_alignToFloor && info.GetMaterialLeft().Intersection(testList) == 0) {\n        OrientToSurfaceNormal(info.GetNormalLeft(), 360.f);\n        CPhysicsActor::Stop();\n        SetVelocityWR(zeus::skZero3f);\n        x742_27_landed = true;\n        x742_28_onGround = true;\n      }\n    }\n  }\n}\n\nvoid CParasite::Death(CStateManager& mgr, const zeus::CVector3f& direction, EScriptObjectState state) {\n  CPhysicsActor::Stop();\n  TelegraphAttack(mgr, EStateMsg::Activate, 0.f);\n  SetMomentumWR({0.f, 0.f, -GetWeight()});\n  CPatterned::Death(mgr, direction, state);\n}\n\nvoid CParasite::Patrol(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x742_26_ = true;\n    x5d6_24_alignToFloor = true;\n    if (!x5d6_27_disableMove && x5d0_walkerType == EWalkerType::Parasite)\n      x450_bodyController->SetLocomotionType(pas::ELocomotionType::Lurk);\n    SetMomentumWR(zeus::skZero3f);\n    x5d6_25_hasAlignSurface = false;\n    xf8_24_movable = false;\n    break;\n  case EStateMsg::Update:\n    if (x5bc_patrolPauseRemTime > 0.f) {\n      x5bc_patrolPauseRemTime -= dt;\n      if (x5bc_patrolPauseRemTime <= 0.f) {\n        if (x5d0_walkerType == EWalkerType::Parasite)\n          x450_bodyController->SetLocomotionType(pas::ELocomotionType::Lurk);\n        x5bc_patrolPauseRemTime = 0.f;\n      }\n    }\n    GotoNextWaypoint(mgr);\n    if (x5bc_patrolPauseRemTime <= 0.f && !x5d6_27_disableMove)\n      DoFlockingBehavior(mgr);\n    break;\n  case EStateMsg::Deactivate:\n    x5d6_24_alignToFloor = false;\n    xf8_24_movable = true;\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CParasite::UpdatePFDestination(CStateManager& mgr) {\n  // Empty\n}\n\nvoid CParasite::DoFlockingBehavior(CStateManager& mgr) {\n  zeus::CVector3f upVec = x34_transform.basis[2];\n  EntityList parasiteList;\n  zeus::CAABox aabb(GetTranslation() - x6e4_parasiteSearchRadius, GetTranslation() + x6e4_parasiteSearchRadius);\n  if ((x5d4_thinkCounter % 6) == 0) {\n    EntityList nearList;\n    static constexpr CMaterialFilter filter = CMaterialFilter::MakeInclude(EMaterialTypes::Character);\n    CParasite* closestParasite = nullptr;\n    float minDistSq = 2.f + x6e8_parasiteSeparationDist * x6e8_parasiteSeparationDist;\n    mgr.BuildNearList(nearList, aabb, filter, nullptr);\n    for (TUniqueId id : nearList) {\n      if (CParasite* parasite = CPatterned::CastTo<CParasite>(mgr.ObjectById(id))) {\n        if (parasite != this && parasite->IsAlive()) {\n          parasiteList.push_back(parasite->GetUniqueId());\n          float distSq = (parasite->GetTranslation() - GetTranslation()).magSquared();\n          if (distSq < minDistSq) {\n            minDistSq = distSq;\n            closestParasite = parasite;\n          }\n        }\n      }\n    }\n    if (closestParasite && x6ec_parasiteSeparationWeight > 0.f && x6e8_parasiteSeparationDist > 0.f)\n      x628_parasiteSeparationMove =\n          x45c_steeringBehaviors.Separation(*this, closestParasite->GetTranslation(), x6e8_parasiteSeparationDist) *\n          x604_activeSpeed;\n    else\n      x628_parasiteSeparationMove = zeus::skZero3f;\n    x634_parasiteCohesionMove = x45c_steeringBehaviors.Cohesion(*this, parasiteList, 0.6f, mgr) * x604_activeSpeed;\n    x640_parasiteAlignmentMove = x45c_steeringBehaviors.Alignment(*this, parasiteList, mgr) * x604_activeSpeed;\n  }\n\n  if ((mgr.GetPlayer().GetTranslation() - GetTranslation()).magSquared() <\n      x700_playerSeparationDist * x700_playerSeparationDist) {\n    x450_bodyController->GetCommandMgr().DeliverCmd(\n        CBCLocomotionCmd(ProjectVectorToPlane(x45c_steeringBehaviors.Separation(*this, mgr.GetPlayer().GetTranslation(),\n                                                                                x700_playerSeparationDist),\n                                              upVec) *\n                             x604_activeSpeed,\n                         zeus::skZero3f, x704_playerSeparationWeight));\n  }\n\n  if (x628_parasiteSeparationMove != zeus::skZero3f) {\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(\n        ProjectVectorToPlane(x628_parasiteSeparationMove, upVec), zeus::skZero3f, x6ec_parasiteSeparationWeight));\n  }\n\n  for (const auto& r : x5d8_doorRepulsors) {\n    if ((r.GetVector() - GetTranslation()).magSquared() < r.GetFloat() * r.GetFloat()) {\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(\n          ProjectVectorToPlane(x45c_steeringBehaviors.Separation(*this, r.GetVector(), r.GetFloat()) * x604_activeSpeed,\n                               upVec),\n          zeus::skZero3f, 1.f));\n    }\n  }\n\n  if (x608_telegraphRemTime <= 0.f) {\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(\n        ProjectVectorToPlane(x634_parasiteCohesionMove, upVec), zeus::skZero3f, x6f4_parasiteCohesionWeight));\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(\n        ProjectVectorToPlane(x640_parasiteAlignmentMove, upVec), zeus::skZero3f, x6f0_parasiteAlignmentWeight));\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(\n        ProjectVectorToPlane(\n            ProjectVectorToPlane(x45c_steeringBehaviors.Seek(*this, x2e0_destPos), upVec) * x604_activeSpeed, upVec),\n        zeus::skZero3f, x6f8_destinationSeekWeight));\n    x450_bodyController->GetCommandMgr().DeliverCmd(\n        CBCLocomotionCmd(x34_transform.basis[1] * x604_activeSpeed, zeus::skZero3f, x6fc_forwardMoveWeight));\n  }\n}\n\nvoid CParasite::PathFind(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x742_26_ = true;\n    x5d6_24_alignToFloor = true;\n    if (x5d0_walkerType == EWalkerType::Parasite)\n      x450_bodyController->SetLocomotionType(pas::ELocomotionType::Lurk);\n    SetMomentumWR(zeus::skZero3f);\n    xf8_24_movable = false;\n    break;\n  case EStateMsg::Update:\n    UpdatePFDestination(mgr);\n    DoFlockingBehavior(mgr);\n    break;\n  case EStateMsg::Deactivate:\n    xf8_24_movable = true;\n    x5d6_24_alignToFloor = false;\n    x742_26_ = false;\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CParasite::TargetPlayer(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x5f8_targetPos = mgr.GetPlayer().GetTranslation() + zeus::CVector3f(0.f, 0.f, 1.5f);\n    break;\n  case EStateMsg::Update:\n    x450_bodyController->FaceDirection3D(\n        ProjectVectorToPlane(x5f8_targetPos - GetTranslation(), x34_transform.basis[2]), x34_transform.basis[1], 2.f);\n    break;\n  default:\n    break;\n  }\n}\n\nTUniqueId CParasite::RecursiveFindClosestWayPoint(CStateManager& mgr, TUniqueId id, float& dist) {\n  TUniqueId ret = id;\n  TCastToPtr<CScriptWaypoint> wp = mgr.ObjectById(id);\n  if (!wp)\n    return ret;\n  wp->SetActive(false);\n  dist = (wp->GetTranslation() - GetTranslation()).magSquared();\n  for (const auto& conn : wp->GetConnectionList()) {\n    if (conn.x0_state == EScriptObjectState::Arrived && conn.x4_msg == EScriptObjectMessage::Next) {\n      TUniqueId nextId = mgr.GetIdForScript(conn.x8_objId);\n      if (nextId != kInvalidUniqueId) {\n        if (TCastToConstPtr<CScriptWaypoint> wp2 = mgr.GetObjectById(nextId)) {\n          if (wp2->GetActive()) {\n            float nextDist;\n            TUniqueId closestWp = RecursiveFindClosestWayPoint(mgr, nextId, nextDist);\n            if (nextDist < dist) {\n              dist = nextDist;\n              ret = closestWp;\n            }\n          }\n        }\n      }\n    }\n  }\n  wp->SetActive(true);\n  return ret;\n}\n\nTUniqueId CParasite::GetClosestWaypointForState(EScriptObjectState state, CStateManager& mgr) {\n  float minDist = FLT_MAX;\n  TUniqueId ret = kInvalidUniqueId;\n  for (const auto& conn : GetConnectionList()) {\n    if (conn.x0_state == state && conn.x4_msg == EScriptObjectMessage::Follow) {\n      TUniqueId id = mgr.GetIdForScript(conn.x8_objId);\n      float dist;\n      TUniqueId closestWp = RecursiveFindClosestWayPoint(mgr, id, dist);\n      if (dist < minDist) {\n        minDist = dist;\n        ret = closestWp;\n      }\n    }\n  }\n  return ret;\n}\n\nvoid CParasite::TargetPatrol(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    SetMomentumWR(zeus::skZero3f);\n    TUniqueId wpId = GetClosestWaypointForState(EScriptObjectState::Patrol, mgr);\n    if (wpId != kInvalidUniqueId)\n      x2dc_destObj = wpId;\n  }\n}\n\nvoid CParasite::Halt(CStateManager& mgr, EStateMsg msg, float) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x330_stateMachineState.SetDelay(x710_haltDelay);\n    x32c_animState = EAnimState::Ready;\n    x743_24_halted = true;\n    x5d6_24_alignToFloor = true;\n    if (x5d0_walkerType == EWalkerType::Geemer)\n      CSfxManager::AddEmitter(x73c_haltSfx, GetTranslation(), zeus::skZero3f, true, false, 0x7f, kInvalidAreaId);\n    break;\n  case EStateMsg::Update:\n    TryCommand(mgr, pas::EAnimationState::LoopReaction, &CPatterned::TryLoopReaction, 1);\n    x400_24_hitByPlayerProjectile = false;\n    break;\n  case EStateMsg::Deactivate:\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBodyStateCmd(EBodyStateCmd::ExitState));\n    x32c_animState = EAnimState::NotReady;\n    x743_24_halted = false;\n    x5d6_24_alignToFloor = false;\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CParasite::Run(CStateManager&, EStateMsg, float) {\n  // Empty\n}\n\nvoid CParasite::Generate(CStateManager&, EStateMsg msg, float) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x5e8_stateProgress = 0;\n    break;\n  case EStateMsg::Update:\n    switch (x5e8_stateProgress) {\n    case 0:\n      if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::Generate)\n        x5e8_stateProgress = 1;\n      else\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCGenerateCmd(pas::EGenerateType::Zero));\n      break;\n    case 1:\n      if (x450_bodyController->GetCurrentStateId() != pas::EAnimationState::Generate)\n        x5e8_stateProgress = 2;\n      break;\n    default:\n      break;\n    }\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CParasite::Deactivate(CStateManager& mgr, EStateMsg msg, float) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x5e8_stateProgress = 0;\n    SendScriptMsgs(EScriptObjectState::DeactivateState, mgr, EScriptObjectMessage::None);\n    mgr.FreeScriptObject(GetUniqueId());\n    break;\n  case EStateMsg::Update:\n    if (x5e8_stateProgress == 0) {\n      if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::Generate)\n        x5e8_stateProgress = 1;\n      else\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCGenerateCmd(pas::EGenerateType::One));\n    }\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CParasite::Attack(CStateManager& mgr, EStateMsg msg, float) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x608_telegraphRemTime = 0.f;\n    if (mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed) {\n      float rz = mgr.GetActiveRandom()->Float();\n      float ry = mgr.GetActiveRandom()->Float();\n      float rx = mgr.GetActiveRandom()->Float();\n      x5f8_targetPos = (zeus::CVector3f(rx, ry, rz) - 0.5f) * 0.5f + mgr.GetPlayer().GetTranslation();\n    } else {\n      float rz = mgr.GetActiveRandom()->Float();\n      float ry = mgr.GetActiveRandom()->Float();\n      float rx = mgr.GetActiveRandom()->Float();\n      x5f8_targetPos =\n          (zeus::CVector3f(rx, ry, rz) + mgr.GetPlayer().GetTranslation() - GetTranslation()).normalized() * 15.f +\n          GetTranslation();\n    }\n    FaceTarget(x5f8_targetPos);\n    x5e8_stateProgress = 0;\n    x742_30_attackOver = false;\n    x742_24_receivedTelegraph = false;\n    x742_28_onGround = false;\n    break;\n  case EStateMsg::Update:\n    switch (x5e8_stateProgress) {\n    case 0:\n      if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::Jump) {\n        x5e8_stateProgress = 1;\n      } else {\n        x742_25_jumpVelDirty = true;\n        FaceTarget(x5f8_targetPos);\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCJumpCmd(x5f8_targetPos, pas::EJumpType::Normal));\n      }\n      break;\n    default:\n      break;\n    }\n    break;\n  case EStateMsg::Deactivate:\n    x742_28_onGround = true;\n    x742_30_attackOver = true;\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CParasite::Crouch(CStateManager&, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Crouch);\n    if (x5d0_walkerType == EWalkerType::Geemer)\n      CSfxManager::AddEmitter(x740_crouchSfx, GetTranslation(), zeus::skZero3f, true, false, 0x7f, kInvalidAreaId);\n  }\n}\n\nvoid CParasite::GetUp(CStateManager&, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n    if (x5d0_walkerType == EWalkerType::Geemer)\n      CSfxManager::AddEmitter(x73e_getUpSfx, GetTranslation(), zeus::skZero3f, true, false, 0x7f, kInvalidAreaId);\n  }\n}\n\nvoid CParasite::TelegraphAttack(CStateManager& mgr, EStateMsg msg, float) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    for (auto it = mgr.GetActiveParasites().begin(); it != mgr.GetActiveParasites().end();) {\n      CParasite* other = CPatterned::CastTo<CParasite>(mgr.ObjectById(*it));\n      if (!other) {\n        it = mgr.GetActiveParasites().erase(it);\n        continue;\n      }\n      if (other != this && other->IsAlive() &&\n          (other->GetTranslation() - GetTranslation()).magSquared() <\n              x6d0_maxTelegraphReactDist * x6d0_maxTelegraphReactDist) {\n        other->x742_24_receivedTelegraph = true;\n        other->x608_telegraphRemTime = mgr.GetActiveRandom()->Float() * 0.5f + 0.5f;\n        other->x5f8_targetPos = GetTranslation();\n      }\n      ++it;\n    }\n    x400_24_hitByPlayerProjectile = false;\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CParasite::Jump(CStateManager& mgr, EStateMsg msg, float) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    AddMaterial(EMaterialTypes::GroundCollider, mgr);\n    SetMomentumWR({0.f, 0.f, -GetWeight()});\n    x742_28_onGround = false;\n    x5d6_24_alignToFloor = false;\n    x742_27_landed = false;\n    x743_27_inJump = true;\n    break;\n  case EStateMsg::Update:\n    SetMomentumWR({0.f, 0.f, -GetWeight()});\n    break;\n  case EStateMsg::Deactivate:\n    RemoveMaterial(EMaterialTypes::GroundCollider, mgr);\n    SetMomentumWR(zeus::skZero3f);\n    x742_28_onGround = true;\n    x742_27_landed = false;\n    x743_27_inJump = false;\n    break;\n  }\n}\n\nvoid CParasite::FaceTarget(const zeus::CVector3f& target) {\n  zeus::CQuaternion q =\n      zeus::CQuaternion::lookAt(zeus::CTransform().basis[1], target - GetTranslation(), zeus::degToRad(360.f));\n  SetTransform(q.toTransform(GetTranslation()));\n}\n\nvoid CParasite::Retreat(CStateManager& mgr, EStateMsg msg, float) {\n  switch (msg) {\n  case EStateMsg::Activate: {\n    zeus::CVector3f dir = mgr.GetPlayer().GetTranslation() - GetTranslation();\n    dir.z() = 0.f;\n    if (dir.canBeNormalized())\n      dir.normalize();\n    else\n      dir = mgr.GetPlayer().GetTransform().basis[1];\n    x5f8_targetPos = GetTranslation() - dir * 3.f;\n    FaceTarget(x5f8_targetPos);\n    x5e8_stateProgress = 0;\n    x742_27_landed = false;\n    x742_28_onGround = false;\n    x742_25_jumpVelDirty = true;\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBCJumpCmd(x5f8_targetPos, pas::EJumpType::One));\n    break;\n  }\n  case EStateMsg::Update:\n    x3b4_speed = 1.f;\n    break;\n  case EStateMsg::Deactivate:\n    x742_28_onGround = true;\n    break;\n  }\n}\n\nbool CParasite::AnimOver(CStateManager&, float) { return x5e8_stateProgress == 2; }\n\nbool CParasite::ShouldAttack(CStateManager& mgr, float arg) {\n  bool shouldAttack = false;\n  if (x742_24_receivedTelegraph && x608_telegraphRemTime > 0.1f)\n    shouldAttack = true;\n  if (!TooClose(mgr, arg) && InMaxRange(mgr, arg))\n    return shouldAttack || InDetectionRange(mgr, 0.f);\n  return false;\n}\n\nbool CParasite::CloseToWall(const CStateManager& mgr) const {\n  static constexpr CMaterialFilter filter = CMaterialFilter::MakeInclude(EMaterialTypes::Solid);\n  zeus::CAABox aabb = CPhysicsActor::GetBoundingBox();\n  const float margin = x590_colSphere.GetSphere().radius + x5b0_collisionCloseMargin;\n  aabb.min -= zeus::CVector3f(margin);\n  aabb.max += zeus::CVector3f(margin);\n  const CCollidableAABox cAABB(aabb, x68_material);\n  return CGameCollision::DetectStaticCollisionBoolean(mgr, cAABB, {}, filter);\n}\n\nbool CParasite::HitSomething(CStateManager& mgr, float) {\n  if (x5d4_thinkCounter & 0x1)\n    return true;\n  return x5b8_tumbleAngle < 270.f && CloseToWall(mgr);\n}\n\nbool CParasite::Stuck(CStateManager&, float) { return x60c_stuckTime > x6e0_stuckTimeThreshold; }\n\nbool CParasite::Landed(CStateManager&, float) { return x742_27_landed; }\n\nbool CParasite::AttackOver(CStateManager&, float) { return x742_30_attackOver; }\n\nbool CParasite::ShotAt(CStateManager&, float) {\n  if (x5d0_walkerType != EWalkerType::Oculus)\n    return x400_24_hitByPlayerProjectile;\n  return x743_26_oculusShotAt;\n}\n\nvoid CParasite::MassiveDeath(CStateManager& mgr) { CPatterned::MassiveDeath(mgr); }\n\nvoid CParasite::MassiveFrozenDeath(CStateManager& mgr) { CPatterned::MassiveFrozenDeath(mgr); }\n\nvoid CParasite::ThinkAboutMove(float dt) {\n  if (!x68_material.HasMaterial(EMaterialTypes::GroundCollider))\n    CPatterned::ThinkAboutMove(dt);\n}\n\nbool CParasite::IsOnGround() const { return x742_28_onGround; }\n\nvoid CParasite::UpdateWalkerAnimation(CStateManager& mgr, float dt) { CActor::UpdateAnimation(dt, mgr, true); }\n\nvoid CParasite::DestroyActorManager(CStateManager& mgr) { x620_collisionActorManager->Destroy(mgr); }\n\nvoid CParasite::UpdateJumpVelocity() {\n  SetMomentumWR({0.f, 0.f, -GetWeight()});\n  zeus::CVector3f vec;\n\n  if (!x742_30_attackOver) {\n    vec = skAttackVelocity * GetTransform().frontVector();\n    vec.z() = 0.5f * skRetreatVelocity;\n  } else {\n    vec = skRetreatVelocity * GetTransform().frontVector();\n    vec.z() = 0.5f * skAttackVelocity;\n  }\n\n  float f30 = x150_momentum.z() / xe8_mass;\n  float f31 = x5f8_targetPos.z() - GetTranslation().z();\n  zeus::CVector3f vec2 = x5f8_targetPos - GetTranslation();\n  vec2.z() = 0.f;\n  float f29 = vec2.magnitude();\n\n  if (f29 > FLT_EPSILON) {\n    vec2 *= zeus::CVector3f{1.f / f29};\n    float f28 = vec2.dot(vec);\n    if (f28 > FLT_EPSILON) {\n      float f27 = 0.f;\n      bool isNeg = f31 < 0.f;\n      float xPos, xNeg;\n      if (CSteeringBehaviors::SolveQuadratic(f30, vec.z(), -f31, xPos, xNeg))\n        f27 = isNeg ? xPos : xNeg;\n\n      if (!isNeg)\n        f27 = f27 * f29 / f28;\n\n      if (f27 < 10.f) {\n        vec = f29 / f27 * vec2;\n        vec.z() = (0.5f * f30 * f27 + f31 / f27);\n      }\n    }\n  }\n  SetVelocityWR(vec);\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CParasite.hpp",
    "content": "#pragma once\n\n#include <vector>\n\n#include \"Runtime/Collision/CCollisionActorManager.hpp\"\n#include \"Runtime/World/CWallWalker.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CModelData;\n}\n\nnamespace metaforce::MP1 {\nclass CParasite : public CWallWalker {\n  class CRepulsor {\n    zeus::CVector3f x0_v;\n    float xc_f;\n\n  public:\n    CRepulsor(const zeus::CVector3f& v, float f) : x0_v(v), xc_f(f) {}\n    const zeus::CVector3f& GetVector() const { return x0_v; }\n    float GetFloat() const { return xc_f; }\n  };\n  static const float flt_805A8FB0;\n  static const float skAttackVelocity;\n  static short word_805A8FC0;\n  static const float flt_805A8FB8;\n  static const float skRetreatVelocity;\n  std::vector<CRepulsor> x5d8_doorRepulsors;\n  s32 x5e8_stateProgress = -1;\n  zeus::CVector3f x5ec_;\n  zeus::CVector3f x5f8_targetPos;\n  float x604_activeSpeed = 1.f;\n  float x608_telegraphRemTime = 0.f;\n  float x60c_stuckTime = 0.f;\n  zeus::CVector3f x614_lastStuckPos;\n  std::unique_ptr<CCollisionActorManager> x620_collisionActorManager;\n  TLockedToken<CSkinnedModel> x624_extraModel;\n  zeus::CVector3f x628_parasiteSeparationMove;\n  zeus::CVector3f x634_parasiteCohesionMove;\n  zeus::CVector3f x640_parasiteAlignmentMove;\n  CDamageVulnerability x64c_oculusHaltDVuln;\n  CDamageInfo x6b4_oculusHaltDInfo;\n  float x6d0_maxTelegraphReactDist;\n  float x6d4_;\n  float x6dc_;\n  float x6e0_stuckTimeThreshold;\n  float x6e4_parasiteSearchRadius;\n  float x6e8_parasiteSeparationDist;\n  float x6ec_parasiteSeparationWeight;\n  float x6f0_parasiteAlignmentWeight;\n  float x6f4_parasiteCohesionWeight;\n  float x6f8_destinationSeekWeight;\n  float x6fc_forwardMoveWeight;\n  float x700_playerSeparationDist;\n  float x704_playerSeparationWeight;\n  float x708_unmorphedRadius;\n  float x710_haltDelay;\n  float x714_iceZoomerJointHP;\n  float x718_ = 0.f;\n  float x71c_ = 0.f;\n  float x720_ = 0.f;\n  float x724_ = 0.f;\n  float x728_ = 0.f;\n  float x72c_ = 0.f;\n  float x730_ = 0.f;\n  float x734_ = 0.f;\n  float x738_ = 0.f;\n  u16 x73c_haltSfx;\n  u16 x73e_getUpSfx;\n  u16 x740_crouchSfx;\n  bool x742_24_receivedTelegraph : 1 = false;\n  bool x742_25_jumpVelDirty : 1 = false;\n  bool x742_26_ : 1 = false;\n  bool x742_27_landed : 1 = false;\n  bool x742_28_onGround : 1 = true;\n  bool x742_29_ : 1 = false;\n  bool x742_30_attackOver : 1 = true;\n  bool x742_31_ : 1 = false;\n  bool x743_24_halted : 1 = false;\n  bool x743_25_vulnerable : 1 = false;\n  bool x743_26_oculusShotAt : 1 = false;\n  bool x743_27_inJump : 1 = false;\n\n  bool CloseToWall(const CStateManager& mgr) const;\n  void FaceTarget(const zeus::CVector3f& target);\n  TUniqueId RecursiveFindClosestWayPoint(CStateManager& mgr, TUniqueId id, float& dist);\n  TUniqueId GetClosestWaypointForState(EScriptObjectState state, CStateManager& mgr);\n  void UpdatePFDestination(CStateManager& mgr);\n  void DoFlockingBehavior(CStateManager& mgr);\n  void SetupIceZoomerCollision(CStateManager& mgr);\n  void SetupIceZoomerVulnerability(CStateManager& mgr, const CDamageVulnerability& dVuln, const CHealthInfo& hInfo);\n  void AddDoorRepulsors(CStateManager& mgr);\n  void UpdateCollisionActors(float dt, CStateManager& mgr);\n  void DestroyActorManager(CStateManager& mgr);\n  void UpdateJumpVelocity();\n\npublic:\n  DEFINE_PATTERNED(Parasite);\n  CParasite(TUniqueId uid, std::string_view name, EFlavorType flavor, const CEntityInfo& info,\n            const zeus::CTransform& xf, CModelData&& mData, const CPatternedInfo& pInfo, EBodyType bodyType,\n            float maxTelegraphReactDist, float advanceWpRadius, float f3, float alignAngVel, float f5,\n            float stuckTimeThreshold, float collisionCloseMargin, float parasiteSearchRadius,\n            float parasiteSeparationDist, float parasiteSeparationWeight, float parasiteAlignmentWeight,\n            float parasiteCohesionWeight, float destinationSeekWeight, float forwardMoveWeight,\n            float playerSeparationDist, float playerSeparationWeight, float playerObstructionMinDist, float haltDelay,\n            bool disableMove, EWalkerType wType, const CDamageVulnerability& dVuln, const CDamageInfo& parInfo,\n            u16 haltSfx, u16 getUpSfx, u16 crouchSfx, CAssetId modelRes, CAssetId skinRes, float iceZoomerJointHP,\n            const CActorParameters& aParams);\n\n  void Accept(IVisitor&) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void PreThink(float, CStateManager&) override;\n  void Think(float dt, CStateManager& mgr) override;\n  void Render(CStateManager&) override;\n  const CDamageVulnerability* GetDamageVulnerability() const override;\n  CDamageInfo GetContactDamage() const override;\n  void Touch(CActor& actor, CStateManager&) override;\n  zeus::CVector3f GetAimPosition(const CStateManager&, float) const override;\n  void CollidedWith(TUniqueId uid, const CCollisionInfoList&, CStateManager&) override;\n  void Death(CStateManager& mgr, const zeus::CVector3f& direction, EScriptObjectState state) override;\n  void Patrol(CStateManager&, EStateMsg msg, float dt) override;\n  void PathFind(CStateManager&, EStateMsg msg, float dt) override;\n  void TargetPlayer(CStateManager&, EStateMsg msg, float dt) override;\n  void TargetPatrol(CStateManager&, EStateMsg msg, float dt) override;\n  void Halt(CStateManager&, EStateMsg, float) override;\n  void Run(CStateManager&, EStateMsg, float) override;\n  void Generate(CStateManager&, EStateMsg, float) override;\n  void Deactivate(CStateManager&, EStateMsg, float) override;\n  void Attack(CStateManager&, EStateMsg, float) override;\n  void Crouch(CStateManager&, EStateMsg, float) override;\n  void GetUp(CStateManager&, EStateMsg, float) override;\n  void TelegraphAttack(CStateManager&, EStateMsg, float) override;\n  void Jump(CStateManager&, EStateMsg, float) override;\n  void Retreat(CStateManager&, EStateMsg, float) override;\n  bool AnimOver(CStateManager&, float) override;\n  bool ShouldAttack(CStateManager&, float) override;\n  bool HitSomething(CStateManager&, float) override;\n  bool Stuck(CStateManager&, float) override;\n  bool Landed(CStateManager&, float) override;\n  bool AttackOver(CStateManager&, float) override;\n  bool ShotAt(CStateManager&, float) override;\n  void MassiveDeath(CStateManager&) override;\n  void MassiveFrozenDeath(CStateManager&) override;\n  void ThinkAboutMove(float) override;\n  bool IsOnGround() const override;\n  virtual void UpdateWalkerAnimation(CStateManager&, float);\n};\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CPhazonHealingNodule.cpp",
    "content": "#include \"Runtime/MP1/World/CPhazonHealingNodule.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/World/CPatternedInfo.hpp\"\n\nnamespace metaforce::MP1 {\nCPhazonHealingNodule::CPhazonHealingNodule(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                                           const zeus::CTransform& xf, CModelData&& mData,\n                                           const CActorParameters& actParams, const CPatternedInfo& pInfo,\n                                           CAssetId particleDescId, std::string actorLctr)\n: CPatterned(ECharacter::PhazonHealingNodule, uid, name, EFlavorType::Zero, info, xf, std::move(mData), pInfo,\n             EMovementType::Flyer, EColliderType::One, EBodyType::Restricted, actParams, EKnockBackVariant::Medium)\n, x570_electricDesc(g_SimplePool->GetObj(SObjectTag{SBIG('ELSC'), particleDescId}))\n, x580_initialHealthInfo(pInfo.GetHealthInfo())\n, x58c_actorLctr(std::move(actorLctr)) {\n  const CMaterialFilter& filter = GetMaterialFilter();\n  CMaterialList include = filter.GetIncludeList();\n  CMaterialList exclude = filter.GetExcludeList();\n  exclude.Add(EMaterialTypes::Character);\n  SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(include, exclude));\n}\n\nvoid CPhazonHealingNodule::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  switch (msg) {\n  case EScriptObjectMessage::Decrement:\n    x568_active = 0;\n    x57c_particleElectric.reset();\n    x56c_emitting = false;\n    break;\n  case EScriptObjectMessage::Increment:\n    x568_active = 1;\n    break;\n  case EScriptObjectMessage::Reset:\n    *HealthInfo(mgr) = x580_initialHealthInfo;\n    break;\n  case EScriptObjectMessage::Registered:\n    if (!GetBodyController()->GetActive()) {\n      GetBodyController()->Activate(mgr);\n    }\n    // TODO remove const_cast\n    *const_cast<CDamageVulnerability*>(GetDamageVulnerability()) = CDamageVulnerability::ImmuneVulnerabilty();\n    GetKnockBackController().SetAutoResetImpulse(false);\n    GetBodyController()->SetLocomotionType(pas::ELocomotionType::Relaxed);\n    RemoveMaterial(EMaterialTypes::Target, EMaterialTypes::Orbit, mgr);\n    AddMaterial(EMaterialTypes::Immovable, mgr);\n    break;\n  case EScriptObjectMessage::InitializedInArea:\n    CPatterned::AcceptScriptMsg(msg, uid, mgr);\n    for (const auto& conn : GetConnectionList()) {\n      auto connId = mgr.GetIdForScript(conn.x8_objId);\n      if (conn.x0_state == EScriptObjectState::Patrol && connId != kInvalidUniqueId &&\n          conn.x4_msg == EScriptObjectMessage::Activate) {\n        x56e_connId = connId;\n      }\n    }\n    break;\n  default:\n    CPatterned::AcceptScriptMsg(msg, uid, mgr);\n    break;\n  }\n}\n\nvoid CPhazonHealingNodule::Death(CStateManager& mgr, const zeus::CVector3f& direction, EScriptObjectState state) {\n  SendScriptMsgs(EScriptObjectState::Dead, mgr, EScriptObjectMessage::None);\n  SendScriptMsgs(EScriptObjectState::DeathRattle, mgr, EScriptObjectMessage::None);\n}\n\nvoid CPhazonHealingNodule::DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type,\n                                           float dt) {\n  if (type == EUserEventType::EndAction) {\n    x56c_emitting = false;\n    x57c_particleElectric.reset();\n  } else if (type == EUserEventType::BeginAction) {\n    x56c_emitting = true;\n    x57c_particleElectric = std::make_unique<CParticleElectric>(x570_electricDesc);\n    x57c_particleElectric->SetParticleEmission(true);\n  } else {\n    CPatterned::DoUserAnimEvent(mgr, node, type, dt);\n  }\n}\n\nvoid CPhazonHealingNodule::Faint(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Update) {\n    if (x588_state == 0) {\n      if (GetBodyController()->GetCurrentStateId() == pas::EAnimationState::Step) {\n        x588_state = 2;\n        GetBodyController()->SetLocomotionType(pas::ELocomotionType::Relaxed);\n      } else {\n        GetBodyController()->GetCommandMgr().DeliverCmd(\n            CBCStepCmd(pas::EStepDirection::Backward, pas::EStepType::Normal));\n      }\n    } else if (x588_state == 2 && GetBodyController()->GetCurrentStateId() != pas::EAnimationState::Step) {\n      x588_state = 3;\n    }\n  } else if (msg == EStateMsg::Activate) {\n    x588_state = 0;\n  }\n}\n\nvoid CPhazonHealingNodule::Growth(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Update) {\n    if (x588_state == 0) {\n      if (GetBodyController()->GetCurrentStateId() == pas::EAnimationState::Step) {\n        x588_state = 2;\n      } else {\n        GetBodyController()->GetCommandMgr().DeliverCmd(\n            CBCStepCmd(pas::EStepDirection::Forward, pas::EStepType::Normal));\n      }\n    } else if (x588_state == 2 && GetBodyController()->GetCurrentStateId() != pas::EAnimationState::Step) {\n      x588_state = 3;\n    }\n  } else if (msg == EStateMsg::Activate) {\n    x588_state = 0;\n    GetBodyController()->SetLocomotionType(pas::ELocomotionType::Lurk);\n  }\n}\n\nvoid CPhazonHealingNodule::KnockBack(const zeus::CVector3f& vec, CStateManager& mgr, const CDamageInfo& info,\n                                     EKnockBackType type, bool inDeferred, float magnitude) {}\n\nvoid CPhazonHealingNodule::Lurk(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    GetBodyController()->SetLocomotionType(pas::ELocomotionType::Lurk);\n  }\n}\n\nvoid CPhazonHealingNodule::Patrol(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    GetBodyController()->SetLocomotionType(pas::ELocomotionType::Relaxed);\n    RemoveMaterial(EMaterialTypes::Target, EMaterialTypes::Orbit, mgr);\n  }\n}\n\nvoid CPhazonHealingNodule::Render(CStateManager& mgr) {\n  if (x57c_particleElectric) {\n    x57c_particleElectric->Render();\n  }\n  CPatterned::Render(mgr);\n}\n\nvoid CPhazonHealingNodule::Think(float dt, CStateManager& mgr) {\n  if (!GetActive()) {\n    return;\n  }\n\n  CPatterned::Think(dt, mgr);\n  if (HealthInfo(mgr)->GetHP() <= 0.f) {\n    x57c_particleElectric.reset();\n    x56c_emitting = false;\n    x330_stateMachineState.SetState(mgr, *this, GetStateMachine(), \"Patrol\"sv);\n    x568_active = 0;\n  }\n  if (x57c_particleElectric) {\n    UpdateParticleElectric(mgr);\n    x57c_particleElectric->Update(dt);\n  }\n}\n\nvoid CPhazonHealingNodule::UpdateParticleElectric(CStateManager& mgr) {\n  if (!x57c_particleElectric) {\n    return;\n  }\n  if (const auto* entity = static_cast<const CPatterned*>(mgr.GetObjectById(x56e_connId))) {\n    const auto electricityLctrXf = GetLctrTransform(\"Electricity_LCTR\"sv);\n    const auto actorLctrXf = entity->GetLctrTransform(x58c_actorLctr);\n    x57c_particleElectric->SetOverrideIPos(electricityLctrXf.origin);\n    x57c_particleElectric->SetOverrideFPos(actorLctrXf.origin);\n  }\n}\n\nbool CPhazonHealingNodule::AnimOver(CStateManager&, float arg) { return x588_state == 3; }\n\nbool CPhazonHealingNodule::InRange(CStateManager&, float arg) { return x568_active == 0; }\n\nbool CPhazonHealingNodule::InDetectionRange(CStateManager&, float arg) { return x568_active == 1; }\n\nvoid CPhazonHealingNodule::MassiveDeath(CStateManager& mgr) { Death(mgr, zeus::skZero3f, EScriptObjectState::Dead); }\n\nvoid CPhazonHealingNodule::MassiveFrozenDeath(CStateManager& mgr) {\n  Death(mgr, zeus::skZero3f, EScriptObjectState::Dead);\n}\n\nvoid CPhazonHealingNodule::PhazeOut(CStateManager& mgr) { Death(mgr, zeus::skZero3f, EScriptObjectState::Dead); }\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CPhazonHealingNodule.hpp",
    "content": "#pragma once\n\n#include \"Runtime/World/CPatterned.hpp\"\n#include \"Runtime/Particle/CParticleElectric.hpp\"\n\nnamespace metaforce::MP1 {\nclass CPhazonHealingNodule : public CPatterned {\nprivate:\n  int x568_active = 0;\n  bool x56c_emitting = false;\n  TUniqueId x56e_connId = kInvalidUniqueId;\n  TCachedToken<CElectricDescription> x570_electricDesc;\n  std::unique_ptr<CParticleElectric> x57c_particleElectric; // was rstl::rc_ptr\n  CHealthInfo x580_initialHealthInfo;\n  int x588_state = 0; // not init in ctr; same as CElitePirate::EState?\n  std::string x58c_actorLctr;\n  // u32 x59c_;\n\npublic:\n  DEFINE_PATTERNED(PhazonHealingNodule);\n\n  CPhazonHealingNodule(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                       CModelData&& mData, const CActorParameters& actParams, const CPatternedInfo& pInfo,\n                       CAssetId particleDescId, std::string actorLctr);\n\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) override;\n  void DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) override;\n  void Render(CStateManager& mgr) override;\n  void Think(float dt, CStateManager& mgr) override;\n\n  void Death(CStateManager& mgr, const zeus::CVector3f& direction, EScriptObjectState state) override;\n  void Faint(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Growth(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void KnockBack(const zeus::CVector3f& vec, CStateManager& mgr, const CDamageInfo& info, EKnockBackType type,\n                 bool inDeferred, float magnitude) override;\n  void Lurk(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Patrol(CStateManager& mgr, EStateMsg msg, float dt) override;\n\n  bool InRange(CStateManager&, float arg) override;\n  bool InDetectionRange(CStateManager&, float arg) override;\n  bool AnimOver(CStateManager&, float arg) override;\n\n  void MassiveDeath(CStateManager& mgr) override;\n  void MassiveFrozenDeath(CStateManager& mgr) override;\n  void PhazeOut(CStateManager&) override;\n\nprivate:\n  void UpdateParticleElectric(CStateManager& mgr);\n};\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CPhazonPool.cpp",
    "content": "#include \"Runtime/MP1/World/CPhazonPool.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce::MP1 {\nCPhazonPool::CPhazonPool(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                         const zeus::CVector3f& scale, bool active, CAssetId w1, CAssetId w2, CAssetId w3, CAssetId w4,\n                         u32 p11, const CDamageInfo& dInfo, const zeus::CVector3f& orientedForce,\n                         ETriggerFlags triggerFlags, bool p15, float p16, float p17, float p18, float p19)\n: CScriptTrigger(uid, name, info, xf.origin, zeus::skNullBox, dInfo, orientedForce, triggerFlags, active, false, false)\n, x168_modelData1(std::make_unique<CModelData>(CStaticRes{w1, zeus::skOne3f}))\n, x16c_modelData2(std::make_unique<CModelData>(CStaticRes{w2, zeus::skOne3f}))\n, x190_scale(scale)\n, x19c_(p16)\n, x1a0_(p16)\n, x1bc_(p17)\n, x1c0_(p19)\n, x1c8_(p18)\n, x1d8_(p11)\n, x1e0_24_(p15) {\n  if (w3.IsValid()) {\n    x170_elementGen1 = std::make_unique<CElementGen>(g_SimplePool->GetObj(SObjectTag{SBIG('PART'), w3}));\n    x170_elementGen1->SetParticleEmission(false);\n  }\n  if (w4.IsValid()) {\n    x174_elementGen2 = std::make_unique<CElementGen>(g_SimplePool->GetObj(SObjectTag{SBIG('PART'), w4}));\n    x174_elementGen2->SetGlobalScale(x190_scale);\n    x174_elementGen2->SetParticleEmission(false);\n  }\n}\n\nvoid CPhazonPool::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CPhazonPool::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  switch (msg) {\n  case EScriptObjectMessage::Activate:\n  case EScriptObjectMessage::Open:\n    UpdateParticleGens(mgr);\n    break;\n  case EScriptObjectMessage::Close:\n    if (!x1e0_25_) {\n      x1e0_25_ = true;\n      x1c4_ = 0.f;\n      SetEmitParticles(false);\n    }\n    break;\n  case EScriptObjectMessage::Decrement:\n    if (x1dc_ == 2) {\n      x1cc_ += 1.f;\n    }\n    break;\n  case EScriptObjectMessage::Registered:\n    if (x170_elementGen1) {\n      x170_elementGen1->SetGlobalTranslation(GetTranslation());\n    }\n    if (x168_modelData1) {\n      x178_bounds = x168_modelData1->GetBounds();\n    } else {\n      x178_bounds = zeus::CAABox{0.5f * -x190_scale, 0.5f * x190_scale};\n    }\n    x130_bounds = zeus::CAABox{x178_bounds.min * x190_scale, x178_bounds.max * x190_scale};\n    break;\n  case EScriptObjectMessage::Deleted:\n    RemoveInhabitants(mgr);\n    break;\n  default:\n    break;\n  }\n  CScriptTrigger::AcceptScriptMsg(msg, uid, mgr);\n}\n\nvoid CPhazonPool::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) {\n  if (GetActive()) {\n    if (x170_elementGen1) {\n      g_Renderer->AddParticleGen(*x170_elementGen1);\n    }\n    if (x174_elementGen2) {\n      g_Renderer->AddParticleGen(*x174_elementGen2);\n    }\n  }\n  CActor::AddToRenderer(frustum, mgr);\n  CActor::EnsureRendered(mgr);\n}\n\nstd::optional<zeus::CAABox> CPhazonPool::GetTouchBounds() const { return CScriptTrigger::GetTouchBounds(); }\n\nvoid CPhazonPool::Render(CStateManager& mgr) {\n  CActor::Render(mgr);\n  bool discard = x1a4_ < 0.25f;\n  const CModelFlags flags{5, 0, static_cast<u16>(discard ? 3 : 0), zeus::CColor{1.f, x1a4_}};\n  if (x168_modelData1) {\n    x168_modelData1->Render(mgr, GetTransform() * zeus::CTransform::RotateZ(x1ac_rotZ), nullptr, flags);\n  }\n  if (x16c_modelData2) {\n    const zeus::CTransform rot = zeus::CTransform::RotateZ(x1ac_rotZ) * zeus::CTransform::RotateX(x1ac_rotZ) *\n                                 zeus::CTransform::RotateY(x1a8_rotY);\n    x16c_modelData2->Render(mgr, GetTransform() * rot, nullptr, flags);\n  }\n}\n\nvoid CPhazonPool::Think(float dt, CStateManager& mgr) {\n  if (!GetActive()) {\n    return;\n  }\n\n  CScriptTrigger::Think(dt, mgr);\n  UpdateInhabitants(mgr);\n  const zeus::CVector3f scale = (x1dc_ == 1 ? x1a4_ : 1.f) * x190_scale;\n  SetTransform({zeus::CMatrix3f{scale}, GetTranslation()});\n  x130_bounds = zeus::CAABox{x178_bounds.min * scale, x178_bounds.max * scale};\n  x1a8_rotY += dt * x1b0_;\n  x1ac_rotZ += dt * x1b4_;\n  if (x1a8_rotY > zeus::degToRad(360.f)) {\n    x1a8_rotY = 0.f;\n  }\n  if (x1ac_rotZ > zeus::degToRad(360.f)) {\n    x1ac_rotZ = 0.f;\n  }\n  if (x170_elementGen1) {\n    x170_elementGen1->SetModulationColor(zeus::CColor{x1a4_, x1a4_, x1a4_, x1a4_});\n    x170_elementGen1->SetGlobalScale(zeus::CVector3f{scale.x()});\n    x170_elementGen1->Update(dt);\n  }\n  if (x174_elementGen2) {\n    x174_elementGen2->Update(dt);\n  }\n\n  bool shouldFree = false;\n  if (x1dc_ == 1) {\n    x1d4_ += dt;\n    if (x1d4_ > 2.f) {\n      x1d4_ = 2.f;\n      x1a4_ += dt * x1b8_;\n      if (x1a4_ > 1.f) {\n        x1a4_ = 1.f;\n        x1dc_ = 2;\n        SetEmitParticles(true);\n        if (x174_elementGen2) {\n          x174_elementGen2->SetParticleEmission(true);\n        }\n      }\n    }\n  } else if (x1dc_ == 2) {\n    float dVar5 = 0.f;\n    if (x1e0_24_ || x1e0_25_) {\n      x1c4_ -= dt;\n      if (x1c4_ <= 0.f) {\n        x1c4_ = 0.f;\n        dVar5 = dt;\n      }\n    }\n    x1a0_ -= x1bc_ * (dt * x1cc_) + dVar5;\n    x1a4_ = x1a0_ / x19c_;\n    if (x1a4_ < 0.001f) {\n      if (x1e0_24_) {\n        shouldFree = true;\n      } else {\n        SetCallTouch(false);\n        if (x1e0_25_) {\n          x1dc_ = 0;\n          SetActive(false);\n        } else {\n          x1dc_ = 3;\n          x1d0_ = x1c8_;\n        }\n      }\n      SetEmitParticles(false);\n    }\n    x1cc_ = 0.f;\n  } else if (x1dc_ == 3) {\n    x1d0_ -= dt;\n    if (x1d0_ <= 0.f) {\n      x1d0_ = 0.f;\n      UpdateParticleGens(mgr);\n    }\n  }\n  if (shouldFree) {\n    mgr.FreeScriptObject(GetUniqueId());\n  }\n}\n\nvoid CPhazonPool::Touch(CActor& actor, CStateManager& mgr) {\n  if (!GetActive() || x1dc_ != 2) {\n    return;\n  }\n\n  CScriptTrigger::Touch(actor, mgr);\n  if (actor.GetMaterialList().HasMaterial(EMaterialTypes::PlatformSlave)) {\n    return;\n  }\n\n  for (auto& entry : x150_inhabitants) {\n    if (entry.first == actor.GetUniqueId()) {\n      entry.second = true;\n      return;\n    }\n  }\n\n  if (actor.GetTouchBounds().has_value()) {\n    x150_inhabitants.emplace_back(actor.GetUniqueId(), true);\n    mgr.SendScriptMsg(&actor, GetUniqueId(), EScriptObjectMessage::AddPhazonPoolInhabitant);\n  }\n}\n\nvoid CPhazonPool::UpdateInhabitants(CStateManager& mgr) {\n  auto iter = x150_inhabitants.begin();\n  const auto end = x150_inhabitants.end();\n  while (iter != end) {\n    TCastToPtr<CActor> actor = mgr.ObjectById(iter->first);\n    if (actor) {\n      if (actor->GetTouchBounds().has_value()) {\n        (void)GetTriggerBoundsWR(); // unused?\n      }\n    }\n    if (!actor || !iter->second) {\n      iter = x150_inhabitants.erase(iter);\n      if (actor) {\n        mgr.SendScriptMsg(actor, GetUniqueId(), EScriptObjectMessage::RemovePhazonPoolInhabitant);\n      }\n    } else {\n      mgr.SendScriptMsg(actor, GetUniqueId(), EScriptObjectMessage::UpdatePhazonPoolInhabitant);\n      iter->second = false;\n      iter++;\n    }\n  }\n}\n\nvoid CPhazonPool::UpdateParticleGens(CStateManager& mgr) {\n  x1b0_ = mgr.GetActiveRandom()->Range(-0.5f, 0.5f);\n  x1b4_ = mgr.GetActiveRandom()->Range(-1.f, 1.f);\n  x1b8_ = mgr.GetActiveRandom()->Range(0.25f, 2.f);\n  x1cc_ = 0.f;\n  x1d4_ = 0.f;\n  x1c4_ = x1c0_;\n  x1a0_ = x19c_;\n  x1dc_ = 1;\n  x1e0_25_ = false;\n  if (x170_elementGen1) {\n    x170_elementGen1->SetGlobalTranslation(GetTranslation());\n    x170_elementGen1->SetParticleEmission(false);\n  }\n  if (x174_elementGen2) {\n    x174_elementGen2->SetGlobalTranslation(GetTranslation());\n    x174_elementGen2->SetParticleEmission(true);\n  }\n}\n\nvoid CPhazonPool::RemoveInhabitants(CStateManager& mgr) {\n  auto iter = x150_inhabitants.begin();\n  const auto end = x150_inhabitants.end();\n  while (iter != end) {\n    if (TCastToPtr<CActor> actor = mgr.ObjectById(iter->first)) {\n      iter = x150_inhabitants.erase(iter);\n      mgr.SendScriptMsg(actor, GetUniqueId(), EScriptObjectMessage::RemovePhazonPoolInhabitant);\n    } else {\n      iter++;\n    }\n  }\n}\n\nvoid CPhazonPool::SetEmitParticles(bool val) {\n  if (x170_elementGen1) {\n    x170_elementGen1->SetParticleEmission(val);\n  }\n}\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CPhazonPool.hpp",
    "content": "#pragma once\n\n#include \"Runtime/World/CScriptTrigger.hpp\"\n\n#include \"Runtime/Particle/CElementGen.hpp\"\n\nnamespace metaforce::MP1 {\nclass CPhazonPool : public CScriptTrigger {\nprivate:\n  std::list<std::pair<TUniqueId, bool>> x150_inhabitants;\n  std::unique_ptr<CModelData> x168_modelData1;\n  std::unique_ptr<CModelData> x16c_modelData2;\n  std::unique_ptr<CElementGen> x170_elementGen1;\n  std::unique_ptr<CElementGen> x174_elementGen2;\n  zeus::CAABox x178_bounds = zeus::skNullBox;\n  zeus::CVector3f x190_scale;\n  float x19c_;\n  float x1a0_;\n  float x1a4_ = 0.001f;\n  float x1a8_rotY = 0.f;\n  float x1ac_rotZ = 0.f;\n  float x1b0_ = 0.f;\n  float x1b4_ = 0.f;\n  float x1b8_ = 0.f;\n  float x1bc_;\n  float x1c0_;\n  float x1c4_ = 0.f;\n  float x1c8_;\n  float x1cc_ = 0.f;\n  float x1d0_ = 0.f;\n  float x1d4_ = 0.f;\n  u32 x1d8_;\n  u32 x1dc_ = 0;\n  bool x1e0_24_ : 1;\n  bool x1e0_25_ : 1 = false;\n\npublic:\n  DEFINE_ENTITY\n  CPhazonPool(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n              const zeus::CVector3f& scale, bool active, CAssetId w1, CAssetId w2, CAssetId w3, CAssetId w4, u32 p11,\n              const CDamageInfo& dInfo, const zeus::CVector3f& orientedForce, ETriggerFlags triggerFlags, bool p15,\n              float p16, float p17, float p18, float p19);\n\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) override;\n  void AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) override;\n  [[nodiscard]] std::optional<zeus::CAABox> GetTouchBounds() const override;\n  void Render(CStateManager& mgr) override;\n  void Think(float dt, CStateManager& mgr) override;\n  void Touch(CActor& actor, CStateManager& mgr) override;\n\nprivate:\n  void UpdateInhabitants(CStateManager& mgr);\n  void UpdateParticleGens(CStateManager& mgr);\n  void RemoveInhabitants(CStateManager& mgr);\n  void SetEmitParticles(bool val);\n};\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CPuddleSpore.cpp",
    "content": "#include \"Runtime/MP1/World/CPuddleSpore.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Collision/CGameCollision.hpp\"\n#include \"Runtime/Weapon/CEnergyProjectile.hpp\"\n#include \"Runtime/Weapon/CGameProjectile.hpp\"\n#include \"Runtime/World/CPatternedInfo.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CScriptWater.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce::MP1 {\nconstexpr u32 kEyeCount = 16;\n\nconstexpr std::array kEyeLocators{\n    \"Glow_1_LCTR\"sv,  \"Glow_2_LCTR\"sv,  \"Glow_3_LCTR\"sv,  \"Glow_4_LCTR\"sv,  \"Glow_5_LCTR\"sv,  \"Glow_6_LCTR\"sv,\n    \"Glow_7_LCTR\"sv,  \"Glow_8_LCTR\"sv,  \"Glow_9_LCTR\"sv,  \"Glow_10_LCTR\"sv, \"Glow_11_LCTR\"sv, \"Glow_12_LCTR\"sv,\n    \"Glow_13_LCTR\"sv, \"Glow_14_LCTR\"sv, \"Glow_15_LCTR\"sv, \"Glow_16_LCTR\"sv,\n};\n\nCPuddleSpore::CPuddleSpore(TUniqueId uid, std::string_view name, EFlavorType flavor, const CEntityInfo& info,\n                           const zeus::CTransform& xf, CModelData&& mData, const CPatternedInfo& pInfo,\n                           EColliderType colType, CAssetId glowFx, float f1, float f2, float f3, float f4, float f5,\n                           const CActorParameters& actParms, CAssetId weapon, const CDamageInfo& dInfo)\n: CPatterned(ECharacter::PuddleSpore, uid, name, flavor, info, xf, std::move(mData), pInfo, EMovementType::Flyer,\n             colType, EBodyType::Restricted, actParms, EKnockBackVariant::Medium)\n, x570_(f1)\n, x574_(f2)\n, x578_(f3)\n, x57c_(f4)\n, x580_(f5)\n, x584_bodyOrigin(pInfo.GetBodyOrigin())\n, x590_halfExtent(pInfo.GetHalfExtent())\n, x594_height(pInfo.GetHeight())\n, x5a0_(CalculateBoundingBox(), GetMaterialList())\n, x5d0_(g_SimplePool->GetObj({SBIG('PART'), glowFx}))\n, x5ec_projectileInfo(weapon, dInfo) {\n  x5dc_elemGens.reserve(kEyeCount);\n  for (u32 i = 0; i < kEyeCount; ++i)\n    x5dc_elemGens.emplace_back(std::make_unique<CElementGen>(x5d0_));\n  x5ec_projectileInfo.Token().Lock();\n  x460_knockBackController.SetAutoResetImpulse(false);\n}\n\nzeus::CAABox CPuddleSpore::CalculateBoundingBox() const {\n  auto aaBox = GetBaseBoundingBox();\n\n  return {{(-x590_halfExtent + x584_bodyOrigin.x()) * 0.05f + (aaBox.min.x() * 0.95f),\n           (-x590_halfExtent + x584_bodyOrigin.y()) * 0.05f + (aaBox.min.y() * 0.95f),\n           (x598_ + x584_bodyOrigin.z()) * 0.05f + (aaBox.min.z() * 0.95f)},\n          {(x590_halfExtent + x584_bodyOrigin.x()) * 0.05f + (aaBox.max.x() * 0.95f),\n           (x590_halfExtent + x584_bodyOrigin.y()) * 0.05f + (aaBox.max.y() * 0.95f),\n           (x594_height * x59c_ + x598_ + x584_bodyOrigin.z()) * 0.05f + (aaBox.max.z() * 0.95f)}};\n}\n\nvoid CPuddleSpore::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  if (msg == EScriptObjectMessage::InvulnDamage)\n    return;\n  if (msg == EScriptObjectMessage::Registered)\n    x450_bodyController->Activate(mgr);\n\n  CPatterned::AcceptScriptMsg(msg, uid, mgr);\n}\n\nbool CPuddleSpore::HitShell(const zeus::CVector3f& point) const {\n  if (x5c8_ != 1)\n    return true;\n  float distance = GetTransform().upVector().dot(zeus::CUnitVector3f(point - GetBoundingBox().center()));\n  return (distance <= -0.5f || distance >= 0.5f);\n}\n\nvoid CPuddleSpore::KnockPlayer(CStateManager& mgr, float arg) {\n  auto selfBox = GetBoundingBox();\n  auto playerBox = mgr.GetPlayer().GetBoundingBox();\n  if (selfBox.max.z() < ((playerBox.min.z() + playerBox.max.z()) * .5f) && playerBox.min.x() <= selfBox.max.x() &&\n      playerBox.min.y() <= selfBox.max.y() && selfBox.min.x() <= playerBox.max.x() &&\n      selfBox.min.y() <= playerBox.max.y() && playerBox.min.z() - selfBox.max.z() < 0.2f) {\n    const float scale =\n        mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed ? 1.5f : 1.f;\n    mgr.GetPlayer().ApplyImpulseWR(scale * (arg * mgr.GetPlayer().GetMass()) * x34_transform.rotate({1.f, 0.f, 0.3f}),\n                                   {});\n    mgr.GetPlayer().SetMoveState(CPlayer::EPlayerMovementState::ApplyJump, mgr);\n  }\n}\n\nvoid CPuddleSpore::UpdateBoundingState(const zeus::CAABox& box, CStateManager& mgr, float dt) {\n  SetBoundingBox(box);\n  x5a0_ = CCollidableAABox(box, x68_material);\n  zeus::CAABox plBox;\n  CPlayer& player = mgr.GetPlayer();\n  zeus::CAABox selfBox = GetBoundingBox();\n  if (player.GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed) {\n    const float ballRad = player.GetMorphBall()->GetBallRadius();\n    zeus::CVector3f ballOffset = (player.GetTranslation() + zeus::CVector3f{0.f, 0.f, ballRad});\n    plBox = zeus::CAABox(ballOffset - ballRad, ballOffset + ballRad);\n  } else {\n    plBox = player.GetBoundingBox();\n  }\n\n  if (selfBox.intersects(plBox)) {\n    float bias = (x5c8_ == 2 ? FLT_EPSILON : -FLT_EPSILON) + (selfBox.max.z() - plBox.min.z());\n    if (bias > 0.f && selfBox.max.z() < plBox.max.z()) {\n      bool hadGroundColliderMat = player.GetMaterialList().HasMaterial(EMaterialTypes::GroundCollider);\n      if (hadGroundColliderMat) {\n        player.RemoveMaterial(EMaterialTypes::GroundCollider, mgr);\n      }\n\n      player.RemoveMaterial(EMaterialTypes::Player, mgr);\n      CPhysicsState state = player.GetPhysicsState();\n      player.MoveToOR(bias * zeus::CVector3f{0.f, 0.f, 1.f}, dt);\n      CGameCollision::Move(mgr, player, dt, nullptr);\n      state.SetTranslation(player.GetTranslation());\n      player.SetPhysicsState(state);\n      if (hadGroundColliderMat) {\n        player.AddMaterial(EMaterialTypes::GroundCollider, mgr);\n      }\n      player.AddMaterial(EMaterialTypes::Player, mgr);\n    }\n  }\n}\n\nvoid CPuddleSpore::PreThink(float dt, CStateManager& mgr) {\n  if (x5c8_ == 2)\n    AddMaterial(EMaterialTypes::SolidCharacter, mgr);\n  else\n    RemoveMaterial(EMaterialTypes::SolidCharacter, mgr);\n\n  UpdateBoundingState(CalculateBoundingBox(), mgr, dt);\n  CPatterned::PreThink(dt, mgr);\n}\n\nvoid CPuddleSpore::Think(float dt, CStateManager& mgr) {\n  if (x614_25)\n    x56c_ += dt;\n  if (x614_24)\n    x568_ += dt;\n  HealthInfo(mgr)->SetHP(1000000.0f);\n  float t = (x56c_ / x570_) - 1.f >= -0.f ? 1.f : x56c_ / x570_;\n  zeus::CColor modColor = zeus::CColor::lerp(zeus::skWhite, zeus::CColor(1.f, 1.f, 1.f, 0.f), t);\n  for (size_t i = 0; i < kEyeCount; ++i) {\n    const auto& elemGen = x5dc_elemGens[i];\n    elemGen->SetModulationColor(modColor);\n    elemGen->SetTranslation(GetLctrTransform(kEyeLocators[i]).origin);\n    elemGen->Update(dt);\n  }\n\n  CPatterned::Think(dt, mgr);\n}\n\nvoid CPuddleSpore::Render(CStateManager& mgr) {\n  CPatterned::Render(mgr);\n  if (x56c_ > 0.01f) {\n    for (const auto& elemGen : x5dc_elemGens)\n      elemGen->Render();\n  }\n}\n\nvoid CPuddleSpore::Touch(CActor& act, CStateManager& mgr) {\n  if (!x400_25_alive)\n    return;\n\n  if (TCastToPtr<CGameProjectile> proj = act) {\n    if (proj->GetOwnerId() == mgr.GetPlayer().GetUniqueId())\n      x400_24_hitByPlayerProjectile = !HitShell(proj->GetTranslation());\n  }\n}\n\nvoid CPuddleSpore::FluidFXThink(EFluidState fState, CScriptWater& water, CStateManager& mgr) {\n  if (fState != EFluidState::InFluid)\n    return;\n\n  CFluidPlaneManager* planeManager = mgr.GetFluidPlaneManager();\n  if (planeManager->GetLastRippleDeltaTime(GetUniqueId()) >= 2.9f) {\n    zeus::CVector3f point = GetTranslation();\n    point.z() = float(water.GetTriggerBoundsWR().max.z());\n    water.GetFluidPlane().AddRipple(2.f, GetUniqueId(), point, water, mgr);\n  }\n}\n\nvoid CPuddleSpore::DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) {\n  if (type == EUserEventType::Projectile) {\n    CProjectileInfo* projInfo = GetProjectileInfo();\n    if (projInfo->Token() && projInfo->Token().IsLocked() &&\n        mgr.CanCreateProjectile(GetUniqueId(), EWeaponType::AI, 16)) {\n      mgr.AddObject(new CEnergyProjectile(\n          true, projInfo->Token(), EWeaponType::AI, GetLctrTransform(node.GetLocatorName()), EMaterialTypes::Character,\n          projInfo->GetDamage(), mgr.AllocateUniqueId(), GetAreaIdAlways(), GetUniqueId(), kInvalidUniqueId,\n          EProjectileAttrib::None, false, zeus::skOne3f, {}, 0xFFFF, false));\n    }\n  } else {\n    CPatterned::DoUserAnimEvent(mgr, node, type, dt);\n  }\n}\n\nbool CPuddleSpore::ShouldTurn(CStateManager& mgr, float) {\n  auto selfBox = GetBoundingBox();\n  auto plBox = mgr.GetPlayer().GetBoundingBox();\n  if (((plBox.min.z() + plBox.max.z()) * 0.5f) <= selfBox.max.z() || selfBox.max.x() < plBox.min.x() ||\n      selfBox.max.y() < plBox.min.y() || plBox.max.x() < selfBox.min.x() || plBox.max.y() < selfBox.min.y() ||\n      mgr.GetPlayer().GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Morphed) {\n    return x578_ <= x568_;\n  }\n\n  return true;\n}\n\nvoid CPuddleSpore::InActive(CStateManager&, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n    x56c_ = 0.f;\n    x598_ = -1.f;\n  }\n}\n\nvoid CPuddleSpore::Active(CStateManager&, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Lurk);\n    x568_ = 0.f;\n    x56c_ = 0.f;\n    x598_ = 0.f;\n    x614_24 = true;\n    x614_25 = true;\n  } else if (msg == EStateMsg::Deactivate) {\n    x614_24 = false;\n    x614_25 = false;\n  }\n}\n\nvoid CPuddleSpore::Run(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x5c8_ = 1;\n    x5cc_ = 0;\n    x568_ = 0.f;\n    x614_24 = false;\n    x598_ = 0.f;\n    x59c_ = 1.5f;\n  } else if (msg == EStateMsg::Update) {\n    if (x5cc_ == 0) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::LoopReaction) {\n        x5cc_ = 1;\n        x614_24 = true;\n      } else {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCLoopReactionCmd(pas::EReactionType::Zero));\n      }\n    } else if (x5cc_ == 1 &&\n               x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::LoopReaction) {\n      x5cc_ = 2;\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBodyStateCmd(EBodyStateCmd::ExitState));\n    x5c8_ = 0;\n    x59c_ = 1.f;\n    x614_24 = false;\n  }\n}\n\nvoid CPuddleSpore::TurnAround(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x568_ = 0.f;\n    x56c_ = 0.f;\n    x400_24_hitByPlayerProjectile = false;\n    x598_ = -2.5f;\n    x5c8_ = 2;\n    x5cc_ = 0;\n    x614_24 = false;\n  } else if (msg == EStateMsg::Update) {\n    if (x5cc_ == 0) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::LieOnGround) {\n        x5cc_ = 1;\n        x614_24 = true;\n      } else {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCKnockDownCmd({1.f, 0.f, 0.f}, pas::ESeverity::One));\n      }\n    } else if (x5cc_ == 1 &&\n               x450_bodyController->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::LieOnGround) {\n      x5cc_ = 2;\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x614_24 = false;\n  }\n}\n\nvoid CPuddleSpore::GetUp(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBCGetupCmd(pas::EGetupType::Zero));\n    KnockPlayer(mgr, x580_);\n    x56c_ = 0.f;\n    x598_ = -1.f;\n    x5cc_ = 0;\n  } else if (msg == EStateMsg::Update) {\n    KnockPlayer(mgr, x580_ * 0.25f);\n    if (x5cc_ == 0) {\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBCGetupCmd(pas::EGetupType::Zero));\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::Getup)\n        x5cc_ = 1;\n    } else if (x5cc_ == 1 &&\n               x450_bodyController->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::Getup) {\n      x5cc_ = 1;\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x5c8_ = 0;\n  }\n}\n\nvoid CPuddleSpore::Attack(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n    x598_ = 0.f;\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::MeleeAttack, &CPatterned::TryMeleeAttack, 1);\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n  }\n}\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CPuddleSpore.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <string_view>\n#include <vector>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/Weapon/CProjectileInfo.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n\nnamespace metaforce::MP1 {\nclass CPuddleSpore : public CPatterned {\n  float x568_ = 0.f;\n  float x56c_ = 0.f;\n  float x570_;\n  float x574_;\n  float x578_;\n  float x57c_;\n  float x580_;\n  zeus::CVector3f x584_bodyOrigin;\n  float x590_halfExtent;\n  float x594_height;\n  float x598_ = 0.f;\n  float x59c_ = 1.f;\n  CCollidableAABox x5a0_;\n  u32 x5c8_ = 0;\n  u32 x5cc_ = 0;\n  TCachedToken<CGenDescription> x5d0_;\n  std::vector<std::unique_ptr<CElementGen>> x5dc_elemGens; // originally a vector of CElementGen\n  CProjectileInfo x5ec_projectileInfo;\n  bool x614_24 : 1 = false;\n  bool x614_25 : 1 = false;\n\n  bool HitShell(const zeus::CVector3f&) const;\n  void KnockPlayer(CStateManager&, float);\n  void UpdateBoundingState(const zeus::CAABox&, CStateManager&, float);\n\npublic:\n  DEFINE_PATTERNED(PuddleSpore);\n\n  CPuddleSpore(TUniqueId, std::string_view, EFlavorType, const CEntityInfo&, const zeus::CTransform&, CModelData&&,\n               const CPatternedInfo&, EColliderType, CAssetId, float, float, float, float, float,\n               const CActorParameters&, CAssetId, const CDamageInfo&);\n\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) override;\n  void PreThink(float, CStateManager&) override;\n  void Think(float, CStateManager&) override;\n  void Render(CStateManager&) override;\n  void Touch(CActor&, CStateManager&) override;\n  void FluidFXThink(EFluidState, CScriptWater&, CStateManager&) override;\n  void KnockBack(const zeus::CVector3f& dir, CStateManager& mgr, const CDamageInfo& dInfo, EKnockBackType type,\n                 bool inDeferred, float dt) override {\n    if (x5c8_ == 1)\n      return;\n    CPatterned::KnockBack(dir, mgr, dInfo, type, inDeferred, dt);\n  }\n  EWeaponCollisionResponseTypes GetCollisionResponseType(const zeus::CVector3f& point, const zeus::CVector3f&,\n                                                         const CWeaponMode&, EProjectileAttrib) const override {\n    return HitShell(point) ? EWeaponCollisionResponseTypes::Unknown84 : EWeaponCollisionResponseTypes::Unknown34;\n  }\n  void DoUserAnimEvent(CStateManager&, const CInt32POINode&, EUserEventType, float) override;\n  void CollidedWith(TUniqueId uid, const CCollisionInfoList& colList, CStateManager& mgr) override {\n    if (x5c8_ == 2)\n      return;\n    CPatterned::CollidedWith(uid, colList, mgr);\n  }\n  const CCollisionPrimitive* GetCollisionPrimitive() const override { return &x5a0_; }\n  zeus::CAABox CalculateBoundingBox() const;\n  CProjectileInfo* GetProjectileInfo() override { return &x5ec_projectileInfo; }\n\n  bool InAttackPosition(CStateManager&, float) override { return x568_ >= x570_; }\n  bool ShouldAttack(CStateManager&, float) override { return x568_ >= x574_; }\n  bool ShouldTurn(CStateManager&, float) override;\n  bool AnimOver(CStateManager&, float) override { return x5cc_ == 2; }\n\n  void InActive(CStateManager&, EStateMsg, float) override;\n  void Active(CStateManager&, EStateMsg, float) override;\n  void Run(CStateManager&, EStateMsg, float) override;\n  void TurnAround(CStateManager&, EStateMsg, float) override;\n  void GetUp(CStateManager&, EStateMsg, float) override;\n  void Attack(CStateManager&, EStateMsg, float) override;\n};\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CPuddleToadGamma.cpp",
    "content": "#include \"Runtime/MP1/World/CPuddleToadGamma.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Collision/CGameCollision.hpp\"\n#include \"Runtime/Weapon/CBomb.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce::MP1 {\nnamespace {\nconstexpr zeus::CVector3f skBellyOffset(0.f, 0.1f, -.3f);\n\nconstexpr std::string_view skMouthLocatorName = \"MOUTH_LCTR_SDK\"sv;\nconstexpr std::string_view skBellyLocatorName = \"SAMUS_POS_LCTR_SDK\"sv;\n\nconstexpr CMaterialFilter skSolidFilter =\n    CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {EMaterialTypes::Character, EMaterialTypes::Player,\n                                                                  EMaterialTypes::ProjectilePassthrough});\n} // Anonymous namespace\n\nCPuddleToadGamma::CPuddleToadGamma(TUniqueId uid, std::string_view name, EFlavorType flavor, const CEntityInfo& info,\n                                   const zeus::CTransform& xf, CModelData&& mData, const CPatternedInfo& pInfo,\n                                   const CActorParameters& aParms, float suckForceMultiplier, float suckAngle,\n                                   float playerSuckRange, const zeus::CVector3f& localShootDir, float playerShootSpeed,\n                                   float shouldAttackWaitTime, float spotPlayerWaitTime,\n                                   const CDamageInfo& playerShootDamage, const CDamageInfo& dInfo2, CAssetId dcln)\n: CPatterned(ECharacter::PuddleToad, uid, name, flavor, info, xf, std::move(mData), pInfo, EMovementType::Flyer,\n             EColliderType::Zero, EBodyType::Restricted, aParms, EKnockBackVariant::Large)\n, x570_playerShootDamage(playerShootDamage)\n, x58c_(dInfo2)\n, x5a8_suckForceMultiplier(suckForceMultiplier)\n, x5ac_minSuckAngleProj(std::cos(zeus::degToRad(suckAngle * 0.5f)))\n, x5b0_playerSuckRange(playerSuckRange)\n, x5b4_localShootDir(localShootDir)\n, x5c0_playerShootSpeed(playerShootSpeed)\n, x5c4_shouldAttackWaitTime(shouldAttackWaitTime)\n, x5c8_spotPlayerWaitTime(spotPlayerWaitTime) {\n  x401_26_disableMove = true;\n  x460_knockBackController.SetEnableBurn(false);\n  x460_knockBackController.SetEnableLaggedBurnDeath(false);\n  x460_knockBackController.SetEnableShock(false);\n  x460_knockBackController.SetX81_31(false);\n  SetMovable(false);\n  if (dcln.IsValid() && g_ResFactory->GetResourceTypeById(dcln) != 0) {\n    TLockedToken<CCollidableOBBTreeGroupContainer> container = g_SimplePool->GetObj({FOURCC('DCLN'), dcln});\n    x5e4_collisionTreePrim = std::make_unique<CCollidableOBBTreeGroup>(container.GetObj(), GetMaterialList());\n  }\n}\n\nvoid CPuddleToadGamma::SetSolid(CStateManager& mgr, bool solid) {\n  if (solid) {\n    AddMaterial(EMaterialTypes::Solid, mgr);\n    RemoveMaterial(EMaterialTypes::NonSolidDamageable, mgr);\n  } else {\n    RemoveMaterial(EMaterialTypes::Solid, mgr);\n    AddMaterial(EMaterialTypes::NonSolidDamageable, mgr);\n  }\n}\n\nconst CCollisionPrimitive* CPuddleToadGamma::GetCollisionPrimitive() const {\n  if (!x5e4_collisionTreePrim)\n    return CPhysicsActor::GetCollisionPrimitive();\n  return x5e4_collisionTreePrim.get();\n}\n\nzeus::CTransform CPuddleToadGamma::GetPrimitiveTransform() const {\n  zeus::CTransform xf = GetTransform();\n  xf.origin += GetPrimitiveOffset();\n  return xf;\n}\n\nvoid CPuddleToadGamma::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  CPatterned::AcceptScriptMsg(msg, uid, mgr);\n\n  if (msg == EScriptObjectMessage::Registered) {\n    x450_bodyController->Activate(mgr);\n    const zeus::CTransform bellyXf = GetLctrTransform(skBellyLocatorName);\n    const zeus::CVector3f bellyOffset = GetTransform().rotate(skBellyOffset);\n    x5d8_damageablePoint = x5cc_suckPoint = bellyXf.origin + bellyOffset;\n    RemoveMaterial(EMaterialTypes::Target, EMaterialTypes::Orbit, mgr);\n    AddMaterial(EMaterialTypes::Immovable, mgr);\n    AddMaterial(EMaterialTypes::SolidCharacter);\n  }\n}\n\nvoid CPuddleToadGamma::Think(float dt, CStateManager& mgr) {\n  CPatterned::Think(dt, mgr);\n  if (x5e8_25_waitTimerActive)\n    x56c_waitTimer += dt;\n}\n\nstd::optional<zeus::CAABox> CPuddleToadGamma::GetTouchBounds() const {\n  if (!GetActive())\n    return {};\n\n  return (x5e4_collisionTreePrim ? x5e4_collisionTreePrim->CalculateAABox(GetTransform()) : GetBoundingBox());\n}\n\nvoid CPuddleToadGamma::CenterPlayer(CStateManager& mgr, const zeus::CVector3f& pos, float dt) {\n  zeus::CVector3f dir = (pos - mgr.GetPlayer().GetTranslation()).normalized();\n  mgr.GetPlayer().SetVelocityWR((1.f / (2.f * dt)) * dir);\n}\n\nconst CDamageVulnerability* CPuddleToadGamma::GetDamageVulnerability(const zeus::CVector3f& pos,\n                                                                     const zeus::CVector3f& dir,\n                                                                     const CDamageInfo& dInfo) const {\n  if (x5e8_24_playerInside && (x5d8_damageablePoint - pos).magSquared() < 4.f)\n    return CAi::GetDamageVulnerability();\n\n  return &CDamageVulnerability::ImmuneVulnerabilty();\n}\n\nvoid CPuddleToadGamma::DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) {\n  if (type == EUserEventType::Projectile) {\n    ShootPlayer(mgr, x5c0_playerShootSpeed);\n    return;\n  }\n\n  CPatterned::DoUserAnimEvent(mgr, node, type, dt);\n}\n\nbool CPuddleToadGamma::SpotPlayer(CStateManager&, float arg) { return x56c_waitTimer >= x5c8_spotPlayerWaitTime; }\n\nbool CPuddleToadGamma::ShouldAttack(CStateManager&, float) { return x56c_waitTimer >= x5c4_shouldAttackWaitTime; }\n\nbool CPuddleToadGamma::LostInterest(CStateManager& mgr, float) {\n  zeus::CAABox box = *GetTouchBounds();\n  zeus::CAABox plBox = mgr.GetPlayer().GetBoundingBox();\n  return !box.intersects(plBox);\n}\n\nvoid CPuddleToadGamma::ShootPlayer(CStateManager& mgr, float speed) {\n  zeus::CVector3f shootDir = x34_transform.rotate(x5b4_localShootDir.normalized());\n  mgr.GetPlayer().SetLeaveMorphBallAllowed(true);\n  if (mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed) {\n    x5e8_26_shotPlayer = true;\n    mgr.GetPlayer().Stop();\n    mgr.GetPlayer().SetVelocityWR(zeus::skZero3f);\n    mgr.GetPlayer().ApplyImpulseWR(mgr.GetPlayer().GetMass() * shootDir * speed, {});\n    mgr.GetPlayer().SetMoveState(CPlayer::EPlayerMovementState::ApplyJump, mgr);\n    mgr.ApplyDamage(GetUniqueId(), mgr.GetPlayer().GetUniqueId(), GetUniqueId(), x570_playerShootDamage,\n                    CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), zeus::skZero3f);\n    mgr.GetPlayer().GetMorphBall()->SetAsProjectile();\n    EntityList nearList;\n    mgr.BuildNearList(nearList, GetBoundingBox(), CMaterialFilter::MakeInclude({EMaterialTypes::Bomb}), this);\n    for (TUniqueId id : nearList) {\n      if (TCastToPtr<CBomb> bomb = mgr.ObjectById(id)) {\n        bomb->SetVelocityWR((mgr.GetActiveRandom()->Float() * 5.f + 20.f) * shootDir);\n        bomb->SetConstantAccelerationWR({0.f, 0.f, -CPhysicsActor::GravityConstant()});\n      }\n    }\n  }\n}\n\nbool CPuddleToadGamma::InAttackPosition(CStateManager& mgr, float) {\n  return mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed &&\n         PlayerInVortexArea(mgr);\n}\n\nbool CPuddleToadGamma::PlayerInVortexArea(const CStateManager& mgr) const {\n  const CPlayer& player = mgr.GetPlayer();\n  const zeus::CTransform xf = GetLctrTransform(skMouthLocatorName);\n\n  const zeus::CVector3f playerOffset =\n      player.GetTranslation() + zeus::CVector3f{0.f, 0.f, player.GetMorphBall()->GetBallRadius()};\n  const zeus::CVector3f rotatedDir = GetTransform().rotate(zeus::skForward);\n\n  const zeus::CVector3f suckPointToPlayer = (playerOffset - (xf.origin - (1.f * rotatedDir)));\n  const float suckProj = suckPointToPlayer.normalized().dot(rotatedDir);\n  const float playerDist = suckPointToPlayer.magnitude();\n  const float suckAngleProj = (player.GetTranslation() - (xf.origin - (4.f * rotatedDir))).normalized().dot(rotatedDir);\n  if (playerDist > 2.f) {\n    const CRayCastResult result =\n        mgr.RayStaticIntersection(suckPointToPlayer, 1.f / playerDist * suckPointToPlayer,\n                                  playerDist - player.GetMorphBall()->GetBallRadius(), skSolidFilter);\n    if (result.IsValid())\n      return false;\n  }\n\n  return (playerDist < x5b0_playerSuckRange && suckProj > 0.f && suckAngleProj > x5ac_minSuckAngleProj);\n}\n\nvoid CPuddleToadGamma::InActive(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n    SetSolid(mgr, true);\n    mgr.GetPlayer().SetLeaveMorphBallAllowed(true);\n    x330_stateMachineState.SetDelay(2.f);\n  }\n}\n\nvoid CPuddleToadGamma::Active(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Lurk);\n    const zeus::CTransform xf = GetLctrTransform(skBellyLocatorName);\n    x5cc_suckPoint = xf.origin + GetTransform().rotate(skBellyOffset);\n    x56c_waitTimer = 0.f;\n    x5e8_25_waitTimerActive = true;\n    SetSolid(mgr, true);\n    mgr.GetPlayer().SetLeaveMorphBallAllowed(true);\n  } else if (msg == EStateMsg::Deactivate) {\n    x5e8_25_waitTimerActive = false;\n  }\n}\n\nvoid CPuddleToadGamma::Suck(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    SetSolid(mgr, false);\n    mgr.GetPlayer().SetLeaveMorphBallAllowed(false);\n    mgr.GetPlayer().GetMorphBall()->DisableHalfPipeStatus();\n  } else if (msg == EStateMsg::Update) {\n    if (x568_stateProg == 0) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::LoopReaction) {\n        x568_stateProg = 1;\n        return;\n      }\n\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBCLoopReactionCmd(pas::EReactionType::Zero));\n    } else if (x568_stateProg == 1)\n      SuckPlayer(mgr, arg);\n  } else if (msg == EStateMsg::Deactivate) {\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBodyStateCmd(EBodyStateCmd::ExitState));\n  }\n}\n\nvoid CPuddleToadGamma::SuckPlayer(CStateManager& mgr, float arg) {\n  CPlayer& player = mgr.GetPlayer();\n  if (player.GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Morphed)\n    return;\n\n  zeus::CVector3f posDiff = player.GetTranslation() - x5cc_suckPoint;\n  float posMag = posDiff.magnitude();\n  if (posMag < 3.f) {\n    player.Stop();\n    CenterPlayer(mgr, x5cc_suckPoint, arg);\n    return;\n  }\n\n  float forceMag = x5a8_suckForceMultiplier * (x5b0_playerSuckRange / (posMag * posMag));\n  zeus::CVector3f force = forceMag * (player.GetMass() * -posDiff);\n  player.ApplyForceWR(force, zeus::CAxisAngle());\n}\n\nvoid CPuddleToadGamma::Attack(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    mgr.GetPlayer().Stop();\n    mgr.GetPlayer().SetVelocityWR({});\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBCMeleeAttackCmd(pas::ESeverity::One));\n    x5e8_26_shotPlayer = false;\n    mgr.GetPlayer().GetMorphBall()->SetBombJumpState(CMorphBall::EBombJumpState::BombJumpDisabled);\n  } else if (msg == EStateMsg::Update) {\n    if (!x5e8_26_shotPlayer) {\n      const zeus::CTransform xf = GetLctrTransform(skBellyLocatorName);\n      x5cc_suckPoint = xf.origin + GetTransform().rotate(skBellyOffset);\n      SetPlayerPosition(mgr, x5cc_suckPoint);\n    } else if (LostInterest(mgr, 0.f))\n      SetSolid(mgr, true);\n  } else if (msg == EStateMsg::Deactivate) {\n    SetSolid(mgr, true);\n    mgr.GetPlayer().SetLeaveMorphBallAllowed(true);\n    mgr.GetPlayer().GetMorphBall()->SetBombJumpState(CMorphBall::EBombJumpState::BombJumpAvailable);\n    x5e8_24_playerInside = false;\n  }\n}\n\nvoid CPuddleToadGamma::SetPlayerPosition(CStateManager& mgr, const zeus::CVector3f& targetPos) {\n  float preThinkDt = x500_preThinkDt;\n  CPlayer& player = mgr.GetPlayer();\n  player.Stop();\n  player.SetVelocityWR({});\n  bool hadPlayerMaterial = player.GetMaterialList().HasMaterial(EMaterialTypes::Player);\n\n  if (hadPlayerMaterial)\n    player.RemoveMaterial(EMaterialTypes::GroundCollider, mgr);\n  player.RemoveMaterial(EMaterialTypes::Player, mgr);\n\n  bool hadSolidMaterial = GetMaterialList().HasMaterial(EMaterialTypes::Solid);\n  if (hadSolidMaterial)\n    RemoveMaterial(EMaterialTypes::Solid, mgr);\n\n  CPhysicsState physState = player.GetPhysicsState();\n  player.Stop();\n  player.MoveToWR(targetPos, preThinkDt);\n  CGameCollision::Move(mgr, player, preThinkDt, nullptr);\n  physState.SetTranslation(player.GetTranslation());\n  player.SetPhysicsState(physState);\n  if (hadPlayerMaterial)\n    player.AddMaterial(EMaterialTypes::GroundCollider, mgr);\n  player.AddMaterial(EMaterialTypes::Player, mgr);\n  if (hadSolidMaterial)\n    AddMaterial(EMaterialTypes::Solid, mgr);\n}\n\nbool CPuddleToadGamma::Inside(CStateManager& mgr, float) {\n  if (mgr.GetPlayer().GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Morphed)\n    return false;\n\n  zeus::CVector3f posDiff = mgr.GetPlayer().GetTranslation() - x5cc_suckPoint;\n  return posDiff.dot(GetTransform().frontVector()) <= 0.f && posDiff.magSquared() < 2.f;\n}\n\nvoid CPuddleToadGamma::Crouch(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x568_stateProg = 0;\n    x56c_waitTimer = 0.f;\n    x5e8_25_waitTimerActive = true;\n    x5e8_24_playerInside = true;\n    mgr.GetPlayer().Stop();\n    mgr.GetPlayer().SetVelocityWR({});\n    SendScriptMsgs(EScriptObjectState::Inside, mgr, EScriptObjectMessage::None);\n    if (!mgr.GetPlayer().AttachActorToPlayer(GetUniqueId(), false))\n      x56c_waitTimer = 100.f;\n\n    SetSolid(mgr, false);\n    mgr.GetPlayer().SetLeaveMorphBallAllowed(false);\n    mgr.GetPlayer().GetMorphBall()->DisableHalfPipeStatus();\n    SetSolid(mgr, false);\n  } else if (msg == EStateMsg::Update) {\n    const zeus::CTransform xf = GetLctrTransform(skBellyLocatorName);\n    x5cc_suckPoint = xf.origin + GetTransform().rotate(skBellyOffset);\n    SetPlayerPosition(mgr, x5cc_suckPoint);\n    if (x568_stateProg == 0) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::Locomotion)\n        x568_stateProg = 1;\n      else\n        x450_bodyController->SetLocomotionType(pas::ELocomotionType::Crouch);\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    if (mgr.GetPlayer().GetAttachedActor() == GetUniqueId())\n      mgr.GetPlayer().DetachActorFromPlayer();\n    mgr.GetPlayer().SetLeaveMorphBallAllowed(true);\n    x5e8_25_waitTimerActive = false;\n  }\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CPuddleToadGamma.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <string_view>\n\n#include \"Runtime/Collision/CCollidableOBBTreeGroup.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce::MP1 {\n\nclass CPuddleToadGamma final : public CPatterned {\n  u32 x568_stateProg = 0;\n  float x56c_waitTimer = 0.f;\n  CDamageInfo x570_playerShootDamage;\n  CDamageInfo x58c_;\n  float x5a8_suckForceMultiplier;\n  float x5ac_minSuckAngleProj;\n  float x5b0_playerSuckRange;\n  zeus::CVector3f x5b4_localShootDir;\n  float x5c0_playerShootSpeed;\n  float x5c4_shouldAttackWaitTime;\n  float x5c8_spotPlayerWaitTime;\n  zeus::CVector3f x5cc_suckPoint;\n  zeus::CVector3f x5d8_damageablePoint;\n  std::unique_ptr<CCollidableOBBTreeGroup> x5e4_collisionTreePrim;\n  bool x5e8_24_playerInside : 1 = false;\n  bool x5e8_25_waitTimerActive : 1 = false;\n  bool x5e8_26_shotPlayer : 1 = false;\n\n  void SetSolid(CStateManager&, bool);\n\n  static void CenterPlayer(CStateManager&, const zeus::CVector3f&, float);\n  void ShootPlayer(CStateManager&, float);\n  void SuckPlayer(CStateManager&, float);\n  bool PlayerInVortexArea(const CStateManager&) const;\n  void SetPlayerPosition(CStateManager&, const zeus::CVector3f&);\n\npublic:\n  DEFINE_PATTERNED(PuddleToad);\n\n  CPuddleToadGamma(TUniqueId uid, std::string_view name, EFlavorType flavor, const CEntityInfo& info,\n                   const zeus::CTransform& xf, CModelData&& mData, const CPatternedInfo& pInfo,\n                   const CActorParameters& aParms, float suckForceMultiplier, float suckAngle, float playerSuckRange,\n                   const zeus::CVector3f& localShootDir, float playerShootSpeed, float shouldAttackWaitTime,\n                   float spotPlayerWaitTime, const CDamageInfo& playerShootDamage, const CDamageInfo& dInfo2,\n                   CAssetId dcln);\n\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void Think(float dt, CStateManager& mgr) override;\n  void DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) override;\n  std::optional<zeus::CAABox> GetTouchBounds() const override;\n\n  const CDamageVulnerability* GetDamageVulnerability(const zeus::CVector3f&, const zeus::CVector3f&,\n                                                     const CDamageInfo&) const override;\n\n  const CDamageVulnerability* GetDamageVulnerability() const override {\n    return &CDamageVulnerability::ImmuneVulnerabilty();\n  }\n\n  const CCollisionPrimitive* GetCollisionPrimitive() const override;\n\n  zeus::CTransform GetPrimitiveTransform() const override;\n\n  void InActive(CStateManager&, EStateMsg, float) override;\n  void Active(CStateManager&, EStateMsg, float) override;\n  void Suck(CStateManager&, EStateMsg, float) override;\n  void Attack(CStateManager&, EStateMsg, float) override;\n  void Crouch(CStateManager&, EStateMsg, float) override;\n  bool InAttackPosition(CStateManager&, float) override;\n  bool SpotPlayer(CStateManager&, float) override;\n  bool ShouldAttack(CStateManager&, float) override;\n  bool LostInterest(CStateManager&, float) override;\n  bool Inside(CStateManager&, float) override;\n};\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CPuffer.cpp",
    "content": "#include \"Runtime/MP1/World/CPuffer.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/World/CFire.hpp\"\n#include \"Runtime/World/CKnockBackController.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce::MP1 {\n\nconstexpr std::array GasLocators{\n    \"Gas_01_LCTR\"sv, \"Gas_02_LCTR\"sv, \"Gas_03_LCTR\"sv, \"Gas_04_LCTR\"sv, \"Gas_05_LCTR\"sv,\n    \"Gas_06_LCTR\"sv, \"Gas_07_LCTR\"sv, \"Gas_08_LCTR\"sv, \"Gas_09_LCTR\"sv, \"Gas_10_LCTR\"sv,\n    \"Gas_11_LCTR\"sv, \"Gas_12_LCTR\"sv, \"Gas_13_LCTR\"sv, \"Gas_14_LCTR\"sv,\n};\n\nconstexpr std::array GesJetLocators{\n    \"GasJet01\"sv, \"GasJet02\"sv, \"GasJet03\"sv, \"GasJet04\"sv, \"GasJet05\"sv, \"GasJet06\"sv, \"GasJet07\"sv,\n    \"GasJet08\"sv, \"GasJet09\"sv, \"GasJet10\"sv, \"GasJet11\"sv, \"GasJet12\"sv, \"GasJet13\"sv, \"GasJet14\"sv,\n};\n\nCPuffer::CPuffer(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                 CModelData&& modelData, const CActorParameters& actorParameters, const CPatternedInfo& patternedInfo,\n                 float hoverSpeed, CAssetId cloudEffect, const CDamageInfo& cloudDamage, CAssetId cloudSteam, float f2,\n                 bool b1, bool b2, bool b3, const CDamageInfo& explosionDamage, s16 sfxId)\n: CPatterned(ECharacter::Puffer, uid, name, EFlavorType::Zero, info, xf, std::move(modelData), patternedInfo,\n             EMovementType::Flyer, EColliderType::One, EBodyType::RestrictedFlyer, actorParameters,\n             EKnockBackVariant::Small)\n, x568_face(xf.frontVector())\n, x574_cloudEffect(g_SimplePool->GetObj({SBIG('PART'), cloudEffect}))\n, x57c_cloudDamage(cloudDamage)\n, x598_24_(b1)\n, x598_25_(b3)\n, x598_26_(b2)\n, x59a_(CSfxManager::TranslateSFXID(sfxId))\n, x59c_explosionDamage(explosionDamage)\n, x5b8_(f2)\n, x5bc_cloudSteam(cloudSteam) {\n  CreateShadow(false);\n  x460_knockBackController.SetImpulseDurationIdx(1);\n  x574_cloudEffect.Lock();\n  x450_bodyController->SetRestrictedFlyerMoveSpeed(hoverSpeed);\n}\n\nvoid CPuffer::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CPuffer::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  CPatterned::AcceptScriptMsg(msg, uid, mgr);\n\n  switch (msg) {\n  case EScriptObjectMessage::Registered:\n    x450_bodyController->Activate(mgr);\n    SetMaterialFilter(\n        CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Player}, {EMaterialTypes::NoStaticCollision}));\n    break;\n  case EScriptObjectMessage::Action:\n    if (GetActive())\n      x401_30_pendingDeath = true;\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CPuffer::Think(float dt, CStateManager& mgr) {\n  CPatterned::Think(dt, mgr);\n  sub8025bfa4(mgr);\n  zeus::CVector3f moveVector = x450_bodyController->GetCommandMgr().GetMoveVector();\n\n  if (x5cc_ != x2dc_destObj) {\n    x5cc_ = x2dc_destObj;\n    CSfxManager::AddEmitter(x59a_, GetTranslation(), {}, true, false, 127, -1);\n  }\n\n  x450_bodyController->GetCommandMgr().ClearLocomotionCmds();\n  if (moveVector.canBeNormalized()) {\n    x5c0_move = (x5c0_move * (1.f - (dt / 0.5f)) + (moveVector * (dt / 0.5f))).normalized();\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(x5c0_move, x568_face, 1.f));\n  }\n}\n\nstd::optional<zeus::CAABox> CPuffer::GetTouchBounds() const {\n  auto touchBounds = CPatterned::GetTouchBounds();\n  if (touchBounds) {\n    touchBounds->accumulateBounds(touchBounds->min - 0.5f);\n    touchBounds->accumulateBounds(touchBounds->max + 0.5f);\n  }\n\n  return touchBounds;\n}\n\nvoid CPuffer::Touch(CActor& act, CStateManager& mgr) {\n  CPatterned::Touch(act, mgr);\n\n  if (x400_25_alive && act.GetUniqueId() == mgr.GetPlayer().GetUniqueId())\n    x401_30_pendingDeath = true;\n}\n\nvoid CPuffer::Death(CStateManager& mgr, const zeus::CVector3f& vec, EScriptObjectState state) {\n  CPatterned::Death(mgr, vec, state);\n  mgr.ApplyDamageToWorld(GetUniqueId(), *this, GetTranslation(), x59c_explosionDamage,\n                         CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}));\n  zeus::CTransform xf = GetTransform() * zeus::CTransform::Scale(x57c_cloudDamage.GetRadius());\n  zeus::CAABox aabox(-1.f, 1.f);\n  mgr.AddObject(new CFire(x574_cloudEffect, mgr.AllocateUniqueId(), GetAreaIdAlways(), true, GetUniqueId(),\n                          GetTransform(), x57c_cloudDamage, aabox.getTransformedAABox(xf), {1.f, 1.f, 1.f}, true,\n                          x5bc_cloudSteam, x598_24_, x598_26_, x598_25_, 1.f, x5b8_, 1.f, 1.f));\n}\n\nvoid CPuffer::sub8025bfa4(CStateManager& mgr) {\n  const zeus::CVector3f moveVector = x450_bodyController->GetCommandMgr().GetMoveVector();\n\n  if (x5d4_gasLocators.empty()) {\n    for (const auto& gasLocator : GasLocators) {\n      x5d4_gasLocators.push_back(GetScaledLocatorTransform(gasLocator).basis[1]);\n    }\n  }\n\n  if (moveVector.canBeNormalized()) {\n    const zeus::CVector3f moveNorm = -moveVector.normalized();\n    for (size_t i = 0; i < GesJetLocators.size(); ++i) {\n      const zeus::CVector3f tmp = GetTransform().rotate(x5d4_gasLocators[i]);\n      const bool enable = std::cos(zeus::degToRad(45.f)) < moveNorm.dot(tmp);\n\n      if ((x5d0_enabledParticles & (1 << i)) != enable) {\n        GetModelData()->GetAnimationData()->SetParticleEffectState(GesJetLocators[i], enable, mgr);\n      }\n      if (enable) {\n        x5d0_enabledParticles |= (1 << i);\n      } else {\n        x5d0_enabledParticles &= ~(1 << i);\n      }\n    }\n  } else {\n    for (size_t i = 0; i < GesJetLocators.size(); ++i) {\n      if ((x5d0_enabledParticles & (1 << i)) != 0) {\n        GetModelData()->GetAnimationData()->SetParticleEffectState(GesJetLocators[i], false, mgr);\n      }\n    }\n    x5d0_enabledParticles = 0;\n  }\n}\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CPuffer.hpp",
    "content": "#pragma once\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce::MP1 {\nclass CPuffer : public CPatterned {\n  zeus::CVector3f x568_face;\n  TToken<CGenDescription> x574_cloudEffect;\n  CDamageInfo x57c_cloudDamage;\n  bool x598_24_ : 1;\n  bool x598_25_ : 1;\n  bool x598_26_ : 1;\n  s16 x59a_;\n  CDamageInfo x59c_explosionDamage;\n  float x5b8_;\n  CAssetId x5bc_cloudSteam;\n  zeus::CVector3f x5c0_move;\n  TUniqueId x5cc_ = kInvalidUniqueId;\n  s32 x5d0_enabledParticles = 0;\n  rstl::reserved_vector<zeus::CVector3f, 14> x5d4_gasLocators;\n\n  void sub8025bfa4(CStateManager&);\n\npublic:\n  DEFINE_PATTERNED(Puffer);\n\n  CPuffer(TUniqueId, std::string_view, const CEntityInfo&, const zeus::CTransform&, CModelData&&,\n          const CActorParameters&, const CPatternedInfo&, float, CAssetId, const CDamageInfo&, CAssetId, float, bool,\n          bool, bool, const CDamageInfo&, s16);\n\n  void Accept(IVisitor&) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void Think(float, CStateManager&) override;\n  std::optional<zeus::CAABox> GetTouchBounds() const override;\n  void Touch(CActor&, CStateManager&) override;\n  void Death(CStateManager&, const zeus::CVector3f&, EScriptObjectState) override;\n};\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CRidley.cpp",
    "content": "#include \"Runtime/MP1/World/CRidley.hpp\"\n\n#include \"Runtime/Character/CPASAnimParmData.hpp\"\n#include \"Runtime/Collision/CCollisionActor.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/Particle/CParticleElectric.hpp\"\n#include \"Runtime/Particle/CParticleSwoosh.hpp\"\n#include \"Runtime/Weapon/CGameProjectile.hpp\"\n#include \"Runtime/Weapon/CEnergyProjectile.hpp\"\n#include \"Runtime/Weapon/CPlasmaProjectile.hpp\"\n#include \"Runtime/World/CExplosion.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CScriptPlatform.hpp\"\n#include \"Runtime/World/CScriptWaypoint.hpp\"\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce::MP1 {\nnamespace {\nstruct SSomeRidleyStruct {\n  u32 x0_;\n  u32 x4_;\n  float x8_;\n  float xc_;\n  float x10_;\n  float x14_;\n  float x18_;\n  u32 x1c_;\n  u8 x20_;\n  u8 x21_;\n  u8 x22_;\n  u8 x23_;\n};\n\nstruct SSomeRidleyStruct2 {\n  s32 x0_;\n  float x4_;\n  s32 x8_;\n};\n\nconstexpr std::array<std::array<SSomeRidleyStruct2, 12>, 5> skSomeRidleyStruct{{\n    {{\n        {0, 100.f, -1},\n        {3, 100.f, -1},\n        {2, 100.f, -1},\n        {3, 50.f, 4},\n        {0, 100.f, -1},\n        {3, 100.f, -1},\n        {2, 100.f, -1},\n        {3, 50.f, 4},\n        {5, 100.f, -1},\n        {-1, 100.f, -1},\n        {-1, 100.f, -1},\n        {-1, 100.f, -1},\n    }},\n    {{\n        {5, 100.f, -1},\n        {1, 100.f, -1},\n        {1, 100.f, -1},\n        {1, 100.f, -1},\n        {1, 100.f, -1},\n        {-1, 100.f, -1},\n        {-1, 100.f, -1},\n        {-1, 100.f, -1},\n        {-1, 100.f, -1},\n        {-1, 100.f, -1},\n        {-1, 100.f, -1},\n        {-1, 100.f, -1},\n    }},\n    {{\n        {5, 100.f, -1},\n        {0, 100.f, -1},\n        {0, 50.f, 4},\n        {2, 100.f, -1},\n        {3, 50.f, 4},\n        {2, 100.f, -1},\n        {3, 50.f, 4},\n        {0, 100.f, -1},\n        {2, 50.f, 3},\n        {2, 100.f, -1},\n        {-1, 100.f, -1},\n        {-1, 100.f, -1},\n    }},\n    {{\n        {5, 100.f, -1},\n        {1, 100.f, -1},\n        {1, 100.f, -1},\n        {1, 100.f, -1},\n        {1, 100.f, -1},\n        {-1, 100.f, -1},\n        {-1, 100.f, -1},\n        {-1, 100.f, -1},\n        {-1, 100.f, -1},\n        {-1, 100.f, -1},\n        {-1, 100.f, -1},\n        {-1, 100.f, -1},\n    }},\n    {{\n        {5, 100.f, -1},\n        {0, 100.f, -1},\n        {0, 50.f, 3},\n        {3, 100.f, -1},\n        {0, 50.f, 3},\n        {2, 100.f, -1},\n        {2, 50.f, 4},\n        {0, 100.f, -1},\n        {-1, 100.f, -1},\n        {-1, 100.f, -1},\n        {-1, 100.f, -1},\n        {-1, 100.f, -1},\n    }},\n}};\n\nconstexpr std::array<SSomeRidleyStruct, 5> skSomeStruct{{\n    {4, 6, 50.f, 50.f, 0.f, 33.f, 0.f, 1, 0, 0, 0, 0},\n    {4, 6, 20.f, 20.f, 60.f, 50.f, 0.f, 2, 0, 0, 0, 0},\n    {4, 6, 40.f, 40.f, 20.f, 50.f, 50.f, 2, 1, 0, 0, 0},\n    {3, 5, 10.f, 15.f, 75.f, 100.f, 25.f, 2, 0, 0, 0, 0},\n    {3, 5, 30.f, 30.f, 40.f, 50.f, 50.f, 2, 1, 0, 0, 0},\n}};\n\nconstexpr std::array skWingBones{\n    \"L_wingBone1_1\"sv,    \"L_wingBone1_2\"sv,    \"L_wingBone2_1\"sv,    \"L_wingBone2_2\"sv,    \"L_wingBone3_1\"sv,\n    \"L_wingBone3_2\"sv,    \"L_wingFlesh1_1\"sv,   \"L_wingFlesh1_2\"sv,   \"L_wingFlesh2_1\"sv,   \"L_wingFlesh2_2\"sv,\n    \"L_wingFlesh3_1\"sv,   \"L_wingFlesh3_2\"sv,   \"R_wingBone1_1\"sv,    \"R_wingBone1_2\"sv,    \"R_wingBone2_1\"sv,\n    \"R_wingBone2_2\"sv,    \"R_wingBone3_1\"sv,    \"R_wingBone3_2\"sv,    \"R_wingFlesh1_1\"sv,   \"R_wingFlesh1_2\"sv,\n    \"R_wingFlesh2_1\"sv,   \"R_wingFlesh2_2\"sv,   \"R_wingFlesh3_1\"sv,   \"R_wingFlesh3_2\"sv,   \"L_wingtip_1_LCTR\"sv,\n    \"L_wingtip_2_LCTR\"sv, \"L_wingtip_3_LCTR\"sv, \"R_wingtip_1_LCTR\"sv, \"R_wingtip_2_LCTR\"sv, \"R_wingtip_3_LCTR\"sv,\n};\n\nconstexpr std::array skWingEffects{\n    \"WingSmokeSmall1\"sv, \"WingSmokeSmall2\"sv, \"WingSmokeSmall3\"sv, \"WingSmokeSmall4\"sv, \"WingSmokeSmall5\"sv,\n    \"WingSmokeSmall6\"sv, \"WingSmokeSmall7\"sv, \"WingSmokeSmall8\"sv, \"WingFire1\"sv,       \"WingFire2\"sv,\n    \"WingFire3\"sv,       \"WingFire4\"sv,       \"WingFire5\"sv,       \"WingFire6\"sv,       \"WingFire7\"sv,\n    \"WingFire8\"sv,       \"WingSparks1\"sv,     \"WingSparks2\"sv,     \"WingSparks3\"sv,     \"WingSparks4\"sv,\n    \"WingSparks5\"sv,     \"WingSparks6\"sv,     \"WingSparks7\"sv,     \"WingSparks8\"sv,\n};\n\nconstexpr std::array<SOBBRadiiJointInfo, 4> skTail{{\n    {\"Tail_1\", \"Tail_3\", 0.66f},\n    {\"Tail_3\", \"Tail_5\", 0.66f},\n    {\"Tail_5\", \"Tail_7\", 0.66f},\n    {\"Tail_7\", \"Tail_9\", 0.66f},\n}};\n\nconstexpr std::array<SSphereJointInfo, 10> skSphereJoints{{\n    {\"Skeleton_Root\", 0.6f},\n    {\"Spine_2\", 0.6f},\n    {\"breastPlate_LCTR\", 0.3f},\n    {\"Head_1\", 0.6f},\n    {\"L_wrist\", 0.5f},\n    {\"R_wrist\", 0.5f},\n    {\"L_ankle\", 0.6f},\n    {\"R_ankle\", 0.6f},\n    {\"L_pinky_1\", 0.4f},\n    {\"R_pinky_1\", 0.4f},\n}};\n\nstruct SSomeRidleyStruct3 {\n  float x0_;\n  float x4_;\n  float x8_;\n  float xc_;\n  float x10_;\n  float x14_;\n};\n\nconstexpr std::array<SSomeRidleyStruct3, 6> skFloats{{\n    {0.0, 20.0, 40.0, 0.0, 0.0, 40.0},\n    {0.0, 0.0, 70.0, 0.0, 0.0, 30.0},\n    {0.0, 60.0, 0.0, 0.0, 0.0, 40.0},\n    {0.0, 40.0, 30.0, 0.0, 0.0, 30.0},\n    {0.0, 0.0, 50.0, 0.0, 0.0, 50.0},\n    {0.0, 40.0, 60.0, 0.0, 0.0, 0.0},\n}};\n\nconstexpr CDamageVulnerability skDirectNormal{EVulnerability::DirectNormal, EVulnerability::DirectNormal,\n                                              EVulnerability::DirectNormal, EVulnerability::DirectNormal,\n                                              EVulnerability::DirectNormal, EVulnerability::DirectNormal,\n                                              EVulnerability::DirectNormal, EVulnerability::DirectNormal,\n                                              EVulnerability::DirectNormal, EVulnerability::DirectNormal,\n                                              EVulnerability::DirectNormal, EVulnerability::DirectNormal,\n                                              EVulnerability::DirectNormal, EVulnerability::DirectNormal,\n                                              EVulnerability::DirectNormal, EDeflectType::None};\nconstexpr CDamageVulnerability skIceWeakness{EVulnerability::DirectNormal, EVulnerability::DirectWeak,\n                                             EVulnerability::DirectNormal, EVulnerability::DirectNormal,\n                                             EVulnerability::DirectNormal, EVulnerability::DirectNormal,\n                                             EVulnerability::DirectNormal, EVulnerability::DirectNormal,\n                                             EVulnerability::DirectNormal, EVulnerability::DirectNormal,\n                                             EVulnerability::DirectNormal, EVulnerability::DirectNormal,\n                                             EVulnerability::DirectNormal, EVulnerability::DirectNormal,\n                                             EVulnerability::DirectNormal, EDeflectType::None};\n} // namespace\nCRidleyData::CRidleyData(CInputStream& in, u32 propCount)\n: x0_(in)\n, x4_(in)\n, x8_(in)\n, xc_(in)\n, x10_(in)\n, x14_(in)\n, x18_(in)\n, x1c_(in)\n, x20_(in)\n, x24_(in)\n, x28_(in)\n, x2c_(in)\n, x30_(in)\n, x34_(in.ReadFloat())\n, x38_(in.ReadFloat())\n, x3c_(in.ReadFloat())\n, x40_(in.ReadFloat())\n, x44_(in)\n, x48_(in)\n, x64_(in)\n, xa8_(CSfxManager::TranslateSFXID(in.ReadLong()))\n, xac_(in)\n, xb0_(in)\n, xcc_(in)\n, x1a0_(in)\n, x1a4_(in)\n, x1c0_(in)\n, x294_(CSfxManager::TranslateSFXID(in.ReadLong()))\n, x298_(in)\n, x2b4_(in)\n, x388_(in.ReadFloat())\n, x38c_(in.ReadFloat())\n, x390_(in)\n, x3ac_(in.ReadFloat())\n, x3b0_(in)\n, x3cc_(in.ReadFloat())\n, x3d0_(in)\n, x3ec_(in.ReadFloat())\n, x3f0_(in)\n, x3f4_(in.ReadFloat())\n, x3f8_(CSfxManager::TranslateSFXID(in.ReadLong()))\n, x3fc_(propCount > 47 ? CDamageInfo(in) : x48_) {}\n\nCRidley::CRidley(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                 CModelData&& mData, const CPatternedInfo& pInfo, const CActorParameters& actParms, CInputStream& in,\n                 u32 propCount)\n: CPatterned(ECharacter::Ridley, uid, name, EFlavorType::Zero, info, xf, std::move(mData), pInfo, EMovementType::Flyer,\n             EColliderType::Zero, EBodyType::Flyer, actParms, EKnockBackVariant::Large)\n, x568_data(in, propCount)\n, x98c_(g_SimplePool->GetObj({SBIG('PART'), x568_data.x30_}))\n, x998_(CStaticRes(x568_data.x28_, GetModelData()->GetScale()))\n, x9e4_(CStaticRes(x568_data.x2c_, GetModelData()->GetScale()))\n, xa30_breastPlateSegId(GetModelData()->GetAnimationData()->GetLocatorSegId(\"breastPlate_LCTR\"sv))\n, xa38_(CStaticRes(x568_data.x24_, 4.f * GetModelData()->GetScale()))\n, xadc_(44.f * GetModelData()->GetScale().z())\n, xae0_(20.f * GetModelData()->GetScale().x())\n, xae8_(9.f * GetModelData()->GetScale().z())\n, xb14_(x568_data.x38_)\n, xb18_(x568_data.x3c_)\n, xb1c_(x568_data.x40_)\n, xb28_(GetModelData()->GetAnimationData()->GetLocatorSegId(\"Skeleton_Root\"sv))\n, xb2c_(*GetModelData()->GetAnimationData(), \"Head_1\"sv, zeus::degToRad(40.f), zeus::degToRad(180.f),\n        EBoneTrackingFlags::NoParentOrigin | EBoneTrackingFlags::ParentIk)\n, xb68_(x568_data.x44_, x568_data.x48_)\n, xb90_headSegId(GetModelData()->GetAnimationData()->GetLocatorSegId(\"Head_1\"sv))\n, xb91_mouthSegId(GetModelData()->GetAnimationData()->GetLocatorSegId(\"mouth_LCTR\"sv))\n, xb94_(zeus::CTransform::RotateX(zeus::degToRad(-40.f)))\n, xc14_(x568_data.xac_, x568_data.xb0_)\n, xc3c_(x568_data.x1a0_, x568_data.x1a4_)\n, xc8c_(CPatterned::GetContactDamage())\n, xcd0_(g_SimplePool->GetObj({SBIG('ELSC'), x568_data.x3f0_}))\n, xce0_(std::make_unique<CParticleElectric>(xcd0_))\n, xd10_(std::make_unique<CProjectedShadow>(128, 128, true)) {\n  xe7_30_doTargetDistanceTest = true;\n  xb68_.Token().Lock();\n  xc14_.Token().Lock();\n  xc3c_.Token().Lock();\n\n  if (xce0_) {\n    xce0_->SetParticleEmission(false);\n  }\n\n  const auto& animData = GetModelData()->GetAnimationData();\n  for (const auto& wingBone : skWingBones) {\n    xce4_wingBoneIds.push_back(animData->GetLocatorSegId(wingBone));\n  }\n\n  xae4_ = GetModelData()->GetScale().x() *\n          GetAnimationDistance(CPASAnimParmData(pas::EAnimationState::MeleeAttack, CPASAnimParm::FromEnum(4),\n                                                CPASAnimParm::FromEnum(3)));\n  x460_knockBackController.SetAnimationStateRange(EKnockBackAnimationState::Flinch, EKnockBackAnimationState::Flinch);\n  x460_knockBackController.SetEnableBurn(false);\n  x460_knockBackController.SetEnableFreeze(false);\n  x460_knockBackController.SetEnableBurnDeath(false);\n  x460_knockBackController.SetEnableLaggedBurnDeath(false);\n  CreateShadow(false);\n}\n\nvoid CRidley::SetupCollisionActorManager(metaforce::CStateManager& mgr) {\n  const auto& animData = GetModelData()->GetAnimationData();\n  std::vector<CJointCollisionDescription> joints;\n  joints.reserve(skTail.size());\n  for (const auto& jInfo : skTail) {\n    joints.push_back(CJointCollisionDescription::OBBAutoSizeCollision(\n        animData->GetLocatorSegId(jInfo.to), animData->GetLocatorSegId(jInfo.from),\n        zeus::CVector3f(GetModelData()->GetScale().z() * jInfo.radius),\n        CJointCollisionDescription::EOrientationType::One, std::string(GetName()) + \" - CollisionActor \" + jInfo.from,\n        10.f));\n  }\n  x980_tailCollision = std::make_unique<CCollisionActorManager>(mgr, GetUniqueId(), GetAreaIdAlways(), joints, false);\n  joints.clear();\n  joints.reserve(skSphereJoints.size());\n  for (const auto& jInfo : skSphereJoints) {\n    joints.push_back(\n        CJointCollisionDescription::SphereCollision(animData->GetLocatorSegId(jInfo.name), jInfo.radius,\n                                                    std::string(GetName()) + \" - CollisionActor \" + jInfo.name, 10.f));\n  }\n  x984_bodyCollision = std::make_unique<CCollisionActorManager>(mgr, GetUniqueId(), GetAreaIdAlways(), joints, false);\n  x988_headId = x984_bodyCollision->GetCollisionDescFromIndex(3).GetCollisionActorId();\n  x98a_breastPlateId = x984_bodyCollision->GetCollisionDescFromIndex(2).GetCollisionActorId();\n  SetupCollisionActors(mgr);\n  CMaterialList exclude = GetMaterialFilter().GetExcludeList();\n  CMaterialList include = GetMaterialFilter().GetIncludeList();\n  exclude.Add(EMaterialTypes::Solid);\n  include.Remove(EMaterialTypes::Solid);\n  exclude.Add(EMaterialTypes::CollisionActor);\n  include.Remove(EMaterialTypes::CollisionActor);\n  exclude.Add(EMaterialTypes::AIPassthrough);\n  include.Remove(EMaterialTypes::AIPassthrough);\n  exclude.Add(EMaterialTypes::Player);\n  include.Remove(EMaterialTypes::Player);\n  exclude.Add(EMaterialTypes::Platform);\n  include.Remove(EMaterialTypes::Platform);\n  SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(include, exclude));\n  AddMaterial(EMaterialTypes::ProjectilePassthrough);\n}\n\nvoid CRidley::SetupCollisionActors(CStateManager& mgr) {\n  for (size_t i = 0; i < x980_tailCollision->GetNumCollisionActors(); ++i) {\n    const auto& colDesc = x980_tailCollision->GetCollisionDescFromIndex(i);\n    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(colDesc.GetCollisionActorId())) {\n      colAct->SetDamageVulnerability(CDamageVulnerability::ImmuneVulnerabilty());\n      colAct->HealthInfo(mgr)->SetHP(1000.f);\n      colAct->SetMaterialFilter(CMaterialFilter::MakeInclude({EMaterialTypes::Player, EMaterialTypes::Platform}));\n      colAct->SetWeaponCollisionResponseType(EWeaponCollisionResponseTypes::EnemyNormal);\n    }\n  }\n\n  x980_tailCollision->AddMaterial(mgr, {EMaterialTypes::AIJoint});\n\n  for (size_t i = 0; i < x984_bodyCollision->GetNumCollisionActors(); ++i) {\n    const auto& colDesc = x984_bodyCollision->GetCollisionDescFromIndex(i);\n    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(colDesc.GetCollisionActorId())) {\n      colAct->SetDamageVulnerability(skDirectNormal);\n      colAct->HealthInfo(mgr)->SetHP(1000.f);\n      colAct->SetMaterialFilter(CMaterialFilter::MakeInclude({EMaterialTypes::Player, EMaterialTypes::Platform}));\n      colAct->SetWeaponCollisionResponseType(EWeaponCollisionResponseTypes::EnemyNormal);\n    }\n  }\n  x984_bodyCollision->AddMaterial(mgr, {EMaterialTypes::AIJoint});\n}\n\nvoid CRidley::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  CPatterned::AcceptScriptMsg(msg, uid, mgr);\n\n  switch (msg) {\n  case EScriptObjectMessage::Registered: {\n    x450_bodyController->Activate(mgr);\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n    SetupCollisionActorManager(mgr);\n    xb10_ = xcb8_ = HealthInfo(mgr)->GetHP();\n    xcbc_ = 0.8f * xcb8_;\n    break;\n  }\n  case EScriptObjectMessage::Reset: {\n    xa34_26_ = true;\n    if (!GetActive()) {\n      CPatterned::AcceptScriptMsg(EScriptObjectMessage::Activate, uid, mgr);\n    }\n    break;\n  }\n  case EScriptObjectMessage::Activate: {\n    mgr.SetBossParams(GetUniqueId(), xb1c_ + xcb8_ + xb18_, 90);\n    HealthInfo(mgr)->SetHP(xb1c_ + xb10_ + xb18_);\n    mgr.GetPlayer().SetIsOverrideRadarRadius(true);\n    mgr.GetPlayer().SetRadarXYRadiusOverride(350.f);\n    mgr.GetPlayer().SetRadarZRadiusOverride(175.f);\n    break;\n  }\n  case EScriptObjectMessage::Deactivate: {\n    x984_bodyCollision->SetActive(mgr, false);\n    x980_tailCollision->SetActive(mgr, false);\n    mgr.GetPlayer().SetIsOverrideRadarRadius(false);\n    break;\n  }\n  case EScriptObjectMessage::Deleted: {\n    x984_bodyCollision->Destroy(mgr);\n    x980_tailCollision->Destroy(mgr);\n    if (xb64_plasmaProjectile != kInvalidUniqueId) {\n      mgr.FreeScriptObject(xb64_plasmaProjectile);\n      xb64_plasmaProjectile = kInvalidUniqueId;\n    }\n    break;\n  }\n  case EScriptObjectMessage::InitializedInArea: {\n    TUniqueId wpId = GetWaypointForState(mgr, EScriptObjectState::Patrol, EScriptObjectMessage::Follow);\n    if (wpId == kInvalidUniqueId) {\n      break;\n    }\n\n    if (TCastToConstPtr<CScriptWaypoint> wp = mgr.GetObjectById(wpId)) {\n      xa84_ = wp->GetTransform();\n      if (TCastToConstPtr<CScriptWaypoint> wpNext = mgr.GetObjectById(wp->NextWaypoint(mgr))) {\n        xab4_ = (wpNext->GetTranslation() - wp->GetTranslation()).toVec2f().magnitude();\n        xab8_ = wpNext->GetTranslation().z() - xa84_.origin.z();\n        if (TCastToConstPtr<CScriptWaypoint> wpNextNext = mgr.GetObjectById(wpNext->NextWaypoint(mgr))) {\n          xabc_ = (wpNextNext->GetTranslation().toVec2f() - xa84_.origin.toVec2f()).magnitude();\n          xac0_ = wpNextNext->GetTranslation().z() - xa84_.origin.z();\n          zeus::CVector3f min(xa84_.origin.x() - xabc_, xa84_.origin.y() - xabc_, xa84_.origin.z() - 10.f);\n          zeus::CVector3f max(xa84_.origin.x() + xabc_, xa84_.origin.y() + xabc_, xa84_.origin.z() + 100.f);\n          xac4_ = zeus::CAABox(min, max);\n        }\n      }\n    }\n    break;\n  }\n  case EScriptObjectMessage::Damage: {\n    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(uid)) {\n      float lowHealth = 1000.f - colAct->HealthInfo(mgr)->GetHP();\n      colAct->HealthInfo(mgr)->SetHP(1000.f);\n      bool dontKnockback = false;\n      if (xc64_aiStage == 2) {\n        if (!xa33_28_) {\n          bool r26 = false;\n          xb10_ -= lowHealth;\n          xb24_ = 0.33f;\n          x430_damageColor = zeus::CColor(0.5f, 0.f, 0.f);\n          if (xb10_ > 0.f) {\n            if (xb10_ >= xcbc_ && xa33_26_ && !xa31_31_ && lowHealth > x568_data.x3f4_) {\n              dontKnockback = true;\n              x450_bodyController->GetCommandMgr().DeliverCmd(\n                  CBCKnockBackCmd(GetTransform().basis[1], pas::ESeverity::Zero));\n            } else {\n              xa32_27_ = true;\n              dontKnockback = true;\n              xcb0_ += 1;\n              xcb0_ = xcb0_ < 5 ? xcb0_ : 4;\n              r26 = true;\n              xcbc_ = .2f * float(5 - (xcb0_ + 1)) * xcb8_;\n              xcb4_ = 0;\n              xcc8_ = 2.f * 0.33f;\n            }\n          } else {\n            xc64_aiStage = 3;\n            xa31_25_ = false;\n            SetStage3BreastVulnerability(mgr);\n            xcbc_ = 0.6667f * x568_data.x3c_;\n            if (x450_bodyController->GetLocomotionType() != pas::ELocomotionType::Combat) {\n              for (const auto& effect : skWingEffects) {\n                GetModelData()->GetAnimationData()->SetParticleEffectState(effect, true, mgr);\n              }\n            }\n\n            xcc8_ = 2.f * 0.33f;\n            xb10_ = 0.f;\n          }\n          ActivateWingElectricity(2.f * 0.33f, r26);\n        } else {\n          dontKnockback = true;\n        }\n      } else if (xc64_aiStage == 3) {\n        if (xa32_29_) {\n          zeus::CTransform xf = GetLctrTransform(xb90_headSegId);\n          if ((mgr.GetPlayer().GetTranslation() - xf.origin).dot(xf.frontVector()) < 0.5f) {\n            HealthInfo(mgr)->SetHP(xb1c_ + xb10_ + xb18_);\n            break;\n          }\n        }\n\n        TakeDamage(zeus::skForward, 1.f);\n        xb20_ = 0.33f;\n        if (xa32_29_) {\n          if (TCastToConstPtr<CGameProjectile> proj = mgr.GetObjectById(colAct->GetLastTouchedObject())) {\n            CWeaponMode wMode = proj->GetDamageInfo().GetWeaponMode();\n            if (wMode.IsCharged() || wMode.IsComboed() || wMode.GetType() == EWeaponType::Missile) {\n              xb14_ = 0.f;\n            }\n            xb14_ -= lowHealth;\n            xb24_ = 0.33f;\n            x430_damageColor = zeus::CColor(0.5f, 0.f, 0.f);\n            if (xb10_ <= 0.f) {\n              xa32_29_ = false;\n              dontKnockback = true;\n              xa32_28_shotAt = true;\n              xb14_ = x568_data.x38_;\n            }\n          }\n        } else if (xa31_27_) {\n          x430_damageColor = zeus::CColor(0.5f, 0.f, 0.f);\n          if (xb18_ > 0.f) {\n            xb18_ -= lowHealth;\n            if (xb18_ <= 0.f) {\n              xa31_26_ = true;\n              dontKnockback = true;\n              xb18_ = 0.f;\n              xcbc_ = 0.6667f * x568_data.x40_;\n            } else if (xb18_ < xcbc_) {\n              x450_bodyController->GetCommandMgr().DeliverCmd(\n                  CBCKnockBackCmd(GetTransform().basis[1], pas::ESeverity::Six));\n              xcbc_ -= (0.333f * x568_data.x3c_);\n            }\n          } else {\n            xb1c_ -= lowHealth;\n            if (xb1c_ <= 0.f) {\n              x401_30_pendingDeath = true;\n              mgr.GetPlayer().SetIsOverrideRadarRadius(false);\n              xb1c_ = 0.f;\n            } else if (xb1c_ < xcbc_) {\n              dontKnockback = true;\n              x450_bodyController->GetCommandMgr().DeliverCmd(\n                  CBCKnockBackCmd(GetTransform().basis[1], pas::ESeverity::Six));\n              xcbc_ -= (0.333f * x568_data.x40_);\n            }\n          }\n        }\n      }\n\n      HealthInfo(mgr)->SetHP(xb1c_ + xb10_ + xb18_);\n\n      if (!dontKnockback) {\n        if (TCastToConstPtr<CGameProjectile> proj = mgr.GetObjectById(colAct->GetLastTouchedObject())) {\n          KnockBack(proj->GetTranslation() - proj->GetPreviousPos(), mgr, proj->GetDamageInfo(), EKnockBackType::Direct,\n                    false, proj->GetDamageInfo().GetKnockBackPower());\n        }\n      }\n    }\n    break;\n  }\n  case EScriptObjectMessage::InvulnDamage: {\n    /* This code never executes, should have a `TCastTo<CCollisionActor>` followed by `GetLastTouchedObject` */\n    if (TCastToConstPtr<CGameProjectile> proj = mgr.GetObjectById(uid)) {\n      TUniqueId tmpId = kInvalidUniqueId;\n      bool doDamage = false;\n      if (xc64_aiStage == 3) {\n        if (!xa32_29_) {\n          if (xa31_27_) {\n            tmpId = x98a_breastPlateId;\n            doDamage = true;\n          }\n        } else {\n          tmpId = x988_headId;\n          doDamage = true;\n        }\n      } else if (xc64_aiStage == 2 && !xa31_31_) {\n        tmpId = x98a_breastPlateId;\n        doDamage = true;\n      }\n\n      if (doDamage) {\n        CDamageInfo info = proj->GetDamageInfo();\n        info.SetRadius(0.f);\n        mgr.ApplyDamage(uid, tmpId, proj->GetOwnerId(), info,\n                        CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), {});\n      }\n    }\n    break;\n  }\n  case EScriptObjectMessage::Touched: {\n    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(uid)) {\n      if (colAct->GetMaterialFilter().Passes({EMaterialTypes::Platform})) {\n        if (TCastToConstPtr<CScriptPlatform> plat = mgr.GetObjectById(colAct->GetLastTouchedObject())) {\n          mgr.ApplyDamage(GetUniqueId(), plat->GetUniqueId(), GetUniqueId(),\n                          CDamageInfo(CWeaponMode(EWeaponType::AI), 1.f + plat->GetHealthInfo(mgr)->GetHP(), 0.f, 1.f),\n                          CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), {});\n        }\n      }\n\n      if (mgr.GetPlayer().GetUniqueId() == colAct->GetLastTouchedObject() && x420_curDamageRemTime <= 0.f) {\n        mgr.ApplyDamage(GetUniqueId(), mgr.GetPlayer().GetUniqueId(), GetUniqueId(), xc8c_,\n                        CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), {});\n        x420_curDamageRemTime = x424_damageWaitTime;\n      }\n    }\n    break;\n  }\n  default:\n    break;\n  }\n}\n\nvoid CRidley::Think(float dt, CStateManager& mgr) {\n  if (!GetActive()) {\n    return;\n  }\n\n  UpdateFlight(dt);\n  CPatterned::Think(dt, mgr);\n  ConstrainToHenge(dt);\n  x984_bodyCollision->Update(dt, mgr, CCollisionActorManager::EUpdateOptions::ObjectSpace);\n  x980_tailCollision->Update(dt, mgr, CCollisionActorManager::EUpdateOptions::ObjectSpace);\n  xb20_ = std::max(0.f, xb20_ - dt);\n  xb24_ = std::max(0.f, xb24_ - dt);\n  xcc8_ = std::max(0.f, xcc8_ - dt);\n  UpdateBeam(dt, mgr);\n  UpdateWingElectricity(dt, mgr);\n  xb2c_.Update(dt);\n}\n\nvoid CRidley::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) {\n  CPatterned::PreRender(mgr, frustum);\n  xb2c_.PreRender(mgr, *GetModelData()->GetAnimationData(), x34_transform, GetModelData()->GetScale(),\n                  *x450_bodyController);\n  u32 matSet = xc74_;\n  if (xcc8_ > 0.f) {\n    u32 numMaterialSets = GetModelData()->GetNumMaterialSets();\n    u32 tmp = (30.f * xcc8_);\n    matSet = tmp - (tmp / numMaterialSets) * numMaterialSets;\n  }\n\n  CPlayerState::EPlayerVisor r28 = mgr.GetPlayerState()->GetActiveVisor(mgr);\n  bool atLastMat = GetModelData()->GetNumMaterialSets() == (matSet + 1);\n  if (r28 == CPlayerState::EPlayerVisor::Thermal && atLastMat) {\n    xb4_drawFlags.x2_flags |= CModelFlagBits::ThermalUnsortedOnly;\n  } else {\n    xb4_drawFlags.x2_flags &= CModelFlagBits::ThermalUnsortedOnly;\n  }\n  xb4_drawFlags.x1_matSetIdx = matSet;\n\n  if (xa33_27_) {\n    float zDiff = std::max(0.f, GetTranslation().z() - xa84_.origin.z());\n    xccc_ = 1.f + zeus::clamp(0.f, zDiff - 20.f, 1.f);\n    zeus::CVector3f extents = x9c_renderBounds.max - x9c_renderBounds.min;\n    zeus::CVector3f something = 0.5f * (xccc_ * extents - extents);\n    zeus::CAABox box(GetRenderBounds().min - something, GetRenderBounds().max + something);\n    if ((r28 != CPlayerState::EPlayerVisor::Combat && r28 != CPlayerState::EPlayerVisor::Scan) ||\n        !xac4_.intersects(box) || zDiff <= -10.f) {\n      xd10_->Unset_X80();\n    } else {\n      xd10_->RenderShadowBuffer(mgr, *GetModelData(), GetTransform(), atLastMat, {}, xccc_, 10.f + zDiff);\n    }\n  } else {\n    xd10_->Unset_X80();\n  }\n}\n\nvoid CRidley::Render(CStateManager& mgr) {\n  zeus::CColor multiplyColor = zeus::skBlack;\n  if (xb24_ > 0.f) {\n    multiplyColor = zeus::CColor::lerp(zeus::skWhite, x430_damageColor, xb24_ / 0.33f);\n  }\n  g_Renderer->SetGXRegister1Color(multiplyColor);\n\n  const zeus::CTransform xf = GetLctrTransform(xa30_breastPlateSegId);\n\n  if (xa31_25_) {\n    if (xb24_ > 0.f) {\n      x9e4_.Render(mgr, xf, GetActorLights(),\n                   CModelFlags(2, 0, 3, zeus::CColor::lerp(zeus::skBlack, x430_damageColor, xb24_ / 0.33f)));\n    } else {\n      x9e4_.Render(mgr, xf, GetActorLights(), CModelFlags(0, 0, 3, zeus::skWhite));\n    }\n  } else if (xa31_24_) {\n    if (xb20_ > 0.f) {\n      x998_.Render(mgr, xf, GetActorLights(),\n                   CModelFlags(2, 0, 3, zeus::CColor::lerp(zeus::skBlack, x430_damageColor, xb20_ / 0.33f)));\n    } else {\n      x998_.Render(mgr, xf, GetActorLights(), CModelFlags(0, 0, 3, zeus::skWhite));\n    }\n  }\n\n  CPatterned::Render(mgr);\n}\n\nvoid CRidley::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) {\n  CPatterned::AddToRenderer(frustum, mgr);\n  if (xce0_ && frustum.aabbFrustumTest(*xce0_->GetBounds())) {\n    g_Renderer->AddParticleGen(*xce0_);\n  }\n}\n\nvoid CRidley::DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) {\n  switch (type) {\n  case EUserEventType::Projectile: {\n    if (xc64_aiStage != 2) {\n      return;\n    }\n\n    if (!xc14_.Token().IsLoaded()) {\n      xc14_.Token().GetObj();\n      return;\n    }\n\n    if (!mgr.CanCreateProjectile(GetUniqueId(), EWeaponType::AI, 9)) {\n      return;\n    }\n\n    zeus::CTransform xf = GetLctrTransform(xa30_breastPlateSegId) * zeus::CTransform::RotateX(zeus::degToRad(-90.f));\n    xf = xf * zeus::CTransform::RotateY(\n                  std::atan2(mgr.GetActiveRandom()->Range(-1.f, 1.f), mgr.GetActiveRandom()->Range(-1.f, 1.f)));\n    xf.origin = xf * zeus::CVector3f(0.f, 1.f, 1.f);\n    auto* proj =\n        new CEnergyProjectile(true, xc14_.Token(), EWeaponType::AI, xf, EMaterialTypes::Character, xc14_.GetDamage(),\n                              mgr.AllocateUniqueId(), GetAreaIdAlways(), GetUniqueId(), mgr.GetPlayer().GetUniqueId(),\n                              EProjectileAttrib::None, false, zeus::skOne3f, {}, -1, false);\n    mgr.AddObject(proj);\n    proj->SetCameraShake(x568_data.xcc_);\n    proj->SetMinHomingDistance(20.f);\n    return;\n  }\n  case EUserEventType::EggLay: {\n    if (xa32_24_) {\n      if (!xc3c_.Token().IsLoaded()) {\n        xc3c_.Token().GetObj();\n        break;\n      }\n\n      if (!mgr.CanCreateProjectile(GetUniqueId(), EWeaponType::AI, 8)) {\n        break;\n      }\n\n      const float x = mgr.GetActiveRandom()->Range(-1.f, 1.f);\n      const float z = mgr.GetActiveRandom()->Range(-1.f, 1.f);\n      const auto vec = GetLctrTransform(xa30_breastPlateSegId) * zeus::CVector3f{x, 1.f, z};\n      const auto dir = mgr.GetPlayer().GetTranslation() + zeus::CVector3f{10.f * x, 10.f * z, 0.f};\n      auto* proj = new CEnergyProjectile(true, xc3c_.Token(), EWeaponType::AI, zeus::lookAt(vec, dir),\n                                         EMaterialTypes::Character, xc3c_.GetDamage(), mgr.AllocateUniqueId(),\n                                         GetAreaIdAlways(), GetUniqueId(), mgr.GetPlayer().GetUniqueId(),\n                                         EProjectileAttrib::None, false, zeus::skOne3f, {}, -1, false);\n\n      mgr.AddObject(proj);\n      proj->SetCameraShake(x568_data.xcc_);\n    } else if (xc64_aiStage == 3) {\n      xa31_24_ = false;\n    }\n    break;\n  }\n  case EUserEventType::DamageOn: {\n    if (xc64_aiStage == 3) {\n      SetStage3ThroatVulnerability(mgr);\n    } else if (xc64_aiStage == 2) {\n      xa33_28_ = false;\n    }\n\n    break;\n  }\n  case EUserEventType::DamageOff: {\n    if (xc64_aiStage == 3) {\n      SetStage3BreastVulnerability(mgr);\n    } else if (xc64_aiStage == 2 && !xa33_31_) {\n      xa33_28_ = true;\n    }\n\n    break;\n  }\n  case EUserEventType::Landing: {\n    SetVelocityWR(zeus::skDown);\n    xaec_.zeroOut();\n    xaf8_.zeroOut();\n    break;\n  }\n  case EUserEventType::FadeOut: {\n    xc74_ = std::min(GetModelData()->GetNumMaterialSets(), xc74_ + 1);\n    return;\n  }\n  case EUserEventType::ScreenShake: {\n    if ((mgr.GetPlayer().GetTranslation() - GetTranslation()).magnitude() >= x568_data.x388_) {\n      break;\n    }\n    mgr.ApplyDamage(GetUniqueId(), mgr.GetPlayer().GetUniqueId(), GetUniqueId(), x568_data.x298_,\n                    CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), {});\n    break;\n  }\n  case EUserEventType::BeginAction: {\n    if (xa32_25_ && !xa31_29_) {\n      FirePlasma(mgr);\n    }\n\n    if (!xa31_31_ || !xa32_26_) {\n      break;\n    }\n\n    xbf0_ = xa84_.basis[0];\n    zeus::CVector3f ourPos = GetTranslation();\n    if ((ourPos - xa84_.origin).dot(xbf0_) >= 30.f) {\n      xbf0_ *= zeus::CVector3f(-1.f);\n    }\n    xbfc_ = xbf0_;\n    xbe4_ = xa84_.origin - xabc_ * xbf0_;\n    xbe4_ += (mgr.GetPlayer().GetTranslation() - xa84_.origin).dot(xa84_.frontVector()) * xa84_.frontVector();\n    break;\n  }\n  case EUserEventType::EndAction: {\n    if (xa31_29_) {\n      ResetPlasmaProjectile(mgr, false);\n    }\n    break;\n  }\n  case EUserEventType::IkLock: {\n    xa32_26_ = true;\n    break;\n  }\n  case EUserEventType::IkRelease: {\n    xa32_26_ = false;\n    break;\n  }\n  case EUserEventType::BreakLockOn: {\n    if (x400_25_alive) {\n      RemoveMaterial(EMaterialTypes::Target, EMaterialTypes::Orbit, mgr);\n      mgr.GetPlayer().TryToBreakOrbit(GetUniqueId(), CPlayer::EPlayerOrbitRequest::ActivateOrbitSource, mgr);\n      return;\n    }\n    break;\n  }\n  case EUserEventType::SoundPlay: {\n    if (xa32_25_) {\n      break;\n    }\n\n    xcac_ = CSfxManager::AddEmitter({GetTranslation(), {}, 1000.f, 0.1f, 1, x568_data.x294_, 127, 63, false, 127}, true,\n                                    -1, false, kInvalidAreaId);\n    break;\n  }\n  default:\n    break;\n  }\n\n  CPatterned::DoUserAnimEvent(mgr, node, type, dt);\n}\n\nvoid CRidley::SetStage3ThroatVulnerability(CStateManager& mgr) {\n  for (size_t i = 0; i < x984_bodyCollision->GetNumCollisionActors(); ++i) {\n    const auto& colDesc = x984_bodyCollision->GetCollisionDescFromIndex(i);\n    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(colDesc.GetCollisionActorId())) {\n      colAct->SetDamageVulnerability(i == 3 ? skIceWeakness : CDamageVulnerability::ImmuneVulnerabilty());\n      colAct->HealthInfo(mgr)->SetHP(1000.f);\n      colAct->CreateShadow(true);\n    }\n  }\n\n  xa32_29_ = true;\n  xa31_27_ = false;\n}\n\nvoid CRidley::ChooseStage2Attack(metaforce::CStateManager& mgr) {\n  xb04_ = skSomeRidleyStruct[xcb0_][xcb4_].x4_ < mgr.GetActiveRandom()->Range(0.f, 100.f)\n              ? skSomeRidleyStruct[xcb0_][xcb4_].x8_\n              : skSomeRidleyStruct[xcb0_][xcb4_].x0_;\n  if (xb04_ == -1) {\n    xcb4_ = 0;\n    xb04_ = skSomeRidleyStruct[xcb0_][xcb4_].x4_ < mgr.GetActiveRandom()->Range(0.f, 100.f)\n                ? skSomeRidleyStruct[xcb0_][xcb4_].x8_\n                : skSomeRidleyStruct[xcb0_][xcb4_].x0_;\n  }\n\n  ++xcb4_;\n  xcc4_ = 1;\n}\n\nvoid CRidley::SetStage3Immunity(metaforce::CStateManager& mgr) {\n  for (size_t i = 0; i < x984_bodyCollision->GetNumCollisionActors(); ++i) {\n    const auto& colDesc = x984_bodyCollision->GetCollisionDescFromIndex(i);\n    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(colDesc.GetCollisionActorId())) {\n      colAct->SetDamageVulnerability(i == 2 ? skDirectNormal : CDamageVulnerability::ImmuneVulnerabilty());\n      colAct->HealthInfo(mgr)->SetHP(1000.f);\n      colAct->CreateShadow(true);\n    }\n  }\n\n  xa32_29_ = false;\n  xa31_27_ = true;\n}\n\nvoid CRidley::FirePlasma(metaforce::CStateManager& mgr) {\n  if (xb64_plasmaProjectile == kInvalidUniqueId) {\n    xb64_plasmaProjectile = mgr.AllocateUniqueId();\n    mgr.AddObject(new CPlasmaProjectile(xb68_.Token(), \"\"sv, EWeaponType::AI, x568_data.x64_, {},\n                                        EMaterialTypes::Character, xb68_.GetDamage(), xb64_plasmaProjectile,\n                                        GetAreaIdAlways(), GetUniqueId(), CPlasmaProjectile::PlayerEffectResources(),\n                                        false, EProjectileAttrib::KeepInCinematic));\n  }\n\n  if (auto* proj = static_cast<CPlasmaProjectile*>(mgr.ObjectById(xb64_plasmaProjectile))) {\n    proj->Fire(GetLctrTransform(xb91_mouthSegId), mgr, false);\n    if (!xca8_) {\n      xca8_ = CSfxManager::AddEmitter({GetTranslation(), {}, 1000.f, 0.1f, 1, x568_data.xa8_, 127, 63, false, 127},\n                                      true, -1, true, -1);\n    }\n  }\n}\n\nvoid CRidley::SetStage3BreastVulnerability(CStateManager& mgr) {\n  for (size_t i = 0; i < x984_bodyCollision->GetNumCollisionActors(); ++i) {\n    const auto& colDesc = x984_bodyCollision->GetCollisionDescFromIndex(i);\n    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(colDesc.GetCollisionActorId())) {\n      colAct->SetDamageVulnerability(CDamageVulnerability::ImmuneVulnerabilty());\n      colAct->HealthInfo(mgr)->SetHP(1000.f);\n      colAct->CreateShadow(true);\n    }\n  }\n\n  xa32_29_ = false;\n  xa31_27_ = false;\n}\n\nvoid CRidley::ActivateWingElectricity(float f31, bool r4) {\n  if (xce0_) {\n    xce0_->SetParticleEmission(true);\n  }\n\n  xd08_ = r4 ? 2.f * f31 : f31;\n  xa32_30_ = r4;\n  if (!xd0c_) {\n    xd0c_ = CSfxManager::AddEmitter(\n        CAudioSys::C3DEmitterParmData{GetTranslation(), zeus::skZero3f, 500.f, 0.1f, 1, 0, 127, 63, false, 127}, true,\n        127, true, kInvalidAreaId);\n  }\n}\n\nvoid CRidley::UpdateFlight(float dt) {\n  if (!IsAlive()) {\n    xaec_.zeroOut();\n  } else if (xaec_.isMagnitudeSafe()) {\n    const float mag = xaec_.magnitude();\n    float magScale = 0.2f;\n    if (xaf8_.magSquared() == 0.f) {\n      magScale *= 3.f;\n    }\n    xaec_ = -((zeus::clamp(0.f, dt * (magScale * mag), 0.5f) * mag) - mag) * ((1.f / mag) * xaec_);\n    ApplyImpulseWR(GetMass() * xaec_, {});\n  }\n  xaf8_.zeroOut();\n}\n\nvoid CRidley::ConstrainToHenge(float dt) {\n  if (xc64_aiStage == 3 && !x328_25_verticalMovement) {\n    SetTranslation({GetTranslation().x(), GetTranslation().y(), xa84_.origin.z()});\n    zeus::CVector3f posDiff = GetTranslation() - xa84_.origin;\n    float mag = posDiff.magnitude();\n    posDiff *= zeus::CVector3f(1.f / mag);\n    if (xab4_ + -6.f * zeus::clamp(-1.f, posDiff.dot(xa84_.basis[1]), 0.f) < mag && GetVelocity().dot(posDiff) > 0.f) {\n      Stop();\n    }\n\n    MoveToInOneFrameWR(GetTranslation() - posDiff, dt);\n  }\n}\n\nvoid CRidley::UpdateBeam(float dt, CStateManager& mgr) {\n  if (auto* proj = static_cast<CPlasmaProjectile*>(mgr.ObjectById(xb64_plasmaProjectile))) {\n    if (!proj->GetActive()) {\n      return;\n    }\n\n    zeus::CTransform mouthXf = GetLctrTransform(xb91_mouthSegId);\n    if (xc64_aiStage == 3) {\n      proj->UpdateFx(mouthXf, dt, mgr);\n    } else {\n      zeus::CTransform xf = zeus::lookAt(xf.origin, xbe4_);\n      proj->UpdateFx(xf, dt, mgr);\n      float d;\n      if (xbf0_.cross(zeus::skUp).dot(mgr.GetPlayer().GetTranslation() + zeus::skUp) <= 0.f)\n        d = xc10_;\n      else\n        d = -xc10_;\n\n      zeus::CQuaternion quat;\n      quat.rotateZ(zeus::degToRad(dt * d));\n      zeus::CVector3f vec = quat.transform(xbf0_);\n      float dist = xbfc_.dot(vec);\n      if (dist > 0.5f || xbfc_.dot(xbfc_) < dist) {\n        xbf0_ = vec;\n      }\n    }\n  }\n\n  if (xca8_) {\n    CSfxManager::UpdateEmitter(xca8_, GetTranslation(), {}, 1.f);\n  }\n}\n\nvoid CRidley::UpdateWingElectricity(float dt, CStateManager& mgr) {\n  if (xce0_) {\n    xce0_->SetGlobalOrientation(GetTransform().getRotation());\n    xce0_->SetGlobalTranslation(GetTranslation());\n    xce0_->SetGlobalScale(GetModelData()->GetScale());\n\n    if (xce0_->GetParticleEmission()) {\n      xd08_ -= dt;\n\n      if (xd08_ <= 0.f) {\n        xce0_->SetParticleEmission(false);\n        if (xd0c_) {\n          CSfxManager::RemoveEmitter(xd0c_);\n          xd0c_.reset();\n        }\n      } else {\n        xce0_->SetOverrideIPos(\n            GetModelData()->GetAnimationData()->GetLocatorTransform(xa30_breastPlateSegId, nullptr).origin);\n        s32 min = 0;\n        s32 max = xce4_wingBoneIds.size() - 1;\n        if (xa32_30_)\n          max = xce4_wingBoneIds.size() - 6;\n        else\n          min = xce4_wingBoneIds.size() - 12;\n        zeus::CTransform xf = GetModelData()->GetAnimationData()->GetLocatorTransform(\n            xce4_wingBoneIds[mgr.GetActiveRandom()->Range(min, max)], nullptr);\n        xce0_->SetOverrideFPos(xf.origin);\n        xce0_->ForceParticleCreation(1);\n      }\n    }\n    xce0_->Update(dt);\n  } else if (xd0c_) {\n    CSfxManager::RemoveEmitter(xd0c_);\n    xd0c_.reset();\n  }\n\n  if (xd0c_)\n    CSfxManager::UpdateEmitter(xd0c_, GetTranslation(), {}, 1.f);\n}\n\nvoid CRidley::Patrol(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg != EStateMsg::Activate)\n    return;\n  ChooseStage2Attack(mgr);\n  xa32_27_ = false;\n  xa33_26_ = true;\n}\n\nvoid CRidley::Dead(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg != EStateMsg::Activate)\n    return;\n  mgr.SetBossParams(kInvalidUniqueId, 0.f, 0);\n}\n\nvoid CRidley::Generate(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg != EStateMsg::Activate)\n    return;\n\n  xa34_26_ = false;\n  SetTranslation(xa84_ * zeus::CVector3f{0.f, xabc_, xac0_ - xadc_});\n  x450_bodyController->GetCommandMgr().DeliverCmd(CBodyStateCmd(EBodyStateCmd::NextState));\n}\n\nvoid CRidley::Attack(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    xbe4_ = (GetTranslation() + zeus::skUp) - (8.f * GetTransform().rightVector());\n    xbfc_ = xbf0_ = GetTransform().rightVector();\n    xc08_ = xc0c_ = 0.f;\n    xc10_ = 240.f;\n    x32c_animState = EAnimState::Ready;\n    xa32_25_ = true;\n    --xcc4_;\n  } else if (msg == EStateMsg::Update) {\n    if (!xa31_29_) {\n      FacePlayer(arg, mgr);\n    } else {\n      xc0c_ = std::min(0.5f, 0.5f * arg + xc0c_);\n      xc08_ = arg * xc0c_ + xc08_;\n      xbe4_ += xc08_ * xbf0_;\n    }\n    TryCommand(mgr, pas::EAnimationState::ProjectileAttack, &CPatterned::TryProjectileAttack, 0);\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n    ResetPlasmaProjectile(mgr, true);\n    xa32_25_ = false;\n  }\n}\n\nvoid CRidley::LoopedAttack(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    if (xa32_31_) {\n      xa33_24_ = false;\n      xbc4_ = GetTranslation();\n      xbd0_ = GetTransform().basis[1];\n    } else {\n      xa33_24_ = (GetTranslation() - xa84_.origin).magSquared() < 0.f;\n\n      for (const auto& conn : GetConnectionList()) {\n        if (conn.x0_state != EScriptObjectState::Attack || conn.x4_msg != EScriptObjectMessage::Follow)\n          continue;\n\n        TUniqueId uid = mgr.GetIdForScript(conn.x8_objId);\n        if (uid == kInvalidUniqueId)\n          continue;\n\n        if (TCastToConstPtr<CScriptWaypoint> wp = mgr.GetObjectById(uid)) {\n          zeus::CVector3f wpPos = wp->GetTranslation();\n          const float mag = xa84_.basis[0].dot(wpPos - xa84_.origin);\n          if ((xa33_24_ && mag > 0.f) || (!xa33_24_ && mag <= 0.f)) {\n            xbc4_ = wp->GetTranslation();\n            xbd0_ = wp->GetTransform().basis[1];\n            x2ec_reflectedDestPos = GetTranslation();\n            break;\n          }\n        }\n      }\n    }\n\n    xbdc_ = 0.f;\n    xbe0_ = 800.f;\n    xa31_30_ = false;\n    xa31_31_ = true;\n    xc10_ = 120.f;\n    xa32_24_ = true;\n    xcc0_ = skSomeStruct[xcb0_].x1c_;\n    xc68_ = GetModelData()->GetScale();\n    SetSphereCollisionRadius(2.f, mgr);\n  } else if (msg == EStateMsg::Update) {\n    zeus::CVector3f diffVec = GetTranslation() - xa84_.origin;\n    float fVar22 = zeus::clamp(0.f, diffVec.magnitude() - 100.f, 250.f) / 250.f;\n    float local_d0 = zeus::clamp(0.3f, 1.f - (0.7f * -(2.f * fVar22 - 3.f) * (fVar22 * fVar22)), 1.f);\n    GetModelData()->SetScale(local_d0 * xc68_);\n    x55c_moveScale = zeus::CVector3f((1.f / local_d0));\n    x9e4_.SetScale(local_d0 * xc68_);\n    if (x330_stateMachineState.GetTime() > 1.f && skSomeStruct[xcb0_].x20_ != 0) {\n      xa32_25_ = true;\n    }\n\n    if (xa32_25_ && xa31_29_) {\n      xbe4_ += (90.f * arg) * xbf0_;\n    }\n\n    if (xa32_24_ && xcac_) {\n      CSfxManager::UpdateEmitter(xca8_, GetTranslation(), {}, 127);\n    }\n    if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::Step)\n      return;\n\n    if (!xa31_30_) {\n      zeus::CVector3f local_54 = xbc4_ - GetTranslation();\n      float mag = local_54.magnitude();\n      if (mag <= 2.f || local_54.dot(xbc4_ - x2ec_reflectedDestPos) <= 0.f) {\n        auto dir = pas::EStepDirection::Right;\n        if (xa32_31_) {\n          dir = pas::EStepDirection::Left;\n        }\n\n        if (xcc0_ == 3) {\n          dir = pas::EStepDirection::Up;\n        } else if (xcc0_ == 2) {\n          dir = pas::EStepDirection::Forward;\n        }\n\n        auto type = pas::EStepType::Normal;\n        if (xa33_24_) {\n          type = pas::EStepType::BreakDodge;\n        }\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCStepCmd(dir, type));\n        xa31_30_ = true;\n      } else {\n        local_54 *= zeus::CVector3f(1.f / mag);\n        xbdc_ += zeus::clamp(-100.f * arg, xbe0_ - xbdc_, 100.f * arg);\n        sub80255fe8(xbdc_, arg, local_54);\n        x450_bodyController->FaceDirection(xbd0_, arg * zeus::clamp(1.f, 10.f / mag, 10.f));\n      }\n    } else {\n      x330_stateMachineState.SetCodeTrigger();\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n    if (xa32_25_) {\n      ResetPlasmaProjectile(mgr, true);\n    }\n\n    if (xa32_24_) {\n      xcac_.reset();\n    }\n    xa32_24_ = false;\n    xa32_25_ = false;\n    GetModelData()->SetScale(xc68_);\n    xa31_31_ = false;\n    x55c_moveScale.splat(1.f);\n    SetupCollisionActors(mgr);\n    SetSphereCollisionRadius(0.5f, mgr);\n    xa32_31_ = false;\n  }\n}\n\nvoid CRidley::ResetPlasmaProjectile(CStateManager& mgr, bool b1) {\n  if (CPlasmaProjectile* ent = static_cast<CPlasmaProjectile*>(mgr.ObjectById(xb64_plasmaProjectile))) {\n    ent->ResetBeam(mgr, b1);\n    xa31_29_ = false;\n  }\n\n  if (xca8_) {\n    CSfxManager::RemoveEmitter(xca8_);\n    xca8_.reset();\n  }\n}\n\nvoid CRidley::SetSphereCollisionRadius(float f1, CStateManager& mgr) {\n  for (size_t i = 0; i < x984_bodyCollision->GetNumCollisionActors(); ++i) {\n    const auto& colDesc = x984_bodyCollision->GetCollisionDescFromIndex(i);\n    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(colDesc.GetCollisionActorId())) {\n      colAct->SetSphereRadius(f1 * colAct->GetSphereRadius());\n    }\n  }\n}\n\nvoid CRidley::JumpBack(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n    SetMomentumWR(zeus::skZero3f);\n    x328_25_verticalMovement = true;\n    SetDestPos(xa84_.origin + (xabc_ * xa84_.basis[1]) + zeus::CVector3f(0.f, 0.f, xac0_));\n    RemoveMaterial(EMaterialTypes::Solid, mgr);\n    CMaterialList include = GetMaterialFilter().GetIncludeList();\n    CMaterialList exclude = GetMaterialFilter().GetExcludeList();\n    include.Remove(EMaterialTypes::Solid);\n    exclude.Add(EMaterialTypes::Solid);\n    SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(include, exclude));\n    xa32_26_ = false;\n    xc7c_ =\n        zeus::CVector2f::getAngleDiff((x2e0_destPos - GetTranslation()).toVec2f(), GetTransform().basis[1].toVec2f());\n\n    if (GetTransform().basis[0].dot(x2e0_destPos - GetTranslation()) > 0.f)\n      xc7c_ = -xc7c_;\n    xc78_ = 0.f;\n\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::Generate, &CPatterned::TryGenerateNoXf, 4);\n\n    if (x32c_animState == EAnimState::Repeat) {\n      x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n      if (xc78_ == 0.f) {\n        CCharAnimTime ikLock =\n            GetModelData()->GetAnimationData()->GetTimeOfUserEvent(EUserEventType::IkLock, CCharAnimTime::Infinity());\n        CCharAnimTime ikRelease = GetModelData()->GetAnimationData()->GetTimeOfUserEvent(EUserEventType::IkRelease,\n                                                                                         CCharAnimTime::Infinity());\n        if (ikLock != CCharAnimTime::Infinity() && ikRelease != CCharAnimTime::Infinity()) {\n          xc78_ = ikRelease.GetSeconds() - ikLock.GetSeconds();\n        }\n      }\n\n      if (xa32_26_) {\n        zeus::CQuaternion q;\n        q.rotateZ((xc7c_ * arg) / xc78_);\n        RotateInOneFrameOR(q, arg);\n      }\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n  }\n}\n\nvoid CRidley::DoubleSnap(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n    x402_28_isMakingBigStrike = x568_data.x3cc_ > 0.f;\n    x504_damageDur = x568_data.x3cc_;\n    xc8c_ = x568_data.x3b0_;\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::MeleeAttack, &CPatterned::TryMeleeAttack, 2);\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n    x402_28_isMakingBigStrike = false;\n    x504_damageDur = 0.f;\n    xc8c_ = GetContactDamage();\n  }\n}\n\nvoid CRidley::CoverAttack(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n    x402_28_isMakingBigStrike = x568_data.x3ec_ > 0.f;\n    x504_damageDur = x568_data.x3ec_;\n    xc8c_ = x568_data.x3d0_;\n    RandomSpeedUp();\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::MeleeAttack, &CPatterned::TryMeleeAttack, 1);\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n    x402_28_isMakingBigStrike = false;\n    x504_damageDur = 0.f;\n    xc8c_ = GetContactDamage();\n  }\n}\n\nvoid CRidley::Crouch(metaforce::CStateManager& mgr, metaforce::EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    SetMomentumWR(GetGravityConstant() * zeus::skDown);\n    if (xc64_aiStage == 3) {\n      ChooseStage3Attack(mgr);\n    }\n  } else if (msg == EStateMsg::Update) {\n    if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::Turn)\n      return;\n\n    zeus::CVector3f faceDir = (mgr.GetPlayer().GetTranslation() - GetTranslation()).normalized();\n    if (faceDir.dot(GetTransform().basis[1]) < 0.9f)\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(zeus::skZero3f, faceDir, 1.f));\n  }\n}\n\nvoid CRidley::FadeOut(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n    for (const auto& effect : skWingEffects) {\n      GetModelData()->GetAnimationData()->SetParticleEffectState(effect, false, mgr);\n    }\n    if (!xa34_24_)\n      xa34_24_ = true;\n\n    xb68_.SetDamage(x568_data.x3fc_);\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::KnockBack, &CPatterned::TryKnockBack_Front, 5);\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n    xcbc_ = 0.6667f * x568_data.x3c_;\n  }\n}\n\nvoid CRidley::Taunt(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::Taunt, &CPatterned::TryTaunt, 3);\n    FacePlayer(arg, mgr);\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n  }\n}\n\nvoid CRidley::Flee(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    zeus::CVector3f destPos =\n        xa84_.origin.toVec2f() + (40.f + xabc_) * (GetTranslation().toVec2f() - xa84_.origin.toVec2f()).normalized();\n    destPos.z() = xac0_ + xa84_.origin.z();\n    SetDestPos(destPos);\n  } else if (msg == EStateMsg::Update) {\n    sub80255fe8(50.f, arg, (x2e0_destPos - GetTranslation()).normalized());\n    x450_bodyController->FaceDirection((x2e0_destPos - GetTranslation()).normalized(), arg);\n  }\n}\n\nvoid CRidley::Lurk(metaforce::CStateManager& mgr, metaforce::EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    if (!xa33_25_) {\n      zeus::CVector3f vec = GetTranslation() - xa84_.origin;\n      vec.z() = 0.f;\n      arg = zeus::CVector3f::getAngleDiff(xa84_.basis[1], vec);\n      if (vec.dot(xa84_.basis[0]) < 0.f)\n        arg = -arg;\n\n      if (std::fabs(mgr.GetActiveRandom()->Range(-1.2566371f, 1.2566371f) - arg) >= 0.39269909f) {\n        /* CodeWarrior was drunk */\n      }\n    } else {\n      x984_bodyCollision->SetActive(mgr, true);\n      x980_tailCollision->SetActive(mgr, true);\n    }\n    xa33_25_ = false;\n    float f30 = xac0_ - xadc_;\n    float f31 = xabc_ * std::cos(0.f);\n    float f0 = xabc_ * std::sin(0.f);\n    zeus::CVector3f destPos = xa84_ * zeus::CVector3f(f0, f31, f30);\n    SetDestPos(destPos);\n    zeus::CVector3f vec = GetTransform().basis[1].toVec2f().normalized();\n    zeus::CTransform xf(vec.cross(zeus::skUp), vec, zeus::skUp, GetTranslation());\n    SetTransform(xf);\n    xa33_27_ = false;\n    xa34_26_ = false;\n  } else if (msg == EStateMsg::Update) {\n    sub80255fe8(50.f, arg, (x2e0_destPos - GetTranslation()).normalized());\n    FacePlayer(10.f * arg, mgr);\n  }\n}\n\nvoid CRidley::ProjectileAttack(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n    --xcc4_;\n    xa33_31_ = true;\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::ProjectileAttack, &CPatterned::TryProjectileAttack, 2);\n    FacePlayer(arg, mgr);\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n    xa33_28_ = false;\n    xa33_31_ = false;\n  }\n}\n\nvoid CRidley::Flinch(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    SetStage3Immunity(mgr);\n    x32c_animState = EAnimState::Ready;\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::KnockBack, &CPatterned::TryKnockBack_Front, 3);\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n    SetStage3BreastVulnerability(mgr);\n    xa32_28_shotAt = false;\n  }\n}\n\nvoid CRidley::Hurled(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg != EStateMsg::Activate)\n    return;\n\n  SetStage3BreastVulnerability(mgr);\n  x450_bodyController->GetCommandMgr().DeliverCmd(CBCKnockBackCmd(GetTransform().frontVector(), pas::ESeverity::Four));\n}\n\nvoid CRidley::TelegraphAttack(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    SetStage3ThroatVulnerability(mgr);\n    x32c_animState = EAnimState::Ready;\n    xa32_25_ = true;\n    RandomSpeedUp();\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::ProjectileAttack, &CPatterned::TryProjectileAttack, 0);\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n    ResetPlasmaProjectile(mgr, true);\n    SetStage3BreastVulnerability(mgr);\n    xa32_25_ = false;\n  }\n}\n\nvoid CRidley::Jump(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n    xc88_ = (mgr.GetPlayer().GetTranslation() - GetTranslation()).magnitude() <= xae4_ ? 4 : 5;\n    x402_28_isMakingBigStrike = x568_data.x3ac_ > 0.f;\n    x504_damageDur = x568_data.x3ac_;\n    xc8c_ = x568_data.x390_;\n    RandomSpeedUp();\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::MeleeAttack, &CPatterned::TryMeleeAttack, xc88_);\n    if (x330_stateMachineState.GetTime() < 1.75f) {\n      FacePlayer(arg, mgr);\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    AddMaterial(EMaterialTypes::Orbit, EMaterialTypes::Target, mgr);\n    x402_28_isMakingBigStrike = false;\n    x504_damageDur = 0.f;\n    xc8c_ = GetContactDamage();\n    x32c_animState = EAnimState::NotReady;\n    xa34_25_ = false;\n  }\n}\n\nvoid CRidley::Explode(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    xa32_27_ = false;\n    x32c_animState = EAnimState::Ready;\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::KnockBack, &CPatterned::TryKnockBack_Front, 2);\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n  }\n}\n\nvoid CRidley::Dodge(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    const zeus::CVector3f vec = zeus::CVector3f{GetTranslation().toVec2f() - xa84_.origin.toVec2f()}.normalized();\n    float dist = zeus::clamp(0.f, 2.f * vec.dot(xa84_.rightVector()) + 1.f, 1.f);\n\n    xc84_ = 2;\n    if (dist < mgr.GetActiveRandom()->Float()) {\n      xc84_ = 3;\n    }\n    x32c_animState = EAnimState::Ready;\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::Step, &CPatterned::TryDodge, xc84_);\n    if (x32c_animState == EAnimState::Over) {\n      zeus::CVector3f vec = zeus::CVector3f(GetTranslation().toVec2f() - xa84_.origin.toVec2f()).normalized();\n      zeus::CVector3f someVec(((xa84_.origin.x() + xabc_) * vec.x()) - GetTranslation().x(),\n                              ((xa84_.origin.y() + xabc_) * vec.y()) - GetTranslation().y(),\n                              ((xa84_.origin.z() + xac0_) - GetTranslation().z()));\n      if (someVec.magnitude() > 1.f) {\n        someVec.normalize();\n      }\n\n      sub80255fe8(10.f, arg, someVec);\n    }\n\n    x450_bodyController->FaceDirection((xa84_.origin - GetTranslation()).normalized(), arg);\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n  }\n}\n\nvoid CRidley::Retreat(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::Step, &CPatterned::TryDodge, 5); // Down\n  } else {\n    x32c_animState = EAnimState::NotReady;\n  }\n}\n\nvoid CRidley::Approach(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    zeus::CVector3f direction = (GetTranslation() - xa84_.origin).normalized();\n    zeus::CVector3f destPos = xa84_.origin.toVec2f() + xab4_ * direction.toVec2f();\n    destPos.z() = (xae8_ + xa84_.origin.z()) - 1.f;\n    SetDestPos(destPos);\n    xa33_26_ = false;\n    if (xc64_aiStage == 3 && !xa34_24_) {\n      xa34_24_ = true;\n      SendScriptMsgs(EScriptObjectState::CameraPath, mgr, EScriptObjectMessage::None);\n    }\n  } else if (msg == EStateMsg::Update) {\n    sub80255fe8(50.f, arg, (x2e0_destPos - GetTranslation()).normalized());\n    FacePlayer(arg, mgr);\n  }\n}\n\nvoid CRidley::Enraged(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n    AddMaterial(EMaterialTypes::Orbit, EMaterialTypes::Target, mgr);\n    zeus::CVector3f vec = (mgr.GetPlayer().GetTranslation().toVec2f() - GetTranslation().toVec2f()).normalized();\n    mgr.AddObject(new CExplosion(\n        x98c_, mgr.AllocateUniqueId(), true, CEntityInfo(GetAreaIdAlways(), NullConnectionList), \"\"sv,\n        zeus::CTransform(vec.cross(zeus::skUp), vec, zeus::skUp, mgr.GetPlayer().GetTranslation() - (20.f * vec)), 0,\n        zeus::skOne3f, zeus::skWhite));\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::Step, &CPatterned::TryDodge, 4);\n    FacePlayer(arg, mgr);\n  } else {\n    x32c_animState = EAnimState::NotReady;\n    xa33_24_ = true;\n  }\n}\n\nvoid CRidley::SpecialAttack(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::Taunt, &CPatterned::TryTaunt, 0);\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n  }\n}\n\nvoid CRidley::Land(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    zeus::CVector3f diff = zeus::CVector3f(mgr.GetPlayer().GetTranslation().x() - xa84_.origin.x(),\n                                           mgr.GetPlayer().GetTranslation().y() - xa84_.origin.y(), 0.f);\n    SetDestPos(xa84_.origin + std::min(5.f + xab4_, diff.magnitude()) * diff.normalized());\n    x32c_animState = EAnimState::Ready;\n    AddMaterial(EMaterialTypes::Solid, mgr);\n    CMaterialList exclude = GetMaterialFilter().GetExcludeList();\n    exclude.Remove(EMaterialTypes::Solid);\n    CMaterialList include = GetMaterialFilter().GetIncludeList();\n    include.Add(EMaterialTypes::Solid);\n    SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(include, exclude));\n\n    if (xc64_aiStage == 3)\n      AddMaterial(EMaterialTypes::GroundCollider, EMaterialTypes::Solid, mgr);\n\n    x402_28_isMakingBigStrike = x568_data.x38c_ > 0.f;\n    x504_damageDur = x568_data.x38c_;\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::MeleeAttack, &CPatterned::TryMeleeAttack_TargetPos, 8);\n\n    if (x32c_animState == EAnimState::Repeat) {\n      x450_bodyController->SetLocomotionType(pas::ELocomotionType::Combat);\n    }\n    PushPlayer(mgr);\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n    x402_28_isMakingBigStrike = false;\n    x504_damageDur = 0.f;\n    xa33_30_ = false;\n    xa33_29_doStrafe = false;\n    if (mgr.GetActiveRandom()->Range(0.f, 100.f) < 50.f) {\n      if ((mgr.GetPlayer().GetTranslation() - GetTranslation()).magSquared() <= 0.f)\n        xa33_29_doStrafe = true;\n      else\n        xa33_30_ = true;\n    }\n  }\n}\nbool CRidley::Attacked(CStateManager& mgr, float arg) { return xa31_24_ && xa31_26_; }\n\nbool CRidley::TooClose(CStateManager& mgr, float arg) {\n  if (xb0c_ == 4)\n    return true;\n\n  if (xb08_ == 4)\n    return false;\n\n  zeus::CVector3f diff = mgr.GetPlayer().GetTranslation() - GetTranslation();\n  if (diff.magnitude() < x2fc_minAttackRange && 0.7f * diff.magnitude() < diff.dot(GetTransform().basis[1])) {\n    xb0c_ = 4;\n    return true;\n  }\n\n  return false;\n}\n\nbool CRidley::InRange(CStateManager& mgr, float arg) { return (GetTranslation() - x2e0_destPos).magnitude() < 2.f; }\n\nbool CRidley::ShouldAttack(CStateManager& mgr, float arg) {\n  return (xc64_aiStage == 3 && xb0c_ == 2) || (xc64_aiStage == 2 && xb04_ == 0);\n}\n\nbool CRidley::ShouldDoubleSnap(CStateManager& mgr, float arg) { return xa33_30_; }\n\nbool CRidley::ShouldTurn(CStateManager& mgr, float arg) { return xb04_ == 5; }\n\nbool CRidley::HitSomething(CStateManager& mgr, float arg) { return xa32_27_ || xc64_aiStage == 3; }\n\nbool CRidley::AttackOver(CStateManager& mgr, float arg) { return xcc4_ == 0; }\n\nbool CRidley::ShouldTaunt(CStateManager& mgr, float arg) {\n  return (xc64_aiStage == 3 && xb0c_ == 1) || (xc64_aiStage == 2 && xb04_ == 4);\n}\nbool CRidley::ShouldFire(CStateManager& mgr, float arg) { return xc64_aiStage == 2 && xb04_ == 2; }\n\nbool CRidley::ShouldDodge(CStateManager& mgr, float arg) { return xb04_ == 3; }\n\nbool CRidley::ShouldRetreat(CStateManager& mgr, float arg) { return xa34_26_; }\n\nbool CRidley::ShouldCrouch(CStateManager& mgr, float arg) { return xb04_ == 1; }\n\nbool CRidley::ShouldMove(metaforce::CStateManager& mgr, float arg) {\n  if (xb0c_ == 5) {\n    xa34_25_ = true;\n    return true;\n  }\n\n  zeus::CVector3f diffVec = mgr.GetPlayer().GetTranslation() - GetTranslation();\n  float mag = diffVec.magnitude();\n  if (x300_maxAttackRange < mag && 0.8f * mag < diffVec.dot(GetTransform().basis[1]) && sub80253960()) {\n    xa34_25_ = true;\n    xb0c_ = 5;\n    return true;\n  }\n\n  return false;\n}\nbool CRidley::ShotAt(CStateManager& mgr, float arg) { return xa32_28_shotAt; }\n\nbool CRidley::SetAIStage(CStateManager& mgr, float arg) {\n  xc64_aiStage = arg;\n  return true;\n}\n\nbool CRidley::AIStage(CStateManager& mgr, float arg) { return xc64_aiStage >= arg; }\n\nbool CRidley::ShouldStrafe(CStateManager& mgr, float arg) { return xa33_29_doStrafe; }\n\nbool CRidley::IsDizzy(CStateManager& mgr, float arg) {\n  if (xb0c_ == 3)\n    return true;\n\n  if (xb08_ != 3) {\n    zeus::CVector3f diff = mgr.GetPlayer().GetTranslation() - GetTranslation();\n    if (diff.magnitude() < x300_maxAttackRange && diff.dot(GetTransform().basis[1]) < 0.f) {\n      xb0c_ = 3;\n      return true;\n    }\n  }\n\n  return false;\n}\n\nvoid CRidley::sub80255fe8(float f1, float f2, const zeus::CVector3f& vec) {\n  xaf8_ = (f1 * (0.2f * f1)) * vec;\n  xaec_ += f2 * xaf8_;\n  if (xaec_.magnitude() > f1) {\n    xaec_ = f1 * xaec_.normalized();\n  }\n}\n\nvoid CRidley::PushPlayer(CStateManager& mgr) {\n  zeus::CVector3f posDiff = mgr.GetPlayer().GetTranslation() - GetTranslation();\n  if (posDiff.magnitude() < 8.f) {\n    float mag = mgr.GetPlayer().GetMass() * (8.f - posDiff.magnitude());\n    zeus::CVector3f impulse = mag * posDiff.toVec2f().normalized();\n    mgr.GetPlayer().ApplyImpulseWR(impulse, {});\n  }\n}\n\nvoid CRidley::FacePlayer(float arg, CStateManager& mgr) {\n  x450_bodyController->FaceDirection((mgr.GetPlayer().GetTranslation() - GetTranslation()).normalized(), arg);\n}\n\nvoid CRidley::ChooseStage3Attack(metaforce::CStateManager& mgr) {\n  xb08_ = xb0c_;\n  float fVar1 = 100.f * mgr.GetActiveRandom()->Float();\n  float fVar6 = 0.f + skFloats[xb08_].x0_;\n  if (fVar6 <= fVar1) {\n    fVar6 += skFloats[xb08_].x4_;\n    if (fVar6 <= fVar1) {\n      fVar6 += skFloats[xb08_].x8_;\n      if (fVar6 <= fVar1) {\n        fVar6 += skFloats[xb08_].xc_;\n        if (fVar6 <= fVar1) {\n          fVar6 += skFloats[xb08_].x10_;\n          if (fVar6 <= fVar1) {\n            if (fVar1 < skFloats[xb08_].x14_) {\n              xb0c_ = 5;\n            }\n          } else {\n            xb0c_ = 4;\n          }\n        } else {\n          xb0c_ = 3;\n        }\n      } else {\n        xb0c_ = 2;\n      }\n    } else {\n      xb0c_ = 1;\n    }\n  } else {\n    xb0c_ = 0;\n  }\n\n  if (xb0c_ == 5 && !sub80253960()) {\n    xb0c_ = 2;\n  }\n\n  zeus::CVector3f diff = mgr.GetPlayer().GetTranslation() - GetTranslation();\n  float diffMag = diff.magnitude();\n  float frontMag = (diff * (1.f / diffMag)).dot(GetTransform().basis[1]);\n  if ((xb0c_ == 2 && frontMag < 0.5f) || (xb0c_ == 5 && frontMag < 0.8f))\n    xb0c_ = 0;\n\n  if (frontMag < 0.f && diffMag < x300_maxAttackRange && xb08_ != 3)\n    xb0c_ = 3;\n  if (frontMag > 0.f && diffMag < x2fc_minAttackRange && xb08_ != 4)\n    xb0c_ = 4;\n}\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CRidley.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/Camera/CCameraShakeData.hpp\"\n#include \"Runtime/Character/CBoneTracking.hpp\"\n#include \"Runtime/Collision/CCollisionActorManager.hpp\"\n#include \"Runtime/Weapon/CBeamInfo.hpp\"\n#include \"Runtime/Weapon/CProjectileInfo.hpp\"\n#include \"Runtime/World/CDamageInfo.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n#include \"Runtime/World/CProjectedShadow.hpp\"\n\nnamespace metaforce {\nclass CParticleElectric;\nnamespace MP1 {\nclass CRidleyData {\n  friend class CRidley;\n  CAssetId x0_;\n  CAssetId x4_;\n  CAssetId x8_;\n  CAssetId xc_;\n  CAssetId x10_;\n  CAssetId x14_;\n  CAssetId x18_;\n  CAssetId x1c_;\n  CAssetId x20_;\n  CAssetId x24_;\n  CAssetId x28_;\n  CAssetId x2c_;\n  CAssetId x30_;\n  float x34_;\n  float x38_;\n  float x3c_;\n  float x40_;\n  CAssetId x44_;\n  CDamageInfo x48_;\n  CBeamInfo x64_;\n  u16 xa8_;\n  CAssetId xac_;\n  CDamageInfo xb0_;\n  CCameraShakeData xcc_;\n  CAssetId x1a0_;\n  CDamageInfo x1a4_;\n  CCameraShakeData x1c0_;\n  u16 x294_;\n  CDamageInfo x298_;\n  CCameraShakeData x2b4_;\n  float x388_;\n  float x38c_;\n  CDamageInfo x390_;\n  float x3ac_;\n  CDamageInfo x3b0_;\n  float x3cc_;\n  CDamageInfo x3d0_;\n  float x3ec_;\n  CAssetId x3f0_;\n  float x3f4_;\n  u16 x3f8_;\n  CDamageInfo x3fc_;\n\npublic:\n  CRidleyData(CInputStream&, u32);\n};\n\nclass CRidley : public CPatterned {\n  CRidleyData x568_data;\n  std::unique_ptr<CCollisionActorManager> x980_tailCollision;\n  std::unique_ptr<CCollisionActorManager> x984_bodyCollision;\n  TUniqueId x988_headId = kInvalidUniqueId;\n  TUniqueId x98a_breastPlateId = kInvalidUniqueId;\n  TLockedToken<CGenDescription> x98c_;\n  CModelData x998_;\n  CModelData x9e4_;\n  CSegId xa30_breastPlateSegId;\n  bool xa31_24_ : 1 = true;\n  bool xa31_25_ : 1 = true;\n  bool xa31_26_ : 1 = false;\n  bool xa31_27_ : 1 = false;\n  bool xa31_28_ : 1 = false;\n  bool xa31_29_ : 1 = false;\n  bool xa31_30_ : 1 = false;\n  bool xa31_31_ : 1 = false;\n  bool xa32_24_ : 1 = false;\n  bool xa32_25_ : 1 = false;\n  bool xa32_26_ : 1 = false;\n  bool xa32_27_ : 1 = false;\n  bool xa32_28_shotAt : 1 = false;\n  bool xa32_29_ : 1 = false;\n  bool xa32_30_ : 1 = false;\n  bool xa32_31_ : 1 = true;\n  bool xa33_24_ : 1 = false;\n  bool xa33_25_ : 1 = true;\n  bool xa33_26_ : 1 = false;\n  bool xa33_27_ : 1 = true;\n  bool xa33_28_ : 1 = false;\n  bool xa33_29_doStrafe : 1 = false;\n  bool xa33_30_ : 1 = false;\n  bool xa33_31_ : 1 = false;\n  bool xa34_24_ : 1 = false;\n  bool xa34_25_ : 1 = false;\n  bool xa34_26_ : 1 = false;\n  CModelData xa38_;\n  zeus::CTransform xa84_;\n  float xab4_ = 20.f;\n  float xab8_ = 12.f;\n  float xabc_ = 40.f;\n  float xac0_ = 10.f;\n  zeus::CAABox xac4_ = zeus::skInvertedBox;\n  float xadc_;\n  float xae0_;\n  float xae4_;\n  float xae8_;\n  zeus::CVector3f xaec_;\n  zeus::CVector3f xaf8_;\n  s32 xb04_ = 2;\n  u32 xb08_;\n  u32 xb0c_ = 0;\n  float xb10_ = 0.f;\n  float xb14_;\n  float xb18_;\n  float xb1c_;\n  float xb20_ = 0.f;\n  float xb24_ = 0.f;\n  CSegId xb28_;\n  CBoneTracking xb2c_;\n  TUniqueId xb64_plasmaProjectile = kInvalidUniqueId;\n  CProjectileInfo xb68_;\n  CSegId xb90_headSegId;\n  CSegId xb91_mouthSegId;\n  u8 xb92_;\n  u8 xb93_;\n  zeus::CTransform xb94_;\n  zeus::CVector3f xbc4_;\n  zeus::CVector3f xbd0_;\n  float xbdc_;\n  float xbe0_;\n  zeus::CVector3f xbe4_;\n  zeus::CVector3f xbf0_ = zeus::skForward;\n  zeus::CVector3f xbfc_;\n  float xc08_ = 0.f;\n  float xc0c_ = 0.f;\n  float xc10_ = 120.f;\n  CProjectileInfo xc14_;\n  CProjectileInfo xc3c_;\n  u32 xc64_aiStage = 2;\n  zeus::CVector3f xc68_;\n  u32 xc74_ = 0;\n  float xc78_ = 0.f;\n  float xc7c_ = 0.f;\n  float xc80_ = 0.f;\n  u32 xc84_;\n  u32 xc88_ = 4;\n  CDamageInfo xc8c_;\n  CSfxHandle xca8_;\n  CSfxHandle xcac_ = 0;\n  u32 xcb0_ = 0;\n  u32 xcb4_ = 0;\n  float xcb8_ = 0.f;\n  float xcbc_ = 0.f;\n  u32 xcc0_ = 1;\n  u32 xcc4_ = 1;\n  float xcc8_ = 0.f;\n  float xccc_;\n  TLockedToken<CElectricDescription> xcd0_;\n  std::unique_ptr<CParticleElectric> xce0_;\n  std::vector<CSegId> xce4_wingBoneIds; // was rstl::reserved_vector<CSegId, 30>\n  float xd08_;\n  CSfxHandle xd0c_;\n  std::unique_ptr<CProjectedShadow> xd10_;\n  u32 xd14_;\n\n  void SetupCollisionActorManager(CStateManager& mgr);\n  void SetupCollisionActors(CStateManager& mgr);\n\n  void SetStage3BreastVulnerability(CStateManager& mgr);\n  void ActivateWingElectricity(float f1, bool r4);\n  void UpdateFlight(float dt);\n  void ConstrainToHenge(float dt);\n  void UpdateBeam(float dt, CStateManager& mgr);\n  void UpdateWingElectricity(float dt, CStateManager& mgr);\n  void ResetPlasmaProjectile(CStateManager& mgr, bool b1);\n  void sub80255fe8(float f1, float f2, const zeus::CVector3f& vec);\n  void PushPlayer(CStateManager& mgr);\n  void SetStage3ThroatVulnerability(CStateManager& mgr);\n  void ChooseStage2Attack(metaforce::CStateManager& mgr);\n  void SetStage3Immunity(CStateManager& mgr);\n  void FirePlasma(CStateManager& mgr);\n  void FacePlayer(float arg, CStateManager& mgr);\n  void SetSphereCollisionRadius(float f1, CStateManager& mgr);\n  void RandomSlowDown() {}\n  void RandomSpeedUp() {\n    if (!xa31_24_)\n      x3b4_speed = 1.2f;\n  }\n\n  void ChooseStage3Attack(metaforce::CStateManager& mgr);\n  bool sub80253960() {\n    const float mag = ((GetTranslation() + ((0.5f * xae4_) * GetTransform().frontVector())) - xa84_.origin).magnitude();\n    return mag < 0.5f * (xab4_ + xabc_);\n  }\n\npublic:\n  DEFINE_PATTERNED(Ridley);\n  CRidley(TUniqueId, std::string_view, const CEntityInfo&, const zeus::CTransform&, CModelData&&, const CPatternedInfo&,\n          const CActorParameters&, CInputStream&, u32);\n\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) override;\n  void Think(float dt, CStateManager& mgr) override;\n  void PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) override;\n  void Render(CStateManager& mgr) override;\n  void AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) override;\n  zeus::CAABox GetSortingBounds(const CStateManager&) const override { return GetBoundingBox(); }\n  const CDamageVulnerability* GetDamageVulnerability() const override {\n    return &CDamageVulnerability::ImmuneVulnerabilty();\n  }\n\n  zeus::CVector3f GetAimPosition(const CStateManager& mgr, float dt) const override {\n    return GetLctrTransform((xc64_aiStage == 3 && !xa32_28_shotAt) ? xb90_headSegId : xa30_breastPlateSegId).origin;\n  }\n\n  float GetGravityConstant() const override { return 50.f; }\n  EWeaponCollisionResponseTypes GetCollisionResponseType(const zeus::CVector3f&, const zeus::CVector3f&,\n                                                         const CWeaponMode&, EProjectileAttrib) const override {\n    return EWeaponCollisionResponseTypes::EnemyNormal;\n  }\n\n  void DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) override;\n  void Patrol(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Dead(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Generate(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Attack(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void LoopedAttack(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void JumpBack(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void DoubleSnap(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void CoverAttack(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Crouch(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void FadeOut(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Taunt(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Flee(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Lurk(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void ProjectileAttack(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Flinch(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Hurled(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void TelegraphAttack(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Jump(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Explode(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Dodge(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Retreat(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Approach(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Enraged(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void SpecialAttack(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Land(CStateManager& mgr, EStateMsg msg, float arg) override;\n  bool Attacked(CStateManager& mgr, float arg) override;\n  bool TooClose(CStateManager& mgr, float arg) override;\n  bool InRange(CStateManager& mgr, float arg) override;\n  bool ShouldAttack(CStateManager& mgr, float arg) override;\n  bool ShouldDoubleSnap(CStateManager& mgr, float arg) override;\n  bool ShouldTurn(CStateManager& mgr, float arg) override;\n  bool HitSomething(CStateManager& mgr, float arg) override;\n  bool AttackOver(CStateManager& mgr, float arg) override;\n  bool ShouldTaunt(CStateManager& mgr, float arg) override;\n  bool ShouldFire(CStateManager& mgr, float arg) override;\n  bool ShouldDodge(CStateManager& mgr, float arg) override;\n  bool ShouldRetreat(CStateManager& mgr, float arg) override;\n  bool ShouldCrouch(CStateManager& mgr, float arg) override;\n  bool ShouldMove(CStateManager& mgr, float arg) override;\n  bool ShotAt(CStateManager& mgr, float arg) override;\n  bool SetAIStage(CStateManager& mgr, float arg) override;\n  bool AIStage(CStateManager& mgr, float arg) override;\n  bool ShouldStrafe(CStateManager& mgr, float arg) override;\n  bool IsDizzy(CStateManager& mgr, float arg) override;\n};\n} // namespace MP1\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/MP1/World/CRipper.cpp",
    "content": "#include \"Runtime/MP1/World/CRipper.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Collision/CCollidableOBBTreeGroup.hpp\"\n#include \"Runtime/Collision/CGameCollision.hpp\"\n#include \"Runtime/Weapon/CPlayerGun.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CScriptGrapplePoint.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce::MP1 {\nCRipper::CRipper(TUniqueId uid, std::string_view name, EFlavorType type, const CEntityInfo& info,\n                 const zeus::CTransform& xf, CModelData&& mData, const CPatternedInfo& pInfo,\n                 const CActorParameters& actParms, const CGrappleParameters& grappleParms)\n: CPatterned(ECharacter::Ripper, uid, name, type, info, xf, std::move(mData), pInfo, EMovementType::Flyer,\n             EColliderType::One, EBodyType::Flyer, actParms, EKnockBackVariant::Medium)\n, x568_grappleParams(grappleParms) {\n  SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(\n      {EMaterialTypes::Solid},\n      {EMaterialTypes::NoStaticCollision, EMaterialTypes::NoPlatformCollision, EMaterialTypes::Platform}));\n  x460_knockBackController.SetAutoResetImpulse(false);\n  x460_knockBackController.SetAnimationStateRange(EKnockBackAnimationState::Flinch,\n                                                  EKnockBackAnimationState::KnockBack);\n}\n\nvoid CRipper::Think(float dt, CStateManager& mgr) {\n  if (!GetActive())\n    return;\n\n  ProcessGrapplePoint(mgr);\n  const CPlayer& pl = mgr.GetPlayer();\n  CGrappleArm::EArmState armState = pl.GetPlayerGun()->GetGrappleArm().GetAnimState();\n  if (x598_grapplePoint != kInvalidUniqueId && pl.GetOrbitTargetId() == x598_grapplePoint &&\n      pl.GetGrappleState() != CPlayer::EGrappleState::None) {\n    if (pl.GetGrappleState() != CPlayer::EGrappleState::Firing && (armState > CGrappleArm::EArmState::Three)) {\n      Stop();\n      if (!x59c_24_muted) {\n        SetMuted(true);\n        x59c_24_muted = true;\n      }\n    } else {\n      CPatterned::Think(dt, mgr);\n    }\n  } else {\n    CPatterned::Think(dt, mgr);\n    if (x59c_24_muted) {\n      SetMuted(false);\n      x59c_24_muted = false;\n    }\n  }\n}\n\nvoid CRipper::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  CPatterned::AcceptScriptMsg(msg, uid, mgr);\n\n  switch (msg) {\n  case EScriptObjectMessage::Deleted:\n  case EScriptObjectMessage::Deactivate: {\n    RemoveGrapplePoint(mgr);\n    RemovePlatform(mgr);\n    break;\n  }\n  case EScriptObjectMessage::Activate: {\n    AddGrapplePoint(mgr);\n    AddPlatform(mgr);\n    break;\n  }\n  case EScriptObjectMessage::Registered: {\n    x450_bodyController->Activate(mgr);\n    AddMaterial(EMaterialTypes::Immovable, mgr);\n    RemoveMaterial(EMaterialTypes::Solid, mgr);\n    if (x3fc_flavor == EFlavorType::One) {\n      AddGrapplePoint(mgr);\n      RemoveMaterial(EMaterialTypes::Orbit, mgr);\n    }\n\n    AddPlatform(mgr);\n    break;\n  }\n  default:\n    break;\n  }\n}\n\nvoid CRipper::KnockBack(const zeus::CVector3f& dir, CStateManager& mgr, const CDamageInfo& dInfo, EKnockBackType kb,\n                        bool inDeferred, float mag) {\n  CPatterned::KnockBack(dir, mgr, dInfo, kb, inDeferred, mag);\n  x450_bodyController->GetCommandMgr().DeliverCmd(CBCKnockBackCmd(-dir, pas::ESeverity::One));\n}\n\nvoid CRipper::Patrol(CStateManager& mgr, EStateMsg msg, float arg) {\n  x450_bodyController->GetCommandMgr().SetSteeringBlendMode(ESteeringBlendMode::FullSpeed);\n  x450_bodyController->GetCommandMgr().SetSteeringSpeedRange(1.f, 1.f);\n  CPatterned::Patrol(mgr, msg, arg);\n}\n\nvoid CRipper::ProcessGrapplePoint(CStateManager& mgr) {\n  if (x3fc_flavor != EFlavorType::One || x598_grapplePoint == kInvalidUniqueId)\n    return;\n\n  if (TCastToPtr<CScriptGrapplePoint> gp = mgr.ObjectById(x598_grapplePoint)) {\n    gp->SetTransform(GetTransform());\n  }\n}\n\nvoid CRipper::AddGrapplePoint(CStateManager& mgr) {\n  if (x598_grapplePoint != kInvalidUniqueId)\n    return;\n\n  x598_grapplePoint = mgr.AllocateUniqueId();\n  mgr.AddObject(new CScriptGrapplePoint(x598_grapplePoint, \"RipperGrapplePoint\"sv,\n                                        CEntityInfo(GetAreaIdAlways(), NullConnectionList), GetTransform(), true,\n                                        x568_grappleParams));\n}\n\nvoid CRipper::RemoveGrapplePoint(CStateManager& mgr) {\n  if (x598_grapplePoint == kInvalidUniqueId)\n    return;\n  mgr.FreeScriptObject(x598_grapplePoint);\n}\n\nvoid CRipper::AddPlatform(CStateManager& mgr) {\n  if (x59a_platformId != kInvalidUniqueId)\n    return;\n\n  x59a_platformId = mgr.AllocateUniqueId();\n  const zeus::CAABox bounds = GetModelData()->GetBounds(GetTransform().getRotation());\n\n  const auto& platform = new CRipperControlledPlatform(x59a_platformId, GetUniqueId(), \"Ripper Controlled Platform\"sv,\n                                                       CEntityInfo(GetAreaIdAlways(), NullConnectionList),\n                                                       GetTransform(), bounds, GetActive(), {});\n  mgr.AddObject(platform);\n  platform->AddMaterial(EMaterialTypes::ProjectilePassthrough);\n}\n\nvoid CRipper::RemovePlatform(CStateManager& mgr) {\n  if (x59a_platformId == kInvalidUniqueId)\n    return;\n  mgr.FreeScriptObject(x59a_platformId);\n  x59a_platformId = kInvalidUniqueId;\n}\n\nCRipperControlledPlatform::CRipperControlledPlatform(\n    TUniqueId uid, TUniqueId owner, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n    const zeus::CAABox& bounds, bool active, const std::optional<TLockedToken<CCollidableOBBTreeGroup>>& colTree)\n: CScriptPlatform(uid, name, info, xf, CModelData::CModelDataNull(), CActorParameters::None(), bounds, 0.f, false, 1.f,\n                  active, CHealthInfo(FLT_MAX, 10.f), CDamageVulnerability::ImmuneVulnerabilty(), colTree, false, 1, 1)\n, x358_owner(owner)\n, x35c_yaw(GetYaw()) {}\n\nconstexpr float RCP_2PI = 0.15915494f;\nconstexpr float M_2PI = 6.2831855f;\n\nzeus::CQuaternion CRipperControlledPlatform::Move(float arg, CStateManager& mgr) {\n  if (const auto* actor = static_cast<CActor*>(mgr.ObjectById(x358_owner))) {\n    MoveToWR(GetTranslation() + (actor->GetTranslation() - GetTranslation()), arg);\n    float yawDiff = actor->GetYaw() - x35c_yaw;\n    float zRot = yawDiff - static_cast<float>(static_cast<int>(yawDiff * RCP_2PI)) * M_2PI;\n    if (zRot < 0.f) {\n      zRot += M_2PI;\n    }\n    if (zRot > M_PIF) {\n      zRot -= M_2PI;\n    }\n    const auto quat = zeus::CQuaternion::fromAxisAngle({0.0f, 0.0f, 1.0f}, zRot);\n    RotateToOR(quat, arg);\n\n    EntityList nearList;\n    EntityList filteredNearList;\n    mgr.BuildColliderList(nearList, *this, GetMotionVolume(arg));\n    for (const auto& id : nearList) {\n      if (!IsRider(id) && !IsSlave(id)) {\n        filteredNearList.push_back(id);\n      }\n    }\n\n    xf8_24_movable = true;\n    CGameCollision::Move(mgr, *this, arg, &filteredNearList);\n    xf8_24_movable = false;\n    x35c_yaw = GetYaw();\n    return quat;\n  }\n  return {};\n}\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CRipper.hpp",
    "content": "#pragma once\n\n#include \"Runtime/World/CGrappleParameters.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n#include \"Runtime/World/CScriptPlatform.hpp\"\n\nnamespace metaforce::MP1 {\n\nclass CRipper : public CPatterned {\n  CGrappleParameters x568_grappleParams;\n  TUniqueId x598_grapplePoint = kInvalidUniqueId;\n  TUniqueId x59a_platformId = kInvalidUniqueId;\n  bool x59c_24_muted : 1 = false;\n\n  void ProcessGrapplePoint(CStateManager&);\n  void AddGrapplePoint(CStateManager&);\n  void RemoveGrapplePoint(CStateManager&);\n  void AddPlatform(CStateManager&);\n  void RemovePlatform(CStateManager&);\n\npublic:\n  DEFINE_PATTERNED(Ripper);\n  CRipper(TUniqueId uid, std::string_view name, EFlavorType type, const CEntityInfo& info, const zeus::CTransform& xf,\n          CModelData&& mData, const CPatternedInfo& pInfo, const CActorParameters& actParms,\n          const CGrappleParameters& grappleParms);\n\n  void Think(float dt, CStateManager& mgr) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) override;\n  EWeaponCollisionResponseTypes GetCollisionResponseType(const zeus::CVector3f&, const zeus::CVector3f&,\n                                                         const CWeaponMode& wp, EProjectileAttrib) const override {\n    if (!GetDamageVulnerability()->WeaponHits(wp, false))\n      return EWeaponCollisionResponseTypes::Unknown82;\n\n    return EWeaponCollisionResponseTypes::Unknown32;\n  }\n\n  void KnockBack(const zeus::CVector3f&, CStateManager&, const CDamageInfo&, EKnockBackType, bool, float) override;\n  void Patrol(CStateManager&, EStateMsg, float) override;\n  bool PathOver(CStateManager&, float) override { return false; } /* They never give you up, or let you down */\n};\n\nclass CRipperControlledPlatform : public CScriptPlatform {\n  TUniqueId x358_owner;\n  float x35c_yaw;\n\npublic:\n  DEFINE_ENTITY\n  CRipperControlledPlatform(TUniqueId, TUniqueId, std::string_view, const CEntityInfo&, const zeus::CTransform&,\n                            const zeus::CAABox&, bool, const std::optional<TLockedToken<CCollidableOBBTreeGroup>>&);\n\n  zeus::CQuaternion Move(float, CStateManager&) override;\n};\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CScriptContraption.cpp",
    "content": "#include \"Runtime/MP1/World/CScriptContraption.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Character/CInt32POINode.hpp\"\n#include \"Runtime/Weapon/CFlameInfo.hpp\"\n#include \"Runtime/Weapon/CFlameThrower.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nMP1::CScriptContraption::CScriptContraption(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                                          const zeus::CTransform& xf, CModelData&& mData, const zeus::CAABox& aabox,\n                                          const CMaterialList& matList, float mass, float zMomentum,\n                                          const CHealthInfo& hInfo, const CDamageVulnerability& dVuln,\n                                          const CActorParameters& aParams, CAssetId part, const CDamageInfo& dInfo,\n                                          bool active)\n: CScriptActor(uid, name, info, xf, std::move(mData), aabox, mass, zMomentum, matList, hInfo, dVuln, aParams, false,\n               active, 0, 1.f, false, false, false, false)\n, x300_flameThrowerGen(g_SimplePool->GetObj(\"FlameThrower\"))\n, x308_flameFxId(part)\n, x30c_dInfo(dInfo) {}\n\nvoid MP1::CScriptContraption::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid MP1::CScriptContraption::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  bool curActive = GetActive();\n  switch (msg) {\n  case EScriptObjectMessage::Registered:\n    AddMaterial(EMaterialTypes::ScanPassthrough, mgr);\n    break;\n  case EScriptObjectMessage::Deleted:\n    for (const std::pair<TUniqueId, std::string>& p : x2e8_children)\n      mgr.FreeScriptObject(p.first);\n    x2e8_children.clear();\n    break;\n  case EScriptObjectMessage::SetToZero:\n    ResetFlameThrowers(mgr);\n    break;\n  default:\n    break;\n  }\n\n  CScriptActor::AcceptScriptMsg(msg, uid, mgr);\n  if (curActive != GetActive() && !GetActive()) {\n    ResetFlameThrowers(mgr);\n    CActor::RemoveEmitter();\n  }\n}\n\nvoid MP1::CScriptContraption::Think(float dt, CStateManager& mgr) {\n  CScriptActor::Think(dt, mgr);\n\n  for (const auto& [uid, name] : x2e8_children) {\n    auto* act = static_cast<CFlameThrower*>(mgr.ObjectById(uid));\n\n    if (act && act->GetActive()) {\n      act->SetTransform(x34_transform * GetScaledLocatorTransform(name), dt);\n    }\n  }\n}\n\nvoid MP1::CScriptContraption::ResetFlameThrowers(CStateManager& mgr) {\n  for (const std::pair<TUniqueId, std::string>& uid : x2e8_children) {\n    CFlameThrower* act = static_cast<CFlameThrower*>(mgr.ObjectById(uid.first));\n    if (act && act->GetParticlesActive())\n      act->Reset(mgr, false);\n  }\n}\n\nvoid MP1::CScriptContraption::DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType evType,\n                                             float dt) {\n  if (evType == EUserEventType::DamageOff) {\n    ResetFlameThrowers(mgr);\n  } else if (evType == EUserEventType::DamageOn) {\n    CFlameThrower* fl = CreateFlameThrower(node.GetLocatorName(), mgr);\n    if (fl && !fl->GetParticlesActive())\n      fl->Fire(GetTransform(), mgr, false);\n  } else\n    CActor::DoUserAnimEvent(mgr, node, evType, dt);\n}\n\nCFlameThrower* MP1::CScriptContraption::CreateFlameThrower(std::string_view name, CStateManager& mgr) {\n  const auto it =\n      std::find_if(x2e8_children.cbegin(), x2e8_children.cend(), [&name](const auto& p) { return p.second == name; });\n\n  if (it != x2e8_children.cend()) {\n    return static_cast<CFlameThrower*>(mgr.ObjectById(it->first));\n  }\n\n  const TUniqueId id = mgr.AllocateUniqueId();\n  const CFlameInfo flameInfo(6, 6, x308_flameFxId, 20, 0.5f, 1.f, 1.f);\n  auto* ret = new CFlameThrower(x300_flameThrowerGen, name, EWeaponType::Missile, flameInfo, zeus::CTransform(),\n                                EMaterialTypes::CollisionActor, x30c_dInfo, id, GetAreaId(), GetUniqueId(),\n                                EProjectileAttrib::None, CAssetId(), -1, CAssetId());\n\n  x2e8_children.emplace_back(id, name);\n\n  mgr.AddObject(ret);\n  return ret;\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/MP1/World/CScriptContraption.hpp",
    "content": "#pragma once\n\n#include <string_view>\n#include <vector>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/World/CDamageInfo.hpp\"\n#include \"Runtime/World/CScriptActor.hpp\"\n#include \"Runtime/Particle/CWeaponDescription.hpp\"\n\nnamespace metaforce {\nclass CFlameThrower;\nclass CWeaponDescription;\nnamespace MP1 {\nclass CScriptContraption : public CScriptActor {\n  /* AKA Why Zoid?!?!?!? */\n  std::vector<std::pair<TUniqueId, std::string>> x2e8_children;\n  TToken<CWeaponDescription> x300_flameThrowerGen;\n  CAssetId x308_flameFxId;\n  CDamageInfo x30c_dInfo;\n\npublic:\n  DEFINE_ENTITY\n  CScriptContraption(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                    CModelData&& mData, const zeus::CAABox& aabox, const CMaterialList& matList, float mass,\n                    float zMomentum, const CHealthInfo& hInfo, const CDamageVulnerability& dVuln,\n                    const CActorParameters& aParams, CAssetId part, const CDamageInfo& dInfo, bool active);\n\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) override;\n  void Think(float dt, CStateManager& mgr) override;\n  void DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType evType, float dt) override;\n  CFlameThrower* CreateFlameThrower(std::string_view name, CStateManager& mgr);\n  void ResetFlameThrowers(CStateManager& mgr);\n};\n} // namespace MP1\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/MP1/World/CSeedling.cpp",
    "content": "#include \"Runtime/MP1/World/CSeedling.hpp\"\n\n#include <array>\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CGameArea.hpp\"\n#include \"Runtime/World/CPatternedInfo.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce::MP1 {\nnamespace {\nconstexpr std::array<std::array<std::string_view, 6>, 2> skNeedleLocators{{\n    {\n        \"A_spike1_LCTR_SDK\",\n        \"A_spike2_LCTR_SDK\",\n        \"A_spike3_LCTR_SDK\",\n        \"A_spike4_LCTR_SDK\",\n        \"A_spike5_LCTR_SDK\",\n        \"A_spike6_LCTR_SDK\",\n    },\n    {\n        \"B_spike1_LCTR_SDK\",\n        \"B_spike2_LCTR_SDK\",\n        \"B_spike3_LCTR_SDK\",\n        \"B_spike4_LCTR_SDK\",\n        \"B_spike5_LCTR_SDK\",\n        \"B_spike6_LCTR_SDK\",\n    },\n}};\n} // Anonymous namespace\n\nCSeedling::CSeedling(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                     CModelData&& mData, const CPatternedInfo& pInfo, const CActorParameters& actParms,\n                     CAssetId needleId, CAssetId weaponId, const CDamageInfo& dInfo1, const CDamageInfo& dInfo2,\n                     float f1, float f2, float f3, float f4)\n: CWallWalker(ECharacter::Seedling, uid, name, EFlavorType::Zero, info, xf, std::move(mData), pInfo,\n              EMovementType::Ground, EColliderType::Zero, EBodyType::WallWalker, actParms, f1, f2,\n              EKnockBackVariant::Small, f3, EWalkerType::Seedling, f4, false)\n, x5d8_searchPath(nullptr, 1, pInfo.GetPathfindingIndex(), 1.f, 1.f)\n, x6bc_spikeData(std::make_unique<CModelData>(CStaticRes(needleId, GetModelData()->GetScale())))\n, x6c0_projectileInfo(weaponId, dInfo1)\n, x6e8_deathDamage(dInfo2) {\n  x6c0_projectileInfo.Token().Lock();\n  CreateShadow(false);\n  MakeThermalColdAndHot();\n}\n\nvoid CSeedling::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CSeedling::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  CPatterned::AcceptScriptMsg(msg, uid, mgr);\n\n  if (msg == EScriptObjectMessage::Activate) {\n    x5d6_27_disableMove = false;\n    TUniqueId id = GetWaypointForState(mgr, EScriptObjectState::Patrol, EScriptObjectMessage::Follow);\n    if (id != kInvalidUniqueId)\n      x2dc_destObj = id;\n  } else if (msg == EScriptObjectMessage::Registered) {\n    x450_bodyController->Activate(mgr);\n    x704_modelBounds = GetModelData()->GetBounds();\n  } else if (msg == EScriptObjectMessage::InitializedInArea) {\n    x5d8_searchPath.SetArea(mgr.GetWorld()->GetAreaAlways(GetAreaIdAlways())->GetPostConstructed()->x10bc_pathArea);\n  }\n}\n\nvoid CSeedling::Think(float dt, CStateManager& mgr) {\n  if (!GetActive())\n    return;\n\n  ++x5d4_thinkCounter;\n  x5d6_26_playerObstructed = false;\n  const CGameArea* area = mgr.GetWorld()->GetAreaAlways(GetAreaIdAlways());\n  CGameArea::EOcclusionState occlusionState = CGameArea::EOcclusionState::Occluded;\n  if (area && area->IsPostConstructed())\n    occlusionState = area->GetPostConstructed()->x10dc_occlusionState;\n\n  if (occlusionState == CGameArea::EOcclusionState::Occluded)\n    x5d6_26_playerObstructed = true;\n\n  if (!x5d6_26_playerObstructed) {\n    zeus::CVector3f playerPos = mgr.GetPlayer().GetTranslation();\n    float distance = (playerPos - GetTranslation()).magnitude();\n    if (distance > x5c4_playerObstructionMinDist) {\n      zeus::CVector3f direction = (playerPos - GetTranslation()).normalized();\n      CRayCastResult result =\n          mgr.RayStaticIntersection(playerPos, direction, distance, CMaterialFilter::skPassEverything);\n      if (result.IsValid())\n        x5d6_26_playerObstructed = true;\n    }\n  }\n\n  if (x5d6_26_playerObstructed)\n    xf8_24_movable = false;\n\n  xf8_24_movable = !x5d6_24_alignToFloor;\n  CWallWalker::Think(dt, mgr);\n\n  if (!x5d6_25_hasAlignSurface && x450_bodyController->GetPercentageFrozen() < 0.00001f && x5d6_24_alignToFloor)\n    AlignToFloor(mgr, x590_colSphere.GetSphere().radius, GetTranslation() + (2.f * (dt * GetVelocity())), dt);\n\n  if (x71c_attackCoolOff > 0.f)\n    x71c_attackCoolOff -= dt;\n}\n\nvoid CSeedling::Render(CStateManager& mgr) {\n  if (x400_25_alive && x6bc_spikeData) {\n    const size_t index = x722_24_renderOnlyClusterA ? 0 : size_t(x722_25_curNeedleCluster);\n    CModelFlags flags{0, 0, 3, zeus::skWhite};\n    for (const auto& sv : skNeedleLocators[index]) {\n      x6bc_spikeData->Render(mgr, GetLctrTransform(sv), x90_actorLights.get(), flags);\n    }\n  }\n\n  CWallWalker::Render(mgr);\n}\n\nvoid CSeedling::DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) {\n  if (type == EUserEventType::Projectile)\n    LaunchNeedles(mgr);\n  else if (type == EUserEventType::BeginAction)\n    x722_24_renderOnlyClusterA = true;\n  else\n    CPatterned::DoUserAnimEvent(mgr, node, type, dt);\n}\n\nstd::optional<zeus::CAABox> CSeedling::GetTouchBounds() const {\n  return x704_modelBounds.getTransformedAABox(GetTransform());\n}\n\nvoid CSeedling::Touch(CActor& act, CStateManager& mgr) {\n  if (x400_25_alive) {\n    if (TCastToPtr<CPlayer> pl = act)\n      MassiveDeath(mgr);\n  }\n\n  CPatterned::Touch(act, mgr);\n}\n\nvoid CSeedling::Patrol(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n    x5d6_24_alignToFloor = true;\n    x150_momentum.zeroOut();\n    x5d6_25_hasAlignSurface = false;\n    xf8_24_movable = false;\n\n    TUniqueId id = (x720_prevObj != kInvalidUniqueId\n                        ? x720_prevObj\n                        : GetWaypointForState(mgr, EScriptObjectState::Patrol, EScriptObjectMessage::Follow));\n\n    if (id != kInvalidUniqueId)\n      x2dc_destObj = id;\n  } else if (msg == EStateMsg::Update) {\n    UpdateWPDestination(mgr);\n    zeus::CVector3f upVec = GetTransform().upVector();\n    x450_bodyController->GetCommandMgr().DeliverCmd(\n        CBCLocomotionCmd(ProjectVectorToPlane((x2e0_destPos - GetTranslation()).normalized(), upVec), {}, 0.f));\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(\n        ProjectVectorToPlane(ProjectVectorToPlane(x45c_steeringBehaviors.Seek(*this, x2e0_destPos), upVec), upVec), {},\n        1.f));\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(1.f * GetTransform().frontVector(), {}, 0.f));\n  } else if (msg == EStateMsg::Deactivate) {\n    x720_prevObj = x2dc_destObj;\n  }\n}\n\nvoid CSeedling::Active(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate)\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Lurk);\n  CPatterned::Patrol(mgr, msg, arg);\n}\n\nvoid CSeedling::Enraged(CStateManager&, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate)\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Internal8);\n}\n\nvoid CSeedling::ProjectileAttack(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate)\n    x32c_animState = EAnimState::Ready;\n  else if (msg == EStateMsg::Update)\n    TryCommand(mgr, pas::EAnimationState::ProjectileAttack, &CPatterned::TryProjectileAttack, 0);\n  else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n    x71c_attackCoolOff = (x300_maxAttackRange * mgr.GetActiveRandom()->Float()) + x304_averageAttackTime;\n  }\n}\n\nvoid CSeedling::Generate(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate)\n    x32c_animState = EAnimState::Ready;\n  else if (msg == EStateMsg::Update)\n    TryCommand(mgr, pas::EAnimationState::Generate, &CPatterned::TryGenerate, 0);\n}\n\nbool CSeedling::ShouldAttack(CStateManager& mgr, float) {\n  if (x71c_attackCoolOff > 0.f)\n    return false;\n\n  return mgr.CanCreateProjectile(GetUniqueId(), EWeaponType::AI, 6);\n}\n\nvoid CSeedling::LaunchNeedles(CStateManager& mgr) {\n  const auto& needleLocators = skNeedleLocators[size_t(x722_25_curNeedleCluster)];\n  for (const auto& needle : needleLocators) {\n    LaunchProjectile(GetLctrTransform(needle), mgr, int(needleLocators.size()), EProjectileAttrib::None, true, {},\n                     0xFFFF, false, GetModelData()->GetScale());\n  }\n\n  x722_25_curNeedleCluster = !x722_25_curNeedleCluster;\n  x722_24_renderOnlyClusterA = false;\n}\n\nvoid CSeedling::MassiveDeath(CStateManager& mgr) {\n  if (x400_25_alive) {\n    mgr.ApplyDamageToWorld(GetUniqueId(), *this, GetTranslation(), x6e8_deathDamage,\n                           CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}));\n    LaunchNeedles(mgr);\n  }\n  CPatterned::MassiveDeath(mgr);\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CSeedling.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <string>\n\n#include \"Runtime/Weapon/CProjectileInfo.hpp\"\n#include \"Runtime/World/CPathFindSearch.hpp\"\n#include \"Runtime/World/CWallWalker.hpp\"\n\n#include <zeus/CAABox.hpp>\n\nnamespace metaforce::MP1 {\nclass CSeedling : public CWallWalker {\n  CPathFindSearch x5d8_searchPath;\n  std::unique_ptr<CModelData> x6bc_spikeData;\n  CProjectileInfo x6c0_projectileInfo;\n  CDamageInfo x6e8_deathDamage;\n  zeus::CAABox x704_modelBounds = zeus::skNullBox;\n  float x71c_attackCoolOff = 0.f;\n  TUniqueId x720_prevObj = kInvalidUniqueId;\n  bool x722_24_renderOnlyClusterA : 1 = true;\n  bool x722_25_curNeedleCluster : 1 = false;\n  void LaunchNeedles(CStateManager&);\n\npublic:\n  DEFINE_PATTERNED(Seedling);\n  CSeedling(TUniqueId, std::string_view, const CEntityInfo&, const zeus::CTransform&, CModelData&&,\n            const CPatternedInfo&, const CActorParameters&, CAssetId, CAssetId, const CDamageInfo&, const CDamageInfo&,\n            float, float, float, float);\n\n  void Accept(IVisitor&) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void Think(float, CStateManager&) override;\n  void Render(CStateManager&) override;\n  void DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) override;\n  CProjectileInfo* GetProjectileInfo() override { return &x6c0_projectileInfo; }\n  std::optional<zeus::CAABox> GetTouchBounds() const override;\n  void Touch(CActor&, CStateManager&) override;\n  CPathFindSearch* GetSearchPath() override { return &x5d8_searchPath; }\n  void Patrol(CStateManager&, EStateMsg, float) override;\n  void Active(CStateManager&, EStateMsg, float) override;\n  void Enraged(CStateManager&, EStateMsg, float) override;\n  void ProjectileAttack(CStateManager&, EStateMsg, float) override;\n  void Generate(CStateManager&, EStateMsg, float) override;\n  bool ShouldAttack(CStateManager&, float) override;\n  void MassiveDeath(CStateManager&) override;\n};\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CShockWave.cpp",
    "content": "#include \"Runtime/MP1/World/CShockWave.hpp\"\n\n#include \"Runtime/Collision/CCollisionActor.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/World/CGameLight.hpp\"\n#include \"Runtime/World/CHUDBillboardEffect.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce::MP1 {\nCShockWave::CShockWave(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                       TUniqueId parent, const CShockWaveInfo& data, float minActiveTime, float knockback)\n: CActor(uid, true, name, info, xf, CModelData::CModelDataNull(), {EMaterialTypes::Projectile},\n         CActorParameters::None(), kInvalidUniqueId)\n, xe8_parentId(parent)\n, xec_damageInfo(data.GetDamageInfo())\n, x108_elementGenDesc(g_SimplePool->GetObj({SBIG('PART'), data.GetParticleDescId()}))\n, x110_elementGen(std::make_unique<CElementGen>(x108_elementGenDesc))\n, x114_data(data)\n, x150_radius(data.GetInitialRadius())\n, x154_expansionSpeed(data.GetInitialExpansionSpeed())\n, x15c_minActiveTime(minActiveTime)\n, x160_knockback(knockback) {\n  if (data.GetWeaponDescId().IsValid()) {\n    x974_electricDesc = g_SimplePool->GetObj({SBIG('ELSC'), data.GetWeaponDescId()});\n  }\n  x110_elementGen->SetParticleEmission(true);\n  x110_elementGen->SetOrientation(GetTransform().getRotation());\n  x110_elementGen->SetGlobalTranslation(GetTranslation());\n  xe6_27_thermalVisorFlags = 2;\n}\n\nvoid CShockWave::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CShockWave::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  if (msg == EScriptObjectMessage::Registered) {\n    if (x110_elementGen->SystemHasLight()) {\n      x980_id2 = mgr.AllocateUniqueId();\n      mgr.AddObject(new CGameLight(x980_id2, GetAreaIdAlways(), GetActive(), \"ShockWaveLight_\" + x10_name,\n                                   GetTransform(), GetUniqueId(), x110_elementGen->GetLight(),\n                                   x114_data.GetParticleDescId().Value(), 1, 0.f));\n    }\n  } else if (msg == EScriptObjectMessage::Deleted) {\n    mgr.FreeScriptObject(x980_id2);\n    x980_id2 = kInvalidUniqueId;\n  }\n  CActor::AcceptScriptMsg(msg, uid, mgr);\n  mgr.SendScriptMsgAlways(x980_id2, uid, msg);\n}\n\nvoid CShockWave::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) {\n  CActor::AddToRenderer(frustum, mgr);\n  g_Renderer->AddParticleGen(*x110_elementGen);\n}\n\nstd::optional<zeus::CAABox> CShockWave::GetTouchBounds() const {\n  if (x150_radius <= 0.f) {\n    return std::nullopt;\n  }\n  return zeus::CAABox({-x150_radius, -x150_radius, 0.f}, {x150_radius, x150_radius, 1.f})\n      .getTransformedAABox(GetTransform());\n}\n\nvoid CShockWave::Render(CStateManager& mgr) {\n  CActor::Render(mgr);\n  x110_elementGen->Render();\n}\n\nvoid CShockWave::Think(float dt, CStateManager& mgr) {\n  if (GetActive()) {\n    x110_elementGen->Update(dt);\n    x158_activeTime += dt;\n    x150_radius += x154_expansionSpeed * dt;\n    x154_expansionSpeed += dt * x114_data.GetSpeedIncrease();\n    x110_elementGen->SetExternalVar(0, x150_radius);\n    for (size_t i = 0; i < x110_elementGen->GetNumActiveChildParticles(); ++i) {\n      auto& particle = static_cast<CElementGen&>(x110_elementGen->GetActiveChildParticle(i));\n      if (particle.Get4CharId() == SBIG('PART')) {\n        particle.SetExternalVar(0, x150_radius);\n      }\n    }\n    if (x16c_hitPlayerInAir) {\n      x164_timeSinceHitPlayerInAir += dt;\n      x16c_hitPlayerInAir = false;\n    }\n    if (x16d_hitPlayer) {\n      x168_timeSinceHitPlayer += dt;\n      x16d_hitPlayer = false;\n    }\n  }\n  if (x110_elementGen->IsSystemDeletable() && x15c_minActiveTime > 0.f && x158_activeTime >= x15c_minActiveTime) {\n    mgr.FreeScriptObject(GetUniqueId());\n  } else if (x980_id2 != kInvalidUniqueId) {\n    if (TCastToPtr<CGameLight> light = mgr.ObjectById(x980_id2)) {\n      if (!GetActive()) {\n        return;\n      }\n      light->SetLight(x110_elementGen->GetLight());\n    }\n  }\n}\n\nvoid CShockWave::Touch(CActor& actor, CStateManager& mgr) {\n  if (x158_activeTime >= x15c_minActiveTime) {\n    return;\n  }\n\n  bool isParent = xe8_parentId == actor.GetUniqueId();\n  if (TCastToConstPtr<CCollisionActor> cactor = mgr.GetObjectById(actor.GetUniqueId())) {\n    isParent = xe8_parentId == cactor->GetOwnerId();\n  }\n  if (isParent) {\n    return;\n  }\n\n  float mmax = x150_radius * x150_radius;\n  float mmin = mmax * x114_data.GetWidthPercent() * x114_data.GetWidthPercent();\n  zeus::CVector3f dist = actor.GetTranslation() - GetTranslation();\n  CDamageInfo damageInfo = xec_damageInfo;\n  float knockBackScale = std::max(0.f, 1.f - x160_knockback * x158_activeTime);\n  bool isPlayer = mgr.GetPlayer().GetUniqueId() == actor.GetUniqueId();\n  bool isPlayerInAir = isPlayer && mgr.GetPlayer().GetPlayerMovementState() != CPlayer::EPlayerMovementState::OnGround;\n  float distXYMag = dist.toVec2f().magSquared();\n  if (distXYMag < mmin || distXYMag > mmax) {\n    return;\n  }\n\n  if (isPlayer) {\n    if (mgr.GetPlayer().GetPlayerMovementState() == CPlayer::EPlayerMovementState::OnGround) {\n      const zeus::CTransform& playerTransform = mgr.GetPlayer().GetTransform();\n      zeus::CVector3f playerDir = GetTranslation() - playerTransform.origin;\n      if (playerDir.canBeNormalized()) {\n        playerDir.normalize();\n        float dot = std::abs(playerDir.dot(playerTransform.frontVector()));\n        knockBackScale = std::max(0.12f, 0.88f * dot * dot);\n      }\n    }\n    if (mgr.GetPlayer().GetVelocity().magnitude() > 40.f) {\n      x168_timeSinceHitPlayer = 0.2666f;\n    }\n  }\n  damageInfo.SetKnockBackPower(knockBackScale * damageInfo.GetKnockBackPower());\n\n  if (isPlayer && (x164_timeSinceHitPlayerInAir >= 0.1333f || x168_timeSinceHitPlayer >= 0.2666f)) {\n    return;\n  }\n  if (!WasAlreadyDamaged(actor.GetUniqueId())) {\n    mgr.ApplyDamage(GetUniqueId(), actor.GetUniqueId(), GetUniqueId(), damageInfo,\n                    CMaterialFilter::MakeInclude({EMaterialTypes::Solid}), zeus::skZero3f);\n    if (isPlayer && x974_electricDesc) {\n      mgr.AddObject(new CHUDBillboardEffect(std::nullopt, x974_electricDesc, mgr.AllocateUniqueId(), true,\n                                            \"VisorElectricFx\", CHUDBillboardEffect::GetNearClipDistance(mgr),\n                                            CHUDBillboardEffect::GetScaleForPOV(mgr), zeus::skWhite, zeus::skOne3f,\n                                            zeus::skZero3f));\n      CSfxManager::SfxStart(x114_data.GetElectrocuteSfx(), 1.f, 1.f, false, 0x7f, false, kInvalidAreaId);\n    }\n    x170_hitIds.push_back(actor.GetUniqueId());\n  } else {\n    damageInfo.SetDamage(0.f);\n    mgr.ApplyDamage(GetUniqueId(), actor.GetUniqueId(), GetUniqueId(), damageInfo,\n                    CMaterialFilter::MakeInclude({EMaterialTypes::Solid}), zeus::skZero3f);\n  }\n  if (isPlayerInAir) {\n    x16c_hitPlayerInAir = true;\n  }\n  if (isPlayer) {\n    x16d_hitPlayer = true;\n  }\n}\n\nbool CShockWave::WasAlreadyDamaged(TUniqueId id) const {\n  return std::find(x170_hitIds.begin(), x170_hitIds.end(), id) != x170_hitIds.end();\n}\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CShockWave.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/World/CActor.hpp\"\n#include \"Runtime/World/CDamageInfo.hpp\"\n\nnamespace metaforce::MP1 {\nstruct CShockWaveInfo {\nprivate:\n  u32 x0_ = 8;\n  CAssetId x4_particleDesc;\n  CDamageInfo x8_damageInfo;\n  float x24_initialRadius = 0.f;\n  float x28_widthPercent = 0.5f;\n  float x2c_initialExpansionSpeed;\n  float x30_speedIncrease = 0.f;\n  CAssetId x34_weaponDesc;\n  u16 x38_electrocuteSfx;\n\npublic:\n  CShockWaveInfo(CAssetId part, const CDamageInfo& dInfo, float initialExpansionSpeed, CAssetId weapon, u16 sfx)\n  : x4_particleDesc(part)\n  , x8_damageInfo(dInfo)\n  , x2c_initialExpansionSpeed(initialExpansionSpeed)\n  , x34_weaponDesc(weapon)\n  , x38_electrocuteSfx(sfx) {}\n\n  [[nodiscard]] CAssetId GetParticleDescId() const { return x4_particleDesc; }\n  [[nodiscard]] const CDamageInfo& GetDamageInfo() const { return x8_damageInfo; }\n  [[nodiscard]] float GetInitialRadius() const { return x24_initialRadius; }\n  [[nodiscard]] float GetWidthPercent() const { return x28_widthPercent; }\n  [[nodiscard]] float GetInitialExpansionSpeed() const { return x2c_initialExpansionSpeed; }\n  [[nodiscard]] float GetSpeedIncrease() const { return x30_speedIncrease; }\n  void SetSpeedIncrease(float speed) { x30_speedIncrease = speed; }\n  [[nodiscard]] CAssetId GetWeaponDescId() const { return x34_weaponDesc; }\n  [[nodiscard]] u16 GetElectrocuteSfx() const { return x38_electrocuteSfx; }\n};\n\nclass CShockWave : public CActor {\nprivate:\n  TUniqueId xe8_parentId;\n  CDamageInfo xec_damageInfo;\n  TToken<CGenDescription> x108_elementGenDesc;\n  std::unique_ptr<CElementGen> x110_elementGen;\n  CShockWaveInfo x114_data;\n  float x150_radius;\n  float x154_expansionSpeed;\n  float x158_activeTime = 0.f;\n  float x15c_minActiveTime;\n  float x160_knockback;\n  float x164_timeSinceHitPlayerInAir = 0.f;\n  float x168_timeSinceHitPlayer = 0.f;\n  bool x16c_hitPlayerInAir = false;\n  bool x16d_hitPlayer = false;\n  EntityList x170_hitIds;\n  std::optional<TToken<CElectricDescription>> x974_electricDesc;\n  TUniqueId x980_id2 = kInvalidUniqueId;\n\npublic:\n  DEFINE_ENTITY\n  CShockWave(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n             TUniqueId parent, const CShockWaveInfo& data, float minActiveTime, float knockback);\n\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) override;\n  void AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) override;\n  [[nodiscard]] std::optional<zeus::CAABox> GetTouchBounds() const override;\n  void Render(CStateManager& mgr) override;\n  void Think(float dt, CStateManager& mgr) override;\n  void Touch(CActor& actor, CStateManager& mgr) override;\n\nprivate:\n  [[nodiscard]] bool WasAlreadyDamaged(TUniqueId id) const;\n};\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CSpacePirate.cpp",
    "content": "#include \"Runtime/MP1/World/CSpacePirate.hpp\"\n\n#include <array>\n\n#include \"Runtime/CTimeProvider.hpp\"\n#include \"Runtime/Character/CCharLayoutInfo.hpp\"\n#include \"Runtime/Character/CPASAnimParmData.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/MP1/World/CMetroid.hpp\"\n#include \"Runtime/Weapon/CGameProjectile.hpp\"\n#include \"Runtime/World/CPatternedInfo.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CScriptAiJumpPoint.hpp\"\n#include \"Runtime/World/CScriptCoverPoint.hpp\"\n#include \"Runtime/World/CScriptTargetingPoint.hpp\"\n#include \"Runtime/World/CScriptWater.hpp\"\n#include \"Runtime/World/CScriptWaypoint.hpp\"\n#include \"Runtime/World/CTeamAiMgr.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n#include \"Runtime/Logging.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce::MP1 {\nnamespace {\nconstexpr std::array skParts{\n    \"Collar\"sv,  \"Neck_1\"sv, \"R_shoulder\"sv, \"R_elbow\"sv, \"R_wrist\"sv, \"L_shoulder\"sv, \"L_elbow\"sv,\n    \"L_wrist\"sv, \"R_hip\"sv,  \"R_knee\"sv,     \"R_ankle\"sv, \"L_hip\"sv,   \"L_knee\"sv,     \"L_ankle\"sv,\n};\n\nconstexpr std::array skRadii{\n    0.45f, 0.52f, 0.35f, 0.1f, 0.15f, 0.35f, 0.1f, 0.15f, 0.15f, 0.15f, 0.15f, 0.15f, 0.15f, 0.15f,\n};\n\nconstexpr std::array<SBurst, 6> skBurstsQuick{{\n    {20, {3, 4, 5, -1, 0, 0, 0, 0}, 0.100000f, 0.050000f},\n    {20, {2, 3, 4, -1, 0, 0, 0, 0}, 0.100000f, 0.050000f},\n    {20, {6, 5, 4, -1, 0, 0, 0, 0}, 0.100000f, 0.050000f},\n    {20, {1, 2, 3, -1, 0, 0, 0, 0}, 0.100000f, 0.050000f},\n    {20, {7, 6, 5, -1, 0, 0, 0, 0}, 0.100000f, 0.050000f},\n    {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0.000000f, 0.000000f},\n}};\n\nconstexpr std::array<SBurst, 7> skBurstsStandard{{\n    {15, {5, 3, 2, 1, -1, 0, 0, 0}, 0.100000f, 0.050000f},\n    {20, {1, 2, 3, 4, -1, 0, 0, 0}, 0.100000f, 0.050000f},\n    {20, {7, 6, 5, 4, -1, 0, 0, 0}, 0.100000f, 0.050000f},\n    {15, {3, 4, 5, 6, -1, 0, 0, 0}, 0.100000f, 0.050000f},\n    {15, {6, 5, 4, 3, -1, 0, 0, 0}, 0.100000f, 0.050000f},\n    {15, {2, 3, 4, 5, -1, 0, 0, 0}, 0.100000f, 0.050000f},\n    {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0.000000f, 0.000000f},\n}};\n\nconstexpr std::array<SBurst, 5> skBurstsFrenzied{{\n    {40, {1, 2, 3, 4, 5, 6, -1, 0}, 0.100000f, 0.050000f},\n    {40, {7, 6, 5, 4, 3, 2, -1, 0}, 0.100000f, 0.050000f},\n    {10, {2, 3, 4, 5, 4, 3, -1, 0}, 0.100000f, 0.050000f},\n    {10, {6, 5, 4, 3, 4, 5, -1, 0}, 0.100000f, 0.050000f},\n    {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0.000000f, 0.000000f},\n}};\n\nconstexpr std::array<SBurst, 4> skBurstsJumping{{\n    {20, {16, 4, -1, 0, 0, 0, 0, 0}, 0.100000f, 0.050000f},\n    {40, {5, 7, -1, 0, 0, 0, 0, 0}, 0.100000f, 0.050000f},\n    {40, {1, 10, -1, 0, 0, 0, 0, 0}, 0.100000f, 0.050000f},\n    {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0.000000f, 0.000000f},\n}};\n\nconstexpr std::array<SBurst, 6> skBurstsInjured{{\n    {15, {16, 1, 3, -1, 0, 0, 0, 0}, 0.100000f, 0.050000f},\n    {20, {3, 4, 6, -1, 0, 0, 0, 0}, 0.100000f, 0.050000f},\n    {25, {7, 5, 4, -1, 0, 0, 0, 0}, 0.100000f, 0.050000f},\n    {25, {2, 6, 4, -1, 0, 0, 0, 0}, 0.100000f, 0.050000f},\n    {15, {7, 5, 3, -1, 0, 0, 0, 0}, 0.100000f, 0.050000f},\n    {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0.000000f, 0.000000f},\n}};\n\nconstexpr std::array<SBurst, 4> skBurstsSeated{{\n    {35, {7, 13, -1, 0, 0, 0, 0, 0}, 0.100000f, 0.050000f},\n    {35, {9, 1, -1, 0, 0, 0, 0, 0}, 0.100000f, 0.050000f},\n    {30, {16, 12, -1, 0, 0, 0, 0, 0}, 0.100000f, 0.050000f},\n    {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0.000000f, 0.000000f},\n}};\n\nconstexpr std::array<SBurst, 6> skBurstsQuickOOV{{\n    {10, {16, 15, 13, -1, 0, 0, 0, 0}, 0.100000f, 0.050000f},\n    {20, {13, 12, 10, -1, 0, 0, 0, 0}, 0.100000f, 0.050000f},\n    {30, {9, 11, 12, -1, 0, 0, 0, 0}, 0.100000f, 0.050000f},\n    {30, {14, 10, 12, -1, 0, 0, 0, 0}, 0.100000f, 0.050000f},\n    {10, {9, 11, 13, -1, 0, 0, 0, 0}, 0.100000f, 0.050000f},\n    {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0.000000f, 0.000000f},\n}};\n\nconstexpr std::array<SBurst, 7> skBurstsStandardOOV{{\n    {26, {16, 8, 11, 14, -1, 0, 0, 0}, 0.100000f, 0.050000f},\n    {26, {16, 13, 11, 12, -1, 0, 0, 0}, 0.100000f, 0.050000f},\n    {16, {9, 11, 13, 10, -1, 0, 0, 0}, 0.100000f, 0.050000f},\n    {16, {14, 13, 12, 11, -1, 0, 0, 0}, 0.100000f, 0.050000f},\n    {8, {10, 11, 12, 13, -1, 0, 0, 0}, 0.100000f, 0.050000f},\n    {8, {6, 8, 11, 13, -1, 0, 0, 0}, 0.100000f, 0.050000f},\n    {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0.000000f, 0.000000f},\n}};\n\nconstexpr std::array<SBurst, 5> skBurstsFrenziedOOV{{\n    {40, {1, 16, 14, 12, 10, 11, -1, 0}, 0.100000f, 0.050000f},\n    {40, {9, 11, 12, 13, 11, 7, -1, 0}, 0.100000f, 0.050000f},\n    {10, {8, 10, 11, 12, 13, 12, -1, 0}, 0.100000f, 0.050000f},\n    {10, {15, 13, 12, 10, 12, 9, -1, 0}, 0.100000f, 0.050000f},\n    {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0.000000f, 0.000000f},\n}};\n\nconstexpr std::array<SBurst, 4> skBurstsJumpingOOV{{\n    {40, {7, 13, -1, 0, 0, 0, 0, 0}, 0.100000f, 0.050000f},\n    {40, {9, 1, -1, 0, 0, 0, 0, 0}, 0.100000f, 0.050000f},\n    {20, {16, 12, -1, 0, 0, 0, 0, 0}, 0.100000f, 0.050000f},\n    {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0.000000f, 0.000000f},\n}};\n\nconstexpr std::array<SBurst, 6> skBurstsInjuredOOV{{\n    {30, {9, 11, 13, -1, 0, 0, 0, 0}, 0.100000f, 0.050000f},\n    {10, {13, 12, 10, -1, 0, 0, 0, 0}, 0.100000f, 0.050000f},\n    {15, {9, 11, 12, -1, 0, 0, 0, 0}, 0.100000f, 0.050000f},\n    {15, {14, 10, 12, -1, 0, 0, 0, 0}, 0.100000f, 0.050000f},\n    {30, {16, 15, 13, -1, 0, 0, 0, 0}, 0.100000f, 0.050000f},\n    {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0.000000f, 0.000000f},\n}};\n\nconstexpr std::array<SBurst, 4> skBurstsSeatedOOV{{\n    {35, {7, 13, -1, 0, 0, 0, 0, 0}, 0.100000f, 0.050000f},\n    {35, {9, 1, -1, 0, 0, 0, 0, 0}, 0.100000f, 0.050000f},\n    {30, {16, 12, -1, 0, 0, 0, 0, 0}, 0.100000f, 0.050000f},\n    {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0.000000f, 0.000000f},\n}};\n\nconstexpr std::array<const SBurst*, 13> skBursts{\n    skBurstsQuick.data(),\n    skBurstsStandard.data(),\n    skBurstsFrenzied.data(),\n    skBurstsJumping.data(),\n    skBurstsInjured.data(),\n    skBurstsSeated.data(),\n    skBurstsQuickOOV.data(),\n    skBurstsStandardOOV.data(),\n    skBurstsFrenziedOOV.data(),\n    skBurstsJumpingOOV.data(),\n    skBurstsInjuredOOV.data(),\n    skBurstsSeatedOOV.data(),\n    nullptr,\n};\n\nstd::list<TUniqueId> mChargePlayerList;\n} // Anonymous namespace\n\nCSpacePirate::CSpacePirateData::CSpacePirateData(metaforce::CInputStream& in, u32 propCount)\n: x0_AggressionCheck(in.ReadFloat())\n, x4_CoverCheck(in.ReadFloat())\n, x8_SearchRadius(in.ReadFloat())\n, xc_FallBackCheck(in.ReadFloat())\n, x10_FallBackRadius(in.ReadFloat())\n, x14_HearingRadius(in.ReadFloat())\n, x18_flags(in.ReadLong())\n, x1c_(in.ReadBool())\n, x20_Projectile(in)\n, x48_Sound_Projectile(CSfxManager::TranslateSFXID(in.ReadLong()))\n, x4c_BladeDamage(in)\n, x68_KneelAttackChance(in.ReadFloat())\n, x6c_KneelAttackShot(in)\n, x94_DodgeCheck(in.ReadFloat())\n, x98_Sound_Impact(CSfxManager::TranslateSFXID(in.ReadLong()))\n, x9c_averageNextShotTime(in.ReadFloat())\n, xa0_nextShotTimeVariation(in.ReadFloat())\n, xa4_Sound_Alert(CSfxManager::TranslateSFXID(in.ReadLong()))\n, xa8_GunTrackDelay(in.ReadFloat())\n, xac_firstBurstCount(in.ReadLong())\n, xb0_CloakOpacity(in.ReadFloat())\n, xb4_MaxCloakOpacity(in.ReadFloat())\n, xb8_dodgeDelayTimeMin(in.ReadFloat())\n, xbc_dodgeDelayTimeMax(in.ReadFloat())\n, xc0_Sound_Hurled(CSfxManager::TranslateSFXID(in.ReadLong()))\n, xc2_Sound_Death(CSfxManager::TranslateSFXID(in.ReadLong()))\n, xc4_(propCount > 35 ? in.ReadFloat() : 0.2f)\n, xc8_AvoidDistance(propCount > 36 ? in.ReadFloat() : 8.f) {}\n\nCPirateRagDoll::CPirateRagDoll(CStateManager& mgr, CSpacePirate* sp, u16 thudSfx, u32 flags)\n: CRagDoll(-sp->GetGravityConstant(), -3.f, 8.f, flags), x6c_spacePirate(sp), x70_thudSfx(thudSfx) {\n  x6c_spacePirate->RemoveMaterial(EMaterialTypes::Solid, EMaterialTypes::AIBlock, EMaterialTypes::GroundCollider, mgr);\n  x6c_spacePirate->HealthInfo(mgr)->SetHP(-1.f);\n  SetNumParticles(14);\n  SetNumLengthConstraints(47);\n  SetNumJointConstraints(4);\n  zeus::CVector3f scale = x6c_spacePirate->GetModelData()->GetScale();\n  CAnimData* aData = x6c_spacePirate->GetModelData()->GetAnimationData();\n  aData->BuildPose();\n  zeus::CVector3f center = x6c_spacePirate->GetBoundingBox().center();\n  for (size_t i = 0; i < skParts.size(); ++i) {\n    const CSegId id = aData->GetLocatorSegId(skParts[i]);\n    AddParticle(id, center, x6c_spacePirate->GetTransform() * (aData->GetPose().GetOffset(id) * scale),\n                skRadii[i] * scale.z());\n  }\n  SatisfyWorldConstraintsOnConstruction(mgr);\n  AddLengthConstraint(0, 1);\n  AddLengthConstraint(0, 2);\n  AddLengthConstraint(0, 8);\n  AddLengthConstraint(0, 11);\n  AddLengthConstraint(0, 5);\n  AddLengthConstraint(2, 3);\n  AddLengthConstraint(3, 4);\n  AddLengthConstraint(5, 6);\n  AddLengthConstraint(6, 7);\n  AddLengthConstraint(2, 5);\n  AddLengthConstraint(2, 8);\n  AddLengthConstraint(2, 11);\n  AddLengthConstraint(5, 8);\n  AddLengthConstraint(5, 11);\n  AddLengthConstraint(8, 11);\n  AddLengthConstraint(8, 9);\n  AddLengthConstraint(9, 10);\n  AddLengthConstraint(11, 12);\n  AddLengthConstraint(12, 13);\n  AddMinLengthConstraint(1, 8, x14_lengthConstraints[2].GetLength());\n  AddMinLengthConstraint(1, 11, x14_lengthConstraints[3].GetLength());\n  AddMinLengthConstraint(1, 2, x14_lengthConstraints[1].GetLength() * 0.9f);\n  AddMinLengthConstraint(1, 5, x14_lengthConstraints[4].GetLength() * 0.9f);\n  AddMinLengthConstraint(1, 4, x14_lengthConstraints[0].GetLength() * 2.5f);\n  AddMinLengthConstraint(1, 7, x14_lengthConstraints[0].GetLength() * 2.5f);\n  AddMinLengthConstraint(4, 2, x14_lengthConstraints[5].GetLength());\n  AddMinLengthConstraint(7, 5, x14_lengthConstraints[7].GetLength());\n  AddMinLengthConstraint(3, 5, x14_lengthConstraints[5].GetLength() * 0.5f + x14_lengthConstraints[9].GetLength());\n  AddMinLengthConstraint(6, 2, x14_lengthConstraints[7].GetLength() * 0.5f + x14_lengthConstraints[9].GetLength());\n  AddMinLengthConstraint(4, 5, x14_lengthConstraints[5].GetLength() * 0.5f + x14_lengthConstraints[9].GetLength());\n  AddMinLengthConstraint(7, 2, x14_lengthConstraints[7].GetLength() * 0.5f + x14_lengthConstraints[9].GetLength());\n  AddMinLengthConstraint(4, 7, x14_lengthConstraints[9].GetLength());\n  AddMinLengthConstraint(4, 8, x14_lengthConstraints[14].GetLength());\n  AddMinLengthConstraint(7, 11, x14_lengthConstraints[14].GetLength());\n  AddMinLengthConstraint(10, 8, x14_lengthConstraints[15].GetLength());\n  AddMinLengthConstraint(13, 11, x14_lengthConstraints[17].GetLength());\n  AddMinLengthConstraint(9, 2, x14_lengthConstraints[15].GetLength() * 0.707f + x14_lengthConstraints[10].GetLength());\n  AddMinLengthConstraint(12, 5, x14_lengthConstraints[17].GetLength() * 0.707f + x14_lengthConstraints[13].GetLength());\n  AddMinLengthConstraint(9, 11, x14_lengthConstraints[15].GetLength());\n  AddMinLengthConstraint(12, 8, x14_lengthConstraints[17].GetLength());\n  AddMinLengthConstraint(10, 0, x14_lengthConstraints[2].GetLength() + x14_lengthConstraints[15].GetLength());\n  AddMinLengthConstraint(13, 0, x14_lengthConstraints[3].GetLength() + x14_lengthConstraints[17].GetLength());\n  AddMinLengthConstraint(10, 13, x14_lengthConstraints[14].GetLength());\n  AddMinLengthConstraint(9, 12, x14_lengthConstraints[14].GetLength());\n  AddMinLengthConstraint(10, 12, x14_lengthConstraints[14].GetLength());\n  AddMinLengthConstraint(13, 9, x14_lengthConstraints[14].GetLength());\n  AddMaxLengthConstraint(10, 13, x14_lengthConstraints[14].GetLength() * 5.f);\n  AddJointConstraint(8, 2, 5, 8, 9, 10);    // R_hip, R_shoulder, L_shoulder, R_hip, R_knee, R_ankle\n  AddJointConstraint(11, 2, 5, 11, 12, 13); // L_hip, R_shoulder, L_shoulder, L_hip, L_knee, L_ankle\n  AddJointConstraint(2, 11, 5, 2, 3, 4);    // R_shoulder, L_hip, L_shoulder, R_shoulder, R_elbow, R_wrist\n  AddJointConstraint(5, 2, 8, 5, 6, 7);     // L_shoulder, R_shoulder, R_hip, L_shoulder, L_elbow, R_wrist\n  for (const auto& conn : x6c_spacePirate->GetConnectionList()) {\n    if (conn.x0_state == EScriptObjectState::Modify && conn.x4_msg == EScriptObjectMessage::Follow) {\n      TUniqueId wpId = mgr.GetIdForScript(conn.x8_objId);\n      if (TCastToConstPtr<CScriptWaypoint> wp = mgr.GetObjectById(wpId)) {\n        x90_waypoints.push_back(wpId);\n        x9c_wpParticleIdxs.push_back(wp->GetAnimation());\n        if (x90_waypoints.size() == 4)\n          break;\n      }\n    }\n  }\n}\n\nvoid CPirateRagDoll::PreRender(const zeus::CVector3f& v, CModelData& mData) {\n  if (!x68_25_over || x68_27_continueSmallMovements) {\n    CAnimData* aData = mData.GetAnimationData();\n    for (CSegId id : aData->GetCharLayoutInfo().GetSegIdList().GetList()) {\n      if (aData->GetCharLayoutInfo().GetRootNode()->GetBoneMap()[id].x10_children.size() > 1) {\n        aData->PoseBuilder().GetTreeMap()[id].x4_rotation = zeus::CQuaternion();\n      }\n    }\n    CSegId rootId = aData->GetLocatorSegId(\"Skeleton_Root\"sv);\n    // R_hip, L_hip\n    aData->PoseBuilder().GetTreeMap()[rootId].x14_offset =\n        (0.5f * (x4_particles[8].GetPosition() + x4_particles[11].GetPosition()) - v) / mData.GetScale();\n    // R_shoulder, L_shoulder\n    zeus::CVector3f rootRight = x4_particles[2].GetPosition() - x4_particles[5].GetPosition();\n    // Collar, R_hip, L_hip\n    zeus::CVector3f rootUp =\n        (x4_particles[0].GetPosition() - (x4_particles[8].GetPosition() + x4_particles[11].GetPosition()) * 0.5f)\n            .normalized();\n    zeus::CVector3f rootFore = rootUp.cross(rootRight).normalized();\n    zeus::CQuaternion rootRot(zeus::CMatrix3f(rootFore.cross(rootUp), rootFore, rootUp));\n    aData->PoseBuilder().GetTreeMap()[rootId].x4_rotation = rootRot;\n    if (x6c_spacePirate->x7b4_attachedActor == kInvalidUniqueId) {\n      zeus::CVector3f neckRestVec = aData->GetCharLayoutInfo().GetFromParentUnrotated(x4_particles[1].GetBone());\n      aData->PoseBuilder().GetTreeMap()[x4_particles[1].GetBone()].x4_rotation = zeus::CQuaternion::shortestRotationArc(\n          neckRestVec, rootRot.inverse().transform(x4_particles[1].GetPosition() - x4_particles[0].GetPosition()));\n    }\n    BoneAlign(aData->PoseBuilder(), aData->GetCharLayoutInfo(), 3, 4,\n              rootRot * BoneAlign(aData->PoseBuilder(), aData->GetCharLayoutInfo(), 2, 3, rootRot));\n    BoneAlign(aData->PoseBuilder(), aData->GetCharLayoutInfo(), 6, 7,\n              rootRot * BoneAlign(aData->PoseBuilder(), aData->GetCharLayoutInfo(), 5, 6, rootRot));\n    BoneAlign(aData->PoseBuilder(), aData->GetCharLayoutInfo(), 9, 10,\n              rootRot * BoneAlign(aData->PoseBuilder(), aData->GetCharLayoutInfo(), 8, 9, rootRot));\n    BoneAlign(aData->PoseBuilder(), aData->GetCharLayoutInfo(), 12, 13,\n              rootRot * BoneAlign(aData->PoseBuilder(), aData->GetCharLayoutInfo(), 11, 12, rootRot));\n    zeus::CQuaternion q;\n    q.rotateX(zeus::degToRad(-70.f));\n    aData->PoseBuilder().GetTreeMap()[x4_particles[10].GetBone()].x4_rotation = q; // R_ankle\n    aData->PoseBuilder().GetTreeMap()[x4_particles[13].GetBone()].x4_rotation = q; // L_ankle\n    aData->MarkPoseDirty();\n  }\n}\n\nvoid CPirateRagDoll::Update(CStateManager& mgr, float dt, float waterTop) {\n  if (!x68_25_over || x68_27_continueSmallMovements) {\n    if (x6c_spacePirate->x7b4_attachedActor != kInvalidUniqueId) {\n      // Shoulder height delta\n      float f2 = x4_particles[2].GetPosition().z() - x4_particles[5].GetPosition().z();\n      if (f2 * f2 > 0.0625f) {\n        zeus::CVector3f vec(0.f, 0.f, ((f2 > 0.f) ? f2 - 0.25f : f2 + 0.25f) * 0.1f);\n        x4_particles[2].Position() -= vec;\n        x4_particles[5].Position() += vec;\n      }\n      // Collar-hips height delta\n      f2 = x4_particles[0].GetPosition().z() -\n           (x4_particles[8].GetPosition().z() + x4_particles[11].GetPosition().z()) * 0.5f;\n      if (f2 * f2 > 0.0625f) {\n        zeus::CVector3f vec(0.f, 0.f, ((f2 > 0.f) ? f2 - 0.25f : f2 + 0.25f) * 0.1f);\n        x4_particles[0].Position() -= vec;\n        x4_particles[8].Position() += vec;\n        x4_particles[11].Position() += vec;\n      }\n    }\n    // Collar-hips weighted center\n    zeus::CVector3f oldTorsoCenter = x4_particles[8].GetPosition() * 0.25f + x4_particles[11].GetPosition() * 0.25f +\n                                     x4_particles[0].GetPosition() * 0.5f;\n    oldTorsoCenter.z() = std::min(x4_particles[0].GetPosition().z() - x4_particles[0].GetRadius(),\n                                  std::min(x4_particles[8].GetPosition().z() - x4_particles[8].GetRadius(),\n                                           x4_particles[11].GetPosition().z() - x4_particles[11].GetRadius()));\n    if (oldTorsoCenter.z() < 0.5f + waterTop) {\n      x84_torsoImpulse = x84_torsoImpulse * 1000.f;\n    }\n    zeus::CVector3f accDelta = x84_torsoImpulse * 0.333f * (1.f / x6c_spacePirate->GetMass());\n    x4_particles[11].Velocity() += accDelta;\n    x4_particles[8].Velocity() += accDelta;\n    x4_particles[0].Velocity() += accDelta;\n    x84_torsoImpulse = zeus::skZero3f;\n    CRagDoll::Update(mgr, dt, waterTop);\n    auto particleIdxIt = x9c_wpParticleIdxs.begin();\n    for (TUniqueId id : x90_waypoints) {\n      if (const auto* wp = static_cast<const CScriptWaypoint*>(mgr.GetObjectById(id))) {\n        if (wp->GetActive()) {\n          x4_particles[*particleIdxIt].Position() = wp->GetTranslation();\n        }\n      }\n      ++particleIdxIt;\n    }\n    // Collar-hips weighted center\n    zeus::CVector3f newTorsoCenter = x4_particles[8].GetPosition() * 0.25f + x4_particles[11].GetPosition() * 0.25f +\n                                     x4_particles[0].GetPosition() * 0.5f;\n    newTorsoCenter.z() = std::min(x4_particles[0].GetPosition().z() - x4_particles[0].GetRadius(),\n                                  std::min(x4_particles[8].GetPosition().z() - x4_particles[8].GetRadius(),\n                                           x4_particles[11].GetPosition().z() - x4_particles[11].GetRadius()));\n    x6c_spacePirate->SetTransform({});\n    x6c_spacePirate->SetTranslation(newTorsoCenter);\n    x6c_spacePirate->SetVelocityWR((newTorsoCenter - oldTorsoCenter) * (1.f / dt));\n    x74_sfxTimer -= dt;\n    if (x54_impactVel > 2.5f && x74_sfxTimer < 0.f &&\n        (xb0_24_initSfx || (x6c_spacePirate->GetTranslation() - x78_lastSFXPos).magSquared() > 0.1f)) {\n      CSfxManager::AddEmitter(x70_thudSfx, x6c_spacePirate->GetTranslation(), zeus::skZero3f,\n                              std::min(25.f * x54_impactVel, 127.f) / 127.f, true, false, 0x7f, kInvalidAreaId);\n      x74_sfxTimer = mgr.GetActiveRandom()->Float() * 0.222f + 0.222f;\n      xb0_24_initSfx = false;\n      x78_lastSFXPos = x6c_spacePirate->GetTranslation();\n    }\n  } else {\n    x6c_spacePirate->SetMomentumWR(zeus::skZero3f);\n    x6c_spacePirate->Stop();\n  }\n}\n\nvoid CPirateRagDoll::Prime(CStateManager& mgr, const zeus::CTransform& xf, CModelData& mData) {\n  const auto& aabb = x6c_spacePirate->GetBaseBoundingBox();\n  zeus::CVector3f newMax = aabb.max;\n  newMax.z() = (aabb.max.z() - aabb.min.z()) * 0.5f + aabb.min.z();\n  x6c_spacePirate->SetBoundingBox({aabb.min, newMax});\n  CRagDoll::Prime(mgr, xf, mData);\n}\n\nCSpacePirate::CSpacePirate(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                           CModelData&& mData, const CActorParameters& aParams, const CPatternedInfo& pInfo,\n                           CInputStream& in, u32 propCount)\n: CPatterned(ECharacter::SpacePirate, uid, name, EFlavorType::Zero, info, xf, std::move(mData), pInfo,\n             EMovementType::Ground, EColliderType::One, EBodyType::BiPedal, aParams, EKnockBackVariant::Medium)\n, x568_pirateData(in, propCount)\n, x660_pathFindSearch(nullptr, 0x1, pInfo.GetPathfindingIndex(), 1.f, 1.f)\n, x750_initialHP(pInfo.GetHealthInfo().GetHP())\n, x764_boneTracking(*x64_modelData->GetAnimationData(), \"Head_1\"sv, zeus::degToRad(70.f), zeus::degToRad(180.f),\n                    EBoneTrackingFlags::None)\n, x7c4_burstFire(skBursts.data(), x568_pirateData.xac_firstBurstCount)\n, x8b8_minCloakAlpha(x568_pirateData.xb0_CloakOpacity)\n, x8bc_maxCloakAlpha(x568_pirateData.xb4_MaxCloakOpacity)\n, x8c0_dodgeDelayTimer(x568_pirateData.xb8_dodgeDelayTimeMin)\n, x8c4_aimDelayTimer(x568_pirateData.xa8_GunTrackDelay) {\n  x634_24_pendingAmbush = bool(x568_pirateData.x18_flags & 0x1);\n  x634_25_ceilingAmbush = bool(x568_pirateData.x18_flags & 0x2);\n  x634_26_nonAggressive = bool(x568_pirateData.x18_flags & 0x4);\n  x634_27_melee = bool(x568_pirateData.x18_flags & 0x8);\n  x634_28_noShuffleCloseCheck = bool(x568_pirateData.x18_flags & 0x10);\n  x634_29_onlyAttackInRange = bool(x568_pirateData.x18_flags & 0x20);\n  x634_30_ = bool(x568_pirateData.x18_flags & 0x40);\n  x634_31_noKnockbackImpulseReset = bool(x568_pirateData.x18_flags & 0x80);\n  x635_24_noMeleeAttack = bool(x568_pirateData.x18_flags & 0x200);\n  x635_25_breakAttack = bool(x568_pirateData.x18_flags & 0x400);\n  x635_26_seated = bool(x568_pirateData.x18_flags & 0x1000);\n  x635_27_shadowPirate = bool(x568_pirateData.x18_flags & 0x2000);\n  x635_28_alertBeforeCloak = bool(x568_pirateData.x18_flags & 0x4000);\n  x635_29_noBreakDodge = bool(x568_pirateData.x18_flags & 0x8000);\n  x635_30_floatingCorpse = bool(x568_pirateData.x18_flags & 0x10000);\n  x635_31_ragdollNoAiCollision = bool(x568_pirateData.x18_flags & 0x20000);\n  x636_24_trooper = bool(x568_pirateData.x18_flags & 0x40000);\n\n  x758_headSeg = x64_modelData->GetAnimationData()->GetLocatorSegId(\"Head_1\"sv);\n  x7b6_gunSeg = x64_modelData->GetAnimationData()->GetLocatorSegId(\"R_gun_LCTR\"sv);\n  x7b7_elbowSeg = x64_modelData->GetAnimationData()->GetLocatorSegId(\"R_elbow\"sv);\n  x7b8_wristSeg = x64_modelData->GetAnimationData()->GetLocatorSegId(\"R_wrist\"sv);\n  x7b9_swooshSeg = x64_modelData->GetAnimationData()->GetLocatorSegId(\"Swoosh_LCTR\"sv);\n\n  if (!x634_29_onlyAttackInRange) {\n    x7a4_intoJumpDist = GetAnimationDistance(\n        CPASAnimParmData(pas::EAnimationState::Jump, CPASAnimParm::FromEnum(0), CPASAnimParm::FromEnum(0)));\n    x848_dodgeDist = GetAnimationDistance(\n        CPASAnimParmData(pas::EAnimationState::Step, CPASAnimParm::FromEnum(3), CPASAnimParm::FromEnum(1)));\n    x84c_breakDodgeDist = GetAnimationDistance(\n        CPASAnimParmData(pas::EAnimationState::Step, CPASAnimParm::FromEnum(3), CPASAnimParm::FromEnum(2)));\n  } else {\n    x450_bodyController->BodyStateInfo().SetLocoAnimChangeAtEndOfAnimOnly(true);\n  }\n\n  const auto& baseAABB = GetBaseBoundingBox();\n  x7a8_eyeHeight = (baseAABB.max.z() - baseAABB.min.z()) * 0.6f;\n\n  if (x90_actorLights) {\n    x90_actorLights->SetAmbienceGenerated(false);\n  }\n\n  x460_knockBackController.sub80233d40(3, 3.f, FLT_MAX);\n  x460_knockBackController.SetLocomotionDuringElectrocution(true);\n\n  if (x634_29_onlyAttackInRange) {\n    x460_knockBackController.SetKnockBackVariant(EKnockBackVariant::Small);\n  } else if (x636_24_trooper && x260_damageVulnerability.WeaponHurts(CWeaponMode(EWeaponType::Plasma), false)) {\n    x460_knockBackController.SetKnockBackVariant(EKnockBackVariant::Large);\n  }\n\n  if (!x450_bodyController->HasBodyState(pas::EAnimationState::AdditiveAim)) {\n    x634_27_melee = true;\n  }\n\n  if (x636_24_trooper) {\n    if (x260_damageVulnerability.WeaponHurts(CWeaponMode(EWeaponType::Plasma), false)) {\n      x8cc_trooperColor = zeus::CColor(0.996f, 0.f, 0.157f, 1.f);\n    } else if (x260_damageVulnerability.WeaponHurts(CWeaponMode(EWeaponType::Ice), false)) {\n      x8cc_trooperColor = zeus::skWhite;\n    } else if (x260_damageVulnerability.WeaponHurts(CWeaponMode(EWeaponType::Power), false)) {\n      x8cc_trooperColor = zeus::CColor(0.992f, 0.937f, 0.337f, 1.f);\n    } else if (x260_damageVulnerability.WeaponHurts(CWeaponMode(EWeaponType::Wave), false)) {\n      x8cc_trooperColor = zeus::CColor(0.776f, 0.054f, 1.f, 1.f);\n    }\n  }\n\n  x568_pirateData.x20_Projectile.Token().Lock();\n  x568_pirateData.x6c_KneelAttackShot.Token().Lock();\n}\n\nvoid CSpacePirate::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CSpacePirate::UpdateCloak(float dt, CStateManager& mgr) {\n  if (x635_27_shadowPirate) {\n    if (x400_25_alive) {\n      if (x8a8_cloakDelayTimer > 0.f) {\n        x8a8_cloakDelayTimer -= dt;\n        if (x8a8_cloakDelayTimer <= 0.f) {\n          x3e8_alphaDelta = -0.4f;\n        }\n      }\n    } else {\n      x8b8_minCloakAlpha = 0.f;\n      x8bc_maxCloakAlpha = 1.f;\n    }\n\n    if (x8ac_electricParticleTimer > 0.f) {\n      x8ac_electricParticleTimer -= dt;\n      if (x8ac_electricParticleTimer <= 0.f && !x450_bodyController->IsElectrocuting()) {\n        mgr.GetActorModelParticles()->StopElectric(*this);\n      }\n    }\n\n    if (x450_bodyController->IsFrozen()) {\n      x3e8_alphaDelta = 2.f;\n    }\n\n    if (x3e8_alphaDelta < 0.f && x42c_color.a() < x8b8_minCloakAlpha) {\n      x42c_color.a() = x8b8_minCloakAlpha;\n      x3e8_alphaDelta = 0.f;\n      RemoveMaterial(EMaterialTypes::Target, mgr);\n    }\n\n    if (x3e8_alphaDelta > 0.f && x42c_color.a() > x8bc_maxCloakAlpha) {\n      x42c_color.a() = x8bc_maxCloakAlpha;\n      AddMaterial(EMaterialTypes::Target, mgr);\n    }\n\n    x8b0_cloakStepTime -= dt;\n    if (x8b0_cloakStepTime < 0.f) {\n      x8b0_cloakStepTime = (1.f - mgr.GetActiveRandom()->Float()) * 0.08f;\n      if (x3e8_alphaDelta < 0.f) {\n        x8b4_shadowPirateAlpha = x42c_color.a();\n        if (x400_25_alive) {\n          x8b4_shadowPirateAlpha -= (x42c_color.a() - x8b8_minCloakAlpha) * x8b0_cloakStepTime;\n        }\n      } else if (x3e8_alphaDelta > 0.f) {\n        x8b4_shadowPirateAlpha = x42c_color.a() + x8b0_cloakStepTime * (x8bc_maxCloakAlpha - x42c_color.a());\n      } else {\n        x8b4_shadowPirateAlpha = x42c_color.a();\n      }\n    }\n  }\n}\n\nbool CSpacePirate::ShouldFrenzy(CStateManager& mgr) {\n  bool reset = false;\n  if (x638_24_pendingFrenzyChance) {\n    x638_24_pendingFrenzyChance = false;\n    if (mgr.GetActiveRandom()->Next() % 100 < 25) {\n      reset = true;\n    }\n  }\n\n  if (!mChargePlayerList.empty()) {\n    reset = true;\n  }\n\n  if (mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed) {\n    reset = true;\n  }\n\n  if (HealthInfo(mgr)->GetHP() < 0.3f * x750_initialHP && mgr.GetActiveRandom()->Next() % 100 < 60 &&\n      x854_lowHealthFrenzyTimer < 0.5f) {\n    reset = true;\n  }\n\n  if (reset) {\n    x63c_frenzyFrames = mgr.GetActiveRandom()->Range(2, 4);\n  }\n  x63c_frenzyFrames -= 1;\n  return x63c_frenzyFrames >= 0;\n}\n\nvoid CSpacePirate::SquadReset(CStateManager& mgr) {\n  CTeamAiMgr::ResetTeamAiRole(!x634_27_melee ? CTeamAiMgr::EAttackType::Ranged : CTeamAiMgr::EAttackType::Melee, mgr,\n                              x8c8_teamAiMgrId, GetUniqueId(), true);\n}\n\nvoid CSpacePirate::SquadAdd(CStateManager& mgr) {\n  if (x8c8_teamAiMgrId == kInvalidUniqueId) {\n    x8c8_teamAiMgrId = CTeamAiMgr::GetTeamAiMgr(*this, mgr);\n  }\n  if (x8c8_teamAiMgrId != kInvalidUniqueId) {\n    if (TCastToPtr<CTeamAiMgr> aimgr = mgr.ObjectById(x8c8_teamAiMgrId)) {\n      aimgr->AssignTeamAiRole(*this, x634_27_melee ? CTeamAiRole::ETeamAiRole::Melee : CTeamAiRole::ETeamAiRole::Ranged,\n                              CTeamAiRole::ETeamAiRole::Unknown, CTeamAiRole::ETeamAiRole::Invalid);\n    }\n  }\n}\n\nvoid CSpacePirate::SquadRemove(CStateManager& mgr) {\n  if (x8c8_teamAiMgrId != kInvalidUniqueId) {\n    if (TCastToPtr<CTeamAiMgr> aimgr = mgr.ObjectById(x8c8_teamAiMgrId)) {\n      if (aimgr->IsPartOfTeam(GetUniqueId())) {\n        aimgr->RemoveTeamAiRole(GetUniqueId());\n        x8c8_teamAiMgrId = kInvalidUniqueId;\n      }\n    }\n  }\n}\n\nbool CSpacePirate::CheckTargetable(const CStateManager& mgr) const { return GetModelAlphau8(mgr) > 127; }\n\nbool CSpacePirate::FireProjectile(float dt, CStateManager& mgr) {\n  bool ret = false;\n  zeus::CTransform gunXf = GetLctrTransform(x7b6_gunSeg);\n  if (!x400_25_alive) {\n    LaunchProjectile(gunXf, mgr, 6, EProjectileAttrib::None, false, {}, 0xffff, false, zeus::skOne3f);\n    ret = true;\n  } else {\n    if (TCastToConstPtr<CActor> act = mgr.GetObjectById(x7c0_targetId)) {\n      zeus::CVector3f pos = act->GetTranslation();\n      if (mgr.GetPlayer().GetUniqueId() == x7c0_targetId) {\n        pos = GetProjectileInfo()->PredictInterceptPos(gunXf.origin, mgr.GetPlayer().GetAimPosition(mgr, 0.f),\n                                                       mgr.GetPlayer(), true, dt);\n      }\n      zeus::CVector3f gunToPos = pos - gunXf.origin;\n      float mag = gunToPos.magnitude();\n      gunToPos = gunToPos / mag;\n      float dot =\n          (GetLctrTransform(x7b8_wristSeg).origin - GetLctrTransform(x7b7_elbowSeg).origin).normalized().dot(gunToPos);\n      if ((dot > 0.707f || (mag < 6.f && dot > 0.5f)) &&\n          LineOfSightTest(mgr, gunXf.origin, pos, {EMaterialTypes::Player, EMaterialTypes::ProjectilePassthrough})) {\n        pos += GetTransform().rotate(x7c4_burstFire.GetDistanceCompensatedError(mag, 6.f));\n        LaunchProjectile(zeus::lookAt(gunXf.origin, pos), mgr, 6, EProjectileAttrib::None, false, {}, 0xffff, false,\n                         zeus::skOne3f);\n        ret = true;\n      }\n    }\n  }\n  if (ret) {\n    const auto bestAnim = x450_bodyController->GetPASDatabase().FindBestAnimation(\n        CPASAnimParmData{pas::EAnimationState::AdditiveReaction, CPASAnimParm::FromEnum(2)}, *mgr.GetActiveRandom(),\n        -1);\n    if (bestAnim.first > 0.f) {\n      x64_modelData->GetAnimationData()->AddAdditiveAnimation(bestAnim.second, 1.f, false, true);\n    }\n    CSfxManager::AddEmitter(x568_pirateData.x48_Sound_Projectile, GetTranslation(), zeus::skZero3f, true, false, 0x7f,\n                            kInvalidAreaId);\n  }\n  return ret;\n}\n\nvoid CSpacePirate::UpdateAttacks(float dt, CStateManager& mgr) {\n  bool reset = true;\n  if ((!x400_25_alive ||\n       (x450_bodyController->GetBodyStateInfo().GetCurrentState()->CanShoot() && x637_25_enableAim && !x634_27_melee &&\n        !x634_25_ceilingAmbush && !x639_26_started && !x450_bodyController->IsElectrocuting())) &&\n      x7c4_burstFire.GetBurstType() != -1) {\n    if (x400_25_alive) {\n      if (!x634_29_onlyAttackInRange ||\n          (mgr.GetPlayer().GetTranslation() - GetTranslation()).magSquared() < x3c8_leashRadius * x3c8_leashRadius) {\n        reset = false;\n        x7bc_attackRemTime -= dt;\n        if (x7bc_attackRemTime < 0.f) {\n          const CTeamAiRole* role = CTeamAiMgr::GetTeamAiRole(mgr, x8c8_teamAiMgrId, GetUniqueId());\n          if ((role == nullptr) || role->GetTeamAiRole() == CTeamAiRole::ETeamAiRole::Ranged) {\n            if (x8c8_teamAiMgrId == kInvalidUniqueId ||\n                CTeamAiMgr::AddAttacker(CTeamAiMgr::EAttackType::Ranged, mgr, x8c8_teamAiMgrId, GetUniqueId())) {\n              if (ShouldFrenzy(mgr)) {\n                x7c4_burstFire.SetBurstType(2);\n              }\n              if (x635_26_seated) {\n                x7c4_burstFire.SetBurstType(5);\n              }\n              if (!PlayerSpot(mgr, 0.f) && x7c4_burstFire.GetBurstType() < 6) {\n                x7c4_burstFire.SetBurstType(x7c4_burstFire.GetBurstType() + 6);\n              }\n\n              x7c4_burstFire.Start(mgr);\n              x7bc_attackRemTime = mgr.GetActiveRandom()->Float() * x308_attackTimeVariation + x304_averageAttackTime;\n              if ((GetGunEyePos() - mgr.GetPlayer().GetAimPosition(mgr, 0.f))\n                      .normalized()\n                      .dot(mgr.GetPlayer().GetTransform().basis[1]) < 0.9f) {\n                for (CEntity* ent : mgr.GetListeningAiObjectList()) {\n                  if (auto* otherPirate = CPatterned::CastTo<CSpacePirate>(ent)) {\n                    if (otherPirate != this && otherPirate->x637_25_enableAim &&\n                        otherPirate->GetAreaIdAlways() == GetAreaIdAlways()) {\n                      x7bc_attackRemTime += 0.2f;\n                    }\n                  }\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n\n    x7c4_burstFire.Update(mgr, dt);\n\n    if (x7c4_burstFire.ShouldFire()) {\n      if (mgr.GetPlayer().IsSidewaysDashing() && mgr.GetActiveRandom()->Float() < 0.5f) {\n        x7c4_burstFire.SetAvoidAccuracy(true);\n      }\n      FireProjectile(dt, mgr);\n      x7c4_burstFire.SetAvoidAccuracy(false);\n      float nextShotTime = x568_pirateData.xa0_nextShotTimeVariation * (mgr.GetActiveRandom()->Float() - 0.5f) +\n                           x568_pirateData.x9c_averageNextShotTime;\n      if (x7c4_burstFire.GetTimeToNextShot() > 0.f) {\n        x7c4_burstFire.SetTimeToNextShot(nextShotTime);\n      }\n    } else if (!x7c4_burstFire.IsBurstSet()) {\n      reset = true;\n    }\n  }\n\n  if (reset) {\n    SquadReset(mgr);\n  }\n\n  xe7_31_targetable = CheckTargetable(mgr);\n}\n\nzeus::CVector3f CSpacePirate::GetTargetPos(const CStateManager& mgr) {\n  if (x7c0_targetId != mgr.GetPlayer().GetUniqueId()) {\n    if (TCastToConstPtr<CActor> act = mgr.GetObjectById(x7c0_targetId)) {\n      if (act->GetActive()) {\n        return act->GetTranslation();\n      }\n    }\n    x764_boneTracking.SetTarget(mgr.GetPlayer().GetUniqueId());\n    x7c0_targetId = mgr.GetPlayer().GetUniqueId();\n  }\n  return mgr.GetPlayer().GetTranslation();\n}\n\nvoid CSpacePirate::UpdateAimBodyState(float dt, const CStateManager& mgr) {\n  if (x400_25_alive && x637_25_enableAim && !x637_29_inWallHang && !x450_bodyController->IsFrozen() && !x634_27_melee &&\n      !x85c_ragDoll && (!x635_26_seated || x639_28_satUp) && x31c_faceVec.z() <= 0.f) {\n    x8c4_aimDelayTimer = std::max(0.f, x8c4_aimDelayTimer - dt);\n    if (x8c4_aimDelayTimer == 0.f) {\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBCAdditiveAimCmd());\n      x450_bodyController->GetCommandMgr().DeliverAdditiveTargetVector(\n          x34_transform.transposeRotate(GetTargetPos(mgr) - GetTranslation()));\n    }\n  } else if (x637_25_enableAim && !x634_27_melee) {\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBodyStateCmd(EBodyStateCmd::AdditiveIdle));\n  }\n}\n\nvoid CSpacePirate::SetCinematicCollision(CStateManager& mgr) {\n  RemoveMaterial(EMaterialTypes::AIBlock, mgr);\n  CMaterialFilter filter = GetMaterialFilter();\n  filter.IncludeList().Remove(EMaterialTypes::AIBlock);\n  SetMaterialFilter(filter);\n}\n\nvoid CSpacePirate::SetNonCinematicCollision(CStateManager& mgr) {\n  AddMaterial(EMaterialTypes::AIBlock, mgr);\n  CMaterialFilter filter = GetMaterialFilter();\n  filter.IncludeList().Add(EMaterialTypes::AIBlock);\n  SetMaterialFilter(filter);\n}\n\nvoid CSpacePirate::CheckForProjectiles(CStateManager& mgr) {\n  if (x637_26_hearPlayerFire) {\n    zeus::CVector3f aimPos = mgr.GetPlayer().GetAimPosition(mgr, 0.f);\n    zeus::CAABox aabb(aimPos - 5.f, aimPos + 5.f);\n    EntityList nearList;\n    mgr.BuildNearList(nearList, aabb, CMaterialFilter::MakeInclude({EMaterialTypes::Projectile}), nullptr);\n    for (TUniqueId id : nearList) {\n      if (TCastToConstPtr<CGameProjectile> proj = mgr.GetObjectById(id)) {\n        zeus::CVector3f delta = GetBoundingBox().center() - proj->GetTranslation();\n        if (delta.isMagnitudeSafe()) {\n          if (x34_transform.basis[1].dot(delta) < 0.f) {\n            delta.normalize();\n            zeus::CVector3f projDelta = proj->GetTranslation() - proj->GetPreviousPos();\n            if (projDelta.isMagnitudeSafe()) {\n              projDelta.normalize();\n              if (projDelta.dot(delta) > 0.939f) {\n                x637_27_inProjectilePath = true;\n              }\n            }\n          }\n        } else {\n          x637_27_inProjectilePath = true;\n        }\n        if (x637_27_inProjectilePath) {\n          break;\n        }\n      }\n    }\n    x637_26_hearPlayerFire = false;\n  }\n}\n\nvoid CSpacePirate::Think(float dt, CStateManager& mgr) {\n  if (!GetActive()) {\n    return;\n  }\n\n  if (!m_lastKnownGoodXf.origin.isZero() && GetTranslation().isNaN()) {\n    spdlog::warn(\"BUG THIS!: Space Pirate attempted to visit it's people, resetting position to sane value\\n\");\n    Stop();\n    SetTransform(m_lastKnownGoodXf);\n  }\n  if (!x450_bodyController->GetActive()) {\n    x450_bodyController->Activate(mgr);\n  }\n\n  bool inCineCam = mgr.GetCameraManager()->IsInCinematicCamera();\n  if (inCineCam && !x637_31_prevInCineCam) {\n    SetCinematicCollision(mgr);\n  } else if (!inCineCam && x637_31_prevInCineCam && !x635_31_ragdollNoAiCollision) {\n    SetNonCinematicCollision(mgr);\n  }\n  x637_31_prevInCineCam = inCineCam;\n\n  float steeringSpeed = x748_steeringDelayTimer == 0.f ? x644_steeringSpeed : 0.f;\n  x450_bodyController->GetCommandMgr().SetSteeringSpeedRange(steeringSpeed, steeringSpeed);\n\n  x744_unkTimer = std::max(x744_unkTimer - dt, 0.f);\n\n  if (x400_25_alive) {\n    x850_timeSinceHitByPlayer += dt;\n    x854_lowHealthFrenzyTimer += dt;\n    if (x637_27_inProjectilePath) {\n      x854_lowHealthFrenzyTimer = 0.f;\n      x637_27_inProjectilePath = false;\n    }\n    if (x400_24_hitByPlayerProjectile) {\n      x850_timeSinceHitByPlayer = 0.f;\n      x400_24_hitByPlayerProjectile = false;\n    }\n  }\n\n  UpdateCloak(dt, mgr);\n\n  if (!x450_bodyController->IsFrozen()) {\n    if (x400_25_alive) {\n      x748_steeringDelayTimer = std::max(x748_steeringDelayTimer - dt, 0.f);\n      if (x637_28_noPlayerLos) {\n        x7ac_timeNoPlayerLos += dt;\n      } else {\n        x7ac_timeNoPlayerLos = 0.f;\n      }\n      x838_strafeDelayTimer = std::max(x838_strafeDelayTimer - dt, 0.f);\n      x8c0_dodgeDelayTimer = std::max(x8c0_dodgeDelayTimer - dt, 0.f);\n      CheckForProjectiles(mgr);\n    }\n    UpdateAttacks(dt, mgr);\n    UpdateAimBodyState(dt, mgr);\n    x860_ikChain.Update(dt);\n  }\n\n  if (x634_24_pendingAmbush) {\n    x634_24_pendingAmbush = false;\n    if (x634_25_ceilingAmbush) {\n      x450_bodyController->SetLocomotionType(pas::ELocomotionType::Internal6);\n    } else {\n      x450_bodyController->SetLocomotionType(pas::ELocomotionType::Crouch);\n    }\n    x330_stateMachineState.SetState(mgr, *this, GetStateMachine(), \"Ambushing\"sv);\n  }\n\n  if (!x85c_ragDoll || !x85c_ragDoll->IsPrimed()) {\n    CPatterned::Think(dt, mgr);\n    if (!x450_bodyController->IsFrozen()) {\n      x764_boneTracking.Update(dt);\n    }\n  } else {\n    UpdateAlphaDelta(dt, mgr);\n    UpdateDamageColor(dt);\n    if (CSfxHandle hnd = GetSfxHandle()) {\n      CSfxManager::UpdateEmitter(hnd, GetTranslation(), zeus::skZero3f, 1.f);\n    }\n  }\n  if (x85c_ragDoll) {\n    if (!x85c_ragDoll->IsPrimed()) {\n      x85c_ragDoll->Prime(mgr, GetTransform(), *x64_modelData);\n      zeus::CVector3f trans = GetTranslation();\n      SetTransform({});\n      SetTranslation(trans);\n      x450_bodyController->SetPlaybackRate(0.f);\n    } else {\n      float waterTop = -FLT_MAX;\n      if (xc4_fluidId != kInvalidUniqueId) {\n        if (TCastToConstPtr<CScriptWater> water = mgr.GetObjectById(xc4_fluidId)) {\n          if (water->GetActive()) {\n            waterTop = water->GetTriggerBoundsWR().max.z();\n          }\n        }\n      }\n      x85c_ragDoll->Update(mgr, dt * CalcDyingThinkRate(), waterTop);\n      x64_modelData->AdvanceParticles(x34_transform, dt, mgr);\n    }\n    if (x85c_ragDoll->IsOver() && !x85c_ragDoll->WillContinueSmallMovements() && !x400_27_fadeToDeath) {\n      /* Ragdoll has finished animating */\n      x400_27_fadeToDeath = true;\n      AddMaterial(EMaterialTypes::ProjectilePassthrough, mgr);\n      x3e8_alphaDelta = -0.333333f;\n      x638_30_allEnergyDrained = true;\n      SetMomentumWR(zeus::skZero3f);\n      CPhysicsActor::Stop();\n    }\n  }\n  if (x858_ragdollDelayTimer > 0.f) {\n    x858_ragdollDelayTimer -= dt;\n    if (x858_ragdollDelayTimer <= 0.f) {\n      if (!x85c_ragDoll) {\n        x85c_ragDoll =\n            std::make_unique<CPirateRagDoll>(mgr, this, x568_pirateData.x98_Sound_Impact,\n                                             (x635_30_floatingCorpse ? 3 : 0) | (x635_31_ragdollNoAiCollision ? 4 : 0));\n        RemoveMaterial(EMaterialTypes::Orbit, EMaterialTypes::Target, mgr);\n      }\n      x858_ragdollDelayTimer = 0.f;\n    }\n  }\n\n  if (!GetTranslation().isNaN()) {\n    m_lastKnownGoodXf = GetTransform();\n  } else {\n    spdlog::warn(\"BUG THIS!: Space Pirate being yeeted to the unknown\\n\");\n  }\n}\n\nvoid CSpacePirate::SetEyeParticleActive(CStateManager& mgr, bool active) {\n  if (!x636_24_trooper) {\n    if (!x634_29_onlyAttackInRange || x635_26_seated) {\n      if (!x635_27_shadowPirate) {\n        x64_modelData->GetAnimationData()->SetParticleEffectState(\"TwoEyes\"sv, active, mgr);\n      }\n    } else {\n      x64_modelData->GetAnimationData()->SetParticleEffectState(\"OneEye\"sv, active, mgr);\n    }\n  }\n}\n\nvoid CSpacePirate::SetVelocityForJump() {\n  if (x637_30_jumpVelSet) {\n    return;\n  }\n\n  zeus::CVector3f delta = x828_patrolDestPos - GetTranslation();\n  float grav = GetGravityConstant();\n  float jumpZ = x824_jumpHeight + std::max(float(x828_patrolDestPos.z()), float(GetTranslation().z()));\n  float zVel = std::sqrt((jumpZ - GetTranslation().z()) * (2.f * grav));\n  float dt = zVel / grav;\n  dt += std::sqrt((jumpZ - x828_patrolDestPos.z()) * 2.f / grav);\n  zeus::CVector3f vel = (1.f / dt) * delta;\n  vel.z() = zVel;\n  CPhysicsActor::SetVelocityWR(vel);\n  x637_30_jumpVelSet = true;\n}\n\nvoid CSpacePirate::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) {\n  if (x637_29_inWallHang || x634_25_ceilingAmbush) {\n    switch (msg) {\n    case EScriptObjectMessage::Falling:\n      if (!x637_29_inWallHang || x450_bodyController->GetCurrentStateId() != pas::EAnimationState::WallHang ||\n          x450_bodyController->GetBodyStateInfo().GetCurrentState()->ApplyGravity()) {\n        if (x634_25_ceilingAmbush) {\n          if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::Locomotion ||\n              (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::Jump &&\n               !x450_bodyController->GetBodyStateInfo().GetCurrentState()->IsMoving())) {\n            CPhysicsActor::Stop();\n            SetMomentumWR(zeus::skZero3f);\n            return;\n          }\n        }\n      }\n      break;\n    case EScriptObjectMessage::OnFloor:\n      x850_timeSinceHitByPlayer = FLT_MAX;\n      break;\n    default:\n      break;\n    }\n  }\n  switch (msg) {\n  case EScriptObjectMessage::Alert:\n  case EScriptObjectMessage::Activate:\n    if (GetActive()) {\n\n      if (x634_29_onlyAttackInRange) {\n        x638_31_mayStartAttack = true;\n      } else {\n        x400_24_hitByPlayerProjectile = true;\n      }\n\n      SquadAdd(mgr);\n    } else if (x634_25_ceilingAmbush) {\n      RemoveMaterial(EMaterialTypes::GroundCollider, mgr);\n      x328_27_onGround = false;\n    }\n    break;\n  default:\n    break;\n  }\n  CPatterned::AcceptScriptMsg(msg, sender, mgr);\n  switch (msg) {\n  case EScriptObjectMessage::InitializedInArea:\n    for (const auto& conn : GetConnectionList()) {\n      if (conn.x0_state == EScriptObjectState::Retreat && conn.x4_msg == EScriptObjectMessage::Next) {\n        TUniqueId id = mgr.GetIdForScript(conn.x8_objId);\n        if (TCastToPtr<CScriptCoverPoint> cp = mgr.ObjectById(id)) {\n          cp->Reserve(GetUniqueId());\n        }\n      } else if (conn.x0_state == EScriptObjectState::Patrol && conn.x4_msg == EScriptObjectMessage::Follow) {\n        x637_24_enablePatrol = true;\n      }\n    }\n    x660_pathFindSearch.SetArea(mgr.GetWorld()->GetAreaAlways(x4_areaId)->GetPostConstructed()->x10bc_pathArea);\n    if (x635_30_floatingCorpse) {\n      x858_ragdollDelayTimer = 0.01f;\n      RemoveMaterial(EMaterialTypes::Character, mgr);\n      x400_25_alive = false;\n      HealthInfo(mgr)->SetHP(-1.f);\n    } else {\n      SetEyeParticleActive(mgr, true);\n    }\n    break;\n  case EScriptObjectMessage::Decrement:\n    if (x85c_ragDoll) {\n      x85c_ragDoll->SetNoOverTimer(false);\n      x85c_ragDoll->SetContinueSmallMovements(false);\n    }\n    break;\n  case EScriptObjectMessage::Registered: {\n    if (x634_25_ceilingAmbush) {\n      x634_24_pendingAmbush = true;\n      if (x635_27_shadowPirate) {\n        x42c_color.a() = x568_pirateData.xb0_CloakOpacity;\n        x3e8_alphaDelta = -1.f;\n      }\n    }\n    x75c_ = mgr.GetActiveRandom()->Next() % 6;\n    CMaterialFilter filter = GetMaterialFilter();\n    filter.IncludeList().Remove(EMaterialTypes::AIPassthrough);\n    filter.ExcludeList().Add(EMaterialTypes::AIPassthrough);\n    SetMaterialFilter(filter);\n    break;\n  }\n  case EScriptObjectMessage::SetToZero:\n    if (x30_24_active) {\n      x636_29_enableRetreat = true;\n    }\n    break;\n  case EScriptObjectMessage::Falling:\n    if (!x450_bodyController->IsFrozen()) {\n      float zMom = GetGravityConstant() * xe8_mass;\n      if (x634_25_ceilingAmbush) {\n        zMom *= 3.f;\n      }\n      SetMomentumWR({0.f, 0.f, -zMom});\n    }\n    if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::Step) {\n      SetVelocityWR({0.f, 0.f, x138_velocity.z()});\n    }\n    x7c4_burstFire.SetBurstType(3);\n    break;\n  case EScriptObjectMessage::Jumped:\n    CPatterned::AcceptScriptMsg(msg, sender, mgr);\n    SetMomentumWR({0.f, 0.f, -GetGravityConstant() * xe8_mass});\n    SetVelocityForJump();\n    break;\n  case EScriptObjectMessage::OnFloor:\n    if (!x634_29_onlyAttackInRange) {\n      x7c4_burstFire.SetBurstType(1);\n    } else {\n      x7c4_burstFire.SetBurstType(4);\n    }\n    x637_30_jumpVelSet = false;\n    if (x635_27_shadowPirate && x138_velocity.z() < -1.f) {\n      x3e8_alphaDelta = 1.f;\n      x8a8_cloakDelayTimer += -0.05f * x138_velocity.z();\n      x8a8_cloakDelayTimer = zeus::clamp(0.f, x8a8_cloakDelayTimer, 1.f);\n      x8bc_maxCloakAlpha = 0.5f;\n      if (x400_25_alive) {\n        mgr.GetActorModelParticles()->LoadAndStartElectric(*this);\n        x8ac_electricParticleTimer = 1.f + x8a8_cloakDelayTimer;\n      }\n    }\n    break;\n  case EScriptObjectMessage::Action:\n    if (TCastToPtr<CScriptTargetingPoint> tp = mgr.ObjectById(sender)) {\n      if (tp->GetActive()) {\n        x764_boneTracking.SetTarget(sender);\n        x7c0_targetId = sender;\n        x400_24_hitByPlayerProjectile = true;\n      } else {\n        x764_boneTracking.SetTarget(mgr.GetPlayer().GetUniqueId());\n        x7c0_targetId = mgr.GetPlayer().GetUniqueId();\n      }\n      x7bc_attackRemTime = 0.f;\n    }\n    break;\n  case EScriptObjectMessage::Deactivate:\n  case EScriptObjectMessage::Deleted:\n    SquadRemove(mgr);\n    mChargePlayerList.remove(GetUniqueId());\n    break;\n  case EScriptObjectMessage::Start:\n    x639_26_started = false;\n    break;\n  case EScriptObjectMessage::Stop:\n    x639_26_started = true;\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CSpacePirate::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) {\n  if (x85c_ragDoll && x85c_ragDoll->IsPrimed()) {\n    x85c_ragDoll->PreRender(GetTranslation(), *x64_modelData);\n  }\n  CPatterned::PreRender(mgr, frustum);\n  if (!x85c_ragDoll || !x85c_ragDoll->IsPrimed()) {\n    x764_boneTracking.PreRender(mgr, *x64_modelData->GetAnimationData(), x34_transform, x64_modelData->GetScale(),\n                                *x450_bodyController);\n    x860_ikChain.PreRender(*x64_modelData->GetAnimationData(), x34_transform, x64_modelData->GetScale());\n  }\n}\n\nvoid CSpacePirate::Render(CStateManager& mgr) {\n  float time = x400_25_alive ? CGraphics::GetSecondsMod900() : 0.f;\n  CTimeProvider prov(time);\n  g_Renderer->SetGXRegister1Color(x8cc_trooperColor);\n  CPatterned::Render(mgr);\n}\n\nvoid CSpacePirate::CalculateRenderBounds() {\n  if (x85c_ragDoll && x85c_ragDoll->IsPrimed()) {\n    zeus::CVector3f margin = x64_modelData->GetScale() * 0.2f;\n    zeus::CAABox ragdollBounds = x85c_ragDoll->CalculateRenderBounds();\n    x9c_renderBounds = zeus::CAABox(ragdollBounds.min - margin, ragdollBounds.max + margin);\n  } else {\n    CActor::CalculateRenderBounds();\n  }\n}\n\nvoid CSpacePirate::Touch(CActor& other, CStateManager& mgr) {\n  CPatterned::Touch(other, mgr);\n  if (x85c_ragDoll && x85c_ragDoll->IsPrimed()) {\n    if (TCastToPtr<CScriptTrigger> trig = other) {\n      if (trig->GetActive() && True(trig->GetTriggerFlags() & ETriggerFlags::DetectAI) &&\n          trig->GetForceMagnitude() > 0.f) {\n        x85c_ragDoll->TorsoImpulse() += trig->GetForceVector();\n      }\n    }\n  }\n}\n\nzeus::CAABox CSpacePirate::GetSortingBounds(const CStateManager& mgr) const {\n  zeus::CAABox aabb = x64_modelData->GetBounds(x34_transform);\n  zeus::CVector3f radius = aabb.extents() * 0.5f;\n  zeus::CVector3f center = aabb.center();\n  return zeus::CAABox(center - radius, center + radius);\n}\n\nvoid CSpacePirate::DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) {\n  bool handled = false;\n  switch (type) {\n  case EUserEventType::BeginAction:\n    RemoveMaterial(EMaterialTypes::Solid, mgr);\n    x638_30_allEnergyDrained = true;\n    handled = true;\n    break;\n  case EUserEventType::EndAction:\n    x639_30_closeMelee = false;\n    handled = true;\n    break;\n  case EUserEventType::Projectile:\n  case EUserEventType::BecomeRagDoll:\n    if (x634_29_onlyAttackInRange || HealthInfo(mgr)->GetHP() <= 0.f) {\n      x858_ragdollDelayTimer = mgr.GetActiveRandom()->Float() * 0.05f + 0.001f;\n    }\n    handled = true;\n    break;\n  case EUserEventType::IkLock:\n    if (!x860_ikChain.GetActive()) {\n      CSegId lctrId = x64_modelData->GetAnimationData()->GetLocatorSegId(node.GetLocatorName());\n      if (lctrId != 3) {\n        zeus::CTransform xf = GetLctrTransform(lctrId);\n        x860_ikChain.Activate(*x64_modelData->GetAnimationData(), lctrId, xf);\n        x639_28_satUp = true;\n      }\n    }\n    handled = true;\n    break;\n  case EUserEventType::IkRelease:\n    x860_ikChain.Deactivate();\n    handled = true;\n    break;\n  case EUserEventType::ScreenShake:\n    SendScriptMsgs(EScriptObjectState::Play, mgr, EScriptObjectMessage::None);\n    handled = true;\n    break;\n  case EUserEventType::FadeOut:\n    x3e8_alphaDelta = -0.8f;\n    mgr.GetActorModelParticles()->LoadAndStartElectric(*this);\n    x8ac_electricParticleTimer = 1.f;\n    handled = true;\n    break;\n  default:\n    break;\n  }\n  if (!handled) {\n    CPatterned::DoUserAnimEvent(mgr, node, type, dt);\n  }\n}\n\nvoid CSpacePirate::Death(CStateManager& mgr, const zeus::CVector3f& direction, EScriptObjectState state) {\n  if (x400_25_alive) {\n    CPatterned::Death(mgr, direction, state);\n    if (x7b4_attachedActor != kInvalidUniqueId) {\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBCKnockDownCmd(GetTransform().basis[1], pas::ESeverity::Two));\n    }\n  }\n}\n\nvoid CSpacePirate::KnockBack(const zeus::CVector3f& backVec, CStateManager& mgr, const CDamageInfo& info,\n                             EKnockBackType type, bool inDeferred, float magnitude) {\n  if (!x634_25_ceilingAmbush || !x400_25_alive || inDeferred) {\n    x460_knockBackController.SetAutoResetImpulse(!x634_31_noKnockbackImpulseReset);\n    x460_knockBackController.SetAvailableState(EKnockBackAnimationState::KnockBack, IsOnGround());\n    x460_knockBackController.SetEnableFreeze(!((x636_24_trooper || x635_27_shadowPirate) &&\n                                               !info.GetWeaponMode().IsCharged() && !info.GetWeaponMode().IsComboed()));\n    CPatterned::KnockBack(backVec, mgr, info, type, inDeferred, magnitude);\n    if (x635_27_shadowPirate) {\n      if (x400_25_alive) {\n        if (magnitude >= 4.f && !x450_bodyController->IsFrozen()) {\n          x3e8_alphaDelta = 1.f;\n          x8a8_cloakDelayTimer += 0.1f * magnitude;\n          x8a8_cloakDelayTimer = zeus::clamp(0.f, x8a8_cloakDelayTimer, 1.f);\n          x8bc_maxCloakAlpha = 0.5f;\n          mgr.GetActorModelParticles()->LoadAndStartElectric(*this);\n          x8ac_electricParticleTimer = x8a8_cloakDelayTimer + 1.f;\n        }\n      } else {\n        x8bc_maxCloakAlpha = x3e8_alphaDelta = 1.f;\n        x8b8_minCloakAlpha = 0.f;\n        mgr.GetActorModelParticles()->LoadAndStartElectric(*this);\n        x8ac_electricParticleTimer = 2.f;\n      }\n    }\n    if (x635_30_floatingCorpse && x85c_ragDoll) {\n      x85c_ragDoll->TorsoImpulse() += (20.f * magnitude) * backVec;\n    }\n    if (x400_25_alive) {\n      if (x460_knockBackController.GetActiveParms().x0_animState == EKnockBackAnimationState::Hurled) {\n        x330_stateMachineState.SetState(mgr, *this, GetStateMachine(), \"GetUpNow\"sv);\n        CSfxManager::AddEmitter(x568_pirateData.xc0_Sound_Hurled, GetTranslation(), zeus::skZero3f, 1.f, true, false,\n                                0x7f, kInvalidAreaId);\n      }\n    } else {\n      if (x460_knockBackController.GetActiveParms().x0_animState == EKnockBackAnimationState::Hurled &&\n          x460_knockBackController.GetActiveParms().x4_animFollowup != EKnockBackAnimationFollowUp::LaggedBurnDeath &&\n          x460_knockBackController.GetActiveParms().x4_animFollowup != EKnockBackAnimationFollowUp::BurnDeath) {\n        CSfxManager::AddEmitter(x568_pirateData.xc2_Sound_Death, GetTranslation(), zeus::skZero3f, 1.f, true, false,\n                                0x7f, kInvalidAreaId);\n      }\n    }\n  }\n}\n\nbool CSpacePirate::IsListening() const { return true; }\n\nbool CSpacePirate::Listen(const zeus::CVector3f& pos, EListenNoiseType type) {\n  bool ret = false;\n  if (x400_25_alive) {\n    zeus::CVector3f delta = pos - GetTranslation();\n    if (delta.magSquared() < x568_pirateData.x14_HearingRadius * x568_pirateData.x14_HearingRadius &&\n        (x3c0_detectionHeightRange == 0.f ||\n         delta.z() * delta.z() < x3c0_detectionHeightRange * x3c0_detectionHeightRange)) {\n      x636_25_hearNoise = true;\n    }\n    if (type == EListenNoiseType::PlayerFire) {\n      x637_26_hearPlayerFire = true;\n    }\n  }\n  return ret;\n}\n\nzeus::CVector3f CSpacePirate::GetOrigin(const CStateManager& mgr, const CTeamAiRole& role,\n                                        const zeus::CVector3f& aimPos) const {\n  return GetTranslation();\n}\n\nvoid CSpacePirate::AvoidActors(CStateManager& mgr) {\n  for (CEntity* ent : mgr.GetListeningAiObjectList()) {\n    if (TCastToPtr<CPatterned> ai = ent) {\n      if (ai.GetPtr() != this && ai->GetAreaIdAlways() == GetAreaIdAlways()) {\n        zeus::CVector3f deltaVec =\n            x45c_steeringBehaviors.Separation(*this, ai->GetTranslation(), x568_pirateData.xc8_AvoidDistance);\n        if (!deltaVec.isZero()) {\n          x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(deltaVec, zeus::skZero3f, 1.f));\n          if (x748_steeringDelayTimer == 0.f) {\n            if (auto* otherSp = CPatterned::CastTo<CSpacePirate>(ai.GetPtr())) {\n              if (otherSp->x748_steeringDelayTimer == 0.f &&\n                  (otherSp->GetTranslation() - GetTranslation()).dot(GetTransform().basis[1]) > 0.f &&\n                  otherSp->GetTransform().basis[1].dot(otherSp->GetVelocity()) > 0.f) {\n                x748_steeringDelayTimer = 1.f;\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n}\n\nbool CSpacePirate::AttachActorToPirate(TUniqueId id) {\n  if (x7b4_attachedActor == kInvalidUniqueId) {\n    x7b4_attachedActor = id;\n    return true;\n  }\n  return false;\n}\n\nvoid CSpacePirate::SetAttackTarget(TUniqueId id) {\n  x7c0_targetId = id;\n  x7c4_burstFire.SetBurstType(1);\n  x7bc_attackRemTime = 0.f;\n}\n\nvoid CSpacePirate::Patrol(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n    x644_steeringSpeed = x450_bodyController->GetBodyStateInfo().GetLocomotionSpeed(pas::ELocomotionAnim::Walk) /\n                         x450_bodyController->GetBodyStateInfo().GetLocomotionSpeed(pas::ELocomotionAnim::Run);\n    break;\n  case EStateMsg::Deactivate:\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Combat);\n    if (!x639_31_sentAttackMsg) {\n      x639_31_sentAttackMsg = true;\n      SendScriptMsgs(EScriptObjectState::Attack, mgr, EScriptObjectMessage::None);\n    }\n    break;\n  default:\n    break;\n  }\n  if (x637_24_enablePatrol) {\n    CPatterned::Patrol(mgr, msg, dt);\n    switch (msg) {\n    case EStateMsg::Activate:\n      x450_bodyController->GetCommandMgr().SetSteeringBlendMode(ESteeringBlendMode::FullSpeed);\n      x450_bodyController->SetTurnSpeed(x450_bodyController->GetTurnSpeed() / 1.25f);\n      break;\n    case EStateMsg::Update:\n      AvoidActors(mgr);\n      x828_patrolDestPos = x2e0_destPos;\n      break;\n    case EStateMsg::Deactivate:\n      x450_bodyController->GetCommandMgr().SetSteeringBlendMode(ESteeringBlendMode::Normal);\n      x450_bodyController->SetTurnSpeed(x450_bodyController->GetTurnSpeed() * 1.25f);\n      break;\n    }\n  }\n}\n\nvoid CSpacePirate::Dead(CStateManager& mgr, EStateMsg msg, float dt) {\n  CPatterned::Dead(mgr, msg, dt);\n  switch (msg) {\n  case EStateMsg::Activate:\n    x764_boneTracking.SetActive(false);\n    SetEyeParticleActive(mgr, false);\n    SquadReset(mgr);\n    break;\n  case EStateMsg::Update:\n    if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::Death) {\n      RemoveMaterial(EMaterialTypes::Target, EMaterialTypes::Orbit, mgr);\n      RemoveMaterial(EMaterialTypes::GroundCollider, EMaterialTypes::NoStaticCollision, EMaterialTypes::AIBlock, mgr);\n      AddMaterial(EMaterialTypes::ProjectilePassthrough, mgr);\n      SetMomentumWR(zeus::skZero3f);\n      CPhysicsActor::Stop();\n    }\n    break;\n  default:\n    break;\n  }\n}\n\nbool CSpacePirate::LineOfSightTest(const CStateManager& mgr, const zeus::CVector3f& eyePos,\n                                   const zeus::CVector3f& targetPos, const CMaterialList& excludeList) const {\n  return mgr.RayCollideWorld(eyePos, targetPos,\n                             CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, excludeList), this);\n}\n\nvoid CSpacePirate::UpdateCantSeePlayer(CStateManager& mgr) {\n  if ((++x7b0_cantSeePlayerCycleCounter + 1) % 7 == 0) {\n    zeus::CVector3f eyePos = GetTranslation() + zeus::CVector3f(0.f, 0.f, x7a8_eyeHeight);\n    zeus::CVector3f aimPos = mgr.GetPlayer().GetAimPosition(mgr, 0.f);\n    if (CScriptCoverPoint* cp = GetCoverPoint(mgr, x640_coverPoint)) {\n      switch (x79c_coverDir) {\n      case pas::ECoverDirection::Left:\n        eyePos -= GetTransform().basis[1] * 2.f;\n        break;\n      case pas::ECoverDirection::Right:\n        eyePos += GetTransform().basis[1] * 2.f;\n        break;\n      default:\n        break;\n      }\n    } else {\n      eyePos += (aimPos - eyePos).normalized().cross(zeus::skUp) * 1.1f;\n    }\n    x637_28_noPlayerLos = !LineOfSightTest(mgr, eyePos, mgr.GetPlayer().GetAimPosition(mgr, 0.f),\n                                           {EMaterialTypes::Player, EMaterialTypes::ProjectilePassthrough});\n  }\n}\n\nvoid CSpacePirate::UpdateHeldPosition(CStateManager& mgr, float dt) {\n  if ((mgr.GetPlayer().GetTranslation().toVec2f() - x8d0_heldPosition).magSquared() < 3.f) {\n    x8d8_holdPositionTime += dt;\n  } else {\n    x8d0_heldPosition = mgr.GetPlayer().GetTranslation().toVec2f();\n    x8d8_holdPositionTime = 0.f;\n  }\n}\n\nvoid CSpacePirate::PathFind(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x840_jumpPoint = kInvalidUniqueId;\n    if (CScriptCoverPoint* cp = GetCoverPoint(mgr, x640_coverPoint)) {\n      x2ec_reflectedDestPos = GetTranslation();\n      x328_24_inPosition = false;\n      x2dc_destObj = cp->GetUniqueId();\n      x2e0_destPos = cp->GetTranslation();\n    }\n    if (GetSearchPath()->Search(GetTranslation(), x2e0_destPos) == CPathFindSearch::EResult::Success) {\n      x2ec_reflectedDestPos = GetTranslation();\n      u32 destWp = GetSearchPath()->GetCurrentWaypoint();\n      if ((destWp + 1) < GetSearchPath()->GetWaypoints().size()) {\n        ++destWp;\n      }\n\n      x2e0_destPos = GetSearchPath()->GetWaypoints()[destWp];\n      x328_24_inPosition = false;\n      x450_bodyController->GetCommandMgr().DeliverCmd(\n          CBCLocomotionCmd(x2e0_destPos - GetTranslation(), zeus::skZero3f, 1.f));\n    } else {\n      CScriptAiJumpPoint* bestJp = nullptr;\n      float minDist = FLT_MAX;\n      for (CEntity* ent : mgr.GetAiWaypointObjectList()) {\n        if (TCastToPtr<CScriptAiJumpPoint> jp = ent) {\n          if (jp->GetActive() && !jp->GetInUse(GetUniqueId()) && jp->GetJumpTarget() == kInvalidUniqueId &&\n              GetAreaIdAlways() == jp->GetAreaIdAlways()) {\n            zeus::CVector3f toJp = jp->GetTranslation() - GetTranslation();\n            float f30 = toJp.magSquared();\n            if (f30 > 25.f && jp->GetTransform().basis[1].dot(toJp) > 0.f) {\n              if (TCastToConstPtr<CScriptWaypoint> wp = mgr.GetObjectById(jp->GetJumpPoint())) {\n                if ((wp->GetTranslation().z() - jp->GetTranslation().z()) * (x2e0_destPos.z() - GetTranslation().z()) >\n                    0.f) {\n                  zeus::CVector3f delta = x2e0_destPos - wp->GetTranslation();\n                  f30 += 4.f * toJp.z() * toJp.z();\n                  f30 += delta.magSquared() + delta.z() * delta.z() * 9.f;\n                  if (f30 < minDist && GetSearchPath()->PathExists(GetTranslation(), jp->GetTranslation()) ==\n                                           CPathFindSearch::EResult::Success) {\n                    bool r24 = false;\n                    auto res = GetSearchPath()->PathExists(wp->GetTranslation(), x2e0_destPos);\n                    if (res != CPathFindSearch::EResult::Success) {\n                      f30 += 1000.f;\n                    }\n                    if (res == CPathFindSearch::EResult::Success) {\n                      r24 = true;\n                    }\n                    if (f30 < minDist) {\n                      minDist = f30;\n                      bestJp = jp.GetPtr();\n                      if (r24) {\n                        break;\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          }\n        }\n      }\n      if (bestJp) {\n        x2e0_destPos = bestJp->GetTranslation();\n        if (GetSearchPath()->Search(GetTranslation(), x2e0_destPos) == CPathFindSearch::EResult::Success) {\n          x2ec_reflectedDestPos = GetTranslation();\n          u32 destWp = GetSearchPath()->GetCurrentWaypoint();\n          if ((destWp + 1) < GetSearchPath()->GetWaypoints().size()) {\n            ++destWp;\n          }\n\n          x2e0_destPos = GetSearchPath()->GetWaypoints()[destWp];\n          x328_24_inPosition = false;\n          x840_jumpPoint = bestJp->GetUniqueId();\n          x824_jumpHeight = bestJp->GetJumpApex();\n          if (TCastToConstPtr<CScriptWaypoint> wp = mgr.GetObjectById(bestJp->GetJumpPoint())) {\n            x828_patrolDestPos = wp->GetTranslation();\n            x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(x2e0_destPos, zeus::skZero3f, 1.f));\n            x30c_behaviourOrient = EBehaviourOrient::MoveDir;\n          }\n        }\n      }\n    }\n    x450_bodyController->GetCommandMgr().SetSteeringBlendMode(ESteeringBlendMode::FullSpeed);\n    if (x637_25_enableAim) {\n      x644_steeringSpeed = 1.f;\n    }\n    x639_27_inRange = false;\n    x63a_24_normalDodge = true;\n    break;\n  case EStateMsg::Update:\n    CPatterned::PathFind(mgr, msg, dt);\n    if (x840_jumpPoint != kInvalidUniqueId) {\n      if (TCastToPtr<CScriptAiJumpPoint> jp = mgr.ObjectById(x840_jumpPoint)) {\n        float jumpDistance =\n            (1.5f * dt + 0.1f) * x64_modelData->GetScale().y() * x450_bodyController->GetBodyStateInfo().GetMaxSpeed() +\n            x7a4_intoJumpDist;\n        if ((GetTranslation() - jp->GetTranslation()).magSquared() < jumpDistance * jumpDistance) {\n          x32c_animState = EAnimState::Ready;\n          TryCommand(mgr, pas::EAnimationState::Jump, &CPatterned::TryJump, 0);\n        }\n      }\n    }\n    AvoidActors(mgr);\n    if (!x639_27_inRange) {\n      if (CScriptCoverPoint* cp = GetCoverPoint(mgr, x640_coverPoint)) {\n        x754_coverRange =\n            (1.5f * dt + 0.1f) * x64_modelData->GetScale().y() * x450_bodyController->GetBodyStateInfo().GetMaxSpeed();\n        if (cp->ShouldWallHang()) {\n          x754_coverRange += x7a4_intoJumpDist;\n        }\n        x639_27_inRange = (GetTranslation() - cp->GetTranslation()).magSquared() < x754_coverRange * x754_coverRange;\n      }\n    }\n    UpdateCantSeePlayer(mgr);\n    UpdateHeldPosition(mgr, dt);\n    break;\n  case EStateMsg::Deactivate:\n    x32c_animState = EAnimState::NotReady;\n    x840_jumpPoint = kInvalidUniqueId;\n    x30c_behaviourOrient = EBehaviourOrient::Constant;\n    x639_27_inRange = false;\n    x450_bodyController->GetCommandMgr().SetSteeringBlendMode(ESteeringBlendMode::Normal);\n    break;\n  }\n}\n\nvoid CSpacePirate::TargetPatrol(CStateManager& mgr, EStateMsg msg, float dt) {\n  CPatterned::Patrol(mgr, msg, dt);\n  switch (msg) {\n  case EStateMsg::Activate:\n    x644_steeringSpeed = 1.f;\n    x450_bodyController->GetCommandMgr().SetSteeringBlendMode(ESteeringBlendMode::FullSpeed);\n    x2dc_destObj = GetWaypointForState(mgr, EScriptObjectState::Attack, EScriptObjectMessage::Follow);\n    break;\n  case EStateMsg::Update:\n    if (TCastToPtr<CScriptWaypoint> wp = mgr.ObjectById(x2dc_destObj)) {\n      if ((wp->GetBehaviourModifiers() & 0x2) != 0 || ((wp->GetBehaviourModifiers() & 0x4) != 0)) {\n        float f0 = x450_bodyController->GetBodyStateInfo().GetMaxSpeed() *\n                       ((1.5f * dt + 0.1f) * x64_modelData->GetScale().y()) +\n                   x7a4_intoJumpDist;\n        if ((GetTranslation() - wp->GetTranslation()).magSquared() < f0 * f0) {\n          x328_24_inPosition = true;\n          x824_jumpHeight = (wp->GetBehaviourModifiers() & 0x2) != 0 ? 3.f : 0.f;\n        }\n      }\n    }\n    if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::Jump) {\n      bool targetPlayer = true;\n      if (TCastToPtr<CScriptWaypoint> wp = mgr.ObjectById(x2dc_destObj)) {\n        for (const auto& conn : wp->GetConnectionList()) {\n          if (conn.x0_state == EScriptObjectState::Arrived && conn.x4_msg == EScriptObjectMessage::Next) {\n            targetPlayer = false;\n          }\n        }\n      }\n      if (targetPlayer) {\n        x450_bodyController->GetCommandMgr().DeliverTargetVector(mgr.GetPlayer().GetTranslation() - GetTranslation());\n      }\n    }\n\n    x828_patrolDestPos = x2e0_destPos;\n    break;\n  case EStateMsg::Deactivate:\n    x450_bodyController->GetCommandMgr().SetSteeringBlendMode(ESteeringBlendMode::Normal);\n    break;\n  }\n}\n\nvoid CSpacePirate::TargetCover(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    if (CScriptCoverPoint* cp = GetCoverPoint(mgr, x640_coverPoint)) {\n      x2dc_destObj = x640_coverPoint;\n      x2e0_destPos = cp->GetTranslation();\n    }\n    x2ec_reflectedDestPos = GetTranslation();\n    x328_24_inPosition = false;\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CSpacePirate::Halt(CStateManager& mgr, EStateMsg msg, float dt) { x644_steeringSpeed = 0.f; }\n\nvoid CSpacePirate::Run(CStateManager& mgr, EStateMsg msg, float dt) { x644_steeringSpeed = 1.f; }\n\nvoid CSpacePirate::Generate(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x637_25_enableAim = true;\n    if (!x639_31_sentAttackMsg) {\n      x639_31_sentAttackMsg = true;\n      SendScriptMsgs(EScriptObjectState::Attack, mgr, EScriptObjectMessage::None);\n    }\n    x32c_animState = EAnimState::Ready;\n    if (x634_25_ceilingAmbush) {\n      x2e0_destPos = GetTranslation() + zeus::skDown;\n      x828_patrolDestPos = x2e0_destPos;\n      x824_jumpHeight = 0.f;\n    } else {\n      TUniqueId wpId = GetWaypointForState(mgr, EScriptObjectState::Attack, EScriptObjectMessage::Follow);\n      if (TCastToConstPtr<CActor> act = mgr.GetObjectById(wpId)) {\n        x2e0_destPos = act->GetTranslation();\n        x828_patrolDestPos = x2e0_destPos;\n        x824_jumpHeight = 3.f;\n      }\n      x450_bodyController->SetLocomotionType(pas::ELocomotionType::Combat);\n    }\n    break;\n  case EStateMsg::Update:\n    TryCommand(mgr, pas::EAnimationState::Jump, &CPatterned::TryJump, x634_25_ceilingAmbush ? 2 : 0);\n    if (x32c_animState == EAnimState::Repeat) {\n      x450_bodyController->SetLocomotionType(pas::ELocomotionType::Combat);\n    }\n    x450_bodyController->GetCommandMgr().DeliverTargetVector(mgr.GetPlayer().GetTranslation() - GetTranslation());\n    break;\n  case EStateMsg::Deactivate:\n    x32c_animState = EAnimState::NotReady;\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Combat);\n    x824_jumpHeight = 3.f;\n    x634_25_ceilingAmbush = false;\n    x764_boneTracking.SetActive(true);\n    x764_boneTracking.SetTarget(mgr.GetPlayer().GetUniqueId());\n    break;\n  }\n}\n\nvoid CSpacePirate::Deactivate(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x401_30_pendingDeath = true;\n  }\n}\n\nvoid CSpacePirate::CheckBlade(CStateManager& mgr) {\n  if (x638_25_appliedBladeDamage || x7b9_swooshSeg.IsInvalid()) {\n    return;\n  }\n\n  if (TCastToPtr<CPhysicsActor> act = mgr.ObjectById(x7c0_targetId)) {\n    zeus::CVector3f extent = x64_modelData->GetScale() * 0.5f;\n    zeus::CVector3f swooshPos = GetLctrTransform(x7b9_swooshSeg).origin;\n    if (zeus::CAABox(swooshPos - extent, swooshPos + extent).intersects(act->GetBoundingBox())) {\n      mgr.ApplyDamage(GetUniqueId(), act->GetUniqueId(), GetUniqueId(), x568_pirateData.x4c_BladeDamage,\n                      CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), zeus::skZero3f);\n      x638_25_appliedBladeDamage = true;\n    }\n  }\n}\n\nvoid CSpacePirate::Attack(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x32c_animState = EAnimState::Ready;\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Combat);\n    x2e0_destPos = GetTargetPos(mgr);\n    x648_targetDelta = x2e0_destPos - GetBoundingBox().center();\n    x644_steeringSpeed = 0.f;\n    x636_26_enableMeleeAttack = false;\n    if (!x635_24_noMeleeAttack && x648_targetDelta.magSquared() < x2fc_minAttackRange * x2fc_minAttackRange &&\n        x648_targetDelta.z() * x648_targetDelta.z() < 4.f) {\n      x636_26_enableMeleeAttack = true;\n      x638_25_appliedBladeDamage = false;\n    } else if (x648_targetDelta.normalized().dot(GetTransform().basis[1]) < 0.8f) {\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(zeus::skZero3f, x648_targetDelta, 1.f));\n    }\n    x636_31_inAttackState = true;\n    x8bc_maxCloakAlpha = 0.75f;\n    break;\n  case EStateMsg::Update:\n    if (x636_26_enableMeleeAttack) {\n      TryCommand(mgr, pas::EAnimationState::MeleeAttack, &CPatterned::TryMeleeAttack, 1);\n      x450_bodyController->GetCommandMgr().DeliverTargetVector(x648_targetDelta);\n      CheckBlade(mgr);\n      if (x635_27_shadowPirate) {\n        if (x32c_animState == EAnimState::Over) {\n          x3e8_alphaDelta = -0.4f;\n        } else {\n          x3e8_alphaDelta = 1.f;\n          x8bc_maxCloakAlpha = 0.75f;\n        }\n      }\n    }\n    UpdateCantSeePlayer(mgr);\n    UpdateHeldPosition(mgr, dt);\n    break;\n  case EStateMsg::Deactivate:\n    x636_26_enableMeleeAttack = false;\n    x636_31_inAttackState = false;\n    x32c_animState = EAnimState::NotReady;\n    break;\n  }\n}\n\nbool CSpacePirate::CantJumpBack(const CStateManager& mgr, const zeus::CVector3f& dir, float dist) const {\n  const zeus::CVector3f center = GetBoundingBox().center();\n  if (!LineOfSightTest(mgr, center, center + dist * dir, {})) {\n    return false;\n  }\n  const zeus::CVector3f center2 = (dist * 0.5f) * dir + center;\n  if (LineOfSightTest(mgr, center2, center2 + 5.f * zeus::skDown, {})) {\n    return false;\n  }\n  const zeus::CVector3f center3 = dist * dir + center;\n  return !LineOfSightTest(mgr, center3, center3 + 5.f * zeus::skDown, {});\n}\n\nvoid CSpacePirate::JumpBack(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (!ShouldJumpBack(mgr, dt)) {\n    return;\n  }\n  switch (msg) {\n  case EStateMsg::Activate:\n    if (!x634_29_onlyAttackInRange && !CantJumpBack(mgr, -GetTransform().basis[1], 5.f)) {\n      float backupChHeight = GetSearchPath()->GetCharacterHeight();\n      x660_pathFindSearch.SetCharacterHeight(5.f + backupChHeight);\n      zeus::CVector3f dest = GetTransform().basis[1] * 10.f + GetTranslation();\n      if (GetSearchPath()->Search(GetTranslation(), dest) == CPathFindSearch::EResult::Success &&\n          (GetSearchPath()->GetWaypoints().back() - dest).magSquared() < 3.f &&\n          std::fabs(GetSearchPath()->RemainingPathDistance(GetTranslation()) - 10.f) < 4.f) {\n        x828_patrolDestPos = GetSearchPath()->GetWaypoints().back();\n        x824_jumpHeight = 5.f;\n        x639_25_useJumpBackJump = true;\n        x32c_animState = EAnimState::Ready;\n      }\n      GetSearchPath()->SetCharacterHeight(backupChHeight);\n    }\n    break;\n  case EStateMsg::Update:\n    if (!x639_25_useJumpBackJump) {\n      x450_bodyController->GetCommandMgr().DeliverCmd(\n          CBCStepCmd(pas::EStepDirection::Backward, pas::EStepType::Normal));\n      x450_bodyController->GetCommandMgr().DeliverTargetVector(GetTargetPos(mgr) - GetTranslation());\n    } else {\n      TryCommand(mgr, pas::EAnimationState::Jump, &CPatterned::TryJump, 0);\n    }\n    break;\n  case EStateMsg::Deactivate:\n    if (x639_25_useJumpBackJump) {\n      x32c_animState = EAnimState::NotReady;\n      x639_25_useJumpBackJump = false;\n    }\n    x8d8_holdPositionTime = 0.f;\n    break;\n  }\n}\n\nvoid CSpacePirate::DoubleSnap(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    if (!x635_24_noMeleeAttack) {\n      x32c_animState = EAnimState::Ready;\n    }\n    x2e0_destPos = GetTargetPos(mgr);\n    x648_targetDelta = x2e0_destPos - GetTranslation();\n    x644_steeringSpeed = 0.f;\n    x636_26_enableMeleeAttack = true;\n    x83c_meleeSeverity = pas::ESeverity::One;\n    x638_25_appliedBladeDamage = false;\n    x636_31_inAttackState = true;\n    x639_30_closeMelee = false;\n    mChargePlayerList.remove(GetUniqueId());\n    break;\n  case EStateMsg::Update:\n    TryCommand(mgr, pas::EAnimationState::MeleeAttack, &CPatterned::TryMeleeAttack, int(x83c_meleeSeverity));\n    if (x83c_meleeSeverity == pas::ESeverity::One && x32c_animState == EAnimState::Over) {\n      zeus::CVector3f delta = GetTargetPos(mgr) - GetTranslation();\n      if (delta.magSquared() < x2fc_minAttackRange * x2fc_minAttackRange &&\n          delta.normalized().dot(GetTransform().basis[1]) > -0.123f) {\n        x32c_animState = EAnimState::Ready;\n        x83c_meleeSeverity = pas::ESeverity::Two;\n        x638_25_appliedBladeDamage = false;\n        x648_targetDelta = delta;\n        x639_30_closeMelee = true;\n      }\n    }\n    if (x639_30_closeMelee) {\n      x648_targetDelta = GetTargetPos(mgr) - GetTranslation();\n    }\n    x450_bodyController->GetCommandMgr().DeliverTargetVector(x648_targetDelta);\n    if (x635_27_shadowPirate) {\n      if (x32c_animState == EAnimState::Over) {\n        x3e8_alphaDelta = -0.4f;\n      } else {\n        x3e8_alphaDelta = 1.f;\n        x8bc_maxCloakAlpha = 0.75f;\n      }\n    }\n    UpdateCantSeePlayer(mgr);\n    UpdateHeldPosition(mgr, dt);\n    CheckBlade(mgr);\n    break;\n  case EStateMsg::Deactivate:\n    x636_26_enableMeleeAttack = false;\n    x636_31_inAttackState = false;\n    x32c_animState = EAnimState::NotReady;\n    break;\n  }\n}\n\nvoid CSpacePirate::Shuffle(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x450_bodyController->GetCommandMgr().SetSteeringBlendMode(ESteeringBlendMode::Normal);\n    if (!x634_28_noShuffleCloseCheck && TooClose(mgr, 0.f)) {\n      SetDestPos(GetTranslation() +\n                 x2fc_minAttackRange * (GetTranslation() - mgr.GetPlayer().GetTranslation()).normalized() +\n                 mgr.Random2f(0.f, 5.f));\n      x2dc_destObj = kInvalidUniqueId;\n      x30c_behaviourOrient = EBehaviourOrient::Constant;\n      x636_30_shuffleClose = true;\n    } else {\n      zeus::CVector3f fromPlayer = GetTranslation() - mgr.GetPlayer().GetTranslation();\n      SetDestPos(mgr.GetPlayer().GetTranslation() +\n                 (x300_maxAttackRange * mgr.GetActiveRandom()->Float() + x300_maxAttackRange) *\n                     fromPlayer.normalized() +\n                 zeus::skUp.cross(fromPlayer).normalized() *\n                     (2.f * x300_maxAttackRange * (mgr.GetActiveRandom()->Float() - 0.5f)));\n      x2dc_destObj = kInvalidUniqueId;\n      x30c_behaviourOrient = EBehaviourOrient::MoveDir;\n      x636_30_shuffleClose = false;\n    }\n    x644_steeringSpeed = 1.f;\n  }\n  CPatterned::PathFind(mgr, msg, dt);\n  switch (msg) {\n  case EStateMsg::Update:\n    AvoidActors(mgr);\n    break;\n  case EStateMsg::Deactivate:\n    x636_30_shuffleClose = false;\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CSpacePirate::TurnAround(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    SetDestPos(GetTargetPos(mgr));\n    if (GetTransform().basis[1].dot((x2e0_destPos - GetTranslation()).normalized()) < 0.8f) {\n      x32c_animState = EAnimState::Ready;\n    }\n    break;\n  case EStateMsg::Update:\n    TryCommand(mgr, pas::EAnimationState::Turn, &CPatterned::TryTurn, 0);\n    UpdateCantSeePlayer(mgr);\n    break;\n  case EStateMsg::Deactivate:\n    x32c_animState = EAnimState::NotReady;\n    break;\n  }\n}\n\nvoid CSpacePirate::Skid(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x838_strafeDelayTimer = 4.f;\n    x636_31_inAttackState = true;\n    break;\n  case EStateMsg::Update:\n    if (x450_bodyController->GetCurrentStateId() != pas::EAnimationState::Step) {\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBCStepCmd(x834_skidDir, pas::EStepType::Normal));\n    }\n    break;\n  case EStateMsg::Deactivate:\n    x636_31_inAttackState = false;\n    break;\n  }\n}\n\nvoid CSpacePirate::CoverAttack(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBodyStateCmd(EBodyStateCmd::LeanFromCover));\n    x636_31_inAttackState = true;\n    break;\n  case EStateMsg::Update:\n    UpdateCantSeePlayer(mgr);\n    break;\n  case EStateMsg::Deactivate:\n    x636_31_inAttackState = false;\n    break;\n  }\n}\n\nvoid CSpacePirate::Crouch(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Crouch);\n    if (CScriptCoverPoint* cp = GetCoverPoint(mgr, x640_coverPoint)) {\n      x648_targetDelta = cp->GetTransform().basis[1];\n    }\n    x644_steeringSpeed = 0.f;\n    TargetPlayer(mgr, msg, dt);\n    x79c_coverDir = pas::ECoverDirection::Invalid;\n    break;\n  case EStateMsg::Update:\n    x450_bodyController->GetCommandMgr().DeliverTargetVector(x648_targetDelta);\n    UpdateCantSeePlayer(mgr);\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CSpacePirate::UpdateLeashTimer(float dt) {\n  if (x450_bodyController->IsFrozen() || x450_bodyController->IsElectrocuting()) {\n    return;\n  }\n  x8dc_leashTimer += dt;\n}\n\nvoid CSpacePirate::GetUp(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x32c_animState = EAnimState::Ready;\n    SquadReset(mgr);\n    x8dc_leashTimer = 0.f;\n    break;\n  case EStateMsg::Update:\n    if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::LieOnGround &&\n        x660_pathFindSearch.Search(GetTranslation(), GetTranslation()) == CPathFindSearch::EResult::NoSourcePoint) {\n      x401_30_pendingDeath = true;\n    } else {\n      TryCommand(mgr, pas::EAnimationState::Getup, &CPatterned::TryGetUp, int(pas::EGetupType::Zero));\n    }\n    UpdateLeashTimer(dt);\n    break;\n  case EStateMsg::Deactivate:\n    x32c_animState = EAnimState::Repeat;\n    break;\n  }\n}\n\nvoid CSpacePirate::Taunt(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x637_25_enableAim = true;\n    x764_boneTracking.SetActive(true);\n    x764_boneTracking.SetTarget(mgr.GetPlayer().GetUniqueId());\n    if (x7c0_targetId == kInvalidUniqueId) {\n      x7c0_targetId = mgr.GetPlayer().GetUniqueId();\n    }\n    if (x450_bodyController->HasBodyState(pas::EAnimationState::Taunt)) {\n      if (!x635_27_shadowPirate) {\n        bool withOtherPirate = true;\n        if (x634_27_melee) {\n          const auto bestAnim = x450_bodyController->GetPASDatabase().FindBestAnimation(\n              CPASAnimParmData{pas::EAnimationState::Taunt, CPASAnimParm::FromEnum(2)}, *mgr.GetActiveRandom(), -1);\n          if (bestAnim.first > 0.f) {\n            withOtherPirate = false;\n            x760_taunt = pas::ETauntType::Two;\n          }\n        }\n        if (withOtherPirate) {\n          withOtherPirate = false;\n          for (CEntity* ent : mgr.GetListeningAiObjectList()) {\n            if (auto* otherSp = CPatterned::CastTo<CSpacePirate>(ent)) {\n              if (otherSp != this && !otherSp->x637_25_enableAim && otherSp->x400_25_alive &&\n                  otherSp->GetAreaIdAlways() == GetAreaIdAlways()) {\n                if ((otherSp->GetTranslation() - GetTranslation()).magSquared() <\n                    x568_pirateData.x14_HearingRadius * x568_pirateData.x14_HearingRadius) {\n                  withOtherPirate = true;\n                }\n              }\n            }\n          }\n          x760_taunt = withOtherPirate ? pas::ETauntType::Zero : pas::ETauntType::One;\n        }\n      } else {\n        x760_taunt = x635_28_alertBeforeCloak ? pas::ETauntType::One : pas::ETauntType::Zero;\n      }\n      x32c_animState = EAnimState::Ready;\n    }\n    CSfxManager::AddEmitter(x568_pirateData.xa4_Sound_Alert, GetTranslation(), zeus::skZero3f, true, false, 0x7f,\n                            kInvalidAreaId);\n    break;\n  case EStateMsg::Update:\n    TryCommand(mgr, pas::EAnimationState::Taunt, &CPatterned::TryTaunt, int(x760_taunt));\n    break;\n  case EStateMsg::Deactivate:\n    if (x760_taunt == pas::ETauntType::Zero) {\n      mgr.InformListeners(GetTranslation(), EListenNoiseType::PlayerFire);\n    }\n    x32c_animState = EAnimState::NotReady;\n    break;\n  }\n}\n\nvoid CSpacePirate::Flee(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x450_bodyController->GetCommandMgr().SetSteeringBlendMode(ESteeringBlendMode::Normal);\n    SetDestPos(GetTranslation() + (GetTranslation() - mgr.GetPlayer().GetTranslation()).normalized() * 15.f);\n    x30c_behaviourOrient = EBehaviourOrient::MoveDir;\n    x644_steeringSpeed = 1.f;\n    break;\n  case EStateMsg::Update:\n    AvoidActors(mgr);\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CSpacePirate::Lurk(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    ReleaseCoverPoint(mgr, x640_coverPoint);\n    x644_steeringSpeed = 0.f;\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Combat);\n    x637_28_noPlayerLos = true;\n    x7ac_timeNoPlayerLos = 0.f;\n    x638_26_alwaysAggressive = mgr.GetActiveRandom()->Range(0.f, 100.f) < x568_pirateData.x0_AggressionCheck;\n    x638_27_coverCheck = mgr.GetActiveRandom()->Range(0.f, 100.f) < x568_pirateData.x4_CoverCheck;\n    x638_28_enableDodge = mgr.GetActiveRandom()->Range(0.f, 100.f) < x568_pirateData.x94_DodgeCheck;\n    x637_25_enableAim = true;\n    x764_boneTracking.SetActive(true);\n    x764_boneTracking.SetTarget(mgr.GetPlayer().GetUniqueId());\n    if (x634_29_onlyAttackInRange) {\n      x7c4_burstFire.SetBurstType(4);\n      x450_bodyController->SetLocomotionType(pas::ELocomotionType::Combat);\n    }\n    x63a_24_normalDodge = false;\n    break;\n  case EStateMsg::Update:\n    if (x450_bodyController->HasBodyState(pas::EAnimationState::Turn)) {\n      if (x32c_animState != EAnimState::NotReady) {\n        TryCommand(mgr, pas::EAnimationState::Turn, &CPatterned::TryTurn, 0);\n      }\n      if (x32c_animState != EAnimState::Repeat) {\n        x2e0_destPos = GetTargetPos(mgr);\n        if ((x2e0_destPos - GetTranslation()).normalized().dot(GetTransform().basis[1]) < 0.9f) {\n          x32c_animState = EAnimState::Ready;\n        }\n      }\n    }\n    if (x635_26_seated && x639_28_satUp) {\n      if (x7bc_attackRemTime > x304_averageAttackTime &&\n          x450_bodyController->GetLocomotionType() == pas::ELocomotionType::Combat) {\n        x450_bodyController->SetLocomotionType(pas::ELocomotionType::Internal5);\n      } else if (x7bc_attackRemTime < 0.5f * x304_averageAttackTime &&\n                 x450_bodyController->GetLocomotionType() == pas::ELocomotionType::Internal5) {\n        x450_bodyController->SetLocomotionType(pas::ELocomotionType::Combat);\n      }\n    }\n    UpdateCantSeePlayer(mgr);\n    UpdateHeldPosition(mgr, dt);\n    break;\n  case EStateMsg::Deactivate:\n    x638_26_alwaysAggressive = false;\n    x638_29_noPlayerDodge = false;\n    x32c_animState = EAnimState::NotReady;\n    break;\n  }\n}\n\nvoid CSpacePirate::Jump(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x32c_animState = EAnimState::Ready;\n    x828_patrolDestPos = GetTranslation() + zeus::skDown;\n    x824_jumpHeight = 0.f;\n    x8dc_leashTimer = 0.f;\n    break;\n  case EStateMsg::Update:\n    TryCommand(mgr, pas::EAnimationState::Jump, &CPatterned::TryJumpInLoop, int(pas::EJumpType::Normal));\n    UpdateLeashTimer(dt);\n    break;\n  case EStateMsg::Deactivate:\n    x32c_animState = EAnimState::NotReady;\n    break;\n  }\n}\n\npas::EStepDirection CSpacePirate::GetStrafeDir(CStateManager& mgr, float dist) const {\n  float distSq = dist * dist;\n  bool left = true;\n  bool right = true;\n  for (CEntity* ent : mgr.GetListeningAiObjectList()) {\n    if (auto* otherSp = CPatterned::CastTo<CSpacePirate>(ent)) {\n      if (otherSp != this && otherSp->GetAreaIdAlways() == GetAreaIdAlways()) {\n        zeus::CVector3f delta = otherSp->GetTranslation() - GetTranslation();\n        float deltaSq = delta.magSquared();\n        if (deltaSq < distSq) {\n          float dot = GetTransform().basis[1].dot(delta);\n          if (dot > 0.866f * deltaSq || (dot > 0.f && deltaSq < 3.f)) {\n            right = false;\n          } else if (dot < -deltaSq * 0.866f || (dot < 0.f && deltaSq < 3.f)) {\n            left = false;\n          }\n        }\n      }\n    }\n  }\n  if (right) {\n    right = CantJumpBack(mgr, GetTransform().basis[0], dist);\n  }\n  if (left) {\n    left = CantJumpBack(mgr, -GetTransform().basis[0], dist);\n  }\n  if (left && right) {\n    if ((mgr.GetActiveRandom()->Next() & 0x4000) != 0) {\n      left = false;\n    } else {\n      right = false;\n    }\n  }\n  if (left) {\n    return pas::EStepDirection::Left;\n  }\n  if (right) {\n    return pas::EStepDirection::Right;\n  }\n  return pas::EStepDirection::Invalid;\n}\n\nvoid CSpacePirate::Dodge(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x639_29_enableBreakDodge = false;\n    if (!x63a_24_normalDodge && !x635_29_noBreakDodge && x8c0_dodgeDelayTimer <= 0.f) {\n      if (mgr.GetActiveRandom()->Float() <\n          ((x750_initialHP - HealthInfo(mgr)->GetHP()) * 4.f / x750_initialHP + 1.f) * 0.15f) {\n        x639_29_enableBreakDodge = true;\n      }\n      x8c0_dodgeDelayTimer =\n          mgr.GetActiveRandom()->Range(x568_pirateData.xb8_dodgeDelayTimeMin, x568_pirateData.xbc_dodgeDelayTimeMax);\n    }\n    x844_dodgeDir = GetStrafeDir(mgr, x639_29_enableBreakDodge ? x84c_breakDodgeDist : x848_dodgeDist);\n    if (x844_dodgeDir != pas::EStepDirection::Invalid) {\n      x32c_animState = EAnimState::Ready;\n    }\n    break;\n  case EStateMsg::Update:\n    if (!x639_29_enableBreakDodge) {\n      if (x63a_24_normalDodge || mgr.GetActiveRandom()->Float() < 0.5f) {\n        TryCommand(mgr, pas::EAnimationState::Step, &CPatterned::TryDodge, int(x844_dodgeDir));\n      } else {\n        TryCommand(mgr, pas::EAnimationState::Step, &CPatterned::TryRollingDodge, int(x844_dodgeDir));\n      }\n    } else {\n      TryCommand(mgr, pas::EAnimationState::Step, &CPatterned::TryBreakDodge, int(x844_dodgeDir));\n      if (GetMaterialList().HasMaterial(EMaterialTypes::Orbit) && x330_stateMachineState.GetTime() > 0.5f) {\n        RemoveMaterial(EMaterialTypes::Orbit, mgr);\n        mgr.GetPlayer().TryToBreakOrbit(GetUniqueId(), CPlayer::EPlayerOrbitRequest::ActivateOrbitSource, mgr);\n      }\n    }\n    break;\n  case EStateMsg::Deactivate:\n    x32c_animState = EAnimState::NotReady;\n    x638_29_noPlayerDodge = true;\n    if (!GetMaterialList().HasMaterial(EMaterialTypes::Orbit)) {\n      AddMaterial(EMaterialTypes::Orbit, mgr);\n    }\n    break;\n  }\n}\n\nvoid CSpacePirate::Cover(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    if (x450_bodyController->GetCurrentStateId() != pas::EAnimationState::Cover) {\n      if (CScriptCoverPoint* cp = GetCoverPoint(mgr, x640_coverPoint)) {\n        x79c_coverDir = cp->GetAttackDirection();\n        x32c_animState = EAnimState::Ready;\n        x2e0_destPos = cp->GetTranslation();\n        TryCommand(mgr, pas::EAnimationState::Cover, &CPatterned::TryCover, int(x79c_coverDir));\n      }\n    }\n    break;\n  case EStateMsg::Update:\n    TryCommand(mgr, pas::EAnimationState::Cover, &CPatterned::TryCover, int(x79c_coverDir));\n    if (CScriptCoverPoint* cp = GetCoverPoint(mgr, x640_coverPoint)) {\n      x450_bodyController->GetCommandMgr().DeliverTargetVector(-cp->GetTransform().basis[1]);\n    }\n    UpdateCantSeePlayer(mgr);\n    break;\n  case EStateMsg::Deactivate:\n    x32c_animState = EAnimState::NotReady;\n    break;\n  }\n}\n\nvoid CSpacePirate::Approach(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x450_bodyController->GetCommandMgr().SetSteeringBlendMode(ESteeringBlendMode::Normal);\n    x30c_behaviourOrient = EBehaviourOrient::MoveDir;\n    x644_steeringSpeed = 1.f;\n    break;\n  case EStateMsg::Update:\n    AvoidActors(mgr);\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CSpacePirate::WallHang(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x32c_animState = EAnimState::Ready;\n    x637_29_inWallHang = true;\n    if (CScriptCoverPoint* cp = GetCoverPoint(mgr, x640_coverPoint)) {\n      for (const auto& conn : cp->GetConnectionList()) {\n        if (conn.x0_state == EScriptObjectState::Arrived && conn.x4_msg == EScriptObjectMessage::Next) {\n          if (TCastToPtr<CScriptWaypoint> wp = mgr.ObjectById(mgr.GetIdForScript(conn.x8_objId))) {\n            x2e0_destPos = wp->GetTranslation();\n            x2ec_reflectedDestPos = GetTranslation();\n            x328_24_inPosition = false;\n            break;\n          }\n        }\n      }\n    }\n    x636_31_inAttackState = true;\n    break;\n  case EStateMsg::Update:\n    TryCommand(mgr, pas::EAnimationState::WallHang, &CSpacePirate::TryWallHang, 0);\n    x450_bodyController->GetCommandMgr().DeliverTargetVector(mgr.GetPlayer().GetTranslation() - GetTranslation());\n    x7c4_burstFire.SetBurstType(1);\n    break;\n  case EStateMsg::Deactivate:\n    x637_29_inWallHang = false;\n    x32c_animState = EAnimState::NotReady;\n    x636_31_inAttackState = false;\n    break;\n  }\n}\n\nvoid CSpacePirate::WallDetach(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x637_29_inWallHang = true;\n    break;\n  case EStateMsg::Update:\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBodyStateCmd(EBodyStateCmd::ExitState));\n    break;\n  case EStateMsg::Deactivate:\n    x637_29_inWallHang = false;\n    break;\n  }\n}\n\nvoid CSpacePirate::Enraged(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBodyStateCmd(EBodyStateCmd::ExitState));\n  }\n}\n\nvoid CSpacePirate::SpecialAttack(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x32c_animState = EAnimState::Ready;\n    x648_targetDelta = mgr.GetPlayer().GetAimPosition(mgr, 0.f) - GetGunEyePos();\n    break;\n  case EStateMsg::Update:\n    TryCommand(mgr, pas::EAnimationState::ProjectileAttack, &CPatterned::TryProjectileAttack, int(pas::ESeverity::One));\n    if (x32c_animState == EAnimState::Ready) {\n      x450_bodyController->GetCommandMgr().DeliverTargetVector(x648_targetDelta);\n    }\n    break;\n  case EStateMsg::Deactivate:\n    x32c_animState = EAnimState::NotReady;\n    break;\n  }\n}\n\nvoid CSpacePirate::Bounce(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    if (TCastToConstPtr<CScriptAiJumpPoint> jp = mgr.GetObjectById(x840_jumpPoint)) {\n      if (TCastToConstPtr<CScriptWaypoint> wp = mgr.GetObjectById(jp->GetJumpTarget())) {\n        x450_bodyController->GetCommandMgr().DeliverCmd(\n            CBCJumpCmd(x828_patrolDestPos, wp->GetTranslation(), pas::EJumpType::Normal));\n      }\n    }\n    break;\n  case EStateMsg::Update:\n    if (x330_stateMachineState.GetTime() > 0.1f &&\n        x450_bodyController->GetCurrentStateId() != pas::EAnimationState::Jump) {\n      x330_stateMachineState.SetCodeTrigger();\n    }\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CSpacePirate::PathFindEx(CStateManager& mgr, EStateMsg msg, float dt) {\n  CPatterned::PathFind(mgr, msg, dt);\n  switch (msg) {\n  case EStateMsg::Activate:\n    x639_27_inRange = false;\n    x30c_behaviourOrient = EBehaviourOrient::MoveDir;\n    break;\n  case EStateMsg::Update:\n    AvoidActors(mgr);\n    if (!x639_27_inRange) {\n      if (TCastToConstPtr<CScriptAiJumpPoint> jp = mgr.GetObjectById(x840_jumpPoint)) {\n        x754_coverRange =\n            (1.5f * dt + 0.1f) * x64_modelData->GetScale().y() * x450_bodyController->GetBodyStateInfo().GetMaxSpeed() +\n            x7a4_intoJumpDist;\n        x639_27_inRange = (GetTranslation() - jp->GetTranslation()).magSquared() < x754_coverRange * x754_coverRange;\n      }\n    }\n    break;\n  case EStateMsg::Deactivate:\n    x639_27_inRange = false;\n    break;\n  }\n}\n\nbool CSpacePirate::Leash(CStateManager& mgr, float arg) { return x8dc_leashTimer > arg; }\n\nbool CSpacePirate::OffLine(CStateManager& mgr, float arg) { return !IsOnGround(); }\n\nbool CSpacePirate::Attacked(CStateManager& mgr, float arg) {\n  return x850_timeSinceHitByPlayer < (arg == 0.f ? 0.5f : arg);\n}\n\nbool CSpacePirate::InRange(CStateManager& mgr, float arg) { return x639_27_inRange; }\n\nbool CSpacePirate::SpotPlayer(CStateManager& mgr, float arg) {\n  zeus::CVector3f toPlayer = mgr.GetPlayer().GetTranslation() - GetTranslation();\n  return toPlayer.dot(GetTransform().basis[1]) > toPlayer.magnitude() * x3c4_detectionAngle;\n}\n\nbool CSpacePirate::PatternOver(CStateManager& mgr, float arg) { return x2dc_destObj == kInvalidUniqueId; }\n\nbool CSpacePirate::PatternShagged(CStateManager& mgr, float arg) {\n  return CPatterned::Stuck(mgr, arg) || CPatterned::PatternShagged(mgr, arg);\n}\n\nbool CSpacePirate::AnimOver(CStateManager& mgr, float arg) {\n  if (x637_29_inWallHang) {\n    return x450_bodyController->GetCurrentStateId() != pas::EAnimationState::WallHang;\n  }\n  return CPatterned::AnimOver(mgr, arg);\n}\n\nbool CSpacePirate::ShouldAttack(CStateManager& mgr, float arg) {\n  bool ret = true;\n  if (mgr.GetPlayer().GetUniqueId() == x7c0_targetId) {\n    zeus::CVector3f targetPos = GetTargetPos(mgr);\n    int numCloserPirates = 0;\n    float distSq = (GetTranslation() - targetPos).magSquared();\n    for (CEntity* ent : mgr.GetListeningAiObjectList()) {\n      if (auto* otherSp = CPatterned::CastTo<CSpacePirate>(ent)) {\n        if (otherSp != this && otherSp->x636_31_inAttackState && otherSp->x400_25_alive &&\n            otherSp->GetAreaIdAlways() == GetAreaIdAlways()) {\n          if ((otherSp->GetTranslation() - targetPos).magSquared() < distSq) {\n            ++numCloserPirates;\n            if (numCloserPirates > 3) {\n              ret = false;\n            }\n          }\n        }\n      }\n    }\n  }\n  return ret;\n}\n\nbool CSpacePirate::ShouldJumpBack(CStateManager& mgr, float arg) {\n  return !x634_28_noShuffleCloseCheck || x8d8_holdPositionTime > 6.f;\n}\n\nbool CSpacePirate::Stuck(CStateManager& mgr, float arg) {\n  if (x330_stateMachineState.GetTime() > 0.5f) {\n    return CPatterned::Stuck(mgr, arg) || CPatterned::PatternShagged(mgr, arg);\n  }\n  return false;\n}\n\nbool CSpacePirate::Landed(CStateManager& mgr, float arg) { return IsOnGround(); }\n\nbool CSpacePirate::HearShot(CStateManager& mgr, float arg) {\n  bool ret = x636_25_hearNoise;\n  x636_25_hearNoise = false;\n  return ret;\n}\n\nbool CSpacePirate::HearPlayer(CStateManager& mgr, float arg) {\n  if (mgr.GetPlayer().GetVelocity().magSquared() > 0.1f) {\n    return (mgr.GetPlayer().GetTranslation() - GetTranslation()).magSquared() <\n           x568_pirateData.x14_HearingRadius * x568_pirateData.x14_HearingRadius;\n  }\n  return false;\n}\n\nbool CSpacePirate::CoverCheck(CStateManager& mgr, float arg) { return x638_27_coverCheck; }\n\nbool CSpacePirate::CoverFind(CStateManager& mgr, float arg) {\n  bool ret = false;\n  float minCpDistSq = x568_pirateData.x8_SearchRadius * x568_pirateData.x8_SearchRadius;\n  CScriptCoverPoint* closestCp = nullptr;\n  for (CEntity* ent : mgr.GetAiWaypointObjectList()) {\n    if (TCastToPtr<CScriptCoverPoint> cp = ent) {\n      if (cp->GetActive() && !cp->ShouldLandHere() && !cp->GetInUse(GetUniqueId()) &&\n          cp->GetAreaIdAlways() == GetAreaIdAlways() && cp->GetUniqueId() != x642_previousCoverPoint) {\n        float fromCpDist = (GetTranslation() - cp->GetTranslation()).magSquared();\n        if (fromCpDist < minCpDistSq && !cp->Blown(mgr.GetPlayer().GetTranslation())) {\n          minCpDistSq = fromCpDist;\n          closestCp = cp.GetPtr();\n        }\n      }\n    }\n  }\n  if (closestCp != nullptr) {\n    ReleaseCoverPoint(mgr, x640_coverPoint);\n    if (TCastToPtr<CScriptCoverPoint> cp = mgr.ObjectById(closestCp->GetUniqueId())) {\n      SetCoverPoint(cp.GetPtr(), x640_coverPoint);\n      x642_previousCoverPoint = x640_coverPoint;\n      x654_coverPointRearDir = -closestCp->GetTransform().basis[1];\n      x30c_behaviourOrient = EBehaviourOrient::MoveDir;\n      ret = true;\n    }\n  }\n  return ret;\n}\n\nbool CSpacePirate::CoverBlown(CStateManager& mgr, float arg) {\n  bool ret = true;\n  if ((mgr.GetPlayer().GetTranslation() - GetTranslation()).magSquared() > x2fc_minAttackRange * x2fc_minAttackRange) {\n    if (CScriptCoverPoint* cp = GetCoverPoint(mgr, x640_coverPoint)) {\n      if (!(ret = cp->Blown(mgr.GetPlayer().GetTranslation())) && x644_steeringSpeed == 0.f &&\n          x450_bodyController->GetCurrentStateId() != pas::EAnimationState::Step &&\n          (cp->GetTranslation() - GetTranslation()).magSquared() > 3.f * x64_modelData->GetScale().y()) {\n        ret = true;\n      }\n    }\n  }\n  return ret;\n}\n\nbool CSpacePirate::CoverNearlyBlown(CStateManager& mgr, float arg) {\n  bool ret = true;\n  if (CScriptCoverPoint* cp = GetCoverPoint(mgr, x640_coverPoint)) {\n    ret = cp->Blown(mgr.GetPlayer().GetTranslation() + mgr.GetPlayer().GetVelocity() * 1.f);\n  }\n  return ret;\n}\n\nbool CSpacePirate::CoveringFire(CStateManager& mgr, float arg) {\n  bool ret = false;\n  for (CEntity* ent : mgr.GetListeningAiObjectList()) {\n    if (auto* otherSp = CPatterned::CastTo<CSpacePirate>(ent)) {\n      if (otherSp != this && otherSp->x636_31_inAttackState && otherSp->GetAreaIdAlways() == GetAreaIdAlways()) {\n        ret = true;\n      }\n    }\n  }\n  return ret;\n}\n\nbool CSpacePirate::LineOfSight(CStateManager& mgr, float arg) { return !x637_28_noPlayerLos; }\n\nbool CSpacePirate::AggressionCheck(CStateManager& mgr, float arg) {\n  bool ret = false;\n  if (!x634_26_nonAggressive) {\n    if (x638_26_alwaysAggressive) {\n      ret = true;\n    } else if (mChargePlayerList.empty() && x7ac_timeNoPlayerLos > 10.f) {\n      ret = true;\n    }\n    if (ret) {\n      x30c_behaviourOrient = EBehaviourOrient::MoveDir;\n      if (std::find(mChargePlayerList.begin(), mChargePlayerList.end(), GetUniqueId()) == mChargePlayerList.end()) {\n        mChargePlayerList.push_back(GetUniqueId());\n      }\n    }\n  }\n  return ret;\n}\n\nbool CSpacePirate::ShouldDodge(CStateManager& mgr, float arg) {\n  bool ret = false;\n  if (x638_28_enableDodge) {\n    if (!x634_26_nonAggressive && !x638_29_noPlayerDodge &&\n        (GetTargetPos(mgr) - GetTranslation()).dot(GetTransform().basis[1]) > 0.f &&\n        (x850_timeSinceHitByPlayer < 0.33f || x854_lowHealthFrenzyTimer < 0.33f) && x7ac_timeNoPlayerLos < 0.5f) {\n      ret = true;\n    }\n    if (!ret) {\n      if (const auto* metroid = CPatterned::CastTo<CMetroid>(mgr.GetObjectById(x7c0_targetId))) {\n        if (metroid->IsAttacking() &&\n            (GetTranslation() - metroid->GetTranslation()).dot(metroid->GetTransform().basis[1]) > 0.f) {\n          ret = true;\n        }\n      }\n    }\n  }\n  return ret;\n}\n\nbool CSpacePirate::ShouldRetreat(CStateManager& mgr, float arg) {\n  bool ret = false;\n  if (x636_29_enableRetreat) {\n    TUniqueId wpId = GetWaypointForState(mgr, EScriptObjectState::Patrol, EScriptObjectMessage::Follow);\n    TCastToConstPtr<CScriptWaypoint> wp = mgr.GetObjectById(wpId);\n    if (!wp) {\n      wpId = GetWaypointForState(mgr, EScriptObjectState::Retreat, EScriptObjectMessage::Follow);\n      wp = TCastToConstPtr<CScriptWaypoint>(mgr.GetObjectById(wpId));\n    }\n    if (wp) {\n      x2dc_destObj = wpId;\n      SetDestPos(wp->GetTranslation());\n    } else {\n      x2dc_destObj = kInvalidUniqueId;\n      SetDestPos(GetTranslation());\n    }\n    x636_29_enableRetreat = false;\n    x2ec_reflectedDestPos = GetTranslation();\n    x328_24_inPosition = false;\n    ReleaseCoverPoint(mgr, x640_coverPoint);\n    x636_25_hearNoise = false;\n    x637_25_enableAim = false;\n    x400_24_hitByPlayerProjectile = false;\n    ret = true;\n  }\n  return ret;\n}\n\nbool CSpacePirate::ShouldCrouch(CStateManager& mgr, float arg) {\n  if (CScriptCoverPoint* cp = GetCoverPoint(mgr, x640_coverPoint)) {\n    return cp->ShouldCrouch();\n  }\n  return false;\n}\n\nbool CSpacePirate::ShouldMove(CStateManager& mgr, float arg) {\n  if (CScriptCoverPoint* cp = GetCoverPoint(mgr, x640_coverPoint)) {\n    return !cp->ShouldStay();\n  }\n  return false;\n}\n\nbool CSpacePirate::ShotAt(CStateManager& mgr, float arg) {\n  return x854_lowHealthFrenzyTimer < (arg == 0.f ? 0.5f : arg);\n}\n\nbool CSpacePirate::HasTargetingPoint(CStateManager& mgr, float arg) {\n  bool ret = true;\n  TCastToPtr<CActor> act = mgr.ObjectById(x7c0_targetId);\n  if (x7c0_targetId == mgr.GetPlayer().GetUniqueId() || !act || !act->GetActive()) {\n    ret = false;\n    x764_boneTracking.SetTarget(mgr.GetPlayer().GetUniqueId());\n    x7c0_targetId = mgr.GetPlayer().GetUniqueId();\n    float margin = x568_pirateData.x8_SearchRadius * 1.f;\n    zeus::CAABox nearAABB(GetTranslation() - margin, GetTranslation() + margin);\n    EntityList nearList;\n    mgr.BuildNearList(nearList, nearAABB, CMaterialFilter::MakeExclude({EMaterialTypes::Solid}), nullptr);\n    for (TUniqueId id : nearList) {\n      if (TCastToConstPtr<CScriptTargetingPoint> tp = mgr.GetObjectById(id)) {\n        if (tp->GetActive() && tp->GetAreaIdAlways() == GetAreaIdAlways() && !tp->GetLocked()) {\n          x764_boneTracking.SetTarget(tp->GetUniqueId());\n          x7c0_targetId = tp->GetUniqueId();\n          ret = true;\n        }\n      }\n    }\n  }\n  return ret;\n}\n\nbool CSpacePirate::ShouldWallHang(CStateManager& mgr, float arg) {\n  if (CScriptCoverPoint* cp = GetCoverPoint(mgr, x640_coverPoint)) {\n    return cp->ShouldWallHang();\n  }\n  return false;\n}\n\nbool CSpacePirate::StartAttack(CStateManager& mgr, float arg) {\n  if (x638_31_mayStartAttack) {\n    x638_31_mayStartAttack = false;\n    return true;\n  }\n  return false;\n}\n\nbool CSpacePirate::BreakAttack(CStateManager& mgr, float arg) { return x635_25_breakAttack; }\n\nbool CSpacePirate::ShouldStrafe(CStateManager& mgr, float arg) {\n  bool ret = false;\n  bool noPlayerStrafe = false;\n  x834_skidDir = pas::EStepDirection::Invalid;\n  if (!x634_26_nonAggressive && (GetTargetPos(mgr) - GetTranslation()).dot(GetTransform().basis[1]) > 0.f) {\n    if ((x854_lowHealthFrenzyTimer < 0.66f || x850_timeSinceHitByPlayer < 0.66f) && x838_strafeDelayTimer == 0.f &&\n        (GetTargetPos(mgr) - GetBoundingBox().center()).normalized().dot(GetTransform().basis[1]) > 0.707f) {\n      x834_skidDir = GetStrafeDir(mgr, 10.f);\n      if (x834_skidDir != pas::EStepDirection::Invalid) {\n        ret = true;\n      } else {\n        noPlayerStrafe = true;\n      }\n    }\n    if (!noPlayerStrafe && !ret && x7c0_targetId == mgr.GetPlayer().GetUniqueId() && x7ac_timeNoPlayerLos > 1.f &&\n        (mgr.GetPlayer().GetTranslation() - GetTranslation()).magnitude() < 15.f &&\n        x834_skidDir == pas::EStepDirection::Invalid) {\n      x834_skidDir = GetStrafeDir(mgr, 5.f);\n      if (x834_skidDir != pas::EStepDirection::Invalid) {\n        ret = true;\n      }\n    }\n  }\n  return ret;\n}\n\nbool CSpacePirate::ShouldSpecialAttack(CStateManager& mgr, float arg) {\n  return x634_29_onlyAttackInRange && !x7c4_burstFire.IsBurstSet() && x7bc_attackRemTime > 2.f;\n}\n\nbool CSpacePirate::LostInterest(CStateManager& mgr, float arg) {\n  return x634_29_onlyAttackInRange && x7bc_attackRemTime < 1.5f;\n}\n\nbool CSpacePirate::BounceFind(CStateManager& mgr, float arg) {\n  float minDistSq = FLT_MAX;\n  CScriptAiJumpPoint* bestJp = nullptr;\n  bool ret = false;\n  for (CEntity* ent : mgr.GetAiWaypointObjectList()) {\n    if (TCastToPtr<CScriptAiJumpPoint> jp = ent) {\n      if (jp->GetActive() && !jp->GetInUse(GetUniqueId()) && jp->GetJumpTarget() != kInvalidUniqueId &&\n          jp->GetAreaIdAlways() == GetAreaIdAlways()) {\n        zeus::CVector3f toJp = jp->GetTranslation() - GetTranslation();\n        float distSq = toJp.magSquared();\n        if (distSq < minDistSq && jp->GetTransform().basis[1].dot(toJp) > 0.f) {\n          if (TCastToConstPtr<CScriptWaypoint> wp = mgr.GetObjectById(jp->GetJumpTarget())) {\n            zeus::CVector3f wpToDest = x2e0_destPos - wp->GetTranslation();\n            distSq += wpToDest.magSquared();\n            if (distSq < minDistSq && wp->GetTransform().basis[1].dot(wpToDest) > 0.f &&\n                GetSearchPath()->PathExists(GetTranslation(), jp->GetTranslation()) ==\n                    CPathFindSearch::EResult::Success) {\n              bool good = false;\n              if (GetSearchPath()->PathExists(wp->GetTranslation(), x2e0_destPos) !=\n                  CPathFindSearch::EResult::Success) {\n                distSq += 1000.f;\n              } else {\n                good = true;\n              }\n              if (distSq < minDistSq) {\n                minDistSq = distSq;\n                bestJp = jp.GetPtr();\n                if (good) {\n                  break;\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n  if (bestJp != nullptr) {\n    if (TCastToConstPtr<CScriptWaypoint> wp = mgr.GetObjectById(bestJp->GetJumpPoint())) {\n      SetDestPos(bestJp->GetTranslation());\n      x840_jumpPoint = bestJp->GetUniqueId();\n      x824_jumpHeight = bestJp->GetJumpApex();\n      x828_patrolDestPos = wp->GetTranslation();\n      ret = true;\n    }\n  }\n  return ret;\n}\n\nCPathFindSearch* CSpacePirate::GetSearchPath() { return &x660_pathFindSearch; }\n\nu8 CSpacePirate::GetModelAlphau8(const CStateManager& mgr) const {\n  if ((mgr.GetPlayerState()->GetActiveVisor(mgr) != CPlayerState::EPlayerVisor::XRay &&\n       mgr.GetPlayerState()->GetActiveVisor(mgr) != CPlayerState::EPlayerVisor::Thermal) ||\n      !x400_25_alive) {\n    if (!x635_27_shadowPirate) {\n      return u8(x42c_color.a() * 255.f);\n    }\n    return u8(x8b4_shadowPirateAlpha * 255.f);\n  }\n  return 255;\n}\n\nfloat CSpacePirate::GetGravityConstant() const { return 50.f; }\n\nCProjectileInfo* CSpacePirate::GetProjectileInfo() { return &x568_pirateData.x20_Projectile; }\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CSpacePirate.hpp",
    "content": "#pragma once\n\n#include <list>\n#include <memory>\n\n#include \"Runtime/Character/CBoneTracking.hpp\"\n#include \"Runtime/Character/CIkChain.hpp\"\n#include \"Runtime/Character/CRagDoll.hpp\"\n#include \"Runtime/Weapon/CBurstFire.hpp\"\n#include \"Runtime/Weapon/CProjectileInfo.hpp\"\n#include \"Runtime/World/CPathFindSearch.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n#include \"Runtime/rstl.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce::MP1 {\nclass CSpacePirate;\n\nclass CPirateRagDoll : public CRagDoll {\n  CSpacePirate* x6c_spacePirate;\n  u16 x70_thudSfx;\n  float x74_sfxTimer = 0.f;\n  zeus::CVector3f x78_lastSFXPos;\n  zeus::CVector3f x84_torsoImpulse;\n  rstl::reserved_vector<TUniqueId, 4> x90_waypoints;\n  rstl::reserved_vector<u32, 4> x9c_wpParticleIdxs;\n  bool xb0_24_initSfx : 1 = true;\n\npublic:\n  CPirateRagDoll(CStateManager& mgr, CSpacePirate* sp, u16 thudSfx, u32 flags);\n\n  void PreRender(const zeus::CVector3f& v, CModelData& mData) override;\n  void Update(CStateManager& mgr, float dt, float waterTop) override;\n  void Prime(CStateManager& mgr, const zeus::CTransform& xf, CModelData& mData) override;\n  zeus::CVector3f& TorsoImpulse() { return x84_torsoImpulse; }\n};\n\nclass CSpacePirate : public CPatterned {\n  friend class CPirateRagDoll;\n\npublic:\n  DEFINE_PATTERNED(SpacePirate);\n\nprivate:\n  class CSpacePirateData {\n    friend class CSpacePirate;\n    float x0_AggressionCheck;\n    float x4_CoverCheck;\n    float x8_SearchRadius;\n    float xc_FallBackCheck;\n    float x10_FallBackRadius;\n    float x14_HearingRadius;\n    /*\n     * 0x1: pendingAmbush\n     * 0x2: ceilingAmbush\n     * 0x4: nonAggressive\n     * 0x8: melee\n     * 0x10: noShuffleCloseCheck\n     * 0x20: onlyAttackInRange\n     * 0x40: unk\n     * 0x80: noKnockbackImpulseReset\n     * 0x200: noMeleeAttack\n     * 0x400: breakAttack\n     * 0x1000: seated\n     * 0x2000: shadowPirate\n     * 0x4000: alertBeforeCloak\n     * 0x8000: noBreakDodge\n     * 0x10000: floatingCorpse\n     * 0x20000: ragdollNoAiCollision\n     * 0x40000: trooper\n     */\n    u32 x18_flags;\n    bool x1c_;\n    CProjectileInfo x20_Projectile;\n    u16 x48_Sound_Projectile;\n    CDamageInfo x4c_BladeDamage;\n    float x68_KneelAttackChance;\n    CProjectileInfo x6c_KneelAttackShot;\n    float x94_DodgeCheck;\n    u16 x98_Sound_Impact;\n    float x9c_averageNextShotTime;\n    float xa0_nextShotTimeVariation;\n    u16 xa4_Sound_Alert;\n    float xa8_GunTrackDelay;\n    u32 xac_firstBurstCount;\n    float xb0_CloakOpacity;\n    float xb4_MaxCloakOpacity;\n    float xb8_dodgeDelayTimeMin;\n    float xbc_dodgeDelayTimeMax;\n    u16 xc0_Sound_Hurled;\n    u16 xc2_Sound_Death;\n    float xc4_;\n    float xc8_AvoidDistance;\n\n  public:\n    CSpacePirateData(CInputStream&, u32);\n  };\n\n  CSpacePirateData x568_pirateData;\n\n  bool x634_24_pendingAmbush : 1;\n  bool x634_25_ceilingAmbush : 1;\n  bool x634_26_nonAggressive : 1;\n  bool x634_27_melee : 1;\n  bool x634_28_noShuffleCloseCheck : 1;\n  bool x634_29_onlyAttackInRange : 1;\n  bool x634_30_ : 1;\n  bool x634_31_noKnockbackImpulseReset : 1;\n  bool x635_24_noMeleeAttack : 1;\n  bool x635_25_breakAttack : 1;\n  bool x635_26_seated : 1;\n  bool x635_27_shadowPirate : 1;\n  bool x635_28_alertBeforeCloak : 1;\n  bool x635_29_noBreakDodge : 1;\n  bool x635_30_floatingCorpse : 1;\n  bool x635_31_ragdollNoAiCollision : 1;\n  bool x636_24_trooper : 1;\n  bool x636_25_hearNoise : 1 = false;\n  bool x636_26_enableMeleeAttack : 1 = false;\n  bool x636_27_ : 1 = false;\n  bool x636_28_ : 1 = false;\n  bool x636_29_enableRetreat : 1 = false;\n  bool x636_30_shuffleClose : 1 = false;\n  bool x636_31_inAttackState : 1 = false;\n  bool x637_24_enablePatrol : 1 = false;\n  bool x637_25_enableAim : 1 = false;\n  bool x637_26_hearPlayerFire : 1 = false;\n  bool x637_27_inProjectilePath : 1 = false;\n  bool x637_28_noPlayerLos : 1 = false;\n  bool x637_29_inWallHang : 1 = false;\n  bool x637_30_jumpVelSet : 1 = false;\n  bool x637_31_prevInCineCam : 1 = false;\n  bool x638_24_pendingFrenzyChance : 1 = false;\n  bool x638_25_appliedBladeDamage : 1 = false;\n  bool x638_26_alwaysAggressive : 1 = false;\n  bool x638_27_coverCheck : 1 = false;\n  bool x638_28_enableDodge : 1 = false;\n  bool x638_29_noPlayerDodge : 1 = false;\n  bool x638_30_allEnergyDrained : 1 = false;\n  bool x638_31_mayStartAttack : 1 = false;\n  bool x639_24_ : 1 = false;\n  bool x639_25_useJumpBackJump : 1 = false;\n  bool x639_26_started : 1 = false;\n  bool x639_27_inRange : 1 = false;\n  bool x639_28_satUp : 1 = false;\n  bool x639_29_enableBreakDodge : 1 = false;\n  bool x639_30_closeMelee : 1 = false;\n  bool x639_31_sentAttackMsg : 1 = false;\n  bool x63a_24_normalDodge : 1 = false;\n\n  s32 x63c_frenzyFrames = 0;\n  TUniqueId x640_coverPoint = kInvalidUniqueId;\n  TUniqueId x642_previousCoverPoint = kInvalidUniqueId;\n  float x644_steeringSpeed = 1.f;\n  zeus::CVector3f x648_targetDelta = zeus::skForward;\n  zeus::CVector3f x654_coverPointRearDir;\n  CPathFindSearch x660_pathFindSearch;\n  float x744_unkTimer = 0.f;\n  float x748_steeringDelayTimer = 0.f;\n  u32 x74c_ = 0;\n  float x750_initialHP;\n  float x754_coverRange = 0.f;\n  CSegId x758_headSeg;\n  u32 x75c_ = 0;\n  pas::ETauntType x760_taunt = pas::ETauntType::Invalid;\n  CBoneTracking x764_boneTracking;\n  pas::ECoverDirection x79c_coverDir = pas::ECoverDirection::Invalid;\n  float x7a4_intoJumpDist = 1.f;\n  float x7a8_eyeHeight = 2.f;\n  float x7ac_timeNoPlayerLos = 0.f;\n  u32 x7b0_cantSeePlayerCycleCounter = 0;\n  TUniqueId x7b4_attachedActor = kInvalidUniqueId;\n  CSegId x7b6_gunSeg;\n  CSegId x7b7_elbowSeg;\n  CSegId x7b8_wristSeg;\n  CSegId x7b9_swooshSeg;\n  float x7bc_attackRemTime = 1.f;\n  TUniqueId x7c0_targetId = kInvalidUniqueId;\n  CBurstFire x7c4_burstFire;\n  float x824_jumpHeight = 3.f;\n  zeus::CVector3f x828_patrolDestPos;\n  pas::EStepDirection x834_skidDir = pas::EStepDirection::Invalid;\n  float x838_strafeDelayTimer = 0.f;\n  pas::ESeverity x83c_meleeSeverity = pas::ESeverity::Invalid;\n  TUniqueId x840_jumpPoint = kInvalidUniqueId;\n  pas::EStepDirection x844_dodgeDir = pas::EStepDirection::Invalid;\n  float x848_dodgeDist = 3.f;\n  float x84c_breakDodgeDist = 3.f;\n  float x850_timeSinceHitByPlayer = FLT_MAX;\n  float x854_lowHealthFrenzyTimer = FLT_MAX;\n  float x858_ragdollDelayTimer = 0.f;\n  std::unique_ptr<CPirateRagDoll> x85c_ragDoll;\n  CIkChain x860_ikChain;\n  float x8a8_cloakDelayTimer = 0.f;\n  float x8ac_electricParticleTimer = 0.f;\n  float x8b0_cloakStepTime = 0.f;\n  float x8b4_shadowPirateAlpha = 0.5f;\n  float x8b8_minCloakAlpha;\n  float x8bc_maxCloakAlpha;\n  float x8c0_dodgeDelayTimer;\n  float x8c4_aimDelayTimer;\n  TUniqueId x8c8_teamAiMgrId = kInvalidUniqueId;\n  zeus::CColor x8cc_trooperColor = zeus::skWhite;\n  zeus::CVector2f x8d0_heldPosition;\n  float x8d8_holdPositionTime = 0.f;\n  float x8dc_leashTimer = 0.f;\n\n  zeus::CTransform m_lastKnownGoodXf;\n  void UpdateCloak(float dt, CStateManager& mgr);\n  bool ShouldFrenzy(CStateManager& mgr);\n  void SquadReset(CStateManager& mgr);\n  void SquadAdd(CStateManager& mgr);\n  void SquadRemove(CStateManager& mgr);\n  [[nodiscard]] bool CheckTargetable(const CStateManager& mgr) const;\n  bool FireProjectile(float dt, CStateManager& mgr);\n  void UpdateAttacks(float dt, CStateManager& mgr);\n  zeus::CVector3f GetTargetPos(const CStateManager& mgr);\n  void UpdateAimBodyState(float dt, const CStateManager& mgr);\n  void SetCinematicCollision(CStateManager& mgr);\n  void SetNonCinematicCollision(CStateManager& mgr);\n  void CheckForProjectiles(CStateManager& mgr);\n  void SetEyeParticleActive(CStateManager& mgr, bool active);\n  void SetVelocityForJump();\n  void AvoidActors(CStateManager& mgr);\n  void UpdateCantSeePlayer(CStateManager& mgr);\n  [[nodiscard]] bool LineOfSightTest(const CStateManager& mgr, const zeus::CVector3f& eyePos,\n                                     const zeus::CVector3f& targetPos, const CMaterialList& excludeList) const;\n  void UpdateHeldPosition(CStateManager& mgr, float dt);\n  void CheckBlade(CStateManager& mgr);\n  [[nodiscard]] bool CantJumpBack(const CStateManager& mgr, const zeus::CVector3f& dir, float dist) const;\n  void UpdateLeashTimer(float dt);\n  pas::EStepDirection GetStrafeDir(CStateManager& mgr, float dist) const;\n\npublic:\n  CSpacePirate(TUniqueId, std::string_view, const CEntityInfo&, const zeus::CTransform&, CModelData&&,\n               const CActorParameters&, const CPatternedInfo&, CInputStream&, u32);\n\n  void Accept(IVisitor& visitor) override;\n  void Think(float dt, CStateManager& mgr) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) override;\n  void PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) override;\n  void Render(CStateManager& mgr) override;\n\n  void CalculateRenderBounds() override;\n  void Touch(CActor& other, CStateManager& mgr) override;\n  [[nodiscard]] zeus::CAABox GetSortingBounds(const CStateManager& mgr) const override;\n  void DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) override;\n  void Death(CStateManager& mgr, const zeus::CVector3f& direction, EScriptObjectState state) override;\n  void KnockBack(const zeus::CVector3f&, CStateManager&, const CDamageInfo& info, EKnockBackType type, bool inDeferred,\n                 float magnitude) override;\n  [[nodiscard]] bool IsListening() const override;\n  bool Listen(const zeus::CVector3f&, EListenNoiseType) override;\n  [[nodiscard]] zeus::CVector3f GetOrigin(const CStateManager& mgr, const CTeamAiRole& role,\n                                          const zeus::CVector3f& aimPos) const override;\n  void DetachActorFromPirate() { x7b4_attachedActor = kInvalidUniqueId; }\n  bool AttachActorToPirate(TUniqueId id);\n  void SetAttackTarget(TUniqueId id);\n\n  void Patrol(CStateManager&, EStateMsg, float) override;\n  void Dead(CStateManager&, EStateMsg, float) override;\n  void PathFind(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void TargetPatrol(CStateManager&, EStateMsg, float) override;\n  void TargetCover(CStateManager&, EStateMsg, float) override;\n  void Halt(CStateManager&, EStateMsg, float) override;\n  void Run(CStateManager&, EStateMsg, float) override;\n  void Generate(CStateManager&, EStateMsg, float) override;\n  void Deactivate(CStateManager&, EStateMsg, float) override;\n  void Attack(CStateManager&, EStateMsg, float) override;\n  void JumpBack(CStateManager&, EStateMsg, float) override;\n  void DoubleSnap(CStateManager&, EStateMsg, float) override;\n  void Shuffle(CStateManager&, EStateMsg, float) override;\n  void TurnAround(CStateManager&, EStateMsg, float) override;\n  void Skid(CStateManager&, EStateMsg, float) override;\n  void CoverAttack(CStateManager&, EStateMsg, float) override;\n  void Crouch(CStateManager&, EStateMsg, float) override;\n  void GetUp(CStateManager&, EStateMsg, float) override;\n  void Taunt(CStateManager&, EStateMsg, float) override;\n  void Flee(CStateManager&, EStateMsg, float) override;\n  void Lurk(CStateManager&, EStateMsg, float) override;\n  void Jump(CStateManager&, EStateMsg, float) override;\n  void Dodge(CStateManager&, EStateMsg, float) override;\n  void Cover(CStateManager&, EStateMsg, float) override;\n  void Approach(CStateManager&, EStateMsg, float) override;\n  void WallHang(CStateManager&, EStateMsg, float) override;\n  void WallDetach(CStateManager&, EStateMsg, float) override;\n  void Enraged(CStateManager&, EStateMsg, float) override;\n  void SpecialAttack(CStateManager&, EStateMsg, float) override;\n  void Bounce(CStateManager&, EStateMsg, float) override;\n  void PathFindEx(CStateManager&, EStateMsg, float) override;\n\n  bool Leash(CStateManager&, float) override;\n  bool OffLine(CStateManager&, float) override;\n  bool Attacked(CStateManager&, float) override;\n  bool InRange(CStateManager&, float) override;\n  bool SpotPlayer(CStateManager&, float) override;\n  bool PatternOver(CStateManager&, float) override;\n  bool PatternShagged(CStateManager&, float) override;\n  bool AnimOver(CStateManager&, float) override;\n  bool ShouldAttack(CStateManager&, float) override;\n  bool ShouldJumpBack(CStateManager& mgr, float arg) override;\n  bool Stuck(CStateManager&, float) override;\n  bool Landed(CStateManager&, float) override;\n  bool HearShot(CStateManager&, float) override;\n  bool HearPlayer(CStateManager&, float) override;\n  bool CoverCheck(CStateManager&, float) override;\n  bool CoverFind(CStateManager&, float) override;\n  bool CoverBlown(CStateManager&, float) override;\n  bool CoverNearlyBlown(CStateManager&, float) override;\n  bool CoveringFire(CStateManager&, float) override;\n  bool LineOfSight(CStateManager&, float) override;\n  bool AggressionCheck(CStateManager&, float) override;\n  bool ShouldDodge(CStateManager&, float) override;\n  bool ShouldRetreat(CStateManager&, float) override;\n  bool ShouldCrouch(CStateManager&, float) override;\n  bool ShouldMove(CStateManager&, float) override;\n  bool ShotAt(CStateManager&, float) override;\n  bool HasTargetingPoint(CStateManager&, float) override;\n  bool ShouldWallHang(CStateManager&, float) override;\n  bool StartAttack(CStateManager&, float) override;\n  bool BreakAttack(CStateManager&, float) override;\n  bool ShouldStrafe(CStateManager& mgr, float arg) override;\n  bool ShouldSpecialAttack(CStateManager&, float) override;\n  bool LostInterest(CStateManager&, float) override;\n  bool BounceFind(CStateManager& mgr, float arg) override;\n\n  CPathFindSearch* GetSearchPath() override;\n  [[nodiscard]] u8 GetModelAlphau8(const CStateManager& mgr) const override;\n  [[nodiscard]] float GetGravityConstant() const override;\n  CProjectileInfo* GetProjectileInfo() override;\n  [[nodiscard]] bool GetEnableAim() const { return x637_25_enableAim; }\n  [[nodiscard]] bool AllEnergyDrained() const { return x638_30_allEnergyDrained; }\n  [[nodiscard]] TUniqueId GetAttachedActor() const { return x7b4_attachedActor; }\n  [[nodiscard]] bool IsTrooper() const { return x636_24_trooper; }\n};\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CSpankWeed.cpp",
    "content": "#include \"Runtime/MP1/World/CSpankWeed.hpp\"\n\n#include <array>\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Collision/CCollisionActor.hpp\"\n#include \"Runtime/World/CPatternedInfo.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/Logging.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\nnamespace metaforce::MP1 {\nconstexpr std::array<SSphereJointInfo, 7> kArmCollision{{\n    {\"Arm_4\", 1.5f},\n    {\"Arm_6\", 1.f},\n    {\"Arm_7\", 1.f},\n    {\"Arm_8\", 1.f},\n    {\"Arm_9\", 1.f},\n    {\"Arm_11\", 1.f},\n    {\"swoosh_LCTR\", 1.5f},\n}};\n\nCSpankWeed::CSpankWeed(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                       CModelData&& mData, const CActorParameters& actParms, const CPatternedInfo& pInfo,\n                       float maxDetectionRange, float maxHearingRange, float maxSightRange, float hideTime)\n: CPatterned(ECharacter::SpankWeed, uid, name, EFlavorType::Zero, info, xf, std::move(mData), pInfo,\n             EMovementType::Flyer, EColliderType::One, EBodyType::Restricted, actParms, EKnockBackVariant::Medium)\n, x568_maxDetectionRange(maxDetectionRange)\n, x56c_detectionHeightRange(pInfo.GetDetectionHeightRange())\n, x570_maxHearingRange(maxHearingRange)\n, x574_maxSightRange(maxSightRange)\n, x578_hideTime(hideTime)\n, x584_retreatOrigin(xf.origin) {\n  SetCallTouch(false);\n  CreateShadow(false);\n\n  const zeus::CVector3f modelScale = GetModelData()->GetScale();\n  if (modelScale.x() != modelScale.y() || modelScale.x() != modelScale.z()) {\n    float scale = modelScale.magnitude() / std::sqrt(3.f);\n\n    GetModelData()->SetScale(zeus::CVector3f(scale));\n    spdlog::warn(\n        \"WARNING: Non-uniform scale {} applied to Spank Weed\"\n        \"...changing scale to ({} {} {})\\n\",\n        modelScale, scale, scale, scale);\n  }\n  CMaterialList list = GetMaterialFilter().GetExcludeList();\n  list.Add(EMaterialTypes::Character);\n  list.Add(EMaterialTypes::Player);\n  SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(GetMaterialFilter().GetIncludeList(), list));\n\n  const CSegId segId = GetModelData()->GetAnimationData()->GetLocatorSegId(\"lockon_target_LCTR\"sv);\n  if (segId.IsValid()) {\n    const zeus::CTransform locatorXf = GetTransform() * zeus::CTransform::Scale(GetModelData()->GetScale()) *\n                                       GetModelData()->GetAnimationData()->GetLocatorTransform(segId, nullptr);\n    x5a8_lockonTarget = locatorXf.origin;\n    x59c_lockonOffset = locatorXf.origin - GetTranslation();\n  }\n  x460_knockBackController.SetAutoResetImpulse(false);\n}\n\nvoid CSpankWeed::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  bool oldActive = GetActive();\n  if (msg == EScriptObjectMessage::Activate) {\n    if (x90_actorLights)\n      x90_actorLights->SetDirty();\n  } else if (msg == EScriptObjectMessage::Decrement) {\n    if (x5b4_ != 0 && x5b4_ != 5 && x5b4_ != 6 && x5b4_ != 4) {\n      x400_24_hitByPlayerProjectile = true;\n      x428_damageCooldownTimer = x424_damageWaitTime;\n    }\n  } else if (msg == EScriptObjectMessage::Registered) {\n    if (!x450_bodyController->GetActive()) {\n      x450_bodyController->Activate(mgr);\n      zeus::CVector3f extents = GetBoundingBox().extents();\n\n      SetBoundingBox({-extents, extents});\n    }\n\n    std::vector<CJointCollisionDescription> joints;\n    joints.reserve(12);\n\n    for (const SSphereJointInfo& joint : kArmCollision) {\n      const CSegId id = GetModelData()->GetAnimationData()->GetLocatorSegId(joint.name);\n      if (id.IsValid()) {\n        joints.push_back(CJointCollisionDescription::SphereCollision(id, joint.radius, joint.name, 0.001f));\n      }\n    }\n\n    x594_collisionMgr =\n        std::make_unique<CCollisionActorManager>(mgr, GetUniqueId(), GetAreaIdAlways(), joints, GetActive());\n    CMaterialList list;\n    list.Add(EMaterialTypes::CameraPassthrough);\n    list.Add(EMaterialTypes::Immovable);\n    x594_collisionMgr->AddMaterial(mgr, list);\n    if (x90_actorLights) {\n      x90_actorLights->SetDirty();\n      zeus::CVector3f swooshOrigin = GetScaledLocatorTransform(\"swoosh_LCTR\"sv).origin;\n      x90_actorLights->SetActorPositionBias(GetTransform().buildMatrix3f() * swooshOrigin);\n    }\n  } else if (msg == EScriptObjectMessage::Touched) {\n    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(uid)) {\n      if (TCastToPtr<CPlayer> player = mgr.ObjectById(colAct->GetLastTouchedObject())) {\n        if (x420_curDamageRemTime <= 0.f && x5b4_ != 4 && x5b4_ != 6) {\n          mgr.ApplyDamage(GetUniqueId(), player->GetUniqueId(), GetUniqueId(), GetContactDamage(),\n                          CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), {});\n          x420_curDamageRemTime = x424_damageWaitTime;\n        }\n      }\n    }\n  } else if (msg == EScriptObjectMessage::Deleted) {\n    mgr.FreeScriptObject(x590_);\n    x594_collisionMgr->Destroy(mgr);\n  } else if (msg == EScriptObjectMessage::SuspendedMove) {\n    x594_collisionMgr->SetMovable(mgr, false);\n  }\n\n  CPatterned::AcceptScriptMsg(msg, uid, mgr);\n\n  bool active = GetActive();\n  if (active != oldActive && x594_collisionMgr)\n    x594_collisionMgr->SetActive(mgr, active);\n}\n\nvoid CSpankWeed::Think(float dt, CStateManager& mgr) {\n  if (!GetActive())\n    return;\n\n  HealthInfo(mgr)->SetHP(1000000.0f);\n\n  if (!x598_isHiding) {\n    zeus::CVector3f eyeOrigin = GetLocatorTransform(\"Eye\"sv).origin;\n    MoveCollisionPrimitive(GetTransform().rotate(GetModelData()->GetScale() * eyeOrigin));\n    x594_collisionMgr->Update(dt, mgr, CCollisionActorManager::EUpdateOptions::ObjectSpace);\n    xe4_27_notInSortedLists = true;\n  }\n\n  CPatterned::Think(dt, mgr);\n}\n\nzeus::CVector3f CSpankWeed::GetOrbitPosition(const CStateManager& mgr) const {\n  zeus::CVector3f ret = CPatterned::GetOrbitPosition(mgr);\n  float delay = std::max(1.f, x330_stateMachineState.GetDelay());\n  if (x5b4_ == 3 && x5b8_ == 2) {\n    return (ret * (1.f - delay) + ((GetTranslation() + x59c_lockonOffset) * delay));\n  } else if (x5b4_ == 2 && x5b8_ == 3) {\n    return (GetTranslation() + x59c_lockonOffset) * (1.f - delay) + (ret * delay);\n  }\n  return ret;\n}\n\nzeus::CVector3f CSpankWeed::GetAimPosition(const CStateManager&, float dt) const {\n  zeus::CVector3f pos = (dt > 0.f ? PredictMotion(dt).x0_translation : zeus::skZero3f);\n\n  const CSegId id = GetModelData()->GetAnimationData()->GetLocatorSegId(\"lockon_target_LCTR\"sv);\n  if (id.IsValid()) {\n    const zeus::CVector3f lockonOff = GetModelData()->GetAnimationData()->GetLocatorTransform(id, nullptr).origin;\n    return pos + (GetTransform() * (GetModelData()->GetScale() * lockonOff));\n  }\n\n  return pos + GetBoundingBox().center();\n}\n\nvoid CSpankWeed::Flinch(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x5bc_ = 0;\n    x5b4_ = 0;\n    RemoveMaterial(EMaterialTypes::Orbit, EMaterialTypes::Target, mgr);\n  } else if (msg == EStateMsg::Update) {\n    if (x5bc_ == 0) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::KnockBack)\n        x5bc_ = 2;\n      else\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCKnockBackCmd({}, pas::ESeverity::Zero));\n    } else if (x5bc_ == 2 &&\n               x450_bodyController->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::KnockBack)\n      x5bc_ = 3;\n  } else if (msg == EStateMsg::Deactivate) {\n    x5b8_ = 4;\n  }\n}\n\nbool CSpankWeed::Delay(CStateManager&, float) {\n  if (x400_24_hitByPlayerProjectile) {\n    if (x330_stateMachineState.GetTime() > x578_hideTime) {\n      x400_24_hitByPlayerProjectile = false;\n      return true;\n    }\n    return false;\n  }\n\n  return true;\n}\n\nbool CSpankWeed::InRange(CStateManager& mgr, float) {\n  float playerDist = GetPlayerDistance(mgr);\n  if (x56c_detectionHeightRange > 0.f) {\n    return std::fabs(mgr.GetPlayer().GetTranslation().z() - GetTranslation().z()) < x56c_detectionHeightRange &&\n           playerDist < (x574_maxSightRange * x574_maxSightRange);\n  }\n\n  return playerDist < (x574_maxSightRange * x574_maxSightRange);\n}\n\nbool CSpankWeed::HearPlayer(CStateManager& mgr, float) {\n  float playerDist = GetPlayerDistance(mgr);\n  if (x56c_detectionHeightRange > 0.f) {\n    return std::fabs(mgr.GetPlayer().GetTranslation().z() - GetTranslation().z()) < x56c_detectionHeightRange &&\n           playerDist < (x570_maxHearingRange * x570_maxHearingRange);\n  }\n\n  return playerDist < (x570_maxHearingRange * x570_maxHearingRange);\n}\n\nbool CSpankWeed::InDetectionRange(CStateManager& mgr, float) {\n  float playerDist = GetPlayerDistance(mgr);\n  if (x56c_detectionHeightRange > 0.f) {\n    return std::fabs(mgr.GetPlayer().GetTranslation().z() - GetTranslation().z()) < x56c_detectionHeightRange &&\n           playerDist < (x568_maxDetectionRange * x568_maxDetectionRange);\n  }\n\n  return playerDist < (x568_maxDetectionRange * x568_maxDetectionRange);\n}\n\nvoid CSpankWeed::Attack(CStateManager&, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBCMeleeAttackCmd(pas::ESeverity::Zero));\n  } else if (msg == EStateMsg::Update) {\n    if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::MeleeAttack)\n      return;\n\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBCMeleeAttackCmd(pas::ESeverity::Zero));\n  } else if (msg == EStateMsg::Deactivate) {\n    x5b8_ = 3;\n  }\n}\n\nvoid CSpankWeed::TargetPatrol(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Combat);\n    RemoveMaterial(EMaterialTypes::Solid, mgr);\n    x5b4_ = 2;\n  } else if (msg == EStateMsg::Deactivate) {\n    x5b8_ = 2;\n  }\n}\n\nvoid CSpankWeed::Lurk(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x460_knockBackController.SetEnableFreeze(true);\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Lurk);\n    RemoveMaterial(EMaterialTypes::Solid, mgr);\n    x5b4_ = 1;\n  } else if (msg == EStateMsg::Deactivate) {\n    x5b8_ = 1;\n  }\n}\n\nvoid CSpankWeed::FadeOut(CStateManager&, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x5bc_ = 0;\n    x57c_canKnockBack = false;\n    x5b4_ = 6;\n  } else if (msg == EStateMsg::Update) {\n    if (x5bc_ == 0) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::Step)\n        x5bc_ = 2;\n      else\n        x450_bodyController->GetCommandMgr().DeliverCmd(\n            CBCStepCmd(pas::EStepDirection::Backward, pas::EStepType::Normal));\n\n    } else if (x5bc_ == 2) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::Step)\n        x5bc_ = 3;\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x5b8_ = 6;\n  }\n}\n\nvoid CSpankWeed::FadeIn(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x5bc_ = 0;\n    x57c_canKnockBack = true;\n    x5b4_ = 5;\n  } else if (msg == EStateMsg::Update) {\n    if (x5bc_ == 0) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::Step)\n        x5bc_ = 2;\n      else\n        x450_bodyController->GetCommandMgr().DeliverCmd(\n            CBCStepCmd(pas::EStepDirection::Forward, pas::EStepType::Normal));\n\n    } else if (x5bc_ == 2) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::Step)\n        x5bc_ = 3;\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x5b8_ = 5;\n  }\n\n  xe7_28_worldLightingDirty = true;\n}\n\nvoid CSpankWeed::Patrol(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x460_knockBackController.SetEnableFreeze(false);\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n    RemoveMaterial(EMaterialTypes::Solid, EMaterialTypes::Scannable, mgr);\n    RemoveMaterial(EMaterialTypes::Orbit, EMaterialTypes::Target, mgr);\n    x594_collisionMgr->SetActive(mgr, false);\n    x598_isHiding = true;\n    x5b4_ = 0;\n  } else if (msg == EStateMsg::Deactivate) {\n    AddMaterial(EMaterialTypes::Orbit, EMaterialTypes::Target, EMaterialTypes::Scannable, mgr);\n    SetTranslation(x584_retreatOrigin);\n    x594_collisionMgr->SetActive(mgr, true);\n    x598_isHiding = false;\n    x460_knockBackController.SetEnableFreeze(true);\n    x5b8_ = 0;\n  }\n}\n\nvoid CSpankWeed::KnockBack(const zeus::CVector3f& backVec, CStateManager& mgr, const CDamageInfo& info,\n                           EKnockBackType type, bool inDeferred, float magnitude) {\n  if (!x57c_canKnockBack)\n    return;\n  CPatterned::KnockBack(backVec, mgr, info, type, inDeferred, magnitude);\n  x57c_canKnockBack = false;\n}\n\nfloat CSpankWeed::GetPlayerDistance(CStateManager& mgr) const {\n  return (mgr.GetPlayer().GetTranslation() - x5a8_lockonTarget).magSquared();\n}\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CSpankWeed.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/Collision/CCollisionActorManager.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce::MP1 {\nclass CSpankWeed : public CPatterned {\n  float x568_maxDetectionRange;\n  float x56c_detectionHeightRange;\n  float x570_maxHearingRange;\n  float x574_maxSightRange;\n  float x578_hideTime;\n  bool x57c_canKnockBack = false;\n  /* float x580_ = 0.f; unused */\n  zeus::CVector3f x584_retreatOrigin;\n  TUniqueId x590_ = kInvalidUniqueId;\n  std::unique_ptr<CCollisionActorManager> x594_collisionMgr;\n  bool x598_isHiding = true;\n  zeus::CVector3f x59c_lockonOffset;\n  zeus::CVector3f x5a8_lockonTarget;\n  s32 x5b4_ = -1;\n  s32 x5b8_ = -1;\n  s32 x5bc_ = -1;\n\n  float GetPlayerDistance(CStateManager&) const;\n\npublic:\n  DEFINE_PATTERNED(SpankWeed);\n\n  CSpankWeed(TUniqueId, std::string_view, const CEntityInfo&, const zeus::CTransform&, CModelData&&,\n             const CActorParameters&, const CPatternedInfo&, float, float, float, float);\n\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void Think(float, CStateManager&) override;\n  zeus::CVector3f GetOrbitPosition(const CStateManager&) const override;\n  zeus::CVector3f GetAimPosition(const CStateManager&, float) const override;\n  bool AnimOver(CStateManager&, float) override { return x5bc_ == 3; }\n  void Flinch(CStateManager&, EStateMsg, float) override;\n  bool Delay(CStateManager&, float) override;\n  bool InRange(CStateManager&, float) override;\n  bool HearPlayer(CStateManager&, float) override;\n  bool InDetectionRange(CStateManager&, float) override;\n  void Attack(CStateManager&, EStateMsg, float) override;\n  void TargetPatrol(CStateManager&, EStateMsg, float) override;\n  void Lurk(CStateManager&, EStateMsg, float) override;\n  void FadeOut(CStateManager&, EStateMsg, float) override;\n  void FadeIn(CStateManager&, EStateMsg, float) override;\n  void Patrol(CStateManager&, EStateMsg, float) override;\n  void KnockBack(const zeus::CVector3f&, CStateManager&, const CDamageInfo& info, EKnockBackType type, bool inDeferred,\n                 float magnitude) override;\n};\n} // namespace metaforce::MP1"
  },
  {
    "path": "Runtime/MP1/World/CThardus.cpp",
    "content": "#include \"Runtime/MP1/World/CThardus.hpp\"\n\n#include <array>\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Camera/CCameraManager.hpp\"\n#include \"Runtime/Camera/CFirstPersonCamera.hpp\"\n#include \"Runtime/Collision/CCollisionActor.hpp\"\n#include \"Runtime/Collision/CCollisionActorManager.hpp\"\n#include \"Runtime/MP1/CSamusHud.hpp\"\n#include \"Runtime/MP1/World/CIceAttackProjectile.hpp\"\n#include \"Runtime/MP1/World/CThardusRockProjectile.hpp\"\n#include \"Runtime/Weapon/CGameProjectile.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/World/CDestroyableRock.hpp\"\n#include \"Runtime/World/CGameLight.hpp\"\n#include \"Runtime/World/CPatternedInfo.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CRepulsor.hpp\"\n#include \"Runtime/World/CScriptDistanceFog.hpp\"\n#include \"Runtime/World/CScriptWaypoint.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\n#include <Audio/SFX/IceWorld.h>\n#include <Audio/SFX/Thardus.h>\nnamespace metaforce::MP1 {\nnamespace {\nconstexpr std::array<SSphereJointInfo, 7> skDamageableSphereJointInfoList1{{\n    {\"R_knee\", 1.f},\n    {\"R_Elbow_Collision_LCTR\", 1.5f},\n    {\"L_Elbow_Collision_LCTR\", 1.5f},\n    {\"L_Knee_Collision_LCTR\", 1.f},\n    {\"R_Back_Rock_Collision_LCTR\", 2.5f},\n    {\"L_Back_Rock_Collision_LCTR\", 1.5f},\n    {\"Head_Collision_LCTR\", 1.5f},\n}};\n\nconstexpr std::array<SSphereJointInfo, 5> skDamageableSphereJointInfoList2{{\n    {\"R_Shoulder_Collision_LCTR\", 0.75f},\n    {\"L_Shoulder_Collision_LCTR\", 0.75f},\n    {\"Spine_Collision_LCTR\", 0.75f},\n    {\"R_Hand_Collision_LCTR\", 2.25f},\n    {\"L_Hand_Collision_LCTR\", 2.f},\n}};\n\nconstexpr std::array<SAABoxJointInfo, 2> skFootCollision{{\n    {\"R_Foot_Collision_LCTR\", zeus::CVector3f(3.f, 3.f, 1.f)},\n    {\"L_Foot_Collision_LCTR\", zeus::CVector3f(3.f, 2.f, 3.f)},\n}};\n\nconstexpr std::array skSearchJointNames{\n    \"R_knee\"sv,\n    \"R_Elbow_Collision_LCTR\"sv,\n    \"L_Elbow_Collision_LCTR\"sv,\n    \"L_Knee_Collision_LCTR\"sv,\n    \"R_Back_Rock_Collision_LCTR\"sv,\n    \"L_Back_Rock_Collision_LCTR\"sv,\n    \"Head_Collision_LCTR\"sv,\n};\n\nconstexpr std::array skRockJoints{\n    \"R_knee\"sv, \"R_forearm\"sv, \"L_elbow\"sv, \"L_hip\"sv, \"R_collar_BigRock_SDK\"sv, \"collar_rock4_SDK\"sv, \"Neck_1\"sv,\n};\n} // Anonymous namespace\n\nCThardus::CThardus(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                   CModelData&& mData, const CActorParameters& actParms, const CPatternedInfo& pInfo,\n                   std::vector<CStaticRes> mData1, std::vector<CStaticRes> mData2, CAssetId particle1,\n                   CAssetId particle2, CAssetId particle3, float f1, float f2, float f3, float f4, float f5, float f6,\n                   CAssetId stateMachine, CAssetId particle4, CAssetId particle5, CAssetId particle6,\n                   CAssetId particle7, CAssetId particle8, CAssetId particle9, CAssetId texture, u32 sfxId1,\n                   CAssetId particle10, u32 sfxId2, u32 sfxId3, u32 sfxId4)\n: CPatterned(ECharacter::Thardus, uid, name, EFlavorType::Zero, info, xf, std::move(mData), pInfo,\n             EMovementType::Ground, EColliderType::One, EBodyType::BiPedal, actParms, EKnockBackVariant::Large)\n, x5cc_(std::move(mData1))\n, x5dc_(std::move(mData2))\n, x600_(particle1)\n, x604_(particle2)\n, x608_(particle3)\n, x630_(stateMachine)\n, x694_(f1)\n, x698_(f2)\n, x6a0_(f3)\n, x6a4_(f4)\n, x6a8_(f5)\n, x6ac_(f6)\n, x6d0_(particle4)\n, x6d4_(particle5)\n, x6d8_(particle6)\n, x6dc_(particle7)\n, x6e0_(particle8)\n, x6e4_(particle9)\n, x6e8_(texture)\n, x6ec_(CSfxManager::TranslateSFXID(sfxId1))\n, x6f0_(particle10)\n, x758_(sfxId2)\n, x75c_(sfxId3)\n, x760_(sfxId4)\n, x7f0_pathFindSearch(nullptr, 1, pInfo.GetPathfindingIndex(), 1.f, 1.f) {\n  CMaterialList exclude = GetMaterialFilter().GetExcludeList();\n  CMaterialList include = GetMaterialFilter().GetIncludeList();\n  exclude.Add(EMaterialTypes::Player);\n  exclude.Add(EMaterialTypes::Character);\n  exclude.Add(EMaterialTypes::CollisionActor);\n  SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(include, exclude));\n  std::vector<CAssetId> gens;\n  gens.reserve(6);\n  gens.push_back(particle4);\n  gens.push_back(particle5);\n  gens.push_back(particle6);\n  gens.push_back(particle7);\n  gens.push_back(particle8);\n  gens.push_back(particle9);\n  GetModelData()->GetAnimationData()->GetParticleDB().CacheParticleDesc(\n      CCharacterInfo::CParticleResData(std::move(gens), {}, {}, {}));\n  x798_.reserve(6);\n  x7a8_timers.reserve(16);\n  UpdateThermalFrozenState(true);\n  xd0_damageMag = 0.f;\n  x50c_baseDamageMag = 0.f;\n  x8f4_waypoints.reserve(16);\n  x91c_flareTexture = g_SimplePool->GetObj(\"Thermal_Spot_2\"sv);\n  x91c_flareTexture.Lock();\n  x403_26_stateControlledMassiveDeath = false;\n  x460_knockBackController.SetAutoResetImpulse(false);\n  SetMass(100000.f);\n}\n\nvoid CThardus::UpdateRockThermalState(float dt, CStateManager& mgr) {\n  if (x7c4_ == 0) {\n    x93a_ = false;\n    return;\n  }\n\n  bool bVar9 = false;\n  float dVar15 = 0.f;\n  float dVar13 = 1.f;\n  if (x928_currentRockId != kInvalidUniqueId) {\n    if (TCastToPtr<CActor> rock = mgr.ObjectById(x928_currentRockId)) {\n      x92c_currentRockPos = rock->GetTranslation();\n    } else {\n      x928_currentRockId = kInvalidUniqueId;\n    }\n  }\n\n  if (!x939_) {\n    CGameCamera* cam = mgr.GetCameraManager()->GetCurrentCamera(mgr);\n    zeus::CVector3f posDiff = x92c_currentRockPos - cam->GetTranslation();\n    zeus::CVector3f camFront = cam->GetTransform().frontVector();\n    zeus::CVector3f direction = posDiff.normalized();\n    dVar13 = 0.f;\n    float dVar11 = direction.dot(camFront);\n    if (dVar13 <= dVar11) {\n      dVar13 = dVar11 * dVar11;\n    }\n  }\n\n  if (x7c4_ == 2) {\n    dVar15 = -(x7b8_ <= 2.f ? x7b8_ : 2.f) * 0.5f - 1.f; // std::min doesn't quite cut the mustard here\n    if (x7b8_ > 2.f) {\n      x7c4_ = 0;\n      x7b8_ = 0.f;\n      x7c0_ = 0.f;\n    }\n    x7b8_ += dt;\n  } else if (x7c4_ == 1) {\n    dVar15 = (x7b8_ <= 0.25f ? x7b8_ : 0.25f) / 0.25f;\n\n    if (x7b8_ > 0.25f) {\n      x7c4_ = 3;\n      x7b8_ = 0.f;\n    }\n    x7b8_ += dt;\n    if (mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::Thermal) {\n      x688_ = true;\n    }\n  } else if (x7c4_ == 3) {\n    dVar15 = 1.f;\n    if (x7bc_ < x7b8_ && !x938_) {\n      x7c4_ = 2;\n      x7b8_ = 0.f;\n    }\n\n    x7b8_ += dt;\n    if (mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::Thermal) {\n      bVar9 = false;\n      x688_ = true;\n      if (x938_ && dVar13 > 0.75f) {\n        bVar9 = true;\n      }\n    }\n  }\n\n  x7c0_ = dVar15 * dVar13;\n  const float mag = 0.8f * (dVar15 * dVar13);\n  mgr.SetThermalColdScale2(mgr.GetThermalColdScale2() + mag);\n  x50c_baseDamageMag = mag;\n\n  if (x93a_ != bVar9) {\n    if (bVar9) {\n      CSamusHud::DisplayHudMemo(g_MainStringTable->GetString(18), CHUDMemoParms(5.f, true, false, false));\n    }\n    x93a_ = bVar9;\n  }\n}\n\nvoid CThardus::sub801de9f8(CStateManager& mgr) {\n  float dVar5 = mgr.GetActiveRandom()->Float();\n  if (!IsLastRock() || dVar5 >= 0.3f) {\n    const float local_28 = std::max(0.f, dVar5 - 0.19999999f);\n    if (local_28 > 0.8f) {\n      x5c4_ = 2;\n    } else if (local_28 >= 0.4f) {\n      x5c4_ = 1;\n    } else {\n      x5c4_ = 0;\n    }\n    ++x574_;\n    x944_ = 0.3f;\n  } else {\n    x93b_ = true;\n  }\n}\n\nvoid CThardus::sub801dd608(CStateManager& mgr) {\n  zeus::CVector3f scale = GetModelData()->GetScale();\n  CAnimData* animData = GetModelData()->GetAnimationData();\n  for (size_t i = 0; i < x610_destroyableRocks.size(); ++i) {\n    zeus::CTransform xf =\n        GetTransform() * (zeus::CTransform::Scale(scale) * animData->GetLocatorTransform(skRockJoints[i], nullptr));\n    if (TCastToPtr<CActor> act = mgr.ObjectById(x610_destroyableRocks[i])) {\n      act->SetTransform(xf);\n    }\n    if (TCastToPtr<CGameLight> gl = mgr.ObjectById(x6c0_rockLights[i])) {\n      gl->SetTransform(xf);\n    }\n  }\n}\nvoid CThardus::sub801dcfa4(CStateManager& mgr) {\n  for (size_t j = 0; j < x5f0_rockColliders->GetNumCollisionActors(); ++j) {\n    const auto& jInfo = x5f0_rockColliders->GetCollisionDescFromIndex(j);\n    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(jInfo.GetCollisionActorId())) {\n      if (!colAct->GetActive()) {\n        continue;\n      }\n      TUniqueId rockId = x610_destroyableRocks[j];\n      if (auto* rock = static_cast<CDestroyableRock*>(mgr.ObjectById(rockId))) {\n        if (x909_) {\n          *rock->HealthInfo(mgr) = CHealthInfo(x90c_rockHealths[j], 0.f);\n        }\n        if (j == x648_currentRock && !x93d_) {\n          colAct->SetDamageVulnerability(*rock->GetDamageVulnerability());\n          rock->SetThermalMag(0.8f);\n        } else {\n          colAct->SetDamageVulnerability(CDamageVulnerability::ImmuneVulnerabilty());\n          rock->SetThermalMag(0.f);\n        }\n\n        CHealthInfo* hInfo = colAct->HealthInfo(mgr);\n        if (hInfo != nullptr) {\n          if (hInfo->GetHP() > 0.f) {\n            *rock->HealthInfo(mgr) = *hInfo;\n            if (!x909_) {\n              x90c_rockHealths[j] = hInfo->GetHP();\n            }\n          } else if (!rock->IsUsingPhazonModel()) {\n            BreakRock(mgr, j);\n            DoFaint(mgr);\n          } else {\n            rock->SetActive(false);\n            colAct->SetActive(false);\n            mgr.ObjectById(x6c0_rockLights[j])->SetActive(false);\n            ++x648_currentRock;\n            DoFlinch(mgr);\n            const bool isThermalActive = mgr.GetPlayerState()->GetCurrentVisor() != CPlayerState::EPlayerVisor::Thermal;\n            if (isThermalActive || (!isThermalActive && x7c4_ != 3)) {\n              SetRockParticle(mgr, GetTranslation(), x6d8_);\n            }\n            ProcessSoundEvent(x758_, 1.f, 0, 0.1f, 1000.f, 0.16f, 1.f, zeus::skZero3f, GetTranslation(),\n                              mgr.GetNextAreaId(), mgr, true);\n\n            if (IsLastRock() && !x8f0_) {\n              DoDoubleSnap(mgr);\n            }\n            sub801dbc40();\n          }\n        }\n      }\n    }\n  }\n}\n\nvoid CThardus::Think(float dt, CStateManager& mgr) {\n  if (!GetActive() || !x450_bodyController->GetActive()) {\n    return;\n  }\n\n  if (!x91c_flareTexture) {\n    x91c_flareTexture.Lock();\n  }\n\n  if (x7c8_) {\n    float fVar2 = 10.f * GetModelData()->GetScale().x();\n    zeus::CVector3f diff = mgr.GetPlayer().GetTranslation() - x7cc_;\n    if (diff.magSquared() < fVar2 * fVar2) {\n      mgr.ApplyDamage(GetUniqueId(), mgr.GetPlayer().GetUniqueId(), GetUniqueId(),\n                      CDamageInfo(CWeaponMode(EWeaponType::AI), 0.f, 0.f, 10.f), CMaterialFilter::skPassEverything,\n                      diff.normalized());\n      x688_ = true;\n      x7c8_ = false;\n      x7cc_.zeroOut();\n    }\n  }\n\n  UpdateRockThermalState(dt, mgr);\n\n  if (!IsLastRock()) {\n    // NOTE: (phil), yes this is what's actually happening\n#if 0\n    if (x648_currentRock < x610_destroyableRocks.size() - 2) {\n      x690_ = 1.f;\n    } else {\n      x690_ = 1.f;\n    }\n#endif\n    x690_ = 1.f;\n  } else {\n    x690_ = 1.f;\n    SendScriptMsgs(EScriptObjectState::DeactivateState, mgr, EScriptObjectMessage::None);\n  }\n\n  if (!x93c_) {\n    x3b4_speed = x690_;\n    x402_28_isMakingBigStrike = false;\n    x504_damageDur = 0.f;\n  } else {\n    x3b4_speed = x690_;\n    x402_28_isMakingBigStrike = true;\n    x504_damageDur = 1.f;\n  }\n\n  CPatterned::Think(dt, mgr);\n\n  if (x648_currentRock > 2 && !x689_) {\n    _DoSuckState(mgr);\n  }\n\n  x5f0_rockColliders->Update(dt, mgr, CCollisionActorManager::EUpdateOptions::ObjectSpace);\n  x5f4_->Update(dt, mgr, CCollisionActorManager::EUpdateOptions::ObjectSpace);\n  x5f8_->Update(dt, mgr, CCollisionActorManager::EUpdateOptions::ObjectSpace);\n\n  sub801dd608(mgr);\n  sub801dcfa4(mgr);\n\n  if (x610_destroyableRocks.size() <= x648_currentRock) {\n    Death(mgr, zeus::skZero3f, EScriptObjectState::DeathRattle);\n  }\n\n  if (mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::Thermal) {\n    x402_29_drawParticles = false;\n    UpdateNonDestroyableCollisionActorMaterials(EUpdateMaterialMode::Remove, EMaterialTypes::ProjectilePassthrough,\n                                                mgr);\n    for (size_t i = 0; i < x610_destroyableRocks.size(); ++i) {\n      if (auto* act = static_cast<CActor*>(mgr.ObjectById(x610_destroyableRocks[i]))) {\n        if (x648_currentRock == i && !x688_ && !x93c_ && !x909_ && !x93d_) {\n          act->AddMaterial(EMaterialTypes::Orbit, mgr);\n          act->AddMaterial(EMaterialTypes::Target, mgr);\n        } else {\n          act->RemoveMaterial(EMaterialTypes::Orbit, mgr);\n          act->RemoveMaterial(EMaterialTypes::Target, mgr);\n        }\n      }\n    }\n    if (x688_) {\n      x688_ = false;\n    }\n\n  } else {\n    x402_29_drawParticles = true;\n    UpdateNonDestroyableCollisionActorMaterials(EUpdateMaterialMode::Add, EMaterialTypes::ProjectilePassthrough, mgr);\n    for (size_t i = 0; i < x610_destroyableRocks.size(); ++i) {\n      if (auto* act = static_cast<CActor*>(mgr.ObjectById(x610_destroyableRocks[i]))) {\n        if (!x688_ && !x93c_ && !x909_ && !x93d_) {\n          bool found = act->GetName().find(\"Neck_1\"sv) != std::string::npos;\n          if (!found || !x6b0_destroyedRocks[x648_currentRock] ||\n              x648_currentRock == x610_destroyableRocks.size() - 1) {\n            if (!x6b0_destroyedRocks[i]) {\n              if (!found || x6b0_destroyedRocks[i]) {\n                act->RemoveMaterial(EMaterialTypes::Orbit, mgr);\n                act->RemoveMaterial(EMaterialTypes::Target, mgr);\n              }\n            } else {\n              act->AddMaterial(EMaterialTypes::Orbit, mgr);\n              act->AddMaterial(EMaterialTypes::Target, mgr);\n            }\n          } else {\n            act->RemoveMaterial(EMaterialTypes::Orbit, mgr);\n            act->RemoveMaterial(EMaterialTypes::Target, mgr);\n          }\n        } else {\n          x688_ = false;\n          act->RemoveMaterial(EMaterialTypes::Orbit, mgr);\n          act->RemoveMaterial(EMaterialTypes::Target, mgr);\n        }\n      }\n    }\n  }\n\n  if (x644_ == 1) {\n    UpdateExcludeList(x5f0_rockColliders, EUpdateMaterialMode::Add, EMaterialTypes::Player, mgr);\n    UpdateExcludeList(x5f4_, EUpdateMaterialMode::Add, EMaterialTypes::Player, mgr);\n    UpdateExcludeList(x5f8_, EUpdateMaterialMode::Add, EMaterialTypes::Player, mgr);\n    CCameraManager* cameraManager = mgr.GetCameraManager();\n    if (x93c_) {\n      if (x6f8_ < 0.3f) {\n        x6f8_ += dt;\n      } else {\n        if (cameraManager->GetFirstPersonCamera()->GetUniqueId() == cameraManager->GetCurrentCameraId()) {\n          cameraManager->AddCameraShaker(\n              CCameraShakeData::BuildMissileCameraShake(0.25f, 0.75f, 125.f, GetTranslation()), true);\n        }\n        x6f8_ = 0.f;\n      }\n\n      if (cameraManager->GetCurrentCameraId() != cameraManager->GetFirstPersonCamera()->GetUniqueId() && x95d_ == 0) {\n        CSamusHud::DisplayHudMemo(g_MainStringTable->GetString(104), CHUDMemoParms(5.f, true, false, false));\n      }\n    }\n  } else {\n    UpdateExcludeList(x5f0_rockColliders, EUpdateMaterialMode::Remove, EMaterialTypes::Player, mgr);\n    UpdateExcludeList(x5f4_, EUpdateMaterialMode::Remove, EMaterialTypes::Player, mgr);\n    UpdateExcludeList(x5f8_, EUpdateMaterialMode::Remove, EMaterialTypes::Player, mgr);\n  }\n\n  UpdateHealthInfo(mgr);\n}\n\nvoid CThardus::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  CPatterned::AcceptScriptMsg(msg, uid, mgr);\n\n  switch (msg) {\n  case EScriptObjectMessage::Reset: {\n    x95c_doCodeTrigger = true;\n    x450_bodyController->SetFallState(pas::EFallState::Zero);\n    x450_bodyController->SetState(pas::EAnimationState::Locomotion);\n    x93d_ = false;\n    break;\n  }\n  case EScriptObjectMessage::SetToMax: {\n    for (size_t i = x648_currentRock; i < x610_destroyableRocks.size() - 1; ++i) {\n      if (CEntity* ent = mgr.ObjectById(x610_destroyableRocks[i])) {\n        ent->SetActive(false);\n      }\n      ++x648_currentRock;\n    }\n    break;\n  }\n  case EScriptObjectMessage::Stop: {\n    Death(mgr, {}, EScriptObjectState::DeathRattle);\n    break;\n  }\n  case EScriptObjectMessage::Action: {\n    if (!x5c8_heardPlayer) {\n      x5c8_heardPlayer = true;\n    }\n    break;\n  }\n  case EScriptObjectMessage::Touched: {\n    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(uid)) {\n      if (TCastToPtr<CPlayer> pl = mgr.ObjectById(colAct->GetLastTouchedObject())) {\n        if (x420_curDamageRemTime > 0.f) {\n          break;\n        }\n        u32 rand = static_cast<u32>(mgr.GetActiveRandom()->Next());\n        float damageMult = 1.f;\n        zeus::CVector3f knockBack = zeus::skForward;\n        if (x644_ == 1) {\n          damageMult = 2.f;\n          knockBack = (rand % 2) != 0u ? zeus::skRight : zeus::skLeft;\n        }\n\n        if (mgr.GetPlayer().GetFrozenState()) {\n          mgr.GetPlayer().UnFreeze(mgr);\n        }\n\n        knockBack = GetTransform().buildMatrix3f() * knockBack;\n        CDamageInfo dInfo = GetContactDamage();\n        dInfo.SetDamage(damageMult * dInfo.GetDamage());\n        mgr.ApplyDamage(GetUniqueId(), pl->GetUniqueId(), GetUniqueId(), dInfo,\n                        CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}),\n                        x644_ == 1 ? knockBack : zeus::skZero3f);\n        x420_curDamageRemTime = x424_damageWaitTime;\n      } else if (TCastToConstPtr<CBomb>(mgr.GetObjectById(colAct->GetLastTouchedObject()))) {\n        if (x644_ == 1 && x93c_) {\n          BreakRock(mgr, x648_currentRock);\n        }\n      }\n    }\n\n    break;\n  }\n  case EScriptObjectMessage::Registered: {\n    x610_destroyableRocks.reserve(x5cc_.size());\n    x6b0_destroyedRocks.reserve(x5cc_.size());\n    x6c0_rockLights.reserve(x5cc_.size());\n    x90c_rockHealths.reserve(x5cc_.size());\n    for (size_t i = 0; i < x5cc_.size(); ++i) {\n      float health = (i == x5cc_.size() - 1) ? 2.f * x6a8_ : x6a8_;\n      TUniqueId rockId = mgr.AllocateUniqueId();\n      CModelData mData1(x5cc_[i]);\n      mgr.AddObject(new CDestroyableRock(\n          rockId, true, \"\", CEntityInfo(GetAreaIdAlways(), NullConnectionList), {}, std::move(mData1), 0.f,\n          CHealthInfo(health, 0.f),\n          CDamageVulnerability(\n              EVulnerability::Normal, EVulnerability::Deflect, EVulnerability::Normal, EVulnerability::Normal,\n              EVulnerability::Normal, EVulnerability::Normal, EVulnerability::Normal, EVulnerability::Deflect,\n              EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect,\n              EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EDeflectType::One),\n          GetMaterialList(), x630_,\n          CActorParameters(CLightParameters(false, 0.f, CLightParameters::EShadowTesselation::Invalid, 0.f, 0.f,\n                                            zeus::skWhite, true, CLightParameters::EWorldLightingOptions::NoShadowCast,\n                                            CLightParameters::ELightRecalculationOptions::LargeFrameCount,\n                                            zeus::skZero3f, -1, -1, false, 0),\n                           {}, {}, {}, {}, true, true, false, false, 0.f, 0.f, 1.f),\n          x5dc_[i], 0));\n      x610_destroyableRocks.push_back(rockId);\n      x6b0_destroyedRocks.push_back(false);\n      TUniqueId lightId = mgr.AllocateUniqueId();\n      auto* gl = new CGameLight(lightId, GetAreaIdAlways(), false, \"\"sv, {}, GetUniqueId(),\n                                CLight::BuildPoint({}, zeus::skBlue), 0, 0, 0.f);\n      gl->SetActive(false);\n      mgr.AddObject(gl);\n      x6c0_rockLights.push_back(lightId);\n      x90c_rockHealths.push_back(health);\n    }\n\n    AddMaterial(EMaterialTypes::ScanPassthrough, mgr);\n    AddMaterial(EMaterialTypes::CameraPassthrough, mgr);\n    RemoveMaterial(EMaterialTypes::Orbit, mgr);\n    RemoveMaterial(EMaterialTypes::Target, mgr);\n    _SetupCollisionManagers(mgr);\n    x450_bodyController->SetFallState(pas::EFallState::Two);\n    x450_bodyController->Activate(mgr);\n    x450_bodyController->BodyStateInfo().SetLocoAnimChangeAtEndOfAnimOnly(true);\n    SetState(0, mgr);\n    sub801dec80();\n    AddMaterial(EMaterialTypes::RadarObject, mgr);\n    break;\n  }\n  case EScriptObjectMessage::Deleted: {\n    x5f0_rockColliders->Destroy(mgr);\n    x5f4_->Destroy(mgr);\n    x5f8_->Destroy(mgr);\n    mgr.FreeScriptObject(x64c_fog);\n    for (const auto& id : x610_destroyableRocks) {\n      mgr.FreeScriptObject(id);\n    }\n\n    for (const auto& id : x6c0_rockLights) {\n      mgr.FreeScriptObject(id);\n    }\n    break;\n  }\n  case EScriptObjectMessage::InitializedInArea: {\n    if (x94c_initialized) {\n      break;\n    }\n    x94c_initialized = true;\n    x764_startTransform = GetTransform();\n\n    for (const SConnection& conn : GetConnectionList()) {\n      TUniqueId connId = mgr.GetIdForScript(conn.x8_objId);\n      if (connId == kInvalidUniqueId) {\n        continue;\n      }\n\n      if (conn.x0_state == EScriptObjectState::Patrol) {\n        if (TCastToPtr<CScriptWaypoint> wp = mgr.ObjectById(connId)) {\n          rstl::reserved_vector<TUniqueId, 16> wpIds;\n          GatherWaypoints(wp, mgr, wpIds);\n          x578_waypoints.push_back(wpIds);\n        } else if (CPatterned* p = CPatterned::CastTo<CThardusRockProjectile>(mgr.ObjectById(connId))) {\n          x5fc_projectileId = connId;\n          x60c_projectileEditorId = conn.x8_objId;\n          p->SetActive(false);\n        } else if (TCastToConstPtr<CRepulsor>(mgr.GetObjectById(connId))) {\n          x664_repulsors.push_back(connId);\n        } else if (TCastToConstPtr<CScriptDistanceFog>(mgr.GetObjectById(connId))) {\n          x64c_fog = connId;\n        }\n      } else if (conn.x0_state == EScriptObjectState::Zero) {\n        if (TCastToPtr<CScriptWaypoint> wp = mgr.ObjectById(connId)) {\n          x8f4_waypoints.push_back(connId);\n          wp->SetActive(false);\n        }\n      } else if (conn.x0_state == EScriptObjectState::Dead) {\n        if (TCastToConstPtr<CScriptTimer>(mgr.GetObjectById(connId))) {\n          x7a8_timers.push_back(connId);\n        }\n      }\n    }\n    x7f0_pathFindSearch.SetArea(mgr.GetWorld()->GetAreaAlways(GetAreaIdAlways())->GetPostConstructed()->x10bc_pathArea);\n    break;\n  }\n  case EScriptObjectMessage::Damage: {\n    if (TCastToConstPtr<CCollisionActor> colAct = mgr.GetObjectById(uid)) {\n      TUniqueId lastTouchedObj = colAct->GetLastTouchedObject();\n      TUniqueId targetRock = kInvalidUniqueId;\n\n      for (size_t i = 0; i < x5f0_rockColliders->GetNumCollisionActors(); ++i) {\n        const CJointCollisionDescription& desc = x5f0_rockColliders->GetCollisionDescFromIndex(i);\n        if (desc.GetCollisionActorId() == uid) {\n          targetRock = x610_destroyableRocks[i];\n          break;\n        }\n      }\n\n      if (targetRock == kInvalidUniqueId) {\n        break;\n      }\n      if (auto* rock = static_cast<CDestroyableRock*>(mgr.ObjectById(targetRock))) {\n        if (TCastToConstPtr<CGameProjectile> proj = mgr.GetObjectById(lastTouchedObj)) {\n          if (GetBodyController()->GetBodyStateInfo().GetCurrentAdditiveStateId() !=\n                  pas::EAnimationState::AdditiveReaction &&\n              rock->Get_x324() <= 0.f) {\n            GetBodyController()->GetCommandMgr().DeliverCmd(\n                CBCAdditiveReactionCmd(pas::EAdditiveReactionType::Five, 1.f, false));\n          }\n\n          rock->TakeDamage(zeus::skZero3f, 0.f);\n          const bool thermalInactive = mgr.GetPlayerState()->GetCurrentVisor() != CPlayerState::EPlayerVisor::Thermal;\n          if (thermalInactive || x7c4_ != 3) {\n            SetRockParticle(mgr, proj->GetTranslation(), x6d0_);\n          }\n          if (!rock->IsUsingPhazonModel()) {\n            ProcessSoundEvent(x75c_, 1.f, 0, 0.1f, 1000.f, 0.16f, 1.f, zeus::skZero3f, rock->GetTranslation(),\n                              mgr.GetNextAreaId(), mgr, true);\n          } else {\n            ProcessSoundEvent(SFXsfx0AC0, 1.f, 0, 0.1f, 1000.f, 0.16f, 1.f, zeus::skZero3f, rock->GetTranslation(),\n                              mgr.GetNextAreaId(), mgr, false);\n          }\n        }\n      }\n    }\n    break;\n  }\n  default:\n    break;\n  }\n}\n\nvoid CThardus::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) {\n  CPatterned::PreRender(mgr, frustum);\n  if (mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::Thermal) {\n    xb4_drawFlags = CModelFlags(0, 0, 1, zeus::skWhite);\n  } else {\n    xb4_drawFlags = CModelFlags(0, 0, 3, zeus::skWhite);\n  }\n}\n\nvoid CThardus::Render(CStateManager& mgr) {\n  CPatterned::Render(mgr);\n  if (mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::Thermal && x7c4_ != 0) {\n    RenderFlare(mgr, x7c0_);\n  }\n}\n\nvoid CThardus::Touch(CActor& act, CStateManager& mgr) {\n  // Intentionally empty\n}\n\nzeus::CVector3f CThardus::GetOrbitPosition(const CStateManager& mgr) const { return GetAimPosition(mgr, 0.f); }\n\nzeus::CVector3f CThardus::GetAimPosition(const CStateManager& mgr, float dt) const {\n  return GetLctrTransform(x93c_ ? \"center_LCTR\"sv : \"Neck_1\"sv).origin;\n}\nzeus::CAABox CThardus::GetSortingBounds(const CStateManager& mgr) const {\n  zeus::CVector3f extents = 0.15f * (x9c_renderBounds.max - x9c_renderBounds.min);\n  return zeus::CAABox(x9c_renderBounds.min - extents, x9c_renderBounds.max + extents);\n}\n\nvoid CThardus::DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) {\n  switch (type) {\n  case EUserEventType::Projectile: {\n    zeus::CTransform wristXf = GetLctrTransform(\"L_wrist\"sv);\n    CRayCastResult res = mgr.RayStaticIntersection(wristXf.origin, zeus::skDown, 100.f,\n                                                   CMaterialFilter::MakeInclude(EMaterialTypes::Solid));\n    zeus::CTransform xf = zeus::lookAt(res.GetPoint() + zeus::CVector3f{0.f, 0.f, 1.f}, GetTranslation());\n    xf.rotateLocalZ(zeus::degToRad(mgr.GetActiveRandom()->Range(-5.f, 5.f)));\n    mgr.AddObject(new CIceAttackProjectile(\n        g_SimplePool->GetObj({SBIG('PART'), x600_}), g_SimplePool->GetObj({SBIG('PART'), x604_}),\n        g_SimplePool->GetObj({SBIG('PART'), x608_}), mgr.AllocateUniqueId(), GetAreaIdAlways(),\n        mgr.GetPlayer().GetUniqueId(), true, xf, CDamageInfo(CWeaponMode::Ice(), 6.f, 0.f, 0.f), zeus::CAABox(0.f, 1.f),\n        x6ac_, zeus::degToRad(42.f), x6e8_, x6ec_, SFXsfx0AAD, x6f0_));\n    break;\n  }\n  case EUserEventType::LoopedSoundStop:\n    CPatterned::DoUserAnimEvent(mgr, node, type, dt);\n    break;\n  case EUserEventType::AlignTargetPos: {\n    SetState(2, mgr);\n    break;\n  }\n  case EUserEventType::Delete:\n    CPatterned::DoUserAnimEvent(mgr, node, type, dt);\n    break;\n  case EUserEventType::DamageOn: {\n    x7c8_ = true;\n    x7cc_ = (zeus::CTransform::Scale(GetModelData()->GetScale()) *\n             GetModelData()->GetAnimationData()->GetLocatorTransform(\"R_ankle\"sv, nullptr))\n                .origin;\n    break;\n  }\n  case EUserEventType::DamageOff: {\n    x7c8_ = false;\n    x7cc_ = zeus::skZero3f;\n    break;\n  }\n  case EUserEventType::Landing: {\n    x93c_ = false;\n    break;\n  }\n  case EUserEventType::TakeOff: {\n    s32 rnd = mgr.GetActiveRandom()->Next();\n    std::vector<u32> inRangeWaypoints;\n    inRangeWaypoints.reserve(x8f4_waypoints.size());\n\n    zeus::CVector3f plPos = mgr.GetPlayer().GetTranslation();\n\n    for (size_t i = 0; i < x8f4_waypoints.size(); ++i) {\n      if (TCastToPtr<CScriptWaypoint> wp = mgr.ObjectById(x8f4_waypoints[i])) {\n        if ((wp->GetTranslation() - plPos).magSquared() > 10.f) {\n          inRangeWaypoints.push_back(i);\n        }\n      }\n    }\n\n    for (size_t i = 0; i < (rnd & 1) + 2; ++i) {\n      if (TCastToPtr<CScriptWaypoint> wp =\n              mgr.ObjectById(x8f4_waypoints[mgr.GetActiveRandom()->Next() % inRangeWaypoints.size()])) {\n        wp->SetActive(true);\n        SendScriptMsgs(EScriptObjectState::Zero, mgr, EScriptObjectMessage::None);\n        wp->SetActive(false);\n      }\n    }\n    break;\n  }\n  case EUserEventType::FadeIn: {\n    break;\n  }\n  case EUserEventType::FadeOut: {\n    if (x644_ == 1) {\n      x93c_ = true;\n      x688_ = true;\n    }\n    break;\n  }\n  case EUserEventType::ScreenShake:\n    ApplyCameraShake(1.25f, 125.f, 1.f, mgr, GetTranslation());\n    [[fallthrough]];\n  default:\n    break;\n  }\n}\n\nvoid CThardus::Patrol(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x658_ = -1;\n    x950_ = mgr.GetPlayer().GetTranslation();\n  } else if (msg == EStateMsg::Update) {\n    if (!ShouldTurn(mgr, 0.f)) {\n      return;\n    }\n\n    zeus::CVector3f plPos = mgr.GetPlayer().GetTranslation();\n    zeus::CQuaternion q = zeus::CQuaternion::lookAt(x950_, plPos.normalized(), zeus::degToRad(360.f));\n    x450_bodyController->GetCommandMgr().DeliverCmd(\n        CBCLocomotionCmd(zeus::skZero3f, q.toTransform() * (plPos - GetTranslation()).normalized(), 1.f));\n    x950_ = plPos;\n  } else if (msg == EStateMsg::Deactivate) {\n    sub80deadc(mgr);\n    x94d_ = false;\n  }\n}\n\nvoid CThardus::Dead(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    SetTransform(x764_startTransform);\n  }\n  CPatterned::Dead(mgr, msg, arg);\n}\n\nvoid CThardus::PathFind(CStateManager& mgr, EStateMsg msg, float arg) {\n  CPatterned::PathFind(mgr, msg, arg);\n  if (msg == EStateMsg::Activate) {\n    x2e0_destPos = sub801de550(mgr);\n    x7e4_ = x7d8_ = x2e0_destPos;\n    CPatterned::PathFind(mgr, EStateMsg::Activate, arg);\n  } else if (msg == EStateMsg::Update) {\n    if ((GetTranslation().toVec2f() - x7e4_.toVec2f()).magnitude() < 10.f) {\n      x2e0_destPos = sub801de434(mgr);\n      x7d8_ = x2e0_destPos;\n      x7e4_ = x7d8_;\n      CPatterned::PathFind(mgr, EStateMsg::Activate, arg);\n      ++x660_;\n    }\n    x650_ = GetTargetVector(arg, mgr);\n  } else if (msg == EStateMsg::Deactivate) {\n    x8d4_ = false;\n  }\n}\n\nvoid CThardus::TargetPatrol(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x5ec_stateProg = 0;\n    if (x95e_) {\n      return;\n    }\n\n    mgr.SetBossParams(GetUniqueId(), GetHealthInfo(mgr)->GetHP(), 88);\n    x95e_ = true;\n  } else if (msg == EStateMsg::Update) {\n    if (x5ec_stateProg == 0) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::Taunt) {\n        x5ec_stateProg = 2;\n      } else {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCTauntCmd(pas::ETauntType::One));\n      }\n    } else if (x5ec_stateProg == 2 &&\n               x450_bodyController->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::Taunt) {\n      x5ec_stateProg = 3;\n    }\n  }\n}\n\nvoid CThardus::Generate(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x5ec_stateProg = 0;\n  } else if (msg == EStateMsg::Update) {\n    if (x5ec_stateProg == 0) {\n      if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::Getup) {\n        x5ec_stateProg = 2;\n      } else {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCGetupCmd(pas::EGetupType::Zero));\n      }\n    } else if (x5ec_stateProg == 2 && x450_bodyController->GetCurrentStateId() != pas::EAnimationState::Getup) {\n      x5ec_stateProg = 3;\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x93d_ = false;\n  }\n}\n\nvoid CThardus::Attack(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x5ec_stateProg = 0;\n    ++x570_;\n    x5ec_stateProg = 0;\n  } else if (msg == EStateMsg::Update) {\n    if (x5ec_stateProg == 0) {\n      if (GetBodyController()->GetCurrentStateId() != pas::EAnimationState::MeleeAttack) {\n        if (mgr.GetActiveRandom()->Float() <= 0.5f) {\n          GetBodyController()->GetCommandMgr().DeliverCmd(CBCMeleeAttackCmd(pas::ESeverity::One));\n        } else {\n          GetBodyController()->GetCommandMgr().DeliverCmd(CBCMeleeAttackCmd(pas::ESeverity::Zero));\n        }\n        ++x570_;\n      } else {\n        x5ec_stateProg = 2;\n      }\n    } else if (x5ec_stateProg == 2 && GetBodyController()->GetCurrentStateId() != pas::EAnimationState::MeleeAttack) {\n      x5ec_stateProg = 3;\n    }\n  }\n}\n\nvoid CThardus::LoopedAttack(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x658_ = 0;\n    x660_ = 0;\n    x570_ = 0;\n    x574_ = 0;\n    sub801dec80();\n    x93b_ = false;\n    x5c4_ = -1;\n  } else if (msg == EStateMsg::Update) {\n    const zeus::CVector3f thisPos = GetTranslation();\n    if (x658_ == 1) {\n      const zeus::CVector3f offset = thisPos + zeus::CVector3f{0.f, 0.f, 10.f};\n      CRayCastResult result = mgr.RayStaticIntersection(\n          offset, zeus::CQuaternion(GetTransform().buildMatrix3f()).toTransform() * zeus::CVector3f{0.f, 1.f, 0.f},\n          100.f, CMaterialFilter::MakeInclude({EMaterialTypes::Wall, EMaterialTypes::Floor, EMaterialTypes::Ceiling}));\n      if (result.IsInvalid()) {\n        zeus::CVector2f vec = GetSteeringVector(mgr);\n        if (vec != zeus::skZero2f) {\n          x650_ = vec;\n        }\n      } else {\n        x8d8_ = result.GetPoint();\n        x8e4_ = offset;\n        if ((result.GetPoint() - offset).magnitude() < 20.f) {\n          x658_ = 2;\n          x8d4_ = true;\n        }\n      }\n    } else if (x658_ == 0) {\n      zeus::CVector3f dir = (mgr.GetPlayer().GetTranslation() - GetTranslation()).normalized();\n      zeus::CVector2f vec = GetSteeringVector(mgr);\n      if (vec != zeus::skZero2f) {\n        x650_ = vec;\n      } else {\n        x650_ = dir.toVec2f().normalized();\n      }\n\n      if (dir.magnitude() < x698_) {\n        x658_ = 1;\n      }\n    }\n    GetBodyController()->GetCommandMgr().DeliverCmd(CBodyStateCmd{EBodyStateCmd::MaintainVelocity});\n    GetBodyController()->GetCommandMgr().DeliverCmd(CBCLocomotionCmd{zeus::CVector3f(x650_), zeus::skZero3f, 1.f});\n  }\n}\n\nvoid CThardus::DoubleSnap(CStateManager& mgr, EStateMsg msg, float arg) {\n  // Intentionally empty\n}\n\nvoid CThardus::Shuffle(CStateManager& mgr, EStateMsg msg, float arg) {\n  // Intentionally empty\n}\n\nvoid CThardus::GetUp(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg != EStateMsg::Activate) {\n    return;\n  }\n  RemoveMaterial(EMaterialTypes::RadarObject, mgr);\n}\n\nvoid CThardus::Taunt(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x5ec_stateProg = 0;\n  } else if (msg == EStateMsg::Update) {\n    if (x5ec_stateProg == 0) {\n      if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::Taunt) {\n        x5ec_stateProg = 2;\n      } else {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCTauntCmd(pas::ETauntType::One));\n      }\n    } else if (x5ec_stateProg == 2 && x450_bodyController->GetCurrentStateId() != pas::EAnimationState::Taunt) {\n      x5ec_stateProg = 3;\n    }\n  }\n}\n\nvoid CThardus::Suck(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x909_ = true;\n    x93d_ = true;\n    SendScriptMsgs(EScriptObjectState::MaxReached, mgr, EScriptObjectMessage::None);\n  } else if (msg == EStateMsg::Deactivate) {\n    x689_ = true;\n  }\n}\n\nvoid CThardus::ProjectileAttack(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x5ec_stateProg = 0;\n  } else if (msg == EStateMsg::Update) {\n    if (x5ec_stateProg == 1) {\n      return;\n    }\n\n    if (x5ec_stateProg == 0) {\n      if (GetBodyController()->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::ProjectileAttack) {\n        GetBodyController()->GetCommandMgr().DeliverCmd(CBCProjectileAttackCmd(pas::ESeverity::Zero, {}, false));\n        x5ec_stateProg = 0;\n      } else {\n        x5ec_stateProg = 2;\n      }\n    } else if (x5ec_stateProg == 2 &&\n               GetBodyController()->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::ProjectileAttack) {\n      x5ec_stateProg = 3;\n    }\n  }\n}\n\nvoid CThardus::Flinch(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x5ec_stateProg = 0;\n    for (TUniqueId uid : x798_) {\n      if (auto* rock = CPatterned::CastTo<CThardusRockProjectile>(mgr.ObjectById(uid))) {\n        rock->sub80203d58();\n      }\n    }\n    x93b_ = true;\n    x93d_ = true;\n    x909_ = true;\n    x93c_ = false;\n    SetState(-1, mgr);\n    x94d_ = true;\n  } else if (msg == EStateMsg::Update) {\n    pas::ESeverity severity = pas::ESeverity::Invalid;\n    switch (x648_currentRock) {\n    case 1:\n      severity = pas::ESeverity::Zero;\n      break;\n    case 2:\n      severity = pas::ESeverity::One;\n      break;\n    case 3:\n      severity = pas::ESeverity::Two;\n      break;\n    case 4:\n      severity = pas::ESeverity::Three;\n      break;\n    case 5:\n      severity = pas::ESeverity::Four;\n      break;\n    case 6:\n      severity = pas::ESeverity::Five;\n      break;\n    default:\n      break;\n    }\n\n    if (x5ec_stateProg == 0) {\n      if (GetBodyController()->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::KnockBack) {\n        GetBodyController()->GetCommandMgr().DeliverCmd(CBCKnockBackCmd({}, severity));\n      } else {\n        x5ec_stateProg = 2;\n      }\n    } else if (x5ec_stateProg == 2 &&\n               GetBodyController()->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::KnockBack) {\n      x5ec_stateProg = 3;\n    }\n  }\n}\n\nvoid CThardus::TelegraphAttack(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x5ec_stateProg = 0;\n  } else if (msg == EStateMsg::Update) {\n    if (x5ec_stateProg == 0) {\n      if (GetBodyController()->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::ProjectileAttack) {\n        GetBodyController()->GetCommandMgr().DeliverCmd(CBCProjectileAttackCmd(pas::ESeverity::One, {}, false));\n        x5ec_stateProg = 0;\n      } else {\n        x5ec_stateProg = 2;\n      }\n    } else if (x5ec_stateProg == 2 &&\n               GetBodyController()->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::ProjectileAttack) {\n      x5ec_stateProg = 3;\n    }\n  }\n}\n\nvoid CThardus::Explode(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x5ec_stateProg = 0;\n    CSfxManager::SfxStop(x904_);\n    x909_ = true;\n    x93d_ = true;\n    x909_ = true;\n    SendScriptMsgs(EScriptObjectState::Arrived, mgr, EScriptObjectMessage::None);\n  } else if (msg == EStateMsg::Update) {\n    if (x5ec_stateProg == 0) {\n      if (GetBodyController()->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::Step) {\n        GetBodyController()->GetCommandMgr().DeliverCmd(\n            CBCStepCmd(pas::EStepDirection::Forward, pas::EStepType::Dodge));\n      } else {\n        x5ec_stateProg = 2;\n      }\n    } else if (x5ec_stateProg == 2 &&\n               GetBodyController()->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::Step) {\n      x5ec_stateProg = 3;\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x8f0_ = true;\n    x909_ = false;\n    x93d_ = false;\n  }\n}\n\nvoid CThardus::Cover(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    SetState(1, mgr);\n    x93d_ = false;\n    x909_ = false;\n    if (x610_destroyableRocks.size() - 2 <= x648_currentRock) {\n      x690_ = 1.1f;\n    }\n    AddMaterial(EMaterialTypes::RadarObject, mgr);\n  } else if (msg == EStateMsg::Deactivate) {\n    x690_ = 1.f;\n  }\n}\n\nvoid CThardus::Enraged(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x5ec_stateProg = 0;\n    x688_ = true;\n    x908_ = true;\n  } else if (msg == EStateMsg::Update) {\n    if (x5ec_stateProg == 0) {\n      if (GetBodyController()->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::Taunt) {\n        GetBodyController()->GetCommandMgr().DeliverCmd(CBCTauntCmd(pas::ETauntType::Zero));\n      } else {\n        x5ec_stateProg = 2;\n      }\n    } else if (x5ec_stateProg == 2 &&\n               GetBodyController()->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::Taunt) {\n      x5ec_stateProg = 3;\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x908_ = false;\n  }\n}\n\nvoid CThardus::Growth(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x5ec_stateProg = 0;\n    x904_ = CSfxManager::SfxStart(SFXsfx07AD, 1.f, 0.f, false, 0x7f, true, GetAreaIdAlways());\n  } else if (msg == EStateMsg::Update) {\n    if (x5ec_stateProg == 0) {\n      if (GetBodyController()->GetCurrentStateId() != pas::EAnimationState::Step) {\n        GetBodyController()->GetCommandMgr().DeliverCmd(\n            CBCStepCmd(pas::EStepDirection::Forward, pas::EStepType::BreakDodge));\n      } else {\n        x5ec_stateProg = 2;\n      }\n    } else if (x5ec_stateProg == 2 && GetBodyController()->GetCurrentStateId() != pas::EAnimationState::Step) {\n      x5ec_stateProg = 3;\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    if (TCastToPtr<CScriptDistanceFog> fog = mgr.ObjectById(x64c_fog)) {\n      mgr.SendScriptMsg(GetUniqueId(), mgr.GetEditorIdForUniqueId(fog->GetUniqueId()), EScriptObjectMessage::Activate,\n                        EScriptObjectState::Any);\n      SendScriptMsgs(EScriptObjectState::Play, mgr, EScriptObjectMessage::None);\n    }\n  }\n}\n\nvoid CThardus::Faint(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x5ec_stateProg = 0;\n    x93c_ = false;\n    SetState(-1, mgr);\n    for (TUniqueId uid : x798_) {\n      if (auto* rock = CPatterned::CastTo<CThardusRockProjectile>(mgr.ObjectById(uid))) {\n        rock->sub80203d58();\n      }\n    }\n    x94d_ = true;\n  } else if (msg == EStateMsg::Update) {\n    if (x5ec_stateProg == 0) {\n      if (GetBodyController()->GetCurrentStateId() != pas::EAnimationState::KnockBack) {\n        GetBodyController()->GetCommandMgr().DeliverCmd(CBCKnockBackCmd({}, pas::ESeverity::Six));\n      } else {\n        x5ec_stateProg = 2;\n      }\n    } else if (x5ec_stateProg == 2 && GetBodyController()->GetCurrentStateId() != pas::EAnimationState::KnockBack) {\n      x5ec_stateProg = 3;\n    }\n  }\n}\n\nbool CThardus::PathFound(CStateManager& mgr, float arg) { return x8d4_; }\nbool CThardus::InRange(CStateManager& mgr, float arg) {\n  return (mgr.GetPlayer().GetTranslation().toVec2f() - GetTranslation().toVec2f()).magnitude() <\n         10.f * GetModelData()->GetScale().x();\n}\n\nbool CThardus::PatternOver(CStateManager& mgr, float arg) { return x570_ != 0 || x93b_; }\nbool CThardus::AnimOver(CStateManager& mgr, float arg) { return x5ec_stateProg == 3; }\nbool CThardus::InPosition(CStateManager& mgr, float arg) { return x660_ > 3; }\nbool CThardus::ShouldTurn(CStateManager& mgr, float arg) {\n  return std::fabs(zeus::CVector2f::getAngleDiff(GetTransform().frontVector().toVec2f(),\n                                                 mgr.GetPlayer().GetTranslation().toVec2f() -\n                                                     GetTranslation().toVec2f())) > zeus::degToRad(30.f);\n}\nbool CThardus::HitSomething(CStateManager& mgr, float arg) { return mgr.GetPlayer().GetFrozenState(); }\n\nvoid CThardus::GatherWaypoints(metaforce::CScriptWaypoint* wp, metaforce::CStateManager& mgr,\n                               rstl::reserved_vector<TUniqueId, 16>& uids) {\n  if (uids.size() < uids.capacity() && wp->GetActive()) {\n    uids.push_back(wp->GetUniqueId());\n    wp->SetActive(false);\n    for (const SConnection& conn : wp->GetConnectionList()) {\n      TUniqueId uid = mgr.GetIdForScript(conn.x8_objId);\n      if (TCastToPtr<CScriptWaypoint> wp2 = mgr.ObjectById(uid)) {\n        GatherWaypoints(wp2, mgr, uids);\n      }\n    }\n    wp->SetActive(true);\n  }\n}\n\nvoid CThardus::_BuildSphereJointList(const SSphereJointInfo* arr, size_t count,\n                                     std::vector<CJointCollisionDescription>& list) {\n  for (size_t i = 0; i < count; ++i) {\n    const auto& jInfo = arr[i];\n    list.push_back(CJointCollisionDescription::SphereCollision(\n        GetModelData()->GetAnimationData()->GetLocatorSegId(jInfo.name), jInfo.radius, jInfo.name, 0.001f));\n  }\n}\n\nvoid CThardus::_BuildAABoxJointList(const SAABoxJointInfo* arr, size_t count,\n                                    std::vector<CJointCollisionDescription>& list) {\n  for (size_t i = 0; i < count; ++i) {\n    const auto& jInfo = arr[i];\n    list.push_back(CJointCollisionDescription::AABoxCollision(\n        GetModelData()->GetAnimationData()->GetLocatorSegId(jInfo.name), jInfo.extents, jInfo.name, 0.001f));\n  }\n}\n\nvoid CThardus::_SetupCollisionActorMaterials(const std::unique_ptr<CCollisionActorManager>& colMgr,\n                                             CStateManager& mgr) {\n  for (size_t i = 0; i < colMgr->GetNumCollisionActors(); ++i) {\n    const auto& colDesc = colMgr->GetCollisionDescFromIndex(i);\n    if (auto* act = static_cast<CActor*>(mgr.ObjectById(colDesc.GetCollisionActorId()))) {\n      act->AddMaterial(EMaterialTypes::ScanPassthrough, mgr);\n      act->AddMaterial(EMaterialTypes::CameraPassthrough, mgr);\n      act->AddMaterial(EMaterialTypes::Immovable, mgr);\n      act->AddMaterial(EMaterialTypes::NoPlayerCollision, mgr);\n      CMaterialList include = GetMaterialFilter().GetIncludeList();\n      include.Add(act->GetMaterialFilter().GetIncludeList());\n      CMaterialList exclude = GetMaterialFilter().GetExcludeList();\n      exclude.Add(act->GetMaterialFilter().GetExcludeList());\n      act->SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(include, exclude));\n    }\n  }\n}\n\nvoid CThardus::_SetupCollisionManagers(CStateManager& mgr) {\n  std::vector<CJointCollisionDescription> list;\n  _BuildSphereJointList(skDamageableSphereJointInfoList1.data(), skDamageableSphereJointInfoList1.size(), list);\n  x5f0_rockColliders = std::make_unique<CCollisionActorManager>(mgr, GetUniqueId(), GetAreaIdAlways(), list, true);\n  _SetupCollisionActorMaterials(x5f0_rockColliders, mgr);\n  list.clear();\n  _BuildSphereJointList(skDamageableSphereJointInfoList2.data(), skDamageableSphereJointInfoList2.size(), list);\n  x5f4_ = std::make_unique<CCollisionActorManager>(mgr, GetUniqueId(), GetAreaIdAlways(), list, true);\n  _SetupCollisionActorMaterials(x5f4_, mgr);\n  list.clear();\n  _BuildAABoxJointList(skFootCollision.data(), skFootCollision.size(), list);\n  x5f8_ = std::make_unique<CCollisionActorManager>(mgr, GetUniqueId(), GetAreaIdAlways(), list, true);\n  _SetupCollisionActorMaterials(x5f8_, mgr);\n  list.clear();\n  x634_nonDestroyableActors.reserve(x5f4_->GetNumCollisionActors() + x5f0_rockColliders->GetNumCollisionActors() +\n                                    x5f8_->GetNumCollisionActors());\n  FindNonDestroyableActors(x5f4_);\n  FindNonDestroyableActors(x5f8_);\n  for (size_t i = 0; i < x5f0_rockColliders->GetNumCollisionActors(); ++i) {\n    const auto& colDesc = x5f0_rockColliders->GetCollisionDescFromIndex(i);\n    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(colDesc.GetCollisionActorId())) {\n      if (auto* rock = static_cast<CDestroyableRock*>(mgr.ObjectById(x610_destroyableRocks[i]))) {\n        if (i == 0) {\n          colAct->SetDamageVulnerability(*rock->GetDamageVulnerability());\n          rock->SetThermalMag(0.8f);\n        } else {\n          colAct->SetDamageVulnerability(CDamageVulnerability::ImmuneVulnerabilty());\n          rock->SetThermalMag(0.f);\n        }\n\n        *colAct->HealthInfo(mgr) = *rock->HealthInfo(mgr);\n      }\n    }\n  }\n}\n\nvoid CThardus::FindNonDestroyableActors(const std::unique_ptr<CCollisionActorManager>& colMgr) {\n  for (size_t i = 0; i < colMgr->GetNumCollisionActors(); ++i) {\n    const auto& colDesc = colMgr->GetCollisionDescFromIndex(i);\n    TUniqueId uid = colDesc.GetCollisionActorId();\n    bool foundBone = false;\n    for (const auto& name : skSearchJointNames) {\n      if (colDesc.GetName().find(name) != std::string::npos) {\n        foundBone = true;\n        break;\n      }\n    }\n\n    if (!foundBone) {\n      x634_nonDestroyableActors.push_back(uid);\n    }\n  }\n}\n\nvoid CThardus::BreakRock(CStateManager& mgr, u32 rockIndex) {\n  TCastToPtr<CCollisionActor> rockCol =\n      mgr.ObjectById(x5f0_rockColliders->GetCollisionDescFromIndex(rockIndex).GetCollisionActorId());\n  if (TCastToPtr<CDestroyableRock> rock = mgr.ObjectById(x610_destroyableRocks[rockIndex])) {\n    if (!rock->IsUsingPhazonModel()) {\n      rock->UsePhazonModel();\n      float hp = rockIndex == x5f0_rockColliders->GetNumCollisionActors() - 1 ? 2.f * x6a4_ : x6a4_;\n      CHealthInfo* hInfo = rock->HealthInfo(mgr);\n      hInfo->SetHP(hp);\n      hInfo->SetKnockbackResistance(2.f);\n      hInfo = rockCol->HealthInfo(mgr);\n      hInfo->SetHP(hp);\n      hInfo->SetKnockbackResistance(2.f);\n      x6b0_destroyedRocks[rockIndex] = true;\n      rock->SetThermalMag(1.5f);\n      auto* light = static_cast<CGameLight*>(mgr.ObjectById(x6c0_rockLights[rockIndex]));\n      light->SetActive(true);\n      if (mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::Thermal ||\n          (mgr.GetPlayerState()->GetCurrentVisor() != CPlayerState::EPlayerVisor::Thermal && x7c4_ != 3)) {\n        SetRockParticle(mgr, GetTranslation(), x6d4_);\n      }\n      x90c_rockHealths[rockIndex] = hp;\n      sub801dbc5c(mgr, rock);\n      ProcessSoundEvent(x760_, 1.f, 0, 0.1f, 1000.f, 0.16f, 1.f, zeus::skZero3f, GetTranslation(), mgr.GetNextAreaId(),\n                        mgr, true);\n    }\n  }\n}\n\nvoid CThardus::SetRockParticle(CStateManager& mgr, const zeus::CVector3f& pos, CAssetId particle) {\n  u32 w = x6f4_;\n  ++x6f4_;\n  std::string particleName = fmt::format(\"ROCK_EFFECT{}-{}\", particle.Value(), w);\n  GetModelData()->GetAnimationData()->GetParticleDB().AddAuxiliaryParticleEffect(\n      particleName, 0x40, CAuxiliaryParticleData(0, {FOURCC('PART'), particle}, pos, 1.f),\n      2.f * GetModelData()->GetScale(), mgr, GetAreaIdAlways(), 0);\n}\n\nvoid CThardus::sub801dbc5c(CStateManager& mgr, CDestroyableRock* rock) {\n  if (x938_) {\n    return;\n  }\n\n  x938_ = true;\n  x939_ = false;\n  sub801dbbdc(mgr, rock);\n}\n\nvoid CThardus::sub801dbbdc(CStateManager& mgr, CDestroyableRock* rock) {\n  if (mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::Thermal) {\n    x688_ = true;\n  }\n\n  if (x7c4_ == 0 || x7c4_ == 2) {\n    x7c4_ = 1;\n    x7b8_ = 0.f;\n  }\n\n  x928_currentRockId = rock->GetUniqueId();\n  x92c_currentRockPos = rock->GetTranslation();\n}\n\nvoid CThardus::UpdateNonDestroyableCollisionActorMaterials(EUpdateMaterialMode mode, EMaterialTypes mat,\n                                                           metaforce::CStateManager& mgr) {\n  for (const auto& uid : x634_nonDestroyableActors) {\n    if (TCastToPtr<CCollisionActor> col = mgr.ObjectById(uid)) {\n      if (mode == EUpdateMaterialMode::Remove) {\n        col->RemoveMaterial(mat, mgr);\n      } else if (mode == EUpdateMaterialMode::Add) {\n        col->AddMaterial(mat, mgr);\n      }\n\n      *col->HealthInfo(mgr) = CHealthInfo(1000000.0f, 10.f);\n    }\n  }\n}\n\nvoid CThardus::UpdateExcludeList(const std::unique_ptr<CCollisionActorManager>& colMgr, EUpdateMaterialMode mode,\n                                 EMaterialTypes w2, CStateManager& mgr) {\n  for (size_t i = 0; i < colMgr->GetNumCollisionActors(); ++i) {\n    if (TCastToPtr<CActor> colAct = mgr.ObjectById(colMgr->GetCollisionDescFromIndex(i).GetCollisionActorId())) {\n      CMaterialList exclude = colAct->GetMaterialFilter().GetExcludeList();\n      if (mode == EUpdateMaterialMode::Remove) {\n        exclude.Remove(w2);\n      } else if (mode == EUpdateMaterialMode::Add) {\n        exclude.Add(w2);\n      }\n\n      colAct->SetMaterialFilter(\n          CMaterialFilter::MakeIncludeExclude(colAct->GetMaterialFilter().GetIncludeList(), exclude));\n    }\n  }\n}\n\nvoid CThardus::RenderFlare(const CStateManager& mgr, float t) {\n  if (!x91c_flareTexture) {\n    return;\n  }\n  x91c_flareTexture->Load(GX_TEXMAP0, EClampMode::Repeat);\n\n  const float scale = 30.f * t;\n  zeus::CVector3f offset = scale * CGraphics::mViewMatrix.basis[2];\n  zeus::CVector3f max = x92c_currentRockPos + (scale * CGraphics::mViewMatrix.basis[0]);\n  zeus::CVector3f min = x92c_currentRockPos - (scale * CGraphics::mViewMatrix.basis[0]);\n  CGraphics::SetModelMatrix({});\n  CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::One, ERglBlendFactor::One, ERglLogicOp::Clear);\n  CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvModulate);\n  CGraphics::SetTevOp(ERglTevStage::Stage1, CTevCombiners::kEnvPassthru);\n  CGraphics::SetDepthWriteMode(false, ERglEnum::Always, false);\n  CGraphics::StreamColor({t, t});\n  CGraphics::StreamBegin(ERglPrimitive::TriangleFan);\n  CGraphics::StreamTexcoord(0.f, 0.f);\n  CGraphics::StreamVertex(min + offset);\n  CGraphics::StreamTexcoord(1.f, 0.f);\n  CGraphics::StreamVertex(min - offset);\n  CGraphics::StreamTexcoord(1.f, 1.f);\n  CGraphics::StreamVertex(max - offset);\n  CGraphics::StreamTexcoord(0.f, 1.f);\n  CGraphics::StreamVertex(max + offset);\n  CGraphics::StreamEnd();\n}\n\nzeus::CVector3f CThardus::sub801de550(CStateManager& mgr) {\n\n  s8 wpIdx = -1;\n  s8 pathIdx = -1;\n  if (!x578_waypoints.empty()) {\n    zeus::CVector2f thisPos = GetTranslation().toVec2f();\n    std::vector<u32> unkVec;\n    unkVec.reserve(x578_waypoints.size());\n    for (const auto& path : x578_waypoints) {\n      float maxDist = 1000000.f;\n      u32 lastIdx = 0;\n      for (size_t i = 0; i < path.size(); ++i) {\n        TCastToConstPtr<CScriptWaypoint> wp = mgr.GetObjectById(path[i]);\n        const float curDist = (wp->GetTranslation().toVec2f() - thisPos).magnitude();\n        if (curDist < maxDist) {\n          lastIdx = i;\n          maxDist = curDist;\n        }\n      }\n\n      unkVec.push_back(lastIdx);\n    }\n    // zeus::CVector2f plVec = mgr.GetPlayer().GetTranslation().toVec2f();\n\n    float maxDist = 0.f;\n    float curDist = 0.f;\n\n    for (size_t i = 0; i < x578_waypoints.size(); ++i) {\n      TCastToConstPtr<CScriptWaypoint> wp1 = mgr.GetObjectById(x578_waypoints[i][unkVec[i]]);\n      TCastToConstPtr<CScriptWaypoint> wp2 = mgr.GetObjectById(wp1->NextWaypoint(mgr));\n      const float wp1Dist = (wp1->GetTranslation().toVec2f() - mgr.GetPlayer().GetTranslation().toVec2f()).magnitude();\n      const float wp2Dist = (wp2->GetTranslation().toVec2f() - mgr.GetPlayer().GetTranslation().toVec2f()).magnitude();\n      if (curDist < wp1Dist && wp1Dist <= wp2Dist) {\n        curDist = wp1Dist;\n        pathIdx = i;\n      }\n\n      if (maxDist < wp1Dist) {\n        maxDist = wp1Dist;\n        wpIdx = i;\n      }\n    }\n\n    if (pathIdx == -1) {\n      pathIdx = wpIdx;\n    }\n  }\n\n  if (pathIdx == -1 || wpIdx == -1) {\n    return {};\n  }\n\n  x8f1_curPatrolPath = pathIdx;\n  x8f2_curPatrolPathWaypoint = wpIdx;\n  return TCastToConstPtr<CScriptWaypoint>(mgr.GetObjectById(x578_waypoints[pathIdx][wpIdx]))->GetTranslation();\n}\n\nzeus::CVector3f CThardus::sub801de434(CStateManager& mgr) {\n  if (x8f1_curPatrolPath == -1 || x8f2_curPatrolPathWaypoint == -1) {\n    return {};\n  }\n  s8 tmpWpIdx = x8f2_curPatrolPathWaypoint;\n  x8f2_curPatrolPathWaypoint = (x8f2_curPatrolPathWaypoint + 1) % x578_waypoints[x8f1_curPatrolPath].size();\n  return TCastToConstPtr<CScriptWaypoint>(\n             mgr.GetObjectById(x578_waypoints[x8f1_curPatrolPath][x8f2_curPatrolPathWaypoint]))\n      ->GetTranslation();\n}\n\nzeus::CVector2f CThardus::GetSteeringVector(CStateManager& mgr) const {\n  zeus::CVector2f ret;\n  zeus::CVector3f pos = GetTranslation();\n  for (const auto& repulsor : x664_repulsors) {\n    TCastToConstPtr<CRepulsor> rep = mgr.GetObjectById(repulsor);\n    const zeus::CVector2f repPos = rep->GetTranslation().toVec2f();\n    const float dist = (pos - repPos).magSquared();\n    if (dist < (rep->GetAffectRadius() * rep->GetAffectRadius())) {\n      // std::sqrt(dist);\n      x45c_steeringBehaviors.Flee2D(*this, repPos);\n      break;\n    }\n  }\n\n  if (ret != zeus::skZero2f) {\n    ret = x45c_steeringBehaviors.Arrival2D(*this, x764_startTransform.origin.toVec2f()) * zeus::CVector2f(1.f) +\n          ret * zeus::CVector2f(0.f);\n  }\n  return ret;\n}\n\nbool CThardus::sub801db5b4(CStateManager& mgr) const {\n  return mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::Thermal ? !x93a_ || x7c4_ == 0 : true;\n}\n\nvoid CThardus::ApplyCameraShake(float magnitude, float sfxDistance, float duration, CStateManager& mgr,\n                                const zeus::CVector3f& v1) {\n  float bounceIntensity =\n      std::max(0.f, -((v1 - mgr.GetPlayer().GetTranslation()).magnitude() * (magnitude / sfxDistance) - magnitude));\n  if (mgr.GetCameraManager()->GetFirstPersonCamera()->GetUniqueId() == mgr.GetCameraManager()->GetCurrentCameraId()) {\n    mgr.GetCameraManager()->AddCameraShaker(\n        CCameraShakeData::BuildMissileCameraShake(duration, magnitude, sfxDistance, GetTranslation()), true);\n  }\n\n  if (x908_) {\n    BouncePlayer(bounceIntensity, mgr);\n  }\n}\n\nvoid CThardus::UpdateHealthInfo(CStateManager& mgr) {\n  // TODO(phil): This isn't quite right, need to figure out why\n  float hp = 0.f;\n  for (size_t i = x648_currentRock; i < x610_destroyableRocks.size(); ++i) {\n    float fVar1 = x648_currentRock == (x610_destroyableRocks.size() - 1) ? 2.f * x6a4_ : x6a4_;\n    if (auto* rock = static_cast<CDestroyableRock*>(mgr.ObjectById(x610_destroyableRocks[i]))) {\n      if (!rock->IsUsingPhazonModel()) {\n        hp += fVar1;\n      }\n      hp += rock->GetHealthInfo(mgr)->GetHP();\n    }\n  }\n  HealthInfo(mgr)->SetHP(hp);\n}\n\nvoid CThardus::BouncePlayer(float intensity, CStateManager& mgr) {\n  if (intensity <= 0.f) {\n    return;\n  }\n  zeus::CVector3f posDiff = GetTranslation() - mgr.GetPlayer().GetTranslation();\n  CPlayer::ESurfaceRestraints restraints = mgr.GetPlayer().GetSurfaceRestraint();\n  if (restraints != CPlayer::ESurfaceRestraints::Air && !mgr.GetPlayer().IsInWaterMovement()) {\n    zeus::CVector3f baseImpulse = intensity * (40.f * zeus::skUp);\n    zeus::CVector3f additionalImpulse;\n    if (posDiff.magnitude() > 10.f) {\n      zeus::CVector3f tmpVec = posDiff.toVec2f();\n      if (tmpVec.canBeNormalized()) {\n        additionalImpulse = intensity * (12.5f * tmpVec.normalized());\n      }\n    }\n    mgr.GetPlayer().ApplyImpulseWR(mgr.GetPlayer().GetMass() * (baseImpulse + additionalImpulse), zeus::CAxisAngle());\n    mgr.GetPlayer().SetMoveState(CPlayer::EPlayerMovementState::ApplyJump, mgr);\n  }\n}\n\nvoid CThardus::sub801dbc40() {\n  x7b8_ = FLT_EPSILON + x7bc_;\n  x938_ = false;\n}\n\nzeus::CVector2f CThardus::GetTargetVector(float arg, CStateManager& mgr) {\n  zeus::CVector2f ret;\n  if (GetSearchPath() != nullptr) {\n    if (GetSearchPath()->GetResult() == CPathFindSearch::EResult::Success) {\n      CPatterned::PathFind(mgr, EStateMsg::Update, arg);\n      ret = GetBodyController()->GetCommandMgr().GetMoveVector().toVec2f();\n    } else {\n      ret = x45c_steeringBehaviors.Arrival(*this, x7d8_, 0.f).toVec2f();\n    }\n  }\n\n  if (x8d4_ || (!x8d4_ && x7f0_pathFindSearch.OnPath(GetTranslation()) != CPathFindSearch::EResult::Success)) {\n    zeus::CVector2f vec = GetSteeringVector(mgr);\n    if (vec != zeus::skZero2f) {\n      return vec;\n    }\n  }\n\n  if (ret == zeus::skZero2f) {\n    return x45c_steeringBehaviors.Arrival(*this, x7d8_, 0.f).toVec2f();\n  }\n\n  return ret;\n}\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CThardus.hpp",
    "content": "#pragma once\n\n#include <Collision/CJointCollisionDescription.hpp>\n#include \"Runtime/World/CPatterned.hpp\"\n#include \"Runtime/World/CPathFindSearch.hpp\"\n\nnamespace metaforce {\nclass CCollisionActorManager;\nnamespace MP1 {\nclass CThardus : public CPatterned {\n\n  enum class EUpdateMaterialMode { Add, Remove };\n\n  class CThardusSomething {\n    TUniqueId x0_ = kInvalidUniqueId;\n    zeus::CVector3f x4_;\n    bool x10_24_ : 1 = false;\n\n  public:\n    CThardusSomething() = default;\n  };\n  u32 x568_;\n  TUniqueId x56c_ = kInvalidUniqueId;\n  u32 x570_ = 0;\n  u32 x574_ = 0;\n  rstl::reserved_vector<rstl::reserved_vector<TUniqueId, 16>, 2> x578_waypoints;\n  s32 x5c4_ = 1;\n  bool x5c8_heardPlayer = false;\n  /* NOTE(phil) These two vectors used to vectors of CModelData, They have been converted to vectors of CStaticRes due\n   * to the use of move semantics to prevent deep copies */\n  std::vector<CStaticRes> x5cc_;\n  std::vector<CStaticRes> x5dc_;\n  s32 x5ec_stateProg = -1;\n  std::unique_ptr<CCollisionActorManager> x5f0_rockColliders;\n  std::unique_ptr<CCollisionActorManager> x5f4_;\n  std::unique_ptr<CCollisionActorManager> x5f8_;\n  TUniqueId x5fc_projectileId = kInvalidUniqueId;\n  CAssetId x600_;\n  CAssetId x604_;\n  CAssetId x608_;\n  TEditorId x60c_projectileEditorId = kInvalidEditorId;\n  std::vector<TUniqueId> x610_destroyableRocks;\n  u32 x624_;\n  u32 x628_;\n  u32 x62c_;\n  CAssetId x630_;\n  std::vector<TUniqueId> x634_nonDestroyableActors;\n  s32 x644_ = -1;\n  u32 x648_currentRock = 0;\n  TUniqueId x64c_fog = kInvalidUniqueId;\n  zeus::CVector2f x650_ = zeus::CVector2f(0.f, 1.f);\n  s32 x658_ = -1;\n  s32 x65c_ = -1;\n  u32 x660_ = 0;\n  rstl::reserved_vector<TUniqueId, 16> x664_repulsors;\n  bool x688_ = false;\n  bool x689_ = false;\n  u32 x68c_ = 0;\n  float x690_ = 0.f;\n  float x694_;\n  float x698_;\n  float x69c_;\n  float x6a0_;\n  float x6a4_;\n  float x6a8_;\n  float x6ac_;\n  std::vector<bool> x6b0_destroyedRocks;\n  std::vector<TUniqueId> x6c0_rockLights;\n  CAssetId x6d0_;\n  CAssetId x6d4_;\n  CAssetId x6d8_;\n  CAssetId x6dc_;\n  CAssetId x6e0_;\n  CAssetId x6e4_;\n  CAssetId x6e8_;\n  s16 x6ec_;\n  CAssetId x6f0_;\n  u32 x6f4_ = 0;\n  float x6f8_ = 0.3f;\n  std::array<CThardusSomething, 4> x6fc_;\n  zeus::CVector3f x74c_ = zeus::skForward;\n  s32 x758_;\n  s32 x75c_;\n  s32 x760_;\n  zeus::CTransform x764_startTransform;\n  u32 x794_ = 0;\n  std::vector<TUniqueId> x798_;\n  std::vector<TUniqueId> x7a8_timers;\n  float x7b8_ = 0.f;\n  float x7bc_ = 10.f;\n  float x7c0_ = 1.0f;\n  u32 x7c4_ = 0;\n  bool x7c8_ = false;\n  zeus::CVector3f x7cc_;\n  zeus::CVector3f x7d8_;\n  zeus::CVector3f x7e4_;\n  CPathFindSearch x7f0_pathFindSearch;\n  bool x8d4_ = false;\n  zeus::CVector3f x8d8_;\n  zeus::CVector3f x8e4_;\n  bool x8f0_ = false;\n  s8 x8f1_curPatrolPath = -1;\n  s8 x8f2_curPatrolPathWaypoint = -1;\n  std::vector<TUniqueId> x8f4_waypoints;\n  CSfxHandle x904_ = 0;\n  bool x908_ = false;\n  bool x909_ = false;\n  std::vector<float> x90c_rockHealths;\n  TLockedToken<CTexture> x91c_flareTexture;\n  TUniqueId x928_currentRockId;\n  zeus::CVector3f x92c_currentRockPos;\n  bool x938_ = false;\n  bool x939_ = false;\n  bool x93a_ = false;\n  bool x93b_ = false;\n  bool x93c_ = false;\n  bool x93d_ = true;\n  u32 x940_ = 0;\n  float x944_ = 0.3f;\n  u32 x948_;\n  bool x94c_initialized = false;\n  bool x94d_ = false;\n  zeus::CVector3f x950_;\n  bool x95c_doCodeTrigger = false;\n  u8 x95d_ = 0;\n  bool x95e_ = false;\n\n  void SetState(s32 state, CStateManager& mgr) {\n    x644_ = state;\n    if (state == 2) {\n      SendScriptMsgs(EScriptObjectState::Patrol, mgr, EScriptObjectMessage::None);\n    } else if (state == 1) {\n      SendScriptMsgs(EScriptObjectState::Retreat, mgr, EScriptObjectMessage::None);\n    }\n  }\n\n  void GatherWaypoints(CScriptWaypoint* wp, CStateManager& mgr, rstl::reserved_vector<TUniqueId, 16>& uids);\n  void sub801dec80() { x68c_ = 20000; }\n  void FindNonDestroyableActors(const std::unique_ptr<CCollisionActorManager>& colMgr);\n  void UpdateRockThermalState(float dt, CStateManager& mgr);\n  bool sub801dc2c8() const { return (x610_destroyableRocks.size() - 1) == x648_currentRock; }\n  void sub801de9f8(CStateManager& mgr);\n  void sub801dd608(CStateManager& mgr);\n  void sub801dcfa4(CStateManager& mgr);\n  void sub80deadc(CStateManager& mgr) {\n    if (x578_waypoints.empty()) {\n      sub801de9f8(mgr);\n    } else {\n      if (IsLastRock() || x5c4_ != 0 || x944_ <= 0.f)\n        sub801de9f8(mgr);\n      else\n        x944_ = 0.f;\n    }\n  }\n  void BreakRock(CStateManager& mgr, u32 rockIndex);\n\n  void SetRockParticle(CStateManager& mgr, const zeus::CVector3f& pos, CAssetId particle);\n  void sub801dbc5c(CStateManager& mgr, CDestroyableRock* rock);\n  void sub801dbbdc(CStateManager& mgr, CDestroyableRock* rock);\n  bool IsLastRock() { return x648_currentRock == (x610_destroyableRocks.size() - 1); }\n  void UpdateNonDestroyableCollisionActorMaterials(EUpdateMaterialMode mode, EMaterialTypes mat, CStateManager& mgr);\n  void UpdateExcludeList(const std::unique_ptr<CCollisionActorManager>& colMgr, EUpdateMaterialMode mode,\n                         EMaterialTypes mat, CStateManager& mgr);\n  void _SetupCollisionActorMaterials(const std::unique_ptr<CCollisionActorManager>& colMgr, CStateManager& mgr);\n  void _SetupCollisionManagers(CStateManager& mgr);\n  void _BuildSphereJointList(const SSphereJointInfo* arr, size_t count, std::vector<CJointCollisionDescription>& list);\n  void _BuildAABoxJointList(const SAABoxJointInfo* arr, size_t count, std::vector<CJointCollisionDescription>& list);\n  void RenderFlare(const CStateManager& mgr, float t);\n  zeus::CVector3f sub801de550(CStateManager& mgr);\n  zeus::CVector3f sub801de434(CStateManager& mgr);\n  zeus::CVector2f GetTargetVector(float arg, CStateManager& mgr);\n  void sub801dbc40();\n\n  void DoDoubleSnap(CStateManager& mgr) {\n    x330_stateMachineState.SetState(mgr, *this, GetStateMachine(), \"DoubleSnap\"sv);\n  }\n  void DoFaint(CStateManager& mgr) {\n    if (x644_ != 1) {\n      x330_stateMachineState.SetState(mgr, *this, GetStateMachine(), \"Faint\"sv);\n    }\n  }\n  void DoFlinch(CStateManager& mgr) { x330_stateMachineState.SetState(mgr, *this, GetStateMachine(), \"Flinch\"sv); }\n  void _DoSuckState(CStateManager& mgr) { x330_stateMachineState.SetState(mgr, *this, GetStateMachine(), \"Suck\"sv); }\n\n  zeus::CVector2f GetSteeringVector(CStateManager& mgr) const;\n  void UpdateHealthInfo(CStateManager& mgr);\n  void BouncePlayer(float f1, CStateManager& mgr);\n\npublic:\n  DEFINE_PATTERNED(Thardus);\n  CThardus(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n           CModelData&& mData, const CActorParameters& actParms, const CPatternedInfo& pInfo,\n           std::vector<CStaticRes> mData1, std::vector<CStaticRes> mData2, CAssetId particle1, CAssetId particle2,\n           CAssetId particle3, float f1, float f2, float f3, float f4, float f5, float f6, CAssetId stateMachine,\n           CAssetId particle4, CAssetId particle5, CAssetId particle6, CAssetId particle7, CAssetId particle8,\n           CAssetId particle9, CAssetId texture, u32 sfxId1, CAssetId particle10, u32 sfxId2, u32 sfxId3, u32 sfxId4);\n\n  void Think(float dt, CStateManager& mgr) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) override;\n  void PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) override;\n  void Render(CStateManager& mgr) override;\n  bool CanRenderUnsorted(const CStateManager&) const override { return false; }\n  void Touch(CActor& act, CStateManager& mgr) override;\n  zeus::CVector3f GetOrbitPosition(const CStateManager& mgr) const override;\n  zeus::CVector3f GetAimPosition(const CStateManager& mgr, float) const override;\n  zeus::CAABox GetSortingBounds(const CStateManager& mgr) const override;\n  void DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) override;\n\n  void Patrol(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Dead(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void PathFind(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void TargetPatrol(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Generate(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Attack(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void LoopedAttack(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void DoubleSnap(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Shuffle(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void GetUp(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Taunt(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Suck(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void ProjectileAttack(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Flinch(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void TelegraphAttack(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Explode(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Cover(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Enraged(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Growth(CStateManager& mgr, EStateMsg msg, float arg) override;\n  void Faint(CStateManager& mgr, EStateMsg msg, float arg) override;\n  bool PathFound(CStateManager& mgr, float arg) override;\n  bool InRange(CStateManager& mgr, float arg) override;\n  bool PatternOver(CStateManager& mgr, float arg) override;\n  bool HasAttackPattern(CStateManager& mgr, float arg) override { return x5c4_ == 1 && !ShouldMove(mgr, 0.f); }\n  bool AnimOver(CStateManager& mgr, float arg) override;\n  bool InPosition(CStateManager& mgr, float arg) override;\n  bool ShouldTurn(CStateManager& mgr, float arg) override;\n  bool HitSomething(CStateManager& mgr, float arg) override;\n  bool HearPlayer(CStateManager& mgr, float arg) override { return x5c8_heardPlayer; }\n  bool CoverBlown(CStateManager& mgr, float arg) override { return x5c4_ == 2 && !ShouldMove(mgr, 0.f); }\n  bool CoveringFire(CStateManager& mgr, float arg) override { return x5c4_ == 0 && !ShouldMove(mgr, 0.f); }\n  bool AggressionCheck(CStateManager& mgr, float arg) override { return x330_stateMachineState.GetTime() > 0.1f; }\n  bool AttackOver(CStateManager& mgr, float arg) override { return true; }\n  bool ShouldTaunt(CStateManager& mgr, float arg) override { return false; }\n  bool ShouldMove(CStateManager& mgr, float arg) override { return x68c_ < x574_ || x93b_; }\n  bool StartAttack(CStateManager& mgr, float arg) override { return true; }\n  bool CodeTrigger(CStateManager& mgr, float arg) override { return x95c_doCodeTrigger; }\n  bool IsDizzy(CStateManager& mgr, float arg) override { return x330_stateMachineState.GetTime() > 4.f; }\n  bool ShouldCallForBackup(CStateManager& mgr, float arg) override { return x330_stateMachineState.GetTime() > .5f; }\n\n  CPathFindSearch* GetSearchPath() override { return &x7f0_pathFindSearch; }\n\n  u32 Get_x7c4() const { return x7c4_; }\n  bool sub801db5b4(CStateManager& mgr) const;\n  void ApplyCameraShake(float magnitude, float sfxDistance, float duration, CStateManager& mgr,\n                        const zeus::CVector3f& v1);\n};\n} // namespace MP1\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/MP1/World/CThardusRockProjectile.cpp",
    "content": "#include \"Runtime/MP1/World/CThardusRockProjectile.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Camera/CCameraManager.hpp\"\n#include \"Runtime/Camera/CFirstPersonCamera.hpp\"\n#include \"Runtime/Collision/CCollisionActor.hpp\"\n#include \"Runtime/Collision/CCollisionActorManager.hpp\"\n#include \"Runtime/Collision/CGameCollision.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/World/CDestroyableRock.hpp\"\n#include \"Runtime/World/CExplosion.hpp\"\n#include \"Runtime/World/CLightParameters.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n\n#include \"Runtime/MP1/World/CThardus.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\nnamespace metaforce::MP1 {\nnamespace {\nconstexpr std::array<SSphereJointInfo, 1> skRockCollisions{{\n    {\"Rock_01_Collision_LCTR\", 1.5f},\n}};\n} // namespace\n\nCThardusRockProjectile::CThardusRockProjectile(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                                               const zeus::CTransform& xf, CModelData&& modelData,\n                                               const CActorParameters& aParms, const CPatternedInfo& patternedInfo,\n                                               std::vector<CStaticRes>&& mDataVec, CAssetId stateMachine, float f1)\n: CPatterned(ECharacter::ThardusRockProjectile, uid, name, EFlavorType::Zero, info, xf, std::move(modelData),\n             patternedInfo, EMovementType::Flyer, EColliderType::One, EBodyType::Flyer, aParms,\n             EKnockBackVariant::Medium)\n, x57c_(std::move(mDataVec))\n, x59c_stateMachine(stateMachine)\n, x5c0_(f1) {\n  CMaterialList exclude = GetMaterialFilter().GetExcludeList();\n  exclude.Add(EMaterialTypes::Player);\n  exclude.Add(EMaterialTypes::Character);\n  exclude.Add(EMaterialTypes::NoPlatformCollision);\n  SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(GetMaterialFilter().GetIncludeList(), exclude));\n  x50c_baseDamageMag = 1.f;\n}\n\nvoid CThardusRockProjectile::Think(float dt, CStateManager& mgr) {\n  if (!GetActive()) {\n    return;\n  }\n\n  if (auto* thardus = static_cast<CThardus*>(mgr.ObjectById(x5d0_thardusId))) {\n    if (!thardus->sub801db5b4(mgr)) {\n      RemoveMaterial(EMaterialTypes::Orbit, mgr);\n      RemoveMaterial(EMaterialTypes::Target, mgr);\n      ModifyActorMaterial(mgr, true, EMaterialTypes::Orbit);\n      ModifyActorMaterial(mgr, true, EMaterialTypes::Target);\n    } else {\n      AddMaterial(EMaterialTypes::Orbit, mgr);\n      AddMaterial(EMaterialTypes::Target, mgr);\n      ModifyActorMaterial(mgr, false, EMaterialTypes::Orbit);\n      ModifyActorMaterial(mgr, false, EMaterialTypes::Target);\n    }\n  }\n  CPatterned::Think(dt, mgr);\n  x3b4_speed = x5de_ ? 0.7f : 0.65f;\n  xe6_27_thermalVisorFlags = 1;\n  x578_collisionManager->Update(dt, mgr, CCollisionActorManager::EUpdateOptions::ObjectSpace);\n  UpdateDestroyableRockPositions(mgr);\n  UpdateDestroyableRockCollisionActors(mgr);\n  if (x58c_destroyableRocks.size() <= x5a0_) {\n    ExplodeAndShake(mgr, GetTranslation());\n    DeathDelete(mgr);\n  }\n}\n\nvoid CThardusRockProjectile::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId other, CStateManager& mgr) {\n  CPatterned::AcceptScriptMsg(msg, other, mgr);\n\n  switch (msg) {\n  case EScriptObjectMessage::Touched: {\n    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(other)) {\n      if (TCastToPtr<CPlayer> player = mgr.ObjectById(colAct->GetLastTouchedObject())) {\n        if (x420_curDamageRemTime <= 0.f) {\n          mgr.ApplyDamage(GetUniqueId(), player->GetUniqueId(), GetUniqueId(), GetContactDamage(),\n                          CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), zeus::skZero3f);\n          x420_curDamageRemTime = x424_damageWaitTime;\n          if (mgr.GetPlayer().GetFrozenState()) {\n            mgr.GetPlayer().UnFreeze(mgr);\n          }\n        }\n\n        TUniqueId rockId = kInvalidUniqueId;\n        for (size_t j = 0; j < x578_collisionManager->GetNumCollisionActors(); ++j) {\n          if (x578_collisionManager->GetCollisionDescFromIndex(j).GetCollisionActorId() == other) {\n            rockId = x58c_destroyableRocks[j];\n          }\n        }\n\n        if (rockId != kInvalidUniqueId) {\n          if (auto rock = static_cast<CDestroyableRock*>(mgr.ObjectById(rockId))) {\n            if (rock->GetActive()) {\n              rock->SetActive(false);\n              colAct->SetActive(false);\n              ++x5a0_;\n            }\n          }\n        }\n      }\n    }\n    break;\n  }\n  case EScriptObjectMessage::Deactivate: {\n    SetChildrenActive(mgr, false);\n    break;\n  }\n  case EScriptObjectMessage::Activate: {\n    SetChildrenActive(mgr, true);\n    break;\n  }\n  case EScriptObjectMessage::Deleted: {\n    x578_collisionManager->Destroy(mgr);\n    for (TUniqueId& uid : x58c_destroyableRocks) {\n      mgr.FreeScriptObject(uid);\n    }\n    break;\n  }\n  case EScriptObjectMessage::Registered: {\n    RemoveMaterial(EMaterialTypes::Solid, mgr);\n    x58c_destroyableRocks.reserve(x57c_.size());\n\n    for (size_t i = 0; i < x57c_.size(); ++i) {\n      TUniqueId uid = mgr.AllocateUniqueId();\n      CModelData mData{x57c_[i]};\n      CHealthInfo hInfo = *GetHealthInfo(mgr);\n      CActorParameters actParms(CLightParameters(false, 0.f, CLightParameters::EShadowTesselation::Invalid, 0.f, 0.f,\n                                                 zeus::skWhite, true,\n                                                 CLightParameters::EWorldLightingOptions::NoShadowCast,\n                                                 CLightParameters::ELightRecalculationOptions::LargeFrameCount,\n                                                 zeus::skZero3f, -1, -1, false, 0),\n                                CScannableParameters(), {}, {}, {}, true, true, false, false, 0.f, 0.f, 1.f);\n      auto rock = new CDestroyableRock(uid, true, skRockCollisions[i].name,\n                                       CEntityInfo(GetAreaIdAlways(), CEntity::NullConnectionList), zeus::CTransform(),\n                                       std::move(mData), 0.f, hInfo, CDamageVulnerability::NormalVulnerabilty(),\n                                       GetMaterialList(), x59c_stateMachine, actParms, x57c_[i], 1);\n      rock->Set_x340(false);\n      rock->SetThermalMag(x50c_baseDamageMag);\n      mgr.AddObject(rock);\n      x58c_destroyableRocks.push_back(uid);\n    }\n    AddMaterial(EMaterialTypes::ScanPassthrough);\n    InitializeCollisionManager(mgr);\n    x450_bodyController->Activate(mgr);\n    SetActive(false);\n    SetChildrenActive(mgr, false);\n    break;\n  }\n  case EScriptObjectMessage::Damage: {\n    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(other)) {\n      TUniqueId rockId = kInvalidUniqueId;\n      for (size_t j = 0; j < x578_collisionManager->GetNumCollisionActors(); ++j) {\n        if (other == x578_collisionManager->GetCollisionDescFromIndex(j).GetCollisionActorId()) {\n          rockId = x58c_destroyableRocks[j];\n        }\n      }\n      if (auto rock = static_cast<CDestroyableRock*>(mgr.ObjectById(rockId))) {\n        rock->TakeDamage(zeus::skZero3f, 0.f);\n        // NOTE(phil): Accessing Thardus as const here rather than non-const like the vanilla version\n        // since we're not modifying him in any way\n        if (const auto* thardus = static_cast<const CThardus*>(mgr.GetObjectById(x5d0_thardusId))) {\n          if (mgr.GetPlayerState()->GetCurrentVisor() != CPlayerState::EPlayerVisor::Thermal ||\n              thardus->Get_x7c4() != 3) {\n            DoExplosion(mgr, x5c4_, GetTranslation(), GetModelData()->GetScale(), 0);\n          }\n          ProcessSoundEvent(x5d4_, 1.f, 0, 0.1f, 1000.f, 0.16f, 1.f, zeus::skZero3f, GetTranslation(),\n                            mgr.GetNextAreaId(), mgr, true);\n        }\n      }\n    }\n    break;\n  }\n  default:\n    break;\n  }\n}\n\nvoid CThardusRockProjectile::Render(CStateManager& mgr) { CPatterned::Render(mgr); }\n\nvoid CThardusRockProjectile::Patrol(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg != EStateMsg::Update) {\n    return;\n  }\n\n  GetBodyController()->GetCommandMgr().DeliverCmd(\n      CBCLocomotionCmd(zeus::skZero3f, (mgr.GetPlayer().GetTranslation() - GetTranslation()).normalized(), 1.f));\n}\n\nvoid CThardusRockProjectile::Dead(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg != EStateMsg::Activate) {\n    return;\n  }\n\n  mgr.FreeScriptObject(GetUniqueId());\n  SendScriptMsgs(EScriptObjectState::MassiveDeath, mgr, EScriptObjectMessage::None);\n  GenerateDeathExplosion(mgr);\n}\n\nvoid CThardusRockProjectile::LoopedAttack(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x5a4_ = true;\n  } else if (msg == EStateMsg::Update) {\n    zeus::CVector3f aimPos = mgr.GetPlayer().GetAimPosition(mgr, 0.f);\n    if (!x5bc_ || (aimPos - GetTranslation()).magSquared() <= x5c0_ * x5c0_) {\n      x5bc_ = false;\n    } else {\n      x5b0_ = x45c_steeringBehaviors.Arrival(*this, aimPos, 0.f);\n      x5bc_ = true;\n    }\n\n    zeus::CVector3f movePos = x5b0_;\n    float radius = skRockCollisions[0].radius;\n    auto result = mgr.RayStaticIntersection(GetTranslation(), zeus::skDown, 100.f,\n                                            CMaterialFilter::MakeInclude({EMaterialTypes::Solid}));\n\n    if (result.IsValid()) {\n      movePos = (x45c_steeringBehaviors.Separation(*this, result.GetPoint(), 2.f * radius) + x5b0_).normalized();\n    }\n    GetBodyController()->GetCommandMgr().DeliverCmd(CBCLocomotionCmd{movePos, zeus::skZero3f, 1.f});\n  }\n}\n\nvoid CThardusRockProjectile::GetUp(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x574_ = EAnimState::NotReady;\n  } else if (msg == EStateMsg::Update) {\n    auto result = mgr.RayStaticIntersection(GetTranslation(), zeus::skDown, 2.f,\n                                            CMaterialFilter::MakeInclude({EMaterialTypes::Solid}));\n    if (result.IsInvalid()) {\n      CThardus* thardus = static_cast<CThardus*>(mgr.ObjectById(x5d0_thardusId));\n      if (thardus != nullptr && !x5dc_) {\n        x5dc_ = true;\n        DoExplosion(mgr, x5cc_, result.GetPoint(), GetModelData()->GetScale(), 0);\n        ProcessSoundEvent(SFXsfx07AE, 1.f, 0, 0.1f, 1.f, 0.16f, 1.f, zeus::skZero3f, GetTranslation(),\n                          mgr.GetNextAreaId(), mgr, false);\n      }\n    } else if (mgr.GetCameraManager()->GetCurrentCameraId() ==\n               mgr.GetCameraManager()->GetFirstPersonCamera()->GetUniqueId()) {\n      const CCameraShakeData data = CCameraShakeData::BuildMissileCameraShake(0.25f, 0.5f, 50.f, GetTranslation());\n      mgr.GetCameraManager()->AddCameraShaker(data, true);\n    }\n\n    if (x574_ == EAnimState::NotReady) {\n      if (GetBodyController()->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::Getup) {\n        x574_ = EAnimState::Repeat;\n      } else {\n        GetBodyController()->GetCommandMgr().DeliverCmd(CBCGetupCmd{pas::EGetupType::Zero});\n      }\n    } else if (x574_ == EAnimState::Repeat &&\n               GetBodyController()->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::Getup) {\n      x574_ = EAnimState::Over;\n    }\n  }\n}\n\nvoid CThardusRockProjectile::Lurk(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg != EStateMsg::Update) {\n    return;\n  }\n\n  GetBodyController()->GetCommandMgr().DeliverCmd(\n      CBCLocomotionCmd{zeus::skZero3f, (mgr.GetPlayer().GetTranslation() - GetTranslation()).normalized(), 1.f});\n}\n\nbool CThardusRockProjectile::Delay(CStateManager& mgr, float arg) { return x5a8_ < x330_stateMachineState.GetTime(); }\n\nbool CThardusRockProjectile::AnimOver(CStateManager& mgr, float arg) { return x574_ == EAnimState::Over; }\n\nbool CThardusRockProjectile::ShouldAttack(CStateManager& mgr, float arg) {\n  if (x5ac_ >= x330_stateMachineState.GetTime() || x56c_ == 3) {\n    return false;\n  }\n  x56c_ = 2;\n  return true;\n}\n\nbool CThardusRockProjectile::HitSomething(CStateManager& mgr, float arg) { return x572_; }\n\nbool CThardusRockProjectile::ShouldMove(CStateManager& mgr, float arg) { return x56c_ != 0; }\n\nvoid CThardusRockProjectile::SetChildrenActive(CStateManager& mgr, bool active) {\n  for (size_t i = 0; i < x58c_destroyableRocks.size(); ++i) {\n    const TUniqueId uid = x58c_destroyableRocks[i];\n    const auto& jInfo = x578_collisionManager->GetCollisionDescFromIndex(i);\n    if (auto rock = static_cast<CDestroyableRock*>(mgr.ObjectById(uid))) {\n      if (auto colAct = static_cast<CCollisionActor*>(mgr.ObjectById(jInfo.GetCollisionActorId()))) {\n        rock->SetActive(active);\n        colAct->SetActive(active);\n      }\n    }\n  }\n}\n\nvoid CThardusRockProjectile::InitializeCollisionManager(CStateManager& mgr) {\n  std::vector<CJointCollisionDescription> joints;\n  AddSphereCollisionList(skRockCollisions.data(), 1, joints);\n  x578_collisionManager = std::make_unique<CCollisionActorManager>(mgr, GetUniqueId(), GetAreaIdAlways(), joints, true);\n  SetMaterialProperties(x578_collisionManager, mgr);\n\n  for (size_t j = 0; j < x578_collisionManager->GetNumCollisionActors(); ++j) {\n    const CJointCollisionDescription& colDesc = x578_collisionManager->GetCollisionDescFromIndex(j);\n    auto rock = static_cast<const CDestroyableRock*>(mgr.GetObjectById(x58c_destroyableRocks[j]));\n    if (rock != nullptr) {\n      if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(colDesc.GetCollisionActorId())) {\n        colAct->SetDamageVulnerability(*rock->GetDamageVulnerability());\n        *colAct->HealthInfo(mgr) = *rock->GetHealthInfo(mgr);\n      }\n    }\n  }\n}\nvoid CThardusRockProjectile::AddSphereCollisionList(const SSphereJointInfo* info, size_t count,\n                                                    std::vector<CJointCollisionDescription>& vecOut) {\n  const auto* animData = GetModelData()->GetAnimationData();\n  for (size_t i = 0; i < count; ++i) {\n    CSegId seg = animData->GetLocatorSegId(info[i].name);\n    if (seg.IsInvalid()) {\n      continue;\n    }\n    vecOut.push_back(CJointCollisionDescription::SphereCollision(seg, info[i].radius, info[i].name, 0.001f));\n  }\n}\nvoid CThardusRockProjectile::SetMaterialProperties(const std::unique_ptr<CCollisionActorManager>& colMgr,\n                                                   CStateManager& mgr) {\n  for (size_t j = 0; j < colMgr->GetNumCollisionActors(); ++j) {\n    const CJointCollisionDescription& desc = colMgr->GetCollisionDescFromIndex(j);\n    if (auto colAct = static_cast<CCollisionActor*>(mgr.ObjectById(desc.GetCollisionActorId()))) {\n      CMaterialList include = GetMaterialFilter().GetIncludeList();\n      CMaterialList exclude = GetMaterialFilter().GetExcludeList();\n      include.Add(colAct->GetMaterialFilter().GetIncludeList());\n      exclude.Add(colAct->GetMaterialFilter().GetExcludeList());\n      colAct->SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(include, exclude));\n    }\n  }\n}\nvoid CThardusRockProjectile::DoExplosion(CStateManager& mgr, CAssetId particleId, const zeus::CVector3f& pos,\n                                         const zeus::CVector3f& scale, u32 w2) {\n  TUniqueId uid = mgr.AllocateUniqueId();\n  std::string name = fmt::format(\"ROCK_PROJECTILE_EFFECT-{}-{}\", particleId.Value(), uid.Value());\n  TLockedToken<CGenDescription> descTok = g_SimplePool->GetObj({SBIG('PART'), particleId});\n  mgr.AddObject(new CExplosion(descTok, uid, true, CEntityInfo(mgr.GetNextAreaId(), NullConnectionList), name,\n                               zeus::CTransform(zeus::CMatrix3f(), pos), w2, scale, zeus::skWhite));\n}\nvoid CThardusRockProjectile::ExplodeAndShake(CStateManager& mgr, const zeus::CVector3f& pos) {\n  if (x5d0_thardusId == kInvalidUniqueId) {\n    return;\n  }\n\n  if (auto thardus = static_cast<CThardus*>(mgr.ObjectById(x5d0_thardusId))) {\n    DoExplosion(mgr, x5c8_, pos, GetModelData()->GetScale(), 0);\n    thardus->ApplyCameraShake(0.75f, 125.f, 1.f, mgr, pos);\n  }\n}\nvoid CThardusRockProjectile::ModifyActorMaterial(CStateManager& mgr, bool remove, EMaterialTypes mat) {\n  for (size_t i = 0; i < x58c_destroyableRocks.size(); ++i) {\n    TUniqueId rockId = x58c_destroyableRocks[i];\n    const auto& jInfo = x578_collisionManager->GetCollisionDescFromIndex(i);\n    if (TCastToPtr<CActor> colAct = mgr.ObjectById(jInfo.GetCollisionActorId())) {\n      if (TCastToPtr<CActor> rock = mgr.ObjectById(rockId)) {\n        if (remove) {\n          colAct->RemoveMaterial(mat, mgr);\n          rock->RemoveMaterial(mat, mgr);\n        } else {\n          colAct->AddMaterial(mat, mgr);\n          rock->AddMaterial(mat, mgr);\n        }\n      }\n    }\n  }\n}\nvoid CThardusRockProjectile::UpdateDestroyableRockPositions(CStateManager& mgr) {\n  const zeus::CVector3f scale = GetModelData()->GetScale();\n  for (size_t i = 0; i < x58c_destroyableRocks.size(); ++i) {\n    zeus::CTransform locatorXf =\n        GetModelData()->GetAnimationData()->GetLocatorTransform(skRockCollisions[i].name, nullptr);\n    if (TCastToPtr<CActor> rock = mgr.ObjectById(x58c_destroyableRocks[i])) {\n      locatorXf = GetTransform() * (zeus::CTransform::Scale(scale) * locatorXf);\n      rock->SetTransform(locatorXf);\n    }\n  }\n}\nvoid CThardusRockProjectile::UpdateDestroyableRockCollisionActors(CStateManager& mgr) {\n  for (size_t j = 0; j < x578_collisionManager->GetNumCollisionActors(); ++j) {\n    const auto jInfo = x578_collisionManager->GetCollisionDescFromIndex(j);\n    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(jInfo.GetCollisionActorId())) {\n      if (!colAct->GetActive()) {\n        continue;\n      }\n      if (auto rock = static_cast<CDestroyableRock*>(mgr.ObjectById(x58c_destroyableRocks[j]))) {\n        colAct->SetDamageVulnerability(*rock->GetDamageVulnerability());\n        const CHealthInfo* hInfo = colAct->GetHealthInfo(mgr);\n        *rock->HealthInfo(mgr) = *hInfo;\n        CMaterialFilter filter = (x5bc_ && x56c_ != 3) ? CMaterialFilter::MakeInclude({EMaterialTypes::Wall})\n                                                       : CMaterialFilter::MakeInclude({EMaterialTypes::Solid});\n        bool collided = CGameCollision::DetectStaticCollisionBoolean(mgr, *colAct->GetCollisionPrimitive(),\n                                                                     colAct->GetTransform(), filter);\n        if (hInfo->GetHP() <= 0.f || (collided && x5a4_)) {\n          rock->SetActive(false);\n          colAct->SetActive(false);\n          ++x5a0_;\n          if (hInfo->GetHP() <= 0.f) {\n            x5dd_ = true;\n          }\n        }\n      }\n    }\n  }\n}\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CThardusRockProjectile.hpp",
    "content": "#pragma once\n\n#include \"Runtime/World/CPatterned.hpp\"\n#include \"Runtime/Collision/CJointCollisionDescription.hpp\"\nnamespace metaforce {\nclass CCollisionActorManager;\nstruct SSphereJointInfo;\nnamespace MP1 {\nclass CThardusRockProjectile : public CPatterned {\n  float x568_ = 1.f;\n  u32 x56c_ = 0;\n  TUniqueId x570_ = kInvalidUniqueId;\n  bool x572_ = false;\n  EAnimState x574_ = EAnimState::Invalid;\n  std::unique_ptr<CCollisionActorManager> x578_collisionManager;\n  std::vector<CStaticRes> x57c_; // Used to be a pointers to CModelData\n  std::vector<TUniqueId> x58c_destroyableRocks;\n  CAssetId x59c_stateMachine;\n  u32 x5a0_ = 0;\n  bool x5a4_ = true;\n  float x5a8_ = 0.f;\n  float x5ac_ = 0.f;\n  zeus::CVector3f x5b0_ = zeus::skForward;\n  bool x5bc_ = true;\n  float x5c0_;\n  u32 x5c4_ = 0;\n  u32 x5c8_ = 0;\n  u32 x5cc_ = 0;\n  TUniqueId x5d0_thardusId = kInvalidUniqueId;\n  u32 x5d4_ = 0;\n  u32 x5d8_ = 0;\n  bool x5dc_ = false;\n  bool x5dd_ = false;\n  bool x5de_;\n  void DoExplosion(CStateManager& mgr, CAssetId particleId, const zeus::CVector3f& pos, const zeus::CVector3f& scale,\n                   u32 w2);\n  void ExplodeAndShake(CStateManager& mgr, const zeus::CVector3f& pos);\n  void ModifyActorMaterial(CStateManager& mgr, bool remove, EMaterialTypes mat);\n  void SetChildrenActive(CStateManager& mgr, bool active);\n  void InitializeCollisionManager(CStateManager& mgr);\n  void AddSphereCollisionList(const SSphereJointInfo* info, size_t count,\n                              std::vector<CJointCollisionDescription>& vecOut);\n  void SetMaterialProperties(const std::unique_ptr<CCollisionActorManager>& colMgr, CStateManager& mgr);\n  void UpdateDestroyableRockPositions(CStateManager& mgr);\n  void UpdateDestroyableRockCollisionActors(CStateManager& mgr);\n\npublic:\n  DEFINE_PATTERNED(ThardusRockProjectile);\n  CThardusRockProjectile(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                         CModelData&& modelData, const CActorParameters& aParms, const CPatternedInfo& patternedInfo,\n                         std::vector<CStaticRes>&& mDataVec, CAssetId stateMachine, float);\n\n  void Think(float dt, CStateManager& mgr) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId other, CStateManager& mgr) override;\n  void Render(CStateManager& mgr) override;\n  void sub80203d58() {\n    x328_25_verticalMovement = false;\n    Stop();\n    x150_momentum = {0.f, 0.f, 2.f * -GetWeight()};\n    x56c_ = 3;\n  }\n\n  void Patrol(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Dead(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void LoopedAttack(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void GetUp(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void Lurk(CStateManager& mgr, EStateMsg msg, float dt) override;\n  bool Delay(CStateManager& mgr, float arg) override;\n  bool AnimOver(CStateManager& mgr, float arg) override;\n  bool ShouldAttack(CStateManager& mgr, float arg) override;\n  bool HitSomething(CStateManager& mgr, float arg) override;\n  bool ShouldMove(CStateManager& mgr, float arg) override;\n};\n} // namespace MP1\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/MP1/World/CTryclops.cpp",
    "content": "#include \"Runtime/MP1/World/CTryclops.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Collision/CGameCollision.hpp\"\n#include \"Runtime/Weapon/CBomb.hpp\"\n#include \"Runtime/World/CGameArea.hpp\"\n#include \"Runtime/World/CPatternedInfo.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\n#include <cmath>\n\nnamespace metaforce::MP1 {\nconst CDamageVulnerability CTryclops::skVulnerabilities = CDamageVulnerability(\n    EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect,\n    EVulnerability::Deflect, EVulnerability::Normal, EVulnerability::Deflect, EVulnerability::Deflect,\n    EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect,\n    EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EDeflectType::Two);\n\nCTryclops::CTryclops(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                     CModelData&& mData, const CPatternedInfo& pInfo, const CActorParameters& actParms, float f1,\n                     float f2, float f3, float launchSpeed)\n: CPatterned(ECharacter::Tryclops, uid, name, EFlavorType::Zero, info, xf, std::move(mData), pInfo,\n             EMovementType::Ground, EColliderType::One, EBodyType::BiPedal, actParms, EKnockBackVariant::Small)\n, x568_pathFindSearch(nullptr, 1, pInfo.GetPathfindingIndex(), 1.f, 1.f)\n, x67c_(f1)\n, x680_(std::cos(zeus::degToRad(0.5f * f2)))\n, x684_(f3)\n, x688_launchSpeed(launchSpeed) {\n  CreateShadow(false);\n  MakeThermalColdAndHot();\n  x460_knockBackController.SetAutoResetImpulse(false);\n  x328_30_lookAtDeathDir = false;\n}\n\nvoid CTryclops::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  CPatterned::AcceptScriptMsg(msg, uid, mgr);\n\n  if (msg == EScriptObjectMessage::Registered) {\n    x450_bodyController->Activate(mgr);\n  } else if (msg == EScriptObjectMessage::InitializedInArea) {\n    x568_pathFindSearch.SetArea(mgr.GetWorld()->GetAreaAlways(GetAreaIdAlways())->GetPostConstructed()->x10bc_pathArea);\n  }\n}\n\nvoid CTryclops::Think(float dt, CStateManager& mgr) {\n  CPatterned::Think(dt, mgr);\n  if (x400_25_alive && x68c_ > 0.f) {\n    x68c_ -= dt;\n  }\n  if (mgr.GetPlayer().GetAttachedActor() != GetUniqueId() || x698_27_dizzy) {\n    return;\n  }\n  x698_27_dizzy = (mgr.GetPlayer().GetAttachedActorStruggle() == 1.f && sub8025dbd0(mgr));\n}\n\nvoid CTryclops::DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& poi, EUserEventType type, float dt) {\n  switch (type) {\n  case EUserEventType::Projectile:\n    if (x694_bombId == kInvalidUniqueId) {\n      LaunchPlayer(mgr, GetLctrTransform(poi.GetLocatorName()), (x698_27_dizzy ? 5.f : x688_launchSpeed));\n    } else {\n      DragBomb(mgr, GetLctrTransform(poi.GetLocatorName()));\n    }\n    return;\n  default:\n    break;\n  }\n  CPatterned::DoUserAnimEvent(mgr, poi, type, dt);\n}\n\nvoid CTryclops::Death(CStateManager& mgr, const zeus::CVector3f& vec, EScriptObjectState state) {\n  if (x400_25_alive) {\n    CPlayer& player = mgr.GetPlayer();\n    if (player.GetAttachedActor() == GetUniqueId()) {\n      player.SetLeaveMorphBallAllowed(true);\n      player.AddMaterial(EMaterialTypes::Solid, mgr);\n      player.DetachActorFromPlayer();\n    } else if (x694_bombId != kInvalidUniqueId) {\n      if (TCastToPtr<CBomb> bomb = mgr.ObjectById(x694_bombId)) {\n        bomb->SetFuseDisabled(false);\n        bomb->SetIsBeingDragged(false);\n      }\n      x694_bombId = kInvalidUniqueId;\n    }\n  }\n\n  CPatterned::Death(mgr, vec, state);\n}\n\nvoid CTryclops::Patrol(CStateManager& mgr, EStateMsg msg, float arg) {\n  CPatterned::Patrol(mgr, msg, arg);\n  if (msg == EStateMsg::Activate) {\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n  } else if (msg == EStateMsg::Update) {\n    ApplySeparation(mgr);\n  }\n}\n\nvoid CTryclops::PathFind(CStateManager& mgr, EStateMsg msg, float arg) {\n  CPatterned::PathFind(mgr, msg, arg);\n  const auto moveVec = x450_bodyController->GetCommandMgr().GetMoveVector();\n  if (GetTransform().frontVector().dot(moveVec) < 0.f && moveVec.canBeNormalized()) {\n    x450_bodyController->GetCommandMgr().ClearLocomotionCmds();\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(zeus::skZero3f, moveVec.normalized(), 1.f));\n  }\n  ApplySeparation(mgr);\n}\n\nvoid CTryclops::SelectTarget(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    mgr.GetPlayer().SetLeaveMorphBallAllowed(true);\n    mgr.GetPlayer().AddMaterial(EMaterialTypes::Solid, mgr);\n    x698_25_ = true;\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Internal6);\n  } else if (msg == EStateMsg::Update) {\n    AttractBomb(mgr, arg);\n  }\n}\n\nvoid CTryclops::TargetPatrol(CStateManager& mgr, EStateMsg msg, float arg) {\n  if (msg == EStateMsg::Activate) {\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n  }\n  CPatterned::TargetPatrol(mgr, msg, arg);\n}\n\nvoid CTryclops::TargetPlayer(CStateManager& mgr, EStateMsg msg, float arg) {\n  CPatterned::TargetPlayer(mgr, msg, arg);\n  if (msg == EStateMsg::Activate && x694_bombId != kInvalidUniqueId) {\n    if (TCastToPtr<CBomb> bomb = mgr.ObjectById(x694_bombId)) {\n      bomb->SetFuseDisabled(false);\n      bomb->SetIsBeingDragged(false);\n      x694_bombId = kInvalidUniqueId;\n    }\n  }\n}\n\nvoid CTryclops::TargetCover(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n    if (x694_bombId == kInvalidUniqueId) {\n      return;\n    }\n    if (TCastToConstPtr<CBomb> bomb = mgr.GetObjectById(x694_bombId)) {\n      SetDestPos(bomb->GetTranslation());\n    } else {\n      x694_bombId = kInvalidUniqueId;\n    }\n  }\n}\n\nvoid CTryclops::Attack(CStateManager& mgr, EStateMsg msg, float arg) {\n  auto& player = mgr.GetPlayer();\n  if (msg == EStateMsg::Activate) {\n    player.Stop();\n    player.RemoveMaterial(EMaterialTypes::Solid, mgr);\n    player.SetLeaveMorphBallAllowed(false);\n    x32c_animState = EAnimState::Ready;\n    x698_24_ = false;\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::MeleeAttack, &CPatterned::TryMeleeAttack, 1);\n    if (!x698_24_) {\n      DragPlayer(mgr, GetLctrTransform(\"bombGrab_locator\"sv).origin);\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n    if (player.GetAttachedActor() == GetUniqueId()) {\n      player.DetachActorFromPlayer();\n    }\n\n    player.SetLeaveMorphBallAllowed(true);\n    player.AddMaterial(EMaterialTypes::Solid, mgr);\n  }\n}\n\nvoid CTryclops::JumpBack(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    if (TCastToConstPtr<CActor> wp =\n            mgr.GetObjectById(GetWaypointForState(mgr, EScriptObjectState::Retreat, EScriptObjectMessage::Follow))) {\n      SetDestPos(wp->GetTranslation());\n    }\n\n    if (x694_bombId != kInvalidUniqueId) {\n      if (TCastToPtr<CBomb> bomb = mgr.ObjectById(x694_bombId)) {\n        bomb->SetFuseDisabled(false);\n      } else {\n        x694_bombId = kInvalidUniqueId;\n      }\n    }\n\n    SendScriptMsgs(EScriptObjectState::Inside, mgr, EScriptObjectMessage::None);\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Combat);\n  } else if (msg == EStateMsg::Update) {\n    GrabBomb(mgr);\n  }\n}\n\nvoid CTryclops::Shuffle(CStateManager& mgr, EStateMsg msg, float arg) { PathFind(mgr, msg, arg); }\n\nvoid CTryclops::TurnAround(CStateManager& mgr, EStateMsg msg, float) {\n  CPlayer& player = mgr.GetPlayer();\n  if (msg == EStateMsg::Activate) {\n    if (x694_bombId == kInvalidUniqueId) {\n      player.Stop();\n      player.RemoveMaterial(EMaterialTypes::Solid, mgr);\n      player.SetLeaveMorphBallAllowed(true);\n    }\n\n    TUniqueId uid = GetWaypointForState(mgr, EScriptObjectState::Modify, EScriptObjectMessage::Follow);\n    bool retreat = uid == kInvalidUniqueId;\n    if (retreat) {\n      uid = GetWaypointForState(mgr, EScriptObjectState::Retreat, EScriptObjectMessage::Follow);\n    }\n\n    if (TCastToConstPtr<CActor> wp = mgr.GetObjectById(uid)) {\n      zeus::CVector3f destVec =\n          (retreat ? wp->GetTransform().frontVector() : wp->GetTranslation() - GetTranslation()).normalized();\n      destVec.z() = 0.f;\n      SetDestPos(GetTranslation() + destVec);\n\n      if (std::fabs(\n              zeus::CVector3f(GetTransform().frontVector().x(), GetTransform().frontVector().y(), 0.f).dot(destVec)) <\n          0.9998) {\n        x32c_animState = EAnimState::Ready;\n      }\n    }\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::Turn, &CPatterned::TryTurn, 0);\n\n    if (x694_bombId == kInvalidUniqueId) {\n      DragPlayer(mgr, GetLctrTransform(\"ballGrab_locator\"sv).origin);\n    } else {\n      GrabBomb(mgr);\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n    player.SetLeaveMorphBallAllowed(true);\n    player.AddMaterial(EMaterialTypes::Solid, mgr);\n    if (player.GetAttachedActor() == GetUniqueId()) {\n      player.DetachActorFromPlayer();\n    }\n  }\n}\n\nvoid CTryclops::Crouch(CStateManager& mgr, EStateMsg msg, float) {\n  auto& player = mgr.GetPlayer();\n  if (msg == EStateMsg::Activate) {\n    if (TCastToConstPtr<CActor> wp =\n            mgr.GetObjectById(GetWaypointForState(mgr, EScriptObjectState::Retreat, EScriptObjectMessage::Follow))) {\n      SetDestPos(wp->GetTranslation());\n    }\n\n    player.Stop();\n    player.RemoveMaterial(EMaterialTypes::Solid, mgr);\n    SendScriptMsgs(EScriptObjectState::Inside, mgr, EScriptObjectMessage::None);\n    player.AttachActorToPlayer(GetUniqueId(), true);\n    player.SetLeaveMorphBallAllowed(false);\n    player.GetMorphBall()->DisableHalfPipeStatus();\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Combat);\n  } else if (msg == EStateMsg::Update) {\n    DragPlayer(mgr, GetLctrTransform(\"ballGrab_locator\"sv).origin);\n  } else if (msg == EStateMsg::Deactivate) {\n    if (player.GetAttachedActor() == GetUniqueId()) {\n      player.DetachActorFromPlayer();\n    }\n    player.SetLeaveMorphBallAllowed(true);\n    player.AddMaterial(EMaterialTypes::Solid, mgr);\n  }\n}\n\nvoid CTryclops::GetUp(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    x32c_animState = EAnimState::Ready;\n    x698_24_ = false;\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::MeleeAttack, &CPatterned::TryMeleeAttack, 1);\n    if (!x698_24_) {\n      GrabBomb(mgr);\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n  }\n}\n\nvoid CTryclops::Suck(CStateManager& mgr, EStateMsg msg, float arg) {\n  auto& player = mgr.GetPlayer();\n  if (msg == EStateMsg::Activate) {\n    player.SetLeaveMorphBallAllowed(false);\n    player.GetMorphBall()->DisableHalfPipeStatus();\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Internal6);\n  } else if (msg == EStateMsg::Update) {\n    SuckPlayer(mgr, arg);\n  } else if (msg == EStateMsg::Deactivate) {\n    player.SetLeaveMorphBallAllowed(true);\n    player.AddMaterial(EMaterialTypes::Solid, mgr);\n  }\n}\n\nvoid CTryclops::Cover(CStateManager& mgr, EStateMsg msg, float) {\n  if (msg == EStateMsg::Activate) {\n    if (!x698_25_) {\n      x68c_ = 1.5f;\n    }\n    auto& player = mgr.GetPlayer();\n    if (player.GetAttachedActor() == GetUniqueId()) {\n      player.SetLeaveMorphBallAllowed(true);\n      player.AddMaterial(EMaterialTypes::Solid, mgr);\n      player.DetachActorFromPlayer();\n    }\n  }\n}\n\nvoid CTryclops::Approach(CStateManager& mgr, EStateMsg msg, float arg) {\n  CPatterned::PathFind(mgr, msg, arg);\n  ApplySeparation(mgr);\n  if (msg == EStateMsg::Update) {\n    GrabBomb(mgr);\n  }\n}\n\nvoid CTryclops::PathFindEx(CStateManager& mgr, EStateMsg msg, float arg) {\n  CPatterned::PathFind(mgr, msg, arg);\n  ApplySeparation(mgr);\n\n  auto& player = mgr.GetPlayer();\n  if (msg == EStateMsg::Activate) {\n    player.Stop();\n    player.RemoveMaterial(EMaterialTypes::Solid, mgr);\n    player.SetLeaveMorphBallAllowed(false);\n    player.GetMorphBall()->DisableHalfPipeStatus();\n    player.AttachActorToPlayer(GetUniqueId(), true);\n  } else if (msg == EStateMsg::Update) {\n    DragPlayer(mgr, GetLctrTransform(\"ballGrab_locator\"sv).origin);\n  }\n}\n\nvoid CTryclops::Dizzy(CStateManager& mgr, EStateMsg msg, float) {\n  auto& player = mgr.GetPlayer();\n  if (msg == EStateMsg::Activate) {\n    player.Stop();\n    player.RemoveMaterial(EMaterialTypes::Solid, mgr);\n    player.SetLeaveMorphBallAllowed(false);\n    x32c_animState = EAnimState::Ready;\n    x698_24_ = false;\n  } else if (msg == EStateMsg::Update) {\n    TryCommand(mgr, pas::EAnimationState::MeleeAttack, &CPatterned::TryMeleeAttack, 0);\n    if (!x698_24_) {\n      DragPlayer(mgr, GetLctrTransform(\"ballGrab_locator\"sv).origin);\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x32c_animState = EAnimState::NotReady;\n    if (player.GetAttachedActor() == GetUniqueId()) {\n      player.SetLeaveMorphBallAllowed(true);\n      player.AddMaterial(EMaterialTypes::Solid, mgr);\n      player.DetachActorFromPlayer();\n      x698_27_dizzy = false;\n    }\n  }\n}\n\nbool CTryclops::InAttackPosition(CStateManager& mgr, float) {\n  if (mgr.GetPlayer().GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Morphed) {\n    return false;\n  }\n  auto& player = mgr.GetPlayer();\n  return sub80260180(player.GetTranslation(),\n                     player.GetTranslation() + zeus::CVector3f(0.f, 0.f, player.GetMorphBall()->GetBallRadius()),\n                     player.GetBoundingBox(), mgr);\n}\n\nbool CTryclops::InRange(CStateManager& mgr, float) {\n  if (x694_bombId != kInvalidUniqueId) {\n    if (TCastToConstPtr<CBomb> bomb = mgr.GetObjectById(x694_bombId)) {\n      return sub80260180(bomb->GetTranslation(), bomb->GetTranslation(), *bomb->GetTouchBounds(), mgr);\n    }\n  }\n  return false;\n}\n\nbool CTryclops::InMaxRange(CStateManager& mgr, float) {\n  if (x694_bombId != kInvalidUniqueId) {\n    return true;\n  }\n\n  EntityList nearList;\n\n  float dectRange = x3bc_detectionRange * x3bc_detectionRange;\n  float dectRangeHeight = x3c0_detectionHeightRange * x3c0_detectionHeightRange;\n  zeus::CAABox aabb{GetTranslation() + zeus::CVector3f{-x3bc_detectionRange, -x3bc_detectionRange, 0.f},\n                    GetTranslation() +\n                        zeus::CVector3f{x3bc_detectionRange, x3bc_detectionRange, x3c0_detectionHeightRange}};\n  mgr.BuildNearList(nearList, aabb, CMaterialFilter::MakeInclude({EMaterialTypes::Bomb}), this);\n\n  x694_bombId = kInvalidUniqueId;\n\n  for (TUniqueId uid : nearList) {\n    if (TCastToConstPtr<CBomb> bomb = mgr.GetObjectById(uid)) {\n      if (!bomb->IsBeingDragged()) {\n        const auto dist = bomb->GetTranslation() - GetTranslation();\n        float distSq = dist.magSquared();\n        if (distSq < dectRange && (dectRangeHeight <= 0.f || (dectRangeHeight > dist.z() * dist.z()))) {\n          if (x568_pathFindSearch.OnPath(bomb->GetTranslation()) == CPathFindSearch::EResult::Success) {\n            x694_bombId = bomb->GetUniqueId();\n            dectRange = distSq;\n          }\n        }\n      }\n    }\n  }\n\n  if (x694_bombId != kInvalidUniqueId) {\n    if (TCastToPtr<CBomb> bomb = mgr.ObjectById(x694_bombId)) {\n      bomb->SetFuseDisabled(true);\n      bomb->SetIsBeingDragged(true);\n      return true;\n    }\n  }\n\n  return false;\n}\n\nbool CTryclops::InDetectionRange(CStateManager& mgr, float arg) {\n  auto& player = mgr.GetPlayer();\n  if (player.GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Morphed ||\n      player.GetAttachedActor() != kInvalidUniqueId || x68c_ > 0.f || !CPatterned::InDetectionRange(mgr, arg)) {\n    return false;\n  }\n  return x568_pathFindSearch.OnPath(player.GetBallPosition()) == CPathFindSearch::EResult::Success;\n}\n\nbool CTryclops::SpotPlayer(CStateManager& mgr, float) {\n  if (x694_bombId != kInvalidUniqueId) {\n\n    CPlayer& player = mgr.GetPlayer();\n    if (TCastToPtr<CBomb> bomb = mgr.ObjectById(x694_bombId)) {\n      if (player.GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed) {\n        bool isPlayerCloser = (player.GetTranslation() - GetTranslation()).magSquared() <\n                              (bomb->GetTranslation() - GetTranslation()).magSquared();\n        if (isPlayerCloser) {\n          bomb->SetFuseDisabled(false);\n          bomb->SetIsBeingDragged(false);\n          x694_bombId = kInvalidUniqueId;\n        }\n        return isPlayerCloser;\n      }\n    }\n  }\n\n  return true;\n}\n\nbool CTryclops::InPosition(CStateManager& mgr, float arg) {\n  if (x694_bombId != kInvalidUniqueId) {\n    if (TCastToConstPtr<CBomb> bomb = mgr.GetObjectById(x694_bombId)) {\n      return InRangeToLocator(bomb->GetTranslation(), arg);\n    }\n  }\n\n  return false;\n}\n\nbool CTryclops::HearShot(CStateManager& mgr, float) {\n  x698_26_ = false;\n  if (x694_bombId != kInvalidUniqueId) {\n    if (TCastToConstPtr<CBomb>(mgr.GetObjectById(x694_bombId))) {\n      x698_26_ = true;\n      return false;\n    }\n    x694_bombId = kInvalidUniqueId;\n  }\n  return true;\n}\n\nbool CTryclops::CoverBlown(CStateManager&, float) {\n  return x568_pathFindSearch.OnPath(GetTranslation()) != CPathFindSearch::EResult::InvalidArea;\n}\n\nbool CTryclops::Inside(CStateManager& mgr, float arg) {\n  const zeus::CTransform xf = mgr.GetPlayer().GetTransform();\n  x64c_ = xf.getRotation();\n\n  return InRangeToLocator(xf.origin + zeus::CVector3f(0.f, 0.f, mgr.GetPlayer().GetMorphBall()->GetBallRadius()), arg);\n}\n\nbool CTryclops::ShouldRetreat(CStateManager& mgr, float) {\n  if (TCastToConstPtr<CActor> wp =\n          mgr.GetObjectById(GetWaypointForState(mgr, EScriptObjectState::Modify, EScriptObjectMessage::Next))) {\n    SetDestPos(wp->GetTranslation());\n    return true;\n  }\n  return false;\n}\n\nbool CTryclops::IsDizzy(CStateManager&, float) { return x698_27_dizzy; }\n\nvoid CTryclops::LaunchPlayer(CStateManager& mgr, const zeus::CTransform& xf, float f1) {\n  CPlayer& player = mgr.GetPlayer();\n  player.SetLeaveMorphBallAllowed(true);\n\n  if (player.GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Morphed) {\n    return;\n  }\n\n  x698_24_ = true;\n  x68c_ = 1.5f;\n  player.Stop();\n  zeus::CTransform tmpXf = (xf * x64c_);\n  tmpXf.origin += zeus::CVector3f(0.f, 0.f, -0.5f);\n  player.Teleport(tmpXf, mgr, false);\n  player.ApplyImpulseWR(f1 * (player.GetMass() * xf.frontVector().normalized()), zeus::CAxisAngle());\n  player.SetMoveState(CPlayer::EPlayerMovementState::ApplyJump, mgr);\n  player.AddMaterial(EMaterialTypes::Solid, mgr);\n  mgr.ApplyDamage(GetUniqueId(), player.GetUniqueId(), GetUniqueId(), GetContactDamage(),\n                  CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), zeus::skZero3f);\n}\n\nvoid CTryclops::DragBomb(CStateManager& mgr, const zeus::CTransform& xf) {\n  if (x694_bombId != kInvalidUniqueId) {\n    if (TCastToPtr<CBomb> bomb = mgr.ObjectById(x694_bombId)) {\n      bomb->SetVelocityWR((5.f * mgr.GetActiveRandom()->Float() + 20.f) * xf.frontVector().normalized());\n      bomb->SetConstantAccelerationWR({0.f, 0.f, -CPhysicsActor::GravityConstant()});\n    }\n  }\n\n  x698_26_ = false;\n  x698_24_ = true;\n  x694_bombId = kInvalidUniqueId;\n}\n\nvoid CTryclops::ApplySeparation(CStateManager& mgr) {\n  for (CEntity* ent : mgr.GetAiWaypointObjectList()) {\n    if (TCastToPtr<CPatterned> ai = ent) {\n      if (ai == this || ai->GetAreaIdAlways() != GetAreaId()) {\n        continue;\n      }\n\n      zeus::CVector3f sep = x45c_steeringBehaviors.Separation(*this, ai->GetTranslation(), 8.f);\n      if (sep.x() != 0.f || sep.y() != 0.f || sep.z() != 0.f) {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(sep, zeus::skZero3f, 1.f));\n      }\n    }\n  }\n}\n\nvoid CTryclops::GrabBomb(CStateManager& mgr) {\n  if (TCastToPtr<CBomb> bomb = mgr.ObjectById(x694_bombId)) {\n    zeus::CTransform grabLctr = GetLctrTransform(\"ballGrab_locator\"sv);\n    grabLctr.origin += zeus::CVector3f(0.f, 0.f, -.3f);\n    bomb->SetTransform(grabLctr);\n  }\n}\n\nvoid CTryclops::DragPlayer(CStateManager& mgr, const zeus::CVector3f& locOrig) {\n  CPlayer& player = mgr.GetPlayer();\n  player.Stop();\n  player.RemoveMaterial(EMaterialTypes::Solid, mgr);\n  zeus::CTransform xf = GetLctrTransform(\"ballGrab_locator\"sv) * x64c_;\n  xf.origin += {0.f, 0.f, -.5f};\n  player.SetTransform(xf);\n}\n\nbool CTryclops::InRangeToLocator(const zeus::CVector3f& vec, float arg) const {\n  return (vec - GetLctrTransform(\"ballGrab_locator\"sv).origin).magSquared() <= arg;\n}\n\nbool CTryclops::sub80260180(const zeus::CVector3f& vec1, const zeus::CVector3f& vec2, const zeus::CAABox& bounds,\n                            CStateManager& mgr) {\n\n  if (bounds.intersects(GetBoundingBox())) {\n    return true;\n  }\n\n  zeus::CTransform xf = GetLctrTransform(\"ballGrab_locator\"sv);\n  zeus::CVector3f tmpVec2 = vec2 - (xf.origin - (1.f * GetTransform().frontVector()));\n  float tmpVec2Dot = tmpVec2.normalized().dot(GetTransform().frontVector());\n  zeus::CVector3f tmpVec1 = vec1 - (xf.origin - (4.f * GetTransform().frontVector()));\n  float tmpVec1Dot = tmpVec1.normalized().dot(GetTransform().frontVector());\n  float tmpVec2Mag = tmpVec2.magnitude();\n\n  if (tmpVec2Mag > 2.f) {\n    CRayCastResult res = mgr.RayStaticIntersection(\n        xf.origin, (1.f / tmpVec2Mag) * tmpVec2, tmpVec2Mag - mgr.GetPlayer().GetMorphBall()->GetBallRadius(),\n        CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {EMaterialTypes::Character, EMaterialTypes::Player,\n                                                                      EMaterialTypes::ProjectilePassthrough}));\n    if (res.IsValid()) {\n      return false;\n    }\n  }\n\n  return !(x684_ <= tmpVec2Mag || tmpVec2Dot <= 0.f || tmpVec1Dot <= x680_);\n}\n\nvoid CTryclops::SuckPlayer(CStateManager& mgr, float arg) {\n  if (mgr.GetPlayer().GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Morphed) {\n    return;\n  }\n  CPlayer& player = mgr.GetPlayer();\n  zeus::CTransform xf = GetLctrTransform(\"ballGrab_locator\"sv);\n  zeus::CVector3f diff = (player.GetTranslation() - xf.origin);\n  float diffMag = diff.magnitude();\n  if (diffMag < 3.f) {\n    player.Stop();\n    AttractPlayer(mgr, xf.origin, arg);\n  } else {\n    player.ApplyForceWR(((x67c_ * (x684_ / (diffMag * diffMag))) * player.GetMass() * -diff), {});\n  }\n}\n\nvoid CTryclops::AttractPlayer(CStateManager& mgr, const zeus::CVector3f& dest, float arg) {\n  CPlayer& player = mgr.GetPlayer();\n  const float ballRad = player.GetMorphBall()->GetBallRadius();\n  player.SetVelocityWR(1.f / (2.f * arg) *\n                       (dest - (player.GetTranslation() + zeus::CVector3f(0.f, 0.f, ballRad))).normalized());\n}\n\nvoid CTryclops::AttractBomb(CStateManager& mgr, float arg) {\n  if (TCastToPtr<CBomb> bomb = mgr.ObjectById(x694_bombId)) {\n    bomb->SetVelocityWR(\n        1.f / (2.f * arg) *\n        (GetLctrTransform(\"ballGrab_locator\"sv).origin + zeus::CVector3f(0.f, 0.f, -.3f) - bomb->GetTranslation())\n            .normalized());\n  }\n}\n\nbool CTryclops::sub8025dbd0(CStateManager& mgr) {\n  const auto result = mgr.RayStaticIntersection(GetLctrTransform(\"Skeleton_Root\").origin, GetTransform().frontVector(),\n                                                3.f, CMaterialFilter::skPassEverything);\n  if (result.IsValid()) {\n    return true;\n  }\n\n  const auto& player = mgr.GetPlayer();\n  const float ballRadius = player.GetMorphBall()->GetBallRadius();\n  constexpr CMaterialList matList{EMaterialTypes::Player, EMaterialTypes::Solid};\n  const CCollidableSphere colSphere{zeus::CSphere{GetTranslation() + zeus::CVector3f{0.f, 0.f, ballRadius}, ballRadius},\n                                    matList};\n  EntityList nearList;\n  mgr.BuildColliderList(nearList, player, colSphere.CalculateLocalAABox());\n  constexpr auto matFilter = CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {EMaterialTypes::Player});\n  const zeus::CTransform skIdentity4f{}; // TODO move to zeus & make constexpr\n  if (CGameCollision::DetectStaticCollisionBoolean(mgr, colSphere, skIdentity4f, matFilter)) {\n    return true;\n  }\n\n  for (const auto& id : nearList) {\n    if (id == GetUniqueId()) {\n      continue;\n    }\n    if (TCastToConstPtr<CPhysicsActor> actor = mgr.GetObjectById(id)) {\n      if (CCollisionPrimitive::CollideBoolean(\n              {colSphere, matFilter, actor->GetPrimitiveTransform()},\n              {*actor->GetCollisionPrimitive(), CMaterialFilter::skPassEverything, skIdentity4f})) {\n        return true;\n      }\n    }\n  }\n\n  return false;\n}\n\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CTryclops.hpp",
    "content": "#pragma once\n\n#include \"Runtime/World/CPathFindSearch.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n\n#include <zeus/CTransform.hpp>\n\nnamespace metaforce::MP1 {\nclass CTryclops : public CPatterned {\n\n  static const CDamageVulnerability skVulnerabilities;\n  CPathFindSearch x568_pathFindSearch;\n  zeus::CTransform x64c_;\n  float x67c_;\n  float x680_;\n  float x684_;\n  float x688_launchSpeed;\n  float x68c_ = 0.f;\n  u32 x690_ = 0;\n  TUniqueId x694_bombId = kInvalidUniqueId;\n  TUniqueId x696_ = kInvalidUniqueId;\n  bool x698_24_ : 1 = false;\n  bool x698_25_ : 1 = false;\n  bool x698_26_ : 1 = false;\n  bool x698_27_dizzy : 1 = false;\n  bool sub8025dbd0(CStateManager& mgr);\n  void LaunchPlayer(CStateManager& mgr, const zeus::CTransform& xf, float);\n  void DragBomb(CStateManager& mgr, const zeus::CTransform& xf);\n  void ApplySeparation(CStateManager&);\n  void GrabBomb(CStateManager& mgr);\n  void DragPlayer(CStateManager& mgr, const zeus::CVector3f& locOrig);\n  bool InRangeToLocator(const zeus::CVector3f& vec, float) const;\n  bool sub80260180(const zeus::CVector3f&, const zeus::CVector3f&, const zeus::CAABox&, CStateManager&);\n  void SuckPlayer(CStateManager& mgr, float);\n  void AttractPlayer(CStateManager& mgr, const zeus::CVector3f& dest, float);\n  void AttractBomb(CStateManager& mgr, float);\n\npublic:\n  DEFINE_PATTERNED(Tryclops);\n  CTryclops(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n            CModelData&& mData, const CPatternedInfo& pInfo, const CActorParameters& actParms, float f1, float f2,\n            float f3, float launchSpeed);\n\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void Think(float, CStateManager&) override;\n  const CDamageVulnerability* GetDamageVulnerability() const override {\n    if (x698_26_)\n      return CAi::GetDamageVulnerability();\n\n    return &skVulnerabilities;\n  }\n\n  const CDamageVulnerability* GetDamageVulnerability(const zeus::CVector3f&, const zeus::CVector3f&,\n                                                     const CDamageInfo&) const override {\n    if (x698_26_)\n      return CAi::GetDamageVulnerability();\n\n    return &skVulnerabilities;\n  }\n\n  void DoUserAnimEvent(CStateManager&, const CInt32POINode&, EUserEventType, float) override;\n  void Death(CStateManager&, const zeus::CVector3f&, EScriptObjectState) override;\n  bool IsListening() const override { return true; }\n  void Patrol(CStateManager&, EStateMsg, float) override;\n  void PathFind(CStateManager&, EStateMsg, float) override;\n  void SelectTarget(CStateManager&, EStateMsg, float) override;\n  void TargetPatrol(CStateManager&, EStateMsg, float) override;\n  void TargetPlayer(CStateManager&, EStateMsg, float) override;\n  void TargetCover(CStateManager&, EStateMsg, float) override;\n  void Attack(CStateManager&, EStateMsg, float) override;\n  void JumpBack(CStateManager&, EStateMsg, float) override;\n  void Shuffle(CStateManager&, EStateMsg, float) override;\n  void TurnAround(CStateManager&, EStateMsg, float) override;\n  void Crouch(CStateManager&, EStateMsg, float) override;\n  void GetUp(CStateManager&, EStateMsg, float) override;\n  void Suck(CStateManager&, EStateMsg, float) override;\n  void Cover(CStateManager&, EStateMsg, float) override;\n  void Approach(CStateManager&, EStateMsg, float) override;\n  void PathFindEx(CStateManager&, EStateMsg, float) override;\n  void Dizzy(CStateManager&, EStateMsg, float) override;\n  bool InAttackPosition(CStateManager&, float) override;\n  bool InRange(CStateManager&, float) override;\n  bool InMaxRange(CStateManager&, float) override;\n  bool InDetectionRange(CStateManager&, float) override;\n  bool SpotPlayer(CStateManager&, float) override;\n  bool InPosition(CStateManager&, float) override;\n  bool HearShot(CStateManager&, float) override;\n  bool CoverBlown(CStateManager&, float) override;\n  bool Inside(CStateManager&, float) override;\n  bool ShouldRetreat(CStateManager&, float) override;\n  bool IsDizzy(CStateManager&, float) override;\n  CPathFindSearch* GetSearchPath() override { return &x568_pathFindSearch; }\n};\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CWarWasp.cpp",
    "content": "#include \"Runtime/MP1/World/CWarWasp.hpp\"\n\n#include <array>\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Character/CCharLayoutInfo.hpp\"\n#include \"Runtime/Weapon/CGameProjectile.hpp\"\n#include \"Runtime/World/CPatternedInfo.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CScriptWaypoint.hpp\"\n#include \"Runtime/World/CTeamAiMgr.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce::MP1 {\nCWarWasp::CWarWasp(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                   CModelData&& mData, const CPatternedInfo& pInfo, CPatterned::EFlavorType flavor,\n                   CPatterned::EColliderType collider, const CDamageInfo& dInfo1, const CActorParameters& actorParms,\n                   CAssetId projectileWeapon, const CDamageInfo& projectileDamage, CAssetId projectileVisorParticle,\n                   u32 projecileVisorSfx)\n: CPatterned(ECharacter::WarWasp, uid, name, flavor, info, xf, std::move(mData), pInfo, EMovementType::Flyer, collider,\n             EBodyType::Flyer, actorParms, EKnockBackVariant::Small)\n, x570_cSphere(zeus::CSphere({0.f, 0.f, 1.8f}, 1.f), x68_material)\n, x590_pfSearch(nullptr, 0x3, pInfo.GetPathfindingIndex(), 1.f, 1.f)\n, x684_(dInfo1)\n, x6d4_projectileInfo(projectileWeapon, projectileDamage)\n, x72c_projectileVisorSfx(CSfxManager::TranslateSFXID(projecileVisorSfx))\n, x72e_26_initiallyInactive(!pInfo.GetActive()) {\n  x6d4_projectileInfo.Token().Lock();\n  UpdateTouchBounds();\n  SetCoefficientOfRestitutionModifier(0.1f);\n  if (projectileVisorParticle.IsValid())\n    x71c_projectileVisorParticle = g_SimplePool->GetObj(SObjectTag{FOURCC('PART'), projectileVisorParticle});\n  x328_29_noPatternShagging = true;\n  x460_knockBackController.SetAnimationStateRange(EKnockBackAnimationState::KnockBack,\n                                                  EKnockBackAnimationState::KnockBack);\n}\n\nvoid CWarWasp::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CWarWasp::SwarmAdd(CStateManager& mgr) {\n  if (x674_aiMgr != kInvalidUniqueId) {\n    if (TCastToPtr<CTeamAiMgr> aimgr = mgr.ObjectById(x674_aiMgr)) {\n      CTeamAiRole::ETeamAiRole role =\n          x3fc_flavor == EFlavorType::Two ? CTeamAiRole::ETeamAiRole::Ranged : CTeamAiRole::ETeamAiRole::Melee;\n      if (!aimgr->IsPartOfTeam(GetUniqueId())) {\n        aimgr->AssignTeamAiRole(*this, role, CTeamAiRole::ETeamAiRole::Invalid, CTeamAiRole::ETeamAiRole::Invalid);\n      }\n    }\n  }\n}\n\nvoid CWarWasp::SwarmRemove(CStateManager& mgr) {\n  if (x674_aiMgr != kInvalidUniqueId) {\n    if (TCastToPtr<CTeamAiMgr> aimgr = mgr.ObjectById(x674_aiMgr)) {\n      if (aimgr->IsPartOfTeam(GetUniqueId())) {\n        aimgr->RemoveTeamAiRole(GetUniqueId());\n      }\n    }\n  }\n}\n\nvoid CWarWasp::ApplyDamage(CStateManager& mgr) {\n  if (x72e_25_canApplyDamage && x450_bodyController->GetCurrentStateId() == pas::EAnimationState::MeleeAttack) {\n    if (mgr.GetPlayer().GetBoundingBox().pointInside(\n            GetTransform() * (GetLocatorTransform(\"LCTR_WARTAIL\"sv).origin * x64_modelData->GetScale()))) {\n      mgr.ApplyDamage(GetUniqueId(), mgr.GetPlayer().GetUniqueId(), GetUniqueId(), GetContactDamage(),\n                      CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), zeus::skZero3f);\n      x72e_25_canApplyDamage = false;\n    }\n  }\n}\n\nvoid CWarWasp::Think(float dt, CStateManager& mgr) {\n  if (!GetActive())\n    return;\n\n  if (x700_attackRemTime > 0.f) {\n    float rate = 1.f;\n    if (mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Unmorphed)\n      rate =\n          1.f - zeus::CVector2f::getAngleDiff(mgr.GetPlayer().GetTransform().basis[1].toVec2f(),\n                                              GetTranslation().toVec2f() - mgr.GetPlayer().GetTranslation().toVec2f()) /\n                    M_PIF * 0.666f;\n    x700_attackRemTime -= rate * dt;\n  }\n\n  ApplyDamage(mgr);\n  CPatterned::Think(dt, mgr);\n}\n\nvoid CWarWasp::SetUpCircleBurstWaypoint(CStateManager& mgr) {\n  for (const auto& conn : GetConnectionList()) {\n    if (conn.x0_state == EScriptObjectState::CloseIn && conn.x4_msg == EScriptObjectMessage::Follow) {\n      if (TCastToConstPtr<CScriptWaypoint> wp = mgr.GetObjectById(mgr.GetIdForScript(conn.x8_objId))) {\n        x6b0_circleBurstPos = wp->GetTranslation();\n        x6bc_circleBurstDir = wp->GetTransform().basis[1];\n        x6c8_circleBurstRight = wp->GetTransform().basis[0];\n        break;\n      }\n    }\n  }\n}\n\nvoid CWarWasp::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) {\n  CPatterned::AcceptScriptMsg(msg, sender, mgr);\n  switch (msg) {\n  case EScriptObjectMessage::Deleted:\n  case EScriptObjectMessage::Deactivate:\n    SwarmRemove(mgr);\n    break;\n  case EScriptObjectMessage::InitializedInArea:\n    if (x674_aiMgr == kInvalidUniqueId)\n      x674_aiMgr = CTeamAiMgr::GetTeamAiMgr(*this, mgr);\n    x590_pfSearch.SetArea(mgr.GetWorld()->GetAreaAlways(GetAreaIdAlways())->GetPostConstructed()->x10bc_pathArea);\n    if (x6b0_circleBurstPos.isZero())\n      SetUpCircleBurstWaypoint(mgr);\n    break;\n  default:\n    break;\n  }\n}\n\nstd::optional<zeus::CAABox> CWarWasp::GetTouchBounds() const { return {x570_cSphere.CalculateAABox(GetTransform())}; }\n\nzeus::CVector3f CWarWasp::GetProjectileAimPos(const CStateManager& mgr, float zBias) const {\n  zeus::CVector3f ret = mgr.GetPlayer().GetAimPosition(mgr, 0.f);\n  if (mgr.GetPlayer().GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Morphed)\n    ret += zeus::CVector3f(0.f, 0.f, zBias);\n  return ret;\n}\n\nvoid CWarWasp::DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) {\n  bool handled = false;\n  switch (type) {\n  case EUserEventType::Projectile: {\n    zeus::CTransform xf = GetLctrTransform(node.GetLocatorName());\n    zeus::CVector3f aimPos = GetProjectileAimPos(mgr, -0.07f);\n    if (mgr.GetPlayer().GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Morphed) {\n      zeus::CVector3f delta = aimPos - xf.origin;\n      if (delta.canBeNormalized()) {\n        EntityList nearList;\n        TUniqueId bestId = kInvalidUniqueId;\n        CRayCastResult res = mgr.RayWorldIntersection(bestId, xf.origin, delta.normalized(), delta.magnitude(),\n                                                      CMaterialFilter::MakeInclude({EMaterialTypes::Solid}), nearList);\n        if (res.IsValid())\n          aimPos = res.GetPoint();\n      }\n    }\n    LaunchProjectile(\n        zeus::lookAt(xf.origin, GetProjectileInfo()->PredictInterceptPos(xf.origin, aimPos, mgr.GetPlayer(), true, dt)),\n        mgr, 4, EProjectileAttrib::None, false, {x71c_projectileVisorParticle}, x72c_projectileVisorSfx, true,\n        zeus::skOne3f);\n    handled = true;\n    break;\n  }\n  case EUserEventType::DeGenerate:\n    SendScriptMsgs(EScriptObjectState::DeactivateState, mgr, EScriptObjectMessage::None);\n    mgr.FreeScriptObject(GetUniqueId());\n    handled = true;\n    break;\n  case EUserEventType::GenerateEnd:\n    AddMaterial(EMaterialTypes::Character, EMaterialTypes::Solid, mgr);\n    handled = true;\n    break;\n  case EUserEventType::DamageOn:\n    x72e_25_canApplyDamage = true;\n    break;\n  case EUserEventType::DamageOff:\n    x72e_25_canApplyDamage = false;\n    break;\n  default:\n    break;\n  }\n  if (!handled)\n    CPatterned::DoUserAnimEvent(mgr, node, type, dt);\n}\n\nconst CCollisionPrimitive* CWarWasp::GetCollisionPrimitive() const { return &x570_cSphere; }\n\nvoid CWarWasp::Death(CStateManager& mgr, const zeus::CVector3f& direction, EScriptObjectState state) {\n  CPatterned::Death(mgr, direction, state);\n  x328_25_verticalMovement = false;\n  AddMaterial(EMaterialTypes::GroundCollider, mgr);\n  SwarmRemove(mgr);\n}\n\nbool CWarWasp::IsListening() const { return true; }\n\nbool CWarWasp::Listen(const zeus::CVector3f& pos, EListenNoiseType type) {\n  switch (type) {\n  case EListenNoiseType::PlayerFire:\n  case EListenNoiseType::BombExplode:\n  case EListenNoiseType::ProjectileExplode:\n    if ((GetTranslation() - pos).magSquared() < x3bc_detectionRange * x3bc_detectionRange) {\n      x72e_31_heardNoise = true;\n      return true;\n    }\n    break;\n  default:\n    break;\n  }\n  return false;\n}\n\nfloat CWarWasp::GetCloseInZBasis(const CStateManager& mgr) const {\n  return mgr.GetPlayer().GetTranslation().z() + mgr.GetPlayer().GetEyeHeight() - 0.5f;\n}\n\nzeus::CVector3f CWarWasp::GetCloseInPos(const CStateManager& mgr, const zeus::CVector3f& aimPos) const {\n  float midRange = (x2fc_minAttackRange + x300_maxAttackRange) * 0.5f;\n  zeus::CVector3f ret;\n  if (mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Unmorphed) {\n    ret = mgr.GetPlayer().GetTransform().basis[1] * midRange + aimPos;\n  } else {\n    zeus::CVector3f delta = GetTranslation() - aimPos;\n    if (delta.canBeNormalized())\n      ret = delta.normalized() * midRange + aimPos;\n    else\n      ret = GetTransform().basis[1] * midRange + aimPos;\n  }\n  ret.z() = 0.5f + GetCloseInZBasis(mgr);\n  return ret;\n}\n\nzeus::CVector3f CWarWasp::GetOrigin(const CStateManager& mgr, const CTeamAiRole& role,\n                                    const zeus::CVector3f& aimPos) const {\n  if (x6b0_circleBurstPos.isZero())\n    return GetCloseInPos(mgr, aimPos);\n  else\n    return GetTranslation();\n}\n\nvoid CWarWasp::UpdateTouchBounds() {\n  zeus::CAABox aabb = x64_modelData->GetAnimationData()->GetBoundingBox();\n  x570_cSphere.SetSphereCenter(aabb.center());\n  SetBoundingBox(aabb.getTransformedAABox(zeus::CTransform(GetTransform().basis)));\n}\n\nvoid CWarWasp::Patrol(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate: {\n    float maxSpeed = x450_bodyController->GetBodyStateInfo().GetMaxSpeed();\n    if (maxSpeed > 0.f) {\n      x450_bodyController->GetCommandMgr().SetSteeringBlendMode(ESteeringBlendMode::FullSpeed);\n      float speedFactor =\n          x450_bodyController->GetBodyStateInfo().GetLocomotionSpeed(pas::ELocomotionAnim::Walk) * 0.9f / maxSpeed;\n      x450_bodyController->GetCommandMgr().SetSteeringSpeedRange(speedFactor, speedFactor);\n    }\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n    x72e_31_heardNoise = false;\n    break;\n  }\n  case EStateMsg::Deactivate:\n    x450_bodyController->GetCommandMgr().SetSteeringBlendMode(ESteeringBlendMode::Normal);\n    break;\n  default:\n    break;\n  }\n  CPatterned::Patrol(mgr, msg, dt);\n}\n\nvoid CWarWasp::SetUpPathFindBehavior(CStateManager& mgr) {\n  x72e_29_pathObstructed = false;\n  if (GetSearchPath()) {\n    SwarmAdd(mgr);\n    zeus::CVector3f pos;\n    if (CTeamAiRole* role = CTeamAiMgr::GetTeamAiRole(mgr, x674_aiMgr, GetUniqueId()))\n      pos = role->GetTeamPosition();\n    else\n      pos = GetCloseInPos(mgr, GetProjectileAimPos(mgr, -1.25f));\n    SetDestPos(pos);\n    if ((x2e0_destPos - GetTranslation()).magSquared() > 64.f ||\n        IsPatternObstructed(mgr, GetTranslation(), x2e0_destPos)) {\n      zeus::CVector3f aimPos = GetProjectileAimPos(mgr, -1.25f);\n      zeus::CVector3f delta = x2e0_destPos - aimPos;\n      if (delta.canBeNormalized()) {\n        zeus::CVector3f dir = delta.normalized();\n        CRayCastResult res = mgr.RayStaticIntersection(\n            aimPos, dir, delta.magnitude(),\n            CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {EMaterialTypes::Player}));\n        if (res.IsValid()) {\n          SetDestPos(dir * res.GetT() * 0.5f + aimPos);\n          x72e_29_pathObstructed = true;\n        }\n      }\n      CPatterned::PathFind(mgr, EStateMsg::Activate, 0.f);\n    }\n  }\n}\n\nvoid CWarWasp::ApplyNormalSteering(CStateManager& mgr) {\n  zeus::CVector3f delta = mgr.GetPlayer().GetTranslation() - GetTranslation();\n  zeus::CVector3f teamPos;\n  if (CTeamAiRole* role = CTeamAiMgr::GetTeamAiRole(mgr, x674_aiMgr, GetUniqueId()))\n    teamPos = role->GetTeamPosition();\n  else\n    teamPos = GetProjectileAimPos(mgr, -1.25f);\n  zeus::CVector2f toTeamPos2d = (teamPos - GetTranslation()).toVec2f();\n  float toTeamPosH = teamPos.z() - GetTranslation().z();\n  if (toTeamPos2d.magSquared() > 1.f || std::fabs(toTeamPosH) > 2.5f) {\n    pas::EStepDirection stepDir = GetStepDirection(toTeamPos2d);\n    if (stepDir != pas::EStepDirection::Forward) {\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBCStepCmd(stepDir, pas::EStepType::Normal));\n    } else {\n      x450_bodyController->GetCommandMgr().DeliverCmd(\n          CBCLocomotionCmd(x45c_steeringBehaviors.Arrival(*this, teamPos, 3.f), zeus::skZero3f, 1.f));\n      zeus::CVector3f target = GetTranslation();\n      target.z() = float(teamPos.z());\n      zeus::CVector3f moveVec = x45c_steeringBehaviors.Arrival(*this, target, 2.5f);\n      if (moveVec.magSquared() > 0.01f) {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(moveVec, zeus::skZero3f, 3.f));\n      }\n    }\n  } else {\n    switch (mgr.GetActiveRandom()->Range(0, 2)) {\n    case 0:\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBCStepCmd(pas::EStepDirection::Left, pas::EStepType::Normal));\n      break;\n    case 1:\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBCStepCmd(pas::EStepDirection::Right, pas::EStepType::Normal));\n      break;\n    case 2:\n      if (ShouldTurn(mgr, 30.f) && delta.canBeNormalized()) {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(zeus::skZero3f, delta.normalized(), 1.f));\n      }\n      break;\n    default:\n      break;\n    }\n  }\n  x450_bodyController->GetCommandMgr().DeliverTargetVector(delta);\n}\n\nvoid CWarWasp::ApplySeparationBehavior(CStateManager& mgr, float sep) {\n  for (CEntity* ent : mgr.GetListeningAiObjectList()) {\n    if (TCastToPtr<CPatterned> ai = ent) {\n      if (ai.GetPtr() != this && ai->GetAreaIdAlways() == GetAreaIdAlways()) {\n        float useSep = sep;\n        if (CTeamAiRole* role = CTeamAiMgr::GetTeamAiRole(mgr, x674_aiMgr, ai->GetUniqueId())) {\n          if (role->GetTeamAiRole() == CTeamAiRole::ETeamAiRole::Melee ||\n              role->GetTeamAiRole() == CTeamAiRole::ETeamAiRole::Ranged)\n            useSep *= 2.f;\n        }\n        zeus::CVector3f separation = x45c_steeringBehaviors.Separation(*this, ai->GetTranslation(), useSep);\n        if (!separation.isZero()) {\n          x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(separation, zeus::skZero3f, 1.f));\n        }\n      }\n    }\n  }\n}\n\nbool CWarWasp::PathToHiveIsClear(CStateManager& mgr) const {\n  zeus::CVector3f delta = x3a0_latestLeashPosition - GetTranslation();\n  if (GetTransform().basis[1].dot(delta) > 0.f) {\n    zeus::CAABox aabb(GetTranslation() - 10.f, GetTranslation() + 10.f);\n    EntityList nearList;\n    mgr.BuildNearList(nearList, aabb, CMaterialFilter::MakeInclude({EMaterialTypes::Character}), nullptr);\n    float deltaMagSq = delta.magSquared();\n    for (TUniqueId id : nearList) {\n      if (const CWarWasp* other = CPatterned::CastTo<CWarWasp>(mgr.GetObjectById(id))) {\n        if (other->GetUniqueId() != GetUniqueId() && other->x72e_30_isRetreating &&\n            zeus::close_enough(other->x3a0_latestLeashPosition, x3a0_latestLeashPosition, 3.f)) {\n          zeus::CVector3f waspDelta = other->GetTranslation() - GetTranslation();\n          if (GetTransform().basis[1].dot(waspDelta) > 0.f && waspDelta.magSquared() < 3.f &&\n              (other->GetTranslation() - x3a0_latestLeashPosition).magSquared() < deltaMagSq) {\n            return false;\n          }\n        }\n      }\n    }\n  }\n  return true;\n}\n\nbool CWarWasp::SteerToDeactivatePos(CStateManager& mgr, EStateMsg msg, float dt) {\n  float distSq = (x3a0_latestLeashPosition - GetTranslation()).magSquared();\n  if (distSq > 1.f + x570_cSphere.GetSphere().radius) {\n    if (PathToHiveIsClear(mgr)) {\n      zeus::CVector3f arrival1 = x45c_steeringBehaviors.Arrival(*this, x3a0_latestLeashPosition, 15.f);\n      float maxSpeed = x450_bodyController->GetBodyStateInfo().GetMaxSpeed();\n      float minMoveFactor;\n      if (maxSpeed > 0.f)\n        minMoveFactor =\n            x450_bodyController->GetBodyStateInfo().GetLocomotionSpeed(pas::ELocomotionAnim::Walk) * 0.5f / maxSpeed;\n      else\n        minMoveFactor = 1.f;\n      float moveFactor = zeus::clamp(minMoveFactor, arrival1.magnitude(), 1.f);\n      zeus::CVector3f moveVec;\n      if (arrival1.canBeNormalized())\n        moveVec = arrival1.normalized() * moveFactor;\n      else\n        moveVec = GetTransform().basis[1] * moveFactor;\n      x450_bodyController->GetCommandMgr().SetSteeringBlendMode(ESteeringBlendMode::FullSpeed);\n      x450_bodyController->GetCommandMgr().SetSteeringSpeedRange(moveFactor, moveFactor);\n      if (distSq > 64.f + x570_cSphere.GetSphere().radius) {\n        if (GetSearchPath() && !PathShagged(mgr, 0.f) && !GetSearchPath()->IsOver()) {\n          CPatterned::PathFind(mgr, msg, dt);\n        } else {\n          x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(moveVec, zeus::skZero3f, 1.f));\n        }\n      } else {\n        RemoveMaterial(EMaterialTypes::Solid, mgr);\n        zeus::CVector3f target = GetTranslation();\n        target.z() = float(x3a0_latestLeashPosition.z());\n        zeus::CVector3f arrival2 = x45c_steeringBehaviors.Arrival(*this, target, 2.5f);\n        if (arrival2.magSquared() > 0.01f) {\n          x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(arrival2, zeus::skZero3f, 3.f));\n        }\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(moveVec, zeus::skZero3f, 1.f));\n      }\n    }\n    return false;\n  } else if (distSq > 0.01f) {\n    RemoveMaterial(EMaterialTypes::Solid, mgr);\n    zeus::CQuaternion q;\n    q.rotateZ(zeus::degToRad(180.f));\n    SetTranslation(GetTranslation() * 0.9f + x3a0_latestLeashPosition * 0.1f);\n    SetTransform(zeus::CQuaternion::slerpShort(zeus::CQuaternion(GetTransform().basis), x6a0_initialRot * q, 0.1f)\n                     .normalized()\n                     .toTransform(GetTranslation()));\n    return false;\n  }\n  return true;\n}\n\nvoid CWarWasp::PathFind(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    SetUpPathFindBehavior(mgr);\n    break;\n  case EStateMsg::Update: {\n    if (mgr.GetPlayer().GetOrbitState() != CPlayer::EPlayerOrbitState::NoOrbit &&\n        mgr.GetPlayer().GetOrbitTargetId() == GetUniqueId())\n      x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n    else\n      x450_bodyController->SetLocomotionType(pas::ELocomotionType::Lurk);\n    if (GetSearchPath() && !PathShagged(mgr, 0.f) && !GetSearchPath()->IsOver()) {\n      CPatterned::PathFind(mgr, msg, dt);\n    } else {\n      ApplyNormalSteering(mgr);\n    }\n    ApplySeparationBehavior(mgr, 9.f);\n    float distTest = 2.f * x300_maxAttackRange;\n    if ((mgr.GetPlayer().GetTranslation() - GetTranslation()).magSquared() > distTest * distTest) {\n      x450_bodyController->GetCommandMgr().SetSteeringBlendMode(ESteeringBlendMode::FullSpeed);\n      x450_bodyController->GetCommandMgr().SetSteeringSpeedRange(1.f, 1.f);\n    } else {\n      x450_bodyController->GetCommandMgr().SetSteeringBlendMode(ESteeringBlendMode::Normal);\n    }\n    break;\n  }\n  case EStateMsg::Deactivate:\n    x450_bodyController->GetCommandMgr().SetSteeringBlendMode(ESteeringBlendMode::Normal);\n    break;\n  }\n}\n\nvoid CWarWasp::TargetPatrol(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n    SwarmRemove(mgr);\n    CPatterned::Patrol(mgr, msg, dt);\n    CPatterned::UpdateDest(mgr);\n    x678_targetPos = x2e0_destPos;\n    if (GetSearchPath())\n      CPatterned::PathFind(mgr, msg, dt);\n    break;\n  case EStateMsg::Update:\n    if (GetSearchPath() && !PathShagged(mgr, 0.f))\n      CPatterned::PathFind(mgr, msg, dt);\n    else\n      CPatterned::Patrol(mgr, msg, dt);\n    ApplySeparationBehavior(mgr, 9.f);\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CWarWasp::Generate(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x450_bodyController->Activate(mgr);\n    if (x72e_26_initiallyInactive) {\n      RemoveMaterial(EMaterialTypes::Character, EMaterialTypes::Solid, mgr);\n      x6a0_initialRot = zeus::CQuaternion(GetTransform().basis);\n      zeus::CQuaternion q;\n      q.rotateZ(mgr.GetActiveRandom()->Float() * zeus::degToRad(45.f) - zeus::degToRad(22.5f));\n      SetTransform((x6a0_initialRot * q).normalized().toTransform(GetTranslation()));\n      x568_stateProg = 0;\n    } else {\n      x568_stateProg = 3;\n    }\n    break;\n  case EStateMsg::Update:\n    switch (x568_stateProg) {\n    case 0:\n      if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::Generate) {\n        x568_stateProg = 2;\n      } else {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCGenerateCmd(pas::EGenerateType::Zero, x388_anim));\n      }\n      break;\n    case 2:\n      if (x450_bodyController->GetCurrentStateId() != pas::EAnimationState::Generate) {\n        x568_stateProg = 3;\n      } else {\n        zeus::CVector3f moveVec;\n        for (CEntity* ent : mgr.GetListeningAiObjectList())\n          if (TCastToPtr<CPatterned> act = ent)\n            if (act.GetPtr() != this && act->GetAreaIdAlways() == GetAreaIdAlways())\n              moveVec += x45c_steeringBehaviors.Separation(*this, act->GetTranslation(), 5.f) * (5.f * dt);\n        if (!moveVec.isZero())\n          ApplyImpulseWR(GetMoveToORImpulseWR(moveVec, dt), {});\n      }\n      break;\n    default:\n      break;\n    }\n    break;\n  case EStateMsg::Deactivate:\n    if (x72e_26_initiallyInactive) {\n      AddMaterial(EMaterialTypes::Character, EMaterialTypes::Solid, mgr);\n      if (x328_26_solidCollision)\n        x401_30_pendingDeath = true;\n    }\n    break;\n  }\n}\n\nvoid CWarWasp::Deactivate(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x568_stateProg = 1;\n    x72e_30_isRetreating = true;\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n    SwarmRemove(mgr);\n    x674_aiMgr = kInvalidUniqueId;\n    x678_targetPos = x3a0_latestLeashPosition;\n    SetDestPos(x678_targetPos);\n    if (GetSearchPath())\n      CPatterned::PathFind(mgr, msg, dt);\n    break;\n  case EStateMsg::Update:\n    switch (x568_stateProg) {\n    case 1:\n      if (SteerToDeactivatePos(mgr, msg, dt)) {\n        RemoveMaterial(EMaterialTypes::Solid, mgr);\n        x568_stateProg = 0;\n      }\n      break;\n    case 0:\n      if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::Generate) {\n        RemoveMaterial(EMaterialTypes::Character, EMaterialTypes::Target, EMaterialTypes::Orbit, mgr);\n        mgr.GetPlayer().TryToBreakOrbit(GetUniqueId(), CPlayer::EPlayerOrbitRequest::ActivateOrbitSource, mgr);\n        x568_stateProg = 2;\n      } else {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCGenerateCmd(pas::EGenerateType::One));\n      }\n      break;\n    case 2:\n      if (x450_bodyController->GetCurrentStateId() != pas::EAnimationState::Generate)\n        x568_stateProg = 3;\n      break;\n    default:\n      break;\n    }\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CWarWasp::Attack(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x72e_27_teamMatesMelee = true;\n    x72e_25_canApplyDamage = false;\n    if (x674_aiMgr != kInvalidUniqueId)\n      x568_stateProg = CTeamAiMgr::AddAttacker(CTeamAiMgr::EAttackType::Melee, mgr, x674_aiMgr, GetUniqueId()) ? 0 : 3;\n    else\n      x568_stateProg = 0;\n    break;\n  case EStateMsg::Update:\n    switch (x568_stateProg) {\n    case 0:\n      if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::MeleeAttack) {\n        x568_stateProg = 2;\n      } else {\n        zeus::CVector3f aimPos = GetProjectileAimPos(mgr, -1.25f);\n        zeus::CVector3f aimDelta = aimPos - GetTranslation();\n        if (aimDelta.canBeNormalized())\n          aimPos += aimDelta.normalized() * 7.5f;\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCMeleeAttackCmd(pas::ESeverity::One, aimPos));\n      }\n      break;\n    case 2:\n      if (x450_bodyController->GetCurrentStateId() != pas::EAnimationState::MeleeAttack) {\n        x568_stateProg = 3;\n      } else {\n        x450_bodyController->GetCommandMgr().DeliverTargetVector(mgr.GetPlayer().GetTranslation() - GetTranslation());\n      }\n      break;\n    default:\n      break;\n    }\n    break;\n  case EStateMsg::Deactivate:\n    CTeamAiMgr::ResetTeamAiRole(CTeamAiMgr::EAttackType::Melee, mgr, x674_aiMgr, GetUniqueId(), false);\n    x700_attackRemTime = CalcTimeToNextAttack(mgr);\n    x72e_27_teamMatesMelee = false;\n    break;\n  }\n}\n\nvoid CWarWasp::JumpBack(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x568_stateProg = 0;\n    x72e_24_jumpBackRepeat = true;\n    SwarmRemove(mgr);\n    break;\n  case EStateMsg::Update:\n    switch (x568_stateProg) {\n    case 0:\n      if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::Step) {\n        x568_stateProg = 2;\n      } else {\n        x450_bodyController->GetCommandMgr().DeliverCmd(\n            CBCStepCmd(pas::EStepDirection::Backward, pas::EStepType::Normal));\n      }\n      break;\n    case 2:\n      if (x450_bodyController->GetCurrentStateId() != pas::EAnimationState::Step) {\n        x568_stateProg = x72e_24_jumpBackRepeat ? 0 : 3;\n        x72e_24_jumpBackRepeat = false;\n      } else {\n        x450_bodyController->GetCommandMgr().DeliverTargetVector(mgr.GetPlayer().GetTranslation() - GetTranslation());\n      }\n      break;\n    default:\n      break;\n    }\n    break;\n  default:\n    break;\n  }\n}\n\nzeus::CVector3f CWarWasp::CalcShuffleDest(const CStateManager& mgr) const {\n  zeus::CVector2f aimPos2d = GetProjectileAimPos(mgr, -1.25f).toVec2f();\n  zeus::CVector3f playerDir2d = mgr.GetPlayer().GetTransform().basis[1];\n  playerDir2d.z() = 0.f;\n  zeus::CVector3f useDir;\n  if (playerDir2d.canBeNormalized())\n    useDir = playerDir2d.normalized();\n  else\n    useDir = zeus::skForward;\n  aimPos2d += useDir.toVec2f() * (7.5f + x300_maxAttackRange);\n  zeus::CVector3f ret(aimPos2d);\n  ret.z() = GetCloseInZBasis(mgr);\n  return ret;\n}\n\nvoid CWarWasp::Shuffle(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Lurk);\n    x568_stateProg = 2;\n    break;\n  case EStateMsg::Update: {\n    s32 numRoles = 0;\n    if (TCastToConstPtr<CTeamAiMgr> aiMgr = mgr.GetObjectById(x674_aiMgr))\n      numRoles = aiMgr->GetNumAssignedAiRoles();\n    if (numRoles && x700_attackRemTime > 0.f) {\n      zeus::CVector3f shuffleDest = CalcShuffleDest(mgr);\n      float zDelta = shuffleDest.z() - GetTranslation().z();\n      if (zDelta * zDelta > 1.f) {\n        zeus::CVector3f dest = GetTranslation();\n        dest.z() = float(shuffleDest.z());\n        zeus::CVector3f moveVec = x45c_steeringBehaviors.Arrival(*this, dest, 1.f);\n        if (moveVec.magSquared() > 0.01f)\n          x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(moveVec, zeus::skZero3f, 1.f));\n      } else {\n        zeus::CVector3f moveVec = shuffleDest - GetTranslation();\n        moveVec.z() = zDelta;\n        if (moveVec.magSquared() > 64.f) {\n          pas::EStepDirection stepDir = CPatterned::GetStepDirection(moveVec);\n          if (stepDir != pas::EStepDirection::Forward) {\n            x450_bodyController->GetCommandMgr().DeliverCmd(CBCStepCmd(stepDir, pas::EStepType::Normal));\n          } else {\n            x450_bodyController->GetCommandMgr().DeliverCmd(\n                CBCLocomotionCmd(x45c_steeringBehaviors.Seek(*this, shuffleDest), zeus::skZero3f, 1.f));\n            ApplySeparationBehavior(mgr, 15.f);\n          }\n        } else {\n          x568_stateProg = 3;\n        }\n      }\n      x450_bodyController->GetCommandMgr().DeliverTargetVector(mgr.GetPlayer().GetTranslation() - GetTranslation());\n    } else {\n      x568_stateProg = 3;\n    }\n    break;\n  }\n  case EStateMsg::Deactivate:\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n    break;\n  }\n}\n\nvoid CWarWasp::ProjectileAttack(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x72e_28_inProjectileAttack = true;\n    if (x674_aiMgr != kInvalidUniqueId) {\n      x568_stateProg = CTeamAiMgr::AddAttacker(CTeamAiMgr::EAttackType::Ranged, mgr, x674_aiMgr, GetUniqueId()) ? 0 : 3;\n    } else {\n      x568_stateProg = 0;\n    }\n    break;\n  case EStateMsg::Update:\n    switch (x568_stateProg) {\n    case 0:\n      if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::ProjectileAttack) {\n        x568_stateProg = 2;\n      } else {\n        SetDestPos(GetProjectileAimPos(mgr, -0.07f));\n        x450_bodyController->GetCommandMgr().DeliverCmd(\n            CBCProjectileAttackCmd(pas::ESeverity::One, x2e0_destPos, false));\n      }\n      break;\n    case 2:\n      if (x450_bodyController->GetCurrentStateId() != pas::EAnimationState::ProjectileAttack) {\n        x450_bodyController->GetCommandMgr().DeliverTargetVector(x2e0_destPos - GetTranslation());\n        x568_stateProg = 3;\n      }\n      break;\n    default:\n      break;\n    }\n    break;\n  case EStateMsg::Deactivate:\n    CTeamAiMgr::ResetTeamAiRole(CTeamAiMgr::EAttackType::Ranged, mgr, x674_aiMgr, GetUniqueId(), false);\n    x700_attackRemTime = CalcTimeToNextAttack(mgr);\n    x72e_28_inProjectileAttack = false;\n    break;\n  }\n}\n\ns32 CWarWasp::GetAttackTeamSize(const CStateManager& mgr, s32 team) const {\n  s32 count = 0;\n  if (TCastToConstPtr<CTeamAiMgr> aimgr = mgr.GetObjectById(x674_aiMgr)) {\n    if (aimgr->IsPartOfTeam(GetUniqueId())) {\n      for (const CTeamAiRole& role : aimgr->GetRoles()) {\n        if (const CWarWasp* other = CPatterned::CastTo<CWarWasp>(mgr.GetObjectById(role.GetOwnerId()))) {\n          if (team == other->x708_circleAttackTeam)\n            ++count;\n        }\n      }\n    }\n  }\n  return count;\n}\n\nfloat CWarWasp::CalcTimeToNextAttack(CStateManager& mgr) const {\n  float mul = 1.f;\n  if (TCastToConstPtr<CTeamAiMgr> aimgr = mgr.GetObjectById(x674_aiMgr)) {\n    const s32 maxCount =\n        (x3fc_flavor == EFlavorType::Two) ? aimgr->GetMaxRangedAttackerCount() : aimgr->GetMaxMeleeAttackerCount();\n    const s32 count = (x3fc_flavor == EFlavorType::Two) ? aimgr->GetNumAssignedOfRole(CTeamAiRole::ETeamAiRole::Ranged)\n                                                        : aimgr->GetNumAssignedOfRole(CTeamAiRole::ETeamAiRole::Melee);\n    if (count <= maxCount)\n      mul *= 0.5f;\n  }\n  return (mgr.GetActiveRandom()->Float() * x308_attackTimeVariation + x304_averageAttackTime) * mul;\n}\n\nfloat CWarWasp::CalcOffTotemAngle(CStateManager& mgr) const {\n  return mgr.GetActiveRandom()->Float() * zeus::degToRad(80.f) + zeus::degToRad(10.f);\n}\n\nvoid CWarWasp::JoinCircleAttackTeam(s32 unit, CStateManager& mgr) {\n  if (!x6b0_circleBurstPos.isZero()) {\n    if (x70c_initialCircleAttackTeam == -1) {\n      x710_initialCircleAttackTeamUnit = GetAttackTeamSize(mgr, unit);\n      x70c_initialCircleAttackTeam = unit;\n    }\n    x708_circleAttackTeam = unit;\n    x700_attackRemTime = CalcTimeToNextAttack(mgr);\n    x718_circleBurstOffTotemAngle = CalcOffTotemAngle(mgr);\n  }\n}\n\nvoid CWarWasp::SetUpCircleTelegraphTeam(CStateManager& mgr) {\n  if (x708_circleAttackTeam == -1) {\n    if (TCastToConstPtr<CTeamAiMgr> aimgr = mgr.GetObjectById(x674_aiMgr)) {\n      if (aimgr->IsPartOfTeam(GetUniqueId()) && aimgr->GetMaxMeleeAttackerCount() > 0) {\n        s32 teamUnit = 0;\n        s32 targetUnitSize = 0;\n        bool rejoinInitial = false;\n        for (const CTeamAiRole& role : aimgr->GetRoles()) {\n          if (const CWarWasp* other = CPatterned::CastTo<CWarWasp>(mgr.GetObjectById(role.GetOwnerId()))) {\n            if (x70c_initialCircleAttackTeam != -1 &&\n                other->x70c_initialCircleAttackTeam == x70c_initialCircleAttackTeam &&\n                other->x708_circleAttackTeam >= 0) {\n              teamUnit = other->x708_circleAttackTeam;\n              rejoinInitial = true;\n              break;\n            }\n            if (other->x708_circleAttackTeam > teamUnit) {\n              teamUnit = other->x708_circleAttackTeam;\n              targetUnitSize = 1;\n            } else if (other->x708_circleAttackTeam == teamUnit) {\n              ++targetUnitSize;\n            }\n          }\n        }\n        if (!rejoinInitial &&\n            (x70c_initialCircleAttackTeam != -1 || targetUnitSize >= aimgr->GetMaxMeleeAttackerCount()))\n          ++teamUnit;\n        JoinCircleAttackTeam(teamUnit, mgr);\n        x714_circleTelegraphSeekHeight = mgr.GetActiveRandom()->Float() * -0.5f;\n      }\n    }\n  }\n}\n\nTUniqueId CWarWasp::GetAttackTeamLeader(const CStateManager& mgr, s32 team) const {\n  if (TCastToConstPtr<CTeamAiMgr> aimgr = mgr.GetObjectById(x674_aiMgr)) {\n    if (aimgr->IsPartOfTeam(GetUniqueId())) {\n      for (const CTeamAiRole& role : aimgr->GetRoles()) {\n        if (const CWarWasp* other = CPatterned::CastTo<CWarWasp>(mgr.GetObjectById(role.GetOwnerId()))) {\n          if (team == other->x708_circleAttackTeam)\n            return role.GetOwnerId();\n        }\n      }\n    }\n  }\n  return kInvalidUniqueId;\n}\n\nvoid CWarWasp::TryCircleTeamMerge(CStateManager& mgr) {\n  if (x708_circleAttackTeam > 0) {\n    if (TCastToConstPtr<CTeamAiMgr> aimgr = mgr.GetObjectById(x674_aiMgr)) {\n      if (aimgr->IsPartOfTeam(GetUniqueId())) {\n        if (GetAttackTeamLeader(mgr, x708_circleAttackTeam) == GetUniqueId()) {\n          if (GetAttackTeamSize(mgr, x708_circleAttackTeam - 1) == 0) {\n            for (const CTeamAiRole& role : aimgr->GetRoles()) {\n              if (const CWarWasp* other = CPatterned::CastTo<CWarWasp>(mgr.GetObjectById(role.GetOwnerId()))) {\n                if (x708_circleAttackTeam == other->x708_circleAttackTeam)\n                  JoinCircleAttackTeam(x708_circleAttackTeam - 1, mgr);\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n}\n\nfloat CWarWasp::GetTeamZStratum(s32 team) const {\n  if (team > 0) {\n    if ((team & 1) == 1)\n      return -3.f - float(team / 2) * 3.f;\n    else\n      return float(team / 2) * 3.f;\n  }\n  return 0.f;\n}\n\nfloat CWarWasp::CalcSeekMagnitude(const CStateManager& mgr) const {\n  static constexpr std::array Table{0.4f, 0.6f, 1.f};\n\n  const float ret =\n      ((x708_circleAttackTeam >= 0 && x708_circleAttackTeam < 3) ? Table[x708_circleAttackTeam] : 1.f) * 0.9f;\n  if (TCastToConstPtr<CTeamAiMgr> aimgr = mgr.GetObjectById(x674_aiMgr)) {\n    if (aimgr->IsPartOfTeam(GetUniqueId())) {\n      if (aimgr->GetMaxMeleeAttackerCount() > 1) {\n        if (GetAttackTeamLeader(mgr, x708_circleAttackTeam) != GetUniqueId()) {\n          const zeus::CVector3f fromPlatformCenter = GetTranslation() - x6b0_circleBurstPos;\n          float minAngle = zeus::degToRad(360.f);\n          for (const CTeamAiRole& role : aimgr->GetRoles()) {\n            if (const CWarWasp* other = CPatterned::CastTo<CWarWasp>(mgr.GetObjectById(role.GetOwnerId()))) {\n              if (x708_circleAttackTeam == other->x708_circleAttackTeam &&\n                  GetTransform().basis[1].dot(other->GetTranslation() - GetTranslation()) > 0.f) {\n                const float angle =\n                    zeus::CVector3f::getAngleDiff(fromPlatformCenter, other->GetTranslation() - x6b0_circleBurstPos);\n                if (angle < minAngle)\n                  minAngle = angle;\n              }\n            }\n          }\n          if (minAngle < zeus::degToRad(30.f))\n            return 0.8f;\n          if (minAngle > zeus::degToRad(50.f))\n            return 1.f;\n        }\n      }\n    }\n  }\n  return ret;\n}\n\nvoid CWarWasp::UpdateTelegraphMoveSpeed(CStateManager& mgr) {\n  TUniqueId leaderId = GetAttackTeamLeader(mgr, x708_circleAttackTeam);\n  if (const CWarWasp* other = CPatterned::CastTo<CWarWasp>(mgr.GetObjectById(leaderId))) {\n    if (leaderId == GetUniqueId()) {\n      float cycleTime = x330_stateMachineState.GetTime() - std::trunc(x330_stateMachineState.GetTime() / 2.8f) * 2.8f;\n      if (cycleTime < 2.f) {\n        x3b4_speed = x6fc_initialSpeed;\n      } else {\n        float t = (cycleTime - 2.f) / 0.8f;\n        x3b4_speed = ((1.f - t) * 0.7f + 2.f * t) * x6fc_initialSpeed;\n      }\n    } else {\n      x3b4_speed = other->x3b4_speed;\n    }\n  } else {\n    x3b4_speed = x6fc_initialSpeed;\n  }\n}\n\nvoid CWarWasp::TelegraphAttack(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Combat);\n    RemoveMaterial(EMaterialTypes::Orbit, mgr);\n    mgr.GetPlayer().TryToBreakOrbit(GetUniqueId(), CPlayer::EPlayerOrbitRequest::ActivateOrbitSource, mgr);\n    SwarmAdd(mgr);\n    SetUpCircleTelegraphTeam(mgr);\n    break;\n  case EStateMsg::Update:\n    if (!x6b0_circleBurstPos.isZero()) {\n      TryCircleTeamMerge(mgr);\n      zeus::CVector3f closeInDelta = GetTranslation() - x6b0_circleBurstPos;\n      closeInDelta.z() = 0.f;\n      zeus::CVector3f moveVec = GetTransform().basis[1];\n      if (closeInDelta.canBeNormalized()) {\n        zeus::CVector3f closeInDeltaNorm = closeInDelta.normalized();\n        moveVec = closeInDeltaNorm.cross(zeus::skUp);\n        zeus::CVector3f seekOrigin = x6b0_circleBurstPos + closeInDeltaNorm * x2fc_minAttackRange;\n        if (x708_circleAttackTeam > 0)\n          moveVec = moveVec * -1.f;\n        float seekHeight = x714_circleTelegraphSeekHeight + GetTeamZStratum(x708_circleAttackTeam);\n        float seekMag = CalcSeekMagnitude(mgr);\n        moveVec =\n            x45c_steeringBehaviors.Seek(*this, seekOrigin + moveVec * 5.f + zeus::CVector3f(0.f, 0.f, seekHeight)) *\n            seekMag;\n      }\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(moveVec, zeus::skZero3f, 1.f));\n      UpdateTelegraphMoveSpeed(mgr);\n    }\n    break;\n  case EStateMsg::Deactivate:\n    AddMaterial(EMaterialTypes::Orbit, mgr);\n    break;\n  }\n}\n\nvoid CWarWasp::Dodge(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    if (x704_dodgeDir != pas::EStepDirection::Invalid) {\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBCStepCmd(x704_dodgeDir, pas::EStepType::Dodge));\n      x568_stateProg = 2;\n    }\n    break;\n  case EStateMsg::Update:\n    if (x450_bodyController->GetCurrentStateId() != pas::EAnimationState::Step) {\n      x568_stateProg = 3;\n    } else {\n      x450_bodyController->GetCommandMgr().DeliverTargetVector(mgr.GetPlayer().GetTranslation() - GetTranslation());\n    }\n    break;\n  case EStateMsg::Deactivate:\n    x704_dodgeDir = pas::EStepDirection::Invalid;\n    break;\n  }\n}\n\nvoid CWarWasp::Retreat(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    SwarmRemove(mgr);\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Internal5);\n    break;\n  case EStateMsg::Update:\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(\n        x45c_steeringBehaviors.Flee2D(*this, mgr.GetPlayer().GetTranslation().toVec2f()), zeus::skZero3f, 1.f));\n    break;\n  case EStateMsg::Deactivate:\n    x400_24_hitByPlayerProjectile = false;\n    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n    break;\n  }\n}\n\nvoid CWarWasp::SpecialAttack(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x72e_27_teamMatesMelee = true;\n    x72e_25_canApplyDamage = true;\n    x568_stateProg = 0;\n    x3b4_speed = x6fc_initialSpeed;\n    break;\n  case EStateMsg::Update:\n    switch (x568_stateProg) {\n    case 0:\n      if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::MeleeAttack) {\n        x568_stateProg = 2;\n      } else {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCMeleeAttackCmd(pas::ESeverity::Eight));\n      }\n      break;\n    case 2:\n      if (x450_bodyController->GetCurrentStateId() != pas::EAnimationState::MeleeAttack) {\n        x568_stateProg = 3;\n      } else if (GetTransform().basis[1].dot(x6b0_circleBurstPos - GetTranslation()) > 0.f) {\n        x450_bodyController->GetCommandMgr().DeliverTargetVector(x6b0_circleBurstPos - GetTranslation());\n      }\n      break;\n    default:\n      break;\n    }\n    break;\n  case EStateMsg::Deactivate:\n    CTeamAiMgr::ResetTeamAiRole(CTeamAiMgr::EAttackType::Melee, mgr, x674_aiMgr, GetUniqueId(), false);\n    x72e_27_teamMatesMelee = false;\n    x708_circleAttackTeam = -1;\n    x718_circleBurstOffTotemAngle = CalcOffTotemAngle(mgr);\n    break;\n  }\n}\n\nbool CWarWasp::InAttackPosition(CStateManager& mgr, float arg) {\n  zeus::CVector3f delta = GetProjectileAimPos(mgr, -1.25f) - GetTranslation();\n  float negTest = x2fc_minAttackRange - 5.f;\n  float posTest = 5.f + x300_maxAttackRange;\n  float magSq = delta.magSquared();\n  bool ret = magSq > negTest * negTest && magSq < posTest * posTest && !ShouldTurn(mgr, 45.f);\n  if (ret && delta.canBeNormalized()) {\n    float deltaMag = delta.magnitude();\n    ret = mgr.RayStaticIntersection(\n                 GetTranslation(), delta / deltaMag, deltaMag,\n                 CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {EMaterialTypes::Player}))\n              .IsInvalid();\n  }\n  return ret;\n}\n\nbool CWarWasp::Leash(CStateManager& mgr, float arg) {\n  if ((x3a0_latestLeashPosition - GetTranslation()).magSquared() > x3c8_leashRadius * x3c8_leashRadius) {\n    if ((mgr.GetPlayer().GetTranslation() - GetTranslation()).magSquared() >\n            x3cc_playerLeashRadius * x3cc_playerLeashRadius &&\n        x3d4_curPlayerLeashTime > x3d0_playerLeashTime) {\n      return true;\n    }\n  }\n  return false;\n}\n\nbool CWarWasp::PathShagged(CStateManager& mgr, float arg) {\n  if (CPathFindSearch* pf = GetSearchPath())\n    return pf->IsShagged();\n  return false;\n}\n\nbool CWarWasp::AnimOver(CStateManager& mgr, float arg) { return x568_stateProg == 3; }\n\nbool CWarWasp::ShouldAttack(CStateManager& mgr, float arg) {\n  if (x700_attackRemTime <= 0.f) {\n    if (CTeamAiRole* role = CTeamAiMgr::GetTeamAiRole(mgr, x674_aiMgr, GetUniqueId())) {\n      if (role->GetTeamAiRole() == CTeamAiRole::ETeamAiRole::Melee && !mgr.GetPlayer().IsInWaterMovement()) {\n        if (TCastToPtr<CTeamAiMgr> tmgr = mgr.ObjectById(x674_aiMgr)) {\n          if (!tmgr->HasMeleeAttackers()) {\n            return !IsPatternObstructed(mgr, GetTranslation(), GetProjectileAimPos(mgr, -1.25f));\n          }\n        }\n      }\n    }\n    if (x3fc_flavor != EFlavorType::Two && !mgr.GetPlayer().IsInWaterMovement()) {\n      return !IsPatternObstructed(mgr, GetTranslation(), GetProjectileAimPos(mgr, -1.25f));\n    }\n  }\n  return false;\n}\n\nbool CWarWasp::InPosition(CStateManager& mgr, float arg) {\n  if (CPathFindSearch* pf = GetSearchPath()) {\n    return pf->IsOver();\n  } else {\n    return (x678_targetPos - GetTranslation()).magSquared() < 1.f;\n  }\n}\n\nbool CWarWasp::ShouldTurn(CStateManager& mgr, float arg) {\n  return zeus::CVector2f::getAngleDiff(GetTransform().basis[1].toVec2f(),\n                                       (mgr.GetPlayer().GetTranslation() - GetTranslation()).toVec2f()) >\n         zeus::degToRad(arg);\n}\n\nbool CWarWasp::HearShot(CStateManager& mgr, float arg) {\n  if (x72e_31_heardNoise || x400_24_hitByPlayerProjectile)\n    return true;\n  if (TCastToConstPtr<CTeamAiMgr> aimgr = mgr.GetObjectById(x674_aiMgr))\n    return aimgr->GetNumRoles() != 0;\n  return false;\n}\n\nbool CWarWasp::ShouldFire(CStateManager& mgr, float arg) {\n  if (x700_attackRemTime <= 0.f) {\n    if (CTeamAiRole* role = CTeamAiMgr::GetTeamAiRole(mgr, x674_aiMgr, GetUniqueId())) {\n      if (role->GetTeamAiRole() == CTeamAiRole::ETeamAiRole::Ranged) {\n        zeus::CVector3f delta = GetProjectileAimPos(mgr, -1.25f) - GetTranslation();\n        if (delta.canBeNormalized() && GetTransform().basis[1].dot(delta.normalized()) >= 0.906f) {\n          if (TCastToPtr<CTeamAiMgr> aimgr = mgr.ObjectById(x674_aiMgr)) {\n            return !aimgr->HasRangedAttackers();\n          }\n        }\n      }\n    } else if (x3fc_flavor == EFlavorType::Two) {\n      zeus::CVector3f delta = GetProjectileAimPos(mgr, -1.25f) - GetTranslation();\n      if (delta.canBeNormalized()) {\n        return GetTransform().basis[1].dot(delta.normalized()) >= 0.906f;\n      }\n    }\n  }\n  return false;\n}\n\nbool CWarWasp::ShouldDodge(CStateManager& mgr, float arg) {\n  zeus::CAABox aabb(GetTranslation() - 7.5f, GetTranslation() + 7.5f);\n  EntityList nearList;\n  mgr.BuildNearList(nearList, aabb, CMaterialFilter::MakeInclude({EMaterialTypes::Projectile}), nullptr);\n  for (TUniqueId id : nearList) {\n    if (TCastToConstPtr<CGameProjectile> proj = mgr.GetObjectById(id)) {\n      if (mgr.GetPlayer().GetUniqueId() == proj->GetOwnerId()) {\n        zeus::CVector3f delta = proj->GetTranslation() - GetTranslation();\n        if (zeus::CVector3f::getAngleDiff(GetTransform().basis[1], delta) < zeus::degToRad(45.f)) {\n          x704_dodgeDir =\n              GetTransform().basis[0].dot(delta) > 0.f ? pas::EStepDirection::Right : pas::EStepDirection::Left;\n          return true;\n        }\n      }\n    }\n  }\n  return false;\n}\n\nbool CWarWasp::CheckCircleAttackSpread(const CStateManager& mgr, s32 team) const {\n  if (TCastToConstPtr<CTeamAiMgr> aimgr = mgr.GetObjectById(x674_aiMgr)) {\n    const s32 teamSize = GetAttackTeamSize(mgr, team);\n    if (teamSize == 1)\n      return true;\n    const TUniqueId leaderId = GetAttackTeamLeader(mgr, team);\n    if (const CWarWasp* leaderWasp = CPatterned::CastTo<CWarWasp>(mgr.GetObjectById(leaderId))) {\n      const zeus::CVector3f platformToLeaderWasp = leaderWasp->GetTranslation() - x6b0_circleBurstPos;\n      float maxAng = 0.f;\n      for (const CTeamAiRole& role : aimgr->GetRoles()) {\n        if (role.GetOwnerId() == leaderId)\n          continue;\n        if (const CWarWasp* wasp2 = CPatterned::CastTo<CWarWasp>(mgr.GetObjectById(role.GetOwnerId()))) {\n          if (wasp2->x708_circleAttackTeam == team) {\n            if (leaderWasp->GetTransform().basis[1].dot(wasp2->GetTranslation() - leaderWasp->GetTranslation()) > 0.f)\n              return false;\n            const float angle =\n                zeus::CVector3f::getAngleDiff(wasp2->GetTranslation() - x6b0_circleBurstPos, platformToLeaderWasp);\n            if (angle > maxAng)\n              maxAng = angle;\n          }\n        }\n      }\n      return maxAng < zeus::degToRad(40.f) * float(teamSize - 1) + zeus::degToRad(20.f);\n    }\n  }\n  return false;\n}\n\nbool CWarWasp::ShouldSpecialAttack(CStateManager& mgr, float arg) {\n  if (x708_circleAttackTeam == 0 && !mgr.GetPlayer().IsInWaterMovement()) {\n    if (TCastToConstPtr<CTeamAiMgr> aimgr = mgr.GetObjectById(x674_aiMgr)) {\n      if (CheckCircleAttackSpread(mgr, x708_circleAttackTeam)) {\n        TUniqueId leaderId = GetAttackTeamLeader(mgr, x708_circleAttackTeam);\n        if (leaderId == GetUniqueId()) {\n          if (x700_attackRemTime <= 0.f &&\n              (mgr.GetPlayer().GetTranslation().toVec2f() - x6b0_circleBurstPos.toVec2f()).magSquared() < 90.25f) {\n            zeus::CVector3f fromPlatformCenter = GetTranslation() - x6b0_circleBurstPos;\n            zeus::CVector3f thresholdVec = x6bc_circleBurstDir;\n            if (x718_circleBurstOffTotemAngle <= zeus::degToRad(90.f)) {\n              thresholdVec =\n                  zeus::CVector3f::slerp(x6c8_circleBurstRight, x6bc_circleBurstDir, x718_circleBurstOffTotemAngle);\n            } else {\n              thresholdVec = zeus::CVector3f::slerp(x6bc_circleBurstDir, -x6c8_circleBurstRight,\n                                                    x718_circleBurstOffTotemAngle - zeus::degToRad(90.f));\n            }\n            if (zeus::CVector3f::getAngleDiff(thresholdVec, fromPlatformCenter) < zeus::degToRad(10.f))\n              return CTeamAiMgr::AddAttacker(CTeamAiMgr::EAttackType::Melee, mgr, x674_aiMgr, GetUniqueId());\n          }\n        } else {\n          if (const CWarWasp* leaderWasp = CPatterned::CastTo<CWarWasp>(mgr.GetObjectById(leaderId))) {\n            if (leaderWasp->x72e_27_teamMatesMelee) {\n              return CTeamAiMgr::AddAttacker(CTeamAiMgr::EAttackType::Melee, mgr, x674_aiMgr, GetUniqueId());\n            }\n          }\n        }\n      }\n    }\n  }\n  return false;\n}\n\nCPathFindSearch* CWarWasp::GetSearchPath() { return &x590_pfSearch; }\n\nCProjectileInfo* CWarWasp::GetProjectileInfo() { return &x6d4_projectileInfo; }\n} // namespace metaforce::MP1\n"
  },
  {
    "path": "Runtime/MP1/World/CWarWasp.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Collision/CCollidableSphere.hpp\"\n#include \"Runtime/Weapon/CProjectileInfo.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n#include \"Runtime/World/CPathFindSearch.hpp\"\n\n#include <zeus/CQuaternion.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CDamageInfo;\nnamespace MP1 {\nclass CWarWasp : public CPatterned {\n  s32 x568_stateProg = -1;\n  CCollidableSphere x570_cSphere;\n  CPathFindSearch x590_pfSearch;\n  TUniqueId x674_aiMgr = kInvalidUniqueId;\n  zeus::CVector3f x678_targetPos;\n  CDamageInfo x684_;\n  zeus::CQuaternion x6a0_initialRot;\n  zeus::CVector3f x6b0_circleBurstPos;\n  zeus::CVector3f x6bc_circleBurstDir;\n  zeus::CVector3f x6c8_circleBurstRight;\n  CProjectileInfo x6d4_projectileInfo;\n  float x6fc_initialSpeed = x3b4_speed;\n  float x700_attackRemTime = 0.f;\n  pas::EStepDirection x704_dodgeDir = pas::EStepDirection::Invalid;\n  s32 x708_circleAttackTeam = -1;\n  s32 x70c_initialCircleAttackTeam = -1;\n  s32 x710_initialCircleAttackTeamUnit = -1;\n  float x714_circleTelegraphSeekHeight = 0.f;\n  float x718_circleBurstOffTotemAngle = zeus::degToRad(90.f);\n  TLockedToken<CGenDescription> x71c_projectileVisorParticle; // Used to be optional\n  u16 x72c_projectileVisorSfx;\n  bool x72e_24_jumpBackRepeat : 1 = true;\n  bool x72e_25_canApplyDamage : 1 = false;\n  bool x72e_26_initiallyInactive : 1;\n  bool x72e_27_teamMatesMelee : 1 = false;\n  bool x72e_28_inProjectileAttack : 1 = false;\n  bool x72e_29_pathObstructed : 1 = false;\n  bool x72e_30_isRetreating : 1 = false;\n  bool x72e_31_heardNoise : 1 = false;\n\n  void SwarmAdd(CStateManager& mgr);\n  void SwarmRemove(CStateManager& mgr);\n  void ApplyDamage(CStateManager& mgr);\n  void SetUpCircleBurstWaypoint(CStateManager& mgr);\n  zeus::CVector3f GetProjectileAimPos(const CStateManager& mgr, float zBias) const;\n  zeus::CVector3f GetCloseInPos(const CStateManager& mgr, const zeus::CVector3f& aimPos) const;\n  float GetCloseInZBasis(const CStateManager& mgr) const;\n  void SetUpPathFindBehavior(CStateManager& mgr);\n  s32 GetAttackTeamSize(const CStateManager& mgr, s32 team) const;\n  float CalcTimeToNextAttack(CStateManager& mgr) const;\n  float CalcOffTotemAngle(CStateManager& mgr) const;\n  void JoinCircleAttackTeam(s32 unit, CStateManager& mgr);\n  void SetUpCircleTelegraphTeam(CStateManager& mgr);\n  TUniqueId GetAttackTeamLeader(const CStateManager& mgr, s32 team) const;\n  void TryCircleTeamMerge(CStateManager& mgr);\n  float GetTeamZStratum(s32 team) const;\n  float CalcSeekMagnitude(const CStateManager& mgr) const;\n  void UpdateTelegraphMoveSpeed(CStateManager& mgr);\n  bool CheckCircleAttackSpread(const CStateManager& mgr, s32 team) const;\n  void ApplyNormalSteering(CStateManager& mgr);\n  void ApplySeparationBehavior(CStateManager& mgr, float sep);\n  bool PathToHiveIsClear(CStateManager& mgr) const;\n  bool SteerToDeactivatePos(CStateManager& mgr, EStateMsg msg, float dt);\n  zeus::CVector3f CalcShuffleDest(const CStateManager& mgr) const;\n\npublic:\n  DEFINE_PATTERNED(WarWasp);\n  CWarWasp(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n           CModelData&& mData, const CPatternedInfo& pInfo, CPatterned::EFlavorType flavor, CPatterned::EColliderType,\n           const CDamageInfo& dInfo1, const CActorParameters&, CAssetId projectileWeapon,\n           const CDamageInfo& projectileDamage, CAssetId projectileVisorParticle, u32 projecileVisorSfx);\n\n  void Accept(IVisitor& visitor) override;\n  void Think(float dt, CStateManager& mgr) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) override;\n\n  std::optional<zeus::CAABox> GetTouchBounds() const override;\n  void DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) override;\n  const CCollisionPrimitive* GetCollisionPrimitive() const override;\n  void Death(CStateManager& mgr, const zeus::CVector3f& direction, EScriptObjectState state) override;\n  bool IsListening() const override;\n  bool Listen(const zeus::CVector3f&, EListenNoiseType) override;\n  zeus::CVector3f GetOrigin(const CStateManager& mgr, const CTeamAiRole& role,\n                            const zeus::CVector3f& aimPos) const override;\n  void UpdateTouchBounds();\n  bool IsRetreating() const { return x72e_30_isRetreating; }\n\n  void Patrol(CStateManager&, EStateMsg, float) override;\n  void PathFind(CStateManager& mgr, EStateMsg msg, float dt) override;\n  void TargetPatrol(CStateManager&, EStateMsg, float) override;\n  void Generate(CStateManager&, EStateMsg, float) override;\n  void Deactivate(CStateManager&, EStateMsg, float) override;\n  void Attack(CStateManager&, EStateMsg, float) override;\n  void JumpBack(CStateManager&, EStateMsg, float) override;\n  void Shuffle(CStateManager&, EStateMsg, float) override;\n  void ProjectileAttack(CStateManager&, EStateMsg, float) override;\n  void TelegraphAttack(CStateManager&, EStateMsg, float) override;\n  void Dodge(CStateManager&, EStateMsg, float) override;\n  void Retreat(CStateManager&, EStateMsg, float) override;\n  void SpecialAttack(CStateManager&, EStateMsg, float) override;\n\n  bool InAttackPosition(CStateManager&, float) override;\n  bool Leash(CStateManager&, float) override;\n  bool PathShagged(CStateManager&, float) override;\n  bool AnimOver(CStateManager&, float) override;\n  bool ShouldAttack(CStateManager&, float) override;\n  bool InPosition(CStateManager&, float) override;\n  bool ShouldTurn(CStateManager&, float) override;\n  bool HearShot(CStateManager&, float) override;\n  bool ShouldFire(CStateManager&, float) override;\n  bool ShouldDodge(CStateManager&, float) override;\n  bool ShouldSpecialAttack(CStateManager&, float) override;\n\n  CPathFindSearch* GetSearchPath() override;\n  CProjectileInfo* GetProjectileInfo() override;\n};\n} // namespace MP1\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/MP2/CMakeLists.txt",
    "content": ""
  },
  {
    "path": "Runtime/MP3/CMakeLists.txt",
    "content": ""
  },
  {
    "path": "Runtime/Memory/CCircularBuffer.cpp",
    "content": "#include \"Runtime/Memory/CCircularBuffer.hpp\"\n\nnamespace metaforce {\nCCircularBuffer::CCircularBuffer(void* buf, s32 len, EOwnership ownership)\n: x0_canDelete(buf != nullptr), x4_ptr(reinterpret_cast<u8*>(buf)), x8_bufferLen(len) {\n  if (ownership == EOwnership::Owned)\n    x0_canDelete = false;\n}\nCCircularBuffer::~CCircularBuffer() {\n  if (x0_canDelete) {\n    delete x4_ptr;\n  }\n}\n\ns32 CCircularBuffer::GetAllocatedAmount() const {\n  s32 res = x10_nextFreeAddr - xc_;\n  return (x14_ == -1) ? res : res + (x8_bufferLen - x14_);\n}\n\nbool CCircularBuffer::IsWrappedMemory(s32 offset, s32 len) {\n  return x14_ > -1 && x14_ >= offset && (x14_ < (offset + len));\n}\n\nvoid* CCircularBuffer::Alloc(s32 len) {\n  if ((x8_bufferLen - x10_nextFreeAddr) >= len && !IsWrappedMemory(x10_nextFreeAddr, len)) {\n    s32 offset = x10_nextFreeAddr;\n    x10_nextFreeAddr = offset + len;\n    return x4_ptr + offset;\n  } else if (xc_ >= len && !IsWrappedMemory(0, len)) {\n    u32 r3 = xc_;\n    xc_ = 0;\n    x10_nextFreeAddr = len;\n    x14_ = r3;\n    return x4_ptr;\n  }\n\n  return nullptr;\n}\n\nvoid CCircularBuffer::Free(void* ptr, s32 r5) {\n  if (x14_ <= -1) {\n    xc_ += r5;\n  } else if (ptr == x4_ptr) {\n    x14_ = -1;\n    xc_ = r5;\n  } else {\n    x14_ += r5;\n  }\n\n  if (x14_ != -1 || xc_ != x10_nextFreeAddr) {\n    return;\n  }\n\n  x10_nextFreeAddr = 0;\n  xc_ = 0;\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Memory/CCircularBuffer.hpp",
    "content": "#pragma once\n#include \"Runtime/GCNTypes.hpp\"\n\nnamespace metaforce {\nclass CCircularBuffer {\npublic:\n  enum class EOwnership { Unowned, Owned };\n\nprivate:\n  bool x0_canDelete;\n  u8* x4_ptr;\n  s32 x8_bufferLen;\n  s32 xc_ = 0;\n  s32 x10_nextFreeAddr = 0;\n  s32 x14_ = -1;\n\npublic:\n  CCircularBuffer(void* buf, s32 len, EOwnership ownership = EOwnership::Owned);\n  ~CCircularBuffer();\n  s32 GetAllocatedAmount() const;\n  bool IsWrappedMemory(s32 offset, s32 len);\n  void* Alloc(s32 len);\n  void Free(void* ptr, s32 r5);\n};\n} // namespace metaforce"
  },
  {
    "path": "Runtime/MkCastTo.py",
    "content": "\"Generates TCastTo source files for constexpr-enabled CEntity casts\"\n\n\nclass Namespace:\n    def __init__(self, name):\n        self.name = name\n\n\nclass EndNamespace:\n    pass\n\n\nCENTITY_TYPES = (\n    # Class, Header\n    ('CActor', 'World/CActor.hpp'),\n    ('CBallCamera', 'Camera/CBallCamera.hpp'),\n    ('CBomb', 'Weapon/CBomb.hpp'),\n    ('CCinematicCamera', 'Camera/CCinematicCamera.hpp'),\n    ('CCollisionActor', 'Collision/CCollisionActor.hpp'),\n    ('CDestroyableRock', 'World/CDestroyableRock.hpp'),\n    ('CEnergyProjectile', 'Weapon/CEnergyProjectile.hpp'),\n    ('CEntity', 'World/CEntity.hpp'),\n    ('CExplosion', 'World/CExplosion.hpp'),\n    ('CFirstPersonCamera', 'Camera/CFirstPersonCamera.hpp'),\n    ('CFishCloud', 'World/CFishCloud.hpp'),\n    ('CGameCamera', 'Camera/CGameCamera.hpp'),\n    ('CGameLight', 'World/CGameLight.hpp'),\n    ('CGameProjectile', 'Weapon/CGameProjectile.hpp'),\n    ('CHUDBillboardEffect', 'World/CHUDBillboardEffect.hpp'),\n    ('CInterpolationCamera', 'Camera/CInterpolationCamera.hpp'),\n    Namespace('MP1'),\n    ('CMetroidPrimeRelay', 'MP1/World/CMetroidPrimeRelay.hpp', 'MP1'),\n    ('CWarWasp', 'MP1/World/CWarWasp.hpp', 'MP1'),\n    ('CScriptContraption', 'MP1/World/CScriptContraption.hpp', 'MP1'),\n    EndNamespace(),\n    ('CPathCamera', 'Camera/CPathCamera.hpp'),\n    ('CAi', 'World/CAi.hpp'),\n    ('CPatterned', 'World/CPatterned.hpp'),\n    ('CPhysicsActor', 'World/CPhysicsActor.hpp'),\n    ('CPlayer', 'World/CPlayer.hpp'),\n    ('CRepulsor', 'World/CRepulsor.hpp'),\n    ('CScriptActor', 'World/CScriptActor.hpp'),\n    ('CScriptActorKeyframe', 'World/CScriptActorKeyframe.hpp'),\n    ('CScriptAiJumpPoint', 'World/CScriptAiJumpPoint.hpp'),\n    ('CScriptCameraHint', 'World/CScriptCameraHint.hpp'),\n    ('CScriptCameraPitchVolume', 'World/CScriptCameraPitchVolume.hpp'),\n    ('CScriptCameraShaker', 'World/CScriptCameraShaker.hpp'),\n    ('CScriptCameraWaypoint', 'World/CScriptCameraWaypoint.hpp'),\n    ('CScriptControllerAction', 'World/CScriptControllerAction.hpp'),\n    ('CScriptCoverPoint', 'World/CScriptCoverPoint.hpp'),\n    ('CScriptDebugCameraWaypoint', 'World/CScriptDebugCameraWaypoint.hpp'),\n    ('CScriptDistanceFog', 'World/CScriptDistanceFog.hpp'),\n    ('CScriptDock', 'World/CScriptDock.hpp'),\n    ('CScriptDoor', 'World/CScriptDoor.hpp'),\n    ('CScriptEffect', 'World/CScriptEffect.hpp'),\n    ('CScriptGrapplePoint', 'World/CScriptGrapplePoint.hpp'),\n    ('CScriptGunTurret', 'World/CScriptGunTurret.hpp'),\n    ('CScriptMazeNode', 'World/CScriptMazeNode.hpp'),\n    ('CScriptPickup', 'World/CScriptPickup.hpp'),\n    ('CScriptPlatform', 'World/CScriptPlatform.hpp'),\n    ('CScriptPlayerHint', 'World/CScriptPlayerHint.hpp'),\n    ('CScriptPointOfInterest', 'World/CScriptPointOfInterest.hpp'),\n    ('CScriptRoomAcoustics', 'World/CScriptRoomAcoustics.hpp'),\n    ('CScriptSound', 'World/CScriptSound.hpp'),\n    ('CScriptMidi', 'World/CScriptMidi.hpp'),\n    ('CScriptSpawnPoint', 'World/CScriptSpawnPoint.hpp'),\n    ('CScriptSpecialFunction', 'World/CScriptSpecialFunction.hpp'),\n    ('CScriptSpiderBallAttractionSurface', 'World/CScriptSpiderBallAttractionSurface.hpp'),\n    ('CScriptSpiderBallWaypoint', 'World/CScriptSpiderBallWaypoint.hpp'),\n    ('CScriptTargetingPoint', 'World/CScriptTargetingPoint.hpp'),\n    ('CTeamAiMgr', 'World/CTeamAiMgr.hpp'),\n    ('CScriptTimer', 'World/CScriptTimer.hpp'),\n    ('CScriptTrigger', 'World/CScriptTrigger.hpp'),\n    ('CScriptVisorFlare', 'World/CScriptVisorFlare.hpp'),\n    ('CScriptVisorGoo', 'World/CScriptVisorGoo.hpp'),\n    ('CScriptWater', 'World/CScriptWater.hpp'),\n    ('CScriptWaypoint', 'World/CScriptWaypoint.hpp'),\n    ('CSnakeWeedSwarm', 'World/CSnakeWeedSwarm.hpp'),\n    ('CScriptSpindleCamera', 'World/CScriptSpindleCamera.hpp'),\n    ('CWallCrawlerSwarm', 'World/CWallCrawlerSwarm.hpp'),\n    ('CWeapon', 'Weapon/CWeapon.hpp'),\n    ('CScriptDebris', 'World/CScriptDebris.hpp'),\n)\n\n\ndef getqualified(tp):\n    if len(tp) >= 3:\n        return tp[2] + '::' + tp[0]\n    else:\n        return tp[0]\n\n\nheaderf = open('TCastTo.hpp', 'w')\nsourcef = open('TCastTo.cpp', 'w')\n\nheaderf.write('''#pragma once\n\nnamespace metaforce {\nclass CEntity;\n''')\n\nfor tp in CENTITY_TYPES:\n    if type(tp) == tuple:\n        headerf.write('class %s;\\n' % tp[0])\n    elif isinstance(tp, Namespace):\n        headerf.write('namespace %s {\\n' % tp.name)\n    elif isinstance(tp, EndNamespace):\n        headerf.write('}\\n')\n\nheaderf.write('\\nclass IVisitor {\\npublic:\\n')\n\nfor tp in CENTITY_TYPES:\n    if type(tp) == tuple:\n        headerf.write('  virtual void Visit(%s* p)=0;\\n' % getqualified(tp))\n\nheaderf.write('''};\n\ntemplate <class T>\nclass TCastToPtr : public IVisitor {\nprotected:\n  T* ptr = nullptr;\npublic:\n  TCastToPtr() = default;\n  TCastToPtr(CEntity* p);\n  TCastToPtr(CEntity& p);\n  TCastToPtr<T>& operator=(CEntity& p);\n  TCastToPtr<T>& operator=(CEntity* p);\n\n''')\n\nfor tp in CENTITY_TYPES:\n    if type(tp) == tuple:\n        headerf.write('  void Visit(%s* p) override;\\n' % getqualified(tp))\n\nheaderf.write('''\n  T* GetPtr() const { return ptr; }\n  operator T*() const { return GetPtr(); }\n  T& operator*() const { return *GetPtr(); }\n  T* operator->() const { return GetPtr(); }\n  bool IsValid() const { return ptr != nullptr; }\n  explicit operator bool() const { return IsValid(); }\n};\n\ntemplate <class T>\nclass TCastToConstPtr : TCastToPtr<T> {\npublic:\n  TCastToConstPtr() = default;\n  TCastToConstPtr(const CEntity* p) : TCastToPtr<T>(const_cast<CEntity*>(p)) {}\n  TCastToConstPtr(const CEntity& p) : TCastToPtr<T>(const_cast<CEntity&>(p)) {}\n  TCastToConstPtr<T>& operator=(const CEntity& p) { TCastToPtr<T>::operator=(const_cast<CEntity&>(p)); return *this; }\n  TCastToConstPtr<T>& operator=(const CEntity* p) { TCastToPtr<T>::operator=(const_cast<CEntity*>(p)); return *this; }\n  const T* GetPtr() const { return TCastToPtr<T>::ptr; }\n  operator const T*() const { return GetPtr(); }\n  const T& operator*() const { return *GetPtr(); }\n  const T* operator->() const { return GetPtr(); }\n  bool IsValid() const { return TCastToPtr<T>::ptr != nullptr; }\n  explicit operator bool() const { return IsValid(); }\n};\n\n}\n''')\n\nheaderf.close()\n\nsourcef.write('#include \"TCastTo.hpp\"\\n\\n')\n\nfor tp in CENTITY_TYPES:\n    if type(tp) == tuple:\n        sourcef.write('#include \"%s\"\\n' % tp[1])\n\nsourcef.write('''\nnamespace metaforce {\n\ntemplate <class T>\nTCastToPtr<T>::TCastToPtr(CEntity* p) { if (p) p->Accept(*this); else ptr = nullptr; }\n\ntemplate <class T>\nTCastToPtr<T>::TCastToPtr(CEntity& p) { p.Accept(*this); }\n\ntemplate <class T>\nTCastToPtr<T>& TCastToPtr<T>::operator=(CEntity* p) { if (p) p->Accept(*this); else ptr = nullptr; return *this; }\n\ntemplate <class T>\nTCastToPtr<T>& TCastToPtr<T>::operator=(CEntity& p) { p.Accept(*this); return *this; }\n\n''')\n\nfor tp in CENTITY_TYPES:\n    if type(tp) == tuple:\n        qual = getqualified(tp)\n        sourcef.write('''template <class T>\nvoid TCastToPtr<T>::Visit(%s* p) {\n  static_assert(sizeof(T) > 0 && !std::is_void_v<T>, \"TCastToPtr can not cast to incomplete type\");\n  ptr = reinterpret_cast<T*>(std::is_convertible_v<%s*, T*> ? p : nullptr);\n}\n\n''' % (qual, qual))\n\nfor tp in CENTITY_TYPES:\n    if type(tp) == tuple:\n        sourcef.write('template class TCastToPtr<%s>;\\n' % getqualified(tp))\n\nsourcef.write('\\n}\\n')\n\nsourcef.close()\n"
  },
  {
    "path": "Runtime/Particle/CColorElement.cpp",
    "content": "#include \"Runtime/Particle/CColorElement.hpp\"\n\n#include \"Runtime/CRandom16.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/Particle/CGenDescription.hpp\"\n#include \"Runtime/Particle/CParticleGlobals.hpp\"\n\n#include <zeus/Math.hpp>\n\n/* Documentation at: https://wiki.axiodl.com/w/Particle_Script#Color_Elements */\n\nnamespace metaforce {\n\nCCEKeyframeEmitter::CCEKeyframeEmitter(CInputStream& in) {\n  x4_percent = in.ReadLong();\n  x8_unk1 = in.ReadLong();\n  xc_loop = in.ReadBool();\n  xd_unk2 = in.ReadBool();\n  x10_loopEnd = in.ReadLong();\n  x14_loopStart = in.ReadLong();\n\n  const u32 count = in.ReadLong();\n  x18_keys.reserve(count);\n  for (u32 i = 0; i < count; ++i) {\n    x18_keys.emplace_back(in.Get<zeus::CColor>());\n  }\n}\n\nbool CCEKeyframeEmitter::GetValue([[maybe_unused]] int frame, zeus::CColor& valOut) const {\n  if (!x4_percent) {\n    int emitterTime = CParticleGlobals::instance()->m_EmitterTime;\n    int calcKey = emitterTime;\n    if (xc_loop) {\n      if (emitterTime >= x10_loopEnd) {\n        int v1 = emitterTime - x14_loopStart;\n        int v2 = x10_loopEnd - x14_loopStart;\n        calcKey = v1 % v2;\n        calcKey += x14_loopStart;\n      }\n    } else {\n      int v1 = x10_loopEnd - 1;\n      if (v1 < emitterTime)\n        calcKey = v1;\n    }\n    valOut = x18_keys[calcKey];\n  } else {\n    int ltPerc = CParticleGlobals::instance()->m_ParticleLifetimePercentage;\n    float ltPercRem = CParticleGlobals::instance()->m_ParticleLifetimePercentageRemainder;\n    if (ltPerc == 100)\n      valOut = x18_keys[100];\n    else\n      valOut = ltPercRem * x18_keys[ltPerc + 1] + (1.0f - ltPercRem) * x18_keys[ltPerc];\n  }\n  return false;\n}\n\nbool CCEConstant::GetValue(int frame, zeus::CColor& valOut) const {\n  float r, g, b, a;\n  x4_r->GetValue(frame, r);\n  x8_g->GetValue(frame, g);\n  xc_b->GetValue(frame, b);\n  x10_a->GetValue(frame, a);\n  valOut = zeus::CColor(r, g, b, a);\n  return false;\n}\n\nbool CCEFastConstant::GetValue([[maybe_unused]] int frame, zeus::CColor& valOut) const {\n  valOut = x4_val;\n  return false;\n}\n\nbool CCETimeChain::GetValue(int frame, zeus::CColor& valOut) const {\n  int v;\n  xc_swFrame->GetValue(frame, v);\n  if (frame < v) {\n    return x4_a->GetValue(frame, valOut);\n  } else {\n    return x8_b->GetValue(frame - v, valOut);\n  }\n}\n\nbool CCEFadeEnd::GetValue(int frame, zeus::CColor& valOut) const {\n  float c;\n  xc_startFrame->GetValue(frame, c);\n\n  if (frame < c) {\n    x4_a->GetValue(frame, valOut);\n    return false;\n  }\n\n  float d;\n  x10_endFrame->GetValue(frame, d);\n\n  zeus::CColor colA;\n  zeus::CColor colB;\n  x4_a->GetValue(frame, colA);\n  x8_b->GetValue(frame, colB);\n\n  float t = (frame - c) / (d - c);\n  valOut = zeus::CColor::lerp(colA, colB, t);\n  return false;\n}\n\nbool CCEFade::GetValue(int frame, zeus::CColor& valOut) const {\n  float c;\n  xc_endFrame->GetValue(frame, c);\n\n  float t = frame / c;\n  if (t > 1.f) {\n    x8_b->GetValue(frame, valOut);\n    return false;\n  }\n\n  zeus::CColor colA;\n  zeus::CColor colB;\n  x4_a->GetValue(frame, colA);\n  x8_b->GetValue(frame, colB);\n\n  valOut = zeus::CColor::lerp(colA, colB, t);\n  return false;\n}\n\nbool CCEPulse::GetValue(int frame, zeus::CColor& valOut) const {\n  int a, b;\n  x4_aDuration->GetValue(frame, a);\n  x8_bDuration->GetValue(frame, b);\n  int cv = a + b + 1;\n  if (cv < 0) {\n    cv = 1;\n  }\n\n  if (b < 1 || frame % cv <= a) {\n    xc_aVal->GetValue(frame, valOut);\n  } else {\n    x10_bVal->GetValue(frame, valOut);\n  }\n  return false;\n}\n\nbool CCEParticleColor::GetValue(int /*frame*/, zeus::CColor& colorOut) const {\n  colorOut = CElementGen::g_currentParticle->x34_color;\n  return false;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CColorElement.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <vector>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/Particle/IElement.hpp\"\n\n/* Documentation at: https://wiki.axiodl.com/w/Particle_Script#Color_Elements */\n\nnamespace metaforce {\n\nclass CCEKeyframeEmitter : public CColorElement {\n  u32 x4_percent;\n  u32 x8_unk1;\n  bool xc_loop;\n  bool xd_unk2;\n  s32 x10_loopEnd;\n  s32 x14_loopStart;\n  std::vector<zeus::CColor> x18_keys;\n\npublic:\n  explicit CCEKeyframeEmitter(CInputStream& in);\n  bool GetValue(int frame, zeus::CColor& colorOut) const override;\n};\n\nclass CCEConstant : public CColorElement {\n  std::unique_ptr<CRealElement> x4_r;\n  std::unique_ptr<CRealElement> x8_g;\n  std::unique_ptr<CRealElement> xc_b;\n  std::unique_ptr<CRealElement> x10_a;\n\npublic:\n  CCEConstant(std::unique_ptr<CRealElement>&& a, std::unique_ptr<CRealElement>&& b, std::unique_ptr<CRealElement>&& c,\n              std::unique_ptr<CRealElement>&& d)\n  : x4_r(std::move(a)), x8_g(std::move(b)), xc_b(std::move(c)), x10_a(std::move(d)) {}\n  bool GetValue(int frame, zeus::CColor& colorOut) const override;\n};\n\nclass CCEFastConstant : public CColorElement {\n  zeus::CColor x4_val;\n\npublic:\n  CCEFastConstant(float a, float b, float c, float d) : x4_val(a, b, c, d) {}\n  bool GetValue(int frame, zeus::CColor& colorOut) const override;\n};\n\nclass CCETimeChain : public CColorElement {\n  std::unique_ptr<CColorElement> x4_a;\n  std::unique_ptr<CColorElement> x8_b;\n  std::unique_ptr<CIntElement> xc_swFrame;\n\npublic:\n  CCETimeChain(std::unique_ptr<CColorElement>&& a, std::unique_ptr<CColorElement>&& b, std::unique_ptr<CIntElement>&& c)\n  : x4_a(std::move(a)), x8_b(std::move(b)), xc_swFrame(std::move(c)) {}\n  bool GetValue(int frame, zeus::CColor& colorOut) const override;\n};\n\nclass CCEFadeEnd : public CColorElement {\n  std::unique_ptr<CColorElement> x4_a;\n  std::unique_ptr<CColorElement> x8_b;\n  std::unique_ptr<CRealElement> xc_startFrame;\n  std::unique_ptr<CRealElement> x10_endFrame;\n\npublic:\n  CCEFadeEnd(std::unique_ptr<CColorElement>&& a, std::unique_ptr<CColorElement>&& b, std::unique_ptr<CRealElement>&& c,\n             std::unique_ptr<CRealElement>&& d)\n  : x4_a(std::move(a)), x8_b(std::move(b)), xc_startFrame(std::move(c)), x10_endFrame(std::move(d)) {}\n  bool GetValue(int frame, zeus::CColor& colorOut) const override;\n};\n\nclass CCEFade : public CColorElement {\n  std::unique_ptr<CColorElement> x4_a;\n  std::unique_ptr<CColorElement> x8_b;\n  std::unique_ptr<CRealElement> xc_endFrame;\n\npublic:\n  CCEFade(std::unique_ptr<CColorElement>&& a, std::unique_ptr<CColorElement>&& b, std::unique_ptr<CRealElement>&& c)\n  : x4_a(std::move(a)), x8_b(std::move(b)), xc_endFrame(std::move(c)) {}\n  bool GetValue(int frame, zeus::CColor& colorOut) const override;\n};\n\nclass CCEPulse : public CColorElement {\n  std::unique_ptr<CIntElement> x4_aDuration;\n  std::unique_ptr<CIntElement> x8_bDuration;\n  std::unique_ptr<CColorElement> xc_aVal;\n  std::unique_ptr<CColorElement> x10_bVal;\n\npublic:\n  CCEPulse(std::unique_ptr<CIntElement>&& a, std::unique_ptr<CIntElement>&& b, std::unique_ptr<CColorElement>&& c,\n           std::unique_ptr<CColorElement>&& d)\n  : x4_aDuration(std::move(a)), x8_bDuration(std::move(b)), xc_aVal(std::move(c)), x10_bVal(std::move(d)) {}\n  bool GetValue(int frame, zeus::CColor& colorOut) const override;\n};\n\nclass CCEParticleColor : public CColorElement {\npublic:\n  bool GetValue(int frame, zeus::CColor& colorOut) const override;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CDecal.cpp",
    "content": "#include \"Runtime/Particle/CDecal.hpp\"\n\n#include \"Runtime/Graphics/CModel.hpp\"\n#include \"Runtime/Particle/CParticleGlobals.hpp\"\n#include \"Runtime/Graphics/CGX.hpp\"\n\nnamespace metaforce {\nCRandom16 CDecal::sDecalRandom;\nbool CDecal::sMoveRedToAlphaBuffer = false;\n\nCDecal::CDecal(const TToken<CDecalDescription>& desc, const zeus::CTransform& xf)\n: x0_description(desc), xc_transform(xf) {\n  CGlobalRandom gr(sDecalRandom);\n\n  CDecalDescription& desco = *x0_description;\n  x5c_31_quad1Invalid = InitQuad(x3c_decalQuads[0], desco.x0_Quads[0]);\n  x5c_30_quad2Invalid = InitQuad(x3c_decalQuads[1], desco.x0_Quads[1]);\n\n  CDecalDescription* d = x0_description.GetObj();\n  if (d->x38_DMDL) {\n    if (d->x48_DLFT) {\n      d->x48_DLFT->GetValue(0, x54_modelLifetime);\n    } else {\n      x54_modelLifetime = 0x7FFFFF;\n    }\n\n    if (d->x50_DMRT) {\n      d->x50_DMRT->GetValue(0, x60_rotation);\n    }\n  } else {\n    x5c_29_modelInvalid = true;\n  }\n}\n\nbool CDecal::InitQuad(CQuadDecal& quad, const SQuadDescr& desc) {\n  if (desc.x14_TEX) {\n    if (desc.x0_LFT) {\n      desc.x0_LFT->GetValue(0, quad.x4_lifetime);\n    } else {\n      quad.x4_lifetime = 0x7FFFFF;\n    }\n    if (desc.x8_ROT) {\n      desc.x8_ROT->GetValue(0, quad.x8_rotation);\n      quad.x0_24_invalid &= desc.x8_ROT->IsConstant();\n    }\n\n    if (desc.x4_SZE) {\n      quad.x0_24_invalid &= desc.x4_SZE->IsConstant();\n      if (quad.x0_24_invalid) {\n        float size = 1.f;\n        desc.x4_SZE->GetValue(0, size);\n        quad.x0_24_invalid = size <= 1.f;\n      }\n    }\n\n    if (desc.xc_OFF) {\n      quad.x0_24_invalid &= desc.xc_OFF->IsFastConstant();\n    }\n    return false;\n  }\n\n  quad.x0_24_invalid = false;\n  return true;\n}\n\nvoid CDecal::SetGlobalSeed(u16 seed) { sDecalRandom.SetSeed(seed); }\n\nvoid CDecal::SetMoveRedToAlphaBuffer(bool move) { sMoveRedToAlphaBuffer = move; }\n\nvoid CDecal::RenderQuad(CQuadDecal& decal, const SQuadDescr& desc) const {\n  zeus::CColor color = zeus::skWhite;\n  float size = 1.f;\n  zeus::CVector3f offset;\n  if (CColorElement* clr = desc.x10_CLR.get()) {\n    clr->GetValue(x58_frameIdx, color);\n  }\n  if (CRealElement* sze = desc.x4_SZE.get()) {\n    sze->GetValue(x58_frameIdx, size);\n    size *= 0.5f;\n  }\n  if (CRealElement* rot = desc.x8_ROT.get()) {\n    rot->GetValue(x58_frameIdx, decal.x8_rotation);\n  }\n  if (CVectorElement* off = desc.xc_OFF.get()) {\n    off->GetValue(x58_frameIdx, offset);\n    offset.y() = 0.f;\n  }\n  zeus::CTransform modXf = xc_transform;\n  modXf.origin += offset;\n  CGraphics::SetModelMatrix(modXf);\n  CGraphics::SetAlphaCompare(ERglAlphaFunc::Always, 0, ERglAlphaOp::And, ERglAlphaFunc::Always, 0);\n\n  bool redToAlpha = CDecal::sMoveRedToAlphaBuffer && desc.x18_ADD && desc.x14_TEX;\n  if (desc.x18_ADD) {\n    CGraphics::SetDepthWriteMode(true, ERglEnum::LEqual, false);\n    if (redToAlpha) {\n      CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::One, ERglBlendFactor::One, ERglLogicOp::Clear);\n    } else {\n      CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::One,\n                              ERglLogicOp::Clear);\n    }\n  } else {\n    CGraphics::SetDepthWriteMode(true, ERglEnum::LEqual, false);\n    CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::InvSrcAlpha,\n                            ERglLogicOp::Clear);\n  }\n\n  SUVElementSet uvSet{0.f, 1.f, 0.f, 1.f};\n  if (desc.x14_TEX) {\n    TLockedToken<CTexture> tex = desc.x14_TEX->GetValueTexture(x58_frameIdx);\n    tex->Load(GX_TEXMAP0, EClampMode::Repeat);\n    CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvModulate);\n    desc.x14_TEX->GetValueUV(x58_frameIdx, uvSet);\n    if (redToAlpha) {\n      CGX::SetNumTevStages(2);\n      CGX::SetTevColorIn(GX_TEVSTAGE1, GX_CC_ZERO, GX_CC_CPREV, GX_CC_APREV, GX_CC_ZERO);\n      CGX::SetTevAlphaIn(GX_TEVSTAGE1, GX_CA_ZERO, GX_CA_TEXA, GX_CA_APREV, GX_CA_ZERO);\n      CGX::SetStandardTevColorAlphaOp(GX_TEVSTAGE1);\n      CGX::SetAlphaCompare(GX_GREATER, 0, GX_AOP_OR, GX_NEVER, 0);\n      GXSetTevSwapMode(GX_TEVSTAGE1, GX_TEV_SWAP0, GX_TEV_SWAP1);\n    } else {\n      CGraphics::SetTevOp(ERglTevStage::Stage1, CTevCombiners::kEnvPassthru);\n    }\n  } else {\n    CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvPassthru);\n    CGraphics::SetTevOp(ERglTevStage::Stage1, CTevCombiners::kEnvPassthru);\n  }\n\n  CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0);\n  CGX::SetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL);\n  CGX::SetNumTexGens(1);\n  CGX::SetNumChans(1);\n  CGX::SetNumIndStages(0);\n  CGX::SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY, false, GX_PTIDENTITY);\n  CGX::SetChanCtrl(CGX::EChannelId::Channel0, false, GX_SRC_REG, GX_SRC_VTX, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE);\n  static const GXVtxDescList vtxDesc[4] = {\n      {GX_VA_POS, GX_DIRECT},\n      {GX_VA_CLR0, GX_DIRECT},\n      {GX_VA_TEX0, GX_DIRECT},\n      {GX_VA_NULL, GX_NONE},\n  };\n  CGX::SetVtxDescv(vtxDesc);\n  CGX::Begin(GX_TRIANGLESTRIP, GX_VTXFMT0, 4);\n\n  float y = 0.001f;\n  if (decal.x8_rotation == 0.f) {\n    // Vertex 0\n    GXPosition3f32(-size, y, size);\n    GXColor4f32(color);\n    GXTexCoord2f32(uvSet.xMin, uvSet.yMin);\n    // Vertex 1\n    GXPosition3f32(size, y, size);\n    GXColor4f32(color);\n    GXTexCoord2f32(uvSet.xMax, uvSet.yMin);\n    // Vertex 2\n    GXPosition3f32(-size, y, -size);\n    GXColor4f32(color);\n    GXTexCoord2f32(uvSet.xMin, uvSet.yMax);\n    // Vertex 3\n    GXPosition3f32(size, y, -size);\n    GXColor4f32(color);\n    GXTexCoord2f32(uvSet.xMax, uvSet.yMax);\n  } else {\n    float ang = zeus::degToRad(decal.x8_rotation);\n    float sinSize = sin(ang) * size;\n    float cosSize = cos(ang) * size;\n    // Vertex 0\n    GXPosition3f32(sinSize - cosSize, y, cosSize + sinSize);\n    GXColor4f32(color);\n    GXTexCoord2f32(uvSet.xMin, uvSet.yMin);\n    // Vertex 1\n    GXPosition3f32(cosSize + sinSize, y, cosSize - sinSize);\n    GXColor4f32(color);\n    GXTexCoord2f32(uvSet.xMax, uvSet.yMin);\n    // Vertex 2\n    GXPosition3f32(-(cosSize + sinSize), y, -(cosSize - sinSize));\n    GXColor4f32(color);\n    GXTexCoord2f32(uvSet.xMin, uvSet.yMax);\n    // Vertex 3\n    GXPosition3f32(-sinSize + cosSize, y, -cosSize - sinSize);\n    GXColor4f32(color);\n    GXTexCoord2f32(uvSet.xMax, uvSet.yMax);\n  }\n\n  CGX::End();\n  if (redToAlpha) {\n    GXSetTevSwapMode(GX_TEVSTAGE1, GX_TEV_SWAP0, GX_TEV_SWAP0);\n    CGX::SetAlphaCompare(GX_ALWAYS, 0, GX_AOP_OR, GX_ALWAYS, 0);\n  }\n}\n\nvoid CDecal::RenderMdl() {\n  CDecalDescription& desc = *x0_description;\n  zeus::CColor color = zeus::skWhite;\n  zeus::CVector3f dmop;\n  zeus::CTransform rotXf;\n\n  if (!desc.x5c_25_DMOO)\n    rotXf = xc_transform.getRotation();\n\n  bool dmrtIsConst = false;\n  if (CVectorElement* dmrt = desc.x50_DMRT.get())\n    dmrtIsConst = dmrt->IsFastConstant();\n\n  zeus::CTransform dmrtXf;\n  if (dmrtIsConst) {\n    desc.x50_DMRT->GetValue(x58_frameIdx, x60_rotation);\n    dmrtXf = zeus::CTransform::RotateZ(zeus::degToRad(x60_rotation.z()));\n    dmrtXf.rotateLocalY(zeus::degToRad(x60_rotation.y()));\n    dmrtXf.rotateLocalX(zeus::degToRad(x60_rotation.x()));\n  }\n\n  dmrtXf = rotXf * dmrtXf;\n\n  if (CVectorElement* dmopo = desc.x4c_DMOP.get())\n    dmopo->GetValue(x58_frameIdx, dmop);\n\n  zeus::CTransform worldXf = zeus::CTransform::Translate(rotXf * dmop + xc_transform.origin);\n\n  if (dmrtIsConst) {\n    worldXf = worldXf * dmrtXf;\n  } else {\n    if (CVectorElement* dmrt = desc.x50_DMRT.get()) {\n      zeus::CVector3f dmrtVec;\n      dmrt->GetValue(x58_frameIdx, dmrtVec);\n      dmrtXf = zeus::CTransform::RotateZ(zeus::degToRad(dmrtVec.z()));\n      dmrtXf.rotateLocalY(zeus::degToRad(dmrtVec.y()));\n      dmrtXf.rotateLocalX(zeus::degToRad(dmrtVec.x()));\n      worldXf = worldXf * rotXf * dmrtXf;\n    } else {\n      worldXf = worldXf * dmrtXf;\n    }\n  }\n\n  if (CVectorElement* dmsc = desc.x54_DMSC.get()) {\n    zeus::CVector3f dmscVec;\n    dmsc->GetValue(x58_frameIdx, dmscVec);\n    worldXf = worldXf * zeus::CTransform::Scale(dmscVec);\n  }\n\n  if (CColorElement* dmcl = desc.x58_DMCL.get())\n    dmcl->GetValue(x58_frameIdx, color);\n\n  CGraphics::SetModelMatrix(worldXf);\n\n  if (desc.x5c_24_DMAB) {\n    const CModelFlags flags(7, 0, 1, color);\n    desc.x38_DMDL->Draw(flags);\n  } else {\n    if (color.a() == 1.f) {\n      constexpr CModelFlags flags(0, 0, 3, zeus::skWhite);\n      desc.x38_DMDL->Draw(flags);\n    } else {\n      const CModelFlags flags(5, 0, 1, color);\n      desc.x38_DMDL->Draw(flags);\n    }\n  }\n\n  CGraphics::SetCullMode(ERglCullMode::Front);\n  CTevCombiners::ResetStates();\n}\n\nvoid CDecal::Render() {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CDecal::Render\", zeus::skYellow);\n  CGlobalRandom gr(sDecalRandom);\n  if (x5c_29_modelInvalid && x5c_30_quad2Invalid && x5c_31_quad1Invalid) {\n    return;\n  }\n\n  CGraphics::DisableAllLights();\n  CParticleGlobals::instance()->SetEmitterTime(x58_frameIdx);\n\n  const CDecalDescription& desc = *x0_description;\n  if (desc.x0_Quads[0].x14_TEX && !x5c_31_quad1Invalid) {\n    CParticleGlobals::instance()->SetParticleLifetime(x3c_decalQuads[0].x4_lifetime);\n    CParticleGlobals::instance()->UpdateParticleLifetimeTweenValues(x58_frameIdx);\n    RenderQuad(x3c_decalQuads[0], desc.x0_Quads[0]);\n  }\n  if (desc.x0_Quads[1].x14_TEX && !x5c_30_quad2Invalid) {\n    CParticleGlobals::instance()->SetParticleLifetime(x3c_decalQuads[1].x4_lifetime);\n    CParticleGlobals::instance()->UpdateParticleLifetimeTweenValues(x58_frameIdx);\n    RenderQuad(x3c_decalQuads[1], desc.x0_Quads[1]);\n  }\n  if (desc.x38_DMDL && !x5c_29_modelInvalid) {\n    CParticleGlobals::instance()->SetParticleLifetime(x54_modelLifetime);\n    CParticleGlobals::instance()->UpdateParticleLifetimeTweenValues(x58_frameIdx);\n    RenderMdl();\n  }\n}\n\nvoid CDecal::Update(float dt) {\n  if (x58_frameIdx >= x3c_decalQuads[0].x4_lifetime)\n    x5c_31_quad1Invalid = true;\n  if (x58_frameIdx >= x3c_decalQuads[1].x4_lifetime)\n    x5c_30_quad2Invalid = true;\n  if (x58_frameIdx >= x54_modelLifetime)\n    x5c_29_modelInvalid = true;\n  ++x58_frameIdx;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CDecal.hpp",
    "content": "#pragma once\n\n#include <array>\n\n#include \"Runtime/CRandom16.hpp\"\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Particle/CDecalDescription.hpp\"\n\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nstruct SQuadDescr;\nstruct CQuadDecal {\n  bool x0_24_invalid : 1 = true;\n  s32 x4_lifetime = 0;\n  float x8_rotation = 0.f;\n  CQuadDecal() = default;\n  CQuadDecal(s32 i, float f) : x4_lifetime(i), x8_rotation(f) {}\n};\n\nclass CDecal {\n  friend class CDecalManager;\n  static bool sMoveRedToAlphaBuffer;\n  static CRandom16 sDecalRandom;\n\n  TLockedToken<CDecalDescription> x0_description;\n  zeus::CTransform xc_transform;\n  std::array<CQuadDecal, 2> x3c_decalQuads;\n  s32 x54_modelLifetime = 0;\n  s32 x58_frameIdx = 0;\n  bool x5c_31_quad1Invalid : 1 = false;\n  bool x5c_30_quad2Invalid : 1 = false;\n  bool x5c_29_modelInvalid : 1 = false;\n  zeus::CVector3f x60_rotation;\n  bool InitQuad(CQuadDecal& quad, const SQuadDescr& desc);\n\npublic:\n  CDecal(const TToken<CDecalDescription>& desc, const zeus::CTransform& xf);\n  void RenderQuad(CQuadDecal& decal, const SQuadDescr& desc) const;\n  void RenderMdl();\n  void Render();\n  void Update(float dt);\n\n  static void SetGlobalSeed(u16);\n  static void SetMoveRedToAlphaBuffer(bool);\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CDecalDataFactory.cpp",
    "content": "#include \"Runtime/Particle/CDecalDataFactory.hpp\"\n\n#include \"Runtime/CRandom16.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/Graphics/CModel.hpp\"\n#include \"Runtime/Particle/CParticleDataFactory.hpp\"\n#include \"Runtime/Logging.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\nnamespace metaforce {\nusing CPF = CParticleDataFactory;\nstd::unique_ptr<CDecalDescription> CDecalDataFactory::GetGeneratorDesc(CInputStream& in, CSimplePool* resPool) {\n  return CreateGeneratorDescription(in, resPool);\n}\n\nstd::unique_ptr<CDecalDescription> CDecalDataFactory::CreateGeneratorDescription(CInputStream& in,\n                                                                                 CSimplePool* resPool) {\n  const FourCC clsId = CPF::GetClassID(in);\n\n  if (clsId == FOURCC('DPSM')) {\n    auto desc = std::make_unique<CDecalDescription>();\n    if (CreateDPSM(desc.get(), in, resPool)) {\n      return desc;\n    }\n  }\n\n  return nullptr;\n}\n\nbool CDecalDataFactory::CreateDPSM(CDecalDescription* desc, CInputStream& in, CSimplePool* resPool) {\n  CRandom16 rand;\n  CGlobalRandom gr{rand};\n  FourCC clsId = CPF::GetClassID(in);\n\n  while (clsId != SBIG('_END')) {\n    bool loadFirstDesc = false;\n    switch (clsId.toUint32()) {\n    case SBIG('1SZE'):\n    case SBIG('1LFT'):\n    case SBIG('1ROT'):\n    case SBIG('1OFF'):\n    case SBIG('1CLR'):\n    case SBIG('1TEX'):\n    case SBIG('1ADD'):\n      loadFirstDesc = true;\n      [[fallthrough]];\n    case SBIG('2LFT'):\n    case SBIG('2SZE'):\n    case SBIG('2ROT'):\n    case SBIG('2OFF'):\n    case SBIG('2CLR'):\n    case SBIG('2TEX'):\n    case SBIG('2ADD'):\n      if (loadFirstDesc)\n        GetQuadDecalInfo(in, resPool, clsId, desc->x0_Quads[0]);\n      else\n        GetQuadDecalInfo(in, resPool, clsId, desc->x0_Quads[1]);\n      break;\n\n    case SBIG('DMDL'):\n      desc->x38_DMDL = CPF::GetModel(in, resPool);\n      break;\n    case SBIG('DLFT'):\n      desc->x48_DLFT = CPF::GetIntElement(in);\n      break;\n    case SBIG('DMOP'):\n      desc->x4c_DMOP = CPF::GetVectorElement(in);\n      break;\n    case SBIG('DMRT'):\n      desc->x50_DMRT = CPF::GetVectorElement(in);\n      break;\n    case SBIG('DMSC'):\n      desc->x54_DMSC = CPF::GetVectorElement(in);\n      break;\n    case SBIG('DMCL'):\n      desc->x58_DMCL = CPF::GetColorElement(in);\n      break;\n    case SBIG('DMAB'):\n      desc->x5c_24_DMAB = CPF::GetBool(in);\n      break;\n    case SBIG('DMOO'):\n      desc->x5c_25_DMOO = CPF::GetBool(in);\n      break;\n    default: {\n      spdlog::fatal(\"Unknown DPSC class {} @{}\", clsId, in.GetReadPosition());\n      return false;\n    }\n    }\n\n    clsId = CPF::GetClassID(in);\n  }\n  return true;\n}\n\nvoid CDecalDataFactory::GetQuadDecalInfo(CInputStream& in, CSimplePool* resPool, FourCC clsId, SQuadDescr& quad) {\n  switch (clsId.toUint32()) {\n  case SBIG('1LFT'):\n  case SBIG('2LFT'):\n    quad.x0_LFT = CPF::GetIntElement(in);\n    break;\n  case SBIG('1SZE'):\n  case SBIG('2SZE'):\n    quad.x4_SZE = CPF::GetRealElement(in);\n    break;\n  case SBIG('1ROT'):\n  case SBIG('2ROT'):\n    quad.x8_ROT = CPF::GetRealElement(in);\n    break;\n  case SBIG('1OFF'):\n  case SBIG('2OFF'):\n    quad.xc_OFF = CPF::GetVectorElement(in);\n    break;\n  case SBIG('1CLR'):\n  case SBIG('2CLR'):\n    quad.x10_CLR = CPF::GetColorElement(in);\n    break;\n  case SBIG('1TEX'):\n  case SBIG('2TEX'):\n    quad.x14_TEX = CPF::GetTextureElement(in, resPool);\n    break;\n  case SBIG('1ADD'):\n  case SBIG('2ADD'):\n    quad.x18_ADD = CPF::GetBool(in);\n    break;\n  }\n}\n\nCFactoryFnReturn FDecalDataFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& vparms,\n                                   CObjectReference*) {\n  CSimplePool* sp = vparms.GetOwnedObj<CSimplePool*>();\n  return TToken<CDecalDescription>::GetIObjObjectFor(CDecalDataFactory::GetGeneratorDesc(in, sp));\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CDecalDataFactory.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/CFactoryMgr.hpp\"\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/IObj.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Particle/CDecalDescription.hpp\"\n\nnamespace metaforce {\nclass CSimplePool;\n\nclass CDecalDataFactory {\n  static bool CreateDPSM(CDecalDescription* desc, CInputStream& in, CSimplePool* resPool);\n  static std::unique_ptr<CDecalDescription> CreateGeneratorDescription(CInputStream& in, CSimplePool* resPool);\n  static void GetQuadDecalInfo(CInputStream& in, CSimplePool* resPool, FourCC clsId, SQuadDescr& quad);\n\npublic:\n  static std::unique_ptr<CDecalDescription> GetGeneratorDesc(CInputStream& in, CSimplePool* resPool);\n};\n\nCFactoryFnReturn FDecalDataFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& vparms,\n                                   CObjectReference*);\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CDecalDescription.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/Particle/CColorElement.hpp\"\n#include \"Runtime/Particle/CIntElement.hpp\"\n#include \"Runtime/Particle/CParticleDataFactory.hpp\"\n#include \"Runtime/Particle/CRealElement.hpp\"\n#include \"Runtime/Particle/CUVElement.hpp\"\n#include \"Runtime/Particle/CVectorElement.hpp\"\n\nnamespace metaforce {\n\nstruct SQuadDescr {\n  std::unique_ptr<CIntElement> x0_LFT;\n  std::unique_ptr<CRealElement> x4_SZE;\n  std::unique_ptr<CRealElement> x8_ROT;\n  std::unique_ptr<CVectorElement> xc_OFF;\n  std::unique_ptr<CColorElement> x10_CLR;\n  std::unique_ptr<CUVElement> x14_TEX;\n  bool x18_ADD = false;\n};\n\nclass CDecalDescription {\npublic:\n  CDecalDescription() = default;\n\n  SQuadDescr x0_Quads[2];\n  SParticleModel x38_DMDL;\n  std::unique_ptr<CIntElement> x48_DLFT;\n  std::unique_ptr<CVectorElement> x4c_DMOP;\n  std::unique_ptr<CVectorElement> x50_DMRT;\n  std::unique_ptr<CVectorElement> x54_DMSC;\n  std::unique_ptr<CColorElement> x58_DMCL;\n  bool x5c_24_DMAB : 1 = false;\n  bool x5c_25_DMOO : 1 = false;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CDecalManager.cpp",
    "content": "#include \"Runtime/Particle/CDecalManager.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Particle/CDecal.hpp\"\n#include \"Runtime/Particle/CDecalDescription.hpp\"\n\nnamespace metaforce {\nbool CDecalManager::m_PoolInitialized = false;\ns32 CDecalManager::m_FreeIndex = 63;\nfloat CDecalManager::m_DeltaTimeSinceLastDecalCreation = 0.f;\ns32 CDecalManager::m_LastDecalCreatedIndex = -1;\nCAssetId CDecalManager::m_LastDecalCreatedAssetId = {};\nrstl::reserved_vector<CDecalManager::SDecal, 64> CDecalManager::m_DecalPool;\nrstl::reserved_vector<s32, 64> CDecalManager::m_ActiveIndexList;\n\nvoid CDecalManager::Initialize() {\n  if (m_PoolInitialized)\n    return;\n\n  m_DecalPool.clear();\n  for (int i = 0; i < 64; ++i) {\n    m_DecalPool.emplace_back(std::nullopt, 0, i - 1, false);\n  }\n\n  m_FreeIndex = 63;\n  m_PoolInitialized = true;\n  m_DeltaTimeSinceLastDecalCreation = 0.f;\n  m_LastDecalCreatedIndex = -1;\n  m_LastDecalCreatedAssetId = {};\n}\n\nvoid CDecalManager::Reinitialize() {\n  if (!m_PoolInitialized)\n    Initialize();\n\n  m_DecalPool.clear();\n  for (int i = 0; i < 64; ++i) {\n    m_DecalPool.emplace_back(std::nullopt, 0, i - 1, false);\n  }\n\n  m_ActiveIndexList.clear();\n\n  m_FreeIndex = 63;\n}\n\nvoid CDecalManager::Shutdown() {\n  m_ActiveIndexList.clear();\n  m_DecalPool.clear();\n}\n\nvoid CDecalManager::AddToRenderer(const zeus::CFrustum& frustum, const CStateManager& mgr) {\n  for (s32 idx : m_ActiveIndexList) {\n    CDecalManager::SDecal& decal = m_DecalPool[idx];\n    if (decal.x75_24_notIce || mgr.GetThermalDrawFlag() != EThermalDrawFlag::Hot) {\n      const zeus::CVector3f& point = decal.x0_decal->xc_transform.origin;\n      zeus::CAABox aabb(point, point);\n      g_Renderer->AddDrawable(&*decal.x0_decal, point, aabb, 2, IRenderer::EDrawableSorting::SortedCallback);\n    }\n  }\n}\n\nrstl::reserved_vector<s32, 64>::iterator\nCDecalManager::RemoveFromActiveList(rstl::reserved_vector<s32, 64>::iterator it, s32 idx) {\n  it = m_ActiveIndexList.erase(it);\n  m_DecalPool[idx].x74_index = u8(m_FreeIndex);\n  m_FreeIndex = idx;\n  if (m_LastDecalCreatedIndex == m_FreeIndex)\n    m_LastDecalCreatedIndex = -1;\n  return it;\n}\n\nvoid CDecalManager::Update(float dt, CStateManager& mgr) {\n  m_DeltaTimeSinceLastDecalCreation += dt;\n  for (auto it = m_ActiveIndexList.begin(); it != m_ActiveIndexList.end();) {\n    SDecal& decal = m_DecalPool[*it];\n    if (decal.x70_areaId != mgr.GetNextAreaId() ||\n        (decal.x0_decal->x5c_29_modelInvalid && decal.x0_decal->x5c_30_quad2Invalid &&\n         decal.x0_decal->x5c_31_quad1Invalid)) {\n      it = RemoveFromActiveList(it, *it);\n      continue;\n    }\n    decal.x0_decal->Update(dt);\n    ++it;\n  }\n}\n\nvoid CDecalManager::AddDecal(const TToken<CDecalDescription>& decal, const zeus::CTransform& xf, bool notIce,\n                             CStateManager& mgr) {\n  //OPTICK_EVENT();\n  if (m_LastDecalCreatedIndex != -1 && m_DeltaTimeSinceLastDecalCreation < 0.75f &&\n      m_LastDecalCreatedAssetId == decal.GetObjectTag()->id) {\n    SDecal& existingDecal = m_DecalPool[m_LastDecalCreatedIndex];\n    if ((existingDecal.x0_decal->xc_transform.origin - xf.origin).magSquared() < 0.01f)\n      return;\n  }\n\n  if (m_FreeIndex == -1)\n    RemoveFromActiveList(m_ActiveIndexList.begin(), m_ActiveIndexList[0]);\n\n  s32 thisIndex = m_FreeIndex;\n  SDecal& freeDecal = m_DecalPool[thisIndex];\n  m_FreeIndex = freeDecal.x74_index;\n  freeDecal.x0_decal.emplace(decal, xf);\n\n  freeDecal.x70_areaId = mgr.GetNextAreaId();\n  freeDecal.x75_24_notIce = notIce;\n  m_DeltaTimeSinceLastDecalCreation = 0.f;\n  m_LastDecalCreatedIndex = thisIndex;\n  m_LastDecalCreatedAssetId = decal.GetObjectTag()->id;\n  m_ActiveIndexList.push_back(thisIndex);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CDecalManager.hpp",
    "content": "#pragma once\n\n#include <optional>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Particle/CDecal.hpp\"\n#include <zeus/CFrustum.hpp>\n\nnamespace metaforce {\nclass CStateManager;\n\nclass CDecalManager {\n  struct SDecal {\n    std::optional<CDecal> x0_decal;\n    TAreaId x70_areaId;\n    s8 x74_index;\n    bool x75_24_notIce : 1;\n    SDecal(const std::optional<CDecal>& decal, TAreaId aid, s8 idx, bool notIce)\n    : x0_decal(decal), x70_areaId(aid), x74_index(idx), x75_24_notIce(notIce) {}\n  };\n\n  static bool m_PoolInitialized;\n  static s32 m_FreeIndex;\n  static float m_DeltaTimeSinceLastDecalCreation;\n  static s32 m_LastDecalCreatedIndex;\n  static CAssetId m_LastDecalCreatedAssetId;\n  static rstl::reserved_vector<SDecal, 64> m_DecalPool;\n  static rstl::reserved_vector<s32, 64> m_ActiveIndexList;\n  static rstl::reserved_vector<s32, 64>::iterator RemoveFromActiveList(rstl::reserved_vector<s32, 64>::iterator it,\n                                                                       s32 idx);\n\npublic:\n  static void Initialize();\n  static void Reinitialize();\n  static void Shutdown();\n  static void AddToRenderer(const zeus::CFrustum& frustum, const CStateManager& mgr);\n  static void Update(float dt, CStateManager& mgr);\n  static void AddDecal(const TToken<CDecalDescription>& decal, const zeus::CTransform& xf, bool notIce,\n                       CStateManager& mgr);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CElectricDescription.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/Particle/CColorElement.hpp\"\n#include \"Runtime/Particle/CIntElement.hpp\"\n#include \"Runtime/Particle/CModVectorElement.hpp\"\n#include \"Runtime/Particle/CParticleDataFactory.hpp\"\n#include \"Runtime/Particle/CRealElement.hpp\"\n#include \"Runtime/Particle/CUVElement.hpp\"\n#include \"Runtime/Particle/CVectorElement.hpp\"\n\nnamespace metaforce {\nclass CElectricDescription {\npublic:\n  std::unique_ptr<CIntElement> x0_LIFE;\n  std::unique_ptr<CIntElement> x4_SLIF;\n  std::unique_ptr<CRealElement> x8_GRAT;\n  std::unique_ptr<CIntElement> xc_SCNT;\n  std::unique_ptr<CIntElement> x10_SSEG;\n  std::unique_ptr<CColorElement> x14_COLR;\n  std::unique_ptr<CEmitterElement> x18_IEMT;\n  std::unique_ptr<CEmitterElement> x1c_FEMT;\n  std::unique_ptr<CRealElement> x20_AMPL;\n  std::unique_ptr<CRealElement> x24_AMPD;\n  std::unique_ptr<CRealElement> x28_LWD1;\n  std::unique_ptr<CRealElement> x2c_LWD2;\n  std::unique_ptr<CRealElement> x30_LWD3;\n  std::unique_ptr<CColorElement> x34_LCL1;\n  std::unique_ptr<CColorElement> x38_LCL2;\n  std::unique_ptr<CColorElement> x3c_LCL3;\n  SSwooshGeneratorDesc x40_SSWH;\n  SChildGeneratorDesc x50_GPSM;\n  SChildGeneratorDesc x60_EPSM;\n  bool x70_ZERY = false;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CElementGen.cpp",
    "content": "#include \"Runtime/Particle/CElementGen.hpp\"\n\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Character/CActorLights.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Graphics/CGX.hpp\"\n#include \"Runtime/Graphics/CModel.hpp\"\n#include \"Runtime/Particle/CElectricDescription.hpp\"\n#include \"Runtime/Particle/CGenDescription.hpp\"\n#include \"Runtime/Particle/CParticleGlobals.hpp\"\n#include \"Runtime/Particle/CParticleElectric.hpp\"\n#include \"Runtime/Particle/CParticleSwoosh.hpp\"\n#include \"Runtime/Particle/CSwooshDescription.hpp\"\n#include \"Runtime/Particle/CWarp.hpp\"\n#include \"Runtime/Logging.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\n#define MAX_GLOBAL_PARTICLES 2560\n\nnamespace metaforce {\nu16 CElementGen::g_GlobalSeed = 99;\nbool CElementGen::g_subtractBlend = false;\n\nint CElementGen::g_ParticleAliveCount;\nint CElementGen::g_ParticleSystemAliveCount;\nbool CElementGen::g_ParticleSystemInitialized = false;\nbool CElementGen::sMoveRedToAlphaBuffer = false;\nCParticle* CElementGen::g_currentParticle = nullptr;\n\nvoid CElementGen::Initialize() {\n  if (g_ParticleSystemInitialized)\n    return;\n\n  g_ParticleAliveCount = 0;\n  g_ParticleSystemAliveCount = 0;\n  g_ParticleSystemInitialized = true;\n}\n\nvoid CElementGen::Shutdown() { g_ParticleSystemInitialized = false; }\n\nCElementGen::CElementGen(TToken<CGenDescription> gen, EModelOrientationType orientType, EOptionalSystemFlags flags)\n: x1c_genDesc(std::move(gen))\n, x2c_orientType(orientType)\n, x26d_27_enableOPTS(True(flags & EOptionalSystemFlags::Two))\n, x27c_randState(x94_randomSeed) {\n  CGenDescription* desc = x1c_genDesc.GetObj();\n  x28_loadedGenDesc = desc;\n\n  if (desc->x54_x40_TEXR)\n    desc->x54_x40_TEXR->GetValueTexture(0).GetObj();\n  if (desc->x58_x44_TIND)\n    desc->x58_x44_TIND->GetValueTexture(0).GetObj();\n\n  CGlobalRandom globRnd(x27c_randState);\n  if (CIntElement* seedElem = desc->x1c_x10_SEED.get()) {\n    int seedVal;\n    seedElem->GetValue(x74_curFrame, seedVal);\n    x94_randomSeed = seedVal;\n  }\n  x27c_randState.SetSeed(x94_randomSeed);\n  ++g_ParticleSystemAliveCount;\n  x26c_25_LIT_ = desc->x44_29_x30_29_LIT_;\n  x26c_26_AAPH = desc->x44_26_x30_26_AAPH;\n  x26c_27_ZBUF = desc->x44_27_x30_27_ZBUF;\n  x26c_28_zTest = true;\n  x26c_29_ORNT = desc->x30_30_ORNT;\n  x26c_30_MBLR = x26c_29_ORNT ? false : desc->x44_30_x31_24_MBLR;\n\n  if (CIntElement* mbspElem = desc->x48_x34_MBSP.get())\n    mbspElem->GetValue(x74_curFrame, x270_MBSP);\n\n  // x280_VELSources' entries should be tightly packed\n  size_t idx = 0;\n  if (CModVectorElement* elem = desc->x7c_x68_VEL1.get()) {\n    x280_VELSources[idx] = elem;\n    x278_hasVMD[idx] = desc->x45_26_x31_28_VMD1;\n    idx++;\n  }\n\n  if (CModVectorElement* elem = desc->x80_x6c_VEL2.get()) {\n    x280_VELSources[idx] = elem;\n    x278_hasVMD[idx] = desc->x45_27_x31_29_VMD2;\n    idx++;\n  }\n\n  if (CModVectorElement* elem = desc->x84_x70_VEL3.get()) {\n    x280_VELSources[idx] = elem;\n    x278_hasVMD[idx] = desc->x45_28_x31_30_VMD3;\n    idx++;\n  }\n\n  if (CModVectorElement* elem = desc->x88_x74_VEL4.get()) {\n    x280_VELSources[idx] = elem;\n    x278_hasVMD[idx] = desc->x45_29_x31_31_VMD4;\n    idx++;\n  }\n\n  if (desc->x10c_ADV1 || desc->x110_ADV2 || desc->x114_ADV3 || desc->x118_ADV4 || desc->x11c_ADV5 || desc->x120_ADV6 ||\n      desc->x124_ADV7 || desc->x128_ADV8)\n    x26d_28_enableADV = true;\n\n  if (CIntElement* cssdElem = desc->xa0_x8c_CSSD.get())\n    cssdElem->GetValue(0, x2a0_CSSD);\n\n  if (CIntElement* pisyElem = desc->xc8_xb4_PISY.get()) {\n    pisyElem->GetValue(0, x2a8_PISY);\n    if (x2a8_PISY <= 0)\n      x2a8_PISY = 1;\n  }\n\n  if (CIntElement* sisyElem = desc->xcc_xb8_SISY.get())\n    sisyElem->GetValue(0, x2a4_SISY);\n\n  if (CIntElement* sssdElem = desc->xe4_xd0_SSSD.get())\n    sssdElem->GetValue(0, x2ac_SSSD);\n\n  if (CVectorElement* sspoElem = desc->xe8_xd4_SSPO.get()) {\n    sspoElem->GetValue(0, x2b0_SSPO);\n    if (!sspoElem->IsFastConstant())\n      x26c_24_translationDirty = true;\n  }\n\n  if (CIntElement* sesdElem = desc->xf8_xe4_SESD.get())\n    sesdElem->GetValue(0, x2bc_SESD);\n\n  if (CVectorElement* sepoElem = desc->xfc_xe8_SEPO.get()) {\n    sepoElem->GetValue(0, x2c0_SEPO);\n    if (!sepoElem->IsFastConstant())\n      x26c_24_translationDirty = true;\n  }\n\n  if (CVectorElement* pofsElem = desc->x18_xc_POFS.get()) {\n    pofsElem->GetValue(x74_curFrame, xf4_POFS);\n    if (!pofsElem->IsFastConstant())\n      x26c_24_translationDirty = true;\n  }\n\n  if (CIntElement* psltElem = desc->xc_x0_PSLT.get())\n    psltElem->GetValue(0, x268_PSLT);\n  else\n    x268_PSLT = INT_MAX;\n\n  if (CIntElement* maxpElem = desc->x28_x1c_MAXP.get()) {\n    maxpElem->GetValue(x74_curFrame, x90_MAXP);\n  }\n\n  s32 count = std::min(256, x90_MAXP);\n  x30_particles.reserve(count);\n  if (x26d_28_enableADV) {\n    x60_advValues.resize(count);\n  }\n  if (x2c_orientType == EModelOrientationType::One)\n    x50_parentMatrices.resize(x90_MAXP);\n\n  x26c_31_LINE = desc->x44_24_x30_24_LINE;\n  x26d_24_FXLL = desc->x44_25_x30_25_FXLL;\n\n  if (CIntElement* ltypElem = desc->x100_xec_LTYP.get()) {\n    int ltyp;\n    ltypElem->GetValue(x74_curFrame, ltyp);\n    switch (LightType(ltyp)) {\n    case LightType::None:\n    default:\n      x308_lightType = LightType::None;\n      break;\n    case LightType::Directional:\n      x308_lightType = LightType::Directional;\n      break;\n    case LightType::Custom:\n      x308_lightType = LightType::Custom;\n      break;\n    case LightType::Spot:\n      x308_lightType = LightType::Spot;\n      break;\n    }\n  }\n\n  if (CIntElement* lfotElem = desc->x114_x100_LFOT.get()) {\n    int lfot;\n    lfotElem->GetValue(x74_curFrame, lfot);\n    switch (EFalloffType(lfot)) {\n    case EFalloffType::Constant:\n      x32c_falloffType = EFalloffType::Constant;\n      break;\n    case EFalloffType::Linear:\n    default:\n      x32c_falloffType = EFalloffType::Linear;\n      break;\n    case EFalloffType::Quadratic:\n      x32c_falloffType = EFalloffType::Quadratic;\n      break;\n    }\n  }\n\n  if (x26c_31_LINE) {\n    CUVElement* texr = desc->x54_x40_TEXR.get();\n    // TODO\n    //    aurora::gfx::TextureHandle tex;\n    //    if (texr)\n    //      tex = texr->GetValueTexture(0).GetObj()->GetTexture();\n    int maxVerts = x90_MAXP;\n    //    m_lineRenderer.reset(\n    //        new CLineRenderer(CLineRenderer::EPrimitiveMode::Lines, maxVerts * 2, tex, x26c_26_AAPH, x26c_28_zTest));\n  } else {\n    // m_shaderClass = CElementGenShaders::GetShaderClass(*this);\n  }\n\n  _RecreatePipelines();\n  //  CGraphics::CommitResources([&](boo::IGraphicsDataFactory::Context& ctx) {\n  //    CElementGenShaders::BuildShaderDataBinding(ctx, *this);\n  //    return true;\n  //  } BooTrace);\n}\n\nCElementGen::~CElementGen() {\n  --g_ParticleSystemAliveCount;\n  g_ParticleAliveCount -= x30_particles.size();\n}\n\nbool CElementGen::Update(double t) {\n  s32 oldMax = x90_MAXP;\n  s32 oldMBSP = x270_MBSP;\n  CParticleGlobals::SParticleSystem* prevSystem = CParticleGlobals::instance()->m_currentParticleSystem;\n  CParticleGlobals::SParticleSystem thisSystem{FOURCC('PART'), this};\n  CParticleGlobals::instance()->m_currentParticleSystem = &thisSystem;\n  CGenDescription* desc = x1c_genDesc.GetObj();\n  CIntElement* pswtElem = desc->x10_x4_PSWT.get();\n  if (pswtElem && !x26d_25_warmedUp) {\n    int pswt = 0;\n    pswtElem->GetValue(x74_curFrame, pswt);\n    // spdlog::info(\"Running warmup on particle system 0x%08x for %d ticks.\", desc, pswt);\n    InternalUpdate((1.f / 60.f) * pswt);\n    x26d_25_warmedUp = true;\n  }\n  bool ret = InternalUpdate(t);\n  CParticleGlobals::instance()->m_currentParticleSystem = prevSystem;\n\n  if (oldMax < x90_MAXP || oldMBSP < x270_MBSP) {\n    //_RecreatePipelines();\n  }\n  return ret;\n}\n\nvoid CElementGen::_RecreatePipelines() {\n  size_t maxInsts = x26c_30_MBLR ? 2560 * 2 : 2560; // x26c_30_MBLR ? (x270_MBSP * x90_MAXP) : x90_MAXP;\n  maxInsts = (maxInsts == 0 ? 256 : maxInsts);\n\n  //  CGraphics::CommitResources([&](boo::IGraphicsDataFactory::Context& ctx) {\n  //    if (!x26c_31_LINE) {\n  //      m_instBuf = ctx.newDynamicBuffer(boo::BufferUse::Vertex, ShadClsSizes[size_t(m_shaderClass)], maxInsts);\n  //      m_uniformBuf = ctx.newDynamicBuffer(boo::BufferUse::Uniform, sizeof(SParticleUniforms), 1);\n  //    }\n  //    if (x28_loadedGenDesc->x45_24_x31_26_PMUS) {\n  //      m_instBufPmus = ctx.newDynamicBuffer(boo::BufferUse::Vertex, ShadClsSizes[size_t(m_shaderClass)], maxInsts);\n  //      m_uniformBufPmus = ctx.newDynamicBuffer(boo::BufferUse::Uniform, sizeof(SParticleUniforms), 1);\n  //    }\n  //    return true;\n  //  } BooTrace);\n}\n\nbool CElementGen::InternalUpdate(double dt) {\n  CGlobalRandom gr(x27c_randState);\n  CGenDescription* desc = x1c_genDesc.GetObj();\n\n  double dt1 = 1.0 / 60.0;\n  if (std::fabs(dt - 1.0 / 60.0) >= 1.0 / 60000.0)\n    dt1 = dt;\n  double t = x74_curFrame / 60.0;\n  CParticleGlobals::instance()->SetEmitterTime(x74_curFrame);\n\n  if (CRealElement* pstsElem = desc->x14_x8_PSTS.get()) {\n    float psts;\n    pstsElem->GetValue(x74_curFrame, psts);\n    double dt1Scaled = psts * dt1;\n    dt1 = std::max(0.0, dt1Scaled);\n  }\n\n  x78_curSeconds += dt1;\n\n  if (x26c_30_MBLR && dt > 0.0) {\n    if (CIntElement* mbspElem = desc->x48_x34_MBSP.get())\n      mbspElem->GetValue(x74_curFrame, x270_MBSP);\n  }\n\n  int frameUpdateCount = 0;\n  while (t < x78_curSeconds && std::fabs(t - x78_curSeconds) >= 1.0 / 60000.0) {\n    x2d4_aabbMin.splat(FLT_MAX);\n    x2e0_aabbMax.splat(-FLT_MAX);\n    x2ec_maxSize = 0.f;\n    CParticleGlobals::instance()->SetEmitterTime(x74_curFrame);\n    UpdateExistingParticles();\n    CParticleGlobals::instance()->SetParticleLifetime(x268_PSLT);\n\n    if (x74_curFrame < x268_PSLT && x88_particleEmission) {\n      float grte = 0.f;\n      if (CRealElement* grteElem = desc->x2c_x20_GRTE.get()) {\n        if (grteElem->GetValue(x74_curFrame, grte)) {\n          x30_particles.clear();\n          return true;\n        }\n      }\n\n      grte = std::max(0.f, grte * x98_generatorRate);\n      x8c_generatorRemainder += grte;\n      int genCount = floorf(x8c_generatorRemainder);\n      x8c_generatorRemainder = x8c_generatorRemainder - genCount;\n\n      if (CIntElement* maxpElem = desc->x28_x1c_MAXP.get())\n        maxpElem->GetValue(x74_curFrame, x90_MAXP);\n\n      CreateNewParticles(genCount);\n    }\n\n    if (x26c_24_translationDirty)\n      UpdatePSTranslationAndOrientation();\n\n    if (x308_lightType != LightType::None)\n      UpdateLightParameters();\n\n    UpdateChildParticleSystems(1 / 60.0);\n\n    ++frameUpdateCount;\n    ++x74_curFrame;\n    t += 1 / 60.0;\n  }\n\n  if (std::fabs(t - x78_curSeconds) < 1.0 / 60000.0) {\n    x78_curSeconds = t;\n    x80_timeDeltaScale = 1.f;\n  } else {\n    UpdateChildParticleSystems(dt1 - (frameUpdateCount / 60.0));\n    x80_timeDeltaScale = 1.f - (t - x78_curSeconds) * 60.f;\n  }\n\n  BuildParticleSystemBounds();\n\n  return false;\n}\n\nvoid CElementGen::AccumulateBounds(const zeus::CVector3f& pos, float size) {\n  x2e0_aabbMax[0] = std::max(pos[0], float(x2e0_aabbMax[0]));\n  x2e0_aabbMax[1] = std::max(pos[1], float(x2e0_aabbMax[1]));\n  x2e0_aabbMax[2] = std::max(pos[2], float(x2e0_aabbMax[2]));\n  x2d4_aabbMin[0] = std::min(pos[0], float(x2d4_aabbMin[0]));\n  x2d4_aabbMin[1] = std::min(pos[1], float(x2d4_aabbMin[1]));\n  x2d4_aabbMin[2] = std::min(pos[2], float(x2d4_aabbMin[2]));\n  x2ec_maxSize = std::max(size, x2ec_maxSize);\n}\n\nvoid CElementGen::UpdateAdvanceAccessParameters(u32 activeParticleCount, s32 particleFrame) {\n  CGenDescription* desc = x28_loadedGenDesc;\n\n  if (activeParticleCount >= x60_advValues.size()) {\n    spdlog::fatal(\"activeParticleCount ({}) >= advValues size ({})\", activeParticleCount, x60_advValues.size());\n  }\n\n  std::array<float, 8>& arr = x60_advValues[activeParticleCount];\n  CParticleGlobals::instance()->m_particleAccessParameters = &arr;\n\n  if (CRealElement* adv1 = desc->x10c_ADV1.get()) {\n    adv1->GetValue(particleFrame, arr[0]);\n  }\n  if (CRealElement* adv2 = desc->x110_ADV2.get()) {\n    adv2->GetValue(particleFrame, arr[1]);\n  }\n  if (CRealElement* adv3 = desc->x114_ADV3.get()) {\n    adv3->GetValue(particleFrame, arr[2]);\n  }\n  if (CRealElement* adv4 = desc->x118_ADV4.get()) {\n    adv4->GetValue(particleFrame, arr[3]);\n  }\n  if (CRealElement* adv5 = desc->x11c_ADV5.get()) {\n    adv5->GetValue(particleFrame, arr[4]);\n  }\n  if (CRealElement* adv6 = desc->x120_ADV6.get()) {\n    adv6->GetValue(particleFrame, arr[5]);\n  }\n  if (CRealElement* adv7 = desc->x124_ADV7.get()) {\n    adv7->GetValue(particleFrame, arr[6]);\n  }\n  if (CRealElement* adv8 = desc->x128_ADV8.get()) {\n    adv8->GetValue(particleFrame, arr[7]);\n  }\n}\n\nbool CElementGen::UpdateVelocitySource(size_t idx, s32 particleFrame, CParticle& particle) {\n  bool err;\n  if (x278_hasVMD[idx]) {\n    zeus::CVector3f localVel = x208_orientationInverse * particle.x1c_vel;\n    zeus::CVector3f localPos = x208_orientationInverse * (particle.x4_pos - xdc_translation);\n    err = x280_VELSources[idx]->GetValue(particleFrame, localVel, localPos);\n    particle.x1c_vel = x1d8_orientation.rotate(localVel);\n    particle.x4_pos = x1d8_orientation.rotate(localPos) + xdc_translation;\n  } else {\n    err = x280_VELSources[idx]->GetValue(particleFrame, particle.x1c_vel, particle.x4_pos);\n  }\n\n  if (err) {\n    particle.x0_endFrame = -1;\n    return true;\n  }\n\n  return false;\n}\n\nvoid CElementGen::UpdateExistingParticles() {\n  CGenDescription* desc = x1c_genDesc.GetObj();\n\n  x25c_activeParticleCount = 0;\n  CParticleGlobals::instance()->SetEmitterTime(x74_curFrame);\n  CParticleGlobals::instance()->m_particleAccessParameters = nullptr;\n\n  for (auto it = x30_particles.begin(); it != x30_particles.end();) {\n    CParticle& particle = *it;\n\n    if (particle.x0_endFrame < x74_curFrame) {\n      --g_ParticleAliveCount;\n      if (it + 1 == x30_particles.end()) {\n        x30_particles.pop_back();\n        break;\n      } else {\n        particle = x30_particles.back();\n\n        if (x2c_orientType == EModelOrientationType::One)\n          x50_parentMatrices[x25c_activeParticleCount] = x50_parentMatrices[x30_particles.size() - 1];\n\n        if (x26d_28_enableADV)\n          x60_advValues[x25c_activeParticleCount] = x60_advValues[x30_particles.size() - 1];\n\n        x30_particles.pop_back();\n        if (particle.x0_endFrame < x74_curFrame)\n          continue;\n      }\n    }\n\n    particle.x10_prevPos = particle.x4_pos;\n    particle.x4_pos += particle.x1c_vel;\n\n    g_currentParticle = &particle;\n\n    CParticleGlobals::instance()->SetParticleLifetime(particle.x0_endFrame - particle.x28_startFrame);\n    const int particleFrame = x74_curFrame - particle.x28_startFrame;\n    CParticleGlobals::instance()->UpdateParticleLifetimeTweenValues(particleFrame);\n\n    if (x26d_28_enableADV) {\n      UpdateAdvanceAccessParameters(x25c_activeParticleCount, particleFrame);\n    }\n\n    ++x25c_activeParticleCount;\n\n    for (size_t i = 0; i < x280_VELSources.size(); ++i) {\n      if (!x280_VELSources[i]) {\n        break;\n      }\n      UpdateVelocitySource(i, particleFrame, particle);\n    }\n\n    if (x26c_31_LINE) {\n      if (CRealElement* leng = desc->x20_x14_LENG.get())\n        leng->GetValue(particleFrame, particle.x2c_lineLengthOrSize);\n      if (CRealElement* widt = desc->x24_x18_WIDT.get())\n        widt->GetValue(particleFrame, particle.x30_lineWidthOrRota);\n    } else {\n      if (CRealElement* rota = desc->x50_x3c_ROTA.get())\n        rota->GetValue(particleFrame, particle.x30_lineWidthOrRota);\n      if (CRealElement* size = desc->x4c_x38_SIZE.get())\n        size->GetValue(particleFrame, particle.x2c_lineLengthOrSize);\n    }\n\n    if (CColorElement* colr = desc->x30_x24_COLR.get())\n      colr->GetValue(particleFrame, particle.x34_color);\n\n    AccumulateBounds(particle.x4_pos, particle.x2c_lineLengthOrSize);\n    ++it;\n  }\n\n  if (x30_particles.empty())\n    return;\n\n  for (CWarp* warp : x4_modifierList)\n    if (warp->UpdateWarp())\n      warp->ModifyParticles(x30_particles);\n}\n\nvoid CElementGen::CreateNewParticles(int count) {\n  CGenDescription* desc = x1c_genDesc.GetObj();\n\n  if (!g_ParticleSystemInitialized) {\n    Initialize();\n  }\n\n  if (x30_particles.size() >= x90_MAXP) {\n    return;\n  }\n\n  if (count + x30_particles.size() > x90_MAXP) {\n    count = x90_MAXP - x30_particles.size();\n  }\n\n  if (g_ParticleAliveCount + count > 2560) {\n    count = 2560 - g_ParticleAliveCount;\n  }\n\n  CGlobalRandom gr(x27c_randState);\n  x30_particles.reserve(count + x90_MAXP);\n  if (x26d_28_enableADV && x60_advValues.size() < count + x30_particles.size()) {\n    x60_advValues.resize(std::min(int(x60_advValues.size() * 2), x90_MAXP));\n  }\n\n  CParticleGlobals::instance()->m_particleAccessParameters = nullptr;\n\n  for (int i = 0; i < count; ++i) {\n    CParticle& particle = x30_particles.emplace_back();\n    ++g_ParticleAliveCount;\n    u32 particleCount = x30_particles.size() - 1;\n    ++x25c_activeParticleCount;\n    ++x260_cumulativeParticles;\n    if (x2c_orientType == EModelOrientationType::One) {\n      x50_parentMatrices[x30_particles.size() - 1] = x1d8_orientation.buildMatrix3f();\n    }\n\n    particle.x28_startFrame = x74_curFrame;\n    if (CIntElement* ltme = desc->x34_x28_LTME.get()) {\n      ltme->GetValue(0, particle.x0_endFrame);\n    }\n    CParticleGlobals::instance()->SetParticleLifetime(particle.x0_endFrame);\n    CParticleGlobals::instance()->UpdateParticleLifetimeTweenValues(0);\n    g_currentParticle = &particle;\n    if (x26d_28_enableADV) {\n      UpdateAdvanceAccessParameters(particleCount, 0);\n    }\n    particle.x0_endFrame += x74_curFrame;\n\n    if (CColorElement* colr = desc->x30_x24_COLR.get()) {\n      colr->GetValue(0, particle.x34_color);\n    } else {\n      particle.x34_color = zeus::skWhite;\n    }\n\n    if (CEmitterElement* emtr = desc->x40_x2c_EMTR.get()) {\n      emtr->GetValue(x74_curFrame, particle.x4_pos, particle.x1c_vel);\n      zeus::CVector3f compXf1 = (x13c_globalScaleTransformInverse * x1a8_localScaleTransformInverse) * xdc_translation;\n      zeus::CVector3f compXf2 = x1d8_orientation.rotate(particle.x4_pos);\n      particle.x4_pos = compXf1 + compXf2 + xf4_POFS;\n      particle.x1c_vel = x1d8_orientation.rotate(particle.x1c_vel);\n    } else {\n      zeus::CVector3f compXf1 = (x13c_globalScaleTransformInverse * x1a8_localScaleTransformInverse) * xdc_translation;\n      particle.x4_pos = compXf1 + xf4_POFS;\n      particle.x1c_vel.zeroOut();\n    }\n    particle.x10_prevPos = particle.x4_pos;\n\n    if (x26c_31_LINE) {\n      if (CRealElement* leng = desc->x20_x14_LENG.get())\n        leng->GetValue(0, particle.x2c_lineLengthOrSize);\n      else\n        particle.x2c_lineLengthOrSize = 1.f;\n\n      if (CRealElement* widt = desc->x24_x18_WIDT.get())\n        widt->GetValue(0, particle.x30_lineWidthOrRota);\n      else\n        particle.x30_lineWidthOrRota = 1.f;\n    } else {\n      if (CRealElement* rota = desc->x50_x3c_ROTA.get())\n        rota->GetValue(0, particle.x30_lineWidthOrRota);\n      else\n        particle.x30_lineWidthOrRota = 0.f;\n\n      if (CRealElement* size = desc->x4c_x38_SIZE.get())\n        size->GetValue(0, particle.x2c_lineLengthOrSize);\n      else\n        particle.x2c_lineLengthOrSize = 0.1f;\n    }\n\n    AccumulateBounds(particle.x4_pos, particle.x2c_lineLengthOrSize);\n  }\n}\n\nvoid CElementGen::UpdatePSTranslationAndOrientation() {\n  CGenDescription* desc = x1c_genDesc.GetObj();\n\n  CGlobalRandom gr(x27c_randState);\n  if (x268_PSLT < x74_curFrame)\n    return;\n\n  if (CVectorElement* pofs = desc->x18_xc_POFS.get())\n    pofs->GetValue(x74_curFrame, xf4_POFS);\n\n  if (CVectorElement* sspo = desc->xe8_xd4_SSPO.get())\n    sspo->GetValue(x74_curFrame, x2b0_SSPO);\n\n  if (CVectorElement* sepo = desc->xfc_xe8_SEPO.get())\n    sepo->GetValue(x74_curFrame, x2c0_SEPO);\n}\n\nstd::unique_ptr<CParticleGen> CElementGen::ConstructChildParticleSystem(const TToken<CGenDescription>& desc) const {\n  //OPTICK_EVENT();\n  auto ret = std::make_unique<CElementGen>(desc, EModelOrientationType::Normal,\n                                           x26d_27_enableOPTS ? EOptionalSystemFlags::Two : EOptionalSystemFlags::One);\n  ret->x26d_26_modelsUseLights = x26d_26_modelsUseLights;\n  ret->SetGlobalTranslation(xe8_globalTranslation);\n  ret->SetGlobalOrientation(x22c_globalOrientation);\n  ret->SetGlobalScale(x100_globalScale);\n  ret->SetLocalScale(x16c_localScale);\n  ret->SetTranslation(xdc_translation);\n  ret->SetOrientation(x1d8_orientation);\n  ret->SetParticleEmission(x88_particleEmission);\n  ret->SetModulationColor(x338_moduColor);\n  return ret;\n}\n\nvoid CElementGen::UpdateChildParticleSystems(double dt) {\n  CGenDescription* desc = x1c_genDesc.GetObj();\n\n  CGlobalRandom gr(x27c_randState);\n\n  SChildGeneratorDesc& icts = desc->x8c_x78_ICTS;\n  if (icts && x84_prevFrame != x74_curFrame && x2a0_CSSD == x74_curFrame) {\n    int ncsyVal = 1;\n    if (CIntElement* ncsy = desc->x9c_x88_NCSY.get())\n      ncsy->GetValue(x74_curFrame, ncsyVal);\n\n    CGenDescription* ictsDesc = icts.GetObj();\n    if (!(x26d_27_enableOPTS && ictsDesc->x45_31_x32_25_OPTS)) {\n      x290_activePartChildren.reserve(ncsyVal + x290_activePartChildren.size());\n      for (int i = 0; i < ncsyVal; ++i) {\n        std::unique_ptr<CParticleGen> chGen = ConstructChildParticleSystem(*icts);\n        x290_activePartChildren.emplace_back(std::move(chGen));\n      }\n    }\n  }\n\n  SChildGeneratorDesc& iits = desc->xb8_xa4_IITS;\n  if (iits && x84_prevFrame != x74_curFrame && x74_curFrame < x268_PSLT && x88_particleEmission == 1 &&\n      x74_curFrame >= x2a4_SISY && ((x74_curFrame - x2a4_SISY) % x2a8_PISY) == 0) {\n    CGenDescription* iitsDesc = iits.GetObj();\n    if (!(x26d_27_enableOPTS && iitsDesc->x45_31_x32_25_OPTS)) {\n      std::unique_ptr<CParticleGen> chGen = ConstructChildParticleSystem(*iits);\n      x290_activePartChildren.emplace_back(std::move(chGen));\n    }\n  }\n\n  CSpawnSystemKeyframeData* kssm = desc->xd0_xbc_KSSM.get();\n  if (kssm && x84_prevFrame != x74_curFrame && x74_curFrame < x268_PSLT) {\n    u16 backupSeed = g_GlobalSeed;\n    u16 incSeed = backupSeed;\n\n    std::vector<CSpawnSystemKeyframeData::CSpawnSystemKeyframeInfo>& systems =\n        kssm->GetSpawnedSystemsAtFrame(x74_curFrame);\n    x290_activePartChildren.reserve(x290_activePartChildren.size() + systems.size());\n    for (CSpawnSystemKeyframeData::CSpawnSystemKeyframeInfo& system : systems) {\n      TLockedToken<CGenDescription>& token = system.GetToken();\n      if (!(x26d_27_enableOPTS && token.GetObj()->x45_31_x32_25_OPTS)) {\n        g_GlobalSeed = incSeed;\n        std::unique_ptr<CParticleGen> chGen = ConstructChildParticleSystem(token);\n        x290_activePartChildren.emplace_back(std::move(chGen));\n      }\n      incSeed += 1;\n    }\n\n    g_GlobalSeed = backupSeed;\n  }\n\n  SChildGeneratorDesc& idts = desc->xa4_x90_IDTS;\n  if (idts && x74_curFrame == x268_PSLT && x84_prevFrame != x74_curFrame) {\n    int ndsyVal = 1;\n    if (CIntElement* ndsy = desc->xb4_xa0_NDSY.get())\n      ndsy->GetValue(0, ndsyVal);\n\n    CGenDescription* idtsDesc = idts.GetObj();\n    if (!(x26d_27_enableOPTS && idtsDesc->x45_31_x32_25_OPTS)) {\n      x290_activePartChildren.reserve(ndsyVal + x290_activePartChildren.size());\n      for (int i = 0; i < ndsyVal; ++i) {\n        std::unique_ptr<CParticleGen> chGen = ConstructChildParticleSystem(*idts);\n        x290_activePartChildren.emplace_back(std::move(chGen));\n      }\n    }\n  }\n\n  SSwooshGeneratorDesc& sswh = desc->xd4_xc0_SSWH;\n  if (sswh && x84_prevFrame != x74_curFrame && x74_curFrame == x2ac_SSSD) {\n    std::unique_ptr<CParticleGen> sswhGen = std::make_unique<CParticleSwoosh>(*sswh, 0);\n    sswhGen->SetGlobalTranslation(xe8_globalTranslation);\n    sswhGen->SetGlobalScale(x100_globalScale);\n    sswhGen->SetLocalScale(x16c_localScale);\n    sswhGen->SetTranslation(xdc_translation + x2b0_SSPO);\n    sswhGen->SetOrientation(x1d8_orientation);\n    sswhGen->SetParticleEmission(x88_particleEmission);\n    x290_activePartChildren.emplace_back(std::move(sswhGen));\n  }\n\n  SElectricGeneratorDesc& selc = desc->xec_xd8_SELC;\n  if (selc && x84_prevFrame != x74_curFrame && x74_curFrame == x2bc_SESD) {\n    std::unique_ptr<CParticleGen> selcGen = std::make_unique<CParticleElectric>(*selc);\n    selcGen->SetGlobalTranslation(xe8_globalTranslation);\n    selcGen->SetGlobalScale(x100_globalScale);\n    selcGen->SetLocalScale(x16c_localScale);\n    selcGen->SetTranslation(xdc_translation + x2c0_SEPO);\n    selcGen->SetOrientation(x1d8_orientation);\n    selcGen->SetParticleEmission(x88_particleEmission);\n    x290_activePartChildren.emplace_back(std::move(selcGen));\n  }\n\n  for (auto p = x290_activePartChildren.begin(); p != x290_activePartChildren.end();) {\n    std::unique_ptr<CParticleGen>& ch = *p;\n\n    ch->Update(dt);\n    if (ch->IsSystemDeletable()) {\n      p = x290_activePartChildren.erase(p);\n      continue;\n    }\n\n    ++p;\n  }\n\n  x84_prevFrame = x74_curFrame;\n}\n\nvoid CElementGen::UpdateLightParameters() {\n  CGenDescription* desc = x1c_genDesc.GetObj();\n\n  if (CColorElement* lclr = desc->x104_xf0_LCLR.get())\n    lclr->GetValue(x74_curFrame, x30c_LCLR);\n\n  if (CRealElement* lint = desc->x108_xf4_LINT.get())\n    lint->GetValue(x74_curFrame, x310_LINT);\n\n  switch (x308_lightType) {\n  default:\n  case LightType::None:\n  case LightType::Custom:\n  case LightType::Spot: {\n    if (CVectorElement* loff = desc->x10c_xf8_LOFF.get())\n      loff->GetValue(x74_curFrame, x314_LOFF);\n\n    if (CRealElement* lfor = desc->x118_x104_LFOR.get())\n      lfor->GetValue(x74_curFrame, x330_LFOR);\n\n    if (x308_lightType == LightType::Spot) {\n      if (CRealElement* lsla = desc->x11c_x108_LSLA.get())\n        lsla->GetValue(x74_curFrame, x334_LSLA);\n    }\n    [[fallthrough]];\n  }\n  case LightType::Directional: {\n    if (x308_lightType != LightType::Custom) {\n      if (CVectorElement* ldir = desc->x110_xfc_LDIR.get())\n        ldir->GetValue(x74_curFrame, x320_LDIR);\n    }\n  }\n  }\n}\n\nu32 CElementGen::GetParticleCountAllInternal() const {\n  u32 ret = x25c_activeParticleCount;\n\n  for (const std::unique_ptr<CParticleGen>& ch : x290_activePartChildren)\n    if (ch->Get4CharId() == FOURCC('PART'))\n      ret += static_cast<CElementGen&>(*ch).GetParticleCountAll();\n\n  return ret;\n}\n\nvoid CElementGen::EndLifetime() {\n  x268_PSLT = 0;\n  for (std::unique_ptr<CParticleGen>& ch : x290_activePartChildren) {\n    if (ch->Get4CharId() == FOURCC('PART'))\n      static_cast<CElementGen&>(*ch).EndLifetime();\n    else\n      ch->SetParticleEmission(false);\n  }\n}\n\nvoid CElementGen::ForceParticleCreation(int amount) {\n  CParticleGlobals::SParticleSystem* prevSystem = CParticleGlobals::instance()->m_currentParticleSystem;\n  CParticleGlobals::SParticleSystem thisSystem{FOURCC('PART'), this};\n  CParticleGlobals::instance()->m_currentParticleSystem = &thisSystem;\n  CParticleGlobals::instance()->SetEmitterTime(x74_curFrame);\n  CreateNewParticles(amount);\n  CParticleGlobals::instance()->m_currentParticleSystem = prevSystem;\n}\n\nvoid CElementGen::BuildParticleSystemBounds() {\n  zeus::CAABox aabb;\n  bool accumulated = false;\n\n  for (std::unique_ptr<CParticleGen>& ch : x290_activePartChildren) {\n    auto chBounds = ch->GetBounds();\n    if (chBounds) {\n      accumulated = true;\n      aabb.accumulateBounds(chBounds.value());\n    }\n  }\n\n  x264_recursiveParticleCount = GetParticleCountAllInternal();\n  if (GetParticleCount() > 0) {\n    zeus::CVector3f scale = x100_globalScale * x2ec_maxSize;\n    zeus::CTransform xf = (x10c_globalScaleTransform * x22c_globalOrientation) * x178_localScaleTransform;\n    zeus::CAABox box = zeus::CAABox(x2d4_aabbMin, x2e0_aabbMax).getTransformedAABox(xf);\n    zeus::CVector3f min = box.min + xe8_globalTranslation - scale;\n    zeus::CVector3f max = box.max + xe8_globalTranslation + scale;\n    x2f0_systemBounds = zeus::CAABox(min, max);\n  } else\n    x2f0_systemBounds = zeus::CAABox();\n\n  if (accumulated)\n    x2f0_systemBounds.accumulateBounds(aabb);\n}\n\nu32 CElementGen::GetSystemCount() const {\n  u32 ret = 0;\n  for (const std::unique_ptr<CParticleGen>& child : x290_activePartChildren) {\n    if (child->Get4CharId() == FOURCC('PART')) {\n      ret += static_cast<const CElementGen&>(*child).GetSystemCount();\n    } else {\n      ret += 1;\n    }\n  }\n\n  return ret + (x25c_activeParticleCount != 0);\n}\n\nvoid CElementGen::Render() {\n  SCOPED_GRAPHICS_DEBUG_GROUP(fmt::format(\"CElementGen::Render {}\", *x1c_genDesc.GetObjectTag()).c_str(),\n                              zeus::skYellow);\n\n  CGenDescription* desc = x1c_genDesc.GetObj();\n\n  x274_backupLightActive = CGraphics::mLightActive;\n  CGraphics::DisableAllLights();\n\n  for (std::unique_ptr<CParticleGen>& child : x290_activePartChildren)\n    child->Render();\n\n  CParticleGlobals::SParticleSystem* prevSystem = CParticleGlobals::instance()->m_currentParticleSystem;\n  CParticleGlobals::SParticleSystem thisSystem{FOURCC('PART'), this};\n  CParticleGlobals::instance()->m_currentParticleSystem = &thisSystem;\n\n  if (!x30_particles.empty()) {\n    if (desc->x5c_x48_PMDL || desc->x45_24_x31_26_PMUS) {\n      RenderModels();\n    }\n    if (x26c_31_LINE) {\n      RenderLines();\n    } else {\n      RenderParticles();\n    }\n  }\n\n  CParticleGlobals::instance()->m_currentParticleSystem = prevSystem;\n}\n\nvoid CElementGen::RenderModels() {\n  SCOPED_GRAPHICS_DEBUG_GROUP(fmt::format(\"CElementGen::RenderModels\").c_str(), zeus::skYellow);\n\n  CParticleGlobals::instance()->m_particleAccessParameters = nullptr;\n  if (x26d_26_modelsUseLights) {\n    CGraphics::SetLightState(x274_backupLightActive);\n  } else {\n    CGraphics::SetAmbientColor(zeus::skWhite);\n  }\n  CGlobalRandom gr(x27c_randState);\n\n  CGenDescription* desc = x1c_genDesc.GetObj();\n\n  SUVElementSet uvs = {0.f, 0.f, 1.f, 1.f};\n  CUVElement* texr = desc->x54_x40_TEXR.get();\n  CTexture* cachedTex = nullptr;\n  bool texConst = true;\n  bool moveRedToAlphaBuffer = false;\n\n  if (desc->x45_24_x31_26_PMUS) {\n    if (sMoveRedToAlphaBuffer && desc->x44_31_x31_25_PMAB && desc->x54_x40_TEXR) {\n      moveRedToAlphaBuffer = true;\n    }\n\n    if (desc->x44_31_x31_25_PMAB) {\n      CGraphics::SetDepthWriteMode(true, ERglEnum::LEqual, false);\n      if (moveRedToAlphaBuffer) {\n        CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::One, ERglBlendFactor::One, ERglLogicOp::Clear);\n      } else {\n        CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::One,\n                                ERglLogicOp::Clear);\n        CGraphics::SetAlphaCompare(ERglAlphaFunc::Greater, 0, ERglAlphaOp::And, ERglAlphaFunc::Always, 0);\n      }\n    } else {\n      CGraphics::SetDepthWriteMode(true, ERglEnum::LEqual, true);\n      CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::InvSrcAlpha,\n                              ERglLogicOp::Clear);\n      CGraphics::SetAlphaCompare(ERglAlphaFunc::Greater, 0, ERglAlphaOp::And, ERglAlphaFunc::Always, 0);\n    }\n\n    CGraphics::SetCullMode(ERglCullMode::None);\n\n    if (texr) {\n      CParticle& target = x30_particles[0];\n      int partFrame = x74_curFrame - target.x28_startFrame;\n      cachedTex = texr->GetValueTexture(partFrame).GetObj();\n      cachedTex->Load(GX_TEXMAP0, EClampMode::Repeat);\n\n      CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvModulate);\n      if (moveRedToAlphaBuffer) {\n        CGX::SetTevColorIn(GX_TEVSTAGE1, GX_CC_ZERO, GX_CC_CPREV, GX_CC_APREV, GX_CC_ZERO);\n        CGX::SetTevAlphaIn(GX_TEVSTAGE1, GX_CA_ZERO, GX_CA_TEXA, GX_CA_APREV, GX_CA_ZERO);\n        CGX::SetStandardTevColorAlphaOp(GX_TEVSTAGE1);\n        CGX::SetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL);\n        GXSetTevSwapMode(GX_TEVSTAGE1, GX_TEV_SWAP0, GX_TEV_SWAP1);\n        CGX::SetNumTevStages(2);\n        constexpr std::array vtxDescList{\n            GXVtxDescList{GX_VA_POS, GX_DIRECT},\n            GXVtxDescList{GX_VA_CLR0, GX_DIRECT},\n            GXVtxDescList{GX_VA_TEX0, GX_DIRECT},\n            GXVtxDescList{GX_VA_NULL, GX_NONE},\n        };\n        CGX::SetVtxDescv(vtxDescList.data());\n        CGX::SetChanCtrl(CGX::EChannelId::Channel0, false, GX_SRC_REG, GX_SRC_VTX, {}, GX_DF_NONE, GX_AF_NONE);\n        CGX::SetNumChans(1);\n        CGX::SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY, false, GX_PTIDENTITY);\n        CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0);\n        CGX::SetNumTexGens(1);\n      } else {\n        CGraphics::SetTevOp(ERglTevStage::Stage1, CTevCombiners::kEnvPassthru);\n      }\n\n      texConst = texr->HasConstantTexture();\n      texr->GetValueUV(partFrame, uvs);\n    } else {\n      CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvPassthru);\n      CGraphics::SetTevOp(ERglTevStage::Stage1, CTevCombiners::kEnvPassthru);\n    }\n  }\n\n  zeus::CTransform orient = zeus::CTransform();\n  if (!desc->x45_25_x31_27_PMOO)\n    orient = x1d8_orientation;\n  orient = orient * x22c_globalOrientation;\n\n  CVectorElement* pmrt = desc->x70_x5c_PMRT.get();\n  bool pmrtConst = false;\n  if (pmrt)\n    pmrtConst = pmrt->IsFastConstant();\n\n  zeus::CVector3f trans = (x13c_globalScaleTransformInverse * x1a8_localScaleTransformInverse) * xe8_globalTranslation;\n\n  zeus::CTransform rot = zeus::CTransform();\n  if (pmrtConst) {\n    zeus::CVector3f pmrtVal;\n    pmrt->GetValue(x74_curFrame, pmrtVal);\n    rot = zeus::CTransform::RotateZ(zeus::degToRad(pmrtVal[2]));\n    rot.rotateLocalY(zeus::degToRad(pmrtVal[1]));\n    rot.rotateLocalX(zeus::degToRad(pmrtVal[0]));\n  }\n  rot = orient * rot;\n\n  CParticleGlobals::instance()->SetEmitterTime(x74_curFrame);\n  zeus::CColor col = x338_moduColor;\n\n  zeus::CVector3f pmopVec;\n  auto matrixIt = x50_parentMatrices.begin();\n  for (size_t i = 0; i < x30_particles.size(); ++i) {\n    CParticle& particle = x30_particles[i];\n    g_currentParticle = &particle;\n\n    if (particle.x0_endFrame == -1) {\n      if (x2c_orientType == EModelOrientationType::One)\n        ++matrixIt;\n      continue;\n    }\n    CParticleGlobals::instance()->SetParticleLifetime(particle.x0_endFrame - particle.x28_startFrame);\n    int partFrame = x74_curFrame - particle.x28_startFrame - 1;\n    CParticleGlobals::instance()->UpdateParticleLifetimeTweenValues(partFrame);\n    if (x26d_28_enableADV) {\n      CParticleGlobals::instance()->m_particleAccessParameters = &x60_advValues[i];\n    }\n\n    CVectorElement* pmop = desc->x6c_x58_PMOP.get();\n    if (pmop)\n      pmop->GetValue(partFrame, pmopVec);\n\n    zeus::CTransform partTrans = zeus::CTransform::Translate(particle.x4_pos + trans);\n    if (x2c_orientType == EModelOrientationType::One) {\n      zeus::CTransform partRot(*matrixIt);\n      zeus::CVector3f pmopRotateOffset = (orient * partRot) * pmopVec;\n      partTrans = partTrans * partRot;\n      partTrans += pmopRotateOffset;\n    } else {\n      partTrans += orient * pmopVec;\n    }\n\n    if (pmrtConst) {\n      partTrans = partTrans * rot;\n    } else {\n      if (pmrt) {\n        zeus::CVector3f pmrtVal;\n        pmrt->GetValue(partFrame, pmrtVal);\n        rot = zeus::CTransform::RotateZ(zeus::degToRad(pmrtVal[2]));\n        rot.rotateLocalY(zeus::degToRad(pmrtVal[1]));\n        rot.rotateLocalX(zeus::degToRad(pmrtVal[0]));\n        partTrans = partTrans * (orient * rot);\n      } else {\n        partTrans = partTrans * rot;\n      }\n    }\n\n    CVectorElement* pmsc = desc->x74_x60_PMSC.get();\n    if (pmsc) {\n      zeus::CVector3f pmscVal;\n      pmsc->GetValue(partFrame, pmscVal);\n      partTrans = partTrans * zeus::CTransform::Scale(pmscVal);\n    }\n\n    CColorElement* pmcl = desc->x78_x64_PMCL.get();\n    if (pmcl) {\n      pmcl->GetValue(partFrame, col);\n      col *= x338_moduColor;\n    }\n\n    CGraphics::SetModelMatrix((x10c_globalScaleTransform * partTrans) * x178_localScaleTransform);\n\n    if (desc->x45_24_x31_26_PMUS) {\n      if (moveRedToAlphaBuffer) {\n        CGX::Begin(GX_QUADS, GX_VTXFMT0, 4);\n        GXPosition3f32(0.5f, 0.f, 0.5f);\n        GXColor4f32(col);\n        GXTexCoord2f32(uvs.xMax, uvs.yMax);\n        GXPosition3f32(-0.5f, 0.f, 0.5f);\n        GXColor4f32(col);\n        GXTexCoord2f32(uvs.xMin, uvs.yMax);\n        GXPosition3f32(-0.5f, 0.f, -0.5f);\n        GXColor4f32(col);\n        GXTexCoord2f32(uvs.xMin, uvs.yMin);\n        GXPosition3f32(0.5f, 0.f, -0.5f);\n        GXColor4f32(col);\n        GXTexCoord2f32(uvs.xMax, uvs.yMin);\n        CGX::End();\n      } else {\n        CGraphics::StreamBegin(ERglPrimitive::Quads);\n        CGraphics::StreamColor(col);\n        CGraphics::StreamTexcoord(uvs.xMax, uvs.yMax);\n        CGraphics::StreamVertex(0.5f, 0.f, 0.5f);\n        CGraphics::StreamTexcoord(uvs.xMin, uvs.yMax);\n        CGraphics::StreamVertex(-0.5f, 0.f, 0.5f);\n        CGraphics::StreamTexcoord(uvs.xMin, uvs.yMin);\n        CGraphics::StreamVertex(-0.5f, 0.f, -0.5f);\n        CGraphics::StreamTexcoord(uvs.xMax, uvs.yMin);\n        CGraphics::StreamVertex(0.5f, 0.f, -0.5f);\n        CGraphics::StreamEnd();\n      }\n    } else {\n      CModel* model = desc->x5c_x48_PMDL.GetObj();\n      if (g_subtractBlend) {\n        model->Draw({5, 0, 1, zeus::CColor{1.f, 0.5f}});\n      } else if (desc->x44_31_x31_25_PMAB) {\n        model->Draw({7, 0, 1, col});\n      } else if (1.f == col.a()) {\n        model->Draw({0, 0, 3, zeus::skWhite});\n      } else {\n        model->Draw({5, 0, 1, zeus::CColor{1.f, col.a()}});\n      }\n    }\n\n    if (x2c_orientType == EModelOrientationType::One)\n      ++matrixIt;\n  }\n\n  if (x26d_26_modelsUseLights) {\n    CGraphics::DisableAllLights();\n  }\n\n  CGraphics::SetCullMode(ERglCullMode::Front);\n  CTevCombiners::ResetStates();\n  if (moveRedToAlphaBuffer) {\n    GXSetTevSwapMode(GX_TEVSTAGE1, GX_TEV_SWAP0, GX_TEV_SWAP0);\n  }\n  CGraphics::SetAlphaCompare(ERglAlphaFunc::Always, 0, ERglAlphaOp::And, ERglAlphaFunc::Always, 0);\n}\n\nvoid CElementGen::RenderLines() {\n  SCOPED_GRAPHICS_DEBUG_GROUP(fmt::format(\"CElementGen::RenderLines\").c_str(), zeus::skYellow);\n\n  CGenDescription* desc = x1c_genDesc.GetObj();\n  CGlobalRandom gr(x27c_randState);\n\n  zeus::CTransform systemViewPointMatrix(CGraphics::mViewMatrix);\n  systemViewPointMatrix.origin.zeroOut();\n  zeus::CTransform systemCameraMatrix = systemViewPointMatrix.inverse() * x22c_globalOrientation;\n  systemViewPointMatrix =\n      ((zeus::CTransform::Translate(xe8_globalTranslation) * x10c_globalScaleTransform) * systemViewPointMatrix) *\n      x178_localScaleTransform;\n  CGraphics::SetModelMatrix(systemViewPointMatrix);\n\n  CGraphics::SetAlphaCompare(ERglAlphaFunc::Always, 0, ERglAlphaOp::And, ERglAlphaFunc::Always, 0);\n\n  if (x26c_26_AAPH) {\n    CGraphics::SetDepthWriteMode(true, ERglEnum::LEqual, false);\n    CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::One, ERglLogicOp::Clear);\n  } else {\n    CGraphics::SetDepthWriteMode(true, ERglEnum::LEqual, true);\n    CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::InvSrcAlpha,\n                            ERglLogicOp::Clear);\n  }\n\n  CRealElement* widt = desc->x24_x18_WIDT.get();\n  bool widtConst = false;\n  if (widt)\n    widtConst = widt->IsConstant();\n\n  CUVElement* texr = desc->x54_x40_TEXR.get();\n  SUVElementSet uvs = {0.f, 0.f, 1.f, 1.f};\n  bool constTexr = true;\n  bool constUVs = true;\n  CTexture* cachedTex = nullptr;\n  zeus::CColor moduColor = zeus::skWhite;\n  if (texr) {\n    CParticle& target = x30_particles[0];\n    int partFrame = x74_curFrame - target.x28_startFrame;\n    cachedTex = texr->GetValueTexture(partFrame).GetObj();\n    cachedTex->Load(GX_TEXMAP0, EClampMode::Repeat);\n\n    /* Set TEXC * RASC */\n\n    if (x338_moduColor != zeus::skBlack) {\n      /* Add RASC * PREVC pass for MODU color loaded into channel mat-color */\n      moduColor = x338_moduColor;\n    }\n\n    constTexr = texr->HasConstantTexture();\n    texr->GetValueUV(partFrame, uvs);\n    constUVs = texr->HasConstantUV();\n  }\n\n  float constWidth = 1.f;\n  if (widtConst) {\n    widt->GetValue(0, constWidth);\n    constWidth = std::max(0.f, std::min(constWidth, 42.5f));\n  }\n\n  // m_lineRenderer->Reset();\n\n  for (auto& particle : x30_particles) {\n    g_currentParticle = &particle;\n\n    int partFrame = x74_curFrame - particle.x28_startFrame;\n\n    if (!constTexr) {\n      CTexture* tex = texr->GetValueTexture(partFrame).GetObj();\n      if (tex != cachedTex) {\n        tex->Load(GX_TEXMAP0, EClampMode::Repeat);\n        cachedTex = tex;\n      }\n    }\n\n    if (!constUVs)\n      texr->GetValueUV(partFrame, uvs);\n\n    zeus::CVector3f dVec = particle.x4_pos - particle.x10_prevPos;\n    if (x26d_24_FXLL)\n      if (dVec.magSquared() >= 0.f)\n        dVec.normalize();\n\n    zeus::CVector3f p1 = systemCameraMatrix * particle.x4_pos;\n    zeus::CVector3f p2 = systemCameraMatrix * (particle.x2c_lineLengthOrSize * dVec + particle.x4_pos);\n\n    if (widtConst) {\n      // m_lineRenderer->AddVertex(p1, particle.x34_color, constWidth, {uvs.xMin, uvs.yMin});\n      // m_lineRenderer->AddVertex(p2, particle.x34_color, constWidth, {uvs.xMax, uvs.yMax});\n    } else if (widt) {\n      float width = 1.f;\n      widt->GetValue(0, width);\n      width = std::max(0.f, std::min(width, 42.5f));\n      // m_lineRenderer->AddVertex(p1, particle.x34_color, width, {uvs.xMin, uvs.yMin});\n      // m_lineRenderer->AddVertex(p2, particle.x34_color, width, {uvs.xMax, uvs.yMax});\n    }\n  }\n\n  // m_lineRenderer->Render(g_Renderer->IsThermalVisorHotPass(), moduColor);\n}\n\nvoid CElementGen::RenderParticles() {\n  CGenDescription* desc = x1c_genDesc.GetObj();\n  CGlobalRandom gr(x27c_randState);\n\n  if (IsIndirectTextured()) {\n    RenderParticlesIndirectTexture();\n    return;\n  }\n\n  SCOPED_GRAPHICS_DEBUG_GROUP(fmt::format(\"CElementGen::RenderParticles\").c_str(), zeus::skYellow);\n\n  CRealElement* size = desc->x4c_x38_SIZE.get();\n  if (size && size->IsConstant()) {\n    float sizeVal;\n    size->GetValue(0, sizeVal);\n    if (sizeVal == 0.f) {\n      size->GetValue(1, sizeVal);\n      if (sizeVal == 0.f)\n        return;\n    }\n  }\n\n  bool hasModuColor = x338_moduColor != zeus::skWhite;\n  CGraphics::SetCullMode(ERglCullMode::None);\n  zeus::CTransform systemModelMatrix(CGraphics::mViewMatrix);\n  systemModelMatrix.origin.zeroOut();\n  zeus::CTransform systemCameraMatrix = systemModelMatrix.inverse() * x22c_globalOrientation;\n  systemModelMatrix =\n      ((zeus::CTransform::Translate(xe8_globalTranslation) * x10c_globalScaleTransform) * systemModelMatrix) *\n      x178_localScaleTransform;\n  if (x26c_29_ORNT)\n    CGraphics::SetModelMatrix(systemModelMatrix * systemCameraMatrix);\n  else\n    CGraphics::SetModelMatrix(systemModelMatrix);\n\n  CGraphics::SetAlphaCompare(ERglAlphaFunc::Greater, 0, ERglAlphaOp::And, ERglAlphaFunc::Always, 0);\n\n  SUVElementSet uvs = {0.f, 0.f, 1.f, 1.f};\n  bool constUVs = true;\n  CTexture* cachedTex = nullptr;\n\n  auto* rota = x28_loadedGenDesc->x50_x3c_ROTA.get();\n  bool noRota = rota == nullptr;\n  if (rota != nullptr && rota->IsConstant()) {\n    float value = 1.f;\n    rota->GetValue(0, value);\n    if (value == 0.f) {\n      value = 1.f;\n      rota->GetValue(1, value);\n      if (value == 0.f) {\n        noRota = true;\n      }\n    }\n  }\n\n  auto* texr = x28_loadedGenDesc->x54_x40_TEXR.get();\n  if (texr != nullptr) {\n    CParticle& target = x30_particles[0];\n    int partFrame = x74_curFrame - target.x28_startFrame;\n    cachedTex = texr->GetValueTexture(partFrame).GetObj();\n    cachedTex->Load(GX_TEXMAP0, EClampMode::Repeat);\n\n    CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvModulate);\n    if (hasModuColor) {\n      /* Add RASC * PREVC pass for MODU color loaded into channel mat-color */\n      static const CTevCombiners::CTevPass kEnvModuColor{\n          {GX_CC_ZERO, GX_CC_CPREV, GX_CC_RASC, GX_CC_ZERO},\n          {GX_CA_ZERO, GX_CA_APREV, GX_CA_RASA, GX_CA_ZERO},\n      };\n      CGraphics::SetTevOp(ERglTevStage::Stage1, kEnvModuColor);\n    } else {\n      CGraphics::SetTevOp(ERglTevStage::Stage1, CTevCombiners::kEnvPassthru);\n    }\n\n    texr->GetValueUV(partFrame, uvs);\n    constUVs = texr->HasConstantUV();\n  } else {\n    CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvPassthru);\n    CGraphics::SetTevOp(ERglTevStage::Stage1, CTevCombiners::kEnvPassthru);\n  }\n\n  constexpr std::array vtxDescList{\n      GXVtxDescList{GX_VA_POS, GX_DIRECT},\n      GXVtxDescList{GX_VA_CLR0, GX_DIRECT},\n      GXVtxDescList{GX_VA_TEX0, GX_DIRECT},\n      GXVtxDescList{GX_VA_NULL, GX_NONE},\n  };\n  CGX::SetVtxDescv(vtxDescList.data());\n  GXTevStageID nextStage;\n  if (hasModuColor) {\n    CGX::SetNumChans(2);\n    nextStage = GX_TEVSTAGE2;\n    CGX::SetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR1A1);\n    CGX::SetChanAmbColor(CGX::EChannelId::Channel1, zeus::skBlack);\n    CGX::SetChanMatColor(CGX::EChannelId::Channel1, x338_moduColor);\n    CGX::SetChanCtrl(CGX::EChannelId::Channel1, false, GX_SRC_REG, GX_SRC_REG, {}, GX_DF_NONE, GX_AF_NONE);\n  } else {\n    CGX::SetNumChans(1);\n    nextStage = GX_TEVSTAGE1;\n  }\n\n  bool moveRedToAlphaBuffer = sMoveRedToAlphaBuffer;\n  if (g_subtractBlend) {\n    CGraphics::SetDepthWriteMode(x26c_28_zTest, ERglEnum::LEqual, false);\n    CGX::SetBlendMode(GX_BM_SUBTRACT, GX_BL_ONE, GX_BL_ZERO, GX_LO_CLEAR);\n    if (moveRedToAlphaBuffer) {\n      CGX::SetTevColorIn(nextStage, GX_CC_ZERO, GX_CC_CPREV, GX_CC_APREV, GX_CC_ZERO);\n      CGX::SetTevAlphaIn(nextStage, GX_CA_ZERO, GX_CA_TEXA, GX_CA_APREV, GX_CA_ZERO);\n      CGX::SetStandardTevColorAlphaOp(nextStage);\n      CGX::SetTevOrder(nextStage, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL);\n      GXSetTevSwapMode(nextStage, GX_TEV_SWAP0, GX_TEV_SWAP1);\n      nextStage = GXTevStageID(nextStage + 1);\n    }\n  } else if (moveRedToAlphaBuffer) {\n    CGraphics::SetDepthWriteMode(x26c_28_zTest, ERglEnum::LEqual, false);\n    CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::One, ERglBlendFactor::One, ERglLogicOp::Clear);\n    CGX::SetTevColorIn(nextStage, GX_CC_ZERO, GX_CC_CPREV, GX_CC_APREV, GX_CC_ZERO);\n    CGX::SetTevAlphaIn(nextStage, GX_CA_ZERO, GX_CA_TEXA, GX_CA_APREV, GX_CA_ZERO);\n    CGX::SetStandardTevColorAlphaOp(nextStage);\n    CGX::SetTevOrder(nextStage, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL);\n    GXSetTevSwapMode(nextStage, GX_TEV_SWAP0, GX_TEV_SWAP1);\n    nextStage = GXTevStageID(nextStage + 1);\n  } else if (x26c_26_AAPH) {\n    CGraphics::SetDepthWriteMode(x26c_28_zTest, ERglEnum::LEqual, false);\n    CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::One, ERglLogicOp::Clear);\n  } else {\n    CGraphics::SetDepthWriteMode(x26c_28_zTest, ERglEnum::LEqual, x26c_27_ZBUF);\n    CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::InvSrcAlpha,\n                            ERglLogicOp::Clear);\n  }\n  CGX::SetNumTevStages(nextStage);\n  CGX::SetNumTexGens(1);\n  CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0);\n  CGX::SetChanCtrl(CGX::EChannelId::Channel0, false, GX_SRC_REG, GX_SRC_VTX, {}, GX_DF_NONE, GX_AF_NONE);\n  CGX::SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY, false, GX_PTIDENTITY);\n  GXSetVtxAttrFmt(GX_VTXFMT6, GX_VA_POS, GX_POS_XYZ, GX_F32, 0);\n  GXSetVtxAttrFmt(GX_VTXFMT6, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);\n  if (constUVs) {\n    GXSetVtxAttrFmt(GX_VTXFMT6, GX_VA_TEX0, GX_TEX_ST, GX_S8, 1);\n  } else {\n    GXSetVtxAttrFmt(GX_VTXFMT6, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0);\n  }\n\n  int mbspVal = std::max(1, x270_MBSP);\n  if (x26c_30_MBLR || x26c_29_ORNT) { // TODO: temp added x26c_29_ORNT to return early\n    // CGX::Begin(GX_QUADS, GX_VTXFMT6, mbspVal * x30_particles.size() * 4);\n\n    // TODO: temp hack to return early since the x26c_30_MBLR case is unimplemented below\n    if (moveRedToAlphaBuffer) {\n      GXSetTevSwapMode(GXTevStageID(nextStage - 1), GX_TEV_SWAP0, GX_TEV_SWAP0);\n    }\n    CGraphics::SetCullMode(ERglCullMode::Front);\n    CGraphics::SetAlphaCompare(ERglAlphaFunc::Always, 0, ERglAlphaOp::And, ERglAlphaFunc::Always, 0);\n    return;\n  } else {\n    CGX::Begin(GX_QUADS, GX_VTXFMT6, x30_particles.size() * 4);\n  }\n\n  std::vector<CParticleListItem> sortItems;\n  if (desc->x44_28_x30_28_SORT) {\n    sortItems.reserve(x30_particles.size());\n\n    for (size_t i = 0; i < x30_particles.size(); ++i) {\n      const CParticle& particle = x30_particles[i];\n      sortItems.emplace_back(s16(i));\n      CParticleListItem& sortItem = sortItems.back();\n      sortItem.x4_viewPoint =\n          systemCameraMatrix * ((particle.x4_pos - particle.x10_prevPos) * x80_timeDeltaScale + particle.x10_prevPos);\n    }\n\n    std::sort(sortItems.begin(), sortItems.end(), [](const CParticleListItem& a, const CParticleListItem& b) -> bool {\n      return a.x4_viewPoint[1] > b.x4_viewPoint[1];\n    });\n  }\n\n  CParticleGlobals::instance()->SetEmitterTime(x74_curFrame);\n  if (!x26c_30_MBLR) {\n    if (!desc->x44_28_x30_28_SORT && constUVs && !x26c_29_ORNT) {\n      if (noRota) {\n        if (zeus::close_enough(x80_timeDeltaScale, 1.f)) {\n          RenderBasicParticlesNoRotNoTS(systemCameraMatrix);\n        } else {\n          RenderBasicParticlesNoRotTS(systemCameraMatrix);\n        }\n      } else if (zeus::close_enough(x80_timeDeltaScale, 1.f)) {\n        RenderBasicParticlesRotNoTS(systemCameraMatrix);\n      } else {\n        RenderBasicParticlesRotTS(systemCameraMatrix);\n      }\n    } else if (!x26c_29_ORNT) {\n      for (size_t i = 0; i < x30_particles.size(); ++i) {\n        const int partIdx = desc->x44_28_x30_28_SORT ? sortItems[i].x0_partIdx : int(i);\n        CParticle& particle = x30_particles[partIdx];\n        g_currentParticle = &particle;\n\n        const int partFrame = x74_curFrame - particle.x28_startFrame - 1;\n        zeus::CVector3f viewPoint;\n        if (desc->x44_28_x30_28_SORT) {\n          viewPoint = sortItems[i].x4_viewPoint;\n        } else {\n          viewPoint = systemCameraMatrix *\n                      ((particle.x4_pos - particle.x10_prevPos) * x80_timeDeltaScale + particle.x10_prevPos);\n        }\n\n        const float size = 0.5f * particle.x2c_lineLengthOrSize;\n        if (!constUVs) {\n          CParticleGlobals::instance()->SetParticleLifetime(particle.x0_endFrame - particle.x28_startFrame);\n          CParticleGlobals::instance()->UpdateParticleLifetimeTweenValues(partFrame);\n          texr->GetValueUV(partFrame, uvs);\n          if (noRota) {\n            GXPosition3f32(viewPoint.x() + size, viewPoint.y(), viewPoint.z() + size);\n            GXColor4f32(particle.x34_color);\n            GXTexCoord2f32(uvs.xMax, uvs.yMax);\n            GXPosition3f32(viewPoint.x() - size, viewPoint.y(), viewPoint.z() + size);\n            GXColor4f32(particle.x34_color);\n            GXTexCoord2f32(uvs.xMin, uvs.yMax);\n            GXPosition3f32(viewPoint.x() - size, viewPoint.y(), viewPoint.z() - size);\n            GXColor4f32(particle.x34_color);\n            GXTexCoord2f32(uvs.xMin, uvs.yMin);\n            GXPosition3f32(viewPoint.x() + size, viewPoint.y(), viewPoint.z() - size);\n            GXColor4f32(particle.x34_color);\n            GXTexCoord2f32(uvs.xMax, uvs.yMin);\n          } else {\n            const float theta = zeus::degToRad(particle.x30_lineWidthOrRota);\n            const float sinT = std::sin(theta) * size;\n            const float cosT = std::cos(theta) * size;\n            GXPosition3f32(viewPoint.x() + (sinT + cosT), viewPoint.y(), viewPoint.z() + (cosT - sinT));\n            GXColor4f32(particle.x34_color);\n            GXTexCoord2f32(uvs.xMax, uvs.yMax);\n            GXPosition3f32(viewPoint.x() + (sinT - cosT), viewPoint.y(), viewPoint.z() + (sinT + cosT));\n            GXColor4f32(particle.x34_color);\n            GXTexCoord2f32(uvs.xMin, uvs.yMax);\n            GXPosition3f32(viewPoint.x() - (sinT + cosT), viewPoint.y(), viewPoint.z() - (cosT - sinT));\n            GXColor4f32(particle.x34_color);\n            GXTexCoord2f32(uvs.xMin, uvs.yMin);\n            GXPosition3f32(viewPoint.x() + (-sinT + cosT), viewPoint.y(), viewPoint.z() + (-cosT - sinT));\n            GXColor4f32(particle.x34_color);\n            GXTexCoord2f32(uvs.xMax, uvs.yMin);\n          }\n        } else if (noRota) {\n          GXPosition3f32(viewPoint.x() + size, viewPoint.y(), viewPoint.z() + size);\n          GXColor4f32(particle.x34_color);\n          GXTexCoord2s8(2, 2);\n          GXPosition3f32(viewPoint.x() - size, viewPoint.y(), viewPoint.z() + size);\n          GXColor4f32(particle.x34_color);\n          GXTexCoord2s8(0, 2);\n          GXPosition3f32(viewPoint.x() - size, viewPoint.y(), viewPoint.z() - size);\n          GXColor4f32(particle.x34_color);\n          GXTexCoord2s8(0, 0);\n          GXPosition3f32(viewPoint.x() + size, viewPoint.y(), viewPoint.z() - size);\n          GXColor4f32(particle.x34_color);\n          GXTexCoord2s8(2, 0);\n        } else {\n          const float theta = zeus::degToRad(particle.x30_lineWidthOrRota);\n          const float sinT = std::sin(theta) * size;\n          const float cosT = std::cos(theta) * size;\n          GXPosition3f32(viewPoint.x() + (sinT + cosT), viewPoint.y(), viewPoint.z() + (cosT - sinT));\n          GXColor4f32(particle.x34_color);\n          GXTexCoord2s8(2, 2);\n          GXPosition3f32(viewPoint.x() + (sinT - cosT), viewPoint.y(), viewPoint.z() + (sinT + cosT));\n          GXColor4f32(particle.x34_color);\n          GXTexCoord2s8(0, 2);\n          GXPosition3f32(viewPoint.x() - (sinT + cosT), viewPoint.y(), viewPoint.z() - (cosT - sinT));\n          GXColor4f32(particle.x34_color);\n          GXTexCoord2s8(0, 0);\n          GXPosition3f32(viewPoint.x() + (-sinT + cosT), viewPoint.y(), viewPoint.z() + (-cosT - sinT));\n          GXColor4f32(particle.x34_color);\n          GXTexCoord2s8(2, 0);\n        }\n      }\n    } else {\n      for (size_t i = 0; i < x30_particles.size(); ++i) {\n        const int partIdx = desc->x44_28_x30_28_SORT ? sortItems[i].x0_partIdx : int(i);\n        CParticle& particle = x30_particles[partIdx];\n        g_currentParticle = &particle;\n\n        const int partFrame = x74_curFrame - particle.x28_startFrame - 1;\n        zeus::CVector3f viewPoint =\n            ((particle.x4_pos - particle.x10_prevPos) * x80_timeDeltaScale + particle.x10_prevPos);\n        const float width = !desc->x50_x3c_ROTA ? 1.f : particle.x30_lineWidthOrRota;\n        zeus::CVector3f dir;\n        if (particle.x1c_vel.canBeNormalized()) {\n          dir = particle.x1c_vel.normalized();\n        } else {\n          zeus::CVector3f delta = particle.x4_pos - particle.x10_prevPos;\n          if (delta.canBeNormalized())\n            dir = delta.normalized();\n          else\n            dir = zeus::skUp;\n        }\n\n        zeus::CVector3f foreVec = particle.x2c_lineLengthOrSize * dir;\n        zeus::CVector3f rightVec;\n        if (desc->x30_31_RSOP) {\n          rightVec = dir.cross(CGraphics::mViewMatrix.basis[1]);\n          if (rightVec.canBeNormalized()) {\n            rightVec = rightVec.normalized() * (particle.x2c_lineLengthOrSize * width);\n          } else {\n            rightVec = dir.cross((CGraphics::mViewMatrix.origin - particle.x4_pos).normalized());\n            if (rightVec.canBeNormalized()) {\n              rightVec = rightVec.normalized() * (particle.x2c_lineLengthOrSize * width);\n            }\n          }\n        } else {\n          rightVec = foreVec.cross(CGraphics::mViewMatrix.basis[1]) * width;\n        }\n\n        if (!constUVs) {\n          CParticleGlobals::instance()->SetParticleLifetime(particle.x0_endFrame - particle.x28_startFrame);\n          CParticleGlobals::instance()->UpdateParticleLifetimeTweenValues(partFrame);\n          texr->GetValueUV(partFrame, uvs);\n        }\n\n//        switch (m_shaderClass) {\n//        case CElementGenShaders::EShaderClass::Tex: {\n//          SParticleInstanceTex& inst = g_instTexData.emplace_back();\n//          viewPoint += rightVec * 0.5f;\n//          inst.pos[0] = zeus::CVector4f{viewPoint + 0.5f * foreVec};\n//          inst.pos[1] = zeus::CVector4f{viewPoint - 0.5f * foreVec};\n//          viewPoint -= rightVec;\n//          inst.pos[2] = zeus::CVector4f{viewPoint + 0.5f * foreVec};\n//          inst.pos[3] = zeus::CVector4f{viewPoint - 0.5f * foreVec};\n//          inst.color = particle.x34_color;\n//          inst.uvs[0] = {uvs.xMax, uvs.yMax};\n//          inst.uvs[1] = {uvs.xMin, uvs.yMax};\n//          inst.uvs[2] = {uvs.xMax, uvs.yMin};\n//          inst.uvs[3] = {uvs.xMin, uvs.yMin};\n//          break;\n//        }\n//        case CElementGenShaders::EShaderClass::NoTex: {\n//          SParticleInstanceNoTex& inst = g_instNoTexData.emplace_back();\n//          viewPoint += rightVec * 0.5f;\n//          inst.pos[0] = zeus::CVector4f{viewPoint + 0.5f * foreVec};\n//          inst.pos[1] = zeus::CVector4f{viewPoint - 0.5f * foreVec};\n//          viewPoint -= rightVec;\n//          inst.pos[2] = zeus::CVector4f{viewPoint + 0.5f * foreVec};\n//          inst.pos[3] = zeus::CVector4f{viewPoint - 0.5f * foreVec};\n//          inst.color = particle.x34_color;\n//          break;\n//        }\n//        default:\n//          break;\n//        }\n      }\n    }\n\n    // switch (m_shaderClass) {\n    // case CElementGenShaders::EShaderClass::Tex:\n    //   m_instBuf->load(g_instTexData.data(), g_instTexData.size() * sizeof(SParticleInstanceTex));\n    //   CGraphics::DrawInstances(0, 4, g_instTexData.size());\n    //   break;\n    // case CElementGenShaders::EShaderClass::NoTex:\n    //   m_instBuf->load(g_instNoTexData.data(), g_instNoTexData.size() * sizeof(SParticleInstanceNoTex));\n    //   CGraphics::DrawInstances(0, 4, g_instNoTexData.size());\n    //   break;\n    // default:\n    //   break;\n    // }\n  } else {\n//    switch (m_shaderClass) {\n//    case CElementGenShaders::EShaderClass::Tex:\n//      g_instTexData.clear();\n//      g_instTexData.reserve(x30_particles.size() * mbspVal);\n//      break;\n//    case CElementGenShaders::EShaderClass::NoTex:\n//      g_instNoTexData.clear();\n//      g_instNoTexData.reserve(x30_particles.size() * mbspVal);\n//      break;\n//    default:\n//      spdlog::fatal(\"unexpected particle shader class\");\n//      break;\n//    }\n    const float mbspFac = 1.f / float(mbspVal);\n    for (size_t i = 0; i < x30_particles.size(); ++i) {\n      const int partIdx = desc->x44_28_x30_28_SORT ? sortItems[i].x0_partIdx : int(i);\n      CParticle& particle = x30_particles[partIdx];\n      g_currentParticle = &particle;\n\n      const int partFrame = x74_curFrame - particle.x28_startFrame - 1;\n\n      if (!constUVs) {\n        CParticleGlobals::instance()->SetParticleLifetime(particle.x0_endFrame - particle.x28_startFrame);\n        CParticleGlobals::instance()->UpdateParticleLifetimeTweenValues(partFrame);\n        texr->GetValueUV(partFrame, uvs);\n      }\n\n      zeus::CVector3f dVec = particle.x4_pos - particle.x10_prevPos;\n      zeus::CVector3f vec = dVec * x80_timeDeltaScale + particle.x10_prevPos;\n      zeus::CVector3f mbspVec = dVec * mbspFac;\n      float size = 0.5f * particle.x2c_lineLengthOrSize;\n      if (0.f == particle.x30_lineWidthOrRota) {\n        for (int j = 0; j < mbspVal; ++j) {\n          vec += mbspVec;\n          zeus::CVector3f vec2 = systemCameraMatrix * vec;\n\n//          switch (m_shaderClass) {\n//          case CElementGenShaders::EShaderClass::Tex: {\n//            SParticleInstanceTex& inst = g_instTexData.emplace_back();\n//            inst.pos[0] = zeus::CVector4f{vec2.x() + size, vec2.y(), vec2.z() + size, 1.f};\n//            inst.pos[1] = zeus::CVector4f{vec2.x() - size, vec2.y(), vec2.z() + size, 1.f};\n//            inst.pos[2] = zeus::CVector4f{vec2.x() + size, vec2.y(), vec2.z() - size, 1.f};\n//            inst.pos[3] = zeus::CVector4f{vec2.x() - size, vec2.y(), vec2.z() - size, 1.f};\n//            inst.color = particle.x34_color;\n//            inst.uvs[0] = {uvs.xMax, uvs.yMax};\n//            inst.uvs[1] = {uvs.xMin, uvs.yMax};\n//            inst.uvs[2] = {uvs.xMax, uvs.yMin};\n//            inst.uvs[3] = {uvs.xMin, uvs.yMin};\n//            break;\n//          }\n//          case CElementGenShaders::EShaderClass::NoTex: {\n//            SParticleInstanceNoTex& inst = g_instNoTexData.emplace_back();\n//            inst.pos[0] = zeus::CVector4f{vec2.x() + size, vec2.y(), vec2.z() + size, 1.f};\n//            inst.pos[1] = zeus::CVector4f{vec2.x() - size, vec2.y(), vec2.z() + size, 1.f};\n//            inst.pos[2] = zeus::CVector4f{vec2.x() + size, vec2.y(), vec2.z() - size, 1.f};\n//            inst.pos[3] = zeus::CVector4f{vec2.x() - size, vec2.y(), vec2.z() - size, 1.f};\n//            inst.color = particle.x34_color;\n//            break;\n//          }\n//          default:\n//            break;\n//          }\n        }\n      } else {\n        float theta = zeus::degToRad(particle.x30_lineWidthOrRota);\n        float sinT = std::sin(theta) * size;\n        float cosT = std::cos(theta) * size;\n\n        for (int j = 0; j < mbspVal; ++j) {\n          vec += mbspVec;\n          zeus::CVector3f vec2 = systemCameraMatrix * vec;\n\n//          switch (m_shaderClass) {\n//          case CElementGenShaders::EShaderClass::Tex: {\n//            SParticleInstanceTex& inst = g_instTexData.emplace_back();\n//            inst.pos[0] = zeus::CVector4f{vec2.x() + sinT + cosT, vec2.y(), vec2.z() + cosT - sinT, 1.f};\n//            inst.pos[1] = zeus::CVector4f{vec2.x() + sinT - cosT, vec2.y(), vec2.z() + sinT + cosT, 1.f};\n//            inst.pos[2] = zeus::CVector4f{vec2.x() + (cosT - sinT), vec2.y(), vec2.z() + (-cosT - sinT), 1.f};\n//            inst.pos[3] = zeus::CVector4f{vec2.x() - (sinT + cosT), vec2.y(), vec2.z() - (cosT - sinT), 1.f};\n//            inst.color = particle.x34_color;\n//            inst.uvs[0] = {uvs.xMax, uvs.yMax};\n//            inst.uvs[1] = {uvs.xMin, uvs.yMax};\n//            inst.uvs[2] = {uvs.xMax, uvs.yMin};\n//            inst.uvs[3] = {uvs.xMin, uvs.yMin};\n//            break;\n//          }\n//          case CElementGenShaders::EShaderClass::NoTex: {\n//            SParticleInstanceNoTex& inst = g_instNoTexData.emplace_back();\n//            inst.pos[0] = zeus::CVector4f{vec2.x() + sinT + cosT, vec2.y(), vec2.z() + cosT - sinT, 1.f};\n//            inst.pos[1] = zeus::CVector4f{vec2.x() + sinT - cosT, vec2.y(), vec2.z() + sinT + cosT, 1.f};\n//            inst.pos[2] = zeus::CVector4f{vec2.x() + (cosT - sinT), vec2.y(), vec2.z() + (-cosT - sinT), 1.f};\n//            inst.pos[3] = zeus::CVector4f{vec2.x() - (sinT + cosT), vec2.y(), vec2.z() - (cosT - sinT), 1.f};\n//            inst.color = particle.x34_color;\n//            break;\n//          }\n//          default:\n//            break;\n//          }\n        }\n      }\n    }\n    // switch (m_shaderClass) {\n    // case CElementGenShaders::EShaderClass::Tex:\n    //   //      m_instBuf->load(g_instTexData.data(), g_instTexData.size() * sizeof(SParticleInstanceTex));\n    //   //      CGraphics::DrawInstances(0, 4, g_instTexData.size());\n    //   break;\n    // case CElementGenShaders::EShaderClass::NoTex:\n    //   //      m_instBuf->load(g_instNoTexData.data(), g_instNoTexData.size() * sizeof(SParticleInstanceNoTex));\n    //   //      CGraphics::DrawInstances(0, 4, g_instNoTexData.size());\n    //   break;\n    // default:\n    //   break;\n    // }\n  }\n\n  CGX::End();\n  if (moveRedToAlphaBuffer) {\n    GXSetTevSwapMode(GXTevStageID(nextStage - 1), GX_TEV_SWAP0, GX_TEV_SWAP0);\n  }\n  CGraphics::SetCullMode(ERglCullMode::Front);\n  CGraphics::SetAlphaCompare(ERglAlphaFunc::Always, 0, ERglAlphaOp::And, ERglAlphaFunc::Always, 0);\n}\n\nvoid CElementGen::RenderParticlesIndirectTexture() {\n  SCOPED_GRAPHICS_DEBUG_GROUP(fmt::format(\"CElementGen::RenderParticlesIndirectTexture\").c_str(), zeus::skYellow);\n\n  CGenDescription* desc = x1c_genDesc.GetObj();\n\n  zeus::CTransform systemViewPointMatrix(CGraphics::mViewMatrix);\n  systemViewPointMatrix.origin.zeroOut();\n  zeus::CTransform systemCameraMatrix = systemViewPointMatrix.inverse() * x22c_globalOrientation;\n  systemViewPointMatrix =\n      ((zeus::CTransform::Translate(xe8_globalTranslation) * x10c_globalScaleTransform) * systemViewPointMatrix) *\n      x178_localScaleTransform;\n  CGraphics::SetModelMatrix(systemViewPointMatrix);\n\n//  SParticleUniforms uniformData = {CGraphics::GetPerspectiveProjectionMatrix(/*true*/) *\n//                                       CGraphics::g_GXModelView.toMatrix4f(),\n//                                   {1.f, 1.f, 1.f, 1.f}};\n  //  m_uniformBuf->load(&uniformData, sizeof(SParticleUniforms));\n\n  CGraphics::SetAlphaCompare(ERglAlphaFunc::Always, 0, ERglAlphaOp::And, ERglAlphaFunc::Always, 0);\n\n  if (x26c_26_AAPH) {\n    CGraphics::SetDepthWriteMode(true, ERglEnum::LEqual, true);\n    CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::One, ERglLogicOp::Clear);\n  } else {\n    CGraphics::SetDepthWriteMode(true, ERglEnum::LEqual, x26c_27_ZBUF);\n    CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::InvSrcAlpha,\n                            ERglLogicOp::Clear);\n  }\n\n  CUVElement* texr = desc->x54_x40_TEXR.get();\n  CParticle& firstParticle = x30_particles[0];\n  int partFrame = x74_curFrame - firstParticle.x28_startFrame;\n  CTexture* cachedTex = texr->GetValueTexture(partFrame).GetObj();\n  cachedTex->Load(GX_TEXMAP0, EClampMode::Repeat);\n\n  SUVElementSet uvs = {0.f, 0.f, 1.f, 1.f};\n  bool constTexr = texr->HasConstantTexture();\n  texr->GetValueUV(partFrame, uvs);\n  bool constUVs = texr->HasConstantUV();\n\n  CUVElement* tind = desc->x58_x44_TIND.get();\n  CTexture* cachedIndTex = tind->GetValueTexture(partFrame).GetObj();\n  cachedIndTex->Load(GX_TEXMAP2, EClampMode::Repeat);\n\n  SUVElementSet uvsInd = {0.f, 0.f, 1.f, 1.f};\n  bool constIndTexr = tind->HasConstantTexture();\n  bool constIndUVs = tind->HasConstantUV();\n  tind->GetValueUV(partFrame, uvsInd);\n\n  std::vector<CParticleListItem> sortItems;\n  if (desc->x44_28_x30_28_SORT) {\n    sortItems.reserve(x30_particles.size());\n\n    for (size_t i = 0; i < x30_particles.size(); ++i) {\n      const CParticle& particle = x30_particles[i];\n      sortItems.emplace_back(s16(i));\n      CParticleListItem& sortItem = sortItems.back();\n      sortItem.x4_viewPoint =\n          systemCameraMatrix * ((particle.x4_pos - particle.x10_prevPos) * x80_timeDeltaScale + particle.x10_prevPos);\n    }\n\n    std::sort(sortItems.begin(), sortItems.end(), [](const CParticleListItem& a, const CParticleListItem& b) -> bool {\n      return a.x4_viewPoint[1] >= b.x4_viewPoint[1];\n    });\n  }\n\n//  g_instIndTexData.clear();\n//  g_instIndTexData.reserve(x30_particles.size());\n\n  //  if (!x30_particles.empty())\n  //    CGraphics::SetShaderDataBinding(m_normalDataBind[g_Renderer->IsThermalVisorHotPass()]);\n\n  for (size_t i = 0; i < x30_particles.size(); ++i) {\n    const int partIdx = desc->x44_28_x30_28_SORT ? sortItems[i].x0_partIdx : int(i);\n    CParticle& particle = x30_particles[partIdx];\n    g_currentParticle = &particle;\n\n    const int thisPartFrame = x74_curFrame - particle.x28_startFrame;\n    zeus::CVector3f viewPoint;\n    if (desc->x44_28_x30_28_SORT) {\n      viewPoint = sortItems[i].x4_viewPoint;\n    } else {\n      viewPoint =\n          systemCameraMatrix * ((particle.x4_pos - particle.x10_prevPos) * x80_timeDeltaScale + particle.x10_prevPos);\n    }\n\n    if (!constTexr) {\n      CTexture* tex = texr->GetValueTexture(thisPartFrame).GetObj();\n      if (tex != cachedTex) {\n        tex->Load(GX_TEXMAP0, EClampMode::Repeat);\n        cachedTex = tex;\n      }\n    }\n\n    if (!constIndTexr) {\n      CTexture* tex = tind->GetValueTexture(thisPartFrame).GetObj();\n      if (tex != cachedIndTex) {\n        tex->Load(GX_TEXMAP2, EClampMode::Repeat);\n        cachedIndTex = tex;\n      }\n    }\n\n    if (!constUVs)\n      texr->GetValueUV(thisPartFrame, uvs);\n\n    if (!constIndUVs)\n      tind->GetValueUV(thisPartFrame, uvsInd);\n\n    float size = 0.5f * particle.x2c_lineLengthOrSize;\n    zeus::CVector3f p1 = {viewPoint.x() - size, viewPoint.y(), viewPoint.z() - size};\n    zeus::CVector3f p2 = {viewPoint.x() + size, viewPoint.y(), viewPoint.z() + size};\n    CGraphics::CClippedScreenRect clipRect = CGraphics::ClipScreenRectFromMS(p1, p2, ETexelFormat::RGB565);\n\n    if (!clipRect.IsValid())\n      continue;\n\n//    CGraphics::ResolveSpareTexture(clipRect);\n\n//    SParticleInstanceIndTex& inst = g_instIndTexData.emplace_back();\n//    inst.pos[0] = zeus::CVector4f{viewPoint.x() + size, viewPoint.y(), viewPoint.z() + size, 1.f};\n//    inst.pos[1] = zeus::CVector4f{viewPoint.x() - size, viewPoint.y(), viewPoint.z() + size, 1.f};\n//    inst.pos[2] = zeus::CVector4f{viewPoint.x() + size, viewPoint.y(), viewPoint.z() - size, 1.f};\n//    inst.pos[3] = zeus::CVector4f{viewPoint.x() - size, viewPoint.y(), viewPoint.z() - size, 1.f};\n//    inst.color = particle.x34_color;\n//    inst.texrTindUVs[0] = zeus::CVector4f{uvs.xMax, uvs.yMax, uvsInd.xMax, uvsInd.yMax};\n//    inst.texrTindUVs[1] = zeus::CVector4f{uvs.xMin, uvs.yMax, uvsInd.xMin, uvsInd.yMax};\n//    inst.texrTindUVs[2] = zeus::CVector4f{uvs.xMax, uvs.yMin, uvsInd.xMax, uvsInd.yMin};\n//    inst.texrTindUVs[3] = zeus::CVector4f{uvs.xMin, uvs.yMin, uvsInd.xMin, uvsInd.yMin};\n    //    switch (CGraphics::g_BooPlatform) {\n    //    case boo::IGraphicsDataFactory::Platform::OpenGL:\n    //      inst.sceneUVs =\n    //          zeus::CVector4f{clipRect.x18_uvXMin, clipRect.x24_uvYMax, clipRect.x1c_uvXMax, clipRect.x20_uvYMin};\n    //      break;\n    //    default:\n//    inst.sceneUVs =\n//        zeus::CVector4f{clipRect.x18_uvXMin, 1.f - clipRect.x24_uvYMax, clipRect.x1c_uvXMax, 1.f - clipRect.x20_uvYMin};\n    //      break;\n    //    }\n    //    CGraphics::DrawInstances(0, 4, 1, g_instIndTexData.size() - 1);\n  }\n\n//  if (g_instIndTexData.size()) {\n    //    m_instBuf->load(g_instIndTexData.data(), g_instIndTexData.size() * sizeof(SParticleInstanceIndTex));\n    // TODO! this looks like a bug\n    // CGraphics::SetShaderDataBinding(m_normalDataBind);\n    // CGraphics::DrawInstances(0, 4, g_instIndTexData.size());\n//  }\n}\n\nvoid CElementGen::SetOrientation(const zeus::CTransform& orientation) {\n  x1d8_orientation = orientation;\n  x208_orientationInverse = x1d8_orientation.basis.transposed();\n\n  for (const std::unique_ptr<CParticleGen>& ch : x290_activePartChildren)\n    ch->SetOrientation(orientation);\n}\n\nvoid CElementGen::SetTranslation(const zeus::CVector3f& translation) {\n  xdc_translation = translation;\n\n  for (const std::unique_ptr<CParticleGen>& ch : x290_activePartChildren) {\n    switch (ch->Get4CharId().toUint32()) {\n    case SBIG('ELSC'):\n      ch->SetTranslation(translation + x2c0_SEPO);\n      break;\n    case SBIG('SWHC'):\n      ch->SetTranslation(translation + x2b0_SSPO);\n      break;\n    default:\n      ch->SetTranslation(translation);\n      break;\n    }\n  }\n}\n\nvoid CElementGen::SetGlobalOrientation(const zeus::CTransform& rotation) {\n  x22c_globalOrientation.setRotation(rotation);\n\n  for (const std::unique_ptr<CParticleGen>& ch : x290_activePartChildren)\n    ch->SetGlobalOrientation(x22c_globalOrientation);\n}\n\nvoid CElementGen::SetGlobalTranslation(const zeus::CVector3f& translation) {\n  xe8_globalTranslation = translation;\n\n  for (const std::unique_ptr<CParticleGen>& ch : x290_activePartChildren)\n    ch->SetGlobalTranslation(translation);\n}\n\nvoid CElementGen::SetGlobalScale(const zeus::CVector3f& scale) {\n  x100_globalScale = scale;\n  x10c_globalScaleTransform = zeus::CTransform::Scale(scale);\n  x13c_globalScaleTransformInverse = zeus::CTransform::Scale(zeus::skOne3f / scale);\n\n  for (const std::unique_ptr<CParticleGen>& ch : x290_activePartChildren)\n    ch->SetGlobalScale(scale);\n}\n\nvoid CElementGen::SetLocalScale(const zeus::CVector3f& scale) {\n  x16c_localScale = scale;\n  x178_localScaleTransform = zeus::CTransform::Scale(scale);\n  x1a8_localScaleTransformInverse = zeus::CTransform::Scale(zeus::skOne3f / scale);\n\n  for (const std::unique_ptr<CParticleGen>& ch : x290_activePartChildren)\n    ch->SetLocalScale(scale);\n}\n\nvoid CElementGen::SetGlobalOrientAndTrans(const zeus::CTransform& xf) {\n  SetGlobalOrientation(xf);\n  SetGlobalTranslation(xf.origin);\n}\n\nvoid CElementGen::SetParticleEmission(bool enabled) {\n  x88_particleEmission = enabled;\n\n  for (const std::unique_ptr<CParticleGen>& ch : x290_activePartChildren)\n    ch->SetParticleEmission(enabled);\n}\n\nvoid CElementGen::SetModulationColor(const zeus::CColor& color) {\n  x338_moduColor = color;\n\n  for (const std::unique_ptr<CParticleGen>& ch : x290_activePartChildren)\n    ch->SetModulationColor(color);\n}\n\nvoid CElementGen::SetGeneratorRate(float rate) {\n  if (rate >= 0.0f)\n    x98_generatorRate = rate;\n  else\n    x98_generatorRate = 0.0f;\n\n  for (std::unique_ptr<CParticleGen>& child : x290_activePartChildren) {\n    if (child->Get4CharId() == FOURCC('PART'))\n      child->SetGeneratorRate(x98_generatorRate);\n  }\n}\n\nconst zeus::CTransform& CElementGen::GetOrientation() const { return x1d8_orientation; }\n\nconst zeus::CVector3f& CElementGen::GetTranslation() const { return xdc_translation; }\n\nconst zeus::CTransform& CElementGen::GetGlobalOrientation() const { return x22c_globalOrientation; }\n\nconst zeus::CVector3f& CElementGen::GetGlobalTranslation() const { return xe8_globalTranslation; }\n\nconst zeus::CVector3f& CElementGen::GetGlobalScale() const { return x100_globalScale; }\n\nconst zeus::CColor& CElementGen::GetModulationColor() const { return x338_moduColor; }\n\nbool CElementGen::IsSystemDeletable() const {\n  for (const std::unique_ptr<CParticleGen>& ch : x290_activePartChildren)\n    if (!ch->IsSystemDeletable())\n      return false;\n\n  return x268_PSLT < x74_curFrame && x25c_activeParticleCount == 0;\n}\n\nstd::optional<zeus::CAABox> CElementGen::GetBounds() const {\n  if (GetParticleCountAll() == 0)\n    return std::nullopt;\n  else\n    return {x2f0_systemBounds};\n}\n\nu32 CElementGen::GetParticleCount() const { return x25c_activeParticleCount; }\n\nbool CElementGen::SystemHasLight() const { return x308_lightType != LightType::None; }\n\nCLight CElementGen::GetLight() const {\n  switch (x308_lightType) {\n  case LightType::Directional:\n    return CLight::BuildDirectional(x320_LDIR.normalized(), x30c_LCLR * x310_LINT);\n  case LightType::Spot:\n    return CLight::BuildSpot(x314_LOFF, x320_LDIR.normalized(), x30c_LCLR * x310_LINT, x334_LSLA);\n  default: {\n    float quad = x32c_falloffType == EFalloffType::Quadratic ? x330_LFOR : 0.f;\n    float linear = x32c_falloffType == EFalloffType::Linear ? x330_LFOR : 0.f;\n    float constant = x32c_falloffType == EFalloffType::Constant ? 1.f : 0.f;\n    return CLight::BuildCustom(x314_LOFF, {1.f, 0.f, 0.f}, x30c_LCLR, constant, linear, quad, x310_LINT, 0.f, 0.f);\n  }\n  }\n}\n\nbool CElementGen::GetParticleEmission() const { return x88_particleEmission; }\n\nvoid CElementGen::DestroyParticles() {\n  g_ParticleAliveCount -= x30_particles.size();\n  x30_particles.clear();\n  x50_parentMatrices.clear();\n\n  for (const std::unique_ptr<CParticleGen>& ch : x290_activePartChildren)\n    ch->DestroyParticles();\n}\n\nvoid CElementGen::Reset() {\n  x30_particles.clear();\n  x50_parentMatrices.clear();\n  x290_activePartChildren.clear();\n\n  x74_curFrame = 0;\n  x78_curSeconds = 0.f;\n  x84_prevFrame = -1;\n  x25c_activeParticleCount = 0;\n  x26d_25_warmedUp = false;\n}\n\nvoid CElementGen::SetMoveRedToAlphaBuffer(bool move) { sMoveRedToAlphaBuffer = move; }\n\nvoid CElementGen::RenderBasicParticlesNoRotNoTS(const zeus::CTransform& xf) noexcept {\n  for (const auto& particle : x30_particles) {\n    const auto pos = xf * particle.x4_pos;\n    const auto size = 0.5f * particle.x2c_lineLengthOrSize;\n    GXPosition3f32(pos.x() + size, pos.y(), pos.z() + size);\n    GXColor4f32(particle.x34_color);\n    GXTexCoord2s8(2, 2);\n    GXPosition3f32(pos.x() - size, pos.y(), pos.z() + size);\n    GXColor4f32(particle.x34_color);\n    GXTexCoord2s8(0, 2);\n    GXPosition3f32(pos.x() - size, pos.y(), pos.z() - size);\n    GXColor4f32(particle.x34_color);\n    GXTexCoord2s8(0, 0);\n    GXPosition3f32(pos.x() + size, pos.y(), pos.z() - size);\n    GXColor4f32(particle.x34_color);\n    GXTexCoord2s8(2, 0);\n  }\n}\n\nvoid CElementGen::RenderBasicParticlesNoRotTS(const zeus::CTransform& xf) noexcept {\n  for (const auto& particle : x30_particles) {\n    const auto pos = xf * (x80_timeDeltaScale * (particle.x4_pos - particle.x10_prevPos) + particle.x10_prevPos);\n    const auto size = 0.5f * particle.x2c_lineLengthOrSize;\n    GXPosition3f32(pos.x() + size, pos.y(), pos.z() + size);\n    GXColor4f32(particle.x34_color);\n    GXTexCoord2s8(2, 2);\n    GXPosition3f32(pos.x() - size, pos.y(), pos.z() + size);\n    GXColor4f32(particle.x34_color);\n    GXTexCoord2s8(0, 2);\n    GXPosition3f32(pos.x() - size, pos.y(), pos.z() - size);\n    GXColor4f32(particle.x34_color);\n    GXTexCoord2s8(0, 0);\n    GXPosition3f32(pos.x() + size, pos.y(), pos.z() - size);\n    GXColor4f32(particle.x34_color);\n    GXTexCoord2s8(2, 0);\n  }\n}\n\nvoid CElementGen::RenderBasicParticlesRotNoTS(const zeus::CTransform& xf) noexcept {\n  for (const auto& particle : x30_particles) {\n    const auto pos = xf * particle.x4_pos;\n    const auto size = 0.5f * particle.x2c_lineLengthOrSize;\n    const float theta = zeus::degToRad(particle.x30_lineWidthOrRota);\n    const float sinT = std::sin(theta) * size;\n    const float cosT = std::cos(theta) * size;\n    GXPosition3f32(pos.x() + (sinT + cosT), pos.y(), pos.z() + (cosT - sinT));\n    GXColor4f32(particle.x34_color);\n    GXTexCoord2s8(2, 2);\n    GXPosition3f32(pos.x() + (sinT - cosT), pos.y(), pos.z() + (sinT + cosT));\n    GXColor4f32(particle.x34_color);\n    GXTexCoord2s8(0, 2);\n    GXPosition3f32(pos.x() - (sinT + cosT), pos.y(), pos.z() - (cosT - sinT));\n    GXColor4f32(particle.x34_color);\n    GXTexCoord2s8(0, 0);\n    GXPosition3f32(pos.x() + (-sinT + cosT), pos.y(), pos.z() + (-cosT - sinT));\n    GXColor4f32(particle.x34_color);\n    GXTexCoord2s8(2, 0);\n  }\n}\n\nvoid CElementGen::RenderBasicParticlesRotTS(const zeus::CTransform& xf) noexcept {\n  for (const auto& particle : x30_particles) {\n    const auto pos = xf * (x80_timeDeltaScale * (particle.x4_pos - particle.x10_prevPos) + particle.x10_prevPos);\n    const auto size = 0.5f * particle.x2c_lineLengthOrSize;\n    const float theta = zeus::degToRad(particle.x30_lineWidthOrRota);\n    const float sinT = std::sin(theta) * size;\n    const float cosT = std::cos(theta) * size;\n    GXPosition3f32(pos.x() + (sinT + cosT), pos.y(), pos.z() + (cosT - sinT));\n    GXColor4f32(particle.x34_color);\n    GXTexCoord2s8(2, 2);\n    GXPosition3f32(pos.x() + (sinT - cosT), pos.y(), pos.z() + (sinT + cosT));\n    GXColor4f32(particle.x34_color);\n    GXTexCoord2s8(0, 2);\n    GXPosition3f32(pos.x() - (sinT + cosT), pos.y(), pos.z() - (cosT - sinT));\n    GXColor4f32(particle.x34_color);\n    GXTexCoord2s8(0, 0);\n    GXPosition3f32(pos.x() + (-sinT + cosT), pos.y(), pos.z() + (-cosT - sinT));\n    GXColor4f32(particle.x34_color);\n    GXTexCoord2s8(2, 0);\n  }\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CElementGen.hpp",
    "content": "#pragma once\n\n#include <array>\n#include <vector>\n\n#include \"Runtime/CRandom16.hpp\"\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Graphics/CGraphics.hpp\"\n#include \"Runtime/Graphics/CLight.hpp\"\n#include \"Runtime/Particle/CGenDescription.hpp\"\n#include \"Runtime/Particle/CParticleGen.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CColor.hpp>\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CActorLights;\nclass CGenDescription;\nclass CLight;\nclass CParticleElectric;\nclass CParticleSwoosh;\nclass CWarp;\nclass IGenDescription;\n\nclass CElementGen : public CParticleGen {\n  static u16 g_GlobalSeed;\n  static bool g_subtractBlend;\n\npublic:\n  static void SetGlobalSeed(u16 seed) { g_GlobalSeed = seed; }\n  static void SetSubtractBlend(bool subtract) { g_subtractBlend = subtract; }\n  enum class EModelOrientationType { Normal, One };\n  enum class EOptionalSystemFlags { None, One, Two };\n  enum class LightType { None = 0, Custom = 1, Directional = 2, Spot = 3 };\n  class CParticleListItem {\n    friend class CElementGen;\n    s16 x0_partIdx;\n    zeus::CVector3f x4_viewPoint;\n\n  public:\n    explicit CParticleListItem(s16 idx) : x0_partIdx(idx) {}\n  };\n  static CParticle* g_currentParticle;\n\nprivate:\n  friend class CElementGenShaders;\n  TLockedToken<CGenDescription> x1c_genDesc;\n  CGenDescription* x28_loadedGenDesc;\n  EModelOrientationType x2c_orientType;\n  std::vector<CParticle> x30_particles;\n  std::vector<u32> x40;\n  std::vector<zeus::CMatrix3f> x50_parentMatrices;\n  std::vector<std::array<float, 8>> x60_advValues;\n\n  int x70_internalStartFrame = 0;\n  int x74_curFrame = 0;\n  double x78_curSeconds = 0.f;\n  float x80_timeDeltaScale = 0.f;\n  int x84_prevFrame = -1;\n  bool x88_particleEmission = true;\n  float x8c_generatorRemainder = 0.f;\n  int x90_MAXP = 0;\n  u16 x94_randomSeed = g_GlobalSeed;\n  float x98_generatorRate = 1.f;\n  std::array<float, 16> x9c_externalVars{};\n\n  zeus::CVector3f xdc_translation;\n  zeus::CVector3f xe8_globalTranslation;\n  zeus::CVector3f xf4_POFS;\n  zeus::CVector3f x100_globalScale = {1.f, 1.f, 1.f};\n  zeus::CTransform x10c_globalScaleTransform = zeus::CTransform();\n  zeus::CTransform x13c_globalScaleTransformInverse = zeus::CTransform();\n  zeus::CVector3f x16c_localScale = {1.f, 1.f, 1.f};\n  zeus::CTransform x178_localScaleTransform = zeus::CTransform();\n  zeus::CTransform x1a8_localScaleTransformInverse = zeus::CTransform();\n  zeus::CTransform x1d8_orientation = zeus::CTransform();\n  zeus::CMatrix3f x208_orientationInverse = zeus::CMatrix3f();\n  zeus::CTransform x22c_globalOrientation = zeus::CTransform();\n\n  u32 x25c_activeParticleCount = 0;\n  u32 x260_cumulativeParticles = 0;\n  u32 x264_recursiveParticleCount = 0;\n  int x268_PSLT;\n  bool x26c_24_translationDirty : 1 = false;\n  bool x26c_25_LIT_ : 1 = false;\n  bool x26c_26_AAPH : 1 = false;\n  bool x26c_27_ZBUF : 1 = false;\n  bool x26c_28_zTest : 1 = false;\n  bool x26c_29_ORNT : 1 = false;\n  bool x26c_30_MBLR : 1 = false;\n  bool x26c_31_LINE : 1 = false;\n  bool x26d_24_FXLL : 1 = false;\n  bool x26d_25_warmedUp : 1 = false;\n  bool x26d_26_modelsUseLights : 1 = false;\n  bool x26d_27_enableOPTS : 1;\n  bool x26d_28_enableADV : 1 = false;\n  int x270_MBSP = 0;\n  GX::LightMask x274_backupLightActive{};\n  std::array<bool, 4> x278_hasVMD{};\n  CRandom16 x27c_randState;\n  std::array<CModVectorElement*, 4> x280_VELSources{};\n\n  std::vector<std::unique_ptr<CParticleGen>> x290_activePartChildren;\n  int x2a0_CSSD = 0;\n  int x2a4_SISY = 16;\n  int x2a8_PISY = 16;\n  int x2ac_SSSD = 0;\n  zeus::CVector3f x2b0_SSPO;\n  int x2bc_SESD = 0;\n  zeus::CVector3f x2c0_SEPO;\n  float x2cc = 0.f;\n  float x2d0 = 0.f;\n  zeus::CVector3f x2d4_aabbMin;\n  zeus::CVector3f x2e0_aabbMax;\n  float x2ec_maxSize = 0.f;\n  zeus::CAABox x2f0_systemBounds = zeus::CAABox();\n  LightType x308_lightType;\n  zeus::CColor x30c_LCLR = zeus::skWhite;\n  float x310_LINT = 1.f;\n  zeus::CVector3f x314_LOFF;\n  zeus::CVector3f x320_LDIR = {1.f, 0.f, 0.f};\n  EFalloffType x32c_falloffType = EFalloffType::Linear;\n  float x330_LFOR = 1.f;\n  float x334_LSLA = 45.f;\n  zeus::CColor x338_moduColor = {1.f, 1.f, 1.f, 1.f};\n\n  void AccumulateBounds(const zeus::CVector3f& pos, float size);\n\n  void _RecreatePipelines();\n\npublic:\n  explicit CElementGen(TToken<CGenDescription> gen, EModelOrientationType orientType = EModelOrientationType::Normal,\n                       EOptionalSystemFlags flags = EOptionalSystemFlags::One);\n  ~CElementGen() override;\n\n  CGenDescription* GetDesc() { return x1c_genDesc.GetObj(); }\n  const SObjectTag* GetDescTag() const { return x1c_genDesc.GetObjectTag(); }\n  CGenDescription* GetLoadedDesc() { return x28_loadedGenDesc; }\n\n  static bool g_ParticleSystemInitialized;\n  static int g_ParticleAliveCount;\n  static int g_ParticleSystemAliveCount;\n  static bool sMoveRedToAlphaBuffer;\n  static void Initialize();\n  static void Shutdown();\n\n  void UpdateAdvanceAccessParameters(u32 activeParticleCount, s32 particleFrame);\n  bool UpdateVelocitySource(size_t idx, s32 particleFrame, CParticle& particle);\n  void UpdateExistingParticles();\n  void CreateNewParticles(int count);\n  void UpdatePSTranslationAndOrientation();\n  void UpdateChildParticleSystems(double dt);\n  std::unique_ptr<CParticleGen> ConstructChildParticleSystem(const TToken<CGenDescription>& desc) const;\n  void UpdateLightParameters();\n  void BuildParticleSystemBounds();\n  u32 GetEmitterTime() const { return x74_curFrame; }\n  u32 GetSystemCount() const;\n  u32 GetCumulativeParticleCount() const { return x260_cumulativeParticles; }\n  u32 GetParticleCountAllInternal() const;\n  u32 GetParticleCountAll() const { return x264_recursiveParticleCount; }\n  void EndLifetime();\n  void ForceParticleCreation(int amount);\n  float GetExternalVar(int index) const { return x9c_externalVars[index]; }\n  void SetExternalVar(int index, float var) { x9c_externalVars[index] = var; }\n\n  bool InternalUpdate(double dt);\n  void RenderModels();\n  void RenderLines();\n  void RenderParticles();\n  void RenderParticlesIndirectTexture();\n\n  bool Update(double t) override;\n  void Render() override;\n  void SetOrientation(const zeus::CTransform& orientation) override;\n  void SetTranslation(const zeus::CVector3f& translation) override;\n  void SetGlobalOrientation(const zeus::CTransform& orientation) override;\n  void SetGlobalTranslation(const zeus::CVector3f& translation) override;\n  void SetGlobalScale(const zeus::CVector3f& scale) override;\n  void SetLocalScale(const zeus::CVector3f& scale) override;\n  void SetGlobalOrientAndTrans(const zeus::CTransform& xf);\n  void SetParticleEmission(bool enabled) override;\n  void SetModulationColor(const zeus::CColor& color) override;\n  void SetGeneratorRate(float rate) override;\n  const zeus::CTransform& GetOrientation() const override;\n  const zeus::CVector3f& GetTranslation() const override;\n  const zeus::CTransform& GetGlobalOrientation() const override;\n  const zeus::CVector3f& GetGlobalTranslation() const override;\n  const zeus::CVector3f& GetGlobalScale() const override;\n  const zeus::CColor& GetModulationColor() const override;\n  float GetGeneratorRate() const override { return x98_generatorRate; }\n  bool IsSystemDeletable() const override;\n  std::optional<zeus::CAABox> GetBounds() const override;\n  u32 GetParticleCount() const override;\n  bool SystemHasLight() const override;\n  CLight GetLight() const override;\n  bool GetParticleEmission() const override;\n  void DestroyParticles() override;\n  void Reset() override;\n  FourCC Get4CharId() const override { return FOURCC('PART'); }\n  size_t GetNumActiveChildParticles() const { return x290_activePartChildren.size(); }\n  CParticleGen& GetActiveChildParticle(size_t idx) const { return *x290_activePartChildren[idx]; }\n  bool IsIndirectTextured() const { return x28_loadedGenDesc->x54_x40_TEXR && x28_loadedGenDesc->x58_x44_TIND; }\n  void SetModelsUseLights(bool useLights) { x26d_26_modelsUseLights = useLights; }\n  void SetZTest(bool z) { x26c_28_zTest = z; }\n  static void SetMoveRedToAlphaBuffer(bool move);\n\n  s32 GetMaxParticles() const { return x90_MAXP; }\n\n  std::vector<CParticle> const& GetParticles() const { return x30_particles; }\n  std::vector<CParticle>& GetParticles() { return x30_particles; }\n\nprivate:\n  void RenderBasicParticlesNoRotNoTS(const zeus::CTransform& xf) noexcept;\n  void RenderBasicParticlesNoRotTS(const zeus::CTransform& xf) noexcept;\n  void RenderBasicParticlesRotNoTS(const zeus::CTransform& xf) noexcept;\n  void RenderBasicParticlesRotTS(const zeus::CTransform& xf) noexcept;\n};\nENABLE_BITWISE_ENUM(CElementGen::EOptionalSystemFlags)\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CEmitterElement.cpp",
    "content": "#include \"Runtime/Particle/CEmitterElement.hpp\"\n\n#include \"Runtime/CRandom16.hpp\"\n\n/* Documentation at: https://wiki.axiodl.com/w/Particle_Script#Emitter_Elements */\n\nnamespace metaforce {\n\nbool CEESimpleEmitter::GetValue(int frame, zeus::CVector3f& pPos, zeus::CVector3f& pVel) const {\n  x4_loc->GetValue(frame, pPos);\n\n  if (x8_vec)\n    x8_vec->GetValue(frame, pVel);\n  else\n    pVel = zeus::CVector3f();\n\n  return false;\n}\n\nbool CVESphere::GetValue(int frame, zeus::CVector3f& pPos, zeus::CVector3f& pVel) const {\n  zeus::CVector3f a;\n  x4_sphereOrigin->GetValue(frame, a);\n  float b;\n  x8_sphereRadius->GetValue(frame, b);\n  CRandom16* rand = CRandom16::GetRandomNumber();\n  int rand1 = rand->Range(-100, 100);\n  int rand2 = rand->Range(-100, 100);\n  int rand3 = rand->Range(-100, 100);\n\n  zeus::CVector3f normVec1 =\n      zeus::CVector3f(0.0099999998f * float(rand3), 0.0099999998f * float(rand2), 0.0099999998f * float(rand1));\n  if (normVec1.canBeNormalized())\n    normVec1.normalize();\n\n  pPos = b * normVec1 + a;\n\n  zeus::CVector3f normVec2 = (pPos - a);\n  if (normVec2.canBeNormalized())\n    normVec2.normalize();\n\n  float c;\n  xc_velocityMag->GetValue(frame, c);\n  pVel = c * normVec2;\n\n  return false;\n}\n\nbool CVEAngleSphere::GetValue(int frame, zeus::CVector3f& pPos, zeus::CVector3f& pVel) const {\n  zeus::CVector3f a;\n  x4_sphereOrigin->GetValue(frame, a);\n\n  float b, d, e, f, g;\n  x8_sphereRadius->GetValue(frame, b);\n  x10_angleXBias->GetValue(frame, d);\n  x14_angleYBias->GetValue(frame, e);\n  x18_angleXRange->GetValue(frame, f);\n  x1c_angleYRange->GetValue(frame, g);\n  CRandom16* rand = CRandom16::GetRandomNumber();\n  d = zeus::degToRad(d + (0.5f * f - f * rand->Float()));\n  e = zeus::degToRad(e + (0.5f * g - g * rand->Float()));\n\n  float cosD = std::cos(d);\n  pPos.x() = a.x() + (b * (-std::sin(e) * cosD));\n  pPos.y() = a.y() + (b * std::sin(d));\n  pPos.z() = a.z() + (b * (cosD * cosD));\n  zeus::CVector3f normVec = (pPos - a).normalized();\n\n  float c;\n  xc_velocityMag->GetValue(frame, c);\n  pVel = c * normVec;\n  return false;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CEmitterElement.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include \"Runtime/Particle/IElement.hpp\"\n\n/* Documentation at: https://wiki.axiodl.com/w/Particle_Script#Emitter_Elements */\n\nnamespace metaforce {\n\nclass CEESimpleEmitter : public CEmitterElement {\n  std::unique_ptr<CVectorElement> x4_loc;\n  std::unique_ptr<CVectorElement> x8_vec;\n\npublic:\n  CEESimpleEmitter(std::unique_ptr<CVectorElement>&& a, std::unique_ptr<CVectorElement>&& b)\n  : x4_loc(std::move(a)), x8_vec(std::move(b)) {}\n  bool GetValue(int frame, zeus::CVector3f& pPos, zeus::CVector3f& pVel) const override;\n};\n\nclass CVESphere : public CEmitterElement {\n  std::unique_ptr<CVectorElement> x4_sphereOrigin;\n  std::unique_ptr<CRealElement> x8_sphereRadius;\n  std::unique_ptr<CRealElement> xc_velocityMag;\n\npublic:\n  CVESphere(std::unique_ptr<CVectorElement>&& a, std::unique_ptr<CRealElement>&& b, std::unique_ptr<CRealElement>&& c)\n  : x4_sphereOrigin(std::move(a)), x8_sphereRadius(std::move(b)), xc_velocityMag(std::move(c)) {}\n  bool GetValue(int frame, zeus::CVector3f& pPos, zeus::CVector3f& pVel) const override;\n};\n\nclass CVEAngleSphere : public CEmitterElement {\n  std::unique_ptr<CVectorElement> x4_sphereOrigin;\n  std::unique_ptr<CRealElement> x8_sphereRadius;\n  std::unique_ptr<CRealElement> xc_velocityMag;\n  std::unique_ptr<CRealElement> x10_angleXBias;\n  std::unique_ptr<CRealElement> x14_angleYBias;\n  std::unique_ptr<CRealElement> x18_angleXRange;\n  std::unique_ptr<CRealElement> x1c_angleYRange;\n\npublic:\n  CVEAngleSphere(std::unique_ptr<CVectorElement>&& a, std::unique_ptr<CRealElement>&& b,\n                 std::unique_ptr<CRealElement>&& c, std::unique_ptr<CRealElement>&& d,\n                 std::unique_ptr<CRealElement>&& e, std::unique_ptr<CRealElement>&& f,\n                 std::unique_ptr<CRealElement>&& g)\n  : x4_sphereOrigin(std::move(a))\n  , x8_sphereRadius(std::move(b))\n  , xc_velocityMag(std::move(c))\n  , x10_angleXBias(std::move(d))\n  , x14_angleYBias(std::move(e))\n  , x18_angleXRange(std::move(f))\n  , x1c_angleYRange(std::move(g)) {}\n  bool GetValue(int frame, zeus::CVector3f& pPos, zeus::CVector3f& pVel) const override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CFlameWarp.cpp",
    "content": "#include \"Runtime/Particle/CFlameWarp.hpp\"\n\n#include <algorithm>\n\n#include \"Runtime/CStateManager.hpp\"\n\nnamespace metaforce {\n\nvoid CFlameWarp::ModifyParticles(std::vector<CParticle>& particles) {\n  if (x9c_stateMgr == nullptr || particles.size() < 9) {\n    return;\n  }\n\n  std::vector<std::pair<float, u8>> vec;\n  vec.reserve(particles.size());\n\n  x90_minSize = FLT_MAX;\n  x94_maxSize = FLT_MIN;\n  float maxTransp = 0.f;\n  u8 idx = 0;\n  for (CParticle& particle : particles) {\n    const float transp = 1.f - particle.x34_color.a();\n    if (transp > maxTransp) {\n      const float distSq = (particle.x4_pos - x74_warpPoint).magSquared();\n      if (distSq > x8c_maxDistSq && distSq < x98_maxInfluenceDistSq) {\n        x8c_maxDistSq = distSq;\n        maxTransp = transp;\n        x80_floatingPoint = particle.x4_pos;\n      }\n    }\n\n    if (particle.x2c_lineLengthOrSize < x90_minSize) {\n      x90_minSize = particle.x2c_lineLengthOrSize;\n    }\n    if (particle.x2c_lineLengthOrSize > x94_maxSize) {\n      x94_maxSize = particle.x2c_lineLengthOrSize;\n    }\n\n    vec.emplace_back(transp, idx);\n\n    if (xa0_25_collisionWarp) {\n      const zeus::CVector3f delta = particle.x4_pos - particle.x10_prevPos;\n      if (delta.magSquared() >= 0.0011920929f) {\n        const zeus::CVector3f deltaNorm = delta.normalized();\n        const zeus::CVector3f behindPos = particle.x10_prevPos - deltaNorm * 5.f;\n        const zeus::CVector3f fullDelta = particle.x4_pos - behindPos;\n        const CRayCastResult result = x9c_stateMgr->RayStaticIntersection(\n            behindPos, deltaNorm, fullDelta.magnitude(),\n            CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {EMaterialTypes::ProjectilePassthrough}));\n        if (result.IsValid()) {\n          const float dist = result.GetPlane().pointToPlaneDist(particle.x4_pos);\n          if (dist <= 0.f) {\n            particle.x4_pos -= result.GetPlane().normal() * dist;\n            if (result.GetPlane().normal().dot(particle.x1c_vel) < 0.f) {\n              const zeus::CVector3f prevStepPos = particle.x4_pos - particle.x1c_vel;\n              particle.x4_pos +=\n                  (-result.GetPlane().pointToPlaneDist(prevStepPos) / particle.x1c_vel.dot(result.GetPlane().normal()) -\n                   1.f) *\n                  particle.x1c_vel;\n              particle.x1c_vel -= particle.x1c_vel * 0.001f;\n            }\n          }\n        }\n      }\n    }\n\n    ++idx;\n  }\n\n  std::sort(vec.begin(), vec.end(), [](auto& a, auto& b) { return a.first < b.first; });\n\n  const size_t pitch = particles.size() / 9;\n  for (size_t i = 0; i < x4_collisionPoints.size(); ++i) {\n    const CParticle& part = particles[vec[i * pitch].second];\n    x4_collisionPoints[i] = part.x4_pos;\n    if (i > 0) {\n      const zeus::CVector3f delta = x4_collisionPoints[i] - x4_collisionPoints[i - 1];\n      if (delta.magnitude() < 0.0011920929f) {\n        x4_collisionPoints[i] += delta.normalized() * 0.0011920929f;\n      }\n    }\n  }\n\n  x4_collisionPoints[0] = x74_warpPoint;\n  x80_floatingPoint = x4_collisionPoints[8];\n  xa0_26_processed = true;\n}\n\nvoid CFlameWarp::ResetPosition(const zeus::CVector3f& pos) {\n  std::fill(x4_collisionPoints.begin(), x4_collisionPoints.end(), pos);\n  xa0_26_processed = false;\n}\n\nzeus::CAABox CFlameWarp::CalculateBounds() const {\n  zeus::CAABox ret;\n  for (const auto& v : x4_collisionPoints) {\n    ret.accumulateBounds(v);\n  }\n  return ret;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CFlameWarp.hpp",
    "content": "#pragma once\n\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Particle/CWarp.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CStateManager;\n\nclass CFlameWarp : public CWarp {\n  rstl::reserved_vector<zeus::CVector3f, 9> x4_collisionPoints;\n  zeus::CVector3f x74_warpPoint;\n  zeus::CVector3f x80_floatingPoint;\n  float x8c_maxDistSq = 0.f;\n  float x90_minSize = FLT_MAX;\n  float x94_maxSize = FLT_MIN;\n  float x98_maxInfluenceDistSq;\n  CStateManager* x9c_stateMgr = nullptr;\n  bool xa0_24_activated : 1 = false;\n  bool xa0_25_collisionWarp : 1;\n  bool xa0_26_processed : 1 = false;\n\npublic:\n  CFlameWarp(float maxInfluenceDist, const zeus::CVector3f& warpPoint, bool collisionWarp)\n  : x74_warpPoint(warpPoint)\n  , x80_floatingPoint(warpPoint)\n  , x98_maxInfluenceDistSq(maxInfluenceDist * maxInfluenceDist)\n  , xa0_25_collisionWarp{collisionWarp} {\n    x4_collisionPoints.resize(9, warpPoint);\n  }\n\n  const rstl::reserved_vector<zeus::CVector3f, 9>& GetCollisionPoints() const { return x4_collisionPoints; }\n  float GetMinSize() const { return x90_minSize; }\n  float GetMaxSize() const { return x94_maxSize; }\n  void SetWarpPoint(const zeus::CVector3f& p) { x74_warpPoint = p; }\n  void SetFloatingPoint(const zeus::CVector3f& p) { x80_floatingPoint = p; }\n  const zeus::CVector3f& GetFloatingPoint() const { return x80_floatingPoint; }\n  void SetMaxDistSq(float d) { x8c_maxDistSq = d; }\n  void SetStateManager(CStateManager& mgr) { x9c_stateMgr = &mgr; }\n  bool UpdateWarp() override { return xa0_24_activated; }\n  void ModifyParticles(std::vector<CParticle>& particles) override;\n  void Activate(bool val) override { xa0_24_activated = val; }\n  bool IsActivated() override { return xa0_24_activated; }\n  bool IsProcessed() const { return xa0_26_processed; }\n  FourCC Get4CharID() override { return FOURCC('FWRP'); }\n  void ResetPosition(const zeus::CVector3f& pos);\n  zeus::CAABox CalculateBounds() const;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CGenDescription.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/Particle/CColorElement.hpp\"\n#include \"Runtime/Particle/CEmitterElement.hpp\"\n#include \"Runtime/Particle/CIntElement.hpp\"\n#include \"Runtime/Particle/CModVectorElement.hpp\"\n#include \"Runtime/Particle/CParticleDataFactory.hpp\"\n#include \"Runtime/Particle/CRealElement.hpp\"\n#include \"Runtime/Particle/CSpawnSystemKeyframeData.hpp\"\n#include \"Runtime/Particle/CUVElement.hpp\"\n#include \"Runtime/Particle/CVectorElement.hpp\"\n\n/* Documentation at: https://wiki.axiodl.com/w/PART_(File_Format) */\n\nnamespace metaforce {\n\nclass CGenDescription {\npublic:\n  /* Naming convention: <demo-offset>_<retail-offset>_<name> */\n\n  /* Removed from demo */\n  // std::unique_ptr<CVectorElement> x0_PSIV;\n  // std::unique_ptr<CModVectorElement> x4_PSVM;\n  // std::unique_ptr<CVectorElement> x8_PSOV;\n  std::unique_ptr<CIntElement> xc_x0_PSLT;\n  std::unique_ptr<CIntElement> x10_x4_PSWT;\n  std::unique_ptr<CRealElement> x14_x8_PSTS;\n  std::unique_ptr<CVectorElement> x18_xc_POFS;\n  std::unique_ptr<CIntElement> x1c_x10_SEED;\n  std::unique_ptr<CRealElement> x20_x14_LENG;\n  std::unique_ptr<CRealElement> x24_x18_WIDT;\n  std::unique_ptr<CIntElement> x28_x1c_MAXP;\n  std::unique_ptr<CRealElement> x2c_x20_GRTE;\n  std::unique_ptr<CColorElement> x30_x24_COLR;\n  std::unique_ptr<CIntElement> x34_x28_LTME;\n  /* Removed from demo (replaced by EMTR) */\n  // std::unique_ptr<CVectorElement> x38_ILOC;\n  // std::unique_ptr<CVectorElement> x3c_IVEC;\n  std::unique_ptr<CEmitterElement> x40_x2c_EMTR;\n  bool x44_28_x30_28_SORT : 1 = false;\n  bool x44_30_x31_24_MBLR : 1 = false;\n  bool x44_24_x30_24_LINE : 1 = false;\n  bool x44_29_x30_29_LIT_ : 1 = false;\n  bool x44_26_x30_26_AAPH : 1 = false;\n  bool x44_27_x30_27_ZBUF : 1 = false;\n  bool x44_25_x30_25_FXLL : 1 = false;\n  bool x44_31_x31_25_PMAB : 1 = false;\n  bool x45_29_x31_31_VMD4 : 1 = false;\n  bool x45_28_x31_30_VMD3 : 1 = false;\n  bool x45_27_x31_29_VMD2 : 1 = false;\n  bool x45_26_x31_28_VMD1 : 1 = false;\n  bool x45_31_x32_25_OPTS : 1 = false;\n  bool x45_24_x31_26_PMUS : 1 = false;\n  bool x45_25_x31_27_PMOO : 1 = true;\n  bool x45_30_x32_24_CIND : 1 = false;\n  /* 0-00 additions */\n  bool x30_30_ORNT : 1 = false;\n  bool x30_31_RSOP : 1 = false;\n  std::unique_ptr<CIntElement> x48_x34_MBSP;\n  std::unique_ptr<CRealElement> x4c_x38_SIZE;\n  std::unique_ptr<CRealElement> x50_x3c_ROTA;\n  std::unique_ptr<CUVElement> x54_x40_TEXR;\n  std::unique_ptr<CUVElement> x58_x44_TIND;\n  SParticleModel x5c_x48_PMDL;\n  std::unique_ptr<CVectorElement> x6c_x58_PMOP;\n  std::unique_ptr<CVectorElement> x70_x5c_PMRT;\n  std::unique_ptr<CVectorElement> x74_x60_PMSC;\n  std::unique_ptr<CColorElement> x78_x64_PMCL;\n  std::unique_ptr<CModVectorElement> x7c_x68_VEL1;\n  std::unique_ptr<CModVectorElement> x80_x6c_VEL2;\n  std::unique_ptr<CModVectorElement> x84_x70_VEL3;\n  std::unique_ptr<CModVectorElement> x88_x74_VEL4;\n  SChildGeneratorDesc x8c_x78_ICTS;\n  std::unique_ptr<CIntElement> x9c_x88_NCSY;\n  std::unique_ptr<CIntElement> xa0_x8c_CSSD;\n  SChildGeneratorDesc xa4_x90_IDTS;\n  std::unique_ptr<CIntElement> xb4_xa0_NDSY;\n  SChildGeneratorDesc xb8_xa4_IITS;\n  std::unique_ptr<CIntElement> xc8_xb4_PISY;\n  std::unique_ptr<CIntElement> xcc_xb8_SISY;\n  std::unique_ptr<CSpawnSystemKeyframeData> xd0_xbc_KSSM;\n  SSwooshGeneratorDesc xd4_xc0_SSWH;\n  std::unique_ptr<CIntElement> xe4_xd0_SSSD;\n  std::unique_ptr<CVectorElement> xe8_xd4_SSPO;\n  SElectricGeneratorDesc xec_xd8_SELC;\n  std::unique_ptr<CIntElement> xf8_xe4_SESD;\n  std::unique_ptr<CVectorElement> xfc_xe8_SEPO;\n  std::unique_ptr<CIntElement> x100_xec_LTYP;\n  std::unique_ptr<CColorElement> x104_xf0_LCLR;\n  std::unique_ptr<CRealElement> x108_xf4_LINT;\n  std::unique_ptr<CVectorElement> x10c_xf8_LOFF;\n  std::unique_ptr<CVectorElement> x110_xfc_LDIR;\n  std::unique_ptr<CIntElement> x114_x100_LFOT;\n  std::unique_ptr<CRealElement> x118_x104_LFOR;\n  std::unique_ptr<CRealElement> x11c_x108_LSLA;\n  std::unique_ptr<CRealElement> x10c_ADV1;\n  std::unique_ptr<CRealElement> x110_ADV2;\n  std::unique_ptr<CRealElement> x114_ADV3;\n  std::unique_ptr<CRealElement> x118_ADV4;\n  std::unique_ptr<CRealElement> x11c_ADV5;\n  std::unique_ptr<CRealElement> x120_ADV6;\n  std::unique_ptr<CRealElement> x124_ADV7;\n  std::unique_ptr<CRealElement> x128_ADV8;\n\n  /* Custom additions */\n  std::unique_ptr<CColorElement> m_bevelGradient; /* FourCC BGCL */\n\n  CGenDescription() = default;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CIntElement.cpp",
    "content": "#include \"Runtime/Particle/CIntElement.hpp\"\n\n#include <algorithm>\n\n#include \"Runtime/CRandom16.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/Particle/CGenDescription.hpp\"\n#include \"Runtime/Particle/CParticleGlobals.hpp\"\n\n/* Documentation at: https://wiki.axiodl.com/w/Particle_Script#Int_Elements */\n\nnamespace metaforce {\n\nCIEKeyframeEmitter::CIEKeyframeEmitter(CInputStream& in) {\n  x4_percent = in.ReadLong();\n  x8_unk1 = in.ReadLong();\n  xc_loop = in.ReadBool();\n  xd_unk2 = in.ReadBool();\n  x10_loopEnd = in.ReadLong();\n  x14_loopStart = in.ReadLong();\n\n  u32 count = in.ReadLong();\n  x18_keys.reserve(count);\n  for (u32 i = 0; i < count; ++i)\n    x18_keys.push_back(in.ReadInt32());\n}\n\nbool CIEKeyframeEmitter::GetValue([[maybe_unused]] int frame, int& valOut) const {\n  if (x4_percent == 0) {\n    int emitterTime = CParticleGlobals::instance()->m_EmitterTime;\n    if (xc_loop) {\n      if (emitterTime >= x10_loopEnd) {\n        emitterTime -= x14_loopStart;\n        emitterTime = emitterTime % (x10_loopEnd - x14_loopStart);\n        emitterTime += x14_loopStart;\n      }\n      valOut = x18_keys[emitterTime];\n    } else {\n      emitterTime = std::min<int>(emitterTime, x10_loopEnd - 1);\n      valOut = x18_keys[emitterTime];\n    }\n    return false;\n  } else {\n    int ltPerc = CParticleGlobals::instance()->m_ParticleLifetimePercentage;\n    if (ltPerc == 100) {\n      valOut = x18_keys[ltPerc];\n    } else {\n      float ltPercRem = CParticleGlobals::instance()->m_ParticleLifetimePercentageRemainder;\n      float lerp = (1.0f - ltPercRem) * x18_keys[ltPerc] + ltPercRem * x18_keys[ltPerc + 1];\n      valOut = static_cast<int>(lerp);\n    }\n    return false;\n  }\n}\n\nint CIEKeyframeEmitter::GetMaxValue() const { return *std::max_element(x18_keys.cbegin(), x18_keys.cend()); }\n\nbool CIEDeath::GetValue(int frame, int& valOut) const {\n  int b;\n  x4_a->GetValue(frame, valOut);\n  x8_b->GetValue(frame, b);\n  return frame >= b;\n}\n\nint CIEDeath::GetMaxValue() const { return x4_a->GetMaxValue(); }\n\nbool CIEClamp::GetValue(int frame, int& valOut) const {\n  int a, b;\n  x4_min->GetValue(frame, a);\n  x8_max->GetValue(frame, b);\n  xc_val->GetValue(frame, valOut);\n  if (valOut > b) {\n    valOut = b;\n  }\n  if (valOut < a) {\n    valOut = a;\n  }\n  return false;\n}\n\nint CIEClamp::GetMaxValue() const {\n  const int a = x4_min->GetMaxValue();\n  const int b = x8_max->GetMaxValue();\n  const int valOut = xc_val->GetMaxValue();\n\n  return std::clamp(valOut, a, b);\n}\n\nbool CIETimeChain::GetValue(int frame, int& valOut) const {\n  int v;\n  xc_swFrame->GetValue(frame, v);\n  if (frame < v) {\n    return x4_a->GetValue(frame, valOut);\n  } else {\n    return x8_b->GetValue(frame - v, valOut);\n  }\n}\n\nint CIETimeChain::GetMaxValue() const { return std::max(x8_b->GetMaxValue(), x4_a->GetMaxValue()); }\n\nbool CIEAdd::GetValue(int frame, int& valOut) const {\n  int a, b;\n  x4_a->GetValue(frame, a);\n  x8_b->GetValue(frame, b);\n  valOut = a + b;\n  return false;\n}\n\nint CIEAdd::GetMaxValue() const {\n  const int a = x4_a->GetMaxValue();\n  const int b = x8_b->GetMaxValue();\n  return a + b;\n}\n\nbool CIEConstant::GetValue([[maybe_unused]] int frame, int& valOut) const {\n  valOut = x4_val;\n  return false;\n}\n\nint CIEConstant::GetMaxValue() const { return x4_val; }\n\nbool CIEImpulse::GetValue(int frame, int& valOut) const {\n  if (frame == 0) {\n    x4_a->GetValue(frame, valOut);\n  } else {\n    valOut = 0;\n  }\n  return false;\n}\n\nint CIEImpulse::GetMaxValue() const { return x4_a->GetMaxValue(); }\n\nbool CIELifetimePercent::GetValue(int frame, int& valOut) const {\n  int a = 0;\n  x4_percentVal->GetValue(frame, a);\n  if (a < 0) {\n    a = 0;\n  }\n  valOut = (a / 100.0f) * CParticleGlobals::instance()->m_ParticleLifetimeReal + 0.5f;\n  return false;\n}\n\nint CIELifetimePercent::GetMaxValue() const {\n  const int a = std::max(0, x4_percentVal->GetMaxValue());\n\n  // Assume 10000 frames max (not ideal estimate)\n  return int((float(a) / 100.0f) * 10000 + 0.5f);\n}\n\nbool CIEInitialRandom::GetValue(int frame, int& valOut) const {\n  if (frame == 0) {\n    int a, b;\n    x4_a->GetValue(frame, a);\n    x8_b->GetValue(frame, b);\n    valOut = CRandom16::GetRandomNumber()->Range(a, b);\n  }\n  return false;\n}\n\nint CIEInitialRandom::GetMaxValue() const { return x8_b->GetMaxValue(); }\n\nbool CIEPulse::GetValue(int frame, int& valOut) const {\n  int a, b;\n  x4_aDuration->GetValue(frame, a);\n  x8_bDuration->GetValue(frame, b);\n  int cv = a + b + 1;\n  if (cv < 0) {\n    cv = 1;\n  }\n\n  if (b >= 1) {\n    if (frame % cv > a) {\n      x10_bVal->GetValue(frame, valOut);\n    } else {\n      xc_aVal->GetValue(frame, valOut);\n    }\n  } else {\n    xc_aVal->GetValue(frame, valOut);\n  }\n  return false;\n}\n\nint CIEPulse::GetMaxValue() const { return std::max(xc_aVal->GetMaxValue(), x10_bVal->GetMaxValue()); }\n\nbool CIEMultiply::GetValue(int frame, int& valOut) const {\n  int a, b;\n  x4_a->GetValue(frame, a);\n  x8_b->GetValue(frame, b);\n  valOut = a * b;\n  return false;\n}\n\nint CIEMultiply::GetMaxValue() const { return x4_a->GetMaxValue() * x8_b->GetMaxValue(); }\n\nbool CIESampleAndHold::GetValue(int frame, int& valOut) const {\n  bool ret;\n  if (x8_nextSampleFrame < frame) {\n    int b, c;\n    xc_waitFramesMin->GetValue(frame, b);\n    x10_waitFramesMax->GetValue(frame, c);\n    x8_nextSampleFrame = CRandom16::GetRandomNumber()->Range(b, c) + frame;\n    ret = x4_sampleSource->GetValue(frame, valOut);\n    x14_holdVal = valOut;\n  } else {\n    valOut = x14_holdVal;\n    ret = false;\n  }\n  return ret;\n}\n\nint CIESampleAndHold::GetMaxValue() const { return x4_sampleSource->GetMaxValue(); }\n\nbool CIERandom::GetValue(int frame, int& valOut) const {\n  int a, b;\n  x4_min->GetValue(frame, a);\n  x8_max->GetValue(frame, b);\n  if (a > 0) {\n    valOut = CRandom16::GetRandomNumber()->Range(a, b);\n  } else {\n    valOut = CRandom16::GetRandomNumber()->Next();\n  }\n  return false;\n}\n\nint CIERandom::GetMaxValue() const {\n  if (x4_min->GetMaxValue() > 0)\n    return x8_max->GetMaxValue();\n  else\n    return 65535;\n}\n\nbool CIETimeScale::GetValue(int frame, int& valOut) const {\n  float a;\n  x4_a->GetValue(frame, a);\n  valOut = static_cast< float >(frame) * a;\n  return false;\n}\n\nint CIETimeScale::GetMaxValue() const { return 10000; /* Assume 10000 frames max (not ideal estimate) */ }\n\nbool CIEGetCumulativeParticleCount::GetValue([[maybe_unused]] int frame, int& valOut) const {\n  valOut = CParticleGlobals::instance()->m_currentParticleSystem->x4_system->GetCumulativeParticleCount();\n  return false;\n}\n\nint CIEGetCumulativeParticleCount::GetMaxValue() const { return 256; }\n\nbool CIEGetActiveParticleCount::GetValue([[maybe_unused]] int frame, int& valOut) const {\n  valOut = CParticleGlobals::instance()->m_currentParticleSystem->x4_system->GetParticleCount();\n  return false;\n}\n\nint CIEGetActiveParticleCount::GetMaxValue() const { return 256; }\n\nbool CIEGetEmitterTime::GetValue([[maybe_unused]] int frame, int& valOut) const {\n  valOut = CParticleGlobals::instance()->m_currentParticleSystem->x4_system->GetEmitterTime();\n  return false;\n}\n\nint CIEGetEmitterTime::GetMaxValue() const { return 10000; /* Assume 10000 frames max (not ideal estimate) */ }\n\nbool CIEModulo::GetValue(int frame, int& valOut) const {\n  int a, b;\n  x4_a->GetValue(frame, a);\n  x8_b->GetValue(frame, b);\n  if (b != 0) {\n    valOut = a % b;\n  } else {\n    valOut = a;\n  }\n  return false;\n}\n\nint CIEModulo::GetMaxValue() const {\n  const int a = x4_a->GetMaxValue();\n  const int b = x8_b->GetMaxValue();\n\n  if (b != 0) {\n    return b - 1;\n  }\n\n  return a;\n}\n\nbool CIESubtract::GetValue(int frame, int& valOut) const {\n  int a, b;\n  x4_a->GetValue(frame, a);\n  x8_b->GetValue(frame, b);\n  valOut = a - b;\n  return false;\n}\n\nint CIESubtract::GetMaxValue() const {\n  const int a = x4_a->GetMaxValue();\n  const int b = x8_b->GetMaxValue();\n  return a - b;\n}\n\nbool CIERealToInt::GetValue(int frame, int& valOut) const {\n  float a = 0.0f;\n  float b = 1.0f;\n\n  x8_b->GetValue(frame, b);\n  x4_a->GetValue(frame, a);\n\n  valOut = static_cast<int>(a * b);\n  return false;\n}\n\nint CIERealToInt::GetMaxValue() const {\n  // TODO: Implement\n  return 1;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CIntElement.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <vector>\n\n#include \"Runtime/Particle/IElement.hpp\"\n\n/* Documentation at: https://wiki.axiodl.com/w/Particle_Script#Int_Elements */\n\nnamespace metaforce {\n\nclass CIEKeyframeEmitter : public CIntElement {\n  u32 x4_percent;\n  u32 x8_unk1;\n  bool xc_loop;\n  bool xd_unk2;\n  u32 x10_loopEnd;\n  u32 x14_loopStart;\n  std::vector<int> x18_keys;\n\npublic:\n  explicit CIEKeyframeEmitter(CInputStream& in);\n  bool GetValue(int frame, int& valOut) const override;\n  int GetMaxValue() const override;\n};\n\nclass CIEDeath : public CIntElement {\n  std::unique_ptr<CIntElement> x4_a;\n  std::unique_ptr<CIntElement> x8_b;\n\npublic:\n  CIEDeath(std::unique_ptr<CIntElement>&& a, std::unique_ptr<CIntElement>&& b)\n  : x4_a(std::move(a)), x8_b(std::move(b)) {}\n  bool GetValue(int frame, int& valOut) const override;\n  int GetMaxValue() const override;\n};\n\nclass CIEClamp : public CIntElement {\n  std::unique_ptr<CIntElement> x4_min;\n  std::unique_ptr<CIntElement> x8_max;\n  std::unique_ptr<CIntElement> xc_val;\n\npublic:\n  CIEClamp(std::unique_ptr<CIntElement>&& a, std::unique_ptr<CIntElement>&& b, std::unique_ptr<CIntElement>&& c)\n  : x4_min(std::move(a)), x8_max(std::move(b)), xc_val(std::move(c)) {}\n  bool GetValue(int frame, int& valOut) const override;\n  int GetMaxValue() const override;\n};\n\nclass CIETimeChain : public CIntElement {\n  std::unique_ptr<CIntElement> x4_a;\n  std::unique_ptr<CIntElement> x8_b;\n  std::unique_ptr<CIntElement> xc_swFrame;\n\npublic:\n  CIETimeChain(std::unique_ptr<CIntElement>&& a, std::unique_ptr<CIntElement>&& b, std::unique_ptr<CIntElement>&& c)\n  : x4_a(std::move(a)), x8_b(std::move(b)), xc_swFrame(std::move(c)) {}\n  bool GetValue(int frame, int& valOut) const override;\n  int GetMaxValue() const override;\n};\n\nclass CIEAdd : public CIntElement {\n  std::unique_ptr<CIntElement> x4_a;\n  std::unique_ptr<CIntElement> x8_b;\n\npublic:\n  CIEAdd(std::unique_ptr<CIntElement>&& a, std::unique_ptr<CIntElement>&& b) : x4_a(std::move(a)), x8_b(std::move(b)) {}\n  bool GetValue(int frame, int& valOut) const override;\n  int GetMaxValue() const override;\n};\n\nclass CIEConstant : public CIntElement {\n  int x4_val;\n\npublic:\n  explicit CIEConstant(int val) : x4_val(val) {}\n  bool GetValue(int frame, int& valOut) const override;\n  int GetMaxValue() const override;\n};\n\nclass CIEImpulse : public CIntElement {\n  std::unique_ptr<CIntElement> x4_a;\n\npublic:\n  explicit CIEImpulse(std::unique_ptr<CIntElement>&& a) : x4_a(std::move(a)) {}\n  bool GetValue(int frame, int& valOut) const override;\n  int GetMaxValue() const override;\n};\n\nclass CIELifetimePercent : public CIntElement {\n  std::unique_ptr<CIntElement> x4_percentVal;\n\npublic:\n  explicit CIELifetimePercent(std::unique_ptr<CIntElement>&& a) : x4_percentVal(std::move(a)) {}\n  bool GetValue(int frame, int& valOut) const override;\n  int GetMaxValue() const override;\n};\n\nclass CIEInitialRandom : public CIntElement {\n  std::unique_ptr<CIntElement> x4_a;\n  std::unique_ptr<CIntElement> x8_b;\n\npublic:\n  CIEInitialRandom(std::unique_ptr<CIntElement>&& a, std::unique_ptr<CIntElement>&& b)\n  : x4_a(std::move(a)), x8_b(std::move(b)) {}\n  bool GetValue(int frame, int& valOut) const override;\n  int GetMaxValue() const override;\n};\n\nclass CIEPulse : public CIntElement {\n  std::unique_ptr<CIntElement> x4_aDuration;\n  std::unique_ptr<CIntElement> x8_bDuration;\n  std::unique_ptr<CIntElement> xc_aVal;\n  std::unique_ptr<CIntElement> x10_bVal;\n\npublic:\n  CIEPulse(std::unique_ptr<CIntElement>&& a, std::unique_ptr<CIntElement>&& b, std::unique_ptr<CIntElement>&& c,\n           std::unique_ptr<CIntElement>&& d)\n  : x4_aDuration(std::move(a)), x8_bDuration(std::move(b)), xc_aVal(std::move(c)), x10_bVal(std::move(d)) {}\n  bool GetValue(int frame, int& valOut) const override;\n  int GetMaxValue() const override;\n};\n\nclass CIEMultiply : public CIntElement {\n  std::unique_ptr<CIntElement> x4_a;\n  std::unique_ptr<CIntElement> x8_b;\n\npublic:\n  CIEMultiply(std::unique_ptr<CIntElement>&& a, std::unique_ptr<CIntElement>&& b)\n  : x4_a(std::move(a)), x8_b(std::move(b)) {}\n  bool GetValue(int frame, int& valOut) const override;\n  int GetMaxValue() const override;\n};\n\nclass CIESampleAndHold : public CIntElement {\n  std::unique_ptr<CIntElement> x4_sampleSource;\n  mutable int x8_nextSampleFrame = 0;\n  std::unique_ptr<CIntElement> xc_waitFramesMin;\n  std::unique_ptr<CIntElement> x10_waitFramesMax;\n  mutable int x14_holdVal = 0;\n\npublic:\n  CIESampleAndHold(std::unique_ptr<CIntElement>&& a, std::unique_ptr<CIntElement>&& b, std::unique_ptr<CIntElement>&& c)\n  : x4_sampleSource(std::move(a)), xc_waitFramesMin(std::move(b)), x10_waitFramesMax(std::move(c)) {}\n  bool GetValue(int frame, int& valOut) const override;\n  int GetMaxValue() const override;\n};\n\nclass CIERandom : public CIntElement {\n  std::unique_ptr<CIntElement> x4_min;\n  std::unique_ptr<CIntElement> x8_max;\n\npublic:\n  CIERandom(std::unique_ptr<CIntElement>&& a, std::unique_ptr<CIntElement>&& b)\n  : x4_min(std::move(a)), x8_max(std::move(b)) {}\n  bool GetValue(int frame, int& valOut) const override;\n  int GetMaxValue() const override;\n};\n\nclass CIETimeScale : public CIntElement {\n  std::unique_ptr<CRealElement> x4_a;\n\npublic:\n  explicit CIETimeScale(std::unique_ptr<CRealElement>&& a) : x4_a(std::move(a)) {}\n  bool GetValue(int frame, int& valOut) const override;\n  int GetMaxValue() const override;\n};\n\nclass CIEGetCumulativeParticleCount : public CIntElement {\npublic:\n  bool GetValue(int frame, int& valOut) const override;\n  int GetMaxValue() const override;\n};\n\nclass CIEGetActiveParticleCount : public CIntElement {\npublic:\n  bool GetValue(int frame, int& valOut) const override;\n  int GetMaxValue() const override;\n};\n\nclass CIEGetEmitterTime : public CIntElement {\npublic:\n  bool GetValue(int frame, int& valOut) const override;\n  int GetMaxValue() const override;\n};\n\nclass CIEModulo : public CIntElement {\n  std::unique_ptr<CIntElement> x4_a;\n  std::unique_ptr<CIntElement> x8_b;\n\npublic:\n  CIEModulo(std::unique_ptr<CIntElement>&& a, std::unique_ptr<CIntElement>&& b)\n  : x4_a(std::move(a)), x8_b(std::move(b)) {}\n  bool GetValue(int frame, int& valOut) const override;\n  int GetMaxValue() const override;\n};\n\nclass CIESubtract : public CIntElement {\n  std::unique_ptr<CIntElement> x4_a;\n  std::unique_ptr<CIntElement> x8_b;\n\npublic:\n  CIESubtract(std::unique_ptr<CIntElement>&& a, std::unique_ptr<CIntElement>&& b)\n  : x4_a(std::move(a)), x8_b(std::move(b)) {}\n  bool GetValue(int frame, int& valOut) const override;\n  int GetMaxValue() const override;\n};\n\nclass CIERealToInt final : public CIntElement {\n  std::unique_ptr<CRealElement> x4_a;\n  std::unique_ptr<CRealElement> x8_b;\n\npublic:\n  explicit CIERealToInt(std::unique_ptr<CRealElement>&& a, std::unique_ptr<CRealElement>&& b)\n  : x4_a{std::move(a)}, x8_b{std::move(b)} {}\n\n  bool GetValue(int frame, int& valOut) const override;\n  int GetMaxValue() const override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CMakeLists.txt",
    "content": "set(PARTICLE_SOURCES\n        IElement.hpp\n        CGenDescription.hpp\n        CRealElement.hpp CRealElement.cpp\n        CIntElement.hpp CIntElement.cpp\n        CVectorElement.hpp CVectorElement.cpp\n        CModVectorElement.hpp CModVectorElement.cpp\n        CColorElement.hpp CColorElement.cpp\n        CUVElement.hpp CUVElement.cpp\n        CEmitterElement.hpp CEmitterElement.cpp\n        CParticleDataFactory.hpp CParticleDataFactory.cpp\n        CSwooshDescription.hpp\n        CElectricDescription.hpp\n        CDecalDescription.hpp\n        CWeaponDescription.hpp\n        CDecalDataFactory.hpp CDecalDataFactory.cpp\n        CElementGen.hpp CElementGen.cpp\n        CParticleSwooshDataFactory.hpp CParticleSwooshDataFactory.cpp\n        CParticleSwoosh.hpp CParticleSwoosh.cpp\n        CParticleElectricDataFactory.hpp CParticleElectricDataFactory.cpp\n        CParticleElectric.hpp CParticleElectric.cpp\n        CParticleGen.hpp CParticleGen.cpp\n        CProjectileWeaponDataFactory.hpp CProjectileWeaponDataFactory.cpp\n        CDecal.hpp CDecal.cpp\n        CDecalManager.hpp CDecalManager.cpp\n        CSpawnSystemKeyframeData.hpp CSpawnSystemKeyframeData.cpp\n        CWarp.hpp\n        CFlameWarp.hpp CFlameWarp.cpp\n        CParticleGlobals.hpp CParticleGlobals.cpp\n        ${PLAT_SRCS})\n\nruntime_add_list(Particle PARTICLE_SOURCES)\n"
  },
  {
    "path": "Runtime/Particle/CModVectorElement.cpp",
    "content": "#include \"Runtime/Particle/CModVectorElement.hpp\"\n\n#include \"Runtime/CRandom16.hpp\"\n#include \"Runtime/Particle/CParticleGlobals.hpp\"\n\n#include <zeus/Math.hpp>\n\n/* Documentation at: https://wiki.axiodl.com/w/Particle_Script#Mod_Vector_Elements */\n\nnamespace metaforce {\n\nbool CMVEImplosion::GetValue(int frame, zeus::CVector3f& pVel, zeus::CVector3f& pPos) const {\n  zeus::CVector3f av;\n  x4_implPoint->GetValue(frame, av);\n\n  zeus::CVector3f dv = av - pPos;\n  float dvm = dv.magnitude();\n\n  float c;\n  xc_maxMag->GetValue(frame, c);\n  if (dvm > c)\n    return false;\n\n  float d;\n  x10_minMag->GetValue(frame, d);\n  if (x14_enableMinMag && dvm < d)\n    return true;\n\n  if (0.f == dvm)\n    return false;\n\n  float b;\n  x8_magScale->GetValue(frame, b);\n  pVel += zeus::CVector3f(b / dvm) * dv;\n  return false;\n}\n\nbool CMVEExponentialImplosion::GetValue(int frame, zeus::CVector3f& pVel, zeus::CVector3f& pPos) const {\n  zeus::CVector3f av;\n  x4_implPoint->GetValue(frame, av);\n\n  zeus::CVector3f dv = av - pPos;\n  float dvm = dv.magnitude();\n\n  float c;\n  xc_maxMag->GetValue(frame, c);\n  if (dvm > c)\n    return false;\n\n  float d;\n  x10_minMag->GetValue(frame, d);\n  if (x14_enableMinMag && dvm < d)\n    return true;\n\n  if (0.f == dvm)\n    return false;\n\n  float b;\n  x8_magScale->GetValue(frame, b);\n  pVel += zeus::CVector3f(b) * dv;\n  return false;\n}\n\nbool CMVELinearImplosion::GetValue(int frame, zeus::CVector3f& pVel, zeus::CVector3f& pPos) const {\n  zeus::CVector3f av;\n  x4_implPoint->GetValue(frame, av);\n\n  zeus::CVector3f dv = av - pPos;\n  float dvm = dv.magnitude();\n\n  float c;\n  xc_maxMag->GetValue(frame, c);\n  if (dvm > c)\n    return false;\n\n  float d;\n  x10_minMag->GetValue(frame, d);\n  if (x14_enableMinMag && dvm < d)\n    return true;\n\n  if (0.f == dvm)\n    return false;\n\n  float b;\n  x8_magScale->GetValue(frame, b);\n  pVel = zeus::CVector3f(b / dvm) * dv;\n  return false;\n}\n\nbool CMVETimeChain::GetValue(int frame, zeus::CVector3f& pVel, zeus::CVector3f& pPos) const {\n  int v;\n  xc_swFrame->GetValue(frame, v);\n  if (frame < v) {\n    return x4_a->GetValue(frame, pVel, pPos);\n  } else {\n    return x8_b->GetValue(frame - v, pVel, pPos);\n  }\n}\n\nCMVEBounce::CMVEBounce(std::unique_ptr<CVectorElement>&& planePoint, std::unique_ptr<CVectorElement>&& planeNormal,\n                       std::unique_ptr<CRealElement>&& friction, std::unique_ptr<CRealElement>&& restitution, bool e)\n: x4_planePoint(std::move(planePoint))\n, x8_planeNormal(std::move(planeNormal))\n, xc_friction(std::move(friction))\n, x10_restitution(std::move(restitution))\n, x15_dieOnPenetrate(e) {\n  if (x4_planePoint && x8_planeNormal && x4_planePoint->IsFastConstant() && x8_planeNormal->IsFastConstant()) {\n    /* Precompute Hesse normal form of plane (for penetration testing)\n     * https://en.wikipedia.org/wiki/Hesse_normal_form */\n    x14_planePrecomputed = true;\n    x8_planeNormal->GetValue(0, x18_planeValidatedNormal);\n\n    if (x18_planeValidatedNormal.magSquared() > 0.0)\n      x18_planeValidatedNormal.normalize();\n    zeus::CVector3f a;\n    x4_planePoint->GetValue(0, a);\n    x24_planeD = x18_planeValidatedNormal.dot(a);\n  }\n}\n\nbool CMVEBounce::GetValue(int frame, zeus::CVector3f& pVel, zeus::CVector3f& pPos) const {\n  if (!x14_planePrecomputed) {\n    /* Compute Hesse normal form of plane (for penetration testing) */\n    x8_planeNormal->GetValue(frame, const_cast<zeus::CVector3f&>(x18_planeValidatedNormal));\n    const_cast<zeus::CVector3f&>(x18_planeValidatedNormal).normalize();\n\n    zeus::CVector3f a;\n    x4_planePoint->GetValue(frame, a);\n\n    const_cast<float&>(x24_planeD) = x18_planeValidatedNormal.dot(a);\n  }\n\n  float dot = x18_planeValidatedNormal.dot(pPos);\n  if (dot - x24_planeD > 0.0f)\n    return false;\n  else if (x15_dieOnPenetrate)\n    return true;\n\n  /* Deflection event */\n\n  if (pVel.dot(x18_planeValidatedNormal) >= 0.0f)\n    return false;\n\n  zeus::CVector3f delta = pPos - pVel;\n  pPos += (-(delta.dot(x18_planeValidatedNormal) - x24_planeD) / pVel.dot(x18_planeValidatedNormal) - 1.f) * pVel;\n\n  float d = 0.0f;\n  x10_restitution->GetValue(frame, d);\n  pVel -= d * pVel;\n\n  float c = 0.0f;\n  xc_friction->GetValue(frame, c);\n  pVel -= (1.f + c) * x18_planeValidatedNormal.dot(pVel) * x18_planeValidatedNormal;\n  return false;\n}\n\nbool CMVEConstant::GetValue(int frame, zeus::CVector3f& pVel, zeus::CVector3f& /*pPos*/) const {\n  float x, y, z;\n  x4_x->GetValue(frame, x);\n  x8_y->GetValue(frame, y);\n  xc_z->GetValue(frame, z);\n  pVel.assign(x, y, z);\n  return false;\n}\n\nbool CMVEFastConstant::GetValue(int /*frame*/, zeus::CVector3f& pVel, zeus::CVector3f& /*pPos*/) const {\n  pVel = x4_val;\n  return false;\n}\n\nbool CMVEGravity::GetValue(int frame, zeus::CVector3f& pVel, zeus::CVector3f& /*pPos*/) const {\n  zeus::CVector3f grav;\n  x4_a->GetValue(frame, grav);\n  pVel += grav;\n  return false;\n}\n\nbool CMVEExplode::GetValue(int frame, zeus::CVector3f& pVel, zeus::CVector3f& /*pPos*/) const {\n  if (frame == 0) {\n    CRandom16* rand = CRandom16::GetRandomNumber();\n    zeus::CVector3f vec = {rand->Float() - 0.5f, rand->Float() - 0.5f, rand->Float() - 0.5f};\n    vec.normalize();\n    float a;\n    x4_a->GetValue(frame, a);\n    pVel = vec * a;\n  } else {\n    float b;\n    x8_b->GetValue(frame, b);\n    pVel *= zeus::CVector3f(b);\n  }\n\n  return false;\n}\n\nbool CMVESetPosition::GetValue(int frame, zeus::CVector3f& /*pVel*/, zeus::CVector3f& pPos) const {\n  x4_a->GetValue(frame, pPos);\n  return false;\n}\n\nbool CMVEPulse::GetValue(int frame, zeus::CVector3f& pVel, zeus::CVector3f& pPos) const {\n  int a, b;\n  x4_aDuration->GetValue(frame, a);\n  x8_bDuration->GetValue(frame, b);\n  int cv = a + b + 1;\n  if (cv < 0) {\n    cv = 1;\n  }\n\n  if (b < 1 || frame % cv <= a) {\n    xc_aVal->GetValue(frame, pVel, pPos);\n  } else {\n    x10_bVal->GetValue(frame, pVel, pPos);\n  }\n  return false;\n}\n\nbool CMVEWind::GetValue(int frame, zeus::CVector3f& pVel, zeus::CVector3f& /*pPos*/) const {\n  zeus::CVector3f wVel;\n  x4_velocity->GetValue(frame, wVel);\n  float factor;\n  x8_factor->GetValue(frame, factor);\n  pVel += (wVel - pVel) * factor;\n  return false;\n}\n\nbool CMVESwirl::GetValue(int frame, zeus::CVector3f& pVel, zeus::CVector3f& pPos) const {\n  zeus::CVector3f a, b;\n  x4_helixPoint->GetValue(frame, a);\n  x8_curveBinormal->GetValue(frame, b);\n\n  const zeus::CVector3f posToOrigin = a - pPos;\n  const zeus::CVector3f posToHelix = posToOrigin - posToOrigin.dot(b) * b;\n  float c = 0.0f, d = 0.0f;\n  xc_filterGain->GetValue(frame, c);\n  x10_tangentialVelocity->GetValue(frame, d);\n  const zeus::CVector3f wetVel = (posToHelix.cross(b) * d + b * b.dot(pVel));\n  pVel = c * wetVel + (1.f - c) * pVel;\n  return false;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CModVectorElement.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include \"Runtime/Particle/IElement.hpp\"\n\n/* Documentation at: https://wiki.axiodl.com/w/Particle_Script#Mod_Vector_Elements */\n\nnamespace metaforce {\n\nclass CMVEImplosion : public CModVectorElement {\n  std::unique_ptr<CVectorElement> x4_implPoint;\n  std::unique_ptr<CRealElement> x8_magScale;\n  std::unique_ptr<CRealElement> xc_maxMag;\n  std::unique_ptr<CRealElement> x10_minMag;\n  bool x14_enableMinMag;\n\npublic:\n  CMVEImplosion(std::unique_ptr<CVectorElement>&& a, std::unique_ptr<CRealElement>&& b,\n                std::unique_ptr<CRealElement>&& c, std::unique_ptr<CRealElement>&& d, bool e)\n  : x4_implPoint(std::move(a))\n  , x8_magScale(std::move(b))\n  , xc_maxMag(std::move(c))\n  , x10_minMag(std::move(d))\n  , x14_enableMinMag(e) {}\n  bool GetValue(int frame, zeus::CVector3f& pVel, zeus::CVector3f& pPos) const override;\n};\n\nclass CMVEExponentialImplosion : public CModVectorElement {\n  std::unique_ptr<CVectorElement> x4_implPoint;\n  std::unique_ptr<CRealElement> x8_magScale;\n  std::unique_ptr<CRealElement> xc_maxMag;\n  std::unique_ptr<CRealElement> x10_minMag;\n  bool x14_enableMinMag;\n\npublic:\n  CMVEExponentialImplosion(std::unique_ptr<CVectorElement>&& a, std::unique_ptr<CRealElement>&& b,\n                           std::unique_ptr<CRealElement>&& c, std::unique_ptr<CRealElement>&& d, bool e)\n  : x4_implPoint(std::move(a))\n  , x8_magScale(std::move(b))\n  , xc_maxMag(std::move(c))\n  , x10_minMag(std::move(d))\n  , x14_enableMinMag(e) {}\n  bool GetValue(int frame, zeus::CVector3f& pVel, zeus::CVector3f& pPos) const override;\n};\n\nclass CMVELinearImplosion : public CModVectorElement {\n  std::unique_ptr<CVectorElement> x4_implPoint;\n  std::unique_ptr<CRealElement> x8_magScale;\n  std::unique_ptr<CRealElement> xc_maxMag;\n  std::unique_ptr<CRealElement> x10_minMag;\n  bool x14_enableMinMag;\n\npublic:\n  CMVELinearImplosion(std::unique_ptr<CVectorElement>&& a, std::unique_ptr<CRealElement>&& b,\n                      std::unique_ptr<CRealElement>&& c, std::unique_ptr<CRealElement>&& d, bool e)\n  : x4_implPoint(std::move(a))\n  , x8_magScale(std::move(b))\n  , xc_maxMag(std::move(c))\n  , x10_minMag(std::move(d))\n  , x14_enableMinMag(e) {}\n  bool GetValue(int frame, zeus::CVector3f& pVel, zeus::CVector3f& pPos) const override;\n};\n\nclass CMVETimeChain : public CModVectorElement {\n  std::unique_ptr<CModVectorElement> x4_a;\n  std::unique_ptr<CModVectorElement> x8_b;\n  std::unique_ptr<CIntElement> xc_swFrame;\n\npublic:\n  CMVETimeChain(std::unique_ptr<CModVectorElement>&& a, std::unique_ptr<CModVectorElement>&& b,\n                std::unique_ptr<CIntElement>&& c)\n  : x4_a(std::move(a)), x8_b(std::move(b)), xc_swFrame(std::move(c)) {}\n  bool GetValue(int frame, zeus::CVector3f& pVel, zeus::CVector3f& pPos) const override;\n};\n\nclass CMVEBounce : public CModVectorElement {\n  std::unique_ptr<CVectorElement> x4_planePoint;\n  std::unique_ptr<CVectorElement> x8_planeNormal;\n  std::unique_ptr<CRealElement> xc_friction;\n  std::unique_ptr<CRealElement> x10_restitution;\n  bool x14_planePrecomputed = false;\n  bool x15_dieOnPenetrate;\n  zeus::CVector3f x18_planeValidatedNormal;\n  float x24_planeD = 0.0f;\n\npublic:\n  CMVEBounce(std::unique_ptr<CVectorElement>&& planePoint, std::unique_ptr<CVectorElement>&& planeNormal,\n             std::unique_ptr<CRealElement>&& friction, std::unique_ptr<CRealElement>&& restitution, bool e);\n  bool GetValue(int frame, zeus::CVector3f& pVel, zeus::CVector3f& pPos) const override;\n};\n\nclass CMVEConstant : public CModVectorElement {\n  std::unique_ptr<CRealElement> x4_x;\n  std::unique_ptr<CRealElement> x8_y;\n  std::unique_ptr<CRealElement> xc_z;\n\npublic:\n  CMVEConstant(std::unique_ptr<CRealElement>&& a, std::unique_ptr<CRealElement>&& b, std::unique_ptr<CRealElement>&& c)\n  : x4_x(std::move(a)), x8_y(std::move(b)), xc_z(std::move(c)) {}\n  bool GetValue(int frame, zeus::CVector3f& pVel, zeus::CVector3f& pPos) const override;\n};\n\nclass CMVEFastConstant : public CModVectorElement {\n  zeus::CVector3f x4_val;\n\npublic:\n  CMVEFastConstant(float a, float b, float c) : x4_val(a, b, c) {}\n  bool GetValue(int frame, zeus::CVector3f& pVel, zeus::CVector3f& pPos) const override;\n};\n\nclass CMVEGravity : public CModVectorElement {\n  std::unique_ptr<CVectorElement> x4_a;\n\npublic:\n  explicit CMVEGravity(std::unique_ptr<CVectorElement>&& a) : x4_a(std::move(a)) {}\n  bool GetValue(int frame, zeus::CVector3f& pVel, zeus::CVector3f& pPos) const override;\n};\n\nclass CMVEExplode : public CModVectorElement {\n  std::unique_ptr<CRealElement> x4_a;\n  std::unique_ptr<CRealElement> x8_b;\n\npublic:\n  CMVEExplode(std::unique_ptr<CRealElement>&& a, std::unique_ptr<CRealElement>&& b)\n  : x4_a(std::move(a)), x8_b(std::move(b)) {}\n  bool GetValue(int frame, zeus::CVector3f& pVel, zeus::CVector3f& pPos) const override;\n};\n\nclass CMVESetPosition : public CModVectorElement {\n  std::unique_ptr<CVectorElement> x4_a;\n\npublic:\n  explicit CMVESetPosition(std::unique_ptr<CVectorElement>&& a) : x4_a(std::move(a)) {}\n  bool GetValue(int frame, zeus::CVector3f& pVel, zeus::CVector3f& pPos) const override;\n};\n\nclass CMVEPulse : public CModVectorElement {\n  std::unique_ptr<CIntElement> x4_aDuration;\n  std::unique_ptr<CIntElement> x8_bDuration;\n  std::unique_ptr<CModVectorElement> xc_aVal;\n  std::unique_ptr<CModVectorElement> x10_bVal;\n\npublic:\n  CMVEPulse(std::unique_ptr<CIntElement>&& a, std::unique_ptr<CIntElement>&& b, std::unique_ptr<CModVectorElement>&& c,\n            std::unique_ptr<CModVectorElement>&& d)\n  : x4_aDuration(std::move(a)), x8_bDuration(std::move(b)), xc_aVal(std::move(c)), x10_bVal(std::move(d)) {}\n  bool GetValue(int frame, zeus::CVector3f& pVel, zeus::CVector3f& pPos) const override;\n};\n\nclass CMVEWind : public CModVectorElement {\n  std::unique_ptr<CVectorElement> x4_velocity;\n  std::unique_ptr<CRealElement> x8_factor;\n\npublic:\n  CMVEWind(std::unique_ptr<CVectorElement>&& a, std::unique_ptr<CRealElement>&& b)\n  : x4_velocity(std::move(a)), x8_factor(std::move(b)) {}\n  bool GetValue(int frame, zeus::CVector3f& pVel, zeus::CVector3f& pPos) const override;\n};\n\nclass CMVESwirl : public CModVectorElement {\n  std::unique_ptr<CVectorElement> x4_helixPoint;\n  std::unique_ptr<CVectorElement> x8_curveBinormal;\n  std::unique_ptr<CRealElement> xc_filterGain;\n  std::unique_ptr<CRealElement> x10_tangentialVelocity;\n\npublic:\n  CMVESwirl(std::unique_ptr<CVectorElement>&& a, std::unique_ptr<CVectorElement>&& b, std::unique_ptr<CRealElement>&& c,\n            std::unique_ptr<CRealElement>&& d)\n  : x4_helixPoint(std::move(a))\n  , x8_curveBinormal(std::move(b))\n  , xc_filterGain(std::move(c))\n  , x10_tangentialVelocity(std::move(d)) {}\n  bool GetValue(int frame, zeus::CVector3f& pVel, zeus::CVector3f& pPos) const override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CParticleDataFactory.cpp",
    "content": "#include \"Runtime/Particle/CParticleDataFactory.hpp\"\n\n#include \"Runtime/CRandom16.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/Graphics/CModel.hpp\"\n#include \"Runtime/Particle/CElectricDescription.hpp\"\n#include \"Runtime/Particle/CGenDescription.hpp\"\n#include \"Runtime/Particle/CSwooshDescription.hpp\"\n#include \"Runtime/Logging.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\nnamespace metaforce {\nfloat CParticleDataFactory::GetReal(CInputStream& in) { return in.ReadFloat(); }\n\ns32 CParticleDataFactory::GetInt(CInputStream& in) { return in.ReadInt32(); }\n\nbool CParticleDataFactory::GetBool(CInputStream& in) {\n  FourCC cid = GetClassID(in);\n  if (cid != FOURCC('CNST'))\n    spdlog::fatal(\"bool element does not begin with CNST\");\n  return in.ReadBool();\n}\n\nFourCC CParticleDataFactory::GetClassID(CInputStream& in) {\n  u32 val = 0;\n  in.Get(reinterpret_cast<u8*>(&val), 4);\n  return val;\n}\n\nSParticleModel CParticleDataFactory::GetModel(CInputStream& in, CSimplePool* resPool) {\n  FourCC clsId = GetClassID(in);\n  if (clsId == SBIG('NONE'))\n    return {};\n  CAssetId id = in.Get<CAssetId>();\n  if (!id.IsValid())\n    return {};\n  return resPool->GetObj({FOURCC('CMDL'), id});\n}\n\nSChildGeneratorDesc CParticleDataFactory::GetChildGeneratorDesc(CAssetId res, CSimplePool* resPool,\n                                                                const std::vector<CAssetId>& tracker) {\n  if (std::count(tracker.cbegin(), tracker.cend(), res) == 0)\n    return resPool->GetObj({FOURCC('PART'), res});\n  return {};\n}\n\nSChildGeneratorDesc CParticleDataFactory::GetChildGeneratorDesc(CInputStream& in, CSimplePool* resPool,\n                                                                const std::vector<CAssetId>& tracker) {\n  FourCC clsId = GetClassID(in);\n  if (clsId == SBIG('NONE'))\n    return {};\n  CAssetId id = in.Get<CAssetId>();\n  if (!id.IsValid())\n    return {};\n  return GetChildGeneratorDesc(id, resPool, tracker);\n}\n\nSSwooshGeneratorDesc CParticleDataFactory::GetSwooshGeneratorDesc(CInputStream& in, CSimplePool* resPool) {\n  FourCC clsId = GetClassID(in);\n  if (clsId == SBIG('NONE'))\n    return {};\n  CAssetId id = in.Get<CAssetId>();\n  if (!id.IsValid())\n    return {};\n  return resPool->GetObj({FOURCC('SWHC'), id});\n}\n\nSElectricGeneratorDesc CParticleDataFactory::GetElectricGeneratorDesc(CInputStream& in, CSimplePool* resPool) {\n  FourCC clsId = GetClassID(in);\n  if (clsId == SBIG('NONE'))\n    return {};\n  CAssetId id = in.Get<CAssetId>();\n  if (!id.IsValid())\n    return {};\n  return resPool->GetObj({FOURCC('ELSC'), id});\n}\n\nstatic std::unique_ptr<CTexture> CreateTexture(u32 value) {\n  auto tex = std::make_unique<CTexture>(ETexelFormat::RGBA8, 4, 4, 1, \"CUVElement Fallback Texture\"sv);\n  auto* data = reinterpret_cast<u32*>(tex->Lock());\n  for (int i = 0; i < 4 * 4; ++i) {\n    data[i] = value;\n  }\n  tex->UnLock();\n  return tex;\n}\n\nstd::unique_ptr<CUVElement> CParticleDataFactory::GetTextureElement(CInputStream& in, CSimplePool* resPool) {\n  FourCC clsId = GetClassID(in);\n  switch (clsId.toUint32()) {\n  case SBIG('CNST'): {\n    CAssetId id;\n    const auto subId = GetClassID(in);\n    if (subId != SBIG('NONE')) {\n      id = in.Get<CAssetId>();\n    }\n    TToken<CTexture> txtr;\n    if (id.IsValid()) {\n      txtr = resPool->GetObj({FOURCC('TXTR'), id});\n    } else {\n      txtr = CreateTexture(0xFFFFFFFF);\n    }\n    return std::make_unique<CUVEConstant>(std::move(txtr));\n  }\n  case SBIG('ATEX'): {\n    CAssetId id;\n    const auto subId = GetClassID(in);\n    if (subId != SBIG('NONE')) {\n      id = in.Get<CAssetId>();\n    }\n    auto a = GetIntElement(in);\n    auto b = GetIntElement(in);\n    auto c = GetIntElement(in);\n    auto d = GetIntElement(in);\n    auto e = GetIntElement(in);\n    const bool f = GetBool(in);\n    TToken<CTexture> txtr;\n    if (id.IsValid()) {\n      txtr = resPool->GetObj({FOURCC('TXTR'), id});\n    } else {\n      txtr = CreateTexture(0xFFFFFFFF);\n    }\n    return std::make_unique<CUVEAnimTexture>(std::move(txtr), std::move(std::move(a)), std::move(b), std::move(c),\n                                             std::move(d), std::move(e), f);\n  }\n  default:\n    break;\n  }\n  return nullptr;\n}\n\nstd::unique_ptr<CColorElement> CParticleDataFactory::GetColorElement(CInputStream& in) {\n  FourCC clsId = GetClassID(in);\n  switch (clsId.toUint32()) {\n  case SBIG('KEYE'):\n  case SBIG('KEYP'): {\n    return std::make_unique<CCEKeyframeEmitter>(in);\n  }\n  case SBIG('CNST'): {\n    auto a = GetRealElement(in);\n    auto b = GetRealElement(in);\n    auto c = GetRealElement(in);\n    auto d = GetRealElement(in);\n    if (a->IsConstant() && b->IsConstant() && c->IsConstant() && d->IsConstant()) {\n      float af, bf, cf, df;\n      a->GetValue(0, af);\n      b->GetValue(0, bf);\n      c->GetValue(0, cf);\n      d->GetValue(0, df);\n      return std::make_unique<CCEFastConstant>(af, bf, cf, df);\n    } else {\n      return std::make_unique<CCEConstant>(std::move(std::move(a)), std::move(b), std::move(c), std::move(d));\n    }\n  }\n  case SBIG('CHAN'): {\n    auto a = GetColorElement(in);\n    auto b = GetColorElement(in);\n    auto c = GetIntElement(in);\n    return std::make_unique<CCETimeChain>(std::move(std::move(a)), std::move(b), std::move(c));\n  }\n  case SBIG('CFDE'): {\n    auto a = GetColorElement(in);\n    auto b = GetColorElement(in);\n    auto c = GetRealElement(in);\n    auto d = GetRealElement(in);\n    return std::make_unique<CCEFadeEnd>(std::move(std::move(a)), std::move(b), std::move(c), std::move(d));\n  }\n  case SBIG('FADE'): {\n    auto a = GetColorElement(in);\n    auto b = GetColorElement(in);\n    auto c = GetRealElement(in);\n    return std::make_unique<CCEFade>(std::move(std::move(a)), std::move(b), std::move(c));\n  }\n  case SBIG('PULS'): {\n    auto a = GetIntElement(in);\n    auto b = GetIntElement(in);\n    auto c = GetColorElement(in);\n    auto d = GetColorElement(in);\n    return std::make_unique<CCEPulse>(std::move(std::move(a)), std::move(b), std::move(c), std::move(d));\n  }\n  case SBIG('PCOL'): {\n    return std::make_unique<CCEParticleColor>();\n  }\n  default:\n    break;\n  }\n  return nullptr;\n}\n\nstd::unique_ptr<CModVectorElement> CParticleDataFactory::GetModVectorElement(CInputStream& in) {\n  FourCC clsId = GetClassID(in);\n  switch (clsId.toUint32()) {\n  case SBIG('IMPL'): {\n    auto a = GetVectorElement(in);\n    auto b = GetRealElement(in);\n    auto c = GetRealElement(in);\n    auto d = GetRealElement(in);\n    const bool e = GetBool(in);\n    return std::make_unique<CMVEImplosion>(std::move(a), std::move(b), std::move(c), std::move(d), e);\n  }\n  case SBIG('EMPL'): {\n    auto a = GetVectorElement(in);\n    auto b = GetRealElement(in);\n    auto c = GetRealElement(in);\n    auto d = GetRealElement(in);\n    const bool e = GetBool(in);\n    return std::make_unique<CMVEExponentialImplosion>(std::move(a), std::move(b), std::move(c), std::move(d), e);\n  }\n  case SBIG('CHAN'): {\n    auto a = GetModVectorElement(in);\n    auto b = GetModVectorElement(in);\n    auto c = GetIntElement(in);\n    return std::make_unique<CMVETimeChain>(std::move(a), std::move(b), std::move(c));\n  }\n  case SBIG('BNCE'): {\n    auto a = GetVectorElement(in);\n    auto b = GetVectorElement(in);\n    auto c = GetRealElement(in);\n    auto d = GetRealElement(in);\n    const bool e = GetBool(in);\n    return std::make_unique<CMVEBounce>(std::move(a), std::move(b), std::move(c), std::move(d), e);\n  }\n  case SBIG('CNST'): {\n    auto a = GetRealElement(in);\n    auto b = GetRealElement(in);\n    auto c = GetRealElement(in);\n    if (a->IsConstant() && b->IsConstant() && c->IsConstant()) {\n      float af, bf, cf;\n      a->GetValue(0, af);\n      b->GetValue(0, bf);\n      c->GetValue(0, cf);\n      // NOTE: 0-00 bug uses the value of a for each element\n      // Other versions unknown\n      return std::make_unique<CMVEFastConstant>(af, bf, cf);\n    } else {\n      return std::make_unique<CMVEConstant>(std::move(a), std::move(b), std::move(c));\n    }\n  }\n  case SBIG('GRAV'): {\n    auto a = GetVectorElement(in);\n    return std::make_unique<CMVEGravity>(std::move(a));\n  }\n  case SBIG('EXPL'): {\n    auto a = GetRealElement(in);\n    auto b = GetRealElement(in);\n    return std::make_unique<CMVEExplode>(std::move(a), std::move(b));\n  }\n  case SBIG('SPOS'): {\n    auto a = GetVectorElement(in);\n    return std::make_unique<CMVESetPosition>(std::move(a));\n  }\n  case SBIG('LMPL'): {\n    auto a = GetVectorElement(in);\n    auto b = GetRealElement(in);\n    auto c = GetRealElement(in);\n    auto d = GetRealElement(in);\n    const bool e = GetBool(in);\n    return std::make_unique<CMVELinearImplosion>(std::move(a), std::move(b), std::move(c), std::move(d), e);\n  }\n  case SBIG('PULS'): {\n    auto a = GetIntElement(in);\n    auto b = GetIntElement(in);\n    auto c = GetModVectorElement(in);\n    auto d = GetModVectorElement(in);\n    return std::make_unique<CMVEPulse>(std::move(a), std::move(b), std::move(c), std::move(d));\n  }\n  case SBIG('WIND'): {\n    auto a = GetVectorElement(in);\n    auto b = GetRealElement(in);\n    return std::make_unique<CMVEWind>(std::move(a), std::move(b));\n  }\n  case SBIG('SWRL'): {\n    auto a = GetVectorElement(in);\n    auto b = GetVectorElement(in);\n    auto c = GetRealElement(in);\n    auto d = GetRealElement(in);\n    return std::make_unique<CMVESwirl>(std::move(a), std::move(b), std::move(c), std::move(d));\n  }\n  default:\n    break;\n  }\n  return nullptr;\n}\n\nstd::unique_ptr<CEmitterElement> CParticleDataFactory::GetEmitterElement(CInputStream& in) {\n  FourCC clsId = GetClassID(in);\n  switch (clsId.toUint32()) {\n  case SBIG('SETR'): {\n    FourCC prop = GetClassID(in);\n    if (prop == SBIG('ILOC')) {\n      auto a = GetVectorElement(in);\n      prop = GetClassID(in);\n      if (prop == SBIG('IVEC')) {\n        auto b = GetVectorElement(in);\n        return std::make_unique<CEESimpleEmitter>(std::move(a), std::move(b));\n      }\n    }\n    return nullptr;\n  }\n  case SBIG('SEMR'): {\n    auto a = GetVectorElement(in);\n    auto b = GetVectorElement(in);\n    return std::make_unique<CEESimpleEmitter>(std::move(a), std::move(b));\n  }\n  case SBIG('SPHE'): {\n    auto a = GetVectorElement(in);\n    auto b = GetRealElement(in);\n    auto c = GetRealElement(in);\n    return std::make_unique<CVESphere>(std::move(a), std::move(b), std::move(c));\n  }\n  case SBIG('ASPH'): {\n    auto a = GetVectorElement(in);\n    auto b = GetRealElement(in);\n    auto c = GetRealElement(in);\n    auto d = GetRealElement(in);\n    auto e = GetRealElement(in);\n    auto f = GetRealElement(in);\n    auto g = GetRealElement(in);\n    return std::make_unique<CVEAngleSphere>(std::move(a), std::move(f), std::move(g), std::move(b), std::move(c),\n                                            std::move(d), std::move(e));\n  }\n  default:\n    break;\n  }\n  return nullptr;\n}\n\nstd::unique_ptr<CVectorElement> CParticleDataFactory::GetVectorElement(CInputStream& in) {\n  FourCC clsId = GetClassID(in);\n  switch (clsId.toUint32()) {\n  case SBIG('CONE'): {\n    auto a = GetVectorElement(in);\n    auto b = GetRealElement(in);\n    return std::make_unique<CVECone>(std::move(a), std::move(b));\n  }\n  case SBIG('CHAN'): {\n    auto a = GetVectorElement(in);\n    auto b = GetVectorElement(in);\n    auto c = GetIntElement(in);\n    return std::make_unique<CVETimeChain>(std::move(a), std::move(b), std::move(c));\n  }\n  case SBIG('ANGC'): {\n    auto a = GetRealElement(in);\n    auto b = GetRealElement(in);\n    auto c = GetRealElement(in);\n    auto d = GetRealElement(in);\n    auto e = GetRealElement(in);\n    return std::make_unique<CVEAngleCone>(std::move(a), std::move(b), std::move(c), std::move(d), std::move(e));\n  }\n  case SBIG('ADD_'): {\n    auto a = GetVectorElement(in);\n    auto b = GetVectorElement(in);\n    return std::make_unique<CVEAdd>(std::move(a), std::move(b));\n  }\n  case SBIG('CCLU'): {\n    auto a = GetVectorElement(in);\n    auto b = GetVectorElement(in);\n    auto c = GetIntElement(in);\n    auto d = GetRealElement(in);\n    return std::make_unique<CVECircleCluster>(std::move(a), std::move(b), std::move(c), std::move(d));\n  }\n  case SBIG('CNST'): {\n    auto a = GetRealElement(in);\n    auto b = GetRealElement(in);\n    auto c = GetRealElement(in);\n    if (a->IsConstant() && b->IsConstant() && c->IsConstant()) {\n      float af, bf, cf;\n      a->GetValue(0, af);\n      b->GetValue(0, bf);\n      c->GetValue(0, cf);\n      return std::make_unique<CVEFastConstant>(af, bf, cf);\n    } else {\n      return std::make_unique<CVEConstant>(std::move(a), std::move(b), std::move(c));\n    }\n  }\n  case SBIG('CIRC'): {\n    auto a = GetVectorElement(in);\n    auto b = GetVectorElement(in);\n    auto c = GetRealElement(in);\n    auto d = GetRealElement(in);\n    auto e = GetRealElement(in);\n    return std::make_unique<CVECircle>(std::move(a), std::move(b), std::move(c), std::move(d), std::move(e));\n  }\n  case SBIG('KEYE'):\n  case SBIG('KEYP'): {\n    return std::make_unique<CVEKeyframeEmitter>(in);\n  }\n  case SBIG('MULT'): {\n    auto a = GetVectorElement(in);\n    auto b = GetVectorElement(in);\n    return std::make_unique<CVEMultiply>(std::move(a), std::move(b));\n  }\n  case SBIG('RTOV'): {\n    auto a = GetRealElement(in);\n    return std::make_unique<CVERealToVector>(std::move(a));\n  }\n  case SBIG('PULS'): {\n    auto a = GetIntElement(in);\n    auto b = GetIntElement(in);\n    auto c = GetVectorElement(in);\n    auto d = GetVectorElement(in);\n    return std::make_unique<CVEPulse>(std::move(a), std::move(b), std::move(c), std::move(d));\n  }\n  case SBIG('PVEL'): {\n    return std::make_unique<CVEParticleVelocity>();\n  }\n  case SBIG('PLCO'): {\n    return std::make_unique<CVEParticlePreviousLocation>();\n  }\n  case SBIG('PLOC'): {\n    return std::make_unique<CVEParticleLocation>();\n  }\n  case SBIG('PSOF'): {\n    return std::make_unique<CVEParticleSystemOrientationFront>();\n  }\n  case SBIG('PSOU'): {\n    return std::make_unique<CVEParticleSystemOrientationUp>();\n  }\n  case SBIG('PSOR'): {\n    return std::make_unique<CVEParticleSystemOrientationRight>();\n  }\n  case SBIG('PSTR'): {\n    return std::make_unique<CVEParticleSystemTranslation>();\n  }\n  case SBIG('SUB_'): {\n    auto a = GetVectorElement(in);\n    auto b = GetVectorElement(in);\n    return std::make_unique<CVESubtract>(std::move(a), std::move(b));\n  }\n  case SBIG('CTVC'): {\n    auto a = GetColorElement(in);\n    return std::make_unique<CVEColorToVector>(std::move(a));\n  }\n  default:\n    break;\n  }\n  return nullptr;\n}\n\nstd::unique_ptr<CRealElement> CParticleDataFactory::GetRealElement(CInputStream& in) {\n  FourCC clsId = GetClassID(in);\n  switch (clsId.toUint32()) {\n  case SBIG('LFTW'): {\n    auto a = GetRealElement(in);\n    auto b = GetRealElement(in);\n    return std::make_unique<CRELifetimeTween>(std::move(a), std::move(b));\n  }\n  case SBIG('CNST'): {\n    const float a = GetReal(in);\n    return std::make_unique<CREConstant>(a);\n  }\n  case SBIG('CHAN'): {\n    auto a = GetRealElement(in);\n    auto b = GetRealElement(in);\n    auto c = GetIntElement(in);\n    return std::make_unique<CRETimeChain>(std::move(a), std::move(b), std::move(c));\n  }\n  case SBIG('ADD_'): {\n    auto a = GetRealElement(in);\n    auto b = GetRealElement(in);\n    return std::make_unique<CREAdd>(std::move(a), std::move(b));\n  }\n  case SBIG('CLMP'): {\n    auto a = GetRealElement(in);\n    auto b = GetRealElement(in);\n    auto c = GetRealElement(in);\n    return std::make_unique<CREClamp>(std::move(a), std::move(b), std::move(c));\n  }\n  case SBIG('KEYE'):\n  case SBIG('KEYP'): {\n    return std::make_unique<CREKeyframeEmitter>(in);\n  }\n  case SBIG('IRND'): {\n    auto a = GetRealElement(in);\n    auto b = GetRealElement(in);\n    return std::make_unique<CREInitialRandom>(std::move(a), std::move(b));\n  }\n  case SBIG('RAND'): {\n    auto a = GetRealElement(in);\n    auto b = GetRealElement(in);\n    return std::make_unique<CRERandom>(std::move(a), std::move(b));\n  }\n  case SBIG('DOTP'): {\n    auto a = GetVectorElement(in);\n    auto b = GetVectorElement(in);\n    return std::make_unique<CREDotProduct>(std::move(a), std::move(b));\n  }\n  case SBIG('MULT'): {\n    auto a = GetRealElement(in);\n    auto b = GetRealElement(in);\n    return std::make_unique<CREMultiply>(std::move(a), std::move(b));\n  }\n  case SBIG('PULS'): {\n    auto a = GetIntElement(in);\n    auto b = GetIntElement(in);\n    auto c = GetRealElement(in);\n    auto d = GetRealElement(in);\n    return std::make_unique<CREPulse>(std::move(a), std::move(b), std::move(c), std::move(d));\n  }\n  case SBIG('SCAL'): {\n    auto a = GetRealElement(in);\n    return std::make_unique<CRETimeScale>(std::move(a));\n  }\n  case SBIG('RLPT'): {\n    auto a = GetRealElement(in);\n    return std::make_unique<CRELifetimePercent>(std::move(a));\n  }\n  case SBIG('SINE'): {\n    auto frequency = GetRealElement(in);\n    auto amplitude = GetRealElement(in);\n    auto phase = GetRealElement(in);\n    return std::make_unique<CRESineWave>(std::move(phase), std::move(frequency), std::move(amplitude));\n  }\n  case SBIG('ISWT'): {\n    auto a = GetRealElement(in);\n    auto b = GetRealElement(in);\n    return std::make_unique<CREInitialSwitch>(std::move(a), std::move(b));\n  }\n  case SBIG('CLTN'): {\n    auto a = GetRealElement(in);\n    auto b = GetRealElement(in);\n    auto c = GetRealElement(in);\n    auto d = GetRealElement(in);\n    return std::make_unique<CRECompareLessThan>(std::move(a), std::move(b), std::move(c), std::move(d));\n  }\n  case SBIG('CEQL'): {\n    auto a = GetRealElement(in);\n    auto b = GetRealElement(in);\n    auto c = GetRealElement(in);\n    auto d = GetRealElement(in);\n    return std::make_unique<CRECompareEquals>(std::move(a), std::move(b), std::move(c), std::move(d));\n  }\n  case SBIG('PAP1'): {\n    return std::make_unique<CREParticleAccessParam1>();\n  }\n  case SBIG('PAP2'): {\n    return std::make_unique<CREParticleAccessParam2>();\n  }\n  case SBIG('PAP3'): {\n    return std::make_unique<CREParticleAccessParam3>();\n  }\n  case SBIG('PAP4'): {\n    return std::make_unique<CREParticleAccessParam4>();\n  }\n  case SBIG('PAP5'): {\n    return std::make_unique<CREParticleAccessParam5>();\n  }\n  case SBIG('PAP6'): {\n    return std::make_unique<CREParticleAccessParam6>();\n  }\n  case SBIG('PAP7'): {\n    return std::make_unique<CREParticleAccessParam7>();\n  }\n  case SBIG('PAP8'): {\n    return std::make_unique<CREParticleAccessParam8>();\n  }\n  case SBIG('PSLL'): {\n    return std::make_unique<CREParticleSizeOrLineLength>();\n  }\n  case SBIG('PRLW'): {\n    return std::make_unique<CREParticleRotationOrLineWidth>();\n  }\n  case SBIG('SUB_'): {\n    auto a = GetRealElement(in);\n    auto b = GetRealElement(in);\n    return std::make_unique<CRESubtract>(std::move(a), std::move(b));\n  }\n  case SBIG('VMAG'): {\n    auto a = GetVectorElement(in);\n    return std::make_unique<CREVectorMagnitude>(std::move(a));\n  }\n  case SBIG('VXTR'): {\n    auto a = GetVectorElement(in);\n    return std::make_unique<CREVectorXToReal>(std::move(a));\n  }\n  case SBIG('VYTR'): {\n    auto a = GetVectorElement(in);\n    return std::make_unique<CREVectorYToReal>(std::move(a));\n  }\n  case SBIG('VZTR'): {\n    auto a = GetVectorElement(in);\n    return std::make_unique<CREVectorZToReal>(std::move(a));\n  }\n  case SBIG('CEXT'): {\n    auto a = GetIntElement(in);\n    return std::make_unique<CREExternalVar>(std::move(a));\n  }\n  case SBIG('ITRL'): {\n    auto a = GetIntElement(in);\n    auto b = GetRealElement(in);\n    return std::make_unique<CREIntTimesReal>(std::move(a), std::move(b));\n  }\n  case SBIG('CRNG'): {\n    auto a = GetRealElement(in);\n    auto b = GetRealElement(in);\n    auto c = GetRealElement(in);\n    auto d = GetRealElement(in);\n    auto e = GetRealElement(in);\n    return std::make_unique<CREConstantRange>(std::move(a), std::move(b), std::move(c), std::move(d), std::move(e));\n  }\n  case SBIG('GTCR'): {\n    auto a = GetColorElement(in);\n    return std::make_unique<CREGetComponentRed>(std::move(a));\n  }\n  case SBIG('GTCG'): {\n    auto a = GetColorElement(in);\n    return std::make_unique<CREGetComponentGreen>(std::move(a));\n  }\n  case SBIG('GTCB'): {\n    auto a = GetColorElement(in);\n    return std::make_unique<CREGetComponentBlue>(std::move(a));\n  }\n  case SBIG('GTCA'): {\n    auto a = GetColorElement(in);\n    return std::make_unique<CREGetComponentAlpha>(std::move(a));\n  }\n  default:\n    break;\n  }\n  return nullptr;\n}\n\nstd::unique_ptr<CIntElement> CParticleDataFactory::GetIntElement(CInputStream& in) {\n  FourCC clsId = GetClassID(in);\n  switch (clsId.toUint32()) {\n  case SBIG('KEYE'):\n  case SBIG('KEYP'): {\n    return std::make_unique<CIEKeyframeEmitter>(in);\n  }\n  case SBIG('DETH'): {\n    auto a = GetIntElement(in);\n    auto b = GetIntElement(in);\n    return std::make_unique<CIEDeath>(std::move(a), std::move(b));\n  }\n  case SBIG('CLMP'): {\n    auto a = GetIntElement(in);\n    auto b = GetIntElement(in);\n    auto c = GetIntElement(in);\n    return std::make_unique<CIEClamp>(std::move(a), std::move(b), std::move(c));\n  }\n  case SBIG('CHAN'): {\n    auto a = GetIntElement(in);\n    auto b = GetIntElement(in);\n    auto c = GetIntElement(in);\n    return std::make_unique<CIETimeChain>(std::move(a), std::move(b), std::move(c));\n  }\n  case SBIG('ADD_'): {\n    auto a = GetIntElement(in);\n    auto b = GetIntElement(in);\n    return std::make_unique<CIEAdd>(std::move(a), std::move(b));\n  }\n  case SBIG('CNST'): {\n    const int a = GetInt(in);\n    return std::make_unique<CIEConstant>(a);\n  }\n  case SBIG('IMPL'): {\n    auto a = GetIntElement(in);\n    return std::make_unique<CIEImpulse>(std::move(a));\n  }\n  case SBIG('ILPT'): {\n    auto a = GetIntElement(in);\n    return std::make_unique<CIELifetimePercent>(std::move(a));\n  }\n  case SBIG('IRND'): {\n    auto a = GetIntElement(in);\n    auto b = GetIntElement(in);\n    return std::make_unique<CIEInitialRandom>(std::move(a), std::move(b));\n  }\n  case SBIG('PULS'): {\n    auto a = GetIntElement(in);\n    auto b = GetIntElement(in);\n    auto c = GetIntElement(in);\n    auto d = GetIntElement(in);\n    return std::make_unique<CIEPulse>(std::move(a), std::move(b), std::move(c), std::move(d));\n  }\n  case SBIG('MULT'): {\n    auto a = GetIntElement(in);\n    auto b = GetIntElement(in);\n    return std::make_unique<CIEMultiply>(std::move(a), std::move(b));\n  }\n  case SBIG('SPAH'): {\n    auto a = GetIntElement(in);\n    auto b = GetIntElement(in);\n    auto c = GetIntElement(in);\n    return std::make_unique<CIESampleAndHold>(std::move(c), std::move(a), std::move(b));\n  }\n  case SBIG('RAND'): {\n    auto a = GetIntElement(in);\n    auto b = GetIntElement(in);\n    return std::make_unique<CIERandom>(std::move(a), std::move(b));\n  }\n  case SBIG('RTOI'): {\n    auto a = GetRealElement(in);\n    auto b = GetRealElement(in);\n    return std::make_unique<CIERealToInt>(std::move(a), std::move(b));\n  }\n  case SBIG('TSCL'): {\n    auto a = GetRealElement(in);\n    return std::make_unique<CIETimeScale>(std::move(a));\n  }\n  case SBIG('GAPC'): {\n    return std::make_unique<CIEGetActiveParticleCount>();\n  }\n  case SBIG('GTCP'): {\n    return std::make_unique<CIEGetCumulativeParticleCount>();\n  }\n  case SBIG('GEMT'): {\n    return std::make_unique<CIEGetEmitterTime>();\n  }\n  case SBIG('MODU'): {\n    auto a = GetIntElement(in);\n    auto b = GetIntElement(in);\n    return std::make_unique<CIEModulo>(std::move(a), std::move(b));\n  }\n  case SBIG('SUB_'): {\n    auto a = GetIntElement(in);\n    auto b = GetIntElement(in);\n    return std::make_unique<CIESubtract>(std::move(a), std::move(b));\n  }\n  default:\n    break;\n  }\n  return nullptr;\n}\n\nstd::unique_ptr<CGenDescription> CParticleDataFactory::GetGeneratorDesc(CInputStream& in, CSimplePool* resPool) {\n  std::vector<CAssetId> tracker;\n  tracker.reserve(8);\n  return CreateGeneratorDescription(in, tracker, {}, resPool);\n}\n\nstd::unique_ptr<CGenDescription> CParticleDataFactory::CreateGeneratorDescription(CInputStream& in,\n                                                                                  std::vector<CAssetId>& tracker,\n                                                                                  CAssetId resId,\n                                                                                  CSimplePool* resPool) {\n  if (std::count(tracker.cbegin(), tracker.cend(), resId) == 0) {\n    tracker.push_back(resId);\n    FourCC cid = GetClassID(in);\n    if (cid == FOURCC('GPSM')) {\n      auto ret = std::make_unique<CGenDescription>();\n      CreateGPSM(ret.get(), in, tracker, resPool);\n      LoadGPSMTokens(ret.get());\n      return ret;\n    }\n  }\n  return nullptr;\n}\n\nbool CParticleDataFactory::CreateGPSM(CGenDescription* fillDesc, CInputStream& in, std::vector<CAssetId>& tracker,\n                                      CSimplePool* resPool) {\n  CRandom16 rand;\n  CGlobalRandom gr(rand);\n  FourCC clsId = GetClassID(in);\n  while (clsId != SBIG('_END')) {\n    switch (clsId.toUint32()) {\n    case SBIG('PMCL'):\n      fillDesc->x78_x64_PMCL = GetColorElement(in);\n      break;\n    case SBIG('LFOR'):\n      fillDesc->x118_x104_LFOR = GetRealElement(in);\n      break;\n    case SBIG('IDTS'):\n      fillDesc->xa4_x90_IDTS = GetChildGeneratorDesc(in, resPool, tracker);\n      break;\n    case SBIG('EMTR'):\n      fillDesc->x40_x2c_EMTR = GetEmitterElement(in);\n      break;\n    case SBIG('COLR'):\n      fillDesc->x30_x24_COLR = GetColorElement(in);\n      break;\n    case SBIG('CIND'):\n      fillDesc->x45_30_x32_24_CIND = GetBool(in);\n      break;\n    case SBIG('AAPH'):\n      fillDesc->x44_26_x30_26_AAPH = GetBool(in);\n      break;\n    case SBIG('CSSD'):\n      fillDesc->xa0_x8c_CSSD = GetIntElement(in);\n      break;\n    case SBIG('GRTE'):\n      fillDesc->x2c_x20_GRTE = GetRealElement(in);\n      break;\n    case SBIG('FXLL'):\n      fillDesc->x44_25_x30_25_FXLL = GetBool(in);\n      break;\n    case SBIG('ICTS'):\n      fillDesc->x8c_x78_ICTS = GetChildGeneratorDesc(in, resPool, tracker);\n      break;\n    case SBIG('KSSM'): {\n      fillDesc->xd0_xbc_KSSM.reset();\n      FourCC cid = GetClassID(in);\n      if (cid != SBIG('CNST'))\n        break;\n      fillDesc->xd0_xbc_KSSM = std::make_unique<CSpawnSystemKeyframeData>(in);\n      fillDesc->xd0_xbc_KSSM->LoadAllSpawnedSystemTokens(resPool);\n      break;\n    }\n    case SBIG('ILOC'):\n      GetVectorElement(in);\n      break;\n    case SBIG('IITS'):\n      fillDesc->xb8_xa4_IITS = GetChildGeneratorDesc(in, resPool, tracker);\n      break;\n    case SBIG('IVEC'):\n      GetVectorElement(in);\n      break;\n    case SBIG('LDIR'):\n      fillDesc->x110_xfc_LDIR = GetVectorElement(in);\n      break;\n    case SBIG('LCLR'):\n      fillDesc->x104_xf0_LCLR = GetColorElement(in);\n      break;\n    case SBIG('LENG'):\n      fillDesc->x20_x14_LENG = GetRealElement(in);\n      break;\n    case SBIG('MAXP'):\n      fillDesc->x28_x1c_MAXP = GetIntElement(in);\n      break;\n    case SBIG('LOFF'):\n      fillDesc->x10c_xf8_LOFF = GetVectorElement(in);\n      break;\n    case SBIG('LINT'):\n      fillDesc->x108_xf4_LINT = GetRealElement(in);\n      break;\n    case SBIG('LINE'):\n      fillDesc->x44_24_x30_24_LINE = GetBool(in);\n      break;\n    case SBIG('LFOT'):\n      fillDesc->x114_x100_LFOT = GetIntElement(in);\n      break;\n    case SBIG('LIT_'):\n      fillDesc->x44_29_x30_29_LIT_ = GetBool(in);\n      break;\n    case SBIG('LTME'):\n      fillDesc->x34_x28_LTME = GetIntElement(in);\n      break;\n    case SBIG('LSLA'):\n      fillDesc->x11c_x108_LSLA = GetRealElement(in);\n      break;\n    case SBIG('LTYP'):\n      fillDesc->x100_xec_LTYP = GetIntElement(in);\n      break;\n    case SBIG('NDSY'):\n      fillDesc->xb4_xa0_NDSY = GetIntElement(in);\n      break;\n    case SBIG('MBSP'):\n      fillDesc->x48_x34_MBSP = GetIntElement(in);\n      break;\n    case SBIG('MBLR'):\n      fillDesc->x44_30_x31_24_MBLR = GetBool(in);\n      break;\n    case SBIG('NCSY'):\n      fillDesc->x9c_x88_NCSY = GetIntElement(in);\n      break;\n    case SBIG('PISY'):\n      fillDesc->xc8_xb4_PISY = GetIntElement(in);\n      break;\n    case SBIG('OPTS'):\n      fillDesc->x45_31_x32_25_OPTS = GetBool(in);\n      break;\n    case SBIG('PMAB'):\n      fillDesc->x44_31_x31_25_PMAB = GetBool(in);\n      break;\n    case SBIG('SESD'):\n      fillDesc->xf8_xe4_SESD = GetIntElement(in);\n      break;\n    case SBIG('SEPO'):\n      fillDesc->xfc_xe8_SEPO = GetVectorElement(in);\n      break;\n    case SBIG('PSLT'):\n      fillDesc->xc_x0_PSLT = GetIntElement(in);\n      break;\n    case SBIG('PMSC'):\n      fillDesc->x74_x60_PMSC = GetVectorElement(in);\n      break;\n    case SBIG('PMOP'):\n      fillDesc->x6c_x58_PMOP = GetVectorElement(in);\n      break;\n    case SBIG('PMDL'):\n      fillDesc->x5c_x48_PMDL = GetModel(in, resPool);\n      break;\n    case SBIG('PMRT'):\n      fillDesc->x70_x5c_PMRT = GetVectorElement(in);\n      break;\n    case SBIG('POFS'):\n      fillDesc->x18_xc_POFS = GetVectorElement(in);\n      break;\n    case SBIG('PMUS'):\n      fillDesc->x45_24_x31_26_PMUS = GetBool(in);\n      break;\n    case SBIG('PSIV'):\n      GetVectorElement(in);\n      break;\n    case SBIG('ROTA'):\n      fillDesc->x50_x3c_ROTA = GetRealElement(in);\n      break;\n    case SBIG('PSVM'):\n      GetModVectorElement(in);\n      break;\n    case SBIG('PSTS'):\n      fillDesc->x14_x8_PSTS = GetRealElement(in);\n      break;\n    case SBIG('PSOV'):\n      GetVectorElement(in);\n      break;\n    case SBIG('PSWT'):\n      fillDesc->x10_x4_PSWT = GetIntElement(in);\n      break;\n    case SBIG('SEED'):\n      fillDesc->x1c_x10_SEED = GetIntElement(in);\n      break;\n    case SBIG('PMOO'):\n      fillDesc->x45_25_x31_27_PMOO = GetBool(in);\n      break;\n    case SBIG('SSSD'):\n      fillDesc->xe4_xd0_SSSD = GetIntElement(in);\n      break;\n    case SBIG('SORT'):\n      fillDesc->x44_28_x30_28_SORT = GetBool(in);\n      break;\n    case SBIG('SIZE'):\n      fillDesc->x4c_x38_SIZE = GetRealElement(in);\n      break;\n    case SBIG('SISY'):\n      fillDesc->xcc_xb8_SISY = GetIntElement(in);\n      break;\n    case SBIG('SSPO'):\n      fillDesc->xe8_xd4_SSPO = GetVectorElement(in);\n      break;\n    case SBIG('TEXR'):\n      fillDesc->x54_x40_TEXR = GetTextureElement(in, resPool);\n      break;\n    case SBIG('SSWH'):\n      fillDesc->xd4_xc0_SSWH = GetSwooshGeneratorDesc(in, resPool);\n      break;\n    case SBIG('TIND'):\n      fillDesc->x58_x44_TIND = GetTextureElement(in, resPool);\n      break;\n    case SBIG('VMD4'):\n      fillDesc->x45_29_x31_31_VMD4 = GetBool(in);\n      break;\n    case SBIG('VMD3'):\n      fillDesc->x45_28_x31_30_VMD3 = GetBool(in);\n      break;\n    case SBIG('VMD2'):\n      fillDesc->x45_27_x31_29_VMD2 = GetBool(in);\n      break;\n    case SBIG('VMD1'):\n      fillDesc->x45_26_x31_28_VMD1 = GetBool(in);\n      break;\n    case SBIG('VEL4'):\n      fillDesc->x88_x74_VEL4 = GetModVectorElement(in);\n      break;\n    case SBIG('VEL3'):\n      fillDesc->x84_x70_VEL3 = GetModVectorElement(in);\n      break;\n    case SBIG('VEL2'):\n      fillDesc->x80_x6c_VEL2 = GetModVectorElement(in);\n      break;\n    case SBIG('VEL1'):\n      fillDesc->x7c_x68_VEL1 = GetModVectorElement(in);\n      break;\n    case SBIG('ZBUF'):\n      fillDesc->x44_27_x30_27_ZBUF = GetBool(in);\n      break;\n    case SBIG('WIDT'):\n      fillDesc->x24_x18_WIDT = GetRealElement(in);\n      break;\n    case SBIG('ORNT'):\n      fillDesc->x30_30_ORNT = GetBool(in);\n      break;\n    case SBIG('RSOP'):\n      fillDesc->x30_31_RSOP = GetBool(in);\n      break;\n    case SBIG('ADV1'):\n      fillDesc->x10c_ADV1 = GetRealElement(in);\n      break;\n    case SBIG('ADV2'):\n      fillDesc->x110_ADV2 = GetRealElement(in);\n      break;\n    case SBIG('ADV3'):\n      fillDesc->x114_ADV3 = GetRealElement(in);\n      break;\n    case SBIG('ADV4'):\n      fillDesc->x118_ADV4 = GetRealElement(in);\n      break;\n    case SBIG('ADV5'):\n      fillDesc->x11c_ADV5 = GetRealElement(in);\n      break;\n    case SBIG('ADV6'):\n      fillDesc->x120_ADV6 = GetRealElement(in);\n      break;\n    case SBIG('ADV7'):\n      fillDesc->x124_ADV7 = GetRealElement(in);\n      break;\n    case SBIG('ADV8'):\n      fillDesc->x128_ADV8 = GetRealElement(in);\n      break;\n    case SBIG('SELC'):\n      fillDesc->xec_xd8_SELC = GetElectricGeneratorDesc(in, resPool);\n      break;\n    default: {\n      spdlog::fatal(\"Unknown GPSM class {} @{}\", clsId, in.GetReadPosition());\n      return false;\n    }\n    }\n    clsId = GetClassID(in);\n  }\n#if 0\n  /* Now for our custom additions, if available */\n  if (!in.atEnd() && (in.position() + 4) < in.length()) {\n    clsId = GetClassID(in);\n    if (clsId == 0xFFFFFFFF)\n      return true;\n\n    while (clsId != SBIG('_END') && !in.atEnd()) {\n      switch (clsId.toUint32()) {\n      case SBIG('BGCL'):\n        fillDesc->m_bevelGradient = GetColorElement(in);\n        break;\n      default:\n        break;\n      }\n      clsId = GetClassID(in);\n    }\n  }\n#endif\n\n  return true;\n}\n\nvoid CParticleDataFactory::LoadGPSMTokens(CGenDescription* desc) {\n  desc->x5c_x48_PMDL.Load();\n  desc->x8c_x78_ICTS.Load();\n  desc->xa4_x90_IDTS.Load();\n  desc->xb8_xa4_IITS.Load();\n  desc->xd4_xc0_SSWH.Load();\n}\n\nCFactoryFnReturn FParticleFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& vparms,\n                                  CObjectReference* selfRef) {\n  auto* const sp = vparms.GetOwnedObj<CSimplePool*>();\n  return TToken<CGenDescription>::GetIObjObjectFor(CParticleDataFactory::GetGeneratorDesc(in, sp));\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CParticleDataFactory.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <vector>\n\n#include \"Runtime/CFactoryMgr.hpp\"\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/IObj.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Graphics/CModel.hpp\"\n\nnamespace metaforce {\nclass CColorElement;\nclass CElectricDescription;\nclass CEmitterElement;\nclass CGenDescription;\nclass CIntElement;\nclass CModVectorElement;\nclass CRealElement;\nclass CSimplePool;\nclass CSwooshDescription;\nclass CUVElement;\nclass CVParamTransfer;\nclass CVectorElement;\n\ntemplate <typename T>\nstruct STokenDesc {\n  std::optional<TLockedToken<T>> x0_res;\n  STokenDesc() = default;\n  STokenDesc(CToken&& tok) : x0_res(std::move(tok)) {}\n  void Load() {\n    if (x0_res) {\n      x0_res->GetObj();\n    }\n  }\n  explicit operator bool() const { return x0_res.has_value(); }\n  auto* GetObj() { return x0_res ? x0_res->GetObj() : nullptr; }\n  const auto* GetObj() const { return x0_res ? x0_res->GetObj() : nullptr; }\n  auto& operator*() { return *x0_res; }\n  const auto& operator*() const { return *x0_res; }\n  auto& operator->() { return *x0_res; }\n  const auto& operator->() const { return *x0_res; }\n};\n\nusing SParticleModel = STokenDesc<CModel>;\nusing SChildGeneratorDesc = STokenDesc<CGenDescription>;\nusing SSwooshGeneratorDesc = STokenDesc<CSwooshDescription>;\nusing SElectricGeneratorDesc = STokenDesc<CElectricDescription>;\n\nclass CParticleDataFactory {\n  friend class CDecalDataFactory;\n  friend class CCollisionResponseData;\n  friend class CParticleElectricDataFactory;\n  friend class CParticleSwooshDataFactory;\n  friend class CProjectileWeaponDataFactory;\n\n  static SParticleModel GetModel(CInputStream& in, CSimplePool* resPool);\n  static SChildGeneratorDesc GetChildGeneratorDesc(CAssetId res, CSimplePool* resPool,\n                                                   const std::vector<CAssetId>& tracker);\n  static SChildGeneratorDesc GetChildGeneratorDesc(CInputStream& in, CSimplePool* resPool,\n                                                   const std::vector<CAssetId>& tracker);\n  static SSwooshGeneratorDesc GetSwooshGeneratorDesc(CInputStream& in, CSimplePool* resPool);\n  static SElectricGeneratorDesc GetElectricGeneratorDesc(CInputStream& in, CSimplePool* resPool);\n  static std::unique_ptr<CUVElement> GetTextureElement(CInputStream& in, CSimplePool* resPool);\n  static std::unique_ptr<CColorElement> GetColorElement(CInputStream& in);\n  static std::unique_ptr<CModVectorElement> GetModVectorElement(CInputStream& in);\n  static std::unique_ptr<CEmitterElement> GetEmitterElement(CInputStream& in);\n  static std::unique_ptr<CVectorElement> GetVectorElement(CInputStream& in);\n  static std::unique_ptr<CRealElement> GetRealElement(CInputStream& in);\n  static std::unique_ptr<CIntElement> GetIntElement(CInputStream& in);\n\n  static float GetReal(CInputStream& in);\n  static s32 GetInt(CInputStream& in);\n  static bool GetBool(CInputStream& in);\n  static FourCC GetClassID(CInputStream& in);\n  static std::unique_ptr<CGenDescription> CreateGeneratorDescription(CInputStream& in, std::vector<CAssetId>& tracker,\n                                                                     CAssetId resId, CSimplePool* resPool);\n  static bool CreateGPSM(CGenDescription* fillDesc, CInputStream& in, std::vector<CAssetId>& tracker,\n                         CSimplePool* resPool);\n  static void LoadGPSMTokens(CGenDescription* desc);\n\npublic:\n  static std::unique_ptr<CGenDescription> GetGeneratorDesc(CInputStream& in, CSimplePool* resPool);\n};\n\nCFactoryFnReturn FParticleFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& vparms,\n                                  CObjectReference* selfRef);\n\n} // namespace metaforce\n\n// FIXME hacky workaround for MSVC; these need to be complete types\n// but introduce circular dependencies if included at the start\n#include \"Runtime/Particle/CGenDescription.hpp\"\n#include \"Runtime/Particle/CSwooshDescription.hpp\"\n#include \"Runtime/Particle/CElectricDescription.hpp\"\n"
  },
  {
    "path": "Runtime/Particle/CParticleElectric.cpp",
    "content": "#include \"Runtime/Particle/CParticleElectric.hpp\"\n\n#include <algorithm>\n\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Graphics/CGraphics.hpp\"\n#include \"Runtime/Graphics/CModel.hpp\"\n#include \"Runtime/Particle/CElectricDescription.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/Particle/CGenDescription.hpp\"\n#include \"Runtime/Particle/CParticleGlobals.hpp\"\n#include \"Runtime/Particle/CParticleSwoosh.hpp\"\n#include \"Runtime/Particle/CSwooshDescription.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\n#include <zeus/CQuaternion.hpp>\n#include <zeus/CRelAngle.hpp>\n\nnamespace metaforce {\n\nu16 CParticleElectric::g_GlobalSeed = 99;\n\nCParticleElectric::CParticleElectric(const TToken<CElectricDescription>& token)\n: x1c_elecDesc(token), x14c_randState(g_GlobalSeed++) {\n  x1bc_allocated.resize(32);\n\n  CElectricDescription* desc = x1c_elecDesc.GetObj();\n\n  if (CIntElement* sseg = desc->x10_SSEG.get()) {\n    sseg->GetValue(x28_currentFrame, x150_SSEG);\n  }\n\n  if (CIntElement* scnt = desc->xc_SCNT.get()) {\n    scnt->GetValue(x28_currentFrame, x154_SCNT);\n  }\n\n  x154_SCNT = std::min(x154_SCNT, 32);\n\n  if (CIntElement* life = desc->x0_LIFE.get()) {\n    life->GetValue(0, x2c_LIFE);\n  } else {\n    x2c_LIFE = INT_MAX;\n  }\n\n  if (desc->x40_SSWH) {\n    x450_27_haveSSWH = true;\n    for (int i = 0; i < x154_SCNT; ++i) {\n      x1e0_swooshGenerators.emplace_back(std::make_unique<CParticleSwoosh>(*desc->x40_SSWH, x150_SSEG));\n      x1e0_swooshGenerators.back()->DoElectricWarmup();\n    }\n  }\n\n  ++x150_SSEG;\n  x420_calculatedVerts.resize(x150_SSEG);\n  x440_fractalOffsets.resize(x150_SSEG);\n  x430_fractalMags.resize(x150_SSEG);\n\n  if (desc->x50_GPSM) {\n    x450_25_haveGPSM = true;\n    x400_gpsmGenerators.reserve(x154_SCNT);\n    for (int i = 0; i < x154_SCNT; ++i) {\n      x400_gpsmGenerators.emplace_back(std::make_unique<CElementGen>(*desc->x50_GPSM));\n      x400_gpsmGenerators.back()->SetParticleEmission(false);\n    }\n  }\n\n  if (desc->x60_EPSM) {\n    x450_26_haveEPSM = true;\n    x410_epsmGenerators.reserve(x154_SCNT);\n    for (int i = 0; i < x154_SCNT; ++i) {\n      x410_epsmGenerators.emplace_back(std::make_unique<CElementGen>(*desc->x60_EPSM));\n      x410_epsmGenerators.back()->SetParticleEmission(false);\n    }\n  }\n\n  if (x1c_elecDesc->x28_LWD1 || x1c_elecDesc->x2c_LWD2 || x1c_elecDesc->x30_LWD3) {\n    x450_28_haveLWD = true;\n    for (int i = 0; i < x154_SCNT; ++i) {\n      x2e4_lineManagers.emplace_back(std::make_unique<CLineManager>());\n    }\n  }\n}\n\nvoid CParticleElectric::RenderSwooshes() {\n  for (const CParticleElectricManager& elec : x3e8_electricManagers) {\n    x1e0_swooshGenerators[elec.x0_idx]->Render();\n  }\n}\n\nvoid CParticleElectric::SetupLineGXMaterial() {\n  // Konst color/alpha 0\n}\n\nvoid CParticleElectric::DrawLineStrip(const std::vector<zeus::CVector3f>& verts, float width,\n                                      const zeus::CColor& color) {\n//  const size_t useIdx = m_nextLineRenderer;\n//  if (++m_nextLineRenderer > m_lineRenderers.size()) {\n//    m_lineRenderers.resize(m_nextLineRenderer);\n//  }\n//  if (!m_lineRenderers[useIdx]) {\n//    m_lineRenderers[useIdx] = std::make_unique<CLineRenderer>(CLineRenderer::EPrimitiveMode::LineStrip, x150_SSEG,\n//                                                              aurora::gfx::TextureHandle{}, true, true);\n//  }\n//  CLineRenderer& renderer = *m_lineRenderers[useIdx];\n//  const zeus::CColor useColor = x1b8_moduColor * color;\n//\n//  renderer.Reset();\n//  for (const zeus::CVector3f& vert : verts) {\n//    renderer.AddVertex(vert, useColor, width);\n//  }\n//  renderer.Render(); // g_Renderer->IsThermalVisorHotPass()\n}\n\nvoid CParticleElectric::RenderLines() {\n  // m_nextLineRenderer = 0;\n  CGraphics::DisableAllLights();\n  // Z-test, no write\n  // Additive blend\n\n  CGraphics::SetModelMatrix(zeus::CTransform::Translate(xa4_globalTranslation) * xb0_globalOrientation *\n                            zeus::CTransform::Translate(x38_translation) * x44_orientation *\n                            zeus::CTransform::Scale(xe0_globalScale) * zeus::CTransform::Scale(xec_localScale));\n  // Disable culling\n  SetupLineGXMaterial();\n  for (CParticleElectricManager& elec : x3e8_electricManagers) {\n    CLineManager& line = *x2e4_lineManagers[elec.x0_idx];\n    if (x1c_elecDesc->x28_LWD1) {\n      DrawLineStrip(line.x0_verts, line.x10_widths[0], line.x1c_colors[0]);\n    }\n    if (x1c_elecDesc->x2c_LWD2) {\n      DrawLineStrip(line.x0_verts, line.x10_widths[1], line.x1c_colors[1]);\n    }\n    if (x1c_elecDesc->x30_LWD3) {\n      DrawLineStrip(line.x0_verts, line.x10_widths[2], line.x1c_colors[2]);\n    }\n  }\n\n  // Enable culling\n  // Line Width 1\n}\n\nvoid CParticleElectric::UpdateCachedTransform() {\n  xf8_cachedXf = zeus::CTransform::Translate(xa4_globalTranslation) * xb0_globalOrientation *\n                 zeus::CTransform::Translate(x38_translation) * x44_orientation;\n  x450_29_transformDirty = false;\n}\n\nvoid CParticleElectric::UpdateLine(size_t idx, int frame) {\n  CLineManager& line = *x2e4_lineManagers[idx];\n\n  if (CColorElement* lcl1 = x1c_elecDesc->x34_LCL1.get()) {\n    lcl1->GetValue(frame, line.x1c_colors[0]);\n  }\n  if (CColorElement* lcl2 = x1c_elecDesc->x38_LCL2.get()) {\n    lcl2->GetValue(frame, line.x1c_colors[1]);\n  }\n  if (CColorElement* lcl3 = x1c_elecDesc->x3c_LCL3.get()) {\n    lcl3->GetValue(frame, line.x1c_colors[2]);\n  }\n\n  if (CRealElement* lwd1 = x1c_elecDesc->x28_LWD1.get()) {\n    lwd1->GetValue(frame, line.x10_widths[0]);\n  }\n  if (CRealElement* lwd2 = x1c_elecDesc->x2c_LWD2.get()) {\n    lwd2->GetValue(frame, line.x10_widths[1]);\n  }\n  if (CRealElement* lwd3 = x1c_elecDesc->x30_LWD3.get()) {\n    lwd3->GetValue(frame, line.x10_widths[2]);\n  }\n}\n\nvoid CParticleElectric::UpdateElectricalEffects() {\n  for (auto it = x3e8_electricManagers.begin(); it != x3e8_electricManagers.end();) {\n    CParticleElectricManager& elec = *it;\n    if (elec.x4_slif <= 1) {\n      x1bc_allocated[elec.x0_idx] = false;\n      if (elec.x10_gpsmIdx != -1) {\n        x400_gpsmGenerators[elec.x10_gpsmIdx]->SetParticleEmission(false);\n      }\n      if (elec.x14_epsmIdx != -1) {\n        x410_epsmGenerators[elec.x14_epsmIdx]->SetParticleEmission(false);\n      }\n      it = x3e8_electricManagers.erase(it);\n      continue;\n    }\n\n    CParticleGlobals::instance()->SetParticleLifetime(int(elec.xc_endFrame - elec.x8_startFrame));\n    const int frame = x28_currentFrame - int(elec.x8_startFrame);\n    CParticleGlobals::instance()->UpdateParticleLifetimeTweenValues(frame);\n\n    if (x450_27_haveSSWH) {\n      CParticleSwoosh& swoosh = *x1e0_swooshGenerators[elec.x0_idx];\n      zeus::CColor color = zeus::skWhite;\n      if (CColorElement* colr = x1c_elecDesc->x14_COLR.get()) {\n        colr->GetValue(frame, color);\n      }\n      swoosh.SetModulationColor(color * x1b8_moduColor);\n    }\n\n    if (x450_28_haveLWD) {\n      UpdateLine(elec.x0_idx, frame);\n    }\n\n    elec.x4_slif -= 1;\n    ++it;\n  }\n}\n\nvoid CParticleElectric::CalculateFractal(int start, int end, float ampl, float ampd) {\n  const float tmp = float(end - start) / float(x430_fractalMags.size()) * ampl;\n  const int storeIdx = (start + end) / 2;\n  x430_fractalMags[storeIdx] = (x430_fractalMags[start] + x430_fractalMags[end]) * 0.5f + tmp * x14c_randState.Float() -\n                               tmp * 0.5f + ampd * x14c_randState.Float() - ampd * 0.5f;\n  if (((start + end) & 1) != 0) {\n    x430_fractalMags[end - 1] = x430_fractalMags[end];\n  }\n\n  if (storeIdx - start > 1) {\n    CalculateFractal(start, storeIdx, ampl, ampd);\n  }\n  if (end - storeIdx > 1) {\n    CalculateFractal(storeIdx, end, ampl, ampd);\n  }\n}\n\nvoid CParticleElectric::CalculatePoints() {\n  zeus::CVector3f pos, vel;\n  if (CEmitterElement* iemt = x1c_elecDesc->x18_IEMT.get()) {\n    iemt->GetValue(x28_currentFrame, pos, vel);\n  }\n\n  if (x178_overrideIPos) {\n    pos = *x178_overrideIPos;\n  }\n  if (x188_overrideIVel) {\n    vel = *x188_overrideIVel;\n  }\n\n  rstl::reserved_vector<zeus::CVector3f, 4> points;\n\n  if (!vel.isZero()) {\n    points.push_back(pos);\n    points.push_back(pos + vel);\n    points.push_back(pos + vel * 2.f);\n  } else {\n    points.push_back(pos);\n  }\n\n  zeus::CVector3f fpos = zeus::skForward;\n  zeus::CVector3f fvel;\n  if (CEmitterElement* femt = x1c_elecDesc->x1c_FEMT.get()) {\n    femt->GetValue(x28_currentFrame, fpos, fvel);\n  }\n\n  if (x198_overrideFPos) {\n    fpos = *x198_overrideFPos;\n  }\n  if (x1a8_overrideFVel) {\n    fvel = *x1a8_overrideFVel;\n  }\n\n  if (!fvel.isZero()) {\n    if (points.size() == 3) {\n      points.push_back(fpos);\n      points[2] = fpos + fvel;\n    } else {\n      points.push_back(fpos + fvel * 2.f);\n      points.push_back(fpos + fvel);\n      points.push_back(fpos);\n    }\n  } else {\n    points.push_back(fpos);\n  }\n\n  if (points.size() == 4) {\n    x420_calculatedVerts[0] = points[0];\n    const int segs = x150_SSEG - 1;\n    const float segDiv = 1.f / float(segs);\n    float curDiv = segDiv;\n    for (int i = 1; i < segs; ++i) {\n      const float t = segDiv * x14c_randState.Range(-0.45f, 0.45f) + curDiv;\n      x420_calculatedVerts[i] = zeus::getBezierPoint(points[0], points[1], points[2], points[3], t);\n      curDiv += segDiv;\n    }\n    x420_calculatedVerts[segs] = points[3];\n  } else {\n    x420_calculatedVerts[0] = pos;\n    const int segs = x150_SSEG - 1;\n    const float segDiv = 1.f / float(segs);\n    zeus::CVector3f accum = x420_calculatedVerts[0];\n    const zeus::CVector3f segDelta = (fpos - pos) * segDiv;\n    for (int i = 1; i < segs; ++i) {\n      float r = x14c_randState.Range(-0.45f, 0.45f);\n      x420_calculatedVerts[i] = segDelta * r + accum;\n      accum += segDelta;\n    }\n    x420_calculatedVerts[segs] = fpos;\n  }\n\n  for (int i = 0; i < x150_SSEG; ++i) {\n    x430_fractalMags[i] = 0.f;\n  }\n\n  float amplVal = 1.f;\n  if (CRealElement* ampl = x1c_elecDesc->x20_AMPL.get()) {\n    ampl->GetValue(x28_currentFrame, amplVal);\n    amplVal *= 2.f;\n  }\n\n  float ampdVal = 0.f;\n  if (CRealElement* ampd = x1c_elecDesc->x24_AMPD.get()) {\n    ampd->GetValue(x28_currentFrame, ampdVal);\n  }\n\n  CalculateFractal(0, x420_calculatedVerts.size() - 1, amplVal, ampdVal);\n\n  zeus::CVector3f v0 = x420_calculatedVerts[0] - x420_calculatedVerts[1];\n  zeus::CVector3f v1 = x420_calculatedVerts[x420_calculatedVerts.size() - 1] - x420_calculatedVerts[1];\n  zeus::CVector3f upVec = zeus::skUp;\n  if (v0.canBeNormalized() && v1.canBeNormalized()) {\n    v0.normalize();\n    v1.normalize();\n    float dot = v0.dot(v1);\n    if (dot < 0) {\n      dot = -dot;\n    }\n    if (std::fabs(dot - 1.f) < 0.00001f) {\n      upVec = zeus::lookAt(x420_calculatedVerts[0], x420_calculatedVerts[1]).basis[2];\n    } else {\n      upVec = v0.cross(v1).normalized();\n    }\n  } else if (x420_calculatedVerts[0] != x420_calculatedVerts[1]) {\n    upVec = zeus::lookAt(x420_calculatedVerts[0], x420_calculatedVerts[1]).basis[2];\n  }\n\n  const float commonRand = x14c_randState.Range(0.f, 360.f);\n\n  for (size_t i = 1; i < x420_calculatedVerts.size() - 1; ++i) {\n    const zeus::CVector3f delta = x420_calculatedVerts[i] - x420_calculatedVerts[i - 1];\n    if (!delta.isZero()) {\n      const zeus::CRelAngle angle =\n          zeus::degToRad(x430_fractalMags[i] / amplVal * 16.f * x14c_randState.Range(-1.f, 1.f) + commonRand);\n      x440_fractalOffsets[i] = zeus::CQuaternion::fromAxisAngle(delta, angle).transform(x430_fractalMags[i] * upVec);\n    }\n  }\n\n  for (size_t i = 1; i < x420_calculatedVerts.size() - 1; ++i) {\n    x420_calculatedVerts[i] += x440_fractalOffsets[i];\n  }\n\n  if (x1c_elecDesc->x70_ZERY) {\n    for (auto& calculatedVert : x420_calculatedVerts) {\n      calculatedVert.y() = 0.f;\n    }\n  }\n}\n\nvoid CParticleElectric::CreateNewParticles(int count) {\n  size_t allocIdx = 0;\n\n  for (int i = 0; i < count; ++i) {\n    if (x3e8_electricManagers.size() < size_t(x154_SCNT)) {\n      const zeus::CTransform cachedRot = xf8_cachedXf.getRotation();\n\n      const size_t toAdd = x1bc_allocated.size() - allocIdx;\n      for (size_t j = 0; j < toAdd; ++j, ++allocIdx) {\n        if (x1bc_allocated[allocIdx]) {\n          continue;\n        }\n        x1bc_allocated[allocIdx] = true;\n\n        int lifetime = 1;\n        if (CIntElement* slif = x1c_elecDesc->x4_SLIF.get()) {\n          slif->GetValue(x28_currentFrame, lifetime);\n        }\n\n        x3e8_electricManagers.emplace_back(allocIdx, lifetime, x28_currentFrame);\n        CParticleElectricManager& elec = x3e8_electricManagers.back();\n        CParticleGlobals::instance()->SetParticleLifetime(elec.xc_endFrame - elec.x8_startFrame);\n        const int frame = x28_currentFrame - int(elec.x8_startFrame);\n        CParticleGlobals::instance()->UpdateParticleLifetimeTweenValues(frame);\n        CalculatePoints();\n\n        if (x450_27_haveSSWH) {\n          CParticleSwoosh& swoosh = *x1e0_swooshGenerators[allocIdx];\n          swoosh.SetParticleEmission(true);\n          swoosh.SetGlobalTranslation(xf8_cachedXf.origin);\n          swoosh.SetGlobalOrientation(cachedRot);\n          swoosh.SetGlobalScale(xe0_globalScale);\n          swoosh.SetLocalScale(xec_localScale);\n          zeus::CColor color = zeus::skWhite;\n          if (CColorElement* colr = x1c_elecDesc->x14_COLR.get()) {\n            colr->GetValue(frame, color);\n          }\n          swoosh.SetModulationColor(color * x1b8_moduColor);\n          swoosh.DoElectricCreate(x420_calculatedVerts);\n        }\n\n        if (x450_28_haveLWD) {\n          CLineManager& line = *x2e4_lineManagers[allocIdx];\n          line.x0_verts = x420_calculatedVerts;\n          UpdateLine(allocIdx, 0);\n          if (x450_27_haveSSWH) {\n            x130_buildBounds = zeus::CAABox();\n            for (const zeus::CVector3f& vec : x420_calculatedVerts) {\n              x130_buildBounds.accumulateBounds(vec);\n            }\n            line.x28_aabb = x130_buildBounds;\n          }\n        }\n\n        if (x450_25_haveGPSM) {\n          for (int k = 0; k < x154_SCNT; ++k) {\n            CElementGen& gen = *x400_gpsmGenerators[k];\n            if (!gen.GetParticleEmission()) {\n              const zeus::CTransform scale =\n                  zeus::CTransform::Scale(xe0_globalScale) * zeus::CTransform::Scale(xec_localScale);\n              gen.SetTranslation(scale * x420_calculatedVerts.front());\n              gen.SetParticleEmission(true);\n              elec.x10_gpsmIdx = k;\n              break;\n            }\n          }\n        }\n\n        if (x450_26_haveEPSM) {\n          for (int k = 0; k < x154_SCNT; ++k) {\n            CElementGen& gen = *x410_epsmGenerators[k];\n            if (!gen.GetParticleEmission()) {\n              const zeus::CTransform scale =\n                  zeus::CTransform::Scale(xe0_globalScale) * zeus::CTransform::Scale(xec_localScale);\n              gen.SetTranslation(scale * x420_calculatedVerts.back());\n              gen.SetParticleEmission(true);\n              elec.x14_epsmIdx = k;\n              break;\n            }\n          }\n        }\n\n        break;\n      }\n    }\n  }\n}\n\nvoid CParticleElectric::AddElectricalEffects() {\n  float genRate = 0.f;\n  if (CRealElement* grat = x1c_elecDesc->x8_GRAT.get()) {\n    if (grat->GetValue(x28_currentFrame, genRate)) {\n      x3e8_electricManagers.clear();\n      std::fill(x1bc_allocated.begin(), x1bc_allocated.end(), false);\n      return;\n    } else {\n      genRate = std::max(0.f, genRate);\n    }\n  }\n\n  x15c_genRem += genRate;\n  const float partCount = std::floor(x15c_genRem);\n  x15c_genRem -= partCount;\n  CreateNewParticles(int(partCount));\n}\n\nvoid CParticleElectric::BuildBounds() {\n  if (GetParticleCount() <= 0) {\n    x160_systemBounds = zeus::CAABox();\n    return;\n  }\n\n  x160_systemBounds = zeus::CAABox();\n\n  if (x450_27_haveSSWH) {\n    for (const CParticleElectricManager& elec : x3e8_electricManagers) {\n      const CParticleSwoosh& swoosh = *x1e0_swooshGenerators[elec.x0_idx];\n      if (const auto bounds = swoosh.GetBounds()) {\n        x160_systemBounds.accumulateBounds(*bounds);\n      }\n    }\n  } else if (x450_28_haveLWD) {\n    zeus::CAABox tmp = zeus::CAABox();\n    for (const CParticleElectricManager& elec : x3e8_electricManagers) {\n      CLineManager& line = *x2e4_lineManagers[elec.x0_idx];\n      tmp.accumulateBounds(line.x28_aabb);\n    }\n    if (!tmp.invalid()) {\n      x160_systemBounds.accumulateBounds(tmp.getTransformedAABox(\n          zeus::CTransform::Translate(xa4_globalTranslation) * xb0_globalOrientation *\n          zeus::CTransform::Translate(x38_translation) * x44_orientation * zeus::CTransform::Scale(xe0_globalScale)));\n    }\n  }\n\n  if (x450_25_haveGPSM) {\n    for (int i = 0; i < x154_SCNT; ++i) {\n      if (auto bounds = x400_gpsmGenerators[i]->GetBounds()) {\n        x160_systemBounds.accumulateBounds(*bounds);\n      }\n    }\n  }\n\n  if (x450_26_haveEPSM) {\n    for (int i = 0; i < x154_SCNT; ++i) {\n      if (auto bounds = x410_epsmGenerators[i]->GetBounds()) {\n        x160_systemBounds.accumulateBounds(*bounds);\n      }\n    }\n  }\n}\n\nbool CParticleElectric::Update(double dt) {\n  [[maybe_unused]] CGlobalRandom gr(x14c_randState);\n  bool ret = false;\n\n  if (x450_25_haveGPSM) {\n    for (int i = 0; i < x154_SCNT; ++i) {\n      if (!x400_gpsmGenerators[i]->IsSystemDeletable()) {\n        break;\n      }\n    }\n  }\n\n  if (x450_26_haveEPSM) {\n    for (int i = 0; i < x154_SCNT; ++i) {\n      if (!x410_epsmGenerators[i]->IsSystemDeletable()) {\n        break;\n      }\n    }\n  }\n\n  const bool emitting = x450_24_emitting && x28_currentFrame < x2c_LIFE;\n\n  double evalTime = x28_currentFrame / 60.0;\n  x30_curTime += dt;\n\n  if (x450_29_transformDirty) {\n    UpdateCachedTransform();\n    const zeus::CTransform globalOrient = xf8_cachedXf.getRotation();\n    if (x450_27_haveSSWH) {\n      for (const CParticleElectricManager& elec : x3e8_electricManagers) {\n        CParticleSwoosh& swoosh = *x1e0_swooshGenerators[elec.x0_idx];\n        swoosh.SetGlobalTranslation(xf8_cachedXf.origin);\n        swoosh.SetGlobalOrientation(globalOrient);\n        swoosh.SetGlobalScale(xe0_globalScale);\n        swoosh.SetLocalScale(xec_localScale);\n      }\n    }\n\n    if (x450_25_haveGPSM) {\n      for (const CParticleElectricManager& elec : x3e8_electricManagers) {\n        CElementGen& gen = *x400_gpsmGenerators[elec.x0_idx];\n        gen.SetGlobalTranslation(xf8_cachedXf.origin);\n        gen.SetGlobalOrientation(globalOrient);\n        gen.SetGlobalScale(xe0_globalScale);\n        gen.SetLocalScale(xec_localScale);\n      }\n    }\n\n    if (x450_26_haveEPSM) {\n      for (const CParticleElectricManager& elec : x3e8_electricManagers) {\n        CElementGen& gen = *x410_epsmGenerators[elec.x0_idx];\n        gen.SetGlobalTranslation(xf8_cachedXf.origin);\n        gen.SetGlobalOrientation(globalOrient);\n        gen.SetGlobalScale(xe0_globalScale);\n        gen.SetLocalScale(xec_localScale);\n      }\n    }\n\n    ret = true;\n  }\n\n  while (evalTime < x30_curTime) {\n    CParticleGlobals::instance()->SetEmitterTime(x28_currentFrame);\n    UpdateElectricalEffects();\n    if (emitting)\n      AddElectricalEffects();\n\n    if (x450_25_haveGPSM) {\n      if (x28_currentFrame >= x2c_LIFE) {\n        for (int i = 0; i < x154_SCNT; ++i) {\n          x400_gpsmGenerators[i]->EndLifetime();\n        }\n      }\n      for (int i = 0; i < x154_SCNT; ++i) {\n        x400_gpsmGenerators[i]->Update(1.0 / 60.0);\n      }\n    }\n\n    if (x450_26_haveEPSM) {\n      if (x28_currentFrame >= x2c_LIFE) {\n        for (int i = 0; i < x154_SCNT; ++i) {\n          x410_epsmGenerators[i]->EndLifetime();\n        }\n      }\n      for (int i = 0; i < x154_SCNT; ++i) {\n        x410_epsmGenerators[i]->Update(1.0 / 60.0);\n      }\n    }\n\n    ret = true;\n    evalTime += (1.0 / 60.0);\n    x28_currentFrame += 1;\n  }\n\n  if (ret) {\n    BuildBounds();\n  }\n\n  return ret;\n}\n\nvoid CParticleElectric::Render() {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\n      fmt::format(\"CParticleElectric::Render {}\", *x1c_elecDesc.GetObjectTag()).c_str(), zeus::skYellow);\n\n  if (!x3e8_electricManagers.empty()) {\n    if (x450_29_transformDirty) {\n      UpdateCachedTransform();\n    }\n    if (x450_27_haveSSWH) {\n      RenderSwooshes();\n    }\n    if (x450_28_haveLWD) {\n      RenderLines();\n    }\n  }\n\n  if (x450_25_haveGPSM) {\n    for (int i = 0; i < x154_SCNT; ++i) {\n      x400_gpsmGenerators[i]->Render();\n    }\n  }\n\n  if (x450_26_haveEPSM) {\n    for (int i = 0; i < x154_SCNT; ++i) {\n      x410_epsmGenerators[i]->Render();\n    }\n  }\n}\n\nvoid CParticleElectric::SetOrientation(const zeus::CTransform& orientation) {\n  x44_orientation = orientation;\n  x74_invOrientation = x44_orientation.inverse();\n  x450_29_transformDirty = true;\n}\n\nvoid CParticleElectric::SetTranslation(const zeus::CVector3f& translation) {\n  x38_translation = translation;\n  x450_29_transformDirty = true;\n}\n\nvoid CParticleElectric::SetGlobalOrientation(const zeus::CTransform& orientation) {\n  xb0_globalOrientation = orientation;\n  x450_29_transformDirty = true;\n\n  if (x450_27_haveSSWH) {\n    for (const CParticleElectricManager& elec : x3e8_electricManagers) {\n      CParticleSwoosh& swoosh = *x1e0_swooshGenerators[elec.x0_idx];\n      swoosh.SetGlobalOrientation(xb0_globalOrientation);\n    }\n  }\n\n  if (x450_25_haveGPSM) {\n    for (int i = 0; i < x154_SCNT; ++i) {\n      x400_gpsmGenerators[i]->SetGlobalOrientation(xb0_globalOrientation);\n    }\n  }\n\n  if (x450_26_haveEPSM) {\n    for (int i = 0; i < x154_SCNT; ++i) {\n      x410_epsmGenerators[i]->SetGlobalOrientation(xb0_globalOrientation);\n    }\n  }\n}\n\nvoid CParticleElectric::SetGlobalTranslation(const zeus::CVector3f& translation) {\n  xa4_globalTranslation = translation;\n  x450_29_transformDirty = true;\n\n  if (x450_27_haveSSWH) {\n    for (const CParticleElectricManager& elec : x3e8_electricManagers) {\n      CParticleSwoosh& swoosh = *x1e0_swooshGenerators[elec.x0_idx];\n      swoosh.SetGlobalTranslation(xa4_globalTranslation);\n    }\n  }\n\n  if (x450_25_haveGPSM) {\n    for (int i = 0; i < x154_SCNT; ++i) {\n      x400_gpsmGenerators[i]->SetGlobalTranslation(xa4_globalTranslation);\n    }\n  }\n\n  if (x450_26_haveEPSM) {\n    for (int i = 0; i < x154_SCNT; ++i) {\n      x410_epsmGenerators[i]->SetGlobalTranslation(xa4_globalTranslation);\n    }\n  }\n}\n\nvoid CParticleElectric::SetGlobalScale(const zeus::CVector3f& scale) {\n  xe0_globalScale = scale;\n  x450_29_transformDirty = true;\n}\n\nvoid CParticleElectric::SetLocalScale(const zeus::CVector3f& scale) {\n  xec_localScale = scale;\n  x450_29_transformDirty = true;\n\n  if (x450_27_haveSSWH) {\n    for (const CParticleElectricManager& elec : x3e8_electricManagers) {\n      CParticleSwoosh& swoosh = *x1e0_swooshGenerators[elec.x0_idx];\n      swoosh.SetLocalScale(xec_localScale);\n    }\n  }\n\n  if (x450_25_haveGPSM) {\n    for (int i = 0; i < x154_SCNT; ++i) {\n      x400_gpsmGenerators[i]->SetLocalScale(xec_localScale);\n    }\n  }\n\n  if (x450_26_haveEPSM) {\n    for (int i = 0; i < x154_SCNT; ++i) {\n      x410_epsmGenerators[i]->SetLocalScale(xec_localScale);\n    }\n  }\n}\n\nvoid CParticleElectric::SetParticleEmission(bool emitting) { x450_24_emitting = emitting; }\n\nvoid CParticleElectric::SetModulationColor(const zeus::CColor& color) { x1b8_moduColor = color; }\n\nconst zeus::CTransform& CParticleElectric::GetOrientation() const { return x44_orientation; }\n\nconst zeus::CVector3f& CParticleElectric::GetTranslation() const { return x38_translation; }\n\nconst zeus::CTransform& CParticleElectric::GetGlobalOrientation() const { return xb0_globalOrientation; }\n\nconst zeus::CVector3f& CParticleElectric::GetGlobalTranslation() const { return xa4_globalTranslation; }\n\nconst zeus::CVector3f& CParticleElectric::GetGlobalScale() const { return xe0_globalScale; }\n\nconst zeus::CColor& CParticleElectric::GetModulationColor() const { return x1b8_moduColor; }\n\nbool CParticleElectric::IsSystemDeletable() const {\n  if (x450_24_emitting && x28_currentFrame < x2c_LIFE) {\n    return false;\n  }\n\n  if (!x3e8_electricManagers.empty()) {\n    return false;\n  }\n\n  if (x450_25_haveGPSM) {\n    for (int i = 0; i < x154_SCNT; ++i) {\n      if (!x400_gpsmGenerators[i]->IsSystemDeletable()) {\n        return false;\n      }\n    }\n  }\n\n  if (x450_26_haveEPSM) {\n    for (int i = 0; i < x154_SCNT; ++i) {\n      if (!x410_epsmGenerators[i]->IsSystemDeletable()) {\n        return false;\n      }\n    }\n  }\n\n  return true;\n}\n\nstd::optional<zeus::CAABox> CParticleElectric::GetBounds() const {\n  if (GetParticleCount() <= 0) {\n    return std::nullopt;\n  }\n\n  return x160_systemBounds;\n}\n\nu32 CParticleElectric::GetParticleCount() const {\n  u32 ret = 0;\n\n  for (const CParticleElectricManager& elec : x3e8_electricManagers) {\n    if (x450_27_haveSSWH) {\n      ret += x1e0_swooshGenerators[elec.x0_idx]->GetParticleCount();\n    }\n    if (x450_28_haveLWD) {\n      ret += x150_SSEG;\n    }\n  }\n\n  if (x450_25_haveGPSM) {\n    for (int i = 0; i < x154_SCNT; ++i) {\n      ret += x400_gpsmGenerators[i]->GetParticleCount();\n    }\n  }\n\n  if (x450_26_haveEPSM) {\n    for (int i = 0; i < x154_SCNT; ++i) {\n      ret += x410_epsmGenerators[i]->GetParticleCount();\n    }\n  }\n\n  return ret;\n}\n\nbool CParticleElectric::SystemHasLight() const {\n  if (x450_25_haveGPSM) {\n    return x400_gpsmGenerators.front()->SystemHasLight();\n  }\n  if (x450_26_haveEPSM) {\n    return x410_epsmGenerators.front()->SystemHasLight();\n  }\n  return false;\n}\n\nCLight CParticleElectric::GetLight() const {\n  if (x450_25_haveGPSM) {\n    return x400_gpsmGenerators.front()->GetLight();\n  }\n  if (x450_26_haveEPSM) {\n    return x410_epsmGenerators.front()->GetLight();\n  }\n  return CLight::BuildLocalAmbient(GetGlobalTranslation(), zeus::skOrange);\n}\n\nbool CParticleElectric::GetParticleEmission() const { return x450_24_emitting; }\n\nvoid CParticleElectric::DestroyParticles() {\n  // Empty\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CParticleElectric.hpp",
    "content": "#pragma once\n\n#include <array>\n#include <list>\n#include <memory>\n#include <optional>\n#include <vector>\n\n#include \"Runtime/CRandom16.hpp\"\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/Particle/CParticleGen.hpp\"\n#include \"Runtime/Particle/CParticleSwoosh.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CColor.hpp>\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CElectricDescription;\n\nclass CParticleElectric : public CParticleGen {\n  static u16 g_GlobalSeed;\n\npublic:\n  static void SetGlobalSeed(u16 seed) { g_GlobalSeed = seed; }\n  class CLineManager {\n    friend class CParticleElectric;\n    std::vector<zeus::CVector3f> x0_verts;\n    std::array<float, 3> x10_widths = {1.f, 2.f, 3.f};\n    std::array<zeus::CColor, 3> x1c_colors;\n    zeus::CAABox x28_aabb = zeus::CAABox();\n  };\n\n  class CParticleElectricManager {\n    friend class CParticleElectric;\n    u32 x0_idx;\n    u32 x4_slif;\n    u32 x8_startFrame;\n    u32 xc_endFrame;\n    int x10_gpsmIdx = -1;\n    int x14_epsmIdx = -1;\n\n  public:\n    CParticleElectricManager(u32 idx, u32 slif, u32 startFrame)\n    : x0_idx(idx), x4_slif(slif), x8_startFrame(startFrame), xc_endFrame(startFrame + slif) {}\n  };\n\nprivate:\n  TLockedToken<CElectricDescription> x1c_elecDesc;\n  int x28_currentFrame = 0;\n  int x2c_LIFE;\n  double x30_curTime = 0.0;\n  zeus::CVector3f x38_translation;\n  zeus::CTransform x44_orientation;\n  zeus::CTransform x74_invOrientation;\n  zeus::CVector3f xa4_globalTranslation;\n  zeus::CTransform xb0_globalOrientation;\n  zeus::CVector3f xe0_globalScale = zeus::skOne3f;\n  zeus::CVector3f xec_localScale = zeus::skOne3f;\n  zeus::CTransform xf8_cachedXf;\n  float x128 = 0.f;\n  float x12c = 0.f;\n  zeus::CAABox x130_buildBounds = zeus::CAABox();\n  CRandom16 x14c_randState;\n  int x150_SSEG = 8;\n  int x154_SCNT = 1;\n  int x158 = 0;\n  float x15c_genRem = 0.f;\n  zeus::CAABox x160_systemBounds = zeus::CAABox();\n  std::optional<zeus::CVector3f> x178_overrideIPos;\n  std::optional<zeus::CVector3f> x188_overrideIVel;\n  std::optional<zeus::CVector3f> x198_overrideFPos;\n  std::optional<zeus::CVector3f> x1a8_overrideFVel;\n  zeus::CColor x1b8_moduColor;\n  rstl::reserved_vector<bool, 32> x1bc_allocated;\n  rstl::reserved_vector<std::unique_ptr<CParticleSwoosh>, 32> x1e0_swooshGenerators;\n  rstl::reserved_vector<std::unique_ptr<CLineManager>, 32> x2e4_lineManagers;\n  std::list<CParticleElectricManager> x3e8_electricManagers;\n  std::vector<std::unique_ptr<CElementGen>> x400_gpsmGenerators;\n  std::vector<std::unique_ptr<CElementGen>> x410_epsmGenerators;\n  std::vector<zeus::CVector3f> x420_calculatedVerts;\n  std::vector<float> x430_fractalMags;\n  std::vector<zeus::CVector3f> x440_fractalOffsets;\n  bool x450_24_emitting : 1 = true;\n  bool x450_25_haveGPSM : 1 = false;\n  bool x450_26_haveEPSM : 1 = false;\n  bool x450_27_haveSSWH : 1 = false;\n  bool x450_28_haveLWD : 1 = false;\n  bool x450_29_transformDirty : 1 = true;\n\n  void SetupLineGXMaterial();\n  void DrawLineStrip(const std::vector<zeus::CVector3f>& verts, float width, const zeus::CColor& color);\n  void RenderLines();\n  void RenderSwooshes();\n  void UpdateCachedTransform();\n  void UpdateLine(size_t idx, int frame);\n  void UpdateElectricalEffects();\n  void CalculateFractal(int start, int end, float ampl, float ampd);\n  void CalculatePoints();\n  void CreateNewParticles(int count);\n  void AddElectricalEffects();\n  void BuildBounds();\n\npublic:\n  explicit CParticleElectric(const TToken<CElectricDescription>& desc);\n\n  bool Update(double) override;\n  void Render() override;\n  void SetOrientation(const zeus::CTransform& orientation) override;\n  void SetTranslation(const zeus::CVector3f& translation) override;\n  void SetGlobalOrientation(const zeus::CTransform& orientation) override;\n  void SetGlobalTranslation(const zeus::CVector3f& translation) override;\n  void SetGlobalScale(const zeus::CVector3f& scale) override;\n  void SetLocalScale(const zeus::CVector3f& scale) override;\n  void SetParticleEmission(bool emitting) override;\n  void SetModulationColor(const zeus::CColor&) override;\n  void SetOverrideIPos(const zeus::CVector3f& vec) { x178_overrideIPos.emplace(vec); }\n  void SetOverrideIVel(const zeus::CVector3f& vec) { x188_overrideIVel.emplace(vec); }\n  void SetOverrideFPos(const zeus::CVector3f& vec) { x198_overrideFPos.emplace(vec); }\n  void SetOverrideFVel(const zeus::CVector3f& vec) { x1a8_overrideFVel.emplace(vec); }\n  const zeus::CTransform& GetOrientation() const override;\n  const zeus::CVector3f& GetTranslation() const override;\n  const zeus::CTransform& GetGlobalOrientation() const override;\n  const zeus::CVector3f& GetGlobalTranslation() const override;\n  const zeus::CVector3f& GetGlobalScale() const override;\n  const zeus::CColor& GetModulationColor() const override;\n  bool IsSystemDeletable() const override;\n  std::optional<zeus::CAABox> GetBounds() const override;\n  u32 GetParticleCount() const override;\n  bool SystemHasLight() const override;\n  CLight GetLight() const override;\n  bool GetParticleEmission() const override;\n  void DestroyParticles() override;\n  void Reset() override {}\n  void ForceParticleCreation(s32 count) {\n    CGlobalRandom gRnd{x14c_randState};\n    CreateNewParticles(count);\n  }\n  FourCC Get4CharId() const override { return FOURCC('ELSC'); }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CParticleElectricDataFactory.cpp",
    "content": "#include \"Runtime/Particle/CParticleElectricDataFactory.hpp\"\n\n#include \"Runtime/CRandom16.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/Graphics/CModel.hpp\"\n#include \"Runtime/Particle/CElectricDescription.hpp\"\n#include \"Runtime/Logging.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\nnamespace metaforce {\nusing CPF = CParticleDataFactory;\n\nstd::unique_ptr<CElectricDescription> CParticleElectricDataFactory::GetGeneratorDesc(CInputStream& in,\n                                                                                     CSimplePool* resPool) {\n  return CreateElectricDescription(in, resPool);\n}\n\nstd::unique_ptr<CElectricDescription> CParticleElectricDataFactory::CreateElectricDescription(CInputStream& in,\n                                                                                              CSimplePool* resPool) {\n  const FourCC cid = CPF::GetClassID(in);\n\n  if (cid != FOURCC('ELSM')) {\n    return nullptr;\n  }\n\n  auto desc = std::make_unique<CElectricDescription>();\n  CreateELSM(desc.get(), in, resPool);\n  LoadELSMTokens(desc.get());\n  return desc;\n}\n\nbool CParticleElectricDataFactory::CreateELSM(CElectricDescription* desc, CInputStream& in, CSimplePool* resPool) {\n  CRandom16 rand;\n  CGlobalRandom gr{rand};\n\n  FourCC clsId = CPF::GetClassID(in);\n  while (clsId != SBIG('_END')) {\n    switch (clsId.toUint32()) {\n    case SBIG('LIFE'):\n      desc->x0_LIFE = CPF::GetIntElement(in);\n      break;\n    case SBIG('SLIF'):\n      desc->x4_SLIF = CPF::GetIntElement(in);\n      break;\n    case SBIG('GRAT'):\n      desc->x8_GRAT = CPF::GetRealElement(in);\n      break;\n    case SBIG('SCNT'):\n      desc->xc_SCNT = CPF::GetIntElement(in);\n      break;\n    case SBIG('SSEG'):\n      desc->x10_SSEG = CPF::GetIntElement(in);\n      break;\n    case SBIG('COLR'):\n      desc->x14_COLR = CPF::GetColorElement(in);\n      break;\n    case SBIG('IEMT'):\n      desc->x18_IEMT = CPF::GetEmitterElement(in);\n      break;\n    case SBIG('FEMT'):\n      desc->x1c_FEMT = CPF::GetEmitterElement(in);\n      break;\n    case SBIG('AMPL'):\n      desc->x20_AMPL = CPF::GetRealElement(in);\n      break;\n    case SBIG('AMPD'):\n      desc->x24_AMPD = CPF::GetRealElement(in);\n      break;\n    case SBIG('LWD1'):\n      desc->x28_LWD1 = CPF::GetRealElement(in);\n      break;\n    case SBIG('LWD2'):\n      desc->x2c_LWD2 = CPF::GetRealElement(in);\n      break;\n    case SBIG('LWD3'):\n      desc->x30_LWD3 = CPF::GetRealElement(in);\n      break;\n    case SBIG('LCL1'):\n      desc->x34_LCL1 = CPF::GetColorElement(in);\n      break;\n    case SBIG('LCL2'):\n      desc->x38_LCL2 = CPF::GetColorElement(in);\n      break;\n    case SBIG('LCL3'):\n      desc->x3c_LCL3 = CPF::GetColorElement(in);\n      break;\n    case SBIG('SSWH'):\n      desc->x40_SSWH = CPF::GetSwooshGeneratorDesc(in, resPool);\n      break;\n    case SBIG('GPSM'): {\n      std::vector<CAssetId> tracker;\n      tracker.reserve(8);\n      desc->x50_GPSM = CPF::GetChildGeneratorDesc(in, resPool, tracker);\n      break;\n    }\n    case SBIG('EPSM'): {\n      std::vector<CAssetId> tracker;\n      tracker.reserve(8);\n      desc->x60_EPSM = CPF::GetChildGeneratorDesc(in, resPool, tracker);\n      break;\n    }\n    case SBIG('ZERY'):\n      desc->x70_ZERY = CPF::GetBool(in);\n      break;\n    default: {\n      spdlog::fatal(\"Unknown ELSM class {} @{}\", clsId, in.GetReadPosition());\n      return false;\n    }\n    }\n    clsId = CPF::GetClassID(in);\n  }\n  return true;\n}\n\nvoid CParticleElectricDataFactory::LoadELSMTokens(CElectricDescription* desc) {\n  desc->x40_SSWH.Load();\n  desc->x50_GPSM.Load();\n  desc->x60_EPSM.Load();\n}\n\nCFactoryFnReturn FParticleElectricDataFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& vparms,\n                                              CObjectReference*) {\n  CSimplePool* sp = vparms.GetOwnedObj<CSimplePool*>();\n  return TToken<CElectricDescription>::GetIObjObjectFor(CParticleElectricDataFactory::GetGeneratorDesc(in, sp));\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CParticleElectricDataFactory.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/CFactoryMgr.hpp\"\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/IObj.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n\nnamespace metaforce {\nclass CElectricDescription;\nclass CSimplePool;\nclass CParticleElectricDataFactory {\n  static std::unique_ptr<CElectricDescription> CreateElectricDescription(CInputStream& in, CSimplePool* resPool);\n  static bool CreateELSM(CElectricDescription* desc, CInputStream& in, CSimplePool* resPool);\n  static void LoadELSMTokens(CElectricDescription* desc);\n\npublic:\n  static std::unique_ptr<CElectricDescription> GetGeneratorDesc(CInputStream& in, CSimplePool* resPool);\n};\n\nCFactoryFnReturn FParticleElectricDataFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& vparms,\n                                              CObjectReference*);\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CParticleGen.cpp",
    "content": "#include \"Runtime/Particle/CParticleGen.hpp\"\n\nnamespace metaforce {\n\nvoid CParticleGen::AddModifier(CWarp* mod) { x4_modifierList.push_back(mod); }\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CParticleGen.hpp",
    "content": "#pragma once\n\n#include <list>\n#include <optional>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Graphics/CLight.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CColor.hpp>\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CWarp;\nclass CActorLights;\n\nstruct CParticle {\n  int x0_endFrame = 0;\n  zeus::CVector3f x4_pos;\n  zeus::CVector3f x10_prevPos;\n  zeus::CVector3f x1c_vel;\n  int x28_startFrame = 0;\n  float x2c_lineLengthOrSize = 0.f;\n  float x30_lineWidthOrRota = 0.f;\n  zeus::CColor x34_color = {1.f, 0.f, 1.f, 1.f};\n};\n\nclass CParticleGen {\nprotected:\n  std::list<CWarp*> x4_modifierList;\n\npublic:\n  virtual ~CParticleGen() = default;\n\n  virtual bool Update(double) = 0;\n  virtual void Render() = 0;\n  virtual void SetOrientation(const zeus::CTransform&) = 0;\n  virtual void SetTranslation(const zeus::CVector3f&) = 0;\n  virtual void SetGlobalOrientation(const zeus::CTransform&) = 0;\n  virtual void SetGlobalTranslation(const zeus::CVector3f&) = 0;\n  virtual void SetGlobalScale(const zeus::CVector3f&) = 0;\n  virtual void SetLocalScale(const zeus::CVector3f&) = 0;\n  virtual void SetParticleEmission(bool) = 0;\n  virtual void SetModulationColor(const zeus::CColor&) = 0;\n  virtual void SetGeneratorRate(float rate) {}\n  virtual const zeus::CTransform& GetOrientation() const = 0;\n  virtual const zeus::CVector3f& GetTranslation() const = 0;\n  virtual const zeus::CTransform& GetGlobalOrientation() const = 0;\n  virtual const zeus::CVector3f& GetGlobalTranslation() const = 0;\n  virtual const zeus::CVector3f& GetGlobalScale() const = 0;\n  virtual const zeus::CColor& GetModulationColor() const = 0;\n  virtual float GetGeneratorRate() const { return 1.f; }\n  virtual bool IsSystemDeletable() const = 0;\n  virtual std::optional<zeus::CAABox> GetBounds() const = 0;\n  virtual u32 GetParticleCount() const = 0;\n  virtual bool SystemHasLight() const = 0;\n  virtual CLight GetLight() const = 0;\n  virtual bool GetParticleEmission() const = 0;\n  virtual void DestroyParticles() = 0;\n  virtual void Reset() = 0;\n  virtual FourCC Get4CharId() const = 0;\n\n  virtual void AddModifier(CWarp* mod);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CParticleGlobals.cpp",
    "content": "#include \"Runtime/Particle/CParticleGlobals.hpp\"\n\nnamespace metaforce {\nstd::unique_ptr<CParticleGlobals> CParticleGlobals::g_ParticleGlobals;\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CParticleGlobals.hpp",
    "content": "#pragma once\n\n#include <array>\n#include <vector>\n#include <memory>\n\n#include \"Runtime/RetroTypes.hpp\"\n\n#include <zeus/CColor.hpp>\n#include <zeus/CMatrix4f.hpp>\n#include <zeus/CVector3f.hpp>\n#include <zeus/CVector4f.hpp>\n\nnamespace metaforce {\nclass CElementGen;\nclass CParticleGlobals {\n  CParticleGlobals() = default;\n  static std::unique_ptr<CParticleGlobals> g_ParticleGlobals;\n\npublic:\n  int m_EmitterTime = 0;\n  float m_EmitterTimeReal = 0.f;\n  void SetEmitterTime(int frame) {\n    m_EmitterTime = frame;\n    m_EmitterTimeReal = float(frame);\n  }\n\n  int m_ParticleLifetime = 0;\n  float m_ParticleLifetimeReal = 0.f;\n  void SetParticleLifetime(int frame) {\n    m_ParticleLifetime = frame;\n    m_ParticleLifetimeReal = float(frame);\n  }\n\n  int m_ParticleLifetimePercentage = 0;\n  float m_ParticleLifetimePercentageReal = 0.f;\n  float m_ParticleLifetimePercentageRemainder = 0.f;\n  void UpdateParticleLifetimeTweenValues(int frame) {\n    const float lt = m_ParticleLifetime != 0 ? float(m_ParticleLifetime) : 1.0f;\n    m_ParticleLifetimePercentageReal = 100.0f * float(frame) / lt;\n    m_ParticleLifetimePercentage = int(m_ParticleLifetimePercentageReal);\n    m_ParticleLifetimePercentageRemainder = m_ParticleLifetimePercentageReal - float(m_ParticleLifetimePercentage);\n    m_ParticleLifetimePercentage = zeus::clamp(0, m_ParticleLifetimePercentage, 100);\n  }\n\n  const std::array<float, 8>* m_particleAccessParameters = nullptr;\n\n  struct SParticleSystem {\n    FourCC x0_type;\n    CElementGen* x4_system;\n  };\n\n  SParticleSystem* m_currentParticleSystem = nullptr;\n\n  static CParticleGlobals* instance() {\n    if (!g_ParticleGlobals)\n      g_ParticleGlobals.reset(new CParticleGlobals());\n\n    return g_ParticleGlobals.get();\n  }\n};\n\n//struct SParticleInstanceTex {\n//  std::array<zeus::CVector4f, 4> pos;\n//  zeus::CColor color;\n//  std::array<zeus::CVector2f, 4> uvs;\n//};\n//extern std::vector<SParticleInstanceTex> g_instTexData;\n//\n//struct SParticleInstanceIndTex {\n//  std::array<zeus::CVector4f, 4> pos;\n//  zeus::CColor color;\n//  std::array<zeus::CVector4f, 4> texrTindUVs;\n//  zeus::CVector4f sceneUVs;\n//};\n//extern std::vector<SParticleInstanceIndTex> g_instIndTexData;\n//\n//struct SParticleInstanceNoTex {\n//  std::array<zeus::CVector4f, 4> pos;\n//  zeus::CColor color;\n//};\n//extern std::vector<SParticleInstanceNoTex> g_instNoTexData;\n//\n//struct SParticleUniforms {\n//  zeus::CMatrix4f mvp;\n//  zeus::CColor moduColor;\n//};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CParticleSwoosh.cpp",
    "content": "#include \"Runtime/Particle/CParticleSwoosh.hpp\"\n\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Particle/CParticleGlobals.hpp\"\n#include \"Runtime/Particle/CSwooshDescription.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\n#include <chrono>\n\nnamespace metaforce {\n\nint CParticleSwoosh::g_ParticleSystemAliveCount = 0;\n\nCParticleSwoosh::CParticleSwoosh(const TToken<CSwooshDescription>& desc, int leng)\n: x1c_desc(desc)\n, x1c0_rand(x1c_desc->x45_26_CRND ? std::chrono::duration_cast<std::chrono::microseconds>(\n                                        std::chrono::steady_clock::now().time_since_epoch())\n                                        .count()\n                                  : 99) {\n  ++g_ParticleSystemAliveCount;\n\n  if (leng > 0) {\n    x1b4_LENG = leng;\n  } else if (CIntElement* lengElement = x1c_desc->x10_LENG.get()) {\n    lengElement->GetValue(0, x1b4_LENG);\n  }\n  x1b4_LENG += 1;\n\n  if (CIntElement* side = x1c_desc->x18_SIDE.get()) {\n    side->GetValue(0, x1b8_SIDE);\n  }\n\n  x1d0_28_LLRD = x1c_desc->x44_24_LLRD;\n  x1d0_29_VLS1 = x1c_desc->x44_26_VLS1;\n  x1d0_30_VLS2 = x1c_desc->x44_27_VLS2;\n\n  if (IsValid()) {\n    if (CIntElement* pslt = x1c_desc->x0_PSLT.get()) {\n      pslt->GetValue(0, x2c_PSLT);\n    } else {\n      x2c_PSLT = INT_MAX;\n    }\n\n    x1d0_25_AALP = x1c_desc->x44_31_AALP;\n\n    if (CIntElement* spln = x1c_desc->x38_SPLN.get()) {\n      spln->GetValue(0, x1b0_SPLN);\n    }\n    if (x1b0_SPLN < 0) {\n      x1b0_SPLN = 0;\n    }\n\n    x15c_swooshes.clear();\n    x15c_swooshes.reserve(x1b4_LENG);\n    for (int i = 0; i < x1b4_LENG; ++i) {\n      x15c_swooshes.emplace_back(zeus::skZero3f, zeus::skZero3f, 0.f, 0.f, 0, false, zeus::CTransform(), zeus::skZero3f,\n                                 0.f, 0.f, zeus::skClear);\n    }\n\n    CParticleSwoosh::SetOrientation(zeus::CTransform());\n\n    x16c_p0.resize(x1b8_SIDE);\n    x17c_p1.resize(x1b8_SIDE);\n    x18c_p2.resize(x1b8_SIDE);\n    x19c_p3.resize(x1b8_SIDE);\n\n    if (x1c_desc->x44_29_WIRE) {\n      const int maxVerts = x1b4_LENG * (x1b0_SPLN + 1) * x1b8_SIDE * 12;\n      // m_lineRenderer.reset(\n      //     new CLineRenderer(CLineRenderer::EPrimitiveMode::Lines, maxVerts * 2, {}, x1d0_25_AALP));\n    } else {\n      const auto maxVerts = size_t(x1b4_LENG * (x1b0_SPLN + 1) * x1b8_SIDE * 4);\n      // m_cachedVerts.reserve(maxVerts);\n//      CGraphics::CommitResources([&](boo::IGraphicsDataFactory::Context& ctx) {\n//        m_vertBuf = ctx.newDynamicBuffer(boo::BufferUse::Vertex, sizeof(CParticleSwooshShaders::Vert), maxVerts);\n//        m_uniformBuf = ctx.newDynamicBuffer(boo::BufferUse::Uniform, sizeof(zeus::CMatrix4f), 1);\n//        CParticleSwooshShaders::BuildShaderDataBinding(ctx, *this);\n//        return true;\n//      } BooTrace);\n    }\n  }\n}\n\nCParticleSwoosh::~CParticleSwoosh() { --g_ParticleSystemAliveCount; }\n\nvoid CParticleSwoosh::UpdateMaxRadius(float r) { x208_maxRadius = std::max(x208_maxRadius, r); }\n\nvoid CParticleSwoosh::UpdateBounds(const zeus::CVector3f& pos) {\n  x1fc_aabbMax[0] = std::max(pos[0], float(x1fc_aabbMax[0]));\n  x1fc_aabbMax[1] = std::max(pos[1], float(x1fc_aabbMax[1]));\n  x1fc_aabbMax[2] = std::max(pos[2], float(x1fc_aabbMax[2]));\n  x1f0_aabbMin[0] = std::min(pos[0], float(x1f0_aabbMin[0]));\n  x1f0_aabbMin[1] = std::min(pos[1], float(x1f0_aabbMin[1]));\n  x1f0_aabbMin[2] = std::min(pos[2], float(x1f0_aabbMin[2]));\n}\n\nfloat CParticleSwoosh::GetLeftRadius(size_t i) const {\n  float ret = 0.f;\n  if (CRealElement* lrad = x1c_desc->x8_LRAD.get()) {\n    lrad->GetValue(x15c_swooshes[i].x68_frame, ret);\n  }\n  return ret;\n}\n\nfloat CParticleSwoosh::GetRightRadius(size_t i) const {\n  float ret = 0.f;\n  if (CRealElement* rrad = x1c_desc->xc_RRAD.get()) {\n    rrad->GetValue(x15c_swooshes[i].x68_frame, ret);\n  }\n  return ret;\n}\n\nvoid CParticleSwoosh::UpdateSwooshTranslation(const zeus::CVector3f& translation) {\n  x15c_swooshes[x158_curParticle].xc_translation = x11c_invScaleXf * translation;\n}\n\nvoid CParticleSwoosh::UpdateTranslationAndOrientation() {\n  x208_maxRadius = 0.f;\n  x1f0_aabbMin = zeus::CVector3f(FLT_MAX);\n  x1fc_aabbMax = zeus::CVector3f(-FLT_MAX);\n  CParticleGlobals::instance()->SetParticleLifetime(x1b4_LENG);\n  CParticleGlobals::instance()->SetEmitterTime(x28_curFrame);\n\n  for (size_t i = 0; i < x15c_swooshes.size(); ++i) {\n    SSwooshData& swoosh = x15c_swooshes[i];\n    if (!swoosh.x0_active)\n      continue;\n\n    swoosh.x68_frame = x28_curFrame - swoosh.x70_startFrame;\n    CParticleGlobals::instance()->UpdateParticleLifetimeTweenValues(swoosh.x68_frame);\n    if (x1c_desc->x44_28_SROT) {\n      if (CRealElement* irot = x1c_desc->x1c_IROT.get()) {\n        irot->GetValue(x28_curFrame, swoosh.x30_irot);\n      }\n      swoosh.x34_rotm = 0.f;\n    } else {\n      if (CRealElement* rotm = x1c_desc->x20_ROTM.get()) {\n        rotm->GetValue(x28_curFrame, swoosh.x34_rotm);\n      } else {\n        swoosh.x34_rotm = 0.f;\n      }\n    }\n\n    if (CModVectorElement* velm = x1c_desc->x30_VELM.get()) {\n      if (x1d0_29_VLS1) {\n        zeus::CVector3f localVel = x74_invOrientation * swoosh.x74_velocity;\n        zeus::CVector3f localTrans = x74_invOrientation * (swoosh.xc_translation - x38_translation);\n        velm->GetValue(swoosh.x68_frame, localVel, localTrans);\n        swoosh.x74_velocity = x44_orientation * localVel;\n        swoosh.xc_translation = x44_orientation * localTrans + x38_translation;\n      } else {\n        velm->GetValue(swoosh.x68_frame, swoosh.x74_velocity, swoosh.xc_translation);\n      }\n    }\n\n    if (CModVectorElement* vlm2 = x1c_desc->x34_VLM2.get()) {\n      if (x1d0_30_VLS2) {\n        zeus::CVector3f localVel = x74_invOrientation * swoosh.x74_velocity;\n        zeus::CVector3f localTrans = x74_invOrientation * (swoosh.xc_translation - x38_translation);\n        vlm2->GetValue(swoosh.x68_frame, localVel, localTrans);\n        swoosh.x74_velocity = x44_orientation * localVel;\n        swoosh.xc_translation = x44_orientation * localTrans + x38_translation;\n      } else {\n        vlm2->GetValue(swoosh.x68_frame, swoosh.x74_velocity, swoosh.xc_translation);\n      }\n    }\n\n    if (swoosh.x68_frame > 0) {\n      swoosh.xc_translation += swoosh.x74_velocity;\n    }\n\n    if (CVectorElement* npos = x1c_desc->x2c_NPOS.get()) {\n      zeus::CVector3f vec;\n      npos->GetValue(swoosh.x68_frame, vec);\n      swoosh.x24_useOffset = swoosh.x18_offset + vec;\n    }\n\n    if (CColorElement* colr = x1c_desc->x14_COLR.get()) {\n      colr->GetValue(swoosh.x68_frame, swoosh.x6c_color);\n    }\n\n    swoosh.x4_leftRad = GetLeftRadius(i);\n    UpdateMaxRadius(swoosh.x4_leftRad);\n\n    if (x1d0_28_LLRD) {\n      swoosh.x8_rightRad = swoosh.x4_leftRad;\n    } else {\n      swoosh.x8_rightRad = GetRightRadius(i);\n      UpdateMaxRadius(swoosh.x8_rightRad);\n    }\n\n    UpdateBounds(swoosh.xc_translation + swoosh.x24_useOffset);\n  }\n}\n\nbool CParticleSwoosh::Update(double dt) {\n  if (!IsValid()) {\n    return false;\n  }\n\n  CParticleGlobals::instance()->SetParticleLifetime(x1b4_LENG);\n  CParticleGlobals::instance()->SetEmitterTime(x28_curFrame);\n  CParticleGlobals::instance()->UpdateParticleLifetimeTweenValues(0);\n  CGlobalRandom gr(x1c0_rand);\n\n  double evalTime = x28_curFrame / 60.0;\n  float time = 1.f;\n  if (CRealElement* timeElem = x1c_desc->x4_TIME.get()) {\n    timeElem->GetValue(x28_curFrame, time);\n  }\n\n  x30_curTime += std::max(0.0, dt * time);\n  while (x1d0_26_forceOneUpdate || evalTime < x30_curTime) {\n    x1d0_26_forceOneUpdate = false;\n\n    x158_curParticle += 1;\n    if (x158_curParticle >= x15c_swooshes.size()) {\n      x158_curParticle = 0;\n    }\n\n    if (x1d0_24_emitting && x28_curFrame < x2c_PSLT) {\n      UpdateSwooshTranslation(x38_translation);\n\n      if (CRealElement* irot = x1c_desc->x1c_IROT.get()) {\n        irot->GetValue(x28_curFrame, x15c_swooshes[x158_curParticle].x30_irot);\n      } else {\n        x15c_swooshes[x158_curParticle].x30_irot = 0.f;\n      }\n\n      x15c_swooshes[x158_curParticle].x34_rotm = 0.f;\n      x15c_swooshes[x158_curParticle].x70_startFrame = x28_curFrame;\n\n      if (!x15c_swooshes[x158_curParticle].x0_active) {\n        x1ac_particleCount += 1;\n        x15c_swooshes[x158_curParticle].x0_active = true;\n      }\n\n      x15c_swooshes[x158_curParticle].x38_orientation = x44_orientation;\n\n      if (CVectorElement* ivel = x1c_desc->x28_IVEL.get()) {\n        ivel->GetValue(x28_curFrame, x15c_swooshes[x158_curParticle].x74_velocity);\n        x15c_swooshes[x158_curParticle].x74_velocity = x44_orientation * x15c_swooshes[x158_curParticle].x74_velocity;\n      }\n\n      if (CVectorElement* pofs = x1c_desc->x24_POFS.get()) {\n        pofs->GetValue(x28_curFrame, x15c_swooshes[x158_curParticle].x18_offset);\n      }\n\n      x15c_swooshes[x158_curParticle].x24_useOffset = x15c_swooshes[x158_curParticle].x18_offset;\n\n      if (CColorElement* colr = x1c_desc->x14_COLR.get()) {\n        colr->GetValue(x28_curFrame, x15c_swooshes[x158_curParticle].x6c_color);\n      } else {\n        x15c_swooshes[x158_curParticle].x6c_color = zeus::skWhite;\n      }\n\n      int tspn = 0;\n      if (CIntElement* tspnElem = x1c_desc->x40_TSPN.get()) {\n        tspnElem->GetValue(x28_curFrame, tspn);\n      }\n      x1cc_TSPN = float(tspn);\n    } else if (x15c_swooshes[x158_curParticle].x0_active) {\n      x1ac_particleCount = std::max(0, int(x1ac_particleCount) - 1);\n      x15c_swooshes[x158_curParticle].x0_active = false;\n    }\n\n    UpdateTranslationAndOrientation();\n\n    evalTime += (1.0 / 60.0);\n    x28_curFrame += 1;\n  }\n\n  return false;\n}\n\nzeus::CVector3f CParticleSwoosh::GetSplinePoint(const zeus::CVector3f& p0, const zeus::CVector3f& p1,\n                                                const zeus::CVector3f& p2, const zeus::CVector3f& p3, float t) {\n  if (t > 0.f) {\n    return p1;\n  }\n  if (t >= 1.f) {\n    return p2;\n  }\n\n  // Tricubic spline interpolation\n  const float t2 = t * t;\n  const float t3 = t2 * t;\n\n  const float p0Coef = -0.5f * t3 + t2 - 0.5f * t;\n  const float p1Coef = 1.5f * t3 - 2.5f * t2 + 1.f;\n  const float p2Coef = -1.5f * t3 + 2.f * t2 + 0.5f * t;\n  const float p3Coef = 0.5f * t3 + 0.5f * t2;\n\n  return p0 * p0Coef + p1 * p1Coef + p2 * p2Coef + p3 * p3Coef;\n}\n\nint CParticleSwoosh::WrapIndex(int i) const {\n  while (i < 0) {\n    i += x1b4_LENG;\n  }\n  while (i >= x1b4_LENG) {\n    i -= x1b4_LENG;\n  }\n  return i;\n}\n\nvoid CParticleSwoosh::RenderNSidedSpline() {\n  if (x1c_desc->x44_29_WIRE) {\n    x1bc_prim = GX_LINES;\n    // m_lineRenderer->Reset();\n  } else {\n    x1bc_prim = GX_QUADS;\n  }\n\n  bool cros = x1c_desc->x44_25_CROS;\n  if (x1b8_SIDE >= 4 || (x1b8_SIDE & 0x1) != 0) {\n    cros = false;\n  }\n\n  int curIdx = x158_curParticle;\n  for (size_t i = 0; i < x15c_swooshes.size(); ++i) {\n    const bool a0 = x15c_swooshes[WrapIndex(curIdx - 1)].x0_active;\n    const bool a1 = x15c_swooshes[WrapIndex(curIdx)].x0_active;\n    if (!a1 || (a1 && !a0)) {\n      curIdx -= 1;\n      if (curIdx < 0) {\n        curIdx = x15c_swooshes.size() - 1;\n      }\n      continue;\n    }\n\n    const SSwooshData& refSwoosh = x15c_swooshes[curIdx];\n\n    const float sideDiv = 360.f / float(x1b8_SIDE);\n    for (int j = 0; j < 4; ++j) {\n      int crossRefIdx = 0;\n      if (j == 0) {\n        crossRefIdx = WrapIndex(curIdx + 1);\n        if (!x15c_swooshes[crossRefIdx].x0_active) {\n          crossRefIdx = curIdx;\n        }\n      } else if (j == 1) {\n        crossRefIdx = WrapIndex(curIdx);\n      } else if (j == 2) {\n        crossRefIdx = WrapIndex(curIdx - 1);\n      } else if (j == 3) {\n        crossRefIdx = WrapIndex(curIdx - 2);\n        if (!x15c_swooshes[crossRefIdx].x0_active) {\n          crossRefIdx = WrapIndex(curIdx - 1);\n        }\n      }\n\n      if (x1b4_LENG == 2) {\n        if (j == 0) {\n          crossRefIdx = WrapIndex(curIdx);\n        }\n        if (j == 3) {\n          crossRefIdx = WrapIndex(curIdx - 1);\n        }\n      } else if (x158_curParticle == curIdx && j == 0) {\n        crossRefIdx = x158_curParticle;\n      } else {\n        if (WrapIndex(x158_curParticle + 2) == curIdx && j == 3) {\n          crossRefIdx = WrapIndex(x158_curParticle + 1);\n        } else if (x1ac_particleCount - 2 == i && j == 3) {\n          crossRefIdx = 0;\n        }\n      }\n\n      const SSwooshData& crossSwoosh = x15c_swooshes[crossRefIdx];\n      for (int k = 0; k < x1b8_SIDE; ++k) {\n        const float n = sideDiv * k;\n        float ang = zeus::degToRad(n + crossSwoosh.x30_irot + crossSwoosh.x34_rotm);\n        if (std::fabs(ang) > M_PIF) {\n          ang -= std::floor(ang / (2.f * M_PIF)) * 2.f * M_PIF;\n          if (ang > M_PIF) {\n            ang -= 2.f * M_PIF;\n          } else if (ang < -M_PIF) {\n            ang += 2.f * M_PIF;\n          }\n        }\n\n        const float z = std::sin(ang);\n        const float x = std::cos(ang);\n\n        const float rad = (n > 0.f && n <= 180.f) ? crossSwoosh.x4_leftRad : crossSwoosh.x8_rightRad;\n        const zeus::CVector3f offset = crossSwoosh.xc_translation + crossSwoosh.x24_useOffset;\n\n        if (j == 0) {\n          x16c_p0[k] = crossSwoosh.x38_orientation * zeus::CVector3f(rad * x, 0.f, rad * z) + offset;\n        } else if (j == 1) {\n          x17c_p1[k] = crossSwoosh.x38_orientation * zeus::CVector3f(rad * x, 0.f, rad * z) + offset;\n        } else if (j == 2) {\n          x18c_p2[k] = crossSwoosh.x38_orientation * zeus::CVector3f(rad * x, 0.f, rad * z) + offset;\n        } else if (j == 3) {\n          x19c_p3[k] = crossSwoosh.x38_orientation * zeus::CVector3f(rad * x, 0.f, rad * z) + offset;\n        }\n      }\n    }\n\n    if (x1c_desc->x3c_TEXR) {\n      if (x1ec_TSPN > 0) {\n        x1d4_uvs.xMin = float((i % x1ec_TSPN) * x1e8_uvSpan);\n      } else {\n        x1d4_uvs.xMin = float(i * x1e8_uvSpan);\n      }\n    }\n\n    const float segUvSpan = x1e8_uvSpan / float(x1b0_SPLN + 1);\n    for (int j = 0; j < x1b0_SPLN + 1; ++j) {\n      const float t0 = j / float(x1b0_SPLN + 1);\n      const float t1 = (j + 1) / float(x1b0_SPLN + 1);\n      int faces = x1b8_SIDE;\n      if (x1b8_SIDE <= 2) {\n        faces = 1;\n      } else if (cros) {\n        faces = x1b8_SIDE / 2;\n      }\n\n      x1d4_uvs.xMax = x1d4_uvs.xMin + segUvSpan;\n\n      for (int k = 0; k < faces; ++k) {\n        int otherK = k + 1;\n        if (k + 1 >= x1b8_SIDE) {\n          otherK = 0;\n        }\n\n        const zeus::CColor color = refSwoosh.x6c_color * x20c_moduColor;\n        if (cros) {\n          otherK = k + x1b8_SIDE / 2;\n          const auto v0 = GetSplinePoint(x16c_p0[k], x17c_p1[k], x18c_p2[k], x19c_p3[k], t0);\n          const auto v1 = GetSplinePoint(x16c_p0[otherK], x17c_p1[otherK], x18c_p2[otherK], x19c_p3[otherK], t0);\n          const auto v2 = GetSplinePoint(x16c_p0[otherK], x17c_p1[otherK], x18c_p2[otherK], x19c_p3[otherK], t1);\n          const auto v3 = GetSplinePoint(x16c_p0[k], x17c_p1[k], x18c_p2[k], x19c_p3[k], t1);\n\n          // m_cachedVerts.push_back({v0, {x1d4_uvs.xMin, x1d4_uvs.yMin}, color});\n          // m_cachedVerts.push_back({v1, {x1d4_uvs.xMin, x1d4_uvs.yMax}, color});\n          // m_cachedVerts.push_back({v2, {x1d4_uvs.xMax, x1d4_uvs.yMin}, color});\n          // m_cachedVerts.push_back({v3, {x1d4_uvs.xMax, x1d4_uvs.yMax}, color});\n//          CGraphics::DrawArray(m_cachedVerts.size() - 4, 4);\n        } else {\n          const auto v0 = GetSplinePoint(x16c_p0[k], x17c_p1[k], x18c_p2[k], x19c_p3[k], t0);\n          const auto v1 = GetSplinePoint(x16c_p0[otherK], x17c_p1[otherK], x18c_p2[otherK], x19c_p3[otherK], t0);\n          const auto v2 = GetSplinePoint(x16c_p0[otherK], x17c_p1[otherK], x18c_p2[otherK], x19c_p3[otherK], t1);\n          const auto v3 = GetSplinePoint(x16c_p0[k], x17c_p1[k], x18c_p2[k], x19c_p3[k], t1);\n\n          if (x1bc_prim == GX_LINES) {\n            // m_lineRenderer->AddVertex(v0, color, 1.f);\n            // m_lineRenderer->AddVertex(v1, color, 1.f);\n            // m_lineRenderer->AddVertex(v1, color, 1.f);\n            // m_lineRenderer->AddVertex(v2, color, 1.f);\n            // m_lineRenderer->AddVertex(v2, color, 1.f);\n            // m_lineRenderer->AddVertex(v0, color, 1.f);\n            // m_lineRenderer->AddVertex(v0, color, 1.f);\n            // m_lineRenderer->AddVertex(v2, color, 1.f);\n            // m_lineRenderer->AddVertex(v2, color, 1.f);\n            // m_lineRenderer->AddVertex(v3, color, 1.f);\n            // m_lineRenderer->AddVertex(v3, color, 1.f);\n            // m_lineRenderer->AddVertex(v0, color, 1.f);\n          } else if (x1bc_prim == GX_QUADS) {\n            // m_cachedVerts.push_back({v0, {x1d4_uvs.xMin, x1d4_uvs.yMin}, color});\n            // m_cachedVerts.push_back({v1, {x1d4_uvs.xMin, x1d4_uvs.yMax}, color});\n            // m_cachedVerts.push_back({v2, {x1d4_uvs.xMax, x1d4_uvs.yMin}, color});\n            // m_cachedVerts.push_back({v3, {x1d4_uvs.xMax, x1d4_uvs.yMax}, color});\n//            CGraphics::DrawArray(m_cachedVerts.size() - 4, 4);\n          }\n        }\n      }\n\n      if (x1c_desc->x3c_TEXR && x1b0_SPLN > 0) {\n        x1d4_uvs.xMin += segUvSpan;\n      }\n    }\n\n    curIdx -= 1;\n    if (curIdx < 0) {\n      curIdx = x15c_swooshes.size() - 1;\n    }\n  }\n\n  if (x1bc_prim == GX_LINES) {\n    // m_lineRenderer->Render(g_Renderer->IsThermalVisorHotPass());\n  }\n}\n\nvoid CParticleSwoosh::RenderNSidedNoSpline() { RenderNSidedSpline(); }\n\nvoid CParticleSwoosh::Render3SidedSolidSpline() {\n  if (x15c_swooshes.size() < 2) {\n    return;\n  }\n\n  int curIdx = x158_curParticle;\n  float curUvSpan = -x1e8_uvSpan;\n  zeus::CColor prevColor0 = zeus::skClear;\n  for (size_t i = 0; i < x15c_swooshes.size(); ++i) {\n    const SSwooshData& swoosh = x15c_swooshes[curIdx];\n\n    curIdx -= 1;\n    if (curIdx < 0) {\n      curIdx = x15c_swooshes.size() - 1;\n    }\n\n    float ang1 = zeus::degToRad(swoosh.x30_irot + swoosh.x34_rotm);\n    if (std::fabs(ang1) > M_PIF) {\n      ang1 -= std::floor(ang1 / (2.f * M_PIF)) * 2.f * M_PIF;\n      if (ang1 > M_PIF) {\n        ang1 -= 2.f * M_PIF;\n      } else if (ang1 < -M_PIF) {\n        ang1 += 2.f * M_PIF;\n      }\n    }\n\n    const zeus::CVector3f ang1Vec(std::sin(ang1) * swoosh.x4_leftRad, 0.f, std::cos(ang1) * swoosh.x4_leftRad);\n\n    float ang2 = ang1 + 2.0943952f; // +120 degrees\n    if (ang2 > M_PIF) {\n      ang2 -= 2.f * M_PIF;\n    }\n\n    const zeus::CVector3f ang2Vec(std::sin(ang2) * swoosh.x4_leftRad, 0.f, std::cos(ang2) * swoosh.x4_leftRad);\n\n    float ang3 = ang2 + 2.0943952f; // +120 degrees\n    if (ang3 > M_PIF) {\n      ang3 -= 2.f * M_PIF;\n    }\n\n    const zeus::CVector3f ang3Vec(std::sin(ang3) * swoosh.x4_leftRad, 0.f, std::cos(ang3) * swoosh.x4_leftRad);\n\n    if (i == 2) {\n      x19c_p3[0] = x17c_p1[0] * 2.f - x16c_p0[0];\n      x19c_p3[1] = x17c_p1[1] * 2.f - x16c_p0[1];\n      x19c_p3[2] = x17c_p1[2] * 2.f - x16c_p0[2];\n    } else {\n      x19c_p3[0] = x18c_p2[0];\n      x19c_p3[1] = x18c_p2[1];\n      x19c_p3[2] = x18c_p2[2];\n    }\n\n    x18c_p2[0] = x17c_p1[0];\n    x18c_p2[1] = x17c_p1[1];\n    x18c_p2[2] = x17c_p1[2];\n\n    x17c_p1[0] = x16c_p0[0];\n    x17c_p1[1] = x16c_p0[1];\n    x17c_p1[2] = x16c_p0[2];\n\n    const zeus::CVector3f useOffset = swoosh.xc_translation + swoosh.x24_useOffset;\n    x16c_p0[0] = swoosh.x38_orientation * ang1Vec + useOffset;\n    x16c_p0[1] = swoosh.x38_orientation * ang2Vec + useOffset;\n    x16c_p0[2] = swoosh.x38_orientation * ang3Vec + useOffset;\n\n    const zeus::CColor useColor0 = prevColor0;\n\n    if (swoosh.x0_active) {\n      const zeus::CColor prevColor1 = prevColor0;\n      prevColor0 = swoosh.x6c_color * x20c_moduColor;\n      float prevUvSpan = curUvSpan;\n      curUvSpan += x1e8_uvSpan;\n      if (i > 1) {\n        // int vertCount = (x1b0_SPLN + 1) * 12;\n        float uv1 = 0.f;\n        zeus::CColor useColor1 = prevColor1;\n        zeus::CVector3f v01 = zeus::skZero3f;\n        zeus::CVector3f v11 = zeus::skZero3f;\n        zeus::CVector3f v21 = zeus::skZero3f;\n        zeus::CColor c1 = zeus::skClear;\n        float uvDelta = prevUvSpan - curUvSpan;\n        for (int j = 0; j < x1b0_SPLN + 1; ++j) {\n          float uv0 = uv1;\n          float t1 = (j + 1) / float(x1b0_SPLN + 1);\n          zeus::CVector3f v00 = v01;\n          zeus::CVector3f v10 = v11;\n          zeus::CVector3f v20 = v21;\n          zeus::CColor c0 = c1;\n\n          if (j == 0) {\n            float t0 = j / float(x1b0_SPLN + 1);\n            v00 = GetSplinePoint(x16c_p0[0], x17c_p1[0], x18c_p2[0], x19c_p3[0], t0);\n            v10 = GetSplinePoint(x16c_p0[1], x17c_p1[1], x18c_p2[1], x19c_p3[1], t0);\n            v20 = GetSplinePoint(x16c_p0[2], x17c_p1[2], x18c_p2[2], x19c_p3[2], t0);\n            c0 = zeus::CColor::lerp(useColor0, useColor1, t0);\n            uv0 = t0 * uvDelta + curUvSpan;\n          }\n\n          v01 = GetSplinePoint(x16c_p0[0], x17c_p1[0], x18c_p2[0], x19c_p3[0], t1);\n          v11 = GetSplinePoint(x16c_p0[1], x17c_p1[1], x18c_p2[1], x19c_p3[1], t1);\n          v21 = GetSplinePoint(x16c_p0[2], x17c_p1[2], x18c_p2[2], x19c_p3[2], t1);\n          c1 = zeus::CColor::lerp(useColor0, useColor1, t1);\n          uv1 = t1 * uvDelta + curUvSpan;\n\n          // m_cachedVerts.push_back({v00, {uv0, x1d4_uvs.yMin}, c0});\n          // m_cachedVerts.push_back({v10, {uv0, x1d4_uvs.yMax}, c0});\n          // m_cachedVerts.push_back({v01, {uv1, x1d4_uvs.yMin}, c1});\n          // m_cachedVerts.push_back({v11, {uv1, x1d4_uvs.yMax}, c1});\n//          CGraphics::DrawArray(m_cachedVerts.size() - 4, 4);\n\n          // m_cachedVerts.push_back({v10, {uv0, x1d4_uvs.yMin}, c0});\n          // m_cachedVerts.push_back({v20, {uv0, x1d4_uvs.yMax}, c0});\n          // m_cachedVerts.push_back({v11, {uv1, x1d4_uvs.yMin}, c1});\n          // m_cachedVerts.push_back({v21, {uv1, x1d4_uvs.yMax}, c1});\n//          CGraphics::DrawArray(m_cachedVerts.size() - 4, 4);\n\n          // m_cachedVerts.push_back({v20, {uv0, x1d4_uvs.yMin}, c0});\n          // m_cachedVerts.push_back({v00, {uv0, x1d4_uvs.yMax}, c0});\n          // m_cachedVerts.push_back({v21, {uv1, x1d4_uvs.yMin}, c1});\n          // m_cachedVerts.push_back({v01, {uv1, x1d4_uvs.yMax}, c1});\n//          CGraphics::DrawArray(m_cachedVerts.size() - 4, 4);\n        }\n      }\n    }\n  }\n}\n\nvoid CParticleSwoosh::Render3SidedSolidNoSplineNoGaps() {\n  if (x15c_swooshes.size() < 2) {\n    return;\n  }\n\n  std::array<zeus::CVector3f, 2> p0;\n  std::array<zeus::CVector3f, 2> p1;\n  std::array<zeus::CVector3f, 2> p2;\n\n  int curIdx = x158_curParticle;\n  bool lastActive = false;\n  zeus::CColor c0 = zeus::skClear;\n  float uv0 = -x1e8_uvSpan;\n  for (size_t i = 0; i < x15c_swooshes.size(); ++i) {\n    const SSwooshData& swoosh = x15c_swooshes[curIdx];\n\n    curIdx -= 1;\n    if (curIdx < 0) {\n      curIdx = x15c_swooshes.size() - 1;\n    }\n\n    float ang1 = zeus::degToRad(swoosh.x30_irot + swoosh.x34_rotm);\n    if (std::fabs(ang1) > M_PIF) {\n      ang1 -= std::floor(ang1 / (2.f * M_PIF)) * 2.f * M_PIF;\n      if (ang1 > M_PIF) {\n        ang1 -= 2.f * M_PIF;\n      } else if (ang1 < -M_PIF) {\n        ang1 += 2.f * M_PIF;\n      }\n    }\n\n    const zeus::CVector3f ang1Vec(std::sin(ang1) * swoosh.x4_leftRad, 0.f, std::cos(ang1) * swoosh.x4_leftRad);\n\n    float ang2 = ang1 + 2.0943952f; // +120 degrees\n    if (ang2 > M_PIF) {\n      ang2 -= 2.f * M_PIF;\n    }\n\n    const zeus::CVector3f ang2Vec(std::sin(ang2) * swoosh.x4_leftRad, 0.f, std::cos(ang2) * swoosh.x4_leftRad);\n\n    float ang3 = ang2 + 2.0943952f; // +120 degrees\n    if (ang3 > M_PIF) {\n      ang3 -= 2.f * M_PIF;\n    }\n\n    const zeus::CVector3f ang3Vec(std::sin(ang3) * swoosh.x4_leftRad, 0.f, std::cos(ang3) * swoosh.x4_leftRad);\n\n    const zeus::CVector3f useOffset = swoosh.xc_translation + swoosh.x24_useOffset;\n    p0[i & 1] = swoosh.x38_orientation * ang1Vec + useOffset;\n    p1[i & 1] = swoosh.x38_orientation * ang2Vec + useOffset;\n    p2[i & 1] = swoosh.x38_orientation * ang3Vec + useOffset;\n\n    if (!swoosh.x0_active) {\n      lastActive = false;\n      continue;\n    }\n\n    if (!lastActive) {\n      lastActive = true;\n      continue;\n    }\n\n    lastActive = true;\n    const zeus::CColor c1 = c0;\n    c0 = swoosh.x6c_color * x20c_moduColor;\n\n    const float uv1 = uv0;\n    uv0 += x1e8_uvSpan;\n\n    // m_cachedVerts.push_back({p0[i & 1], {uv0, x1d4_uvs.yMin}, c0});\n    // m_cachedVerts.push_back({p1[i & 1], {uv0, x1d4_uvs.yMax}, c0});\n    // m_cachedVerts.push_back({p0[!(i & 1)], {uv1, x1d4_uvs.yMin}, c1});\n    // m_cachedVerts.push_back({p1[!(i & 1)], {uv1, x1d4_uvs.yMax}, c1});\n//    CGraphics::DrawArray(m_cachedVerts.size() - 4, 4);\n\n    // m_cachedVerts.push_back({p1[i & 1], {uv0, x1d4_uvs.yMin}, c0});\n    // m_cachedVerts.push_back({p2[i & 1], {uv0, x1d4_uvs.yMax}, c0});\n    // m_cachedVerts.push_back({p1[!(i & 1)], {uv1, x1d4_uvs.yMin}, c1});\n    // m_cachedVerts.push_back({p2[!(i & 1)], {uv1, x1d4_uvs.yMax}, c1});\n//    CGraphics::DrawArray(m_cachedVerts.size() - 4, 4);\n\n    // m_cachedVerts.push_back({p2[i & 1], {uv0, x1d4_uvs.yMin}, c0});\n    // m_cachedVerts.push_back({p0[i & 1], {uv0, x1d4_uvs.yMax}, c0});\n    // m_cachedVerts.push_back({p2[!(i & 1)], {uv1, x1d4_uvs.yMin}, c1});\n    // m_cachedVerts.push_back({p0[!(i & 1)], {uv1, x1d4_uvs.yMax}, c1});\n//    CGraphics::DrawArray(m_cachedVerts.size() - 4, 4);\n  }\n}\n\nvoid CParticleSwoosh::Render2SidedSpline() { RenderNSidedSpline(); }\n\nvoid CParticleSwoosh::Render2SidedNoSplineGaps() {\n  int drawStart = 0;\n  bool streaming = false;\n  int curIdx = x158_curParticle;\n  for (size_t i = 0; i < x15c_swooshes.size(); ++i) {\n    const SSwooshData& swoosh = x15c_swooshes[curIdx];\n    const bool otherActive = x15c_swooshes[WrapIndex(curIdx - 1)].x0_active;\n\n    curIdx -= 1;\n    if (curIdx < 0) {\n      curIdx = x15c_swooshes.size() - 2;\n    }\n\n    if (!swoosh.x0_active) {\n      if (streaming) {\n        streaming = false;\n//        CGraphics::DrawArray(drawStart, m_cachedVerts.size() - drawStart);\n      }\n      continue;\n    }\n\n    if (!streaming) {\n      if (!otherActive) {\n        continue;\n      }\n      if (i >= x15c_swooshes.size() - 2) {\n        continue;\n      }\n      streaming = true;\n      // drawStart = m_cachedVerts.size();\n    }\n\n    float ang = zeus::degToRad(swoosh.x30_irot + swoosh.x34_rotm);\n    if (std::fabs(ang) > M_PIF) {\n      ang -= std::floor(ang / (2.f * M_PIF)) * 2.f * M_PIF;\n      if (ang > M_PIF) {\n        ang -= 2.f * M_PIF;\n      } else if (ang < -M_PIF) {\n        ang += 2.f * M_PIF;\n      }\n    }\n\n    const float sinAng = std::sin(ang);\n    const float cosAng = std::cos(ang);\n\n    const zeus::CVector3f useOffset = swoosh.xc_translation + swoosh.x24_useOffset;\n    const zeus::CVector3f v0 =\n        swoosh.x38_orientation * zeus::CVector3f(cosAng * swoosh.x4_leftRad, 0.f, sinAng * swoosh.x4_leftRad) +\n        useOffset;\n    const zeus::CVector3f v1 =\n        swoosh.x38_orientation * zeus::CVector3f(-cosAng * swoosh.x8_rightRad, 0.f, -sinAng * swoosh.x8_rightRad) +\n        useOffset;\n\n    const zeus::CColor color = swoosh.x6c_color * x20c_moduColor;\n\n    // m_cachedVerts.push_back({v0, {1.f, x1d4_uvs.yMin}, color});\n    // m_cachedVerts.push_back({v1, {1.f, x1d4_uvs.yMax}, color});\n    // m_cachedVerts.push_back({v0, {0.f, x1d4_uvs.yMin}, color});\n    // m_cachedVerts.push_back({v1, {0.f, x1d4_uvs.yMax}, color});\n  }\n\n//  if (streaming)\n//    CGraphics::DrawArray(drawStart, m_cachedVerts.size() - drawStart);\n}\n\nvoid CParticleSwoosh::Render2SidedNoSplineNoGaps() {\n  int drawStart = 0;\n  int curIdx = x158_curParticle;\n  int particleCount = x1ac_particleCount;\n  float uvOffset = 0.f;\n\n  if (x1c_desc->x3c_TEXR) {\n    if (x1c_desc->x45_25_ORNT) {\n      const zeus::CVector3f camToParticle =\n          ((zeus::CTransform::Translate(xa4_globalTranslation) * xb0_globalOrientation * xec_scaleXf).inverse() *\n           CGraphics::mViewMatrix)\n              .origin;\n      zeus::CVector3f dotVec = zeus::skZero3f;\n\n      for (size_t i = 0; i < x15c_swooshes.size(); ++i) {\n        const SSwooshData& swoosh = x15c_swooshes[curIdx];\n\n        curIdx -= 1;\n        if (curIdx < 0) {\n          curIdx = x15c_swooshes.size() - 1;\n        }\n\n        if (swoosh.x0_active) {\n          particleCount -= 1;\n          int otherIdx = curIdx - 1;\n          if (otherIdx < 0) {\n            otherIdx = x15c_swooshes.size() - 1;\n          }\n\n          const SSwooshData& otherSwoosh = x15c_swooshes[otherIdx];\n\n          zeus::CVector3f delta = otherSwoosh.xc_translation - swoosh.xc_translation;\n          if (otherIdx == x158_curParticle) {\n            delta = swoosh.xc_translation - x15c_swooshes[(curIdx + 1) % x15c_swooshes.size()].xc_translation;\n          }\n\n          if (delta.canBeNormalized()) {\n            zeus::CVector3f deltaCross = delta.cross(camToParticle - swoosh.xc_translation);\n            if (deltaCross.canBeNormalized()) {\n              deltaCross.normalize();\n              dotVec = (deltaCross.dot(dotVec) < 0.f ? -1.f : 1.f) * deltaCross;\n              const zeus::CVector3f useOffset = swoosh.xc_translation + swoosh.x24_useOffset;\n              const zeus::CVector3f v0 = dotVec * swoosh.x4_leftRad + useOffset;\n              const zeus::CVector3f v1 = dotVec * -swoosh.x8_rightRad + useOffset;\n\n              const zeus::CColor color = swoosh.x6c_color * x20c_moduColor;\n\n              // m_cachedVerts.push_back({v0, {uvOffset, x1d4_uvs.yMin}, color});\n              // m_cachedVerts.push_back({v1, {uvOffset, x1d4_uvs.yMax}, color});\n              if (uvOffset >= 1.f && particleCount) {\n//                CGraphics::DrawArray(drawStart, m_cachedVerts.size() - drawStart);\n                // drawStart = m_cachedVerts.size();\n                uvOffset -= 1.f;\n                // m_cachedVerts.push_back({v0, {uvOffset, x1d4_uvs.yMin}, color});\n                // m_cachedVerts.push_back({v1, {uvOffset, x1d4_uvs.yMax}, color});\n              }\n\n              if (x1ec_TSPN > 0) {\n                uvOffset += x1e8_uvSpan;\n              } else {\n                uvOffset = float(i * x1e8_uvSpan);\n              }\n            }\n          }\n        }\n      }\n    } else {\n      for (size_t i = 0; i < x15c_swooshes.size(); ++i) {\n        const SSwooshData& swoosh = x15c_swooshes[curIdx];\n\n        curIdx -= 1;\n        if (curIdx < 0) {\n          curIdx = x15c_swooshes.size() - 1;\n        }\n\n        if (swoosh.x0_active) {\n          particleCount -= 1;\n\n          float ang = zeus::degToRad(swoosh.x30_irot + swoosh.x34_rotm);\n          if (std::fabs(ang) > M_PIF) {\n            ang -= std::floor(ang / (2.f * M_PIF)) * 2.f * M_PIF;\n            if (ang > M_PIF) {\n              ang -= 2.f * M_PIF;\n            } else if (ang < -M_PIF) {\n              ang += 2.f * M_PIF;\n            }\n          }\n\n          float sinAng = std::sin(ang);\n          float cosAng = std::cos(ang);\n\n          const zeus::CVector3f useOffset = swoosh.xc_translation + swoosh.x24_useOffset;\n          const zeus::CVector3f v0 =\n              swoosh.x38_orientation * zeus::CVector3f(cosAng * swoosh.x4_leftRad, 0.f, sinAng * swoosh.x4_leftRad) +\n              useOffset;\n          const zeus::CVector3f v1 = swoosh.x38_orientation * zeus::CVector3f(-cosAng * swoosh.x8_rightRad, 0.f,\n                                                                              -sinAng * swoosh.x8_rightRad) +\n                                     useOffset;\n\n          const zeus::CColor color = swoosh.x6c_color * x20c_moduColor;\n\n          // m_cachedVerts.push_back({v0, {uvOffset, x1d4_uvs.yMin}, color});\n          // m_cachedVerts.push_back({v1, {uvOffset, x1d4_uvs.yMax}, color});\n          if (uvOffset >= 1.f && particleCount) {\n//            CGraphics::DrawArray(drawStart, m_cachedVerts.size() - drawStart);\n            // drawStart = m_cachedVerts.size();\n            uvOffset -= 1.f;\n            // m_cachedVerts.push_back({v0, {uvOffset, x1d4_uvs.yMin}, color});\n            // m_cachedVerts.push_back({v1, {uvOffset, x1d4_uvs.yMax}, color});\n          }\n\n          if (x1ec_TSPN > 0) {\n            uvOffset += x1e8_uvSpan;\n          } else {\n            uvOffset = float(i * x1e8_uvSpan);\n          }\n        }\n      }\n    }\n  } else {\n    for (size_t i = 0; i < x15c_swooshes.size(); ++i) {\n      const SSwooshData& swoosh = x15c_swooshes[curIdx];\n\n      curIdx -= 1;\n      if (curIdx < 0) {\n        curIdx = x15c_swooshes.size() - 1;\n      }\n\n      if (swoosh.x0_active) {\n        float ang = zeus::degToRad(swoosh.x30_irot + swoosh.x34_rotm);\n        if (std::fabs(ang) > M_PIF) {\n          ang -= std::floor(ang / (2.f * M_PIF)) * 2.f * M_PIF;\n          if (ang > M_PIF) {\n            ang -= 2.f * M_PIF;\n          } else if (ang < -M_PIF) {\n            ang += 2.f * M_PIF;\n          }\n        }\n\n        const float sinAng = std::sin(ang);\n        const float cosAng = std::cos(ang);\n\n        const zeus::CVector3f useOffset = swoosh.xc_translation + swoosh.x24_useOffset;\n        const zeus::CVector3f v0 =\n            swoosh.x38_orientation * zeus::CVector3f(cosAng * swoosh.x4_leftRad, 0.f, sinAng * swoosh.x4_leftRad) +\n            useOffset;\n        const zeus::CVector3f v1 =\n            swoosh.x38_orientation * zeus::CVector3f(-cosAng * swoosh.x8_rightRad, 0.f, -sinAng * swoosh.x8_rightRad) +\n            useOffset;\n\n        const zeus::CColor color = swoosh.x6c_color * x20c_moduColor;\n        // m_cachedVerts.push_back({v0, {}, color});\n        // m_cachedVerts.push_back({v1, {}, color});\n      }\n    }\n  }\n\n//  CGraphics::DrawArray(drawStart, m_cachedVerts.size() - drawStart);\n}\n\nvoid CParticleSwoosh::Render() {\n  if (x1b4_LENG < 2 || x1ac_particleCount <= 1) {\n    return;\n  }\n\n  SCOPED_GRAPHICS_DEBUG_GROUP(fmt::format(\"CParticleSwoosh::Render {}\", *x1c_desc.GetObjectTag()).c_str(),\n                              zeus::skYellow);\n\n  // m_cachedVerts.clear();\n//  if (m_dataBind[0]) {\n//    CGraphics::SetShaderDataBinding(m_dataBind[g_Renderer->IsThermalVisorHotPass()]);\n//  }\n\n  CParticleGlobals::instance()->SetParticleLifetime(x1b4_LENG);\n  CGlobalRandom gr(x1c0_rand);\n  CGraphics::DisableAllLights();\n  // Z-test, Z-update if x45_24_ZBUF\n  // Additive if x1d0_25_AALP, otherwise alpha blend\n\n  CGraphics::SetModelMatrix(zeus::CTransform::Translate(xa4_globalTranslation) * xb0_globalOrientation * xec_scaleXf *\n                            zeus::CTransform::Scale(x14c_localScale));\n\n  // Disable face culling\n\n  if (CUVElement* texr = x1c_desc->x3c_TEXR.get()) {\n    TLockedToken<CTexture> tex = texr->GetValueTexture(x28_curFrame);\n    // Load tex\n    x1e4_tex = tex.GetObj();\n\n    texr->GetValueUV(x28_curFrame, x1d4_uvs);\n\n    x1d0_31_constantTex = texr->HasConstantTexture();\n    x1d1_24_constantUv = texr->HasConstantUV();\n\n    if (CIntElement* tspn = x1c_desc->x40_TSPN.get()) {\n      tspn->GetValue(x28_curFrame, x1ec_TSPN);\n    }\n\n    if (x1ec_TSPN <= 0) {\n      x1ec_TSPN = x15c_swooshes.size() - 1;\n    }\n\n    x1e8_uvSpan = 1.f;\n    if (x1ec_TSPN > 0) {\n      x1e8_uvSpan = 1.f / float(x1ec_TSPN);\n    }\n\n    // TEV0 modulate\n  } else {\n    // TEV0 passthru\n  }\n\n  // TEV1 passthru\n\n  if (x1b8_SIDE == 2) {\n    if (x1b0_SPLN <= 0) {\n      if (x1d0_27_renderGaps) {\n        Render2SidedNoSplineGaps();\n      } else {\n        Render2SidedNoSplineNoGaps();\n      }\n    } else {\n      Render2SidedSpline();\n    }\n  } else if (x1b8_SIDE == 3) {\n    if (x1b0_SPLN > 0) {\n      Render3SidedSolidSpline();\n    } else {\n      Render3SidedSolidNoSplineNoGaps();\n    }\n  } else {\n    if (x1b0_SPLN > 0) {\n      RenderNSidedSpline();\n    } else {\n      RenderNSidedNoSpline();\n    }\n  }\n\n  // zeus::CMatrix4f mvp = CGraphics::GetPerspectiveProjectionMatrix(/*true*/) * CGraphics::g_GXModelView.toMatrix4f();\n//  m_uniformBuf->load(&mvp, sizeof(zeus::CMatrix4f));\n//  if (m_cachedVerts.size()) {\n//    m_vertBuf->load(m_cachedVerts.data(), m_cachedVerts.size() * sizeof(CParticleSwooshShaders::Vert));\n//  }\n}\n\nvoid CParticleSwoosh::SetOrientation(const zeus::CTransform& xf) {\n  x44_orientation = xf;\n  x74_invOrientation = xf.inverse();\n  x15c_swooshes[x158_curParticle].x38_orientation = xf;\n}\n\nvoid CParticleSwoosh::SetTranslation(const zeus::CVector3f& translation) {\n  x38_translation = translation;\n  UpdateSwooshTranslation(x38_translation);\n}\n\nvoid CParticleSwoosh::SetGlobalOrientation(const zeus::CTransform& xf) { xb0_globalOrientation = xf.getRotation(); }\n\nvoid CParticleSwoosh::SetGlobalTranslation(const zeus::CVector3f& translation) { xa4_globalTranslation = translation; }\n\nvoid CParticleSwoosh::SetGlobalScale(const zeus::CVector3f& scale) {\n  xe0_globalScale = scale;\n  xec_scaleXf = zeus::CTransform::Scale(scale);\n  x11c_invScaleXf = zeus::CTransform::Scale(1.f / scale);\n}\n\nvoid CParticleSwoosh::SetLocalScale(const zeus::CVector3f& scale) { x14c_localScale = scale; }\n\nvoid CParticleSwoosh::SetParticleEmission(bool e) { x1d0_24_emitting = e; }\n\nvoid CParticleSwoosh::SetModulationColor(const zeus::CColor& color) { x20c_moduColor = color; }\n\nconst zeus::CTransform& CParticleSwoosh::GetOrientation() const { return x44_orientation; }\n\nconst zeus::CVector3f& CParticleSwoosh::GetTranslation() const { return x38_translation; }\n\nconst zeus::CTransform& CParticleSwoosh::GetGlobalOrientation() const { return xb0_globalOrientation; }\n\nconst zeus::CVector3f& CParticleSwoosh::GetGlobalTranslation() const { return xa4_globalTranslation; }\n\nconst zeus::CVector3f& CParticleSwoosh::GetGlobalScale() const { return xe0_globalScale; }\n\nconst zeus::CColor& CParticleSwoosh::GetModulationColor() const { return x20c_moduColor; }\n\nbool CParticleSwoosh::IsSystemDeletable() const {\n  if (x1d0_24_emitting && x28_curFrame < x2c_PSLT) {\n    return false;\n  }\n\n  return GetParticleCount() < 2;\n}\n\nstd::optional<zeus::CAABox> CParticleSwoosh::GetBounds() const {\n  if (GetParticleCount() <= 1) {\n    const zeus::CVector3f trans = x38_translation + xa4_globalTranslation;\n    return zeus::CAABox(trans, trans);\n  } else {\n    const zeus::CTransform xf =\n        zeus::CTransform::Translate(xa4_globalTranslation) * xb0_globalOrientation * xec_scaleXf;\n    return zeus::CAABox(x1f0_aabbMin - x208_maxRadius, x1fc_aabbMax + x208_maxRadius).getTransformedAABox(xf);\n  }\n}\n\nu32 CParticleSwoosh::GetParticleCount() const { return x1ac_particleCount; }\n\nbool CParticleSwoosh::SystemHasLight() const { return false; }\n\nCLight CParticleSwoosh::GetLight() const { return CLight::BuildLocalAmbient(zeus::skZero3f, zeus::skWhite); }\n\nbool CParticleSwoosh::GetParticleEmission() const { return x1d0_24_emitting; }\n\nvoid CParticleSwoosh::DestroyParticles() {\n  // Empty\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CParticleSwoosh.hpp",
    "content": "#pragma once\n\n#include <array>\n#include <memory>\n#include <vector>\n\n#include \"Runtime/CRandom16.hpp\"\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/Graphics/CTexture.hpp\"\n#include \"Runtime/Particle/CParticleGen.hpp\"\n#include \"Runtime/Particle/CUVElement.hpp\"\n\n#include <zeus/CColor.hpp>\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CSwooshDescription;\n\nclass CParticleSwoosh : public CParticleGen {\n  friend class CParticleSwooshShaders;\npublic:\n  struct SSwooshData {\n    bool x0_active;\n    float x4_leftRad;\n    float x8_rightRad;\n    zeus::CVector3f xc_translation;   // Updated by system's velocity sources or user code\n    zeus::CVector3f x18_offset;       // Updated by POFS once per system update (also resets x24_useOffset)\n    zeus::CVector3f x24_useOffset;    // Combination of POFS and NPOS, once per particle instance\n    float x30_irot;                   // Rotation bias once per system update\n    float x34_rotm;                   // Rotation bias once per particle instance\n    zeus::CTransform x38_orientation; // Updated by user code\n    int x68_frame = 0;                // Frame index of evaluated data\n    zeus::CColor x6c_color;           // Updated by COLR\n    int x70_startFrame;\n    zeus::CVector3f x74_velocity;\n\n    SSwooshData(const zeus::CVector3f& translation, const zeus::CVector3f& offset, float irot, float rotm,\n                int startFrame, bool active, const zeus::CTransform& orient, const zeus::CVector3f& vel, float leftRad,\n                float rightRad, const zeus::CColor& color)\n    : x0_active(active)\n    , x4_leftRad(leftRad)\n    , x8_rightRad(rightRad)\n    , xc_translation(translation)\n    , x18_offset(offset)\n    , x24_useOffset(offset)\n    , x30_irot(irot)\n    , x34_rotm(rotm)\n    , x38_orientation(orient)\n    , x6c_color(color)\n    , x70_startFrame(startFrame)\n    , x74_velocity(vel) {}\n  };\n\nprivate:\n  TLockedToken<CSwooshDescription> x1c_desc;\n  u32 x28_curFrame = 0;\n  int x2c_PSLT = 0;\n  double x30_curTime = 0.0;\n  zeus::CVector3f x38_translation;\n  zeus::CTransform x44_orientation;\n  zeus::CTransform x74_invOrientation;\n  zeus::CVector3f xa4_globalTranslation;\n  zeus::CTransform xb0_globalOrientation;\n  zeus::CVector3f xe0_globalScale = {1.f, 1.f, 1.f};\n  zeus::CTransform xec_scaleXf;\n  zeus::CTransform x11c_invScaleXf;\n  zeus::CVector3f x14c_localScale = {1.f, 1.f, 1.f};\n  u32 x158_curParticle = 0;\n  std::vector<SSwooshData> x15c_swooshes;\n  std::vector<zeus::CVector3f> x16c_p0;\n  std::vector<zeus::CVector3f> x17c_p1;\n  std::vector<zeus::CVector3f> x18c_p2;\n  std::vector<zeus::CVector3f> x19c_p3;\n  u32 x1ac_particleCount = 0;\n  int x1b0_SPLN = 0;\n  int x1b4_LENG = 0;\n  int x1b8_SIDE = 0;\n  GXPrimitive x1bc_prim{};\n  CRandom16 x1c0_rand;\n  float x1c4_ = 0.f;\n  float x1c8_ = 0.f;\n  float x1cc_TSPN = 0.f;\n  bool x1d0_24_emitting : 1 = true;\n  bool x1d0_25_AALP : 1 = false;\n  bool x1d0_26_forceOneUpdate : 1 = false;\n  bool x1d0_27_renderGaps : 1 = false;\n  bool x1d0_28_LLRD : 1 = false;\n  bool x1d0_29_VLS1 : 1 = false;\n  bool x1d0_30_VLS2 : 1 = false;\n  bool x1d0_31_constantTex : 1 = false;\n  bool x1d1_24_constantUv : 1 = false;\n\n  SUVElementSet x1d4_uvs = {};\n  CTexture* x1e4_tex = nullptr;\n  float x1e8_uvSpan = 1.f;\n  int x1ec_TSPN = 0;\n  zeus::CVector3f x1f0_aabbMin;\n  zeus::CVector3f x1fc_aabbMax;\n  float x208_maxRadius = 0.f;\n  zeus::CColor x20c_moduColor = zeus::skWhite;\n\n  static int g_ParticleSystemAliveCount;\n\n  bool IsValid() const { return x1b4_LENG >= 2 && x1b8_SIDE >= 2; }\n  void UpdateMaxRadius(float r);\n  void UpdateBounds(const zeus::CVector3f& pos);\n  float GetLeftRadius(size_t i) const;\n  float GetRightRadius(size_t i) const;\n  void UpdateSwooshTranslation(const zeus::CVector3f& translation);\n  void UpdateTranslationAndOrientation();\n\n  static zeus::CVector3f GetSplinePoint(const zeus::CVector3f& p0, const zeus::CVector3f& p1, const zeus::CVector3f& p2,\n                                        const zeus::CVector3f& p3, float t);\n  int WrapIndex(int i) const;\n  void RenderNSidedSpline();\n  void RenderNSidedNoSpline();\n  void Render3SidedSolidSpline();\n  void Render3SidedSolidNoSplineNoGaps();\n  void Render2SidedSpline();\n  void Render2SidedNoSplineGaps();\n  void Render2SidedNoSplineNoGaps();\n\npublic:\n  CParticleSwoosh(const TToken<CSwooshDescription>& desc, int);\n  ~CParticleSwoosh() override;\n\n  CSwooshDescription* GetDesc() { return x1c_desc.GetObj(); }\n\n  bool Update(double) override;\n  void Render() override;\n  void SetOrientation(const zeus::CTransform&) override;\n  void SetTranslation(const zeus::CVector3f&) override;\n  void SetGlobalOrientation(const zeus::CTransform&) override;\n  void SetGlobalTranslation(const zeus::CVector3f&) override;\n  void SetGlobalScale(const zeus::CVector3f&) override;\n  void SetLocalScale(const zeus::CVector3f&) override;\n  void SetParticleEmission(bool) override;\n  void SetModulationColor(const zeus::CColor&) override;\n  const zeus::CTransform& GetOrientation() const override;\n  const zeus::CVector3f& GetTranslation() const override;\n  const zeus::CTransform& GetGlobalOrientation() const override;\n  const zeus::CVector3f& GetGlobalTranslation() const override;\n  const zeus::CVector3f& GetGlobalScale() const override;\n  const zeus::CColor& GetModulationColor() const override;\n  bool IsSystemDeletable() const override;\n  std::optional<zeus::CAABox> GetBounds() const override;\n  u32 GetParticleCount() const override;\n  bool SystemHasLight() const override;\n  CLight GetLight() const override;\n  bool GetParticleEmission() const override;\n  void DestroyParticles() override;\n  void Reset() override {}\n  FourCC Get4CharId() const override { return FOURCC('SWHC'); }\n  void SetRenderGaps(bool r) { x1d0_27_renderGaps = r; }\n  size_t GetSwooshDataCount() const { return x15c_swooshes.size(); }\n  SSwooshData& GetSwooshData(size_t idx) { return x15c_swooshes[idx]; }\n  const SSwooshData& GetSwooshData(size_t idx) const { return x15c_swooshes[idx]; }\n  std::vector<SSwooshData>& GetSwooshVector() { return x15c_swooshes; }\n  const std::vector<SSwooshData>& GetSwooshVector() const { return x15c_swooshes; }\n\n  void DoWarmupUpdate() {\n    x1d0_26_forceOneUpdate = true;\n    Update(0.0);\n  }\n\n  void DoElectricWarmup() {\n    for (size_t i = 0; i < x15c_swooshes.size(); ++i) {\n      x1d0_26_forceOneUpdate = true;\n      Update(0.0);\n    }\n  }\n\n  void DoElectricCreate(const std::vector<zeus::CVector3f>& offsets) {\n    u32 curIdx = x158_curParticle;\n    for (size_t i = 0; i < x15c_swooshes.size(); ++i) {\n      curIdx = u32((curIdx + 1) % x15c_swooshes.size());\n      x15c_swooshes[curIdx].xc_translation = offsets[i];\n    }\n  }\n\n  void DoGrappleWarmup() {\n    for (size_t i = 0; i < x15c_swooshes.size() - 1; ++i) {\n      x1d0_26_forceOneUpdate = true;\n      Update(0.0);\n    }\n  }\n\n  void DoGrappleUpdate(const zeus::CVector3f& beamGunPos, const zeus::CTransform& rotation, float anglePhase,\n                       float xAmplitude, float zAmplitude, const zeus::CVector3f& swooshSegDelta) {\n    float rot = x15c_swooshes.back().x30_irot;\n    zeus::CVector3f trans = beamGunPos;\n    for (size_t i = 0; i < x15c_swooshes.size(); ++i) {\n      SSwooshData& data = x15c_swooshes[i];\n      zeus::CVector3f vec;\n      if (i > 0)\n        vec = rotation * zeus::CVector3f(std::cos(i + anglePhase) * xAmplitude, 0.f, std::sin(float(i)) * zAmplitude);\n      data.xc_translation = trans + vec;\n      trans += swooshSegDelta;\n      std::swap(rot, data.x30_irot);\n    }\n  }\n\n  void DoSpiderBallWarmup(zeus::CVector3f& translation, const zeus::CVector3f& transInc) {\n    SetOrientation(zeus::lookAt(zeus::skZero3f, transInc));\n    for (int i = 0; i < 6; ++i) {\n      SetTranslation(translation);\n      x1d0_26_forceOneUpdate = true;\n      Update(0.0);\n      translation += transInc;\n    }\n  }\n\n  void ForceOneUpdate(float dt) {\n    x1d0_26_forceOneUpdate = true;\n    Update(dt);\n  }\n  std::vector<SSwooshData> const& GetSwooshes() const { return x15c_swooshes; }\n  std::vector<SSwooshData>& GetSwooshes() { return x15c_swooshes; }\n  u32 GetCurParticle() const { return x158_curParticle; }\n  static u32 GetAliveParticleSystemCount() { return g_ParticleSystemAliveCount; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CParticleSwooshDataFactory.cpp",
    "content": "#include \"Runtime/Particle/CParticleSwooshDataFactory.hpp\"\n\n#include \"Runtime/CRandom16.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/Graphics/CModel.hpp\"\n#include \"Runtime/Particle/CElectricDescription.hpp\"\n#include \"Runtime/Particle/CGenDescription.hpp\"\n#include \"Runtime/Particle/CSwooshDescription.hpp\"\n#include \"Runtime/Logging.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\nnamespace metaforce {\nusing CPF = CParticleDataFactory;\n\nstd::unique_ptr<CSwooshDescription> CParticleSwooshDataFactory::GetGeneratorDesc(CInputStream& in,\n                                                                                 CSimplePool* resPool) {\n  return CreateGeneratorDescription(in, resPool);\n}\n\nstd::unique_ptr<CSwooshDescription> CParticleSwooshDataFactory::CreateGeneratorDescription(CInputStream& in,\n                                                                                           CSimplePool* resPool) {\n  const FourCC clsId = CPF::GetClassID(in);\n\n  if (clsId == FOURCC('SWSH')) {\n    auto desc = std::make_unique<CSwooshDescription>();\n    if (CreateWPSM(desc.get(), in, resPool)) {\n      return desc;\n    }\n  }\n\n  return nullptr;\n}\n\nbool CParticleSwooshDataFactory::CreateWPSM(CSwooshDescription* desc, CInputStream& in, CSimplePool* resPool) {\n  CRandom16 rand;\n  FourCC clsId = CPF::GetClassID(in);\n  while (clsId != SBIG('_END')) {\n    CGlobalRandom gr(rand);\n    switch (clsId.toUint32()) {\n    case SBIG('PSLT'):\n      desc->x0_PSLT = CPF::GetIntElement(in);\n      break;\n    case SBIG('TIME'):\n      desc->x4_TIME = CPF::GetRealElement(in);\n      break;\n    case SBIG('LRAD'):\n      desc->x8_LRAD = CPF::GetRealElement(in);\n      break;\n    case SBIG('RRAD'):\n      desc->xc_RRAD = CPF::GetRealElement(in);\n      break;\n    case SBIG('LENG'):\n      desc->x10_LENG = CPF::GetIntElement(in);\n      break;\n    case SBIG('COLR'):\n      desc->x14_COLR = CPF::GetColorElement(in);\n      break;\n    case SBIG('SIDE'):\n      desc->x18_SIDE = CPF::GetIntElement(in);\n      break;\n    case SBIG('IROT'):\n      desc->x1c_IROT = CPF::GetRealElement(in);\n      break;\n    case SBIG('ROTM'):\n      desc->x20_ROTM = CPF::GetRealElement(in);\n      break;\n    case SBIG('POFS'):\n      desc->x24_POFS = CPF::GetVectorElement(in);\n      break;\n    case SBIG('IVEL'):\n      desc->x28_IVEL = CPF::GetVectorElement(in);\n      break;\n    case SBIG('NPOS'):\n      desc->x2c_NPOS = CPF::GetVectorElement(in);\n      break;\n    case SBIG('VELM'):\n      desc->x30_VELM = CPF::GetModVectorElement(in);\n      break;\n    case SBIG('VLM2'):\n      desc->x34_VLM2 = CPF::GetModVectorElement(in);\n      break;\n    case SBIG('SPLN'):\n      desc->x38_SPLN = CPF::GetIntElement(in);\n      break;\n    case SBIG('TEXR'):\n      desc->x3c_TEXR = CPF::GetTextureElement(in, resPool);\n      break;\n    case SBIG('TSPN'):\n      desc->x40_TSPN = CPF::GetIntElement(in);\n      break;\n    case SBIG('LLRD'):\n      desc->x44_24_LLRD = CPF::GetBool(in);\n      break;\n    case SBIG('CROS'):\n      desc->x44_25_CROS = CPF::GetBool(in);\n      break;\n    case SBIG('VLS1'):\n      desc->x44_26_VLS1 = CPF::GetBool(in);\n      break;\n    case SBIG('VLS2'):\n      desc->x44_27_VLS2 = CPF::GetBool(in);\n      break;\n    case SBIG('SROT'):\n      desc->x44_28_SROT = CPF::GetBool(in);\n      break;\n    case SBIG('WIRE'):\n      desc->x44_29_WIRE = CPF::GetBool(in);\n      break;\n    case SBIG('TEXW'):\n      desc->x44_30_TEXW = CPF::GetBool(in);\n      break;\n    case SBIG('AALP'):\n      desc->x44_31_AALP = CPF::GetBool(in);\n      break;\n    case SBIG('ZBUF'):\n      desc->x45_24_ZBUF = CPF::GetBool(in);\n      break;\n    case SBIG('ORNT'):\n      desc->x45_25_ORNT = CPF::GetBool(in);\n      break;\n    case SBIG('CRND'):\n      desc->x45_26_CRND = CPF::GetBool(in);\n      break;\n    default: {\n      spdlog::fatal(\"Unknown SWSH class {} @{}\", clsId, in.GetReadPosition());\n      return false;\n    }\n    }\n    clsId = CPF::GetClassID(in);\n  }\n\n  return true;\n}\n\nCFactoryFnReturn FParticleSwooshDataFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& vparms,\n                                            CObjectReference*) {\n  CSimplePool* sp = vparms.GetOwnedObj<CSimplePool*>();\n  return TToken<CSwooshDescription>::GetIObjObjectFor(CParticleSwooshDataFactory::GetGeneratorDesc(in, sp));\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CParticleSwooshDataFactory.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/CFactoryMgr.hpp\"\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/IObj.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n\nnamespace metaforce {\nclass CSwooshDescription;\nclass CSimplePool;\nclass CParticleSwooshDataFactory {\n  static std::unique_ptr<CSwooshDescription> CreateGeneratorDescription(CInputStream& in, CSimplePool* resPool);\n  static bool CreateWPSM(CSwooshDescription* desc, CInputStream& in, CSimplePool* resPool);\n\npublic:\n  static std::unique_ptr<CSwooshDescription> GetGeneratorDesc(CInputStream& in, CSimplePool* resPool);\n};\n\nCFactoryFnReturn FParticleSwooshDataFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& vparms,\n                                            CObjectReference*);\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CProjectileWeaponDataFactory.cpp",
    "content": "#include \"Runtime/Particle/CProjectileWeaponDataFactory.hpp\"\n\n#include \"Runtime/CRandom16.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/Collision/CCollisionResponseData.hpp\"\n#include \"Runtime/Graphics/CModel.hpp\"\n#include \"Runtime/Particle/CElectricDescription.hpp\"\n#include \"Runtime/Particle/CGenDescription.hpp\"\n#include \"Runtime/Particle/CSwooshDescription.hpp\"\n#include \"Runtime/Particle/CWeaponDescription.hpp\"\n#include \"Runtime/Logging.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\nnamespace metaforce {\nusing CPF = CParticleDataFactory;\n\nstd::unique_ptr<CWeaponDescription> CProjectileWeaponDataFactory::GetGeneratorDesc(CInputStream& in,\n                                                                                   CSimplePool* resPool) {\n  return CreateGeneratorDescription(in, resPool);\n}\n\nstd::unique_ptr<CWeaponDescription> CProjectileWeaponDataFactory::CreateGeneratorDescription(CInputStream& in,\n                                                                                             CSimplePool* resPool) {\n  const FourCC clsId = CPF::GetClassID(in);\n  if (clsId != FOURCC('WPSM')) {\n    return nullptr;\n  }\n\n  auto desc = std::make_unique<CWeaponDescription>();\n  CreateWPSM(desc.get(), in, resPool);\n  return desc;\n}\n\nbool CProjectileWeaponDataFactory::CreateWPSM(CWeaponDescription* desc, CInputStream& in, CSimplePool* resPool) {\n  CRandom16 rand;\n  CGlobalRandom gr{rand};\n  FourCC clsId = CPF::GetClassID(in);\n\n  while (clsId != SBIG('_END')) {\n    switch (clsId.toUint32()) {\n    case SBIG('IORN'):\n      desc->x0_IORN = CPF::GetVectorElement(in);\n      break;\n    case SBIG('IVEC'):\n      desc->x4_IVEC = CPF::GetVectorElement(in);\n      break;\n    case SBIG('PSOV'):\n      desc->x8_PSOV = CPF::GetVectorElement(in);\n      break;\n    case SBIG('PSVM'):\n      desc->xc_PSVM = CPF::GetModVectorElement(in);\n      break;\n    case SBIG('VMD2'):\n      desc->x10_VMD2 = CPF::GetBool(in);\n      break;\n    case SBIG('PSLT'):\n      desc->x14_PSLT = CPF::GetIntElement(in);\n      break;\n    case SBIG('PSCL'):\n      desc->x18_PSCL = CPF::GetVectorElement(in);\n      break;\n    case SBIG('PCOL'):\n      desc->x1c_PCOL = CPF::GetColorElement(in);\n      break;\n    case SBIG('POFS'):\n      desc->x20_POFS = CPF::GetVectorElement(in);\n      break;\n    case SBIG('OFST'):\n      desc->x24_OFST = CPF::GetVectorElement(in);\n      break;\n    case SBIG('APSO'):\n      desc->x28_APSO = CPF::GetBool(in);\n      break;\n    case SBIG('HOMG'):\n      desc->x29_HOMG = CPF::GetBool(in);\n      break;\n    case SBIG('AP11'):\n      desc->x2a_AP11 = CPF::GetBool(in);\n      break;\n    case SBIG('AP21'):\n      desc->x2b_AP21 = CPF::GetBool(in);\n      break;\n    case SBIG('AS11'):\n      desc->x2c_AS11 = CPF::GetBool(in);\n      break;\n    case SBIG('AS12'):\n      desc->x2d_AS12 = CPF::GetBool(in);\n      break;\n    case SBIG('AS13'):\n      desc->x2e_AS13 = CPF::GetBool(in);\n      break;\n    case SBIG('TRAT'):\n      desc->x30_TRAT = CPF::GetRealElement(in);\n      break;\n    case SBIG('APSM'): {\n      std::vector<CAssetId> tracker;\n      tracker.reserve(8);\n      desc->x34_APSM = CPF::GetChildGeneratorDesc(in, resPool, tracker);\n      break;\n    }\n    case SBIG('APS2'): {\n      std::vector<CAssetId> tracker;\n      tracker.reserve(8);\n      desc->x44_APS2 = CPF::GetChildGeneratorDesc(in, resPool, tracker);\n      break;\n    }\n    case SBIG('ASW1'):\n      desc->x54_ASW1 = CPF::GetSwooshGeneratorDesc(in, resPool);\n      break;\n    case SBIG('ASW2'):\n      desc->x64_ASW2 = CPF::GetSwooshGeneratorDesc(in, resPool);\n      break;\n    case SBIG('ASW3'):\n      desc->x74_ASW3 = CPF::GetSwooshGeneratorDesc(in, resPool);\n      break;\n    case SBIG('OHEF'):\n      desc->x84_OHEF = CPF::GetModel(in, resPool);\n      break;\n    case SBIG('COLR'): {\n      FourCC cid = CPF::GetClassID(in);\n      if (cid == SBIG('NONE'))\n        break;\n      CAssetId id(in);\n      if (id.IsValid())\n        desc->x94_COLR = resPool->GetObj({FOURCC('CRSC'), id});\n      break;\n    }\n    case SBIG('EWTR'):\n      desc->xa4_EWTR = CPF::GetBool(in);\n      break;\n    case SBIG('LWTR'):\n      desc->xa5_LWTR = CPF::GetBool(in);\n      break;\n    case SBIG('SWTR'):\n      desc->xa6_SWTR = CPF::GetBool(in);\n      break;\n    case SBIG('PJFX'): {\n      FourCC cid = CPF::GetClassID(in);\n      if (cid == FOURCC('NONE'))\n        break;\n\n      desc->xa8_PJFX = CPF::GetInt(in);\n      break;\n    }\n    case SBIG('RNGE'):\n      desc->xac_RNGE = CPF::GetRealElement(in);\n      break;\n    case SBIG('FOFF'):\n      desc->xb0_FOFF = CPF::GetRealElement(in);\n      break;\n    case SBIG('SPS1'):\n      desc->x28_SPS1 = CPF::GetBool(in);\n      break;\n    case SBIG('SPS2'):\n      desc->x29_SPS2 = CPF::GetBool(in);\n      break;\n    case SBIG('FC60'):\n      desc->x29_FC60 = CPF::GetBool(in);\n      break;\n    default: {\n      spdlog::fatal(\"Unknown WPSM class {} @{}\", clsId, in.GetReadPosition());\n      return false;\n    }\n    }\n    clsId = CPF::GetClassID(in);\n  }\n  return true;\n}\n\nCFactoryFnReturn FProjectileWeaponDataFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& vparms,\n                                              CObjectReference*) {\n  CSimplePool* sp = vparms.GetOwnedObj<CSimplePool*>();\n  return TToken<CWeaponDescription>::GetIObjObjectFor(CProjectileWeaponDataFactory::GetGeneratorDesc(in, sp));\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CProjectileWeaponDataFactory.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/CFactoryMgr.hpp\"\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/IObj.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n\nnamespace metaforce {\nclass CSimplePool;\nclass CWeaponDescription;\n\nclass CProjectileWeaponDataFactory {\n  static std::unique_ptr<CWeaponDescription> CreateGeneratorDescription(CInputStream& in, CSimplePool* resPool);\n  static bool CreateWPSM(CWeaponDescription* desc, CInputStream& in, CSimplePool* resPool);\n\npublic:\n  static std::unique_ptr<CWeaponDescription> GetGeneratorDesc(CInputStream& in, CSimplePool* resPool);\n};\n\nCFactoryFnReturn FProjectileWeaponDataFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& vparms,\n                                              CObjectReference*);\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CRealElement.cpp",
    "content": "#include \"Runtime/Particle/CRealElement.hpp\"\n\n#include \"Runtime/CRandom16.hpp\"\n#include \"Runtime/Graphics/CTexture.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/Particle/CGenDescription.hpp\"\n#include \"Runtime/Particle/CParticleGlobals.hpp\"\n\n#include <zeus/Math.hpp>\n\n/* Documentation at: https://wiki.axiodl.com/w/Particle_Script#Real_Elements */\n\nnamespace metaforce {\n\nCREKeyframeEmitter::CREKeyframeEmitter(CInputStream& in) {\n  x4_percent = in.ReadLong();\n  x8_unk1 = in.ReadLong();\n  xc_loop = in.ReadBool();\n  xd_unk2 = in.ReadBool();\n  x10_loopEnd = in.ReadLong();\n  x14_loopStart = in.ReadLong();\n\n  u32 count = in.ReadLong();\n  x18_keys.reserve(count);\n  for (u32 i = 0; i < count; ++i)\n    x18_keys.push_back(in.ReadFloat());\n}\n\nbool CREKeyframeEmitter::GetValue([[maybe_unused]] int frame, float& valOut) const {\n  if (!x4_percent) {\n    int emitterTime = CParticleGlobals::instance()->m_EmitterTime;\n    int calcKey = emitterTime;\n    if (xc_loop) {\n      if (emitterTime >= x10_loopEnd) {\n        int v1 = emitterTime - x14_loopStart;\n        int v2 = x10_loopEnd - x14_loopStart;\n        calcKey = v1 % v2;\n        calcKey += x14_loopStart;\n      }\n    } else {\n      int v1 = x10_loopEnd - 1;\n      if (v1 < emitterTime)\n        calcKey = v1;\n    }\n    valOut = x18_keys[calcKey];\n  } else {\n    int ltPerc = CParticleGlobals::instance()->m_ParticleLifetimePercentage;\n    float ltPercRem = CParticleGlobals::instance()->m_ParticleLifetimePercentageRemainder;\n    if (ltPerc == 100)\n      valOut = x18_keys[100];\n    else\n      valOut = ltPercRem * x18_keys[ltPerc + 1] + (1.0f - ltPercRem) * x18_keys[ltPerc];\n  }\n  return false;\n}\n\nbool CRELifetimeTween::GetValue(int frame, float& valOut) const {\n  float ltFac = frame / CParticleGlobals::instance()->m_ParticleLifetimeReal;\n  float a, b;\n  x4_a->GetValue(frame, a);\n  x8_b->GetValue(frame, b);\n  valOut = b * ltFac + (1.0f - ltFac) * a;\n  return false;\n}\n\nbool CREConstant::GetValue([[maybe_unused]] int frame, float& valOut) const {\n  valOut = x4_val;\n  return false;\n}\n\nbool CRETimeChain::GetValue(int frame, float& valOut) const {\n  int v;\n  xc_swFrame->GetValue(frame, v);\n  if (frame < v) {\n    return x4_a->GetValue(frame, valOut);\n  } else {\n    return x8_b->GetValue(frame - v, valOut);\n  }\n}\n\nbool CREAdd::GetValue(int frame, float& valOut) const {\n  float a, b;\n  x4_a->GetValue(frame, a);\n  x8_b->GetValue(frame, b);\n  valOut = a + b;\n  return false;\n}\n\nbool CREClamp::GetValue(int frame, float& valOut) const {\n  float a, b;\n  x4_min->GetValue(frame, a);\n  x8_max->GetValue(frame, b);\n  xc_val->GetValue(frame, valOut);\n  if (valOut > b)\n    valOut = b;\n  if (valOut < a)\n    valOut = a;\n  return false;\n}\n\nbool CREInitialRandom::GetValue(int frame, float& valOut) const {\n  if (frame == 0) {\n    float a, b;\n    x4_min->GetValue(frame, a);\n    x8_max->GetValue(frame, b);\n    float rand = CRandom16::GetRandomNumber()->Float();\n    valOut = b * rand + a * (1.0f - rand);\n  }\n  return false;\n}\n\nbool CRERandom::GetValue(int frame, float& valOut) const {\n  float min;\n  float max;\n  x4_min->GetValue(frame, min);\n  x8_max->GetValue(frame, max);\n  valOut = (max - min) * CRandom16::GetRandomNumber()->Float() + min;\n  return false;\n}\n\nbool CREDotProduct::GetValue(int frame, float& valOut) const {\n  zeus::CVector3f a, b;\n  x4_a->GetValue(frame, a);\n  x8_b->GetValue(frame, b);\n  valOut = a.dot(b);\n  return false;\n}\n\nbool CREMultiply::GetValue(int frame, float& valOut) const {\n  float a, b;\n  x4_a->GetValue(frame, a);\n  x8_b->GetValue(frame, b);\n  valOut = a * b;\n  return false;\n}\n\nbool CREPulse::GetValue(int frame, float& valOut) const {\n  int a, b;\n  x4_aDuration->GetValue(frame, a);\n  x8_bDuration->GetValue(frame, b);\n  int cv = a + b + 1;\n  if (cv < 0) {\n    cv = 1;\n  }\n\n  // CREPulse is an outlier here, the other\n  // IElement classes  use <= instead of <.\n  if (b < 1 || frame % cv < a) {\n    xc_valA->GetValue(frame, valOut);\n  } else {\n    x10_valB->GetValue(frame, valOut);\n  }\n  return false;\n}\n\nbool CRETimeScale::GetValue(int frame, float& valOut) const {\n  float a;\n  x4_a->GetValue(frame, a);\n  valOut = float(frame) * a;\n  return false;\n}\n\nbool CRELifetimePercent::GetValue(int frame, float& valOut) const {\n  float a;\n  x4_percentVal->GetValue(frame, a);\n  a = std::max(0.0f, a);\n  valOut = (a / 100.0f) * CParticleGlobals::instance()->m_ParticleLifetimeReal;\n  return false;\n}\n\nbool CRESineWave::GetValue(int frame, float& valOut) const {\n  float a, b, c;\n  x4_frequency->GetValue(frame, a);\n  x8_amplitude->GetValue(frame, b);\n  xc_phase->GetValue(frame, c);\n  valOut = std::sin(zeus::degToRad(frame * a + c)) * b;\n  return false;\n}\n\nbool CREInitialSwitch::GetValue(int frame, float& valOut) const {\n  if (frame == 0) {\n    x4_a->GetValue(0, valOut);\n  } else {\n    x8_b->GetValue(frame - 1, valOut);\n  }\n  return false;\n}\n\nbool CRECompareLessThan::GetValue(int frame, float& valOut) const {\n  float a, b;\n  x4_a->GetValue(frame, a);\n  x8_b->GetValue(frame, b);\n  if (a < b)\n    xc_c->GetValue(frame, valOut);\n  else\n    x10_d->GetValue(frame, valOut);\n  return false;\n}\n\nbool CRECompareEquals::GetValue(int frame, float& valOut) const {\n  float a, b;\n  x4_a->GetValue(frame, a);\n  x8_b->GetValue(frame, b);\n  if (zeus::close_enough(a, b))\n    xc_c->GetValue(frame, valOut);\n  else\n    x10_d->GetValue(frame, valOut);\n  return false;\n}\n\nbool CREParticleAccessParam1::GetValue(int /*frame*/, float& valOut) const {\n  valOut = (*CParticleGlobals::instance()->m_particleAccessParameters)[0];\n  return false;\n}\n\nbool CREParticleAccessParam2::GetValue(int /*frame*/, float& valOut) const {\n  valOut = (*CParticleGlobals::instance()->m_particleAccessParameters)[1];\n  return false;\n}\n\nbool CREParticleAccessParam3::GetValue(int /*frame*/, float& valOut) const {\n  valOut = (*CParticleGlobals::instance()->m_particleAccessParameters)[2];\n  return false;\n}\n\nbool CREParticleAccessParam4::GetValue(int /*frame*/, float& valOut) const {\n  valOut = (*CParticleGlobals::instance()->m_particleAccessParameters)[3];\n  return false;\n}\n\nbool CREParticleAccessParam5::GetValue(int /*frame*/, float& valOut) const {\n  valOut = (*CParticleGlobals::instance()->m_particleAccessParameters)[4];\n  return false;\n}\n\nbool CREParticleAccessParam6::GetValue(int /*frame*/, float& valOut) const {\n  valOut = (*CParticleGlobals::instance()->m_particleAccessParameters)[5];\n  return false;\n}\n\nbool CREParticleAccessParam7::GetValue(int /*frame*/, float& valOut) const {\n  valOut = (*CParticleGlobals::instance()->m_particleAccessParameters)[6];\n  return false;\n}\n\nbool CREParticleAccessParam8::GetValue(int /*frame*/, float& valOut) const {\n  valOut = (*CParticleGlobals::instance()->m_particleAccessParameters)[7];\n  return false;\n}\n\nbool CREParticleSizeOrLineLength::GetValue(int /*frame*/, float& valOut) const {\n  valOut = CElementGen::g_currentParticle->x2c_lineLengthOrSize;\n  return false;\n}\n\nbool CREParticleRotationOrLineWidth::GetValue(int /*frame*/, float& valOut) const {\n  valOut = CElementGen::g_currentParticle->x30_lineWidthOrRota;\n  return false;\n}\n\nbool CRESubtract::GetValue(int frame, float& valOut) const {\n  float a, b;\n  x4_a->GetValue(frame, a);\n  x8_b->GetValue(frame, b);\n  valOut = a - b;\n  return false;\n}\n\nbool CREVectorMagnitude::GetValue(int frame, float& valOut) const {\n  zeus::CVector3f a;\n  x4_a->GetValue(frame, a);\n  valOut = a.magnitude();\n  return false;\n}\n\nbool CREVectorXToReal::GetValue(int frame, float& valOut) const {\n  zeus::CVector3f a;\n  x4_a->GetValue(frame, a);\n  valOut = a[0];\n  return false;\n}\n\nbool CREVectorYToReal::GetValue(int frame, float& valOut) const {\n  zeus::CVector3f a;\n  x4_a->GetValue(frame, a);\n  valOut = a[1];\n  return false;\n}\n\nbool CREVectorZToReal::GetValue(int frame, float& valOut) const {\n  zeus::CVector3f a;\n  x4_a->GetValue(frame, a);\n  valOut = a[2];\n  return false;\n}\n\nbool CREExternalVar::GetValue(int frame, float& valOut) const {\n  int a;\n  x4_a->GetValue(frame, a);\n  int cv = std::max(0, a);\n  valOut = CParticleGlobals::instance()->m_currentParticleSystem->x4_system->GetExternalVar(cv & 0xf);\n  return false;\n}\n\nbool CREIntTimesReal::GetValue(int frame, float& valOut) const {\n  int a;\n  x4_a->GetValue(frame, a);\n  float b;\n  x8_b->GetValue(frame, b);\n  valOut = float(a) * b;\n  return false;\n}\n\nbool CREConstantRange::GetValue(int frame, float& valOut) const {\n  float val, min, max;\n  x4_val->GetValue(frame, val);\n  x8_min->GetValue(frame, min);\n  xc_max->GetValue(frame, max);\n\n  if (val > min && val < max)\n    x10_inRange->GetValue(frame, valOut);\n  else\n    x14_outOfRange->GetValue(frame, valOut);\n\n  return false;\n}\n\nbool CREGetComponentRed::GetValue(int frame, float& valOut) const {\n  zeus::CColor a = zeus::skBlack;\n  x4_a->GetValue(frame, a);\n  valOut = a.r();\n  return false;\n}\n\nbool CREGetComponentGreen::GetValue(int frame, float& valOut) const {\n  zeus::CColor a = zeus::skBlack;\n  x4_a->GetValue(frame, a);\n  valOut = a.g();\n  return false;\n}\n\nbool CREGetComponentBlue::GetValue(int frame, float& valOut) const {\n  zeus::CColor a = zeus::skBlack;\n  x4_a->GetValue(frame, a);\n  valOut = a.b();\n  return false;\n}\n\nbool CREGetComponentAlpha::GetValue(int frame, float& valOut) const {\n  zeus::CColor a = zeus::skBlack;\n  x4_a->GetValue(frame, a);\n  valOut = a.a();\n  return false;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CRealElement.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <vector>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/Particle/IElement.hpp\"\n\n/* Documentation at: https://wiki.axiodl.com/w/Particle_Script#Real_Elements */\n\nnamespace metaforce {\n\nclass CREKeyframeEmitter : public CRealElement {\n  u32 x4_percent;\n  u32 x8_unk1;\n  bool xc_loop;\n  bool xd_unk2;\n  u32 x10_loopEnd;\n  u32 x14_loopStart;\n  std::vector<float> x18_keys;\n\npublic:\n  explicit CREKeyframeEmitter(CInputStream& in);\n  bool GetValue(int frame, float& valOut) const override;\n};\n\nclass CRELifetimeTween : public CRealElement {\n  std::unique_ptr<CRealElement> x4_a;\n  std::unique_ptr<CRealElement> x8_b;\n\npublic:\n  CRELifetimeTween(std::unique_ptr<CRealElement>&& a, std::unique_ptr<CRealElement>&& b)\n  : x4_a(std::move(a)), x8_b(std::move(b)) {}\n  bool GetValue(int frame, float& valOut) const override;\n};\n\nclass CREConstant : public CRealElement {\n  float x4_val;\n\npublic:\n  explicit CREConstant(float val) : x4_val(val) {}\n  bool GetValue(int frame, float& valOut) const override;\n  bool IsConstant() const override { return true; }\n};\n\nclass CRETimeChain : public CRealElement {\n  std::unique_ptr<CRealElement> x4_a;\n  std::unique_ptr<CRealElement> x8_b;\n  std::unique_ptr<CIntElement> xc_swFrame;\n\npublic:\n  CRETimeChain(std::unique_ptr<CRealElement>&& a, std::unique_ptr<CRealElement>&& b, std::unique_ptr<CIntElement>&& c)\n  : x4_a(std::move(a)), x8_b(std::move(b)), xc_swFrame(std::move(c)) {}\n  bool GetValue(int frame, float& valOut) const override;\n};\n\nclass CREAdd : public CRealElement {\n  std::unique_ptr<CRealElement> x4_a;\n  std::unique_ptr<CRealElement> x8_b;\n\npublic:\n  CREAdd(std::unique_ptr<CRealElement>&& a, std::unique_ptr<CRealElement>&& b)\n  : x4_a(std::move(a)), x8_b(std::move(b)) {}\n  bool GetValue(int frame, float& valOut) const override;\n};\n\nclass CREClamp : public CRealElement {\n  std::unique_ptr<CRealElement> x4_min;\n  std::unique_ptr<CRealElement> x8_max;\n  std::unique_ptr<CRealElement> xc_val;\n\npublic:\n  CREClamp(std::unique_ptr<CRealElement>&& a, std::unique_ptr<CRealElement>&& b, std::unique_ptr<CRealElement>&& c)\n  : x4_min(std::move(a)), x8_max(std::move(b)), xc_val(std::move(c)) {}\n  bool GetValue(int frame, float& valOut) const override;\n};\n\nclass CREInitialRandom : public CRealElement {\n  std::unique_ptr<CRealElement> x4_min;\n  std::unique_ptr<CRealElement> x8_max;\n\npublic:\n  CREInitialRandom(std::unique_ptr<CRealElement>&& a, std::unique_ptr<CRealElement>&& b)\n  : x4_min(std::move(a)), x8_max(std::move(b)) {}\n  bool GetValue(int frame, float& valOut) const override;\n  bool IsConstant() const override { return true; }\n};\n\nclass CRERandom : public CRealElement {\n  std::unique_ptr<CRealElement> x4_min;\n  std::unique_ptr<CRealElement> x8_max;\n\npublic:\n  CRERandom(std::unique_ptr<CRealElement>&& a, std::unique_ptr<CRealElement>&& b)\n  : x4_min(std::move(a)), x8_max(std::move(b)) {}\n  bool GetValue(int frame, float& valOut) const override;\n};\n\nclass CREDotProduct : public CRealElement {\n  std::unique_ptr<CVectorElement> x4_a;\n  std::unique_ptr<CVectorElement> x8_b;\n\npublic:\n  CREDotProduct(std::unique_ptr<CVectorElement>&& a, std::unique_ptr<CVectorElement>&& b)\n  : x4_a(std::move(a)), x8_b(std::move(b)) {}\n  bool GetValue(int frame, float& valOut) const override;\n};\n\nclass CREMultiply : public CRealElement {\n  std::unique_ptr<CRealElement> x4_a;\n  std::unique_ptr<CRealElement> x8_b;\n\npublic:\n  CREMultiply(std::unique_ptr<CRealElement>&& a, std::unique_ptr<CRealElement>&& b)\n  : x4_a(std::move(a)), x8_b(std::move(b)) {}\n  bool GetValue(int frame, float& valOut) const override;\n};\n\nclass CREPulse : public CRealElement {\n  std::unique_ptr<CIntElement> x4_aDuration;\n  std::unique_ptr<CIntElement> x8_bDuration;\n  std::unique_ptr<CRealElement> xc_valA;\n  std::unique_ptr<CRealElement> x10_valB;\n\npublic:\n  CREPulse(std::unique_ptr<CIntElement>&& a, std::unique_ptr<CIntElement>&& b, std::unique_ptr<CRealElement>&& c,\n           std::unique_ptr<CRealElement>&& d)\n  : x4_aDuration(std::move(a)), x8_bDuration(std::move(b)), xc_valA(std::move(c)), x10_valB(std::move(d)) {}\n  bool GetValue(int frame, float& valOut) const override;\n};\n\nclass CRETimeScale : public CRealElement {\n  std::unique_ptr<CRealElement> x4_a;\n\npublic:\n  explicit CRETimeScale(std::unique_ptr<CRealElement>&& a) : x4_a(std::move(a)) {}\n  bool GetValue(int frame, float& valOut) const override;\n};\n\nclass CRELifetimePercent : public CRealElement {\n  std::unique_ptr<CRealElement> x4_percentVal;\n\npublic:\n  explicit CRELifetimePercent(std::unique_ptr<CRealElement>&& a) : x4_percentVal(std::move(a)) {}\n  bool GetValue(int frame, float& valOut) const override;\n};\n\nclass CRESineWave : public CRealElement {\n  std::unique_ptr<CRealElement> x4_frequency;\n  std::unique_ptr<CRealElement> x8_amplitude;\n  std::unique_ptr<CRealElement> xc_phase;\n\npublic:\n  CRESineWave(std::unique_ptr<CRealElement>&& phase, std::unique_ptr<CRealElement>&& frequency,\n              std::unique_ptr<CRealElement>&& amplitude)\n  : x4_frequency(std::move(frequency)), x8_amplitude(std::move(amplitude)), xc_phase(std::move(phase)) {}\n  bool GetValue(int frame, float& valOut) const override;\n};\n\nclass CREInitialSwitch : public CRealElement {\n  std::unique_ptr<CRealElement> x4_a;\n  std::unique_ptr<CRealElement> x8_b;\n\npublic:\n  CREInitialSwitch(std::unique_ptr<CRealElement>&& a, std::unique_ptr<CRealElement>&& b)\n  : x4_a(std::move(a)), x8_b(std::move(b)) {}\n  bool GetValue(int frame, float& valOut) const override;\n};\n\nclass CRECompareLessThan : public CRealElement {\n  std::unique_ptr<CRealElement> x4_a;\n  std::unique_ptr<CRealElement> x8_b;\n  std::unique_ptr<CRealElement> xc_c;\n  std::unique_ptr<CRealElement> x10_d;\n\npublic:\n  CRECompareLessThan(std::unique_ptr<CRealElement>&& a, std::unique_ptr<CRealElement>&& b,\n                     std::unique_ptr<CRealElement>&& c, std::unique_ptr<CRealElement>&& d)\n  : x4_a(std::move(a)), x8_b(std::move(b)), xc_c(std::move(c)), x10_d(std::move(d)) {}\n  bool GetValue(int frame, float& valOut) const override;\n};\n\nclass CRECompareEquals : public CRealElement {\n  std::unique_ptr<CRealElement> x4_a;\n  std::unique_ptr<CRealElement> x8_b;\n  std::unique_ptr<CRealElement> xc_c;\n  std::unique_ptr<CRealElement> x10_d;\n\npublic:\n  CRECompareEquals(std::unique_ptr<CRealElement>&& a, std::unique_ptr<CRealElement>&& b,\n                   std::unique_ptr<CRealElement>&& c, std::unique_ptr<CRealElement>&& d)\n  : x4_a(std::move(a)), x8_b(std::move(b)), xc_c(std::move(c)), x10_d(std::move(d)) {}\n  bool GetValue(int frame, float& valOut) const override;\n};\n\nclass CREParticleAccessParam1 : public CRealElement {\npublic:\n  bool GetValue(int frame, float& valOut) const override;\n};\n\nclass CREParticleAccessParam2 : public CRealElement {\npublic:\n  bool GetValue(int frame, float& valOut) const override;\n};\n\nclass CREParticleAccessParam3 : public CRealElement {\npublic:\n  bool GetValue(int frame, float& valOut) const override;\n};\n\nclass CREParticleAccessParam4 : public CRealElement {\npublic:\n  bool GetValue(int frame, float& valOut) const override;\n};\n\nclass CREParticleAccessParam5 : public CRealElement {\npublic:\n  bool GetValue(int frame, float& valOut) const override;\n};\n\nclass CREParticleAccessParam6 : public CRealElement {\npublic:\n  bool GetValue(int frame, float& valOut) const override;\n};\n\nclass CREParticleAccessParam7 : public CRealElement {\npublic:\n  bool GetValue(int frame, float& valOut) const override;\n};\n\nclass CREParticleAccessParam8 : public CRealElement {\npublic:\n  bool GetValue(int frame, float& valOut) const override;\n};\n\nclass CREParticleSizeOrLineLength : public CRealElement {\npublic:\n  bool GetValue(int frame, float& valOut) const override;\n};\n\nclass CREParticleRotationOrLineWidth : public CRealElement {\npublic:\n  bool GetValue(int frame, float& valOut) const override;\n};\n\nclass CRESubtract : public CRealElement {\n  std::unique_ptr<CRealElement> x4_a;\n  std::unique_ptr<CRealElement> x8_b;\n\npublic:\n  CRESubtract(std::unique_ptr<CRealElement>&& a, std::unique_ptr<CRealElement>&& b)\n  : x4_a(std::move(a)), x8_b(std::move(b)) {}\n  bool GetValue(int frame, float& valOut) const override;\n};\n\nclass CREVectorMagnitude : public CRealElement {\n  std::unique_ptr<CVectorElement> x4_a;\n\npublic:\n  explicit CREVectorMagnitude(std::unique_ptr<CVectorElement>&& a) : x4_a(std::move(a)) {}\n  bool GetValue(int frame, float& valOut) const override;\n};\n\nclass CREVectorXToReal : public CRealElement {\n  std::unique_ptr<CVectorElement> x4_a;\n\npublic:\n  explicit CREVectorXToReal(std::unique_ptr<CVectorElement>&& a) : x4_a(std::move(a)) {}\n  bool GetValue(int frame, float& valOut) const override;\n};\n\nclass CREVectorYToReal : public CRealElement {\n  std::unique_ptr<CVectorElement> x4_a;\n\npublic:\n  explicit CREVectorYToReal(std::unique_ptr<CVectorElement>&& a) : x4_a(std::move(a)) {}\n  bool GetValue(int frame, float& valOut) const override;\n};\n\nclass CREVectorZToReal : public CRealElement {\n  std::unique_ptr<CVectorElement> x4_a;\n\npublic:\n  explicit CREVectorZToReal(std::unique_ptr<CVectorElement>&& a) : x4_a(std::move(a)) {}\n  bool GetValue(int frame, float& valOut) const override;\n};\n\nclass CREExternalVar : public CRealElement {\n  std::unique_ptr<CIntElement> x4_a;\n\npublic:\n  explicit CREExternalVar(std::unique_ptr<CIntElement>&& a) : x4_a(std::move(a)) {}\n  bool GetValue(int frame, float& valOut) const override;\n};\n\nclass CREIntTimesReal : public CRealElement {\n  std::unique_ptr<CIntElement> x4_a;\n  std::unique_ptr<CRealElement> x8_b;\n\npublic:\n  CREIntTimesReal(std::unique_ptr<CIntElement>&& a, std::unique_ptr<CRealElement>&& b)\n  : x4_a(std::move(a)), x8_b(std::move(b)) {}\n  bool GetValue(int frame, float& valOut) const override;\n};\n\nclass CREConstantRange : public CRealElement {\n  std::unique_ptr<CRealElement> x4_val;\n  std::unique_ptr<CRealElement> x8_min;\n  std::unique_ptr<CRealElement> xc_max;\n  std::unique_ptr<CRealElement> x10_inRange;\n  std::unique_ptr<CRealElement> x14_outOfRange;\n\npublic:\n  CREConstantRange(std::unique_ptr<CRealElement>&& a, std::unique_ptr<CRealElement>&& b,\n                   std::unique_ptr<CRealElement>&& c, std::unique_ptr<CRealElement>&& d,\n                   std::unique_ptr<CRealElement>&& e)\n  : x4_val(std::move(a))\n  , x8_min(std::move(b))\n  , xc_max(std::move(c))\n  , x10_inRange(std::move(d))\n  , x14_outOfRange(std::move(e)) {}\n\n  bool GetValue(int frame, float& valOut) const override;\n};\n\nclass CREGetComponentRed : public CRealElement {\n  std::unique_ptr<CColorElement> x4_a;\n\npublic:\n  explicit CREGetComponentRed(std::unique_ptr<CColorElement>&& a) : x4_a(std::move(a)) {}\n\n  bool GetValue(int frame, float& valOut) const override;\n};\n\nclass CREGetComponentGreen : public CRealElement {\n  std::unique_ptr<CColorElement> x4_a;\n\npublic:\n  explicit CREGetComponentGreen(std::unique_ptr<CColorElement>&& a) : x4_a(std::move(a)) {}\n\n  bool GetValue(int frame, float& valOut) const override;\n};\n\nclass CREGetComponentBlue : public CRealElement {\n  std::unique_ptr<CColorElement> x4_a;\n\npublic:\n  explicit CREGetComponentBlue(std::unique_ptr<CColorElement>&& a) : x4_a(std::move(a)) {}\n\n  bool GetValue(int frame, float& valOut) const override;\n};\n\nclass CREGetComponentAlpha : public CRealElement {\n  std::unique_ptr<CColorElement> x4_a;\n\npublic:\n  explicit CREGetComponentAlpha(std::unique_ptr<CColorElement>&& a) : x4_a(std::move(a)) {}\n\n  bool GetValue(int frame, float& valOut) const override;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CSpawnSystemKeyframeData.cpp",
    "content": "#include \"Runtime/Particle/CSpawnSystemKeyframeData.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/Graphics/CModel.hpp\"\n#include \"Runtime/Particle/CElectricDescription.hpp\"\n\nnamespace metaforce {\n\nCSpawnSystemKeyframeData::CSpawnSystemKeyframeData(CInputStream& in) {\n  x0 = in.ReadLong();\n  x4 = in.ReadLong();\n  x8_endFrame = in.ReadLong();\n  xc = in.ReadLong();\n\n  u32 count = in.ReadLong();\n  x10_spawns.reserve(count);\n  for (u32 i = 0; i < count; ++i) {\n    u32 v1 = in.ReadLong();\n    x10_spawns.emplace_back(v1, std::vector<CSpawnSystemKeyframeInfo>());\n    std::vector<CSpawnSystemKeyframeInfo>& v2 = x10_spawns.back().second;\n    u32 v2c = in.ReadLong();\n    v2.reserve(v2c);\n    for (u32 j = 0; j < v2c; ++j)\n      v2.emplace_back(in);\n  }\n}\n\nCSpawnSystemKeyframeData::CSpawnSystemKeyframeInfo::CSpawnSystemKeyframeInfo(CInputStream& in) {\n  x0_id = in.ReadLong();\n  x4 = in.ReadLong();\n  x8 = in.ReadLong();\n  xc = in.ReadLong();\n}\n\nvoid CSpawnSystemKeyframeData::LoadAllSpawnedSystemTokens(CSimplePool* pool) {\n  for (auto& spawn : x10_spawns)\n    for (CSpawnSystemKeyframeInfo& elem : spawn.second)\n      elem.LoadToken(pool);\n}\n\nvoid CSpawnSystemKeyframeData::CSpawnSystemKeyframeInfo::LoadToken(CSimplePool* pool) {\n  x10_token = std::move(pool->GetObj({FOURCC('PART'), x0_id}));\n  x18_found = true;\n}\n\nstd::vector<CSpawnSystemKeyframeData::CSpawnSystemKeyframeInfo>&\nCSpawnSystemKeyframeData::GetSpawnedSystemsAtFrame(u32 frame) {\n  static std::vector<CSpawnSystemKeyframeData::CSpawnSystemKeyframeInfo> emptyReturn;\n  if (frame >= x8_endFrame)\n    return emptyReturn;\n  for (auto& spawn : x10_spawns)\n    if (spawn.first == frame)\n      return spawn.second;\n  return emptyReturn;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CSpawnSystemKeyframeData.hpp",
    "content": "#pragma once\n\n#include <utility>\n#include <vector>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n\nnamespace metaforce {\nclass CSimplePool;\nclass CGenDescription;\n\nclass CSpawnSystemKeyframeData {\npublic:\n  class CSpawnSystemKeyframeInfo {\n    friend class CSpawnSystemKeyframeData;\n    u32 x0_id;\n    u32 x4;\n    u32 x8;\n    u32 xc;\n    TLockedToken<CGenDescription> x10_token;\n    bool x18_found = false;\n    void LoadToken(CSimplePool* pool);\n\n  public:\n    explicit CSpawnSystemKeyframeInfo(CInputStream& in);\n    TLockedToken<CGenDescription>& GetToken() { return x10_token; }\n  };\n\nprivate:\n  u32 x0;\n  u32 x4;\n  u32 x8_endFrame;\n  u32 xc;\n  std::vector<std::pair<u32, std::vector<CSpawnSystemKeyframeInfo>>> x10_spawns;\n\npublic:\n  explicit CSpawnSystemKeyframeData(CInputStream& in);\n  void LoadAllSpawnedSystemTokens(CSimplePool* pool);\n  std::vector<CSpawnSystemKeyframeInfo>& GetSpawnedSystemsAtFrame(u32 frame);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CSwooshDescription.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/Particle/CColorElement.hpp\"\n#include \"Runtime/Particle/CIntElement.hpp\"\n#include \"Runtime/Particle/CModVectorElement.hpp\"\n#include \"Runtime/Particle/CParticleDataFactory.hpp\"\n#include \"Runtime/Particle/CRealElement.hpp\"\n#include \"Runtime/Particle/CUVElement.hpp\"\n#include \"Runtime/Particle/CVectorElement.hpp\"\n\nnamespace metaforce {\nclass CSwooshDescription {\npublic:\n  std::unique_ptr<CIntElement> x0_PSLT;\n  std::unique_ptr<CRealElement> x4_TIME;\n  std::unique_ptr<CRealElement> x8_LRAD;\n  std::unique_ptr<CRealElement> xc_RRAD;\n  std::unique_ptr<CIntElement> x10_LENG;\n  std::unique_ptr<CColorElement> x14_COLR;\n  std::unique_ptr<CIntElement> x18_SIDE;\n  std::unique_ptr<CRealElement> x1c_IROT;\n  std::unique_ptr<CRealElement> x20_ROTM;\n  std::unique_ptr<CVectorElement> x24_POFS;\n  std::unique_ptr<CVectorElement> x28_IVEL;\n  std::unique_ptr<CVectorElement> x2c_NPOS;\n  std::unique_ptr<CModVectorElement> x30_VELM;\n  std::unique_ptr<CModVectorElement> x34_VLM2;\n  std::unique_ptr<CIntElement> x38_SPLN;\n  std::unique_ptr<CUVElement> x3c_TEXR;\n  std::unique_ptr<CIntElement> x40_TSPN;\n  bool x44_24_LLRD : 1 = false;\n  bool x44_25_CROS : 1 = true;\n  bool x44_26_VLS1 : 1 = false;\n  bool x44_27_VLS2 : 1 = false;\n  bool x44_28_SROT : 1 = false;\n  bool x44_29_WIRE : 1 = false;\n  bool x44_30_TEXW : 1 = false;\n  bool x44_31_AALP : 1 = false;\n  bool x45_24_ZBUF : 1 = false;\n  bool x45_25_ORNT : 1 = false;\n  bool x45_26_CRND : 1 = false;\n\n  CSwooshDescription() = default;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CUVElement.cpp",
    "content": "#include \"Runtime/Particle/CUVElement.hpp\"\n\n/* Documentation at: https://wiki.axiodl.com/w/Particle_Script#UV_Elements */\n\nnamespace metaforce {\n\nCUVEAnimTexture::CUVEAnimTexture(TToken<CTexture>&& tex, std::unique_ptr<CIntElement>&& tileW,\n                                 std::unique_ptr<CIntElement>&& tileH, std::unique_ptr<CIntElement>&& strideW,\n                                 std::unique_ptr<CIntElement>&& strideH, std::unique_ptr<CIntElement>&& cycleFrames,\n                                 bool loop)\n: x4_tex(std::move(tex)), x24_loop(loop), x28_cycleFrames(std::move(cycleFrames)) {\n  tileW->GetValue(0, x10_tileW);\n  tileH->GetValue(0, x14_tileH);\n  strideW->GetValue(0, x18_strideW);\n  strideH->GetValue(0, x1c_strideH);\n\n  const int width = int(x4_tex.GetObj()->GetWidth());\n  const int height = int(x4_tex.GetObj()->GetHeight());\n  const float widthF = float(width);\n  const float heightF = float(height);\n  const int xTiles = std::max(1, width / x18_strideW);\n  const int yTiles = std::max(1, height / x1c_strideH);\n\n  x20_tiles = xTiles * yTiles;\n  x2c_uvElems.reserve(x20_tiles);\n\n  for (int y = yTiles - 1; y >= 0; --y) {\n    for (int x = 0; x < xTiles; ++x) {\n      const int px = x18_strideW * x;\n      const int px2 = px + x10_tileW;\n      const int py = x1c_strideH * y;\n      const int py2 = py + x14_tileH;\n\n      x2c_uvElems.push_back({\n          float(px) / widthF,\n          float(py) / heightF,\n          float(px2) / widthF,\n          float(py2) / heightF,\n      });\n    }\n  }\n}\n\nvoid CUVEAnimTexture::GetValueUV(int frame, SUVElementSet& valOut) const {\n  int cv = 1;\n  x28_cycleFrames->GetValue(frame, cv);\n  float cvf = float(cv) / float(x20_tiles);\n  cvf = float(frame) / cvf;\n\n  int tile = int(cvf);\n  if (x24_loop) {\n    // HACK\n    // Check bad values for cvf\n    tile = !(std::isnan(cvf) || std::isinf(cvf)) && int(cvf) >= 0 ? int(cvf) : 0;\n    if (tile >= x20_tiles) {\n      tile = tile % x20_tiles;\n    }\n  } else {\n    if (cvf >= float(x20_tiles)) {\n      tile = x20_tiles - 1;\n    }\n  }\n\n  valOut = x2c_uvElems[tile];\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CUVElement.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <vector>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Graphics/CTexture.hpp\"\n#include \"Runtime/Particle/IElement.hpp\"\n\n/* Documentation at: https://wiki.axiodl.com/w/Particle_Script#UV_Elements */\n\nnamespace metaforce {\nclass CToken;\n\nstruct SUVElementSet {\n  float xMin, yMin, xMax, yMax;\n};\n\nclass CUVElement : public IElement {\npublic:\n  virtual TLockedToken<CTexture> GetValueTexture(int frame) const = 0;\n  virtual void GetValueUV(int frame, SUVElementSet& valOut) const = 0;\n  virtual bool HasConstantTexture() const = 0;\n  virtual bool HasConstantUV() const = 0;\n};\n\nclass CUVEConstant : public CUVElement {\n  TLockedToken<CTexture> x4_tex;\n\npublic:\n  explicit CUVEConstant(TToken<CTexture>&& tex) : x4_tex(std::move(tex)) {}\n  TLockedToken<CTexture> GetValueTexture([[maybe_unused]] int frame) const override {\n    return TLockedToken<CTexture>(x4_tex);\n  }\n  void GetValueUV([[maybe_unused]] int frame, SUVElementSet& valOut) const override { valOut = {0.f, 0.f, 1.f, 1.f}; }\n  bool HasConstantTexture() const override { return true; }\n  bool HasConstantUV() const override { return true; }\n};\n\nclass CUVEAnimTexture : public CUVElement {\n  TLockedToken<CTexture> x4_tex;\n  int x10_tileW, x14_tileH, x18_strideW, x1c_strideH;\n  int x20_tiles;\n  bool x24_loop;\n  std::unique_ptr<CIntElement> x28_cycleFrames;\n  std::vector<SUVElementSet> x2c_uvElems;\n\npublic:\n  CUVEAnimTexture(TToken<CTexture>&& tex, std::unique_ptr<CIntElement>&& tileW, std::unique_ptr<CIntElement>&& tileH,\n                  std::unique_ptr<CIntElement>&& strideW, std::unique_ptr<CIntElement>&& strideH,\n                  std::unique_ptr<CIntElement>&& cycleFrames, bool loop);\n  TLockedToken<CTexture> GetValueTexture([[maybe_unused]] int frame) const override {\n    return TLockedToken<CTexture>(x4_tex);\n  }\n  void GetValueUV(int frame, SUVElementSet& valOut) const override;\n  bool HasConstantTexture() const override { return true; }\n  bool HasConstantUV() const override { return false; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CVectorElement.cpp",
    "content": "#include \"Runtime/Particle/CVectorElement.hpp\"\n\n#include \"Runtime/CRandom16.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/Particle/CGenDescription.hpp\"\n#include \"Runtime/Particle/CParticleGlobals.hpp\"\n\n#include <zeus/Math.hpp>\n\n/* Documentation at: https://wiki.axiodl.com/w/Particle_Script#Vector_Elements */\n\nnamespace metaforce {\n\nCVEKeyframeEmitter::CVEKeyframeEmitter(CInputStream& in) {\n  x4_percent = in.ReadLong();\n  x8_unk1 = in.ReadLong();\n  xc_loop = in.ReadBool();\n  xd_unk2 = in.ReadBool();\n  x10_loopEnd = in.ReadLong();\n  x14_loopStart = in.ReadLong();\n\n  const u32 count = in.ReadLong();\n  x18_keys.reserve(count);\n  for (u32 i = 0; i < count; ++i) {\n    x18_keys.emplace_back(in.Get<zeus::CVector3f>());\n  }\n}\n\nbool CVEKeyframeEmitter::GetValue([[maybe_unused]] int frame, zeus::CVector3f& valOut) const {\n  if (!x4_percent) {\n    int emitterTime = CParticleGlobals::instance()->m_EmitterTime;\n    int calcKey = emitterTime;\n    if (xc_loop) {\n      if (emitterTime >= x10_loopEnd) {\n        int v1 = emitterTime - x14_loopStart;\n        int v2 = x10_loopEnd - x14_loopStart;\n        calcKey = v1 % v2;\n        calcKey += x14_loopStart;\n      }\n    } else {\n      int v1 = x10_loopEnd - 1;\n      if (v1 < emitterTime)\n        calcKey = v1;\n    }\n    valOut = x18_keys[calcKey];\n  } else {\n    int ltPerc = CParticleGlobals::instance()->m_ParticleLifetimePercentage;\n    float ltPercRem = CParticleGlobals::instance()->m_ParticleLifetimePercentageRemainder;\n    if (ltPerc == 100)\n      valOut = x18_keys[100];\n    else\n      valOut = ltPercRem * x18_keys[ltPerc + 1] + (1.0f - ltPercRem) * x18_keys[ltPerc];\n  }\n  return false;\n}\n\nCVECone::CVECone(std::unique_ptr<CVectorElement>&& a, std::unique_ptr<CRealElement>&& b)\n: x4_direction(std::move(a)), x8_magnitude(std::move(b)) {\n  zeus::CVector3f av;\n  x4_direction->GetValue(0, av);\n  zeus::CVector3f avNorm = av.normalized();\n  if (avNorm.x() > 0.8f)\n    xc_xVec = av.cross(zeus::CVector3f(0.f, 1.f, 0.f));\n  else\n    xc_xVec = av.cross(zeus::CVector3f(1.f, 0.f, 0.f));\n  x18_yVec = avNorm.cross(xc_xVec);\n}\n\nbool CVECone::GetValue(int frame, zeus::CVector3f& valOut) const {\n  float b;\n  x8_magnitude->GetValue(frame, b);\n  zeus::CVector3f dir;\n  x4_direction->GetValue(frame, dir);\n  float b2 = std::min(1.f, b);\n\n  float randX, randY;\n  do {\n    float rand1 = CRandom16::GetRandomNumber()->Float() - 0.5f;\n    randX = 2.f * b2 * rand1;\n    float rand2 = CRandom16::GetRandomNumber()->Float() - 0.5f;\n    randY = 2.f * b2 * rand2;\n  } while (randX * randX + randY * randY > 1.f);\n\n  valOut = xc_xVec * randX + x18_yVec * randY + dir;\n  return false;\n}\n\nbool CVETimeChain::GetValue(int frame, zeus::CVector3f& valOut) const {\n  int v;\n  xc_swFrame->GetValue(frame, v);\n  if (frame >= v)\n    return x8_b->GetValue(frame, valOut);\n  else\n    return x4_a->GetValue(frame, valOut);\n}\n\nbool CVEAngleCone::GetValue(int frame, zeus::CVector3f& valOut) const {\n  float xc, yc, xr, yr;\n  x4_angleXConstant->GetValue(frame, xc);\n  x8_angleYConstant->GetValue(frame, yc);\n  xc_angleXRange->GetValue(frame, xr);\n  x10_angleYRange->GetValue(frame, yr);\n\n  float xtmp = CRandom16::GetRandomNumber()->Float() * xr;\n  float xang = zeus::degToRad(0.5f * xr - xtmp + xc);\n\n  float ytmp = CRandom16::GetRandomNumber()->Float() * yr;\n  float yang = zeus::degToRad(0.5f * yr - ytmp + yc);\n\n  float mag;\n  x14_magnitude->GetValue(frame, mag);\n\n  /* This takes a +Z vector and rotates it around X and Y axis (like a rotation matrix would) */\n  valOut = zeus::CVector3f(std::cos(xang) * -std::sin(yang), std::sin(xang), std::cos(xang) * std::cos(yang)) *\n           zeus::CVector3f(mag);\n  return false;\n}\n\nbool CVEAdd::GetValue(int frame, zeus::CVector3f& valOut) const {\n  zeus::CVector3f a, b;\n  x4_a->GetValue(frame, a);\n  x8_b->GetValue(frame, b);\n  valOut = a + b;\n  return false;\n}\n\nCVECircleCluster::CVECircleCluster(std::unique_ptr<CVectorElement>&& origin, std::unique_ptr<CVectorElement>&& xVec,\n                                   std::unique_ptr<CIntElement>&& deltaAngle, std::unique_ptr<CRealElement>&& magnitude)\n: x4_origin(std::move(origin)), x24_magnitude(std::move(magnitude)) {\n  int cv;\n  deltaAngle->GetValue(0, cv);\n  x20_deltaAngle = zeus::degToRad(360.f / float(cv));\n\n  zeus::CVector3f bv;\n  xVec->GetValue(0, bv);\n  bv.normalize();\n  if (bv[0] > 0.8f)\n    x8_xVec = bv.cross(zeus::CVector3f(0.f, 1.f, 0.f));\n  else\n    x8_xVec = bv.cross(zeus::CVector3f(1.f, 0.f, 0.f));\n  x14_yVec = bv.cross(x8_xVec);\n}\n\nbool CVECircleCluster::GetValue(int frame, zeus::CVector3f& valOut) const {\n  zeus::CVector3f av;\n  x4_origin->GetValue(frame, av);\n\n  float curAngle = frame * x20_deltaAngle;\n  zeus::CVector3f x = x8_xVec * std::cos(curAngle);\n  zeus::CVector3f y = x14_yVec * std::sin(curAngle);\n  zeus::CVector3f tv = x + y + av;\n\n  float dv;\n  x24_magnitude->GetValue(frame, dv);\n\n  zeus::CVector3f magVec(dv * tv.magnitude());\n  zeus::CVector3f rv =\n      magVec * zeus::CVector3f(CRandom16::GetRandomNumber()->Float(), CRandom16::GetRandomNumber()->Float(),\n                               CRandom16::GetRandomNumber()->Float());\n\n  valOut = tv + rv;\n  return false;\n}\n\nbool CVEConstant::GetValue(int frame, zeus::CVector3f& valOut) const {\n  float a, b, c;\n  x4_a->GetValue(frame, a);\n  x8_b->GetValue(frame, b);\n  xc_c->GetValue(frame, c);\n  valOut = zeus::CVector3f(a, b, c);\n  return false;\n}\n\nbool CVEFastConstant::GetValue([[maybe_unused]] int frame, zeus::CVector3f& valOut) const {\n  valOut = x4_val;\n  return false;\n}\n\nCVECircle::CVECircle(std::unique_ptr<CVectorElement>&& direction, std::unique_ptr<CVectorElement>&& upVector,\n                     std::unique_ptr<CRealElement>&& angleConstant, std::unique_ptr<CRealElement>&& angleLinear,\n                     std::unique_ptr<CRealElement>&& radius)\n: x4_direction(std::move(direction)), x20_angleConstant(std::move(angleConstant)), x24_angleLinear(std::move(angleLinear)), x28_radius(std::move(radius)) {\n  zeus::CVector3f bv;\n  upVector->GetValue(0, bv);\n  bv.normalize();\n  if (bv[0] > 0.8f)\n    x8_xVec = bv.cross(zeus::CVector3f(0.f, 1.f, 0.f));\n  else\n    x8_xVec = bv.cross(zeus::CVector3f(1.f, 0.f, 0.f));\n  x14_yVec = bv.cross(x8_xVec);\n}\n\nbool CVECircle::GetValue(int frame, zeus::CVector3f& valOut) const {\n  float c, d, e;\n  x20_angleConstant->GetValue(frame, c);\n  x24_angleLinear->GetValue(frame, d);\n  x28_radius->GetValue(frame, e);\n\n  float curAngle = zeus::degToRad(d * frame + c);\n\n  zeus::CVector3f av;\n  x4_direction->GetValue(frame, av);\n\n  zeus::CVector3f x = x8_xVec * e * std::cos(curAngle);\n  zeus::CVector3f y = x14_yVec * e * std::sin(curAngle);\n\n  valOut = x + y + av;\n  return false;\n}\n\nbool CVEMultiply::GetValue(int frame, zeus::CVector3f& valOut) const {\n  zeus::CVector3f a, b;\n  x4_a->GetValue(frame, a);\n  x8_b->GetValue(frame, b);\n  valOut = a * b;\n  return false;\n}\n\nbool CVERealToVector::GetValue(int frame, zeus::CVector3f& valOut) const {\n  float a;\n  x4_a->GetValue(frame, a);\n  valOut = zeus::CVector3f(a);\n  return false;\n}\n\nbool CVEPulse::GetValue(int frame, zeus::CVector3f& valOut) const {\n  int a, b;\n  x4_aDuration->GetValue(frame, a);\n  x8_bDuration->GetValue(frame, b);\n  int cv = a + b + 1;\n  if (cv < 0) {\n    cv = 1;\n  }\n\n  if (b < 1 || frame % cv <= a) {\n    xc_aVal->GetValue(frame, valOut);\n  } else {\n    x10_bVal->GetValue(frame, valOut);\n  }\n  return false;\n}\n\nbool CVEParticleVelocity::GetValue(int /*frame*/, zeus::CVector3f& valOut) const {\n  valOut = CElementGen::g_currentParticle->x1c_vel;\n  return false;\n}\n\nbool CVEParticlePreviousLocation::GetValue(int /*frame*/, zeus::CVector3f& valOut) const {\n  valOut = CElementGen::g_currentParticle->x10_prevPos;\n  return false;\n}\n\nbool CVEParticleLocation::GetValue(int /*frame*/, zeus::CVector3f& valOut) const {\n  valOut = CElementGen::g_currentParticle->x4_pos;\n  return false;\n}\n\nbool CVEParticleSystemOrientationFront::GetValue(int /*frame*/, zeus::CVector3f& valOut) const {\n  zeus::CMatrix4f trans =\n      CParticleGlobals::instance()->m_currentParticleSystem->x4_system->GetOrientation().toMatrix4f().transposed();\n  valOut.assign(trans.m[0].y(), trans.m[1].y(), trans.m[2].y());\n  return false;\n}\n\nbool CVEParticleSystemOrientationUp::GetValue(int /*frame*/, zeus::CVector3f& valOut) const {\n  zeus::CMatrix4f trans =\n      CParticleGlobals::instance()->m_currentParticleSystem->x4_system->GetOrientation().toMatrix4f().transposed();\n  valOut.assign(trans.m[0].z(), trans.m[1].z(), trans.m[2].z());\n  return false;\n}\n\nbool CVEParticleSystemOrientationRight::GetValue(int /*frame*/, zeus::CVector3f& valOut) const {\n  zeus::CMatrix4f trans =\n      CParticleGlobals::instance()->m_currentParticleSystem->x4_system->GetOrientation().toMatrix4f().transposed();\n  valOut.assign(trans.m[0].x(), trans.m[1].x(), trans.m[2].x());\n  return false;\n}\n\nbool CVEParticleSystemTranslation::GetValue(int /*frame*/, zeus::CVector3f& valOut) const {\n  valOut = CParticleGlobals::instance()->m_currentParticleSystem->x4_system->GetTranslation();\n  return false;\n}\n\nbool CVESubtract::GetValue(int frame, zeus::CVector3f& valOut) const {\n  zeus::CVector3f a, b;\n  x4_a->GetValue(frame, a);\n  x8_b->GetValue(frame, b);\n  valOut = a - b;\n  return false;\n}\n\nbool CVEColorToVector::GetValue(int frame, zeus::CVector3f& valOut) const {\n  zeus::CColor val = {0.0f, 0.0f, 0.0f, 1.0f};\n  x4_a->GetValue(frame, val);\n  valOut = zeus::CVector3f{val.mSimd};\n  return false;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CVectorElement.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <vector>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/Particle/IElement.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\n/* Documentation at: https://wiki.axiodl.com/w/Particle_Script#Vector_Elements */\n\nnamespace metaforce {\n\nclass CVEKeyframeEmitter : public CVectorElement {\n  u32 x4_percent;\n  u32 x8_unk1;\n  bool xc_loop;\n  bool xd_unk2;\n  u32 x10_loopEnd;\n  u32 x14_loopStart;\n  std::vector<zeus::CVector3f> x18_keys;\n\npublic:\n  explicit CVEKeyframeEmitter(CInputStream& in);\n  bool GetValue(int frame, zeus::CVector3f& valOut) const override;\n};\n\nclass CVECone : public CVectorElement {\n  std::unique_ptr<CVectorElement> x4_direction;\n  std::unique_ptr<CRealElement> x8_magnitude;\n  zeus::CVector3f xc_xVec;\n  zeus::CVector3f x18_yVec;\n\npublic:\n  CVECone(std::unique_ptr<CVectorElement>&& a, std::unique_ptr<CRealElement>&& b);\n  bool GetValue(int frame, zeus::CVector3f& valOut) const override;\n};\n\nclass CVETimeChain : public CVectorElement {\n  std::unique_ptr<CVectorElement> x4_a;\n  std::unique_ptr<CVectorElement> x8_b;\n  std::unique_ptr<CIntElement> xc_swFrame;\n\npublic:\n  CVETimeChain(std::unique_ptr<CVectorElement>&& a, std::unique_ptr<CVectorElement>&& b,\n               std::unique_ptr<CIntElement>&& c)\n  : x4_a(std::move(a)), x8_b(std::move(b)), xc_swFrame(std::move(c)) {}\n  bool GetValue(int frame, zeus::CVector3f& valOut) const override;\n};\n\nclass CVEAngleCone : public CVectorElement {\n  std::unique_ptr<CRealElement> x4_angleXConstant;\n  std::unique_ptr<CRealElement> x8_angleYConstant;\n  std::unique_ptr<CRealElement> xc_angleXRange;\n  std::unique_ptr<CRealElement> x10_angleYRange;\n  std::unique_ptr<CRealElement> x14_magnitude;\n\npublic:\n  CVEAngleCone(std::unique_ptr<CRealElement>&& a, std::unique_ptr<CRealElement>&& b, std::unique_ptr<CRealElement>&& c,\n               std::unique_ptr<CRealElement>&& d, std::unique_ptr<CRealElement>&& e)\n  : x4_angleXConstant(std::move(a))\n  , x8_angleYConstant(std::move(b))\n  , xc_angleXRange(std::move(c))\n  , x10_angleYRange(std::move(d))\n  , x14_magnitude(std::move(e)) {}\n  bool GetValue(int frame, zeus::CVector3f& valOut) const override;\n};\n\nclass CVEAdd : public CVectorElement {\n  std::unique_ptr<CVectorElement> x4_a;\n  std::unique_ptr<CVectorElement> x8_b;\n\npublic:\n  CVEAdd(std::unique_ptr<CVectorElement>&& a, std::unique_ptr<CVectorElement>&& b)\n  : x4_a(std::move(a)), x8_b(std::move(b)) {}\n  bool GetValue(int frame, zeus::CVector3f& valOut) const override;\n};\n\nclass CVECircleCluster : public CVectorElement {\n  std::unique_ptr<CVectorElement> x4_origin;\n  zeus::CVector3f x8_xVec;\n  zeus::CVector3f x14_yVec;\n  float x20_deltaAngle;\n  std::unique_ptr<CRealElement> x24_magnitude;\n\npublic:\n  CVECircleCluster(std::unique_ptr<CVectorElement>&& origin, std::unique_ptr<CVectorElement>&& xVec,\n                   std::unique_ptr<CIntElement>&& deltaAngle, std::unique_ptr<CRealElement>&& magnitude);\n  bool GetValue(int frame, zeus::CVector3f& valOut) const override;\n};\n\nclass CVEConstant : public CVectorElement {\n  std::unique_ptr<CRealElement> x4_a;\n  std::unique_ptr<CRealElement> x8_b;\n  std::unique_ptr<CRealElement> xc_c;\n\npublic:\n  CVEConstant(std::unique_ptr<CRealElement>&& a, std::unique_ptr<CRealElement>&& b, std::unique_ptr<CRealElement>&& c)\n  : x4_a(std::move(a)), x8_b(std::move(b)), xc_c(std::move(c)) {}\n  bool GetValue(int frame, zeus::CVector3f& valOut) const override;\n};\n\nclass CVEFastConstant : public CVectorElement {\n  zeus::CVector3f x4_val;\n\npublic:\n  CVEFastConstant(float a, float b, float c) : x4_val(a, b, c) {}\n  bool GetValue(int frame, zeus::CVector3f& valOut) const override;\n  bool IsFastConstant() const override { return true; }\n};\n\nclass CVECircle : public CVectorElement {\n  std::unique_ptr<CVectorElement> x4_direction;\n  zeus::CVector3f x8_xVec;\n  zeus::CVector3f x14_yVec;\n  std::unique_ptr<CRealElement> x20_angleConstant;\n  std::unique_ptr<CRealElement> x24_angleLinear;\n  std::unique_ptr<CRealElement> x28_radius;\n\npublic:\n  CVECircle(std::unique_ptr<CVectorElement>&& direction, std::unique_ptr<CVectorElement>&& upVector, std::unique_ptr<CRealElement>&& angleConstant,\n            std::unique_ptr<CRealElement>&& angleLinear, std::unique_ptr<CRealElement>&& radius);\n  bool GetValue(int frame, zeus::CVector3f& valOut) const override;\n};\n\nclass CVEMultiply : public CVectorElement {\n  std::unique_ptr<CVectorElement> x4_a;\n  std::unique_ptr<CVectorElement> x8_b;\n\npublic:\n  CVEMultiply(std::unique_ptr<CVectorElement>&& a, std::unique_ptr<CVectorElement>&& b)\n  : x4_a(std::move(a)), x8_b(std::move(b)) {}\n  bool GetValue(int frame, zeus::CVector3f& valOut) const override;\n};\n\nclass CVERealToVector : public CVectorElement {\n  std::unique_ptr<CRealElement> x4_a;\n\npublic:\n  explicit CVERealToVector(std::unique_ptr<CRealElement>&& a) : x4_a(std::move(a)) {}\n  bool GetValue(int frame, zeus::CVector3f& valOut) const override;\n};\n\nclass CVEPulse : public CVectorElement {\n  std::unique_ptr<CIntElement> x4_aDuration;\n  std::unique_ptr<CIntElement> x8_bDuration;\n  std::unique_ptr<CVectorElement> xc_aVal;\n  std::unique_ptr<CVectorElement> x10_bVal;\n\npublic:\n  CVEPulse(std::unique_ptr<CIntElement>&& a, std::unique_ptr<CIntElement>&& b, std::unique_ptr<CVectorElement>&& c,\n           std::unique_ptr<CVectorElement>&& d)\n  : x4_aDuration(std::move(a)), x8_bDuration(std::move(b)), xc_aVal(std::move(c)), x10_bVal(std::move(d)) {}\n  bool GetValue(int frame, zeus::CVector3f& valOut) const override;\n};\n\nclass CVEParticleVelocity : public CVectorElement {\npublic:\n  bool GetValue(int frame, zeus::CVector3f& valOut) const override;\n};\n\nclass CVEParticlePreviousLocation : public CVectorElement {\npublic:\n  bool GetValue(int frame, zeus::CVector3f& valOut) const override;\n};\n\nclass CVEParticleLocation : public CVectorElement {\npublic:\n  bool GetValue(int frame, zeus::CVector3f& valOut) const override;\n};\n\nclass CVEParticleSystemOrientationFront : public CVectorElement {\npublic:\n  bool GetValue(int frame, zeus::CVector3f& valOut) const override;\n};\n\nclass CVEParticleSystemOrientationUp : public CVectorElement {\npublic:\n  bool GetValue(int frame, zeus::CVector3f& valOut) const override;\n};\n\nclass CVEParticleSystemOrientationRight : public CVectorElement {\npublic:\n  bool GetValue(int frame, zeus::CVector3f& valOut) const override;\n};\n\nclass CVEParticleSystemTranslation : public CVectorElement {\npublic:\n  bool GetValue(int frame, zeus::CVector3f& valOut) const override;\n};\n\nclass CVESubtract : public CVectorElement {\n  std::unique_ptr<CVectorElement> x4_a;\n  std::unique_ptr<CVectorElement> x8_b;\n\npublic:\n  CVESubtract(std::unique_ptr<CVectorElement>&& a, std::unique_ptr<CVectorElement>&& b)\n  : x4_a(std::move(a)), x8_b(std::move(b)) {}\n  bool GetValue(int frame, zeus::CVector3f& valOut) const override;\n};\n\nclass CVEColorToVector : public CVectorElement {\n  std::unique_ptr<CColorElement> x4_a;\n\npublic:\n  explicit CVEColorToVector(std::unique_ptr<CColorElement>&& a) : x4_a(std::move(a)) {}\n\n  bool GetValue(int frame, zeus::CVector3f& valOut) const override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CWarp.hpp",
    "content": "#pragma once\n\n#include <vector>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Particle/CParticleGen.hpp\"\n\nnamespace metaforce {\n\nclass CWarp {\npublic:\n  virtual ~CWarp() = default;\n  virtual bool UpdateWarp() = 0;\n  virtual void ModifyParticles(std::vector<CParticle>& particles) = 0;\n  virtual void Activate(bool) = 0;\n  virtual bool IsActivated() = 0;\n  virtual FourCC Get4CharID() = 0;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/CWeaponDescription.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Particle/CColorElement.hpp\"\n#include \"Runtime/Particle/CIntElement.hpp\"\n#include \"Runtime/Particle/CModVectorElement.hpp\"\n#include \"Runtime/Particle/CParticleDataFactory.hpp\"\n#include \"Runtime/Particle/CRealElement.hpp\"\n#include \"Runtime/Particle/CVectorElement.hpp\"\n\nnamespace metaforce {\nclass CCollisionResponseData;\n\nusing SCollisionResponseData = STokenDesc<CCollisionResponseData>;\n\nclass CWeaponDescription {\npublic:\n  std::unique_ptr<CVectorElement> x0_IORN;\n  std::unique_ptr<CVectorElement> x4_IVEC;\n  std::unique_ptr<CVectorElement> x8_PSOV;\n  std::unique_ptr<CModVectorElement> xc_PSVM;\n  bool x10_VMD2 = false;\n  std::unique_ptr<CIntElement> x14_PSLT;\n  std::unique_ptr<CVectorElement> x18_PSCL;\n  std::unique_ptr<CColorElement> x1c_PCOL;\n  std::unique_ptr<CVectorElement> x20_POFS;\n  std::unique_ptr<CVectorElement> x24_OFST;\n  bool x28_APSO = false;\n  bool x29_HOMG = false;\n  bool x2a_AP11 = false;\n  bool x2b_AP21 = false;\n  bool x2c_AS11 = false;\n  bool x2d_AS12 = false;\n  bool x2e_AS13 = false;\n  std::unique_ptr<CRealElement> x30_TRAT;\n  SChildGeneratorDesc x34_APSM;\n  SChildGeneratorDesc x44_APS2;\n  SSwooshGeneratorDesc x54_ASW1;\n  SSwooshGeneratorDesc x64_ASW2;\n  SSwooshGeneratorDesc x74_ASW3;\n  SParticleModel x84_OHEF;\n  SCollisionResponseData x94_COLR;\n  bool xa4_EWTR = true;\n  bool xa5_LWTR = true;\n  bool xa6_SWTR = true;\n  s32 xa8_PJFX = -1;\n  std::unique_ptr<CRealElement> xac_RNGE;\n  std::unique_ptr<CRealElement> xb0_FOFF;\n  // PAL/RS5\n  bool x28_SPS1 = false;\n  bool x29_SPS2 = false;\n  bool x29_FC60 = false;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Particle/IElement.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n\n#include <zeus/CColor.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\n\nclass IElement {\npublic:\n  virtual ~IElement() = default;\n};\n\nclass CRealElement : public IElement {\npublic:\n  virtual bool GetValue(int frame, float& valOut) const = 0;\n  virtual bool IsConstant() const { return false; }\n};\n\nclass CIntElement : public IElement {\npublic:\n  virtual bool GetValue(int frame, int& valOut) const = 0;\n  virtual int GetMaxValue() const = 0;\n};\n\nclass CVectorElement : public IElement {\npublic:\n  virtual bool GetValue(int frame, zeus::CVector3f& valOut) const = 0;\n  virtual bool IsFastConstant() const { return false; }\n};\n\nclass CModVectorElement : public IElement {\npublic:\n  virtual bool GetValue(int frame, zeus::CVector3f& pVel, zeus::CVector3f& pPos) const = 0;\n};\n\nclass CColorElement : public IElement {\npublic:\n  virtual bool GetValue(int frame, zeus::CColor& colorOut) const = 0;\n};\n\nclass CEmitterElement : public IElement {\npublic:\n  virtual bool GetValue(int frame, zeus::CVector3f& pPos, zeus::CVector3f& pVel) const = 0;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/RetroTypes.cpp",
    "content": "#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/IMain.hpp\"\n#include \"Runtime/Logging.hpp\"\n\nnamespace metaforce {\nSObjectTag::SObjectTag(CInputStream& in) {\n  in.ReadBytes(reinterpret_cast<u8*>(&type), 4);\n  id = in.Get<CAssetId>();\n}\n\nvoid SObjectTag::ReadMLVL(CInputStream& in) {\n  id = in.Get<CAssetId>();\n  in.ReadBytes(reinterpret_cast<u8*>(&type), 4);\n}\n\nCAssetId::CAssetId(CInputStream& in) {\n  if (g_Main != nullptr) {\n    if (g_Main->GetExpectedIdSize() == sizeof(u32)) {\n      Assign(u32(in.ReadLong()));\n    } else if (g_Main->GetExpectedIdSize() == sizeof(u64)) {\n      Assign(in.ReadLongLong());\n    } else {\n      spdlog::fatal(\"Unsupported id length {}\", g_Main->GetExpectedIdSize());\n    }\n  } else {\n    spdlog::fatal(\"Input constructor called before runtime Main entered!\");\n  }\n}\n\nvoid CAssetId::PutTo(COutputStream& out) const {\n  if (g_Main != nullptr) {\n    if (g_Main->GetExpectedIdSize() == sizeof(u32)) {\n      out.Put(u32(id));\n    } else if (g_Main->GetExpectedIdSize() == sizeof(u64)) {\n      out.Put(id);\n    } else {\n      spdlog::fatal(\"Unsupported id length {}\", g_Main->GetExpectedIdSize());\n    }\n  } else {\n    spdlog::fatal(\"PutTo called before runtime Main entered!\");\n  }\n}\n\n} // namespace metaforce"
  },
  {
    "path": "Runtime/RetroTypes.hpp",
    "content": "#pragma once\n\n#include <functional>\n#include <optional>\n#include <string>\n#include <vector>\n\n#include \"GCNTypes.hpp\"\n#include \"rstl.hpp\"\n\n#undef min\n#undef max\n\nusing namespace std::literals;\n\nnamespace metaforce {\nclass CInputStream;\nclass COutputStream;\nusing kUniqueIdType = u16;\nstatic constexpr int kMaxEntities = 1024;\nconstexpr kUniqueIdType kUniqueIdSize = sizeof(u16);\nconstexpr kUniqueIdType kUniqueIdBits = kUniqueIdSize * 8;\nconstexpr kUniqueIdType kUniqueIdMax = UINT16_MAX;\nconstexpr kUniqueIdType kUniqueIdVersionMax = 64;\nconstexpr kUniqueIdType kUniqueIdVersionMask = kUniqueIdVersionMax - 1;\nconstexpr kUniqueIdType kUniqueIdValueMask = kMaxEntities - 1;\nconstexpr kUniqueIdType kUniqueIdValueBits = 10;\nconstexpr kUniqueIdType kUniqueIdVersionBits = 6;\n\n#undef bswap16\n#undef bswap32\n#undef bswap64\n\n/* Type-sensitive byte swappers */\ntemplate <typename T>\nconstexpr T bswap16(T val) noexcept {\n#if __GNUC__\n  return __builtin_bswap16(val);\n#elif _WIN32\n  return _byteswap_ushort(val);\n#else\n  return (val = (val << 8) | ((val >> 8) & 0xFF));\n#endif\n}\n\ntemplate <typename T>\nconstexpr T bswap32(T val) noexcept {\n#if __GNUC__\n  return __builtin_bswap32(val);\n#elif _WIN32\n  return _byteswap_ulong(val);\n#else\n  val = (val & 0x0000FFFF) << 16 | (val & 0xFFFF0000) >> 16;\n  val = (val & 0x00FF00FF) << 8 | (val & 0xFF00FF00) >> 8;\n  return val;\n#endif\n}\n\ntemplate <typename T>\nconstexpr T bswap64(T val) noexcept {\n#if __GNUC__\n  return __builtin_bswap64(val);\n#elif _WIN32\n  return _byteswap_uint64(val);\n#else\n  return ((val & 0xFF00000000000000ULL) >> 56) | ((val & 0x00FF000000000000ULL) >> 40) |\n         ((val & 0x0000FF0000000000ULL) >> 24) | ((val & 0x000000FF00000000ULL) >> 8) |\n         ((val & 0x00000000FF000000ULL) << 8) | ((val & 0x0000000000FF0000ULL) << 24) |\n         ((val & 0x000000000000FF00ULL) << 40) | ((val & 0x00000000000000FFULL) << 56);\n#endif\n}\n\n#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__\nconstexpr int16_t SBig(int16_t val) noexcept { return bswap16(val); }\nconstexpr uint16_t SBig(uint16_t val) noexcept { return bswap16(val); }\nconstexpr int32_t SBig(int32_t val) noexcept { return bswap32(val); }\nconstexpr uint32_t SBig(uint32_t val) noexcept { return bswap32(val); }\nconstexpr int64_t SBig(int64_t val) noexcept { return bswap64(val); }\nconstexpr uint64_t SBig(uint64_t val) noexcept { return bswap64(val); }\nconstexpr float SBig(float val) noexcept {\n  union {\n    float f;\n    u32 i;\n  } uval1 = {val};\n  union {\n    u32 i;\n    float f;\n  } uval2 = {bswap32(uval1.i)};\n  return uval2.f;\n}\nconstexpr double SBig(double val) noexcept {\n  union {\n    double f;\n    u32 i;\n  } uval1 = {val};\n  union {\n    u32 i;\n    double f;\n  } uval2 = {bswap64(uval1.i)};\n  return uval2.f;\n}\n#ifndef SBIG\n#define SBIG(q)                                                                                                        \\\n  (((q) & 0x000000FF) << 24 | ((q) & 0x0000FF00) << 8 | ((q) & 0x00FF0000) >> 8 | ((q) & 0xFF000000) >> 24)\n#endif\n\nconstexpr int16_t SLittle(int16_t val) noexcept { return val; }\nconstexpr uint16_t SLittle(uint16_t val) noexcept { return val; }\nconstexpr int32_t SLittle(int32_t val) noexcept { return val; }\nconstexpr uint32_t SLittle(uint32_t val) noexcept { return val; }\nconstexpr int64_t SLittle(int64_t val) noexcept { return val; }\nconstexpr uint64_t SLittle(uint64_t val) noexcept { return val; }\nconstexpr float SLittle(float val) noexcept { return val; }\nconstexpr double SLittle(double val) noexcept { return val; }\n#ifndef SLITTLE\n#define SLITTLE(q) (q)\n#endif\n#else\nconstexpr int16_t SLittle(int16_t val) noexcept { return bswap16(val); }\nconstexpr uint16_t SLittle(uint16_t val) noexcept { return bswap16(val); }\nconstexpr int32_t SLittle(int32_t val) noexcept { return bswap32(val); }\nconstexpr uint32_t SLittle(uint32_t val) noexcept { return bswap32(val); }\nconstexpr int64_t SLittle(int64_t val) noexcept { return bswap64(val); }\nconstexpr uint64_t SLittle(uint64_t val) noexcept { return bswap64(val); }\nconstexpr float SLittle(float val) noexcept {\n  int32_t ival = bswap32(*((int32_t*)(&val)));\n  return *((float*)(&ival));\n}\nconstexpr double SLittle(double val) noexcept {\n  int64_t ival = bswap64(*((int64_t*)(&val)));\n  return *((double*)(&ival));\n}\n#ifndef SLITTLE\n#define SLITTLE(q)                                                                                                     \\\n  (((q) & 0x000000FF) << 24 | ((q) & 0x0000FF00) << 8 | ((q) & 0x00FF0000) >> 8 | ((q) & 0xFF000000) >> 24)\n#endif\n\nconstexpr int16_t SBig(int16_t val) noexcept { return val; }\nconstexpr uint16_t SBig(uint16_t val) noexcept { return val; }\nconstexpr int32_t SBig(int32_t val) noexcept { return val; }\nconstexpr uint32_t SBig(uint32_t val) noexcept { return val; }\nconstexpr int64_t SBig(int64_t val) noexcept { return val; }\nconstexpr uint64_t SBig(uint64_t val) noexcept { return val; }\nconstexpr float SBig(float val) noexcept { return val; }\nconstexpr double SBig(double val) noexcept { return val; }\n#ifndef SBIG\n#define SBIG(q) (q)\n#endif\n#endif\n\nclass FourCC {\nprotected:\n  union {\n    char fcc[4];\n    uint32_t num = 0;\n  };\n\npublic:\n  // Sentinel FourCC\n  constexpr FourCC() noexcept = default;\n  constexpr FourCC(const FourCC& other) noexcept = default;\n  constexpr FourCC(FourCC&& other) noexcept = default;\n  constexpr FourCC(const char* name) noexcept : fcc{name[0], name[1], name[2], name[3]} {}\n  constexpr FourCC(uint32_t n) noexcept : num(n) {}\n\n  constexpr FourCC& operator=(const FourCC&) noexcept = default;\n  constexpr FourCC& operator=(FourCC&&) noexcept = default;\n\n  constexpr bool operator==(const FourCC& other) const noexcept { return num == other.num; }\n  constexpr bool operator!=(const FourCC& other) const noexcept { return !operator==(other); }\n  constexpr bool operator==(const char* other) const noexcept {\n    return other[0] == fcc[0] && other[1] == fcc[1] && other[2] == fcc[2] && other[3] == fcc[3];\n  }\n  constexpr bool operator!=(const char* other) const noexcept { return !operator==(other); }\n  constexpr bool operator==(int32_t other) const noexcept { return num == uint32_t(other); }\n  constexpr bool operator!=(int32_t other) const noexcept { return !operator==(other); }\n  constexpr bool operator==(uint32_t other) const noexcept { return num == other; }\n  constexpr bool operator!=(uint32_t other) const noexcept { return !operator==(other); }\n\n  std::string toString() const { return std::string(std::begin(fcc), std::end(fcc)); }\n  constexpr std::string_view toStringView() const { return std::string_view(fcc, std::size(fcc)); }\n  constexpr uint32_t toUint32() const noexcept { return num; }\n  constexpr const char* getChars() const noexcept { return fcc; }\n  constexpr char* getChars() noexcept { return fcc; }\n  constexpr bool IsValid() const noexcept { return num != 0; }\n};\n#define FOURCC(chars) FourCC(SBIG(chars))\n\nclass CAssetId {\n  u64 id = UINT64_MAX;\n\npublic:\n  constexpr CAssetId() noexcept = default;\n  constexpr CAssetId(u32 v) noexcept { Assign(u32(v)); }\n  constexpr CAssetId(u64 v) noexcept { Assign(v); }\n  explicit CAssetId(CInputStream& in);\n  [[nodiscard]] constexpr bool IsValid() const noexcept { return id != UINT64_MAX; }\n  [[nodiscard]] constexpr u64 Value() const noexcept { return id; }\n  constexpr void Assign(u64 v) noexcept { id = (v == UINT32_MAX ? UINT64_MAX : (v == 0 ? UINT64_MAX : v)); }\n  constexpr void Reset() noexcept { id = UINT64_MAX; }\n  void PutTo(COutputStream& out) const;\n  [[nodiscard]] constexpr bool operator==(CAssetId other) const noexcept { return id == other.id; }\n  [[nodiscard]] constexpr bool operator!=(CAssetId other) const noexcept { return !operator==(other); }\n  [[nodiscard]] constexpr bool operator<(CAssetId other) const noexcept { return id < other.id; }\n};\n\n// #define kInvalidAssetId CAssetId()\n\nstruct SObjectTag {\n  FourCC type;\n  CAssetId id;\n\n  constexpr explicit operator bool() const noexcept { return id.IsValid(); }\n  [[nodiscard]] constexpr bool operator==(const SObjectTag& other) const noexcept { return id == other.id; }\n  [[nodiscard]] constexpr bool operator!=(const SObjectTag& other) const noexcept { return !operator==(other); }\n  [[nodiscard]] constexpr bool operator<(const SObjectTag& other) const noexcept { return id < other.id; }\n  constexpr SObjectTag() noexcept = default;\n  constexpr SObjectTag(FourCC tp, CAssetId rid) noexcept : type(tp), id(rid) {}\n  explicit SObjectTag(CInputStream& in);\n  void ReadMLVL(CInputStream& in);\n};\n\nstruct TEditorId {\n  u32 id = UINT32_MAX;\n\n  constexpr TEditorId() noexcept = default;\n  constexpr TEditorId(u32 idin) noexcept : id(idin) {}\n  [[nodiscard]] constexpr u8 LayerNum() const noexcept { return u8((id >> 26) & 0x3f); }\n  [[nodiscard]] constexpr u16 AreaNum() const noexcept { return u16((id >> 16) & 0x3ff); }\n  [[nodiscard]] constexpr u16 Id() const noexcept { return u16(id & 0xffff); }\n  [[nodiscard]] constexpr bool operator<(TEditorId other) const noexcept {\n    return (id & 0x3ffffff) < (other.id & 0x3ffffff);\n  }\n  [[nodiscard]] constexpr bool operator==(TEditorId other) const noexcept {\n    return (id & 0x3ffffff) == (other.id & 0x3ffffff);\n  }\n  [[nodiscard]] constexpr bool operator!=(TEditorId other) const noexcept { return !operator==(other); }\n};\n\n#define kInvalidEditorId TEditorId()\n\nstruct TUniqueId {\n  kUniqueIdType id = kUniqueIdMax;\n\n  constexpr TUniqueId() noexcept = default;\n  constexpr TUniqueId(kUniqueIdType value, kUniqueIdType version) noexcept\n  : id(value | (version << kUniqueIdValueBits)) {}\n  [[nodiscard]] constexpr kUniqueIdType Version() const noexcept {\n    return kUniqueIdType((id >> kUniqueIdValueBits) & kUniqueIdVersionMask);\n  }\n  [[nodiscard]] constexpr kUniqueIdType Value() const noexcept { return kUniqueIdType(id & kUniqueIdValueMask); }\n  [[nodiscard]] constexpr bool operator<(TUniqueId other) const noexcept { return id < other.id; }\n  [[nodiscard]] constexpr bool operator==(TUniqueId other) const noexcept { return id == other.id; }\n  [[nodiscard]] constexpr bool operator!=(TUniqueId other) const noexcept { return !operator==(other); }\n};\n\n#define kInvalidUniqueId TUniqueId()\nusing EntityList = rstl::reserved_vector<TUniqueId, kMaxEntities>;\n\nusing TAreaId = s32;\n\n#define kInvalidAreaId TAreaId(-1)\n\n#if 0\ntemplate <class T, size_t N>\nclass TRoundRobin\n{\n    rstl::reserved_vector<T, N> vals;\n\npublic:\n    TRoundRobin(const T& val) : vals(N, val) {}\n\n    void PushBack(const T& val) { vals.push_back(val); }\n\n    size_t Size() const { return vals.size(); }\n\n    const T& GetLastValue() const { return vals.back(); }\n\n    void Clear() { vals.clear(); }\n\n    const T& GetValue(s32) const {}\n};\n#endif\n\ntemplate <class T>\n[[nodiscard]] T GetAverage(const T* v, s32 count) noexcept {\n  T r = v[0];\n  for (s32 i = 1; i < count; ++i)\n    r += v[i];\n\n  return r / count;\n}\n\ntemplate <class T, size_t N>\nclass TReservedAverage : rstl::reserved_vector<T, N> {\npublic:\n  TReservedAverage() = default;\n\n  TReservedAverage(const T& t) { rstl::reserved_vector<T, N>::resize(N, t); }\n\n  void AddValue(const T& t) {\n    if (this->size() < N) {\n      this->insert(this->begin(), t);\n    } else {\n      this->pop_back();\n      this->insert(this->begin(), t);\n    }\n  }\n\n  [[nodiscard]] std::optional<T> GetAverage() const {\n    if (this->empty()) {\n      return std::nullopt;\n    }\n\n    return {metaforce::GetAverage<T>(this->data(), this->size())};\n  }\n\n  [[nodiscard]] std::optional<T> GetEntry(int i) const {\n    if (i >= this->size()) {\n      return std::nullopt;\n    }\n    return this->operator[](i);\n  }\n\n  void Clear() { this->clear(); }\n\n  [[nodiscard]] size_t Size() const { return this->size(); }\n};\n} // namespace metaforce\n\nnamespace std {\ntemplate <>\nstruct hash<metaforce::FourCC> {\n  size_t operator()(const metaforce::FourCC& val) const noexcept { return val.toUint32(); }\n};\n\ntemplate <>\nstruct hash<metaforce::SObjectTag> {\n  size_t operator()(const metaforce::SObjectTag& tag) const noexcept { return tag.id.Value(); }\n};\n\ntemplate <>\nstruct hash<metaforce::CAssetId> {\n  size_t operator()(const metaforce::CAssetId& id) const noexcept { return id.Value(); }\n};\n} // namespace std\n\n#if defined(__has_feature)\n#if __has_feature(memory_sanitizer)\n#define URDE_MSAN 1\n#endif\n#endif\n"
  },
  {
    "path": "Runtime/Streams/CFileOutStream.cpp",
    "content": "#include \"Runtime/Streams/CFileOutStream.hpp\"\n\nnamespace metaforce {\nCFileOutStream::CFileOutStream(std::string_view name, u32 blockLen) : COutputStream(blockLen) {\n  m_file = fopen(name.data(), \"wbe\");\n}\n\nCFileOutStream::~CFileOutStream() {\n  Flush();\n  if (m_file) {\n    fclose(m_file);\n  }\n}\n\nvoid CFileOutStream::Write(const u8* ptr, u32 len) {\n  if (!m_file) {\n    return;\n  }\n  fwrite(ptr, 1, len, m_file);\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Streams/CFileOutStream.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Streams/COutputStream.hpp\"\n#include <cstdio>\n\nnamespace metaforce {\nclass CFileOutStream final : public COutputStream {\n  FILE* m_file;\npublic:\n  explicit CFileOutStream(std::string_view name, u32 blockLen = 4096);\n  virtual ~CFileOutStream();\n\nprotected:\n  void Write(const u8* ptr, u32 len);\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Streams/CInputStream.cpp",
    "content": "#include \"CInputStream.hpp\"\n#if METAFORCE_TARGET_BYTE_ORDER == __ORDER_LITTLE_ENDIAN__\n#include \"Runtime/CBasics.hpp\"\n#endif\n#ifndef NDEBUG\n#include \"Runtime/Logging.hpp\"\n#endif\n\n#include <cstring>\n\nnamespace metaforce {\nstatic u32 min_containing_bytes(u32 v) {\n  v = 32 - v;\n  v = (v >> 3) - (static_cast<s32>(-(v & 7)) >> 31);\n  return v;\n}\n\nCInputStream::CInputStream(s32 len) : xc_len(len), x10_ptr(new u8[len]), x14_owned(true) {}\nCInputStream::CInputStream(const void* ptr, u32 len, bool owned)\n: x8_blockLen(len), xc_len(len), x10_ptr(reinterpret_cast<u8*>(const_cast<void*>(ptr))), x14_owned(owned) {}\n\nCInputStream::~CInputStream() {\n  if (x14_owned) {\n    delete[] x10_ptr;\n  }\n}\n\nbool CInputStream::InternalReadNext() {\n  x8_blockLen = Read(x10_ptr, xc_len);\n  x4_blockOffset = 0;\n  return x8_blockLen != 0;\n}\n\nbool CInputStream::GrabAnotherBlock() { return InternalReadNext(); }\n\nvoid CInputStream::Get(u8* dest, u32 len) {\n  s32 readCount = 0;\n  x20_bitOffset = 0;\n  while (len != 0) {\n    s32 blockLen = x8_blockLen - x4_blockOffset;\n    if (len < blockLen) {\n      blockLen = len;\n    }\n\n    if (blockLen != 0) {\n      memcpy(dest + readCount, x10_ptr + x4_blockOffset, blockLen);\n      len -= blockLen;\n      readCount += blockLen;\n      x4_blockOffset += blockLen;\n    } else if (len > 256) {\n      u32 readLen = Read(dest + readCount, len);\n#ifndef NDEBUG\n      if (readLen == 0) {\n        spdlog::fatal(\"Invalid read size!\");\n        break;\n      }\n#endif\n      len -= readLen;\n      readCount += readLen;\n    } else {\n      GrabAnotherBlock();\n    };\n  }\n  x18_readPosition += readCount;\n}\n\nu32 CInputStream::ReadBytes(void* dest, u32 len) {\n  if (len == 0) {\n    return 0;\n  }\n\n  if (x4_blockOffset == x8_blockLen) {\n    GrabAnotherBlock();\n  }\n\n  u32 curLen = len;\n  u32 curReadLen = 0;\n\n  while (curReadLen < len) {\n    if ((x8_blockLen - x4_blockOffset) == 0 && !InternalReadNext()) {\n      break;\n    }\n\n    u32 readCount = x8_blockLen - x4_blockOffset;\n    if (curLen < (x8_blockLen - x4_blockOffset)) {\n      readCount = curLen;\n    }\n\n    memcpy(reinterpret_cast<u8*>(dest) + curReadLen, x10_ptr + x4_blockOffset, readCount);\n    curReadLen += readCount;\n    curLen -= readCount;\n    x4_blockOffset += readCount;\n  }\n\n  x18_readPosition += curReadLen;\n  return curReadLen;\n}\n\nu32 CInputStream::ReadBits(u32 bitCount) {\n  u32 ret = 0;\n  u32 bitOffset = x20_bitOffset;\n  if (bitOffset < bitCount) {\n    u32 shiftAmt = bitCount - bitOffset;\n    const u32 mask = bitOffset == 32 ? 0xffffffff : (1 << bitOffset) - 1;\n    const u32 bitWord = x1c_bitWord;\n    x20_bitOffset = 0;\n    const u32 len = (shiftAmt / 8) + static_cast<unsigned int>((shiftAmt % 8) != 0);\n    Get(reinterpret_cast<u8*>(&x1c_bitWord), len);\n#if METAFORCE_TARGET_BYTE_ORDER == __LITTLE_ENDIAN\n    x1c_bitWord = CBasics::SwapBytes(x1c_bitWord);\n#endif\n    const u32 mask2 = shiftAmt == 32 ? 0xffffffff : (1 << shiftAmt) - 1;\n    u32 tmp = x20_bitOffset;\n    x20_bitOffset = len * 8;\n    ret = ((mask & (bitWord >> (32 - bitOffset))) << shiftAmt) | ((mask2 & (x1c_bitWord >> (32 - shiftAmt))) << tmp);\n    x20_bitOffset -= shiftAmt;\n    x1c_bitWord <<= u64(shiftAmt);\n  } else {\n    u32 baseVal2 = (bitCount == 0x20 ? 0xffffffff : (1 << bitCount) - 1);\n    x20_bitOffset -= bitCount;\n    ret = baseVal2 & (x1c_bitWord >> (32 - bitCount));\n    x1c_bitWord <<= u64(bitCount);\n  }\n\n  return ret;\n}\n\nchar CInputStream::ReadChar() {\n  u8 tmp = 0;\n  Get(&tmp, sizeof(tmp));\n  return static_cast<char>(tmp);\n}\n\nbool CInputStream::ReadBool() { return Get<bool>(); }\n\ns16 CInputStream::ReadInt16() {\n  s16 tmp = 0;\n  Get(reinterpret_cast<u8*>(&tmp), sizeof(tmp));\n#if METAFORCE_TARGET_BYTE_ORDER == __ORDER_LITTLE_ENDIAN__\n  CBasics::Swap2Bytes(reinterpret_cast<u8*>(&tmp));\n#endif\n  return tmp;\n}\n\nu16 CInputStream::ReadUint16() {\n  u16 tmp = 0;\n  Get(reinterpret_cast<u8*>(&tmp), sizeof(tmp));\n#if METAFORCE_TARGET_BYTE_ORDER == __ORDER_LITTLE_ENDIAN__\n  CBasics::Swap2Bytes(reinterpret_cast<u8*>(&tmp));\n#endif\n  return tmp;\n}\n\ns32 CInputStream::ReadInt32() {\n  s32 tmp = 0;\n  Get(reinterpret_cast<u8*>(&tmp), sizeof(tmp));\n#if METAFORCE_TARGET_BYTE_ORDER == __ORDER_LITTLE_ENDIAN__\n  CBasics::Swap4Bytes(reinterpret_cast<u8*>(&tmp));\n#endif\n  return tmp;\n}\n\nu32 CInputStream::ReadUint32() {\n  u32 tmp = 0;\n  Get(reinterpret_cast<u8*>(&tmp), sizeof(tmp));\n#if METAFORCE_TARGET_BYTE_ORDER == __ORDER_LITTLE_ENDIAN__\n  CBasics::Swap4Bytes(reinterpret_cast<u8*>(&tmp));\n#endif\n  return tmp;\n}\n\ns64 CInputStream::ReadInt64() {\n  s64 tmp = 0;\n  Get(reinterpret_cast<u8*>(&tmp), sizeof(tmp));\n#if METAFORCE_TARGET_BYTE_ORDER == __ORDER_LITTLE_ENDIAN__\n  CBasics::Swap8Bytes(reinterpret_cast<u8*>(&tmp));\n#endif\n  return tmp;\n}\n\nu64 CInputStream::ReadUint64() {\n  u64 tmp = 0;\n  Get(reinterpret_cast<u8*>(&tmp), sizeof(tmp));\n#if METAFORCE_TARGET_BYTE_ORDER == __ORDER_LITTLE_ENDIAN__\n  CBasics::Swap8Bytes(reinterpret_cast<u8*>(&tmp));\n#endif\n  return tmp;\n}\n\nfloat CInputStream::ReadFloat() {\n  float tmp = 0.f;\n  Get(reinterpret_cast<u8*>(&tmp), sizeof(tmp));\n#if METAFORCE_TARGET_BYTE_ORDER == __ORDER_LITTLE_ENDIAN__\n  CBasics::Swap4Bytes(reinterpret_cast<u8*>(&tmp));\n#endif\n  return tmp;\n}\n\nfloat CInputStream::ReadReal32() { return Get<float>(); }\n\ndouble CInputStream::ReadDouble() {\n  double tmp = 0.0;\n  Get(reinterpret_cast<u8*>(&tmp), sizeof(tmp));\n#if METAFORCE_TARGET_BYTE_ORDER == __ORDER_LITTLE_ENDIAN__\n  CBasics::Swap8Bytes(reinterpret_cast<u8*>(&tmp));\n#endif\n  return tmp;\n}\n\ndouble CInputStream::ReadReal64() { return Get<double>(); }\n\ntemplate <>\nbool cinput_stream_helper(CInputStream& in) {\n  return in.ReadChar() != 0;\n}\n\ntemplate <>\ns8 cinput_stream_helper(CInputStream& in) {\n  return in.ReadChar();\n}\ntemplate <>\nu8 cinput_stream_helper(CInputStream& in) {\n  return in.ReadChar();\n}\n\ntemplate <>\ns16 cinput_stream_helper(CInputStream& in) {\n  return in.ReadInt16();\n}\n\ntemplate <>\ns32 cinput_stream_helper(CInputStream& in) {\n  return in.ReadInt32();\n}\n\ntemplate <>\nu32 cinput_stream_helper(CInputStream& in) {\n  return in.ReadUint32();\n}\n\ntemplate <>\ns64 cinput_stream_helper(CInputStream& in) {\n  return in.ReadInt64();\n}\n\ntemplate <>\nu64 cinput_stream_helper(CInputStream& in) {\n  return in.ReadUint64();\n}\n\ntemplate <>\nfloat cinput_stream_helper(CInputStream& in) {\n  return in.ReadFloat();\n}\n\ntemplate <>\ndouble cinput_stream_helper(CInputStream& in) {\n  return in.ReadDouble();\n}\n\ntemplate <>\nstd::string cinput_stream_helper(CInputStream& in) {\n  std::string ret;\n  auto chr = in.ReadChar();\n  while (chr != '\\0') {\n    ret += chr;\n    chr = in.ReadChar();\n  }\n\n  return ret;\n}\n\nu32 CInputStream::GetBitCount(u32 val) {\n  int bits = 0;\n  for (; val != 0; val >>= 1) {\n    bits += 1;\n  }\n  return bits;\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Streams/CInputStream.hpp",
    "content": "#pragma once\n#include \"Runtime/GCNTypes.hpp\"\n\n#include <array>\n#include <string>\n\nnamespace metaforce {\nclass CInputStream {\n  u32 x4_blockOffset = 0;\n  u32 x8_blockLen = 0;\n  u32 xc_len = 0;\n  u8* x10_ptr = nullptr;\n  bool x14_owned = false;\n  u32 x18_readPosition = 0;\n  u32 x1c_bitWord = 0;\n  u32 x20_bitOffset = 0;\n\n  bool InternalReadNext();\n  bool GrabAnotherBlock();\n\n  virtual u32 Read(void* dest, u32 len) = 0;\npublic:\n  explicit CInputStream(s32 len);\n  CInputStream(const void* ptr, u32 len, bool owned);\n  virtual ~CInputStream();\n\n  u32 GetReadPosition() const { return x18_readPosition; }\n  u32 ReadBits(u32 bitCount);\n  u32 ReadBytes(void* dest, u32 len);\n  s8 ReadInt8() { return Get<s8>(); }\n  u8 ReadUint8() { return Get<u8>(); }\n  char ReadChar();\n  bool ReadBool();\n  s16 ReadInt16();\n  s16 ReadShort() { return Get<s16>(); }\n  u16 ReadUint16();\n  s32 ReadInt32();\n  s32 ReadLong() { return Get<s32>(); }\n  u32 ReadUint32();\n  s64 ReadInt64();\n  s64 ReadLongLong() { return Get<s64>(); }\n  u64 ReadUint64();\n\n  float ReadReal32();\n  float ReadFloat();\n  double ReadReal64();\n  double ReadDouble();\n\n  void Get(u8* dest, u32 len);\n  template <typename T>\n  T Get() {\n    return cinput_stream_helper<T>(*this);\n  }\n\n  static u32 GetBitCount(u32 val);\n};\n\ntemplate <class T>\nT cinput_stream_helper(CInputStream& in) {\n  return T(in);\n}\ntemplate <>\nbool cinput_stream_helper(CInputStream& in);\ntemplate <>\ns8 cinput_stream_helper(CInputStream& in);\ntemplate <>\nu8 cinput_stream_helper(CInputStream& in);\ntemplate <>\ns16 cinput_stream_helper(CInputStream& in);\ntemplate <>\ns32 cinput_stream_helper(CInputStream& in);\ntemplate <>\nu32 cinput_stream_helper(CInputStream& in);\ntemplate <>\ns64 cinput_stream_helper(CInputStream& in);\ntemplate <>\nu64 cinput_stream_helper(CInputStream& in);\ntemplate <>\nfloat cinput_stream_helper(CInputStream& in);\ntemplate <>\ndouble cinput_stream_helper(CInputStream& in);\ntemplate <>\nstd::string cinput_stream_helper(CInputStream& in);\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Streams/CMemoryInStream.hpp",
    "content": "#pragma once\n#include \"Runtime/Streams/CInputStream.hpp\"\n\nnamespace metaforce {\nclass CMemoryInStream final : public CInputStream {\npublic:\n  enum class EOwnerShip {\n    Owned,\n    NotOwned,\n  };\n\n  CMemoryInStream(const void* ptr, u32 len) : CInputStream(ptr, len, false) {}\n  CMemoryInStream(const void* ptr, u32 len, EOwnerShip ownership)\n  : CInputStream(ptr, len, ownership == EOwnerShip::Owned) {}\n  u32 Read(void* dest, u32 len) override { return 0; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Streams/CMemoryStreamOut.cpp",
    "content": "#include \"CMemoryStreamOut.hpp\"\n\n#include <cstring>\n\nnamespace metaforce {\nCMemoryStreamOut::~CMemoryStreamOut() {\n  Flush();\n\n  if (x88_owned) {\n    delete[] x7c_ptr;\n  }\n}\n\nvoid CMemoryStreamOut::Write(const u8* ptr, u32 len) {\n  const auto offset = (x80_len - x84_position);\n  if (offset < len) {\n    len = offset;\n  }\n\n  if (len != 0) {\n    memcpy(x7c_ptr + x84_position, ptr, len);\n    x84_position += len;\n  }\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Streams/CMemoryStreamOut.hpp",
    "content": "#pragma once\n#include \"COutputStream.hpp\"\n\nnamespace metaforce {\nclass CMemoryStreamOut final : public COutputStream {\npublic:\n  enum class EOwnerShip {\n    Owned,\n    NotOwned,\n  };\n\nprivate:\n  u8* x7c_ptr = nullptr;\n  u32 x80_len = 0;\n  u32 x84_position = 0;\n  bool x88_owned;\n\nprotected:\n  void Write(const u8* ptr, u32 len) override;\npublic:\n  CMemoryStreamOut(u8* workBuf, u32 len, EOwnerShip ownership = EOwnerShip::NotOwned, s32 blockLen = 4096)\n  : COutputStream(blockLen), x7c_ptr(workBuf), x80_len(len), x88_owned(ownership == EOwnerShip::Owned) {}\n\n  ~CMemoryStreamOut() override;\n\n  u32 GetWritePosition() const { return x84_position; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Streams/COutputStream.cpp",
    "content": "#include \"COutputStream.hpp\"\n\n#include \"Runtime/CBasics.hpp\"\n\n#include <cstring>\n\nnamespace metaforce {\nstatic u32 min_containing_bytes(u32 v) {\n  v = 32 - v;\n  return (v / 8) + static_cast<unsigned int>((v % 8) != 0);\n}\n\nCOutputStream::COutputStream(s32 len) : x8_bufLen(len) {\n  xc_ptr = len <= 64 ? reinterpret_cast<u8*>(((reinterpret_cast<uintptr_t>(x1c_scratch) + 7) & ~7) + 6) : new u8[len];\n}\n\nCOutputStream::~COutputStream() {\n  if (x8_bufLen > 64) {\n    delete[] xc_ptr;\n  }\n}\n\nvoid COutputStream::DoFlush() {\n  if (x4_position != 0) {\n    Write(xc_ptr, x4_position);\n    x4_position = 0;\n  }\n}\n\nvoid COutputStream::DoPut(const u8* ptr, u32 len) {\n  if (len == 0) {\n    return;\n  }\n\n  x10_numWrites += len;\n  u32 curLen = len;\n  if (x8_bufLen < len + x4_position) {\n    while (curLen != 0) {\n      u32 count = x8_bufLen - x4_position;\n      if (curLen < count) {\n        count = curLen;\n      }\n      if (count == 0) {\n        DoFlush();\n      } else {\n        memcpy(xc_ptr + x4_position, ptr + (len - curLen), count);\n        x4_position += count;\n        curLen -= count;\n      }\n    }\n  } else {\n    memcpy(xc_ptr + x4_position, ptr, len);\n    x4_position += len;\n  }\n}\n\nvoid COutputStream::FlushShiftRegister() {\n  if (x18_shiftRegisterOffset < 32) {\n#if METAFORCE_TARGET_BYTE_ORDER == __ORDER_LITTLE_ENDIAN__\n    x14_shiftRegister = CBasics::SwapBytes(x14_shiftRegister);\n#endif\n    DoPut(reinterpret_cast<const u8*>(&x14_shiftRegister), min_containing_bytes(x18_shiftRegisterOffset));\n    x14_shiftRegister = 0;\n    x18_shiftRegisterOffset = 32;\n  }\n}\n\nvoid COutputStream::Flush() {\n  FlushShiftRegister();\n  DoFlush();\n}\n\nvoid COutputStream::Put(const u8* ptr, u32 len) {\n  FlushShiftRegister();\n  DoPut(ptr, len);\n}\n\nvoid COutputStream::WriteBits(u32 value, u32 bitCount) {\n  u32 bitOffset = x18_shiftRegisterOffset;\n  if (bitOffset < bitCount) {\n    u32 shiftAmt = bitCount - bitOffset;\n    x14_shiftRegister |= (value >> shiftAmt) & (bitOffset == 32 ? 0xffffffff : (1 << bitOffset) - 1);\n    x18_shiftRegisterOffset = 0;\n    FlushShiftRegister();\n    const u32 mask = (shiftAmt == 0x20 ? 0xffffffff : (1 << shiftAmt) - 1);\n    x14_shiftRegister = (value & mask) << (0x20 - shiftAmt);\n    x18_shiftRegisterOffset -= shiftAmt;\n  } else {\n    const u32 mask = bitCount == 32 ? 0xffffffff :  (1 << bitCount) - 1;\n    x14_shiftRegister |= (value & mask) << (bitOffset - bitCount);\n    x18_shiftRegisterOffset -= bitCount;\n  }\n}\n\nvoid COutputStream::WriteChar(u8 c) {\n  FlushShiftRegister();\n  if (x8_bufLen <= x4_position) {\n    DoFlush();\n  }\n  ++x10_numWrites;\n  *reinterpret_cast<u8*>(xc_ptr + x4_position) = c;\n  ++x4_position;\n}\nvoid COutputStream::WriteShort(u16 s) {\n#if METAFORCE_TARGET_BYTE_ORDER == __ORDER_LITTLE_ENDIAN__\n  s = CBasics::SwapBytes(s);\n#endif\n  Put(reinterpret_cast<const u8*>(&s), sizeof(s));\n}\nvoid COutputStream::WriteLong(u32 l) {\n#if METAFORCE_TARGET_BYTE_ORDER == __ORDER_LITTLE_ENDIAN__\n  l = CBasics::SwapBytes(l);\n#endif\n  Put(reinterpret_cast<const u8*>(&l), sizeof(l));\n}\nvoid COutputStream::WriteLongLong(u64 ll) {\n#if METAFORCE_TARGET_BYTE_ORDER == __ORDER_LITTLE_ENDIAN__\n  ll = CBasics::SwapBytes(ll);\n#endif\n  Put(reinterpret_cast<const u8*>(&ll), sizeof(ll));\n}\nvoid COutputStream::WriteFloat(float f) {\n#if METAFORCE_TARGET_BYTE_ORDER == __ORDER_LITTLE_ENDIAN__\n  f = CBasics::SwapBytes(f);\n#endif\n  Put(reinterpret_cast<const u8*>(&f), sizeof(f));\n}\n\nvoid COutputStream::WriteDouble(double d) {\n#if METAFORCE_TARGET_BYTE_ORDER == __ORDER_LITTLE_ENDIAN__\n  d = CBasics::SwapBytes(d);\n#endif\n  Put(reinterpret_cast<const u8*>(&d), sizeof(d));\n}\n\n/* Default Stream Helpers */\ntemplate <>\nvoid coutput_stream_helper(const bool& t, COutputStream& out) {\n  out.WriteChar(static_cast<char>(t));\n}\ntemplate <>\nvoid coutput_stream_helper(const char& t, COutputStream& out) {\n  out.WriteChar(static_cast<char>(t));\n}\ntemplate <>\nvoid coutput_stream_helper(const s8& t, COutputStream& out) {\n  out.WriteChar(t);\n}\ntemplate <>\nvoid coutput_stream_helper(const u8& t, COutputStream& out) {\n  out.WriteChar(t);\n}\ntemplate <>\nvoid coutput_stream_helper(const s16& t, COutputStream& out) {\n  out.WriteShort(t);\n}\ntemplate <>\nvoid coutput_stream_helper(const u16& t, COutputStream& out) {\n  out.WriteShort(t);\n}\ntemplate <>\nvoid coutput_stream_helper(const s32& t, COutputStream& out) {\n  out.WriteLong(t);\n}\ntemplate <>\nvoid coutput_stream_helper(const u32& t, COutputStream& out) {\n  out.WriteLong(t);\n}\ntemplate <>\nvoid coutput_stream_helper(const s64& t, COutputStream& out) {\n  out.WriteLongLong(t);\n}\ntemplate <>\nvoid coutput_stream_helper(const u64& t, COutputStream& out) {\n  out.WriteLongLong(t);\n}\ntemplate <>\nvoid coutput_stream_helper(const float& t, COutputStream& out) {\n  out.WriteFloat(t);\n}\ntemplate <>\nvoid coutput_stream_helper(const double& t, COutputStream& out) {\n  out.WriteDouble(t);\n}\ntemplate <>\nvoid coutput_stream_helper(const std::string& t, COutputStream& out) {\n  for (size_t i = 0; i < t.size() + 1; ++i) {\n    out.FlushShiftRegister();\n    out.Put(t[i]);\n  }\n}\n\ntemplate <>\nvoid coutput_stream_helper(const std::string_view& t, COutputStream& out) {\n  for (size_t i = 0; i < t.size() + 1; ++i) {\n    out.FlushShiftRegister();\n    out.Put(t[i]);\n  }\n}\n\nu32 COutputStream::GetBitCount(u32 val) {\n  int bits = 0;\n  for (; val != 0; val >>= 1) {\n    bits += 1;\n  }\n  return bits;\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Streams/COutputStream.hpp",
    "content": "#pragma once\n#include \"Runtime/GCNTypes.hpp\"\n\n#include <array>\n#include <string>\n\nnamespace metaforce {\nclass COutputStream {\n  template <typename T>\n  friend void coutput_stream_helper(const T& t, COutputStream& out);\n  u32 x4_position = 0;\n  u32 x8_bufLen = 0;\n  u8* xc_ptr = nullptr;\n  u32 x10_numWrites = 0;\n  u32 x14_shiftRegister = 0;\n  u32 x18_shiftRegisterOffset = 32;\n  u8 x1c_scratch[96]{};\n\nprotected:\n  void DoFlush();\n  void DoPut(const u8* ptr, u32 len);\n  virtual void Write(const u8* ptr, u32 len) = 0;\npublic:\n  COutputStream(s32 unk);\n  virtual ~COutputStream();\n\n  u32 GetNumWrites() const { return x10_numWrites; }\n  void WriteBits(u32 val, u32 bitCount);\n  void WriteChar(u8 c);\n  void WriteShort(u16 s);\n  void WriteLong(u32 l);\n  void WriteLongLong(u64 ll);\n  void WriteFloat(float f);\n  void WriteDouble(double d);\n\n  void WriteInt8(s8 c) { Put(c); }\n  void WriteUint8(u8 c) { Put(c); }\n  void WriteInt16(s16 s) { Put(s); }\n  void WriteUint16(u16 s) { Put(s); }\n  void WriteInt32(s32 l) { Put(l); }\n  void WriteUint32(u32 l) { Put(l); }\n  void WriteInt64(u64 ll) { Put(ll); }\n  void WriteUint64(u64 ll) { Put(ll); }\n  void WriteReal32(float f) { Put(f); }\n  void WriteReal64(double d) { Put(d); }\n\n  void FlushShiftRegister();\n  void Flush();\n  void Put(const u8* ptr, u32 len);\n  template <typename T>\n  void Put(const T& t) {\n    coutput_stream_helper(t, *this);\n  }\n\n  static u32 GetBitCount(u32 val);\n};\n\ntemplate <typename T>\nvoid coutput_stream_helper(const T& t, COutputStream& out) {\n  t.PutTo(out);\n}\n\ntemplate <>\nvoid coutput_stream_helper(const bool& t, COutputStream& out);\ntemplate <>\nvoid coutput_stream_helper(const char& t, COutputStream& out);\ntemplate <>\nvoid coutput_stream_helper(const s8& t, COutputStream& out);\ntemplate <>\nvoid coutput_stream_helper(const u8& t, COutputStream& out);\ntemplate <>\nvoid coutput_stream_helper(const s16& t, COutputStream& out);\ntemplate <>\nvoid coutput_stream_helper(const u16& t, COutputStream& out);\ntemplate <>\nvoid coutput_stream_helper(const s32& t, COutputStream& out);\ntemplate <>\nvoid coutput_stream_helper(const u32& t, COutputStream& out);\ntemplate <>\nvoid coutput_stream_helper(const s64& t, COutputStream& out);\ntemplate <>\nvoid coutput_stream_helper(const u64& t, COutputStream& out);\ntemplate <>\nvoid coutput_stream_helper(const float& t, COutputStream& out);\ntemplate <>\nvoid coutput_stream_helper(const double& t, COutputStream& out);\ntemplate <>\nvoid coutput_stream_helper(const std::string& t, COutputStream& out);\ntemplate <>\nvoid coutput_stream_helper(const std::string_view& t, COutputStream& out);\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Streams/CTextInStream.cpp",
    "content": "#include \"Runtime/Streams/CTextInStream.hpp\"\n#include <algorithm>\n\nnamespace metaforce {\nCTextInStream::CTextInStream(CInputStream& in, int len) : m_in(&in), m_len(len) {}\n\nstd::string CTextInStream::GetNextLine() {\n  std::string ret;\n  while (!IsEOF()) {\n    auto chr = m_in->ReadChar();\n    ret += chr;\n    if (ret.back() == '\\n') {\n      break;\n    }\n  }\n\n  ret.erase(std::remove(ret.begin(), ret.end(), '\\r'), ret.end());\n  ret.erase(std::remove(ret.begin(), ret.end(), '\\n'), ret.end());\n  return ret;\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Streams/CTextInStream.hpp",
    "content": "#pragma once\n#include \"Runtime/Streams/CInputStream.hpp\"\n\nnamespace metaforce {\nclass CTextInStream {\n  CInputStream* m_in;\n  s32 m_len;\n\npublic:\n  CTextInStream(CInputStream& in, int len);\n\n  bool IsEOF() { return m_in->GetReadPosition() >= m_len; }\n  std::string GetNextLine();\n};\n} // namespace metaforce"
  },
  {
    "path": "Runtime/Streams/CTextOutStream.cpp",
    "content": "#include \"Runtime/Streams/CTextOutStream.hpp\"\n\nnamespace metaforce {\nCTextOutStream::CTextOutStream(COutputStream& out) : m_out(&out) {}\n\nvoid CTextOutStream::WriteString(const std::string& str) { CTextOutStream::WriteString(str.c_str(), str.length()); }\nvoid CTextOutStream::WriteString(const char* str, u32 len) {\n  bool wroteCarriageReturn = false;\n  bool wroteLineFeed = false;\n  for (u32 i = 0; i < len; ++i) {\n    if (str[i] == '\\r') {\n      wroteCarriageReturn = true;\n    } else if (str[i] == '\\n' && !wroteCarriageReturn) {\n      m_out->WriteChar('\\r');\n      wroteLineFeed = true;\n      wroteCarriageReturn = true;\n    }\n    m_out->WriteChar(str[i]);\n  }\n\n  /* If we didn't write either a line feed or carriage return we need to do that now */\n  if (!wroteCarriageReturn && !wroteLineFeed) {\n    m_out->WriteChar('\\r');\n    m_out->WriteChar('\\n');\n  }\n\n  /* Since this is a text buffer, we always want to flush after writing a string */\n  m_out->Flush();\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Streams/CTextOutStream.hpp",
    "content": "#pragma once\n#include \"Runtime/Streams/COutputStream.hpp\"\n\nnamespace metaforce {\nclass CTextOutStream {\n  COutputStream* m_out;\n\npublic:\n  explicit CTextOutStream(COutputStream& out);\n\n  void WriteString(const std::string& str);\n  void WriteString(const char* str, u32 len);\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Streams/CZipInputStream.cpp",
    "content": "#include \"CZipInputStream.hpp\"\n\nnamespace metaforce {\nCZipInputStream::CZipInputStream(std::unique_ptr<CInputStream>&& strm)\n: CInputStream(4096), x24_compBuf(new u8[4096]), x28_strm(std::move(strm)) {\n  x30_zstrm = std::make_unique<z_stream>();\n  x30_zstrm->next_in = x24_compBuf.get();\n  x30_zstrm->avail_in = 0;\n  x30_zstrm->zalloc = [](void*, u32 c, u32 n) -> void* { return new u8[size_t{c} * size_t{n}]; };\n  x30_zstrm->zfree = [](void*, void* buf) { delete[] static_cast<u8*>(buf); };\n  inflateInit(x30_zstrm.get());\n}\n\nCZipInputStream::~CZipInputStream() { inflateEnd(x30_zstrm.get()); }\n\nu32 CZipInputStream::Read(void* buf, u32 len) {\n  x30_zstrm->next_out = static_cast<Bytef*>(buf);\n  x30_zstrm->avail_out = len;\n  if (x30_zstrm->avail_in == 0) {\n    x30_zstrm->avail_in = x28_strm->ReadBytes(x24_compBuf.get(), 4096);\n    x30_zstrm->next_in = x24_compBuf.get();\n  }\n  inflate(x30_zstrm.get(), Z_NO_FLUSH);\n  return len - x30_zstrm->avail_out;\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Streams/CZipInputStream.hpp",
    "content": "#pragma once\n\n#include \"CInputStream.hpp\"\n\n#include <memory>\n\n#include <zlib.h>\n\nnamespace metaforce {\nclass CZipInputStream final : public CInputStream {\n  std::unique_ptr<u8[]> x24_compBuf;\n  std::unique_ptr<CInputStream> x28_strm;\n  std::unique_ptr<z_stream> x30_zstrm = {};\n\n  u32 Read(void* ptr, u32 len) override;\npublic:\n  explicit CZipInputStream(std::unique_ptr<CInputStream>&& strm);\n  ~CZipInputStream() override;\n\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Streams/ContainerReaders.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Streams/CInputStream.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include <vector>\nnamespace metaforce {\ntemplate <class T, size_t N>\nvoid read_reserved_vector(rstl::reserved_vector<T, N>& v, CInputStream& in) {\n  u32 count = in.ReadUint32();\n  v.resize(count);\n  for (u32 i = 0; i < count; ++i) {\n    v[i] = in.Get<T>();\n  }\n}\n\ntemplate <class T>\nvoid read_vector(std::vector<T>& v, CInputStream& in) {\n  u32 count = in.ReadUint32();\n  v.reserve(count);\n  for (u32 i = 0; i < count; ++i) {\n    v.emplace_back(in.Get<T>());\n  }\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Streams/ContainerWriters.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Streams/COutputStream.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include <vector>\nnamespace metaforce {\ntemplate <class T, size_t N>\nvoid write_reserved_vector(const rstl::reserved_vector<T, N>& v, COutputStream& out) {\n  out.Put<u32>(v.size());\n  for (const auto& t : v) {\n    out.Put(t);\n  }\n}\n\ntemplate <class T>\nvoid write_vector(const std::vector<T>& v, COutputStream& out) {\n  out.Put<u32>(v.size());\n  for (const auto& t : v) {\n    out.Put(t);\n  }\n}\n} // namespace metaforce"
  },
  {
    "path": "Runtime/Streams/IOStreams.cpp",
    "content": "#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"zeus/zeus.hpp\"\n\nnamespace metaforce {\n// Input\ntemplate <>\nzeus::CVector2f cinput_stream_helper(CInputStream& in) {\n  zeus::CVector2f ret;\n  ret.x() = in.ReadFloat();\n  ret.y() = in.ReadFloat();\n  return ret;\n}\ntemplate <>\nzeus::CVector3f cinput_stream_helper(CInputStream& in) {\n  zeus::CVector3f ret;\n  ret.x() = in.ReadFloat();\n  ret.y() = in.ReadFloat();\n  ret.z() = in.ReadFloat();\n  return ret;\n}\ntemplate <>\nzeus::CVector4f cinput_stream_helper(CInputStream& in) {\n  zeus::CVector4f ret;\n  ret.x() = in.ReadFloat();\n  ret.y() = in.ReadFloat();\n  ret.z() = in.ReadFloat();\n  ret.w() = in.ReadFloat();\n  return ret;\n}\n\ntemplate <>\nzeus::CQuaternion cinput_stream_helper(CInputStream& in) {\n  zeus::CQuaternion ret;\n  ret.w() = in.ReadFloat();\n  ret.x() = in.ReadFloat();\n  ret.y() = in.ReadFloat();\n  ret.z() = in.ReadFloat();\n  return ret;\n}\n\ntemplate <>\nzeus::CAABox cinput_stream_helper(CInputStream& in) {\n  zeus::CAABox ret;\n  ret.min = in.Get<zeus::CVector3f>();\n  ret.max = in.Get<zeus::CVector3f>();\n  return ret;\n}\n\ntemplate <>\nzeus::COBBox cinput_stream_helper(CInputStream& in) {\n  zeus::COBBox ret;\n  ret.transform = in.Get<zeus::CTransform>();\n  ret.extents = in.Get<zeus::CVector3f>();\n  return ret;\n}\ntemplate <>\nzeus::CColor cinput_stream_helper(CInputStream& in) {\n  zeus::CColor ret;\n  ret.r() = in.ReadFloat();\n  ret.g() = in.ReadFloat();\n  ret.b() = in.ReadFloat();\n  ret.a() = in.ReadFloat();\n  return ret;\n}\n\ntemplate <>\nzeus::CTransform cinput_stream_helper(CInputStream& in) {\n  zeus::CTransform ret;\n  auto r0 = in.Get<zeus::CVector4f>();\n  auto r1 = in.Get<zeus::CVector4f>();\n  auto r2 = in.Get<zeus::CVector4f>();\n  ret.basis = zeus::CMatrix3f(r0.toVec3f(), r1.toVec3f(), r2.toVec3f());\n  ret.basis.transpose();\n  ret.origin = zeus::CVector3f(r0.w(), r1.w(), r2.w());\n  return ret;\n}\n\ntemplate <>\nzeus::CMatrix3f cinput_stream_helper(CInputStream& in) {\n  zeus::CMatrix3f ret;\n  ret.m[0] = in.Get<zeus::CVector3f>();\n  ret.m[1] = in.Get<zeus::CVector3f>();\n  ret.m[2] = in.Get<zeus::CVector3f>();\n  return ret.transposed();\n}\n\ntemplate <>\nzeus::CMatrix4f cinput_stream_helper(CInputStream& in) {\n  zeus::CMatrix4f ret;\n  ret.m[0] = in.Get<zeus::CVector4f>();\n  ret.m[1] = in.Get<zeus::CVector4f>();\n  ret.m[2] = in.Get<zeus::CVector4f>();\n  ret.m[3] = in.Get<zeus::CVector4f>();\n  return ret.transposed();\n}\n\ntemplate <typename T, size_t N>\nrstl::reserved_vector<T, N> cinput_stream_helper(CInputStream& in) {\n  return rstl::reserved_vector<T, N>(in);\n}\n\n// Output\ntemplate <>\nvoid coutput_stream_helper(const zeus::CVector3f& v, COutputStream& out) {\n  out.Put(v.x());\n  out.Put(v.y());\n  out.Put(v.z());\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Streams/IOStreams.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Streams/CMemoryInStream.hpp\"\n#include \"Runtime/Streams/CMemoryStreamOut.hpp\"\n#include \"Runtime/Streams/CZipInputStream.hpp\"\n#include \"Runtime/Streams/ContainerReaders.hpp\"\n#include \"Runtime/rstl.hpp\"\n\nnamespace zeus {\nclass CVector2f;\nclass CVector3f;\nclass CVector4f;\nclass CTransform;\nclass CMatrix3f;\nclass CMatrix4f;\nclass CAABox;\nclass COBBox;\nclass CQuaternion;\nclass CColor;\n} // namespace zeus\n\nnamespace metaforce {\n// Custom helpers for input/output\ntemplate <>\nzeus::CVector2f cinput_stream_helper(CInputStream& in);\ntemplate <>\nzeus::CVector3f cinput_stream_helper(CInputStream& in);\ntemplate <>\nzeus::CVector4f cinput_stream_helper(CInputStream& in);\ntemplate <>\nzeus::CQuaternion cinput_stream_helper(CInputStream& in);\ntemplate <>\nzeus::CAABox cinput_stream_helper(CInputStream& in);\ntemplate <>\nzeus::COBBox cinput_stream_helper(CInputStream& in);\ntemplate <>\nzeus::CColor cinput_stream_helper(CInputStream& in);\ntemplate <>\nzeus::CTransform cinput_stream_helper(CInputStream& in);\ntemplate <>\nzeus::CMatrix3f cinput_stream_helper(CInputStream& in);\ntemplate <>\nzeus::CMatrix4f cinput_stream_helper(CInputStream& in);\n\ntemplate <>\nvoid coutput_stream_helper(const zeus::CVector3f& v, COutputStream& out);\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Tweaks/ITweak.hpp",
    "content": "#pragma once\n\n// Gonna need these in all the tweaks anyway, so we'll include them here\n#include \"Runtime/GCNTypes.hpp\"\n\n#include \"zeus/zeus.hpp\"\n\nnamespace metaforce {\nclass CVar;\nclass CVarManager;\nclass CInputStream;\nclass COutputStream;\nclass ITweak {\npublic:\n  virtual ~ITweak() = default;\n  virtual void initCVars(CVarManager*) {}\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Tweaks/ITweakAutoMapper.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Tweaks/ITweak.hpp\"\nnamespace metaforce::Tweaks {\nstruct ITweakAutoMapper : public ITweak {\n  virtual bool GetShowOneMiniMapArea() const = 0;\n  virtual bool GetScaleMoveSpeedWithCamDist() const = 0;\n  virtual float GetCamDist() const = 0;\n  virtual float GetMinCamDist() const = 0;\n  virtual float GetMaxCamDist() const = 0;\n  virtual float GetMinCamRotateX() const = 0;\n  virtual float GetMaxCamRotateX() const = 0;\n  virtual float GetCamAngle() const = 0;\n  virtual const zeus::CColor& GetAutomapperWidgetColor() const = 0;\n  virtual float GetMiniCamDist() const = 0;\n  virtual float GetMiniCamXAngle() const = 0;\n  virtual float GetMiniCamAngle() const = 0;\n  virtual const zeus::CColor& GetAutomapperWidgetMiniColor() const = 0;\n  virtual const zeus::CColor& GetSurfaceVisitedColor() const = 0;\n  virtual const zeus::CColor& GetOutlineVisitedColor() const = 0;\n  virtual const zeus::CColor& GetSurfaceUnvisitedColor() const = 0;\n  virtual const zeus::CColor& GetOutlineUnvisitedColor() const = 0;\n  virtual const zeus::CColor& GetSurfaceSelectVisitedColor() const = 0;\n  virtual const zeus::CColor& GetOutlineSelectVisitedColor() const = 0;\n  virtual float GetMapSurfaceNormColorLinear() const = 0;\n  virtual float GetMapSurfaceNormColorConstant() const = 0;\n  virtual float GetOpenMapScreenTime() const = 0;\n  virtual float GetCloseMapScreenTime() const = 0;\n  virtual float GetHintPanTime() const = 0;\n  virtual float GetCamZoomUnitsPerFrame() const = 0;\n  virtual float GetCamRotateDegreesPerFrame() const = 0;\n  virtual float GetBaseMapScreenCameraMoveSpeed() const = 0;\n  virtual const zeus::CColor& GetSurfaceSelectUnvisitedColor() const = 0;\n  virtual const zeus::CColor& GetOutlineSelectUnvisitedColor() const = 0;\n  virtual float GetMiniAlphaSurfaceVisited() const = 0;\n  virtual float GetAlphaSurfaceVisited() const = 0;\n  virtual float GetMiniAlphaOutlineVisited() const = 0;\n  virtual float GetAlphaOutlineVisited() const = 0;\n  virtual float GetMiniAlphaSurfaceUnvisited() const = 0;\n  virtual float GetAlphaSurfaceUnvisited() const = 0;\n  virtual float GetMiniAlphaOutlineUnvisited() const = 0;\n  virtual float GetAlphaOutlineUnvisited() const = 0;\n  virtual float GetMiniMapViewportWidth() const = 0;\n  virtual float GetMiniMapViewportHeight() const = 0;\n  virtual float GetMiniMapCamDistScale() const = 0;\n  virtual float GetMapPlaneScaleX() const = 0;\n  virtual float GetMapPlaneScaleZ() const = 0;\n  virtual float GetUniverseCamDist() const = 0;\n  virtual float GetMinUniverseCamDist() const = 0;\n  virtual float GetMaxUniverseCamDist() const = 0;\n  virtual float GetSwitchToFromUniverseTime() const = 0;\n  virtual float GetCamPanUnitsPerFrame() const = 0;\n  virtual float GetAutomapperScaleX() const = 0;\n  virtual float GetAutomapperScaleZ() const = 0;\n  virtual float GetCamVerticalOffset() const = 0;\n  virtual const zeus::CColor& GetMiniMapSamusModColor() const = 0;\n  virtual const zeus::CColor& GetAreaFlashPulseColor() const = 0;\n  virtual const zeus::CColor& GetDoorColor(int idx) const = 0;\n  virtual const zeus::CColor& GetOpenDoorColor() const = 0;\n};\n} // namespace DataSpec\n"
  },
  {
    "path": "Runtime/Tweaks/ITweakBall.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Tweaks/ITweak.hpp\"\n\nnamespace metaforce::Tweaks {\nstruct ITweakBall : ITweak {\n  virtual float GetMaxBallTranslationAcceleration(int s) const = 0;\n  virtual float GetBallTranslationFriction(int s) const = 0;\n  virtual float GetBallTranslationMaxSpeed(int s) const = 0;\n  virtual float GetBallCameraElevation() const = 0;\n  virtual float GetBallCameraAnglePerSecond() const = 0;\n  virtual const zeus::CVector3f& GetBallCameraOffset() const = 0;\n  virtual float GetBallCameraMinSpeedDistance() const = 0;\n  virtual float GetBallCameraMaxSpeedDistance() const = 0;\n  virtual float GetBallCameraBackwardsDistance() const = 0;\n  virtual float GetBallCameraSpringConstant() const = 0;\n  virtual float GetBallCameraSpringMax() const = 0;\n  virtual float GetBallCameraSpringTardis() const = 0;\n  virtual float GetBallCameraCentroidSpringConstant() const = 0;\n  virtual float GetBallCameraCentroidSpringMax() const = 0;\n  virtual float GetBallCameraCentroidSpringTardis() const = 0;\n  virtual float GetBallCameraCentroidDistanceSpringConstant() const = 0;\n  virtual float GetBallCameraCentroidDistanceSpringMax() const = 0;\n  virtual float GetBallCameraCentroidDistanceSpringTardis() const = 0;\n  virtual float GetBallCameraLookAtSpringConstant() const = 0;\n  virtual float GetBallCameraLookAtSpringMax() const = 0;\n  virtual float GetBallCameraLookAtSpringTardis() const = 0;\n  virtual float GetBallForwardBrakingAcceleration(int s) const = 0;\n  virtual float GetBallGravity() const = 0;\n  virtual float GetBallWaterGravity() const = 0;\n  virtual float GetBallSlipFactor(int s) const = 0;\n  virtual float GetConservativeDoorCameraDistance() const = 0;\n  virtual float GetBallCameraChaseElevation() const = 0;\n  virtual float GetBallCameraChaseDampenAngle() const = 0;\n  virtual float GetBallCameraChaseDistance() const = 0;\n  virtual float GetBallCameraChaseYawSpeed() const = 0;\n  virtual float GetBallCameraChaseAnglePerSecond() const = 0;\n  virtual const zeus::CVector3f& GetBallCameraChaseLookAtOffset() const = 0;\n  virtual float GetBallCameraChaseSpringConstant() const = 0;\n  virtual float GetBallCameraChaseSpringMax() const = 0;\n  virtual float GetBallCameraChaseSpringTardis() const = 0;\n  virtual float GetBallCameraBoostElevation() const = 0;\n  virtual float GetBallCameraBoostDampenAngle() const = 0;\n  virtual float GetBallCameraBoostDistance() const = 0;\n  virtual float GetBallCameraBoostYawSpeed() const = 0;\n  virtual float GetBallCameraBoostAnglePerSecond() const = 0;\n  virtual const zeus::CVector3f& GetBallCameraBoostLookAtOffset() const = 0;\n  virtual float GetBallCameraBoostSpringConstant() const = 0;\n  virtual float GetBallCameraBoostSpringMax() const = 0;\n  virtual float GetBallCameraBoostSpringTardis() const = 0;\n  virtual float GetMinimumAlignmentSpeed() const = 0;\n  virtual float GetTireness() const = 0;\n  virtual float GetMaxLeanAngle() const = 0;\n  virtual float GetTireToMarbleThresholdSpeed() const = 0;\n  virtual float GetMarbleToTireThresholdSpeed() const = 0;\n  virtual float GetForceToLeanGain() const = 0;\n  virtual float GetLeanTrackingGain() const = 0;\n  virtual float GetBallCameraControlDistance() const = 0;\n  virtual float GetLeftStickDivisor() const = 0;\n  virtual float GetRightStickDivisor() const = 0;\n  virtual float GetBallTouchRadius() const = 0;\n  virtual float GetBoostBallDrainTime() const = 0;\n  virtual float GetBoostBallMaxChargeTime() const = 0;\n  virtual float GetBoostBallMinChargeTime() const = 0;\n  virtual float GetBoostBallMinRelativeSpeedForDamage() const = 0;\n  virtual float GetBoostBallChargeTimeTable(int i) const = 0;\n  virtual float GetBoostBallIncrementalSpeedTable(int i) const = 0;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Tweaks/ITweakGame.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Tweaks/ITweak.hpp\"\n\nnamespace metaforce::Tweaks {\n\nstruct ITweakGame : ITweak {\n  virtual std::string_view GetWorldPrefix() const = 0;\n  virtual bool GetSplashScreensDisabled() const = 0;\n  virtual float GetFirstPersonFOV() const = 0;\n  virtual float GetPressStartDelay() const = 0;\n  virtual float GetWavecapIntensityNormal() const = 0;\n  virtual float GetWavecapIntensityPoison() const = 0;\n  virtual float GetWavecapIntensityLava() const = 0;\n  virtual float GetRippleIntensityNormal() const = 0;\n  virtual float GetRippleIntensityPoison() const = 0;\n  virtual float GetRippleIntensityLava() const = 0;\n  virtual float GetFluidEnvBumpScale() const = 0;\n  virtual float GetWaterFogDistanceBase() const = 0;\n  virtual float GetWaterFogDistanceRange() const = 0;\n  virtual float GetGravityWaterFogDistanceBase() const = 0;\n  virtual float GetGravityWaterFogDistanceRange() const = 0;\n  virtual float GetHardModeDamageMultiplier() const = 0;\n  virtual float GetHardModeWeaponMultiplier() const = 0;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Tweaks/ITweakGui.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Tweaks/ITweak.hpp\"\n\nnamespace metaforce::Tweaks {\n\nstruct ITweakGui : ITweak {\n  enum class EHudVisMode : u32 { Zero, One, Two, Three };\n\n  enum class EHelmetVisMode : u32 { ReducedUpdate, NotVisible, Deco, HelmetDeco, GlowHelmetDeco, HelmetOnly };\n\n  virtual float GetMapAlphaInterpolant() const = 0;\n  virtual float GetPauseBlurFactor() const = 0;\n  virtual float GetRadarXYRadius() const = 0;\n  virtual float GetRadarZRadius() const = 0;\n  virtual float GetRadarZCloseRadius() const = 0;\n  virtual float GetEnergyBarFilledSpeed() const = 0;\n  virtual float GetEnergyBarShadowSpeed() const = 0;\n  virtual float GetEnergyBarDrainDelay() const = 0;\n  virtual bool GetEnergyBarAlwaysResetDelay() const = 0;\n  virtual float GetHudDamagePracticalsGainConstant() const = 0;\n  virtual float GetHudDamagePracticalsGainLinear() const = 0;\n  virtual float GetHudDamagePracticalsInitConstant() const = 0;\n  virtual float GetHudDamagePracticalsInitLinear() const = 0;\n  virtual float GetHudDamageLightSpotAngle() const = 0;\n  virtual float GetDamageLightAngleC() const = 0;\n  virtual float GetDamageLightAngleL() const = 0;\n  virtual float GetDamageLightAngleQ() const = 0;\n  virtual zeus::CVector3f GetDamageLightPreTranslate() const = 0;\n  virtual zeus::CVector3f GetDamageLightCenterTranslate() const = 0;\n  virtual float GetDamageLightXfXAngle() const = 0;\n  virtual float GetDamageLightXfZAngle() const = 0;\n  virtual float GetHudDecoShakeTranslateVelConstant() const = 0;\n  virtual float GetHudDecoShakeTranslateVelLinear() const = 0;\n  virtual float GetMaxDecoDamageShakeTranslate() const = 0;\n  virtual float GetDecoDamageShakeDeceleration() const = 0;\n  virtual float GetDecoShakeGainConstant() const = 0;\n  virtual float GetDecoShakeGainLinear() const = 0;\n  virtual float GetDecoShakeInitConstant() const = 0;\n  virtual float GetDecoShakeInitLinear() const = 0;\n  virtual float GetMaxDecoDamageShakeRotate() const = 0;\n  virtual u32 GetHudCamFovTweak() const = 0;\n  virtual u32 GetHudCamYTweak() const = 0;\n  virtual u32 GetHudCamZTweak() const = 0;\n  virtual float GetBeamVisorMenuAnimTime() const = 0;\n  virtual float GetVisorBeamMenuItemActiveScale() const = 0;\n  virtual float GetVisorBeamMenuItemInactiveScale() const = 0;\n  virtual float GetVisorBeamMenuItemTranslate() const = 0;\n  virtual float GetThreatRange() const = 0;\n  virtual float GetRadarScopeCoordRadius() const = 0;\n  virtual float GetRadarPlayerPaintRadius() const = 0;\n  virtual float GetRadarEnemyPaintRadius() const = 0;\n  virtual float GetMissileArrowVisTime() const = 0;\n  virtual EHudVisMode GetHudVisMode() const = 0;\n  virtual EHelmetVisMode GetHelmetVisMode() const = 0;\n  virtual u32 GetEnableAutoMapper() const = 0;\n  virtual u32 GetEnableTargetingManager() const = 0;\n  virtual u32 GetEnablePlayerVisor() const = 0;\n  virtual float GetThreatWarningFraction() const = 0;\n  virtual float GetMissileWarningFraction() const = 0;\n  virtual float GetFreeLookFadeTime() const = 0;\n  virtual float GetFreeLookSfxPitchScale() const = 0;\n  virtual bool GetNoAbsoluteFreeLookSfxPitch() const = 0;\n  virtual float GetFaceReflectionOrthoWidth() const = 0;\n  virtual float GetFaceReflectionOrthoHeight() const = 0;\n  virtual float GetFaceReflectionDistance() const = 0;\n  virtual float GetFaceReflectionHeight() const = 0;\n  virtual float GetFaceReflectionAspect() const = 0;\n  virtual float GetMissileWarningPulseTime() const = 0;\n  virtual float GetExplosionLightFalloffMultConstant() const = 0;\n  virtual float GetExplosionLightFalloffMultLinear() const = 0;\n  virtual float GetExplosionLightFalloffMultQuadratic() const = 0;\n  virtual float GetHudDamagePeakFactor() const = 0;\n  virtual float GetHudDamageFilterGainConstant() const = 0;\n  virtual float GetHudDamageFilterGainLinear() const = 0;\n  virtual float GetHudDamageFilterInitConstant() const = 0;\n  virtual float GetHudDamageFilterInitLinear() const = 0;\n  virtual float GetEnergyDrainModPeriod() const = 0;\n  virtual bool GetEnergyDrainSinusoidalPulse() const = 0;\n  virtual bool GetEnergyDrainFilterAdditive() const = 0;\n  virtual float GetHudDamagePulseDuration() const = 0;\n  virtual float GetHudDamageColorGain() const = 0;\n  virtual float GetHudDecoShakeTranslateGain() const = 0;\n  virtual float GetHudLagOffsetScale() const = 0;\n  virtual float GetScanAppearanceDuration() const = 0;\n  virtual float GetScanPaneFlashFactor() const = 0;\n  virtual float GetScanPaneFadeInTime() const = 0;\n  virtual float GetScanPaneFadeOutTime() const = 0;\n  virtual float GetBallViewportYReduction() const = 0;\n  virtual float GetScanWindowIdleWidth() const = 0;\n  virtual float GetScanWindowIdleHeight() const = 0;\n  virtual float GetScanWindowActiveWidth() const = 0;\n  virtual float GetScanWindowActiveHeight() const = 0;\n  virtual float GetScanWindowMagnification() const = 0;\n  virtual float GetScanWindowScanningAspect() const = 0;\n  virtual float GetScanSpeed(int idx) const = 0;\n  virtual float GetXrayBlurScaleLinear() const = 0;\n  virtual float GetXrayBlurScaleQuadratic() const = 0;\n  virtual float GetScanSidesAngle() const = 0;\n  virtual float GetScanSidesXScale() const = 0;\n  virtual float GetScanSidesPositionEnd() const = 0;\n  virtual float GetScanSidesDuration() const = 0;\n  virtual float GetScanSidesStartTime() const = 0;\n  virtual float GetScanSidesEndTime() const = 0;\n  virtual float GetScanDataDotRadius() const = 0;\n  virtual float GetScanDataDotPosRandMagnitude() const = 0;\n  virtual float GetScanDataDotSeekDurationMin() const = 0;\n  virtual float GetScanDataDotSeekDurationMax() const = 0;\n  virtual float GetScanDataDotHoldDurationMin() const = 0;\n  virtual float GetScanDataDotHoldDurationMax() const = 0;\n  virtual float GetScanSidesPositionStart() const = 0;\n  virtual bool GetShowAutomapperInMorphball() const = 0;\n  virtual bool GetLatchArticleText() const = 0;\n  virtual float GetWorldTransManagerCharsPerSfx() const = 0;\n  virtual u32 GetXRayFogMode() const = 0;\n  virtual float GetXRayFogNearZ() const = 0;\n  virtual float GetXRayFogFarZ() const = 0;\n  virtual const zeus::CColor& GetXRayFogColor() const = 0;\n  virtual float GetThermalVisorLevel() const = 0;\n  virtual const zeus::CColor& GetThermalVisorColor() const = 0;\n  virtual const zeus::CColor& GetVisorHudLightAdd(int v) const = 0;\n  virtual const zeus::CColor& GetVisorHudLightMultiply(int v) const = 0;\n  virtual const zeus::CColor& GetHudReflectivityLightColor() const = 0;\n  virtual float GetHudLightAttMulConstant() const = 0;\n  virtual float GetHudLightAttMulLinear() const = 0;\n  virtual float GetHudLightAttMulQuadratic() const = 0;\n  virtual std::string_view GetCreditsTable() const = 0;\n  virtual std::string_view GetCreditsFont() const = 0;\n  virtual std::string_view GetJapaneseCreditsFont() const = 0;\n  virtual const zeus::CColor& GetCreditsTextFontColor() const = 0;\n  virtual const zeus::CColor& GetCreditsTextBorderColor() const = 0;\n\n  static float FaceReflectionDistanceDebugValueToActualValue(float v) { return 0.015f * v + 0.2f; }\n  static float FaceReflectionHeightDebugValueToActualValue(float v) { return 0.005f * v - 0.05f; }\n  static float FaceReflectionAspectDebugValueToActualValue(float v) { return 0.05f * v + 1.f; }\n  static float FaceReflectionOrthoWidthDebugValueToActualValue(float v) { return 0.007f * v + 0.02f; }\n  static float FaceReflectionOrthoHeightDebugValueToActualValue(float v) { return 0.007f * v + 0.02f; }\n};\n\n} // namespace DataSpec\n"
  },
  {
    "path": "Runtime/Tweaks/ITweakGuiColors.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Tweaks/ITweak.hpp\"\n\nnamespace metaforce::Tweaks {\nstruct ITweakGuiColors : ITweak {\n  struct SVisorEnergyInitColors {\n    const zeus::CColor& tankFilled;\n    const zeus::CColor& tankEmpty;\n    const zeus::CColor& digitsFont;\n    const zeus::CColor& digitsOutline;\n  };\n\n  struct SVisorEnergyBarColors {\n    const zeus::CColor& filled;\n    const zeus::CColor& empty;\n    const zeus::CColor& shadow;\n  };\n\n  virtual const zeus::CColor& GetPauseBlurFilterColor() const = 0;\n  virtual const zeus::CColor& GetRadarStuffColor() const = 0;\n  virtual const zeus::CColor& GetRadarPlayerPaintColor() const = 0;\n  virtual const zeus::CColor& GetRadarEnemyPaintColor() const = 0;\n  virtual const zeus::CColor& GetHudMessageFill() const = 0;\n  virtual const zeus::CColor& GetHudMessageOutline() const = 0;\n  virtual const zeus::CColor& GetHudFrameColor() const = 0;\n  virtual const zeus::CColor& GetMissileIconColorActive() const = 0;\n  virtual const zeus::CColor& GetVisorBeamMenuItemActive() const = 0;\n  virtual const zeus::CColor& GetVisorBeamMenuItemInactive() const = 0;\n  virtual const zeus::CColor& GetEnergyBarFilledLowEnergy() const = 0;\n  virtual const zeus::CColor& GetEnergyBarShadowLowEnergy() const = 0;\n  virtual const zeus::CColor& GetEnergyBarEmptyLowEnergy() const = 0;\n  virtual const zeus::CColor& GetHudDamageLightColor() const = 0;\n  virtual const zeus::CColor& GetVisorMenuTextFont() const = 0;\n  virtual const zeus::CColor& GetVisorMenuTextOutline() const = 0;\n  virtual const zeus::CColor& GetBeamMenuTextFont() const = 0;\n  virtual const zeus::CColor& GetBeamMenuTextOutline() const = 0;\n  virtual const zeus::CColor& GetEnergyWarningFont() const = 0;\n  virtual const zeus::CColor& GetThreatWarningFont() const = 0;\n  virtual const zeus::CColor& GetMissileWarningFont() const = 0;\n  virtual const zeus::CColor& GetThreatBarFilled() const = 0;\n  virtual const zeus::CColor& GetThreatBarShadow() const = 0;\n  virtual const zeus::CColor& GetThreatBarEmpty() const = 0;\n  virtual const zeus::CColor& GetMissileBarFilled() const = 0;\n  virtual const zeus::CColor& GetMissileBarShadow() const = 0;\n  virtual const zeus::CColor& GetMissileBarEmpty() const = 0;\n  virtual const zeus::CColor& GetThreatIconColor() const = 0;\n  virtual const zeus::CColor& GetTickDecoColor() const = 0;\n  virtual const zeus::CColor& GetHelmetLightColor() const = 0;\n  virtual const zeus::CColor& GetThreatIconSafeColor() const = 0;\n  virtual const zeus::CColor& GetMissileIconColorInactive() const = 0;\n  virtual const zeus::CColor& GetMissileIconColorChargedCanAlt() const = 0;\n  virtual const zeus::CColor& GetMissileIconColorChargedNoAlt() const = 0;\n  virtual const zeus::CColor& GetMissileIconColorDepleteAlt() const = 0;\n  virtual const zeus::CColor& GetVisorBeamMenuLozColor() const = 0;\n  virtual const zeus::CColor& GetEnergyWarningOutline() const = 0;\n  virtual const zeus::CColor& GetThreatWarningOutline() const = 0;\n  virtual const zeus::CColor& GetMissileWarningOutline() const = 0;\n  virtual const zeus::CColor& GetDamageAmbientColor() const = 0;\n  virtual const zeus::CColor& GetScanFrameInactiveColor() const = 0;\n  virtual const zeus::CColor& GetScanFrameActiveColor() const = 0;\n  virtual const zeus::CColor& GetScanFrameImpulseColor() const = 0;\n  virtual const zeus::CColor& GetScanVisorHudLightMultiply() const = 0;\n  virtual const zeus::CColor& GetScanVisorScreenDimColor() const = 0;\n  virtual const zeus::CColor& GetThermalVisorHudLightMultiply() const = 0;\n  virtual const zeus::CColor& GetEnergyDrainFilterColor() const = 0;\n  virtual const zeus::CColor& GetDamageAmbientPulseColor() const = 0;\n  virtual const zeus::CColor& GetEnergyBarFlashColor() const = 0;\n  virtual const zeus::CColor& GetXRayEnergyDecoColor() const = 0;\n  virtual const zeus::CColor& GetScanDataDotColor() const = 0;\n  virtual const zeus::CColor& GetPowerBombDigitAvailableFont() const = 0;\n  virtual const zeus::CColor& GetPowerBombDigitAvailableOutline() const = 0;\n  virtual const zeus::CColor& GetBallBombFilledColor() const = 0;\n  virtual const zeus::CColor& GetBallBombEmptyColor() const = 0;\n  virtual const zeus::CColor& GetPowerBombIconAvailableColor() const = 0;\n  virtual const zeus::CColor& GetBallBombEnergyColor() const = 0;\n  virtual const zeus::CColor& GetBallBombDecoColor() const = 0;\n  virtual const zeus::CColor& GetPowerBombDigitDelpetedFont() const = 0;\n  virtual const zeus::CColor& GetPowerBombDigitDelpetedOutline() const = 0;\n  virtual const zeus::CColor& GetPowerBombIconDepletedColor() const = 0;\n  virtual const zeus::CColor& GetScanDisplayImagePaneColor() const = 0;\n  virtual const zeus::CColor& GetThreatIconWarningColor() const = 0;\n  virtual const zeus::CColor& GetHudCounterFill() const = 0;\n  virtual const zeus::CColor& GetHudCounterOutline() const = 0;\n  virtual const zeus::CColor& GetScanIconCriticalColor() const = 0;\n  virtual const zeus::CColor& GetScanIconCriticalDimColor() const = 0;\n  virtual const zeus::CColor& GetScanIconNoncriticalColor() const = 0;\n  virtual const zeus::CColor& GetScanIconNoncriticalDimColor() const = 0;\n  virtual const zeus::CColor& GetScanReticuleColor() const = 0;\n  virtual const zeus::CColor& GetThreatDigitsFont() const = 0;\n  virtual const zeus::CColor& GetThreatDigitsOutline() const = 0;\n  virtual const zeus::CColor& GetMissileDigitsFont() const = 0;\n  virtual const zeus::CColor& GetMissileDigitsOutline() const = 0;\n  virtual const zeus::CColor& GetThermalDecoColor() const = 0;\n  virtual const zeus::CColor& GetThermalOutlinesColor() const = 0;\n  virtual const zeus::CColor& GetThermalLockColor() const = 0;\n  virtual const zeus::CColor& GetPauseItemAmberColor() const = 0;\n  virtual const zeus::CColor& GetPauseItemBlueColor() const = 0;\n  virtual SVisorEnergyInitColors GetVisorEnergyInitColors(int idx) const = 0;\n  virtual SVisorEnergyBarColors GetVisorEnergyBarColors(int idx) const = 0;\n};\n} // namespace DataSpec\n"
  },
  {
    "path": "Runtime/Tweaks/ITweakGunRes.hpp",
    "content": "#pragma once\n\n#include <array>\n\n#include \"Runtime/Tweaks/ITweak.hpp\"\n#include \"Runtime/IFactory.hpp\"\n#include \"Runtime/CPlayerState.hpp\"\n\nnamespace metaforce::Tweaks {\n\nstruct ITweakGunRes : ITweak {\n  using ResId = metaforce::CAssetId;\n  using EBeamId = metaforce::CPlayerState::EBeamId;\n\n  ResId x4_gunMotion;\n  ResId x8_grappleArm;\n  ResId xc_rightHand;\n\n  ResId x10_powerBeam;\n  ResId x14_iceBeam;\n  ResId x18_waveBeam;\n  ResId x1c_plasmaBeam;\n  ResId x20_phazonBeam;\n\n  ResId x24_holoTransition;\n\n  ResId x28_bombSet;\n  ResId x2c_bombExplode;\n  ResId x30_powerBombExplode;\n\n  /* Power, Ice, Wave, Plasma, Phazon / Beam, Ball */\n  using WeaponPair = std::array<ResId, 2>;\n  std::array<WeaponPair, 5> x34_weapons;\n  std::array<ResId, 5> x84_muzzle;\n  std::array<ResId, 5> x94_charge;\n  std::array<ResId, 5> xa4_auxMuzzle;\n\n  ResId xb4_grappleSegment;\n  ResId xb8_grappleClaw;\n  ResId xbc_grappleHit;\n  ResId xc0_grappleMuzzle;\n  ResId xc4_grappleSwoosh;\n\n  ResId GetBeamModel(EBeamId beam) const {\n    auto b = int(beam);\n    if (b < 0 || b > 4)\n      b = 0;\n    switch (EBeamId(b)) {\n    default:\n    case EBeamId::Power:\n      return x10_powerBeam;\n    case EBeamId::Ice:\n      return x14_iceBeam;\n    case EBeamId::Wave:\n      return x18_waveBeam;\n    case EBeamId::Plasma:\n      return x1c_plasmaBeam;\n    case EBeamId::Phazon:\n      return x20_phazonBeam;\n    }\n  }\n\n  const WeaponPair& GetWeaponPair(EBeamId beam) const {\n    const auto b = int(beam);\n    if (b < 0 || b > 4) {\n      return x34_weapons[0];\n    }\n    return x34_weapons[b];\n  }\n\n  void ResolveResources(const metaforce::IFactory& factory) {\n    x4_gunMotion = factory.GetResourceIdByName(GetGunMotion())->id;\n    x8_grappleArm = factory.GetResourceIdByName(GetGrappleArm())->id;\n    xc_rightHand = factory.GetResourceIdByName(GetRightHand())->id;\n\n    x10_powerBeam = factory.GetResourceIdByName(GetPowerBeam())->id;\n    x14_iceBeam = factory.GetResourceIdByName(GetIceBeam())->id;\n    x18_waveBeam = factory.GetResourceIdByName(GetWaveBeam())->id;\n    x1c_plasmaBeam = factory.GetResourceIdByName(GetPlasmaBeam())->id;\n    x20_phazonBeam = factory.GetResourceIdByName(GetPhazonBeam())->id;\n\n    x24_holoTransition = factory.GetResourceIdByName(GetHoloTransition())->id;\n\n    x28_bombSet = factory.GetResourceIdByName(GetBombSet())->id;\n    x2c_bombExplode = factory.GetResourceIdByName(GetBombExplode())->id;\n    x30_powerBombExplode = factory.GetResourceIdByName(GetPowerBombExplode())->id;\n\n    for (size_t i = 0; i < x34_weapons.size(); ++i) {\n      for (size_t j = 0; j < x34_weapons[i].size(); ++j) {\n        x34_weapons[i][j] = factory.GetResourceIdByName(GetWeapon(i, j != 0))->id;\n      }\n    }\n\n    for (size_t i = 0; i < x84_muzzle.size(); ++i) {\n      x84_muzzle[i] = factory.GetResourceIdByName(GetMuzzleParticle(i))->id;\n    }\n\n    for (size_t i = 0; i < x94_charge.size(); ++i) {\n      x94_charge[i] = factory.GetResourceIdByName(GetChargeParticle(i))->id;\n    }\n\n    for (size_t i = 0; i < xa4_auxMuzzle.size(); ++i) {\n      xa4_auxMuzzle[i] = factory.GetResourceIdByName(GetAuxMuzzleParticle(i))->id;\n    }\n\n    xb4_grappleSegment = factory.GetResourceIdByName(GetGrappleSegmentParticle())->id;\n    xb8_grappleClaw = factory.GetResourceIdByName(GetGrappleClawParticle())->id;\n    xbc_grappleHit = factory.GetResourceIdByName(GetGrappleHitParticle())->id;\n    xc0_grappleMuzzle = factory.GetResourceIdByName(GetGrappleMuzzleParticle())->id;\n    xc4_grappleSwoosh = factory.GetResourceIdByName(GetGrappleSwooshParticle())->id;\n  }\n\nprotected:\n  virtual const std::string& GetGunMotion() const = 0;\n  virtual const std::string& GetGrappleArm() const = 0;\n  virtual const std::string& GetRightHand() const = 0;\n\n  virtual const std::string& GetPowerBeam() const = 0;\n  virtual const std::string& GetIceBeam() const = 0;\n  virtual const std::string& GetWaveBeam() const = 0;\n  virtual const std::string& GetPlasmaBeam() const = 0;\n  virtual const std::string& GetPhazonBeam() const = 0;\n\n  virtual const std::string& GetHoloTransition() const = 0;\n\n  virtual const std::string& GetBombSet() const = 0;\n  virtual const std::string& GetBombExplode() const = 0;\n  virtual const std::string& GetPowerBombExplode() const = 0;\n\n  virtual const std::string& GetWeapon(size_t idx, bool ball) const = 0;\n  virtual const std::string& GetMuzzleParticle(size_t idx) const = 0;\n  virtual const std::string& GetChargeParticle(size_t idx) const = 0;\n  virtual const std::string& GetAuxMuzzleParticle(size_t idx) const = 0;\n\n  virtual const std::string& GetGrappleSegmentParticle() const = 0;\n  virtual const std::string& GetGrappleClawParticle() const = 0;\n  virtual const std::string& GetGrappleHitParticle() const = 0;\n  virtual const std::string& GetGrappleMuzzleParticle() const = 0;\n  virtual const std::string& GetGrappleSwooshParticle() const = 0;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Tweaks/ITweakParticle.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Tweaks/ITweak.hpp\"\n\nnamespace metaforce::Tweaks {\n\nstruct ITweakParticle : ITweak {};\n\n} // namespace DataSpec\n"
  },
  {
    "path": "Runtime/Tweaks/ITweakPlayer.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Tweaks/ITweak.hpp\"\n#include \"zeus/CAABox.hpp\"\n\nnamespace metaforce::Tweaks {\n\nstruct ITweakPlayer : ITweak {\n  virtual float GetMaxTranslationalAcceleration(int s) const = 0;\n  virtual float GetMaxRotationalAcceleration(int s) const = 0;\n  virtual float GetPlayerTranslationFriction(int s) const = 0;\n  virtual float GetPlayerRotationFriction(int s) const = 0;\n  virtual float GetPlayerRotationMaxSpeed(int s) const = 0;\n  virtual float GetPlayerTranslationMaxSpeed(int s) const = 0;\n  virtual float GetNormalGravAccel() const = 0;\n  virtual float GetFluidGravAccel() const = 0;\n  virtual float GetVerticalJumpAccel() const = 0;\n  virtual float GetHorizontalJumpAccel() const = 0;\n  virtual float GetVerticalDoubleJumpAccel() const = 0;\n  virtual float GetHorizontalDoubleJumpAccel() const = 0;\n  virtual float GetWaterJumpFactor() const = 0;\n  virtual float GetWaterBallJumpFactor() const = 0;\n  virtual float GetLavaJumpFactor() const = 0;\n  virtual float GetLavaBallJumpFactor() const = 0;\n  virtual float GetPhazonJumpFactor() const = 0;\n  virtual float GetPhazonBallJumpFactor() const = 0;\n  virtual float GetAllowedJumpTime() const = 0;\n  virtual float GetAllowedDoubleJumpTime() const = 0;\n  virtual float GetMinDoubleJumpWindow() const = 0;\n  virtual float GetMaxDoubleJumpWindow() const = 0;\n  virtual float GetMinJumpTime() const = 0;\n  virtual float GetMinDoubleJumpTime() const = 0;\n  virtual float GetAllowedLedgeTime() const = 0;\n  virtual float GetDoubleJumpImpulse() const = 0;\n  virtual float GetBackwardsForceMultiplier() const = 0;\n  virtual float GetBombJumpRadius() const = 0;\n  virtual float GetBombJumpHeight() const = 0;\n  virtual float GetEyeOffset() const = 0;\n  virtual float GetTurnSpeedMultiplier() const = 0;\n  virtual float GetFreeLookTurnSpeedMultiplier() const = 0;\n  virtual float GetFreeLookSpeed() const = 0;\n  virtual float GetFreeLookSnapSpeed() const = 0;\n  virtual float GetFreeLookCenteredThresholdAngle() const = 0;\n  virtual float GetFreeLookCenteredTime() const = 0;\n  virtual float GetOrbitModeTimer() const = 0;\n  virtual float GetOrbitUpperAngle() const = 0;\n  virtual float GetOrbitLowerAngle() const = 0;\n  virtual float GetOrbitHorizAngle() const = 0;\n  virtual float GetOrbitMaxTargetDistance() const = 0;\n  virtual float GetOrbitMaxLockDistance() const = 0;\n  virtual float GetOrbitDistanceThreshold() const = 0;\n  virtual u32 GetOrbitScreenBoxHalfExtentX(int zone) const = 0;\n  virtual u32 GetOrbitScreenBoxHalfExtentY(int zone) const = 0;\n  virtual u32 GetOrbitScreenBoxCenterX(int zone) const = 0;\n  virtual u32 GetOrbitScreenBoxCenterY(int zone) const = 0;\n  virtual u32 GetOrbitZoneIdealX(int zone) const = 0;\n  virtual u32 GetOrbitZoneIdealY(int zone) const = 0;\n  virtual float GetOrbitNearX() const = 0;\n  virtual float GetOrbitNearZ() const = 0;\n  virtual float GetOrbitFixedOffsetZDiff() const = 0;\n  virtual float GetOrbitZRange() const = 0;\n  virtual bool GetDashEnabled() const = 0;\n  virtual bool GetDashOnButtonRelease() const = 0;\n  virtual float GetDashButtonHoldCancelTime() const = 0;\n  virtual float GetDashStrafeInputThreshold() const = 0;\n  virtual float GetSidewaysDoubleJumpImpulse() const = 0;\n  virtual float GetSidewaysVerticalDoubleJumpAccel() const = 0;\n  virtual float GetSidewaysHorizontalDoubleJumpAccel() const = 0;\n  virtual float GetScanningRange() const = 0; // x218\n  virtual bool GetScanRetention() const = 0;\n  virtual bool GetScanFreezesGame() const = 0; // x21c_25\n  virtual bool GetOrbitWhileScanning() const = 0;\n  virtual bool GetFallingDoubleJump() const = 0;\n  virtual bool GetImpulseDoubleJump() const = 0;\n  virtual bool GetFiringCancelsCameraPitch() const = 0;\n  virtual bool GetAssistedAimingIgnoreHorizontal() const = 0;\n  virtual bool GetAssistedAimingIgnoreVertical() const = 0;\n  virtual float GetAimMaxDistance() const = 0;\n  virtual float GetAimThresholdDistance() const = 0;\n  virtual float GetAimBoxWidth() const = 0;\n  virtual float GetAimBoxHeight() const = 0;\n  virtual float GetAimTargetTimer() const = 0;\n  virtual float GetAimAssistHorizontalAngle() const = 0;\n  virtual float GetAimAssistVerticalAngle() const = 0;\n  virtual float GetScanMaxTargetDistance() const = 0;\n  virtual float GetScanMaxLockDistance() const = 0;\n  virtual bool GetMoveDuringFreeLook() const = 0;\n  virtual bool GetHoldButtonsForFreeLook() const = 0;\n  virtual bool GetTwoButtonsForFreeLook() const = 0;\n  virtual bool GetAimWhenOrbitingPoint() const = 0;\n  virtual bool GetStayInFreeLookWhileFiring() const = 0;\n  virtual bool GetOrbitFixedOffset() const = 0;\n  virtual bool GetGunButtonTogglesHolster() const = 0;\n  virtual bool GetGunNotFiringHolstersGun() const = 0;\n  virtual float GetPlayerHeight() const = 0;         // x26c\n  virtual float GetPlayerXYHalfExtent() const = 0;   // x270\n  virtual bool GetFreeLookTurnsPlayer() const = 0;   // x228_24\n  virtual float GetStepUpHeight() const = 0;         // x274\n  virtual float GetStepDownHeight() const = 0;       // x278\n  virtual float GetPlayerBallHalfExtent() const = 0; // x27c\n  virtual float GetOrbitDistanceMax() const = 0;\n  virtual float GetGrappleSwingLength() const = 0;\n  virtual float GetGrappleSwingPeriod() const = 0;\n  virtual float GetGrapplePullSpeedMin() const = 0;\n  virtual float GetMaxGrappleLockedTurnAlignDistance() const = 0;\n  virtual float GetGrapplePullSpeedProportion() const = 0;\n  virtual float GetGrapplePullSpeedMax() const = 0;\n  virtual float GetGrappleLookCenterSpeed() const = 0;\n  virtual float GetMaxGrappleTurnSpeed() const = 0;\n  virtual float GetGrappleJumpForce() const = 0;\n  virtual float GetGrappleReleaseTime() const = 0;\n  virtual u32 GetGrappleJumpMode() const = 0;\n  virtual bool GetOrbitReleaseBreaksGrapple() const = 0;\n  virtual bool GetInvertGrappleTurn() const = 0;\n  virtual float GetGrappleBeamSpeed() const = 0;\n  virtual float GetGrappleBeamXWaveAmplitude() const = 0;\n  virtual float GetGrappleBeamZWaveAmplitude() const = 0;\n  virtual float GetGrappleBeamAnglePhaseDelta() const = 0;\n  virtual float GetHorizontalFreeLookAngleVel() const = 0;\n  virtual float GetVerticalFreeLookAngleVel() const = 0; // x134\n  virtual float GetOrbitCameraSpeed() const = 0;         // x184\n  virtual float GetOrbitPreventionTime() const = 0;\n  virtual float GetJumpCameraPitchDownStart() const = 0; // x288\n  virtual float GetJumpCameraPitchDownFull() const = 0;  // x28c\n  virtual float GetJumpCameraPitchDownAngle() const = 0; // x290\n  virtual float GetFallCameraPitchDownStart() const = 0; // x294\n  virtual float GetFallCameraPitchDownFull() const = 0;  // x298\n  virtual float GetFallCameraPitchDownAngle() const = 0; // x29c\n  virtual float GetFirstPersonCameraSpeed() const = 0;   // x280\n  virtual float GetGrappleCameraSpeed() const = 0;       // x2b0\n  virtual float GetFreeLookDampenFactor() const = 0;     // x14c\n  virtual float GetLeftLogicalThreshold() const = 0;\n  virtual float GetRightLogicalThreshold() const = 0;\n  virtual float GetOrbitMinDistance(int type) const = 0;\n  virtual float GetOrbitNormalDistance(int type) const = 0;\n  virtual float GetOrbitMaxDistance(int type) const = 0;\n  virtual float GetFrozenTimeout() const = 0;\n  virtual u32 GetIceBreakJumpCount() const = 0;\n  virtual float GetVariaDamageReduction() const = 0;\n  virtual float GetGravityDamageReduction() const = 0;\n  virtual float GetPhazonDamageReduction() const = 0;\n};\n\n} // namespace DataSpec\n"
  },
  {
    "path": "Runtime/Tweaks/ITweakPlayerControl.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Tweaks/ITweak.hpp\"\n#include \"Runtime/Input/ControlMapper.hpp\"\n\nnamespace metaforce::Tweaks {\n\nstruct ITweakPlayerControl : ITweak {\n  [[nodiscard]] virtual ControlMapper::EFunctionList GetMapping(u32) const = 0;\n};\n\n} // namespace DataSpec\n"
  },
  {
    "path": "Runtime/Tweaks/ITweakPlayerGun.cpp",
    "content": "#include \"Runtime/Tweaks/ITweakPlayerGun.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n\nnamespace metaforce {\nSShotParam::SShotParam(CInputStream& in)\n: x0_weaponType(in.ReadLong())\n, x8_damage(in.ReadFloat())\n, xc_radiusDamage(in.ReadFloat())\n, x10_radius(in.ReadFloat())\n, x14_knockback(in.ReadFloat()) {}\n\nSWeaponInfo::SWeaponInfo(CInputStream& in)\n: x0_coolDown(in.ReadFloat()), x4_normal(in.Get<SShotParam>()), x20_charged(in.Get<SChargedShotParam>()) {}\n} // namespace metaforce"
  },
  {
    "path": "Runtime/Tweaks/ITweakPlayerGun.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Tweaks/ITweak.hpp\"\n\nnamespace metaforce {\n/* Same as CDamageInfo */\nstruct SShotParam {\n  u32 x0_weaponType = -1;\n  bool x4_24_charged : 1 = false;\n  bool x4_25_combo : 1 = false;\n  bool x4_26_instaKill : 1 = false;\n  float x8_damage = 0.f;\n  float xc_radiusDamage = 0.f;\n  float x10_radius = 0.f;\n  float x14_knockback = 0.f;\n  bool x18_24_noImmunity : 1 = false;\n  SShotParam() = default;\n  explicit SShotParam(CInputStream& in);\n};\n\nstruct SComboShotParam : SShotParam {\n  SComboShotParam() { x4_25_combo = true; }\n  explicit SComboShotParam(CInputStream& in) : SShotParam(in) { x4_25_combo = true; }\n};\n\nstruct SChargedShotParam : SShotParam {\n  SChargedShotParam() { x4_24_charged = true; }\n  explicit SChargedShotParam(CInputStream& in) : SShotParam(in) { x4_24_charged = true; }\n};\n\nstruct SWeaponInfo {\n  float x0_coolDown = 0.1f;\n  SShotParam x4_normal;\n  SChargedShotParam x20_charged;\n  SWeaponInfo() = default;\n  explicit SWeaponInfo(CInputStream& in);\n};\n\nnamespace Tweaks {\nstruct ITweakPlayerGun : ITweak {\n  virtual float GetUpLookAngle() const = 0;\n  virtual float GetDownLookAngle() const = 0;\n  virtual float GetVerticalSpread() const = 0;\n  virtual float GetHorizontalSpread() const = 0;\n  virtual float GetHighVerticalSpread() const = 0;\n  virtual float GetHighHorizontalSpread() const = 0;\n  virtual float GetLowVerticalSpread() const = 0;\n  virtual float GetLowHorizontalSpread() const = 0;\n  virtual float GetAimVerticalSpeed() const = 0;   // x24\n  virtual float GetAimHorizontalSpeed() const = 0; // x28\n  virtual float GetBombFuseTime() const = 0;       // x2c\n  virtual float GetBombDropDelayTime() const = 0;  // x30\n  virtual float GetHoloHoldTime() const = 0;       // x34\n  virtual float GetGunTransformTime() const = 0;   // x38\n  virtual float GetGunHolsterTime() const = 0;\n  virtual float GetGunNotFiringTime() const = 0;\n  virtual float GetFixedVerticalAim() const = 0;\n  virtual float GetGunExtendDistance() const = 0;\n  virtual const zeus::CVector3f& GetGunPosition() const = 0;\n  virtual const zeus::CVector3f& GetGrapplingArmPosition() const = 0;\n  virtual float GetRichochetDamage(u32) const = 0;\n  virtual const SWeaponInfo& GetBeamInfo(s32 beam) const = 0;\n  virtual const SComboShotParam& GetComboShotInfo(s32 beam) const = 0;\n  virtual const SShotParam& GetBombInfo() const = 0;\n  virtual const SShotParam& GetPowerBombInfo() const = 0;\n};\n} // namespace Tweaks\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Tweaks/ITweakPlayerRes.hpp",
    "content": "#pragma once\n\n#include <array>\n\n#include \"Runtime/Tweaks/ITweak.hpp\"\n#include \"Runtime/IFactory.hpp\"\n#include \"Runtime/CPlayerState.hpp\"\n\nnamespace metaforce::Tweaks {\n\nstruct ITweakPlayerRes : ITweak {\n  using ResId = metaforce::CAssetId;\n  using EBeamId = metaforce::CPlayerState::EBeamId;\n\n  ResId x4_saveStationIcon;\n  ResId x8_missileStationIcon;\n  ResId xc_elevatorIcon;\n\n  ResId x10_minesBreakFirstTopIcon;\n  ResId x14_minesBreakFirstBottomIcon;\n  ResId x18_minesBreakSecondTopIcon;\n  ResId x1c_minesBreakSecondBottomIcon;\n\n  ResId rs5_mapArrowUp;\n  ResId rs5_mapArrowDown;\n\n  /* N, U, UL, L, DL, D, DR, R, UR */\n  std::array<ResId, 9> x24_lStick;\n  std::array<ResId, 9> x4c_cStick;\n\n  /* Out, In */\n  std::array<ResId, 2> x74_lTrigger;\n  std::array<ResId, 2> x80_rTrigger;\n  std::array<ResId, 2> x8c_startButton;\n  std::array<ResId, 2> x98_aButton;\n  std::array<ResId, 2> xa4_bButton;\n  std::array<ResId, 2> xb0_xButton;\n  std::array<ResId, 2> xbc_yButton;\n\n  ResId xc4_ballTransitionsANCS;\n\n  /* Power, Ice, Wave, Plasma, Phazon */\n  std::array<ResId, 5> xc8_ballTransitions;\n  std::array<ResId, 5> xdc_cineGun;\n\n  float xf0_cinematicMoveOutofIntoPlayerDistance;\n\n  ResId GetBeamBallTransitionModel(EBeamId beam) const {\n    auto b = size_t(beam);\n    if (b >= xc8_ballTransitions.size()) {\n      b = 0;\n    }\n    switch (EBeamId(b)) {\n    case EBeamId::Power:\n    default:\n      return xc8_ballTransitions[0];\n    case EBeamId::Ice:\n      return xc8_ballTransitions[1];\n    case EBeamId::Wave:\n      return xc8_ballTransitions[2];\n    case EBeamId::Plasma:\n      return xc8_ballTransitions[3];\n    case EBeamId::Phazon:\n      return xc8_ballTransitions[4];\n    }\n  }\n\n  ResId GetBeamCineModel(EBeamId beam) const {\n    auto b = size_t(beam);\n    if (b >= xdc_cineGun.size()) {\n      b = 0;\n    }\n    switch (EBeamId(b)) {\n    case EBeamId::Power:\n    default:\n      return xdc_cineGun[0];\n    case EBeamId::Ice:\n      return xdc_cineGun[1];\n    case EBeamId::Wave:\n      return xdc_cineGun[2];\n    case EBeamId::Plasma:\n      return xdc_cineGun[3];\n    case EBeamId::Phazon:\n      return xdc_cineGun[4];\n    }\n  }\n\n  void ResolveResources(const metaforce::IFactory& factory) {\n    x4_saveStationIcon = factory.GetResourceIdByName(_GetSaveStationIcon())->id;\n    x8_missileStationIcon = factory.GetResourceIdByName(_GetMissileStationIcon())->id;\n    xc_elevatorIcon = factory.GetResourceIdByName(_GetElevatorIcon())->id;\n\n    x10_minesBreakFirstTopIcon = factory.GetResourceIdByName(_GetMinesBreakFirstTopIcon())->id;\n    x14_minesBreakFirstBottomIcon = factory.GetResourceIdByName(_GetMinesBreakFirstBottomIcon())->id;\n    x18_minesBreakSecondTopIcon = factory.GetResourceIdByName(_GetMinesBreakSecondTopIcon())->id;\n    x1c_minesBreakSecondBottomIcon = factory.GetResourceIdByName(_GetMinesBreakSecondBottomIcon())->id;\n\n    for (size_t i = 0; i < x24_lStick.size(); ++i) {\n      x24_lStick[i] = factory.GetResourceIdByName(_GetLStick(i))->id;\n    }\n\n    for (size_t i = 0; i < x4c_cStick.size(); ++i) {\n      x4c_cStick[i] = factory.GetResourceIdByName(_GetCStick(i))->id;\n    }\n\n    for (size_t i = 0; i < x74_lTrigger.size(); ++i) {\n      x74_lTrigger[i] = factory.GetResourceIdByName(_GetLTrigger(i))->id;\n    }\n\n    for (size_t i = 0; i < x80_rTrigger.size(); ++i) {\n      x80_rTrigger[i] = factory.GetResourceIdByName(_GetRTrigger(i))->id;\n    }\n\n    for (size_t i = 0; i < x8c_startButton.size(); ++i) {\n      x8c_startButton[i] = factory.GetResourceIdByName(_GetStartButton(i))->id;\n    }\n\n    for (size_t i = 0; i < x98_aButton.size(); ++i) {\n      x98_aButton[i] = factory.GetResourceIdByName(_GetAButton(i))->id;\n    }\n\n    for (size_t i = 0; i < xa4_bButton.size(); ++i) {\n      xa4_bButton[i] = factory.GetResourceIdByName(_GetBButton(i))->id;\n    }\n\n    for (size_t i = 0; i < xb0_xButton.size(); ++i) {\n      xb0_xButton[i] = factory.GetResourceIdByName(_GetXButton(i))->id;\n    }\n\n    for (size_t i = 0; i < xbc_yButton.size(); ++i) {\n      xbc_yButton[i] = factory.GetResourceIdByName(_GetYButton(i))->id;\n    }\n\n    xc4_ballTransitionsANCS = factory.GetResourceIdByName(_GetBallTransitionsANCS())->id;\n\n    for (size_t i = 0; i < xc8_ballTransitions.size(); ++i) {\n      xc8_ballTransitions[i] = factory.GetResourceIdByName(_GetBallTransitionBeamRes(i))->id;\n    }\n\n    for (size_t i = 0; i < xdc_cineGun.size(); ++i) {\n      xdc_cineGun[i] = factory.GetResourceIdByName(_GetBeamCineModel(i))->id;\n    }\n\n    xf0_cinematicMoveOutofIntoPlayerDistance = _GetCinematicMoveOutofIntoPlayerDistance();\n  }\n\nprotected:\n  virtual std::string_view _GetSaveStationIcon() const = 0;\n  virtual std::string_view _GetMissileStationIcon() const = 0;\n  virtual std::string_view _GetElevatorIcon() const = 0;\n\n  virtual std::string_view _GetMinesBreakFirstTopIcon() const = 0;\n  virtual std::string_view _GetMinesBreakFirstBottomIcon() const = 0;\n  virtual std::string_view _GetMinesBreakSecondTopIcon() const = 0;\n  virtual std::string_view _GetMinesBreakSecondBottomIcon() const = 0;\n\n  virtual std::string_view _GetLStick(size_t idx) const = 0;\n  virtual std::string_view _GetCStick(size_t idx) const = 0;\n\n  virtual std::string_view _GetLTrigger(size_t idx) const = 0;\n  virtual std::string_view _GetRTrigger(size_t idx) const = 0;\n  virtual std::string_view _GetStartButton(size_t idx) const = 0;\n  virtual std::string_view _GetAButton(size_t idx) const = 0;\n  virtual std::string_view _GetBButton(size_t idx) const = 0;\n  virtual std::string_view _GetXButton(size_t idx) const = 0;\n  virtual std::string_view _GetYButton(size_t idx) const = 0;\n\n  virtual std::string_view _GetBallTransitionsANCS() const = 0;\n\n  virtual std::string_view _GetBallTransitionBeamRes(size_t idx) const = 0;\n  virtual std::string_view _GetBeamCineModel(size_t idx) const = 0;\n\n  virtual float _GetCinematicMoveOutofIntoPlayerDistance() const = 0;\n};\n\n} // namespace DataSpec\n"
  },
  {
    "path": "Runtime/Tweaks/ITweakSlideShow.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Tweaks/ITweak.hpp\"\n\nnamespace metaforce::Tweaks {\n\nstruct ITweakSlideShow : ITweak {\n  virtual std::string_view GetFont() const = 0;\n  virtual const zeus::CColor& GetFontColor() const = 0;\n  virtual const zeus::CColor& GetOutlineColor() const = 0;\n  virtual float GetScanPercentInterval() const = 0;\n  virtual float GetX54() const = 0;\n};\n\n} // namespace DataSpec\n"
  },
  {
    "path": "Runtime/Tweaks/ITweakTargeting.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Tweaks/ITweak.hpp\"\n\nnamespace metaforce::Tweaks {\nstruct ITweakTargeting : public ITweak {\n  virtual u32 GetTargetRadiusMode() const = 0;\n  virtual float GetCurrLockOnExitDuration() const = 0;\n  virtual float GetCurrLockOnEnterDuration() const = 0;\n  virtual float GetCurrLockOnSwitchDuration() const = 0;\n  virtual float GetLockConfirmScale() const = 0;\n  virtual float GetNextLockOnEnterDuration() const = 0;\n  virtual float GetNextLockOnExitDuration() const = 0;\n  virtual float GetNextLockOnSwitchDuration() const = 0;\n  virtual float GetSeekerScale() const = 0;\n  virtual float GetSeekerAngleSpeed() const = 0;\n  virtual float GetXRayRetAngleSpeed() const = 0;\n  virtual float GetOrbitPointZOffset() const = 0;\n  virtual float GetOrbitPointInTime() const = 0;\n  virtual float GetOrbitPointOutTime() const = 0;\n  virtual const zeus::CColor& GetThermalReticuleColor() const = 0;\n  virtual float GetTargetFlowerScale() const = 0;\n  virtual const zeus::CColor& GetTargetFlowerColor() const = 0;\n  virtual float GetMissileBracketDuration() const = 0;\n  virtual float GetMissileBracketScaleStart() const = 0;\n  virtual float GetMissileBracketScaleEnd() const = 0;\n  virtual float GetMissileBracketScaleDuration() const = 0;\n  virtual const zeus::CColor& GetMissileBracketColor() const = 0;\n  virtual float GetChargeGaugeOvershootOffset() const = 0;\n  virtual float GetChargeGaugeOvershootDuration() const = 0;\n  virtual float GetOuterBeamSquaresScale() const = 0;\n  virtual const zeus::CColor& GetOuterBeamSquareColor() const = 0;\n  virtual float GetLockonDuration() const = 0;\n  virtual float GetInnerBeamScale() const = 0;\n  virtual const zeus::CColor& GetInnerBeamColorPower() const = 0;\n  virtual const zeus::CColor& GetInnerBeamColorIce() const = 0;\n  virtual const zeus::CColor& GetInnerBeamColorWave() const = 0;\n  virtual const zeus::CColor& GetInnerBeamColorPlasma() const = 0;\n  virtual const float* GetOuterBeamSquareAngles(int i) const = 0;\n  virtual float GetChargeGaugeAngle(int i) const = 0;\n  virtual float GetChargeGaugeScale() const = 0;\n  virtual const zeus::CColor& GetChargeGaugeNonFullColor() const = 0;\n  virtual u32 GetChargeTickCount() const = 0;\n  virtual float GetChargeTickAnglePitch() const = 0;\n  virtual float GetLockFireScale() const = 0;\n  virtual float GetLockFireDuration() const = 0;\n  virtual const zeus::CColor& GetLockFireColor() const = 0;\n  virtual float GetLockDaggerScaleStart() const = 0;\n  virtual float GetLockDaggerScaleEnd() const = 0;\n  virtual const zeus::CColor& GetLockDaggerColor() const = 0;\n  virtual float GetLockDaggerAngle0() const = 0;\n  virtual float GetLockDaggerAngle1() const = 0;\n  virtual float GetLockDaggerAngle2() const = 0;\n  virtual const zeus::CColor& GetLockConfirmColor() const = 0;\n  virtual const zeus::CColor& GetSeekerColor() const = 0;\n  virtual float GetLockConfirmClampMin() const = 0;\n  virtual float GetLockConfirmClampMax() const = 0;\n  virtual float GetTargetFlowerClampMin() const = 0;\n  virtual float GetTargetFlowerClampMax() const = 0;\n  virtual float GetSeekerClampMin() const = 0;\n  virtual float GetSeekerClampMax() const = 0;\n  virtual float GetMissileBracketClampMin() const = 0;\n  virtual float GetMissileBracketClampMax() const = 0;\n  virtual float GetInnerBeamClampMin() const = 0;\n  virtual float GetInnerBeamClampMax() const = 0;\n  virtual float GetChargeGaugeClampMin() const = 0;\n  virtual float GetChargeGaugeClampMax() const = 0;\n  virtual float GetLockFireClampMin() const = 0;\n  virtual float GetLockFireClampMax() const = 0;\n  virtual float GetLockDaggerClampMin() const = 0;\n  virtual float GetLockDaggerClampMax() const = 0;\n  virtual float GetGrappleSelectScale() const = 0;\n  virtual float GetGrappleScale() const = 0;\n  virtual float GetGrappleClampMin() const = 0;\n  virtual float GetGrappleClampMax() const = 0;\n  virtual const zeus::CColor& GetGrapplePointSelectColor() const = 0;\n  virtual const zeus::CColor& GetGrapplePointColor() const = 0;\n  virtual const zeus::CColor& GetLockedGrapplePointSelectColor() const = 0;\n  virtual float GetGrappleMinClampScale() const = 0;\n  virtual const zeus::CColor& GetChargeGaugePulseColorHigh() const = 0;\n  virtual float GetFullChargeFadeDuration() const = 0;\n  virtual const zeus::CColor& GetOrbitPointColor() const = 0;\n  virtual const zeus::CColor& GetCrosshairsColor() const = 0;\n  virtual float GetCrosshairsScaleDuration() const = 0;\n  virtual bool DrawOrbitPoint() const = 0;\n  virtual const zeus::CColor& GetChargeGaugePulseColorLow() const = 0;\n  virtual float GetChargeGaugePulsePeriod() const = 0;\n  virtual float GetReticuleClampMin() const = 0;\n  virtual float GetReticuleClampMax() const = 0;\n  virtual const zeus::CColor& GetXRayRetRingColor() const = 0;\n  virtual float GetReticuleScale() const = 0;\n  virtual float GetScanTargetClampMin() const = 0;\n  virtual float GetScanTargetClampMax() const = 0;\n  virtual float GetAngularLagSpeed() const = 0;\n};\n} // namespace DataSpec\n"
  },
  {
    "path": "Runtime/Weapon/CAuxWeapon.cpp",
    "content": "#include \"Runtime/Weapon/CAuxWeapon.hpp\"\n\n#include <array>\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Weapon/CEnergyProjectile.hpp\"\n#include \"Runtime/Weapon/CNewFlameThrower.hpp\"\n#include \"Runtime/Weapon/CWaveBuster.hpp\"\n\nnamespace metaforce {\nconstexpr CCameraShakeData skHardShake{\n    0.3f,\n    100.f,\n    0,\n    zeus::skZero3f,\n    {},\n    {\n        true,\n        {false, 0.f, 0.f, 0.3f, -2.f},\n        {true, 0.f, 0.f, 0.05f, 0.5f},\n    },\n    {},\n};\n\nconstexpr std::array skComboNames{\n    \"SuperMissile\"sv, \"IceCombo\"sv, \"WaveBuster\"sv, \"FlameThrower\"sv, \"SuperMissile\"sv,\n};\n\nconstexpr std::array<u16, 5> skSoundId{\n    SFXsfx0712, SFXsfx072D, SFXwpn_combo_wavebuster, SFXwpn_combo_flamethrower, SFXsfx0712,\n};\n\nCAuxWeapon::CAuxWeapon(TUniqueId playerId)\n: x0_missile(g_SimplePool->GetObj(\"Missile\"))\n, xc_flameMuzzle(g_SimplePool->GetObj(\"FlameMuzzle\"))\n, x18_busterMuzzle(g_SimplePool->GetObj(\"BusterMuzzle\"))\n, x6c_playerId(playerId) {\n  x0_missile.GetObj();\n  xc_flameMuzzle.GetObj();\n  x18_busterMuzzle.GetObj();\n  InitComboData();\n}\n\nvoid CAuxWeapon::InitComboData() {\n  for (const auto& comboName : skComboNames) {\n    x28_combos.push_back(g_SimplePool->GetObj(comboName));\n  }\n}\n\nvoid CAuxWeapon::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) {\n  if (msg == EScriptObjectMessage::Deleted) {\n    DeleteFlameThrower(mgr);\n    DeleteWaveBusterBeam(mgr);\n  }\n}\n\nbool CAuxWeapon::IsComboFxActive(const CStateManager& mgr) const {\n  switch (x74_firingBeamId) {\n  case CPlayerState::EBeamId::Wave:\n    if (const CEntity* ent = mgr.GetObjectById(x70_waveBusterId))\n      return static_cast<const CWaveBuster*>(ent)->IsFiring();\n    break;\n  case CPlayerState::EBeamId::Plasma:\n    if (const CEntity* ent = mgr.GetObjectById(x6e_flameThrowerId))\n      return static_cast<const CNewFlameThrower*>(ent)->IsFiring();\n    break;\n  default:\n    break;\n  }\n  return false;\n}\n\nvoid CAuxWeapon::Load(CPlayerState::EBeamId curBeam, CStateManager& mgr) {\n  x80_24_isLoaded = false;\n  switch (x78_loadBeamId) {\n  case CPlayerState::EBeamId::Wave:\n    DeleteWaveBusterBeam(mgr);\n    break;\n  case CPlayerState::EBeamId::Plasma:\n    DeleteFlameThrower(mgr);\n    break;\n  default:\n    break;\n  }\n  x28_combos[int(x78_loadBeamId)].Unlock();\n  x28_combos[int(curBeam)].Lock();\n  x78_loadBeamId = curBeam;\n  LoadIdle();\n}\n\nvoid CAuxWeapon::StopComboFx(CStateManager& mgr, bool deactivate) {\n  switch (x74_firingBeamId) {\n  case CPlayerState::EBeamId::Wave: {\n    auto* wb = static_cast<CWaveBuster*>(mgr.ObjectById(x70_waveBusterId));\n    if (wb) {\n      wb->ResetBeam(deactivate);\n      DeleteWaveBusterBeam(mgr);\n    }\n    break;\n  }\n  case CPlayerState::EBeamId::Plasma: {\n    auto* ft = static_cast<CNewFlameThrower*>(mgr.ObjectById(x6e_flameThrowerId));\n    if (ft) {\n      mgr.GetPlayerState()->SetFiringComboBeam(false);\n      if (ft->IsFiring()) {\n        ft->Reset(mgr, deactivate);\n        FreeComboVoiceId();\n      } else if (ft->GetActive() && deactivate) {\n        ft->Reset(mgr, deactivate);\n      }\n    }\n    break;\n  }\n  default:\n    break;\n  }\n\n  if (deactivate) {\n    x74_firingBeamId = CPlayerState::EBeamId::Invalid;\n    x68_ammoConsumeTimer = 0.f;\n  }\n}\n\nbool CAuxWeapon::UpdateComboFx(float dt, const zeus::CVector3f& scale, const zeus::CVector3f& pos,\n                               const zeus::CTransform& xf, CStateManager& mgr) {\n  if (!x80_24_isLoaded || x74_firingBeamId == CPlayerState::EBeamId::Invalid)\n    return false;\n\n  bool firing = false;\n  if (x7c_comboSfx && !CSfxManager::IsPlaying(x7c_comboSfx))\n    FreeComboVoiceId();\n\n  switch (x74_firingBeamId) {\n  case CPlayerState::EBeamId::Wave:\n  case CPlayerState::EBeamId::Plasma: {\n    bool firingFx = false;\n    if (x74_firingBeamId == CPlayerState::EBeamId::Wave) {\n      auto* wb = static_cast<CWaveBuster*>(mgr.ObjectById(x70_waveBusterId));\n      if (wb && wb->IsFiring()) {\n        wb->UpdateFx(xf, dt, mgr);\n        firing = true;\n        firingFx = true;\n      } else {\n        DeleteWaveBusterBeam(mgr);\n        mgr.GetPlayerState()->SetFiringComboBeam(false);\n      }\n    } else {\n      auto* ft = static_cast<CNewFlameThrower*>(mgr.ObjectById(x6e_flameThrowerId));\n      bool needsDelete = true;\n      if (ft) {\n        firingFx = ft->CanRenderAuxEffects();\n        if (ft->GetActive()) {\n          ft->UpdateFx(xf, dt, mgr);\n          firing = ft->IsFiring();\n        }\n        if (x6e_flameThrowerId != kInvalidUniqueId)\n          needsDelete = ft->AreEffectsFinished();\n      }\n      if (needsDelete) {\n        DeleteFlameThrower(mgr);\n        mgr.GetPlayerState()->SetFiringComboBeam(false);\n      }\n    }\n\n    if (firingFx) {\n      x68_ammoConsumeTimer += dt;\n      if (mgr.GetPlayerState()->GetItemAmount(CPlayerState::EItemType::Missiles) > 0) {\n        if (x68_ammoConsumeTimer >= mgr.GetPlayerState()->GetComboFireAmmoPeriod()) {\n          mgr.GetPlayerState()->DecrPickup(CPlayerState::EItemType::Missiles, 1);\n          x68_ammoConsumeTimer = 0.f;\n        }\n      }\n    }\n\n    if (mgr.GetPlayerState()->GetItemAmount(CPlayerState::EItemType::Missiles) == 0)\n      StopComboFx(mgr, false);\n\n    x24_muzzleFxGen->SetGlobalTranslation(pos);\n    x24_muzzleFxGen->SetGlobalScale(scale);\n    x24_muzzleFxGen->SetParticleEmission(firingFx);\n    x24_muzzleFxGen->Update(dt);\n    break;\n  }\n  default:\n    break;\n  }\n\n  return firing;\n}\n\nvoid CAuxWeapon::FreeComboVoiceId() {\n  CSfxManager::SfxStop(x7c_comboSfx);\n  x7c_comboSfx.reset();\n}\n\nvoid CAuxWeapon::DeleteFlameThrower(CStateManager& mgr) {\n  FreeComboVoiceId();\n  if (x6e_flameThrowerId != kInvalidUniqueId) {\n    mgr.FreeScriptObject(x6e_flameThrowerId);\n    x6e_flameThrowerId = kInvalidUniqueId;\n    x74_firingBeamId = CPlayerState::EBeamId::Invalid;\n    mgr.GetPlayerState()->SetFiringComboBeam(false);\n  }\n}\n\nvoid CAuxWeapon::CreateFlameThrower(const zeus::CTransform& xf, CStateManager& mgr, float dt) {\n  DeleteFlameThrower(mgr);\n  if (x6e_flameThrowerId != kInvalidUniqueId)\n    return;\n\n  const std::array<CAssetId, 8> resInfo{\n      NWeaponTypes::get_asset_id_from_name(\"NFTMainFire\"),\n      NWeaponTypes::get_asset_id_from_name(\"NFTMainSmoke\"),\n      NWeaponTypes::get_asset_id_from_name(\"NFTSwooshCenter\"),\n      NWeaponTypes::get_asset_id_from_name(\"NFTSwooshFire\"),\n      NWeaponTypes::get_asset_id_from_name(\"NFTSecondarySmoke\"),\n      NWeaponTypes::get_asset_id_from_name(\"NFTSecondaryFire\"),\n      NWeaponTypes::get_asset_id_from_name(\"NFTSecondarySparks\"),\n      {},\n  };\n  x6e_flameThrowerId = mgr.AllocateUniqueId();\n  auto* ft = new CNewFlameThrower(x28_combos[3], \"Player_FlameThrower\", EWeaponType::Plasma, resInfo, xf,\n                                  EMaterialTypes::Player,\n                                  CGunWeapon::GetShotDamageInfo(g_tweakPlayerGun->GetComboShotInfo(3), mgr),\n                                  x6e_flameThrowerId, kInvalidAreaId, x6c_playerId, EProjectileAttrib::None);\n  mgr.AddObject(ft);\n  ft->Think(dt, mgr);\n  ft->StartFiring(xf, mgr);\n  x24_muzzleFxGen = std::make_unique<CElementGen>(xc_flameMuzzle);\n  x7c_comboSfx = NWeaponTypes::play_sfx(SFXwpn_combo_flamethrower, false, true, 0.165f);\n  mgr.GetCameraManager()->AddCameraShaker(skHardShake, false);\n  mgr.GetPlayerState()->SetFiringComboBeam(true);\n  x74_firingBeamId = CPlayerState::EBeamId::Plasma;\n}\n\nvoid CAuxWeapon::DeleteWaveBusterBeam(CStateManager& mgr) {\n  FreeComboVoiceId();\n  if (x70_waveBusterId != kInvalidUniqueId) {\n    mgr.FreeScriptObject(x70_waveBusterId);\n    x70_waveBusterId = kInvalidUniqueId;\n    x74_firingBeamId = CPlayerState::EBeamId::Invalid;\n    mgr.GetPlayerState()->SetFiringComboBeam(false);\n  }\n}\n\nvoid CAuxWeapon::CreateWaveBusterBeam(EProjectileAttrib attribs, TUniqueId homingTarget, const zeus::CTransform& xf,\n                                      CStateManager& mgr) {\n  DeleteFlameThrower(mgr);\n  if (x70_waveBusterId != kInvalidUniqueId)\n    return;\n\n  x70_waveBusterId = mgr.AllocateUniqueId();\n  CWaveBuster* wb = new CWaveBuster(x28_combos[2], EWeaponType::Wave, xf, EMaterialTypes::Player,\n                                    CGunWeapon::GetShotDamageInfo(g_tweakPlayerGun->GetComboShotInfo(2), mgr),\n                                    x70_waveBusterId, kInvalidAreaId, x6c_playerId, homingTarget, attribs);\n  mgr.AddObject(wb);\n  x24_muzzleFxGen = std::make_unique<CElementGen>(x18_busterMuzzle);\n  x7c_comboSfx = NWeaponTypes::play_sfx(SFXwpn_combo_wavebuster, false, true, 0.165f);\n  mgr.GetCameraManager()->AddCameraShaker(CCameraShakeData::skChargedShotCameraShakeData, false);\n  mgr.GetPlayerState()->SetFiringComboBeam(true);\n  x74_firingBeamId = CPlayerState::EBeamId::Wave;\n}\n\nvoid CAuxWeapon::LaunchMissile(float dt, bool underwater, bool charged, CPlayerState::EBeamId currentBeam,\n                               EProjectileAttrib attrib, const zeus::CTransform& xf, TUniqueId homingId,\n                               CStateManager& mgr) {\n  const SShotParam& info =\n      charged ? g_tweakPlayerGun->GetComboShotInfo(int(currentBeam)) : g_tweakPlayerGun->GetMissileInfo();\n  u16 sfxId = charged ? skSoundId[int(currentBeam)] : u16(SFXwpn_fire_missile);\n  CEnergyProjectile* proj = new CEnergyProjectile(\n      true, charged ? x28_combos[int(currentBeam)] : x0_missile, charged ? EWeaponType::Power : EWeaponType::Missile,\n      xf, EMaterialTypes::Player, CGunWeapon::GetShotDamageInfo(info, mgr), mgr.AllocateUniqueId(), kInvalidAreaId,\n      x6c_playerId, homingId, attrib | EProjectileAttrib::ArmCannon, underwater, zeus::skOne3f, {}, -1, false);\n  mgr.AddObject(proj);\n  proj->Think(dt, mgr);\n  if (charged) {\n    proj->SetCameraShake(CCameraShakeData::BuildMissileCameraShake(0.25f, 0.75f, 50.f, proj->GetTranslation()));\n    mgr.GetCameraManager()->AddCameraShaker(skHardShake, false);\n  } else {\n    mgr.GetRumbleManager().Rumble(mgr, ERumbleFxId::PlayerMissileFire, 0.5f, ERumblePriority::One);\n  }\n  x7c_comboSfx = NWeaponTypes::play_sfx(sfxId, underwater, false, 0.165f);\n}\n\nvoid CAuxWeapon::Fire(float dt, bool underwater, CPlayerState::EBeamId currentBeam, EChargeState chargeState,\n                      const zeus::CTransform& xf, CStateManager& mgr, EWeaponType type, TUniqueId homingId) {\n  if (!x80_24_isLoaded)\n    return;\n\n  EProjectileAttrib attrib = EProjectileAttrib::None;\n  if (chargeState == EChargeState::Charged)\n    attrib = CGameProjectile::GetBeamAttribType(type) | EProjectileAttrib::ComboShot;\n\n  if (chargeState == EChargeState::Normal) {\n    LaunchMissile(dt, underwater, false, currentBeam, attrib, xf, homingId, mgr);\n  } else {\n    switch (currentBeam) {\n    case CPlayerState::EBeamId::Power:\n    case CPlayerState::EBeamId::Ice:\n      LaunchMissile(dt, underwater, chargeState == EChargeState::Charged, currentBeam, attrib, xf, homingId, mgr);\n      break;\n    case CPlayerState::EBeamId::Wave:\n      CreateWaveBusterBeam(attrib, homingId, xf, mgr);\n      break;\n    case CPlayerState::EBeamId::Plasma:\n      CreateFlameThrower(xf, mgr, dt);\n      break;\n    default:\n      break;\n    }\n  }\n}\n\nvoid CAuxWeapon::LoadIdle() { x80_24_isLoaded = x28_combos[int(x78_loadBeamId)].IsLoaded(); }\n\nvoid CAuxWeapon::RenderMuzzleFx() const {\n  switch (x74_firingBeamId) {\n  case CPlayerState::EBeamId::Wave:\n  case CPlayerState::EBeamId::Plasma:\n    x24_muzzleFxGen->Render();\n    break;\n  default:\n    break;\n  }\n}\n\nTUniqueId CAuxWeapon::HasTarget(const CStateManager& mgr) const {\n  if (x74_firingBeamId == CPlayerState::EBeamId::Wave) {\n    if (const auto* wb = static_cast<const CWaveBuster*>(mgr.GetObjectById(x70_waveBusterId))) {\n      return wb->GetHomingTargetId();\n    }\n  }\n  return kInvalidUniqueId;\n}\n\nvoid CAuxWeapon::SetNewTarget(TUniqueId targetId, CStateManager& mgr) {\n  if (x74_firingBeamId == CPlayerState::EBeamId::Wave)\n    if (auto* wb = static_cast<CWaveBuster*>(mgr.ObjectById(x70_waveBusterId)))\n      wb->SetNewTarget(targetId, mgr);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CAuxWeapon.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/CPlayerState.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Weapon/CGunWeapon.hpp\"\n#include \"Runtime/Weapon/CWeapon.hpp\"\n\nnamespace metaforce {\n\nclass CAuxWeapon {\n  TCachedToken<CWeaponDescription> x0_missile;\n  TCachedToken<CGenDescription> xc_flameMuzzle;\n  TCachedToken<CGenDescription> x18_busterMuzzle;\n  std::unique_ptr<CElementGen> x24_muzzleFxGen;\n  rstl::reserved_vector<TCachedToken<CWeaponDescription>, 5> x28_combos;\n  float x68_ammoConsumeTimer = 0.f;\n  TUniqueId x6c_playerId;\n  TUniqueId x6e_flameThrowerId = kInvalidUniqueId;\n  TUniqueId x70_waveBusterId = kInvalidUniqueId;\n  CPlayerState::EBeamId x74_firingBeamId = CPlayerState::EBeamId::Invalid;\n  CPlayerState::EBeamId x78_loadBeamId = CPlayerState::EBeamId::Power;\n  CSfxHandle x7c_comboSfx;\n  bool x80_24_isLoaded : 1 = false;\n  void InitComboData();\n  void FreeComboVoiceId();\n  void DeleteFlameThrower(CStateManager& mgr);\n  void CreateFlameThrower(const zeus::CTransform& xf, CStateManager& mgr, float dt);\n  void DeleteWaveBusterBeam(CStateManager& mgr);\n  void CreateWaveBusterBeam(EProjectileAttrib attribs, TUniqueId homingTarget, const zeus::CTransform& xf,\n                            CStateManager& mgr);\n  void LaunchMissile(float dt, bool underwater, bool charged, CPlayerState::EBeamId currentBeam,\n                     EProjectileAttrib attrib, const zeus::CTransform& xf, TUniqueId homingId, CStateManager& mgr);\n\npublic:\n  explicit CAuxWeapon(TUniqueId playerId);\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr);\n  bool IsComboFxActive(const CStateManager& mgr) const;\n  void Load(CPlayerState::EBeamId curBeam, CStateManager& mgr);\n  void StopComboFx(CStateManager& mgr, bool deactivate);\n  bool UpdateComboFx(float dt, const zeus::CVector3f& scale, const zeus::CVector3f& pos, const zeus::CTransform& xf,\n                     CStateManager& mgr);\n  void Fire(float dt, bool underwater, CPlayerState::EBeamId currentBeam, EChargeState chargeState,\n            const zeus::CTransform& xf, CStateManager& mgr, EWeaponType type, TUniqueId homingId);\n  void LoadIdle();\n  bool IsLoaded() const { return x80_24_isLoaded; }\n  void RenderMuzzleFx() const;\n  TUniqueId HasTarget(const CStateManager& mgr) const;\n  void SetNewTarget(TUniqueId targetId, CStateManager& mgr);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CBeamInfo.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include <zeus/CColor.hpp>\n\nnamespace metaforce {\nclass CBeamInfo {\n  u32 x0_;\n  /*\n   * 0x1: motion blur\n   * 0x2: pulse effect\n   * 0x4: one shot\n   * 0x8: phazon damage\n   */\n  s32 x4_beamAttributes;\n  CAssetId x8_contactFxId;\n  CAssetId xc_pulseFxId;\n  CAssetId x10_textureId;\n  CAssetId x14_glowTextureId;\n  s32 x18_length;\n  float x1c_radius;\n  float x20_expansionSpeed;\n  float x24_lifeTime;\n  float x28_pulseSpeed;\n  float x2c_shutdownTime;\n  float x30_contactFxScale;\n  float x34_pulseFxScale;\n  float x38_travelSpeed;\n  zeus::CColor x3c_innerColor;\n  zeus::CColor x40_outerColor;\n\npublic:\n  explicit CBeamInfo(CInputStream& in)\n  : x0_(in.ReadLong())\n  , x4_beamAttributes(in.ReadLong())\n  , x8_contactFxId(in)\n  , xc_pulseFxId(in)\n  , x10_textureId(in)\n  , x14_glowTextureId(in)\n  , x18_length(in.ReadFloat())\n  , x1c_radius(in.ReadFloat())\n  , x20_expansionSpeed(in.ReadFloat())\n  , x24_lifeTime(in.ReadFloat())\n  , x28_pulseSpeed(in.ReadFloat())\n  , x2c_shutdownTime(in.ReadFloat())\n  , x30_contactFxScale(in.ReadFloat())\n  , x34_pulseFxScale(in.ReadFloat())\n  , x38_travelSpeed(in.ReadFloat())\n  , x3c_innerColor(in.Get<zeus::CColor>())\n  , x40_outerColor(in.Get<zeus::CColor>()) {}\n\n  CBeamInfo(s32 beamAttributes, CAssetId contactFxId, CAssetId pulseFxId, CAssetId textureId, CAssetId glowTextureId,\n            s32 length, float radius, float expansionSpeed, float lifeTime, float pulseSpeed, float shutdownTime,\n            float contactFxScale, float pulseFxScale, const zeus::CColor& innerColor, const zeus::CColor& outerColor,\n            float travelSpeed)\n  : x4_beamAttributes(beamAttributes)\n  , x8_contactFxId(contactFxId)\n  , xc_pulseFxId(pulseFxId)\n  , x10_textureId(textureId)\n  , x14_glowTextureId(glowTextureId)\n  , x18_length(length)\n  , x1c_radius(radius)\n  , x20_expansionSpeed(expansionSpeed)\n  , x24_lifeTime(lifeTime)\n  , x28_pulseSpeed(pulseSpeed)\n  , x2c_shutdownTime(shutdownTime)\n  , x30_contactFxScale(contactFxScale)\n  , x34_pulseFxScale(pulseFxScale)\n  , x38_travelSpeed(travelSpeed)\n  , x3c_innerColor(innerColor)\n  , x40_outerColor(outerColor) {}\n\n  s32 GetBeamAttributes() const { return x4_beamAttributes; }\n  CAssetId GetContactFxId() const { return x8_contactFxId; }\n  CAssetId GetPulseFxId() const { return xc_pulseFxId; }\n  CAssetId GetTextureId() const { return x10_textureId; }\n  CAssetId GetGlowTextureId() const { return x14_glowTextureId; }\n  s32 GetLength() const { return x18_length; }\n  float GetRadius() const { return x1c_radius; }\n  float GetExpansionSpeed() const { return x20_expansionSpeed; }\n  float GetLifeTime() const { return x24_lifeTime; }\n  float GetPulseSpeed() const { return x28_pulseSpeed; }\n  float GetShutdownTime() const { return x2c_shutdownTime; }\n  float GetContactFxScale() const { return x30_contactFxScale; }\n  float GetPulseFxScale() const { return x34_pulseFxScale; }\n  float GetTravelSpeed() const { return x38_travelSpeed; }\n  const zeus::CColor& GetInnerColor() const { return x3c_innerColor; }\n  const zeus::CColor& GetOuterColor() const { return x40_outerColor; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CBeamProjectile.cpp",
    "content": "#include \"Runtime/Weapon/CBeamProjectile.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCBeamProjectile::CBeamProjectile(const TToken<CWeaponDescription>& wDesc, std::string_view name, EWeaponType wType,\n                                 const zeus::CTransform& xf, s32 maxLength, float beamRadius, float travelSpeed,\n                                 EMaterialTypes matType, const CDamageInfo& dInfo, TUniqueId uid, TAreaId aid,\n                                 TUniqueId owner, EProjectileAttrib attribs, bool growingBeam)\n: CGameProjectile(false, wDesc, name, wType, xf, matType, dInfo, uid, aid, owner, kInvalidUniqueId, attribs, false,\n                  zeus::skOne3f, {}, 0xffff, false)\n, x2e8_intMaxLength(std::abs(maxLength))\n, x2ec_maxLength(x2e8_intMaxLength)\n, x2f0_invMaxLength(1.f / x2ec_maxLength)\n, x2f4_beamRadius(beamRadius)\n, x300_intBeamLength(growingBeam ? 0.f : x2ec_maxLength)\n, x304_beamLength(x2ec_maxLength)\n, x308_travelSpeed(travelSpeed)\n, x464_24_growingBeam(growingBeam) {\n  x384_.resize(10);\n  x400_pointCache.resize(8);\n}\n\nstd::optional<zeus::CAABox> CBeamProjectile::GetTouchBounds() const {\n  if (!GetActive()) {\n    return std::nullopt;\n  }\n\n  if (x464_25_enableTouchDamage) {\n    const zeus::CVector3f pos = GetTranslation();\n    return {{pos - 0.1f, pos + 0.1f}};\n  }\n\n  return std::nullopt;\n}\n\nvoid CBeamProjectile::CalculateRenderBounds() { x9c_renderBounds = x354_.getTransformedAABox(x324_xf); }\n\nvoid CBeamProjectile::ResetBeam(CStateManager&, bool) {\n  if (x464_24_growingBeam)\n    x300_intBeamLength = 0.f;\n}\n\nvoid CBeamProjectile::SetCollisionResultData(EDamageType dType, CRayCastResult& res, TUniqueId id) {\n  x2f8_damageType = dType;\n  x304_beamLength = res.GetT();\n  x318_collisionPoint = res.GetPoint();\n  x30c_collisionNormal = res.GetPlane().normal();\n  x2fe_collisionActorId = dType == EDamageType::Actor ? id : kInvalidUniqueId;\n  SetTranslation(res.GetPoint());\n}\n\nvoid CBeamProjectile::UpdateFx(const zeus::CTransform& xf, float dt, CStateManager& mgr) {\n  if (!GetActive())\n    return;\n\n  SetTransform(xf.getRotation());\n  if (x464_24_growingBeam) {\n    x300_intBeamLength += x308_travelSpeed * dt;\n    if (x300_intBeamLength > x2ec_maxLength)\n      x300_intBeamLength = x2ec_maxLength;\n  }\n  x304_beamLength = x300_intBeamLength;\n  x2f8_damageType = EDamageType::None;\n  x298_previousPos = xf.origin;\n  zeus::CVector3f beamEnd = xf.basis[1].normalized() * x300_intBeamLength + xf.origin;\n  SetTranslation(beamEnd);\n  x354_ = zeus::CAABox(zeus::CVector3f{-x2f4_beamRadius, 0.f, -x2f4_beamRadius},\n                       zeus::CVector3f{x2f4_beamRadius, x304_beamLength, x2f4_beamRadius});\n  x36c_ = zeus::CAABox(zeus::CVector3f{-x2f4_beamRadius, 0.f, -x2f4_beamRadius},\n                       zeus::CVector3f{x2f4_beamRadius, x300_intBeamLength, x2f4_beamRadius})\n              .getTransformedAABox(xf);\n  EntityList nearList;\n  mgr.BuildNearList(nearList, x36c_, CMaterialFilter::MakeExclude({EMaterialTypes::ProjectilePassthrough}), nullptr);\n  TUniqueId collideId = kInvalidUniqueId;\n  CRayCastResult res =\n      RayCollisionCheckWithWorld(collideId, x298_previousPos, beamEnd, x300_intBeamLength, nearList, mgr);\n  if (TCastToConstPtr<CActor>(mgr.ObjectById(collideId))) {\n    SetCollisionResultData(EDamageType::Actor, res, collideId);\n    if (x464_25_enableTouchDamage)\n      ApplyDamageToActors(mgr, CDamageInfo(x12c_curDamageInfo, dt));\n  } else if (res.IsValid()) {\n    SetCollisionResultData(EDamageType::World, res, kInvalidUniqueId);\n    if (x464_25_enableTouchDamage)\n      mgr.ApplyDamageToWorld(xec_ownerId, *this, res.GetPoint(), CDamageInfo(x12c_curDamageInfo, dt), xf8_filter);\n  } else {\n    x318_collisionPoint = xf * zeus::CVector3f(x2f4_beamRadius, x304_beamLength, x2f4_beamRadius);\n    SetTranslation(x318_collisionPoint);\n  }\n  x324_xf = xf;\n}\n\nvoid CBeamProjectile::Accept(metaforce::IVisitor& visitor) { visitor.Visit(this); }\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CBeamProjectile.hpp",
    "content": "#pragma once\n\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Weapon/CGameProjectile.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CBeamProjectile : public CGameProjectile {\npublic:\n  enum class EDamageType { None, Actor, World };\n\nprivate:\n  s32 x2e8_intMaxLength;\n  float x2ec_maxLength;\n  float x2f0_invMaxLength;\n  float x2f4_beamRadius;\n  EDamageType x2f8_damageType = EDamageType::None;\n  TUniqueId x2fc_ = kInvalidUniqueId;\n  TUniqueId x2fe_collisionActorId = kInvalidUniqueId;\n  float x300_intBeamLength;\n  float x304_beamLength;\n  float x308_travelSpeed;\n  zeus::CVector3f x30c_collisionNormal = zeus::skUp;\n  zeus::CVector3f x318_collisionPoint = zeus::skZero3f;\n  zeus::CTransform x324_xf;\n  zeus::CAABox x354_ = zeus::skNullBox;\n  zeus::CAABox x36c_ = zeus::skNullBox;\n  rstl::reserved_vector<zeus::CVector3f, 10> x384_;\n  rstl::reserved_vector<zeus::CVector3f, 8> x400_pointCache;\n  bool x464_24_growingBeam : 1;\n  bool x464_25_enableTouchDamage : 1 = false;\n\n  void SetCollisionResultData(EDamageType dType, CRayCastResult& res, TUniqueId id);\n\npublic:\n  DEFINE_ENTITY\n  CBeamProjectile(const TToken<CWeaponDescription>& wDesc, std::string_view name, EWeaponType wType,\n                  const zeus::CTransform& xf, s32 maxLength, float beamRadius, float travelSpeed,\n                  EMaterialTypes matType, const CDamageInfo& dInfo, TUniqueId uid, TAreaId aid, TUniqueId owner,\n                  EProjectileAttrib attribs, bool growingBeam);\n\n  void Accept(IVisitor& visitor) override;\n  float GetMaxRadius() const { return x2f4_beamRadius; }\n  const zeus::CVector3f& GetSurfaceNormal() const { return x30c_collisionNormal; }\n  EDamageType GetDamageType() const { return x2f8_damageType; }\n  const zeus::CVector3f& GetCurrentPos() const { return x318_collisionPoint; }\n  rstl::reserved_vector<zeus::CVector3f, 8>& PointCache() { return x400_pointCache; }\n  const rstl::reserved_vector<zeus::CVector3f, 8>& GetPointCache() const { return x400_pointCache; }\n  void CauseDamage(bool b) { x464_25_enableTouchDamage = b; }\n  const zeus::CTransform& GetBeamTransform() const { return x324_xf; }\n  float GetInvMaxLength() const { return x2f0_invMaxLength; }\n  float GetCurrentLength() const { return x304_beamLength; }\n  float GetMaxLength() const { return x2ec_maxLength; }\n  s32 GetIntMaxLength() const { return x2e8_intMaxLength; }\n  TUniqueId GetCollisionActorId() const { return x2fe_collisionActorId; }\n\n  std::optional<zeus::CAABox> GetTouchBounds() const override;\n  void CalculateRenderBounds() override;\n  virtual void ResetBeam(CStateManager&, bool);\n  virtual void UpdateFx(const zeus::CTransform&, float, CStateManager&);\n  virtual void Fire(const zeus::CTransform&, CStateManager&, bool) = 0;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CBomb.cpp",
    "content": "#include \"Runtime/Weapon/CBomb.hpp\"\n\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/World/CGameLight.hpp\"\n#include \"Runtime/World/CMorphBall.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n\n#include \"Audio/SFX/Weapons.h\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCBomb::CBomb(const TCachedToken<CGenDescription>& particle1, const TCachedToken<CGenDescription>& particle2,\n             TUniqueId uid, TAreaId aid, TUniqueId playerId, float f1, const zeus::CTransform& xf,\n             const CDamageInfo& dInfo)\n: CWeapon(uid, aid, true, playerId, EWeaponType::Bomb, \"Bomb\", xf,\n          CMaterialFilter::MakeIncludeExclude(\n              {EMaterialTypes::Solid, EMaterialTypes::Trigger, EMaterialTypes::NonSolidDamageable},\n              {EMaterialTypes::Projectile, EMaterialTypes::Bomb}),\n          {EMaterialTypes::Projectile, EMaterialTypes::Bomb}, dInfo, EProjectileAttrib::Bombs,\n          CModelData::CModelDataNull())\n, x170_prevLocation(xf.origin)\n, x17c_fuseTime(f1)\n, x180_particle1(std::make_unique<CElementGen>(particle1, CElementGen::EModelOrientationType::Normal,\n                                               CElementGen::EOptionalSystemFlags::One))\n, x184_particle2(std::make_unique<CElementGen>(particle2, CElementGen::EModelOrientationType::Normal,\n                                               CElementGen::EOptionalSystemFlags::One))\n, x18c_particle2Obj(particle2.GetObj()) {\n  x180_particle1->SetGlobalTranslation(xf.origin);\n  x184_particle2->SetGlobalTranslation(xf.origin);\n}\n\nvoid CBomb::Accept(metaforce::IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CBomb::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  if (msg == EScriptObjectMessage::Registered) {\n    if (x184_particle2->SystemHasLight()) {\n      x188_lightId = mgr.AllocateUniqueId();\n      CGameLight* gameLight = new CGameLight(\n          x188_lightId, GetAreaIdAlways(), false, std::string(\"Bomb_PLight\") + GetName().data(), GetTransform(),\n          GetUniqueId(), x184_particle2->GetLight(), reinterpret_cast<size_t>(x18c_particle2Obj), 1, 0.f);\n      mgr.AddObject(gameLight);\n    }\n    mgr.AddWeaponId(xec_ownerId, xf0_weaponType);\n    CSfxManager::AddEmitter(SFXwpn_bomb_drop, GetTranslation(), {}, true, false, 0x7f, -1);\n    mgr.InformListeners(GetTranslation(), EListenNoiseType::BombExplode);\n    return;\n  } else if (msg == EScriptObjectMessage::Deleted) {\n    if (x188_lightId != kInvalidUniqueId)\n      mgr.FreeScriptObject(x188_lightId);\n\n    if (x190_24_isNotDetonated)\n      mgr.RemoveWeaponId(xec_ownerId, xf0_weaponType);\n\n    return;\n  }\n\n  CActor::AcceptScriptMsg(msg, uid, mgr);\n}\n\nconstexpr CMaterialFilter kSolidFilter =\n    CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {EMaterialTypes::Character, EMaterialTypes::Player,\n                                                                  EMaterialTypes::ProjectilePassthrough});\nvoid CBomb::Think(float dt, metaforce::CStateManager& mgr) {\n  CWeapon::Think(dt, mgr);\n\n  if (x190_24_isNotDetonated) {\n    if (x17c_fuseTime <= 0.f) {\n      Explode(GetTranslation(), mgr);\n      if (TCastToPtr<CGameLight> light = mgr.ObjectById(x188_lightId))\n        light->SetActive(true);\n    }\n\n    if (x17c_fuseTime > 0.5f)\n      x180_particle1->Update(dt);\n    else\n      UpdateLight(dt, mgr);\n\n    if (!x190_26_disableFuse)\n      x17c_fuseTime -= dt;\n  } else {\n    UpdateLight(dt, mgr);\n    if (x184_particle2->IsSystemDeletable())\n      mgr.FreeScriptObject(GetUniqueId());\n  }\n\n  if (x190_24_isNotDetonated) {\n\n    if (x164_acceleration.magSquared() > 0.f)\n      x158_velocity += dt * x164_acceleration;\n\n    if (x158_velocity.magSquared() > 0.f) {\n      x170_prevLocation = GetTranslation();\n      CActor::SetTranslation((dt * x158_velocity) + GetTranslation());\n\n      zeus::CVector3f diffVec = (GetTranslation() - x170_prevLocation);\n      float diffMag = diffVec.magnitude();\n      if (diffMag == 0.f)\n        Explode(GetTranslation(), mgr);\n      else {\n        CRayCastResult res =\n            mgr.RayStaticIntersection(x170_prevLocation, (1.f / diffMag) * diffVec, diffMag, kSolidFilter);\n        if (res.IsValid())\n          Explode(GetTranslation(), mgr);\n      }\n    }\n  }\n\n  x180_particle1->SetGlobalTranslation(GetTranslation());\n  x184_particle2->SetGlobalTranslation(GetTranslation());\n}\n\nvoid CBomb::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) {\n  zeus::CVector3f origin = GetTranslation();\n  float ballRadius = mgr.GetPlayer().GetMorphBall()->GetBallRadius();\n\n  zeus::CAABox aabox(origin - (0.9f * ballRadius), origin + (0.9f * ballRadius));\n  zeus::CVector3f closestPoint = aabox.closestPointAlongVector(CGraphics::mViewMatrix.frontVector());\n\n  if (x190_24_isNotDetonated) {\n    if (x17c_fuseTime > 0.5f) {\n      g_Renderer->AddParticleGen(*x180_particle1, closestPoint, aabox);\n    } else {\n      g_Renderer->AddParticleGen(*x184_particle2, closestPoint, aabox);\n    }\n  } else {\n    g_Renderer->AddParticleGen(*x184_particle2, closestPoint, aabox);\n  }\n}\n\nvoid CBomb::Touch(CActor&, metaforce::CStateManager&) {\n#if 0\n    x190_24_isNotDetonated; /* wat? */\n#endif\n}\n\nstd::optional<zeus::CAABox> CBomb::GetTouchBounds() const {\n  float radius = (x190_24_isNotDetonated ? 0.2f : x12c_curDamageInfo.GetRadius());\n  float minX = (x170_prevLocation.x() >= GetTranslation().x() ? x170_prevLocation.x() : GetTranslation().x()) - radius;\n  float minY = (x170_prevLocation.y() >= GetTranslation().y() ? x170_prevLocation.y() : GetTranslation().y()) - radius;\n  float minZ = (x170_prevLocation.z() >= GetTranslation().z() ? x170_prevLocation.z() : GetTranslation().z()) - radius;\n  float maxX = (x170_prevLocation.x() >= GetTranslation().x() ? x170_prevLocation.x() : GetTranslation().x()) + radius;\n  float maxY = (x170_prevLocation.y() >= GetTranslation().y() ? x170_prevLocation.y() : GetTranslation().y()) + radius;\n  float maxZ = (x170_prevLocation.z() >= GetTranslation().z() ? x170_prevLocation.z() : GetTranslation().z()) + radius;\n\n  return {{minX, minY, minZ, maxX, maxY, maxZ}};\n}\n\nvoid CBomb::Explode(const zeus::CVector3f& pos, CStateManager& mgr) {\n  mgr.ApplyDamageToWorld(xec_ownerId, *this, pos, x12c_curDamageInfo, xf8_filter);\n  CSfxManager::AddEmitter(SFXwpn_bomb_explo, GetTranslation(), {}, true, false, 0x7f, -1);\n  mgr.InformListeners(pos, EListenNoiseType::BombExplode);\n  mgr.RemoveWeaponId(xec_ownerId, xf0_weaponType);\n  x190_24_isNotDetonated = false;\n}\nvoid CBomb::UpdateLight(float dt, CStateManager& mgr) {\n  x184_particle2->Update(dt);\n  if (x188_lightId == kInvalidUniqueId)\n    return;\n\n  if (TCastToPtr<CGameLight> light = mgr.ObjectById(x188_lightId))\n    if (light->GetActive())\n      light->SetLight(x184_particle2->GetLight());\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CBomb.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <optional>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/Weapon/CWeapon.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\n\nclass CElementGen;\nclass CBomb : public CWeapon {\n\n  zeus::CVector3f x158_velocity;\n  zeus::CVector3f x164_acceleration;\n  zeus::CVector3f x170_prevLocation;\n  float x17c_fuseTime;\n  std::unique_ptr<CElementGen> x180_particle1;\n  std::unique_ptr<CElementGen> x184_particle2;\n  TUniqueId x188_lightId = kInvalidUniqueId;\n  const CGenDescription* x18c_particle2Obj;\n  bool x190_24_isNotDetonated : 1 = true;\n  bool x190_25_beingDragged : 1 = false;\n  bool x190_26_disableFuse : 1 = false;\n\npublic:\n  DEFINE_ENTITY\n  CBomb(const TCachedToken<CGenDescription>& particle1, const TCachedToken<CGenDescription>& particle2, TUniqueId uid,\n        TAreaId aid, TUniqueId playerId, float f1, const zeus::CTransform& xf, const CDamageInfo& dInfo);\n\n  void Accept(IVisitor&) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void Think(float, CStateManager&) override;\n  void AddToRenderer(const zeus::CFrustum&, CStateManager&) override;\n  void Render(CStateManager&) override {}\n  void Touch(CActor&, CStateManager&) override;\n  void Explode(const zeus::CVector3f&, CStateManager&);\n  void UpdateLight(float, CStateManager&);\n  std::optional<zeus::CAABox> GetTouchBounds() const override;\n  void SetVelocityWR(const zeus::CVector3f& vel) { x158_velocity = vel; }\n  void SetConstantAccelerationWR(const zeus::CVector3f& acc) { x164_acceleration = acc; }\n  void SetFuseDisabled(bool disabled) { x190_26_disableFuse = disabled; }\n  void SetIsBeingDragged(bool b) { x190_25_beingDragged = b; }\n  bool IsBeingDragged() const { return x190_25_beingDragged; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CBurstFire.cpp",
    "content": "#include \"Runtime/Weapon/CBurstFire.hpp\"\n\n#include <algorithm>\n#include <cmath>\n\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n\n#include <zeus/Math.hpp>\n\nnamespace metaforce {\nCBurstFire::CBurstFire(const SBurst* const* burstDefs, s32 firstBurstCount) : x10_firstBurstCounter(firstBurstCount) {\n  while (*burstDefs) {\n    x1c_burstDefs.push_back(*burstDefs);\n    ++burstDefs;\n  }\n}\n\nvoid CBurstFire::Update(CStateManager& mgr, float dt) {\n  x14_24_shouldFire = false;\n  if (!x18_curBursts) {\n    return;\n  }\n\n  x8_timeToNextShot -= dt;\n  if (x8_timeToNextShot >= 0.f) {\n    return;\n  }\n\n  x4_angleIdx += 1;\n  if (x18_curBursts->x4_shotAngles[x4_angleIdx] > 0) {\n    x14_24_shouldFire = true;\n    x8_timeToNextShot = x18_curBursts->x24_timeToNextShot;\n    x8_timeToNextShot += (mgr.GetActiveRandom()->Float() - 0.5f) * x18_curBursts->x28_timeToNextShotVariance;\n  } else {\n    x18_curBursts = nullptr;\n  }\n}\n\nzeus::CVector3f CBurstFire::GetDistanceCompensatedError(float dist, float maxErrDist) const {\n  float xErr = GetMaxXError();\n  float zErr = GetMaxZError();\n  xErr = std::min(xErr, dist / maxErrDist * xErr);\n  zErr = std::min(zErr, dist / maxErrDist * zErr);\n  return GetError(xErr, zErr);\n}\n\nvoid CBurstFire::Start(CStateManager& mgr) {\n  s32 burstIdx = -1;\n  const SBurst* bursts = x1c_burstDefs[x0_burstType];\n  if (x10_firstBurstCounter-- > 0) {\n    burstIdx = xc_firstBurstIdx < 0 ? 0 : xc_firstBurstIdx;\n  } else {\n    int random = mgr.GetActiveRandom()->Range(0, 100);\n    int advanceAccum = 0;\n    do {\n      burstIdx += 1;\n      s32 advanceWeight = bursts[burstIdx].x0_randomSelectionWeight;\n      if (advanceWeight == 0) {\n        advanceAccum = 100;\n        burstIdx -= 1;\n      }\n      advanceAccum += advanceWeight;\n    } while (random > advanceAccum);\n  }\n  x18_curBursts = &bursts[burstIdx];\n  x4_angleIdx = -1;\n  x8_timeToNextShot = 0.f;\n  x14_24_shouldFire = false;\n}\n\nzeus::CVector3f CBurstFire::GetError(float xMag, float zMag) const {\n  if (!x14_24_shouldFire || !x18_curBursts) {\n    return {};\n  }\n\n  s32 r0 = x18_curBursts->x4_shotAngles[x4_angleIdx];\n  if (x14_25_avoidAccuracy && (r0 == 4 || r0 == 12)) {\n    r0 =\n        x4_angleIdx > 0 ? x18_curBursts->x4_shotAngles[x4_angleIdx - 1] : x18_curBursts->x4_shotAngles[x4_angleIdx + 1];\n  }\n\n  if (r0 <= 0) {\n    return {};\n  }\n\n  const float angle = r0 * zeus::degToRad(-22.5f);\n  zeus::CVector3f ret;\n  ret.x() = std::cos(angle) * xMag;\n  ret.z() = std::sin(angle) * zMag;\n\n  return ret;\n}\n\nfloat CBurstFire::GetMaxXError() const { return g_tweakPlayer->GetPlayerXYHalfExtent() * 3.625f + 0.2f; }\n\nfloat CBurstFire::GetMaxZError() const { return g_tweakPlayer->GetEyeOffset(); }\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CBurstFire.hpp",
    "content": "#pragma once\n\n#include <array>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CStateManager;\n\nstruct SBurst {\n  s32 x0_randomSelectionWeight;\n  std::array<s32, 8> x4_shotAngles;\n  float x24_timeToNextShot;\n  float x28_timeToNextShotVariance;\n};\n\nclass CBurstFire {\n  s32 x0_burstType = -1;\n  s32 x4_angleIdx = -1;\n  float x8_timeToNextShot = 0.f;\n  s32 xc_firstBurstIdx = 0;\n  s32 x10_firstBurstCounter;\n  bool x14_24_shouldFire : 1 = false;\n  bool x14_25_avoidAccuracy : 1 = false;\n\n  const SBurst* x18_curBursts = nullptr;\n  rstl::reserved_vector<const SBurst*, 16> x1c_burstDefs;\n\npublic:\n  CBurstFire(const SBurst* const* burstDefs, s32 firstBurstCount);\n\n  void SetAvoidAccuracy(bool b) { x14_25_avoidAccuracy = b; }\n  void SetBurstType(s32 type) { x0_burstType = type; }\n  void SetTimeToNextShot(float t) { x8_timeToNextShot = t; }\n  float GetTimeToNextShot() const { return x8_timeToNextShot; }\n  s32 GetBurstType() const { return x0_burstType; }\n  void Start(CStateManager& mgr);\n  void Update(CStateManager& mgr, float dt);\n  zeus::CVector3f GetError(float xMag, float zMag) const;\n  zeus::CVector3f GetDistanceCompensatedError(float dist, float maxErrDist) const;\n  float GetMaxXError() const;\n  float GetMaxZError() const;\n  void SetFirstBurstIndex(s32 idx) { xc_firstBurstIdx = idx; }\n  bool ShouldFire() const { return x14_24_shouldFire; }\n  bool IsBurstSet() const { return x18_curBursts != nullptr; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CElectricBeamProjectile.cpp",
    "content": "#include \"Runtime/Weapon/CElectricBeamProjectile.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Particle/CElectricDescription.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/Particle/CParticleElectric.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nCElectricBeamProjectile::CElectricBeamProjectile(const TToken<CWeaponDescription>& wDesc, EWeaponType wType,\n                                                 const SElectricBeamInfo& elec, const zeus::CTransform& xf,\n                                                 EMaterialTypes matTypes, const CDamageInfo& dInfo, TUniqueId uid,\n                                                 TAreaId areaId, TUniqueId owner, EProjectileAttrib attribs)\n: CBeamProjectile(wDesc, \"ElectricBeamProjectile\"sv, wType, xf, u32(elec.x8_maxLength), elec.xc_radius,\n                  elec.x10_travelSpeed, matTypes, dInfo, uid, areaId, owner, attribs, false)\n, x468_electric(std::make_unique<CParticleElectric>(elec.x0_electricDescription))\n, x46c_genDescription(g_SimplePool->GetObj({SBIG('PART'), elec.x14_particleId}))\n, x478_elementGen(std::make_unique<CElementGen>(x46c_genDescription))\n, x47c_fadeSpeed(elec.x18_fadeSpeed)\n, x488_damageInterval(elec.x1c_damageInterval) {\n  x478_elementGen->SetParticleEmission(false);\n  x468_electric->SetParticleEmission(false);\n}\n\nvoid CElectricBeamProjectile::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CElectricBeamProjectile::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  if (msg == EScriptObjectMessage::Registered) {\n    mgr.AddWeaponId(xec_ownerId, xf0_weaponType);\n    CauseDamage(true);\n  } else if (msg == EScriptObjectMessage::Deleted) {\n    DeleteProjectileLight(mgr);\n  }\n  CGameProjectile::AcceptScriptMsg(msg, uid, mgr);\n}\n\nvoid CElectricBeamProjectile::PreRender(CStateManager&, const zeus::CFrustum&) {\n  if (!GetActive())\n    return;\n\n  g_Renderer->AddParticleGen(*x478_elementGen);\n  g_Renderer->AddParticleGen(*x468_electric);\n}\n\nvoid CElectricBeamProjectile::UpdateFx(const zeus::CTransform& xf, float dt, CStateManager& mgr) {\n  if (!GetActive())\n    return;\n\n  if (x484_damageTimer <= 0.f)\n    CauseDamage(true);\n\n  if (GetDamageType() == EDamageType::Actor) {\n    x484_damageTimer = x488_damageInterval;\n    CauseDamage(false);\n  }\n\n  x484_damageTimer -= dt;\n  if (zeus::close_enough(x47c_fadeSpeed, 0.f)) {\n    x480_intensity = 1.f;\n  } else {\n    float fVar1 = x48c_ ? 1.f : -1.f;\n    x480_intensity = std::min(1.f, dt * (fVar1 / x47c_fadeSpeed) + x480_intensity);\n    if (x480_intensity < 0.f) {\n      ResetBeam(mgr, true);\n    }\n  }\n\n  CBeamProjectile::UpdateFx(xf, dt, mgr);\n\n  x478_elementGen->SetModulationColor(zeus::CColor::lerp(zeus::skBlack, zeus::skWhite, x480_intensity));\n  bool hasDamage = GetDamageType() != EDamageType::None;\n  if (hasDamage) {\n    x478_elementGen->SetGlobalOrientation(zeus::lookAt(zeus::skZero3f, GetSurfaceNormal(), zeus::skUp));\n    x478_elementGen->SetGlobalTranslation(GetCurrentPos() + (0.001f * GetSurfaceNormal()));\n  }\n  x478_elementGen->SetParticleEmission(hasDamage);\n  x478_elementGen->Update(dt);\n\n  x468_electric->SetModulationColor(zeus::CColor::lerp(zeus::skBlack, zeus::skWhite, x480_intensity));\n  x468_electric->SetParticleEmission(true);\n  zeus::CVector3f dist = GetCurrentPos() - GetBeamTransform().origin;\n  if (dist.canBeNormalized()) {\n    dist.normalize();\n  }\n  x468_electric->SetOverrideIPos(GetBeamTransform().origin);\n  x468_electric->SetOverrideIVel(dist);\n  x468_electric->SetOverrideFPos(GetCurrentPos());\n  x468_electric->SetOverrideFVel(-dist);\n  x468_electric->Update(dt);\n}\n\nvoid CElectricBeamProjectile::ResetBeam(CStateManager& mgr, bool b) {\n  if (b) {\n    SetActive(false);\n    x478_elementGen->SetParticleEmission(false);\n    x468_electric->SetParticleEmission(false);\n    CBeamProjectile::ResetBeam(mgr, true);\n  } else {\n    x48c_ = false;\n  }\n}\n\nvoid CElectricBeamProjectile::Fire(const zeus::CTransform&, CStateManager&, bool) {\n  x48c_ = true;\n  SetActive(true);\n  x480_intensity = 0.f;\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CElectricBeamProjectile.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/Weapon/CBeamProjectile.hpp\"\n\nnamespace metaforce {\nstruct SElectricBeamInfo {\n  TToken<CElectricDescription> x0_electricDescription;\n  float x8_maxLength;\n  float xc_radius;\n  float x10_travelSpeed;\n  CAssetId x14_particleId;\n  float x18_fadeSpeed;\n  float x1c_damageInterval;\n};\n\nclass CElectricBeamProjectile : public CBeamProjectile {\n  std::unique_ptr<CParticleElectric> x468_electric;\n  TCachedToken<CGenDescription> x46c_genDescription;\n  std::unique_ptr<CElementGen> x478_elementGen;\n  float x47c_fadeSpeed;\n  float x480_intensity;\n  float x484_damageTimer = 0.f;\n  float x488_damageInterval;\n  bool x48c_ = false;\n\npublic:\n  DEFINE_ENTITY\n  CElectricBeamProjectile(const TToken<CWeaponDescription>&, EWeaponType, const SElectricBeamInfo&,\n                          const zeus::CTransform&, EMaterialTypes, const CDamageInfo&, TUniqueId, TAreaId, TUniqueId,\n                          EProjectileAttrib);\n\n  void Accept(IVisitor&) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void PreRender(CStateManager&, const zeus::CFrustum&) override;\n  void Touch(CActor&, CStateManager&) override {}\n  void UpdateFx(const zeus::CTransform&, float, CStateManager&) override;\n  void ResetBeam(CStateManager&, bool) override;\n  void Fire(const zeus::CTransform&, CStateManager&, bool) override;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CEnergyProjectile.cpp",
    "content": "#include \"Runtime/Weapon/CEnergyProjectile.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Camera/CGameCamera.hpp\"\n#include \"Runtime/Particle/CDecalManager.hpp\"\n#include \"Runtime/World/CExplosion.hpp\"\n#include \"Runtime/World/CGameLight.hpp\"\n#include \"Runtime/World/CIceImpact.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CScriptPlatform.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCEnergyProjectile::CEnergyProjectile(bool active, const TToken<CWeaponDescription>& desc, EWeaponType type,\n                                     const zeus::CTransform& xf, EMaterialTypes excludeMat, const CDamageInfo& damage,\n                                     TUniqueId uid, TAreaId aid, TUniqueId owner, TUniqueId homingTarget,\n                                     EProjectileAttrib attribs, bool underwater, const zeus::CVector3f& scale,\n                                     const std::optional<TLockedToken<CGenDescription>>& visorParticle, u16 visorSfx,\n                                     bool sendCollideMsg)\n: CGameProjectile(active, desc, \"GameProjectile\"sv, type, xf, excludeMat, damage, uid, aid, owner, homingTarget,\n                  attribs, underwater, scale, visorParticle, visorSfx, sendCollideMsg)\n, x2ec_dir(xf.frontVector())\n, x2f8_mag(x2ec_dir.magnitude())\n, x2fc_camShake(CCameraShakeData::BuildProjectileCameraShake(0.5f, 0.75f)) {\n  xe6_27_thermalVisorFlags = 2;\n}\n\nvoid CEnergyProjectile::PlayImpactSound(const zeus::CVector3f& pos, EWeaponCollisionResponseTypes type) {\n  const s32 sfxId = x170_projectile.GetSoundIdForCollision(type);\n  if (sfxId < 0) {\n    return;\n  }\n\n  CAudioSys::C3DEmitterParmData parmData = {};\n  parmData.x18_maxDist = x170_projectile.GetAudibleRange();\n  parmData.x1c_distComp = x170_projectile.GetAudibleFallOff();\n  parmData.x20_flags = 0x1; // Continuous parameter update\n  parmData.x24_sfxId = CSfxManager::TranslateSFXID(u16(sfxId));\n  parmData.x26_maxVol = 1.f;\n  parmData.x27_minVol = 0.16f;\n  parmData.x29_prio = 0x7f;\n\n  const CSfxHandle hnd = CSfxManager::AddEmitter(parmData, true, 0x7f, false, kInvalidAreaId);\n  if (!x2e4_26_waterUpdate) {\n    return;\n  }\n\n  CSfxManager::PitchBend(hnd, -1.f);\n}\n\nvoid CEnergyProjectile::ChangeProjectileOwner(TUniqueId owner, CStateManager& mgr) {\n  const TCastToConstPtr<CActor> act = mgr.GetObjectById(owner);\n  if (!act) {\n    return;\n  }\n\n  const float rDam = g_tweakPlayerGun->GetRichochetDamage(u32(x110_origDamageInfo.GetWeaponMode().GetType()));\n  x110_origDamageInfo.MultiplyDamageAndRadius(rDam);\n  mgr.RemoveWeaponId(xec_ownerId, xf0_weaponType);\n  xec_ownerId = owner;\n  mgr.AddWeaponId(xec_ownerId, xf0_weaponType);\n\n  /* Can now damage Player */\n  xf8_filter.ExcludeList().Add(EMaterialTypes::Character);\n  xf8_filter.ExcludeList().Remove(EMaterialTypes::Player);\n  xf8_filter = CMaterialFilter::MakeIncludeExclude(xf8_filter.GetIncludeList(), xf8_filter.GetExcludeList());\n}\n\nvoid CEnergyProjectile::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) {\n  switch (msg) {\n  case EScriptObjectMessage::Deleted:\n    if (x2e4_24_active) {\n      mgr.RemoveWeaponId(xec_ownerId, xf0_weaponType);\n    }\n    if (x2e8_sfx) {\n      CSfxManager::RemoveEmitter(x2e8_sfx);\n      x2e8_sfx.reset();\n    }\n    break;\n  case EScriptObjectMessage::Registered: {\n    if (CElementGen* ps1 = x170_projectile.GetAttachedPS1()) {\n      if (ps1->SystemHasLight()) {\n        CreateProjectileLight(\"ProjectileLight_GameProjectile\", ps1->GetLight(), mgr);\n      }\n    }\n    const TLockedToken<CWeaponDescription> desc = x170_projectile.GetWeaponDescription();\n    const s32 sfx = desc->xa8_PJFX;\n    if (sfx != -1) {\n      float range = 50.f;\n      float falloff = 0.2f;\n      if (CRealElement* rnge = desc->xac_RNGE.get()) {\n        rnge->GetValue(0, range);\n      }\n      if (CRealElement* foff = desc->xb0_FOFF.get()) {\n        foff->GetValue(0, falloff);\n      }\n\n      CAudioSys::C3DEmitterParmData parmData = {};\n      parmData.x0_pos = x170_projectile.GetTranslation();\n      parmData.xc_dir = x170_projectile.GetVelocity();\n      parmData.x18_maxDist = range;\n      parmData.x1c_distComp = falloff;\n      parmData.x20_flags = 0x9; // Continuous parameter update, doppler\n      parmData.x24_sfxId = CSfxManager::TranslateSFXID(sfx);\n      parmData.x26_maxVol = 1.f;\n      parmData.x27_minVol = 0.16f;\n      parmData.x29_prio = 0x7f;\n      x2e8_sfx = CSfxManager::AddEmitter(parmData, true, 0x7f, false, kInvalidAreaId);\n    }\n    mgr.AddWeaponId(xec_ownerId, xf0_weaponType);\n    break;\n  }\n  default:\n    break;\n  }\n  CGameProjectile::AcceptScriptMsg(msg, sender, mgr);\n}\n\nvoid CEnergyProjectile::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\n/* Material surface types only (not meta flags) */\nstatic constexpr u64 kCheckMaterial = 0xE3FFFE;\n\nvoid CEnergyProjectile::ResolveCollisionWithWorld(const CRayCastResult& res, CStateManager& mgr) {\n  const auto crType = CCollisionResponseData::GetWorldCollisionResponseType(\n      CMaterialList::BitPosition((res.GetMaterial().GetValue() & 0xffffffff) & kCheckMaterial));\n\n  if ((xe8_projectileAttribs & (EProjectileAttrib::Wave | EProjectileAttrib::ComboShot)) ==\n      (EProjectileAttrib::Wave | EProjectileAttrib::ComboShot)) {\n    return;\n  }\n\n  // Not wavebuster\n  if (Explode(res.GetPoint(), res.GetPlane().normal(), crType, mgr, CDamageVulnerability::NormalVulnerabilty(),\n              kInvalidUniqueId)) {\n    mgr.ApplyDamageToWorld(xec_ownerId, *this, res.GetPoint(), x12c_curDamageInfo, xf8_filter);\n  }\n\n  x2c2_lastResolvedObj = kInvalidUniqueId;\n}\n\nvoid CEnergyProjectile::ResolveCollisionWithActor(const CRayCastResult& res, CActor& act, CStateManager& mgr) {\n  x2c2_lastResolvedObj = act.GetUniqueId();\n  const auto crType = act.GetCollisionResponseType(res.GetPoint(), x34_transform.basis[1].normalized(),\n                                                   x12c_curDamageInfo.GetWeaponMode(), xe8_projectileAttribs);\n  act.Touch(*this, mgr);\n  const CDamageVulnerability* dVuln = act.GetDamageVulnerability();\n  if (!Explode(res.GetPoint(), res.GetPlane().normal(), crType, mgr, *dVuln, act.GetUniqueId())) {\n    mgr.SendScriptMsg(&act, GetUniqueId(), EScriptObjectMessage::Touched);\n    act.SendScriptMsgs(EScriptObjectState::ReflectedDamage, mgr, EScriptObjectMessage::None);\n  } else {\n    CGameProjectile::ResolveCollisionWithActor(res, act, mgr);\n    ApplyDamageToActors(mgr, x12c_curDamageInfo);\n  }\n  if (const TCastToPtr<CEnergyProjectile> proj = act) {\n    proj->SetHitProjectileOwner(xec_ownerId);\n    proj->Explode(GetTranslation(), x34_transform.basis[1], EWeaponCollisionResponseTypes::OtherProjectile, mgr,\n                  *GetDamageVulnerability(), GetUniqueId());\n  }\n}\n\nvoid CEnergyProjectile::Think(float dt, CStateManager& mgr) {\n  CWeapon::Think(dt, mgr);\n  if (mgr.GetWorld()->GetCurrentAreaId() != GetAreaIdAlways() &&\n      (xe8_projectileAttribs & EProjectileAttrib::ArmCannon) == EProjectileAttrib::ArmCannon) {\n    mgr.SetActorAreaId(*this, mgr.GetWorld()->GetCurrentAreaId());\n  }\n\n  UpdateProjectileMovement(dt, mgr);\n  TUniqueId id = kInvalidUniqueId;\n  const CRayCastResult res = DoCollisionCheck(id, mgr);\n  if (res.IsValid()) {\n    if (const TCastToPtr<CActor> act = mgr.ObjectById(id)) {\n      ResolveCollisionWithActor(res, *act, mgr);\n    } else {\n      ResolveCollisionWithWorld(res, mgr);\n    }\n  }\n\n  x170_projectile.UpdateParticleFX();\n  if (x2e4_24_active && x3d0_26_) {\n    Explode(GetTranslation(), zeus::skUp, EWeaponCollisionResponseTypes::Default, mgr,\n            CDamageVulnerability::NormalVulnerabilty(), kInvalidUniqueId);\n  }\n\n  if (x2c8_projectileLight != kInvalidUniqueId) {\n    if (const TCastToPtr<CGameLight> light = mgr.ObjectById(x2c8_projectileLight)) {\n      light->SetTransform(GetTransform());\n      light->SetTranslation(GetTranslation());\n      if (CElementGen* ps1 = x170_projectile.GetAttachedPS1()) {\n        if (ps1->SystemHasLight()) {\n          light->SetLight(ps1->GetLight());\n        }\n      }\n    }\n  }\n\n  if (x2e8_sfx) {\n    CSfxManager::UpdateEmitter(x2e8_sfx, x170_projectile.GetTranslation(), x170_projectile.GetVelocity(), 1.f);\n    CSfxManager::PitchBend(x2e8_sfx, x2e4_26_waterUpdate ? -1.f : 0.f);\n  }\n\n  x3d4_curTime += dt;\n  if (x3d4_curTime > 45.f || x170_projectile.IsSystemDeletable() || x3d0_24_dead) {\n    mgr.FreeScriptObject(GetUniqueId());\n  }\n}\n\nvoid CEnergyProjectile::Render(CStateManager& mgr) {\n  SCOPED_GRAPHICS_DEBUG_GROUP(fmt::format(\"CEnergyProjectile::Render WPSC_{}\", x2cc_wpscId).c_str(),\n                              zeus::skOrange);\n\n  const auto visor = mgr.GetPlayerState()->GetActiveVisor(mgr);\n  if (visor == CPlayerState::EPlayerVisor::Combat) {\n    if ((xe8_projectileAttribs & EProjectileAttrib::Charged) == EProjectileAttrib::Charged ||\n        (xe8_projectileAttribs & EProjectileAttrib::ComboShot) == EProjectileAttrib::ComboShot) {\n      const float warpTime = 1.f - float(x170_projectile.GameTime());\n      if (warpTime > 0.f) {\n        mgr.DrawSpaceWarp(GetTranslation(), warpTime * 0.75f);\n      }\n    }\n  }\n\n  if (visor == CPlayerState::EPlayerVisor::XRay) {\n    CElementGen::SetSubtractBlend((xe8_projectileAttribs & EProjectileAttrib::Ice) != EProjectileAttrib::Ice);\n    CGraphics::SetFog(ERglFogMode::PerspLin, 0.f, 75.f, zeus::skBlack);\n    x170_projectile.RenderParticles();\n    CGameProjectile::Render(mgr);\n    mgr.SetupFogForArea(GetAreaIdAlways());\n    CElementGen::SetSubtractBlend(false);\n  } else if ((xe8_projectileAttribs & EProjectileAttrib::Ice) == EProjectileAttrib::Ice &&\n             mgr.GetThermalDrawFlag() == EThermalDrawFlag::Hot) {\n    CElementGen::SetSubtractBlend(true);\n    x170_projectile.RenderParticles();\n    CGameProjectile::Render(mgr);\n    mgr.SetupFogForArea(GetAreaIdAlways());\n    CElementGen::SetSubtractBlend(false);\n  } else {\n    CGameProjectile::Render(mgr);\n  }\n}\n\nvoid CEnergyProjectile::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) {\n  const auto bounds = x170_projectile.GetBounds();\n  if (bounds && !frustum.aabbFrustumTest(*bounds)) {\n    return;\n  }\n\n  const auto visor = mgr.GetPlayerState()->GetActiveVisor(mgr);\n  if (visor != CPlayerState::EPlayerVisor::XRay &&\n      ((xe8_projectileAttribs & EProjectileAttrib::Ice) != EProjectileAttrib::Ice ||\n       mgr.GetThermalDrawFlag() != EThermalDrawFlag::Hot)) {\n    x170_projectile.AddToRenderer();\n  }\n  EnsureRendered(mgr);\n}\n\nvoid CEnergyProjectile::Touch(CActor& act, CStateManager& mgr) {\n  // Empty\n}\n\nbool CEnergyProjectile::Explode(const zeus::CVector3f& pos, const zeus::CVector3f& normal,\n                                EWeaponCollisionResponseTypes type, CStateManager& mgr,\n                                const CDamageVulnerability& dVuln, TUniqueId hitActor) {\n  zeus::CVector3f offsetPos = pos + normal * 0.01f;\n  bool done = true;\n  bool retargetPlayer = false;\n  bool deflect = false;\n  zeus::CVector3f targetPos;\n  const EVulnerability vulnType = dVuln.GetVulnerability(x12c_curDamageInfo.GetWeaponMode(), false);\n\n  if (vulnType == EVulnerability::Deflect) {\n    deflect = true;\n    EDeflectType deflectType = dVuln.GetDeflectionType(x12c_curDamageInfo.GetWeaponMode());\n    switch (deflectType) {\n    case EDeflectType::None:\n      deflect = false;\n      break;\n    case EDeflectType::Two:\n    case EDeflectType::Three:\n      if (deflectType != EDeflectType::Two ||\n          (xf0_weaponType != EWeaponType::Missile &&\n           (xe8_projectileAttribs & EProjectileAttrib::ComboShot) != EProjectileAttrib::ComboShot)) {\n        if (xf8_filter.GetExcludeList().HasMaterial(EMaterialTypes::Player)) {\n          retargetPlayer = true;\n        }\n      }\n      break;\n    default:\n      break;\n    }\n    if (retargetPlayer) {\n      const float ang = mgr.GetActiveRandom()->Range(0.f, 2.f * M_PIF);\n      const float y = std::sin(ang);\n      const float x = std::cos(ang);\n      targetPos = mgr.GetPlayer().GetAimPosition(mgr, 0.f) + zeus::CVector3f(x, 0.f, y);\n      ChangeProjectileOwner(hitActor, mgr);\n    }\n  }\n\n  if (vulnType != EVulnerability::Immune && !deflect) {\n    deflect =\n        (type == EWeaponCollisionResponseTypes::Unknown15 || type == EWeaponCollisionResponseTypes::EnemyShielded ||\n         (type >= EWeaponCollisionResponseTypes::Unknown69 &&\n          type <= EWeaponCollisionResponseTypes::AtomicAlphaReflect));\n  }\n\n  SetTranslation(offsetPos);\n\n  if (deflect) {\n    done = false;\n    x2c0_homingTargetId = kInvalidUniqueId;\n    x3d0_25_ = false;\n  } else {\n    StopProjectile(mgr);\n    if (x3d0_27_camShakeDirty) {\n      x2fc_camShake.SetSfxPositionAndDistance(pos, 50.f);\n      mgr.GetCameraManager()->AddCameraShaker(x2fc_camShake, false);\n    }\n  }\n\n  PlayImpactSound(pos, type);\n  mgr.InformListeners(pos, EListenNoiseType::ProjectileExplode);\n  if (auto particle = x170_projectile.CollisionOccured(type, !done, retargetPlayer, offsetPos, normal, targetPos)) {\n    zeus::CTransform particleXf = zeus::lookAt(zeus::skZero3f, normal);\n    particleXf.origin = offsetPos;\n    if (xf0_weaponType != EWeaponType::Power || !xf8_filter.GetExcludeList().HasMaterial(EMaterialTypes::Player) ||\n        !x2e4_27_inWater) {\n      if (auto decal = x170_projectile.GetDecalForCollision(type)) {\n        CDecalManager::AddDecal(*decal, particleXf,\n                                (xe8_projectileAttribs & EProjectileAttrib::Ice) != EProjectileAttrib::Ice, mgr);\n      }\n      zeus::CVector3f scale = zeus::skOne3f;\n      bool camClose = false;\n      if (mgr.GetPlayer().GetCameraState() == CPlayer::EPlayerCameraState::FirstPerson) {\n        const float mag = (offsetPos - mgr.GetCameraManager()->GetCurrentCamera(mgr)->GetTranslation()).magnitude();\n        if (mag < 4.f) {\n          scale = zeus::CVector3f(0.75f * (mag * 0.25f) + 0.25f);\n          camClose = true;\n        }\n      }\n      u32 explodeFlags = 0x8;\n      if ((xe8_projectileAttribs & EProjectileAttrib::Ice) == EProjectileAttrib::Ice) {\n        explodeFlags |= 0x4;\n      }\n      if (camClose) {\n        explodeFlags |= 0x2;\n      }\n      const CEntityInfo explosionInfo(GetAreaIdAlways(), CEntity::NullConnectionList);\n      auto* explosion = new CExplosion(*particle, mgr.AllocateUniqueId(), true, explosionInfo,\n                                       \"Projectile collision response\", particleXf, explodeFlags, scale, zeus::skWhite);\n      mgr.AddObject(explosion);\n      if (const TCastToPtr<CActor> hActor = mgr.ObjectById(hitActor)) {\n        bool validPlat = false;\n        CScriptPlatform* plat = TCastToPtr<CScriptPlatform>(hActor.GetPtr()).GetPtr();\n        validPlat = plat != nullptr;\n        if (!validPlat && hActor->GetMaterialList().HasMaterial(EMaterialTypes::Bomb)) {\n          for (CEntity* ent : mgr.GetPlatformAndDoorObjectList()) {\n            if (const TCastToPtr<CScriptPlatform> otherPlat = ent) {\n              if (otherPlat->IsSlave(hitActor)) {\n                plat = otherPlat.GetPtr();\n                validPlat = true;\n                break;\n              }\n            }\n          }\n        }\n        if (validPlat) {\n          plat->AddSlave(explosion->GetUniqueId(), mgr);\n        }\n      }\n    } else {\n      x3d0_24_dead = true;\n    }\n\n    if ((xe8_projectileAttribs & (EProjectileAttrib::ComboShot | EProjectileAttrib::Ice)) ==\n        (EProjectileAttrib::ComboShot | EProjectileAttrib::Ice)) {\n      // Ice Spreader\n      const TLockedToken<CGenDescription> iceSpreadParticle = g_SimplePool->GetObj(\"IceSpread1\");\n\n      u32 flags = (xe6_27_thermalVisorFlags & 0x2) == 0 ? 1 : 0;\n      flags |= 0x2;\n\n      auto* iceImpact = new CIceImpact(iceSpreadParticle, mgr.AllocateUniqueId(), GetAreaIdAlways(), true,\n                                       \"Ice spread explosion\", particleXf, flags, zeus::skOne3f, zeus::skWhite);\n      mgr.AddObject(iceImpact);\n    }\n  }\n\n  return done;\n}\n\nvoid CEnergyProjectile::StopProjectile(CStateManager& mgr) {\n  DeleteProjectileLight(mgr);\n  mgr.RemoveWeaponId(xec_ownerId, xf0_weaponType);\n  x2e4_24_active = false;\n  x68_material = CMaterialList();\n  mgr.UpdateActorInSortedLists(*this);\n  if (x2e8_sfx) {\n    CSfxManager::RemoveEmitter(x2e8_sfx);\n    x2e8_sfx.reset();\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CEnergyProjectile.hpp",
    "content": "#pragma once\n\n#include \"CGameProjectile.hpp\"\n#include \"Camera/CCameraShakeData.hpp\"\n\nnamespace metaforce {\n\nclass CEnergyProjectile : public CGameProjectile {\n  CSfxHandle x2e8_sfx;\n  zeus::CVector3f x2ec_dir;\n  float x2f8_mag;\n  CCameraShakeData x2fc_camShake;\n  bool x3d0_24_dead : 1 = false;\n  bool x3d0_25_ : 1 = false;\n  bool x3d0_26_ : 1 = false;\n  bool x3d0_27_camShakeDirty : 1 = false;\n  float x3d4_curTime = 0.f;\n  void StopProjectile(CStateManager& mgr);\n\npublic:\n  DEFINE_ENTITY\n  CEnergyProjectile(bool active, const TToken<CWeaponDescription>& desc, EWeaponType type, const zeus::CTransform& xf,\n                    EMaterialTypes excludeMat, const CDamageInfo& damage, TUniqueId uid, TAreaId aid, TUniqueId owner,\n                    TUniqueId homingTarget, EProjectileAttrib attribs, bool underwater, const zeus::CVector3f& scale,\n                    const std::optional<TLockedToken<CGenDescription>>& visorParticle, u16 visorSfx,\n                    bool sendCollideMsg);\n  void SetCameraShake(const CCameraShakeData& data) {\n    x2fc_camShake = data;\n    x3d0_27_camShakeDirty = true;\n  }\n  void PlayImpactSound(const zeus::CVector3f& pos, EWeaponCollisionResponseTypes type);\n  void ChangeProjectileOwner(TUniqueId owner, CStateManager& mgr);\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) override;\n  void Accept(IVisitor& visitor) override;\n  void ResolveCollisionWithWorld(const CRayCastResult& res, CStateManager& mgr);\n  void ResolveCollisionWithActor(const CRayCastResult& res, CActor& act, CStateManager& mgr) override;\n  void Think(float dt, CStateManager& mgr) override;\n  void Render(CStateManager& mgr) override;\n  void AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) override;\n  void Touch(CActor& act, CStateManager& mgr) override;\n  virtual bool Explode(const zeus::CVector3f& pos, const zeus::CVector3f& normal, EWeaponCollisionResponseTypes type,\n                       CStateManager& mgr, const CDamageVulnerability& dVuln, TUniqueId hitActor);\n  void Set3d0_26(bool v) { x3d0_26_ = v; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CFidget.cpp",
    "content": "#include \"Runtime/Weapon/CFidget.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n\nnamespace metaforce {\n\nstatic float kMinorFidgetDelay = 20.f;\nstatic float kMajorFidgetDelay = 20.f;\n\nCFidget::EState CFidget::Update(int fireButtonStates, bool bobbing, bool inStrikeCooldown, float dt,\n                                CStateManager& mgr) {\n  switch (x0_state) {\n  case EState::NoFidget:\n    break;\n  case EState::MinorFidget:\n    return x34_24_loading ? EState::Loading : EState::StillMinorFidget;\n  case EState::MajorFidget:\n    return x34_24_loading ? EState::Loading : EState::StillMajorFidget;\n  case EState::HolsterBeam:\n    return x34_24_loading ? EState::Loading : EState::StillHolsterBeam;\n  default:\n    x0_state = EState::NoFidget;\n    break;\n  }\n\n  if (fireButtonStates != 0) {\n    x14_timeSinceFire = 0.f;\n    x2c_holsterTimeSinceFire = 0.f;\n  } else {\n    if (x14_timeSinceFire < 6.f)\n      x14_timeSinceFire += dt;\n    if (x2c_holsterTimeSinceFire < x30_timeUntilHolster + 1.f)\n      x2c_holsterTimeSinceFire += dt;\n  }\n\n  if (inStrikeCooldown)\n    x18_timeSinceStrikeCooldown = 0.f;\n  else if (x18_timeSinceStrikeCooldown < 11.f)\n    x18_timeSinceStrikeCooldown += dt;\n\n  if (mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Unmorphed) {\n    if (x1c_timeSinceUnmorph < 21.f)\n      x1c_timeSinceUnmorph += dt;\n  } else {\n    x1c_timeSinceUnmorph = 0.f;\n  }\n\n  if (bobbing)\n    x20_timeSinceBobbing = 0.f;\n  else if (x20_timeSinceBobbing < 21.f)\n    x20_timeSinceBobbing += dt;\n\n  u32 pendingTriggerBits = 0;\n  if (x0_state == EState::NoFidget) {\n    if ((x10_delayTimerEnableBits & 0x1) != 0) {\n      x24_minorDelayTimer += dt;\n      if (x24_minorDelayTimer > kMinorFidgetDelay) {\n        pendingTriggerBits |= 0x1;\n        x24_minorDelayTimer = 0.f;\n      }\n    }\n\n    if ((x10_delayTimerEnableBits & 0x2) != 0) {\n      x28_majorDelayTimer += dt;\n      if (x28_majorDelayTimer > kMajorFidgetDelay) {\n        pendingTriggerBits |= 0x2;\n        x28_majorDelayTimer = 0.f;\n      }\n    }\n  }\n\n  if (x2c_holsterTimeSinceFire > x30_timeUntilHolster) {\n    x0_state = EState::HolsterBeam;\n  } else {\n    if (x18_timeSinceStrikeCooldown > 10.f && x1c_timeSinceUnmorph > 20.f && x20_timeSinceBobbing > 20.f) {\n      if ((pendingTriggerBits & 0x1) != 0)\n        x8_delayTriggerBits |= 0x1;\n      else if ((pendingTriggerBits & 0x2) != 0)\n        x8_delayTriggerBits |= 0x2;\n    }\n\n    if ((x8_delayTriggerBits & 0x3) == 0x3)\n      x0_state = (mgr.GetActiveRandom()->Next() % 100) >= 50 ? EState::MajorFidget : EState::MinorFidget;\n    else if ((x8_delayTriggerBits & 0x1) == 0x1)\n      x0_state = EState::MinorFidget;\n    else if ((x8_delayTriggerBits & 0x2) == 0x2)\n      x0_state = EState::MajorFidget;\n    else\n      x0_state = EState::NoFidget;\n  }\n\n  switch (x0_state) {\n  case EState::MinorFidget:\n    x34_24_loading = true;\n    x10_delayTimerEnableBits = 2;\n    x8_delayTriggerBits &= ~0x1;\n    kMinorFidgetDelay = mgr.GetActiveRandom()->Range(20.f, 29.f);\n    x4_type = SamusGun::EFidgetType::Minor;\n    xc_animSet = mgr.GetActiveRandom()->Range(0, 4);\n    break;\n  case EState::MajorFidget:\n    x34_24_loading = true;\n    x10_delayTimerEnableBits = 1;\n    x8_delayTriggerBits &= ~0x2;\n    kMajorFidgetDelay = mgr.GetActiveRandom()->Range(20.f, 30.f);\n    x4_type = SamusGun::EFidgetType::Major;\n    xc_animSet = mgr.GetActiveRandom()->Range(0, 5);\n    break;\n  case EState::HolsterBeam:\n    x4_type = SamusGun::EFidgetType::Minor;\n    x34_24_loading = true;\n    xc_animSet = 0;\n    break;\n  default:\n    break;\n  }\n\n  return x0_state;\n}\n\nvoid CFidget::ResetMinor() { x0_state = EState::NoFidget; }\n\nvoid CFidget::ResetAll() {\n  x0_state = EState::NoFidget;\n  x4_type = SamusGun::EFidgetType::Invalid;\n  x18_timeSinceStrikeCooldown = 0.f;\n  x1c_timeSinceUnmorph = 0.f;\n  x14_timeSinceFire = 0.f;\n  x24_minorDelayTimer = 0.f;\n  x28_majorDelayTimer = 0.f;\n  x2c_holsterTimeSinceFire = 0.f;\n  x8_delayTriggerBits = 0;\n  xc_animSet = -1;\n  x10_delayTimerEnableBits = 3;\n  x34_24_loading = false;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CFidget.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Weapon/CGunMotion.hpp\"\n\nnamespace metaforce {\nclass CStateManager;\n\nclass CFidget {\npublic:\n  enum class EState {\n    NoFidget,\n    MinorFidget,\n    MajorFidget,\n    HolsterBeam,\n    StillMinorFidget,\n    StillMajorFidget,\n    StillHolsterBeam,\n    Loading\n  };\n\nprivate:\n  EState x0_state = EState::NoFidget;\n  SamusGun::EFidgetType x4_type = SamusGun::EFidgetType::Invalid;\n  u32 x8_delayTriggerBits = 0;\n  // 0: panel, 1: panel reset, 2: adjust nozzle, 3: panel buttons\n  s32 xc_animSet = -1;\n  u32 x10_delayTimerEnableBits = 3;\n  float x14_timeSinceFire = 0.f;\n  float x18_timeSinceStrikeCooldown = 0.f;\n  float x1c_timeSinceUnmorph = 0.f;\n  float x20_timeSinceBobbing = 0.f;\n  float x24_minorDelayTimer = 0.f;\n  float x28_majorDelayTimer = 0.f;\n  float x2c_holsterTimeSinceFire = 0.f;\n  float x30_timeUntilHolster = 105.f;\n  bool x34_24_loading = false;\n\npublic:\n  EState GetState() const { return x0_state; }\n  SamusGun::EFidgetType GetType() const { return x4_type; }\n  s32 GetAnimSet() const { return xc_animSet; }\n  EState Update(int fireButtonStates, bool bobbing, bool inStrikeCooldown, float dt, CStateManager& mgr);\n  void ResetMinor();\n  void ResetAll();\n  void DoneLoading() { x34_24_loading = false; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CFlameInfo.cpp",
    "content": "#include \"Runtime/Weapon/CFlameInfo.hpp\"\n\nnamespace metaforce {\n\nCFlameInfo::CFlameInfo(s32 w1, s32 w2, CAssetId flameFxId, s32 w3, float f1, float f2, float f3)\n: x0_propertyCount(w1), x4_attributes(w2), x8_flameFxId(flameFxId), xc_length(w3), x10_(f1), x18_(f2), x1c_(f3) {}\n\nCFlameInfo::CFlameInfo(CInputStream& in)\n: x0_propertyCount(in.ReadLong())\n, x4_attributes(in.ReadLong())\n, x8_flameFxId(in)\n, xc_length(in.ReadLong())\n, x10_(in.ReadFloat())\n, x18_(in.ReadFloat())\n, x1c_(in.ReadFloat()) {}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CFlameInfo.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Weapon/CGameProjectile.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n\nnamespace metaforce {\nclass CFlameInfo {\n  friend class CFlameThrower;\n  s32 x0_propertyCount;\n  s32 x4_attributes;\n  CAssetId x8_flameFxId;\n  s32 xc_length;\n  float x10_;\n  // float x14_;\n  float x18_;\n  float x1c_;\n\npublic:\n  CFlameInfo(s32, s32, CAssetId, s32, float, float, float);\n  CFlameInfo(CInputStream& in);\n\n  [[nodiscard]] s32 GetAttributes() const { return x4_attributes; }\n  [[nodiscard]] s32 GetLength() const { return xc_length; }\n  [[nodiscard]] CAssetId GetFlameFxId() const { return x8_flameFxId; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CFlameThrower.cpp",
    "content": "#include \"Runtime/Weapon/CFlameThrower.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Collision/CInternalRayCastStructure.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/Weapon/CFlameInfo.hpp\"\n#include \"Runtime/Weapon/CFlameThrower.hpp\"\n#include \"Runtime/World/CGameLight.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nconst zeus::CVector3f CFlameThrower::kLightOffset(0, 3.f, 2.f);\n\nCFlameThrower::CFlameThrower(const TToken<CWeaponDescription>& wDesc, std::string_view name, EWeaponType wType,\n                             const CFlameInfo& flameInfo, const zeus::CTransform& xf, EMaterialTypes matType,\n                             const CDamageInfo& dInfo, TUniqueId uid, TAreaId aId, TUniqueId owner,\n                             EProjectileAttrib attribs, CAssetId playerSteamTxtr, s16 playerHitSfx,\n                             CAssetId playerIceTxtr)\n: CGameProjectile(false, wDesc, name, wType, xf, matType, dInfo, uid, aId, owner, kInvalidUniqueId, attribs, false,\n                  zeus::CVector3f(1.f), {}, -1, false)\n, x2e8_flameXf(xf)\n, x338_(flameInfo.x10_)\n, x33c_flameDesc(g_SimplePool->GetObj({FOURCC('PART'), flameInfo.GetFlameFxId()}))\n, x348_flameGen(std::make_unique<CElementGen>(x33c_flameDesc))\n, x34c_flameWarp(176.f - float(flameInfo.GetLength()), xf.origin, bool(flameInfo.GetAttributes() & 0x4))\n, x3f4_playerSteamTxtr(playerSteamTxtr)\n, x3f8_playerHitSfx(playerHitSfx)\n, x3fc_playerIceTxtr(playerIceTxtr)\n, x400_26_((flameInfo.GetAttributes() & 1) == 0)\n, x400_27_coneCollision((flameInfo.GetAttributes() & 0x2) != 0) {}\n\nvoid CFlameThrower::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CFlameThrower::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  if (msg == EScriptObjectMessage::Registered) {\n    xe6_27_thermalVisorFlags = 2;\n    mgr.AddWeaponId(xec_ownerId, xf0_weaponType);\n  } else if (msg == EScriptObjectMessage::Deleted) {\n    mgr.RemoveWeaponId(xec_ownerId, xf0_weaponType);\n    DeleteProjectileLight(mgr);\n  }\n\n  CGameProjectile::AcceptScriptMsg(msg, uid, mgr);\n}\n\nvoid CFlameThrower::SetTransform(const zeus::CTransform& xf, float) { x2e8_flameXf = xf; }\n\nvoid CFlameThrower::Reset(CStateManager& mgr, bool resetWarp) {\n  SetFlameLightActive(mgr, false);\n  if (resetWarp) {\n    SetActive(false);\n    x400_25_particlesActive = false;\n    x3f0_flameState = EFlameState::Default;\n    x330_particleWaitDelayTimer = 0.f;\n    x334_fireStopTimer = 0.f;\n    x318_flameBounds = zeus::skNullBox;\n    x348_flameGen->SetParticleEmission(false);\n    x34c_flameWarp.ResetPosition(x2e8_flameXf.origin);\n  } else {\n    x348_flameGen->SetParticleEmission(false);\n    x400_25_particlesActive = false;\n    x3f0_flameState = EFlameState::FireStopTimer;\n  }\n}\n\nvoid CFlameThrower::Fire(const zeus::CTransform&, CStateManager& mgr, bool) {\n  SetActive(true);\n  x400_25_particlesActive = true;\n  x400_24_active = true;\n  x3f0_flameState = EFlameState::FireStart;\n  CreateFlameParticles(mgr);\n}\n\nvoid CFlameThrower::CreateFlameParticles(CStateManager& mgr) {\n  DeleteProjectileLight(mgr);\n  x348_flameGen = std::make_unique<CElementGen>(x33c_flameDesc);\n  x348_flameGen->SetParticleEmission(true);\n  x348_flameGen->SetZTest(x400_27_coneCollision);\n  x348_flameGen->AddModifier(&x34c_flameWarp);\n  if (x348_flameGen->SystemHasLight() && x2c8_projectileLight == kInvalidUniqueId)\n    CreateProjectileLight(\"FlameThrower_Light\"sv, x348_flameGen->GetLight(), mgr);\n}\n\nvoid CFlameThrower::AddToRenderer(const zeus::CFrustum&, CStateManager& mgr) {\n  g_Renderer->AddParticleGen(*x348_flameGen);\n  EnsureRendered(mgr, x2e8_flameXf.origin, GetRenderBounds());\n}\n\nvoid CFlameThrower::Render(CStateManager&) {}\n\nstd::optional<zeus::CAABox> CFlameThrower::GetTouchBounds() const { return std::nullopt; }\n\nvoid CFlameThrower::Touch(CActor&, CStateManager&) {}\n\nvoid CFlameThrower::SetFlameLightActive(CStateManager& mgr, bool active) {\n  if (x2c8_projectileLight == kInvalidUniqueId)\n    return;\n\n  if (TCastToPtr<CGameLight> light = mgr.ObjectById(x2c8_projectileLight))\n    light->SetActive(active);\n}\n\nvoid CFlameThrower::UpdateFlameState(float dt, CStateManager& mgr) {\n  switch (x3f0_flameState) {\n  case EFlameState::FireStart:\n    x3f0_flameState = EFlameState::FireActive;\n    break;\n  case EFlameState::FireStopTimer:\n    x334_fireStopTimer += 4.f * dt;\n    if (x334_fireStopTimer > 1.f) {\n      x334_fireStopTimer = 1.f;\n      x3f0_flameState = EFlameState::FireWaitForParticlesDone;\n      x400_24_active = false;\n    }\n    break;\n  case EFlameState::FireWaitForParticlesDone:\n    x330_particleWaitDelayTimer += dt;\n    if (x330_particleWaitDelayTimer > 0.1f && x348_flameGen && x348_flameGen->GetParticleCountAll() == 0) {\n      x3f0_flameState = EFlameState::Default;\n      Reset(mgr, true);\n    }\n    break;\n  default:\n    break;\n  }\n}\n\nCRayCastResult CFlameThrower::DoCollisionCheck(TUniqueId& idOut, const zeus::CAABox& aabb, CStateManager& mgr) {\n  CRayCastResult ret;\n  EntityList nearList;\n  mgr.BuildNearList(nearList, aabb, CMaterialFilter::skPassEverything, this);\n  const auto& colPoints = x34c_flameWarp.GetCollisionPoints();\n\n  if (x400_27_coneCollision && !colPoints.empty()) {\n    const float radiusPitch =\n        (x34c_flameWarp.GetMaxSize() - x34c_flameWarp.GetMinSize()) / float(colPoints.size()) * 0.5f;\n    float curRadius = radiusPitch;\n\n    for (size_t i = 1; i < colPoints.size(); ++i) {\n      const zeus::CVector3f delta = colPoints[i] - colPoints[i - 1];\n      zeus::CTransform lookXf = zeus::lookAt(colPoints[i - 1], colPoints[i]);\n      lookXf.origin = delta * 0.5f + colPoints[i - 1];\n      const zeus::COBBox obb(lookXf, {curRadius, delta.magnitude() * 0.5f, curRadius});\n\n      for (const auto& id : nearList) {\n        if (auto* act = static_cast<CActor*>(mgr.ObjectById(id))) {\n          const CProjectileTouchResult tres = CanCollideWith(*act, mgr);\n          if (tres.GetActorId() == kInvalidUniqueId) {\n            continue;\n          }\n\n          const auto tb = act->GetTouchBounds();\n          if (!tb) {\n            continue;\n          }\n\n          if (obb.AABoxIntersectsBox(*tb)) {\n            const CCollidableAABox caabb(*tb, act->GetMaterialList());\n            const zeus::CVector3f flameToAct = act->GetAimPosition(mgr, 0.f) - x2e8_flameXf.origin;\n            const float flameToActDist = flameToAct.magnitude();\n            const CInternalRayCastStructure rc(x2e8_flameXf.origin, flameToAct.normalized(), flameToActDist, {},\n                                               CMaterialFilter::skPassEverything);\n            const CRayCastResult cres = caabb.CastRayInternal(rc);\n            if (cres.IsInvalid()) {\n              continue;\n            }\n            return cres;\n          }\n        }\n      }\n\n      curRadius += radiusPitch;\n    }\n  } else {\n    for (size_t i = 0; i < colPoints.size() - 1; ++i) {\n      const zeus::CVector3f delta = colPoints[i + 1] - colPoints[i];\n      const float deltaMag = delta.magnitude();\n      if (deltaMag <= 0.f) {\n        break;\n      }\n\n      const CRayCastResult cres =\n          RayCollisionCheckWithWorld(idOut, colPoints[i], colPoints[i + 1], deltaMag, nearList, mgr);\n      if (cres.IsValid()) {\n        return cres;\n      }\n    }\n  }\n\n  return ret;\n}\n\nvoid CFlameThrower::ApplyDamageToActor(CStateManager& mgr, TUniqueId id, float dt) {\n  if (mgr.GetPlayer().GetUniqueId() == id && x3f4_playerSteamTxtr.IsValid() && x3fc_playerIceTxtr.IsValid())\n    mgr.GetPlayer().Freeze(mgr, x3f4_playerSteamTxtr, x3f8_playerHitSfx, x3fc_playerIceTxtr);\n  CDamageInfo useDInfo = CDamageInfo(x12c_curDamageInfo, dt);\n  ApplyDamageToActors(mgr, useDInfo);\n}\n\nvoid CFlameThrower::Think(float dt, CStateManager& mgr) {\n  CWeapon::Think(dt, mgr);\n  if (!GetActive())\n    return;\n\n  UpdateFlameState(dt, mgr);\n\n  zeus::CVector3f flamePoint = x2e8_flameXf.origin;\n  bool r28 = x3f0_flameState == EFlameState::FireActive || x3f0_flameState == EFlameState::FireStopTimer;\n  if (r28) {\n    x34c_flameWarp.Activate(true);\n    x34c_flameWarp.SetWarpPoint(flamePoint);\n    x34c_flameWarp.SetStateManager(mgr);\n    x348_flameGen->SetTranslation(flamePoint);\n    x348_flameGen->SetOrientation(x2e8_flameXf.getRotation());\n  } else {\n    x34c_flameWarp.Activate(false);\n  }\n\n  x348_flameGen->Update(dt);\n  x34c_flameWarp.SetMaxDistSq(0.f);\n  x34c_flameWarp.SetFloatingPoint(flamePoint);\n\n  if (r28 && x34c_flameWarp.IsProcessed()) {\n    x318_flameBounds = x34c_flameWarp.CalculateBounds();\n    TUniqueId id = kInvalidUniqueId;\n    const CRayCastResult res = DoCollisionCheck(id, x318_flameBounds, mgr);\n    if (TCastToConstPtr<CActor>(mgr.ObjectById(id))) {\n      ApplyDamageToActor(mgr, id, dt);\n    } else if (res.IsValid()) {\n      const CMaterialFilter useFilter = xf8_filter;\n      const CDamageInfo useDInfo = CDamageInfo(x12c_curDamageInfo, dt);\n      mgr.ApplyDamageToWorld(xec_ownerId, *this, res.GetPoint(), useDInfo, useFilter);\n    }\n  }\n\n  CActor::SetTransform(x2e8_flameXf.getRotation());\n  CActor::SetTranslation(x2e8_flameXf.origin);\n\n  if (x2c8_projectileLight != kInvalidUniqueId) {\n    if (TCastToPtr<CGameLight> light = mgr.ObjectById(x2c8_projectileLight)) {\n      light->SetTransform(GetTransform());\n      light->SetTranslation(x34c_flameWarp.GetFloatingPoint());\n      if (x348_flameGen && x348_flameGen->SystemHasLight())\n        light->SetLight(x348_flameGen->GetLight());\n    }\n  }\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CFlameThrower.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/Particle/CFlameWarp.hpp\"\n#include \"Runtime/Weapon/CGameProjectile.hpp\"\n\nnamespace metaforce {\nclass CFlameInfo;\nclass CElementGen;\nclass CFlameThrower : public CGameProjectile {\npublic:\n  enum class EFlameState { Default, FireStart, FireActive, FireStopTimer, FireWaitForParticlesDone };\n\nprivate:\n  static const zeus::CVector3f kLightOffset;\n  zeus::CTransform x2e8_flameXf;\n  zeus::CAABox x318_flameBounds = zeus::skNullBox;\n  float x330_particleWaitDelayTimer = 0.f;\n  float x334_fireStopTimer = 0.f;\n  float x338_;\n  TToken<CGenDescription> x33c_flameDesc;\n  std::unique_ptr<CElementGen> x348_flameGen;\n  CFlameWarp x34c_flameWarp;\n  EFlameState x3f0_flameState = EFlameState::Default;\n  CAssetId x3f4_playerSteamTxtr;\n  s16 x3f8_playerHitSfx;\n  CAssetId x3fc_playerIceTxtr;\n  bool x400_24_active : 1 = false;\n  bool x400_25_particlesActive : 1 = false;\n  bool x400_26_ : 1;\n  bool x400_27_coneCollision : 1; /* Z-sort and finer collision detection */\n\n  void CreateFlameParticles(CStateManager&);\n  void SetFlameLightActive(CStateManager&, bool);\n  void UpdateFlameState(float, CStateManager&);\n  CRayCastResult DoCollisionCheck(TUniqueId& idOut, const zeus::CAABox& aabb, CStateManager& mgr);\n  void ApplyDamageToActor(CStateManager& mgr, TUniqueId id, float dt);\n\npublic:\n  DEFINE_ENTITY\n  CFlameThrower(const TToken<CWeaponDescription>& wDesc, std::string_view name, EWeaponType wType,\n                const CFlameInfo& flameInfo, const zeus::CTransform& xf, EMaterialTypes matType,\n                const CDamageInfo& dInfo, TUniqueId uid, TAreaId aId, TUniqueId owner, EProjectileAttrib attribs,\n                CAssetId playerSteamTxtr, s16 playerHitSfx, CAssetId playerIceTxtr);\n\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void Think(float, CStateManager&) override;\n  void AddToRenderer(const zeus::CFrustum&, CStateManager&) override;\n  void Render(CStateManager& mgr) override;\n  std::optional<zeus::CAABox> GetTouchBounds() const override;\n  void Touch(CActor& actor, CStateManager& mgr) override;\n  void SetTransform(const zeus::CTransform& xf, float);\n  void Reset(CStateManager&, bool);\n  void Fire(const zeus::CTransform&, CStateManager&, bool);\n  bool GetParticlesActive() const { return x400_25_particlesActive; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CGSComboFire.cpp",
    "content": "#include \"Runtime/Weapon/CGSComboFire.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Character/CAnimData.hpp\"\n#include \"Runtime/Character/CPASAnimParmData.hpp\"\n\nnamespace metaforce {\n\nbool CGSComboFire::Update(CAnimData& data, float dt, CStateManager& mgr) {\n  if (x8_cueAnimId != -1) {\n    x0_delay -= dt;\n    if (x0_delay <= 0.f) {\n      data.EnableLooping(x4_loopState == 1);\n      CAnimPlaybackParms aparms(x8_cueAnimId, -1, 1.f, true);\n      data.SetAnimation(aparms, false);\n      x0_delay = 0.f;\n      x8_cueAnimId = -1;\n    }\n  } else if (!data.IsAnimTimeRemaining(0.001f, \"Whole Body\")) {\n    switch (x4_loopState) {\n    case 0:\n      SetAnim(data, xc_gunId, 1, mgr, 0.f);\n      switch (xc_gunId) {\n      case 4:\n      case 0:\n      case 1:\n        x10_24_over = true;\n        break;\n      default:\n        break;\n      }\n      break;\n    case 2:\n      x4_loopState = -1;\n      return true;\n    default:\n      break;\n    }\n  }\n  return false;\n}\n\ns32 CGSComboFire::SetAnim(CAnimData& data, s32 gunId, s32 loopState, CStateManager& mgr, float delay) {\n  s32 useLoopState = 2;\n  if (!x10_25_idle)\n    useLoopState = loopState;\n  x10_25_idle = false;\n  const CPASDatabase& pas = data.GetCharacterInfo().GetPASDatabase();\n  CPASAnimParmData parms(pas::EAnimationState::Death, CPASAnimParm::FromInt32(gunId),\n                         CPASAnimParm::FromEnum(useLoopState));\n  auto anim = pas.FindBestAnimation(parms, *mgr.GetActiveRandom(), -1);\n  x10_24_over = false;\n  xc_gunId = gunId;\n  x4_loopState = useLoopState;\n  if (delay != 0.f) {\n    x0_delay = delay;\n    x8_cueAnimId = anim.second;\n  } else {\n    data.EnableLooping(useLoopState == 1);\n    CAnimPlaybackParms aparms(anim.second, -1, 1.f, true);\n    data.SetAnimation(aparms, false);\n  }\n  return anim.second;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CGSComboFire.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n\nnamespace metaforce {\n\nclass CAnimData;\nclass CStateManager;\nclass CGSComboFire {\n  float x0_delay = 0.f;\n  s32 x4_loopState = -1; // In, loop, out\n  s32 x8_cueAnimId = -1;\n  s32 xc_gunId = -1;\n  bool x10_24_over : 1 = false;\n  bool x10_25_idle : 1 = false;\n\npublic:\n  CGSComboFire() = default;\n\n  bool IsComboOver() const { return x10_24_over; }\n  s32 GetLoopState() const { return x4_loopState; }\n  void SetLoopState(s32 l) { x4_loopState = l; }\n  void SetIdle(bool i) { x10_25_idle = i; }\n  s32 GetGunId() const { return xc_gunId; }\n  bool Update(CAnimData& data, float dt, CStateManager& mgr);\n  s32 SetAnim(CAnimData& data, s32 gunId, s32 loopState, CStateManager& mgr, float delay);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CGSFidget.cpp",
    "content": "#include \"Runtime/Weapon/CGSFidget.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Character/CPASAnimParmData.hpp\"\n#include \"Runtime/Character/CAnimData.hpp\"\n#include \"Runtime/Weapon/WeaponCommon.hpp\"\n\nnamespace metaforce {\nbool CGSFidget::Update(CAnimData& data, float dt, CStateManager& mgr) {\n  return !data.IsAnimTimeRemaining(0.001f, \"Whole Body\");\n}\n\ns32 CGSFidget::SetAnim(CAnimData& data, s32 type, s32 gunId, s32 animSet, CStateManager& mgr) {\n  const CPASDatabase& pas = data.GetCharacterInfo().GetPASDatabase();\n  CPASAnimParmData parms(pas::EAnimationState::Getup, CPASAnimParm::FromEnum(type), CPASAnimParm::FromInt32(gunId),\n                         CPASAnimParm::FromInt32(animSet));\n  auto anim = pas.FindBestAnimation(parms, *mgr.GetActiveRandom(), -1);\n  bool loop = pas.GetAnimState(pas::EAnimationState::Getup)->GetAnimParmData(anim.second, 3).GetBoolValue();\n  x14_gunId = gunId;\n  x18_animSet = animSet;\n  if (anim.second != -1) {\n    data.EnableLooping(loop);\n    CAnimPlaybackParms aParms(anim.second, -1, 1.f, true);\n    data.SetAnimation(aParms, false);\n    UnLoadAnim();\n  }\n  return anim.second;\n}\n\nvoid CGSFidget::LoadAnimAsync(CAnimData& data, s32 type, s32 gunId, s32 animSet, CStateManager& mgr) {\n  CPASAnimParmData parms(pas::EAnimationState::Getup, CPASAnimParm::FromEnum(type), CPASAnimParm::FromInt32(gunId),\n                         CPASAnimParm::FromInt32(animSet));\n  auto anim = data.GetCharacterInfo().GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1);\n  if (anim.second != -1)\n    NWeaponTypes::get_token_vector(data, anim.second, x0_anims, true);\n}\n\nvoid CGSFidget::UnLoadAnim() { x0_anims.clear(); }\n\nbool CGSFidget::IsAnimLoaded() const { return NWeaponTypes::are_tokens_ready(x0_anims); }\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CGSFidget.hpp",
    "content": "#pragma once\n\n#include <vector>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n\nnamespace metaforce {\nclass CAnimData;\nclass CStateManager;\nclass CGSFidget {\n  std::vector<CToken> x0_anims;\n  s32 x10_ = -1;\n  s32 x14_gunId = -1;\n  s32 x18_animSet = -1;\n\npublic:\n  bool Update(CAnimData& data, float dt, CStateManager& mgr);\n  s32 SetAnim(CAnimData& data, s32 type, s32 gunId, s32 animSet, CStateManager& mgr);\n  void LoadAnimAsync(CAnimData& data, s32 type, s32 gunId, s32 animSet, CStateManager& mgr);\n  void UnLoadAnim();\n  bool IsAnimLoaded() const;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CGSFreeLook.cpp",
    "content": "#include \"Runtime/Weapon/CGSFreeLook.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Character/CPASAnimParmData.hpp\"\n\nnamespace metaforce {\n\nbool CGSFreeLook::Update(CAnimData& data, float dt, CStateManager& mgr) {\n  if (x4_cueAnimId != -1) {\n    x0_delay -= dt;\n    if (x0_delay <= 0.f) {\n      data.EnableLooping(x8_loopState == 1);\n      CAnimPlaybackParms aparms(x4_cueAnimId, -1, 1.f, true);\n      data.SetAnimation(aparms, false);\n      x0_delay = 0.f;\n      x4_cueAnimId = -1;\n    }\n  } else if (!data.IsAnimTimeRemaining(0.001f, \"Whole Body\")) {\n    switch (x8_loopState) {\n    case 0:\n      SetAnim(data, xc_gunId, x10_setId, 1, mgr, 0.f);\n      break;\n    case 2:\n      x8_loopState = -1;\n      return true;\n    default:\n      break;\n    }\n  }\n  return false;\n}\n\ns32 CGSFreeLook::SetAnim(CAnimData& data, s32 gunId, s32 setId, s32 loopState, CStateManager& mgr, float delay) {\n  s32 useLoopState = 1;\n  if (!x14_idle)\n    useLoopState = loopState;\n  x14_idle = false;\n  const CPASDatabase& pas = data.GetCharacterInfo().GetPASDatabase();\n  CPASAnimParmData parms(pas::EAnimationState::Step, CPASAnimParm::FromInt32(gunId), CPASAnimParm::FromInt32(setId),\n                         CPASAnimParm::FromEnum(useLoopState));\n  auto anim = pas.FindBestAnimation(parms, *mgr.GetActiveRandom(), -1);\n  xc_gunId = gunId;\n  x10_setId = pas.GetAnimState(pas::EAnimationState::Step)->GetAnimParmData(anim.second, 1).GetInt32Value();\n  x8_loopState = useLoopState;\n  if (delay != 0.f) {\n    x0_delay = delay;\n    x4_cueAnimId = anim.second;\n  } else {\n    data.EnableLooping(loopState == 1);\n    CAnimPlaybackParms aparms(anim.second, -1, 1.f, true);\n    data.SetAnimation(aparms, false);\n  }\n  return anim.second;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CGSFreeLook.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n\nnamespace metaforce {\n\nclass CAnimData;\nclass CStateManager;\nclass CGSFreeLook {\n  float x0_delay = 0.f;\n  s32 x4_cueAnimId = -1;\n  s32 x8_loopState = -1; // In, loop, out\n  s32 xc_gunId = 0;\n  s32 x10_setId = -1;\n  bool x14_idle = false;\n\npublic:\n  s32 GetSetId() const { return x10_setId; }\n  void SetLoopState(s32 l) { x8_loopState = l; }\n  s32 GetLoopState() const { return x8_loopState; }\n  void SetIdle(bool l) { x14_idle = l; }\n  s32 GetGunId() const { return xc_gunId; }\n  bool Update(CAnimData& data, float dt, CStateManager& mgr);\n  s32 SetAnim(CAnimData& data, s32 gunId, s32 setId, s32 loopState, CStateManager& mgr, float delay);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CGameProjectile.cpp",
    "content": "#include \"Runtime/Weapon/CGameProjectile.hpp\"\n\n#include <utility>\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Collision/CCollisionActor.hpp\"\n#include \"Runtime/Collision/CInternalRayCastStructure.hpp\"\n#include \"Runtime/MP1/World/CPuddleToadGamma.hpp\"\n#include \"Runtime/World/CGameLight.hpp\"\n#include \"Runtime/World/CHUDBillboardEffect.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CScriptDoor.hpp\"\n#include \"Runtime/World/CScriptPlatform.hpp\"\n#include \"Runtime/World/CWallCrawlerSwarm.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nCGameProjectile::CGameProjectile(bool active, const TToken<CWeaponDescription>& wDesc, std::string_view name,\n                                 EWeaponType wType, const zeus::CTransform& xf, EMaterialTypes excludeMat,\n                                 const CDamageInfo& dInfo, TUniqueId uid, TAreaId aid, TUniqueId owner,\n                                 TUniqueId homingTarget, EProjectileAttrib attribs, bool underwater,\n                                 const zeus::CVector3f& scale,\n                                 std::optional<TLockedToken<CGenDescription>> visorParticle, u16 visorSfx,\n                                 bool sendCollideMsg)\n: CWeapon(uid, aid, active, owner, wType, name, xf,\n          CMaterialFilter::MakeIncludeExclude(\n              {EMaterialTypes::Solid, EMaterialTypes::NonSolidDamageable},\n              {EMaterialTypes::Projectile, EMaterialTypes::ProjectilePassthrough, excludeMat}),\n          CMaterialList(EMaterialTypes::Projectile), dInfo, attribs | GetBeamAttribType(wType),\n          CModelData::CModelDataNull())\n, x158_visorParticle(std::move(visorParticle))\n, x168_visorSfx(visorSfx)\n, x170_projectile(wDesc, xf.origin, xf.basis, scale,\n                  (attribs & EProjectileAttrib::ParticleOPTS) == EProjectileAttrib::ParticleOPTS)\n, x298_previousPos(xf.origin)\n, x2a4_projExtent((xe8_projectileAttribs & EProjectileAttrib::BigProjectile) == EProjectileAttrib::BigProjectile ? 0.25f\n                                                                                                                 : 0.1f)\n, x2c0_homingTargetId(homingTarget)\n, x2cc_wpscId(wDesc.GetObjectTag()->id)\n, x2e4_25_startedUnderwater(underwater)\n, x2e4_26_waterUpdate(underwater)\n, x2e4_27_inWater(underwater)\n, x2e4_28_sendProjectileCollideMsg(sendCollideMsg) {}\n\nvoid CGameProjectile::Accept(metaforce::IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CGameProjectile::ResolveCollisionWithActor(const CRayCastResult& res, CActor& act, CStateManager& mgr) {\n  const zeus::CVector3f revDir = -x34_transform.basis[1].normalized();\n  const TCastToConstPtr<CPlayer> player(act);\n\n  if (!player) {\n    return;\n  }\n\n  if (!x158_visorParticle || mgr.GetPlayer().GetCameraState() != CPlayer::EPlayerCameraState::FirstPerson) {\n    return;\n  }\n\n  if (zeus::radToDeg(\n          std::acos(mgr.GetCameraManager()->GetCurrentCameraTransform(mgr).basis[1].normalized().dot(revDir))) > 45.f) {\n    return;\n  }\n\n  // Hit us head on! Draw Billboard!\n  std::optional<TToken<CGenDescription>> bb = {*x158_visorParticle};\n  auto* effect = new CHUDBillboardEffect(\n      bb, {}, mgr.AllocateUniqueId(), true, \"VisorAcid\", CHUDBillboardEffect::GetNearClipDistance(mgr),\n      CHUDBillboardEffect::GetScaleForPOV(mgr), zeus::skWhite, zeus::skOne3f, zeus::skZero3f);\n  mgr.AddObject(effect);\n  CSfxManager::SfxStart(x168_visorSfx, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n\n  if (!x2e4_28_sendProjectileCollideMsg) {\n    return;\n  }\n\n  mgr.SendScriptMsg(&mgr.GetPlayer(), GetUniqueId(), EScriptObjectMessage::ProjectileCollide);\n}\n\nvoid CGameProjectile::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId /*uid*/, CStateManager& mgr) {\n  if (msg == EScriptObjectMessage::AddSplashInhabitant) {\n    if (!x2e4_27_inWater) {\n      x2e4_27_inWater = true;\n      x2e4_26_waterUpdate = true;\n    }\n  } else if (msg == EScriptObjectMessage::UpdateSplashInhabitant) {\n    if (!x2e4_26_waterUpdate)\n      x2e4_26_waterUpdate = true;\n  } else if (msg == EScriptObjectMessage::RemoveSplashInhabitant) {\n    if (x2e4_26_waterUpdate) {\n      x2e4_26_waterUpdate = false;\n      x2e4_27_inWater = false;\n    }\n  } else if (msg == EScriptObjectMessage::Deleted)\n    DeleteProjectileLight(mgr);\n}\n\nvoid CGameProjectile::Render(CStateManager& mgr) {\n  x170_projectile.Render();\n  CWeapon::Render(mgr);\n}\n\nEProjectileAttrib CGameProjectile::GetBeamAttribType(EWeaponType wType) {\n  if (wType == EWeaponType::Ice)\n    return EProjectileAttrib::Ice;\n  else if (wType == EWeaponType::Wave)\n    return EProjectileAttrib::Wave;\n  else if (wType == EWeaponType::Plasma)\n    return EProjectileAttrib::Plasma;\n  else if (wType == EWeaponType::Phazon)\n    return EProjectileAttrib::Phazon;\n\n  return EProjectileAttrib::None;\n}\n\nvoid CGameProjectile::DeleteProjectileLight(CStateManager& mgr) {\n  if (x2c8_projectileLight != kInvalidUniqueId) {\n    mgr.FreeScriptObject(x2c8_projectileLight);\n    x2c8_projectileLight = kInvalidUniqueId;\n  }\n}\n\nvoid CGameProjectile::CreateProjectileLight(std::string_view name, const CLight& light, CStateManager& mgr) {\n  DeleteProjectileLight(mgr);\n  x2c8_projectileLight = mgr.AllocateUniqueId();\n  mgr.AddObject(new CGameLight(x2c8_projectileLight, GetAreaId(), GetActive(), name, GetTransform(), GetUniqueId(),\n                               light, u32(x2cc_wpscId.Value()), 0, 0.f));\n}\n\nvoid CGameProjectile::Chase(float dt, CStateManager& mgr) {\n  if (!x170_projectile.IsProjectileActive() || x2c0_homingTargetId == kInvalidUniqueId)\n    return;\n\n  if (const TCastToConstPtr<CActor> act = mgr.GetObjectById(x2c0_homingTargetId)) {\n    if (!act->GetMaterialList().HasMaterial(EMaterialTypes::Target) &&\n        !act->GetMaterialList().HasMaterial(EMaterialTypes::Player)) {\n      x2c0_homingTargetId = kInvalidUniqueId;\n    } else {\n      zeus::CVector3f homingPos = act->GetHomingPosition(mgr, 0.f);\n\n      const TCastToConstPtr<CWallCrawlerSwarm> swarm = act.GetPtr();\n      if (swarm) {\n        int lockOnId = swarm->GetCurrentLockOnId();\n        if (swarm->GetLockOnLocationValid(lockOnId)) {\n          homingPos = swarm->GetLockOnLocation(lockOnId);\n        } else {\n          x2c0_homingTargetId = kInvalidUniqueId;\n          return;\n        }\n      }\n\n      zeus::CVector3f projToPos = homingPos - x170_projectile.GetTranslation();\n      if (x2e0_minHomingDist > 0.f && projToPos.magnitude() < x2e0_minHomingDist) {\n        x2c0_homingTargetId = kInvalidUniqueId;\n        return;\n      }\n\n      if (!swarm && !TCastToConstPtr<CPhysicsActor>(act.GetPtr()))\n        if (auto tb = act->GetTouchBounds())\n          projToPos.z() += (tb->max.z() - tb->min.z()) * 0.5f;\n\n      zeus::CQuaternion qDelta =\n          zeus::CQuaternion::shortestRotationArc(x170_projectile.GetTransform().basis[1], projToPos);\n\n      float wThres = qDelta.w() * qDelta.w() * 2.f - 1.f;\n      if (wThres > 0.99f)\n        return;\n\n      float turnRate;\n      if (x2e4_26_waterUpdate)\n        turnRate = x170_projectile.GetMaxTurnRate() * 0.5f;\n      else\n        turnRate = x170_projectile.GetMaxTurnRate();\n\n      float maxTurnDelta = zeus::degToRad(turnRate * dt);\n      float turnDelta = std::acos(wThres);\n      if (maxTurnDelta < turnDelta) {\n        /* Clamp quat to max delta */\n        qDelta =\n            zeus::CQuaternion(std::cos(maxTurnDelta * 0.5f),\n                              (std::sin(maxTurnDelta * 0.5f) / std::sin(turnDelta * 0.5f)) * qDelta.getImaginary());\n      }\n\n      zeus::CTransform xf = qDelta.toTransform() * x170_projectile.GetTransform();\n      xf.orthonormalize();\n      x170_projectile.SetWorldSpaceOrientation(xf);\n    }\n  }\n}\n\nvoid CGameProjectile::UpdateHoming(float dt, CStateManager& mgr) {\n  if (!x2e4_24_active || x2c0_homingTargetId == kInvalidUniqueId || x2a8_homingDt <= 0.f)\n    return;\n\n  x2b0_targetHomingTime += dt;\n\n  while (x2b0_targetHomingTime >= x2b8_curHomingTime) {\n    Chase(x2a8_homingDt, mgr);\n    x2b8_curHomingTime += x2a8_homingDt;\n  }\n}\n\nvoid CGameProjectile::UpdateProjectileMovement(float dt, CStateManager& mgr) {\n  float useDt = dt;\n  if (x2e4_26_waterUpdate)\n    useDt = 37.5f * dt * dt;\n\n  x298_previousPos = x34_transform.origin;\n  x170_projectile.Update(useDt);\n  SetTransform(x170_projectile.GetTransform());\n  SetTranslation(x170_projectile.GetTranslation());\n  UpdateHoming(dt, mgr);\n}\n\nCRayCastResult CGameProjectile::DoCollisionCheck(TUniqueId& idOut, CStateManager& mgr) {\n  CRayCastResult res;\n  if (x2e4_24_active) {\n    zeus::CVector3f posDelta = x34_transform.origin - x298_previousPos;\n    EntityList nearList;\n    mgr.BuildNearList(nearList, GetProjectileBounds(),\n                      CMaterialFilter::MakeExclude(EMaterialTypes::ProjectilePassthrough), this);\n\n    res =\n        RayCollisionCheckWithWorld(idOut, x298_previousPos, x34_transform.origin, posDelta.magnitude(), nearList, mgr);\n  }\n  return res;\n}\n\nvoid CGameProjectile::ApplyDamageToActors(CStateManager& mgr, const CDamageInfo& dInfo) {\n  if (x2c6_pendingDamagee != kInvalidUniqueId) {\n    if (const TCastToConstPtr<CActor> act = mgr.ObjectById(x2c6_pendingDamagee)) {\n      mgr.ApplyDamage(GetUniqueId(), act->GetUniqueId(), xec_ownerId, dInfo, xf8_filter, x34_transform.basis[1]);\n      if ((xe8_projectileAttribs & EProjectileAttrib::PlayerUnFreeze) == EProjectileAttrib::PlayerUnFreeze &&\n          mgr.GetPlayer().GetUniqueId() == act->GetUniqueId() && mgr.GetPlayer().GetFrozenState()) {\n        mgr.GetPlayer().UnFreeze(mgr);\n      }\n    }\n    x2c6_pendingDamagee = kInvalidUniqueId;\n  }\n\n  for (const CProjectileTouchResult& res : x2d0_touchResults) {\n    if (const TCastToConstPtr<CActor> act = mgr.GetObjectById(res.GetActorId())) {\n      mgr.ApplyDamage(GetUniqueId(), act->GetUniqueId(), xec_ownerId, dInfo, xf8_filter, x34_transform.basis[1]);\n      if ((xe8_projectileAttribs & EProjectileAttrib::PlayerUnFreeze) == EProjectileAttrib::PlayerUnFreeze &&\n          mgr.GetPlayer().GetUniqueId() == act->GetUniqueId() && mgr.GetPlayer().GetFrozenState()) {\n        mgr.GetPlayer().UnFreeze(mgr);\n      }\n    }\n  }\n\n  x2d0_touchResults.clear();\n}\n\nvoid CGameProjectile::FluidFXThink(EFluidState state, CScriptWater& water, CStateManager& mgr) {\n  if (x170_projectile.GetWeaponDescription()->xa6_SWTR)\n    CWeapon::FluidFXThink(state, water, mgr);\n}\n\nCRayCastResult CGameProjectile::RayCollisionCheckWithWorld(TUniqueId& idOut, const zeus::CVector3f& start,\n                                                           const zeus::CVector3f& end, float mag,\n                                                           const EntityList& nearList, CStateManager& mgr) {\n  x2d0_touchResults.clear();\n  idOut = kInvalidUniqueId;\n  x2c6_pendingDamagee = kInvalidUniqueId;\n  CRayCastResult res;\n  zeus::CVector3f delta = end - start;\n  if (!delta.canBeNormalized())\n    return res;\n\n  float bestMag = mag;\n  zeus::CVector3f dir = delta.normalized();\n  CRayCastResult res2 = mgr.RayStaticIntersection(start, dir, mag, xf8_filter);\n  if (res2.IsValid()) {\n    bestMag = res2.GetT();\n    res = res2;\n  }\n\n  for (TUniqueId id : nearList) {\n    if (CActor* ent = static_cast<CActor*>(mgr.ObjectById(id))) {\n      CProjectileTouchResult tRes = CanCollideWith(*ent, mgr);\n      if (tRes.GetActorId() == kInvalidUniqueId)\n        continue;\n      if (tRes.HasRayCastResult()) {\n        if (tRes.GetRayCastResult().GetT() < bestMag) {\n          ent->Touch(*this, mgr);\n          bestMag = tRes.GetRayCastResult().GetT();\n          res = tRes.GetRayCastResult();\n          x2c6_pendingDamagee = idOut = tRes.GetActorId();\n        }\n      } else {\n        auto tb = ent->GetTouchBounds();\n        const CGameProjectile* projObj = nullptr;\n        if (const TCastToConstPtr<CScriptDoor> door = ent) {\n          tb = door->GetProjectileBounds();\n        } else if (const TCastToConstPtr<CGameProjectile> proj = ent) {\n          tb.emplace(proj->GetProjectileBounds());\n          projObj = proj.GetPtr();\n        }\n        if (!tb)\n          continue;\n\n        CCollidableAABox prim(*tb, ent->GetMaterialList());\n        CRayCastResult res3 =\n            prim.CastRayInternal(CInternalRayCastStructure(start, dir, mag, {}, CMaterialFilter::skPassEverything));\n        if (res3.IsValid()) {\n          if (res3.GetT() < bestMag) {\n            bestMag = res3.GetT();\n            res = res3;\n            x2c6_pendingDamagee = idOut = tRes.GetActorId();\n          }\n        } else if (tb->pointInside(start) || (projObj && projObj->GetProjectileBounds().intersects(*tb))) {\n          x2c6_pendingDamagee = idOut = ent->GetUniqueId();\n          zeus::CUnitVector3f norm(-dir);\n          res = CRayCastResult(0.f, start, {norm, norm.dot(start)}, ent->GetMaterialList());\n          break;\n        }\n      }\n    }\n  }\n\n  if (x2e4_27_inWater && idOut == kInvalidUniqueId)\n    x2e4_27_inWater = false;\n\n  return res;\n}\n\nCProjectileTouchResult CGameProjectile::CanCollideWith(CActor& act, CStateManager& mgr) const {\n  if (act.GetDamageVulnerability()->GetVulnerability(x12c_curDamageInfo.GetWeaponMode(), false) ==\n      EVulnerability::PassThrough) {\n    return {kInvalidUniqueId, std::nullopt};\n  }\n\n  if (TCastToConstPtr<CScriptTrigger>(act)) {\n    return CanCollideWithTrigger(act, mgr);\n  } else if (TCastToConstPtr<CScriptPlatform>(act) || TCastToConstPtr<CCollisionActor>(act) ||\n             CPatterned::CastTo<MP1::CPuddleToadGamma>(&act)) {\n    return CanCollideWithComplexCollision(act, mgr);\n  } else {\n    return CanCollideWithGameObject(act, mgr);\n  }\n}\n\nCProjectileTouchResult CGameProjectile::CanCollideWithComplexCollision(const CActor& act,\n                                                                       const CStateManager& mgr) const {\n  const CPhysicsActor* useAct = nullptr;\n  if (const TCastToConstPtr<CScriptPlatform> plat = act) {\n    if (plat->HasComplexCollision()) {\n      useAct = plat.GetPtr();\n    }\n  } else if (const MP1::CPuddleToadGamma* toad = CPatterned::CastTo<MP1::CPuddleToadGamma>(&act)) {\n    useAct = toad;\n  } else if (const TCastToConstPtr<CCollisionActor> cact = act) {\n    if (cact->GetOwnerId() == xec_ownerId) {\n      return {kInvalidUniqueId, std::nullopt};\n    }\n    useAct = cact.GetPtr();\n  }\n\n  if (!useAct) {\n    return {act.GetUniqueId(), std::nullopt};\n  }\n\n  const CCollisionPrimitive* prim = useAct->GetCollisionPrimitive();\n  const zeus::CTransform xf = useAct->GetPrimitiveTransform();\n  const zeus::CVector3f deltaPos = GetTranslation() - x298_previousPos;\n  if (!deltaPos.canBeNormalized()) {\n    return {kInvalidUniqueId, std::nullopt};\n  }\n\n  const zeus::CVector3f dir = deltaPos.normalized();\n  float mag = deltaPos.magnitude();\n  const CRayCastResult res = prim->CastRayInternal(\n      {x298_previousPos, dir, mag, xf,\n       CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {EMaterialTypes::ProjectilePassthrough})});\n  if (res.IsValid()) {\n    return {act.GetUniqueId(), {res}};\n  }\n\n  if (prim->GetPrimType() == FOURCC('SPHR')) {\n    mag *= 2.f;\n    const CRayCastResult res2 = prim->CastRayInternal(\n        {x298_previousPos - dir * mag, dir, deltaPos.magnitude(), xf,\n         CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {EMaterialTypes::ProjectilePassthrough})});\n    if (res2.IsValid()) {\n      return {act.GetUniqueId(), {res2}};\n    }\n  } else if (const TCastToConstPtr<CCollisionActor> cAct = act) {\n    const float rad = cAct->GetSphereRadius();\n    if ((x298_previousPos - GetTranslation()).magSquared() < rad * rad) {\n      const zeus::CVector3f point = x298_previousPos - dir * rad * 1.125f;\n      const zeus::CUnitVector3f revDir(-dir);\n      return {act.GetUniqueId(), {{0.f, point, {revDir, point.dot(revDir)}, act.GetMaterialList()}}};\n    }\n  }\n\n  return {kInvalidUniqueId, std::nullopt};\n}\n\nCProjectileTouchResult CGameProjectile::CanCollideWithGameObject(CActor& act, CStateManager& mgr) const {\n  const TCastToConstPtr<CGameProjectile> proj = act;\n  if (!proj) {\n    if (!act.GetMaterialList().HasMaterial(EMaterialTypes::Solid) && !act.HealthInfo(mgr)) {\n      return {kInvalidUniqueId, std::nullopt};\n    } else if (act.GetUniqueId() == xec_ownerId) {\n      return {kInvalidUniqueId, std::nullopt};\n    } else if (act.GetUniqueId() == x2c2_lastResolvedObj) {\n      return {kInvalidUniqueId, std::nullopt};\n    } else if (xf8_filter.GetExcludeList().Intersection(act.GetMaterialList())) {\n      return {kInvalidUniqueId, std::nullopt};\n    } else if (TCastToPtr<CPatterned> ai = act) {\n      if (!ai->CanBeShot(mgr, int(xe8_projectileAttribs))) {\n        return {kInvalidUniqueId, std::nullopt};\n      }\n    }\n  } else if ((xe8_projectileAttribs & EProjectileAttrib::PartialCharge) == EProjectileAttrib::PartialCharge ||\n             (proj->xe8_projectileAttribs & EProjectileAttrib::PartialCharge) == EProjectileAttrib::PartialCharge) {\n    return {act.GetUniqueId(), std::nullopt};\n  } else if ((xe8_projectileAttribs & EProjectileAttrib::PartialCharge) != EProjectileAttrib::PartialCharge &&\n             (proj->xe8_projectileAttribs & EProjectileAttrib::PartialCharge) != EProjectileAttrib::PartialCharge) {\n    return {kInvalidUniqueId, std::nullopt};\n  }\n  return {act.GetUniqueId(), std::nullopt};\n}\n\nCProjectileTouchResult CGameProjectile::CanCollideWithTrigger(const CActor& act, const CStateManager& mgr) const {\n  const bool isWater = TCastToConstPtr<CScriptWater>(act).operator bool();\n  if (isWater) {\n    bool enteredWater = false;\n    if (isWater && !x2e4_25_startedUnderwater) {\n      if (!x170_projectile.GetWeaponDescription()->xa4_EWTR) {\n        enteredWater = true;\n      }\n    }\n    /* This case is logically unreachable */\n    bool leftWater = false;\n    if (!isWater && x2e4_25_startedUnderwater) {\n      if (!x170_projectile.GetWeaponDescription()->xa5_LWTR) {\n        leftWater = true;\n      }\n    }\n    return {(enteredWater || leftWater) ? act.GetUniqueId() : kInvalidUniqueId, std::nullopt};\n  }\n  return {kInvalidUniqueId, std::nullopt};\n}\n\nzeus::CAABox CGameProjectile::GetProjectileBounds() const {\n  return {{std::min(x298_previousPos.x(), GetTranslation().x()) - x2a4_projExtent,\n           std::min(x298_previousPos.y(), GetTranslation().y()) - x2a4_projExtent,\n           std::min(x298_previousPos.z(), GetTranslation().z()) - x2a4_projExtent},\n          {std::max(x298_previousPos.x(), GetTranslation().x()) + x2a4_projExtent,\n           std::max(x298_previousPos.y(), GetTranslation().y()) + x2a4_projExtent,\n           std::max(x298_previousPos.z(), GetTranslation().z()) + x2a4_projExtent}};\n}\n\nstd::optional<zeus::CAABox> CGameProjectile::GetTouchBounds() const {\n  if (x2e4_24_active) {\n    return {GetProjectileBounds()};\n  }\n  return std::nullopt;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CGameProjectile.hpp",
    "content": "#pragma once\n\n#include <optional>\n#include <vector>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Collision/CMaterialList.hpp\"\n#include \"Runtime/Collision/CRayCastResult.hpp\"\n#include \"Runtime/Weapon/CProjectileWeapon.hpp\"\n#include \"Runtime/Weapon/CWeapon.hpp\"\n#include \"Runtime/Weapon/CWeaponMode.hpp\"\n#include \"Runtime/World/CDamageInfo.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CGenDescription;\nclass CWeaponDescription;\n\nclass CProjectileTouchResult {\n  TUniqueId x0_id;\n  std::optional<CRayCastResult> x4_result;\n\npublic:\n  CProjectileTouchResult(TUniqueId id, const std::optional<CRayCastResult>& result) : x0_id(id), x4_result(result) {}\n  TUniqueId GetActorId() const { return x0_id; }\n  bool HasRayCastResult() const { return x4_result.operator bool(); }\n  const CRayCastResult& GetRayCastResult() const { return *x4_result; }\n};\n\nclass CGameProjectile : public CWeapon {\nprotected:\n  std::optional<TLockedToken<CGenDescription>> x158_visorParticle;\n  u16 x168_visorSfx;\n  CProjectileWeapon x170_projectile;\n  zeus::CVector3f x298_previousPos;\n  float x2a4_projExtent;\n  float x2a8_homingDt = 0.03f;\n  double x2b0_targetHomingTime = 0.0;\n  double x2b8_curHomingTime = x2a8_homingDt;\n  TUniqueId x2c0_homingTargetId;\n  TUniqueId x2c2_lastResolvedObj = kInvalidUniqueId;\n  TUniqueId x2c4_hitProjectileOwner = kInvalidUniqueId;\n  TUniqueId x2c6_pendingDamagee = kInvalidUniqueId;\n  TUniqueId x2c8_projectileLight = kInvalidUniqueId;\n  CAssetId x2cc_wpscId;\n  std::vector<CProjectileTouchResult> x2d0_touchResults;\n  float x2e0_minHomingDist = 0.f;\n  bool x2e4_24_active : 1 = true;\n  bool x2e4_25_startedUnderwater : 1;\n  bool x2e4_26_waterUpdate : 1;\n  bool x2e4_27_inWater : 1;\n  bool x2e4_28_sendProjectileCollideMsg : 1;\n\npublic:\n  DEFINE_ENTITY\n  CGameProjectile(bool active, const TToken<CWeaponDescription>&, std::string_view name, EWeaponType wType,\n                  const zeus::CTransform& xf, EMaterialTypes excludeMat, const CDamageInfo& dInfo, TUniqueId uid,\n                  TAreaId aid, TUniqueId owner, TUniqueId homingTarget, EProjectileAttrib attribs, bool underwater,\n                  const zeus::CVector3f& scale, std::optional<TLockedToken<CGenDescription>> visorParticle,\n                  u16 visorSfx, bool sendCollideMsg);\n\n  void Accept(IVisitor& visitor) override;\n  virtual void ResolveCollisionWithActor(const CRayCastResult& res, CActor& act, CStateManager& mgr);\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void Render(CStateManager& mgr) override;\n  static EProjectileAttrib GetBeamAttribType(EWeaponType wType);\n  void DeleteProjectileLight(CStateManager&);\n  void CreateProjectileLight(std::string_view, const CLight&, CStateManager&);\n  void Chase(float dt, CStateManager& mgr);\n  void UpdateHoming(float dt, CStateManager& mgr);\n  void UpdateProjectileMovement(float dt, CStateManager& mgr);\n  CRayCastResult DoCollisionCheck(TUniqueId& idOut, CStateManager& mgr);\n  void ApplyDamageToActors(CStateManager& mgr, const CDamageInfo& dInfo);\n  void FluidFXThink(EFluidState state, CScriptWater& water, CStateManager& mgr) override;\n  CRayCastResult RayCollisionCheckWithWorld(TUniqueId& idOut, const zeus::CVector3f& start, const zeus::CVector3f& end,\n                                            float mag, const EntityList& nearList, CStateManager& mgr);\n  CProjectileTouchResult CanCollideWith(CActor& act, CStateManager& mgr) const;\n  CProjectileTouchResult CanCollideWithComplexCollision(const CActor& act, const CStateManager& mgr) const;\n  CProjectileTouchResult CanCollideWithGameObject(CActor& act, CStateManager& mgr) const;\n  CProjectileTouchResult CanCollideWithTrigger(const CActor& act, const CStateManager& mgr) const;\n  zeus::CAABox GetProjectileBounds() const;\n  std::optional<zeus::CAABox> GetTouchBounds() const override;\n  CProjectileWeapon& ProjectileWeapon() { return x170_projectile; }\n  const CProjectileWeapon& GetProjectileWeapon() const { return x170_projectile; }\n  TUniqueId GetHomingTargetId() const { return x2c0_homingTargetId; }\n  zeus::CVector3f GetPreviousPos() const { return x298_previousPos; }\n  void SetMinHomingDistance(float dist) { x2e0_minHomingDist = dist; }\n  void SetHitProjectileOwner(TUniqueId id) { x2c4_hitProjectileOwner = id; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CGrappleArm.cpp",
    "content": "#include \"Runtime/Weapon/CGrappleArm.hpp\"\n\n#include <array>\n\n#include \"Runtime/CDependencyGroup.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Camera/CGameCamera.hpp\"\n#include \"Runtime/Graphics/CSkinnedModel.hpp\"\n#include \"Runtime/Graphics/CVertexMorphEffect.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nfloat CGrappleArm::g_GrappleBeamAnglePhaseDelta = 0.875f;\nfloat CGrappleArm::g_GrappleBeamXWaveAmplitude = 0.25f;\nfloat CGrappleArm::g_GrappleBeamZWaveAmplitude = 0.125f;\nfloat CGrappleArm::g_GrappleBeamSpeed = 5.f;\n\nCGrappleArm::CGrappleArm(const zeus::CVector3f& scale)\n: x0_grappleArmModel(CAnimRes(g_tweakGunRes->x8_grappleArm, 0, scale, 41, false))\n, xa0_grappleGearModel(CStaticRes(NWeaponTypes::get_asset_id_from_name(\"GrappleGear\"), scale))\n, xec_grapNoz1Model(CStaticRes(NWeaponTypes::get_asset_id_from_name(\"GrapNoz1\"), scale))\n, x138_grapNoz2Model(CStaticRes(NWeaponTypes::get_asset_id_from_name(\"GrapNoz2\"), scale))\n, x184_grappleArm(g_SimplePool->GetObj(SObjectTag{FOURCC('ANCS'), g_tweakGunRes->x8_grappleArm}))\n, x31c_scale(scale)\n, x354_grappleSegmentDesc(g_SimplePool->GetObj(SObjectTag{FOURCC('PART'), g_tweakGunRes->xb4_grappleSegment}))\n, x360_grappleClawDesc(g_SimplePool->GetObj(SObjectTag{FOURCC('PART'), g_tweakGunRes->xb8_grappleClaw}))\n, x36c_grappleHitDesc(g_SimplePool->GetObj(SObjectTag{FOURCC('PART'), g_tweakGunRes->xbc_grappleHit}))\n, x378_grappleMuzzleDesc(g_SimplePool->GetObj(SObjectTag{FOURCC('PART'), g_tweakGunRes->xc0_grappleMuzzle}))\n, x384_grappleSwooshDesc(g_SimplePool->GetObj(SObjectTag{FOURCC('SWHC'), g_tweakGunRes->xc4_grappleSwoosh}))\n, x390_grappleSegmentGen(std::make_unique<CElementGen>(x354_grappleSegmentDesc))\n, x394_grappleClawGen(std::make_unique<CElementGen>(x360_grappleClawDesc))\n, x398_grappleHitGen(std::make_unique<CElementGen>(x36c_grappleHitDesc))\n, x39c_grappleMuzzleGen(std::make_unique<CElementGen>(x378_grappleMuzzleDesc))\n, x3a0_grappleSwooshGen(std::make_unique<CParticleSwoosh>(x384_grappleSwooshDesc, 0))\n, x3a4_rainSplashGenerator(std::make_unique<CRainSplashGenerator>(scale, 20, 2, 0.f, 0.125f)) {\n  x0_grappleArmModel->SetSortThermal(true);\n  xa0_grappleGearModel.SetSortThermal(true);\n  xec_grapNoz1Model.SetSortThermal(true);\n  x138_grapNoz2Model.SetSortThermal(true);\n\n  g_GrappleBeamAnglePhaseDelta = g_tweakPlayer->GetGrappleBeamAnglePhaseDelta();\n  g_GrappleBeamXWaveAmplitude = g_tweakPlayer->GetGrappleBeamXWaveAmplitude();\n  g_GrappleBeamZWaveAmplitude = g_tweakPlayer->GetGrappleBeamZWaveAmplitude();\n  g_GrappleBeamSpeed = g_tweakPlayer->GetGrappleBeamSpeed();\n\n  x39c_grappleMuzzleGen->SetParticleEmission(false);\n  x390_grappleSegmentGen->SetParticleEmission(false);\n  x3a0_grappleSwooshGen->DoGrappleWarmup();\n\n  BuildSuitDependencyList();\n  LoadAnimations();\n}\n\nvoid CGrappleArm::FillTokenVector(const std::vector<SObjectTag>& tags, std::vector<CToken>& objects) {\n  objects.reserve(tags.size());\n  for (const SObjectTag& tag : tags)\n    objects.push_back(g_SimplePool->GetObj(tag));\n}\n\nvoid CGrappleArm::BuildSuitDependencyList() {\n  static constexpr std::array dependencyNames{\n      \"PowerSuit_DGRP\"sv,  \"GravitySuit_DGRP\"sv, \"VariaSuit_DGRP\"sv,   \"PhazonSuit_DGRP\"sv,\n      \"FusionSuit_DGRP\"sv, \"FusionSuitG_DGRP\"sv, \"FusionSuitV_DGRP\"sv, \"FusionSuitP_DGRP\"sv,\n  };\n\n  x184_grappleArm.Lock();\n  for (const auto& name : dependencyNames) {\n    TLockedToken<CDependencyGroup> dgrp = g_SimplePool->GetObj(name);\n    std::vector<CToken>& depsOut = x19c_suitDeps.emplace_back();\n    FillTokenVector(dgrp->GetObjectTagVector(), depsOut);\n  }\n}\n\nvoid CGrappleArm::LoadAnimations() {\n  NWeaponTypes::get_token_vector(*x0_grappleArmModel->GetAnimationData(), 0, 42, x18c_anims, true);\n  x0_grappleArmModel = std::nullopt;\n}\n\nvoid CGrappleArm::AsyncLoadSuit(CStateManager& mgr) {\n  CPlayerState::EPlayerSuit suit = NWeaponTypes::get_current_suit(mgr);\n  if (suit == x3a8_loadedSuit)\n    return;\n\n  x0_grappleArmModel = std::nullopt;\n  x3b2_29_suitLoading = true;\n  if (x3a8_loadedSuit != CPlayerState::EPlayerSuit::Invalid) {\n    NWeaponTypes::unlock_tokens(x19c_suitDeps[int(x3a8_loadedSuit)]);\n    x19c_suitDeps[int(x3a8_loadedSuit)].clear();\n  }\n\n  if (suit < CPlayerState::EPlayerSuit::Power || suit > CPlayerState::EPlayerSuit::FusionPhazon)\n    x3a8_loadedSuit = CPlayerState::EPlayerSuit::Power;\n  else\n    x3a8_loadedSuit = suit;\n\n  NWeaponTypes::lock_tokens(x19c_suitDeps[int(x3a8_loadedSuit)]);\n}\n\nvoid CGrappleArm::ResetAuxParams(bool resetGunController) {\n  x3b2_24_active = false;\n  x3b2_27_armMoving = false;\n  x2e0_auxXf = zeus::CTransform();\n  if (resetGunController)\n    x328_gunController->Reset();\n}\n\nvoid CGrappleArm::DisconnectGrappleBeam() {\n  x394_grappleClawGen->SetParticleEmission(false);\n  x3b2_25_beamActive = false;\n  GrappleBeamDisconnected();\n}\n\nvoid CGrappleArm::SetAnimState(EArmState state) {\n  if (x334_animState == state)\n    return;\n\n  x0_grappleArmModel->GetAnimationData()->EnableLooping(false);\n  x3b2_28_isGrappling = true;\n\n  switch (state) {\n  case EArmState::IntoGrapple: {\n    ResetAuxParams(true);\n    constexpr CAnimPlaybackParms parms(0, -1, 1.f, true);\n    x0_grappleArmModel->GetAnimationData()->SetAnimation(parms, false);\n    x3b2_25_beamActive = false;\n    x3b2_24_active = true;\n    break;\n  }\n  case EArmState::IntoGrappleIdle: {\n    constexpr CAnimPlaybackParms parms(1, -1, 1.f, true);\n    x0_grappleArmModel->GetAnimationData()->EnableLooping(true);\n    x0_grappleArmModel->GetAnimationData()->SetAnimation(parms, false);\n    break;\n  }\n  case EArmState::FireGrapple: {\n    constexpr CAnimPlaybackParms parms(2, -1, 1.f, true);\n    x0_grappleArmModel->GetAnimationData()->SetAnimation(parms, false);\n    break;\n  }\n  case EArmState::ConnectGrapple: {\n    constexpr CAnimPlaybackParms parms(3, -1, 1.f, true);\n    x0_grappleArmModel->GetAnimationData()->SetAnimation(parms, false);\n    break;\n  }\n  case EArmState::Connected: {\n    constexpr CAnimPlaybackParms parms(3, -1, 1.f, true);\n    x0_grappleArmModel->GetAnimationData()->SetAnimation(parms, false);\n    break;\n  }\n  case EArmState::OutOfGrapple: {\n    constexpr CAnimPlaybackParms parms(4, -1, 1.f, true);\n    x0_grappleArmModel->GetAnimationData()->SetAnimation(parms, false);\n    DisconnectGrappleBeam();\n    break;\n  }\n  case EArmState::Done:\n    x3b2_28_isGrappling = false;\n    break;\n  default:\n    break;\n  }\n\n  x334_animState = state;\n}\n\nvoid CGrappleArm::Activate(bool intoGrapple) {\n  SetAnimState(intoGrapple ? EArmState::IntoGrapple : EArmState::OutOfGrapple);\n}\n\nvoid CGrappleArm::GrappleBeamDisconnected() {\n  if (x32c_grappleLoopSfx) {\n    CSfxManager::SfxStop(x32c_grappleLoopSfx);\n    x32c_grappleLoopSfx.reset();\n  }\n}\n\nvoid CGrappleArm::GrappleBeamConnected() {\n  if (!x32c_grappleLoopSfx)\n    x32c_grappleLoopSfx = NWeaponTypes::play_sfx(SFXsam_grapple_lp, false, true, -0.15f);\n}\n\nvoid CGrappleArm::RenderGrappleBeam(const CStateManager& mgr, const zeus::CVector3f& pos) {\n  if (!x3b2_24_active || x3b2_29_suitLoading) {\n    return;\n  }\n\n  const zeus::CTransform tmpXf = zeus::CTransform::Translate(pos) * x220_xf;\n  if (!x3b2_25_beamActive) {\n    return;\n  }\n\n  if (x3b2_26_grappleHit) {\n    x398_grappleHitGen->Render();\n  }\n\n  x394_grappleClawGen->Render();\n  x3a0_grappleSwooshGen->Render();\n  x390_grappleSegmentGen->Render();\n  const zeus::CTransform backupViewMtx = CGraphics::mViewMatrix;\n  CGraphics::SetViewPointMatrix(tmpXf.inverse() * backupViewMtx);\n  CGraphics::SetModelMatrix(zeus::CTransform());\n  x39c_grappleMuzzleGen->Render();\n  CGraphics::SetViewPointMatrix(backupViewMtx);\n}\n\nvoid CGrappleArm::TouchModel(const CStateManager& mgr) {\n  if (!x3b2_24_active || x3b2_29_suitLoading) {\n    return;\n  }\n\n  x0_grappleArmModel->Touch(mgr, 0);\n  if (x50_grappleArmSkeletonModel) {\n    x50_grappleArmSkeletonModel->Touch(mgr, 0);\n  }\n\n  if (mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::GrappleBeam)) {\n    xa0_grappleGearModel.Touch(mgr, 0);\n    xec_grapNoz1Model.Touch(mgr, 0);\n    x138_grapNoz2Model.Touch(mgr, 0);\n  }\n}\n\nvoid CGrappleArm::LoadSuitPoll() {\n  if (!NWeaponTypes::are_tokens_ready(x19c_suitDeps[size_t(x3a8_loadedSuit)])) {\n    return;\n  }\n\n  x0_grappleArmModel.emplace(CAnimRes(g_tweakGunRes->x8_grappleArm, int(x3a8_loadedSuit), x31c_scale, 41, false));\n  x0_grappleArmModel->SetSortThermal(true);\n  x328_gunController = std::make_unique<CGunController>(*x0_grappleArmModel);\n  x3b2_29_suitLoading = false;\n}\n\nvoid CGrappleArm::BuildXRayModel() {\n  x50_grappleArmSkeletonModel.emplace(CAnimRes(g_tweakGunRes->x8_grappleArm, 8, x31c_scale,\n                                               !x328_gunController ? 41 : x328_gunController->GetCurAnimId(), false));\n  x50_grappleArmSkeletonModel->SetSortThermal(true);\n}\n\nvoid CGrappleArm::DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type) {\n  switch (type) {\n  case EUserEventType::Projectile:\n    if (x3b2_27_armMoving)\n      return;\n    x3b2_25_beamActive = true;\n    x398_grappleHitGen = std::make_unique<CElementGen>(x36c_grappleHitDesc);\n    x39c_grappleMuzzleGen = std::make_unique<CElementGen>(x378_grappleMuzzleDesc);\n    x338_beamT = 0.f;\n    x33c_beamDist = 0.f;\n    x340_anglePhase = 0.f;\n    x344_xAmplitude = g_GrappleBeamXWaveAmplitude;\n    x348_zAmplitude = g_GrappleBeamZWaveAmplitude;\n    x398_grappleHitGen->SetParticleEmission(false);\n    x394_grappleClawGen->SetParticleEmission(true);\n    NWeaponTypes::play_sfx(SFXsam_grapple_fire, false, false, -0.15f);\n    mgr.GetRumbleManager().Rumble(mgr, ERumbleFxId::PlayerGrappleFire, 1.f, ERumblePriority::Three);\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CGrappleArm::DoUserAnimEvents(CStateManager& mgr) {\n  zeus::CVector3f armToCam = mgr.GetCameraManager()->GetCurrentCamera(mgr)->GetTranslation() - x220_xf.origin;\n  const CAnimData& animData = *x0_grappleArmModel->GetAnimationData();\n  for (size_t i = 0; i < animData.GetPassedSoundPOICount(); ++i) {\n    const CSoundPOINode& node = CAnimData::g_SoundPOINodes[i];\n    if (node.GetPoiType() != EPOIType::Sound ||\n        (node.GetCharacterIndex() != -1 && animData.x204_charIdx != node.GetCharacterIndex()))\n      continue;\n    NWeaponTypes::do_sound_event(x34c_animSfx, x3ac_pitchBend, false, node.GetSfxId(), node.GetWeight(),\n                                 node.GetFlags(), node.GetFalloff(), node.GetMaxDist(), 0.16f, 1.f, armToCam,\n                                 x220_xf.origin, mgr.GetPlayer().GetAreaIdAlways(), mgr);\n  }\n  for (size_t i = 0; i < animData.GetPassedIntPOICount(); ++i) {\n    const CInt32POINode& node = CAnimData::g_Int32POINodes[i];\n    switch (node.GetPoiType()) {\n    case EPOIType::UserEvent:\n      DoUserAnimEvent(mgr, node, EUserEventType(node.GetValue()));\n      break;\n    case EPOIType::SoundInt32:\n      if (node.GetCharacterIndex() != -1 && animData.x204_charIdx != node.GetCharacterIndex())\n        break;\n      NWeaponTypes::do_sound_event(x34c_animSfx, x3ac_pitchBend, false, u32(node.GetValue()), node.GetWeight(),\n                                   node.GetFlags(), 0.1f, 150.f, 0.16f, 1.f, armToCam, x220_xf.origin,\n                                   mgr.GetPlayer().GetAreaIdAlways(), mgr);\n      break;\n    default:\n      break;\n    }\n  }\n}\n\nvoid CGrappleArm::UpdateArmMovement(float dt, CStateManager& mgr) {\n  DoUserAnimEvents(mgr);\n  if (x328_gunController->Update(dt, mgr))\n    ResetAuxParams(false);\n}\n\nvoid CGrappleArm::UpdateGrappleBeamFx(const zeus::CVector3f& beamGunPos, const zeus::CVector3f& beamAirPos,\n                                      CStateManager& mgr) {\n  x394_grappleClawGen->SetTranslation(beamAirPos);\n  x390_grappleSegmentGen->SetParticleEmission(true);\n\n  zeus::CVector3f segmentDelta = beamAirPos - beamGunPos;\n  zeus::CVector3f swooshSegmentDelta = segmentDelta * 0.02f;\n  int numSegments = int(2.f * segmentDelta.magnitude() + 1.f);\n  segmentDelta = (1.f / float(numSegments)) * segmentDelta;\n\n  zeus::CVector3f segmentPos = beamGunPos;\n  zeus::CTransform rotation = x220_xf.getRotation();\n  for (int i = 0; i < numSegments; ++i) {\n    zeus::CVector3f vec;\n    if (i > 0)\n      vec = rotation *\n            zeus::CVector3f(std::cos(i + x340_anglePhase) * x344_xAmplitude, 0.f, std::sin(float(i)) * x348_zAmplitude);\n    x390_grappleSegmentGen->SetTranslation(vec * segmentPos);\n    x390_grappleSegmentGen->ForceParticleCreation(1);\n    segmentPos += segmentDelta;\n  }\n\n  x390_grappleSegmentGen->SetParticleEmission(false);\n  x3a0_grappleSwooshGen->DoGrappleUpdate(beamGunPos, rotation, x340_anglePhase, x344_xAmplitude, x348_zAmplitude,\n                                         swooshSegmentDelta);\n}\n\nbool CGrappleArm::UpdateGrappleBeam(float dt, const zeus::CTransform& beamLoc, CStateManager& mgr) {\n  bool beamConnected = false;\n  if (TCastToConstPtr<CActor> act = mgr.GetObjectById(mgr.GetPlayer().GetOrbitTargetId()))\n    x310_grapplePointPos = act->GetTranslation();\n  else\n    x310_grapplePointPos = x220_xf.origin;\n\n  zeus::CVector3f beamGunPos = (x220_xf * beamLoc).origin;\n  zeus::CVector3f beamAirPos = beamGunPos * (1.f - x338_beamT) + x310_grapplePointPos * x338_beamT;\n\n  switch (x334_animState) {\n  case EArmState::FireGrapple:\n  case EArmState::Three: {\n    float gunToPointMag = (x310_grapplePointPos - beamGunPos).magnitude();\n    if (gunToPointMag > 0.f)\n      x338_beamT = x33c_beamDist / gunToPointMag;\n    else\n      x338_beamT = 1.f;\n    float speedMult = mgr.GetPlayer().GetPlayerMovementState() != CPlayer::EPlayerMovementState::OnGround ? 2.f : 1.f;\n    x33c_beamDist += speedMult * (dt * g_GrappleBeamSpeed);\n    if (x338_beamT >= 1.f) {\n      x338_beamT = 1.f;\n      beamConnected = true;\n    }\n    break;\n  }\n  case EArmState::ConnectGrapple: {\n    float delta = 4.f * dt;\n    x344_xAmplitude -= delta;\n    x348_zAmplitude -= delta;\n    if (x344_xAmplitude < 0.f)\n      x344_xAmplitude = 0.f;\n    if (x348_zAmplitude < 0.f)\n      x348_zAmplitude = 0.f;\n    break;\n  }\n  default:\n    break;\n  }\n\n  if (x3b2_25_beamActive) {\n    x340_anglePhase += g_GrappleBeamAnglePhaseDelta;\n    UpdateGrappleBeamFx(beamGunPos, beamAirPos, mgr);\n    x394_grappleClawGen->Update(dt);\n    x390_grappleSegmentGen->Update(dt);\n  }\n\n  return beamConnected;\n}\n\nvoid CGrappleArm::UpdateSwingAction(float grappleSwingT, float dt, CStateManager& mgr) {\n  if (x3b2_29_suitLoading)\n    return;\n\n  if (x334_animState == EArmState::FireGrapple)\n    DoUserAnimEvents(mgr);\n\n  zeus::CTransform beamLocXf = x0_grappleArmModel->GetScaledLocatorTransform(\"LGBeam\");\n  bool grappleConnected = UpdateGrappleBeam(dt, beamLocXf, mgr);\n\n  if ((grappleSwingT > 0.175f && grappleSwingT < 0.3f) || (grappleSwingT > 0.7f && grappleSwingT < 0.9f)) {\n    if (!CSfxManager::IsPlaying(x330_swooshSfx)) {\n      x330_swooshSfx = NWeaponTypes::play_sfx(SFXsam_grapple_swoosh, false, false, -0.15f);\n      if (x3b0_rumbleHandle != -1)\n        mgr.GetRumbleManager().StopRumble(x3b0_rumbleHandle);\n      x3b0_rumbleHandle =\n          mgr.GetRumbleManager().Rumble(mgr, ERumbleFxId::PlayerGrappleSwoosh, 1.f, ERumblePriority::Three);\n    }\n  }\n\n  if (!x0_grappleArmModel->GetAnimationData()->IsAnimTimeRemaining(dt, \"Whole Body\")) {\n    switch (x334_animState) {\n    case EArmState::IntoGrapple:\n    case EArmState::Seven:\n      SetAnimState(EArmState::IntoGrappleIdle);\n      break;\n    case EArmState::FireGrapple:\n      if (grappleConnected) {\n        SetAnimState(EArmState::ConnectGrapple);\n        x3b2_26_grappleHit = true;\n        x398_grappleHitGen->SetParticleEmission(true);\n        GrappleBeamConnected();\n        if (x3b0_rumbleHandle != -1)\n          mgr.GetRumbleManager().StopRumble(x3b0_rumbleHandle);\n      }\n      break;\n    case EArmState::ConnectGrapple:\n      if (x344_xAmplitude == 0.f)\n        SetAnimState(EArmState::Connected);\n      break;\n    case EArmState::OutOfGrapple:\n      if (x3b0_rumbleHandle != -1)\n        mgr.GetRumbleManager().StopRumble(x3b0_rumbleHandle);\n      SetAnimState(EArmState::Done);\n      x3b2_24_active = false;\n      break;\n    default:\n      break;\n    }\n  }\n\n  if (x3b2_25_beamActive) {\n    x39c_grappleMuzzleGen->SetTranslation(beamLocXf.origin);\n    x39c_grappleMuzzleGen->Update(dt);\n    if (x3b2_26_grappleHit) {\n      x3b2_26_grappleHit = !x398_grappleHitGen->IsSystemDeletable();\n      x398_grappleHitGen->SetTranslation(x310_grapplePointPos);\n      x398_grappleHitGen->Update(dt);\n    }\n  }\n}\n\nvoid CGrappleArm::Update(float grappleSwingT, float dt, CStateManager& mgr) {\n  if (!(x3b2_24_active && !x3b2_29_suitLoading)) {\n    if (x3b2_29_suitLoading)\n      LoadSuitPoll();\n    return;\n  }\n\n  if (mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::XRay) {\n    if (!x50_grappleArmSkeletonModel)\n      BuildXRayModel();\n  } else {\n    if (x50_grappleArmSkeletonModel)\n      x50_grappleArmSkeletonModel = std::nullopt;\n  }\n\n  float speed = 1.f;\n  if (!x3b2_27_armMoving)\n    speed = (mgr.GetPlayer().GetPlayerMovementState() != CPlayer::EPlayerMovementState::OnGround &&\n             x334_animState != EArmState::OutOfGrapple)\n                ? 4.f\n                : 1.f;\n  x0_grappleArmModel->AdvanceAnimation(speed * dt, mgr, kInvalidAreaId, true);\n  if (mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::GrappleBeam)) {\n    x250_grapLocatorXf = x0_grappleArmModel->GetScaledLocatorTransformDynamic(\"grapLocator_SDK\", nullptr);\n    x280_grapNozLoc1Xf = x0_grappleArmModel->GetScaledLocatorTransform(\"gNozLoc1_SDK\");\n    x2b0_grapNozLoc2Xf = x0_grappleArmModel->GetScaledLocatorTransform(\"gNozLoc1_SDK\");\n  }\n\n  if (x3b2_27_armMoving)\n    UpdateArmMovement(dt, mgr);\n  else\n    UpdateSwingAction(grappleSwingT, dt, mgr);\n\n  if (x3a4_rainSplashGenerator)\n    x3a4_rainSplashGenerator->Update(dt, mgr);\n}\n\nvoid CGrappleArm::PreRender(const CStateManager& mgr, const zeus::CFrustum& frustum, const zeus::CVector3f& camPos) {\n  if (!x3b2_24_active || x3b2_29_suitLoading) {\n    return;\n  }\n\n  x0_grappleArmModel->GetAnimationData()->PreRender();\n  if (x50_grappleArmSkeletonModel) {\n    x50_grappleArmSkeletonModel->GetAnimationData()->PreRender();\n  }\n}\n\nvoid CGrappleArm::RenderXRayModel(const CStateManager& mgr, const zeus::CTransform& modelXf, const CModelFlags& flags) {\n  CGraphics::SetModelMatrix(modelXf * zeus::CTransform::Scale(x0_grappleArmModel->GetScale()));\n  // TODO\n  // CGraphics::DisableAllLights();\n  // g_Renderer->SetAmbientColor(zeus::skWhite);\n  CSkinnedModel& model = *x50_grappleArmSkeletonModel->GetAnimationData()->GetModelData();\n  // model.GetModelInst()->ActivateLights({CLight::BuildLocalAmbient({}, zeus::skWhite)});\n  x0_grappleArmModel->GetAnimationData()->Render(model, flags, nullptr, {});\n  // g_Renderer->SetAmbientColor(zeus::skWhite);\n  // CGraphics::DisableAllLights();\n}\n\nvoid CGrappleArm::Render(const CStateManager& mgr, const zeus::CVector3f& pos, const CModelFlags& flags,\n                         const CActorLights* lights) {\n  if (!x3b2_24_active || x3b2_29_suitLoading) {\n    return;\n  }\n\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CGrappleArm::Render\", zeus::skOrange);\n  const zeus::CTransform modelXf = zeus::CTransform::Translate(pos) * x220_xf * x2e0_auxXf;\n  if (x50_grappleArmSkeletonModel) {\n    RenderXRayModel(mgr, modelXf, flags);\n  }\n\n  CModelFlags useFlags;\n  const CActorLights* useLights;\n  if (mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::XRay) {\n    useFlags = CModelFlags(5, 0, 3, zeus::CColor(1.f, 0.25f));\n    useLights = nullptr;\n  } else {\n    useFlags = flags;\n    useLights = lights;\n  }\n\n  if (x3a4_rainSplashGenerator && x3a4_rainSplashGenerator->IsRaining()) {\n    CSkinnedModel::SetPointGeneratorFunc(\n        [&](const auto& workspace) { x3a4_rainSplashGenerator->GeneratePoints(workspace); });\n  }\n\n  x0_grappleArmModel->Render(mgr, modelXf, useLights, useFlags);\n\n  if (x3a4_rainSplashGenerator && x3a4_rainSplashGenerator->IsRaining()) {\n    CSkinnedModel::ClearPointGeneratorFunc();\n    x3a4_rainSplashGenerator->Draw(modelXf);\n  }\n\n  if (mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::GrappleBeam)) {\n    xa0_grappleGearModel.Render(mgr, modelXf * x250_grapLocatorXf, useLights, useFlags);\n    xec_grapNoz1Model.Render(mgr, modelXf * x280_grapNozLoc1Xf, useLights, useFlags);\n    x138_grapNoz2Model.Render(mgr, modelXf * x2b0_grapNozLoc2Xf, useLights, useFlags);\n  }\n}\n\nvoid CGrappleArm::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) {\n  if (msg == EScriptObjectMessage::Registered)\n    AsyncLoadSuit(mgr);\n}\n\nvoid CGrappleArm::EnterStruck(CStateManager& mgr, float angle, bool bigStrike, bool notInFreeLook) {\n  if (x3b2_29_suitLoading)\n    return;\n\n  if (x3b2_28_isGrappling) {\n    DisconnectGrappleBeam();\n    x3b2_28_isGrappling = false;\n  }\n\n  if (!x3b2_27_armMoving) {\n    x3b2_24_active = true;\n    x3b2_27_armMoving = true;\n    x334_animState = EArmState::GunControllerAnimation;\n  }\n\n  x328_gunController->EnterStruck(mgr, angle, bigStrike, notInFreeLook);\n}\n\nvoid CGrappleArm::EnterIdle(CStateManager& mgr) {\n  if (x3b2_29_suitLoading)\n    return;\n\n  x328_gunController->EnterIdle(mgr);\n}\n\nvoid CGrappleArm::EnterFidget(CStateManager& mgr, SamusGun::EFidgetType type, s32 gunId, s32 animSet) {\n  if (x3b2_29_suitLoading)\n    return;\n\n  x3b2_24_active = true;\n  x3b2_27_armMoving = true;\n  x334_animState = EArmState::GunControllerAnimation;\n\n  x328_gunController->EnterFidget(mgr, s32(type), gunId, animSet);\n}\n\nvoid CGrappleArm::EnterFreeLook(s32 gunId, s32 setId, CStateManager& mgr) {\n  if (x3b2_29_suitLoading)\n    return;\n\n  x3b2_24_active = true;\n  x3b2_27_armMoving = true;\n  x334_animState = EArmState::GunControllerAnimation;\n\n  x328_gunController->EnterFreeLook(mgr, gunId, setId);\n}\n\nvoid CGrappleArm::EnterComboFire(s32 gunId, CStateManager& mgr) {\n  if (x3b2_29_suitLoading)\n    return;\n\n  x3b2_24_active = true;\n  x3b2_27_armMoving = true;\n  x334_animState = EArmState::GunControllerAnimation;\n\n  x328_gunController->EnterComboFire(mgr, gunId);\n}\n\nvoid CGrappleArm::ReturnToDefault(CStateManager& mgr, float dt, bool setState) {\n  if (x3b2_29_suitLoading)\n    return;\n\n  x328_gunController->ReturnToDefault(mgr, dt, setState);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CGrappleArm.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <optional>\n#include <vector>\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Character/CModelData.hpp\"\n#include \"Runtime/Character/CAnimCharacterSet.hpp\"\n#include \"Runtime/Weapon/CGunController.hpp\"\n#include \"Runtime/Weapon/CGunMotion.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CActorLights;\nclass CStateManager;\nstruct CModelFlags;\n\nclass CGrappleArm {\npublic:\n  enum class EArmState {\n    IntoGrapple,\n    IntoGrappleIdle,\n    FireGrapple,\n    Three,\n    ConnectGrapple,\n    Five,\n    Connected,\n    Seven,\n    OutOfGrapple,\n    GunControllerAnimation,\n    Done\n  };\n\nprivate:\n  std::optional<CModelData> x0_grappleArmModel;\n  std::optional<CModelData> x50_grappleArmSkeletonModel;\n  CModelData xa0_grappleGearModel;\n  CModelData xec_grapNoz1Model;\n  CModelData x138_grapNoz2Model;\n  TCachedToken<CAnimCharacterSet> x184_grappleArm;\n  std::vector<CToken> x18c_anims;\n  rstl::reserved_vector<std::vector<CToken>, 8> x19c_suitDeps;\n  zeus::CTransform x220_xf;\n  zeus::CTransform x250_grapLocatorXf;\n  zeus::CTransform x280_grapNozLoc1Xf;\n  zeus::CTransform x2b0_grapNozLoc2Xf;\n  zeus::CTransform x2e0_auxXf;\n  zeus::CVector3f x310_grapplePointPos;\n  zeus::CVector3f x31c_scale;\n  std::unique_ptr<CGunController> x328_gunController;\n  CSfxHandle x32c_grappleLoopSfx;\n  CSfxHandle x330_swooshSfx;\n  EArmState x334_animState = EArmState::Done;\n  float x338_beamT = 0.f;\n  float x33c_beamDist = 0.f;\n  float x340_anglePhase = 0.f;\n  float x344_xAmplitude = 0.f;\n  float x348_zAmplitude = 0.f;\n  std::pair<u16, CSfxHandle> x34c_animSfx = {0xffff, {}};\n  TCachedToken<CGenDescription> x354_grappleSegmentDesc;\n  TCachedToken<CGenDescription> x360_grappleClawDesc;\n  TCachedToken<CGenDescription> x36c_grappleHitDesc;\n  TCachedToken<CGenDescription> x378_grappleMuzzleDesc;\n  TCachedToken<CSwooshDescription> x384_grappleSwooshDesc;\n  std::unique_ptr<CElementGen> x390_grappleSegmentGen;\n  std::unique_ptr<CElementGen> x394_grappleClawGen;\n  std::unique_ptr<CElementGen> x398_grappleHitGen;\n  std::unique_ptr<CElementGen> x39c_grappleMuzzleGen;\n  std::unique_ptr<CParticleSwoosh> x3a0_grappleSwooshGen;\n  std::unique_ptr<CRainSplashGenerator> x3a4_rainSplashGenerator;\n  CPlayerState::EPlayerSuit x3a8_loadedSuit = CPlayerState::EPlayerSuit::Invalid;\n  float x3ac_pitchBend = 0.f;\n  s16 x3b0_rumbleHandle = -1;\n  bool x3b2_24_active : 1 = false;\n  bool x3b2_25_beamActive : 1 = false;\n  bool x3b2_26_grappleHit : 1 = false;\n  bool x3b2_27_armMoving : 1 = false;\n  bool x3b2_28_isGrappling : 1 = false;\n  bool x3b2_29_suitLoading : 1 = false;\n\n  static float g_GrappleBeamAnglePhaseDelta;\n  static float g_GrappleBeamXWaveAmplitude;\n  static float g_GrappleBeamZWaveAmplitude;\n  static float g_GrappleBeamSpeed;\n\n  void FillTokenVector(const std::vector<SObjectTag>& tags, std::vector<CToken>& objects);\n  void BuildSuitDependencyList();\n  void LoadAnimations();\n  void ResetAuxParams(bool resetGunController);\n  void DisconnectGrappleBeam();\n  void LoadSuitPoll();\n  void BuildXRayModel();\n  void DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type);\n  void DoUserAnimEvents(CStateManager& mgr);\n  void UpdateArmMovement(float dt, CStateManager& mgr);\n  void UpdateGrappleBeamFx(const zeus::CVector3f& beamGunPos, const zeus::CVector3f& beamAirPos, CStateManager& mgr);\n  bool UpdateGrappleBeam(float dt, const zeus::CTransform& beamLoc, CStateManager& mgr);\n  void UpdateSwingAction(float grappleSwingT, float dt, CStateManager& mgr);\n  void RenderXRayModel(const CStateManager& mgr, const zeus::CTransform& modelXf, const CModelFlags& flags);\n\npublic:\n  explicit CGrappleArm(const zeus::CVector3f& scale);\n  void AsyncLoadSuit(CStateManager& mgr);\n  void SetTransform(const zeus::CTransform& xf) { x220_xf = xf; }\n  const zeus::CTransform& GetTransform() const { return x220_xf; }\n  zeus::CTransform& AuxTransform() { return x2e0_auxXf; }\n  void SetAnimState(EArmState state);\n  EArmState GetAnimState() const { return x334_animState; }\n  bool GetActive() const { return x3b2_24_active; }\n  bool BeamActive() const { return x3b2_25_beamActive; }\n  bool IsArmMoving() const { return x3b2_27_armMoving; }\n  bool IsGrappling() const { return x3b2_28_isGrappling; }\n  bool IsSuitLoading() const { return x3b2_29_suitLoading; }\n  void Activate(bool);\n  void GrappleBeamDisconnected();\n  void GrappleBeamConnected();\n  void RenderGrappleBeam(const CStateManager& mgr, const zeus::CVector3f& pos);\n  void TouchModel(const CStateManager& mgr);\n  void Update(float grappleSwingT, float dt, CStateManager& mgr);\n  void PreRender(const CStateManager& mgr, const zeus::CFrustum& frustum, const zeus::CVector3f& camPos);\n  void Render(const CStateManager& mgr, const zeus::CVector3f& pos, const CModelFlags& flags,\n              const CActorLights* lights);\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&);\n  void EnterStruck(CStateManager& mgr, float angle, bool bigStrike, bool notInFreeLook);\n  void EnterIdle(CStateManager& mgr);\n  void EnterFidget(CStateManager& mgr, SamusGun::EFidgetType type, s32 gunId, s32 animSet);\n  void EnterFreeLook(s32 gunId, s32 setId, CStateManager& mgr);\n  void EnterComboFire(s32 gunId, CStateManager& mgr);\n  void ReturnToDefault(CStateManager& mgr, float dt, bool setState);\n  CGunController* GunController() { return x328_gunController.get(); }\n  const CGunController* GunController() const { return x328_gunController.get(); }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CGunController.cpp",
    "content": "#include \"Runtime/Weapon/CGunController.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Character/CCharLayoutInfo.hpp\"\n#include \"Runtime/Character/CPASAnimParmData.hpp\"\n\nnamespace metaforce {\n\nvoid CGunController::LoadFidgetAnimAsync(CStateManager& mgr, s32 type, s32 gunId, s32 animSet) {\n  x30_fidget.LoadAnimAsync(*x0_modelData.GetAnimationData(), type, gunId, animSet, mgr);\n}\n\nvoid CGunController::EnterFidget(CStateManager& mgr, s32 type, s32 gunId, s32 animSet) {\n  x54_curAnimId = x30_fidget.SetAnim(*x0_modelData.GetAnimationData(), type, gunId, animSet, mgr);\n  x50_gunState = EGunState::Fidget;\n}\n\nvoid CGunController::EnterFreeLook(CStateManager& mgr, s32 gunId, s32 setId) {\n  if (x50_gunState != EGunState::ComboFire && !x58_25_enteredComboFire)\n    x54_curAnimId = x4_freeLook.SetAnim(*x0_modelData.GetAnimationData(), gunId, setId, 0, mgr, 0.f);\n  else\n    x4_freeLook.SetLoopState(x1c_comboFire.GetLoopState());\n  x50_gunState = EGunState::FreeLook;\n}\n\nvoid CGunController::EnterComboFire(CStateManager& mgr, s32 gunId) {\n  if (x50_gunState != EGunState::FreeLook)\n    x54_curAnimId = x1c_comboFire.SetAnim(*x0_modelData.GetAnimationData(), gunId, 0, mgr, 0.f);\n  else\n    x1c_comboFire.SetLoopState(x4_freeLook.GetLoopState());\n  x50_gunState = EGunState::ComboFire;\n  x58_25_enteredComboFire = true;\n}\n\nvoid CGunController::EnterStruck(CStateManager& mgr, float angle, bool bigStrike, bool b2) {\n  switch (x50_gunState) {\n  case EGunState::FreeLook:\n    x4_freeLook.SetIdle(true);\n    break;\n  case EGunState::Inactive:\n  case EGunState::Fidget:\n    break;\n  default:\n    return;\n  }\n\n  const CPASAnimParmData aparam =\n      CPASAnimParmData(pas::EAnimationState::LieOnGround, CPASAnimParm::FromInt32(x4_freeLook.GetGunId()),\n                       CPASAnimParm::FromReal32(angle), CPASAnimParm::FromBool(bigStrike), CPASAnimParm::FromBool(b2));\n  CAnimData& animData = *x0_modelData.GetAnimationData();\n  const std::pair<float, int> anim =\n      animData.GetCharacterInfo().GetPASDatabase().FindBestAnimation(aparam, *mgr.GetActiveRandom(), -1);\n  animData.EnableLooping(false);\n  animData.SetAnimation(CAnimPlaybackParms(anim.second, -1, 1.f, true), false);\n  x54_curAnimId = anim.second;\n  x50_gunState = bigStrike ? EGunState::BigStrike : EGunState::Strike;\n}\n\nvoid CGunController::EnterIdle(CStateManager& mgr) {\n  CPASAnimParm parm = CPASAnimParm::NoParameter();\n  switch (x50_gunState) {\n  case EGunState::FreeLook:\n    parm = CPASAnimParm::FromEnum(1);\n    x4_freeLook.SetIdle(true);\n    break;\n  case EGunState::ComboFire:\n    parm = CPASAnimParm::FromEnum(1);\n    x1c_comboFire.SetIdle(true);\n    break;\n  default:\n    return;\n  }\n\n  const CPASDatabase& pasDatabase = x0_modelData.GetAnimationData()->GetCharacterInfo().GetPASDatabase();\n  CPASAnimParmData parms(pas::EAnimationState::Locomotion, parm);\n  std::pair<float, s32> anim = pasDatabase.FindBestAnimation(parms, *mgr.GetActiveRandom(), -1);\n  x0_modelData.GetAnimationData()->EnableLooping(false);\n  CAnimPlaybackParms aparms(anim.second, -1, 1.f, true);\n  x0_modelData.GetAnimationData()->SetAnimation(aparms, false);\n  x54_curAnimId = anim.second;\n  x50_gunState = EGunState::Idle;\n  x58_25_enteredComboFire = false;\n}\n\nbool CGunController::Update(float dt, CStateManager& mgr) {\n  CAnimData& animData = *x0_modelData.GetAnimationData();\n  switch (x50_gunState) {\n  case EGunState::FreeLook: {\n    x58_24_animDone = x4_freeLook.Update(animData, dt, mgr);\n    if (!x58_24_animDone || !x58_25_enteredComboFire)\n      break;\n\n    EnterComboFire(mgr, x4_freeLook.GetGunId());\n    x58_24_animDone = false;\n    break;\n  }\n  case EGunState::ComboFire:\n    x58_24_animDone = x1c_comboFire.Update(animData, dt, mgr);\n    break;\n  case EGunState::Fidget:\n    x58_24_animDone = x30_fidget.Update(animData, dt, mgr);\n    break;\n  case EGunState::Strike: {\n    if (animData.IsAnimTimeRemaining(0.001f, \"Whole Body\"))\n      break;\n    x54_curAnimId = x4_freeLook.SetAnim(animData, x4_freeLook.GetGunId(), x4_freeLook.GetSetId(), 0, mgr, 0.f);\n    x50_gunState = EGunState::FreeLook;\n    break;\n  }\n  case EGunState::BigStrike:\n    x58_24_animDone = !animData.IsAnimTimeRemaining(0.001f, \"Whole Body\");\n    break;\n  default:\n    break;\n  }\n\n  if (!x58_24_animDone)\n    return false;\n\n  x50_gunState = EGunState::Inactive;\n  x58_25_enteredComboFire = false;\n\n  return true;\n}\n\nvoid CGunController::ReturnToDefault(CStateManager& mgr, float dt, bool setState) {\n  CAnimData& animData = *x0_modelData.GetAnimationData();\n\n  switch (x50_gunState) {\n  case EGunState::Strike:\n    x50_gunState = EGunState::FreeLook;\n    [[fallthrough]];\n  case EGunState::Idle:\n    x4_freeLook.SetIdle(false);\n    [[fallthrough]];\n  case EGunState::FreeLook:\n    if (setState)\n      break;\n    x54_curAnimId = x4_freeLook.SetAnim(animData, x4_freeLook.GetGunId(), x4_freeLook.GetSetId(), 2, mgr, dt);\n    x58_25_enteredComboFire = false;\n    break;\n  case EGunState::ComboFire:\n    x54_curAnimId = x1c_comboFire.SetAnim(animData, x1c_comboFire.GetGunId(), 2, mgr, dt);\n    break;\n  case EGunState::Fidget:\n    ReturnToBasePosition(mgr, dt);\n    break;\n  case EGunState::BigStrike:\n    x4_freeLook.SetIdle(false);\n    break;\n  default:\n    break;\n  }\n\n  if (setState)\n    x50_gunState = EGunState::Default;\n}\n\nvoid CGunController::ReturnToBasePosition(CStateManager& mgr, float) {\n  const CPASDatabase& pasDatabase = x0_modelData.GetAnimationData()->GetCharacterInfo().GetPASDatabase();\n  std::pair<float, s32> anim =\n      pasDatabase.FindBestAnimation(CPASAnimParmData(pas::EAnimationState::KnockBack), *mgr.GetActiveRandom(), -1);\n  x0_modelData.GetAnimationData()->EnableLooping(false);\n  CAnimPlaybackParms parms(anim.second, -1, 1.f, true);\n  x0_modelData.GetAnimationData()->SetAnimation(parms, false);\n  x54_curAnimId = anim.second;\n  x58_25_enteredComboFire = false;\n}\n\nvoid CGunController::Reset() {\n  x58_24_animDone = true;\n  x58_25_enteredComboFire = false;\n  x50_gunState = EGunState::Inactive;\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CGunController.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Character/CModelData.hpp\"\n#include \"Runtime/Weapon/CGSComboFire.hpp\"\n#include \"Runtime/Weapon/CGSFidget.hpp\"\n#include \"Runtime/Weapon/CGSFreeLook.hpp\"\n\nnamespace metaforce {\nenum class EGunState { Inactive, Default, FreeLook, ComboFire, Idle, Fidget, Strike, BigStrike };\n\nclass CGunController {\n  CModelData& x0_modelData;\n  CGSFreeLook x4_freeLook;\n  CGSComboFire x1c_comboFire;\n  CGSFidget x30_fidget;\n  EGunState x50_gunState = EGunState::Inactive;\n  s32 x54_curAnimId = -1;\n  bool x58_24_animDone : 1 = true;\n  bool x58_25_enteredComboFire : 1 = false;\n\npublic:\n  explicit CGunController(CModelData& modelData) : x0_modelData(modelData) {}\n\n  void UnLoadFidget() { x30_fidget.UnLoadAnim(); }\n  void LoadFidgetAnimAsync(CStateManager& mgr, s32 type, s32 gunId, s32 animSet);\n  void EnterFidget(CStateManager& mgr, s32 type, s32 gunId, s32 animSet);\n  bool IsFidgetLoaded() const { return x30_fidget.IsAnimLoaded(); }\n  s32 GetFreeLookSetId() const { return x4_freeLook.GetSetId(); }\n  bool IsComboOver() const { return x1c_comboFire.IsComboOver(); }\n  void EnterFreeLook(CStateManager& mgr, s32 gunId, s32 setId);\n  void EnterComboFire(CStateManager& mgr, s32 gunId);\n  void EnterStruck(CStateManager& mgr, float angle, bool bigStrike, bool b2);\n  void EnterIdle(CStateManager& mgr);\n  bool Update(float dt, CStateManager& mgr);\n  void ReturnToDefault(CStateManager& mgr, float dt, bool setState);\n  void ReturnToBasePosition(CStateManager&, float);\n  void Reset();\n  s32 GetCurAnimId() const { return x54_curAnimId; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CGunMotion.cpp",
    "content": "#include \"Runtime/Weapon/CGunMotion.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Character/CPASAnimParmData.hpp\"\n#include \"Runtime/Weapon/WeaponCommon.hpp\"\n\nnamespace metaforce {\n\nCGunMotion::CGunMotion(CAssetId ancsId, const zeus::CVector3f& scale)\n: x0_modelData(CAnimRes(ancsId, 0, scale, 0, false))/*, 1*/, x4c_gunController(x0_modelData) {\n  LoadAnimations();\n}\n\nvoid CGunMotion::LoadAnimations() {\n  NWeaponTypes::get_token_vector(*x0_modelData.GetAnimationData(), 0, 14, xa8_anims, true);\n}\n\nbool CGunMotion::PlayPasAnim(SamusGun::EAnimationState state, CStateManager& mgr, float angle, bool bigStrike) {\n  const CPASDatabase& pas = x0_modelData.GetAnimationData()->GetCharacterInfo().GetPASDatabase();\n\n  s32 animId = -1;\n  bool loop = true;\n  switch (state) {\n  case SamusGun::EAnimationState::Wander: {\n    CPASAnimParmData parms((pas::EAnimationState(state)));\n    auto anim = pas.FindBestAnimation(parms, *mgr.GetActiveRandom(), -1);\n    animId = anim.second;\n    break;\n  }\n  case SamusGun::EAnimationState::Idle: {\n    CPASAnimParmData parms(pas::EAnimationState(state), CPASAnimParm::FromEnum(0));\n    auto anim = pas.FindBestAnimation(parms, *mgr.GetActiveRandom(), -1);\n    animId = anim.second;\n    break;\n  }\n  case SamusGun::EAnimationState::Struck: {\n    CPASAnimParmData parms(pas::EAnimationState(state), CPASAnimParm::FromInt32(0), CPASAnimParm::FromReal32(angle),\n                           CPASAnimParm::FromBool(bigStrike), CPASAnimParm::FromBool(false));\n    auto anim = pas.FindBestAnimation(parms, *mgr.GetActiveRandom(), -1);\n    animId = anim.second;\n    loop = false;\n    break;\n  }\n  case SamusGun::EAnimationState::FreeLook:\n    x4c_gunController.EnterFreeLook(mgr, 0, -1);\n    break;\n  case SamusGun::EAnimationState::ComboFire:\n    x4c_gunController.EnterComboFire(mgr, 0);\n    break;\n  default:\n    break;\n  }\n\n  if (animId != -1) {\n    x0_modelData.GetAnimationData()->EnableLooping(loop);\n    CAnimPlaybackParms aparms(animId, -1, 1.f, true);\n    x0_modelData.GetAnimationData()->SetAnimation(aparms, false);\n  }\n\n  return loop;\n}\n\nvoid CGunMotion::ReturnToDefault(CStateManager& mgr, bool setState) {\n  x4c_gunController.ReturnToDefault(mgr, 0.f, setState);\n}\n\nvoid CGunMotion::BasePosition(bool bigStrikeReset) {\n  x0_modelData.GetAnimationData()->EnableLooping(false);\n  CAnimPlaybackParms aparms(bigStrikeReset ? 6 : 0, -1, 1.f, true);\n  x0_modelData.GetAnimationData()->SetAnimation(aparms, false);\n}\n\nvoid CGunMotion::EnterFidget(CStateManager& mgr, SamusGun::EFidgetType type, s32 parm2) {\n  xb8_24_animPlaying = true;\n  x4c_gunController.EnterFidget(mgr, s32(type), 0, parm2);\n}\n\nvoid CGunMotion::Update(float dt, CStateManager& mgr) {\n  x0_modelData.AdvanceAnimation(dt, mgr, kInvalidAreaId, true);\n  if (x4c_gunController.Update(dt, mgr))\n    xb8_24_animPlaying = false;\n}\n\nvoid CGunMotion::Draw(const CStateManager& mgr, const zeus::CTransform& xf) {\n  constexpr CModelFlags flags(0, 0, 3, zeus::skWhite);\n  x0_modelData.Render(mgr, xf, nullptr, flags);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CGunMotion.hpp",
    "content": "#pragma once\n\n#include <vector>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Character/CModelData.hpp\"\n#include \"Runtime/Weapon/CGunController.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\n\nnamespace SamusGun {\nenum class EAnimationState { Wander, Fidget, Struck, FreeLook, ComboFire, Idle, BasePosition };\nenum class EFidgetType { Invalid = -1, Minor, Major };\n} // namespace SamusGun\n\nclass CGunMotion {\n  CModelData x0_modelData;\n  CGunController x4c_gunController;\n  std::vector<CToken> xa8_anims;\n  bool xb8_24_animPlaying : 1 = false;\n\n  void LoadAnimations();\n\npublic:\n  CGunMotion(CAssetId ancsId, const zeus::CVector3f& scale);\n  CModelData& GetModelData() { return x0_modelData; }\n  const CModelData& GetModelData() const { return x0_modelData; }\n  bool PlayPasAnim(SamusGun::EAnimationState state, CStateManager& mgr, float angle, bool bigStrike);\n  void ReturnToDefault(CStateManager& mgr, bool setState);\n  void BasePosition(bool bigStrikeReset);\n  void EnterFidget(CStateManager& mgr, SamusGun::EFidgetType type, s32 parm2);\n  void Update(float dt, CStateManager& mgr);\n  void Draw(const CStateManager& mgr, const zeus::CTransform& xf);\n  s32 GetFreeLookSetId() const { return x4c_gunController.GetFreeLookSetId(); }\n  CGunController& GunController() { return x4c_gunController; }\n  bool IsAnimPlaying() const { return xb8_24_animPlaying; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CGunWeapon.cpp",
    "content": "#include \"Runtime/Weapon/CGunWeapon.hpp\"\n\n#include <algorithm>\n#include <array>\n\n#include \"Runtime/CDependencyGroup.hpp\"\n#include \"Runtime/CGameState.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Graphics/CSkinnedModel.hpp\"\n#include \"Runtime/Graphics/CVertexMorphEffect.hpp\"\n#include \"Runtime/Weapon/CEnergyProjectile.hpp\"\n#include \"Runtime/Weapon/CWeapon.hpp\"\n\nnamespace metaforce {\nnamespace {\nconstexpr std::array skBeamXferNames{\n    \"PowerXfer\", \"IceXfer\", \"WaveXfer\", \"PlasmaXfer\", \"PhazonXfer\",\n};\n\nconstexpr std::array skSuitArmNames{\n    \"PowerArm\", \"GravityArm\", \"VariaArm\", \"PhazonArm\", \"FusionArm\", \"FusionArmG\", \"FusionArmV\", \"FusionArmP\",\n};\n\nconstexpr std::array skMuzzleNames{\n    \"PowerMuzzle\", \"PowerCharge\",  \"IceMuzzle\",    \"IceCharge\",    \"PowerMuzzle\",\n    \"WaveCharge\",  \"PlasmaMuzzle\", \"PlasmaCharge\", \"PhazonMuzzle\", \"EmptyMuzzle\",\n};\n\nconstexpr std::array skFrozenNames{\n    \"powerFrozen\", \"Ice2nd_2\",     \"iceFrozen\", \"Ice2nd_2\",  \"waveFrozen\",\n    \"Ice2nd_2\",    \"plasmaFrozen\", \"Ice2nd_2\",  \"iceFrozen\", \"Ice2nd_2\",\n};\n\nconstexpr std::array skDependencyNames{\n    \"Power_DGRP\", \"Ice_DGRP\", \"Wave_DGRP\", \"Plasma_DGRP\", \"Phazon_DGRP\",\n};\n\nconstexpr std::array skAnimDependencyNames{\n    \"Power_Anim_DGRP\", \"Ice_Anim_DGRP\", \"Wave_Anim_DGRP\", \"Plasma_Anim_DGRP\", \"Phazon_Anim_DGRP\",\n};\n\nconstexpr std::array skAnimTypeList{\n    0, 4, 1, 2, 3, 5, 6, 7, 8, 9, 10,\n};\n\nCPlayerState::EBeamId GetWeaponIndex(EWeaponType type) {\n  if (type == EWeaponType::Power)\n    return CPlayerState::EBeamId::Power;\n  else if (type == EWeaponType::Ice)\n    return CPlayerState::EBeamId::Ice;\n  else if (type == EWeaponType::Wave)\n    return CPlayerState::EBeamId::Wave;\n  else if (type == EWeaponType::Plasma)\n    return CPlayerState::EBeamId::Plasma;\n  else if (type == EWeaponType::Phazon)\n    return CPlayerState::EBeamId::Phazon;\n  return CPlayerState::EBeamId::Power;\n}\n} // Anonymous namespace\n\nCGunWeapon::CGunWeapon(CAssetId ancsId, EWeaponType type, TUniqueId playerId, EMaterialTypes playerMaterial,\n                       const zeus::CVector3f& scale)\n: x4_scale(scale)\n, x104_gunCharacter(g_SimplePool->GetObj(SObjectTag{FOURCC('ANCS'), ancsId}))\n, x13c_armCharacter(g_SimplePool->GetObj(skSuitArmNames[0]))\n, x160_xferEffect(g_SimplePool->GetObj(skBeamXferNames[size_t(GetWeaponIndex(type))]))\n, x1c0_weaponType(type)\n, x1c4_playerId(playerId)\n, x1c8_playerMaterial(playerMaterial)\n, x200_beamId(GetWeaponIndex(type))\n, x20c_shaderIdx(u32(x200_beamId))\n, x214_ancsId(ancsId) {\n  AllocResPools(x200_beamId);\n  BuildDependencyList(x200_beamId);\n}\n\nCGunWeapon::~CGunWeapon() = default;\n\nvoid CGunWeapon::AllocResPools(CPlayerState::EBeamId beam) {\n  const auto& wPair = g_tweakGunRes->GetWeaponPair(beam);\n  const char* const* muzzleNames = &skMuzzleNames[size_t(beam) * 2];\n  const char* const* frozenNames = &skFrozenNames[size_t(beam) * 2];\n\n  for (size_t i = 0; i < x16c_muzzleEffects.capacity(); ++i) {\n    x16c_muzzleEffects.push_back(g_SimplePool->GetObj(muzzleNames[i]));\n    x144_weapons.push_back(g_SimplePool->GetObj(SObjectTag{FOURCC('WPSC'), wPair[i]}));\n    x188_frozenEffects.push_back(g_SimplePool->GetObj(frozenNames[i]));\n  }\n}\n\nvoid CGunWeapon::FreeResPools() {\n  x160_xferEffect.Unlock();\n\n  for (size_t i = 0; i < x16c_muzzleEffects.size(); ++i) {\n    x16c_muzzleEffects[i].Unlock();\n    x144_weapons[i].Unlock();\n    x188_frozenEffects[i].Unlock();\n  }\n\n  x10c_anims.clear();\n  x1a4_muzzleGenerators.clear();\n  x1d0_velInfo.Clear();\n}\n\nvoid CGunWeapon::FillTokenVector(const std::vector<SObjectTag>& tags, std::vector<CToken>& objects) {\n  for (const SObjectTag& tag : tags)\n    objects.push_back(g_SimplePool->GetObj(tag));\n}\n\nvoid CGunWeapon::BuildDependencyList(CPlayerState::EBeamId beam) {\n  TLockedToken<CDependencyGroup> deps = g_SimplePool->GetObj(skDependencyNames[size_t(beam)]);\n  TLockedToken<CDependencyGroup> animDeps = g_SimplePool->GetObj(skAnimDependencyNames[size_t(beam)]);\n  x12c_deps.reserve(deps->GetObjectTagVector().size() + animDeps->GetObjectTagVector().size());\n  FillTokenVector(deps->GetObjectTagVector(), x12c_deps);\n  FillTokenVector(animDeps->GetObjectTagVector(), x12c_deps);\n}\n\nvoid CGunWeapon::AsyncLoadSuitArm(CStateManager& mgr) {\n\n  xb0_suitArmModelData = std::nullopt;\n  x13c_armCharacter = g_SimplePool->GetObj(skSuitArmNames[size_t(NWeaponTypes::get_current_suit(mgr))]);\n  x13c_armCharacter.Lock();\n  x218_28_suitArmLocked = true;\n}\n\nvoid CGunWeapon::Reset(CStateManager& mgr) {\n  if (!x218_26_loaded)\n    return;\n\n  x10_solidModelData->GetAnimationData()->EnableLooping(false);\n  if (x218_25_enableCharge)\n    x218_25_enableCharge = false;\n  else\n    x100_gunController->Reset();\n}\n\nvoid CGunWeapon::PlayAnim(NWeaponTypes::EGunAnimType type, bool loop) {\n  if (!x218_26_loaded || type < NWeaponTypes::EGunAnimType::BasePosition || type > NWeaponTypes::EGunAnimType::ToBeam) {\n    return;\n  }\n\n  x10_solidModelData->GetAnimationData()->EnableLooping(loop);\n  const CAnimPlaybackParms parms(skAnimTypeList[size_t(type)], -1, 1.f, true);\n  x10_solidModelData->GetAnimationData()->SetAnimation(parms, false);\n}\n\nvoid CGunWeapon::PreRenderGunFx(const CStateManager& mgr, const zeus::CTransform& xf) {\n  // Empty\n}\n\nvoid CGunWeapon::PostRenderGunFx(const CStateManager& mgr, const zeus::CTransform& xf) {\n  if (x218_26_loaded && x1b8_frozenGenerator && x204_frozenEffect != EFrozenFxType::None)\n    x1b8_frozenGenerator->Render();\n}\n\nvoid CGunWeapon::UpdateGunFx(bool shotSmoke, float dt, const CStateManager& mgr, const zeus::CTransform& xf) {\n  if (x218_26_loaded && x204_frozenEffect != EFrozenFxType::None) {\n    if (x204_frozenEffect == EFrozenFxType::Thawed) {\n      if (x1b8_frozenGenerator->IsSystemDeletable()) {\n        x204_frozenEffect = EFrozenFxType::None;\n        x1b8_frozenGenerator.reset();\n      } else {\n        x1b8_frozenGenerator->SetTranslation(xf.origin);\n        x1b8_frozenGenerator->SetOrientation(xf.getRotation());\n      }\n    } else {\n      x1b8_frozenGenerator->SetGlobalOrientAndTrans(xf);\n    }\n    if (x1b8_frozenGenerator)\n      x1b8_frozenGenerator->Update(dt);\n  }\n}\n\nconstexpr std::array<s32, 2> CGunWeapon::skShootAnim{4, 3};\n\nvoid CGunWeapon::Fire(bool underwater, float dt, EChargeState chargeState, const zeus::CTransform& xf,\n                      CStateManager& mgr, TUniqueId homingTarget, float chargeFactor1, float chargeFactor2) {\n  //OPTICK_EVENT();\n  CDamageInfo dInfo = GetDamageInfo(mgr, chargeState, chargeFactor1);\n  zeus::CVector3f scale(chargeState == EChargeState::Normal ? 1.f : chargeFactor2);\n  bool partialCharge = chargeState == EChargeState::Normal ? false : !zeus::close_enough(chargeFactor1, 1.f);\n  EProjectileAttrib attribs = EProjectileAttrib::ArmCannon;\n  if (partialCharge)\n    attribs |= EProjectileAttrib::PartialCharge;\n  if (chargeState == EChargeState::Charged)\n    attribs |= EProjectileAttrib::Charged;\n\n  CEnergyProjectile* proj = new CEnergyProjectile(\n      true, x144_weapons[int(chargeState)], x1c0_weaponType, xf, x1c8_playerMaterial, dInfo, mgr.AllocateUniqueId(),\n      kInvalidAreaId, x1c4_playerId, homingTarget, attribs, underwater, scale, {}, -1, false);\n  mgr.AddObject(proj);\n  proj->Think(dt, mgr);\n\n  if (chargeState == EChargeState::Charged) {\n    x218_25_enableCharge = true;\n    mgr.GetCameraManager()->AddCameraShaker(CCameraShakeData::skChargedShotCameraShakeData, false);\n  }\n\n  x10_solidModelData->GetAnimationData()->EnableLooping(false);\n  CAnimPlaybackParms parms(skShootAnim[int(chargeState)], -1, 1.f, true);\n  x10_solidModelData->GetAnimationData()->SetAnimation(parms, false);\n}\n\nvoid CGunWeapon::EnableFx(bool enable) {\n  // Empty\n}\n\nvoid CGunWeapon::EnableSecondaryFx(ESecondaryFxType type) { x1cc_enabledSecondaryEffect = type; }\n\nvoid CGunWeapon::EnableFrozenEffect(EFrozenFxType type) {\n  switch (type) {\n  case EFrozenFxType::Thawed:\n    if (x204_frozenEffect == EFrozenFxType::Thawed)\n      break;\n    x1b8_frozenGenerator = std::make_unique<CElementGen>(x188_frozenEffects[1]);\n    x1b8_frozenGenerator->SetGlobalScale(x4_scale);\n    break;\n  case EFrozenFxType::Frozen:\n    if (x204_frozenEffect == EFrozenFxType::Frozen)\n      break;\n    x1b8_frozenGenerator = std::make_unique<CElementGen>(x188_frozenEffects[0]);\n    x1b8_frozenGenerator->SetGlobalScale(x4_scale);\n    break;\n  default:\n    break;\n  }\n  x204_frozenEffect = type;\n}\n\nvoid CGunWeapon::ActivateCharge(bool enable, bool resetEffect) {\n  x1a4_muzzleGenerators[x208_muzzleEffectIdx]->SetParticleEmission(false);\n  x208_muzzleEffectIdx = u32(enable);\n  if (enable || resetEffect) {\n    x1a4_muzzleGenerators[x208_muzzleEffectIdx] =\n        std::make_unique<CElementGen>(x16c_muzzleEffects[x208_muzzleEffectIdx]);\n  }\n}\n\nvoid CGunWeapon::Touch(const CStateManager& mgr) {\n  if (x10_solidModelData) {\n    x10_solidModelData->Touch(mgr, x20c_shaderIdx);\n    if (xb0_suitArmModelData)\n      xb0_suitArmModelData->Touch(mgr, 0);\n  }\n}\n\nvoid CGunWeapon::TouchHolo(const CStateManager& mgr) {\n  if (x60_holoModelData)\n    x60_holoModelData->Touch(mgr, 0);\n}\n\nvoid CGunWeapon::Draw(bool drawSuitArm, const CStateManager& mgr, const zeus::CTransform& xf, const CModelFlags& flags,\n                      const CActorLights* lights) {\n  if (!x218_26_loaded)\n    return;\n\n  zeus::CTransform armXf = xf * x10_solidModelData->GetScaledLocatorTransform(\"elbow\");\n\n  if (x1bc_rainSplashGenerator && x1bc_rainSplashGenerator->IsRaining())\n    CSkinnedModel::SetPointGeneratorFunc(\n        [&](const auto& workspace) { x1bc_rainSplashGenerator->GeneratePoints(workspace); });\n\n  if (mgr.GetThermalDrawFlag() == EThermalDrawFlag::Hot && x200_beamId != CPlayerState::EBeamId::Ice) {\n    /* Hot Draw */\n    const zeus::CColor mulColor(flags.x4_color.a(), flags.x4_color.a());\n    constexpr zeus::CColor addColor(0.25f, 0.25f);\n    if (x218_29_drawHologram) {\n      DrawHologram(mgr, xf, flags);\n    } else {\n      constexpr CModelFlags useFlags(0, 0, 3, zeus::skWhite);\n      x10_solidModelData->RenderThermal(xf, mulColor, addColor, useFlags);\n    }\n\n    if (drawSuitArm && xb0_suitArmModelData) {\n      constexpr CModelFlags useFlags(0, 0, 3, zeus::skWhite);\n      xb0_suitArmModelData->RenderThermal(xf, mulColor, addColor, useFlags);\n    }\n  } else {\n    /* Cold Draw */\n    if (mgr.GetPlayerState()->GetCurrentVisor() != CPlayerState::EPlayerVisor::XRay && !x218_29_drawHologram) {\n      CModelFlags useFlags = flags;\n      useFlags.x1_matSetIdx = u8(x20c_shaderIdx);\n      x10_solidModelData->Render(mgr, xf, lights, useFlags);\n    } else {\n      DrawHologram(mgr, xf, flags);\n    }\n\n    if (drawSuitArm && xb0_suitArmModelData) {\n      xb0_suitArmModelData->Render(mgr, armXf, lights, flags);\n    }\n  }\n\n  if (x1bc_rainSplashGenerator && x1bc_rainSplashGenerator->IsRaining()) {\n    CSkinnedModel::ClearPointGeneratorFunc();\n    x1bc_rainSplashGenerator->Draw(xf);\n  }\n}\n\nvoid CGunWeapon::DrawMuzzleFx(const CStateManager& mgr) const {\n  if (CElementGen* const effect = x1a4_muzzleGenerators[x208_muzzleEffectIdx].get()) {\n    if (x200_beamId != CPlayerState::EBeamId::Ice &&\n        mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::XRay) {\n      CElementGen::SetSubtractBlend(true);\n      effect->Render();\n      CElementGen::SetSubtractBlend(false);\n    } else {\n      effect->Render();\n    }\n  }\n}\n\nvoid CGunWeapon::LoadSuitArm(CStateManager& mgr) {\n  if (!x13c_armCharacter.IsLoaded()) {\n    return;\n  }\n\n  const CAssetId armId =\n      NWeaponTypes::get_asset_id_from_name(skSuitArmNames[size_t(NWeaponTypes::get_current_suit(mgr))]);\n  xb0_suitArmModelData.emplace(CStaticRes(armId, x4_scale));\n  xb0_suitArmModelData->SetSortThermal(true);\n  x218_28_suitArmLocked = false;\n  x13c_armCharacter.Unlock();\n}\n\nvoid CGunWeapon::LoadGunModels(CStateManager& mgr) {\n  s32 defaultAnim = x218_27_subtypeBasePose ? 0 : 9;\n  x10_solidModelData.emplace(CAnimRes(x214_ancsId, 0, x4_scale, defaultAnim, false));\n  x60_holoModelData.emplace(CAnimRes(x214_ancsId, 1, x4_scale, defaultAnim, false));\n  CAnimPlaybackParms parms(defaultAnim, -1, 1.f, true);\n  x10_solidModelData->GetAnimationData()->SetAnimation(parms, true);\n  LoadSuitArm(mgr);\n  x10_solidModelData->SetSortThermal(true);\n  x60_holoModelData->SetSortThermal(true);\n  x100_gunController = std::make_unique<CGunController>(*x10_solidModelData);\n}\n\nvoid CGunWeapon::LoadAnimations() {\n  NWeaponTypes::get_token_vector(*x10_solidModelData->GetAnimationData(), 0, 15, x10c_anims, true);\n}\n\nbool CGunWeapon::IsAnimsLoaded() const {\n  return std::all_of(x10c_anims.cbegin(), x10c_anims.cend(), [](const auto& anim) { return anim.IsLoaded(); });\n}\n\nvoid CGunWeapon::LoadMuzzleFx(float dt) {\n  for (const auto& muzzleEffect : x16c_muzzleEffects) {\n    x1a4_muzzleGenerators.push_back(std::make_unique<CElementGen>(muzzleEffect));\n    x1a4_muzzleGenerators.back()->SetParticleEmission(false);\n    x1a4_muzzleGenerators.back()->Update(dt);\n  }\n}\n\nvoid CGunWeapon::LoadProjectileData(CStateManager& mgr) {\n  CRandom16 random(mgr.GetUpdateFrameIndex());\n  CGlobalRandom grand(random);\n\n  for (const auto& weapon : x144_weapons) {\n    zeus::CVector3f weaponVel;\n    if (const CVectorElement* ivec = weapon->x4_IVEC.get()) {\n      ivec->GetValue(0, weaponVel);\n    }\n\n    x1d0_velInfo.x0_vel.push_back(weaponVel);\n    float tratVal = 0.f;\n    if (const CRealElement* trat = weapon->x30_TRAT.get()) {\n      trat->GetValue(0, tratVal);\n    }\n\n    x1d0_velInfo.x24_trat.push_back(tratVal);\n    x1d0_velInfo.x1c_targetHoming.push_back(weapon->x29_HOMG);\n    if (weaponVel.y() > 0.f) {\n      x1d0_velInfo.x0_vel.back() *= zeus::CVector3f(60.f);\n    } else {\n      x1d0_velInfo.x0_vel.back() = zeus::skForward;\n    }\n  }\n}\n\nvoid CGunWeapon::LoadFxIdle(float dt, CStateManager& mgr) {\n  if (!NWeaponTypes::are_tokens_ready(x12c_deps)) {\n    return;\n  }\n\n  if ((x210_loadFlags & 0x2) != 0 && (x210_loadFlags & 0x4) != 0 && (x210_loadFlags & 0x10) != 0) {\n    return;\n  }\n\n  const bool muzzlesLoaded = std::all_of(x16c_muzzleEffects.cbegin(), x16c_muzzleEffects.cend(),\n                                         [](const auto& muzzle) { return muzzle.IsLoaded(); });\n  if (!muzzlesLoaded) {\n    return;\n  }\n\n  const bool weaponsLoaded =\n      std::all_of(x144_weapons.cbegin(), x144_weapons.cend(), [](const auto& weapon) { return weapon.IsLoaded(); });\n  if (!weaponsLoaded) {\n    return;\n  }\n\n  const bool frozenLoaded = std::all_of(x188_frozenEffects.cbegin(), x188_frozenEffects.cend(),\n                                        [](const auto& effect) { return effect.IsLoaded(); });\n  if (!frozenLoaded) {\n    return;\n  }\n\n  if (!x160_xferEffect.IsLoaded()) {\n    return;\n  }\n\n  if ((x210_loadFlags & 0x2) != 0x2) {\n    LoadMuzzleFx(dt);\n    x210_loadFlags |= 0x2;\n  }\n  x210_loadFlags |= 0x10;\n  if ((x210_loadFlags & 0x4) != 0x4) {\n    LoadProjectileData(mgr);\n    x210_loadFlags |= 0x4;\n  }\n}\n\nvoid CGunWeapon::Update(float dt, CStateManager& mgr) {\n  if (x218_26_loaded) {\n    x10_solidModelData->AdvanceAnimation(dt, mgr, kInvalidAreaId, true);\n    x100_gunController->Update(dt, mgr);\n    if (x218_28_suitArmLocked)\n      LoadSuitArm(mgr);\n  } else {\n    if (x104_gunCharacter) {\n      if (x104_gunCharacter.IsLoaded()) {\n        if ((x210_loadFlags & 0x1) != 0x1) {\n          LoadGunModels(mgr);\n          LoadAnimations();\n          x210_loadFlags |= 0x1;\n        }\n        if ((x210_loadFlags & 0x8) != 0x8) {\n          if (IsAnimsLoaded())\n            x210_loadFlags |= 0x8;\n        }\n      }\n\n      LoadFxIdle(dt, mgr);\n      if ((x210_loadFlags & 0x1f) == 0x1f) {\n        if (x10_solidModelData->PickAnimatedModel(CModelData::EWhichModel::Normal)\n                .GetModel()\n                ->IsLoaded(x20c_shaderIdx) &&\n            xb0_suitArmModelData->IsLoaded(0))\n          x218_26_loaded = true;\n      }\n    }\n  }\n}\n\nvoid CGunWeapon::LockTokens(CStateManager& mgr) {\n  AsyncLoadSuitArm(mgr);\n  NWeaponTypes::lock_tokens(x12c_deps);\n}\n\nvoid CGunWeapon::UnlockTokens() {\n  x13c_armCharacter.Unlock();\n  NWeaponTypes::unlock_tokens(x12c_deps);\n}\n\nvoid CGunWeapon::Load(CStateManager& mgr, bool subtypeBasePose) {\n  LockTokens(mgr);\n  x218_27_subtypeBasePose = subtypeBasePose;\n  x204_frozenEffect = EFrozenFxType::None;\n  x1b8_frozenGenerator.reset();\n  x104_gunCharacter.Lock();\n  x160_xferEffect.Lock();\n\n  for (size_t i = 0; i < x16c_muzzleEffects.size(); ++i) {\n    x16c_muzzleEffects[i].Lock();\n    x144_weapons[i].Lock();\n  }\n\n  for (auto& frozenEffect : x188_frozenEffects) {\n    frozenEffect.Lock();\n  }\n}\n\nvoid CGunWeapon::Unload(CStateManager& mgr) {\n  UnlockTokens();\n  x210_loadFlags = 0;\n  x204_frozenEffect = EFrozenFxType::None;\n  x10_solidModelData = std::nullopt;\n  x60_holoModelData = std::nullopt;\n  xb0_suitArmModelData = std::nullopt;\n  x100_gunController.reset();\n  x1bc_rainSplashGenerator = nullptr;\n  x1b8_frozenGenerator.reset();\n  FreeResPools();\n  x104_gunCharacter.Unlock();\n  x218_26_loaded = false;\n}\n\nbool CGunWeapon::IsLoaded() const { return x218_26_loaded; }\n\nvoid CGunWeapon::DrawHologram(const CStateManager& mgr, const zeus::CTransform& xf, const CModelFlags& flags) {\n  if (!x218_26_loaded)\n    return;\n\n  // TODO\n  if (x218_29_drawHologram) {\n    CModelFlags useFlags = flags;\n    x60_holoModelData->FlatDraw(CModelData::EWhichModel::Normal, xf, false, useFlags);\n  } else {\n    CGraphics::SetModelMatrix(xf * zeus::CTransform::Scale(x10_solidModelData->GetScale()));\n    CGraphics::DisableAllLights();\n    g_Renderer->SetAmbientColor(zeus::skWhite);\n    CSkinnedModel& model = *x60_holoModelData->GetAnimationData()->GetModelData();\n    x10_solidModelData->GetAnimationData()->Render(model, flags, nullptr, {});\n    g_Renderer->SetAmbientColor(zeus::skWhite);\n    CGraphics::DisableAllLights();\n  }\n}\n\nvoid CGunWeapon::UpdateMuzzleFx(float dt, const zeus::CVector3f& scale, const zeus::CVector3f& pos, bool emitting) {\n  x1a4_muzzleGenerators[x208_muzzleEffectIdx]->SetGlobalTranslation(pos);\n  x1a4_muzzleGenerators[x208_muzzleEffectIdx]->SetGlobalScale(scale);\n  x1a4_muzzleGenerators[x208_muzzleEffectIdx]->SetParticleEmission(emitting);\n  x1a4_muzzleGenerators[x208_muzzleEffectIdx]->Update(dt);\n}\n\nvoid CGunWeapon::ReturnToDefault(CStateManager& mgr) { x100_gunController->ReturnToDefault(mgr, 0.f, false); }\n\nbool CGunWeapon::PlayPasAnim(SamusGun::EAnimationState state, CStateManager& mgr, float angle) {\n  switch (state) {\n  case SamusGun::EAnimationState::ComboFire:\n    x100_gunController->EnterComboFire(mgr, s32(x200_beamId));\n    return true;\n  default:\n    return false;\n  case SamusGun::EAnimationState::Wander:\n    return true;\n  }\n}\n\nvoid CGunWeapon::UnLoadFidget() { x100_gunController->UnLoadFidget(); }\n\nbool CGunWeapon::IsFidgetLoaded() const { return x100_gunController->IsFidgetLoaded(); }\n\nvoid CGunWeapon::AsyncLoadFidget(CStateManager& mgr, SamusGun::EFidgetType type, s32 animSet) {\n  x100_gunController->LoadFidgetAnimAsync(mgr, s32(type), s32(x200_beamId), animSet);\n}\n\nvoid CGunWeapon::EnterFidget(CStateManager& mgr, SamusGun::EFidgetType type, s32 parm2) {\n  x100_gunController->EnterFidget(mgr, s32(type), s32(x200_beamId), parm2);\n}\n\nCDamageInfo CGunWeapon::GetShotDamageInfo(const SShotParam& shotParam, CStateManager& mgr) {\n  CDamageInfo ret(shotParam);\n  if (g_GameState->GetHardMode())\n    ret.MultiplyDamage(g_GameState->GetHardModeWeaponMultiplier());\n  return ret;\n}\n\nCDamageInfo CGunWeapon::GetDamageInfo(CStateManager& mgr, EChargeState chargeState, float chargeFactor) const {\n  const SWeaponInfo& wInfo = GetWeaponInfo();\n  if (chargeState == EChargeState::Normal) {\n    return GetShotDamageInfo(wInfo.x4_normal, mgr);\n  } else {\n    SShotParam param = wInfo.x20_charged;\n    param.x8_damage *= chargeFactor;\n    param.xc_radiusDamage *= chargeFactor;\n    param.x10_radius *= chargeFactor;\n    param.x14_knockback *= chargeFactor;\n    param.x18_24_noImmunity = false;\n    return GetShotDamageInfo(param, mgr);\n  }\n}\n\nconst SWeaponInfo& CGunWeapon::GetWeaponInfo() const { return g_tweakPlayerGun->GetBeamInfo(s32(x200_beamId)); }\n\nzeus::CAABox CGunWeapon::GetBounds() const {\n  if (x10_solidModelData)\n    return x10_solidModelData->GetBounds();\n  return zeus::skNullBox;\n}\n\nzeus::CAABox CGunWeapon::GetBounds(const zeus::CTransform& xf) const {\n  if (x10_solidModelData)\n    return x10_solidModelData->GetBounds(xf);\n  return zeus::skNullBox;\n}\n\nbool CGunWeapon::IsChargeAnimOver() const {\n  return !(x218_25_enableCharge && x10_solidModelData->GetAnimationData()->IsAnimTimeRemaining(0.001f, \"Whole Body\"));\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CGunWeapon.hpp",
    "content": "#pragma once\n\n#include <array>\n#include <memory>\n#include <vector>\n\n#include \"Runtime/RetroTypes.hpp\"\n\n#include \"Runtime/CPlayerState.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/Character/CAnimCharacterSet.hpp\"\n#include \"Runtime/Collision/CMaterialList.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/Particle/CWeaponDescription.hpp\"\n#include \"Runtime/Weapon/CGunController.hpp\"\n#include \"Runtime/Weapon/CGunMotion.hpp\"\n#include \"Runtime/Weapon/CWeaponMgr.hpp\"\n#include \"Runtime/Weapon/WeaponCommon.hpp\"\n#include \"Runtime/World/CDamageInfo.hpp\"\n#include \"Runtime/rstl.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\n\nclass CActorLights;\nstruct CModelFlags;\n\nenum class EChargeState { Normal, Charged };\n\nclass CVelocityInfo {\n  friend class CGunWeapon;\n  rstl::reserved_vector<zeus::CVector3f, 2> x0_vel;\n  rstl::reserved_vector<bool, 2> x1c_targetHoming;\n  rstl::reserved_vector<float, 2> x24_trat;\n\npublic:\n  const zeus::CVector3f& GetVelocity(int i) const { return x0_vel[i]; }\n  bool GetTargetHoming(int i) const { return x1c_targetHoming[i]; }\n  void Clear() {\n    x0_vel.clear();\n    x1c_targetHoming.clear();\n    x24_trat.clear();\n  }\n};\n\nclass CGunWeapon {\npublic:\n  enum class ESecondaryFxType { None, Charge, ToCombo, CancelCharge };\n  enum class EFrozenFxType { None, Frozen, Thawed };\n\nprotected:\n  static const std::array<s32, 2> skShootAnim;\n  zeus::CVector3f x4_scale;\n  std::optional<CModelData> x10_solidModelData;\n  std::optional<CModelData> x60_holoModelData;\n  std::optional<CModelData> xb0_suitArmModelData;\n  std::unique_ptr<CGunController> x100_gunController;\n  TToken<CAnimCharacterSet> x104_gunCharacter;\n  std::vector<CToken> x10c_anims;\n  std::vector<CToken> x12c_deps;\n  TToken<CAnimCharacterSet> x13c_armCharacter;\n  rstl::reserved_vector<TCachedToken<CWeaponDescription>, 2> x144_weapons;\n  TCachedToken<CGenDescription> x160_xferEffect;\n  rstl::reserved_vector<TCachedToken<CGenDescription>, 2> x16c_muzzleEffects;\n  rstl::reserved_vector<TCachedToken<CGenDescription>, 2> x188_frozenEffects;\n  rstl::reserved_vector<std::unique_ptr<CElementGen>, 2> x1a4_muzzleGenerators;\n  std::unique_ptr<CElementGen> x1b8_frozenGenerator;\n  CRainSplashGenerator* x1bc_rainSplashGenerator = nullptr;\n  EWeaponType x1c0_weaponType;\n  TUniqueId x1c4_playerId;\n  EMaterialTypes x1c8_playerMaterial;\n  ESecondaryFxType x1cc_enabledSecondaryEffect = ESecondaryFxType::None;\n  CVelocityInfo x1d0_velInfo;\n  CPlayerState::EBeamId x200_beamId;\n  EFrozenFxType x204_frozenEffect = EFrozenFxType::None;\n  u32 x208_muzzleEffectIdx = 0;\n  u32 x20c_shaderIdx;\n  // 0x1: load request, 0x2: muzzle fx, 0x4: projectile data, 0x8: anims, 0x10: everything else\n  u32 x210_loadFlags = 0;\n  CAssetId x214_ancsId;\n  bool x218_24 : 1 = false;\n  bool x218_25_enableCharge : 1 = false;\n  bool x218_26_loaded : 1 = false;\n  // Initialize in selected beam's pose, rather than power beam's pose\n  bool x218_27_subtypeBasePose : 1 = false;\n  bool x218_28_suitArmLocked : 1 = false;\n  bool x218_29_drawHologram : 1 = false;\n\n  void AllocResPools(CPlayerState::EBeamId beam);\n  void FreeResPools();\n  void FillTokenVector(const std::vector<SObjectTag>& tags, std::vector<CToken>& objects);\n  void BuildDependencyList(CPlayerState::EBeamId beam);\n  void LoadSuitArm(CStateManager& mgr);\n  void LoadGunModels(CStateManager& mgr);\n  void LoadAnimations();\n  bool IsAnimsLoaded() const;\n  void LoadMuzzleFx(float dt);\n  void LoadProjectileData(CStateManager& mgr);\n  void LoadFxIdle(float dt, CStateManager& mgr);\n  void LockTokens(CStateManager& mgr);\n  void UnlockTokens();\n\npublic:\n  CGunWeapon(CAssetId ancsId, EWeaponType type, TUniqueId playerId, EMaterialTypes playerMaterial,\n             const zeus::CVector3f& scale);\n  virtual ~CGunWeapon();\n\n  void AsyncLoadSuitArm(CStateManager& mgr);\n  virtual void Reset(CStateManager& mgr);\n  virtual void PlayAnim(NWeaponTypes::EGunAnimType type, bool loop);\n  virtual void PreRenderGunFx(const CStateManager& mgr, const zeus::CTransform& xf);\n  virtual void PostRenderGunFx(const CStateManager& mgr, const zeus::CTransform& xf);\n  virtual void UpdateGunFx(bool shotSmoke, float dt, const CStateManager& mgr, const zeus::CTransform& xf);\n  virtual void Fire(bool underwater, float dt, EChargeState chargeState, const zeus::CTransform& xf, CStateManager& mgr,\n                    TUniqueId homingTarget, float chargeFactor1, float chargeFactor2);\n  virtual void EnableFx(bool enable);\n  virtual void EnableSecondaryFx(ESecondaryFxType type);\n  void EnableFrozenEffect(EFrozenFxType type);\n  void ActivateCharge(bool enable, bool resetEffect);\n  void Touch(const CStateManager& mgr);\n  void TouchHolo(const CStateManager& mgr);\n  virtual void Draw(bool drawSuitArm, const CStateManager& mgr, const zeus::CTransform& xf, const CModelFlags& flags,\n                    const CActorLights* lights);\n  virtual void DrawMuzzleFx(const CStateManager& mgr) const;\n  virtual void Update(float dt, CStateManager& mgr);\n  virtual void Load(CStateManager& mgr, bool subtypeBasePose);\n  virtual void Unload(CStateManager& mgr);\n  virtual bool IsLoaded() const;\n  void DrawHologram(const CStateManager& mgr, const zeus::CTransform& xf, const CModelFlags& flags);\n  void UpdateMuzzleFx(float dt, const zeus::CVector3f& scale, const zeus::CVector3f& pos, bool emitting);\n  const CVelocityInfo& GetVelocityInfo() const { return x1d0_velInfo; }\n  void SetRainSplashGenerator(CRainSplashGenerator* g) { x1bc_rainSplashGenerator = g; }\n  CElementGen* GetChargeMuzzleFx() const { return x1a4_muzzleGenerators[1].get(); }\n  const TToken<CGenDescription>& GetComboXferDescr() const { return x160_xferEffect; }\n  void ReturnToDefault(CStateManager& mgr);\n  bool PlayPasAnim(SamusGun::EAnimationState state, CStateManager& mgr, float angle);\n  void UnLoadFidget();\n  bool IsFidgetLoaded() const;\n  void AsyncLoadFidget(CStateManager& mgr, SamusGun::EFidgetType type, s32 animSet);\n  void EnterFidget(CStateManager& mgr, SamusGun::EFidgetType type, s32 parm2);\n  bool HasSolidModelData() const { return x10_solidModelData.operator bool(); }\n  CModelData& GetSolidModelData() { return *x10_solidModelData; }\n  const SWeaponInfo& GetWeaponInfo() const;\n  CDamageInfo GetDamageInfo(CStateManager& mgr, EChargeState chargeState, float chargeFactor) const;\n  EWeaponType GetWeaponType() const { return x1c0_weaponType; }\n  zeus::CAABox GetBounds() const;\n  zeus::CAABox GetBounds(const zeus::CTransform& xf) const;\n  bool ComboFireOver() const { return x100_gunController->IsComboOver(); }\n  bool IsChargeAnimOver() const;\n  void SetDrawHologram(bool d) { x218_29_drawHologram = d; }\n  void EnableCharge(bool c) { x218_25_enableCharge = c; }\n\n  static CDamageInfo GetShotDamageInfo(const SShotParam& shotParam, CStateManager& mgr);\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CIceBeam.cpp",
    "content": "#include \"Runtime/Weapon/CIceBeam.hpp\"\n\n#include <array>\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n\nnamespace metaforce {\n\nCIceBeam::CIceBeam(CAssetId characterId, EWeaponType type, TUniqueId playerId, EMaterialTypes playerMaterial,\n                   const zeus::CVector3f& scale)\n: CGunWeapon(characterId, type, playerId, playerMaterial, scale) {\n  x21c_iceSmoke = g_SimplePool->GetObj(\"IceSmoke\");\n  x228_ice2nd1 = g_SimplePool->GetObj(\"Ice2nd_1\");\n  x234_ice2nd2 = g_SimplePool->GetObj(\"Ice2nd_2\");\n}\n\nvoid CIceBeam::PreRenderGunFx(const CStateManager& mgr, const zeus::CTransform& xf) {\n  // Empty\n}\n\nvoid CIceBeam::PostRenderGunFx(const CStateManager& mgr, const zeus::CTransform& xf) {\n  bool subtractBlend = mgr.GetThermalDrawFlag() == EThermalDrawFlag::Hot;\n  if (subtractBlend)\n    CElementGen::SetSubtractBlend(true);\n  if (x240_smokeGen)\n    x240_smokeGen->Render();\n  if (x1cc_enabledSecondaryEffect != ESecondaryFxType::None && x244_chargeFx)\n    x244_chargeFx->Render();\n  CGunWeapon::PostRenderGunFx(mgr, xf);\n  if (subtractBlend)\n    CElementGen::SetSubtractBlend(false);\n}\n\nvoid CIceBeam::UpdateGunFx(bool shotSmoke, float dt, const CStateManager& mgr, const zeus::CTransform& xf) {\n  if (x240_smokeGen) {\n    zeus::CTransform beamLoc = x10_solidModelData->GetScaledLocatorTransform(\"LBEAM\");\n    x240_smokeGen->SetTranslation(beamLoc.origin);\n    x240_smokeGen->SetOrientation(beamLoc.getRotation());\n    x240_smokeGen->Update(dt);\n  }\n\n  if (x244_chargeFx) {\n    if (x248_25_inEndFx && x244_chargeFx->IsSystemDeletable()) {\n      x1cc_enabledSecondaryEffect = ESecondaryFxType::None;\n      x244_chargeFx.reset();\n    }\n    if (x1cc_enabledSecondaryEffect != ESecondaryFxType::None) {\n      if (x248_25_inEndFx) {\n        x244_chargeFx->SetTranslation(xf.origin);\n        x244_chargeFx->SetOrientation(xf.getRotation());\n      } else {\n        x244_chargeFx->SetGlobalOrientAndTrans(xf);\n      }\n      x244_chargeFx->Update(dt);\n    }\n  }\n\n  CGunWeapon::UpdateGunFx(shotSmoke, dt, mgr, xf);\n}\n\nvoid CIceBeam::Fire(bool underwater, float dt, EChargeState chargeState, const zeus::CTransform& xf, CStateManager& mgr,\n                    TUniqueId homingTarget, float chargeFactor1, float chargeFactor2) {\n  static constexpr std::array<u16, 2> soundId{SFXwpn_fire_ice_normal, SFXwpn_fire_ice_charged};\n\n  CGunWeapon::Fire(underwater, dt, chargeState, xf, mgr, homingTarget, chargeFactor1, chargeFactor2);\n  NWeaponTypes::play_sfx(soundId[size_t(chargeState)], underwater, false, 0.165f);\n}\n\nvoid CIceBeam::EnableFx(bool enable) {\n  if (x240_smokeGen)\n    x240_smokeGen->SetParticleEmission(enable);\n}\n\nvoid CIceBeam::EnableSecondaryFx(ESecondaryFxType type) {\n  switch (type) {\n  case ESecondaryFxType::CancelCharge:\n  case ESecondaryFxType::None:\n    if (x1cc_enabledSecondaryEffect == ESecondaryFxType::None)\n      break;\n    [[fallthrough]];\n  default:\n    switch (type) {\n    case ESecondaryFxType::None:\n    case ESecondaryFxType::ToCombo:\n    case ESecondaryFxType::CancelCharge:\n      if (!x248_25_inEndFx) {\n        x244_chargeFx = std::make_unique<CElementGen>(x234_ice2nd2);\n        x244_chargeFx->SetGlobalScale(x4_scale);\n        x248_25_inEndFx = true;\n        x1cc_enabledSecondaryEffect = ESecondaryFxType::CancelCharge;\n      }\n      break;\n    case ESecondaryFxType::Charge:\n      x244_chargeFx = std::make_unique<CElementGen>(x228_ice2nd1);\n      x244_chargeFx->SetGlobalScale(x4_scale);\n      x248_25_inEndFx = false;\n      x1cc_enabledSecondaryEffect = type;\n      break;\n    }\n    break;\n  }\n}\n\nvoid CIceBeam::Update(float dt, CStateManager& mgr) {\n  CGunWeapon::Update(dt, mgr);\n\n  if (!x248_24_loaded) {\n    x248_24_loaded = x21c_iceSmoke.IsLoaded() && x228_ice2nd1.IsLoaded() && x234_ice2nd2.IsLoaded();\n    if (x248_24_loaded) {\n      x240_smokeGen = std::make_unique<CElementGen>(x21c_iceSmoke);\n      x240_smokeGen->SetGlobalScale(x4_scale);\n      x240_smokeGen->SetParticleEmission(false);\n    }\n  }\n}\n\nvoid CIceBeam::Load(CStateManager& mgr, bool subtypeBasePose) {\n  CGunWeapon::Load(mgr, subtypeBasePose);\n  x21c_iceSmoke.Lock();\n  x228_ice2nd1.Lock();\n  x234_ice2nd2.Lock();\n  x248_25_inEndFx = false;\n}\n\nvoid CIceBeam::ReInitVariables() {\n  x240_smokeGen.reset();\n  x244_chargeFx.reset();\n  x248_24_loaded = false;\n  x248_25_inEndFx = false;\n  x1cc_enabledSecondaryEffect = ESecondaryFxType::None;\n}\n\nvoid CIceBeam::Unload(CStateManager& mgr) {\n  CGunWeapon::Unload(mgr);\n  x234_ice2nd2.Unlock();\n  x228_ice2nd1.Unlock();\n  x21c_iceSmoke.Unlock();\n  ReInitVariables();\n}\n\nbool CIceBeam::IsLoaded() const { return CGunWeapon::IsLoaded() && x248_24_loaded; }\n\n} // namespace metaforce"
  },
  {
    "path": "Runtime/Weapon/CIceBeam.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/Weapon/CGunWeapon.hpp\"\n\nnamespace metaforce {\n\nclass CIceBeam final : public CGunWeapon {\n  TCachedToken<CGenDescription> x21c_iceSmoke;\n  TCachedToken<CGenDescription> x228_ice2nd1;\n  TCachedToken<CGenDescription> x234_ice2nd2;\n  std::unique_ptr<CElementGen> x240_smokeGen;\n  std::unique_ptr<CElementGen> x244_chargeFx;\n  bool x248_24_loaded : 1 = false;\n  bool x248_25_inEndFx : 1 = false;\n  void ReInitVariables();\n\npublic:\n  CIceBeam(CAssetId characterId, EWeaponType type, TUniqueId playerId, EMaterialTypes playerMaterial,\n           const zeus::CVector3f& scale);\n\n  void PreRenderGunFx(const CStateManager& mgr, const zeus::CTransform& xf) override;\n  void PostRenderGunFx(const CStateManager& mgr, const zeus::CTransform& xf) override;\n  void UpdateGunFx(bool shotSmoke, float dt, const CStateManager& mgr, const zeus::CTransform& xf) override;\n  void Fire(bool underwater, float dt, EChargeState chargeState, const zeus::CTransform& xf, CStateManager& mgr,\n            TUniqueId homingTarget, float chargeFactor1, float chargeFactor2) override;\n  void EnableFx(bool enable) override;\n  void EnableSecondaryFx(ESecondaryFxType type) override;\n  void Update(float dt, CStateManager& mgr) override;\n  void Load(CStateManager& mgr, bool subtypeBasePose) override;\n  void Unload(CStateManager& mgr) override;\n  bool IsLoaded() const override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CMakeLists.txt",
    "content": "set(WEAPON_SOURCES\n        WeaponCommon.hpp WeaponCommon.cpp\n        CWeaponMgr.hpp CWeaponMgr.cpp\n        CGunController.hpp CGunController.cpp\n        CGunMotion.hpp CGunMotion.cpp\n        CGrappleArm.hpp CGrappleArm.cpp\n        CWeaponMode.hpp\n        CPlayerGun.hpp CPlayerGun.cpp\n        CGunWeapon.hpp CGunWeapon.cpp\n        CAuxWeapon.hpp CAuxWeapon.cpp\n        CPowerBeam.hpp CPowerBeam.cpp\n        CIceBeam.hpp CIceBeam.cpp\n        CWaveBeam.hpp CWaveBeam.cpp\n        CPlasmaBeam.hpp CPlasmaBeam.cpp\n        CPhazonBeam.hpp CPhazonBeam.cpp\n        CGSFreeLook.hpp CGSFreeLook.cpp\n        CGSComboFire.hpp CGSComboFire.cpp\n        CGSFidget.hpp CGSFidget.cpp\n        CFidget.hpp CFidget.cpp\n        CWeapon.hpp CWeapon.cpp\n        CGameProjectile.hpp CGameProjectile.cpp\n        CBeamProjectile.hpp CBeamProjectile.cpp\n        CElectricBeamProjectile.hpp CElectricBeamProjectile.cpp\n        CTargetableProjectile.hpp CTargetableProjectile.cpp\n        CBeamInfo.hpp\n        CPlasmaProjectile.hpp CPlasmaProjectile.cpp\n        CEnergyProjectile.cpp CEnergyProjectile.cpp\n        CProjectileWeapon.hpp CProjectileWeapon.cpp\n        CBomb.hpp CBomb.cpp\n        CPowerBomb.hpp CPowerBomb.cpp\n        CFlameInfo.hpp CFlameInfo.cpp\n        CFlameThrower.hpp CFlameThrower.cpp\n        CWaveBuster.hpp CWaveBuster.cpp\n        CNewFlameThrower.hpp CNewFlameThrower.cpp\n        CProjectileInfo.hpp CProjectileInfo.cpp\n        CBurstFire.hpp CBurstFire.cpp)\n\nruntime_add_list(Weapon WEAPON_SOURCES)\n"
  },
  {
    "path": "Runtime/Weapon/CNewFlameThrower.cpp",
    "content": "#include \"Runtime/Weapon/CNewFlameThrower.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Collision/CCollisionInfoList.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/World/CGameArea.hpp\"\n#include \"Runtime/World/CGameLight.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n#include \"TCastTo.hpp\"\n#include \"Runtime/Collision/CCollisionPrimitive.hpp\"\n\n#include \"Runtime/World/CPatterned.hpp\"\n#include \"Runtime/MP1/World/CFlickerBat.hpp\"\n#include \"Runtime/World/CSnakeWeedSwarm.hpp\"\n#include \"Runtime/Collision/CMetroidAreaCollider.hpp\"\n#include \"Runtime/Collision/CGameCollision.hpp\"\n#include \"Runtime/Particle/CParticleGlobals.hpp\"\n#include \"Runtime/World/CScriptTrigger.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n\nnamespace metaforce {\nnamespace {\nconstexpr CMaterialFilter skExcludeProjectilePassthrough =\n    CMaterialFilter::MakeExclude(EMaterialTypes::ProjectilePassthrough);\n}\n\nCNewFlameThrower::CNewFlameThrower(const TToken<CWeaponDescription>& desc, std::string_view name, EWeaponType wType,\n                                   const std::array<CAssetId, 8>& resInfo, const zeus::CTransform& xf,\n                                   EMaterialTypes matType, const CDamageInfo& dInfo, TUniqueId uid, TAreaId aid,\n                                   TUniqueId owner, EProjectileAttrib attribs)\n: CGameProjectile(false, desc, name, wType, xf, matType, dInfo, uid, aid, owner, kInvalidUniqueId, attribs, false,\n                  zeus::skOne3f, {}, -1, false)\n, x304_mainFire(g_SimplePool->GetObj(SObjectTag{FOURCC('PART'), resInfo[0]}))\n, x310_mainSmoke(g_SimplePool->GetObj(SObjectTag{FOURCC('PART'), resInfo[1]}))\n, x31c_secondarySmoke(g_SimplePool->GetObj(SObjectTag{FOURCC('PART'), resInfo[4]}))\n, x328_secondaryFire(g_SimplePool->GetObj(SObjectTag{FOURCC('PART'), resInfo[5]}))\n, x334_secondarySparks(g_SimplePool->GetObj(SObjectTag{FOURCC('PART'), resInfo[6]}))\n, x340_swooshCenter(g_SimplePool->GetObj(SObjectTag{FOURCC('SWHC'), resInfo[2]}))\n, x34c_swooshFire(g_SimplePool->GetObj(SObjectTag{FOURCC('SWHC'), resInfo[3]})) {\n  x304_mainFire.GetObj();\n  x310_mainSmoke.GetObj();\n  x31c_secondarySmoke.GetObj();\n  x328_secondaryFire.GetObj();\n  x334_secondarySparks.GetObj();\n  x340_swooshCenter.GetObj();\n  x34c_swooshFire.GetObj();\n  x380_flameContactPoints.resize(3);\n}\n\nvoid CNewFlameThrower::Think(float dt, CStateManager& mgr) {\n  CWeapon::Think(dt, mgr);\n\n  TAreaId cur_area_id = mgr.GetWorld()->GetCurrentAreaId();\n  mgr.SetActorAreaId(*this, cur_area_id);\n\n  for (TUniqueId& light_id : x3b8_lightIds) {\n    CEntity* light = mgr.ObjectById(light_id);\n    if (light == nullptr) {\n      light_id = kInvalidUniqueId;\n    } else {\n      mgr.SetActorAreaId(*reinterpret_cast<CActor*>(light), cur_area_id);\n    }\n  }\n}\n\nvoid CNewFlameThrower::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CNewFlameThrower::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  if (msg == EScriptObjectMessage::Deleted) {\n    mgr.RemoveWeaponId(GetOwnerId(), GetType());\n    DeleteLightObjects(mgr);\n    SetWorldLighting(mgr, mgr.GetPlayer().GetAreaIdAlways(), 4.f, 1.f);\n  } else if (msg == EScriptObjectMessage::Registered) {\n    xe6_27_thermalVisorFlags = 2; // Thermal hot\n    Think(1.f / 60.f, mgr);\n    mgr.AddWeaponId(xec_ownerId, xf0_weaponType);\n  }\n\n  CGameProjectile::AcceptScriptMsg(msg, uid, mgr);\n}\n\nvoid CNewFlameThrower::DeleteLightObjects(CStateManager& mgr) {\n  for (TUniqueId const& id : x3b8_lightIds) {\n    mgr.FreeScriptObject(id);\n  }\n  x3b8_lightIds.clear();\n}\n\nvoid CNewFlameThrower::AddToRenderer(zeus::CFrustum const& planes, CStateManager& mgr) {\n  zeus::CAABox sorting_bounds = GetSortingBounds(mgr);\n  EnsureRendered(mgr, x34_transform.origin, sorting_bounds);\n}\n\nvoid CNewFlameThrower::CreateLightObjects(CStateManager& mgr) {\n  DeleteLightObjects(mgr);\n  for (int i = 0; i < 4; ++i) {\n    TUniqueId uid = mgr.AllocateUniqueId();\n    CLight lObj = x358_mainFireGen->GetLight();\n    CGameLight* light = new CGameLight(uid, GetAreaId(), false, \"FlamethrowerLight\", zeus::CTransform(), x8_uid, lObj,\n                                       u32(reinterpret_cast<uintptr_t>(this) + (i & 0x1)), 0, 0.f);\n    mgr.AddObject(light);\n    x3b8_lightIds.push_back(uid);\n  }\n}\n\nvoid CNewFlameThrower::EnableFx(CStateManager& mgr) {\n  DeleteProjectileLight(mgr);\n  x358_mainFireGen = std::make_unique<CElementGen>(x304_mainFire);\n  x35c_mainSmokeGen = std::make_unique<CElementGen>(x310_mainSmoke);\n  x360_secondarySmokeGen = std::make_unique<CElementGen>(x31c_secondarySmoke);\n  x364_secondaryFireGen = std::make_unique<CElementGen>(x328_secondaryFire);\n  x368_secondarySparksGen = std::make_unique<CElementGen>(x334_secondarySparks);\n  x36c_swooshCenterGen = std::make_unique<CParticleSwoosh>(x340_swooshCenter, 0);\n  x36c_swooshCenterGen->SetRenderGaps(true);\n  x370_swooshFireGen = std::make_unique<CParticleSwoosh>(x34c_swooshFire, 0);\n  x370_swooshFireGen->SetRenderGaps(true);\n  if (x358_mainFireGen && x358_mainFireGen->SystemHasLight() && x3b8_lightIds.empty())\n    CreateLightObjects(mgr);\n}\n\nvoid CNewFlameThrower::StartFiring(const zeus::CTransform& xf, CStateManager& mgr) {\n  SetActive(true);\n  x37c_25_firing = true;\n  x37c_24_renderAuxEffects = true;\n  x374_flameState = EFlameState::FireStart;\n  EnableFx(mgr);\n}\n\nbool CNewFlameThrower::AreEffectsFinished() const {\n  if (x358_mainFireGen && x358_mainFireGen->GetParticleCount() != 0)\n    return false;\n  if (x35c_mainSmokeGen && x35c_mainSmokeGen->GetParticleCount() != 0)\n    return false;\n  if (x360_secondarySmokeGen && x360_secondarySmokeGen->GetParticleCount() != 0)\n    return false;\n  if (x364_secondaryFireGen && x364_secondaryFireGen->GetParticleCount() != 0)\n    return false;\n  return !(x368_secondarySparksGen && x368_secondarySparksGen->GetParticleCount() != 0);\n}\n\n/* Used for old CNewFlameThrower::RenderParticles\n\nvoid CNewFlameThrower::LoadParticleGenQuads() {\n  if (!loaded_textures) {\n    beam_filters[0] = std::make_unique<CTexturedQuadFilter>(\n        EFilterType::Add, x358_mainFireGen->GetLoadedDesc()->x54_x40_TEXR->GetValueTexture(0));\n    beam_filters[1] = std::make_unique<CTexturedQuadFilter>(\n        EFilterType::Add, x35c_mainSmokeGen->GetLoadedDesc()->x54_x40_TEXR->GetValueTexture(0));\n    beam_filters[2] = std::make_unique<CTexturedQuadFilter>(\n        EFilterType::Add, x360_secondarySmokeGen->GetLoadedDesc()->x54_x40_TEXR->GetValueTexture(0));\n    beam_filters[3] = std::make_unique<CTexturedQuadFilter>(\n        EFilterType::Add, x364_secondaryFireGen->GetLoadedDesc()->x54_x40_TEXR->GetValueTexture(0));\n    beam_filters[4] = std::make_unique<CTexturedQuadFilter>(\n        EFilterType::Add, x368_secondarySparksGen->GetLoadedDesc()->x54_x40_TEXR->GetValueTexture(0));\n    loaded_textures = true;\n  }\n}\n*/\n\nvoid CNewFlameThrower::Render(CStateManager& mgr) {\n  if (x30_24_active) {\n    x36c_swooshCenterGen->Render();\n    x370_swooshFireGen->Render();\n\n    x368_secondarySparksGen->Render();\n    x364_secondaryFireGen->Render();\n    x360_secondarySmokeGen->Render();\n    x35c_mainSmokeGen->Render();\n    x358_mainFireGen->Render();\n\n    /*RenderBeam({x358_mainFireGen.get(), x35c_mainSmokeGen.get(), x360_secondarySmokeGen.get(),\n                x364_secondaryFireGen.get(), x368_secondarySparksGen.get()});*/\n  }\n}\n\n/* Removed this, source function was painfully similar to CElementGen::RenderParticles\n\nvoid CNewFlameThrower::RenderParticles(std::array<CElementGen*, 5> const& elem_gens) {\n  LoadParticleGenQuads();\n  zeus::CTransform xf(CGraphics::mViewMatrix);\n  zeus::CTransform xf2 = xf;\n  xf2.origin = zeus::skZero3f;\n  zeus::CTransform xf3 = xf2.inverse();\n  zeus::CTransform xf4 = xf3;\n  // CGraphics::SetModelMatrix(xf2);\n  // CGraphics::SetCullMode(ERglCullMode::None);\n  // CGraphics::SetDepthWriteMode(true, ERglEnum::LEqual, false);\n  // CGraphics::SetAlphaCompare(ERglAlphaFunc::Always, 0, ERglAlphaOp::And, ERglAlphaFunc::Always, 0);\n\n  // TODO: check sMoveRedToAlphaBuffer\n\n  // CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::One, ERglBlendFactor::One, ERglLogicOp::Clear);\n  int total_particles = 0;\n  for (CElementGen* elem : elem_gens) {\n    total_particles += elem->GetParticleCount();\n  }\n\n  // This is... something isn't it\n  struct ParticleElement {\n    u16 elem_gen_idx;\n    u16 part_idx;\n    zeus::CVector3f vec;\n  };\n\n  auto* translated_sorted_particles =\n      reinterpret_cast<ParticleElement*>(_alloca(sizeof(ParticleElement) * total_particles));\n  int active_particle_count = 0;\n\n  for (int i = 0; i < elem_gens.size(); i++) {\n    CElementGen* elem = elem_gens[i];\n\n    int iter_count = elem->GetParticleCount();\n    for (int j = 0; j < iter_count; j++) {\n      CParticle& part = elem->GetParticles()[j];\n      if (part.x0_endFrame == -1) {\n        continue;\n      }\n\n      // ????\n      // translated_sorted_particles[part_idx].vec = xf4 * part.x4_pos;\n      translated_sorted_particles[active_particle_count].vec =\n          xf4 * (((part.x4_pos - part.x10_prevPos) * elem->GetTimeDeltaScale()) + part.x10_prevPos);\n      translated_sorted_particles[active_particle_count].elem_gen_idx = static_cast<u16>(i);\n      translated_sorted_particles[active_particle_count].part_idx = static_cast<u16>(j);\n\n      active_particle_count++;\n    }\n  }\n  Log.report(logvisor::Info, \"Active particle count (render count) {}\", active_particle_count);\n  std::sort(translated_sorted_particles, translated_sorted_particles + active_particle_count,\n            [](ParticleElement const& l, ParticleElement const& r) { return l.vec.y() > r.vec.y(); });\n\n  int last_gen_idx = 0xffff;\n  CElementGen* cur_gen = nullptr;\n  CGenDescription* gen_desc = nullptr;\n  u32 emitter_time = 0;\n\n  for (int i = 0; i < active_particle_count; i++) {\n    ParticleElement* pe = translated_sorted_particles + i;\n    if (pe->elem_gen_idx != last_gen_idx) {\n      cur_gen = elem_gens[pe->elem_gen_idx];\n\n      emitter_time = cur_gen->GetEmitterTime();\n      gen_desc = cur_gen->GetLoadedDesc();\n      if (CElementGen::sMoveRedToAlphaBuffer) {\n        // TEV stuff\n      } else {\n        if (gen_desc->x44_26_x30_26_AAPH) {\n          // CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::One,\n          //                         ERglLogicOp::Clear);\n        } else {\n          // CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::InvSrcAlpha,\n          //                         ERglLogicOp::Clear);\n        }\n      }\n      // more TEV stuff\n      last_gen_idx = pe->elem_gen_idx;\n    }\n    CParticle& part = cur_gen->GetParticles()[pe->part_idx];\n\n    u32 elapsed_time = emitter_time - part.x28_startFrame - 1;\n    CParticleGlobals::instance()->SetParticleLifetime(part.x0_endFrame - part.x28_startFrame);\n    CParticleGlobals::instance()->UpdateParticleLifetimeTweenValues(elapsed_time);\n    SUVElementSet uvs;\n\n    xf3 = xf2.inverse();\n    zeus::CTransform system_camera_matrix = xf3 * cur_gen->x22c_globalOrientation;\n    xf3 = ((zeus::CTransform::Translate(cur_gen->xe8_globalTranslation) * cur_gen->x10c_globalScaleTransform) * xf3) *\n          cur_gen->x178_localScaleTransform;\n    g_Renderer->SetModelMatrix(xf3);\n\n    gen_desc->x54_x40_TEXR->GetValueUV(elapsed_time, uvs);\n    float ang = zeus::degToRad(part.x30_lineWidthOrRota);\n    float size = part.x2c_lineLengthOrSize * 0.5f;\n    float sin_extent = sinf(ang) * size;\n    float cos_extent = cosf(ang) * size;\n    std::array<CTexturedQuadFilter::Vert, 4> vertices;\n    zeus::CVector3f simd_vec(sin_extent + cos_extent, 0, cos_extent - sin_extent);\n    vertices[0].m_pos = pe->vec + zeus::CVector3f(sin_extent + cos_extent, 0, cos_extent - sin_extent);\n    vertices[0].m_uv = zeus::CVector2f(uvs.xMax, uvs.yMax);\n\n    vertices[1].m_pos = pe->vec - zeus::CVector3f(sin_extent - cos_extent, 0, sin_extent + cos_extent);\n    vertices[1].m_uv = zeus::CVector2f(uvs.xMin, uvs.yMax);\n\n    vertices[2].m_pos = pe->vec - zeus::CVector3f(sin_extent + cos_extent, 0, cos_extent - sin_extent);\n    vertices[2].m_uv = zeus::CVector2f(uvs.xMin, uvs.yMin);\n\n    vertices[3].m_pos = pe->vec + zeus::CVector3f(sin_extent - cos_extent, 0, sin_extent + cos_extent);\n    vertices[3].m_uv = zeus::CVector2f(uvs.xMin, uvs.yMax);\n\n    beam_filters[pe->elem_gen_idx]->drawVerts(part.x34_color, vertices);\n  }\n}\n*/\n\nvoid CNewFlameThrower::UpdateFx(const zeus::CTransform& xf, float dt, CStateManager& mgr) {\n  if (!x30_24_active) {\n    return;\n  }\n  float active_time =\n      0; // g_Main.x118_avgTickTimePerSec + g_Main.x11c_avgDrawTimePerSec; omitting this until handled by URDE\n  x37c_26_runningSlowish = (active_time > 0.65f);\n  UpdateFlameState(dt, mgr);\n  zeus::CVector3f org = xf.origin;\n  zeus::CTransform rot = xf.getRotation();\n  zeus::CTransform rot_copy(rot);\n\n  x358_mainFireGen->SetTranslation(org);\n  x358_mainFireGen->SetOrientation(rot_copy);\n  x36c_swooshCenterGen->SetTranslation(org);\n  x36c_swooshCenterGen->SetOrientation(rot_copy);\n  x370_swooshFireGen->SetTranslation(org);\n  x370_swooshFireGen->SetOrientation(rot_copy);\n\n  float particle_rate = (x37c_26_runningSlowish ? 1.f : 0.5f);\n  x358_mainFireGen->SetGeneratorRate(particle_rate);\n  x358_mainFireGen->Update(dt);\n  x35c_mainSmokeGen->Update(dt);\n  x360_secondarySmokeGen->Update(dt);\n  x364_secondaryFireGen->Update(dt);\n  x368_secondarySparksGen->Update(dt);\n  x36c_swooshCenterGen->Update(dt);\n  x370_swooshFireGen->Update(dt);\n  rstl::reserved_vector<Cube, 32> collision_list;\n\n  UpdateParticleCollisions(dt, mgr, collision_list);\n  if (collision_list.size() > 0) {\n    for (auto& swoosh : x36c_swooshCenterGen->GetSwooshes()) {\n      for (auto& cube : collision_list) {\n        float dpos = (cube.center - swoosh.xc_translation).magSquared();\n        if (dpos < (cube.bounds * cube.bounds)) {\n          swoosh.x0_active = false;\n        }\n      }\n    }\n    for (auto& particle : x358_mainFireGen->GetParticles()) {\n      for (auto& cube : collision_list) {\n        float dpos = (cube.center - particle.x4_pos).magSquared();\n        if (dpos < (cube.bounds * cube.bounds)) {\n          particle.x0_endFrame = -1;\n        }\n      }\n    }\n    for (auto& particle : x35c_mainSmokeGen->GetParticles()) {\n      for (auto& cube : collision_list) {\n        float dpos = (cube.center - particle.x4_pos).magSquared();\n        if (dpos < (cube.bounds * cube.bounds)) {\n          particle.x0_endFrame = -1;\n        }\n      }\n    }\n  }\n  if (x374_flameState == EFlameState::FireActive) {\n    int free_space = x36c_swooshCenterGen->GetSwooshes().capacity() - x36c_swooshCenterGen->GetSwooshes().size();\n    if (free_space < 4) {\n      const int swoosh_count = static_cast<int>(x36c_swooshCenterGen->GetSwooshes().size());\n      int quarter_behind_cur = ((((swoosh_count / 2) * 3) / 2) + x36c_swooshCenterGen->GetCurParticle()) % swoosh_count;\n      if (x36c_swooshCenterGen->GetSwooshes()[quarter_behind_cur].x0_active) {\n        // Need better names here\n        int next_idx = (quarter_behind_cur + 1) % swoosh_count;\n        auto& swoosh_1 = x36c_swooshCenterGen->GetSwooshes()[quarter_behind_cur];\n        auto& swoosh_2 = x36c_swooshCenterGen->GetSwooshes()[next_idx];\n        zeus::CVector3f delta = swoosh_1.xc_translation - swoosh_2.xc_translation;\n        float f0 = delta.dot(swoosh_1.x38_orientation.frontVector());\n        zeus::CVector3f unk = delta - (swoosh_1.x38_orientation.frontVector() * f0);\n        float tmp = std::clamp(unk.magnitude() * 30.f, 1.f, x37c_26_runningSlowish ? 2.f : 4.f);\n\n        x3b4_numSmokeParticlesSpawned = std::max(static_cast<int>(round(tmp)), x3b4_numSmokeParticlesSpawned - 1);\n\n        x35c_mainSmokeGen->SetTranslation(swoosh_1.xc_translation);\n        x35c_mainSmokeGen->SetOrientation(swoosh_1.x38_orientation);\n        if (x3b4_numSmokeParticlesSpawned > 0) {\n          x35c_mainSmokeGen->ForceParticleCreation(x3b4_numSmokeParticlesSpawned);\n        }\n      }\n    }\n  }\n  UpdateLights(mgr);\n}\n\n// TODO: lights don't actually light\nvoid CNewFlameThrower::UpdateLights(CStateManager& mgr) {\n  CGlobalRandom rand(x2e8_rand);\n\n  int lit_particle_step = std::max(2, static_cast<int>(x370_swooshFireGen->GetSwooshes().size() / 4));\n  int prev_particle = (x370_swooshFireGen->GetCurParticle() - 1) % x370_swooshFireGen->GetSwooshes().size();\n\n  int lit_particle_offset = 0;\n  const int fire_swoosh_count = static_cast<int>(x370_swooshFireGen->GetSwooshes().size());\n  for (auto& light_id : x3b8_lightIds) {\n    if (TCastToPtr<CGameLight> light = mgr.ObjectById(light_id)) {\n      bool should_light = true;\n      if (fire_swoosh_count <= lit_particle_offset) {\n        should_light = false;\n      }\n      auto& light_swoosh = x370_swooshFireGen->GetSwooshes()[(lit_particle_offset + prev_particle) % fire_swoosh_count];\n      if (!light_swoosh.x0_active) {\n        should_light = false;\n      }\n      light->SetActive(should_light);\n      if (!should_light) {\n        lit_particle_offset += lit_particle_step;\n        continue;\n      }\n      CLight light_data = x358_mainFireGen->GetLight();\n      if (x304_mainFire.GetObj()->x104_xf0_LCLR.get()) {\n        s32 rand_int = x2e8_rand.Range(0, 16);\n        CParticleGlobals::instance()->SetEmitterTime(rand_int);\n        zeus::CColor out_color(0xffff00ff);\n        x304_mainFire.GetObj()->x104_xf0_LCLR->GetValue(rand_int, out_color);\n        light_data.SetColor(out_color);\n      }\n      if (x304_mainFire.GetObj()->x108_xf4_LINT.get()) {\n        s32 rand_int = x2e8_rand.Range(0, 16);\n        CParticleGlobals::instance()->SetEmitterTime(rand_int);\n        float out_const_attenuation = 1.f;\n        x304_mainFire.GetObj()->x108_xf4_LINT->GetValue(rand_int, out_const_attenuation);\n        light_data.SetAngleAttenuation(out_const_attenuation, 0.f, 0.f);\n      }\n      light->SetLight(light_data);\n      light->SetTranslation(light_swoosh.xc_translation);\n      lit_particle_offset += lit_particle_step;\n    }\n  }\n}\n\nbool CNewFlameThrower::UpdateParticleCollisions(float dt, CStateManager& mgr,\n                                                rstl::reserved_vector<Cube, 32>& collisions_out) {\n  x300_wasPointAdded = false;\n  bool any_particle_collisions = false;\n  EntityList near_list_cache;\n  // rstl::reserved_vector<rstl::reserved_vector<, ?>, ?> unk_rstl_vec; // inner vectors of size 0x90c, never used\n  // though\n  CCollisionInfoList cached_cinfo;\n  auto& swoosh_vec = x370_swooshFireGen->GetSwooshes();\n  const int swoosh_count = static_cast<int>(swoosh_vec.size());\n  const int tmp = swoosh_count / 4;\n  const int batch_process_size = tmp < 6 ? 6 : tmp;\n  const int prev_particle = ((swoosh_count + x370_swooshFireGen->GetCurParticle()) - 1) % swoosh_count;\n  int i = 0;\n  while (i < swoosh_count) {\n    int batch_start = i;\n    int batch_end = i + batch_process_size;\n    if (batch_end >= swoosh_count) {\n      batch_end = swoosh_count;\n    }\n    float batch_highest_speed = 0.f;\n    bool processed_swoosh_in_batch = false;\n    zeus::CAABox batch_particle_bounds;\n    // Accumulate bounds of this batch and its max speed\n    for (int j = batch_start; j < batch_end; j++) {\n      if (!swoosh_vec[j].x0_active) {\n        continue;\n      }\n      if (!processed_swoosh_in_batch) {\n        // Retro assigned MIN_FLOAT and MAX_FLOAT, but this should suffice\n        batch_particle_bounds = zeus::CAABox();\n      }\n      processed_swoosh_in_batch = true;\n      batch_particle_bounds.accumulateBounds(swoosh_vec[j].xc_translation);\n      float swoosh_speed_sq = swoosh_vec[j].x74_velocity.magSquared();\n      if (batch_highest_speed < swoosh_speed_sq) {\n        batch_highest_speed = swoosh_speed_sq;\n      }\n    }\n    batch_highest_speed = sqrtf(batch_highest_speed) + 0.1f;\n    batch_particle_bounds.accumulateBounds(batch_particle_bounds.min - zeus::CVector3f(batch_highest_speed));\n    batch_particle_bounds.accumulateBounds(batch_particle_bounds.max + zeus::CVector3f(batch_highest_speed));\n\n    near_list_cache.clear();\n\n    CMaterialFilter near_list_filter = CMaterialFilter::MakeIncludeExclude(\n        CMaterialList(EMaterialTypes::Solid), CMaterialList(EMaterialTypes::ProjectilePassthrough));\n    mgr.BuildNearList(near_list_cache, batch_particle_bounds, near_list_filter, mgr.Player());\n    CAreaCollisionCache coll_cache(batch_particle_bounds);\n    CGameCollision::BuildAreaCollisionCache(mgr, coll_cache);\n\n    for (int j = batch_start; j < batch_end; j++) {\n      if (j == prev_particle || !swoosh_vec[j].x0_active) {\n        continue;\n      }\n      auto& cur_swoosh = swoosh_vec[j];\n\n      CCollidableSphere coll_prim(zeus::CSphere(cur_swoosh.xc_translation, batch_highest_speed),\n                                  CMaterialList(EMaterialTypes::Solid));\n      CCollisionInfoList coll_info_out;\n      bool collided_static = CGameCollision::DetectStaticCollision_Cached(\n          mgr, coll_cache, coll_prim, zeus::CTransform(), skExcludeProjectilePassthrough, coll_info_out);\n\n      TUniqueId first_actor_hit = kInvalidUniqueId;\n      bool collided_other = FindCollisionInNearList(mgr, near_list_cache, coll_prim, first_actor_hit, coll_info_out);\n\n      if ((collided_static || collided_other) && coll_info_out.GetCount() > 0) {\n        cur_swoosh.x0_active = false;\n        any_particle_collisions = true;\n        cached_cinfo.Clear();\n        coll_info_out.AccumulateNewContactsInto(cached_cinfo);\n        int k = 0;\n        zeus::CVector3f last_added_pt = zeus::skZero3f;\n        for (CCollisionInfo const& info : cached_cinfo) {\n          if (k > 3) {\n            break;\n          }\n          float overlap_range = x37c_26_runningSlowish ? 1 : 0.75f;\n          int num_overlap = SortAndFindOverlappingPoints(Cube{info.GetPoint(), overlap_range});\n          const int max_overlapping = (x37c_26_runningSlowish ? 2 : 3);\n          if (num_overlap < max_overlapping) {\n            AddContactPoint(info, 10);\n            zeus::CTransform xf = zeus::lookAt(zeus::skZero3f, info.GetNormalLeft(), zeus::skUp);\n            x360_secondarySmokeGen->SetOrientation(xf);\n            x364_secondaryFireGen->SetOrientation(xf);\n            x368_secondarySparksGen->SetOrientation(xf);\n            x360_secondarySmokeGen->SetTranslation(info.GetPoint());\n            x364_secondaryFireGen->SetTranslation(info.GetPoint());\n            x368_secondarySparksGen->SetTranslation(info.GetPoint());\n\n            x360_secondarySmokeGen->ForceParticleCreation(1);\n            x364_secondaryFireGen->ForceParticleCreation(max_overlapping);\n            x368_secondarySparksGen->ForceParticleCreation(x37c_26_runningSlowish ? 3 : 5);\n            if (x37c_26_runningSlowish) {\n              break;\n            }\n            last_added_pt = info.GetPoint();\n          }\n          k++;\n        }\n        if (!x37c_26_runningSlowish && !x300_wasPointAdded) {\n          float dist_between = (x2f4_lastParticleCollisionLoc - last_added_pt).magSquared();\n          if (cached_cinfo.GetCount() < 3 || dist_between > 3.0f) {\n            zeus::CVector3f avg_loc = (x2f4_lastParticleCollisionLoc + last_added_pt) * 0.5f;\n            x364_secondaryFireGen->SetTranslation(avg_loc);\n            x364_secondaryFireGen->ForceParticleCreation(2);\n          }\n        }\n\n        x2f4_lastParticleCollisionLoc = last_added_pt;\n        x300_wasPointAdded = true;\n\n        if (first_actor_hit != kInvalidUniqueId) {\n          if (TCastToPtr<CActor> act = mgr.ObjectById(first_actor_hit)) {\n            if (CanDamageActor(*act, mgr)) {\n              CDamageInfo dmg_info(x12c_curDamageInfo, dt);\n              mgr.ApplyDamage(x8_uid, act->GetUniqueId(), xec_ownerId, dmg_info, xf8_filter,\n                              cur_swoosh.x74_velocity.normalized());\n            }\n          }\n        }\n        CDamageInfo dmg_info(x12c_curDamageInfo, dt);\n        mgr.ApplyDamageToWorld(xec_ownerId, *this, cur_swoosh.xc_translation, dmg_info, xf8_filter);\n        collisions_out.push_back({cur_swoosh.xc_translation, batch_highest_speed});\n        if (collisions_out.size() == 32) {\n          cached_cinfo.Clear();\n          coll_info_out.Clear();\n          near_list_cache.clear();\n          return true;\n        }\n        cached_cinfo.Clear();\n      }\n      coll_info_out.Clear();\n    }\n    near_list_cache.clear();\n    mgr.BuildNearList(near_list_cache, batch_particle_bounds,\n                      CMaterialFilter::MakeInclude(CMaterialList(EMaterialTypes::NonSolidDamageable)), mgr.Player());\n    for (TUniqueId const& uid : near_list_cache) {\n      if (TCastToPtr<CSnakeWeedSwarm> sw = mgr.ObjectById(uid)) {\n        for (int j = batch_start; j < batch_end; j++) {\n          if (j == prev_particle || !swoosh_vec[j].x0_active) {\n            continue;\n          }\n          float damage_radius_multiplier = batch_highest_speed < 1.f ? 1 : batch_highest_speed;\n          sw->HandleRadiusDamage(damage_radius_multiplier * sw->GetWeaponDamageRadius(), mgr,\n                                 swoosh_vec[j].xc_translation);\n        }\n      }\n    }\n    for (int j = batch_start; j < batch_end; j++) {\n      if (j == prev_particle || !swoosh_vec[j].x0_active) {\n        continue;\n      }\n      auto& cur_swoosh = swoosh_vec[j];\n\n      CCollidableSphere coll_prim(zeus::CSphere(cur_swoosh.xc_translation, batch_highest_speed),\n                                  CMaterialList(EMaterialTypes::Solid));\n      CCollisionInfoList coll_info_out;\n      TUniqueId first_actor_hit;\n\n      FindCollisionInNearList(mgr, near_list_cache, coll_prim, first_actor_hit, coll_info_out);\n      if (first_actor_hit != kInvalidUniqueId) {\n        if (TCastToPtr<CActor> act = mgr.ObjectById(first_actor_hit)) {\n          if (CanDamageActor(*act, mgr)) {\n            CDamageInfo dmg_info(x12c_curDamageInfo, dt);\n            mgr.ApplyDamage(x8_uid, act->GetUniqueId(), xec_ownerId, dmg_info, xf8_filter,\n                            cur_swoosh.x74_velocity.normalized());\n          }\n        }\n      }\n      coll_info_out.Clear();\n    }\n    i += batch_process_size;\n  }\n  DecrementContactPointTimers();\n  near_list_cache.clear();\n  return any_particle_collisions;\n}\n\nbool CNewFlameThrower::CanDamageActor(CActor& hit_actor, CStateManager& mgr) {\n  CDamageVulnerability const* vuln = hit_actor.GetDamageVulnerability();\n  if (vuln->GetVulnerability(x12c_curDamageInfo.GetWeaponMode(), false) == EVulnerability::PassThrough) {\n    return false;\n  }\n  if (TCastToPtr<CScriptTrigger> trigger = hit_actor) {\n    CProjectileTouchResult res = CanCollideWithTrigger(*trigger, mgr);\n    return res.GetActorId() != kInvalidUniqueId;\n  }\n  if (TCastToPtr<CScriptPlatform> platform = hit_actor) {\n    return true;\n  }\n  if (TCastToPtr<CCollisionActor> coll_actor = hit_actor) {\n    return true;\n  }\n  if (CPatterned::CastTo<MP1::CFlickerBat>(&hit_actor)) {\n    return true;\n  }\n  CProjectileTouchResult res = CanCollideWithGameObject(hit_actor, mgr);\n  return res.GetActorId() != kInvalidUniqueId;\n}\n\nvoid CNewFlameThrower::AddContactPoint(CCollisionInfo const& cinfo, u32 time) {\n  int elem = 0;\n  for (auto& cp_vec : x380_flameContactPoints) {\n    cp_vec.emplace_back(cinfo.GetPoint()[elem], time);\n    elem++;\n  }\n  x37c_27_newPointAdded = true;\n}\n\nint CNewFlameThrower::SortAndFindOverlappingPoints(Cube const& box) {\n  if (x37c_27_newPointAdded) {\n    for (auto& component_vec : x380_flameContactPoints) {\n      std::sort(component_vec.begin(), component_vec.end());\n    }\n  }\n  int min_overlap = std::numeric_limits<int>::max();\n\n  for (int i = 0; i < x380_flameContactPoints.size(); i++) {\n    auto const& component_vec = x380_flameContactPoints[i];\n    float search_min = box.center[i] - box.bounds;\n    float search_max = box.center[i] + box.bounds;\n    auto min_result = rstl::binary_find(component_vec.begin(), component_vec.end(), Contact{search_min, 0});\n    auto max_result = rstl::binary_find(component_vec.begin(), component_vec.end(), Contact{search_max, 0});\n    if (min_result == component_vec.end()) {\n      min_result = component_vec.begin();\n    }\n\n    int num_overlap = static_cast<int>(max_result - min_result);\n    num_overlap = num_overlap < min_overlap ? num_overlap : min_overlap;\n    if (num_overlap == 0) {\n      return 0;\n    }\n    min_overlap = num_overlap;\n  }\n  return min_overlap;\n}\n\nbool CNewFlameThrower::FindCollisionInNearList(CStateManager& mgr, EntityList const& near_list,\n                                               CCollisionPrimitive const& coll, TUniqueId& first_coll_out,\n                                               CCollisionInfoList& collisions) {\n  for (TUniqueId const& cur_uid : near_list) {\n    if (TCastToPtr<CActor> near_actor = mgr.ObjectById(cur_uid)) {\n      if (TCastToPtr<CPhysicsActor> pa = *near_actor) {\n        CInternalCollisionStructure::CPrimDesc pa_desc(*pa->GetCollisionPrimitive(), pa->GetMaterialFilter(),\n                                                       pa->GetPrimitiveTransform());\n        CInternalCollisionStructure::CPrimDesc param_desc(coll, skExcludeProjectilePassthrough, zeus::CTransform());\n\n        if (CCollisionPrimitive::Collide(pa_desc, param_desc, collisions)) {\n          first_coll_out = cur_uid;\n          return true;\n        }\n      } else {\n        auto bounds = near_actor->GetTouchBounds();\n        if (!bounds.has_value()) {\n          continue;\n        }\n        CCollidableAABox coll_aabb(bounds.value(), CMaterialList(EMaterialTypes::Solid));\n        CInternalCollisionStructure::CPrimDesc aabb_desc(coll_aabb, CMaterialFilter::skPassEverything,\n                                                         zeus::CTransform());\n        CInternalCollisionStructure::CPrimDesc param_desc(coll, skExcludeProjectilePassthrough, zeus::CTransform());\n\n        if (CCollisionPrimitive::Collide(param_desc, aabb_desc, collisions)) {\n          first_coll_out = cur_uid;\n          return true;\n        }\n      }\n    }\n  }\n  return false;\n}\n\nvoid CNewFlameThrower::DecrementContactPointTimers() {\n  for (auto& vec : x380_flameContactPoints) {\n    int end = static_cast<int>(vec.size() - 1);\n    for (size_t i = 0; i < vec.size(); i++) {\n      vec[i].remainingTime--;\n      if (vec[i].remainingTime == 0) {\n        vec[i] = vec[end];\n        vec.pop_back();\n        // forgot to decrement iterator ?\n        // i--;\n        end--;\n      }\n    }\n  }\n}\nvoid CNewFlameThrower::Reset(CStateManager& mgr, bool deactivate) {\n  if (deactivate) {\n    SetLightsActive(mgr, false);\n    SetActive(false);\n    x374_flameState = EFlameState::Default;\n    x2ec_particlesDoneTimer = 0.f;\n    x2f0_flamesDoneTimer = 0.f;\n  } else {\n    x374_flameState = EFlameState::FireStopTimer;\n  }\n  x37c_25_firing = false;\n  x358_mainFireGen->SetParticleEmission(false);\n  x35c_mainSmokeGen->SetParticleEmission(false);\n  x36c_swooshCenterGen->SetParticleEmission(false);\n  x370_swooshFireGen->SetParticleEmission(false);\n}\n\nvoid CNewFlameThrower::SetLightsActive(CStateManager& mgr, bool active) {\n  for (TUniqueId const& uid : x3b8_lightIds) {\n    if (TCastToPtr<CGameLight> light = mgr.ObjectById(uid)) {\n      light->SetActive(active);\n    }\n  }\n}\n\nvoid CNewFlameThrower::UpdateFlameState(float dt, CStateManager& mgr) {\n  bool flame_light_active = false;\n  switch (x374_flameState) {\n  case EFlameState::FireWaitForParticlesDone:\n    x2ec_particlesDoneTimer += dt;\n    if (x2ec_particlesDoneTimer > 0.1f && AreEffectsFinished()) {\n      x374_flameState = EFlameState::Default;\n      Reset(mgr, true);\n    }\n    break;\n  case EFlameState::FireStopTimer:\n    flame_light_active = true;\n    x2f0_flamesDoneTimer = (dt * 4) + x2f0_flamesDoneTimer;\n    if (x2f0_flamesDoneTimer > 1.f) {\n      x2f0_flamesDoneTimer = 1.f;\n      x374_flameState = EFlameState::FireWaitForParticlesDone;\n      x37c_24_renderAuxEffects = false;\n    }\n    break;\n  case EFlameState::FireStart:\n    x374_flameState = EFlameState::FireActive;\n    break;\n  case EFlameState::FireActive:\n    flame_light_active = true;\n    break;\n  default:\n    break;\n  }\n  const float speed = flame_light_active ? 4.f : 1.f;\n  const float target = flame_light_active ? 0.7f : 1.f;\n  SetWorldLighting(mgr, mgr.GetPlayer().GetAreaIdAlways(), speed, target);\n}\n\nvoid CNewFlameThrower::SetWorldLighting(CStateManager& mgr, TAreaId area, float speed, float target) {\n  if (x37c_28_activeLighting && x378_currentLitArea != kInvalidAreaId) {\n    CGameArea* lit_area = mgr.GetWorld()->GetArea(x378_currentLitArea);\n    // Restore previous area's lighting\n    if (!lit_area->IsPostConstructed()) {\n      lit_area->SetWeaponWorldLighting(1.f, 1.f);\n    }\n  }\n  x378_currentLitArea = area;\n  x37c_28_activeLighting = (target != 1.f);\n  if (x378_currentLitArea != kInvalidAreaId) {\n    CGameArea* lit_area = mgr.GetWorld()->GetArea(x378_currentLitArea);\n    if (!lit_area->IsPostConstructed()) {\n      lit_area->SetWeaponWorldLighting(speed, target);\n    }\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CNewFlameThrower.hpp",
    "content": "#pragma once\n\n#include <array>\n#include <memory>\n#include <optional>\n#include <utility>\n#include <vector>\n\n#include <zeus/CFrustum.hpp>\n\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Weapon/CGameProjectile.hpp\"\n\nnamespace metaforce {\nclass CCollisionPrimitive;\nclass CCollisionInfoList;\nclass CCollisionInfo;\n\nclass CNewFlameThrower : public CGameProjectile {\n  struct Contact {\n    Contact(float contact, u32 remainingTime) : contact(contact), remainingTime(remainingTime) {}\n    float contact;\n    u32 remainingTime;\n    bool operator<(Contact const& o) const { return contact < o.contact; }\n  };\n  struct Cube {\n    Cube(zeus::CVector3f center, float bounds) : center(center), bounds(bounds) {}\n    zeus::CVector3f center;\n    float bounds;\n  };\n  enum class EFlameState { Default, FireStart, FireActive, FireStopTimer, FireWaitForParticlesDone };\n\n  CRandom16 x2e8_rand{99};\n  float x2ec_particlesDoneTimer = 0.f;\n  float x2f0_flamesDoneTimer = 0.f;\n  zeus::CVector3f x2f4_lastParticleCollisionLoc;\n  bool x300_wasPointAdded = false;\n  TCachedToken<CGenDescription> x304_mainFire;\n  TCachedToken<CGenDescription> x310_mainSmoke;\n  TCachedToken<CGenDescription> x31c_secondarySmoke;\n  TCachedToken<CGenDescription> x328_secondaryFire;\n  TCachedToken<CGenDescription> x334_secondarySparks;\n  TCachedToken<CSwooshDescription> x340_swooshCenter;\n  TCachedToken<CSwooshDescription> x34c_swooshFire;\n  std::unique_ptr<CElementGen> x358_mainFireGen;\n  std::unique_ptr<CElementGen> x35c_mainSmokeGen;\n  std::unique_ptr<CElementGen> x360_secondarySmokeGen;\n  std::unique_ptr<CElementGen> x364_secondaryFireGen;\n  std::unique_ptr<CElementGen> x368_secondarySparksGen;\n  std::unique_ptr<CParticleSwoosh> x36c_swooshCenterGen;\n  std::unique_ptr<CParticleSwoosh> x370_swooshFireGen;\n  EFlameState x374_flameState = EFlameState::Default;\n  TAreaId x378_currentLitArea = kInvalidAreaId;\n  bool x37c_24_renderAuxEffects : 1 = false;\n  bool x37c_25_firing : 1 = false;\n  bool x37c_26_runningSlowish : 1 = false;\n  bool x37c_27_newPointAdded : 1 = true;\n  bool x37c_28_activeLighting : 1 = false;\n  rstl::reserved_vector<std::vector<Contact>, 3> x380_flameContactPoints;\n  int x3b4_numSmokeParticlesSpawned = 0;\n  rstl::reserved_vector<TUniqueId, 4> x3b8_lightIds;\n\n  // std::array<std::unique_ptr<CTexturedQuadFilter>, 5> beam_filters;\n\n  void DeleteLightObjects(CStateManager& mgr);\n  void CreateLightObjects(CStateManager& mgr);\n  void EnableFx(CStateManager& mgr);\n  void UpdateLights(CStateManager& mgr);\n  bool UpdateParticleCollisions(float dt, CStateManager& mgr, rstl::reserved_vector<Cube, 32>& collisions_out);\n  bool CanDamageActor(CActor& hit_actor, CStateManager& mgr);\n  void AddContactPoint(CCollisionInfo const& cinfo, u32 time);\n  int SortAndFindOverlappingPoints(Cube const& box);\n  bool FindCollisionInNearList(CStateManager& mgr, EntityList const& near_list, CCollisionPrimitive const& coll,\n                               TUniqueId& first_coll_out, CCollisionInfoList& collisions);\n  void DecrementContactPointTimers();\n  void SetLightsActive(CStateManager& mgr, bool active);\n  void UpdateFlameState(float dt, CStateManager& mgr);\n  void SetWorldLighting(CStateManager& mgr, TAreaId area, float speed, float target);\n  // void RenderParticles(std::array<CElementGen *, 5> const& elem_gens);\n\n  // void LoadParticleGenQuads();\n  bool loaded_textures = false;\n\npublic:\n  // Resinfo:\n  //  NFTMainFire\n  //  NFTMainSmoke\n  //  NFTSwooshCenter\n  //  NFTSwooshFire\n  //  NFTSecondarySmoke\n  //  NFTSecondaryFire\n  //  NFTSecondarySparks\n  //  <invalid>\n  DEFINE_ENTITY\n  CNewFlameThrower(const TToken<CWeaponDescription>& desc, std::string_view name, EWeaponType wType,\n                   const std::array<CAssetId, 8>& resInfo, const zeus::CTransform& xf, EMaterialTypes matType,\n                   const CDamageInfo& dInfo, TUniqueId uid, TAreaId aid, TUniqueId owner, EProjectileAttrib attribs);\n  void StartFiring(const zeus::CTransform& xf, CStateManager& mgr);\n  bool CanRenderAuxEffects() const { return x37c_24_renderAuxEffects; }\n  bool IsFiring() const { return x37c_25_firing; }\n  bool AreEffectsFinished() const;\n  void UpdateFx(const zeus::CTransform& xf, float dt, CStateManager& mgr);\n  void Reset(CStateManager& mgr, bool deactivate);\n  void Render(CStateManager& mgr) override;\n  void Think(float dt, CStateManager& mgr) override;\n  std::optional<zeus::CAABox> GetTouchBounds() const override { return {}; }\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) override;\n  void AddToRenderer(zeus::CFrustum const& planes, CStateManager& mgr) override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CPhazonBeam.cpp",
    "content": "#include \"Runtime/Weapon/CPhazonBeam.hpp\"\n\n#include <array>\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Weapon/CProjectileWeapon.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\nnamespace metaforce {\n\nCPhazonBeam::CPhazonBeam(CAssetId characterId, EWeaponType type, TUniqueId playerId, EMaterialTypes playerMaterial,\n                         const zeus::CVector3f& scale)\n: CGunWeapon(characterId, type, playerId, playerMaterial, scale)\n, x238_aaBoxScale(zeus::CVector3f(-0.14664599f, 0.f, -0.14909725f) * scale.y(),\n                  zeus::CVector3f(0.14664599f, 0.64619601f, 0.14909725f) * scale.y())\n, x250_aaBoxTranslate(zeus::CVector3f(-0.0625f, 0.f, -0.09375f) * scale.y(),\n                      zeus::CVector3f(0.0625f, -0.25f, 0.09375f) * scale.y()) {\n  x21c_phazonVeins = g_SimplePool->GetObj(\"PhazonVeins\");\n  x228_phazon2nd1 = g_SimplePool->GetObj(\"Phazon2nd_1\");\n  // m_aaboxShaderScale.setAABB(x238_aaBoxScale);\n  // m_aaboxShaderTranslate.setAABB(x250_aaBoxTranslate);\n}\n\nvoid CPhazonBeam::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) {\n  TAreaId aid = mgr.GetPlayer().GetAreaIdAlways();\n  if (msg == EScriptObjectMessage::Deleted && aid != kInvalidAreaId) {\n    mgr.GetWorld()->GetArea(aid)->SetWeaponWorldLighting(4.f, 1.f);\n  }\n}\n\nvoid CPhazonBeam::StopBeam(CStateManager& mgr, bool b1) {\n  if (x234_chargeFxGen)\n    x234_chargeFxGen->SetParticleEmission(false);\n}\n\nvoid CPhazonBeam::UpdateBeam(float dt, const zeus::CTransform& targetXf, const zeus::CVector3f& localBeamPos,\n                             CStateManager& mgr) {\n  if (x234_chargeFxGen) {\n    x234_chargeFxGen->SetParticleEmission(IsFiring());\n  }\n  CGunWeapon::UpdateMuzzleFx(dt, x4_scale, localBeamPos, IsFiring());\n}\n\nvoid CPhazonBeam::CreateBeam(CStateManager& mgr) {\n  x234_chargeFxGen = std::make_unique<CElementGen>(x228_phazon2nd1);\n  if (x234_chargeFxGen) {\n    x234_chargeFxGen->SetGlobalScale(x4_scale);\n    x234_chargeFxGen->SetParticleEmission(false);\n  }\n}\n\nvoid CPhazonBeam::PreRenderGunFx(const CStateManager& mgr, const zeus::CTransform& xf) {\n  if (IsFiring()) {\n    zeus::CTransform backupView = CGraphics::mViewMatrix;\n    CGraphics::SetViewPointMatrix(xf.inverse() * backupView);\n    CGraphics::SetModelMatrix(zeus::CTransform());\n    CGunWeapon::DrawMuzzleFx(mgr);\n    CGraphics::SetViewPointMatrix(backupView);\n  }\n}\n\nvoid CPhazonBeam::PostRenderGunFx(const CStateManager& mgr, const zeus::CTransform& xf) {\n  if (x234_chargeFxGen) {\n    x234_chargeFxGen->Render();\n  }\n  CGunWeapon::PostRenderGunFx(mgr, xf);\n}\n\nvoid CPhazonBeam::UpdateGunFx(bool shotSmoke, float dt, const CStateManager& mgr, const zeus::CTransform& xf) {\n  if (x234_chargeFxGen) {\n    x234_chargeFxGen->SetGlobalOrientAndTrans(xf);\n    x234_chargeFxGen->Update(dt);\n  }\n\n  CGunWeapon::UpdateGunFx(shotSmoke, dt, mgr, xf);\n}\n\nvoid CPhazonBeam::Fire(bool underwater, float dt, EChargeState chargeState, const zeus::CTransform& xf,\n                       CStateManager& mgr, TUniqueId homingTarget, float chargeFactor1, float chargeFactor2) {\n  if (chargeState == EChargeState::Normal) {\n    ActivateCharge(false, false);\n    int count = x278_fireTime > 1.f / 3.f ? 5 : 2;\n    int seedOffset = 0;\n    for (int i = 0; i < count; ++i, seedOffset += 1000) {\n      CProjectileWeapon::SetGlobalSeed(u16(mgr.GetUpdateFrameIndex() + seedOffset));\n      CGunWeapon::Fire(underwater, dt, chargeState, xf, mgr, homingTarget, chargeFactor1, chargeFactor2);\n      CProjectileWeapon::SetGlobalSeed(u16(mgr.GetUpdateFrameIndex()));\n    }\n    x278_fireTime = 0.f;\n  } else {\n    CGunWeapon::Fire(underwater, dt, chargeState, xf, mgr, homingTarget, chargeFactor1, chargeFactor2);\n  }\n\n  static constexpr std::array<u16, 2> soundId{SFXwpn_fire_phazon_normal, SFXwpn_fire_power_charged};\n  NWeaponTypes::play_sfx(soundId[size_t(chargeState)], underwater, false, 0.165f);\n}\n\nvoid CPhazonBeam::Update(float dt, CStateManager& mgr) {\n  CGunWeapon::Update(dt, mgr);\n  x278_fireTime += dt;\n  TAreaId aid = mgr.GetPlayer().GetAreaIdAlways();\n  if (aid != kInvalidAreaId) {\n    CGameArea* area = mgr.GetWorld()->GetArea(aid);\n    if (x278_fireTime > 1.f / 6.f) {\n      area->SetWeaponWorldLighting(4.f, 1.f);\n    } else {\n      area->SetWeaponWorldLighting(4.f, 0.9f);\n    }\n  }\n\n  if (!IsLoaded()) {\n    if (CGunWeapon::IsLoaded() && !x274_24_loaded) {\n      x274_24_loaded = x228_phazon2nd1.IsLoaded() && x21c_phazonVeins.IsLoaded();\n      if (x274_24_loaded) {\n        CreateBeam(mgr);\n        x224_phazonVeinsData = std::make_unique<CModelData>(CStaticRes(\n            NWeaponTypes::get_asset_id_from_name(x274_27_phazonVeinsIdx ? \"PhazonVeins_2\" : \"PhazonVeins\"), x4_scale));\n        x21c_phazonVeins.Unlock();\n        x274_25_clipWipeActive = true;\n      }\n    }\n  } else if (x274_25_clipWipeActive) {\n    x268_clipWipeScale += 0.75f * dt;\n    if (x268_clipWipeScale > 1.f) {\n      x268_clipWipeScale = 1.f;\n    }\n    if (x268_clipWipeScale > 0.4f) {\n      if (x26c_clipWipeTranslate < 0.5f) {\n        x26c_clipWipeTranslate += 0.75f * dt;\n      } else {\n        x274_25_clipWipeActive = false;\n      }\n    }\n  } else if (x274_26_veinsAlphaActive) {\n    x270_indirectAlpha = x10_solidModelData->GetLocatorTransform(\"phazonScale_LCTR_SDK\").origin.y();\n  }\n}\n\nvoid CPhazonBeam::Load(CStateManager& mgr, bool subtypeBasePose) {\n  CGunWeapon::Load(mgr, subtypeBasePose);\n  x228_phazon2nd1.Lock();\n  x274_27_phazonVeinsIdx = (mgr.GetActiveRandom()->Next() & 0x2) != 0;\n  x21c_phazonVeins = g_SimplePool->GetObj(x274_27_phazonVeinsIdx ? \"PhazonVeins_2\" : \"PhazonVeins\");\n  x21c_phazonVeins.Lock();\n}\n\nvoid CPhazonBeam::ReInitVariables() {\n  x268_clipWipeScale = 0.f;\n  x26c_clipWipeTranslate = 0.f;\n  x270_indirectAlpha = 1.f;\n  x234_chargeFxGen.reset();\n  x224_phazonVeinsData.reset();\n  x274_24_loaded = false;\n  x274_25_clipWipeActive = true;\n  x274_26_veinsAlphaActive = false;\n  x1cc_enabledSecondaryEffect = ESecondaryFxType::None;\n}\n\nvoid CPhazonBeam::Unload(CStateManager& mgr) {\n  CGunWeapon::Unload(mgr);\n  x228_phazon2nd1.Unlock();\n  x21c_phazonVeins.Unlock();\n  ReInitVariables();\n}\n\nbool CPhazonBeam::IsLoaded() const { return CGunWeapon::IsLoaded() && x274_24_loaded; }\n\nvoid CPhazonBeam::DrawClipScaleCube() {\n  // Render AABB as completely transparent object, only modifying Z-buffer\n  // m_aaboxShaderScale.draw(zeus::skClear);\n}\n\nvoid CPhazonBeam::DrawClipTranslateCube() {\n  // Render AABB as completely transparent object, only modifying Z-buffer\n  // m_aaboxShaderTranslate.draw(zeus::skClear);\n}\n\nvoid CPhazonBeam::Draw(bool drawSuitArm, const CStateManager& mgr, const zeus::CTransform& xf, const CModelFlags& flags,\n                       const CActorLights* lights) {\n  CPlayerState::EPlayerVisor visor = mgr.GetPlayerState()->GetActiveVisor(mgr);\n  bool drawIndirect = visor == CPlayerState::EPlayerVisor::Combat || visor == CPlayerState::EPlayerVisor::Scan;\n\n  if (drawIndirect) {\n    // TODO CGraphics::ResolveSpareTexture(CGraphics::g_Viewport);\n    CModelFlags tmpFlags = flags;\n    // TODO tmpFlags.m_extendedShader = EExtendedShader::SolidColorBackfaceCullLEqualAlphaOnly;\n    CGunWeapon::Draw(drawSuitArm, mgr, xf, tmpFlags, lights);\n  }\n\n  CGunWeapon::Draw(drawSuitArm, mgr, xf, flags, lights);\n\n  if (drawIndirect) {\n    g_Renderer->DrawPhazonSuitIndirectEffect(zeus::CColor(0.3f * x270_indirectAlpha, 0.6f * x270_indirectAlpha,\n                                                          x270_indirectAlpha, 0.5f * x270_indirectAlpha),\n                                             {}, zeus::skWhite, 1.f, 0.f, 0.f, 0.f);\n  }\n\n  if (x224_phazonVeinsData) {\n    zeus::CTransform modelXf = xf * x10_solidModelData->GetScaledLocatorTransform(\"elbow\");\n    if (x274_25_clipWipeActive) {\n      CGraphics::SetModelMatrix(modelXf * zeus::CTransform::Scale(1.f - x268_clipWipeScale));\n      DrawClipScaleCube();\n      CGraphics::SetModelMatrix(modelXf * zeus::CTransform::Translate(0.f, x26c_clipWipeTranslate, 0.f));\n      DrawClipTranslateCube();\n    }\n    if (x274_26_veinsAlphaActive) {\n      CModelFlags useFlags(5, 0, 3, zeus::CColor(1.f, 0.5f * x270_indirectAlpha));\n      x224_phazonVeinsData->Render(mgr, modelXf * zeus::CTransform::Scale(x270_indirectAlpha), lights, useFlags);\n    } else {\n      x224_phazonVeinsData->Render(mgr, modelXf, lights, flags);\n    }\n  }\n}\n\nvoid CPhazonBeam::DrawMuzzleFx(const CStateManager& mgr) const {\n  if (!IsFiring()) {\n    return;\n  }\n  CGunWeapon::DrawMuzzleFx(mgr);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CPhazonBeam.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/Weapon/CGunWeapon.hpp\"\n\nnamespace metaforce {\n\nclass CPhazonBeam final : public CGunWeapon {\n  TCachedToken<CModel> x21c_phazonVeins;\n  std::unique_ptr<CModelData> x224_phazonVeinsData;\n  TCachedToken<CGenDescription> x228_phazon2nd1;\n  std::unique_ptr<CElementGen> x234_chargeFxGen;\n  zeus::CAABox x238_aaBoxScale;\n  zeus::CAABox x250_aaBoxTranslate;\n  float x268_clipWipeScale = 0.f;\n  float x26c_clipWipeTranslate = 0.f;\n  float x270_indirectAlpha = 1.f;\n  bool x274_24_loaded : 1 = false;\n  bool x274_25_clipWipeActive : 1 = true;\n  bool x274_26_veinsAlphaActive : 1 = false;\n  bool x274_27_phazonVeinsIdx : 1 = false;\n  float x278_fireTime = 1.f / 3.f;\n\n  void ReInitVariables();\n  void DrawClipScaleCube();\n  void DrawClipTranslateCube();\n\npublic:\n  CPhazonBeam(CAssetId characterId, EWeaponType type, TUniqueId playerId, EMaterialTypes playerMaterial,\n              const zeus::CVector3f& scale);\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr);\n  bool IsFiring() const { return x278_fireTime < 1.f / 6.f; }\n  void SetClipWipeActive(bool b) { x274_25_clipWipeActive = b; }\n  void SetVeinsAlphaActive(bool b) { x274_26_veinsAlphaActive = b; }\n  void StopBeam(CStateManager& mgr, bool b1);\n  void UpdateBeam(float dt, const zeus::CTransform& targetXf, const zeus::CVector3f& localBeamPos, CStateManager& mgr);\n  void CreateBeam(CStateManager& mgr);\n\n  void PreRenderGunFx(const CStateManager& mgr, const zeus::CTransform& xf) override;\n  void PostRenderGunFx(const CStateManager& mgr, const zeus::CTransform& xf) override;\n  void UpdateGunFx(bool shotSmoke, float dt, const CStateManager& mgr, const zeus::CTransform& xf) override;\n  void Fire(bool underwater, float dt, EChargeState chargeState, const zeus::CTransform& xf, CStateManager& mgr,\n            TUniqueId homingTarget, float chargeFactor1, float chargeFactor2) override;\n  void Update(float dt, CStateManager& mgr) override;\n  void Load(CStateManager& mgr, bool subtypeBasePose) override;\n  void Unload(CStateManager& mgr) override;\n  bool IsLoaded() const override;\n  void Draw(bool drawSuitArm, const CStateManager& mgr, const zeus::CTransform& xf, const CModelFlags& flags,\n            const CActorLights* lights) override;\n  void DrawMuzzleFx(const CStateManager& mgr) const override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CPlasmaBeam.cpp",
    "content": "#include \"Runtime/Weapon/CPlasmaBeam.hpp\"\n\n#include <array>\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\nnamespace metaforce {\nnamespace {\nconstexpr CCameraShakeData CameraShaker{0.125f, 0.25f};\nconstexpr std::array<u16, 2> kSoundId{SFXwpn_fire_plasma_normal, SFXwpn_fire_plasma_charged};\n} // Anonymous namespace\n\nCPlasmaBeam::CPlasmaBeam(CAssetId characterId, EWeaponType type, TUniqueId playerId, EMaterialTypes playerMaterial,\n                         const zeus::CVector3f& scale)\n: CGunWeapon(characterId, type, playerId, playerMaterial, scale) {\n  x21c_plasma2nd1 = g_SimplePool->GetObj(\"Plasma2nd_1\");\n}\n\nvoid CPlasmaBeam::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) {\n  if (msg == EScriptObjectMessage::Deleted)\n    DeleteBeam(mgr);\n}\n\nvoid CPlasmaBeam::SetWorldLighting(CStateManager& mgr, TAreaId aid, float speed, float target) {\n  if (x22c_25_worldLighingDim && x23c_stateArea != aid && x23c_stateArea != kInvalidAreaId) {\n    CGameArea* area = mgr.GetWorld()->GetArea(x23c_stateArea);\n    if (area->IsPostConstructed())\n      area->SetWeaponWorldLighting(2.f, 1.f);\n  }\n\n  x23c_stateArea = aid;\n  x22c_25_worldLighingDim = target != 1.f;\n\n  if (x23c_stateArea != kInvalidAreaId) {\n    CGameArea* area = mgr.GetWorld()->GetArea(x23c_stateArea);\n    if (area->IsPostConstructed())\n      area->SetWeaponWorldLighting(speed, target);\n  }\n}\n\nvoid CPlasmaBeam::DeleteBeam(CStateManager& mgr) {\n  if (x22c_25_worldLighingDim)\n    SetWorldLighting(mgr, mgr.GetPlayer().GetAreaIdAlways(), 2.f, 1.f);\n}\n\nvoid CPlasmaBeam::PostRenderGunFx(const CStateManager& mgr, const zeus::CTransform& xf) {\n  if (x228_chargeFx && x1cc_enabledSecondaryEffect != ESecondaryFxType::None)\n    x228_chargeFx->Render();\n  CGunWeapon::PostRenderGunFx(mgr, xf);\n}\n\nvoid CPlasmaBeam::UpdateGunFx(bool shotSmoke, float dt, const CStateManager& mgr, const zeus::CTransform& xf) {\n  if (x228_chargeFx && x1cc_enabledSecondaryEffect != ESecondaryFxType::None) {\n    if (x228_chargeFx->IsSystemDeletable())\n      x1cc_enabledSecondaryEffect = ESecondaryFxType::None;\n    x228_chargeFx->SetTranslation(xf.origin);\n    x228_chargeFx->SetOrientation(xf.getRotation());\n    x228_chargeFx->Update(dt);\n  }\n  CGunWeapon::UpdateGunFx(shotSmoke, dt, mgr, xf);\n}\n\nvoid CPlasmaBeam::Fire(bool underwater, float dt, EChargeState chargeState, const zeus::CTransform& xf,\n                       CStateManager& mgr, TUniqueId homingTarget, float chargeFactor1, float chargeFactor2) {\n  bool fired = false;\n  if (chargeState == EChargeState::Normal) {\n    if (x230_fireShotDelayTimer < 0.01f) {\n      ActivateCharge(false, true);\n      CGunWeapon::Fire(underwater, dt, chargeState, xf, mgr, homingTarget, chargeFactor1, chargeFactor2);\n      x230_fireShotDelayTimer += 0.33f;\n      x234_fireShotDelay = 0.33f;\n      fired = true;\n    }\n  } else {\n    CGunWeapon::Fire(underwater, dt, chargeState, xf, mgr, homingTarget, chargeFactor1, 1.f);\n    mgr.GetCameraManager()->AddCameraShaker(CameraShaker, false);\n    x238_lightingResetDelayTimer = 0.65f;\n    SetWorldLighting(mgr, mgr.GetPlayer().GetAreaIdAlways(), 8.f, 0.7f);\n    fired = true;\n  }\n\n  if (!fired) {\n    return;\n  }\n\n  NWeaponTypes::play_sfx(kSoundId[size_t(chargeState)], underwater, false, 0.165f);\n}\n\nvoid CPlasmaBeam::EnableSecondaryFx(ESecondaryFxType type) {\n  switch (type) {\n  case ESecondaryFxType::CancelCharge:\n    if (x1cc_enabledSecondaryEffect == ESecondaryFxType::None || !x228_chargeFx)\n      return;\n    x228_chargeFx->SetParticleEmission(false);\n    break;\n  case ESecondaryFxType::Charge:\n    x228_chargeFx = std::make_unique<CElementGen>(x21c_plasma2nd1);\n    x228_chargeFx->SetGlobalScale(x4_scale);\n    break;\n  default:\n    break;\n  }\n  x1cc_enabledSecondaryEffect = type;\n}\n\nvoid CPlasmaBeam::Update(float dt, CStateManager& mgr) {\n  CGunWeapon::Update(dt, mgr);\n  x230_fireShotDelayTimer = std::max(0.f, x230_fireShotDelayTimer - dt);\n  x238_lightingResetDelayTimer -= dt;\n  if ((mgr.GetPlayer().GetPlayerGun()->IsCharging() ? mgr.GetPlayer().GetPlayerGun()->GetChargeBeamFactor() : 0.f) >\n      0.5f)\n    SetWorldLighting(mgr, mgr.GetPlayer().GetAreaIdAlways(), 0.2f, 0.8f);\n  else if (x238_lightingResetDelayTimer < 0.f && x22c_25_worldLighingDim)\n    SetWorldLighting(mgr, mgr.GetPlayer().GetAreaIdAlways(), 2.f, 1.f);\n\n  if (IsLoaded())\n    return;\n\n  if (CGunWeapon::IsLoaded() && !x22c_24_loaded) {\n    x22c_24_loaded = x21c_plasma2nd1.IsLoaded();\n    if (x22c_24_loaded)\n      CreateBeam(mgr);\n  }\n}\n\nvoid CPlasmaBeam::Load(CStateManager& mgr, bool subtypeBasePose) {\n  CGunWeapon::Load(mgr, subtypeBasePose);\n  x21c_plasma2nd1.Lock();\n}\n\nvoid CPlasmaBeam::ReInitVariables() {\n  x228_chargeFx.reset();\n  x22c_24_loaded = false;\n  x1cc_enabledSecondaryEffect = ESecondaryFxType::None;\n}\n\nvoid CPlasmaBeam::Unload(CStateManager& mgr) {\n  CGunWeapon::Unload(mgr);\n  x21c_plasma2nd1.Unlock();\n  DeleteBeam(mgr);\n  ReInitVariables();\n}\n\nbool CPlasmaBeam::IsLoaded() const { return CGunWeapon::IsLoaded() && x22c_24_loaded; }\n\n} // namespace metaforce"
  },
  {
    "path": "Runtime/Weapon/CPlasmaBeam.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include \"Runtime/Weapon/CGunWeapon.hpp\"\n\nnamespace metaforce {\n\nclass CPlasmaBeam final : public CGunWeapon {\n  TCachedToken<CGenDescription> x21c_plasma2nd1;\n  std::unique_ptr<CElementGen> x228_chargeFx;\n  bool x22c_24_loaded : 1 = false;\n  bool x22c_25_worldLighingDim : 1 = false;\n  float x230_fireShotDelayTimer = 0.f;\n  float x234_fireShotDelay = 0.f;\n  float x238_lightingResetDelayTimer = 0.f;\n  TAreaId x23c_stateArea = kInvalidAreaId;\n  void ReInitVariables();\n  void SetWorldLighting(CStateManager& mgr, TAreaId aid, float speed, float target);\n\npublic:\n  CPlasmaBeam(CAssetId characterId, EWeaponType type, TUniqueId playerId, EMaterialTypes playerMaterial,\n              const zeus::CVector3f& scale);\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr);\n  bool IsFiring() const { return x234_fireShotDelay > 0.f; }\n  void StopBeam(CStateManager& mgr, bool b1) { /* Empty */\n  }\n  void CreateBeam(CStateManager& mgr) { /* Empty */\n  }\n  void UpdateBeam(float dt, const zeus::CTransform& targetXf, const zeus::CVector3f& localBeamPos,\n                  CStateManager& mgr) { /* Empty */\n  }\n  void DeleteBeam(CStateManager& mgr);\n\n  void PostRenderGunFx(const CStateManager& mgr, const zeus::CTransform& xf) override;\n  void UpdateGunFx(bool shotSmoke, float dt, const CStateManager& mgr, const zeus::CTransform& xf) override;\n  void Fire(bool underwater, float dt, EChargeState chargeState, const zeus::CTransform& xf, CStateManager& mgr,\n            TUniqueId homingTarget, float chargeFactor1, float chargeFactor2) override;\n  void EnableSecondaryFx(ESecondaryFxType type) override;\n  void Update(float dt, CStateManager& mgr) override;\n  void Load(CStateManager& mgr, bool subtypeBasePose) override;\n  void Unload(CStateManager& mgr) override;\n  bool IsLoaded() const override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CPlasmaProjectile.cpp",
    "content": "#include \"Runtime/Weapon/CPlasmaProjectile.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/World/CGameLight.hpp\"\n#include \"Runtime/World/CHUDBillboardEffect.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\n//CPlasmaProjectile::RenderObjects::RenderObjects(aurora::gfx::TextureHandle tex, aurora::gfx::TextureHandle glowTex)\n//: m_beamStrip1(8, CColoredStripShader::Mode::Additive, {})\n//, m_beamStrip2(10, CColoredStripShader::Mode::FullAdditive, tex)\n//, m_beamStrip3(18, CColoredStripShader::Mode::FullAdditive, tex)\n//, m_beamStrip4(14, CColoredStripShader::Mode::Additive, glowTex)\n//, m_beamStrip1Sub(8, CColoredStripShader::Mode::Subtractive, {})\n//, m_beamStrip2Sub(10, CColoredStripShader::Mode::Subtractive, tex)\n//, m_beamStrip3Sub(18, CColoredStripShader::Mode::Subtractive, tex)\n//, m_beamStrip4Sub(14, CColoredStripShader::Mode::Subtractive, glowTex)\n//, m_motionBlurStrip(16, CColoredStripShader::Mode::Alpha, {}) {}\n\nCPlasmaProjectile::CPlasmaProjectile(const TToken<CWeaponDescription>& wDesc, std::string_view name, EWeaponType wType,\n                                     const CBeamInfo& bInfo, const zeus::CTransform& xf, EMaterialTypes matType,\n                                     const CDamageInfo& dInfo, TUniqueId uid, TAreaId aid, TUniqueId owner,\n                                     const PlayerEffectResources& res, bool growingBeam, EProjectileAttrib attribs)\n: CBeamProjectile(wDesc, name, wType, xf, bInfo.GetLength(), bInfo.GetRadius(), bInfo.GetTravelSpeed(), matType, dInfo,\n                  uid, aid, owner, attribs, growingBeam)\n, x478_beamAttributes(bInfo.GetBeamAttributes())\n, x47c_lifeTime(bInfo.GetLifeTime())\n, x480_pulseSpeed(bInfo.GetPulseSpeed())\n, x484_shutdownTime(bInfo.GetShutdownTime())\n, x488_expansionSpeed(bInfo.GetExpansionSpeed())\n, x48c_(bInfo.GetLength() / 32.f)\n, x490_innerColor(bInfo.GetInnerColor())\n, x494_outerColor(bInfo.GetOuterColor())\n, x548_28_drawOwnerFirst(growingBeam) {\n  x4e8_texture = g_SimplePool->GetObj(SObjectTag{FOURCC('TXTR'), bInfo.GetTextureId()});\n  x4f4_glowTexture = g_SimplePool->GetObj(SObjectTag{FOURCC('TXTR'), bInfo.GetGlowTextureId()});\n  x500_contactFxDesc = g_SimplePool->GetObj(SObjectTag{FOURCC('PART'), bInfo.GetContactFxId()});\n  x50c_pulseFxDesc = g_SimplePool->GetObj(SObjectTag{FOURCC('PART'), bInfo.GetPulseFxId()});\n  x518_contactGen = std::make_unique<CElementGen>(x500_contactFxDesc, CElementGen::EModelOrientationType::One);\n  x51c_pulseGen = std::make_unique<CElementGen>(x50c_pulseFxDesc, CElementGen::EModelOrientationType::Normal);\n  x524_freezeSteamTxtr = res[0];\n  x528_freezeIceTxtr = res[1];\n  if (res[2].IsValid()) {\n    x52c_visorElectric = g_SimplePool->GetObj(SObjectTag{FOURCC('ELSC'), res[2]});\n  }\n  if (res[3].IsValid()) {\n    x538_visorParticle = g_SimplePool->GetObj(SObjectTag{FOURCC('PART'), res[3]});\n  }\n  x544_freezeSfx = CSfxManager::TranslateSFXID(u16(res[4].Value()));\n  x546_electricSfx = CSfxManager::TranslateSFXID(u16(res[5].Value()));\n  x518_contactGen->SetGlobalScale(zeus::CVector3f(bInfo.GetContactFxScale()));\n  x51c_pulseGen->SetGlobalScale(zeus::CVector3f(bInfo.GetPulseFxScale()));\n  x518_contactGen->SetParticleEmission(false);\n  x51c_pulseGen->SetParticleEmission(false);\n\n//  CGraphics::CommitResources([this](boo::IGraphicsDataFactory::Context& ctx) {\n//    m_renderObjs.emplace(x4e8_texture->GetTexture(), x4f4_glowTexture->GetTexture());\n//    return true;\n//  } BooTrace);\n}\n\nvoid CPlasmaProjectile::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CPlasmaProjectile::SetLightsActive(bool active, CStateManager& mgr) {\n  for (TUniqueId lid : x468_lights) {\n    if (lid != kInvalidUniqueId) {\n      if (TCastToPtr<CGameLight> light = mgr.ObjectById(lid)) {\n        light->SetActive(active);\n      }\n    }\n  }\n}\n\nvoid CPlasmaProjectile::CreatePlasmaLights(u32 sourceId, const CLight& l, CStateManager& mgr) {\n  DeletePlasmaLights(mgr);\n  x468_lights.reserve(3);\n  for (size_t i = 0; i < x468_lights.capacity(); ++i) {\n    const TUniqueId lid = mgr.AllocateUniqueId();\n    auto* light = new CGameLight(lid, GetAreaId(), GetActive(), \"\", GetTransform(), GetUniqueId(), l, sourceId, 0, 0.f);\n    mgr.AddObject(light);\n    x468_lights.push_back(lid);\n  }\n}\n\nvoid CPlasmaProjectile::DeletePlasmaLights(CStateManager& mgr) {\n  for (TUniqueId lid : x468_lights)\n    mgr.FreeScriptObject(lid);\n  x468_lights.clear();\n}\n\nvoid CPlasmaProjectile::UpdateLights(float expansion, float dt, CStateManager& mgr) {\n  if (x520_weaponGen) {\n    x520_weaponGen->Update(dt);\n    CLight l = x520_weaponGen->GetLight();\n    l.SetColor(zeus::CColor::lerp(zeus::skClear, l.GetColor(), expansion));\n    float halfLen = 0.5f * GetCurrentLength();\n    float y = 0.f;\n    for (TUniqueId lid : x468_lights) {\n      if (TCastToPtr<CGameLight> light = mgr.ObjectById(lid)) {\n        light->SetTransform({});\n        light->SetTranslation(GetBeamTransform() * zeus::CVector3f(0.f, y, 0.f));\n        light->SetLight(l);\n      }\n      y += halfLen;\n    }\n  }\n}\n\nvoid CPlasmaProjectile::UpdateEnergyPulse(float dt) {\n  if (GetDamageType() != EDamageType::None && x548_25_enableEnergyPulse) {\n    x4d8_energyPulseTimer -= dt;\n    if (x4d8_energyPulseTimer <= 0.f) {\n      x4d8_energyPulseTimer = 2.f * dt;\n      x51c_pulseGen->SetParticleEmission(true);\n      float t = GetCurrentLength() / GetMaxLength();\n      for (float i = 0.f; i <= t; i += 0.1f) {\n        float y = i * GetMaxLength() + x4cc_energyPulseStartY;\n        if (y > GetCurrentLength())\n          continue;\n        x51c_pulseGen->SetTranslation({0.f, y, 0.f});\n        x51c_pulseGen->ForceParticleCreation(1);\n      }\n      x51c_pulseGen->SetGlobalOrientAndTrans(GetBeamTransform());\n      x51c_pulseGen->SetParticleEmission(false);\n    }\n  }\n  x51c_pulseGen->Update(dt);\n}\n\nvoid CPlasmaProjectile::RenderMotionBlur() {\n  CGraphics::SetModelMatrix({});\n  zeus::CColor color1 = x494_outerColor;\n  zeus::CColor color2 = x494_outerColor;\n  color1.a() = 63.f / 255.f;\n  color2.a() = 0.f;\n  // std::array<CColoredStripShader::Vert, 16> verts;\n  // for (size_t i = 0; i < verts.size() / 2; ++i) {\n  //   auto& v1 = verts[i * 2];\n  //   auto& v2 = verts[i * 2 + 1];\n  //   v1.m_pos = GetBeamTransform().origin;\n  //   v1.m_color = zeus::CColor::lerp(color1, color2, float(i) / 8.f);\n  //   v2.m_pos = GetPointCache()[i];\n  //   v2.m_color = v1.m_color;\n  // }\n//  m_renderObjs->m_motionBlurStrip.draw(zeus::skWhite, verts.size(), verts.data());\n}\n\n// void CPlasmaProjectile::RenderBeam(s32 subdivs, float width, const zeus::CColor& color, s32 flags,\n//                                    CColoredStripShader& shader) const {\n//   // Flags: 0x1: textured, 0x2: length controlled UVY 0x4: alpha controlled additive blend,\n//   // 0x8: glow texture, 0x10: subtractive blend\n//   if ((flags & 0x1) == 0 || (flags & 0x8) ? x4f4_glowTexture.IsLoaded() : x4e8_texture.IsLoaded()) {\n//     float angIncrement = 2.f * M_PIF / float(subdivs);\n//     float uvY1 = -(x4cc_energyPulseStartY / 16.f);\n//     float uvY2 = (uvY1 + float((flags & 0x3) == 0x3) != 0.f) ? 2.f : 0.5f * GetCurrentLength();\n//     std::array<CColoredStripShader::Vert, 18> verts;\n//     s32 numNodes = subdivs + 1;\n//     float angle = 0.f;\n//     bool flip = false;\n//     for (s32 i = 0; i < numNodes; ++i) {\n//       CColoredStripShader::Vert& v0 = verts[i * 2];\n//       CColoredStripShader::Vert& v1 = verts[i * 2 + 1];\n//       float x = std::cos(angle);\n//       float y = std::sin(angle);\n//       float uvX;\n//       if (flags & 0x8)\n//         uvX = 0.5f * y;\n//       else if (flip)\n//         uvX = width;\n//       else\n//         uvX = 0.f;\n//       flip ^= true;\n//       v0.m_pos = zeus::CVector3f(width * x, 0.f, width * y);\n//       v0.m_color = color;\n//       v0.m_uv = zeus::CVector2f(uvX, uvY1);\n//       v1.m_pos = zeus::CVector3f(width * x, GetCurrentLength(), width * y);\n//       v1.m_color = color;\n//       v1.m_uv = zeus::CVector2f(uvX, uvY2);\n//       angle += angIncrement;\n//     }\n//     shader.draw(zeus::skWhite, numNodes * 2, verts.data());\n//   }\n// }\n\nvoid CPlasmaProjectile::ResetBeam(CStateManager& mgr, bool fullReset) {\n  if (fullReset) {\n    SetActive(false);\n    SetLightsActive(false, mgr);\n    x4bc_lifeTimer = 0.f;\n    x4c0_expansionT = 0.f;\n    x4c8_beamAngle = 0.f;\n    x4d0_shutdownTimer = 0.f;\n    x4d4_contactPulseTimer = 0.f;\n    x4d8_energyPulseTimer = 0.f;\n    x4dc_playerEffectPulseTimer = 0.f;\n    x4b4_expansionState = EExpansionState::Inactive;\n    x548_26_firing = false;\n    x518_contactGen->SetParticleEmission(false);\n    x51c_pulseGen->SetParticleEmission(false);\n  } else {\n    x548_26_firing = false;\n    x4b4_expansionState = EExpansionState::Release;\n    x518_contactGen->SetParticleEmission(false);\n    x51c_pulseGen->SetParticleEmission(false);\n  }\n}\n\nfloat CPlasmaProjectile::UpdateBeamState(float dt, CStateManager& mgr) {\n  switch (x4b4_expansionState) {\n  case EExpansionState::Attack:\n    if (x4c0_expansionT > 0.5f)\n      x4b4_expansionState = EExpansionState::Sustain;\n    else\n      x4c0_expansionT += dt * x488_expansionSpeed;\n    break;\n  case EExpansionState::Sustain:\n    if (x478_beamAttributes & 0x4) {\n      if (x4bc_lifeTimer > x47c_lifeTime)\n        x4b4_expansionState = EExpansionState::Release;\n      else\n        x4bc_lifeTimer += dt;\n    }\n    break;\n  case EExpansionState::Release:\n    x4c0_expansionT += dt * x488_expansionSpeed;\n    if (x4c0_expansionT > 1.f) {\n      x4c0_expansionT = 1.f;\n      x4b4_expansionState = EExpansionState::Done;\n      x548_25_enableEnergyPulse = false;\n    }\n    break;\n  case EExpansionState::Done:\n    x4d0_shutdownTimer += dt;\n    if (x4d0_shutdownTimer > x484_shutdownTime && (!x518_contactGen || x518_contactGen->GetParticleCountAll() == 0)) {\n      x4b4_expansionState = EExpansionState::Inactive;\n      ResetBeam(mgr, true);\n    }\n    break;\n  default:\n    break;\n  }\n  return -4.f * x4c0_expansionT * (x4c0_expansionT - 1.f);\n}\n\nvoid CPlasmaProjectile::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) {\n  switch (msg) {\n  case EScriptObjectMessage::Registered: {\n    xe6_27_thermalVisorFlags = 2;\n    const auto& weaponDesc = x170_projectile.GetWeaponDescription();\n    if (weaponDesc->x34_APSM)\n      x520_weaponGen = std::make_unique<CElementGen>(*weaponDesc->x34_APSM);\n    if (x520_weaponGen && x520_weaponGen->SystemHasLight())\n      CreatePlasmaLights(weaponDesc.GetObjectTag()->id.Value(), x520_weaponGen->GetLight(), mgr);\n    else\n      x520_weaponGen.reset();\n    if (x548_28_drawOwnerFirst)\n      xc6_nextDrawNode = xec_ownerId;\n    mgr.AddWeaponId(xec_ownerId, xf0_weaponType);\n    break;\n  }\n  case EScriptObjectMessage::Deleted:\n    mgr.RemoveWeaponId(xec_ownerId, xf0_weaponType);\n    DeletePlasmaLights(mgr);\n    if (x548_29_activePlayerPhazon) {\n      mgr.GetPlayer().DecrementEnvironmentDamage();\n      x548_29_activePlayerPhazon = false;\n    }\n    break;\n  default:\n    break;\n  }\n  CGameProjectile::AcceptScriptMsg(msg, sender, mgr);\n}\n\nvoid CPlasmaProjectile::MakeBillboardEffect(const std::optional<TToken<CGenDescription>>& particle,\n                                            const std::optional<TToken<CElectricDescription>>& electric,\n                                            std::string_view name, CStateManager& mgr) {\n  auto* effect = new CHUDBillboardEffect(\n      particle, electric, mgr.AllocateUniqueId(), true, name, CHUDBillboardEffect::GetNearClipDistance(mgr),\n      CHUDBillboardEffect::GetScaleForPOV(mgr), zeus::skWhite, zeus::skOne3f, zeus::skZero3f);\n  mgr.AddObject(effect);\n}\n\nvoid CPlasmaProjectile::UpdatePlayerEffects(float dt, CStateManager& mgr) {\n  x4dc_playerEffectPulseTimer -= dt;\n  if ((x4b4_expansionState == EExpansionState::Attack || x4b4_expansionState == EExpansionState::Sustain) &&\n      x4dc_playerEffectPulseTimer <= 0.f && GetDamageType() == EDamageType::Actor &&\n      GetCollisionActorId() == mgr.GetPlayer().GetUniqueId()) {\n    if ((x478_beamAttributes & 0x8) && !x548_29_activePlayerPhazon) {\n      x548_29_activePlayerPhazon = true;\n      x4e4_playerDamageTimer = 0.f;\n      mgr.GetPlayer().IncrementEnvironmentDamage();\n    }\n    switch (xf0_weaponType) {\n    case EWeaponType::Ice:\n      mgr.GetPlayer().Freeze(mgr, x524_freezeSteamTxtr, x544_freezeSfx, x528_freezeIceTxtr);\n      break;\n    case EWeaponType::Wave:\n      if (x52c_visorElectric) {\n        MakeBillboardEffect({}, {x52c_visorElectric}, \"PlasmaElectricFx\"sv, mgr);\n        CSfxManager::SfxStart(x546_electricSfx, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n        mgr.GetPlayer().SetHudDisable(3.f, 0.5f, 2.5f);\n        mgr.GetPlayer().TryToBreakOrbit(mgr.GetPlayer().GetOrbitTargetId(),\n                                                 CPlayer::EPlayerOrbitRequest::ActivateOrbitSource, mgr);\n        mgr.GetPlayerState()->GetStaticInterference().AddSource(GetUniqueId(), 0.2f, 3.f);\n      }\n      break;\n    case EWeaponType::Plasma:\n      if (x538_visorParticle)\n        MakeBillboardEffect({x538_visorParticle}, {}, \"PlasmaVisorFx\"sv, mgr);\n      break;\n    default:\n      break;\n    }\n    x4dc_playerEffectPulseTimer = 0.75f;\n  }\n  if (x548_29_activePlayerPhazon) {\n    CDamageInfo scaledDamage(x498_phazonDamage, dt);\n    mgr.ApplyDamage(GetUniqueId(), mgr.GetPlayer().GetUniqueId(), xec_ownerId, scaledDamage, xf8_filter,\n                    zeus::skZero3f);\n    x4e4_playerDamageTimer += dt;\n    if (x4e4_playerDamageTimer >= x4e0_playerDamageDuration) {\n      mgr.GetPlayer().DecrementEnvironmentDamage();\n      x4e4_playerDamageTimer = 0.f;\n      x548_29_activePlayerPhazon = false;\n    }\n  }\n}\n\nvoid CPlasmaProjectile::UpdateFx(const zeus::CTransform& xf, float dt, CStateManager& mgr) {\n  if (!GetActive())\n    return;\n\n  x548_27_texturesLoaded = x4e8_texture.IsLoaded() && x4f4_glowTexture.IsLoaded();\n  CauseDamage(x4b4_expansionState == EExpansionState::Attack || x4b4_expansionState == EExpansionState::Sustain);\n  CBeamProjectile::UpdateFx(xf, dt, mgr);\n  UpdatePlayerEffects(dt, mgr);\n  if (x478_beamAttributes & 0x1) {\n    for (int i = 7; i > 0; --i)\n      PointCache()[i] = PointCache()[i - 1];\n    PointCache()[0] = GetCurrentPos();\n  }\n  if (x518_contactGen) {\n    x4d4_contactPulseTimer -= dt;\n    if ((GetDamageType() != EDamageType::None ? x548_25_enableEnergyPulse : false) && x4d4_contactPulseTimer <= 0.f) {\n      x518_contactGen->SetOrientation(zeus::lookAt(zeus::skZero3f, GetSurfaceNormal()));\n      x518_contactGen->SetTranslation(GetSurfaceNormal() * 0.001f + GetCurrentPos());\n      x518_contactGen->SetParticleEmission(true);\n      x4d4_contactPulseTimer = 1.f / 16.f;\n    } else {\n      x518_contactGen->SetParticleEmission(false);\n    }\n    x518_contactGen->Update(dt);\n  }\n  float modulation = UpdateBeamState(dt, mgr);\n  UpdateEnergyPulse(dt);\n  x4c8_beamAngle += 720.f * dt;\n  if (x4c8_beamAngle > 360.f)\n    x4c8_beamAngle = 0.f;\n  x4b8_beamWidth = modulation * GetMaxRadius();\n  x4c4_expansion = modulation;\n  x4cc_energyPulseStartY += dt * x480_pulseSpeed;\n  if (x4cc_energyPulseStartY > 5.f)\n    x4cc_energyPulseStartY = 0.f;\n  UpdateLights(modulation, dt, mgr);\n}\n\nvoid CPlasmaProjectile::Fire(const zeus::CTransform& xf, CStateManager& mgr, bool b) {\n  SetActive(true);\n  SetLightsActive(true, mgr);\n  x548_25_enableEnergyPulse = true;\n  x548_26_firing = true;\n  x548_24_ = b;\n  x4b4_expansionState = EExpansionState::Attack;\n  if (x478_beamAttributes & 0x1)\n    std::fill(PointCache().begin(), PointCache().end(), xf.origin);\n}\n\nvoid CPlasmaProjectile::Touch(CActor& other, CStateManager& mgr) {\n  // Empty\n}\n\nbool CPlasmaProjectile::CanRenderUnsorted(const CStateManager& mgr) const { return false; }\n\nvoid CPlasmaProjectile::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) {\n  if (GetActive()) {\n    g_Renderer->AddParticleGen(*x518_contactGen);\n    if (x478_beamAttributes & 0x2) {\n      g_Renderer->AddParticleGen(*x51c_pulseGen);\n    }\n  }\n  EnsureRendered(mgr, GetBeamTransform().origin, GetSortingBounds(mgr));\n}\n\nvoid CPlasmaProjectile::Render(CStateManager& mgr) {\n  if (!GetActive())\n    return;\n\n  // TODO\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CPlasmaProjectile::Render\", zeus::skOrange);\n\n  zeus::CTransform xf = GetBeamTransform();\n\n  // Subtractive blending for xray\n  s32 flags = mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::XRay ? 0x10 : 0x0;\n\n  if ((x478_beamAttributes & 0x1) == 0)\n    xf.origin += mgr.GetCameraManager()->GetGlobalCameraTranslation(mgr);\n\n  // Z test, no write\n\n  if ((x478_beamAttributes & 0x1) && x548_25_enableEnergyPulse && x4b4_expansionState != EExpansionState::Attack)\n    RenderMotionBlur();\n\n  // Pass1: alpha-controlled additive\n  CGraphics::SetModelMatrix(xf);\n//  RenderBeam(3, 0.25f * x4b8_beamWidth, zeus::CColor(1.f, 0.3f), flags | 0x4,\n//             (flags & 0x10) ? m_renderObjs->m_beamStrip1Sub : m_renderObjs->m_beamStrip1);\n\n  // Pass2: textured\n  CGraphics::SetModelMatrix(xf * zeus::CTransform::RotateY(zeus::degToRad(x4c8_beamAngle)));\n//  RenderBeam(4, 0.5f * x4b8_beamWidth, x490_innerColor, flags | 0x1,\n//             (flags & 0x10) ? m_renderObjs->m_beamStrip2Sub : m_renderObjs->m_beamStrip2);\n\n  // Pass3: textured | length-controlled UVY\n  CGraphics::SetModelMatrix(xf * zeus::CTransform::RotateY(zeus::degToRad(-x4c8_beamAngle)));\n//  RenderBeam(8, x4b8_beamWidth, x494_outerColor, flags | 0x3,\n//             (flags & 0x10) ? m_renderObjs->m_beamStrip3Sub : m_renderObjs->m_beamStrip3);\n\n  // Pass4: textured | alpha-controlled additive | glow texture\n  CGraphics::SetModelMatrix(xf);\n//  RenderBeam(6, 1.25f * x4b8_beamWidth, x494_outerColor, flags | 0xd,\n//             (flags & 0x10) ? m_renderObjs->m_beamStrip4Sub : m_renderObjs->m_beamStrip4);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CPlasmaProjectile.hpp",
    "content": "#pragma once\n\n#include <cstdint>\n#include <memory>\n#include <optional>\n#include <string_view>\n#include <vector>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Weapon/CBeamInfo.hpp\"\n#include \"Runtime/Weapon/CBeamProjectile.hpp\"\n#include \"Runtime/World/CDamageInfo.hpp\"\n\n#include <zeus/CColor.hpp>\n\nnamespace metaforce {\nclass CPlasmaProjectile : public CBeamProjectile {\npublic:\n  struct PlayerEffectResources : rstl::reserved_vector<CAssetId, 8> {\n    PlayerEffectResources(CAssetId a = {}, CAssetId b = {}, CAssetId c = {}, CAssetId d = {}, CAssetId e = {},\n                          CAssetId f = {}, CAssetId g = {}, CAssetId h = {})\n    : rstl::reserved_vector<CAssetId, 8>({a, b, c, d, e, f, g, h}) {}\n  };\n  static PlayerEffectResources LoadPlayerEffectResources(CInputStream& in) {\n    u32 propCount = in.ReadLong();\n    CAssetId a{in};\n    CAssetId b{in};\n    CAssetId c{in};\n    CAssetId d{in};\n    CAssetId e{in};\n    CAssetId f{in};\n    CAssetId g{in};\n    CAssetId h{in};\n    return {a, b, c, d, e, f, g, h};\n  }\n\nprivate:\n  std::vector<TUniqueId> x468_lights;\n  s32 x478_beamAttributes;\n  float x47c_lifeTime;\n  float x480_pulseSpeed;\n  float x484_shutdownTime;\n  float x488_expansionSpeed;\n  float x48c_;\n  zeus::CColor x490_innerColor;\n  zeus::CColor x494_outerColor;\n  CDamageInfo x498_phazonDamage;\n  enum class EExpansionState { Inactive, Attack, Sustain, Release, Done };\n  EExpansionState x4b4_expansionState = EExpansionState::Inactive;\n  float x4b8_beamWidth = 0.f;\n  float x4bc_lifeTimer = 0.f;\n  float x4c0_expansionT = 0.f;\n  float x4c4_expansion = 0.f;\n  float x4c8_beamAngle = 0.f;\n  float x4cc_energyPulseStartY = 0.f;\n  float x4d0_shutdownTimer = 0.f;\n  float x4d4_contactPulseTimer = 0.f;\n  float x4d8_energyPulseTimer = 0.f;\n  float x4dc_playerEffectPulseTimer = 0.f;\n  float x4e0_playerDamageDuration = 0.f;\n  float x4e4_playerDamageTimer = 0.f;\n  TLockedToken<CTexture> x4e8_texture;\n  TLockedToken<CTexture> x4f4_glowTexture;\n  TCachedToken<CGenDescription> x500_contactFxDesc;\n  TCachedToken<CGenDescription> x50c_pulseFxDesc;\n  std::unique_ptr<CElementGen> x518_contactGen;\n  std::unique_ptr<CElementGen> x51c_pulseGen;\n  std::unique_ptr<CElementGen> x520_weaponGen;\n  CAssetId x524_freezeSteamTxtr;\n  CAssetId x528_freezeIceTxtr;\n  TToken<CElectricDescription> x52c_visorElectric; // Used to be optional\n  TToken<CGenDescription> x538_visorParticle;      // Used to be optional\n  u16 x544_freezeSfx;\n  u16 x546_electricSfx;\n  bool x548_24_ : 1 = false;\n  bool x548_25_enableEnergyPulse : 1 = true;\n  bool x548_26_firing : 1 = false;\n  bool x548_27_texturesLoaded : 1 = false;\n  bool x548_28_drawOwnerFirst : 1;\n  bool x548_29_activePlayerPhazon : 1 = false;\n\n//  struct RenderObjects {\n//    CColoredStripShader m_beamStrip1;\n//    CColoredStripShader m_beamStrip2;\n//    CColoredStripShader m_beamStrip3;\n//    CColoredStripShader m_beamStrip4;\n//    CColoredStripShader m_beamStrip1Sub;\n//    CColoredStripShader m_beamStrip2Sub;\n//    CColoredStripShader m_beamStrip3Sub;\n//    CColoredStripShader m_beamStrip4Sub;\n//    CColoredStripShader m_motionBlurStrip;\n//    RenderObjects(CTexture& tex, CTexture& glowTex);\n//  };\n//  std::optional<RenderObjects> m_renderObjs;\n\n  void SetLightsActive(bool active, CStateManager& mgr);\n  void CreatePlasmaLights(u32 sourceId, const CLight& l, CStateManager& mgr);\n  void DeletePlasmaLights(CStateManager& mgr);\n  void UpdateLights(float expansion, float dt, CStateManager& mgr);\n  void UpdateEnergyPulse(float dt);\n  void RenderMotionBlur();\n  // void RenderBeam(s32 subdivs, float width, const zeus::CColor& color, s32 flags, CColoredStripShader& shader) const;\n  float UpdateBeamState(float dt, CStateManager& mgr);\n  void MakeBillboardEffect(const std::optional<TToken<CGenDescription>>& particle,\n                           const std::optional<TToken<CElectricDescription>>& electric, std::string_view name,\n                           CStateManager& mgr);\n  void UpdatePlayerEffects(float dt, CStateManager& mgr);\n\npublic:\n  DEFINE_ENTITY\n  CPlasmaProjectile(const TToken<CWeaponDescription>& wDesc, std::string_view name, EWeaponType wType,\n                    const CBeamInfo& bInfo, const zeus::CTransform& xf, EMaterialTypes matType,\n                    const CDamageInfo& dInfo, TUniqueId uid, TAreaId aid, TUniqueId owner,\n                    const PlayerEffectResources& res, bool growingBeam, EProjectileAttrib attribs);\n\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) override;\n  void ResetBeam(CStateManager& mgr, bool fullReset) override;\n  void UpdateFx(const zeus::CTransform& xf, float dt, CStateManager& mgr) override;\n  void Fire(const zeus::CTransform& xf, CStateManager& mgr, bool b) override;\n  void Touch(CActor& other, CStateManager& mgr) override;\n  bool CanRenderUnsorted(const CStateManager& mgr) const override;\n  void AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) override;\n  void Render(CStateManager& mgr) override;\n  zeus::CColor GetInnerColor() const { return x490_innerColor; }\n  zeus::CColor GetOuterColor() const { return x494_outerColor; }\n  bool IsFiring() const { return x548_26_firing; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CPlayerGun.cpp",
    "content": "#include \"Runtime/Weapon/CPlayerGun.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/Character/CPrimitive.hpp\"\n#include \"Runtime/Camera/CGameCamera.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Graphics/CGX.hpp\"\n#include \"Runtime/Input/ControlMapper.hpp\"\n#include \"Runtime/MP1/CSamusHud.hpp\"\n#include \"Runtime/MP1/World/CMetroid.hpp\"\n#include \"Runtime/MP1/World/CMetroidBeta.hpp\"\n#include \"Runtime/World/CGameLight.hpp\"\n#include \"Runtime/World/CScriptPlatform.hpp\"\n#include \"Runtime/World/CScriptWater.hpp\"\n#include \"Runtime/Weapon/CBomb.hpp\"\n#include \"Runtime/Weapon/CEnergyProjectile.hpp\"\n#include \"Runtime/Weapon/CPowerBomb.hpp\"\n\nnamespace metaforce {\nnamespace {\nstd::array kVerticalAngleTable{-30.f, 0.f, 30.f};\nstd::array kHorizontalAngleTable{30.f, 30.f, 30.f};\nstd::array kVerticalVarianceTable{30.f, 30.f, 30.f};\n\nconstexpr zeus::CVector3f sGunScale(2.f);\n\nconstexpr std::array<u32, 4> skBeamAnimIds{\n    0,\n    1,\n    2,\n    1,\n};\n\nconstexpr std::array skBeamArr{\n    CPlayerState::EItemType::PowerBeam,\n    CPlayerState::EItemType::IceBeam,\n    CPlayerState::EItemType::WaveBeam,\n    CPlayerState::EItemType::PlasmaBeam,\n};\n\nconstexpr std::array skBeamComboArr{\n    CPlayerState::EItemType::SuperMissile,\n    CPlayerState::EItemType::IceSpreader,\n    CPlayerState::EItemType::Wavebuster,\n    CPlayerState::EItemType::Flamethrower,\n};\n\nconstexpr std::array mBeamCtrlCmd{\n    ControlMapper::ECommands::PowerBeam,\n    ControlMapper::ECommands::IceBeam,\n    ControlMapper::ECommands::WaveBeam,\n    ControlMapper::ECommands::PlasmaBeam,\n};\n\nconstexpr std::array<u16, 4> skFromMissileSound{\n    SFXwpn_from_missile_power,\n    SFXwpn_from_missile_ice,\n    SFXwpn_from_missile_wave,\n    SFXwpn_from_missile_plasma,\n};\n\nconstexpr std::array<u16, 4> skFromBeamSound{\n    SFXsfx0000,\n    SFXwpn_from_beam_ice,\n    SFXwpn_from_beam_wave,\n    SFXwpn_from_beam_plasma,\n};\n\nconstexpr std::array<u16, 4> skToMissileSound{\n    SFXwpn_to_missile_power,\n    SFXwpn_to_missile_ice,\n    SFXwpn_to_missile_wave,\n    SFXwpn_to_missile_plasma,\n};\n\nconstexpr std::array<u16, 4> skIntoBeamSound{\n    SFXsfx0000,\n    SFXwpn_into_beam_ice,\n    SFXwpn_into_beam_wave,\n    SFXwpn_into_beam_plasma,\n};\n\nconstexpr float kChargeSpeed = 1.f / CPlayerState::GetMissileComboChargeFactor();\nconstexpr float kChargeFxStart = 1.f / CPlayerState::GetMissileComboChargeFactor();\nconstexpr float kChargeAnimStart = 0.25f / CPlayerState::GetMissileComboChargeFactor();\nconstexpr float kChargeStart = 0.025f / CPlayerState::GetMissileComboChargeFactor();\n\nconstexpr std::array<u16, 4> skBeamChargeUpSound{\n    SFXwpn_chargeup_power,\n    SFXwpn_chargeup_ice,\n    SFXwpn_chargeup_wave,\n    SFXwpn_chargeup_plasma,\n};\n\nconstexpr std::array skItemArr{\n    CPlayerState::EItemType::Invalid,\n    CPlayerState::EItemType::Missiles,\n};\n\nconstexpr std::array<u16, 2> skItemEmptySound{\n    SFXsfx0000,\n    SFXwpn_empty_action,\n};\n\nconstexpr std::array chargeShakeTbl{\n    -0.001f,\n    0.f,\n    0.001f,\n};\nconstexpr CMaterialFilter sAimFilter =\n    CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {EMaterialTypes::ProjectilePassthrough});\n\nconstexpr std::array<CModelFlags, 4> kThermalFlags{{\n    {0, 0, 3, zeus::skWhite},\n    {5, 0, 3, zeus::CColor(0.f, 0.5f)},\n    {0, 0, 3, zeus::skWhite},\n    {0, 0, 3, zeus::skWhite},\n}};\n\nconstexpr CModelFlags kHandThermalFlag{7, 0, 3, zeus::skWhite};\nconstexpr CModelFlags kHandHoloFlag{1, 0, 3, zeus::CColor(0.75f, 0.5f, 0.f, 1.f)};\n} // Anonymous namespace\n\nfloat CPlayerGun::CMotionState::gGunExtendDistance = 0.125f;\nfloat CPlayerGun::skTractorBeamFactor = 0.5f / CPlayerState::GetMissileComboChargeFactor();\n\nCPlayerGun::CPlayerGun(TUniqueId playerId)\n: x0_lights(8, zeus::skZero3f, 4, 4, false, false, false, 0.1f)\n, x538_playerId(playerId)\n, x550_camBob(CPlayerCameraBob::ECameraBobType::One, CPlayerCameraBob::GetCameraBobExtent(),\n              CPlayerCameraBob::GetCameraBobPeriod())\n, x678_morph(g_tweakPlayerGun->GetGunTransformTime(), g_tweakPlayerGun->GetHoloHoldTime())\n, x6c8_hologramClipCube(zeus::CVector3f(-0.29329199f, 0.f, -0.2481945f),\n                        zeus::CVector3f(0.29329199f, 1.292392f, 0.2481945f))\n, x6e0_rightHandModel(CAnimRes(g_tweakGunRes->xc_rightHand, 0, zeus::CVector3f(3.f), 0, true)) {\n  x354_bombFuseTime = g_tweakPlayerGun->GetBombFuseTime();\n  x358_bombDropDelayTime = g_tweakPlayerGun->GetBombDropDelayTime();\n  x668_aimVerticalSpeed = g_tweakPlayerGun->GetAimVerticalSpeed();\n  x66c_aimHorizontalSpeed = g_tweakPlayerGun->GetAimHorizontalSpeed();\n\n  x73c_gunMotion = std::make_unique<CGunMotion>(g_tweakGunRes->x4_gunMotion, sGunScale);\n  x740_grappleArm = std::make_unique<CGrappleArm>(sGunScale);\n  x744_auxWeapon = std::make_unique<CAuxWeapon>(playerId);\n  x748_rainSplashGenerator = std::make_unique<CRainSplashGenerator>(sGunScale, 20, 2, 0.f, 0.125f);\n  x74c_powerBeam = std::make_unique<CPowerBeam>(g_tweakGunRes->x10_powerBeam, EWeaponType::Power, playerId,\n                                                EMaterialTypes::Player, sGunScale);\n  x750_iceBeam = std::make_unique<CIceBeam>(g_tweakGunRes->x14_iceBeam, EWeaponType::Ice, playerId,\n                                            EMaterialTypes::Player, sGunScale);\n  x754_waveBeam = std::make_unique<CWaveBeam>(g_tweakGunRes->x18_waveBeam, EWeaponType::Wave, playerId,\n                                              EMaterialTypes::Player, sGunScale);\n  x758_plasmaBeam = std::make_unique<CPlasmaBeam>(g_tweakGunRes->x1c_plasmaBeam, EWeaponType::Plasma, playerId,\n                                                  EMaterialTypes::Player, sGunScale);\n  x75c_phazonBeam = std::make_unique<CPhazonBeam>(g_tweakGunRes->x20_phazonBeam, EWeaponType::Phazon, playerId,\n                                                  EMaterialTypes::Player, sGunScale);\n  x774_holoTransitionGen = std::make_unique<CElementGen>(\n      g_SimplePool->GetObj(SObjectTag{FOURCC('PART'), g_tweakGunRes->x24_holoTransition}));\n  x82c_shadow = std::make_unique<CWorldShadow>(256, 256, true);\n\n  x6e0_rightHandModel.SetSortThermal(true);\n\n  kVerticalAngleTable[2] = g_tweakPlayerGun->GetUpLookAngle();\n  kVerticalAngleTable[0] = g_tweakPlayerGun->GetDownLookAngle();\n  kHorizontalAngleTable[1] = g_tweakPlayerGun->GetHorizontalSpread();\n  kHorizontalAngleTable[2] = g_tweakPlayerGun->GetHighHorizontalSpread();\n  kHorizontalAngleTable[0] = g_tweakPlayerGun->GetLowHorizontalSpread();\n  kVerticalVarianceTable[1] = g_tweakPlayerGun->GetVerticalSpread();\n  kVerticalVarianceTable[2] = g_tweakPlayerGun->GetHighVerticalSpread();\n  kVerticalVarianceTable[0] = g_tweakPlayerGun->GetLowVerticalSpread();\n  CMotionState::SetExtendDistance(g_tweakPlayerGun->GetGunExtendDistance());\n\n  InitBeamData();\n  InitBombData();\n  InitMuzzleData();\n  InitCTData();\n  LoadHandAnimTokens();\n  x550_camBob.SetPlayerVelocity(zeus::skZero3f);\n  x550_camBob.SetBobMagnitude(0.f);\n  x550_camBob.SetBobTimeScale(0.f);\n}\n\nvoid CPlayerGun::InitBeamData() {\n  x760_selectableBeams[0] = x74c_powerBeam.get();\n  x760_selectableBeams[1] = x750_iceBeam.get();\n  x760_selectableBeams[2] = x754_waveBeam.get();\n  x760_selectableBeams[3] = x758_plasmaBeam.get();\n  x72c_currentBeam = x760_selectableBeams[0];\n  x738_nextBeam = x72c_currentBeam;\n  x774_holoTransitionGen->SetParticleEmission(true);\n}\n\nvoid CPlayerGun::InitBombData() {\n  x784_bombEffects.resize(2);\n  x784_bombEffects[0].push_back(g_SimplePool->GetObj(SObjectTag{FOURCC('PART'), g_tweakGunRes->x28_bombSet}));\n  x784_bombEffects[0].push_back(g_SimplePool->GetObj(SObjectTag{FOURCC('PART'), g_tweakGunRes->x2c_bombExplode}));\n  TLockedToken<CGenDescription> pbExplode =\n      g_SimplePool->GetObj(SObjectTag{FOURCC('PART'), g_tweakGunRes->x30_powerBombExplode});\n  x784_bombEffects[1].push_back(pbExplode);\n  x784_bombEffects[1].push_back(pbExplode);\n}\n\nvoid CPlayerGun::InitMuzzleData() {\n  for (const auto& muzzleID : g_tweakGunRes->xa4_auxMuzzle) {\n    x7c0_auxMuzzleEffects.push_back(g_SimplePool->GetObj(SObjectTag{FOURCC('PART'), muzzleID}));\n    x800_auxMuzzleGenerators.emplace_back(std::make_unique<CElementGen>(x7c0_auxMuzzleEffects.back()));\n    x800_auxMuzzleGenerators.back()->SetParticleEmission(false);\n  }\n}\n\nvoid CPlayerGun::InitCTData() { x77c_comboXferGen.reset(); }\n\nvoid CPlayerGun::LoadHandAnimTokens() {\n  std::set<CPrimitive> prims;\n  for (int i = 0; i < 3; ++i) {\n    CAnimPlaybackParms parms(i, -1, 1.f, true);\n    x6e0_rightHandModel.GetAnimationData()->GetAnimationPrimitives(parms, prims);\n  }\n  CAnimData::PrimitiveSetToTokenVector(prims, x540_handAnimTokens, true);\n}\n\nvoid CPlayerGun::TakeDamage(bool bigStrike, bool notFromMetroid, CStateManager& mgr) {\n  bool hasStrikeAngle = false;\n  float angle = 0.f;\n  if (x398_damageAmt >= 10.f && !bigStrike && (x2f8_stateFlags & 0x10) != 0x10 && !x832_26_comboFiring &&\n      x384_gunStrikeDelayTimer <= 0.f) {\n    x384_gunStrikeDelayTimer = 20.f;\n    x364_gunStrikeCoolTimer = 0.75f;\n    if (x678_morph.GetGunState() == CGunMorph::EGunState::OutWipeDone) {\n      zeus::CVector3f localDamageLoc = mgr.GetPlayer().GetTransform().transposeRotate(x3dc_damageLocation);\n      angle = zeus::CRelAngle(std::atan2(localDamageLoc.y(), localDamageLoc.x())).asRel().asDegrees();\n      hasStrikeAngle = true;\n    }\n  }\n\n  if (hasStrikeAngle || bigStrike) {\n    if (mgr.GetPlayerState()->GetCurrentVisor() != CPlayerState::EPlayerVisor::Scan) {\n      x73c_gunMotion->PlayPasAnim(SamusGun::EAnimationState::Struck, mgr, angle, bigStrike);\n      if ((bigStrike && notFromMetroid) || x833_31_inFreeLook)\n        x740_grappleArm->EnterStruck(mgr, angle, bigStrike, !x833_31_inFreeLook);\n    }\n  }\n\n  x398_damageAmt = 0.f;\n  x3dc_damageLocation = zeus::skZero3f;\n}\n\nvoid CPlayerGun::CreateGunLight(CStateManager& mgr) {\n  if (x53c_lightId != kInvalidUniqueId)\n    return;\n  x53c_lightId = mgr.AllocateUniqueId();\n  CGameLight* light =\n      new CGameLight(x53c_lightId, kInvalidAreaId, false, \"GunLite\", x3e8_xf, x538_playerId,\n                     CLight::BuildDirectional(zeus::skForward, zeus::skBlack), x53c_lightId.Value(), 0, 0.f);\n  mgr.AddObject(light);\n}\n\nvoid CPlayerGun::DeleteGunLight(CStateManager& mgr) {\n  if (x53c_lightId == kInvalidUniqueId)\n    return;\n  mgr.FreeScriptObject(x53c_lightId);\n  x53c_lightId = kInvalidUniqueId;\n}\n\nvoid CPlayerGun::UpdateGunLight(const zeus::CTransform& xf, CStateManager& mgr) {\n  if (x53c_lightId == kInvalidUniqueId || x32c_chargePhase == EChargePhase::NotCharging)\n    return;\n\n  if (TCastToPtr<CGameLight> light = mgr.ObjectById(x53c_lightId)) {\n    if (light->GetActive()) {\n      CElementGen* chargeFx = x72c_currentBeam->GetChargeMuzzleFx();\n      light->SetTransform(xf);\n      light->SetTranslation(xf.origin);\n      if (chargeFx && chargeFx->SystemHasLight()) {\n        CLight l = chargeFx->GetLight();\n        l.SetColor(zeus::CColor::lerp(zeus::skClear, l.GetColor(), x340_chargeBeamFactor));\n        light->SetLight(l);\n      }\n    }\n  }\n}\n\nvoid CPlayerGun::SetGunLightActive(bool active, CStateManager& mgr) {\n  if (x53c_lightId == kInvalidUniqueId)\n    return;\n\n  if (TCastToPtr<CGameLight> light = mgr.ObjectById(x53c_lightId)) {\n    light->SetActive(active);\n    if (active) {\n      if (CElementGen* gen = x72c_currentBeam->GetChargeMuzzleFx()) {\n        if (gen->SystemHasLight()) {\n          CLight genLight = gen->GetLight();\n          genLight.SetColor(zeus::skBlack);\n          light->SetLight(genLight);\n        }\n      }\n    }\n  }\n}\n\nvoid CPlayerGun::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) {\n  const CPlayer& player = mgr.GetPlayer();\n  bool isUnmorphed = player.GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Unmorphed;\n  switch (msg) {\n  case EScriptObjectMessage::Registered: {\n    CreateGunLight(mgr);\n    x320_currentAuxBeam = x314_nextBeam = x310_currentBeam = mgr.GetPlayerState()->GetCurrentBeam();\n    x72c_currentBeam = x738_nextBeam = x760_selectableBeams[size_t(x310_currentBeam)];\n    x72c_currentBeam->Load(mgr, true);\n    x72c_currentBeam->SetRainSplashGenerator(x748_rainSplashGenerator.get());\n    x744_auxWeapon->Load(x310_currentBeam, mgr);\n    const CAnimPlaybackParms parms(skBeamAnimIds[size_t(mgr.GetPlayerState()->GetCurrentBeam())], -1, 1.f, true);\n    x6e0_rightHandModel.GetAnimationData()->SetAnimation(parms, false);\n    break;\n  }\n  case EScriptObjectMessage::Deleted:\n    DeleteGunLight(mgr);\n    break;\n  case EScriptObjectMessage::UpdateSplashInhabitant:\n    if (mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::PhazonSuit) && isUnmorphed) {\n      if (TCastToConstPtr<CScriptWater> water = mgr.GetObjectById(sender)) {\n        if (water->GetFluidPlane().GetFluidType() == EFluidType::PhazonFluid) {\n          x835_24_canFirePhazon = true;\n          x835_25_inPhazonBeam = true;\n        }\n      }\n    }\n    if (player.GetDistanceUnderWater() > player.GetEyeHeight()) {\n      x834_27_underwater = true;\n      if (x744_auxWeapon->IsComboFxActive(mgr) && x310_currentBeam != CPlayerState::EBeamId::Wave)\n        StopContinuousBeam(mgr, false);\n    } else {\n      x834_27_underwater = false;\n    }\n    break;\n  case EScriptObjectMessage::RemoveSplashInhabitant:\n    x834_27_underwater = false;\n    x835_24_canFirePhazon = false;\n    break;\n  case EScriptObjectMessage::AddPhazonPoolInhabitant:\n    x835_30_inPhazonPool = true;\n    if (mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::PhazonSuit) && isUnmorphed)\n      x835_24_canFirePhazon = true;\n    break;\n  case EScriptObjectMessage::UpdatePhazonPoolInhabitant:\n    x835_30_inPhazonPool = true;\n    if (mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::PhazonSuit) && isUnmorphed) {\n      x835_24_canFirePhazon = true;\n      x835_25_inPhazonBeam = true;\n      if (x833_28_phazonBeamActive && static_cast<CPhazonBeam*>(x72c_currentBeam)->IsFiring()) {\n        if (TCastToPtr<CEntity> ent = mgr.ObjectById(sender)) {\n          mgr.SendScriptMsg(ent.GetPtr(), x538_playerId, EScriptObjectMessage::Decrement);\n        }\n      }\n    }\n    break;\n  case EScriptObjectMessage::RemovePhazonPoolInhabitant:\n    x835_30_inPhazonPool = false;\n    x835_24_canFirePhazon = false;\n    break;\n  case EScriptObjectMessage::Damage: {\n    bool bigStrike = false;\n    bool metroidAttached = false;\n    if (TCastToConstPtr<CEnergyProjectile> proj = mgr.GetObjectById(sender)) {\n      if ((proj->GetAttribField() & EProjectileAttrib::BigStrike) == EProjectileAttrib::BigStrike) {\n        x394_damageTimer = proj->GetDamageDuration();\n        bigStrike = true;\n      }\n    } else if (TCastToConstPtr<CPatterned> ai = mgr.GetObjectById(sender)) {\n      if (ai->IsMakingBigStrike()) {\n        x394_damageTimer = ai->GetDamageDuration();\n        bigStrike = true;\n        if (player.GetAttachedActor() != kInvalidUniqueId) {\n          metroidAttached = CPatterned::CastTo<MP1::CMetroid>(mgr.GetObjectById(player.GetAttachedActor())) != nullptr;\n        }\n      }\n    }\n    if (!x834_30_inBigStrike) {\n      if (bigStrike) {\n        x834_31_gunMotionInFidgetBasePosition = false;\n        CancelFiring(mgr);\n      }\n      TakeDamage(bigStrike, !metroidAttached, mgr);\n      x834_30_inBigStrike = bigStrike;\n    }\n    break;\n  }\n  case EScriptObjectMessage::OnFloor:\n    if (player.GetControlsFrozen() && !x834_30_inBigStrike) {\n      x2f4_fireButtonStates = 0;\n      x2ec_lastFireButtonStates = 0;\n      CancelFiring(mgr);\n      TakeDamage(true, false, mgr);\n      x394_damageTimer = 0.75f;\n      x834_30_inBigStrike = true;\n    }\n    break;\n  default:\n    break;\n  }\n\n  x740_grappleArm->AcceptScriptMsg(msg, sender, mgr);\n  x758_plasmaBeam->AcceptScriptMsg(msg, sender, mgr);\n  x75c_phazonBeam->AcceptScriptMsg(msg, sender, mgr);\n  x744_auxWeapon->AcceptScriptMsg(msg, sender, mgr);\n}\n\nvoid CPlayerGun::AsyncLoadSuit(CStateManager& mgr) {\n  x72c_currentBeam->AsyncLoadSuitArm(mgr);\n  x740_grappleArm->AsyncLoadSuit(mgr);\n}\n\nvoid CPlayerGun::TouchModel(const CStateManager& mgr) {\n  if (mgr.GetPlayer().GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Morphed) {\n    x73c_gunMotion->GetModelData().Touch(mgr, 0);\n    switch (x33c_phazonBeamState) {\n    case EPhazonBeamState::Entering:\n      if (x75c_phazonBeam)\n        x75c_phazonBeam->Touch(mgr);\n      break;\n    case EPhazonBeamState::Exiting:\n      if (x738_nextBeam)\n        x738_nextBeam->Touch(mgr);\n      break;\n    default:\n      if (!x833_28_phazonBeamActive)\n        x72c_currentBeam->Touch(mgr);\n      else\n        x75c_phazonBeam->Touch(mgr);\n      break;\n    }\n    x72c_currentBeam->TouchHolo(mgr);\n    x740_grappleArm->TouchModel(mgr);\n    x6e0_rightHandModel.Touch(mgr, 0);\n  }\n\n  if (x734_loadingBeam) {\n    x734_loadingBeam->Touch(mgr);\n    x734_loadingBeam->TouchHolo(mgr);\n  }\n}\n\nvoid CPlayerGun::DamageRumble(const zeus::CVector3f& location, float damage, const CStateManager& mgr) {\n  x398_damageAmt = damage;\n  x3dc_damageLocation = location;\n}\n\nvoid CPlayerGun::StopChargeSound(CStateManager& mgr) {\n  if (x2e0_chargeSfx) {\n    CSfxManager::SfxStop(x2e0_chargeSfx);\n    x2e0_chargeSfx.reset();\n  }\n  if (x830_chargeRumbleHandle != -1) {\n    mgr.GetRumbleManager().StopRumble(x830_chargeRumbleHandle);\n    x830_chargeRumbleHandle = -1;\n  }\n}\n\nvoid CPlayerGun::ResetCharge(CStateManager& mgr, bool resetBeam) {\n  if (x32c_chargePhase != EChargePhase::NotCharging)\n    StopChargeSound(mgr);\n\n  if ((x2f8_stateFlags & 0x8) != 0x8 && (x2f8_stateFlags & 0x10) != 0x10) {\n    bool doResetBeam =\n        mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed || resetBeam;\n    if (x832_27_chargeAnimStarted || doResetBeam)\n      PlayAnim(NWeaponTypes::EGunAnimType::BasePosition, false);\n    if (doResetBeam)\n      x72c_currentBeam->EnableSecondaryFx(CGunWeapon::ESecondaryFxType::None);\n    if ((x2f8_stateFlags & 0x2) != 0x2 || x330_chargeState != EChargeState::Normal) {\n      if ((x2f8_stateFlags & 0x8) != 0x8) {\n        x2f8_stateFlags |= 0x1;\n        x2f8_stateFlags &= 0xFFE9;\n      }\n      x318_comboAmmoIdx = 0;\n      x31c_missileMode = EMissileMode::Inactive;\n    }\n  }\n\n  x32c_chargePhase = EChargePhase::NotCharging;\n  x330_chargeState = EChargeState::Normal;\n  x320_currentAuxBeam = x310_currentBeam;\n  x833_30_canShowAuxMuzzleEffect = true;\n  x832_27_chargeAnimStarted = false;\n  x832_26_comboFiring = false;\n  x344_comboXferTimer = 0.f;\n}\n\nbool CPlayerGun::ExitMissile() {\n  if ((x2f8_stateFlags & 0x1) == 0x1)\n    return true;\n  if ((x2f8_stateFlags & 0x10) == 0x10 || x338_nextState == ENextState::ExitMissile)\n    return false;\n  x338_nextState = ENextState::ExitMissile;\n  PlayAnim(NWeaponTypes::EGunAnimType::FromMissile, false);\n  return false;\n}\n\nvoid CPlayerGun::HandleBeamChange(const CFinalInput& input, CStateManager& mgr) {\n  const CPlayerState& playerState = *mgr.GetPlayerState();\n  float maxBeamInput = 0.f;\n  CPlayerState::EBeamId selectBeam = CPlayerState::EBeamId::Invalid;\n  for (size_t i = 0; i < skBeamArr.size(); ++i) {\n    if (playerState.HasPowerUp(skBeamArr[i])) {\n      const float inputVal = ControlMapper::GetAnalogInput(mBeamCtrlCmd[i], input);\n      if (inputVal > 0.65f && inputVal > maxBeamInput) {\n        maxBeamInput = inputVal;\n        selectBeam = CPlayerState::EBeamId(i);\n      }\n    }\n  }\n\n  if (selectBeam == CPlayerState::EBeamId::Invalid)\n    return;\n\n  x833_25_ = true;\n  if (x310_currentBeam != selectBeam && playerState.HasPowerUp(skBeamArr[size_t(selectBeam)])) {\n    x314_nextBeam = selectBeam;\n    u32 flags = 0;\n    if ((x2f8_stateFlags & 0x10) == 0x10)\n      flags = 0x10;\n    flags |= 0x8;\n    x2f8_stateFlags = flags;\n    PlayAnim(NWeaponTypes::EGunAnimType::FromBeam, false);\n    if (x833_31_inFreeLook || x744_auxWeapon->IsComboFxActive(mgr) || x832_26_comboFiring) {\n      x832_30_requestReturnToDefault = true;\n      x740_grappleArm->EnterIdle(mgr);\n    }\n    x72c_currentBeam->EnableSecondaryFx(CGunWeapon::ESecondaryFxType::None);\n    x338_nextState = ENextState::ChangeWeapon;\n    x2e4_invalidSfx.reset();\n  } else if (playerState.HasPowerUp(skBeamArr[size_t(selectBeam)])) {\n    if (ExitMissile()) {\n      if (!CSfxManager::IsPlaying(x2e4_invalidSfx))\n        x2e4_invalidSfx = NWeaponTypes::play_sfx(SFXwpn_empty_action, x834_27_underwater, false, 0.165f);\n    } else {\n      x2e4_invalidSfx.reset();\n    }\n  }\n}\n\nvoid CPlayerGun::SetPhazonBeamMorph(bool intoPhazonBeam) {\n  x39c_phazonMorphT = intoPhazonBeam ? 0.f : 1.f;\n  x835_27_intoPhazonBeam = intoPhazonBeam;\n  x835_26_phazonBeamMorphing = true;\n}\n\nvoid CPlayerGun::Reset(CStateManager& mgr, bool b1) {\n  x72c_currentBeam->Reset(mgr);\n  x832_25_chargeEffectVisible = false;\n  x832_24_coolingCharge = false;\n  x833_26_ = false;\n  x348_chargeCooldownTimer = 0.f;\n  SetGunLightActive(false, mgr);\n  if ((x2f8_stateFlags & 0x10) != 0x10) {\n    if (!b1 && (x2f8_stateFlags & 0x2) != 0x2) {\n      if ((x2f8_stateFlags & 0x8) != 0x8) {\n        x2f8_stateFlags |= 0x1;\n        x2f8_stateFlags &= 0xFFE9;\n      }\n      x318_comboAmmoIdx = 0;\n      x31c_missileMode = EMissileMode::Inactive;\n    }\n  } else {\n    x2f8_stateFlags &= ~0x7;\n  }\n}\n\nvoid CPlayerGun::ResetBeamParams(CStateManager& mgr, const CPlayerState& playerState, bool playSelectionSfx) {\n  StopContinuousBeam(mgr, true);\n  if (playerState.ItemEnabled(CPlayerState::EItemType::ChargeBeam)) {\n    ResetCharge(mgr, false);\n  }\n  const CAnimPlaybackParms parms(skBeamAnimIds[size_t(x314_nextBeam)], -1, 1.f, true);\n  x6e0_rightHandModel.GetAnimationData()->SetAnimation(parms, false);\n  Reset(mgr, false);\n  if (playSelectionSfx) {\n    CSfxManager::SfxStart(SFXwpn_morph_out_wipe, 1.f, 0.f, true, 0x7f, false, kInvalidAreaId);\n  }\n  x2ec_lastFireButtonStates &= ~0x1;\n  x320_currentAuxBeam = x310_currentBeam;\n  x833_30_canShowAuxMuzzleEffect = true;\n}\n\nvoid CPlayerGun::PlayAnim(NWeaponTypes::EGunAnimType type, bool loop) {\n  if (x338_nextState != ENextState::ChangeWeapon)\n    x72c_currentBeam->PlayAnim(type, loop);\n\n  u16 sfx = 0xffff;\n  switch (type) {\n  case NWeaponTypes::EGunAnimType::FromMissile:\n    x2f8_stateFlags &= ~0x4;\n    sfx = skFromMissileSound[size_t(x310_currentBeam)];\n    break;\n  case NWeaponTypes::EGunAnimType::MissileReload:\n    sfx = SFXwpn_reload_missile;\n    break;\n  case NWeaponTypes::EGunAnimType::FromBeam:\n    sfx = skFromBeamSound[size_t(x310_currentBeam)];\n    break;\n  case NWeaponTypes::EGunAnimType::ToMissile:\n    x2f8_stateFlags &= ~0x1;\n    sfx = skToMissileSound[size_t(x310_currentBeam)];\n    break;\n  default:\n    break;\n  }\n\n  if (sfx != 0xffff)\n    NWeaponTypes::play_sfx(sfx, x834_27_underwater, false, 0.165f);\n}\n\nvoid CPlayerGun::CancelCharge(CStateManager& mgr, bool withEffect) {\n  if (withEffect) {\n    x32c_chargePhase = EChargePhase::ChargeCooldown;\n    x72c_currentBeam->EnableSecondaryFx(CGunWeapon::ESecondaryFxType::CancelCharge);\n  } else {\n    x72c_currentBeam->EnableSecondaryFx(CGunWeapon::ESecondaryFxType::None);\n  }\n\n  x834_24_charging = false;\n  x348_chargeCooldownTimer = 0.f;\n  x72c_currentBeam->ActivateCharge(false, false);\n  SetGunLightActive(false, mgr);\n}\n\nvoid CPlayerGun::HandlePhazonBeamChange(CStateManager& mgr) {\n  bool inMorph = false;\n  switch (x33c_phazonBeamState) {\n  case EPhazonBeamState::Inactive:\n    SetPhazonBeamMorph(true);\n    x338_nextState = ENextState::EnterPhazonBeam;\n    inMorph = true;\n    break;\n  case EPhazonBeamState::Active:\n    if (!x835_24_canFirePhazon) {\n      SetPhazonBeamMorph(true);\n      x338_nextState = ENextState::ExitPhazonBeam;\n      inMorph = true;\n      if (x75c_phazonBeam) {\n        x75c_phazonBeam->SetClipWipeActive(false);\n        x75c_phazonBeam->SetVeinsAlphaActive(true);\n      }\n    }\n    break;\n  default:\n    break;\n  }\n\n  if (inMorph) {\n    ResetBeamParams(mgr, *mgr.GetPlayerState(), true);\n    x2f8_stateFlags = 0x8;\n    PlayAnim(NWeaponTypes::EGunAnimType::FromBeam, false);\n    if (x833_31_inFreeLook) {\n      x832_30_requestReturnToDefault = true;\n      x740_grappleArm->EnterIdle(mgr);\n    }\n    CancelCharge(mgr, false);\n  }\n}\n\nvoid CPlayerGun::HandleWeaponChange(const CFinalInput& input, CStateManager& mgr) {\n  x833_25_ = false;\n  if (ControlMapper::GetPressInput(ControlMapper::ECommands::Morph, input)) {\n    StopContinuousBeam(mgr, true);\n  }\n  if ((x2f8_stateFlags & 0x8) != 0x8) {\n    if (!x835_25_inPhazonBeam) {\n      HandleBeamChange(input, mgr);\n    } else {\n      HandlePhazonBeamChange(mgr);\n    }\n  }\n}\n\nvoid CPlayerGun::ProcessInput(const CFinalInput& input, CStateManager& mgr) {\n  CPlayerState& state = *mgr.GetPlayerState();\n  bool damageNotMorphed =\n      (x834_30_inBigStrike && mgr.GetPlayer().GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Morphed);\n  if (x832_24_coolingCharge || damageNotMorphed || (x2f8_stateFlags & 0x8) == 0x8) {\n    return;\n  }\n  if (state.HasPowerUp(CPlayerState::EItemType::ChargeBeam)) {\n    if (!state.ItemEnabled(CPlayerState::EItemType::ChargeBeam))\n      state.EnableItem(CPlayerState::EItemType::ChargeBeam);\n  } else if (state.ItemEnabled(CPlayerState::EItemType::ChargeBeam)) {\n    state.DisableItem(CPlayerState::EItemType::ChargeBeam);\n    ResetCharge(mgr, false);\n  }\n  switch (mgr.GetPlayer().GetMorphballTransitionState()) {\n  default:\n    x2f4_fireButtonStates = 0;\n    break;\n  case CPlayer::EPlayerMorphBallState::Unmorphed:\n    if ((x2f8_stateFlags & 0x10) != 0x10)\n      HandleWeaponChange(input, mgr);\n    [[fallthrough]];\n  case CPlayer::EPlayerMorphBallState::Morphed:\n    x2f4_fireButtonStates = ControlMapper::GetDigitalInput(ControlMapper::ECommands::FireOrBomb, input) ? 1 : 0;\n    x2f4_fireButtonStates |=\n        ControlMapper::GetDigitalInput(ControlMapper::ECommands::MissileOrPowerBomb, input) ? 2 : 0;\n    break;\n  }\n}\n\nvoid CPlayerGun::UnLoadFidget() {\n  if ((x2fc_fidgetAnimBits & 0x1) == 0x1)\n    x73c_gunMotion->GunController().UnLoadFidget();\n  if ((x2fc_fidgetAnimBits & 0x2) == 0x2)\n    x72c_currentBeam->UnLoadFidget();\n  if ((x2fc_fidgetAnimBits & 0x4) == 0x4)\n    if (CGunController* gc = x740_grappleArm->GunController())\n      gc->UnLoadFidget();\n  x2fc_fidgetAnimBits = 0;\n}\n\nvoid CPlayerGun::ReturnArmAndGunToDefault(CStateManager& mgr, bool returnToDefault) {\n  if (returnToDefault || !x833_31_inFreeLook) {\n    x73c_gunMotion->ReturnToDefault(mgr, false);\n    x740_grappleArm->ReturnToDefault(mgr, 0.f, false);\n  }\n  if (!x834_25_gunMotionFidgeting)\n    x72c_currentBeam->ReturnToDefault(mgr);\n  x834_25_gunMotionFidgeting = false;\n}\n\nvoid CPlayerGun::ReturnToRestPose() {\n  if (x832_31_inRestPose)\n    return;\n  if ((x2f8_stateFlags & 0x1) == 0x1)\n    PlayAnim(NWeaponTypes::EGunAnimType::BasePosition, false);\n  else if ((x2f8_stateFlags & 0x4) == 0x4)\n    PlayAnim(NWeaponTypes::EGunAnimType::ToMissile, false);\n  x832_31_inRestPose = true;\n}\n\nvoid CPlayerGun::ResetIdle(CStateManager& mgr) {\n  CFidget::EState fidgetState = x3a4_fidget.GetState();\n  x370_gunMotionSpeedMult = 1.f;\n  x550_camBob.SetState(CPlayerCameraBob::ECameraBobState::GunFireNoBob, mgr);\n  if (fidgetState != CFidget::EState::NoFidget) {\n    if (fidgetState == CFidget::EState::Loading)\n      UnLoadFidget();\n    ReturnArmAndGunToDefault(mgr, true);\n  }\n  x3a4_fidget.ResetAll();\n  ReturnToRestPose();\n  if (x324_idleState != EIdleState::NotIdle)\n    x324_idleState = EIdleState::NotIdle;\n  if (!x740_grappleArm->GetActive())\n    x834_26_animPlaying = false;\n}\n\nvoid CPlayerGun::CancelFiring(CStateManager& mgr) {\n  if (x32c_chargePhase == EChargePhase::ComboFireDone)\n    ReturnArmAndGunToDefault(mgr, true);\n  if ((x2f8_stateFlags & 0x10) == 0x10) {\n    StopContinuousBeam(mgr, true);\n    if ((x2f8_stateFlags & 0x8) == 0x8) {\n      x2f8_stateFlags |= 0x1;\n      x2f8_stateFlags &= 0xFFE9;\n    }\n    x318_comboAmmoIdx = 0;\n    x31c_missileMode = EMissileMode::Inactive;\n  }\n  if (x32c_chargePhase != EChargePhase::NotCharging) {\n    x72c_currentBeam->ActivateCharge(false, false);\n    SetGunLightActive(false, mgr);\n    ResetCharge(mgr, true);\n  }\n  Reset(mgr, (x2f8_stateFlags & 0x2) == 0x2);\n}\n\nfloat CPlayerGun::GetBeamVelocity() const {\n  if (x72c_currentBeam->IsLoaded())\n    return x72c_currentBeam->GetVelocityInfo().GetVelocity(int(x330_chargeState)).y();\n  return 10.f;\n}\n\nvoid CPlayerGun::StopContinuousBeam(CStateManager& mgr, bool b1) {\n  if ((x2f8_stateFlags & 0x10) == 0x10) {\n    ReturnArmAndGunToDefault(mgr, false);\n    x744_auxWeapon->StopComboFx(mgr, b1);\n    switch (x310_currentBeam) {\n    case CPlayerState::EBeamId::Power:\n    case CPlayerState::EBeamId::Wave:\n    case CPlayerState::EBeamId::Plasma:\n      // All except ice\n      if (x310_currentBeam != CPlayerState::EBeamId::Power || x833_28_phazonBeamActive) {\n        x72c_currentBeam->EnableSecondaryFx(b1 ? CGunWeapon::ESecondaryFxType::None\n                                               : CGunWeapon::ESecondaryFxType::CancelCharge);\n      }\n      break;\n    default:\n      break;\n    }\n  } else if (x833_28_phazonBeamActive) {\n    if (static_cast<CPhazonBeam*>(x72c_currentBeam)->IsFiring())\n      static_cast<CPhazonBeam*>(x72c_currentBeam)->StopBeam(mgr, b1);\n  } else if (x310_currentBeam == CPlayerState::EBeamId::Plasma) {\n    if (static_cast<CPlasmaBeam*>(x72c_currentBeam)->IsFiring())\n      static_cast<CPlasmaBeam*>(x72c_currentBeam)->StopBeam(mgr, b1);\n  }\n}\n\nvoid CPlayerGun::CMotionState::Update(bool firing, float dt, zeus::CTransform& xf, CStateManager& mgr) {\n  if (firing) {\n    x24_fireState = EFireState::StartFire;\n    x8_fireTime = 0.f;\n  } else if (x24_fireState != EFireState::NotFiring) {\n    if (x8_fireTime > dt)\n      x24_fireState = EFireState::Firing;\n    x8_fireTime += dt;\n  }\n\n  if (x0_24_extendParabola && x20_state == EMotionState::LockOn) {\n    float extendT = xc_curExtendDist / gGunExtendDistance;\n    xf = xf * zeus::CTransform(zeus::CMatrix3f::RotateZ(zeus::degToRad(extendT * -4.f * (extendT - 1.f) * 15.f)),\n                               {0.f, xc_curExtendDist, 0.f});\n  } else if (x24_fireState == EFireState::StartFire || x24_fireState == EFireState::Firing) {\n    if (std::fabs(x14_rotationT - 1.f) < 0.1f) {\n      x18_startRotation = x1c_endRotation;\n      x14_rotationT = 0.f;\n      if (x24_fireState == EFireState::StartFire) {\n        x1c_endRotation = mgr.GetActiveRandom()->Next() % 15;\n        x1c_endRotation *= (mgr.GetActiveRandom()->Next() % 100) > 45 ? 1.f : -1.f;\n      } else {\n        x1c_endRotation = 0.f;\n        if (x18_startRotation == x1c_endRotation) {\n          x10_curRotation = x1c_endRotation;\n          x24_fireState = EFireState::NotFiring;\n        }\n      }\n    } else {\n      x10_curRotation = (x1c_endRotation - x18_startRotation) * x14_rotationT + x18_startRotation;\n    }\n\n    x14_rotationT += (1.f - x14_rotationT) * 0.8f * (10.f * dt);\n    zeus::CTransform tmpXf =\n        zeus::CQuaternion::fromAxisAngle(xf.frontVector(), zeus::degToRad(x10_curRotation)).toTransform() *\n        xf.getRotation();\n    tmpXf.origin = xf.origin;\n    xf = tmpXf * zeus::CTransform::Translate(0.f, xc_curExtendDist, 0.f);\n  } else {\n    xf = xf * zeus::CTransform::Translate(0.f, xc_curExtendDist, 0.f);\n  }\n\n  switch (x20_state) {\n  case EMotionState::LockOn:\n    xc_curExtendDist += 3.f * dt;\n    if (xc_curExtendDist > gGunExtendDistance) {\n      xc_curExtendDist = gGunExtendDistance;\n      x20_state = EMotionState::One;\n      x0_24_extendParabola = false;\n    }\n    break;\n  case EMotionState::CancelLockOn:\n    xc_curExtendDist -= 3.f * dt;\n    if (xc_curExtendDist < 0.f) {\n      xc_curExtendDist = 0.f;\n      x20_state = EMotionState::Zero;\n    }\n    break;\n  default:\n    break;\n  }\n\n  if (!x0_24_extendParabola) {\n    if (x4_extendParabolaDelayTimer < 30.f) {\n      x4_extendParabolaDelayTimer += dt;\n    } else {\n      x0_24_extendParabola = true;\n      x4_extendParabolaDelayTimer = 0.f;\n    }\n  }\n}\n\nvoid CPlayerGun::ChangeWeapon(const CPlayerState& playerState, CStateManager& mgr) {\n  if (x730_outgoingBeam != nullptr && x72c_currentBeam != x730_outgoingBeam)\n    x730_outgoingBeam->Unload(mgr);\n\n  x734_loadingBeam = x760_selectableBeams[size_t(x314_nextBeam)];\n  if (x734_loadingBeam && x72c_currentBeam != x734_loadingBeam) {\n    x734_loadingBeam->Load(mgr, false);\n    x744_auxWeapon->Load(x314_nextBeam, mgr);\n  }\n\n  x72c_currentBeam->EnableFx(false);\n  x834_28_requestImmediateRecharge = x32c_chargePhase != EChargePhase::NotCharging;\n  ResetBeamParams(mgr, playerState, true);\n  x678_morph.StartWipe(CGunMorph::EDir::In);\n}\n\nvoid CPlayerGun::GetLctrWithShake(zeus::CTransform& xfOut, const CModelData& mData, std::string_view lctrName,\n                                  bool shake, bool dyn) const {\n  if (dyn)\n    xfOut = mData.GetScaledLocatorTransformDynamic(lctrName, nullptr);\n  else\n    xfOut = mData.GetScaledLocatorTransform(lctrName);\n\n  if (x834_24_charging && shake)\n    xfOut.origin += zeus::CVector3f(x34c_shakeX, 0.f, x350_shakeZ);\n}\n\nvoid CPlayerGun::UpdateLeftArmTransform(const CModelData& mData, const CStateManager& mgr) {\n  if (x834_26_animPlaying)\n    x740_grappleArm->AuxTransform() = zeus::CTransform();\n  else\n    GetLctrWithShake(x740_grappleArm->AuxTransform(), mData, \"elbow\", true, false);\n\n  x740_grappleArm->AuxTransform().origin = x740_grappleArm->AuxTransform() * zeus::CVector3f(-0.9f, -0.4f, 0.4f);\n  x740_grappleArm->SetTransform(x3e8_xf);\n}\n\nCPlayerGun::CGunMorph::EMorphEvent CPlayerGun::CGunMorph::Update(float inY, float outY, float dt) {\n  EMorphEvent ret = EMorphEvent::None;\n\n  if (x20_gunState == EGunState::InWipeDone) {\n    x14_remHoldTime -= dt;\n    if (x14_remHoldTime <= 0.f && x24_25_weaponChanged) {\n      StartWipe(EDir::Out);\n      x24_25_weaponChanged = false;\n      x14_remHoldTime = 0.f;\n      ret = EMorphEvent::InWipeDone;\n    }\n  }\n\n  if (x24_24_morphing) {\n    float omt = x8_remTime * xc_speed;\n    float t = 1.f - omt;\n    if (x1c_dir == EDir::In) {\n      x0_yLerp = omt * outY + t * inY;\n      x18_transitionFactor = omt;\n    } else {\n      x0_yLerp = omt * inY + t * outY;\n      x18_transitionFactor = t;\n    }\n\n    if (x8_remTime <= 0.f) {\n      x24_24_morphing = false;\n      x8_remTime = 0.f;\n      if (x1c_dir == EDir::In) {\n        x20_gunState = EGunState::InWipeDone;\n        x18_transitionFactor = 0.f;\n      } else {\n        x18_transitionFactor = 1.f;\n        x20_gunState = EGunState::OutWipeDone;\n        x1c_dir = EDir::Done;\n        ret = EMorphEvent::OutWipeDone;\n      }\n    } else {\n      x8_remTime -= dt;\n    }\n  }\n\n  return ret;\n}\n\nvoid CPlayerGun::CGunMorph::StartWipe(EDir dir) {\n  x14_remHoldTime = x10_holoHoldTime;\n  if (dir == EDir::In && x20_gunState == EGunState::InWipeDone)\n    return;\n\n  if (dir != x1c_dir && x20_gunState != EGunState::OutWipe) {\n    x8_remTime = x4_gunTransformTime;\n    xc_speed = 1.f / x4_gunTransformTime;\n  } else if (x20_gunState != EGunState::InWipe) {\n    x8_remTime = x4_gunTransformTime - x8_remTime;\n  }\n\n  x1c_dir = dir;\n  x20_gunState = x1c_dir == EDir::In ? EGunState::InWipe : EGunState::OutWipe;\n  x24_24_morphing = true;\n}\n\nvoid CPlayerGun::ProcessGunMorph(float dt, CStateManager& mgr) {\n  bool isUnmorphed = mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Unmorphed;\n  switch (x678_morph.GetGunState()) {\n  case CGunMorph::EGunState::InWipeDone:\n    if (x310_currentBeam != x314_nextBeam && x734_loadingBeam != nullptr) {\n      if (!isUnmorphed)\n        x734_loadingBeam->Touch(mgr);\n      if (x734_loadingBeam->IsLoaded() && x744_auxWeapon->IsLoaded()) {\n        x730_outgoingBeam = (x734_loadingBeam == x72c_currentBeam ? nullptr : x72c_currentBeam);\n        x734_loadingBeam = nullptr;\n        x310_currentBeam = x314_nextBeam;\n        x320_currentAuxBeam = x314_nextBeam;\n        x833_30_canShowAuxMuzzleEffect = true;\n        x72c_currentBeam = x760_selectableBeams[size_t(x314_nextBeam)];\n        x738_nextBeam = x72c_currentBeam;\n        x678_morph.SetWeaponChanged();\n        mgr.GetPlayerState()->SetCurrentBeam(x314_nextBeam);\n      }\n    }\n    break;\n  case CGunMorph::EGunState::InWipe:\n  case CGunMorph::EGunState::OutWipe:\n    x774_holoTransitionGen->SetGlobalScale(sGunScale);\n    x774_holoTransitionGen->SetGlobalTranslation(zeus::CVector3f(0.f, x678_morph.GetYLerp(), 0.f));\n    x774_holoTransitionGen->Update(dt);\n    break;\n  default:\n    break;\n  }\n\n  switch (x678_morph.Update(0.2f, 1.292392f, dt)) {\n  case CGunMorph::EMorphEvent::InWipeDone:\n    CSfxManager::SfxStart(SFXwpn_morph_in_wipe_done, 1.f, 0.f, true, 0x74, false, kInvalidAreaId);\n    break;\n  case CGunMorph::EMorphEvent::OutWipeDone:\n    if (x730_outgoingBeam != nullptr && x72c_currentBeam != x730_outgoingBeam) {\n      x730_outgoingBeam->Unload(mgr);\n      x730_outgoingBeam = nullptr;\n    }\n    if (isUnmorphed) {\n      NWeaponTypes::play_sfx(skIntoBeamSound[size_t(x310_currentBeam)], x834_27_underwater, false, 0.165f);\n    }\n    x72c_currentBeam->SetRainSplashGenerator(x748_rainSplashGenerator.get());\n    x72c_currentBeam->EnableFx(true);\n    PlayAnim(NWeaponTypes::EGunAnimType::ToBeam, false);\n    if (x833_31_inFreeLook)\n      EnterFreeLook(mgr);\n    else if (x832_30_requestReturnToDefault)\n      ReturnArmAndGunToDefault(mgr, false);\n    if (x834_28_requestImmediateRecharge || (x2ec_lastFireButtonStates & 0x1) != 0) {\n      if (mgr.GetPlayerState()->GetCurrentVisor() != CPlayerState::EPlayerVisor::Scan)\n        x32c_chargePhase = EChargePhase::ChargeRequested;\n      x834_28_requestImmediateRecharge = false;\n    }\n    x832_30_requestReturnToDefault = false;\n    x338_nextState = ENextState::SetupBeam;\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CPlayerGun::SetPhazonBeamFeedback(bool active) {\n  const char16_t* str = g_MainStringTable->GetString(21); // Hyper-mode\n  CHUDMemoParms parms(5.f, true, !active, false);\n  MP1::CSamusHud::DisplayHudMemo(str, parms);\n  if (CSfxManager::IsPlaying(x2e8_phazonBeamSfx))\n    CSfxManager::SfxStop(x2e8_phazonBeamSfx);\n  x2e8_phazonBeamSfx.reset();\n  if (active)\n    x2e8_phazonBeamSfx = NWeaponTypes::play_sfx(SFXphg_charge_lp, x834_27_underwater, false, 0.165f);\n}\n\nvoid CPlayerGun::StartPhazonBeamTransition(bool active, CStateManager& mgr, CPlayerState& playerState) {\n  if (x833_28_phazonBeamActive == active) {\n    return;\n  }\n  x760_selectableBeams[size_t(x310_currentBeam)]->Unload(mgr);\n  x760_selectableBeams[size_t(x310_currentBeam)] = active ? x75c_phazonBeam.get() : x738_nextBeam;\n  ResetBeamParams(mgr, playerState, false);\n  x72c_currentBeam = x760_selectableBeams[size_t(x310_currentBeam)];\n  x833_28_phazonBeamActive = active;\n  SetPhazonBeamFeedback(active);\n  x72c_currentBeam->SetRainSplashGenerator(x748_rainSplashGenerator.get());\n  x72c_currentBeam->EnableFx(true);\n  x72c_currentBeam->SetDrawHologram(false);\n  PlayAnim(NWeaponTypes::EGunAnimType::ToBeam, false);\n  if (x833_31_inFreeLook)\n    EnterFreeLook(mgr);\n  else if (x832_30_requestReturnToDefault)\n    ReturnArmAndGunToDefault(mgr, false);\n  x832_30_requestReturnToDefault = false;\n}\n\nvoid CPlayerGun::ProcessPhazonGunMorph(float dt, CStateManager& mgr) {\n  if (x835_26_phazonBeamMorphing) {\n    if (x835_27_intoPhazonBeam) {\n      x39c_phazonMorphT += 15.f * dt;\n      if (x39c_phazonMorphT > 1.f)\n        x39c_phazonMorphT = 1.f;\n    } else {\n      x39c_phazonMorphT -= 2.f * dt;\n      if (x39c_phazonMorphT < 0.f) {\n        x835_26_phazonBeamMorphing = false;\n        x39c_phazonMorphT = 0.f;\n      }\n    }\n  }\n\n  switch (x33c_phazonBeamState) {\n  case EPhazonBeamState::Entering:\n    if (x75c_phazonBeam) {\n      x75c_phazonBeam->Update(dt, mgr);\n      if (x75c_phazonBeam->IsLoaded()) {\n        StartPhazonBeamTransition(true, mgr, *mgr.GetPlayerState());\n        SetPhazonBeamMorph(false);\n        x33c_phazonBeamState = EPhazonBeamState::Active;\n        x338_nextState = ENextState::SetupBeam;\n      }\n    }\n    break;\n  case EPhazonBeamState::Exiting:\n    if (x738_nextBeam) {\n      x738_nextBeam->Update(dt, mgr);\n      if (x738_nextBeam->IsLoaded()) {\n        x835_25_inPhazonBeam = false;\n        StartPhazonBeamTransition(false, mgr, *mgr.GetPlayerState());\n        SetPhazonBeamMorph(false);\n        x33c_phazonBeamState = EPhazonBeamState::Inactive;\n        x338_nextState = ENextState::SetupBeam;\n      }\n    }\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CPlayerGun::EnableChargeFx(EChargeState state, CStateManager& mgr) {\n  x72c_currentBeam->ActivateCharge(true, false);\n  SetGunLightActive(true, mgr);\n  x72c_currentBeam->EnableSecondaryFx(CGunWeapon::ESecondaryFxType::Charge);\n  StopContinuousBeam(mgr, false);\n\n  switch (x310_currentBeam) {\n  case CPlayerState::EBeamId::Plasma:\n  case CPlayerState::EBeamId::Power:\n    x832_25_chargeEffectVisible = true;\n    break;\n  default:\n    break;\n  }\n\n  x2f8_stateFlags |= 0x7;\n  x318_comboAmmoIdx = 1;\n  x338_nextState = ENextState::StatusQuo;\n  x833_30_canShowAuxMuzzleEffect = true;\n\n  x800_auxMuzzleGenerators[size_t(x320_currentAuxBeam)] =\n      std::make_unique<CElementGen>(x7c0_auxMuzzleEffects[size_t(x320_currentAuxBeam)]);\n\n  x800_auxMuzzleGenerators[size_t(x320_currentAuxBeam)]->SetParticleEmission(true);\n}\n\nvoid CPlayerGun::UpdateChargeState(float dt, CStateManager& mgr) {\n  switch (x32c_chargePhase) {\n  case EChargePhase::ChargeRequested:\n    x340_chargeBeamFactor = 0.f;\n    x330_chargeState = EChargeState::Normal;\n    x832_27_chargeAnimStarted = false;\n    x834_24_charging = true;\n    x32c_chargePhase = EChargePhase::AnimAndSfx;\n    break;\n  case EChargePhase::AnimAndSfx:\n    if (!x832_27_chargeAnimStarted) {\n      if (x340_chargeBeamFactor > kChargeStart && x832_25_chargeEffectVisible) {\n        x832_25_chargeEffectVisible = false;\n      }\n      if (x340_chargeBeamFactor > kChargeAnimStart) {\n        PlayAnim(NWeaponTypes::EGunAnimType::ChargeUp, false);\n        if (!x2e0_chargeSfx) {\n          x2e0_chargeSfx =\n              NWeaponTypes::play_sfx(skBeamChargeUpSound[size_t(x310_currentBeam)], x834_27_underwater, true, 0.165f);\n        }\n        if (x830_chargeRumbleHandle == -1) {\n          x830_chargeRumbleHandle =\n              mgr.GetRumbleManager().Rumble(mgr, ERumbleFxId::PlayerGunCharge, 1.f, ERumblePriority::Three);\n        }\n        x832_27_chargeAnimStarted = true;\n      }\n    } else {\n      if (x340_chargeBeamFactor >= kChargeFxStart && (x2f8_stateFlags & 0x8) != 0x8) {\n        x832_25_chargeEffectVisible = true;\n        x832_27_chargeAnimStarted = false;\n        x32c_chargePhase = EChargePhase::FxGrowing;\n        x330_chargeState = EChargeState::Charged;\n        EnableChargeFx(EChargeState::Charged, mgr);\n        PlayAnim(NWeaponTypes::EGunAnimType::ChargeLoop, true);\n      }\n    }\n    break;\n  case EChargePhase::FxGrowing:\n    if (x340_chargeBeamFactor >= 1.f)\n      x32c_chargePhase = EChargePhase::FxGrown;\n    break;\n  case EChargePhase::ComboXfer:\n    if (x344_comboXferTimer >= 1.f) {\n      x32c_chargePhase = EChargePhase::ComboXferDone;\n      x832_25_chargeEffectVisible = false;\n    }\n    break;\n  case EChargePhase::ComboXferDone:\n    x32c_chargePhase = EChargePhase::ComboFire;\n    x348_chargeCooldownTimer = 0.f;\n    break;\n  case EChargePhase::ComboFire:\n    x740_grappleArm->EnterComboFire(s32(x310_currentBeam), mgr);\n    x73c_gunMotion->PlayPasAnim(SamusGun::EAnimationState::ComboFire, mgr, 0.f, false);\n    x72c_currentBeam->PlayPasAnim(SamusGun::EAnimationState::ComboFire, mgr, 0.f);\n    x833_31_inFreeLook = false;\n    x32c_chargePhase = EChargePhase::ComboFireDone;\n    break;\n  case EChargePhase::ChargeCooldown:\n    if ((x2f8_stateFlags & 0x10) != 0x10) {\n      x348_chargeCooldownTimer += dt;\n      if (x348_chargeCooldownTimer >= 0.3f && x72c_currentBeam->IsChargeAnimOver())\n        x32c_chargePhase = EChargePhase::ChargeDone;\n    } else {\n      x832_24_coolingCharge = false;\n    }\n    break;\n  case EChargePhase::ChargeDone:\n    ResetCharge(mgr, false);\n    Reset(mgr, false);\n    break;\n  default:\n    break;\n  }\n\n  if (x2e0_chargeSfx)\n    CSfxManager::PitchBend(x2e0_chargeSfx, x834_27_underwater ? -1.f : 0.f);\n  if (x32c_chargePhase > EChargePhase::NotCharging && x32c_chargePhase < EChargePhase::FxGrown) {\n    x340_chargeBeamFactor += kChargeSpeed * dt;\n    if (x340_chargeBeamFactor > 1.f)\n      x340_chargeBeamFactor = 1.f;\n  }\n}\n\nvoid CPlayerGun::UpdateAuxWeapons(float dt, const zeus::CTransform& targetXf, CStateManager& mgr) {\n  zeus::CVector3f firePoint =\n      x4a8_gunWorldXf * x418_beamLocalXf.origin + mgr.GetCameraManager()->GetGlobalCameraTranslation(mgr);\n  bool done = x744_auxWeapon->UpdateComboFx(dt, sGunScale, firePoint, targetXf, mgr);\n  if ((x2f8_stateFlags & 0x10) == 0x10) {\n    if (x310_currentBeam == CPlayerState::EBeamId::Wave && x744_auxWeapon->HasTarget(mgr) == kInvalidUniqueId) {\n      TUniqueId targetId = GetTargetId(mgr);\n      if (targetId == kInvalidUniqueId)\n        targetId = mgr.GetPlayer().GetAimTarget();\n      x744_auxWeapon->SetNewTarget(targetId, mgr);\n    }\n    if (done)\n      return;\n    done = x310_currentBeam == CPlayerState::EBeamId::Wave || x310_currentBeam == CPlayerState::EBeamId::Plasma;\n    if (!done)\n      if (x72c_currentBeam->ComboFireOver())\n        done = true;\n    x72c_currentBeam->EnableSecondaryFx(CGunWeapon::ESecondaryFxType::CancelCharge);\n    if (done) {\n      x32c_chargePhase = EChargePhase::ChargeDone;\n      ReturnArmAndGunToDefault(mgr, false);\n      if ((x2f8_stateFlags & 0x8) != 0x8) {\n        x2f8_stateFlags |= 0x1;\n        x2f8_stateFlags &= 0xFFE9;\n      }\n      x318_comboAmmoIdx = 0;\n      x31c_missileMode = EMissileMode::Inactive;\n    }\n  } else if (x833_28_phazonBeamActive) {\n    static_cast<CPhazonBeam*>(x72c_currentBeam)->UpdateBeam(dt, targetXf, x418_beamLocalXf.origin, mgr);\n  } else if (x310_currentBeam == CPlayerState::EBeamId::Plasma) {\n    static_cast<CPlasmaBeam*>(x72c_currentBeam)->UpdateBeam(dt, targetXf, x418_beamLocalXf.origin, mgr);\n  }\n}\n\nvoid CPlayerGun::DoUserAnimEvent(float dt, CStateManager& mgr, const CInt32POINode& node, EUserEventType type) {\n  switch (type) {\n  case EUserEventType::Projectile:\n    if (x32c_chargePhase != EChargePhase::ComboFireDone)\n      return;\n    bool doFireSecondary;\n    if (x310_currentBeam != CPlayerState::EBeamId::Wave && x310_currentBeam != CPlayerState::EBeamId::Plasma)\n      doFireSecondary = true;\n    else\n      doFireSecondary = (x2ec_lastFireButtonStates & 0x1) != 0;\n    if (doFireSecondary)\n      FireSecondary(dt, mgr);\n    if ((x2f8_stateFlags & 0x10) != 0x10)\n      x2f8_stateFlags |= 0x10;\n    CancelCharge(mgr, true);\n    if (doFireSecondary)\n      x72c_currentBeam->EnableSecondaryFx(CGunWeapon::ESecondaryFxType::ToCombo);\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CPlayerGun::DoUserAnimEvents(float dt, CStateManager& mgr) {\n  zeus::CVector3f posToCam = mgr.GetCameraManager()->GetCurrentCamera(mgr)->GetTranslation() - x3e8_xf.origin;\n  const CAnimData& animData = *x72c_currentBeam->GetSolidModelData().GetAnimationData();\n  for (size_t i = 0; i < animData.GetPassedSoundPOICount(); ++i) {\n    const CSoundPOINode& node = CAnimData::g_SoundPOINodes[i];\n    if (node.GetPoiType() != EPOIType::Sound ||\n        (node.GetCharacterIndex() != -1 && animData.x204_charIdx != node.GetCharacterIndex())) {\n      continue;\n    }\n    NWeaponTypes::do_sound_event(x670_animSfx, x328_animSfxPitch, false, node.GetSfxId(), node.GetWeight(),\n                                 node.GetFlags(), node.GetFalloff(), node.GetMaxDist(), 0.16f, 1.f, posToCam,\n                                 x3e8_xf.origin, mgr.GetPlayer().GetAreaIdAlways(), mgr);\n  }\n  for (size_t i = 0; i < animData.GetPassedIntPOICount(); ++i) {\n    const CInt32POINode& node = CAnimData::g_Int32POINodes[i];\n    switch (node.GetPoiType()) {\n    case EPOIType::UserEvent:\n      DoUserAnimEvent(dt, mgr, node, EUserEventType(node.GetValue()));\n      break;\n    case EPOIType::SoundInt32:\n      if (node.GetCharacterIndex() != -1 && animData.x204_charIdx != node.GetCharacterIndex())\n        break;\n      NWeaponTypes::do_sound_event(x670_animSfx, x328_animSfxPitch, false, u32(node.GetValue()), node.GetWeight(),\n                                   node.GetFlags(), 0.1f, 150.f, 0.16f, 1.f, posToCam, x3e8_xf.origin,\n                                   mgr.GetPlayer().GetAreaIdAlways(), mgr);\n      break;\n    default:\n      break;\n    }\n  }\n}\n\nTUniqueId CPlayerGun::GetTargetId(CStateManager& mgr) const {\n  TUniqueId ret = mgr.GetPlayer().GetOrbitTargetId();\n  if (x832_26_comboFiring && ret == kInvalidUniqueId && x310_currentBeam == CPlayerState::EBeamId::Wave)\n    ret = mgr.GetPlayer().GetOrbitNextTargetId();\n  if (ret == kInvalidUniqueId)\n    return ret;\n  if (TCastToConstPtr<CActor> act = mgr.GetObjectById(ret))\n    if (!act->GetMaterialList().HasMaterial(EMaterialTypes::Target))\n      ret = kInvalidUniqueId;\n  return ret;\n}\n\nvoid CPlayerGun::CancelLockOn() {\n  if (x832_29_lockedOn) {\n    x832_29_lockedOn = false;\n    x6a0_motionState.SetState(CMotionState::EMotionState::CancelLockOn);\n    if (x32c_chargePhase == EChargePhase::NotCharging && x318_comboAmmoIdx != 1)\n      PlayAnim(NWeaponTypes::EGunAnimType::BasePosition, false);\n  }\n}\n\nvoid CPlayerGun::FireSecondary(float dt, CStateManager& mgr) {\n  if (mgr.GetCameraManager()->IsInCinematicCamera()) {\n    return;\n  }\n\n  if (x835_25_inPhazonBeam || x318_comboAmmoIdx == 0 ||\n      !mgr.GetPlayerState()->HasPowerUp(skItemArr[x318_comboAmmoIdx]) || (x2f8_stateFlags & 0x4) != 0x4) {\n    NWeaponTypes::play_sfx(SFXwpn_invalid_action, x834_27_underwater, false, 0.165f);\n    return;\n  }\n\n  bool comboFired = false;\n  if (x318_comboAmmoIdx == 1) {\n    x300_remainingMissiles = mgr.GetPlayerState()->GetItemAmount(CPlayerState::EItemType::Missiles);\n    if (mgr.GetWeaponIdCount(x538_playerId, EWeaponType::Missile) < 3 && x300_remainingMissiles != 0) {\n      mgr.GetPlayerState()->DecrPickup(CPlayerState::EItemType::Missiles,\n                                       x832_26_comboFiring ? mgr.GetPlayerState()->GetMissileCostForAltAttack() : 1);\n      comboFired = true;\n    }\n    if (x300_remainingMissiles > 5) {\n      x300_remainingMissiles = 5;\n    } else {\n      x300_remainingMissiles -= 1;\n    }\n  }\n\n  if (comboFired) {\n    TUniqueId targetId = GetTargetId(mgr);\n    if (x832_26_comboFiring && targetId == kInvalidUniqueId && x310_currentBeam == CPlayerState::EBeamId::Wave) {\n      targetId = mgr.GetPlayer().GetAimTarget();\n    }\n    zeus::CTransform fireXf = x833_29_pointBlankWorldSurface ? x448_elbowWorldXf : x4a8_gunWorldXf * x418_beamLocalXf;\n    if (!x833_29_pointBlankWorldSurface && x364_gunStrikeCoolTimer <= 0.f) {\n      zeus::CVector3f backupOrigin = fireXf.origin;\n      fireXf = x478_assistAimXf;\n      fireXf.origin = backupOrigin;\n    }\n    fireXf.origin += mgr.GetCameraManager()->GetGlobalCameraTranslation(mgr);\n    x744_auxWeapon->Fire(dt, x834_27_underwater, x310_currentBeam, x330_chargeState, fireXf, mgr,\n                         x72c_currentBeam->GetWeaponType(), targetId);\n    mgr.InformListeners(x4a8_gunWorldXf.origin, EListenNoiseType::PlayerFire);\n    x3a0_missileExitTimer = 7.f;\n    if (!x832_26_comboFiring) {\n      PlayAnim(NWeaponTypes::EGunAnimType::MissileShoot, false);\n      x338_nextState = x300_remainingMissiles > 0 ? ENextState::MissileReload : ENextState::MissileShotDone;\n      x2f8_stateFlags &= ~0x4;\n    }\n  } else {\n    NWeaponTypes::play_sfx(skItemEmptySound[x318_comboAmmoIdx], x834_27_underwater, false, 0.165f);\n  }\n}\n\nvoid CPlayerGun::ResetCharged(float dt, CStateManager& mgr) {\n  if (x832_26_comboFiring)\n    return;\n  if (x32c_chargePhase >= EChargePhase::FxGrowing) {\n    x833_30_canShowAuxMuzzleEffect = false;\n    UpdateNormalShotCycle(dt, mgr);\n    x832_24_coolingCharge = true;\n    CancelCharge(mgr, true);\n  } else if (x32c_chargePhase != EChargePhase::NotCharging) {\n    x320_currentAuxBeam = x310_currentBeam;\n    x833_30_canShowAuxMuzzleEffect = true;\n    x32c_chargePhase = EChargePhase::ChargeDone;\n  }\n  StopChargeSound(mgr);\n}\n\nvoid CPlayerGun::ActivateCombo(CStateManager& mgr) {\n  if (x832_26_comboFiring)\n    return;\n\n  if (mgr.GetPlayerState()->GetItemAmount(skItemArr[x318_comboAmmoIdx]) >=\n      mgr.GetPlayerState()->GetMissileCostForAltAttack()) {\n    bool canFire = true;\n    if (x310_currentBeam == CPlayerState::EBeamId::Plasma)\n      canFire = !x834_27_underwater;\n    if (canFire) {\n      x832_26_comboFiring = true;\n      const auto& xferEffect = x72c_currentBeam->GetComboXferDescr();\n      if (xferEffect.IsLoaded()) {\n        x77c_comboXferGen = std::make_unique<CElementGen>(xferEffect);\n        x77c_comboXferGen->SetGlobalScale(sGunScale);\n      }\n      x72c_currentBeam->EnableCharge(true);\n      StopChargeSound(mgr);\n      NWeaponTypes::play_sfx(SFXwpn_combo_xfer, x834_27_underwater, false, 0.165f);\n      x32c_chargePhase = EChargePhase::ComboXfer;\n    }\n  } else {\n    NWeaponTypes::play_sfx(SFXwpn_invalid_action, x834_27_underwater, false, 0.165f);\n  }\n}\n\nvoid CPlayerGun::ProcessChargeState(u32 releasedStates, u32 pressedStates, CStateManager& mgr, float dt) {\n  if ((releasedStates & 0x1) != 0) {\n    ResetCharged(dt, mgr);\n    return;\n  }\n  if ((pressedStates & 0x1) != 0) {\n    if (x32c_chargePhase == EChargePhase::NotCharging && (pressedStates & 0x1) != 0 &&\n        x348_chargeCooldownTimer == 0.f && x832_28_readyForShot == 1) {\n      UpdateNormalShotCycle(dt, mgr);\n      x32c_chargePhase = EChargePhase::ChargeRequested;\n    }\n  } else {\n    const auto& state = mgr.GetPlayerState();\n    if (state->HasPowerUp(CPlayerState::EItemType::Missiles) && (pressedStates & 0x2) != 0) {\n      if (x32c_chargePhase >= EChargePhase::FxGrown) {\n        if (state->HasPowerUp(skBeamComboArr[size_t(x310_currentBeam)]))\n          ActivateCombo(mgr);\n      } else if (x32c_chargePhase == EChargePhase::NotCharging) {\n        FireSecondary(dt, mgr);\n      }\n    }\n  }\n}\n\nvoid CPlayerGun::ResetNormal(CStateManager& mgr) {\n  Reset(mgr, false);\n  x832_28_readyForShot = false;\n}\n\nvoid CPlayerGun::UpdateNormalShotCycle(float dt, CStateManager& mgr) {\n  if (!ExitMissile())\n    return;\n  if (mgr.GetCameraManager()->IsInCinematicCamera())\n    return;\n  x832_25_chargeEffectVisible = x833_28_phazonBeamActive || x310_currentBeam != CPlayerState::EBeamId::Plasma ||\n                                x32c_chargePhase != EChargePhase::NotCharging;\n  x30c_rapidFireShots += 1;\n  zeus::CTransform xf = x833_29_pointBlankWorldSurface ? x448_elbowWorldXf : x4a8_gunWorldXf * x418_beamLocalXf;\n  if (!x833_29_pointBlankWorldSurface && x364_gunStrikeCoolTimer <= 0.f) {\n    zeus::CVector3f oldOrigin = xf.origin;\n    xf = x478_assistAimXf;\n    xf.origin = oldOrigin;\n  }\n  xf.origin += mgr.GetCameraManager()->GetGlobalCameraTranslation(mgr);\n  x38c_muzzleEffectVisTimer = 0.0625f;\n  TUniqueId homingTarget;\n  if (x72c_currentBeam->GetVelocityInfo().GetTargetHoming(int(x330_chargeState)))\n    homingTarget = GetTargetId(mgr);\n  else\n    homingTarget = kInvalidUniqueId;\n  x72c_currentBeam->Fire(x834_27_underwater, dt, x330_chargeState, xf, mgr, homingTarget, x340_chargeBeamFactor,\n                         x340_chargeBeamFactor);\n  mgr.InformListeners(x4a8_gunWorldXf.origin, EListenNoiseType::PlayerFire);\n}\n\nvoid CPlayerGun::ProcessNormalState(u32 releasedStates, u32 pressedStates, CStateManager& mgr, float dt) {\n  if ((releasedStates & 0x1) != 0) {\n    ResetNormal(mgr);\n    return;\n  }\n\n  if ((pressedStates & 0x1) != 0 && x348_chargeCooldownTimer == 0.f && x832_28_readyForShot == 1) {\n    UpdateNormalShotCycle(dt, mgr);\n    return;\n  }\n  if ((pressedStates & 0x2) != 0) {\n    FireSecondary(dt, mgr);\n  }\n}\n\nvoid CPlayerGun::UpdateWeaponFire(float dt, const CPlayerState& playerState, CStateManager& mgr) {\n  u32 oldFiring = x2ec_lastFireButtonStates;\n  x2ec_lastFireButtonStates = x2f4_fireButtonStates;\n  u32 pressedStates = x2f4_fireButtonStates & (oldFiring ^ x2f4_fireButtonStates);\n  x2f0_pressedFireButtonStates = pressedStates;\n  u32 releasedStates = oldFiring & (oldFiring ^ x2f4_fireButtonStates);\n  x832_28_readyForShot = false;\n\n  CPlayer& player = mgr.GetPlayer();\n  if (!x832_24_coolingCharge && !x834_30_inBigStrike) {\n    float coolDown = x72c_currentBeam->GetWeaponInfo().x0_coolDown;\n    if ((pressedStates & 0x1) == 0) {\n      if (x390_cooldown >= coolDown) {\n        x390_cooldown = coolDown;\n        if (player.GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Unmorphed &&\n            mgr.GetPlayerState()->ItemEnabled(CPlayerState::EItemType::ChargeBeam) &&\n            player.GetGunHolsterState() == CPlayer::EGunHolsterState::Drawn &&\n            player.GetGrappleState() == CPlayer::EGrappleState::None &&\n            mgr.GetPlayerState()->GetTransitioningVisor() != CPlayerState::EPlayerVisor::Scan &&\n            mgr.GetPlayerState()->GetCurrentVisor() != CPlayerState::EPlayerVisor::Scan &&\n            (x2ec_lastFireButtonStates & 0x1) != 0 && x32c_chargePhase == EChargePhase::NotCharging) {\n          x832_28_readyForShot = true;\n          pressedStates |= 0x1;\n          x390_cooldown = 0.f;\n        }\n      }\n    } else if (x390_cooldown >= coolDown) {\n      x832_28_readyForShot = true;\n      x390_cooldown = 0.f;\n    }\n    x390_cooldown += dt;\n  }\n\n  if (x834_28_requestImmediateRecharge)\n    x834_28_requestImmediateRecharge = (x2ec_lastFireButtonStates & 0x1) != 0;\n\n  if (player.GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed) {\n    x835_28_bombReady = false;\n    x835_29_powerBombReady = false;\n    if (!x835_31_actorAttached) {\n      x835_28_bombReady = true;\n      if (x53a_powerBomb != kInvalidUniqueId && !mgr.CanCreateProjectile(x538_playerId, EWeaponType::PowerBomb, 1)) {\n        const auto* pb = static_cast<const CPowerBomb*>(mgr.GetObjectById(x53a_powerBomb));\n        if (pb && pb->GetCurTime() <= 4.25f) {\n          x835_28_bombReady = false;\n        } else {\n          x53a_powerBomb = kInvalidUniqueId;\n        }\n      }\n      if (((pressedStates & 0x1) != 0 || x32c_chargePhase != EChargePhase::NotCharging) &&\n          mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::MorphBallBombs)) {\n        if (x835_28_bombReady)\n          DropBomb(EBWeapon::Bomb, mgr);\n      } else if (mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::PowerBombs) &&\n                 mgr.GetPlayerState()->GetItemAmount(CPlayerState::EItemType::PowerBombs) > 0) {\n        x835_29_powerBombReady = mgr.CanCreateProjectile(x538_playerId, EWeaponType::PowerBomb, 1) &&\n                                 mgr.CanCreateProjectile(x538_playerId, EWeaponType::Bomb, 1);\n        if ((pressedStates & 0x2) != 0 && x835_29_powerBombReady)\n          DropBomb(EBWeapon::PowerBomb, mgr);\n      }\n    }\n  } else if ((x2f8_stateFlags & 0x8) != 0x8 &&\n             player.GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Unmorphed) {\n    if ((pressedStates & 0x2) != 0 && x318_comboAmmoIdx == 0 && (x2f8_stateFlags & 0x2) != 0x2 &&\n        x32c_chargePhase == EChargePhase::NotCharging) {\n      u32 missileCount = mgr.GetPlayerState()->GetItemAmount(CPlayerState::EItemType::Missiles);\n      if (x338_nextState != ENextState::EnterMissile && x338_nextState != ENextState::ExitMissile) {\n        if (mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::Missiles) && missileCount > 0) {\n          x300_remainingMissiles = missileCount;\n          if (x300_remainingMissiles > 5)\n            x300_remainingMissiles = 5;\n          if (!x835_25_inPhazonBeam) {\n            x2f8_stateFlags &= ~0x1;\n            x2f8_stateFlags |= 0x6;\n            x318_comboAmmoIdx = 1;\n            x31c_missileMode = EMissileMode::Active;\n          }\n          FireSecondary(dt, mgr);\n        } else {\n          if (!CSfxManager::IsPlaying(x2e4_invalidSfx))\n            x2e4_invalidSfx = NWeaponTypes::play_sfx(SFXwpn_invalid_action, x834_27_underwater, false, 0.165f);\n          else\n            x2e4_invalidSfx.reset();\n        }\n      }\n    } else {\n      if (x3a4_fidget.GetState() == CFidget::EState::NoFidget) {\n        if ((x2f8_stateFlags & 0x10) == 0x10 && x744_auxWeapon->IsComboFxActive(mgr)) {\n          if (x2ec_lastFireButtonStates == 0 ||\n              (x310_currentBeam == CPlayerState::EBeamId::Wave && x833_29_pointBlankWorldSurface)) {\n            StopContinuousBeam(mgr, (x2f8_stateFlags & 0x8) == 0x8);\n          }\n        } else {\n          if (mgr.GetPlayerState()->ItemEnabled(CPlayerState::EItemType::ChargeBeam) &&\n              x33c_phazonBeamState == EPhazonBeamState::Inactive)\n            ProcessChargeState(releasedStates, pressedStates, mgr, dt);\n          else\n            ProcessNormalState(releasedStates, pressedStates, mgr, dt);\n        }\n      }\n    }\n  }\n}\n\nvoid CPlayerGun::EnterFreeLook(CStateManager& mgr) {\n  if (!x832_30_requestReturnToDefault)\n    x73c_gunMotion->PlayPasAnim(SamusGun::EAnimationState::FreeLook, mgr, 0.f, false);\n  x740_grappleArm->EnterFreeLook(x835_25_inPhazonBeam ? 1 : s32(x310_currentBeam), x73c_gunMotion->GetFreeLookSetId(),\n                                 mgr);\n}\n\nvoid CPlayerGun::SetFidgetAnimBits(int animSet, bool beamOnly) {\n  x2fc_fidgetAnimBits = 0;\n  if (beamOnly) {\n    x2fc_fidgetAnimBits = 2;\n    return;\n  }\n\n  switch (x3a4_fidget.GetType()) {\n  case SamusGun::EFidgetType::Minor:\n    x2fc_fidgetAnimBits = 1;\n    if (animSet != 1)\n      return;\n    x2fc_fidgetAnimBits |= 4;\n    break;\n  case SamusGun::EFidgetType::Major:\n    if (animSet >= 6 || animSet < 4)\n      x2fc_fidgetAnimBits = 2;\n    else\n      x2fc_fidgetAnimBits = 1;\n    x2fc_fidgetAnimBits |= 4;\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CPlayerGun::AsyncLoadFidget(CStateManager& mgr) {\n  SetFidgetAnimBits(x3a4_fidget.GetAnimSet(), x3a4_fidget.GetState() == CFidget::EState::HolsterBeam);\n  if ((x2fc_fidgetAnimBits & 0x1) == 0x1)\n    x73c_gunMotion->GunController().LoadFidgetAnimAsync(mgr, s32(x3a4_fidget.GetType()), s32(x310_currentBeam),\n                                                        x3a4_fidget.GetAnimSet());\n  if ((x2fc_fidgetAnimBits & 0x2) == 0x2) {\n    x72c_currentBeam->AsyncLoadFidget(\n        mgr,\n        (x3a4_fidget.GetState() == CFidget::EState::HolsterBeam ? SamusGun::EFidgetType::Minor : x3a4_fidget.GetType()),\n        x3a4_fidget.GetAnimSet());\n    x832_31_inRestPose = false;\n  }\n  if ((x2fc_fidgetAnimBits & 0x4) == 0x4)\n    if (CGunController* gc = x740_grappleArm->GunController())\n      gc->LoadFidgetAnimAsync(mgr, s32(x3a4_fidget.GetType()),\n                              x3a4_fidget.GetType() != SamusGun::EFidgetType::Minor ? s32(x310_currentBeam) : 0,\n                              x3a4_fidget.GetAnimSet());\n}\n\nbool CPlayerGun::IsFidgetLoaded() const {\n  u32 loadFlags = 0;\n  if ((x2fc_fidgetAnimBits & 0x1) == 0x1 && x73c_gunMotion->GunController().IsFidgetLoaded()) {\n    loadFlags |= 0x1;\n  }\n  if ((x2fc_fidgetAnimBits & 0x2) == 0x2 && x72c_currentBeam->IsFidgetLoaded()) {\n    loadFlags |= 0x2;\n  }\n  if ((x2fc_fidgetAnimBits & 0x4) == 0x4) {\n    if (CGunController* gc = x740_grappleArm->GunController()) {\n      if (gc->IsFidgetLoaded()) {\n        loadFlags |= 0x4;\n      }\n    }\n  }\n  return x2fc_fidgetAnimBits == loadFlags;\n}\n\nvoid CPlayerGun::EnterFidget(CStateManager& mgr) {\n  if ((x2fc_fidgetAnimBits & 0x1) == 0x1) {\n    x73c_gunMotion->EnterFidget(mgr, x3a4_fidget.GetType(), x3a4_fidget.GetAnimSet());\n    x834_25_gunMotionFidgeting = true;\n  } else {\n    x834_25_gunMotionFidgeting = false;\n  }\n\n  if ((x2fc_fidgetAnimBits & 0x2) == 0x2)\n    x72c_currentBeam->EnterFidget(mgr, x3a4_fidget.GetType(), x3a4_fidget.GetAnimSet());\n\n  if ((x2fc_fidgetAnimBits & 0x4) == 0x4)\n    x740_grappleArm->EnterFidget(mgr, x3a4_fidget.GetType(),\n                                 x3a4_fidget.GetType() != SamusGun::EFidgetType::Minor ? s32(x310_currentBeam) : 0,\n                                 x3a4_fidget.GetAnimSet());\n\n  UnLoadFidget();\n  x3a4_fidget.DoneLoading();\n}\n\nvoid CPlayerGun::UpdateGunIdle(bool inStrikeCooldown, float camBobT, float dt, CStateManager& mgr) {\n  CPlayer& player = mgr.GetPlayer();\n  if (player.IsInFreeLook() && !x832_29_lockedOn && !x740_grappleArm->IsGrappling() &&\n      x3a4_fidget.GetState() != CFidget::EState::HolsterBeam &&\n      player.GetGunHolsterState() == CPlayer::EGunHolsterState::Drawn && !x834_30_inBigStrike) {\n    if ((x2f8_stateFlags & 0x8) != 0x8) {\n      if (!x833_31_inFreeLook && !x834_26_animPlaying) {\n        if (x388_enterFreeLookDelayTimer < 0.25f)\n          x388_enterFreeLookDelayTimer += dt;\n        if (x388_enterFreeLookDelayTimer >= 0.25f && !x740_grappleArm->IsSuitLoading()) {\n          EnterFreeLook(mgr);\n          x833_31_inFreeLook = true;\n        }\n      } else {\n        x388_enterFreeLookDelayTimer = 0.f;\n        if (x834_26_animPlaying)\n          ResetIdle(mgr);\n      }\n    }\n  } else {\n    if (x833_31_inFreeLook) {\n      if ((x2f8_stateFlags & 0x10) != 0x10) {\n        x73c_gunMotion->ReturnToDefault(mgr, x834_30_inBigStrike);\n        x740_grappleArm->ReturnToDefault(mgr, 0.f, false);\n      }\n      x833_31_inFreeLook = false;\n    }\n    x388_enterFreeLookDelayTimer = 0.f;\n    if (player.GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Morphed) {\n      x833_24_notFidgeting =\n          !(player.GetSurfaceRestraint() != CPlayer::ESurfaceRestraints::Water &&\n            mgr.GetPlayerState()->GetCurrentVisor() != CPlayerState::EPlayerVisor::Scan &&\n            (x2f4_fireButtonStates & 0x3) == 0 && x32c_chargePhase == EChargePhase::NotCharging && !x832_29_lockedOn &&\n            (x2f8_stateFlags & 0x8) != 0x8 && x364_gunStrikeCoolTimer <= 0.f &&\n            player.GetPlayerMovementState() == CPlayer::EPlayerMovementState::OnGround && !player.IsInFreeLook() &&\n            !player.GetFreeLookStickState() && player.GetOrbitState() == CPlayer::EPlayerOrbitState::NoOrbit &&\n            std::fabs(player.GetAngularVelocityOR().angle()) <= 0.1f && camBobT <= 0.01f &&\n            !mgr.GetCameraManager()->IsInCinematicCamera() &&\n            player.GetGunHolsterState() == CPlayer::EGunHolsterState::Drawn &&\n            player.GetGrappleState() == CPlayer::EGrappleState::None && !x834_30_inBigStrike && !x835_25_inPhazonBeam);\n      if (x833_24_notFidgeting) {\n        if (!x834_30_inBigStrike) {\n          bool doWander = camBobT > 0.01f && (x2f4_fireButtonStates & 0x3) == 0;\n          if (doWander) {\n            x370_gunMotionSpeedMult = 1.f;\n            x374_ = 0.f;\n            if (x364_gunStrikeCoolTimer <= 0.f && x368_idleWanderDelayTimer <= 0.f) {\n              x368_idleWanderDelayTimer = 8.f;\n              x73c_gunMotion->PlayPasAnim(SamusGun::EAnimationState::Wander, mgr, 0.f, false);\n              x324_idleState = EIdleState::Wander;\n              x550_camBob.SetState(CPlayerCameraBob::ECameraBobState::Walk, mgr);\n            }\n            x368_idleWanderDelayTimer -= dt;\n            x360_ -= dt;\n          }\n          if (!doWander || x834_26_animPlaying)\n            ResetIdle(mgr);\n        } else if (x394_damageTimer > 0.f) {\n          x394_damageTimer -= dt;\n        } else if (!x834_31_gunMotionInFidgetBasePosition) {\n          x394_damageTimer = 0.f;\n          x834_31_gunMotionInFidgetBasePosition = true;\n          x73c_gunMotion->BasePosition(true);\n        } else if (!x73c_gunMotion->GetModelData().GetAnimationData()->IsAnimTimeRemaining(0.001f, \"Whole Body\")) {\n          x834_30_inBigStrike = false;\n          x834_31_gunMotionInFidgetBasePosition = false;\n        }\n      } else {\n        switch (x3a4_fidget.Update(x2ec_lastFireButtonStates, camBobT > 0.01f, inStrikeCooldown, dt, mgr)) {\n        case CFidget::EState::NoFidget:\n          if (x324_idleState != EIdleState::Idle) {\n            x73c_gunMotion->PlayPasAnim(SamusGun::EAnimationState::Idle, mgr, 0.f, false);\n            x324_idleState = EIdleState::Idle;\n          }\n          x550_camBob.SetState(CPlayerCameraBob::ECameraBobState::WalkNoBob, mgr);\n          break;\n        case CFidget::EState::MinorFidget:\n        case CFidget::EState::MajorFidget:\n        case CFidget::EState::HolsterBeam:\n          if (x324_idleState != EIdleState::NotIdle) {\n            x73c_gunMotion->BasePosition(false);\n            x324_idleState = EIdleState::NotIdle;\n          }\n          AsyncLoadFidget(mgr);\n          break;\n        case CFidget::EState::Loading:\n          if (IsFidgetLoaded())\n            EnterFidget(mgr);\n          break;\n        case CFidget::EState::StillMinorFidget:\n        case CFidget::EState::StillMajorFidget:\n          x550_camBob.SetState(CPlayerCameraBob::ECameraBobState::Walk, mgr);\n          x833_24_notFidgeting = false;\n          x834_26_animPlaying =\n              x834_25_gunMotionFidgeting\n                  ? x73c_gunMotion->IsAnimPlaying()\n                  : x72c_currentBeam->GetSolidModelData().GetAnimationData()->IsAnimTimeRemaining(0.001f, \"Whole Body\");\n          if (!x834_26_animPlaying) {\n            x3a4_fidget.ResetMinor();\n            ReturnToRestPose();\n          }\n          break;\n        default:\n          break;\n        }\n      }\n      x550_camBob.Update(dt, mgr);\n    }\n  }\n}\n\nvoid CPlayerGun::Update(float grappleSwingT, float cameraBobT, float dt, CStateManager& mgr) {\n  CPlayer& player = mgr.GetPlayer();\n  CPlayerState& playerState = *mgr.GetPlayerState();\n  const bool isUnmorphed = player.GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Unmorphed;\n\n  bool becameFrozen = false;\n  if (isUnmorphed) {\n    becameFrozen = !x834_29_frozen && player.GetFrozenState();\n  }\n\n  bool becameThawed = false;\n  if (isUnmorphed) {\n    becameThawed = x834_29_frozen && !player.GetFrozenState();\n  }\n\n  x834_29_frozen = isUnmorphed && player.GetFrozenState();\n  float advDt = dt;\n  if (x834_29_frozen) {\n    advDt = 0.f;\n  }\n\n  const bool r23 = x678_morph.GetGunState() != CGunMorph::EGunState::OutWipeDone;\n  if (mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::XRay || r23) {\n    x6e0_rightHandModel.AdvanceAnimation(advDt, mgr, kInvalidAreaId, true);\n  }\n  if (r23 && x734_loadingBeam != nullptr && x734_loadingBeam != x72c_currentBeam) {\n    x744_auxWeapon->LoadIdle();\n    x734_loadingBeam->Update(advDt, mgr);\n  }\n  if (!x744_auxWeapon->IsLoaded()) {\n    x744_auxWeapon->LoadIdle();\n  }\n\n  if (becameFrozen) {\n    x72c_currentBeam->EnableSecondaryFx(CGunWeapon::ESecondaryFxType::None);\n    x72c_currentBeam->EnableFrozenEffect(CGunWeapon::EFrozenFxType::Frozen);\n  } else if (becameThawed) {\n    x72c_currentBeam->EnableFrozenEffect(CGunWeapon::EFrozenFxType::Thawed);\n  }\n\n  if (becameFrozen || becameThawed) {\n    x2f4_fireButtonStates = 0;\n    x2ec_lastFireButtonStates = 0;\n    CancelFiring(mgr);\n  }\n\n  x72c_currentBeam->Update(advDt, mgr);\n  x73c_gunMotion->Update(advDt * x370_gunMotionSpeedMult, mgr);\n  x740_grappleArm->Update(grappleSwingT, advDt, mgr);\n\n  if (x338_nextState != ENextState::StatusQuo) {\n    if (x678_morph.GetGunState() == CGunMorph::EGunState::InWipeDone) {\n      if (x338_nextState == ENextState::ChangeWeapon) {\n        ChangeWeapon(playerState, mgr);\n        x338_nextState = ENextState::StatusQuo;\n      }\n    } else if (!x72c_currentBeam->GetSolidModelData().GetAnimationData()->IsAnimTimeRemaining(0.001f, \"Whole Body\") ||\n               x832_30_requestReturnToDefault) {\n      bool statusQuo = true;\n      switch (x338_nextState) {\n      case ENextState::EnterMissile:\n        x2f8_stateFlags &= ~0x1;\n        x2f8_stateFlags |= 0x6;\n        x318_comboAmmoIdx = 1;\n        x31c_missileMode = EMissileMode::Active;\n        break;\n      case ENextState::ExitMissile:\n        if ((x2f8_stateFlags & 0x8) != 0x8) {\n          x2f8_stateFlags |= 0x1;\n          x2f8_stateFlags &= 0xFFE9;\n        }\n        x318_comboAmmoIdx = 0;\n        x31c_missileMode = EMissileMode::Inactive;\n        x390_cooldown = x72c_currentBeam->GetWeaponInfo().x0_coolDown;\n        break;\n      case ENextState::MissileReload:\n        PlayAnim(NWeaponTypes::EGunAnimType::MissileReload, false);\n        x338_nextState = ENextState::MissileShotDone;\n        statusQuo = false;\n        break;\n      case ENextState::MissileShotDone:\n        x2f8_stateFlags |= 0x4;\n        break;\n      case ENextState::ChangeWeapon:\n        ChangeWeapon(playerState, mgr);\n        break;\n      case ENextState::SetupBeam:\n        x390_cooldown = x72c_currentBeam->GetWeaponInfo().x0_coolDown;\n        x2f8_stateFlags &= ~0x8;\n        if ((x2f8_stateFlags & 0x8) != 0x8) {\n          x2f8_stateFlags |= 0x1;\n          x2f8_stateFlags &= 0xFFE9;\n        }\n        x318_comboAmmoIdx = 0;\n        x31c_missileMode = EMissileMode::Inactive;\n        break;\n      case ENextState::EnterPhazonBeam:\n        if (x75c_phazonBeam->IsLoaded())\n          break;\n        x72c_currentBeam->SetDrawHologram(true);\n        x75c_phazonBeam->Load(mgr, false);\n        x33c_phazonBeamState = EPhazonBeamState::Entering;\n        break;\n      case ENextState::ExitPhazonBeam:\n        if (x738_nextBeam->IsLoaded())\n          break;\n        x72c_currentBeam->SetDrawHologram(true);\n        x738_nextBeam->Load(mgr, false);\n        x33c_phazonBeamState = EPhazonBeamState::Exiting;\n        break;\n      default:\n        break;\n      }\n\n      if (statusQuo)\n        x338_nextState = ENextState::StatusQuo;\n    }\n  }\n\n  if (x37c_rapidFireShotsDecayTimer < 0.2f) {\n    x37c_rapidFireShotsDecayTimer += advDt;\n  } else {\n    x37c_rapidFireShotsDecayTimer = 0.f;\n    if (x30c_rapidFireShots > 0)\n      x30c_rapidFireShots -= 1;\n  }\n\n  if (x32c_chargePhase != EChargePhase::NotCharging && !player.GetFrozenState()) {\n    x34c_shakeX = chargeShakeTbl[mgr.GetActiveRandom()->Next() % 3] * x340_chargeBeamFactor;\n    x350_shakeZ = chargeShakeTbl[mgr.GetActiveRandom()->Next() % 3] * x340_chargeBeamFactor;\n  }\n\n  if (!x72c_currentBeam->IsLoaded()) {\n    return;\n  }\n\n  GetLctrWithShake(x4d8_gunLocalXf, x73c_gunMotion->GetModelData(), \"GBSE_SDK\", true, true);\n  GetLctrWithShake(x418_beamLocalXf, x72c_currentBeam->GetSolidModelData(), \"LBEAM\", false, true);\n  GetLctrWithShake(x508_elbowLocalXf, x72c_currentBeam->GetSolidModelData(), \"elbow\", false, false);\n  x4a8_gunWorldXf = x3e8_xf * x4d8_gunLocalXf * x550_camBob.GetCameraBobTransformation();\n\n  if (x740_grappleArm->GetActive() && !x740_grappleArm->IsGrappling())\n    UpdateLeftArmTransform(x72c_currentBeam->GetSolidModelData(), mgr);\n\n  x6a0_motionState.Update(x2f0_pressedFireButtonStates != 0 && x832_28_readyForShot &&\n                              x32c_chargePhase < EChargePhase::AnimAndSfx && !player.IsInFreeLook(),\n                          advDt, x4a8_gunWorldXf, mgr);\n\n  x72c_currentBeam->GetSolidModelData().AdvanceParticles(x4a8_gunWorldXf, advDt, mgr);\n  x72c_currentBeam->UpdateGunFx(x380_shotSmokeTimer > 2.f && x378_shotSmokeStartTimer > 0.15f, dt, mgr,\n                                x508_elbowLocalXf);\n\n  zeus::CTransform beamWorldXf = x4a8_gunWorldXf * x418_beamLocalXf;\n\n  if (player.GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Unmorphed &&\n      !mgr.GetCameraManager()->IsInCinematicCamera()) {\n    EntityList nearList;\n    zeus::CAABox aabb = x72c_currentBeam->GetBounds().getTransformedAABox(x4a8_gunWorldXf);\n    mgr.BuildNearList(nearList, aabb, sAimFilter, &player);\n    TUniqueId bestId = kInvalidUniqueId;\n    zeus::CVector3f dir = x4a8_gunWorldXf.frontVector().normalized();\n    zeus::CVector3f pos = dir * -0.5f + x4a8_gunWorldXf.origin;\n    CRayCastResult result = mgr.RayWorldIntersection(bestId, pos, dir, 3.5f, sAimFilter, nearList);\n    x833_29_pointBlankWorldSurface = result.IsValid();\n    if (result.IsValid()) {\n      x448_elbowWorldXf = x4a8_gunWorldXf * x508_elbowLocalXf;\n      x448_elbowWorldXf.origin += dir * -0.5f;\n      beamWorldXf.origin = result.GetPoint();\n    }\n  } else {\n    x833_29_pointBlankWorldSurface = false;\n  }\n\n  zeus::CTransform beamTargetXf = x833_29_pointBlankWorldSurface ? x448_elbowWorldXf : beamWorldXf;\n\n  zeus::CVector3f camTrans = mgr.GetCameraManager()->GetGlobalCameraTranslation(mgr);\n  beamWorldXf.origin += camTrans;\n  beamTargetXf.origin += camTrans;\n\n  if (x832_25_chargeEffectVisible) {\n    bool emitting = x833_30_canShowAuxMuzzleEffect ? x344_comboXferTimer < 1.f : false;\n    zeus::CVector3f scale((emitting && x832_26_comboFiring) ? (1.f - x344_comboXferTimer) * 2.f : 2.f);\n    x72c_currentBeam->UpdateMuzzleFx(advDt, scale, x418_beamLocalXf.origin, emitting);\n    CElementGen& gen = *x800_auxMuzzleGenerators[size_t(x320_currentAuxBeam)];\n    gen.SetGlobalOrientAndTrans(x418_beamLocalXf);\n    gen.SetGlobalScale(scale);\n    gen.SetParticleEmission(emitting);\n    gen.Update(advDt);\n  }\n\n  if (x748_rainSplashGenerator)\n    x748_rainSplashGenerator->Update(advDt, mgr);\n\n  UpdateGunLight(beamWorldXf, mgr);\n  ProcessGunMorph(advDt, mgr);\n  if (x835_26_phazonBeamMorphing)\n    ProcessPhazonGunMorph(advDt, mgr);\n\n  if (x832_26_comboFiring && x77c_comboXferGen) {\n    x77c_comboXferGen->SetGlobalTranslation(x418_beamLocalXf.origin);\n    x77c_comboXferGen->SetGlobalOrientation(x418_beamLocalXf.getRotation());\n    x77c_comboXferGen->Update(advDt);\n    x344_comboXferTimer += advDt * 4.f;\n  }\n\n  if (x35c_bombTime > 0.f) {\n    x35c_bombTime -= advDt;\n    if (x35c_bombTime <= 0.f)\n      x308_bombCount = 3;\n  }\n\n  if (playerState.ItemEnabled(CPlayerState::EItemType::ChargeBeam) && x32c_chargePhase != EChargePhase::NotCharging) {\n    UpdateChargeState(advDt, mgr);\n  } else {\n    x340_chargeBeamFactor -= advDt;\n    if (x340_chargeBeamFactor < 0.f)\n      x340_chargeBeamFactor = 0.f;\n  }\n\n  UpdateAuxWeapons(advDt, beamTargetXf, mgr);\n  DoUserAnimEvents(advDt, mgr);\n\n  if (player.GetOrbitState() == CPlayer::EPlayerOrbitState::OrbitObject && GetTargetId(mgr) != kInvalidUniqueId) {\n    if (!x832_29_lockedOn && !x832_26_comboFiring && (x2f8_stateFlags & 0x10) != 0x10) {\n      x832_29_lockedOn = true;\n      x6a0_motionState.SetState(CMotionState::EMotionState::LockOn);\n      ReturnArmAndGunToDefault(mgr, true);\n    }\n  } else {\n    CancelLockOn();\n  }\n\n  UpdateWeaponFire(advDt, playerState, mgr);\n  UpdateGunIdle(x364_gunStrikeCoolTimer > 0.f, cameraBobT, advDt, mgr);\n\n  if ((x2ec_lastFireButtonStates & 0x1) == 0x1) {\n    x378_shotSmokeStartTimer = 0.f;\n  } else if (x378_shotSmokeStartTimer < 2.f) {\n    x378_shotSmokeStartTimer += advDt;\n    if (x378_shotSmokeStartTimer > 1.f) {\n      x30c_rapidFireShots = 0;\n      x380_shotSmokeTimer = 0.f;\n    }\n  }\n\n  if (x38c_muzzleEffectVisTimer > 0.f)\n    x38c_muzzleEffectVisTimer -= advDt;\n\n  if (x30c_rapidFireShots > 5 && x380_shotSmokeTimer < 2.f)\n    x380_shotSmokeTimer += advDt;\n\n  if (x384_gunStrikeDelayTimer > 0.f)\n    x384_gunStrikeDelayTimer -= advDt;\n\n  if (x364_gunStrikeCoolTimer > 0.f) {\n    x2f4_fireButtonStates = 0;\n    x364_gunStrikeCoolTimer -= advDt;\n  }\n\n  if (isUnmorphed && (x2f8_stateFlags & 0x4) == 0x4) {\n    x3a0_missileExitTimer -= advDt;\n    if (x3a0_missileExitTimer < 0.f) {\n      x3a0_missileExitTimer = 0.f;\n      ExitMissile();\n    }\n  }\n}\n\nvoid CPlayerGun::PreRender(const CStateManager& mgr, const zeus::CFrustum& frustum, const zeus::CVector3f& camPos) {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CPlayerGun::PreRender\", zeus::skBlue);\n  const CPlayerState& playerState = *mgr.GetPlayerState();\n  if (playerState.GetCurrentVisor() == CPlayerState::EPlayerVisor::Scan)\n    return;\n\n  CPlayerState::EPlayerVisor activeVisor = playerState.GetActiveVisor(mgr);\n  switch (activeVisor) {\n  case CPlayerState::EPlayerVisor::Thermal:\n    x0_lights.BuildConstantAmbientLighting(\n        zeus::CColor(zeus::clamp(0.6f, 0.5f * x380_shotSmokeTimer + 0.6f - x378_shotSmokeStartTimer, 1.f), 1.f));\n    break;\n  case CPlayerState::EPlayerVisor::Combat: {\n    zeus::CAABox aabb = x72c_currentBeam->GetBounds(zeus::CTransform::Translate(camPos) * x4a8_gunWorldXf);\n    if (mgr.GetNextAreaId() != kInvalidAreaId) {\n      x0_lights.SetFindShadowLight(true);\n      x0_lights.SetShadowDynamicRangeThreshold(0.25f);\n      x0_lights.BuildAreaLightList(mgr, *mgr.GetWorld()->GetAreaAlways(mgr.GetNextAreaId()), aabb);\n    }\n    x0_lights.BuildDynamicLightList(mgr, aabb);\n    if (x0_lights.HasShadowLight()) {\n      if (x72c_currentBeam->IsLoaded()) {\n        x82c_shadow->BuildLightShadowTexture(mgr, mgr.GetNextAreaId(), x0_lights.GetShadowLightIndex(), aabb, true,\n                                             false);\n      }\n    } else {\n      x82c_shadow->ResetBlur();\n    }\n    break;\n  }\n  default:\n    break;\n  }\n\n  if (x740_grappleArm->GetActive())\n    x740_grappleArm->PreRender(mgr, frustum, camPos);\n\n  if (x678_morph.GetGunState() != CGunMorph::EGunState::OutWipeDone || activeVisor == CPlayerState::EPlayerVisor::XRay)\n    x6e0_rightHandModel.GetAnimationData()->PreRender();\n\n  if (x833_28_phazonBeamActive)\n    g_Renderer->AllocatePhazonSuitMaskTexture();\n}\n\nvoid CPlayerGun::RenderEnergyDrainEffects(const CStateManager& mgr) const {\n  if (const TCastToConstPtr<CPlayer> player = mgr.GetObjectById(x538_playerId)) {\n    for (const auto& source : player->GetEnergyDrain().GetEnergyDrainSources()) {\n      if (const auto* metroid =\n              CPatterned::CastTo<MP1::CMetroidBeta>(mgr.GetObjectById(source.GetEnergyDrainSourceId()))) {\n        metroid->RenderHitGunEffect();\n        return;\n      }\n    }\n  }\n}\n\nvoid CPlayerGun::DrawArm(const CStateManager& mgr, const zeus::CVector3f& pos, const CModelFlags& flags) const {\n  const CPlayer& player = mgr.GetPlayer();\n  if (!x740_grappleArm->GetActive() || x740_grappleArm->GetAnimState() == CGrappleArm::EArmState::Done)\n    return;\n\n  if (player.GetGrappleState() != CPlayer::EGrappleState::None ||\n      x740_grappleArm->GetTransform().basis[1].dot(player.GetTransform().basis[1]) > 0.1f) {\n    CModelFlags useFlags;\n    if (x740_grappleArm->IsArmMoving())\n      useFlags = flags;\n    else\n      useFlags = CModelFlags(0, 0, 3, zeus::skWhite);\n\n    x740_grappleArm->Render(mgr, pos, useFlags, &x0_lights);\n  }\n}\n\nzeus::CVector3f CPlayerGun::ConvertToScreenSpace(const zeus::CVector3f& pos, const CGameCamera& cam) const {\n  return cam.ConvertToScreenSpace(pos);\n}\n\nvoid CPlayerGun::CopyScreenTex() {\n  // Copy lower right quadrant to gpCopyTexBuf as RGBA8\n  u16 width = CGraphics::mViewport.mWidth / 2;\n  u16 height = CGraphics::mViewport.mHeight / 2;\n  GXSetTexCopySrc(width, height, width, height);\n  GXSetTexCopyDst(width, height, GX_TF_RGBA8, false);\n  GXCopyTex(CGraphics::mpSpareBuffer, false);\n  GXPixModeSync();\n}\n\nvoid CPlayerGun::DrawScreenTex(float z) {\n  // Use CopyScreenTex rendering to draw over framebuffer pixels in front of `z`\n  // This is accomplished using orthographic projection quad with sweeping `y` coordinates\n  // Depth is set to GEQUAL to obscure pixels in front rather than behind\n  const auto backupViewMatrix = CGraphics::mViewMatrix;\n  const auto backupProjectionState = CGraphics::GetProjectionState();\n  g_Renderer->SetViewportOrtho(false, -1.f, 1.f);\n  g_Renderer->SetBlendMode_AlphaBlended();\n  CGraphics::SetDepthWriteMode(true, ERglEnum::GEqual, true);\n  u16 width = CGraphics::mViewport.mWidth / 2;\n  u16 height = CGraphics::mViewport.mHeight / 2;\n  CGraphics::LoadDolphinSpareTexture(width, height, GX_TF_RGBA8, nullptr, GX_TEXMAP7);\n  constexpr std::array vtxDescList{\n      GXVtxDescList{GX_VA_POS, GX_DIRECT},\n      GXVtxDescList{GX_VA_TEX0, GX_DIRECT},\n      GXVtxDescList{GX_VA_NULL, GX_NONE},\n  };\n  CGX::SetVtxDescv(vtxDescList.data());\n  CGX::SetNumChans(0);\n  CGX::SetNumTexGens(1);\n  CGX::SetNumTevStages(1);\n  CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP7, GX_COLOR_NULL);\n  CGX::SetChanCtrl(CGX::EChannelId::Channel0, false, GX_SRC_REG, GX_SRC_REG, {}, GX_DF_NONE, GX_AF_NONE);\n  CGX::Begin(GX_TRIANGLESTRIP, GX_VTXFMT0, 4);\n  GXPosition3f32(CGraphics::mViewport.mHalfWidth, z, 0.f);\n  GXTexCoord2f32(0.f, 1.f);\n  GXPosition3f32(CGraphics::mViewport.mWidth, z, 0.f);\n  GXTexCoord2f32(1.f, 1.f);\n  GXPosition3f32(CGraphics::mViewport.mHalfWidth, z, CGraphics::mViewport.mHalfHeight);\n  GXTexCoord2f32(0.f, 0.f);\n  GXPosition3f32(CGraphics::mViewport.mWidth, z, CGraphics::mViewport.mHalfHeight);\n  GXTexCoord2f32(1.f, 0.f);\n  CGX::End();\n  CGraphics::SetDepthWriteMode(true, ERglEnum::LEqual, true);\n  CGraphics::SetViewPointMatrix(backupViewMatrix);\n  CGraphics::SetProjectionState(backupProjectionState);\n}\n\nvoid CPlayerGun::DrawClipCube(const zeus::CAABox& aabb) {\n  // Render AABB as completely transparent object, only modifying Z-buffer\n  const zeus::CColor color{1.f, 0.f};\n  g_Renderer->SetBlendMode_AlphaBlended();\n  CGraphics::SetCullMode(ERglCullMode::None);\n\n  g_Renderer->BeginTriangleStrip(4);\n  g_Renderer->PrimColor(color);\n  g_Renderer->PrimVertex(zeus::CVector3f{aabb.max.x(), aabb.max.y(), aabb.min.z()});\n  g_Renderer->PrimVertex(zeus::CVector3f{aabb.max.x(), aabb.min.y(), aabb.min.z()});\n  g_Renderer->PrimVertex(zeus::CVector3f{aabb.max.x(), aabb.max.y(), aabb.max.z()});\n  g_Renderer->PrimVertex(zeus::CVector3f{aabb.max.x(), aabb.min.y(), aabb.max.z()});\n  g_Renderer->EndPrimitive();\n\n  g_Renderer->BeginTriangleStrip(4);\n  g_Renderer->PrimColor(color);\n  g_Renderer->PrimVertex(zeus::CVector3f{aabb.min.x(), aabb.max.y(), aabb.min.z()});\n  g_Renderer->PrimVertex(zeus::CVector3f{aabb.max.x(), aabb.max.y(), aabb.min.z()});\n  g_Renderer->PrimVertex(zeus::CVector3f{aabb.min.x(), aabb.max.y(), aabb.max.z()});\n  g_Renderer->PrimVertex(zeus::CVector3f{aabb.max.x(), aabb.max.y(), aabb.max.z()});\n  g_Renderer->EndPrimitive();\n\n  g_Renderer->BeginTriangleStrip(4);\n  g_Renderer->PrimColor(color);\n  g_Renderer->PrimVertex(zeus::CVector3f{aabb.min.x(), aabb.max.y(), aabb.min.z()});\n  g_Renderer->PrimVertex(zeus::CVector3f{aabb.min.x(), aabb.min.y(), aabb.min.z()});\n  g_Renderer->PrimVertex(zeus::CVector3f{aabb.min.x(), aabb.max.y(), aabb.max.z()});\n  g_Renderer->PrimVertex(zeus::CVector3f{aabb.min.x(), aabb.min.y(), aabb.max.z()});\n  g_Renderer->EndPrimitive();\n\n  g_Renderer->BeginTriangleStrip(4);\n  g_Renderer->PrimColor(color);\n  g_Renderer->PrimVertex(zeus::CVector3f{aabb.min.x(), aabb.min.y(), aabb.min.z()});\n  g_Renderer->PrimVertex(zeus::CVector3f{aabb.max.x(), aabb.min.y(), aabb.min.z()});\n  g_Renderer->PrimVertex(zeus::CVector3f{aabb.min.x(), aabb.min.y(), aabb.max.z()});\n  g_Renderer->PrimVertex(zeus::CVector3f{aabb.max.x(), aabb.min.y(), aabb.max.z()});\n  g_Renderer->EndPrimitive();\n\n  g_Renderer->BeginTriangleStrip(4);\n  g_Renderer->PrimColor(color);\n  g_Renderer->PrimVertex(zeus::CVector3f{aabb.min.x(), aabb.min.y(), aabb.max.z()});\n  g_Renderer->PrimVertex(zeus::CVector3f{aabb.max.x(), aabb.min.y(), aabb.max.z()});\n  g_Renderer->PrimVertex(zeus::CVector3f{aabb.min.x(), aabb.max.y(), aabb.max.z()});\n  g_Renderer->PrimVertex(zeus::CVector3f{aabb.max.x(), aabb.max.y(), aabb.max.z()});\n  g_Renderer->EndPrimitive();\n\n  g_Renderer->BeginTriangleStrip(4);\n  g_Renderer->PrimColor(color);\n  g_Renderer->PrimVertex(zeus::CVector3f{aabb.min.x(), aabb.min.y(), aabb.min.z()});\n  g_Renderer->PrimVertex(zeus::CVector3f{aabb.max.x(), aabb.min.y(), aabb.min.z()});\n  g_Renderer->PrimVertex(zeus::CVector3f{aabb.min.x(), aabb.max.y(), aabb.min.z()});\n  g_Renderer->PrimVertex(zeus::CVector3f{aabb.max.x(), aabb.max.y(), aabb.min.z()});\n  g_Renderer->EndPrimitive();\n\n  CGraphics::SetCullMode(ERglCullMode::Front);\n}\n\nvoid CPlayerGun::Render(const CStateManager& mgr, const zeus::CVector3f& pos, const CModelFlags& flags) {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CPlayerGun::Render\", zeus::skMagenta);\n\n  CGraphics::CProjectionState projState = CGraphics::GetProjectionState();\n  CModelFlags beamFlags = flags;\n  if (mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::Thermal) {\n    beamFlags = kThermalFlags[size_t(x310_currentBeam)];\n  } else if (x835_26_phazonBeamMorphing) {\n    beamFlags = CModelFlags{1, 0, 3, zeus::CColor::lerp(zeus::skWhite, zeus::skBlack, x39c_phazonMorphT)};\n  }\n\n  const CGameCamera* cam = mgr.GetCameraManager()->GetCurrentCamera(mgr);\n  CGraphics::SetDepthRange(DEPTH_GUN, DEPTH_WORLD);\n  zeus::CTransform offsetWorldXf = zeus::CTransform::Translate(pos) * x4a8_gunWorldXf;\n  zeus::CTransform elbowOffsetXf = offsetWorldXf * x508_elbowLocalXf;\n  if (x32c_chargePhase != EChargePhase::NotCharging && (x2f8_stateFlags & 0x10) != 0x10) {\n    offsetWorldXf.origin += zeus::CVector3f(x34c_shakeX, 0.f, x350_shakeZ);\n  }\n\n  zeus::CTransform oldViewMtx = CGraphics::mViewMatrix;\n  CGraphics::SetViewPointMatrix(offsetWorldXf.inverse() * oldViewMtx);\n  CGraphics::SetModelMatrix(zeus::CTransform());\n  if (x32c_chargePhase >= EChargePhase::FxGrown && x32c_chargePhase < EChargePhase::ComboXfer) {\n    x800_auxMuzzleGenerators[size_t(x320_currentAuxBeam)]->Render();\n  }\n\n  if (x832_25_chargeEffectVisible && (x38c_muzzleEffectVisTimer > 0.f || x32c_chargePhase > EChargePhase::AnimAndSfx)) {\n    x72c_currentBeam->DrawMuzzleFx(mgr);\n  }\n\n  if (x678_morph.GetGunState() == CGunMorph::EGunState::InWipe ||\n      x678_morph.GetGunState() == CGunMorph::EGunState::OutWipe) {\n    x774_holoTransitionGen->Render();\n  }\n\n  CGraphics::SetViewPointMatrix(oldViewMtx);\n  if ((x2f8_stateFlags & 0x10) == 0x10) {\n    x744_auxWeapon->RenderMuzzleFx();\n  }\n\n  x72c_currentBeam->PreRenderGunFx(mgr, offsetWorldXf);\n  bool drawSuitArm =\n      !x740_grappleArm->IsGrappling() && mgr.GetPlayer().GetGunHolsterState() == CPlayer::EGunHolsterState::Drawn;\n  x73c_gunMotion->Draw(mgr, offsetWorldXf);\n\n  switch (x678_morph.GetGunState()) {\n  case CGunMorph::EGunState::OutWipeDone:\n    if (x0_lights.HasShadowLight())\n      x82c_shadow->EnableModelProjectedShadow(offsetWorldXf, x0_lights.GetShadowLightArrIndex(), 2.15f);\n    if (mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::XRay) {\n      x6e0_rightHandModel.Render(mgr, elbowOffsetXf * zeus::CTransform::Translate(0.f, -0.2f, 0.02f), &x0_lights,\n                                 mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::Thermal\n                                     ? kHandThermalFlag\n                                     : kHandHoloFlag);\n    }\n    DrawArm(mgr, pos, flags);\n    x72c_currentBeam->Draw(drawSuitArm, mgr, offsetWorldXf, beamFlags, &x0_lights);\n    x82c_shadow->DisableModelProjectedShadow();\n    break;\n  case CGunMorph::EGunState::InWipeDone:\n  case CGunMorph::EGunState::InWipe:\n  case CGunMorph::EGunState::OutWipe:\n    if (x678_morph.GetGunState() != CGunMorph::EGunState::InWipeDone) {\n      zeus::CTransform morphXf = elbowOffsetXf * zeus::CTransform::Translate(0.f, x678_morph.GetYLerp(), 0.f);\n      CopyScreenTex();\n      x6e0_rightHandModel.Render(mgr, elbowOffsetXf * zeus::CTransform::Translate(0.f, -0.2f, 0.02f), &x0_lights,\n                                 mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::Thermal\n                                     ? kHandThermalFlag\n                                     : kHandHoloFlag);\n      x72c_currentBeam->DrawHologram(mgr, offsetWorldXf, CModelFlags(0, 0, 3, zeus::skWhite));\n      DrawScreenTex(ConvertToScreenSpace(morphXf.origin, *cam).z());\n      if (x0_lights.HasShadowLight())\n        x82c_shadow->EnableModelProjectedShadow(offsetWorldXf, x0_lights.GetShadowLightArrIndex(), 2.15f);\n      CGraphics::SetModelMatrix(morphXf);\n      DrawClipCube(x6c8_hologramClipCube);\n      x72c_currentBeam->Draw(drawSuitArm, mgr, offsetWorldXf, beamFlags, &x0_lights);\n      x82c_shadow->DisableModelProjectedShadow();\n    } else {\n      x6e0_rightHandModel.Render(mgr, elbowOffsetXf * zeus::CTransform::Translate(0.f, -0.2f, 0.02f), &x0_lights,\n                                 mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::Thermal\n                                     ? kHandThermalFlag\n                                     : kHandHoloFlag);\n      x72c_currentBeam->DrawHologram(mgr, offsetWorldXf, CModelFlags(0, 0, 3, zeus::skWhite));\n      if (x0_lights.HasShadowLight())\n        x82c_shadow->EnableModelProjectedShadow(offsetWorldXf, x0_lights.GetShadowLightArrIndex(), 2.15f);\n      DrawArm(mgr, pos, flags);\n      x82c_shadow->DisableModelProjectedShadow();\n    }\n    break;\n  }\n\n  oldViewMtx = CGraphics::mViewMatrix;\n  CGraphics::SetViewPointMatrix(offsetWorldXf.inverse() * oldViewMtx);\n  CGraphics::SetModelMatrix(zeus::CTransform());\n  x72c_currentBeam->PostRenderGunFx(mgr, offsetWorldXf);\n  if (x832_26_comboFiring && x77c_comboXferGen)\n    x77c_comboXferGen->Render();\n  CGraphics::SetViewPointMatrix(oldViewMtx);\n\n  RenderEnergyDrainEffects(mgr);\n\n  CGraphics::SetDepthRange(DEPTH_WORLD, DEPTH_FAR);\n  CGraphics::SetProjectionState(projState);\n}\n\nvoid CPlayerGun::AddToRenderer(const zeus::CFrustum& frustum, const CStateManager& mgr) const {\n  if (x72c_currentBeam->HasSolidModelData())\n    x72c_currentBeam->GetSolidModelData().RenderParticles(frustum);\n}\n\nvoid CPlayerGun::DropBomb(EBWeapon weapon, CStateManager& mgr) {\n  if (weapon == EBWeapon::Bomb) {\n    if (x32c_chargePhase != EChargePhase::NotCharging) {\n      x32c_chargePhase = EChargePhase::ChargeDone;\n      return;\n    }\n\n    if (x308_bombCount <= 0)\n      return;\n\n    const zeus::CVector3f plPos = mgr.GetPlayer().GetTranslation();\n    const zeus::CTransform xf =\n        zeus::CTransform::Translate({plPos.x(), plPos.y(), plPos.z() + g_tweakPlayer->GetPlayerBallHalfExtent()});\n    CBomb* bomb = new CBomb(x784_bombEffects[u32(weapon)][0], x784_bombEffects[u32(weapon)][1], mgr.AllocateUniqueId(),\n                            mgr.GetPlayer().GetAreaId(), x538_playerId, x354_bombFuseTime, xf,\n                            CDamageInfo{g_tweakPlayerGun->GetBombInfo()});\n    mgr.AddObject(bomb);\n\n    if (x308_bombCount == 3)\n      x35c_bombTime = x358_bombDropDelayTime;\n\n    --x308_bombCount;\n    if (TCastToPtr<CScriptPlatform> plat = mgr.ObjectById(mgr.GetPlayer().GetRidingPlatformId()))\n      plat->AddSlave(bomb->GetUniqueId(), mgr);\n  } else if (weapon == EBWeapon::PowerBomb) {\n    mgr.GetPlayerState()->DecrPickup(CPlayerState::EItemType::PowerBombs, 1);\n    x53a_powerBomb = DropPowerBomb(mgr);\n  }\n}\n\nTUniqueId CPlayerGun::DropPowerBomb(CStateManager& mgr) {\n  const auto dInfo = mgr.GetPlayer().GetDeathTime() <= 0.f ? CDamageInfo{g_tweakPlayerGun->GetPowerBombInfo()}\n                                                           : CDamageInfo{CWeaponMode::PowerBomb(), 0.f, 0.f, 0.f};\n\n  TUniqueId uid = mgr.AllocateUniqueId();\n  zeus::CVector3f plVec = mgr.GetPlayer().GetTranslation();\n  zeus::CTransform xf =\n      zeus::CTransform::Translate({plVec.x(), plVec.y(), plVec.z() + g_tweakPlayer->GetPlayerBallHalfExtent()});\n  CPowerBomb* pBomb = new CPowerBomb(x784_bombEffects[1][0], uid, kInvalidAreaId, x538_playerId, xf, dInfo);\n  mgr.AddObject(*pBomb);\n  return uid;\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CPlayerGun.hpp",
    "content": "#pragma once\n\n#include <array>\n#include <memory>\n#include <vector>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Camera/CCameraFilter.hpp\"\n#include \"Runtime/Character/CActorLights.hpp\"\n#include \"Runtime/Character/CModelData.hpp\"\n#include \"Runtime/Graphics/CRainSplashGenerator.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/Weapon/CAuxWeapon.hpp\"\n#include \"Runtime/Weapon/CFidget.hpp\"\n#include \"Runtime/Weapon/CGrappleArm.hpp\"\n#include \"Runtime/Weapon/CGunMotion.hpp\"\n#include \"Runtime/Weapon/CIceBeam.hpp\"\n#include \"Runtime/Weapon/CPhazonBeam.hpp\"\n#include \"Runtime/Weapon/CPlasmaBeam.hpp\"\n#include \"Runtime/Weapon/CPowerBeam.hpp\"\n#include \"Runtime/Weapon/CWaveBeam.hpp\"\n#include \"Runtime/World/CPlayerCameraBob.hpp\"\n#include \"Runtime/World/CWorldShadow.hpp\"\n#include \"Runtime/World/ScriptObjectSupport.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CTransform.hpp>\n\nnamespace metaforce {\nstruct CFinalInput;\n\nclass CPlayerGun {\n  // For ImGuiEntitySupport\n  friend class CPlayer;\npublic:\n  static float skTractorBeamFactor;\n  enum class EMissileMode { Inactive, Active };\n  enum class EBWeapon { Bomb, PowerBomb };\n  enum class EPhazonBeamState { Inactive, Entering, Exiting, Active };\n  enum class EChargePhase {\n    NotCharging,\n    ChargeRequested,\n    AnimAndSfx,\n    FxGrowing,\n    FxGrown,\n    ComboXfer,\n    ComboXferDone,\n    ComboFire,\n    ComboFireDone,\n    ChargeCooldown,\n    ChargeDone\n  };\n  enum class ENextState {\n    StatusQuo,\n    EnterMissile,\n    ExitMissile,\n    MissileShotDone,\n    MissileReload,\n    ChangeWeapon,\n    SetupBeam,\n    Seven,\n    EnterPhazonBeam,\n    ExitPhazonBeam\n  };\n  enum class EIdleState { NotIdle, Wander, Idle, Three, Four };\n\nprivate:\n  class CGunMorph {\n  public:\n    enum class EGunState { InWipeDone, OutWipeDone, InWipe, OutWipe };\n    enum class EMorphEvent { None, InWipeDone, OutWipeDone };\n    enum class EDir { In, Out, Done };\n\n  private:\n    float x0_yLerp = 0.f;\n    float x4_gunTransformTime;\n    float x8_remTime = 0.f;\n    float xc_speed = 0.1f;\n    float x10_holoHoldTime;\n    float x14_remHoldTime = 2.f;\n    float x18_transitionFactor = 1.f;\n    EDir x1c_dir = EDir::Done;\n    EGunState x20_gunState = EGunState::OutWipeDone;\n    bool x24_24_morphing : 1 = false;\n    bool x24_25_weaponChanged : 1 = false;\n\n  public:\n    CGunMorph(float gunTransformTime, float holoHoldTime)\n    : x4_gunTransformTime(gunTransformTime), x10_holoHoldTime(std::fabs(holoHoldTime)) {}\n    float GetYLerp() const { return x0_yLerp; }\n    float GetTransitionFactor() const { return x18_transitionFactor; }\n    EGunState GetGunState() const { return x20_gunState; }\n    void SetWeaponChanged() { x24_25_weaponChanged = true; }\n    EMorphEvent Update(float inY, float outY, float dt);\n    void StartWipe(EDir dir);\n  };\n\n  class CMotionState {\n  public:\n    enum class EMotionState { Zero, One, LockOn, CancelLockOn };\n    enum class EFireState { NotFiring, StartFire, Firing };\n\n  private:\n    static float gGunExtendDistance;\n    bool x0_24_extendParabola = true;\n    float x4_extendParabolaDelayTimer = 0.f;\n    float x8_fireTime = 0.f;\n    float xc_curExtendDist = 0.f;\n    float x10_curRotation = 0.f;\n    float x14_rotationT = 0.f;\n    float x18_startRotation = 0.f;\n    float x1c_endRotation = 0.f;\n    EMotionState x20_state = EMotionState::Zero;\n    EFireState x24_fireState = EFireState::NotFiring;\n\n  public:\n    static void SetExtendDistance(float d) { gGunExtendDistance = d; }\n    void SetState(EMotionState state) { x20_state = state; }\n    void Update(bool firing, float dt, zeus::CTransform& xf, CStateManager& mgr);\n  };\n\n  CActorLights x0_lights;\n  CSfxHandle x2e0_chargeSfx;\n  CSfxHandle x2e4_invalidSfx;\n  CSfxHandle x2e8_phazonBeamSfx;\n  // 0x1: FireOrBomb, 0x2: MissileOrPowerBomb\n  u32 x2ec_lastFireButtonStates = 0;\n  u32 x2f0_pressedFireButtonStates = 0;\n  u32 x2f4_fireButtonStates = 0;\n  // 0x1: beam mode, 0x2: missile mode, 0x4: missile ready, 0x8: morphing, 0x10: combo fire\n  u32 x2f8_stateFlags = 0x1;\n  u32 x2fc_fidgetAnimBits = 0;\n  u32 x300_remainingMissiles = 0;\n  u32 x304_ = 0;\n  u32 x308_bombCount = 3;\n  u32 x30c_rapidFireShots = 0;\n  CPlayerState::EBeamId x310_currentBeam = CPlayerState::EBeamId::Power;\n  CPlayerState::EBeamId x314_nextBeam = CPlayerState::EBeamId::Power;\n  u32 x318_comboAmmoIdx = 0;\n  EMissileMode x31c_missileMode = EMissileMode::Inactive;\n  CPlayerState::EBeamId x320_currentAuxBeam = CPlayerState::EBeamId::Power;\n  EIdleState x324_idleState = EIdleState::Four;\n  float x328_animSfxPitch = 0.f;\n  EChargePhase x32c_chargePhase = EChargePhase::NotCharging;\n  EChargeState x330_chargeState = EChargeState::Normal;\n  u32 x334_ = 0;\n  ENextState x338_nextState = ENextState::StatusQuo;\n  EPhazonBeamState x33c_phazonBeamState = EPhazonBeamState::Inactive;\n  float x340_chargeBeamFactor = 0.f;\n  float x344_comboXferTimer = 0.f;\n  float x348_chargeCooldownTimer = 0.f;\n  float x34c_shakeX = 0.f;\n  float x350_shakeZ = 0.f;\n  float x354_bombFuseTime;\n  float x358_bombDropDelayTime;\n  float x35c_bombTime = 0.f;\n  float x360_ = 0.f;\n  float x364_gunStrikeCoolTimer = 0.f;\n  float x368_idleWanderDelayTimer = 0.f;\n  float x36c_ = 1.f;\n  float x370_gunMotionSpeedMult = 1.f;\n  float x374_ = 0.f;\n  float x378_shotSmokeStartTimer = 0.f;\n  float x37c_rapidFireShotsDecayTimer = 0.f;\n  float x380_shotSmokeTimer = 0.f;\n  float x384_gunStrikeDelayTimer = 0.f;\n  float x388_enterFreeLookDelayTimer = 0.f;\n  float x38c_muzzleEffectVisTimer = 0.f;\n  float x390_cooldown = 0.f;\n  float x394_damageTimer = 0.f;\n  float x398_damageAmt = 0.f;\n  float x39c_phazonMorphT = 0.f;\n  float x3a0_missileExitTimer = 0.f;\n  CFidget x3a4_fidget;\n  zeus::CVector3f x3dc_damageLocation;\n  zeus::CTransform x3e8_xf;\n  zeus::CTransform x418_beamLocalXf;\n  zeus::CTransform x448_elbowWorldXf;\n  zeus::CTransform x478_assistAimXf;\n  zeus::CTransform x4a8_gunWorldXf;\n  zeus::CTransform x4d8_gunLocalXf;\n  zeus::CTransform x508_elbowLocalXf;\n  TUniqueId x538_playerId;\n  TUniqueId x53a_powerBomb = kInvalidUniqueId;\n  TUniqueId x53c_lightId = kInvalidUniqueId;\n  std::vector<CToken> x540_handAnimTokens;\n  CPlayerCameraBob x550_camBob;\n  u32 x658_ = 1;\n  float x65c_ = 0.f;\n  float x660_ = 0.f;\n  float x664_ = 0.f;\n  float x668_aimVerticalSpeed;\n  float x66c_aimHorizontalSpeed;\n  std::pair<u16, CSfxHandle> x670_animSfx{0xffff, {}};\n  CGunMorph x678_morph;\n  CMotionState x6a0_motionState;\n  zeus::CAABox x6c8_hologramClipCube;\n  CModelData x6e0_rightHandModel;\n  CGunWeapon* x72c_currentBeam = nullptr;\n  CGunWeapon* x730_outgoingBeam = nullptr;\n  CGunWeapon* x734_loadingBeam = nullptr;\n  CGunWeapon* x738_nextBeam = nullptr;\n  std::unique_ptr<CGunMotion> x73c_gunMotion;\n  std::unique_ptr<CGrappleArm> x740_grappleArm;\n  std::unique_ptr<CAuxWeapon> x744_auxWeapon;\n  std::unique_ptr<CRainSplashGenerator> x748_rainSplashGenerator;\n  std::unique_ptr<CPowerBeam> x74c_powerBeam;\n  std::unique_ptr<CIceBeam> x750_iceBeam;\n  std::unique_ptr<CWaveBeam> x754_waveBeam;\n  std::unique_ptr<CPlasmaBeam> x758_plasmaBeam;\n  std::unique_ptr<CPhazonBeam> x75c_phazonBeam;\n  std::array<CGunWeapon*, 4> x760_selectableBeams{}; // Used to be reserved_vector\n  std::unique_ptr<CElementGen> x774_holoTransitionGen;\n  std::unique_ptr<CElementGen> x77c_comboXferGen;\n  rstl::reserved_vector<rstl::reserved_vector<TLockedToken<CGenDescription>, 2>, 2> x784_bombEffects;\n  rstl::reserved_vector<TLockedToken<CGenDescription>, 5> x7c0_auxMuzzleEffects;\n  rstl::reserved_vector<std::unique_ptr<CElementGen>, 5> x800_auxMuzzleGenerators;\n  std::unique_ptr<CWorldShadow> x82c_shadow;\n  s16 x830_chargeRumbleHandle = -1;\n\n  bool x832_24_coolingCharge : 1 = false;\n  bool x832_25_chargeEffectVisible : 1 = false;\n  bool x832_26_comboFiring : 1 = false;\n  bool x832_27_chargeAnimStarted : 1 = false;\n  bool x832_28_readyForShot : 1 = false;\n  bool x832_29_lockedOn : 1 = false;\n  bool x832_30_requestReturnToDefault : 1 = false;\n  bool x832_31_inRestPose : 1 = true;\n\n  bool x833_24_notFidgeting : 1 = true;\n  bool x833_25_ : 1 = false;\n  bool x833_26_ : 1 = false;\n  bool x833_27_ : 1 = false;\n  bool x833_28_phazonBeamActive : 1 = false;\n  bool x833_29_pointBlankWorldSurface : 1 = false;\n  bool x833_30_canShowAuxMuzzleEffect : 1 = true;\n  bool x833_31_inFreeLook : 1 = false;\n\n  bool x834_24_charging : 1 = false;\n  bool x834_25_gunMotionFidgeting : 1 = false;\n  bool x834_26_animPlaying : 1 = false;\n  bool x834_27_underwater : 1 = false;\n  bool x834_28_requestImmediateRecharge : 1 = false;\n  bool x834_29_frozen : 1 = false;\n  bool x834_30_inBigStrike : 1 = false;\n  bool x834_31_gunMotionInFidgetBasePosition : 1 = false;\n\n  bool x835_24_canFirePhazon : 1 = false;\n  bool x835_25_inPhazonBeam : 1 = false;\n  bool x835_26_phazonBeamMorphing : 1 = false;\n  bool x835_27_intoPhazonBeam : 1 = false;\n  bool x835_28_bombReady : 1 = false;\n  bool x835_29_powerBombReady : 1 = false;\n  bool x835_30_inPhazonPool : 1 = false;\n  bool x835_31_actorAttached : 1 = false;\n\n  void InitBeamData();\n  void InitBombData();\n  void InitMuzzleData();\n  void InitCTData();\n  void LoadHandAnimTokens();\n  void CreateGunLight(CStateManager& mgr);\n  void DeleteGunLight(CStateManager& mgr);\n  void UpdateGunLight(const zeus::CTransform& xf, CStateManager& mgr);\n  void SetGunLightActive(bool active, CStateManager& mgr);\n  void SetPhazonBeamMorph(bool intoPhazonBeam);\n  void Reset(CStateManager& mgr, bool b1);\n  void ResetBeamParams(CStateManager& mgr, const CPlayerState& playerState, bool playSelectionSfx);\n  void PlayAnim(NWeaponTypes::EGunAnimType type, bool loop);\n  void CancelCharge(CStateManager& mgr, bool withEffect);\n  bool ExitMissile();\n  void StopChargeSound(CStateManager& mgr);\n  void UnLoadFidget();\n  void ReturnArmAndGunToDefault(CStateManager& mgr, bool returnToDefault);\n  void ReturnToRestPose();\n  void ChangeWeapon(const CPlayerState& playerState, CStateManager& mgr);\n  void GetLctrWithShake(zeus::CTransform& xfOut, const CModelData& mData, std::string_view lctrName, bool shake,\n                        bool dyn) const;\n  void UpdateLeftArmTransform(const CModelData& mData, const CStateManager& mgr);\n  void ProcessGunMorph(float dt, CStateManager& mgr);\n  void SetPhazonBeamFeedback(bool active);\n  void StartPhazonBeamTransition(bool active, CStateManager& mgr, CPlayerState& playerState);\n  void ProcessPhazonGunMorph(float dt, CStateManager& mgr);\n  void EnableChargeFx(EChargeState state, CStateManager& mgr);\n  void UpdateChargeState(float dt, CStateManager& mgr);\n  void UpdateAuxWeapons(float dt, const zeus::CTransform& targetXf, CStateManager& mgr);\n  void DoUserAnimEvent(float dt, CStateManager& mgr, const CInt32POINode& node, EUserEventType type);\n  void DoUserAnimEvents(float dt, CStateManager& mgr);\n  TUniqueId GetTargetId(CStateManager& mgr) const;\n  void CancelLockOn();\n  void FireSecondary(float dt, CStateManager& mgr);\n  void ResetCharged(float dt, CStateManager& mgr);\n  void ActivateCombo(CStateManager& mgr);\n  void ProcessChargeState(u32 releasedStates, u32 pressedStates, CStateManager& mgr, float dt);\n  void ResetNormal(CStateManager& mgr);\n  void UpdateNormalShotCycle(float dt, CStateManager& mgr);\n  void ProcessNormalState(u32 releasedStates, u32 pressedStates, CStateManager& mgr, float dt);\n  void UpdateWeaponFire(float dt, const CPlayerState& playerState, CStateManager& mgr);\n  void EnterFreeLook(CStateManager& mgr);\n  void SetFidgetAnimBits(int animSet, bool beamOnly);\n  void AsyncLoadFidget(CStateManager& mgr);\n  bool IsFidgetLoaded() const;\n  void EnterFidget(CStateManager& mgr);\n  void UpdateGunIdle(bool inStrikeCooldown, float camBobT, float dt, CStateManager& mgr);\n  void RenderEnergyDrainEffects(const CStateManager& mgr) const;\n  void DrawArm(const CStateManager& mgr, const zeus::CVector3f& pos, const CModelFlags& flags) const;\n  zeus::CVector3f ConvertToScreenSpace(const zeus::CVector3f& pos, const CGameCamera& cam) const;\n  static void CopyScreenTex();\n  void DrawScreenTex(float z);\n  void DrawClipCube(const zeus::CAABox& aabb);\n\npublic:\n  explicit CPlayerGun(TUniqueId playerId);\n  void TakeDamage(bool bigStrike, bool notFromMetroid, CStateManager& mgr);\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&);\n  void AsyncLoadSuit(CStateManager& mgr);\n  void TouchModel(const CStateManager& stateMgr);\n  EMissileMode GetMissleMode() const { return x31c_missileMode; }\n  bool IsFidgeting() const { return x833_24_notFidgeting; }\n  bool IsCharging() const { return x834_24_charging; }\n  float GetChargeBeamFactor() const { return x340_chargeBeamFactor; }\n  bool IsBombReady() const { return x835_28_bombReady; }\n  u32 GetBombCount() const { return x308_bombCount; }\n  bool IsPowerBombReady() const { return x835_29_powerBombReady; }\n  CPlayerState::EBeamId GetCurrentBeam() const { return x310_currentBeam; }\n  CPlayerState::EBeamId GetNextBeam() const { return x314_nextBeam; }\n  const CGunMorph& GetGunMorph() const { return x678_morph; }\n  float GetHoloTransitionFactor() const { return x678_morph.GetTransitionFactor(); }\n  void SetTransform(const zeus::CTransform& xf) { x3e8_xf = xf; }\n  void SetAssistAimTransform(const zeus::CTransform& xf) { x478_assistAimXf = xf; }\n  CGrappleArm& GetGrappleArm() { return *x740_grappleArm; }\n  const CGrappleArm& GetGrappleArm() const { return *x740_grappleArm; }\n  void DamageRumble(const zeus::CVector3f& location, float damage, const CStateManager& mgr);\n  void ResetCharge(CStateManager& mgr, bool resetBeam);\n  void HandleBeamChange(const CFinalInput& input, CStateManager& mgr);\n  void HandlePhazonBeamChange(CStateManager& mgr);\n  void HandleWeaponChange(const CFinalInput& input, CStateManager& mgr);\n  void ProcessInput(const CFinalInput& input, CStateManager& mgr);\n  void ResetIdle(CStateManager& mgr);\n  void CancelFiring(CStateManager& mgr);\n  float GetBeamVelocity() const;\n  void StopContinuousBeam(CStateManager& mgr, bool b1);\n  void Update(float grappleSwingT, float cameraBobT, float dt, CStateManager& mgr);\n  void PreRender(const CStateManager& mgr, const zeus::CFrustum& frustum, const zeus::CVector3f& camPos);\n  void Render(const CStateManager& mgr, const zeus::CVector3f& pos, const CModelFlags& flags);\n  void AddToRenderer(const zeus::CFrustum& frustum, const CStateManager& mgr) const;\n  u32 GetLastFireButtonStates() const { return x2ec_lastFireButtonStates; }\n  void DropBomb(EBWeapon weapon, CStateManager& mgr);\n  TUniqueId DropPowerBomb(CStateManager& mgr);\n  void SetActorAttached(bool b) { x835_31_actorAttached = b; }\n  CAuxWeapon& GetAuxWeapon() const { return *x744_auxWeapon; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CPowerBeam.cpp",
    "content": "#include \"Runtime/Weapon/CPowerBeam.hpp\"\n\n#include <array>\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n\nnamespace metaforce {\n\nCPowerBeam::CPowerBeam(CAssetId characterId, EWeaponType type, TUniqueId playerId, EMaterialTypes playerMaterial,\n                       const zeus::CVector3f& scale)\n: CGunWeapon(characterId, type, playerId, playerMaterial, scale) {\n  x21c_shotSmoke = g_SimplePool->GetObj(\"ShotSmoke\");\n  x228_power2nd1 = g_SimplePool->GetObj(\"Power2nd_1\");\n}\n\nvoid CPowerBeam::PreRenderGunFx(const CStateManager& mgr, const zeus::CTransform& xf) {\n  zeus::CTransform backupView = CGraphics::mViewMatrix;\n  CGraphics::SetViewPointMatrix(xf.inverse() * backupView);\n  g_Renderer->SetModelMatrix(zeus::CTransform());\n  \n  if (x234_shotSmokeGen && x240_smokeState != ESmokeState::Inactive)\n    x234_shotSmokeGen->Render();\n  CGraphics::SetViewPointMatrix(backupView);\n}\n\nvoid CPowerBeam::PostRenderGunFx(const CStateManager& mgr, const zeus::CTransform& xf) {\n  if (x1cc_enabledSecondaryEffect != ESecondaryFxType::None && x238_power2ndGen)\n    x238_power2ndGen->Render();\n  CGunWeapon::PostRenderGunFx(mgr, xf);\n}\n\nvoid CPowerBeam::UpdateGunFx(bool shotSmoke, float dt, const CStateManager& mgr, const zeus::CTransform& xf) {\n  switch (x240_smokeState) {\n  case ESmokeState::Inactive:\n    if (shotSmoke) {\n      if (x234_shotSmokeGen)\n        x234_shotSmokeGen->SetParticleEmission(true);\n      x23c_smokeTimer = 2.f;\n      x240_smokeState = ESmokeState::Active;\n    }\n    break;\n  case ESmokeState::Active:\n    if (x23c_smokeTimer > 0.f) {\n      x23c_smokeTimer -= dt;\n    } else {\n      if (x234_shotSmokeGen)\n        x234_shotSmokeGen->SetParticleEmission(false);\n      x240_smokeState = ESmokeState::Done;\n    }\n    [[fallthrough]];\n  case ESmokeState::Done:\n    if (x234_shotSmokeGen) {\n      zeus::CTransform locator = x10_solidModelData->GetScaledLocatorTransform(\"LBEAM\");\n      x234_shotSmokeGen->SetGlobalTranslation(locator.origin);\n      x234_shotSmokeGen->Update(dt);\n      if (x240_smokeState == ESmokeState::Done && x234_shotSmokeGen->GetSystemCount() == 0)\n        x240_smokeState = ESmokeState::Inactive;\n    } else {\n      x240_smokeState = ESmokeState::Inactive;\n    }\n    break;\n  }\n\n  if (x1cc_enabledSecondaryEffect != ESecondaryFxType::None && x238_power2ndGen) {\n    x238_power2ndGen->SetGlobalOrientAndTrans(xf);\n    x238_power2ndGen->Update(dt);\n  }\n\n  CGunWeapon::UpdateGunFx(shotSmoke, dt, mgr, xf);\n}\n\nvoid CPowerBeam::Fire(bool underwater, float dt, EChargeState chargeState, const zeus::CTransform& xf,\n                      CStateManager& mgr, TUniqueId homingTarget, float chargeFactor1, float chargeFactor2) {\n  static constexpr std::array<u16, 2> skSoundId{\n      SFXwpn_fire_power_normal,\n      SFXwpn_fire_power_charged,\n  };\n\n  CGunWeapon::Fire(underwater, dt, chargeState, xf, mgr, homingTarget, chargeFactor1, chargeFactor2);\n  NWeaponTypes::play_sfx(skSoundId[size_t(chargeState)], underwater, false, 0.165f);\n}\n\nvoid CPowerBeam::EnableSecondaryFx(ESecondaryFxType type) {\n  switch (type) {\n  case ESecondaryFxType::None:\n  case ESecondaryFxType::ToCombo:\n  case ESecondaryFxType::CancelCharge:\n    if (x1cc_enabledSecondaryEffect != ESecondaryFxType::None && x238_power2ndGen)\n      x238_power2ndGen->SetParticleEmission(false);\n    x1cc_enabledSecondaryEffect = ESecondaryFxType::None;\n    break;\n  case ESecondaryFxType::Charge:\n    x238_power2ndGen = std::make_unique<CElementGen>(x228_power2nd1);\n    x238_power2ndGen->SetGlobalScale(x4_scale);\n    x1cc_enabledSecondaryEffect = type;\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CPowerBeam::Update(float dt, CStateManager& mgr) {\n  CGunWeapon::Update(dt, mgr);\n  if (IsLoaded())\n    return;\n  if (CGunWeapon::IsLoaded() && !x244_25_loaded) {\n    x244_25_loaded = x21c_shotSmoke.IsLoaded() && x228_power2nd1.IsLoaded();\n    if (x244_25_loaded) {\n      x234_shotSmokeGen = std::make_unique<CElementGen>(x21c_shotSmoke);\n      x234_shotSmokeGen->SetParticleEmission(false);\n    }\n  }\n}\n\nvoid CPowerBeam::Load(CStateManager& mgr, bool subtypeBasePose) {\n  CGunWeapon::Load(mgr, subtypeBasePose);\n  x21c_shotSmoke.Lock();\n  x228_power2nd1.Lock();\n}\n\nvoid CPowerBeam::ReInitVariables() {\n  x234_shotSmokeGen.reset();\n  x238_power2ndGen.reset();\n  x23c_smokeTimer = 0.f;\n  x240_smokeState = ESmokeState::Inactive;\n  x244_24 = false;\n  x244_25_loaded = false;\n  x1cc_enabledSecondaryEffect = ESecondaryFxType::None;\n}\n\nvoid CPowerBeam::Unload(CStateManager& mgr) {\n  CGunWeapon::Unload(mgr);\n  x228_power2nd1.Unlock();\n  x21c_shotSmoke.Unlock();\n  ReInitVariables();\n}\n\nbool CPowerBeam::IsLoaded() const { return CGunWeapon::IsLoaded() && x244_25_loaded; }\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CPowerBeam.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include \"Runtime/Weapon/CGunWeapon.hpp\"\n\nnamespace metaforce {\n\nclass CPowerBeam final : public CGunWeapon {\n  enum class ESmokeState { Inactive, Active, Done };\n  TCachedToken<CGenDescription> x21c_shotSmoke;\n  TCachedToken<CGenDescription> x228_power2nd1;\n  std::unique_ptr<CElementGen> x234_shotSmokeGen;\n  std::unique_ptr<CElementGen> x238_power2ndGen;\n  float x23c_smokeTimer = 0.f;\n  ESmokeState x240_smokeState = ESmokeState::Inactive;\n  bool x244_24 : 1 = false;\n  bool x244_25_loaded : 1 = false;\n  void ReInitVariables();\n\npublic:\n  CPowerBeam(CAssetId characterId, EWeaponType type, TUniqueId playerId, EMaterialTypes playerMaterial,\n             const zeus::CVector3f& scale);\n\n  void PreRenderGunFx(const CStateManager& mgr, const zeus::CTransform& xf) override;\n  void PostRenderGunFx(const CStateManager& mgr, const zeus::CTransform& xf) override;\n  void UpdateGunFx(bool shotSmoke, float dt, const CStateManager& mgr, const zeus::CTransform& xf) override;\n  void Fire(bool underwater, float dt, EChargeState chargeState, const zeus::CTransform& xf, CStateManager& mgr,\n            TUniqueId homingTarget, float chargeFactor1, float chargeFactor2) override;\n  void EnableSecondaryFx(ESecondaryFxType type) override;\n  void Update(float dt, CStateManager& mgr) override;\n  void Load(CStateManager& mgr, bool subtypeBasePose) override;\n  void Unload(CStateManager& mgr) override;\n  bool IsLoaded() const override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CPowerBomb.cpp",
    "content": "#include \"Runtime/Weapon/CPowerBomb.hpp\"\n\n#include \"Runtime/CPlayerState.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/World/CDamageInfo.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n\n#include \"Audio/SFX/Weapons.h\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\n#include <zeus/CColor.hpp>\n\nnamespace metaforce {\nconstexpr zeus::CColor kFadeColor(COLOR(0xffffff7));\n\nCPowerBomb::CPowerBomb(const TToken<CGenDescription>& particle, TUniqueId uid, TAreaId aid, TUniqueId playerId,\n                       const zeus::CTransform& xf, const CDamageInfo& dInfo)\n: CWeapon(\n      uid, aid, true, playerId, EWeaponType::PowerBomb, \"PowerBomb\", xf,\n      CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid, EMaterialTypes::Immovable, EMaterialTypes::Trigger},\n                                          {EMaterialTypes::Projectile, EMaterialTypes::PowerBomb}),\n      {EMaterialTypes::Projectile, EMaterialTypes::PowerBomb}, dInfo, EProjectileAttrib::PowerBombs,\n      CModelData::CModelDataNull())\n, x164_radiusIncrement(dInfo.GetRadius() / 2.5f)\n, x168_particle(std::make_unique<CElementGen>(particle))\n, x16c_radius(dInfo.GetRadius()) {\n  x168_particle->SetGlobalTranslation(xf.origin); //.GetTranslation());\n}\n\nvoid CPowerBomb::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CPowerBomb::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  if (msg == EScriptObjectMessage::Registered) {\n    mgr.AddWeaponId(xec_ownerId, xf0_weaponType);\n    x110_origDamageInfo.SetRadius(0.f);\n\n    if (mgr.GetPlayerState()->IsPlayerAlive()) {\n      CSfxManager::AddEmitter(SFXsfx0710, GetTranslation(), {}, true, false, 0x7f, -1);\n      mgr.InformListeners(GetTranslation(), EListenNoiseType::BombExplode);\n    } else {\n      auto handle = CSfxManager::SfxStart(SFXsfx073F, 0x7f, 0x40, false, 0xFF, false, -1);\n      mgr.Player()->ApplySubmergedPitchBend(handle);\n    }\n  } else if (msg == EScriptObjectMessage::Deleted) {\n    if (x15c_curTime <= 7.0f)\n      mgr.GetCameraFilterPass(6).DisableFilter(0.f);\n\n    mgr.RemoveWeaponId(xec_ownerId, xf0_weaponType);\n  }\n  CActor::AcceptScriptMsg(msg, uid, mgr);\n}\n\nvoid CPowerBomb::Think(float dt, CStateManager& mgr) {\n  CWeapon::Think(dt, mgr);\n  if (x158_24_canStartFilter) {\n    if (x15c_curTime > 1.f && !x158_25_filterEnabled) {\n      mgr.GetCameraFilterPass(6).SetFilter(EFilterType::Add, EFilterShape::Fullscreen, 1.5f, kFadeColor, {});\n      x158_25_filterEnabled = true;\n    }\n\n    if (x15c_curTime > 2.5f)\n      x158_24_canStartFilter = false;\n  } else {\n    if (x15c_curTime > 3.75 && x158_25_filterEnabled) {\n      mgr.GetCameraFilterPass(6).DisableFilter(.5f);\n      x158_25_filterEnabled = false;\n    }\n\n    if (x15c_curTime > 7.f) {\n      if (x168_particle->IsSystemDeletable())\n        mgr.FreeScriptObject(GetUniqueId());\n    }\n\n    if (x15c_curTime > 30.f) {\n      mgr.FreeScriptObject(GetUniqueId());\n      return;\n    }\n  }\n\n  if (x15c_curTime > 1.f && x15c_curTime < 4.f) {\n    x110_origDamageInfo.SetRadius(x160_curRadius);\n    ApplyDynamicDamage(GetTranslation(), mgr);\n    x160_curRadius += dt * x164_radiusIncrement;\n  }\n\n  x168_particle->Update(dt);\n  x15c_curTime += dt;\n}\n\nvoid CPowerBomb::AddToRenderer(const zeus::CFrustum&, CStateManager&) { g_Renderer->AddParticleGen(*x168_particle); }\n\nvoid CPowerBomb::ApplyDynamicDamage(const zeus::CVector3f& pos, metaforce::CStateManager& mgr) {\n  mgr.ApplyDamageToWorld(xec_ownerId, *this, pos, x12c_curDamageInfo, xf8_filter);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CPowerBomb.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <optional>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/Weapon/CWeapon.hpp\"\n\n#include <zeus/CAABox.hpp>\n\nnamespace metaforce {\nclass CElementGen;\n\nclass CPowerBomb : public CWeapon {\n  bool x158_24_canStartFilter : 1 = true;\n  bool x158_25_filterEnabled : 1 = false;\n  float x15c_curTime = 0.f;\n  float x160_curRadius = 0.f;\n  float x164_radiusIncrement;\n  std::unique_ptr<CElementGen> x168_particle;\n  float x16c_radius;\n\npublic:\n  DEFINE_ENTITY\n  CPowerBomb(const TToken<CGenDescription>& particle, TUniqueId uid, TAreaId aid, TUniqueId playerId,\n             const zeus::CTransform& xf, const CDamageInfo& dInfo);\n\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void Think(float, CStateManager&) override;\n  void AddToRenderer(const zeus::CFrustum&, CStateManager&) override;\n  void Render(CStateManager&) override {}\n  std::optional<zeus::CAABox> GetTouchBounds() const override { return std::nullopt; }\n  void Touch(CActor&, CStateManager&) override { /*x158_24_canStartFilter; */\n  }\n  float GetCurTime() const { return x15c_curTime; }\n  void ApplyDynamicDamage(const zeus::CVector3f&, CStateManager&);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CProjectileInfo.cpp",
    "content": "#include \"Runtime/Weapon/CProjectileInfo.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Character/CSteeringBehaviors.hpp\"\n#include \"Runtime/Weapon/CProjectileWeapon.hpp\"\n#include \"Runtime/World/CDamageInfo.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n\nnamespace metaforce {\n\nCProjectileInfo::CProjectileInfo(metaforce::CInputStream& in)\n: x0_weaponDescription(g_SimplePool->GetObj({SBIG('WPSC'), CAssetId(in)})), xc_damageInfo(in) {}\n\nCProjectileInfo::CProjectileInfo(CAssetId proj, const CDamageInfo& dInfo)\n: x0_weaponDescription(g_SimplePool->GetObj({SBIG('WPSC'), proj})), xc_damageInfo(dInfo) {}\n\nzeus::CVector3f CProjectileInfo::PredictInterceptPos(const zeus::CVector3f& gunPos, const zeus::CVector3f& aimPos,\n                                                     const CPlayer& player, bool gravity, float speed, float dt) {\n  zeus::CVector3f ret;\n  const zeus::CVector3f playerVel = player.GetDampedClampedVelocityWR();\n  const zeus::CVector3f gravVec(0.f, 0.f, player.GetGravity());\n  bool result = false;\n\n  switch (player.GetOrbitState()) {\n  case CPlayer::EPlayerOrbitState::OrbitObject:\n  case CPlayer::EPlayerOrbitState::OrbitPoint:\n  case CPlayer::EPlayerOrbitState::OrbitCarcass:\n  case CPlayer::EPlayerOrbitState::ForcedOrbitObject:\n  case CPlayer::EPlayerOrbitState::Grapple: {\n    if (gravity && player.GetPlayerMovementState() == CPlayer::EPlayerMovementState::ApplyJump) {\n      result = CSteeringBehaviors::ProjectOrbitalIntersection(gunPos, speed, dt, aimPos, playerVel, gravVec,\n                                                              player.GetOrbitPoint(), ret);\n      break;\n    }\n    zeus::CVector3f vel;\n    if (playerVel.canBeNormalized()) {\n      vel = playerVel.normalized() * player.GetAverageSpeed();\n    } else {\n      vel = playerVel;\n    }\n    result =\n        CSteeringBehaviors::ProjectOrbitalIntersection(gunPos, speed, dt, aimPos, vel, player.GetOrbitPoint(), ret);\n    break;\n  }\n  case CPlayer::EPlayerOrbitState::NoOrbit:\n    if (gravity && player.GetPlayerMovementState() == CPlayer::EPlayerMovementState::ApplyJump) {\n      result = CSteeringBehaviors::ProjectLinearIntersection(gunPos, speed, aimPos, playerVel, gravVec, ret);\n    } else {\n      result = CSteeringBehaviors::ProjectLinearIntersection(gunPos, speed, aimPos, playerVel, ret);\n    }\n    break;\n  }\n\n  if (!result) {\n    ret = playerVel * 1.5f + aimPos;\n  }\n\n  return ret;\n}\n\nfloat CProjectileInfo::GetProjectileSpeed() const {\n  auto wpsc = x0_weaponDescription;\n  if (wpsc->x4_IVEC) {\n    zeus::CVector3f vec;\n    wpsc->x4_IVEC->GetValue(0, vec);\n    return vec.magnitude() / CProjectileWeapon::GetTickPeriod();\n  }\n  return 45000.0f;\n}\n\nzeus::CVector3f CProjectileInfo::PredictInterceptPos(const zeus::CVector3f& gunPos, const zeus::CVector3f& aimPos,\n                                                     const CPlayer& player, bool gravity, float dt) const {\n  return PredictInterceptPos(gunPos, aimPos, player, gravity, GetProjectileSpeed(), dt);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CProjectileInfo.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Particle/CWeaponDescription.hpp\"\n#include \"Runtime/World/CDamageInfo.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CPlayer;\nclass CProjectileInfo {\n  TCachedToken<CWeaponDescription> x0_weaponDescription;\n  CDamageInfo xc_damageInfo;\n\npublic:\n  explicit CProjectileInfo(CInputStream&);\n  CProjectileInfo(CAssetId, const CDamageInfo&);\n\n  float GetProjectileSpeed() const;\n  static zeus::CVector3f PredictInterceptPos(const zeus::CVector3f& gunPos, const zeus::CVector3f& aimPos,\n                                             const CPlayer& player, bool gravity, float speed, float dt);\n  zeus::CVector3f PredictInterceptPos(const zeus::CVector3f& gunPos, const zeus::CVector3f& aimPos,\n                                      const CPlayer& player, bool gravity, float dt) const;\n\n  const CDamageInfo& GetDamage() const { return xc_damageInfo; }\n  void SetDamage(const CDamageInfo& damageInfo) { xc_damageInfo = damageInfo; }\n  TCachedToken<CWeaponDescription>& Token() { return x0_weaponDescription; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CProjectileWeapon.cpp",
    "content": "#include \"Runtime/Weapon/CProjectileWeapon.hpp\"\n\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Graphics/CModel.hpp\"\n#include \"Runtime/Particle/CParticleGlobals.hpp\"\n\nnamespace metaforce {\n\nu16 CProjectileWeapon::g_GlobalSeed = 99;\n\nCProjectileWeapon::CProjectileWeapon(const TToken<CWeaponDescription>& wDesc, const zeus::CVector3f& worldOffset,\n                                     const zeus::CTransform& localToWorld, const zeus::CVector3f& scale, s32 flags)\n: x4_weaponDesc(wDesc)\n, x10_random(g_GlobalSeed)\n, x14_localToWorldXf(localToWorld)\n, x74_worldOffset(worldOffset)\n, xe4_flags(flags) {\n  CGlobalRandom gr(x10_random);\n  x124_31_VMD2 = x4_weaponDesc->x10_VMD2;\n  x124_25_APSO = x4_weaponDesc->x28_APSO;\n  if (x4_weaponDesc->x34_APSM) {\n    xfc_APSMGen = std::make_unique<CElementGen>(\n        *x4_weaponDesc->x34_APSM, CElementGen::EModelOrientationType::Normal,\n        (xe4_flags & 0x1) == 0x1 ? CElementGen::EOptionalSystemFlags::Two : CElementGen::EOptionalSystemFlags::One);\n    xfc_APSMGen->SetGlobalScale(scale);\n  }\n  if (x4_weaponDesc->x44_APS2) {\n    x100_APS2Gen = std::make_unique<CElementGen>(\n        *x4_weaponDesc->x44_APS2, CElementGen::EModelOrientationType::Normal,\n        (xe4_flags & 0x1) == 0x1 ? CElementGen::EOptionalSystemFlags::Two : CElementGen::EOptionalSystemFlags::One);\n    x100_APS2Gen->SetGlobalScale(scale);\n  }\n  if (x4_weaponDesc->x54_ASW1) {\n    x118_swoosh1 = std::make_unique<CParticleSwoosh>(*x4_weaponDesc->x54_ASW1, 0);\n    x118_swoosh1->SetGlobalScale(scale);\n  }\n  if (x4_weaponDesc->x64_ASW2) {\n    x11c_swoosh2 = std::make_unique<CParticleSwoosh>(*x4_weaponDesc->x64_ASW2, 0);\n    x11c_swoosh2->SetGlobalScale(scale);\n  }\n  if (x4_weaponDesc->x74_ASW3) {\n    x120_swoosh3 = std::make_unique<CParticleSwoosh>(*x4_weaponDesc->x74_ASW3, 0);\n    x120_swoosh3->SetGlobalScale(scale);\n  }\n  if (CIntElement* pslt = x4_weaponDesc->x14_PSLT.get())\n    pslt->GetValue(0, xe8_lifetime);\n  else\n    xe8_lifetime = 0x7FFFFF;\n  if (CVectorElement* ivec = x4_weaponDesc->x4_IVEC.get())\n    ivec->GetValue(0, xb0_velocity);\n  if (CVectorElement* iorn = x4_weaponDesc->x0_IORN.get()) {\n    zeus::CTransform xf;\n    zeus::CVector3f orn;\n    iorn->GetValue(0, orn);\n    xf.rotateLocalX(zeus::degToRad(orn.x()));\n    xf.rotateLocalY(zeus::degToRad(orn.y()));\n    xf.rotateLocalZ(zeus::degToRad(orn.z()));\n    SetRelativeOrientation(xf);\n  } else {\n    SetRelativeOrientation(zeus::CTransform());\n  }\n  x108_model = x4_weaponDesc->x84_OHEF.x0_res;\n  x124_26_AP11 = x4_weaponDesc->x2a_AP11;\n  x124_27_AP21 = x4_weaponDesc->x2b_AP21;\n  x124_28_AS11 = x4_weaponDesc->x2c_AS11;\n  x124_29_AS12 = x4_weaponDesc->x2d_AS12;\n  x124_30_AS13 = x4_weaponDesc->x2e_AS13;\n  UpdateChildParticleSystems(1.f / 60.f);\n}\n\nzeus::CTransform CProjectileWeapon::GetTransform() const { return x14_localToWorldXf * x44_localXf; }\n\nzeus::CVector3f CProjectileWeapon::GetTranslation() const {\n  return x14_localToWorldXf * (x44_localXf * x8c_projOffset + x80_localOffset) + x74_worldOffset;\n}\n\nstd::optional<zeus::CAABox> CProjectileWeapon::GetBounds() const {\n  zeus::CAABox aabb;\n  bool ret = false;\n\n  if (xfc_APSMGen) {\n    if (auto b = xfc_APSMGen->GetBounds()) {\n      aabb.accumulateBounds(*b);\n      ret = true;\n    }\n  }\n\n  if (x100_APS2Gen) {\n    if (auto b = x100_APS2Gen->GetBounds()) {\n      aabb.accumulateBounds(*b);\n      ret = true;\n    }\n  }\n\n  if (x118_swoosh1) {\n    if (auto b = x118_swoosh1->GetBounds()) {\n      aabb.accumulateBounds(*b);\n      ret = true;\n    }\n  }\n\n  if (x11c_swoosh2) {\n    if (auto b = x11c_swoosh2->GetBounds()) {\n      aabb.accumulateBounds(*b);\n      ret = true;\n    }\n  }\n\n  if (x120_swoosh3) {\n    if (auto b = x120_swoosh3->GetBounds()) {\n      aabb.accumulateBounds(*b);\n      ret = true;\n    }\n  }\n\n  if (ret) {\n    return {aabb};\n  }\n  return std::nullopt;\n}\n\nfloat CProjectileWeapon::GetAudibleFallOff() const {\n  if (!x4_weaponDesc->x94_COLR)\n    return 0.f;\n  return x4_weaponDesc->x94_COLR->GetAudibleFallOff();\n}\n\nfloat CProjectileWeapon::GetAudibleRange() const {\n  if (!x4_weaponDesc->x94_COLR)\n    return 0.f;\n  return x4_weaponDesc->x94_COLR->GetAudibleRange();\n}\n\nstd::optional<TLockedToken<CDecalDescription>>\nCProjectileWeapon::GetDecalForCollision(EWeaponCollisionResponseTypes type) const {\n  if (!x4_weaponDesc->x94_COLR) {\n    return std::nullopt;\n  }\n  return x4_weaponDesc->x94_COLR->GetDecalDescription(type);\n}\n\ns32 CProjectileWeapon::GetSoundIdForCollision(EWeaponCollisionResponseTypes type) const {\n  if (!x4_weaponDesc->x94_COLR)\n    return -1;\n  return x4_weaponDesc->x94_COLR->GetSoundEffectId(type);\n}\n\nstd::optional<TLockedToken<CGenDescription>> CProjectileWeapon::CollisionOccured(EWeaponCollisionResponseTypes type,\n                                                                                 bool deflected, bool useTarget,\n                                                                                 const zeus::CVector3f& pos,\n                                                                                 const zeus::CVector3f& normal,\n                                                                                 const zeus::CVector3f& target) {\n  x80_localOffset = x14_localToWorldXf.transposeRotate(pos - x74_worldOffset) - x8c_projOffset;\n  zeus::CVector3f posToTarget = target - GetTranslation();\n  if (deflected) {\n    if (useTarget && posToTarget.canBeNormalized()) {\n      SetWorldSpaceOrientation(zeus::lookAt(zeus::skZero3f, posToTarget.normalized()));\n    } else {\n      zeus::CTransform xf = GetTransform();\n      SetWorldSpaceOrientation(\n          zeus::lookAt(zeus::skZero3f, xf.basis[1] - normal * (normal.dot(xf.basis[1]) * 2.f), normal));\n    }\n    return std::nullopt;\n  } else {\n    x124_24_active = false;\n    if (xfc_APSMGen) {\n      xfc_APSMGen->SetParticleEmission(false);\n    }\n    if (x100_APS2Gen) {\n      x100_APS2Gen->SetParticleEmission(false);\n    }\n    if (x118_swoosh1) {\n      x118_swoosh1->SetParticleEmission(false);\n    }\n    if (x11c_swoosh2) {\n      x11c_swoosh2->SetParticleEmission(false);\n    }\n    if (x120_swoosh3) {\n      x120_swoosh3->SetParticleEmission(false);\n    }\n    if (!x4_weaponDesc->x94_COLR) {\n      return std::nullopt;\n    }\n    return x4_weaponDesc->x94_COLR->GetParticleDescription(type);\n  }\n}\n\nvoid CProjectileWeapon::RenderParticles() const {\n  if (xfc_APSMGen)\n    xfc_APSMGen->Render();\n  if (x100_APS2Gen)\n    x100_APS2Gen->Render();\n  if (x118_swoosh1)\n    x118_swoosh1->Render();\n  if (x11c_swoosh2)\n    x11c_swoosh2->Render();\n  if (x120_swoosh3)\n    x120_swoosh3->Render();\n  if (x104_)\n    x104_->Render();\n}\n\nvoid CProjectileWeapon::AddToRenderer() {\n  if (xfc_APSMGen)\n    g_Renderer->AddParticleGen(*xfc_APSMGen);\n  if (x100_APS2Gen)\n    g_Renderer->AddParticleGen(*x100_APS2Gen);\n  if (x118_swoosh1)\n    g_Renderer->AddParticleGen(*x118_swoosh1);\n  if (x11c_swoosh2)\n    g_Renderer->AddParticleGen(*x11c_swoosh2);\n  if (x120_swoosh3)\n    g_Renderer->AddParticleGen(*x120_swoosh3);\n  if (x104_)\n    g_Renderer->AddParticleGen(*x104_);\n}\n\nvoid CProjectileWeapon::Render() {\n  if (xf4_curFrame > xe8_lifetime || !x124_24_active || !x108_model)\n    return;\n\n  CGraphics::SetModelMatrix(\n      zeus::CTransform::Translate(x74_worldOffset) * x14_localToWorldXf *\n      zeus::CTransform::Translate(x44_localXf * x8c_projOffset + x80_localOffset + xa4_localOffset2) *\n      zeus::CTransform::Scale(x98_scale) * x44_localXf);\n\n  // TODO\n//  std::vector<CLight> useLights;\n//  useLights.push_back(CLight::BuildLocalAmbient({}, xc8_ambientLightColor));\n//  (**x108_model).GetInstance().ActivateLights(useLights);\n  constexpr CModelFlags flags(0, 0, 3, zeus::skWhite);\n  (*x108_model)->Draw(flags);\n}\n\nbool CProjectileWeapon::IsSystemDeletable() const {\n  if (xfc_APSMGen && !xfc_APSMGen->IsSystemDeletable())\n    return false;\n  if (x100_APS2Gen && !x100_APS2Gen->IsSystemDeletable())\n    return false;\n  if (x118_swoosh1 && !x118_swoosh1->IsSystemDeletable())\n    return false;\n  if (x11c_swoosh2 && !x11c_swoosh2->IsSystemDeletable())\n    return false;\n  if (x120_swoosh3 && !x120_swoosh3->IsSystemDeletable())\n    return false;\n  if (x104_ && !x104_->IsSystemDeletable())\n    return false;\n  if (x124_24_active)\n    return xf4_curFrame >= xe8_lifetime;\n  return true;\n}\n\nvoid CProjectileWeapon::UpdateChildParticleSystems(float dt) {\n  double useDt;\n  if (zeus::close_enough(dt, 1.f / 60.f))\n    useDt = 1.0 / 60.0;\n  else\n    useDt = dt;\n\n  if (xfc_APSMGen) {\n    if (xf8_lastParticleFrame != xf4_curFrame) {\n      if (xf4_curFrame > xe8_lifetime) {\n        xfc_APSMGen->SetParticleEmission(false);\n        xfc_APSMGen->EndLifetime();\n      } else {\n        if (x124_26_AP11)\n          xfc_APSMGen->SetGlobalTranslation(GetTranslation());\n        else\n          xfc_APSMGen->SetTranslation(GetTranslation());\n        if (x124_25_APSO)\n          xfc_APSMGen->SetOrientation(GetTransform());\n      }\n    }\n    xfc_APSMGen->Update(useDt);\n    if (xfc_APSMGen->IsSystemDeletable())\n      xfc_APSMGen.reset();\n  }\n\n  if (x100_APS2Gen) {\n    if (xf8_lastParticleFrame != xf4_curFrame) {\n      if (xf4_curFrame > xe8_lifetime) {\n        x100_APS2Gen->SetParticleEmission(false);\n        x100_APS2Gen->EndLifetime();\n      } else {\n        if (x124_27_AP21)\n          x100_APS2Gen->SetGlobalTranslation(GetTranslation());\n        else\n          x100_APS2Gen->SetTranslation(GetTranslation());\n        if (x124_25_APSO)\n          x100_APS2Gen->SetOrientation(GetTransform());\n      }\n    }\n    x100_APS2Gen->Update(useDt);\n    if (x100_APS2Gen->IsSystemDeletable())\n      x100_APS2Gen.reset();\n  }\n\n  if (x118_swoosh1) {\n    if (xf8_lastParticleFrame != xf4_curFrame) {\n      if (xf4_curFrame > xe8_lifetime) {\n        x118_swoosh1->SetParticleEmission(false);\n      } else {\n        if (x124_28_AS11)\n          x118_swoosh1->SetGlobalTranslation(GetTranslation());\n        else\n          x118_swoosh1->SetTranslation(GetTranslation());\n        x118_swoosh1->SetOrientation(GetTransform());\n      }\n    }\n    x118_swoosh1->DoWarmupUpdate();\n    if (x118_swoosh1->IsSystemDeletable())\n      x118_swoosh1.reset();\n  }\n\n  if (x11c_swoosh2) {\n    if (xf8_lastParticleFrame != xf4_curFrame) {\n      if (xf4_curFrame > xe8_lifetime) {\n        x11c_swoosh2->SetParticleEmission(false);\n      } else {\n        if (x124_29_AS12)\n          x11c_swoosh2->SetGlobalTranslation(GetTranslation());\n        else\n          x11c_swoosh2->SetTranslation(GetTranslation());\n        x11c_swoosh2->SetOrientation(GetTransform());\n      }\n    }\n    x11c_swoosh2->DoWarmupUpdate();\n    if (x11c_swoosh2->IsSystemDeletable())\n      x11c_swoosh2.reset();\n  }\n\n  if (x120_swoosh3) {\n    if (xf8_lastParticleFrame != xf4_curFrame) {\n      if (xf4_curFrame > xe8_lifetime) {\n        x120_swoosh3->SetParticleEmission(false);\n      } else {\n        if (x124_30_AS13)\n          x120_swoosh3->SetGlobalTranslation(GetTranslation());\n        else\n          x120_swoosh3->SetTranslation(GetTranslation());\n        x120_swoosh3->SetOrientation(GetTransform());\n      }\n    }\n    x120_swoosh3->DoWarmupUpdate();\n    if (x120_swoosh3->IsSystemDeletable())\n      x120_swoosh3.reset();\n  }\n\n  if (x104_) {\n    x104_->Update(useDt);\n    if (x104_->IsSystemDeletable())\n      x104_.reset();\n  }\n\n  xf8_lastParticleFrame = xf4_curFrame;\n}\n\nvoid CProjectileWeapon::UpdatePSTranslationAndOrientation() {\n  if (xe8_lifetime < xf4_curFrame || !x124_24_active)\n    return;\n\n  if (CModVectorElement* psvm = x4_weaponDesc->xc_PSVM.get())\n    psvm->GetValue(xf4_curFrame, xb0_velocity, x80_localOffset);\n\n  if (x124_31_VMD2)\n    x80_localOffset += x44_localXf * xb0_velocity;\n  else\n    x80_localOffset += xb0_velocity;\n\n  xb0_velocity += xbc_gravity / 60.f;\n\n  if (CVectorElement* psov = x4_weaponDesc->x8_PSOV.get()) {\n    zeus::CVector3f orient;\n    psov->GetValue(xf4_curFrame, orient);\n\n    zeus::CTransform xf = x44_localXf;\n    xf.rotateLocalX(zeus::degToRad(orient.x()));\n    xf.rotateLocalY(zeus::degToRad(orient.y()));\n    xf.rotateLocalZ(zeus::degToRad(orient.z()));\n    SetRelativeOrientation(xf);\n  }\n\n  if (CVectorElement* pscl = x4_weaponDesc->x18_PSCL.get())\n    pscl->GetValue(xf4_curFrame, x98_scale);\n\n  if (CColorElement* pcol = x4_weaponDesc->x1c_PCOL.get())\n    pcol->GetValue(xf4_curFrame, xc8_ambientLightColor);\n\n  if (CVectorElement* pofs = x4_weaponDesc->x20_POFS.get())\n    pofs->GetValue(xf4_curFrame, xa4_localOffset2);\n\n  if (CVectorElement* ofst = x4_weaponDesc->x24_OFST.get())\n    ofst->GetValue(xf4_curFrame, x8c_projOffset);\n}\n\nvoid CProjectileWeapon::SetWorldSpaceOrientation(const zeus::CTransform& xf) {\n  x44_localXf = x14_localToWorldXf.inverse() * xf;\n}\n\nvoid CProjectileWeapon::UpdateParticleFX() {\n  for (int i = 0; i < xec_childSystemUpdateRate; ++i)\n    UpdateChildParticleSystems(1.f / 60.f);\n}\n\nvoid CProjectileWeapon::Update(float dt) {\n  CGlobalRandom gr(x10_random);\n  xec_childSystemUpdateRate = 0;\n  double useDt;\n  if (zeus::close_enough(dt, 1.f / 60.f))\n    useDt = 1.0 / 60.0;\n  else\n    useDt = dt;\n  useDt = std::max(0.0, useDt);\n  xd0_curTime += useDt;\n\n  double actualTime = xf4_curFrame * (1.0 / 60.0);\n\n  while (actualTime < xd0_curTime && !zeus::close_enough(actualTime, xd0_curTime)) {\n    if (xf4_curFrame < xe8_lifetime) {\n      CParticleGlobals::instance()->SetEmitterTime(xf4_curFrame);\n      CParticleGlobals::instance()->SetParticleLifetime(xe8_lifetime);\n      CParticleGlobals::instance()->UpdateParticleLifetimeTweenValues(xf4_curFrame);\n      UpdatePSTranslationAndOrientation();\n    }\n    actualTime += (1.0 / 60.0);\n    ++xf4_curFrame;\n    ++xec_childSystemUpdateRate;\n  }\n\n  if (zeus::close_enough(actualTime, xd0_curTime))\n    xd0_curTime = actualTime;\n\n  xd8_remainderTime = (actualTime - xd0_curTime) * 60.0;\n\n  if (xf4_curFrame < xe8_lifetime) {\n    xe0_maxTurnRate = 0.f;\n    if (CRealElement* trat = x4_weaponDesc->x30_TRAT.get())\n      trat->GetValue(0, xe0_maxTurnRate);\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CProjectileWeapon.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <optional>\n\n#include \"Runtime/CRandom16.hpp\"\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Collision/CCollisionResponseData.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/Particle/CGenDescription.hpp\"\n#include \"Runtime/Particle/CParticleSwoosh.hpp\"\n#include \"Runtime/Particle/CSwooshDescription.hpp\"\n#include \"Runtime/Particle/CWeaponDescription.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CColor.hpp>\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CDecalDescription;\nclass CGenDescription;\nclass CModel;\n\nclass CProjectileWeapon {\n  static u16 g_GlobalSeed;\n  TLockedToken<CWeaponDescription> x4_weaponDesc;\n  CRandom16 x10_random;\n  zeus::CTransform x14_localToWorldXf;\n  zeus::CTransform x44_localXf;\n  zeus::CVector3f x74_worldOffset;\n  zeus::CVector3f x80_localOffset = zeus::skZero3f;\n  zeus::CVector3f x8c_projOffset = zeus::skZero3f;\n  zeus::CVector3f x98_scale = zeus::skOne3f;\n  zeus::CVector3f xa4_localOffset2 = zeus::skZero3f;\n  zeus::CVector3f xb0_velocity = zeus::skZero3f;\n  zeus::CVector3f xbc_gravity = zeus::skZero3f;\n  zeus::CColor xc8_ambientLightColor = zeus::skWhite;\n  double xd0_curTime = 0.0;\n  double xd8_remainderTime = 0.0;\n  float xe0_maxTurnRate = 0.f;\n  int xe4_flags;\n  int xe8_lifetime = 0;\n  int xec_childSystemUpdateRate = 0;\n  int xf0_ = 0;\n  int xf4_curFrame = 0;\n  int xf8_lastParticleFrame = -1;\n  std::unique_ptr<CElementGen> xfc_APSMGen;\n  std::unique_ptr<CElementGen> x100_APS2Gen;\n  std::unique_ptr<CElementGen> x104_;\n  std::optional<TLockedToken<CModel>> x108_model;\n  std::unique_ptr<CParticleSwoosh> x118_swoosh1;\n  std::unique_ptr<CParticleSwoosh> x11c_swoosh2;\n  std::unique_ptr<CParticleSwoosh> x120_swoosh3;\n  bool x124_24_active : 1 = true;\n  bool x124_25_APSO : 1 = false;\n  bool x124_26_AP11 : 1 = false;\n  bool x124_27_AP21 : 1 = false;\n  bool x124_28_AS11 : 1 = false;\n  bool x124_29_AS12 : 1 = false;\n  bool x124_30_AS13 : 1 = false;\n  bool x124_31_VMD2 : 1 = false;\n\npublic:\n  CProjectileWeapon(const TToken<CWeaponDescription>& wDesc, const zeus::CVector3f& worldOffset,\n                    const zeus::CTransform& orient, const zeus::CVector3f& scale, s32);\n  virtual ~CProjectileWeapon() = default;\n  bool IsProjectileActive() const { return x124_24_active; }\n  std::optional<zeus::CAABox> GetBounds() const;\n  const zeus::CVector3f& GetVelocity() const { return xb0_velocity; }\n  void SetVelocity(const zeus::CVector3f& vel) { xb0_velocity = vel; }\n  float GetMaxTurnRate() const { return xe0_maxTurnRate; }\n  float GetAudibleFallOff() const;\n  float GetAudibleRange() const;\n  std::optional<TLockedToken<CDecalDescription>> GetDecalForCollision(EWeaponCollisionResponseTypes type) const;\n  s32 GetSoundIdForCollision(EWeaponCollisionResponseTypes type) const;\n  std::optional<TLockedToken<CGenDescription>> CollisionOccured(EWeaponCollisionResponseTypes type, bool deflected,\n                                                                bool useTarget, const zeus::CVector3f& pos,\n                                                                const zeus::CVector3f& normal,\n                                                                const zeus::CVector3f& target);\n  TLockedToken<CWeaponDescription> GetWeaponDescription() const { return x4_weaponDesc; }\n  void RenderParticles() const;\n  virtual void AddToRenderer();\n  virtual void Render();\n  bool IsSystemDeletable() const;\n  void UpdateChildParticleSystems(float);\n  void UpdatePSTranslationAndOrientation();\n  void SetWorldSpaceOrientation(const zeus::CTransform& xf);\n  void SetRelativeOrientation(const zeus::CTransform& xf) { x44_localXf = xf; }\n  virtual zeus::CVector3f GetTranslation() const;\n  virtual zeus::CTransform GetTransform() const;\n  void UpdateParticleFX();\n  virtual void Update(float dt);\n  void SetGravity(const zeus::CVector3f& grav) { xbc_gravity = grav; }\n  zeus::CVector3f GetGravity() const { return xbc_gravity; }\n  static void SetGlobalSeed(u16 seed) { g_GlobalSeed = seed; }\n  CElementGen* GetAttachedPS1() const { return xfc_APSMGen.get(); }\n  double GameTime() const { return xd0_curTime; }\n  static constexpr float GetTickPeriod() { return 0.0166667f; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CTargetableProjectile.cpp",
    "content": "#include \"Runtime/Weapon/CTargetableProjectile.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCTargetableProjectile::CTargetableProjectile(\n    const TToken<CWeaponDescription>& desc, EWeaponType type, const zeus::CTransform& xf, EMaterialTypes materials,\n    const CDamageInfo& damage, const CDamageInfo& damage2, TUniqueId uid, TAreaId aid, TUniqueId owner,\n    const TLockedToken<CWeaponDescription>& weapDesc, TUniqueId homingTarget, EProjectileAttrib attribs,\n    const std::optional<TLockedToken<CGenDescription>>& visorParticle, u16 visorSfx, bool sendCollideMsg)\n: CEnergyProjectile(true, desc, type, xf, materials, damage, uid, aid, owner, homingTarget,\n                    attribs | EProjectileAttrib::BigProjectile | EProjectileAttrib::PartialCharge |\n                        EProjectileAttrib::PlasmaProjectile,\n                    false, zeus::skOne3f, visorParticle, visorSfx, sendCollideMsg)\n, x3d8_weaponDesc(weapDesc)\n, x3e0_damage(damage2) {\n  x68_material.Add(EMaterialTypes::Target);\n  x68_material.Add(EMaterialTypes::Orbit);\n}\n\nvoid CTargetableProjectile::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nzeus::CVector3f CTargetableProjectile::GetAimPosition(const CStateManager& mgr, float dt) const {\n  static constexpr float tickRecip = 1.f / CProjectileWeapon::GetTickPeriod();\n  return (dt * dt * 0.5f * tickRecip * x170_projectile.GetGravity()) +\n         (dt * tickRecip * x170_projectile.GetVelocity() + GetTranslation());\n}\n\nbool CTargetableProjectile::Explode(const zeus::CVector3f& pos, const zeus::CVector3f& normal,\n                                    EWeaponCollisionResponseTypes type, CStateManager& mgr,\n                                    const CDamageVulnerability& dVuln, TUniqueId hitActor) {\n  bool ret = CEnergyProjectile::Explode(pos, normal, type, mgr, dVuln, hitActor);\n\n  if (x2e4_24_active || x2c4_hitProjectileOwner == kInvalidUniqueId ||\n      x2c4_hitProjectileOwner != mgr.GetPlayer().GetUniqueId()) {\n    return ret;\n  }\n\n  if (TCastToConstPtr<CActor> act = mgr.GetObjectById(xec_ownerId)) {\n    TUniqueId uid = mgr.AllocateUniqueId();\n    zeus::CTransform xf = zeus::lookAt(x170_projectile.GetTranslation(), act->GetAimPosition(mgr, 0.f), zeus::skUp);\n    auto* projectile = new CEnergyProjectile(true, x3d8_weaponDesc, xf0_weaponType, xf, EMaterialTypes::Player,\n                                             x3e0_damage, uid, GetAreaIdAlways(), x2c4_hitProjectileOwner, xec_ownerId,\n                                             EProjectileAttrib::None, false, zeus::skOne3f, {}, 0xFFFF, false);\n    mgr.AddObject(projectile);\n    projectile->AddMaterial(EMaterialTypes::Orbit);\n    mgr.GetPlayer().ResetAimTargetPrediction(uid);\n    mgr.GetPlayer().SetOrbitTargetId(uid, mgr);\n    x2c4_hitProjectileOwner = kInvalidUniqueId;\n  }\n\n  return ret;\n}\n\nvoid CTargetableProjectile::ResolveCollisionWithActor(const CRayCastResult& res, CActor& act, CStateManager& mgr) {\n  zeus::CTransform xf = zeus::lookAt(GetTranslation(), GetAimPosition(mgr, 0.1f));\n  xf.origin = GetTranslation();\n  SetTransform(xf);\n  CEnergyProjectile::ResolveCollisionWithActor(res, act, mgr);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CTargetableProjectile.hpp",
    "content": "#pragma once\n\n#include <optional>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Weapon/CEnergyProjectile.hpp\"\n\nnamespace metaforce {\n\nclass CTargetableProjectile : public CEnergyProjectile {\n  TLockedToken<CWeaponDescription> x3d8_weaponDesc;\n  CDamageInfo x3e0_damage;\n\npublic:\n  DEFINE_ENTITY\n  CTargetableProjectile(const TToken<CWeaponDescription>& desc, EWeaponType type, const zeus::CTransform& xf,\n                        EMaterialTypes materials, const CDamageInfo& damage, const CDamageInfo& damage2, TUniqueId uid,\n                        TAreaId aid, TUniqueId owner, const TLockedToken<CWeaponDescription>& weapDesc,\n                        TUniqueId homingTarget, EProjectileAttrib attribs,\n                        const std::optional<TLockedToken<CGenDescription>>& visorParticle, u16 visorSfx,\n                        bool sendCollideMsg);\n\n  void Accept(IVisitor&) override;\n  zeus::CVector3f GetAimPosition(const CStateManager&, float) const override;\n  bool Explode(const zeus::CVector3f& pos, const zeus::CVector3f& normal, EWeaponCollisionResponseTypes type,\n               CStateManager& mgr, const CDamageVulnerability& dVuln, TUniqueId hitActor) override;\n  void ResolveCollisionWithActor(const CRayCastResult& res, CActor& act, CStateManager& mgr) override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CWaveBeam.cpp",
    "content": "#include \"Runtime/Weapon/CWaveBeam.hpp\"\n\n#include <array>\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Weapon/CEnergyProjectile.hpp\"\n\nnamespace metaforce {\nnamespace {\nconstexpr float skShotAnglePitch = 120.f;\n\nconstexpr std::array<u16, 2> kSoundId{\n    SFXwpn_fire_wave_normal,\n    SFXwpn_fire_wave_charged,\n};\n} // Anonymous namespace\n\nCWaveBeam::CWaveBeam(CAssetId characterId, EWeaponType type, TUniqueId playerId, EMaterialTypes playerMaterial,\n                     const zeus::CVector3f& scale)\n: CGunWeapon(characterId, type, playerId, playerMaterial, scale) {\n  x21c_waveBeam = g_SimplePool->GetObj(\"WaveBeam\");\n  x228_wave2nd1 = g_SimplePool->GetObj(\"Wave2nd_1\");\n  x234_wave2nd2 = g_SimplePool->GetObj(\"Wave2nd_2\");\n  x240_wave2nd3 = g_SimplePool->GetObj(\"Wave2nd_3\");\n}\n\nvoid CWaveBeam::PostRenderGunFx(const CStateManager& mgr, const zeus::CTransform& xf) {\n  if (x1cc_enabledSecondaryEffect != ESecondaryFxType::None) {\n    if (x254_chargeFx)\n      x254_chargeFx->Render();\n    if (x250_chargeElec)\n      x250_chargeElec->Render();\n  }\n  CGunWeapon::PostRenderGunFx(mgr, xf);\n}\n\nvoid CWaveBeam::UpdateGunFx(bool shotSmoke, float dt, const CStateManager& mgr, const zeus::CTransform& xf) {\n  if (x1cc_enabledSecondaryEffect != ESecondaryFxType::None) {\n    if (x258_25_effectTimerActive && x24c_effectTimer < 0.f) {\n      x1cc_enabledSecondaryEffect = ESecondaryFxType::None;\n      x24c_effectTimer = 0.f;\n      x258_25_effectTimerActive = false;\n    } else {\n      if (x254_chargeFx) {\n        x254_chargeFx->SetGlobalTranslation(xf.origin);\n        x254_chargeFx->SetGlobalOrientation(xf.getRotation());\n        x254_chargeFx->Update(dt);\n      }\n      if (x250_chargeElec) {\n        x250_chargeElec->SetGlobalTranslation(xf.origin);\n        x250_chargeElec->SetGlobalOrientation(xf.getRotation());\n        x250_chargeElec->Update(dt);\n      }\n    }\n    if (x258_25_effectTimerActive && x24c_effectTimer > 0.f)\n      x24c_effectTimer -= dt;\n  }\n  CGunWeapon::UpdateGunFx(shotSmoke, dt, mgr, xf);\n}\n\nvoid CWaveBeam::Fire(bool underwater, float dt, EChargeState chargeState, const zeus::CTransform& xf,\n                     CStateManager& mgr, TUniqueId homingTarget, float chargeFactor1, float chargeFactor2) {\n  if (chargeState == EChargeState::Charged) {\n    CGunWeapon::Fire(underwater, dt, chargeState, xf, mgr, homingTarget, chargeFactor1, chargeFactor2);\n  } else {\n    float randAng = mgr.GetActiveRandom()->Float() * 360.f;\n    auto& weaponDesc = x144_weapons[int(chargeState)];\n    for (int i = 0; i < 3; ++i) {\n      zeus::CTransform shotXf = xf * zeus::CTransform::RotateY(zeus::degToRad((randAng + i) * skShotAnglePitch));\n      CEnergyProjectile* proj = new CEnergyProjectile(\n          true, weaponDesc, x1c0_weaponType, shotXf, x1c8_playerMaterial,\n          GetDamageInfo(mgr, chargeState, chargeFactor1), mgr.AllocateUniqueId(), kInvalidAreaId, x1c4_playerId,\n          homingTarget, EProjectileAttrib::ArmCannon, underwater, zeus::skOne3f, {}, -1, false);\n      mgr.AddObject(proj);\n      proj->Think(dt, mgr);\n    }\n  }\n\n  if (chargeState == EChargeState::Charged)\n    x218_25_enableCharge = true;\n\n  NWeaponTypes::play_sfx(kSoundId[size_t(chargeState)], underwater, false, 0.165f);\n  const CAnimPlaybackParms parms(skShootAnim[size_t(chargeState)], -1, 1.f, true);\n  x10_solidModelData->GetAnimationData()->EnableLooping(false);\n  x10_solidModelData->GetAnimationData()->SetAnimation(parms, false);\n}\n\nvoid CWaveBeam::EnableSecondaryFx(ESecondaryFxType type) {\n  switch (type) {\n  case ESecondaryFxType::None:\n    x1cc_enabledSecondaryEffect = ESecondaryFxType::None;\n    break;\n  case ESecondaryFxType::CancelCharge:\n    if (x1cc_enabledSecondaryEffect == ESecondaryFxType::None)\n      break;\n    [[fallthrough]];\n  default:\n    if (x1cc_enabledSecondaryEffect != ESecondaryFxType::ToCombo) {\n      auto& fx = type == ESecondaryFxType::Charge ? x228_wave2nd1 : x234_wave2nd2;\n      x250_chargeElec = std::make_unique<CParticleElectric>(fx);\n      x250_chargeElec->SetGlobalScale(x4_scale);\n    }\n    switch (type) {\n    case ESecondaryFxType::Charge:\n      x254_chargeFx.reset();\n      break;\n    case ESecondaryFxType::CancelCharge:\n      if (x1cc_enabledSecondaryEffect != ESecondaryFxType::CancelCharge) {\n        x258_25_effectTimerActive = true;\n        x24c_effectTimer = 3.f;\n        if (x254_chargeFx)\n          x254_chargeFx->SetParticleEmission(false);\n      }\n      break;\n    case ESecondaryFxType::ToCombo:\n      x254_chargeFx = std::make_unique<CElementGen>(x240_wave2nd3);\n      x254_chargeFx->SetGlobalScale(x4_scale);\n      x24c_effectTimer = 0.f;\n      x258_25_effectTimerActive = true;\n      break;\n    default:\n      break;\n    }\n    x1cc_enabledSecondaryEffect = type;\n  }\n}\n\nvoid CWaveBeam::Update(float dt, CStateManager& mgr) {\n  CGunWeapon::Update(dt, mgr);\n  if (IsLoaded())\n    return;\n\n  if (CGunWeapon::IsLoaded() && !x258_24_loaded) {\n    x258_24_loaded =\n        x228_wave2nd1.IsLoaded() && x234_wave2nd2.IsLoaded() && x240_wave2nd3.IsLoaded() && x21c_waveBeam.IsLoaded();\n  }\n}\n\nvoid CWaveBeam::Load(CStateManager& mgr, bool subtypeBasePose) {\n  CGunWeapon::Load(mgr, subtypeBasePose);\n  x228_wave2nd1.Lock();\n  x234_wave2nd2.Lock();\n  x240_wave2nd3.Lock();\n  x21c_waveBeam.Lock();\n}\n\nvoid CWaveBeam::ReInitVariables() {\n  x24c_effectTimer = 0.f;\n  x250_chargeElec.reset();\n  x254_chargeFx.reset();\n  x258_24_loaded = false;\n  x258_25_effectTimerActive = false;\n  x1cc_enabledSecondaryEffect = ESecondaryFxType::None;\n}\n\nvoid CWaveBeam::Unload(CStateManager& mgr) {\n  CGunWeapon::Unload(mgr);\n  x21c_waveBeam.Unlock();\n  x240_wave2nd3.Unlock();\n  x234_wave2nd2.Unlock();\n  x228_wave2nd1.Unlock();\n  ReInitVariables();\n}\n\nbool CWaveBeam::IsLoaded() const { return CGunWeapon::IsLoaded() && x258_24_loaded; }\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CWaveBeam.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Weapon/CGunWeapon.hpp\"\n\nnamespace metaforce {\n\nclass CWaveBeam final : public CGunWeapon {\n  TCachedToken<CWeaponDescription> x21c_waveBeam;\n  TCachedToken<CElectricDescription> x228_wave2nd1;\n  TCachedToken<CElectricDescription> x234_wave2nd2;\n  TCachedToken<CGenDescription> x240_wave2nd3;\n  float x24c_effectTimer = 0.f;\n  std::unique_ptr<CParticleElectric> x250_chargeElec;\n  std::unique_ptr<CElementGen> x254_chargeFx;\n  bool x258_24_loaded : 1 = false;\n  bool x258_25_effectTimerActive : 1 = false;\n  void ReInitVariables();\n\npublic:\n  CWaveBeam(CAssetId characterId, EWeaponType type, TUniqueId playerId, EMaterialTypes playerMaterial,\n            const zeus::CVector3f& scale);\n\n  void PostRenderGunFx(const CStateManager& mgr, const zeus::CTransform& xf) override;\n  void UpdateGunFx(bool shotSmoke, float dt, const CStateManager& mgr, const zeus::CTransform& xf) override;\n  void Fire(bool underwater, float dt, EChargeState chargeState, const zeus::CTransform& xf, CStateManager& mgr,\n            TUniqueId homingTarget, float chargeFactor1, float chargeFactor2) override;\n  void EnableSecondaryFx(ESecondaryFxType type) override;\n  void Update(float dt, CStateManager& mgr) override;\n  void Load(CStateManager& mgr, bool subtypeBasePose) override;\n  void Unload(CStateManager& mgr) override;\n  bool IsLoaded() const override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CWaveBuster.cpp",
    "content": "#include \"Runtime/Weapon/CWaveBuster.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Input/CRumbleManager.hpp\"\n#include \"Runtime/World/CGameLight.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n\n#include \"Audio/SFX/Weapons.h\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCWaveBuster::CWaveBuster(const TToken<CWeaponDescription>& desc, EWeaponType type, const zeus::CTransform& xf,\n                         EMaterialTypes matType, const CDamageInfo& dInfo, TUniqueId uid, TAreaId aid, TUniqueId owner,\n                         TUniqueId homingTarget, EProjectileAttrib attrib)\n: CGameProjectile(true, desc, \"WaveBuster\"sv, type, xf, matType, dInfo, uid, aid, owner, homingTarget, attrib, false,\n                  zeus::skOne3f, {}, -1, false)\n, x2e8_originalXf(xf)\n, x348_targetPoint(x2e8_originalXf.frontVector().normalized() * 25.f + x2e8_originalXf.origin)\n, x354_busterSwoosh1(g_SimplePool->GetObj(\"BusterSwoosh1\"))\n, x360_busterSwoosh2(g_SimplePool->GetObj(\"BusterSwoosh2\"))\n, x36c_busterSparks(g_SimplePool->GetObj(\"BusterSparks\"))\n, x378_busterLight(g_SimplePool->GetObj(\"BusterLight\")) {\n  x354_busterSwoosh1.GetObj();\n  x360_busterSwoosh2.GetObj();\n  x36c_busterSparks.GetObj();\n  x378_busterLight.GetObj();\n  x384_busterSwoosh1Gen = std::make_unique<CParticleSwoosh>(x354_busterSwoosh1, 0);\n  x388_busterSwoosh2Gen = std::make_unique<CParticleSwoosh>(x360_busterSwoosh2, 0);\n  x38c_busterSparksGen = std::make_unique<CElementGen>(x36c_busterSparks);\n  x390_busterLightGen = std::make_unique<CElementGen>(x378_busterLight);\n\n  for (size_t i = 0; i < x384_busterSwoosh1Gen->GetSwooshDataCount() - 1; i++) {\n    x384_busterSwoosh1Gen->ForceOneUpdate(0.f);\n    x388_busterSwoosh2Gen->ForceOneUpdate(0.f);\n  }\n}\n\nvoid CWaveBuster::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CWaveBuster::Think(float dt, CStateManager& mgr) {\n  CWeapon::Think(dt, mgr);\n  if (!GetActive()) {\n    return;\n  }\n\n  if (GetAreaIdAlways() != mgr.GetWorld()->GetCurrentAreaId()) {\n    mgr.SetActorAreaId(*this, mgr.GetWorld()->GetCurrentAreaId());\n  }\n  x3d0_27_ = false;\n  x3d0_28_ = false;\n  const zeus::CVector3f beamForward = x2e8_originalXf.frontVector().normalized();\n\n  float beamDistance = 25.f;\n  if (!x3d0_25_ && !x3d0_26_trackingTarget) {\n    TUniqueId uid = kInvalidUniqueId;\n    CRayCastResult res = SeekDamageTarget(uid, x2e8_originalXf.origin, beamForward, mgr, dt);\n    if (res.IsValid() && res.GetT() < 25.f) {\n      if (TCastToPtr<CActor> act = mgr.ObjectById(uid)) {\n        act->Touch(*this, mgr);\n        mgr.ApplyDamage(GetUniqueId(), act->GetUniqueId(), GetOwnerId(), CDamageInfo(x12c_curDamageInfo, dt),\n                        xf8_filter, GetTransform().basis[1]);\n      } else {\n        x3d0_28_ = true;\n      }\n      x3d0_27_ = true;\n      beamDistance = res.GetT();\n    }\n  }\n\n  if (x2c0_homingTargetId == kInvalidUniqueId || !x3d0_26_trackingTarget) {\n    beamDistance = std::max(1.f, beamDistance);\n    x348_targetPoint = x2e8_originalXf.origin + (beamDistance * beamForward);\n    if (!x3d0_25_) {\n      const float x = mgr.GetActiveRandom()->Range(-1.f, 1.f);\n      const float z = mgr.GetActiveRandom()->Range(-1.f, 1.f);\n      x348_targetPoint += zeus::CVector3f{x, 0.f, z};\n      SetTranslation(x348_targetPoint);\n    } else {\n      UpdateTargetSeek(dt, mgr);\n    }\n  } else {\n    UpdateTargetDamage(dt, mgr);\n  }\n\n  if (UpdateBeamFrame(mgr, dt)) {\n    ResetBeam(true);\n  }\n\n  zeus::CVector3f vec = x2c0_homingTargetId != kInvalidUniqueId && x3d0_26_trackingTarget\n                        ? GetTransform() * zeus::CTransform::RotateY(x3c4_) * zeus::CVector3f{0.f, -3.f, -1.5f}\n                        : (GetTransform().frontVector() * -1.5f) + GetTransform().origin;\n  if (x3a0_ >= 0.5f || x2c0_homingTargetId == kInvalidUniqueId) {\n    x330_ = x324_bezierC;\n    x324_bezierC = vec;\n    x3a0_ = 0.5f;\n  } else {\n    x324_bezierC = x330_ * (1.f - x330_) + vec * (x3a0_ / 0.5f);\n    x3a0_ += 0.125f * dt;\n  }\n\n  if (x2c8_projectileLight != kInvalidUniqueId) {\n    x390_busterLightGen->Update(dt);\n    if (TCastToPtr<CGameLight> light = mgr.ObjectById(x2c8_projectileLight)) {\n      light->SetTransform(GetTransform());\n      if (x390_busterLightGen && x390_busterLightGen->SystemHasLight()) {\n        light->SetLight(x390_busterLightGen->GetLight());\n      }\n    }\n  }\n\n  x3c8_innerSwooshColorT += 20.f * dt;\n  if (x3c8_innerSwooshColorT > 1.f) {\n    ++x3cc_innerSwooshColorIdx;\n    if (x3cc_innerSwooshColorIdx > 2)\n      x3cc_innerSwooshColorIdx = 0;\n    x3c8_innerSwooshColorT = 0.f;\n  }\n  x38c_busterSparksGen->Update(dt);\n}\n\nvoid CWaveBuster::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) {\n  EnsureRendered(mgr, x2e8_originalXf.origin, GetSortingBounds(mgr));\n}\n\nvoid CWaveBuster::Render(CStateManager& mgr) {\n  RenderParticles();\n  RenderBeam();\n  CWeapon::Render(mgr);\n}\n\nvoid CWaveBuster::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId senderId, CStateManager& mgr) {\n  if (msg == EScriptObjectMessage::Deleted) {\n    DeleteProjectileLight(mgr);\n  } else if (msg == EScriptObjectMessage::Registered) {\n    if (x390_busterLightGen != nullptr && x390_busterLightGen->SystemHasLight()) {\n      const CLight light = x390_busterLightGen->GetLight();\n      CreateProjectileLight(\"WaveBuster_Light\", light, mgr);\n    }\n\n    // Thermal hot\n    xe6_27_thermalVisorFlags = 2;\n\n    x318_bezierB = x2e8_originalXf.origin;\n    x324_bezierC = x34_transform.origin;\n    x330_ = x34_transform.origin;\n  }\n\n  CGameProjectile::AcceptScriptMsg(msg, senderId, mgr);\n}\n\nstd::optional<zeus::CAABox> CWaveBuster::GetTouchBounds() const {\n  if (x3d0_28_) {\n    return std::nullopt;\n  }\n\n  return GetProjectileBounds();\n}\n\nvoid CWaveBuster::UpdateFx(const zeus::CTransform& xf, float dt, CStateManager& mgr) {\n  if (!GetActive()) {\n    return;\n  }\n\n  x2e8_originalXf = xf;\n  x398_spiralOffset -= 60.f * dt;\n  if (x398_spiralOffset < 0.f) {\n     x398_spiralOffset = 2.f * M_PIF;\n  }\n  x170_projectile.SetVelocity(zeus::CVector3f{0.f, x3d0_25_ ? 1.6f : 0.f, 0.f});\n}\n\nvoid CWaveBuster::ResetBeam(bool deactivate) {\n  if (!deactivate) {\n    x38c_busterSparksGen->SetParticleEmission(false);\n    x3d0_24_firing = false;\n  } else {\n    SetActive(false);\n    x3d0_24_firing = false;\n    x38c_busterSparksGen->SetParticleEmission(false);\n    x398_spiralOffset = 2.f * M_PIF;\n  }\n}\n\nvoid CWaveBuster::SetNewTarget(TUniqueId id, CStateManager& mgr) {\n  x2c0_homingTargetId = id;\n  if (id == kInvalidUniqueId) {\n    x3d0_26_trackingTarget = false;\n  } else {\n    x3d0_26_trackingTarget = true;\n    CSfxManager::AddEmitter(SFXsfx06FF, GetTranslation(), zeus::skZero3f, true, false, 255, kInvalidAreaId);\n    mgr.GetRumbleManager().Rumble(mgr, ERumbleFxId::PlayerBump, 0.5f, ERumblePriority::Three);\n  }\n}\n\nvoid CWaveBuster::RenderParticles() {\n  static constexpr std::array<zeus::CColor, 4> skCols = {{\n      zeus::skWhite,\n      {1.f, 0.f, 1.f, 1.f},\n      {1.f, 0.f, 0.f, 1.f},\n      {0.f, 0.f, 1.f, 1.f},\n  }};\n  zeus::CTransform originalRotation = x2e8_originalXf.getRotation();\n  x38c_busterSparksGen->SetParticleEmission(true);\n  const zeus::CColor col = zeus::CColor::lerp(skCols[x3cc_innerSwooshColorIdx], skCols[x3cc_innerSwooshColorIdx + 1], x3c8_innerSwooshColorT);\n  float prevSwoosh2Rot = x388_busterSwoosh2Gen->GetSwooshData(x388_busterSwoosh2Gen->GetSwooshDataCount() - 1).x30_irot;\n  float prevSwoosh1Rot = x384_busterSwoosh1Gen->GetSwooshData(x384_busterSwoosh1Gen->GetSwooshDataCount() - 1).x30_irot;\n\n  float t = 0.f;\n  for (int i = 0; i < x384_busterSwoosh1Gen->GetSwooshDataCount(); i++) {\n    zeus::CVector3f point = zeus::getBezierPoint(GetTranslation(), x324_bezierC, x318_bezierB, x2e8_originalXf.origin, t);\n    CParticleSwoosh::SSwooshData& swoosh1 = x384_busterSwoosh1Gen->GetSwooshData(i);\n    CParticleSwoosh::SSwooshData& swoosh2 = x388_busterSwoosh2Gen->GetSwooshData(i);\n\n    swoosh1.xc_translation = point;\n    swoosh2.xc_translation = point;\n\n    swoosh1.x38_orientation = originalRotation;\n    swoosh2.x38_orientation = originalRotation;\n\n    swoosh2.x6c_color = col;\n\n    const float curSwoosh1Rot = swoosh1.x30_irot;\n    const float curSwoosh2Rot = swoosh2.x30_irot;\n\n    swoosh1.x30_irot = prevSwoosh1Rot;\n    swoosh2.x30_irot = prevSwoosh2Rot;\n\n    x38c_busterSparksGen->SetTranslation(point);\n    x38c_busterSparksGen->ForceParticleCreation(1);\n    t += 0.04f;\n\n    prevSwoosh1Rot = curSwoosh1Rot;\n    prevSwoosh2Rot = curSwoosh2Rot;\n  }\n\n  x38c_busterSparksGen->SetParticleEmission(false);\n  x384_busterSwoosh1Gen->Render();\n  x388_busterSwoosh2Gen->Render();\n  x38c_busterSparksGen->Render();\n}\n\nvoid CWaveBuster::RenderBeam() {\n  const zeus::CTransform inv = x2e8_originalXf.inverse();\n  const zeus::CVector3f vecA = inv * x2e8_originalXf.origin;\n  const zeus::CVector3f vecB = inv * x318_bezierB;\n  const zeus::CVector3f vecC = inv * x324_bezierC;\n  const zeus::CVector3f vecD = inv * GetTranslation();\n  float spiralRadius = 0.f;\n  rstl::reserved_vector<zeus::CVector3f, 36 * 6> linePoints; // Used to be L2Cache access\n  linePoints.resize(36 * 6);\n  float t = 0.16;\n  zeus::CVector3f lastPoint = vecA;\n  int splineBaseIndex = 0;\n  while (t <= 1.f) {\n    const zeus::CVector3f point = zeus::getBezierPoint(vecA, vecB, vecC, vecD, t);\n    float angle = 0.f;\n    for (size_t i = 0; i < 36; ++i) {\n      const float randX = x394_rand.Range(-0.041667f, 0.041667f);\n      const float randZ = x394_rand.Range(-0.041667f, 0.041667f);\n      const float offX = spiralRadius * std::cos(angle + x398_spiralOffset) + randX;\n      const float offZ = spiralRadius * std::sin(angle + x398_spiralOffset) + randZ;\n      const float angleRad = angle / (2.f * M_PIF);\n      linePoints[i + splineBaseIndex] = lastPoint * (1.f - angleRad) + point * angleRad + zeus::CVector3f{offX, 0.f, offZ};\n      angle += zeus::degToRad(10.f);\n    }\n    spiralRadius = 0.25f;\n    t += 0.16;\n    lastPoint = point;\n    splineBaseIndex += 36;\n  }\n  g_Renderer->SetModelMatrix(x2e8_originalXf);\n  // m_lineRenderer1.Reset();\n  // for (const zeus::CVector3f& vec : linePoints) {\n  //   m_lineRenderer1.AddVertex(vec, zeus::skWhite, 12.f / 6.f);\n  // }\n  // m_lineRenderer1.Render();\n  //\n  // m_lineRenderer2.Reset();\n  // for (const zeus::CVector3f& vec : linePoints) {\n  //   m_lineRenderer2.AddVertex(vec, zeus::CColor{1.f, 0.f, 1.f, 0.5f}, 48.f / 6);\n  // }\n  // m_lineRenderer2.Render();\n}\n\nCRayCastResult CWaveBuster::SeekDamageTarget(TUniqueId& uid, const zeus::CVector3f& pos, const zeus::CVector3f& dir,\n                                             CStateManager& mgr, float dt) {\n  CRayCastResult res = mgr.RayStaticIntersection(pos, dir, 25.f, xf8_filter);\n  TUniqueId physId = kInvalidUniqueId;\n  TUniqueId actId = kInvalidUniqueId;\n  CRayCastResult physRes;\n  CRayCastResult actRes;\n  RayCastTarget(mgr, physId, actId, pos, dir, 25.f, physRes, actRes);\n\n  if (physRes.IsValid() && ApplyDamageToTarget(actId, actRes, physRes, res, mgr, dt)) {\n    uid = physId;\n    return physRes;\n  }\n\n  if (actRes.IsValid() && ApplyDamageToTarget(physId, actRes, physRes, res, mgr, dt)) {\n    uid = actId;\n    return actRes;\n  }\n\n  return res;\n}\n\nbool CWaveBuster::ApplyDamageToTarget(TUniqueId damagee, const CRayCastResult& actRes, const CRayCastResult& physRes,\n                                      const CRayCastResult& selfRes, CStateManager& mgr, float dt) {\n\n  if (selfRes.IsInvalid() || physRes.GetT() <= selfRes.GetT()) {\n    if (actRes.IsValid()) {\n      if (TCastToPtr<CActor> act = mgr.ObjectById(damagee)) {\n        act->Touch(*this, mgr);\n        mgr.ApplyDamage(GetUniqueId(), damagee, GetOwnerId(), CDamageInfo(x12c_curDamageInfo, dt), xf8_filter,\n                        GetTransform().basis[1]);\n      }\n    }\n    return true;\n  }\n  return false;\n}\n\nvoid CWaveBuster::UpdateTargetSeek(float dt, CStateManager& mgr) {\n  TUniqueId uid = kInvalidUniqueId;\n  SeekTarget(dt, uid, mgr);\n  if (x2c0_homingTargetId == kInvalidUniqueId && !x3d0_26_trackingTarget &&\n      (GetTranslation() - x2e8_originalXf.origin).magSquared() > 625.f) {\n    x3d0_25_ = false;\n  } else if (const TCastToConstPtr<CActor> act = mgr.GetObjectById(x2c0_homingTargetId)) {\n    zeus::CVector3f vec = zeus::skForward;\n    if (GetViewAngleToTarget(vec, *act) <= zeus::degToRad(90.f) && x3d0_26_trackingTarget) {\n      x3d0_25_ = false;\n    } else {\n      x2c0_homingTargetId = kInvalidUniqueId;\n      x3d0_26_trackingTarget = false;\n    }\n  }\n}\n\nvoid CWaveBuster::UpdateTargetDamage(float dt, CStateManager& mgr) {\n  if (const TCastToConstPtr<CActor> act = mgr.GetObjectById(x2c0_homingTargetId)) {\n    const CHealthInfo* hInfo = act->GetHealthInfo(mgr);\n    if (hInfo != nullptr && hInfo->GetHP() > 0.f) {\n      x33c_homingTargetPoint = act->GetAimPosition(mgr, 0.f);\n      SetTranslation(x33c_homingTargetPoint);\n      mgr.ApplyDamage(GetUniqueId(), x2c0_homingTargetId, GetOwnerId(), CDamageInfo(x12c_curDamageInfo, dt), xf8_filter,\n                      zeus::skZero3f);\n      return;\n    }\n  }\n\n  SetTransform(zeus::lookAt(GetTranslation(), x348_targetPoint + (0.001f * x2e8_originalXf.basis[1].normalized())));\n  x2c0_homingTargetId = kInvalidUniqueId;\n  x39c_ = 0.f;\n  x3a0_ = 0.f;\n}\n\nbool CWaveBuster::UpdateBeamFrame(CStateManager& mgr, float dt) {\n  zeus::CVector3f local_ac = zeus::skForward;\n  float viewAngle = 0.f;\n  if (const TCastToConstPtr<CActor> act = mgr.GetObjectById(x2c0_homingTargetId)) {\n    const CHealthInfo* hInfo = act->GetHealthInfo(mgr);\n    if (hInfo != nullptr && hInfo->GetHP() > 0.f &&\n        (act->GetTranslation() - x2e8_originalXf.origin).magSquared() > 10000.f) {\n      return true;\n    }\n    viewAngle = GetViewAngleToTarget(local_ac, *act);\n  }\n  x3a8_ -= dt;\n  if (x3a8_ <= 0.f) {\n    const float dVar10 = mgr.GetActiveRandom()->Range(0.f, 2 * M_PIF);\n    const float dVar9 = mgr.GetActiveRandom()->Range(0.05f, 0.25f);\n    x3a4_ = (1.f / dVar9) * (dVar10 - x3ac_);\n    x3a8_ = dVar9;\n  }\n  x3b8_ -= dt;\n  if (x3b8_ <= 0.f) {\n    const float dVar10 = mgr.GetActiveRandom()->Range(0.f, 0.5f);\n    const float dVar9 = mgr.GetActiveRandom()->Range(0.1f, 0.5f);\n    x3b4_ = (1.f / dVar9) * (dVar10 - x3b0_);\n    x3b8_ = dVar9;\n  }\n\n  x3c0_ -= dt;\n  if (x3c0_ <= 0.f) {\n    const float dVar10 = mgr.GetActiveRandom()->Range(0.f, 2 * M_PIF);\n    const float dVar9 = mgr.GetActiveRandom()->Range(0.05f, 0.25f);\n    x3bc_ = (1.f / dVar9) * (dVar10 - x3ac_);\n    x3c0_ = dVar9;\n  }\n  x3ac_ += x3a4_ * dt;\n  x3b0_ += x3b4_ * dt;\n  x3c4_ += x3bc_ * dt;\n  x318_bezierB = x2e8_originalXf * zeus::CTransform::RotateY(x3ac_) *\n          zeus::CVector3f(0.f, 2.f, 1.5f * ((x2c0_homingTargetId == kInvalidUniqueId ? 1.f : 1.25f) - x3b0_ * x3b0_));\n  return viewAngle > zeus::degToRad(90.f);\n}\n\nfloat CWaveBuster::GetViewAngleToTarget(zeus::CVector3f& p1, const CActor& act) {\n  p1 = act.GetTranslation() - x2e8_originalXf.origin;\n  if (!p1.canBeNormalized()) {\n    p1 = GetTransform().basis[1];\n  } else {\n    p1.normalize();\n  }\n\n  return zeus::CVector2f::getAngleDiff(x2e8_originalXf.frontVector().toVec2f(), p1.toVec2f());\n}\n\nvoid CWaveBuster::RayCastTarget(CStateManager& mgr, TUniqueId& physId, TUniqueId& actId, const zeus::CVector3f& start,\n                                const zeus::CVector3f& end, float length, CRayCastResult& physRes,\n                                CRayCastResult& actorRes) {\n  const zeus::CAABox box = zeus::CAABox(-0.5f, 0.f, -0.5f, 0.5f, 25.f, 0.5f).getTransformedAABox(x2e8_originalXf);\n  rstl::reserved_vector<TUniqueId, 1024> nearList;\n  mgr.BuildNearList(nearList, box,\n                    CMaterialFilter::MakeExclude({EMaterialTypes::ProjectilePassthrough, EMaterialTypes::Player}),\n                    this);\n  if (length <= 0.f) {\n    length = 100000.f;\n  }\n\n  float nearestPhysT = length;\n  float nearestActorT = length;\n  for (TUniqueId uid : nearList) {\n    if (const TCastToConstPtr<CPhysicsActor> pAct = mgr.GetObjectById(uid)) {\n      CRayCastResult res =\n          pAct->GetCollisionPrimitive()->CastRay(start, end, length, xf8_filter, pAct->GetPrimitiveTransform());\n      if (!res.IsValid() || res.GetT() >= nearestPhysT) {\n        continue;\n      }\n      actorRes = res;\n      actId = pAct->GetUniqueId();\n      nearestPhysT = res.GetT();\n    } else if (const TCastToConstPtr<CActor> act = mgr.GetObjectById(uid)) {\n      if (auto bounds = act->GetTouchBounds()) {\n        CCollidableAABox collidableBox(*bounds, act->GetMaterialList());\n        CRayCastResult res = collidableBox.CastRay(start, end, length, xf8_filter, zeus::CTransform{});\n        if (!res.IsValid() || res.GetT() >= nearestActorT) {\n          continue;\n        }\n        actorRes = res;\n        actId = act->GetUniqueId();\n        nearestActorT = res.GetT();\n      }\n    }\n  }\n}\n\nCRayCastResult CWaveBuster::SeekTarget(float dt, TUniqueId& uid, CStateManager& mgr) {\n  rstl::reserved_vector<TUniqueId, 1024> nearList;\n  mgr.BuildNearList(nearList, GetProjectileBounds(),\n                    CMaterialFilter::MakeIncludeExclude(\n                        {EMaterialTypes::Solid}, {EMaterialTypes::ProjectilePassthrough, EMaterialTypes::Player}),\n                    this);\n  CRayCastResult res = RayCollisionCheckWithWorld(uid, x298_previousPos, GetTranslation(),\n                                                  (GetTranslation() - x298_previousPos).magnitude(), nearList, mgr);\n  if (res.IsInvalid()) {\n    UpdateProjectileMovement(dt, mgr);\n  } else {\n    x3d0_25_ = false;\n    if (uid == kInvalidUniqueId || uid != x2c0_homingTargetId) {\n      x2c0_homingTargetId = kInvalidUniqueId;\n    } else {\n      x3d0_26_trackingTarget = true;\n      CSfxManager::AddEmitter(SFXsfx06FF, res.GetPoint(), zeus::skZero3f, true, false, 255, kInvalidAreaId);\n    }\n  }\n\n  return res;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CWaveBuster.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/CRandom16.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Weapon/CGameProjectile.hpp\"\n\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\n\nclass CWaveBuster : public CGameProjectile {\n  zeus::CTransform x2e8_originalXf;\n  // Wavebuster is rendered as a cubic bezier\n  // points A & D are at the arm cannon and endpoint/target respectively\n  zeus::CVector3f x318_bezierB;\n  zeus::CVector3f x324_bezierC;\n  zeus::CVector3f x330_;\n  zeus::CVector3f x33c_homingTargetPoint;\n  zeus::CVector3f x348_targetPoint;\n  TCachedToken<CSwooshDescription> x354_busterSwoosh1;\n  TCachedToken<CSwooshDescription> x360_busterSwoosh2;\n  TCachedToken<CGenDescription> x36c_busterSparks;\n  TCachedToken<CGenDescription> x378_busterLight;\n  std::unique_ptr<CParticleSwoosh> x384_busterSwoosh1Gen;\n  std::unique_ptr<CParticleSwoosh> x388_busterSwoosh2Gen;\n  std::unique_ptr<CElementGen> x38c_busterSparksGen;\n  std::unique_ptr<CElementGen> x390_busterLightGen;\n  CRandom16 x394_rand{99};\n  float x398_spiralOffset = 2.f * M_PIF;\n  float x39c_ = 0.5f;\n  float x3a0_ = 0.5f;\n  float x3a4_ = 0.f;\n  float x3a8_ = 0.f;\n  float x3ac_ = 0.f;\n  float x3b0_ = 0.f;\n  float x3b4_ = 0.f;\n  float x3b8_ = 0.f;\n  float x3bc_ = 0.f;\n  float x3c0_ = 0.f;\n  float x3c4_ = 0.f;\n  float x3c8_innerSwooshColorT = 0.f;\n  u32 x3cc_innerSwooshColorIdx = 0;\n  bool x3d0_24_firing : 1 = true;\n  bool x3d0_25_ : 1 = true;\n  bool x3d0_26_trackingTarget : 1 = false;\n  bool x3d0_27_ : 1 = false;\n  bool x3d0_28_ : 1 = true;\n\n  void RenderParticles();\n  void RenderBeam();\n  CRayCastResult SeekDamageTarget(TUniqueId& uid, const zeus::CVector3f& pos, const zeus::CVector3f& dir, CStateManager& mgr,\n                              float dt);\n  bool ApplyDamageToTarget(TUniqueId damagee, const CRayCastResult& actRes, const CRayCastResult& physRes,\n                    const CRayCastResult& selfRes, CStateManager& mgr, float dt);\n  [[nodiscard]] float GetViewAngleToTarget(zeus::CVector3f& p1, const CActor& act);\n  void UpdateTargetSeek(float dt, CStateManager& mgr);\n  void UpdateTargetDamage(float dt, CStateManager& mgr);\n  bool UpdateBeamFrame(CStateManager& mgr, float dt);\n  void RayCastTarget(CStateManager& mgr, TUniqueId& physId, TUniqueId& actId, const zeus::CVector3f& start,\n                    const zeus::CVector3f& end, float length, CRayCastResult& physRes, CRayCastResult& actorRes);\n  CRayCastResult SeekTarget(float dt, TUniqueId& uid, CStateManager& mgr);\n\npublic:\n  DEFINE_ENTITY\n  CWaveBuster(const TToken<CWeaponDescription>& desc, EWeaponType type, const zeus::CTransform& xf,\n              EMaterialTypes matType, const CDamageInfo& dInfo, TUniqueId uid, TAreaId aid, TUniqueId owner,\n              TUniqueId homingTarget, EProjectileAttrib attrib);\n  void Accept(IVisitor& visitor) override;\n  void Think(float dt, CStateManager& mgr) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) override;\n  void AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) override;\n  void Render(CStateManager& mgr) override;\n  std::optional<zeus::CAABox> GetTouchBounds() const override;\n  bool IsFiring() const { return x3d0_24_firing; }\n  void UpdateFx(const zeus::CTransform& xf, float dt, CStateManager& mgr);\n  void ResetBeam(bool deactivate);\n  void SetNewTarget(TUniqueId id, CStateManager& mgr);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CWeapon.cpp",
    "content": "#include \"Runtime/Weapon/CWeapon.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/World/CScriptWater.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCWeapon::CWeapon(TUniqueId uid, TAreaId aid, bool active, TUniqueId owner, EWeaponType type, std::string_view name,\n                 const zeus::CTransform& xf, const CMaterialFilter& filter, const CMaterialList& mList,\n                 const CDamageInfo& dInfo, EProjectileAttrib attribs, CModelData&& mData)\n: CActor(uid, active, name, CEntityInfo(aid, CEntity::NullConnectionList), xf, std::move(mData), mList,\n         CActorParameters::None().HotInThermal(true), kInvalidUniqueId)\n, xe8_projectileAttribs(attribs)\n, xec_ownerId(owner)\n, xf0_weaponType(type)\n, xf8_filter(filter)\n, x110_origDamageInfo(dInfo)\n, x12c_curDamageInfo(dInfo) {}\n\nvoid CWeapon::Accept(metaforce::IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CWeapon::Think(float dt, CStateManager& mgr) {\n  x148_curTime += dt;\n  if ((xe8_projectileAttribs & EProjectileAttrib::DamageFalloff) == EProjectileAttrib::DamageFalloff) {\n    float damMul = std::max(0.f, 1.f - x148_curTime * x14c_damageFalloffSpeed);\n    x12c_curDamageInfo.SetDamage(x110_origDamageInfo.GetDamage() * damMul);\n    x12c_curDamageInfo.SetRadius(x110_origDamageInfo.GetRadius() * damMul);\n    x12c_curDamageInfo.SetKnockBackPower(x110_origDamageInfo.GetKnockBackPower() * damMul);\n    x12c_curDamageInfo.SetWeaponMode(x110_origDamageInfo.GetWeaponMode());\n    x12c_curDamageInfo.SetNoImmunity(false);\n  } else {\n    x12c_curDamageInfo = x110_origDamageInfo;\n  }\n  CEntity::Think(dt, mgr);\n}\n\nvoid CWeapon::Render(CStateManager&) {\n  // Empty\n}\n\nEWeaponCollisionResponseTypes CWeapon::GetCollisionResponseType(const zeus::CVector3f&, const zeus::CVector3f&,\n                                                                const CWeaponMode&, EProjectileAttrib) const {\n  return EWeaponCollisionResponseTypes::Projectile;\n}\n\nvoid CWeapon::FluidFXThink(EFluidState state, CScriptWater& water, CStateManager& mgr) {\n  bool doRipple = true;\n  float mag = 0.f;\n  switch (xf0_weaponType) {\n  case EWeaponType::Power:\n    mag = 0.1f;\n    break;\n  case EWeaponType::Ice:\n    mag = 0.3f;\n    break;\n  case EWeaponType::Wave:\n    mag = 0.1f;\n    break;\n  case EWeaponType::Plasma:\n    break;\n  case EWeaponType::Missile:\n    mag = 0.5f;\n    break;\n  case EWeaponType::Phazon:\n    mag = 0.1f;\n    break;\n  default:\n    doRipple = false;\n    break;\n  }\n\n  if (True(xe8_projectileAttribs & EProjectileAttrib::ComboShot) && state != EFluidState::InFluid)\n    mag += 0.5f;\n  if (True(xe8_projectileAttribs & EProjectileAttrib::Charged))\n    mag += 0.25f;\n  if (mag > 1.f)\n    mag = 1.f;\n\n  if (doRipple) {\n    zeus::CVector3f pos = GetTranslation();\n    pos.z() = float(water.GetTriggerBoundsWR().max.z());\n    if (True(xe8_projectileAttribs & EProjectileAttrib::ComboShot)) {\n      if (!water.CanRippleAtPoint(pos))\n        doRipple = false;\n    } else if (state == EFluidState::InFluid) {\n      doRipple = false;\n    }\n\n    if (doRipple) {\n      water.GetFluidPlane().AddRipple(mag, x8_uid, pos, water, mgr);\n      mgr.GetFluidPlaneManager()->CreateSplash(x8_uid, mgr, water, pos, mag,\n                                               state == EFluidState::EnteredFluid || state == EFluidState::LeftFluid);\n    }\n  }\n}\n\nvoid CWeapon::SetDamageFalloffSpeed(float d) {\n  if (d <= 0.f) {\n    return;\n  }\n\n  x14c_damageFalloffSpeed = 1.f / d;\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CWeapon.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Collision/CMaterialFilter.hpp\"\n#include \"Runtime/World/CActor.hpp\"\n#include \"Runtime/World/CDamageInfo.hpp\"\n#include \"Runtime/Weapon/WeaponCommon.hpp\"\n\nnamespace metaforce {\nclass CWeapon : public CActor {\nprotected:\n  EProjectileAttrib xe8_projectileAttribs;\n  TUniqueId xec_ownerId;\n  EWeaponType xf0_weaponType;\n  CMaterialFilter xf8_filter;\n  CDamageInfo x110_origDamageInfo;\n  CDamageInfo x12c_curDamageInfo;\n  float x148_curTime = 0.f;\n  float x14c_damageFalloffSpeed = 0.f;\n  float x150_damageDuration = 0.f;\n  float x154_interferenceDuration = 0.f;\n\npublic:\n  DEFINE_ENTITY\n  CWeapon(TUniqueId uid, TAreaId aid, bool active, TUniqueId owner, EWeaponType type, std::string_view name,\n          const zeus::CTransform& xf, const CMaterialFilter& filter, const CMaterialList& mList, const CDamageInfo&,\n          EProjectileAttrib attribs, CModelData&& mData);\n\n  void Accept(IVisitor& visitor) override;\n  bool HasAttrib(EProjectileAttrib attrib) const { return (int(xe8_projectileAttribs) & int(attrib)) == int(attrib); }\n  EProjectileAttrib GetAttribField() const { return xe8_projectileAttribs; }\n  void AddAttrib(EProjectileAttrib attrib) { xe8_projectileAttribs |= attrib; }\n  const CMaterialFilter& GetFilter() const { return xf8_filter; }\n  void SetFilter(const CMaterialFilter& filter) { xf8_filter = filter; }\n  TUniqueId GetOwnerId() const { return xec_ownerId; }\n  void SetOwnerId(TUniqueId oid) { xec_ownerId = oid; }\n  EWeaponType GetType() const { return xf0_weaponType; }\n  const CDamageInfo& GetDamageInfo() const { return x12c_curDamageInfo; }\n  CDamageInfo& DamageInfo() { return x12c_curDamageInfo; }\n  void SetDamageInfo(const CDamageInfo& dInfo) { x12c_curDamageInfo = dInfo; }\n  float GetDamageDuration() const { return x150_damageDuration; }\n  void SetDamageDuration(float dur) { x150_damageDuration = dur; }\n  float GetInterferenceDuration() const { return x154_interferenceDuration; }\n  void SetInterferenceDuration(float dur) { x154_interferenceDuration = dur; }\n\n  void Think(float, CStateManager&) override;\n  void Render(CStateManager&) override;\n  EWeaponCollisionResponseTypes GetCollisionResponseType(const zeus::CVector3f&, const zeus::CVector3f&,\n                                                         const CWeaponMode&, EProjectileAttrib) const override;\n  void FluidFXThink(EFluidState state, CScriptWater& water, CStateManager& mgr) override;\n\n  void SetDamageFalloffSpeed(float d);\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CWeaponMgr.cpp",
    "content": "#include \"Runtime/Weapon/CWeaponMgr.hpp\"\n\nnamespace metaforce {\n\nvoid CWeaponMgr::Add(TUniqueId uid, EWeaponType type) {\n  auto iter = x0_weapons.emplace(uid, rstl::reserved_vector<s32, 15>()).first;\n  iter->second.resize(15);\n  ++iter->second[size_t(type)];\n}\n\nvoid CWeaponMgr::Remove(TUniqueId uid) {\n  const auto& weapon = x0_weapons[uid];\n\n  s32 totalActive = 0;\n  for (size_t i = 0; i < 10; ++i) {\n    totalActive += weapon[i];\n  }\n\n  if (totalActive != 0) {\n    return;\n  }\n\n  x0_weapons.erase(uid);\n}\n\nvoid CWeaponMgr::IncrCount(TUniqueId uid, EWeaponType type) {\n  if (GetIndex(uid) < 0) {\n    Add(uid, type);\n  } else {\n    x0_weapons[uid][size_t(type)]++;\n  }\n}\n\nvoid CWeaponMgr::DecrCount(TUniqueId uid, EWeaponType type) {\n  if (GetIndex(uid) < 0) {\n    return;\n  }\n\n  auto& weapon = x0_weapons[uid];\n  weapon[size_t(type)]--;\n  if (weapon[size_t(type)] > 0) {\n    return;\n  }\n\n  Remove(uid);\n}\n\ns32 CWeaponMgr::GetNumActive(TUniqueId uid, EWeaponType type) const {\n  if (GetIndex(uid) < 0) {\n    return 0;\n  }\n\n  return x0_weapons.at(uid)[size_t(type)];\n}\n\ns32 CWeaponMgr::GetIndex(TUniqueId uid) const {\n  const auto iter = x0_weapons.find(uid);\n\n  if (iter == x0_weapons.cend()) {\n    return -1;\n  }\n\n  return s32(std::distance(x0_weapons.cbegin(), iter));\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CWeaponMgr.hpp",
    "content": "#pragma once\n\n#include <map>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Weapon/WeaponCommon.hpp\"\n\nnamespace metaforce {\n\nclass CWeaponMgr {\n  std::map<TUniqueId, rstl::reserved_vector<s32, 15>> x0_weapons;\n\npublic:\n  void Add(TUniqueId, EWeaponType);\n  void Remove(TUniqueId);\n  void IncrCount(TUniqueId, EWeaponType);\n  void DecrCount(TUniqueId, EWeaponType);\n  s32 GetNumActive(TUniqueId, EWeaponType) const;\n  s32 GetIndex(TUniqueId) const;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/CWeaponMode.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Weapon/WeaponCommon.hpp\"\n\nnamespace metaforce {\nclass CWeaponMode {\n  EWeaponType x0_weaponType = EWeaponType::None;\n  bool x4_24_charged : 1 = false;\n  bool x4_25_comboed : 1 = false;\n  bool x4_26_instantKill : 1 = false;\n\npublic:\n  constexpr CWeaponMode() = default;\n  constexpr explicit CWeaponMode(EWeaponType type, bool charged = false, bool comboed = false, bool instaKill = false)\n  : x0_weaponType(type), x4_24_charged(charged), x4_25_comboed(comboed), x4_26_instantKill(instaKill) {}\n  constexpr EWeaponType GetType() const { return x0_weaponType; }\n\n  constexpr bool IsCharged() const { return x4_24_charged; }\n  constexpr bool IsComboed() const { return x4_25_comboed; }\n  constexpr bool IsInstantKill() const { return x4_26_instantKill; }\n\n  static constexpr CWeaponMode Invalid() { return CWeaponMode(EWeaponType::None); }\n  static constexpr CWeaponMode Phazon() { return CWeaponMode(EWeaponType::Phazon); }\n  static constexpr CWeaponMode Plasma() { return CWeaponMode(EWeaponType::Plasma); }\n  static constexpr CWeaponMode Wave() { return CWeaponMode(EWeaponType::Wave); }\n  static constexpr CWeaponMode BoostBall() { return CWeaponMode(EWeaponType::BoostBall); }\n  static constexpr CWeaponMode Ice() { return CWeaponMode(EWeaponType::Ice); }\n  static constexpr CWeaponMode Power() { return CWeaponMode(EWeaponType::Power); }\n  static constexpr CWeaponMode Bomb() { return CWeaponMode(EWeaponType::Bomb); }\n  static constexpr CWeaponMode PowerBomb() { return CWeaponMode(EWeaponType::PowerBomb); }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/Weapon/WeaponCommon.cpp",
    "content": "#include \"Runtime/Weapon/WeaponCommon.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Audio/CSfxManager.hpp\"\n#include \"Runtime/Character/CAnimData.hpp\"\n#include \"Runtime/Character/CPrimitive.hpp\"\n\nnamespace metaforce::NWeaponTypes {\n\nvoid primitive_set_to_token_vector(const CAnimData& animData, const std::set<CPrimitive>& primSet,\n                                   std::vector<CToken>& tokensOut, bool preLock) {\n  int eventCount = 0;\n  for (const CPrimitive& prim : primSet)\n    if (animData.GetEventResourceIdForAnimResourceId(prim.GetAnimResId()).IsValid())\n      ++eventCount;\n\n  tokensOut.clear();\n  tokensOut.reserve(primSet.size() + eventCount);\n\n  SObjectTag atag{FOURCC('ANIM'), {}};\n  SObjectTag etag{FOURCC('EVNT'), {}};\n  for (const CPrimitive& prim : primSet) {\n    CAssetId eId = animData.GetEventResourceIdForAnimResourceId(prim.GetAnimResId());\n    if (eId.IsValid()) {\n      etag.id = prim.GetAnimResId();\n      tokensOut.push_back(g_SimplePool->GetObj(etag));\n      if (preLock)\n        tokensOut.back().Lock();\n    }\n    atag.id = prim.GetAnimResId();\n    tokensOut.push_back(g_SimplePool->GetObj(atag));\n    if (preLock)\n      tokensOut.back().Lock();\n  }\n}\n\nvoid unlock_tokens(std::vector<CToken>& anims) {\n  for (CToken& tok : anims)\n    tok.Unlock();\n}\n\nvoid lock_tokens(std::vector<CToken>& anims) {\n  for (CToken& tok : anims)\n    tok.Lock();\n}\n\nbool are_tokens_ready(const std::vector<CToken>& anims) {\n  for (const CToken& tok : anims)\n    if (!tok.IsLoaded())\n      return false;\n  return true;\n}\n\nvoid get_token_vector(const CAnimData& animData, int begin, int end, std::vector<CToken>& tokensOut, bool preLock) {\n  std::set<CPrimitive> prims;\n  for (int i = begin; i < end; ++i) {\n    CAnimPlaybackParms parms(i, -1, 1.f, true);\n    animData.GetAnimationPrimitives(parms, prims);\n  }\n  primitive_set_to_token_vector(animData, prims, tokensOut, preLock);\n}\n\nvoid get_token_vector(const CAnimData& animData, int animIdx, std::vector<CToken>& tokensOut, bool preLock) {\n  std::set<CPrimitive> prims;\n  CAnimPlaybackParms parms(animIdx, -1, 1.f, true);\n  animData.GetAnimationPrimitives(parms, prims);\n  primitive_set_to_token_vector(animData, prims, tokensOut, preLock);\n}\n\nvoid do_sound_event(std::pair<u16, CSfxHandle>& sfxHandle, float& pitch, bool doPitchBend, u32 soundId, float weight,\n                    u32 flags, float falloff, float maxDist, float minVol, float maxVol,\n                    const zeus::CVector3f& posToCam, const zeus::CVector3f& pos, TAreaId aid, CStateManager& mgr) {\n  if (posToCam.magSquared() >= maxDist * maxDist)\n    return;\n\n  u16 useSfxId = CSfxManager::TranslateSFXID(u16(soundId));\n  u32 useFlags = 0x1; // Continuous parameter update\n  if ((flags & 0x8) != 0)\n    useFlags |= 0x8; // Doppler effect\n  bool useAcoustics = (flags & 0x80) == 0;\n\n  CAudioSys::C3DEmitterParmData parms;\n  parms.x0_pos = pos;\n  parms.xc_dir = zeus::skUp;\n  parms.x18_maxDist = maxDist;\n  parms.x1c_distComp = falloff;\n  parms.x20_flags = useFlags;\n  parms.x24_sfxId = useSfxId;\n  parms.x26_maxVol = maxVol;\n  parms.x27_minVol = minVol;\n  parms.x28_important = false;\n  parms.x29_prio = 0x7f;\n\n  if (mgr.GetActiveRandom()->Float() <= weight) {\n    if ((soundId & 0x80000000) != 0) {\n      if (!sfxHandle.second) {\n        CSfxHandle hnd;\n        if ((soundId & 0x40000000) != 0)\n          hnd = CSfxManager::SfxStart(useSfxId, 1.f, 0.f, true, 0x7f, true, aid);\n        else\n          hnd = CSfxManager::AddEmitter(parms, useAcoustics, 0x7f, true, aid);\n        if (hnd) {\n          sfxHandle.first = useSfxId;\n          sfxHandle.second = hnd;\n          if (doPitchBend)\n            CSfxManager::PitchBend(hnd, pitch);\n        }\n      } else {\n        if (sfxHandle.first == useSfxId) {\n          CSfxManager::UpdateEmitter(sfxHandle.second, parms.x0_pos, parms.xc_dir, parms.x26_maxVol);\n        } else if ((flags & 0x4) != 0) // Pausable\n        {\n          CSfxManager::RemoveEmitter(sfxHandle.second);\n          CSfxHandle hnd = CSfxManager::AddEmitter(parms, useAcoustics, 0x7f, true, aid);\n          if (hnd) {\n            sfxHandle.first = useSfxId;\n            sfxHandle.second = hnd;\n            if (doPitchBend)\n              CSfxManager::PitchBend(hnd, pitch);\n          }\n        }\n      }\n    } else {\n      CSfxHandle hnd;\n      if ((soundId & 0x40000000) != 0)\n        hnd = CSfxManager::SfxStart(useSfxId, 1.f, 0.f, true, 0x7f, false, aid);\n      else\n        hnd = CSfxManager::AddEmitter(parms, useAcoustics, 0x7f, false, aid);\n      if (doPitchBend)\n        CSfxManager::PitchBend(hnd, pitch);\n    }\n  }\n}\n\nCAssetId get_asset_id_from_name(std::string_view name) {\n  const SObjectTag* tag = g_ResFactory->GetResourceIdByName(name);\n  if (!tag) {\n    return {};\n  }\n  return tag->id;\n}\n\nCPlayerState::EPlayerSuit get_current_suit(const CStateManager& mgr) {\n  CPlayerState::EPlayerSuit suit = mgr.GetPlayerState()->GetCurrentSuit();\n  if (suit < CPlayerState::EPlayerSuit::Power || suit > CPlayerState::EPlayerSuit::FusionGravity)\n    suit = CPlayerState::EPlayerSuit::Power;\n  if (suit == CPlayerState::EPlayerSuit::FusionPower)\n    suit = CPlayerState::EPlayerSuit(int(suit) + int(mgr.GetPlayerState()->GetCurrentSuitRaw()));\n  return suit;\n}\n\nCSfxHandle play_sfx(u16 sfx, bool underwater, bool looped, float pan) {\n  CSfxHandle hnd = CSfxManager::SfxStart(sfx, 1.f, pan, true, 0x7f, looped, kInvalidAreaId);\n  CSfxManager::SfxSpan(hnd, 0.f);\n  if (underwater)\n    CSfxManager::PitchBend(hnd, -1.f);\n  return hnd;\n}\n\n} // namespace metaforce::NWeaponTypes\n"
  },
  {
    "path": "Runtime/Weapon/WeaponCommon.hpp",
    "content": "#pragma once\n\n#include <set>\n#include <string_view>\n#include <vector>\n\n#include \"Runtime/Tweaks/ITweakPlayerGun.hpp\"\n\n#include \"Runtime/CPlayerState.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Audio/CSfxManager.hpp\"\n\nnamespace metaforce {\nclass CAnimData;\nclass CPrimitive;\nclass CStateManager;\nclass CToken;\n\nenum class EWeaponType {\n  None = -1,\n  Power = 0,\n  Ice = 1,\n  Wave = 2,\n  Plasma = 3,\n  Bomb = 4,\n  PowerBomb = 5,\n  Missile = 6,\n  BoostBall = 7,\n  Phazon = 8,\n  AI = 9,\n  PoisonWater = 10,\n  Lava = 11,\n  Heat = 12,\n  Unused1 = 13,\n  OrangePhazon = 14,\n  Max = 15\n};\n\nenum class EProjectileAttrib {\n  None = 0,\n  PartialCharge = (1 << 0),\n  PlasmaProjectile = (1 << 1),\n  Charged = (1 << 2),\n  Ice = (1 << 3),\n  Wave = (1 << 4),\n  Plasma = (1 << 5),\n  Phazon = (1 << 6),\n  ComboShot = (1 << 7),\n  Bombs = (1 << 8),\n  PowerBombs = (1 << 9),\n  BigProjectile = (1 << 10),\n  ArmCannon = (1 << 11),\n  BigStrike = (1 << 12),\n  DamageFalloff = (1 << 13),\n  StaticInterference = (1 << 14),\n  PlayerUnFreeze = (1 << 15),\n  ParticleOPTS = (1 << 16),\n  KeepInCinematic = (1 << 17),\n};\nENABLE_BITWISE_ENUM(EProjectileAttrib)\n\nnamespace NWeaponTypes {\n\nenum class EGunAnimType {\n  BasePosition,\n  Shoot,\n  ChargeUp,\n  ChargeLoop,\n  ChargeShoot,\n  FromMissile,\n  ToMissile,\n  MissileShoot,\n  MissileReload,\n  FromBeam,\n  ToBeam\n};\n\nvoid primitive_set_to_token_vector(const CAnimData& animData, const std::set<CPrimitive>& primSet,\n                                   std::vector<CToken>& tokensOut, bool preLock);\nvoid unlock_tokens(std::vector<CToken>& anims);\nvoid lock_tokens(std::vector<CToken>& anims);\nbool are_tokens_ready(const std::vector<CToken>& anims);\nvoid get_token_vector(const CAnimData& animData, int begin, int end, std::vector<CToken>& tokensOut, bool preLock);\nvoid get_token_vector(const CAnimData& animData, int animIdx, std::vector<CToken>& tokensOut, bool preLock);\nvoid do_sound_event(std::pair<u16, CSfxHandle>& sfxHandle, float& pitch, bool doPitchBend, u32 soundId, float weight,\n                    u32 flags, float falloff, float maxDist, float minVol, float maxVol,\n                    const zeus::CVector3f& posToCam, const zeus::CVector3f& pos, TAreaId aid, CStateManager& mgr);\nCAssetId get_asset_id_from_name(std::string_view name);\nCPlayerState::EPlayerSuit get_current_suit(const CStateManager& mgr);\nCSfxHandle play_sfx(u16 sfx, bool underwater, bool looped, float pan);\n\n} // namespace NWeaponTypes\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CActor.cpp",
    "content": "#include \"Runtime/World/CActor.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/CTimeProvider.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Audio/CSfxManager.hpp\"\n#include \"Runtime/Camera/CGameCamera.hpp\"\n#include \"Runtime/Character/CActorLights.hpp\"\n#include \"Runtime/Character/IAnimReader.hpp\"\n#include \"Runtime/Collision/CMaterialList.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Graphics/CSkinnedModel.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\n#include \"ConsoleVariables/CVarManager.hpp\"\n\nnamespace metaforce {\nstatic CMaterialList MakeActorMaterialList(const CMaterialList& materialList, const CActorParameters& params) {\n  CMaterialList ret = materialList;\n  if (params.GetVisorParameters().x0_4_b1)\n    ret.Add(EMaterialTypes::Unknown46);\n  if (params.GetVisorParameters().x0_5_scanPassthrough)\n    ret.Add(EMaterialTypes::ScanPassthrough);\n  return ret;\n}\n\nCActor::CActor(TUniqueId uid, bool active, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n               CModelData&& mData, const CMaterialList& list, const CActorParameters& params, TUniqueId otherUid)\n: CEntity(uid, info, active, name)\n, x34_transform(xf)\n, x68_material(MakeActorMaterialList(list, params))\n, x70_materialFilter(CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {0ull}))\n, xc6_nextDrawNode(otherUid)\n, xe5_29_globalTimeProvider(params.x58_24_globalTimeProvider)\n, xe5_30_renderUnsorted(params.x58_26_renderUnsorted)\n, xe6_27_thermalVisorFlags(u8(params.x58_25_thermalHeat ? 2 : 1))\n, xe6_31_targetableVisorFlags(params.GetVisorParameters().GetMask())\n, xe7_29_drawEnabled(active) {\n  x90_actorLights = mData.IsNull() ? nullptr : params.x0_lightParms.MakeActorLights();\n  if (mData.x10_animData || mData.x1c_normalModel)\n    x64_modelData = std::make_unique<CModelData>(std::move(mData));\n  xd0_damageMag = params.x64_thermalMag;\n  xd8_nonLoopingSfxHandles.resize(2);\n\n  if (x64_modelData) {\n    if (params.x44_xrayAssets.first.IsValid())\n      x64_modelData->SetXRayModel(params.x44_xrayAssets);\n    if (params.x4c_thermalAssets.first.IsValid())\n      x64_modelData->SetInfraModel(params.x4c_thermalAssets);\n    if (!params.x0_lightParms.x1c_makeLights || params.x0_lightParms.x3c_maxAreaLights == 0)\n      x64_modelData->x18_ambientColor = params.x0_lightParms.x18_noLightsAmbient;\n    x64_modelData->x14_25_sortThermal = !params.x58_27_noSortThermal;\n  }\n\n  if (params.x40_scanParms.GetScanId().IsValid())\n    x98_scanObjectInfo = g_SimplePool->GetObj(SObjectTag{FOURCC('SCAN'), params.x40_scanParms.GetScanId()});\n}\n\nCActor::~CActor() { RemoveEmitter(); }\n\nvoid CActor::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  switch (msg) {\n  case EScriptObjectMessage::Activate: {\n    if (!x30_24_active)\n      xbc_time = CGraphics::GetSecondsMod900();\n    break;\n  }\n  case EScriptObjectMessage::Deactivate:\n    RemoveEmitter();\n    break;\n  case EScriptObjectMessage::Deleted: // 34\n  {\n    RemoveEmitter();\n    if (HasModelData() && !x64_modelData->IsNull())\n      if (CAnimData* aData = x64_modelData->GetAnimationData())\n        aData->GetParticleDB().DeleteAllLights(mgr);\n    break;\n  }\n  case EScriptObjectMessage::Registered: // 33\n  {\n    if (x98_scanObjectInfo)\n      AddMaterial(EMaterialTypes::Scannable, mgr);\n    else\n      RemoveMaterial(EMaterialTypes::Scannable, mgr);\n\n    if (HasModelData() && x64_modelData->HasAnimData()) {\n      TAreaId aid = GetAreaId();\n      x64_modelData->GetAnimationData()->InitializeEffects(mgr, aid, x64_modelData->GetScale());\n    }\n    break;\n  }\n  case EScriptObjectMessage::AddSplashInhabitant: // 37\n    SetInFluid(true, uid);\n    break;\n  case EScriptObjectMessage::RemoveSplashInhabitant: // 39\n    SetInFluid(false, uid);\n    break;\n  case EScriptObjectMessage::InitializedInArea: // 35\n  {\n    for (const SConnection& conn : x20_conns) {\n      if (conn.x0_state != EScriptObjectState::Default)\n        continue;\n\n      const CActor* act = TCastToConstPtr<CActor>(mgr.GetObjectById(mgr.GetIdForScript(conn.x8_objId)));\n      if (act && xc6_nextDrawNode == kInvalidUniqueId)\n        xc6_nextDrawNode = act->GetUniqueId();\n    }\n    break;\n  }\n  default:\n    break;\n  }\n  CEntity::AcceptScriptMsg(msg, uid, mgr);\n}\n\nvoid CActor::PreRender(CStateManager& mgr, const zeus::CFrustum& planes) {\n  if (!x64_modelData || x64_modelData->IsNull())\n    return;\n\n  xe4_30_outOfFrustum = !planes.aabbFrustumTest(x9c_renderBounds);\n  if (!xe4_30_outOfFrustum) {\n    bool lightsDirty = false;\n    if (xe4_29_actorLightsDirty) {\n      xe4_29_actorLightsDirty = false;\n      lightsDirty = true;\n      xe5_25_shadowDirty = true;\n    } else if (xe7_28_worldLightingDirty) {\n      lightsDirty = true;\n    } else if (x90_actorLights && x90_actorLights->GetIsDirty()) {\n      lightsDirty = true;\n    }\n\n    if (xe5_25_shadowDirty && xe5_24_shadowEnabled && x94_simpleShadow) {\n      x94_simpleShadow->Calculate(x64_modelData->GetBounds(), x34_transform, mgr);\n      xe5_25_shadowDirty = false;\n    }\n\n    if (xe4_31_calculateLighting && x90_actorLights) {\n      zeus::CAABox bounds = x64_modelData->GetBounds(x34_transform);\n      if (mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::Thermal) {\n        x90_actorLights->BuildConstantAmbientLighting();\n      } else {\n        if (lightsDirty && x4_areaId != kInvalidAreaId) {\n          const CGameArea* area = mgr.GetWorld()->GetAreaAlways(x4_areaId);\n          if (area->IsPostConstructed())\n            if (x90_actorLights->BuildAreaLightList(mgr, *area, bounds))\n              xe7_28_worldLightingDirty = false;\n        }\n        x90_actorLights->BuildDynamicLightList(mgr, bounds);\n      }\n    }\n\n    if (x64_modelData->HasAnimData())\n      x64_modelData->GetAnimationData()->PreRender();\n  } else {\n    if (xe4_29_actorLightsDirty) {\n      xe4_29_actorLightsDirty = false;\n      xe5_25_shadowDirty = true;\n    }\n\n    if (xe5_25_shadowDirty && xe5_24_shadowEnabled && x94_simpleShadow) {\n      zeus::CAABox bounds = x64_modelData->GetBounds(x34_transform);\n      if (planes.aabbFrustumTest(x94_simpleShadow->GetMaxShadowBox(bounds))) {\n        x94_simpleShadow->Calculate(x64_modelData->GetBounds(), x34_transform, mgr);\n        xe5_25_shadowDirty = false;\n      }\n    }\n  }\n}\n\nvoid CActor::AddToRenderer(const zeus::CFrustum& planes, CStateManager& mgr) {\n  if (!x64_modelData || x64_modelData->IsNull()) {\n    return;\n  }\n\n  if (xe6_29_renderParticleDBInside) {\n    x64_modelData->RenderParticles(planes);\n  }\n\n  if (!xe4_30_outOfFrustum) {\n    if (CanRenderUnsorted(mgr)) {\n      Render(mgr);\n    } else {\n      EnsureRendered(mgr);\n    }\n  }\n\n  if (mgr.GetPlayerState()->GetActiveVisor(mgr) != CPlayerState::EPlayerVisor::XRay &&\n      mgr.GetPlayerState()->GetActiveVisor(mgr) != CPlayerState::EPlayerVisor::Thermal && xe5_24_shadowEnabled &&\n      x94_simpleShadow->Valid() && planes.aabbFrustumTest(x94_simpleShadow->GetBounds())) {\n    g_Renderer->AddDrawable(x94_simpleShadow.get(), x94_simpleShadow->GetTransform().origin,\n                            x94_simpleShadow->GetBounds(), 1, CCubeRenderer::EDrawableSorting::SortedCallback);\n  }\n}\n\nvoid CActor::DrawTouchBounds() {\n  // Empty in retail\n  if (m_debugHovered || m_debugSelected) {\n    // auto aabox = GetTouchBounds();\n    // if (aabox) {\n    //   if (!m_actorDebugRender) {\n    //     m_actorDebugRender = CAABoxShader();\n    //   }\n    //   m_actorDebugRender->setAABB(*aabox);\n    //   m_actorDebugRender->draw(m_debugAddColor);\n    // }\n  }\n}\n\nvoid CActor::RenderInternal(const CStateManager& mgr) const {\n  SCOPED_GRAPHICS_DEBUG_GROUP(fmt::format(\"CActor::RenderInternal {} {} {}\", x8_uid, xc_editorId, x10_name).c_str(),\n                              zeus::skOrange);\n\n  CModelData::EWhichModel which = CModelData::GetRenderingModel(mgr);\n  if (which == CModelData::EWhichModel::ThermalHot) {\n    if (x64_modelData->GetSortThermal()) {\n      float addMag;\n      float mulMag = 1.f;\n      if (xd0_damageMag <= 1.f) {\n        mulMag = xd0_damageMag;\n        addMag = 0.f;\n      } else if (xd0_damageMag < 2.f) {\n        addMag = xd0_damageMag - 1.f;\n      } else {\n        addMag = 1.f;\n      }\n\n      zeus::CColor mulColor(mulMag * xb4_drawFlags.x4_color.a(), xb4_drawFlags.x4_color.a());\n      zeus::CColor addColor(addMag, xb4_drawFlags.x4_color.a() / 4.f);\n      if (m_debugSelected || m_debugHovered) {\n        addColor += m_debugAddColor;\n      }\n      x64_modelData->RenderThermal(x34_transform, mulColor, addColor, xb4_drawFlags);\n      return;\n    } else if (mgr.GetThermalColdScale2() > 0.00001f && !xb4_drawFlags.x0_blendMode) {\n      zeus::CColor color(\n          zeus::clamp(0.f,\n                      std::min((mgr.GetThermalColdScale2() + mgr.GetThermalColdScale1()) * mgr.GetThermalColdScale2(),\n                               mgr.GetThermalColdScale2()),\n                      1.f),\n          1.f);\n      CModelFlags flags(2, xb4_drawFlags.x1_matSetIdx, xb4_drawFlags.x2_flags, color);\n      if (m_debugSelected || m_debugHovered) {\n        flags.x4_color += m_debugAddColor;\n      }\n      x64_modelData->Render(mgr, x34_transform, x90_actorLights.get(), flags);\n      return;\n    }\n  }\n  CModelFlags flags = xb4_drawFlags;\n  if (m_debugSelected || m_debugHovered) {\n    if (flags.x0_blendMode == 0) {\n      flags.x0_blendMode = 2;\n      flags.x4_color = m_debugAddColor;\n    } else {\n      flags.x4_color += m_debugAddColor;\n    }\n  }\n  x64_modelData->Render(which, x34_transform, x90_actorLights.get(), flags);\n}\n\nbool CActor::IsModelOpaque(const CStateManager& mgr) const {\n  if (xe5_31_pointGeneratorParticles)\n    return false;\n  if (!x64_modelData || x64_modelData->IsNull())\n    return true;\n  if (xb4_drawFlags.x0_blendMode > 4)\n    return false;\n  return x64_modelData->IsDefinitelyOpaque(CModelData::GetRenderingModel(mgr));\n}\n\nvoid CActor::Render(CStateManager& mgr) {\n  if (x64_modelData && !x64_modelData->IsNull()) {\n    bool renderPrePostParticles = xe6_29_renderParticleDBInside && x64_modelData && x64_modelData->HasAnimData();\n    if (renderPrePostParticles)\n      x64_modelData->GetAnimationData()->GetParticleDB().RenderSystemsToBeDrawnFirst();\n\n    if (xe7_27_enableRender) {\n      if (xe5_31_pointGeneratorParticles)\n        mgr.SetupParticleHook(*this);\n      if (xe5_29_globalTimeProvider) {\n        RenderInternal(mgr);\n      } else {\n        const float timeSince = CGraphics::GetSecondsMod900() - xbc_time;\n        const float tpTime = timeSince - std::floor(timeSince / 900.f) * 900.f;\n        CTimeProvider tp(tpTime);\n        RenderInternal(mgr);\n      }\n      if (xe5_31_pointGeneratorParticles) {\n        CSkinnedModel::ClearPointGeneratorFunc();\n        mgr.GetActorModelParticles()->Render(mgr, *this);\n      }\n    }\n\n    if (renderPrePostParticles)\n      x64_modelData->GetAnimationData()->GetParticleDB().RenderSystemsToBeDrawnLast();\n  }\n  DrawTouchBounds();\n}\n\nbool CActor::CanRenderUnsorted(const CStateManager& mgr) const {\n  if (x64_modelData && x64_modelData->HasAnimData() &&\n      x64_modelData->GetAnimationData()->GetParticleDB().AreAnySystemsDrawnWithModel() && xe6_29_renderParticleDBInside)\n    return false;\n  else if (xe5_30_renderUnsorted || IsModelOpaque(mgr))\n    return true;\n  return false;\n}\n\nvoid CActor::CalculateRenderBounds() {\n  if (x64_modelData && (x64_modelData->GetAnimationData() || x64_modelData->GetNormalModel()))\n    x9c_renderBounds = x64_modelData->GetBounds(x34_transform);\n  else\n    x9c_renderBounds = zeus::CAABox(x34_transform.origin, x34_transform.origin);\n}\n\nCHealthInfo* CActor::HealthInfo(CStateManager&) { return nullptr; }\n\nconst CDamageVulnerability* CActor::GetDamageVulnerability() const {\n  return &CDamageVulnerability::NormalVulnerabilty();\n}\n\nconst CDamageVulnerability* CActor::GetDamageVulnerability(const zeus::CVector3f&, const zeus::CVector3f&,\n                                                           const CDamageInfo&) const {\n  return GetDamageVulnerability();\n}\n\nstd::optional<zeus::CAABox> CActor::GetTouchBounds() const { return {}; }\n\nvoid CActor::Touch(CActor&, CStateManager&) {}\n\nzeus::CVector3f CActor::GetOrbitPosition(const CStateManager&) const { return x34_transform.origin; }\n\nzeus::CVector3f CActor::GetAimPosition(const CStateManager&, float) const { return x34_transform.origin; }\n\nzeus::CVector3f CActor::GetHomingPosition(const CStateManager& mgr, float f) const { return GetAimPosition(mgr, f); }\n\nzeus::CVector3f CActor::GetScanObjectIndicatorPosition(const CStateManager& mgr) const {\n  const CGameCamera* cam = mgr.GetCameraManager()->GetCurrentCamera(mgr);\n  zeus::CVector3f orbitPos = GetOrbitPosition(mgr);\n  float camToOrbitPos = (cam->GetTranslation() - orbitPos).magnitude();\n  const zeus::CVector3f boundsExtent = x9c_renderBounds.max - x9c_renderBounds.min;\n  float distFac = std::min(std::max(boundsExtent.x(), std::max(boundsExtent.y(), boundsExtent.z())) * 0.5f,\n                           camToOrbitPos - cam->GetNearClipDistance() - 0.1f);\n  return orbitPos - (orbitPos - cam->GetTranslation()).normalized() * distFac;\n}\n\nvoid CActor::RemoveEmitter() {\n  if (x8c_loopingSfxHandle) {\n    CSfxManager::RemoveEmitter(x8c_loopingSfxHandle);\n    x88_sfxId = -1;\n    x8c_loopingSfxHandle.reset();\n  }\n}\n\nvoid CActor::SetVolume(float vol) {\n  if (x8c_loopingSfxHandle)\n    CSfxManager::UpdateEmitter(x8c_loopingSfxHandle, GetTranslation(), zeus::skZero3f, vol);\n  xd4_maxVol = vol;\n}\n\nzeus::CTransform CActor::GetScaledLocatorTransform(std::string_view segName) const {\n  return x64_modelData->GetScaledLocatorTransform(segName);\n}\n\nzeus::CTransform CActor::GetLocatorTransform(std::string_view segName) const {\n  return x64_modelData->GetLocatorTransform(segName);\n}\n\nEWeaponCollisionResponseTypes CActor::GetCollisionResponseType(const zeus::CVector3f&, const zeus::CVector3f&,\n                                                               const CWeaponMode&, EProjectileAttrib) const {\n  return EWeaponCollisionResponseTypes::OtherProjectile;\n}\n\nvoid CActor::FluidFXThink(CActor::EFluidState, CScriptWater&, CStateManager&) {}\n\nvoid CActor::OnScanStateChanged(EScanState state, CStateManager& mgr) {\n  if (state == EScanState::Start)\n    SendScriptMsgs(EScriptObjectState::ScanStart, mgr, EScriptObjectMessage::None);\n  else if (state == EScanState::Processing)\n    SendScriptMsgs(EScriptObjectState::ScanProcessing, mgr, EScriptObjectMessage::None);\n  else if (state == EScanState::Done)\n    SendScriptMsgs(EScriptObjectState::ScanDone, mgr, EScriptObjectMessage::None);\n}\n\nzeus::CAABox CActor::GetSortingBounds(const CStateManager&) const { return x9c_renderBounds; }\n\nvoid CActor::DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType event, float dt) {\n  if (event == EUserEventType::LoopedSoundStop)\n    RemoveEmitter();\n}\n\nvoid CActor::RemoveMaterial(EMaterialTypes t1, EMaterialTypes t2, EMaterialTypes t3, EMaterialTypes t4,\n                            CStateManager& mgr) {\n  x68_material.Remove(t1);\n  RemoveMaterial(t2, t3, t4, mgr);\n}\n\nvoid CActor::RemoveMaterial(EMaterialTypes t1, EMaterialTypes t2, EMaterialTypes t3, CStateManager& mgr) {\n  x68_material.Remove(t1);\n  RemoveMaterial(t2, t3, mgr);\n}\n\nvoid CActor::RemoveMaterial(EMaterialTypes t1, EMaterialTypes t2, CStateManager& mgr) {\n  x68_material.Remove(t1);\n  RemoveMaterial(t2, mgr);\n}\n\nvoid CActor::RemoveMaterial(EMaterialTypes t, CStateManager& mgr) {\n  x68_material.Remove(t);\n  mgr.UpdateObjectInLists(*this);\n}\n\nvoid CActor::AddMaterial(EMaterialTypes t1, EMaterialTypes t2, EMaterialTypes t3, EMaterialTypes t4, EMaterialTypes t5,\n                         CStateManager& mgr) {\n  x68_material.Add(t1);\n  AddMaterial(t2, t3, t4, t5, mgr);\n}\n\nvoid CActor::AddMaterial(EMaterialTypes t1, EMaterialTypes t2, EMaterialTypes t3, EMaterialTypes t4,\n                         CStateManager& mgr) {\n  x68_material.Add(t1);\n  AddMaterial(t2, t3, t4, mgr);\n}\n\nvoid CActor::AddMaterial(EMaterialTypes t1, EMaterialTypes t2, EMaterialTypes t3, CStateManager& mgr) {\n  x68_material.Add(t1);\n  AddMaterial(t2, t3, mgr);\n}\n\nvoid CActor::AddMaterial(EMaterialTypes t1, EMaterialTypes t2, CStateManager& mgr) {\n  x68_material.Add(t1);\n  AddMaterial(t2, mgr);\n}\n\nvoid CActor::AddMaterial(EMaterialTypes type, CStateManager& mgr) {\n  x68_material.Add(type);\n  mgr.UpdateObjectInLists(*this);\n}\n\nvoid CActor::AddMaterial(const CMaterialList& l) { x68_material.Add(l); }\n\nvoid CActor::CreateShadow(bool enabled) {\n  if (enabled) {\n    _CreateShadow();\n    if (!xe5_24_shadowEnabled && x94_simpleShadow)\n      xe5_25_shadowDirty = true;\n  }\n  xe5_24_shadowEnabled = enabled;\n}\n\nvoid CActor::_CreateShadow() {\n  if (!x94_simpleShadow && x64_modelData && (x64_modelData->HasAnimData() || x64_modelData->HasNormalModel())) {\n    x94_simpleShadow = std::make_unique<CSimpleShadow>(1.f, 1.f, 20.f, 0.05f);\n  }\n}\n\nvoid CActor::_CreateReflectionCube() {\n  //  if (hecl::com_cubemaps->toBoolean()) {\n  //    CGraphics::CommitResources([this](boo::IGraphicsDataFactory::Context& ctx) {\n  //      m_reflectionCube = ctx.newCubeRenderTexture(CUBEMAP_RES, CUBEMAP_MIPS);\n  //      return true;\n  //    } BooTrace);\n  //  }\n}\n\nvoid CActor::SetCallTouch(bool callTouch) { xe5_28_callTouch = callTouch; }\n\nbool CActor::GetCallTouch() const { return xe5_28_callTouch; }\n\nvoid CActor::SetUseInSortedLists(bool use) { xe5_27_useInSortedLists = use; }\n\nbool CActor::GetUseInSortedLists() const { return xe5_27_useInSortedLists; }\n\nvoid CActor::SetInFluid(bool in, TUniqueId uid) {\n  if (in) {\n    xe6_24_fluidCounter += 1;\n    xc4_fluidId = uid;\n  } else {\n    if (!xe6_24_fluidCounter)\n      return;\n\n    xe6_24_fluidCounter -= 1;\n    if (xe6_24_fluidCounter == 0)\n      xc4_fluidId = kInvalidUniqueId;\n  }\n}\n\nbool CActor::HasModelData() const { return bool(x64_modelData); }\n\nvoid CActor::SetSoundEventPitchBend(s32 val) {\n  xe6_30_enablePitchBend = true;\n  xc0_pitchBend = val / 8192.f - 1.f;\n  if (!x8c_loopingSfxHandle)\n    return;\n\n  CSfxManager::PitchBend(x8c_loopingSfxHandle, xc0_pitchBend);\n}\n\nvoid CActor::SetRotation(const zeus::CQuaternion& q) {\n  x34_transform = q.toTransform(x34_transform.origin);\n  xe4_27_notInSortedLists = true;\n  xe4_28_transformDirty = true;\n  xe4_29_actorLightsDirty = true;\n}\n\nvoid CActor::SetTranslation(const zeus::CVector3f& tr) {\n  x34_transform.origin = tr;\n  xe4_27_notInSortedLists = true;\n  xe4_28_transformDirty = true;\n  xe4_29_actorLightsDirty = true;\n}\n\nvoid CActor::SetTransform(const zeus::CTransform& tr) {\n  x34_transform = tr;\n  xe4_27_notInSortedLists = true;\n  xe4_28_transformDirty = true;\n  xe4_29_actorLightsDirty = true;\n}\n\nvoid CActor::SetAddedToken(u32 tok) { xcc_addedToken = tok; }\n\nfloat CActor::GetPitch() const { return zeus::CQuaternion(x34_transform.buildMatrix3f()).pitch(); }\n\nfloat CActor::GetYaw() const { return zeus::CQuaternion(x34_transform.buildMatrix3f()).yaw(); }\n\nvoid CActor::EnsureRendered(const CStateManager& mgr) {\n  const auto bounds = GetSortingBounds(mgr);\n  EnsureRendered(mgr, bounds.closestPointAlongVector(CGraphics::mViewMatrix.frontVector()), bounds);\n}\n\nvoid CActor::EnsureRendered(const CStateManager& stateMgr, const zeus::CVector3f& pos, const zeus::CAABox& aabb) {\n  if (x64_modelData) {\n    x64_modelData->RenderUnsortedParts(x64_modelData->GetRenderingModel(stateMgr), x34_transform, x90_actorLights.get(),\n                                       xb4_drawFlags);\n  }\n  stateMgr.AddDrawableActor(*this, pos, aabb);\n}\n\nvoid CActor::UpdateSfxEmitters() {\n  for (CSfxHandle& sfx : xd8_nonLoopingSfxHandles)\n    CSfxManager::UpdateEmitter(sfx, x34_transform.origin, zeus::skZero3f, xd4_maxVol);\n}\n\nvoid CActor::ProcessSoundEvent(u32 sfxId, float weight, u32 flags, float falloff, float maxDist, float minVol,\n                               float maxVol, const zeus::CVector3f& toListener, const zeus::CVector3f& position,\n                               TAreaId aid, CStateManager& mgr, bool translateId) {\n  if (toListener.magSquared() >= maxDist * maxDist)\n    return;\n  u16 id = translateId ? CSfxManager::TranslateSFXID(sfxId) : sfxId;\n\n  u32 musyxFlags = 0x1; // Continuous parameter update\n  if (flags & 0x8)\n    musyxFlags |= 0x8; // Doppler FX\n\n  CAudioSys::C3DEmitterParmData parms;\n  parms.x0_pos = position;\n  parms.xc_dir = zeus::skZero3f;\n  parms.x18_maxDist = maxDist;\n  parms.x1c_distComp = falloff;\n  parms.x20_flags = musyxFlags;\n  parms.x24_sfxId = id;\n  parms.x26_maxVol = maxVol;\n  parms.x27_minVol = minVol;\n  parms.x28_important = false;\n  parms.x29_prio = 0x7f;\n\n  bool useAcoustics = (flags & 0x80) == 0;\n  bool looping = (sfxId & 0x80000000) != 0;\n  bool nonEmitter = (sfxId & 0x40000000) != 0;\n  bool continuousUpdate = (sfxId & 0x20000000) != 0;\n\n  if (mgr.GetActiveRandom()->Float() > weight)\n    return;\n\n  if (looping) {\n    u16 curId = x88_sfxId;\n    if (!x8c_loopingSfxHandle) {\n      CSfxHandle handle;\n      if (nonEmitter)\n        handle = CSfxManager::SfxStart(id, 1.f, 0.f, true, 0x7f, true, aid);\n      else\n        handle = CSfxManager::AddEmitter(parms, useAcoustics, 0x7f, true, aid);\n      if (handle) {\n        x88_sfxId = id;\n        x8c_loopingSfxHandle = handle;\n        if (xe6_30_enablePitchBend)\n          CSfxManager::PitchBend(handle, xc0_pitchBend);\n      }\n    } else if (curId == id) {\n      CSfxManager::UpdateEmitter(x8c_loopingSfxHandle, position, zeus::skZero3f, maxVol);\n    } else if (flags & 0x4) {\n      CSfxManager::RemoveEmitter(x8c_loopingSfxHandle);\n      CSfxHandle handle = CSfxManager::AddEmitter(parms, useAcoustics, 0x7f, true, aid);\n      if (handle) {\n        x88_sfxId = id;\n        x8c_loopingSfxHandle = handle;\n        if (xe6_30_enablePitchBend)\n          CSfxManager::PitchBend(handle, xc0_pitchBend);\n      }\n    }\n  } else {\n    CSfxHandle handle;\n    if (nonEmitter)\n      handle = CSfxManager::SfxStart(id, 1.f, 0.f, useAcoustics, 0x7f, false, aid);\n    else\n      handle = CSfxManager::AddEmitter(parms, useAcoustics, 0x7f, false, aid);\n\n    if (continuousUpdate) {\n      xd8_nonLoopingSfxHandles[xe4_24_nextNonLoopingSfxHandle] = handle;\n      xe4_24_nextNonLoopingSfxHandle = (xe4_24_nextNonLoopingSfxHandle + 1) % xd8_nonLoopingSfxHandles.size();\n    }\n\n    if (xe6_30_enablePitchBend)\n      CSfxManager::PitchBend(handle, xc0_pitchBend);\n  }\n}\n\nSAdvancementDeltas CActor::UpdateAnimation(float dt, CStateManager& mgr, bool advTree) {\n  SAdvancementDeltas deltas = x64_modelData->AdvanceAnimation(dt, mgr, GetAreaId(), advTree);\n  x64_modelData->AdvanceParticles(x34_transform, dt, mgr);\n  UpdateSfxEmitters();\n  if (x64_modelData && x64_modelData->HasAnimData()) {\n    zeus::CVector3f toCamera = mgr.GetCameraManager()->GetCurrentCamera(mgr)->GetTranslation() - x34_transform.origin;\n\n    for (int i = 0; i < x64_modelData->GetAnimationData()->GetPassedSoundPOICount(); ++i) {\n      CSoundPOINode& poi = CAnimData::g_SoundPOINodes[i];\n      if (poi.GetPoiType() != EPOIType::Sound)\n        continue;\n      if (xe5_26_muted)\n        continue;\n      if (poi.GetCharacterIndex() != -1 &&\n          x64_modelData->GetAnimationData()->GetCharacterIndex() != poi.GetCharacterIndex())\n        continue;\n      ProcessSoundEvent(poi.GetSfxId(), poi.GetWeight(), poi.GetFlags(), poi.GetFalloff(), poi.GetMaxDist(), 0.16f,\n                        xd4_maxVol, toCamera, x34_transform.origin, x4_areaId, mgr, true);\n    }\n\n    for (int i = 0; i < x64_modelData->GetAnimationData()->GetPassedIntPOICount(); ++i) {\n      CInt32POINode& poi = CAnimData::g_Int32POINodes[i];\n      if (poi.GetPoiType() == EPOIType::SoundInt32) {\n        if (xe5_26_muted)\n          continue;\n        if (poi.GetCharacterIndex() != -1 &&\n            x64_modelData->GetAnimationData()->GetCharacterIndex() != poi.GetCharacterIndex())\n          continue;\n        ProcessSoundEvent(poi.GetValue(), poi.GetWeight(), poi.GetFlags(), 0.1f, 150.f, 0.16f, xd4_maxVol, toCamera,\n                          x34_transform.origin, x4_areaId, mgr, true);\n      } else if (poi.GetPoiType() == EPOIType::UserEvent) {\n        DoUserAnimEvent(mgr, poi, EUserEventType(poi.GetValue()), dt);\n      }\n    }\n\n    for (int i = 0; i < x64_modelData->GetAnimationData()->GetPassedParticlePOICount(); ++i) {\n      CParticlePOINode& poi = CAnimData::g_ParticlePOINodes[i];\n      if (poi.GetCharacterIndex() != -1 &&\n          x64_modelData->GetAnimationData()->GetCharacterIndex() != poi.GetCharacterIndex())\n        continue;\n      x64_modelData->GetAnimationData()->GetParticleDB().SetParticleEffectState(poi.GetString(), true, mgr);\n    }\n  }\n  return deltas;\n}\n\nvoid CActor::SetActorLights(std::unique_ptr<CActorLights>&& lights) {\n  x90_actorLights = std::move(lights);\n  xe4_31_calculateLighting = true;\n}\n\nbool CActor::CanDrawStatic() const {\n  if (!x30_24_active)\n    return false;\n\n  if (x64_modelData && x64_modelData->HasNormalModel())\n    return xb4_drawFlags.x0_blendMode <= 4;\n\n  return false;\n}\n\nconst CScannableObjectInfo* CActor::GetScannableObjectInfo() const {\n  if (!x98_scanObjectInfo || !x98_scanObjectInfo.IsLoaded())\n    return nullptr;\n  return x98_scanObjectInfo.GetObj();\n}\n\nvoid CActor::SetCalculateLighting(bool c) {\n  if (!x90_actorLights)\n    x90_actorLights = std::make_unique<CActorLights>(8, zeus::skZero3f, 4, 4, false, false, false, 0.1f);\n  xe4_31_calculateLighting = c;\n}\n\nfloat CActor::GetAverageAnimVelocity(int anim) const {\n  if (HasModelData() && GetModelData()->HasAnimData())\n    return GetModelData()->GetAnimationData()->GetAverageVelocity(anim);\n  return 0.f;\n}\n\nvoid CActor::SetModelData(std::unique_ptr<CModelData>&& mData) {\n  if (mData->IsNull())\n    x64_modelData.reset();\n  else\n    x64_modelData = std::move(mData);\n}\n\nvoid CActor::SetMuted(bool muted) {\n  xe5_26_muted = muted;\n  RemoveEmitter();\n}\n\nvoid CActor::MoveScannableObjectInfoToActor(CActor* act, CStateManager& mgr) {\n  if (!act)\n    return;\n\n  act->x98_scanObjectInfo = x98_scanObjectInfo;\n  x98_scanObjectInfo = TLockedToken<CScannableObjectInfo>();\n  act->AddMaterial(EMaterialTypes::Scannable, mgr);\n  RemoveMaterial(EMaterialTypes::Scannable, mgr);\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CActor.hpp",
    "content": "#pragma once\n\n#include \"Runtime/CScannableObjectInfo.hpp\"\n#include \"Runtime/Audio/CSfxManager.hpp\"\n#include \"Runtime/Collision/CCollisionResponseData.hpp\"\n#include \"Runtime/Collision/CMaterialFilter.hpp\"\n#include \"Runtime/Character/CActorLights.hpp\"\n#include \"Runtime/Character/CModelData.hpp\"\n#include \"Runtime/Graphics/CGraphics.hpp\"\n#include \"Runtime/Graphics/CSimpleShadow.hpp\"\n#include \"Runtime/Weapon/WeaponCommon.hpp\"\n#include \"Runtime/World/CEntity.hpp\"\n\n#include <zeus/zeus.hpp>\n\nnamespace metaforce {\n\nclass CActorParameters;\nclass CWeaponMode;\nclass CHealthInfo;\nclass CDamageInfo;\nclass CDamageVulnerability;\nclass CLightParameters;\nclass CScriptWater;\nclass CSimpleShadow;\n\nclass CActor : public CEntity {\n  friend class CStateManager;\n  friend class ImGuiConsole;\n\nprotected:\n  zeus::CTransform x34_transform;\n  std::unique_ptr<CModelData> x64_modelData;\n  CMaterialList x68_material;\n  CMaterialFilter x70_materialFilter;\n  s16 x88_sfxId = -1;\n  CSfxHandle x8c_loopingSfxHandle;\n  std::unique_ptr<CActorLights> x90_actorLights;\n  std::unique_ptr<CSimpleShadow> x94_simpleShadow;\n  TLockedToken<CScannableObjectInfo> x98_scanObjectInfo;\n  zeus::CAABox x9c_renderBounds;\n  CModelFlags xb4_drawFlags{0, 0, 3, zeus::skWhite};\n  float xbc_time = 0.f;\n  float xc0_pitchBend = 0.f;\n  TUniqueId xc4_fluidId = kInvalidUniqueId;\n  TUniqueId xc6_nextDrawNode;\n  int xc8_drawnToken = -1;\n  int xcc_addedToken = -1;\n  float xd0_damageMag;\n  float xd4_maxVol = 1.f;\n  rstl::reserved_vector<CSfxHandle, 2> xd8_nonLoopingSfxHandles;\n  u8 xe4_24_nextNonLoopingSfxHandle : 3 = 0;\n  bool xe4_27_notInSortedLists : 1 = true;\n  bool xe4_28_transformDirty : 1 = true;\n  bool xe4_29_actorLightsDirty : 1 = true;\n  bool xe4_30_outOfFrustum : 1 = false;\n  bool xe4_31_calculateLighting : 1 = true;\n  bool xe5_24_shadowEnabled : 1 = false;\n  bool xe5_25_shadowDirty : 1 = false;\n  bool xe5_26_muted : 1 = false;\n  bool xe5_27_useInSortedLists : 1 = true;\n  bool xe5_28_callTouch : 1 = true;\n  bool xe5_29_globalTimeProvider : 1;\n  bool xe5_30_renderUnsorted : 1;\n  bool xe5_31_pointGeneratorParticles : 1 = false;\n  u8 xe6_24_fluidCounter : 3 = 0;\n  u8 xe6_27_thermalVisorFlags : 2; // 1: thermal cold, 2: thermal hot\n  bool xe6_29_renderParticleDBInside : 1 = true;\n  bool xe6_30_enablePitchBend : 1 = false;\n  u8 xe6_31_targetableVisorFlags : 4;\n  bool xe7_27_enableRender : 1 = true;\n  bool xe7_28_worldLightingDirty : 1 = false;\n  bool xe7_29_drawEnabled : 1;\n  bool xe7_30_doTargetDistanceTest : 1 = true;\n  bool xe7_31_targetable : 1 = true;\n\n  // GXTexObj m_reflectionCube;\n  zeus::CColor m_debugAddColor = zeus::skClear;\n  float m_debugAddColorTime = 0.f;\n\n  void _CreateShadow();\n  void _CreateReflectionCube();\n  void UpdateSfxEmitters();\n  void DrawTouchBounds();\n  void RenderInternal(const CStateManager& mgr) const;\n  bool IsModelOpaque(const CStateManager& mgr) const;\n\npublic:\n  enum class EFluidState { EnteredFluid, InFluid, LeftFluid };\n\n  enum class EScanState {\n    Start,\n    Processing,\n    Done,\n  };\n\n  DEFINE_ENTITY\n  CActor(TUniqueId uid, bool active, std::string_view name, const CEntityInfo& info, const zeus::CTransform&,\n         CModelData&& mData, const CMaterialList& list, const CActorParameters& params, TUniqueId otherUid);\n  ~CActor();\n\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void SetActive(bool active) override {\n    xe4_27_notInSortedLists = true;\n    xe4_28_transformDirty = true;\n    xe4_29_actorLightsDirty = true;\n    xe7_29_drawEnabled = active;\n    CEntity::SetActive(active);\n  }\n  virtual void PreRender(CStateManager&, const zeus::CFrustum&);\n  virtual void AddToRenderer(const zeus::CFrustum&, CStateManager&);\n  virtual void Render(CStateManager&);\n  virtual bool CanRenderUnsorted(const CStateManager&) const;\n  virtual void CalculateRenderBounds();\n  virtual CHealthInfo* HealthInfo(CStateManager&);\n  virtual const CDamageVulnerability* GetDamageVulnerability() const;\n  virtual const CDamageVulnerability* GetDamageVulnerability(const zeus::CVector3f&, const zeus::CVector3f&,\n                                                             const CDamageInfo&) const;\n  virtual std::optional<zeus::CAABox> GetTouchBounds() const;\n  virtual void Touch(CActor&, CStateManager&);\n  virtual zeus::CVector3f GetOrbitPosition(const CStateManager&) const;\n  virtual zeus::CVector3f GetAimPosition(const CStateManager&, float) const;\n  virtual zeus::CVector3f GetHomingPosition(const CStateManager&, float) const;\n  virtual zeus::CVector3f GetScanObjectIndicatorPosition(const CStateManager&) const;\n  virtual EWeaponCollisionResponseTypes GetCollisionResponseType(const zeus::CVector3f&, const zeus::CVector3f&,\n                                                                 const CWeaponMode&, EProjectileAttrib) const;\n  virtual void FluidFXThink(EFluidState, CScriptWater&, CStateManager&);\n  virtual void OnScanStateChanged(EScanState, CStateManager&);\n  virtual zeus::CAABox GetSortingBounds(const CStateManager&) const;\n  virtual void DoUserAnimEvent(CStateManager&, const CInt32POINode&, EUserEventType, float dt);\n\n  void RemoveEmitter();\n  void SetVolume(float vol);\n  void SetMuted(bool);\n  const zeus::CTransform& GetTransform() const { return x34_transform; }\n  const zeus::CVector3f& GetTranslation() const { return x34_transform.origin; }\n  zeus::CTransform GetScaledLocatorTransform(std::string_view segName) const;\n  zeus::CTransform GetLocatorTransform(std::string_view segName) const;\n  void RemoveMaterial(EMaterialTypes, EMaterialTypes, EMaterialTypes, EMaterialTypes, CStateManager&);\n  void RemoveMaterial(EMaterialTypes, EMaterialTypes, EMaterialTypes, CStateManager&);\n  void RemoveMaterial(EMaterialTypes, EMaterialTypes, CStateManager&);\n  void RemoveMaterial(EMaterialTypes, CStateManager&);\n  void AddMaterial(EMaterialTypes, EMaterialTypes, EMaterialTypes, EMaterialTypes, EMaterialTypes, CStateManager&);\n  void AddMaterial(EMaterialTypes, EMaterialTypes, EMaterialTypes, EMaterialTypes, CStateManager&);\n  void AddMaterial(EMaterialTypes, EMaterialTypes, EMaterialTypes, CStateManager&);\n  void AddMaterial(EMaterialTypes, EMaterialTypes, CStateManager&);\n  void AddMaterial(EMaterialTypes, CStateManager&);\n  void AddMaterial(const CMaterialList& l);\n\n  void CreateShadow(bool);\n  void SetCallTouch(bool callTouch);\n  bool GetCallTouch() const;\n  void SetUseInSortedLists(bool use);\n  bool GetUseInSortedLists() const;\n  const CMaterialFilter& GetMaterialFilter() const { return x70_materialFilter; }\n  void SetMaterialFilter(const CMaterialFilter& filter) { x70_materialFilter = filter; }\n  const CMaterialList& GetMaterialList() const { return x68_material; }\n  void SetMaterialList(const CMaterialList& list) { x68_material = list; }\n  void SetInFluid(bool in, TUniqueId uid);\n  bool HasModelData() const;\n  const CSfxHandle& GetSfxHandle() const { return x8c_loopingSfxHandle; }\n  void SetSoundEventPitchBend(s32);\n  void SetRotation(const zeus::CQuaternion& q);\n  void SetTranslation(const zeus::CVector3f& tr);\n  void SetTransform(const zeus::CTransform& tr);\n  void SetAddedToken(u32 tok);\n  float GetPitch() const;\n  float GetYaw() const;\n  const CModelData* GetModelData() const { return x64_modelData.get(); }\n  CModelData* GetModelData() { return x64_modelData.get(); }\n  void EnsureRendered(const CStateManager&);\n  void EnsureRendered(const CStateManager&, const zeus::CVector3f&, const zeus::CAABox&);\n  void ProcessSoundEvent(u32 sfxId, float weight, u32 flags, float falloff, float maxDist, float minVol, float maxVol,\n                         const zeus::CVector3f& toListener, const zeus::CVector3f& position, TAreaId aid,\n                         CStateManager& mgr, bool translateId);\n  SAdvancementDeltas UpdateAnimation(float, CStateManager&, bool);\n  void SetActorLights(std::unique_ptr<CActorLights>&& lights);\n  const CActorLights* GetActorLights() const { return x90_actorLights.get(); }\n  CActorLights* GetActorLights() { return x90_actorLights.get(); }\n  bool CanDrawStatic() const;\n  bool IsDrawEnabled() const { return xe7_29_drawEnabled; }\n  void SetWorldLightingDirty(bool b) { xe7_28_worldLightingDirty = b; }\n  const CScannableObjectInfo* GetScannableObjectInfo() const;\n  const CHealthInfo* GetHealthInfo(const CStateManager& mgr) const {\n    return const_cast<CActor*>(this)->HealthInfo(const_cast<CStateManager&>(mgr));\n  }\n  bool GetDoTargetDistanceTest() const { return xe7_30_doTargetDistanceTest; }\n  void SetCalculateLighting(bool c);\n  float GetAverageAnimVelocity(int anim) const;\n  u8 GetTargetableVisorFlags() const { return xe6_31_targetableVisorFlags; }\n  bool GetIsTargetable() const { return xe7_31_targetable; }\n  const CModelFlags& GetDrawFlags() const { return xb4_drawFlags; }\n  void SetDrawFlags(const CModelFlags& flags) { xb4_drawFlags = flags; }\n  void SetModelData(std::unique_ptr<CModelData>&& mData);\n  u8 GetFluidCounter() const { return xe6_24_fluidCounter; }\n  TUniqueId GetFluidId() const { return xc4_fluidId; }\n  bool GetPointGeneratorParticles() const { return xe5_31_pointGeneratorParticles; }\n  void SetPointGeneratorParticles(bool s) { xe5_31_pointGeneratorParticles = s; }\n  CSimpleShadow* Shadow() { return x94_simpleShadow.get(); }\n  void MoveScannableObjectInfoToActor(CActor*, CStateManager&);\n  const zeus::CAABox& GetRenderBounds() const { return x9c_renderBounds; }\n  void SetNotInSortedLists(bool notIn) { xe4_27_notInSortedLists = notIn; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CActorModelParticles.cpp",
    "content": "#include \"Runtime/World/CActorModelParticles.hpp\"\n\n#include <algorithm>\n#include <array>\n\n#include \"Runtime/CDependencyGroup.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Graphics/CSkinnedModel.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/Particle/CGenDescription.hpp\"\n#include \"Runtime/Particle/CParticleElectric.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n#include \"Runtime/World/CScriptPlayerActor.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\nnamespace metaforce {\n\nstatic bool IsMediumOrLarge(const CActor& act) {\n  if (const TCastToConstPtr<CPatterned> pat = act) {\n    return pat->GetKnockBackController().GetVariant() != EKnockBackVariant::Small;\n  }\n  return false;\n}\n\nCActorModelParticles::CItem::CItem(const CEntity& ent, CActorModelParticles& parent)\n: x0_id(ent.GetUniqueId()), x4_areaId(ent.GetAreaIdAlways()), xdc_ashy(parent.x48_ashy), x128_parent(parent) {\n  x8_onFireGens.resize(8);\n}\n\nstatic s32 GetNextBestPt(s32 start, const SSkinningWorkspace& workspace, CRandom16& rnd) {\n  const auto& verts = workspace.m_vertexWorkspace;\n  const auto& startVecA = verts[start];\n  const zeus::CVector3f startVec{startVecA.x, startVecA.y, startVecA.z};\n  s32 ret = start;\n  float maxMag = 0.f;\n  for (s32 i = 0; i < 10; ++i) {\n    s32 idx = rnd.Range(0, s32(verts.size()) - 1);\n    const auto& rndVecA = verts[idx];\n    const zeus::CVector3f rndVec{rndVecA.x, rndVecA.y, rndVecA.z};\n    float mag = (startVec - rndVec).magSquared();\n    if (mag > maxMag) {\n      ret = idx;\n      maxMag = mag;\n    }\n  }\n  return ret;\n}\n\nvoid CActorModelParticles::CItem::GeneratePoints(const SSkinningWorkspace& workspace) {\n  const auto& verts = workspace.m_vertexWorkspace;\n  const auto& norms = workspace.m_normalWorkspace;\n\n  for (std::pair<std::unique_ptr<CElementGen>, u32>& pair : x8_onFireGens) {\n    if (pair.first) {\n      CRandom16 rnd(pair.second);\n      const auto& vecA = verts[rnd.Float() * (verts.size() - 1)];\n      const zeus::CVector3f vec{vecA.x, vecA.y, vecA.z};\n      pair.first->SetTranslation(xec_particleOffsetScale * vec);\n    }\n  }\n\n  if (x84_ashMaxParticles > 0) {\n    CRandom16 rnd(x88_ashSeed);\n    s32 count = (x84_ashMaxParticles >= 16 ? 16 : x84_ashMaxParticles);\n    s32 idx = x80_ashPointIterator;\n    for (u32 i = 0; i < count; ++i) {\n      idx = GetNextBestPt(idx, workspace, rnd);\n      const auto& vertA = verts[idx];\n      x78_ashGen->SetTranslation(xec_particleOffsetScale * zeus::CVector3f{vertA.x, vertA.y, vertA.z});\n      const auto& normA = norms[idx];\n      zeus::CVector3f v{normA.x, normA.y, normA.z};\n      if (v.canBeNormalized()) {\n        v.normalize();\n        x78_ashGen->SetOrientation(zeus::CTransform{v.cross(zeus::skUp), v, zeus::skUp, zeus::skZero3f});\n      }\n      x78_ashGen->ForceParticleCreation(1);\n    }\n    x84_ashMaxParticles -= count;\n    x88_ashSeed = rnd.GetSeed();\n    x80_ashPointIterator = idx;\n  }\n\n  if (xb0_icePointIterator != -1) {\n    CRandom16 rnd(xb4_iceSeed);\n\n    std::unique_ptr<CElementGen> iceGen = x128_parent.MakeIceGen();\n    iceGen->SetGlobalOrientAndTrans(xf8_iceXf);\n\n    s32 idx = GetNextBestPt(xb0_icePointIterator, workspace, rnd);\n\n    const auto& vertA = verts[idx];\n    zeus::CVector3f vert{vertA.x, vertA.y, vertA.z};\n    const auto& normA = norms[idx];\n    zeus::CVector3f norm{normA.x, normA.y, normA.z};\n\n    iceGen->SetTranslation(xec_particleOffsetScale * vert);\n\n    iceGen->SetOrientation(zeus::CTransform::MakeRotationsBasedOnY(zeus::CUnitVector3f(norm)));\n\n    x8c_iceGens.push_back(std::move(iceGen));\n    xb0_icePointIterator = (x8c_iceGens.size() == 4 ? -1 : idx);\n  }\n\n  if (xc0_electricGen && xc0_electricGen->GetParticleEmission()) {\n    CRandom16 rnd(xcc_electricSeed);\n    u32 end = 1;\n#if 0\n    if (4 < 1)\n      end = 4;\n#endif\n    s32 idx = xc8_electricPointIterator;\n    for (u32 i = 0; i < end; ++i) {\n      const auto& iPosA = verts[rnd.Range(0, s32(verts.size()) - 1)];\n      xc0_electricGen->SetOverrideIPos(zeus::CVector3f{iPosA.x, iPosA.y, iPosA.z} * xec_particleOffsetScale);\n      idx = rnd.Range(0, s32(verts.size()) - 1);\n      const auto& vertA = verts[idx];\n      xc0_electricGen->SetOverrideFPos(zeus::CVector3f{vertA.x, vertA.y, vertA.z} * xec_particleOffsetScale);\n      xc0_electricGen->ForceParticleCreation(1);\n    }\n\n    xcc_electricSeed = rnd.GetSeed();\n    xc8_electricPointIterator = idx;\n  }\n\n  if (xd4_rainSplashGen)\n    xd4_rainSplashGen->GeneratePoints(workspace);\n}\n\nbool CActorModelParticles::CItem::UpdateOnFire(float dt, CActor* actor, CStateManager& mgr) {\n  bool effectActive = false;\n  bool sfxActive = false;\n  x6c_onFireDelayTimer -= dt;\n  if (x6c_onFireDelayTimer < 0.f)\n    x6c_onFireDelayTimer = 0.f;\n  if (x134_lockDeps & 0x1) {\n    if (x128_parent.xe6_loadedDeps & 0x1) {\n      if (x70_onFire && actor) {\n        bool doCreate = true;\n        if (x78_ashGen || xdc_ashy) {\n          doCreate = false;\n        } else if (!IsMediumOrLarge(*actor)) {\n          int activeParts = 0;\n          for (const auto& p : x8_onFireGens)\n            if (p.first)\n              ++activeParts;\n          if (activeParts >= 4)\n            doCreate = false;\n        }\n        if (doCreate) {\n          for (auto& p : x8_onFireGens) {\n            if (!p.first) {\n              p.second = mgr.GetActiveRandom()->Next();\n              p.first = x128_parent.MakeOnFireGen();\n              x6c_onFireDelayTimer = 0.3f;\n              break;\n            }\n          }\n        }\n        if (!x74_sfx) {\n          x74_sfx = CSfxManager::AddEmitter(SFXsfx0480 + (IsMediumOrLarge(*actor) ? 1 : 0), actor->GetTranslation(),\n                                            zeus::skZero3f, true, true, 0x7f, kInvalidAreaId);\n        }\n        x70_onFire = false;\n      }\n      for (auto& p : x8_onFireGens) {\n        if (p.first) {\n          if (p.first->IsSystemDeletable())\n            p.first.reset();\n          else if (actor)\n            p.first->SetGlobalOrientAndTrans(actor->GetTransform());\n          p.first->Update(dt);\n          effectActive = true;\n          sfxActive = true;\n        }\n      }\n    } else {\n      effectActive = true;\n    }\n  }\n  if (x74_sfx) {\n    if (sfxActive) {\n      CSfxManager::UpdateEmitter(x74_sfx, xf8_iceXf.origin, zeus::skZero3f, 1.f);\n    } else {\n      CSfxManager::RemoveEmitter(x74_sfx);\n      x74_sfx.reset();\n    }\n  }\n  if (!effectActive)\n    Unlock(EDependency::OnFire);\n  return effectActive;\n}\n\nbool CActorModelParticles::CItem::UpdateAshGen(float dt, CActor* actor, CStateManager& mgr) {\n  if (x78_ashGen) {\n    if (x84_ashMaxParticles == 0 && x78_ashGen->IsSystemDeletable()) {\n      x78_ashGen.reset();\n    } else {\n      if (actor)\n        x78_ashGen->SetGlobalOrientAndTrans(actor->GetTransform());\n      x78_ashGen->Update(dt);\n      return true;\n    }\n  } else if (x134_lockDeps & 0x4 && actor) {\n    if (x128_parent.xe6_loadedDeps & 0x4) {\n      x78_ashGen = x128_parent.MakeAshGen();\n      x80_ashPointIterator = 0;\n      x78_ashGen->SetGlobalOrientAndTrans(actor->GetTransform());\n      x84_ashMaxParticles = s32((IsMediumOrLarge(*actor) ? 1.f : 0.3f) * x78_ashGen->GetMaxParticles());\n      x88_ashSeed = mgr.GetActiveRandom()->Next();\n    }\n    return true;\n  }\n  Unlock(EDependency::Ash);\n  return false;\n}\n\nbool CActorModelParticles::CItem::UpdateIceGen(float dt, CActor* actor, CStateManager& mgr) {\n  if (xb0_icePointIterator != -1)\n    return true;\n  if (!x8c_iceGens.empty()) {\n    bool active = false;\n    for (auto& p : x8c_iceGens) {\n      if (!p->IsSystemDeletable())\n        active = true;\n      p->Update(dt);\n    }\n    if (!active)\n      x8c_iceGens.clear();\n    else\n      return true;\n  } else if (x134_lockDeps & 0x2 && actor) {\n    if (x128_parent.xe6_loadedDeps & 0x2) {\n      xb0_icePointIterator = 0;\n      xb4_iceSeed = mgr.GetActiveRandom()->Next();\n    }\n    return true;\n  }\n  Unlock(EDependency::Ice);\n  return false;\n}\n\nbool CActorModelParticles::CItem::UpdateFirePop(float dt, CActor* actor, CStateManager& mgr) {\n  if (xb8_firePopGen) {\n    if (xb8_firePopGen->IsSystemDeletable()) {\n      xb8_firePopGen.reset();\n    } else {\n      xb8_firePopGen->Update(dt);\n      return true;\n    }\n  } else if (x134_lockDeps & 0x8 && actor) {\n    if (x128_parent.xe6_loadedDeps & 0x8) {\n      xb8_firePopGen = x128_parent.MakeFirePopGen();\n      xb8_firePopGen->SetGlobalOrientation(actor->GetTransform());\n      xb8_firePopGen->SetGlobalTranslation(actor->GetRenderBounds().center());\n    }\n    return true;\n  }\n  Unlock(EDependency::FirePop);\n  return false;\n}\n\nbool CActorModelParticles::CItem::UpdateElectric(float dt, CActor* actor, CStateManager& mgr) {\n  if (xc0_electricGen) {\n    if (xc0_electricGen->IsSystemDeletable()) {\n      xc0_electricGen.reset();\n    } else {\n      if (actor && actor->GetActive()) {\n        xc0_electricGen->SetGlobalOrientation(actor->GetTransform().getRotation());\n        xc0_electricGen->SetGlobalTranslation(actor->GetTranslation());\n      }\n      if (!actor || actor->GetActive()) {\n        xc0_electricGen->SetModulationColor(xd0_electricColor);\n        xc0_electricGen->Update(dt);\n        return true;\n      }\n    }\n  } else if (x134_lockDeps & 0x10) {\n    if (x128_parent.xe6_loadedDeps & 0x10) {\n      xc0_electricGen = x128_parent.MakeElectricGen();\n      xc0_electricGen->SetModulationColor(xd0_electricColor);\n      xc8_electricPointIterator = 0;\n      xcc_electricSeed = mgr.GetActiveRandom()->Next();\n    }\n    return true;\n  }\n  Unlock(EDependency::Electric);\n  return false;\n}\n\nbool CActorModelParticles::CItem::UpdateRainSplash(float dt, CActor* actor, CStateManager& mgr) {\n  if (xd4_rainSplashGen) {\n    if (!xd4_rainSplashGen->IsRaining()) {\n      xd4_rainSplashGen.reset();\n    } else {\n      xd4_rainSplashGen->Update(dt, mgr);\n      return true;\n    }\n  }\n  return false;\n}\n\nbool CActorModelParticles::CItem::UpdateBurn(float dt, CActor* actor, CStateManager& mgr) {\n  if (!actor)\n    xdc_ashy.Unlock();\n  return xdc_ashy.IsLocked();\n}\n\nbool CActorModelParticles::CItem::UpdateIcePop(float dt, CActor* actor, CStateManager& mgr) {\n  if (xe4_icePopGen) {\n    if (xe4_icePopGen->IsSystemDeletable()) {\n      xe4_icePopGen.reset();\n    } else {\n      xe4_icePopGen->Update(dt);\n      return true;\n    }\n  } else if (x134_lockDeps & 0x20 && actor) {\n    if (x128_parent.xe6_loadedDeps & 0x20) {\n      xe4_icePopGen = x128_parent.MakeIcePopGen();\n      xe4_icePopGen->SetGlobalOrientation(actor->GetTransform());\n      xe4_icePopGen->SetGlobalTranslation(actor->GetRenderBounds().center());\n    }\n    return true;\n  }\n  Unlock(EDependency::IcePop);\n  return false;\n}\n\nbool CActorModelParticles::CItem::Update(float dt, CStateManager& mgr) {\n  CActor* act = static_cast<CActor*>(mgr.ObjectById(x0_id));\n  if (act && act->HasModelData() && !act->GetModelData()->IsNull()) {\n    xec_particleOffsetScale = act->GetModelData()->GetScale();\n    xf8_iceXf = act->GetTransform();\n    x4_areaId = act->GetAreaIdAlways();\n  } else {\n    x0_id = kInvalidUniqueId;\n    x84_ashMaxParticles = 0;\n    xb0_icePointIterator = -1;\n    if (xc0_electricGen)\n      xc0_electricGen->SetParticleEmission(false);\n    if (x74_sfx) {\n      CSfxManager::RemoveEmitter(x74_sfx);\n      x74_sfx.reset();\n    }\n    x130_remTime -= dt;\n    if (x130_remTime <= 0.f)\n      return false;\n  }\n  bool ret = false;\n  if (UpdateOnFire(dt, act, mgr))\n    ret = true;\n  if (UpdateAshGen(dt, act, mgr))\n    ret = true;\n  if (UpdateIceGen(dt, act, mgr))\n    ret = true;\n  if (UpdateFirePop(dt, act, mgr))\n    ret = true;\n  if (UpdateElectric(dt, act, mgr))\n    ret = true;\n  if (UpdateRainSplash(dt, act, mgr))\n    ret = true;\n  if (UpdateBurn(dt, act, mgr))\n    ret = true;\n  if (UpdateIcePop(dt, act, mgr))\n    ret = true;\n  return ret;\n}\n\nvoid CActorModelParticles::CItem::Lock(EDependency d) {\n  if (!(x134_lockDeps & (1 << int(d)))) {\n    x128_parent.IncrementDependency(d);\n    x134_lockDeps |= (1 << int(d));\n  }\n}\n\nvoid CActorModelParticles::CItem::Unlock(EDependency d) {\n  if (x134_lockDeps & (1 << int(d))) {\n    x128_parent.DecrementDependency(d);\n    x134_lockDeps &= ~(1 << int(d));\n  }\n}\n\nvoid CActorModelParticles::DecrementDependency(EDependency d) {\n  Dependency& dep = x50_dgrps[int(d)];\n  dep.Decrement();\n  if (dep.x10_refCount == 0) {\n    xe4_loadingDeps &= ~(1 << int(d));\n    xe6_loadedDeps &= ~(1 << int(d));\n    xe5_justLoadedDeps &= ~(1 << int(d));\n  }\n}\n\nvoid CActorModelParticles::IncrementDependency(EDependency d) {\n  x50_dgrps[int(d)].Increment();\n  if (!(xe6_loadedDeps & (1 << int(d))))\n    xe4_loadingDeps |= (1 << int(d));\n}\n\nCActorModelParticles::Dependency CActorModelParticles::GetParticleDGRPTokens(std::string_view name) const {\n  Dependency ret = {};\n  TToken<CDependencyGroup> dgrp = g_SimplePool->GetObj(name);\n  const auto& vector = dgrp->GetObjectTagVector();\n  ret.x0_tokens.reserve(vector.size());\n  for (const SObjectTag& tag : vector) {\n    ret.x0_tokens.push_back(g_SimplePool->GetObj(tag));\n  }\n  return ret;\n}\n\nvoid CActorModelParticles::LoadParticleDGRPs() {\n  static constexpr std::array particleDGRPs{\n      \"Effect_OnFire_DGRP\"sv,  \"Effect_IceBreak_DGRP\"sv, \"Effect_Ash_DGRP\"sv,\n      \"Effect_FirePop_DGRP\"sv, \"Effect_Electric_DGRP\"sv, \"Effect_IcePop_DGRP\"sv,\n  };\n\n  for (const auto& dgrp : particleDGRPs) {\n    x50_dgrps.push_back(GetParticleDGRPTokens(dgrp));\n  }\n}\n\nstd::unique_ptr<CElementGen> CActorModelParticles::MakeOnFireGen() const {\n  return std::make_unique<CElementGen>(x18_onFire);\n}\n\nstd::unique_ptr<CElementGen> CActorModelParticles::MakeAshGen() const { return std::make_unique<CElementGen>(x20_ash); }\n\nstd::unique_ptr<CElementGen> CActorModelParticles::MakeIceGen() const {\n  return std::make_unique<CElementGen>(x28_iceBreak);\n}\n\nstd::unique_ptr<CElementGen> CActorModelParticles::MakeFirePopGen() const {\n  return std::make_unique<CElementGen>(x30_firePop);\n}\n\nstd::unique_ptr<CElementGen> CActorModelParticles::MakeIcePopGen() const {\n  return std::make_unique<CElementGen>(x38_icePop);\n}\n\nstd::unique_ptr<CParticleElectric> CActorModelParticles::MakeElectricGen() const {\n  return std::make_unique<CParticleElectric>(x40_electric);\n}\n\nCActorModelParticles::CActorModelParticles() {\n  x18_onFire = g_SimplePool->GetObj(\"Effect_OnFire\");\n  x20_ash = g_SimplePool->GetObj(\"Effect_Ash\");\n  x28_iceBreak = g_SimplePool->GetObj(\"Effect_IceBreak\");\n  x30_firePop = g_SimplePool->GetObj(\"Effect_FirePop\");\n  x38_icePop = g_SimplePool->GetObj(\"Effect_IcePop\");\n  x40_electric = g_SimplePool->GetObj(\"Effect_Electric\");\n  x48_ashy = g_SimplePool->GetObj(\"TXTR_Ashy\");\n  LoadParticleDGRPs();\n}\n\nvoid CActorModelParticles::AddStragglersToRenderer(const CStateManager& mgr) {\n  bool isNotCold = mgr.GetThermalDrawFlag() != EThermalDrawFlag::Cold;\n  bool isNotHot = mgr.GetThermalDrawFlag() != EThermalDrawFlag::Hot;\n\n  for (CItem& item : x0_items) {\n    if (item.x4_areaId != kInvalidAreaId) {\n      const CGameArea* area = mgr.GetWorld()->GetAreaAlways(item.x4_areaId);\n      if (!area->IsPostConstructed())\n        continue;\n      CGameArea::EOcclusionState occState = area->GetPostConstructed()->x10dc_occlusionState;\n      if (occState == CGameArea::EOcclusionState::Occluded)\n        continue;\n    }\n    if (mgr.GetObjectById(item.x0_id) &&\n        ((isNotCold && item.x12c_24_thermalCold) || (isNotHot && item.x12c_25_thermalHot))) {\n      item.x12c_24_thermalCold = false;\n      item.x12c_25_thermalHot = false;\n      continue;\n    }\n    if (isNotCold) {\n      // Hot Draw\n      for (auto& entry : item.x8_onFireGens) {\n        std::unique_ptr<CElementGen>& gen = entry.first;\n        if (gen) {\n          g_Renderer->AddParticleGen(*gen);\n        }\n      }\n      if (mgr.GetThermalDrawFlag() != EThermalDrawFlag::Hot && item.x78_ashGen)\n        g_Renderer->AddParticleGen(*item.x78_ashGen);\n      if (item.xb8_firePopGen)\n        g_Renderer->AddParticleGen(*item.xb8_firePopGen);\n      if (item.xc0_electricGen)\n        g_Renderer->AddParticleGen(*item.xc0_electricGen);\n    }\n    if (isNotHot) {\n      /* Cold Draw */\n      for (std::unique_ptr<CElementGen>& gen : item.x8c_iceGens)\n        g_Renderer->AddParticleGen(*gen);\n      if (item.xe4_icePopGen)\n        g_Renderer->AddParticleGen(*item.xe4_icePopGen);\n    }\n    if (isNotCold) {\n      /* Thermal Reset */\n      item.x12c_24_thermalCold = false;\n      item.x12c_25_thermalHot = false;\n    }\n  }\n}\n\nvoid CActorModelParticles::UpdateLoad() {\n  if (!xe4_loadingDeps) {\n    return;\n  }\n\n  xe5_justLoadedDeps = 0;\n  for (size_t i = 0; i < x50_dgrps.size(); ++i) {\n    if ((xe4_loadingDeps & (1U << i)) != 0) {\n      x50_dgrps[i].UpdateLoad();\n      if (x50_dgrps[i].x14_loaded) {\n        xe5_justLoadedDeps |= (1U << i);\n        xe4_loadingDeps &= ~(1U << i);\n      }\n    }\n  }\n  xe6_loadedDeps |= xe5_justLoadedDeps;\n}\n\nvoid CActorModelParticles::Update(float dt, CStateManager& mgr) {\n  UpdateLoad();\n  for (auto it = x0_items.begin(); it != x0_items.end();) {\n    if (!it->Update(dt, mgr)) {\n      if (CActor* act = static_cast<CActor*>(mgr.ObjectById(it->x0_id)))\n        act->SetPointGeneratorParticles(false);\n      it = x0_items.erase(it);\n      continue;\n    }\n    ++it;\n  }\n}\n\nvoid CActorModelParticles::SetupHook(TUniqueId uid) {\n  const auto search = FindSystem(uid);\n  if (search == x0_items.cend()) {\n    return;\n  }\n  CSkinnedModel::SetPointGeneratorFunc([=](const auto& workspace) { search->GeneratePoints(workspace); });\n}\n\nstd::list<CActorModelParticles::CItem>::iterator CActorModelParticles::FindSystem(TUniqueId uid) {\n  return std::find_if(x0_items.begin(), x0_items.end(), [uid](const auto& entry) { return entry.x0_id == uid; });\n}\n\nstd::list<CActorModelParticles::CItem>::const_iterator CActorModelParticles::FindSystem(TUniqueId uid) const {\n  return std::find_if(x0_items.begin(), x0_items.end(), [uid](const auto& entry) { return entry.x0_id == uid; });\n}\n\nstd::list<CActorModelParticles::CItem>::iterator CActorModelParticles::FindOrCreateSystem(CActor& act) {\n  if (act.GetPointGeneratorParticles()) {\n    for (auto it = x0_items.begin(); it != x0_items.end(); ++it)\n      if (it->x0_id == act.GetUniqueId())\n        return it;\n  }\n\n  act.SetPointGeneratorParticles(true);\n  return x0_items.emplace(x0_items.end(), act, *this);\n}\n\nvoid CActorModelParticles::StartIce(CActor& act) {\n  auto iter = FindOrCreateSystem(act);\n  iter->Lock(EDependency::Ice);\n}\n\nvoid CActorModelParticles::StartElectric(CActor& act) {\n  auto iter = FindOrCreateSystem(act);\n  if (iter->xc0_electricGen && !iter->xc0_electricGen->GetParticleEmission())\n    iter->xc0_electricGen->SetParticleEmission(true);\n}\n\nvoid CActorModelParticles::StopElectric(CActor& act) {\n  if (act.GetPointGeneratorParticles()) {\n    auto iter = FindSystem(act.GetUniqueId());\n    if (iter != x0_items.cend() && iter->xc0_electricGen)\n      iter->xc0_electricGen->SetParticleEmission(false);\n  }\n}\n\nvoid CActorModelParticles::LoadAndStartElectric(CActor& act) {\n  auto iter = FindOrCreateSystem(act);\n  if (!iter->xc0_electricGen)\n    iter->Lock(EDependency::Electric);\n  else {\n    if (!iter->xc0_electricGen->GetParticleEmission())\n      iter->xc0_electricGen->SetParticleEmission(true);\n  }\n}\n\nvoid CActorModelParticles::StopThermalHotParticles(CActor& act) {\n  if (act.GetPointGeneratorParticles()) {\n    auto iter = FindSystem(act.GetUniqueId());\n    if (iter != x0_items.cend()) {\n      for (auto& part : iter->x8_onFireGens)\n        if (part.first)\n          part.first->SetParticleEmission(false);\n    }\n  }\n}\n\nvoid CActorModelParticles::StartBurnDeath(CActor& act) {\n  auto iter = FindOrCreateSystem(act);\n  u16 sfx = SFXeff_x_smallburndeath_lp_00 - s16(IsMediumOrLarge(act));\n  CSfxManager::AddEmitter(sfx, act.GetTranslation(), zeus::skZero3f, true, false, 0x7f, kInvalidAreaId);\n  iter->xdc_ashy.Lock();\n}\n\nvoid CActorModelParticles::EnsureElectricLoaded(CActor& act) {\n  auto iter = FindOrCreateSystem(act);\n  iter->Lock(EDependency::IcePop);\n}\n\nvoid CActorModelParticles::EnsureFirePopLoaded(CActor& act) {\n  auto iter = FindOrCreateSystem(act);\n  iter->Lock(EDependency::FirePop);\n}\n\nvoid CActorModelParticles::EnsureIceBreakLoaded(CActor& act) {\n  auto iter = FindOrCreateSystem(act);\n  iter->Lock(EDependency::Ash);\n}\n\nvoid CActorModelParticles::LightDudeOnFire(CActor& act) {\n  auto iter = FindOrCreateSystem(act);\n  iter->Lock(EDependency::OnFire);\n  if (iter->x6c_onFireDelayTimer <= 0.f)\n    iter->x70_onFire = true;\n}\n\nCTexture* CActorModelParticles::GetAshyTexture(const CActor& act) {\n  auto iter = FindSystem(act.GetUniqueId());\n  if (iter != x0_items.cend() && iter->xdc_ashy && iter->xdc_ashy.IsLoaded()) {\n    //    iter->xdc_ashy->GetBooTexture()->setClampMode(boo::TextureClampMode::ClampToEdge);\n    return iter->xdc_ashy.GetObj();\n  }\n  return nullptr;\n}\n\nvoid CActorModelParticles::AddRainSplashGenerator(CActor& act, CStateManager& mgr, u32 maxSplashes, u32 genRate,\n                                                  float minZ) {\n  auto it = FindOrCreateSystem(act);\n  if (it->xd4_rainSplashGen)\n    return;\n\n  if (act.GetModelData() && !act.GetModelData()->IsNull())\n    it->xd4_rainSplashGen =\n        std::make_unique<CRainSplashGenerator>(act.GetModelData()->GetScale(), maxSplashes, genRate, minZ, 0.1875f);\n}\n\nvoid CActorModelParticles::RemoveRainSplashGenerator(CActor& act) {\n  auto it = FindOrCreateSystem(act);\n  it->xd4_rainSplashGen.reset();\n}\n\nvoid CActorModelParticles::Render(const CStateManager& mgr, const CActor& actor) const {\n  zeus::CTransform backupModel = CGraphics::mModelMatrix;\n  auto search = FindSystem(actor.GetUniqueId());\n  if (search == x0_items.end())\n    return;\n  if (search->x4_areaId != kInvalidAreaId) {\n    const CGameArea* area = mgr.GetWorld()->GetAreaAlways(search->x4_areaId);\n    if (!area->IsPostConstructed())\n      return;\n    if (area->GetOcclusionState() == CGameArea::EOcclusionState::Occluded)\n      return;\n  }\n  if (mgr.GetThermalDrawFlag() != EThermalDrawFlag::Cold) {\n    for (const auto& gen : search->x8_onFireGens)\n      if (gen.first)\n        gen.first->Render();\n    if (mgr.GetThermalDrawFlag() != EThermalDrawFlag::Hot && search->x78_ashGen)\n      search->x78_ashGen->Render();\n    if (search->xb8_firePopGen)\n      search->xb8_firePopGen->Render();\n    if (search->xc0_electricGen)\n      search->xc0_electricGen->Render();\n    search->x134_lockDeps |= 0x80;\n  }\n  if (mgr.GetThermalDrawFlag() != EThermalDrawFlag::Hot) {\n    for (const auto& gen : search->x8c_iceGens)\n      gen->Render();\n    if (search->xd4_rainSplashGen && actor.GetModelData() && !actor.GetModelData()->IsNull())\n      search->xd4_rainSplashGen->Draw(actor.GetTransform());\n    if (search->xe4_icePopGen)\n      search->xe4_icePopGen->Render();\n    search->x134_lockDeps |= 0x40;\n  }\n  CGraphics::SetModelMatrix(backupModel);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CActorModelParticles.hpp",
    "content": "#pragma once\n\n#include <list>\n#include <memory>\n#include <string_view>\n#include <utility>\n#include <vector>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Audio/CSfxManager.hpp\"\n#include \"Runtime/Graphics/CRainSplashGenerator.hpp\"\n#include \"Runtime/Particle/CParticleElectric.hpp\"\n#include \"Runtime/Particle/CParticleSwoosh.hpp\"\n\n#include <zeus/CColor.hpp>\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CStateManager;\nclass CEntity;\nclass CElementGen;\nclass CTexture;\nclass CGenDescription;\nclass CActor;\nclass CScriptPlayerActor;\n\nclass CActorModelParticles {\npublic:\n  enum class EDependency { OnFire, Ice, Ash, FirePop, Electric, IcePop };\n\n  class CItem {\n    friend class CActorModelParticles;\n    TUniqueId x0_id;\n    TAreaId x4_areaId;\n    rstl::reserved_vector<std::pair<std::unique_ptr<CElementGen>, u32>, 8> x8_onFireGens;\n    float x6c_onFireDelayTimer = 0.f;\n    bool x70_onFire = false;\n    CSfxHandle x74_sfx;\n    std::unique_ptr<CElementGen> x78_ashGen;\n    s32 x80_ashPointIterator = 0;\n    s32 x84_ashMaxParticles = -1;\n    u32 x88_ashSeed = 99;\n    rstl::reserved_vector<std::unique_ptr<CElementGen>, 4> x8c_iceGens;\n    s32 xb0_icePointIterator = -1;\n    u32 xb4_iceSeed = 99;\n    std::unique_ptr<CElementGen> xb8_firePopGen;\n    std::unique_ptr<CParticleElectric> xc0_electricGen;\n    s32 xc8_electricPointIterator = 0;\n    u32 xcc_electricSeed = 99;\n    zeus::CColor xd0_electricColor;\n    std::unique_ptr<CRainSplashGenerator> xd4_rainSplashGen;\n    TToken<CTexture> xdc_ashy;\n    std::unique_ptr<CElementGen> xe4_icePopGen;\n    zeus::CVector3f xec_particleOffsetScale = zeus::skOne3f;\n    zeus::CTransform xf8_iceXf;\n    CActorModelParticles& x128_parent;\n    bool x12c_24_thermalCold : 1 = false;\n    bool x12c_25_thermalHot : 1 = false;\n    float x130_remTime = 10.f;\n    mutable u8 x134_lockDeps = 0;\n    bool UpdateOnFire(float dt, CActor* actor, CStateManager& mgr);\n    bool UpdateAshGen(float dt, CActor* actor, CStateManager& mgr);\n    bool UpdateIceGen(float dt, CActor* actor, CStateManager& mgr);\n    bool UpdateFirePop(float dt, CActor* actor, CStateManager& mgr);\n    bool UpdateElectric(float dt, CActor* actor, CStateManager& mgr);\n    bool UpdateRainSplash(float dt, CActor* actor, CStateManager& mgr);\n    bool UpdateBurn(float dt, CActor* actor, CStateManager& mgr);\n    bool UpdateIcePop(float dt, CActor* actor, CStateManager& mgr);\n\n  public:\n    CItem(const CEntity& ent, CActorModelParticles& parent);\n    void GeneratePoints(const SSkinningWorkspace& workspace);\n    bool Update(float dt, CStateManager& mgr);\n    void Lock(EDependency i);\n    void Unlock(EDependency i);\n  };\n\nprivate:\n  friend class CItem;\n  std::list<CItem> x0_items;\n  TToken<CGenDescription> x18_onFire;\n  TToken<CGenDescription> x20_ash;\n  TToken<CGenDescription> x28_iceBreak;\n  TToken<CGenDescription> x30_firePop;\n  TToken<CGenDescription> x38_icePop;\n  TToken<CElectricDescription> x40_electric;\n  TToken<CTexture> x48_ashy;\n  struct Dependency {\n    std::vector<CToken> x0_tokens;\n    int x10_refCount = 0;\n    bool x14_loaded = false;\n    void Increment() {\n      ++x10_refCount;\n      if (x10_refCount == 1)\n        Load();\n    }\n    void Decrement() {\n      --x10_refCount;\n      if (x10_refCount <= 0)\n        Unload();\n    }\n    void Load() {\n      bool loading = false;\n      for (CToken& tok : x0_tokens) {\n        tok.Lock();\n        if (!tok.IsLoaded())\n          loading = true;\n      }\n      if (!loading)\n        x14_loaded = true;\n    }\n    void Unload() {\n      for (CToken& tok : x0_tokens)\n        tok.Unlock();\n      x14_loaded = false;\n    }\n    void UpdateLoad() {\n      if (x14_loaded || x10_refCount == 0)\n        return;\n      bool loading = false;\n      for (CToken& tok : x0_tokens) {\n        if (!tok.IsLoaded())\n          loading = true;\n      }\n      if (!loading)\n        x14_loaded = true;\n    }\n  };\n  rstl::reserved_vector<Dependency, 6> x50_dgrps;\n  u8 xe4_loadingDeps = 0;\n  u8 xe5_justLoadedDeps = 0;\n  u8 xe6_loadedDeps = 0;\n\n  Dependency GetParticleDGRPTokens(std::string_view name) const;\n  void LoadParticleDGRPs();\n\n  std::unique_ptr<CElementGen> MakeOnFireGen() const;\n  std::unique_ptr<CElementGen> MakeAshGen() const;\n  std::unique_ptr<CElementGen> MakeIceGen() const;\n  std::unique_ptr<CElementGen> MakeFirePopGen() const;\n  std::unique_ptr<CElementGen> MakeIcePopGen() const;\n  std::unique_ptr<CParticleElectric> MakeElectricGen() const;\n\n  void DecrementDependency(EDependency d);\n  void IncrementDependency(EDependency d);\n\n  void UpdateLoad();\n\npublic:\n  CActorModelParticles();\n  void AddStragglersToRenderer(const CStateManager& mgr);\n  void Update(float dt, CStateManager& mgr);\n  void SetupHook(TUniqueId uid);\n  std::list<CItem>::iterator FindSystem(TUniqueId uid);\n  std::list<CItem>::const_iterator FindSystem(TUniqueId uid) const;\n  std::list<CItem>::iterator FindOrCreateSystem(CActor& act);\n  void StartIce(CActor& actor);\n  void AddRainSplashGenerator(CActor& act, CStateManager& mgr, u32 maxSplashes, u32 genRate, float minZ);\n  void RemoveRainSplashGenerator(CActor& act);\n  void Render(const CStateManager& mgr, const CActor& actor) const;\n  void StartElectric(CActor& act);\n  void StopElectric(CActor& act);\n  void LoadAndStartElectric(CActor& act);\n  void StopThermalHotParticles(CActor& act);\n  void StartBurnDeath(CActor& act);\n  void EnsureElectricLoaded(CActor& act);\n  void EnsureFirePopLoaded(CActor& act);\n  void EnsureIceBreakLoaded(CActor& act);\n  void LightDudeOnFire(CActor& act);\n  CTexture* GetAshyTexture(const CActor& act);\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CActorParameters.hpp",
    "content": "#pragma once\n\n#include <utility>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/World/CLightParameters.hpp\"\n#include \"Runtime/World/CScannableParameters.hpp\"\n#include \"Runtime/World/CVisorParameters.hpp\"\n\nnamespace metaforce {\n\nclass CActorParameters {\n  friend class ScriptLoader;\n  friend class CActor;\n  friend class CScriptActor;\n  CLightParameters x0_lightParms;\n  CScannableParameters x40_scanParms;\n  std::pair<CAssetId, CAssetId> x44_xrayAssets = {};\n  std::pair<CAssetId, CAssetId> x4c_thermalAssets = {};\n  CVisorParameters x54_visorParms;\n  bool x58_24_globalTimeProvider : 1 = true;\n  bool x58_25_thermalHeat : 1 = false;\n  bool x58_26_renderUnsorted : 1 = false;\n  bool x58_27_noSortThermal : 1 = false;\n  float x5c_fadeInTime = 0.f;\n  float x60_fadeOutTime = 0.f;\n  float x64_thermalMag = 0.f;\n\npublic:\n  CActorParameters() = default;\n  CActorParameters(const CLightParameters& lightParms, const CScannableParameters& scanParms,\n                   const std::pair<CAssetId, CAssetId>& xrayAssets, const std::pair<CAssetId, CAssetId>& thermalAssets,\n                   const CVisorParameters& visorParms, bool globalTimeProvider, bool thermalHeat, bool renderUnsorted,\n                   bool noSortThermal, float fadeInTime, float fadeOutTime, float thermalMag)\n  : x0_lightParms(lightParms)\n  , x40_scanParms(scanParms)\n  , x44_xrayAssets(xrayAssets)\n  , x4c_thermalAssets(thermalAssets)\n  , x54_visorParms(visorParms)\n  , x58_24_globalTimeProvider(globalTimeProvider)\n  , x58_25_thermalHeat(thermalHeat)\n  , x58_26_renderUnsorted(renderUnsorted)\n  , x58_27_noSortThermal(noSortThermal)\n  , x5c_fadeInTime(fadeInTime)\n  , x60_fadeOutTime(fadeOutTime)\n  , x64_thermalMag(thermalMag) {}\n  CActorParameters Scannable(const CScannableParameters& sParms) const {\n    CActorParameters aParms = *this;\n    aParms.x40_scanParms = sParms;\n    return aParms;\n  }\n\n  static CActorParameters None() { return CActorParameters(); }\n  CActorParameters HotInThermal(bool hot) const {\n    CActorParameters ret = *this;\n    ret.x58_25_thermalHeat = hot;\n    return ret;\n  }\n  void SetVisorParameters(const CVisorParameters& vParams) { x54_visorParms = vParams; }\n  const CVisorParameters& GetVisorParameters() const { return x54_visorParms; }\n  const CLightParameters& GetLightParameters() const { return x0_lightParms; }\n  bool HasThermalHeat() const { return x58_25_thermalHeat; }\n  float GetThermalMag() const { return x64_thermalMag; }\n  const std::pair<CAssetId, CAssetId>& GetXRayAssets() const { return x44_xrayAssets; }\n  const std::pair<CAssetId, CAssetId>& GetThermalAssets() const { return x4c_thermalAssets; }\n  float GetFadeInTime() const { return x5c_fadeInTime; }\n  float GetFadeOutTime() const { return x60_fadeOutTime; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CAi.cpp",
    "content": "#include \"Runtime/World/CAi.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Character/CModelData.hpp\"\n#include \"Runtime/World/CScriptWater.hpp\"\n#include \"Runtime/World/CStateMachine.hpp\"\n\nnamespace metaforce {\n\nstatic CMaterialList MakeAiMaterialList(const CMaterialList& in) {\n  CMaterialList ret = in;\n  ret.Add(EMaterialTypes::AIBlock);\n  ret.Add(EMaterialTypes::CameraPassthrough);\n  return ret;\n}\n\nCAi::CAi(TUniqueId uid, bool active, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n         CModelData&& mData, const zeus::CAABox& box, float mass, const CHealthInfo& hInfo,\n         const CDamageVulnerability& dmgVuln, const CMaterialList& list, CAssetId fsm,\n         const CActorParameters& actorParams, float stepUp, float stepDown)\n: CPhysicsActor(uid, active, name, info, xf, std::move(mData), MakeAiMaterialList(list), box, SMoverData(mass),\n                actorParams, stepUp, stepDown)\n, x258_healthInfo(hInfo)\n, x260_damageVulnerability(dmgVuln)\n, x2c8_stateMachine(g_SimplePool->GetObj({FOURCC('AFSM'), fsm})) {\n  _CreateShadow();\n\n  if (x94_simpleShadow) {\n    CreateShadow(true);\n    x94_simpleShadow->SetAlwaysCalculateRadius(false);\n  }\n\n  if (x90_actorLights)\n    x90_actorLights->SetCastShadows(true);\n}\n\nvoid CAi::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  if (msg == EScriptObjectMessage::InitializedInArea) {\n    CMaterialList exclude = GetMaterialFilter().GetExcludeList();\n    CMaterialList include = GetMaterialFilter().GetIncludeList();\n    include.Add(EMaterialTypes::AIBlock);\n    SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(include, exclude));\n  }\n\n  CActor::AcceptScriptMsg(msg, uid, mgr);\n}\n\nEWeaponCollisionResponseTypes CAi::GetCollisionResponseType(const zeus::CVector3f&, const zeus::CVector3f&,\n                                                            const metaforce::CWeaponMode&,\n                                                            metaforce::EProjectileAttrib) const {\n  return EWeaponCollisionResponseTypes::EnemyNormal;\n}\n\nvoid CAi::FluidFXThink(EFluidState state, CScriptWater& water, metaforce::CStateManager& mgr) {\n  if (state == EFluidState::EnteredFluid || state == EFluidState::LeftFluid) {\n    float dt = mgr.GetFluidPlaneManager()->GetLastSplashDeltaTime(GetUniqueId());\n    if (dt >= 0.02f) {\n      float vel = (0.5f * GetMass()) * GetVelocity().magSquared();\n\n      if (vel > 500.f) {\n        zeus::CVector3f pos = x34_transform.origin;\n        pos.z() = float(water.GetTriggerBoundsWR().max.z());\n        mgr.GetFluidPlaneManager()->CreateSplash(GetUniqueId(), mgr, water, pos,\n                                                 0.1f + ((0.4f * zeus::min(vel, 30000.f) - 500.f) / 29500.f), true);\n      }\n    }\n  }\n\n  if (mgr.GetFluidPlaneManager()->GetLastRippleDeltaTime(GetUniqueId()) <\n      (GetHealthInfo(mgr)->GetHP() > 0.f ? 0.2f : 0.7f))\n    return;\n\n  zeus::CVector3f pos = x34_transform.origin;\n  zeus::CVector3f center = pos;\n  center.z() = float(water.GetTriggerBoundsWR().max.z());\n  pos.normalize();\n  water.GetFluidPlane().AddRipple(GetMass(), GetUniqueId(), center, GetVelocity(), water, mgr, pos);\n}\n\nCAiStateFunc CAi::GetStateFunc(std::string_view func) { return m_FuncMap->GetStateFunc(func); }\n\nCAiTriggerFunc CAi::GetTriggerFunc(std::string_view func) { return m_FuncMap->GetTriggerFunc(func); }\n\nconst CStateMachine* CAi::GetStateMachine() const { return x2c8_stateMachine.GetObj(); }\nvoid CAi::CreateFuncLookup(CAiFuncMap* funcMap) { m_FuncMap = funcMap; }\nCAiFuncMap* CAi::m_FuncMap = nullptr;\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CAi.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/World/CDamageVulnerability.hpp\"\n#include \"Runtime/World/CEntity.hpp\"\n#include \"Runtime/World/CHealthInfo.hpp\"\n#include \"Runtime/World/CKnockBackController.hpp\"\n#include \"Runtime/World/CPhysicsActor.hpp\"\n#include \"Runtime/World/CStateMachine.hpp\"\n#include \"Runtime/World/ScriptObjectSupport.hpp\"\n\n#include <zeus/zeus.hpp>\n\nnamespace metaforce {\n\nenum class EListenNoiseType { PlayerFire, BombExplode, ProjectileExplode };\n\nclass CAiFuncMap;\nclass CStateManager;\nclass CScriptWater;\nclass CTeamAiRole;\n\nclass CAi : public CPhysicsActor {\n  static CAiFuncMap* m_FuncMap;\n\nprotected:\n  CHealthInfo x258_healthInfo;\n  CDamageVulnerability x260_damageVulnerability;\n  TLockedToken<CStateMachine> x2c8_stateMachine;\n\npublic:\n  DEFINE_ENTITY\n  CAi(TUniqueId uid, bool active, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n      CModelData&& mData, const zeus::CAABox& box, float mass, const CHealthInfo& hInfo, const CDamageVulnerability&,\n      const CMaterialList& list, CAssetId fsm, const CActorParameters&, float f1, float f2);\n\n  static void CreateFuncLookup(CAiFuncMap* funcMap);\n  static CAiStateFunc GetStateFunc(std::string_view func);\n  static CAiTriggerFunc GetTriggerFunc(std::string_view func);\n\n  const CStateMachine* GetStateMachine() const;\n\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  CHealthInfo* HealthInfo(CStateManager&) override { return &x258_healthInfo; }\n  const CDamageVulnerability* GetDamageVulnerability() const override { return &x260_damageVulnerability; }\n  EWeaponCollisionResponseTypes GetCollisionResponseType(const zeus::CVector3f&, const zeus::CVector3f&,\n                                                         const CWeaponMode&, EProjectileAttrib) const override;\n  void FluidFXThink(EFluidState, CScriptWater&, CStateManager&) override;\n\n  virtual void Death(CStateManager& mgr, const zeus::CVector3f& direction, EScriptObjectState state) = 0;\n  virtual void KnockBack(const zeus::CVector3f&, CStateManager&, const CDamageInfo& info, EKnockBackType type,\n                         bool inDeferred, float magnitude) = 0;\n\n  virtual void TakeDamage(const zeus::CVector3f& direction, float magnitude) {}\n  virtual bool CanBeShot(const CStateManager&, int) { return true; }\n  virtual bool IsListening() const { return false; }\n  virtual bool Listen(const zeus::CVector3f&, EListenNoiseType) { return false; }\n\n  virtual zeus::CVector3f GetOrigin(const CStateManager& mgr, const CTeamAiRole& role,\n                                    const zeus::CVector3f& aimPos) const {\n    return x34_transform.origin;\n  }\n  virtual void Patrol(CStateManager&, EStateMsg, float) {}\n  virtual void FollowPattern(CStateManager&, EStateMsg, float) {}\n  virtual void Dead(CStateManager&, EStateMsg, float) {}\n  virtual void PathFind(CStateManager&, EStateMsg, float) {}\n  virtual void Start(CStateManager&, EStateMsg, float) {}\n  virtual void SelectTarget(CStateManager&, EStateMsg, float) {}\n  virtual void TargetPatrol(CStateManager&, EStateMsg, float) {}\n  virtual void TargetPlayer(CStateManager&, EStateMsg, float) {}\n  virtual void TargetCover(CStateManager&, EStateMsg, float) {}\n  virtual void Halt(CStateManager&, EStateMsg, float) {}\n  virtual void Walk(CStateManager&, EStateMsg, float) {}\n  virtual void Run(CStateManager&, EStateMsg, float) {}\n  virtual void Generate(CStateManager&, EStateMsg, float) {}\n  virtual void Deactivate(CStateManager&, EStateMsg, float) {}\n  virtual void Attack(CStateManager&, EStateMsg, float) {}\n  virtual void LoopedAttack(CStateManager&, EStateMsg, float) {}\n  virtual void JumpBack(CStateManager&, EStateMsg, float) {}\n  virtual void DoubleSnap(CStateManager&, EStateMsg, float) {}\n  virtual void Shuffle(CStateManager&, EStateMsg, float) {}\n  virtual void TurnAround(CStateManager&, EStateMsg, float) {}\n  virtual void Skid(CStateManager&, EStateMsg, float) {}\n  virtual void Active(CStateManager&, EStateMsg, float) {}\n  virtual void InActive(CStateManager&, EStateMsg, float) {}\n  virtual void CoverAttack(CStateManager&, EStateMsg, float) {}\n  virtual void Crouch(CStateManager&, EStateMsg, float) {}\n  virtual void FadeIn(CStateManager&, EStateMsg, float) {}\n  virtual void FadeOut(CStateManager&, EStateMsg, float) {}\n  virtual void GetUp(CStateManager&, EStateMsg, float) {}\n  virtual void Taunt(CStateManager&, EStateMsg, float) {}\n  virtual void Suck(CStateManager&, EStateMsg, float) {}\n  virtual void Flee(CStateManager&, EStateMsg, float) {}\n  virtual void Lurk(CStateManager&, EStateMsg, float) {}\n  virtual void ProjectileAttack(CStateManager&, EStateMsg, float) {}\n  virtual void Flinch(CStateManager&, EStateMsg, float) {}\n  virtual void Hurled(CStateManager&, EStateMsg, float) {}\n  virtual void TelegraphAttack(CStateManager&, EStateMsg, float) {}\n  virtual void Jump(CStateManager&, EStateMsg, float) {}\n  virtual void Explode(CStateManager&, EStateMsg, float) {}\n  virtual void Dodge(CStateManager&, EStateMsg, float) {}\n  virtual void Retreat(CStateManager&, EStateMsg, float) {}\n  virtual void Cover(CStateManager&, EStateMsg, float) {}\n  virtual void Approach(CStateManager&, EStateMsg, float) {}\n  virtual void WallHang(CStateManager&, EStateMsg, float) {}\n  virtual void WallDetach(CStateManager&, EStateMsg, float) {}\n  virtual void Enraged(CStateManager&, EStateMsg, float) {}\n  virtual void SpecialAttack(CStateManager&, EStateMsg, float) {}\n  virtual void Growth(CStateManager&, EStateMsg, float) {}\n  virtual void Faint(CStateManager&, EStateMsg, float) {}\n  virtual void Land(CStateManager&, EStateMsg, float) {}\n  virtual void Bounce(CStateManager&, EStateMsg, float) {}\n  virtual void PathFindEx(CStateManager&, EStateMsg, float) {}\n  virtual void Dizzy(CStateManager&, EStateMsg, float) {}\n  virtual void CallForBackup(CStateManager&, EStateMsg, float) {}\n  virtual void BulbAttack(CStateManager&, EStateMsg, float) {}\n  virtual void PodAttack(CStateManager&, EStateMsg, float) {}\n\n  virtual bool InAttackPosition(CStateManager&, float) { return false; }\n  virtual bool Leash(CStateManager&, float) { return false; }\n  virtual bool OffLine(CStateManager&, float) { return false; }\n  virtual bool Attacked(CStateManager&, float) { return false; }\n  virtual bool PathShagged(CStateManager&, float) { return false; }\n  virtual bool PathOver(CStateManager&, float) { return false; }\n  virtual bool PathFound(CStateManager&, float) { return false; }\n  virtual bool TooClose(CStateManager&, float) { return false; }\n  virtual bool InRange(CStateManager&, float) { return false; }\n  virtual bool InMaxRange(CStateManager&, float) { return false; }\n  virtual bool InDetectionRange(CStateManager&, float) { return false; }\n  virtual bool SpotPlayer(CStateManager&, float) { return false; }\n  virtual bool PlayerSpot(CStateManager&, float) { return false; }\n  virtual bool PatternOver(CStateManager&, float) { return false; }\n  virtual bool PatternShagged(CStateManager&, float) { return false; }\n  virtual bool HasAttackPattern(CStateManager&, float) { return false; }\n  virtual bool HasPatrolPath(CStateManager&, float) { return false; }\n  virtual bool HasRetreatPattern(CStateManager&, float) { return false; }\n  virtual bool Delay(CStateManager&, float) { return false; }\n  virtual bool RandomDelay(CStateManager&, float) { return false; }\n  virtual bool FixedDelay(CStateManager&, float) { return false; }\n  virtual bool Default(CStateManager&, float) { return false; }\n  virtual bool AnimOver(CStateManager&, float) { return false; }\n  virtual bool ShouldAttack(CStateManager&, float) { return false; }\n  virtual bool ShouldDoubleSnap(CStateManager&, float) { return false; }\n  virtual bool InPosition(CStateManager&, float) { return false; }\n  virtual bool ShouldTurn(CStateManager&, float) { return false; }\n  virtual bool HitSomething(CStateManager&, float) { return false; }\n  virtual bool ShouldJumpBack(CStateManager&, float) { return false; }\n  virtual bool Stuck(CStateManager&, float) { return false; }\n  virtual bool NoPathNodes(CStateManager&, float) { return false; }\n  virtual bool Landed(CStateManager&, float) { return false; }\n  virtual bool HearShot(CStateManager&, float) { return false; }\n  virtual bool HearPlayer(CStateManager&, float) { return false; }\n  virtual bool CoverCheck(CStateManager&, float) { return false; }\n  virtual bool CoverFind(CStateManager&, float) { return false; }\n  virtual bool CoverBlown(CStateManager&, float) { return false; }\n  virtual bool CoverNearlyBlown(CStateManager&, float) { return false; }\n  virtual bool CoveringFire(CStateManager&, float) { return false; }\n  virtual bool GotUp(CStateManager&, float) { return false; }\n  virtual bool LineOfSight(CStateManager&, float) { return false; }\n  virtual bool AggressionCheck(CStateManager&, float) { return false; }\n  virtual bool AttackOver(CStateManager&, float) { return false; }\n  virtual bool ShouldTaunt(CStateManager&, float) { return false; }\n  virtual bool Inside(CStateManager&, float) { return false; }\n  virtual bool ShouldFire(CStateManager&, float) { return false; }\n  virtual bool ShouldFlinch(CStateManager&, float) { return false; }\n  virtual bool PatrolPathOver(CStateManager&, float) { return false; }\n  virtual bool ShouldDodge(CStateManager&, float) { return false; }\n  virtual bool ShouldRetreat(CStateManager&, float) { return false; }\n  virtual bool ShouldCrouch(CStateManager&, float) { return false; }\n  virtual bool ShouldMove(CStateManager&, float) { return false; }\n  virtual bool ShotAt(CStateManager&, float) { return false; }\n  virtual bool HasTargetingPoint(CStateManager&, float) { return false; }\n  virtual bool ShouldWallHang(CStateManager&, float) { return false; }\n  virtual bool SetAIStage(CStateManager&, float) { return false; }\n  virtual bool AIStage(CStateManager&, float) { return false; }\n  virtual bool StartAttack(CStateManager&, float) { return false; }\n  virtual bool BreakAttack(CStateManager&, float) { return false; }\n  virtual bool ShouldStrafe(CStateManager&, float) { return false; }\n  virtual bool ShouldSpecialAttack(CStateManager&, float) { return false; }\n  virtual bool LostInterest(CStateManager&, float) { return false; }\n  virtual bool CodeTrigger(CStateManager&, float) { return false; }\n  virtual bool BounceFind(CStateManager&, float) { return false; }\n  virtual bool Random(CStateManager&, float) { return false; }\n  virtual bool FixedRandom(CStateManager&, float) { return false; }\n  virtual bool IsDizzy(CStateManager&, float) { return false; }\n  virtual bool ShouldCallForBackup(CStateManager&, float) { return false; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CAiFuncMap.cpp",
    "content": "#include \"Runtime/World/CAiFuncMap.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CAi.hpp\"\n\nnamespace metaforce {\nCAiFuncMap::CAiFuncMap() {\n  /* Ai States */\n  x0_stateFuncs.reserve(55);\n  x0_stateFuncs.emplace(\"Patrol\", &CAi::Patrol);\n  x0_stateFuncs.emplace(\"FollowPattern\", &CAi::FollowPattern);\n  x0_stateFuncs.emplace(\"Dead\", &CAi::Dead);\n  x0_stateFuncs.emplace(\"PathFind\", &CAi::PathFind);\n  x0_stateFuncs.emplace(\"Start\", &CAi::Start);\n  x0_stateFuncs.emplace(\"SelectTarget\", &CAi::SelectTarget);\n  x0_stateFuncs.emplace(\"TargetPatrol\", &CAi::TargetPatrol);\n  x0_stateFuncs.emplace(\"TargetPlayer\", &CAi::TargetPlayer);\n  x0_stateFuncs.emplace(\"TargetCover\", &CAi::TargetCover);\n  x0_stateFuncs.emplace(\"Halt\", &CAi::Halt);\n  x0_stateFuncs.emplace(\"Walk\", &CAi::Walk);\n  x0_stateFuncs.emplace(\"Run\", &CAi::Run);\n  x0_stateFuncs.emplace(\"Generate\", &CAi::Generate);\n  x0_stateFuncs.emplace(\"Deactivate\", &CAi::Deactivate);\n  x0_stateFuncs.emplace(\"Attack\", &CAi::Attack);\n  x0_stateFuncs.emplace(\"LoopedAttack\", &CAi::LoopedAttack);\n  x0_stateFuncs.emplace(\"JumpBack\", &CAi::JumpBack);\n  x0_stateFuncs.emplace(\"DoubleSnap\", &CAi::DoubleSnap);\n  x0_stateFuncs.emplace(\"Shuffle\", &CAi::Shuffle);\n  x0_stateFuncs.emplace(\"TurnAround\", &CAi::TurnAround);\n  x0_stateFuncs.emplace(\"Skid\", &CAi::Skid);\n  x0_stateFuncs.emplace(\"Active\", &CAi::Active);\n  x0_stateFuncs.emplace(\"InActive\", &CAi::InActive);\n  x0_stateFuncs.emplace(\"CoverAttack\", &CAi::CoverAttack);\n  x0_stateFuncs.emplace(\"Crouch\", &CAi::Crouch);\n  x0_stateFuncs.emplace(\"FadeIn\", &CAi::FadeIn);\n  x0_stateFuncs.emplace(\"FadeOut\", &CAi::FadeOut);\n  x0_stateFuncs.emplace(\"GetUp\", &CAi::GetUp);\n  x0_stateFuncs.emplace(\"Taunt\", &CAi::Taunt);\n  x0_stateFuncs.emplace(\"Suck\", &CAi::Suck);\n  x0_stateFuncs.emplace(\"Flee\", &CAi::Flee);\n  x0_stateFuncs.emplace(\"Lurk\", &CAi::Lurk);\n  x0_stateFuncs.emplace(\"ProjectileAttack\", &CAi::ProjectileAttack);\n  x0_stateFuncs.emplace(\"Flinch\", &CAi::Flinch);\n  x0_stateFuncs.emplace(\"Hurled\", &CAi::Hurled);\n  x0_stateFuncs.emplace(\"TelegraphAttack\", &CAi::TelegraphAttack);\n  x0_stateFuncs.emplace(\"Jump\", &CAi::Jump);\n  x0_stateFuncs.emplace(\"Explode\", &CAi::Explode);\n  x0_stateFuncs.emplace(\"Dodge\", &CAi::Dodge);\n  x0_stateFuncs.emplace(\"Retreat\", &CAi::Retreat);\n  x0_stateFuncs.emplace(\"Cover\", &CAi::Cover);\n  x0_stateFuncs.emplace(\"Approach\", &CAi::Approach);\n  x0_stateFuncs.emplace(\"WallHang\", &CAi::WallHang);\n  x0_stateFuncs.emplace(\"WallDetach\", &CAi::WallDetach);\n  x0_stateFuncs.emplace(\"Enraged\", &CAi::Enraged);\n  x0_stateFuncs.emplace(\"SpecialAttack\", &CAi::SpecialAttack);\n  x0_stateFuncs.emplace(\"Growth\", &CAi::Growth);\n  x0_stateFuncs.emplace(\"Faint\", &CAi::Faint);\n  x0_stateFuncs.emplace(\"Land\", &CAi::Land);\n  x0_stateFuncs.emplace(\"Bounce\", &CAi::Bounce);\n  x0_stateFuncs.emplace(\"PathFindEx\", &CAi::PathFindEx);\n  x0_stateFuncs.emplace(\"Dizzy\", &CAi::Dizzy);\n  x0_stateFuncs.emplace(\"CallForBackup\", &CAi::CallForBackup);\n  x0_stateFuncs.emplace(\"BulbAttack\", &CAi::BulbAttack);\n  x0_stateFuncs.emplace(\"PodAttack\", &CAi::PodAttack);\n\n  /* Ai Triggers */\n  x10_triggerFuncs.reserve(68);\n  x10_triggerFuncs.emplace(\"InAttackPosition\", &CAi::InAttackPosition);\n  x10_triggerFuncs.emplace(\"Leash\", &CAi::Leash);\n  x10_triggerFuncs.emplace(\"OffLine\", &CAi::OffLine);\n  x10_triggerFuncs.emplace(\"Attacked\", &CAi::Attacked);\n  x10_triggerFuncs.emplace(\"PathShagged\", &CAi::PathShagged);\n  x10_triggerFuncs.emplace(\"PathOver\", &CAi::PathOver);\n  x10_triggerFuncs.emplace(\"PathFound\", &CAi::PathFound);\n  x10_triggerFuncs.emplace(\"TooClose\", &CAi::TooClose);\n  x10_triggerFuncs.emplace(\"InRange\", &CAi::InRange);\n  x10_triggerFuncs.emplace(\"InMaxRange\", &CAi::InMaxRange);\n  x10_triggerFuncs.emplace(\"InDetectionRange\", &CAi::InDetectionRange);\n  x10_triggerFuncs.emplace(\"SpotPlayer\", &CAi::SpotPlayer);\n  x10_triggerFuncs.emplace(\"PlayerSpot\", &CAi::PlayerSpot);\n  x10_triggerFuncs.emplace(\"PatternOver\", &CAi::PatternOver);\n  x10_triggerFuncs.emplace(\"PatternShagged\", &CAi::PatternShagged);\n  x10_triggerFuncs.emplace(\"HasAttackPattern\", &CAi::HasAttackPattern);\n  x10_triggerFuncs.emplace(\"HasPatrolPath\", &CAi::HasPatrolPath);\n  x10_triggerFuncs.emplace(\"HasRetreatPattern\", &CAi::HasRetreatPattern);\n  x10_triggerFuncs.emplace(\"Delay\", &CAi::Delay);\n  x10_triggerFuncs.emplace(\"RandomDelay\", &CAi::RandomDelay);\n  x10_triggerFuncs.emplace(\"FixedDelay\", &CAi::FixedDelay);\n  x10_triggerFuncs.emplace(\"Default\", &CAi::Default);\n  x10_triggerFuncs.emplace(\"AnimOver\", &CAi::AnimOver);\n  x10_triggerFuncs.emplace(\"ShouldAttack\", &CAi::ShouldAttack);\n  x10_triggerFuncs.emplace(\"ShouldDoubleSnap\", &CAi::ShouldDoubleSnap);\n  x10_triggerFuncs.emplace(\"InPosition\", &CAi::InPosition);\n  x10_triggerFuncs.emplace(\"ShouldTurn\", &CAi::ShouldTurn);\n  x10_triggerFuncs.emplace(\"HitSomething\", &CAi::HitSomething);\n  x10_triggerFuncs.emplace(\"ShouldJumpBack\", &CAi::ShouldJumpBack);\n  x10_triggerFuncs.emplace(\"Stuck\", &CAi::Stuck);\n  x10_triggerFuncs.emplace(\"NoPathNodes\", &CAi::NoPathNodes);\n  x10_triggerFuncs.emplace(\"Landed\", &CAi::Landed);\n  x10_triggerFuncs.emplace(\"HearShot\", &CAi::HearShot);\n  x10_triggerFuncs.emplace(\"HearPlayer\", &CAi::HearPlayer);\n  x10_triggerFuncs.emplace(\"CoverCheck\", &CAi::CoverCheck);\n  x10_triggerFuncs.emplace(\"CoverFind\", &CAi::CoverFind);\n  x10_triggerFuncs.emplace(\"CoverBlown\", &CAi::CoverBlown);\n  x10_triggerFuncs.emplace(\"CoverNearlyBlown\", &CAi::CoverNearlyBlown);\n  x10_triggerFuncs.emplace(\"CoveringFire\", &CAi::CoveringFire);\n  x10_triggerFuncs.emplace(\"GotUp\", &CAi::GotUp);\n  x10_triggerFuncs.emplace(\"LineOfSight\", &CAi::LineOfSight);\n  x10_triggerFuncs.emplace(\"AggressionCheck\", &CAi::AggressionCheck);\n  x10_triggerFuncs.emplace(\"AttackOver\", &CAi::AttackOver);\n  x10_triggerFuncs.emplace(\"ShouldTaunt\", &CAi::ShouldTaunt);\n  x10_triggerFuncs.emplace(\"Inside\", &CAi::Inside);\n  x10_triggerFuncs.emplace(\"ShouldFire\", &CAi::ShouldFire);\n  x10_triggerFuncs.emplace(\"ShouldFlinch\", &CAi::ShouldFlinch);\n  x10_triggerFuncs.emplace(\"PatrolPathOver\", &CAi::PatrolPathOver);\n  x10_triggerFuncs.emplace(\"ShouldDodge\", &CAi::ShouldDodge);\n  x10_triggerFuncs.emplace(\"ShouldRetreat\", &CAi::ShouldRetreat);\n  x10_triggerFuncs.emplace(\"ShouldCrouch\", &CAi::ShouldCrouch);\n  x10_triggerFuncs.emplace(\"ShouldMove\", &CAi::ShouldMove);\n  x10_triggerFuncs.emplace(\"ShotAt\", &CAi::ShotAt);\n  x10_triggerFuncs.emplace(\"HasTargetingPoint\", &CAi::HasTargetingPoint);\n  x10_triggerFuncs.emplace(\"ShouldWallHang\", &CAi::ShouldWallHang);\n  x10_triggerFuncs.emplace(\"SetAIStage\", &CAi::SetAIStage);\n  x10_triggerFuncs.emplace(\"AIStage\", &CAi::AIStage);\n  x10_triggerFuncs.emplace(\"StartAttack\", &CAi::StartAttack);\n  x10_triggerFuncs.emplace(\"BreakAttack\", &CAi::BreakAttack);\n  x10_triggerFuncs.emplace(\"ShouldStrafe\", &CAi::ShouldStrafe);\n  x10_triggerFuncs.emplace(\"ShouldSpecialAttack\", &CAi::ShouldSpecialAttack);\n  x10_triggerFuncs.emplace(\"LostInterest\", &CAi::LostInterest);\n  x10_triggerFuncs.emplace(\"CodeTrigger\", &CAi::CodeTrigger);\n  x10_triggerFuncs.emplace(\"BounceFind\", &CAi::BounceFind);\n  x10_triggerFuncs.emplace(\"Random\", &CAi::Random);\n  x10_triggerFuncs.emplace(\"FixedRandom\", &CAi::FixedRandom);\n  x10_triggerFuncs.emplace(\"IsDizzy\", &CAi::IsDizzy);\n  x10_triggerFuncs.emplace(\"ShouldCallForBackup\", &CAi::ShouldCallForBackup);\n\n  CAi::CreateFuncLookup(this);\n}\n\nCAiStateFunc CAiFuncMap::GetStateFunc(std::string_view func) const {\n  const auto iter = x0_stateFuncs.find(func);\n\n  if (iter == x0_stateFuncs.cend()) {\n    return nullptr;\n  }\n\n  return iter->second;\n}\n\nCAiTriggerFunc CAiFuncMap::GetTriggerFunc(std::string_view func) const {\n  const auto iter = x10_triggerFuncs.find(func);\n\n  if (iter == x10_triggerFuncs.cend()) {\n    return nullptr;\n  }\n\n  return iter->second;\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CAiFuncMap.hpp",
    "content": "#pragma once\n\n#include <string_view>\n#include <unordered_map>\n\nnamespace metaforce {\nclass CAi;\nclass CStateManager;\n\nenum class EStateMsg { Activate = 0, Update = 1, Deactivate = 2 };\n\nusing CAiStateFunc = void (CAi::*)(CStateManager&, EStateMsg, float);\nusing CAiTriggerFunc = bool (CAi::*)(CStateManager&, float);\n\nclass CAiFuncMap {\n  std::unordered_map<std::string_view, CAiStateFunc> x0_stateFuncs;\n  std::unordered_map<std::string_view, CAiTriggerFunc> x10_triggerFuncs;\n\npublic:\n  CAiFuncMap();\n  CAiStateFunc GetStateFunc(std::string_view func) const;\n  CAiTriggerFunc GetTriggerFunc(std::string_view func) const;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CAmbientAI.cpp",
    "content": "#include \"Runtime/World/CAmbientAI.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCAmbientAI::CAmbientAI(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                       CModelData&& mData, const zeus::CAABox& aabox, const CMaterialList& matList, float mass,\n                       const CHealthInfo& hInfo, const CDamageVulnerability& dVuln, const CActorParameters& aParms,\n                       float alertRange, float impactRange, s32 alertAnim, s32 impactAnim, bool active)\n: CPhysicsActor(uid, active, name, info, xf, std::move(mData), matList, aabox, SMoverData(mass), aParms, 0.3f, 0.1f)\n, x258_initialHealthInfo(hInfo)\n, x260_healthInfo(hInfo)\n, x268_dVuln(dVuln)\n, x2d4_alertRange(alertRange)\n, x2d8_impactRange(impactRange)\n, x2dc_defaultAnim(GetModelData()->GetAnimationData()->GetDefaultAnimation())\n, x2e0_alertAnim(alertAnim)\n, x2e4_impactAnim(impactAnim) {\n  GetModelData()->GetAnimationData()->EnableLooping(true);\n}\n\nvoid CAmbientAI::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CAmbientAI::Think(float dt, CStateManager& mgr) {\n  if (!GetActive())\n    return;\n\n  if (GetModelData() && GetModelData()->GetAnimationData() != nullptr) {\n    bool hasAnimTime = GetModelData()->GetAnimationData()->IsAnimTimeRemaining(\n        dt - FLT_EPSILON, \"Whole Body\"sv);\n    bool isLooping = GetModelData()->GetIsLoop();\n\n    if (hasAnimTime || isLooping) {\n      x2e8_25_animating = true;\n      SAdvancementDeltas deltas = UpdateAnimation(dt, mgr, true);\n      MoveToOR(deltas.x0_posDelta, dt);\n      RotateToOR(deltas.xc_rotDelta, dt);\n    }\n\n    if (hasAnimTime) {\n    } else if (x2e8_25_animating && !isLooping) {\n      SendScriptMsgs(EScriptObjectState::MaxReached, mgr, EScriptObjectMessage::None);\n      x2e8_25_animating = false;\n    }\n  }\n\n  bool inAlertRange =\n      (mgr.GetPlayer().GetTranslation() - GetTranslation()).magnitude() < x2d4_alertRange;\n  bool inImpactRange =\n      (mgr.GetPlayer().GetTranslation() - GetTranslation()).magnitude() < x2d8_impactRange;\n\n  switch (x2d0_animState) {\n  case EAnimationState::Ready: {\n    if (inAlertRange) {\n      x2d0_animState = EAnimationState::Alert;\n      GetModelData()->GetAnimationData()->SetAnimation(CAnimPlaybackParms(x2e0_alertAnim, -1, 1.f, true),\n                                                 false);\n      GetModelData()->EnableLooping(true);\n      RandomizePlaybackRate(mgr);\n    }\n    break;\n  }\n  case EAnimationState::Alert: {\n    if (!inAlertRange) {\n      x2d0_animState = EAnimationState::Ready;\n      GetModelData()->GetAnimationData()->SetAnimation(\n          CAnimPlaybackParms(x2dc_defaultAnim, -1, 1.f, true), false);\n      GetModelData()->EnableLooping(true);\n      RandomizePlaybackRate(mgr);\n    } else if (inImpactRange) {\n      SendScriptMsgs(EScriptObjectState::Dead, mgr, EScriptObjectMessage::None);\n      RemoveEmitter();\n      SetActive(false);\n    }\n    break;\n  }\n  case EAnimationState::Impact: {\n    if (!x2e8_25_animating) {\n      x2d0_animState = EAnimationState::Ready;\n      GetModelData()->GetAnimationData()->SetAnimation(\n          CAnimPlaybackParms(x2dc_defaultAnim, -1, 1.f, true), false);\n      GetModelData()->EnableLooping(true);\n      RandomizePlaybackRate(mgr);\n    }\n    break;\n  }\n  }\n\n  if (x2e8_24_dead) {\n    return;\n  }\n  CHealthInfo* hInfo = HealthInfo(mgr);\n  if (hInfo->GetHP() <= 0.f) {\n    x2e8_24_dead = true;\n    SendScriptMsgs(EScriptObjectState::Dead, mgr, EScriptObjectMessage::None);\n    RemoveEmitter();\n    SetActive(false);\n  }\n}\n\nvoid CAmbientAI::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  switch (msg) {\n  case EScriptObjectMessage::Reset: {\n    if (!GetActive())\n      SetActive(true);\n    x2d0_animState = EAnimationState::Ready;\n    GetModelData()->GetAnimationData()->SetAnimation(CAnimPlaybackParms(x2dc_defaultAnim, -1, 1.f, true), false);\n    GetModelData()->GetAnimationData()->EnableLooping(true);\n    RandomizePlaybackRate(mgr);\n    x2e8_24_dead = false;\n    x260_healthInfo = x258_initialHealthInfo;\n    break;\n  }\n  case EScriptObjectMessage::InitializedInArea:\n    RandomizePlaybackRate(mgr);\n    break;\n  case EScriptObjectMessage::Damage: {\n    if (GetActive()) {\n      x2d0_animState = EAnimationState::Impact;\n      GetModelData()->GetAnimationData()->SetAnimation(CAnimPlaybackParms(x2e4_impactAnim, -1, 1.f, true), false);\n      GetModelData()->GetAnimationData()->EnableLooping(false);\n      RandomizePlaybackRate(mgr);\n    }\n    break;\n  }\n  default:\n    break;\n  }\n  CPhysicsActor::AcceptScriptMsg(msg, uid, mgr);\n}\n\nstd::optional<zeus::CAABox> CAmbientAI::GetTouchBounds() const {\n  if (GetActive()) {\n    return {GetBoundingBox()};\n  }\n  return std::nullopt;\n}\n\nvoid CAmbientAI::RandomizePlaybackRate(CStateManager& mgr) {\n  GetModelData()->GetAnimationData()->MultiplyPlaybackRate(0.4f * mgr.GetActiveRandom()->Float() + 0.8f);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CAmbientAI.hpp",
    "content": "#pragma once\n\n#include <optional>\n#include <string_view>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/World/CDamageVulnerability.hpp\"\n#include \"Runtime/World/CPhysicsActor.hpp\"\n\n#include <zeus/CAABox.hpp>\n\nnamespace zeus {\nclass CTransform;\n}\n\nnamespace metaforce {\nclass CAmbientAI : public CPhysicsActor {\n  enum class EAnimationState { Ready, Alert, Impact };\n\n  CHealthInfo x258_initialHealthInfo;\n  CHealthInfo x260_healthInfo;\n  CDamageVulnerability x268_dVuln;\n  EAnimationState x2d0_animState = EAnimationState::Ready;\n  float x2d4_alertRange;\n  float x2d8_impactRange;\n  s32 x2dc_defaultAnim;\n  s32 x2e0_alertAnim;\n  s32 x2e4_impactAnim;\n  bool x2e8_24_dead : 1 = false;\n  bool x2e8_25_animating : 1 = false;\n\npublic:\n  DEFINE_ENTITY\n  CAmbientAI(TUniqueId, std::string_view, const CEntityInfo&, const zeus::CTransform&, CModelData&&,\n             const zeus::CAABox&, const CMaterialList&, float, const CHealthInfo&, const CDamageVulnerability&,\n             const CActorParameters&, float, float, s32, s32, bool);\n\n  void Accept(IVisitor&) override;\n  void Think(float, CStateManager&) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  CHealthInfo* HealthInfo(CStateManager&) override { return &x260_healthInfo; }\n  const CDamageVulnerability* GetDamageVulnerability() const override { return &x268_dVuln; }\n  std::optional<zeus::CAABox> GetTouchBounds() const override;\n  void Touch(CActor&, CStateManager&) override {}\n  void RandomizePlaybackRate(CStateManager&);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CAnimationParameters.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Streams/CInputStream.hpp\"\n\nnamespace metaforce {\n\nclass CAnimationParameters {\n  CAssetId x0_ancs;\n  u32 x4_charIdx = -1;\n  u32 x8_defaultAnim = -1;\n\npublic:\n  CAnimationParameters() = default;\n  CAnimationParameters(CAssetId ancs, u32 charIdx, u32 defaultAnim)\n  : x0_ancs(ancs), x4_charIdx(charIdx), x8_defaultAnim(defaultAnim) {}\n  explicit CAnimationParameters(CInputStream& in)\n  : x0_ancs(in.Get<CAssetId>()), x4_charIdx(in.ReadLong()), x8_defaultAnim(in.ReadLong()) {}\n\n  CAssetId GetACSFile() const { return x0_ancs; }\n  u32 GetCharacter() const { return x4_charIdx; }\n  u32 GetInitialAnimation() const { return x8_defaultAnim; }\n  void SetCharacter(u32 charIdx) { x4_charIdx = charIdx; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CDamageInfo.cpp",
    "content": "#include \"Runtime/World/CDamageInfo.hpp\"\n\n#include \"Runtime/World/CDamageVulnerability.hpp\"\n\nnamespace metaforce {\n\nCDamageInfo::CDamageInfo(const SShotParam& other)\n: x0_weaponMode(CWeaponMode(EWeaponType(other.x0_weaponType), other.x4_24_charged, other.x4_25_combo, other.x4_26_instaKill))\n, x8_damage(other.x8_damage)\n, xc_radiusDamage(other.xc_radiusDamage)\n, x10_radius(other.x10_radius)\n, x14_knockback(other.x14_knockback)\n, x18_24_noImmunity(other.x18_24_noImmunity) {}\n\nCDamageInfo& CDamageInfo::operator=(const SShotParam& other) {\n  x0_weaponMode =\n      CWeaponMode(EWeaponType(other.x0_weaponType), other.x4_24_charged, other.x4_25_combo, other.x4_26_instaKill);\n  x8_damage = other.x8_damage;\n  xc_radiusDamage = other.xc_radiusDamage;\n  x10_radius = other.x10_radius;\n  x14_knockback = other.x14_knockback;\n  x18_24_noImmunity = other.x18_24_noImmunity;\n  return *this;\n}\n\nfloat CDamageInfo::GetDamage(const CDamageVulnerability& dVuln) const {\n  EVulnerability vuln = dVuln.GetVulnerability(x0_weaponMode, false);\n  if (vuln == EVulnerability::Deflect)\n    return 0.f;\n  else if (vuln == EVulnerability::Weak)\n    return 2.f * x8_damage;\n\n  return x8_damage;\n}\n\nfloat CDamageInfo::GetRadiusDamage(const CDamageVulnerability& dVuln) const {\n  EVulnerability vuln = dVuln.GetVulnerability(x0_weaponMode, false);\n  if (vuln == EVulnerability::Deflect) {\n    return 0.f;\n  }\n  if (vuln == EVulnerability::Weak) {\n    return 2.f * xc_radiusDamage;\n  }\n\n  return xc_radiusDamage;\n}\n\nCDamageInfo::CDamageInfo(const CDamageInfo& other, float dt) {\n  x0_weaponMode = other.x0_weaponMode;\n  x8_damage = other.x8_damage * (60.f * dt);\n  xc_radiusDamage = x8_damage;\n  x10_radius = other.x10_radius;\n  x14_knockback = other.x14_knockback;\n  x18_24_noImmunity = true;\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CDamageInfo.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Weapon/CWeaponMgr.hpp\"\n#include \"Runtime/Weapon/CWeaponMode.hpp\"\n\nnamespace metaforce {\nstruct SShotParam;\nclass CDamageVulnerability;\nclass CDamageInfo {\n  CWeaponMode x0_weaponMode;\n  float x8_damage = 0.f;\n  float xc_radiusDamage = 0.f;\n  float x10_radius = 0.f;\n  float x14_knockback = 0.f;\n  bool x18_24_noImmunity : 1 = false;\n\npublic:\n  constexpr CDamageInfo() = default;\n  explicit CDamageInfo(CInputStream& in) {\n    in.ReadLong();\n    x0_weaponMode = CWeaponMode(EWeaponType(in.ReadLong()));\n    x8_damage = in.ReadFloat();\n    xc_radiusDamage = x8_damage;\n    x10_radius = in.ReadFloat();\n    x14_knockback = in.ReadFloat();\n  }\n  constexpr CDamageInfo(const CWeaponMode& mode, float damage, float radius, float knockback)\n  : x0_weaponMode(mode), x8_damage(damage), xc_radiusDamage(damage), x10_radius(radius), x14_knockback(knockback) {}\n\n  constexpr CDamageInfo(const CDamageInfo&) = default;\n  constexpr CDamageInfo& operator=(const CDamageInfo&) = default;\n\n  constexpr CDamageInfo(CDamageInfo&&) = default;\n  constexpr CDamageInfo& operator=(CDamageInfo&&) = default;\n\n  CDamageInfo(const CDamageInfo&, float);\n  explicit CDamageInfo(const SShotParam& other);\n  CDamageInfo& operator=(const SShotParam& other);\n\n  const CWeaponMode& GetWeaponMode() const { return x0_weaponMode; }\n  void SetWeaponMode(const CWeaponMode& mode) { x0_weaponMode = mode; }\n  float GetRadius() const { return x10_radius; }\n  void SetRadius(float r) { x10_radius = r; }\n  float GetKnockBackPower() const { return x14_knockback; }\n  void SetKnockBackPower(float k) { x14_knockback = k; }\n  float GetDamage() const { return x8_damage; }\n  void SetDamage(float d) { x8_damage = d; }\n  float GetDamage(const CDamageVulnerability& dVuln) const;\n  float GetRadiusDamage() const { return xc_radiusDamage; }\n  void SetRadiusDamage(float r) { xc_radiusDamage = r; }\n  float GetRadiusDamage(const CDamageVulnerability& dVuln) const;\n  bool NoImmunity() const { return x18_24_noImmunity; }\n  void SetNoImmunity(bool b) { x18_24_noImmunity = b; }\n  void MultiplyDamage(float m) {\n    x8_damage *= m;\n    xc_radiusDamage *= m;\n    x14_knockback *= m;\n  }\n  void MultiplyDamageAndRadius(float m) {\n    x8_damage *= m;\n    xc_radiusDamage *= m;\n    x10_radius *= m;\n    x14_knockback *= m;\n  }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CDamageVulnerability.cpp",
    "content": "#include \"Runtime/World/CDamageVulnerability.hpp\"\n\nnamespace metaforce {\n\nconst CDamageVulnerability CDamageVulnerability::sNormalVulnerability(\n    EVulnerability::Normal, EVulnerability::Normal, EVulnerability::Normal, EVulnerability::Normal,\n    EVulnerability::Normal, EVulnerability::Normal, EVulnerability::Normal, EVulnerability::Normal,\n    EVulnerability::Normal, EVulnerability::Normal, EVulnerability::Normal, EVulnerability::Normal,\n    EVulnerability::Normal, EVulnerability::Normal, EVulnerability::Normal, EVulnerability::Normal,\n    EVulnerability::Normal, EVulnerability::Normal, EVulnerability::Normal, EVulnerability::Normal,\n    EVulnerability::Normal, EVulnerability::Normal, EVulnerability::Normal, EDeflectType::None);\n\nconst CDamageVulnerability CDamageVulnerability::sImmuneVulnerability(\n    EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect,\n    EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect,\n    EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect,\n    EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect,\n    EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect,\n    EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EDeflectType::None);\n\nconst CDamageVulnerability CDamageVulnerability::sReflectVulnerability(\n    EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect,\n    EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect,\n    EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect,\n    EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect,\n    EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect,\n    EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EDeflectType::One);\n\nconst CDamageVulnerability CDamageVulnerability::sPassThroughVulnerability(\n    EVulnerability::PassThrough, EVulnerability::PassThrough, EVulnerability::PassThrough, EVulnerability::PassThrough,\n    EVulnerability::PassThrough, EVulnerability::PassThrough, EVulnerability::PassThrough, EVulnerability::PassThrough,\n    EVulnerability::PassThrough, EVulnerability::PassThrough, EVulnerability::PassThrough, EVulnerability::PassThrough,\n    EVulnerability::PassThrough, EVulnerability::PassThrough, EVulnerability::PassThrough, EVulnerability::PassThrough,\n    EVulnerability::PassThrough, EVulnerability::PassThrough, EVulnerability::PassThrough, EVulnerability::PassThrough,\n    EVulnerability::PassThrough, EVulnerability::PassThrough, EVulnerability::PassThrough, EDeflectType::None);\n\nstatic constexpr bool is_deflect_direct(EVulnerability vuln) {\n  return vuln == EVulnerability::Deflect ||\n         (EVulnerability(u32(vuln) - u32(EVulnerability::DirectWeak)) < EVulnerability::Deflect) ||\n         vuln == EVulnerability::DirectImmune;\n}\n\nstatic constexpr bool is_normal_or_weak(EVulnerability vuln) {\n  return vuln == EVulnerability::Weak || vuln == EVulnerability::Normal;\n}\nstatic constexpr bool is_not_deflect(EVulnerability vuln) { return vuln != EVulnerability::Deflect; }\n\nvoid CDamageVulnerability::ConstructNew(CInputStream& in, int propCount) {\n  propCount -= 3;\n\n  for (int i = 0; i < std::min(propCount, 15); ++i) {\n    x0_normal[i] = EVulnerability(in.ReadLong());\n  }\n\n  if (propCount < 15) {\n    for (int i = propCount; i < 15; ++i) {\n      x0_normal[i] = EVulnerability::Deflect;\n    }\n  }\n\n  for (int i = 15; i < propCount; ++i) {\n    in.ReadLong();\n  }\n\n  x5c_deflected = EDeflectType(in.ReadLong());\n  in.ReadLong();\n  for (int i = 0; i < 4; ++i) {\n    x3c_charged[i] = EVulnerability(in.ReadLong());\n  }\n\n  x60_chargedDeflected = EDeflectType(in.ReadLong());\n  in.ReadLong();\n  for (int i = 0; i < 4; ++i) {\n    x4c_combo[i] = EVulnerability(in.ReadLong());\n  }\n\n  x64_comboDeflected = EDeflectType(in.ReadLong());\n}\n\nCDamageVulnerability::CDamageVulnerability(CInputStream& in) {\n  u32 propCount = in.ReadLong();\n  if (propCount == 11) {\n    for (int i = 0; i < 15; ++i) {\n      x0_normal[i] = EVulnerability(in.ReadLong());\n    }\n\n    x5c_deflected = EDeflectType(in.ReadLong());\n    x3c_charged[0] = x0_normal[0];\n    x4c_combo[0] = x0_normal[0];\n    x3c_charged[1] = x0_normal[1];\n    x4c_combo[1] = x0_normal[1];\n    x3c_charged[2] = x0_normal[2];\n    x4c_combo[2] = x0_normal[2];\n    x3c_charged[3] = x0_normal[3];\n    x4c_combo[3] = x0_normal[3];\n  } else {\n    ConstructNew(in, propCount);\n  }\n}\n\nEDeflectType CDamageVulnerability::GetDeflectionType(const CWeaponMode& mode) const {\n  if (mode.IsCharged()) {\n    return x60_chargedDeflected;\n  }\n  if (mode.IsComboed()) {\n    return x64_comboDeflected;\n  }\n  return x5c_deflected;\n}\n\nstatic inline bool check_hurts(EVulnerability vuln, bool direct) {\n  return direct == 0\n             ? (is_normal_or_weak(vuln) || vuln == EVulnerability::DirectWeak || vuln == EVulnerability::DirectNormal)\n             : is_normal_or_weak(vuln);\n}\n\nbool CDamageVulnerability::WeaponHurts(const CWeaponMode& mode, bool ignoreDirect) const {\n  if (mode.GetType() < EWeaponType::Power || mode.GetType() > EWeaponType::OrangePhazon) {\n    return false;\n  }\n\n  if (mode.IsInstantKill()) {\n    return true;\n  }\n\n  bool normalHurts = check_hurts(x0_normal[u32(mode.GetType())], ignoreDirect);\n  bool chargedHurts =\n      (mode.GetType() < EWeaponType::Bomb) ? check_hurts(x3c_charged[u32(mode.GetType())], ignoreDirect) : true;\n  bool comboedHurts =\n      (mode.GetType() < EWeaponType::Bomb) ? check_hurts(x4c_combo[u32(mode.GetType())], ignoreDirect) : true;\n\n  return (normalHurts && !mode.IsCharged() && !mode.IsComboed()) || (chargedHurts && mode.IsCharged()) ||\n         (comboedHurts && mode.IsComboed());\n}\n\nstatic inline bool check_hits(EVulnerability vuln, bool direct) {\n  if (!direct) {\n    return is_not_deflect(vuln);\n  }\n  if (vuln == EVulnerability::Deflect ||\n      (static_cast<EVulnerability>(static_cast<u32>(vuln) - static_cast<u32>(EVulnerability::DirectWeak)) <=\n       EVulnerability::Normal) ||\n      vuln == EVulnerability::DirectImmune) {\n    return false;\n  }\n  return true;\n}\n\nbool CDamageVulnerability::WeaponHits(const CWeaponMode& mode, bool checkDirect) const {\n  if (mode.GetType() < EWeaponType::Power || mode.GetType() > EWeaponType::OrangePhazon) {\n    return false;\n  }\n  if (mode.IsInstantKill()) {\n    return true;\n  }\n\n  bool normalHits = check_hits(x0_normal[u32(mode.GetType())], checkDirect);\n  bool chargedHits =\n      mode.GetType() < EWeaponType::Bomb ? check_hits(x3c_charged[u32(mode.GetType())], checkDirect) : true;\n  bool comboedHits =\n      mode.GetType() < EWeaponType::Bomb ? check_hits(x4c_combo[u32(mode.GetType())], checkDirect) : true;\n  bool result = false;\n  if ((normalHits && !mode.IsCharged() && !mode.IsComboed()) || (chargedHits && mode.IsCharged()) ||\n      (comboedHits && mode.IsComboed())) {\n    result = true;\n  }\n\n  return (normalHits && !mode.IsCharged() && !mode.IsComboed()) || (chargedHits && mode.IsCharged()) ||\n         (comboedHits && mode.IsComboed());\n}\n\ninline EVulnerability direct_to_normal(EVulnerability vuln) {\n  if (vuln == EVulnerability::DirectWeak) {\n    return EVulnerability::Weak;\n  }\n  if (vuln == EVulnerability::DirectNormal) {\n    return EVulnerability::Normal;\n  }\n  if (vuln == EVulnerability::DirectImmune) {\n    return EVulnerability::Immune;\n  }\n  return vuln;\n}\n\nEVulnerability CDamageVulnerability::GetVulnerability(const CWeaponMode& mode, bool ignoreDirect) const {\n  if (mode.GetType() < EWeaponType::Power || mode.GetType() > EWeaponType::OrangePhazon) {\n    return EVulnerability::Deflect;\n  }\n\n  if (mode.IsInstantKill()) {\n    return EVulnerability::Normal;\n  }\n\n  EVulnerability vuln = x0_normal[u32(mode.GetType())];\n  EWeaponType type = mode.GetType();\n  if (mode.IsCharged()) {\n    vuln = type < EWeaponType::Bomb ? x3c_charged[u32(type)] : EVulnerability::Normal;\n  }\n\n  if (mode.IsComboed()) {\n    vuln = type < EWeaponType::Bomb ? x4c_combo[u32(type)] : EVulnerability::Normal;\n  }\n  if (ignoreDirect == 1) {\n    return vuln;\n  }\n  return direct_to_normal(vuln);\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CDamageVulnerability.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Weapon/CWeaponMode.hpp\"\n\nnamespace metaforce {\n\nenum class EVulnerability { Weak, Normal, Deflect, Immune, PassThrough, DirectWeak, DirectNormal, DirectImmune };\nenum class EDeflectType { None, One, Two, Three, Four };\n\nclass CDamageVulnerability {\n  std::array<EVulnerability, 15> x0_normal;\n  std::array<EVulnerability, 4> x3c_charged;\n  std::array<EVulnerability, 4> x4c_combo;\n  EDeflectType x5c_deflected;\n  EDeflectType x60_chargedDeflected{};\n  EDeflectType x64_comboDeflected{};\n\n  void ConstructNew(CInputStream& in, int propCount);\n\n  static const CDamageVulnerability sNormalVulnerability;\n  static const CDamageVulnerability sImmuneVulnerability;\n  static const CDamageVulnerability sReflectVulnerability;\n  static const CDamageVulnerability sPassThroughVulnerability;\n\npublic:\n  explicit CDamageVulnerability(CInputStream& in);\n\n  constexpr CDamageVulnerability(EVulnerability power, EVulnerability ice, EVulnerability wave, EVulnerability plasma,\n                                 EVulnerability bomb, EVulnerability powerBomb, EVulnerability missile,\n                                 EVulnerability boostBall, EVulnerability phazon, EVulnerability enemyWp1,\n                                 EVulnerability enemyWp2, EVulnerability enemyWp3, EVulnerability enemyWp4,\n                                 EVulnerability v1, EVulnerability v2, EDeflectType deflectType)\n  : x0_normal({power, ice, wave, plasma, bomb, powerBomb, missile, boostBall, phazon, enemyWp1, enemyWp2, enemyWp3,\n               enemyWp4, v1, v2})\n  , x3c_charged({x0_normal[0], x0_normal[1], x0_normal[2], x0_normal[3]})\n  , x4c_combo({x0_normal[0], x0_normal[1], x0_normal[2], x0_normal[3]})\n  , x5c_deflected(deflectType) {}\n\n  constexpr CDamageVulnerability(EVulnerability power, EVulnerability ice, EVulnerability wave, EVulnerability plasma,\n                                 EVulnerability bomb, EVulnerability powerBomb, EVulnerability missile,\n                                 EVulnerability boostBall, EVulnerability phazon, EVulnerability enemyWp1,\n                                 EVulnerability enemyWp2, EVulnerability enemyWp3, EVulnerability enemyWp4,\n                                 EVulnerability v1, EVulnerability v2, EVulnerability chargedPower,\n                                 EVulnerability chargedIce, EVulnerability chargedWave, EVulnerability chargedPlasma,\n                                 EVulnerability superMissile, EVulnerability iceSpreader, EVulnerability waveBuster,\n                                 EVulnerability flameThrower, EDeflectType deflected)\n  : x0_normal({power, ice, wave, plasma, bomb, powerBomb, missile, boostBall, phazon, enemyWp1, enemyWp2, enemyWp3,\n               enemyWp4, v1, v2})\n  , x3c_charged({chargedPower, chargedIce, chargedWave, chargedPlasma})\n  , x4c_combo({superMissile, iceSpreader, waveBuster, flameThrower})\n  , x5c_deflected(deflected) {}\n\n  EDeflectType GetDeflectionType(const CWeaponMode& mode) const;\n\n  bool WeaponHurts(const CWeaponMode&, bool ignoreDirect) const;\n  bool WeaponHits(const CWeaponMode& mode, bool checkDirect) const;\n  EVulnerability GetVulnerability(const CWeaponMode& mode, bool ignoreDirect) const;\n\n  static const CDamageVulnerability& NormalVulnerabilty() { return sNormalVulnerability; }\n  static const CDamageVulnerability& ImmuneVulnerabilty() { return sImmuneVulnerability; }\n  static const CDamageVulnerability& ReflectVulnerabilty() { return sReflectVulnerability; }\n  static const CDamageVulnerability& PassThroughVulnerabilty() { return sPassThroughVulnerability; }\n\n  // Used in ImGuiEntitySupport\n  void ImGuiEditWindow(const char* title, bool& open);\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CDestroyableRock.cpp",
    "content": "#include \"Runtime/World/CDestroyableRock.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/CPlayerState.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\nnamespace metaforce {\n\nCDestroyableRock::CDestroyableRock(TUniqueId id, bool active, std::string_view name, const CEntityInfo& info,\n                                   const zeus::CTransform& xf, CModelData&& modelData, float mass,\n                                   const CHealthInfo& health, const CDamageVulnerability& vulnerability,\n                                   const CMaterialList& matList, CAssetId fsm, const CActorParameters& actParams,\n                                   const CStaticRes& phazonModel, s32 w1)\n: CAi(id, active, name, info, xf, std::move(modelData), modelData.GetBounds(), mass, health, vulnerability, matList,\n      fsm, actParams, 0.3f, 0.8f)\n, x2d8_phazonModel(phazonModel)\n, x32c_thermalMag(actParams.GetThermalMag())\n, x338_healthInfo(health)\n, x341_(w1 != 0) {\n  /* This is now in UsePhazonModel\n  x2d8_phazonModel->SetSortThermal(true);\n   */\n  CreateShadow(false);\n}\n\nvoid CDestroyableRock::Accept(metaforce::IVisitor& visitor) { visitor.Visit(this); }\nvoid CDestroyableRock::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) {\n  if (GetActive()) {\n    if (mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::Thermal) {\n      xb4_drawFlags = CModelFlags(0, 0, 1, zeus::skWhite);\n    } else {\n      u8 r;\n      u8 g;\n      u8 b;\n      u8 a;\n      x330_.toRGBA8(r, g, b, a);\n      if ((r & g & b) == 0xFF) {\n        xb4_drawFlags = CModelFlags(0, 0, 3, zeus::skWhite);\n      } else {\n        x330_.a() = 1.f;\n        xb4_drawFlags = CModelFlags(2, 0, 3, x330_);\n      }\n    }\n  }\n\n  CActor::PreRender(mgr, frustum);\n}\nvoid CDestroyableRock::Think(float dt, CStateManager& mgr) {\n  if (!GetActive()) {\n    return;\n  }\n\n  float damageMag = x32c_thermalMag;\n  if (x324_ > 0.f) {\n    x324_ = std::max(0.f, x324_ - dt);\n    zeus::CColor tmpColor = zeus::CColor(0.5, 0.f, 0.f, 1.f);\n    // TODO: Use skBlack once CModelFlags is properly reverse engineered\n    x330_ = zeus::CColor::lerp(/*zeus::skBlack*/ zeus::skWhite, tmpColor, x324_);\n    damageMag = ((x335_usePhazonModel ? 0.5f : 0.25f) * x324_) + damageMag;\n  }\n  xd0_damageMag = damageMag;\n  CEntity::Think(dt, mgr);\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CDestroyableRock.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/World/CAi.hpp\"\n\nnamespace metaforce {\n\nclass CDestroyableRock : public CAi {\n\n  /* Used to be a CModelData */\n  CStaticRes x2d8_phazonModel;\n  float x324_ = 0.f;\n  float x328_ = 0.f;\n  float x32c_thermalMag;\n  zeus::CColor x330_ = zeus::skWhite;\n  bool x334_isCold = false;\n  bool x335_usePhazonModel = false;\n  CHealthInfo x338_healthInfo;\n  bool x340_;\n  bool x341_;\n\npublic:\n  DEFINE_ENTITY\n  CDestroyableRock(TUniqueId id, bool active, std::string_view name, const CEntityInfo& info,\n                   const zeus::CTransform& xf, CModelData&& modelData, float mass, const CHealthInfo& health,\n                   const CDamageVulnerability& vulnerability, const CMaterialList& matList, CAssetId fsm,\n                   const CActorParameters& actParams, const CStaticRes& phazonModel, s32);\n\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) override {\n    CAi::AcceptScriptMsg(msg, uid, mgr);\n    if (msg == EScriptObjectMessage::Registered) {\n      AddMaterial(EMaterialTypes::ProjectilePassthrough, mgr);\n      AddMaterial(EMaterialTypes::CameraPassthrough, mgr);\n    }\n  }\n  void Death(CStateManager& mgr, const zeus::CVector3f& direction, EScriptObjectState state) override {\n    x334_isCold = true;\n  }\n  void KnockBack(const zeus::CVector3f&, CStateManager&, const CDamageInfo& info, EKnockBackType type, bool inDeferred,\n                 float magnitude) override {}\n\n  zeus::CVector3f GetAimPosition(const CStateManager&, float) const override { return GetTranslation(); }\n  zeus::CVector3f GetOrbitPosition(const CStateManager&) const override { return GetTranslation(); }\n  std::optional<zeus::CAABox> GetTouchBounds() const override { return GetModelData()->GetBounds(GetTransform()); }\n  bool CanRenderUnsorted(const CStateManager&) const override { return true; }\n  void PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) override;\n  void TakeDamage(const zeus::CVector3f&, float) override {\n    x324_ = 1.f;\n    x328_ = 2.f;\n  }\n\n  void Think(float dt, CStateManager& mgr) override;\n\n  float Get_x324() const { return x324_; }\n  void SetThermalMag(float val) { x32c_thermalMag = val; }\n  void SetIsCold(bool v) { x334_isCold = v; }\n  bool IsUsingPhazonModel() const { return x335_usePhazonModel; }\n  void UsePhazonModel() {\n    SetModelData(std::make_unique<CModelData>(x2d8_phazonModel));\n    x335_usePhazonModel = true;\n    /* This used to be in the constructor, since we can't store CModelData directly in the class we must set it here */\n    GetModelData()->SetSortThermal(true);\n  }\n\n  void Set_x340(bool v) { x340_ = v; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CEffect.cpp",
    "content": "#include \"Runtime/World/CEffect.hpp\"\n\n#include \"Runtime/World/CActorParameters.hpp\"\n\nnamespace metaforce {\n\nCEffect::CEffect(TUniqueId uid, const CEntityInfo& info, bool active, std::string_view name, const zeus::CTransform& xf)\n: CActor(uid, active, name, info, xf, CModelData::CModelDataNull(), CMaterialList(EMaterialTypes::NoStepLogic),\n         CActorParameters::None(), kInvalidUniqueId) {}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CEffect.hpp",
    "content": "#pragma once\n\n#include \"Runtime/World/CActor.hpp\"\n\nnamespace metaforce {\n\nclass CEffect : public CActor {\npublic:\n  DEFINE_ENTITY\n  CEffect(TUniqueId uid, const CEntityInfo& info, bool active, std::string_view name, const zeus::CTransform& xf);\n\n  void AddToRenderer(const zeus::CFrustum&, CStateManager&) override {}\n  void Render(CStateManager&) override {}\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CEnergyDrainSource.cpp",
    "content": "#include \"Runtime/World/CEnergyDrainSource.hpp\"\n\nnamespace metaforce {\n\nCEnergyDrainSource::CEnergyDrainSource(TUniqueId src, float intensity) : x0_source(src), x4_intensity(intensity) {}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CEnergyDrainSource.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n\nnamespace metaforce {\nclass CEnergyDrainSource {\n  TUniqueId x0_source;\n  float x4_intensity;\n\npublic:\n  CEnergyDrainSource(TUniqueId src, float intensity);\n  TUniqueId GetEnergyDrainSourceId() const { return x0_source; }\n  void SetEnergyDrainIntensity(float in) { x4_intensity = in; }\n  float GetEnergyDrainIntensity() const { return x4_intensity; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CEntity.cpp",
    "content": "#include \"Runtime/World/CEntity.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n\nnamespace metaforce {\nconst std::vector<SConnection> CEntity::NullConnectionList;\n\nCEntity::CEntity(TUniqueId uniqueId, const CEntityInfo& info, bool active, std::string_view name)\n: x4_areaId(info.GetAreaId())\n, x8_uid(uniqueId)\n, xc_editorId(info.GetEditorId())\n, x10_name(name)\n, x20_conns(info.GetConnectionList())\n, x30_24_active(active)\n, x30_27_inUse(x4_areaId != kInvalidAreaId) {}\n\nvoid CEntity::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) {\n  switch (msg) {\n  case EScriptObjectMessage::Activate:\n    if (!GetActive()) {\n      SetActive(true);\n      SendScriptMsgs(EScriptObjectState::Active, stateMgr, EScriptObjectMessage::None);\n    }\n    break;\n  case EScriptObjectMessage::Deactivate:\n    if (GetActive()) {\n      SetActive(false);\n      SendScriptMsgs(EScriptObjectState::Inactive, stateMgr, EScriptObjectMessage::None);\n    }\n    break;\n  case EScriptObjectMessage::ToggleActive:\n    if (GetActive()) {\n      SetActive(false);\n      SendScriptMsgs(EScriptObjectState::Inactive, stateMgr, EScriptObjectMessage::None);\n    } else {\n      SetActive(true);\n      SendScriptMsgs(EScriptObjectState::Active, stateMgr, EScriptObjectMessage::None);\n    }\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CEntity::SendScriptMsgs(EScriptObjectState state, CStateManager& stateMgr, EScriptObjectMessage skipMsg) {\n  for (const SConnection& conn : x20_conns) {\n    if (conn.x0_state == state && conn.x4_msg != skipMsg) {\n      stateMgr.SendScriptMsg(x8_uid, conn.x8_objId, conn.x4_msg, state);\n    }\n  }\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CEntity.hpp",
    "content": "#pragma once\n\n#include <string>\n#include <vector>\n#include <set>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/World/CEntityInfo.hpp\"\n#include \"Runtime/World/ScriptObjectSupport.hpp\"\n\n#ifndef ENABLE_IMGUI\n#define ENABLE_IMGUI 1\n#endif\n\n#if ENABLE_IMGUI\n#define IMGUI_ENTITY_PROTOTYPES                                                                                        \\\n  std::string_view ImGuiType() override;                                                                               \\\n  void ImGuiInspect() override;\n#else\n#define IMGUI_ENTITY_PROTOTYPES\n#endif\n\n#define DEFINE_ENTITY IMGUI_ENTITY_PROTOTYPES\n\nnamespace metaforce {\nclass CStateManager;\nclass IVisitor;\n\nclass CEntity {\n  friend class CStateManager;\n  friend class CObjectList;\n  friend class ImGuiConsole;\n\nprotected:\n  TAreaId x4_areaId;\n  TUniqueId x8_uid;\n  TEditorId xc_editorId;\n  std::string x10_name;\n  std::vector<SConnection> x20_conns;\n  bool x30_24_active : 1;\n  bool x30_25_inGraveyard : 1 = false;\n  bool x30_26_scriptingBlocked : 1 = false;\n  bool x30_27_inUse : 1;\n\n  // Used in ImGuiConsole\n  bool m_debugSelected = false;\n  bool m_debugHovered = false;\n  const std::set<SConnection>* m_incomingConnections = nullptr;\n\npublic:\n  static const std::vector<SConnection> NullConnectionList;\n  virtual ~CEntity() = default;\n  CEntity(TUniqueId uid, const CEntityInfo& info, bool active, std::string_view name);\n  virtual void Accept(IVisitor& visitor) = 0;\n  virtual void PreThink(float dt, CStateManager& mgr) {}\n  virtual void Think(float dt, CStateManager& mgr) {}\n  virtual void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr);\n  virtual void SetActive(bool active) { x30_24_active = active; }\n\n  // Debugging utilities\n  virtual std::string_view ImGuiType();\n  virtual void ImGuiInspect();\n\n  bool GetActive() const { return x30_24_active; }\n  void ToggleActive() { x30_24_active ^= 1; }\n\n  bool IsInGraveyard() const { return x30_25_inGraveyard; }\n  void SetIsInGraveyard(bool in) { x30_25_inGraveyard = in; }\n  bool IsScriptingBlocked() const { return x30_26_scriptingBlocked; }\n  void SetIsScriptingBlocked(bool blocked) { x30_26_scriptingBlocked = blocked; }\n  bool IsInUse() const { return x30_27_inUse; }\n\n  TAreaId GetAreaId() const {\n    if (x30_27_inUse)\n      return x4_areaId;\n    return kInvalidAreaId;\n  }\n  TAreaId GetAreaIdAlways() const { return x4_areaId; }\n  TUniqueId GetUniqueId() const { return x8_uid; }\n  TEditorId GetEditorId() const { return xc_editorId; }\n  void SendScriptMsgs(EScriptObjectState state, CStateManager& stateMgr, EScriptObjectMessage msg);\n\n  std::vector<SConnection>& GetConnectionList() { return x20_conns; }\n  const std::vector<SConnection>& GetConnectionList() const { return x20_conns; }\n\n  std::string_view GetName() const { return x10_name; }\n  void SetIncomingConnectionList(const std::set<SConnection>* conns) { m_incomingConnections = conns; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CEntityInfo.hpp",
    "content": "#pragma once\n\n#include <vector>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/World/ScriptObjectSupport.hpp\"\n\nnamespace metaforce {\nstruct SConnection {\n  EScriptObjectState x0_state = EScriptObjectState::Any;\n  EScriptObjectMessage x4_msg = EScriptObjectMessage::None;\n  TEditorId x8_objId = kInvalidEditorId;\n  bool operator==(const SConnection& other) const {\n    return x0_state == other.x0_state && x4_msg == other.x4_msg && x8_objId == other.x8_objId;\n  }\n  bool operator<(const SConnection& other) const { return x8_objId < other.x8_objId; }\n};\n\nclass CEntityInfo {\n  TAreaId x0_areaId;\n  std::vector<SConnection> x4_conns;\n  TEditorId x14_editorId;\n\npublic:\n  CEntityInfo(TAreaId aid, std::vector<SConnection> conns, TEditorId eid = kInvalidEditorId)\n  : x0_areaId(aid), x4_conns(std::move(conns)), x14_editorId(eid) {}\n  TAreaId GetAreaId() const { return x0_areaId; }\n  std::vector<SConnection> GetConnectionList() const { return x4_conns; }\n  TEditorId GetEditorId() const { return x14_editorId; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CEnvFxManager.cpp",
    "content": "#include \"Runtime/World/CEnvFxManager.hpp\"\n\n#include \"Runtime/CRandom16.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Collision/CGameCollision.hpp\"\n#include \"Runtime/Collision/CInternalRayCastStructure.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Graphics/CTexture.hpp\"\n#include \"Runtime/World/CActor.hpp\"\n#include \"Runtime/World/CHUDBillboardEffect.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CScriptTrigger.hpp\"\n#include \"Runtime/World/CScriptWater.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nstatic rstl::reserved_vector<zeus::CVector2f, 256> g_SnowForces;\n\nCEnvFxManagerGrid::CEnvFxManagerGrid(const zeus::CVector2i& position, const zeus::CVector2i& extent,\n                                     std::vector<CVectorFixed8_8> initialParticles, int reserve, CEnvFxManager& parent)\n: x4_position(position), xc_extent(extent), x1c_particles(std::move(initialParticles)) {\n  x1c_particles.reserve(reserve);\n}\n\nCEnvFxManager::CEnvFxManager() {\n  x40_txtrEnvGradient = g_SimplePool->GetObj(\"TXTR_EnvGradient\");\n//  x40_txtrEnvGradient->GetBooTexture()->setClampMode(boo::TextureClampMode::ClampToEdge);\n  xb58_envRainSplash = g_SimplePool->GetObj(\"PART_EnvRainSplash\");\n  xb74_txtrSnowFlake = g_SimplePool->GetObj(\"TXTR_SnowFlake\");\n  xc48_underwaterFlake = g_SimplePool->GetObj(\"TXTR_UnderwaterFlake\");\n  CRandom16 random(0);\n//  CGraphics::CommitResources([this](boo::IGraphicsDataFactory::Context& ctx) {\n//    m_fogUniformBuf = ctx.newDynamicBuffer(boo::BufferUse::Uniform, sizeof(CGraphics::g_Fog), 1);\n    for (int i = 0; i < 8; ++i)\n      for (int j = 0; j < 8; ++j)\n        x50_grids.emplace_back(zeus::CVector2i{j * 2048, i * 2048}, zeus::CVector2i{2048, 2048},\n                               std::vector<CVectorFixed8_8>{}, 171, *this);\n//    return true;\n//  } BooTrace);\n  for (int i = 0; i < 16; ++i)\n    xb84_snowZDeltas.emplace_back(0.f, 0.f, random.Range(-2.f, -4.f));\n}\n\nvoid CEnvFxManager::SetSplashEffectRate(float rate, const CStateManager& mgr) {\n  if (TCastToPtr<CHUDBillboardEffect> splashEffect = mgr.ObjectById(xb68_envRainSplashId))\n    if (splashEffect->IsElementGen())\n      splashEffect->GetParticleGen()->SetGeneratorRate(rate);\n}\n\n/* Used to be MIDI scale */\nstatic float CalcRainVolume(float f) {\n  if (f < 0.1f)\n    return (f / 0.1f * 74.f) / 127.f;\n  else\n    return (f / 0.9f * 21.f + 74.f) / 127.f;\n}\n\n/* Used to be MIDI scale */\nstatic float CalcRainPitch(float f) { return f - 1.f; }\n\nvoid CEnvFxManager::UpdateRainSounds(const CStateManager& mgr) {\n  if (mgr.GetWorld()->GetNeededEnvFx() == EEnvFxType::Rain) {\n    zeus::CTransform camXf = mgr.GetCameraManager()->GetCurrentCameraTransform(mgr);\n    float rainVol = CalcRainVolume(x30_fxDensity);\n    if (!xb6a_rainSoundActive) {\n      xb6c_leftRainSound =\n          CSfxManager::AddEmitter(SFXsfx09F0, zeus::skZero3f, zeus::skZero3f, false, true, 0xff, kInvalidAreaId);\n      xb70_rightRainSound =\n          CSfxManager::AddEmitter(SFXsfx09F1, zeus::skZero3f, zeus::skZero3f, false, true, 0xff, kInvalidAreaId);\n      xb6a_rainSoundActive = true;\n    }\n    CSfxManager::UpdateEmitter(xb6c_leftRainSound, camXf.origin - camXf.basis[0], camXf.basis[0], rainVol);\n    CSfxManager::UpdateEmitter(xb70_rightRainSound, camXf.origin + camXf.basis[0], -camXf.basis[0], rainVol);\n    float rainPitch = CalcRainPitch(x30_fxDensity);\n    CSfxManager::PitchBend(xb6c_leftRainSound, rainPitch);\n    CSfxManager::PitchBend(xb70_rightRainSound, rainPitch);\n  } else if (xb6a_rainSoundActive) {\n    CSfxManager::RemoveEmitter(xb6c_leftRainSound);\n    CSfxManager::RemoveEmitter(xb70_rightRainSound);\n    xb6a_rainSoundActive = false;\n  }\n}\n\nzeus::CVector3f CEnvFxManager::GetParticleBoundsToWorldScale() const {\n  return (x0_particleBounds.max - x0_particleBounds.min) / 127.f;\n}\n\nzeus::CTransform CEnvFxManager::GetParticleBoundsToWorldTransform() const {\n  return zeus::CTransform::Translate(x18_focusCellPosition) * zeus::CTransform::Translate(zeus::CVector3f(-31.75f)) *\n         zeus::CTransform::Scale(GetParticleBoundsToWorldScale());\n}\n\nvoid CEnvFxManager::UpdateVisorSplash(CStateManager& mgr, float dt, const zeus::CTransform& camXf) {\n  EEnvFxType fxType = mgr.GetWorld()->GetNeededEnvFx();\n  if (xb68_envRainSplashId != kInvalidUniqueId)\n    if (TCastToPtr<CHUDBillboardEffect> splashEffect = mgr.ObjectById(xb68_envRainSplashId))\n      mgr.SetActorAreaId(*splashEffect, mgr.GetNextAreaId());\n  float camUpness = camXf.basis[1].dot(zeus::skUp);\n  float splashRateFactor;\n  if (x24_enableSplash)\n    splashRateFactor = std::max(0.f, camUpness) * x30_fxDensity;\n  else\n    splashRateFactor = 0.f;\n  float forwardRateFactor = 0.f;\n  if (x24_enableSplash && camUpness >= -0.1f) {\n    zeus::CVector3f pRelVel = mgr.GetPlayer().GetTransform().transposeRotate(mgr.GetPlayer().GetVelocity());\n    if (pRelVel.canBeNormalized()) {\n      float velMag = pRelVel.magnitude();\n      zeus::CVector3f normRelVel = pRelVel * (1.f / velMag);\n      forwardRateFactor = std::min(velMag / 60.f, 1.f) * normRelVel.dot(zeus::skForward);\n    }\n  }\n  float additionalFactor;\n  if (fxType == EEnvFxType::Rain)\n    additionalFactor = splashRateFactor + forwardRateFactor;\n  else\n    additionalFactor = 0.f;\n  SetSplashEffectRate(xb54_baseSplashRate + additionalFactor, mgr);\n  xb54_baseSplashRate = 0.f;\n}\n\nvoid CEnvFxManager::MoveWrapCells(s32 moveX, s32 moveY) {\n  if (moveX == 0 && moveY == 0)\n    return;\n  bool r5 = std::fabs(moveX) >= 1.f || std::fabs(moveY) >= 1.f;\n  s32 moveXMaj = moveX << 11;\n  s32 moveYMaj = moveY << 11;\n  for (int i = 0; i < 8; ++i) {\n    s32 r28 = i - moveY;\n    for (int j = 0; j < 8; ++j) {\n      CEnvFxManagerGrid& grid = x50_grids[i * 8 + j];\n      s32 r3 = j - moveX;\n      if (!r5) {\n        CEnvFxManagerGrid& otherGrid = x50_grids[r28 * 8 + r3];\n        grid.x14_block = otherGrid.x14_block;\n      } else {\n        grid.x0_24_blockDirty = true;\n      }\n      grid.x4_position =\n          zeus::CVector2i((moveXMaj + grid.x4_position.x) & 0x3fff, (moveYMaj + grid.x4_position.y) & 0x3fff);\n    }\n  }\n}\n\nvoid CEnvFxManager::CalculateSnowForces(const CVectorFixed8_8& zVec,\n                                        rstl::reserved_vector<CVectorFixed8_8, 256>& snowForces, EEnvFxType type,\n                                        const zeus::CVector3f& oopbtws, float dt) {\n  if (type == EEnvFxType::Snow) {\n    CVectorFixed8_8 vecf;\n    zeus::CVector3f vec;\n    for (int i = 255; i >= 0; --i) {\n      const zeus::CVector2f& force = g_SnowForces[i];\n      zeus::CVector3f delta = zeus::CVector3f(force * dt) * oopbtws;\n      vec += delta;\n      CVectorFixed8_8 vecf2(vec);\n      snowForces.push_back(vecf2 - vecf);\n      vecf = vecf2;\n    }\n\n    for (int i = 0; i < snowForces.size(); ++i)\n      snowForces[i] = snowForces[i] + zVec + CVectorFixed8_8(xb84_snowZDeltas[i & 0xf] * dt * oopbtws);\n  }\n}\n\nvoid CEnvFxManager::BuildBlockObjectList(EntityList& list, CStateManager& mgr) {\n  for (CEntity* ent : mgr.GetAllObjectList()) {\n    const TCastToConstPtr<CScriptTrigger> trig = ent;\n    if (trig && True(trig->GetTriggerFlags() & ETriggerFlags::BlockEnvironmentalEffects)) {\n      list.push_back(ent->GetUniqueId());\n    } else if (TCastToConstPtr<CScriptWater>(ent)) {\n      list.push_back(ent->GetUniqueId());\n    }\n  }\n}\n\nvoid CEnvFxManager::UpdateBlockedGrids(CStateManager& mgr, EEnvFxType type, const zeus::CTransform& camXf,\n                                       const zeus::CTransform& xf, const zeus::CTransform& invXf) {\n  zeus::CVector3f playerPos;\n  if (mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Unmorphed)\n    playerPos = camXf.origin;\n  else\n    playerPos = mgr.GetPlayer().GetBallPosition();\n  zeus::CVector2i localPlayerPos((invXf * playerPos * 256.f).toVec2f());\n  x2c_lastBlockedGridIdx = -1;\n  x24_enableSplash = false;\n  EntityList blockList;\n  bool blockListBuilt = false;\n  int blockedGrids = 0;\n  for (int i = 0; i < x50_grids.size(); ++i) {\n    CEnvFxManagerGrid& grid = x50_grids[i];\n    if (blockedGrids < 8 && grid.x0_24_blockDirty) {\n      if (type == EEnvFxType::UnderwaterFlake) {\n        grid.x14_block = std::make_pair(true, float(-FLT_MAX));\n      } else {\n        constexpr auto filter =\n            CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid, EMaterialTypes::Trigger},\n                                                {EMaterialTypes::ProjectilePassthrough, EMaterialTypes::SeeThrough});\n        zeus::CVector3f pos =\n            xf * zeus::CVector3f((grid.x4_position + grid.xc_extent * 0).toVec2f() / 256.f) + zeus::skUp * 500.f;\n        CRayCastResult result = CGameCollision::RayStaticIntersection(mgr, pos, zeus::skDown, 1000.f, filter);\n        if (result.IsValid()) {\n          if (!blockListBuilt) {\n            BuildBlockObjectList(blockList, mgr);\n            blockListBuilt = true;\n          }\n          for (TUniqueId id : blockList) {\n            if (TCastToConstPtr<CScriptTrigger> trig = mgr.GetObjectById(id)) {\n              if (auto tb = trig->GetTouchBounds()) {\n                CCollidableAABox caabb(*tb, {EMaterialTypes::Trigger});\n                CRayCastResult result2 = caabb.CastRayInternal({pos, zeus::skDown, 1000.f, {}, filter});\n                if (result2.IsValid() && result2.GetT() < result.GetT())\n                  result = result2;\n              }\n            }\n          }\n        }\n        ++blockedGrids;\n        grid.x14_block = std::make_pair(result.IsValid(), result.GetPoint().z());\n      }\n      grid.x0_24_blockDirty = false;\n    }\n    zeus::CVector2i gridEnd = grid.x4_position + grid.xc_extent;\n    if (localPlayerPos.x >= grid.x4_position.x && localPlayerPos.y >= grid.x4_position.y &&\n        localPlayerPos.x < gridEnd.x && localPlayerPos.y < gridEnd.y && grid.x14_block.first &&\n        grid.x14_block.second <= playerPos.z()) {\n      x24_enableSplash = true;\n      x2c_lastBlockedGridIdx = i;\n    }\n  }\n}\n\nvoid CEnvFxManager::CreateNewParticles(EEnvFxType type) {\n  int maxCellParticleCount;\n  if (type == EEnvFxType::Snow)\n    maxCellParticleCount = 0x1c98;\n  else if (type == EEnvFxType::Rain)\n    maxCellParticleCount = 0x2af8;\n  else if (type == EEnvFxType::UnderwaterFlake)\n    maxCellParticleCount = 0x1fd6;\n  else\n    maxCellParticleCount = 0;\n  maxCellParticleCount >>= 6;\n  int cellParticleCount = maxCellParticleCount * x30_fxDensity;\n  static u32 seedStash = 0;\n  CRandom16 random(seedStash);\n  for (auto it = x50_grids.rbegin(); it != x50_grids.rend(); ++it) {\n    if (it->x14_block.first) {\n      if (cellParticleCount > it->x1c_particles.size()) {\n        if (cellParticleCount > it->x1c_particles.capacity())\n          it->x1c_particles.reserve(maxCellParticleCount);\n        int remCellParticleCount = cellParticleCount - it->x1c_particles.size();\n        for (int i = 0; i < remCellParticleCount; ++i) {\n          int x = random.Range(0.f, float(it->xc_extent.x));\n          int y = random.Range(0.f, float(it->xc_extent.y));\n          int z = 256.f * random.Range(0.f, 63.f);\n          it->x1c_particles.emplace_back(x, y, z);\n        }\n      } else {\n        it->x1c_particles.resize(cellParticleCount);\n      }\n    }\n  }\n  seedStash = random.GetSeed();\n}\n\nvoid CEnvFxManager::UpdateSnowParticles(const rstl::reserved_vector<CVectorFixed8_8, 256>& snowForces) {\n  for (auto it = x50_grids.rbegin(); it != x50_grids.rend(); ++it) {\n    int forceIt = int(x28_firstSnowForce);\n    if (it->x14_block.first) {\n      for (auto pit = it->x1c_particles.rbegin(); pit != it->x1c_particles.rend(); ++pit) {\n        const CVectorFixed8_8& force = snowForces[forceIt];\n        forceIt = (forceIt + 1) & 0xff;\n        *pit = *pit + force;\n        pit->z = s16(pit->z & 0x3fff);\n      }\n    }\n  }\n}\n\nvoid CEnvFxManager::UpdateRainParticles(const CVectorFixed8_8& zVec, const zeus::CVector3f& oopbtws, float dt) {\n  s16 deltaZ = zVec.z + s16(-40.f * dt * oopbtws.z() * 256.f);\n  for (auto it = x50_grids.rbegin(); it != x50_grids.rend(); ++it)\n    for (auto pit = it->x1c_particles.rbegin(); pit != it->x1c_particles.rend(); ++pit)\n      pit->z = s16((pit->z + deltaZ) & 0x3fff);\n}\n\nvoid CEnvFxManager::UpdateUnderwaterParticles(const CVectorFixed8_8& zVec) {\n  for (auto it = x50_grids.rbegin(); it != x50_grids.rend(); ++it)\n    for (auto pit = it->x1c_particles.rbegin(); pit != it->x1c_particles.rend(); ++pit)\n      pit->z = s16((pit->z + zVec.z) & 0x3fff);\n}\n\nvoid CEnvFxManager::Update(float dt, CStateManager& mgr) {\n  EEnvFxType fxType = mgr.GetWorld()->GetNeededEnvFx();\n  zeus::CTransform camXf = mgr.GetCameraManager()->GetCurrentCameraTransform(mgr);\n  if (mgr.GetCameraManager()->GetFluidCounter() != 0) {\n    x2c_lastBlockedGridIdx = -1;\n    x24_enableSplash = false;\n    SetSplashEffectRate(0.f, mgr);\n  }\n  UpdateRainSounds(mgr);\n  UpdateVisorSplash(mgr, dt, camXf);\n  if (fxType == EEnvFxType::None) {\n    for (auto it = x50_grids.rbegin(); it != x50_grids.rend(); ++it)\n      if (it->x14_block.first)\n        it->x1c_particles = std::vector<CVectorFixed8_8>();\n  } else {\n    float densityDelta = x34_targetFxDensity - x30_fxDensity;\n    float densityDeltaDamper = std::min(std::fabs(densityDelta) / 0.15f, 1.f);\n    float maxDensityDelta = x38_maxDensityDeltaSpeed / 11000.f * dt;\n    if (std::fabs(densityDelta) > maxDensityDelta)\n      densityDelta = (densityDelta > 0.f ? 1.f : -1.f) * maxDensityDelta;\n    x30_fxDensity += densityDeltaDamper * densityDelta;\n    zeus::CVector3f pbtws = GetParticleBoundsToWorldScale();\n    zeus::CVector3f oopbtws = 1.f / pbtws;\n    zeus::CVector3f forwardPoint = camXf.basis[1] * 23.8125f + camXf.origin;\n    float modX = std::fmod(forwardPoint.x(), 7.9375f);\n    float modY = std::fmod(forwardPoint.y(), 7.9375f);\n    s32 moveX = (x18_focusCellPosition.x() - (forwardPoint.x() - modX)) / 7.9375f;\n    x18_focusCellPosition.x() = forwardPoint.x() - modX;\n    s32 moveY = (x18_focusCellPosition.y() - (forwardPoint.y() - modY)) / 7.9375f;\n    x18_focusCellPosition.y() = forwardPoint.y() - modY;\n    float deltaZ = x18_focusCellPosition.z() - forwardPoint.z();\n    x18_focusCellPosition.z() = float(forwardPoint.z());\n    MoveWrapCells(moveX, moveY);\n    CVectorFixed8_8 zVec(oopbtws * zeus::CVector3f(0.f, 0.f, deltaZ));\n    if (fxType == EEnvFxType::UnderwaterFlake)\n      zVec.z += s16(256.f * 0.5f * dt);\n    rstl::reserved_vector<CVectorFixed8_8, 256> snowForces;\n    CalculateSnowForces(zVec, snowForces, fxType, oopbtws, dt);\n    zeus::CTransform xf = GetParticleBoundsToWorldTransform();\n    zeus::CTransform invXf = xf.inverse();\n    UpdateBlockedGrids(mgr, fxType, camXf, xf, invXf);\n    CreateNewParticles(fxType);\n    switch (fxType) {\n    case EEnvFxType::Snow:\n      UpdateSnowParticles(snowForces);\n      break;\n    case EEnvFxType::Rain:\n      UpdateRainParticles(zVec, oopbtws, dt);\n      break;\n    case EEnvFxType::UnderwaterFlake:\n      UpdateUnderwaterParticles(zVec);\n      break;\n    default:\n      break;\n    }\n    if (fxType == EEnvFxType::Snow)\n      x28_firstSnowForce = std::fmod(1.f + x28_firstSnowForce, 256.f);\n    else\n      x28_firstSnowForce = std::fmod(0.125f + x28_firstSnowForce, 256.f);\n  }\n}\n\n// static zeus::CColor GetFlakeColor(const zeus::CMatrix4f& mvp, const CEnvFxShaders::Instance& inst) {\n//   float screenHeight =\n//       std::fabs(mvp.multiplyOneOverW(inst.positions[1]).y() - mvp.multiplyOneOverW(inst.positions[0]).y()) / 2.f;\n//   screenHeight -= (32.f / 480.f);\n//   screenHeight /= (32.f / 480.f);\n//   return zeus::CColor(1.f - zeus::clamp(0.f, screenHeight, 1.f), 1.f);\n// }\n\nvoid CEnvFxManagerGrid::RenderSnowParticles(const zeus::CTransform& camXf) {\n  const zeus::CVector3f xVec = 0.2f * camXf.basis[0];\n  const zeus::CVector3f zVec = 0.2f * camXf.basis[2];\n  // const zeus::CMatrix4f mvp = CGraphics::GetPerspectiveProjectionMatrix() * CGraphics::g_GXModelView.toMatrix4f();\n  //  auto* bufOut = m_instBuf.access();\n  //  for (const auto& particle : x1c_particles) {\n  //    bufOut->positions[0] = particle.toVec3f();\n  //    bufOut->uvs[0] = zeus::CVector2f(0.f, 0.f);\n  //    bufOut->positions[1] = bufOut->positions[0] + zVec;\n  //    bufOut->uvs[1] = zeus::CVector2f(0.f, 1.f);\n  //    bufOut->positions[3] = bufOut->positions[1] + xVec;\n  //    bufOut->uvs[3] = zeus::CVector2f(1.f, 1.f);\n  //    bufOut->positions[2] = bufOut->positions[3] - zVec;\n  //    bufOut->uvs[2] = zeus::CVector2f(1.f, 0.f);\n  //    bufOut->color = GetFlakeColor(mvp, *bufOut);\n  //    ++bufOut;\n  //  }\n  //  CGraphics::SetShaderDataBinding(m_snowBinding);\n  //  CGraphics::DrawInstances(0, 4, x1c_particles.size());\n}\n\nvoid CEnvFxManagerGrid::RenderRainParticles(const zeus::CTransform& camXf) {\n//  m_lineRenderer.Reset();\n  const float zOffset = 2.f * (1.f - std::fabs(camXf.basis[2].dot(zeus::skUp))) + 1.f;\n  const zeus::CColor color0(1.f, 10.f / 15.f);\n  for (const auto& particle : x1c_particles) {\n    const zeus::CVector3f pos0 = particle.toVec3f();\n    zeus::CVector3f pos1 = pos0;\n    pos1.z() += zOffset;\n    const float uvy0 = pos0.z() * 10.f + m_uvyOffset;\n    const float uvy1 = pos1.z() * 10.f + m_uvyOffset;\n//    m_lineRenderer.AddVertex(pos0, zeus::skWhite, 1.f, {0.f, uvy0});\n//    m_lineRenderer.AddVertex(pos1, zeus::skClear, 1.f, {0.f, uvy1});\n  }\n//  m_lineRenderer.Render(false, zeus::CColor(1.f, 0.15f)); // g_Renderer->IsThermalVisorHotPass()\n}\n\nvoid CEnvFxManagerGrid::RenderUnderwaterParticles(const zeus::CTransform& camXf) {\n  const zeus::CVector3f xVec = 0.5f * camXf.basis[0];\n  const zeus::CVector3f zVec = 0.5f * camXf.basis[2];\n  // const zeus::CMatrix4f mvp = CGraphics::GetPerspectiveProjectionMatrix() * CGraphics::g_GXModelView.toMatrix4f();\n  //  auto* bufOut = m_instBuf.access();\n  //  for (const auto& particle : x1c_particles) {\n  //    bufOut->positions[0] = particle.toVec3f();\n  //    bufOut->uvs[0] = zeus::CVector2f(0.f, 0.f);\n  //    bufOut->positions[1] = bufOut->positions[0] + zVec;\n  //    bufOut->uvs[1] = zeus::CVector2f(0.f, 1.f);\n  //    bufOut->positions[3] = bufOut->positions[1] + xVec;\n  //    bufOut->uvs[3] = zeus::CVector2f(1.f, 1.f);\n  //    bufOut->positions[2] = bufOut->positions[3] - zVec;\n  //    bufOut->uvs[2] = zeus::CVector2f(1.f, 0.f);\n  //    bufOut->color = GetFlakeColor(mvp, *bufOut);\n  //    ++bufOut;\n  //  }\n  //  CGraphics::SetShaderDataBinding(m_underwaterBinding);\n  //  CGraphics::DrawInstances(0, 4, x1c_particles.size());\n}\n\nvoid CEnvFxManagerGrid::Render(const zeus::CTransform& xf, const zeus::CTransform& invXf, const zeus::CTransform& camXf,\n                               float fxDensity, EEnvFxType fxType, CEnvFxManager& parent) {\n  if (!x1c_particles.empty() && x14_block.first) {\n    CGraphics::SetModelMatrix(xf * zeus::CTransform::Translate(x4_position.toVec2f() / 256.f));\n    // parent.m_uniformData.mv = CGraphics::g_GXModelView.toMatrix4f();\n    // parent.m_uniformData.proj = CGraphics::GetPerspectiveProjectionMatrix(/*true*/);\n    switch (fxType) {\n    case EEnvFxType::Snow:\n    case EEnvFxType::Rain: {\n      zeus::CMatrix4f envTexMtx(true);\n      envTexMtx[2][1] = 10.f;\n      envTexMtx[3][1] = 0.5f - (invXf * (zeus::skUp * x14_block.second)).z() * 10.f;\n      m_uvyOffset = envTexMtx[3][1];\n      // parent.m_uniformData.envMtx = envTexMtx;\n      break;\n    }\n    default:\n      break;\n    }\n    //    m_uniformBuf.access() = parent.m_uniformData;\n    switch (fxType) {\n    case EEnvFxType::Snow:\n      RenderSnowParticles(camXf);\n      break;\n    case EEnvFxType::Rain:\n      RenderRainParticles(camXf);\n      break;\n    case EEnvFxType::UnderwaterFlake:\n      RenderUnderwaterParticles(camXf);\n      break;\n    default:\n      break;\n    }\n  }\n}\n\nvoid CEnvFxManager::SetupSnowTevs(const CStateManager& mgr) {\n  mgr.GetCameraManager()->GetCurrentCamera(mgr);\n  if (mgr.GetCameraManager()->GetFluidCounter() != 0) {\n    g_Renderer->SetWorldFog(ERglFogMode::PerspExp, 0.f, 35.f, zeus::skBlack);\n    // m_uniformData.moduColor = zeus::CColor(1.f, 0.5f);\n  } else {\n    g_Renderer->SetWorldFog(ERglFogMode::PerspLin, 52.f, 57.f, zeus::skBlack);\n  }\n\n  // Blend One One\n  // 2 stages\n\n  // xb74_txtrSnowFlake\n  // Texcoord0: 2x4, TEX0, GX_IDENTITY, no norm, GX_PTTIDENTITY\n  // 0: Standard alpha, map0, tcg0\n  // Color: Zero, Konst, TexC, Zero\n  // Alpha: Zero, Konst, TexA, Zero\n\n  // x40_txtrEnvGradient\n  // Texcoord1: 2x4, POS, GX_TEXMTX5, no norm, GX_PTTIDENTITY\n  // 0: Standard alpha, map0, tcg0\n  // Color: Zero, TexC, CPrev, Zero\n  // Alpha: Zero, Zero, Zero, TexA\n}\n\nvoid CEnvFxManager::SetupRainTevs() const {\n  // Line-width 1px\n  // Blend SrcAlpha One\n\n  // x40_txtrEnvGradient\n  // Texcoord0: 2x4, POS, GX_TEXMTX5, no norm, GX_PTTIDENTITY\n  // 0: Standard alpha, map0, tcg0\n  // Color: Zero, Zero, Zero, TexC\n  // Alpha: Zero, RasA, Konst, Zero\n  // Ras vertex color\n  // KAlpha 0.15\n}\n\nvoid CEnvFxManager::SetupUnderwaterTevs(const zeus::CTransform& invXf, const CStateManager& mgr) const {\n  // Blend SrcAlpha InvSrcAlpha\n\n  // xc48_underwaterFlake\n  // Texcoord0: 2x4, TEX0, GX_IDENTITY, no norm, GX_PTTIDENTITY\n  // Color: Zero, Zero, Zero, TexC\n  // Alpha: Zero, Zero, Zero, TexA\n\n  float waterTop = FLT_MAX;\n  for (CEntity* ent : mgr.GetAllObjectList())\n    if (TCastToPtr<CScriptWater> water = ent)\n      if (auto tb = water->GetTouchBounds())\n        waterTop = std::min(waterTop, float(tb->max.z()));\n  zeus::CVector3f localWaterTop = invXf * (zeus::skUp * waterTop);\n  zeus::CMatrix4f envTexMtx(true);\n  envTexMtx[2][1] = -10.f;\n  envTexMtx[3][1] = localWaterTop.z() * -10.f + 0.5f;\n  // Load into texmtx5\n\n  // x40_txtrEnvGradient\n  // Texcoord1: 2x4, POS, GX_TEXMTX5, no norm, GX_PTTIDENTITY\n  // MTX: y-scale -10.0 of Z, y-trans -10.0 * ()\n  // 1: Standard alpha, map1, tcg1\n  // Color: Zero, One, CPrev, Zero\n  // Alpha: Zero, TexA, APrev, Zero\n  // Swap: RGBR\n}\n\nvoid CEnvFxManager::Render(const CStateManager& mgr) {\n  EEnvFxType fxType = mgr.GetWorld()->GetNeededEnvFx();\n  if (fxType != EEnvFxType::None) {\n    if (mgr.GetPlayer().GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Unmorphed ||\n        (mgr.GetPlayerState()->GetCurrentVisor() != CPlayerState::EPlayerVisor::Thermal &&\n         (fxType != EEnvFxType::Snow || mgr.GetPlayerState()->GetCurrentVisor() != CPlayerState::EPlayerVisor::XRay))) {\n      SCOPED_GRAPHICS_DEBUG_GROUP(\"CEnvFxManager::Render\", zeus::skCyan);\n      // No Cull\n      // ZTest, No ZWrite\n      zeus::CTransform xf = GetParticleBoundsToWorldTransform();\n      zeus::CTransform invXf = xf.inverse();\n      zeus::CTransform camXf = mgr.GetCameraManager()->GetCurrentCameraTransform(mgr);\n      // m_uniformData.moduColor = zeus::skWhite;\n      switch (fxType) {\n      case EEnvFxType::Snow:\n        SetupSnowTevs(mgr);\n        break;\n      case EEnvFxType::Rain:\n        SetupRainTevs();\n        break;\n      case EEnvFxType::UnderwaterFlake:\n        SetupUnderwaterTevs(invXf, mgr);\n        break;\n      default:\n        break;\n      }\n//      m_fogUniformBuf->load(&CGraphics::g_Fog, sizeof(CGraphics::g_Fog));\n      for (auto& grid : x50_grids)\n        grid.Render(xf, invXf, camXf, x30_fxDensity, fxType, *this);\n      // Backface cull\n\n//      m_uniformPool.updateBuffers();\n//      m_instPool.updateBuffers();\n    }\n  }\n}\n\nvoid CEnvFxManager::AsyncLoadResources(CStateManager& mgr) {\n  xb68_envRainSplashId = mgr.AllocateUniqueId();\n  CHUDBillboardEffect* effect =\n      new CHUDBillboardEffect(xb58_envRainSplash, {}, xb68_envRainSplashId, true, \"VisorRainSplashes\",\n                              CHUDBillboardEffect::GetNearClipDistance(mgr), CHUDBillboardEffect::GetScaleForPOV(mgr),\n                              zeus::skWhite, zeus::skOne3f, zeus::skZero3f);\n  effect->SetRunIndefinitely(true);\n  mgr.AddObject(effect);\n}\n\nvoid CEnvFxManager::SetFxDensity(s32 val, float density) {\n  x34_targetFxDensity = density;\n  x38_maxDensityDeltaSpeed = val;\n}\n\nvoid CEnvFxManager::AreaLoaded() {\n  for (CEnvFxManagerGrid& grid : x50_grids)\n    grid.x0_24_blockDirty = true;\n}\n\nvoid CEnvFxManager::Cleanup() {\n  xb68_envRainSplashId = kInvalidUniqueId;\n  xb6a_rainSoundActive = false;\n  xb6c_leftRainSound.reset();\n  xb70_rightRainSound.reset();\n}\n\nvoid CEnvFxManager::Initialize() {\n  const SObjectTag* tag = g_ResFactory->GetResourceIdByName(\"DUMB_SnowForces\");\n  std::unique_ptr<u8[]> data = g_ResFactory->LoadResourceSync(*tag);\n  CMemoryInStream r(data.get(), 2048, CMemoryInStream::EOwnerShip::NotOwned);\n  for (int i = 0; i < 256; ++i)\n    g_SnowForces.push_back(r.Get<zeus::CVector2f>());\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CEnvFxManager.hpp",
    "content": "#pragma once\n\n#include <array>\n#include <vector>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Audio/CSfxManager.hpp\"\n#include \"Runtime/Particle/CGenDescription.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector2i.hpp>\n\nnamespace metaforce {\nclass CActor;\nclass CStateManager;\nclass CTexture;\n\nenum class EEnvFxType { None, Snow, Rain, UnderwaterFlake };\n\nenum class EPhazonType { None, Blue, Orange };\n\nclass CVectorFixed8_8 {\npublic:\n  union {\n    struct {\n      s16 x, y, z;\n    };\n    std::array<s16, 3> v{};\n  };\n  CVectorFixed8_8() = default;\n  CVectorFixed8_8(s16 xi, s16 yi, s16 zi) : v{xi, yi, zi} {}\n  CVectorFixed8_8(const zeus::CVector3f& vec) {\n    zeus::simd_floats f(vec.mSimd);\n    x = s16(f[0] * 256.f);\n    y = s16(f[1] * 256.f);\n    z = s16(f[2] * 256.f);\n  }\n  CVectorFixed8_8 operator+(const CVectorFixed8_8& other) const {\n    return {s16(x + other.x), s16(y + other.y), s16(z + other.z)};\n  }\n  CVectorFixed8_8 operator-(const CVectorFixed8_8& other) const {\n    return {s16(x - other.x), s16(y - other.y), s16(z - other.z)};\n  }\n  zeus::CVector3f toVec3f() const { return {x / 256.f, y / 256.f, z / 256.f}; }\n};\nclass CEnvFxManager;\nclass CEnvFxManagerGrid {\n  friend class CEnvFxManager;\n  friend class CEnvFxShaders;\n  bool x0_24_blockDirty = true;\n  zeus::CVector2i x4_position;                         /* 8.8 fixed point */\n  zeus::CVector2i xc_extent;                           /* 8.8 fixed point */\n  std::pair<bool, float> x14_block = {false, FLT_MAX}; /* Blocked-bool, Z-coordinate */\n  std::vector<CVectorFixed8_8> x1c_particles;\n\n//  hecl::VertexBufferPool<CEnvFxShaders::Instance>::Token m_instBuf;\n//  hecl::UniformBufferPool<CEnvFxShaders::Uniform>::Token m_uniformBuf;\n//  CLineRenderer m_lineRenderer;\n//\n//  boo::ObjToken<boo::IShaderDataBinding> m_snowBinding;\n//  boo::ObjToken<boo::IShaderDataBinding> m_underwaterBinding;\n\n  float m_uvyOffset = 0.f;\n\n  void RenderSnowParticles(const zeus::CTransform& camXf);\n  void RenderRainParticles(const zeus::CTransform& camXf);\n  void RenderUnderwaterParticles(const zeus::CTransform& camXf);\n\npublic:\n  CEnvFxManagerGrid(const zeus::CVector2i& position, const zeus::CVector2i& extent,\n                    std::vector<CVectorFixed8_8> initialParticles, int reserve, CEnvFxManager& parent);\n  void Render(const zeus::CTransform& xf, const zeus::CTransform& invXf, const zeus::CTransform& camXf, float fxDensity,\n              EEnvFxType fxType, CEnvFxManager& parent);\n};\n\nclass CEnvFxManager {\n  friend class CEnvFxManagerGrid;\n  friend class CEnvFxShaders;\n\n//  hecl::VertexBufferPool<CEnvFxShaders::Instance> m_instPool;\n//  hecl::UniformBufferPool<CEnvFxShaders::Uniform> m_uniformPool;\n  // CEnvFxShaders::Uniform m_uniformData;\n//  boo::ObjToken<boo::IGraphicsBufferD> m_fogUniformBuf;\n\n  zeus::CAABox x0_particleBounds = zeus::CAABox(-63.5f, 63.5f);\n  zeus::CVector3f x18_focusCellPosition = zeus::skZero3f;\n  bool x24_enableSplash = false;\n  float x28_firstSnowForce = 0.f;\n  s32 x2c_lastBlockedGridIdx = -1;\n  float x30_fxDensity = 0.f;\n  float x34_targetFxDensity = 0.f;\n  float x38_maxDensityDeltaSpeed = 0.f;\n  // bool x3c_snowflakeTextureMipBlanked = false; /* Shader simulates this texture mod */\n  TLockedToken<CTexture> x40_txtrEnvGradient;\n  rstl::reserved_vector<CEnvFxManagerGrid, 64> x50_grids;\n  float xb54_baseSplashRate = 0.f;\n  TLockedToken<CGenDescription> xb58_envRainSplash;\n  bool xb64_ = true;\n  TUniqueId xb68_envRainSplashId = kInvalidUniqueId;\n  bool xb6a_rainSoundActive = false;\n  CSfxHandle xb6c_leftRainSound;\n  CSfxHandle xb70_rightRainSound;\n  TLockedToken<CTexture> xb74_txtrSnowFlake;\n  bool xb80_ = true;\n  rstl::reserved_vector<zeus::CVector3f, 16> xb84_snowZDeltas;\n  TLockedToken<CTexture> xc48_underwaterFlake;\n  bool xc54_ = true;\n\n  void SetSplashEffectRate(float f, const CStateManager& mgr);\n  void UpdateRainSounds(const CStateManager& mgr);\n  zeus::CVector3f GetParticleBoundsToWorldScale() const;\n  zeus::CTransform GetParticleBoundsToWorldTransform() const;\n  void UpdateVisorSplash(CStateManager& mgr, float dt, const zeus::CTransform& camXf);\n  void MoveWrapCells(s32, s32);\n  void CalculateSnowForces(const CVectorFixed8_8& zVec, rstl::reserved_vector<CVectorFixed8_8, 256>& snowForces,\n                           EEnvFxType type, const zeus::CVector3f& oopbtws, float dt);\n  static void BuildBlockObjectList(EntityList& list, CStateManager& mgr);\n  void UpdateBlockedGrids(CStateManager& mgr, EEnvFxType type, const zeus::CTransform& camXf,\n                          const zeus::CTransform& xf, const zeus::CTransform& invXf);\n  void CreateNewParticles(EEnvFxType type);\n  void UpdateSnowParticles(const rstl::reserved_vector<CVectorFixed8_8, 256>& snowForces);\n  void UpdateRainParticles(const CVectorFixed8_8& zVec, const zeus::CVector3f& oopbtws, float dt);\n  void UpdateUnderwaterParticles(const CVectorFixed8_8& zVec);\n  void SetupSnowTevs(const CStateManager& mgr);\n  void SetupRainTevs() const;\n  void SetupUnderwaterTevs(const zeus::CTransform& invXf, const CStateManager& mgr) const;\n\npublic:\n  CEnvFxManager();\n  void AsyncLoadResources(CStateManager& mgr);\n\n  void Update(float, CStateManager& mgr);\n  void Render(const CStateManager& mgr);\n  void SetFxDensity(s32, float);\n  void AreaLoaded();\n  void SetSplashRate(float f) { xb54_baseSplashRate = f; }\n  bool IsSplashActive() const { return x24_enableSplash; }\n  float GetRainMagnitude() const { return x30_fxDensity; }\n  void Cleanup();\n\n  static void Initialize();\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CExplosion.cpp",
    "content": "#include \"Runtime/World/CExplosion.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/World/CGameLight.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCExplosion::CExplosion(const TLockedToken<CGenDescription>& particle, TUniqueId uid, bool active,\n                       const CEntityInfo& info, std::string_view name, const zeus::CTransform& xf, u32 flags,\n                       const zeus::CVector3f& scale, const zeus::CColor& color)\n: CEffect(uid, info, active, name, xf) {\n  xe8_particleGen = std::make_unique<CElementGen>(particle, CElementGen::EModelOrientationType::Normal,\n                                                  flags & 0x2 ? CElementGen::EOptionalSystemFlags::Two\n                                                              : CElementGen::EOptionalSystemFlags::One);\n  xf0_particleDesc = particle.GetObj();\n  xf4_24_renderThermalHot = flags & 0x4;\n  xf4_26_renderXray = flags & 0x8;\n  xe6_27_thermalVisorFlags = flags & 0x1 ? 1 : 2;\n  xe8_particleGen->SetGlobalTranslation(xf.origin);\n  xe8_particleGen->SetOrientation(xf.getRotation());\n  xe8_particleGen->SetGlobalScale(scale);\n  xe8_particleGen->SetModulationColor(color);\n}\n\nCExplosion::CExplosion(const TLockedToken<CElectricDescription>& electric, TUniqueId uid, bool active,\n                       const CEntityInfo& info, std::string_view name, const zeus::CTransform& xf, u32 flags,\n                       const zeus::CVector3f& scale, const zeus::CColor& color)\n: CEffect(uid, info, active, name, xf) {\n  xe8_particleGen = std::make_unique<CParticleElectric>(electric);\n  xf0_electricDesc = electric.GetObj();\n  xf4_24_renderThermalHot = flags & 0x4;\n  xf4_26_renderXray = flags & 0x8;\n  xe6_27_thermalVisorFlags = flags & 0x1 ? 1 : 2;\n  xe8_particleGen->SetGlobalTranslation(xf.origin);\n  xe8_particleGen->SetOrientation(xf.getRotation());\n  xe8_particleGen->SetGlobalScale(scale);\n  xe8_particleGen->SetModulationColor(color);\n}\n\nvoid CExplosion::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CExplosion::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) {\n  if (msg == EScriptObjectMessage::Deleted) {\n    if (xec_explosionLight != kInvalidUniqueId)\n      mgr.FreeScriptObject(xec_explosionLight);\n  } else if (msg == EScriptObjectMessage::Registered) {\n    if (xe8_particleGen->SystemHasLight()) {\n      xec_explosionLight = mgr.AllocateUniqueId();\n      mgr.AddObject(new CGameLight(xec_explosionLight, GetAreaIdAlways(), GetActive(), \"ExplodePLight_\" + x10_name,\n                                   x34_transform, GetUniqueId(), xe8_particleGen->GetLight(), 1, 0, 0.f));\n    }\n  }\n\n  CActor::AcceptScriptMsg(msg, sender, mgr);\n\n  if (xec_explosionLight != kInvalidUniqueId)\n    mgr.SendScriptMsgAlways(xec_explosionLight, sender, msg);\n}\n\nvoid CExplosion::Think(float dt, CStateManager& mgr) {\n  if (xe4_28_transformDirty) {\n    xe8_particleGen->SetGlobalTranslation(GetTranslation());\n    xe8_particleGen->SetOrientation(GetTransform().getRotation());\n    xe4_28_transformDirty = false;\n  }\n  xe8_particleGen->Update(dt);\n\n  if (xec_explosionLight != kInvalidUniqueId) {\n    TCastToPtr<CGameLight> light = mgr.ObjectById(xec_explosionLight);\n    if (light && x30_24_active)\n      light->SetLight(xe8_particleGen->GetLight());\n  }\n\n  xf8_time += dt;\n\n  if (xf8_time > 15.f || xe8_particleGen->IsSystemDeletable())\n    mgr.FreeScriptObject(GetUniqueId());\n}\n\nvoid CExplosion::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) {\n  CActor::PreRender(mgr, frustum);\n  xe4_30_outOfFrustum = !xf4_25_ || !frustum.aabbFrustumTest(x9c_renderBounds);\n}\n\nvoid CExplosion::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) {\n  if (xe4_30_outOfFrustum) {\n    return;\n  }\n\n  if (!(xf4_24_renderThermalHot && mgr.GetThermalDrawFlag() == EThermalDrawFlag::Hot) &&\n      !(xf4_26_renderXray && mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::XRay)) {\n    g_Renderer->AddParticleGen(*xe8_particleGen);\n    return;\n  }\n\n  EnsureRendered(mgr);\n}\n\nvoid CExplosion::Render(CStateManager& mgr) {\n  if (mgr.GetThermalDrawFlag() == EThermalDrawFlag::Hot && xf4_24_renderThermalHot) {\n    CElementGen::SetSubtractBlend(true);\n    CCubeModel::SetRenderModelBlack(true);\n    xe8_particleGen->Render();\n    CCubeModel::SetRenderModelBlack(false);\n    CElementGen::SetSubtractBlend(false);\n    return;\n  }\n\n  CElementGen::SetSubtractBlend(!xf4_24_renderThermalHot);\n  CGraphics::SetFog(ERglFogMode::PerspLin, 0.f, 75.f, zeus::skBlack);\n  xe8_particleGen->Render();\n  mgr.SetupFogForArea(GetAreaIdAlways());\n  CElementGen::SetSubtractBlend(false);\n}\n\nbool CExplosion::CanRenderUnsorted(const CStateManager&) const { return false; }\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CExplosion.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <string_view>\n\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/Particle/CGenDescription.hpp\"\n#include \"Runtime/World/CEffect.hpp\"\n\nnamespace metaforce {\n\nclass CExplosion : public CEffect {\n  std::unique_ptr<CParticleGen> xe8_particleGen;\n  TUniqueId xec_explosionLight = kInvalidUniqueId;\n  union {\n    const CGenDescription* xf0_particleDesc;\n    const CElectricDescription* xf0_electricDesc;\n  };\n  bool xf4_24_renderThermalHot : 1;\n  bool xf4_25_ : 1 = true;\n  bool xf4_26_renderXray : 1;\n  float xf8_time = 0.f;\n\npublic:\n  DEFINE_ENTITY\n  CExplosion(const TLockedToken<CGenDescription>& particle, TUniqueId uid, bool active, const CEntityInfo& info,\n             std::string_view name, const zeus::CTransform& xf, u32, const zeus::CVector3f& scale,\n             const zeus::CColor& color);\n  CExplosion(const TLockedToken<CElectricDescription>& electric, TUniqueId uid, bool active, const CEntityInfo& info,\n             std::string_view name, const zeus::CTransform& xf, u32, const zeus::CVector3f& scale,\n             const zeus::CColor& color);\n\n  void Accept(IVisitor&) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void Think(float, CStateManager&) override;\n  void PreRender(CStateManager&, const zeus::CFrustum&) override;\n  void AddToRenderer(const zeus::CFrustum&, CStateManager&) override;\n  void Render(CStateManager&) override;\n  bool CanRenderUnsorted(const CStateManager&) const override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CFire.cpp",
    "content": "#include \"Runtime/World/CFire.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nCFire::CFire(TToken<CGenDescription> effect, TUniqueId uid, TAreaId aId, bool active, TUniqueId owner,\n             const zeus::CTransform& xf, const CDamageInfo& dInfo, const zeus::CAABox& aabox,\n             const zeus::CVector3f& vec, bool b1, CAssetId visorEffect, bool b2, bool b3, bool b4, float f1, float f2,\n             float f3, float f4)\n: CActor(uid, active, \"Fire\"sv, CEntityInfo(aId, NullConnectionList), xf, CModelData::CModelDataNull(),\n         CMaterialList(EMaterialTypes::Projectile), CActorParameters::None(), owner)\n, xe8_(std::make_unique<CElementGen>(effect))\n, xec_ownerId(owner)\n, xf0_damageInfo(dInfo)\n, x10c_damageInfo(dInfo)\n, x128_(aabox)\n, x144_(f1)\n, x148_24_(b2)\n, x148_25_(b3)\n, x148_26_(b4)\n, x148_27_(b4 && b3 && b2)\n, x148_29_(b1)\n, x14c_(f2)\n, x150_(visorEffect)\n, x154_(f3)\n, x158_(f4) {\n  xe8_->SetGlobalScale(vec);\n  xe8_->SetTranslation(xf.origin);\n}\n\nvoid CFire::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CFire::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  CActor::AcceptScriptMsg(msg, uid, mgr);\n\n  if (msg == EScriptObjectMessage::Registered) {\n    xe8_->SetParticleEmission(true);\n    SetActive(true);\n  }\n}\n\nvoid CFire::Think(float dt, CStateManager& mgr) {\n  float particleCount = xe8_->GetParticleCount() / xe8_->GetMaxParticles();\n  if (GetActive()) {\n    xe8_->Update(dt * x144_);\n    x10c_damageInfo = CDamageInfo(xf0_damageInfo, dt * std::max(0.5f, particleCount));\n  }\n\n  bool doFree = false;\n  if (xe8_->IsSystemDeletable())\n    doFree = true;\n\n  if (x148_29_) {\n    auto playerBounds = mgr.GetPlayer().GetTouchBounds();\n    auto bounds = GetTouchBounds();\n    if (playerBounds->intersects(*bounds) && doFree && particleCount > 0.5f)\n      mgr.GetPlayer().SetVisorSteam(particleCount * x14c_, x154_, x158_, x150_, true);\n    else\n      mgr.GetPlayer().SetVisorSteam(0.f, 1.f, 1.f, {}, true);\n  }\n\n  x15c_ += dt;\n\n  if (x15c_ > 45.f)\n    doFree = true;\n\n  if (doFree)\n    mgr.FreeScriptObject(GetUniqueId());\n}\n\nvoid CFire::Touch(CActor& act, CStateManager& mgr) {\n  if (act.GetUniqueId() == xec_ownerId)\n    return;\n\n  mgr.ApplyDamage(GetUniqueId(), act.GetUniqueId(), GetUniqueId(), x10c_damageInfo,\n                  CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), {});\n}\n\nvoid CFire::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) {\n  bool drawParticles = true;\n  if (!x148_27_) {\n    using EPlayerVisor = CPlayerState::EPlayerVisor;\n    CPlayerState::EPlayerVisor visor = mgr.GetPlayerState()->GetActiveVisor(mgr);\n    if (visor == EPlayerVisor::Combat || visor == EPlayerVisor::Scan)\n      drawParticles = x148_24_;\n    else if (visor == EPlayerVisor::XRay)\n      drawParticles = x148_26_;\n    else if (visor == EPlayerVisor::Thermal)\n      drawParticles = x148_25_;\n  }\n\n  if (drawParticles)\n    g_Renderer->AddParticleGen(*xe8_);\n  CActor::AddToRenderer(frustum, mgr);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CFire.hpp",
    "content": "#pragma once\n\n#include <optional>\n#include <memory>\n\n#include \"Runtime/World/CActor.hpp\"\n#include \"Runtime/World/CDamageInfo.hpp\"\n\nnamespace metaforce {\nclass CElementGen;\nclass CFire : public CActor {\n  std::unique_ptr<CElementGen> xe8_;\n  TUniqueId xec_ownerId;\n  CDamageInfo xf0_damageInfo;\n  CDamageInfo x10c_damageInfo;\n  std::optional<zeus::CAABox> x128_;\n  float x144_;\n  bool x148_24_ : 1;\n  bool x148_25_ : 1;\n  bool x148_26_ : 1;\n  bool x148_27_ : 1;\n  bool x148_28_ : 1 = false;\n  bool x148_29_ : 1;\n  float x14c_;\n  CAssetId x150_;\n  float x154_;\n  float x158_;\n  float x15c_ = 0.f;\n\npublic:\n  DEFINE_ENTITY\n  CFire(TToken<CGenDescription>, TUniqueId, TAreaId, bool, TUniqueId, const zeus::CTransform&, const CDamageInfo&,\n        const zeus::CAABox&, const zeus::CVector3f&, bool, CAssetId, bool, bool, bool, float, float, float, float);\n\n  void Accept(IVisitor&) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void Think(float, CStateManager&) override;\n  std::optional<zeus::CAABox> GetTouchBounds() const override {\n    if (GetActive()) {\n      return x128_;\n    }\n\n    return std::nullopt;\n  }\n\n  void Touch(CActor&, CStateManager&) override;\n  void AddToRenderer(const zeus::CFrustum&, CStateManager&) override;\n};\n} // namespace metaforce"
  },
  {
    "path": "Runtime/World/CFishCloud.cpp",
    "content": "#include \"Runtime/World/CFishCloud.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Graphics/CSkinnedModel.hpp\"\n#include \"Runtime/Graphics/CVertexMorphEffect.hpp\"\n#include \"Runtime/Weapon/CWeapon.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCFishCloud::CFishCloud(TUniqueId uid, bool active, std::string_view name, const CEntityInfo& info,\n                       const zeus::CVector3f& scale, const zeus::CTransform& xf, CModelData&& mData,\n                       const CAnimRes& aRes, u32 numBoids, float speed, float separationRadius, float cohesionMagnitude,\n                       float alignmentWeight, float separationMagnitude, float weaponRepelMagnitude,\n                       float playerRepelMagnitude, float containmentMagnitude, float scatterVel, float maxScatterAngle,\n                       float weaponRepelDampingSpeed, float playerRepelDampingSpeed, float containmentRadius,\n                       u32 updateShift, const zeus::CColor& color, bool killable, float weaponKillRadius,\n                       CAssetId part1, u32 partCount1, CAssetId part2, u32 partCount2, CAssetId part3, u32 partCount3,\n                       CAssetId part4, u32 partCount4, u32 deathSfx, bool repelFromThreats, bool hotInThermal)\n: CActor(uid, active, name, info, xf, std::move(mData), {EMaterialTypes::NoStepLogic},\n         CActorParameters::None().HotInThermal(hotInThermal), kInvalidUniqueId)\n, x11c_updateMask(u32((1 << updateShift) - 1))\n, x120_scale(scale)\n, x130_speed(speed)\n, x134_numBoids(numBoids)\n, x138_separationRadius(separationRadius)\n, x13c_cohesionMagnitude(cohesionMagnitude)\n, x140_alignmentWeight(alignmentWeight)\n, x144_separationMagnitude(separationMagnitude)\n, x148_weaponRepelMagnitude(weaponRepelMagnitude)\n, x14c_playerRepelMagnitude(playerRepelMagnitude)\n, x150_scatterVel(scatterVel)\n, x154_maxScatterAngle(maxScatterAngle)\n, x158_containmentMagnitude(containmentMagnitude)\n, x15c_playerRepelDampingSpeed(playerRepelDampingSpeed)\n, x160_weaponRepelDampingSpeed(weaponRepelDampingSpeed)\n, x164_playerRepelDamping(playerRepelDampingSpeed)\n, x168_weaponRepelDamping(weaponRepelDampingSpeed)\n, x16c_color(color)\n, x170_weaponKillRadius(weaponKillRadius)\n, x174_containmentRadius(containmentRadius)\n, x234_deathSfx(deathSfx != 0xffffffff ? CSfxManager::TranslateSFXID(u16(deathSfx & 0xffff)) : u16(0xffff))\n, x250_28_killable(killable)\n, x250_29_repelFromThreats(repelFromThreats) {\n  x108_modifierSources.reserve(10);\n  if (aRes.GetId().IsValid()) {\n    x1b0_models.emplace_back(std::make_unique<CModelData>(aRes));\n    x1b0_models.emplace_back(std::make_unique<CModelData>(aRes));\n    x1b0_models.emplace_back(std::make_unique<CModelData>(aRes));\n    x1b0_models.emplace_back(std::make_unique<CModelData>(aRes));\n    x250_27_validModel = true;\n  }\n  if (part1.IsValid()) {\n    x1c4_particleDescs.push_back(g_SimplePool->GetObj({FOURCC('PART'), part1}));\n  }\n  if (part2.IsValid()) {\n    x1c4_particleDescs.push_back(g_SimplePool->GetObj({FOURCC('PART'), part2}));\n  }\n  if (part3.IsValid()) {\n    x1c4_particleDescs.push_back(g_SimplePool->GetObj({FOURCC('PART'), part3}));\n  }\n  if (part4.IsValid()) {\n    x1c4_particleDescs.push_back(g_SimplePool->GetObj({FOURCC('PART'), part4}));\n  }\n  for (const auto& p : x1c4_particleDescs) {\n    x1f8_particleGens.emplace_back(std::make_unique<CElementGen>(p));\n    x1f8_particleGens.back()->SetParticleEmission(false);\n  }\n  x21c_deathParticleCounts.push_back(partCount1);\n  x21c_deathParticleCounts.push_back(partCount2);\n  x21c_deathParticleCounts.push_back(partCount3);\n  x21c_deathParticleCounts.push_back(partCount4);\n  const zeus::CAABox aabb = GetBoundingBox();\n  x238_partitionPitch = (aabb.max - aabb.min) / 7.f;\n  x244_ooPartitionPitch = 1.f / x238_partitionPitch;\n}\n\nvoid CFishCloud::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CFishCloud::UpdateParticles(float dt) {\n  for (auto& p : x1f8_particleGens) {\n    p->Update(dt);\n  }\n}\n\nvoid CFishCloud::UpdatePartitionList() {\n  xf8_boidPartitionLists.clear();\n  xf8_boidPartitionLists.resize(xf8_boidPartitionLists.capacity());\n  const auto aabb = GetBoundingBox();\n  for (auto& b : xe8_boids) {\n    const zeus::CVector3f idxs = (b.x0_pos - aabb.min) * x244_ooPartitionPitch;\n    const int idx = int(idxs.x()) + int(idxs.y()) * 7 + int(idxs.z()) * 49;\n    if (idx >= 0 && idx < 343) {\n      b.x1c_next = xf8_boidPartitionLists[idx];\n      xf8_boidPartitionLists[idx] = &b;\n    }\n  }\n}\n\nbool CFishCloud::PointInBox(const zeus::CAABox& aabb, const zeus::CVector3f& point) const {\n  if (!x250_25_worldSpace) {\n    return aabb.pointInside(point);\n  }\n  return GetUntransformedBoundingBox().pointInside(GetTransform().transposeRotate(point - GetTranslation()));\n}\n\nzeus::CPlane CFishCloud::FindClosestPlane(const zeus::CAABox& aabb, const zeus::CVector3f& point) const {\n  if (!x250_25_worldSpace) {\n    float minDist = FLT_MAX;\n    auto minFace = zeus::CAABox::EBoxFaceId::YMin;\n    for (int i = 0; i < 6; ++i) {\n      const auto tri = aabb.getTri(zeus::CAABox::EBoxFaceId(i), 0);\n      const float dist = zeus::CPlane(tri.x10_verts[0], tri.x10_verts[2], tri.x10_verts[1]).pointToPlaneDist(point);\n      if (dist >= 0.f && dist < minDist) {\n        minDist = dist;\n        minFace = zeus::CAABox::EBoxFaceId(i);\n      }\n    }\n    const auto tri = aabb.getTri(minFace, 0);\n    return zeus::CPlane(tri.x10_verts[0], tri.x10_verts[2], tri.x10_verts[1]);\n  } else {\n    const auto unPoint = GetTransform().transposeRotate(point - GetTranslation());\n    const auto unAabb = GetUntransformedBoundingBox();\n    float minDist = FLT_MAX;\n    auto minFace = zeus::CAABox::EBoxFaceId::YMin;\n    for (int i = 0; i < 6; ++i) {\n      const auto tri = unAabb.getTri(zeus::CAABox::EBoxFaceId(i), 0);\n      const float dist = zeus::CPlane(tri.x10_verts[0], tri.x10_verts[2], tri.x10_verts[1]).pointToPlaneDist(unPoint);\n      if (dist >= 0.f && dist < minDist) {\n        minDist = dist;\n        minFace = zeus::CAABox::EBoxFaceId(i);\n      }\n    }\n\n    const auto tri = unAabb.getTri(minFace, 0);\n    return zeus::CPlane(GetTransform() * tri.x10_verts[0], GetTransform() * tri.x10_verts[2],\n                        GetTransform() * tri.x10_verts[1]);\n  }\n}\n\nCFishCloud::CBoid* CFishCloud::GetListAt(const zeus::CVector3f& pos) {\n  const zeus::CAABox aabb = GetBoundingBox();\n  const zeus::CVector3f ints = (pos - aabb.min) * x244_ooPartitionPitch;\n  const int idx = int(ints.x()) + int(ints.y()) * 7 + int(ints.z()) * 49;\n\n  if (idx < 0 || idx >= 343) {\n    return nullptr;\n  }\n\n  return xf8_boidPartitionLists[idx];\n}\n\nvoid CFishCloud::BuildBoidNearList(const zeus::CVector3f& pos, float radius,\n                                   rstl::reserved_vector<CBoid*, 25>& nearList) {\n  const float radiusSq = radius * radius;\n  CBoid* b = GetListAt(pos);\n\n  while (b != nullptr && nearList.size() < 25) {\n    if (b->x20_active) {\n      const float distSq = (b->GetTranslation() - pos).magSquared();\n      if (distSq != 0.f && distSq < radiusSq) {\n        nearList.push_back(b);\n      }\n    }\n    b = b->x1c_next;\n  }\n}\n\nvoid CFishCloud::BuildBoidNearPartitionList(const zeus::CVector3f& pos, float radius,\n                                            rstl::reserved_vector<CBoid*, 25>& nearList) {\n  const float radiusSq = radius * radius;\n  const zeus::CAABox aabb = GetBoundingBox();\n  const float x = std::max(radius * x244_ooPartitionPitch.x(), float(x238_partitionPitch.x()));\n  const float y = std::max(radius * x244_ooPartitionPitch.y(), float(x238_partitionPitch.y()));\n  const float z = std::max(radius * x244_ooPartitionPitch.z(), float(x238_partitionPitch.z()));\n  const float nx = 0.01f - x;\n  const float ny = 0.01f - y;\n  const float nz = 0.01f - z;\n\n  for (float lnx = nx; lnx < x; lnx += x238_partitionPitch.x()) {\n    const float cx = lnx + pos.x();\n    if (cx < aabb.min.x()) {\n      continue;\n    }\n    if (cx >= aabb.max.x()) {\n      break;\n    }\n    for (float lny = ny; lny < y; lny += x238_partitionPitch.y()) {\n      const float cy = lny + pos.y();\n      if (cy < aabb.min.y()) {\n        continue;\n      }\n      if (cy >= aabb.max.y()) {\n        break;\n      }\n      for (float lnz = nz; lnz < z; lnz += x238_partitionPitch.z()) {\n        const float cz = lnz + pos.z();\n        if (cz < aabb.min.z()) {\n          continue;\n        }\n        if (cz >= aabb.max.z()) {\n          break;\n        }\n        const zeus::CVector3f ints = (zeus::CVector3f(cx, cy, cz) - aabb.min) * x244_ooPartitionPitch;\n        const int idx = int(ints.x()) + int(ints.y()) * 7 + int(ints.z()) * 49;\n        if (idx < 0) {\n          continue;\n        }\n        if (idx < 343) {\n          CBoid* boid = xf8_boidPartitionLists[idx];\n          while (boid != nullptr) {\n            if (boid->x20_active) {\n              const float distSq = (boid->x0_pos - pos).magSquared();\n              if (distSq != 0.f && distSq < radiusSq) {\n                nearList.push_back(boid);\n                if (nearList.size() == 25) {\n                  return;\n                }\n              }\n            }\n            boid = boid->x1c_next;\n          }\n        }\n      }\n    }\n  }\n}\n\nvoid CFishCloud::PlaceBoid(CStateManager& mgr, CBoid& boid, const zeus::CAABox& aabb) const {\n  const auto plane = FindClosestPlane(aabb, boid.x0_pos);\n  boid.x0_pos -= plane.pointToPlaneDist(boid.x0_pos) * plane.normal() + 0.0001f * plane.normal();\n  boid.xc_vel.y() = mgr.GetActiveRandom()->Float() - 0.5f;\n  boid.xc_vel.x() = mgr.GetActiveRandom()->Float() - 0.5f;\n  boid.xc_vel.z() = 0.f;\n  if (!x250_25_worldSpace) {\n    if (!aabb.pointInside(boid.x0_pos)) {\n      boid.x0_pos.z() = mgr.GetActiveRandom()->Float() * (aabb.max.z() - aabb.min.z()) + aabb.min.z();\n      boid.x0_pos.y() = mgr.GetActiveRandom()->Float() * (aabb.max.y() - aabb.min.y()) + aabb.min.y();\n      boid.x0_pos.x() = mgr.GetActiveRandom()->Float() * (aabb.max.x() - aabb.min.x()) + aabb.min.x();\n    }\n  } else {\n    if (!PointInBox(aabb, boid.x0_pos)) {\n      const auto unAabb = GetUntransformedBoundingBox();\n      boid.x0_pos.z() = mgr.GetActiveRandom()->Float() * (unAabb.max.z() - unAabb.min.z()) + unAabb.min.z();\n      boid.x0_pos.y() = mgr.GetActiveRandom()->Float() * (unAabb.max.y() - unAabb.min.y()) + unAabb.min.y();\n      boid.x0_pos.x() = mgr.GetActiveRandom()->Float() * (unAabb.max.x() - unAabb.min.x()) + unAabb.min.x();\n      boid.x0_pos = GetTransform() * boid.x0_pos;\n    }\n  }\n}\n\nvoid CFishCloud::ApplySeparation(CBoid& boid, const rstl::reserved_vector<CBoid*, 25>& nearList) const {\n  if (nearList.empty()) {\n    return;\n  }\n\n  float minDist = FLT_MAX;\n  zeus::CVector3f pos;\n  for (const CBoid* b : nearList) {\n    const float dist = (boid.GetTranslation() - b->GetTranslation()).magSquared();\n    if (dist < minDist) {\n      minDist = dist;\n      pos = b->GetTranslation();\n    }\n  }\n\n  ApplySeparation(boid, pos, x138_separationRadius, x144_separationMagnitude);\n}\n\nvoid CFishCloud::ApplySeparation(CBoid& boid, const zeus::CVector3f& separateFrom, float separationRadius,\n                                 float separationMagnitude) const {\n  const zeus::CVector3f delta = boid.GetTranslation() - separateFrom;\n  if (!delta.canBeNormalized()) {\n    return;\n  }\n\n  const float deltaDistSq = delta.magSquared();\n  const float capDeltaDistSq = separationRadius * separationRadius;\n  if (deltaDistSq >= capDeltaDistSq) {\n    return;\n  }\n\n  boid.xc_vel += (1.f - deltaDistSq / capDeltaDistSq) * delta.normalized() * separationMagnitude;\n}\n\nvoid CFishCloud::ApplyCohesion(CBoid& boid, const rstl::reserved_vector<CBoid*, 25>& nearList) const {\n  if (nearList.empty()) {\n    return;\n  }\n\n  zeus::CVector3f avg;\n  for (const CBoid* b : nearList) {\n    avg += b->GetTranslation();\n  }\n\n  avg = avg / float(nearList.size());\n  ApplyCohesion(boid, avg, x138_separationRadius, x13c_cohesionMagnitude);\n}\n\nvoid CFishCloud::ApplyCohesion(CBoid& boid, const zeus::CVector3f& cohesionFrom, float cohesionRadius,\n                               float cohesionMagnitude) const {\n  const zeus::CVector3f delta = cohesionFrom - boid.GetTranslation();\n  if (!delta.canBeNormalized()) {\n    return;\n  }\n\n  const float distSq = delta.magSquared();\n  const float capDistSq = cohesionRadius * cohesionRadius;\n  boid.xc_vel += ((distSq > capDistSq) ? 1.f : distSq / capDistSq) * delta.normalized() * cohesionMagnitude;\n}\n\nvoid CFishCloud::ApplyAlignment(CBoid& boid, const rstl::reserved_vector<CBoid*, 25>& nearList) const {\n  if (nearList.empty()) {\n    return;\n  }\n\n  zeus::CVector3f avg;\n  for (const CBoid* b : nearList) {\n    avg += b->xc_vel;\n  }\n\n  avg = avg / float(nearList.size());\n  boid.xc_vel += zeus::CVector3f::getAngleDiff(boid.xc_vel, avg) / M_PIF * (avg * x140_alignmentWeight);\n}\n\nvoid CFishCloud::ApplyAttraction(CBoid& boid, const zeus::CVector3f& attractTo, float attractionRadius,\n                                 float attractionMagnitude) const {\n  const zeus::CVector3f delta = attractTo - boid.GetTranslation();\n  if (!delta.canBeNormalized()) {\n    return;\n  }\n\n  const float distSq = delta.magSquared();\n  const float capDistSq = attractionRadius * attractionRadius;\n  boid.xc_vel += ((distSq > capDistSq) ? 0.f : (1.f - distSq / capDistSq)) * delta.normalized() * attractionMagnitude;\n}\n\nvoid CFishCloud::ApplyRepulsion(CBoid& boid, const zeus::CVector3f& attractTo, float repulsionRadius,\n                                float repulsionMagnitude) const {\n  ApplySeparation(boid, attractTo, repulsionRadius, repulsionMagnitude);\n}\n\nvoid CFishCloud::ApplySwirl(CBoid& boid, const zeus::CVector3f& swirlPoint, bool clockwise, float magnitude,\n                            float radius) const {\n  const zeus::CVector3f delta = boid.x0_pos - swirlPoint;\n  const float deltaMag = delta.magnitude();\n\n  zeus::CVector3f alignVec;\n  if (clockwise) {\n    alignVec = delta.normalized().cross(zeus::skUp);\n  } else {\n    alignVec = zeus::skUp.cross(delta / deltaMag);\n  }\n\n  const float weight = deltaMag > radius ? 0.f : 1.f - deltaMag / radius;\n  boid.xc_vel += zeus::CVector3f::getAngleDiff(boid.xc_vel, alignVec) / M_PIF * weight * (magnitude * alignVec);\n}\n\nvoid CFishCloud::ApplyContainment(CBoid& boid, const zeus::CAABox& aabb) const {\n  if (!boid.xc_vel.canBeNormalized()) {\n    return;\n  }\n\n  if (PointInBox(aabb, boid.xc_vel.normalized() * x130_speed * x174_containmentRadius + boid.x0_pos)) {\n    return;\n  }\n\n  ApplyAttraction(boid, aabb.center(), 100000.f, x158_containmentMagnitude);\n}\n\nvoid CFishCloud::ScatterBoid(CStateManager& mgr, CBoid& b) const {\n  const float angle = (mgr.GetActiveRandom()->Float() - 0.5f) * M_PIF * x154_maxScatterAngle;\n  const float cosAngle = std::cos(angle);\n  const float sinAngle = std::sin(angle);\n  b.xc_vel.x() += x150_scatterVel * (b.xc_vel.y() * sinAngle + b.xc_vel.x() * cosAngle);\n  b.xc_vel.y() += x150_scatterVel * (b.xc_vel.y() * cosAngle + b.xc_vel.x() * sinAngle);\n}\n\nvoid CFishCloud::Think(float dt, CStateManager& mgr) {\n  if (!GetActive()) {\n    return;\n  }\n\n  const CGameArea* area = mgr.GetWorld()->GetAreaAlways(x4_areaId);\n  const auto occState = area->IsPostConstructed() ? area->GetOcclusionState() : CGameArea::EOcclusionState::Occluded;\n  if (occState == CGameArea::EOcclusionState::Visible) {\n    x168_weaponRepelDamping = std::max(0.f, x168_weaponRepelDamping - x160_weaponRepelDampingSpeed * dt * 0.1f);\n    if (x250_26_enableWeaponRepelDamping) {\n      x168_weaponRepelDamping =\n          std::min(x160_weaponRepelDampingSpeed * dt + x168_weaponRepelDamping, x148_weaponRepelMagnitude);\n    }\n\n    x164_playerRepelDamping = std::max(0.f, x164_playerRepelDamping - x15c_playerRepelDampingSpeed * dt * 0.1f);\n    if (x250_30_enablePlayerRepelDamping) {\n      x164_playerRepelDamping =\n          std::min(x15c_playerRepelDampingSpeed * dt + x164_playerRepelDamping, x14c_playerRepelMagnitude);\n    }\n\n    x250_26_enableWeaponRepelDamping = false;\n    x250_30_enablePlayerRepelDamping = false;\n    ++x118_thinkCounter;\n\n    UpdateParticles(dt);\n    UpdatePartitionList();\n\n    const zeus::CAABox aabb = GetBoundingBox();\n    int idx = 0;\n    for (auto& b : xe8_boids) {\n      if (b.x20_active && (idx & x11c_updateMask) == (x118_thinkCounter & x11c_updateMask)) {\n        rstl::reserved_vector<CBoid*, 25> nearList;\n        if (x250_31_updateWithoutPartitions) {\n          BuildBoidNearList(b.x0_pos, x138_separationRadius, nearList);\n        } else {\n          BuildBoidNearPartitionList(b.x0_pos, x138_separationRadius, nearList);\n        }\n\n        for (int i = 0; i < 5; ++i) {\n          switch (i) {\n          case 1:\n            ApplySeparation(b, nearList);\n            break;\n          case 2:\n            if (!x250_24_randomMovement || mgr.GetActiveRandom()->Float() > x12c_randomMovementTimer) {\n              ApplyCohesion(b, nearList);\n            }\n            break;\n          case 3:\n            if (!x250_24_randomMovement || mgr.GetActiveRandom()->Float() > x12c_randomMovementTimer) {\n              ApplyAlignment(b, nearList);\n            }\n            break;\n          case 4:\n            ScatterBoid(mgr, b);\n            break;\n          default:\n            break;\n          }\n          if (b.xc_vel.magSquared() > 3.2f) {\n            break;\n          }\n        }\n\n        if (!x250_24_randomMovement && b.xc_vel.magSquared() < 3.2f) {\n          for (const auto& m : x108_modifierSources) {\n            if (const TCastToConstPtr<CActor> act = mgr.ObjectById(m.x0_source)) {\n              if (m.xd_isSwirl) {\n                ApplySwirl(b, act->GetTranslation(), m.xc_isRepulsor, m.x8_priority, m.x4_radius);\n              } else if (m.xc_isRepulsor) {\n                ApplyRepulsion(b, act->GetTranslation(), m.x4_radius, m.x8_priority);\n              } else {\n                ApplyAttraction(b, act->GetTranslation(), m.x4_radius, m.x8_priority);\n              }\n            } else {\n              if (m.xc_isRepulsor)\n                RemoveRepulsor(m.x0_source);\n              else\n                RemoveAttractor(m.x0_source);\n              break;\n            }\n          }\n        }\n      }\n      ++idx;\n    }\n\n    for (auto& b : xe8_boids) {\n      if (b.x20_active) {\n        ApplyContainment(b, aabb);\n        const float velMag = b.xc_vel.magnitude();\n        if (!zeus::close_enough(velMag, 0.f)) {\n          b.xc_vel = b.xc_vel / velMag;\n        }\n        b.xc_vel.z() *= 0.99f;\n      }\n    }\n\n    if (x12c_randomMovementTimer > 0.f) {\n      x12c_randomMovementTimer -= dt;\n    } else {\n      x12c_randomMovementTimer = 0.f;\n      x250_24_randomMovement = false;\n    }\n\n    for (auto& b : xe8_boids) {\n      if (b.x20_active) {\n        b.x0_pos += b.xc_vel * dt * x130_speed;\n        if (!PointInBox(aabb, b.x0_pos)) {\n          PlaceBoid(mgr, b, aabb);\n        }\n      }\n    }\n\n    if (x250_27_validModel) {\n      for (auto& m : x1b0_models) {\n        m->GetAnimationData()->SetPlaybackRate(1.f);\n        m->AdvanceAnimation(dt, mgr, x4_areaId, true);\n      }\n    }\n  }\n}\n\nvoid CFishCloud::CreatePartitionList() { xf8_boidPartitionLists.reserve(343); }\n\nvoid CFishCloud::AllocateSkinnedModels(CStateManager& mgr, CModelData::EWhichModel which) {\n  x178_workspaces.clear();\n  int idx = 0;\n  for (auto& m : x1b0_models) {\n    x178_workspaces.emplace_back(m->PickAnimatedModel(which).CloneWorkspace());\n    m->EnableLooping(true);\n    m->AdvanceAnimation(m->GetAnimationData()->GetAnimTimeRemaining(\"Whole Body\"sv) * 0.25f * float(idx), mgr,\n                        x4_areaId, true);\n    ++idx;\n  }\n  x230_whichModel = which;\n}\n\nvoid CFishCloud::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) {\n  CActor::AcceptScriptMsg(msg, sender, mgr);\n  switch (msg) {\n  case EScriptObjectMessage::Registered: {\n    xe8_boids.reserve(x134_numBoids);\n    const zeus::CAABox aabb = GetUntransformedBoundingBox();\n    const zeus::CVector3f extent = aabb.max - aabb.min;\n    zeus::CVector3f randPoint;\n    for (u32 i = 0; i < x134_numBoids; ++i) {\n      randPoint.z() = mgr.GetActiveRandom()->Float() * extent.z() + aabb.min.z();\n      randPoint.y() = mgr.GetActiveRandom()->Float() * extent.y() + aabb.min.y();\n      randPoint.x() = mgr.GetActiveRandom()->Float() * extent.x() + aabb.min.x();\n      zeus::CVector3f vel;\n      vel.y() = mgr.GetActiveRandom()->Float() - 0.5f;\n      vel.x() = mgr.GetActiveRandom()->Float() - 0.5f;\n      xe8_boids.emplace_back(x34_transform * randPoint, vel,\n                             0.2f * std::pow(mgr.GetActiveRandom()->Float(), 7.f) + 0.9f);\n    }\n    CreatePartitionList();\n    if (x250_27_validModel) {\n      AllocateSkinnedModels(mgr, CModelData::EWhichModel::Normal);\n    }\n    break;\n  }\n  default:\n    break;\n  }\n}\n\nvoid CFishCloud::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) {\n  CActor::PreRender(mgr, frustum);\n  if (x250_27_validModel) {\n    for (auto& m : x1b0_models) {\n      m->GetAnimationData()->PreRender();\n    }\n  }\n  xe4_30_outOfFrustum = false;\n}\n\nvoid CFishCloud::AddParticlesToRenderer() const {\n  for (const auto& p : x1f8_particleGens) {\n    g_Renderer->AddParticleGen(*p);\n  }\n}\n\nvoid CFishCloud::RenderBoid(int idx, const CBoid& boid, u32& drawMask, bool thermalHot,\n                            const CModelFlags& flags) const {\n  const u32 modelIndex = idx & 0x3;\n  CModelData& mData = *x1b0_models[modelIndex];\n  CSkinnedModel& model = mData.PickAnimatedModel(CModelData::EWhichModel::Normal);\n\n  const u32 thisDrawMask = 1u << modelIndex;\n  if ((drawMask & thisDrawMask) != 0) {\n    drawMask &= ~thisDrawMask;\n    mData.GetAnimationData()->BuildPose();\n    model.Calculate(mData.GetAnimationData()->GetPose(), nullptr, {}, &x178_workspaces[modelIndex]);\n  }\n\n  CGraphics::SetModelMatrix(zeus::lookAt(boid.x0_pos, boid.x0_pos + boid.xc_vel));\n  const auto& positions = x178_workspaces[modelIndex].m_vertexWorkspace;\n  const auto& normals = x178_workspaces[modelIndex].m_normalWorkspace;\n  if (thermalHot) {\n    constexpr CModelFlags thermFlags(0, 0, 3, zeus::skWhite);\n    CModelData::ThermalDraw(model, positions, normals, zeus::skWhite, zeus::CColor(0.f, 0.25f), thermFlags);\n  } else {\n    model.Draw(positions, normals, flags);\n  }\n}\n\nvoid CFishCloud::Render(CStateManager& mgr) {\n  if (!GetActive()) {\n    return;\n  }\n\n  SCOPED_GRAPHICS_DEBUG_GROUP(\n      fmt::format(\"CFishCloud::Render {} {} {}\", x8_uid, xc_editorId, x10_name).c_str(), zeus::skOrange);\n\n  const bool thermalHot = mgr.GetThermalDrawFlag() == EThermalDrawFlag::Hot;\n  CModelFlags flags(0, 0, 3, zeus::skWhite);\n  if (mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::XRay) {\n    flags = CModelFlags(5, 0, 3, x16c_color);\n  } else {\n    flags = CModelFlags(1, 0, 3, x16c_color);\n  }\n\n  AddParticlesToRenderer();\n\n  if (x250_27_validModel) {\n    g_Renderer->SetAmbientColor(zeus::skWhite);\n    CGraphics::DisableAllLights();\n    int idx = 0;\n    u32 drawMask = 0xffffffff;\n    for (const auto& b : xe8_boids) {\n      if (b.x20_active) {\n        RenderBoid(idx, b, drawMask, thermalHot, flags);\n      }\n      ++idx;\n    }\n  } else {\n    g_Renderer->SetAmbientColor(zeus::skWhite);\n    g_Renderer->SetModelMatrix({});\n    for (const auto& b : xe8_boids) {\n      if (b.x20_active) {\n        x64_modelData->SetScale(zeus::CVector3f(b.x18_scale));\n        x64_modelData->Render(mgr, zeus::lookAt(b.x0_pos, b.x0_pos + b.xc_vel), nullptr, flags);\n      }\n    }\n  }\n}\n\nvoid CFishCloud::CalculateRenderBounds() { x9c_renderBounds = GetBoundingBox(); }\n\nstd::optional<zeus::CAABox> CFishCloud::GetTouchBounds() const { return {GetBoundingBox()}; }\n\nvoid CFishCloud::CreateBoidDeathParticle(CBoid& b) const {\n  auto it = x21c_deathParticleCounts.begin();\n  for (const auto& p : x1f8_particleGens) {\n    p->SetParticleEmission(true);\n    p->SetTranslation(b.x0_pos);\n    p->ForceParticleCreation(*it);\n    p->SetParticleEmission(false);\n    ++it;\n  }\n}\n\nvoid CFishCloud::KillBoid(CBoid& b) const {\n  b.x20_active = false;\n  CreateBoidDeathParticle(b);\n  CAudioSys::C3DEmitterParmData parmData = {};\n  parmData.x0_pos = b.x0_pos;\n  parmData.x18_maxDist = 250.f;\n  parmData.x1c_distComp = 0.1f;\n  parmData.x20_flags = 0x1;\n  parmData.x24_sfxId = x234_deathSfx;\n  parmData.x26_maxVol = 1.f;\n  parmData.x27_minVol = 0.157f;\n  parmData.x29_prio = 0x7f;\n  CSfxManager::AddEmitter(parmData, true, 0x7f, false, x4_areaId);\n}\n\nvoid CFishCloud::Touch(CActor& other, CStateManager& mgr) {\n  CActor::Touch(other, mgr);\n\n  if (const TCastToConstPtr<CWeapon> weap = other) {\n    if (!x250_26_enableWeaponRepelDamping && x250_29_repelFromThreats) {\n      int idx = 0;\n      for (auto& b : xe8_boids) {\n        if ((idx & 0x3) == (x118_thinkCounter & 0x3)) {\n          ApplyRepulsion(b, weap->GetTranslation(), 8.f, x148_weaponRepelMagnitude - x168_weaponRepelDamping);\n        }\n        ++idx;\n      }\n    }\n\n    x250_26_enableWeaponRepelDamping = true;\n\n    if (x250_28_killable) {\n      if (const auto tb = weap->GetTouchBounds()) {\n        for (auto& b : xe8_boids) {\n          if (b.x20_active && tb->intersects(zeus::CAABox(weap->GetTranslation() - x170_weaponKillRadius,\n                                                          weap->GetTranslation() + x170_weaponKillRadius))) {\n            KillBoid(b);\n          }\n        }\n      }\n    }\n  }\n\n  if (x250_29_repelFromThreats) {\n    if (const TCastToConstPtr<CPlayer> player = other) {\n      const zeus::CVector3f playerPos = player->GetTranslation();\n      for (auto& b : xe8_boids) {\n        zeus::CVector3f adjPlayerPos = playerPos;\n        const float zDelta = b.x0_pos.z() - adjPlayerPos.z();\n        if (zDelta > 0.f && zDelta < 2.3f) {\n          adjPlayerPos.z() = float(b.x0_pos.z());\n        }\n        adjPlayerPos.x() += mgr.GetActiveRandom()->Float() * 0.2f - 0.1f;\n        adjPlayerPos.y() += mgr.GetActiveRandom()->Float() * 0.2f - 0.1f;\n        ApplyRepulsion(b, adjPlayerPos, 8.f, x14c_playerRepelMagnitude - x164_playerRepelDamping);\n      }\n    }\n    x250_30_enablePlayerRepelDamping = true;\n  }\n}\n\nzeus::CAABox CFishCloud::GetUntransformedBoundingBox() const {\n  const zeus::CVector3f extent = x120_scale * 0.75f;\n  return zeus::CAABox(-extent, extent);\n}\n\nzeus::CAABox CFishCloud::GetBoundingBox() const {\n  return GetUntransformedBoundingBox().getTransformedAABox(x34_transform);\n}\n\nvoid CFishCloud::RemoveRepulsor(TUniqueId sourceId) {\n  const CModifierSource source(sourceId, true, false, 0.f, 0.f);\n  const auto it = rstl::binary_find(x108_modifierSources.begin(), x108_modifierSources.end(), source);\n\n  if (it == x108_modifierSources.end()) {\n    return;\n  }\n\n  x108_modifierSources.erase(it);\n}\n\nvoid CFishCloud::RemoveAttractor(TUniqueId sourceId) {\n  const CModifierSource source(sourceId, false, false, 0.f, 0.f);\n  const auto it = rstl::binary_find(x108_modifierSources.begin(), x108_modifierSources.end(), source);\n\n  if (it == x108_modifierSources.end()) {\n    return;\n  }\n\n  x108_modifierSources.erase(it);\n}\n\nbool CFishCloud::AddRepulsor(TUniqueId sourceId, bool swirl, float radius, float priority) {\n  const CModifierSource source(sourceId, true, swirl, radius, priority);\n  const auto it = rstl::binary_find(x108_modifierSources.begin(), x108_modifierSources.end(), source);\n\n  if (it != x108_modifierSources.end()) {\n    it->x4_radius = radius;\n    it->x8_priority = priority;\n    return true;\n  }\n\n  if (x108_modifierSources.size() < x108_modifierSources.capacity()) {\n    x108_modifierSources.insert(std::lower_bound(x108_modifierSources.begin(), x108_modifierSources.end(), source),\n                                source);\n    return true;\n  }\n\n  return false;\n}\n\nbool CFishCloud::AddAttractor(TUniqueId sourceId, bool swirl, float radius, float priority) {\n  const CModifierSource source(sourceId, false, swirl, radius, priority);\n  const auto it = rstl::binary_find(x108_modifierSources.begin(), x108_modifierSources.end(), source);\n\n  if (it != x108_modifierSources.end()) {\n    it->x4_radius = radius;\n    it->x8_priority = priority;\n    return true;\n  }\n\n  if (x108_modifierSources.size() < x108_modifierSources.capacity()) {\n    x108_modifierSources.insert(std::lower_bound(x108_modifierSources.begin(), x108_modifierSources.end(), source),\n                                source);\n    return true;\n  }\n\n  return false;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CFishCloud.hpp",
    "content": "#pragma once\n\n#include <optional>\n#include <vector>\n\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/World/CActor.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\n\nclass CFishCloud : public CActor {\n  class CBoid {\n    friend class CFishCloud;\n    zeus::CVector3f x0_pos;\n    zeus::CVector3f xc_vel;\n    float x18_scale;\n    CBoid* x1c_next = nullptr;\n    bool x20_active = true;\n\n  public:\n    CBoid(const zeus::CVector3f& pos, const zeus::CVector3f& vel, float scale)\n    : x0_pos(pos), xc_vel(vel), x18_scale(scale) {}\n    zeus::CVector3f& Translation() { return x0_pos; }\n    const zeus::CVector3f& GetTranslation() const { return x0_pos; }\n  };\n  class CModifierSource {\n    friend class CFishCloud;\n    TUniqueId x0_source;\n    float x4_radius;\n    float x8_priority;\n    bool xc_isRepulsor;\n    bool xd_isSwirl;\n\n  public:\n    CModifierSource(TUniqueId source, bool repulsor, bool swirl, float radius, float priority)\n    : x0_source(source), x4_radius(radius), x8_priority(priority), xc_isRepulsor(repulsor), xd_isSwirl(swirl) {}\n    void SetAffectPriority(float p) { x8_priority = p; }\n    void SetAffectRadius(float r) { x4_radius = r; }\n    float GetAffectPriority() const { return x8_priority; }\n    float GetAffectRadius() const { return x4_radius; }\n    bool IsRepulsor() const { return xc_isRepulsor; }\n    bool IsSwirl() const { return xd_isSwirl; }\n    TUniqueId GetSource() const { return x0_source; }\n    bool operator<(const CModifierSource& other) const {\n      if (x0_source == other.x0_source)\n        return xc_isRepulsor < other.xc_isRepulsor;\n      return x0_source < other.x0_source;\n    }\n  };\n  std::vector<CBoid> xe8_boids;\n  std::vector<CBoid*> xf8_boidPartitionLists;\n  std::vector<CModifierSource> x108_modifierSources;\n  u32 x118_thinkCounter = 0;\n  u32 x11c_updateMask;\n  zeus::CVector3f x120_scale;\n  float x12c_randomMovementTimer = 0.f;\n  float x130_speed;\n  u32 x134_numBoids;\n  float x138_separationRadius;\n  float x13c_cohesionMagnitude;\n  float x140_alignmentWeight;\n  float x144_separationMagnitude;\n  float x148_weaponRepelMagnitude;\n  float x14c_playerRepelMagnitude;\n  float x150_scatterVel;\n  float x154_maxScatterAngle;\n  float x158_containmentMagnitude;\n  float x15c_playerRepelDampingSpeed;\n  float x160_weaponRepelDampingSpeed;\n  float x164_playerRepelDamping;\n  float x168_weaponRepelDamping;\n  zeus::CColor x16c_color;\n  float x170_weaponKillRadius;\n  float x174_containmentRadius;\n  mutable rstl::reserved_vector<SSkinningWorkspace, 4> x178_workspaces;\n  // Originally:\n  // rstl::reserved_vector<rstl::auto_ptr<float[]>, 4> x178_posWorkspaces;\n  // rstl::reserved_vector<float[], 4> x19c_nrmWorkspaces;\n  rstl::reserved_vector<std::shared_ptr<CModelData>, 4> x1b0_models;\n  rstl::reserved_vector<TLockedToken<CGenDescription>, 4> x1c4_particleDescs;\n  rstl::reserved_vector<std::unique_ptr<CElementGen>, 4> x1f8_particleGens;\n  rstl::reserved_vector<u32, 4> x21c_deathParticleCounts;\n  CModelData::EWhichModel x230_whichModel{};\n  u16 x234_deathSfx;\n  zeus::CVector3f x238_partitionPitch;\n  zeus::CVector3f x244_ooPartitionPitch;\n  bool x250_24_randomMovement : 1 = false;\n  bool x250_25_worldSpace : 1 = true; // The result of a close_enough paradox (weird inlined test?)\n  bool x250_26_enableWeaponRepelDamping : 1 = false;\n  bool x250_27_validModel : 1 = false;\n  bool x250_28_killable : 1;\n  bool x250_29_repelFromThreats : 1;\n  bool x250_30_enablePlayerRepelDamping : 1 = false;\n  bool x250_31_updateWithoutPartitions : 1 = false;\n\n  void UpdateParticles(float dt);\n  void UpdatePartitionList();\n  bool PointInBox(const zeus::CAABox& aabb, const zeus::CVector3f& point) const;\n  zeus::CPlane FindClosestPlane(const zeus::CAABox& aabb, const zeus::CVector3f& point) const;\n  CBoid* GetListAt(const zeus::CVector3f& pos);\n  void BuildBoidNearList(const zeus::CVector3f& pos, float radius, rstl::reserved_vector<CBoid*, 25>& nearList);\n  void BuildBoidNearPartitionList(const zeus::CVector3f& pos, float radius,\n                                  rstl::reserved_vector<CBoid*, 25>& nearList);\n  void PlaceBoid(CStateManager& mgr, CBoid& boid, const zeus::CAABox& aabb) const;\n  void ApplySeparation(CBoid& boid, const rstl::reserved_vector<CBoid*, 25>& nearList) const;\n  void ApplySeparation(CBoid& boid, const zeus::CVector3f& separateFrom, float separationRadius,\n                       float separationMagnitude) const;\n  void ApplyCohesion(CBoid& boid, const rstl::reserved_vector<CBoid*, 25>& nearList) const;\n  void ApplyCohesion(CBoid& boid, const zeus::CVector3f& cohesionFrom, float cohesionRadius,\n                     float cohesionMagnitude) const;\n  void ApplyAlignment(CBoid& boid, const rstl::reserved_vector<CBoid*, 25>& nearList) const;\n  void ApplyAttraction(CBoid& boid, const zeus::CVector3f& attractTo, float attractionRadius,\n                       float attractionMagnitude) const;\n  void ApplyRepulsion(CBoid& boid, const zeus::CVector3f& attractTo, float repulsionRadius,\n                      float repulsionMagnitude) const;\n  void ApplySwirl(CBoid& boid, const zeus::CVector3f& swirlPoint, bool clockwise, float magnitude, float radius) const;\n  void ApplyContainment(CBoid& boid, const zeus::CAABox& aabb) const;\n  void ScatterBoid(CStateManager& mgr, CBoid& b) const;\n  void CreateBoidDeathParticle(CBoid& b) const;\n  void KillBoid(CBoid& b) const;\n  zeus::CAABox GetUntransformedBoundingBox() const;\n  zeus::CAABox GetBoundingBox() const;\n  void CreatePartitionList();\n  void AllocateSkinnedModels(CStateManager& mgr, CModelData::EWhichModel which);\n  void AddParticlesToRenderer() const;\n  void RenderBoid(int idx, const CBoid& boid, u32& drawMask, bool thermalHot, const CModelFlags& flags) const;\n\npublic:\n  DEFINE_ENTITY\n  CFishCloud(TUniqueId uid, bool active, std::string_view name, const CEntityInfo& info, const zeus::CVector3f& scale,\n             const zeus::CTransform& xf, CModelData&& mData, const CAnimRes& aRes, u32 numBoids, float speed,\n             float separationRadius, float cohesionMagnitude, float alignmentWeight, float separationMagnitude,\n             float weaponRepelMagnitude, float playerRepelMagnitude, float containmentMagnitude, float scatterVel,\n             float maxScatterAngle, float weaponRepelDampingSpeed, float playerRepelDampingSpeed,\n             float containmentRadius, u32 updateShift, const zeus::CColor& color, bool killable, float weaponKillRadius,\n             CAssetId part1, u32 partCount1, CAssetId part2, u32 partCount2, CAssetId part3, u32 partCount3,\n             CAssetId part4, u32 partCount4, u32 deathSfx, bool repelFromThreats, bool hotInThermal);\n\n  void Accept(IVisitor& visitor) override;\n  void Think(float dt, CStateManager& mgr) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) override;\n  void PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) override;\n  void Render(CStateManager& mgr) override;\n  void CalculateRenderBounds() override;\n  std::optional<zeus::CAABox> GetTouchBounds() const override;\n  void Touch(CActor& other, CStateManager& mgr) override;\n  void RemoveRepulsor(TUniqueId source);\n  void RemoveAttractor(TUniqueId source);\n  bool AddRepulsor(TUniqueId source, bool swirl, float radius, float priority);\n  bool AddAttractor(TUniqueId source, bool swirl, float radius, float priority);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CFishCloudModifier.cpp",
    "content": "#include \"Runtime/World/CFishCloudModifier.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/World/CFishCloud.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nCFishCloudModifier::CFishCloudModifier(TUniqueId uid, bool active, std::string_view name, const CEntityInfo& eInfo,\n                                       const zeus::CVector3f& pos, bool isRepulsor, bool swirl, float radius,\n                                       float priority)\n: CActor(uid, active, name, eInfo, zeus::CTransform::Translate(pos), CModelData::CModelDataNull(),\n         {EMaterialTypes::NoStepLogic}, CActorParameters::None(), kInvalidUniqueId)\n, xe8_radius(radius)\n, xec_priority(priority)\n, xf0_isRepulsor(isRepulsor)\n, xf1_swirl(swirl) {}\n\nvoid CFishCloudModifier::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CFishCloudModifier::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  CActor::AcceptScriptMsg(msg, uid, mgr);\n\n  if ((msg == EScriptObjectMessage::Activate || msg == EScriptObjectMessage::InitializedInArea) && GetActive())\n    AddSelf(mgr);\n  else if (msg == EScriptObjectMessage::Deactivate || msg == EScriptObjectMessage::Deleted)\n    RemoveSelf(mgr);\n}\n\nvoid CFishCloudModifier::AddSelf(CStateManager& mgr) {\n  for (const SConnection& conn : GetConnectionList()) {\n    if (conn.x0_state != EScriptObjectState::Modify || conn.x4_msg != EScriptObjectMessage::Follow)\n      continue;\n\n    if (TCastToPtr<CFishCloud> fishCloud = mgr.ObjectById(mgr.GetIdForScript(conn.x8_objId))) {\n      if (xf0_isRepulsor)\n        fishCloud->AddRepulsor(GetUniqueId(), xf1_swirl, xe8_radius, xec_priority);\n      else\n        fishCloud->AddAttractor(GetUniqueId(), xf1_swirl, xe8_radius, xec_priority);\n    }\n  }\n}\n\nvoid CFishCloudModifier::RemoveSelf(CStateManager& mgr) {\n  for (const SConnection& conn : GetConnectionList()) {\n    if (conn.x0_state != EScriptObjectState::Modify || conn.x4_msg != EScriptObjectMessage::Follow)\n      continue;\n\n    if (TCastToPtr<CFishCloud> fishCloud = mgr.ObjectById(mgr.GetIdForScript(conn.x8_objId))) {\n      if (xf0_isRepulsor)\n        fishCloud->RemoveRepulsor(GetUniqueId());\n      else\n        fishCloud->RemoveAttractor(GetUniqueId());\n    }\n  }\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CFishCloudModifier.hpp",
    "content": "#pragma once\n\n#include \"Runtime/World/CActor.hpp\"\n\nnamespace metaforce {\nclass CFishCloudModifier : public CActor {\n  float xe8_radius;\n  float xec_priority;\n  bool xf0_isRepulsor;\n  bool xf1_swirl;\n\npublic:\n  DEFINE_ENTITY\n  CFishCloudModifier(TUniqueId uid, bool active, std::string_view name, const CEntityInfo& eInfo,\n                     const zeus::CVector3f& pos, bool isRepulsor, bool swirl, float radius, float priority);\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n\n  void AddSelf(CStateManager&);\n  void RemoveSelf(CStateManager&);\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CFluidPlane.cpp",
    "content": "#include \"Runtime/World/CFluidPlane.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/World/CRipple.hpp\"\n#include \"Runtime/World/CScriptWater.hpp\"\n\nnamespace metaforce {\n\nCFluidPlane::CFluidPlane(CAssetId texPattern1, CAssetId texPattern2, CAssetId texColor, float alpha,\n                         EFluidType fluidType, float rippleIntensity, const CFluidUVMotion& motion)\n: x4_texPattern1Id(texPattern1)\n, x8_texPattern2Id(texPattern2)\n, xc_texColorId(texColor)\n, x40_alpha(alpha)\n, x44_fluidType(fluidType)\n, x48_rippleIntensity(rippleIntensity)\n, x4c_uvMotion(motion) {\n  if (g_ResFactory->GetResourceTypeById(texPattern1) == FOURCC('TXTR'))\n    x10_texPattern1 = g_SimplePool->GetObj(SObjectTag{FOURCC('TXTR'), texPattern1});\n  if (g_ResFactory->GetResourceTypeById(texPattern2) == FOURCC('TXTR'))\n    x20_texPattern2 = g_SimplePool->GetObj(SObjectTag{FOURCC('TXTR'), texPattern2});\n  if (g_ResFactory->GetResourceTypeById(texColor) == FOURCC('TXTR'))\n    x30_texColor = g_SimplePool->GetObj(SObjectTag{FOURCC('TXTR'), texColor});\n}\n\nfloat CFluidPlane::ProjectRippleVelocity(float baseI, float velDot) const {\n  float tmp = 0.5f * baseI * velDot * velDot;\n  if (tmp != 0.f)\n    tmp = std::sqrt(tmp);\n  if (tmp >= 160.f)\n    return 1.f;\n  return tmp / 160.f;\n}\n\nfloat CFluidPlane::CalculateRippleIntensity(float baseI) const {\n  float mul = 0.0f;\n  switch (x44_fluidType) {\n  case EFluidType::NormalWater:\n    mul = g_tweakGame->GetRippleIntensityNormal();\n    break;\n  case EFluidType::PoisonWater:\n    mul = g_tweakGame->GetRippleIntensityPoison();\n    break;\n  case EFluidType::Lava:\n    mul = g_tweakGame->GetRippleIntensityLava();\n    break;\n  case EFluidType::PhazonFluid:\n  case EFluidType::Four:\n    mul = 0.8f;\n    break;\n  case EFluidType::ThickLava:\n    mul = 1.f;\n    break;\n  }\n\n  return zeus::clamp(0.f, baseI * mul * (1.f - x48_rippleIntensity + 0.5f), 1.f);\n}\n\nvoid CFluidPlane::AddRipple(float mag, TUniqueId rippler, const zeus::CVector3f& center, CScriptWater& water,\n                            CStateManager& mgr) {\n  if (!water.CanRippleAtPoint(center))\n    return;\n\n  mag = CalculateRippleIntensity(mag);\n  mgr.GetFluidPlaneManager()->RippleManager().AddRipple(CRipple(rippler, center, mag));\n}\n\nvoid CFluidPlane::AddRipple(float intensity, TUniqueId rippler, const zeus::CVector3f& center,\n                            const zeus::CVector3f& velocity, const CScriptWater& water, CStateManager& mgr,\n                            const zeus::CVector3f& upVec) {\n  if (!water.CanRippleAtPoint(center))\n    return;\n\n  intensity = CalculateRippleIntensity(ProjectRippleVelocity(intensity, upVec.dot(velocity)));\n  mgr.GetFluidPlaneManager()->RippleManager().AddRipple(CRipple(rippler, center, intensity));\n}\n\nvoid CFluidPlane::AddRipple(const CRipple& ripple, const CScriptWater& water, CStateManager& mgr) {\n  if (!water.CanRippleAtPoint(ripple.GetCenter()))\n    return;\n  mgr.GetFluidPlaneManager()->RippleManager().AddRipple(ripple);\n}\n\n// void CFluidPlane::RenderStripWithRipples(float curY, const Heights& heights, const Flags& flags, int startYDiv,\n//                                          const CFluidPlaneRender::SPatchInfo& info,\n//                                          std::vector<CFluidPlaneShader::Vertex>& vOut,\n//                                          std::vector<CFluidPlaneShader::PatchVertex>& pvOut) {\n//   m_shader->bindRegular();\n//\n//   int yTile = (startYDiv + CFluidPlaneRender::numSubdivisionsInTile - 1) / CFluidPlaneRender::numSubdivisionsInTile;\n//   int endXTile =\n//       (info.x0_xSubdivs + CFluidPlaneRender::numSubdivisionsInTile - 4) / CFluidPlaneRender::numSubdivisionsInTile;\n//\n//   int midDiv = CFluidPlaneRender::numSubdivisionsInTile / 2;\n//   float tileMid = info.x18_rippleResolution * midDiv;\n//   float yMin = curY;\n//   float yMid = curY + tileMid;\n//\n//   float curX = info.x4_localMin.x();\n//   int gridCell = info.x28_tileX + info.x2a_gridDimX * (info.x2e_tileY + yTile - 1);\n//   int xTile = 1;\n//   int tileSpan;\n//   for (int i = 1; i < info.x0_xSubdivs - 2; i += CFluidPlaneRender::numSubdivisionsInTile * tileSpan,\n//            gridCell += tileSpan, xTile += tileSpan, curX += info.x14_tileSize * tileSpan) {\n//     tileSpan = 1;\n//     if (info.x30_gridFlags && !info.x30_gridFlags[gridCell])\n//       continue;\n//\n//     if ((flags[yTile][xTile] & 0x1f) == 0x1f) {\n//       for (; xTile + tileSpan <= endXTile; ++tileSpan) {\n//         if ((flags[yTile][xTile + tileSpan] & 0x1f) != 0x1f)\n//           break;\n//         if (info.x30_gridFlags && !info.x30_gridFlags[gridCell + tileSpan])\n//           break;\n//       }\n//\n//       int stripDivCount = tileSpan * CFluidPlaneRender::numSubdivisionsInTile + 1;\n//       int remSubdivs = CFluidPlaneRender::numSubdivisionsInTile;\n//       std::function<void(float x, float y, const CFluidPlaneRender::SHFieldSample& samp)> func;\n//\n//       switch (info.x37_normalMode) {\n//       case CFluidPlaneRender::NormalMode::None:\n//         func = [&](float x, float y, const CFluidPlaneRender::SHFieldSample& samp) {\n//           vOut.emplace_back(zeus::CVector3f(x, y, samp.height));\n//         };\n//         break;\n//       case CFluidPlaneRender::NormalMode::NoNormals:\n//         func = [&](float x, float y, const CFluidPlaneRender::SHFieldSample& samp) {\n//           vOut.emplace_back(zeus::CVector3f(x, y, samp.height), samp.MakeColor(info));\n//         };\n//         break;\n//       case CFluidPlaneRender::NormalMode::Normals:\n//         func = [&](float x, float y, const CFluidPlaneRender::SHFieldSample& samp) {\n//           vOut.emplace_back(zeus::CVector3f(x, y, samp.height), samp.MakeNormal(), samp.MakeColor(info));\n//         };\n//         break;\n//       case CFluidPlaneRender::NormalMode::NBT:\n//         func = [&](float x, float y, const CFluidPlaneRender::SHFieldSample& samp) {\n//           vOut.emplace_back(zeus::CVector3f(x, y, samp.height), samp.MakeNormal(), samp.MakeBinormal(),\n//                             samp.MakeTangent(), samp.MakeColor(info));\n//         };\n//         break;\n//       }\n//\n//       float curTileY = yMin;\n//       int curYDiv = startYDiv;\n//       for (; remSubdivs > 0; --remSubdivs, ++curYDiv, curTileY += info.x18_rippleResolution) {\n//         size_t start = vOut.size();\n//         float curTileX = curX;\n//         for (int v = 0; v < stripDivCount; ++v) {\n//           func(curTileX, curTileY, heights[curYDiv][i + v]);\n//           func(curTileX, curTileY + info.x18_rippleResolution, heights[curYDiv + 1][i + v]);\n//           curTileX += info.x18_rippleResolution;\n//         }\n// //        CGraphics::DrawArray(start, vOut.size() - start);\n//       }\n//     } else {\n//       bool r19 = (flags[yTile + 1][xTile] & 0x2) != 0; // North\n//       bool r16 = (flags[yTile][xTile - 1] & 0x8) != 0; // West\n//       bool r18 = (flags[yTile][xTile + 1] & 0x4) != 0; // East\n//       bool r17 = (flags[yTile - 1][xTile] & 0x1) != 0; // South\n//\n//       int r6 = (r19 ? CFluidPlaneRender::numSubdivisionsInTile : 1) + 2;\n//       r6 += r18 ? CFluidPlaneRender::numSubdivisionsInTile : 1;\n//       r6 += r17 ? CFluidPlaneRender::numSubdivisionsInTile : 1;\n//       r6 += r16 ? CFluidPlaneRender::numSubdivisionsInTile : 1;\n//\n//       if (r6 == 6 && (info.x37_normalMode == CFluidPlaneRender::NormalMode::Normals ||\n//                       info.x37_normalMode == CFluidPlaneRender::NormalMode::NBT)) {\n//         for (; xTile + tileSpan <= endXTile; ++tileSpan) {\n//           if ((flags[yTile][xTile + tileSpan] & 0x1f) == 0x1f)\n//             break;\n//           if (info.x30_gridFlags && !info.x30_gridFlags[gridCell + tileSpan])\n//             break;\n//           if ((flags[yTile + 1][xTile + tileSpan] & 0x2) != 0x0)\n//             break;\n//           if ((flags[yTile][xTile + tileSpan + 1] & 0x4) != 0x0)\n//             break;\n//           if ((flags[yTile - 1][xTile + tileSpan] & 0x1) != 0x0)\n//             break;\n//         }\n//\n//         int stripDivCount = tileSpan + 1;\n//         size_t start = vOut.size();\n//         switch (info.x37_normalMode) {\n//         case CFluidPlaneRender::NormalMode::Normals: {\n//           int curYDiv0 = startYDiv;\n//           int curYDiv1 = startYDiv + CFluidPlaneRender::numSubdivisionsInTile;\n//           float curTileX = curX;\n//           for (int v = 0; v < stripDivCount; ++v) {\n//             int curXDiv = v * CFluidPlaneRender::numSubdivisionsInTile + i;\n//             const CFluidPlaneRender::SHFieldSample& samp0 = heights[curYDiv0][curXDiv];\n//             const CFluidPlaneRender::SHFieldSample& samp1 = heights[curYDiv1][curXDiv];\n//             vOut.emplace_back(zeus::CVector3f(curTileX, yMin, samp0.height), samp0.MakeNormal(), samp0.MakeColor(info));\n//             vOut.emplace_back(zeus::CVector3f(curTileX, yMin + info.x14_tileSize, samp1.height), samp1.MakeNormal(),\n//                               samp1.MakeColor(info));\n//             curTileX += info.x14_tileSize;\n//           }\n//           break;\n//         }\n//         case CFluidPlaneRender::NormalMode::NBT: {\n//           int curYDiv0 = startYDiv;\n//           int curYDiv1 = startYDiv + CFluidPlaneRender::numSubdivisionsInTile;\n//           float curTileX = curX;\n//           for (int v = 0; v < stripDivCount; ++v) {\n//             int curXDiv = v * CFluidPlaneRender::numSubdivisionsInTile + i;\n//             const CFluidPlaneRender::SHFieldSample& samp0 = heights[curYDiv0][curXDiv];\n//             const CFluidPlaneRender::SHFieldSample& samp1 = heights[curYDiv1][curXDiv];\n//             vOut.emplace_back(zeus::CVector3f(curTileX, yMin, samp0.height), samp0.MakeNormal(), samp0.MakeBinormal(),\n//                               samp0.MakeTangent(), samp0.MakeColor(info));\n//             vOut.emplace_back(zeus::CVector3f(curTileX, yMin + info.x14_tileSize, samp1.height), samp1.MakeNormal(),\n//                               samp1.MakeBinormal(), samp1.MakeTangent(), samp1.MakeColor(info));\n//             curTileX += info.x14_tileSize;\n//           }\n//           break;\n//         }\n//         default:\n//           break;\n//         }\n// //        CGraphics::DrawArray(start, vOut.size() - start);\n//       } else {\n//         TriFanToStrip<CFluidPlaneShader::Vertex> toStrip(vOut);\n//         std::function<void(float x, float y, const CFluidPlaneRender::SHFieldSample& samp)> func;\n//\n//         switch (info.x37_normalMode) {\n//         case CFluidPlaneRender::NormalMode::None:\n//           func = [&](float x, float y, const CFluidPlaneRender::SHFieldSample& samp) {\n//             toStrip.EmplaceVert(zeus::CVector3f(x, y, samp.height));\n//           };\n//           break;\n//         case CFluidPlaneRender::NormalMode::NoNormals:\n//           func = [&](float x, float y, const CFluidPlaneRender::SHFieldSample& samp) {\n//             toStrip.EmplaceVert(zeus::CVector3f(x, y, samp.height), samp.MakeColor(info));\n//           };\n//           break;\n//         case CFluidPlaneRender::NormalMode::Normals:\n//           func = [&](float x, float y, const CFluidPlaneRender::SHFieldSample& samp) {\n//             toStrip.EmplaceVert(zeus::CVector3f(x, y, samp.height), samp.MakeNormal(), samp.MakeColor(info));\n//           };\n//           break;\n//         case CFluidPlaneRender::NormalMode::NBT:\n//           func = [&](float x, float y, const CFluidPlaneRender::SHFieldSample& samp) {\n//             toStrip.EmplaceVert(zeus::CVector3f(x, y, samp.height), samp.MakeNormal(), samp.MakeBinormal(),\n//                                 samp.MakeTangent(), samp.MakeColor(info));\n//           };\n//           break;\n//         }\n//\n//         func(tileMid + curX, yMid, heights[startYDiv + midDiv][i + midDiv]);\n//\n//         int curXDiv = i;\n//         int curYDiv = startYDiv + CFluidPlaneRender::numSubdivisionsInTile;\n//         float curTileX = curX;\n//         float curTileY = yMin + info.x14_tileSize;\n//         for (int v = 0; v < (r19 ? CFluidPlaneRender::numSubdivisionsInTile : 1); ++v) {\n//           const CFluidPlaneRender::SHFieldSample& samp = heights[curYDiv][curXDiv + v];\n//           func(curTileX, curTileY, samp);\n//           curTileX += info.x18_rippleResolution;\n//         }\n//\n//         curXDiv = i + CFluidPlaneRender::numSubdivisionsInTile;\n//         curYDiv = startYDiv + CFluidPlaneRender::numSubdivisionsInTile;\n//         curTileX = curX + info.x14_tileSize;\n//         curTileY = yMin + info.x14_tileSize;\n//         for (int v = 0; v < (r18 ? CFluidPlaneRender::numSubdivisionsInTile : 1); ++v) {\n//           const CFluidPlaneRender::SHFieldSample& samp = heights[curYDiv - v][curXDiv];\n//           func(curTileX, curTileY, samp);\n//           curTileY -= info.x18_rippleResolution;\n//         }\n//\n//         curXDiv = i + CFluidPlaneRender::numSubdivisionsInTile;\n//         curYDiv = startYDiv;\n//         curTileX = curX + info.x14_tileSize;\n//         curTileY = yMin;\n//         for (int v = 0; v < (r17 ? CFluidPlaneRender::numSubdivisionsInTile : 1); ++v) {\n//           const CFluidPlaneRender::SHFieldSample& samp = heights[curYDiv][curXDiv - v];\n//           func(curTileX, curTileY, samp);\n//           curTileX -= info.x18_rippleResolution;\n//         }\n//\n//         curXDiv = i;\n//         curYDiv = startYDiv;\n//         curTileX = curX;\n//         curTileY = yMin;\n//         if (r16) {\n//           for (int v = 0; v < CFluidPlaneRender::numSubdivisionsInTile + 1; ++v) {\n//             const CFluidPlaneRender::SHFieldSample& samp = heights[curYDiv + v][curXDiv];\n//             func(curTileX, curTileY, samp);\n//             curTileY += info.x18_rippleResolution;\n//           }\n//         } else {\n//           {\n//             const CFluidPlaneRender::SHFieldSample& samp = heights[curYDiv][curXDiv];\n//             func(curTileX, curTileY, samp);\n//           }\n//           curTileY += info.x14_tileSize;\n//           {\n//             const CFluidPlaneRender::SHFieldSample& samp =\n//                 heights[curYDiv + CFluidPlaneRender::numSubdivisionsInTile][curXDiv];\n//             func(curTileX, curTileY, samp);\n//           }\n//         }\n//\n// //        toStrip.Draw();\n//       }\n//     }\n//   }\n// }\n//\n// void CFluidPlane::RenderPatch(const CFluidPlaneRender::SPatchInfo& info, const Heights& heights, const Flags& flags,\n//                               bool noRipples, bool flagIs1, std::vector<CFluidPlaneShader::Vertex>& vOut,\n//                               std::vector<CFluidPlaneShader::PatchVertex>& pvOut) {\n//   OPTICK_EVENT();\n//   if (noRipples) {\n//     m_shader->bindRegular();\n//\n//     float xMin = info.x4_localMin.x();\n//     float yMin = info.x4_localMin.y();\n//     float xMax = info.x18_rippleResolution * (info.x0_xSubdivs - 2) + xMin;\n//     float yMax = info.x18_rippleResolution * (info.x1_ySubdivs - 2) + yMin;\n//\n//     switch (info.x37_normalMode) {\n//     case CFluidPlaneRender::NormalMode::None: {\n//       size_t start = vOut.size();\n//       vOut.emplace_back(zeus::CVector3f(xMin, yMin, 0.f));\n//       vOut.emplace_back(zeus::CVector3f(xMin, yMax, 0.f));\n//       vOut.emplace_back(zeus::CVector3f(xMax, yMin, 0.f));\n//       vOut.emplace_back(zeus::CVector3f(xMax, yMax, 0.f));\n// //      CGraphics::DrawArray(start, 4);\n//       break;\n//     }\n//     case CFluidPlaneRender::NormalMode::NoNormals: {\n//       size_t start = vOut.size();\n//       vOut.emplace_back(zeus::CVector3f(xMin, yMin, 0.f), zeus::skBlack);\n//       vOut.emplace_back(zeus::CVector3f(xMin, yMax, 0.f), zeus::skBlack);\n//       vOut.emplace_back(zeus::CVector3f(xMax, yMin, 0.f), zeus::skBlack);\n//       vOut.emplace_back(zeus::CVector3f(xMax, yMax, 0.f), zeus::skBlack);\n// //      CGraphics::DrawArray(start, 4);\n//       break;\n//     }\n//     case CFluidPlaneRender::NormalMode::Normals: {\n//       int yTiles = (info.x1_ySubdivs - 3) / CFluidPlaneRender::numSubdivisionsInTile + 1;\n//       int xTiles = (info.x0_xSubdivs - 3) / CFluidPlaneRender::numSubdivisionsInTile + 1;\n//       int xTileStart = info.x28_tileX + info.x2e_tileY * info.x2a_gridDimX;\n//       yMax = yMin;\n//       for (int curYTile = yTiles; curYTile > 0;\n//            --curYTile, yMax += info.x14_tileSize, xTileStart += info.x2a_gridDimX) {\n//         xMax = xMin;\n//         int nextXTile;\n//         for (int curXTile = 0; curXTile < xTiles; curXTile = nextXTile) {\n//           if (!info.x30_gridFlags || info.x30_gridFlags[xTileStart + curXTile]) {\n//             if (curYTile == yTiles || curYTile == 1 || curXTile == 0 || xTiles - 1 == curXTile) {\n//               TriFanToStrip<CFluidPlaneShader::Vertex> toStrip(vOut);\n//\n//               toStrip.EmplaceVert(\n//                   zeus::CVector3f(xMax + 0.5f * info.x14_tileSize, yMax + 0.5f * info.x14_tileSize, 0.f), zeus::skUp,\n//                   zeus::skBlack);\n//\n//               float tmp = xMax;\n//               for (int v = 0; v < ((curYTile == 1) ? CFluidPlaneRender::numSubdivisionsInTile : 1); ++v) {\n//                 toStrip.EmplaceVert(zeus::CVector3f(tmp, yMax + info.x14_tileSize, 0.f), zeus::skUp, zeus::skBlack);\n//                 tmp += info.x18_rippleResolution;\n//               }\n//\n//               tmp = yMax + info.x14_tileSize;\n//               for (int v = 0; v < ((xTiles - 1 == curXTile) ? CFluidPlaneRender::numSubdivisionsInTile : 1); ++v) {\n//                 toStrip.EmplaceVert(zeus::CVector3f(xMax + info.x14_tileSize, tmp, 0.f), zeus::skUp, zeus::skBlack);\n//                 tmp -= info.x18_rippleResolution;\n//               }\n//\n//               tmp = xMax + info.x14_tileSize;\n//               for (int v = 0; v < ((curYTile == yTiles) ? CFluidPlaneRender::numSubdivisionsInTile : 1); ++v) {\n//                 toStrip.EmplaceVert(zeus::CVector3f(tmp, yMax, 0.f), zeus::skUp, zeus::skBlack);\n//                 tmp -= info.x18_rippleResolution;\n//               }\n//\n//               tmp = yMax;\n//               for (int v = 0; v < ((curXTile == 0) ? CFluidPlaneRender::numSubdivisionsInTile : 1); ++v) {\n//                 toStrip.EmplaceVert(zeus::CVector3f(xMax, tmp, 0.f), zeus::skUp, zeus::skBlack);\n//                 tmp += info.x18_rippleResolution;\n//               }\n//\n//               toStrip.EmplaceVert(zeus::CVector3f(xMax, yMax + info.x14_tileSize, 0.f), zeus::skUp, zeus::skBlack);\n//\n// //              toStrip.Draw();\n//\n//               nextXTile = curXTile + 1;\n//               xMax += info.x14_tileSize;\n//             } else {\n//               nextXTile = curXTile + 1;\n//               while (nextXTile < xTiles - 1 && (!info.x30_gridFlags || info.x30_gridFlags[xTileStart + nextXTile]))\n//                 ++nextXTile;\n//\n//               size_t start = vOut.size();\n//               for (int v = 0; v < nextXTile - curXTile + 1; ++v) {\n//                 vOut.emplace_back(zeus::CVector3f(xMax, yMax, 0.f), zeus::skUp, zeus::skBlack);\n//                 vOut.emplace_back(zeus::CVector3f(xMax, yMax + info.x14_tileSize, 0.f), zeus::skUp, zeus::skBlack);\n//                 xMax += info.x14_tileSize;\n//               }\n// //              CGraphics::DrawArray(start, vOut.size() - start);\n//\n//               ++nextXTile;\n//               if (nextXTile == xTiles) {\n//                 --nextXTile;\n//                 xMax -= info.x14_tileSize;\n//               }\n//             }\n//           } else {\n//             nextXTile = curXTile + 1;\n//             xMax += info.x14_tileSize;\n//             while (nextXTile < xTiles && !info.x30_gridFlags[xTileStart + nextXTile]) {\n//               xMax += info.x14_tileSize;\n//               ++nextXTile;\n//             }\n//           }\n//         }\n//       }\n//       break;\n//     }\n//     case CFluidPlaneRender::NormalMode::NBT: {\n//       if (flagIs1 || !info.x30_gridFlags) {\n//         size_t start = vOut.size();\n//         vOut.emplace_back(zeus::CVector3f(xMin, yMin, 0.f), zeus::skUp, zeus::skForward, zeus::skRight, zeus::skBlack);\n//         vOut.emplace_back(zeus::CVector3f(xMin, yMax, 0.f), zeus::skUp, zeus::skForward, zeus::skRight, zeus::skBlack);\n//         vOut.emplace_back(zeus::CVector3f(xMax, yMin, 0.f), zeus::skUp, zeus::skForward, zeus::skRight, zeus::skBlack);\n//         vOut.emplace_back(zeus::CVector3f(xMax, yMax, 0.f), zeus::skUp, zeus::skForward, zeus::skRight, zeus::skBlack);\n// //        CGraphics::DrawArray(start, 4);\n//       } else {\n//         int xTiles = (info.x0_xSubdivs - 3) / CFluidPlaneRender::numSubdivisionsInTile + 1;\n//         int yTiles = (info.x1_ySubdivs - 3) / CFluidPlaneRender::numSubdivisionsInTile + 1;\n//         int xTileStart = info.x28_tileX + info.x2e_tileY * info.x2a_gridDimX;\n//         for (; yTiles > 0; --yTiles, yMin += info.x14_tileSize, xTileStart += info.x2a_gridDimX) {\n//           xMax = xMin;\n//           int nextXTile;\n//           for (int curXTile = 0; curXTile < xTiles; curXTile = nextXTile) {\n//             if (info.x30_gridFlags[xTileStart + curXTile]) {\n//               nextXTile = curXTile + 1;\n//               int tile = xTileStart + nextXTile;\n//               while (nextXTile < xTiles && info.x30_gridFlags[tile]) {\n//                 ++nextXTile;\n//                 ++tile;\n//               }\n//\n//               size_t start = vOut.size();\n//               for (int v = 0; v < nextXTile - curXTile + 1; ++v) {\n//                 vOut.emplace_back(zeus::CVector3f(xMax, yMin, 0.f), zeus::skUp, zeus::skForward, zeus::skRight,\n//                                   zeus::skBlack);\n//                 vOut.emplace_back(zeus::CVector3f(xMax, yMin + info.x14_tileSize, 0.f), zeus::skUp, zeus::skForward,\n//                                   zeus::skRight, zeus::skBlack);\n//                 xMax += info.x14_tileSize;\n//               }\n// //              CGraphics::DrawArray(start, vOut.size() - start);\n//             } else {\n//               nextXTile = curXTile + 1;\n//               xMax += info.x14_tileSize;\n//               int tile = xTileStart + nextXTile;\n//               while (nextXTile < xTiles && !info.x30_gridFlags[tile]) {\n//                 xMax += info.x14_tileSize;\n//                 ++nextXTile;\n//                 ++tile;\n//               }\n//             }\n//           }\n//         }\n//       }\n//       break;\n//     }\n//     }\n//   } else {\n//     float curY = info.x4_localMin.y();\n//     for (int startYDiv = 1; startYDiv < info.x1_ySubdivs - 2;\n//          startYDiv += CFluidPlaneRender::numSubdivisionsInTile, curY += info.x14_tileSize)\n//       RenderStripWithRipples(curY, heights, flags, startYDiv, info, vOut, pvOut);\n//   }\n// }\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CFluidPlane.hpp",
    "content": "#pragma once\n\n#include <algorithm>\n#include <array>\n#include <cmath>\n#include <optional>\n#include <vector>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Graphics/CTexture.hpp\"\n#include \"Runtime/World/CFluidUVMotion.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CColor.hpp>\n#include <zeus/CFrustum.hpp>\n#include <zeus/CVector2f.hpp>\n\nnamespace metaforce {\nclass CFluidUVMotion;\nclass CRipple;\nclass CRippleManager;\nclass CScriptWater;\nclass CStateManager;\n\nclass CFluidPlaneRender {\npublic:\n  enum class NormalMode { None, NoNormals, Normals, NBT };\n\n  static int numTilesInHField;\n  static int numSubdivisionsInTile;\n  static int numSubdivisionsInHField;\n\n  struct SPatchInfo {\n    u8 x0_xSubdivs, x1_ySubdivs;\n    zeus::CVector2f x4_localMin, xc_globalMin;\n    float x14_tileSize;\n    float x18_rippleResolution;\n    float x1c_tileHypRadius;\n    float x20_ooTileSize;\n    float x24_ooRippleResolution;\n    u16 x28_tileX;\n    u16 x2a_gridDimX;\n    u16 x2c_gridDimY;\n    u16 x2e_tileY;\n    const bool* x30_gridFlags;\n    u8 x34_redShift;\n    u8 x35_greenShift;\n    u8 x36_blueShift;\n    NormalMode x37_normalMode;\n    float x38_wavecapIntensityScale;\n\n  public:\n    SPatchInfo(const zeus::CVector3f& localMin, const zeus::CVector3f& localMax, const zeus::CVector3f& pos,\n               float rippleResolution, float tileSize, float wavecapIntensityScale, int numSubdivisionsInHField,\n               NormalMode normalMode, int redShift, int greenShift, int blueShift, u32 tileX, u32 gridDimX,\n               u32 gridDimY, u32 tileY, const bool* gridFlags) {\n      x0_xSubdivs = std::min(s16((localMax.x() - localMin.x()) / rippleResolution + 1.f - FLT_EPSILON) + 2,\n                             numSubdivisionsInHField + 2);\n      x1_ySubdivs = std::min(s16((localMax.y() - localMin.y()) / rippleResolution + 1.f - FLT_EPSILON) + 2,\n                             numSubdivisionsInHField + 2);\n      float tileHypRadius = tileSize * tileSize * 2 * 0.25f;\n      x4_localMin = localMin.toVec2f();\n      xc_globalMin = x4_localMin + pos.toVec2f();\n      x14_tileSize = tileSize;\n      x18_rippleResolution = rippleResolution;\n      if (tileHypRadius != 0.f)\n        tileHypRadius = std::sqrt(tileHypRadius);\n      x1c_tileHypRadius = tileHypRadius;\n      x20_ooTileSize = 1.f / x14_tileSize;\n      x24_ooRippleResolution = 1.f / x18_rippleResolution;\n      x28_tileX = u16(tileX);\n      x2a_gridDimX = u16(gridDimX);\n      x2c_gridDimY = u16(gridDimY);\n      x2e_tileY = u16(tileY);\n      x30_gridFlags = gridFlags;\n      x34_redShift = u8(redShift);\n      x35_greenShift = u8(greenShift);\n      x36_blueShift = u8(blueShift);\n      x37_normalMode = normalMode;\n      x38_wavecapIntensityScale = wavecapIntensityScale;\n    }\n  };\n\n  struct SRippleInfo {\n    const CRipple& x0_ripple;\n    int x4_fromX = 0;\n    int x8_toX = 0;\n    int xc_fromY = 0;\n    int x10_toY = 0;\n    int x14_gfromX;\n    int x18_gtoX;\n    int x1c_gfromY;\n    int x20_gtoY;\n\n  public:\n    SRippleInfo(const CRipple& ripple, int fromX, int toX, int fromY, int toY)\n    : x0_ripple(ripple), x14_gfromX(fromX), x18_gtoX(toX), x1c_gfromY(fromY), x20_gtoY(toY) {}\n  };\n\n  struct SHFieldSample {\n    float height;\n    s8 nx;\n    s8 ny;\n    s8 nz;\n    u8 wavecapIntensity;\n\n    zeus::CVector3f MakeNormal() const { return zeus::CVector3f{nx / 63.f, ny / 63.f, nz / 63.f}.normalized(); }\n    zeus::CVector3f MakeBinormal() const { return zeus::CVector3f{nx / 63.f, nz / 63.f, -ny / 63.f}.normalized(); }\n    zeus::CVector3f MakeTangent() const { return zeus::CVector3f{nz / 63.f, ny / 63.f, -nx / 63.f}.normalized(); }\n    zeus::CColor MakeColor(const CFluidPlaneRender::SPatchInfo& info) const {\n      return {(wavecapIntensity >> info.x34_redShift) / 255.f, (wavecapIntensity >> info.x35_greenShift) / 255.f,\n              (wavecapIntensity >> info.x36_blueShift) / 255.f};\n    }\n  };\n};\n\nenum class EFluidType { NormalWater, PoisonWater, Lava, PhazonFluid, Four, ThickLava };\n\nclass CFluidPlane {\npublic:\n  using Flags = std::array<std::array<u8, 9>, 9>;\n  using Heights = std::array<std::array<CFluidPlaneRender::SHFieldSample, 46>, 46>;\n\nprotected:\n  CAssetId x4_texPattern1Id;\n  CAssetId x8_texPattern2Id;\n  CAssetId xc_texColorId;\n  TLockedToken<CTexture> x10_texPattern1;\n  TLockedToken<CTexture> x20_texPattern2;\n  TLockedToken<CTexture> x30_texColor;\n  float x40_alpha;\n  EFluidType x44_fluidType;\n  float x48_rippleIntensity;\n  CFluidUVMotion x4c_uvMotion;\n\n  // std::vector<CFluidPlaneShader::Vertex> m_verts;\n  // std::vector<CFluidPlaneShader::PatchVertex> m_pVerts;\n  // std::optional<CFluidPlaneShader> m_shader;\n\n  float ProjectRippleVelocity(float baseI, float velDot) const;\n  float CalculateRippleIntensity(float baseI) const;\n\n  // virtual void RenderStripWithRipples(float curY, const Heights& heights, const Flags& flags, int startYDiv,\n  //                                     const CFluidPlaneRender::SPatchInfo& info,\n  //                                     std::vector<CFluidPlaneShader::Vertex>& vOut,\n  //                                     std::vector<CFluidPlaneShader::PatchVertex>& pvOut);\n  // void RenderPatch(const CFluidPlaneRender::SPatchInfo& info, const Heights& heights, const Flags& flags,\n  //                  bool noRipples, bool flagIs1, std::vector<CFluidPlaneShader::Vertex>& vOut,\n  //                  std::vector<CFluidPlaneShader::PatchVertex>& pvOut);\n\npublic:\n  virtual ~CFluidPlane() = default;\n  CFluidPlane(CAssetId texPattern1, CAssetId texPattern2, CAssetId texColor, float alpha, EFluidType fluidType,\n              float rippleIntensity, const CFluidUVMotion& motion);\n\n  // Called by CPlayer, CMorphBall, CWeapon, CPuddleSpore, CMagdolite\n  virtual void AddRipple(float mag, TUniqueId rippler, const zeus::CVector3f& center, CScriptWater& water,\n                         CStateManager& mgr);\n\n  // Called by CAi\n  virtual void AddRipple(float intensity, TUniqueId rippler, const zeus::CVector3f& center,\n                         const zeus::CVector3f& velocity, const CScriptWater& water, CStateManager& mgr,\n                         const zeus::CVector3f& upVec);\n\n  virtual void AddRipple(const CRipple& ripple, const CScriptWater& water, CStateManager& mgr);\n\n  virtual void Render(const CStateManager& mgr, float alpha, const zeus::CAABox& aabb, const zeus::CTransform& xf,\n                      const zeus::CTransform& areaXf, bool noNormals, const zeus::CFrustum& frustum,\n                      const std::optional<CRippleManager>& rippleManager, TUniqueId waterId, const bool* gridFlags,\n                      u32 gridDimX, u32 gridDimY, const zeus::CVector3f& areaCenter) {}\n\n  float GetAlpha() const { return x40_alpha; }\n  EFluidType GetFluidType() const { return x44_fluidType; }\n  const CFluidUVMotion& GetUVMotion() const { return x4c_uvMotion; }\n  const CTexture& GetColorTexture() const { return *x30_texColor; }\n  bool HasColorTexture() const { return x30_texColor.HasReference(); }\n  const CTexture& GetTexturePattern1() const { return *x10_texPattern1; }\n  bool HasTexturePattern1() const { return x10_texPattern1.HasReference(); }\n  const CTexture& GetTexturePattern2() const { return *x20_texPattern2; }\n  bool HasTexturePattern2() const { return x20_texPattern2.HasReference(); }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CFluidPlaneCPU.cpp",
    "content": "#include \"Runtime/World/CFluidPlaneCPU.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Camera/CGameCamera.hpp\"\n#include \"Runtime/World/CFluidPlaneManager.hpp\"\n#include \"Runtime/World/CScriptWater.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nconstexpr u32 kTableSize = 2048;\n\nCFluidPlaneCPU::CTurbulence::CTurbulence(float speed, float distance, float freqMax, float freqMin, float phaseMax,\n                                         float phaseMin, float amplitudeMax, float amplitudeMin)\n: x0_speed(speed)\n, x4_distance(distance)\n, x8_freqMax(freqMax)\n, xc_freqMin(freqMin)\n, x10_phaseMax(phaseMax)\n, x14_phaseMin(phaseMin)\n, x18_amplitudeMax(amplitudeMax)\n, x1c_amplitudeMin(amplitudeMin)\n, x2c_ooTurbSpeed(1.f / x0_speed)\n, x30_ooTurbDistance(1.f / x4_distance) {\n  if (x18_amplitudeMax == 0.f && x1c_amplitudeMin == 0.f) {\n    return;\n  }\n\n  x24_tableCount = kTableSize;\n  x28_heightSelPitch = x24_tableCount;\n  x20_table.reset(new float[x24_tableCount]);\n  const float anglePitch = 2.f * M_PIF / x28_heightSelPitch;\n  const float freqConstant = 0.5f * (x8_freqMax + xc_freqMin);\n  const float freqLinear = 0.5f * (x8_freqMax - xc_freqMin);\n  const float phaseConstant = 0.5f * (x10_phaseMax + x14_phaseMin);\n  const float phaseLinear = 0.5f * (x10_phaseMax - x14_phaseMin);\n  const float amplitudeConstant = 0.5f * (x18_amplitudeMax + x1c_amplitudeMin);\n  const float amplitudeLinear = 0.5f * (x18_amplitudeMax - x1c_amplitudeMin);\n\n  float curAng = 0.f;\n  for (size_t i = 0; i < x24_tableCount; ++i, curAng += anglePitch) {\n    const float angCos = std::cos(curAng);\n    x20_table[i] = (amplitudeLinear * angCos + amplitudeConstant) *\n                   std::sin((freqLinear * angCos + freqConstant) * curAng + (phaseLinear * angCos + phaseConstant));\n  }\n\n  x34_hasTurbulence = true;\n}\n\nCFluidPlaneCPU::CFluidPlaneCPU(CAssetId texPattern1, CAssetId texPattern2, CAssetId texColor, CAssetId bumpMap,\n                               CAssetId envMap, CAssetId envBumpMap, CAssetId lightMap, float unitsPerLightmapTexel,\n                               float tileSize, u32 tileSubdivisions, EFluidType fluidType, float alpha,\n                               const zeus::CVector3f& bumpLightDir, float bumpScale, const CFluidUVMotion& mot,\n                               float turbSpeed, float turbDistance, float turbFreqMax, float turbFreqMin,\n                               float turbPhaseMax, float turbPhaseMin, float turbAmplitudeMax, float turbAmplitudeMin,\n                               float specularMin, float specularMax, float reflectionBlend, float reflectionSize,\n                               float rippleIntensity, u32 maxVertCount)\n: CFluidPlane(texPattern1, texPattern2, texColor, alpha, fluidType, rippleIntensity, mot)\n, xa0_texIdBumpMap(bumpMap)\n, xa4_texIdEnvMap(envMap)\n, xa8_texIdEnvBumpMap(envBumpMap)\n, xac_texId4(lightMap)\n, xf0_bumpLightDir(bumpLightDir)\n, xfc_bumpScale(bumpScale)\n, x100_tileSize(tileSize)\n, x104_tileSubdivisions(tileSubdivisions & ~0x1)\n, x108_rippleResolution(x100_tileSize / float(x104_tileSubdivisions))\n, x10c_specularMin(specularMin)\n, x110_specularMax(specularMax)\n, x114_reflectionBlend(reflectionBlend)\n, x118_reflectionSize(reflectionSize)\n, x11c_unitsPerLightmapTexel(unitsPerLightmapTexel)\n, x120_turbulence(turbSpeed, turbDistance, turbFreqMax, turbFreqMin, turbPhaseMax, turbPhaseMin, turbAmplitudeMax,\n                  turbAmplitudeMin)\n, m_maxVertCount(maxVertCount) {\n  if (g_ResFactory->GetResourceTypeById(xa0_texIdBumpMap) == FOURCC('TXTR'))\n    xb0_bumpMap = g_SimplePool->GetObj(SObjectTag{FOURCC('TXTR'), xa0_texIdBumpMap});\n  if (g_ResFactory->GetResourceTypeById(xa4_texIdEnvMap) == FOURCC('TXTR'))\n    xc0_envMap = g_SimplePool->GetObj(SObjectTag{FOURCC('TXTR'), xa4_texIdEnvMap});\n  if (g_ResFactory->GetResourceTypeById(xa8_texIdEnvBumpMap) == FOURCC('TXTR'))\n    xd0_envBumpMap = g_SimplePool->GetObj(SObjectTag{FOURCC('TXTR'), xa8_texIdEnvBumpMap});\n  if (g_ResFactory->GetResourceTypeById(xac_texId4) == FOURCC('TXTR'))\n    xe0_lightmap = g_SimplePool->GetObj(SObjectTag{FOURCC('TXTR'), xac_texId4});\n}\n\nvoid CFluidPlaneCPU::CreateRipple(const CRipple& ripple, CStateManager& mgr) {}\n\nvoid CFluidPlaneCPU::CalculateLightmapMatrix(const zeus::CTransform& areaXf, const zeus::CTransform& xf,\n                                             const zeus::CAABox& aabb, zeus::CMatrix4f& mtxOut) const {\n  int width = GetLightMap().GetWidth();\n  int height = GetLightMap().GetHeight();\n\n  zeus::CTransform toLocal = areaXf.getRotation().inverse();\n  zeus::CAABox areaLocalAABB = aabb.getTransformedAABox(toLocal);\n  float f26 = (areaLocalAABB.max.x() - areaLocalAABB.min.x()) / (width * x11c_unitsPerLightmapTexel);\n  float f25 = (areaLocalAABB.max.y() - areaLocalAABB.min.y()) / (height * x11c_unitsPerLightmapTexel);\n  float f24 = (1.f + std::fmod(areaLocalAABB.min.x() + xf.origin.x(), x11c_unitsPerLightmapTexel)) / width;\n  float f23 = (2.f - std::fmod(areaLocalAABB.max.x() + xf.origin.x(), x11c_unitsPerLightmapTexel)) / width;\n  float f29 = (1.f + std::fmod(areaLocalAABB.min.y() + xf.origin.y(), x11c_unitsPerLightmapTexel)) / height;\n  float f6 = (2.f - std::fmod(areaLocalAABB.max.y() + xf.origin.y(), x11c_unitsPerLightmapTexel)) / height;\n\n  float scaleX = (f26 - f24 - f23) / (areaLocalAABB.max.x() - areaLocalAABB.min.x());\n  float scaleY = -(f25 - f29 - f6) / (areaLocalAABB.max.y() - areaLocalAABB.min.y());\n  float offX = f24 + f26 * -areaLocalAABB.min.x() / (areaLocalAABB.max.x() - areaLocalAABB.min.x());\n  float offY = f25 * areaLocalAABB.min.y() / (areaLocalAABB.max.y() - areaLocalAABB.min.y()) - f6;\n  mtxOut = (zeus::CTransform(zeus::CMatrix3f(zeus::CVector3f(scaleX, scaleY, 0.f)), zeus::CVector3f(offX, offY, 0.f)) *\n            toLocal)\n               .toMatrix4f();\n}\n\nstatic bool sSineWaveInitialized = false;\nstatic CFluidPlaneCPU::SineTable sGlobalSineWave{};\nstatic void InitializeSineWave() {\n  if (sSineWaveInitialized) {\n    return;\n  }\n  for (size_t i = 0; i < sGlobalSineWave.size(); ++i) {\n    sGlobalSineWave[i] = std::sin(2.f * M_PIF * (float(i) / 256.f));\n  }\n  sSineWaveInitialized = true;\n}\n\n#define kEnableWaterBumpMaps true\n\n// CFluidPlaneShader::RenderSetupInfo CFluidPlaneCPU::RenderSetup(const CStateManager& mgr, float alpha,\n//                                                                const zeus::CTransform& xf,\n//                                                                const zeus::CTransform& areaXf, const zeus::CAABox& aabb,\n//                                                                const CScriptWater* water) {\n//   OPTICK_EVENT();\n//   CFluidPlaneShader::RenderSetupInfo out;\n//\n//   const float uvT = mgr.GetFluidPlaneManager()->GetUVT();\n//   const bool hasBumpMap = HasBumpMap() && kEnableWaterBumpMaps;\n//   bool doubleLightmapBlend = false;\n//   const bool hasEnvMap = mgr.GetCameraManager()->GetFluidCounter() == 0 && HasEnvMap();\n//   const bool hasEnvBumpMap = HasEnvBumpMap();\n//   InitializeSineWave();\n//   CGraphics::SetModelMatrix(xf);\n//\n//   if (hasBumpMap) {\n//     // Build 50% grey directional light with xf0_bumpLightDir and load into LIGHT_3\n//     // Light 3 in channel 1\n//     // Vertex colors in channel 0\n//     out.lights.resize(4);\n//     out.lights[3] = CLight::BuildDirectional(xf0_bumpLightDir, zeus::skGrey);\n//   } else {\n//     // Normal light mask in channel 1\n//     // Vertex colors in channel 0\n//     out.lights = water->GetActorLights()->BuildLightVector();\n//   }\n//\n//   int curTex = 3;\n//\n//   if (hasBumpMap) {\n//     // Load into next\n//     curTex++;\n//   }\n//\n//   if (hasEnvMap) {\n//     // Load into next\n//     curTex++;\n//   }\n//\n//   if (hasEnvBumpMap) {\n//     // Load into next\n//     curTex++;\n//   }\n//\n//   const auto fluidUVs = x4c_uvMotion.CalculateFluidTextureOffset(uvT);\n//\n//   out.texMtxs[0][0][0] = x4c_uvMotion.GetFluidLayers()[1].GetUVScale();\n//   out.texMtxs[0][1][1] = x4c_uvMotion.GetFluidLayers()[1].GetUVScale();\n//   out.texMtxs[0][3][0] = fluidUVs[1][0];\n//   out.texMtxs[0][3][1] = fluidUVs[1][1];\n//\n//   out.texMtxs[1][0][0] = x4c_uvMotion.GetFluidLayers()[2].GetUVScale();\n//   out.texMtxs[1][1][1] = x4c_uvMotion.GetFluidLayers()[2].GetUVScale();\n//   out.texMtxs[1][3][0] = fluidUVs[2][0];\n//   out.texMtxs[1][3][1] = fluidUVs[2][1];\n//\n//   out.texMtxs[2][0][0] = x4c_uvMotion.GetFluidLayers()[0].GetUVScale();\n//   out.texMtxs[2][1][1] = x4c_uvMotion.GetFluidLayers()[0].GetUVScale();\n//   out.texMtxs[2][3][0] = fluidUVs[0][0];\n//   out.texMtxs[2][3][1] = fluidUVs[0][1];\n//\n//   // Load normal mtx 0 with\n//   out.normMtx = (zeus::CTransform::Scale(xfc_bumpScale) * CGraphics::mViewMatrix.getRotation().inverse()).toMatrix4f();\n//\n//   // Setup TCGs\n//   int nextTexMtx = 3;\n//\n//   if (hasEnvBumpMap) {\n//     float pttScale;\n//     if (hasEnvMap)\n//       pttScale = 0.5f * (1.f - x118_reflectionSize);\n//     else\n//       pttScale = g_tweakGame->GetFluidEnvBumpScale() * x4c_uvMotion.GetFluidLayers()[0].GetUVScale();\n//\n//     // Load GX_TEXMTX3 with identity\n//     zeus::CMatrix4f& texMtx = out.texMtxs[nextTexMtx++];\n//     texMtx[0][0] = pttScale;\n//     texMtx[1][1] = pttScale;\n//     texMtx[3][0] = 0.5f;\n//     texMtx[3][1] = 0.5f;\n//     // Load GX_PTTEXMTX0 with scale of pttScale\n//     // Next: GX_TG_MTX2x4 GX_TG_NRM, GX_TEXMTX3, true, GX_PTTEXMTX0\n//\n//     out.indScale = 0.5f * (hasEnvMap ? x118_reflectionSize : 1.f);\n//     // Load ind mtx with scale of (indScale, -indScale)\n//     // Load envBumpMap into ind stage 0 with previous TCG\n//   }\n//\n//   if (hasEnvMap) {\n//     float scale = std::max(aabb.max.x() - aabb.min.x(), aabb.max.y() - aabb.min.y());\n//     zeus::CMatrix4f& texMtx = out.texMtxs[nextTexMtx++];\n//     texMtx[0][0] = 1.f / scale;\n//     texMtx[1][1] = 1.f / scale;\n//     zeus::CVector3f center = aabb.center();\n//     texMtx[3][0] = 0.5f + -center.x() / scale;\n//     texMtx[3][1] = 0.5f + -center.y() / scale;\n//     // Next: GX_TG_MTX2x4 GX_TG_POS, mtxNext, false, GX_PTIDENTITY\n//   }\n//\n//   if (HasLightMap()) {\n//     float lowLightBlend = 1.f;\n//     const CGameArea* area = mgr.GetWorld()->GetAreaAlways(mgr.GetNextAreaId());\n//     float lightLevel = area->GetPostConstructed()->x1128_worldLightingLevel;\n//     const CScriptWater* nextWater = water->GetNextConnectedWater(mgr);\n//     if (std::fabs(water->GetMorphFactor()) < 0.00001f || !nextWater || !nextWater->GetFluidPlane().HasLightMap()) {\n//       // Load lightmap\n//       CalculateLightmapMatrix(areaXf, xf, aabb, out.texMtxs[nextTexMtx++]);\n//       // Next: GX_TG_MTX2x4 GX_TG_POS, mtxNext, false, GX_PTIDENTITY\n//     } else if (nextWater && nextWater->GetFluidPlane().HasLightMap()) {\n//       if (std::fabs(water->GetMorphFactor() - 1.f) < 0.00001f) {\n//         // Load lightmap\n//         CalculateLightmapMatrix(areaXf, xf, aabb, out.texMtxs[nextTexMtx++]);\n//         // Next: GX_TG_MTX2x4 GX_TG_POS, mtxNext, false, GX_PTIDENTITY\n//       } else {\n//         // Load lightmap\n//         CalculateLightmapMatrix(areaXf, xf, aabb, out.texMtxs[nextTexMtx++]);\n//         // Next: GX_TG_MTX2x4 GX_TG_POS, mtxNext, false, GX_PTIDENTITY\n//         // Load lightmap\n//         CalculateLightmapMatrix(areaXf, xf, aabb, out.texMtxs[nextTexMtx++]);\n//         // Next: GX_TG_MTX2x4 GX_TG_POS, mtxNext, false, GX_PTIDENTITY\n//\n//         float lum = lightLevel * water->GetMorphFactor();\n//         out.kColors[3] = zeus::CColor(lum, 1.f);\n//         lowLightBlend = (1.f - water->GetMorphFactor()) / (1.f - lum);\n//         doubleLightmapBlend = true;\n//       }\n//     }\n//\n//     out.kColors[2] = zeus::CColor(lowLightBlend * lightLevel, 1.f);\n//   }\n//\n//   float waterPlaneOrthoDot =\n//       xf.transposeRotate(zeus::skUp).dot(CGraphics::mViewMatrix.inverse().transposeRotate(zeus::skForward));\n//   if (waterPlaneOrthoDot < 0.f)\n//     waterPlaneOrthoDot = -waterPlaneOrthoDot;\n//\n//   out.kColors[0] =\n//       zeus::CColor((1.f - waterPlaneOrthoDot) * (x110_specularMax - x10c_specularMin) + x10c_specularMin, alpha);\n//   out.kColors[1] = zeus::CColor(x114_reflectionBlend, 1.f);\n//\n//   if (!m_shader || m_cachedDoubleLightmapBlend != doubleLightmapBlend ||\n//       m_cachedAdditive != (mgr.GetThermalDrawFlag() == EThermalDrawFlag::Hot)) {\n//     m_cachedDoubleLightmapBlend = doubleLightmapBlend;\n//     m_cachedAdditive = mgr.GetThermalDrawFlag() == EThermalDrawFlag::Hot;\n// //    m_shader.emplace(x44_fluidType, x10_texPattern1, x20_texPattern2, x30_texColor, xb0_bumpMap, xc0_envMap,\n// //                     xd0_envBumpMap, xe0_lightmap,\n// //                     m_tessellation ? CFluidPlaneManager::RippleMapTex : aurora::gfx::TextureHandle{},\n// //                     m_cachedDoubleLightmapBlend, m_cachedAdditive, m_maxVertCount);\n//   }\n//\n//   return out;\n// }\n\nint CFluidPlaneRender::numTilesInHField;\nint CFluidPlaneRender::numSubdivisionsInTile;\nint CFluidPlaneRender::numSubdivisionsInHField;\n\nbool CFluidPlaneCPU::PrepareRipple(const CRipple& ripple, const CFluidPlaneRender::SPatchInfo& info,\n                                   CFluidPlaneRender::SRippleInfo& rippleOut) {\n  auto lifeIdx = int((1.f - (ripple.GetTimeFalloff() - ripple.GetTime()) / ripple.GetTimeFalloff()) * 64.f);\n  float dist = CFluidPlaneManager::RippleMaxs[lifeIdx] * (ripple.GetDistanceFalloff() / 256.f);\n  dist *= dist;\n  if (dist != 0)\n    dist = std::sqrt(dist);\n  dist = info.x24_ooRippleResolution * dist + 1.f;\n  float centerX = info.x24_ooRippleResolution * (ripple.GetCenter().x() - info.xc_globalMin.x());\n  float centerY = info.x24_ooRippleResolution * (ripple.GetCenter().y() - info.xc_globalMin.y());\n  int fromX = int(centerX - dist) - 1;\n  int toX = int(centerX + dist) + 1;\n  int fromY = int(centerY - dist) - 1;\n  int toY = int(centerY + dist) + 1;\n  rippleOut.x4_fromX = std::max(0, fromX);\n  rippleOut.x8_toX = std::min(int(info.x0_xSubdivs), toX);\n  rippleOut.xc_fromY = std::max(0, fromY);\n  rippleOut.x10_toY = std::min(int(info.x1_ySubdivs), toY);\n  rippleOut.x14_gfromX = std::max(rippleOut.x14_gfromX, fromX);\n  rippleOut.x18_gtoX = std::min(rippleOut.x18_gtoX, toX);\n  rippleOut.x1c_gfromY = std::max(rippleOut.x1c_gfromY, fromY);\n  rippleOut.x20_gtoY = std::min(rippleOut.x20_gtoY, toY);\n  return !(rippleOut.x14_gfromX > rippleOut.x18_gtoX || rippleOut.x1c_gfromY > rippleOut.x20_gtoY);\n}\n\nvoid CFluidPlaneCPU::ApplyTurbulence(float t, Heights& heights, const Flags& flags, const SineTable& sineWave,\n                                     const CFluidPlaneRender::SPatchInfo& info,\n                                     const zeus::CVector3f& areaCenter) const {\n  if (!HasTurbulence()) {\n    memset(&heights, 0, sizeof(heights));\n    return;\n  }\n\n  float scaledT = t * GetOOTurbulenceSpeed();\n  float curY = info.x4_localMin.y() - info.x18_rippleResolution - areaCenter.y();\n  int xDivs = (info.x0_xSubdivs + CFluidPlaneRender::numSubdivisionsInTile - 4) /\n                  CFluidPlaneRender::numSubdivisionsInTile * CFluidPlaneRender::numSubdivisionsInTile +\n              2;\n  int yDivs = (info.x1_ySubdivs + CFluidPlaneRender::numSubdivisionsInTile - 4) /\n                  CFluidPlaneRender::numSubdivisionsInTile * CFluidPlaneRender::numSubdivisionsInTile +\n              2;\n  for (int i = 0; i <= yDivs; ++i) {\n    float curYSq = curY * curY;\n    float curX = info.x4_localMin.x() - info.x18_rippleResolution - areaCenter.x();\n    for (int j = 0; j <= xDivs; ++j) {\n      float distFac = curX * curX + curYSq;\n      if (distFac != 0.f)\n        distFac = std::sqrt(distFac);\n      heights[i][j].height = GetTurbulenceHeight(GetOOTurbulenceDistance() * distFac + scaledT);\n      curX += info.x18_rippleResolution;\n    }\n    curY += info.x18_rippleResolution;\n  }\n}\n\nvoid CFluidPlaneCPU::ApplyRipple(const CFluidPlaneRender::SRippleInfo& rippleInfo, Heights& heights, Flags& flags,\n                                 const SineTable& sineWave, const CFluidPlaneRender::SPatchInfo& info) const {\n  float lookupT = 256.f *\n                  (1.f - rippleInfo.x0_ripple.GetTime() * rippleInfo.x0_ripple.GetOOTimeFalloff() *\n                             rippleInfo.x0_ripple.GetOOTimeFalloff()) *\n                  rippleInfo.x0_ripple.GetFrequency();\n  auto lifeIdx = int(64.f * rippleInfo.x0_ripple.GetTime() * rippleInfo.x0_ripple.GetOOTimeFalloff());\n  float distMul = rippleInfo.x0_ripple.GetDistanceFalloff() / 255.f;\n  float minDist = CFluidPlaneManager::RippleMins[lifeIdx] * distMul;\n  float minDistSq = minDist * minDist;\n  if (minDistSq != 0.f)\n    minDist = std::sqrt(minDistSq);\n  float maxDist = CFluidPlaneManager::RippleMaxs[lifeIdx] * distMul;\n  float maxDistSq = maxDist * maxDist;\n  if (maxDistSq != 0.f)\n    maxDist = std::sqrt(maxDistSq);\n  int fromY =\n      (rippleInfo.x1c_gfromY + CFluidPlaneRender::numSubdivisionsInTile - 1) / CFluidPlaneRender::numSubdivisionsInTile;\n  int fromX =\n      (rippleInfo.x14_gfromX + CFluidPlaneRender::numSubdivisionsInTile - 1) / CFluidPlaneRender::numSubdivisionsInTile;\n  int toY =\n      (rippleInfo.x20_gtoY + CFluidPlaneRender::numSubdivisionsInTile - 1) / CFluidPlaneRender::numSubdivisionsInTile;\n  int toX =\n      (rippleInfo.x18_gtoX + CFluidPlaneRender::numSubdivisionsInTile - 1) / CFluidPlaneRender::numSubdivisionsInTile;\n\n  float curY = rippleInfo.x0_ripple.GetCenter().y() - info.xc_globalMin.y() -\n               (0.5f * info.x14_tileSize + (fromY - 1) * info.x14_tileSize);\n  int curGridY = info.x2a_gridDimX * (info.x2e_tileY + fromY - 1);\n  int startGridX = (info.x28_tileX + fromX - 1);\n  int gridCells = info.x2a_gridDimX * info.x2c_gridDimY;\n  float distFalloff = 64.f * rippleInfo.x0_ripple.GetOODistanceFalloff();\n  int curYDiv = rippleInfo.xc_fromY;\n\n  for (int i = fromY; i <= toY; ++i, curY -= info.x14_tileSize) {\n    int nextYDiv = (i + 1) * CFluidPlaneRender::numSubdivisionsInTile;\n    float curYSq = curY * curY;\n    int curGridX = startGridX;\n    int curXDiv = rippleInfo.x4_fromX;\n    float curX = rippleInfo.x0_ripple.GetCenter().x() - info.xc_globalMin.x() -\n                 (0.5f * info.x14_tileSize + (fromX - 1) * info.x14_tileSize);\n    for (int j = fromX; j <= toX; ++j, curX -= info.x14_tileSize, ++curGridX) {\n      float dist = curX * curX + curYSq;\n      if (dist != 0.f)\n        dist = std::sqrt(dist);\n      if (maxDist < dist - info.x1c_tileHypRadius || minDist > dist + info.x1c_tileHypRadius)\n        continue;\n\n      bool addedRipple = false;\n      int nextXDiv = (j + 1) * CFluidPlaneRender::numSubdivisionsInTile;\n      float curXMod =\n          (rippleInfo.x0_ripple.GetCenter().x() - info.xc_globalMin.x()) - info.x18_rippleResolution * curXDiv;\n      float curYMod =\n          (rippleInfo.x0_ripple.GetCenter().y() - info.xc_globalMin.y()) - info.x18_rippleResolution * curYDiv;\n\n      if (!info.x30_gridFlags || (info.x30_gridFlags && curGridY >= 0 && curGridY < gridCells && curGridX >= 0 &&\n                                  curGridX < info.x2a_gridDimX && info.x30_gridFlags[curGridX + curGridY])) {\n        for (int k = curYDiv; k <= std::min(rippleInfo.x10_toY, nextYDiv - 1);\n             ++k, curYMod -= info.x18_rippleResolution) {\n          float tmpXMod = curXMod;\n          float curYModSq = curYMod * curYMod;\n          for (int l = curXDiv; l <= std::min(rippleInfo.x8_toX, nextXDiv - 1);\n               ++l, tmpXMod -= info.x18_rippleResolution) {\n            float divDistSq = tmpXMod * tmpXMod + curYModSq;\n            if (divDistSq < minDistSq || divDistSq > maxDistSq)\n              continue;\n\n            if (m_tessellation) {\n              /* This will be evaluated in tessellation shader instead */\n              addedRipple = true;\n              break;\n            }\n\n            float divDist = (divDistSq != 0.f) ? std::sqrt(divDistSq) : 0.f;\n            if (u8 rippleV = CFluidPlaneManager::RippleValues[lifeIdx][int(divDist * distFalloff)]) {\n              heights[k][l].height +=\n                  rippleV * rippleInfo.x0_ripple.GetLookupAmplitude() *\n                  sineWave[size_t(divDist * rippleInfo.x0_ripple.GetLookupPhase() + lookupT) & 0xff];\n            } else {\n              heights[k][l].height += 0.f;\n            }\n            addedRipple = true;\n          }\n        }\n\n        if (addedRipple)\n          flags[i][j] = 0x1f;\n      } else {\n        int yMin = nextYDiv - 1;\n        int yMax = nextYDiv - CFluidPlaneRender::numSubdivisionsInTile + 1;\n        int xMin = nextXDiv - 1;\n        int xMax = nextXDiv - CFluidPlaneRender::numSubdivisionsInTile + 1;\n\n        if (curGridX >= 0.f && curGridX < info.x2a_gridDimX && curGridY - info.x2a_gridDimX >= 0 &&\n            !info.x30_gridFlags[curGridX + curGridY - info.x2a_gridDimX])\n          yMax -= 2;\n\n        if (curGridX >= 0.f && curGridX < info.x2a_gridDimX && curGridY + info.x2a_gridDimX < gridCells &&\n            !info.x30_gridFlags[curGridX + info.x2a_gridDimX])\n          yMin += 2;\n\n        if (curGridY >= 0 && curGridY < info.x2c_gridDimY && curGridX > 0 && !info.x30_gridFlags[curGridX - 1])\n          xMax -= 2;\n\n        if (curGridY >= 0 && curGridY < info.x2c_gridDimY && curGridX + 1 < info.x2a_gridDimX &&\n            !info.x30_gridFlags[curGridX + 1])\n          xMin += 2;\n\n        for (int k = curYDiv; k <= std::min(rippleInfo.x10_toY, nextYDiv - 1);\n             ++k, curYMod -= info.x18_rippleResolution) {\n          float tmpXMod = curXMod;\n          float curYModSq = curYMod * curYMod;\n          for (int l = curXDiv; l <= std::min(rippleInfo.x8_toX, nextXDiv - 1);\n               ++l, tmpXMod -= info.x18_rippleResolution) {\n            if (k <= yMax || k >= yMin || l <= xMax || l >= xMin) {\n              float divDistSq = tmpXMod * tmpXMod + curYModSq;\n              if (divDistSq < minDistSq || divDistSq > maxDistSq)\n                continue;\n\n              if (m_tessellation) {\n                /* This will be evaluated in tessellation shader instead */\n                addedRipple = true;\n                break;\n              }\n\n              float divDist = (divDistSq != 0.f) ? std::sqrt(divDistSq) : 0.f;\n              if (u8 rippleV = CFluidPlaneManager::RippleValues[lifeIdx][int(divDist * distFalloff)]) {\n                heights[k][l].height +=\n                    rippleV * rippleInfo.x0_ripple.GetLookupAmplitude() *\n                    sineWave[size_t(divDist * rippleInfo.x0_ripple.GetLookupPhase() + lookupT) & 0xff];\n              } else {\n                heights[k][l].height += 0.f;\n              }\n              addedRipple = true;\n            }\n          }\n\n          if (m_tessellation && addedRipple)\n            break;\n        }\n\n        if (addedRipple)\n          flags[i][j] = 0xf;\n      }\n      curXDiv = nextXDiv;\n    }\n\n    curYDiv = nextYDiv;\n    curGridY += info.x2a_gridDimX;\n  }\n}\n\nvoid CFluidPlaneCPU::ApplyRipples(const rstl::reserved_vector<CFluidPlaneRender::SRippleInfo, 32>& rippleInfos,\n                                  Heights& heights, Flags& flags, const SineTable& sineWave,\n                                  const CFluidPlaneRender::SPatchInfo& info) const {\n  for (const CFluidPlaneRender::SRippleInfo& rippleInfo : rippleInfos)\n    ApplyRipple(rippleInfo, heights, flags, sineWave, info);\n  for (int i = 0; i < CFluidPlaneRender::numTilesInHField; ++i)\n    flags[0][i + 1] |= 1;\n  for (int i = 0; i < CFluidPlaneRender::numTilesInHField; ++i)\n    flags[i + 1][0] |= 8;\n  for (int i = 0; i < CFluidPlaneRender::numTilesInHField; ++i)\n    flags[i + 1][CFluidPlaneRender::numTilesInHField + 1] |= 4;\n  for (int i = 0; i < CFluidPlaneRender::numTilesInHField; ++i)\n    flags[CFluidPlaneRender::numTilesInHField + 1][i + 1] |= 2;\n}\n\nvoid CFluidPlaneCPU::UpdatePatchNoNormals(Heights& heights, const Flags& flags,\n                                          const CFluidPlaneRender::SPatchInfo& info) {\n  for (int i = 1; i <= (info.x1_ySubdivs + CFluidPlaneRender::numSubdivisionsInTile - 2) /\n                           CFluidPlaneRender::numSubdivisionsInTile;\n       ++i) {\n    int r10 = i * CFluidPlaneRender::numSubdivisionsInTile + 1;\n    int r9 = std::max(0, r10 - CFluidPlaneRender::numSubdivisionsInTile);\n    int x24 = std::min(r10, info.x1_ySubdivs + 1);\n    for (int j = 1; j <= (info.x0_xSubdivs + CFluidPlaneRender::numSubdivisionsInTile - 2) /\n                             CFluidPlaneRender::numSubdivisionsInTile;\n         ++j) {\n      int r29 = j * CFluidPlaneRender::numSubdivisionsInTile + 1;\n      int r11 = std::max(0, r29 - CFluidPlaneRender::numSubdivisionsInTile);\n      int x28 = std::min(r29, info.x0_xSubdivs + 1);\n      if ((flags[i][j] & 0x1f) == 0x1f) {\n        for (int k = r9; k < x24; ++k) {\n          for (int l = r11; l < x28; ++l) {\n            CFluidPlaneRender::SHFieldSample& sample = heights[k][l];\n            if (sample.height > 0.f)\n              sample.wavecapIntensity = u8(std::min(255, int(info.x38_wavecapIntensityScale * sample.height)));\n            else\n              sample.wavecapIntensity = 0;\n          }\n        }\n      } else {\n        if (i > 0 && i < CFluidPlaneRender::numTilesInHField + 1 && j > 0 &&\n            j < CFluidPlaneRender::numTilesInHField + 1) {\n          int halfSubdivs = CFluidPlaneRender::numSubdivisionsInTile / 2;\n          CFluidPlaneRender::SHFieldSample& sample = heights[halfSubdivs + r9][halfSubdivs + r11];\n          if (sample.height > 0.f)\n            sample.wavecapIntensity = u8(std::min(255, int(info.x38_wavecapIntensityScale * sample.height)));\n          else\n            sample.wavecapIntensity = 0;\n        }\n\n        if (i != 0) {\n          for (int l = r11; l < x28; ++l) {\n            CFluidPlaneRender::SHFieldSample& sample = heights[r9][l];\n            if (sample.height > 0.f)\n              sample.wavecapIntensity = u8(std::min(255, int(info.x38_wavecapIntensityScale * sample.height)));\n            else\n              sample.wavecapIntensity = 0;\n          }\n        }\n\n        if (j != 0) {\n          for (int k = r9 + 1; k < x24; ++k) {\n            CFluidPlaneRender::SHFieldSample& sample = heights[k][r11];\n            if (sample.height > 0.f)\n              sample.wavecapIntensity = u8(std::min(255, int(info.x38_wavecapIntensityScale * sample.height)));\n            else\n              sample.wavecapIntensity = 0;\n          }\n        }\n      }\n    }\n  }\n}\n\nvoid CFluidPlaneCPU::UpdatePatchWithNormals(Heights& heights, const Flags& flags,\n                                            const CFluidPlaneRender::SPatchInfo& info) {\n  float normalScale = -(2.f * info.x18_rippleResolution);\n  float nz = 0.25f * 2.f * info.x18_rippleResolution;\n  int curGridY = info.x2e_tileY * info.x2a_gridDimX - 1 + info.x28_tileX;\n  for (int i = 1; i <= (info.x1_ySubdivs + CFluidPlaneRender::numSubdivisionsInTile - 2) /\n                           CFluidPlaneRender::numSubdivisionsInTile;\n       ++i, curGridY += info.x2a_gridDimX) {\n    int r11 = i * CFluidPlaneRender::numSubdivisionsInTile + 1;\n    int r9 = std::max(0, r11 - CFluidPlaneRender::numSubdivisionsInTile);\n    int x38 = std::min(r11, info.x1_ySubdivs + 1);\n    for (int j = 1; j <= (info.x0_xSubdivs + CFluidPlaneRender::numSubdivisionsInTile - 2) /\n                             CFluidPlaneRender::numSubdivisionsInTile;\n         ++j) {\n      int r12 = j * CFluidPlaneRender::numSubdivisionsInTile + 1;\n      int x3c = std::min(r12, info.x0_xSubdivs + 1);\n      r12 -= CFluidPlaneRender::numSubdivisionsInTile;\n      if ((flags[i][j] & 0x1f) == 0x1f) {\n        for (int k = r9; k < x38; ++k) {\n          for (int l = r12; l < x3c; ++l) {\n            CFluidPlaneRender::SHFieldSample& sample = heights[k][l];\n            CFluidPlaneRender::SHFieldSample& up = heights[k + 1][l];\n            CFluidPlaneRender::SHFieldSample& down = heights[k - 1][l];\n            CFluidPlaneRender::SHFieldSample& right = heights[k][l + 1];\n            CFluidPlaneRender::SHFieldSample& left = heights[k][l - 1];\n            float nx = (right.height - left.height) * normalScale;\n            float ny = (up.height - down.height) * normalScale;\n            float normalizer = ny * ny + nx * nx + nz * nz;\n            if (normalizer != 0.f)\n              normalizer = std::sqrt(normalizer);\n            normalizer = 63.f / normalizer;\n            sample.nx = s8(nx * normalizer);\n            sample.ny = s8(ny * normalizer);\n            sample.nz = s8(nz * normalizer);\n            if (sample.height > 0.f)\n              sample.wavecapIntensity = u8(std::min(255, int(info.x38_wavecapIntensityScale * sample.height)));\n            else\n              sample.wavecapIntensity = 0;\n          }\n        }\n      } else {\n        if (!info.x30_gridFlags || info.x30_gridFlags[curGridY + j]) {\n          if (i > 0 && i < CFluidPlaneRender::numTilesInHField + 1 && j > 0 &&\n              j < CFluidPlaneRender::numTilesInHField + 1) {\n            int halfSubdivs = CFluidPlaneRender::numSubdivisionsInTile / 2;\n            int k = halfSubdivs + r9;\n            int l = halfSubdivs + r12;\n            CFluidPlaneRender::SHFieldSample& sample = heights[k][l];\n            CFluidPlaneRender::SHFieldSample& up = heights[k + 1][l];\n            CFluidPlaneRender::SHFieldSample& down = heights[k - 1][l];\n            CFluidPlaneRender::SHFieldSample& right = heights[k][l + 1];\n            CFluidPlaneRender::SHFieldSample& left = heights[k][l - 1];\n            float nx = (right.height - left.height) * normalScale;\n            float ny = (up.height - down.height) * normalScale;\n            float normalizer = ny * ny + nx * nx + nz * nz;\n            if (normalizer != 0.f)\n              normalizer = std::sqrt(normalizer);\n            normalizer = 63.f / normalizer;\n            sample.nx = s8(nx * normalizer);\n            sample.ny = s8(ny * normalizer);\n            sample.nz = s8(nz * normalizer);\n            if (sample.height > 0.f)\n              sample.wavecapIntensity = u8(std::min(255, int(info.x38_wavecapIntensityScale * sample.height)));\n            else\n              sample.wavecapIntensity = 0;\n          }\n        }\n\n        if (j != 0 && i != 0) {\n          if ((flags[i][j] & 2) != 0 || (flags[i - 1][j] & 1) != 0 || (flags[i][j] & 4) != 0 ||\n              (flags[i][j - 1] & 8) != 0) {\n            for (int l = r12; l < x3c; ++l) {\n              CFluidPlaneRender::SHFieldSample& sample = heights[r9][l];\n              CFluidPlaneRender::SHFieldSample& up = heights[r9 + 1][l];\n              CFluidPlaneRender::SHFieldSample& down = heights[r9 - 1][l];\n              CFluidPlaneRender::SHFieldSample& right = heights[r9][l + 1];\n              CFluidPlaneRender::SHFieldSample& left = heights[r9][l - 1];\n              float nx = (right.height - left.height) * normalScale;\n              float ny = (up.height - down.height) * normalScale;\n              float normalizer = ny * ny + nx * nx + nz * nz;\n              if (normalizer != 0.f)\n                normalizer = std::sqrt(normalizer);\n              normalizer = 63.f / normalizer;\n              sample.nx = s8(nx * normalizer);\n              sample.ny = s8(ny * normalizer);\n              sample.nz = s8(nz * normalizer);\n              if (sample.height > 0.f)\n                sample.wavecapIntensity = u8(std::min(255, int(info.x38_wavecapIntensityScale * sample.height)));\n              else\n                sample.wavecapIntensity = 0;\n            }\n\n            for (int k = r9; k < x38; ++k) {\n              CFluidPlaneRender::SHFieldSample& sample = heights[k][r12];\n              CFluidPlaneRender::SHFieldSample& up = heights[k + 1][r12];\n              CFluidPlaneRender::SHFieldSample& down = heights[k - 1][r12];\n              CFluidPlaneRender::SHFieldSample& right = heights[k][r12 + 1];\n              CFluidPlaneRender::SHFieldSample& left = heights[k][r12 - 1];\n              float nx = (right.height - left.height) * normalScale;\n              float ny = (up.height - down.height) * normalScale;\n              float normalizer = ny * ny + nx * nx + nz * nz;\n              if (normalizer != 0.f)\n                normalizer = std::sqrt(normalizer);\n              normalizer = 63.f / normalizer;\n              sample.nx = s8(nx * normalizer);\n              sample.ny = s8(ny * normalizer);\n              sample.nz = s8(nz * normalizer);\n              if (sample.height > 0.f)\n                sample.wavecapIntensity = u8(std::min(255, int(info.x38_wavecapIntensityScale * sample.height)));\n              else\n                sample.wavecapIntensity = 0;\n            }\n          } else {\n            CFluidPlaneRender::SHFieldSample& sample = heights[r9][r12];\n            CFluidPlaneRender::SHFieldSample& up = heights[r9 + 1][r12];\n            CFluidPlaneRender::SHFieldSample& down = heights[r9 - 1][r12];\n            CFluidPlaneRender::SHFieldSample& right = heights[r9][r12 + 1];\n            CFluidPlaneRender::SHFieldSample& left = heights[r9][r12 - 1];\n            float nx = (right.height - left.height) * normalScale;\n            float ny = (up.height - down.height) * normalScale;\n            float normalizer = ny * ny + nx * nx + nz * nz;\n            if (normalizer != 0.f)\n              normalizer = std::sqrt(normalizer);\n            normalizer = 63.f / normalizer;\n            sample.nx = s8(nx * normalizer);\n            sample.ny = s8(ny * normalizer);\n            sample.nz = s8(nz * normalizer);\n            if (sample.height > 0.f)\n              sample.wavecapIntensity = u8(std::min(255, int(info.x38_wavecapIntensityScale * sample.height)));\n            else\n              sample.wavecapIntensity = 0;\n          }\n        }\n      }\n    }\n  }\n}\n\nbool CFluidPlaneCPU::UpdatePatch(float time, const CFluidPlaneRender::SPatchInfo& info, Heights& heights, Flags& flags,\n                                 const zeus::CVector3f& areaCenter, const std::optional<CRippleManager>& rippleManager,\n                                 int fromX, int toX, int fromY, int toY) const {\n  rstl::reserved_vector<CFluidPlaneRender::SRippleInfo, 32> rippleInfos;\n  if (rippleManager) {\n    for (const CRipple& ripple : rippleManager->GetRipples()) {\n      if (ripple.GetTime() >= ripple.GetTimeFalloff())\n        continue;\n      CFluidPlaneRender::SRippleInfo rippleInfo(ripple, fromX, toX, fromY, toY);\n      if (PrepareRipple(ripple, info, rippleInfo))\n        rippleInfos.push_back(rippleInfo);\n    }\n  }\n\n  if (rippleInfos.empty())\n    return true;\n\n  ApplyTurbulence(time, heights, flags, sGlobalSineWave, info, areaCenter);\n  ApplyRipples(rippleInfos, heights, flags, sGlobalSineWave, info);\n\n  /* No further action necessary if using tessellation shaders */\n  if (m_tessellation)\n    return false;\n\n  if (info.x37_normalMode == CFluidPlaneRender::NormalMode::NoNormals)\n    UpdatePatchNoNormals(heights, flags, info);\n  else\n    UpdatePatchWithNormals(heights, flags, info);\n\n  return false;\n}\n\n// Used to be part of locked cache\n// These are too big for stack allocation\nstatic CFluidPlane::Heights lc_heights{};\nstatic CFluidPlane::Flags lc_flags{};\n\nvoid CFluidPlaneCPU::Render(const CStateManager& mgr, float alpha, const zeus::CAABox& aabb, const zeus::CTransform& xf,\n                            const zeus::CTransform& areaXf, bool noNormals, const zeus::CFrustum& frustum,\n                            const std::optional<CRippleManager>& rippleManager, TUniqueId waterId,\n                            const bool* gridFlags, u32 gridDimX, u32 gridDimY, const zeus::CVector3f& areaCenter) {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CFluidPlaneCPU::Render\", zeus::skCyan);\n  TCastToConstPtr<CScriptWater> water = mgr.GetObjectById(waterId);\n  // CFluidPlaneShader::RenderSetupInfo setupInfo = RenderSetup(mgr, alpha, xf, areaXf, aabb, water.GetPtr());\n\n  // if (!m_shader->isReady())\n  //   return;\n\n  CFluidPlaneRender::NormalMode normalMode;\n  if (xb0_bumpMap && kEnableWaterBumpMaps)\n    normalMode = CFluidPlaneRender::NormalMode::NBT;\n  else if (!noNormals)\n    normalMode = CFluidPlaneRender::NormalMode::Normals;\n  else\n    normalMode = CFluidPlaneRender::NormalMode::NoNormals;\n\n  // Set Position and color format\n\n  switch (normalMode) {\n  case CFluidPlaneRender::NormalMode::NBT:\n    // Set NBT format\n    break;\n  case CFluidPlaneRender::NormalMode::Normals:\n    // Set Normal format\n    break;\n  default:\n    break;\n  }\n\n  float rippleResolutionRecip = 1.f / x108_rippleResolution;\n  CFluidPlaneRender::numSubdivisionsInTile = x104_tileSubdivisions;\n  CFluidPlaneRender::numTilesInHField = std::min(7, 42 / CFluidPlaneRender::numSubdivisionsInTile);\n  CFluidPlaneRender::numSubdivisionsInHField =\n      CFluidPlaneRender::numTilesInHField * CFluidPlaneRender::numSubdivisionsInTile;\n\n  zeus::CVector2f ripplePitch(x108_rippleResolution * CFluidPlaneRender::numSubdivisionsInHField);\n\n  // Amount to shift intensity values right (for added wavecap color)\n  int redShift = 0;\n  int greenShift = 0;\n  int blueShift = 0;\n  float wavecapIntensityScale = g_tweakGame->GetWavecapIntensityNormal();\n  switch (x44_fluidType) {\n  case EFluidType::PoisonWater:\n    wavecapIntensityScale = g_tweakGame->GetWavecapIntensityPoison();\n    redShift = 1;\n    blueShift = 1;\n    break;\n  case EFluidType::Lava:\n  case EFluidType::ThickLava:\n    wavecapIntensityScale = g_tweakGame->GetWavecapIntensityLava();\n    blueShift = 8;\n    greenShift = 8;\n    break;\n  default:\n    break;\n  }\n\n  if (water) {\n    float cameraPenetration = mgr.GetCameraManager()->GetCurrentCamera(mgr)->GetTranslation().dot(zeus::skUp) -\n                              water->GetTriggerBoundsWR().max.z();\n    wavecapIntensityScale *= (cameraPenetration >= 0.5f || cameraPenetration < 0.f) ? 1.f : 2.f * cameraPenetration;\n  }\n\n  u32 patchDimX = (water && water->GetPatchDimensionX()) ? water->GetPatchDimensionX() : 128;\n  u32 patchDimY = (water && water->GetPatchDimensionY()) ? water->GetPatchDimensionY() : 128;\n\n  // m_verts.clear();\n  // m_pVerts.clear();\n  if (m_tessellation) {\n    /* Additional uniform data for tessellation evaluation shader */\n    zeus::CColor colorMul;\n    colorMul.r() = wavecapIntensityScale / 255.f / float(1 << redShift);\n    colorMul.g() = wavecapIntensityScale / 255.f / float(1 << greenShift);\n    colorMul.b() = wavecapIntensityScale / 255.f / float(1 << blueShift);\n    // m_shader->prepareDraw(setupInfo, xf.origin, *rippleManager, colorMul, x108_rippleResolution / 4.f);\n  } else {\n    // m_shader->prepareDraw(setupInfo);\n  }\n\n  u32 tileY = 0;\n  float curY = aabb.min.y();\n  for (int i = 0; curY < aabb.max.y() && i < patchDimY; ++i) {\n    u32 tileX = 0;\n    float curX = aabb.min.x();\n    float _remDivsY = (aabb.max.y() - curY) * rippleResolutionRecip;\n    for (int j = 0; curX < aabb.max.x() && j < patchDimX; ++j) {\n      if (u8 renderFlags = water->GetPatchRenderFlags(j, i)) {\n        s16 remDivsX = std::min(s16((aabb.max.x() - curX) * rippleResolutionRecip),\n                                s16(CFluidPlaneRender::numSubdivisionsInHField));\n        s16 remDivsY = std::min(s16(_remDivsY), s16(CFluidPlaneRender::numSubdivisionsInHField));\n        zeus::CVector3f localMax(x108_rippleResolution * remDivsX + curX, x108_rippleResolution * remDivsY + curY,\n                                 aabb.max.z());\n        zeus::CVector3f localMin(curX, curY, aabb.min.z());\n        zeus::CAABox testaabb(localMin + xf.origin, localMax + xf.origin);\n        if (frustum.aabbFrustumTest(testaabb)) {\n          CFluidPlaneRender::SPatchInfo info(localMin, localMax, xf.origin, x108_rippleResolution, x100_tileSize,\n                                             wavecapIntensityScale, CFluidPlaneRender::numSubdivisionsInHField,\n                                             normalMode, redShift, greenShift, blueShift, tileX, gridDimX, gridDimY,\n                                             tileY, gridFlags);\n\n          int fromX = tileX != 0 ? (2 - CFluidPlaneRender::numSubdivisionsInTile) : 0;\n          int toX;\n          if (tileX != gridDimX - 1)\n            toX = info.x0_xSubdivs + (CFluidPlaneRender::numSubdivisionsInTile - 2);\n          else\n            toX = info.x0_xSubdivs;\n\n          int fromY = tileY != 0 ? (2 - CFluidPlaneRender::numSubdivisionsInTile) : 0;\n          int toY;\n          if (tileY != gridDimY - 1)\n            toY = info.x1_ySubdivs + (CFluidPlaneRender::numSubdivisionsInTile - 2);\n          else\n            toY = info.x1_ySubdivs;\n\n          bool noRipples = UpdatePatch(mgr.GetFluidPlaneManager()->GetUVT(), info, lc_heights, lc_flags, areaCenter,\n                                       rippleManager, fromX, toX, fromY, toY);\n          // RenderPatch(info, lc_heights, lc_flags, noRipples, renderFlags == 1, m_verts, m_pVerts);\n        }\n      }\n      curX += ripplePitch.x();\n      tileX += CFluidPlaneRender::numTilesInHField;\n    }\n    curY += ripplePitch.y();\n    tileY += CFluidPlaneRender::numTilesInHField;\n  }\n\n  // m_shader->loadVerts(m_verts, m_pVerts);\n  // m_shader->doneDrawing();\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CFluidPlaneCPU.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/World/CFluidPlane.hpp\"\n#include \"Runtime/World/CRipple.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CFluidUVMotion;\n\nclass CFluidPlaneCPU : public CFluidPlane {\npublic:\n  using SineTable = std::array<float, 256>;\n\nprotected:\n  class CTurbulence {\n    float x0_speed;\n    float x4_distance;\n    float x8_freqMax;\n    float xc_freqMin;\n    float x10_phaseMax;\n    float x14_phaseMin;\n    float x18_amplitudeMax;\n    float x1c_amplitudeMin;\n    std::unique_ptr<float[]> x20_table; // x140\n    u32 x24_tableCount = 0;             // x144\n    float x28_heightSelPitch = 0.f;     // x148\n    float x2c_ooTurbSpeed;              // x14c\n    float x30_ooTurbDistance;           // x150\n    bool x34_hasTurbulence = false;     // x154\n  public:\n    CTurbulence(float speed, float distance, float freqMax, float freqMin, float phaseMax, float phaseMin,\n                float amplitudeMax, float amplitudeMin);\n    float GetHeight(float sel) const { return x20_table[(x24_tableCount - 1) & int(sel * x28_heightSelPitch)]; }\n    float GetOODistance() const { return x30_ooTurbDistance; }\n    float GetOOSpeed() const { return x2c_ooTurbSpeed; }\n    bool HasTurbulence() const { return x34_hasTurbulence; }\n  };\n\n  CAssetId xa0_texIdBumpMap;\n  CAssetId xa4_texIdEnvMap;\n  CAssetId xa8_texIdEnvBumpMap;\n  CAssetId xac_texId4;\n  TLockedToken<CTexture> xb0_bumpMap;\n  TLockedToken<CTexture> xc0_envMap;\n  TLockedToken<CTexture> xd0_envBumpMap;\n  TLockedToken<CTexture> xe0_lightmap;\n  zeus::CVector3f xf0_bumpLightDir;\n  float xfc_bumpScale;\n  float x100_tileSize;\n  int x104_tileSubdivisions;\n  float x108_rippleResolution;\n  float x10c_specularMin;\n  float x110_specularMax;\n  float x114_reflectionBlend;\n  float x118_reflectionSize;\n  float x11c_unitsPerLightmapTexel;\n  CTurbulence x120_turbulence;\n\n  u32 m_maxVertCount;\n  bool m_tessellation = false;\n\n  bool m_cachedDoubleLightmapBlend = false;\n  bool m_cachedAdditive = false;\n\n  static bool PrepareRipple(const CRipple& ripple, const CFluidPlaneRender::SPatchInfo& info,\n                            CFluidPlaneRender::SRippleInfo& rippleOut);\n  void ApplyTurbulence(float t, Heights& heights, const Flags& flags, const SineTable& sineWave,\n                       const CFluidPlaneRender::SPatchInfo& info, const zeus::CVector3f& areaCenter) const;\n  void ApplyRipple(const CFluidPlaneRender::SRippleInfo& rippleInfo, Heights& heights, Flags& flags,\n                   const SineTable& sineWave, const CFluidPlaneRender::SPatchInfo& info) const;\n  void ApplyRipples(const rstl::reserved_vector<CFluidPlaneRender::SRippleInfo, 32>& rippleInfos, Heights& heights,\n                    Flags& flags, const SineTable& sineWave, const CFluidPlaneRender::SPatchInfo& info) const;\n  static void UpdatePatchNoNormals(Heights& heights, const Flags& flags, const CFluidPlaneRender::SPatchInfo& info);\n  static void UpdatePatchWithNormals(Heights& heights, const Flags& flags, const CFluidPlaneRender::SPatchInfo& info);\n  bool UpdatePatch(float time, const CFluidPlaneRender::SPatchInfo& info, Heights& heights, Flags& flags,\n                   const zeus::CVector3f& areaCenter, const std::optional<CRippleManager>& rippleManager, int fromX,\n                   int toX, int fromY, int toY) const;\n\npublic:\n  CFluidPlaneCPU(CAssetId texPattern1, CAssetId texPattern2, CAssetId texColor, CAssetId bumpMap, CAssetId envMap,\n                 CAssetId envBumpMap, CAssetId lightMap, float unitsPerLightmapTexel, float tileSize,\n                 u32 tileSubdivisions, EFluidType fluidType, float alpha, const zeus::CVector3f& bumpLightDir,\n                 float bumpScale, const CFluidUVMotion& mot, float turbSpeed, float turbDistance, float turbFreqMax,\n                 float turbFreqMin, float turbPhaseMax, float turbPhaseMin, float turbAmplitudeMax,\n                 float turbAmplitudeMin, float specularMin, float specularMax, float reflectionBlend,\n                 float reflectionSize, float rippleIntensity, u32 maxVertCount);\n  void CreateRipple(const CRipple& ripple, CStateManager& mgr);\n  void CalculateLightmapMatrix(const zeus::CTransform& areaXf, const zeus::CTransform& xf, const zeus::CAABox& aabb,\n                               zeus::CMatrix4f& mtxOut) const;\n  // CFluidPlaneShader::RenderSetupInfo RenderSetup(const CStateManager& mgr, float, const zeus::CTransform& xf,\n  //                                                const zeus::CTransform& areaXf, const zeus::CAABox& aabb,\n  //                                                const CScriptWater* water);\n  void Render(const CStateManager& mgr, float alpha, const zeus::CAABox& aabb, const zeus::CTransform& xf,\n              const zeus::CTransform& areaXf, bool noNormals, const zeus::CFrustum& frustum,\n              const std::optional<CRippleManager>& rippleManager, TUniqueId waterId, const bool* gridFlags,\n              u32 gridDimX, u32 gridDimY, const zeus::CVector3f& areaCenter) override;\n  float GetReflectionBlend() const { return x114_reflectionBlend; }\n  float GetSpecularMax() const { return x110_specularMax; }\n  float GetSpecularMin() const { return x10c_specularMin; }\n  float GetReflectionSize() const { return x118_reflectionSize; }\n  float GetBumpScale() const { return xfc_bumpScale; }\n  bool HasBumpMap() const { return xb0_bumpMap.HasReference(); }\n  const CTexture& GetBumpMap() const { return *xb0_bumpMap; }\n  bool HasEnvMap() const { return xc0_envMap.HasReference(); }\n  const CTexture& GetEnvMap() const { return *xc0_envMap; }\n  bool HasEnvBumpMap() const { return xd0_envBumpMap.HasReference(); }\n  const CTexture& GetEnvBumpMap() const { return *xd0_envBumpMap; }\n  bool HasLightMap() const { return xe0_lightmap.HasReference(); }\n  const CTexture& GetLightMap() const { return *xe0_lightmap; }\n  const zeus::CVector3f& GetBumpLightDir() const { return xf0_bumpLightDir; }\n  float GetTileSize() const { return x100_tileSize; }\n  int GetTileSubdivisions() const { return x104_tileSubdivisions; }\n  float GetRippleResolution() const { return x108_rippleResolution; }\n  float GetTurbulenceHeight(float sel) const { return x120_turbulence.GetHeight(sel); }\n  float GetOOTurbulenceDistance() const { return x120_turbulence.GetOODistance(); }\n  float GetOOTurbulenceSpeed() const { return x120_turbulence.GetOOSpeed(); }\n  bool HasTurbulence() const { return x120_turbulence.HasTurbulence(); }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CFluidPlaneDoor.cpp",
    "content": "#include \"Runtime/World/CFluidPlaneDoor.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CFluidPlaneCPU.hpp\"\n\nnamespace metaforce {\n\nCFluidPlaneDoor::CFluidPlaneDoor(CAssetId patternTex1, CAssetId patternTex2, CAssetId colorTex, float tileSize,\n                                 u32 tileSubdivisions, EFluidType fluidType, float alpha,\n                                 const CFluidUVMotion& uvMotion)\n: CFluidPlane(patternTex1, patternTex2, colorTex, alpha, fluidType, 0.5f, uvMotion)\n, xa0_tileSize(tileSize)\n, xa4_tileSubdivisions(tileSubdivisions & ~0x1)\n, xa8_rippleResolution(xa0_tileSize / float(xa4_tileSubdivisions)) {}\n\n// CFluidPlaneShader::RenderSetupInfo CFluidPlaneDoor::RenderSetup(const CStateManager& mgr, float alpha,\n//                                                                 const zeus::CTransform& xf, const zeus::CAABox& aabb,\n//                                                                 bool noNormals) {\n//   CFluidPlaneShader::RenderSetupInfo out;\n//\n//   const float uvT = mgr.GetFluidPlaneManager()->GetUVT();\n//   CGraphics::SetModelMatrix(xf);\n//\n//   const auto fluidUVs = x4c_uvMotion.CalculateFluidTextureOffset(uvT);\n//\n//   out.texMtxs[0][0][0] = x4c_uvMotion.GetFluidLayers()[1].GetUVScale();\n//   out.texMtxs[0][1][1] = x4c_uvMotion.GetFluidLayers()[1].GetUVScale();\n//   out.texMtxs[0][3][0] = fluidUVs[1][0];\n//   out.texMtxs[0][3][1] = fluidUVs[1][1];\n//\n//   out.texMtxs[1][0][0] = x4c_uvMotion.GetFluidLayers()[2].GetUVScale();\n//   out.texMtxs[1][1][1] = x4c_uvMotion.GetFluidLayers()[2].GetUVScale();\n//   out.texMtxs[1][3][0] = fluidUVs[2][0];\n//   out.texMtxs[1][3][1] = fluidUVs[2][1];\n//\n//   out.texMtxs[2][0][0] = x4c_uvMotion.GetFluidLayers()[0].GetUVScale();\n//   out.texMtxs[2][1][1] = x4c_uvMotion.GetFluidLayers()[0].GetUVScale();\n//   out.texMtxs[2][3][0] = fluidUVs[0][0];\n//   out.texMtxs[2][3][1] = fluidUVs[0][1];\n//\n//   out.kColors[0] = zeus::CColor(1.f, alpha);\n//\n//   if (!m_shader) {\n//     auto gridDimX = u32((xa0_tileSize + aabb.max.x() - aabb.min.x() - 0.01f) / xa0_tileSize);\n//     auto gridDimY = u32((xa0_tileSize + aabb.max.y() - aabb.min.y() - 0.01f) / xa0_tileSize);\n//     u32 gridCellCount = (gridDimX + 1) * (gridDimY + 1);\n//     u32 maxVerts = gridCellCount * ((std::max(2, xa4_tileSubdivisions) * 4 + 2) * 4);\n//     m_shader.emplace(x10_texPattern1, x20_texPattern2, x30_texColor, maxVerts);\n//   }\n//\n//   return out;\n// }\n\n// Used to be part of locked cache\n// These are too big for stack allocation\nstatic CFluidPlane::Heights lc_heights{};\nstatic CFluidPlane::Flags lc_flags{};\n\nvoid CFluidPlaneDoor::Render(const CStateManager& mgr, float alpha, const zeus::CAABox& aabb,\n                             const zeus::CTransform& xf, const zeus::CTransform& areaXf, bool noNormals,\n                             const zeus::CFrustum& frustum, const std::optional<CRippleManager>& rippleManager,\n                             TUniqueId waterId, const bool* gridFlags, u32 gridDimX, u32 gridDimY,\n                             const zeus::CVector3f& areaCenter) {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CFluidPlaneDoor::Render\", zeus::skCyan);\n  // CFluidPlaneShader::RenderSetupInfo setupInfo = RenderSetup(mgr, alpha, xf, aabb, noNormals);\n\n  // if (!m_shader->isReady())\n  //   return;\n\n  CFluidPlaneRender::numSubdivisionsInTile = xa4_tileSubdivisions;\n  CFluidPlaneRender::numTilesInHField = 42 / xa4_tileSubdivisions;\n  CFluidPlaneRender::numSubdivisionsInHField = CFluidPlaneRender::numTilesInHField * xa4_tileSubdivisions;\n  zeus::CVector2f centerPlane(aabb.center().x(), aabb.center().y());\n  float patchSize = xa8_rippleResolution * CFluidPlaneRender::numSubdivisionsInHField;\n  float ooSubdivSize = 1.f / xa8_rippleResolution;\n\n  // m_verts.clear();\n  // m_pVerts.clear();\n  // m_shader->prepareDraw(setupInfo);\n\n  for (float curX = aabb.min.x(); curX < aabb.max.x(); curX += patchSize) {\n    float remSubdivsX = (aabb.max.x() - curX) * ooSubdivSize;\n    for (float curY = aabb.min.y(); curY < aabb.max.y(); curY += patchSize) {\n      float remSubdivsY = (aabb.max.y() - curY) * ooSubdivSize;\n      int remSubdivsXi = std::min(CFluidPlaneRender::numSubdivisionsInHField, int(remSubdivsX));\n      int remSubdivsYi = std::min(CFluidPlaneRender::numSubdivisionsInHField, int(remSubdivsY));\n      zeus::CAABox aabb2(aabb.min, zeus::CVector3f(xa8_rippleResolution * remSubdivsXi + curX,\n                                                   xa8_rippleResolution * remSubdivsYi + curY, aabb.max.z()));\n      if (frustum.aabbFrustumTest(aabb2.getTransformedAABox(xf))) {\n        CFluidPlaneRender::SPatchInfo patchInfo(zeus::CVector3f(curX, curY, aabb.min.z()), aabb2.max, xf.origin,\n                                                xa8_rippleResolution, xa0_tileSize, 0.f,\n                                                CFluidPlaneRender::numSubdivisionsInHField,\n                                                CFluidPlaneRender::NormalMode::None, 0, 0, 0, 0, 0, 0, 0, nullptr);\n\n        // RenderPatch(patchInfo, lc_heights, lc_flags, true, true, m_verts, m_pVerts);\n      }\n    }\n  }\n\n  // m_shader->loadVerts(m_verts, m_pVerts);\n  // m_shader->doneDrawing();\n}\n\n} // namespace metaforce"
  },
  {
    "path": "Runtime/World/CFluidPlaneDoor.hpp",
    "content": "#pragma once\n\n#include <optional>\n\n#include \"Runtime/World/CFluidPlane.hpp\"\n\nnamespace metaforce {\nclass CFluidPlaneDoor final : public CFluidPlane {\n  float xa0_tileSize;\n  int xa4_tileSubdivisions;\n  float xa8_rippleResolution;\n\n  // CFluidPlaneShader::RenderSetupInfo RenderSetup(const CStateManager& mgr, float alpha, const zeus::CTransform& xf,\n  //                                                const zeus::CAABox& aabb, bool noNormals);\n\npublic:\n  CFluidPlaneDoor(CAssetId patternTex1, CAssetId patternTex2, CAssetId colorTex, float tileSize, u32 tileSubdivisions,\n                  EFluidType fluidType, float alpha, const CFluidUVMotion& uvMotion);\n  void AddRipple(float mag, TUniqueId rippler, const zeus::CVector3f& center, CScriptWater& water,\n                 CStateManager& mgr) override {}\n  void AddRipple(float intensity, TUniqueId rippler, const zeus::CVector3f& center, const zeus::CVector3f& velocity,\n                 const CScriptWater& water, CStateManager& mgr, const zeus::CVector3f& upVec) override {}\n  void AddRipple(const CRipple& ripple, const CScriptWater& water, CStateManager& mgr) override {}\n\n  void Render(const CStateManager& mgr, float alpha, const zeus::CAABox& aabb, const zeus::CTransform& xf,\n              const zeus::CTransform& areaXf, bool noNormals, const zeus::CFrustum& frustum,\n              const std::optional<CRippleManager>& rippleManager, TUniqueId waterId, const bool* gridFlags,\n              u32 gridDimX, u32 gridDimY, const zeus::CVector3f& areaCenter) override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CFluidPlaneGPU.cpp",
    "content": "#include \"Runtime/World/CFluidPlaneGPU.hpp\"\n\nnamespace metaforce {\n\nCFluidPlaneGPU::CFluidPlaneGPU(CAssetId texPattern1, CAssetId texPattern2, CAssetId texColor, CAssetId bumpMap,\n                               CAssetId envMap, CAssetId envBumpMap, CAssetId lightMap, float unitsPerLightmapTexel,\n                               float tileSize, u32 tileSubdivisions, EFluidType fluidType, float alpha,\n                               const zeus::CVector3f& bumpLightDir, float bumpScale, const CFluidUVMotion& mot,\n                               float turbSpeed, float turbDistance, float turbFreqMax, float turbFreqMin,\n                               float turbPhaseMax, float turbPhaseMin, float turbAmplitudeMax, float turbAmplitudeMin,\n                               float specularMin, float specularMax, float reflectionBlend, float reflectionSize,\n                               float rippleIntensity, u32 maxVertCount)\n: CFluidPlaneCPU(texPattern1, texPattern2, texColor, bumpMap, envMap, envBumpMap, lightMap, unitsPerLightmapTexel,\n                 tileSize, tileSubdivisions, fluidType, alpha, bumpLightDir, bumpScale, mot, turbSpeed, turbDistance,\n                 turbFreqMax, turbFreqMin, turbPhaseMax, turbPhaseMin, turbAmplitudeMax, turbAmplitudeMin, specularMin,\n                 specularMax, reflectionBlend, reflectionSize, rippleIntensity, maxVertCount) {\n  m_tessellation = true;\n}\n\n// void CFluidPlaneGPU::RenderStripWithRipples(float curY, const Heights& heights, const Flags& flags, int startYDiv,\n//                                             const CFluidPlaneRender::SPatchInfo& info,\n//                                             std::vector<CFluidPlaneShader::Vertex>& vOut,\n//                                             std::vector<CFluidPlaneShader::PatchVertex>& pvOut) {\n//   m_shader->bindTessellation();\n//\n//   int yTile = (startYDiv + CFluidPlaneRender::numSubdivisionsInTile - 1) / CFluidPlaneRender::numSubdivisionsInTile;\n//   int endXTile =\n//       (info.x0_xSubdivs + CFluidPlaneRender::numSubdivisionsInTile - 4) / CFluidPlaneRender::numSubdivisionsInTile;\n//\n//   float yMin = curY;\n//   float subdivF = CFluidPlaneRender::numSubdivisionsInTile;\n//\n//   float curX = info.x4_localMin.x();\n//   int gridCell = info.x28_tileX + info.x2a_gridDimX * (info.x2e_tileY + yTile - 1);\n//   int xTile = 1;\n//   int tileSpan;\n//   for (int i = 1; i < info.x0_xSubdivs - 2; i += CFluidPlaneRender::numSubdivisionsInTile * tileSpan,\n//            gridCell += tileSpan, xTile += tileSpan, curX += info.x14_tileSize * tileSpan) {\n//     tileSpan = 1;\n//     if (info.x30_gridFlags && !info.x30_gridFlags[gridCell])\n//       continue;\n//\n//     CFluidPlaneShader::PatchVertex pv;\n//     size_t start = pvOut.size();\n//\n//     if ((flags[yTile][xTile] & 0x1f) == 0x1f) {\n//       for (; xTile + tileSpan <= endXTile; ++tileSpan) {\n//         if ((flags[yTile][xTile + tileSpan] & 0x1f) != 0x1f)\n//           break;\n//         if (info.x30_gridFlags && !info.x30_gridFlags[gridCell + tileSpan])\n//           break;\n//       }\n//\n//       pv.m_outerLevels.fill(subdivF);\n//       pv.m_innerLevels.fill(subdivF);\n//     } else {\n//       const bool north = (flags[yTile + 1][xTile] & 0x2) != 0;\n//       const bool west = (flags[yTile][xTile - 1] & 0x8) != 0;\n//       const bool east = (flags[yTile][xTile + 1] & 0x4) != 0;\n//       const bool south = (flags[yTile - 1][xTile] & 0x1) != 0;\n//\n//       pv.m_outerLevels[0] = west ? subdivF : 1.f;\n//       pv.m_outerLevels[1] = south ? subdivF : 1.f;\n//       pv.m_outerLevels[2] = east ? subdivF : 1.f;\n//       pv.m_outerLevels[3] = north ? subdivF : 1.f;\n//       pv.m_innerLevels.fill(subdivF);\n//     }\n//\n//     float curTileY = yMin;\n//     float curTileX = curX;\n//     for (int t = 0; t < tileSpan; ++t) {\n//       pv.m_pos = zeus::CVector4f(curTileX, curTileY, curTileX + info.x14_tileSize, curTileY + info.x14_tileSize);\n//       pvOut.push_back(pv);\n//       curTileX += info.x14_tileSize;\n//     }\n//\n// //    CGraphics::DrawArray(start, pvOut.size() - start);\n//   }\n// }\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CFluidPlaneGPU.hpp",
    "content": "#pragma once\n\n#include <vector>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/World/CFluidPlaneCPU.hpp\"\n\nnamespace metaforce {\n\nclass CFluidPlaneGPU final : public CFluidPlaneCPU {\npublic:\n  CFluidPlaneGPU(CAssetId texPattern1, CAssetId texPattern2, CAssetId texColor, CAssetId bumpMap, CAssetId envMap,\n                 CAssetId envBumpMap, CAssetId lightMap, float unitsPerLightmapTexel, float tileSize,\n                 u32 tileSubdivisions, EFluidType fluidType, float alpha, const zeus::CVector3f& bumpLightDir,\n                 float bumpScale, const CFluidUVMotion& mot, float turbSpeed, float turbDistance, float turbFreqMax,\n                 float turbFreqMin, float turbPhaseMax, float turbPhaseMin, float turbAmplitudeMax,\n                 float turbAmplitudeMin, float specularMin, float specularMax, float reflectionBlend,\n                 float reflectionSize, float rippleIntensity, u32 maxVertCount);\n\n  // void RenderStripWithRipples(float curY, const Heights& heights, const Flags& flags, int startYDiv,\n  //                             const CFluidPlaneRender::SPatchInfo& info, std::vector<CFluidPlaneShader::Vertex>& vOut,\n  //                             std::vector<CFluidPlaneShader::PatchVertex>& pvOut) override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CFluidPlaneManager.cpp",
    "content": "#include \"Runtime/World/CFluidPlaneManager.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CExplosion.hpp\"\n#include \"Runtime/World/CScriptWater.hpp\"\n\nnamespace metaforce {\n\nCFluidPlaneManager::CFluidProfile CFluidPlaneManager::sProfile = {};\n\nCFluidPlaneManager::CFluidPlaneManager() : x0_rippleManager(20, 0.5f) {\n  sProfile.Clear();\n  SetupRippleMap();\n}\n\nvoid CFluidPlaneManager::CFluidProfile::Clear() {\n  x0_ = 0.f;\n  x4_ = 0.f;\n  x8_ = 0.f;\n  xc_ = 0.f;\n  x10_ = 0.f;\n}\n\nvoid CFluidPlaneManager::StartFrame(bool b) {\n  x121_ = b;\n  sProfile.Clear();\n}\n\nvoid CFluidPlaneManager::Update(float dt) {\n  x11c_uvT += dt;\n  x0_rippleManager.Update(dt);\n  for (CSplashRecord& record : x18_splashes) {\n    record.SetTime(record.GetTime() + dt);\n    if (record.GetTime() > 9999.f)\n      record.SetTime(9999.f);\n  }\n}\n\nfloat CFluidPlaneManager::GetLastRippleDeltaTime(TUniqueId rippler) const {\n  return x0_rippleManager.GetLastRippleDeltaTime(rippler);\n}\n\nfloat CFluidPlaneManager::GetLastSplashDeltaTime(TUniqueId splasher) const {\n  float newestTime = 9999.f;\n  for (const CSplashRecord& record : x18_splashes)\n    if (record.GetUniqueId() == splasher && newestTime > record.GetTime())\n      newestTime = record.GetTime();\n  return newestTime;\n}\n\nvoid CFluidPlaneManager::CreateSplash(TUniqueId splasher, CStateManager& mgr, const CScriptWater& water,\n                                      const zeus::CVector3f& pos, float factor, bool sfx) {\n  if (water.CanRippleAtPoint(pos)) {\n    float oldestTime = 0.f;\n    CSplashRecord* oldestRecord = nullptr;\n    for (CSplashRecord& record : x18_splashes) {\n      if (record.GetTime() > oldestTime) {\n        oldestRecord = &record;\n        oldestTime = record.GetTime();\n      }\n    }\n    if (oldestRecord)\n      *oldestRecord = CSplashRecord(0.f, splasher);\n    else\n      x18_splashes.emplace_back(0.f, splasher);\n    float splashScale = water.GetSplashEffectScale(factor);\n    if (water.GetSplashEffect(factor)) {\n      CExplosion* expl = new CExplosion(*water.GetSplashEffect(factor), mgr.AllocateUniqueId(), true,\n                                        CEntityInfo(water.GetAreaIdAlways(), CEntity::NullConnectionList), \"Splash\",\n                                        zeus::CTransform(zeus::CMatrix3f(), pos), 1, zeus::CVector3f{splashScale},\n                                        water.GetSplashColor());\n      mgr.AddObject(expl);\n    }\n    if (sfx) {\n      CSfxManager::AddEmitter(water.GetSplashSound(factor), pos, zeus::skUp, true, false, 0x7f, kInvalidAreaId);\n    }\n  }\n}\n\nstatic bool g_RippleMapSetup = false;\nstd::array<std::array<u8, 64>, 64> CFluidPlaneManager::RippleValues{};\nstd::array<u8, 64> CFluidPlaneManager::RippleMins{};\nstd::array<u8, 64> CFluidPlaneManager::RippleMaxs{};\nTGXTexObj CFluidPlaneManager::RippleMapTex;\n\nvoid CFluidPlaneManager::SetupRippleMap() {\n  if (g_RippleMapSetup) {\n    return;\n  }\n  g_RippleMapSetup = true;\n\n  float curX = 0.f;\n  for (size_t i = 0; i < 64; ++i) {\n    float curY = 0.f;\n    float minY = 1.f;\n    float maxY = 0.f;\n    for (size_t j = 0; j < 64; ++j) {\n      const float rVal = 1.f - curY;\n      float minX = curY;\n      float maxX = 1.25f * (0.25f * rVal + 0.1f) + curY;\n      if (curY < 0.f) {\n        minX = 0.f;\n      } else if (maxX > 1.f) {\n        maxX = 1.f;\n      }\n\n      float val = 0.f;\n      if (curX >= minX && curX <= maxX) {\n        const float t = (curX - minX) / (maxX - minX);\n        if (t < 0.4f) {\n          val = 2.5f * t;\n        } else if (t > 0.75f) {\n          val = 4.f * (1.f - t);\n        } else {\n          val = 1.f;\n        }\n      }\n\n      const auto valA = u8(std::max(int(255.f * val * rVal * rVal) - 1, 0));\n      RippleValues[i][j] = valA;\n      if (valA != 0 && curY < minY) {\n        minY = curY;\n      }\n      if (valA != 0 && curY > maxY) {\n        maxY = curY;\n      }\n\n      curY += (1.f / 63.f);\n    }\n\n    const auto valB = u8(std::max(int(255.f * minY) - 1, 0));\n    const auto valC = u8(std::min(int(255.f * maxY) + 1, 255));\n    RippleMins[i] = valB;\n    RippleMaxs[i] = valC;\n    curX += (1.f / 63.f);\n  }\n\n  GXInitTexObj(&RippleMapTex, RippleValues.data(), 64, 64, static_cast<GXTexFmt>(GX_TF_R8_PC), GX_REPEAT, GX_REPEAT, false);\n}\n\nvoid CFluidPlaneManager::Shutdown() { GXDestroyTexObj(&RippleMapTex); }\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CFluidPlaneManager.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Graphics/CGraphics.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/World/CRippleManager.hpp\"\n\nnamespace metaforce {\n\nclass CStateManager;\nclass CScriptWater;\n\nclass CFluidPlaneManager {\n  class CSplashRecord {\n    float x0_time = 0.f;\n    TUniqueId x4_id;\n\n  public:\n    CSplashRecord(float time, TUniqueId id) : x0_time(time), x4_id(id) {}\n    void SetTime(float t) { x0_time = t; }\n    float GetTime() const { return x0_time; }\n    void SetUniqueId(TUniqueId id) { x4_id = id; }\n    TUniqueId GetUniqueId() const { return x4_id; }\n  };\n\n  CRippleManager x0_rippleManager;\n  rstl::reserved_vector<CSplashRecord, 32> x18_splashes;\n  float x11c_uvT = 0.f;\n  bool x120_ = false;\n  bool x121_ = false;\n\n  class CFluidProfile {\n    float x0_ = 0.f;\n    float x4_ = 0.f;\n    float x8_ = 0.f;\n    float xc_ = 0.f;\n    float x10_ = 0.f;\n\n  public:\n    void Clear();\n  };\n  static CFluidProfile sProfile;\n  static void SetupRippleMap();\n\npublic:\n  static std::array<std::array<u8, 64>, 64> RippleValues;\n  static std::array<u8, 64> RippleMins;\n  static std::array<u8, 64> RippleMaxs;\n  static TGXTexObj RippleMapTex;\n\n  CFluidPlaneManager();\n  void StartFrame(bool);\n  void EndFrame() { x121_ = false; }\n  void Update(float dt);\n  float GetUVT() const { return x11c_uvT; }\n  float GetLastRippleDeltaTime(TUniqueId rippler) const;\n  float GetLastSplashDeltaTime(TUniqueId splasher) const;\n  void CreateSplash(TUniqueId splasher, CStateManager& mgr, const CScriptWater& water, const zeus::CVector3f& pos,\n                    float factor, bool sfx);\n  rstl::reserved_vector<CSplashRecord, 32>& SplashRecords() { return x18_splashes; }\n  const CRippleManager& GetRippleManager() const { return x0_rippleManager; }\n  CRippleManager& RippleManager() { return x0_rippleManager; }\n\n  static void Shutdown();\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CFluidUVMotion.cpp",
    "content": "#include \"Runtime/World/CFluidUVMotion.hpp\"\n\n#include <cmath>\n\n#include <zeus/Math.hpp>\n\nnamespace metaforce {\n\nCFluidUVMotion::CFluidUVMotion(float timeToWrap, float orientation, const SFluidLayerMotion& colorLayer,\n                               const SFluidLayerMotion& pattern1Layer, const SFluidLayerMotion& pattern2Layer)\n: x0_fluidLayers{{colorLayer, pattern1Layer, pattern2Layer}}\n, x4c_ooTimeToWrap(1.f / timeToWrap)\n, x50_orientation(orientation) {}\n\nCFluidUVMotion::CFluidUVMotion(float timeToWrap, float orientation)\n: x4c_ooTimeToWrap(1.f / timeToWrap), x50_orientation(orientation) {\n  x0_fluidLayers.resize(3);\n  x0_fluidLayers[0].x4_ooTimeToWrap = 0.001f;\n  x0_fluidLayers[1].x4_ooTimeToWrap = 0.33333334f;\n  x0_fluidLayers[2].x4_ooTimeToWrap = 0.2f;\n  x0_fluidLayers[2].x8_orientation = 0.78539819f;\n}\n\nCFluidUVMotion::FluidOffsets CFluidUVMotion::CalculateFluidTextureOffset(float t) const {\n  FluidOffsets offsets;\n  const float totalYOffset = t * x4c_ooTimeToWrap * std::cos(x50_orientation);\n  const float totalXOffset = t * x4c_ooTimeToWrap * std::sin(x50_orientation);\n\n  for (size_t i = 0; i < x0_fluidLayers.size(); ++i) {\n    const SFluidLayerMotion& layer = x0_fluidLayers[i];\n\n    const float speedT = t * layer.x4_ooTimeToWrap;\n    const float cycleT = speedT - std::floor(speedT);\n    float localY;\n    float localX;\n    switch (layer.x0_motion) {\n    case EFluidUVMotion::Linear: {\n      localX = speedT;\n      localY = 0.f;\n    } break;\n    case EFluidUVMotion::Circular: {\n      const float angle = (M_PIF * 2) * cycleT;\n      localY = layer.xc_magnitude * std::sin(angle);\n      localX = layer.xc_magnitude * std::cos(angle);\n    } break;\n    case EFluidUVMotion::Oscillate: {\n      localY = 0.f;\n      localX = layer.xc_magnitude * std::cos((M_PIF * 2) * cycleT);\n    } break;\n    default:\n      localY = localX = 0.f;\n      break;\n    }\n\n    const float x = localX * std::sin(layer.x8_orientation) + localY * std::cos(layer.x8_orientation) + totalXOffset;\n    const float y = localY * std::sin(layer.x8_orientation) + localX * std::cos(layer.x8_orientation) + totalYOffset;\n\n    offsets[i][0] = x - std::floor(x);\n    offsets[i][1] = y - std::floor(y);\n  }\n\n  return offsets;\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CFluidUVMotion.hpp",
    "content": "#pragma once\n\n#include <array>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n\nnamespace metaforce {\nclass CFluidUVMotion {\npublic:\n  enum class EFluidUVMotion {\n    Linear,\n    Circular,\n    Oscillate,\n  };\n\n  struct SFluidLayerMotion {\n    EFluidUVMotion x0_motion = EFluidUVMotion::Linear;\n    float x4_ooTimeToWrap = 0.16666667f;\n    float x8_orientation = 0.f;\n    float xc_magnitude = 1.f;\n    float x10_uvMul = 5.f;\n    float x14_uvScale = 0.2f;\n\n    SFluidLayerMotion() = default;\n    SFluidLayerMotion(EFluidUVMotion motion, float timeToWrap, float orientation, float magnitude, float uvMul)\n    : x0_motion(motion)\n    , x4_ooTimeToWrap(1.f / timeToWrap)\n    , x8_orientation(orientation)\n    , xc_magnitude(magnitude)\n    , x10_uvMul(uvMul)\n    , x14_uvScale(1.f / uvMul) {}\n\n    float GetUVScale() const { return x14_uvScale; }\n  };\n\nprivate:\n  rstl::reserved_vector<SFluidLayerMotion, 3> x0_fluidLayers;\n  float x4c_ooTimeToWrap;\n  float x50_orientation;\n\npublic:\n  using FluidOffsets = std::array<std::array<float, 2>, 3>;\n\n  CFluidUVMotion(float timeToWrap, float orientation, const SFluidLayerMotion& colorLayer,\n                 const SFluidLayerMotion& pattern1Layer, const SFluidLayerMotion& pattern2Layer);\n  CFluidUVMotion(float timeToWrap, float orientation);\n\n  const rstl::reserved_vector<SFluidLayerMotion, 3>& GetFluidLayers() const { return x0_fluidLayers; }\n  float GetOrientation() const { return x50_orientation; }\n  float GetOOTimeToWrapTexPage() const { return x4c_ooTimeToWrap; }\n\n  // In game binaries this uses an out pointer instead of return by value.\n  FluidOffsets CalculateFluidTextureOffset(float t) const;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CGameArea.cpp",
    "content": "#include \"Runtime/World/CGameArea.hpp\"\n\n#include <array>\n#include <cstring>\n\n#include \"Runtime/CGameState.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Graphics/CCubeSurface.hpp\"\n#include \"Runtime/Logging.hpp\"\n#include \"Runtime/World/CScriptAreaAttributes.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nCAreaRenderOctTree::CAreaRenderOctTree(const u8* buf) : x0_buf(buf) {\n  CMemoryInStream r(x0_buf + 8, INT32_MAX);\n  x8_bitmapCount = r.ReadLong();\n  xc_meshCount = r.ReadLong();\n  x10_nodeCount = r.ReadLong();\n  x14_bitmapWordCount = (xc_meshCount + 31) / 32;\n  x18_aabb = r.Get<zeus::CAABox>();\n\n  x30_bitmaps = reinterpret_cast<const u32*>(x0_buf + 64);\n  u32 wc = x14_bitmapWordCount * x8_bitmapCount;\n  for (u32 i = 0; i < wc; ++i)\n    const_cast<u32*>(x30_bitmaps)[i] = CBasics::SwapBytes(x30_bitmaps[i]);\n\n  x34_indirectionTable = x30_bitmaps + wc;\n  x38_entries = reinterpret_cast<const u8*>(x34_indirectionTable + x10_nodeCount);\n  for (u32 i = 0; i < x10_nodeCount; ++i) {\n    const_cast<u32*>(x34_indirectionTable)[i] = CBasics::SwapBytes(x34_indirectionTable[i]);\n    Node* n = reinterpret_cast<Node*>(const_cast<u8*>(x38_entries) + x34_indirectionTable[i]);\n    n->x0_bitmapIdx = CBasics::SwapBytes(n->x0_bitmapIdx);\n    n->x2_flags = CBasics::SwapBytes(n->x2_flags);\n    if (n->x2_flags) {\n      u32 childCount = n->GetChildCount();\n      for (u32 c = 0; c < childCount; ++c)\n        n->x4_children[c] = CBasics::SwapBytes(n->x4_children[c]);\n    }\n  }\n}\n\nu32 CAreaRenderOctTree::Node::GetChildCount() const {\n  static constexpr std::array<u32, 8> ChildCounts{0, 2, 2, 4, 2, 4, 4, 8};\n  return ChildCounts[x2_flags];\n}\n\nzeus::CAABox CAreaRenderOctTree::Node::GetNodeBounds(const zeus::CAABox& curAABB, int idx) const {\n  zeus::CVector3f center = curAABB.center();\n  switch (x2_flags) {\n  case 0:\n  default:\n    return curAABB;\n  case 1:\n    if (idx == 0)\n      return {curAABB.min.x(), curAABB.min.y(), curAABB.min.z(), center.x(), curAABB.max.y(), curAABB.max.z()};\n    else\n      return {center.x(), curAABB.min.y(), curAABB.min.z(), curAABB.max.x(), curAABB.max.y(), curAABB.max.z()};\n  case 2:\n    if (idx == 0)\n      return {curAABB.min.x(), curAABB.min.y(), curAABB.min.z(), curAABB.max.x(), center.y(), curAABB.max.z()};\n    else\n      return {curAABB.min.x(), center.y(), curAABB.min.z(), curAABB.max.x(), curAABB.max.y(), curAABB.max.z()};\n  case 3: {\n    switch (idx) {\n    case 0:\n    default:\n      return {curAABB.min.x(), curAABB.min.y(), curAABB.min.z(), center.x(), center.y(), curAABB.max.z()};\n    case 1:\n      return {center.x(), curAABB.min.y(), curAABB.min.z(), curAABB.max.x(), center.y(), curAABB.max.z()};\n    case 2:\n      return {curAABB.min.x(), center.y(), curAABB.min.z(), center.x(), curAABB.max.y(), curAABB.max.z()};\n    case 3:\n      return {center.x(), center.y(), curAABB.min.z(), curAABB.max.x(), curAABB.max.y(), curAABB.max.z()};\n    }\n  }\n  case 4:\n    if (idx == 0)\n      return {curAABB.min.x(), curAABB.min.y(), curAABB.min.z(), curAABB.max.x(), curAABB.max.y(), center.z()};\n    else\n      return {curAABB.min.x(), curAABB.min.y(), center.z(), curAABB.max.x(), curAABB.max.y(), curAABB.max.z()};\n  case 5: {\n    switch (idx) {\n    case 0:\n    default:\n      return {curAABB.min.x(), curAABB.min.y(), curAABB.min.z(), center.x(), curAABB.max.y(), center.z()};\n    case 1:\n      return {center.x(), curAABB.min.y(), curAABB.min.z(), curAABB.max.x(), curAABB.max.y(), center.z()};\n    case 2:\n      return {curAABB.min.x(), curAABB.min.y(), center.z(), center.x(), curAABB.max.y(), curAABB.max.z()};\n    case 3:\n      return {center.x(), curAABB.min.y(), center.z(), curAABB.max.x(), curAABB.max.y(), curAABB.max.z()};\n    }\n  }\n  case 6: {\n    switch (idx) {\n    case 0:\n    default:\n      return {curAABB.min.x(), curAABB.min.y(), curAABB.min.z(), curAABB.max.x(), center.y(), center.z()};\n    case 1:\n      return {curAABB.min.x(), center.y(), curAABB.min.z(), curAABB.max.x(), curAABB.max.y(), center.z()};\n    case 2:\n      return {curAABB.min.x(), curAABB.min.y(), center.z(), curAABB.max.x(), center.y(), curAABB.max.z()};\n    case 3:\n      return {curAABB.min.x(), center.y(), center.z(), curAABB.max.x(), curAABB.max.y(), curAABB.max.z()};\n    }\n  }\n  case 7: {\n    switch (idx) {\n    case 0:\n    default:\n      return {curAABB.min.x(), curAABB.min.y(), curAABB.min.z(), center.x(), center.y(), center.z()};\n    case 1:\n      return {center.x(), curAABB.min.y(), curAABB.min.z(), curAABB.max.x(), center.y(), center.z()};\n    case 2:\n      return {curAABB.min.x(), center.y(), curAABB.min.z(), center.x(), curAABB.max.y(), center.z()};\n    case 3:\n      return {center.x(), center.y(), curAABB.min.z(), curAABB.max.x(), curAABB.max.y(), center.z()};\n    case 4:\n      return {curAABB.min.x(), curAABB.min.y(), center.z(), center.x(), center.y(), curAABB.max.z()};\n    case 5:\n      return {center.x(), curAABB.min.y(), center.z(), curAABB.max.x(), center.y(), curAABB.max.z()};\n    case 6:\n      return {curAABB.min.x(), center.y(), center.z(), center.x(), curAABB.max.y(), curAABB.max.z()};\n    case 7:\n      return {center.x(), center.y(), center.z(), curAABB.max.x(), curAABB.max.y(), curAABB.max.z()};\n    }\n  }\n  }\n}\n\nvoid CAreaRenderOctTree::Node::RecursiveBuildOverlaps(u32* bmpOut, const CAreaRenderOctTree& parent,\n                                                      const zeus::CAABox& curAABB, const zeus::CAABox& testAABB) const {\n  if (testAABB.intersects(curAABB)) {\n    u32 childCount = GetChildCount(); // HACK: Always return the smallest set of intersections\n    if (curAABB.inside(testAABB) || childCount == 0) {\n      const u32* bmp = &parent.x30_bitmaps[x0_bitmapIdx * parent.x14_bitmapWordCount];\n      for (u32 c = 0; c < parent.x14_bitmapWordCount; ++c)\n        bmpOut[c] |= bmp[c];\n    } else {\n      for (u32 c = 0; c < childCount; ++c) {\n        zeus::CAABox childAABB = GetNodeBounds(curAABB, c);\n        reinterpret_cast<const Node*>(parent.x38_entries + parent.x34_indirectionTable[x4_children[c]])\n            ->RecursiveBuildOverlaps(bmpOut, parent, childAABB, testAABB);\n      }\n    }\n  }\n}\n\nvoid CAreaRenderOctTree::FindOverlappingModels(std::vector<u32>& out, const zeus::CAABox& testAABB) const {\n  out.resize(x14_bitmapWordCount);\n  reinterpret_cast<const Node*>(x38_entries + x34_indirectionTable[0])\n      ->RecursiveBuildOverlaps(out.data(), *this, x18_aabb, testAABB);\n}\n\nvoid CAreaRenderOctTree::FindOverlappingModels(u32* out, const zeus::CAABox& testAABB) const {\n  reinterpret_cast<const Node*>(x38_entries + x34_indirectionTable[0])\n      ->RecursiveBuildOverlaps(out, *this, x18_aabb, testAABB);\n}\n\nvoid CGameArea::CAreaFog::SetCurrent() const {\n  g_Renderer->SetWorldFog(x0_fogMode, x4_rangeCur[0], x4_rangeCur[1], x1c_colorCur);\n}\n\nvoid CGameArea::CAreaFog::Update(float dt) {\n  if (x0_fogMode == ERglFogMode::None)\n    return;\n  if (x1c_colorCur == x28_colorTarget && x4_rangeCur == xc_rangeTarget)\n    return;\n\n  float colorDelta = x34_colorDelta * dt;\n  zeus::CVector2f rangeDelta = x14_rangeDelta * dt;\n\n  for (u32 i = 0; i < 3; ++i) {\n    float delta = x28_colorTarget[i] - x1c_colorCur[i];\n    if (std::fabs(delta) <= colorDelta) {\n      x1c_colorCur[i] = float(x28_colorTarget[i]);\n    } else {\n      if (delta < 0.f)\n        x1c_colorCur[i] -= colorDelta;\n      else\n        x1c_colorCur[i] += colorDelta;\n    }\n  }\n\n  for (u32 i = 0; i < 2; ++i) {\n    float delta = xc_rangeTarget[i] - x4_rangeCur[i];\n    if (std::fabs(delta) <= rangeDelta[i]) {\n      x4_rangeCur[i] = float(xc_rangeTarget[i]);\n    } else {\n      if (delta < 0.f)\n        x4_rangeCur[i] -= rangeDelta[i];\n      else\n        x4_rangeCur[i] += rangeDelta[i];\n    }\n  }\n}\n\nvoid CGameArea::CAreaFog::RollFogOut(float rangeDelta, float colorDelta, const zeus::CColor& color) {\n  x14_rangeDelta = {rangeDelta, rangeDelta * 2.f};\n  xc_rangeTarget = {4096.f, 4096.f};\n  x34_colorDelta = colorDelta;\n  x28_colorTarget = color;\n}\n\nvoid CGameArea::CAreaFog::FadeFog(ERglFogMode mode, const zeus::CColor& color, const zeus::CVector2f& vec1, float speed,\n                                  const zeus::CVector2f& vec2) {\n  if (x0_fogMode == ERglFogMode::None) {\n    x1c_colorCur = color;\n    x28_colorTarget = color;\n    x4_rangeCur = {vec1[1], vec1[1]};\n    xc_rangeTarget = vec1;\n  } else {\n    x28_colorTarget = color;\n    xc_rangeTarget = vec1;\n  }\n  x0_fogMode = mode;\n  x34_colorDelta = speed;\n  x14_rangeDelta = vec2;\n}\n\nvoid CGameArea::CAreaFog::SetFogExplicit(ERglFogMode mode, const zeus::CColor& color, const zeus::CVector2f& range) {\n  x0_fogMode = mode;\n  x1c_colorCur = color;\n  x28_colorTarget = color;\n  x4_rangeCur = range;\n  xc_rangeTarget = range;\n}\n\nbool CGameArea::CAreaFog::IsFogDisabled() const { return x0_fogMode == ERglFogMode::None; }\n\nvoid CGameArea::CAreaFog::DisableFog() { x0_fogMode = ERglFogMode::None; }\n\nstatic std::vector<SObjectTag> ReadDependencyList(CInputStream& in) {\n  std::vector<SObjectTag> ret;\n  const u32 count = in.ReadLong();\n  ret.reserve(count);\n  for (u32 i = 0; i < count; ++i) {\n    ret.emplace_back().ReadMLVL(in);\n  }\n  return ret;\n}\n\nstd::pair<std::unique_ptr<u8[]>, s32> GetScriptingMemoryAlways(const IGameArea& area) {\n  const SObjectTag tag = {SBIG('MREA'), area.IGetAreaAssetId()};\n  std::unique_ptr<u8[]> data = g_ResFactory->LoadNewResourcePartSync(tag, 0, 96);\n\n  u32 magic{};\n  std::memcpy(&magic, data.get(), sizeof(u32));\n  if (magic != SBIG(0xDEADBEEF)) {\n    return {};\n  }\n\n  CMemoryInStream r(data.get() + 4, 96 - 4);\n  u32 version = r.ReadLong();\n  if ((version & 0x10000) == 0) {\n    spdlog::fatal(\"Attempted to load non-URDE MREA\");\n  }\n  version &= ~0x10000;\n\n  SMREAHeader header;\n  header.version = (version >= 12 && version <= 15) ? version : 0;\n  if (!header.version) {\n    return {};\n  }\n\n  header.xf = r.Get<zeus::CTransform>();\n  header.modelCount = r.ReadLong();\n  header.secCount = r.ReadLong();\n  header.geomSecIdx = r.ReadLong();\n  header.sclySecIdx = r.ReadLong();\n  header.collisionSecIdx = r.ReadLong();\n  header.unkSecIdx = r.ReadLong();\n  header.lightSecIdx = r.ReadLong();\n  header.visiSecIdx = r.ReadLong();\n  header.pathSecIdx = r.ReadLong();\n  header.arotSecIdx = r.ReadLong();\n\n  u32 dataLen = ROUND_UP_32(header.secCount * 4);\n\n  data = g_ResFactory->LoadNewResourcePartSync(tag, 96, dataLen);\n\n  r = CMemoryInStream(data.get(), dataLen);\n\n  std::vector<u32> secSizes(header.secCount);\n  u32 lastSize;\n  for (u32 i = 0; i < header.secCount; ++i) {\n    lastSize = r.ReadLong();\n    secSizes.push_back(lastSize);\n  }\n\n  // TODO: Finish\n  return {};\n}\n\nCDummyGameArea::CDummyGameArea(CInputStream& in, int idx, int mlvlVersion) {\n  x8_nameSTRG = in.Get<CAssetId>();\n  x14_transform = in.Get<zeus::CTransform>();\n  zeus::CAABox aabb = in.Get<zeus::CAABox>();\n  xc_mrea = in.Get<CAssetId>();\n  if (mlvlVersion > 15) {\n    x10_areaId = in.ReadLong();\n  } else {\n    x10_areaId = -1;\n  }\n\n  u32 attachAreaCount = in.ReadLong();\n  x44_attachedAreaIndices.reserve(attachAreaCount);\n  for (u32 i = 0; i < attachAreaCount; ++i)\n    x44_attachedAreaIndices.push_back(in.ReadShort());\n\n  ::metaforce::ReadDependencyList(in);\n  ::metaforce::ReadDependencyList(in);\n\n  if (mlvlVersion > 13) {\n    u32 depCount = in.ReadLong();\n    for (u32 i = 0; i < depCount; ++i)\n      in.ReadLong();\n  }\n\n  u32 dockCount = in.ReadLong();\n  x54_docks.reserve(dockCount);\n  for (u32 i = 0; i < dockCount; ++i)\n    x54_docks.emplace_back(in, x14_transform);\n}\n\nstd::pair<std::unique_ptr<u8[]>, s32> CDummyGameArea::IGetScriptingMemoryAlways() const {\n  return GetScriptingMemoryAlways(*this);\n}\n\ns32 CDummyGameArea::IGetAreaSaveId() const { return x10_areaId; }\n\nCAssetId CDummyGameArea::IGetAreaAssetId() const { return xc_mrea; }\n\nbool CDummyGameArea::IIsActive() const { return true; }\n\nTAreaId CDummyGameArea::IGetAttachedAreaId(int idx) const { return x44_attachedAreaIndices[idx]; }\n\nu32 CDummyGameArea::IGetNumAttachedAreas() const { return x44_attachedAreaIndices.size(); }\n\nCAssetId CDummyGameArea::IGetStringTableAssetId() const { return x8_nameSTRG; }\n\nconst zeus::CTransform& CDummyGameArea::IGetTM() const { return x14_transform; }\n\nCGameArea::CGameArea(CInputStream& in, int idx, int mlvlVersion) : x4_selfIdx(idx) {\n  x8_nameSTRG = in.Get<CAssetId>();\n  xc_transform = in.Get<zeus::CTransform>();\n  x3c_invTransform = xc_transform.inverse();\n  x6c_aabb = in.Get<zeus::CAABox>();\n\n  x84_mrea = in.Get<CAssetId>();\n  if (mlvlVersion > 15)\n    x88_areaId = in.ReadLong();\n  else\n    x88_areaId = INT_MAX;\n\n  const u32 attachedCount = in.ReadLong();\n  x8c_attachedAreaIndices.reserve(attachedCount);\n  for (u32 i = 0; i < attachedCount; ++i) {\n    x8c_attachedAreaIndices.emplace_back(in.ReadShort());\n  }\n\n  x9c_deps1 = metaforce::ReadDependencyList(in);\n  xac_deps2 = metaforce::ReadDependencyList(in);\n\n  const zeus::CAABox aabb = x6c_aabb.getTransformedAABox(xc_transform);\n  x6c_aabb = aabb;\n\n  if (mlvlVersion > 13) {\n    const u32 depCount = in.ReadLong();\n    xbc_layerDepOffsets.reserve(depCount);\n    for (u32 i = 0; i < depCount; ++i) {\n      xbc_layerDepOffsets.emplace_back(in.ReadLong());\n    }\n  }\n\n  const u32 dockCount = in.ReadLong();\n  xcc_docks.reserve(dockCount);\n  for (u32 i = 0; i < dockCount; ++i) {\n    xcc_docks.emplace_back(in, xc_transform);\n  }\n\n  ClearTokenList();\n\n  for (CToken& tok : xdc_tokens)\n    xec_totalResourcesSize += g_ResFactory->ResourceSize(*tok.GetObjectTag());\n\n  xec_totalResourcesSize += g_ResFactory->ResourceSize(SObjectTag{FOURCC('MREA'), x84_mrea});\n}\n\nCGameArea::~CGameArea() {\n  for (auto& lt : xf8_loadTransactions)\n    lt->PostCancelRequest();\n\n  if (xf0_24_postConstructed)\n    RemoveStaticGeometry();\n  else\n    while (!Invalidate(nullptr)) {}\n}\n\nbool CGameArea::IsFinishedOccluding() const {\n  if (x12c_postConstructed->x10dc_occlusionState != EOcclusionState::Occluded)\n    return true;\n\n  return x12c_postConstructed->x1108_27_;\n}\n\nstd::pair<std::unique_ptr<u8[]>, s32> CGameArea::IGetScriptingMemoryAlways() const {\n  return GetScriptingMemoryAlways(*this);\n}\n\nbool CGameArea::IIsActive() const { return xf0_25_active; }\n\nTAreaId CGameArea::IGetAttachedAreaId(int idx) const { return x8c_attachedAreaIndices[idx]; }\n\nu32 CGameArea::IGetNumAttachedAreas() const { return x8c_attachedAreaIndices.size(); }\n\nCAssetId CGameArea::IGetStringTableAssetId() const { return x8_nameSTRG; }\n\nconst zeus::CTransform& CGameArea::IGetTM() const { return xc_transform; }\n\nvoid CGameArea::SetLoadPauseState(bool paused) {\n  if (xf0_26_tokensReady)\n    return;\n  xf0_27_loadPaused = paused;\n  if (!paused)\n    return;\n\n  for (CToken& tok : xdc_tokens)\n    if (!tok.IsLoaded())\n      tok.Unlock();\n}\n\nvoid CGameArea::SetXRaySpeedAndTarget(float f1, float f2) {\n  x12c_postConstructed->x112c_xraySpeed = f1;\n  x12c_postConstructed->x1130_xrayTarget = f2;\n}\n\nvoid CGameArea::SetThermalSpeedAndTarget(float speed, float target) {\n  x12c_postConstructed->x1120_thermalSpeed = speed;\n  x12c_postConstructed->x1124_thermalTarget = target;\n}\n\nvoid CGameArea::SetWeaponWorldLighting(float speed, float target) {\n  x12c_postConstructed->x1134_weaponWorldLightingSpeed = speed;\n  x12c_postConstructed->x1138_weaponWorldLightingTarget = target;\n}\n\nfloat CGameArea::GetXRayFogDistance() const {\n  const CScriptAreaAttributes* attrs = x12c_postConstructed->x10d8_areaAttributes;\n  if (attrs)\n    return attrs->GetXRayFogDistance();\n  return 1.f;\n}\n\nEEnvFxType CGameArea::DoesAreaNeedEnvFx() const {\n  const CPostConstructed* postConstructed = GetPostConstructed();\n  if (!postConstructed)\n    return EEnvFxType::None;\n\n  const CScriptAreaAttributes* attrs = postConstructed->x10d8_areaAttributes;\n  if (!attrs)\n    return EEnvFxType::None;\n  if (postConstructed->x10dc_occlusionState == EOcclusionState::Occluded)\n    return EEnvFxType::None;\n  return attrs->GetEnvFxType();\n}\n\nbool CGameArea::DoesAreaNeedSkyNow() const {\n  const CPostConstructed* postConstructed = GetPostConstructed();\n  if (!postConstructed)\n    return false;\n\n  const CScriptAreaAttributes* attrs = postConstructed->x10d8_areaAttributes;\n  if (!attrs)\n    return false;\n\n  return attrs->GetNeedsSky();\n}\n\nvoid CGameArea::UpdateFog(float dt) {\n  CAreaFog* fog = GetPostConstructed()->x10c4_areaFog.get();\n  if (fog)\n    fog->Update(dt);\n}\n\nvoid CGameArea::OtherAreaOcclusionChanged() {\n  if (GetPostConstructed()->x10e0_ == 3 && GetPostConstructed()->x10dc_occlusionState == EOcclusionState::Visible) {\n    x12c_postConstructed->x1108_27_ = false;\n  } else if (GetPostConstructed()->x10dc_occlusionState == EOcclusionState::Visible) {\n    ReloadAllUnloadedTextures();\n  }\n}\n\nvoid CGameArea::PingOcclusionState() {\n  if (GetOcclusionState() == EOcclusionState::Occluded && GetPostConstructed()->x10e0_ < 2) {\n    x12c_postConstructed->x10e0_ += 1;\n    return;\n  }\n\n  x12c_postConstructed->x10e0_ = 3;\n  if (!x12c_postConstructed->x1108_27_) {\n    bool unloaded = true;\n    bool transferred = true;\n#if 0\n        unloaded = UnloadAllLoadedTextures();\n        transferred = TransferTokens();\n#endif\n    if (unloaded && transferred)\n      x12c_postConstructed->x1108_27_ = true;\n  }\n  x12c_postConstructed->x1108_26_ = true;\n}\n\nvoid CGameArea::PreRender() {\n  if (!xf0_24_postConstructed)\n    return;\n\n  if (x12c_postConstructed->x1108_28_occlusionPinged)\n    x12c_postConstructed->x1108_28_occlusionPinged = false;\n  else\n    PingOcclusionState();\n}\n\nvoid CGameArea::UpdateThermalVisor(float dt) {\n  if (x12c_postConstructed->x1120_thermalSpeed == 0.f)\n    return;\n\n  float influence = x12c_postConstructed->x111c_thermalCurrent;\n\n  float delta = x12c_postConstructed->x1120_thermalSpeed * dt;\n  if (std::fabs(x12c_postConstructed->x1124_thermalTarget - x12c_postConstructed->x111c_thermalCurrent) < delta) {\n    influence = x12c_postConstructed->x1124_thermalTarget;\n    x12c_postConstructed->x1120_thermalSpeed = 0.f;\n  } else if (x12c_postConstructed->x1124_thermalTarget < influence)\n    influence -= delta;\n  else\n    influence += delta;\n\n  x12c_postConstructed->x111c_thermalCurrent = influence;\n}\n\nvoid CGameArea::UpdateWeaponWorldLighting(float dt) {\n  float newLightingLevel = x12c_postConstructed->x1128_worldLightingLevel;\n  if (x12c_postConstructed->x112c_xraySpeed != 0.f) {\n    float speed = dt * x12c_postConstructed->x112c_xraySpeed;\n    if (std::fabs(x12c_postConstructed->x1130_xrayTarget - newLightingLevel) < speed) {\n      newLightingLevel = x12c_postConstructed->x1130_xrayTarget;\n      x12c_postConstructed->x1134_weaponWorldLightingSpeed = 0.f;\n    } else if (x12c_postConstructed->x1130_xrayTarget < newLightingLevel) {\n      newLightingLevel -= speed;\n    } else {\n      newLightingLevel += speed;\n    }\n  }\n\n  if (x12c_postConstructed->x1134_weaponWorldLightingSpeed != 0.f) {\n    float newWeaponWorldLightingLevel = x12c_postConstructed->x1128_worldLightingLevel;\n    float speed = dt * x12c_postConstructed->x1134_weaponWorldLightingSpeed;\n    if (std::fabs(x12c_postConstructed->x1138_weaponWorldLightingTarget - newLightingLevel) < speed) {\n      newWeaponWorldLightingLevel = x12c_postConstructed->x1138_weaponWorldLightingTarget;\n      x12c_postConstructed->x1134_weaponWorldLightingSpeed = 0.f;\n    } else if (x12c_postConstructed->x1138_weaponWorldLightingTarget < newWeaponWorldLightingLevel) {\n      newWeaponWorldLightingLevel -= speed;\n    } else {\n      newWeaponWorldLightingLevel += speed;\n    }\n    if (x12c_postConstructed->x112c_xraySpeed != 0.f) {\n      newLightingLevel = std::min(newLightingLevel, newWeaponWorldLightingLevel);\n    } else {\n      newLightingLevel = newWeaponWorldLightingLevel;\n    }\n  }\n\n  if (std::fabs(x12c_postConstructed->x1128_worldLightingLevel - newLightingLevel) >= 0.00001f) {\n    x12c_postConstructed->x1128_worldLightingLevel = newLightingLevel;\n    for (CEntity* ent : *x12c_postConstructed->x10c0_areaObjs)\n      if (TCastToPtr<CActor> act = ent)\n        act->SetWorldLightingDirty(true);\n  }\n}\n\nvoid CGameArea::AliveUpdate(float dt) {\n  if (x12c_postConstructed->x10dc_occlusionState == EOcclusionState::Occluded)\n    x12c_postConstructed->x10e4_occludedTime += dt;\n  else\n    x12c_postConstructed->x10e4_occludedTime = 0.f;\n  UpdateFog(dt);\n  UpdateThermalVisor(dt);\n  UpdateWeaponWorldLighting(dt);\n}\n\nvoid CGameArea::SetOcclusionState(EOcclusionState state) {\n  if (!xf0_24_postConstructed || x12c_postConstructed->x10dc_occlusionState == state)\n    return;\n\n  if (state != EOcclusionState::Occluded) {\n    ReloadAllUnloadedTextures();\n    AddStaticGeometry();\n  } else {\n    x12c_postConstructed->x1108_26_ = true;\n    x12c_postConstructed->x1108_27_ = false;\n    RemoveStaticGeometry();\n  }\n}\n\nvoid CGameArea::RemoveStaticGeometry() {\n  if (!xf0_24_postConstructed || !x12c_postConstructed ||\n      x12c_postConstructed->x10dc_occlusionState == EOcclusionState::Occluded)\n    return;\n  x12c_postConstructed->x10e0_ = 0;\n  x12c_postConstructed->x10dc_occlusionState = EOcclusionState::Occluded;\n  g_Renderer->RemoveStaticGeometry(&x12c_postConstructed->x4c_insts);\n}\n\nvoid CGameArea::AddStaticGeometry() {\n  if (x12c_postConstructed->x10dc_occlusionState != EOcclusionState::Visible) {\n    x12c_postConstructed->x10e0_ = 0;\n    x12c_postConstructed->x10dc_occlusionState = EOcclusionState::Visible;\n    if (!x12c_postConstructed->x1108_25_modelsConstructed)\n      FillInStaticGeometry();\n    g_Renderer->AddStaticGeometry(&x12c_postConstructed->x4c_insts,\n                                  x12c_postConstructed->xc_octTree ? &*x12c_postConstructed->xc_octTree : nullptr,\n                                  x4_selfIdx);\n  }\n}\n\nEChain CGameArea::SetChain(CGameArea* next, EChain setChain) {\n  if (x138_curChain == setChain)\n    return x138_curChain;\n\n  if (x134_prev)\n    x134_prev->x130_next = x130_next;\n  if (x130_next)\n    x130_next->x134_prev = x134_prev;\n\n  x134_prev = nullptr;\n  x130_next = next;\n  if (next)\n    next->x134_prev = this;\n\n  EChain ret = x138_curChain;\n  x138_curChain = setChain;\n  return ret;\n}\n\nbool CGameArea::StartStreamingMainArea() {\n  if (xf0_24_postConstructed)\n    return false;\n\n  switch (xf4_phase) {\n  case EPhase::LoadHeader: {\n    x110_mreaSecBufs.reserve(3);\n    AllocNewAreaData(0, 96);\n    x12c_postConstructed = std::make_unique<CPostConstructed>();\n    xf4_phase = EPhase::LoadSecSizes;\n    break;\n  }\n  case EPhase::LoadSecSizes: {\n    CullDeadAreaRequests();\n    if (xf8_loadTransactions.size())\n      break;\n    SMREAHeader header = VerifyHeader();\n    AllocNewAreaData(x110_mreaSecBufs[0].second, ROUND_UP_32(header.secCount * 4));\n    xf4_phase = EPhase::ReserveSections;\n    break;\n  }\n  case EPhase::ReserveSections: {\n    CullDeadAreaRequests();\n    if (xf8_loadTransactions.size())\n      break;\n    // x110_mreaSecBufs.reserve(GetNumPartSizes() + 2);\n    x124_secCount = 0;\n    x128_mreaDataOffset = x110_mreaSecBufs[0].second + x110_mreaSecBufs[1].second;\n    xf4_phase = EPhase::LoadDataSections;\n    break;\n  }\n  case EPhase::LoadDataSections: {\n    CullDeadAreaRequests();\n\n    u32 totalSz = 0;\n    u32 secCount = GetNumPartSizes();\n    for (u32 i = 0; i < secCount; ++i)\n      totalSz += CBasics::SwapBytes(reinterpret_cast<u32*>(x110_mreaSecBufs[1].first.get())[i]);\n\n    AllocNewAreaData(x128_mreaDataOffset, totalSz);\n\n    m_resolvedBufs.reserve(secCount);\n    m_resolvedBufs.emplace_back(x110_mreaSecBufs[0].first.get(), x110_mreaSecBufs[0].second);\n    m_resolvedBufs.emplace_back(x110_mreaSecBufs[1].first.get(), x110_mreaSecBufs[1].second);\n\n    u32 curOff = 0;\n    for (u32 i = 0; i < secCount; ++i) {\n      u32 size = CBasics::SwapBytes(reinterpret_cast<u32*>(x110_mreaSecBufs[1].first.get())[i]);\n      m_resolvedBufs.emplace_back(x110_mreaSecBufs[2].first.get() + curOff, size);\n      curOff += size;\n    }\n\n    xf4_phase = EPhase::WaitForFinish;\n    break;\n  }\n  case EPhase::WaitForFinish: {\n    CullDeadAreaRequests();\n    if (xf8_loadTransactions.size())\n      break;\n    return false;\n  }\n  default:\n    break;\n  }\n\n  return true;\n}\n\nvoid CGameArea::ReloadAllUnloadedTextures() {}\n\nu32 CGameArea::GetNumPartSizes() const {\n  u32 value{};\n  std::memcpy(&value, x110_mreaSecBufs[0].first.get() + 60, sizeof(u32));\n  return CBasics::SwapBytes(value);\n}\n\nvoid CGameArea::AllocNewAreaData(int offset, int size) {\n  x110_mreaSecBufs.emplace_back(std::unique_ptr<u8[]>(new u8[size]), size);\n  xf8_loadTransactions.push_back(g_ResFactory->LoadResourcePartAsync(SObjectTag{FOURCC('MREA'), x84_mrea}, offset, size,\n                                                                     x110_mreaSecBufs.back().first.get()));\n}\n\nbool CGameArea::Invalidate(CStateManager* mgr) {\n  if (!xf0_24_postConstructed) {\n    ClearTokenList();\n\n    for (auto it = xf8_loadTransactions.begin(); it != xf8_loadTransactions.end();) {\n      if (!(*it)->IsComplete()) {\n        (*it)->PostCancelRequest();\n        ++it;\n        continue;\n      }\n      it = xf8_loadTransactions.erase(it);\n    }\n    if (xf8_loadTransactions.size() != 0)\n      return false;\n\n    x12c_postConstructed.reset();\n    KillmAreaData();\n\n    return true;\n  }\n\n  if (mgr)\n    mgr->PrepareAreaUnload(GetAreaId());\n\n#if 0\n    dword_805a8eb0 -= GetPostConstructedSize();\n#endif\n  RemoveStaticGeometry();\n  x12c_postConstructed.reset();\n  xf0_24_postConstructed = false;\n  xf0_28_validated = false;\n  xf4_phase = EPhase::LoadHeader;\n  xf8_loadTransactions.clear();\n  CullDeadAreaRequests();\n  KillmAreaData();\n  ClearTokenList();\n  if (mgr)\n    mgr->AreaUnloaded(GetAreaId());\n\n  return true;\n}\n\nvoid CGameArea::KillmAreaData() {\n  m_resolvedBufs.clear();\n  x110_mreaSecBufs.clear();\n}\n\nvoid CGameArea::CullDeadAreaRequests() {\n  for (auto it = xf8_loadTransactions.begin(); it != xf8_loadTransactions.end();) {\n    if ((*it)->IsComplete()) {\n      it = xf8_loadTransactions.erase(it);\n      continue;\n    }\n    ++it;\n  }\n}\n\nvoid CGameArea::StartStreamIn(CStateManager& mgr) {\n  if (xf0_24_postConstructed || xf0_27_loadPaused)\n    return;\n\n  //OPTICK_EVENT();\n  VerifyTokenList(mgr);\n\n  if (!xf0_26_tokensReady) {\n    u32 notLoaded = 0;\n    for (CToken& tok : xdc_tokens) {\n      tok.Lock();\n      if (!tok.IsLoaded())\n        ++notLoaded;\n    }\n    if (notLoaded)\n      return;\n    xf0_26_tokensReady = true;\n  }\n\n  StartStreamingMainArea();\n  if (xf4_phase != EPhase::WaitForFinish)\n    return;\n  CullDeadAreaRequests();\n  if (xf8_loadTransactions.size())\n    return;\n  Validate(mgr);\n}\n\nvoid CGameArea::Validate(CStateManager& mgr) {\n  if (xf0_24_postConstructed)\n    return;\n\n  while (StartStreamingMainArea()) {}\n\n  for (auto& req : xf8_loadTransactions)\n    req->WaitUntilComplete();\n\n  if (xdc_tokens.empty()) {\n    VerifyTokenList(mgr);\n    for (CToken& tok : xdc_tokens)\n      tok.Lock();\n    for (CToken& tok : xdc_tokens)\n      tok.GetObj();\n    xf0_26_tokensReady = true;\n  }\n\n  xf8_loadTransactions.clear();\n\n  PostConstructArea();\n  if (x4_selfIdx != kInvalidAreaId)\n    mgr.GetWorld()->MoveAreaToAliveChain(x4_selfIdx);\n\n  LoadScriptObjects(mgr);\n\n  const CPVSAreaSet* pvs = x12c_postConstructed->xa0_pvs.get();\n  if (pvs && x12c_postConstructed->x1108_29_pvsHasActors) {\n    for (size_t i = 0; i < pvs->GetNumActors(); ++i) {\n      const TEditorId entId = pvs->GetEntityIdByIndex(i) | (x4_selfIdx << 16);\n      const TUniqueId id = mgr.GetIdForScript(entId);\n\n      if (id == kInvalidUniqueId) {\n        continue;\n      }\n\n      CPostConstructed::MapEntry& ent = x12c_postConstructed->xa8_pvsEntityMap[id.Value()];\n      ent.x0_id = static_cast<s16>(i + (pvs->GetNumFeatures() - pvs->GetNumActors()));\n      ent.x4_uid = id;\n    }\n  }\n\n  xf0_28_validated = true;\n  mgr.AreaLoaded(x4_selfIdx);\n}\n\nvoid CGameArea::LoadScriptObjects(CStateManager& mgr) {\n  CScriptLayerManager& layerState = *mgr.WorldLayerState();\n  u32 layerCount = layerState.GetAreaLayerCount(x4_selfIdx);\n  std::vector<TEditorId> objIds;\n  for (u32 i = 0; i < layerCount; ++i) {\n    if (layerState.IsLayerActive(x4_selfIdx, i)) {\n      auto layerBuf = GetLayerScriptBuffer(i);\n      CMemoryInStream r(layerBuf.first, layerBuf.second);\n      mgr.LoadScriptObjects(x4_selfIdx, r, objIds);\n    }\n  }\n  mgr.InitScriptObjects(objIds);\n}\n\nstd::pair<const u8*, u32> CGameArea::GetLayerScriptBuffer(int layer) const {\n  if (!xf0_24_postConstructed)\n    return {};\n  return x12c_postConstructed->x110c_layerPtrs[layer];\n}\n\nvoid CGameArea::PostConstructArea() {\n  SMREAHeader header = VerifyHeader();\n\n  /* Materials */\n  x12c_postConstructed->x10ec_firstMatSection = 2;\n\n  /* Models */\n  auto secIt = m_resolvedBufs.begin() + 3;\n  x12c_postConstructed->x4c_insts.resize(header.modelCount);\n  for (u32 i = 0; i < header.modelCount; ++i) {\n    u32 surfCount = CBasics::SwapBytes(*reinterpret_cast<const u32*>((secIt + 6)->data()));\n    secIt += 7 + surfCount;\n  }\n\n  /* Render octree */\n  if (header.version > 14 && header.arotSecIdx != -1) {\n    x12c_postConstructed->xc_octTree.emplace(secIt->data());\n    ++secIt;\n  }\n\n  /* Scriptable layer section */\n  x12c_postConstructed->x10c8_sclyBuf = secIt->data();\n  x12c_postConstructed->x10d0_sclySize = secIt->size_bytes();\n  ++secIt;\n\n  /* Collision section */\n  x12c_postConstructed->x0_collision = CAreaOctTree::MakeFromMemory(secIt->data(), secIt->size_bytes());\n  ++secIt;\n\n  /* Unknown section */\n  ++secIt;\n\n  /* Lights section */\n  if (header.version > 6) {\n    CMemoryInStream r(secIt->data(), secIt->size_bytes(), CMemoryInStream::EOwnerShip::NotOwned);\n    u32 magic = r.ReadLong();\n    u32 aCount = magic;\n    if (magic == 0xBABEDEAD) {\n      aCount = r.ReadLong();\n    }\n    x12c_postConstructed->x60_lightsA.reserve(aCount);\n    x12c_postConstructed->x70_gfxLightsA.reserve(aCount);\n    for (u32 i = 0; i < aCount; ++i) {\n      x12c_postConstructed->x60_lightsA.emplace_back(r);\n      x12c_postConstructed->x70_gfxLightsA.push_back(x12c_postConstructed->x60_lightsA.back().GetAsCGraphicsLight());\n    }\n\n    if (magic == 0xBABEDEAD) {\n      u32 bCount = r.ReadLong();\n      x12c_postConstructed->x80_lightsB.reserve(bCount);\n      x12c_postConstructed->x90_gfxLightsB.reserve(bCount);\n      for (u32 i = 0; i < bCount; ++i) {\n        x12c_postConstructed->x80_lightsB.emplace_back(r);\n        x12c_postConstructed->x90_gfxLightsB.push_back(x12c_postConstructed->x80_lightsB.back().GetAsCGraphicsLight());\n      }\n    }\n\n    if (x12c_postConstructed->x80_lightsB.empty()) {\n      x12c_postConstructed->x80_lightsB = x12c_postConstructed->x60_lightsA;\n      x12c_postConstructed->x90_gfxLightsB = x12c_postConstructed->x70_gfxLightsA;\n    }\n\n    ++secIt;\n  }\n\n  /* PVS section */\n  if (header.version > 7) {\n    CMemoryInStream r(secIt->data(), secIt->size_bytes(), CMemoryInStream::EOwnerShip::NotOwned);\n    if (secIt->size() > 0) { // TODO this works around CMemoryInStream inf loop on 0 len\n      u32 magic = r.ReadLong();\n      if (magic == 'VISI') {\n        x12c_postConstructed->x10a8_pvsVersion = r.ReadLong();\n        if (x12c_postConstructed->x10a8_pvsVersion == 2) {\n          x12c_postConstructed->x1108_29_pvsHasActors = r.ReadBool();\n          x12c_postConstructed->x1108_30_ = r.ReadBool();\n          x12c_postConstructed->xa0_pvs =\n              std::make_unique<CPVSAreaSet>(secIt->data() + r.GetReadPosition(), secIt->size_bytes() - r.GetReadPosition());\n        }\n      }\n    }\n\n    ++secIt;\n  }\n\n  /* Pathfinding section */\n  if (header.version > 9) {\n    CMemoryInStream r(secIt->data(), secIt->size_bytes(), CMemoryInStream::EOwnerShip::NotOwned);\n    CAssetId pathId = r.Get<CAssetId>();\n    x12c_postConstructed->x10ac_pathToken = g_SimplePool->GetObj(SObjectTag{FOURCC('PATH'), pathId});\n    x12c_postConstructed->x10bc_pathArea = x12c_postConstructed->x10ac_pathToken.GetObj();\n    x12c_postConstructed->x10bc_pathArea->SetTransform(xc_transform);\n    ++secIt;\n  }\n\n  x12c_postConstructed->x10c0_areaObjs = std::make_unique<CAreaObjectList>(x4_selfIdx);\n  x12c_postConstructed->x10c4_areaFog = std::make_unique<CAreaFog>();\n\n  /* URDE addition: preemptively fill in area models so shaders may be polled for completion */\n//  if (!x12c_postConstructed->x1108_25_modelsConstructed)\n//    FillInStaticGeometry();\n\n  xf0_24_postConstructed = true;\n\n  /* Resolve layer pointers */\n  if (x12c_postConstructed->x10c8_sclyBuf != nullptr) {\n    CMemoryInStream r(x12c_postConstructed->x10c8_sclyBuf, x12c_postConstructed->x10d0_sclySize,\n                      CMemoryInStream::EOwnerShip::NotOwned);\n    FourCC magic;\n    r.Get(reinterpret_cast<u8*>(&magic), 4);\n    if (magic == FOURCC('SCLY')) {\n      r.ReadLong();\n      u32 layerCount = r.ReadLong();\n      x12c_postConstructed->x110c_layerPtrs.resize(layerCount);\n      for (u32 l = 0; l < layerCount; ++l)\n        x12c_postConstructed->x110c_layerPtrs[l].second = r.ReadLong();\n      const u8* ptr = x12c_postConstructed->x10c8_sclyBuf + r.GetReadPosition();\n      for (u32 l = 0; l < layerCount; ++l) {\n        x12c_postConstructed->x110c_layerPtrs[l].first = ptr;\n        ptr += x12c_postConstructed->x110c_layerPtrs[l].second;\n      }\n    }\n  }\n}\n\nvoid CGameArea::FillInStaticGeometry() {\n  u32 start = x12c_postConstructed->x10ec_firstMatSection;\n  x12c_postConstructed->x10d4_firstMatPtr = x110_mreaSecBufs[start].first.get();\n\n  // Clear the instances without resizing\n  if (!x12c_postConstructed->x4c_insts.empty()) {\n    for (CMetroidModelInstance& inst : x12c_postConstructed->x4c_insts) {\n      inst = {};\n    }\n  }\n\n  auto iter = m_resolvedBufs.begin() + start + 1;\n  for (auto& inst : x12c_postConstructed->x4c_insts) {\n    auto modelHeader = *iter++;\n    auto positions = *iter++;\n    auto normals = *iter++;\n    auto colors = *iter++;\n    auto texCoords = *iter++;\n    auto packedTexCoords = *iter++;\n    u32 surfaceCount = CBasics::SwapBytes(*reinterpret_cast<const u32*>(iter++->data()));\n    if (surfaceCount != 0) {\n      std::vector<CCubeSurface> surfaces;\n      surfaces.reserve(surfaceCount);\n      for (int idx = 0; idx < surfaceCount; ++idx) {\n        const auto sp = *iter++;\n        surfaces.emplace_back(sp.data(), sp.size());\n      }\n      inst = CMetroidModelInstance{\n          modelHeader,     x12c_postConstructed->x10d4_firstMatPtr,\n          positions,       normals,\n          colors,          texCoords,\n          packedTexCoords, std::move(surfaces),\n      };\n    }\n  }\n\n  x12c_postConstructed->x1108_25_modelsConstructed = true;\n}\n\nvoid CGameArea::VerifyTokenList(CStateManager& stateMgr) {\n  if (xdc_tokens.size())\n    return;\n  ClearTokenList();\n\n  if (xac_deps2.empty())\n    return;\n\n  auto end = xac_deps2.end();\n  for (int lidx = int(xbc_layerDepOffsets.size() - 1); lidx >= 0; --lidx) {\n    auto begin = xac_deps2.begin() + xbc_layerDepOffsets[lidx];\n    if (stateMgr.WorldLayerState()->IsLayerActive(x4_selfIdx, lidx)) {\n      for (auto it = begin; it != end; ++it) {\n        xdc_tokens.push_back(g_SimplePool->GetObj(*it));\n        xdc_tokens.back().Lock();\n      }\n    }\n    end = begin;\n  }\n}\n\nvoid CGameArea::ClearTokenList() {\n  if (xdc_tokens.empty())\n    xdc_tokens.reserve(xac_deps2.size());\n  else\n    xdc_tokens.clear();\n\n  xf0_26_tokensReady = false;\n}\n\nu32 CGameArea::GetPreConstructedSize() const { return 0; }\n\nSMREAHeader CGameArea::VerifyHeader() const {\n  if (x110_mreaSecBufs.empty()) {\n    return {};\n  }\n\n  u32 magic{};\n  std::memcpy(&magic, x110_mreaSecBufs[0].first.get(), sizeof(u32));\n  if (magic != SBIG(0xDEADBEEF)) {\n    return {};\n  }\n\n  CMemoryInStream r(x110_mreaSecBufs[0].first.get() + 4, x110_mreaSecBufs[0].second - 4);\n  u32 version = r.ReadLong();\n  if ((version & 0x10000) != 0) {\n    spdlog::fatal(\"Attempted to load non-retail MREA\");\n  }\n\n  SMREAHeader header;\n  header.version = (version >= 12 && version <= 15) ? version : 0;\n  if (!header.version) {\n    return {};\n  }\n\n  header.xf = r.Get<zeus::CTransform>();\n  header.modelCount = r.ReadLong();\n  header.secCount = r.ReadLong();\n  header.geomSecIdx = r.ReadLong();\n  header.sclySecIdx = r.ReadLong();\n  header.collisionSecIdx = r.ReadLong();\n  header.unkSecIdx = r.ReadLong();\n  header.lightSecIdx = r.ReadLong();\n  header.visiSecIdx = r.ReadLong();\n  header.pathSecIdx = r.ReadLong();\n  header.arotSecIdx = r.ReadLong();\n\n  return header;\n}\n\nTUniqueId CGameArea::LookupPVSUniqueID(TUniqueId id) const {\n  return x12c_postConstructed->xa8_pvsEntityMap[id.Value()].x4_uid;\n}\n\ns16 CGameArea::LookupPVSID(TUniqueId id) const { return x12c_postConstructed->xa8_pvsEntityMap[id.Value()].x0_id; }\n\nvoid CGameArea::SetAreaAttributes(const CScriptAreaAttributes* areaAttributes) {\n  x12c_postConstructed->x10d8_areaAttributes = areaAttributes;\n  if (areaAttributes == nullptr)\n    return;\n\n  x12c_postConstructed->x111c_thermalCurrent = areaAttributes->GetThermalHeat();\n  x12c_postConstructed->x1128_worldLightingLevel = areaAttributes->GetWorldLightingLevel();\n}\n\nbool CGameArea::CAreaObjectList::IsQualified(const CEntity& ent) const {\n  return (ent.GetAreaIdAlways() == x200c_areaIdx);\n}\n\nvoid CGameArea::DebugDraw() {\n  if (!m_debugSphereRes) {\n    const auto* tok = g_ResFactory->GetResourceIdByName(\"CMDL_DebugSphere\");\n    if (tok != nullptr && tok->type == FOURCC('CMDL')) {\n      m_debugSphereRes = CStaticRes(tok->id, zeus::skOne3f);\n    }\n  }\n\n  if (m_debugSphereRes && !m_debugSphereModel) {\n    m_debugSphereModel = std::make_unique<CModelData>(*m_debugSphereRes);\n  }\n\n  if (!m_debugConeRes) {\n    const auto* tok = g_ResFactory->GetResourceIdByName(\"CMDL_DebugLightCone\");\n    if (tok != nullptr && tok->type == FOURCC('CMDL')) {\n      m_debugConeRes = CStaticRes(tok->id, zeus::skOne3f);\n    }\n  }\n\n  if (m_debugConeRes && !m_debugConeModel) {\n    m_debugConeModel = std::make_unique<CModelData>(*m_debugConeRes);\n  }\n\n  if (IsPostConstructed()) {\n    for (const auto& light : x12c_postConstructed->x70_gfxLightsA) {\n      DebugDrawLight(light);\n    }\n    for (const auto& light : x12c_postConstructed->x90_gfxLightsB) {\n      DebugDrawLight(light);\n    }\n  }\n}\n\nvoid CGameArea::DebugDrawLight(const CLight& light) {\n  if (light.GetType() == ELightType::LocalAmbient) {\n    return;\n  }\n  g_Renderer->SetGXRegister1Color(light.GetColor());\n  CModelFlags modelFlags;\n  modelFlags.x0_blendMode = 5;\n  modelFlags.x4_color = zeus::skWhite;\n  modelFlags.x4_color.a() = 0.5f;\n  if ((light.GetType() == ELightType::Spot || light.GetType() == ELightType::Directional) && m_debugConeModel) {\n    m_debugConeModel->Render(CModelData::EWhichModel::Normal,\n                             zeus::lookAt(light.GetPosition(), light.GetPosition() + light.GetDirection()) *\n                                 zeus::CTransform::Scale(zeus::clamp(-90.f, light.GetRadius(), 90.f)),\n                             nullptr, modelFlags);\n  } else if (m_debugSphereModel) {\n    m_debugSphereModel->Render(CModelData::EWhichModel::Normal, zeus::CTransform::Translate(light.GetPosition()),\n                               nullptr, modelFlags);\n    m_debugSphereModel->Render(CModelData::EWhichModel::Normal,\n                               zeus::CTransform::Translate(light.GetPosition()) *\n                                   zeus::CTransform::Scale(light.GetRadius()),\n                               nullptr, modelFlags);\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CGameArea.hpp",
    "content": "#pragma once\n\n#include <array>\n\n#include \"Runtime/CObjectList.hpp\"\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Collision/CAreaOctTree.hpp\"\n#include \"Runtime/Graphics/CGraphics.hpp\"\n#include \"Runtime/Graphics/CMetroidModelInstance.hpp\"\n#include \"Runtime/Graphics/CModel.hpp\"\n#include \"Runtime/Graphics/CPVSAreaSet.hpp\"\n#include \"Runtime/World/CEnvFxManager.hpp\"\n#include \"Runtime/World/CPathFindArea.hpp\"\n#include \"Runtime/World/CWorldLight.hpp\"\n#include \"Runtime/World/IGameArea.hpp\"\n#include \"Runtime/Character/CModelData.hpp\"\n\n#include <list>\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CColor.hpp>\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector2f.hpp>\n\nnamespace metaforce {\nclass CStateManager;\nclass CScriptAreaAttributes;\n\n// Metaforce addition: store byte-swapped data for later use\nstruct SMREAHeader {\n  u32 version = 0;\n  zeus::CTransform xf;\n  u32 modelCount;\n  u32 secCount;\n  u32 geomSecIdx;\n  u32 sclySecIdx;\n  u32 collisionSecIdx;\n  u32 unkSecIdx;\n  u32 lightSecIdx;\n  u32 visiSecIdx;\n  u32 pathSecIdx;\n  u32 arotSecIdx;\n};\n\nclass CDummyGameArea final : public IGameArea {\n  friend class CDummyWorld;\n\n  int x4_mlvlVersion;\n  CAssetId x8_nameSTRG;\n  CAssetId xc_mrea;\n  s32 x10_areaId;\n  zeus::CTransform x14_transform;\n  std::vector<u16> x44_attachedAreaIndices;\n  std::vector<Dock> x54_docks;\n\npublic:\n  CDummyGameArea(CInputStream& in, int idx, int mlvlVersion);\n\n  std::pair<std::unique_ptr<u8[]>, s32> IGetScriptingMemoryAlways() const override;\n  s32 IGetAreaSaveId() const override;\n  CAssetId IGetAreaAssetId() const override;\n  bool IIsActive() const override;\n  TAreaId IGetAttachedAreaId(int) const override;\n  u32 IGetNumAttachedAreas() const override;\n  CAssetId IGetStringTableAssetId() const override;\n  const zeus::CTransform& IGetTM() const override;\n};\n\nstruct CAreaRenderOctTree {\n  struct Node {\n    u16 x0_bitmapIdx;\n    u16 x2_flags;\n    u16 x4_children[];\n\n    u32 GetChildCount() const;\n    zeus::CAABox GetNodeBounds(const zeus::CAABox& curAABB, int idx) const;\n\n    void RecursiveBuildOverlaps(u32* out, const CAreaRenderOctTree& parent, const zeus::CAABox& curAABB,\n                                const zeus::CAABox& testAABB) const;\n  };\n\n  const u8* x0_buf;\n  u32 x8_bitmapCount;\n  u32 xc_meshCount;\n  u32 x10_nodeCount;\n  u32 x14_bitmapWordCount;\n  zeus::CAABox x18_aabb;\n  const u32* x30_bitmaps;\n  const u32* x34_indirectionTable;\n  const u8* x38_entries;\n\n  explicit CAreaRenderOctTree(const u8* buf);\n\n  void FindOverlappingModels(std::vector<u32>& out, const zeus::CAABox& testAABB) const;\n  void FindOverlappingModels(u32* out, const zeus::CAABox& testAABB) const;\n};\n\nclass CGameArea final : public IGameArea {\n  friend class CWorld;\n  friend class CStateManager;\n\n  TAreaId x4_selfIdx;\n  CAssetId x8_nameSTRG;\n  zeus::CTransform xc_transform;\n  zeus::CTransform x3c_invTransform;\n  zeus::CAABox x6c_aabb;\n  CAssetId x84_mrea;\n  s32 x88_areaId;\n  std::vector<u16> x8c_attachedAreaIndices;\n  std::vector<SObjectTag> x9c_deps1;\n  std::vector<SObjectTag> xac_deps2;\n\n  std::vector<u32> xbc_layerDepOffsets;\n  std::vector<Dock> xcc_docks;\n  std::vector<CToken> xdc_tokens;\n\n  u32 xec_totalResourcesSize = 0;\n\n  bool xf0_24_postConstructed : 1 = false;\n  bool xf0_25_active : 1 = true;\n  bool xf0_26_tokensReady : 1 = false;\n  bool xf0_27_loadPaused : 1 = false;\n  bool xf0_28_validated : 1 = false;\n\n  enum class EPhase {\n    LoadHeader,\n    LoadSecSizes,\n    ReserveSections,\n    LoadDataSections,\n    WaitForFinish\n  } xf4_phase = EPhase::LoadHeader;\n\n  std::list<std::shared_ptr<IDvdRequest>> xf8_loadTransactions;\n\npublic:\n  class CChainIterator {\n    CGameArea* m_area = nullptr;\n\n  public:\n    constexpr CChainIterator() = default;\n    explicit constexpr CChainIterator(CGameArea* area) : m_area(area) {}\n    CGameArea& operator*() const { return *m_area; }\n    CGameArea* operator->() const { return m_area; }\n    CChainIterator& operator++() {\n      m_area = m_area->GetNext();\n      return *this;\n    }\n    bool operator!=(const CChainIterator& other) const { return m_area != other.m_area; }\n    bool operator==(const CChainIterator& other) const { return m_area == other.m_area; }\n  };\n\n  class CConstChainIterator {\n    const CGameArea* m_area = nullptr;\n\n  public:\n    constexpr CConstChainIterator() = default;\n    explicit constexpr CConstChainIterator(const CGameArea* area) : m_area(area) {}\n    const CGameArea& operator*() const { return *m_area; }\n    const CGameArea* operator->() const { return m_area; }\n    CConstChainIterator& operator++() {\n      m_area = m_area->GetNext();\n      return *this;\n    }\n    bool operator!=(const CConstChainIterator& other) const { return m_area != other.m_area; }\n    bool operator==(const CConstChainIterator& other) const { return m_area == other.m_area; }\n  };\n\n  class CAreaObjectList : public CObjectList {\n  private:\n    TAreaId x200c_areaIdx = 0;\n\n  public:\n    explicit CAreaObjectList(TAreaId areaIdx) : CObjectList(EGameObjectList::Invalid), x200c_areaIdx(areaIdx) {}\n\n    bool IsQualified(const CEntity& ent) const override;\n  };\n\n  enum class EOcclusionState { Occluded, Visible };\n\n  class CAreaFog {\n    ERglFogMode x0_fogMode = ERglFogMode::None;\n    zeus::CVector2f x4_rangeCur = {0.f, 1024.f};\n    zeus::CVector2f xc_rangeTarget = {0.f, 1024.f};\n    zeus::CVector2f x14_rangeDelta;\n    zeus::CColor x1c_colorCur = {0.5f, 0.5f, 0.5f, 1.f};\n    zeus::CColor x28_colorTarget = {0.5f, 0.5f, 0.5f, 1.f};\n    float x34_colorDelta = 0.f;\n\n  public:\n    void SetCurrent() const;\n    void Update(float dt);\n    void RollFogOut(float rangeDelta, float colorDelta, const zeus::CColor& color);\n    void FadeFog(ERglFogMode, const zeus::CColor& color, const zeus::CVector2f& vec1, float,\n                 const zeus::CVector2f& vec2);\n    void SetFogExplicit(ERglFogMode mode, const zeus::CColor& color, const zeus::CVector2f& range);\n    bool IsFogDisabled() const;\n    void DisableFog();\n  };\n\n  struct CPostConstructed {\n    std::unique_ptr<CAreaOctTree> x0_collision; // was rstl::optional_object<CAreaOctTree*>\n    std::optional<CAreaRenderOctTree> xc_octTree;\n    std::vector<CMetroidModelInstance> x4c_insts;\n    // std::unique_ptr<from unknown, pointless MREA section> x5c_;\n    std::vector<CWorldLight> x60_lightsA;\n    std::vector<CLight> x70_gfxLightsA;\n    std::vector<CWorldLight> x80_lightsB;\n    std::vector<CLight> x90_gfxLightsB;\n    std::unique_ptr<CPVSAreaSet> xa0_pvs;\n    u32 xa4_elemCount = kMaxEntities;\n    struct MapEntry {\n      s16 x0_id = -1;\n      TUniqueId x4_uid = kInvalidUniqueId;\n    };\n    std::array<MapEntry, kMaxEntities> xa8_pvsEntityMap;\n    u32 x10a8_pvsVersion = 0;\n    TLockedToken<CPFArea> x10ac_pathToken;\n    // bool x10b8_ = 0; optional flag for CToken\n    CPFArea* x10bc_pathArea = nullptr;\n    std::unique_ptr<CAreaObjectList> x10c0_areaObjs;\n    std::unique_ptr<CAreaFog> x10c4_areaFog;\n    const u8* x10c8_sclyBuf = nullptr; // was rstl::optional_object<void*>\n    u32 x10d0_sclySize = 0;\n    const u8* x10d4_firstMatPtr = nullptr;\n    const CScriptAreaAttributes* x10d8_areaAttributes = nullptr;\n    EOcclusionState x10dc_occlusionState = EOcclusionState::Occluded;\n    u32 x10e0_ = 0;\n    float x10e4_occludedTime = 5.f;\n    u32 x10e8_ = -1;\n    u32 x10ec_firstMatSection = 0;\n    // std::vector<CAramToken> x10f0_tokens;\n    u32 x1100_ = 0;\n    u32 x1104_ = 0;\n    bool x1108_24_ : 1 = false;\n    bool x1108_25_modelsConstructed : 1 = false;\n    bool x1108_26_ : 1 = false;\n    bool x1108_27_ : 1 = false;\n    bool x1108_28_occlusionPinged : 1 = false;\n    bool x1108_29_pvsHasActors : 1 = false;\n    bool x1108_30_ : 1 = false;\n    std::vector<std::pair<const u8*, u32>> x110c_layerPtrs;\n    float x111c_thermalCurrent = 0.f;\n    float x1120_thermalSpeed = 0.f;\n    float x1124_thermalTarget = 0.f;\n    float x1128_worldLightingLevel = 1.f;\n    float x112c_xraySpeed = 0.f;\n    float x1130_xrayTarget = 1.f;\n    float x1134_weaponWorldLightingSpeed = 0.f;\n    float x1138_weaponWorldLightingTarget = 1.f;\n    u32 x113c_playerActorsLoading = 0;\n\n    CPostConstructed() = default;\n  };\n\nprivate:\n  std::vector<std::pair<std::unique_ptr<u8[]>, int>> x110_mreaSecBufs;\n  std::vector<std::span<const u8>> m_resolvedBufs;\n  u32 x124_secCount = 0;\n  u32 x128_mreaDataOffset = 0;\n  std::unique_ptr<CPostConstructed> x12c_postConstructed;\n\n  CGameArea* x130_next = nullptr;\n  CGameArea* x134_prev = nullptr;\n  EChain x138_curChain = EChain::ToDeallocate;\n\n  void UpdateFog(float dt);\n  void UpdateThermalVisor(float dt);\n  void UpdateWeaponWorldLighting(float dt);\n\n  std::optional<CStaticRes> m_debugSphereRes;\n  std::unique_ptr<CModelData> m_debugSphereModel;\n  std::optional<CStaticRes> m_debugConeRes;\n  std::unique_ptr<CModelData> m_debugConeModel;\n\npublic:\n  explicit CGameArea(CInputStream& in, int idx, int mlvlVersion);\n  ~CGameArea();\n\n  bool IsFinishedOccluding() const;\n  void ReadDependencyList();\n  void SetLoadPauseState(bool paused);\n\n  std::pair<std::unique_ptr<u8[]>, s32> IGetScriptingMemoryAlways() const override;\n  TAreaId GetAreaId() const { return x4_selfIdx; }\n  s32 IGetAreaSaveId() const override { return x88_areaId; }\n  CAssetId IGetAreaAssetId() const override { return x84_mrea; }\n  bool IIsActive() const override;\n  TAreaId IGetAttachedAreaId(int) const override;\n  u32 IGetNumAttachedAreas() const override;\n  CAssetId IGetStringTableAssetId() const override;\n  const zeus::CTransform& IGetTM() const override;\n\n  void SetXRaySpeedAndTarget(float speed, float target);\n  void SetThermalSpeedAndTarget(float speed, float target);\n  void SetWeaponWorldLighting(float speed, float target);\n\n  CAssetId GetAreaAssetId() const { return x84_mrea; }\n  const CAreaFog* GetAreaFog() const { return GetPostConstructed()->x10c4_areaFog.get(); }\n  CAreaFog* GetAreaFog() { return GetPostConstructed()->x10c4_areaFog.get(); }\n  float GetXRayFogDistance() const;\n  EEnvFxType DoesAreaNeedEnvFx() const;\n  bool DoesAreaNeedSkyNow() const;\n  void OtherAreaOcclusionChanged();\n  void PingOcclusionState();\n  void PreRender();\n  void AliveUpdate(float dt);\n  void SetOcclusionState(EOcclusionState state);\n  EOcclusionState GetOcclusionState() const { return GetPostConstructed()->x10dc_occlusionState; }\n  void RemoveStaticGeometry();\n  void AddStaticGeometry();\n  // void TransferTokensToARAM();\n  // void TransferARAMTokensOver();\n  EChain SetChain(CGameArea* prev, EChain chain);\n  bool StartStreamingMainArea();\n  // void UnloadAllLoadedTextures();\n  // void ReloadAllLoadedTextures();\n  void ReloadAllUnloadedTextures();\n  u32 GetNumPartSizes() const;\n  void AllocNewAreaData(int, int);\n  bool Invalidate(CStateManager* mgr);\n  void KillmAreaData();\n  void CullDeadAreaRequests();\n  void StartStreamIn(CStateManager& mgr);\n  void Validate(CStateManager& mgr);\n  void LoadScriptObjects(CStateManager& mgr);\n  std::pair<const u8*, u32> GetLayerScriptBuffer(int layer) const;\n  void PostConstructArea();\n  void FillInStaticGeometry();\n  void VerifyTokenList(CStateManager& stateMgr);\n  void ClearTokenList();\n  u32 GetPreConstructedSize() const;\n  SMREAHeader VerifyHeader() const;\n  TUniqueId LookupPVSUniqueID(TUniqueId id) const;\n  s16 LookupPVSID(TUniqueId id) const;\n  const CPVSAreaSet* GetAreaVisSet() const { return GetPostConstructed()->xa0_pvs.get(); }\n  u32 Get1stPVSLightFeature(u32 lightIdx) const {\n    return GetAreaVisSet() ? GetAreaVisSet()->Get1stLightIndex(lightIdx) : UINT32_MAX;\n  }\n  u32 Get2ndPVSLightFeature(u32 lightIdx) const {\n    return GetAreaVisSet() ? GetAreaVisSet()->Get2ndLightIndex(lightIdx) : UINT32_MAX;\n  }\n\n  const zeus::CTransform& GetTransform() const { return xc_transform; }\n  const zeus::CTransform& GetInverseTransform() const { return x3c_invTransform; }\n  const zeus::CAABox& GetAABB() const { return x6c_aabb; }\n\n  const std::vector<Dock>& GetDocks() const { return xcc_docks; }\n  const Dock* GetDock(s32 dock) const { return &xcc_docks[dock]; }\n  Dock* GetDock(s32 dock) { return &xcc_docks[dock]; }\n  s32 GetDockCount() const { return xcc_docks.size(); }\n\n  bool IsPostConstructed() const { return xf0_24_postConstructed; }\n  CPostConstructed* GetPostConstructed() { return x12c_postConstructed.get(); }\n  const CPostConstructed* GetPostConstructed() const { return x12c_postConstructed.get(); }\n\n  bool IsValidated() const { return xf0_28_validated; }\n\n  void SetAreaAttributes(const CScriptAreaAttributes* areaAttributes);\n  bool GetActive() const { return xf0_25_active; }\n  void SetActive(bool active) { xf0_25_active = active; }\n  CObjectList* GetAreaObjects() const {\n    return GetPostConstructed() ? GetPostConstructed()->x10c0_areaObjs.get() : nullptr;\n  }\n\n  CGameArea* GetNext() const { return x130_next; }\n\n  void DebugDraw();\n  void DebugDrawLight(const CLight& light);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CGameLight.cpp",
    "content": "#include \"Runtime/World/CGameLight.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Graphics/CCubeRenderer.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCGameLight::CGameLight(TUniqueId uid, TAreaId aid, bool active, std::string_view name, const zeus::CTransform& xf,\n                       TUniqueId parentId, const CLight& light, u32 sourceId, u32 priority, float lifeTime)\n: CActor(uid, active, name, CEntityInfo(aid, CEntity::NullConnectionList), xf, CModelData::CModelDataNull(),\n         CMaterialList(), CActorParameters::None(), kInvalidUniqueId)\n, xe8_parentId(parentId)\n, xec_light(light)\n, x13c_lightId(sourceId)\n, x140_priority(priority)\n, x144_lifeTime(lifeTime) {\n  xec_light.GetRadius();\n  xec_light.GetIntensity();\n  SetLightPriorityAndId();\n}\n\nvoid CGameLight::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CGameLight::Think(float dt, CStateManager& mgr) {\n  if (x144_lifeTime <= 0.f)\n    return;\n  x144_lifeTime -= dt;\n\n  if (x144_lifeTime <= 0.f)\n    mgr.FreeScriptObject(GetUniqueId());\n}\n\nvoid CGameLight::SetLightPriorityAndId() {\n  xec_light.x3c_priority = x140_priority;\n  xec_light.x40_lightId = x13c_lightId;\n}\n\nvoid CGameLight::SetLight(const CLight& light) {\n  xec_light = light;\n  xec_light.GetRadius();\n  xec_light.GetIntensity();\n  SetLightPriorityAndId();\n}\n\nCLight CGameLight::GetLight() const {\n  CLight ret = xec_light;\n  ret.SetPosition(x34_transform * xec_light.GetPosition());\n\n  if (ret.GetType() != ELightType::Point)\n    ret.SetDirection(x34_transform.rotate(xec_light.GetDirection()).normalized());\n\n  return ret;\n}\n\nvoid CGameLight::DebugDraw() {\n  if (!m_debugRes) {\n    const auto* tok = (xec_light.GetType() == ELightType::Spot || xec_light.GetType() == ELightType::Directional)\n                          ? g_ResFactory->GetResourceIdByName(\"CMDL_DebugLightCone\")\n                          : g_ResFactory->GetResourceIdByName(\"CMDL_DebugSphere\");\n    if (tok != nullptr && tok->type == FOURCC('CMDL')) {\n      m_debugRes = CStaticRes(tok->id, zeus::skOne3f);\n    }\n  }\n\n  if (m_debugRes && !m_debugModel) {\n    m_debugModel = std::make_unique<CModelData>(*m_debugRes);\n  }\n\n  if (m_debugModel) {\n    g_Renderer->SetGXRegister1Color(xec_light.GetColor());\n    CModelFlags modelFlags;\n    modelFlags.x0_blendMode = 5;\n    modelFlags.x4_color = zeus::skWhite;\n    modelFlags.x4_color.a() = 0.5f;\n    m_debugModel->Render(CModelData::EWhichModel::Normal, zeus::CTransform::Translate(xec_light.GetPosition()), nullptr,\n                         modelFlags);\n    m_debugModel->Render(CModelData::EWhichModel::Normal, x34_transform, nullptr, modelFlags);\n  }\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CGameLight.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/World/CActor.hpp\"\n#include \"Runtime/Graphics/CLight.hpp\"\n\nnamespace metaforce {\nclass CGameLight : public CActor {\n  TUniqueId xe8_parentId;\n  CLight xec_light;\n  u32 x13c_lightId;\n  u32 x140_priority;\n  float x144_lifeTime;\n\n  std::optional<CStaticRes> m_debugRes;\n  std::unique_ptr<CModelData> m_debugModel;\n\npublic:\n  DEFINE_ENTITY\n  CGameLight(TUniqueId uid, TAreaId aid, bool active, std::string_view name, const zeus::CTransform& xf,\n             TUniqueId parentId, const CLight& light, u32 sourceId, u32 priority, float lifeTime);\n\n  void Accept(IVisitor& visitor) override;\n  void Think(float dt, CStateManager& mgr) override;\n  void SetLightPriorityAndId();\n  void SetLight(const CLight& light);\n  CLight GetLight() const;\n  TUniqueId GetParentId() const { return xe8_parentId; }\n\n  void DebugDraw();\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CGrappleParameters.hpp",
    "content": "#pragma once\n\nnamespace metaforce {\n\nclass CGrappleParameters {\n  float x0_a;\n  float x4_b;\n  float x8_c;\n  float xc_d;\n  float x10_e;\n  float x14_f;\n  float x18_g;\n  float x1c_h;\n  float x20_i;\n  float x24_j;\n  float x28_k;\n  bool x2c_lockSwingTurn;\n\npublic:\n  CGrappleParameters(float a, float b, float c, float d, float e, float f, float g, float h, float i, float j, float k,\n                     bool lockSwingTurn)\n  : x0_a(a)\n  , x4_b(b)\n  , x8_c(c)\n  , xc_d(d)\n  , x10_e(e)\n  , x14_f(f)\n  , x18_g(g)\n  , x1c_h(h)\n  , x20_i(i)\n  , x24_j(j)\n  , x28_k(k)\n  , x2c_lockSwingTurn(lockSwingTurn) {}\n\n  bool GetLockSwingTurn() const { return x2c_lockSwingTurn; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CHUDBillboardEffect.cpp",
    "content": "#include \"Runtime/World/CHUDBillboardEffect.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Camera/CGameCamera.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nu32 CHUDBillboardEffect::g_IndirectTexturedBillboardCount = 0;\nu32 CHUDBillboardEffect::g_BillboardCount = 0;\n\nCHUDBillboardEffect::CHUDBillboardEffect(const std::optional<TToken<CGenDescription>>& particle,\n                                         const std::optional<TToken<CElectricDescription>>& electric, TUniqueId uid,\n                                         bool active, std::string_view name, float dist, const zeus::CVector3f& scale0,\n                                         const zeus::CColor& color, const zeus::CVector3f& scale1,\n                                         const zeus::CVector3f& translation)\n: CEffect(uid, CEntityInfo(kInvalidAreaId, CEntity::NullConnectionList), active, name, zeus::CTransform()) {\n  xec_translation = translation;\n  xec_translation.y() += dist;\n  xf8_localScale = scale1 * scale0;\n\n  if (particle) {\n    x104_26_isElementGen = true;\n    xe8_generator = std::make_unique<CElementGen>(*particle);\n    if (static_cast<CElementGen&>(*xe8_generator).IsIndirectTextured())\n      ++g_IndirectTexturedBillboardCount;\n  } else {\n    xe8_generator = std::make_unique<CParticleElectric>(*electric);\n  }\n  ++g_BillboardCount;\n  xe8_generator->SetModulationColor(color);\n  xe8_generator->SetLocalScale(xf8_localScale);\n}\n\nCHUDBillboardEffect::~CHUDBillboardEffect() {\n  --g_BillboardCount;\n  if (xe8_generator->Get4CharId() == FOURCC('PART'))\n    if (static_cast<CElementGen&>(*xe8_generator).IsIndirectTextured())\n      --g_IndirectTexturedBillboardCount;\n}\n\nvoid CHUDBillboardEffect::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nfloat CHUDBillboardEffect::CalcGenRate() {\n  float f1;\n  if (g_BillboardCount + g_IndirectTexturedBillboardCount <= 4)\n    f1 = 0.f;\n  else\n    f1 = g_BillboardCount * 0.2f + g_IndirectTexturedBillboardCount * 0.1f;\n  return 1.f - std::min(f1, 0.8f);\n}\n\nvoid CHUDBillboardEffect::Think(float dt, CStateManager& mgr) {\n  if (GetActive()) {\n    mgr.SetActorAreaId(*this, mgr.GetWorld()->GetCurrentAreaId());\n    float oldGenRate = xe8_generator->GetGeneratorRate();\n    xe8_generator->SetGeneratorRate(oldGenRate * CalcGenRate());\n    xe8_generator->Update(dt);\n    xe8_generator->SetGeneratorRate(oldGenRate);\n    if (!x104_27_runIndefinitely) {\n      x108_timeoutTimer += dt;\n      if (x108_timeoutTimer > 30.f) {\n        mgr.FreeScriptObject(GetUniqueId());\n        return;\n      }\n    }\n    if (xe8_generator->IsSystemDeletable())\n      mgr.FreeScriptObject(GetUniqueId());\n  }\n}\n\nvoid CHUDBillboardEffect::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) {\n  if (x104_25_enableRender && x104_24_renderAsParticleGen) {\n    g_Renderer->AddParticleGen(*xe8_generator);\n  }\n}\n\nvoid CHUDBillboardEffect::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) {\n  if (mgr.GetPlayer().GetCameraState() == CPlayer::EPlayerCameraState::FirstPerson) {\n    zeus::CTransform camXf = mgr.GetCameraManager()->GetCurrentCameraTransform(mgr);\n    xe8_generator->SetGlobalTranslation(camXf * xec_translation);\n    xe8_generator->SetGlobalOrientation(camXf);\n    x104_25_enableRender = true;\n  } else {\n    x104_25_enableRender = false;\n  }\n  x104_24_renderAsParticleGen = !mgr.RenderLast(GetUniqueId());\n}\n\nvoid CHUDBillboardEffect::Render(CStateManager& mgr) {\n  if (x104_25_enableRender && !x104_24_renderAsParticleGen) {\n    SCOPED_GRAPHICS_DEBUG_GROUP(\"CHUDBillboardEffect::Render\", zeus::skPurple);\n    xe8_generator->Render();\n  }\n}\n\nfloat CHUDBillboardEffect::GetNearClipDistance(CStateManager& mgr) {\n  return mgr.GetCameraManager()->GetCurrentCamera(mgr)->GetNearClipDistance() + 0.01f;\n}\n\nzeus::CVector3f CHUDBillboardEffect::GetScaleForPOV(CStateManager& mgr) { return {0.155f, 1.f, 0.155f}; }\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CHUDBillboardEffect.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <optional>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/Particle/CParticleElectric.hpp\"\n#include \"Runtime/Particle/CParticleSwoosh.hpp\"\n#include \"Runtime/World/CEffect.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CGenDescription;\nclass CElectricDescription;\n\nclass CHUDBillboardEffect : public CEffect {\n  std::unique_ptr<CParticleGen> xe8_generator;\n  zeus::CVector3f xec_translation;\n  zeus::CVector3f xf8_localScale;\n  bool x104_24_renderAsParticleGen : 1 = true;\n  bool x104_25_enableRender : 1 = false;\n  bool x104_26_isElementGen : 1 = false;\n  bool x104_27_runIndefinitely : 1 = false;\n  float x108_timeoutTimer = 0.f;\n  static u32 g_IndirectTexturedBillboardCount;\n  static u32 g_BillboardCount;\n  static float CalcGenRate();\n\npublic:\n  DEFINE_ENTITY\n  CHUDBillboardEffect(const std::optional<TToken<CGenDescription>>& particle,\n                      const std::optional<TToken<CElectricDescription>>& electric, TUniqueId uid, bool active,\n                      std::string_view name, float dist, const zeus::CVector3f& scale0, const zeus::CColor& color,\n                      const zeus::CVector3f& scale1, const zeus::CVector3f& translation);\n  ~CHUDBillboardEffect() override;\n  void Accept(IVisitor& visitor) override;\n  void Think(float dt, CStateManager& mgr) override;\n  void AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) override;\n  void PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) override;\n  void Render(CStateManager& mgr) override;\n  bool IsElementGen() const { return x104_26_isElementGen; }\n  void SetRunIndefinitely(bool b) { x104_27_runIndefinitely = b; }\n  CParticleGen* GetParticleGen() const { return xe8_generator.get(); }\n\n  static float GetNearClipDistance(CStateManager& mgr);\n  static zeus::CVector3f GetScaleForPOV(CStateManager& mgr);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CHUDMemoParms.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Streams/IOStreams.hpp\"\n\nnamespace metaforce {\n\nclass CHUDMemoParms {\n  float x0_dispTime = 0.f;\n  bool x4_clearMemoWindow = false;\n  bool x5_fadeOutOnly = false;\n  bool x6_hintMemo = false;\n\npublic:\n  CHUDMemoParms() = default;\n  CHUDMemoParms(float dispTime, bool clearMemoWindow, bool fadeOutOnly, bool hintMemo)\n  : x0_dispTime(dispTime), x4_clearMemoWindow(clearMemoWindow), x5_fadeOutOnly(fadeOutOnly), x6_hintMemo(hintMemo) {}\n  explicit CHUDMemoParms(CInputStream& in) {\n    x0_dispTime = in.ReadFloat();\n    x4_clearMemoWindow = in.ReadBool();\n  }\n\n  float GetDisplayTime() const { return x0_dispTime; }\n  bool IsClearMemoWindow() const { return x4_clearMemoWindow; }\n  bool IsFadeOutOnly() const { return x5_fadeOutOnly; }\n  bool IsHintMemo() const { return x6_hintMemo; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CHealthInfo.cpp",
    "content": "#include \"Runtime/World/CHealthInfo.hpp\"\n\n#include \"Runtime/Streams/CInputStream.hpp\"\nnamespace metaforce {\n\nCHealthInfo::CHealthInfo(CInputStream& in) {\n  in.ReadLong();\n  x0_health = in.ReadFloat();\n  x4_knockbackResistance = in.ReadFloat();\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CHealthInfo.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n\nnamespace metaforce {\n\nclass CHealthInfo {\n  float x0_health;\n  float x4_knockbackResistance;\n\npublic:\n  explicit CHealthInfo(float hp) : x0_health(hp), x4_knockbackResistance(0.f) {}\n\n  CHealthInfo(float hp, float resist) : x0_health(hp), x4_knockbackResistance(resist) {}\n\n  explicit CHealthInfo(CInputStream& in);\n  void SetHP(float hp) { x0_health = hp; }\n  float GetHP() const { return x0_health; }\n  float GetKnockbackResistance() const { return x4_knockbackResistance; }\n  void SetKnockbackResistance(float r) { x4_knockbackResistance = r; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CIceImpact.cpp",
    "content": "#include \"Runtime/World/CIceImpact.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Collision/CCollidableOBBTreeGroup.hpp\"\n#include \"Runtime/Collision/CGameCollision.hpp\"\n#include \"Runtime/Collision/CollisionUtil.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/World/CDamageVulnerability.hpp\"\n#include \"Runtime/World/CGameLight.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CWallCrawlerSwarm.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nstatic bool PointInSphere(zeus::CSphere const& sphere, zeus::CVector3f const& point) {\n  return (point - sphere.position).magSquared() <= (sphere.radius * sphere.radius);\n}\n\nCIceImpact::CIceImpact(const TLockedToken<CGenDescription>& particle, TUniqueId uid, TAreaId aid, bool active,\n                       std::string_view name, const zeus::CTransform& xf, u32 flags, const zeus::CVector3f& scale,\n                       const zeus::CColor& color)\n: CEffect(uid, CEntityInfo(aid, CEntity::NullConnectionList), active, name, xf)\n, xe8_elementGen(std::make_unique<CElementGen>(particle, CElementGen::EModelOrientationType::One,\n                                               CElementGen::EOptionalSystemFlags::One))\n, xf0_genAssetId(particle.GetObjectTag()->id)\n, x108_sphereGenRange(x34_transform.origin, 6.4f)\n, x118_grid(zeus::CAABox(xf.origin - zeus::CVector3f(x100_halfBounds), xf.origin + zeus::CVector3f(x100_halfBounds)))\n, x598_24_(flags & 0x2) {\n\n  xe6_27_thermalVisorFlags = 2;\n  x540_impactSpheres.push_back(SImpactSphere(x34_transform.origin, 2.4f, 1.6f, 0.f, 0.f));\n  x540_impactSpheres.push_back(SImpactSphere(x34_transform.origin, 0.f, 1.f, 1.f, 0.f));\n  x540_impactSpheres.push_back(SImpactSphere(x34_transform.origin, 0.f, 1.f, 1.f, 0.f));\n  // ???\n  x540_impactSpheres[1].x18_d = x540_impactSpheres[2].x14_c;\n  x540_impactSpheres[1].x14_c += x540_impactSpheres[2].x10_b;\n  x540_impactSpheres[2].x18_d = x540_impactSpheres[2].x14_c;\n  x540_impactSpheres[2].x14_c += x540_impactSpheres[2].x10_b;\n  x118_grid.MarkCells(zeus::CSphere(x34_transform.origin, 2.4f), 2);\n}\n\nvoid CIceImpact::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CIceImpact::CalculateRenderBounds() {\n  auto bounds = xe8_elementGen->GetBounds();\n  if (bounds) {\n    x598_25_hasRenderBounds = true;\n    x9c_renderBounds = *bounds;\n  } else {\n    x598_25_hasRenderBounds = false;\n    x9c_renderBounds = zeus::CAABox(x34_transform.origin, x34_transform.origin);\n  }\n}\n\nvoid CIceImpact::PreRender(CStateManager& mgr, zeus::CFrustum const& planes) {\n  CActor::PreRender(mgr, planes);\n  bool out_of_frustum = false;\n  if (!x598_25_hasRenderBounds || !planes.aabbFrustumTest(x9c_renderBounds)) {\n    out_of_frustum = true;\n  }\n\n  xe4_30_outOfFrustum = out_of_frustum;\n}\n\nvoid CIceImpact::AddToRenderer(zeus::CFrustum const& planes, CStateManager& mgr) {\n  if (xe4_30_outOfFrustum) {\n    return;\n  }\n  if (mgr.GetThermalDrawFlag() == EThermalDrawFlag::Hot) {\n    EnsureRendered(mgr);\n  } else {\n    g_Renderer->AddParticleGen(*xe8_elementGen);\n  }\n}\n\nvoid CIceImpact::Render(CStateManager& mgr) {\n  CElementGen::SetSubtractBlend(true);\n  CCubeModel::SetRenderModelBlack(true);\n  xe8_elementGen->Render();\n  CElementGen::SetSubtractBlend(false);\n  CCubeModel::SetRenderModelBlack(false);\n}\n\nvoid CIceImpact::Think(float dt, CStateManager& mgr) {\n  xf4_lifeTimer += dt;\n  if (xf4_lifeTimer < 0.8f && xe8_elementGen->GetParticleCount() < 0x190) {\n    for (SImpactSphere& sphere : x540_impactSpheres) {\n      if (sphere.x14_c <= sphere.xc_a) {\n        continue;\n      }\n      auto new_sphere = GenerateNewSphere();\n      if (new_sphere) {\n        sphere = *new_sphere;\n      }\n    }\n    for (SImpactSphere& sphere : x540_impactSpheres) {\n      if (sphere.x14_c > sphere.xc_a) {\n        continue;\n      }\n      sphere.x18_d = sphere.x14_c;\n      sphere.x14_c += sphere.x10_b;\n      zeus::CSphere sphere_a(sphere.x0_pos, sphere.x14_c);\n      zeus::CSphere sphere_b(sphere.x0_pos, sphere.x18_d);\n      zeus::CAABox bbox(sphere_b.position - sphere_a.radius, sphere_b.position + sphere_a.radius);\n      x104_ = 0.f;\n      GenerateParticlesAgainstActors(mgr, bbox, sphere_a, sphere_b);\n      CAreaCollisionCache cache(bbox);\n      CGameCollision::BuildAreaCollisionCache(mgr, cache);\n      for (auto const& leaf_cache : cache) {\n        GenerateParticlesAgainstWorld(mgr, leaf_cache, sphere_a, sphere_b);\n      }\n    }\n  }\n  xe8_elementGen->SetOrientation(zeus::CTransform());\n  xe8_elementGen->Update(dt);\n\n  if (xec_ != kInvalidUniqueId) {\n    if (TCastToPtr<CGameLight> light = mgr.ObjectById(xec_)) {\n      if (x30_24_active) {\n        light->SetLight(xe8_elementGen->GetLight());\n      }\n    }\n  }\n  if (x598_24_) {\n    mgr.SetActorAreaId(*this, mgr.GetPlayer().GetAreaIdAlways());\n  }\n  if (xe8_elementGen->IsSystemDeletable()) {\n    mgr.FreeScriptObject(x8_uid);\n  }\n}\n\nstd::optional<CIceImpact::SImpactSphere> CIceImpact::GenerateNewSphere() {\n  xfc_searchDirection = (xfc_searchDirection + 1) & 7;\n  bool fwd_z = (xfc_searchDirection & 1) != 0u;\n  bool fwd_y = (xfc_searchDirection & 2) != 0u;\n  bool fwd_x = (xfc_searchDirection & 4) != 0u;\n\n  constexpr auto loop_cond = [](bool fwd, u32 counter) {\n    if (fwd) {\n      return counter < 14;\n    }\n    return counter != 0;\n  };\n  constexpr auto loop_step = [](bool fwd, u32 counter) {\n    if (fwd) {\n      return counter + 1;\n    }\n    return counter - 1;\n  };\n\n  for (u32 z = 8; loop_cond(fwd_z, z); z = loop_step(fwd_z, z)) {\n    for (u32 y = 8; loop_cond(fwd_y, y); y = loop_step(fwd_y, y)) {\n      for (u32 x = 8; loop_cond(fwd_x, x); x = loop_step(fwd_x, x)) {\n        u32 grid_val = x118_grid.GetValue(x, y, z);\n        if (grid_val == 1) {\n          zeus::CVector3f pos = x118_grid.GetWorldPositionForCell(x, y, z);\n          x118_grid.SetValue(x, y, z, 3);\n          if (PointInSphere(x108_sphereGenRange, pos)) {\n            x118_grid.MarkCells(zeus::CSphere(pos, 1.6f), 2);\n            return SImpactSphere(pos, 1.6f, 1.6f, 0.f, 0.f);\n          }\n        }\n      }\n    }\n  }\n  return {};\n}\n\nvoid CIceImpact::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  switch (msg) {\n  case EScriptObjectMessage::Deleted:\n    if (xec_ != kInvalidUniqueId) {\n      mgr.FreeScriptObject(xec_);\n      xec_ = kInvalidUniqueId;\n    }\n    break;\n  case EScriptObjectMessage::Registered:\n    if (xe8_elementGen->SystemHasLight()) {\n      xec_ = mgr.AllocateUniqueId();\n      auto* new_light = new CGameLight(xec_, x4_areaId, x30_24_active, \"IcePLight_\"sv, x34_transform, x8_uid,\n                                       xe8_elementGen->GetLight(), xf0_genAssetId.Value(), 1, 0.f);\n      mgr.AddObject(new_light);\n    }\n    break;\n  default:\n    break;\n  }\n\n  CActor::AcceptScriptMsg(msg, uid, mgr);\n  if (xec_ != kInvalidUniqueId) {\n    mgr.SendScriptMsgAlways(xec_, uid, msg);\n  }\n}\n\nvoid CIceImpact::Touch(CActor& actor, CStateManager& mgr) {\n  if (xf4_lifeTimer > xf8_latestDamageTime) {\n    return;\n  }\n  auto bounds = actor.GetTouchBounds();\n  if (!bounds) {\n    return;\n  }\n\n  if (TCastToPtr<CPatterned> ai = actor) {\n    if (x118_grid.AABoxTouchesData(\n            zeus::CAABox(bounds->min - zeus::CVector3f(0, 0, 0.5f), bounds->max + zeus::CVector3f(0, 0, 0.5f)), 1)) {\n      CDamageVulnerability const* vuln = ai->GetDamageVulnerability();\n      if (vuln->WeaponHits(CWeaponMode(EWeaponType::Ice), false) && ai->GetKnockBackController().GetEnableFreeze() &&\n          ai->GetBodyController()->GetPercentageFrozen() == 0.f && (xf8_latestDamageTime - xf4_lifeTimer) > 0.5f &&\n          xf4_lifeTimer < 0.8f) {\n        mgr.ApplyDamage(x8_uid, actor.GetUniqueId(), kInvalidUniqueId,\n                        CDamageInfo(CWeaponMode(EWeaponType::Ice), 100.f, 0.f, 1.f),\n                        CMaterialFilter::MakeIncludeExclude(CMaterialList(EMaterialTypes::Solid), CMaterialList()),\n                        zeus::CVector3f());\n      }\n    }\n  }\n  if (actor.GetMaterialList().HasMaterial(EMaterialTypes::ExcludeFromLineOfSightTest) &&\n      x118_grid.AABoxTouchesData(*bounds, 1)) {\n    mgr.ApplyDamage(\n        x8_uid, actor.GetUniqueId(), kInvalidUniqueId, CDamageInfo(CWeaponMode(EWeaponType::Ice), 100.f, 0.f, 1.f),\n        CMaterialFilter::MakeIncludeExclude(CMaterialList(EMaterialTypes::Solid), CMaterialList()), zeus::CVector3f());\n  }\n  if (TCastToPtr<CWallCrawlerSwarm> wall_crawler = actor) {\n    if (xf8_latestDamageTime - xf4_lifeTimer > 0.5f) {\n      wall_crawler->FreezeCollision(x118_grid);\n    }\n  }\n}\n\nvoid CIceImpact::GenerateParticlesAgainstWorld(CStateManager& mgr,\n                                               const CMetroidAreaCollider::COctreeLeafCache& leaf_cache,\n                                               zeus::CSphere& a, zeus::CSphere& b) {\n  CMetroidAreaCollider::ResetInternalCounters();\n  auto triangle_list = CMetroidAreaCollider::GetTriangleList();\n  auto filter = CMaterialFilter::MakeExclude(EMaterialTypes::ProjectilePassthrough);\n  for (auto const& node : leaf_cache) {\n    CAreaOctTree::TriListReference arr = node.GetTriangleArray();\n    bool subdivide_result = false;\n    for (int i = 0; i < arr.GetSize() && !subdivide_result; i++) {\n      u16 v1 = arr.GetAt(i);\n      if (triangle_list[v1] != CMetroidAreaCollider::GetPrimitiveCheckCount()) {\n        triangle_list[v1] = CMetroidAreaCollider::GetPrimitiveCheckCount();\n        CCollisionSurface surface = node.GetOwner().GetMasterListTriangle(arr.GetAt(i));\n        if (filter.Passes(CMaterialList(surface.GetSurfaceFlags()))) {\n          subdivide_result =\n              SubdivideAndGenerateParticles(mgr, surface.GetVert(0), surface.GetVert(1), surface.GetVert(2), a, b);\n        }\n      }\n    }\n  }\n}\n\nvoid CIceImpact::GenerateParticlesAgainstActors(CStateManager& mgr, const zeus::CAABox& box, const zeus::CSphere& a,\n                                                const zeus::CSphere& b) {\n  CMaterialFilter filter =\n      CMaterialFilter::MakeExclude({EMaterialTypes::Character, EMaterialTypes::Player, EMaterialTypes::Projectile,\n                                    EMaterialTypes::ProjectilePassthrough, EMaterialTypes::AIJoint});\n  rstl::reserved_vector<TUniqueId, 1024> near_list;\n  mgr.BuildNearList(near_list, box, filter, this);\n  for (TUniqueId uid : near_list) {\n    CEntity* ent = mgr.ObjectById(uid);\n    TCastToPtr<CPhysicsActor> pAct = ent;\n    if (pAct && pAct->GetCollisionPrimitive()->GetPrimType() == FOURCC('OBTG')) {\n      const auto* prim = static_cast<const CCollidableOBBTreeGroup*>(pAct->GetCollisionPrimitive());\n      for (int i = 0; i < prim->GetContainer()->NumTrees(); ++i) {\n        GenerateParticlesAgainstOBBTree(mgr, *prim->GetOBBTreeAABox(i), pAct->GetPrimitiveTransform(), a, b);\n      }\n    } else if (const auto* actor = static_cast<CActor*>(ent)) {\n      if (!actor->GetMaterialList().HasMaterial(EMaterialTypes::Solid)) {\n        if (!TCastToPtr<CScriptWater>(ent)) {\n          continue;\n        }\n        GenerateParticlesAgainstAABox(mgr, *actor->GetTouchBounds(), a, b);\n      }\n    }\n  }\n}\n\nvoid CIceImpact::GenerateParticlesAgainstOBBTree(CStateManager& mgr, const COBBTree& tree, const zeus::CTransform& xf,\n                                                 const zeus::CSphere& a, const zeus::CSphere& b) {\n  auto filter = CMaterialFilter::MakeExclude(EMaterialTypes::Solid);\n\n  for (int i = 0; i < tree.NumSurfaceMaterials(); i++) {\n    CCollisionSurface surface = tree.GetTransformedSurface(i, xf);\n    if (filter.Passes(static_cast<u64>(surface.GetSurfaceFlags()))) {\n      if (SubdivideAndGenerateParticles(mgr, surface.GetVert(0), surface.GetVert(1), surface.GetVert(2), a, b)) {\n        break;\n      }\n    }\n  }\n}\n\nvoid CIceImpact::GenerateParticlesAgainstAABox(CStateManager& mgr, const zeus::CAABox& box,\n                                               const zeus::CSphere& a, const zeus::CSphere& b) {\n  for (int i = 0; i < 12; ++i) {\n    auto tri = box.getTri(zeus::CAABox::EBoxFaceId(i / 2), (i & 1) * 2);\n    if (SubdivideAndGenerateParticles(mgr, tri.x10_verts[0], tri.x10_verts[2], tri.x10_verts[1], a, b)) {\n      return;\n    }\n  }\n}\n\nbool CIceImpact::SubdivideAndGenerateParticles(CStateManager& mgr, zeus::CVector3f const& v1, zeus::CVector3f const& v2,\n                                               zeus::CVector3f const& v3, zeus::CSphere const& a,\n                                               zeus::CSphere const& b) {\n  if (!CollisionUtil::TriSphereOverlap(a, v1, v2, v3)) {\n    return false;\n  }\n  if (PointInSphere(b, v1) && PointInSphere(b, v2) && PointInSphere(b, v3)) {\n    return false;\n  }\n\n  zeus::CVector3f xprod = (v2 - v1).cross(v3 - v1);\n  float mag = xprod.magnitude();\n  if (mag <= 1.f) {\n    x104_ += mag;\n    int particle_create_count = static_cast<int>(x104_);\n    x104_ -= particle_create_count;\n    for (int i = 0; i < particle_create_count; i++) {\n      float rx = mgr.GetActiveRandom()->Float();\n      float ry = mgr.GetActiveRandom()->Float();\n      float rz = mgr.GetActiveRandom()->Float();\n      float random_inv = 1.f / (rx + ry + rz);\n      zeus::CVector3f point =\n          zeus::baryToWorld(v1, v2, v3, zeus::CVector3f(rx * random_inv, ry * random_inv, rz * random_inv));\n      u32 cx, cy, cz;\n      if (!PointInSphere(b, point) && PointInSphere(a, point) && x118_grid.GetCoords(point, cx, cy, cz) &&\n          ((x118_grid.GetValue(cx, cy, cz) & 1) == 0u)) {\n        x118_grid.SetValue(cx, cy, cz, 1);\n        zeus::CVector3f vec;\n        switch (mgr.GetActiveRandom()->Range(0, 2)) {\n        case 0:\n          vec = v1 - point;\n          if (!vec.canBeNormalized()) {\n            vec = v2 - point;\n          }\n          break;\n        case 1:\n          vec = v2 - point;\n          if (!vec.canBeNormalized()) {\n            vec = v3 - point;\n          }\n          break;\n        case 2:\n          vec = v3 - point;\n          if (!vec.canBeNormalized()) {\n            vec = v1 - point;\n          }\n          break;\n        }\n        vec = vec.normalized();\n        zeus::CVector3f norm_xprod = xprod.normalized();\n        norm_xprod.x() = (mgr.GetActiveRandom()->Float() - 0.5f) * 0.4f + norm_xprod.x();\n        norm_xprod.y() = (mgr.GetActiveRandom()->Float() - 0.5f) * 0.4f + norm_xprod.y();\n        norm_xprod.z() = (mgr.GetActiveRandom()->Float() - 0.5f) * 0.4f + norm_xprod.z();\n        xe8_elementGen->SetOrientation(zeus::lookAt(zeus::CVector3f(), norm_xprod.normalized(), vec));\n        xe8_elementGen->SetTranslation(point);\n        xe8_elementGen->ForceParticleCreation(1);\n        if (xe8_elementGen->GetParticleCount() == xe8_elementGen->GetMaxParticles()) {\n          return true;\n        }\n      }\n    }\n  } else {\n    zeus::CVector3f point = zeus::baryToWorld(v1, v2, v3, zeus::CVector3f(1.f / 3.f));\n    SubdivideAndGenerateParticles(mgr, v1, v2, point, a, b);\n    SubdivideAndGenerateParticles(mgr, v2, v3, point, a, b);\n    SubdivideAndGenerateParticles(mgr, v3, v1, point, a, b);\n  }\n\n  return false;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CIceImpact.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Collision/CMetroidAreaCollider.hpp\"\n#include \"Runtime/World/CEffect.hpp\"\n#include \"Runtime/World/CMarkerGrid.hpp\"\n\nnamespace metaforce {\nclass COBBTree;\nclass CElementGen;\nclass CIceImpact : public CEffect {\nprivate:\n  struct SImpactSphere {\n    zeus::CVector3f x0_pos;\n    float xc_a;\n    float x10_b;\n    float x14_c;\n    float x18_d;\n\n    SImpactSphere(zeus::CVector3f const& pos, float a, float b, float c, float d)\n    : x0_pos(pos), xc_a(a), x10_b(b), x14_c(c), x18_d(d) {}\n  };\n  std::unique_ptr<CElementGen> xe8_elementGen;\n  TUniqueId xec_ = kInvalidUniqueId;\n  CAssetId xf0_genAssetId;\n  float xf4_lifeTimer = 0.f;\n  float xf8_latestDamageTime = 4.f;\n  u32 xfc_searchDirection = 0;\n  float x100_halfBounds = 8.f;\n  float x104_ = 0;\n  zeus::CSphere x108_sphereGenRange;\n  CMarkerGrid x118_grid;\n  rstl::reserved_vector<SImpactSphere, 3> x540_impactSpheres;\n  bool x598_24_ : 1;\n  bool x598_25_hasRenderBounds : 1 = false;\n\n  std::optional<CIceImpact::SImpactSphere> GenerateNewSphere();\n  void GenerateParticlesAgainstWorld(CStateManager& mgr, const CMetroidAreaCollider::COctreeLeafCache& leaf_cache,\n                                     zeus::CSphere& a, zeus::CSphere& b);\n  void GenerateParticlesAgainstActors(CStateManager& mgr, const zeus::CAABox& box, const zeus::CSphere& a,\n                                      const zeus::CSphere& b);\n  void GenerateParticlesAgainstOBBTree(CStateManager& mgr, const COBBTree& tree, const zeus::CTransform& xf,\n                                       const zeus::CSphere& a, const zeus::CSphere& b);\n  void GenerateParticlesAgainstAABox(CStateManager& mgr, const zeus::CAABox& box, const zeus::CSphere& a,\n                                     const zeus::CSphere& b);\n  bool SubdivideAndGenerateParticles(CStateManager& mgr, zeus::CVector3f const& v1, zeus::CVector3f const& v2,\n                                     zeus::CVector3f const& v3, zeus::CSphere const& a, zeus::CSphere const& b);\n\npublic:\n  DEFINE_ENTITY\n  CIceImpact(const TLockedToken<CGenDescription>& particle, TUniqueId uid, TAreaId aid, bool active,\n             std::string_view name, const zeus::CTransform& xf, u32 flags, const zeus::CVector3f& scale,\n             const zeus::CColor& color);\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) override;\n  void Think(float dt, CStateManager& mgr) override;\n  void PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) override;\n  void AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) override;\n  void Render(CStateManager& frustum) override;\n  std::optional<zeus::CAABox> GetTouchBounds() const override { return x118_grid.GetBounds(); }\n  void Touch(CActor& actor, CStateManager& mgr) override;\n  void CalculateRenderBounds() override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CKnockBackController.cpp",
    "content": "#include \"Runtime/World/CKnockBackController.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Character/CPASAnimParmData.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n\nnamespace metaforce {\n\nconstexpr std::array<std::array<std::array<CKnockBackController::KnockBackParms, 4>, 19>, 3> KnockBackParmsTable{{\n    {{\n        {{\n            {EKnockBackAnimationState::KnockBack, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::ExplodeDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::KnockBack, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::ExplodeDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, 0.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::ExplodeDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::ExplodeDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, 0.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::ExplodeDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::ExplodeDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::None, 0.000000f, 0.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::Flinch, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::ExplodeDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::Shock, 1.000000f, 0.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::Shock, 2.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::ExplodeDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::Shock, 2.000000f, 0.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::Shock, 4.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::ExplodeDeath, 4.000000f, -1.000000f},\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::Shock, 0.000000f, 0.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::Shock, 4.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::ExplodeDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::Shock, 0.000000f, 0.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::Freeze, 2.500000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::Freeze, 2.500000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::Freeze, 5.500000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::Freeze, 5.500000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::Freeze, 8.500000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::Freeze, 4.500000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::ExplodeDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::ExplodeDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::ExplodeDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::ExplodeDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::ExplodeDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::ExplodeDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::ExplodeDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::ExplodeDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::KnockBack, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::ExplodeDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::None, 0.000000f, 0.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::ExplodeDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::ExplodeDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::Flinch, EKnockBackAnimationFollowUp::ExplodeDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::ExplodeDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n    }},\n    {{\n        {{\n            {EKnockBackAnimationState::Flinch, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::None, 0.000000f, 0.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::KnockBack, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, 0.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, 0.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, 0.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::Flinch, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::Shock, 2.000000f, -1.000000f},\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::Shock, 1.000000f, 0.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::KnockBack, EKnockBackAnimationFollowUp::Shock, 2.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::Shock, 2.000000f, -1.000000f},\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::Shock, 2.000000f, 0.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::Shock, 4.000000f, -1.000000f},\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::Shock, 4.000000f, -1.000000f},\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::Shock, 0.000000f, 0.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::Shock, 4.000000f, -1.000000f},\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::Shock, 4.000000f, -1.000000f},\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::Shock, 0.000000f, 0.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::Freeze, 2.500000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::Freeze, 2.500000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::None, 0.000000f, 0.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::Freeze, 5.500000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::Freeze, 5.500000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::Freeze, 8.500000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::Freeze, 8.500000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::Freeze, 4.500000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::Freeze, 4.500000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::KnockBack, EKnockBackAnimationFollowUp::Burn, 6.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::BurnDeath, 6.000000f, -1.000000f},\n            {EKnockBackAnimationState::KnockBack, EKnockBackAnimationFollowUp::Burn, 6.000000f, 0.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::KnockBack, EKnockBackAnimationFollowUp::BurnDeath, 6.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::BurnDeath, 6.000000f, -1.000000f},\n            {EKnockBackAnimationState::KnockBack, EKnockBackAnimationFollowUp::Burn, 6.000000f, 0.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::Burn, 6.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::BurnDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::KnockBack, EKnockBackAnimationFollowUp::Burn, 6.000000f, 0.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::KnockBack, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::KnockBack, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, 0.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::KnockBack, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::None, 0.000000f, 0.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::LaggedBurnDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::LaggedBurnDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::Flinch, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::PhazeOut, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n    }},\n    {{\n        {{\n            {EKnockBackAnimationState::Flinch, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::None, 0.000000f, 0.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::KnockBack, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::None, 0.000000f, 0.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::None, 0.000000f, 0.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::KnockBack, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::None, 0.000000f, 0.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::Flinch, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::Shock, 2.000000f, -1.000000f},\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::Shock, 1.000000f, 0.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::Flinch, EKnockBackAnimationFollowUp::Shock, 2.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::Shock, 2.000000f, -1.000000f},\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::Shock, 2.000000f, 0.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::Shock, 4.000000f, -1.000000f},\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::Shock, 4.000000f, -1.000000f},\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::Shock, 4.000000f, 0.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::Shock, 4.000000f, -1.000000f},\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::Shock, 4.000000f, -1.000000f},\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::Shock, 4.000000f, 0.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::Freeze, 2.500000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::None, 0.000000f, 0.000000f},\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::Freeze, 2.500000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::None, 0.000000f, 0.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::Freeze, 2.500000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::None, 0.000000f, 0.000000f},\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::Freeze, 2.500000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::None, 0.000000f, 0.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::Freeze, 4.500000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::None, 0.000000f, 0.000000f},\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::Freeze, 4.500000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::Freeze, 2.500000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::None, 0.000000f, 0.000000f},\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::Freeze, 2.500000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::Flinch, EKnockBackAnimationFollowUp::Burn, 6.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::Burn, 6.000000f, -1.000000f},\n            {EKnockBackAnimationState::KnockBack, EKnockBackAnimationFollowUp::Burn, 6.000000f, 0.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::KnockBack, EKnockBackAnimationFollowUp::Burn, 6.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::Burn, 6.000000f, -1.000000f},\n            {EKnockBackAnimationState::KnockBack, EKnockBackAnimationFollowUp::Burn, 6.000000f, 0.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::Burn, 6.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::BurnDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::KnockBack, EKnockBackAnimationFollowUp::Burn, 6.000000f, 0.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::Flinch, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::KnockBack, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, 0.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::Flinch, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::None, EKnockBackAnimationFollowUp::None, 0.000000f, 0.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::LaggedBurnDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::LaggedBurnDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n        {{\n            {EKnockBackAnimationState::Flinch, EKnockBackAnimationFollowUp::None, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::PhazeOut, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Hurled, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n            {EKnockBackAnimationState::Fall, EKnockBackAnimationFollowUp::IceDeath, 0.000000f, -1.000000f},\n        }},\n    }},\n}};\n\nconstexpr std::array ImpulseDurationTable{0.1f, 0.3f};\n\nCKnockBackController::CKnockBackController(EKnockBackVariant variant) : x0_variant(variant) {\n  x24_.resize(x24_.capacity(), std::make_pair(0.0f, FLT_MAX));\n}\n\nvoid CKnockBackController::ApplyImpulse(float dt, CPatterned& parent) {\n  x60_impulseRemTime = std::max(0.f, x60_impulseRemTime - dt);\n\n  if (parent.GetMaterialList().HasMaterial(EMaterialTypes::Immovable) || x60_impulseRemTime <= 0.f) {\n    return;\n  }\n\n  float remFac = 1.f;\n  if (x20_impulseDurationIdx == 1) {\n    remFac = x60_impulseRemTime / ImpulseDurationTable[x20_impulseDurationIdx];\n  }\n\n  parent.ApplyImpulseWR(\n      parent.GetMoveToORImpulseWR(\n          parent.GetTransform().transposeRotate(\n              x50_impulseDir * (remFac * x5c_impulseMag * dt / ImpulseDurationTable[x20_impulseDurationIdx])),\n          dt),\n      zeus::CAxisAngle());\n}\n\nbool CKnockBackController::TickDeferredTimer(float dt) {\n  x68_deferRemTime -= dt;\n  if (x14_deferWeaponType != EWeaponType::None) {\n    return x68_deferRemTime <= 0.f;\n  }\n  return false;\n}\n\nEKnockBackCharacterState CKnockBackController::GetKnockBackCharacterState(const CPatterned& parent) const {\n  if (parent.GetBodyController()->IsFrozen()) {\n    return parent.IsAlive() ? EKnockBackCharacterState::FrozenAlive : EKnockBackCharacterState::FrozenDead;\n  }\n  return parent.IsAlive() ? EKnockBackCharacterState::Alive : EKnockBackCharacterState::Dead;\n}\n\nvoid CKnockBackController::ValidateState(const CPatterned& parent) {\n  if (x4_activeParms.x0_animState < x18_minAnimState) {\n    x4_activeParms.x0_animState = x18_minAnimState;\n  } else if (x4_activeParms.x0_animState > x1c_maxAnimState) {\n    x4_activeParms.x0_animState = x1c_maxAnimState;\n  }\n\n  auto useState = EKnockBackAnimationState::Invalid;\n  if (parent.IsAlive()) {\n    if (parent.GetBodyController()->HasBodyState(pas::EAnimationState::Hurled) && x80_availableStates.test(3) &&\n        x4_activeParms.x0_animState >= EKnockBackAnimationState::Hurled) {\n      useState = EKnockBackAnimationState::Hurled;\n    } else if (parent.GetBodyController()->HasBodyState(pas::EAnimationState::KnockBack) &&\n               x80_availableStates.test(2) && x4_activeParms.x0_animState >= EKnockBackAnimationState::KnockBack) {\n      useState = EKnockBackAnimationState::KnockBack;\n    } else if (parent.GetBodyController()->HasBodyState(pas::EAnimationState::AdditiveFlinch) &&\n               x80_availableStates.test(1) && x4_activeParms.x0_animState >= EKnockBackAnimationState::Flinch) {\n      useState = EKnockBackAnimationState::Flinch;\n    }\n  } else if (parent.GetBodyController()->HasBodyState(pas::EAnimationState::Fall) && x80_availableStates.test(4) &&\n             (x4_activeParms.x0_animState >= EKnockBackAnimationState::Fall ||\n              (!parent.GetBodyController()->HasBodyState(pas::EAnimationState::Hurled) &&\n               x4_activeParms.x0_animState >= EKnockBackAnimationState::Hurled))) {\n    useState = EKnockBackAnimationState::Fall;\n  } else if (parent.GetBodyController()->HasBodyState(pas::EAnimationState::Hurled) && x80_availableStates.test(3) &&\n             x4_activeParms.x0_animState >= EKnockBackAnimationState::Hurled) {\n    useState = EKnockBackAnimationState::Hurled;\n  }\n\n  x4_activeParms.x0_animState =\n      (useState != EKnockBackAnimationState::Invalid) ? useState : EKnockBackAnimationState::None;\n\n  bool disableFollowup = false;\n  switch (x4_activeParms.x4_animFollowup) {\n  case EKnockBackAnimationFollowUp::Freeze:\n    disableFollowup = !x81_25_enableFreeze;\n    break;\n  case EKnockBackAnimationFollowUp::Shock:\n    disableFollowup = !x81_26_enableShock;\n    break;\n  case EKnockBackAnimationFollowUp::Burn:\n    disableFollowup = !x81_27_enableBurn;\n    break;\n  case EKnockBackAnimationFollowUp::ExplodeDeath:\n  case EKnockBackAnimationFollowUp::IceDeath:\n    disableFollowup = !x81_29_enableExplodeDeath;\n    break;\n  case EKnockBackAnimationFollowUp::BurnDeath:\n    disableFollowup = !x81_28_enableBurnDeath;\n    break;\n  case EKnockBackAnimationFollowUp::LaggedBurnDeath:\n    disableFollowup = !x81_30_enableLaggedBurnDeath;\n    break;\n  default:\n    break;\n  }\n\n  if (disableFollowup) {\n    x4_activeParms.x4_animFollowup = EKnockBackAnimationFollowUp::None;\n    x4_activeParms.x8_followupDuration = 0.f;\n  }\n}\n\nfloat CKnockBackController::CalculateExtraHurlVelocity(CStateManager& mgr, float magnitude, float kbResistance) const {\n  if (magnitude <= kbResistance) {\n    return 0.f;\n  }\n\n  return (1.1f - 0.2f * mgr.GetActiveRandom()->Float()) * 2.f * (magnitude - kbResistance);\n}\n\nvoid CKnockBackController::DoKnockBackAnimation(const zeus::CVector3f& backVec, CStateManager& mgr, CPatterned& parent,\n                                                float magnitude) {\n  switch (x4_activeParms.x0_animState) {\n  case EKnockBackAnimationState::Hurled: {\n    float hurlVel = 5.f;\n    if (CHealthInfo* hInfo = parent.HealthInfo(mgr)) {\n      hurlVel += CalculateExtraHurlVelocity(mgr, magnitude, hInfo->GetKnockbackResistance());\n    }\n    hurlVel = std::sqrt(parent.GetGravityConstant() * 0.5f * hurlVel);\n    const zeus::CVector3f backUpVec = backVec + backVec.magnitude() * zeus::skUp;\n    if (backUpVec.canBeNormalized()) {\n      parent.GetBodyController()->GetCommandMgr().DeliverCmd(CBCHurledCmd(-backVec, backUpVec.normalized() * hurlVel));\n      parent.SetMomentumWR({0.f, 0.f, parent.GetGravityConstant() * -parent.GetMass()});\n    }\n    break;\n  }\n  case EKnockBackAnimationState::Fall: {\n    parent.GetBodyController()->GetCommandMgr().DeliverCmd(CBCKnockDownCmd(-backVec, x7c_severity));\n    break;\n  }\n  case EKnockBackAnimationState::KnockBack: {\n    parent.GetBodyController()->GetCommandMgr().DeliverCmd(CBCKnockBackCmd(-backVec, x7c_severity));\n    break;\n  }\n  case EKnockBackAnimationState::Flinch: {\n    const std::pair<float, s32> bestAnim = parent.GetBodyController()->GetPASDatabase().FindBestAnimation(\n        CPASAnimParmData(pas::EAnimationState::AdditiveFlinch), *mgr.GetActiveRandom(), -1);\n    if (bestAnim.first > 0.f) {\n      parent.GetModelData()->GetAnimationData()->AddAdditiveAnimation(bestAnim.second, 1.f, false, true);\n      x64_flinchRemTime =\n          std::max(parent.GetModelData()->GetAnimationData()->GetAnimationDuration(bestAnim.second), x64_flinchRemTime);\n    }\n    break;\n  }\n  default:\n    break;\n  }\n}\n\nvoid CKnockBackController::ResetKnockBackImpulse(const CPatterned& parent, const zeus::CVector3f& backVec,\n                                                 float magnitude) {\n  if (!x81_24_autoResetImpulse || x4_activeParms.x0_animState != EKnockBackAnimationState::KnockBack ||\n      x4_activeParms.x4_animFollowup == EKnockBackAnimationFollowUp::Freeze) {\n    return;\n  }\n  x50_impulseDir = backVec.canBeNormalized() ? backVec.normalized() : -parent.GetTransform().frontVector();\n  if (x60_impulseRemTime <= 0.f) {\n    x5c_impulseMag = magnitude;\n  } else {\n    x5c_impulseMag += magnitude * (1.f - x60_impulseRemTime / ImpulseDurationTable[x20_impulseDurationIdx]);\n  }\n  x60_impulseRemTime = ImpulseDurationTable[x20_impulseDurationIdx];\n}\n\nvoid CKnockBackController::DoDeferredKnockBack(CStateManager& mgr, CPatterned& parent) {\n  if (x14_deferWeaponType == EWeaponType::Wave) {\n    x4_activeParms = KnockBackParmsTable[size_t(x0_variant)][size_t(EKnockBackWeaponType::WaveComboedDirect)]\n                                        [size_t(GetKnockBackCharacterState(parent))];\n    ValidateState(parent);\n    if (parent.HealthInfo(mgr) != nullptr) {\n      const zeus::CVector3f backVec = -parent.GetTransform().basis[1];\n      DoKnockBackAnimation(backVec, mgr, parent, 10.f);\n      ResetKnockBackImpulse(parent, backVec, 2.f);\n      x82_25_inDeferredKnockBack = true;\n      parent.KnockBack(backVec, mgr, CDamageInfo(CWeaponMode{x14_deferWeaponType, false, true, false}, 0.f, 0.f, 10.f),\n                       EKnockBackType::Direct, x82_25_inDeferredKnockBack, 10.f);\n      x82_25_inDeferredKnockBack = false;\n    }\n  }\n  x68_deferRemTime = 0.f;\n  x4_activeParms = KnockBackParms{};\n  x14_deferWeaponType = EWeaponType::None;\n}\n\nvoid CKnockBackController::sub80233d40(int i, float f1, float f2) {\n  if (i < 0 || i > 4) {\n    return;\n  }\n  x24_[i] = std::make_pair(f1, f2);\n}\n\nvoid CKnockBackController::SetAutoResetImpulse(bool b) {\n  x81_24_autoResetImpulse = b;\n\n  if (b) {\n    return;\n  }\n\n  x5c_impulseMag = 0.f;\n  x60_impulseRemTime = 0.f;\n}\n\nvoid CKnockBackController::Update(float dt, CStateManager& mgr, CPatterned& parent) {\n  ApplyImpulse(dt, parent);\n  x64_flinchRemTime -= dt;\n  if (TickDeferredTimer(dt)) {\n    DoDeferredKnockBack(mgr, parent);\n  }\n  if (x82_26_locomotionDuringElectrocution && parent.GetBodyController()->IsElectrocuting()) {\n    parent.GetBodyController()->GetCommandMgr().DeliverCmd(CBodyStateCmd(EBodyStateCmd::Locomotion));\n  }\n}\n\nEKnockBackWeaponType CKnockBackController::GetKnockBackWeaponType(const CDamageInfo& info, EWeaponType wType,\n                                                                  EKnockBackType type) {\n  int stacking = 0;\n  if (info.GetWeaponMode().IsCharged()) {\n    stacking = 1;\n  } else if (info.GetWeaponMode().IsComboed()) {\n    stacking = 2;\n  }\n\n  switch (wType) {\n  case EWeaponType::Power:\n    return EKnockBackWeaponType(type != EKnockBackType::Radius ? stacking : stacking + 1);\n  case EWeaponType::Ice:\n    return EKnockBackWeaponType(type != EKnockBackType::Radius ? stacking + 8 : stacking + 9);\n  case EWeaponType::Wave:\n    return EKnockBackWeaponType(stacking + 4);\n  case EWeaponType::Plasma:\n    return EKnockBackWeaponType(stacking + 12);\n  case EWeaponType::Bomb:\n    return EKnockBackWeaponType::Bomb;\n  case EWeaponType::PowerBomb:\n    return EKnockBackWeaponType::PowerBomb;\n  case EWeaponType::Missile:\n    return EKnockBackWeaponType::Missile;\n  case EWeaponType::Phazon:\n    return EKnockBackWeaponType::Phazon;\n  default:\n    return EKnockBackWeaponType::Invalid;\n  }\n}\n\nvoid CKnockBackController::SelectDamageState(const CPatterned& parent, const CDamageInfo& info, EWeaponType wType,\n                                             EKnockBackType type) {\n  x4_activeParms = KnockBackParms();\n\n  const EKnockBackWeaponType weaponType = GetKnockBackWeaponType(info, wType, type);\n  if (weaponType == EKnockBackWeaponType::Invalid) {\n    return;\n  }\n\n  x4_activeParms =\n      KnockBackParmsTable[size_t(x0_variant)][size_t(weaponType)][size_t(GetKnockBackCharacterState(parent))];\n  ValidateState(parent);\n}\n\nvoid CKnockBackController::KnockBack(const zeus::CVector3f& backVec, CStateManager& mgr, CPatterned& parent,\n                                     const CDamageInfo& info, EKnockBackType type, float magnitude) {\n  if (x82_25_inDeferredKnockBack) {\n    return;\n  }\n\n  zeus::CVector3f vec(backVec.toVec2f());\n  if (!vec.isMagnitudeSafe()) {\n    vec = -parent.GetTransform().frontVector();\n  }\n\n  SelectDamageState(parent, info, info.GetWeaponMode().GetType(), type);\n  DoKnockBackAnimation(vec, mgr, parent, magnitude);\n  ResetKnockBackImpulse(parent, vec, 2.f);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CKnockBackController.hpp",
    "content": "#pragma once\n\n#include <bitset>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Character/CharacterCommon.hpp\"\n#include \"Runtime/Weapon/WeaponCommon.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CDamageInfo;\nclass CPatterned;\n\nenum class EKnockBackType { Radius, Direct };\n\nenum class EKnockBackVariant { Small, Medium, Large };\n\nenum class EKnockBackWeaponType {\n  Invalid = -1,\n  Power,\n  PowerCharged,\n  PowerComboed,\n  PowerComboedDirect,\n  Wave,\n  WaveCharged,\n  WaveComboed,\n  WaveComboedDirect,\n  Ice,\n  IceCharged,\n  IceComboed,\n  IceComboedDirect,\n  Plasma,\n  PlasmaCharged,\n  PlasmaComboed,\n  Missile,\n  Bomb,\n  PowerBomb,\n  Phazon\n};\n\nenum class EKnockBackCharacterState { Alive, Dead, FrozenAlive, FrozenDead };\n\nenum class EKnockBackAnimationState { Invalid = -1, None, Flinch, KnockBack, Hurled, Fall };\n\nenum class EKnockBackAnimationFollowUp {\n  Invalid = -1,\n  None,\n  Freeze,\n  Shock,\n  Burn,\n  PhazeOut,\n  Death,\n  ExplodeDeath,\n  IceDeath,\n  BurnDeath,\n  LaggedBurnDeath\n};\n\nclass CKnockBackController {\npublic:\n  struct KnockBackParms {\n    EKnockBackAnimationState x0_animState = EKnockBackAnimationState::None;\n    EKnockBackAnimationFollowUp x4_animFollowup = EKnockBackAnimationFollowUp::None;\n    float x8_followupDuration = 0.f;\n    float xc_intoFreezeDur = 0.f;\n  };\n\nprivate:\n  friend class CPatterned;\n  EKnockBackVariant x0_variant;\n  KnockBackParms x4_activeParms{};\n  EWeaponType x14_deferWeaponType = EWeaponType::None;\n  EKnockBackAnimationState x18_minAnimState = EKnockBackAnimationState::None;\n  EKnockBackAnimationState x1c_maxAnimState = EKnockBackAnimationState::Fall;\n  u32 x20_impulseDurationIdx = 0;\n  rstl::reserved_vector<std::pair<float, float>, 5> x24_;\n  zeus::CVector3f x50_impulseDir;\n  float x5c_impulseMag = 0.f;\n  float x60_impulseRemTime = 0.f;\n  float x64_flinchRemTime = 0.f;\n  float x68_deferRemTime = 0.f;\n  u32 x6c_ = 0;\n  u32 x70_ = 0;\n  u32 x74_ = 0;\n  pas::ESeverity x7c_severity = pas::ESeverity::One;\n  std::bitset<5> x80_availableStates{0b11111};\n  bool x81_24_autoResetImpulse : 1 = true;\n  bool x81_25_enableFreeze : 1 = true;\n  bool x81_26_enableShock : 1 = false;\n  bool x81_27_enableBurn : 1 = true;\n  bool x81_28_enableBurnDeath : 1 = true;\n  bool x81_29_enableExplodeDeath : 1 = true;\n  bool x81_30_enableLaggedBurnDeath : 1 = true;\n  bool x81_31_ : 1 = true;\n  bool x82_24_ : 1 = true;\n  bool x82_25_inDeferredKnockBack : 1 = false;\n  bool x82_26_locomotionDuringElectrocution : 1 = false;\n  void ApplyImpulse(float dt, CPatterned& parent);\n  bool TickDeferredTimer(float dt);\n  EKnockBackCharacterState GetKnockBackCharacterState(const CPatterned& parent) const;\n  void ValidateState(const CPatterned& parent);\n  float CalculateExtraHurlVelocity(CStateManager& mgr, float magnitude, float kbResistance) const;\n  void DoKnockBackAnimation(const zeus::CVector3f& backVec, CStateManager& mgr, CPatterned& parent, float magnitude);\n  void ResetKnockBackImpulse(const CPatterned& parent, const zeus::CVector3f& backVec, float magnitude);\n  void DoDeferredKnockBack(CStateManager& mgr, CPatterned& parent);\n  EKnockBackWeaponType GetKnockBackWeaponType(const CDamageInfo& info, EWeaponType wType, EKnockBackType type);\n  void SelectDamageState(const CPatterned& parent, const CDamageInfo& info, EWeaponType wType, EKnockBackType type);\n\npublic:\n  explicit CKnockBackController(EKnockBackVariant variant);\n  void SetKnockBackVariant(EKnockBackVariant v) { x0_variant = v; }\n  void DeferKnockBack(EWeaponType tp) {\n    x14_deferWeaponType = tp;\n    x68_deferRemTime = 0.05f;\n  }\n  void sub80233d40(int i, float f1, float f2);\n  void SetAutoResetImpulse(bool b);\n  void SetImpulseDurationIdx(u32 i) { x20_impulseDurationIdx = i; }\n  void SetAnimationStateRange(EKnockBackAnimationState a, EKnockBackAnimationState b) {\n    x18_minAnimState = a;\n    x1c_maxAnimState = b;\n  }\n  void Update(float dt, CStateManager& mgr, CPatterned& parent);\n  void KnockBack(const zeus::CVector3f& backVec, CStateManager& mgr, CPatterned& parent, const CDamageInfo& info,\n                 EKnockBackType type, float magnitude);\n  void SetSeverity(pas::ESeverity v) { x7c_severity = v; }\n  void SetEnableFreeze(bool b) { x81_25_enableFreeze = b; }\n  bool GetEnableFreeze() const { return x81_25_enableFreeze; }\n  void SetEnableShock(bool b) { x81_26_enableShock = b; }\n  void SetEnableBurn(bool b) { x81_27_enableBurn = b; }\n  void SetEnableBurnDeath(bool b) { x81_28_enableBurnDeath = b; }\n  void SetEnableExplodeDeath(bool b) { x81_29_enableExplodeDeath = b; }\n  void SetEnableLaggedBurnDeath(bool b) { x81_30_enableLaggedBurnDeath = b; }\n  void SetX81_31(bool b) { x81_31_ = b; }\n  void SetX82_24(bool b) { x82_24_ = b; }\n  void SetLocomotionDuringElectrocution(bool b) { x82_26_locomotionDuringElectrocution = b; }\n  const KnockBackParms& GetActiveParms() const { return x4_activeParms; }\n  EKnockBackVariant GetVariant() const { return x0_variant; }\n  float GetFlinchRemTime() const { return x64_flinchRemTime; }\n  void SetAvailableState(EKnockBackAnimationState s, bool b) { x80_availableStates.set(size_t(s), b); }\n  bool TestAvailableState(EKnockBackAnimationState s) const { return x80_availableStates.test(size_t(s)); }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CLightParameters.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Character/CActorLights.hpp\"\n\n#include <zeus/CColor.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\n\nclass CLightParameters {\n  friend class CActor;\n\npublic:\n  enum class EShadowTesselation { Invalid = -1, Zero };\n\n  enum class EWorldLightingOptions { Zero, NormalWorld, NoShadowCast, DisableWorld };\n\n  enum class ELightRecalculationOptions { LargeFrameCount, EightFrames, FourFrames, OneFrame };\n\nprivate:\n  bool x4_a = false;\n  float x8_b = 0.f;\n  EShadowTesselation xc_shadowTesselation = EShadowTesselation::Zero;\n  float x10_d = 0.f;\n  float x14_e = 0.f;\n  zeus::CColor x18_noLightsAmbient;\n  bool x1c_makeLights = false;\n  bool x1d_ambientChannelOverflow = false;\n  EWorldLightingOptions x20_worldLightingOptions = EWorldLightingOptions::Zero;\n  ELightRecalculationOptions x24_lightRecalcOpts = ELightRecalculationOptions::EightFrames;\n  s32 x28_layerIdx = 0;\n  zeus::CVector3f x2c_actorPosBias;\n  s32 x38_maxDynamicLights = 4;\n  s32 x3c_maxAreaLights = 4;\n\npublic:\n  CLightParameters() = default;\n  CLightParameters(bool a, float b, EShadowTesselation shadowTess, float d, float e,\n                   const zeus::CColor& noLightsAmbient, bool makeLights, EWorldLightingOptions lightingOpts,\n                   ELightRecalculationOptions lightRecalcOpts, const zeus::CVector3f& actorPosBias,\n                   s32 maxDynamicLights, s32 maxAreaLights, bool ambChannelOverflow, s32 layerIdx)\n  : x4_a(a)\n  , x8_b(b)\n  , xc_shadowTesselation(shadowTess)\n  , x10_d(d)\n  , x14_e(e)\n  , x18_noLightsAmbient(noLightsAmbient)\n  , x1c_makeLights(makeLights)\n  , x1d_ambientChannelOverflow(ambChannelOverflow)\n  , x20_worldLightingOptions(lightingOpts)\n  , x24_lightRecalcOpts(lightRecalcOpts)\n  , x28_layerIdx(layerIdx)\n  , x2c_actorPosBias(actorPosBias)\n  , x38_maxDynamicLights(maxDynamicLights)\n  , x3c_maxAreaLights(maxAreaLights) {\n    if (x38_maxDynamicLights > 4 || x38_maxDynamicLights == -1)\n      x38_maxDynamicLights = 4;\n    if (x3c_maxAreaLights > 4 || x3c_maxAreaLights == -1)\n      x3c_maxAreaLights = 4;\n  }\n  static CLightParameters None() { return CLightParameters(); }\n\n  static u32 GetFramesBetweenRecalculation(ELightRecalculationOptions opts) {\n    if (opts == ELightRecalculationOptions::LargeFrameCount)\n      return 0x3FFFFFFF;\n    else if (opts == ELightRecalculationOptions::EightFrames)\n      return 8;\n    else if (opts == ELightRecalculationOptions::FourFrames)\n      return 4;\n    else if (opts == ELightRecalculationOptions::OneFrame)\n      return 1;\n    return 8;\n  }\n\n  std::unique_ptr<CActorLights> MakeActorLights() const {\n    if (!x1c_makeLights) {\n      return nullptr;\n    }\n\n    const u32 updateFrames = GetFramesBetweenRecalculation(x24_lightRecalcOpts);\n    auto lights = std::make_unique<CActorLights>(updateFrames, x2c_actorPosBias, x38_maxDynamicLights,\n                                                 x3c_maxAreaLights, x1d_ambientChannelOverflow, x28_layerIdx == 1,\n                                                 x20_worldLightingOptions == EWorldLightingOptions::DisableWorld, 0.1f);\n    if (x20_worldLightingOptions == EWorldLightingOptions::NoShadowCast) {\n      lights->SetCastShadows(false);\n    }\n    if (x3c_maxAreaLights == 0) {\n      lights->SetAmbientColor(x18_noLightsAmbient);\n    }\n    return lights;\n  }\n  const zeus::CColor& GetNoLightsAmbient() const { return x18_noLightsAmbient; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CMakeLists.txt",
    "content": "set(WORLD_SOURCES\n        CWorld.hpp CWorld.cpp\n        CWorldLight.hpp CWorldLight.cpp\n        IGameArea.hpp IGameArea.cpp\n        CGameArea.hpp CGameArea.cpp\n        CPlayer.hpp CPlayer.cpp\n        CPlayerEnergyDrain.hpp CPlayerEnergyDrain.cpp\n        CEnergyDrainSource.hpp CEnergyDrainSource.cpp\n        CPlayerCameraBob.hpp CPlayerCameraBob.cpp\n        CScriptCameraShaker.hpp CScriptCameraShaker.cpp\n        CMorphBall.hpp CMorphBall.cpp\n        CMorphBallShadow.hpp CMorphBallShadow.cpp\n        CActor.hpp CActor.cpp\n        CAi.hpp CAi.cpp\n        CAiFuncMap.hpp CAiFuncMap.cpp\n        CStateMachine.hpp CStateMachine.cpp\n        CPatterned.hpp CPatterned.cpp\n        CKnockBackController.hpp CKnockBackController.cpp\n        CPathFindArea.hpp CPathFindArea.cpp\n        CPathFindRegion.hpp CPathFindRegion.cpp\n        CPathFindSearch.hpp CPathFindSearch.cpp\n        CPathFindSpline.cpp\n        CPhysicsActor.hpp CPhysicsActor.cpp\n        CEntity.hpp CEntity.cpp\n        CPhysicsActor.hpp CPhysicsActor.cpp\n        CWorldTransManager.hpp CWorldTransManager.cpp\n        CEnvFxManager.hpp CEnvFxManager.cpp\n        CActorModelParticles.hpp CActorModelParticles.cpp\n        CTeamAiTypes.hpp\n        ScriptObjectSupport.hpp ScriptObjectSupport.cpp\n        ScriptLoader.hpp ScriptLoader.cpp\n        CScriptActor.hpp CScriptActor.cpp\n        CScriptWaypoint.hpp CScriptWaypoint.cpp\n        CScriptDoor.hpp CScriptDoor.cpp\n        CScriptTrigger.hpp CScriptTrigger.cpp\n        CScriptTimer.hpp CScriptTimer.cpp\n        CScriptCounter.hpp CScriptCounter.cpp\n        CScriptEffect.hpp CScriptEffect.cpp\n        CScriptSteam.hpp CScriptSteam.cpp\n        CScriptRipple.hpp CScriptRipple.cpp\n        CScriptBallTrigger.hpp CScriptBallTrigger.cpp\n        CScriptPlatform.hpp CScriptPlatform.cpp\n        CScriptSound.hpp CScriptSound.cpp\n        CScriptGenerator.hpp CScriptGenerator.cpp\n        CScriptDock.hpp CScriptDock.cpp\n        CScriptActorKeyframe.hpp CScriptActorKeyframe.cpp\n        CScriptWater.hpp CScriptWater.cpp\n        CScriptGrapplePoint.hpp CScriptGrapplePoint.cpp\n        CScriptSpiderBallAttractionSurface.hpp CScriptSpiderBallAttractionSurface.cpp\n        CScriptPickupGenerator.hpp CScriptPickupGenerator.cpp\n        CScriptPointOfInterest.hpp CScriptPointOfInterest.cpp\n        CScriptAreaAttributes.hpp CScriptAreaAttributes.cpp\n        CScriptVisorFlare.hpp CScriptVisorFlare.cpp\n        CScriptWorldTeleporter.hpp CScriptWorldTeleporter.cpp\n        CScriptCameraWaypoint.hpp CScriptCameraWaypoint.cpp\n        CScriptCoverPoint.hpp CScriptCoverPoint.cpp\n        CScriptSpiderBallWaypoint.hpp CScriptSpiderBallWaypoint.cpp\n        CScriptSpawnPoint.hpp CScriptSpawnPoint.cpp\n        CScriptCameraHint.hpp CScriptCameraHint.cpp\n        CScriptPickup.hpp CScriptPickup.cpp\n        CScriptMemoryRelay.hpp CScriptMemoryRelay.cpp\n        CScriptRandomRelay.hpp CScriptRandomRelay.cpp\n        CScriptRelay.hpp CScriptRelay.cpp\n        CScriptHUDMemo.hpp CScriptHUDMemo.cpp\n        CScriptCameraFilterKeyframe.hpp CScriptCameraFilterKeyframe.cpp\n        CScriptCameraBlurKeyframe.hpp CScriptCameraBlurKeyframe.cpp\n        CScriptDamageableTrigger.hpp CScriptDamageableTrigger.cpp\n        CScriptDebris.hpp CScriptDebris.cpp\n        CScriptDebugCameraWaypoint.hpp CScriptDebugCameraWaypoint.cpp\n        CScriptDistanceFog.hpp CScriptDistanceFog.cpp\n        CScriptDockAreaChange.hpp CScriptDockAreaChange.cpp\n        CScriptActorRotate.hpp CScriptActorRotate.cpp\n        CScriptSpecialFunction.hpp CScriptSpecialFunction.cpp\n        CScriptPlayerHint.hpp CScriptPlayerHint.cpp\n        CScriptPlayerStateChange.hpp CScriptPlayerStateChange.cpp\n        CScriptTargetingPoint.hpp CScriptTargetingPoint.cpp\n        CScriptEMPulse.hpp CScriptEMPulse.cpp\n        CScriptPlayerActor.hpp CScriptPlayerActor.cpp\n        CFishCloud.hpp CFishCloud.cpp\n        CFishCloudModifier.hpp CFishCloudModifier.cpp\n        CScriptSwitch.hpp CScriptSwitch.cpp\n        CWallWalker.hpp CWallWalker.cpp\n        CWallCrawlerSwarm.hpp CWallCrawlerSwarm.cpp\n        CScriptAiJumpPoint.hpp CScriptAiJumpPoint.cpp\n        CScriptRoomAcoustics.hpp CScriptRoomAcoustics.cpp\n        CScriptColorModulate.hpp CScriptColorModulate.cpp\n        CScriptStreamedMusic.hpp CScriptStreamedMusic.cpp\n        CScriptMidi.hpp CScriptMidi.cpp\n        CRepulsor.hpp CRepulsor.cpp\n        CScriptGunTurret.hpp CScriptGunTurret.cpp\n        CScriptCameraPitchVolume.hpp CScriptCameraPitchVolume.cpp\n        CTeamAiMgr.hpp CTeamAiMgr.cpp\n        CSnakeWeedSwarm.hpp CSnakeWeedSwarm.cpp\n        CScriptSpindleCamera.hpp CScriptSpindleCamera.cpp\n        CScriptCameraHintTrigger.hpp CScriptCameraHintTrigger.cpp\n        CAmbientAI.hpp CAmbientAI.cpp\n        CScriptBeam.hpp CScriptBeam.cpp\n        CScriptMazeNode.hpp CScriptMazeNode.cpp\n        CScriptShadowProjector.hpp CScriptShadowProjector.cpp\n        CScriptStreamedMusic.hpp CScriptStreamedMusic.cpp\n        CScriptRoomAcoustics.hpp CScriptRoomAcoustics.cpp\n        CScriptControllerAction.hpp CScriptControllerAction.cpp\n        CVisorFlare.hpp CVisorFlare.cpp\n        CScriptVisorGoo.hpp CScriptVisorGoo.cpp\n        CGrappleParameters.hpp\n        CActorParameters.hpp\n        CLightParameters.hpp\n        CScannableParameters.hpp\n        CVisorParameters.hpp\n        CAnimationParameters.hpp\n        CDamageInfo.hpp CDamageInfo.cpp\n        CDamageVulnerability.hpp CDamageVulnerability.cpp\n        CFluidPlaneManager.hpp CFluidPlaneManager.cpp\n        CFluidUVMotion.hpp CFluidUVMotion.cpp\n        CPatternedInfo.hpp CPatternedInfo.cpp\n        CHealthInfo.hpp CHealthInfo.cpp\n        CPatterned.hpp CPatterned.cpp\n        CHUDMemoParms.hpp\n        CWorldShadow.hpp CWorldShadow.cpp\n        CProjectedShadow.hpp CProjectedShadow.cpp\n        CGameLight.hpp CGameLight.cpp\n        CFluidPlane.hpp CFluidPlane.cpp\n        CFluidPlaneCPU.hpp CFluidPlaneCPU.cpp\n        CFluidPlaneGPU.hpp CFluidPlaneGPU.cpp\n        CFluidPlaneDoor.hpp CFluidPlaneDoor.cpp\n        CRippleManager.hpp CRippleManager.cpp\n        CRipple.hpp CRipple.cpp\n        CDestroyableRock.hpp CDestroyableRock.cpp\n        CEffect.hpp CEffect.cpp\n        CHUDBillboardEffect.hpp CHUDBillboardEffect.cpp\n        CExplosion.hpp CExplosion.cpp\n        CIceImpact.hpp CIceImpact.cpp\n        CMarkerGrid.hpp CMarkerGrid.cpp\n        CFire.hpp CFire.cpp\n        CEntityInfo.hpp)\n\nruntime_add_list(World WORLD_SOURCES)\n"
  },
  {
    "path": "Runtime/World/CMarkerGrid.cpp",
    "content": "#include \"Runtime/World/CMarkerGrid.hpp\"\n\nnamespace metaforce {\nCMarkerGrid::CMarkerGrid(const zeus::CAABox& bounds) : x0_bounds(bounds) {\n  x18_gridUnits = zeus::CVector3f((bounds.max - bounds.min) * 0.0625f);\n  x24_gridState.resize(0x400);\n}\n\nvoid CMarkerGrid::MarkCells(const zeus::CSphere& area, u32 val) {\n  int width_units = static_cast<int>((area.radius - x18_gridUnits.x()) / x18_gridUnits.x());\n  int length_units = static_cast<int>((area.radius - x18_gridUnits.y()) / x18_gridUnits.y());\n  int height_units = static_cast<int>((area.radius - x18_gridUnits.z()) / x18_gridUnits.z());\n  u32 x_coord, y_coord, z_coord;\n  if (!GetCoords(area.position, x_coord, y_coord, z_coord)) {\n    return;\n  }\n  for (int i = width_units - z_coord; i < (z_coord + width_units); i++) {\n    for (int j = length_units - y_coord; j < (y_coord + length_units); j++) {\n      for (int k = height_units - x_coord; k < (z_coord + height_units); k++) {\n        u32 new_cell_val = val | GetValue(x_coord, y_coord, z_coord);\n        SetValue(k, j, i, new_cell_val);\n      }\n    }\n  }\n}\n\nbool CMarkerGrid::GetCoords(zeus::CVector3f const& vec, u32& x, u32& y, u32& z) const {\n  if (x0_bounds.pointInside(vec)) {\n    x = static_cast<u32>((vec.x() - x0_bounds.min.x()) / x18_gridUnits.x());\n    y = static_cast<u32>((vec.y() - x0_bounds.min.y()) / x18_gridUnits.y());\n    z = static_cast<u32>((vec.z() - x0_bounds.min.z()) / x18_gridUnits.z());\n    return true;\n  }\n  return false;\n}\n\nu32 CMarkerGrid::GetValue(u32 x, u32 y, u32 z) const {\n  const u32 bit_offset = (x & 3) << 1;\n  u8 marker_byte = x24_gridState[(z << 6) + (y << 2) + (x >> 2)];\n  return static_cast<u32>((marker_byte & (3 << bit_offset)) >> bit_offset);\n}\n\nvoid CMarkerGrid::SetValue(u32 x, u32 y, u32 z, u32 val) {\n  const u32 bit_offset = (x & 3) << 1;\n  const u32 grid_offset = (z << 6) + (y << 2) + (x >> 2);\n  u8 marker_byte = x24_gridState[grid_offset];\n  marker_byte |= (marker_byte & ~(3 << bit_offset)) | (val << bit_offset);\n  x24_gridState[grid_offset] = marker_byte;\n}\n\nbool CMarkerGrid::AABoxTouchesData(const zeus::CAABox& box, u32 val) const {\n  if (!x0_bounds.intersects(box)) {\n    return false;\n  }\n  zeus::CAABox in_box = box;\n  if (!box.inside(x0_bounds)) {\n    zeus::CVector3f max_of_min(x0_bounds.min.x() > box.min.x() ? x0_bounds.min.x() : box.min.x(),\n                               x0_bounds.min.y() > box.min.y() ? x0_bounds.min.y() : box.min.y(),\n                               x0_bounds.min.z() > box.min.z() ? x0_bounds.min.z() : box.min.z());\n    zeus::CVector3f min_of_max(x0_bounds.max.x() < box.max.x() ? x0_bounds.max.x() : box.max.x(),\n                               x0_bounds.max.y() < box.max.y() ? x0_bounds.max.y() : box.max.y(),\n                               x0_bounds.max.z() < box.max.z() ? x0_bounds.max.z() : box.max.z());\n    in_box = zeus::CAABox(max_of_min, min_of_max);\n  }\n  u32 c1x, c1y, c1z, c2x, c2y, c2z;\n  GetCoords(in_box.min, c1x, c1y, c1z);\n  GetCoords(in_box.max, c2x, c2y, c2z);\n\n  for (int i = c1z; i < c2z; i++) {\n    for (int j = c1y; j < c2y; j++) {\n      for (int k = c1x; k < c2x; k++) {\n        if ((GetValue(k, j, i) & val) != 0u) {\n          return true;\n        }\n      }\n    }\n  }\n  return false;\n}\n\nzeus::CVector3f CMarkerGrid::GetWorldPositionForCell(u32 x, u32 y, u32 z) const {\n  // returns the center of a given cell\n  return zeus::CVector3f(static_cast<float>(x), static_cast<float>(y), static_cast<float>(z)) * x18_gridUnits +\n         x0_bounds.min + (x18_gridUnits / 2.f);\n}\n} // namespace urde\n"
  },
  {
    "path": "Runtime/World/CMarkerGrid.hpp",
    "content": "#pragma once\n\n#include <zeus/CAABox.hpp>\n#include \"Runtime/RetroTypes.hpp\"\n\nnamespace metaforce {\nclass CMarkerGrid {\nprivate:\n  zeus::CAABox x0_bounds;\n  zeus::CVector3f x18_gridUnits;\n  rstl::reserved_vector<u8, 0x400> x24_gridState;\n\npublic:\n  CMarkerGrid(const zeus::CAABox& bounds);\n  void MarkCells(const zeus::CSphere& area, u32 val);\n  bool GetCoords(const zeus::CVector3f& vec, u32& x, u32& y, u32& z) const;\n  u32 GetValue(u32 x, u32 y, u32 z) const;\n  void SetValue(u32 x, u32 y, u32 z, u32 val);\n\n  bool AABoxTouchesData(const zeus::CAABox& box, u32 val) const;\n  zeus::CVector3f GetWorldPositionForCell(u32 x, u32 y, u32 z) const;\n  const zeus::CAABox& GetBounds() const { return x0_bounds; }\n};\n} // namespace urde"
  },
  {
    "path": "Runtime/World/CMorphBall.cpp",
    "content": "#include \"Runtime/World/CMorphBall.hpp\"\n\n#include <array>\n\n#include \"Runtime/CDependencyGroup.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/Camera/CGameCamera.hpp\"\n#include \"Runtime/Collision/CGameCollision.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CSkinnedModel.hpp\"\n#include \"Runtime/Input/ControlMapper.hpp\"\n#include \"Runtime/MP1/World/CMetroidBeta.hpp\"\n#include \"Runtime/World/CGameLight.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CScriptAreaAttributes.hpp\"\n#include \"Runtime/World/CScriptSpiderBallAttractionSurface.hpp\"\n#include \"Runtime/World/CScriptSpiderBallWaypoint.hpp\"\n#include \"Runtime/World/CScriptWater.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n#include \"Runtime/ConsoleVariables/CVar.hpp\"\n#include \"Runtime/ConsoleVariables/CVarManager.hpp\"\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nnamespace {\nCVar* mb_spooderBall = nullptr;\nbool mb_spooderBallCached = false;\nfloat kSpiderBallCollisionRadius;\n\nconstexpr std::array<std::pair<const char*, u32>, 8> kBallCharacterTable{{\n    {\"SamusBallANCS\", 0},\n    {\"SamusBallANCS\", 0},\n    {\"SamusBallANCS\", 1},\n    {\"SamusBallANCS\", 0},\n    {\"SamusFusionBallANCS\", 0},\n    {\"SamusFusionBallANCS\", 2},\n    {\"SamusFusionBallANCS\", 1},\n    {\"SamusFusionBallANCS\", 3},\n}};\n\nconstexpr std::array<std::pair<const char*, u32>, 8> kBallLowPolyTable{{\n    {\"SamusBallLowPolyCMDL\", 0},\n    {\"SamusBallLowPolyCMDL\", 0},\n    {\"SamusBallLowPolyCMDL\", 1},\n    {\"SamusBallLowPolyCMDL\", 0},\n    {\"SamusBallFusionLowPolyCMDL\", 0},\n    {\"SamusBallFusionLowPolyCMDL\", 2},\n    {\"SamusBallFusionLowPolyCMDL\", 1},\n    {\"SamusBallFusionLowPolyCMDL\", 3},\n}};\n\nconstexpr std::array<std::pair<const char*, u32>, 8> kSpiderBallLowPolyTable{{\n    {\"SamusSpiderBallLowPolyCMDL\", 0},\n    {\"SamusSpiderBallLowPolyCMDL\", 0},\n    {\"SamusSpiderBallLowPolyCMDL\", 1},\n    {\"SamusSpiderBallLowPolyCMDL\", 2},\n    {\"SamusBallFusionLowPolyCMDL\", 0},\n    {\"SamusBallFusionLowPolyCMDL\", 2},\n    {\"SamusBallFusionLowPolyCMDL\", 1},\n    {\"SamusBallFusionLowPolyCMDL\", 3},\n}};\n\nconstexpr std::array<std::pair<const char*, u32>, 8> kSpiderBallCharacterTable{{\n    {\"SamusSpiderBallANCS\", 0},\n    {\"SamusSpiderBallANCS\", 0},\n    {\"SamusSpiderBallANCS\", 1},\n    {\"SamusPhazonBallANCS\", 0},\n    {\"SamusFusionBallANCS\", 0},\n    {\"SamusFusionBallANCS\", 2},\n    {\"SamusFusionBallANCS\", 1},\n    {\"SamusFusionBallANCS\", 3},\n}};\n\nconstexpr std::array<std::pair<const char*, u32>, 8> kSpiderBallGlassTable{{\n    {\"SamusSpiderBallGlassCMDL\", 0},\n    {\"SamusSpiderBallGlassCMDL\", 0},\n    {\"SamusSpiderBallGlassCMDL\", 1},\n    {\"SamusPhazonBallGlassCMDL\", 0},\n    {\"SamusSpiderBallGlassCMDL\", 0},\n    {\"SamusSpiderBallGlassCMDL\", 0},\n    {\"SamusSpiderBallGlassCMDL\", 1},\n    {\"SamusPhazonBallGlassCMDL\", 0},\n}};\n\nconstexpr std::array<u32, 8> kSpiderBallGlowColorIdxTable{\n    3, 3, 2, 4, 5, 7, 6, 8,\n};\n\nconstexpr std::array<u32, 8> kBallGlowColorIdxTable{\n    0, 0, 1, 0, 5, 7, 6, 8,\n};\n\n/* Maps material index to effect in generator array */\nconstexpr std::array<s32, 32> skWakeEffectMap{\n    -1, -1, -1, -1, -1, -1, -1,\n    0, // Phazon\n    2, // Dirt\n    3, // Lava\n    -1,\n    4, // Snow\n    5, // MudSlow\n    -1, -1, -1, -1,\n    6, // Sand\n    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,\n};\n\nconstexpr std::array<u16, 24> skBallRollSfx{\n    0xFFFF,\n    SFXsam_ballroll_stone,\n    SFXsam_ballroll_metal,\n    SFXsam_ballroll_grass,\n    SFXice_ballroll_ice,\n    0xFFFF,\n    SFXsam_ballroll_grate,\n    SFXsam_ballroll_phazon,\n    SFXsam_ballroll_dirt,\n    SFXlav_ballroll_lava,\n    SFXsam_ballroll_lavastone,\n    SFXice_ballroll_snow,\n    SFXsam_ballroll_mud,\n    0xFFFF,\n    SFXsam_ballroll_org,\n    SFXsam_ballroll_metal,\n    SFXsam_ballroll_metal,\n    SFXsam_ballroll_dirt,\n    0xFFFF,\n    0xFFFF,\n    0xFFFF,\n    0xFFFF,\n    SFXsam_ballroll_wood,\n    SFXsam_ballroll_org,\n};\n\nconstexpr std::array<u16, 24> skBallLandSfx{\n    0xFFFF,\n    SFXsam_ballland_stone,\n    SFXsam_ballland_metal,\n    SFXsam_ballland_grass,\n    SFXsam_ballland_ice,\n    0xFFFF,\n    SFXsam_ballland_grate,\n    SFXsam_ballland_phazon,\n    SFXsam_landdirt_00,\n    SFXsam_ballland_lava,\n    SFXsam_ballland_lava,\n    SFXsam_ballland_snow,\n    SFXsam_ballland_mud,\n    0xFFFF,\n    SFXsam_ballland_org,\n    SFXsam_ballland_metal,\n    SFXsam_ballland_metal,\n    SFXsam_landdirt_00,\n    0xFFFF,\n    0xFFFF,\n    0xFFFF,\n    0xFFFF,\n    SFXsam_ballland_wood,\n    SFXsam_ballland_org,\n};\n\nconstexpr std::array<CMorphBall::ColorArray, 9> skBallInnerGlowColors{{\n    {0xc2, 0x7e, 0x10},\n    {0x66, 0xc4, 0xff},\n    {0x60, 0xff, 0x90},\n    {0x33, 0x33, 0xff},\n    {0xff, 0x80, 0x80},\n    {0x0, 0x9d, 0xb6},\n    {0xd3, 0xf1, 0x0},\n    {0x60, 0x33, 0xff},\n    {0xfb, 0x98, 0x21},\n}};\n\nconstexpr std::array<CMorphBall::ColorArray, 9> BallSwooshColors{{\n    {0xC2, 0x8F, 0x17},\n    {0x70, 0xD4, 0xFF},\n    {0x6A, 0xFF, 0x8A},\n    {0x3D, 0x4D, 0xFF},\n    {0xC0, 0x00, 0x00},\n    {0x00, 0xBE, 0xDC},\n    {0xDF, 0xFF, 0x00},\n    {0xC4, 0x9E, 0xFF},\n    {0xFF, 0x9A, 0x22},\n}};\n\nconstexpr std::array<CMorphBall::ColorArray, 9> BallSwooshColorsCharged{{\n    {0xFF, 0xE6, 0x00},\n    {0xFF, 0xE6, 0x00},\n    {0xFF, 0xE6, 0x00},\n    {0xFF, 0xE6, 0x00},\n    {0xFF, 0x80, 0x20},\n    {0xFF, 0xE6, 0x00},\n    {0xFF, 0xE6, 0x00},\n    {0xFF, 0xE6, 0x00},\n    {0xFF, 0xE6, 0x00},\n}};\n\nconstexpr std::array<CMorphBall::ColorArray, 9> BallSwooshColorsJaggy{{\n    {0xFF, 0xCC, 0x00},\n    {0xFF, 0xCC, 0x00},\n    {0xFF, 0xCC, 0x00},\n    {0xFF, 0xCC, 0x00},\n    {0xFF, 0xD5, 0x19},\n    {0xFF, 0xCC, 0x00},\n    {0xFF, 0xCC, 0x00},\n    {0xFF, 0xCC, 0x00},\n    {0xFF, 0xCC, 0x00},\n}};\n} // Anonymous namespace\n\nconstexpr std::array<CMorphBall::ColorArray, 9> CMorphBall::BallGlowColors{{\n    {0xff, 0xff, 0xff},\n    {0xff, 0xff, 0xff},\n    {0xff, 0xff, 0xff},\n    {0xff, 0xff, 0xff},\n    {0xff, 0xd5, 0x19},\n    {0xff, 0xff, 0xff},\n    {0xff, 0xff, 0xff},\n    {0xff, 0xff, 0xff},\n    {0xff, 0xff, 0xff},\n}};\n\nconstexpr std::array<CMorphBall::ColorArray, 9> CMorphBall::BallTransFlashColors{{\n    {0xc2, 0x7e, 0x10},\n    {0x66, 0xc4, 0xff},\n    {0x60, 0xff, 0x90},\n    {0x33, 0x33, 0xff},\n    {0xff, 0x20, 0x20},\n    {0x0, 0x9d, 0xb6},\n    {0xd3, 0xf1, 0x0},\n    {0xa6, 0x86, 0xd8},\n    {0xfb, 0x98, 0x21},\n}};\n\nconstexpr std::array<CMorphBall::ColorArray, 9> CMorphBall::BallAuxGlowColors{{\n    {0xc2, 0x7e, 0x10},\n    {0x66, 0xc4, 0xff},\n    {0x6c, 0xff, 0x61},\n    {0x33, 0x33, 0xff},\n    {0xff, 0x20, 0x20},\n    {0x0, 0x9d, 0xb6},\n    {0xd3, 0xf1, 0x0},\n    {0xa6, 0x86, 0xd8},\n    {0xfb, 0x98, 0x21},\n}};\n\nCMorphBall::CMorphBall(CPlayer& player, float radius)\n: x0_player(player)\n, xc_radius(radius)\n, x38_collisionSphere({{0.f, 0.f, radius}, radius},\n                      {EMaterialTypes::Player, EMaterialTypes::Solid, EMaterialTypes::GroundCollider})\n, x58_ballModel(GetMorphBallModel(\"SamusBallANCS\", radius))\n, x60_spiderBallGlassModel(GetMorphBallModel(\"SamusSpiderBallGlassCMDL\", radius))\n, x68_lowPolyBallModel(GetMorphBallModel(\"SamusBallLowPolyCMDL\", radius))\n, x70_frozenBallModel(GetMorphBallModel(\"SamusBallFrozenCMDL\", radius))\n, x1968_slowBlueTailSwoosh(g_SimplePool->GetObj(\"SlowBlueTailSwoosh\"))\n, x1970_slowBlueTailSwoosh2(g_SimplePool->GetObj(\"SlowBlueTailSwoosh2\"))\n, x1978_jaggyTrail(g_SimplePool->GetObj(\"JaggyTrail\"))\n, x1980_wallSpark(g_SimplePool->GetObj(\"WallSpark\"))\n, x1988_ballInnerGlow(g_SimplePool->GetObj(\"BallInnerGlow\"))\n, x1990_spiderBallMagnetEffect(g_SimplePool->GetObj(\"SpiderBallMagnetEffect\"))\n, x1998_boostBallGlow(g_SimplePool->GetObj(\"BoostBallGlow\"))\n, x19a0_spiderElectric(g_SimplePool->GetObj(\"SpiderElectric\"))\n, x19a8_morphBallTransitionFlash(g_SimplePool->GetObj(\"MorphBallTransitionFlash\"))\n, x19b0_effect_morphBallIceBreak(g_SimplePool->GetObj(\"Effect_MorphBallIceBreak\")) {\n  x19b8_slowBlueTailSwooshGen = std::make_unique<CParticleSwoosh>(x1968_slowBlueTailSwoosh, 0);\n  x19bc_slowBlueTailSwooshGen2 = std::make_unique<CParticleSwoosh>(x1968_slowBlueTailSwoosh, 0);\n  x19c0_slowBlueTailSwoosh2Gen = std::make_unique<CParticleSwoosh>(x1970_slowBlueTailSwoosh2, 0);\n  x19c4_slowBlueTailSwoosh2Gen2 = std::make_unique<CParticleSwoosh>(x1970_slowBlueTailSwoosh2, 0);\n  x19c8_jaggyTrailGen = std::make_unique<CParticleSwoosh>(x1978_jaggyTrail, 0);\n  x19cc_wallSparkGen = std::make_unique<CElementGen>(x1980_wallSpark);\n  x19d0_ballInnerGlowGen = std::make_unique<CElementGen>(x1988_ballInnerGlow);\n  x19d4_spiderBallMagnetEffectGen = std::make_unique<CElementGen>(x1990_spiderBallMagnetEffect);\n  x19d8_boostBallGlowGen = std::make_unique<CElementGen>(x1998_boostBallGlow);\n  x1c14_worldShadow = std::make_unique<CWorldShadow>(128, 128, false);\n  x1c18_actorLights = std::make_unique<CActorLights>(8, zeus::skZero3f, 4, 4, false, false, false, 0.1f);\n  x1c1c_rainSplashGen = std::make_unique<CRainSplashGenerator>(x58_ballModel->GetScale(), 40, 2, 0.15f, 0.5f);\n\n  x19d4_spiderBallMagnetEffectGen->SetParticleEmission(false);\n  x19d4_spiderBallMagnetEffectGen->Update(1.0 / 60.0);\n\n  kSpiderBallCollisionRadius = GetBallRadius() + 0.2f;\n\n  for (size_t i = 0; i < x19e4_spiderElectricGens.capacity(); ++i) {\n    x19e4_spiderElectricGens.emplace_back(std::make_unique<CParticleSwoosh>(x19a0_spiderElectric, 0), false);\n  }\n\n  LoadAnimationTokens(\"SamusBallANCS\");\n  InitializeWakeEffects();\n  if (mb_spooderBall == nullptr) {\n    mb_spooderBall = CVarManager::instance()->findOrMakeCVar(\n        \"morphball.enableSpooderBall\",\n        \"Enables the ability to spiderball everywhere after touch a spiderball track with spiderball enabled\", false,\n        CVar::EFlags::Game | CVar::EFlags::Cheat);\n    mb_spooderBall->addListener([](CVar* cv) { mb_spooderBallCached = cv->toBoolean(); });\n  }\n}\n\nvoid CMorphBall::LoadAnimationTokens(std::string_view ancsName) {\n  const TToken<CDependencyGroup> dgrp = g_SimplePool->GetObj(std::string(ancsName).append(\"_DGRP\"));\n  x1958_animationTokens.clear();\n  x1958_animationTokens.reserve(dgrp->GetObjectTagVector().size());\n  for (const SObjectTag& tag : dgrp->GetObjectTagVector()) {\n    if (tag.type == FOURCC('CMDL') || tag.type == FOURCC('CSKR') || tag.type == FOURCC('TXTR')) {\n      continue;\n    }\n    x1958_animationTokens.push_back(g_SimplePool->GetObj(tag));\n    x1958_animationTokens.back().Lock();\n  }\n}\n\nvoid CMorphBall::InitializeWakeEffects() {\n  const TToken<CGenDescription> nullParticle =\n      CToken(TObjOwnerDerivedFromIObj<CGenDescription>::GetNewDerivedObject(std::make_unique<CGenDescription>()));\n  x1b84_wakeEffects.resize(x1b84_wakeEffects.capacity(), nullParticle);\n\n  x1b84_wakeEffects[2] = g_SimplePool->GetObj(\"DirtWake\");\n  x1b84_wakeEffects[0] = g_SimplePool->GetObj(\"PhazonWake\");\n  x1b84_wakeEffects[1] = g_SimplePool->GetObj(\"PhazonWakeOrange\");\n  x1b84_wakeEffects[3] = g_SimplePool->GetObj(\"LavaWake\");\n  x1b84_wakeEffects[4] = g_SimplePool->GetObj(\"SnowWake\");\n  x1b84_wakeEffects[5] = g_SimplePool->GetObj(\"MudWake\");\n  x1b84_wakeEffects[6] = g_SimplePool->GetObj(\"SandWake\");\n  x1b84_wakeEffects[7] = g_SimplePool->GetObj(\"RainWake\");\n\n  x1bc8_wakeEffectGens.resize(x1b84_wakeEffects.capacity());\n  x1bc8_wakeEffectGens[2] = std::make_unique<CElementGen>(x1b84_wakeEffects[2]);\n  x1bc8_wakeEffectGens[0] = std::make_unique<CElementGen>(x1b84_wakeEffects[0]);\n  x1bc8_wakeEffectGens[1] = std::make_unique<CElementGen>(x1b84_wakeEffects[1]);\n  x1bc8_wakeEffectGens[3] = std::make_unique<CElementGen>(x1b84_wakeEffects[3]);\n  x1bc8_wakeEffectGens[4] = std::make_unique<CElementGen>(x1b84_wakeEffects[4]);\n  x1bc8_wakeEffectGens[5] = std::make_unique<CElementGen>(x1b84_wakeEffects[5]);\n  x1bc8_wakeEffectGens[6] = std::make_unique<CElementGen>(x1b84_wakeEffects[6]);\n  x1bc8_wakeEffectGens[7] = std::make_unique<CElementGen>(x1b84_wakeEffects[7]);\n}\n\nstd::unique_ptr<CModelData> CMorphBall::GetMorphBallModel(const char* name, float radius) {\n  const SObjectTag* tag = g_ResFactory->GetResourceIdByName(name);\n  if (tag->type == FOURCC('CMDL')) {\n    return std::make_unique<CModelData>(CStaticRes(tag->id, zeus::CVector3f(radius * 2.f)));\n  } else {\n    return std::make_unique<CModelData>(CAnimRes(tag->id, 0, zeus::CVector3f(radius * 2.f), 0, false));\n  }\n}\n\nvoid CMorphBall::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) {\n  switch (msg) {\n  case EScriptObjectMessage::Registered:\n    if (x19d0_ballInnerGlowGen && x19d0_ballInnerGlowGen->SystemHasLight()) {\n      x1c10_ballInnerGlowLight = mgr.AllocateUniqueId();\n      auto* l = new CGameLight(x1c10_ballInnerGlowLight, kInvalidAreaId, false, \"BallLight\", GetBallToWorld(),\n                               x0_player.GetUniqueId(), x19d0_ballInnerGlowGen->GetLight(),\n                               u32(x1988_ballInnerGlow.GetObjectTag()->id.Value()), 0, 0.f);\n      mgr.AddObject(l);\n    }\n    break;\n  case EScriptObjectMessage::Deleted:\n    DeleteLight(mgr);\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CMorphBall::DrawBallShadow(const CStateManager& mgr) {\n  if (!x1e50_shadow) {\n    return;\n  }\n\n  float alpha = 1.f;\n  switch (x0_player.x2f8_morphBallState) {\n  case CPlayer::EPlayerMorphBallState::Unmorphed:\n    return;\n  case CPlayer::EPlayerMorphBallState::Unmorphing:\n    alpha = 0.f;\n    if (x0_player.x578_morphDuration != 0.f) {\n      alpha = zeus::clamp(0.f, x0_player.x574_morphTime / x0_player.x578_morphDuration, 1.f);\n    }\n    alpha = 1.f - alpha;\n    break;\n  case CPlayer::EPlayerMorphBallState::Morphing:\n    alpha = 0.f;\n    if (x0_player.x578_morphDuration != 0.f) {\n      alpha = zeus::clamp(0.f, x0_player.x574_morphTime / x0_player.x578_morphDuration, 1.f);\n    }\n    break;\n  default:\n    break;\n  }\n  x1e50_shadow->Render(mgr, alpha);\n}\n\nvoid CMorphBall::DeleteBallShadow() { x1e50_shadow.reset(); }\n\nvoid CMorphBall::CreateBallShadow() { x1e50_shadow = std::make_unique<CMorphBallShadow>(); }\n\nvoid CMorphBall::RenderToShadowTex(CStateManager& mgr) {\n  if (!x1e50_shadow) {\n    return;\n  }\n\n  const zeus::CVector3f center =\n      x0_player.GetPrimitiveOffset() + x0_player.GetTranslation() + zeus::CVector3f(0.f, 0.f, xc_radius);\n  const zeus::CAABox aabb(center - zeus::CVector3f(1.25f * xc_radius, 1.25f * xc_radius, 10.f),\n                          center + zeus::CVector3f(1.25f * xc_radius, 1.25f * xc_radius, xc_radius));\n  x1e50_shadow->RenderIdBuffer(aabb, mgr, x0_player);\n}\n\nvoid CMorphBall::SelectMorphBallSounds(const CMaterialList& mat) {\n  u16 rollSfx;\n  if (x0_player.x9c5_30_selectFluidBallSound) {\n    if (x0_player.x82c_inLava) {\n      rollSfx = 2186;\n    } else {\n      rollSfx = 1481;\n    }\n  } else {\n    rollSfx = CPlayer::SfxIdFromMaterial(mat, skBallRollSfx.data(), skBallRollSfx.size(), 0xffff);\n  }\n  x0_player.x9c5_30_selectFluidBallSound = false;\n\n  if (rollSfx != 0xffff) {\n    if (x1e34_rollSfx != rollSfx && x1e2c_rollSfxHandle) {\n      CSfxManager::SfxStop(x1e2c_rollSfxHandle);\n      x1e2c_rollSfxHandle.reset();\n    }\n    x1e34_rollSfx = rollSfx;\n  }\n\n  x1e36_landSfx = CPlayer::SfxIdFromMaterial(mat, skBallLandSfx.data(), skBallLandSfx.size(), 0xffff);\n}\n\nvoid CMorphBall::UpdateMorphBallSounds(float dt) {\n  zeus::CVector3f velocity = x0_player.GetVelocity();\n  if (x187c_spiderBallState != ESpiderBallState::Active) {\n    velocity.z() = 0.f;\n  }\n\n  switch (x0_player.GetPlayerMovementState()) {\n  case CPlayer::EPlayerMovementState::OnGround:\n  case CPlayer::EPlayerMovementState::FallingMorphed: {\n    float vel = velocity.magnitude();\n    if (x187c_spiderBallState == ESpiderBallState::Active) {\n      vel += g_tweakBall->GetBallGravity() * dt * 4.f;\n    }\n    if (vel > 0.8f) {\n      if (!x1e2c_rollSfxHandle) {\n        if (x1e34_rollSfx != 0xffff) {\n          x1e2c_rollSfxHandle = CSfxManager::AddEmitter(x1e34_rollSfx, x0_player.GetTranslation(), zeus::skZero3f, true,\n                                                        true, 0x7f, kInvalidAreaId);\n        }\n        x0_player.ApplySubmergedPitchBend(x1e2c_rollSfxHandle);\n      }\n      CSfxManager::PitchBend(x1e2c_rollSfxHandle, zeus::clamp(-1.f, vel * 0.122f - 0.831f, 1.f));\n      const float maxVol = zeus::clamp(0.f, 0.025f * vel + 0.5f, 1.f);\n      CSfxManager::UpdateEmitter(x1e2c_rollSfxHandle, x0_player.GetTranslation(), zeus::skZero3f, maxVol);\n      break;\n    }\n    [[fallthrough]];\n  }\n  default:\n    if (x1e2c_rollSfxHandle) {\n      CSfxManager::SfxStop(x1e2c_rollSfxHandle);\n      x1e2c_rollSfxHandle.reset();\n    }\n    break;\n  }\n\n  if (x187c_spiderBallState == ESpiderBallState::Active) {\n    if (!x1e30_spiderSfxHandle) {\n      x1e30_spiderSfxHandle = CSfxManager::AddEmitter(SFXsam_spider_lp, x0_player.GetTranslation(), zeus::skZero3f,\n                                                      true, true, 0xc8, kInvalidAreaId);\n      x0_player.ApplySubmergedPitchBend(x1e30_spiderSfxHandle);\n    }\n    CSfxManager::UpdateEmitter(x1e30_spiderSfxHandle, x0_player.GetTranslation(), zeus::skZero3f, 1.f);\n  } else if (x1e30_spiderSfxHandle) {\n    CSfxManager::SfxStop(x1e30_spiderSfxHandle);\n    x1e30_spiderSfxHandle.reset();\n  }\n}\n\nfloat CMorphBall::GetBallRadius() const { return g_tweakPlayer->GetPlayerBallHalfExtent(); }\n\nfloat CMorphBall::GetBallTouchRadius() const { return g_tweakBall->GetBallTouchRadius(); }\n\nfloat CMorphBall::ForwardInput(const CFinalInput& input) const {\n  if (!IsMovementAllowed()) {\n    return 0.f;\n  }\n  return ControlMapper::GetAnalogInput(ControlMapper::ECommands::Forward, input) -\n         ControlMapper::GetAnalogInput(ControlMapper::ECommands::Backward, input);\n}\n\nfloat CMorphBall::BallTurnInput(const CFinalInput& input) const {\n  if (!IsMovementAllowed()) {\n    return 0.f;\n  }\n  return ControlMapper::GetAnalogInput(ControlMapper::ECommands::TurnLeft, input) -\n         ControlMapper::GetAnalogInput(ControlMapper::ECommands::TurnRight, input);\n}\n\nvoid CMorphBall::ComputeBallMovement(const CFinalInput& input, CStateManager& mgr, float dt) {\n  ComputeBoostBallMovement(input, mgr, dt);\n  ComputeMarioMovement(input, mgr, dt);\n}\n\nbool CMorphBall::IsMovementAllowed() const {\n  if (!g_tweakPlayer->GetMoveDuringFreeLook() && (x0_player.x3dc_inFreeLook || x0_player.x3dd_lookButtonHeld)) {\n    return false;\n  }\n  if (x0_player.IsMorphBallTransitioning()) {\n    return false;\n  }\n  return x1e00_disableControlCooldown <= 0.f;\n}\n\nvoid CMorphBall::UpdateSpiderBall(const CFinalInput& input, CStateManager& mgr, float dt) {\n  SetSpiderBallSwingingState(CheckForSwitchToSpiderBallSwinging(mgr));\n  if (x18be_spiderBallSwinging) {\n    ApplySpiderBallSwingingForces(input, mgr, dt);\n  } else {\n    ApplySpiderBallRollForces(input, mgr, dt);\n  }\n}\n\nvoid CMorphBall::ApplySpiderBallSwingingForces(const CFinalInput& input, CStateManager& mgr, float dt) {\n  x18b4_linVelDamp = 0.04f;\n  x18b8_angVelDamp = 0.99f;\n  x1880_playerToSpiderNormal = x1890_spiderTrackPoint - x0_player.GetTranslation();\n\n  const float playerToSpiderDist = x1880_playerToSpiderNormal.magnitude();\n  x1880_playerToSpiderNormal = x1880_playerToSpiderNormal * (-1.f / playerToSpiderDist);\n\n  const float movement = GetSpiderBallControllerMovement(input);\n  UpdateSpiderBallSwingControllerMovementTimer(movement, dt);\n\n  const float swingMovement = movement * GetSpiderBallSwingControllerMovementScalar();\n  const float f29 = playerToSpiderDist * 110000.f / 3.7f;\n  x0_player.ApplyForceWR(\n      x1880_playerToSpiderNormal.cross(x18a8_spiderBetweenPoints).cross(x1880_playerToSpiderNormal).normalized() * f29 *\n          swingMovement * 0.06f,\n      zeus::CAxisAngle());\n  x0_player.SetMomentumWR({0.f, 0.f, x0_player.GetMass() * g_tweakBall->GetBallGravity()});\n  x18fc_refPullVel = (1.f - x188c_spiderPullMovement) * 3.7f + 1.4f;\n  x1900_playerToSpiderTrackDist = playerToSpiderDist;\n\n  zeus::CVector3f playerVel = x0_player.GetVelocity();\n  const float playerSpeed = playerVel.magnitude();\n  playerVel -= x1880_playerToSpiderNormal * playerSpeed * x1880_playerToSpiderNormal.dot(playerVel.normalized());\n\n  float maxPullVel = 0.04f;\n  if (x188c_spiderPullMovement == 1.f && std::fabs(x1880_playerToSpiderNormal.z()) > 0.8f) {\n    maxPullVel = 0.3f;\n  }\n\n  playerVel +=\n      x1880_playerToSpiderNormal * zeus::clamp(-maxPullVel, x18fc_refPullVel - playerToSpiderDist, maxPullVel) / dt;\n  x0_player.SetVelocityWR(playerVel);\n}\n\nzeus::CVector3f CMorphBall::TransformSpiderBallForcesXY(const zeus::CVector2f& forces, CStateManager& mgr) {\n  return forces.x() * mgr.GetCameraManager()->GetCurrentCamera(mgr)->GetTransform().basis[0] +\n         forces.y() * mgr.GetCameraManager()->GetCurrentCamera(mgr)->GetTransform().basis[1];\n}\n\nzeus::CVector3f CMorphBall::TransformSpiderBallForcesXZ(const zeus::CVector2f& forces, CStateManager& mgr) {\n  return forces.x() * mgr.GetCameraManager()->GetCurrentCamera(mgr)->GetTransform().basis[0] +\n         forces.y() * mgr.GetCameraManager()->GetCurrentCamera(mgr)->GetTransform().basis[2];\n}\n\nvoid CMorphBall::ApplySpiderBallRollForces(const CFinalInput& input, CStateManager& mgr, float dt) {\n  const zeus::CVector2f surfaceForces = CalculateSpiderBallAttractionSurfaceForces(input);\n  zeus::CVector3f viewSurfaceForces = TransformSpiderBallForcesXZ(surfaceForces, mgr);\n  const zeus::CTransform camXf = mgr.GetCameraManager()->GetCurrentCamera(mgr)->GetTransform();\n  const zeus::CVector3f spiderDirNorm = x189c_spiderInterpBetweenPoints.normalized();\n  const float upDot = std::fabs(spiderDirNorm.dot(camXf.basis[2]));\n  const float foreDot = std::fabs(spiderDirNorm.dot(camXf.basis[1]));\n\n  if (x0_player.x9c4_29_spiderBallControlXY && upDot < 0.25f && foreDot > 0.25f) {\n    viewSurfaceForces = TransformSpiderBallForcesXY(surfaceForces, mgr);\n  }\n\n  const float forceMag = surfaceForces.magnitude();\n  zeus::CVector2f normSurfaceForces;\n  float trackForceMag = x18c0_isSpiderSurface ? forceMag : viewSurfaceForces.dot(spiderDirNorm);\n  bool forceApplied = true;\n  bool continueTrackForce = false;\n  if (std::fabs(forceMag) <= 0.05f) {\n    forceApplied = false;\n  } else {\n    normSurfaceForces = surfaceForces.normalized();\n    if (x18c0_isSpiderSurface || normSurfaceForces.dot(x190c_normSpiderSurfaceForces) <= 0.9f) {\n      if (std::fabs(trackForceMag) <= 0.05f) {\n        forceApplied = false;\n      } else {\n        trackForceMag = trackForceMag <= 0.f ? -forceMag : forceMag;\n      }\n    } else {\n      trackForceMag = 1.f <= 0.f ? -forceMag : forceMag;\n      continueTrackForce = true;\n    }\n  }\n\n  if (!continueTrackForce) {\n    x190c_normSpiderSurfaceForces = normSurfaceForces;\n    x1914_spiderTrackForceMag = trackForceMag;\n    x1920_spiderForcesReset = true;\n  }\n\n  if (!forceApplied) {\n    ResetSpiderBallForces();\n  }\n\n  bool moving = true;\n  if (!forceApplied && x0_player.GetVelocity().magnitude() <= 6.5f) {\n    moving = false;\n  }\n\n  zeus::CVector3f moveDelta;\n  if (x18bd_touchingSpider && forceApplied) {\n    if (!x18c0_isSpiderSurface) {\n      moveDelta = x18a8_spiderBetweenPoints.normalized() * (0.1f * (1.f <= 0.f ? -1.f : 1.f));\n    } else {\n      moveDelta = 0.1f * viewSurfaceForces;\n    }\n  }\n\n  const zeus::CVector3f ballPos = GetBallToWorld().origin + moveDelta;\n  float distance = 0.f;\n  if (!moving && x18bd_touchingSpider && x188c_spiderPullMovement == 1.f && !x18bf_spiderSwingInAir) {\n    x1880_playerToSpiderNormal = x1890_spiderTrackPoint - ballPos;\n    distance = x1880_playerToSpiderNormal.magnitude();\n    x1880_playerToSpiderNormal = x1880_playerToSpiderNormal * (-1.f / distance);\n    x18bc_spiderNearby = true;\n  } else {\n    if (!mb_spooderBallCached) {\n      x18bc_spiderNearby = false;\n    }\n    if (FindClosestSpiderBallWaypoint(mgr, ballPos, x1890_spiderTrackPoint, x189c_spiderInterpBetweenPoints,\n                                      x18a8_spiderBetweenPoints, distance, x1880_playerToSpiderNormal,\n                                      x18c0_isSpiderSurface, x18c4_spiderSurfaceTransform)) {\n      x18bc_spiderNearby = true;\n      x18bf_spiderSwingInAir = false;\n    }\n  }\n\n  if (x18bc_spiderNearby) {\n    if (distance < kSpiderBallCollisionRadius) {\n      x18bd_touchingSpider = true;\n    }\n    if (x18bd_touchingSpider) {\n      if (moving) {\n        if (!x18c0_isSpiderSurface) {\n          x18b4_linVelDamp = 0.4f;\n          x18b8_angVelDamp = 0.2f;\n          float viewControlMag = viewSurfaceForces.dot(x189c_spiderInterpBetweenPoints.normalized());\n          if (continueTrackForce && !x1920_spiderForcesReset) {\n            viewControlMag = x1918_spiderViewControlMag;\n          } else {\n            x1918_spiderViewControlMag = viewControlMag;\n            x1920_spiderForcesReset = false;\n          }\n          float finalForceMag = 0.f;\n          if (std::fabs(viewControlMag) <= 0.1f) {\n            finalForceMag = 0.f;\n            ResetSpiderBallForces();\n          } else {\n            finalForceMag = (viewControlMag > 0.f ? 1.f : -1.f) * zeus::clamp(-1.f, forceMag, 1.f);\n          }\n          if (distance > 1.05f) {\n            finalForceMag *= (1.05f - (distance - 1.05f)) / 1.05f;\n          }\n          x0_player.ApplyForceWR(finalForceMag * (x18a8_spiderBetweenPoints.normalized() * 90000.f),\n                                 zeus::CAxisAngle());\n        } else {\n          x18b4_linVelDamp = 0.3f;\n          x18b8_angVelDamp = 0.2f;\n\n          const zeus::CVector3f wut1{x18c4_spiderSurfaceTransform.basis[0].x(),\n                                     x18c4_spiderSurfaceTransform.basis[1].x(),\n                                     x18c4_spiderSurfaceTransform.basis[2].x()};\n          const zeus::CVector3f wut2{x18c4_spiderSurfaceTransform.basis[0].z(),\n                                     x18c4_spiderSurfaceTransform.basis[1].z(),\n                                     x18c4_spiderSurfaceTransform.basis[2].z()};\n          const float f30 = wut1.dot(viewSurfaceForces);\n          const float f31 = wut2.dot(viewSurfaceForces);\n          const zeus::CVector3f forceVec = 45000.0f * ((f30 * wut1) + (f31 * wut2));\n          x0_player.ApplyForceWR(forceVec, zeus::CAxisAngle());\n          if (forceVec.magSquared() > 0.f) {\n            float angle = std::atan2(45000.f * f31, 45000.f * f30);\n            if (angle - x18f4_spiderSurfacePivotAngle > M_PIF / 2.f) {\n              angle -= M_PIF;\n            } else if (x18f4_spiderSurfacePivotAngle - angle > M_PIF / 2.f) {\n              angle += M_PIF;\n            }\n            x18f8_spiderSurfacePivotTargetAngle = angle;\n          }\n          const float minAngle =\n              std::min(std::fabs(x18f8_spiderSurfacePivotTargetAngle - x18f4_spiderSurfacePivotAngle), 0.2f);\n          x18f4_spiderSurfacePivotAngle = minAngle * 1.f + x18f4_spiderSurfacePivotAngle;\n          x189c_spiderInterpBetweenPoints =\n              x18c4_spiderSurfaceTransform.rotate(zeus::CTransform::RotateY(x18f4_spiderSurfacePivotAngle).basis[2]);\n        }\n      }\n      x0_player.ApplyForceWR(\n          {0.f, 0.f, (1.f - x188c_spiderPullMovement) * 8.f * (x0_player.GetMass() * g_tweakBall->GetBallGravity())},\n          zeus::CAxisAngle());\n    } else {\n      x18b4_linVelDamp = 0.2f;\n      x18b8_angVelDamp = 0.2f;\n    }\n    x0_player.SetMomentumWR(4.f * (x0_player.GetMass() * g_tweakBall->GetBallGravity()) * x1880_playerToSpiderNormal);\n  }\n}\n\nzeus::CVector2f CMorphBall::CalculateSpiderBallAttractionSurfaceForces(const CFinalInput& input) const {\n  if (!IsMovementAllowed()) {\n    return zeus::CVector2f();\n  }\n\n  return {ControlMapper::GetAnalogInput(ControlMapper::ECommands::TurnRight, input) -\n              ControlMapper::GetAnalogInput(ControlMapper::ECommands::TurnLeft, input),\n          ControlMapper::GetAnalogInput(ControlMapper::ECommands::Forward, input) -\n              ControlMapper::GetAnalogInput(ControlMapper::ECommands::Backward, input)};\n}\n\nbool CMorphBall::CheckForSwitchToSpiderBallSwinging(CStateManager& mgr) const {\n  if (!x18bd_touchingSpider) {\n    return false;\n  }\n\n  if (x188c_spiderPullMovement == 1.f) {\n    if (x18be_spiderBallSwinging) {\n      const zeus::CTransform ballToWorld = GetBallToWorld();\n      zeus::CVector3f closestPoint;\n      zeus::CVector3f interpDeltaBetweenPoints;\n      zeus::CVector3f deltaBetweenPoints;\n      zeus::CVector3f normal;\n      float distance = 0.f;\n      bool isSurface = false;\n      zeus::CTransform surfaceTransform;\n      return FindClosestSpiderBallWaypoint(mgr, ballToWorld.origin, closestPoint, interpDeltaBetweenPoints,\n                                           deltaBetweenPoints, distance, normal, isSurface, surfaceTransform) ||\n             distance >= 2.1f;\n    }\n    return false;\n  }\n\n  if (x18be_spiderBallSwinging) {\n    return true;\n  }\n\n  return std::fabs(x1880_playerToSpiderNormal.z()) > 0.9f;\n}\n\nbool CMorphBall::FindClosestSpiderBallWaypoint(CStateManager& mgr, const zeus::CVector3f& ballCenter,\n                                               zeus::CVector3f& closestPoint, zeus::CVector3f& interpDeltaBetweenPoints,\n                                               zeus::CVector3f& deltaBetweenPoints, float& distance,\n                                               zeus::CVector3f& normal, bool& isSurface,\n                                               zeus::CTransform& surfaceTransform) const {\n  bool ret = false;\n  const zeus::CAABox aabb(ballCenter - 2.1f, ballCenter + 2.1f);\n  EntityList nearList;\n  mgr.BuildNearList(nearList, aabb, CMaterialFilter::skPassEverything, nullptr);\n  float minDist = 2.1f;\n\n  for (const auto& id : nearList) {\n    if (const TCastToConstPtr<CScriptSpiderBallAttractionSurface> surface = mgr.GetObjectById(id)) {\n      const zeus::CUnitVector3f surfaceNorm(surface->GetTransform().basis[1]);\n      const zeus::CPlane plane(surfaceNorm, surface->GetTranslation().dot(surfaceNorm));\n      zeus::CVector3f intersectPoint;\n\n      if (plane.rayPlaneIntersection(ballCenter + surfaceNorm * 2.1f, ballCenter - surfaceNorm * 2.1f,\n                                     intersectPoint)) {\n        const zeus::CVector3f halfScale = surface->GetScale() * 0.5f;\n\n        zeus::CVector3f localPoint =\n            zeus::CTransform::Scale(1.f / halfScale) * surface->GetTransform().inverse() * intersectPoint;\n        localPoint.x() = zeus::clamp(-1.f, float(localPoint.x()), 1.f);\n        localPoint.z() = zeus::clamp(-1.f, float(localPoint.z()), 1.f);\n\n        const zeus::CVector3f worldPoint = surface->GetTransform() * zeus::CTransform::Scale(halfScale) * localPoint;\n        const zeus::CVector3f finalDelta = worldPoint - ballCenter;\n        const float finalMag = finalDelta.magnitude();\n\n        if (finalMag < minDist) {\n          minDist = finalMag;\n          closestPoint = worldPoint;\n          distance = finalMag;\n          normal = finalDelta * (-1.f / finalMag);\n          isSurface = true;\n          surfaceTransform = surface->GetTransform();\n          ret = true;\n        }\n      }\n    }\n  }\n\n  for (const auto& id : nearList) {\n    if (const TCastToConstPtr<CScriptSpiderBallWaypoint> wp = mgr.GetObjectById(id)) {\n      const CScriptSpiderBallWaypoint* closestWp = nullptr;\n      zeus::CVector3f worldPoint;\n      zeus::CVector3f useDeltaBetweenPoints = deltaBetweenPoints;\n      zeus::CVector3f useInterpDeltaBetweenPoints = interpDeltaBetweenPoints;\n      wp->GetClosestPointAlongWaypoints(mgr, ballCenter, 2.1f, closestWp, worldPoint, useDeltaBetweenPoints, 0.8f,\n                                        useInterpDeltaBetweenPoints);\n      if (closestWp != nullptr) {\n        const zeus::CVector3f ballToPoint = worldPoint - ballCenter;\n        const float ballToPointMag = ballToPoint.magnitude();\n        if (ballToPointMag < minDist) {\n          ret = true;\n          closestPoint = worldPoint;\n          interpDeltaBetweenPoints = useInterpDeltaBetweenPoints;\n          deltaBetweenPoints = useDeltaBetweenPoints;\n          distance = ballToPointMag;\n          normal = (-1.f / ballToPointMag) * ballToPoint;\n          isSurface = false;\n          minDist = ballToPointMag;\n        }\n      }\n    }\n  }\n\n  return ret;\n}\n\nvoid CMorphBall::SetSpiderBallSwingingState(bool active) {\n  if (x18be_spiderBallSwinging != active) {\n    ResetSpiderBallSwingControllerMovementTimer();\n    x18bf_spiderSwingInAir = true;\n  }\n  x18be_spiderBallSwinging = active;\n}\n\nfloat CMorphBall::GetSpiderBallControllerMovement(const CFinalInput& input) const {\n  if (!IsMovementAllowed()) {\n    return 0.f;\n  }\n\n  const float forward = ControlMapper::GetAnalogInput(ControlMapper::ECommands::Forward, input) -\n                        ControlMapper::GetAnalogInput(ControlMapper::ECommands::Backward, input);\n  const float turn = ControlMapper::GetAnalogInput(ControlMapper::ECommands::TurnRight, input) -\n                     ControlMapper::GetAnalogInput(ControlMapper::ECommands::TurnLeft, input);\n  const float angle = zeus::radToDeg(std::atan2(forward, turn));\n  const float hyp = std::sqrt(forward * forward + turn * turn);\n\n  if (angle > -35.f && angle < 125.f) {\n    return hyp;\n  }\n  if (angle < -55.f || angle > 145.f) {\n    return -hyp;\n  }\n  return 0.f;\n}\n\nvoid CMorphBall::ResetSpiderBallSwingControllerMovementTimer() {\n  x1904_swingControlDir = 0.f;\n  x1908_swingControlTime = 0.f;\n}\n\nvoid CMorphBall::UpdateSpiderBallSwingControllerMovementTimer(float movement, float dt) {\n  if (std::fabs(movement) < 0.05f) {\n    ResetSpiderBallSwingControllerMovementTimer();\n  } else {\n    if ((movement >= 0.f ? 1.f : -1.f) != x1904_swingControlDir) {\n      ResetSpiderBallSwingControllerMovementTimer();\n      x1904_swingControlDir = (movement >= 0.f ? 1.f : -1.f);\n    } else {\n      x1908_swingControlTime += dt;\n    }\n  }\n}\n\nfloat CMorphBall::GetSpiderBallSwingControllerMovementScalar() const {\n  if (x1908_swingControlTime < 1.2f) {\n    return 1.f;\n  }\n  return std::max(0.f, (2.4f - x1908_swingControlTime) / 1.2f);\n}\n\nvoid CMorphBall::CreateSpiderBallParticles(const zeus::CVector3f& ballPos, const zeus::CVector3f& trackPoint) {\n  x19d4_spiderBallMagnetEffectGen->SetParticleEmission(true);\n\n  zeus::CVector3f ballToTrack = trackPoint - ballPos;\n  const float ballToTrackMag = ballToTrack.magnitude();\n  const int subCount = static_cast<int>(ballToTrackMag / 0.2f + 1.f);\n  ballToTrack = ballToTrack * (1.f / static_cast<float>(subCount));\n  const int count = static_cast<int>(8.f * (ballToTrackMag / 2.1f));\n\n  for (int i = count; i >= 0; --i) {\n    zeus::CVector3f translation = ballPos;\n    for (int j = 0; j < subCount; ++j) {\n      x19d4_spiderBallMagnetEffectGen->SetTranslation(translation);\n      x19d4_spiderBallMagnetEffectGen->ForceParticleCreation(1);\n      translation += ballToTrack;\n    }\n  }\n\n  x19d4_spiderBallMagnetEffectGen->SetParticleEmission(false);\n}\n\nvoid CMorphBall::ResetSpiderBallForces() {\n  x190c_normSpiderSurfaceForces = zeus::CVector2f();\n  x1914_spiderTrackForceMag = 0.f;\n  x1918_spiderViewControlMag = 0.f;\n  x1920_spiderForcesReset = true;\n}\n\nvoid CMorphBall::ComputeMarioMovement(const CFinalInput& input, CStateManager& mgr, float dt) {\n  x1c_controlForce = zeus::skZero3f;\n  x10_boostControlForce = zeus::skZero3f;\n  if (!IsMovementAllowed()) {\n    return;\n  }\n\n  x188c_spiderPullMovement =\n      (ControlMapper::GetAnalogInput(ControlMapper::ECommands::SpiderBall, input) >= 0.5f / 100.f) ? 1.f : 0.f;\n  if (mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::SpiderBall) && x188c_spiderPullMovement != 0.f &&\n      x191c_damageTimer == 0.f) {\n    if (x187c_spiderBallState != ESpiderBallState::Active) {\n      x18bd_touchingSpider = false;\n      x187c_spiderBallState = ESpiderBallState::Active;\n      x18a8_spiderBetweenPoints = x189c_spiderInterpBetweenPoints = x0_player.GetTransform().basis[2];\n    }\n    UpdateSpiderBall(input, mgr, dt);\n\n    if (!x18bc_spiderNearby) {\n      x187c_spiderBallState = ESpiderBallState::Inactive;\n      ResetSpiderBallForces();\n    }\n  } else {\n    x187c_spiderBallState = ESpiderBallState::Inactive;\n    ResetSpiderBallForces();\n  }\n\n  if (x187c_spiderBallState != ESpiderBallState::Active) {\n    const float forward = ForwardInput(input);\n    const float turn = -BallTurnInput(input);\n    const float maxSpeed = ComputeMaxSpeed();\n    const float curSpeed = x0_player.GetVelocity().magnitude();\n    const zeus::CTransform controlXf = zeus::lookAt(zeus::skZero3f, x0_player.x54c_controlDirFlat);\n    const zeus::CVector3f controlFrameVel = controlXf.transposeRotate(x0_player.GetVelocity());\n    float fwdAcc = 0.f;\n    float turnAcc = 0.f;\n\n    if (std::fabs(turn) > 0.1f) {\n      const float controlTurn = turn * maxSpeed;\n      const float controlTurnDelta = controlTurn - controlFrameVel.x();\n      const float accFactor = zeus::clamp(0.f, std::fabs(controlTurnDelta) / maxSpeed, 1.f);\n      float maxAcc;\n\n      if ((controlFrameVel.x() > 0.f ? 1.f : -1.f) != (controlTurn > 0.f ? 1.f : -1.f) && curSpeed > 0.8f * maxSpeed) {\n        maxAcc = g_tweakBall->GetBallForwardBrakingAcceleration(int(x0_player.GetSurfaceRestraint()));\n      } else {\n        maxAcc = g_tweakBall->GetMaxBallTranslationAcceleration(int(x0_player.GetSurfaceRestraint()));\n      }\n\n      if (controlTurnDelta < 0.f) {\n        turnAcc = -maxAcc * accFactor;\n      } else {\n        turnAcc = maxAcc * accFactor;\n      }\n    }\n\n    if (std::fabs(forward) > 0.1f) {\n      const float controlFwd = forward * maxSpeed;\n      const float controlFwdDelta = controlFwd - controlFrameVel.y();\n      const float accFactor = zeus::clamp(0.f, std::fabs(controlFwdDelta) / maxSpeed, 1.f);\n      float maxAcc;\n\n      if ((controlFrameVel.y() > 0.f ? 1.f : -1.f) != (controlFwd > 0.f ? 1.f : -1.f) && curSpeed > 0.8f * maxSpeed) {\n        maxAcc = g_tweakBall->GetBallForwardBrakingAcceleration(int(x0_player.GetSurfaceRestraint()));\n      } else {\n        maxAcc = g_tweakBall->GetMaxBallTranslationAcceleration(int(x0_player.GetSurfaceRestraint()));\n      }\n\n      if (controlFwdDelta < 0.f) {\n        fwdAcc = -maxAcc * accFactor;\n      } else {\n        fwdAcc = maxAcc * accFactor;\n      }\n    }\n\n    if (fwdAcc != 0.f || turnAcc != 0.f || x1de4_24_inBoost || GetIsInHalfPipeMode()) {\n      zeus::CVector3f controlForce = controlXf.rotate({0.f, fwdAcc, 0.f}) + controlXf.rotate({turnAcc, 0.f, 0.f});\n      x1c_controlForce = controlForce;\n      if (x1de4_24_inBoost && !GetIsInHalfPipeMode()) {\n        controlForce = x1924_surfaceToWorld.rotate({x1924_surfaceToWorld.transposeRotate(controlForce).x(), 0.f, 0.f});\n      }\n\n      if (GetIsInHalfPipeMode() && controlForce.magnitude() > FLT_EPSILON) {\n        if (GetIsInHalfPipeModeInAir() && curSpeed <= 15.f &&\n            controlForce.dot(x1924_surfaceToWorld.basis[2]) / controlForce.magnitude() < -0.85f) {\n          DisableHalfPipeStatus();\n          x1e00_disableControlCooldown = 0.2f;\n          x0_player.ApplyImpulseWR(x1924_surfaceToWorld.basis[2] * (x0_player.GetMass() * -7.5f), zeus::CAxisAngle());\n        }\n        if (GetIsInHalfPipeMode()) {\n          controlForce -= controlForce.dot(x1924_surfaceToWorld.basis[2]) * x1924_surfaceToWorld.basis[2];\n          zeus::CVector3f controlForceSurfaceLocal = x1924_surfaceToWorld.transposeRotate(controlForce);\n          controlForceSurfaceLocal.x() *= 0.6f;\n          controlForceSurfaceLocal.y() *= (x1de4_24_inBoost ? 0.f : 0.35f) * 1.4f;\n          controlForce = x1924_surfaceToWorld.rotate(controlForceSurfaceLocal);\n          if (maxSpeed > 95.f) {\n            x0_player.SetVelocityWR(x0_player.GetVelocity() * 0.99f);\n          }\n        }\n      }\n\n      if (GetTouchedHalfPipeRecently()) {\n        const float hpNormComp = x1e08_prevHalfPipeNormal.dot(x1e14_halfPipeNormal);\n        if (hpNormComp < 0.99f && hpNormComp > 0.5f) {\n          const zeus::CVector3f hpRampAxis = x1e08_prevHalfPipeNormal.cross(x1e14_halfPipeNormal).normalized();\n          zeus::CVector3f newVel = x0_player.GetVelocity();\n          newVel -= hpRampAxis * hpRampAxis.dot(x0_player.GetVelocity()) * 0.15f;\n          x0_player.SetVelocityWR(newVel);\n        }\n      }\n\n      const float speedThres = 0.75f * maxSpeed;\n      if (curSpeed >= speedThres) {\n        const float dot = controlForce.dot(x0_player.GetVelocity().normalized());\n        if (dot > 0.f) {\n          controlForce -= x0_player.GetVelocity().normalized() *\n                          zeus::clamp(0.f, (curSpeed - speedThres) / (maxSpeed - speedThres), 1.f) * dot;\n        }\n      }\n      x10_boostControlForce = controlForce;\n      x0_player.ApplyForceWR(controlForce, zeus::CAxisAngle());\n    }\n    ComputeLiftForces(x1c_controlForce, x0_player.GetVelocity(), mgr);\n  }\n}\n\nzeus::CTransform CMorphBall::GetSwooshToWorld() const {\n  return zeus::CTransform::Translate(x0_player.GetTranslation() + zeus::CVector3f(0.f, 0.f, GetBallRadius())) *\n         x1924_surfaceToWorld.getRotation() * zeus::CTransform::RotateY(x30_ballTiltAngle);\n}\n\nzeus::CTransform CMorphBall::GetBallToWorld() const {\n  return zeus::CTransform::Translate(x0_player.GetTranslation() + zeus::CVector3f(0.f, 0.f, GetBallRadius())) *\n         x0_player.GetTransform().getRotation();\n}\n\nzeus::CTransform CMorphBall::CalculateSurfaceToWorld(const zeus::CVector3f& trackNormal,\n                                                     const zeus::CVector3f& trackPoint,\n                                                     const zeus::CVector3f& ballDir) const {\n  if (ballDir.canBeNormalized()) {\n    const zeus::CVector3f forward = ballDir.normalized();\n    const zeus::CVector3f right = ballDir.cross(trackNormal);\n    if (right.canBeNormalized()) {\n      return zeus::CTransform(right, forward, right.cross(forward).normalized(), trackPoint);\n    }\n  }\n  return zeus::CTransform();\n}\n\nbool CMorphBall::CalculateBallContactInfo(zeus::CVector3f& normal, zeus::CVector3f& point) const {\n  if (x74_collisionInfos.GetCount() != 0) {\n    normal = x74_collisionInfos.Front().GetNormalLeft();\n    point = x74_collisionInfos.Front().GetPoint();\n    return true;\n  }\n  return false;\n}\n\nvoid CMorphBall::UpdateBallDynamics(CStateManager& mgr, float dt) {\n  x0_player.SetAngularVelocityWR(x0_player.GetAngularVelocityWR().getVector() * 0.95f);\n  x1df8_27_ballCloseToCollision =\n      BallCloseToCollision(mgr, kSpiderBallCollisionRadius, CMaterialFilter::MakeInclude(EMaterialTypes::Solid));\n  UpdateHalfPipeStatus(mgr, dt);\n  x1e00_disableControlCooldown -= dt;\n  x1e00_disableControlCooldown = std::max(0.f, x1e00_disableControlCooldown);\n  x191c_damageTimer -= dt;\n  x191c_damageTimer = std::max(0.f, x191c_damageTimer);\n\n  if (x187c_spiderBallState == ESpiderBallState::Active) {\n    x1924_surfaceToWorld =\n        CalculateSurfaceToWorld(x1880_playerToSpiderNormal, x1890_spiderTrackPoint, x189c_spiderInterpBetweenPoints);\n    x2c_tireLeanAngle = 0.f;\n    if (!x28_tireMode) {\n      SwitchToTire();\n    }\n    x1c2c_tireInterpolating = true;\n    x1c28_tireInterpSpeed = -1.f;\n    UpdateMarbleDynamics(mgr, dt, x1890_spiderTrackPoint);\n  } else {\n    if (x0_player.GetSurfaceRestraint() != CPlayer::ESurfaceRestraints::Air) {\n      zeus::CVector3f normal, point;\n      if (CalculateBallContactInfo(normal, point)) {\n        x1924_surfaceToWorld = CalculateSurfaceToWorld(normal, point, x0_player.x500_lookDir);\n        const float speed = x0_player.GetVelocity().magnitude();\n        if (speed < g_tweakBall->GetTireToMarbleThresholdSpeed() && x28_tireMode) {\n          SwitchToMarble();\n        }\n        if (UpdateMarbleDynamics(mgr, dt, point) && speed >= g_tweakBall->GetMarbleToTireThresholdSpeed() &&\n            !x28_tireMode) {\n          SwitchToTire();\n        }\n        if (x28_tireMode) {\n          x2c_tireLeanAngle = x0_player.GetTransform().transposeRotate(x0_player.GetForceOR()).x() /\n                              g_tweakBall->GetMaxBallTranslationAcceleration(int(x0_player.GetSurfaceRestraint())) *\n                              g_tweakBall->GetMaxLeanAngle() * g_tweakBall->GetForceToLeanGain();\n          x2c_tireLeanAngle =\n              zeus::clamp(-g_tweakBall->GetMaxLeanAngle(), x2c_tireLeanAngle, g_tweakBall->GetMaxLeanAngle());\n          if (x0_player.GetTransform().basis[0].dot(x1924_surfaceToWorld.basis[0]) < 0.f) {\n            x2c_tireLeanAngle = -x2c_tireLeanAngle;\n          }\n        }\n      }\n    } else {\n      x2c_tireLeanAngle = 0.f;\n    }\n  }\n\n  zeus::CRelAngle angle(x2c_tireLeanAngle - x30_ballTiltAngle);\n  const float leanSpeed = std::fabs(angle) * g_tweakBall->GetMaxLeanAngle() * g_tweakBall->GetLeanTrackingGain();\n  if (angle.asRadians() > 0.05f) {\n    x30_ballTiltAngle += leanSpeed * dt;\n  } else if (angle.asRadians() < -0.05f) {\n    x30_ballTiltAngle -= leanSpeed * dt;\n  } else {\n    x30_ballTiltAngle = x2c_tireLeanAngle;\n  }\n\n  if (x187c_spiderBallState != ESpiderBallState::Active) {\n    ApplyFriction(CalculateSurfaceFriction());\n  } else {\n    DampLinearAndAngularVelocities(x18b4_linVelDamp, x18b8_angVelDamp);\n  }\n\n  if (x187c_spiderBallState != ESpiderBallState::Active) {\n    ApplyGravity(mgr);\n  }\n\n  x74_collisionInfos.Clear();\n\n  x1c3c_ballOrientAvg.AddValue(zeus::CQuaternion(GetBallToWorld().basis));\n  x1c90_ballPosAvg.AddValue(GetBallToWorld().origin);\n}\n\nvoid CMorphBall::SwitchToMarble() {\n  x0_player.SetTransform(x0_player.GetTransform() *\n                         zeus::CQuaternion::fromAxisAngle(\n                             x0_player.GetTransform().transposeRotate(x0_player.x500_lookDir), x30_ballTiltAngle)\n                             .toTransform());\n  x28_tireMode = false;\n  x1c2c_tireInterpolating = true;\n  x1c28_tireInterpSpeed = -1.f;\n}\n\nvoid CMorphBall::SwitchToTire() {\n  x28_tireMode = true;\n  x1c2c_tireInterpolating = true;\n  x30_ballTiltAngle = 0.f;\n  x1c28_tireInterpSpeed = 1.f;\n}\n\nvoid CMorphBall::Update(float dt, CStateManager& mgr) {\n  if (x187c_spiderBallState == ESpiderBallState::Active) {\n    CreateSpiderBallParticles(GetBallToWorld().origin, x1890_spiderTrackPoint);\n  }\n\n  if (x0_player.GetDeathTime() <= 0.f) {\n    UpdateEffects(dt, mgr);\n  }\n\n  if (x1e44_damageEffect > 0.f) {\n    x1e44_damageEffect -= x1e48_damageEffectDecaySpeed * dt;\n    if (x1e44_damageEffect <= 0.f) {\n      x1e44_damageEffect = 0.f;\n      x1e48_damageEffectDecaySpeed = 0.f;\n      x1e4c_damageTime = 0.f;\n    } else {\n      x1e4c_damageTime += dt;\n    }\n  }\n\n  if (x58_ballModel) {\n    x58_ballModel->AdvanceAnimation(dt, mgr, kInvalidAreaId, true);\n  }\n\n  if (x1c2c_tireInterpolating) {\n    x1c20_tireFactor += x1c28_tireInterpSpeed * dt;\n    if (x1c20_tireFactor < 0.f) {\n      x1c2c_tireInterpolating = false;\n      x1c20_tireFactor = 0.f;\n    } else if (x1c20_tireFactor > x1c24_maxTireFactor) {\n      x1c2c_tireInterpolating = false;\n      x1c20_tireFactor = x1c24_maxTireFactor;\n    }\n  }\n\n  if (x1c1c_rainSplashGen) {\n    x1c1c_rainSplashGen->Update(dt, mgr);\n  }\n\n  UpdateMorphBallSounds(dt);\n}\n\nvoid CMorphBall::DeleteLight(CStateManager& mgr) {\n  if (x1c10_ballInnerGlowLight == kInvalidUniqueId) {\n    return;\n  }\n\n  mgr.FreeScriptObject(x1c10_ballInnerGlowLight);\n  x1c10_ballInnerGlowLight = kInvalidUniqueId;\n}\n\nvoid CMorphBall::SetBallLightActive(CStateManager& mgr, bool active) {\n  if (x1c10_ballInnerGlowLight == kInvalidUniqueId) {\n    return;\n  }\n\n  if (const TCastToPtr<CGameLight> light = mgr.ObjectById(x1c10_ballInnerGlowLight)) {\n    light->SetActive(active);\n  }\n}\n\nvoid CMorphBall::EnterMorphBallState(CStateManager& mgr) {\n  x1c20_tireFactor = 0.f;\n  UpdateEffects(0.f, mgr);\n  x187c_spiderBallState = ESpiderBallState::Inactive;\n  constexpr CAnimPlaybackParms parms(0, -1, 1.f, true);\n  x58_ballModel->GetAnimationData()->SetAnimation(parms, false);\n  x1e20_ballAnimIdx = 0;\n  StopEffects();\n  x1c30_boostOverLightFactor = 0.f;\n  x1c34_boostLightFactor = 0.f;\n  x1c38_spiderLightFactor = 0.f;\n  DisableHalfPipeStatus();\n  x30_ballTiltAngle = 0.f;\n  x2c_tireLeanAngle = 0.f;\n}\n\nvoid CMorphBall::LeaveMorphBallState(CStateManager& mgr) {\n  LeaveBoosting();\n  CancelBoosting();\n  CSfxManager::SfxStop(x1e24_boostSfxHandle);\n  StopEffects();\n}\n\nvoid CMorphBall::UpdateEffects(float dt, CStateManager& mgr) {\n  const zeus::CTransform swooshToWorld = GetSwooshToWorld();\n  x19b8_slowBlueTailSwooshGen->SetTranslation(swooshToWorld.rotate({0.1f, 0.f, 0.f}) + swooshToWorld.origin);\n  x19b8_slowBlueTailSwooshGen->SetOrientation(swooshToWorld.getRotation());\n  x19b8_slowBlueTailSwooshGen->DoWarmupUpdate();\n  x19bc_slowBlueTailSwooshGen2->SetTranslation(swooshToWorld.rotate({-0.1f, 0.f, 0.f}) + swooshToWorld.origin);\n  x19bc_slowBlueTailSwooshGen2->SetOrientation(swooshToWorld.getRotation());\n  x19bc_slowBlueTailSwooshGen2->DoWarmupUpdate();\n  x19c0_slowBlueTailSwoosh2Gen->SetTranslation(swooshToWorld.rotate({0.f, 0.f, 0.65f}) + swooshToWorld.origin);\n  x19c0_slowBlueTailSwoosh2Gen->SetOrientation(swooshToWorld.getRotation());\n  x19c0_slowBlueTailSwoosh2Gen->DoWarmupUpdate();\n  x19c4_slowBlueTailSwoosh2Gen2->SetTranslation(swooshToWorld.rotate({0.f, 0.f, -0.65f}) + swooshToWorld.origin);\n  x19c4_slowBlueTailSwoosh2Gen2->SetOrientation(swooshToWorld.getRotation());\n  x19c4_slowBlueTailSwoosh2Gen2->DoWarmupUpdate();\n  x19c8_jaggyTrailGen->SetTranslation(swooshToWorld.origin);\n  x19c8_jaggyTrailGen->SetOrientation(swooshToWorld.getRotation());\n  x19c8_jaggyTrailGen->DoWarmupUpdate();\n  x19cc_wallSparkGen->Update(dt);\n  x1bc8_wakeEffectGens[7]->Update(dt);\n  const bool emitRainWake =\n      (x0_player.GetPlayerMovementState() == CPlayer::EPlayerMovementState::OnGround &&\n       mgr.GetWorld()->GetNeededEnvFx() == EEnvFxType::Rain && mgr.GetEnvFxManager()->GetRainMagnitude() > 0.f &&\n       mgr.GetEnvFxManager()->IsSplashActive());\n  x1bc8_wakeEffectGens[7]->SetParticleEmission(emitRainWake);\n  const float rainGenRate = std::min(mgr.GetEnvFxManager()->GetRainMagnitude() * 2.f * x0_player.x4fc_flatMoveSpeed /\n                                         x0_player.GetBallMaxVelocity(),\n                                     1.f);\n  x1bc8_wakeEffectGens[7]->SetGeneratorRate(rainGenRate);\n  x1bc8_wakeEffectGens[7]->SetTranslation(x0_player.GetTranslation());\n  if (emitRainWake) {\n    const zeus::CTransform rainOrient =\n        zeus::lookAt(x0_player.x50c_moveDir + x0_player.GetTranslation(), x0_player.GetTranslation());\n    x1bc8_wakeEffectGens[7]->SetOrientation(rainOrient);\n  }\n  if (x1c0c_wakeEffectIdx != -1) {\n    x1bc8_wakeEffectGens[x1c0c_wakeEffectIdx]->Update(dt);\n  }\n  if (x1e38_wallSparkFrameCountdown > 0) {\n    x1e38_wallSparkFrameCountdown -= 1;\n    if (x1e38_wallSparkFrameCountdown <= 0) {\n      x19cc_wallSparkGen->SetParticleEmission(false);\n    }\n  }\n  x19d0_ballInnerGlowGen->SetGlobalTranslation(swooshToWorld.origin);\n  x19d0_ballInnerGlowGen->Update(dt);\n  if (x1de8_boostChargeTime == 0.f && x1df4_boostDrainTime == 0.f) {\n    x19d8_boostBallGlowGen->SetModulationColor(zeus::skClear);\n  } else {\n    x19d8_boostBallGlowGen->SetGlobalTranslation(swooshToWorld.origin);\n    float t;\n    if (x1df4_boostDrainTime == 0.f) {\n      t = x1de8_boostChargeTime / g_tweakBall->GetBoostBallMaxChargeTime();\n    } else {\n      t = 1.f - x1df4_boostDrainTime / g_tweakBall->GetBoostBallDrainTime();\n    }\n    x19d8_boostBallGlowGen->SetModulationColor(zeus::CColor::lerp(zeus::skBlack, zeus::CColor(1.f, 1.f, 0.4f, 1.f), t));\n    x19d8_boostBallGlowGen->Update(dt);\n  }\n  x19d4_spiderBallMagnetEffectGen->Update(dt);\n  x1c30_boostOverLightFactor -= 0.03f;\n  x1c30_boostOverLightFactor = std::max(0.f, x1c30_boostOverLightFactor);\n  if (x1c30_boostOverLightFactor == 0.f) {\n    x1c34_boostLightFactor -= 0.04f;\n    x1c34_boostLightFactor = std::max(0.f, x1c34_boostLightFactor);\n  }\n  if (x1de4_24_inBoost) {\n    x1c30_boostOverLightFactor = 1.f;\n    x1c34_boostLightFactor = 1.f;\n  } else {\n    x1c34_boostLightFactor =\n        std::max(x1de8_boostChargeTime / g_tweakBall->GetBoostBallMaxChargeTime(), x1c34_boostLightFactor);\n    x1c34_boostLightFactor = std::min(x1c34_boostLightFactor, 1.f);\n  }\n  UpdateMorphBallTransitionFlash(dt);\n  UpdateIceBreakEffect(dt);\n  if (x1c10_ballInnerGlowLight != kInvalidUniqueId) {\n    if (const TCastToPtr<CGameLight> light = mgr.ObjectById(x1c10_ballInnerGlowLight)) {\n      light->SetTranslation(swooshToWorld.origin + zeus::CVector3f(0.f, 0.f, GetBallRadius()));\n\n      std::optional<CLight> lObj;\n      if (IsMorphBallTransitionFlashValid() && x19dc_morphBallTransitionFlashGen->SystemHasLight()) {\n        lObj.emplace(x19dc_morphBallTransitionFlashGen->GetLight());\n      } else if (x19d0_ballInnerGlowGen->SystemHasLight()) {\n        lObj.emplace(x19d0_ballInnerGlowGen->GetLight());\n      }\n\n      if (lObj) {\n        const auto c = skBallInnerGlowColors[x8_ballGlowColorIdx];\n        const zeus::CColor color(c[0] / 255.f, c[1] / 255.f, c[2] / 255.f, 1.f);\n        lObj->SetColor(lObj->GetColor() * color);\n\n        if (x0_player.GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Unmorphing) {\n          float t = 0.f;\n          if (x0_player.x578_morphDuration != 0.f) {\n            t = zeus::clamp(0.f, x0_player.x574_morphTime / x0_player.x578_morphDuration, 1.f);\n          }\n          lObj->SetColor(zeus::CColor::lerp(lObj->GetColor(), zeus::skBlack, t));\n        } else if (x0_player.GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphing) {\n          float t = 0.f;\n          if (x0_player.x578_morphDuration != 0.f) {\n            t = zeus::clamp(0.f, x0_player.x574_morphTime / x0_player.x578_morphDuration, 1.f);\n          }\n          if (t < 0.5f) {\n            lObj->SetColor(zeus::CColor::lerp(zeus::skBlack, lObj->GetColor(), std::min(2.f * t, 1.f)));\n          }\n        } else {\n          lObj->SetColor(zeus::CColor::lerp(lObj->GetColor(), zeus::skWhite, x1c34_boostLightFactor));\n        }\n\n        light->SetLight(*lObj);\n      }\n    }\n  }\n\n  if (x187c_spiderBallState == ESpiderBallState::Active) {\n    AddSpiderBallElectricalEffect();\n    AddSpiderBallElectricalEffect();\n    AddSpiderBallElectricalEffect();\n    AddSpiderBallElectricalEffect();\n    AddSpiderBallElectricalEffect();\n    x1c38_spiderLightFactor = std::min(x1c38_spiderLightFactor + 0.25f, 1.f);\n  } else {\n    x1c38_spiderLightFactor = std::max(0.f, x1c38_spiderLightFactor - 0.15f);\n  }\n\n  UpdateSpiderBallElectricalEffects();\n}\n\nvoid CMorphBall::ComputeBoostBallMovement(const CFinalInput& input, CStateManager& mgr, float dt) {\n  if (!IsMovementAllowed() || !mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::BoostBall)) {\n    return;\n  }\n\n  if (!x1de4_25_boostEnabled) {\n    CancelBoosting();\n    LeaveBoosting();\n    return;\n  }\n\n  if (!x1de4_24_inBoost) {\n    x1dec_timeNotInBoost += dt;\n    if (ControlMapper::GetDigitalInput(ControlMapper::ECommands::JumpOrBoost, input) &&\n        x187c_spiderBallState != ESpiderBallState::Active) {\n      if (x1e20_ballAnimIdx == 0) {\n        constexpr CAnimPlaybackParms parms(1, -1, 1.f, true);\n        x58_ballModel->GetAnimationData()->SetAnimation(parms, false);\n        x1e20_ballAnimIdx = 1;\n        x1e24_boostSfxHandle = CSfxManager::SfxStart(SFXsam_ball_charge_lp, 1.f, 0.f, true, 0x7f, true, kInvalidAreaId);\n      }\n      x1de8_boostChargeTime += dt;\n      if (x1de8_boostChargeTime > g_tweakBall->GetBoostBallMaxChargeTime()) {\n        x1de8_boostChargeTime = g_tweakBall->GetBoostBallMaxChargeTime();\n      }\n    } else {\n      if (x1e20_ballAnimIdx == 1) {\n        constexpr CAnimPlaybackParms parms(0, -1, 1.f, true);\n        x58_ballModel->GetAnimationData()->SetAnimation(parms, false);\n        x1e20_ballAnimIdx = 0;\n        CSfxManager::RemoveEmitter(x1e24_boostSfxHandle);\n        if (x1de8_boostChargeTime >= g_tweakBall->GetBoostBallMinChargeTime()) {\n          CSfxManager::AddEmitter(SFXsam_ball_boost, x0_player.GetTranslation(), zeus::skZero3f, true, false, 0xb4,\n                                  kInvalidAreaId);\n        }\n      }\n\n      if (x1de8_boostChargeTime >= g_tweakBall->GetBoostBallMinChargeTime()) {\n        if (GetBallBoostState() == EBallBoostState::BoostAvailable) {\n          if (GetIsInHalfPipeMode() || x1df8_27_ballCloseToCollision) {\n            EnterBoosting(mgr);\n          } else {\n            x0_player.ApplyImpulseWR(zeus::skZero3f, zeus::CAxisAngle(-x1924_surfaceToWorld.basis[1] * 10000.f));\n            CancelBoosting();\n          }\n        } else if (GetBallBoostState() == EBallBoostState::BoostDisabled) {\n          x0_player.SetTransform(\n              zeus::lookAt(x0_player.GetTranslation(), x0_player.GetTranslation() + GetBallToWorld().basis[1]));\n          x0_player.ApplyImpulseWR(zeus::skZero3f, zeus::CAxisAngle(-x0_player.GetTransform().basis[0] * 10000.f));\n          CancelBoosting();\n        }\n      } else if (x1de8_boostChargeTime > 0.f) {\n        CancelBoosting();\n      }\n    }\n  } else {\n    x1df4_boostDrainTime += dt;\n    if (x1df4_boostDrainTime > g_tweakBall->GetBoostBallDrainTime()) {\n      LeaveBoosting();\n    }\n    if (!GetIsInHalfPipeMode() && !x1df8_27_ballCloseToCollision) {\n      if (x1df4_boostDrainTime / g_tweakBall->GetBoostBallDrainTime() < 0.3f) {\n        DampLinearAndAngularVelocities(0.5f, 0.01f);\n      } else {\n        LeaveBoosting();\n      }\n    }\n  }\n}\n\nvoid CMorphBall::EnterBoosting(CStateManager& mgr) {\n  x1de4_24_inBoost = true;\n\n  float incSpeed = 0.f;\n  if (x1de8_boostChargeTime <= g_tweakBall->GetBoostBallChargeTimeTable(0)) {\n    incSpeed = g_tweakBall->GetBoostBallIncrementalSpeedTable(0);\n  } else if (x1de8_boostChargeTime <= g_tweakBall->GetBoostBallChargeTimeTable(1)) {\n    incSpeed = g_tweakBall->GetBoostBallIncrementalSpeedTable(1);\n  } else if (x1de8_boostChargeTime <= g_tweakBall->GetBoostBallChargeTimeTable(2)) {\n    incSpeed = g_tweakBall->GetBoostBallIncrementalSpeedTable(2);\n  }\n\n  if (GetIsInHalfPipeMode()) {\n    const float speedMul = x0_player.GetVelocity().magnitude() / 95.f;\n    if (speedMul > 0.3f) {\n      incSpeed -= (speedMul - 0.3f) * incSpeed;\n    }\n    incSpeed = std::max(0.f, incSpeed);\n  }\n\n  zeus::CVector3f lookDir = x0_player.x500_lookDir;\n  float lookMag2d = std::sqrt(lookDir.x() * lookDir.x() + lookDir.y() * lookDir.y());\n  float vertLookAngle = zeus::radToDeg(std::atan2(lookDir.z(), lookMag2d));\n  if (lookMag2d < 0.001f && x0_player.GetPlayerMovementState() == CPlayer::EPlayerMovementState::OnGround) {\n    const float velMag2d = std::sqrt(x0_player.GetVelocity().x() * x0_player.GetVelocity().x() +\n                                     x0_player.GetVelocity().y() * x0_player.GetVelocity().y());\n    if (velMag2d < 0.001f && std::fabs(x0_player.GetVelocity().z()) < 2.f) {\n      lookDir = mgr.GetCameraManager()->GetCurrentCamera(mgr)->GetTransform().basis[1];\n      lookMag2d = std::sqrt(lookDir.x() * lookDir.x() + lookDir.y() * lookDir.y());\n      vertLookAngle = zeus::radToDeg(std::atan2(lookDir.z(), lookMag2d));\n    }\n  }\n\n  float speedMul = 1.f;\n  if (vertLookAngle > 40.f) {\n    const float speedDamp = (vertLookAngle - 40.f) / 50.f;\n    speedMul = 0.35f * speedDamp + (1.f - speedDamp);\n  }\n\n  x0_player.ApplyImpulseWR(lookDir * (speedMul * incSpeed * x0_player.GetMass()), zeus::CAxisAngle());\n\n  x1df4_boostDrainTime = 0.f;\n  x1de8_boostChargeTime = 0.f;\n\n  x0_player.SetTransform(zeus::CTransform(x1924_surfaceToWorld.basis, x0_player.GetTranslation()));\n  SwitchToTire();\n}\n\nvoid CMorphBall::LeaveBoosting() {\n  if (x1de4_24_inBoost) {\n    x1dec_timeNotInBoost = 0.f;\n    x1de8_boostChargeTime = 0.f;\n  }\n\n  x1de4_24_inBoost = false;\n  x1df4_boostDrainTime = 0.f;\n}\n\nvoid CMorphBall::CancelBoosting() {\n  x1de8_boostChargeTime = 0.f;\n  x1df4_boostDrainTime = 0.f;\n\n  if (x1e20_ballAnimIdx == 1) {\n    constexpr CAnimPlaybackParms parms(0, -1, 1.f, true);\n    x58_ballModel->GetAnimationData()->SetAnimation(parms, false);\n    x1e20_ballAnimIdx = 0;\n    CSfxManager::SfxStop(x1e24_boostSfxHandle);\n  }\n}\n\nbool CMorphBall::UpdateMarbleDynamics(CStateManager& mgr, float dt, const zeus::CVector3f& point) {\n  bool continueForce = false;\n  const float maxAcc = g_tweakBall->GetMaxBallTranslationAcceleration(int(x0_player.GetSurfaceRestraint()));\n\n  if (x0_player.GetVelocity().magnitude() < 3.f && x10_boostControlForce.magnitude() > 0.95f * maxAcc) {\n    zeus::CVector3f localMomentum = x1924_surfaceToWorld.transposeRotate(x0_player.GetMomentum());\n    localMomentum.z() = 0.f;\n    zeus::CVector3f localControlForce = x1924_surfaceToWorld.transposeRotate(x10_boostControlForce);\n    localControlForce.z() = 0.f;\n    if (localMomentum.canBeNormalized() && localControlForce.canBeNormalized()) {\n      if (localMomentum.normalized().dot(localControlForce.normalized()) < -0.9f) {\n        continueForce = true;\n      }\n    }\n  }\n\n  if (!continueForce) {\n    const zeus::CVector3f vel = x0_player.GetVelocity();\n    const zeus::CVector3f ballToPoint =\n        point - (x0_player.GetTranslation() + zeus::CVector3f(0.f, 0.f, GetBallRadius()));\n    const zeus::CVector3f addVel = x0_player.GetAngularVelocityWR().getVector().cross(ballToPoint);\n    zeus::CVector3f velDelta = vel - addVel;\n    const float f28 = x187c_spiderBallState == ESpiderBallState::Active ? -1.f : 0.4f;\n    float liftSpeed = 0.f;\n\n    if (x1cd0_liftSpeedAvg.Size() > 3) {\n      liftSpeed = *x1cd0_liftSpeedAvg.GetEntry(0);\n      liftSpeed = std::min(liftSpeed, *x1cd0_liftSpeedAvg.GetEntry(1));\n      liftSpeed = std::min(liftSpeed, *x1cd0_liftSpeedAvg.GetEntry(2));\n    }\n\n    if (velDelta.magSquared() > 1.f && liftSpeed > f28) {\n      if (velDelta.magnitude() > 25.132742f) {\n        velDelta = velDelta.normalized() * M_PIF * 8.f;\n      }\n\n      const zeus::CVector3f newVel = vel + addVel;\n      if (newVel.canBeNormalized()) {\n        const float f26 = (x28_tireMode && x187c_spiderBallState != ESpiderBallState::Active) ? 0.25f : 1.f;\n        const zeus::CVector3f f27 =\n            newVel.normalized() *\n            (velDelta.magnitude() * -g_tweakBall->GetBallSlipFactor(int(x0_player.GetSurfaceRestraint())) * f26 * 0.5f /\n             GetBallRadius());\n        x0_player.ApplyTorqueWR(ballToPoint.normalized().cross(f27));\n      }\n    }\n  } else {\n    const zeus::CVector3f rotAxis = x1924_surfaceToWorld.basis[2].cross(x10_boostControlForce);\n    if (rotAxis.canBeNormalized()) {\n      SpinToSpeed(25.f / GetBallRadius(), rotAxis.normalized(), 800.f);\n    }\n  }\n\n  if (x0_player.GetVelocity().magnitude() >= GetMinimumAlignmentSpeed()) {\n    zeus::CVector3f axis = x1924_surfaceToWorld.basis[0];\n    if (x0_player.GetTransform().basis[0].dot(axis) < 0.f) {\n      axis = -axis;\n    }\n\n    const zeus::CVector3f upVec = x0_player.GetTransform().basis[0].cross(axis);\n    if (upVec.canBeNormalized()) {\n      if (!x28_tireMode) {\n        x0_player.SetAngularImpulse(x0_player.GetAngularImpulse().getVector() +\n                                    upVec.normalized() * g_tweakBall->GetTireness());\n      } else {\n        x0_player.RotateInOneFrameOR(\n            zeus::CQuaternion::shortestRotationArc(zeus::skRight, GetBallToWorld().transposeRotate(axis)), dt);\n      }\n    }\n    return upVec.magnitude() < (GetIsInHalfPipeMode() ? 0.2f : 0.05f);\n  }\n\n  return false;\n}\n\nvoid CMorphBall::ApplyFriction(float f) {\n  zeus::CVector3f vel = x0_player.GetVelocity();\n\n  if (f < vel.magnitude()) {\n    vel = vel.normalized() * (vel.magnitude() - f);\n  } else {\n    vel = zeus::skZero3f;\n  }\n\n  x0_player.SetVelocityWR(vel);\n}\n\nvoid CMorphBall::DampLinearAndAngularVelocities(float linDamp, float angDamp) {\n  const zeus::CVector3f vel = x0_player.GetVelocity() * (1.f - linDamp);\n  x0_player.SetVelocityWR(vel);\n\n  zeus::CAxisAngle ang = x0_player.GetAngularVelocityWR();\n  ang = ang * (1.f - angDamp);\n  x0_player.SetAngularVelocityWR(ang);\n}\n\nfloat CMorphBall::GetMinimumAlignmentSpeed() const {\n  if (x187c_spiderBallState == ESpiderBallState::Active) {\n    return 0.f;\n  }\n\n  return g_tweakBall->GetMinimumAlignmentSpeed();\n}\n\nvoid CMorphBall::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) {\n  if (x1c34_boostLightFactor == 1.f) {\n    return;\n  }\n\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CMorphBall::PreRender\", zeus::skBlue);\n  x0_player.GetActorLights()->SetFindShadowLight(x1e44_damageEffect < 0.25f);\n  x0_player.GetActorLights()->SetShadowDynamicRangeThreshold(0.05f);\n  x0_player.GetActorLights()->SetDirty();\n\n  CCollidableSphere sphere = x38_collisionSphere;\n  sphere.SetSphereCenter(zeus::skZero3f);\n  const zeus::CAABox ballAABB = sphere.CalculateAABox(GetBallToWorld());\n\n  if (x0_player.GetAreaIdAlways() != kInvalidAreaId) {\n    const CGameArea* area = mgr.GetWorld()->GetAreaAlways(x0_player.GetAreaIdAlways());\n    if (area->IsPostConstructed()) {\n      x0_player.GetActorLights()->BuildAreaLightList(mgr, *area, ballAABB);\n    }\n  }\n\n  x0_player.GetActorLights()->BuildDynamicLightList(mgr, ballAABB);\n  if (x0_player.GetActorLights()->HasShadowLight()) {\n    CCollidableSphere collisionSphere = x38_collisionSphere;\n    collisionSphere.SetSphereCenter(zeus::skZero3f);\n    x1c14_worldShadow->BuildLightShadowTexture(mgr, x0_player.GetAreaIdAlways(),\n                                               x0_player.GetActorLights()->GetShadowLightIndex(),\n                                               collisionSphere.CalculateAABox(GetBallToWorld()), false, false);\n  } else {\n    x1c14_worldShadow->ResetBlur();\n  }\n\n  zeus::CColor ambColor = x0_player.GetActorLights()->GetAmbientColor();\n  ambColor.a() = 1.f;\n  x0_player.GetActorLights()->SetAmbientColor(zeus::CColor::lerp(ambColor, zeus::skWhite, x1c34_boostLightFactor));\n  *x1c18_actorLights = *x0_player.GetActorLights();\n\n  ambColor = x0_player.GetActorLights()->GetAmbientColor();\n  ambColor.a() = 1.f;\n  x1c18_actorLights->SetAmbientColor(\n      zeus::CColor::lerp(ambColor, zeus::skWhite, std::max(x1c38_spiderLightFactor, x1c34_boostLightFactor)));\n\n  if (CAnimData* animData = x58_ballModel->GetAnimationData()) {\n    animData->PreRender();\n  }\n}\n\nvoid CMorphBall::Render(const CStateManager& mgr, const CActorLights* lights) const {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CPlayer::Render\", zeus::skPurple);\n  zeus::CTransform ballToWorld = GetBallToWorld();\n  if (x28_tireMode) {\n    ballToWorld = ballToWorld * zeus::CQuaternion::fromAxisAngle(ballToWorld.transposeRotate(x0_player.x500_lookDir),\n                                                                 x30_ballTiltAngle)\n                                    .toTransform();\n  }\n\n  bool dying = x0_player.x9f4_deathTime > 0.f;\n  if (dying) {\n    const zeus::CColor modColor(0.f, zeus::clamp(0.f, 1.f - x0_player.x9f4_deathTime / 0.2f * 6.f, 1.f));\n    CModelFlags flags(7, u8(x5c_ballModelShader), 1, modColor);\n    // flags.m_extendedShader = EExtendedShader::LightingCubeReflection;\n    x58_ballModel->Render(mgr, ballToWorld, nullptr, flags);\n  }\n\n  CModelFlags flags(0, 0, 3, zeus::skWhite);\n  if (x1e44_damageEffect > 0.f) {\n    flags = CModelFlags(1, 0, 3, zeus::CColor(1.f, 1.f - x1e44_damageEffect, 1.f - x1e44_damageEffect, 1.f));\n  }\n  // flags.m_extendedShader = EExtendedShader::LightingCubeReflection;\n\n  if (x1c1c_rainSplashGen && x1c1c_rainSplashGen->IsRaining()) {\n    CSkinnedModel::SetPointGeneratorFunc(\n        [&](const auto& workspace) { x1c1c_rainSplashGen->GeneratePoints(workspace); });\n  }\n\n  if (x1c34_boostLightFactor != 1.f) {\n    if (lights->HasShadowLight()) {\n      x1c14_worldShadow->EnableModelProjectedShadow(ballToWorld, lights->GetShadowLightArrIndex(), 1.f);\n      // flags.m_extendedShader = EExtendedShader::LightingCubeReflectionWorldShadow;\n    }\n    x58_ballModel->Render(mgr, ballToWorld, lights, flags);\n    x1c14_worldShadow->DisableModelProjectedShadow();\n  } else {\n    // Lights used to be nullptr here, but we keep it due to PC's increased dynamic lighting range\n    x58_ballModel->Render(mgr, ballToWorld, lights, flags);\n  }\n\n  if (x1c1c_rainSplashGen && x1c1c_rainSplashGen->IsRaining()) {\n    CSkinnedModel::ClearPointGeneratorFunc();\n    x1c1c_rainSplashGen->Draw(zeus::CTransform::Translate(ballToWorld.origin));\n  }\n\n  float speed = x0_player.GetVelocity().magnitude();\n  if (x1e44_damageEffect > 0.25f) {\n    RenderDamageEffects(mgr, ballToWorld);\n  } else if (x1c30_boostOverLightFactor > 0.f && !dying) {\n    const int count = std::min(int(speed * 0.5f), 5);\n    for (int i = 0; i < count; ++i) {\n      const zeus::CTransform xf =\n          zeus::CTransform::Translate(*x1c90_ballPosAvg.GetEntry(i)) * x1c3c_ballOrientAvg.GetEntry(i)->toTransform();\n      const float alpha = (1.f - static_cast<float>(i) / 5.f) * x1c30_boostOverLightFactor * 0.2f;\n      if (x68_lowPolyBallModel) {\n        const CModelFlags lpFlags(7, u8(x6c_lowPolyBallModelShader), 1, zeus::CColor(1.f, alpha));\n        x68_lowPolyBallModel->Render(mgr, xf, nullptr, lpFlags);\n      }\n    }\n  }\n\n  ColorArray c = BallSwooshColors[x8_ballGlowColorIdx];\n  const float swooshAlpha = x1c20_tireFactor / x1c24_maxTireFactor;\n  zeus::CColor color0 = {c[0] / 255.f, c[1] / 255.f, c[2] / 255.f, swooshAlpha};\n  c = BallSwooshColorsCharged[x8_ballGlowColorIdx];\n  zeus::CColor color1 = {c[0] / 255.f, c[1] / 255.f, c[2] / 255.f, swooshAlpha};\n  float t = 0.f;\n  if (x1df4_boostDrainTime > 0.f) {\n    t = zeus::clamp(0.f, (speed - 25.f) / 15.f, 1.f);\n  }\n  const zeus::CColor tailColor = zeus::CColor::lerp(color0, color1, t);\n\n  x19b8_slowBlueTailSwooshGen->SetModulationColor(tailColor);\n  x19b8_slowBlueTailSwooshGen->Render();\n  x19bc_slowBlueTailSwooshGen2->SetModulationColor(tailColor);\n  x19bc_slowBlueTailSwooshGen2->Render();\n  x19c0_slowBlueTailSwoosh2Gen->SetModulationColor(tailColor);\n  x19c0_slowBlueTailSwoosh2Gen->Render();\n  x19c4_slowBlueTailSwoosh2Gen2->SetModulationColor(tailColor);\n  x19c4_slowBlueTailSwoosh2Gen2->Render();\n\n  if (x1df4_boostDrainTime > 0.f && speed > 23.f && swooshAlpha > 0.5f) {\n    const float laggyAlpha = zeus::clamp(0.f, (speed - 23.f) / 17.f, t);\n    c = BallSwooshColorsJaggy[x8_ballGlowColorIdx];\n    const zeus::CColor colorJaggy = {c[0] / 255.f, c[1] / 255.f, c[2] / 255.f, laggyAlpha};\n    x19c8_jaggyTrailGen->SetModulationColor(colorJaggy);\n    x19c8_jaggyTrailGen->Render();\n  }\n\n  RenderSpiderBallElectricalEffect();\n\n  if (mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::SpiderBall) && x60_spiderBallGlassModel) {\n    const float tmp = std::max(x1c38_spiderLightFactor, x1c34_boostLightFactor);\n    CModelFlags sflags(0, u8(x64_spiderBallGlassModelShader), 3, zeus::skWhite);\n    // sflags.m_extendedShader = EExtendedShader::LightingCubeReflection;\n    if (tmp != 1.f) {\n      if (lights->HasShadowLight()) {\n        x1c14_worldShadow->EnableModelProjectedShadow(ballToWorld, lights->GetShadowLightArrIndex(), 1.f);\n        // sflags.m_extendedShader = EExtendedShader::LightingCubeReflectionWorldShadow;\n      }\n      x60_spiderBallGlassModel->Render(mgr, ballToWorld, x1c18_actorLights.get(), sflags);\n      x1c14_worldShadow->DisableModelProjectedShadow();\n    } else {\n      x60_spiderBallGlassModel->Render(mgr, ballToWorld, nullptr, sflags);\n    }\n  }\n\n  x19cc_wallSparkGen->Render();\n  x1bc8_wakeEffectGens[7]->Render();\n  if (x1c0c_wakeEffectIdx != -1) {\n    x1bc8_wakeEffectGens[x1c0c_wakeEffectIdx]->Render();\n  }\n\n  c = BallGlowColors[x8_ballGlowColorIdx];\n  zeus::CColor glowColor = {c[0] / 255.f, c[1] / 255.f, c[2] / 255.f, 1.f};\n  x19d0_ballInnerGlowGen->SetModulationColor(glowColor);\n  if (x19d0_ballInnerGlowGen->GetNumActiveChildParticles() > 0) {\n    c = BallTransFlashColors[x8_ballGlowColorIdx];\n    glowColor = zeus::CColor{c[0] / 255.f, c[1] / 255.f, c[2] / 255.f, 1.f};\n    x19d0_ballInnerGlowGen->GetActiveChildParticle(0).SetModulationColor(glowColor);\n    if (x19d0_ballInnerGlowGen->GetNumActiveChildParticles() > 1) {\n      c = BallAuxGlowColors[x8_ballGlowColorIdx];\n      glowColor = zeus::CColor{c[0] / 255.f, c[1] / 255.f, c[2] / 255.f, 1.f};\n      x19d0_ballInnerGlowGen->GetActiveChildParticle(1).SetModulationColor(glowColor);\n    }\n  }\n\n  x19d0_ballInnerGlowGen->Render();\n  x19d4_spiderBallMagnetEffectGen->Render();\n  RenderEnergyDrainEffects(mgr);\n  if (x19d8_boostBallGlowGen->GetModulationColor() != zeus::skClear) {\n    x19d8_boostBallGlowGen->Render();\n  }\n\n  RenderMorphBallTransitionFlash(mgr);\n\n  if (x0_player.GetFrozenState()) {\n    constexpr CModelFlags modelFlags(0, 0, 3, zeus::skWhite);\n    x70_frozenBallModel->Render(mgr, zeus::CTransform::Translate(ballToWorld.origin), lights, modelFlags);\n  }\n\n  RenderIceBreakEffect(mgr);\n}\n\nvoid CMorphBall::ResetMorphBallTransitionFlash() {\n  x19a8_morphBallTransitionFlash.Lock();\n  if (x19dc_morphBallTransitionFlashGen) {\n    x19dc_morphBallTransitionFlashGen.reset();\n  }\n}\n\nvoid CMorphBall::UpdateMorphBallTransitionFlash(float dt) {\n  if (!x19dc_morphBallTransitionFlashGen && x19a8_morphBallTransitionFlash.IsLoaded()) {\n    x19dc_morphBallTransitionFlashGen = std::make_unique<CElementGen>(x19a8_morphBallTransitionFlash);\n    x19dc_morphBallTransitionFlashGen->SetOrientation(x0_player.GetTransform().getRotation());\n  }\n  if (x19dc_morphBallTransitionFlashGen) {\n    if (x19dc_morphBallTransitionFlashGen->IsSystemDeletable()) {\n      x19dc_morphBallTransitionFlashGen.reset();\n      x19a8_morphBallTransitionFlash.Unlock();\n    } else {\n      x19dc_morphBallTransitionFlashGen->SetGlobalTranslation(GetBallToWorld().origin);\n      x19dc_morphBallTransitionFlashGen->Update(dt);\n    }\n  }\n}\n\nvoid CMorphBall::RenderMorphBallTransitionFlash(const CStateManager&) const {\n  if (x19dc_morphBallTransitionFlashGen == nullptr) {\n    return;\n  }\n\n  const auto colorData = BallTransFlashColors[x8_ballGlowColorIdx];\n  const zeus::CColor color = {\n      float(colorData[0]) / 255.f,\n      float(colorData[1]) / 255.f,\n      float(colorData[2]) / 255.f,\n      1.f,\n  };\n  x19dc_morphBallTransitionFlashGen->SetModulationColor(color);\n  x19dc_morphBallTransitionFlashGen->Render();\n}\n\nvoid CMorphBall::UpdateIceBreakEffect(float dt) {\n  if (!x19e0_effect_morphBallIceBreakGen && x19b0_effect_morphBallIceBreak.IsLoaded()) {\n    x19e0_effect_morphBallIceBreakGen = std::make_unique<CElementGen>(x19b0_effect_morphBallIceBreak);\n    x19e0_effect_morphBallIceBreakGen->SetOrientation(x0_player.GetTransform().getRotation());\n  }\n  if (x19e0_effect_morphBallIceBreakGen) {\n    if (x19e0_effect_morphBallIceBreakGen->IsSystemDeletable()) {\n      x19e0_effect_morphBallIceBreakGen.reset();\n      x19b0_effect_morphBallIceBreak.Unlock();\n    } else {\n      x19e0_effect_morphBallIceBreakGen->SetGlobalTranslation(GetBallToWorld().origin);\n      x19e0_effect_morphBallIceBreakGen->Update(dt);\n    }\n  }\n}\n\nvoid CMorphBall::RenderIceBreakEffect(const CStateManager& mgr) const {\n  if (x19e0_effect_morphBallIceBreakGen) {\n    x19e0_effect_morphBallIceBreakGen->Render();\n  }\n}\n\nvoid CMorphBall::RenderDamageEffects(const CStateManager& mgr, const zeus::CTransform& xf) const {\n  CRandom16 rand;\n  CModelFlags flags(7, 0, 1,\n                    zeus::CColor(0.25f * x1e44_damageEffect, 0.1f * x1e44_damageEffect, 0.1f * x1e44_damageEffect,\n                                 1.f)); // No Z update\n  // flags.m_extendedShader = EExtendedShader::SolidColorAdditive;\n  for (int i = 0; i < 5; ++i) {\n    rand.Float();\n    const float translateMag = 0.15f * x1e44_damageEffect * std::sin(30.f * x1e4c_damageTime + rand.Float() * M_PIF);\n    const zeus::CTransform modelXf =\n        xf * zeus::CTransform::Translate(translateMag * rand.Float(), translateMag * rand.Float(),\n                                         translateMag * rand.Float());\n    x68_lowPolyBallModel->Render(CModelData::EWhichModel::Normal, modelXf, nullptr, flags);\n  }\n}\n\nvoid CMorphBall::UpdateHalfPipeStatus(CStateManager& mgr, float dt) {\n  x1dfc_touchHalfPipeCooldown -= dt;\n  x1dfc_touchHalfPipeCooldown = std::max(0.f, x1dfc_touchHalfPipeCooldown);\n  x1e04_touchHalfPipeRecentCooldown -= dt;\n  x1e04_touchHalfPipeRecentCooldown = std::max(0.f, x1e04_touchHalfPipeRecentCooldown);\n  if (x1dfc_touchHalfPipeCooldown > 0.f) {\n    const float avg = *x1cd0_liftSpeedAvg.GetAverage();\n    if (avg > 25.f || (GetIsInHalfPipeMode() && avg > 4.5f)) {\n      SetIsInHalfPipeMode(true);\n      SetIsInHalfPipeModeInAir(!x1df8_27_ballCloseToCollision);\n      SetTouchedHalfPipeRecently(x1e04_touchHalfPipeRecentCooldown > 0.f);\n      if (GetIsInHalfPipeModeInAir()) {\n        x1e08_prevHalfPipeNormal = zeus::skZero3f;\n        x1e14_halfPipeNormal = zeus::skZero3f;\n      }\n    } else {\n      DisableHalfPipeStatus();\n    }\n  } else {\n    DisableHalfPipeStatus();\n  }\n\n  if (GetIsInHalfPipeMode()) {\n    x0_player.SetCollisionAccuracyModifier(10.f);\n  } else {\n    x0_player.SetCollisionAccuracyModifier(1.f);\n  }\n}\n\nvoid CMorphBall::DisableHalfPipeStatus() {\n  SetIsInHalfPipeMode(false);\n  SetIsInHalfPipeModeInAir(false);\n  SetTouchedHalfPipeRecently(false);\n  x1dfc_touchHalfPipeCooldown = 0.f;\n  x1e00_disableControlCooldown = 0.f;\n  x0_player.SetCollisionAccuracyModifier(1.f);\n  x1e08_prevHalfPipeNormal = zeus::skZero3f;\n  x1e14_halfPipeNormal = zeus::skZero3f;\n}\n\nbool CMorphBall::BallCloseToCollision(const CStateManager& mgr, float dist, const CMaterialFilter& filter) const {\n  constexpr CMaterialList playerOrSolid(EMaterialTypes::Player, EMaterialTypes::Solid);\n  const CCollidableSphere sphere(\n      zeus::CSphere(x0_player.GetTranslation() + zeus::CVector3f(0.f, 0.f, GetBallRadius()), dist), playerOrSolid);\n\n  EntityList nearList;\n  mgr.BuildColliderList(nearList, x0_player, sphere.CalculateLocalAABox());\n\n  if (CGameCollision::DetectStaticCollisionBoolean(mgr, sphere, zeus::CTransform(), filter)) {\n    return true;\n  }\n\n  for (const auto& id : nearList) {\n    if (const TCastToConstPtr<CPhysicsActor> act = mgr.GetObjectById(id)) {\n      if (CCollisionPrimitive::CollideBoolean(\n              {sphere, filter, zeus::CTransform()},\n              {*act->GetCollisionPrimitive(), CMaterialFilter::skPassEverything, act->GetPrimitiveTransform()})) {\n        return true;\n      }\n    }\n  }\n  return false;\n}\n\nvoid CMorphBall::CollidedWith(TUniqueId id, const CCollisionInfoList& list, CStateManager& mgr) {\n  x74_collisionInfos = list;\n\n  CMaterialList allMats;\n  for (const CCollisionInfo& info : list) {\n    allMats.Add(info.GetMaterialLeft());\n  }\n\n  const zeus::CVector3f vel = x0_player.GetVelocity();\n  const float velMag = vel.magnitude();\n  auto wakeMaterial = EMaterialTypes::NoStepLogic;\n  if (velMag > 7.f && x0_player.GetFluidCounter() == 0) {\n    bool hitWall = false;\n    for (const CCollisionInfo& info : list) {\n      if (!hitWall) {\n        if (info.GetMaterialLeft().HasMaterial(EMaterialTypes::Wall)) {\n          hitWall = true;\n          if (info.GetMaterialLeft().HasMaterial(EMaterialTypes::Stone) ||\n              info.GetMaterialLeft().HasMaterial(EMaterialTypes::Metal)) {\n            x19cc_wallSparkGen->SetTranslation(info.GetPoint());\n            x19cc_wallSparkGen->SetParticleEmission(true);\n            x1e38_wallSparkFrameCountdown = 7;\n          }\n        }\n      }\n\n      if (wakeMaterial == EMaterialTypes::NoStepLogic) {\n        if (info.GetMaterialLeft().HasMaterial(EMaterialTypes::Floor)) {\n          EMaterialTypes tmpMaterial = EMaterialTypes::NoStepLogic;\n          if (info.GetMaterialLeft().HasMaterial(EMaterialTypes::Dirt)) {\n            tmpMaterial = EMaterialTypes::Dirt;\n          } else {\n            tmpMaterial = wakeMaterial;\n          }\n\n          if (info.GetMaterialLeft().HasMaterial(EMaterialTypes::Sand)) {\n            tmpMaterial = EMaterialTypes::Sand;\n          }\n\n          if (info.GetMaterialLeft().HasMaterial(EMaterialTypes::Lava)) {\n            tmpMaterial = EMaterialTypes::Lava;\n          }\n\n          if (info.GetMaterialLeft().HasMaterial(EMaterialTypes::MudSlow)) {\n            tmpMaterial = EMaterialTypes::MudSlow;\n          }\n\n          if (info.GetMaterialLeft().HasMaterial(EMaterialTypes::Snow)) {\n            tmpMaterial = EMaterialTypes::Snow;\n          }\n\n          if (info.GetMaterialLeft().HasMaterial(EMaterialTypes::Phazon)) {\n            tmpMaterial = EMaterialTypes::Phazon;\n          }\n\n          wakeMaterial = tmpMaterial;\n          if (tmpMaterial != EMaterialTypes::NoStepLogic) {\n            int mappedIdx = skWakeEffectMap[size_t(tmpMaterial)];\n\n            // Phazon\n            if (mappedIdx == 0) {\n              const CGameArea* area = mgr.GetWorld()->GetAreaAlways(mgr.GetNextAreaId());\n              if (const CScriptAreaAttributes* attribs = area->GetPostConstructed()->x10d8_areaAttributes) {\n                if (attribs->GetPhazonType() == EPhazonType::Orange) {\n                  mappedIdx = 1; // Orange Phazon\n                }\n              }\n            }\n\n            if (x1c0c_wakeEffectIdx != mappedIdx) {\n              if (x1c0c_wakeEffectIdx != -1) {\n                x1bc8_wakeEffectGens[x1c0c_wakeEffectIdx]->SetParticleEmission(false);\n              }\n              x1c0c_wakeEffectIdx = mappedIdx;\n            }\n\n            x1bc8_wakeEffectGens[x1c0c_wakeEffectIdx]->SetParticleEmission(true);\n            x1bc8_wakeEffectGens[x1c0c_wakeEffectIdx]->SetTranslation(info.GetPoint());\n          }\n        }\n      }\n    }\n\n    if (hitWall && !CSfxManager::IsPlaying(x1e28_wallHitSfxHandle)) {\n      x1e28_wallHitSfxHandle = CSfxManager::AddEmitter(SFXsam_ball_wallhit, x0_player.GetTranslation(), zeus::skZero3f,\n                                                       true, false, 0x7f, kInvalidAreaId);\n      x0_player.ApplySubmergedPitchBend(x1e28_wallHitSfxHandle);\n    }\n  }\n\n  if (wakeMaterial == EMaterialTypes::NoStepLogic && x1c0c_wakeEffectIdx != -1) {\n    x1bc8_wakeEffectGens[int(x1c0c_wakeEffectIdx)]->SetParticleEmission(false);\n  }\n\n  x1954_isProjectile = false;\n\n  if (allMats.HasMaterial(EMaterialTypes::HalfPipe)) {\n    x1dfc_touchHalfPipeCooldown = 4.f;\n    x1e04_touchHalfPipeRecentCooldown = 0.05f;\n    for (const CCollisionInfo& info : list) {\n      if (info.GetMaterialLeft().HasMaterial(EMaterialTypes::HalfPipe)) {\n        if (info.GetNormalLeft().dot(x1e14_halfPipeNormal) < 0.99) {\n          x1e08_prevHalfPipeNormal = x1e14_halfPipeNormal;\n          x1e14_halfPipeNormal = info.GetNormalLeft();\n          if (zeus::close_enough(x1e08_prevHalfPipeNormal, zeus::skZero3f, 0.000011920929f)) {\n            x1e08_prevHalfPipeNormal = x1e14_halfPipeNormal;\n          }\n        }\n      }\n    }\n  }\n\n  if (x28_tireMode && allMats.HasMaterial(EMaterialTypes::Floor) && allMats.HasMaterial(EMaterialTypes::Wall)) {\n    SwitchToMarble();\n  }\n\n  if (!GetIsInHalfPipeMode() && x1de4_24_inBoost && velMag > 3.f) {\n    const zeus::CVector3f velNorm = vel.normalized();\n    for (const CCollisionInfo& info : list) {\n      if (!info.GetMaterialLeft().HasMaterial(EMaterialTypes::HalfPipe) && info.GetNormalLeft().dot(velNorm) < -0.4f) {\n        LeaveBoosting();\n        DampLinearAndAngularVelocities(0.4f, 0.01f);\n        break;\n      }\n    }\n  }\n\n  if (id == kInvalidUniqueId) {\n    const zeus::CVector3f cvel = x0_player.GetVelocity();\n    const float cvelMag = cvel.magnitude();\n    const zeus::CVector3f cforce = x1c_controlForce;\n    if (cforce.magnitude() > 1000.f && cvelMag > 8.f) {\n      const zeus::CVector3f cforceNorm = cforce.normalized();\n      const zeus::CVector3f cvelNorm = cvel.normalized();\n      for (const CCollisionInfo& info : list) {\n        if (IsClimbable(info) && info.GetNormalLeft().dot(cforceNorm) < -0.4f &&\n            info.GetNormalLeft().dot(cvelNorm) < -0.6f) {\n          const float threeQVel = 0.75f * cvelMag;\n          const float maxSpeed = g_tweakBall->GetBallTranslationMaxSpeed(int(x0_player.GetSurfaceRestraint()));\n          const float f0 = maxSpeed * 0.15f;\n          const float f3 = (threeQVel - f0) < 0.f ? threeQVel : f0;\n          float f4 = maxSpeed * 0.25f;\n          f4 = (f3 - f4) < 0.f ? f4 : f3;\n          const zeus::CVector3f newVel = cvel + zeus::CVector3f(0.f, 0.f, f4);\n          x1dd8_ = newVel;\n          x0_player.SetVelocityWR(newVel);\n          x1dc8_failsafeCounter += 1;\n          break;\n        }\n      }\n    }\n  }\n\n  if (list.GetCount() > 2 && list.GetItem(0).GetNormalLeft().z() > 0.2f &&\n      std::fabs(x0_player.GetVelocity().dot(list.GetItem(0).GetNormalLeft())) > 2.f) {\n    float accum = 0.f;\n    int count = 0;\n    for (auto it = list.begin() + 1; it != list.end(); ++it) {\n      const CCollisionInfo& item1 = *it;\n      for (auto it2 = list.begin() + 1; it2 != list.end(); ++it2) {\n        const CCollisionInfo& item2 = *it2;\n        accum += item1.GetNormalLeft().dot(item2.GetNormalLeft());\n        count += 1;\n      }\n    }\n\n    if (accum / float(count) < 0.5f) {\n      x1dc8_failsafeCounter += 1;\n    }\n  }\n\n  if (list.GetCount() != 0) {\n    SelectMorphBallSounds(list.GetItem(0).GetMaterialLeft());\n  }\n}\n\nbool CMorphBall::IsInFrustum(const zeus::CFrustum& frustum) const {\n  if (x58_ballModel->IsNull()) {\n    return false;\n  }\n\n  if (x58_ballModel->IsInFrustum(GetBallToWorld(), frustum)) {\n    return true;\n  }\n\n  const auto swooshBounds = x19b8_slowBlueTailSwooshGen->GetBounds();\n  return x19b8_slowBlueTailSwooshGen->GetModulationColor().a() != 0.f && swooshBounds &&\n         frustum.aabbFrustumTest(*swooshBounds);\n}\n\nvoid CMorphBall::ComputeLiftForces(const zeus::CVector3f& controlForce, const zeus::CVector3f& velocity,\n                                   const CStateManager& mgr) {\n  x1cd0_liftSpeedAvg.AddValue(velocity.magnitude());\n  x1d10_liftControlForceAvg.AddValue(controlForce);\n\n  const zeus::CVector3f avgControlForce = *x1d10_liftControlForceAvg.GetAverage();\n  const float avgControlForceMag = avgControlForce.magnitude();\n\n  if (avgControlForceMag > 12000.f) {\n    const float avgLiftSpeed = *x1cd0_liftSpeedAvg.GetAverage();\n\n    if (avgLiftSpeed < 4.f) {\n      const zeus::CTransform xf = x0_player.GetPrimitiveTransform();\n      zeus::CAABox aabb = x0_player.GetCollisionPrimitive()->CalculateAABox(xf);\n      aabb.min -= zeus::CVector3f(0.1f, 0.1f, -0.05f);\n      aabb.max += zeus::CVector3f(0.1f, 0.1f, -0.05f);\n      const CCollidableAABox colAABB(aabb, {EMaterialTypes::Solid});\n\n      if (CGameCollision::DetectStaticCollisionBoolean(mgr, colAABB, zeus::CTransform(),\n                                                       CMaterialFilter::skPassEverything)) {\n        const zeus::CVector3f pos = xf.origin + zeus::CVector3f(0.f, 0.f, 1.75f * GetBallRadius());\n        const zeus::CVector3f dir = avgControlForce * (1.f / avgControlForceMag);\n        const CRayCastResult result =\n            mgr.RayStaticIntersection(pos, dir, 1.4f, CMaterialFilter::MakeInclude({EMaterialTypes::Solid}));\n        if (result.IsInvalid()) {\n          const float mag = 1.f - std::max(0.f, avgLiftSpeed - 3.f);\n          const zeus::CVector3f force(0.f, 0.f, mag * 40000.f);\n          x0_player.ApplyForceWR(force, zeus::CAxisAngle());\n          x0_player.ApplyImpulseWR(zeus::skZero3f, zeus::CAxisAngle(-x1924_surfaceToWorld.basis[0] * 1000.f * mag));\n        }\n      }\n    }\n  }\n}\n\nfloat CMorphBall::CalculateSurfaceFriction() const {\n  float friction = g_tweakBall->GetBallTranslationFriction(int(x0_player.GetSurfaceRestraint()));\n  if (x0_player.GetAttachedActor() != kInvalidUniqueId) {\n    friction *= 2.f;\n  }\n\n  const size_t drainSourceCount = x0_player.GetEnergyDrain().GetEnergyDrainSources().size();\n  if (drainSourceCount > 0) {\n    friction *= drainSourceCount * 1.5f;\n  }\n\n  return friction;\n}\n\nvoid CMorphBall::ApplyGravity(const CStateManager& mgr) {\n  if (!x0_player.CheckSubmerged() || mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::GravitySuit)) {\n    x0_player.SetMomentumWR({0.f, 0.f, x0_player.GetMass() * g_tweakBall->GetBallGravity()});\n  } else {\n    x0_player.SetMomentumWR({0.f, 0.f, x0_player.GetMass() * g_tweakBall->GetBallWaterGravity()});\n  }\n}\n\nvoid CMorphBall::SpinToSpeed(float holdMag, const zeus::CVector3f& torque, float mag) {\n  x0_player.ApplyTorqueWR(torque * ((holdMag - x0_player.GetAngularVelocityWR().getVector().magnitude()) * mag));\n}\n\nfloat CMorphBall::ComputeMaxSpeed() const {\n  if (GetIsInHalfPipeMode()) {\n    return std::min(x0_player.GetVelocity().magnitude() * 1.5f, 95.f);\n  }\n  return g_tweakBall->GetBallTranslationMaxSpeed(int(x0_player.GetSurfaceRestraint()));\n}\n\nconstexpr CDamageInfo kBallDamage = {CWeaponMode(EWeaponType::BoostBall), 50000.f, 0.f, 0.f};\n\nvoid CMorphBall::Touch(CActor& actor, CStateManager& mgr) {\n  if (const TCastToConstPtr<CPhysicsActor> act = actor) {\n    if (x1de4_24_inBoost && (act->GetVelocity() - x0_player.GetVelocity()).magnitude() >\n                                g_tweakBall->GetBoostBallMinRelativeSpeedForDamage()) {\n      mgr.ApplyDamage(x0_player.GetUniqueId(), actor.GetUniqueId(), x0_player.GetUniqueId(), kBallDamage,\n                      CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), zeus::skZero3f);\n    }\n  }\n}\n\nbool CMorphBall::IsClimbable(const CCollisionInfo& cinfo) const {\n  if (std::fabs(cinfo.GetNormalLeft().z()) < 0.7f) {\n    const float pointToBall = GetBallToWorld().origin.z() - cinfo.GetPoint().z();\n    if (pointToBall > 0.1f && pointToBall < GetBallRadius() - 0.05f) {\n      return true;\n    }\n  }\n  return false;\n}\n\nvoid CMorphBall::FluidFXThink(CActor::EFluidState state, CScriptWater& water, CStateManager& mgr) {\n  zeus::CVector3f vec = x0_player.GetTranslation();\n  vec.z() = float(water.GetTriggerBoundsWR().max.z());\n\n  if (x0_player.x4fc_flatMoveSpeed >= 8.f) {\n    const float maxVel = x0_player.GetBallMaxVelocity();\n    if (mgr.GetFluidPlaneManager()->GetLastSplashDeltaTime(x0_player.GetUniqueId()) >=\n        (maxVel - x0_player.x4fc_flatMoveSpeed) / (maxVel - 8.f) * 0.1f) {\n      mgr.GetFluidPlaneManager()->CreateSplash(x0_player.GetUniqueId(), mgr, water, vec, 0.f,\n                                               state == CActor::EFluidState::EnteredFluid);\n    }\n  }\n\n  if (x0_player.x4fc_flatMoveSpeed >= 0.2f) {\n    const float deltaTime = mgr.GetFluidPlaneManager()->GetLastRippleDeltaTime(x0_player.GetUniqueId());\n    float f0;\n    if (x0_player.x4fc_flatMoveSpeed <= 15.f) {\n      f0 = 0.13f;\n    } else {\n      f0 = std::max(0.1f, 0.13f - 0.03f * (x0_player.x4fc_flatMoveSpeed - 15.f) /\n                                      (x0_player.GetBallMaxVelocity() - x0_player.x4fc_flatMoveSpeed));\n    }\n    if (deltaTime >= f0) {\n      water.GetFluidPlane().AddRipple(0.65f * x0_player.x4fc_flatMoveSpeed / x0_player.GetBallMaxVelocity(),\n                                      x0_player.GetUniqueId(), vec, water, mgr);\n    }\n  }\n}\n\nvoid CMorphBall::LoadMorphBallModel(CStateManager& mgr) {\n  const bool spiderBall = mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::SpiderBall);\n\n  int modelIdx = int(mgr.GetPlayerState()->GetCurrentSuitRaw());\n  if (mgr.GetPlayerState()->IsFusionEnabled()) {\n    modelIdx += 4;\n  }\n\n  int loadModelId = modelIdx;\n  if (spiderBall) {\n    loadModelId += 4;\n  }\n  if (mgr.GetPlayerState()->IsFusionEnabled()) {\n    loadModelId += 100;\n  }\n\n  if (loadModelId != x4_loadedModelId) {\n    x4_loadedModelId = loadModelId;\n    if (spiderBall) {\n      x58_ballModel = GetMorphBallModel(kSpiderBallCharacterTable[modelIdx].first, xc_radius);\n      x5c_ballModelShader = kSpiderBallCharacterTable[modelIdx].second;\n\n      x68_lowPolyBallModel = GetMorphBallModel(kSpiderBallLowPolyTable[modelIdx].first, xc_radius);\n      x6c_lowPolyBallModelShader = kSpiderBallLowPolyTable[modelIdx].second;\n\n      if (kSpiderBallGlassTable[modelIdx].first) {\n        x60_spiderBallGlassModel = GetMorphBallModel(kSpiderBallGlassTable[modelIdx].first, xc_radius);\n        x64_spiderBallGlassModelShader = kSpiderBallGlassTable[modelIdx].second;\n      } else {\n        x60_spiderBallGlassModel.reset();\n        x64_spiderBallGlassModelShader = 0;\n      }\n\n      x8_ballGlowColorIdx = kSpiderBallGlowColorIdxTable[modelIdx];\n    } else {\n      x58_ballModel = GetMorphBallModel(kBallCharacterTable[modelIdx].first, xc_radius);\n      x5c_ballModelShader = kBallCharacterTable[modelIdx].second;\n\n      x68_lowPolyBallModel = GetMorphBallModel(kBallLowPolyTable[modelIdx].first, xc_radius);\n      x6c_lowPolyBallModelShader = kBallLowPolyTable[modelIdx].second;\n\n      x8_ballGlowColorIdx = kBallGlowColorIdxTable[modelIdx];\n    }\n\n    x58_ballModel->SetScale(zeus::CVector3f(g_tweakPlayer->GetPlayerBallHalfExtent() * 2.f));\n  }\n}\n\nvoid CMorphBall::AddSpiderBallElectricalEffect() {\n  u32 idx = 0;\n  for (auto& gen : x19e4_spiderElectricGens) {\n    if (gen.second) {\n      ++idx;\n      continue;\n    }\n\n    gen.second = true;\n    x1b6c_activeSpiderElectricList.emplace_back(idx, x1b80_rand.Range(4, 8));\n\n    const float sign = (x1b80_rand.Next() & 256) < 128 ? -1.f : 1.f;\n    const float randDir = sign * (0.9f * GetBallRadius());\n    const float ang0 = zeus::degToRad(-(80.f * x1b80_rand.Float() - 40.f));\n    const float ang1 = zeus::degToRad(90 + -(80.f * x1b80_rand.Float() - 40.f));\n    zeus::CVector3f translation = zeus::CVector3f(1.32f * randDir, 0, 0) +\n                                  0.6f * zeus::CVector3f(sign * -(std::sin(ang1) * std::cos(ang0)),\n                                                         sign * std::sin(ang0), sign * std::cos(ang0) * std::cos(ang1));\n    zeus::CVector3f transInc = (1 / 6.f) * (zeus::CVector3f(randDir, 0.f, 0.f) - translation);\n    gen.first->DoSpiderBallWarmup(translation, transInc);\n    break;\n  }\n}\n\nvoid CMorphBall::UpdateSpiderBallElectricalEffects() {\n  zeus::CTransform ballToWorld = GetBallToWorld();\n  const zeus::CVector3f ballTranslation = ballToWorld.origin;\n  ballToWorld.origin = zeus::skZero3f;\n\n  for (auto it = x1b6c_activeSpiderElectricList.begin(); it != x1b6c_activeSpiderElectricList.end();) {\n    CSpiderBallElectricityManager& elec = *it;\n    if (elec.x8_curFrame >= elec.x4_lifetime) {\n      x19e4_spiderElectricGens[elec.x0_effectIdx].second = false;\n      it = x1b6c_activeSpiderElectricList.erase(it);\n      continue;\n    }\n\n    CParticleSwoosh& swoosh = *x19e4_spiderElectricGens[elec.x0_effectIdx].first;\n    swoosh.SetModulationColor(zeus::CColor(1.f, 1.f - elec.x8_curFrame / elec.x4_lifetime));\n    swoosh.SetGlobalTranslation(ballTranslation);\n    swoosh.SetGlobalOrientation(ballToWorld);\n    elec.x8_curFrame += 1;\n    ++it;\n  }\n}\n\nvoid CMorphBall::RenderSpiderBallElectricalEffect() const {\n  for (const CSpiderBallElectricityManager& effect : x1b6c_activeSpiderElectricList) {\n    x19e4_spiderElectricGens[effect.x0_effectIdx].first->Render();\n  }\n}\n\nvoid CMorphBall::RenderEnergyDrainEffects(const CStateManager& mgr) const {\n  for (const CEnergyDrainSource& source : x0_player.x274_energyDrain.GetEnergyDrainSources()) {\n    if (const auto* metroid =\n            CPatterned::CastTo<MP1::CMetroidBeta>(mgr.GetObjectById(source.GetEnergyDrainSourceId()))) {\n      metroid->RenderHitBallEffect();\n      break;\n    }\n  }\n}\n\nvoid CMorphBall::TouchModel(const CStateManager& mgr) const {\n  x58_ballModel->Touch(mgr, x5c_ballModelShader);\n  if (mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::SpiderBall) && x60_spiderBallGlassModel) {\n    x60_spiderBallGlassModel->Touch(mgr, x64_spiderBallGlassModelShader);\n  }\n  x68_lowPolyBallModel->Touch(mgr, x6c_lowPolyBallModelShader);\n}\n\nvoid CMorphBall::TakeDamage(float dam) {\n  if (dam <= 0.f) {\n    x1e44_damageEffect = 0.f;\n    x1e48_damageEffectDecaySpeed = 0.f;\n    return;\n  }\n\n  if (dam >= 20.f) {\n    x1e48_damageEffectDecaySpeed = 0.25f;\n  } else if (dam > 5.f) {\n    x1e48_damageEffectDecaySpeed = 1.f - (dam - 5.f) / 15.f * 0.75f;\n  } else {\n    x1e48_damageEffectDecaySpeed = 1.f;\n  }\n\n  x1e44_damageEffect = 1.f;\n}\n\nvoid CMorphBall::StartLandingSfx() {\n  if (x0_player.GetVelocity().z() >= -5.f || x1e36_landSfx == 0xffff) {\n    return;\n  }\n\n  const float vol = zeus::clamp(0.75f, 0.0125f * x0_player.GetLastVelocity().z() + 0.75f, 1.f);\n  CSfxHandle hnd = CSfxManager::SfxStart(x1e36_landSfx, vol, 0.f, true, 0x7f, false, kInvalidAreaId);\n  x0_player.ApplySubmergedPitchBend(hnd);\n}\n\nvoid CMorphBall::Stop() {\n  x19b0_effect_morphBallIceBreak.Lock();\n  if (x19e0_effect_morphBallIceBreakGen) {\n    x19e0_effect_morphBallIceBreakGen.reset();\n  }\n}\n\nvoid CMorphBall::StopSounds() {\n  if (x1e2c_rollSfxHandle) {\n    CSfxManager::SfxStop(x1e2c_rollSfxHandle);\n    x1e2c_rollSfxHandle.reset();\n  }\n  if (x1e30_spiderSfxHandle) {\n    CSfxManager::SfxStop(x1e30_spiderSfxHandle);\n    x1e30_spiderSfxHandle.reset();\n  }\n}\n\nvoid CMorphBall::StopEffects() {\n  x19cc_wallSparkGen->SetParticleEmission(false);\n  x1bc8_wakeEffectGens[7]->SetParticleEmission(false);\n  if (x1c0c_wakeEffectIdx != -1) {\n    x1bc8_wakeEffectGens[x1c0c_wakeEffectIdx]->SetParticleEmission(false);\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CMorphBall.hpp",
    "content": "#pragma once\n\n#include <array>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Collision/CCollidableSphere.hpp\"\n#include \"Runtime/Collision/CCollisionInfoList.hpp\"\n#include \"Runtime/Graphics/CRainSplashGenerator.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/Particle/CParticleSwoosh.hpp\"\n#include \"Runtime/World/CActor.hpp\"\n#include \"Runtime/World/CMorphBallShadow.hpp\"\n#include \"Runtime/World/CWorldShadow.hpp\"\n#include \"Runtime/World/ScriptObjectSupport.hpp\"\n\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector2f.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CActorLights;\nclass CDamageInfo;\nclass CPlayer;\nclass CScriptWater;\nclass CStateManager;\n\nstruct CFinalInput;\n\nclass CMorphBall {\npublic:\n  enum class EBallBoostState { BoostAvailable, BoostDisabled };\n\n  enum class ESpiderBallState { Inactive, Active };\n\n  enum class EBombJumpState { BombJumpAvailable, BombJumpDisabled };\n\nprivate:\n  struct CSpiderBallElectricityManager {\n    u32 x0_effectIdx;\n    u32 x4_lifetime;\n    u32 x8_curFrame = 0;\n    CSpiderBallElectricityManager(u32 effectIdx, u32 lifetime) : x0_effectIdx(effectIdx), x4_lifetime(lifetime) {}\n  };\n  CPlayer& x0_player;\n  s32 x4_loadedModelId = -1;\n  u32 x8_ballGlowColorIdx = 0;\n  float xc_radius;\n  zeus::CVector3f x10_boostControlForce;\n  zeus::CVector3f x1c_controlForce;\n  bool x28_tireMode = false;\n  float x2c_tireLeanAngle = 0.f;\n  float x30_ballTiltAngle = 0.f;\n  CCollidableSphere x38_collisionSphere;\n  std::unique_ptr<CModelData> x58_ballModel;\n  u32 x5c_ballModelShader = 0;\n  std::unique_ptr<CModelData> x60_spiderBallGlassModel;\n  u32 x64_spiderBallGlassModelShader = 0;\n  std::unique_ptr<CModelData> x68_lowPolyBallModel;\n  u32 x6c_lowPolyBallModelShader = 0;\n  std::unique_ptr<CModelData> x70_frozenBallModel;\n  CCollisionInfoList x74_collisionInfos;\n  u32 xc78_ = 0;\n  ESpiderBallState x187c_spiderBallState = ESpiderBallState::Inactive;\n  zeus::CVector3f x1880_playerToSpiderNormal;\n  float x188c_spiderPullMovement = 1.f;\n  zeus::CVector3f x1890_spiderTrackPoint;\n  zeus::CVector3f x189c_spiderInterpBetweenPoints;\n  zeus::CVector3f x18a8_spiderBetweenPoints;\n  float x18b4_linVelDamp = 0.f;\n  float x18b8_angVelDamp = 0.f;\n  bool x18bc_spiderNearby = false;\n  bool x18bd_touchingSpider = false;\n  bool x18be_spiderBallSwinging = false;\n  bool x18bf_spiderSwingInAir = true;\n  bool x18c0_isSpiderSurface = false;\n  zeus::CTransform x18c4_spiderSurfaceTransform;\n  float x18f4_spiderSurfacePivotAngle = 0.f;\n  float x18f8_spiderSurfacePivotTargetAngle = 0.f;\n  float x18fc_refPullVel = 0.f;\n  float x1900_playerToSpiderTrackDist = 0.f;\n  float x1904_swingControlDir = 0.f;\n  float x1908_swingControlTime = 0.f;\n  zeus::CVector2f x190c_normSpiderSurfaceForces;\n  float x1914_spiderTrackForceMag = 0.f;\n  float x1918_spiderViewControlMag = 0.f;\n  float x191c_damageTimer = 0.f;\n  bool x1920_spiderForcesReset = false;\n  zeus::CTransform x1924_surfaceToWorld;\n  bool x1954_isProjectile = false;\n  std::vector<CToken> x1958_animationTokens;\n  TToken<CSwooshDescription> x1968_slowBlueTailSwoosh;\n  TToken<CSwooshDescription> x1970_slowBlueTailSwoosh2;\n  TToken<CSwooshDescription> x1978_jaggyTrail;\n  TToken<CGenDescription> x1980_wallSpark;\n  TToken<CGenDescription> x1988_ballInnerGlow;\n  TToken<CGenDescription> x1990_spiderBallMagnetEffect;\n  TToken<CGenDescription> x1998_boostBallGlow;\n  TToken<CSwooshDescription> x19a0_spiderElectric;\n  TToken<CGenDescription> x19a8_morphBallTransitionFlash;\n  TToken<CGenDescription> x19b0_effect_morphBallIceBreak;\n  std::unique_ptr<CParticleSwoosh> x19b8_slowBlueTailSwooshGen;\n  std::unique_ptr<CParticleSwoosh> x19bc_slowBlueTailSwooshGen2;\n  std::unique_ptr<CParticleSwoosh> x19c0_slowBlueTailSwoosh2Gen;\n  std::unique_ptr<CParticleSwoosh> x19c4_slowBlueTailSwoosh2Gen2;\n  std::unique_ptr<CParticleSwoosh> x19c8_jaggyTrailGen;\n  std::unique_ptr<CElementGen> x19cc_wallSparkGen;\n  std::unique_ptr<CElementGen> x19d0_ballInnerGlowGen;\n  std::unique_ptr<CElementGen> x19d4_spiderBallMagnetEffectGen;\n  std::unique_ptr<CElementGen> x19d8_boostBallGlowGen;\n  std::unique_ptr<CElementGen> x19dc_morphBallTransitionFlashGen;\n  std::unique_ptr<CElementGen> x19e0_effect_morphBallIceBreakGen;\n  rstl::reserved_vector<std::pair<std::unique_ptr<CParticleSwoosh>, bool>, 32> x19e4_spiderElectricGens;\n  std::list<CSpiderBallElectricityManager> x1b6c_activeSpiderElectricList;\n  CRandom16 x1b80_rand{99};\n  rstl::reserved_vector<TToken<CGenDescription>, 8> x1b84_wakeEffects;\n  rstl::reserved_vector<std::unique_ptr<CElementGen>, 8> x1bc8_wakeEffectGens;\n  s32 x1c0c_wakeEffectIdx = -1;\n  TUniqueId x1c10_ballInnerGlowLight = kInvalidUniqueId;\n  std::unique_ptr<CWorldShadow> x1c14_worldShadow;\n  std::unique_ptr<CActorLights> x1c18_actorLights;\n  std::unique_ptr<CRainSplashGenerator> x1c1c_rainSplashGen;\n  float x1c20_tireFactor = 0.f;\n  float x1c24_maxTireFactor = 0.5f;\n  float x1c28_tireInterpSpeed = 1.f;\n  bool x1c2c_tireInterpolating = false;\n  float x1c30_boostOverLightFactor = 0.f;\n  float x1c34_boostLightFactor = 0.f;\n  float x1c38_spiderLightFactor = 0.f;\n  TReservedAverage<zeus::CQuaternion, 5> x1c3c_ballOrientAvg = {{}};\n  TReservedAverage<zeus::CVector3f, 5> x1c90_ballPosAvg = {{}};\n  TReservedAverage<float, 15> x1cd0_liftSpeedAvg = {{}};\n  TReservedAverage<zeus::CVector3f, 15> x1d10_liftControlForceAvg = {{}};\n  u32 x1dc8_failsafeCounter = 0;\n  zeus::CVector3f x1dcc_;\n  zeus::CVector3f x1dd8_;\n  bool x1de4_24_inBoost : 1 = false;\n  bool x1de4_25_boostEnabled : 1 = true;\n  float x1de8_boostChargeTime = 0.f;\n  float x1dec_timeNotInBoost = 0.f;\n  float x1df0_ = 0.f;\n  float x1df4_boostDrainTime = 0.f;\n  bool x1df8_24_inHalfPipeMode : 1 = false;\n  bool x1df8_25_inHalfPipeModeInAir : 1 = false;\n  bool x1df8_26_touchedHalfPipeRecently : 1 = false;\n  bool x1df8_27_ballCloseToCollision : 1 = false;\n  float x1dfc_touchHalfPipeCooldown = 0.f;\n  float x1e00_disableControlCooldown = 0.f;\n  float x1e04_touchHalfPipeRecentCooldown = 0.f;\n  zeus::CVector3f x1e08_prevHalfPipeNormal;\n  zeus::CVector3f x1e14_halfPipeNormal;\n  u32 x1e20_ballAnimIdx = 0;\n  CSfxHandle x1e24_boostSfxHandle;\n  CSfxHandle x1e28_wallHitSfxHandle;\n  CSfxHandle x1e2c_rollSfxHandle;\n  CSfxHandle x1e30_spiderSfxHandle;\n  u16 x1e34_rollSfx = 0xffff;\n  u16 x1e36_landSfx = 0xffff;\n  u32 x1e38_wallSparkFrameCountdown = 0;\n  EBallBoostState x1e3c_boostState = EBallBoostState::BoostAvailable;\n  EBombJumpState x1e40_bombJumpState = EBombJumpState::BombJumpAvailable;\n  float x1e44_damageEffect = 0.f;\n  float x1e48_damageEffectDecaySpeed = 0.f;\n  float x1e4c_damageTime = 0.f;\n  std::unique_ptr<CMorphBallShadow> x1e50_shadow;\n  void LoadAnimationTokens(std::string_view ancsName);\n  void InitializeWakeEffects();\n  static std::unique_ptr<CModelData> GetMorphBallModel(const char* name, float radius);\n  void SelectMorphBallSounds(const CMaterialList& mat);\n  void UpdateMorphBallSounds(float dt);\n  static zeus::CVector3f TransformSpiderBallForcesXY(const zeus::CVector2f& forces, CStateManager& mgr);\n  static zeus::CVector3f TransformSpiderBallForcesXZ(const zeus::CVector2f& forces, CStateManager& mgr);\n  void ResetSpiderBallForces();\n\npublic:\n  CMorphBall(CPlayer& player, float radius);\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr);\n  const CCollidableSphere& GetCollidableSphere() const { return x38_collisionSphere; }\n  bool IsProjectile() const { return x1954_isProjectile; }\n  void GetBallContactMaterials() const {}\n  void GetWallBumpCounter() const {}\n  void GetBoostChargeTimer() const {}\n  bool IsBoosting() const { return x1de4_24_inBoost; }\n  float GetBallRadius() const;\n  float GetBallTouchRadius() const;\n  float ForwardInput(const CFinalInput& input) const;\n  float BallTurnInput(const CFinalInput& input) const;\n  void ComputeBallMovement(const CFinalInput& input, CStateManager& mgr, float dt);\n  bool IsMovementAllowed() const;\n  void UpdateSpiderBall(const CFinalInput& input, CStateManager& mgr, float dt);\n  void ApplySpiderBallSwingingForces(const CFinalInput& input, CStateManager& mgr, float dt);\n  void ApplySpiderBallRollForces(const CFinalInput& input, CStateManager& mgr, float dt);\n  zeus::CVector2f CalculateSpiderBallAttractionSurfaceForces(const CFinalInput& input) const;\n  bool CheckForSwitchToSpiderBallSwinging(CStateManager& mgr) const;\n  bool FindClosestSpiderBallWaypoint(CStateManager& mgr, const zeus::CVector3f& ballCenter,\n                                     zeus::CVector3f& closestPoint, zeus::CVector3f& interpDeltaBetweenPoints,\n                                     zeus::CVector3f& deltaBetweenPoints, float& distance, zeus::CVector3f& normal,\n                                     bool& isSurface, zeus::CTransform& surfaceTransform) const;\n  void SetSpiderBallSwingingState(bool active);\n  float GetSpiderBallControllerMovement(const CFinalInput& input) const;\n  void ResetSpiderBallSwingControllerMovementTimer();\n  void UpdateSpiderBallSwingControllerMovementTimer(float movement, float dt);\n  float GetSpiderBallSwingControllerMovementScalar() const;\n  void CreateSpiderBallParticles(const zeus::CVector3f& ballPos, const zeus::CVector3f& trackPoint);\n  void ComputeMarioMovement(const CFinalInput& input, CStateManager& mgr, float dt);\n  void SetSpiderBallState(ESpiderBallState state) { x187c_spiderBallState = state; }\n  zeus::CTransform GetSwooshToWorld() const;\n  zeus::CTransform GetBallToWorld() const;\n  zeus::CTransform CalculateSurfaceToWorld(const zeus::CVector3f& trackNormal, const zeus::CVector3f& trackPoint,\n                                           const zeus::CVector3f& ballDir) const;\n  bool CalculateBallContactInfo(zeus::CVector3f& normal, zeus::CVector3f& point) const;\n  void UpdateBallDynamics(CStateManager& mgr, float dt);\n  void SwitchToMarble();\n  void SwitchToTire();\n  void Update(float dt, CStateManager& mgr);\n  void DeleteLight(CStateManager& mgr);\n  void SetBallLightActive(CStateManager& mgr, bool active);\n  void EnterMorphBallState(CStateManager& mgr);\n  void LeaveMorphBallState(CStateManager& mgr);\n  void UpdateEffects(float dt, CStateManager& mgr);\n  void ComputeBoostBallMovement(const CFinalInput& input, CStateManager& mgr, float dt);\n  void EnterBoosting(CStateManager& mgr);\n  void LeaveBoosting();\n  void CancelBoosting();\n  bool UpdateMarbleDynamics(CStateManager& mgr, float dt, const zeus::CVector3f& point);\n  void ApplyFriction(float);\n  void DampLinearAndAngularVelocities(float linDamp, float angDamp);\n  float GetMinimumAlignmentSpeed() const;\n  void PreRender(CStateManager& mgr, const zeus::CFrustum& frustum);\n  void Render(const CStateManager& mgr, const CActorLights* lights) const;\n  void ResetMorphBallTransitionFlash();\n  void UpdateMorphBallTransitionFlash(float dt);\n  void RenderMorphBallTransitionFlash(const CStateManager&) const;\n  void UpdateIceBreakEffect(float dt);\n  void RenderIceBreakEffect(const CStateManager& mgr) const;\n  bool IsMorphBallTransitionFlashValid() const { return x19dc_morphBallTransitionFlashGen != nullptr; }\n  void RenderDamageEffects(const CStateManager& mgr, const zeus::CTransform& xf) const;\n  void UpdateHalfPipeStatus(CStateManager& mgr, float dt);\n  bool GetIsInHalfPipeMode() const { return x1df8_24_inHalfPipeMode; }\n  void SetIsInHalfPipeMode(bool b) { x1df8_24_inHalfPipeMode = b; }\n  bool GetIsInHalfPipeModeInAir() const { return x1df8_25_inHalfPipeModeInAir; }\n  void SetIsInHalfPipeModeInAir(bool b) { x1df8_25_inHalfPipeModeInAir = b; }\n  bool GetTouchedHalfPipeRecently() const { return x1df8_26_touchedHalfPipeRecently; }\n  void SetTouchedHalfPipeRecently(bool b) { x1df8_26_touchedHalfPipeRecently = b; }\n  void DisableHalfPipeStatus();\n  bool BallCloseToCollision(const CStateManager& mgr, float dist, const CMaterialFilter& filter) const;\n  void CollidedWith(TUniqueId id, const CCollisionInfoList& list, CStateManager& mgr);\n  bool IsInFrustum(const zeus::CFrustum& frustum) const;\n  void ComputeLiftForces(const zeus::CVector3f& controlForce, const zeus::CVector3f& velocity,\n                         const CStateManager& mgr);\n  float CalculateSurfaceFriction() const;\n  void ApplyGravity(const CStateManager& mgr);\n  void SpinToSpeed(float holdMag, const zeus::CVector3f& torque, float mag);\n  float ComputeMaxSpeed() const;\n  void Touch(CActor& actor, CStateManager& mgr);\n  bool IsClimbable(const CCollisionInfo& cinfo) const;\n  void FluidFXThink(CActor::EFluidState state, CScriptWater& water, CStateManager& mgr);\n  void LoadMorphBallModel(CStateManager& mgr);\n  void AddSpiderBallElectricalEffect();\n  void UpdateSpiderBallElectricalEffects();\n  void RenderSpiderBallElectricalEffect() const;\n  void RenderEnergyDrainEffects(const CStateManager& mgr) const;\n  void TouchModel(const CStateManager& mgr) const;\n  void SetAsProjectile() { x1954_isProjectile = true; }\n  EBallBoostState GetBallBoostState() const { return x1e3c_boostState; }\n  void SetBallBoostState(EBallBoostState state) { x1e3c_boostState = state; }\n  EBombJumpState GetBombJumpState() const { return x1e40_bombJumpState; }\n  void SetBombJumpState(EBombJumpState state) { x1e40_bombJumpState = state; }\n  void TakeDamage(float dam);\n  void DrawBallShadow(const CStateManager& mgr);\n  void DeleteBallShadow();\n  void CreateBallShadow();\n  void RenderToShadowTex(CStateManager& mgr);\n  void StartLandingSfx();\n  ESpiderBallState GetSpiderBallState() const { return x187c_spiderBallState; }\n  void SetDamageTimer(float t) { x191c_damageTimer = t; }\n  void Stop();\n  void StopSounds();\n  void StopEffects();\n  CModelData& GetMorphballModelData() const { return *x58_ballModel; }\n  u32 GetMorphballModelShader() const { return x5c_ballModelShader; }\n  bool GetBoostEnabled() const { return x1de4_25_boostEnabled; }\n  void SetBoostEnabed(bool b) { x1de4_25_boostEnabled = b; }\n  bool IsInBoost() const { return x1de4_24_inBoost; }\n  float GetBoostChargeTime() const { return x1de8_boostChargeTime; }\n\n  // Contains red, green, and blue channel values\n  using ColorArray = std::array<u8, 3>;\n  static const std::array<ColorArray, 9> BallGlowColors;\n  static const std::array<ColorArray, 9> BallTransFlashColors;\n  static const std::array<ColorArray, 9> BallAuxGlowColors;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CMorphBallShadow.cpp",
    "content": "#include \"Runtime/World/CMorphBallShadow.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Particle/CGenDescription.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\nnamespace metaforce {\n\nvoid CMorphBallShadow::GatherAreas(const CStateManager& mgr) {\n  x18_areas.clear();\n  for (const CGameArea& area : *mgr.GetWorld()) {\n    CGameArea::EOcclusionState occState = CGameArea::EOcclusionState::Occluded;\n    if (area.IsPostConstructed())\n      occState = area.GetPostConstructed()->x10dc_occlusionState;\n    if (occState == CGameArea::EOcclusionState::Visible)\n      x18_areas.push_back(area.GetAreaId());\n  }\n}\n\nvoid CMorphBallShadow::RenderIdBuffer(const zeus::CAABox& aabb, const CStateManager& mgr, CPlayer& player) {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CMorphBallShadow::RenderIdBuffer\", zeus::skBlue);\n  xb8_shadowVolume = aabb;\n  x0_actors.clear();\n  x18_areas.clear();\n  x30_worldModelBits.clear();\n  g_Renderer->x318_26_requestRGBA6 = true;\n\n  if (true /* TODO */ || !g_Renderer->x318_27_currentRGBA6) {\n    xd0_hasIds = false;\n    return;\n  }\n\n  GatherAreas(mgr);\n\n  CViewport backupVp = CGraphics::mViewport;\n  // g_Renderer->BindBallShadowIdTarget();\n  // CGraphics::g_BooMainCommandQueue->clearTarget();\n\n  zeus::CTransform backupViewMtx = CGraphics::mViewMatrix;\n  CGraphics::CProjectionState backupProjection = CGraphics::mProj;\n  float backupDepthNear = CGraphics::mDepthNear;\n  float backupDepthFar = CGraphics::mDepthFar;\n  zeus::CTransform viewMtx(\n      zeus::skRight, zeus::skDown, zeus::skForward,\n      zeus::CVector3f((aabb.min.x() + aabb.max.x()) * 0.5f, (aabb.min.y() + aabb.max.y()) * 0.5f, aabb.max.z()));\n\n  CGraphics::SetDepthRange(DEPTH_NEAR, DEPTH_FAR);\n  float vpX = (aabb.max.x() - aabb.min.x()) * 0.5f;\n  float vpY = (aabb.max.y() - aabb.min.y()) * 0.5f;\n  float vpZ = (aabb.max.z() - aabb.min.z()) + FLT_EPSILON;\n  CGraphics::SetOrtho(-vpX, vpX, vpY, -vpY, 0.f, vpZ);\n\n  EntityList nearItems;\n  mgr.BuildNearList(nearItems, aabb, CMaterialFilter::skPassEverything, &player);\n\n  CGraphics::SetViewPointMatrix(viewMtx);\n\n  int alphaVal = 4;\n  for (TUniqueId id : nearItems) {\n    if (alphaVal > 255)\n      break;\n\n    CActor* actor = static_cast<CActor*>(mgr.ObjectById(id));\n    if (!actor || !actor->CanDrawStatic())\n      continue;\n\n    x0_actors.push_back(actor);\n\n    auto* modelData = actor->GetModelData();\n    zeus::CTransform modelXf = actor->GetTransform() * zeus::CTransform::Scale(modelData->GetScale());\n    CGraphics::SetModelMatrix(modelXf);\n\n    CModelFlags flags(0, 0, 3, zeus::CColor{1.f, 1.f, 1.f, alphaVal / 255.f});\n    // flags.m_extendedShader = EExtendedShader::SolidColor; // Do solid color draw\n    auto& model = *modelData->PickStaticModel(CModelData::EWhichModel::Normal);\n    model.VerifyCurrentShader(flags.x1_matSetIdx);\n    model.DrawUnsortedParts(flags);\n    alphaVal += 4;\n  }\n\n  CGraphics::SetModelMatrix(zeus::CTransform());\n\n  g_Renderer->FindOverlappingWorldModels(x30_worldModelBits, aabb);\n  alphaVal = g_Renderer->DrawOverlappingWorldModelIDs(alphaVal, x30_worldModelBits, aabb);\n\n  // g_Renderer->ResolveBallShadowIdTarget();\n\n  // g_Renderer->BindMainDrawTarget();\n  CGraphics::SetViewPointMatrix(backupViewMtx);\n  CGraphics::SetProjectionState(backupProjection);\n  g_Renderer->SetViewport(backupVp.mLeft, backupVp.mTop, backupVp.mWidth, backupVp.mHeight);\n  CGraphics::SetDepthRange(backupDepthNear, backupDepthFar);\n\n  xd0_hasIds = alphaVal != 4;\n}\n\nbool CMorphBallShadow::AreasValid(const CStateManager& mgr) const {\n  auto it = x18_areas.begin();\n  for (const CGameArea& area : *mgr.GetWorld()) {\n    CGameArea::EOcclusionState occState = CGameArea::EOcclusionState::Occluded;\n    if (area.IsPostConstructed())\n      occState = area.GetPostConstructed()->x10dc_occlusionState;\n    if (occState != CGameArea::EOcclusionState::Visible)\n      continue;\n    if (it == x18_areas.end())\n      return false;\n    if (*it != area.GetAreaId())\n      return false;\n    ++it;\n  }\n  return true;\n}\n\nvoid CMorphBallShadow::Render(const CStateManager& mgr, float alpha) {\n  if (!xd0_hasIds || !AreasValid(mgr))\n    return;\n\n  CModelFlags flags;\n  flags.x4_color.a() = alpha;\n  // flags.m_extendedShader = EExtendedShader::MorphBallShadow;\n  // flags.mbShadowBox = xb8_shadowVolume;\n\n  int alphaVal = 4;\n  for (auto* actor : x0_actors) {\n    auto* modelData = actor->GetModelData();\n    zeus::CTransform modelXf = actor->GetTransform() * zeus::CTransform::Scale(modelData->GetScale());\n    CGraphics::SetModelMatrix(modelXf);\n\n    flags.x4_color.r() = alphaVal / 255.f;\n    auto& model = *modelData->PickStaticModel(CModelData::EWhichModel::Normal);\n    model.VerifyCurrentShader(flags.x1_matSetIdx);\n    model.DrawUnsortedParts(flags);\n    alphaVal += 4;\n  }\n\n  CGraphics::SetModelMatrix(zeus::CTransform());\n  g_Renderer->DrawOverlappingWorldModelShadows(alphaVal, x30_worldModelBits, xb8_shadowVolume);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CMorphBallShadow.hpp",
    "content": "#pragma once\n\n#include <list>\n#include <vector>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/Graphics/CTexture.hpp\"\n\n#include <zeus/CAABox.hpp>\n\nnamespace metaforce {\nclass CActor;\nclass CGameArea;\nclass CPlayer;\nclass CStateManager;\n\nclass CMorphBallShadow {\n  std::list<CActor*> x0_actors;\n  std::list<TAreaId> x18_areas;\n  std::vector<u32> x30_worldModelBits;\n  // CTexture x40_;\n  // TToken<CTexture> xa8_ballFade;\n  // int xb0_idW;\n  // int xb4_idH;\n  zeus::CAABox xb8_shadowVolume;\n  bool xd0_hasIds = false;\n  void GatherAreas(const CStateManager& mgr);\n  bool AreasValid(const CStateManager& mgr) const;\n\npublic:\n  void RenderIdBuffer(const zeus::CAABox& aabb, const CStateManager& mgr, CPlayer& player);\n  void Render(const CStateManager& mgr, float alpha);\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CPathFindArea.cpp",
    "content": "#include \"Runtime/World/CPathFindArea.hpp\"\n\n#include \"Runtime/Logging.hpp\"\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/IVParamObj.hpp\"\n\nnamespace metaforce {\nCPFAreaOctree::CPFAreaOctree(CMemoryInStream& in) {\n  x0_isLeaf = in.ReadLong();\n  x4_aabb = in.Get<zeus::CAABox>();\n  x1c_center = in.Get<zeus::CVector3f>();\n  for (auto& ptr : x28_children)\n    ptr = reinterpret_cast<CPFAreaOctree*>(in.ReadLong());\n  x48_regions.set_size(in.ReadLong());\n  x48_regions.set_data(reinterpret_cast<CPFRegion**>(in.ReadLong()));\n}\n\nvoid CPFAreaOctree::Fixup(CPFArea& area) {\n  x0_isLeaf = x0_isLeaf != 0 ? 1 : 0;\n  if (x0_isLeaf) {\n    if (x48_regions.empty())\n      return;\n    x48_regions.set_data(&area.x160_octreeRegionLookup[reinterpret_cast<uintptr_t>(x48_regions.data())]);\n    return;\n  }\n\n  for (auto& ptr : x28_children) {\n    if ((reinterpret_cast<uintptr_t>(ptr) & 0x80000000) == 0)\n      ptr = &area.x158_octree[reinterpret_cast<uintptr_t>(ptr)];\n    else\n      ptr = nullptr;\n  }\n}\n\nu32 CPFAreaOctree::GetChildIndex(const zeus::CVector3f& point) const {\n  u32 idx = 0x0u;\n  if (point.x() > x1c_center.x())\n    idx = 0x1u;\n  if (point.y() > x1c_center.y())\n    idx |= 0x2u;\n  if (point.z() > x1c_center.z())\n    idx |= 0x4u;\n  return idx;\n}\n\nrstl::prereserved_vector<CPFRegion*>* CPFAreaOctree::GetRegionList(const zeus::CVector3f& point) {\n  if (x0_isLeaf != 0u) {\n    return &x48_regions;\n  }\n  return x28_children[GetChildIndex(point)]->GetRegionList(point);\n}\n\nvoid CPFAreaOctree::GetRegionListList(rstl::reserved_vector<rstl::prereserved_vector<CPFRegion*>*, 32>& listOut,\n                                      const zeus::CVector3f& point, float padding) {\n  if (listOut.size() >= listOut.capacity())\n    return;\n\n  if (x0_isLeaf) {\n    listOut.push_back(&x48_regions);\n    return;\n  }\n\n  for (CPFAreaOctree* ch : x28_children) {\n    if (ch->IsPointInsidePaddedAABox(point, padding))\n      ch->GetRegionListList(listOut, point, padding);\n  }\n}\n\nbool CPFAreaOctree::IsPointInsidePaddedAABox(const zeus::CVector3f& point, float padding) const {\n  return point.x() >= x4_aabb.min.x() - padding && point.x() <= x4_aabb.max.x() + padding &&\n         point.y() >= x4_aabb.min.y() - padding && point.y() <= x4_aabb.max.y() + padding &&\n         point.z() >= x4_aabb.min.z() - padding && point.z() <= x4_aabb.max.z() + padding;\n}\n\nCPFOpenList::CPFOpenList() {\n  x40_region.SetData(&x90_regionData);\n  Clear();\n}\n\nvoid CPFOpenList::Clear() {\n  x40_region.Data()->SetOpenMore(&x40_region);\n  x40_region.Data()->SetOpenLess(&x40_region);\n  x0_bitSet.Clear();\n}\n\nvoid CPFOpenList::Push(CPFRegion* reg) {\n  x0_bitSet.Add(reg->GetIndex());\n  CPFRegion* other = x40_region.Data()->GetOpenMore();\n  while (other != &x40_region && reg->Data()->GetCost() > other->Data()->GetCost()) {\n    other = other->Data()->GetOpenMore();\n  }\n  other->Data()->GetOpenLess()->Data()->SetOpenMore(reg);\n  reg->Data()->SetOpenLess(other->Data()->GetOpenLess());\n  other->Data()->SetOpenLess(reg);\n  reg->Data()->SetOpenMore(other);\n}\n\nCPFRegion* CPFOpenList::Pop() {\n  CPFRegion* reg = x40_region.Data()->GetOpenMore();\n  if (reg != &x40_region) {\n    x0_bitSet.Rmv(reg->GetIndex());\n    reg->Data()->GetOpenMore()->Data()->SetOpenLess(reg->Data()->GetOpenLess());\n    reg->Data()->GetOpenLess()->Data()->SetOpenMore(reg->Data()->GetOpenMore());\n    reg->Data()->SetOpenMore(nullptr);\n    reg->Data()->SetOpenLess(nullptr);\n    return reg;\n  }\n  return nullptr;\n}\n\nvoid CPFOpenList::Pop(CPFRegion* reg) {\n  x0_bitSet.Rmv(reg->GetIndex());\n  reg->Data()->GetOpenMore()->Data()->SetOpenLess(reg->Data()->GetOpenLess());\n  reg->Data()->GetOpenLess()->Data()->SetOpenMore(reg->Data()->GetOpenMore());\n  reg->Data()->SetOpenMore(nullptr);\n  reg->Data()->SetOpenLess(nullptr);\n}\n\nbool CPFOpenList::Test(const CPFRegion* reg) const { return x0_bitSet.Test(reg->GetIndex()); }\n\nCPFArea::CPFArea(std::unique_ptr<u8[]>&& buf, u32 len) {\n  CMemoryInStream r(buf.get(), len);\n\n  u32 version = r.ReadLong();\n  if (version != 4)\n    spdlog::fatal(\"Unexpected PATH version {}, should be 4\", version);\n\n  u32 numNodes = r.ReadLong();\n  x140_nodes.reserve(numNodes);\n  for (u32 i = 0; i < numNodes; ++i)\n    x140_nodes.emplace_back(r);\n\n  u32 numLinks = r.ReadLong();\n  x148_links.reserve(numLinks);\n  for (u32 i = 0; i < numLinks; ++i)\n    x148_links.emplace_back(r);\n\n  u32 numRegions = r.ReadLong();\n  x150_regions.reserve(numRegions);\n  for (u32 i = 0; i < numRegions; ++i)\n    x150_regions.emplace_back(r);\n\n  x178_regionDatas.resize(numRegions);\n\n  u32 maxRegionNodes = 0;\n  for (CPFRegion& region : x150_regions)\n    region.Fixup(*this, maxRegionNodes);\n  maxRegionNodes = std::max(maxRegionNodes, 4u);\n\n  x10_tmpPolyPoints.reserve(maxRegionNodes);\n\n  u32 numBitfieldWords = (numRegions * (numRegions - 1) / 2 + 31) / 32;\n  x168_connectionsGround.reserve(numBitfieldWords);\n  for (u32 i = 0; i < numBitfieldWords; ++i)\n    x168_connectionsGround.push_back(r.ReadLong());\n  x170_connectionsFlyers.reserve(numBitfieldWords);\n  for (u32 i = 0; i < numBitfieldWords; ++i)\n    x170_connectionsFlyers.push_back(r.ReadLong());\n\n  for (u32 i = 0; i < ((((numRegions * numRegions) + 31) / 32) - numBitfieldWords) * 2 * sizeof(u32); ++i) {\n    r.ReadChar();\n  }\n\n  u32 numRegionLookups = r.ReadLong();\n  x160_octreeRegionLookup.reserve(numRegionLookups);\n  for (u32 i = 0; i < numRegionLookups; ++i)\n    x160_octreeRegionLookup.push_back(reinterpret_cast<CPFRegion*>(r.ReadLong()));\n\n  for (CPFRegion*& rl : x160_octreeRegionLookup)\n    rl = &x150_regions[reinterpret_cast<uintptr_t>(rl)];\n\n  u32 numOctreeNodes = r.ReadLong();\n  x158_octree.reserve(numOctreeNodes);\n  for (u32 i = 0; i < numOctreeNodes; ++i)\n    x158_octree.emplace_back(r);\n\n  for (CPFAreaOctree& node : x158_octree)\n    node.Fixup(*this);\n}\n\nrstl::prereserved_vector<CPFRegion*>* CPFArea::GetOctreeRegionList(const zeus::CVector3f& point) {\n  if (x30_hasCachedRegionList && zeus::close_enough(point, x24_cachedRegionListPoint))\n    return x20_cachedRegionList;\n  return x158_octree.back().GetRegionList(point);\n}\n\nu32 CPFArea::FindRegions(rstl::reserved_vector<CPFRegion*, 4>& regions, const zeus::CVector3f& point, u32 flags,\n                         u32 indexMask) {\n  bool isFlyer = (flags & 0x2u) != 0;\n  bool isSwimmer = (flags & 0x4u) != 0;\n  for (CPFRegion* region : *GetOctreeRegionList(point)) {\n    if (region->GetFlags() & 0xffu & flags && (region->GetFlags() >> 16u) & 0xffu & indexMask &&\n        region->IsPointInside(point) && (isFlyer || isSwimmer || region->PointHeight(point) < 3.f)) {\n      regions.push_back(region);\n      if (regions.size() == regions.capacity())\n        break;\n    }\n  }\n  return u32(regions.size());\n}\n\nCPFRegion* CPFArea::FindClosestRegion(const zeus::CVector3f& point, u32 flags, u32 indexMask, float padding) {\n  rstl::reserved_vector<rstl::prereserved_vector<CPFRegion*>*, 32> regionListList;\n  x158_octree.back().GetRegionListList(regionListList, point, padding);\n  bool isFlyer = (flags & 0x2u) != 0;\n  CPFRegion* ret = nullptr;\n  for (rstl::prereserved_vector<CPFRegion*>* list : regionListList) {\n    for (CPFRegion* region : *list) {\n      if (region->Data()->GetCookie() != x34_regionFindCookie) {\n        if (region->GetFlags() & 0xffu & flags && (region->GetFlags() >> 16u) & 0xffu & indexMask &&\n            region->IsPointInsidePaddedAABox(point, padding) && (isFlyer || region->PointHeight(point) < 3.f)) {\n          if (region->FindBestPoint(x10_tmpPolyPoints, point, flags, padding * padding)) {\n            ret = region;\n            padding = region->Data()->GetBestPointDistanceSquared() == 0.0\n                          ? 0.f\n                          : std::sqrt(region->Data()->GetBestPointDistanceSquared());\n            x4_closestPoint = region->Data()->GetBestPoint();\n          }\n        }\n        region->Data()->SetCookie(x34_regionFindCookie);\n      }\n    }\n  }\n  ++x34_regionFindCookie;\n  return ret;\n}\n\nzeus::CVector3f CPFArea::FindClosestReachablePoint(rstl::reserved_vector<CPFRegion*, 4>& regs,\n                                                   const zeus::CVector3f& point, u32 flags, u32 indexMask) {\n  zeus::CVector3f ret;\n  float closestDistSq = FLT_MAX;\n  for (CPFRegion& reg : x150_regions) {\n    if (reg.GetFlags() & 0xffu & flags && (reg.GetFlags() >> 16u) & 0xffu & indexMask) {\n      for (CPFRegion* oreg : regs) {\n        if (PathExists(oreg, &reg, flags)) {\n          float distSq = (reg.GetCentroid() - point).magSquared();\n          if (distSq < closestDistSq) {\n            closestDistSq = distSq;\n            ret = reg.GetCentroid();\n            break;\n          }\n        }\n      }\n    }\n  }\n  return ret;\n}\n\nbool CPFArea::PathExists(const CPFRegion* r1, const CPFRegion* r2, u32 flags) const {\n  if (r1 == r2 || (flags & 0x4u) != 0)\n    return true;\n\n  u32 i1 = r1->GetIndex();\n  u32 i2 = r2->GetIndex();\n  if (i1 > i2)\n    std::swap(i1, i2);\n\n  u32 remRegions = u32(x150_regions.size()) - i1;\n  u32 remConnections = remRegions * (remRegions - 1) / 2;\n  u32 totalConnections = u32(x150_regions.size()) * (u32(x150_regions.size()) - 1) / 2;\n  u32 bit = totalConnections - remConnections + i2 - (i1 + 1);\n\n  auto d = std::div(bit, 32);\n  if ((flags & 0x2u) != 0)\n    return ((x170_connectionsFlyers[d.quot] >> u32(d.rem)) & 0x1u) != 0;\n  else\n    return ((x168_connectionsGround[d.quot] >> u32(d.rem)) & 0x1u) != 0;\n}\n\nCFactoryFnReturn FPathFindAreaFactory(const metaforce::SObjectTag& tag, std::unique_ptr<u8[]>&& in, u32 len,\n                                      const metaforce::CVParamTransfer& vparms, CObjectReference*) {\n  return TToken<CPFArea>::GetIObjObjectFor(std::make_unique<CPFArea>(std::move(in), len));\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CPathFindArea.hpp",
    "content": "#pragma once\n\n#include <bitset>\n#include <memory>\n#include <vector>\n\n#include \"Runtime/IFactory.hpp\"\n#include \"Runtime/IObj.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/World/CPathFindRegion.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CVParamTransfer;\nclass CObjectReference;\n\nclass CPFBitSet {\n  std::bitset<512> x0_bitmap;\n\npublic:\n  void Clear() { x0_bitmap.reset(); }\n  void Add(s32 i) { x0_bitmap.set(i); }\n  bool Test(s32 i) const { return x0_bitmap.test(i); }\n  void Rmv(s32 i) { x0_bitmap.reset(i); }\n};\n\nclass CPFAreaOctree {\n  u32 x0_isLeaf;\n  zeus::CAABox x4_aabb;\n  zeus::CVector3f x1c_center;\n  std::array<CPFAreaOctree*, 8> x28_children{};\n  rstl::prereserved_vector<CPFRegion*> x48_regions;\n\npublic:\n  explicit CPFAreaOctree(CMemoryInStream& in);\n  void Fixup(CPFArea& area);\n  u32 GetChildIndex(const zeus::CVector3f& point) const;\n  rstl::prereserved_vector<CPFRegion*>* GetRegionList(const zeus::CVector3f& point);\n  void GetRegionListList(rstl::reserved_vector<rstl::prereserved_vector<CPFRegion*>*, 32>& listOut,\n                         const zeus::CVector3f& point, float padding);\n  bool IsPointInsidePaddedAABox(const zeus::CVector3f& point, float padding) const;\n  void Render();\n};\n\nclass CPFOpenList {\n  friend class CPFArea;\n  CPFBitSet x0_bitSet;\n  CPFRegion x40_region;\n  CPFRegionData x90_regionData;\n\npublic:\n  CPFOpenList();\n  void Clear();\n  void Push(CPFRegion* reg);\n  CPFRegion* Pop();\n  void Pop(CPFRegion* reg);\n  bool Test(const CPFRegion* reg) const;\n};\n\nclass CPFArea {\n  friend class CPFRegion;\n  friend class CPFAreaOctree;\n  friend class CPathFindSearch;\n\n  float x0_ = FLT_MAX;\n  zeus::CVector3f x4_closestPoint;\n  std::vector<zeus::CVector3f> x10_tmpPolyPoints;\n  rstl::prereserved_vector<CPFRegion*>* x20_cachedRegionList = nullptr;\n  zeus::CVector3f x24_cachedRegionListPoint;\n  bool x30_hasCachedRegionList = false;\n  s32 x34_regionFindCookie = 0;\n  CPFBitSet x38_closedSet;\n  CPFOpenList x78_openList;\n  // u32 x138_;\n  // std::unique_ptr<u8[]> x13c_data;\n  /* Used to be prereserved_vectors backed by x13c_data\n   * This has been changed to meet storage requirements of\n   * modern systems */\n  std::vector<CPFNode> x140_nodes;                 // x140: count, x144: ptr\n  std::vector<CPFLink> x148_links;                 // x148: count, x14c: ptr\n  std::vector<CPFRegion> x150_regions;             // x150: count, x154: ptr\n  std::vector<CPFAreaOctree> x158_octree;          // x158: count, x15c: ptr\n  std::vector<CPFRegion*> x160_octreeRegionLookup; // x160: count, x164: ptr\n  std::vector<u32> x168_connectionsGround;         // x168: word_count, x16c: ptr\n  std::vector<u32> x170_connectionsFlyers;         // x170: word_count, x174: ptr\n  std::vector<CPFRegionData> x178_regionDatas;\n  zeus::CTransform x188_transform;\n\npublic:\n  CPFArea(std::unique_ptr<u8[]>&& buf, u32 len);\n\n  void SetTransform(const zeus::CTransform& xf) { x188_transform = xf; }\n  const zeus::CTransform& GetTransform() const { return x188_transform; }\n  const CPFRegion& GetRegion(s32 i) const { return x150_regions[i]; }\n  const zeus::CVector3f& GetClosestPoint() const { return x4_closestPoint; }\n  CPFOpenList& OpenList() { return x78_openList; }\n  CPFBitSet& ClosedSet() { return x38_closedSet; }\n  const CPFRegionData& GetRegionData(s32 i) const { return x178_regionDatas[i]; }\n  const CPFLink& GetLink(s32 i) const { return x148_links[i]; }\n  const CPFNode& GetNode(s32 i) const { return x140_nodes[i]; }\n  const CPFAreaOctree& GetOctree(s32 i) const { return x158_octree[i]; }\n  const CPFRegion* GetOctreeRegionPtrs(s32 i) const { return x160_octreeRegionLookup[i]; }\n  rstl::prereserved_vector<CPFRegion*>* GetOctreeRegionList(const zeus::CVector3f& point);\n  u32 FindRegions(rstl::reserved_vector<CPFRegion*, 4>& regions, const zeus::CVector3f& point, u32 flags,\n                  u32 indexMask);\n  CPFRegion* FindClosestRegion(const zeus::CVector3f& point, u32 flags, u32 indexMask, float padding);\n  zeus::CVector3f FindClosestReachablePoint(rstl::reserved_vector<CPFRegion*, 4>& regs, const zeus::CVector3f& point,\n                                            u32 flags, u32 indexMask);\n  bool PathExists(const CPFRegion* r1, const CPFRegion* r2, u32 flags) const;\n};\n\nCFactoryFnReturn FPathFindAreaFactory(const metaforce::SObjectTag& tag, std::unique_ptr<u8[]>&& in, u32 len,\n                                      const metaforce::CVParamTransfer& vparms, CObjectReference* selfRef);\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CPathFindRegion.cpp",
    "content": "#include \"Runtime/World/CPathFindRegion.hpp\"\n\n#include \"Runtime/World/CPathFindArea.hpp\"\n\nnamespace metaforce {\n\nCPFNode::CPFNode(CMemoryInStream& in) {\n  x0_position = in.Get<zeus::CVector3f>();\n  xc_normal = in.Get<zeus::CVector3f>();\n}\n\nCPFLink::CPFLink(CMemoryInStream& in) {\n  x0_node = in.ReadLong();\n  x4_region = in.ReadLong();\n  x8_2dWidth = in.ReadFloat();\n  xc_oo2dWidth = in.ReadFloat();\n}\n\nCPFRegion::CPFRegion(CMemoryInStream& in) {\n  x0_numNodes = in.ReadLong();\n  x4_startNode = reinterpret_cast<CPFNode*>(in.ReadLong());\n  x8_numLinks = in.ReadLong();\n  xc_startLink = reinterpret_cast<CPFLink*>(in.ReadLong());\n  x10_flags = in.ReadLong();\n  x14_height = in.ReadFloat();\n  x18_normal = in.Get<zeus::CVector3f>();\n  x24_regionIdx = in.ReadLong();\n  x28_centroid = in.Get<zeus::CVector3f>();\n  x34_aabb = in.Get<zeus::CAABox>();\n  x4c_regionData = reinterpret_cast<CPFRegionData*>(in.ReadLong());\n}\n\nconst CPFLink* CPFRegion::GetPathLink() const { return &xc_startLink[x4c_regionData->GetPathLink()]; }\n\nvoid CPFRegion::Fixup(CPFArea& area, u32& maxRegionNodes) {\n  if (x0_numNodes != 0) {\n    x4_startNode = &area.x140_nodes[reinterpret_cast<uintptr_t>(x4_startNode)];\n  } else {\n    x4_startNode = nullptr;\n  }\n\n  if (x8_numLinks != 0) {\n    xc_startLink = &area.x148_links[reinterpret_cast<uintptr_t>(xc_startLink)];\n  } else {\n    xc_startLink = nullptr;\n  }\n\n  x4c_regionData = &area.x178_regionDatas[x24_regionIdx];\n  if (x0_numNodes > maxRegionNodes) {\n    maxRegionNodes = x0_numNodes;\n  }\n}\n\nbool CPFRegion::IsPointInside(const zeus::CVector3f& point) const {\n  if (!x34_aabb.pointInside(point)) {\n    return false;\n  }\n\n  u32 i;\n  for (i = 0; i < x0_numNodes; ++i) {\n    const CPFNode& node = x4_startNode[i];\n    if ((point - node.GetPos()).dot(node.GetNormal()) < 0.f) {\n      break;\n    }\n  }\n\n  if (i != x0_numNodes) {\n    return false;\n  }\n\n  const zeus::CVector3f nodeToPoint = point - x4_startNode->GetPos();\n  return nodeToPoint.dot(x18_normal) >= 0.f && (nodeToPoint - x14_height * zeus::skUp).dot(x18_normal) <= 0.f;\n}\n\nfloat CPFRegion::PointHeight(const zeus::CVector3f& point) const {\n  return (point - x4_startNode->GetPos()).dot(x18_normal);\n}\n\nbool CPFRegion::FindClosestPointOnPolygon(const std::vector<zeus::CVector3f>& polyPoints, const zeus::CVector3f& normal,\n                                          const zeus::CVector3f& point, bool excludePolyPoints) const {\n  bool found = false;\n  size_t i;\n  for (i = 0; i < polyPoints.size(); ++i) {\n    const zeus::CVector3f& p0 = polyPoints[i];\n    const zeus::CVector3f& p1 = polyPoints[(i + 1) % polyPoints.size()];\n    if ((p1 - p0).cross(normal).dot(point - p0) < 0.f) {\n      break;\n    }\n  }\n\n  if (i == polyPoints.size()) {\n    const float distToPoly = (polyPoints.front() - point).dot(normal);\n    const float distToPolySq = distToPoly * distToPoly;\n    if (distToPolySq < x4c_regionData->GetBestPointDistanceSquared()) {\n      found = true;\n      x4c_regionData->SetBestPointDistanceSquared(distToPolySq);\n      x4c_regionData->SetBestPoint(normal * distToPoly + point);\n    }\n  } else {\n    bool projected = false;\n    for (i = 0; i < polyPoints.size(); ++i) {\n      const zeus::CVector3f& p0 = polyPoints[i];\n      const zeus::CVector3f& p1 = polyPoints[(i + 1) % polyPoints.size()];\n      zeus::CVector3f p0ToP1 = p1 - p0;\n      zeus::CVector3f p1ToPoint = point - p1;\n      zeus::CVector3f sum = p1ToPoint + p0ToP1;\n\n      if (p0ToP1.cross(normal).dot(p1ToPoint) < 0.f && p0ToP1.dot(p1ToPoint) <= 0.f && sum.dot(p0ToP1) >= 0.f) {\n        projected = true;\n        p0ToP1.normalize();\n        sum -= p0ToP1.dot(sum) * p0ToP1;\n        const float distSq = sum.magSquared();\n        if (distSq < x4c_regionData->GetBestPointDistanceSquared()) {\n          found = true;\n          x4c_regionData->SetBestPointDistanceSquared(distSq);\n          x4c_regionData->SetBestPoint(point - sum);\n        }\n        break;\n      }\n    }\n\n    if (!projected && !excludePolyPoints) {\n      for (i = 0; i < polyPoints.size(); ++i) {\n        const zeus::CVector3f& p0 = polyPoints[i];\n        const float distSq = (point - p0).magSquared();\n        if (distSq < x4c_regionData->GetBestPointDistanceSquared()) {\n          found = true;\n          x4c_regionData->SetBestPointDistanceSquared(distSq);\n          x4c_regionData->SetBestPoint(p0);\n        }\n      }\n    }\n  }\n  return found;\n}\n\nbool CPFRegion::FindBestPoint(std::vector<zeus::CVector3f>& polyPoints, const zeus::CVector3f& point, u32 flags,\n                              float paddingSq) const {\n  bool found = false;\n  const bool isFlyer = (flags & 0x2) != 0;\n\n  x4c_regionData->SetBestPointDistanceSquared(paddingSq);\n\n  if (!isFlyer) {\n    for (u32 i = 0; i < x0_numNodes; ++i) {\n      const CPFNode& node = x4_startNode[i];\n      const CPFNode& nextNode = x4_startNode[(i + 1) % x0_numNodes];\n      polyPoints.clear();\n      polyPoints.push_back(node.GetPos());\n      polyPoints.push_back(node.GetPos());\n      polyPoints.back().z() += x14_height;\n      polyPoints.push_back(nextNode.GetPos());\n      polyPoints.back().z() += x14_height;\n      polyPoints.push_back(nextNode.GetPos());\n      found |= FindClosestPointOnPolygon(polyPoints, node.GetNormal(), point, true);\n    }\n  }\n\n  polyPoints.clear();\n  for (u32 i = 0; i < x0_numNodes; ++i) {\n    const CPFNode& node = x4_startNode[i];\n    polyPoints.push_back(node.GetPos());\n  }\n  found |= FindClosestPointOnPolygon(polyPoints, x18_normal, point, false);\n\n  if (!isFlyer) {\n    polyPoints.clear();\n    for (int i = int(x0_numNodes) - 1; i >= 0; --i) {\n      const CPFNode& node = x4_startNode[i];\n      polyPoints.push_back(node.GetPos());\n      polyPoints.back().z() += x14_height;\n    }\n    found |= FindClosestPointOnPolygon(polyPoints, -x18_normal, point, false);\n  }\n\n  return found;\n}\n\nvoid CPFRegion::SetLinkTo(u32 idx) {\n  if (x8_numLinks <= 0) {\n    return;\n  }\n\n  for (u32 i = 0; i < x8_numLinks; ++i) {\n    if (xc_startLink[i].GetRegion() == idx) {\n      Data()->SetPathLink(i);\n      return;\n    }\n  }\n}\n\nvoid CPFRegion::DropToGround(zeus::CVector3f& point) const {\n  point.z() -= (point - x4_startNode->GetPos()).dot(x18_normal) / x18_normal.z();\n}\n\nzeus::CVector3f CPFRegion::GetLinkMidPoint(const CPFLink& link) const {\n  const CPFNode& node = x4_startNode[link.GetNode()];\n  const CPFNode& nextNode = x4_startNode[(link.GetNode() + 1) % x0_numNodes];\n  return (node.GetPos() + nextNode.GetPos()) * 0.5f;\n}\n\nzeus::CVector3f CPFRegion::FitThroughLink2d(const zeus::CVector3f& p1, const CPFLink& link, const zeus::CVector3f& p2,\n                                            float chRadius) const {\n  const CPFNode& node = x4_startNode[link.GetNode()];\n  const CPFNode& nextNode = x4_startNode[(link.GetNode() + 1) % x0_numNodes];\n  const zeus::CVector3f nodeDelta = nextNode.GetPos() - node.GetPos();\n  float t = 0.5f;\n\n  if (chRadius < 0.5f * link.Get2dWidth()) {\n    zeus::CVector2f delta2d = nodeDelta.toVec2f();\n    delta2d *= link.GetOO2dWidth();\n    const zeus::CVector3f nodeToP1 = p1 - node.GetPos();\n    const float f27 = nodeToP1.dot(node.GetNormal());\n    const float f31 = delta2d.dot(nodeToP1.toVec2f());\n    const zeus::CVector3f nodeToP2 = p2 - node.GetPos();\n    const float f26 = -nodeToP2.dot(node.GetNormal());\n    const float f1b = delta2d.dot(nodeToP2.toVec2f());\n    const float f3 = f27 + f26;\n    if (f3 > FLT_EPSILON) {\n      t = zeus::clamp(chRadius, 1.f / f3 * (f26 * f31 + f27 * f1b), link.Get2dWidth() - chRadius) * link.GetOO2dWidth();\n    }\n  }\n  return nodeDelta * t + node.GetPos();\n}\n\nzeus::CVector3f CPFRegion::FitThroughLink3d(const zeus::CVector3f& p1, const CPFLink& link, float regionHeight,\n                                            const zeus::CVector3f& p2, float chRadius, float chHalfHeight) const {\n  const CPFNode& node = x4_startNode[link.GetNode()];\n  const CPFNode& nextNode = x4_startNode[(link.GetNode() + 1) % x0_numNodes];\n  const zeus::CVector3f nodeDelta = nextNode.GetPos() - node.GetPos();\n  const float f25 = (p1 - node.GetPos()).dot(node.GetNormal());\n  const float f24 = (node.GetPos() - p2).dot(node.GetNormal());\n  const float f23 = f25 + f24;\n\n#if 0\n  if (chRadius < 0.5f * link.Get2dWidth()) {\n    zeus::CVector2f delta2d(nodeDelta.x, nodeDelta.y);\n    delta2d *= link.GetOO2dWidth();\n    const zeus::CVector3f nodeToP1 = p1 - node.GetPos();\n    const float f29 = delta2d.dot(zeus::CVector2f(nodeToP1.y, nodeToP1.y));\n    const zeus::CVector3f nodeToP2 = p2 - node.GetPos();\n    const float f1b = delta2d.dot(zeus::CVector2f(nodeToP2.y, nodeToP2.y));\n    if (f23 > FLT_EPSILON) {\n      zeus::clamp(chRadius, 1.f / f23 * f24 * f29 + f25 * f1b, link.Get2dWidth() - chRadius) * link.GetOO2dWidth();\n    }\n  }\n#endif\n\n  const zeus::CVector3f midPoint = nodeDelta * 0.5f + node.GetPos();\n  float z;\n  if (chHalfHeight < 0.5f * regionHeight) {\n    const float minZ = chHalfHeight + midPoint.z();\n    z = 0.5f * (p1.z() + p2.z());\n    if (f23 > FLT_EPSILON) {\n      z = (f24 * p1.z() + f25 * p2.z()) / f23;\n    }\n    z = zeus::clamp(minZ, z, regionHeight + midPoint.z() - chHalfHeight);\n  } else {\n    z = (p1.z() + p2.z()) * 0.5f;\n  }\n  return {midPoint.x(), midPoint.y(), z};\n}\n\nbool CPFRegion::IsPointInsidePaddedAABox(const zeus::CVector3f& point, float padding) const {\n  return point.x() >= x34_aabb.min.x() - padding && point.x() <= x34_aabb.max.x() + padding &&\n         point.y() >= x34_aabb.min.y() - padding && point.y() <= x34_aabb.max.y() + padding &&\n         point.z() >= x34_aabb.min.z() - padding && point.z() <= x34_aabb.max.z() + padding;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CPathFindRegion.hpp",
    "content": "#pragma once\n\n#include <vector>\n\n#include \"RetroTypes.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CPFArea;\nclass CPFLink;\nclass CPFRegionData;\n\nclass CPFNode {\n  zeus::CVector3f x0_position;\n  zeus::CVector3f xc_normal;\n\npublic:\n  explicit CPFNode(CMemoryInStream& in);\n  const zeus::CVector3f& GetPos() const { return x0_position; }\n  const zeus::CVector3f& GetNormal() const { return xc_normal; }\n};\n\nclass CPFLink {\n  u32 x0_node;\n  u32 x4_region;\n  float x8_2dWidth;\n  float xc_oo2dWidth;\n\npublic:\n  explicit CPFLink(CMemoryInStream& in);\n  u32 GetNode() const { return x0_node; }\n  u32 GetRegion() const { return x4_region; }\n  float Get2dWidth() const { return x8_2dWidth; }\n  float GetOO2dWidth() const { return xc_oo2dWidth; }\n};\n\nclass CPFRegion {\n  u32 x0_numNodes = 0;\n  CPFNode* x4_startNode = nullptr;\n  u32 x8_numLinks = 0;\n  CPFLink* xc_startLink = nullptr;\n  u32 x10_flags = 0;\n  float x14_height = 0.f;\n  zeus::CVector3f x18_normal;\n  u32 x24_regionIdx = 0;\n  zeus::CVector3f x28_centroid;\n  zeus::CAABox x34_aabb;\n  CPFRegionData* x4c_regionData;\n\npublic:\n  CPFRegion() = default;\n  explicit CPFRegion(CMemoryInStream& in);\n\n  void SetData(CPFRegionData* data) { x4c_regionData = data; }\n  CPFRegionData* Data() { return x4c_regionData; }\n  const CPFRegionData* Data() const { return x4c_regionData; }\n\n  u32 GetIndex() const { return x24_regionIdx; }\n  float GetHeight() const { return x14_height; }\n  const CPFLink* GetPathLink() const;\n  u32 GetNumLinks() const { return x8_numLinks; }\n  u32 GetFlags() const { return x10_flags; }\n  const CPFLink* GetLink(u32 i) const { return xc_startLink + i; }\n\n  void SetCentroid(const zeus::CVector3f& c) { x28_centroid = c; }\n  const zeus::CVector3f& GetCentroid() const { return x28_centroid; }\n\n  void Fixup(CPFArea& area, u32& maxRegionNodes);\n  bool IsPointInside(const zeus::CVector3f& point) const;\n\n  const zeus::CVector3f& GetNormal() const { return x18_normal; }\n  u32 GetNumNodes() const { return x0_numNodes; }\n  const CPFNode* GetNode(u32 i) const { return x4_startNode + i; }\n\n  float PointHeight(const zeus::CVector3f& point) const;\n  bool FindClosestPointOnPolygon(const std::vector<zeus::CVector3f>&, const zeus::CVector3f&, const zeus::CVector3f&,\n                                 bool) const;\n  bool FindBestPoint(std::vector<zeus::CVector3f>& polyPoints, const zeus::CVector3f& point, u32 flags,\n                     float paddingSq) const;\n\n  void SetLinkTo(u32 idx);\n  void DropToGround(zeus::CVector3f& point) const;\n  zeus::CVector3f GetLinkMidPoint(const CPFLink& link) const;\n  zeus::CVector3f FitThroughLink2d(const zeus::CVector3f& p1, const CPFLink& link, const zeus::CVector3f& p2,\n                                   float chRadius) const;\n  zeus::CVector3f FitThroughLink3d(const zeus::CVector3f& p1, const CPFLink& link, float regionHeight,\n                                   const zeus::CVector3f& p2, float chRadius, float chHalfHeight) const;\n  bool IsPointInsidePaddedAABox(const zeus::CVector3f& point, float padding) const;\n};\n\nclass CPFRegionData {\n  float x0_bestPointDistSq = 0.f;\n  zeus::CVector3f x4_bestPoint;\n  s32 x10_cookie = -1;\n  float x14_cost = 0.f;\n  float x18_g = 0.f;\n  float x1c_h = 0.f;\n  CPFRegion* x20_parent = nullptr;\n  CPFRegion* x24_openLess = nullptr;\n  CPFRegion* x28_openMore = nullptr;\n  s32 x2c_pathLink = 0;\n\npublic:\n  CPFRegionData() = default;\n  void SetOpenLess(CPFRegion* r) { x24_openLess = r; }\n  void SetOpenMore(CPFRegion* r) { x28_openMore = r; }\n  CPFRegion* GetOpenLess() const { return x24_openLess; }\n  CPFRegion* GetOpenMore() const { return x28_openMore; }\n  float GetCost() const { return x14_cost; }\n  float GetG() const { return x18_g; }\n  s32 GetPathLink() const { return x2c_pathLink; }\n  void SetPathLink(s32 l) { x2c_pathLink = l; }\n  CPFRegion* GetParent() const { return x20_parent; }\n  void SetBestPoint(const zeus::CVector3f& bestPoint) { x4_bestPoint = bestPoint; }\n  const zeus::CVector3f& GetBestPoint() const { return x4_bestPoint; }\n  void SetBestPointDistanceSquared(float distSq) { x0_bestPointDistSq = distSq; }\n  float GetBestPointDistanceSquared() const { return x0_bestPointDistSq; }\n  void SetCookie(s32 c) { x10_cookie = c; }\n  s32 GetCookie() const { return x10_cookie; }\n\n  void Setup(CPFRegion* parent, float g, float h) {\n    x20_parent = parent;\n    x18_g = g;\n    x1c_h = h;\n    x14_cost = x18_g + x1c_h;\n  }\n  void Setup(CPFRegion* parent, float g) {\n    x20_parent = parent;\n    x18_g = g;\n    x14_cost = x18_g + x1c_h;\n  }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CPathFindSearch.cpp",
    "content": "#include \"Runtime/World/CPathFindSearch.hpp\"\n\n#include \"Runtime/Graphics/CGraphics.hpp\"\n\nnamespace metaforce {\n\nCPathFindSearch::CPathFindSearch(CPFArea* area, u32 flags, u32 index, float chRadius, float chHeight)\n: x0_area(area), xd0_chHeight(chHeight), xd4_chRadius(chRadius), xdc_flags(flags), xe0_indexMask(1u << index) {}\n\nCPathFindSearch::EResult CPathFindSearch::FindClosestReachablePoint(const zeus::CVector3f& p1,\n                                                                    zeus::CVector3f& p2) const {\n  if (!x0_area)\n    return EResult::InvalidArea;\n\n  /* Work in local PFArea coordinates */\n  zeus::CVector3f localP1 = x0_area->x188_transform.transposeRotate(p1 - x0_area->x188_transform.origin);\n  zeus::CVector3f localP2 = x0_area->x188_transform.transposeRotate(p2 - x0_area->x188_transform.origin);\n\n  /* Raise a bit above ground for step-up resolution */\n  if (!(xdc_flags & 0x2) && !(xdc_flags & 0x4)) {\n    localP2.z() += 0.3f;\n    localP1.z() += 0.3f;\n  }\n\n  rstl::reserved_vector<CPFRegion*, 4> regions;\n  if (x0_area->FindRegions(regions, localP1, xdc_flags, xe0_indexMask) == 0) {\n    /* Point outside PATH; find nearest region point */\n    CPFRegion* region = x0_area->FindClosestRegion(localP1, xdc_flags, xe0_indexMask, xd8_padding);\n    if (!region)\n      return EResult::NoSourcePoint;\n\n    regions.push_back(region);\n  }\n\n  /* Override dest point to be reachable */\n  zeus::CVector3f closestPoint =\n      x0_area->FindClosestReachablePoint(regions, localP2, xdc_flags, xe0_indexMask) + zeus::CVector3f(0.f, 0.f, 3.f);\n  p2 = x0_area->x188_transform * closestPoint;\n\n  return EResult::Success;\n}\n\nCPathFindSearch::EResult CPathFindSearch::PathExists(const zeus::CVector3f& p1, const zeus::CVector3f& p2) const {\n  if (!x0_area)\n    return EResult::InvalidArea;\n\n  /* Work in local PFArea coordinates */\n  zeus::CVector3f localP1 = x0_area->x188_transform.transposeRotate(p1 - x0_area->x188_transform.origin);\n  zeus::CVector3f localP2 = x0_area->x188_transform.transposeRotate(p2 - x0_area->x188_transform.origin);\n\n  /* Raise a bit above ground for step-up resolution */\n  if (!(xdc_flags & 0x2) && !(xdc_flags & 0x4)) {\n    localP2.z() += 0.3f;\n    localP1.z() += 0.3f;\n  }\n\n  rstl::reserved_vector<CPFRegion*, 4> regions1;\n  if (x0_area->FindRegions(regions1, localP1, xdc_flags, xe0_indexMask) == 0)\n    return EResult::NoSourcePoint;\n\n  rstl::reserved_vector<CPFRegion*, 4> regions2;\n  if (x0_area->FindRegions(regions2, localP2, xdc_flags, xe0_indexMask) == 0)\n    return EResult::NoDestPoint;\n\n  for (CPFRegion* reg1 : regions1)\n    for (CPFRegion* reg2 : regions2)\n      if (reg1 == reg2 || x0_area->PathExists(reg1, reg2, xdc_flags))\n        return EResult::Success;\n\n  return EResult::NoPath;\n}\n\nCPathFindSearch::EResult CPathFindSearch::OnPath(const zeus::CVector3f& p1) const {\n  if (!x0_area)\n    return EResult::InvalidArea;\n\n  /* Work in local PFArea coordinates */\n  zeus::CVector3f localP1 = x0_area->x188_transform.transposeRotate(p1 - x0_area->x188_transform.origin);\n\n  /* Raise a bit above ground for step-up resolution */\n  if (!(xdc_flags & 0x2) && !(xdc_flags & 0x4))\n    localP1.z() += 0.3f;\n\n  rstl::reserved_vector<CPFRegion*, 4> regions1;\n  if (x0_area->FindRegions(regions1, localP1, xdc_flags, xe0_indexMask) == 0)\n    return EResult::NoSourcePoint;\n\n  return EResult::Success;\n}\n\nCPathFindSearch::EResult CPathFindSearch::Search(const zeus::CVector3f& p1, const zeus::CVector3f& p2) {\n  u32 firstPoint = 0;\n  u32 flyToOutsidePoint = 0;\n  x4_waypoints.clear();\n  xc8_curWaypoint = 0;\n\n  if (!x0_area || x0_area->x150_regions.size() > 512) {\n    xcc_result = EResult::InvalidArea;\n    return xcc_result;\n  }\n\n  if (zeus::close_enough(p1, p2)) {\n    /* That was easy */\n    x4_waypoints.push_back(p1);\n    xcc_result = EResult::Success;\n    return xcc_result;\n  }\n\n  /* Work in local PFArea coordinates */\n  zeus::CVector3f localP1 = x0_area->x188_transform.transposeRotate(p1 - x0_area->x188_transform.origin);\n  zeus::CVector3f localP2 = x0_area->x188_transform.transposeRotate(p2 - x0_area->x188_transform.origin);\n\n  /* Raise a bit above ground for step-up resolution */\n  if (!(xdc_flags & 0x2) && !(xdc_flags & 0x4)) {\n    localP2.z() += 0.3f;\n    localP1.z() += 0.3f;\n  }\n\n  rstl::reserved_vector<CPFRegion*, 4> regions1;\n  rstl::reserved_vector<zeus::CVector3f, 16> points;\n  if (x0_area->FindRegions(regions1, localP1, xdc_flags, xe0_indexMask) == 0) {\n    /* Point outside PATH; find nearest region point */\n    CPFRegion* region = x0_area->FindClosestRegion(localP1, xdc_flags, xe0_indexMask, xd8_padding);\n    if (!region) {\n      xcc_result = EResult::NoSourcePoint;\n      return xcc_result;\n    }\n\n    if (xdc_flags & 0x2 || xdc_flags & 0x4) {\n      points.push_back(localP1);\n      firstPoint = 1;\n    }\n    regions1.push_back(region);\n    localP1 = x0_area->GetClosestPoint();\n  }\n\n  zeus::CVector3f finalP2 = localP2;\n  rstl::reserved_vector<CPFRegion*, 4> regions2;\n  if (x0_area->FindRegions(regions2, localP2, xdc_flags, xe0_indexMask) == 0) {\n    /* Point outside PATH; find nearest region point */\n    CPFRegion* region = x0_area->FindClosestRegion(localP2, xdc_flags, xe0_indexMask, xd8_padding);\n    if (!region) {\n      xcc_result = EResult::NoDestPoint;\n      return xcc_result;\n    }\n\n    if (xdc_flags & 0x2 || xdc_flags & 0x4) {\n      flyToOutsidePoint = 1;\n    }\n    regions2.push_back(region);\n    localP2 = x0_area->GetClosestPoint();\n  }\n\n  rstl::reserved_vector<CPFRegion*, 4> regions1Uniq;\n  rstl::reserved_vector<CPFRegion*, 4> regions2Uniq;\n  bool noPath = true;\n  for (CPFRegion* reg1 : regions1) {\n    for (CPFRegion* reg2 : regions2) {\n      if (reg1 == reg2) {\n        /* Route within one region */\n        if (!(xdc_flags & 0x2) && !(xdc_flags & 0x4)) {\n          reg2->DropToGround(localP1);\n          reg2->DropToGround(localP2);\n        }\n        x4_waypoints.push_back(x0_area->x188_transform * localP1);\n        if (!zeus::close_enough(localP1, localP2))\n          x4_waypoints.push_back(x0_area->x188_transform * localP2);\n        if (flyToOutsidePoint && !zeus::close_enough(localP2, finalP2))\n          x4_waypoints.push_back(x0_area->x188_transform * finalP2);\n        xcc_result = EResult::Success;\n        return xcc_result;\n      }\n\n      if (x0_area->PathExists(reg1, reg2, xdc_flags)) {\n        /* Build unique source/dest region lists */\n        if (std::find(regions1Uniq.rbegin(), regions1Uniq.rend(), reg1) == regions1Uniq.rend())\n          regions1Uniq.push_back(reg1);\n        if (std::find(regions2Uniq.rbegin(), regions2Uniq.rend(), reg2) == regions2Uniq.rend())\n          regions2Uniq.push_back(reg2);\n        noPath = false;\n      }\n    }\n  }\n\n  /* Perform A* algorithm if path is known to exist */\n  if (noPath || !Search(regions1Uniq, localP1, regions2Uniq, localP2)) {\n    xcc_result = EResult::NoPath;\n    return xcc_result;\n  }\n\n  /* Set forward links with best path */\n  CPFRegion* reg = regions2Uniq[0];\n  u32 lastPoint = 0;\n  do {\n    reg->Data()->GetParent()->SetLinkTo(reg->GetIndex());\n    reg = reg->Data()->GetParent();\n    ++lastPoint;\n  } while (reg != regions1Uniq[0]);\n\n  /* Setup point range */\n  bool includeP2 = true;\n  lastPoint -= 1;\n  firstPoint += 1;\n  lastPoint += firstPoint;\n  if (lastPoint > 15)\n    lastPoint = 15;\n  if (lastPoint + flyToOutsidePoint + 1 > 15)\n    includeP2 = false;\n\n  /* Ensure start and finish points are on ground */\n  if (!(xdc_flags & 0x2) && !(xdc_flags & 0x4)) {\n    regions1Uniq[0]->DropToGround(localP1);\n    regions2Uniq[0]->DropToGround(localP2);\n  }\n\n  /* Gather link points using midpoints */\n  float chHalfHeight = 0.5f * xd0_chHeight;\n  points.push_back(localP1);\n  reg = regions1Uniq[0];\n  for (u32 i = firstPoint; i <= lastPoint; ++i) {\n    const CPFLink* link = reg->GetPathLink();\n    CPFRegion* linkReg = &x0_area->x150_regions[link->GetRegion()];\n    zeus::CVector3f midPoint = reg->GetLinkMidPoint(*link);\n    if (xdc_flags & 0x2 || xdc_flags & 0x4) {\n      float minHeight = std::min(reg->GetHeight(), linkReg->GetHeight());\n      midPoint.z() = zeus::clamp(chHalfHeight + midPoint.z(), p2.z(), minHeight + midPoint.z() - chHalfHeight);\n    }\n    points.push_back(midPoint);\n    reg = linkReg;\n  }\n\n  /* Gather finish points */\n  if (includeP2) {\n    points.push_back(localP2);\n    if (flyToOutsidePoint)\n      points.push_back(finalP2);\n  }\n\n  /* Optimize link points using character radius and height */\n  for (int i = 0; i < 2; ++i) {\n    reg = regions1Uniq[0];\n    for (u32 j = firstPoint; j <= (includeP2 ? lastPoint : lastPoint - 1); ++j) {\n      const CPFLink* link = reg->GetPathLink();\n      CPFRegion* linkReg = &x0_area->x150_regions[link->GetRegion()];\n      if (xdc_flags & 0x2 || xdc_flags & 0x4) {\n        float minHeight = std::min(reg->GetHeight(), linkReg->GetHeight());\n        points[j] = reg->FitThroughLink3d(points[j - 1], *link, minHeight, points[j + 1], xd4_chRadius, chHalfHeight);\n      } else {\n        points[j] = reg->FitThroughLink2d(points[j - 1], *link, points[j + 1], xd4_chRadius);\n      }\n      reg = linkReg;\n    }\n  }\n\n  /* Write out points */\n  for (u32 i = 0; i < points.size(); ++i)\n    if (i == points.size() - 1 || !zeus::close_enough(points[i], points[i + 1]))\n      x4_waypoints.push_back(x0_area->x188_transform * points[i]);\n\n  /* Done! */\n  xcc_result = EResult::Success;\n  return xcc_result;\n}\n\n/* A* search algorithm\n * Reference: https://en.wikipedia.org/wiki/A*_search_algorithm\n */\nbool CPathFindSearch::Search(rstl::reserved_vector<CPFRegion*, 4>& regs1, const zeus::CVector3f& p1,\n                             rstl::reserved_vector<CPFRegion*, 4>& regs2, const zeus::CVector3f& p2) {\n  /* Reset search sets */\n  x0_area->ClosedSet().Clear();\n  x0_area->OpenList().Clear();\n\n  /* Backup dest centroids */\n  rstl::reserved_vector<zeus::CVector3f, 4> centroidBackup2;\n  for (CPFRegion* reg2 : regs2) {\n    centroidBackup2.push_back(reg2->GetCentroid());\n    reg2->SetCentroid(p2);\n  }\n\n  /* Initial heuristic */\n  float h = (p2 - p1).magnitude();\n\n  /* Backup source centroids and initialize heuristics */\n  rstl::reserved_vector<zeus::CVector3f, 4> centroidBackup1;\n  for (CPFRegion* reg1 : regs1) {\n    centroidBackup1.push_back(reg1->GetCentroid());\n    reg1->SetCentroid(p1);\n    reg1->Data()->Setup(nullptr, 0.f, h);\n    x0_area->OpenList().Push(reg1);\n  }\n\n  /* Resolve path */\n  CPFRegion* reg;\n  while ((reg = x0_area->OpenList().Pop())) {\n    /* Stop if we're at the destination */\n    if (std::find(regs2.begin(), regs2.end(), reg) != regs2.end())\n      break;\n\n    /* Exclude region from further resolves */\n    x0_area->ClosedSet().Add(reg->GetIndex());\n    for (u32 i = 0; i < reg->GetNumLinks(); ++i) {\n      /* Potential link to follow */\n      CPFRegion* linkReg = &x0_area->x150_regions[reg->GetLink(i)->GetRegion()];\n      if (linkReg != reg->Data()->GetParent() && linkReg->GetFlags() & 0xff & xdc_flags &&\n          (linkReg->GetFlags() >> 16) & 0xff & xe0_indexMask) {\n        /* Next G */\n        float g = (linkReg->GetCentroid() - reg->GetCentroid()).magnitude() + reg->Data()->GetG();\n        if ((!x0_area->ClosedSet().Test(linkReg->GetIndex()) && !x0_area->OpenList().Test(linkReg)) ||\n            linkReg->Data()->GetG() > g) {\n          if (x0_area->OpenList().Test(linkReg)) {\n            /* In rare cases, revisiting a region will yield a lower G (actual cost) */\n            x0_area->OpenList().Pop(linkReg);\n            linkReg->Data()->Setup(reg, g);\n          } else {\n            /* Compute next heuristic */\n            x0_area->ClosedSet().Rmv(linkReg->GetIndex());\n            const float nextHeuristic = (p2 - linkReg->GetCentroid()).magnitude();\n            linkReg->Data()->Setup(reg, g, nextHeuristic);\n          }\n\n          /* Make next potential candidate */\n          x0_area->OpenList().Push(linkReg);\n        }\n      }\n    }\n  }\n\n  /* Restore source centroids */\n  auto p1It = centroidBackup1.begin();\n  for (CPFRegion* r : regs1)\n    r->SetCentroid(*p1It++);\n\n  /* Restore dest centroids */\n  auto p2It = centroidBackup2.begin();\n  for (CPFRegion* r : regs2)\n    r->SetCentroid(*p2It++);\n\n  /* Best destination region */\n  if (reg) {\n    regs2.clear();\n    regs2.push_back(reg);\n    /* Retrace parents to find best source region */\n    while (CPFRegion* p = reg->Data()->GetParent())\n      reg = p;\n    regs1.clear();\n    regs1.push_back(reg);\n  }\n\n  return reg != nullptr;\n}\n\nvoid CPathFindVisualizer::Draw(const CPathFindSearch& path) {\n  // m_spline.Reset();\n  // for (const auto& wp : path.GetWaypoints())\n  //   m_spline.AddVertex(wp, zeus::skBlue, 2.f);\n  // m_spline.Render();\n}\n\nvoid CPathFindSearch::DebugDraw() {\n  if (!m_viz) {\n    m_viz.emplace();\n  }\n  m_viz->Draw(*this);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CPathFindSearch.hpp",
    "content": "#pragma once\n\n#include <optional>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/World/CPathFindArea.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CPathFindSearch;\n\nclass CPathFindVisualizer {\npublic:\n  void Draw(const CPathFindSearch& path);\n};\n\nclass CPathFindSearch {\npublic:\n  enum class EResult { Success, InvalidArea, NoSourcePoint, NoDestPoint, NoPath };\n\nprivate:\n  CPFArea* x0_area;\n  rstl::reserved_vector<zeus::CVector3f, 16> x4_waypoints;\n  u32 xc8_curWaypoint = 0;\n  EResult xcc_result{};\n  float xd0_chHeight;\n  float xd4_chRadius;\n  float xd8_padding = 10.f;\n  u32 xdc_flags; // 0x2: flyer, 0x4: path-always-exists (swimmers)\n  u32 xe0_indexMask;\n  std::optional<CPathFindVisualizer> m_viz;\n  bool Search(rstl::reserved_vector<CPFRegion*, 4>& regs1, const zeus::CVector3f& p1,\n              rstl::reserved_vector<CPFRegion*, 4>& regs2, const zeus::CVector3f& p2);\n  void GetSplinePoint(zeus::CVector3f& pOut, const zeus::CVector3f& p1, u32 wpIdx) const;\n  void GetSplinePointWithLookahead(zeus::CVector3f& pOut, const zeus::CVector3f& p1, u32 wpIdx, float lookahead) const;\n\npublic:\n  CPathFindSearch(CPFArea* area, u32 flags, u32 index, float chRadius, float chHeight);\n  EResult Search(const zeus::CVector3f& p1, const zeus::CVector3f& p2);\n  EResult FindClosestReachablePoint(const zeus::CVector3f& p1, zeus::CVector3f& p2) const;\n  EResult PathExists(const zeus::CVector3f& p1, const zeus::CVector3f& p2) const;\n  EResult OnPath(const zeus::CVector3f& p1) const;\n  EResult GetResult() const { return xcc_result; }\n  u32 GetCurrentWaypoint() const { return xc8_curWaypoint; }\n  void SetCurrentWaypoint(u32 wp) { xc8_curWaypoint = wp; }\n  const rstl::reserved_vector<zeus::CVector3f, 16>& GetWaypoints() const { return x4_waypoints; }\n  bool IsOver() const { return GetCurrentWaypoint() >= x4_waypoints.size() - 1; }\n  bool IsShagged() const { return GetResult() != EResult::Success; }\n  bool SegmentOver(const zeus::CVector3f& p1) const;\n  void GetSplinePoint(zeus::CVector3f& pOut, const zeus::CVector3f& p1) const;\n  void GetSplinePointWithLookahead(zeus::CVector3f& pOut, const zeus::CVector3f& p1, float lookahead) const;\n  void SetArea(CPFArea* area) { x0_area = area; }\n  float GetCharacterHeight() const { return xd0_chHeight; }\n  void SetCharacterHeight(float h) { xd0_chHeight = h; }\n  float GetCharacterRadius() const { return xd4_chRadius; }\n  void SetCharacterRadius(float r) { xd4_chRadius = r; }\n  void SetPadding(float padding) { xd8_padding = padding; }\n  float RemainingPathDistance(const zeus::CVector3f& pos) const;\n  void DebugDraw();\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CPathFindSpline.cpp",
    "content": "#include \"Runtime/World/CPathFindSearch.hpp\"\n\nnamespace metaforce {\n\nbool CPathFindSearch::SegmentOver(const zeus::CVector3f& p1) const {\n  if (x4_waypoints.size() > 1 && xc8_curWaypoint < x4_waypoints.size() - 1) {\n    const zeus::CVector3f& wp0 = x4_waypoints[xc8_curWaypoint];\n    const zeus::CVector3f& wp1 = x4_waypoints[xc8_curWaypoint + 1];\n    const zeus::CVector3f& wp2 = x4_waypoints[std::min(u32(x4_waypoints.size()) - 1, xc8_curWaypoint + 2)];\n    return (p1 - wp1).dot(wp2 - wp0) >= 0.f;\n  }\n  return true;\n}\n\nvoid CPathFindSearch::GetSplinePoint(zeus::CVector3f& pOut, const zeus::CVector3f& p1, u32 wpIdx) const {\n  if (x4_waypoints.size() > 1 && wpIdx < x4_waypoints.size() - 1) {\n    zeus::CVector3f a = (wpIdx == 0) ? x4_waypoints[0] * 2.f - x4_waypoints[1] : x4_waypoints[wpIdx - 1];\n    const zeus::CVector3f& b = x4_waypoints[wpIdx];\n    const zeus::CVector3f& c = x4_waypoints[wpIdx + 1];\n    zeus::CVector3f d = (wpIdx + 2 >= x4_waypoints.size())\n                            ? x4_waypoints[x4_waypoints.size() - 1] * 2.f - x4_waypoints[x4_waypoints.size() - 2]\n                            : x4_waypoints[wpIdx + 2];\n    zeus::CVector3f delta = c - b;\n    if (delta.isMagnitudeSafe())\n      pOut = zeus::getCatmullRomSplinePoint(a, b, c, d, (p1 - b).dot(delta) / delta.magSquared());\n    else\n      pOut = b;\n  }\n}\n\nvoid CPathFindSearch::GetSplinePoint(zeus::CVector3f& pOut, const zeus::CVector3f& p1) const {\n  GetSplinePoint(pOut, p1, xc8_curWaypoint);\n}\n\nvoid CPathFindSearch::GetSplinePointWithLookahead(zeus::CVector3f& pOut, const zeus::CVector3f& p1, u32 wpIdx,\n                                                  float lookahead) const {\n  if (x4_waypoints.size() > 1 && wpIdx < x4_waypoints.size() - 1) {\n    const zeus::CVector3f& wp0 = x4_waypoints[wpIdx];\n    const zeus::CVector3f& wp1 = x4_waypoints[wpIdx + 1];\n    zeus::CVector3f delta = wp1 - wp0;\n    if (delta.isMagnitudeSafe()) {\n      float deltaMag = delta.magnitude();\n      delta = delta * (1.f / deltaMag);\n      float bToPtProj = (p1 - wp0).dot(delta);\n      if (bToPtProj + lookahead <= deltaMag)\n        GetSplinePoint(pOut, delta * lookahead + p1, wpIdx);\n      else if (wpIdx < x4_waypoints.size() - 2)\n        GetSplinePointWithLookahead(pOut, wp1, wpIdx + 1, lookahead - (deltaMag - bToPtProj));\n      else\n        pOut = delta * (lookahead - (deltaMag - bToPtProj)) + wp1;\n    } else {\n      pOut = wp1;\n    }\n  }\n}\n\nvoid CPathFindSearch::GetSplinePointWithLookahead(zeus::CVector3f& pOut, const zeus::CVector3f& p1,\n                                                  float lookahead) const {\n  GetSplinePointWithLookahead(pOut, p1, xc8_curWaypoint, lookahead);\n}\n\nfloat CPathFindSearch::RemainingPathDistance(const zeus::CVector3f& pos) const {\n  float f31 = 0.f;\n  if (xc8_curWaypoint < x4_waypoints.size() - 1) {\n    f31 += (x4_waypoints[xc8_curWaypoint + 1] - pos).magnitude();\n    for (size_t i = xc8_curWaypoint + 1; i < x4_waypoints.size() - 1; ++i) {\n      f31 += (x4_waypoints[i + 1] - x4_waypoints[i]).magnitude();\n    }\n  }\n  return f31;\n}\n\n} // namespace metaforce"
  },
  {
    "path": "Runtime/World/CPatterned.cpp",
    "content": "#include \"Runtime/World/CPatterned.hpp\"\n\n#include \"Runtime/Camera/CFirstPersonCamera.hpp\"\n#include \"Runtime/Character/CAnimData.hpp\"\n#include \"Runtime/Character/CPASAnimParmData.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CSkinnedModel.hpp\"\n#include \"Runtime/MP1/World/CMetroid.hpp\"\n#include \"Runtime/MP1/World/CSpacePirate.hpp\"\n#include \"Runtime/Weapon/CEnergyProjectile.hpp\"\n#include \"Runtime/Weapon/CGameProjectile.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/World/CExplosion.hpp\"\n#include \"Runtime/World/CPathFindSearch.hpp\"\n#include \"Runtime/World/CPatternedInfo.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CScriptActorKeyframe.hpp\"\n#include \"Runtime/World/CScriptCoverPoint.hpp\"\n#include \"Runtime/World/CScriptWaypoint.hpp\"\n#include \"Runtime/World/CStateMachine.hpp\"\n\n#include \"Runtime/ConsoleVariables/CVarManager.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n#include <cmath>\n\nnamespace metaforce {\nnamespace {\nCVar* cv_disableAi = nullptr;\n} // namespace\n\nconstexpr CMaterialList skPatternedGroundMaterialList(EMaterialTypes::Character, EMaterialTypes::Solid,\n                                                      EMaterialTypes::Orbit, EMaterialTypes::GroundCollider,\n                                                      EMaterialTypes::Target);\nconstexpr CMaterialList skPatternedFlyerMaterialList(EMaterialTypes::Character, EMaterialTypes::Solid,\n                                                     EMaterialTypes::Orbit, EMaterialTypes::Target);\n\nCPatterned::CPatterned(ECharacter character, TUniqueId uid, std::string_view name, CPatterned::EFlavorType flavor,\n                       const CEntityInfo& info, const zeus::CTransform& xf, CModelData&& mData,\n                       const CPatternedInfo& pInfo, CPatterned::EMovementType moveType,\n                       CPatterned::EColliderType colliderType, EBodyType bodyType, const CActorParameters& actorParms,\n                       EKnockBackVariant kbVariant)\n: CAi(uid, pInfo.xf8_active, name, info, xf, std::move(mData),\n      zeus::CAABox(pInfo.xcc_bodyOrigin - zeus::CVector3f{pInfo.xc4_halfExtent, pInfo.xc4_halfExtent, 0.f},\n                   pInfo.xcc_bodyOrigin +\n                       zeus::CVector3f{pInfo.xc4_halfExtent, pInfo.xc4_halfExtent, pInfo.xc8_height}),\n      pInfo.x0_mass, pInfo.x54_healthInfo, pInfo.x5c_damageVulnerability,\n      moveType == EMovementType::Flyer ? skPatternedFlyerMaterialList : skPatternedGroundMaterialList,\n      pInfo.xfc_stateMachineId, actorParms, pInfo.xd8_stepUpHeight, 0.8f)\n, x2fc_minAttackRange(pInfo.x18_minAttackRange)\n, x300_maxAttackRange(pInfo.x1c_maxAttackRange)\n, x304_averageAttackTime(pInfo.x20_averageAttackTime)\n, x308_attackTimeVariation(pInfo.x24_attackTimeVariation)\n, x328_25_verticalMovement(moveType == EMovementType::Flyer)\n, x328_27_onGround(moveType != EMovementType::Flyer)\n, x34c_character(character)\n, x388_anim(pInfo.GetAnimationParameters().GetInitialAnimation())\n, x3b4_speed(pInfo.x4_speed)\n, x3b8_turnSpeed(pInfo.x8_turnSpeed)\n, x3bc_detectionRange(pInfo.xc_detectionRange)\n, x3c0_detectionHeightRange(pInfo.x10_detectionHeightRange)\n, x3c4_detectionAngle(std::cos(zeus::degToRad(pInfo.x14_dectectionAngle)))\n, x3c8_leashRadius(pInfo.x28_leashRadius)\n, x3cc_playerLeashRadius(pInfo.x2c_playerLeashRadius)\n, x3d0_playerLeashTime(pInfo.x30_playerLeashTime)\n, x3d8_xDamageThreshold(pInfo.xdc_xDamage)\n, x3dc_frozenXDamageThreshold(pInfo.xe0_frozenXDamage)\n, x3e0_xDamageDelay(pInfo.xe4_xDamageDelay)\n, x3fc_flavor(flavor)\n, x400_31_isFlyer(moveType == CPatterned::EMovementType::Flyer)\n, x402_30_updateThermalFrozenState(x402_31_thawed = actorParms.HasThermalHeat())\n, x460_knockBackController(kbVariant) {\n  x404_contactDamage = pInfo.x34_contactDamageInfo;\n  x424_damageWaitTime = pInfo.x50_damageWaitTime;\n  x454_deathSfx = pInfo.xe8_deathSfx;\n  x458_iceShatterSfx = pInfo.x134_iceShatterSfx;\n  x4f4_intoFreezeDur = pInfo.x100_intoFreezeDur;\n  x4f8_outofFreezeDur = pInfo.x104_outofFreezeDur;\n  x4fc_freezeDur = pInfo.x108_freezeDur;\n  x508_colliderType = colliderType;\n  x50c_baseDamageMag = actorParms.GetThermalMag();\n  x514_deathExplosionOffset = pInfo.x110_particle1Scale;\n  x540_iceDeathExplosionOffset = pInfo.x124_particle2Scale;\n\n  if (pInfo.x11c_particle1.IsValid()) {\n    x520_deathExplosionParticle = {g_SimplePool->GetObj({FOURCC('PART'), pInfo.x11c_particle1})};\n  }\n\n  if (pInfo.x120_electric.IsValid()) {\n    x530_deathExplosionElectric = {g_SimplePool->GetObj({FOURCC('ELSC'), pInfo.x120_electric})};\n  }\n\n  if (pInfo.x130_particle2.IsValid()) {\n    x54c_iceDeathExplosionParticle = {g_SimplePool->GetObj({FOURCC('PART'), pInfo.x130_particle2})};\n  }\n\n  if (x404_contactDamage.GetRadius() > 0.f) {\n    x404_contactDamage.SetRadius(0.f);\n  }\n\n  xe6_29_renderParticleDBInside = false;\n  if (x64_modelData) {\n    x402_27_noXrayModel = !x64_modelData->HasModel(CModelData::EWhichModel::XRay);\n    BuildBodyController(bodyType);\n  }\n}\n\nvoid CPatterned::Accept(metaforce::IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CPatterned::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  CAi::AcceptScriptMsg(msg, uid, mgr);\n\n  switch (msg) {\n  case EScriptObjectMessage::Registered: {\n    if (x508_colliderType != EColliderType::One) {\n      CMaterialList include = GetMaterialFilter().GetIncludeList();\n      CMaterialList exclude = GetMaterialFilter().GetExcludeList();\n      include.Remove(EMaterialTypes::Character);\n      exclude.Add(EMaterialTypes::Character);\n      SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(include, exclude));\n    }\n\n    if (HasModelData() && GetModelData()->HasAnimData() && GetModelData()->GetAnimationData()->GetIceModel()) {\n      const auto& baseAABB = GetBaseBoundingBox();\n      float diagExtent = (baseAABB.max - baseAABB.min).magnitude() * 0.5f;\n      x510_vertexMorph = std::make_shared<CVertexMorphEffect>(zeus::skRight, zeus::CVector3f{}, 0.f, diagExtent,\n                                                              *mgr.GetActiveRandom());\n    }\n\n    xf8_25_angularEnabled = true;\n    break;\n  }\n  case EScriptObjectMessage::OnFloor: {\n    if (!x328_25_verticalMovement) {\n      x150_momentum = {};\n      AddMaterial(EMaterialTypes::GroundCollider, mgr);\n    }\n    x328_27_onGround = true;\n    break;\n  }\n  case EScriptObjectMessage::Falling: {\n    if (!x328_25_verticalMovement) {\n      if (x450_bodyController->GetPercentageFrozen() == 0.f) {\n        x150_momentum = {0.f, 0.f, -GetWeight()};\n        RemoveMaterial(EMaterialTypes::GroundCollider, mgr);\n      }\n    }\n    x328_27_onGround = false;\n    break;\n  }\n  case EScriptObjectMessage::Activate:\n    x3a0_latestLeashPosition = GetTranslation();\n    break;\n  case EScriptObjectMessage::Deleted:\n    if (x330_stateMachineState.GetActorState() != nullptr) {\n      x330_stateMachineState.GetActorState()->CallFunc(mgr, *this, EStateMsg::Deactivate, 0.f);\n    }\n    break;\n  case EScriptObjectMessage::Damage: {\n    if (TCastToConstPtr<CGameProjectile> proj = mgr.GetObjectById(uid)) {\n      const CDamageInfo& info = proj->GetDamageInfo();\n      if (info.GetWeaponMode().GetType() == EWeaponType::Wave) {\n        if (x460_knockBackController.x81_26_enableShock && info.GetWeaponMode().IsComboed() &&\n            HealthInfo(mgr) != nullptr) {\n          x401_31_nextPendingShock = true;\n          KnockBack(GetTransform().frontVector(), mgr, info, EKnockBackType::Direct, false, info.GetKnockBackPower());\n          x460_knockBackController.DeferKnockBack(EWeaponType::Wave);\n        }\n      } else if (info.GetWeaponMode().GetType() == EWeaponType::Plasma) {\n        if (x460_knockBackController.x81_27_enableBurn && info.GetWeaponMode().IsCharged() &&\n            HealthInfo(mgr) != nullptr) {\n          KnockBack(GetTransform().frontVector(), mgr, info, EKnockBackType::Direct, false, info.GetKnockBackPower());\n          x460_knockBackController.DeferKnockBack(EWeaponType::Plasma);\n        }\n      }\n      if (mgr.GetPlayer().GetUniqueId() == proj->GetOwnerId()) {\n        x400_24_hitByPlayerProjectile = true;\n      }\n    }\n    break;\n  }\n  case EScriptObjectMessage::InvulnDamage: {\n    if (TCastToConstPtr<CGameProjectile> proj = mgr.GetObjectById(uid)) {\n      if (proj->GetOwnerId() == mgr.GetPlayer().GetUniqueId()) {\n        x400_24_hitByPlayerProjectile = true;\n      }\n    }\n    break;\n  }\n  default:\n    break;\n  }\n}\n\nvoid CPatterned::MakeThermalColdAndHot() {\n  x403_24_keepThermalVisorState = true;\n  xe6_27_thermalVisorFlags = 3;\n}\n\nvoid CPatterned::UpdateThermalFrozenState(bool thawed) {\n  x402_31_thawed = thawed;\n  if (x403_24_keepThermalVisorState) {\n    return;\n  }\n  xe6_27_thermalVisorFlags = u8(thawed ? 2 : 1);\n}\n\nvoid CPatterned::Think(float dt, CStateManager& mgr) {\n  if (!GetActive() || (cv_disableAi != nullptr && cv_disableAi->toBoolean())) {\n    Stop();\n    ClearForcesAndTorques();\n    return;\n  }\n\n  if (x402_30_updateThermalFrozenState) {\n    UpdateThermalFrozenState(x450_bodyController->GetPercentageFrozen() == 0.f);\n  }\n\n  if (x64_modelData->GetAnimationData()->GetIceModel()) {\n    x510_vertexMorph->Update(dt);\n  }\n\n  if (x402_26_dieIf80PercFrozen) {\n    float froz = x450_bodyController->GetPercentageFrozen();\n    if (froz > 0.8f) {\n      x400_29_pendingMassiveFrozenDeath = true;\n    }\n  }\n\n  if (!x400_25_alive) {\n    if ((x400_28_pendingMassiveDeath || x400_29_pendingMassiveFrozenDeath) && x3e0_xDamageDelay <= 0.f) {\n      if (x400_29_pendingMassiveFrozenDeath) {\n        SendScriptMsgs(EScriptObjectState::AboutToMassivelyDie, mgr, EScriptObjectMessage::None);\n        MassiveFrozenDeath(mgr);\n      } else {\n        SendScriptMsgs(EScriptObjectState::AboutToMassivelyDie, mgr, EScriptObjectMessage::None);\n        MassiveDeath(mgr);\n      }\n    } else {\n      x3e0_xDamageDelay -= dt;\n      if (x403_26_stateControlledMassiveDeath && x330_stateMachineState.GetName() != nullptr) {\n        bool isDead = x330_stateMachineState.GetName() == \"Dead\"sv;\n        if (isDead && x330_stateMachineState.x8_time > 15.f) {\n          MassiveDeath(mgr);\n        }\n      }\n    }\n  }\n\n  UpdateAlphaDelta(dt, mgr);\n\n  x3e4_lastHP = HealthInfo(mgr)->GetHP();\n  if (x330_stateMachineState.x4_state == nullptr) {\n    x330_stateMachineState.SetState(mgr, *this, GetStateMachine(), \"Start\"sv);\n  }\n\n  zeus::CVector3f diffVec = x4e4_latestPredictedTranslation - GetTranslation();\n  if (!x328_25_verticalMovement) {\n    diffVec.z() = 0.f;\n  }\n\n  if (diffVec.magSquared() > (0.1f * dt)) {\n    x4f0_predictedLeashTime += dt;\n  } else {\n    x4f0_predictedLeashTime = 0.f;\n  }\n\n  if (x460_knockBackController.x81_26_enableShock) {\n    /* Shock on logical falling edge */\n    if (!x401_31_nextPendingShock && x402_24_pendingShock) {\n      Shock(mgr, 0.5f + mgr.GetActiveRandom()->Range(0.f, 0.5f), 0.2f);\n    }\n    x402_24_pendingShock = x401_31_nextPendingShock;\n    x401_31_nextPendingShock = false;\n\n    if (x450_bodyController->IsElectrocuting()) {\n      mgr.GetActorModelParticles()->StartElectric(*this);\n      if (x3f0_pendingShockDamage > 0.f && x400_25_alive) {\n        const CDamageInfo dInfo(CDamageInfo{CWeaponMode{EWeaponType::Wave}, x3f0_pendingShockDamage, 0.f, 0.f}, dt);\n        mgr.ApplyDamage(kInvalidUniqueId, GetUniqueId(), kInvalidUniqueId, dInfo,\n                        CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), {});\n      }\n    } else {\n      if (x3f0_pendingShockDamage != 0.f) {\n        x450_bodyController->StopElectrocution();\n        mgr.GetActorModelParticles()->StopElectric(*this);\n      }\n    }\n  }\n\n  if (x450_bodyController->IsOnFire()) {\n    if (x400_25_alive) {\n      mgr.GetActorModelParticles()->LightDudeOnFire(*this);\n      const CDamageInfo dInfo(CDamageInfo{CWeaponMode{EWeaponType::Plasma}, x3ec_pendingFireDamage, 0.f, 0.f}, dt);\n      mgr.ApplyDamage(kInvalidUniqueId, GetUniqueId(), kInvalidUniqueId, dInfo,\n                      CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), {});\n    }\n  } else {\n    if (x3ec_pendingFireDamage > 0.f) {\n      x3ec_pendingFireDamage = 0.f;\n    }\n    if (x450_bodyController->IsFrozen()) {\n      mgr.GetActorModelParticles()->StopThermalHotParticles(*this);\n    }\n  }\n\n  if (x401_27_phazingOut || x401_28_burning) {\n    x3e8_alphaDelta = -0.33333334f;\n  }\n\n  if (x401_30_pendingDeath) {\n    x401_30_pendingDeath = false;\n    Death(mgr, GetTransform().frontVector(), EScriptObjectState::DeathRattle);\n  }\n\n  float thinkDt = (x400_25_alive ? dt : dt * CalcDyingThinkRate());\n\n  x450_bodyController->Update(thinkDt, mgr);\n  x450_bodyController->MultiplyPlaybackRate(x3b4_speed);\n  SAdvancementDeltas deltas = UpdateAnimation(thinkDt, mgr, !x450_bodyController->IsFrozen());\n  x434_posDelta = deltas.x0_posDelta;\n  x440_rotDelta = deltas.xc_rotDelta;\n\n  if (x403_25_enableStateMachine && x450_bodyController->GetPercentageFrozen() < 1.f) {\n    x330_stateMachineState.Update(mgr, *this, thinkDt);\n  }\n\n  ThinkAboutMove(thinkDt);\n\n  x460_knockBackController.Update(thinkDt, mgr, *this);\n  x4e4_latestPredictedTranslation = GetTranslation() + PredictMotion(thinkDt).x0_translation;\n  x328_26_solidCollision = false;\n  if (x420_curDamageRemTime > 0.f) {\n    x420_curDamageRemTime -= dt;\n  }\n\n  if (x401_28_burning && x3f4_burnThinkRateTimer > dt) {\n    x3f4_burnThinkRateTimer -= dt;\n  }\n\n  xd0_damageMag = x50c_baseDamageMag;\n  UpdateDamageColor(dt);\n\n  if (!x450_bodyController->IsFrozen()) {\n    if (x3a0_latestLeashPosition == zeus::CVector3f()) {\n      x3a0_latestLeashPosition = GetTranslation();\n    }\n\n    if (x3cc_playerLeashRadius != 0.f) {\n      if ((GetTranslation() - mgr.GetPlayer().GetTranslation()).magSquared() >\n          x3cc_playerLeashRadius * x3cc_playerLeashRadius) {\n        x3d4_curPlayerLeashTime += dt;\n      } else {\n        x3d4_curPlayerLeashTime = 0.f;\n      }\n    }\n  } else {\n    RemoveEmitter();\n  }\n\n  if (x2f8_waypointPauseRemTime > 0.f) {\n    x2f8_waypointPauseRemTime -= dt;\n  }\n}\n\nvoid CPatterned::CollidedWith(TUniqueId other, const CCollisionInfoList& list, CStateManager& mgr) {\n  if (x420_curDamageRemTime <= 0.f) {\n    if (TCastToPtr<CPlayer> player = mgr.ObjectById(other)) {\n      bool jumpOnHead =\n          player->GetTimeSinceJump() < 5.f && list.GetCount() != 0 && list.Front().GetNormalLeft().z() > 0.707f;\n      if (x400_25_alive || jumpOnHead) {\n        CDamageInfo cDamage = GetContactDamage();\n        if (!x400_25_alive || x450_bodyController->IsFrozen()) {\n          cDamage.SetDamage(0.f);\n        }\n        if (jumpOnHead) {\n          mgr.ApplyDamage(GetUniqueId(), player->GetUniqueId(), GetUniqueId(), cDamage,\n                          CMaterialFilter::skPassEverything, -player->GetVelocity());\n          player->ResetTimeSinceJump();\n        } else if (x400_25_alive && !x450_bodyController->IsFrozen()) {\n          mgr.ApplyDamage(GetUniqueId(), player->GetUniqueId(), GetUniqueId(), cDamage,\n                          CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), zeus::skZero3f);\n        }\n        x420_curDamageRemTime = x424_damageWaitTime;\n      }\n    }\n  }\n  static constexpr CMaterialList testList(EMaterialTypes::Solid, EMaterialTypes::Ceiling, EMaterialTypes::Wall,\n                                          EMaterialTypes::Floor, EMaterialTypes::Character);\n  for (const CCollisionInfo& info : list) {\n    if (info.GetMaterialLeft().Intersection(testList) != 0u) {\n      if (!info.GetMaterialLeft().HasMaterial(EMaterialTypes::Floor)) {\n        if (!x310_moveVec.isZero() && info.GetNormalLeft().dot(x310_moveVec) >= 0.f) {\n          continue;\n        }\n      } else if (!x400_31_isFlyer) {\n        continue;\n      }\n      x328_26_solidCollision = true;\n      return;\n    }\n  }\n  CPhysicsActor::CollidedWith(other, list, mgr);\n}\n\nvoid CPatterned::Touch(CActor& act, CStateManager& mgr) {\n  if (!x400_25_alive) {\n    return;\n  }\n\n  if (TCastToPtr<CGameProjectile> proj = act) {\n    if (mgr.GetPlayer().GetUniqueId() == proj->GetOwnerId()) {\n      x400_24_hitByPlayerProjectile = true;\n    }\n  }\n}\n\nstd::optional<zeus::CAABox> CPatterned::GetTouchBounds() const { return GetBoundingBox(); }\n\nbool CPatterned::CanRenderUnsorted(const metaforce::CStateManager& mgr) const {\n  return x64_modelData->GetAnimationData()->GetParticleDB().AreAnySystemsDrawnWithModel()\n             ? false\n             : CActor::CanRenderUnsorted(mgr);\n}\n\nzeus::CVector3f CPatterned::GetAimPosition(const metaforce::CStateManager& mgr, float dt) const {\n  zeus::CVector3f offset;\n  if (dt > 0.f) {\n    offset = PredictMotion(dt).x0_translation;\n  }\n\n  const CSegId segId = GetModelData()->GetAnimationData()->GetLocatorSegId(\"lockon_target_LCTR\"sv);\n  if (segId.IsValid()) {\n    const zeus::CTransform xf = GetModelData()->GetAnimationData()->GetLocatorTransform(segId, nullptr);\n    const zeus::CVector3f scaledOrigin = GetModelData()->GetScale() * xf.origin;\n\n    if (const auto tb = GetTouchBounds()) {\n      return offset + tb->clampToBox(x34_transform * scaledOrigin);\n    }\n\n    const zeus::CAABox aabox = GetBaseBoundingBox();\n    const zeus::CAABox primBox(aabox.min + GetPrimitiveOffset(), aabox.max + GetPrimitiveOffset());\n\n    return offset + (x34_transform * primBox.clampToBox(scaledOrigin));\n  }\n\n  return offset + GetBoundingBox().center();\n}\n\nzeus::CTransform CPatterned::GetLctrTransform(std::string_view name) const {\n  return x34_transform * GetScaledLocatorTransform(name);\n}\n\nzeus::CTransform CPatterned::GetLctrTransform(CSegId id) const {\n  zeus::CTransform xf = x64_modelData->GetAnimationData()->GetLocatorTransform(id, nullptr);\n  return x34_transform * zeus::CTransform(xf.buildMatrix3f(), x64_modelData->GetScale() * xf.origin);\n}\n\nvoid CPatterned::DeathDelete(CStateManager& mgr) {\n  SendScriptMsgs(EScriptObjectState::Dead, mgr, EScriptObjectMessage::None);\n  if (x450_bodyController->IsElectrocuting()) {\n    x3f0_pendingShockDamage = 0.f;\n    x450_bodyController->StopElectrocution();\n    mgr.GetActorModelParticles()->StopElectric(*this);\n  }\n  mgr.FreeScriptObject(GetUniqueId());\n}\n\nvoid CPatterned::Death(CStateManager& mgr, const zeus::CVector3f& dir, EScriptObjectState state) {\n  if (x400_25_alive) {\n    if (!x450_bodyController->IsOnFire()) {\n      x402_25_lostMassiveFrozenHP = (x3e4_lastHP - HealthInfo(mgr)->GetHP()) >= x3dc_frozenXDamageThreshold;\n      if (x402_25_lostMassiveFrozenHP && x54c_iceDeathExplosionParticle &&\n          x450_bodyController->GetPercentageFrozen() > 0.8f) {\n        x400_29_pendingMassiveFrozenDeath = true;\n      } else if ((x3e4_lastHP - HealthInfo(mgr)->GetHP()) >= x3d8_xDamageThreshold) {\n        x400_28_pendingMassiveDeath = true;\n      }\n    }\n    if (x400_28_pendingMassiveDeath || x400_29_pendingMassiveFrozenDeath) {\n      if (x328_30_lookAtDeathDir && x3e0_xDamageDelay <= 0.f && dir != zeus::skZero3f) {\n        SetTransform(zeus::lookAt(GetTranslation(), GetTranslation() - dir) *\n                     zeus::CTransform::RotateX(zeus::degToRad(45.f)));\n      }\n    } else {\n      x330_stateMachineState.SetState(mgr, *this, GetStateMachine(), \"Dead\"sv);\n      RemoveMaterial(EMaterialTypes::GroundCollider, mgr);\n      x328_25_verticalMovement = false;\n    }\n    x400_25_alive = false;\n    if (x450_bodyController->HasBodyState(pas::EAnimationState::Hurled) &&\n        x450_bodyController->GetBodyType() == EBodyType::Flyer) {\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBCHurledCmd(-dir, zeus::skZero3f));\n    } else if (x450_bodyController->HasBodyState(pas::EAnimationState::Fall)) {\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBCKnockDownCmd(-dir, pas::ESeverity::One));\n    }\n    if (state != EScriptObjectState::Any) {\n      SendScriptMsgs(state, mgr, EScriptObjectMessage::None);\n    }\n  }\n}\n\nvoid CPatterned::KnockBack(const zeus::CVector3f& backVec, CStateManager& mgr, const CDamageInfo& info,\n                           EKnockBackType type, bool inDeferred, float magnitude) {\n  CHealthInfo* hInfo = HealthInfo(mgr);\n  if (!x401_27_phazingOut && !x401_28_burning && hInfo != nullptr) {\n    x460_knockBackController.KnockBack(backVec, mgr, *this, info, type, magnitude);\n    if (x450_bodyController->IsFrozen() && x460_knockBackController.GetActiveParms().xc_intoFreezeDur >= 0.f) {\n      x450_bodyController->FrozenBreakout();\n    }\n    switch (x460_knockBackController.GetActiveParms().x4_animFollowup) {\n    case EKnockBackAnimationFollowUp::Freeze:\n      Freeze(mgr, zeus::skZero3f, zeus::CUnitVector3f(x34_transform.transposeRotate(backVec)),\n             x460_knockBackController.GetActiveParms().x8_followupDuration);\n      break;\n    case EKnockBackAnimationFollowUp::PhazeOut:\n      PhazeOut(mgr);\n      break;\n    case EKnockBackAnimationFollowUp::Shock:\n      Shock(mgr, x460_knockBackController.GetActiveParms().x8_followupDuration, -1.f);\n      break;\n    case EKnockBackAnimationFollowUp::Burn:\n      Burn(x460_knockBackController.GetActiveParms().x8_followupDuration, 0.25f);\n      break;\n    case EKnockBackAnimationFollowUp::LaggedBurnDeath:\n      x401_29_laggedBurnDeath = true;\n      [[fallthrough]];\n    case EKnockBackAnimationFollowUp::BurnDeath:\n      Burn(x460_knockBackController.GetActiveParms().x8_followupDuration, -1.f);\n      Death(mgr, zeus::skZero3f, EScriptObjectState::DeathRattle);\n      x400_28_pendingMassiveDeath = x400_29_pendingMassiveFrozenDeath = false;\n      x400_27_fadeToDeath = x401_28_burning = true;\n      x3f4_burnThinkRateTimer = 1.5f;\n      x402_29_drawParticles = false;\n      x450_bodyController->DouseFlames();\n      mgr.GetActorModelParticles()->StopThermalHotParticles(*this);\n      mgr.GetActorModelParticles()->StartBurnDeath(*this);\n      if (!x401_29_laggedBurnDeath) {\n        mgr.GetActorModelParticles()->EnsureFirePopLoaded(*this);\n        mgr.GetActorModelParticles()->EnsureIceBreakLoaded(*this);\n      }\n      break;\n    case EKnockBackAnimationFollowUp::Death:\n      Death(mgr, zeus::skZero3f, EScriptObjectState::DeathRattle);\n      break;\n    case EKnockBackAnimationFollowUp::ExplodeDeath:\n      Death(mgr, zeus::skZero3f, EScriptObjectState::DeathRattle);\n      if (GetDeathExplosionParticle() || x530_deathExplosionElectric) {\n        MassiveDeath(mgr);\n      } else if (x450_bodyController->IsFrozen()) {\n        x450_bodyController->FrozenBreakout();\n      }\n      break;\n    case EKnockBackAnimationFollowUp::IceDeath:\n      Death(mgr, zeus::skZero3f, EScriptObjectState::DeathRattle);\n      if (x54c_iceDeathExplosionParticle) {\n        MassiveFrozenDeath(mgr);\n      } else if (x450_bodyController->IsFrozen()) {\n        x450_bodyController->FrozenBreakout();\n      }\n      break;\n    default:\n      break;\n    }\n  }\n}\n\nvoid CPatterned::TakeDamage(const zeus::CVector3f&, float arg) { x428_damageCooldownTimer = 0.33f; }\n\nbool CPatterned::FixedRandom(CStateManager&, float arg) {\n  return x330_stateMachineState.GetRandom() < x330_stateMachineState.x14_;\n}\n\nbool CPatterned::Random(CStateManager&, float arg) { return x330_stateMachineState.GetRandom() < arg; }\n\nbool CPatterned::CodeTrigger(CStateManager&, float arg) { return x330_stateMachineState.x18_24_codeTrigger; }\n\nbool CPatterned::FixedDelay(CStateManager&, float arg) {\n  return x330_stateMachineState.GetTime() > x330_stateMachineState.GetDelay();\n}\n\nbool CPatterned::RandomDelay(CStateManager&, float arg) {\n  return x330_stateMachineState.GetTime() > arg * x330_stateMachineState.GetRandom();\n}\n\nbool CPatterned::Delay(CStateManager&, float arg) { return x330_stateMachineState.GetTime() > arg; }\n\nbool CPatterned::PatrolPathOver(CStateManager&, float arg) { return x2dc_destObj == kInvalidUniqueId; }\n\nbool CPatterned::Stuck(CStateManager&, float arg) { return x4f0_predictedLeashTime > 0.2f; }\n\nbool CPatterned::AnimOver(CStateManager&, float arg) { return x32c_animState == EAnimState::Over; }\n\nbool CPatterned::InPosition(CStateManager&, float arg) { return x328_24_inPosition; }\n\nbool CPatterned::HasPatrolPath(CStateManager& mgr, float arg) {\n  return GetWaypointForState(mgr, EScriptObjectState::Patrol, EScriptObjectMessage::Follow) != kInvalidUniqueId;\n}\n\nbool CPatterned::Attacked(CStateManager&, float arg) { return x400_24_hitByPlayerProjectile; }\n\nbool CPatterned::PatternShagged(CStateManager&, float arg) { return x400_30_patternShagged; }\n\nbool CPatterned::PatternOver(CStateManager&, float arg) { return x38c_patterns.size() <= x39c_curPattern; }\n\nbool CPatterned::HasRetreatPattern(CStateManager& mgr, float arg) {\n  return GetWaypointForState(mgr, EScriptObjectState::Retreat, EScriptObjectMessage::Follow) != kInvalidUniqueId;\n}\n\nbool CPatterned::HasAttackPattern(CStateManager& mgr, float arg) {\n  return GetWaypointForState(mgr, EScriptObjectState::Attack, EScriptObjectMessage::Follow) != kInvalidUniqueId;\n}\n\nbool CPatterned::NoPathNodes(CStateManager&, float arg) {\n  if (CPathFindSearch* search = GetSearchPath()) {\n    return search->OnPath(GetTranslation()) != CPathFindSearch::EResult::Success;\n  }\n  return true;\n}\n\nconstexpr float skActorApproachDistance = 3.f;\n\nbool CPatterned::PathShagged(CStateManager&, float arg) {\n  if (CPathFindSearch* search = GetSearchPath()) {\n    if (search->IsShagged()) {\n      return true;\n    }\n    if (search->GetCurrentWaypoint() > 0 && x401_24_pathOverCount == 0) {\n      zeus::CVector3f origPoint = GetTranslation() + 0.3f * zeus::skUp;\n      zeus::CVector3f point = origPoint;\n      search->GetSplinePoint(point, GetTranslation());\n      return (point - origPoint).magSquared() > 4.f * skActorApproachDistance * skActorApproachDistance;\n    }\n  }\n  return false;\n}\n\nbool CPatterned::PathFound(CStateManager&, float arg) {\n  if (CPathFindSearch* search = GetSearchPath()) {\n    if (!search->IsShagged()) {\n      return true;\n    }\n  }\n  return false;\n}\n\nbool CPatterned::PathOver(CStateManager&, float arg) {\n  if (CPathFindSearch* search = GetSearchPath()) {\n    if (x328_25_verticalMovement || x328_27_onGround) {\n      if (!search->IsShagged() && search->IsOver()) {\n        return true;\n      }\n    }\n  }\n  return false;\n}\n\nbool CPatterned::Landed(CStateManager&, float arg) {\n  bool ret = x328_27_onGround && !x328_28_prevOnGround;\n  x328_28_prevOnGround = x328_27_onGround;\n  return ret;\n}\n\nbool CPatterned::PlayerSpot(CStateManager& mgr, float arg) {\n  if (mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Unmorphed) {\n    zeus::CVector3f aimPosition = mgr.GetPlayer().GetAimPosition(mgr, 0.f);\n    zeus::CVector3f center = GetBoundingBox().center();\n    zeus::CVector3f aimToCenter = center - aimPosition;\n    float aimToCenterMag = aimToCenter.magnitude();\n    zeus::CVector3f aimToCenterNorm = aimToCenter * (1.f / aimToCenterMag);\n    zeus::CVector3f screenSpace = mgr.GetCameraManager()->GetFirstPersonCamera()->ConvertToScreenSpace(center);\n    if (screenSpace.z() > 0.f && screenSpace.x() * screenSpace.x() < 1.f && screenSpace.y() * screenSpace.y() < 1.f) {\n      CRayCastResult res = mgr.RayStaticIntersection(aimPosition, aimToCenterNorm, aimToCenterMag,\n                                                     CMaterialFilter::MakeInclude(EMaterialTypes::Solid));\n      return res.IsInvalid();\n    }\n  }\n  return false;\n}\n\nbool CPatterned::SpotPlayer(CStateManager& mgr, float arg) {\n  zeus::CVector3f gunToPlayer = mgr.GetPlayer().GetAimPosition(mgr, 0.f) - GetGunEyePos();\n  float lookDot = gunToPlayer.dot(x34_transform.basis[1]);\n  if (lookDot > 0.f) {\n    return lookDot * lookDot > gunToPlayer.magSquared() * x3c4_detectionAngle;\n  }\n  return false;\n}\n\nbool CPatterned::Leash(CStateManager&, float arg) {\n  bool ret = x3d4_curPlayerLeashTime > x3d0_playerLeashTime;\n  if (ret) {\n    float posToLeashMagSq = (x3a0_latestLeashPosition - GetTranslation()).magSquared();\n    return posToLeashMagSq > x3c8_leashRadius * x3c8_leashRadius;\n  }\n  return ret;\n}\n\nbool CPatterned::InDetectionRange(CStateManager& mgr, float arg) {\n  zeus::CVector3f delta = GetTranslation() - mgr.GetPlayer().GetTranslation();\n  const float maxRange = x3bc_detectionRange * x3bc_detectionRange;\n  const float dist = delta.magSquared();\n  if (dist < maxRange) {\n    if (x3c0_detectionHeightRange > 0.f) {\n      return delta.z() * delta.z() < x3c0_detectionHeightRange * x3c0_detectionHeightRange;\n    }\n    return true;\n  }\n  return false;\n}\n\nbool CPatterned::InMaxRange(CStateManager& mgr, float arg) {\n  return (mgr.GetPlayer().GetTranslation() - GetTranslation()).magSquared() < x300_maxAttackRange * x300_maxAttackRange;\n}\n\nbool CPatterned::TooClose(CStateManager& mgr, float arg) {\n  return (mgr.GetPlayer().GetTranslation() - GetTranslation()).magSquared() < x2fc_minAttackRange * x2fc_minAttackRange;\n}\n\nbool CPatterned::InRange(CStateManager& mgr, float arg) {\n  float range = 0.5f * (x2fc_minAttackRange + x300_maxAttackRange);\n  return (mgr.GetPlayer().GetTranslation() - GetTranslation()).magSquared() < range * range;\n}\n\nbool CPatterned::OffLine(CStateManager&, float arg) {\n  zeus::CVector3f curLine = GetTranslation() - x2ec_reflectedDestPos;\n  zeus::CVector3f pathLine = x2e0_destPos - x2ec_reflectedDestPos;\n  float distSq = 0.f;\n  if (curLine.dot(pathLine) <= 0.f) {\n    distSq = curLine.magSquared();\n  } else {\n    pathLine.normalize();\n    distSq = (curLine - pathLine.dot(curLine) * pathLine).magSquared();\n    zeus::CVector3f delta = GetTranslation() - x2e0_destPos;\n    if (pathLine.dot(delta) > 0.f) {\n      distSq = delta.magSquared();\n    }\n  }\n  return distSq > arg * arg;\n}\n\nvoid CPatterned::PathFind(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (CPathFindSearch* search = GetSearchPath()) {\n    switch (msg) {\n    case EStateMsg::Activate: {\n      if (search->Search(GetTranslation(), x2e0_destPos) == CPathFindSearch::EResult::Success) {\n        x2ec_reflectedDestPos = GetTranslation();\n        zeus::CVector3f destPos;\n        if (search->GetCurrentWaypoint() + 1 < search->GetWaypoints().size()) {\n          destPos = search->GetWaypoints()[search->GetCurrentWaypoint() + 1];\n        } else {\n          destPos = search->GetWaypoints()[search->GetCurrentWaypoint()];\n        }\n        SetDestPos(destPos);\n        x328_24_inPosition = false;\n        ApproachDest(mgr);\n      }\n      break;\n    }\n    case EStateMsg::Update: {\n      if (search->GetCurrentWaypoint() < search->GetWaypoints().size() - 1) {\n        if (x328_25_verticalMovement || x328_27_onGround) {\n          x401_24_pathOverCount += 1;\n        }\n        zeus::CVector3f biasedPos = GetTranslation() + 0.3f * zeus::skUp;\n        x2ec_reflectedDestPos = biasedPos - (x2e0_destPos - biasedPos);\n        ApproachDest(mgr);\n        zeus::CVector3f biasedForward = x34_transform.basis[1] * x64_modelData->GetScale().y() + biasedPos;\n        search->GetSplinePointWithLookahead(biasedForward, biasedPos, 3.f * x64_modelData->GetScale().y());\n        SetDestPos(biasedForward);\n        if (search->SegmentOver(biasedPos)) {\n          search->SetCurrentWaypoint(search->GetCurrentWaypoint() + 1);\n        }\n      }\n      break;\n    }\n    default:\n      break;\n    }\n  }\n}\n\nvoid CPatterned::Dead(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    x31c_faceVec = zeus::skZero3f;\n    break;\n  case EStateMsg::Update:\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBodyStateCmd(EBodyStateCmd::Die));\n    if (!x400_27_fadeToDeath) {\n      if (x450_bodyController->GetBodyStateInfo().GetCurrentState()->IsDead()) {\n        x400_27_fadeToDeath = true;\n        x3e8_alphaDelta = -0.333333f;\n        RemoveMaterial(EMaterialTypes::Character, EMaterialTypes::Solid, EMaterialTypes::Target, EMaterialTypes::Orbit,\n                       mgr);\n        AddMaterial(EMaterialTypes::ProjectilePassthrough, mgr);\n      }\n    }\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CPatterned::TargetPlayer(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x2dc_destObj = mgr.GetPlayer().GetUniqueId();\n    SetDestPos(mgr.GetPlayer().GetTranslation());\n    x2ec_reflectedDestPos = GetTranslation();\n    x328_24_inPosition = false;\n  }\n}\n\nvoid CPatterned::TargetPatrol(CStateManager& mgr, EStateMsg msg, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x2dc_destObj = GetWaypointForState(mgr, EScriptObjectState::Patrol, EScriptObjectMessage::Follow);\n    if (TCastToConstPtr<CActor> act = mgr.GetObjectById(x2dc_destObj)) {\n      SetDestPos(act->GetTranslation());\n    }\n    x2ec_reflectedDestPos = GetTranslation();\n    x328_24_inPosition = false;\n  }\n}\n\nvoid CPatterned::FollowPattern(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    SetupPattern(mgr);\n    if (x328_29_noPatternShagging || !IsPatternObstructed(mgr, GetTranslation(), x2e0_destPos)) {\n      ApproachDest(mgr);\n    } else {\n      x39c_curPattern = x38c_patterns.size();\n      x400_30_patternShagged = true;\n    }\n    break;\n  case EStateMsg::Update:\n    if (x328_24_inPosition) {\n      x39c_curPattern += 1;\n      UpdatePatternDestPos(mgr);\n      if (!x328_29_noPatternShagging && IsPatternObstructed(mgr, GetTranslation(), x2e0_destPos)) {\n        x39c_curPattern = x38c_patterns.size();\n        x400_30_patternShagged = true;\n      } else if (x39c_curPattern < x38c_patterns.size()) {\n        x2ec_reflectedDestPos = GetTranslation();\n        x328_24_inPosition = false;\n        x3b0_moveSpeed = x38c_patterns[x39c_curPattern].GetSpeed();\n        x380_behaviour = EBehaviour(x38c_patterns[x39c_curPattern].GetBehaviour());\n        x30c_behaviourOrient = EBehaviourOrient(x38c_patterns[x39c_curPattern].GetBehaviourOrient());\n        x384_behaviourModifiers = EBehaviourModifiers(x38c_patterns[x39c_curPattern].GetBehaviourModifiers());\n      }\n    } else {\n      UpdatePatternDestPos(mgr);\n    }\n    ApproachDest(mgr);\n    break;\n  case EStateMsg::Deactivate:\n    x38c_patterns.clear();\n    x400_30_patternShagged = false;\n  }\n}\n\nvoid CPatterned::Patrol(CStateManager& mgr, EStateMsg msg, float dt) {\n  switch (msg) {\n  case EStateMsg::Activate:\n    if (x3ac_lastPatrolDest == kInvalidUniqueId) {\n      x2dc_destObj = GetWaypointForState(mgr, EScriptObjectState::Patrol, EScriptObjectMessage::Follow);\n      x30c_behaviourOrient = EBehaviourOrient::MoveDir;\n      x3b0_moveSpeed = 1.f;\n      if (x2dc_destObj != kInvalidUniqueId) {\n        if (TCastToConstPtr<CScriptWaypoint> wp = mgr.GetObjectById(x2dc_destObj)) {\n          x30c_behaviourOrient = EBehaviourOrient(wp->GetBehaviourOrient());\n          x3b0_moveSpeed = wp->GetSpeed();\n        }\n      }\n    } else {\n      x2dc_destObj = x3ac_lastPatrolDest;\n    }\n    x2ec_reflectedDestPos = GetTranslation();\n    x328_24_inPosition = false;\n    x2d8_patrolState = EPatrolState::Patrol;\n    x2f8_waypointPauseRemTime = 0.f;\n    break;\n  case EStateMsg::Update:\n    switch (x2d8_patrolState) {\n    case EPatrolState::Patrol:\n      if (x328_24_inPosition && x2dc_destObj != kInvalidUniqueId) {\n        if (TCastToConstPtr<CScriptWaypoint> wp = mgr.GetObjectById(x2dc_destObj)) {\n          if (wp->GetPause() > 0.f) {\n            x2f8_waypointPauseRemTime = wp->GetPause();\n            x2d8_patrolState = EPatrolState::Pause;\n          }\n        }\n      }\n      if (x2dc_destObj == kInvalidUniqueId) {\n        x2d8_patrolState = EPatrolState::Done;\n      }\n      UpdateDest(mgr);\n      ApproachDest(mgr);\n      break;\n    case EPatrolState::Pause:\n      if (x2f8_waypointPauseRemTime <= 0.f) {\n        x2d8_patrolState = EPatrolState::Patrol;\n      }\n      break;\n    case EPatrolState::Done:\n      if (x2dc_destObj != kInvalidUniqueId) {\n        x2d8_patrolState = EPatrolState::Patrol;\n      }\n      break;\n    default:\n      break;\n    }\n    break;\n  case EStateMsg::Deactivate:\n    x3ac_lastPatrolDest = x2dc_destObj;\n    x2d8_patrolState = EPatrolState::Invalid;\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CPatterned::TryCommand(CStateManager& mgr, pas::EAnimationState state, CPatternedTryFunc func, int arg) {\n  if (state == x450_bodyController->GetCurrentStateId()) {\n    x32c_animState = EAnimState::Repeat;\n  } else if (x32c_animState == EAnimState::Ready) {\n    (this->*func)(mgr, arg);\n  } else {\n    x32c_animState = EAnimState::Over;\n  }\n}\n\nvoid CPatterned::TryLoopReaction(CStateManager& mgr, int arg) {\n  x450_bodyController->GetCommandMgr().DeliverCmd(CBCLoopReactionCmd(pas::EReactionType(arg)));\n}\n\nvoid CPatterned::TryProjectileAttack(CStateManager&, int arg) {\n  x450_bodyController->GetCommandMgr().DeliverCmd(CBCProjectileAttackCmd(pas::ESeverity(arg), x2e0_destPos, false));\n}\n\nvoid CPatterned::TryMeleeAttack_TargetPos(CStateManager& mgr, int arg) {\n  x450_bodyController->GetCommandMgr().DeliverCmd(CBCMeleeAttackCmd(pas::ESeverity(arg), x2e0_destPos));\n}\n\nvoid CPatterned::TryMeleeAttack(CStateManager& mgr, int arg) {\n  x450_bodyController->GetCommandMgr().DeliverCmd(CBCMeleeAttackCmd(pas::ESeverity(arg)));\n}\n\nvoid CPatterned::TryGenerateNoXf(CStateManager& mgr, int arg) {\n  x450_bodyController->GetCommandMgr().DeliverCmd(CBCGenerateCmd(pas::EGenerateType(arg), x2e0_destPos, false));\n}\n\nvoid CPatterned::TryGenerate(CStateManager& mgr, int arg) {\n  x450_bodyController->GetCommandMgr().DeliverCmd(CBCGenerateCmd(pas::EGenerateType::Zero, x2e0_destPos, true));\n}\n\nvoid CPatterned::TryJump(CStateManager& mgr, int arg) {\n  x450_bodyController->GetCommandMgr().DeliverCmd(CBCJumpCmd(x2e0_destPos, pas::EJumpType(arg)));\n}\n\nvoid CPatterned::TryTurn(CStateManager& mgr, int arg) {\n  x450_bodyController->GetCommandMgr().DeliverCmd(\n      CBCLocomotionCmd(zeus::skZero3f, (x2e0_destPos - GetTranslation()).normalized(), 1.f));\n}\n\nvoid CPatterned::TryGetUp(CStateManager& mgr, int arg) {\n  x450_bodyController->GetCommandMgr().DeliverCmd(CBCGetupCmd(pas::EGetupType(arg)));\n}\n\nvoid CPatterned::TryTaunt(CStateManager& mgr, int arg) {\n  x450_bodyController->GetCommandMgr().DeliverCmd(CBCTauntCmd(pas::ETauntType(arg)));\n}\n\nvoid CPatterned::TryJumpInLoop(CStateManager& mgr, int arg) {\n  x450_bodyController->GetCommandMgr().DeliverCmd(CBCJumpCmd(x2e0_destPos, pas::EJumpType(arg), true));\n}\n\nvoid CPatterned::TryDodge(CStateManager& mgr, int arg) {\n  x450_bodyController->GetCommandMgr().DeliverCmd(CBCStepCmd(pas::EStepDirection(arg), pas::EStepType::Dodge));\n}\n\nvoid CPatterned::TryRollingDodge(CStateManager& mgr, int arg) {\n  x450_bodyController->GetCommandMgr().DeliverCmd(CBCStepCmd(pas::EStepDirection(arg), pas::EStepType::RollDodge));\n}\n\nvoid CPatterned::TryBreakDodge(CStateManager& mgr, int arg) {\n  x450_bodyController->GetCommandMgr().DeliverCmd(CBCStepCmd(pas::EStepDirection(arg), pas::EStepType::BreakDodge));\n}\n\nvoid CPatterned::TryCover(CStateManager& mgr, int arg) {\n  if (CScriptCoverPoint* cp = GetCoverPoint(mgr, x2dc_destObj)) {\n    x450_bodyController->GetCommandMgr().DeliverCmd(\n        CBCCoverCmd(pas::ECoverDirection(arg), cp->GetTranslation(), -cp->GetTransform().basis[1]));\n  }\n}\n\nvoid CPatterned::TryWallHang(CStateManager& mgr, int arg) {\n  x450_bodyController->GetCommandMgr().DeliverCmd(CBCWallHangCmd(x2dc_destObj));\n}\n\nvoid CPatterned::TryKnockBack(CStateManager& mgr, int arg) {\n  x450_bodyController->GetCommandMgr().DeliverCmd(CBCKnockBackCmd(GetTranslation(), pas::ESeverity(arg)));\n}\n\nvoid CPatterned::TryKnockBack_Front(CStateManager& mgr, int arg) {\n  x450_bodyController->GetCommandMgr().DeliverCmd(CBCKnockBackCmd(GetTransform().frontVector(), pas::ESeverity(arg)));\n}\n\nvoid CPatterned::TryGenerateDeactivate(metaforce::CStateManager& mgr, int arg) {\n  x450_bodyController->GetCommandMgr().DeliverCmd(CBCGenerateCmd(pas::EGenerateType(arg), zeus::skZero3f));\n}\n\nvoid CPatterned::TryStep(CStateManager& mgr, int arg) {\n  x450_bodyController->GetCommandMgr().DeliverCmd(CBCStepCmd(pas::EStepDirection(arg), pas::EStepType::Normal));\n}\n\nvoid CPatterned::TryScripted(CStateManager& mgr, int arg) {\n  x450_bodyController->GetCommandMgr().DeliverCmd(CBCScriptedCmd(arg, false, false, 0.f));\n}\n\nvoid CPatterned::BuildBodyController(EBodyType bodyType) {\n  if (x450_bodyController) {\n    return;\n  }\n\n  x450_bodyController = std::make_unique<CBodyController>(*this, x3b8_turnSpeed, bodyType);\n  auto anim = x450_bodyController->GetPASDatabase().FindBestAnimation(\n      CPASAnimParmData(pas::EAnimationState::AdditiveReaction, CPASAnimParm::FromEnum(0)), -1);\n  x460_knockBackController.x81_26_enableShock = anim.first > 0.f;\n}\n\nvoid CPatterned::GenerateDeathExplosion(CStateManager& mgr) {\n  if (auto particle = GetDeathExplosionParticle()) {\n    zeus::CTransform xf(GetTransform());\n    xf.origin = GetTransform() * (x64_modelData->GetScale() * x514_deathExplosionOffset);\n    auto* explo = new CExplosion(*particle, mgr.AllocateUniqueId(), true,\n                                 CEntityInfo(GetAreaIdAlways(), CEntity::NullConnectionList), \"\", xf, 1, zeus::skOne3f,\n                                 zeus::skWhite);\n    mgr.AddObject(explo);\n  }\n  if (x530_deathExplosionElectric) {\n    zeus::CTransform xf(GetTransform());\n    xf.origin = GetTransform() * (x64_modelData->GetScale() * x514_deathExplosionOffset);\n    auto* explo = new CExplosion(*x530_deathExplosionElectric, mgr.AllocateUniqueId(), true,\n                                 CEntityInfo(GetAreaIdAlways(), CEntity::NullConnectionList), \"\", xf, 1, zeus::skOne3f,\n                                 zeus::skWhite);\n    mgr.AddObject(explo);\n  }\n}\n\nvoid CPatterned::MassiveDeath(CStateManager& mgr) {\n  CSfxManager::AddEmitter(x454_deathSfx, GetTranslation(), zeus::skZero3f, true, false, 0x7f, kInvalidAreaId);\n  if (!x401_28_burning) {\n    SendScriptMsgs(EScriptObjectState::MassiveDeath, mgr, EScriptObjectMessage::None);\n    GenerateDeathExplosion(mgr);\n  }\n  DeathDelete(mgr);\n  x400_28_pendingMassiveDeath = x400_29_pendingMassiveFrozenDeath = false;\n}\n\nvoid CPatterned::GenerateIceDeathExplosion(CStateManager& mgr) {\n  if (x54c_iceDeathExplosionParticle) {\n    zeus::CTransform xf(GetTransform());\n    xf.origin = GetTransform() * (x64_modelData->GetScale() * x540_iceDeathExplosionOffset);\n    auto* explo = new CExplosion(*x54c_iceDeathExplosionParticle, mgr.AllocateUniqueId(), true,\n                                 CEntityInfo(GetAreaIdAlways(), CEntity::NullConnectionList), \"\", xf, 1, zeus::skOne3f,\n                                 zeus::skWhite);\n    mgr.AddObject(explo);\n  }\n}\n\nvoid CPatterned::MassiveFrozenDeath(CStateManager& mgr) {\n  if (x458_iceShatterSfx == 0xffff) {\n    x458_iceShatterSfx = x454_deathSfx;\n  }\n  CSfxManager::AddEmitter(x458_iceShatterSfx, GetTranslation(), zeus::skZero3f, true, false, 0x7f, kInvalidAreaId);\n  SendScriptMsgs(EScriptObjectState::MassiveFrozenDeath, mgr, EScriptObjectMessage::None);\n  GenerateIceDeathExplosion(mgr);\n  float toPlayerDist = (mgr.GetPlayer().GetTranslation() - GetTranslation()).magnitude();\n  if (toPlayerDist < 40.f) {\n    mgr.GetCameraManager()->AddCameraShaker(\n        CCameraShakeData::BuildPatternedExplodeShakeData(GetTranslation(), 0.25f, 0.3f, 40.f), true);\n  }\n  DeathDelete(mgr);\n  x400_28_pendingMassiveDeath = x400_29_pendingMassiveFrozenDeath = false;\n}\n\nvoid CPatterned::Burn(float duration, float damage) {\n  switch (GetDamageVulnerability()->GetVulnerability(CWeaponMode(EWeaponType::Plasma), false)) {\n  case EVulnerability::Weak:\n    x450_bodyController->SetOnFire(1.5f * duration);\n    x3ec_pendingFireDamage = 1.5f * damage;\n    break;\n  case EVulnerability::Normal:\n    x450_bodyController->SetOnFire(duration);\n    x3ec_pendingFireDamage = damage;\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CPatterned::Shock(CStateManager& mgr, float duration, float damage) {\n  switch (GetDamageVulnerability()->GetVulnerability(CWeaponMode(EWeaponType::Wave), false)) {\n  case EVulnerability::Weak:\n    x450_bodyController->SetElectrocuting(1.5f * duration);\n    x3f0_pendingShockDamage = 1.5f * damage;\n    break;\n  case EVulnerability::Normal:\n    x450_bodyController->SetElectrocuting(duration);\n    x3f0_pendingShockDamage = damage;\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CPatterned::Freeze(CStateManager& mgr, const zeus::CVector3f& pos, const zeus::CUnitVector3f& dir,\n                        float frozenDur) {\n  if (x402_25_lostMassiveFrozenHP) {\n    x402_26_dieIf80PercFrozen = true;\n  }\n  bool playSfx = false;\n  if (x450_bodyController->IsFrozen()) {\n    x450_bodyController->Freeze(x460_knockBackController.GetActiveParms().xc_intoFreezeDur, frozenDur,\n                                x4f8_outofFreezeDur);\n    mgr.GetActorModelParticles()->EnsureElectricLoaded(*this);\n    playSfx = true;\n  } else if (!x450_bodyController->IsElectrocuting() && !x450_bodyController->IsOnFire()) {\n    x450_bodyController->Freeze(x4f4_intoFreezeDur, frozenDur, x4f8_outofFreezeDur);\n    if (x510_vertexMorph) {\n      x510_vertexMorph->Reset(dir, pos, x4f4_intoFreezeDur);\n    }\n    playSfx = true;\n  }\n\n  if (playSfx) {\n    u16 sfx = (x460_knockBackController.GetVariant() != EKnockBackVariant::Small &&\n               CPatterned::CastTo<MP1::CMetroid>(mgr.GetObjectById(GetUniqueId())) != nullptr)\n                  ? SFXsfx0701\n                  : SFXsfx0708;\n    CSfxManager::AddEmitter(sfx, GetTranslation(), zeus::skZero3f, true, false, 0x7f, kInvalidAreaId);\n  }\n}\n\nzeus::CVector3f CPatterned::GetGunEyePos() const {\n  zeus::CVector3f origin = GetTranslation();\n  zeus::CAABox baseBox = GetBaseBoundingBox();\n  origin.z() = 0.6f * (baseBox.max.z() - baseBox.min.z()) + origin.z();\n  return origin;\n}\n\nvoid CPatterned::SetupPlayerCollision(bool v) {\n  CMaterialList include = GetMaterialFilter().GetIncludeList();\n  CMaterialList exclude = GetMaterialFilter().GetExcludeList();\n  CMaterialList* modList = (v ? &exclude : &include);\n  modList->Add(EMaterialTypes::Player);\n  SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(include, exclude));\n}\n\nCGameProjectile* CPatterned::LaunchProjectile(const zeus::CTransform& gunXf, CStateManager& mgr, int maxAllowed,\n                                              EProjectileAttrib attrib, bool playerHoming,\n                                              const std::optional<TLockedToken<CGenDescription>>& visorParticle,\n                                              u16 visorSfx, bool sendCollideMsg, const zeus::CVector3f& scale) {\n  CProjectileInfo* pInfo = GetProjectileInfo();\n  if (pInfo && pInfo->Token().IsLoaded()) {\n    if (mgr.CanCreateProjectile(GetUniqueId(), EWeaponType::AI, maxAllowed)) {\n      TUniqueId homingId = playerHoming ? mgr.GetPlayer().GetUniqueId() : kInvalidUniqueId;\n      auto* newProjectile =\n          new CEnergyProjectile(true, pInfo->Token(), EWeaponType::AI, gunXf, EMaterialTypes::Character,\n                                pInfo->GetDamage(), mgr.AllocateUniqueId(), GetAreaIdAlways(), GetUniqueId(), homingId,\n                                attrib, false, scale, visorParticle, visorSfx, sendCollideMsg);\n      mgr.AddObject(newProjectile);\n      return newProjectile;\n    }\n  }\n  return nullptr;\n}\n\nvoid CPatterned::DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) {\n  switch (type) {\n  case EUserEventType::Projectile: {\n    zeus::CTransform lctrXf = GetLctrTransform(node.GetLocatorName());\n    zeus::CVector3f aimPos = mgr.GetPlayer().GetAimPosition(mgr, 0.f);\n    if ((aimPos - lctrXf.origin).normalized().dot(lctrXf.basis[1]) > 0.f) {\n      zeus::CTransform gunXf = zeus::lookAt(lctrXf.origin, aimPos);\n      LaunchProjectile(gunXf, mgr, 1, EProjectileAttrib::None, false, {}, 0xffff, false, zeus::skOne3f);\n    } else {\n      LaunchProjectile(lctrXf, mgr, 1, EProjectileAttrib::None, false, {}, 0xffff, false, zeus::skOne3f);\n    }\n    break;\n  }\n  case EUserEventType::DamageOn: {\n    zeus::CTransform lctrXf = GetLctrTransform(node.GetLocatorName());\n    zeus::CVector3f xfOrigin = x34_transform * (x64_modelData->GetScale() * lctrXf.origin);\n    zeus::CVector3f margin = zeus::CVector3f(1.f, 1.f, 0.5f) * x64_modelData->GetScale();\n    if (zeus::CAABox(xfOrigin - margin, xfOrigin + margin).intersects(mgr.GetPlayer().GetBoundingBox())) {\n      mgr.ApplyDamage(GetUniqueId(), mgr.GetPlayer().GetUniqueId(), GetUniqueId(), GetContactDamage(),\n                      CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), zeus::skZero3f);\n    }\n    break;\n  }\n  case EUserEventType::Delete: {\n    if (!x400_25_alive) {\n      if (!x400_27_fadeToDeath) {\n        x3e8_alphaDelta = -0.333333f;\n        x400_27_fadeToDeath = true;\n      }\n      RemoveMaterial(EMaterialTypes::Character, EMaterialTypes::Solid, EMaterialTypes::Target, EMaterialTypes::Orbit,\n                     mgr);\n      AddMaterial(EMaterialTypes::ProjectilePassthrough, mgr);\n    } else {\n      DeathDelete(mgr);\n    }\n    break;\n  }\n  case EUserEventType::BreakLockOn: {\n    RemoveMaterial(EMaterialTypes::Character, EMaterialTypes::Target, EMaterialTypes::Orbit, mgr);\n    break;\n  }\n  case EUserEventType::BecomeShootThrough: {\n    AddMaterial(EMaterialTypes::ProjectilePassthrough, mgr);\n    break;\n  }\n  case EUserEventType::RemoveCollision: {\n    RemoveMaterial(EMaterialTypes::Solid, mgr);\n    break;\n  }\n  default:\n    break;\n  }\n  CActor::DoUserAnimEvent(mgr, node, type, dt);\n}\n\nvoid CPatterned::UpdateAlphaDelta(float dt, CStateManager& mgr) {\n  if (x3e8_alphaDelta == 0.f) {\n    return;\n  }\n\n  float alpha = dt * x3e8_alphaDelta + x42c_color.a();\n  if (alpha > 1.f) {\n    alpha = 1.f;\n    x3e8_alphaDelta = 0.f;\n  } else if (alpha < 0.f) {\n    alpha = 0.f;\n    x3e8_alphaDelta = 0.f;\n    if (x400_27_fadeToDeath) {\n      DeathDelete(mgr);\n    }\n  }\n  x94_simpleShadow->SetUserAlpha(alpha);\n  SetModelAlpha(alpha);\n  x64_modelData->GetAnimationData()->GetParticleDB().SetModulationColorAllActiveEffects(zeus::CColor(1.f, alpha));\n}\n\nfloat CPatterned::CalcDyingThinkRate() const {\n  float f0 = (x401_28_burning ? (x3f4_burnThinkRateTimer / 1.5f) : 1.f);\n  return zeus::max(0.1f, f0);\n}\n\nvoid CPatterned::UpdateDamageColor(float dt) {\n  if (x428_damageCooldownTimer > 0.f) {\n    x428_damageCooldownTimer = std::max(0.f, x428_damageCooldownTimer - dt);\n    float alpha = x42c_color.a();\n    x42c_color = zeus::CColor::lerp(zeus::skBlack, x430_damageColor, std::min(x428_damageCooldownTimer / 0.33f, 1.f));\n    x42c_color.a() = alpha;\n    if (!x450_bodyController->IsFrozen()) {\n      xd0_damageMag = x50c_baseDamageMag + x428_damageCooldownTimer;\n    }\n  }\n}\n\nCScriptCoverPoint* CPatterned::GetCoverPoint(CStateManager& mgr, TUniqueId id) const {\n  if (id != kInvalidUniqueId) {\n    if (TCastToPtr<CScriptCoverPoint> cp = mgr.ObjectById(id)) {\n      return cp.GetPtr();\n    }\n  }\n  return nullptr;\n}\n\nvoid CPatterned::SetCoverPoint(CScriptCoverPoint* cp, TUniqueId& id) {\n  cp->SetInUse(true);\n  id = cp->GetUniqueId();\n}\n\nvoid CPatterned::ReleaseCoverPoint(CStateManager& mgr, TUniqueId& id) const {\n  if (CScriptCoverPoint* cp = GetCoverPoint(mgr, id)) {\n    cp->SetInUse(false);\n    id = kInvalidUniqueId;\n  }\n}\n\nTUniqueId CPatterned::GetWaypointForState(CStateManager& mgr, EScriptObjectState state,\n                                          EScriptObjectMessage msg) const {\n  rstl::reserved_vector<TUniqueId, 12> ids;\n  for (const auto& conn : GetConnectionList()) {\n    if (conn.x0_state == state && conn.x4_msg == msg) {\n      TUniqueId id = mgr.GetIdForScript(conn.x8_objId);\n      if (const CEntity* ent = mgr.GetObjectById(id)) {\n        if (ent->GetActive()) {\n          ids.push_back(id);\n        }\n      }\n    }\n  }\n\n  if (!ids.empty()) {\n    return ids[mgr.GetActiveRandom()->Next() % ids.size()];\n  }\n\n  return kInvalidUniqueId;\n}\n\nvoid CPatterned::UpdateActorKeyframe(CStateManager& mgr) const {\n  if (TCastToConstPtr<CScriptWaypoint> wp = mgr.GetObjectById(x2dc_destObj)) {\n    for (const auto& conn : wp->GetConnectionList()) {\n      if (conn.x0_state == EScriptObjectState::Arrived && conn.x4_msg == EScriptObjectMessage::Action) {\n        if (TCastToPtr<CScriptActorKeyframe> kf = mgr.ObjectById(mgr.GetIdForScript(conn.x8_objId))) {\n          if (kf->GetActive() && kf->IsPassive()) {\n            kf->UpdateEntity(GetUniqueId(), mgr);\n          }\n        }\n      }\n    }\n  }\n}\n\npas::EStepDirection CPatterned::GetStepDirection(const zeus::CVector3f& moveVec) const {\n  zeus::CVector3f localMove = x34_transform.transposeRotate(moveVec);\n  float angle = zeus::CVector3f::getAngleDiff(localMove, zeus::skForward);\n  if (angle < zeus::degToRad(45.f)) {\n    return pas::EStepDirection::Forward;\n  }\n  if (angle > zeus::degToRad(135.f)) {\n    return pas::EStepDirection::Backward;\n  }\n  if (localMove.dot(zeus::skRight) > 0.f) {\n    return pas::EStepDirection::Right;\n  }\n  return pas::EStepDirection::Left;\n}\n\nbool CPatterned::IsPatternObstructed(CStateManager& mgr, const zeus::CVector3f& p0, const zeus::CVector3f& p1) const {\n  CMaterialFilter filter = CMaterialFilter::MakeInclude(EMaterialTypes::Character);\n  zeus::CVector3f delta = p1 - p0;\n  EntityList nearList;\n  bool ret = false;\n  if (delta.canBeNormalized()) {\n    zeus::CVector3f deltaNorm = delta.normalized();\n    float deltaMag = delta.magnitude();\n    mgr.BuildNearList(nearList, p0, deltaNorm, deltaMag, filter, this);\n    TUniqueId bestId = kInvalidUniqueId;\n    CRayCastResult res = mgr.RayWorldIntersection(bestId, p0, deltaNorm, deltaMag,\n                                                  CMaterialFilter::MakeInclude(EMaterialTypes::Solid), nearList);\n    ret = res.IsValid();\n  }\n  return ret;\n}\n\nvoid CPatterned::UpdateDest(CStateManager& mgr) {\n  if (x328_24_inPosition && x2dc_destObj != kInvalidUniqueId) {\n    if (TCastToPtr<CScriptWaypoint> wp = mgr.ObjectById(x2dc_destObj)) {\n      UpdateActorKeyframe(mgr);\n      x2dc_destObj = wp->NextWaypoint(mgr);\n      if (x2dc_destObj != kInvalidUniqueId) {\n        x2ec_reflectedDestPos = GetTranslation();\n        x328_24_inPosition = false;\n        if (TCastToConstPtr<CScriptWaypoint> wp2 = mgr.GetObjectById(x2dc_destObj)) {\n          x3b0_moveSpeed = wp->GetSpeed();\n          x30c_behaviourOrient = EBehaviourOrient(wp->GetBehaviourOrient());\n          if ((wp->GetBehaviourModifiers() & 0x2) != 0) {\n            x450_bodyController->GetCommandMgr().DeliverCmd(CBCJumpCmd(wp2->GetTranslation(), pas::EJumpType::Normal));\n          } else if ((wp->GetBehaviourModifiers() & 0x4) != 0) {\n            TUniqueId wp3Id = wp2->NextWaypoint(mgr);\n            if (wp3Id != kInvalidUniqueId) {\n              if (TCastToConstPtr<CScriptWaypoint> wp3 = mgr.GetObjectById(wp3Id)) {\n                x450_bodyController->GetCommandMgr().DeliverCmd(\n                    CBCJumpCmd(wp2->GetTranslation(), wp3->GetTranslation(), pas::EJumpType::Normal));\n              }\n            }\n          }\n        }\n      }\n      mgr.SendScriptMsg(wp.GetPtr(), GetUniqueId(), EScriptObjectMessage::Arrived);\n    }\n  }\n\n  if (x2dc_destObj != kInvalidUniqueId) {\n    if (TCastToConstPtr<CActor> act = mgr.GetObjectById(x2dc_destObj)) {\n      SetDestPos(act->GetTranslation());\n    }\n  }\n}\n\nvoid CPatterned::ApproachDest(CStateManager& mgr) {\n  zeus::CVector3f faceVec = mgr.GetPlayer().GetTranslation() - GetTranslation();\n  zeus::CVector3f moveVec = x2e0_destPos - GetTranslation();\n  if (!x328_25_verticalMovement) {\n    moveVec.z() = 0.f;\n    faceVec.z() = 0.f;\n  }\n  zeus::CVector3f pathLine = x2e0_destPos - x2ec_reflectedDestPos;\n  if (pathLine.dot(moveVec) <= 0.f) {\n    x328_24_inPosition = true;\n  } else if (moveVec.magSquared() < 3.f * 3.f) {\n    moveVec = pathLine;\n  }\n  if (!x328_24_inPosition) {\n    if (moveVec.canBeNormalized()) {\n      moveVec.normalize();\n    }\n    switch (x30c_behaviourOrient) {\n    case EBehaviourOrient::MoveDir:\n      faceVec = moveVec;\n      break;\n    case EBehaviourOrient::Destination:\n      if (x39c_curPattern != 0u && x39c_curPattern < x38c_patterns.size()) {\n        faceVec = x38c_patterns[x39c_curPattern].GetForward();\n      } else if (x2dc_destObj != kInvalidUniqueId) {\n        if (TCastToConstPtr<CScriptWaypoint> wp = mgr.GetObjectById(x2dc_destObj)) {\n          faceVec = wp->GetTransform().basis[1];\n        }\n      }\n      break;\n    default:\n      break;\n    }\n    x31c_faceVec = faceVec;\n    x310_moveVec = x3b0_moveSpeed * moveVec;\n    if (!KnockbackWhenFrozen()) {\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(x310_moveVec, x31c_faceVec, 1.f));\n    } else if (x30c_behaviourOrient == EBehaviourOrient::MoveDir ||\n               !x450_bodyController->HasBodyState(pas::EAnimationState::Step)) {\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(x310_moveVec, zeus::skZero3f, 1.f));\n    } else {\n      pas::EStepDirection stepDir = GetStepDirection(x310_moveVec);\n      if (stepDir == pas::EStepDirection::Forward) {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(x310_moveVec, zeus::skZero3f, 1.f));\n      } else {\n        x450_bodyController->GetCommandMgr().DeliverCmd(CBCStepCmd(stepDir, pas::EStepType::Normal));\n      }\n      x450_bodyController->GetCommandMgr().DeliverTargetVector(x31c_faceVec);\n    }\n  } else if (x450_bodyController->GetBodyStateInfo().GetMaxSpeed() > FLT_EPSILON) {\n    x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(\n        (x138_velocity.magnitude() / x450_bodyController->GetBodyStateInfo().GetMaxSpeed()) * x34_transform.basis[1],\n        zeus::skZero3f, 1.f));\n  }\n}\n\nstd::pair<CScriptWaypoint*, CScriptWaypoint*> CPatterned::GetDestWaypoints(CStateManager& mgr) const {\n  std::pair<CScriptWaypoint*, CScriptWaypoint*> ret{};\n  if (TCastToPtr<CScriptWaypoint> wp = mgr.ObjectById(x2dc_destObj)) {\n    ret.first = wp.GetPtr();\n    ret.second = TCastToPtr<CScriptWaypoint>(mgr.ObjectById(wp->FollowWaypoint(mgr))).GetPtr();\n  }\n  return ret;\n}\n\nzeus::CQuaternion CPatterned::FindPatternRotation(const zeus::CVector3f& dir) const {\n  zeus::CVector3f wpDeltaFlat = x368_destWPDelta;\n  wpDeltaFlat.z() = 0.f;\n  wpDeltaFlat.normalize();\n  zeus::CVector3f dirFlat = dir;\n  dirFlat.z() = 0.f;\n  dirFlat.normalize();\n\n  zeus::CQuaternion q;\n  if ((wpDeltaFlat - dirFlat).magSquared() > 3.99f) {\n    q.rotateZ(zeus::degToRad(180.f));\n  } else {\n    q = zeus::CQuaternion::shortestRotationArc(wpDeltaFlat, dirFlat);\n  }\n\n  if (x328_25_verticalMovement) {\n    q = zeus::CQuaternion::shortestRotationArc(\n            (q * zeus::CQuaternion(0.f, x368_destWPDelta) * q.inverse()).getImaginary().normalized(),\n            dir.normalized()) *\n        q;\n  }\n\n  return q;\n}\n\nzeus::CVector3f CPatterned::FindPatternDir(CStateManager& mgr) const {\n  zeus::CVector3f ret;\n  switch (x378_patternOrient) {\n  case EPatternOrient::StartToPlayerStart:\n    ret = x35c_patternStartPlayerPos - x350_patternStartPos;\n    break;\n  case EPatternOrient::StartToPlayer:\n    ret = mgr.GetPlayer().GetTranslation() - x350_patternStartPos;\n    break;\n  case EPatternOrient::ReversePlayerForward:\n    ret = -mgr.GetPlayer().GetTransform().basis[1];\n    break;\n  case EPatternOrient::Forward:\n    ret = GetTransform().basis[1];\n    break;\n  default:\n    break;\n  }\n  return ret;\n}\n\nvoid CPatterned::UpdatePatternDestPos(CStateManager& mgr) {\n  if (x39c_curPattern < x38c_patterns.size()) {\n    if (x368_destWPDelta != zeus::skZero3f) {\n      zeus::CVector3f patternDir = FindPatternDir(mgr);\n      SetDestPos(FindPatternRotation(patternDir).transform(x38c_patterns[x39c_curPattern].GetPos()));\n      if (x37c_patternFit == EPatternFit::Zero) {\n        float magSq = x328_25_verticalMovement\n                          ? patternDir.magSquared() / x368_destWPDelta.magSquared()\n                          : patternDir.toVec2f().magSquared() / x368_destWPDelta.toVec2f().magSquared();\n        SetDestPos(std::sqrt(magSq) * x2e0_destPos);\n      }\n    } else {\n      SetDestPos(x38c_patterns[x39c_curPattern].GetPos());\n    }\n  }\n\n  switch (x374_patternTranslate) {\n  case EPatternTranslate::RelativeStart:\n    SetDestPos(x2e0_destPos + x350_patternStartPos);\n    break;\n  case EPatternTranslate::RelativePlayerStart:\n    SetDestPos(x2e0_destPos + x35c_patternStartPlayerPos);\n    break;\n  case EPatternTranslate::RelativePlayer:\n    SetDestPos(x2e0_destPos + mgr.GetPlayer().GetTranslation());\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CPatterned::SetupPattern(CStateManager& mgr) {\n  EScriptObjectState state = GetDesiredAttackState(mgr);\n  x2dc_destObj = GetWaypointForState(mgr, state, EScriptObjectMessage::Follow);\n  if (x2dc_destObj == kInvalidUniqueId && state != EScriptObjectState::Attack) {\n    x2dc_destObj = GetWaypointForState(mgr, EScriptObjectState::Attack, EScriptObjectMessage::Follow);\n  }\n  x38c_patterns.clear();\n  if (x2dc_destObj != kInvalidUniqueId) {\n    x350_patternStartPos = GetTranslation();\n    x35c_patternStartPlayerPos = mgr.GetPlayer().GetTranslation();\n    auto destWPs = GetDestWaypoints(mgr);\n    if (destWPs.first != nullptr) {\n      x374_patternTranslate = EPatternTranslate(destWPs.first->GetPatternTranslate());\n      x378_patternOrient = EPatternOrient(destWPs.first->GetPatternOrient());\n      x37c_patternFit = EPatternFit(destWPs.first->GetPatternFit());\n      if (destWPs.second != nullptr) {\n        x368_destWPDelta = destWPs.second->GetTranslation() - destWPs.first->GetTranslation();\n      } else {\n        x368_destWPDelta = zeus::skZero3f;\n      }\n\n      int numPatterns = 0;\n      CScriptWaypoint* curWp = destWPs.first;\n      do {\n        ++numPatterns;\n        curWp = TCastToPtr<CScriptWaypoint>(mgr.ObjectById(curWp->NextWaypoint(mgr))).GetPtr();\n        if (curWp == nullptr) {\n          break;\n        }\n      } while (curWp->GetUniqueId() != destWPs.first->GetUniqueId());\n      x38c_patterns.reserve(numPatterns);\n\n      zeus::CVector3f basePos;\n      switch (x374_patternTranslate) {\n      case EPatternTranslate::RelativePlayerStart:\n        if (destWPs.second != nullptr) {\n          basePos = destWPs.second->GetTranslation();\n        }\n        break;\n      case EPatternTranslate::Absolute:\n        break;\n      default:\n        basePos = destWPs.first->GetTranslation();\n        break;\n      }\n\n      curWp = destWPs.first;\n      do {\n        zeus::CVector3f wpForward = curWp->GetTransform().basis[1];\n        if (x368_destWPDelta != zeus::skZero3f) {\n          wpForward = FindPatternRotation(FindPatternDir(mgr)).transform(wpForward);\n        }\n        x38c_patterns.emplace_back(curWp->GetTranslation() - basePos, wpForward, curWp->GetSpeed(),\n                                   curWp->GetBehaviour(), curWp->GetBehaviourOrient(), curWp->GetBehaviourModifiers(),\n                                   curWp->GetAnimation());\n        curWp = TCastToPtr<CScriptWaypoint>(mgr.ObjectById(curWp->NextWaypoint(mgr))).GetPtr();\n        if (curWp == nullptr) {\n          break;\n        }\n      } while (curWp->GetUniqueId() != destWPs.first->GetUniqueId());\n    }\n  }\n\n  x400_30_patternShagged = false;\n  x39c_curPattern = 0;\n  x328_24_inPosition = false;\n  x2ec_reflectedDestPos = GetTranslation();\n  if (!x38c_patterns.empty()) {\n    x3b0_moveSpeed = x38c_patterns.front().GetSpeed();\n    x380_behaviour = EBehaviour(x38c_patterns.front().GetBehaviour());\n    x30c_behaviourOrient = EBehaviourOrient(x38c_patterns.front().GetBehaviourOrient());\n    x384_behaviourModifiers = EBehaviourModifiers(x38c_patterns.front().GetBehaviourModifiers());\n  }\n}\n\nEScriptObjectState CPatterned::GetDesiredAttackState(CStateManager& mgr) const {\n  float deltaMagSq = (GetTranslation() - mgr.GetPlayer().GetTranslation()).magSquared();\n  if (deltaMagSq < x2fc_minAttackRange * x2fc_minAttackRange) {\n    return EScriptObjectState::Retreat;\n  }\n  if (deltaMagSq > x300_maxAttackRange * x300_maxAttackRange) {\n    return EScriptObjectState::CloseIn;\n  }\n  return EScriptObjectState::Attack;\n}\n\nfloat CPatterned::GetAnimationDistance(const CPASAnimParmData& data) const {\n  auto bestAnim = x64_modelData->GetAnimationData()->GetCharacterInfo().GetPASDatabase().FindBestAnimation(data, -1);\n  float dist = 1.f;\n  if (bestAnim.first > FLT_EPSILON) {\n    dist = x64_modelData->GetAnimationData()->GetAnimationDuration(bestAnim.second) *\n           x64_modelData->GetAnimationData()->GetAverageVelocity(bestAnim.second);\n  }\n  return dist;\n}\n\nvoid CPatterned::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) {\n  if (mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::Thermal) {\n    SetCalculateLighting(false);\n    x90_actorLights->BuildConstantAmbientLighting(zeus::skWhite);\n  } else {\n    SetCalculateLighting(true);\n  }\n\n  zeus::CColor col = x42c_color;\n  u8 alpha = GetModelAlphau8(mgr);\n  if (x402_27_noXrayModel && mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::XRay) {\n    alpha = 76;\n  }\n\n  if (alpha < 255) {\n    if (col.r() == 0.f && col.g() == 0.f && col.b() == 0.f) {\n      col = zeus::skWhite; /* Not being damaged */\n    }\n\n    if (x401_29_laggedBurnDeath) {\n      u32 stripedAlpha = 255;\n      if (alpha > 127) {\n        stripedAlpha = u32(alpha) * 2;\n      }\n      xb4_drawFlags = CModelFlags(3, 0, 3, zeus::CColor(0.f, float((stripedAlpha * stripedAlpha) >> 8) / 255.f));\n    } else if (x401_28_burning) {\n      xb4_drawFlags = CModelFlags(5, 0, 3, zeus::CColor(0.f, 1.f));\n    } else {\n      zeus::CColor col2 = col;\n      col2.a() = float(alpha) / 255.f;\n      xb4_drawFlags = CModelFlags(5, 0, 3, col2);\n    }\n  } else {\n    if (col.r() != 0.f || col.g() != 0.f || col.b() != 0.f) {\n      /* Being damaged */\n      zeus::CColor col2 = col;\n      col2.a() = 1.f;\n      xb4_drawFlags = CModelFlags(2, 0, 3, col2);\n    } else {\n      xb4_drawFlags = CModelFlags(0, 0, 3, zeus::skWhite);\n    }\n  }\n\n  CActor::PreRender(mgr, frustum);\n}\n\nvoid CPatterned::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) {\n  if (x402_29_drawParticles) {\n    if (x64_modelData && !x64_modelData->IsNull()) {\n      int mask = 0;\n      int target = 0;\n      mgr.GetCharacterRenderMaskAndTarget(x402_31_thawed, mask, target);\n      if (CAnimData* aData = x64_modelData->GetAnimationData()) {\n        aData->GetParticleDB().AddToRendererClippedMasked(frustum, mask, target);\n      }\n    }\n  }\n  CActor::AddToRenderer(frustum, mgr);\n}\n\nvoid CPatterned::RenderIceModelWithFlags(const CModelFlags& flags) const {\n  CModelFlags useFlags = flags;\n  useFlags.x1_matSetIdx = 0;\n  CAnimData* animData = x64_modelData->GetAnimationData();\n  if (CSkinnedModelWithAvgNormals* iceModel = animData->GetIceModel().GetObj()) {\n    animData->Render(*iceModel, useFlags, x510_vertexMorph.get(), iceModel->GetAveragedNormals());\n  }\n}\n\nvoid CPatterned::Render(CStateManager& mgr) {\n  int mask = 0;\n  int target = 0;\n  if (x402_29_drawParticles) {\n    mgr.GetCharacterRenderMaskAndTarget(x402_31_thawed, mask, target);\n    x64_modelData->GetAnimationData()->GetParticleDB().RenderSystemsToBeDrawnFirstMasked(mask, target);\n  }\n  if ((mgr.GetThermalDrawFlag() == EThermalDrawFlag::Cold && !x402_31_thawed) ||\n      (mgr.GetThermalDrawFlag() == EThermalDrawFlag::Hot && x402_31_thawed) ||\n      mgr.GetThermalDrawFlag() == EThermalDrawFlag::Bypass) {\n    if (x401_28_burning) {\n      CTexture* ashy = mgr.GetActorModelParticles()->GetAshyTexture(*this);\n      u8 alpha = GetModelAlphau8(mgr);\n      if (ashy != nullptr && ((!x401_29_laggedBurnDeath && alpha <= 255) || alpha <= 127)) {\n        if (xe5_31_pointGeneratorParticles) {\n          mgr.SetupParticleHook(*this);\n        }\n        zeus::CColor addColor;\n        if (x401_29_laggedBurnDeath) {\n          addColor = zeus::skClear;\n        } else {\n          addColor = mgr.GetThermalDrawFlag() == EThermalDrawFlag::Hot ? zeus::skWhite : zeus::skBlack;\n        }\n        x64_modelData->DisintegrateDraw(mgr, GetTransform(), *ashy, addColor,\n                                        float(alpha) * (x401_29_laggedBurnDeath ? 0.00787402f : 0.00392157f));\n        if (xe5_31_pointGeneratorParticles) {\n          CSkinnedModel::ClearPointGeneratorFunc();\n          mgr.GetActorModelParticles()->Render(mgr, *this);\n        }\n      } else {\n        CPhysicsActor::Render(mgr);\n      }\n    } else {\n      CPhysicsActor::Render(mgr);\n    }\n\n    if (x450_bodyController->IsFrozen() && !x401_28_burning) {\n      RenderIceModelWithFlags(CModelFlags(0, 0, 3, zeus::skWhite));\n    }\n  }\n\n  if (x402_29_drawParticles) {\n    x64_modelData->GetAnimationData()->GetParticleDB().RenderSystemsToBeDrawnLastMasked(mask, target);\n  }\n}\n\nvoid CPatterned::ThinkAboutMove(float dt) {\n  bool doMove = true;\n  if (!x328_25_verticalMovement && !x328_27_onGround) {\n    x310_moveVec.zeroOut();\n    doMove = false;\n  }\n\n  if (doMove && x39c_curPattern < x38c_patterns.size()) {\n    zeus::CVector3f frontVec = GetTransform().frontVector();\n    zeus::CVector3f x31cCpy = x31c_faceVec;\n    if (x31c_faceVec.magSquared() > 0.1f) {\n      x31cCpy.normalize();\n    }\n    float mag = frontVec.dot(x31cCpy);\n\n    switch (x3f8_moveState) {\n    case EMoveState::Zero:\n      if (!x328_26_solidCollision) {\n        break;\n      }\n      [[fallthrough]];\n    case EMoveState::One:\n      doMove = false;\n      if (mag > 0.85f) {\n        doMove = true;\n        x3f8_moveState = EMoveState::Two;\n        break;\n      }\n      x3f8_moveState = EMoveState::One;\n      break;\n    case EMoveState::Two:\n      x3f8_moveState = EMoveState::Three;\n      [[fallthrough]];\n    case EMoveState::Three:\n      doMove = true;\n      if (!x328_26_solidCollision) {\n        x3f8_moveState = EMoveState::Zero;\n        break;\n      }\n      if (mag > 0.9f) {\n        x3f8_moveState = EMoveState::Four;\n      }\n      break;\n    case EMoveState::Four:\n      x328_24_inPosition = true;\n      doMove = false;\n      x3f8_moveState = EMoveState::Zero;\n      break;\n    default:\n      break;\n    }\n  }\n\n  if (!x401_26_disableMove && doMove) {\n    const CBodyState* state = x450_bodyController->GetBodyStateInfo().GetCurrentState();\n    if (state->ApplyAnimationDeltas() && !zeus::close_enough(x2e0_destPos - GetTranslation(), {})) {\n      MoveToOR((x64_modelData->GetScale() * x434_posDelta) * x55c_moveScale, dt);\n    }\n  }\n\n  RotateToOR(x440_rotDelta, dt);\n}\n\nvoid CPatterned::PhazeOut(CStateManager& mgr) {\n  if (!x400_27_fadeToDeath) {\n    SendScriptMsgs(EScriptObjectState::DeathRattle, mgr, EScriptObjectMessage::None);\n  }\n  x401_27_phazingOut = true;\n  x450_bodyController->SetPlaybackRate(0.f);\n  x64_modelData->GetAnimationData()->GetParticleDB().SetUpdatesEnabled(false);\n}\n\nbool CPatterned::ApplyBoneTracking() const {\n  if (x400_25_alive) {\n    return x460_knockBackController.GetFlinchRemTime() <= 0.f;\n  }\n\n  return false;\n}\n\nvoid CPatterned::Initialize() {\n  if (cv_disableAi == nullptr) {\n    cv_disableAi = CVarManager::instance()->findOrMakeCVar(\"disableAi\"sv, \"Disables AI state machines\", false,\n                                                           CVar::EFlags::Cheat | CVar::EFlags::Game);\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CPatterned.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <vector>\n\n#include \"Runtime/Character/CBodyController.hpp\"\n#include \"Runtime/Character/CSteeringBehaviors.hpp\"\n#include \"Runtime/Graphics/CVertexMorphEffect.hpp\"\n#include \"Runtime/Particle/CElectricDescription.hpp\"\n#include \"Runtime/Particle/CGenDescription.hpp\"\n#include \"Runtime/World/CAi.hpp\"\n#include \"Runtime/World/CDamageInfo.hpp\"\n#include \"Runtime/World/CKnockBackController.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\n#include <zeus/CQuaternion.hpp>\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\n#ifndef DEFINE_PATTERNED\n#define DEFINE_PATTERNED(type) DEFINE_ENTITY static constexpr ECharacter CharacterType = ECharacter::type\n#endif\n\nnamespace metaforce {\nclass CPatternedInfo;\nclass CProjectileInfo;\nclass CPathFindSearch;\n\nusing CPatternedTryFunc = void (CPatterned::*)(CStateManager&, int);\n\nclass CPatterned : public CAi {\npublic:\n  static constexpr zeus::CColor skDamageColor{0.5f, 0.f, 0.f};\n  enum class ECharacter {\n    AtomicAlpha = 0,\n    AtomicBeta = 1,\n    Babygoth = 2,\n    Beetle = 3,\n    BloodFlower = 4,\n    Burrower = 5,\n    ChozoGhost = 6,\n    Drone = 7,\n    ElitePirate = 8,\n    EyeBall = 9,\n    FireFlea = 10,\n    Flaahgra = 11,\n    FlaahgraTentacle = 12,\n    FlickerBat = 13,\n    FlyingPirate = 14,\n    IceSheeegoth = 15,\n    JellyZap = 16,\n    Magdolite = 17,\n    Metaree = 18,\n    Metroid = 19,\n    MetroidBeta = 20,\n    MetroidPrimeExo = 21,\n    MetroidPrimeEssence = 22,\n    NewIntroBoss = 23,\n    Parasite = 24,\n    PuddleSpore = 27,\n    PuddleToad = 28,\n    Puffer = 29,\n    Ridley = 30,\n    Ripper = 31,\n    Seedling = 32,\n    SpacePirate = 34,\n    SpankWeed = 35,\n    PhazonHealingNodule = 35,\n    Thardus = 36,\n    ThardusRockProjectile = 37,\n    Tryclops = 38,\n    WarWasp = 39,\n    EnergyBall = 40\n  };\n  enum class EFlavorType { Zero = 0, One = 1, Two = 2 };\n  enum class EMovementType { Ground = 0, Flyer = 1 };\n  enum class EColliderType { Zero = 0, One = 1 };\n  enum class EPatternTranslate { RelativeStart, RelativePlayerStart, RelativePlayer, Absolute };\n  enum class EPatternOrient { StartToPlayer, StartToPlayerStart, ReversePlayerForward, Forward };\n  enum class EPatternFit { Zero, One };\n  enum class EMoveState { Zero, One, Two, Three, Four };\n  enum class EBehaviour { Zero };\n  enum class EBehaviourOrient { MoveDir, Constant, Destination };\n  enum class EBehaviourModifiers { Zero };\n  enum class EPatrolState { Invalid = -1, Patrol, Pause, Done };\n  enum class EAnimState { NotReady, Ready, Repeat, Over, Invalid = -1 };\n  class CPatternNode {\n    zeus::CVector3f x0_pos;\n    zeus::CVector3f xc_forward;\n    float x18_speed;\n    u8 x1c_behaviour;\n    u8 x1d_behaviourOrient;\n    u16 x1e_behaviourModifiers;\n    u32 x20_animation;\n\n  public:\n    CPatternNode(const zeus::CVector3f& pos, const zeus::CVector3f& forward, float speed, u8 behaviour,\n                 u8 behaviourOrient, u16 behaviourModifiers, u32 animation)\n    : x0_pos(pos)\n    , xc_forward(forward)\n    , x18_speed(speed)\n    , x1c_behaviour(behaviour)\n    , x1d_behaviourOrient(behaviourOrient)\n    , x1e_behaviourModifiers(behaviourModifiers)\n    , x20_animation(animation) {}\n    const zeus::CVector3f& GetPos() const { return x0_pos; }\n    const zeus::CVector3f& GetForward() const { return xc_forward; }\n    float GetSpeed() const { return x18_speed; }\n    u8 GetBehaviour() const { return x1c_behaviour; }\n    u8 GetBehaviourOrient() const { return x1d_behaviourOrient; }\n    u16 GetBehaviourModifiers() const { return x1e_behaviourModifiers; }\n  };\n\nprotected:\n  EPatrolState x2d8_patrolState = EPatrolState::Invalid;\n  TUniqueId x2dc_destObj = kInvalidUniqueId;\n  zeus::CVector3f x2e0_destPos;\n  zeus::CVector3f x2ec_reflectedDestPos;\n  float x2f8_waypointPauseRemTime = 0.f;\n  float x2fc_minAttackRange;\n  float x300_maxAttackRange;\n  float x304_averageAttackTime;\n  float x308_attackTimeVariation;\n  EBehaviourOrient x30c_behaviourOrient = EBehaviourOrient::MoveDir;\n  zeus::CVector3f x310_moveVec;\n  zeus::CVector3f x31c_faceVec;\n  bool x328_24_inPosition : 1 = false;\n  bool x328_25_verticalMovement : 1;\n  bool x328_26_solidCollision : 1 = false;\n  bool x328_27_onGround : 1;\n  bool x328_28_prevOnGround : 1 = true;\n  bool x328_29_noPatternShagging : 1 = false;\n  bool x328_30_lookAtDeathDir : 1 = true;\n  bool x328_31_energyAttractor : 1 = false;\n  bool x329_24_ : 1 = true;\n  EAnimState x32c_animState = EAnimState::NotReady;\n  CStateMachineState x330_stateMachineState;\n  ECharacter x34c_character;\n  zeus::CVector3f x350_patternStartPos;\n  zeus::CVector3f x35c_patternStartPlayerPos;\n  zeus::CVector3f x368_destWPDelta;\n  EPatternTranslate x374_patternTranslate = EPatternTranslate::RelativeStart;\n  EPatternOrient x378_patternOrient = EPatternOrient::ReversePlayerForward;\n  EPatternFit x37c_patternFit = EPatternFit::One;\n  EBehaviour x380_behaviour = EBehaviour::Zero;\n  EBehaviourModifiers x384_behaviourModifiers = EBehaviourModifiers::Zero;\n  s32 x388_anim;\n  std::vector<CPatternNode> x38c_patterns;\n  u32 x39c_curPattern = 0;\n  zeus::CVector3f x3a0_latestLeashPosition;\n  TUniqueId x3ac_lastPatrolDest = kInvalidUniqueId;\n  float x3b0_moveSpeed = 1.f;\n  float x3b4_speed;\n  float x3b8_turnSpeed;\n  float x3bc_detectionRange;\n  float x3c0_detectionHeightRange;\n  float x3c4_detectionAngle;\n  float x3c8_leashRadius;\n  float x3cc_playerLeashRadius;\n  float x3d0_playerLeashTime;\n  float x3d4_curPlayerLeashTime = 0.f;\n  float x3d8_xDamageThreshold;\n  float x3dc_frozenXDamageThreshold;\n  float x3e0_xDamageDelay;\n  float x3e4_lastHP = 0.f;\n  float x3e8_alphaDelta = 0.f;\n  float x3ec_pendingFireDamage = 0.f;\n  float x3f0_pendingShockDamage = 0.f;\n  float x3f4_burnThinkRateTimer = 0.f;\n  EMoveState x3f8_moveState = EMoveState::Zero;\n  EFlavorType x3fc_flavor;\n  bool x400_24_hitByPlayerProjectile : 1 = false;\n  bool x400_25_alive : 1 = true;\n  bool x400_26_ : 1 = false;\n  bool x400_27_fadeToDeath : 1 = false;\n  bool x400_28_pendingMassiveDeath : 1 = false;\n  bool x400_29_pendingMassiveFrozenDeath : 1 = false;\n  bool x400_30_patternShagged : 1 = false;\n  bool x400_31_isFlyer : 1;\n  uint32_t x401_24_pathOverCount : 2 = 0;\n  bool x401_26_disableMove : 1 = false;\n  bool x401_27_phazingOut : 1 = false;\n  bool x401_28_burning : 1 = false;\n  bool x401_29_laggedBurnDeath : 1 = false;\n  bool x401_30_pendingDeath : 1 = false;\n  bool x401_31_nextPendingShock : 1 = false;\n  bool x402_24_pendingShock : 1 = false;\n  bool x402_25_lostMassiveFrozenHP : 1 = false;\n  bool x402_26_dieIf80PercFrozen : 1 = false;\n  bool x402_27_noXrayModel : 1 = false;\n  bool x402_28_isMakingBigStrike : 1 = false;\n  bool x402_29_drawParticles : 1 = true;\n  bool x402_30_updateThermalFrozenState : 1;\n  bool x402_31_thawed : 1 = false;\n  bool x403_24_keepThermalVisorState : 1 = false;\n  bool x403_25_enableStateMachine : 1 = true;\n  bool x403_26_stateControlledMassiveDeath : 1 = true;\n  CDamageInfo x404_contactDamage;\n  float x420_curDamageRemTime = 0.f;\n  float x424_damageWaitTime;\n  float x428_damageCooldownTimer = -1.f;\n  zeus::CColor x42c_color = zeus::skBlack;\n  zeus::CColor x430_damageColor = skDamageColor;\n  zeus::CVector3f x434_posDelta;\n  zeus::CQuaternion x440_rotDelta;\n  std::unique_ptr<CBodyController> x450_bodyController;\n  u16 x454_deathSfx;\n  u16 x458_iceShatterSfx;\n  CSteeringBehaviors x45c_steeringBehaviors;\n\n  CKnockBackController x460_knockBackController;\n  zeus::CVector3f x4e4_latestPredictedTranslation;\n  float x4f0_predictedLeashTime = 0.f;\n  float x4f4_intoFreezeDur;\n  float x4f8_outofFreezeDur;\n  float x4fc_freezeDur;\n  float x500_preThinkDt = 0.f;\n  float x504_damageDur = 0.f;\n  EColliderType x508_colliderType;\n  float x50c_baseDamageMag;\n  std::shared_ptr<CVertexMorphEffect> x510_vertexMorph;\n  zeus::CVector3f x514_deathExplosionOffset;\n  std::optional<TLockedToken<CGenDescription>> x520_deathExplosionParticle;\n  std::optional<TLockedToken<CElectricDescription>> x530_deathExplosionElectric;\n  zeus::CVector3f x540_iceDeathExplosionOffset;\n  std::optional<TLockedToken<CGenDescription>> x54c_iceDeathExplosionParticle;\n  zeus::CVector3f x55c_moveScale = zeus::skOne3f;\n\n  void MakeThermalColdAndHot();\n  void UpdateThermalFrozenState(bool thawed);\n  void GenerateIceDeathExplosion(CStateManager& mgr);\n  void GenerateDeathExplosion(CStateManager& mgr);\n  void RenderIceModelWithFlags(const CModelFlags& flags) const;\n  TUniqueId GetWaypointForState(CStateManager& mgr, EScriptObjectState state, EScriptObjectMessage msg) const;\n  void UpdateActorKeyframe(CStateManager& mgr) const;\n  pas::EStepDirection GetStepDirection(const zeus::CVector3f& moveVec) const;\n  bool IsPatternObstructed(CStateManager& mgr, const zeus::CVector3f& p0, const zeus::CVector3f& p1) const;\n  void UpdateDest(CStateManager& mgr);\n  void ApproachDest(CStateManager& mgr);\n  std::pair<CScriptWaypoint*, CScriptWaypoint*> GetDestWaypoints(CStateManager& mgr) const;\n  zeus::CQuaternion FindPatternRotation(const zeus::CVector3f& dir) const;\n  zeus::CVector3f FindPatternDir(CStateManager& mgr) const;\n  void UpdatePatternDestPos(CStateManager& mgr);\n  void SetupPattern(CStateManager& mgr);\n  EScriptObjectState GetDesiredAttackState(CStateManager& mgr) const;\n  float GetAnimationDistance(const CPASAnimParmData& data) const;\n\npublic:\n  DEFINE_ENTITY\n  CPatterned(ECharacter character, TUniqueId uid, std::string_view name, EFlavorType flavor, const CEntityInfo& info,\n             const zeus::CTransform& xf, CModelData&& mData, const CPatternedInfo& pinfo,\n             CPatterned::EMovementType movement, EColliderType collider, EBodyType body, const CActorParameters& params,\n             EKnockBackVariant kbVariant);\n\n  void Accept(IVisitor&) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void PreThink(float dt, CStateManager& mgr) override {\n    x500_preThinkDt = dt;\n    CEntity::Think(x500_preThinkDt, mgr);\n  }\n  void Think(float, CStateManager&) override;\n  void PreRender(CStateManager&, const zeus::CFrustum&) override;\n  void AddToRenderer(const zeus::CFrustum&, CStateManager&) override;\n  void Render(CStateManager& mgr) override;\n\n  void CollidedWith(TUniqueId, const CCollisionInfoList&, CStateManager& mgr) override;\n  void Touch(CActor& act, CStateManager& mgr) override;\n  std::optional<zeus::CAABox> GetTouchBounds() const override;\n  bool CanRenderUnsorted(const CStateManager& mgr) const override;\n  zeus::CVector3f GetOrbitPosition(const CStateManager& mgr) const override { return GetAimPosition(mgr, 0.f); }\n  zeus::CVector3f GetAimPosition(const CStateManager& mgr, float) const override;\n  zeus::CTransform GetLctrTransform(std::string_view name) const;\n  zeus::CTransform GetLctrTransform(CSegId id) const;\n\n  bool ApplyBoneTracking() const;\n\n  void DeathDelete(CStateManager& mgr);\n  void Death(CStateManager& mgr, const zeus::CVector3f& direction, EScriptObjectState state) override;\n  void KnockBack(const zeus::CVector3f&, CStateManager&, const CDamageInfo& info, EKnockBackType type, bool inDeferred,\n                 float magnitude) override;\n  void TakeDamage(const zeus::CVector3f&, float arg) override;\n  bool FixedRandom(CStateManager&, float arg) override;\n  bool Random(CStateManager&, float arg) override;\n  bool CodeTrigger(CStateManager&, float arg) override;\n  bool FixedDelay(CStateManager&, float arg) override;\n  bool RandomDelay(CStateManager&, float arg) override;\n  bool Delay(CStateManager&, float arg) override;\n  bool PatrolPathOver(CStateManager&, float arg) override;\n  bool Stuck(CStateManager&, float arg) override;\n  bool AnimOver(CStateManager&, float arg) override;\n  bool InPosition(CStateManager&, float arg) override;\n  bool HasPatrolPath(CStateManager& mgr, float arg) override;\n  bool Attacked(CStateManager&, float arg) override;\n  bool PatternShagged(CStateManager&, float arg) override;\n  bool PatternOver(CStateManager&, float arg) override;\n  bool HasRetreatPattern(CStateManager& mgr, float arg) override;\n  bool HasAttackPattern(CStateManager& mgr, float arg) override;\n  bool NoPathNodes(CStateManager&, float arg) override;\n  bool PathShagged(CStateManager&, float arg) override;\n  bool PathFound(CStateManager&, float arg) override;\n  bool PathOver(CStateManager&, float arg) override;\n  bool Landed(CStateManager&, float arg) override;\n  bool PlayerSpot(CStateManager&, float arg) override;\n  bool SpotPlayer(CStateManager&, float arg) override;\n  bool Leash(CStateManager&, float arg) override;\n  bool InDetectionRange(CStateManager&, float arg) override;\n  bool InMaxRange(CStateManager&, float arg) override;\n  bool TooClose(CStateManager&, float arg) override;\n  bool InRange(CStateManager&, float arg) override;\n  bool OffLine(CStateManager&, float arg) override;\n  bool Default(CStateManager&, float arg) override { return true; }\n  void PathFind(CStateManager&, EStateMsg msg, float dt) override;\n  void Dead(CStateManager&, EStateMsg msg, float dt) override;\n  void TargetPlayer(CStateManager&, EStateMsg msg, float dt) override;\n  void TargetPatrol(CStateManager&, EStateMsg msg, float dt) override;\n  void FollowPattern(CStateManager&, EStateMsg msg, float dt) override;\n  void Patrol(CStateManager&, EStateMsg msg, float dt) override;\n  void Start(CStateManager&, EStateMsg msg, float dt) override {}\n\n  void TryCommand(CStateManager& mgr, pas::EAnimationState state, CPatternedTryFunc func, int arg);\n  void TryLoopReaction(CStateManager& mgr, int arg);\n  void TryProjectileAttack(CStateManager& mgr, int arg);\n  void TryMeleeAttack_TargetPos(CStateManager& mgr, int arg);\n  void TryMeleeAttack(CStateManager& mgr, int arg);\n  void TryGenerateNoXf(CStateManager& mgr, int arg);\n  void TryGenerate(CStateManager& mgr, int arg);\n  void TryJump(CStateManager& mgr, int arg);\n  void TryTurn(CStateManager& mgr, int arg);\n  void TryGetUp(CStateManager& mgr, int arg);\n  void TryTaunt(CStateManager& mgr, int arg);\n  void TryJumpInLoop(CStateManager& mgr, int arg);\n  void TryDodge(CStateManager& mgr, int arg);\n  void TryRollingDodge(CStateManager& mgr, int arg);\n  void TryBreakDodge(CStateManager& mgr, int arg);\n  void TryCover(CStateManager& mgr, int arg);\n  void TryWallHang(CStateManager& mgr, int arg);\n  void TryKnockBack(CStateManager& mgr, int arg);\n  void TryKnockBack_Front(CStateManager& mgr, int arg);\n  void TryGenerateDeactivate(CStateManager& mgr, int arg);\n  void TryStep(CStateManager& mgr, int arg);\n  void TryScripted(CStateManager& mgr, int arg);\n\n  virtual bool KnockbackWhenFrozen() const { return true; }\n  virtual void MassiveDeath(CStateManager& mgr);\n  virtual void MassiveFrozenDeath(CStateManager& mgr);\n  virtual void Burn(float duration, float damage);\n  virtual void Shock(CStateManager& mgr, float duration, float damage);\n  virtual void Freeze(CStateManager& mgr, const zeus::CVector3f& pos, const zeus::CUnitVector3f& dir, float frozenDur);\n  virtual void ThinkAboutMove(float);\n  virtual CPathFindSearch* GetSearchPath() { return nullptr; }\n  virtual CDamageInfo GetContactDamage() const { return x404_contactDamage; }\n  virtual u8 GetModelAlphau8(const CStateManager&) const { return u8(x42c_color.a() * 255); }\n  virtual bool IsOnGround() const { return x328_27_onGround; }\n  virtual float GetGravityConstant() const { return CPhysicsActor::GravityConstant(); }\n  virtual CProjectileInfo* GetProjectileInfo() { return nullptr; }\n  virtual void PhazeOut(CStateManager&);\n  virtual const std::optional<TLockedToken<CGenDescription>>& GetDeathExplosionParticle() const {\n    return x520_deathExplosionParticle;\n  }\n  float GetDamageDuration() const { return x504_damageDur; }\n  zeus::CVector3f GetGunEyePos() const;\n  bool IsEnergyAttractor() const { return x328_31_energyAttractor; }\n  bool IsAlive() const { return x400_25_alive; }\n\n  void BuildBodyController(EBodyType);\n  const CBodyController* GetBodyController() const { return x450_bodyController.get(); }\n  CBodyController* GetBodyController() { return x450_bodyController.get(); }\n  const CKnockBackController& GetKnockBackController() const { return x460_knockBackController; }\n  CKnockBackController& GetKnockBackController() { return x460_knockBackController; }\n  void SetupPlayerCollision(bool);\n  CGameProjectile* LaunchProjectile(const zeus::CTransform& gunXf, CStateManager& mgr, int maxAllowed,\n                                    EProjectileAttrib attrib, bool playerHoming,\n                                    const std::optional<TLockedToken<CGenDescription>>& visorParticle, u16 visorSfx,\n                                    bool sendCollideMsg, const zeus::CVector3f& scale);\n  void DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) override;\n\n  const zeus::CVector3f& GetDestPos() const { return x2e0_destPos; }\n  void SetDestPos(const zeus::CVector3f& pos) { x2e0_destPos = pos; }\n  void UpdateAlphaDelta(float dt, CStateManager& mgr);\n  void SetModelAlpha(float a) { x42c_color.a() = a; }\n  float CalcDyingThinkRate() const;\n  void UpdateDamageColor(float dt);\n  CScriptCoverPoint* GetCoverPoint(CStateManager& mgr, TUniqueId id) const;\n  void SetCoverPoint(CScriptCoverPoint* cp, TUniqueId& id);\n  void ReleaseCoverPoint(CStateManager& mgr, TUniqueId& id) const;\n\n  bool MadeSolidCollision() const { return x328_26_solidCollision; }\n  bool IsMakingBigStrike() const { return x402_28_isMakingBigStrike; }\n\n  // region Casting Functions\n\n  template <class T>\n  static T* CastTo(CEntity* ent) {\n    if (TCastToPtr<CPatterned> patterned = ent)\n      return CastTo<T>(patterned.GetPtr());\n    return nullptr;\n  }\n\n  template <class T>\n  static const T* CastTo(const CEntity* ent) {\n    if (TCastToConstPtr<CPatterned> patterned = ent)\n      return CastTo<T>(patterned.GetPtr());\n    return nullptr;\n  }\n\n  template <class T>\n  static T* CastTo(CPatterned* patterned) {\n    if (patterned->x34c_character == T::CharacterType)\n      return static_cast<T*>(patterned);\n    return nullptr;\n  }\n\n  template <class T>\n  static const T* CastTo(const CPatterned* patterned) {\n    if (patterned->x34c_character == T::CharacterType)\n      return static_cast<const T*>(patterned);\n    return nullptr;\n  }\n\n  static void Initialize();\n  // endregion\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CPatternedInfo.cpp",
    "content": "#include \"Runtime/World/CPatternedInfo.hpp\"\n\n#include \"Runtime/Audio/CSfxManager.hpp\"\n\nnamespace metaforce {\n\nCPatternedInfo::CPatternedInfo(CInputStream& in, u32 pcount)\n: x0_mass(in.ReadFloat())\n, x4_speed(in.ReadFloat())\n, x8_turnSpeed(in.ReadFloat())\n, xc_detectionRange(in.ReadFloat())\n, x10_detectionHeightRange(in.ReadFloat())\n, x14_dectectionAngle(in.ReadFloat())\n, x18_minAttackRange(in.ReadFloat())\n, x1c_maxAttackRange(in.ReadFloat())\n, x20_averageAttackTime(in.ReadFloat())\n, x24_attackTimeVariation(in.ReadFloat())\n, x28_leashRadius(in.ReadFloat())\n, x2c_playerLeashRadius(in.ReadFloat())\n, x30_playerLeashTime(in.ReadFloat())\n, x34_contactDamageInfo(in)\n, x50_damageWaitTime(in.ReadFloat())\n, x54_healthInfo(in)\n, x5c_damageVulnerability(in)\n, xc4_halfExtent(in.ReadFloat())\n, xc8_height(in.ReadFloat())\n, xcc_bodyOrigin(in.Get<zeus::CVector3f>())\n, xd8_stepUpHeight(in.ReadFloat())\n, xdc_xDamage(in.ReadFloat())\n, xe0_frozenXDamage(in.ReadFloat())\n, xe4_xDamageDelay(in.ReadFloat())\n, xe8_deathSfx(CSfxManager::TranslateSFXID(in.ReadLong()))\n, xec_animParams(in)\n, xf8_active(in.ReadBool())\n, xfc_stateMachineId(in.Get<CAssetId>())\n, x100_intoFreezeDur(in.ReadFloat())\n, x104_outofFreezeDur(in.ReadFloat())\n, x108_freezeDur(in.ReadFloat())\n, x10c_pathfindingIndex(in.ReadLong())\n, x110_particle1Scale(in.Get<zeus::CVector3f>())\n, x11c_particle1(in)\n, x120_electric(in) {\n  if (pcount >= 36)\n    x124_particle2Scale = in.Get<zeus::CVector3f>();\n  if (pcount >= 37)\n    x130_particle2 = in.Get<CAssetId>();\n  if (pcount >= 38)\n    x134_iceShatterSfx = CSfxManager::TranslateSFXID(in.ReadLong());\n}\n\nstd::pair<bool, u32> CPatternedInfo::HasCorrectParameterCount(CInputStream& in) {\n  u32 pcount = in.ReadLong();\n  return {(pcount >= 35 && pcount <= 38), pcount};\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CPatternedInfo.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/World/CAnimationParameters.hpp\"\n#include \"Runtime/World/CDamageInfo.hpp\"\n#include \"Runtime/World/CDamageVulnerability.hpp\"\n#include \"Runtime/World/CHealthInfo.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\n\nclass CPatternedInfo {\n  friend class CPatterned;\n  float x0_mass;\n  float x4_speed;\n  float x8_turnSpeed;\n  float xc_detectionRange;\n  float x10_detectionHeightRange;\n  float x14_dectectionAngle;\n  float x18_minAttackRange;\n  float x1c_maxAttackRange;\n  float x20_averageAttackTime;\n  float x24_attackTimeVariation;\n  float x28_leashRadius;\n  float x2c_playerLeashRadius;\n  float x30_playerLeashTime;\n  CDamageInfo x34_contactDamageInfo;\n  float x50_damageWaitTime;\n  CHealthInfo x54_healthInfo;\n  CDamageVulnerability x5c_damageVulnerability;\n  float xc4_halfExtent;\n  float xc8_height;\n  zeus::CVector3f xcc_bodyOrigin;\n  float xd8_stepUpHeight;\n  float xdc_xDamage;\n  float xe0_frozenXDamage;\n  float xe4_xDamageDelay;\n  u16 xe8_deathSfx;\n  CAnimationParameters xec_animParams;\n  bool xf8_active;\n  CAssetId xfc_stateMachineId;\n  float x100_intoFreezeDur;\n  float x104_outofFreezeDur;\n  float x108_freezeDur;\n\n  u32 x10c_pathfindingIndex;\n\n  zeus::CVector3f x110_particle1Scale;\n  CAssetId x11c_particle1;\n  CAssetId x120_electric;\n  zeus::CVector3f x124_particle2Scale;\n  CAssetId x130_particle2;\n\n  u16 x134_iceShatterSfx = 0xffff;\n\npublic:\n  CPatternedInfo(CInputStream& in, u32 pcount);\n  static std::pair<bool, u32> HasCorrectParameterCount(CInputStream& in);\n\n  float GetTurnSpeed() const { return x8_turnSpeed; }\n  float GetDetectionHeightRange() const { return x10_detectionHeightRange; }\n  const CHealthInfo& GetHealthInfo() const { return x54_healthInfo; }\n  const CDamageVulnerability& GetDamageVulnerability() const { return x5c_damageVulnerability; }\n  float GetHalfExtent() const { return xc4_halfExtent; }\n  float GetHeight() const { return xc8_height; }\n  zeus::CVector3f GetBodyOrigin() const { return xcc_bodyOrigin; }\n  CAnimationParameters& GetAnimationParameters() { return xec_animParams; }\n  const CAnimationParameters& GetAnimationParameters() const { return xec_animParams; }\n  u32 GetPathfindingIndex() const { return x10c_pathfindingIndex; }\n  bool GetActive() const { return xf8_active; }\n  void SetActive(bool active) { xf8_active = active; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CPhysicsActor.cpp",
    "content": "#include \"Runtime/World/CPhysicsActor.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCPhysicsActor::CPhysicsActor(TUniqueId uid, bool active, std::string_view name, const CEntityInfo& info,\n                             const zeus::CTransform& xf, CModelData&& mData, const CMaterialList& matList,\n                             const zeus::CAABox& box, const SMoverData& moverData, const CActorParameters& actorParms,\n                             float stepUp, float stepDown)\n: CActor(uid, active, name, info, xf, std::move(mData), matList, actorParms, kInvalidUniqueId)\n, xe8_mass(moverData.x30_mass)\n, xec_massRecip(moverData.x30_mass > 0.f ? 1.f / moverData.x30_mass : 1.f)\n, x150_momentum(moverData.x18_momentum)\n, x1a4_baseBoundingBox(box)\n, x1c0_collisionPrimitive(box, matList)\n, x1f4_lastNonCollidingState(xf.origin, xf.buildMatrix3f())\n, x23c_stepUpHeight(stepUp)\n, x240_stepDownHeight(stepDown) {\n  SetMass(moverData.x30_mass);\n  MoveCollisionPrimitive(zeus::skZero3f);\n  SetVelocityOR(moverData.x0_velocity);\n  SetAngularVelocityOR(moverData.xc_angularVelocity);\n  ComputeDerivedQuantities();\n}\n\nvoid CPhysicsActor::Render(CStateManager& mgr) { CActor::Render(mgr); }\n\nzeus::CVector3f CPhysicsActor::GetOrbitPosition(const CStateManager&) const { return GetBoundingBox().center(); }\n\nzeus::CVector3f CPhysicsActor::GetAimPosition(const CStateManager&, float dt) const {\n  if (dt <= 0.0) {\n    return GetBoundingBox().center();\n  }\n  zeus::CVector3f trans = PredictMotion(dt).x0_translation;\n  return GetBoundingBox().center() + trans;\n}\n\nvoid CPhysicsActor::CollidedWith(TUniqueId, const CCollisionInfoList&, CStateManager&) {}\n\nconst CCollisionPrimitive* CPhysicsActor::GetCollisionPrimitive() const { return &x1c0_collisionPrimitive; }\n\nzeus::CTransform CPhysicsActor::GetPrimitiveTransform() const {\n  return zeus::CTransform::Translate(x34_transform.origin + x1e8_primitiveOffset);\n}\n\nfloat CPhysicsActor::GetStepUpHeight() const { return x23c_stepUpHeight; }\n\nfloat CPhysicsActor::GetStepDownHeight() const { return x240_stepDownHeight; }\n\nfloat CPhysicsActor::GetWeight() const { return CPhysicsActor::GravityConstant() * xe8_mass; }\n\nvoid CPhysicsActor::SetPrimitiveOffset(const zeus::CVector2f& offset) { x1e8_primitiveOffset = offset; }\n\nvoid CPhysicsActor::MoveCollisionPrimitive(const zeus::CVector3f& offset) { x1e8_primitiveOffset = offset; }\n\nvoid CPhysicsActor::SetBoundingBox(const zeus::CAABox& box) {\n  x1a4_baseBoundingBox = box;\n  MoveCollisionPrimitive(zeus::skZero3f);\n}\n\nzeus::CAABox CPhysicsActor::GetMotionVolume(float dt) const {\n  zeus::CAABox aabox = GetCollisionPrimitive()->CalculateAABox(GetPrimitiveTransform());\n  zeus::CVector3f velocity = CalculateNewVelocityWR_UsingImpulses();\n\n  const zeus::CVector3f dv = (dt * velocity);\n  aabox.accumulateBounds(aabox.max + dv);\n  aabox.accumulateBounds(aabox.min + dv);\n\n  float up = GetStepUpHeight();\n  up = zeus::max(up, 0.f);\n  aabox.accumulateBounds(aabox.max + zeus::CVector3f(0.5f, 0.5f, up + 1.f));\n\n  float down = GetStepDownHeight();\n  down = zeus::max(down, 0.f);\n  aabox.accumulateBounds(aabox.min - zeus::CVector3f(0.5f, 0.5f, down + 1.5f));\n  return aabox;\n}\n\nzeus::CVector3f CPhysicsActor::CalculateNewVelocityWR_UsingImpulses() const {\n  return x138_velocity + xec_massRecip * (x168_impulse + x18c_moveImpulse);\n}\n\nzeus::CAABox CPhysicsActor::GetBoundingBox() const {\n  return {x1a4_baseBoundingBox.min + x1e8_primitiveOffset + x34_transform.origin,\n          x1a4_baseBoundingBox.max + x1e8_primitiveOffset + x34_transform.origin};\n}\n\nconst zeus::CAABox& CPhysicsActor::GetBaseBoundingBox() const { return x1a4_baseBoundingBox; }\n\nvoid CPhysicsActor::AddMotionState(const CMotionState& mst) {\n  zeus::CNUQuaternion q{x34_transform.buildMatrix3f()};\n  q += mst.xc_orientation;\n  zeus::CQuaternion quat = zeus::CQuaternion::fromNUQuaternion(q);\n  //   if (TCastToPtr<CPlayer>(this)) {\n  //    spdlog::debug(\"ADD {}\\n\", mst.x0_translation);\n  //  }\n  SetTransform(zeus::CTransform(quat, x34_transform.origin));\n\n  SetTranslation(x34_transform.origin + mst.x0_translation);\n\n  xfc_constantForce += mst.x1c_velocity;\n  x108_angularMomentum += mst.x28_angularMomentum;\n\n  ComputeDerivedQuantities();\n}\n\nCMotionState CPhysicsActor::GetMotionState() const {\n  return {x34_transform.origin, {x34_transform.buildMatrix3f()}, xfc_constantForce, x108_angularMomentum};\n}\n\nvoid CPhysicsActor::SetMotionState(const CMotionState& mst) {\n  SetTransform(zeus::CTransform(zeus::CQuaternion::fromNUQuaternion(mst.xc_orientation), x34_transform.origin));\n  SetTranslation(mst.x0_translation);\n\n  xfc_constantForce = mst.x1c_velocity;\n  x108_angularMomentum = mst.x28_angularMomentum;\n  ComputeDerivedQuantities();\n}\n\nvoid CPhysicsActor::SetInertiaTensorScalar(float tensor) {\n  if (tensor <= 0.0f) {\n    tensor = 1.0f;\n  }\n  xf0_inertiaTensor = tensor;\n  xf4_inertiaTensorRecip = 1.0f / tensor;\n}\n\nvoid CPhysicsActor::SetMass(float mass) {\n  xe8_mass = mass;\n  float tensor = 1.0f;\n  if (mass > 0.0f) {\n    tensor = 1.0f / mass;\n  }\n\n  xec_massRecip = tensor;\n  SetInertiaTensorScalar(0.16666667f * mass);\n}\n\nvoid CPhysicsActor::SetAngularVelocityOR(const zeus::CAxisAngle& angVel) {\n  x144_angularVelocity = x34_transform.rotate(angVel);\n  x108_angularMomentum = xf0_inertiaTensor * x144_angularVelocity;\n}\n\nzeus::CAxisAngle CPhysicsActor::GetAngularVelocityOR() const {\n  return x34_transform.transposeRotate(x144_angularVelocity);\n}\n\nvoid CPhysicsActor::SetAngularVelocityWR(const zeus::CAxisAngle& angVel) {\n  x144_angularVelocity = angVel;\n  x108_angularMomentum = xf0_inertiaTensor * x144_angularVelocity;\n}\n\nvoid CPhysicsActor::SetVelocityWR(const zeus::CVector3f& vel) {\n  x138_velocity = vel;\n  xfc_constantForce = xe8_mass * x138_velocity;\n}\n\nvoid CPhysicsActor::SetVelocityOR(const zeus::CVector3f& vel) { SetVelocityWR(x34_transform.rotate(vel)); }\n\nzeus::CVector3f CPhysicsActor::GetTotalForcesWR() const { return x15c_force + x150_momentum; }\n\nvoid CPhysicsActor::RotateInOneFrameOR(const zeus::CQuaternion& q, float d) {\n  x198_moveAngularImpulse += GetRotateToORAngularMomentumWR(q, d);\n}\n\nvoid CPhysicsActor::MoveInOneFrameOR(const zeus::CVector3f& trans, float d) {\n  x18c_moveImpulse += GetMoveToORImpulseWR(trans, d);\n}\n\nvoid CPhysicsActor::RotateToOR(const zeus::CQuaternion& q, float d) {\n  x108_angularMomentum = GetRotateToORAngularMomentumWR(q, d);\n  ComputeDerivedQuantities();\n}\n\nvoid CPhysicsActor::MoveToOR(const zeus::CVector3f& trans, float d) {\n  xfc_constantForce = GetMoveToORImpulseWR(trans, d);\n  ComputeDerivedQuantities();\n}\n\nvoid CPhysicsActor::MoveToInOneFrameWR(const zeus::CVector3f& trans, float d) {\n  x18c_moveImpulse += (1.f / d) * xe8_mass * (trans - x34_transform.origin);\n}\n\nvoid CPhysicsActor::MoveToWR(const zeus::CVector3f& trans, float d) {\n  xfc_constantForce = (1.f / d) * xe8_mass * (trans - x34_transform.origin);\n  ComputeDerivedQuantities();\n}\n\nzeus::CAxisAngle CPhysicsActor::GetRotateToORAngularMomentumWR(const zeus::CQuaternion& q, float d) const {\n  if (q.w() > 0.99999976) {\n    return zeus::CAxisAngle();\n  }\n  return (xf0_inertiaTensor *\n          (((2.f * std::acos(q.w())) * (1.f / d)) * x34_transform.rotate(q.getImaginary()).normalized()));\n}\n\nzeus::CVector3f CPhysicsActor::GetMoveToORImpulseWR(const zeus::CVector3f& trans, float d) const {\n  return (1.f / d) * xe8_mass * x34_transform.rotate(trans);\n}\n\nvoid CPhysicsActor::ClearImpulses() {\n  x18c_moveImpulse = x168_impulse = zeus::skZero3f;\n  x198_moveAngularImpulse = x180_angularImpulse = zeus::CAxisAngle();\n}\n\nvoid CPhysicsActor::ClearForcesAndTorques() {\n  x18c_moveImpulse = x168_impulse = x15c_force = zeus::skZero3f;\n  x198_moveAngularImpulse = x180_angularImpulse = x174_torque = zeus::CAxisAngle();\n}\n\nvoid CPhysicsActor::Stop() {\n  ClearForcesAndTorques();\n  xfc_constantForce = zeus::skZero3f;\n  x108_angularMomentum = zeus::CAxisAngle();\n  ComputeDerivedQuantities();\n}\n\nvoid CPhysicsActor::ComputeDerivedQuantities() {\n  x138_velocity = xec_massRecip * xfc_constantForce;\n  x114_ = x34_transform.buildMatrix3f();\n  x144_angularVelocity = xf4_inertiaTensorRecip * x108_angularMomentum;\n}\n\nbool CPhysicsActor::WillMove(const CStateManager&) const {\n  return !zeus::close_enough(zeus::skZero3f, x138_velocity) || !zeus::close_enough(zeus::skZero3f, x168_impulse) ||\n         !zeus::close_enough(zeus::skZero3f, x174_torque) || !zeus::close_enough(zeus::skZero3f, x18c_moveImpulse) ||\n         !zeus::close_enough(zeus::skZero3f, x144_angularVelocity) ||\n         !zeus::close_enough(zeus::skZero3f, x180_angularImpulse) ||\n         !zeus::close_enough(zeus::skZero3f, x198_moveAngularImpulse) ||\n         !zeus::close_enough(zeus::skZero3f, GetTotalForcesWR());\n}\n\nvoid CPhysicsActor::SetPhysicsState(const CPhysicsState& state) {\n  SetTranslation(state.GetTranslation());\n  SetTransform(zeus::CTransform(state.GetOrientation(), x34_transform.origin));\n\n  xfc_constantForce = state.GetConstantForceWR();\n  x108_angularMomentum = state.GetAngularMomentumWR();\n  x150_momentum = state.GetMomentumWR();\n  x15c_force = state.GetForceWR();\n  x168_impulse = state.GetImpulseWR();\n  x174_torque = state.GetTorque();\n  x180_angularImpulse = state.GetAngularImpulseWR();\n  ComputeDerivedQuantities();\n}\n\nCPhysicsState CPhysicsActor::GetPhysicsState() const {\n  return {x34_transform.origin, {x34_transform.buildMatrix3f()},\n          xfc_constantForce,    x108_angularMomentum,\n          x150_momentum,        x15c_force,\n          x168_impulse,         x174_torque,\n          x180_angularImpulse};\n}\n\nCMotionState CPhysicsActor::PredictMotion_Internal(float dt) const {\n  if (xf8_25_angularEnabled) {\n    return PredictLinearMotion(dt);\n  }\n\n  CMotionState msl = PredictLinearMotion(dt);\n  CMotionState msa = PredictAngularMotion(dt);\n  return {msl.x0_translation, msa.xc_orientation, msl.x1c_velocity, msa.x28_angularMomentum};\n}\n\nCMotionState CPhysicsActor::PredictMotion(float dt) const {\n  CMotionState msl = PredictLinearMotion(dt);\n  CMotionState msa = PredictAngularMotion(dt);\n  return {msl.x0_translation, msa.xc_orientation, msl.x1c_velocity, msa.x28_angularMomentum};\n}\n\nCMotionState CPhysicsActor::PredictLinearMotion(float dt) const {\n  zeus::CVector3f velocity = CalculateNewVelocityWR_UsingImpulses();\n  return {dt * velocity, zeus::CNUQuaternion(0.f, zeus::skZero3f), (dt * (x15c_force + x150_momentum)) + x168_impulse,\n          zeus::CAxisAngle()};\n}\n\nCMotionState CPhysicsActor::PredictAngularMotion(float dt) const {\n  const zeus::CVector3f v1 = xf4_inertiaTensorRecip * (x180_angularImpulse + x198_moveAngularImpulse);\n  zeus::CNUQuaternion q = 0.5f * zeus::CNUQuaternion(0.f, x144_angularVelocity.getVector() + v1);\n  CMotionState ret = {zeus::skZero3f, (q * zeus::CNUQuaternion(x34_transform.buildMatrix3f())) * dt, zeus::skZero3f,\n                      (x174_torque * dt) + x180_angularImpulse};\n  return ret;\n}\n\nvoid CPhysicsActor::ApplyForceOR(const zeus::CVector3f& force, const zeus::CAxisAngle& torque) {\n  x15c_force += x34_transform.rotate(force);\n  x174_torque += x34_transform.rotate(torque);\n}\n\nvoid CPhysicsActor::ApplyForceWR(const zeus::CVector3f& force, const zeus::CAxisAngle& torque) {\n  x15c_force += force;\n  x174_torque += torque;\n}\n\nvoid CPhysicsActor::ApplyImpulseOR(const zeus::CVector3f& impulse, const zeus::CAxisAngle& angle) {\n  x168_impulse += x34_transform.rotate(impulse);\n  x180_angularImpulse += x34_transform.rotate(angle);\n}\n\nvoid CPhysicsActor::ApplyImpulseWR(const zeus::CVector3f& impulse, const zeus::CAxisAngle& angleImp) {\n  x168_impulse += impulse;\n  x180_angularImpulse += angleImp;\n}\n\nvoid CPhysicsActor::ApplyTorqueWR(const zeus::CVector3f& torque) { x174_torque += torque; }\n\nvoid CPhysicsActor::UseCollisionImpulses() {\n  xfc_constantForce += x168_impulse;\n  x108_angularMomentum += x180_angularImpulse;\n\n  x168_impulse = zeus::skZero3f;\n  x180_angularImpulse = zeus::CAxisAngle();\n  ComputeDerivedQuantities();\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CPhysicsActor.hpp",
    "content": "#pragma once\n\n#include <optional>\n\n#include \"Runtime/Collision/CCollidableAABox.hpp\"\n#include \"Runtime/World/CActor.hpp\"\n\n#include <zeus/CAxisAngle.hpp>\n#include <zeus/CQuaternion.hpp>\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CCollisionInfoList;\nstruct SMoverData;\n\nstruct SMoverData {\n  zeus::CVector3f x0_velocity;\n  zeus::CAxisAngle xc_angularVelocity;\n  zeus::CVector3f x18_momentum;\n  zeus::CAxisAngle x24_;\n  float x30_mass;\n\n  explicit SMoverData(float mass) : x30_mass(mass) {}\n};\n\nstruct CMotionState {\n  zeus::CVector3f x0_translation;\n  zeus::CNUQuaternion xc_orientation;\n  zeus::CVector3f x1c_velocity;\n  zeus::CAxisAngle x28_angularMomentum;\n  CMotionState(const zeus::CVector3f& origin, const zeus::CNUQuaternion& orientation, const zeus::CVector3f& velocity,\n               const zeus::CAxisAngle& angle)\n  : x0_translation(origin), xc_orientation(orientation), x1c_velocity(velocity), x28_angularMomentum(angle) {}\n  CMotionState(const zeus::CVector3f& origin, const zeus::CNUQuaternion& orientation)\n  : x0_translation(origin), xc_orientation(orientation) {}\n};\n\nclass CPhysicsState {\n  zeus::CVector3f x0_translation;\n  zeus::CQuaternion xc_orientation;\n  zeus::CVector3f x1c_constantForce;\n  zeus::CAxisAngle x28_angularMomentum;\n  zeus::CVector3f x34_momentum;\n  zeus::CVector3f x40_force;\n  zeus::CVector3f x4c_impulse;\n  zeus::CAxisAngle x58_torque;\n  zeus::CAxisAngle x64_angularImpulse;\n\npublic:\n  CPhysicsState(const zeus::CVector3f& translation, const zeus::CQuaternion& orient,\n                const zeus::CVector3f& constantForce, const zeus::CAxisAngle& angularMomentum,\n                const zeus::CVector3f& momentum, const zeus::CVector3f& force, const zeus::CVector3f& impulse,\n                const zeus::CAxisAngle& torque, const zeus::CAxisAngle& angularImpulse)\n  : x0_translation(translation)\n  , xc_orientation(orient)\n  , x1c_constantForce(constantForce)\n  , x28_angularMomentum(angularMomentum)\n  , x34_momentum(momentum)\n  , x40_force(force)\n  , x4c_impulse(impulse)\n  , x58_torque(torque)\n  , x64_angularImpulse(angularImpulse) {}\n\n  void SetTranslation(const zeus::CVector3f& tr) { x0_translation = tr; }\n  void SetOrientation(const zeus::CQuaternion& orient) { xc_orientation = orient; }\n  const zeus::CQuaternion& GetOrientation() const { return xc_orientation; }\n  const zeus::CVector3f& GetTranslation() const { return x0_translation; }\n  const zeus::CVector3f& GetConstantForceWR() const { return x1c_constantForce; }\n  const zeus::CAxisAngle& GetAngularMomentumWR() const { return x28_angularMomentum; }\n  const zeus::CVector3f& GetMomentumWR() const { return x34_momentum; }\n  const zeus::CVector3f& GetForceWR() const { return x40_force; }\n  const zeus::CVector3f& GetImpulseWR() const { return x4c_impulse; }\n  const zeus::CAxisAngle& GetTorque() const { return x58_torque; }\n  const zeus::CAxisAngle& GetAngularImpulseWR() const { return x64_angularImpulse; }\n};\n\nclass CPhysicsActor : public CActor {\n  friend class CGroundMovement;\n\nprotected:\n  float xe8_mass;\n  float xec_massRecip;\n  float xf0_inertiaTensor = 0.f;\n  float xf4_inertiaTensorRecip = 0.f;\n  bool xf8_24_movable : 1 = true;\n  bool xf8_25_angularEnabled : 1 = false;\n  bool xf9_standardCollider = false;\n  zeus::CVector3f xfc_constantForce;\n  zeus::CAxisAngle x108_angularMomentum;\n  zeus::CMatrix3f x114_;\n  zeus::CVector3f x138_velocity;\n  zeus::CAxisAngle x144_angularVelocity;\n  zeus::CVector3f x150_momentum;\n  zeus::CVector3f x15c_force;\n  zeus::CVector3f x168_impulse;\n  zeus::CAxisAngle x174_torque;\n  zeus::CAxisAngle x180_angularImpulse;\n  zeus::CVector3f x18c_moveImpulse;\n  zeus::CAxisAngle x198_moveAngularImpulse;\n  zeus::CAABox x1a4_baseBoundingBox;\n  CCollidableAABox x1c0_collisionPrimitive;\n  zeus::CVector3f x1e8_primitiveOffset;\n  CMotionState x1f4_lastNonCollidingState;\n  std::optional<zeus::CVector3f> x228_lastFloorPlaneNormal;\n  float x238_maximumCollisionVelocity = 1000000.0f;\n  float x23c_stepUpHeight;\n  float x240_stepDownHeight;\n  float x244_restitutionCoefModifier = 0.f;\n  float x248_collisionAccuracyModifier = 1.f;\n  u32 x24c_numTicksStuck = 0;\n  u32 x250_numTicksPartialUpdate = 0;\n\npublic:\n  DEFINE_ENTITY\n  CPhysicsActor(TUniqueId uid, bool active, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                CModelData&& mData, const CMaterialList& matList, const zeus::CAABox& box, const SMoverData& moverData,\n                const CActorParameters& actorParms, float stepUp, float stepDown);\n\n  void Render(CStateManager& mgr) override;\n  zeus::CVector3f GetOrbitPosition(const CStateManager& mgr) const override;\n  zeus::CVector3f GetAimPosition(const CStateManager& mgr, float val) const override;\n  virtual const CCollisionPrimitive* GetCollisionPrimitive() const;\n  virtual zeus::CTransform GetPrimitiveTransform() const;\n  virtual void CollidedWith(TUniqueId id, const CCollisionInfoList& list, CStateManager& mgr);\n  virtual float GetStepUpHeight() const;\n  virtual float GetStepDownHeight() const;\n  virtual float GetWeight() const;\n\n  float GetMass() const { return xe8_mass; }\n  void SetPrimitiveOffset(const zeus::CVector2f& offset);\n  const zeus::CVector3f& GetPrimitiveOffset() const { return x1e8_primitiveOffset; }\n  void MoveCollisionPrimitive(const zeus::CVector3f& offset);\n  void SetBoundingBox(const zeus::CAABox& box);\n  zeus::CAABox GetMotionVolume(float dt) const;\n  zeus::CVector3f CalculateNewVelocityWR_UsingImpulses() const;\n  zeus::CAABox GetBoundingBox() const;\n  const zeus::CAABox& GetBaseBoundingBox() const;\n  void AddMotionState(const CMotionState& mst);\n  CMotionState GetMotionState() const;\n  const CMotionState& GetLastNonCollidingState() const { return x1f4_lastNonCollidingState; }\n  void SetLastNonCollidingState(const CMotionState& mst) { x1f4_lastNonCollidingState = mst; }\n  void SetMotionState(const CMotionState& mst);\n  float GetMaximumCollisionVelocity() const { return x238_maximumCollisionVelocity; }\n  void SetMaximumCollisionVelocity(float velocity) { x238_maximumCollisionVelocity = velocity; }\n  void SetInertiaTensorScalar(float tensor);\n  void SetMass(float mass);\n  void SetAngularVelocityOR(const zeus::CAxisAngle& angVel);\n  zeus::CAxisAngle GetAngularVelocityOR() const;\n  const zeus::CAxisAngle& GetAngularVelocityWR() const { return x144_angularVelocity; }\n  void SetAngularVelocityWR(const zeus::CAxisAngle& angVel);\n  const zeus::CVector3f& GetForceOR() const { return x15c_force; }\n  const zeus::CVector3f& GetImpulseOR() const { return x168_impulse; }\n  const zeus::CVector3f& GetMoveImpulseOR() const { return x18c_moveImpulse; }\n  void SetVelocityWR(const zeus::CVector3f& vel);\n  void SetVelocityOR(const zeus::CVector3f& vel);\n  void SetMomentumWR(const zeus::CVector3f& momentum) { x150_momentum = momentum; }\n  const zeus::CVector3f& GetConstantForce() const { return xfc_constantForce; }\n  void SetConstantForce(const zeus::CVector3f& force) { xfc_constantForce = force; }\n  const zeus::CVector3f& GetAngularMomentum() const { return x108_angularMomentum; }\n  void SetAngularMomentum(const zeus::CAxisAngle& momentum) { x108_angularMomentum = momentum; }\n  const zeus::CVector3f& GetMomentum() const { return x150_momentum; }\n  const zeus::CVector3f& GetVelocity() const { return x138_velocity; }\n  const zeus::CAxisAngle& GetAngularImpulse() const { return x180_angularImpulse; }\n  void SetAngularImpulse(const zeus::CAxisAngle& impulse) { x180_angularImpulse = impulse; }\n  zeus::CVector3f GetTotalForcesWR() const;\n  void RotateInOneFrameOR(const zeus::CQuaternion& q, float d);\n  void MoveInOneFrameOR(const zeus::CVector3f& trans, float d);\n  void RotateToOR(const zeus::CQuaternion& q, float d);\n  void MoveToOR(const zeus::CVector3f& trans, float d);\n  void MoveToInOneFrameWR(const zeus::CVector3f& v1, float d);\n  void MoveToWR(const zeus::CVector3f& trans, float d);\n  zeus::CAxisAngle GetRotateToORAngularMomentumWR(const zeus::CQuaternion& q, float d) const;\n  zeus::CVector3f GetMoveToORImpulseWR(const zeus::CVector3f& trans, float d) const;\n  void ClearImpulses();\n  void ClearForcesAndTorques();\n  void Stop();\n  void ComputeDerivedQuantities();\n  bool WillMove(const CStateManager& mgr) const;\n  void SetPhysicsState(const CPhysicsState& state);\n  CPhysicsState GetPhysicsState() const;\n  bool IsMovable() const { return xf8_24_movable; }\n  void SetMovable(bool movable) { xf8_24_movable = movable; }\n  bool IsAngularEnabled() const { return xf8_25_angularEnabled; }\n  void SetAngularEnabled(bool enabled) { xf8_25_angularEnabled = enabled; }\n  float GetCollisionAccuracyModifier() const { return x248_collisionAccuracyModifier; }\n  void SetCollisionAccuracyModifier(float modifier) { x248_collisionAccuracyModifier = modifier; }\n  float GetCoefficientOfRestitutionModifier() const { return x244_restitutionCoefModifier; }\n  void SetCoefficientOfRestitutionModifier(float modifier) { x244_restitutionCoefModifier = modifier; }\n  bool IsUseStandardCollider() const { return xf9_standardCollider; }\n  u32 GetNumTicksPartialUpdate() const { return x250_numTicksPartialUpdate; }\n  void SetNumTicksPartialUpdate(u32 ticks) { x250_numTicksPartialUpdate = ticks; }\n  u32 GetNumTicksStuck() const { return x24c_numTicksStuck; }\n  void SetNumTicksStuck(u32 ticks) { x24c_numTicksStuck = ticks; }\n  const std::optional<zeus::CVector3f>& GetLastFloorPlaneNormal() const { return x228_lastFloorPlaneNormal; }\n  void SetLastFloorPlaneNormal(const std::optional<zeus::CVector3f>& normal) { x228_lastFloorPlaneNormal = normal; }\n\n  CMotionState PredictMotion_Internal(float) const;\n  CMotionState PredictMotion(float dt) const;\n  CMotionState PredictLinearMotion(float dt) const;\n  CMotionState PredictAngularMotion(float dt) const;\n  void ApplyForceOR(const zeus::CVector3f& force, const zeus::CAxisAngle& angle);\n  void ApplyForceWR(const zeus::CVector3f& force, const zeus::CAxisAngle& angle);\n  void ApplyImpulseOR(const zeus::CVector3f& impulse, const zeus::CAxisAngle& angle);\n  void ApplyImpulseWR(const zeus::CVector3f& impulse, const zeus::CAxisAngle& angle);\n  void ApplyTorqueWR(const zeus::CVector3f& torque);\n\n  void UseCollisionImpulses();\n  static constexpr float GravityConstant() {\n    return 9.81f * 2.5f;\n  } /* 9.81 m/s ^ 2 is normal acceleration under earth gravity, Tallon 4 is 2.5 times that */\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CPlayer.cpp",
    "content": "#include \"Runtime/World/CPlayer.hpp\"\n\n#include <algorithm>\n#include <array>\n#include <cmath>\n#include <string>\n\n#include \"Runtime/CDependencyGroup.hpp\"\n#include \"Runtime/CGameState.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Audio/CStreamAudioManager.hpp\"\n#include \"Runtime/Camera/CBallCamera.hpp\"\n#include \"Runtime/Camera/CCinematicCamera.hpp\"\n#include \"Runtime/Camera/CFirstPersonCamera.hpp\"\n#include \"Runtime/Character/CSteeringBehaviors.hpp\"\n#include \"Runtime/Collision/CGameCollision.hpp\"\n#include \"Runtime/Collision/CMetroidAreaCollider.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Input/ControlMapper.hpp\"\n#include \"Runtime/MP1/CSamusHud.hpp\"\n#include \"Runtime/MP1/World/CMetroidBeta.hpp\"\n#include \"Runtime/MP1/World/CThardusRockProjectile.hpp\"\n#include \"Runtime/Weapon/CEnergyProjectile.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/World/CHUDBillboardEffect.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n#include \"Runtime/World/CScriptAreaAttributes.hpp\"\n#include \"Runtime/World/CScriptGrapplePoint.hpp\"\n#include \"Runtime/World/CScriptPlayerHint.hpp\"\n#include \"Runtime/World/CScriptWater.hpp\"\n#include \"Runtime/Logging.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nnamespace {\nconstexpr CMaterialFilter SolidMaterialFilter = CMaterialFilter::MakeInclude(CMaterialList(EMaterialTypes::Solid));\n\nconstexpr CMaterialFilter LineOfSightFilter = CMaterialFilter::MakeIncludeExclude(\n    {EMaterialTypes::Solid},\n    {EMaterialTypes::ProjectilePassthrough, EMaterialTypes::ScanPassthrough, EMaterialTypes::Player});\n\nconstexpr CMaterialFilter OccluderFilter = CMaterialFilter::MakeIncludeExclude(\n    {EMaterialTypes::Solid, EMaterialTypes::Occluder},\n    {EMaterialTypes::ProjectilePassthrough, EMaterialTypes::ScanPassthrough, EMaterialTypes::Player});\n\nconstexpr CMaterialFilter BallTransitionCollide = CMaterialFilter::MakeIncludeExclude(\n    {EMaterialTypes::Solid}, {EMaterialTypes::ProjectilePassthrough, EMaterialTypes::Player, EMaterialTypes::Character,\n                              EMaterialTypes::CameraPassthrough});\n\nconstexpr std::array<float, 8> skStrafeDistances{\n    11.8f, 11.8f, 11.8f, 5.0f, 6.0f, 5.0f, 5.0f, 6.0f,\n};\n\nconstexpr std::array<float, 8> skDashStrafeDistances{\n    11.8f, 30.0f, 22.6f, 10.0f, 10.0f, 10.0f, 10.0f, 10.0f,\n};\n\nconstexpr std::array<float, 8> skOrbitForwardDistances{\n    11.8f, 11.8f, 11.8f, 5.0f, 6.0f, 5.0f, 5.0f, 6.0f,\n};\n\nconstexpr std::array<const char*, 2> UnlockMessageResBases{\n    \"STRG_SlideShow_Unlock1_\",\n    \"STRG_SlideShow_Unlock2_\",\n};\n\nconstexpr std::array<u16, 24> skPlayerLandSfxSoft{\n    0xFFFF,\n    SFXsam_landstone_00,\n    SFXsam_landmetl_00,\n    SFXsam_landgrass_00,\n    SFXsam_landice_00,\n    0xFFFF,\n    SFXsam_landgrate_00,\n    SFXsam_landphazon_00,\n    SFXsam_landdirt_00,\n    SFXlav_landlava_00,\n    SFXsam_landlavastone_00,\n    SFXsam_landsnow_00,\n    SFXsam_landmud_00,\n    0xFFFF,\n    SFXsam_landgrass_00,\n    SFXsam_landmetl_00,\n    SFXsam_landmetl_00,\n    SFXsam_landdirt_00,\n    0xFFFF,\n    0xFFFF,\n    0xFFFF,\n    0xFFFF,\n    SFXsam_landwood_00,\n    SFXsam_b_landorg_00,\n};\n\nconstexpr std::array<u16, 24> skPlayerLandSfxHard{\n    0xFFFF,\n    SFXsam_landstone_02,\n    SFXsam_b_landmetl_02,\n    SFXsam_landgrass_02,\n    SFXsam_landice_02,\n    0xFFFF,\n    SFXsam_landgrate_02,\n    SFXsam_landphazon_02,\n    SFXsam_landdirt_02,\n    SFXlav_landlava_02,\n    SFXsam_landlavastone_02,\n    SFXsam_landsnow_02,\n    SFXsam_landmud_02,\n    0xFFFF,\n    SFXsam_landgrass_02,\n    SFXsam_b_landmetl_02,\n    SFXsam_b_landmetl_02,\n    SFXsam_landdirt_02,\n    0xFFFF,\n    0xFFFF,\n    0xFFFF,\n    0xFFFF,\n    SFXsam_landwood_02,\n    SFXsam_landorg_02,\n};\n\nconstexpr std::array<u16, 24> skLeftStepSounds{\n    0xFFFF,\n    SFXsam_wlkstone_00,\n    SFXsam_wlkmetal_00,\n    SFXsam_b_wlkgrass_00,\n    SFXsam_wlkice_00,\n    0xFFFF,\n    SFXsam_wlkgrate_00,\n    SFXsam_wlkphazon_00,\n    SFXsam_wlkdirt_00,\n    SFXlav_wlklava_00,\n    SFXsam_wlklavastone_00,\n    SFXsam_wlksnow_00,\n    SFXsam_wlkmud_00,\n    0xFFFF,\n    SFXsam_b_wlkorg_00,\n    SFXsam_wlkmetal_00,\n    SFXsam_wlkmetal_00,\n    SFXsam_wlkdirt_00,\n    0xFFFF,\n    0xFFFF,\n    0xFFFF,\n    0xFFFF,\n    SFXsam_wlkwood_00,\n    SFXsam_b_wlkorg_00,\n};\n\nconstexpr std::array<u16, 24> skRightStepSounds{\n    0xFFFF,\n    SFXsam_wlkstone_01,\n    SFXsam_wlkmetal_01,\n    SFXsam_b_wlkgrass_01,\n    SFXsam_wlkice_01,\n    0xFFFF,\n    SFXsam_wlkgrate_01,\n    SFXsam_wlkphazon_01,\n    SFXsam_wlkdirt_01,\n    SFXlav_wlklava_01,\n    SFXsam_wlklavastone_01,\n    SFXsam_wlksnow_01,\n    SFXsam_wlkmud_01,\n    0xFFFF,\n    SFXsam_b_wlkorg_01,\n    SFXsam_wlkmetal_01,\n    SFXsam_wlkmetal_01,\n    SFXsam_wlkdirt_01,\n    0xFFFF,\n    0xFFFF,\n    0xFFFF,\n    0xFFFF,\n    SFXsam_wlkwood_01,\n    SFXsam_b_wlkorg_01,\n};\n\nconstexpr float RCP_2PI = 0.15915494;\n\nconstexpr std::array<std::pair<CPlayerState::EItemType, ControlMapper::ECommands>, 4> skVisorToItemMapping{{\n    {CPlayerState::EItemType::CombatVisor, ControlMapper::ECommands::NoVisor},\n    {CPlayerState::EItemType::XRayVisor, ControlMapper::ECommands::XrayVisor},\n    {CPlayerState::EItemType::ScanVisor, ControlMapper::ECommands::InviroVisor},\n    {CPlayerState::EItemType::ThermalVisor, ControlMapper::ECommands::ThermoVisor},\n}};\n\nCModelData MakePlayerAnimRes(CAssetId resId, const zeus::CVector3f& scale) {\n  return CModelData{CAnimRes(resId, 0, scale, 0, true)};\n}\n\nuint32_t GetOrbitScreenBoxHalfExtentXScaled(int zone) {\n  return g_tweakPlayer->GetOrbitScreenBoxHalfExtentX(zone) * CGraphics::GetViewportWidth() / 640;\n}\n\nuint32_t GetOrbitScreenBoxHalfExtentYScaled(int zone) {\n  return g_tweakPlayer->GetOrbitScreenBoxHalfExtentY(zone) * CGraphics::GetViewportHeight() / 448;\n}\n\nuint32_t GetOrbitScreenBoxCenterXScaled(int zone) {\n  return g_tweakPlayer->GetOrbitScreenBoxCenterX(zone) * CGraphics::GetViewportWidth() / 640;\n}\n\nuint32_t GetOrbitScreenBoxCenterYScaled(int zone) {\n  return g_tweakPlayer->GetOrbitScreenBoxCenterY(zone) * CGraphics::GetViewportHeight() / 448;\n}\n\nuint32_t GetOrbitZoneIdealXScaled(int zone) {\n  return g_tweakPlayer->GetOrbitZoneIdealX(zone) * CGraphics::GetViewportWidth() / 640;\n}\n\nuint32_t GetOrbitZoneIdealYScaled(int zone) {\n  return g_tweakPlayer->GetOrbitZoneIdealY(zone) * CGraphics::GetViewportHeight() / 448;\n}\n} // Anonymous namespace\n\nCPlayer::CPlayer(TUniqueId uid, const zeus::CTransform& xf, const zeus::CAABox& aabb, CAssetId resId,\n                 const zeus::CVector3f& playerScale, float mass, float stepUp, float stepDown, float ballRadius,\n                 const CMaterialList& ml)\n: CPhysicsActor(uid, true, \"CPlayer\", CEntityInfo(kInvalidAreaId, CEntity::NullConnectionList), xf,\n                MakePlayerAnimRes(resId, playerScale), ml, aabb, SMoverData(mass), CActorParameters::None(), stepUp,\n                stepDown)\n, x2d8_fpBounds(aabb)\n, x7d0_animRes(resId, 0, playerScale, 0, true)\n, x7d8_beamScale(playerScale) {\n  x490_gun = std::make_unique<CPlayerGun>(uid);\n  x49c_gunHolsterRemTime = g_tweakPlayerGun->GetGunNotFiringTime();\n  x4a0_failsafeTest = std::make_unique<CFailsafeTest>();\n  x76c_cameraBob =\n      std::make_unique<CPlayerCameraBob>(CPlayerCameraBob::ECameraBobType::One, CPlayerCameraBob::GetCameraBobExtent(),\n                                         CPlayerCameraBob::GetCameraBobPeriod());\n  const CAssetId beamId = g_tweakPlayerRes->GetBeamBallTransitionModel(x7ec_beam);\n  x7f0_ballTransitionBeamModel = std::make_unique<CModelData>(CStaticRes(beamId, playerScale));\n  x730_transitionModels.reserve(3);\n  x768_morphball = std::make_unique<CMorphBall>(*this, ballRadius);\n\n  SetInertiaTensorScalar(xe8_mass);\n  x1f4_lastNonCollidingState = GetMotionState();\n  x490_gun->SetTransform(x34_transform);\n  x490_gun->GetGrappleArm().SetTransform(x34_transform);\n\n  InitializeBallTransition();\n  const zeus::CAABox ballTransAABB = x64_modelData->GetBounds();\n  x2f0_ballTransHeight = ballTransAABB.max.z() - ballTransAABB.min.z();\n\n  SetCalculateLighting(true);\n\n  x90_actorLights->SetCastShadows(true);\n  x50c_moveDir.z() = 0.f;\n  if (x50c_moveDir.canBeNormalized()) {\n    x50c_moveDir.normalize();\n  }\n  x2b4_accelerationTable.push_back(20.f);\n  x2b4_accelerationTable.push_back(80.f);\n  x2b4_accelerationTable.push_back(80.f);\n  x2b4_accelerationTable.push_back(270.f);\n  SetMaximumCollisionVelocity(25.f);\n  x354_onScreenOrbitObjects.reserve(64);\n  x344_nearbyOrbitObjects.reserve(64);\n  x364_offScreenOrbitObjects.reserve(64);\n  x64_modelData->SetScale(playerScale);\n  x7f0_ballTransitionBeamModel->SetScale(playerScale);\n  LoadAnimationTokens();\n\n  _CreateReflectionCube();\n}\n\nvoid CPlayer::InitializeBallTransition() {\n  if (x64_modelData && x64_modelData->HasAnimData()) {\n    x64_modelData->GetAnimationData()->SetAnimation(CAnimPlaybackParms(2, -1, 1.f, true), false);\n  }\n}\n\nbool CPlayer::IsTransparent() const { return x588_alpha < 1.f; }\n\nfloat CPlayer::GetTransitionAlpha(const zeus::CVector3f& camPos, float zNear) const {\n  const float zLimit = (x2d8_fpBounds.max.x() - x2d8_fpBounds.min.x()) * 0.5f + zNear;\n  const float zStart = 1.f + zLimit;\n  const float dist = (camPos - GetEyePosition()).magnitude();\n\n  if (dist >= zLimit && dist <= zStart) {\n    return (dist - zLimit) / (zStart - zLimit);\n  }\n\n  if (dist > zStart) {\n    return 1.f;\n  }\n\n  return 0.f;\n}\n\ns32 CPlayer::ChooseTransitionToAnimation(float dt, CStateManager& mgr) const {\n  if (x258_movementState == EPlayerMovementState::ApplyJump) {\n    return 3; // B_airposetoball_samus\n  }\n\n  const zeus::CVector3f localVel = x34_transform.transposeRotate(x138_velocity);\n  zeus::CVector3f localVelFlat = localVel;\n  localVelFlat.z() = 0.f;\n  const float localVelFlatMag = localVelFlat.magnitude();\n  if (localVelFlatMag <= 1.f) {\n    return 2; // B_readytoball_samus\n  }\n\n  float velAng = std::atan2(-localVelFlat.x(), localVelFlat.y());\n  constexpr float twoPi = zeus::degToRad(360.f);\n  if (velAng > twoPi) {\n    velAng = velAng - static_cast<int>(velAng * RCP_2PI) * twoPi;\n  } else if (velAng < 0.f) {\n    velAng = twoPi + (velAng - static_cast<int>(velAng * RCP_2PI) * twoPi);\n  }\n  const float velDeg = zeus::radToDeg(velAng);\n  if (velDeg >= 45.f && velDeg <= 315.f) {\n    return 1; // B_readytostationarybackwards_samus\n  }\n\n  if (localVelFlatMag < 0.5f * GetActualFirstPersonMaxVelocity(dt)) {\n    return 0; // B_forwardtoballforward_samus\n  }\n\n  return 4; // B_runtoballfoward_samus\n}\n\nvoid CPlayer::TransitionToMorphBallState(float dt, CStateManager& mgr) {\n  x584_ballTransitionAnim = ChooseTransitionToAnimation(dt, mgr);\n  x58c_transitionVel = x138_velocity.magnitude();\n  if (x64_modelData && x64_modelData->HasAnimData()) {\n    const CAnimPlaybackParms parms(x584_ballTransitionAnim, -1, 1.f, true);\n    x64_modelData->GetAnimationData()->SetAnimation(parms, false);\n    x64_modelData->GetAnimationData()->SetAnimDir(CAnimData::EAnimDir::Forward);\n  }\n  x64_modelData->EnableLooping(false);\n  x64_modelData->Touch(mgr, 0);\n  x150_momentum = zeus::skZero3f;\n  CPhysicsActor::Stop();\n  SetMorphBallState(EPlayerMorphBallState::Morphing, mgr);\n  SetCameraState(EPlayerCameraState::Transitioning, mgr);\n  x500_lookDir = x34_transform.basis[1];\n  x50c_moveDir = x500_lookDir;\n  x50c_moveDir.z() = 0.f;\n  if (x50c_moveDir.canBeNormalized()) {\n    x50c_moveDir.normalize();\n  } else {\n    x500_lookDir = zeus::skForward;\n    x50c_moveDir = zeus::skForward;\n  }\n  CBallCamera* ballCam = mgr.GetCameraManager()->GetBallCamera();\n  mgr.GetCameraManager()->SetPlayerCamera(mgr, ballCam->GetUniqueId());\n  if (!mgr.GetCameraManager()->HasBallCameraInitialPositionHint(mgr)) {\n    mgr.GetCameraManager()->SetupBallCamera(mgr);\n    ballCam->SetState(CBallCamera::EBallCameraState::ToBall, mgr);\n  } else {\n    ballCam->SetState(CBallCamera::EBallCameraState::Default, mgr);\n    SetCameraState(EPlayerCameraState::Ball, mgr);\n    const zeus::CTransform newXf = mgr.GetCameraManager()->GetFirstPersonCamera()->GetTransform();\n    ballCam->SetTransform(newXf);\n    ballCam->TeleportCamera(newXf.origin, mgr);\n    mgr.GetCameraManager()->SetupBallCamera(mgr);\n    ballCam->SetFovInterpolation(mgr.GetCameraManager()->GetFirstPersonCamera()->GetFov(),\n                                 CCameraManager::ThirdPersonFOV(), 1.f, 0.f);\n  }\n  SetOrbitRequest(EPlayerOrbitRequest::EnterMorphBall, mgr);\n  x490_gun->CancelFiring(mgr);\n  HolsterGun(mgr);\n}\n\nvoid CPlayer::TransitionFromMorphBallState(CStateManager& mgr) {\n  x584_ballTransitionAnim = 14; // B_ball_unfurl\n  x58c_transitionVel = zeus::CVector2f(x138_velocity.x(), x138_velocity.y()).magnitude();\n  if (x58c_transitionVel < 1.f) {\n    x584_ballTransitionAnim = 5; // ballstationarytoready_random\n  }\n\n  if (x258_movementState != EPlayerMovementState::OnGround) {\n    const zeus::CVector3f ballPos = GetBallPosition();\n    if (mgr.RayCollideWorld(ballPos, ballPos + zeus::CVector3f(0.f, 0.f, -7.f), BallTransitionCollide, this)) {\n      x584_ballTransitionAnim = 7; // B_balljumptoairpose\n    }\n  }\n\n  if (x64_modelData && x64_modelData->HasAnimData()) {\n    const CAnimPlaybackParms parms(x584_ballTransitionAnim, -1, 1.f, true);\n    x64_modelData->GetAnimationData()->SetAnimation(parms, false);\n    x64_modelData->GetAnimationData()->SetAnimDir(CAnimData::EAnimDir::Forward);\n  }\n\n  x64_modelData->EnableLooping(false);\n  x64_modelData->Touch(mgr, 0);\n  SetMorphBallState(EPlayerMorphBallState::Unmorphing, mgr);\n  x768_morphball->LeaveMorphBallState(mgr);\n  mgr.GetCameraManager()->SetPlayerCamera(mgr, mgr.GetCameraManager()->GetFirstPersonCamera()->GetUniqueId());\n\n  zeus::CVector3f camToPlayer = GetTranslation() - mgr.GetCameraManager()->GetBallCamera()->GetTranslation();\n  if (camToPlayer.canBeNormalized()) {\n    camToPlayer.normalize();\n    zeus::CVector3f vecFlat = x500_lookDir;\n    vecFlat.z() = 0.f;\n    zeus::CVector3f f31 = vecFlat.canBeNormalized() && vecFlat.magnitude() >= 0.1f ? x518_leaveMorphDir : camToPlayer;\n    if (x9c6_26_outOfBallLookAtHint) {\n      if (const TCastToConstPtr<CScriptPlayerHint> hint = mgr.GetObjectById(x830_playerHint)) {\n        zeus::CVector3f deltaFlat = hint->GetTranslation() - GetTranslation();\n        deltaFlat.z() = 0.f;\n        if (deltaFlat.canBeNormalized()) {\n          f31 = deltaFlat.normalized();\n        }\n      }\n    }\n    if (x9c7_25_outOfBallLookAtHintActor) {\n      if (const TCastToConstPtr<CScriptPlayerHint> hint = mgr.GetObjectById(x830_playerHint)) {\n        if (const TCastToConstPtr<CActor> act = mgr.GetObjectById(hint->GetActorId())) {\n          zeus::CVector3f deltaFlat = act->GetOrbitPosition(mgr) - GetTranslation();\n          deltaFlat.z() = 0.f;\n          if (deltaFlat.canBeNormalized()) {\n            f31 = deltaFlat.normalized();\n          }\n        }\n      }\n    }\n\n    if (std::acos(zeus::clamp(-1.f, camToPlayer.dot(f31), 1.f)) < M_PIF / 1.2f || x9c7_25_outOfBallLookAtHintActor) {\n      SetTransform(zeus::lookAt(GetTranslation(), GetTranslation() + f31));\n    } else {\n      SetTransform(zeus::lookAt(GetTranslation(), GetTranslation() + camToPlayer));\n      UpdateArmAndGunTransforms(0.01f, mgr);\n    }\n  } else {\n    SetTransform(CreateTransformFromMovementDirection());\n  }\n\n  CBallCamera* ballCam = mgr.GetCameraManager()->GetBallCamera();\n  if (const TCastToConstPtr<CActor> act = mgr.GetObjectById(ballCam->GetTooCloseActorId())) {\n    if (ballCam->GetTooCloseActorDistance() < 20.f && ballCam->GetTooCloseActorDistance() > 1.f) {\n      zeus::CVector3f deltaFlat = act->GetTranslation() - GetTranslation();\n      deltaFlat.z() = 0.f;\n      zeus::CVector3f deltaFlat2 = act->GetTranslation() - ballCam->GetTranslation();\n      deltaFlat2.z() = 0.f;\n      if (deltaFlat.canBeNormalized() && deltaFlat2.canBeNormalized()) {\n        deltaFlat.normalize();\n        zeus::CVector3f camLookFlat = ballCam->GetTransform().basis[1];\n        camLookFlat.z() = 0.f;\n        camLookFlat.normalize();\n        deltaFlat2.normalize();\n        if (deltaFlat.dot(deltaFlat2) >= 0.3f && deltaFlat2.dot(camLookFlat) >= 0.7f) {\n          SetTransform(zeus::lookAt(GetTranslation(), GetTranslation() + deltaFlat));\n        }\n      }\n    }\n  }\n\n  ForceGunOrientation(x34_transform, mgr);\n  DrawGun(mgr);\n  ballCam->SetState(CBallCamera::EBallCameraState::FromBall, mgr);\n  ClearForcesAndTorques();\n  SetAngularVelocityWR(zeus::CAxisAngle());\n  AddMaterial(EMaterialTypes::GroundCollider, mgr);\n  x150_momentum = zeus::skZero3f;\n  SetCameraState(EPlayerCameraState::Transitioning, mgr);\n  x824_transitionFilterTimer = 0.01f;\n  x57c_ = 0;\n  x580_ = 0;\n  if (!ballCam->TransitionFromMorphBallState(mgr)) {\n    x824_transitionFilterTimer = 0.95f;\n    LeaveMorphBallState(mgr);\n  }\n}\n\ns32 CPlayer::GetNextBallTransitionAnim(float dt, bool& loopOut, CStateManager& mgr) {\n  loopOut = false;\n\n  const zeus::CVector2f vel(x138_velocity.x(), x138_velocity.y());\n  if (!vel.canBeNormalized()) {\n    return 12; // B_ball_ready_samus\n  }\n\n  const float velMag = vel.magnitude();\n  const float maxVel = GetActualFirstPersonMaxVelocity(dt);\n  if (velMag <= 0.2f * maxVel) {\n    return 12; // B_ball_ready_samus\n  }\n\n  loopOut = true;\n\n  const s32 ret = velMag >= maxVel ? 13 : 15; // B_ball_runloop_samus : B_ball_walkloop_samus\n  if (x50c_moveDir.dot(mgr.GetCameraManager()->GetBallCamera()->GetTransform().basis[1]) < -0.5f) {\n    return 12; // B_ball_ready_samus\n  }\n\n  return ret;\n}\n\nvoid CPlayer::UpdateMorphBallTransition(float dt, CStateManager& mgr) {\n  switch (x2f8_morphBallState) {\n  case EPlayerMorphBallState::Unmorphed:\n  case EPlayerMorphBallState::Morphed: {\n    CPlayerState::EPlayerSuit suit = mgr.GetPlayerState()->GetCurrentSuitRaw();\n    if (mgr.GetPlayerState()->IsFusionEnabled()) {\n      suit = CPlayerState::EPlayerSuit(int(suit) + 4);\n    }\n\n    if (x7cc_transitionSuit != suit) {\n      x7cc_transitionSuit = suit;\n      CAnimRes useRes = x7d0_animRes;\n      useRes.SetCharacterNodeId(s32(x7cc_transitionSuit));\n      SetModelData(std::make_unique<CModelData>(useRes));\n      SetIntoBallReadyAnimation(mgr);\n    }\n    return;\n  }\n  case EPlayerMorphBallState::Unmorphing:\n    if (x584_ballTransitionAnim == 14) // B_ball_unfurl\n    {\n      const float dur = x64_modelData->GetAnimationData()->GetAnimationDuration(x584_ballTransitionAnim);\n      const float facRemaining = x64_modelData->GetAnimationData()->GetAnimTimeRemaining(\"Whole Body\") / dur;\n      if (facRemaining < 0.5f) {\n        bool loop = false;\n        x584_ballTransitionAnim = GetNextBallTransitionAnim(dt, loop, mgr);\n        if (x64_modelData && x64_modelData->HasAnimData()) {\n          const CAnimPlaybackParms parms(x584_ballTransitionAnim, -1, 1.f, true);\n          x64_modelData->GetAnimationData()->SetAnimation(parms, false);\n          x64_modelData->GetAnimationData()->EnableLooping(loop);\n        }\n      }\n    } else if (x584_ballTransitionAnim != 5 && x584_ballTransitionAnim != 7)\n    // ballstationarytoready_random, B_balljumptoairpose\n    {\n      const float velMag = zeus::CVector2f(x138_velocity.x(), x138_velocity.y()).magnitude();\n      if (std::fabs(x58c_transitionVel - velMag) > 0.04f * GetActualFirstPersonMaxVelocity(dt) || velMag < 1.f) {\n        bool loop = false;\n        const s32 nextAnim = GetNextBallTransitionAnim(dt, loop, mgr);\n        if (x64_modelData && x64_modelData->HasAnimData() && x584_ballTransitionAnim != nextAnim &&\n            x584_ballTransitionAnim != 7) {\n          x584_ballTransitionAnim = nextAnim;\n          const CAnimPlaybackParms parms(x584_ballTransitionAnim, -1, 1.f, true);\n          x64_modelData->GetAnimationData()->SetAnimation(parms, false);\n          x64_modelData->GetAnimationData()->EnableLooping(loop);\n          x58c_transitionVel = velMag;\n        }\n      }\n    }\n    break;\n  default:\n    break;\n  }\n\n  const SAdvancementDeltas deltas = UpdateAnimation(dt, mgr, true);\n  MoveInOneFrameOR(deltas.x0_posDelta, dt);\n  RotateInOneFrameOR(deltas.xc_rotDelta, dt);\n  x574_morphTime = std::min(x574_morphTime + dt, x578_morphDuration);\n  const float morphT = x574_morphTime / x578_morphDuration;\n  if ((morphT >= 0.7f || x574_morphTime <= 2.f * dt) && x730_transitionModels.size() != 0) {\n    x730_transitionModels.erase(x730_transitionModels.begin());\n  }\n\n  for (auto& m : x730_transitionModels) {\n    m->AdvanceAnimation(dt, mgr, kInvalidAreaId, true);\n  }\n\n  const CGameCamera* cam = mgr.GetCameraManager()->GetCurrentCamera(mgr);\n  x588_alpha = GetTransitionAlpha(cam->GetTranslation(), cam->GetNearClipDistance());\n\n  if (x2f8_morphBallState == EPlayerMorphBallState::Morphing && morphT > 0.93f) {\n    x588_alpha *= std::min(1.f, 1.f - (morphT - 0.93f) / 0.07f + 0.2f);\n    xb4_drawFlags = CModelFlags(5, 0, 1, zeus::CColor(1.f, x588_alpha));\n  } else if (x2f8_morphBallState == EPlayerMorphBallState::Unmorphing && x588_alpha < 1.f) {\n    if (x588_alpha > 0.05f) {\n      xb4_drawFlags = CModelFlags(5, 0, 0x21, zeus::CColor(1.f, x588_alpha));\n    } else {\n      xb4_drawFlags = CModelFlags(5, 0, 1, zeus::CColor(1.f, x588_alpha));\n    }\n  } else {\n    xb4_drawFlags = CModelFlags(5, 0, 3, zeus::CColor(1.f, x588_alpha));\n  }\n\n  x594_transisionBeamXfs.AddValue(x7f4_gunWorldXf);\n  x658_transitionModelXfs.AddValue(x34_transform);\n  x71c_transitionModelAlphas.AddValue(x588_alpha);\n\n  switch (x2f8_morphBallState) {\n  case EPlayerMorphBallState::Unmorphing:\n    (void)GetCollisionPrimitive()->CalculateAABox(GetPrimitiveTransform()).center();\n    ClearForcesAndTorques();\n    SetAngularVelocityWR(zeus::CAxisAngle());\n    if (x574_morphTime >= x578_morphDuration || mgr.GetCameraManager()->IsInCinematicCamera()) {\n      x824_transitionFilterTimer = std::max(x824_transitionFilterTimer, 0.95f);\n      zeus::CVector3f pos;\n      if (CanLeaveMorphBallState(mgr, pos)) {\n        SetTranslation(GetTranslation() + pos);\n        LeaveMorphBallState(mgr);\n        xb4_drawFlags = CModelFlags(0, 0, 3, zeus::skWhite);\n      } else {\n        x574_morphTime = x578_morphDuration - x574_morphTime;\n        TransitionToMorphBallState(dt, mgr);\n      }\n    }\n    break;\n  case EPlayerMorphBallState::Morphing:\n    ClearForcesAndTorques();\n    SetAngularVelocityWR(zeus::CAxisAngle());\n    if (x574_morphTime >= x578_morphDuration || mgr.GetCameraManager()->IsInCinematicCamera()) {\n      if (CanEnterMorphBallState(mgr, 1.f)) {\n        ActivateMorphBallCamera(mgr);\n        EnterMorphBallState(mgr);\n        xb4_drawFlags = CModelFlags(0, 0, 3, zeus::skWhite);\n      } else {\n        x574_morphTime = x578_morphDuration - x574_morphTime;\n        TransitionFromMorphBallState(mgr);\n      }\n    }\n    if (x578_morphDuration != 0.f) {\n      if (zeus::clamp(0.f, x574_morphTime, 1.f) >= 0.5f) {\n        if (!x768_morphball->IsMorphBallTransitionFlashValid()) {\n          x768_morphball->ResetMorphBallTransitionFlash();\n        }\n      }\n    }\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CPlayer::UpdateGunAlpha() {\n  switch (x498_gunHolsterState) {\n  case EGunHolsterState::Holstered:\n    x494_gunAlpha = 0.f;\n    break;\n  case EGunHolsterState::Holstering:\n    x494_gunAlpha = zeus::clamp(0.f, x49c_gunHolsterRemTime / g_tweakPlayerGun->GetGunHolsterTime(), 1.f);\n    break;\n  case EGunHolsterState::Drawing:\n    x494_gunAlpha = 1.f - zeus::clamp(0.f, x49c_gunHolsterRemTime / 0.45f, 1.f);\n    break;\n  case EGunHolsterState::Drawn:\n    x494_gunAlpha = 1.f;\n    break;\n  }\n}\n\nvoid CPlayer::UpdatePlayerSounds(float dt) {\n  if (x784_damageSfxTimer <= 0.f) {\n    return;\n  }\n\n  x784_damageSfxTimer -= dt;\n  if (x784_damageSfxTimer > 0.f) {\n    return;\n  }\n\n  CSfxManager::SfxStop(x770_damageLoopSfx);\n  x770_damageLoopSfx.reset();\n}\n\nvoid CPlayer::Update(float dt, CStateManager& mgr) {\n  SetCoefficientOfRestitutionModifier(0.f);\n  UpdateMorphBallTransition(dt, mgr);\n\n  const CPlayerState::EBeamId newBeam = mgr.GetPlayerState()->GetCurrentBeam();\n  if (newBeam != x7ec_beam) {\n    x7ec_beam = newBeam;\n    x7f0_ballTransitionBeamModel = std::make_unique<CModelData>(\n        CStaticRes(g_tweakPlayerRes->GetBeamBallTransitionModel(x7ec_beam), x7d8_beamScale));\n  }\n\n  if (!mgr.GetPlayerState()->IsPlayerAlive()) {\n    if (x9f4_deathTime == 0.f) {\n      CSfxManager::KillAll(CSfxManager::ESfxChannels::Game);\n      CStreamAudioManager::StopAll();\n      if (x2f8_morphBallState == EPlayerMorphBallState::Unmorphed) {\n        CSfxHandle hnd = CSfxManager::SfxStart(SFXsam_death, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n        ApplySubmergedPitchBend(hnd);\n      }\n    }\n\n    const float prevDeathTime = x9f4_deathTime;\n    x9f4_deathTime += dt;\n    if (x2f8_morphBallState != EPlayerMorphBallState::Unmorphed) {\n      if (x9f4_deathTime >= 1.f && prevDeathTime < 1.f) {\n        xa00_deathPowerBomb = x490_gun->DropPowerBomb(mgr);\n      }\n      if (x9f4_deathTime >= 4.f && prevDeathTime < 4.f) {\n        CSfxHandle hnd = CSfxManager::SfxStart(SFXsam_death, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n        ApplySubmergedPitchBend(hnd);\n      }\n    }\n  }\n\n  switch (x2f8_morphBallState) {\n  case EPlayerMorphBallState::Unmorphed:\n  case EPlayerMorphBallState::Morphing:\n  case EPlayerMorphBallState::Unmorphing:\n    x7f4_gunWorldXf = x34_transform * x64_modelData->GetScaledLocatorTransform(\"GUN_LCTR\");\n    break;\n  case EPlayerMorphBallState::Morphed:\n    break;\n  }\n\n  if (x2f8_morphBallState == EPlayerMorphBallState::Unmorphed) {\n    UpdateAimTargetTimer(dt);\n    UpdateAimTarget(mgr);\n    UpdateOrbitModeTimer(dt);\n  }\n  UpdateOrbitPreventionTimer(dt);\n  if (x2f8_morphBallState == EPlayerMorphBallState::Morphed) {\n    x768_morphball->Update(dt, mgr);\n  } else {\n    x768_morphball->StopSounds();\n  }\n  if (x2f8_morphBallState == EPlayerMorphBallState::Unmorphing ||\n      x2f8_morphBallState == EPlayerMorphBallState::Morphing) {\n    x768_morphball->UpdateEffects(dt, mgr);\n  }\n  UpdateGunAlpha();\n  UpdateDebugCamera(mgr);\n  UpdateVisorTransition(dt, mgr);\n  mgr.SetActorAreaId(*this, mgr.GetWorld()->GetCurrentAreaId());\n  UpdatePlayerSounds(dt);\n  if (x26c_attachedActor != kInvalidUniqueId) {\n    x270_attachedActorTime += dt;\n  }\n\n  x740_staticTimer = std::max(0.f, x740_staticTimer - dt);\n  if (x740_staticTimer > 0.f) {\n    x74c_visorStaticAlpha = std::max(0.f, x74c_visorStaticAlpha - x744_staticOutSpeed * dt);\n  } else {\n    x74c_visorStaticAlpha = std::min(1.f, x74c_visorStaticAlpha + x748_staticInSpeed * dt);\n  }\n\n  x274_energyDrain.ProcessEnergyDrain(mgr, dt);\n  x4a4_moveSpeedAvg.AddValue(x4f8_moveSpeed);\n\n  mgr.GetPlayerState()->UpdateStaticInterference(mgr, dt);\n  if (!ShouldSampleFailsafe(mgr)) {\n    CPhysicsActor::Stop();\n  }\n\n  if (IsEnergyLow(mgr)) {\n    xa30_samusExhaustedVoiceTimer -= dt;\n  } else {\n    xa30_samusExhaustedVoiceTimer = 4.f;\n  }\n\n  if (!mgr.GetCameraManager()->IsInCinematicCamera() && xa30_samusExhaustedVoiceTimer <= 0.f) {\n    StartSamusVoiceSfx(SFXsam_vox_exhausted, 1.f, 7);\n    xa30_samusExhaustedVoiceTimer = 4.f;\n  }\n}\n\nfloat CPlayer::UpdateCameraBob(float dt, CStateManager& mgr) {\n  float bobMag = 0.f;\n  CPlayerCameraBob::ECameraBobState state;\n  const zeus::CVector3f backupVel = x138_velocity;\n  if (x304_orbitState == EPlayerOrbitState::NoOrbit) {\n    bobMag = std::fabs(backupVel.dot(x34_transform.basis[1]) / GetActualFirstPersonMaxVelocity(dt));\n    state = CPlayerCameraBob::ECameraBobState::Walk;\n    if (bobMag < 0.01f) {\n      bobMag = 0.f;\n      state = CPlayerCameraBob::ECameraBobState::WalkNoBob;\n    }\n  } else {\n    state = CPlayerCameraBob::ECameraBobState::Orbit;\n    const float f29 = backupVel.dot(x34_transform.basis[0]);\n    const float f30 = backupVel.dot(x34_transform.basis[1]);\n    const float maxVel = GetActualFirstPersonMaxVelocity(dt);\n    const float strafeDist =\n        skStrafeDistances[size_t(x2b0_outOfWaterTicks == 2 ? x2ac_surfaceRestraint : ESurfaceRestraints::Water)];\n    bobMag = std::min(std::sqrt(f30 * f30 + f29 * f29) / std::sqrt(strafeDist * strafeDist + maxVel * maxVel) *\n                          CPlayerCameraBob::GetOrbitBobScale(),\n                      CPlayerCameraBob::GetMaxOrbitBobScale());\n    if (bobMag < 0.01f) {\n      bobMag = 0.f;\n    }\n  }\n\n  if (x258_movementState != EPlayerMovementState::OnGround) {\n    bobMag = 0.f;\n    state = CPlayerCameraBob::ECameraBobState::InAir;\n  } else if (bobMag < 0.01f) {\n    if (x490_gun->GetLastFireButtonStates() != 0) {\n      bobMag = 0.f;\n      state = CPlayerCameraBob::ECameraBobState::GunFireNoBob;\n    } else if (std::fabs(GetAngularVelocityOR().angle()) > 0.1f) {\n      bobMag = 0.f;\n      state = CPlayerCameraBob::ECameraBobState::TurningNoBob;\n    }\n  }\n\n  if (x3dc_inFreeLook || x3dd_lookButtonHeld) {\n    bobMag = 0.f;\n    state = CPlayerCameraBob::ECameraBobState::FreeLookNoBob;\n  }\n\n  if (x304_orbitState == EPlayerOrbitState::Grapple) {\n    bobMag = 0.f;\n    state = CPlayerCameraBob::ECameraBobState::GrappleNoBob;\n  }\n\n  if (x3a8_scanState == EPlayerScanState::ScanComplete) {\n    bobMag = 0.f;\n  }\n\n  if (x38c_doneSidewaysDashing) {\n    bobMag *= 0.1f;\n    state = CPlayerCameraBob::ECameraBobState::FreeLookNoBob;\n    if (x258_movementState == EPlayerMovementState::OnGround) {\n      x38c_doneSidewaysDashing = false;\n    }\n  }\n\n  if (mgr.GetCameraManager()->IsInCinematicCamera()) {\n    bobMag = 0.f;\n  }\n\n  bobMag *= mgr.GetCameraManager()->GetCameraBobMagnitude();\n\n  x76c_cameraBob->SetPlayerVelocity(backupVel);\n  x76c_cameraBob->SetState(state, mgr);\n  x76c_cameraBob->SetBobMagnitude(bobMag);\n  x76c_cameraBob->SetBobTimeScale((1.f - CPlayerCameraBob::GetSlowSpeedPeriodScale()) * bobMag +\n                                  CPlayerCameraBob::GetSlowSpeedPeriodScale());\n  x76c_cameraBob->Update(dt, mgr);\n\n  return bobMag;\n}\n\nfloat CPlayer::GetAcceleration() const {\n  if (x2d0_curAcceleration >= x2b4_accelerationTable.size()) {\n    return x2b4_accelerationTable.back();\n  }\n  return x2b4_accelerationTable[x2d0_curAcceleration];\n}\n\nfloat CPlayer::CalculateOrbitMinDistance(EPlayerOrbitType type) const {\n  return zeus::clamp(1.f, std::fabs(x314_orbitPoint.z() - GetTranslation().z()) / 20.f, 4.f) *\n         g_tweakPlayer->GetOrbitMinDistance(int(type));\n}\n\nvoid CPlayer::PostUpdate(float dt, CStateManager& mgr) {\n  UpdateArmAndGunTransforms(dt, mgr);\n\n  float grappleSwingT;\n  if (x3b8_grappleState != EGrappleState::Swinging) {\n    grappleSwingT = 0.f;\n  } else {\n    grappleSwingT = x3bc_grappleSwingTimer / g_tweakPlayer->GetGrappleSwingPeriod();\n  }\n\n  float cameraBobT = 0.f;\n  if (mgr.GetCameraManager()->IsInCinematicCamera()) {\n    x76c_cameraBob = std::make_unique<CPlayerCameraBob>(CPlayerCameraBob::ECameraBobType::One,\n                                                        CPlayerCameraBob::GetCameraBobExtent(),\n                                                        CPlayerCameraBob::GetCameraBobPeriod());\n  } else {\n    cameraBobT = UpdateCameraBob(dt, mgr);\n  }\n\n  x490_gun->Update(grappleSwingT, cameraBobT, dt, mgr);\n  UpdateOrbitTarget(mgr);\n  UpdateOrbitOrientation(mgr);\n}\n\nbool CPlayer::StartSamusVoiceSfx(u16 sfx, float vol, int prio) {\n  if (x2f8_morphBallState == EPlayerMorphBallState::Morphed) {\n    return false;\n  }\n\n  bool started = true;\n  if (x77c_samusVoiceSfx) {\n    if (CSfxManager::IsPlaying(x77c_samusVoiceSfx)) {\n      started = false;\n      if (prio > x780_samusVoicePriority) {\n        CSfxManager::SfxStop(x77c_samusVoiceSfx);\n        started = true;\n      }\n    }\n\n    if (started) {\n      x77c_samusVoiceSfx = CSfxManager::SfxStart(sfx, vol, 0.f, false, 0x7f, false, kInvalidAreaId);\n      x780_samusVoicePriority = prio;\n    }\n  }\n\n  return started;\n}\n\nbool CPlayer::IsPlayerDeadEnough() const {\n  if (x2f8_morphBallState == EPlayerMorphBallState::Unmorphed) {\n    return x9f4_deathTime > 2.5f;\n  }\n\n  if (x2f8_morphBallState == EPlayerMorphBallState::Morphed) {\n    return x9f4_deathTime > 6.f;\n  }\n\n  return false;\n}\n\nvoid CPlayer::AsyncLoadSuit(CStateManager& mgr) { x490_gun->AsyncLoadSuit(mgr); }\n\nvoid CPlayer::LoadAnimationTokens() {\n  TLockedToken<CDependencyGroup> transGroup = g_SimplePool->GetObj(\"BallTransition_DGRP\");\n  CDependencyGroup& group = *transGroup;\n  x25c_ballTransitionsRes.reserve(group.GetObjectTagVector().size());\n  for (const SObjectTag& tag : group.GetObjectTagVector()) {\n    if (tag.type == FOURCC('CMDL') || tag.type == FOURCC('CSKR') || tag.type == FOURCC('TXTR')) {\n      continue;\n    }\n    x25c_ballTransitionsRes.push_back(g_SimplePool->GetObj(tag));\n  }\n}\n\nbool CPlayer::HasTransitionBeamModel() const {\n  return x7f0_ballTransitionBeamModel && !x7f0_ballTransitionBeamModel->IsNull();\n}\n\nbool CPlayer::CanRenderUnsorted(const CStateManager& mgr) const { return false; }\n\nconst CDamageVulnerability* CPlayer::GetDamageVulnerability(const zeus::CVector3f& v1, const zeus::CVector3f& v2,\n                                                            const CDamageInfo& info) const {\n  if (x2f8_morphBallState == EPlayerMorphBallState::Morphed && x570_immuneTimer > 0.f && !info.NoImmunity()) {\n    return &CDamageVulnerability::ImmuneVulnerabilty();\n  }\n  return &CDamageVulnerability::NormalVulnerabilty();\n}\n\nconst CDamageVulnerability* CPlayer::GetDamageVulnerability() const {\n  constexpr CDamageInfo info(CWeaponMode(EWeaponType::Power, false, false, false), 0.f, 0.f, 0.f);\n  return GetDamageVulnerability(zeus::skZero3f, zeus::skUp, info);\n}\n\nzeus::CVector3f CPlayer::GetHomingPosition(const CStateManager& mgr, float dt) const {\n  if (dt > 0.f) {\n    return x34_transform.origin + PredictMotion(dt).x0_translation;\n  }\n  return x34_transform.origin;\n}\n\nzeus::CVector3f CPlayer::GetAimPosition(const CStateManager& mgr, float dt) const {\n  zeus::CVector3f ret = x34_transform.origin;\n  if (dt > 0.f) {\n    if (x304_orbitState == EPlayerOrbitState::NoOrbit) {\n      ret += PredictMotion(dt).x0_translation;\n    } else {\n      ret = CSteeringBehaviors::ProjectOrbitalPosition(ret, x138_velocity, x314_orbitPoint, dt, xa04_preThinkDt);\n    }\n  }\n\n  if (x2f8_morphBallState == EPlayerMorphBallState::Morphed) {\n    ret.z() += g_tweakPlayer->GetPlayerBallHalfExtent();\n  } else {\n    ret.z() += GetEyeHeight();\n  }\n\n  return ret;\n}\n\nvoid CPlayer::FluidFXThink(EFluidState state, CScriptWater& water, CStateManager& mgr) {\n  if (x2f8_morphBallState == EPlayerMorphBallState::Morphed) {\n    x768_morphball->FluidFXThink(state, water, mgr);\n    if (state == EFluidState::InFluid) {\n      x9c5_30_selectFluidBallSound = true;\n    }\n  } else if (x2f8_morphBallState != EPlayerMorphBallState::Unmorphed) {\n    if (mgr.GetFluidPlaneManager()->GetLastSplashDeltaTime(x8_uid) >= 0.2f) {\n      zeus::CVector3f position(x34_transform.origin);\n      position.z() = float(water.GetTriggerBoundsWR().max.z());\n      mgr.GetFluidPlaneManager()->CreateSplash(x8_uid, mgr, water, position, 0.1f, state == EFluidState::EnteredFluid);\n    }\n  } else {\n    if (mgr.GetFluidPlaneManager()->GetLastSplashDeltaTime(x8_uid) >= 0.2f) {\n      zeus::CVector3f posOffset = x50c_moveDir;\n      if (posOffset.canBeNormalized()) {\n        posOffset = posOffset.normalized() * zeus::CVector3f(1.2f, 1.2f, 0.f);\n      }\n      switch (state) {\n      case EFluidState::EnteredFluid: {\n        bool doSplash = true;\n        if (x4fc_flatMoveSpeed > 12.5f) {\n          zeus::CVector3f lookDir = x34_transform.basis[1];\n          lookDir.z() = 0.f;\n          lookDir.normalize();\n          zeus::CVector3f dcVel = GetDampedClampedVelocityWR();\n          dcVel.z() = 0.f;\n          if (lookDir.dot(dcVel.normalized()) > 0.75f) {\n            doSplash = false;\n          }\n        }\n        if (doSplash) {\n          zeus::CVector3f position = x34_transform.origin + posOffset;\n          position.z() = float(water.GetTriggerBoundsWR().max.z());\n          mgr.GetFluidPlaneManager()->CreateSplash(x8_uid, mgr, water, position, 0.3f, true);\n          if (water.GetFluidPlane().GetFluidType() == EFluidType::NormalWater) {\n            const float velMag = mgr.GetPlayer().GetVelocity().magnitude() / 10.f;\n            mgr.GetEnvFxManager()->SetSplashRate(10.f * std::max(1.f, velMag));\n          }\n        }\n        break;\n      }\n      case EFluidState::InFluid: {\n        if (x138_velocity.magnitude() > 1.f && mgr.GetFluidPlaneManager()->GetLastRippleDeltaTime(x8_uid) >= 0.2f) {\n          zeus::CVector3f position(x34_transform.origin);\n          position.z() = float(water.GetTriggerBoundsWR().max.z());\n          water.GetFluidPlane().AddRipple(0.5f, x8_uid, position, water, mgr);\n        }\n        break;\n      }\n      case EFluidState::LeftFluid: {\n        zeus::CVector3f position = x34_transform.origin + posOffset;\n        position.z() = float(water.GetTriggerBoundsWR().max.z());\n        mgr.GetFluidPlaneManager()->CreateSplash(x8_uid, mgr, water, position, 0.15f, true);\n        break;\n      }\n      default:\n        break;\n      }\n    }\n  }\n}\n\nvoid CPlayer::TakeDamage(bool significant, const zeus::CVector3f& location, float dam, EWeaponType type,\n                         CStateManager& mgr) {\n  if (!significant) {\n    return;\n  }\n\n  if (dam >= 0.f) {\n    x570_immuneTimer = 0.5f;\n    x55c_damageAmt = dam;\n    x560_prevDamageAmt = (type == EWeaponType::AI && dam == 0.00002f) ? 10.f : dam;\n    x564_damageLocation = location;\n    x558_wasDamaged = true;\n\n    bool doRumble = false;\n    u16 suitDamageSfx = 0;\n    u16 damageLoopSfx = 0;\n    u16 damageSamusVoiceSfx = 0;\n\n    switch (type) {\n    case EWeaponType::Phazon:\n    case EWeaponType::OrangePhazon:\n      damageLoopSfx = SFXphz_damage_lp;\n      damageSamusVoiceSfx = SFXsam_vox_damage_phazon;\n      break;\n    case EWeaponType::PoisonWater:\n      damageLoopSfx = SFXsam_damage_poison_lp;\n      damageSamusVoiceSfx = SFXsam_vox_damage_poison;\n      break;\n    case EWeaponType::Lava:\n      damageLoopSfx = SFXpds_lava_damage_lp;\n      [[fallthrough]];\n    case EWeaponType::Heat:\n      damageSamusVoiceSfx = SFXsam_vox_damage_heat;\n      break;\n    default:\n      if (x2f8_morphBallState == EPlayerMorphBallState::Unmorphed) {\n        if (dam > 30.f) {\n          damageSamusVoiceSfx = SFXsam_vox_damage30;\n        } else if (dam > 15.f) {\n          damageSamusVoiceSfx = SFXsam_vox_damage15;\n        } else {\n          damageSamusVoiceSfx = SFXsam_vox_damage;\n        }\n        suitDamageSfx = SFXsam_suit_damage;\n      } else {\n        if (dam > 30.f) {\n          suitDamageSfx = SFXsam_ball_damage30;\n        } else if (dam > 15.f) {\n          suitDamageSfx = SFXsam_ball_damage15;\n        } else {\n          suitDamageSfx = SFXsam_ball_damage;\n        }\n      }\n      break;\n    }\n\n    if (damageSamusVoiceSfx != 0 && x774_samusVoiceTimeout <= 0.f) {\n      StartSamusVoiceSfx(damageSamusVoiceSfx, 1.f, 8);\n      x774_samusVoiceTimeout = mgr.GetActiveRandom()->Range(3.f, 4.f);\n      doRumble = true;\n    }\n\n    if (damageLoopSfx != 0 && !x9c7_24_noDamageLoopSfx && xa2c_damageLoopSfxDelayTicks >= 2) {\n      if (!x770_damageLoopSfx || x788_damageLoopSfxId != damageLoopSfx) {\n        if (x770_damageLoopSfx && x788_damageLoopSfxId != damageLoopSfx) {\n          CSfxManager::SfxStop(x770_damageLoopSfx);\n        }\n        x770_damageLoopSfx = CSfxManager::SfxStart(damageLoopSfx, 1.f, 0.f, false, 0x7f, true, kInvalidAreaId);\n        x788_damageLoopSfxId = damageLoopSfx;\n      }\n      x784_damageSfxTimer = 0.5f;\n    }\n\n    if (suitDamageSfx != 0) {\n      if (x770_damageLoopSfx) {\n        CSfxManager::SfxStop(x770_damageLoopSfx);\n        x770_damageLoopSfx.reset();\n      }\n      CSfxManager::SfxStart(suitDamageSfx, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n      x788_damageLoopSfxId = suitDamageSfx;\n      xa2c_damageLoopSfxDelayTicks = 0;\n      doRumble = true;\n    }\n\n    if (doRumble) {\n      if (x2f8_morphBallState == EPlayerMorphBallState::Unmorphed) {\n        x490_gun->DamageRumble(location, dam, mgr);\n      }\n\n      float tmp = x55c_damageAmt / 25.f;\n      if (std::fabs(tmp) > 1.f) {\n        tmp = tmp > 0.f ? 1.f : -1.f;\n      }\n\n      mgr.GetRumbleManager().Rumble(mgr, ERumbleFxId::PlayerBump, tmp, ERumblePriority::One);\n    }\n\n    if (x2f8_morphBallState != EPlayerMorphBallState::Unmorphed) {\n      x768_morphball->TakeDamage(x55c_damageAmt);\n      x768_morphball->SetDamageTimer(0.4f);\n    }\n  }\n\n  if (x3b8_grappleState != EGrappleState::None) {\n    BreakGrapple(EPlayerOrbitRequest::DamageOnGrapple, mgr);\n  }\n}\n\nvoid CPlayer::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nCHealthInfo* CPlayer::HealthInfo(CStateManager& mgr) { return &mgr.GetPlayerState()->GetHealthInfo(); }\n\nbool CPlayer::IsUnderBetaMetroidAttack(const CStateManager& mgr) const {\n  if (x274_energyDrain.GetEnergyDrainIntensity() > 0.f) {\n    for (const CEnergyDrainSource& source : x274_energyDrain.GetEnergyDrainSources()) {\n      if (CPatterned::CastTo<MP1::CMetroidBeta>(mgr.GetObjectById(source.GetEnergyDrainSourceId()))) {\n        return true;\n      }\n    }\n  }\n\n  return false;\n}\n\nstd::optional<zeus::CAABox> CPlayer::GetTouchBounds() const {\n  if (x2f8_morphBallState != EPlayerMorphBallState::Morphed) {\n    return GetBoundingBox();\n  }\n\n  const float ballTouchRad = x768_morphball->GetBallTouchRadius();\n  const zeus::CVector3f ballCenter = GetTranslation() + zeus::CVector3f(0.f, 0.f, x768_morphball->GetBallRadius());\n  return zeus::CAABox(ballCenter - ballTouchRad, ballCenter + ballTouchRad);\n}\n\nvoid CPlayer::DoPreThink(float dt, CStateManager& mgr) {\n  PreThink(dt, mgr);\n  if (CEntity* ent = mgr.ObjectById(xa00_deathPowerBomb)) {\n    ent->PreThink(dt, mgr);\n  }\n}\n\nvoid CPlayer::DoThink(float dt, CStateManager& mgr) {\n  Think(dt, mgr);\n  if (CEntity* ent = mgr.ObjectById(xa00_deathPowerBomb)) {\n    ent->Think(dt, mgr);\n  }\n}\n\nvoid CPlayer::UpdateScanningState(const CFinalInput& input, CStateManager& mgr, float dt) {\n  if (mgr.GetPlayerState()->GetCurrentVisor() != CPlayerState::EPlayerVisor::Scan) {\n    SetScanningState(EPlayerScanState::NotScanning, mgr);\n    return;\n  }\n\n  if (x3a8_scanState != EPlayerScanState::NotScanning && x3b4_scanningObject != x310_orbitTargetId &&\n      x310_orbitTargetId != kInvalidUniqueId) {\n    SetScanningState(EPlayerScanState::NotScanning, mgr);\n  }\n\n  switch (x3a8_scanState) {\n  case EPlayerScanState::NotScanning:\n    if (ValidateScanning(input, mgr)) {\n      if (const TCastToConstPtr<CActor> act = mgr.ObjectById(x310_orbitTargetId)) {\n        const CScannableObjectInfo* scanInfo = act->GetScannableObjectInfo();\n        float scanTime = mgr.GetPlayerState()->GetScanTime(scanInfo->GetScannableObjectId());\n        if (scanTime >= 1.f) {\n          x9c6_30_newScanScanning = false;\n          scanTime = 1.f;\n        } else {\n          x9c6_30_newScanScanning = true;\n        }\n\n        SetScanningState(EPlayerScanState::Scanning, mgr);\n        x3ac_scanningTime = scanTime * scanInfo->GetTotalDownloadTime();\n        x3b0_curScanTime = 0.f;\n      }\n    }\n    break;\n  case EPlayerScanState::Scanning:\n    if (ValidateScanning(input, mgr)) {\n      if (const TCastToConstPtr<CActor> act = mgr.ObjectById(x310_orbitTargetId)) {\n        if (const CScannableObjectInfo* scanInfo = act->GetScannableObjectInfo()) {\n          x3ac_scanningTime = std::min(scanInfo->GetTotalDownloadTime(), x3ac_scanningTime + dt);\n          x3b0_curScanTime += dt;\n          mgr.GetPlayerState()->SetScanTime(scanInfo->GetScannableObjectId(),\n                                            x3ac_scanningTime / scanInfo->GetTotalDownloadTime());\n          if (x3ac_scanningTime >= scanInfo->GetTotalDownloadTime() &&\n              x3b0_curScanTime >= g_tweakGui->GetScanSidesStartTime()) {\n            SetScanningState(EPlayerScanState::ScanComplete, mgr);\n          }\n        }\n      } else {\n        SetScanningState(EPlayerScanState::NotScanning, mgr);\n      }\n    } else {\n      SetScanningState(EPlayerScanState::NotScanning, mgr);\n    }\n    break;\n  case EPlayerScanState::ScanComplete:\n    if (!ValidateScanning(input, mgr)) {\n      SetScanningState(EPlayerScanState::NotScanning, mgr);\n    }\n    break;\n  }\n}\n\nbool CPlayer::ValidateScanning(const CFinalInput& input, const CStateManager& mgr) const {\n  if (ControlMapper::GetDigitalInput(ControlMapper::ECommands::ScanItem, input)) {\n    if (x304_orbitState == EPlayerOrbitState::OrbitObject) {\n      if (const TCastToConstPtr<CActor> act = mgr.ObjectById(x310_orbitTargetId)) {\n        if (act->GetMaterialList().HasMaterial(EMaterialTypes::Scannable)) {\n          const zeus::CVector3f targetToPlayer = GetTranslation() - act->GetTranslation();\n          if (targetToPlayer.canBeNormalized() && targetToPlayer.magnitude() < g_tweakPlayer->GetScanningRange()) {\n            return true;\n          }\n        }\n      }\n    }\n  }\n\n  return false;\n}\n\nstatic bool IsDataLoreResearchScan(CAssetId id) {\n  const auto it = g_MemoryCardSys->LookupScanState(id);\n  if (it == g_MemoryCardSys->GetScanStates().cend()) {\n    return false;\n  }\n\n  switch (it->second) {\n  case CWorldSaveGameInfo::EScanCategory::Data:\n  case CWorldSaveGameInfo::EScanCategory::Lore:\n  case CWorldSaveGameInfo::EScanCategory::Research:\n    return true;\n  default:\n    return false;\n  }\n}\n\nstatic CAssetId UpdatePersistentScanPercent(u32 prevLogScans, u32 logScans, u32 totalLogScans) {\n  if (prevLogScans == logScans) {\n    return {};\n  }\n\n  const float scanPercent = logScans / float(totalLogScans) * 100.f;\n  const float prevScanPercent = prevLogScans / float(totalLogScans) * 100.f;\n  const float scanMessageInterval = g_tweakSlideShow->GetScanPercentInterval();\n  const auto scanPercentProgStep = int(scanPercent / scanMessageInterval);\n  const auto prevScanPercentProgStep = int(prevScanPercent / scanMessageInterval);\n  const bool firstTime = scanPercent > g_GameState->SystemOptions().GetLogScanPercent();\n\n  if (firstTime) {\n    g_GameState->SystemOptions().SetLogScanPercent(u32(scanPercent));\n  }\n\n  if (scanPercentProgStep > prevScanPercentProgStep) {\n    const char* const messageResBase = UnlockMessageResBases[zeus::clamp(0, scanPercentProgStep - 1, 1)];\n    const auto message = std::string(messageResBase).append(1, firstTime ? '1' : '2');\n    const auto* const id = g_ResFactory->GetResourceIdByName(message);\n    if (id != nullptr) {\n      return id->id;\n    }\n  }\n\n  return {};\n}\n\nvoid CPlayer::FinishNewScan(CStateManager& mgr) {\n  const TCastToConstPtr<CActor> act = mgr.ObjectById(x310_orbitTargetId);\n\n  if (!act) {\n    return;\n  }\n\n  if (!act->GetMaterialList().HasMaterial(EMaterialTypes::Scannable)) {\n    return;\n  }\n\n  const auto* const scanInfo = act->GetScannableObjectInfo();\n  if (!scanInfo) {\n    return;\n  }\n\n  if (mgr.GetPlayerState()->GetScanTime(scanInfo->GetScannableObjectId()) < 1.f) {\n    return;\n  }\n\n  if (!IsDataLoreResearchScan(scanInfo->GetScannableObjectId())) {\n    return;\n  }\n\n  const auto scanCompletion = mgr.CalculateScanCompletionRate();\n  const CAssetId message =\n      UpdatePersistentScanPercent(mgr.GetPlayerState()->GetLogScans(), scanCompletion.first, scanCompletion.second);\n  if (message.IsValid()) {\n    mgr.ShowPausedHUDMemo(message, 0.f);\n  }\n  mgr.GetPlayerState()->SetScanCompletionRate(scanCompletion);\n}\n\nvoid CPlayer::SetScanningState(EPlayerScanState state, CStateManager& mgr) {\n  if (x3a8_scanState == state) {\n    return;\n  }\n\n  mgr.SetGameState(CStateManager::EGameState::Running);\n  if (x3a8_scanState == EPlayerScanState::ScanComplete) {\n    if (TCastToPtr<CActor> act = mgr.ObjectById(x3b4_scanningObject)) {\n      act->OnScanStateChanged(EScanState::Done, mgr);\n    }\n  }\n\n  switch (state) {\n  case EPlayerScanState::NotScanning:\n    if (x3a8_scanState == EPlayerScanState::Scanning || x3a8_scanState == EPlayerScanState::ScanComplete) {\n      if (x9c6_30_newScanScanning) {\n        FinishNewScan(mgr);\n      }\n    }\n    x3ac_scanningTime = 0.f;\n    x3b0_curScanTime = 0.f;\n    if (!g_tweakPlayer->GetScanRetention()) {\n      if (const TCastToConstPtr<CActor> act = mgr.ObjectById(x310_orbitTargetId)) {\n        if (act->GetMaterialList().HasMaterial(EMaterialTypes::Scannable)) {\n          if (const auto* scanInfo = act->GetScannableObjectInfo()) {\n            if (mgr.GetPlayerState()->GetScanTime(scanInfo->GetScannableObjectId()) < 1.f) {\n              mgr.GetPlayerState()->SetScanTime(scanInfo->GetScannableObjectId(), 0.f);\n            }\n          }\n        }\n      }\n    }\n    x3b4_scanningObject = kInvalidUniqueId;\n    break;\n  case EPlayerScanState::Scanning:\n    x3b4_scanningObject = x310_orbitTargetId;\n    break;\n  case EPlayerScanState::ScanComplete:\n    if (g_tweakPlayer->GetScanFreezesGame()) {\n      mgr.SetGameState(CStateManager::EGameState::SoftPaused);\n    }\n    x3b4_scanningObject = x310_orbitTargetId;\n    break;\n  }\n\n  x3a8_scanState = state;\n}\n\nbool CPlayer::GetExplorationMode() const {\n  switch (x498_gunHolsterState) {\n  case EGunHolsterState::Holstering:\n  case EGunHolsterState::Holstered:\n    return true;\n  default:\n    return false;\n  }\n}\n\nbool CPlayer::GetCombatMode() const {\n  switch (x498_gunHolsterState) {\n  case EGunHolsterState::Drawing:\n  case EGunHolsterState::Drawn:\n    return true;\n  default:\n    return false;\n  }\n}\n\nvoid CPlayer::RenderGun(const CStateManager& mgr, const zeus::CVector3f& pos) const {\n  if (mgr.GetCameraManager()->IsInCinematicCamera()) {\n    return;\n  }\n\n  if (x490_gun->GetGrappleArm().GetActive() &&\n      x490_gun->GetGrappleArm().GetAnimState() != CGrappleArm::EArmState::Done) {\n    x490_gun->GetGrappleArm().RenderGrappleBeam(mgr, pos);\n  }\n\n  if (mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::Scan &&\n      mgr.GetPlayerState()->GetVisorTransitionFactor() >= 1.f) {\n    return;\n  }\n\n  if ((mgr.GetCameraManager()->IsInFirstPersonCamera() && x2f4_cameraState == EPlayerCameraState::FirstPerson) ||\n      (x2f8_morphBallState == EPlayerMorphBallState::Morphing &&\n       x498_gunHolsterState == EGunHolsterState::Holstering)) {\n    CModelFlags flags(5, 0, 3, zeus::CColor(1.f, x494_gunAlpha));\n    x490_gun->Render(mgr, pos, flags);\n  }\n}\n\nvoid CPlayer::Render(CStateManager& mgr) {\n  bool doRender = x2f4_cameraState != EPlayerCameraState::Spawned;\n  if (!doRender) {\n    if (TCastToConstPtr<CCinematicCamera> cam = mgr.GetCameraManager()->GetCurrentCamera(mgr)) {\n      doRender = (x2f8_morphBallState == EPlayerMorphBallState::Morphed && cam->GetFlags() & 0x40);\n    }\n  }\n\n  if (x2f4_cameraState != EPlayerCameraState::FirstPerson && doRender) {\n    SCOPED_GRAPHICS_DEBUG_GROUP(\"CPlayer::Render\", zeus::skOrange);\n    // CBooModel::SetReflectionCube(m_reflectionCube);\n    bool doTransitionRender = false;\n    bool doBallRender = false;\n    switch (x2f8_morphBallState) {\n    case EPlayerMorphBallState::Unmorphed:\n      x64_modelData->Touch(mgr, 0);\n      CPhysicsActor::Render(mgr);\n      if (HasTransitionBeamModel()) {\n        x7f0_ballTransitionBeamModel->Touch(mgr, 0);\n        CModelFlags flags(0, 0, 3, zeus::skWhite);\n        // flags.m_extendedShader = EExtendedShader::LightingCubeReflection;\n        x7f0_ballTransitionBeamModel->Render(mgr, x7f4_gunWorldXf, x90_actorLights.get(), flags);\n      }\n      break;\n    case EPlayerMorphBallState::Morphing:\n      x768_morphball->TouchModel(mgr);\n      doTransitionRender = true;\n      doBallRender = true;\n      break;\n    case EPlayerMorphBallState::Unmorphing:\n      x490_gun->TouchModel(mgr);\n      doTransitionRender = true;\n      doBallRender = true;\n      break;\n    case EPlayerMorphBallState::Morphed:\n      x64_modelData->Touch(mgr, 0);\n      x768_morphball->Render(mgr, x90_actorLights.get());\n      break;\n    }\n\n    if (doTransitionRender) {\n      CPhysicsActor::Render(mgr);\n      if (HasTransitionBeamModel()) {\n        CModelFlags flags(5, 0, 3, zeus::CColor(1.f, x588_alpha));\n        // flags.m_extendedShader = EExtendedShader::LightingCubeReflection;\n        x7f0_ballTransitionBeamModel->Render(CModelData::EWhichModel::Normal, x7f4_gunWorldXf, x90_actorLights.get(),\n                                             flags);\n      }\n\n      float morphFactor = x574_morphTime / x578_morphDuration;\n      float transitionAlpha;\n      if (morphFactor < 0.05f) {\n        transitionAlpha = 0.f;\n      } else if (morphFactor < 0.1f) {\n        transitionAlpha = (morphFactor - 0.05f) / 0.05f;\n      } else if (morphFactor < 0.8f) {\n        transitionAlpha = 1.f;\n      } else {\n        transitionAlpha = 1.f - (morphFactor - 0.8f) / 0.2f;\n      }\n\n      const auto mdsp1 = int(x730_transitionModels.size() + 1);\n      for (int i = 0; i < x730_transitionModels.size(); ++i) {\n        const int ni = i + 1;\n        const float alpha =\n            transitionAlpha * (1.f - (ni + 1) / float(mdsp1)) * *x71c_transitionModelAlphas.GetEntry(ni);\n        if (alpha != 0.f) {\n          CModelData& data = *x730_transitionModels[i];\n          CModelFlags flags(5, 0, 3, zeus::CColor(1.f, alpha));\n          // flags.m_extendedShader = EExtendedShader::LightingCubeReflection;\n          data.Render(CModelData::GetRenderingModel(mgr), *x658_transitionModelXfs.GetEntry(ni), x90_actorLights.get(),\n                      flags);\n          if (HasTransitionBeamModel()) {\n            CModelFlags transFlags(5, 0, 3, zeus::CColor(1.f, alpha));\n            // transFlags.m_extendedShader = EExtendedShader::LightingCubeReflection;\n            x7f0_ballTransitionBeamModel->Render(CModelData::EWhichModel::Normal, *x594_transisionBeamXfs.GetEntry(ni),\n                                                 x90_actorLights.get(), transFlags);\n          }\n        }\n      }\n\n      if (doBallRender) {\n        float morphFactor = x574_morphTime / x578_morphDuration;\n        float ballAlphaStart = 0.75f;\n        float ballAlphaMag = 4.f;\n        if (x2f8_morphBallState == EPlayerMorphBallState::Unmorphing) {\n          ballAlphaStart = 0.875f;\n          morphFactor = 1.f - morphFactor;\n          ballAlphaMag = 8.f;\n        }\n\n        if (morphFactor > ballAlphaStart) {\n          CModelFlags flags(5, u8(x768_morphball->GetMorphballModelShader()), 3,\n                            zeus::CColor(1.f, ballAlphaMag * (morphFactor - ballAlphaStart)));\n          // flags.m_extendedShader = EExtendedShader::LightingCubeReflection;\n          x768_morphball->GetMorphballModelData().Render(mgr, x768_morphball->GetBallToWorld(), x90_actorLights.get(),\n                                                         flags);\n        }\n\n        if (x2f8_morphBallState == EPlayerMorphBallState::Morphing) {\n          if (morphFactor > 0.5f) {\n            float tmp = (morphFactor - 0.5f) / 0.5f;\n            float rotate = 1.f - tmp;\n            float scale = 0.75f * rotate + 1.f;\n            float ballAlpha;\n            if (tmp < 0.1f) {\n              ballAlpha = 0.f;\n            } else if (tmp < 0.2f) {\n              ballAlpha = (tmp - 0.1f) / 0.1f;\n            } else if (tmp < 0.9f) {\n              ballAlpha = 1.f;\n            } else {\n              ballAlpha = 1.f - (morphFactor - 0.9f) / 0.1f;\n            }\n\n            const float theta = zeus::degToRad(360.f * rotate);\n            ballAlpha *= 0.5f;\n            if (ballAlpha > 0.f) {\n              CModelFlags flags(7, 0, 3, zeus::CColor(1.f, ballAlpha));\n              // flags.m_extendedShader = EExtendedShader::LightingCubeReflection;\n              x768_morphball->GetMorphballModelData().Render(\n                  mgr,\n                  x768_morphball->GetBallToWorld() * zeus::CTransform::RotateZ(theta) * zeus::CTransform::Scale(scale),\n                  x90_actorLights.get(), flags);\n            }\n          }\n          x768_morphball->RenderMorphBallTransitionFlash(mgr);\n        }\n      }\n    }\n  }\n}\n\nvoid CPlayer::RenderReflectedPlayer(CStateManager& mgr) {\n  switch (x2f8_morphBallState) {\n  case EPlayerMorphBallState::Unmorphed:\n  case EPlayerMorphBallState::Morphing:\n  case EPlayerMorphBallState::Unmorphing:\n    SetCalculateLighting(true);\n    if (x2f4_cameraState == EPlayerCameraState::FirstPerson) {\n      const zeus::CFrustum frustum;\n      CActor::PreRender(mgr, frustum);\n    }\n    CPhysicsActor::Render(mgr);\n    if (HasTransitionBeamModel()) {\n      constexpr CModelFlags flags(0, 0, 3, zeus::skWhite);\n      x7f0_ballTransitionBeamModel->Render(mgr, x7f4_gunWorldXf, nullptr, flags);\n    }\n    break;\n  case EPlayerMorphBallState::Morphed:\n    x768_morphball->Render(mgr, x90_actorLights.get());\n    break;\n  }\n}\n\nvoid CPlayer::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CPlayer::PreRender\", zeus::skBlue);\n  if (x2f8_morphBallState == EPlayerMorphBallState::Morphed) {\n    SetCalculateLighting(false);\n    x768_morphball->PreRender(mgr, frustum);\n  } else {\n    SetCalculateLighting(true);\n    if (x2f8_morphBallState == EPlayerMorphBallState::Unmorphed) {\n      x490_gun->PreRender(mgr, frustum, mgr.GetCameraManager()->GetGlobalCameraTranslation(mgr));\n    }\n  }\n\n  if (x2f8_morphBallState == EPlayerMorphBallState::Unmorphed || mgr.GetCameraManager()->IsInCinematicCamera()) {\n    x768_morphball->DeleteBallShadow();\n  } else {\n    x768_morphball->CreateBallShadow();\n    x768_morphball->RenderToShadowTex(mgr);\n  }\n\n  for (auto& model : x730_transitionModels) {\n    model->GetAnimationData()->PreRender();\n  }\n\n  if (x2f4_cameraState != EPlayerCameraState::FirstPerson) {\n    CActor::PreRender(mgr, frustum);\n  }\n}\n\nvoid CPlayer::CalculateRenderBounds() {\n  if (x2f8_morphBallState == EPlayerMorphBallState::Morphed) {\n    const float rad = x768_morphball->GetBallRadius();\n    x9c_renderBounds = zeus::CAABox(GetTranslation() - zeus::CVector3f(rad, rad, 0.f),\n                                    GetTranslation() + zeus::CVector3f(rad, rad, rad * 2.f));\n  } else {\n    CActor::CalculateRenderBounds();\n  }\n}\n\nvoid CPlayer::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) {\n  if (x2f4_cameraState != EPlayerCameraState::FirstPerson && x2f8_morphBallState == EPlayerMorphBallState::Morphed) {\n    if (x768_morphball->IsInFrustum(frustum)) {\n      CActor::AddToRenderer(frustum, mgr);\n    } else {\n      x768_morphball->TouchModel(mgr);\n    }\n  } else {\n    x490_gun->AddToRenderer(frustum, mgr);\n    CActor::AddToRenderer(frustum, mgr);\n  }\n}\n\nvoid CPlayer::ComputeFreeLook(const CFinalInput& input) {\n  const float lookLeft = ControlMapper::GetAnalogInput(ControlMapper::ECommands::LookLeft, input);\n  const float lookRight = ControlMapper::GetAnalogInput(ControlMapper::ECommands::LookRight, input);\n  float lookUp = ControlMapper::GetAnalogInput(ControlMapper::ECommands::LookUp, input);\n  float lookDown = ControlMapper::GetAnalogInput(ControlMapper::ECommands::LookDown, input);\n\n  if (g_GameState->GameOptions().GetInvertYAxis()) {\n    lookUp = ControlMapper::GetAnalogInput(ControlMapper::ECommands::LookDown, input);\n    lookDown = ControlMapper::GetAnalogInput(ControlMapper::ECommands::LookUp, input);\n  }\n\n  if (!g_tweakPlayer->GetStayInFreeLookWhileFiring() &&\n      (ControlMapper::GetDigitalInput(ControlMapper::ECommands::FireOrBomb, input) ||\n       x304_orbitState != EPlayerOrbitState::NoOrbit)) {\n    x3e8_horizFreeLookAngleVel = 0.f;\n    x3f0_vertFreeLookAngleVel = 0.f;\n  } else {\n    if (x3dc_inFreeLook) {\n      x3e8_horizFreeLookAngleVel = (lookLeft - lookRight) * g_tweakPlayer->GetHorizontalFreeLookAngleVel();\n      x3f0_vertFreeLookAngleVel = (lookUp - lookDown) * g_tweakPlayer->GetVerticalFreeLookAngleVel();\n    }\n    if (!x3de_lookAnalogHeld || !x3dd_lookButtonHeld) {\n      x3e8_horizFreeLookAngleVel = 0.f;\n      x3f0_vertFreeLookAngleVel = 0.f;\n    }\n  }\n\n  if (g_tweakPlayer->GetHoldButtonsForFreeLook()) {\n    if ((g_tweakPlayer->GetTwoButtonsForFreeLook() &&\n         (!ControlMapper::GetDigitalInput(ControlMapper::ECommands::LookHold1, input) ||\n          !ControlMapper::GetDigitalInput(ControlMapper::ECommands::LookHold2, input))) ||\n        (!ControlMapper::GetDigitalInput(ControlMapper::ECommands::LookHold1, input) &&\n         !ControlMapper::GetDigitalInput(ControlMapper::ECommands::LookHold2, input))) {\n      x3e8_horizFreeLookAngleVel = 0.f;\n      x3f0_vertFreeLookAngleVel = 0.f;\n    }\n  }\n\n  if (IsMorphBallTransitioning()) {\n    x3e8_horizFreeLookAngleVel = 0.f;\n    x3f0_vertFreeLookAngleVel = 0.f;\n  }\n}\n\nvoid CPlayer::UpdateFreeLookState(const CFinalInput& input, float dt, CStateManager& mgr) {\n  if (x304_orbitState == EPlayerOrbitState::ForcedOrbitObject || IsMorphBallTransitioning() ||\n      x2f8_morphBallState != EPlayerMorphBallState::Unmorphed ||\n      (x3b8_grappleState != EGrappleState::None && x3b8_grappleState != EGrappleState::Firing)) {\n    x3dc_inFreeLook = false;\n    x3dd_lookButtonHeld = false;\n    x3de_lookAnalogHeld = false;\n    x3e8_horizFreeLookAngleVel = 0.f;\n    x3f0_vertFreeLookAngleVel = 0.f;\n    x9c4_25_showCrosshairs = false;\n    return;\n  }\n\n  if (g_tweakPlayer->GetHoldButtonsForFreeLook()) {\n    if ((g_tweakPlayer->GetTwoButtonsForFreeLook() &&\n         (ControlMapper::GetDigitalInput(ControlMapper::ECommands::LookHold1, input) &&\n          ControlMapper::GetDigitalInput(ControlMapper::ECommands::LookHold2, input))) ||\n        (!g_tweakPlayer->GetTwoButtonsForFreeLook() &&\n         (ControlMapper::GetDigitalInput(ControlMapper::ECommands::LookHold1, input) ||\n          ControlMapper::GetDigitalInput(ControlMapper::ECommands::LookHold2, input)))) {\n      if (!x3dd_lookButtonHeld) {\n        const zeus::CVector3f lookDir = mgr.GetCameraManager()->GetFirstPersonCamera()->GetTransform().basis[1];\n        zeus::CVector3f lookDirFlat = lookDir;\n        lookDirFlat.z() = 0.f;\n        x3e4_freeLookYawAngle = 0.f;\n        if (lookDirFlat.canBeNormalized()) {\n          lookDirFlat.normalize();\n          x3ec_freeLookPitchAngle = std::acos(zeus::clamp(-1.f, lookDirFlat.dot(lookDir), 1.f));\n          if (lookDir.z() < 0.f)\n            x3ec_freeLookPitchAngle = -x3ec_freeLookPitchAngle;\n        }\n      }\n      x3dc_inFreeLook = true;\n      x3dd_lookButtonHeld = true;\n\n      x3de_lookAnalogHeld = (ControlMapper::GetAnalogInput(ControlMapper::ECommands::LookLeft, input) >= 0.1f ||\n                             ControlMapper::GetAnalogInput(ControlMapper::ECommands::LookRight, input) >= 0.1f ||\n                             ControlMapper::GetAnalogInput(ControlMapper::ECommands::LookDown, input) >= 0.1f ||\n                             ControlMapper::GetAnalogInput(ControlMapper::ECommands::LookUp, input) >= 0.1f);\n    } else {\n      x3dc_inFreeLook = false;\n      x3dd_lookButtonHeld = false;\n      x3de_lookAnalogHeld = false;\n      x3e8_horizFreeLookAngleVel = 0.f;\n      x3f0_vertFreeLookAngleVel = 0.f;\n    }\n  } else {\n    x3de_lookAnalogHeld = (ControlMapper::GetAnalogInput(ControlMapper::ECommands::LookLeft, input) >= 0.1f ||\n                           ControlMapper::GetAnalogInput(ControlMapper::ECommands::LookRight, input) >= 0.1f ||\n                           ControlMapper::GetAnalogInput(ControlMapper::ECommands::LookDown, input) >= 0.1f ||\n                           ControlMapper::GetAnalogInput(ControlMapper::ECommands::LookUp, input) >= 0.1f);\n    x3dd_lookButtonHeld = false;\n    if (std::fabs(x3e4_freeLookYawAngle) < g_tweakPlayer->GetFreeLookCenteredThresholdAngle() &&\n        std::fabs(x3ec_freeLookPitchAngle) < g_tweakPlayer->GetFreeLookCenteredThresholdAngle()) {\n      if (x3e0_curFreeLookCenteredTime > g_tweakPlayer->GetFreeLookCenteredTime()) {\n        x3dc_inFreeLook = false;\n        x3e8_horizFreeLookAngleVel = 0.f;\n        x3f0_vertFreeLookAngleVel = 0.f;\n      } else {\n        x3e0_curFreeLookCenteredTime += dt;\n      }\n    } else {\n      x3dc_inFreeLook = true;\n      x3e0_curFreeLookCenteredTime = 0.f;\n    }\n  }\n\n  UpdateCrosshairsState(input);\n}\n\nvoid CPlayer::UpdateFreeLook(float dt) {\n  if (GetFrozenState()) {\n    return;\n  }\n\n  float lookDeltaAngle = dt * g_tweakPlayer->GetFreeLookSpeed();\n  if (!x3de_lookAnalogHeld) {\n    lookDeltaAngle = dt * g_tweakPlayer->GetFreeLookSnapSpeed();\n  }\n\n  float angleVelP = x3f0_vertFreeLookAngleVel - x3ec_freeLookPitchAngle;\n  const float vertLookDamp = zeus::clamp(0.f, std::fabs(angleVelP / 1.0471976f), 1.f);\n  float dx = lookDeltaAngle * (2.f * vertLookDamp - std::sin((M_PIF / 2.f) * vertLookDamp));\n  if (0.f <= angleVelP) {\n    x3ec_freeLookPitchAngle += dx;\n  } else {\n    x3ec_freeLookPitchAngle -= dx;\n  }\n\n  angleVelP = x3e8_horizFreeLookAngleVel - x3e4_freeLookYawAngle;\n  dx = lookDeltaAngle * zeus::clamp(0.f, std::fabs(angleVelP / g_tweakPlayer->GetHorizontalFreeLookAngleVel()), 1.f);\n  if (0.f <= angleVelP) {\n    x3e4_freeLookYawAngle += dx;\n  } else {\n    x3e4_freeLookYawAngle -= dx;\n  }\n\n  if (g_tweakPlayer->GetFreeLookTurnsPlayer()) {\n    x3e4_freeLookYawAngle = 0.f;\n  }\n}\n\nfloat CPlayer::GetMaximumPlayerPositiveVerticalVelocity(CStateManager& mgr) const {\n  return mgr.GetPlayerState()->GetItemAmount(CPlayerState::EItemType::SpaceJumpBoots) ? 14.f : 11.666666f;\n}\n\nvoid CPlayer::StartLandingControlFreeze() {\n  x760_controlsFrozen = true;\n  x764_controlsFrozenTimeout = 0.75f;\n}\n\nvoid CPlayer::EndLandingControlFreeze() {\n  x760_controlsFrozen = false;\n  x764_controlsFrozenTimeout = 0.f;\n}\n\nvoid CPlayer::ProcessFrozenInput(float dt, CStateManager& mgr) {\n  x764_controlsFrozenTimeout -= dt;\n  if (x764_controlsFrozenTimeout <= 0.f) {\n    EndLandingControlFreeze();\n  } else {\n    const CFinalInput dummy;\n    if (x2f8_morphBallState == EPlayerMorphBallState::Morphed) {\n      x768_morphball->ComputeBallMovement(dummy, mgr, dt);\n      x768_morphball->UpdateBallDynamics(mgr, dt);\n    } else {\n      ComputeMovement(dummy, mgr, dt);\n    }\n  }\n}\n\nvoid CPlayer::ProcessInput(const CFinalInput& input, CStateManager& mgr) {\n  if (input.ControllerIdx() != 0) {\n    return;\n  }\n\n  if (x2f8_morphBallState != EPlayerMorphBallState::Morphed) {\n    UpdateScanningState(input, mgr, input.DeltaTime());\n  }\n\n  if (mgr.GetGameState() != CStateManager::EGameState::Running) {\n    return;\n  }\n\n  if (!mgr.GetPlayerState()->IsPlayerAlive()) {\n    return;\n  }\n\n  if (GetFrozenState()) {\n    UpdateFrozenState(input, mgr);\n\n    if (GetFrozenState()) {\n      if (x258_movementState == EPlayerMovementState::OnGround ||\n          x258_movementState == EPlayerMovementState::FallingMorphed) {\n        return;\n      }\n\n      const CFinalInput dummyInput;\n      if (x2f8_morphBallState == EPlayerMorphBallState::Morphed) {\n        x768_morphball->ComputeBallMovement(dummyInput, mgr, input.DeltaTime());\n        x768_morphball->UpdateBallDynamics(mgr, input.DeltaTime());\n      } else {\n        ComputeMovement(dummyInput, mgr, input.DeltaTime());\n      }\n\n      return;\n    }\n  }\n\n  if (x760_controlsFrozen) {\n    ProcessFrozenInput(input.DeltaTime(), mgr);\n    return;\n  }\n\n  if (x2f8_morphBallState == EPlayerMorphBallState::Unmorphed && x4a0_failsafeTest->Passes()) {\n    const auto* prim = static_cast<const CCollidableAABox*>(GetCollisionPrimitive());\n    const zeus::CAABox tmpAABB(prim->GetBox().min - 0.2f, prim->GetBox().max + 0.2f);\n    const CCollidableAABox tmpBox(tmpAABB, prim->GetMaterial());\n    CPhysicsActor::Stop();\n    const zeus::CAABox testBounds = prim->GetBox().getTransformedAABox(x34_transform);\n    const zeus::CAABox expandedBounds(testBounds.min - 3.f, testBounds.max + 3.f);\n    CAreaCollisionCache cache(expandedBounds);\n    CGameCollision::BuildAreaCollisionCache(mgr, cache);\n    EntityList nearList;\n    mgr.BuildColliderList(nearList, *this, expandedBounds);\n    const std::optional<zeus::CVector3f> nonIntVec =\n        CGameCollision::FindNonIntersectingVector(mgr, cache, *this, tmpBox, nearList);\n    if (nonIntVec) {\n      x4a0_failsafeTest->Reset();\n      SetTranslation(GetTranslation() + *nonIntVec);\n    }\n  }\n\n  UpdateGrappleState(input, mgr);\n  if (x2f8_morphBallState == EPlayerMorphBallState::Morphed) {\n    float leftDiv = g_tweakBall->GetLeftStickDivisor();\n    const float rightDiv = g_tweakBall->GetRightStickDivisor();\n    if (x26c_attachedActor != kInvalidUniqueId || IsUnderBetaMetroidAttack(mgr)) {\n      leftDiv = 2.f;\n    }\n    const CFinalInput scaledInput = input.ScaleAnalogueSticks(leftDiv, rightDiv);\n    x768_morphball->ComputeBallMovement(scaledInput, mgr, input.DeltaTime());\n    x768_morphball->UpdateBallDynamics(mgr, input.DeltaTime());\n    x4a0_failsafeTest->Reset();\n  } else {\n    if (x304_orbitState == EPlayerOrbitState::Grapple) {\n      ApplyGrappleForces(input, mgr, input.DeltaTime());\n    } else {\n      const CFinalInput scaledInput = input.ScaleAnalogueSticks(IsUnderBetaMetroidAttack(mgr) ? 3.f : 1.f, 1.f);\n      ComputeMovement(scaledInput, mgr, input.DeltaTime());\n    }\n\n    if (ShouldSampleFailsafe(mgr)) {\n      CFailsafeTest::EInputState inputState = CFailsafeTest::EInputState::Moving;\n      if (x258_movementState == EPlayerMovementState::ApplyJump) {\n        inputState = CFailsafeTest::EInputState::StartingJump;\n      } else if (x258_movementState == EPlayerMovementState::Jump) {\n        inputState = CFailsafeTest::EInputState::Jump;\n      }\n      x4a0_failsafeTest->AddSample(inputState, GetTranslation(), x138_velocity,\n                                   zeus::CVector2f(input.ALeftX(), input.ALeftY()));\n    }\n  }\n\n  ComputeFreeLook(input);\n  UpdateFreeLookState(input, input.DeltaTime(), mgr);\n  UpdateOrbitInput(input, mgr);\n  UpdateOrbitZone(mgr);\n  UpdateGunState(input, mgr);\n  UpdateVisorState(input, input.DeltaTime(), mgr);\n\n  if (x2f8_morphBallState == EPlayerMorphBallState::Morphed ||\n      (x2f8_morphBallState == EPlayerMorphBallState::Unmorphed && x498_gunHolsterState == EGunHolsterState::Drawn)) {\n    x490_gun->ProcessInput(input, mgr);\n    if (x2f8_morphBallState == EPlayerMorphBallState::Morphed && x26c_attachedActor != kInvalidUniqueId) {\n      if (ControlMapper::GetPressInput(ControlMapper::ECommands::TurnLeft, input) ||\n          ControlMapper::GetPressInput(ControlMapper::ECommands::TurnRight, input) ||\n          ControlMapper::GetPressInput(ControlMapper::ECommands::Forward, input) ||\n          ControlMapper::GetPressInput(ControlMapper::ECommands::Backward, input) ||\n          ControlMapper::GetPressInput(ControlMapper::ECommands::JumpOrBoost, input)) {\n        xa28_attachedActorStruggle += input.DeltaTime() * 600.f * input.DeltaTime();\n        if (xa28_attachedActorStruggle > 1.f) {\n          xa28_attachedActorStruggle = 1.f;\n        }\n      } else {\n        const float tmp = 7.5f * input.DeltaTime();\n        xa28_attachedActorStruggle -= input.DeltaTime() * std::min(1.f, xa28_attachedActorStruggle * tmp + tmp);\n        if (xa28_attachedActorStruggle < 0.f) {\n          xa28_attachedActorStruggle = 0.f;\n        }\n      }\n    }\n  }\n\n  UpdateCameraState(mgr);\n  UpdateMorphBallState(input.DeltaTime(), input, mgr);\n  UpdateCameraTimers(input.DeltaTime(), input);\n  UpdateFootstepSounds(input, mgr, input.DeltaTime());\n  x2a8_timeSinceJump += input.DeltaTime();\n\n  if (CheckSubmerged()) {\n    SetSoundEventPitchBend(0);\n  } else {\n    SetSoundEventPitchBend(8192);\n  }\n\n  CalculateLeaveMorphBallDirection(input);\n}\n\nbool CPlayer::ShouldSampleFailsafe(CStateManager& mgr) const {\n  const TCastToConstPtr<CCinematicCamera> cineCam = mgr.GetCameraManager()->GetCurrentCamera(mgr);\n  if (!mgr.GetPlayerState()->IsPlayerAlive()) {\n    return false;\n  }\n  return x2f4_cameraState != EPlayerCameraState::Spawned || !cineCam || (cineCam->GetFlags() & 0x80) == 0;\n}\n\nvoid CPlayer::CalculateLeaveMorphBallDirection(const CFinalInput& input) {\n  if (x2f8_morphBallState != EPlayerMorphBallState::Morphed) {\n    x518_leaveMorphDir = x50c_moveDir;\n  } else {\n    if (ControlMapper::GetAnalogInput(ControlMapper::ECommands::Forward, input) > 0.3f ||\n        ControlMapper::GetAnalogInput(ControlMapper::ECommands::Backward, input) > 0.3f ||\n        ControlMapper::GetAnalogInput(ControlMapper::ECommands::TurnLeft, input) > 0.3f ||\n        ControlMapper::GetAnalogInput(ControlMapper::ECommands::TurnRight, input) > 0.3f) {\n      if (x138_velocity.magnitude() > 0.5f) {\n        x518_leaveMorphDir = x50c_moveDir;\n      }\n    }\n  }\n}\n\nvoid CPlayer::CalculatePlayerControlDirection(CStateManager& mgr) {\n  if (x9c4_30_controlDirOverride) {\n    if (x9d8_controlDirOverrideDir.canBeNormalized()) {\n      x540_controlDir = x9d8_controlDirOverrideDir.normalized();\n      x54c_controlDirFlat = x9d8_controlDirOverrideDir;\n      x54c_controlDirFlat.z() = 0.f;\n      if (x54c_controlDirFlat.canBeNormalized()) {\n        x54c_controlDirFlat.normalize();\n      } else {\n        x540_controlDir = x54c_controlDirFlat = zeus::skForward;\n      }\n    } else {\n      x540_controlDir = x54c_controlDirFlat = zeus::skForward;\n    }\n  } else {\n    const zeus::CVector3f camToPlayer =\n        GetTranslation() - mgr.GetCameraManager()->GetCurrentCamera(mgr)->GetTranslation();\n    if (!camToPlayer.canBeNormalized()) {\n      x540_controlDir = x54c_controlDirFlat = zeus::skForward;\n    } else {\n      zeus::CVector3f camToPlayerFlat(camToPlayer.x(), camToPlayer.y(), 0.f);\n      if (camToPlayerFlat.canBeNormalized()) {\n        if (camToPlayerFlat.magnitude() > g_tweakBall->GetBallCameraControlDistance()) {\n          x540_controlDir = camToPlayer.normalized();\n          if (camToPlayerFlat.canBeNormalized()) {\n            camToPlayerFlat.normalize();\n            switch (x2f8_morphBallState) {\n            case EPlayerMorphBallState::Morphed:\n              x54c_controlDirFlat = camToPlayerFlat;\n              break;\n            default:\n              x540_controlDir = GetTransform().basis[1];\n              x54c_controlDirFlat = x540_controlDir;\n              x54c_controlDirFlat.z() = 0.f;\n              if (x54c_controlDirFlat.canBeNormalized()) {\n                x54c_controlDirFlat.normalize();\n              }\n            }\n          } else if (x2f8_morphBallState != EPlayerMorphBallState::Morphed) {\n            x540_controlDir = GetTransform().basis[1];\n            x54c_controlDirFlat.z() = 0.f;\n            if (x54c_controlDirFlat.canBeNormalized()) {\n              x54c_controlDirFlat.normalize();\n            }\n          }\n        } else {\n          if (x4fc_flatMoveSpeed < 0.25f) {\n            x540_controlDir = camToPlayer;\n            x54c_controlDirFlat = camToPlayerFlat;\n          } else if (x2f8_morphBallState != EPlayerMorphBallState::Morphed) {\n            x540_controlDir = GetTransform().basis[1];\n            x54c_controlDirFlat.z() = 0.f;\n            if (x54c_controlDirFlat.canBeNormalized()) {\n              x54c_controlDirFlat.normalize();\n            }\n          }\n        }\n      }\n    }\n  }\n}\n\nvoid CPlayer::CalculatePlayerMovementDirection(float dt) {\n  if (x2f8_morphBallState == EPlayerMorphBallState::Morphing ||\n      x2f8_morphBallState == EPlayerMorphBallState::Unmorphing) {\n    return;\n  }\n\n  const zeus::CVector3f delta = GetTranslation() - x524_lastPosForDirCalc;\n  if (delta.canBeNormalized() && delta.magnitude() > 0.02f) {\n    x53c_timeMoving += dt;\n    x4f8_moveSpeed = std::fabs(delta.magnitude() / dt);\n    x500_lookDir = delta.normalized();\n    zeus::CVector3f flatDelta(delta.x(), delta.y(), 0.f);\n    if (flatDelta.canBeNormalized()) {\n      x4fc_flatMoveSpeed = std::fabs(flatDelta.magnitude() / dt);\n      flatDelta.normalize();\n      switch (x2f8_morphBallState) {\n      case EPlayerMorphBallState::Morphed:\n        if (x4fc_flatMoveSpeed > 0.25f) {\n          x50c_moveDir = flatDelta;\n        }\n        x530_gunDir = x50c_moveDir;\n        x524_lastPosForDirCalc = GetTranslation();\n        break;\n      default:\n        x500_lookDir = GetTransform().basis[1];\n        x50c_moveDir = x500_lookDir;\n        x50c_moveDir.z() = 0.f;\n        if (x50c_moveDir.canBeNormalized()) {\n          x50c_moveDir.normalize();\n        }\n        x530_gunDir = x50c_moveDir;\n        x524_lastPosForDirCalc = GetTranslation();\n        break;\n      }\n    } else {\n      if (x2f8_morphBallState != EPlayerMorphBallState::Morphed) {\n        x500_lookDir = GetTransform().basis[1];\n        x50c_moveDir = x500_lookDir;\n        x50c_moveDir.z() = 0.f;\n        if (x50c_moveDir.canBeNormalized()) {\n          x50c_moveDir.normalize();\n        }\n        x530_gunDir = x50c_moveDir;\n        x524_lastPosForDirCalc = GetTranslation();\n      }\n      x4fc_flatMoveSpeed = 0.f;\n    }\n  } else {\n    x53c_timeMoving = 0.f;\n    switch (x2f8_morphBallState) {\n    case EPlayerMorphBallState::Morphed:\n    case EPlayerMorphBallState::Morphing:\n    case EPlayerMorphBallState::Unmorphing:\n      x500_lookDir = x50c_moveDir;\n      break;\n    default:\n      x500_lookDir = GetTransform().basis[1];\n      x50c_moveDir = x500_lookDir;\n      x50c_moveDir.z() = 0.f;\n      if (x50c_moveDir.canBeNormalized()) {\n        x50c_moveDir.normalize();\n      }\n      x530_gunDir = x50c_moveDir;\n      x524_lastPosForDirCalc = GetTranslation();\n      break;\n    }\n    x4f8_moveSpeed = 0.f;\n    x4fc_flatMoveSpeed = 0.f;\n  }\n\n  x50c_moveDir.z() = 0.f;\n  if (x50c_moveDir.canBeNormalized()) {\n    x500_lookDir.normalize();\n  }\n}\n\nvoid CPlayer::UnFreeze(CStateManager& stateMgr) {\n  if (!GetFrozenState()) {\n    return;\n  }\n\n  x750_frozenTimeout = 0.f;\n  x754_iceBreakJumps = 0;\n  CPhysicsActor::Stop();\n  ClearForcesAndTorques();\n  RemoveMaterial(EMaterialTypes::Immovable, stateMgr);\n  if (!stateMgr.GetCameraManager()->IsInCinematicCamera() && xa0c_iceTextureId.IsValid()) {\n    std::optional<TToken<CGenDescription>> gpsm;\n    gpsm.emplace(g_SimplePool->GetObj(SObjectTag(FOURCC('PART'), xa0c_iceTextureId)));\n    auto* effect = new CHUDBillboardEffect(gpsm, {}, stateMgr.AllocateUniqueId(), true, \"FrostExplosion\",\n                                           CHUDBillboardEffect::GetNearClipDistance(stateMgr),\n                                           CHUDBillboardEffect::GetScaleForPOV(stateMgr), zeus::skWhite, zeus::skOne3f,\n                                           zeus::skZero3f);\n    stateMgr.AddObject(effect);\n    CSfxHandle hnd = CSfxManager::SfxStart(SFXcrk_break_final, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n    ApplySubmergedPitchBend(hnd);\n  }\n\n  x768_morphball->Stop();\n  SetVisorSteam(0.f, 6.f / 14.f, 1.f / 14.f, xa08_steamTextureId, false);\n}\n\nvoid CPlayer::Freeze(CStateManager& stateMgr, CAssetId steamTxtr, u16 sfx, CAssetId iceTxtr) {\n  if (stateMgr.GetCameraManager()->IsInCinematicCamera() || GetFrozenState()) {\n    return;\n  }\n\n  bool showMsg;\n  if (x2f8_morphBallState == EPlayerMorphBallState::Unmorphed) {\n    showMsg = g_GameState->SystemOptions().GetShowFrozenFpsMessage();\n  } else {\n    showMsg = g_GameState->SystemOptions().GetShowFrozenBallMessage();\n  }\n\n  if (showMsg) {\n    const char16_t* msg = g_MainStringTable->GetString(x2f8_morphBallState != EPlayerMorphBallState::Morphed ? 19 : 20);\n    const CHUDMemoParms parms(5.f, true, false, false);\n    MP1::CSamusHud::DisplayHudMemo(msg, parms);\n  }\n\n  x750_frozenTimeout = x758_frozenTimeoutBias + g_tweakPlayer->GetFrozenTimeout();\n  x754_iceBreakJumps = -x75c_additionalIceBreakJumps;\n\n  CPhysicsActor::Stop();\n  ClearForcesAndTorques();\n  if (x3b8_grappleState != EGrappleState::None) {\n    BreakGrapple(EPlayerOrbitRequest::Freeze, stateMgr);\n  } else {\n    SetOrbitRequest(EPlayerOrbitRequest::Freeze, stateMgr);\n  }\n\n  AddMaterial(EMaterialTypes::Immovable, stateMgr);\n  xa08_steamTextureId = steamTxtr;\n  xa0c_iceTextureId = iceTxtr;\n  CSfxHandle hnd = CSfxManager::SfxStart(sfx, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n  ApplySubmergedPitchBend(hnd);\n  EndLandingControlFreeze();\n}\n\nbool CPlayer::GetFrozenState() const { return x750_frozenTimeout > 0.f; }\n\nvoid CPlayer::UpdateFrozenState(const CFinalInput& input, CStateManager& mgr) {\n  x750_frozenTimeout -= input.DeltaTime();\n  if (x750_frozenTimeout > 0.f) {\n    SetVisorSteam(0.7f, 6.f / 14.f, 1.f / 14.f, xa08_steamTextureId, false);\n  } else {\n    UnFreeze(mgr);\n    return;\n  }\n  if (x258_movementState == EPlayerMovementState::OnGround ||\n      x258_movementState == EPlayerMovementState::FallingMorphed) {\n    Stop();\n    ClearForcesAndTorques();\n  }\n  x7a0_visorSteam.Update(input.DeltaTime());\n\n  switch (x2f8_morphBallState) {\n  case EPlayerMorphBallState::Morphed:\n    x490_gun->ProcessInput(input, mgr);\n    break;\n  case EPlayerMorphBallState::Unmorphed:\n  case EPlayerMorphBallState::Morphing:\n  case EPlayerMorphBallState::Unmorphing:\n    if (ControlMapper::GetPressInput(ControlMapper::ECommands::JumpOrBoost, input)) {\n      if (x754_iceBreakJumps != 0) {\n        /* Subsequent Breaks */\n        CSfxHandle hnd = CSfxManager::SfxStart(SFXcrk_break_subsequent, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n        ApplySubmergedPitchBend(hnd);\n      } else {\n        /* Initial Break */\n        CSfxHandle hnd = CSfxManager::SfxStart(SFXcrk_break_initial, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n        ApplySubmergedPitchBend(hnd);\n      }\n      x754_iceBreakJumps += 1;\n      if (x754_iceBreakJumps > g_tweakPlayer->GetIceBreakJumpCount()) {\n        g_GameState->SystemOptions().IncrementFrozenFpsCount();\n        const CHUDMemoParms info(0.f, true, true, true);\n        MP1::CSamusHud::DisplayHudMemo(u\"\", info);\n        UnFreeze(mgr);\n      }\n    }\n    break;\n  }\n}\n\nvoid CPlayer::UpdateStepCameraZBias(float dt) {\n  float newBias = GetTranslation().z() + GetUnbiasedEyeHeight();\n  if (x258_movementState == EPlayerMovementState::OnGround && !IsMorphBallTransitioning()) {\n    const float oldBias = newBias;\n    if (!x9c5_31_stepCameraZBiasDirty) {\n      const float delta = newBias - x9cc_stepCameraZBias;\n      float newDelta = 5.f * dt;\n      if (delta > 0.f) {\n        if (delta > dt * x138_velocity.z() && delta > newDelta) {\n          if (delta > GetStepUpHeight()) {\n            newDelta += delta - GetStepUpHeight();\n          }\n          newBias = x9cc_stepCameraZBias + newDelta;\n        }\n      } else {\n        if (delta < dt * x138_velocity.z() && delta < -newDelta) {\n          if (delta < -GetStepDownHeight()) {\n            newDelta += -delta - GetStepDownHeight();\n          }\n          newBias = x9cc_stepCameraZBias - newDelta;\n        }\n      }\n    }\n    x9c8_eyeZBias = newBias - oldBias;\n  } else {\n    x9c8_eyeZBias = 0.f;\n  }\n  x9cc_stepCameraZBias = newBias;\n  x9c5_31_stepCameraZBiasDirty = false;\n}\n\nvoid CPlayer::UpdateWaterSurfaceCameraBias(CStateManager& mgr) {\n  const TCastToConstPtr<CScriptWater> water = mgr.GetObjectById(xc4_fluidId);\n  if (!water) {\n    return;\n  }\n\n  const float waterZ = water->GetTriggerBoundsWR().max.z();\n  const float biasToEyeDelta = GetEyePosition().z() - x9c8_eyeZBias;\n  const float waterToDeltaDelta = biasToEyeDelta - waterZ;\n\n  if (biasToEyeDelta >= waterZ && waterToDeltaDelta <= 0.25f) {\n    x9c8_eyeZBias += waterZ + 0.25f - biasToEyeDelta;\n  } else if (biasToEyeDelta < waterZ && waterToDeltaDelta >= -0.2f) {\n    x9c8_eyeZBias += waterZ - 0.2f - biasToEyeDelta;\n  }\n}\n\nvoid CPlayer::UpdateEnvironmentDamageCameraShake(float dt, CStateManager& mgr) {\n  xa2c_damageLoopSfxDelayTicks = std::min(2, xa2c_damageLoopSfxDelayTicks + 1);\n\n  if (xa10_envDmgCounter == 0) {\n    return;\n  }\n\n  if (xa14_envDmgCameraShakeTimer == 0.f) {\n    mgr.GetCameraManager()->AddCameraShaker(CCameraShakeData::BuildPhazonCameraShakeData(1.f, 0.075f), false);\n  }\n  xa14_envDmgCameraShakeTimer += dt;\n  if (xa14_envDmgCameraShakeTimer > 2.f) {\n    xa14_envDmgCameraShakeTimer = 0.f;\n  }\n}\n\nvoid CPlayer::UpdatePhazonDamage(float dt, CStateManager& mgr) {\n  if (x4_areaId == kInvalidAreaId) {\n    return;\n  }\n\n  const CGameArea* area = mgr.GetWorld()->GetAreaAlways(x4_areaId);\n  if (!area->IsPostConstructed()) {\n    return;\n  }\n\n  bool touchingPhazon = false;\n  EPhazonType phazonType;\n  if (const CScriptAreaAttributes* attr = area->GetPostConstructed()->x10d8_areaAttributes) {\n    phazonType = attr->GetPhazonType();\n  } else {\n    phazonType = EPhazonType::None;\n  }\n\n  if (phazonType == EPhazonType::Orange ||\n      (!mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::PhazonSuit) && phazonType == EPhazonType::Blue)) {\n    constexpr CMaterialFilter filter = CMaterialFilter::MakeInclude({EMaterialTypes::Phazon});\n    if (x2f8_morphBallState == EPlayerMorphBallState::Morphed) {\n      touchingPhazon = x768_morphball->BallCloseToCollision(mgr, 2.9f, filter);\n    } else {\n      constexpr CMaterialList primMaterial(EMaterialTypes::Player, EMaterialTypes::Solid);\n      const CCollidableSphere prim(\n          zeus::CSphere(GetCollisionPrimitive()->CalculateAABox(x34_transform).center(), 4.25f), primMaterial);\n      EntityList nearList;\n      mgr.BuildColliderList(nearList, *this, prim.CalculateLocalAABox());\n      if (CGameCollision::DetectStaticCollisionBoolean(mgr, prim, zeus::CTransform(), filter)) {\n        touchingPhazon = true;\n      } else {\n        for (const auto& id : nearList) {\n          if (const TCastToConstPtr<CPhysicsActor> act = mgr.GetObjectById(id)) {\n            const CInternalCollisionStructure::CPrimDesc prim0(prim, filter, zeus::CTransform());\n            const CInternalCollisionStructure::CPrimDesc prim1(\n                *act->GetCollisionPrimitive(), CMaterialFilter::skPassEverything, act->GetPrimitiveTransform());\n            if (CCollisionPrimitive::CollideBoolean(prim0, prim1)) {\n              touchingPhazon = true;\n              break;\n            }\n          }\n        }\n      }\n    }\n  }\n\n  if (touchingPhazon) {\n    xa18_phazonDamageLag += dt;\n    xa18_phazonDamageLag = std::min(xa18_phazonDamageLag, 3.f);\n    if (xa18_phazonDamageLag > 0.2f) {\n      const float damage = (xa18_phazonDamageLag - 0.2f) / 3.f * 60.f * dt;\n      const CDamageInfo dInfo(\n          CWeaponMode(phazonType == EPhazonType::Orange ? EWeaponType::OrangePhazon : EWeaponType::Phazon), damage, 0.f,\n          0.f);\n      mgr.ApplyDamage(kInvalidUniqueId, GetUniqueId(), kInvalidUniqueId, dInfo,\n                      CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), zeus::skZero3f);\n    }\n  } else {\n    xa18_phazonDamageLag -= dt;\n    xa18_phazonDamageLag = std::min(0.2f, xa18_phazonDamageLag);\n    xa18_phazonDamageLag = std::max(0.f, xa18_phazonDamageLag);\n  }\n\n  xa1c_threatOverride = std::min(1.f, xa18_phazonDamageLag / 0.2f);\n}\n\nvoid CPlayer::ResetPlayerHintState() {\n  x9c4_26_ = true;\n  x9c4_27_canEnterMorphBall = true;\n  x9c4_28_canLeaveMorphBall = true;\n  x9c4_30_controlDirOverride = false;\n  x9c6_24_extendTargetDistance = false;\n  x9c6_26_outOfBallLookAtHint = false;\n  x9c4_29_spiderBallControlXY = false;\n  x9c6_29_disableInput = false;\n  x9c7_25_outOfBallLookAtHintActor = false;\n  x768_morphball->SetBoostEnabed(true);\n  ResetControlDirectionInterpolation();\n}\n\nbool CPlayer::SetAreaPlayerHint(const CScriptPlayerHint& hint, CStateManager& mgr) {\n  x9c4_26_ = (hint.GetOverrideFlags() & 0x1) != 0;\n  x9c4_27_canEnterMorphBall = (hint.GetOverrideFlags() & 0x40) == 0;\n  x9c4_28_canLeaveMorphBall = (hint.GetOverrideFlags() & 0x20) == 0;\n  x9c4_30_controlDirOverride = (hint.GetOverrideFlags() & 0x2) != 0;\n  if (x9c4_30_controlDirOverride) {\n    x9d8_controlDirOverrideDir = hint.GetTransform().basis[1];\n  }\n  x9c6_24_extendTargetDistance = (hint.GetOverrideFlags() & 0x4) != 0;\n  x9c6_26_outOfBallLookAtHint = (hint.GetOverrideFlags() & 0x8) != 0;\n  x9c4_29_spiderBallControlXY = (hint.GetOverrideFlags() & 0x10) != 0;\n  x9c6_29_disableInput = (hint.GetOverrideFlags() & 0x80) != 0;\n  x9c7_25_outOfBallLookAtHintActor = (hint.GetOverrideFlags() & 0x4000) != 0;\n  x768_morphball->SetBoostEnabed((hint.GetOverrideFlags() & 0x100) == 0);\n  bool switchedVisor = false;\n  if ((hint.GetOverrideFlags() & 0x200) != 0) {\n    if (mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::CombatVisor)) {\n      mgr.GetPlayerState()->StartTransitionToVisor(CPlayerState::EPlayerVisor::Combat);\n    }\n    switchedVisor = true;\n  }\n  if ((hint.GetOverrideFlags() & 0x400) != 0) {\n    if (mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::ScanVisor)) {\n      mgr.GetPlayerState()->StartTransitionToVisor(CPlayerState::EPlayerVisor::Scan);\n    }\n    switchedVisor = true;\n  }\n  if ((hint.GetOverrideFlags() & 0x800) != 0) {\n    if (mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::ThermalVisor)) {\n      mgr.GetPlayerState()->StartTransitionToVisor(CPlayerState::EPlayerVisor::Thermal);\n    }\n    switchedVisor = true;\n  }\n  if ((hint.GetOverrideFlags() & 0x1000) != 0) {\n    if (mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::XRayVisor)) {\n      mgr.GetPlayerState()->StartTransitionToVisor(CPlayerState::EPlayerVisor::XRay);\n    }\n    switchedVisor = true;\n  }\n  return switchedVisor;\n}\n\nvoid CPlayer::AddToPlayerHintRemoveList(TUniqueId id, CStateManager& mgr) {\n  const TCastToConstPtr<CScriptPlayerHint> hint = mgr.ObjectById(id);\n  if (!hint) {\n    return;\n  }\n\n  for (const auto& existId : x93c_playerHintsToRemove) {\n    if (id == existId) {\n      return;\n    }\n  }\n\n  if (x93c_playerHintsToRemove.size() != 32) {\n    x93c_playerHintsToRemove.push_back(id);\n  }\n}\n\nvoid CPlayer::AddToPlayerHintAddList(TUniqueId id, CStateManager& mgr) {\n  const TCastToConstPtr<CScriptPlayerHint> hint = mgr.ObjectById(id);\n  if (!hint) {\n    return;\n  }\n\n  for (const auto& existId : x980_playerHintsToAdd) {\n    if (id == existId) {\n      return;\n    }\n  }\n\n  if (x980_playerHintsToAdd.size() != 32) {\n    x980_playerHintsToAdd.push_back(id);\n  }\n}\n\nvoid CPlayer::DeactivatePlayerHint(TUniqueId id, CStateManager& mgr) {\n  const TCastToPtr<CScriptPlayerHint> hint = mgr.ObjectById(id);\n  if (!hint) {\n    return;\n  }\n\n  for (const auto& existId : x93c_playerHintsToRemove) {\n    if (id == existId) {\n      return;\n    }\n  }\n\n  if (x93c_playerHintsToRemove.size() != 32) {\n    x93c_playerHintsToRemove.push_back(id);\n    hint->ClearObjectList();\n    hint->SetDeactivated();\n  }\n}\n\nvoid CPlayer::UpdatePlayerHints(CStateManager& mgr) {\n  bool removedHint = false;\n  for (auto it = x838_playerHints.begin(); it != x838_playerHints.end();) {\n    auto& p = *it;\n    const TCastToConstPtr<CScriptPlayerHint> hint = mgr.ObjectById(p.second);\n    if (!hint) {\n      it = x838_playerHints.erase(it);\n      removedHint = true;\n    } else {\n      ++it;\n    }\n  }\n\n  bool needsNewHint = false;\n  for (const auto& id : x93c_playerHintsToRemove) {\n    for (auto it = x838_playerHints.begin(); it != x838_playerHints.end();) {\n      if (it->second == id) {\n        it = x838_playerHints.erase(it);\n        if (id == x830_playerHint) {\n          needsNewHint = true;\n        }\n        break;\n      }\n      ++it;\n    }\n  }\n  x93c_playerHintsToRemove.clear();\n\n  bool addedHint = false;\n  for (const auto& id : x980_playerHintsToAdd) {\n    if (const TCastToConstPtr<CScriptPlayerHint> hint = mgr.ObjectById(id)) {\n      bool exists = false;\n      for (auto& p : x838_playerHints) {\n        if (p.second == id) {\n          exists = true;\n          break;\n        }\n      }\n      if (!exists) {\n        x838_playerHints.emplace_back(hint->GetPriority(), id);\n        addedHint = true;\n      }\n    }\n  }\n  x980_playerHintsToAdd.clear();\n\n  if (needsNewHint || addedHint || removedHint) {\n    std::sort(x838_playerHints.begin(), x838_playerHints.end(),\n              [](const auto& a, const auto& b) { return a.first < b.first; });\n\n    if ((needsNewHint || removedHint) && x838_playerHints.empty()) {\n      x830_playerHint = kInvalidUniqueId;\n      x834_playerHintPriority = 1000;\n      ResetPlayerHintState();\n      return;\n    }\n\n    const CScriptPlayerHint* foundHint = nullptr;\n    bool foundHintInArea = false;\n    for (const auto& p : x838_playerHints) {\n      if (const TCastToConstPtr<CScriptPlayerHint> hint = mgr.ObjectById(p.second)) {\n        foundHint = hint.GetPtr();\n        if (hint->GetAreaIdAlways() == mgr.GetNextAreaId()) {\n          foundHintInArea = true;\n          break;\n        }\n      }\n    }\n\n    if (!foundHintInArea) {\n      x830_playerHint = kInvalidUniqueId;\n      x834_playerHintPriority = 1000;\n      ResetPlayerHintState();\n    }\n\n    if (foundHint != nullptr && foundHintInArea && x830_playerHint != foundHint->GetUniqueId()) {\n      x830_playerHint = foundHint->GetUniqueId();\n      x834_playerHintPriority = foundHint->GetPriority();\n      if (SetAreaPlayerHint(*foundHint, mgr)) {\n        DeactivatePlayerHint(x830_playerHint, mgr);\n      }\n    }\n  }\n}\n\nvoid CPlayer::UpdateBombJumpStuff() {\n  if (x9d0_bombJumpCount == 0) {\n    return;\n  }\n  x9d4_bombJumpCheckDelayFrames -= 1;\n  if (x9d4_bombJumpCheckDelayFrames > 0) {\n    return;\n  }\n\n  zeus::CVector3f velFlat = x138_velocity;\n  velFlat.z() = 0.f;\n  if (x258_movementState == EPlayerMovementState::OnGround ||\n      (velFlat.canBeNormalized() && velFlat.magnitude() > 6.f)) {\n    x9d0_bombJumpCount = 0;\n  }\n}\n\nvoid CPlayer::UpdateTransitionFilter(float dt, CStateManager& mgr) {\n  if (x824_transitionFilterTimer <= 0.f) {\n    mgr.GetCameraFilterPass(8).DisableFilter(0.f);\n    return;\n  }\n\n  x824_transitionFilterTimer += dt;\n  if (x824_transitionFilterTimer > 1.25f) {\n    x824_transitionFilterTimer = 0.f;\n    mgr.GetCameraFilterPass(8).DisableFilter(0.f);\n    return;\n  }\n\n  if (x824_transitionFilterTimer < 0.95f) {\n    return;\n  }\n\n  const float time = x824_transitionFilterTimer - 0.95f;\n  zeus::CColor color(1.f, 0.87f, 0.54f, 1.f);\n  if (time < 0.1f) {\n    color.a() = 0.3f * time / 0.1f;\n  } else if (time >= 0.15f) {\n    color.a() = 1.f - zeus::clamp(-1.f, (time - 0.15f) / 0.15f, 1.f) * 0.3f;\n  } else {\n    color.a() = 0.3f;\n  }\n\n  mgr.GetCameraFilterPass(8).SetFilter(EFilterType::Add, EFilterShape::ScanLinesEven, 0.f, color, {});\n}\n\nvoid CPlayer::ResetControlDirectionInterpolation() {\n  x9c6_25_interpolatingControlDir = false;\n  x9f8_controlDirInterpTime = 0.f;\n}\n\nvoid CPlayer::SetControlDirectionInterpolation(float time) {\n  x9c6_25_interpolatingControlDir = true;\n  x9f8_controlDirInterpTime = 0.f;\n  x9fc_controlDirInterpDur = time;\n}\n\nvoid CPlayer::UpdatePlayerControlDirection(float dt, CStateManager& mgr) {\n  const zeus::CVector3f oldControlDir = x540_controlDir;\n  const zeus::CVector3f oldControlDirFlat = x54c_controlDirFlat;\n  CalculatePlayerControlDirection(mgr);\n\n  if (!x9c6_25_interpolatingControlDir || x2f8_morphBallState != EPlayerMorphBallState::Morphed) {\n    return;\n  }\n\n  x9f8_controlDirInterpTime += dt;\n  if (x9f8_controlDirInterpTime > x9fc_controlDirInterpDur) {\n    x9f8_controlDirInterpTime = x9fc_controlDirInterpDur;\n    ResetControlDirectionInterpolation();\n  }\n\n  const float t = zeus::clamp(-1.f, x9f8_controlDirInterpTime / x9fc_controlDirInterpDur, 1.f);\n  x540_controlDir = zeus::CVector3f::lerp(oldControlDir, x540_controlDir, t);\n  x54c_controlDirFlat = zeus::CVector3f::lerp(oldControlDirFlat, x540_controlDir, t);\n}\n\nvoid CPlayer::Think(float dt, CStateManager& mgr) {\n  UpdateStepCameraZBias(dt);\n  UpdateWaterSurfaceCameraBias(mgr);\n  UpdateEnvironmentDamageCameraShake(dt, mgr);\n  UpdatePhazonDamage(dt, mgr);\n  UpdateFreeLook(dt);\n  UpdatePlayerHints(mgr);\n\n  if (x2b0_outOfWaterTicks < 2) {\n    x2b0_outOfWaterTicks += 1;\n  }\n\n  x9c5_24_ = x9c4_31_inWaterMovement;\n  x9c4_31_inWaterMovement = x9c5_25_splashUpdated;\n  x9c5_25_splashUpdated = false;\n  UpdateBombJumpStuff();\n\n  if (0.f < x288_startingJumpTimeout) {\n    x288_startingJumpTimeout -= dt;\n    if (0.f >= x288_startingJumpTimeout) {\n      SetMoveState(EPlayerMovementState::ApplyJump, mgr);\n    }\n  }\n\n  if (x2a0_ > 0.f) {\n    x2a0_ += dt;\n  }\n  if (x774_samusVoiceTimeout > 0.f) {\n    x774_samusVoiceTimeout -= dt;\n  }\n  if (0.f < x28c_sjTimer) {\n    x28c_sjTimer -= dt;\n  }\n\n  x300_fallingTime += dt;\n  if (x258_movementState == EPlayerMovementState::FallingMorphed && x300_fallingTime > 0.4f) {\n    SetMoveState(EPlayerMovementState::ApplyJump, mgr);\n  }\n\n  if (x570_immuneTimer > 0.f) {\n    x570_immuneTimer -= dt;\n  }\n\n  Update(dt, mgr);\n  UpdateTransitionFilter(dt, mgr);\n  CalculatePlayerMovementDirection(dt);\n  UpdatePlayerControlDirection(dt, mgr);\n\n#if 0\n    if (g_factoryManager == 4) {\n        x2b0_ = 0;\n    } else {\n        x2ac_surfaceRestraint = ESurfaceRestraints::Normal;\n    }\n#endif\n\n  if (x2f8_morphBallState == EPlayerMorphBallState::Unmorphed && x9c5_27_camSubmerged &&\n      mgr.GetCameraManager()->GetFluidCounter() == 0) {\n    if (const auto* water = GetVisorRunoffEffect(mgr)) {\n      if (const auto& effect = water->GetVisorRunoffEffect()) {\n        auto* sheets = new CHUDBillboardEffect(\n            *effect, {}, mgr.AllocateUniqueId(), true, \"WaterSheets\", CHUDBillboardEffect::GetNearClipDistance(mgr),\n            CHUDBillboardEffect::GetScaleForPOV(mgr), zeus::skWhite, zeus::skOne3f, zeus::skZero3f);\n        mgr.AddObject(sheets);\n      }\n      CSfxHandle hnd = CSfxManager::SfxStart(water->GetVisorRunoffSfx(), 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n      ApplySubmergedPitchBend(hnd);\n    }\n  }\n  x9c5_27_camSubmerged = mgr.GetCameraManager()->GetFluidCounter() != 0;\n\n  if (x2f8_morphBallState != EPlayerMorphBallState::Morphed) {\n    if (std::fabs(x34_transform.basis[0].z()) > FLT_EPSILON || std::fabs(x34_transform.basis[1].z()) > FLT_EPSILON) {\n      const zeus::CVector3f backupTranslation = GetTranslation();\n      const zeus::CVector3f lookDirFlat(x34_transform.basis[1].x(), x34_transform.basis[1].y(), 0.f);\n      if (lookDirFlat.canBeNormalized()) {\n        SetTransform(zeus::lookAt(zeus::skZero3f, lookDirFlat.normalized()));\n      } else {\n        SetTransform(zeus::CTransform());\n      }\n      SetTranslation(backupTranslation);\n    }\n  }\n\n  x794_lastVelocity = x138_velocity;\n}\n\nvoid CPlayer::PreThink(float dt, CStateManager& mgr) {\n  x558_wasDamaged = false;\n  x55c_damageAmt = 0.f;\n  x560_prevDamageAmt = 0.f;\n  x564_damageLocation = zeus::skZero3f;\n  xa04_preThinkDt = dt;\n}\n\nvoid CPlayer::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) {\n  switch (msg) {\n  case EScriptObjectMessage::OnFloor:\n    if (x258_movementState != EPlayerMovementState::OnGround && x2f8_morphBallState != EPlayerMorphBallState::Morphed &&\n        x300_fallingTime > 0.3f) {\n      if (x258_movementState != EPlayerMovementState::Falling) {\n        float hardThres = 30.f * 2.f * -g_tweakPlayer->GetNormalGravAccel();\n        hardThres = (hardThres != 0.f) ? hardThres * (1.f / std::sqrt(hardThres)) : 0.f;\n        const float landVol = zeus::clamp(95.f, 1.6f * -x794_lastVelocity.z() + 95.f, 127.f) / 127.f;\n        u16 landSfx;\n        if (-x794_lastVelocity.z() < hardThres) {\n          landSfx = GetMaterialSoundUnderPlayer(mgr, skPlayerLandSfxSoft.data(), skPlayerLandSfxSoft.size(), 0xffff);\n        } else {\n          landSfx = GetMaterialSoundUnderPlayer(mgr, skPlayerLandSfxHard.data(), skPlayerLandSfxHard.size(), 0xffff);\n          StartSamusVoiceSfx(SFXsam_voxland_02, 1.f, 5);\n          x55c_damageAmt = 0.f;\n          x560_prevDamageAmt = 10.f;\n          x564_damageLocation = x34_transform.origin;\n          x558_wasDamaged = true;\n          mgr.GetCameraManager()->AddCameraShaker(CCameraShakeData::BuildLandingCameraShakeData(0.3f, 1.25f), false);\n          StartLandingControlFreeze();\n        }\n        CSfxHandle handle = CSfxManager::SfxStart(landSfx, landVol, 0.f, false, 0x7f, false, kInvalidAreaId);\n        ApplySubmergedPitchBend(handle);\n\n        float rumbleMag = -x794_lastVelocity.z() / 110.f;\n        if (rumbleMag > 0.f) {\n          if (std::fabs(rumbleMag) > 0.8f) {\n            rumbleMag = (rumbleMag > 0.f) ? 0.8f : -0.8f;\n          }\n          mgr.GetRumbleManager().Rumble(mgr, ERumbleFxId::PlayerLand, rumbleMag, ERumblePriority::One);\n        }\n\n        x2a0_ = 0.f;\n      }\n    } else if (x258_movementState != EPlayerMovementState::OnGround &&\n               x2f8_morphBallState == EPlayerMorphBallState::Morphed) {\n      if (x138_velocity.z() < -40.f && !x768_morphball->GetIsInHalfPipeMode() &&\n          x258_movementState == EPlayerMovementState::ApplyJump && x300_fallingTime > 0.75f) {\n        SetCoefficientOfRestitutionModifier(0.2f);\n      }\n      x768_morphball->StartLandingSfx();\n      if (x138_velocity.z() < -5.f) {\n        float rumbleMag = -x138_velocity.z() / 110.f * 0.5f;\n        if (std::fabs(rumbleMag) > 0.8f) {\n          rumbleMag = (rumbleMag > 0.f) ? 0.8f : -0.8f;\n        }\n        mgr.GetRumbleManager().Rumble(mgr, ERumbleFxId::PlayerLand, rumbleMag, ERumblePriority::One);\n        x2a0_ = 0.f;\n      }\n      if (x138_velocity.z() < -30.f) {\n        float rumbleMag = -x138_velocity.z() / 110.f;\n        if (std::fabs(rumbleMag) > 0.8f) {\n          rumbleMag = (rumbleMag > 0.f) ? 0.8f : -0.8f;\n        }\n        mgr.GetRumbleManager().Rumble(mgr, ERumbleFxId::PlayerLand, rumbleMag, ERumblePriority::One);\n        x2a0_ = 0.f;\n      }\n    }\n    x300_fallingTime = 0.f;\n    SetMoveState(EPlayerMovementState::OnGround, mgr);\n    break;\n  case EScriptObjectMessage::Falling:\n    if (x2f8_morphBallState == EPlayerMorphBallState::Morphed) {\n      if (x768_morphball->GetSpiderBallState() == CMorphBall::ESpiderBallState::Active) {\n        break;\n      }\n    }\n    if (x2f8_morphBallState != EPlayerMorphBallState::Morphed) {\n      SetMoveState(EPlayerMovementState::Falling, mgr);\n    } else if (x258_movementState == EPlayerMovementState::OnGround) {\n      SetMoveState(EPlayerMovementState::FallingMorphed, mgr);\n    }\n    break;\n  case EScriptObjectMessage::LandOnNotFloor:\n    if (x2f8_morphBallState == EPlayerMorphBallState::Morphed &&\n        x768_morphball->GetSpiderBallState() == CMorphBall::ESpiderBallState::Active &&\n        x258_movementState != EPlayerMovementState::ApplyJump) {\n      SetMoveState(EPlayerMovementState::ApplyJump, mgr);\n    }\n    break;\n  case EScriptObjectMessage::OnIceSurface:\n    x2ac_surfaceRestraint = ESurfaceRestraints::Ice;\n    break;\n  case EScriptObjectMessage::OnMudSlowSurface:\n    x2ac_surfaceRestraint = ESurfaceRestraints::Organic;\n    break;\n  case EScriptObjectMessage::OnNormalSurface:\n    x2ac_surfaceRestraint = ESurfaceRestraints::Normal;\n    break;\n  case EScriptObjectMessage::InSnakeWeed:\n    x2ac_surfaceRestraint = ESurfaceRestraints::Shrubbery;\n    break;\n  case EScriptObjectMessage::AddSplashInhabitant: {\n    SetInFluid(true, sender);\n    UpdateSubmerged(mgr);\n    const CRayCastResult result =\n        mgr.RayStaticIntersection(x34_transform.origin, zeus::skDown, 0.5f * GetEyeHeight(), SolidMaterialFilter);\n    if (result.IsInvalid()) {\n      SetVelocityWR(x138_velocity * 0.095f);\n      xfc_constantForce *= zeus::CVector3f(0.095f);\n    }\n    break;\n  }\n  case EScriptObjectMessage::UpdateSplashInhabitant:\n    UpdateSubmerged(mgr);\n    if (CheckSubmerged() && !mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::GravitySuit)) {\n      if (const TCastToConstPtr<CScriptWater> water = mgr.ObjectById(xc4_fluidId)) {\n        switch (water->GetFluidPlane().GetFluidType()) {\n        case EFluidType::NormalWater:\n          x2b0_outOfWaterTicks = 0;\n          break;\n        case EFluidType::Lava:\n        case EFluidType::ThickLava:\n          x2ac_surfaceRestraint = ESurfaceRestraints::Lava;\n          break;\n        case EFluidType::PoisonWater:\n          x2b0_outOfWaterTicks = 0;\n          break;\n        case EFluidType::PhazonFluid:\n          x2ac_surfaceRestraint = ESurfaceRestraints::Phazon;\n          break;\n        default:\n          break;\n        }\n      }\n    }\n    x9c5_25_splashUpdated = true;\n    break;\n  case EScriptObjectMessage::RemoveSplashInhabitant:\n    SetInFluid(false, kInvalidUniqueId);\n    UpdateSubmerged(mgr);\n    break;\n  case EScriptObjectMessage::ProjectileCollide:\n    x378_orbitPreventionTimer = g_tweakPlayer->GetOrbitPreventionTime();\n    SetOrbitRequest(EPlayerOrbitRequest::ProjectileCollide, mgr);\n    break;\n  case EScriptObjectMessage::AddPlatformRider:\n    x82e_ridingPlatform = sender;\n    break;\n  case EScriptObjectMessage::Damage:\n    if (const TCastToConstPtr<CEnergyProjectile> energ = mgr.ObjectById(sender)) {\n      if (True(energ->GetAttribField() & EProjectileAttrib::StaticInterference)) {\n        mgr.GetPlayerState()->GetStaticInterference().AddSource(GetUniqueId(), 0.3f, energ->GetInterferenceDuration());\n      }\n    }\n    break;\n  case EScriptObjectMessage::Deleted:\n    mgr.GetPlayerState()->ResetVisor();\n    x730_transitionModels.clear();\n    break;\n  default:\n    break;\n  }\n\n  x490_gun->AcceptScriptMsg(msg, sender, mgr);\n  x768_morphball->AcceptScriptMsg(msg, sender, mgr);\n  CActor::AcceptScriptMsg(msg, sender, mgr);\n}\n\nvoid CPlayer::SetVisorSteam(float targetAlpha, float alphaInDur, float alphaOutDur, CAssetId txtr,\n                            bool affectsThermal) {\n  x7a0_visorSteam.SetSteam(targetAlpha, alphaInDur, alphaOutDur, txtr, affectsThermal);\n}\n\nvoid CPlayer::UpdateFootstepSounds(const CFinalInput& input, CStateManager& mgr, float dt) {\n  if (x2f8_morphBallState != EPlayerMorphBallState::Unmorphed || x258_movementState != EPlayerMovementState::OnGround ||\n      x3dc_inFreeLook || x3dd_lookButtonHeld) {\n    return;\n  }\n\n  float sfxVol = 1.f;\n  x78c_footstepSfxTimer += dt;\n  float turn = TurnInput(input);\n  const float forward = std::fabs(ForwardInput(input, turn));\n  turn = std::fabs(turn);\n  float sfxDelay = 0.f;\n  if (forward > 0.05f || x304_orbitState != EPlayerOrbitState::NoOrbit) {\n    const float vel = std::min(1.f, x138_velocity.magnitude() / GetActualFirstPersonMaxVelocity(dt));\n    if (vel > 0.05f) {\n      sfxDelay = -0.475f * vel + 0.85f;\n      if (x790_footstepSfxSel == EFootstepSfx::None) {\n        x790_footstepSfxSel = EFootstepSfx::Left;\n      }\n    } else {\n      x78c_footstepSfxTimer = 0.f;\n      x790_footstepSfxSel = EFootstepSfx::None;\n    }\n\n    sfxVol = 0.3f * vel + 0.7f;\n  } else if (turn > 0.05f) {\n    if (x790_footstepSfxSel == EFootstepSfx::Left) {\n      sfxDelay = -0.813f * turn + 1.f;\n    } else {\n      sfxDelay = -2.438f * turn + 3.f;\n    }\n    if (x790_footstepSfxSel == EFootstepSfx::None) {\n      x790_footstepSfxSel = EFootstepSfx::Left;\n      sfxDelay = x78c_footstepSfxTimer;\n    }\n    sfxVol = 0.75f;\n  } else {\n    x78c_footstepSfxTimer = 0.f;\n    x790_footstepSfxSel = EFootstepSfx::None;\n  }\n\n  if (x790_footstepSfxSel != EFootstepSfx::None && x78c_footstepSfxTimer > sfxDelay) {\n    static float EarHeight = GetEyeHeight() - 0.1f;\n    if (xe6_24_fluidCounter != 0 && x828_distanceUnderWater > 0.f && x828_distanceUnderWater < EarHeight) {\n      if (x82c_inLava) {\n        if (x790_footstepSfxSel == EFootstepSfx::Left) {\n          CSfxHandle hnd = CSfxManager::SfxStart(SFXlav_wlklava_00, sfxVol, 0.f, true, 0x7f, false, kInvalidAreaId);\n          ApplySubmergedPitchBend(hnd);\n        } else {\n          CSfxHandle hnd = CSfxManager::SfxStart(SFXlav_wlklava_01, sfxVol, 0.f, true, 0x7f, false, kInvalidAreaId);\n          ApplySubmergedPitchBend(hnd);\n        }\n      } else {\n        if (x790_footstepSfxSel == EFootstepSfx::Left) {\n          CSfxHandle hnd = CSfxManager::SfxStart(SFXsam_wlkwater_00, sfxVol, 0.f, true, 0x7f, false, kInvalidAreaId);\n          ApplySubmergedPitchBend(hnd);\n        } else {\n          CSfxHandle hnd = CSfxManager::SfxStart(SFXsam_wlkwater_01, sfxVol, 0.f, true, 0x7f, false, kInvalidAreaId);\n          ApplySubmergedPitchBend(hnd);\n        }\n      }\n    } else {\n      u16 sfx;\n      if (x790_footstepSfxSel == EFootstepSfx::Left) {\n        sfx = GetMaterialSoundUnderPlayer(mgr, skLeftStepSounds.data(), skLeftStepSounds.size(), 0xffff);\n      } else {\n        sfx = GetMaterialSoundUnderPlayer(mgr, skRightStepSounds.data(), skRightStepSounds.size(), 0xffff);\n      }\n      CSfxHandle hnd = CSfxManager::SfxStart(sfx, sfxVol, 0.f, true, 0x7f, false, kInvalidAreaId);\n      ApplySubmergedPitchBend(hnd);\n    }\n\n    x78c_footstepSfxTimer = 0.f;\n    if (x790_footstepSfxSel == EFootstepSfx::Left) {\n      x790_footstepSfxSel = EFootstepSfx::Right;\n    } else {\n      x790_footstepSfxSel = EFootstepSfx::Left;\n    }\n  }\n}\n\nu16 CPlayer::GetMaterialSoundUnderPlayer(const CStateManager& mgr, const u16* table, size_t length, u16 defId) const {\n  u16 ret = defId;\n  zeus::CAABox aabb = GetBoundingBox();\n  aabb.accumulateBounds(x34_transform.origin + zeus::skDown);\n  EntityList nearList;\n  mgr.BuildNearList(nearList, aabb, SolidMaterialFilter, nullptr);\n  TUniqueId collideId = kInvalidUniqueId;\n  const CRayCastResult result =\n      mgr.RayWorldIntersection(collideId, x34_transform.origin, zeus::skDown, 1.5f, SolidMaterialFilter, nearList);\n  if (result.IsValid()) {\n    ret = SfxIdFromMaterial(result.GetMaterial(), table, length, defId);\n  }\n  return ret;\n}\n\nu16 CPlayer::SfxIdFromMaterial(const CMaterialList& mat, const u16* idList, size_t tableLen, u16 defId) {\n  u16 id = defId;\n  for (size_t i = 0; i < tableLen; ++i) {\n    if (mat.HasMaterial(EMaterialTypes(i)) && idList[i] != 0xFFFF) {\n      id = idList[i];\n    }\n  }\n  return id;\n}\n\nvoid CPlayer::UpdateCrosshairsState(const CFinalInput& input) {\n  x9c4_25_showCrosshairs = ControlMapper::GetDigitalInput(ControlMapper::ECommands::ShowCrosshairs, input);\n}\n\nvoid CPlayer::UpdateVisorTransition(float dt, CStateManager& mgr) {\n  if (mgr.GetPlayerState()->GetIsVisorTransitioning()) {\n    mgr.GetPlayerState()->UpdateVisorTransition(dt);\n  }\n}\n\nvoid CPlayer::UpdateVisorState(const CFinalInput& input, float dt, CStateManager& mgr) {\n  x7a0_visorSteam.Update(dt);\n  if (x7a0_visorSteam.AffectsThermal()) {\n    mgr.SetThermalColdScale2(mgr.GetThermalColdScale2() + x7a0_visorSteam.GetAlpha());\n  }\n\n  if (x304_orbitState == EPlayerOrbitState::Grapple ||\n      TCastToPtr<CScriptGrapplePoint>(mgr.ObjectById(x310_orbitTargetId)) ||\n      x2f8_morphBallState != EPlayerMorphBallState::Unmorphed || mgr.GetPlayerState()->GetIsVisorTransitioning() ||\n      x3a8_scanState != EPlayerScanState::NotScanning) {\n    return;\n  }\n\n  if (mgr.GetPlayerState()->GetTransitioningVisor() == CPlayerState::EPlayerVisor::Scan &&\n      (ControlMapper::GetDigitalInput(ControlMapper::ECommands::FireOrBomb, input) ||\n       ControlMapper::GetDigitalInput(ControlMapper::ECommands::MissileOrPowerBomb, input)) &&\n      mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::CombatVisor)) {\n    mgr.GetPlayerState()->StartTransitionToVisor(CPlayerState::EPlayerVisor::Combat);\n    DrawGun(mgr);\n  }\n\n  for (size_t i = 0; i < skVisorToItemMapping.size(); ++i) {\n    const auto mapping = skVisorToItemMapping[i];\n\n    if (mgr.GetPlayerState()->HasPowerUp(mapping.first) && ControlMapper::GetPressInput(mapping.second, input)) {\n      x9c4_24_visorChangeRequested = true;\n      const auto visor = CPlayerState::EPlayerVisor(i);\n\n      if (mgr.GetPlayerState()->GetTransitioningVisor() != visor) {\n        mgr.GetPlayerState()->StartTransitionToVisor(visor);\n        if (visor == CPlayerState::EPlayerVisor::Scan) {\n          HolsterGun(mgr);\n        } else {\n          DrawGun(mgr);\n        }\n      }\n    }\n  }\n}\n\nvoid CPlayer::UpdateGunState(const CFinalInput& input, CStateManager& mgr) {\n  switch (x498_gunHolsterState) {\n  case EGunHolsterState::Drawn: {\n    bool needsHolster = false;\n    if (g_tweakPlayer->GetGunButtonTogglesHolster()) {\n      if (ControlMapper::GetPressInput(ControlMapper::ECommands::ToggleHolster, input)) {\n        needsHolster = true;\n      }\n      if (!ControlMapper::GetDigitalInput(ControlMapper::ECommands::FireOrBomb, input) &&\n          !ControlMapper::GetDigitalInput(ControlMapper::ECommands::MissileOrPowerBomb, input) &&\n          g_tweakPlayer->GetGunNotFiringHolstersGun()) {\n        x49c_gunHolsterRemTime -= input.DeltaTime();\n        if (x49c_gunHolsterRemTime <= 0.f) {\n          needsHolster = true;\n        }\n      }\n    } else {\n      if (!ControlMapper::GetDigitalInput(ControlMapper::ECommands::FireOrBomb, input) &&\n          !ControlMapper::GetDigitalInput(ControlMapper::ECommands::MissileOrPowerBomb, input) &&\n          x490_gun->IsFidgeting()) {\n        if (g_tweakPlayer->GetGunNotFiringHolstersGun()) {\n          x49c_gunHolsterRemTime -= input.DeltaTime();\n        }\n      } else {\n        x49c_gunHolsterRemTime = g_tweakPlayerGun->GetGunNotFiringTime();\n      }\n    }\n\n    if (needsHolster) {\n      HolsterGun(mgr);\n    }\n    break;\n  }\n  case EGunHolsterState::Drawing: {\n    if (x49c_gunHolsterRemTime > 0.f) {\n      x49c_gunHolsterRemTime -= input.DeltaTime();\n    } else {\n      x498_gunHolsterState = EGunHolsterState::Drawn;\n      x49c_gunHolsterRemTime = g_tweakPlayerGun->GetGunNotFiringTime();\n    }\n    break;\n  }\n  case EGunHolsterState::Holstered: {\n    bool needsDraw = false;\n    if (ControlMapper::GetDigitalInput(ControlMapper::ECommands::FireOrBomb, input) ||\n        ControlMapper::GetDigitalInput(ControlMapper::ECommands::MissileOrPowerBomb, input) ||\n        x3b8_grappleState == EGrappleState::None) {\n      needsDraw = true;\n    } else if (g_tweakPlayer->GetGunButtonTogglesHolster() &&\n               ControlMapper::GetPressInput(ControlMapper::ECommands::ToggleHolster, input)) {\n      needsDraw = true;\n    }\n\n    if (x3b8_grappleState != EGrappleState::None ||\n        mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::Scan ||\n        mgr.GetPlayerState()->GetTransitioningVisor() == CPlayerState::EPlayerVisor::Scan) {\n      needsDraw = false;\n    }\n\n    if (needsDraw) {\n      DrawGun(mgr);\n    }\n    break;\n  }\n  case EGunHolsterState::Holstering:\n    if (x49c_gunHolsterRemTime > 0.f) {\n      x49c_gunHolsterRemTime -= input.DeltaTime();\n    } else {\n      x498_gunHolsterState = EGunHolsterState::Holstered;\n    }\n    break;\n  }\n}\n\nvoid CPlayer::ResetGun(CStateManager& mgr) {\n  x498_gunHolsterState = EGunHolsterState::Holstered;\n  x49c_gunHolsterRemTime = 0.f;\n  x490_gun->CancelFiring(mgr);\n  ResetAimTargetPrediction(kInvalidUniqueId);\n}\n\nvoid CPlayer::UpdateArmAndGunTransforms(float dt, CStateManager& mgr) {\n  zeus::CVector3f grappleOffset;\n  zeus::CVector3f gunOffset;\n  if (x2f8_morphBallState == EPlayerMorphBallState::Morphed) {\n    gunOffset = {0.f, 0.f, 0.6f};\n  } else {\n    gunOffset = g_tweakPlayerGun->GetGunPosition();\n    grappleOffset =\n        x490_gun->GetGrappleArm().IsArmMoving() ? zeus::skZero3f : g_tweakPlayerGun->GetGrapplingArmPosition();\n    gunOffset.z() += GetEyeHeight();\n    grappleOffset.z() += GetEyeHeight();\n  }\n\n  UpdateGunTransform(gunOffset + x76c_cameraBob->GetGunBobTransformation().origin, mgr);\n  UpdateGrappleArmTransform(grappleOffset, mgr, dt);\n}\n\nvoid CPlayer::ForceGunOrientation(const zeus::CTransform& xf, CStateManager& mgr) {\n  ResetGun(mgr);\n  x530_gunDir = xf.basis[1];\n  x490_gun->SetTransform(xf);\n  UpdateArmAndGunTransforms(0.01f, mgr);\n}\n\nvoid CPlayer::UpdateCameraState(CStateManager& mgr) { UpdateCinematicState(mgr); }\n\nvoid CPlayer::UpdateDebugCamera(CStateManager& mgr) {\n  // Empty\n}\n\nvoid CPlayer::UpdateCameraTimers(float dt, const CFinalInput& input) {\n  if (x3dc_inFreeLook || x3dd_lookButtonHeld) {\n    x294_jumpCameraTimer = 0.f;\n    x29c_fallCameraTimer = 0.f;\n    return;\n  }\n\n  if (g_tweakPlayer->GetFiringCancelsCameraPitch()) {\n    if (ControlMapper::GetDigitalInput(ControlMapper::ECommands::FireOrBomb, input) ||\n        ControlMapper::GetDigitalInput(ControlMapper::ECommands::MissileOrPowerBomb, input)) {\n      if (x288_startingJumpTimeout > 0.f) {\n        x2a4_cancelCameraPitch = true;\n        return;\n      }\n    }\n  }\n\n  if (ControlMapper::GetPressInput(ControlMapper::ECommands::JumpOrBoost, input)) {\n    ++x298_jumpPresses;\n  }\n\n  if (ControlMapper::GetDigitalInput(ControlMapper::ECommands::JumpOrBoost, input) && x294_jumpCameraTimer > 0.f &&\n      !x2a4_cancelCameraPitch && x298_jumpPresses <= 2) {\n    x294_jumpCameraTimer += dt;\n  }\n\n  if (x29c_fallCameraTimer > 0.f && !x2a4_cancelCameraPitch) {\n    x29c_fallCameraTimer += dt;\n  }\n}\n\nvoid CPlayer::UpdateMorphBallState(float dt, const CFinalInput& input, CStateManager& mgr) {\n  if (!ControlMapper::GetPressInput(ControlMapper::ECommands::Morph, input)) {\n    return;\n  }\n\n  switch (x2f8_morphBallState) {\n  case EPlayerMorphBallState::Unmorphed:\n    if (mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::MorphBall) && CanEnterMorphBallState(mgr, 0.f)) {\n      x574_morphTime = 0.f;\n      x578_morphDuration = 1.f;\n      TransitionToMorphBallState(dt, mgr);\n    } else {\n      CSfxHandle hnd = CSfxManager::SfxStart(SFXwpn_invalid_action, 1.f, 0.f, true, 0x7f, false, kInvalidAreaId);\n      ApplySubmergedPitchBend(hnd);\n    }\n    break;\n  case EPlayerMorphBallState::Morphed: {\n    zeus::CVector3f posDelta;\n    if (CanLeaveMorphBallState(mgr, posDelta)) {\n      SetTranslation(x34_transform.origin + posDelta);\n      x574_morphTime = 0.f;\n      x578_morphDuration = 1.f;\n      TransitionFromMorphBallState(mgr);\n    } else {\n      CSfxHandle hnd = CSfxManager::SfxStart(SFXwpn_invalid_action, 1.f, 0.f, true, 0x7f, false, kInvalidAreaId);\n      ApplySubmergedPitchBend(hnd);\n    }\n    break;\n  }\n  default:\n    break;\n  }\n}\n\nCFirstPersonCamera& CPlayer::GetFirstPersonCamera(CStateManager& mgr) {\n  return *mgr.GetCameraManager()->GetFirstPersonCamera();\n}\n\nvoid CPlayer::UpdateGunTransform(const zeus::CVector3f& gunPos, CStateManager& mgr) {\n  const float eyeHeight = GetEyeHeight();\n  const zeus::CTransform camXf = mgr.GetCameraManager()->GetCurrentCameraTransform(mgr);\n  zeus::CTransform gunXf = camXf;\n\n  zeus::CVector3f viewGunPos;\n  if (x2f8_morphBallState == EPlayerMorphBallState::Morphing) {\n    viewGunPos = camXf * (gunPos - zeus::CVector3f(0.f, 0.f, eyeHeight));\n  } else {\n    viewGunPos = camXf.rotate(gunPos - zeus::CVector3f(0.f, 0.f, eyeHeight)) + GetEyePosition();\n  }\n\n  const zeus::CUnitVector3f rightDir(camXf.basis[0]);\n  gunXf.origin = viewGunPos;\n\n  switch (x498_gunHolsterState) {\n  case EGunHolsterState::Drawing: {\n    const float liftAngle = zeus::clamp(-1.f, x49c_gunHolsterRemTime / 0.45f, 1.f);\n    if (liftAngle > 0.01f) {\n      gunXf = zeus::CQuaternion::fromAxisAngle(rightDir, -liftAngle * g_tweakPlayerGun->GetFixedVerticalAim())\n                  .toTransform() *\n              camXf.getRotation();\n      gunXf.origin = viewGunPos;\n    }\n    break;\n  }\n  case EGunHolsterState::Holstered: {\n    gunXf = zeus::CQuaternion::fromAxisAngle(rightDir, -g_tweakPlayerGun->GetFixedVerticalAim()).toTransform() *\n            camXf.getRotation();\n    gunXf.origin = viewGunPos;\n    break;\n  }\n  case EGunHolsterState::Holstering: {\n    float liftAngle = 1.f - zeus::clamp(-1.f, x49c_gunHolsterRemTime / g_tweakPlayerGun->GetGunHolsterTime(), 1.f);\n    if (x2f8_morphBallState == EPlayerMorphBallState::Morphing) {\n      liftAngle = 1.f - zeus::clamp(-1.f, x49c_gunHolsterRemTime / 0.1f, 1.f);\n    }\n    if (liftAngle > 0.01f) {\n      gunXf = zeus::CQuaternion::fromAxisAngle(rightDir, -liftAngle * g_tweakPlayerGun->GetFixedVerticalAim())\n                  .toTransform() *\n              camXf.getRotation();\n      gunXf.origin = viewGunPos;\n    }\n    break;\n  }\n  default:\n    break;\n  }\n\n  x490_gun->SetTransform(gunXf);\n  UpdateAimTargetPrediction(gunXf, mgr);\n  UpdateAssistedAiming(gunXf, mgr);\n}\n\nvoid CPlayer::UpdateAssistedAiming(const zeus::CTransform& xf, const CStateManager& mgr) {\n  zeus::CTransform assistXf = xf;\n  const TCastToConstPtr<CActor> target = mgr.GetObjectById(x3f4_aimTarget);\n\n  if (target) {\n    zeus::CVector3f gunToTarget = x480_assistedTargetAim - xf.origin;\n    zeus::CVector3f gunToTargetFlat = gunToTarget;\n    gunToTargetFlat.z() = 0.f;\n    const float gunToTargetFlatMag = gunToTargetFlat.magnitude();\n    zeus::CVector3f gunDirFlat = xf.basis[1];\n    gunDirFlat.z() = 0.f;\n    const float gunDirFlatMag = gunDirFlat.magnitude();\n    if (gunToTargetFlat.canBeNormalized() && gunDirFlat.canBeNormalized()) {\n      gunToTargetFlat = gunToTargetFlat / gunToTargetFlatMag;\n      gunDirFlat = gunDirFlat / gunDirFlatMag;\n      float vAngleDelta = std::atan2(gunToTarget.z(), gunToTargetFlatMag) - std::atan2(xf.basis[1].z(), gunDirFlatMag);\n      bool hasVAngleDelta = true;\n      if (!x9c6_27_aimingAtProjectile && std::fabs(vAngleDelta) > g_tweakPlayer->GetAimAssistVerticalAngle()) {\n        if (g_tweakPlayer->GetAssistedAimingIgnoreVertical()) {\n          vAngleDelta = 0.f;\n          hasVAngleDelta = false;\n        } else if (vAngleDelta > 0.f) {\n          vAngleDelta = g_tweakPlayer->GetAimAssistVerticalAngle();\n        } else {\n          vAngleDelta = -g_tweakPlayer->GetAimAssistVerticalAngle();\n        }\n      }\n\n      const bool targetToLeft = gunDirFlat.cross(gunToTargetFlat).z() > 0.f;\n      float hAngleDelta = std::acos(zeus::clamp(-1.f, gunDirFlat.dot(gunToTargetFlat), 1.f));\n      bool hasHAngleDelta = true;\n      if (!x9c6_27_aimingAtProjectile && std::fabs(hAngleDelta) > g_tweakPlayer->GetAimAssistHorizontalAngle()) {\n        hAngleDelta = g_tweakPlayer->GetAimAssistHorizontalAngle();\n        if (g_tweakPlayer->GetAssistedAimingIgnoreHorizontal()) {\n          hAngleDelta = 0.f;\n          hasHAngleDelta = false;\n        }\n      }\n\n      if (targetToLeft)\n        hAngleDelta = -hAngleDelta;\n\n      if (!hasVAngleDelta || !hasHAngleDelta) {\n        vAngleDelta = 0.f;\n        hAngleDelta = 0.f;\n      }\n\n      gunToTarget.x() = std::sin(hAngleDelta) * std::cos(vAngleDelta);\n      gunToTarget.y() = std::cos(hAngleDelta) * std::cos(vAngleDelta);\n      gunToTarget.z() = std::sin(vAngleDelta);\n      gunToTarget = xf.rotate(gunToTarget);\n      assistXf = zeus::lookAt(zeus::skZero3f, gunToTarget, zeus::skUp);\n    }\n  }\n\n  x490_gun->SetAssistAimTransform(assistXf);\n}\n\nvoid CPlayer::UpdateAimTargetPrediction(const zeus::CTransform& xf, const CStateManager& mgr) {\n  if (x3f4_aimTarget == kInvalidUniqueId) {\n    return;\n  }\n\n  const TCastToConstPtr<CActor> target = mgr.GetObjectById(x3f4_aimTarget);\n  if (!target) {\n    return;\n  }\n\n  x9c6_27_aimingAtProjectile = TCastToConstPtr<CGameProjectile>(target.GetPtr()).IsValid();\n  const zeus::CVector3f instantTarget = target->GetAimPosition(mgr, 0.f);\n  const zeus::CVector3f gunToTarget = instantTarget - xf.origin;\n  const float timeToTarget = gunToTarget.magnitude() / x490_gun->GetBeamVelocity();\n  const zeus::CVector3f predictTarget = target->GetAimPosition(mgr, timeToTarget);\n  const zeus::CVector3f predictOffset = predictTarget - instantTarget;\n  x3f8_targetAimPosition = instantTarget;\n\n  if (predictOffset.magnitude() < 0.1f) {\n    x404_aimTargetAverage.AddValue(zeus::skZero3f);\n  } else {\n    x404_aimTargetAverage.AddValue(predictOffset);\n  }\n\n  if (auto avg = x404_aimTargetAverage.GetAverage()) {\n    x480_assistedTargetAim = instantTarget + *avg;\n  } else {\n    x480_assistedTargetAim = predictTarget;\n  }\n}\n\nvoid CPlayer::ResetAimTargetPrediction(TUniqueId target) {\n  if (target == kInvalidUniqueId || x3f4_aimTarget != target) {\n    x404_aimTargetAverage.Clear();\n  }\n  x3f4_aimTarget = target;\n}\n\nvoid CPlayer::DrawGun(CStateManager& mgr) {\n  if (x498_gunHolsterState != EGunHolsterState::Holstered || InGrappleJumpCooldown()) {\n    return;\n  }\n\n  x498_gunHolsterState = EGunHolsterState::Drawing;\n  x49c_gunHolsterRemTime = 0.45f;\n  x490_gun->ResetIdle(mgr);\n}\n\nvoid CPlayer::HolsterGun(CStateManager& mgr) {\n  if (x498_gunHolsterState == EGunHolsterState::Holstered || x498_gunHolsterState == EGunHolsterState::Holstering) {\n    return;\n  }\n\n  const float time =\n      x2f8_morphBallState == EPlayerMorphBallState::Morphing ? 0.1f : g_tweakPlayerGun->GetGunHolsterTime();\n  if (x498_gunHolsterState == EGunHolsterState::Drawing) {\n    x49c_gunHolsterRemTime = time * (1.f - x49c_gunHolsterRemTime / 0.45f);\n  } else {\n    x49c_gunHolsterRemTime = time;\n  }\n\n  x498_gunHolsterState = EGunHolsterState::Holstering;\n  x490_gun->CancelFiring(mgr);\n  ResetAimTargetPrediction(kInvalidUniqueId);\n}\n\nbool CPlayer::IsMorphBallTransitioning() const {\n  switch (x2f8_morphBallState) {\n  case EPlayerMorphBallState::Morphing:\n  case EPlayerMorphBallState::Unmorphing:\n    return true;\n  default:\n    return false;\n  }\n}\n\nvoid CPlayer::UpdateGrappleArmTransform(const zeus::CVector3f& offset, CStateManager& mgr, float dt) {\n  zeus::CTransform armXf = x34_transform;\n  zeus::CVector3f armPosition = x34_transform.rotate(offset) + x34_transform.origin;\n  armXf.origin = armPosition;\n\n  if (x2f8_morphBallState != EPlayerMorphBallState::Unmorphed) {\n    x490_gun->GetGrappleArm().SetTransform(armXf);\n  } else if (!x490_gun->GetGrappleArm().IsArmMoving()) {\n    zeus::CVector3f lookDir = x34_transform.basis[1];\n    zeus::CVector3f armToTarget = x490_gun->GetGrappleArm().GetTransform().basis[1];\n    if (lookDir.canBeNormalized()) {\n      lookDir.normalize();\n      if (x3b8_grappleState != EGrappleState::None) {\n        if (const TCastToConstPtr<CActor> target = mgr.ObjectById(x310_orbitTargetId)) {\n          armToTarget = target->GetTranslation() - armPosition;\n          zeus::CVector3f armToTargetFlat = armToTarget;\n          armToTargetFlat.z() = 0.f;\n          if (armToTarget.canBeNormalized()) {\n            armToTarget.normalize();\n          }\n          if (armToTargetFlat.canBeNormalized() && x3b8_grappleState != EGrappleState::Firing) {\n            const zeus::CQuaternion adjRot =\n                zeus::CQuaternion::lookAt(armToTargetFlat.normalized(), lookDir, 2.f * M_PIF);\n            armToTarget = adjRot.transform(armToTarget);\n            if (x3bc_grappleSwingTimer >= 0.25f * g_tweakPlayer->GetGrappleSwingPeriod() &&\n                x3bc_grappleSwingTimer < 0.75f * g_tweakPlayer->GetGrappleSwingPeriod()) {\n              armToTarget = x490_gun->GetGrappleArm().GetTransform().basis[1];\n            }\n          }\n        }\n      }\n      armXf = zeus::lookAt(zeus::skZero3f, armToTarget, zeus::skUp);\n      armXf.origin = armPosition;\n      x490_gun->GetGrappleArm().SetTransform(armXf);\n    }\n  }\n}\n\nfloat CPlayer::GetGravity() const {\n  if (!g_GameState->GetPlayerState()->HasPowerUp(CPlayerState::EItemType::GravitySuit) && CheckSubmerged()) {\n    return g_tweakPlayer->GetFluidGravAccel();\n  }\n\n  if (x37c_sidewaysDashing) {\n    return -100.f;\n  }\n\n  return g_tweakPlayer->GetNormalGravAccel();\n}\n\nvoid CPlayer::ApplyGrappleForces(const CFinalInput& input, CStateManager& mgr, float dt) {\n  if (const TCastToConstPtr<CScriptGrapplePoint> point = mgr.ObjectById(x310_orbitTargetId)) {\n    switch (x3b8_grappleState) {\n    case EGrappleState::Pull: {\n      const zeus::CVector3f playerToPoint = point->GetTranslation() - GetTranslation();\n      if (playerToPoint.canBeNormalized()) {\n        zeus::CVector3f playerToSwingLow = point->GetTranslation() +\n                                           zeus::CVector3f(0.f, 0.f, -g_tweakPlayer->GetGrappleSwingLength()) -\n                                           GetTranslation();\n        if (playerToSwingLow.canBeNormalized()) {\n          const float distToSwingLow = playerToSwingLow.magnitude();\n          playerToSwingLow.normalize();\n          const float timeToLow =\n              zeus::clamp(-1.f, distToSwingLow / g_tweakPlayer->GetGrapplePullSpeedProportion(), 1.f);\n          const float pullSpeed =\n              timeToLow * (g_tweakPlayer->GetGrapplePullSpeedMax() - g_tweakPlayer->GetGrapplePullSpeedMin()) +\n              g_tweakPlayer->GetGrapplePullSpeedMin();\n          SetVelocityWR(playerToSwingLow * pullSpeed);\n\n          if (distToSwingLow < g_tweakPlayer->GetMaxGrappleLockedTurnAlignDistance()) {\n            x3b8_grappleState = EGrappleState::Swinging;\n            x3bc_grappleSwingTimer = 0.25f * g_tweakPlayer->GetGrappleSwingPeriod();\n            x3d8_grappleJumpTimeout = 0.f;\n            x9c6_28_aligningGrappleSwingTurn = point->GetGrappleParameters().GetLockSwingTurn();\n          } else {\n            const CMotionState mState = PredictMotion(dt);\n            zeus::CVector3f lookDirFlat = x34_transform.basis[1];\n            lookDirFlat.z() = 0.f;\n            zeus::CVector3f newPlayerToPointFlat = point->GetTranslation() - (GetTranslation() + mState.x0_translation);\n            newPlayerToPointFlat.z() = 0.f;\n            if (lookDirFlat.canBeNormalized()) {\n              lookDirFlat.normalize();\n            }\n            if (newPlayerToPointFlat.canBeNormalized()) {\n              newPlayerToPointFlat.normalize();\n            }\n            const float lookToPointAngle = std::acos(zeus::clamp(-1.f, lookDirFlat.dot(newPlayerToPointFlat), 1.f));\n            if (lookToPointAngle > 0.001f) {\n              float deltaAngle = dt * g_tweakPlayer->GetGrappleLookCenterSpeed();\n              if (lookToPointAngle >= deltaAngle) {\n                zeus::CVector3f leftDirFlat(lookDirFlat.y(), -lookDirFlat.x(), 0.f);\n                if (leftDirFlat.canBeNormalized()) {\n                  leftDirFlat.normalize();\n                }\n                if (newPlayerToPointFlat.dot(leftDirFlat) >= 0.f) {\n                  deltaAngle = -deltaAngle;\n                }\n                RotateToOR(zeus::CQuaternion::fromAxisAngle(zeus::skUp, deltaAngle), dt);\n              } else if (std::fabs(lookToPointAngle - M_PIF) > 0.001f) {\n                RotateToOR(zeus::CQuaternion::shortestRotationArc(lookDirFlat, newPlayerToPointFlat), dt);\n              }\n            } else {\n              SetAngularVelocityWR(zeus::CAxisAngle());\n              x174_torque = zeus::CAxisAngle();\n            }\n          }\n        } else {\n          x3b8_grappleState = EGrappleState::Swinging;\n          x3bc_grappleSwingTimer = 0.25f * g_tweakPlayer->GetGrappleSwingPeriod();\n          x3d8_grappleJumpTimeout = 0.f;\n        }\n      }\n      break;\n    }\n    case EGrappleState::Swinging: {\n      float turnAngleSpeed = zeus::degToRad(g_tweakPlayer->GetMaxGrappleTurnSpeed());\n      if (g_tweakPlayer->GetInvertGrappleTurn()) {\n        turnAngleSpeed *= -1.f;\n      }\n      const zeus::CVector3f pointToPlayer = GetTranslation() - point->GetTranslation();\n      const float pointToPlayerZProj = zeus::clamp(-1.f, std::fabs(pointToPlayer.z() / pointToPlayer.magnitude()), 1.f);\n\n      bool enableTurn = false;\n      if (!point->GetGrappleParameters().GetLockSwingTurn()) {\n        if (ControlMapper::GetAnalogInput(ControlMapper::ECommands::TurnLeft, input) > 0.05f) {\n          enableTurn = true;\n          turnAngleSpeed *= -ControlMapper::GetAnalogInput(ControlMapper::ECommands::TurnLeft, input);\n        }\n        if (ControlMapper::GetAnalogInput(ControlMapper::ECommands::TurnRight, input) > 0.05f) {\n          enableTurn = true;\n          turnAngleSpeed *= ControlMapper::GetAnalogInput(ControlMapper::ECommands::TurnRight, input);\n        }\n      } else if (x9c6_28_aligningGrappleSwingTurn) {\n        enableTurn = true;\n      }\n\n      x3bc_grappleSwingTimer += dt;\n      if (x3bc_grappleSwingTimer > g_tweakPlayer->GetGrappleSwingPeriod()) {\n        x3bc_grappleSwingTimer -= g_tweakPlayer->GetGrappleSwingPeriod();\n      }\n\n      zeus::CVector3f swingAxis = x3c0_grappleSwingAxis;\n      if (x3bc_grappleSwingTimer < 0.5f * g_tweakPlayer->GetGrappleSwingPeriod()) {\n        swingAxis *= zeus::skNegOne3f;\n      }\n\n      const float pullSpeed =\n          std::fabs(zeus::clamp(\n              -1.f,\n              std::cos(2.f * M_PIF * (x3bc_grappleSwingTimer / g_tweakPlayer->GetGrappleSwingPeriod()) + (M_PIF / 2.f)),\n              1.f)) *\n          g_tweakPlayer->GetGrapplePullSpeedMin();\n      zeus::CVector3f pullVec = pointToPlayer.normalized().cross(swingAxis) * pullSpeed;\n      pullVec += pointToPlayer *\n                 zeus::clamp(-1.f,\n                             (pointToPlayer.magnitude() - g_tweakPlayer->GetGrappleSwingLength()) /\n                                 g_tweakPlayer->GetGrappleSwingLength(),\n                             1.f) *\n                 -32.f * pointToPlayerZProj;\n      const zeus::CVector3f backupVel = x138_velocity;\n      SetVelocityWR(pullVec);\n\n      const zeus::CTransform backupXf = x34_transform;\n      const CMotionState predMotion = PredictMotion(dt);\n      const zeus::CVector3f newPos = x34_transform.origin + predMotion.x0_translation;\n      if (ValidateFPPosition(newPos, mgr)) {\n        if (enableTurn) {\n          zeus::CQuaternion turnRot;\n          turnRot.rotateZ(turnAngleSpeed * dt);\n          if (point->GetGrappleParameters().GetLockSwingTurn() && x9c6_28_aligningGrappleSwingTurn) {\n            zeus::CVector3f pointDir = point->GetTransform().basis[1].normalized();\n            const zeus::CVector3f playerDir = x34_transform.basis[1].normalized();\n            float playerPointProj = zeus::clamp(-1.f, playerDir.dot(pointDir), 1.f);\n            if (std::fabs(playerPointProj) == 1.f) {\n              x9c6_28_aligningGrappleSwingTurn = false;\n            }\n            if (playerPointProj < 0.f) {\n              playerPointProj = -playerPointProj;\n              pointDir = -pointDir;\n            }\n\n            const float turnAngleAdj = std::acos(playerPointProj) * dt;\n            turnRot = zeus::CQuaternion::lookAt(playerDir, pointDir, turnAngleAdj);\n          }\n\n          if (pointToPlayer.magSquared() > 0.04f) {\n            zeus::CVector3f pointToPlayerFlat = pointToPlayer;\n            pointToPlayerFlat.z() = 0.f;\n            zeus::CVector3f pointAtPlayerHeight = point->GetTranslation();\n            pointAtPlayerHeight.z() = GetTranslation().z();\n            const zeus::CVector3f playerToGrapplePlane =\n                pointAtPlayerHeight + turnRot.transform(pointToPlayerFlat) - GetTranslation();\n            if (playerToGrapplePlane.canBeNormalized()) {\n              pullVec += playerToGrapplePlane / dt;\n            }\n          }\n\n          const zeus::CVector3f swingAxisBackup = x3c0_grappleSwingAxis;\n          x3c0_grappleSwingAxis = turnRot.transform(x3c0_grappleSwingAxis);\n          x3c0_grappleSwingAxis.normalize();\n          const zeus::CVector3f swingForward(-x3c0_grappleSwingAxis.y(), x3c0_grappleSwingAxis.x(), 0.f);\n          SetTransform(zeus::CTransform(x3c0_grappleSwingAxis, swingForward, zeus::skUp, GetTranslation()));\n          SetVelocityWR(pullVec);\n\n          if (!ValidateFPPosition(GetTranslation(), mgr)) {\n            x3c0_grappleSwingAxis = swingAxisBackup;\n            SetTransform(backupXf);\n            SetVelocityWR(backupVel);\n          }\n        }\n      } else {\n        BreakGrapple(EPlayerOrbitRequest::InvalidateTarget, mgr);\n      }\n      break;\n    }\n    case EGrappleState::JumpOff: {\n      const zeus::CVector3f gravForce = {0.f, 0.f, GetGravity() * xe8_mass};\n      ApplyForceOR(gravForce, zeus::CAxisAngle());\n      break;\n    }\n    default:\n      break;\n    }\n  }\n\n  const zeus::CVector3f newAngVel = {0.f, 0.f, 0.9f * GetAngularVelocityOR().getVector().z()};\n  SetAngularVelocityOR(newAngVel);\n}\n\nbool CPlayer::ValidateFPPosition(const zeus::CVector3f& pos, const CStateManager& mgr) const {\n  constexpr CMaterialFilter solidFilter = CMaterialFilter::MakeInclude({EMaterialTypes::Solid});\n  const zeus::CAABox aabb(x2d8_fpBounds.min - 1.f + pos, x2d8_fpBounds.max + 1.f + pos);\n  EntityList nearList;\n  mgr.BuildColliderList(nearList, *this, aabb);\n  const CCollidableAABox colAABB({GetBaseBoundingBox().min + pos, GetBaseBoundingBox().max + pos}, {});\n  return !CGameCollision::DetectCollisionBoolean(mgr, colAABB, zeus::CTransform(), solidFilter, nearList);\n}\n\nvoid CPlayer::UpdateGrappleState(const CFinalInput& input, CStateManager& mgr) {\n  if (!mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::GrappleBeam) ||\n      x2f8_morphBallState == EPlayerMorphBallState::Morphed ||\n      mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::Scan ||\n      mgr.GetPlayerState()->GetTransitioningVisor() == CPlayerState::EPlayerVisor::Scan) {\n    return;\n  }\n\n  if (x310_orbitTargetId == kInvalidUniqueId) {\n    x3b8_grappleState = EGrappleState::None;\n    AddMaterial(EMaterialTypes::GroundCollider, mgr);\n    return;\n  }\n\n  const TCastToConstPtr<CScriptGrapplePoint> point = mgr.ObjectById(x310_orbitTargetId);\n  if (point) {\n    const zeus::CVector3f eyePosition = GetEyePosition();\n    zeus::CVector3f playerToPoint = point->GetTranslation() - eyePosition;\n    zeus::CVector3f playerToPointFlat = playerToPoint;\n    playerToPointFlat.z() = 0.f;\n    if (playerToPoint.canBeNormalized() && playerToPointFlat.canBeNormalized() && playerToPointFlat.magnitude() > 2.f) {\n      switch (x304_orbitState) {\n      case EPlayerOrbitState::Grapple:\n        switch (g_tweakPlayer->GetGrappleJumpMode()) {\n        case 0:\n        case 1:\n          if (ControlMapper::GetPressInput(ControlMapper::ECommands::FireOrBomb, input)) {\n            if (const TCastToConstPtr<CScriptGrapplePoint> point2 = mgr.ObjectById(x33c_orbitNextTargetId)) {\n              playerToPoint = point2->GetTranslation() - eyePosition;\n              playerToPoint.z() = 0.f;\n              if (playerToPoint.canBeNormalized()) {\n                x490_gun->GetGrappleArm().GrappleBeamDisconnected();\n                x3c0_grappleSwingAxis.x() = float(playerToPoint.y());\n                x3c0_grappleSwingAxis.y() = -playerToPoint.x();\n                x3c0_grappleSwingAxis.normalize();\n                x3bc_grappleSwingTimer = 0.f;\n                SetOrbitTargetId(x33c_orbitNextTargetId, mgr);\n                x3b8_grappleState = EGrappleState::Pull;\n                x33c_orbitNextTargetId = kInvalidUniqueId;\n                x490_gun->GetGrappleArm().GrappleBeamConnected();\n              }\n            } else {\n              if (g_tweakPlayer->GetGrappleJumpMode() == 0 && x3d8_grappleJumpTimeout <= 0.f) {\n                ApplyGrappleJump(mgr);\n              }\n              BreakGrapple(EPlayerOrbitRequest::StopOrbit, mgr);\n            }\n          }\n          break;\n        default:\n          break;\n        }\n\n        break;\n      case EPlayerOrbitState::OrbitObject:\n        if (playerToPoint.canBeNormalized()) {\n          const CRayCastResult result = mgr.RayStaticIntersection(eyePosition, playerToPoint.normalized(),\n                                                                  playerToPoint.magnitude(), LineOfSightFilter);\n          if (result.IsInvalid()) {\n            HolsterGun(mgr);\n            switch (x3b8_grappleState) {\n            case EGrappleState::Firing:\n            case EGrappleState::Swinging:\n              switch (g_tweakPlayer->GetGrappleJumpMode()) {\n              case 0:\n                switch (x490_gun->GetGrappleArm().GetAnimState()) {\n                case CGrappleArm::EArmState::IntoGrappleIdle:\n                  if (ControlMapper::GetDigitalInput(ControlMapper::ECommands::FireOrBomb, input)) {\n                    x490_gun->GetGrappleArm().SetAnimState(CGrappleArm::EArmState::FireGrapple);\n                  }\n                  break;\n                case CGrappleArm::EArmState::Connected:\n                  BeginGrapple(playerToPoint, mgr);\n                  break;\n                default:\n                  break;\n                }\n                break;\n              case 1:\n                if (ControlMapper::GetDigitalInput(ControlMapper::ECommands::FireOrBomb, input)) {\n                  switch (x490_gun->GetGrappleArm().GetAnimState()) {\n                  case CGrappleArm::EArmState::IntoGrappleIdle:\n                    x490_gun->GetGrappleArm().SetAnimState(CGrappleArm::EArmState::FireGrapple);\n                    break;\n                  case CGrappleArm::EArmState::Connected:\n                    BeginGrapple(playerToPoint, mgr);\n                    break;\n                  default:\n                    break;\n                  }\n                  break;\n                }\n                break;\n              case 2:\n                switch (x490_gun->GetGrappleArm().GetAnimState()) {\n                case CGrappleArm::EArmState::IntoGrappleIdle:\n                  x490_gun->GetGrappleArm().SetAnimState(CGrappleArm::EArmState::FireGrapple);\n                  break;\n                case CGrappleArm::EArmState::Connected:\n                  BeginGrapple(playerToPoint, mgr);\n                  break;\n                default:\n                  break;\n                }\n                break;\n              default:\n                break;\n              }\n              break;\n            case EGrappleState::None:\n              x3b8_grappleState = EGrappleState::Firing;\n              x490_gun->GetGrappleArm().Activate(true);\n              break;\n            default:\n              break;\n            }\n          }\n        }\n        break;\n      default:\n        break;\n      }\n    }\n  }\n\n  if (x304_orbitState != EPlayerOrbitState::Grapple) {\n    if (x304_orbitState >= EPlayerOrbitState::Grapple) {\n      return;\n    }\n    if (x304_orbitState != EPlayerOrbitState::OrbitObject) {\n      return;\n    }\n  } else {\n    if (!point) {\n      BreakGrapple(EPlayerOrbitRequest::Default, mgr);\n      return;\n    }\n\n    switch (g_tweakPlayer->GetGrappleJumpMode()) {\n    case 0:\n      if (x3b8_grappleState == EGrappleState::JumpOff) {\n        x3d8_grappleJumpTimeout -= input.DeltaTime();\n        if (x3d8_grappleJumpTimeout <= 0.f) {\n          BreakGrapple(EPlayerOrbitRequest::StopOrbit, mgr);\n          SetMoveState(EPlayerMovementState::ApplyJump, mgr);\n          ComputeMovement(input, mgr, input.DeltaTime());\n          PreventFallingCameraPitch();\n        }\n      }\n      break;\n    case 1:\n      switch (x3b8_grappleState) {\n      case EGrappleState::Swinging:\n        if (!ControlMapper::GetDigitalInput(ControlMapper::ECommands::FireOrBomb, input) &&\n            x3d8_grappleJumpTimeout <= 0.f) {\n          x3d8_grappleJumpTimeout = g_tweakPlayer->GetGrappleReleaseTime();\n          x3b8_grappleState = EGrappleState::JumpOff;\n          ApplyGrappleJump(mgr);\n        }\n        break;\n      case EGrappleState::JumpOff:\n        x3d8_grappleJumpTimeout -= input.DeltaTime();\n        if (x3d8_grappleJumpTimeout <= 0.f) {\n          SetMoveState(EPlayerMovementState::ApplyJump, mgr);\n          ComputeMovement(input, mgr, input.DeltaTime());\n          BreakGrapple(EPlayerOrbitRequest::StopOrbit, mgr);\n          PreventFallingCameraPitch();\n        }\n        break;\n      case EGrappleState::Firing:\n      case EGrappleState::Pull:\n        if (!ControlMapper::GetDigitalInput(ControlMapper::ECommands::FireOrBomb, input)) {\n          BreakGrapple(EPlayerOrbitRequest::StopOrbit, mgr);\n        }\n        break;\n      default:\n        break;\n      }\n      break;\n    default:\n      break;\n    }\n\n    const zeus::CVector3f eyePos = GetEyePosition();\n    const zeus::CVector3f playerToPoint = point->GetTranslation() - eyePos;\n    if (playerToPoint.canBeNormalized()) {\n      const CRayCastResult result =\n          mgr.RayStaticIntersection(eyePos, playerToPoint.normalized(), playerToPoint.magnitude(), LineOfSightFilter);\n      if (result.IsValid()) {\n        BreakGrapple(EPlayerOrbitRequest::LostGrappleLineOfSight, mgr);\n      }\n    }\n    return;\n  }\n\n  if (x490_gun->GetGrappleArm().BeamActive() && g_tweakPlayer->GetGrappleJumpMode() == 1 &&\n      !ControlMapper::GetDigitalInput(ControlMapper::ECommands::FireOrBomb, input)) {\n    BreakGrapple(EPlayerOrbitRequest::StopOrbit, mgr);\n  }\n}\n\nvoid CPlayer::ApplyGrappleJump(CStateManager& mgr) {\n  const TCastToConstPtr<CScriptGrapplePoint> point = mgr.ObjectById(x310_orbitTargetId);\n\n  if (!point) {\n    return;\n  }\n\n  zeus::CVector3f tmp = x3c0_grappleSwingAxis;\n  if (x3bc_grappleSwingTimer < 0.5f * g_tweakPlayer->GetGrappleSwingPeriod()) {\n    tmp *= zeus::skNegOne3f;\n  }\n  const zeus::CVector3f pointToPlayer = GetTranslation() - point->GetTranslation();\n  const zeus::CVector3f cross = pointToPlayer.normalized().cross(tmp);\n  const zeus::CVector3f pointToPlayerFlat(pointToPlayer.x(), pointToPlayer.y(), 0.f);\n  float dot = 1.f;\n  if (pointToPlayerFlat.canBeNormalized() && cross.canBeNormalized()) {\n    dot = zeus::clamp(-1.f, std::fabs(cross.normalized().dot(pointToPlayerFlat.normalized())), 1.f);\n  }\n  ApplyForceWR(g_tweakPlayer->GetGrappleJumpForce() * cross * 10000.f * dot, zeus::CAxisAngle());\n}\n\nvoid CPlayer::BeginGrapple(zeus::CVector3f& vec, CStateManager& mgr) {\n  vec.z() = 0.f;\n  if (vec.canBeNormalized()) {\n    x3c0_grappleSwingAxis.x() = float(vec.y());\n    x3c0_grappleSwingAxis.y() = -vec.x();\n    x3c0_grappleSwingAxis.normalize();\n    x3bc_grappleSwingTimer = 0.f;\n    SetOrbitState(EPlayerOrbitState::Grapple, mgr);\n    x3b8_grappleState = EGrappleState::Pull;\n    RemoveMaterial(EMaterialTypes::GroundCollider, mgr);\n  }\n}\n\nvoid CPlayer::BreakGrapple(EPlayerOrbitRequest req, CStateManager& mgr) {\n  x294_jumpCameraTimer = 0.f;\n  x29c_fallCameraTimer = 0.f;\n  if (g_tweakPlayer->GetGrappleJumpMode() == 2 && x3b8_grappleState == EGrappleState::Swinging) {\n    ApplyGrappleJump(mgr);\n    PreventFallingCameraPitch();\n  }\n\n  SetOrbitRequest(req, mgr);\n  x3b8_grappleState = EGrappleState::None;\n  AddMaterial(EMaterialTypes::GroundCollider, mgr);\n  x490_gun->GetGrappleArm().SetAnimState(CGrappleArm::EArmState::OutOfGrapple);\n  if (!InGrappleJumpCooldown() && x3b8_grappleState != EGrappleState::JumpOff) {\n    DrawGun(mgr);\n  }\n}\n\nvoid CPlayer::SetOrbitRequest(EPlayerOrbitRequest req, CStateManager& mgr) {\n  x30c_orbitRequest = req;\n  switch (req) {\n  case EPlayerOrbitRequest::ActivateOrbitSource:\n    ActivateOrbitSource(mgr);\n    break;\n  case EPlayerOrbitRequest::BadVerticalAngle:\n    SetOrbitState(EPlayerOrbitState::OrbitPoint, mgr);\n    x314_orbitPoint =\n        g_tweakPlayer->GetOrbitNormalDistance(int(x308_orbitType)) * x34_transform.basis[1] + x34_transform.origin;\n    break;\n  default:\n    SetOrbitState(EPlayerOrbitState::NoOrbit, mgr);\n    break;\n  }\n}\n\nvoid CPlayer::TryToBreakOrbit(TUniqueId id, EPlayerOrbitRequest req, CStateManager& mgr) {\n  switch (x304_orbitState) {\n  case EPlayerOrbitState::OrbitObject:\n  case EPlayerOrbitState::ForcedOrbitObject:\n  case EPlayerOrbitState::Grapple:\n    if (id == x310_orbitTargetId)\n      SetOrbitRequest(req, mgr);\n    break;\n  default:\n    break;\n  }\n}\n\nbool CPlayer::InGrappleJumpCooldown() const {\n  if (x258_movementState == EPlayerMovementState::OnGround) {\n    return false;\n  }\n  return x3d8_grappleJumpTimeout > 0.f || x294_jumpCameraTimer == 0.f;\n}\n\nvoid CPlayer::PreventFallingCameraPitch() {\n  x294_jumpCameraTimer = 0.f;\n  x29c_fallCameraTimer = 0.01f;\n  x2a4_cancelCameraPitch = true;\n}\n\nvoid CPlayer::OrbitCarcass(CStateManager& mgr) {\n  if (x304_orbitState != EPlayerOrbitState::OrbitObject) {\n    return;\n  }\n\n  x308_orbitType = EPlayerOrbitType::Default;\n  SetOrbitState(EPlayerOrbitState::OrbitCarcass, mgr);\n}\n\nvoid CPlayer::OrbitPoint(EPlayerOrbitType type, CStateManager& mgr) {\n  x308_orbitType = type;\n  SetOrbitState(EPlayerOrbitState::OrbitPoint, mgr);\n  SetOrbitPosition(g_tweakPlayer->GetOrbitNormalDistance(int(x308_orbitType)), mgr);\n}\n\nzeus::CVector3f CPlayer::GetHUDOrbitTargetPosition() const {\n  return x314_orbitPoint + x76c_cameraBob->GetCameraBobTransformation().origin;\n}\n\nvoid CPlayer::SetOrbitState(EPlayerOrbitState state, CStateManager& mgr) {\n  x304_orbitState = state;\n  CFirstPersonCamera* cam = mgr.GetCameraManager()->GetFirstPersonCamera();\n  switch (x304_orbitState) {\n  case EPlayerOrbitState::OrbitObject:\n#ifndef NDEBUG\n    if (x310_orbitTargetId != kInvalidUniqueId) {\n      if (const CEntity* ent = mgr.GetObjectById(x310_orbitTargetId)) {\n        spdlog::info(\"Orbiting {} {}\", ent->GetEditorId(), ent->GetName());\n      }\n    }\n#endif\n    cam->SetLockCamera(false);\n    break;\n  case EPlayerOrbitState::OrbitCarcass: {\n    cam->SetLockCamera(true);\n    const zeus::CVector3f playerToPoint = x314_orbitPoint - GetTranslation();\n    if (playerToPoint.canBeNormalized()) {\n      x340_ = playerToPoint.magnitude();\n    } else {\n      x340_ = 0.f;\n    }\n    SetOrbitTargetId(kInvalidUniqueId, mgr);\n    x33c_orbitNextTargetId = kInvalidUniqueId;\n    break;\n  }\n  case EPlayerOrbitState::NoOrbit:\n    x32c_orbitModeTimer = g_tweakPlayer->GetOrbitModeTimer();\n    x32c_orbitModeTimer = 0.28f;\n    SetOrbitTargetId(kInvalidUniqueId, mgr);\n    x33c_orbitNextTargetId = kInvalidUniqueId;\n    break;\n  case EPlayerOrbitState::OrbitPoint:\n    SetOrbitTargetId(kInvalidUniqueId, mgr);\n    x33c_orbitNextTargetId = kInvalidUniqueId;\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CPlayer::SetOrbitTargetId(TUniqueId id, CStateManager& mgr) {\n  if (id != kInvalidUniqueId) {\n    x394_orbitingEnemy =\n        (TCastToPtr<CPatterned>(mgr.ObjectById(id)) || TCastToPtr<CWallCrawlerSwarm>(mgr.ObjectById(id)) ||\n         CPatterned::CastTo<MP1::CThardusRockProjectile>(mgr.ObjectById(id)) ||\n         TCastToPtr<CScriptGunTurret>(mgr.ObjectById(id)));\n  }\n\n  x310_orbitTargetId = id;\n  if (x310_orbitTargetId == kInvalidUniqueId) {\n    x374_orbitLockEstablished = false;\n  }\n}\n\nvoid CPlayer::UpdateOrbitPosition(float dist, CStateManager& mgr) {\n  switch (x304_orbitState) {\n  case EPlayerOrbitState::OrbitPoint:\n  case EPlayerOrbitState::OrbitCarcass:\n    SetOrbitPosition(dist, mgr);\n    break;\n  case EPlayerOrbitState::OrbitObject:\n  case EPlayerOrbitState::ForcedOrbitObject:\n  case EPlayerOrbitState::Grapple:\n    if (const TCastToConstPtr<CActor> act = mgr.ObjectById(x310_orbitTargetId)) {\n      if (x310_orbitTargetId != kInvalidUniqueId) {\n        x314_orbitPoint = act->GetOrbitPosition(mgr);\n      }\n    }\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CPlayer::UpdateOrbitZPosition() {\n  if (x304_orbitState != EPlayerOrbitState::OrbitPoint) {\n    return;\n  }\n\n  if (std::fabs(x320_orbitVector.z()) >= g_tweakPlayer->GetOrbitZRange()) {\n    return;\n  }\n\n  x314_orbitPoint.z() = x320_orbitVector.z() + x34_transform.origin.z() + GetEyeHeight();\n}\n\nvoid CPlayer::UpdateOrbitFixedPosition() {\n  x314_orbitPoint = x34_transform.rotate(x320_orbitVector) + GetEyePosition();\n}\n\nvoid CPlayer::SetOrbitPosition(float dist, CStateManager& mgr) {\n  zeus::CTransform camXf = GetFirstPersonCameraTransform(mgr);\n  if (x304_orbitState == EPlayerOrbitState::OrbitPoint && x30c_orbitRequest == EPlayerOrbitRequest::BadVerticalAngle) {\n    camXf = x34_transform;\n  }\n\n  const zeus::CVector3f fwd = camXf.basis[1];\n  float dot = fwd.normalized().dot(fwd);\n  if (std::fabs(dot) > 1.f) {\n    dot = (dot > 0.f) ? 1.f : -1.f;\n  }\n\n  x314_orbitPoint = camXf.rotate(zeus::CVector3f(0.f, dist / dot, 0.f)) + camXf.origin;\n  x320_orbitVector = zeus::CVector3f(0.f, dist, x314_orbitPoint.z() - camXf.origin.z());\n}\n\nvoid CPlayer::UpdateAimTarget(CStateManager& mgr) {\n  if (!ValidateAimTargetId(x3f4_aimTarget, mgr)) {\n    ResetAimTargetPrediction(kInvalidUniqueId);\n  }\n\n  if (!GetCombatMode()) {\n    ResetAimTargetPrediction(kInvalidUniqueId);\n    x48c_aimTargetTimer = 0.f;\n    return;\n  }\n\n#if 0\n    if (!0 && 0)\n    {\n        ResetAimTargetPrediction(kInvalidUniqueId);\n        x48c_aimTargetTimer = 0.f;\n        if (x304_orbitState == EPlayerOrbitState::One ||\n            x304_orbitState == EPlayerOrbitState::Four) {\n            if (!ValidateOrbitTargetId(x310_orbitTargetId, mgr)) {\n                ResetAimTargetPrediction(x310_orbitTargetId);\n            }\n        }\n        return;\n    }\n#endif\n\n  bool needsReset = false;\n  const TCastToConstPtr<CActor> act = mgr.ObjectById(x3f4_aimTarget);\n  const CActor* actp = act.GetPtr();\n  if (act) {\n    if (!act->GetMaterialList().HasMaterial(EMaterialTypes::Target)) {\n      actp = nullptr;\n    }\n  }\n\n  if (g_tweakPlayer->GetAimWhenOrbitingPoint()) {\n    if (x304_orbitState == EPlayerOrbitState::OrbitObject || x304_orbitState == EPlayerOrbitState::ForcedOrbitObject) {\n      if (ValidateOrbitTargetId(x310_orbitTargetId, mgr) == EOrbitValidationResult::OK) {\n        ResetAimTargetPrediction(x310_orbitTargetId);\n      } else {\n        needsReset = true;\n      }\n    } else {\n      needsReset = true;\n    }\n  } else {\n    if (x304_orbitState == EPlayerOrbitState::NoOrbit) {\n      needsReset = true;\n    }\n  }\n\n  if (!needsReset) {\n    return;\n  }\n\n  if (actp && ValidateObjectForMode(x3f4_aimTarget, mgr)) {\n    ResetAimTargetPrediction(kInvalidUniqueId);\n  } else {\n    ResetAimTargetPrediction(FindAimTargetId(mgr));\n  }\n}\n\nvoid CPlayer::UpdateAimTargetTimer(float dt) {\n  if (x3f4_aimTarget == kInvalidUniqueId) {\n    return;\n  }\n\n  if (x48c_aimTargetTimer <= 0.f) {\n    return;\n  }\n\n  x48c_aimTargetTimer -= dt;\n}\n\nbool CPlayer::ValidateAimTargetId(TUniqueId uid, CStateManager& mgr) {\n  if (uid == kInvalidUniqueId) {\n    x404_aimTargetAverage.Clear();\n    x48c_aimTargetTimer = 0.f;\n    return false;\n  }\n\n  const TCastToConstPtr<CActor> act = mgr.ObjectById(uid);\n  if (!act || !act->GetMaterialList().HasMaterial(EMaterialTypes::Target) || !act->GetIsTargetable()) {\n    return false;\n  }\n\n  if (x304_orbitState == EPlayerOrbitState::OrbitObject || x304_orbitState == EPlayerOrbitState::ForcedOrbitObject) {\n    if (ValidateOrbitTargetId(x310_orbitTargetId, mgr) != EOrbitValidationResult::OK) {\n      ResetAimTargetPrediction(kInvalidUniqueId);\n      x48c_aimTargetTimer = 0.f;\n      return false;\n    }\n    return true;\n  }\n\n  if (act->GetMaterialList().HasMaterial(EMaterialTypes::Target) && uid != kInvalidUniqueId &&\n      ValidateObjectForMode(uid, mgr)) {\n    const float vpWHalf = CGraphics::GetViewportWidth() / 2;\n    const float vpHHalf = CGraphics::GetViewportHeight() / 2;\n    const zeus::CVector3f aimPos = act->GetAimPosition(mgr, 0.f);\n    const zeus::CVector3f eyePos = GetEyePosition();\n    zeus::CVector3f eyeToAim = aimPos - eyePos;\n    const zeus::CVector3f screenPos = mgr.GetCameraManager()->GetFirstPersonCamera()->ConvertToScreenSpace(aimPos);\n    const zeus::CVector3f posInBox(vpWHalf + screenPos.x() * vpWHalf, vpHHalf + screenPos.y() * vpHHalf, screenPos.z());\n    if (WithinOrbitScreenBox(posInBox, x330_orbitZoneMode, x334_orbitType) ||\n        (x330_orbitZoneMode != EPlayerZoneInfo::Targeting &&\n         WithinOrbitScreenBox(posInBox, EPlayerZoneInfo::Targeting, x334_orbitType))) {\n      const float eyeToAimMag = eyeToAim.magnitude();\n      if (eyeToAimMag <= g_tweakPlayer->GetAimMaxDistance()) {\n        EntityList nearList;\n        TUniqueId intersectId = kInvalidUniqueId;\n        eyeToAim.normalize();\n        mgr.BuildNearList(nearList, eyePos, eyeToAim, eyeToAimMag, OccluderFilter, act);\n        eyeToAim.normalize();\n        const CRayCastResult result =\n            mgr.RayWorldIntersection(intersectId, eyePos, eyeToAim, eyeToAimMag, LineOfSightFilter, nearList);\n        if (result.IsInvalid()) {\n          x48c_aimTargetTimer = g_tweakPlayer->GetAimTargetTimer();\n          return true;\n        }\n      }\n    }\n\n    if (x48c_aimTargetTimer > 0.f) {\n      return true;\n    }\n  }\n\n  ResetAimTargetPrediction(kInvalidUniqueId);\n  x48c_aimTargetTimer = 0.f;\n  return false;\n}\n\nbool CPlayer::ValidateObjectForMode(TUniqueId uid, CStateManager& mgr) const {\n  const TCastToPtr<CActor> act = mgr.ObjectById(uid);\n  if (!act || uid == kInvalidUniqueId) {\n    return false;\n  }\n\n  if (TCastToConstPtr<CScriptDoor>(mgr.ObjectById(uid))) {\n    return true;\n  }\n\n  if (GetCombatMode()) {\n    if (const CHealthInfo* hInfo = act->HealthInfo(mgr)) {\n      if (hInfo->GetHP() > 0.f) {\n        return true;\n      }\n    } else {\n      if (act->GetMaterialList().HasMaterial(EMaterialTypes::Projectile) ||\n          act->GetMaterialList().HasMaterial(EMaterialTypes::Scannable)) {\n        return true;\n      }\n\n      if (const TCastToConstPtr<CScriptGrapplePoint> point = mgr.ObjectById(uid)) {\n        const zeus::CVector3f playerToPoint = point->GetTranslation() - GetTranslation();\n        if (playerToPoint.canBeNormalized() && playerToPoint.magnitude() < g_tweakPlayer->GetOrbitDistanceMax()) {\n          return true;\n        }\n      }\n    }\n  }\n\n  if (GetExplorationMode()) {\n    if (!act->HealthInfo(mgr)) {\n      if (const TCastToConstPtr<CScriptGrapplePoint> point = mgr.ObjectById(uid)) {\n        const zeus::CVector3f playerToPoint = point->GetTranslation() - GetTranslation();\n        if (playerToPoint.canBeNormalized() && playerToPoint.magnitude() < g_tweakPlayer->GetOrbitDistanceMax()) {\n          return true;\n        }\n      } else {\n        return true;\n      }\n    } else {\n      return true;\n    }\n  }\n\n  return false;\n}\n\nstatic zeus::CAABox BuildNearListBox(bool cropBottom, const zeus::CTransform& xf, float x, float z, float y) {\n  const zeus::CAABox aabb(-x, cropBottom ? 0.f : -y, -z, x, y, z);\n  return aabb.getTransformedAABox(xf);\n}\n\nTUniqueId CPlayer::FindAimTargetId(CStateManager& mgr) const {\n  float dist = g_tweakPlayer->GetAimMaxDistance();\n  if (x9c6_24_extendTargetDistance) {\n    dist *= 5.f;\n  }\n  const zeus::CAABox aabb = BuildNearListBox(true, GetFirstPersonCameraTransform(mgr), g_tweakPlayer->GetAimBoxWidth(),\n                                             g_tweakPlayer->GetAimBoxHeight(), dist);\n  EntityList nearList;\n  mgr.BuildNearList(nearList, aabb, CMaterialFilter::MakeInclude({EMaterialTypes::Target}), this);\n  return CheckEnemiesAgainstOrbitZone(nearList, EPlayerZoneInfo::Targeting, EPlayerZoneType::Ellipse, mgr);\n}\n\nconst zeus::CTransform& CPlayer::GetFirstPersonCameraTransform(const CStateManager& mgr) const {\n  return mgr.GetCameraManager()->GetFirstPersonCamera()->GetGunFollowTransform();\n}\n\nTUniqueId CPlayer::CheckEnemiesAgainstOrbitZone(const EntityList& list, EPlayerZoneInfo info, EPlayerZoneType zone,\n                                                CStateManager& mgr) const {\n  const zeus::CVector3f eyePos = GetEyePosition();\n  float minEyeToAimMag = 10000.f;\n  float minPosInBoxMagSq = 10000.f;\n  TUniqueId bestId = kInvalidUniqueId;\n  const float vpWHalf = CGraphics::GetViewportWidth() / 2;\n  const float vpHHalf = CGraphics::GetViewportHeight() / 2;\n  const float boxLeft = (GetOrbitZoneIdealXScaled(int(info)) - vpWHalf) / vpWHalf;\n  const float boxTop = (GetOrbitZoneIdealYScaled(int(info)) - vpHHalf) / vpHHalf;\n  const CFirstPersonCamera* fpCam = mgr.GetCameraManager()->GetFirstPersonCamera();\n\n  for (const auto& id : list) {\n    if (const auto* act = static_cast<CActor*>(mgr.ObjectById(id))) {\n      if (act->GetUniqueId() != GetUniqueId() && ValidateObjectForMode(act->GetUniqueId(), mgr)) {\n        const zeus::CVector3f aimPos = act->GetAimPosition(mgr, 0.f);\n        const zeus::CVector3f screenPos = fpCam->ConvertToScreenSpace(aimPos);\n        const zeus::CVector3f posInBox(vpWHalf + screenPos.x() * vpWHalf, vpHHalf + screenPos.y() * vpHHalf,\n                                       screenPos.z());\n        if (WithinOrbitScreenBox(posInBox, info, zone)) {\n          zeus::CVector3f eyeToAim = aimPos - eyePos;\n          const float eyeToAimMag = eyeToAim.magnitude();\n          if (eyeToAimMag <= g_tweakPlayer->GetAimMaxDistance()) {\n            if (minEyeToAimMag - eyeToAimMag > g_tweakPlayer->GetAimThresholdDistance()) {\n              EntityList nearList;\n              TUniqueId intersectId = kInvalidUniqueId;\n              eyeToAim.normalize();\n              mgr.BuildNearList(nearList, eyePos, eyeToAim, eyeToAimMag, OccluderFilter, act);\n              eyeToAim.normalize();\n              const CRayCastResult result =\n                  mgr.RayWorldIntersection(intersectId, eyePos, eyeToAim, eyeToAimMag, LineOfSightFilter, nearList);\n              if (result.IsInvalid()) {\n                bestId = act->GetUniqueId();\n                const float posInBoxLeft = posInBox.x() - boxLeft;\n                const float posInBoxTop = posInBox.y() - boxTop;\n                minEyeToAimMag = eyeToAimMag;\n                minPosInBoxMagSq = posInBoxLeft * posInBoxLeft + posInBoxTop * posInBoxTop;\n              }\n            } else if (std::fabs(eyeToAimMag - minEyeToAimMag) < g_tweakPlayer->GetAimThresholdDistance()) {\n              const float posInBoxLeft = posInBox.x() - boxLeft;\n              const float posInBoxTop = posInBox.y() - boxTop;\n              const float posInBoxMagSq = posInBoxLeft * posInBoxLeft + posInBoxTop * posInBoxTop;\n              if (posInBoxMagSq < minPosInBoxMagSq) {\n                EntityList nearList;\n                TUniqueId intersectId = kInvalidUniqueId;\n                eyeToAim.normalize();\n                mgr.BuildNearList(nearList, eyePos, eyeToAim, eyeToAimMag, OccluderFilter, act);\n                eyeToAim.normalize();\n                const CRayCastResult result =\n                    mgr.RayWorldIntersection(intersectId, eyePos, eyeToAim, eyeToAimMag, LineOfSightFilter, nearList);\n                if (result.IsInvalid()) {\n                  bestId = act->GetUniqueId();\n                  minEyeToAimMag = eyeToAimMag;\n                  minPosInBoxMagSq = posInBoxMagSq;\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n\n  return bestId;\n}\n\nTUniqueId CPlayer::FindOrbitTargetId(CStateManager& mgr) const {\n  return FindBestOrbitableObject(x354_onScreenOrbitObjects, x330_orbitZoneMode, mgr);\n}\n\nvoid CPlayer::UpdateOrbitableObjects(CStateManager& mgr) {\n  x354_onScreenOrbitObjects.clear();\n  x344_nearbyOrbitObjects.clear();\n  x364_offScreenOrbitObjects.clear();\n\n  if (CheckOrbitDisableSourceList(mgr)) {\n    return;\n  }\n\n  float dist = GetOrbitMaxTargetDistance(mgr);\n  if (x9c6_24_extendTargetDistance) {\n    dist *= 5.f;\n  }\n  const zeus::CAABox nearAABB = BuildNearListBox(true, GetFirstPersonCameraTransform(mgr),\n                                                 g_tweakPlayer->GetOrbitNearX(), g_tweakPlayer->GetOrbitNearZ(), dist);\n\n  const CMaterialFilter filter = mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::Scan\n                                     ? CMaterialFilter::MakeInclude({EMaterialTypes::Scannable})\n                                     : CMaterialFilter::MakeInclude({EMaterialTypes::Orbit});\n  EntityList nearList;\n  mgr.BuildNearList(nearList, nearAABB, filter, nullptr);\n\n  FindOrbitableObjects(nearList, x344_nearbyOrbitObjects, x330_orbitZoneMode, EPlayerZoneType::Always, mgr, true);\n  FindOrbitableObjects(nearList, x354_onScreenOrbitObjects, x330_orbitZoneMode, x334_orbitType, mgr, true);\n  FindOrbitableObjects(nearList, x364_offScreenOrbitObjects, x330_orbitZoneMode, x334_orbitType, mgr, false);\n}\n\nTUniqueId CPlayer::FindBestOrbitableObject(const std::vector<TUniqueId>& ids, EPlayerZoneInfo info,\n                                           CStateManager& mgr) const {\n  const zeus::CVector3f eyePos = GetEyePosition();\n  float minEyeToOrbitMag = 10000.f;\n  float minPosInBoxMagSq = 10000.f;\n  TUniqueId bestId = kInvalidUniqueId;\n  const float vpWidthHalf = CGraphics::GetViewportWidth() / 2;\n  const float vpHeightHalf = CGraphics::GetViewportHeight() / 2;\n  const float boxLeft = (GetOrbitZoneIdealXScaled(int(info)) - vpWidthHalf) / vpWidthHalf;\n  const float boxTop = (GetOrbitZoneIdealYScaled(int(info)) - vpHeightHalf) / vpHeightHalf;\n\n  const CFirstPersonCamera* fpCam = mgr.GetCameraManager()->GetFirstPersonCamera();\n\n  for (const auto& id : ids) {\n    if (const TCastToConstPtr<CActor> act = mgr.ObjectById(id)) {\n      const zeus::CVector3f orbitPos = act->GetOrbitPosition(mgr);\n      zeus::CVector3f eyeToOrbit = orbitPos - eyePos;\n      const float eyeToOrbitMag = eyeToOrbit.magnitude();\n      const zeus::CVector3f orbitPosScreen = fpCam->ConvertToScreenSpace(orbitPos);\n      if (orbitPosScreen.z() >= 0.f) {\n        if (x310_orbitTargetId != id) {\n          if (const TCastToConstPtr<CScriptGrapplePoint> point = act.GetPtr()) {\n            if (x310_orbitTargetId != point->GetUniqueId()) {\n              if (mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::GrappleBeam) &&\n                  eyeToOrbitMag < minEyeToOrbitMag && eyeToOrbitMag < g_tweakPlayer->GetOrbitDistanceMax()) {\n                EntityList nearList;\n                TUniqueId intersectId = kInvalidUniqueId;\n                eyeToOrbit.normalize();\n                mgr.BuildNearList(nearList, eyePos, eyeToOrbit, eyeToOrbitMag, OccluderFilter, act.GetPtr());\n                eyeToOrbit.normalize();\n                const CRayCastResult result = mgr.RayWorldIntersection(intersectId, eyePos, eyeToOrbit, eyeToOrbitMag,\n                                                                       LineOfSightFilter, nearList);\n                if (result.IsInvalid()) {\n                  if (point->GetGrappleParameters().GetLockSwingTurn()) {\n                    zeus::CVector3f pointToPlayer = GetTranslation() - point->GetTranslation();\n                    if (pointToPlayer.canBeNormalized()) {\n                      pointToPlayer.z() = 0.f;\n                      if (std::fabs(point->GetTransform().basis[1].normalized().dot(pointToPlayer.normalized())) <=\n                          M_SQRT1_2F) {\n                        continue;\n                      }\n                    }\n                  }\n\n                  bestId = act->GetUniqueId();\n                  const float posInBoxLeft = orbitPosScreen.x() - boxLeft;\n                  const float posInBoxTop = orbitPosScreen.y() - boxTop;\n                  minEyeToOrbitMag = eyeToOrbitMag;\n                  minPosInBoxMagSq = posInBoxLeft * posInBoxLeft + posInBoxTop * posInBoxTop;\n                }\n              }\n              continue;\n            }\n          }\n\n          if (minEyeToOrbitMag - eyeToOrbitMag > g_tweakPlayer->GetOrbitDistanceThreshold() &&\n              mgr.GetPlayerState()->GetCurrentVisor() != CPlayerState::EPlayerVisor::Scan) {\n            EntityList nearList;\n            TUniqueId idOut = kInvalidUniqueId;\n            eyeToOrbit.normalize();\n            mgr.BuildNearList(nearList, eyePos, eyeToOrbit, eyeToOrbitMag, OccluderFilter, act.GetPtr());\n            for (auto it = nearList.begin(); it != nearList.end();) {\n              if (const CEntity* obj = mgr.ObjectById(*it)) {\n                if (obj->GetAreaIdAlways() != kInvalidAreaId) {\n                  if (mgr.GetNextAreaId() != obj->GetAreaIdAlways()) {\n                    const CGameArea* area = mgr.GetWorld()->GetAreaAlways(obj->GetAreaIdAlways());\n                    const CGameArea::EOcclusionState state =\n                        area->IsPostConstructed() ? area->GetOcclusionState() : CGameArea::EOcclusionState::Occluded;\n                    if (state == CGameArea::EOcclusionState::Occluded) {\n                      it = nearList.erase(it);\n                      continue;\n                    }\n                  }\n                } else {\n                  it = nearList.erase(it);\n                  continue;\n                }\n              }\n              ++it;\n            }\n\n            eyeToOrbit.normalize();\n            const CRayCastResult result =\n                mgr.RayWorldIntersection(idOut, eyePos, eyeToOrbit, eyeToOrbitMag, LineOfSightFilter, nearList);\n            if (result.IsInvalid()) {\n              bestId = act->GetUniqueId();\n              const float posInBoxLeft = orbitPosScreen.x() - boxLeft;\n              const float posInBoxTop = orbitPosScreen.y() - boxTop;\n              minEyeToOrbitMag = eyeToOrbitMag;\n              minPosInBoxMagSq = posInBoxLeft * posInBoxLeft + posInBoxTop * posInBoxTop;\n            }\n            continue;\n          }\n\n          if (std::fabs(eyeToOrbitMag - minEyeToOrbitMag) < g_tweakPlayer->GetOrbitDistanceThreshold() ||\n              mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::Scan) {\n            const float posInBoxLeft = orbitPosScreen.x() - boxLeft;\n            const float posInBoxTop = orbitPosScreen.y() - boxTop;\n            const float posInBoxMagSq = posInBoxLeft * posInBoxLeft + posInBoxTop * posInBoxTop;\n            if (posInBoxMagSq < minPosInBoxMagSq) {\n              EntityList nearList;\n              TUniqueId idOut = kInvalidUniqueId;\n              eyeToOrbit.normalize();\n              mgr.BuildNearList(nearList, eyePos, eyeToOrbit, eyeToOrbitMag, OccluderFilter, act.GetPtr());\n              for (auto it = nearList.begin(); it != nearList.end();) {\n                if (const CEntity* obj = mgr.ObjectById(*it)) {\n                  if (obj->GetAreaIdAlways() != kInvalidAreaId) {\n                    if (mgr.GetNextAreaId() != obj->GetAreaIdAlways()) {\n                      const CGameArea* area = mgr.GetWorld()->GetAreaAlways(obj->GetAreaIdAlways());\n                      const CGameArea::EOcclusionState state =\n                          area->IsPostConstructed() ? area->GetOcclusionState() : CGameArea::EOcclusionState::Occluded;\n                      if (state == CGameArea::EOcclusionState::Occluded) {\n                        it = nearList.erase(it);\n                        continue;\n                      }\n                    }\n                  } else {\n                    it = nearList.erase(it);\n                    continue;\n                  }\n                }\n                ++it;\n              }\n\n              eyeToOrbit.normalize();\n              const CRayCastResult result =\n                  mgr.RayWorldIntersection(idOut, eyePos, eyeToOrbit, eyeToOrbitMag, LineOfSightFilter, nearList);\n              if (result.IsInvalid()) {\n                bestId = act->GetUniqueId();\n                minPosInBoxMagSq = posInBoxMagSq;\n                minEyeToOrbitMag = eyeToOrbitMag;\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n\n  return bestId;\n}\n\nvoid CPlayer::FindOrbitableObjects(const EntityList& nearObjects, std::vector<TUniqueId>& listOut, EPlayerZoneInfo zone,\n                                   EPlayerZoneType type, CStateManager& mgr, bool onScreenTest) const {\n  const CFirstPersonCamera* fpCam = mgr.GetCameraManager()->GetFirstPersonCamera();\n  const zeus::CVector3f eyePos = GetEyePosition();\n\n  for (const auto& id : nearObjects) {\n    if (const TCastToConstPtr<CActor> act = mgr.GetObjectById(id)) {\n      if (GetUniqueId() == act->GetUniqueId()) {\n        continue;\n      }\n      if (ValidateOrbitTargetId(act->GetUniqueId(), mgr) != EOrbitValidationResult::OK) {\n        continue;\n      }\n      const zeus::CVector3f orbitPos = act->GetOrbitPosition(mgr);\n      zeus::CVector3f screenPos = fpCam->ConvertToScreenSpace(orbitPos);\n      screenPos.x() = CGraphics::GetViewportWidth() * screenPos.x() / 2.f + CGraphics::GetViewportWidth() / 2.f;\n      screenPos.y() = CGraphics::GetViewportHeight() * screenPos.y() / 2.f + CGraphics::GetViewportHeight() / 2.f;\n\n      bool pass = false;\n      if (onScreenTest) {\n        if (WithinOrbitScreenBox(screenPos, zone, type)) {\n          pass = true;\n        }\n      } else {\n        if (!WithinOrbitScreenBox(screenPos, zone, type)) {\n          pass = true;\n        }\n      }\n\n      if (pass &&\n          (!act->GetDoTargetDistanceTest() || (orbitPos - eyePos).magnitude() <= GetOrbitMaxTargetDistance(mgr))) {\n        listOut.push_back(id);\n      }\n    }\n  }\n}\n\nbool CPlayer::WithinOrbitScreenBox(const zeus::CVector3f& screenCoords, EPlayerZoneInfo zone,\n                                   EPlayerZoneType type) const {\n  if (screenCoords.z() >= 1.f) {\n    return false;\n  }\n\n  switch (type) {\n  case EPlayerZoneType::Box:\n    return std::fabs(screenCoords.x() - GetOrbitScreenBoxCenterXScaled(int(zone))) <\n               GetOrbitScreenBoxHalfExtentXScaled(int(zone)) &&\n           std::fabs(screenCoords.y() - GetOrbitScreenBoxCenterYScaled(int(zone))) <\n               GetOrbitScreenBoxHalfExtentYScaled(int(zone)) &&\n           screenCoords.z() < 1.f;\n  case EPlayerZoneType::Ellipse:\n    return WithinOrbitScreenEllipse(screenCoords, zone);\n  default:\n    return true;\n  }\n}\n\nbool CPlayer::WithinOrbitScreenEllipse(const zeus::CVector3f& screenCoords, EPlayerZoneInfo zone) const {\n  if (screenCoords.z() >= 1.f) {\n    return false;\n  }\n\n  float heYSq = GetOrbitScreenBoxHalfExtentYScaled(int(zone));\n  heYSq *= heYSq;\n  float heXSq = GetOrbitScreenBoxHalfExtentXScaled(int(zone));\n  heXSq *= heXSq;\n  const float tmpY = std::fabs(screenCoords.y() - GetOrbitScreenBoxCenterYScaled(int(zone)));\n  const float tmpX = std::fabs(screenCoords.x() - GetOrbitScreenBoxCenterXScaled(int(zone)));\n  return tmpX * tmpX <= (1.f - tmpY * tmpY / heYSq) * heXSq;\n}\n\nbool CPlayer::CheckOrbitDisableSourceList(CStateManager& mgr) {\n  for (auto it = x9e4_orbitDisableList.begin(); it != x9e4_orbitDisableList.end();) {\n    if (mgr.GetObjectById(*it) == nullptr) {\n      it = x9e4_orbitDisableList.erase(it);\n      continue;\n    }\n    ++it;\n  }\n  return !x9e4_orbitDisableList.empty();\n}\n\nvoid CPlayer::RemoveOrbitDisableSource(TUniqueId uid) {\n  for (auto it = x9e4_orbitDisableList.begin(); it != x9e4_orbitDisableList.end();) {\n    if (*it == uid) {\n      it = x9e4_orbitDisableList.erase(it);\n      return;\n    }\n    ++it;\n  }\n}\n\nvoid CPlayer::AddOrbitDisableSource(CStateManager& mgr, TUniqueId addId) {\n  if (x9e4_orbitDisableList.size() >= 5) {\n    return;\n  }\n\n  for (const auto& uid : x9e4_orbitDisableList) {\n    if (uid == addId) {\n      return;\n    }\n  }\n\n  x9e4_orbitDisableList.push_back(addId);\n  ResetAimTargetPrediction(kInvalidUniqueId);\n  if (!TCastToConstPtr<CScriptGrapplePoint>(mgr.GetObjectById(x310_orbitTargetId))) {\n    SetOrbitTargetId(kInvalidUniqueId, mgr);\n  }\n}\n\nvoid CPlayer::UpdateOrbitPreventionTimer(float dt) {\n  if (x378_orbitPreventionTimer <= 0.f) {\n    return;\n  }\n\n  x378_orbitPreventionTimer -= dt;\n}\n\nvoid CPlayer::UpdateOrbitModeTimer(float dt) {\n  if (x304_orbitState == EPlayerOrbitState::NoOrbit && x32c_orbitModeTimer > 0.f) {\n    x32c_orbitModeTimer -= dt;\n    return;\n  }\n  x32c_orbitModeTimer = 0.f;\n}\n\nvoid CPlayer::UpdateOrbitZone(CStateManager& mgr) {\n  if (mgr.GetPlayerState()->GetCurrentVisor() != CPlayerState::EPlayerVisor::Scan) {\n    x334_orbitType = EPlayerZoneType::Ellipse;\n    x338_ = 1;\n    x330_orbitZoneMode = EPlayerZoneInfo::Targeting;\n  } else {\n    x334_orbitType = EPlayerZoneType::Box;\n    x338_ = 2;\n    x330_orbitZoneMode = EPlayerZoneInfo::Scan;\n  }\n}\n\nvoid CPlayer::UpdateOrbitInput(const CFinalInput& input, CStateManager& mgr) {\n  if (x2f8_morphBallState != EPlayerMorphBallState::Unmorphed || x378_orbitPreventionTimer > 0.f) {\n    return;\n  }\n\n  UpdateOrbitableObjects(mgr);\n  if (x304_orbitState == EPlayerOrbitState::NoOrbit) {\n    x33c_orbitNextTargetId = FindOrbitTargetId(mgr);\n  }\n\n  if (ControlMapper::GetDigitalInput(ControlMapper::ECommands::OrbitClose, input) ||\n      ControlMapper::GetDigitalInput(ControlMapper::ECommands::OrbitFar, input) ||\n      ControlMapper::GetDigitalInput(ControlMapper::ECommands::OrbitObject, input)) {\n    switch (x304_orbitState) {\n    case EPlayerOrbitState::NoOrbit:\n      /* Disabled transitions directly from NoOrbit to OrbitObject for better keyboard handling */\n#if 0\n      if (ControlMapper::GetPressInput(ControlMapper::ECommands::OrbitObject, input)) {\n        SetOrbitTargetId(x33c_orbitNextTargetId, mgr);\n        if (x310_orbitTargetId != kInvalidUniqueId) {\n          if (ValidateAimTargetId(x310_orbitTargetId, mgr)) {\n            ResetAimTargetPrediction(x310_orbitTargetId);\n          }\n          SetOrbitState(EPlayerOrbitState::OrbitObject, mgr);\n          UpdateOrbitPosition(g_tweakPlayer->GetOrbitNormalDistance(int(x308_orbitType)), mgr);\n        }\n      } else {\n#else\n      m_deferredOrbitObject = ControlMapper::GetPressInput(ControlMapper::ECommands::OrbitObject, input);\n#endif\n      if (ControlMapper::GetPressInput(ControlMapper::ECommands::OrbitFar, input)) {\n        OrbitPoint(EPlayerOrbitType::Far, mgr);\n      }\n      if (ControlMapper::GetPressInput(ControlMapper::ECommands::OrbitClose, input)) {\n        OrbitPoint(EPlayerOrbitType::Close, mgr);\n      }\n#if 0\n      }\n#endif\n      break;\n    case EPlayerOrbitState::Grapple:\n      if (x310_orbitTargetId == kInvalidUniqueId) {\n        BreakGrapple(EPlayerOrbitRequest::StopOrbit, mgr);\n      }\n      break;\n    case EPlayerOrbitState::OrbitObject:\n      if (TCastToConstPtr<CScriptGrapplePoint>(mgr.GetObjectById(x310_orbitTargetId))) {\n        if (ValidateCurrentOrbitTargetId(mgr) == EOrbitValidationResult::OK) {\n          UpdateOrbitPosition(g_tweakPlayer->GetOrbitNormalDistance(int(x308_orbitType)), mgr);\n        } else {\n          BreakGrapple(EPlayerOrbitRequest::InvalidateTarget, mgr);\n        }\n      } else {\n        const EOrbitValidationResult result = ValidateCurrentOrbitTargetId(mgr);\n        if (result == EOrbitValidationResult::OK) {\n          UpdateOrbitPosition(g_tweakPlayer->GetOrbitNormalDistance(int(x308_orbitType)), mgr);\n        } else if (result == EOrbitValidationResult::BrokenLookAngle) {\n          OrbitPoint(EPlayerOrbitType::Far, mgr);\n        } else if (result == EOrbitValidationResult::ExtremeHorizonAngle) {\n          SetOrbitRequest(EPlayerOrbitRequest::BadVerticalAngle, mgr);\n        } else {\n          ActivateOrbitSource(mgr);\n        }\n      }\n      UpdateOrbitSelection(input, mgr);\n      break;\n    case EPlayerOrbitState::OrbitPoint:\n      if (ControlMapper::GetPressInput(ControlMapper::ECommands::OrbitObject, input) || m_deferredOrbitObject) {\n        m_deferredOrbitObject = false;\n        SetOrbitTargetId(FindOrbitTargetId(mgr), mgr);\n        if (x310_orbitTargetId != kInvalidUniqueId) {\n          if (ValidateAimTargetId(x310_orbitTargetId, mgr)) {\n            ResetAimTargetPrediction(x310_orbitTargetId);\n          }\n          SetOrbitState(EPlayerOrbitState::OrbitObject, mgr);\n          UpdateOrbitPosition(g_tweakPlayer->GetOrbitNormalDistance(int(x308_orbitType)), mgr);\n        }\n      } else {\n        switch (x308_orbitType) {\n        case EPlayerOrbitType::Far:\n          if (ControlMapper::GetDigitalInput(ControlMapper::ECommands::OrbitClose, input)) {\n            x308_orbitType = EPlayerOrbitType::Close;\n            UpdateOrbitPosition(g_tweakPlayer->GetOrbitNormalDistance(int(x308_orbitType)), mgr);\n          }\n          break;\n        case EPlayerOrbitType::Close:\n          if (ControlMapper::GetDigitalInput(ControlMapper::ECommands::OrbitFar, input) &&\n              !ControlMapper::GetDigitalInput(ControlMapper::ECommands::OrbitClose, input)) {\n            x308_orbitType = EPlayerOrbitType::Far;\n            UpdateOrbitPosition(g_tweakPlayer->GetOrbitNormalDistance(int(x308_orbitType)), mgr);\n          }\n          break;\n        default:\n          break;\n        }\n      }\n      UpdateOrbitPosition(g_tweakPlayer->GetOrbitNormalDistance(int(x308_orbitType)), mgr);\n      break;\n    case EPlayerOrbitState::OrbitCarcass:\n      if (ControlMapper::GetPressInput(ControlMapper::ECommands::OrbitObject, input) || m_deferredOrbitObject) {\n        m_deferredOrbitObject = false;\n        SetOrbitTargetId(FindOrbitTargetId(mgr), mgr);\n        if (x310_orbitTargetId != kInvalidUniqueId) {\n          if (ValidateAimTargetId(x310_orbitTargetId, mgr)) {\n            ResetAimTargetPrediction(x310_orbitTargetId);\n          }\n          SetOrbitState(EPlayerOrbitState::OrbitObject, mgr);\n          UpdateOrbitPosition(g_tweakPlayer->GetOrbitNormalDistance(int(x308_orbitType)), mgr);\n        }\n      }\n      UpdateOrbitSelection(input, mgr);\n      break;\n    case EPlayerOrbitState::ForcedOrbitObject:\n      UpdateOrbitPosition(g_tweakPlayer->GetOrbitNormalDistance(int(x308_orbitType)), mgr);\n      UpdateOrbitSelection(input, mgr);\n      break;\n    }\n\n    if (x304_orbitState == EPlayerOrbitState::Grapple) {\n      x33c_orbitNextTargetId = FindOrbitTargetId(mgr);\n      if (x33c_orbitNextTargetId == x310_orbitTargetId) {\n        x33c_orbitNextTargetId = kInvalidUniqueId;\n      }\n    }\n  } else {\n    switch (x304_orbitState) {\n    case EPlayerOrbitState::NoOrbit:\n      break;\n    case EPlayerOrbitState::OrbitObject:\n      if (TCastToConstPtr<CScriptGrapplePoint>(mgr.GetObjectById(x310_orbitTargetId))) {\n        BreakGrapple(EPlayerOrbitRequest::Default, mgr);\n      } else {\n        SetOrbitRequest(EPlayerOrbitRequest::StopOrbit, mgr);\n      }\n      break;\n    case EPlayerOrbitState::Grapple:\n      if (!g_tweakPlayer->GetOrbitReleaseBreaksGrapple()) {\n        x33c_orbitNextTargetId = FindOrbitTargetId(mgr);\n        if (x33c_orbitNextTargetId == x310_orbitTargetId) {\n          x33c_orbitNextTargetId = kInvalidUniqueId;\n        }\n      } else {\n        BreakGrapple(EPlayerOrbitRequest::StopOrbit, mgr);\n      }\n      break;\n    case EPlayerOrbitState::ForcedOrbitObject:\n      UpdateOrbitPosition(g_tweakPlayer->GetOrbitNormalDistance(int(x308_orbitType)), mgr);\n      UpdateOrbitSelection(input, mgr);\n      break;\n    default:\n      SetOrbitRequest(EPlayerOrbitRequest::StopOrbit, mgr);\n      break;\n    }\n  }\n}\n\nvoid CPlayer::ActivateOrbitSource(CStateManager& mgr) {\n  switch (x390_orbitSource) {\n  default:\n    OrbitCarcass(mgr);\n    break;\n  case 1:\n    SetOrbitRequest(EPlayerOrbitRequest::InvalidateTarget, mgr);\n    break;\n  case 2:\n    if (x394_orbitingEnemy) {\n      OrbitPoint(EPlayerOrbitType::Far, mgr);\n    } else {\n      OrbitCarcass(mgr);\n    }\n    break;\n  }\n}\n\nvoid CPlayer::UpdateOrbitSelection(const CFinalInput& input, CStateManager& mgr) {\n  x33c_orbitNextTargetId = FindOrbitTargetId(mgr);\n  const TCastToConstPtr<CScriptGrapplePoint> curPoint = mgr.GetObjectById(x310_orbitTargetId);\n  const TCastToConstPtr<CScriptGrapplePoint> nextPoint = mgr.GetObjectById(x33c_orbitNextTargetId);\n  if (curPoint || (x304_orbitState == EPlayerOrbitState::Grapple && !nextPoint)) {\n    x33c_orbitNextTargetId = kInvalidUniqueId;\n    return;\n  }\n\n  if (ControlMapper::GetPressInput(ControlMapper::ECommands::OrbitObject, input) &&\n      x33c_orbitNextTargetId != kInvalidUniqueId) {\n    SetOrbitTargetId(x33c_orbitNextTargetId, mgr);\n    if (ValidateAimTargetId(x310_orbitTargetId, mgr)) {\n      ResetAimTargetPrediction(x310_orbitTargetId);\n    }\n    SetOrbitState(EPlayerOrbitState::OrbitObject, mgr);\n    UpdateOrbitPosition(g_tweakPlayer->GetOrbitNormalDistance(int(x308_orbitType)), mgr);\n  }\n}\n\nvoid CPlayer::UpdateOrbitOrientation(CStateManager& mgr) {\n  if (x2f8_morphBallState != EPlayerMorphBallState::Unmorphed) {\n    return;\n  }\n\n  switch (x304_orbitState) {\n  case EPlayerOrbitState::OrbitPoint:\n    if (x3dc_inFreeLook) {\n      return;\n    }\n    [[fallthrough]];\n  case EPlayerOrbitState::OrbitObject:\n  case EPlayerOrbitState::OrbitCarcass:\n  case EPlayerOrbitState::ForcedOrbitObject: {\n    zeus::CVector3f playerToPoint = x314_orbitPoint - GetTranslation();\n    if (!x374_orbitLockEstablished) {\n      playerToPoint = mgr.GetCameraManager()->GetFirstPersonCamera()->GetTransform().basis[1];\n    }\n    playerToPoint.z() = 0.f;\n    if (playerToPoint.canBeNormalized()) {\n      zeus::CTransform xf = zeus::lookAt(zeus::skZero3f, playerToPoint);\n      xf.origin = GetTranslation();\n      SetTransform(xf);\n    }\n    break;\n  }\n  default:\n    break;\n  }\n}\n\nvoid CPlayer::UpdateOrbitTarget(CStateManager& mgr) {\n  if (!ValidateOrbitTargetIdAndPointer(x310_orbitTargetId, mgr)) {\n    SetOrbitTargetId(kInvalidUniqueId, mgr);\n  }\n  if (!ValidateOrbitTargetIdAndPointer(x33c_orbitNextTargetId, mgr)) {\n    x33c_orbitNextTargetId = kInvalidUniqueId;\n  }\n\n  zeus::CVector3f playerToPoint = x314_orbitPoint - GetTranslation();\n  playerToPoint.z() = 0.f;\n  const float playerToPointMag = playerToPoint.magnitude();\n\n  switch (x304_orbitState) {\n  case EPlayerOrbitState::OrbitObject:\n    if (const auto* ent = static_cast<const CActor*>(mgr.GetObjectById(x310_orbitTargetId))) {\n      if (ent->GetDoTargetDistanceTest() &&\n          (playerToPointMag >= GetOrbitMaxLockDistance(mgr) || playerToPointMag < 0.5f)) {\n        if (playerToPointMag < 0.5f) {\n          SetOrbitRequest(EPlayerOrbitRequest::BadVerticalAngle, mgr);\n        } else {\n          ActivateOrbitSource(mgr);\n        }\n      }\n    }\n    UpdateOrbitPosition(g_tweakPlayer->GetOrbitNormalDistance(int(x308_orbitType)), mgr);\n    break;\n  case EPlayerOrbitState::OrbitPoint: {\n    if (g_tweakPlayer->GetOrbitFixedOffset() &&\n        std::fabs(x320_orbitVector.z()) > g_tweakPlayer->GetOrbitFixedOffsetZDiff()) {\n      UpdateOrbitFixedPosition();\n      return;\n    }\n    if (playerToPointMag < CalculateOrbitMinDistance(x308_orbitType)) {\n      UpdateOrbitPosition(CalculateOrbitMinDistance(x308_orbitType), mgr);\n    }\n    const float maxDist = g_tweakPlayer->GetOrbitMaxDistance(int(x308_orbitType));\n    if (playerToPointMag > maxDist) {\n      UpdateOrbitPosition(maxDist, mgr);\n    }\n    if (x3dd_lookButtonHeld) {\n      SetOrbitPosition(g_tweakPlayer->GetOrbitNormalDistance(int(x308_orbitType)), mgr);\n    }\n    const zeus::CVector3f eyeToPoint = x314_orbitPoint - GetEyePosition();\n    const float angleToPoint = std::asin(zeus::clamp(-1.f, std::fabs(eyeToPoint.z()) / eyeToPoint.magnitude(), 1.f));\n    if ((eyeToPoint.z() >= 0.f && angleToPoint >= g_tweakPlayer->GetOrbitUpperAngle()) ||\n        (eyeToPoint.z() < 0.f && angleToPoint >= g_tweakPlayer->GetOrbitLowerAngle())) {\n      SetOrbitRequest(EPlayerOrbitRequest::BadVerticalAngle, mgr);\n    }\n    break;\n  }\n  case EPlayerOrbitState::OrbitCarcass: {\n    if (x3dd_lookButtonHeld) {\n      SetOrbitPosition(x340_, mgr);\n    }\n    if (playerToPointMag < CalculateOrbitMinDistance(x308_orbitType)) {\n      UpdateOrbitPosition(CalculateOrbitMinDistance(x308_orbitType), mgr);\n      x340_ = CalculateOrbitMinDistance(x308_orbitType);\n    }\n    const float maxDist = g_tweakPlayer->GetOrbitMaxDistance(int(x308_orbitType));\n    if (playerToPointMag > maxDist) {\n      UpdateOrbitPosition(maxDist, mgr);\n      x340_ = g_tweakPlayer->GetOrbitMaxDistance(int(x308_orbitType));\n    }\n    break;\n  }\n  case EPlayerOrbitState::NoOrbit:\n    SetOrbitTargetId(kInvalidUniqueId, mgr);\n    break;\n  default:\n    break;\n  }\n  UpdateOrbitZPosition();\n}\n\nfloat CPlayer::GetOrbitMaxLockDistance(CStateManager& mgr) const {\n  return mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::Scan\n             ? g_tweakPlayer->GetScanMaxLockDistance()\n             : g_tweakPlayer->GetOrbitMaxLockDistance();\n}\n\nfloat CPlayer::GetOrbitMaxTargetDistance(CStateManager& mgr) const {\n  return mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::Scan\n             ? g_tweakPlayer->GetScanMaxTargetDistance()\n             : g_tweakPlayer->GetOrbitMaxTargetDistance();\n}\n\nCPlayer::EOrbitValidationResult CPlayer::ValidateOrbitTargetId(TUniqueId uid, CStateManager& mgr) const {\n  if (uid == kInvalidUniqueId) {\n    return EOrbitValidationResult::InvalidTarget;\n  }\n\n  const TCastToConstPtr<CActor> act = mgr.GetObjectById(uid);\n  if (!act || !act->GetIsTargetable() || !act->GetActive()) {\n    return EOrbitValidationResult::InvalidTarget;\n  }\n\n  if (x740_staticTimer != 0.f) {\n    return EOrbitValidationResult::PlayerNotReadyToTarget;\n  }\n\n  const zeus::CVector3f eyePos = GetEyePosition();\n  const zeus::CVector3f eyeToOrbit = act->GetOrbitPosition(mgr) - eyePos;\n  zeus::CVector3f eyeToOrbitFlat = eyeToOrbit;\n  eyeToOrbitFlat.z() = 0.f;\n\n  if (eyeToOrbitFlat.canBeNormalized() && eyeToOrbitFlat.magnitude() > 1.f) {\n    const float angleFromHorizon =\n        std::asin(zeus::clamp(-1.f, std::fabs(eyeToOrbit.z()) / eyeToOrbit.magnitude(), 1.f));\n    if ((eyeToOrbit.z() >= 0.f && angleFromHorizon >= g_tweakPlayer->GetOrbitUpperAngle()) ||\n        (eyeToOrbit.z() < 0.f && angleFromHorizon >= g_tweakPlayer->GetOrbitLowerAngle())) {\n      return EOrbitValidationResult::ExtremeHorizonAngle;\n    }\n  } else {\n    return EOrbitValidationResult::ExtremeHorizonAngle;\n  }\n\n  const CPlayerState::EPlayerVisor visor = mgr.GetPlayerState()->GetCurrentVisor();\n  const u8 flags = act->GetTargetableVisorFlags();\n  if (visor == CPlayerState::EPlayerVisor::Combat && (flags & 1) == 0) {\n    return EOrbitValidationResult::PlayerNotReadyToTarget;\n  }\n  if (visor == CPlayerState::EPlayerVisor::Scan && (flags & 2) == 0) {\n    return EOrbitValidationResult::PlayerNotReadyToTarget;\n  }\n  if (visor == CPlayerState::EPlayerVisor::Thermal && (flags & 4) == 0) {\n    return EOrbitValidationResult::PlayerNotReadyToTarget;\n  }\n  if (visor == CPlayerState::EPlayerVisor::XRay && (flags & 8) == 0) {\n    return EOrbitValidationResult::PlayerNotReadyToTarget;\n  }\n\n  if (visor == CPlayerState::EPlayerVisor::Scan && act->GetAreaIdAlways() != GetAreaIdAlways()) {\n    return EOrbitValidationResult::TargetingThroughDoor;\n  }\n\n  return EOrbitValidationResult::OK;\n}\n\nCPlayer::EOrbitValidationResult CPlayer::ValidateCurrentOrbitTargetId(CStateManager& mgr) {\n  const TCastToConstPtr<CActor> act = mgr.GetObjectById(x310_orbitTargetId);\n  if (!act || !act->GetIsTargetable() || !act->GetActive()) {\n    return EOrbitValidationResult::InvalidTarget;\n  }\n\n  if (!act->GetMaterialList().HasMaterial(EMaterialTypes::Orbit)) {\n    if (!act->GetMaterialList().HasMaterial(EMaterialTypes::Scannable)) {\n      return EOrbitValidationResult::NonTargetableTarget;\n    }\n    if (mgr.GetPlayerState()->GetCurrentVisor() != CPlayerState::EPlayerVisor::Scan) {\n      return EOrbitValidationResult::NonTargetableTarget;\n    }\n  }\n\n  const EOrbitValidationResult type = ValidateOrbitTargetId(x310_orbitTargetId, mgr);\n  if (type != EOrbitValidationResult::OK) {\n    return type;\n  }\n\n  if (mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::Scan &&\n      act->GetAreaIdAlways() != GetAreaIdAlways()) {\n    return EOrbitValidationResult::TargetingThroughDoor;\n  }\n\n  const TCastToConstPtr<CScriptGrapplePoint> point = mgr.GetObjectById(x310_orbitTargetId);\n  if ((mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::Scan &&\n       g_tweakPlayer->GetOrbitWhileScanning()) ||\n      point || act->GetAreaIdAlways() != GetAreaIdAlways()) {\n    const zeus::CVector3f eyePos = GetEyePosition();\n    TUniqueId bestId = kInvalidUniqueId;\n    const zeus::CVector3f eyeToOrbit = act->GetOrbitPosition(mgr) - eyePos;\n    if (eyeToOrbit.canBeNormalized()) {\n      EntityList nearList;\n      mgr.BuildNearList(nearList, eyePos, eyeToOrbit.normalized(), eyeToOrbit.magnitude(), OccluderFilter,\n                        act.GetPtr());\n      for (auto it = nearList.begin(); it != nearList.end();) {\n        if (const CEntity* ent = mgr.GetObjectById(*it)) {\n          if (ent->GetAreaIdAlways() != mgr.GetNextAreaId()) {\n            const CGameArea* area = mgr.GetWorld()->GetAreaAlways(ent->GetAreaIdAlways());\n            CGameArea::EOcclusionState occState = CGameArea::EOcclusionState::Occluded;\n            if (area->IsPostConstructed()) {\n              occState = area->GetOcclusionState();\n            }\n            if (occState == CGameArea::EOcclusionState::Occluded) {\n              it = nearList.erase(it);\n              continue;\n            }\n          }\n        }\n        ++it;\n      }\n\n      const CRayCastResult result = mgr.RayWorldIntersection(bestId, eyePos, eyeToOrbit.normalized(),\n                                                             eyeToOrbit.magnitude(), LineOfSightFilter, nearList);\n      if (result.IsValid()) {\n        if (TCastToConstPtr<CScriptDoor>(mgr.ObjectById(bestId)) || point) {\n          return EOrbitValidationResult::TargetingThroughDoor;\n        }\n      }\n    }\n\n    zeus::CVector3f eyeToOrbitFlat = eyeToOrbit;\n    eyeToOrbitFlat.z() = 0.f;\n    if (eyeToOrbitFlat.canBeNormalized()) {\n      const float lookToOrbitAngle =\n          std::acos(zeus::clamp(-1.f, eyeToOrbitFlat.normalized().dot(GetTransform().basis[1]), 1.f));\n      if (x374_orbitLockEstablished) {\n        if (lookToOrbitAngle >= g_tweakPlayer->GetOrbitHorizAngle()) {\n          return EOrbitValidationResult::BrokenLookAngle;\n        }\n      } else {\n        if (lookToOrbitAngle <= M_PIF / 180.f) {\n          x374_orbitLockEstablished = true;\n        }\n      }\n    } else {\n      return EOrbitValidationResult::BrokenLookAngle;\n    }\n  }\n\n  return EOrbitValidationResult::OK;\n}\n\nbool CPlayer::ValidateOrbitTargetIdAndPointer(TUniqueId uid, CStateManager& mgr) const {\n  if (uid == kInvalidUniqueId) {\n    return false;\n  }\n  return TCastToConstPtr<CActor>(mgr.GetObjectById(uid)).IsValid();\n}\n\nzeus::CVector3f CPlayer::GetBallPosition() const {\n  return GetTranslation() + zeus::CVector3f(0.f, 0.f, g_tweakPlayer->GetPlayerBallHalfExtent());\n}\n\nzeus::CVector3f CPlayer::GetEyePosition() const { return GetTranslation() + zeus::CVector3f(0.f, 0.f, GetEyeHeight()); }\n\nfloat CPlayer::GetEyeHeight() const { return x9c8_eyeZBias + (x2d8_fpBounds.max.z() - g_tweakPlayer->GetEyeOffset()); }\n\nfloat CPlayer::GetUnbiasedEyeHeight() const { return x2d8_fpBounds.max.z() - g_tweakPlayer->GetEyeOffset(); }\n\nfloat CPlayer::GetStepUpHeight() const {\n  if (x258_movementState == EPlayerMovementState::Jump || x258_movementState == EPlayerMovementState::ApplyJump) {\n    return 0.3f;\n  }\n  return CPhysicsActor::GetStepUpHeight();\n}\n\nfloat CPlayer::GetStepDownHeight() const {\n  if (x258_movementState == EPlayerMovementState::Jump) {\n    return -1.f;\n  }\n  if (x258_movementState == EPlayerMovementState::ApplyJump) {\n    return 0.1f;\n  }\n  return CPhysicsActor::GetStepDownHeight();\n}\n\nvoid CPlayer::Teleport(const zeus::CTransform& xf, CStateManager& mgr, bool resetBallCam) {\n  CPhysicsActor::Stop();\n  zeus::CVector3f lookDir = xf.basis[1];\n  if (lookDir.canBeNormalized()) {\n    lookDir.normalize();\n    SetTransform(zeus::lookAt(zeus::skZero3f, lookDir));\n    SetTranslation(xf.origin);\n    x500_lookDir = lookDir;\n    x50c_moveDir = lookDir;\n    x530_gunDir = lookDir;\n    x524_lastPosForDirCalc = xf.origin;\n    x4f8_moveSpeed = 0.f;\n    x4fc_flatMoveSpeed = 0.f;\n    x53c_timeMoving = 0.f;\n    x4a4_moveSpeedAvg.Clear();\n    x540_controlDir = lookDir;\n    x54c_controlDirFlat = lookDir;\n  } else {\n    SetTranslation(xf.origin);\n  }\n\n  x9c5_31_stepCameraZBiasDirty = true;\n  x9c8_eyeZBias = 0.f;\n  x1f4_lastNonCollidingState = GetMotionState();\n  SetMoveState(EPlayerMovementState::OnGround, mgr);\n  zeus::CTransform eyeXf = x34_transform;\n  eyeXf.origin = GetEyePosition();\n  mgr.GetCameraManager()->GetFirstPersonCamera()->Reset(eyeXf, mgr);\n  if (resetBallCam) {\n    mgr.GetCameraManager()->GetBallCamera()->Reset(eyeXf, mgr);\n  }\n  ForceGunOrientation(x34_transform, mgr);\n  SetOrbitRequest(EPlayerOrbitRequest::Respawn, mgr);\n}\n\nvoid CPlayer::BombJump(const zeus::CVector3f& pos, CStateManager& mgr) {\n  if (x2f8_morphBallState == EPlayerMorphBallState::Morphed &&\n      x768_morphball->GetBombJumpState() != CMorphBall::EBombJumpState::BombJumpDisabled) {\n    const zeus::CVector3f posToBall =\n        GetTranslation() + zeus::CVector3f(0.f, 0.f, g_tweakPlayer->GetPlayerBallHalfExtent()) - pos;\n    const float maxJump = g_tweakPlayer->GetBombJumpHeight();\n    if (posToBall.magSquared() < maxJump * maxJump &&\n        posToBall.dot(zeus::skUp) >= -g_tweakPlayer->GetPlayerBallHalfExtent()) {\n      float upVel =\n          std::sqrt(2.f * std::fabs(g_tweakPlayer->GetNormalGravAccel()) * g_tweakPlayer->GetBombJumpRadius());\n      mgr.GetRumbleManager().Rumble(mgr, ERumbleFxId::PlayerBump, 0.3f, ERumblePriority::One);\n      x2a0_ = 0.01f;\n      switch (GetSurfaceRestraint()) {\n      case ESurfaceRestraints::Water:\n        upVel *= g_tweakPlayer->GetWaterBallJumpFactor();\n        break;\n      case ESurfaceRestraints::Lava:\n        upVel *= g_tweakPlayer->GetLavaBallJumpFactor();\n        break;\n      case ESurfaceRestraints::Phazon:\n        upVel *= g_tweakPlayer->GetPhazonBallJumpFactor();\n        break;\n      default:\n        break;\n      }\n      SetVelocityWR(zeus::CVector3f(0.f, 0.f, upVel));\n      x768_morphball->SetDamageTimer(0.1f);\n      x768_morphball->CancelBoosting();\n      if (x9d0_bombJumpCount > 0) {\n        if (x9d0_bombJumpCount > 2) {\n          x9d0_bombJumpCount = 0;\n          x9d4_bombJumpCheckDelayFrames = 0;\n        } else {\n          ++x9d0_bombJumpCount;\n        }\n      } else {\n        const CBallCamera* ballCam = mgr.GetCameraManager()->GetBallCamera();\n        if (ballCam->GetTooCloseActorId() != kInvalidUniqueId && ballCam->GetTooCloseActorDistance() < 5.f) {\n          x9d0_bombJumpCount = 1;\n          x9d4_bombJumpCheckDelayFrames = 2;\n        }\n      }\n      CSfxHandle hnd = CSfxManager::AddEmitter(SFXsam_ball_jump, GetTranslation(), zeus::skZero3f, false, false, 0x7f,\n                                               kInvalidAreaId);\n      ApplySubmergedPitchBend(hnd);\n    }\n  }\n}\n\nzeus::CTransform CPlayer::CreateTransformFromMovementDirection() const {\n  zeus::CVector3f moveDir = x50c_moveDir;\n  if (moveDir.canBeNormalized()) {\n    moveDir.normalize();\n  } else {\n    moveDir = zeus::skForward;\n  }\n\n  return {zeus::CVector3f(moveDir.y(), -moveDir.x(), 0.f), moveDir, zeus::skUp, GetTranslation()};\n}\n\nconst CCollisionPrimitive* CPlayer::GetCollisionPrimitive() const {\n  switch (x2f8_morphBallState) {\n  case EPlayerMorphBallState::Morphed:\n    return GetCollidableSphere();\n  default:\n    return CPhysicsActor::GetCollisionPrimitive();\n  }\n}\n\nconst CCollidableSphere* CPlayer::GetCollidableSphere() const { return &x768_morphball->GetCollidableSphere(); }\n\nzeus::CTransform CPlayer::GetPrimitiveTransform() const { return CPhysicsActor::GetPrimitiveTransform(); }\n\nvoid CPlayer::CollidedWith(TUniqueId id, const CCollisionInfoList& list, CStateManager& mgr) {\n  if (x2f8_morphBallState == EPlayerMorphBallState::Unmorphed) {\n    return;\n  }\n\n  x768_morphball->CollidedWith(id, list, mgr);\n}\n\nfloat CPlayer::GetBallMaxVelocity() const {\n  return g_tweakBall->GetBallTranslationMaxSpeed(int(GetSurfaceRestraint()));\n}\n\nfloat CPlayer::GetActualBallMaxVelocity(float dt) const {\n  const ESurfaceRestraints surf = GetSurfaceRestraint();\n  const float friction = g_tweakBall->GetBallTranslationFriction(int(surf));\n  const float maxSpeed = g_tweakBall->GetBallTranslationMaxSpeed(int(surf));\n  const float acceleration = g_tweakBall->GetMaxBallTranslationAcceleration(int(surf));\n  return -(friction * xe8_mass * maxSpeed / (acceleration * dt) - maxSpeed - friction);\n}\n\nfloat CPlayer::GetActualFirstPersonMaxVelocity(float dt) const {\n  const ESurfaceRestraints surf = GetSurfaceRestraint();\n  const float friction = g_tweakPlayer->GetPlayerTranslationFriction(int(surf));\n  const float maxSpeed = g_tweakPlayer->GetPlayerTranslationMaxSpeed(int(surf));\n  const float acceleration = g_tweakPlayer->GetMaxTranslationalAcceleration(int(surf));\n  return -(friction * xe8_mass * maxSpeed / (acceleration * dt) - maxSpeed - friction);\n}\n\nvoid CPlayer::SetMoveState(EPlayerMovementState newState, CStateManager& mgr) {\n  switch (newState) {\n  case EPlayerMovementState::Jump:\n    if (x258_movementState == EPlayerMovementState::ApplyJump) {\n      CSfxHandle hnd = CSfxManager::SfxStart(SFXsam_b_jump_00, 1.f, 0.f, true, 0x7f, false, kInvalidAreaId);\n      ApplySubmergedPitchBend(hnd);\n      mgr.GetRumbleManager().Rumble(mgr, ERumbleFxId::PlayerBump, 0.2015f, ERumblePriority::One);\n      x288_startingJumpTimeout = g_tweakPlayer->GetAllowedDoubleJumpTime();\n      x290_minJumpTimeout = g_tweakPlayer->GetAllowedDoubleJumpTime() - g_tweakPlayer->GetMinDoubleJumpTime();\n      x28c_sjTimer = 0.f;\n    } else if (x258_movementState != EPlayerMovementState::Jump) {\n      CSfxHandle hnd = CSfxManager::SfxStart(SFXsam_firstjump, 1.f, 0.f, true, 0x7f, false, kInvalidAreaId);\n      ApplySubmergedPitchBend(hnd);\n      x2a0_ = 0.01f;\n      x288_startingJumpTimeout = g_tweakPlayer->GetAllowedJumpTime();\n      x290_minJumpTimeout = g_tweakPlayer->GetAllowedJumpTime() - g_tweakPlayer->GetMinJumpTime();\n      if (mgr.GetPlayerState()->GetItemAmount(CPlayerState::EItemType::SpaceJumpBoots) != 0) {\n        x28c_sjTimer = g_tweakPlayer->GetMaxDoubleJumpWindow();\n      } else {\n        x28c_sjTimer = 0.f;\n      }\n      if (x294_jumpCameraTimer <= 0.f && x29c_fallCameraTimer <= 0.f && !x3dc_inFreeLook && !x3dd_lookButtonHeld) {\n        x294_jumpCameraTimer = 0.01f;\n        x2a4_cancelCameraPitch = false;\n      }\n    }\n    x258_movementState = EPlayerMovementState::Jump;\n    x2ac_surfaceRestraint = ESurfaceRestraints::Air;\n    x2a8_timeSinceJump = 0.f;\n    break;\n  case EPlayerMovementState::Falling:\n    if (x258_movementState == EPlayerMovementState::OnGround) {\n      x288_startingJumpTimeout = g_tweakPlayer->GetAllowedLedgeTime();\n      x258_movementState = EPlayerMovementState::Falling;\n      x2a0_ = 0.01f;\n      if (g_tweakPlayer->GetFallingDoubleJump()) {\n        x28c_sjTimer = g_tweakPlayer->GetMaxDoubleJumpWindow();\n      } else {\n        x28c_sjTimer = 0.f;\n      }\n    }\n    break;\n  case EPlayerMovementState::FallingMorphed:\n    x258_movementState = EPlayerMovementState::FallingMorphed;\n    x2ac_surfaceRestraint = ESurfaceRestraints::Normal;\n    break;\n  case EPlayerMovementState::OnGround:\n    x300_fallingTime = 0.f;\n    x258_movementState = EPlayerMovementState::OnGround;\n    x288_startingJumpTimeout = 0.f;\n    x28c_sjTimer = 0.f;\n    x2ac_surfaceRestraint = ESurfaceRestraints::Normal;\n    if (x2f8_morphBallState != EPlayerMorphBallState::Morphed) {\n      AddMaterial(EMaterialTypes::GroundCollider);\n    }\n    x294_jumpCameraTimer = 0.f;\n    x29c_fallCameraTimer = 0.f;\n    x2a4_cancelCameraPitch = false;\n    x298_jumpPresses = 0;\n    break;\n  case EPlayerMovementState::ApplyJump:\n    x288_startingJumpTimeout = 0.f;\n    if (x258_movementState != EPlayerMovementState::ApplyJump) {\n      x258_movementState = EPlayerMovementState::ApplyJump;\n      if (x294_jumpCameraTimer <= x288_startingJumpTimeout && x29c_fallCameraTimer <= x288_startingJumpTimeout &&\n          !x3dc_inFreeLook && !x3dd_lookButtonHeld) {\n        x29c_fallCameraTimer = 0.01f;\n        x2a4_cancelCameraPitch = false;\n      }\n    }\n    x2ac_surfaceRestraint = ESurfaceRestraints::Air;\n    break;\n  }\n}\n\nfloat CPlayer::JumpInput(const CFinalInput& input, CStateManager& mgr) {\n  if (IsMorphBallTransitioning()) {\n    return GetGravity() * xe8_mass;\n  }\n\n  float jumpFactor = 1.f;\n  if (!mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::GravitySuit)) {\n    const ESurfaceRestraints restraints = GetSurfaceRestraint();\n    switch (restraints) {\n    case ESurfaceRestraints::Water:\n      jumpFactor = g_tweakPlayer->GetWaterJumpFactor();\n      break;\n    case ESurfaceRestraints::Lava:\n      jumpFactor = g_tweakPlayer->GetLavaJumpFactor();\n      break;\n    case ESurfaceRestraints::Phazon:\n      jumpFactor = g_tweakPlayer->GetPhazonJumpFactor();\n      break;\n    default:\n      break;\n    }\n  }\n\n  const float vJumpAccel = g_tweakPlayer->GetVerticalJumpAccel();\n  const float hJumpAccel = g_tweakPlayer->GetHorizontalJumpAccel();\n  float doubleJumpImpulse = g_tweakPlayer->GetDoubleJumpImpulse();\n  float vDoubleJumpAccel = g_tweakPlayer->GetVerticalDoubleJumpAccel();\n  float hDoubleJumpAccel = g_tweakPlayer->GetHorizontalDoubleJumpAccel();\n\n  if (x37c_sidewaysDashing) {\n    doubleJumpImpulse = g_tweakPlayer->GetSidewaysDoubleJumpImpulse();\n    vDoubleJumpAccel = g_tweakPlayer->GetSidewaysVerticalDoubleJumpAccel();\n    hDoubleJumpAccel = g_tweakPlayer->GetSidewaysHorizontalDoubleJumpAccel();\n  }\n\n  if (x828_distanceUnderWater >= 0.8f * GetEyeHeight()) {\n    doubleJumpImpulse *= jumpFactor;\n  }\n\n  if (x258_movementState == EPlayerMovementState::ApplyJump) {\n    if (g_tweakPlayer->GetMaxDoubleJumpWindow() - g_tweakPlayer->GetMinDoubleJumpWindow() >= x28c_sjTimer &&\n        0.f < x28c_sjTimer && ControlMapper::GetPressInput(ControlMapper::ECommands::JumpOrBoost, input)) {\n      SetMoveState(EPlayerMovementState::Jump, mgr);\n      x384_dashTimer = 0.f;\n      x380_strafeInputAtDash = StrafeInput(input);\n      if (g_tweakPlayer->GetImpulseDoubleJump()) {\n        const zeus::CVector3f impulse(0.f, 0.f, (doubleJumpImpulse - x138_velocity.z()) * xe8_mass);\n        ApplyImpulseWR(impulse, zeus::CAxisAngle());\n      }\n\n      float forwards = ControlMapper::GetAnalogInput(ControlMapper::ECommands::Forward, input);\n      const float backwards = ControlMapper::GetAnalogInput(ControlMapper::ECommands::Backward, input);\n      if (forwards < backwards) {\n        forwards = ControlMapper::GetAnalogInput(ControlMapper::ECommands::Backward, input);\n      }\n      return ((vDoubleJumpAccel - (vDoubleJumpAccel - hDoubleJumpAccel) * forwards) * xe8_mass) * jumpFactor;\n    }\n\n    return GetGravity() * xe8_mass;\n  }\n\n  if (ControlMapper::GetDigitalInput(ControlMapper::ECommands::JumpOrBoost, input) ||\n      (x258_movementState == EPlayerMovementState::Jump && x290_minJumpTimeout <= x288_startingJumpTimeout)) {\n    if (x258_movementState != EPlayerMovementState::Jump) {\n      if (ControlMapper::GetPressInput(ControlMapper::ECommands::JumpOrBoost, input)) {\n        SetMoveState(EPlayerMovementState::Jump, mgr);\n        return (vJumpAccel * xe8_mass) * jumpFactor;\n      }\n      return 0.f;\n    }\n    float forwards = ControlMapper::GetAnalogInput(ControlMapper::ECommands::Forward, input);\n    const float backwards = ControlMapper::GetAnalogInput(ControlMapper::ECommands::Backward, input);\n    if (forwards < backwards) {\n      forwards = ControlMapper::GetAnalogInput(ControlMapper::ECommands::Backward, input);\n    }\n    return ((vJumpAccel - (vJumpAccel - hJumpAccel) * forwards) * xe8_mass) * jumpFactor;\n  }\n\n  if (x258_movementState == EPlayerMovementState::Jump) {\n    SetMoveState(EPlayerMovementState::ApplyJump, mgr);\n  }\n\n  return 0.f;\n}\n\nfloat CPlayer::TurnInput(const CFinalInput& input) const {\n  if (x304_orbitState == EPlayerOrbitState::OrbitObject || x304_orbitState == EPlayerOrbitState::Grapple) {\n    return 0.f;\n  }\n  if (IsMorphBallTransitioning()) {\n    return 0.f;\n  }\n\n  float left = ControlMapper::GetAnalogInput(ControlMapper::ECommands::TurnLeft, input);\n  float right = ControlMapper::GetAnalogInput(ControlMapper::ECommands::TurnRight, input);\n\n  if (g_tweakPlayer->GetFreeLookTurnsPlayer()) {\n    if (!g_tweakPlayer->GetHoldButtonsForFreeLook() || x3dd_lookButtonHeld) {\n      if (left < 0.01f && right < 0.01f) {\n        left = ControlMapper::GetAnalogInput(ControlMapper::ECommands::LookLeft, input);\n        right = ControlMapper::GetAnalogInput(ControlMapper::ECommands::LookRight, input);\n      }\n    }\n  } else {\n    if (!g_tweakPlayer->GetHoldButtonsForFreeLook() || x3dd_lookButtonHeld) {\n      if (left < 0.01f && right < 0.01f) {\n        const float f31 = ControlMapper::GetAnalogInput(ControlMapper::ECommands::LookLeft, input);\n        const float f1 = ControlMapper::GetAnalogInput(ControlMapper::ECommands::LookRight, input);\n        if (f31 > 0.01f || f1 > 0.01f) {\n          return 0.f;\n        }\n      }\n    }\n  }\n\n  right = left - right;\n  if (x32c_orbitModeTimer > 0.f) {\n    right *= (1.f - zeus::clamp(0.f, x32c_orbitModeTimer / g_tweakPlayer->GetOrbitModeTimer(), 1.f) * 0.5f);\n  }\n  return zeus::clamp(-1.f, right, 1.f);\n}\n\nfloat CPlayer::StrafeInput(const CFinalInput& input) const {\n  if (IsMorphBallTransitioning() || x304_orbitState == EPlayerOrbitState::NoOrbit) {\n    return 0.f;\n  }\n  return ControlMapper::GetAnalogInput(ControlMapper::ECommands::StrafeRight, input) -\n         ControlMapper::GetAnalogInput(ControlMapper::ECommands::StrafeLeft, input);\n}\n\nfloat CPlayer::ForwardInput(const CFinalInput& input, float turnInput) const {\n  float forwards = ControlMapper::GetAnalogInput(ControlMapper::ECommands::Forward, input);\n  float backwards = ControlMapper::GetAnalogInput(ControlMapper::ECommands::Backward, input);\n\n  if (x2f8_morphBallState != EPlayerMorphBallState::Unmorphed || InGrappleJumpCooldown()) {\n    backwards = 0.f;\n  }\n\n  if (x2f8_morphBallState == EPlayerMorphBallState::Morphing && x584_ballTransitionAnim == 2) {\n    forwards = 0.f;\n  }\n\n  if (x2f8_morphBallState == EPlayerMorphBallState::Unmorphing && x584_ballTransitionAnim == 5) {\n    forwards = 0.f;\n  }\n\n  if (forwards >= 0.001f) {\n    forwards = zeus::clamp(-1.f, forwards / 0.8f, 1.f);\n    if (std::fabs(std::atan2(std::fabs(turnInput), forwards)) < M_PIF / 3.6f) {\n      const zeus::CVector3f x50(std::fabs(turnInput), forwards, 0.f);\n      if (x50.canBeNormalized()) {\n        forwards = x50.magnitude();\n      }\n    }\n  }\n  if (backwards >= 0.001f) {\n    backwards = zeus::clamp(-1.f, backwards / 0.8f, 1.f);\n    if (std::fabs(std::atan2(std::fabs(turnInput), backwards)) < M_PIF / 3.6f) {\n      const zeus::CVector3f x5c(std::fabs(turnInput), backwards, 0.f);\n      if (x5c.canBeNormalized()) {\n        backwards = x5c.magnitude();\n      }\n    }\n  }\n\n  if (!g_tweakPlayer->GetMoveDuringFreeLook()) {\n    zeus::CVector3f velFlat = x138_velocity;\n    velFlat.z() = 0.f;\n    if (x3dc_inFreeLook || x3dd_lookButtonHeld) {\n      if (x258_movementState == EPlayerMovementState::OnGround || std::fabs(velFlat.magnitude()) < 0.00001f) {\n        return 0.f;\n      }\n    }\n  }\n\n  return zeus::clamp(-1.f, forwards - backwards * g_tweakPlayer->GetBackwardsForceMultiplier(), 1.f);\n}\n\nzeus::CVector3f CPlayer::CalculateLeftStickEdgePosition(float strafeInput, float forwardInput) const {\n  float f31 = -1.f;\n  float f30 = -0.555f;\n  float f29 = 0.555f;\n\n  if (strafeInput >= 0.f) {\n    f31 = -f31;\n    f30 = -f30;\n  }\n\n  if (forwardInput < 0.f) {\n    f29 = -f29;\n  }\n\n  const float f4 = zeus::clamp(-1.f, std::atan(std::fabs(forwardInput) / std::fabs(strafeInput)) / (M_PIF / 4.f), 1.f);\n  return zeus::CVector3f(f31, 0.f, 0.f) + f4 * (zeus::CVector3f(f30, f29, 0.f) - zeus::CVector3f(f31, 0.f, 0.f));\n}\n\nbool CPlayer::SidewaysDashAllowed(float strafeInput, float forwardInput, const CFinalInput& input,\n                                  CStateManager& mgr) const {\n  if (x9c5_28_slidingOnWall || x9c5_29_hitWall || x304_orbitState != EPlayerOrbitState::OrbitObject) {\n    return false;\n  }\n\n  if (g_tweakPlayer->GetDashOnButtonRelease()) {\n    if (x304_orbitState != EPlayerOrbitState::NoOrbit && g_tweakPlayer->GetDashEnabled() &&\n        x288_startingJumpTimeout > 0.f &&\n        !ControlMapper::GetDigitalInput(ControlMapper::ECommands::JumpOrBoost, input) &&\n        x388_dashButtonHoldTime < g_tweakPlayer->GetDashButtonHoldCancelTime() &&\n        std::fabs(strafeInput) >= std::fabs(forwardInput) &&\n        std::fabs(strafeInput) > g_tweakPlayer->GetDashStrafeInputThreshold()) {\n      return true;\n    }\n  } else {\n    if (x304_orbitState != EPlayerOrbitState::NoOrbit && g_tweakPlayer->GetDashEnabled() &&\n        ControlMapper::GetPressInput(ControlMapper::ECommands::JumpOrBoost, input) && x288_startingJumpTimeout > 0.f &&\n        std::fabs(strafeInput) >= std::fabs(forwardInput) && std::fabs(strafeInput) > 0.01f) {\n      const float threshold = std::sqrt(strafeInput * strafeInput + forwardInput * forwardInput) /\n                              CalculateLeftStickEdgePosition(strafeInput, forwardInput).magnitude();\n      if (threshold >= g_tweakPlayer->GetDashStrafeInputThreshold()) {\n        return true;\n      }\n    }\n  }\n\n  return false;\n}\n\nvoid CPlayer::FinishSidewaysDash() {\n  if (x37c_sidewaysDashing) {\n    x38c_doneSidewaysDashing = true;\n  }\n  x37c_sidewaysDashing = false;\n  x380_strafeInputAtDash = 0.f;\n  x384_dashTimer = 0.f;\n}\n\nvoid CPlayer::ComputeDash(const CFinalInput& input, float dt, CStateManager& mgr) {\n  const float strafeInput = StrafeInput(input);\n  const float forwardInput = ForwardInput(input, TurnInput(input));\n  zeus::CVector3f orbitPointFlattened(x314_orbitPoint.x(), x314_orbitPoint.y(), GetTranslation().z());\n  const zeus::CVector3f orbitToPlayer = GetTranslation() - orbitPointFlattened;\n  if (!orbitToPlayer.canBeNormalized()) {\n    return;\n  }\n\n  zeus::CVector3f useOrbitToPlayer = orbitToPlayer;\n  const ESurfaceRestraints restraints = GetSurfaceRestraint();\n  float strafeVel = dt * skStrafeDistances[size_t(restraints)];\n  if (ControlMapper::GetDigitalInput(ControlMapper::ECommands::JumpOrBoost, input)) {\n    x388_dashButtonHoldTime += dt;\n  }\n\n  if (!x37c_sidewaysDashing) {\n    if (SidewaysDashAllowed(strafeInput, forwardInput, input, mgr)) {\n      x37c_sidewaysDashing = true;\n      x380_strafeInputAtDash = strafeInput;\n      x38c_doneSidewaysDashing = true;\n      x384_dashTimer = 0.f;\n      zeus::CVector3f vel = x138_velocity;\n      if (vel.z() > 0.f) {\n        vel.z() *= 0.1f;\n        if (!x9c5_28_slidingOnWall) {\n          SetVelocityWR(vel);\n          x778_dashSfx = CSfxManager::SfxStart(SFXsam_dash, 1.f, 0.f, true, 0x7f, false, kInvalidAreaId);\n          ApplySubmergedPitchBend(x778_dashSfx);\n          mgr.GetRumbleManager().Rumble(mgr, ERumbleFxId::PlayerBump, 0.24375f, ERumblePriority::One);\n        }\n      }\n    }\n    strafeVel *= strafeInput;\n  } else {\n    x384_dashTimer += dt;\n    if (x258_movementState == EPlayerMovementState::OnGround || x384_dashTimer >= x3a0_dashDuration ||\n        x9c5_28_slidingOnWall || x9c5_29_hitWall || x304_orbitState != EPlayerOrbitState::OrbitObject) {\n      FinishSidewaysDash();\n      strafeVel *= strafeInput;\n      CSfxManager::RemoveEmitter(x778_dashSfx);\n    } else {\n      if (x39c_noStrafeDashBlend) {\n        strafeVel = dt * skDashStrafeDistances[size_t(restraints)] * x398_dashSpeedMultiplier;\n      } else {\n        strafeVel = ((skDashStrafeDistances[size_t(restraints)] - skStrafeDistances[size_t(restraints)]) *\n                         (1.f - zeus::clamp(-1.f, x384_dashTimer / x3a4_strafeDashBlendDuration, 1.f)) +\n                     skStrafeDistances[size_t(restraints)]) *\n                    x398_dashSpeedMultiplier * dt;\n      }\n      if (x380_strafeInputAtDash < 0.f) {\n        strafeVel = -strafeVel;\n      }\n    }\n  }\n\n  const float f3 = strafeVel / orbitToPlayer.magnitude();\n  const float f2 = dt * (x37c_sidewaysDashing ? M_PIF : (M_PIF * 2.f / 3.f));\n  useOrbitToPlayer = zeus::CQuaternion::fromAxisAngle(zeus::skUp, zeus::clamp(-f2, f3, f2)).transform(orbitToPlayer);\n  orbitPointFlattened += useOrbitToPlayer;\n  if (!ControlMapper::GetDigitalInput(ControlMapper::ECommands::JumpOrBoost, input)) {\n    x388_dashButtonHoldTime = 0.f;\n  }\n\n  strafeVel = skOrbitForwardDistances[size_t(restraints)] * forwardInput * dt;\n  orbitPointFlattened += -useOrbitToPlayer.normalized() * strafeVel;\n  const zeus::CVector2f velFlat(x138_velocity.x(), x138_velocity.y());\n  zeus::CVector3f newVelocity = (orbitPointFlattened - GetTranslation()) / dt;\n  const zeus::CVector3f velDelta(newVelocity.x() - x138_velocity.x(), newVelocity.y() - x138_velocity.y(), 0.f);\n  strafeVel = velDelta.magnitude();\n\n  if (strafeVel <= FLT_EPSILON) {\n    return;\n  }\n\n  const float accel = dt * GetAcceleration();\n  newVelocity = velDelta / strafeVel * accel * zeus::clamp(-1.f, strafeVel / accel, 1.f) + x138_velocity;\n  if (x9c5_28_slidingOnWall) {\n    return;\n  }\n\n  SetVelocityWR(newVelocity);\n}\n\nvoid CPlayer::ComputeMovement(const CFinalInput& input, CStateManager& mgr, float dt) {\n  ESurfaceRestraints restraints = GetSurfaceRestraint();\n\n  const float jumpInput = JumpInput(input, mgr);\n  float zRotateInput = TurnInput(input);\n  const float forwardInput = ForwardInput(input, zRotateInput);\n  SetVelocityWR(GetDampedClampedVelocityWR());\n  float turnSpeedMul = g_tweakPlayer->GetTurnSpeedMultiplier();\n  if (g_tweakPlayer->GetFreeLookTurnsPlayer()) {\n    if (!g_tweakPlayer->GetHoldButtonsForFreeLook() || x3dd_lookButtonHeld) {\n      turnSpeedMul = g_tweakPlayer->GetFreeLookTurnSpeedMultiplier();\n    }\n  }\n\n  if (x304_orbitState == EPlayerOrbitState::NoOrbit ||\n      (x3dd_lookButtonHeld && x304_orbitState != EPlayerOrbitState::OrbitObject &&\n       x304_orbitState != EPlayerOrbitState::Grapple)) {\n    if (std::fabs(zRotateInput) < 0.00001f) {\n      const float frictionZAngVel =\n          g_tweakPlayer->GetPlayerRotationFriction(int(restraints)) * GetAngularVelocityOR().getVector().z();\n      SetAngularVelocityOR({0.f, 0.f, frictionZAngVel});\n    }\n\n    const float curZAngVel = GetAngularVelocityOR().getVector().z();\n    const float maxZAngVel = g_tweakPlayer->GetPlayerRotationMaxSpeed(int(restraints)) * turnSpeedMul;\n    if (curZAngVel > maxZAngVel) {\n      SetAngularVelocityOR({0.f, 0.f, maxZAngVel});\n    } else if (-curZAngVel > maxZAngVel) {\n      SetAngularVelocityOR({0.f, 0.f, -maxZAngVel});\n    }\n  }\n\n  float f26 = g_tweakPlayer->GetPlayerRotationMaxSpeed(int(restraints)) * zRotateInput * turnSpeedMul;\n  f26 -= GetAngularVelocityOR().getVector().z();\n  const float remainVel = zeus::clamp(\n      0.f, std::fabs(f26) / (turnSpeedMul * g_tweakPlayer->GetPlayerRotationMaxSpeed(int(restraints))), 1.f);\n  if (f26 < 0.f) {\n    zRotateInput = remainVel * -g_tweakPlayer->GetMaxRotationalAcceleration(int(restraints));\n  } else {\n    zRotateInput = remainVel * g_tweakPlayer->GetMaxRotationalAcceleration(int(restraints));\n  }\n\n  float forwardForce;\n  if (std::fabs(forwardInput) >= 0.00001f) {\n    const float maxSpeed = g_tweakPlayer->GetPlayerTranslationMaxSpeed(int(restraints));\n    const float calcSpeed = g_tweakPlayer->GetPlayerTranslationFriction(int(restraints)) * xe8_mass /\n                            (dt * g_tweakPlayer->GetMaxTranslationalAcceleration(int(restraints))) * maxSpeed;\n    const float f28 = (forwardInput > 0.f ? 1.f : -1.f) * calcSpeed + (maxSpeed - calcSpeed) * forwardInput;\n    forwardForce = zeus::clamp(-1.f, (f28 - x34_transform.transposeRotate(x138_velocity).y()) / maxSpeed, 1.f) *\n                   g_tweakPlayer->GetMaxTranslationalAcceleration(int(restraints));\n  } else {\n    forwardForce = 0.f;\n  }\n\n  if (x304_orbitState != EPlayerOrbitState::NoOrbit && x3dd_lookButtonHeld) {\n    forwardForce = 0.f;\n  }\n\n  if (x304_orbitState == EPlayerOrbitState::NoOrbit || x3dd_lookButtonHeld) {\n    ApplyForceOR({0.f, forwardForce, jumpInput}, zeus::CAxisAngle());\n    if (zRotateInput != 0.f) {\n      ApplyForceOR(zeus::skZero3f, zeus::CAxisAngle({0.f, 0.f, 1.f}, zRotateInput));\n    }\n    if (x37c_sidewaysDashing) {\n      x38c_doneSidewaysDashing = true;\n    }\n    x37c_sidewaysDashing = false;\n    x380_strafeInputAtDash = 0.f;\n    x384_dashTimer = 0.f;\n  } else {\n    switch (x304_orbitState) {\n    case EPlayerOrbitState::OrbitObject:\n    case EPlayerOrbitState::OrbitPoint:\n    case EPlayerOrbitState::OrbitCarcass:\n    case EPlayerOrbitState::ForcedOrbitObject:\n      if (!InGrappleJumpCooldown()) {\n        ComputeDash(input, dt, mgr);\n      }\n      break;\n    default:\n      break;\n    }\n    ApplyForceOR({0.f, 0.f, jumpInput}, zeus::CAxisAngle());\n  }\n\n  if (x3dc_inFreeLook || x3dd_lookButtonHeld) {\n    if (!x9c5_28_slidingOnWall && x258_movementState == EPlayerMovementState::OnGround) {\n      const zeus::CVector3f revVelFlat(-x138_velocity.x(), -x138_velocity.y(), 0.f);\n      const float revVelFlatMag = revVelFlat.magnitude();\n      if (revVelFlatMag > FLT_EPSILON) {\n        const float accel = 0.2f * dt * GetAcceleration();\n        const float damp = zeus::clamp(-1.f, revVelFlatMag / accel, 1.f);\n        SetVelocityWR(revVelFlat / revVelFlatMag * accel * damp + x138_velocity);\n      }\n    }\n  }\n\n  x9c5_29_hitWall = false;\n  if (x2d4_accelerationChangeTimer > 0.f) {\n    x2d0_curAcceleration = 0;\n  } else {\n    x2d0_curAcceleration += 1;\n  }\n  x2d4_accelerationChangeTimer -= dt;\n  x2d4_accelerationChangeTimer = std::max(0.f, x2d4_accelerationChangeTimer);\n}\n\nfloat CPlayer::GetWeight() const { return xe8_mass * -GetGravity(); }\n\nzeus::CVector3f CPlayer::GetDampedClampedVelocityWR() const {\n  zeus::CVector3f localVel = x34_transform.transposeRotate(x138_velocity);\n  if ((x258_movementState != EPlayerMovementState::ApplyJump || GetSurfaceRestraint() != ESurfaceRestraints::Air) &&\n      x304_orbitState == EPlayerOrbitState::NoOrbit) {\n    const float friction = g_tweakPlayer->GetPlayerTranslationFriction(int(GetSurfaceRestraint()));\n    if (localVel.y() > 0.f) {\n      localVel.y() = std::max(0.f, localVel.y() - friction);\n    } else {\n      localVel.y() = std::min(0.f, localVel.y() + friction);\n    }\n    if (localVel.x() > 0.f) {\n      localVel.x() = std::max(0.f, localVel.x() - friction);\n    } else {\n      localVel.x() = std::min(0.f, localVel.x() + friction);\n    }\n  }\n\n  const float maxSpeed = g_tweakPlayer->GetPlayerTranslationMaxSpeed(int(GetSurfaceRestraint()));\n  localVel.y() = zeus::clamp(-maxSpeed, float(localVel.y()), maxSpeed);\n  if (x258_movementState == EPlayerMovementState::OnGround) {\n    localVel.z() = 0.f;\n  }\n  return x34_transform.rotate(localVel);\n}\n\nconst CScriptWater* CPlayer::GetVisorRunoffEffect(const CStateManager& mgr) const {\n  if (xc4_fluidId == kInvalidUniqueId) {\n    return nullptr;\n  }\n  return TCastToConstPtr<CScriptWater>(mgr.GetObjectById(xc4_fluidId)).GetPtr();\n}\n\nvoid CPlayer::SetMorphBallState(EPlayerMorphBallState state, CStateManager& mgr) {\n  if (x2f8_morphBallState == EPlayerMorphBallState::Morphed && state != EPlayerMorphBallState::Morphed) {\n    x9c5_26_ = x9c4_31_inWaterMovement;\n  }\n\n  x2f8_morphBallState = state;\n  xf9_standardCollider = state == EPlayerMorphBallState::Morphed;\n\n  switch (x2f8_morphBallState) {\n  case EPlayerMorphBallState::Unmorphed:\n    if (x9c5_26_ && mgr.GetCameraManager()->GetFluidCounter() == 0) {\n      if (const auto* water = GetVisorRunoffEffect(mgr)) {\n        if (const auto& effect = water->GetUnmorphVisorRunoffEffect()) {\n          auto* sheets = new CHUDBillboardEffect(\n              *effect, {}, mgr.AllocateUniqueId(), true, \"WaterSheets\", CHUDBillboardEffect::GetNearClipDistance(mgr),\n              CHUDBillboardEffect::GetScaleForPOV(mgr), zeus::skWhite, zeus::skOne3f, zeus::skZero3f);\n          mgr.AddObject(sheets);\n        }\n        CSfxHandle hnd =\n            CSfxManager::SfxStart(water->GetUnmorphVisorRunoffSfx(), 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n        ApplySubmergedPitchBend(hnd);\n      }\n    }\n    break;\n  case EPlayerMorphBallState::Morphed:\n  case EPlayerMorphBallState::Morphing:\n    x768_morphball->LoadMorphBallModel(mgr);\n    break;\n  default:\n    break;\n  }\n}\n\nbool CPlayer::CanLeaveMorphBallState(CStateManager& mgr, zeus::CVector3f& pos) const {\n  if (x768_morphball->IsProjectile() || !x590_leaveMorphballAllowed ||\n      (IsUnderBetaMetroidAttack(mgr) && x2f8_morphBallState == EPlayerMorphBallState::Morphed)) {\n    return false;\n  }\n\n  if (!x9c4_28_canLeaveMorphBall) {\n    return false;\n  }\n\n  constexpr CMaterialFilter filter = CMaterialFilter::MakeInclude({EMaterialTypes::Solid});\n  const zeus::CAABox aabb(x2d8_fpBounds.min - zeus::CVector3f(1.f) + GetTranslation(),\n                          x2d8_fpBounds.max + zeus::CVector3f(1.f) + GetTranslation());\n  EntityList nearList;\n  mgr.BuildColliderList(nearList, *this, aabb);\n  const zeus::CAABox& baseAABB = GetBaseBoundingBox();\n  pos = zeus::skZero3f;\n\n  for (int i = 0; i < 8; ++i) {\n    const zeus::CAABox aabb2(baseAABB.min + pos + GetTranslation(), baseAABB.max + pos + GetTranslation());\n    const CCollidableAABox cAABB(aabb2, CMaterialList());\n    if (!CGameCollision::DetectCollisionBoolean(mgr, cAABB, zeus::CTransform(), filter, nearList)) {\n      return true;\n    }\n    pos.z() += 0.1f;\n  }\n\n  return false;\n}\n\nvoid CPlayer::SetHudDisable(float staticTimer, float outSpeed, float inSpeed) {\n  x740_staticTimer = staticTimer;\n  x744_staticOutSpeed = outSpeed;\n  x748_staticInSpeed = inSpeed;\n\n  if (x744_staticOutSpeed != 0.f) {\n    return;\n  }\n\n  if (x740_staticTimer == 0.f) {\n    x74c_visorStaticAlpha = 1.f;\n  } else {\n    x74c_visorStaticAlpha = 0.f;\n  }\n}\n\nvoid CPlayer::SetIntoBallReadyAnimation(CStateManager& mgr) {\n  constexpr CAnimPlaybackParms parms(2, -1, 1.f, true);\n  x64_modelData->GetAnimationData()->SetAnimation(parms, false);\n  x64_modelData->GetAnimationData()->EnableLooping(false);\n  x64_modelData->AdvanceAnimation(0.f, mgr, kInvalidAreaId, true);\n  x64_modelData->GetAnimationData()->EnableAnimation(false);\n}\n\nvoid CPlayer::LeaveMorphBallState(CStateManager& mgr) {\n  x730_transitionModels.clear();\n  AddMaterial(EMaterialTypes::GroundCollider, mgr);\n  x150_momentum = zeus::skZero3f;\n  SetMorphBallState(EPlayerMorphBallState::Unmorphed, mgr);\n  SetHudDisable(FLT_EPSILON, 0.f, 2.f);\n  SetHudDisable(FLT_EPSILON, 0.f, 2.f);\n  SetIntoBallReadyAnimation(mgr);\n  CPhysicsActor::Stop();\n  x3e4_freeLookYawAngle = 0.f;\n  x3e8_horizFreeLookAngleVel = 0.f;\n  x3ec_freeLookPitchAngle = 0.f;\n  x3f0_vertFreeLookAngleVel = 0.f;\n  x768_morphball->LeaveMorphBallState(mgr);\n  mgr.GetCameraManager()->SetPlayerCamera(mgr, mgr.GetCameraManager()->GetFirstPersonCamera()->GetUniqueId());\n  mgr.GetCameraManager()->GetBallCamera()->SetState(CBallCamera::EBallCameraState::Default, mgr);\n  SetCameraState(EPlayerCameraState::FirstPerson, mgr);\n  mgr.GetCameraManager()->GetFirstPersonCamera()->DeferBallTransitionProcessing();\n  mgr.GetCameraManager()->GetFirstPersonCamera()->Think(0.f, mgr);\n  ForceGunOrientation(x34_transform, mgr);\n  DrawGun(mgr);\n}\n\nbool CPlayer::CanEnterMorphBallState(CStateManager& mgr, float f1) const {\n  if (x3b8_grappleState != EGrappleState::None ||\n      (IsUnderBetaMetroidAttack(mgr) && x2f8_morphBallState == EPlayerMorphBallState::Unmorphed)) {\n    return false;\n  }\n  return x9c4_27_canEnterMorphBall;\n}\n\nvoid CPlayer::EnterMorphBallState(CStateManager& mgr) {\n  SetMorphBallState(EPlayerMorphBallState::Morphed, mgr);\n  RemoveMaterial(EMaterialTypes::GroundCollider, mgr);\n  x730_transitionModels.clear();\n  SetAngularVelocityOR(zeus::CAxisAngle(\n      zeus::CVector3f(x138_velocity.magnitude() / g_tweakPlayer->GetPlayerBallHalfExtent(), 0.f, 0.f)));\n  x768_morphball->EnterMorphBallState(mgr);\n  x768_morphball->TakeDamage(-1.f);\n  x768_morphball->SetDamageTimer(0.f);\n  mgr.GetPlayerState()->StartTransitionToVisor(CPlayerState::EPlayerVisor::Combat);\n}\n\nvoid CPlayer::ActivateMorphBallCamera(CStateManager& mgr) {\n  SetCameraState(EPlayerCameraState::Ball, mgr);\n  mgr.GetCameraManager()->GetBallCamera()->SetState(CBallCamera::EBallCameraState::Default, mgr);\n}\n\nvoid CPlayer::UpdateCinematicState(CStateManager& mgr) {\n  if (mgr.GetCameraManager()->IsInCinematicCamera()) {\n    if (x2f4_cameraState != EPlayerCameraState::Spawned) {\n      x2fc_spawnedMorphBallState = x2f8_morphBallState;\n      if (x2fc_spawnedMorphBallState == EPlayerMorphBallState::Unmorphing) {\n        x2fc_spawnedMorphBallState = EPlayerMorphBallState::Unmorphed;\n      }\n      if (x2fc_spawnedMorphBallState == EPlayerMorphBallState::Morphing) {\n        x2fc_spawnedMorphBallState = EPlayerMorphBallState::Morphed;\n      }\n      SetCameraState(EPlayerCameraState::Spawned, mgr);\n    }\n  } else {\n    if (x2f4_cameraState == EPlayerCameraState::Spawned) {\n      if (x2fc_spawnedMorphBallState == x2f8_morphBallState) {\n        switch (x2fc_spawnedMorphBallState) {\n        case EPlayerMorphBallState::Morphed:\n          SetCameraState(EPlayerCameraState::Ball, mgr);\n          break;\n        case EPlayerMorphBallState::Unmorphed:\n          SetCameraState(EPlayerCameraState::FirstPerson, mgr);\n          if (mgr.GetPlayerState()->GetCurrentVisor() != CPlayerState::EPlayerVisor::Scan) {\n            ForceGunOrientation(x34_transform, mgr);\n            DrawGun(mgr);\n          }\n          break;\n        default:\n          break;\n        }\n      } else {\n        CPhysicsActor::Stop();\n        SetOrbitRequest(EPlayerOrbitRequest::Respawn, mgr);\n        switch (x2fc_spawnedMorphBallState) {\n        case EPlayerMorphBallState::Unmorphed: {\n          zeus::CVector3f vec;\n          if (CanLeaveMorphBallState(mgr, vec)) {\n            SetTranslation(GetTranslation() + vec);\n            LeaveMorphBallState(mgr);\n            SetCameraState(EPlayerCameraState::FirstPerson, mgr);\n            ForceGunOrientation(x34_transform, mgr);\n            DrawGun(mgr);\n          }\n          break;\n        }\n        case EPlayerMorphBallState::Morphed:\n          EnterMorphBallState(mgr);\n          ActivateMorphBallCamera(mgr);\n          mgr.GetCameraManager()->SetupBallCamera(mgr);\n          mgr.GetCameraManager()->GetBallCamera()->Reset(CreateTransformFromMovementDirection(), mgr);\n          break;\n        default:\n          break;\n        }\n      }\n    }\n  }\n}\n\nvoid CPlayer::SetCameraState(EPlayerCameraState camState, CStateManager& stateMgr) {\n  if (x2f4_cameraState == camState) {\n    return;\n  }\n\n  x2f4_cameraState = camState;\n\n  CCameraManager* camMgr = stateMgr.GetCameraManager();\n  switch (camState) {\n  case EPlayerCameraState::FirstPerson:\n    camMgr->SetCurrentCameraId(camMgr->GetFirstPersonCamera()->GetUniqueId(), stateMgr);\n    x768_morphball->SetBallLightActive(stateMgr, false);\n    break;\n  case EPlayerCameraState::Ball:\n  case EPlayerCameraState::Transitioning:\n    camMgr->SetCurrentCameraId(camMgr->GetBallCamera()->GetUniqueId(), stateMgr);\n    x768_morphball->SetBallLightActive(stateMgr, true);\n    break;\n  case EPlayerCameraState::Two:\n    break;\n  case EPlayerCameraState::Spawned: {\n    bool ballLight = false;\n    if (const TCastToConstPtr<CCinematicCamera> cineCam = camMgr->GetCurrentCamera(stateMgr)) {\n      ballLight = x2f8_morphBallState == EPlayerMorphBallState::Morphed && cineCam->GetFlags() & 0x40;\n    }\n    x768_morphball->SetBallLightActive(stateMgr, ballLight);\n    break;\n  }\n  }\n}\n\nbool CPlayer::IsEnergyLow(const CStateManager& mgr) const {\n  const float lowThreshold =\n      mgr.GetPlayerState()->GetItemCapacity(CPlayerState::EItemType::EnergyTanks) < 4 ? 30.f : 100.f;\n  return GetHealthInfo(mgr)->GetHP() < lowThreshold;\n}\n\nbool CPlayer::ObjectInScanningRange(TUniqueId id, const CStateManager& mgr) const {\n  const CEntity* ent = mgr.GetObjectById(id);\n  if (const TCastToConstPtr<CActor> act = ent) {\n    const zeus::CVector3f delta = act->GetTranslation() - GetTranslation();\n    if (delta.canBeNormalized()) {\n      return delta.magnitude() < g_tweakPlayer->GetScanningRange();\n    }\n  }\n  return false;\n}\n\nvoid CPlayer::SetPlayerHitWallDuringMove() {\n  x9c5_29_hitWall = true;\n  x2d0_curAcceleration = 1;\n}\n\nvoid CPlayer::Touch(CActor& actor, CStateManager& mgr) {\n  if (x2f8_morphBallState != EPlayerMorphBallState::Morphed) {\n    return;\n  }\n\n  x768_morphball->Touch(actor, mgr);\n}\n\nvoid CPlayer::CVisorSteam::SetSteam(float targetAlpha, float alphaInDur, float alphaOutDur, CAssetId txtr,\n                                    bool affectsThermal) {\n  if (!x1c_txtr.IsValid() || targetAlpha > x10_nextTargetAlpha) {\n    x10_nextTargetAlpha = targetAlpha;\n    x14_nextAlphaInDur = alphaInDur;\n    x18_nextAlphaOutDur = alphaOutDur;\n    x1c_txtr = txtr;\n  }\n  x28_affectsThermal = affectsThermal;\n}\n\nCAssetId CPlayer::CVisorSteam::GetTextureId() const { return xc_tex; }\n\nvoid CPlayer::CVisorSteam::Update(float dt) {\n  if (!x1c_txtr.IsValid()) {\n    x0_curTargetAlpha = 0.f;\n  } else {\n    x0_curTargetAlpha = x10_nextTargetAlpha;\n    x4_curAlphaInDur = x14_nextAlphaInDur;\n    x8_curAlphaOutDur = x18_nextAlphaOutDur;\n    xc_tex = x1c_txtr;\n  }\n\n  x1c_txtr.Reset();\n  if (zeus::close_enough(x20_alpha, x0_curTargetAlpha) && zeus::close_enough(x20_alpha, 0.f)) {\n    return;\n  }\n\n  if (x20_alpha > x0_curTargetAlpha) {\n    if (x24_delayTimer <= 0.f) {\n      x20_alpha -= dt / x8_curAlphaOutDur;\n      if (x20_alpha < x0_curTargetAlpha) {\n        x20_alpha = x0_curTargetAlpha;\n      }\n    } else {\n      x24_delayTimer -= dt;\n      if (x24_delayTimer < 0.f) {\n        x24_delayTimer = 0.f;\n      }\n    }\n    return;\n  }\n\n  const CToken tmpTex = g_SimplePool->GetObj({SBIG('TXTR'), xc_tex});\n  if (!tmpTex) {\n    return;\n  }\n\n  x20_alpha += dt / x4_curAlphaInDur;\n  if (x20_alpha > x0_curTargetAlpha) {\n    x20_alpha = x0_curTargetAlpha;\n  }\n\n  x24_delayTimer = 0.1f;\n}\n\nvoid CPlayer::CFailsafeTest::Reset() {\n  x0_stateSamples.clear();\n  x54_posSamples.clear();\n  x148_velSamples.clear();\n  x23c_inputSamples.clear();\n}\n\nvoid CPlayer::CFailsafeTest::AddSample(EInputState state, const zeus::CVector3f& pos, const zeus::CVector3f& vel,\n                                       const zeus::CVector2f& input) {\n  if (x0_stateSamples.size() >= 20) {\n    x0_stateSamples.resize(19);\n  }\n  x0_stateSamples.insert(x0_stateSamples.begin(), state);\n\n  if (x54_posSamples.size() >= 20) {\n    x54_posSamples.resize(19);\n  }\n  x54_posSamples.insert(x54_posSamples.begin(), pos);\n\n  if (x148_velSamples.size() >= 20) {\n    x148_velSamples.resize(19);\n  }\n  x148_velSamples.insert(x148_velSamples.begin(), vel);\n\n  if (x23c_inputSamples.size() >= 20) {\n    x23c_inputSamples.resize(19);\n  }\n  x23c_inputSamples.insert(x23c_inputSamples.begin(), input);\n}\n\nbool CPlayer::CFailsafeTest::Passes() const {\n  if (x0_stateSamples.size() != 20) {\n    return false;\n  }\n\n  float posMag = 0.f;\n\n  zeus::CAABox velAABB(x148_velSamples[0], x148_velSamples[0]);\n  zeus::CAABox posAABB(x54_posSamples[0], x54_posSamples[0]);\n  zeus::CVector3f inputVec(x23c_inputSamples[0].x(), x23c_inputSamples[0].y(), 0.f);\n  zeus::CAABox inputAABB(inputVec, inputVec);\n\n  float maxVelMag = x148_velSamples[0].magnitude();\n  float minVelMag = maxVelMag;\n  u32 notEqualStates = 0;\n\n  for (int i = 1; i < 20; ++i) {\n    const float mag = (x54_posSamples[i - 1] - x54_posSamples[i]).magSquared();\n    if (mag > FLT_EPSILON) {\n      posMag += std::sqrt(mag);\n    }\n\n    posAABB.accumulateBounds(x54_posSamples[i]);\n    velAABB.accumulateBounds(x148_velSamples[i]);\n    const float velMag = x148_velSamples[i].magnitude();\n    minVelMag = std::min(minVelMag, velMag);\n    maxVelMag = std::max(maxVelMag, velMag);\n\n    const zeus::CVector3f inputVec2(x23c_inputSamples[i].x(), x23c_inputSamples[i].y(), 0.f);\n    inputAABB.accumulateBounds(inputVec2);\n\n    if (x0_stateSamples[i] != x0_stateSamples[i - 1]) {\n      notEqualStates += 1;\n    }\n  }\n\n  bool test1 = true;\n  if (posMag >= 1.f / 30.f && posMag >= minVelMag / 30.f) {\n    test1 = false;\n  }\n\n  if (notEqualStates == 0 && x0_stateSamples[0] == EInputState::StartingJump) {\n    const float inputMag = (inputAABB.max - inputAABB.min).magnitude();\n    zeus::CAABox inputFrom0AABB(inputAABB);\n    inputFrom0AABB.accumulateBounds(zeus::skZero3f);\n    bool test2 = true;\n    if ((inputFrom0AABB.max - inputFrom0AABB.min).magnitude() >= 0.01f && inputMag <= 1.5f) {\n      test2 = false;\n    }\n    return test1 && test2;\n  }\n\n  return false;\n}\n\nvoid CPlayer::SetSpawnedMorphBallState(EPlayerMorphBallState state, CStateManager& mgr) {\n  x2fc_spawnedMorphBallState = state;\n  SetCameraState(EPlayerCameraState::Spawned, mgr);\n  if (x2fc_spawnedMorphBallState != x2f8_morphBallState) {\n    CPhysicsActor::Stop();\n    SetOrbitRequest(EPlayerOrbitRequest::Respawn, mgr);\n    switch (x2fc_spawnedMorphBallState) {\n    case EPlayerMorphBallState::Unmorphed: {\n      zeus::CVector3f pos;\n      if (CanLeaveMorphBallState(mgr, pos)) {\n        SetTranslation(GetTranslation() + pos);\n        LeaveMorphBallState(mgr);\n        ForceGunOrientation(x34_transform, mgr);\n        DrawGun(mgr);\n      }\n      break;\n    }\n    case EPlayerMorphBallState::Morphed:\n      EnterMorphBallState(mgr);\n      ActivateMorphBallCamera(mgr);\n      mgr.GetCameraManager()->SetupBallCamera(mgr);\n      mgr.GetCameraManager()->GetBallCamera()->Reset(CreateTransformFromMovementDirection(), mgr);\n      break;\n    default:\n      break;\n    }\n  }\n}\n\nvoid CPlayer::DecrementEnvironmentDamage() {\n  if (xa10_envDmgCounter == 0) {\n    return;\n  }\n\n  xa10_envDmgCounter--;\n}\n\nvoid CPlayer::IncrementEnvironmentDamage() {\n  if (xa10_envDmgCounter == 0) {\n    xa14_envDmgCameraShakeTimer = 0.f;\n  }\n  xa10_envDmgCounter++;\n}\n\nbool CPlayer::CheckSubmerged() const {\n  if (xe6_24_fluidCounter == 0) {\n    return false;\n  }\n\n  return x828_distanceUnderWater >= (x2f8_morphBallState == EPlayerMorphBallState::Morphed\n                                         ? 2.f * g_tweakPlayer->GetPlayerBallHalfExtent()\n                                         : 0.5f * GetEyeHeight());\n}\n\nvoid CPlayer::UpdateSubmerged(CStateManager& mgr) {\n  x82c_inLava = false;\n  x828_distanceUnderWater = 0.f;\n\n  if (xe6_24_fluidCounter == 0) {\n    return;\n  }\n\n  if (const TCastToConstPtr<CScriptWater> water = mgr.ObjectById(xc4_fluidId)) {\n    x828_distanceUnderWater = -(zeus::skUp.dot(x34_transform.origin) - water->GetTriggerBoundsWR().max.z());\n    const EFluidType fluidType = water->GetFluidPlane().GetFluidType();\n    x82c_inLava = (fluidType == EFluidType::Lava || fluidType == EFluidType::ThickLava);\n    CheckSubmerged();\n  }\n}\n\nvoid CPlayer::ApplySubmergedPitchBend(CSfxHandle& sfx) {\n  if (!CheckSubmerged()) {\n    return;\n  }\n\n  CSfxManager::PitchBend(sfx, -1.f);\n}\n\nvoid CPlayer::DetachActorFromPlayer() {\n  x26c_attachedActor = kInvalidUniqueId;\n  x270_attachedActorTime = 0.f;\n  xa28_attachedActorStruggle = 0.f;\n  x490_gun->SetActorAttached(false);\n}\n\nbool CPlayer::AttachActorToPlayer(TUniqueId id, bool disableGun) {\n  if (x26c_attachedActor != kInvalidUniqueId) {\n    return false;\n  }\n\n  if (disableGun) {\n    x490_gun->SetActorAttached(true);\n  }\n\n  x26c_attachedActor = id;\n  x270_attachedActorTime = 0.f;\n  xa28_attachedActorStruggle = 0.f;\n  x768_morphball->StopEffects();\n  return true;\n}\n\nfloat CPlayer::GetAverageSpeed() const {\n  if (const auto avg = x4a4_moveSpeedAvg.GetAverage()) {\n    return *avg;\n  }\n  return x4f8_moveSpeed;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CPlayer.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <optional>\n#include <vector>\n\n#include \"Runtime/Weapon/CPlayerGun.hpp\"\n#include \"Runtime/Weapon/CWeaponMgr.hpp\"\n#include \"Runtime/World/CMorphBall.hpp\"\n#include \"Runtime/World/CPhysicsActor.hpp\"\n#include \"Runtime/World/CPlayerEnergyDrain.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CCollidableSphere;\nclass CDamageInfo;\nclass CFirstPersonCamera;\nclass CMaterialList;\nclass CMorphBall;\nclass CPlayerCameraBob;\nclass CPlayerGun;\nclass CScriptPlayerHint;\nclass CScriptWater;\nclass IVisitor;\n\nstruct CFinalInput;\n\nclass CPlayer : public CPhysicsActor {\n  friend class CFirstPersonCamera;\n  friend class CGroundMovement;\n  friend class CMorphBall;\n  friend class CPlayerCameraBob;\n  friend class CStateManager;\n\npublic:\n  enum class EPlayerScanState { NotScanning, Scanning, ScanComplete };\n\n  enum class EPlayerOrbitType { Close, Far, Default };\n\n  enum class EPlayerOrbitState {\n    NoOrbit,\n    OrbitObject,\n    OrbitPoint,\n    OrbitCarcass,\n    ForcedOrbitObject, // For CMetroidBeta attack\n    Grapple\n  };\n\n  enum class EPlayerOrbitRequest {\n    StopOrbit,\n    Respawn,\n    EnterMorphBall,\n    Default,\n    Four,\n    Five,\n    InvalidateTarget,\n    BadVerticalAngle,\n    ActivateOrbitSource,\n    ProjectileCollide,\n    Freeze,\n    DamageOnGrapple,\n    LostGrappleLineOfSight\n  };\n\n  enum class EOrbitValidationResult {\n    OK,\n    InvalidTarget,\n    PlayerNotReadyToTarget,\n    NonTargetableTarget,\n    ExtremeHorizonAngle,\n    BrokenLookAngle,\n    TargetingThroughDoor\n  };\n\n  enum class EPlayerZoneInfo { Targeting, Scan };\n\n  enum class EPlayerZoneType { Always = -1, Box = 0, Ellipse };\n\n  enum class EPlayerMovementState { OnGround, Jump, ApplyJump, Falling, FallingMorphed };\n\n  enum class EPlayerMorphBallState { Unmorphed, Morphed, Morphing, Unmorphing };\n\n  enum class EPlayerCameraState { FirstPerson, Ball, Two, Transitioning, Spawned };\n\n  enum class ESurfaceRestraints { Normal, Air, Ice, Organic, Water, Lava, Phazon, Shrubbery };\n\n  enum class EFootstepSfx { None, Left, Right };\n\n  enum class EGrappleState { None, Firing, Pull, Swinging, JumpOff };\n\n  enum class EGunHolsterState { Holstered, Drawing, Drawn, Holstering };\n\nprivate:\n  struct CVisorSteam {\n    float x0_curTargetAlpha;\n    float x4_curAlphaInDur;\n    float x8_curAlphaOutDur;\n    CAssetId xc_tex;\n    float x10_nextTargetAlpha = 0.f;\n    float x14_nextAlphaInDur = 0.f;\n    float x18_nextAlphaOutDur = 0.f;\n    CAssetId x1c_txtr;\n    float x20_alpha = 0.f;\n    float x24_delayTimer = 0.f;\n    bool x28_affectsThermal = false;\n\n  public:\n    CVisorSteam(float targetAlpha, float alphaInDur, float alphaOutDur, CAssetId tex)\n    : x0_curTargetAlpha(targetAlpha), x4_curAlphaInDur(alphaInDur), x8_curAlphaOutDur(alphaOutDur), xc_tex(tex) {}\n    CAssetId GetTextureId() const;\n    void SetSteam(float targetAlpha, float alphaInDur, float alphaOutDur, CAssetId txtr, bool affectsThermal);\n    void Update(float dt);\n    float GetAlpha() const { return x20_alpha; }\n    bool AffectsThermal() const { return x28_affectsThermal; }\n  };\n\n  class CFailsafeTest {\n  public:\n    enum class EInputState { Jump, StartingJump, Moving };\n\n  private:\n    rstl::reserved_vector<EInputState, 20> x0_stateSamples;\n    rstl::reserved_vector<zeus::CVector3f, 20> x54_posSamples;\n    rstl::reserved_vector<zeus::CVector3f, 20> x148_velSamples;\n    rstl::reserved_vector<zeus::CVector2f, 20> x23c_inputSamples;\n\n  public:\n    void Reset();\n    void AddSample(EInputState state, const zeus::CVector3f& pos, const zeus::CVector3f& vel,\n                   const zeus::CVector2f& input);\n    bool Passes() const;\n  };\n\n  EPlayerMovementState x258_movementState = EPlayerMovementState::OnGround;\n  std::vector<CToken> x25c_ballTransitionsRes;\n  TUniqueId x26c_attachedActor = kInvalidUniqueId;\n  float x270_attachedActorTime = 0.f;\n  CPlayerEnergyDrain x274_energyDrain{4};\n  float x288_startingJumpTimeout = 0.f;\n  float x28c_sjTimer = 0.f;\n  float x290_minJumpTimeout = 0.f;\n  float x294_jumpCameraTimer = 0.f;\n  u32 x298_jumpPresses = 0;\n  float x29c_fallCameraTimer = 0.f;\n  float x2a0_ = 0.f;\n  bool x2a4_cancelCameraPitch = false;\n  float x2a8_timeSinceJump = 1000.f;\n  ESurfaceRestraints x2ac_surfaceRestraint = ESurfaceRestraints::Normal;\n  u32 x2b0_outOfWaterTicks = 2;\n  rstl::reserved_vector<float, 6> x2b4_accelerationTable;\n  u32 x2d0_curAcceleration = 1;\n  float x2d4_accelerationChangeTimer = 0.f;\n  zeus::CAABox x2d8_fpBounds;\n  float x2f0_ballTransHeight = 1.f;\n  EPlayerCameraState x2f4_cameraState = EPlayerCameraState::FirstPerson;\n  EPlayerMorphBallState x2f8_morphBallState = EPlayerMorphBallState::Unmorphed;\n  EPlayerMorphBallState x2fc_spawnedMorphBallState = EPlayerMorphBallState::Unmorphed;\n  float x300_fallingTime = 0.f;\n  EPlayerOrbitState x304_orbitState = EPlayerOrbitState::NoOrbit;\n  EPlayerOrbitType x308_orbitType = EPlayerOrbitType::Close;\n  EPlayerOrbitRequest x30c_orbitRequest = EPlayerOrbitRequest::Default;\n  TUniqueId x310_orbitTargetId = kInvalidUniqueId;\n  zeus::CVector3f x314_orbitPoint;\n  zeus::CVector3f x320_orbitVector;\n  float x32c_orbitModeTimer = 0.f;\n  EPlayerZoneInfo x330_orbitZoneMode = EPlayerZoneInfo::Targeting;\n  EPlayerZoneType x334_orbitType = EPlayerZoneType::Ellipse;\n  u32 x338_ = 1;\n  TUniqueId x33c_orbitNextTargetId = kInvalidUniqueId;\n  bool m_deferredOrbitObject = false;\n  float x340_ = 0.f;\n  std::vector<TUniqueId> x344_nearbyOrbitObjects;\n  std::vector<TUniqueId> x354_onScreenOrbitObjects;\n  std::vector<TUniqueId> x364_offScreenOrbitObjects;\n  bool x374_orbitLockEstablished = false;\n  float x378_orbitPreventionTimer = 0.f;\n  bool x37c_sidewaysDashing = false;\n  float x380_strafeInputAtDash = 0.f;\n  float x384_dashTimer = 0.f;\n  float x388_dashButtonHoldTime = 0.f;\n  bool x38c_doneSidewaysDashing = false;\n  u32 x390_orbitSource = 2;\n  bool x394_orbitingEnemy = false;\n  float x398_dashSpeedMultiplier = 1.5f;\n  bool x39c_noStrafeDashBlend = false;\n  float x3a0_dashDuration = 0.5f;\n  float x3a4_strafeDashBlendDuration = 0.449f;\n  EPlayerScanState x3a8_scanState = EPlayerScanState::NotScanning;\n  float x3ac_scanningTime = 0.f;\n  float x3b0_curScanTime = 0.f;\n  TUniqueId x3b4_scanningObject = kInvalidUniqueId;\n  EGrappleState x3b8_grappleState = EGrappleState::None;\n  float x3bc_grappleSwingTimer = 0.f;\n  zeus::CVector3f x3c0_grappleSwingAxis = zeus::skRight;\n  float x3cc_ = 0.f;\n  float x3d0_ = 0.f;\n  float x3d4_ = 0.f;\n  float x3d8_grappleJumpTimeout = 0.f;\n  bool x3dc_inFreeLook = false;\n  bool x3dd_lookButtonHeld = false;\n  bool x3de_lookAnalogHeld = false;\n  float x3e0_curFreeLookCenteredTime = 0.f;\n  float x3e4_freeLookYawAngle = 0.f;\n  float x3e8_horizFreeLookAngleVel = 0.f;\n  float x3ec_freeLookPitchAngle = 0.f;\n  float x3f0_vertFreeLookAngleVel = 0.f;\n  TUniqueId x3f4_aimTarget = kInvalidUniqueId;\n  zeus::CVector3f x3f8_targetAimPosition = zeus::skZero3f;\n  TReservedAverage<zeus::CVector3f, 10> x404_aimTargetAverage;\n  zeus::CVector3f x480_assistedTargetAim = zeus::skZero3f;\n  float x48c_aimTargetTimer = 0.f;\n  std::unique_ptr<CPlayerGun> x490_gun;\n  float x494_gunAlpha = 1.f;\n  EGunHolsterState x498_gunHolsterState = EGunHolsterState::Drawn;\n  float x49c_gunHolsterRemTime;\n  std::unique_ptr<CFailsafeTest> x4a0_failsafeTest;\n  TReservedAverage<float, 20> x4a4_moveSpeedAvg;\n  float x4f8_moveSpeed = 0.f;\n  float x4fc_flatMoveSpeed = 0.f;\n  zeus::CVector3f x500_lookDir = x34_transform.basis[1];\n  zeus::CVector3f x50c_moveDir = x34_transform.basis[1];\n  zeus::CVector3f x518_leaveMorphDir = x34_transform.basis[1];\n  zeus::CVector3f x524_lastPosForDirCalc = x34_transform.origin;\n  zeus::CVector3f x530_gunDir = x34_transform.basis[1];\n  float x53c_timeMoving = 0.f;\n  zeus::CVector3f x540_controlDir = x34_transform.basis[1];\n  zeus::CVector3f x54c_controlDirFlat = x34_transform.basis[1];\n  bool x558_wasDamaged = false;\n  float x55c_damageAmt = 0.f;\n  float x560_prevDamageAmt = 0.f;\n  zeus::CVector3f x564_damageLocation;\n  float x570_immuneTimer = 0.f;\n  float x574_morphTime = 0.f;\n  float x578_morphDuration = 0.f;\n  u32 x57c_ = 0;\n  u32 x580_ = 0;\n  int x584_ballTransitionAnim = -1;\n  float x588_alpha = 1.f;\n  float x58c_transitionVel = 0.f;\n  bool x590_leaveMorphballAllowed = true;\n  TReservedAverage<zeus::CTransform, 4> x594_transisionBeamXfs;\n  TReservedAverage<zeus::CTransform, 4> x658_transitionModelXfs;\n  TReservedAverage<float, 4> x71c_transitionModelAlphas;\n  std::vector<std::unique_ptr<CModelData>> x730_transitionModels;\n  float x740_staticTimer = 0.f;\n  float x744_staticOutSpeed = 0.f;\n  float x748_staticInSpeed = 0.f;\n  float x74c_visorStaticAlpha = 1.f;\n  float x750_frozenTimeout = 0.f;\n  s32 x754_iceBreakJumps = 0;\n  float x758_frozenTimeoutBias = 0.f;\n  s32 x75c_additionalIceBreakJumps = 0;\n  bool x760_controlsFrozen = false;\n  float x764_controlsFrozenTimeout = 0.f;\n  std::unique_ptr<CMorphBall> x768_morphball;\n  std::unique_ptr<CPlayerCameraBob> x76c_cameraBob;\n  CSfxHandle x770_damageLoopSfx;\n  float x774_samusVoiceTimeout = 0.f;\n  CSfxHandle x778_dashSfx;\n  CSfxHandle x77c_samusVoiceSfx;\n  int x780_samusVoicePriority = 0;\n  float x784_damageSfxTimer = 0.f;\n  u16 x788_damageLoopSfxId = 0;\n  float x78c_footstepSfxTimer = 0.f;\n  EFootstepSfx x790_footstepSfxSel = EFootstepSfx::None;\n  zeus::CVector3f x794_lastVelocity;\n  CVisorSteam x7a0_visorSteam = CVisorSteam(0.f, 0.f, 0.f, CAssetId() /*kInvalidAssetId*/);\n  CPlayerState::EPlayerSuit x7cc_transitionSuit = CPlayerState::EPlayerSuit::Invalid;\n  CAnimRes x7d0_animRes;\n  zeus::CVector3f x7d8_beamScale;\n  bool x7e4_ = true;\n  u32 x7e8_ = 0;\n  CPlayerState::EBeamId x7ec_beam = CPlayerState::EBeamId::Power;\n  std::unique_ptr<CModelData> x7f0_ballTransitionBeamModel;\n  zeus::CTransform x7f4_gunWorldXf;\n  float x824_transitionFilterTimer = 0.f;\n  float x828_distanceUnderWater = 0.f;\n  bool x82c_inLava = false;\n  TUniqueId x82e_ridingPlatform = kInvalidUniqueId;\n  TUniqueId x830_playerHint = kInvalidUniqueId;\n  u32 x834_playerHintPriority = 1000;\n  rstl::reserved_vector<std::pair<u32, TUniqueId>, 32> x838_playerHints;\n  rstl::reserved_vector<TUniqueId, 32> x93c_playerHintsToRemove;\n  rstl::reserved_vector<TUniqueId, 32> x980_playerHintsToAdd;\n  bool x9c4_24_visorChangeRequested : 1 = false;\n  bool x9c4_25_showCrosshairs : 1 = false;\n  bool x9c4_26_ : 1 = true;\n  bool x9c4_27_canEnterMorphBall : 1 = true;\n  bool x9c4_28_canLeaveMorphBall : 1 = true;\n  bool x9c4_29_spiderBallControlXY : 1 = false;\n  bool x9c4_30_controlDirOverride : 1 = false;\n  bool x9c4_31_inWaterMovement : 1 = false;\n  bool x9c5_24_ : 1 = false;\n  bool x9c5_25_splashUpdated : 1 = false;\n  bool x9c5_26_ : 1 = false;\n  bool x9c5_27_camSubmerged : 1 = false;\n  bool x9c5_28_slidingOnWall : 1 = false;\n  bool x9c5_29_hitWall : 1 = false;\n  bool x9c5_30_selectFluidBallSound : 1 = false;\n  bool x9c5_31_stepCameraZBiasDirty : 1 = true;\n  bool x9c6_24_extendTargetDistance : 1 = false;\n  bool x9c6_25_interpolatingControlDir : 1 = false;\n  bool x9c6_26_outOfBallLookAtHint : 1 = false;\n  bool x9c6_27_aimingAtProjectile : 1 = false;\n  bool x9c6_28_aligningGrappleSwingTurn : 1 = false;\n  bool x9c6_29_disableInput : 1 = false;\n  bool x9c6_30_newScanScanning : 1 = false;\n  bool x9c6_31_overrideRadarRadius : 1 = false;\n  bool x9c7_24_noDamageLoopSfx : 1 = false;\n  bool x9c7_25_outOfBallLookAtHintActor : 1 = false;\n  float x9c8_eyeZBias = 0.f;\n  float x9cc_stepCameraZBias = 0.f;\n  u32 x9d0_bombJumpCount = 0;\n  s32 x9d4_bombJumpCheckDelayFrames = 0;\n  zeus::CVector3f x9d8_controlDirOverrideDir = zeus::skForward;\n  rstl::reserved_vector<TUniqueId, 5> x9e4_orbitDisableList;\n\n  float x9f4_deathTime = 0.f;\n  float x9f8_controlDirInterpTime = 0.f;\n  float x9fc_controlDirInterpDur = 0.f;\n  TUniqueId xa00_deathPowerBomb = kInvalidUniqueId;\n  float xa04_preThinkDt = 0.f;\n  CAssetId xa08_steamTextureId;\n  CAssetId xa0c_iceTextureId;\n  u32 xa10_envDmgCounter = 0;\n  float xa14_envDmgCameraShakeTimer = 0.f;\n  float xa18_phazonDamageLag = 0.f;\n  float xa1c_threatOverride = 0.f;\n  float xa20_radarXYRadiusOverride = 1.f;\n  float xa24_radarZRadiusOverride = 1.f;\n  float xa28_attachedActorStruggle = 0.f;\n  int xa2c_damageLoopSfxDelayTicks = 2;\n  float xa30_samusExhaustedVoiceTimer = 4.f;\n\n  void StartLandingControlFreeze();\n  void EndLandingControlFreeze();\n  void ProcessFrozenInput(float dt, CStateManager& mgr);\n  bool CheckSubmerged() const;\n  void UpdateSubmerged(CStateManager& mgr);\n  void InitializeBallTransition();\n  float UpdateCameraBob(float dt, CStateManager& mgr);\n  float GetAcceleration() const;\n  float CalculateOrbitMinDistance(EPlayerOrbitType type) const;\n\npublic:\n  DEFINE_ENTITY\n  CPlayer(TUniqueId uid, const zeus::CTransform& xf, const zeus::CAABox& aabb, CAssetId resId,\n          const zeus::CVector3f& playerScale, float mass, float stepUp, float stepDown, float ballRadius,\n          const CMaterialList& ml);\n\n  bool IsTransparent() const;\n  bool GetControlsFrozen() const { return x760_controlsFrozen; }\n  float GetTransitionAlpha(const zeus::CVector3f& camPos, float zNear) const;\n  s32 ChooseTransitionToAnimation(float dt, CStateManager& mgr) const;\n  void TransitionToMorphBallState(float dt, CStateManager& mgr);\n  void TransitionFromMorphBallState(CStateManager& mgr);\n  s32 GetNextBallTransitionAnim(float dt, bool& loopOut, CStateManager& mgr);\n  void UpdateMorphBallTransition(float dt, CStateManager& mgr);\n  void UpdateGunAlpha();\n  void UpdatePlayerSounds(float dt);\n  void Update(float dt, CStateManager& mgr);\n  void PostUpdate(float dt, CStateManager& mgr);\n  bool StartSamusVoiceSfx(u16 sfx, float vol, int prio);\n  bool IsPlayerDeadEnough() const;\n  void AsyncLoadSuit(CStateManager& mgr);\n  void LoadAnimationTokens();\n  bool HasTransitionBeamModel() const;\n  bool CanRenderUnsorted(const CStateManager& mgr) const override;\n  const CDamageVulnerability* GetDamageVulnerability(const zeus::CVector3f& v1, const zeus::CVector3f& v2,\n                                                     const CDamageInfo& info) const override;\n  const CDamageVulnerability* GetDamageVulnerability() const override;\n  zeus::CVector3f GetHomingPosition(const CStateManager& mgr, float dt) const override;\n  zeus::CVector3f GetAimPosition(const CStateManager& mgr, float dt) const override;\n  void FluidFXThink(EFluidState state, CScriptWater& water, CStateManager& mgr) override;\n  zeus::CVector3f GetDamageLocationWR() const { return x564_damageLocation; }\n  float GetPrevDamageAmount() const { return x560_prevDamageAmt; }\n  float GetDamageAmount() const { return x55c_damageAmt; }\n  bool WasDamaged() const { return x558_wasDamaged; }\n  void TakeDamage(bool significant, const zeus::CVector3f& location, float dam, EWeaponType type, CStateManager& mgr);\n  void Accept(IVisitor& visitor) override;\n  CHealthInfo* HealthInfo(CStateManager& mgr) override;\n  bool IsUnderBetaMetroidAttack(const CStateManager& mgr) const;\n  std::optional<zeus::CAABox> GetTouchBounds() const override;\n  void Touch(CActor& actor, CStateManager& mgr) override;\n  void DoPreThink(float dt, CStateManager& mgr);\n  void DoThink(float dt, CStateManager& mgr);\n  void UpdateScanningState(const CFinalInput& input, CStateManager& mgr, float dt);\n  bool ValidateScanning(const CFinalInput& input, const CStateManager& mgr) const;\n  void FinishNewScan(CStateManager& mgr);\n  void SetScanningState(EPlayerScanState state, CStateManager& mgr);\n  void SetSpawnedMorphBallState(EPlayerMorphBallState state, CStateManager& mgr);\n  bool GetExplorationMode() const;\n  bool GetCombatMode() const;\n  void RenderGun(const CStateManager& mgr, const zeus::CVector3f& pos) const;\n  void Render(CStateManager& mgr) override;\n  void RenderReflectedPlayer(CStateManager& mgr);\n  void PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) override;\n  void CalculateRenderBounds() override;\n  void AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) override;\n  void ComputeFreeLook(const CFinalInput& input);\n  void UpdateFreeLookState(const CFinalInput& input, float dt, CStateManager& mgr);\n  void UpdateFreeLook(float dt);\n  float GetMaximumPlayerPositiveVerticalVelocity(CStateManager& mgr) const;\n  void ProcessInput(const CFinalInput& input, CStateManager& mgr);\n  bool ShouldSampleFailsafe(CStateManager& mgr) const;\n  void CalculateLeaveMorphBallDirection(const CFinalInput& input);\n  void CalculatePlayerControlDirection(CStateManager& mgr);\n  void CalculatePlayerMovementDirection(float dt);\n  void UnFreeze(CStateManager& stateMgr);\n  void Freeze(CStateManager& stateMgr, CAssetId steamTxtr, u16 sfx, CAssetId iceTxtr);\n  bool GetFrozenState() const;\n  void UpdateFrozenState(const CFinalInput& input, CStateManager& mgr);\n  void UpdateStepCameraZBias(float dt);\n  void UpdateWaterSurfaceCameraBias(CStateManager& mgr);\n  void UpdateEnvironmentDamageCameraShake(float dt, CStateManager& mgr);\n  void UpdatePhazonDamage(float dt, CStateManager& mgr);\n  void ResetPlayerHintState();\n  bool SetAreaPlayerHint(const CScriptPlayerHint& hint, CStateManager& mgr);\n  void AddToPlayerHintRemoveList(TUniqueId id, CStateManager& mgr);\n  void AddToPlayerHintAddList(TUniqueId id, CStateManager& mgr);\n  void DeactivatePlayerHint(TUniqueId id, CStateManager& mgr);\n  void UpdatePlayerHints(CStateManager& mgr);\n  void UpdateBombJumpStuff();\n  void UpdateTransitionFilter(float dt, CStateManager& mgr);\n  void ResetControlDirectionInterpolation();\n  void SetControlDirectionInterpolation(float time);\n  void UpdatePlayerControlDirection(float dt, CStateManager& mgr);\n  void Think(float dt, CStateManager& mgr) override;\n  void PreThink(float dt, CStateManager& mgr) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) override;\n  void SetVisorSteam(float targetAlpha, float alphaInDur, float alphaOutDir, CAssetId txtr, bool affectsThermal);\n  void UpdateFootstepSounds(const CFinalInput& input, CStateManager& mgr, float dt);\n  u16 GetMaterialSoundUnderPlayer(const CStateManager& mgr, const u16* idList, size_t length, u16 defId) const;\n  static u16 SfxIdFromMaterial(const CMaterialList& mat, const u16* idList, size_t tableLen, u16 defId);\n  void UpdateCrosshairsState(const CFinalInput& input);\n  void UpdateVisorTransition(float, CStateManager& mgr);\n  void UpdateVisorState(const CFinalInput& input, float dt, CStateManager& mgr);\n  void UpdateGunState(const CFinalInput& input, CStateManager& mgr);\n  void ResetGun(CStateManager& mgr);\n  void UpdateArmAndGunTransforms(float dt, CStateManager& mgr);\n  void ForceGunOrientation(const zeus::CTransform&, CStateManager& mgr);\n  void UpdateCameraState(CStateManager& mgr);\n  void UpdateDebugCamera(CStateManager& mgr);\n  void UpdateCameraTimers(float dt, const CFinalInput& input);\n  void UpdateMorphBallState(float dt, const CFinalInput&, CStateManager& mgr);\n  CFirstPersonCamera& GetFirstPersonCamera(CStateManager& mgr);\n  void UpdateGunTransform(const zeus::CVector3f& gunPos, CStateManager& mgr);\n  void UpdateAssistedAiming(const zeus::CTransform& xf, const CStateManager& mgr);\n  void UpdateAimTargetPrediction(const zeus::CTransform& xf, const CStateManager& mgr);\n  void ResetAimTargetPrediction(TUniqueId target);\n  void DrawGun(CStateManager& mgr);\n  void HolsterGun(CStateManager& mgr);\n  EPlayerCameraState GetCameraState() const { return x2f4_cameraState; }\n  EPlayerMorphBallState GetMorphballTransitionState() const { return x2f8_morphBallState; }\n  EGunHolsterState GetGunHolsterState() const { return x498_gunHolsterState; }\n  EPlayerMovementState GetPlayerMovementState() const { return x258_movementState; }\n  bool IsMorphBallTransitioning() const;\n  void UpdateGrappleArmTransform(const zeus::CVector3f& offset, CStateManager& mgr, float dt);\n  float GetGravity() const;\n  void ApplyGrappleForces(const CFinalInput& input, CStateManager& mgr, float dt);\n  bool ValidateFPPosition(const zeus::CVector3f& pos, const CStateManager& mgr) const;\n  void UpdateGrappleState(const CFinalInput& input, CStateManager& mgr);\n  void ApplyGrappleJump(CStateManager& mgr);\n  void BeginGrapple(zeus::CVector3f& vec, CStateManager& mgr);\n  void BreakGrapple(EPlayerOrbitRequest req, CStateManager& mgr);\n  void SetOrbitRequest(EPlayerOrbitRequest req, CStateManager& mgr);\n  void TryToBreakOrbit(TUniqueId id, EPlayerOrbitRequest req, CStateManager& mgr);\n  bool InGrappleJumpCooldown() const;\n  void PreventFallingCameraPitch();\n  void OrbitCarcass(CStateManager& mgr);\n  void OrbitPoint(EPlayerOrbitType type, CStateManager& mgr);\n  zeus::CVector3f GetHUDOrbitTargetPosition() const;\n  void SetOrbitState(EPlayerOrbitState state, CStateManager& mgr);\n  void SetOrbitTargetId(TUniqueId id, CStateManager& mgr);\n  void UpdateOrbitPosition(float dist, CStateManager& mgr);\n  void UpdateOrbitZPosition();\n  void UpdateOrbitFixedPosition();\n  void SetOrbitPosition(float dist, CStateManager& mgr);\n  void UpdateAimTarget(CStateManager& mgr);\n  void UpdateAimTargetTimer(float dt);\n  bool ValidateAimTargetId(TUniqueId uid, CStateManager& mgr);\n  bool ValidateObjectForMode(TUniqueId uid, CStateManager& mgr) const;\n  TUniqueId FindAimTargetId(CStateManager& mgr) const;\n  TUniqueId GetAimTarget() const { return x3f4_aimTarget; }\n  TUniqueId CheckEnemiesAgainstOrbitZone(const EntityList& list, EPlayerZoneInfo info, EPlayerZoneType zone,\n                                         CStateManager& mgr) const;\n  TUniqueId FindOrbitTargetId(CStateManager& mgr) const;\n  void UpdateOrbitableObjects(CStateManager& mgr);\n  TUniqueId FindBestOrbitableObject(const std::vector<TUniqueId>& ids, EPlayerZoneInfo info, CStateManager& mgr) const;\n  void FindOrbitableObjects(const EntityList& nearObjects, std::vector<TUniqueId>& listOut, EPlayerZoneInfo zone,\n                            EPlayerZoneType type, CStateManager& mgr, bool onScreenTest) const;\n  bool WithinOrbitScreenBox(const zeus::CVector3f& screenCoords, EPlayerZoneInfo zone, EPlayerZoneType type) const;\n  bool WithinOrbitScreenEllipse(const zeus::CVector3f& screenCoords, EPlayerZoneInfo zone) const;\n  bool CheckOrbitDisableSourceList(CStateManager& mgr);\n  bool CheckOrbitDisableSourceList() const { return !x9e4_orbitDisableList.empty(); }\n  void RemoveOrbitDisableSource(TUniqueId uid);\n  void AddOrbitDisableSource(CStateManager& mgr, TUniqueId addId);\n  void UpdateOrbitPreventionTimer(float dt);\n  void UpdateOrbitModeTimer(float dt);\n  void UpdateOrbitZone(CStateManager& mgr);\n  void UpdateOrbitInput(const CFinalInput& input, CStateManager& mgr);\n  void ActivateOrbitSource(CStateManager& mgr);\n  void UpdateOrbitSelection(const CFinalInput& input, CStateManager& mgr);\n  void UpdateOrbitOrientation(CStateManager& mgr);\n  void UpdateOrbitTarget(CStateManager& mgr);\n  float GetOrbitMaxLockDistance(CStateManager& mgr) const;\n  float GetOrbitMaxTargetDistance(CStateManager& mgr) const;\n  EOrbitValidationResult ValidateOrbitTargetId(TUniqueId uid, CStateManager& mgr) const;\n  EOrbitValidationResult ValidateCurrentOrbitTargetId(CStateManager& mgr);\n  bool ValidateOrbitTargetIdAndPointer(TUniqueId uid, CStateManager& mgr) const;\n  zeus::CVector3f GetBallPosition() const;\n  zeus::CVector3f GetEyePosition() const;\n  float GetEyeHeight() const;\n  float GetUnbiasedEyeHeight() const;\n  float GetStepUpHeight() const override;\n  float GetStepDownHeight() const override;\n  void Teleport(const zeus::CTransform& xf, CStateManager& mgr, bool resetBallCam);\n  void BombJump(const zeus::CVector3f& pos, CStateManager& mgr);\n  zeus::CTransform CreateTransformFromMovementDirection() const;\n  const CCollisionPrimitive* GetCollisionPrimitive() const override;\n  const CCollidableSphere* GetCollidableSphere() const;\n  zeus::CTransform GetPrimitiveTransform() const override;\n  void CollidedWith(TUniqueId id, const CCollisionInfoList& list, CStateManager& mgr) override;\n  float GetBallMaxVelocity() const;\n  float GetActualBallMaxVelocity(float dt) const;\n  float GetActualFirstPersonMaxVelocity(float dt) const;\n  void SetMoveState(EPlayerMovementState newState, CStateManager& mgr);\n  float JumpInput(const CFinalInput& input, CStateManager& mgr);\n  float TurnInput(const CFinalInput& input) const;\n  float StrafeInput(const CFinalInput& input) const;\n  float ForwardInput(const CFinalInput& input, float turnInput) const;\n  zeus::CVector3f CalculateLeftStickEdgePosition(float strafeInput, float forwardInput) const;\n  bool SidewaysDashAllowed(float strafeInput, float forwardInput, const CFinalInput& input, CStateManager& mgr) const;\n  void FinishSidewaysDash();\n  void ComputeDash(const CFinalInput& input, float dt, CStateManager& mgr);\n  void ComputeMovement(const CFinalInput& input, CStateManager& mgr, float dt);\n  float GetWeight() const override;\n  zeus::CVector3f GetDampedClampedVelocityWR() const;\n  const CVisorSteam& GetVisorSteam() const { return x7a0_visorSteam; }\n  float GetVisorStaticAlpha() const { return x74c_visorStaticAlpha; }\n  float GetGunAlpha() const { return x494_gunAlpha; }\n  const CScriptWater* GetVisorRunoffEffect(const CStateManager& mgr) const;\n  void SetMorphBallState(EPlayerMorphBallState state, CStateManager& mgr);\n  bool CanLeaveMorphBallState(CStateManager& mgr, zeus::CVector3f& pos) const;\n  void SetHudDisable(float staticTimer, float outSpeed, float inSpeed);\n  void SetIntoBallReadyAnimation(CStateManager& mgr);\n  void LeaveMorphBallState(CStateManager& mgr);\n  bool CanEnterMorphBallState(CStateManager& mgr, float f1) const;\n  void EnterMorphBallState(CStateManager& mgr);\n  void ActivateMorphBallCamera(CStateManager& mgr);\n  void UpdateCinematicState(CStateManager& mgr);\n  void SetCameraState(EPlayerCameraState camState, CStateManager& stateMgr);\n  bool IsEnergyLow(const CStateManager& mgr) const;\n  EPlayerOrbitState GetOrbitState() const { return x304_orbitState; }\n  EPlayerScanState GetScanningState() const { return x3a8_scanState; }\n  float GetScanningTime() const { return x3ac_scanningTime; }\n  TUniqueId GetOrbitTargetId() const { return x310_orbitTargetId; }\n  TUniqueId GetOrbitNextTargetId() const { return x33c_orbitNextTargetId; }\n  TUniqueId GetScanningObjectId() const { return x3b4_scanningObject; }\n  EGrappleState GetGrappleState() const { return x3b8_grappleState; }\n  bool IsNewScanScanning() const { return x9c6_30_newScanScanning; }\n  float GetThreatOverride() const { return xa1c_threatOverride; }\n  bool IsOverrideRadarRadius() const { return x9c6_31_overrideRadarRadius; }\n  void SetIsOverrideRadarRadius(bool override) { x9c6_31_overrideRadarRadius = override; }\n  float GetRadarXYRadiusOverride() const { return xa20_radarXYRadiusOverride; }\n  void SetRadarXYRadiusOverride(float xyOverride) { xa20_radarXYRadiusOverride = xyOverride; }\n  float GetRadarZRadiusOverride() const { return xa24_radarZRadiusOverride; }\n  void SetRadarZRadiusOverride(float zOverride) { xa24_radarZRadiusOverride = zOverride; }\n  bool ObjectInScanningRange(TUniqueId id, const CStateManager& mgr) const;\n  float GetMorphTime() const { return x574_morphTime; }\n  float GetMorphDuration() const { return x578_morphDuration; }\n  float GetMorphFactor() const {\n    if (0.f != x578_morphDuration)\n      return zeus::clamp(0.f, x574_morphTime / x578_morphDuration, 1.f);\n    return 0.f;\n  }\n  bool IsInFreeLook() const { return x3dc_inFreeLook; }\n  bool GetFreeLookStickState() const { return x3de_lookAnalogHeld; }\n  CPlayerGun* GetPlayerGun() const { return x490_gun.get(); }\n  CMorphBall* GetMorphBall() const { return x768_morphball.get(); }\n  CPlayerCameraBob* GetCameraBob() const { return x76c_cameraBob.get(); }\n  float GetStaticTimer() const { return x740_staticTimer; }\n  float GetDeathTime() const { return x9f4_deathTime; }\n  const CPlayerEnergyDrain& GetEnergyDrain() const { return x274_energyDrain; }\n  CPlayerEnergyDrain& GetEnergyDrain() { return x274_energyDrain; }\n  EPlayerZoneInfo GetOrbitZone() const { return x330_orbitZoneMode; }\n  EPlayerZoneType GetOrbitType() const { return x334_orbitType; }\n  const zeus::CTransform& GetFirstPersonCameraTransform(const CStateManager& mgr) const;\n  const std::vector<TUniqueId>& GetNearbyOrbitObjects() const { return x344_nearbyOrbitObjects; }\n  const std::vector<TUniqueId>& GetOnScreenOrbitObjects() const { return x354_onScreenOrbitObjects; }\n  const std::vector<TUniqueId>& GetOffScreenOrbitObjects() const { return x364_offScreenOrbitObjects; }\n  void SetPlayerHitWallDuringMove();\n  float GetTimeSinceJump() const { return x2a8_timeSinceJump; }\n  void ResetTimeSinceJump() { x2a8_timeSinceJump = 1000.f; }\n  ESurfaceRestraints GetCurrentSurfaceRestraint() const { return x2ac_surfaceRestraint; }\n  ESurfaceRestraints GetSurfaceRestraint() const {\n    return x2b0_outOfWaterTicks == 2 ? GetCurrentSurfaceRestraint() : ESurfaceRestraints::Water;\n  }\n  void DecrementEnvironmentDamage();\n  void IncrementEnvironmentDamage();\n  void ApplySubmergedPitchBend(CSfxHandle& sfx);\n  void DetachActorFromPlayer();\n  bool AttachActorToPlayer(TUniqueId id, bool disableGun);\n  TUniqueId GetAttachedActor() const { return x26c_attachedActor; }\n  float GetAttachedActorStruggle() const { return xa28_attachedActorStruggle; }\n  void SetFrozenTimeoutBias(float bias) { x758_frozenTimeoutBias = bias; }\n  float GetDistanceUnderWater() const { return x828_distanceUnderWater; }\n  TUniqueId GetRidingPlatformId() const { return x82e_ridingPlatform; }\n  const zeus::CVector3f& GetLastVelocity() const { return x794_lastVelocity; }\n  const zeus::CVector3f& GetMoveDir() const { return x50c_moveDir; }\n  const zeus::CVector3f& GetLeaveMorphDir() const { return x518_leaveMorphDir; }\n  u32 GetBombJumpCount() const { return x9d0_bombJumpCount; }\n  float GetMoveSpeed() const { return x4f8_moveSpeed; }\n  EPlayerOrbitRequest GetOrbitRequest() const { return x30c_orbitRequest; }\n  bool IsShowingCrosshairs() const { return x9c4_25_showCrosshairs; }\n  bool IsSidewaysDashing() const { return x37c_sidewaysDashing; }\n  void SetLeaveMorphBallAllowed(bool b) { x590_leaveMorphballAllowed = b; }\n  const zeus::CVector3f& GetOrbitPoint() const { return x314_orbitPoint; }\n  float GetAverageSpeed() const;\n  bool IsInWaterMovement() const { return x9c4_31_inWaterMovement; }\n  void SetNoDamageLoopSfx(bool val) { x9c7_24_noDamageLoopSfx = val; }\n  void SetAccelerationChangeTimer(float time) { x2d4_accelerationChangeTimer = time; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CPlayerCameraBob.cpp",
    "content": "#include \"Runtime/World/CPlayerCameraBob.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n\n#include <zeus/Math.hpp>\n\nnamespace metaforce {\nfloat CPlayerCameraBob::kCameraBobExtentX = 0.071f;\nfloat CPlayerCameraBob::kCameraBobExtentY = 0.142f;\nfloat CPlayerCameraBob::kCameraBobPeriod = 0.47f;\nfloat CPlayerCameraBob::kOrbitBobScale = 0.769f;\nfloat CPlayerCameraBob::kMaxOrbitBobScale = 0.8f;\nfloat CPlayerCameraBob::kSlowSpeedPeriodScale = 0.3f;\nfloat CPlayerCameraBob::kTargetMagnitudeTrackingRate = 0.1f;\nfloat CPlayerCameraBob::kLandingBobSpringConstant = 150.f;\nfloat lbl_805A7398 = -30.f;\nfloat lbl_805A739C = -35.f;\nfloat CPlayerCameraBob::kLandingBobSpringConstant2 = 40.f;\nfloat lbl_805A73A4 = 80.f;\nfloat CPlayerCameraBob::kViewWanderRadius = 0.03f;\nfloat CPlayerCameraBob::kViewWanderSpeedMin = 0.1f;\nfloat CPlayerCameraBob::kViewWanderSpeedMax = 0.3f;\nfloat CPlayerCameraBob::kViewWanderRollVariation = 0.3f;\nfloat CPlayerCameraBob::kGunBobMagnitude = 0.3f;\nfloat CPlayerCameraBob::kHelmetBobMagnitude = 2.f;\nconst float CPlayerCameraBob::kLandingBobDamping = 2.f * std::sqrt(kLandingBobSpringConstant);\nconst float CPlayerCameraBob::kLandingBobDamping2 = 4.f * std::sqrt(kLandingBobSpringConstant2);\nconst float CPlayerCameraBob::kCameraDamping = 6.f * std::sqrt(lbl_805A73A4);\n\nCPlayerCameraBob::CPlayerCameraBob(ECameraBobType type, const zeus::CVector2f& vec, float bobPeriod)\n: x0_type(type), x4_vec(vec), xc_bobPeriod(bobPeriod) {\n  x7c_wanderPoints.fill(zeus::skForward);\n}\n\nzeus::CTransform CPlayerCameraBob::GetViewWanderTransform() const { return xd0_viewWanderXf; }\n\nzeus::CVector3f CPlayerCameraBob::GetHelmetBobTranslation() const {\n  return {kHelmetBobMagnitude * x2c_cameraBobTransform.origin.x(),\n          kHelmetBobMagnitude * x2c_cameraBobTransform.origin.y(),\n          kHelmetBobMagnitude * (x2c_cameraBobTransform.origin.z() - x78_camTranslation)};\n}\n\nzeus::CTransform CPlayerCameraBob::GetGunBobTransformation() const {\n  return zeus::CTransform::Translate((1.f + kGunBobMagnitude) * x2c_cameraBobTransform.origin);\n}\n\nzeus::CTransform CPlayerCameraBob::GetCameraBobTransformation() const { return x2c_cameraBobTransform; }\n\nvoid CPlayerCameraBob::SetPlayerVelocity(const zeus::CVector3f& velocity) {\n  x5c_playerVelocity = velocity;\n  x68_playerPeakFallVel = zeus::min(x68_playerPeakFallVel, velocity.z());\n}\n\nvoid CPlayerCameraBob::SetBobMagnitude(float magnitude) { x10_targetBobMagnitude = zeus::clamp(0.f, magnitude, 1.f); }\n\nvoid CPlayerCameraBob::SetBobTimeScale(float ts) { x18_bobTimeScale = zeus::clamp(0.f, ts, 1.f); }\n\nvoid CPlayerCameraBob::ResetCameraBobTime() { x1c_bobTime = 0.f; }\n\nvoid CPlayerCameraBob::SetCameraBobTransform(const zeus::CTransform& xf) { x2c_cameraBobTransform = xf; }\n\nvoid CPlayerCameraBob::SetState(CPlayerCameraBob::ECameraBobState state, CStateManager& mgr) {\n  if (x24_curState == state)\n    return;\n\n  x20_oldState = x24_curState;\n  x24_curState = state;\n  if (x20_oldState == ECameraBobState::InAir) {\n    x28_applyLandingTrans = true;\n    x68_playerPeakFallVel = std::max(x68_playerPeakFallVel, lbl_805A739C);\n    x29_hardLand = x68_playerPeakFallVel < lbl_805A7398;\n    if (x29_hardLand)\n      x74_camVelocity += x68_playerPeakFallVel;\n    x6c_landingVelocity += x68_playerPeakFallVel;\n    x68_playerPeakFallVel = 0.f;\n  }\n\n  if (x24_curState == ECameraBobState::WalkNoBob && x100_wanderMagnitude != 0.f)\n    InitViewWander(mgr);\n}\n\nvoid CPlayerCameraBob::InitViewWander(CStateManager& mgr) {\n  x7c_wanderPoints[0] = {0.f, 1.f, 0.f};\n  x7c_wanderPoints[1] = x7c_wanderPoints[0];\n  x7c_wanderPoints[2] = x7c_wanderPoints[0];\n  x7c_wanderPoints[3] = CalculateRandomViewWanderPosition(mgr);\n  xb0_wanderPitches[0] = 0.f;\n  xb0_wanderPitches[1] = xb0_wanderPitches[0];\n  xb0_wanderPitches[2] = xb0_wanderPitches[0];\n  xb0_wanderPitches[3] = CalculateRandomViewWanderPitch(mgr);\n\n  xc8_viewWanderSpeed = (kViewWanderSpeedMax - kViewWanderRadius) * kViewWanderRadius + mgr.GetActiveRandom()->Float();\n  xc4_wanderTime = 0.f;\n  xcc_wanderIndex = 0;\n}\n\nvoid CPlayerCameraBob::UpdateViewWander(float dt, CStateManager& mgr) {\n  zeus::CVector3f pt = zeus::getCatmullRomSplinePoint(\n      x7c_wanderPoints[xcc_wanderIndex], x7c_wanderPoints[(xcc_wanderIndex + 1) & 3],\n      x7c_wanderPoints[(xcc_wanderIndex + 2) & 3], x7c_wanderPoints[(xcc_wanderIndex + 3) & 3], xc4_wanderTime);\n\n  pt.x() *= x100_wanderMagnitude;\n  pt.z() *= x100_wanderMagnitude;\n  zeus::CTransform orient = zeus::CTransform::RotateY(\n      (zeus::getCatmullRomSplinePoint(xb0_wanderPitches[xcc_wanderIndex], xb0_wanderPitches[(xcc_wanderIndex + 1) & 3],\n                                      xb0_wanderPitches[(xcc_wanderIndex + 2) & 3],\n                                      xb0_wanderPitches[(xcc_wanderIndex + 3) & 3], xc4_wanderTime) *\n       x100_wanderMagnitude));\n  xd0_viewWanderXf = zeus::lookAt(zeus::skZero3f, pt, zeus::skUp) * orient;\n\n  xc4_wanderTime += xc8_viewWanderSpeed * dt;\n  if (xc4_wanderTime >= 1.f) {\n    x7c_wanderPoints[xcc_wanderIndex] = CalculateRandomViewWanderPosition(mgr);\n    xb0_wanderPitches[xcc_wanderIndex] = CalculateRandomViewWanderPitch(mgr);\n    xc8_viewWanderSpeed =\n        ((kViewWanderSpeedMax - kViewWanderSpeedMin) * kViewWanderSpeedMin) + mgr.GetActiveRandom()->Float();\n    xcc_wanderIndex = (xcc_wanderIndex + 1) & 3;\n    xc4_wanderTime -= 1.f;\n  }\n}\n\nvoid CPlayerCameraBob::Update(float dt, CStateManager& mgr) {\n  x1c_bobTime += dt * x18_bobTimeScale;\n\n  if (x28_applyLandingTrans) {\n    float landDampen = kLandingBobDamping;\n    float landSpring = kLandingBobSpringConstant;\n    if (x29_hardLand) {\n      landDampen = kLandingBobDamping2;\n      landSpring = kLandingBobSpringConstant2;\n    }\n\n    x6c_landingVelocity += dt * (-(landDampen * x6c_landingVelocity) - landSpring * x70_landingTranslation);\n    x70_landingTranslation += x6c_landingVelocity * dt;\n    x74_camVelocity += dt * (-(kCameraDamping * x74_camVelocity) - lbl_805A73A4 * x78_camTranslation);\n    x78_camTranslation += x74_camVelocity * dt;\n\n    if (std::fabs(x6c_landingVelocity) < 0.005f && std::fabs(x70_landingTranslation) < 0.005f &&\n        std::fabs(x74_camVelocity) < 0.005f && std::fabs(x78_camTranslation) < 0.005f) {\n      x28_applyLandingTrans = false;\n      x70_landingTranslation = 0.f;\n      x78_camTranslation = 0.f;\n    }\n  }\n\n  if (x24_curState == ECameraBobState::WalkNoBob)\n    x104_targetWanderMagnitude = 1.f;\n  else\n    x104_targetWanderMagnitude = 0.f;\n\n  float mag = mgr.GetCameraManager()->GetCameraBobMagnitude();\n  x70_landingTranslation *= mag;\n  x78_camTranslation *= mag;\n  x104_targetWanderMagnitude *= mag;\n  if (mgr.GetPlayer().x38c_doneSidewaysDashing) {\n    x70_landingTranslation *= 0.2f;\n    x78_camTranslation *= 0.2f;\n    x104_targetWanderMagnitude *= 0.2f;\n  }\n\n  x100_wanderMagnitude += kTargetMagnitudeTrackingRate * (x104_targetWanderMagnitude - x100_wanderMagnitude);\n  x100_wanderMagnitude = std::max(x100_wanderMagnitude, 0.f);\n  x14_bobMagnitude += kTargetMagnitudeTrackingRate * (x10_targetBobMagnitude - x14_bobMagnitude);\n  UpdateViewWander(dt, mgr);\n\n  x2c_cameraBobTransform = CalculateCameraBobTransformation() * GetViewWanderTransform() *\n                           zeus::lookAt(zeus::skZero3f, {0.f, 2.f, x78_camTranslation}, zeus::skUp);\n}\n\nzeus::CVector3f CPlayerCameraBob::CalculateRandomViewWanderPosition(CStateManager& mgr) const {\n  const float angle = (2.f * (M_PIF * mgr.GetActiveRandom()->Float()));\n  const float bias = kViewWanderRadius * mgr.GetActiveRandom()->Float();\n  return {(bias * std::sin(angle)), 1.f, (bias * std::cos(angle))};\n}\n\nfloat CPlayerCameraBob::CalculateRandomViewWanderPitch(CStateManager& mgr) const {\n  return zeus::degToRad((2.f * (mgr.GetActiveRandom()->Float() - 0.5f)) * kViewWanderRollVariation);\n}\n\nvoid CPlayerCameraBob::CalculateMovingTranslation(float& x, float& y) const {\n  if (x0_type == ECameraBobType::Zero) {\n    double c = ((M_PIF * 2.f) * std::fmod(x1c_bobTime, 2.0f * xc_bobPeriod) / xc_bobPeriod);\n    x = (x14_bobMagnitude * x4_vec.x()) * float(std::sin(c));\n    y = (x14_bobMagnitude * x4_vec.y()) * float(std::fabs(std::cos(c * .5)) * std::cos(c * .5));\n  } else if (x0_type == ECameraBobType::One) {\n    float fX = std::fmod(x1c_bobTime, 2.f * xc_bobPeriod);\n    if (fX > xc_bobPeriod)\n      x = (2.f - (fX / xc_bobPeriod)) * (x14_bobMagnitude * x4_vec.x());\n    else\n      x = ((fX / xc_bobPeriod)) * (x14_bobMagnitude * x4_vec.x());\n\n    auto sY = float(std::sin(std::fmod((M_PI * fX) / xc_bobPeriod, M_PI)));\n    y = (1.f - sY) * (x14_bobMagnitude * x4_vec.y()) * 0.5f +\n        (0.5f * -((sY * sY) - 1.f) * (x14_bobMagnitude * x4_vec.y()));\n  }\n}\n\nfloat CPlayerCameraBob::CalculateLandingTranslation() const { return x70_landingTranslation; }\n\nzeus::CTransform CPlayerCameraBob::CalculateCameraBobTransformation() const {\n  float x = 0.f;\n  float y = 0.f;\n  CalculateMovingTranslation(x, y);\n  if (x28_applyLandingTrans)\n    y += CalculateLandingTranslation();\n\n  return zeus::CTransform::Translate(x, 0.f, y);\n}\n\nvoid CPlayerCameraBob::ReadTweaks(CInputStream& in) {\n  kCameraBobExtentX = in.ReadFloat();\n  kCameraBobExtentY = in.ReadFloat();\n  kCameraBobPeriod = in.ReadFloat();\n  kOrbitBobScale = in.ReadFloat();\n  kMaxOrbitBobScale = in.ReadFloat();\n  kSlowSpeedPeriodScale = in.ReadFloat();\n  kTargetMagnitudeTrackingRate = in.ReadFloat();\n  kLandingBobSpringConstant = in.ReadFloat();\n  kViewWanderRadius = in.ReadFloat();\n  kViewWanderSpeedMin = in.ReadFloat();\n  kViewWanderSpeedMax = in.ReadFloat();\n  kViewWanderRollVariation = in.ReadFloat();\n  kGunBobMagnitude = in.ReadFloat();\n  kHelmetBobMagnitude = in.ReadFloat();\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CPlayerCameraBob.hpp",
    "content": "#pragma once\n\n#include <cfloat>\n\n#include \"Runtime/RetroTypes.hpp\"\n\n#include <zeus/CVector2f.hpp>\n#include <zeus/CVector3f.hpp>\n#include <zeus/CTransform.hpp>\n\nnamespace metaforce {\n\nclass CStateManager;\nclass CPlayerCameraBob {\npublic:\n  enum class ECameraBobType { Zero, One };\n\n  enum class ECameraBobState {\n    Walk,\n    Orbit,\n    InAir,\n    WalkNoBob,\n    GunFireNoBob,\n    TurningNoBob,\n    FreeLookNoBob,\n    GrappleNoBob,\n    Unspecified\n  };\n\n  static zeus::CVector2f GetCameraBobExtent() { return {kCameraBobExtentX, kCameraBobExtentY}; }\n  static float GetCameraBobPeriod() { return kCameraBobPeriod; }\n  static float GetOrbitBobScale() { return kOrbitBobScale; }\n  static float GetMaxOrbitBobScale() { return kMaxOrbitBobScale; }\n  static float GetSlowSpeedPeriodScale() { return kSlowSpeedPeriodScale; }\n\nprivate:\n  static float kCameraBobExtentX;\n  static float kCameraBobExtentY;\n  static float kCameraBobPeriod;\n  static float kOrbitBobScale;\n  static float kMaxOrbitBobScale;\n  static float kSlowSpeedPeriodScale;\n  static float kTargetMagnitudeTrackingRate;\n  static float kLandingBobSpringConstant;\n  static float kLandingBobSpringConstant2;\n  static float kViewWanderRadius;\n  static float kViewWanderSpeedMin;\n  static float kViewWanderSpeedMax;\n  static float kViewWanderRollVariation;\n  static float kGunBobMagnitude;\n  static float kHelmetBobMagnitude;\n  static const float kLandingBobDamping;\n  static const float kLandingBobDamping2;\n  static const float kCameraDamping;\n\n  ECameraBobType x0_type;\n  zeus::CVector2f x4_vec;\n  float xc_bobPeriod;\n  float x10_targetBobMagnitude = 0.f;\n  float x14_bobMagnitude = 0.f;\n  float x18_bobTimeScale = 0.f;\n  float x1c_bobTime = 0.f;\n  ECameraBobState x20_oldState = ECameraBobState::Unspecified;\n  ECameraBobState x24_curState = ECameraBobState::Unspecified;\n  bool x28_applyLandingTrans = false;\n  bool x29_hardLand = false;\n  zeus::CTransform x2c_cameraBobTransform;\n  zeus::CVector3f x5c_playerVelocity;\n  float x68_playerPeakFallVel = 0.f;\n  float x6c_landingVelocity = 0.f;\n  float x70_landingTranslation = 0.f;\n  float x74_camVelocity = 0.f;\n  float x78_camTranslation = 0.f;\n  std::array<zeus::CVector3f, 4> x7c_wanderPoints;\n  std::array<float, 4> xb0_wanderPitches{};\n  float xc4_wanderTime = 0.f;\n  float xc8_viewWanderSpeed = kViewWanderSpeedMin;\n  u32 xcc_wanderIndex = 0;\n  zeus::CTransform xd0_viewWanderXf;\n  float x100_wanderMagnitude = FLT_EPSILON;\n  float x104_targetWanderMagnitude = 0.f;\n\npublic:\n  CPlayerCameraBob(ECameraBobType type, const zeus::CVector2f& vec, float bobPeriod);\n\n  zeus::CTransform GetViewWanderTransform() const;\n  zeus::CVector3f GetHelmetBobTranslation() const;\n  zeus::CTransform GetGunBobTransformation() const;\n  zeus::CTransform GetCameraBobTransformation() const;\n  void SetPlayerVelocity(const zeus::CVector3f& velocity);\n  void SetBobMagnitude(float);\n  void SetBobTimeScale(float);\n  void ResetCameraBobTime();\n  void SetCameraBobTransform(const zeus::CTransform&);\n  void SetState(ECameraBobState, CStateManager&);\n  void InitViewWander(CStateManager&);\n  void UpdateViewWander(float, CStateManager&);\n  void Update(float, CStateManager&);\n  zeus::CVector3f CalculateRandomViewWanderPosition(CStateManager&) const;\n  float CalculateRandomViewWanderPitch(CStateManager&) const;\n  void CalculateMovingTranslation(float& x, float& y) const;\n  float CalculateLandingTranslation() const;\n  zeus::CTransform CalculateCameraBobTransformation() const;\n  static void ReadTweaks(CInputStream& in);\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CPlayerEnergyDrain.cpp",
    "content": "#include \"Runtime/World/CPlayerEnergyDrain.hpp\"\n\n#include <numeric>\n\n#include \"Runtime/CStateManager.hpp\"\n\nnamespace metaforce {\n\nCPlayerEnergyDrain::CPlayerEnergyDrain(u32 numSources) { x0_sources.reserve(numSources); }\n\nvoid CPlayerEnergyDrain::AddEnergyDrainSource(TUniqueId id, float intensity) { x0_sources.emplace_back(id, intensity); }\n\nvoid CPlayerEnergyDrain::RemoveEnergyDrainSource(TUniqueId id) {\n  auto it = rstl::binary_find(x0_sources.begin(), x0_sources.end(), id,\n                              [](const CEnergyDrainSource& item) { return item.GetEnergyDrainSourceId(); });\n  if (it != x0_sources.end())\n    x0_sources.erase(it);\n}\n\nfloat CPlayerEnergyDrain::GetEnergyDrainIntensity() const {\n  return std::accumulate(x0_sources.cbegin(), x0_sources.cend(), 0.0f,\n                         [](float value, const auto& src) { return value + src.GetEnergyDrainIntensity(); });\n}\n\nvoid CPlayerEnergyDrain::ProcessEnergyDrain(const CStateManager& mgr, float dt) {\n  auto it = x0_sources.begin();\n\n  for (; it != x0_sources.end(); ++it) {\n    if (mgr.GetObjectById((*it).GetEnergyDrainSourceId()) == nullptr)\n      RemoveEnergyDrainSource((*it).GetEnergyDrainSourceId());\n  }\n\n  if (x0_sources.empty())\n    x10_energyDrainTime = 0.f;\n  else\n    x10_energyDrainTime += dt;\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CPlayerEnergyDrain.hpp",
    "content": "#pragma once\n\n#include <vector>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/World/CEnergyDrainSource.hpp\"\n\nnamespace metaforce {\nclass CStateManager;\nclass CPlayerEnergyDrain {\n  std::vector<CEnergyDrainSource> x0_sources;\n  float x10_energyDrainTime = 0.0f;\n\npublic:\n  explicit CPlayerEnergyDrain(u32 numSources);\n  const std::vector<CEnergyDrainSource>& GetEnergyDrainSources() const { return x0_sources; }\n  void AddEnergyDrainSource(TUniqueId, float);\n  void RemoveEnergyDrainSource(TUniqueId id);\n  float GetEnergyDrainIntensity() const;\n  float GetEnergyDrainTime() const { return x10_energyDrainTime; }\n  void ProcessEnergyDrain(const CStateManager&, float);\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CProjectedShadow.cpp",
    "content": "#include \"Runtime/World/CProjectedShadow.hpp\"\n\nnamespace metaforce {\n\nCProjectedShadow::CProjectedShadow(u32 w, u32 h, bool persistent)\n: x0_texture(CTexture(ETexelFormat::I4, w, h, 1, \"Projected Shadow Texture\")), x81_persistent(persistent) {}\n\nzeus::CAABox CProjectedShadow::CalculateRenderBounds() { return {}; }\n\nvoid CProjectedShadow::Render(const CStateManager& mgr) {}\n\nvoid CProjectedShadow::RenderShadowBuffer(const CStateManager&, const CModelData&, const zeus::CTransform&, s32,\n                                          const zeus::CVector3f&, float, float) {}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CProjectedShadow.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Graphics/CTexture.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CStateManager;\nclass CModelData;\nclass CProjectedShadow {\n  CTexture x0_texture;\n  zeus::CAABox x68_ = zeus::CAABox();\n  bool x80_;\n  bool x81_persistent;\n  float x84_ = 1.f;\n  zeus::CVector3f x88_ = zeus::skZero3f;\n  float x94_zDistanceAdjust = 0.f;\n  float x98_ = 1.f;\n\npublic:\n  CProjectedShadow(u32, u32, bool);\n\n  zeus::CAABox CalculateRenderBounds();\n  void Render(const CStateManager& mgr);\n  void RenderShadowBuffer(const CStateManager&, const CModelData&, const zeus::CTransform&, s32, const zeus::CVector3f&,\n                          float, float);\n\n  void Unset_X80() { x80_ = false; }\n  void Set_x98(float f) { x98_ = f; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CRepulsor.cpp",
    "content": "#include \"Runtime/World/CRepulsor.hpp\"\n\n#include \"Runtime/World/CActorParameters.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nCRepulsor::CRepulsor(TUniqueId uid, bool active, std::string_view name, const CEntityInfo& info,\n                     const zeus::CVector3f& pos, float radius)\n: CActor(uid, active, name, info, zeus::CTransform::Translate(pos), CModelData::CModelDataNull(), CMaterialList(),\n         CActorParameters::None(), kInvalidUniqueId)\n, xe8_affectRadius(radius) {}\n\nvoid CRepulsor::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CRepulsor::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) {\n  CActor::AcceptScriptMsg(msg, objId, stateMgr);\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CRepulsor.hpp",
    "content": "#pragma once\n\n#include <string_view>\n#include \"Runtime/World/CActor.hpp\"\n\nnamespace metaforce {\nclass CRepulsor : public CActor {\n  float xe8_affectRadius;\n\npublic:\n  DEFINE_ENTITY\n  CRepulsor(TUniqueId, bool, std::string_view, const CEntityInfo&, const zeus::CVector3f&, float);\n\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n\n  float GetAffectRadius() const { return xe8_affectRadius; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CRipple.cpp",
    "content": "#include \"Runtime/World/CRipple.hpp\"\n\n#include \"Runtime/CRandom16.hpp\"\n\nnamespace metaforce {\nstatic CRandom16 sRippleRandom(0xABBA);\n\nCRipple::CRipple(TUniqueId id, const zeus::CVector3f& center, float intensity) : x0_id(id), x8_center(center) {\n  if (intensity >= 0.f && intensity <= 1.f) {\n    float tmp = 2.f * std::min(1.f, std::max(0.f, intensity * (sRippleRandom.Float() - 0.5f) * 2.f * 0.1f + intensity));\n    x14_timeFalloff = 0.5f * tmp + 1.5f;\n    x18_distFalloff = 4.f * tmp + 8.f;\n    x1c_frequency = 2.f + tmp;\n    x20_amplitude = 0.15f * tmp + 0.1f;\n    x24_lookupAmplitude = x20_amplitude / 255.f;\n  }\n\n  x28_ooTimeFalloff = 1.f / x14_timeFalloff;\n  x2c_ooDistFalloff = 1.f / x18_distFalloff;\n  x30_ooPhase = x18_distFalloff / 2.5f;\n  x34_phase = 1.f / x30_ooPhase;\n  x38_lookupPhase = 256.f * x34_phase;\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CRipple.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CRipple {\nprivate:\n  TUniqueId x0_id;\n  float x4_time = 0.f;\n  zeus::CVector3f x8_center;\n  float x14_timeFalloff = 2.f;\n  float x18_distFalloff = 12.f;\n  float x1c_frequency = 3.f;\n  float x20_amplitude = 0.25f;\n  float x24_lookupAmplitude = 0.00098039221f;\n  float x28_ooTimeFalloff = 0.f;\n  float x2c_ooDistFalloff = 0.f;\n  float x30_ooPhase = 0.f;\n  float x34_phase = 0.f;\n  float x38_lookupPhase = 0.f;\n  u32 x3c_ = 0;\n\npublic:\n  CRipple(TUniqueId id, const zeus::CVector3f& center, float intensity);\n\n  void SetTime(float t) { x4_time = t; }\n  float GetTime() const { return x4_time; }\n  float GetTimeFalloff() const { return x14_timeFalloff; }\n  TUniqueId GetUniqueId() const { return x0_id; }\n  float GetFrequency() const { return x1c_frequency; }\n  float GetAmplitude() const { return x20_amplitude; }\n  float GetLookupAmplitude() const { return x24_lookupAmplitude; }\n  float GetOODistanceFalloff() const { return x2c_ooDistFalloff; }\n  float GetDistanceFalloff() const { return x18_distFalloff; }\n  const zeus::CVector3f& GetCenter() const { return x8_center; }\n  float GetOOTimeFalloff() const { return x28_ooTimeFalloff; }\n  float GetPhase() const { return x34_phase; }\n  float GetLookupPhase() const { return x38_lookupPhase; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CRippleManager.cpp",
    "content": "#include \"Runtime/World/CRippleManager.hpp\"\n\nnamespace metaforce {\n\nCRippleManager::CRippleManager(int maxRipples, float alpha) : x14_alpha(alpha) { Init(maxRipples); }\n\nvoid CRippleManager::Init(int maxRipples) {\n  x4_ripples.resize(maxRipples, CRipple(kInvalidUniqueId, zeus::skZero3f, 0.f));\n  for (CRipple& r : x4_ripples)\n    r.SetTime(9999.f);\n}\n\nvoid CRippleManager::Update(float dt) {\n  for (CRipple& ripple : x4_ripples) {\n    ripple.SetTime(ripple.GetTime() + dt);\n    if (ripple.GetTime() > 9999.f)\n      ripple.SetTime(9999.f);\n  }\n}\n\nfloat CRippleManager::GetLastRippleDeltaTime(TUniqueId rippler) const {\n  float res = 9999.f;\n  for (const CRipple& r : x4_ripples)\n    if (r.GetUniqueId() == rippler)\n      if (r.GetTime() < res)\n        res = r.GetTime();\n  return res;\n}\n\nvoid CRippleManager::AddRipple(const CRipple& ripple) {\n  float maxTime = 0.f;\n  auto oldestRipple = x4_ripples.end();\n  for (auto it = x4_ripples.begin(); it != x4_ripples.end(); ++it)\n    if (it->GetTime() > maxTime) {\n      oldestRipple = it;\n      maxTime = it->GetTime();\n    }\n\n  if (oldestRipple != x4_ripples.end()) {\n    *oldestRipple = ripple;\n    oldestRipple->SetTime(0.f);\n    SetMaxTimeFalloff(std::max(GetMaxTimeFalloff(), ripple.GetTimeFalloff()));\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CRippleManager.hpp",
    "content": "#pragma once\n\n#include <vector>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/World/CRipple.hpp\"\n\nnamespace metaforce {\n\nclass CRippleManager {\n  float x0_maxTimeFalloff = 0.f;\n  std::vector<CRipple> x4_ripples;\n  float x14_alpha;\n\npublic:\n  CRippleManager(int maxRipples, float alpha);\n  void Init(int maxRipples);\n  std::vector<CRipple>& GetRipples() { return x4_ripples; }\n  const std::vector<CRipple>& GetRipples() const { return x4_ripples; }\n  void Update(float dt);\n  float GetLastRippleDeltaTime(TUniqueId rippler) const;\n  void AddRipple(const CRipple& ripple);\n  void SetMaxTimeFalloff(float time) { x0_maxTimeFalloff = time; }\n  float GetMaxTimeFalloff() const { return x0_maxTimeFalloff; }\n  void SetAlpha(float a) { x14_alpha = a; }\n  float GetAlpha() const { return x14_alpha; }\n};\n\n}; // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScannableParameters.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n\nnamespace metaforce {\n\nclass CScannableParameters {\n  CAssetId x0_scanId;\n\npublic:\n  constexpr CScannableParameters() = default;\n  constexpr explicit CScannableParameters(CAssetId id) : x0_scanId(id) {}\n  [[nodiscard]] constexpr CAssetId GetScanId() const { return x0_scanId; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptActor.cpp",
    "content": "#include \"Runtime/World/CScriptActor.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/CPlayerState.hpp\"\n#include \"Runtime/Camera/CGameCamera.hpp\"\n#include \"Runtime/Character/IAnimReader.hpp\"\n#include \"Runtime/MP1/World/CScriptContraption.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/World/CDamageVulnerability.hpp\"\n#include \"Runtime/World/CScriptColorModulate.hpp\"\n#include \"Runtime/World/CScriptTrigger.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\n#include <zeus/CEulerAngles.hpp>\n\nnamespace metaforce {\n\nCScriptActor::CScriptActor(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                           CModelData&& mData, const zeus::CAABox& aabb, float mass, float zMomentum,\n                           const CMaterialList& matList, const CHealthInfo& hInfo, const CDamageVulnerability& dVuln,\n                           const CActorParameters& actParms, bool looping, bool active, s32 shaderIdx, float xrayAlpha,\n                           bool noThermalHotZ, bool castsShadow, bool scaleAdvancementDelta, bool materialFlag54)\n: CPhysicsActor(uid, active, name, info, xf, std::move(mData), matList, aabb, SMoverData(mass), actParms, 0.3f, 0.1f)\n, x258_initialHealth(hInfo)\n, x260_currentHealth(hInfo)\n, x268_damageVulnerability(dVuln)\n, x2d0_fadeInTime(actParms.x5c_fadeInTime)\n, x2d4_fadeOutTime(actParms.x60_fadeOutTime)\n, x2d8_shaderIdx(shaderIdx)\n, x2dc_xrayAlpha(xrayAlpha) {\n  x2e2_24_noThermalHotZ = noThermalHotZ;\n  x2e2_27_xrayAlphaEnabled = !zeus::close_enough(1.f, xrayAlpha);\n  x2e2_29_processModelFlags = (x2e2_27_xrayAlphaEnabled || x2e2_24_noThermalHotZ || x2d8_shaderIdx != 0);\n  x2e2_30_scaleAdvancementDelta = scaleAdvancementDelta;\n  x2e2_31_materialFlag54 = materialFlag54;\n\n  if (x64_modelData && (x64_modelData->HasAnimData() || x64_modelData->HasNormalModel()) && castsShadow) {\n    CreateShadow(true);\n  }\n\n  if (x64_modelData && x64_modelData->HasAnimData()) {\n    x64_modelData->EnableLooping(looping);\n  }\n\n  x150_momentum = zeus::CVector3f(0.f, 0.f, -zMomentum);\n}\n\nvoid CScriptActor::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptActor::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  switch (msg) {\n  case EScriptObjectMessage::InitializedInArea:\n    for (const SConnection& conn : x20_conns) {\n      if (conn.x0_state != EScriptObjectState::InheritBounds || conn.x4_msg != EScriptObjectMessage::Activate) {\n        continue;\n      }\n\n      const auto search = mgr.GetIdListForScript(conn.x8_objId);\n      for (auto it = search.first; it != search.second; ++it) {\n        if (TCastToConstPtr<CScriptTrigger>(mgr.GetObjectById(it->second))) {\n          x2e0_triggerId = it->second;\n          break;\n        }\n      }\n    }\n\n    if (x2e2_31_materialFlag54) {\n      CActor::AddMaterial(EMaterialTypes::Unknown54, mgr);\n    }\n    break;\n  case EScriptObjectMessage::Reset:\n    x2e2_25_dead = false;\n    x260_currentHealth = x258_initialHealth;\n    break;\n  case EScriptObjectMessage::Increment:\n    if (!GetActive()) {\n      mgr.SendScriptMsg(this, x8_uid, EScriptObjectMessage::Activate);\n      CScriptColorModulate::FadeInHelper(mgr, x8_uid, x2d0_fadeInTime);\n    }\n    break;\n  case EScriptObjectMessage::Decrement:\n    CScriptColorModulate::FadeOutHelper(mgr, x8_uid, x2d4_fadeOutTime);\n    break;\n  default:\n    break;\n  }\n\n  CActor::AcceptScriptMsg(msg, uid, mgr);\n}\n\nvoid CScriptActor::Think(float dt, CStateManager& mgr) {\n  if (!GetActive()) {\n    return;\n  }\n\n  if (HasModelData() && x64_modelData->HasAnimData()) {\n    const bool timeRemaining = x64_modelData->GetAnimationData()->IsAnimTimeRemaining(dt - FLT_EPSILON, \"Whole Body\");\n    const bool loop = x64_modelData->GetIsLoop();\n\n    const SAdvancementDeltas deltas = CActor::UpdateAnimation(dt, mgr, true);\n\n    if (timeRemaining || loop) {\n      x2e2_26_animating = true;\n\n      if (x2e2_30_scaleAdvancementDelta) {\n        MoveToOR(x34_transform.rotate(x64_modelData->GetScale() * x34_transform.transposeRotate(deltas.x0_posDelta)),\n                 dt);\n      } else {\n        MoveToOR(deltas.x0_posDelta, dt);\n      }\n\n      RotateToOR(deltas.xc_rotDelta, dt);\n    }\n\n    if (!timeRemaining && x2e2_26_animating && !loop) {\n      SendScriptMsgs(EScriptObjectState::MaxReached, mgr, EScriptObjectMessage::None);\n      x2e2_26_animating = false;\n    }\n  }\n\n  if (!x2e2_25_dead && HealthInfo(mgr)->GetHP() <= 0.f) {\n    x2e2_25_dead = true;\n    SendScriptMsgs(EScriptObjectState::Dead, mgr, EScriptObjectMessage::None);\n  }\n}\n\nvoid CScriptActor::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) {\n  CActor::PreRender(mgr, frustum);\n\n  if (xe4_30_outOfFrustum && TCastToConstPtr<CCinematicCamera>(mgr.GetCameraManager()->GetCurrentCamera(mgr))) {\n    xe4_30_outOfFrustum = false;\n  }\n\n  if (!xe4_30_outOfFrustum && x2e2_29_processModelFlags) {\n    if (x2e2_27_xrayAlphaEnabled) {\n      const zeus::CColor col(1.f, x2dc_xrayAlpha);\n      const CModelFlags xrayFlags(5, 0, 3, col);\n      if (mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::XRay) {\n        xb4_drawFlags = xrayFlags;\n        x2e2_28_inXrayAlpha = true;\n      } else if (x2e2_28_inXrayAlpha) {\n        x2e2_28_inXrayAlpha = false;\n        if (xb4_drawFlags == xrayFlags) {\n          xb4_drawFlags = CModelFlags(0, 0, 3, zeus::skWhite);\n        }\n      }\n    }\n\n    if (x2e2_24_noThermalHotZ && xe6_27_thermalVisorFlags == 2) {\n      if (mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::Thermal) {\n        // Disable Z test/update\n        xb4_drawFlags.x2_flags &= CModelFlagBits::DepthTest;\n        xb4_drawFlags.x2_flags &= CModelFlagBits::DepthUpdate;\n      } else {\n        // Enable Z test/update\n        xb4_drawFlags.x2_flags |= CModelFlagBits::DepthTest;\n        xb4_drawFlags.x2_flags |= CModelFlagBits::DepthUpdate;\n      }\n    }\n\n    if (x2d8_shaderIdx != 0) {\n      xb4_drawFlags.x1_matSetIdx = u8(x2d8_shaderIdx);\n    }\n  }\n\n  if (mgr.GetObjectById(x2e0_triggerId) == nullptr) {\n    x2e0_triggerId = kInvalidUniqueId;\n  }\n}\n\nzeus::CAABox CScriptActor::GetSortingBounds(const CStateManager& mgr) const {\n  if (x2e0_triggerId != kInvalidUniqueId) {\n    const TCastToConstPtr<CScriptTrigger> trigger(mgr.GetObjectById(x2e0_triggerId));\n    if (trigger) {\n      return trigger->GetTriggerBoundsWR();\n    }\n  }\n\n  return CActor::GetSortingBounds(mgr);\n}\n\nEWeaponCollisionResponseTypes CScriptActor::GetCollisionResponseType(const zeus::CVector3f& v1,\n                                                                     const zeus::CVector3f& v2,\n                                                                     const CWeaponMode& wMode,\n                                                                     EProjectileAttrib w) const {\n  const CDamageVulnerability* dVuln = GetDamageVulnerability();\n  if (dVuln->GetVulnerability(wMode, false) == EVulnerability::Deflect) {\n    const EDeflectType deflectType = dVuln->GetDeflectionType(wMode);\n    if (deflectType < EDeflectType::Four && deflectType >= EDeflectType::One) {\n      return EWeaponCollisionResponseTypes::Unknown15;\n    }\n  }\n  return CActor::GetCollisionResponseType(v1, v2, wMode, w);\n}\n\nstd::optional<zeus::CAABox> CScriptActor::GetTouchBounds() const {\n  if (GetActive() && x68_material.HasMaterial(EMaterialTypes::Solid)) {\n    return {CPhysicsActor::GetBoundingBox()};\n  }\n  return std::nullopt;\n}\n\nvoid CScriptActor::Touch(CActor&, CStateManager&) {\n  // Empty\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptActor.hpp",
    "content": "#pragma once\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/World/CDamageVulnerability.hpp\"\n#include \"Runtime/World/CHealthInfo.hpp\"\n#include \"Runtime/World/CPhysicsActor.hpp\"\n\nnamespace metaforce {\n\nclass CScriptActor : public CPhysicsActor {\nprotected:\n  CHealthInfo x258_initialHealth;\n  CHealthInfo x260_currentHealth;\n  CDamageVulnerability x268_damageVulnerability;\n  float x2d0_fadeInTime;\n  float x2d4_fadeOutTime;\n  s32 x2d8_shaderIdx;\n  float x2dc_xrayAlpha;\n  TUniqueId x2e0_triggerId = kInvalidUniqueId;\n  bool x2e2_24_noThermalHotZ : 1;\n  bool x2e2_25_dead : 1 = false;\n  bool x2e2_26_animating : 1 = true;\n  bool x2e2_27_xrayAlphaEnabled : 1;\n  bool x2e2_28_inXrayAlpha : 1 = false;\n  bool x2e2_29_processModelFlags : 1;\n  bool x2e2_30_scaleAdvancementDelta : 1;\n  bool x2e2_31_materialFlag54 : 1;\n  bool x2e3_24_isPlayerActor : 1 = false;\n\n#if ENABLE_IMGUI\n  bool m_editingDamageVulnerability = false;\n#endif\n\npublic:\n  DEFINE_ENTITY\n  CScriptActor(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n               CModelData&& mData, const zeus::CAABox& aabb, float mass, float zMomentum, const CMaterialList& matList,\n               const CHealthInfo& hInfo, const CDamageVulnerability& dVuln, const CActorParameters& actParms,\n               bool looping, bool active, s32 shaderIdx, float xrayAlpha, bool noThermalHotZ, bool castsShadow,\n               bool scaleAdvancementDelta, bool materialFlag54);\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void Think(float, CStateManager&) override;\n  void PreRender(CStateManager&, const zeus::CFrustum&) override;\n  zeus::CAABox GetSortingBounds(const CStateManager&) const override;\n  EWeaponCollisionResponseTypes GetCollisionResponseType(const zeus::CVector3f&, const zeus::CVector3f&,\n                                                         const CWeaponMode&, EProjectileAttrib) const override;\n  std::optional<zeus::CAABox> GetTouchBounds() const override;\n  void Touch(CActor&, CStateManager&) override;\n  const CDamageVulnerability* GetDamageVulnerability() const override { return &x268_damageVulnerability; }\n  CHealthInfo* HealthInfo(CStateManager&) override { return &x260_currentHealth; }\n  bool IsPlayerActor() const { return x2e3_24_isPlayerActor; }\n};\n}; // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptActorKeyframe.cpp",
    "content": "#include \"Runtime/World/CScriptActorKeyframe.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n#include \"Runtime/World/CScriptActor.hpp\"\n#include \"Runtime/World/CScriptPlatform.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nCScriptActorKeyframe::CScriptActorKeyframe(TUniqueId uid, std::string_view name, const CEntityInfo& info, s32 animId,\n                                           bool looping, float lifetime, bool isPassive, u32 fadeOut, bool active,\n                                           float totalPlayback)\n: CEntity(uid, info, active, name)\n, x34_animationId(animId)\n, x38_initialLifetime(lifetime)\n, x3c_playbackRate(totalPlayback)\n, x40_lifetime(lifetime)\n, x44_24_looping(looping)\n, x44_25_isPassive(isPassive)\n, x44_26_fadeOut(fadeOut != 0u)\n, x44_27_timedLoop(fadeOut != 0u) {}\n\nvoid CScriptActorKeyframe::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptActorKeyframe::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  if (msg == EScriptObjectMessage::Action) {\n    if (GetActive()) {\n      if (!x44_25_isPassive) {\n        for (const SConnection& conn : x20_conns) {\n          if (conn.x0_state != EScriptObjectState::Play || conn.x4_msg != EScriptObjectMessage::Play) {\n            continue;\n          }\n\n          const auto search = mgr.GetIdListForScript(conn.x8_objId);\n          for (auto it = search.first; it != search.second; ++it) {\n            UpdateEntity(it->second, mgr);\n          }\n        }\n      }\n\n      x44_28_playing = true;\n      x40_lifetime = x38_initialLifetime;\n      SendScriptMsgs(EScriptObjectState::Play, mgr, EScriptObjectMessage::None);\n    }\n  } else if (msg == EScriptObjectMessage::InitializedInArea) {\n    if (x34_animationId == -1)\n      x34_animationId = 0;\n  }\n\n  CEntity::AcceptScriptMsg(msg, uid, mgr);\n}\n\nvoid CScriptActorKeyframe::Think(float dt, CStateManager& mgr) {\n  if (x44_25_isPassive || !x44_24_looping || !x44_27_timedLoop || !x44_28_playing || x40_lifetime <= 0.f) {\n    CEntity::Think(dt, mgr);\n    return;\n  }\n\n  x40_lifetime -= dt;\n  if (x40_lifetime > 0.f) {\n    CEntity::Think(dt, mgr);\n    return;\n  }\n\n  x44_28_playing = false;\n  for (const SConnection& conn : x20_conns) {\n    if (conn.x0_state != EScriptObjectState::Play || conn.x4_msg != EScriptObjectMessage::Play) {\n      continue;\n    }\n\n    CEntity* ent = mgr.ObjectById(mgr.GetIdForScript(conn.x8_objId));\n    if (const TCastToPtr<CScriptActor> act = ent) {\n      if (act->HasModelData() && act->GetModelData()->HasAnimData()) {\n        CAnimData* animData = act->GetModelData()->GetAnimationData();\n        if (animData->IsAdditiveAnimation(x34_animationId)) {\n          animData->DelAdditiveAnimation(x34_animationId);\n        } else if (animData->GetDefaultAnimation() == x34_animationId) {\n          animData->EnableLooping(false);\n        }\n      }\n    } else if (const TCastToPtr<CPatterned> ai = ent) {\n      CAnimData* animData = ai->GetModelData()->GetAnimationData();\n      if (animData->IsAdditiveAnimation(x34_animationId)) {\n        animData->DelAdditiveAnimation(x34_animationId);\n      } else if (ai->GetBodyController()->GetCurrentStateId() == pas::EAnimationState::Scripted &&\n                 animData->GetDefaultAnimation() == x34_animationId) {\n        ai->GetBodyController()->GetCommandMgr().DeliverCmd(CBodyStateCmd(EBodyStateCmd::ExitState));\n      }\n    }\n  }\n\n  CEntity::Think(dt, mgr);\n}\n\nvoid CScriptActorKeyframe::UpdateEntity(TUniqueId uid, CStateManager& mgr) {\n  CEntity* ent = mgr.ObjectById(uid);\n  CActor* act = nullptr;\n  if (const TCastToPtr<CScriptActor> actor = ent) {\n    act = actor.GetPtr();\n  } else if (const TCastToPtr<CScriptPlatform> platform = ent) {\n    act = platform.GetPtr();\n  }\n\n  if (act) {\n    if (!act->GetActive()) {\n      mgr.SendScriptMsg(act, GetUniqueId(), EScriptObjectMessage::Activate);\n    }\n    act->SetDrawFlags({0, 0, 3, zeus::skWhite});\n    if (act->HasModelData() && act->GetModelData()->HasAnimData()) {\n      CAnimData* animData = act->GetModelData()->GetAnimationData();\n      if (animData->IsAdditiveAnimation(x34_animationId)) {\n        animData->AddAdditiveAnimation(x34_animationId, 1.f, x44_24_looping, x44_26_fadeOut);\n      } else {\n        animData->SetAnimation(CAnimPlaybackParms(x34_animationId, -1, 1.f, true), false);\n        act->GetModelData()->EnableLooping(x44_24_looping);\n        animData->MultiplyPlaybackRate(x3c_playbackRate);\n      }\n    }\n  } else if (TCastToPtr<CPatterned> ai = ent) {\n    CAnimData* animData = ai->GetModelData()->GetAnimationData();\n    if (animData->IsAdditiveAnimation(x34_animationId)) {\n      animData->AddAdditiveAnimation(x34_animationId, 1.f, x44_24_looping, x44_26_fadeOut);\n    } else {\n      ai->GetBodyController()->GetCommandMgr().DeliverCmd(\n          CBCScriptedCmd(x34_animationId, x44_24_looping, x44_27_timedLoop, x38_initialLifetime));\n    }\n  }\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptActorKeyframe.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/World/CEntity.hpp\"\n\nnamespace metaforce {\nclass CScriptActorKeyframe : public CEntity {\nprivate:\n  s32 x34_animationId;\n  float x38_initialLifetime;\n  float x3c_playbackRate;\n  float x40_lifetime;\n  bool x44_24_looping : 1;\n  bool x44_25_isPassive : 1;\n  bool x44_26_fadeOut : 1;\n  bool x44_27_timedLoop : 1;\n  bool x44_28_playing : 1 = false;\n  bool x44_29_ : 1 = false;\n\npublic:\n  DEFINE_ENTITY\n  CScriptActorKeyframe(TUniqueId uid, std::string_view name, const CEntityInfo& info, s32 animId, bool looping,\n                       float lifetime, bool isPassive, u32 fadeOut, bool active, float totalPlayback);\n\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) override;\n  void Think(float, CStateManager&) override;\n  void UpdateEntity(TUniqueId, CStateManager&);\n  bool IsPassive() const { return x44_25_isPassive; }\n  void SetIsPassive(bool b) { x44_25_isPassive = b; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptActorRotate.cpp",
    "content": "#include \"Runtime/World/CScriptActorRotate.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CScriptPlatform.hpp\"\n#include \"Runtime/World/CScriptSpiderBallWaypoint.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nCScriptActorRotate::CScriptActorRotate(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                                       const zeus::CVector3f& rotation, float maxTime, bool updateActors,\n                                       bool updateOnCreation, bool active)\n: CEntity(uid, info, active, name)\n, x34_rotation(rotation)\n, x40_maxTime(maxTime)\n, x58_26_updateActors(updateActors)\n, x58_27_updateOnCreation(updateOnCreation) {}\n\nvoid CScriptActorRotate::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptActorRotate::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  if (msg == EScriptObjectMessage::Activate) {\n    CEntity::AcceptScriptMsg(msg, uid, mgr);\n    return;\n  }\n\n  if (msg == EScriptObjectMessage::Action || msg == EScriptObjectMessage::Next ||\n      (msg == EScriptObjectMessage::Registered && x58_27_updateOnCreation)) {\n    UpdateActors(msg == EScriptObjectMessage::Next, mgr);\n  }\n\n  CEntity::AcceptScriptMsg(msg, uid, mgr);\n}\n\nvoid CScriptActorRotate::Think(float dt, CStateManager& mgr) {\n  if (x58_24_updateRotation && GetActive()) {\n    x44_currentTime += dt;\n    if (x44_currentTime >= x40_maxTime) {\n      x58_24_updateRotation = false;\n      x44_currentTime = x40_maxTime;\n    }\n\n    const float timeOffset = x44_currentTime / x40_maxTime;\n\n    for (const auto& actorPair : x48_actors) {\n      if (const TCastToPtr<CActor> act = mgr.ObjectById(actorPair.first)) {\n        const zeus::CTransform xf = zeus::CTransform::RotateX(zeus::degToRad(timeOffset * x34_rotation.x())) *\n                                    zeus::CTransform::RotateY(zeus::degToRad(timeOffset * x34_rotation.y())) *\n                                    zeus::CTransform::RotateZ(zeus::degToRad(timeOffset * x34_rotation.z()));\n        zeus::CTransform localRot = actorPair.second * xf;\n        localRot.origin += act->GetTranslation();\n        act->SetTransform(localRot);\n\n        if (const TCastToPtr<CScriptPlatform> plat = mgr.ObjectById(actorPair.first)) {\n          UpdatePlatformRiders(*plat.GetPtr(), xf, mgr);\n        }\n      }\n    }\n\n    if (!x58_24_updateRotation) {\n      if (x58_25_updateSpiderBallWaypoints) {\n        UpdateSpiderBallWaypoints(mgr);\n      }\n\n      if (x58_26_updateActors) {\n        UpdateActors(false, mgr);\n      }\n    }\n  }\n}\n\nvoid CScriptActorRotate::UpdatePlatformRiders(CScriptPlatform& plat, const zeus::CTransform& xf, CStateManager& mgr) {\n  UpdatePlatformRiders(plat.GetStaticSlaves(), plat, xf, mgr);\n  UpdatePlatformRiders(plat.GetDynamicSlaves(), plat, xf, mgr);\n}\n\nvoid CScriptActorRotate::UpdatePlatformRiders(std::vector<SRiders>& riders, CScriptPlatform& plat,\n                                              const zeus::CTransform& xf, CStateManager& mgr) {\n  for (SRiders& rider : riders) {\n    if (const TCastToPtr<CActor> act = mgr.ObjectById(rider.x0_uid)) {\n      zeus::CTransform& riderXf = rider.x8_transform;\n      act->SetTransform(xf * rider.x8_transform);\n      act->SetTranslation(act->GetTranslation() + plat.GetTranslation());\n      if (!x58_24_updateRotation) {\n        riderXf = {act->GetTransform().basis, act->GetTranslation() - plat.GetTranslation()};\n\n        if (TCastToConstPtr<CScriptSpiderBallWaypoint>(act.GetPtr())) {\n          x58_25_updateSpiderBallWaypoints = true;\n        }\n      }\n\n      if (const TCastToPtr<CScriptPlatform> plat2 = mgr.ObjectById(rider.x0_uid)) {\n        UpdatePlatformRiders(*plat2.GetPtr(), xf, mgr);\n      }\n    }\n  }\n}\n\nvoid CScriptActorRotate::UpdateActors(bool next, CStateManager& mgr) {\n  if (x58_24_updateRotation) {\n    return;\n  }\n\n  x48_actors.clear();\n\n  for (const SConnection& conn : x20_conns) {\n    if (conn.x0_state != EScriptObjectState::Play || conn.x4_msg != EScriptObjectMessage::Play) {\n      continue;\n    }\n\n    auto search = mgr.GetIdListForScript(conn.x8_objId);\n    for (auto it = search.first; it != search.second; ++it) {\n      if (const TCastToConstPtr<CActor> act = mgr.ObjectById(it->second)) {\n        x48_actors.insert_or_assign(it->second, act->GetTransform().getRotation());\n      }\n    }\n  }\n\n  SendScriptMsgs(EScriptObjectState::Play, mgr, EScriptObjectMessage::None);\n\n  if (!x48_actors.empty()) {\n    x58_24_updateRotation = true;\n    x44_currentTime = (next ? x40_maxTime : 0.f);\n  }\n}\n\nvoid CScriptActorRotate::UpdateSpiderBallWaypoints(CStateManager& mgr) {\n  EntityList waypointIds;\n  CObjectList& objectList = mgr.GetAllObjectList();\n  for (CEntity* ent : objectList) {\n    if (const TCastToPtr<CScriptSpiderBallWaypoint> wp = ent) {\n      waypointIds.push_back(wp->GetUniqueId());\n      wp->ClearWaypoints();\n    }\n  }\n\n  for (const TUniqueId& uid : waypointIds) {\n    auto* wp = static_cast<CScriptSpiderBallWaypoint*>(mgr.ObjectById(uid));\n    if (wp) {\n      wp->BuildWaypointListAndBounds(mgr);\n      wp->SetNotInSortedLists(false);\n    }\n  }\n\n  x58_25_updateSpiderBallWaypoints = false;\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptActorRotate.hpp",
    "content": "#pragma once\n\n#include <map>\n#include <string_view>\n#include <vector>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/World/CEntity.hpp\"\n\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nstruct SRiders;\nclass CScriptPlatform;\nclass CScriptActorRotate : public CEntity {\n  zeus::CVector3f x34_rotation;\n  float x40_maxTime;\n  float x44_currentTime = 0.f;\n  std::map<TUniqueId, zeus::CTransform> x48_actors;\n  bool x58_24_updateRotation : 1 = false;\n  bool x58_25_updateSpiderBallWaypoints : 1 = false;\n  bool x58_26_updateActors : 1;\n  bool x58_27_updateOnCreation : 1;\n\n  void UpdateActors(bool, CStateManager&);\n  void UpdateSpiderBallWaypoints(CStateManager&);\n  void UpdatePlatformRiders(CScriptPlatform&, const zeus::CTransform&, CStateManager&);\n  void UpdatePlatformRiders(std::vector<SRiders>&, CScriptPlatform&, const zeus::CTransform&, CStateManager&);\n\npublic:\n  DEFINE_ENTITY\n  CScriptActorRotate(TUniqueId, std::string_view, const CEntityInfo&, const zeus::CVector3f&, float, bool, bool, bool);\n\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void Think(float, CStateManager&) override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptAiJumpPoint.cpp",
    "content": "#include \"Runtime/World/CScriptAiJumpPoint.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/World/CScriptWaypoint.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nCScriptAiJumpPoint::CScriptAiJumpPoint(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                                       zeus::CTransform& xf, bool active, float apex)\n: CActor(uid, active, name, info, xf, CModelData::CModelDataNull(), CMaterialList(EMaterialTypes::NoStepLogic),\n         CActorParameters::None(), kInvalidUniqueId)\n, xe8_apex(apex)\n, xec_touchBounds(xf.origin, xf.origin) {}\n\nvoid CScriptAiJumpPoint::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptAiJumpPoint::Think(float dt, CStateManager&) {\n  if (x110_timeRemaining <= 0) {\n    return;\n  }\n\n  x110_timeRemaining -= dt;\n}\n\nvoid CScriptAiJumpPoint::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId other, CStateManager& mgr) {\n  CActor::AcceptScriptMsg(msg, other, mgr);\n\n  if (msg != EScriptObjectMessage::InitializedInArea) {\n    return;\n  }\n\n  for (const SConnection& conn : x20_conns) {\n    if (conn.x0_state != EScriptObjectState::Arrived || conn.x4_msg != EScriptObjectMessage::Next) {\n      continue;\n    }\n\n    const auto* wpnt = static_cast<const CScriptWaypoint*>(mgr.GetObjectById(mgr.GetIdForScript(conn.x8_objId)));\n    if (wpnt) {\n      x10c_currentWaypoint = wpnt->GetUniqueId();\n      x10e_nextWaypoint = wpnt->NextWaypoint(mgr);\n    }\n  }\n}\n\nstd::optional<zeus::CAABox> CScriptAiJumpPoint::GetTouchBounds() const { return xec_touchBounds; }\n\nbool CScriptAiJumpPoint::GetInUse(TUniqueId uid) const {\n  return x108_24_inUse || x110_timeRemaining > 0.f ||\n         (x10a_occupant != kInvalidUniqueId && uid != kInvalidUniqueId && x10a_occupant != uid);\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptAiJumpPoint.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/World/CActor.hpp\"\n\n#include <zeus/CAABox.hpp>\n\nnamespace metaforce {\nclass CScriptAiJumpPoint : public CActor {\nprivate:\n  float xe8_apex;\n  zeus::CAABox xec_touchBounds;\n  bool x108_24_inUse : 1 = false;\n  TUniqueId x10a_occupant = kInvalidUniqueId;\n  TUniqueId x10c_currentWaypoint = kInvalidUniqueId;\n  TUniqueId x10e_nextWaypoint = kInvalidUniqueId;\n  float x110_timeRemaining = 0.f;\n\npublic:\n  DEFINE_ENTITY\n  CScriptAiJumpPoint(TUniqueId, std::string_view, const CEntityInfo&, zeus::CTransform&, bool, float);\n\n  void Accept(IVisitor& visitor) override;\n  void Think(float, CStateManager&) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void AddToRenderer(const zeus::CFrustum&, CStateManager&) override {}\n  void Render(CStateManager&) override {}\n  std::optional<zeus::CAABox> GetTouchBounds() const override;\n  bool GetInUse(TUniqueId uid) const;\n  TUniqueId GetJumpPoint() const { return x10c_currentWaypoint; }\n  TUniqueId GetJumpTarget() const { return x10e_nextWaypoint; }\n  float GetJumpApex() const { return xe8_apex; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptAreaAttributes.cpp",
    "content": "#include \"Runtime/World/CScriptAreaAttributes.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CEnvFxManager.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCScriptAreaAttributes::CScriptAreaAttributes(TUniqueId uid, const CEntityInfo& info, bool showSkybox, EEnvFxType fxType,\n                                             float envFxDensity, float thermalHeat, float xrayFogDistance,\n                                             float worldLightingLevel, CAssetId skybox, EPhazonType phazonType)\n: CEntity(uid, info, true, std::string())\n, x34_24_showSkybox(showSkybox)\n, x38_envFx(fxType)\n, x3c_envFxDensity(envFxDensity)\n, x40_thermalHeat(thermalHeat)\n, x44_xrayFogDistance(xrayFogDistance)\n, x48_worldLightingLevel(worldLightingLevel)\n, x4c_skybox(skybox)\n, x50_phazon(phazonType) {}\n\nvoid CScriptAreaAttributes::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptAreaAttributes::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) {\n  CEntity::AcceptScriptMsg(msg, objId, stateMgr);\n  if (x4_areaId == kInvalidAreaId) {\n    return;\n  }\n\n  if (msg == EScriptObjectMessage::InitializedInArea) {\n    CGameArea* area = stateMgr.GetWorld()->GetArea(x4_areaId);\n    area->SetAreaAttributes(this);\n    stateMgr.GetEnvFxManager()->SetFxDensity(500, x3c_envFxDensity);\n  } else if (msg == EScriptObjectMessage::Deleted) {\n    CGameArea* area = stateMgr.GetWorld()->GetArea(x4_areaId);\n\n    if (!area->IsPostConstructed()) {\n      return;\n    }\n\n    area->SetAreaAttributes(nullptr);\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptAreaAttributes.hpp",
    "content": "#pragma once\n\n#include \"Runtime/World/CEntity.hpp\"\n#include \"Runtime/World/CEnvFxManager.hpp\"\n\nnamespace metaforce {\nclass CScriptAreaAttributes : public CEntity {\n  bool x34_24_showSkybox : 1;\n  EEnvFxType x38_envFx;\n  float x3c_envFxDensity;\n  float x40_thermalHeat;\n  float x44_xrayFogDistance;\n  float x48_worldLightingLevel;\n  CAssetId x4c_skybox;\n  EPhazonType x50_phazon;\n\npublic:\n  DEFINE_ENTITY\n  CScriptAreaAttributes(TUniqueId uid, const CEntityInfo& info, bool showSkybox, EEnvFxType fxType, float envFxDensity,\n                        float thermalHeat, float xrayFogDistance, float worldLightingLevel, CAssetId skybox,\n                        EPhazonType phazonType);\n\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) override;\n\n  bool GetNeedsSky() const { return x34_24_showSkybox; }\n  bool GetNeedsEnvFx() const { return x38_envFx != EEnvFxType::None; }\n  CAssetId GetSkyModel() const { return x4c_skybox; }\n  EEnvFxType GetEnvFxType() const { return x38_envFx; }\n  float GetEnvFxDensity() const { return x3c_envFxDensity; }\n  float GetThermalHeat() const { return x40_thermalHeat; }\n  float GetXRayFogDistance() const { return x44_xrayFogDistance; }\n  float GetWorldLightingLevel() const { return x48_worldLightingLevel; }\n  EPhazonType GetPhazonType() const { return x50_phazon; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptBallTrigger.cpp",
    "content": "#include \"Runtime/World/CScriptBallTrigger.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/World/CMorphBall.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nstatic zeus::CAABox calculate_ball_aabox() {\n  const float extent = 0.33f * g_tweakPlayer->GetPlayerBallHalfExtent();\n  return {-extent, extent};\n}\n\nCScriptBallTrigger::CScriptBallTrigger(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                                       const zeus::CVector3f& pos, const zeus::CVector3f& scale, bool active, float f1,\n                                       float f2, float f3, const zeus::CVector3f& vec, bool b2)\n: CScriptTrigger(uid, name, info, pos, calculate_ball_aabox(), CDamageInfo(CWeaponMode::Power(), 0.f, 0.f, 0.f),\n                 zeus::skZero3f, ETriggerFlags::DetectMorphedPlayer, active, false, false)\n, x150_force(f1)\n, x154_minAngle(f2)\n, x158_maxDistance(f3)\n, x168_25_stopPlayer(b2) {\n\n  if (vec.canBeNormalized()) {\n    x15c_forceAngle = vec.normalized();\n  }\n}\n\nvoid CScriptBallTrigger::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptBallTrigger::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  if (msg == EScriptObjectMessage::Deactivate && GetActive()) {\n    mgr.GetPlayer().GetMorphBall()->SetBallBoostState(CMorphBall::EBallBoostState::BoostAvailable);\n    x168_24_canApplyForce = false;\n  }\n\n  CScriptTrigger::AcceptScriptMsg(msg, uid, mgr);\n}\n\nvoid CScriptBallTrigger::Think(float dt, CStateManager& mgr) {\n  if (!GetActive()) {\n    return;\n  }\n\n  CScriptTrigger::Think(dt, mgr);\n  CPlayer& player = mgr.GetPlayer();\n\n  if (player.GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Morphed) {\n    x168_24_canApplyForce = false;\n    return;\n  }\n\n  const float ballRadius = player.GetMorphBall()->GetBallRadius();\n  const zeus::CVector3f radiusPosDif =\n      GetTranslation() - (player.GetTranslation() + zeus::CVector3f{0.f, 0.f, ballRadius});\n  const float distance = radiusPosDif.magnitude();\n\n  if (!x168_24_canApplyForce) {\n    if (distance < ballRadius) {\n      x168_24_canApplyForce = true;\n    } else {\n      const zeus::CVector3f offset = radiusPosDif.normalized();\n      if (std::cos(zeus::degToRad(x154_minAngle)) < (-offset).dot(x15c_forceAngle) && distance < x158_maxDistance) {\n        const float force = zeus::min((1.f / dt * distance), x150_force * (x158_maxDistance / (distance * distance)));\n        player.ApplyForceWR(force * (player.GetMass() * offset), zeus::CAxisAngle());\n      }\n    }\n  }\n\n  if (x148_28_playerTriggerProc) {\n    const zeus::CVector3f offset = GetTranslation() - zeus::CVector3f(0.f, 0.f, ballRadius);\n    if (x168_25_stopPlayer) {\n      player.Stop();\n    }\n    player.MoveToWR(offset, dt);\n  }\n}\n\nvoid CScriptBallTrigger::InhabitantAdded(CActor& act, CStateManager& /*mgr*/) {\n  if (const TCastToPtr<CPlayer> player = act) {\n    player->GetMorphBall()->SetBallBoostState(CMorphBall::EBallBoostState::BoostDisabled);\n  }\n}\n\nvoid CScriptBallTrigger::InhabitantExited(CActor& act, CStateManager&) {\n  if (const TCastToPtr<CPlayer> player = act) {\n    player->GetMorphBall()->SetBallBoostState(CMorphBall::EBallBoostState::BoostAvailable);\n    x168_24_canApplyForce = false;\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptBallTrigger.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/World/CScriptTrigger.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CScriptBallTrigger : public CScriptTrigger {\n  float x150_force;\n  float x154_minAngle;\n  float x158_maxDistance;\n  zeus::CVector3f x15c_forceAngle = zeus::skZero3f;\n  bool x168_24_canApplyForce : 1 = false;\n  bool x168_25_stopPlayer : 1;\n\npublic:\n  DEFINE_ENTITY\n  CScriptBallTrigger(TUniqueId, std::string_view, const CEntityInfo&, const zeus::CVector3f&, const zeus::CVector3f&,\n                     bool, float, float, float, const zeus::CVector3f&, bool);\n\n  void Accept(IVisitor&) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void Think(float, CStateManager& mgr) override;\n  void InhabitantAdded(CActor&, CStateManager&) override;\n  void InhabitantExited(CActor&, CStateManager&) override;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptBeam.cpp",
    "content": "#include \"Runtime/World/CScriptBeam.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Particle/CWeaponDescription.hpp\"\n#include \"Runtime/Weapon/CPlasmaProjectile.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCScriptBeam::CScriptBeam(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                         bool active, const TToken<CWeaponDescription>& weaponDesc, const CBeamInfo& bInfo,\n                         const CDamageInfo& dInfo)\n: CActor(uid, active, name, info, xf, CModelData::CModelDataNull(), CMaterialList(), CActorParameters::None(),\n         kInvalidUniqueId)\n, xe8_weaponDescription(weaponDesc)\n, xf4_beamInfo(bInfo)\n, x138_damageInfo(dInfo) {}\n\nvoid CScriptBeam::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptBeam::Think(float dt, CStateManager& mgr) {\n  if (CPlasmaProjectile* proj = static_cast<CPlasmaProjectile*>(mgr.ObjectById(x154_projectileId))) {\n    if (proj->GetActive()) {\n      proj->UpdateFx(x34_transform, dt, mgr);\n    }\n  } else {\n    x154_projectileId = kInvalidUniqueId;\n  }\n}\n\nvoid CScriptBeam::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& mgr) {\n  if (msg == EScriptObjectMessage::Increment) {\n    if (CPlasmaProjectile* proj = static_cast<CPlasmaProjectile*>(mgr.ObjectById(x154_projectileId))) {\n      proj->ResetBeam(mgr, true);\n      proj->Fire(GetTransform(), mgr, false);\n    }\n  } else if (msg == EScriptObjectMessage::Decrement) {\n    if (CPlasmaProjectile* proj = static_cast<CPlasmaProjectile*>(mgr.ObjectById(x154_projectileId))) {\n      if (proj->GetActive()) {\n        proj->ResetBeam(mgr, false);\n      }\n    }\n  } else if (msg == EScriptObjectMessage::Registered) {\n    x154_projectileId = mgr.AllocateUniqueId();\n    mgr.AddObject(new CPlasmaProjectile(xe8_weaponDescription, x10_name + \"-Projectile\",\n                                        x138_damageInfo.GetWeaponMode().GetType(), xf4_beamInfo, x34_transform,\n                                        EMaterialTypes::Projectile, x138_damageInfo, x154_projectileId, x4_areaId,\n                                        GetUniqueId(), {}, false, EProjectileAttrib::PlasmaProjectile));\n  } else if (msg == EScriptObjectMessage::Deleted) {\n    mgr.FreeScriptObject(x154_projectileId);\n  }\n\n  CActor::AcceptScriptMsg(msg, objId, mgr);\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptBeam.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/Weapon/CBeamInfo.hpp\"\n#include \"Runtime/World/CActor.hpp\"\n#include \"Runtime/World/CDamageInfo.hpp\"\n\nnamespace metaforce {\nclass CWeaponDescription;\nclass CScriptBeam : public CActor {\n  TCachedToken<CWeaponDescription> xe8_weaponDescription;\n  CBeamInfo xf4_beamInfo;\n  CDamageInfo x138_damageInfo;\n  TUniqueId x154_projectileId;\n\npublic:\n  DEFINE_ENTITY\n  CScriptBeam(TUniqueId, std::string_view, const CEntityInfo&, const zeus::CTransform&, bool,\n              const TToken<CWeaponDescription>&, const CBeamInfo&, const CDamageInfo&);\n\n  void Accept(IVisitor& visitor) override;\n  void Think(float, CStateManager&) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptCameraBlurKeyframe.cpp",
    "content": "#include \"Runtime/World/CScriptCameraBlurKeyframe.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nCScriptCameraBlurKeyframe::CScriptCameraBlurKeyframe(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                                                     EBlurType type, float amount, u32 unk, float timeIn, float timeOut,\n                                                     bool active)\n: CEntity(uid, info, active, name)\n, x34_type(type)\n, x38_amount(amount)\n, x3c_(unk)\n, x40_timeIn(timeIn)\n, x44_timeOut(timeOut) {}\n\nvoid CScriptCameraBlurKeyframe::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) {\n  CEntity::AcceptScriptMsg(msg, objId, stateMgr);\n\n  switch (msg) {\n  case EScriptObjectMessage::Increment:\n    if (GetActive()) {\n      stateMgr.GetCameraBlurPass(3).SetBlur(x34_type, x38_amount, x40_timeIn, false);\n    }\n    break;\n  case EScriptObjectMessage::Decrement:\n    if (GetActive()) {\n      stateMgr.GetCameraBlurPass(3).DisableBlur(x44_timeOut);\n    }\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CScriptCameraBlurKeyframe::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptCameraBlurKeyframe.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/Camera/CCameraFilter.hpp\"\n#include \"Runtime/World/CEntity.hpp\"\n\nnamespace metaforce {\nclass CScriptCameraBlurKeyframe : public CEntity {\n  EBlurType x34_type;\n  float x38_amount;\n  u32 x3c_;\n  float x40_timeIn;\n  float x44_timeOut;\n\npublic:\n  DEFINE_ENTITY\n  CScriptCameraBlurKeyframe(TUniqueId uid, std::string_view name, const CEntityInfo& info, EBlurType type, float amount,\n                            u32 unk, float timeIn, float timeOut, bool active);\n\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) override;\n  void Accept(IVisitor& visitor) override;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptCameraFilterKeyframe.cpp",
    "content": "#include \"Runtime/World/CScriptCameraFilterKeyframe.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nCScriptCameraFilterKeyframe::CScriptCameraFilterKeyframe(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                                                         EFilterType type, EFilterShape shape, u32 filterIdx, u32 unk,\n                                                         const zeus::CColor& color, float timeIn, float timeOut,\n                                                         CAssetId txtr, bool active)\n: CEntity(uid, info, active, name)\n, x34_type(type)\n, x38_shape(shape)\n, x3c_filterIdx(filterIdx)\n, x40_(unk)\n, x44_color(color)\n, x48_timeIn(timeIn)\n, x4c_timeOut(timeOut)\n, x50_txtr(txtr) {}\n\nvoid CScriptCameraFilterKeyframe::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) {\n  switch (msg) {\n  case EScriptObjectMessage::Increment:\n    if (GetActive()) {\n      stateMgr.GetCameraFilterPass(x3c_filterIdx).SetFilter(x34_type, x38_shape, x48_timeIn, x44_color, x50_txtr);\n    }\n    break;\n  case EScriptObjectMessage::Decrement:\n    if (GetActive()) {\n      stateMgr.GetCameraFilterPass(x3c_filterIdx).DisableFilter(x4c_timeOut);\n    }\n    break;\n  case EScriptObjectMessage::Deactivate:\n    if (GetActive()) {\n      stateMgr.GetCameraFilterPass(x3c_filterIdx).DisableFilter(0.f);\n    }\n    break;\n  default:\n    break;\n  }\n\n  CEntity::AcceptScriptMsg(msg, objId, stateMgr);\n}\n\nvoid CScriptCameraFilterKeyframe::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptCameraFilterKeyframe.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/Camera/CCameraFilter.hpp\"\n#include \"Runtime/World/CEntity.hpp\"\n\n#include <zeus/CColor.hpp>\n\nnamespace metaforce {\nclass CScriptCameraFilterKeyframe : public CEntity {\n  EFilterType x34_type;\n  EFilterShape x38_shape;\n  u32 x3c_filterIdx;\n  u32 x40_;\n  zeus::CColor x44_color;\n  float x48_timeIn;\n  float x4c_timeOut;\n  CAssetId x50_txtr;\n\npublic:\n  DEFINE_ENTITY\n  CScriptCameraFilterKeyframe(TUniqueId uid, std::string_view name, const CEntityInfo& info, EFilterType type,\n                              EFilterShape shape, u32 filterIdx, u32 unk, const zeus::CColor& color, float timeIn,\n                              float timeOut, CAssetId txtr, bool active);\n\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) override;\n  void Accept(IVisitor& visitor) override;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptCameraHint.cpp",
    "content": "#include \"Runtime/World/CScriptCameraHint.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCScriptCameraHint::CScriptCameraHint(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                                     const zeus::CTransform& xf, bool active, s32 priority,\n                                     CBallCamera::EBallCameraBehaviour behaviour, u32 overrideFlags, float minDist,\n                                     float maxDist, float backwardsDist, const zeus::CVector3f& lookAtOffset,\n                                     const zeus::CVector3f& chaseLookAtOffset, const zeus::CVector3f& ballToCam,\n                                     float fov, float attitudeRange, float azimuthRange, float anglePerSecond,\n                                     float clampVelRange, float clampRotRange, float elevation, float interpolateTime,\n                                     float clampVelTime, float controlInterpDur)\n: CActor(uid, active, name, info, xf, CModelData::CModelDataNull(), CMaterialList(EMaterialTypes::NoStepLogic),\n         CActorParameters::None(), kInvalidUniqueId)\n, xe8_priority(priority)\n, xec_hint(overrideFlags, behaviour, minDist, maxDist, backwardsDist, lookAtOffset, chaseLookAtOffset, ballToCam, fov,\n           attitudeRange, azimuthRange, anglePerSecond, clampVelRange, clampRotRange, elevation, interpolateTime,\n           clampVelTime, controlInterpDur)\n, x168_origXf(xf) {}\n\nvoid CScriptCameraHint::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptCameraHint::InitializeInArea(CStateManager& mgr) {\n  x164_delegatedCamera = kInvalidUniqueId;\n  for (CEntity* ent : mgr.GetAllObjectList()) {\n    for (const SConnection& conn : ent->GetConnectionList()) {\n      if (mgr.GetIdForScript(conn.x8_objId) != GetUniqueId()) {\n        continue;\n      }\n      if (conn.x4_msg != EScriptObjectMessage::Increment && conn.x4_msg != EScriptObjectMessage::Decrement) {\n        continue;\n      }\n\n      for (auto it = ent->GetConnectionList().begin(); it != ent->GetConnectionList().end(); ++it) {\n        const SConnection& conn2 = *it;\n        if (conn2.x4_msg != EScriptObjectMessage::Increment && conn2.x4_msg != EScriptObjectMessage::Decrement) {\n          continue;\n        }\n\n        const TUniqueId id = mgr.GetIdForScript(conn2.x8_objId);\n        const auto* const obj = mgr.ObjectById(id);\n        if (TCastToConstPtr<CPathCamera>(obj) || TCastToConstPtr<CScriptSpindleCamera>(obj)) {\n          it = ent->GetConnectionList().erase(it);\n          if (x164_delegatedCamera != id) {\n            x164_delegatedCamera = id;\n          }\n          break;\n        }\n      }\n      break;\n    }\n  }\n}\n\nvoid CScriptCameraHint::AddHelper(TUniqueId id) {\n  const auto search =\n      std::find_if(x150_helpers.cbegin(), x150_helpers.cend(), [id](TUniqueId tid) { return tid == id; });\n\n  if (search != x150_helpers.end()) {\n    return;\n  }\n\n  x150_helpers.push_back(id);\n}\n\nvoid CScriptCameraHint::RemoveHelper(TUniqueId id) {\n  const auto search =\n      std::find_if(x150_helpers.cbegin(), x150_helpers.cend(), [id](TUniqueId tid) { return tid == id; });\n\n  if (search != x150_helpers.cend()) {\n    x150_helpers.erase(search);\n  } else {\n    x150_helpers.pop_front();\n  }\n}\n\nvoid CScriptCameraHint::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) {\n  switch (msg) {\n  case EScriptObjectMessage::Deleted:\n  case EScriptObjectMessage::Deactivate:\n    mgr.GetCameraManager()->DeleteCameraHint(GetUniqueId(), mgr);\n    break;\n  case EScriptObjectMessage::InitializedInArea:\n    InitializeInArea(mgr);\n    break;\n  default:\n    break;\n  }\n\n  if (GetActive()) {\n    switch (msg) {\n    case EScriptObjectMessage::Increment:\n      AddHelper(sender);\n      mgr.GetCameraManager()->AddActiveCameraHint(GetUniqueId(), mgr);\n      x166_inactive = false;\n      break;\n    case EScriptObjectMessage::Decrement:\n      RemoveHelper(sender);\n      mgr.GetCameraManager()->AddInactiveCameraHint(GetUniqueId(), mgr);\n      break;\n    default:\n      break;\n    }\n  }\n\n  if (msg == EScriptObjectMessage::Follow) {\n    if (!GetActive()) {\n      if (const TCastToConstPtr<CActor> act = mgr.GetObjectById(sender)) {\n        zeus::CVector3f followerToThisFlat = x168_origXf.origin - act->GetTranslation();\n        followerToThisFlat.z() = 0.f;\n        if (followerToThisFlat.canBeNormalized()) {\n          followerToThisFlat.normalize();\n        } else {\n          followerToThisFlat = act->GetTransform().basis[1];\n        }\n        zeus::CVector3f target = act->GetTranslation() + followerToThisFlat;\n        target.z() = x168_origXf.origin.z() + followerToThisFlat.z();\n        SetTransform(zeus::lookAt(act->GetTranslation(), target));\n      }\n    }\n    AddHelper(sender);\n    mgr.GetCameraManager()->AddActiveCameraHint(GetUniqueId(), mgr);\n  }\n\n  CActor::AcceptScriptMsg(msg, sender, mgr);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptCameraHint.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Camera/CBallCamera.hpp\"\n#include \"Runtime/World/CActor.hpp\"\n\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\n\nclass CCameraOverrideInfo {\n  u32 x4_overrideFlags;\n  CBallCamera::EBallCameraBehaviour x8_behaviour;\n  float xc_minDist;\n  float x10_maxDist;\n  float x14_backwardsDist;\n  zeus::CVector3f x18_lookAtOffset;\n  zeus::CVector3f x24_chaseLookAtOffset;\n  zeus::CVector3f x30_ballToCam;\n  float x3c_fov;\n  float x40_attitudeRange;\n  float x44_azimuthRange;\n  float x48_anglePerSecond;\n  float x4c_clampVelRange;\n  float x50_clampRotRange;\n  float x54_elevation;\n  float x58_interpolateTime;\n  float x5c_clampVelTime;\n  float x60_controlInterpDur;\n\npublic:\n  CCameraOverrideInfo(u32 overrideFlags, CBallCamera::EBallCameraBehaviour behaviour, float minDist, float maxDist,\n                      float backwardsDist, const zeus::CVector3f& lookAtOffset,\n                      const zeus::CVector3f& chaseLookAtOffset, const zeus::CVector3f& ballToCam, float fov,\n                      float attitudeRange, float azimuthRange, float anglePerSecond, float clampVelRange,\n                      float clampRotRange, float elevation, float interpolateTime, float clampVelTime,\n                      float controlInterpDur)\n  : x4_overrideFlags(overrideFlags)\n  , x8_behaviour(behaviour)\n  , xc_minDist(minDist)\n  , x10_maxDist(maxDist)\n  , x14_backwardsDist(backwardsDist)\n  , x18_lookAtOffset(lookAtOffset)\n  , x24_chaseLookAtOffset(chaseLookAtOffset)\n  , x30_ballToCam(ballToCam)\n  , x3c_fov(fov)\n  , x40_attitudeRange(attitudeRange)\n  , x44_azimuthRange(azimuthRange)\n  , x48_anglePerSecond(anglePerSecond)\n  , x4c_clampVelRange(clampVelRange)\n  , x50_clampRotRange(clampRotRange)\n  , x54_elevation(elevation)\n  , x58_interpolateTime(interpolateTime)\n  , x5c_clampVelTime(clampVelTime)\n  , x60_controlInterpDur(controlInterpDur) {}\n\n  u32 GetOverrideFlags() const { return x4_overrideFlags; }\n  CBallCamera::EBallCameraBehaviour GetBehaviourType() const { return x8_behaviour; }\n  float GetMinDist() const { return xc_minDist; }\n  float GetMaxDist() const { return x10_maxDist; }\n  float GetBackwardsDist() const { return x14_backwardsDist; }\n  const zeus::CVector3f& GetLookAtOffset() const { return x18_lookAtOffset; }\n  const zeus::CVector3f& GetChaseLookAtOffset() const { return x24_chaseLookAtOffset; }\n  const zeus::CVector3f& GetBallToCam() const { return x30_ballToCam; }\n  float GetFov() const { return x3c_fov; }\n  float GetAttitudeRange() const { return x40_attitudeRange; }\n  float GetAzimuthRange() const { return x44_azimuthRange; }\n  float GetAnglePerSecond() const { return x48_anglePerSecond; }\n  float GetClampVelRange() const { return x4c_clampVelRange; }\n  float GetClampRotRange() const { return x50_clampRotRange; }\n  float GetElevation() const { return x54_elevation; }\n  float GetInterpolateTime() const { return x58_interpolateTime; }\n  float GetClampVelTime() const { return x5c_clampVelTime; }\n  float GetControlInterpDur() const { return x60_controlInterpDur; }\n};\n\nclass CScriptCameraHint : public CActor {\n  s32 xe8_priority;\n  CCameraOverrideInfo xec_hint;\n  rstl::reserved_vector<TUniqueId, 8> x150_helpers;\n  TUniqueId x164_delegatedCamera = kInvalidUniqueId;\n  bool x166_inactive = false;\n  zeus::CTransform x168_origXf;\n  void InitializeInArea(CStateManager& mgr);\n  void AddHelper(TUniqueId id);\n  void RemoveHelper(TUniqueId id);\n\npublic:\n  DEFINE_ENTITY\n  CScriptCameraHint(TUniqueId, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf, bool active,\n                    s32 priority, CBallCamera::EBallCameraBehaviour behaviour, u32 overrideFlags, float minDist,\n                    float maxDist, float backwardsDist, const zeus::CVector3f& lookAtOffset,\n                    const zeus::CVector3f& chaseLookAtOffset, const zeus::CVector3f& ballToCam, float fov,\n                    float attitudeRange, float azimuthRange, float anglePerSecond, float clampVelRange,\n                    float clampRotRange, float elevation, float interpolateTime, float clampVelTime,\n                    float controlInterpDur);\n\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) override;\n\n  void ClearIdList() { x150_helpers.clear(); }\n  void SetInactive(bool inactive) { x166_inactive = inactive; }\n  bool GetInactive() const { return x166_inactive; }\n  size_t GetHelperCount() const { return x150_helpers.size(); }\n  TUniqueId GetFirstHelper() const { return x150_helpers.empty() ? kInvalidUniqueId : x150_helpers[0]; }\n  s32 GetPriority() const { return xe8_priority; }\n  const CCameraOverrideInfo& GetHint() const { return xec_hint; }\n  TUniqueId GetDelegatedCamera() const { return x164_delegatedCamera; }\n  const zeus::CTransform& GetOriginalTransform() const { return x168_origXf; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptCameraHintTrigger.cpp",
    "content": "#include \"Runtime/World/CScriptCameraHintTrigger.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCScriptCameraHintTrigger::CScriptCameraHintTrigger(TUniqueId uid, bool active, std::string_view name,\n                                                   const CEntityInfo& info, const zeus::CVector3f& scale,\n                                                   const zeus::CTransform& xf, bool deactivateOnEnter,\n                                                   bool deactivateOnExit)\n: CActor(uid, active, name, info, xf, CModelData::CModelDataNull(), CMaterialList(EMaterialTypes::Trigger),\n         CActorParameters::None(), kInvalidUniqueId)\n, xe8_obb(xf, scale)\n, x124_scale(scale)\n, x130_24_deactivateOnEnter(deactivateOnEnter)\n, x130_25_deactivateOnExit(deactivateOnExit) {}\n\nvoid CScriptCameraHintTrigger::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptCameraHintTrigger::Think(float dt, CStateManager& mgr) {\n  if (!GetActive()) {\n    return;\n  }\n\n  if (x130_26_playerInside && !x130_27_playerWasInside) {\n    x130_27_playerWasInside = true;\n    SendScriptMsgs(EScriptObjectState::Entered, mgr, EScriptObjectMessage::None);\n    if (x130_24_deactivateOnEnter) {\n      mgr.SendScriptMsg(this, kInvalidUniqueId, EScriptObjectMessage::Deactivate);\n    }\n  }\n\n  if (!x130_26_playerInside && x130_27_playerWasInside) {\n    x130_27_playerWasInside = false;\n    SendScriptMsgs(EScriptObjectState::Exited, mgr, EScriptObjectMessage::None);\n    if (x130_25_deactivateOnExit) {\n      mgr.SendScriptMsg(this, kInvalidUniqueId, EScriptObjectMessage::Deactivate);\n    }\n  }\n\n  if (x130_26_playerInside) {\n    SendScriptMsgs(EScriptObjectState::Inside, mgr, EScriptObjectMessage::None);\n  }\n\n  x130_26_playerInside = false;\n}\n\nvoid CScriptCameraHintTrigger::Touch(CActor& other, CStateManager& mgr) {\n  if (TCastToConstPtr<CPlayer>(other)) {\n    if (const auto tb = other.GetTouchBounds()) {\n      x130_26_playerInside = xe8_obb.OBBIntersectsBox(zeus::COBBox::FromAABox(*tb, {}));\n    }\n  }\n}\n\nstd::optional<zeus::CAABox> CScriptCameraHintTrigger::GetTouchBounds() const { return {xe8_obb.calculateAABox()}; }\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptCameraHintTrigger.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/World/CActor.hpp\"\n\n#include <zeus/COBBox.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CScriptCameraHintTrigger : public CActor {\n  zeus::COBBox xe8_obb;\n  zeus::CVector3f x124_scale;\n  bool x130_24_deactivateOnEnter : 1;\n  bool x130_25_deactivateOnExit : 1;\n  bool x130_26_playerInside : 1 = false;\n  bool x130_27_playerWasInside : 1 = false;\n\npublic:\n  DEFINE_ENTITY\n  CScriptCameraHintTrigger(TUniqueId uid, bool active, std::string_view name, const CEntityInfo& info,\n                           const zeus::CVector3f& scale, const zeus::CTransform& xf, bool deactivateOnEnter,\n                           bool deactivateOnExit);\n\n  void Accept(IVisitor& visitor) override;\n  void Think(float dt, CStateManager& mgr) override;\n  void Touch(CActor& other, CStateManager& mgr) override;\n  std::optional<zeus::CAABox> GetTouchBounds() const override;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptCameraPitchVolume.cpp",
    "content": "#include \"Runtime/World/CScriptCameraPitchVolume.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Camera/CCameraManager.hpp\"\n#include \"Runtime/Camera/CFirstPersonCamera.hpp\"\n#include \"Runtime/Particle/CGenDescription.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nconst zeus::CVector3f CScriptCameraPitchVolume::skScaleFactor = zeus::CVector3f(0.5f);\n\nCScriptCameraPitchVolume::CScriptCameraPitchVolume(TUniqueId uid, bool active, std::string_view name,\n                                                   const CEntityInfo& info, const zeus::CVector3f& scale,\n                                                   const zeus::CTransform& xf, const zeus::CRelAngle& upPitch,\n                                                   const zeus::CRelAngle& downPitch, float maxInterpDistance)\n: CActor(uid, active, name, info, xf, CModelData::CModelDataNull(), CMaterialList(EMaterialTypes::Trigger),\n         CActorParameters::None(), kInvalidUniqueId)\n, xe8_obbox(xf, scale * skScaleFactor)\n, x124_upPitch(upPitch)\n, x128_downPitch(downPitch)\n, x12c_scale(scale * skScaleFactor)\n, x138_maxInterpDistance(maxInterpDistance) {}\n\nvoid CScriptCameraPitchVolume::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptCameraPitchVolume::Think(float, CStateManager& mgr) {\n  if (!GetActive()) {\n    return;\n  }\n\n  if (x13c_24_entered && !x13c_25_occupied) {\n    Entered(mgr);\n  } else if (!x13c_24_entered && x13c_25_occupied) {\n    Exited(mgr);\n  }\n\n  x13c_24_entered = false;\n}\n\nstd::optional<zeus::CAABox> CScriptCameraPitchVolume::GetTouchBounds() const {\n  return {xe8_obbox.calculateAABox(zeus::CTransform())};\n}\n\nvoid CScriptCameraPitchVolume::Touch(CActor& act, CStateManager& mgr) {\n  const TCastToConstPtr<CPlayer> pl(act);\n  if (!pl) {\n    return;\n  }\n\n  const auto plBox = pl->GetTouchBounds();\n  if (!plBox) {\n    return;\n  }\n\n  x13c_24_entered = xe8_obbox.AABoxIntersectsBox(plBox.value());\n}\n\nvoid CScriptCameraPitchVolume::Entered(metaforce::CStateManager& mgr) {\n  x13c_25_occupied = true;\n  mgr.GetCameraManager()->GetFirstPersonCamera()->SetScriptPitchId(GetUniqueId());\n}\n\nvoid CScriptCameraPitchVolume::Exited(CStateManager& mgr) {\n  x13c_25_occupied = false;\n  mgr.GetCameraManager()->GetFirstPersonCamera()->SetScriptPitchId(kInvalidUniqueId);\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptCameraPitchVolume.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/World/CActor.hpp\"\n\n#include <zeus/COBBox.hpp>\n#include <zeus/CRelAngle.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CScriptCameraPitchVolume : public CActor {\n  static const zeus::CVector3f skScaleFactor;\n  zeus::COBBox xe8_obbox;\n  zeus::CRelAngle x124_upPitch;\n  zeus::CRelAngle x128_downPitch;\n  zeus::CVector3f x12c_scale;\n  float x138_maxInterpDistance;\n  bool x13c_24_entered : 1 = false;\n  bool x13c_25_occupied : 1 = false;\n\npublic:\n  DEFINE_ENTITY\n  CScriptCameraPitchVolume(TUniqueId, bool, std::string_view, const CEntityInfo&, const zeus::CVector3f&,\n                           const zeus::CTransform&, const zeus::CRelAngle&, const zeus::CRelAngle&, float);\n\n  void Accept(IVisitor& visitor) override;\n  void Think(float, CStateManager&) override;\n  std::optional<zeus::CAABox> GetTouchBounds() const override;\n  void Touch(CActor&, CStateManager&) override;\n  float GetUpPitch() const { return x124_upPitch; }\n  float GetDownPitch() const { return x128_downPitch; }\n  const zeus::CVector3f& GetScale() const { return x12c_scale; }\n  float GetMaxInterpolationDistance() const { return x138_maxInterpDistance; }\n  void Entered(CStateManager&);\n  void Exited(CStateManager&);\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptCameraShaker.cpp",
    "content": "#include \"Runtime/World/CScriptCameraShaker.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCScriptCameraShaker::CScriptCameraShaker(TUniqueId uid, std::string_view name, const CEntityInfo& info, bool active,\n                                         const CCameraShakeData& shakeData)\n: CEntity(uid, info, active, name), x34_shakeData(shakeData) {}\n\nvoid CScriptCameraShaker::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptCameraShaker::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) {\n  switch (msg) {\n  case EScriptObjectMessage::Action: {\n    TAreaId aid = GetAreaIdAlways();\n    if (GetActive() && aid != kInvalidAreaId) {\n      const CGameArea* area = stateMgr.GetWorld()->GetAreaAlways(aid);\n      CGameArea::EOcclusionState occState = CGameArea::EOcclusionState::Occluded;\n      if (area->IsPostConstructed())\n        occState = area->GetPostConstructed()->x10dc_occlusionState;\n      if (occState == CGameArea::EOcclusionState::Visible)\n        x34_shakeData.SetShakerId(stateMgr.GetCameraManager()->AddCameraShaker(x34_shakeData, false));\n    }\n    break;\n  }\n  case EScriptObjectMessage::Deactivate: {\n    if (GetActive())\n      stateMgr.GetCameraManager()->RemoveCameraShaker(x34_shakeData.GetShakerId());\n    break;\n  }\n  default:\n    break;\n  }\n  CEntity::AcceptScriptMsg(msg, objId, stateMgr);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptCameraShaker.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/Camera/CCameraShakeData.hpp\"\n#include \"Runtime/World/CEntity.hpp\"\n\nnamespace metaforce {\n\nclass CScriptCameraShaker : public CEntity {\n  CCameraShakeData x34_shakeData;\n\npublic:\n  DEFINE_ENTITY\n  CScriptCameraShaker(TUniqueId uid, std::string_view name, const CEntityInfo& info, bool active,\n                      const CCameraShakeData& shakeData);\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptCameraWaypoint.cpp",
    "content": "#include \"Runtime/World/CScriptCameraWaypoint.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCScriptCameraWaypoint::CScriptCameraWaypoint(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                                             const zeus::CTransform& xf, bool active, float hfov, u32 w1)\n: CActor(uid, active, name, info, xf, CModelData::CModelDataNull(), CMaterialList(EMaterialTypes::NoStepLogic),\n         CActorParameters::None(), kInvalidUniqueId)\n, xe8_hfov(hfov)\n, xec_(w1) {}\n\nvoid CScriptCameraWaypoint::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptCameraWaypoint::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  CActor::AcceptScriptMsg(msg, uid, mgr);\n  if (GetActive() && msg == EScriptObjectMessage::Arrived)\n    SendScriptMsgs(EScriptObjectState::Arrived, mgr, EScriptObjectMessage::None);\n}\n\nTUniqueId CScriptCameraWaypoint::GetRandomNextWaypointId(CStateManager& mgr) const {\n  std::vector<TUniqueId> candidateIds;\n  for (const SConnection& conn : x20_conns) {\n    if (conn.x0_state == EScriptObjectState::Arrived && conn.x4_msg == EScriptObjectMessage::Next) {\n      TUniqueId uid = mgr.GetIdForScript(conn.x8_objId);\n      if (uid == kInvalidUniqueId)\n        continue;\n      candidateIds.push_back(uid);\n    }\n  }\n\n  if (candidateIds.empty())\n    return kInvalidUniqueId;\n\n  return candidateIds[mgr.GetActiveRandom()->Range(0, s32(candidateIds.size() - 1))];\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptCameraWaypoint.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/World/CActor.hpp\"\n\nnamespace metaforce {\n\nclass CScriptCameraWaypoint : public CActor {\n  float xe8_hfov;\n  u32 xec_;\n\npublic:\n  DEFINE_ENTITY\n  CScriptCameraWaypoint(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                        bool active, float hfov, u32);\n\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void AddToRenderer(const zeus::CFrustum&, CStateManager&) override {}\n  void Render(CStateManager&) override {}\n  TUniqueId GetRandomNextWaypointId(CStateManager& mgr) const;\n  float GetHFov() const { return xe8_hfov; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptColorModulate.cpp",
    "content": "#include \"Runtime/World/CScriptColorModulate.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Graphics/CModel.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nCScriptColorModulate::CScriptColorModulate(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                                           const zeus::CColor& colorA, const zeus::CColor& colorB, EBlendMode blendMode,\n                                           float timeA2B, float timeB2A, bool doReverse, bool resetTargetWhenDone,\n                                           bool depthCompare, bool depthUpdate, bool depthBackwards, bool active)\n: CEntity(uid, info, active, name)\n, x40_colorA(colorA)\n, x44_colorB(colorB)\n, x48_blendMode(blendMode)\n, x4c_timeA2B(timeA2B)\n, x50_timeB2A(timeB2A)\n, x54_24_doReverse(doReverse)\n, x54_25_resetTargetWhenDone(resetTargetWhenDone)\n, x54_26_depthCompare(depthCompare)\n, x54_27_depthUpdate(depthUpdate)\n, x54_28_depthBackwards(depthBackwards) {}\n\nvoid CScriptColorModulate::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptColorModulate::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& mgr) {\n  CEntity::AcceptScriptMsg(msg, objId, mgr);\n\n  if (!GetActive()) {\n    return;\n  }\n\n  switch (msg) {\n  case EScriptObjectMessage::Increment:\n    if (x54_29_reversing) {\n      x38_fadeState = x38_fadeState == EFadeState::A2B ? EFadeState::B2A : EFadeState::A2B;\n      x54_29_reversing = false;\n      return;\n    }\n    if (x54_30_enable) {\n      if (x38_fadeState == EFadeState::A2B) {\n        x3c_curTime = 0.f;\n      } else {\n        x3c_curTime = x4c_timeA2B - x4c_timeA2B * (x3c_curTime / x50_timeB2A);\n      }\n    } else {\n      SetTargetFlags(mgr, CalculateFlags(x40_colorA));\n    }\n    x54_30_enable = true;\n    x38_fadeState = EFadeState::A2B;\n    break;\n  case EScriptObjectMessage::Decrement:\n    if (x54_29_reversing) {\n      x38_fadeState = x38_fadeState == EFadeState::A2B ? EFadeState::B2A : EFadeState::A2B;\n      x54_29_reversing = false;\n      return;\n    }\n    if (x54_30_enable) {\n      if (x38_fadeState == EFadeState::A2B) {\n        x3c_curTime = 0.f;\n      } else {\n        x3c_curTime = x50_timeB2A - x50_timeB2A * (x3c_curTime / x4c_timeA2B);\n      }\n    } else {\n      SetTargetFlags(mgr, CalculateFlags(x44_colorB));\n    }\n    x54_30_enable = true;\n    x38_fadeState = EFadeState::B2A;\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CScriptColorModulate::Think(float dt, CStateManager& mgr) {\n  if (!GetActive() || !x54_30_enable) {\n    return;\n  }\n\n  x3c_curTime += dt;\n  if (x38_fadeState == EFadeState::B2A) {\n    float t = 1.f;\n    if (!zeus::close_enough(x50_timeB2A, 0.f)) {\n      t = std::min(1.f, x3c_curTime / x50_timeB2A);\n    }\n\n    zeus::CColor lerpedCol = zeus::CColor::lerp(x44_colorB, x40_colorA, t);\n    CModelFlags flags = CalculateFlags(lerpedCol);\n    SetTargetFlags(mgr, flags);\n\n    if (x3c_curTime > x50_timeB2A) {\n      End(mgr);\n    }\n  } else {\n    float t = 1.f;\n    if (!zeus::close_enough(x4c_timeA2B, 0.f)) {\n      t = std::min(1.f, x3c_curTime / x4c_timeA2B);\n    }\n\n    zeus::CColor lerpedCol = zeus::CColor::lerp(x40_colorA, x44_colorB, t);\n    CModelFlags flags = CalculateFlags(lerpedCol);\n    SetTargetFlags(mgr, flags);\n\n    if (x3c_curTime > x4c_timeA2B) {\n      End(mgr);\n    }\n  }\n}\n\nCModelFlags CScriptColorModulate::CalculateFlags(const zeus::CColor& col) const {\n  if (x54_28_depthBackwards) {\n    if (x48_blendMode == EBlendMode::Alpha) {\n      return {5, 0, static_cast<u16>(x54_26_depthCompare << 0 | x54_27_depthUpdate << 1 | 0x8), col};\n    } else if (x48_blendMode == EBlendMode::Additive) {\n      return {7, 0, static_cast<u16>(x54_26_depthCompare << 0 | x54_27_depthUpdate << 1 | 0x8), col};\n    } else if (x48_blendMode == EBlendMode::Additive2) {\n      return {8, 0, static_cast<u16>(x54_26_depthCompare << 0 | x54_27_depthUpdate << 1 | 0x8), col};\n    } else if (x48_blendMode == EBlendMode::Opaque) {\n      return {1, 0, static_cast<u16>(x54_26_depthCompare << 0 | x54_27_depthUpdate << 1 | 0x8), col};\n    } else if (x48_blendMode == EBlendMode::OpaqueAdd) {\n      return {2, 0, static_cast<u16>(x54_26_depthCompare << 0 | x54_27_depthUpdate << 1 | 0x8), col};\n    }\n  } else if (x48_blendMode == EBlendMode::Alpha) {\n    if (col == zeus::skWhite) {\n      return {0, 0, static_cast<u16>(x54_26_depthCompare << 0 | x54_27_depthUpdate << 1), zeus::skWhite};\n    } else {\n      return {5, 0, static_cast<u16>(x54_26_depthCompare << 0 | x54_27_depthUpdate << 1), col};\n    }\n  } else if (x48_blendMode == EBlendMode::Additive) {\n    return {7, 0, static_cast<u16>(x54_26_depthCompare << 0 | x54_27_depthUpdate << 1), col};\n  } else if (x48_blendMode == EBlendMode::Additive2) {\n    return {8, 0, static_cast<u16>(x54_26_depthCompare << 0 | x54_27_depthUpdate << 1), col};\n  } else if (x48_blendMode == EBlendMode::Opaque) {\n    if (col == zeus::skWhite) {\n      return {0, 0, static_cast<u16>(x54_26_depthCompare << 0 | x54_27_depthUpdate << 1), zeus::skWhite};\n    } else {\n      return {1, 0, static_cast<u16>(x54_26_depthCompare << 0 | x54_27_depthUpdate << 1), col};\n    }\n  } else if (x48_blendMode == EBlendMode::OpaqueAdd) {\n    return {2, 0, static_cast<u16>(x54_26_depthCompare << 0 | x54_27_depthUpdate << 1), col};\n  }\n  return {0, 0, 3, zeus::skWhite};\n}\n\nvoid CScriptColorModulate::SetTargetFlags(CStateManager& stateMgr, const CModelFlags& flags) {\n  for (const SConnection& conn : x20_conns) {\n    if (conn.x0_state != EScriptObjectState::Play || conn.x4_msg != EScriptObjectMessage::Activate) {\n      continue;\n    }\n\n    auto search = stateMgr.GetIdListForScript(conn.x8_objId);\n    for (auto it = search.first; it != search.second; ++it) {\n      if (TCastToPtr<CActor> act = stateMgr.ObjectById(it->second)) {\n        act->SetDrawFlags(flags);\n      }\n    }\n  }\n\n  if (x34_parent != kInvalidUniqueId) {\n    if (TCastToPtr<CActor> act = stateMgr.ObjectById(x34_parent)) {\n      act->SetDrawFlags(flags);\n    }\n  }\n}\n\nTUniqueId CScriptColorModulate::FadeOutHelper(CStateManager& mgr, TUniqueId parent, float fadeTime) {\n  TAreaId aId = mgr.GetNextAreaId();\n  if (const auto* ent = mgr.GetObjectById(parent)) {\n    aId = ent->GetAreaIdAlways();\n  }\n\n  TUniqueId ret = mgr.AllocateUniqueId();\n  auto* colMod = new CScriptColorModulate(ret, \"\", CEntityInfo(aId, CEntity::NullConnectionList), zeus::skWhite,\n                                          zeus::CColor{1.f, 0.f}, EBlendMode::Alpha, fadeTime, 0.f, false, false, true,\n                                          true, false, true);\n  mgr.AddObject(colMod);\n  colMod->x34_parent = parent;\n  colMod->x54_30_enable = true;\n  colMod->x54_31_dieOnEnd = true;\n  colMod->x55_24_isFadeOutHelper = true;\n\n  colMod->Think(0.f, mgr);\n  return ret;\n}\n\nTUniqueId CScriptColorModulate::FadeInHelper(CStateManager& mgr, TUniqueId parent, float fadeTime) {\n  TAreaId aId = mgr.GetNextAreaId();\n  if (const auto* ent = mgr.GetObjectById(parent)) {\n    aId = ent->GetAreaIdAlways();\n  }\n\n  TUniqueId ret = mgr.AllocateUniqueId();\n  auto* colMod =\n      new CScriptColorModulate(ret, \"\", CEntityInfo(aId, CEntity::NullConnectionList), zeus::CColor{1.f, 0.f},\n                               zeus::skWhite, EBlendMode::Alpha, fadeTime, 0.f, false, true, true, true, false, true);\n  mgr.AddObject(colMod);\n  colMod->x34_parent = parent;\n  colMod->x54_30_enable = true;\n  colMod->x54_31_dieOnEnd = true;\n\n  colMod->Think(0.f, mgr);\n  return ret;\n}\n\nvoid CScriptColorModulate::End(CStateManager& stateMgr) {\n  x3c_curTime = 0.f;\n  if (x54_24_doReverse && !x54_29_reversing) {\n    x54_29_reversing = true;\n    x38_fadeState = x38_fadeState == EFadeState::A2B ? EFadeState::B2A : EFadeState::A2B;\n    return;\n  }\n\n  x54_30_enable = false;\n  x54_29_reversing = false;\n  if (x54_25_resetTargetWhenDone) {\n    SetTargetFlags(stateMgr, CModelFlags(0, 0, 3, zeus::skWhite));\n  }\n\n  if (x55_24_isFadeOutHelper) {\n    stateMgr.SendScriptMsgAlways(x34_parent, x8_uid, EScriptObjectMessage::Deactivate);\n  }\n\n  SendScriptMsgs(EScriptObjectState::MaxReached, stateMgr, EScriptObjectMessage::None);\n\n  if (x54_31_dieOnEnd) {\n    stateMgr.FreeScriptObject(GetUniqueId());\n  }\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptColorModulate.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/World/CEntity.hpp\"\n\n#include <zeus/CColor.hpp>\n\nnamespace metaforce {\nstruct CModelFlags;\n\nclass CScriptColorModulate : public CEntity {\npublic:\n  enum class EBlendMode {\n    Alpha,\n    Additive,\n    Additive2,\n    Opaque,\n    OpaqueAdd,\n  };\n  enum class EFadeState { A2B, B2A };\n\nprivate:\n  TUniqueId x34_parent = kInvalidUniqueId;\n  EFadeState x38_fadeState = EFadeState::A2B;\n  float x3c_curTime = 0.f;\n  zeus::CColor x40_colorA;\n  zeus::CColor x44_colorB;\n  EBlendMode x48_blendMode;\n  float x4c_timeA2B;\n  float x50_timeB2A;\n  bool x54_24_doReverse : 1;\n  bool x54_25_resetTargetWhenDone : 1;\n  bool x54_26_depthCompare : 1;\n  bool x54_27_depthUpdate : 1;\n  bool x54_28_depthBackwards : 1;\n  bool x54_29_reversing : 1 = false;\n  bool x54_30_enable : 1 = false;\n  bool x54_31_dieOnEnd : 1 = false;\n  bool x55_24_isFadeOutHelper : 1 = false;\n\npublic:\n  DEFINE_ENTITY\n  CScriptColorModulate(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CColor& colorA,\n                       const zeus::CColor& colorB, EBlendMode blendMode, float timeA2B, float timeB2A, bool doReverse,\n                       bool resetTargetWhenDone, bool depthCompare, bool depthUpdate, bool depthBackwards, bool active);\n\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) override;\n  void Think(float, CStateManager&) override;\n  CModelFlags CalculateFlags(const zeus::CColor&) const;\n  void SetTargetFlags(CStateManager&, const CModelFlags&);\n  static TUniqueId FadeOutHelper(CStateManager& mgr, TUniqueId obj, float fadetime);\n  static TUniqueId FadeInHelper(CStateManager& mgr, TUniqueId obj, float fadetime);\n  void End(CStateManager&);\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptControllerAction.cpp",
    "content": "#include \"Runtime/World/CScriptControllerAction.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Input/ControlMapper.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCScriptControllerAction::CScriptControllerAction(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                                                 bool active, ControlMapper::ECommands command, bool mapScreenResponse,\n                                                 u32 w1, bool deactivateOnClose)\n: CEntity(uid, info, active, name)\n, x34_command(command)\n, x38_mapScreenSubaction(w1)\n, x3c_24_mapScreenResponse(mapScreenResponse)\n, x3c_25_deactivateOnClose(deactivateOnClose) {}\n\nvoid CScriptControllerAction::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptControllerAction::Think(float, CStateManager& stateMgr) {\n  bool oldPressed = x3c_26_pressed;\n  if (x3c_24_mapScreenResponse) {\n    if (x38_mapScreenSubaction == 0)\n      x3c_26_pressed = stateMgr.GetInMapScreen();\n  } else {\n    x3c_26_pressed = ControlMapper::GetDigitalInput(x34_command, stateMgr.GetFinalInput());\n  }\n\n  if (GetActive() && x3c_26_pressed != oldPressed) {\n    if (x3c_26_pressed) {\n      SendScriptMsgs(EScriptObjectState::Open, stateMgr, EScriptObjectMessage::None);\n    } else {\n      SendScriptMsgs(EScriptObjectState::Closed, stateMgr, EScriptObjectMessage::None);\n      if (x3c_25_deactivateOnClose) {\n        SetActive(false);\n        SendScriptMsgs(EScriptObjectState::Inactive, stateMgr, EScriptObjectMessage::None);\n      }\n    }\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptControllerAction.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/Input/ControlMapper.hpp\"\n#include \"Runtime/World/CEntity.hpp\"\n\nnamespace metaforce {\n\nclass CScriptControllerAction : public CEntity {\n  ControlMapper::ECommands x34_command;\n  u32 x38_mapScreenSubaction;\n  bool x3c_24_mapScreenResponse : 1;\n  bool x3c_25_deactivateOnClose : 1;\n  bool x3c_26_pressed : 1 = false;\n\npublic:\n  DEFINE_ENTITY\n  CScriptControllerAction(TUniqueId uid, std::string_view name, const CEntityInfo& info, bool active,\n                          ControlMapper::ECommands command, bool b1, u32 w1, bool b2);\n  void Accept(IVisitor& visitor) override;\n  void Think(float, CStateManager&) override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptCounter.cpp",
    "content": "#include \"Runtime/World/CScriptCounter.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCScriptCounter::CScriptCounter(TUniqueId uid, std::string_view name, const CEntityInfo& info, s32 initial, s32 max,\n                               bool autoReset, bool active)\n: CEntity(uid, info, active, name)\n, x34_initial(initial)\n, x38_current(initial)\n, x3c_max(max)\n, x40_autoReset(autoReset) {}\n\nvoid CScriptCounter::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptCounter::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) {\n  switch (msg) {\n  case EScriptObjectMessage::SetToZero:\n    if (GetActive()) {\n      x38_current = 0;\n      SendScriptMsgs(EScriptObjectState::Zero, stateMgr, EScriptObjectMessage::None);\n\n      if (x40_autoReset)\n        x38_current = x34_initial;\n    }\n    break;\n  case EScriptObjectMessage::SetToMax:\n    if (GetActive()) {\n      x38_current = x3c_max;\n      SendScriptMsgs(EScriptObjectState::MaxReached, stateMgr, EScriptObjectMessage::None);\n\n      if (x40_autoReset)\n        x38_current = x34_initial;\n    }\n    break;\n  case EScriptObjectMessage::Decrement:\n    if (GetActive() && x38_current > 0) {\n      --x38_current;\n      if (x38_current == 0) {\n        SendScriptMsgs(EScriptObjectState::Zero, stateMgr, EScriptObjectMessage::None);\n        if (x40_autoReset)\n          x38_current = x34_initial;\n      }\n    }\n    break;\n  case EScriptObjectMessage::Increment:\n    if (GetActive() && x38_current < x3c_max) {\n      ++x38_current;\n      if (x38_current >= x3c_max) {\n        SendScriptMsgs(EScriptObjectState::MaxReached, stateMgr, EScriptObjectMessage::None);\n        if (x40_autoReset)\n          x38_current = x34_initial;\n      }\n    }\n    break;\n  case EScriptObjectMessage::Reset:\n    if (GetActive())\n      x38_current = x34_initial;\n    break;\n  default:\n    break;\n  }\n\n  CEntity::AcceptScriptMsg(msg, objId, stateMgr);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptCounter.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/World/CEntity.hpp\"\n\nnamespace metaforce {\n\nclass CScriptCounter : public CEntity {\n  s32 x34_initial;\n  s32 x38_current;\n  s32 x3c_max;\n  bool x40_autoReset;\n\npublic:\n  DEFINE_ENTITY\n  CScriptCounter(TUniqueId, std::string_view name, const CEntityInfo& info, s32, s32, bool, bool);\n\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptCoverPoint.cpp",
    "content": "#include \"Runtime/World/CScriptCoverPoint.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nCScriptCoverPoint::CScriptCoverPoint(TUniqueId uid, std::string_view name, const CEntityInfo& info, zeus::CTransform xf,\n                                     bool active, u32 flags, bool crouch, float horizontalAngle, float verticalAngle,\n                                     float coverTime)\n: CActor(uid, active, name, info, xf, CModelData::CModelDataNull(), CMaterialList(EMaterialTypes::NoStepLogic),\n         CActorParameters::None(), kInvalidUniqueId)\n, xe8_26_landHere((flags & 0x20) != 0u)\n, xe8_27_wallHang((flags & 0x10) != 0u)\n, xe8_28_stay((flags & 0x8) != 0u)\n, xe8_29_((flags & 0x4) != 0u)\n, xe8_30_attackDirection((flags & 0x2) != 0u)\n, xf4_coverTime(coverTime)\n, xf8_24_crouch(crouch) {\n  xec_cosHorizontalAngle = std::cos(zeus::degToRad(horizontalAngle) * 0.5f);\n  xf0_sinVerticalAngle = std::sin(zeus::degToRad(verticalAngle) * 0.5f);\n  x100_touchBounds.emplace(xf.origin, xf.origin);\n}\n\nvoid CScriptCoverPoint::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptCoverPoint::Think(float delta, CStateManager&) {\n  if (x11c_timeLeft <= 0.f)\n    return;\n  x11c_timeLeft -= delta;\n}\n\nstd::optional<zeus::CAABox> CScriptCoverPoint::GetTouchBounds() const {\n  if (x100_touchBounds) {\n    return x100_touchBounds;\n  }\n\n  return std::nullopt;\n}\n\nvoid CScriptCoverPoint::SetInUse(bool inUse) {\n  xf8_25_inUse = inUse;\n  if (inUse)\n    x11c_timeLeft = xf4_coverTime;\n}\n\nbool CScriptCoverPoint::GetInUse(TUniqueId uid) const {\n  if (xf8_25_inUse || x11c_timeLeft > 0.f)\n    return true;\n\n  return !(xfa_occupant == kInvalidUniqueId || uid == kInvalidUniqueId || xfa_occupant == uid);\n}\n\nbool CScriptCoverPoint::Blown(const zeus::CVector3f& point) const {\n  if (!x30_24_active)\n    return true;\n\n  if (ShouldWallHang()) {\n    zeus::CVector3f posDif = point - x34_transform.origin;\n    posDif *= zeus::CVector3f(1.f / posDif.magnitude());\n    zeus::CVector3f normDif = posDif.normalized();\n\n    zeus::CVector3f frontVec = x34_transform.frontVector();\n    frontVec.normalize();\n\n    if (frontVec.dot(normDif) <= GetCosHorizontalAngle() || (posDif.z() * posDif.z()) >= GetSinSqVerticalAngle())\n      return true;\n  }\n  return false;\n}\n\nfloat CScriptCoverPoint::GetSinSqVerticalAngle() const { return xf0_sinVerticalAngle * xf0_sinVerticalAngle; }\n\nvoid CScriptCoverPoint::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  CActor::AcceptScriptMsg(msg, uid, mgr);\n\n  if (msg == EScriptObjectMessage::InitializedInArea) {\n    for (const SConnection& con : x20_conns)\n      if (con.x0_state == EScriptObjectState::Retreat) {\n        xfc_retreating = mgr.GetIdForScript(con.x8_objId);\n        break;\n      }\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptCoverPoint.hpp",
    "content": "#pragma once\n\n#include <optional>\n#include <string_view>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/Character/CharacterCommon.hpp\"\n#include \"Runtime/World/CActor.hpp\"\n\n#include <zeus/CAABox.hpp>\n\nnamespace metaforce {\nclass CScriptCoverPoint : public CActor {\n  bool xe8_26_landHere : 1;\n  bool xe8_27_wallHang : 1;\n  bool xe8_28_stay : 1;\n  bool xe8_29_ : 1;\n  bool xe8_30_attackDirection : 1;\n  float xec_cosHorizontalAngle;\n  float xf0_sinVerticalAngle;\n  float xf4_coverTime;\n  bool xf8_24_crouch : 1;\n  bool xf8_25_inUse : 1 = false;\n  TUniqueId xfa_occupant = kInvalidUniqueId;\n  TUniqueId xfc_retreating = kInvalidUniqueId;\n  std::optional<zeus::CAABox> x100_touchBounds;\n  float x11c_timeLeft = 0.f;\n\npublic:\n  DEFINE_ENTITY\n  CScriptCoverPoint(TUniqueId uid, std::string_view name, const CEntityInfo& info, zeus::CTransform xf, bool active,\n                    u32 flags, bool crouch, float horizontalAngle, float verticalAngle, float coverTime);\n\n  void Accept(IVisitor& visitor) override;\n  void Think(float, CStateManager&) override;\n  void AddToRenderer(const zeus::CFrustum&, CStateManager&) override {}\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void Render(CStateManager&) override {}\n  std::optional<zeus::CAABox> GetTouchBounds() const override;\n  void SetInUse(bool inUse);\n  bool GetInUse(TUniqueId uid) const;\n  bool ShouldLandHere() const { return xe8_26_landHere; }\n  bool ShouldWallHang() const { return xe8_27_wallHang; }\n  bool ShouldStay() const { return xe8_28_stay; }\n  bool ShouldCrouch() const { return xf8_24_crouch; }\n  bool Blown(const zeus::CVector3f& pos) const;\n  float GetSinSqVerticalAngle() const;\n  float GetCosHorizontalAngle() const { return xec_cosHorizontalAngle; }\n  pas::ECoverDirection GetAttackDirection() const {\n    return xe8_30_attackDirection ? pas::ECoverDirection::Right : pas::ECoverDirection::Left;\n  }\n  void Reserve(TUniqueId id) { xfa_occupant = id; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptDamageableTrigger.cpp",
    "content": "#include \"Runtime/World/CScriptDamageableTrigger.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/World/CScriptActor.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nstatic CActorParameters MakeDamageableTriggerActorParms(const CActorParameters& aParams,\n                                                        const CVisorParameters& vParams) {\n  CActorParameters ret = aParams;\n  ret.SetVisorParameters(vParams);\n  return ret;\n}\n\nstatic constexpr CMaterialList MakeDamageableTriggerMaterial(CScriptDamageableTrigger::ECanOrbit canOrbit) {\n  if (canOrbit == CScriptDamageableTrigger::ECanOrbit::Orbit) {\n    return CMaterialList(EMaterialTypes::Orbit, EMaterialTypes::Trigger, EMaterialTypes::Immovable,\n                         EMaterialTypes::NonSolidDamageable, EMaterialTypes::ExcludeFromLineOfSightTest);\n  }\n  return CMaterialList(EMaterialTypes::Trigger, EMaterialTypes::Immovable, EMaterialTypes::NonSolidDamageable,\n                       EMaterialTypes::ExcludeFromLineOfSightTest);\n}\n\nCScriptDamageableTrigger::CScriptDamageableTrigger(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                                                   const zeus::CVector3f& position, const zeus::CVector3f& extent,\n                                                   const CHealthInfo& hInfo, const CDamageVulnerability& dVuln,\n                                                   u32 faceFlag, CAssetId patternTex1, CAssetId patternTex2,\n                                                   CAssetId colorTex, ECanOrbit canOrbit, bool active,\n                                                   const CVisorParameters& vParams)\n: CActor(uid, active, name, info, zeus::CTransform::Translate(position), CModelData::CModelDataNull(),\n         MakeDamageableTriggerMaterial(canOrbit), MakeDamageableTriggerActorParms(CActorParameters::None(), vParams),\n         kInvalidUniqueId)\n, x14c_bounds(-extent * 0.5f, extent * 0.5f)\n, x164_origHInfo(hInfo)\n, x16c_hInfo(hInfo)\n, x174_dVuln(dVuln)\n, x1dc_faceFlag(faceFlag)\n, x254_fluidPlane(patternTex1, patternTex2, colorTex, 1.f, 2, EFluidType::NormalWater, 1.f, CFluidUVMotion(6.f, 0.f))\n, x300_28_canOrbit(canOrbit == ECanOrbit::Orbit) {\n  if (x1dc_faceFlag & 0x1) {\n    x244_faceTranslate = zeus::CVector3f(0.f, x14c_bounds.max.y(), 0.f);\n    x1e4_faceDir = zeus::CTransform::RotateX(-M_PIF / 2.f);\n  } else if (x1dc_faceFlag & 0x2) {\n    x244_faceTranslate = zeus::CVector3f(0.f, x14c_bounds.min.y(), 0.f);\n    x1e4_faceDir = zeus::CTransform::RotateX(M_PIF / 2.f);\n  } else if (x1dc_faceFlag & 0x4) {\n    x244_faceTranslate = zeus::CVector3f(x14c_bounds.min.x(), 0.f, 0.f);\n    x1e4_faceDir = zeus::CTransform::RotateY(-M_PIF / 2.f);\n  } else if (x1dc_faceFlag & 0x8) {\n    x244_faceTranslate = zeus::CVector3f(x14c_bounds.max.x(), 0.f, 0.f);\n    x1e4_faceDir = zeus::CTransform::RotateY(M_PIF / 2.f);\n  } else if (x1dc_faceFlag & 0x10) {\n    x244_faceTranslate = zeus::CVector3f(0.f, 0.f, x14c_bounds.max.z());\n    x1e4_faceDir = zeus::CTransform();\n  } else if (x1dc_faceFlag & 0x20) {\n    x244_faceTranslate = zeus::CVector3f(0.f, 0.f, x14c_bounds.min.z());\n    x1e4_faceDir = zeus::CTransform::RotateY(M_PIF);\n  }\n\n  x214_faceDirInv = x1e4_faceDir.inverse();\n}\n\nvoid CScriptDamageableTrigger::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptDamageableTrigger::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) {\n  switch (msg) {\n  case EScriptObjectMessage::Deactivate:\n    if (x30_24_active && x300_25_alphaOut) {\n      return;\n    }\n    [[fallthrough]];\n  case EScriptObjectMessage::Activate:\n    if (!x30_24_active || x300_25_alphaOut) {\n      x250_alphaTimer = 0.f;\n      x16c_hInfo = x164_origHInfo;\n      x300_25_alphaOut = false;\n      if (x300_28_canOrbit) {\n        AddMaterial(EMaterialTypes::Orbit, mgr);\n      }\n      SetLinkedObjectAlpha(0.f, mgr);\n      x1e0_alpha = 0.f;\n    }\n    break;\n  case EScriptObjectMessage::Damage:\n    if (x300_27_invulnerable) {\n      x16c_hInfo = x164_origHInfo;\n    }\n    break;\n  case EScriptObjectMessage::Increment:\n    x300_27_invulnerable = true;\n    break;\n  case EScriptObjectMessage::Decrement:\n    x300_27_invulnerable = false;\n    break;\n  default:\n    break;\n  }\n\n  CActor::AcceptScriptMsg(msg, sender, mgr);\n}\n\nEWeaponCollisionResponseTypes CScriptDamageableTrigger::GetCollisionResponseType(const zeus::CVector3f&,\n                                                                                 const zeus::CVector3f&,\n                                                                                 const CWeaponMode& weapMode,\n                                                                                 EProjectileAttrib) const {\n  return x174_dVuln.WeaponHurts(weapMode, false) ? EWeaponCollisionResponseTypes::OtherProjectile\n                                                 : EWeaponCollisionResponseTypes::Unknown15;\n}\n\nvoid CScriptDamageableTrigger::Render(CStateManager& mgr) {\n  if (x30_24_active && x1dc_faceFlag != 0 && std::fabs(x1e0_alpha) >= 0.00001f) {\n    const zeus::CAABox aabb = x14c_bounds.getTransformedAABox(x214_faceDirInv);\n    const zeus::CTransform xf = x34_transform * zeus::CTransform::Translate(x244_faceTranslate) * x1e4_faceDir;\n    x254_fluidPlane.Render(mgr, x1e0_alpha, aabb, xf, zeus::CTransform(), false, xe8_frustum, {}, kInvalidUniqueId,\n                           nullptr, 0, 0, zeus::skZero3f);\n  }\n\n  CActor::Render(mgr);\n}\n\nvoid CScriptDamageableTrigger::AddToRenderer(const zeus::CFrustum& /*frustum*/, CStateManager& mgr) {\n  if (x300_26_outOfFrustum) {\n    return;\n  }\n\n  EnsureRendered(mgr, GetTranslation() - x244_faceTranslate, GetSortingBounds(mgr));\n}\n\nvoid CScriptDamageableTrigger::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) {\n  x300_26_outOfFrustum = !frustum.aabbFrustumTest(x14c_bounds.getTransformedAABox(x34_transform));\n\n  if (x300_26_outOfFrustum) {\n    return;\n  }\n\n  xe8_frustum = frustum;\n  CActor::PreRender(mgr, frustum);\n}\n\nvoid CScriptDamageableTrigger::SetLinkedObjectAlpha(float a, CStateManager& mgr) {\n  for (const SConnection& conn : x20_conns) {\n    if (conn.x0_state != EScriptObjectState::MaxReached || conn.x4_msg != EScriptObjectMessage::Activate) {\n      continue;\n    }\n    if (const TCastToPtr<CScriptActor> act = mgr.ObjectById(mgr.GetIdForScript(conn.x8_objId))) {\n      if (!act->GetActive()) {\n        act->SetActive(true);\n      }\n      act->SetDrawFlags(CModelFlags(5, 0, 3, zeus::CColor(1.f, a)));\n    }\n  }\n}\n\nfloat CScriptDamageableTrigger::GetPuddleAlphaScale() const {\n  if (x250_alphaTimer <= 0.75f) {\n    if (x300_25_alphaOut) {\n      return 1.f - x250_alphaTimer / 0.75f;\n    }\n    return x250_alphaTimer / 0.75f;\n  }\n\n  if (x300_25_alphaOut) {\n    return 0.f;\n  }\n  return 1.f;\n}\n\nvoid CScriptDamageableTrigger::Think(float dt, CStateManager& mgr) {\n  if (!GetActive()) {\n    return;\n  }\n\n  const CGameArea* area = mgr.GetWorld()->GetAreaAlways(GetAreaIdAlways());\n  const auto occState = area->IsPostConstructed() ? area->GetOcclusionState() : CGameArea::EOcclusionState::Occluded;\n  x300_24_notOccluded = occState == CGameArea::EOcclusionState::Visible;\n\n  if (x300_25_alphaOut) {\n    if (x250_alphaTimer >= 0.75f) {\n      SetActive(false);\n      for (const SConnection& conn : x20_conns) {\n        if (conn.x0_state != EScriptObjectState::MaxReached || conn.x4_msg != EScriptObjectMessage::Activate) {\n          continue;\n        }\n        if (const TCastToPtr<CScriptActor> act = mgr.ObjectById(mgr.GetIdForScript(conn.x8_objId))) {\n          act->SetActive(false);\n        }\n      }\n\n      SetLinkedObjectAlpha(0.f, mgr);\n      x300_25_alphaOut = false;\n      return;\n    }\n  } else if (x16c_hInfo.GetHP() <= 0.f && x30_24_active) {\n    SendScriptMsgs(EScriptObjectState::Dead, mgr, EScriptObjectMessage::None);\n    RemoveMaterial(EMaterialTypes::Orbit, mgr);\n    x300_25_alphaOut = true;\n    x250_alphaTimer = 0.f;\n  }\n\n  if (x250_alphaTimer <= 0.75f) {\n    x250_alphaTimer += dt;\n  }\n\n  const float objAlpha = GetPuddleAlphaScale();\n  x1e0_alpha = 0.2f * objAlpha;\n  SetLinkedObjectAlpha(objAlpha, mgr);\n}\n\nstd::optional<zeus::CAABox> CScriptDamageableTrigger::GetTouchBounds() const {\n  if (x30_24_active && x300_24_notOccluded) {\n    return zeus::CAABox(x14c_bounds.min + GetTranslation(), x14c_bounds.max + GetTranslation());\n  }\n  return std::nullopt;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptDamageableTrigger.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/World/CActor.hpp\"\n#include \"Runtime/World/CDamageVulnerability.hpp\"\n#include \"Runtime/World/CFluidPlaneDoor.hpp\"\n#include \"Runtime/World/CHealthInfo.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CFrustum.hpp>\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CVisorParameters;\n\nclass CScriptDamageableTrigger : public CActor {\nprivate:\n  zeus::CFrustum xe8_frustum;\n  zeus::CAABox x14c_bounds;\n  CHealthInfo x164_origHInfo;\n  CHealthInfo x16c_hInfo;\n  CDamageVulnerability x174_dVuln;\n  u32 x1dc_faceFlag;\n  float x1e0_alpha = 1.f;\n  zeus::CTransform x1e4_faceDir;\n  zeus::CTransform x214_faceDirInv;\n  zeus::CVector3f x244_faceTranslate;\n  float x250_alphaTimer = 0.f;\n  CFluidPlaneDoor x254_fluidPlane;\n  bool x300_24_notOccluded : 1 = false;\n  bool x300_25_alphaOut : 1 = false;\n  bool x300_26_outOfFrustum : 1 = false;\n  bool x300_27_invulnerable : 1 = false;\n  bool x300_28_canOrbit : 1;\n\n  void SetLinkedObjectAlpha(float a, CStateManager& mgr);\n  float GetPuddleAlphaScale() const;\n\npublic:\n  enum class ECanOrbit {\n    NoOrbit,\n    Orbit,\n  };\n\n  DEFINE_ENTITY\n  CScriptDamageableTrigger(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                           const zeus::CVector3f& position, const zeus::CVector3f& extent, const CHealthInfo& hInfo,\n                           const CDamageVulnerability& dVuln, u32 faceFlag, CAssetId patternTex1, CAssetId patternTex2,\n                           CAssetId colorTex, ECanOrbit canOrbit, bool active, const CVisorParameters& vParams);\n\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  EWeaponCollisionResponseTypes GetCollisionResponseType(const zeus::CVector3f&, const zeus::CVector3f&,\n                                                         const CWeaponMode&, EProjectileAttrib) const override;\n  void Render(CStateManager& mgr) override;\n  void AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) override;\n  void PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) override;\n  const CDamageVulnerability* GetDamageVulnerability() const override { return &x174_dVuln; }\n  CHealthInfo* HealthInfo(CStateManager&) override { return &x16c_hInfo; }\n  void Think(float, CStateManager&) override;\n  std::optional<zeus::CAABox> GetTouchBounds() const override;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptDebris.cpp",
    "content": "#include \"Runtime/World/CScriptDebris.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Collision/CCollisionInfoList.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCScriptDebris::CScriptDebris(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                             CModelData&& mData, const CActorParameters& aParams, CAssetId particleId,\n                             const zeus::CVector3f& particleScale, float zImpulse, const zeus::CVector3f& velocity,\n                             const zeus::CColor& endsColor, float mass, float restitution, float duration,\n                             EScaleType scaleType, bool b1, bool randomAngImpulse, bool active)\n: CPhysicsActor(uid, active, name, info, xf, std::move(mData),\n                CMaterialList(EMaterialTypes::Solid, EMaterialTypes::Debris), mData.GetBounds(xf.getRotation()),\n                SMoverData(mass), aParams, 0.3f, 0.1f)\n, x258_velocity(velocity)\n, x264_color(1.f, 0.5f, 0.5f, 1.f)\n, x268_endsColor(endsColor)\n, x26c_zImpulse(zImpulse)\n, x274_duration(duration >= 0.f ? duration : 0.5f)\n, x278_ooDuration(1.f / x274_duration)\n, x27c_restitution(restitution)\n, x280_scaleType(scaleType)\n, x281_24_randomAngImpulse(randomAngImpulse)\n, x2b0_scale(mData.GetScale())\n, x2e0_speedAvg(2.f) {\n  if (scaleType == EScaleType::NoScale) {\n    x2bc_endScale = mData.GetScale();\n  } else if (scaleType == EScaleType::EndsToZero) {\n    x2bc_endScale = zeus::skZero3f;\n  } else {\n    x2bc_endScale.splat(5.f);\n  }\n\n  xe7_30_doTargetDistanceTest = false;\n\n  if (x90_actorLights) {\n    x90_actorLights->SetAreaUpdateFramePeriod(x90_actorLights->GetAreaUpdateFramePeriod() * 2);\n  }\n\n  SetUseInSortedLists(false);\n\n  SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(\n      {EMaterialTypes::Solid},\n      {EMaterialTypes::Debris, EMaterialTypes::Character, EMaterialTypes::Player, EMaterialTypes::Projectile}));\n\n  if (g_ResFactory->GetResourceTypeById(particleId).IsValid()) {\n    TToken<CGenDescription> desc = g_SimplePool->GetObj({FOURCC('PART'), particleId});\n    x2d4_particleGens[0] = std::make_unique<CElementGen>(desc, CElementGen::EModelOrientationType::Normal,\n                                                         CElementGen::EOptionalSystemFlags::One);\n    x2d4_particleGens[0]->SetGlobalScale(particleScale);\n  }\n\n  x150_momentum = zeus::CVector3f(0.f, 0.f, -CPhysicsActor::GravityConstant() * xe8_mass);\n\n  if (x90_actorLights) {\n    x90_actorLights->SetAmbienceGenerated(true);\n  }\n}\n\nCScriptDebris::CScriptDebris(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                             CModelData&& mData, const CActorParameters& aParams, float linConeAngle, float linMinMag,\n                             float linMaxMag, float angMinMag, float angMaxMag, float minDuration, float maxDuration,\n                             float colorInT, float colorOutT, const zeus::CColor& color, const zeus::CColor& endsColor,\n                             float scaleOutStartT, const zeus::CVector3f& scale, const zeus::CVector3f& endScale,\n                             float restitution, float downwardSpeed, const zeus::CVector3f& localOffset,\n                             CAssetId particle1, const zeus::CVector3f& particle1Scale, bool particle1GlobalTranslation,\n                             bool deferDeleteTillParticle1Done, EOrientationType particle1Or, CAssetId particle2,\n                             const zeus::CVector3f& particle2Scale, bool particle2GlobalTranslation,\n                             bool deferDeleteTillParticle2Done, EOrientationType particle2Or, CAssetId particle3,\n                             const zeus::CVector3f& particle3Scale, EOrientationType particle3Or, bool solid,\n                             bool dieOnProjectile, bool noBounce, bool active)\n: CPhysicsActor(\n      uid, active, name, info, xf, std::move(mData), CMaterialList(EMaterialTypes::Solid, EMaterialTypes::Debris),\n      (mData.HasAnimData() || mData.HasNormalModel() ? mData.GetBounds(xf.getRotation()) : zeus::CAABox{-0.5f, 0.5f}),\n      SMoverData(1.f), aParams, 0.3f, 0.1f)\n, x264_color(color)\n, x268_endsColor(endsColor)\n, x27c_restitution(restitution)\n, x281_24_randomAngImpulse(false)\n, x281_25_particle1GlobalTranslation(particle1GlobalTranslation)\n, x281_26_deferDeleteTillParticle1Done(deferDeleteTillParticle1Done)\n, x281_27_particle2GlobalTranslation(particle2GlobalTranslation)\n, x281_28_deferDeleteTillParticle2Done(deferDeleteTillParticle2Done)\n, x281_30_debrisExtended(true)\n, x281_31_dieOnProjectile(dieOnProjectile)\n, x282_24_noBounce(noBounce)\n, x288_linConeAngle(linConeAngle)\n, x28c_linMinMag(linMinMag)\n, x290_linMaxMag(linMaxMag)\n, x294_angMinMag(angMinMag)\n, x298_angMaxMag(angMaxMag)\n, x29c_minDuration(minDuration)\n, x2a0_maxDuration(maxDuration)\n, x2a4_colorInT(colorInT / 100.f)\n, x2a8_colorOutT(colorOutT / 100.f)\n, x2ac_scaleOutStartT(scaleOutStartT / 100.f)\n, x2b0_scale(scale)\n, x2bc_endScale(scale * endScale)\n, x2e0_speedAvg(2.f) {\n  x283_particleOrs[0] = particle1Or;\n  x283_particleOrs[1] = particle2Or;\n  x283_particleOrs[2] = particle3Or;\n\n  SetUseInSortedLists(false);\n\n  SetTranslation(x34_transform.rotate(localOffset) + x34_transform.origin);\n\n  if (solid) {\n    SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(\n        {EMaterialTypes::Solid}, {EMaterialTypes::Debris, EMaterialTypes::Character, EMaterialTypes::Player}));\n  } else {\n    SetMaterialFilter(CMaterialFilter::MakeIncludeExclude({}, {EMaterialTypes::Debris, EMaterialTypes::Character,\n                                                               EMaterialTypes::Player, EMaterialTypes::Projectile,\n                                                               EMaterialTypes::Solid}));\n  }\n\n  if (g_ResFactory->GetResourceTypeById(particle1).IsValid()) {\n    TToken<CGenDescription> desc = g_SimplePool->GetObj({FOURCC('PART'), particle1});\n    x2d4_particleGens[0] = std::make_unique<CElementGen>(desc, CElementGen::EModelOrientationType::Normal,\n                                                         CElementGen::EOptionalSystemFlags::One);\n    x2d4_particleGens[0]->SetGlobalScale(particle1Scale);\n  }\n\n  if (g_ResFactory->GetResourceTypeById(particle2).IsValid()) {\n    TToken<CGenDescription> desc = g_SimplePool->GetObj({FOURCC('PART'), particle2});\n    x2d4_particleGens[1] = std::make_unique<CElementGen>(desc, CElementGen::EModelOrientationType::Normal,\n                                                         CElementGen::EOptionalSystemFlags::One);\n    x2d4_particleGens[1]->SetGlobalScale(particle2Scale);\n  }\n\n  if (g_ResFactory->GetResourceTypeById(particle3).IsValid()) {\n    TToken<CGenDescription> desc = g_SimplePool->GetObj({FOURCC('PART'), particle3});\n    x2d4_particleGens[2] = std::make_unique<CElementGen>(desc, CElementGen::EModelOrientationType::Normal,\n                                                         CElementGen::EOptionalSystemFlags::One);\n    x2d4_particleGens[2]->SetGlobalScale(particle3Scale);\n  }\n\n  x150_momentum = zeus::CVector3f(0.f, 0.f, -downwardSpeed * xe8_mass);\n}\n\nvoid CScriptDebris::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptDebris::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) {\n  if (x2d4_particleGens[0]) {\n    if (x270_curTime < x274_duration || x281_26_deferDeleteTillParticle1Done) {\n      g_Renderer->AddParticleGen(*x2d4_particleGens[0]);\n    }\n  }\n\n  if (x2d4_particleGens[1]) {\n    if (x270_curTime < x274_duration || x281_28_deferDeleteTillParticle2Done) {\n      g_Renderer->AddParticleGen(*x2d4_particleGens[1]);\n    }\n  }\n\n  if (x281_29_particle3Active) {\n    g_Renderer->AddParticleGen(*x2d4_particleGens[2]);\n  }\n\n  if (x64_modelData && !x64_modelData->IsNull()) {\n    if (x270_curTime < x274_duration) {\n      CActor::AddToRenderer(frustum, mgr);\n    }\n  }\n}\n\nstatic zeus::CVector3f debris_cone(CStateManager& mgr, float coneAng, float minMag, float maxMag) {\n  const float mag = mgr.GetActiveRandom()->Float() * (maxMag - minMag) + minMag;\n  const float side = 1.f - (1.f - std::cos(zeus::degToRad(coneAng * 0.5f))) * mgr.GetActiveRandom()->Float();\n\n  float hyp = std::max(0.f, 1.f - side * side);\n  if (hyp != 0.f) {\n    hyp = std::sqrt(hyp);\n  }\n  hyp *= mag;\n\n  const float ang = mgr.GetActiveRandom()->Float() * 2.f * M_PIF;\n  return {std::cos(ang) * hyp, std::sin(ang) * hyp, mag * side};\n}\n\nvoid CScriptDebris::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) {\n  switch (msg) {\n  case EScriptObjectMessage::Activate:\n    if (!x281_30_debrisExtended) {\n      zeus::CVector3f linImpulse;\n      linImpulse.z() =\n          std::fabs(mgr.GetActiveRandom()->Next() % 32767 / 16383.5f - 1.f) * xe8_mass * x258_velocity.z() +\n          x26c_zImpulse;\n      linImpulse.y() = (mgr.GetActiveRandom()->Next() % 32767 / 16383.5f - 1.f) * xe8_mass * x258_velocity.y();\n      linImpulse.x() = (mgr.GetActiveRandom()->Next() % 32767 / 16383.5f - 1.f) * xe8_mass * x258_velocity.x();\n      linImpulse += x34_transform.basis[2];\n      zeus::CAxisAngle angImpulse;\n      if (x281_24_randomAngImpulse) {\n        if (mgr.GetActiveRandom()->Next() % 100 < 50) {\n          zeus::CVector3f rotVec;\n          rotVec.x() = (mgr.GetActiveRandom()->Next() % 32767 / 16383.5f - 1.f) * 45.f;\n          rotVec.y() = (mgr.GetActiveRandom()->Next() % 32767 / 16383.5f - 1.f) * 15.f;\n          rotVec.z() = (mgr.GetActiveRandom()->Next() % 32767 / 16383.5f - 1.f) * 35.f;\n          angImpulse = zeus::CAxisAngle(rotVec);\n        }\n      }\n      ApplyImpulseWR(linImpulse, angImpulse);\n    } else {\n      const zeus::CVector3f linImpulse = debris_cone(mgr, x288_linConeAngle, x28c_linMinMag, x290_linMaxMag);\n      const zeus::CVector3f angImpulse = debris_cone(mgr, 360.f, x294_angMinMag, x298_angMaxMag);\n      ApplyImpulseOR(linImpulse, angImpulse);\n      x274_duration = mgr.GetActiveRandom()->Float() * (x2a0_maxDuration - x29c_minDuration) + x29c_minDuration;\n    }\n\n    if (x2d4_particleGens[0]) {\n      x2d4_particleGens[0]->SetParticleEmission(true);\n    }\n    if (x2d4_particleGens[1]) {\n      x2d4_particleGens[1]->SetParticleEmission(true);\n    }\n    break;\n  case EScriptObjectMessage::OnFloor:\n    if (!x282_24_noBounce) {\n      ApplyImpulseWR(-x27c_restitution * xfc_constantForce, -x27c_restitution * x108_angularMomentum);\n    }\n    break;\n  default:\n    break;\n  }\n\n  CActor::AcceptScriptMsg(msg, sender, mgr);\n}\n\nvoid CScriptDebris::Think(float dt, CStateManager& mgr) {\n  if (!GetActive()) {\n    return;\n  }\n\n  x270_curTime += dt;\n  bool done = x270_curTime >= x274_duration;\n\n  if (x2d4_particleGens[0]) {\n    if (x270_curTime >= x274_duration) {\n      x2d4_particleGens[0]->SetParticleEmission(false);\n    } else {\n      if (x281_25_particle1GlobalTranslation) {\n        x2d4_particleGens[0]->SetGlobalTranslation(GetTranslation());\n      } else {\n        x2d4_particleGens[0]->SetTranslation(GetTranslation());\n      }\n\n      if (x283_particleOrs[0] == EOrientationType::AlongVelocity) {\n        if (x138_velocity.canBeNormalized()) {\n          const zeus::CVector3f normVel = x138_velocity.normalized();\n          const zeus::CTransform orient =\n              zeus::lookAt(zeus::skZero3f, normVel, std::fabs(normVel.z()) < 0.99f ? zeus::skUp : zeus::skForward);\n          x2d4_particleGens[0]->SetOrientation(orient);\n        }\n      } else if (x283_particleOrs[0] == EOrientationType::ToObject) {\n        x2d4_particleGens[0]->SetOrientation(x34_transform.getRotation());\n      }\n    }\n\n    if (x281_26_deferDeleteTillParticle1Done && x2d4_particleGens[0]->GetParticleCount() != 0) {\n      done = false;\n    }\n\n    if (x270_curTime < x274_duration || x281_26_deferDeleteTillParticle1Done) {\n      x2d4_particleGens[0]->Update(dt);\n    }\n  }\n\n  if (x2d4_particleGens[1]) {\n    if (x270_curTime >= x274_duration) {\n      x2d4_particleGens[1]->SetParticleEmission(false);\n    } else {\n      if (x281_27_particle2GlobalTranslation) {\n        x2d4_particleGens[1]->SetGlobalTranslation(GetTranslation());\n      } else {\n        x2d4_particleGens[1]->SetTranslation(GetTranslation());\n      }\n\n      if (x283_particleOrs[1] == EOrientationType::AlongVelocity) {\n        if (x138_velocity.canBeNormalized()) {\n          const zeus::CVector3f normVel = x138_velocity.normalized();\n          const zeus::CTransform orient =\n              zeus::lookAt(zeus::skZero3f, normVel, std::fabs(normVel.z()) < 0.99f ? zeus::skUp : zeus::skForward);\n          x2d4_particleGens[1]->SetOrientation(orient);\n        }\n      } else if (x283_particleOrs[1] == EOrientationType::ToObject) {\n        x2d4_particleGens[1]->SetOrientation(x34_transform.getRotation());\n      }\n    }\n\n    if (x281_28_deferDeleteTillParticle2Done && x2d4_particleGens[1]->GetParticleCount() != 0) {\n      done = false;\n    }\n\n    if (x270_curTime < x274_duration || x281_28_deferDeleteTillParticle2Done) {\n      x2d4_particleGens[1]->Update(dt);\n    }\n  }\n\n  /* End particle */\n  if (x2d4_particleGens[2]) {\n    if (x270_curTime >= x274_duration && !x281_29_particle3Active) {\n      x2d4_particleGens[2]->SetGlobalTranslation(GetTranslation());\n\n      if (x283_particleOrs[2] == EOrientationType::AlongVelocity) {\n        if (x138_velocity.canBeNormalized()) {\n          const zeus::CVector3f normVel = x138_velocity.normalized();\n          const zeus::CTransform orient =\n              zeus::lookAt(zeus::skZero3f, normVel, std::fabs(normVel.z()) < 0.99f ? zeus::skUp : zeus::skForward);\n          x2d4_particleGens[2]->SetOrientation(orient);\n        }\n      } else if (x283_particleOrs[2] == EOrientationType::ToObject) {\n        x2d4_particleGens[2]->SetOrientation(x34_transform.getRotation());\n      } else if (x283_particleOrs[2] == EOrientationType::AlongCollisionNormal) {\n        if (x2c8_collisionNormal.magSquared() == 0.f) {\n          x2c8_collisionNormal = zeus::skUp;\n        }\n        const zeus::CTransform orient =\n            zeus::lookAt(zeus::skZero3f, x2c8_collisionNormal,\n                         std::fabs(x2c8_collisionNormal.dot(zeus::skUp)) > 0.99f ? zeus::skRight : zeus::skUp);\n        x2d4_particleGens[2]->SetOrientation(orient);\n      }\n\n      x281_29_particle3Active = true;\n    }\n\n    if (x281_29_particle3Active) {\n      x2d4_particleGens[2]->Update(dt);\n      if (!x2d4_particleGens[2]->IsSystemDeletable()) {\n        done = false;\n      }\n    }\n  }\n\n  if (x64_modelData && !x64_modelData->IsNull()) {\n    const float t =\n        x270_curTime / x274_duration > x2ac_scaleOutStartT\n            ? (x270_curTime - x2ac_scaleOutStartT * x274_duration) / ((1.f - x2ac_scaleOutStartT) * x274_duration)\n            : 0.f;\n    x64_modelData->SetScale(zeus::CVector3f::lerp(x2b0_scale, x2bc_endScale, t));\n  }\n\n  if (x270_curTime >= x274_duration) {\n    x150_momentum = zeus::skZero3f;\n    SetMaterialFilter(CMaterialFilter::MakeExclude(\n        {EMaterialTypes::Debris, EMaterialTypes::Character, EMaterialTypes::Player, EMaterialTypes::Projectile}));\n\n    if (done) {\n      mgr.FreeScriptObject(x8_uid);\n      return;\n    }\n  }\n\n  if (xf8_24_movable) {\n    const float speed = x138_velocity.magnitude();\n    x2e0_speedAvg.AddValue(speed);\n    if (x2e0_speedAvg.GetAverage() < 0.1f) {\n      xf8_24_movable = false;\n    }\n  }\n}\n\nvoid CScriptDebris::Touch(CActor& other, CStateManager& mgr) {\n  if (!x281_31_dieOnProjectile) {\n    return;\n  }\n\n  if (TCastToPtr<CGameProjectile>(other)) {\n    SendScriptMsgs(EScriptObjectState::Dead, mgr, EScriptObjectMessage::None);\n    mgr.FreeScriptObject(x8_uid);\n  }\n}\n\nstd::optional<zeus::CAABox> CScriptDebris::GetTouchBounds() const {\n  if (x281_31_dieOnProjectile) {\n    return GetBoundingBox();\n  }\n  return std::nullopt;\n}\n\nvoid CScriptDebris::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) {\n  CActor::PreRender(mgr, frustum);\n\n  float t = x270_curTime / x274_duration;\n  if (t < x2a4_colorInT) {\n    t = x2a4_colorInT > 0.f ? 1.f - x270_curTime / (x274_duration * x2a4_colorInT) : 0.f;\n  } else if (t > x2a8_colorOutT) {\n    t = (x270_curTime - x274_duration * x2a8_colorOutT) / (x274_duration * (1.f - x2a8_colorOutT));\n  } else {\n    t = 0.f;\n  }\n\n  xb4_drawFlags = CModelFlags(5, 0, t == 1.f ? 3 : 1, zeus::CColor::lerp(zeus::skWhite, x268_endsColor, t));\n}\n\nvoid CScriptDebris::Render(CStateManager& mgr) { CPhysicsActor::Render(mgr); }\n\nvoid CScriptDebris::CollidedWith(TUniqueId, const CCollisionInfoList& colList, CStateManager&) {\n  if (colList.GetCount() == 0) {\n    return;\n  }\n\n  if (x282_24_noBounce) {\n    x274_duration = x270_curTime;\n    SetVelocityWR(zeus::skZero3f);\n  } else {\n    x2c8_collisionNormal = colList.GetItem(0).GetNormalLeft();\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptDebris.hpp",
    "content": "#pragma once\n\n#include <array>\n#include <memory>\n#include <string_view>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/World/CPhysicsActor.hpp\"\n\n#include <zeus/CColor.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CElementGen;\nclass CScriptDebris : public CPhysicsActor {\npublic:\n  enum class EScaleType : u8 { NoScale, EndsToZero };\n\n  enum class EOrientationType : u8 { NotOriented, AlongVelocity, ToObject, AlongCollisionNormal };\n\nprivate:\n  zeus::CVector3f x258_velocity;\n  zeus::CColor x264_color;\n  zeus::CColor x268_endsColor;\n  float x26c_zImpulse = 0.f;\n  float x270_curTime = 0.f;\n  float x274_duration = 0.f;\n  float x278_ooDuration = 0.f;\n  float x27c_restitution;\n  CScriptDebris::EScaleType x280_scaleType = CScriptDebris::EScaleType::NoScale;\n  bool x281_24_randomAngImpulse : 1;\n  bool x281_25_particle1GlobalTranslation : 1 = false;\n  bool x281_26_deferDeleteTillParticle1Done : 1 = false;\n  bool x281_27_particle2GlobalTranslation : 1 = false;\n  bool x281_28_deferDeleteTillParticle2Done : 1 = false;\n  bool x281_29_particle3Active : 1 = false;\n  bool x281_30_debrisExtended : 1 = false;\n  bool x281_31_dieOnProjectile : 1 = false;\n  bool x282_24_noBounce : 1 = false;\n  EOrientationType x283_particleOrs[3] = {};\n  float x288_linConeAngle = 0.f;\n  float x28c_linMinMag = 0.f;\n  float x290_linMaxMag = 0.f;\n  float x294_angMinMag = 0.f;\n  float x298_angMaxMag = 0.f;\n  float x29c_minDuration = 0.f;\n  float x2a0_maxDuration = 0.f;\n  float x2a4_colorInT = 0.f;\n  float x2a8_colorOutT = 0.f;\n  float x2ac_scaleOutStartT = 0.f;\n  zeus::CVector3f x2b0_scale;\n  zeus::CVector3f x2bc_endScale;\n  zeus::CVector3f x2c8_collisionNormal;\n  std::array<std::unique_ptr<CElementGen>, 3> x2d4_particleGens; /* x2d4, x2d8, x2dc */\n  TReservedAverage<float, 8> x2e0_speedAvg;\n\npublic:\n  DEFINE_ENTITY\n  CScriptDebris(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                CModelData&& mData, const CActorParameters& aParams, CAssetId particleId,\n                const zeus::CVector3f& particleScale, float zImpulse, const zeus::CVector3f& velocity,\n                const zeus::CColor& endsColor, float mass, float restitution, float duration, EScaleType scaleType,\n                bool b1, bool randomAngImpulse, bool active);\n\n  CScriptDebris(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                CModelData&& mData, const CActorParameters& aParams, float linConeAngle, float linMinMag,\n                float linMaxMag, float angMinMag, float angMaxMag, float minDuration, float maxDuration, float colorInT,\n                float colorOutT, const zeus::CColor& color, const zeus::CColor& endsColor, float scaleOutStartT,\n                const zeus::CVector3f& scale, const zeus::CVector3f& endScale, float restitution, float downwardSpeed,\n                const zeus::CVector3f& localOffset, CAssetId particle1, const zeus::CVector3f& particle1Scale,\n                bool particle1GlobalTranslation, bool deferDeleteTillParticle1Done, EOrientationType particle1Or,\n                CAssetId particle2, const zeus::CVector3f& particle2Scale, bool particle2GlobalTranslation,\n                bool deferDeleteTillParticle2Done, EOrientationType particle2Or, CAssetId particle3,\n                const zeus::CVector3f& particle3Scale, EOrientationType particle3Or, bool solid, bool dieOnProjectile,\n                bool noBounce, bool active);\n\n  void Accept(IVisitor& visitor) override;\n  void AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) override;\n  void Think(float dt, CStateManager& mgr) override;\n  void Touch(CActor& other, CStateManager& mgr) override;\n  std::optional<zeus::CAABox> GetTouchBounds() const override;\n  void PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) override;\n  void Render(CStateManager& mgr) override;\n\n  void CollidedWith(TUniqueId uid, const CCollisionInfoList&, CStateManager&) override;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptDebugCameraWaypoint.cpp",
    "content": "#include \"Runtime/World/CScriptDebugCameraWaypoint.hpp\"\n\n#include \"Runtime/World/CActorParameters.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCScriptDebugCameraWaypoint::CScriptDebugCameraWaypoint(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                                                       const zeus::CTransform& xf, u32 w1)\n: CActor(uid, true, name, info, xf, CModelData::CModelDataNull(), {EMaterialTypes::NoStepLogic},\n         CActorParameters::None(), kInvalidUniqueId)\n, xe8_w1(w1) {}\n\nvoid CScriptDebugCameraWaypoint::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptDebugCameraWaypoint.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/World/CActor.hpp\"\n\nnamespace metaforce {\n\nclass CScriptDebugCameraWaypoint : public CActor {\n  u32 xe8_w1;\n\npublic:\n  DEFINE_ENTITY\n  CScriptDebugCameraWaypoint(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                             u32 w1);\n\n  void Accept(IVisitor&) override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptDistanceFog.cpp",
    "content": "#include \"Runtime/World/CScriptDistanceFog.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nCScriptDistanceFog::CScriptDistanceFog(TUniqueId uid, std::string_view name, const CEntityInfo& info, ERglFogMode mode,\n                                       const zeus::CColor& color, const zeus::CVector2f& range, float colorDelta,\n                                       const zeus::CVector2f& rangeDelta, bool expl, bool active, float thermalTarget,\n                                       float thermalSpeed, float xrayTarget, float xraySpeed)\n: CEntity(uid, info, active, name)\n, x34_mode(mode)\n, x38_color(color)\n, x3c_range(range)\n, x44_colorDelta(colorDelta)\n, x48_rangeDelta(rangeDelta)\n, x50_thermalTarget(thermalTarget)\n, x54_thermalSpeed(thermalSpeed)\n, x58_xrayTarget(xrayTarget)\n, x5c_xraySpeed(xraySpeed)\n, x60_explicit(expl)\n\n{\n  if (zeus::close_enough(rangeDelta, zeus::skZero2f) && zeus::close_enough(colorDelta, 0.f)) {\n    x61_nonZero = false;\n  } else {\n    x61_nonZero = true;\n  }\n}\n\nvoid CScriptDistanceFog::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptDistanceFog::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) {\n  CEntity::AcceptScriptMsg(msg, objId, stateMgr);\n\n  if (x4_areaId == kInvalidAreaId || !GetActive()) {\n    return;\n  }\n\n  if (msg == EScriptObjectMessage::InitializedInArea) {\n    if (!x60_explicit) {\n      return;\n    }\n\n    CGameArea::CAreaFog* fog = stateMgr.GetWorld()->GetArea(x4_areaId)->GetAreaFog();\n    if (x34_mode == ERglFogMode::None) {\n      fog->DisableFog();\n    } else {\n      fog->SetFogExplicit(x34_mode, x38_color, x3c_range);\n    }\n  } else if (msg == EScriptObjectMessage::Action) {\n    if (x61_nonZero) {\n      CGameArea::CAreaFog* fog = stateMgr.GetWorld()->GetArea(x4_areaId)->GetAreaFog();\n      if (x34_mode == ERglFogMode::None) {\n        fog->RollFogOut(x48_rangeDelta.x(), x44_colorDelta, x38_color);\n      } else {\n        fog->FadeFog(x34_mode, x38_color, x3c_range, x44_colorDelta, x48_rangeDelta);\n      }\n    }\n\n    CWorld* world = stateMgr.GetWorld();\n    CGameArea* area = world->GetArea(x4_areaId);\n    if (!zeus::close_enough(x54_thermalSpeed, 0.f)) {\n      area->SetThermalSpeedAndTarget(x54_thermalSpeed, x50_thermalTarget);\n    }\n\n    if (!zeus::close_enough(x5c_xraySpeed, 0.f)) {\n      area->SetXRaySpeedAndTarget(x5c_xraySpeed, x58_xrayTarget);\n    }\n  }\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptDistanceFog.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/Graphics/CGraphics.hpp\"\n#include \"Runtime/World/CEntity.hpp\"\n\n#include <zeus/CColor.hpp>\n#include <zeus/CVector2f.hpp>\n\nnamespace metaforce {\nclass CScriptDistanceFog : public CEntity {\n  ERglFogMode x34_mode;\n  zeus::CColor x38_color;\n  zeus::CVector2f x3c_range;\n  float x44_colorDelta;\n  zeus::CVector2f x48_rangeDelta;\n  float x50_thermalTarget;\n  float x54_thermalSpeed;\n  float x58_xrayTarget;\n  float x5c_xraySpeed;\n  bool x60_explicit;\n  bool x61_nonZero;\n\npublic:\n  DEFINE_ENTITY\n  CScriptDistanceFog(TUniqueId, std::string_view, const CEntityInfo&, ERglFogMode, const zeus::CColor&,\n                     const zeus::CVector2f&, float, const zeus::CVector2f&, bool, bool, float, float, float, float);\n\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) override;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptDock.cpp",
    "content": "#include \"Runtime/World/CScriptDock.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Character/CModelData.hpp\"\n#include \"Runtime/Collision/CMaterialList.hpp\"\n#include \"Runtime/Particle/CGenDescription.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CScriptDoor.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nconstexpr auto skDockMaterialList =\n    CMaterialList{EMaterialTypes::Trigger, EMaterialTypes::Immovable, EMaterialTypes::AIBlock};\n\nCScriptDock::CScriptDock(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CVector3f& position,\n                         const zeus::CVector3f& extents, s32 dock, TAreaId area, bool active, s32 dockReferenceCount,\n                         bool loadConnected)\n: CPhysicsActor(uid, active, name, info, zeus::CTransform(zeus::CMatrix3f(), position), CModelData::CModelDataNull(),\n                skDockMaterialList, zeus::CAABox(-extents * 0.5f, extents * 0.5f), SMoverData(1.f),\n                CActorParameters::None(), 0.3f, 0.1f)\n, x258_dockReferenceCount(dockReferenceCount)\n, x25c_dock(dock)\n, x260_area(area)\n, x268_25_loadConnected(loadConnected) {}\n\nvoid CScriptDock::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptDock::Think(float dt, CStateManager& mgr) {\n  if (!GetActive()) {\n    UpdateAreaActivateFlags(mgr);\n    x268_24_dockReferenced = false;\n  }\n\n  const IGameArea::Dock* gameDock = mgr.GetWorld()->GetArea(x260_area)->GetDock(x25c_dock);\n  const TAreaId connArea = gameDock->GetConnectedAreaId(gameDock->GetReferenceCount());\n  if (connArea != kInvalidAreaId) {\n    const bool connPostConstructed = mgr.GetWorld()->GetArea(connArea)->IsPostConstructed();\n    if (x268_26_areaPostConstructed != connPostConstructed) {\n      x268_26_areaPostConstructed = connPostConstructed;\n      if (connPostConstructed) {\n        CEntity::SendScriptMsgs(EScriptObjectState::MaxReached, mgr, EScriptObjectMessage::None);\n      } else {\n        CEntity::SendScriptMsgs(EScriptObjectState::Zero, mgr, EScriptObjectMessage::None);\n      }\n    }\n  }\n\n  if (mgr.GetNextAreaId() != x260_area) {\n    x264_dockState = EDockState::InNextRoom;\n  } else if (x264_dockState == EDockState::InNextRoom) {\n    x264_dockState = EDockState::InSourceRoom;\n  } else if (x264_dockState == EDockState::PlayerTouched) {\n    x264_dockState = EDockState::EnterNextArea;\n  } else if (x264_dockState == EDockState::EnterNextArea) {\n    CPlayer& player = mgr.GetPlayer();\n    if (HasPointCrossedDock(mgr, player.GetTranslation())) {\n      const IGameArea::Dock* dock = mgr.GetWorld()->GetArea(mgr.GetNextAreaId())->GetDock(x25c_dock);\n      const TAreaId aid = dock->GetConnectedAreaId(dock->GetReferenceCount());\n      if (aid != kInvalidAreaId && mgr.GetWorld()->GetArea(aid)->GetActive()) {\n        mgr.SetCurrentAreaId(aid);\n        const s32 otherDock = dock->GetOtherDockNumber(dock->GetReferenceCount());\n\n        if (CObjectList* objs = mgr.GetWorld()->GetArea(aid)->GetAreaObjects()) {\n          for (CEntity* ent : *objs) {\n            const TCastToPtr<CScriptDock> dock2(ent);\n            if (dock2 && dock2->GetDockId() == otherDock) {\n              dock2->SetLoadConnected(mgr, true);\n            }\n          }\n        }\n      }\n    }\n\n    x264_dockState = EDockState::InSourceRoom;\n  }\n}\n\nvoid CScriptDock::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  switch (msg) {\n  case EScriptObjectMessage::Registered: {\n    CGameArea* area = mgr.GetWorld()->GetArea(x260_area);\n    if (area->GetDockCount() < x25c_dock) {\n      return;\n    }\n    IGameArea::Dock* dock = area->GetDock(x25c_dock);\n    if (!dock->IsReferenced()) {\n      dock->SetReferenceCount(x258_dockReferenceCount);\n    }\n  } break;\n  case EScriptObjectMessage::Deleted:\n    CleanUp();\n    break;\n  case EScriptObjectMessage::InitializedInArea:\n    AreaLoaded(mgr);\n    break;\n  case EScriptObjectMessage::WorldInitialized: {\n    UpdateAreaActivateFlags(mgr);\n    const CMaterialList exclude = GetMaterialFilter().GetExcludeList();\n    CMaterialList include = GetMaterialFilter().GetIncludeList();\n    include.Add(EMaterialTypes::AIBlock);\n    SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(include, exclude));\n  } break;\n  case EScriptObjectMessage::SetToZero: {\n    if (mgr.GetNextAreaId() != x260_area) {\n      return;\n    }\n\n    SetLoadConnected(mgr, false);\n\n    const CGameArea* area = mgr.GetWorld()->GetArea(x260_area);\n    const IGameArea::Dock* dock = area->GetDock(x25c_dock);\n\n    const TAreaId aid = dock->GetConnectedAreaId(dock->GetReferenceCount());\n\n    CPlatformAndDoorList& lst = mgr.GetPlatformAndDoorObjectList();\n    for (CEntity* ent : lst) {\n      const TCastToPtr<CScriptDoor> door(ent);\n      if (door && door->IsConnectedToArea(mgr, aid)) {\n        door->ForceClosed(mgr);\n      }\n    }\n  } break;\n  case EScriptObjectMessage::SetToMax:\n    if (mgr.GetNextAreaId() != x260_area) {\n      return;\n    }\n\n    SetLoadConnected(mgr, true);\n    break;\n  case EScriptObjectMessage::Increment:\n    SetLoadConnected(mgr, true);\n    [[fallthrough]];\n  case EScriptObjectMessage::Decrement: {\n    TAreaId aid = x260_area;\n    if (mgr.GetNextAreaId() == x260_area) {\n      const IGameArea::Dock* dock = mgr.GetWorld()->GetArea(x260_area)->GetDock(x25c_dock);\n      aid = dock->GetConnectedAreaId(dock->GetReferenceCount());\n    }\n\n    if (aid >= 0 && aid < mgr.GetWorld()->GetNumAreas() && mgr.GetWorld()->GetArea(aid)->GetActive()) {\n      CWorld::PropogateAreaChain(CGameArea::EOcclusionState(msg == EScriptObjectMessage::Increment),\n                                 mgr.GetWorld()->GetArea(aid), mgr.GetWorld());\n    }\n  } break;\n  default:\n    CPhysicsActor::AcceptScriptMsg(msg, uid, mgr);\n    break;\n  }\n}\n\nstd::optional<zeus::CAABox> CScriptDock::GetTouchBounds() const {\n  if (x264_dockState == EDockState::InNextRoom) {\n    return std::nullopt;\n  }\n\n  return GetBoundingBox();\n}\n\nvoid CScriptDock::Touch(CActor& act, CStateManager&) {\n  if (x264_dockState == EDockState::InNextRoom) {\n    return;\n  }\n\n  if (TCastToConstPtr<CPlayer>(act)) {\n    x264_dockState = EDockState::PlayerTouched;\n  }\n}\n\nzeus::CPlane CScriptDock::GetPlane(const CStateManager& mgr) const {\n  const IGameArea::Dock* dock = mgr.GetWorld()->GetAreaAlways(x260_area)->GetDock(x25c_dock);\n  return zeus::CPlane(dock->GetPoint(0), dock->GetPoint(1), dock->GetPoint(2));\n}\n\nvoid CScriptDock::SetDockReference(CStateManager& mgr, s32 ref) {\n  mgr.GetWorld()->GetArea(x260_area)->GetDock(x25c_dock)->SetReferenceCount(ref);\n  x268_24_dockReferenced = true;\n}\n\ns32 CScriptDock::GetDockReference(const CStateManager& mgr) const {\n  return mgr.GetWorld()->GetAreaAlways(x260_area)->GetDock(x25c_dock)->GetReferenceCount();\n}\n\nTAreaId CScriptDock::GetCurrentConnectedAreaId(const CStateManager& mgr) const {\n  if (x260_area >= mgr.GetWorld()->GetNumAreas()) {\n    return kInvalidAreaId;\n  }\n\n  const CGameArea* area = mgr.GetWorld()->GetAreaAlways(x260_area);\n  if (x25c_dock >= area->GetDockCount()) {\n    return kInvalidAreaId;\n  }\n\n  const IGameArea::Dock* dock = area->GetDock(x25c_dock);\n  return dock->GetConnectedAreaId(dock->GetReferenceCount());\n}\n\nvoid CScriptDock::UpdateAreaActivateFlags(CStateManager& mgr) {\n  CWorld* world = mgr.GetWorld();\n  if (x260_area >= world->GetNumAreas()) {\n    return;\n  }\n\n  CGameArea* area = world->GetArea(x260_area);\n  if (x25c_dock >= area->GetDockCount())\n    return;\n\n  const IGameArea::Dock* dock = area->GetDock(x25c_dock);\n\n  for (s32 i = 0; i < dock->GetDockRefs().size(); ++i) {\n    const s32 dockRefCount = dock->GetReferenceCount();\n    const TAreaId aid = dock->GetConnectedAreaId(i);\n    if (aid != kInvalidAreaId) {\n      world->GetArea(aid)->SetActive(i == dockRefCount);\n    }\n  }\n  mgr.SetCurrentAreaId(mgr.GetNextAreaId());\n}\n\nbool CScriptDock::HasPointCrossedDock(const CStateManager& mgr, const zeus::CVector3f& point) const {\n  const zeus::CPlane plane = GetPlane(mgr);\n  return plane.pointToPlaneDist(point) >= 0.f;\n}\n\nvoid CScriptDock::AreaLoaded(CStateManager& mgr) { SetLoadConnected(mgr, x268_25_loadConnected); }\n\nvoid CScriptDock::SetLoadConnected(CStateManager& mgr, bool loadOther) {\n  IGameArea::Dock* dock = mgr.GetWorld()->GetArea(x260_area)->GetDock(x25c_dock);\n  const bool cur = dock->GetShouldLoadOther(dock->GetReferenceCount());\n  if (cur == loadOther) {\n    return;\n  }\n\n  dock->SetShouldLoadOther(dock->GetReferenceCount(), loadOther);\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptDock.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/World/CPhysicsActor.hpp\"\n\nnamespace metaforce {\n\nclass CScriptDock : public CPhysicsActor {\n  enum class EDockState { InSourceRoom, PlayerTouched, EnterNextArea, InNextRoom };\n\n  friend class CScriptDoor;\n  s32 x258_dockReferenceCount;\n  s32 x25c_dock;\n  TAreaId x260_area;\n  EDockState x264_dockState = EDockState::InNextRoom;\n  bool x268_24_dockReferenced : 1 = false;\n  bool x268_25_loadConnected : 1;\n  bool x268_26_areaPostConstructed : 1 = false;\n\npublic:\n  DEFINE_ENTITY\n  CScriptDock(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CVector3f& position,\n              const zeus::CVector3f& extent, s32 dock, TAreaId area, bool active, s32 dockReferenceCount,\n              bool loadConnected);\n\n  void Accept(IVisitor& visitor) override;\n  void Think(float, CStateManager&) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  std::optional<zeus::CAABox> GetTouchBounds() const override;\n  void Touch(CActor&, CStateManager&) override;\n  void CleanUp() {}\n  zeus::CPlane GetPlane(const CStateManager&) const;\n  TAreaId GetAreaId() const { return x260_area; }\n  s32 GetDockId() const { return x25c_dock; }\n  void SetDockReference(CStateManager& mgr, s32);\n  s32 GetDockReference(const CStateManager& mgr) const;\n  TAreaId GetCurrentConnectedAreaId(const CStateManager&) const;\n  void UpdateAreaActivateFlags(CStateManager&);\n  bool HasPointCrossedDock(const CStateManager&, const zeus::CVector3f&) const;\n  void AreaLoaded(CStateManager&);\n  void AreaUnloaded(CStateManager&);\n  void SetLoadConnected(CStateManager&, bool);\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptDockAreaChange.cpp",
    "content": "#include \"Runtime/World/CScriptDockAreaChange.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CScriptDock.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nCScriptDockAreaChange::CScriptDockAreaChange(TUniqueId uid, std::string_view name, const CEntityInfo& info, s32 w1,\n                                             bool active)\n: CEntity(uid, info, active, name), x34_dockReference(w1) {}\n\nvoid CScriptDockAreaChange::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptDockAreaChange::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) {\n  if (msg == EScriptObjectMessage::Action && GetActive()) {\n    for (SConnection conn : x20_conns) {\n      if (conn.x0_state != EScriptObjectState::Play) {\n        continue;\n      }\n\n      const auto search = stateMgr.GetIdListForScript(conn.x8_objId);\n      for (auto it = search.first; it != search.second; ++it) {\n        const TUniqueId id = it->second;\n        const TCastToPtr<CScriptDock> dock(stateMgr.ObjectById(id));\n        if (dock) {\n          dock->SetDockReference(stateMgr, x34_dockReference);\n        }\n      }\n    }\n\n    SendScriptMsgs(EScriptObjectState::Play, stateMgr, EScriptObjectMessage::None);\n  }\n\n  CEntity::AcceptScriptMsg(msg, objId, stateMgr);\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptDockAreaChange.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/World/CEntity.hpp\"\n\nnamespace metaforce {\nclass CScriptDockAreaChange : public CEntity {\n  s32 x34_dockReference;\n\npublic:\n  DEFINE_ENTITY\n  CScriptDockAreaChange(TUniqueId, std::string_view, const CEntityInfo&, s32, bool);\n\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) override;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptDoor.cpp",
    "content": "#include \"Runtime/World/CScriptDoor.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/AutoMapper/CMapWorldInfo.hpp\"\n#include \"Runtime/Camera/CBallCamera.hpp\"\n#include \"Runtime/Camera/CCameraManager.hpp\"\n#include \"Runtime/Character/CAnimData.hpp\"\n#include \"Runtime/Character/CAnimPlaybackParms.hpp\"\n#include \"Runtime/Collision/CMaterialList.hpp\"\n#include \"Runtime/World/CScriptDock.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nstatic CMaterialList MakeDoorMaterialList(bool open) {\n  CMaterialList ret;\n  ret.Add(EMaterialTypes::Solid);\n  ret.Add(EMaterialTypes::Immovable);\n  ret.Add(EMaterialTypes::Orbit);\n  if (!open) {\n    ret.Add(EMaterialTypes::Occluder);\n  }\n\n  return ret;\n}\n\nCScriptDoor::CScriptDoor(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                         CModelData&& mData, const CActorParameters& actParms, const zeus::CVector3f& orbitPos,\n                         const zeus::CAABox& aabb, bool active, bool open, bool projectilesCollide, float animLen,\n                         bool ballDoor)\n: CPhysicsActor(uid, active, name, info, xf, std::move(mData), MakeDoorMaterialList(open), aabb, SMoverData(1.f),\n                actParms, 0.3f, 0.1f) {\n  x258_animLen = animLen;\n  x2a8_25_wasOpen = open;\n  x2a8_26_isOpen = open;\n  x2a8_28_projectilesCollide = projectilesCollide;\n  x2a8_29_ballDoor = ballDoor;\n  x264_ = GetBoundingBox();\n  x284_modelBounds = x64_modelData->GetBounds(xf.getRotation());\n  x29c_orbitPos = orbitPos;\n\n  xe6_27_thermalVisorFlags = 1;\n  if (open) {\n    SetDoorAnimation(EDoorAnimType::Open);\n  }\n\n  SetMass(0.f);\n}\n\nvoid CScriptDoor::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\n/* ORIGINAL 0-00 OFFSET: 8007F054 */\nzeus::CVector3f CScriptDoor::GetOrbitPosition(const CStateManager& /*mgr*/) const {\n  return x34_transform.origin + x29c_orbitPos;\n}\n\n/* ORIGINAL 0-00 OFFSET: 8007E550 */\nvoid CScriptDoor::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  switch (msg) {\n  case EScriptObjectMessage::Close: {\n    if (!GetActive()) {\n      return;\n    }\n\n    if (x27c_partner1 != kInvalidUniqueId && x27c_partner1 != uid) {\n      return;\n    }\n\n    if (x2a8_26_isOpen) {\n      if (x27e_partner2 != kInvalidUniqueId) {\n        if (CEntity* ent = mgr.ObjectById(x27e_partner2)) {\n          mgr.SendScriptMsg(ent, GetUniqueId(), EScriptObjectMessage::Close);\n        }\n      }\n      x2a8_26_isOpen = false;\n      SetDoorAnimation(EDoorAnimType::Close);\n      mgr.GetCameraManager()->GetBallCamera()->DoorClosing(GetUniqueId());\n    } else if (x2a8_27_conditionsMet) {\n      x2a8_27_conditionsMet = false;\n      SendScriptMsgs(EScriptObjectState::Closed, mgr, EScriptObjectMessage::None);\n    }\n    break;\n  }\n  case EScriptObjectMessage::Action: {\n    if (x27c_partner1 != kInvalidUniqueId) {\n      if (const TCastToPtr<CScriptDoor> door = mgr.ObjectById(x27c_partner1)) {\n        if (door->x2a8_26_isOpen) {\n          x2a8_30_doClose = true;\n          mgr.SendScriptMsg(door, GetUniqueId(), EScriptObjectMessage::Close);\n          door->x2a8_30_doClose = true;\n        }\n      }\n    } else if (x2a8_26_isOpen) {\n      x2a8_30_doClose = true;\n      if (const TCastToPtr<CScriptDoor> door = mgr.ObjectById(x27e_partner2)) {\n        mgr.SendScriptMsg(door, GetUniqueId(), EScriptObjectMessage::Close);\n        door->x2a8_30_doClose = true;\n      }\n      x2a8_26_isOpen = false;\n      SetDoorAnimation(EDoorAnimType::Close);\n      mgr.GetCameraManager()->GetBallCamera()->DoorClosing(GetUniqueId());\n    }\n    break;\n  }\n  case EScriptObjectMessage::Open: {\n    if (!GetActive() || x2a8_26_isOpen) {\n      return;\n    }\n\n    const auto doorCond =\n        TCastToConstPtr<CScriptDoor>(mgr.GetObjectById(uid)) ? EDoorOpenCondition::Ready : GetDoorOpenCondition(mgr);\n    switch (doorCond) {\n    case EDoorOpenCondition::Loading:\n      x2a8_27_conditionsMet = true;\n      x280_prevDoor = uid;\n      break;\n    case EDoorOpenCondition::Ready:\n      OpenDoor(uid, mgr);\n      break;\n    default:\n      x2a8_25_wasOpen = false;\n      x2a8_24_closing = true;\n      break;\n    }\n    break;\n  }\n  case EScriptObjectMessage::InitializedInArea: {\n    for (const SConnection& conn : x20_conns) {\n      if (conn.x4_msg == EScriptObjectMessage::Increment) {\n        const TUniqueId dock = mgr.GetIdForScript(conn.x8_objId);\n        if (const TCastToConstPtr<CScriptDock> d = mgr.GetObjectById(dock)) {\n          x282_dockId = d->GetUniqueId();\n          break;\n        }\n      }\n    }\n    break;\n  }\n  case EScriptObjectMessage::SetToZero: {\n    x2a8_28_projectilesCollide = true;\n    mgr.MapWorldInfo()->SetDoorVisited(mgr.GetEditorIdForUniqueId(GetUniqueId()), true);\n    break;\n  }\n  case EScriptObjectMessage::SetToMax: {\n    x2a8_28_projectilesCollide = false;\n    break;\n  }\n  default:\n    CActor::AcceptScriptMsg(msg, uid, mgr);\n  }\n}\n\nvoid CScriptDoor::Think(float dt, CStateManager& mgr) {\n  if (!GetActive()) {\n    return;\n  }\n\n  if (!x2a8_26_isOpen && x25c_animTime < 0.5f) {\n    x25c_animTime += dt;\n  }\n\n  if (x2a8_27_conditionsMet && GetDoorOpenCondition(mgr) == EDoorOpenCondition::Ready) {\n    x2a8_27_conditionsMet = false;\n    OpenDoor(x280_prevDoor, mgr);\n  }\n\n  if (x2a8_24_closing) {\n    x2a8_25_wasOpen = false;\n    mgr.GetCameraManager()->GetBallCamera()->DoorClosed(GetUniqueId());\n    x2a8_28_projectilesCollide = false;\n    x2a8_24_closing = false;\n    SendScriptMsgs(EScriptObjectState::Closed, mgr, EScriptObjectMessage::Decrement);\n    x25c_animTime = 0.f;\n    x2a8_30_doClose = false;\n  }\n\n  if (x2a8_26_isOpen && !x64_modelData->IsAnimating()) {\n    RemoveMaterial(EMaterialTypes::Solid, EMaterialTypes::Occluder, EMaterialTypes::Orbit, EMaterialTypes::Scannable,\n                   mgr);\n  } else {\n    if (x2a8_25_wasOpen && !x64_modelData->IsAnimating()) {\n      x2a8_25_wasOpen = false;\n      mgr.GetCameraManager()->GetBallCamera()->DoorClosed(GetUniqueId());\n      x2a8_28_projectilesCollide = false;\n      x2a8_27_conditionsMet = false;\n      SendScriptMsgs(EScriptObjectState::Closed, mgr, EScriptObjectMessage::None);\n      x25c_animTime = 0.f;\n      x2a8_30_doClose = false;\n    }\n\n    if (GetScannableObjectInfo()) {\n      AddMaterial(EMaterialTypes::Solid, EMaterialTypes::Metal, EMaterialTypes::Occluder, EMaterialTypes::Orbit,\n                  EMaterialTypes::Scannable, mgr);\n    } else {\n      AddMaterial(EMaterialTypes::Solid, EMaterialTypes::Metal, EMaterialTypes::Occluder, EMaterialTypes::Orbit, mgr);\n    }\n  }\n\n  if (x64_modelData->IsAnimating()) {\n    UpdateAnimation((x64_modelData->GetAnimationDuration(s32(x260_doorAnimState)) / x258_animLen) * dt, mgr, true);\n  }\n\n  xe7_31_targetable = mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::Scan;\n}\n\nvoid CScriptDoor::AddToRenderer(const zeus::CFrustum& /*frustum*/, CStateManager& mgr) {\n  if (xe4_30_outOfFrustum) {\n    return;\n  }\n\n  CPhysicsActor::Render(mgr);\n}\n\n/* ORIGINAL 0-00 OFFSET: 8007E0BC */\nvoid CScriptDoor::ForceClosed(CStateManager& mgr) {\n  if (x2a8_26_isOpen) {\n    x2a8_26_isOpen = false;\n    x2a8_25_wasOpen = false;\n\n    mgr.GetCameraManager()->GetBallCamera()->DoorClosing(x8_uid);\n    mgr.GetCameraManager()->GetBallCamera()->DoorClosed(x8_uid);\n\n    SetDoorAnimation(EDoorAnimType::Close);\n    SendScriptMsgs(EScriptObjectState::Closed, mgr, EScriptObjectMessage::None);\n\n    x2a8_27_conditionsMet = false;\n    x25c_animTime = 0.f;\n    x2a8_30_doClose = false;\n  } else if (x2a8_27_conditionsMet) {\n    x2a8_27_conditionsMet = false;\n    x2a8_30_doClose = false;\n    SendScriptMsgs(EScriptObjectState::Closed, mgr, EScriptObjectMessage::None);\n  }\n}\n\n/* ORIGINAL 0-00 OFFSET: 8007E1C4 */\nbool CScriptDoor::IsConnectedToArea(const CStateManager& mgr, TAreaId areaId) const {\n  const CScriptDock* dockEnt = TCastToConstPtr<CScriptDock>(mgr.GetObjectById(x282_dockId));\n  if (dockEnt) {\n    if (dockEnt->GetAreaId() == areaId) {\n      return true;\n    }\n\n    const CWorld* world = mgr.GetWorld();\n    const CGameArea* area = world->GetAreaAlways(dockEnt->GetAreaId());\n    const CGameArea::Dock* dock = area->GetDock(dockEnt->GetDockId());\n    if (dock->GetConnectedAreaId(dockEnt->GetDockReference(mgr)) == areaId) {\n      return true;\n    }\n  }\n  return false;\n}\n\n/* ORIGINAL 0-00 OFFSET: 8007EA64 */\nvoid CScriptDoor::OpenDoor(TUniqueId uid, CStateManager& mgr) {\n  const TEditorId eid = mgr.GetEditorIdForUniqueId(uid);\n  mgr.MapWorldInfo()->SetDoorVisited(eid, true);\n  x2a8_26_isOpen = true;\n  x2a8_25_wasOpen = true;\n  x2a8_27_conditionsMet = false;\n  x27c_partner1 = kInvalidUniqueId;\n  x27e_partner2 = kInvalidUniqueId;\n\n  if (const CScriptDoor* door = TCastToConstPtr<CScriptDoor>(mgr.GetObjectById(uid))) {\n    x27c_partner1 = door->GetUniqueId();\n  }\n\n  SetDoorAnimation(EDoorAnimType::Open);\n  if (x27c_partner1 == kInvalidUniqueId) {\n    SendScriptMsgs(EScriptObjectState::Open, mgr, EScriptObjectMessage::None);\n  } else {\n    SendScriptMsgs(EScriptObjectState::MaxReached, mgr, EScriptObjectMessage::None);\n  }\n\n  if (const TCastToConstPtr<CScriptDock> dock1 = mgr.GetObjectById(x282_dockId)) {\n    for (CEntity* ent : mgr.GetPlatformAndDoorObjectList()) {\n      const TCastToConstPtr<CScriptDoor> door = ent;\n      if (!door || door->GetUniqueId() == uid) {\n        continue;\n      }\n\n      if (const TCastToConstPtr<CScriptDock> dock2 = mgr.GetObjectById(door->x282_dockId)) {\n        if (dock1->GetCurrentConnectedAreaId(mgr) == dock2->GetAreaId() &&\n            dock2->GetCurrentConnectedAreaId(mgr) == dock1->GetAreaId()) {\n          x27e_partner2 = door->GetUniqueId();\n          mgr.SendScriptMsg(ent, GetUniqueId(), EScriptObjectMessage::Open);\n          break;\n        }\n      }\n    }\n  }\n\n  if (x27c_partner1 != kInvalidUniqueId || x27e_partner2 != kInvalidUniqueId) {\n    return;\n  }\n\n  for (const SConnection& conn : x20_conns) {\n    if (conn.x4_msg != EScriptObjectMessage::Open) {\n      continue;\n    }\n    if (const TCastToConstPtr<CScriptDoor> door = mgr.GetObjectById(mgr.GetIdForScript(conn.x8_objId))) {\n      x27e_partner2 = door->GetUniqueId();\n      break;\n    }\n  }\n}\n\n/* ORIGINAL 0-00 OFFSET: 8007ED4C */\nCScriptDoor::EDoorOpenCondition CScriptDoor::GetDoorOpenCondition(CStateManager& mgr) const {\n  const TCastToPtr<CScriptDock> dock = mgr.ObjectById(x282_dockId);\n  if (!dock) {\n    return EDoorOpenCondition::Ready;\n  }\n\n  if (x25c_animTime < 0.05f || x2a8_30_doClose) {\n    return EDoorOpenCondition::Loading;\n  }\n\n  const TAreaId destArea = dock->GetAreaId();\n  if (destArea < 0 || destArea >= mgr.GetWorld()->GetNumAreas()) {\n    return EDoorOpenCondition::NotReady;\n  }\n\n  if (!mgr.GetWorld()->GetAreaAlways(destArea)->IsPostConstructed()) {\n    return EDoorOpenCondition::Loading;\n  }\n\n  if (!mgr.GetWorld()->AreSkyNeedsMet()) {\n    return EDoorOpenCondition::Loading;\n  }\n\n  const TAreaId connArea = mgr.GetWorld()\n                               ->GetAreaAlways(dock->GetAreaId())\n                               ->GetDock(dock->GetDockId())\n                               ->GetConnectedAreaId(dock->GetDockReference(mgr));\n\n  if (connArea == kInvalidAreaId) {\n    return EDoorOpenCondition::NotReady;\n  }\n\n  CWorld* world = mgr.GetWorld();\n  const CGameArea* area = world->GetAreaAlways(connArea);\n\n  if (!area->IsPostConstructed()) {\n    mgr.SendScriptMsg(dock, GetUniqueId(), EScriptObjectMessage::SetToMax);\n    return EDoorOpenCondition::Loading;\n  }\n\n  if (area->GetPostConstructed()->x113c_playerActorsLoading != 0) {\n    return EDoorOpenCondition::Loading;\n  }\n\n  for (const CEntity* ent : mgr.GetPlatformAndDoorObjectList()) {\n    const TCastToConstPtr<CScriptDoor> door(ent);\n    if (!door || door->GetUniqueId() == GetUniqueId())\n      continue;\n\n    if (door->GetAreaIdAlways() == GetAreaIdAlways() && door->x2a8_25_wasOpen) {\n      if (door->x282_dockId != kInvalidUniqueId) {\n        return EDoorOpenCondition::Loading;\n      }\n    }\n  }\n\n  for (const CGameArea& aliveArea : *world) {\n    if (aliveArea.GetAreaId() == area->GetAreaId()) {\n      continue;\n    }\n\n    if (!aliveArea.IsFinishedOccluding()) {\n      return EDoorOpenCondition::Loading;\n    }\n  }\n\n  // if (area->TryTakingOutOfARAM())\n  {\n    if (world->GetMapWorld()->IsMapAreasStreaming()) {\n      return EDoorOpenCondition::Loading;\n    }\n  }\n\n  // TODO\n//  if (!CGraphics::g_BooFactory->areShadersReady()) {\n//    return EDoorOpenCondition::Loading;\n//  }\n\n  return EDoorOpenCondition::Ready;\n}\n\n/* ORIGINAL 0-00 OFFSET: 8007E9D0 */\nvoid CScriptDoor::SetDoorAnimation(EDoorAnimType type) {\n  x260_doorAnimState = type;\n  CModelData* modelData = x64_modelData.get();\n  if (modelData && modelData->GetAnimationData()) {\n    modelData->GetAnimationData()->SetAnimation(CAnimPlaybackParms(s32(type), -1, 1.f, true), false);\n  }\n}\n\nstd::optional<zeus::CAABox> CScriptDoor::GetTouchBounds() const {\n  if (GetActive() && GetMaterialList().HasMaterial(EMaterialTypes::Solid)) {\n    return CPhysicsActor::GetBoundingBox();\n  }\n  return std::nullopt;\n}\n\nstd::optional<zeus::CAABox> CScriptDoor::GetProjectileBounds() const {\n  if (x2a8_28_projectilesCollide) {\n    return zeus::CAABox{x284_modelBounds.min + GetTranslation(), x284_modelBounds.max + GetTranslation()};\n  }\n  return std::nullopt;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptDoor.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/World/CPhysicsActor.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\n\nclass CScriptDoor : public CPhysicsActor {\npublic:\n  enum class EDoorAnimType {\n    Open,\n    Close,\n    Ready,\n  };\n\n  enum class EDoorOpenCondition { NotReady, Loading, Ready };\n\n  float x258_animLen;\n  float x25c_animTime = 0.f;\n  EDoorAnimType x260_doorAnimState = EDoorAnimType::Open;\n  zeus::CAABox x264_;\n  TUniqueId x27c_partner1 = kInvalidUniqueId;\n  TUniqueId x27e_partner2 = kInvalidUniqueId;\n  TUniqueId x280_prevDoor = kInvalidUniqueId;\n  TUniqueId x282_dockId = kInvalidUniqueId;\n  zeus::CAABox x284_modelBounds;\n  zeus::CVector3f x29c_orbitPos;\n\n  bool x2a8_24_closing : 1 = false;\n  bool x2a8_25_wasOpen : 1;\n  bool x2a8_26_isOpen : 1;\n  bool x2a8_27_conditionsMet : 1 = false;\n  bool x2a8_28_projectilesCollide : 1;\n  bool x2a8_29_ballDoor : 1;\n  bool x2a8_30_doClose : 1 = false;\n\npublic:\n  DEFINE_ENTITY\n  CScriptDoor(TUniqueId, std::string_view name, const CEntityInfo& info, const zeus::CTransform&, CModelData&&,\n              const CActorParameters&, const zeus::CVector3f&, const zeus::CAABox&, bool active, bool open, bool, float,\n              bool ballDoor);\n\n  zeus::CVector3f GetOrbitPosition(const CStateManager& mgr) const override;\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) override;\n  void Think(float, CStateManager& mgr) override;\n  void AddToRenderer(const zeus::CFrustum&, CStateManager& mgr) override;\n  void Render(CStateManager&) override {}\n  void ForceClosed(CStateManager&);\n  bool IsConnectedToArea(const CStateManager& mgr, TAreaId area) const;\n  void OpenDoor(TUniqueId, CStateManager&);\n  EDoorOpenCondition GetDoorOpenCondition(CStateManager& mgr) const;\n  void SetDoorAnimation(EDoorAnimType);\n  std::optional<zeus::CAABox> GetTouchBounds() const override;\n  std::optional<zeus::CAABox> GetProjectileBounds() const;\n  bool IsOpen() const { return x2a8_26_isOpen; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptEMPulse.cpp",
    "content": "#include \"Runtime/World/CScriptEMPulse.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Particle/CGenDescription.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCScriptEMPulse::CScriptEMPulse(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                               const zeus::CTransform& xf, bool active, float initialRadius, float finalRadius,\n                               float duration, float interferenceDur, float f5, float interferenceMag, float f7,\n                               CAssetId partId)\n: CActor(uid, active, name, info, xf, CModelData::CModelDataNull(), CMaterialList(EMaterialTypes::Projectile),\n         CActorParameters::None().HotInThermal(true), kInvalidUniqueId)\n, xe8_duration(duration)\n, xec_finalRadius(finalRadius)\n, xf0_currentRadius(initialRadius)\n, xf4_initialRadius(initialRadius)\n, xf8_interferenceDur(interferenceDur)\n, xfc_(f5)\n, x100_interferenceMag(interferenceMag)\n, x104_(f7)\n, x108_particleDesc(g_SimplePool->GetObj({SBIG('PART'), partId})) {}\n\nvoid CScriptEMPulse::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptEMPulse::Think(float dt, CStateManager& mgr) {\n  if (!GetActive()) {\n    return;\n  }\n\n  xf0_currentRadius += ((xec_finalRadius - xf4_initialRadius) / xe8_duration) * dt;\n  if (xf0_currentRadius < xec_finalRadius) {\n    mgr.FreeScriptObject(GetUniqueId());\n  }\n\n  x114_particleGen->Update(dt);\n}\n\nvoid CScriptEMPulse::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  CActor::AcceptScriptMsg(msg, uid, mgr);\n\n  if (msg != EScriptObjectMessage::Activate) {\n    return;\n  }\n\n  x114_particleGen = std::make_unique<CElementGen>(x108_particleDesc, CElementGen::EModelOrientationType::Normal,\n                                                   CElementGen::EOptionalSystemFlags::One);\n\n  x114_particleGen->SetOrientation(GetTransform().getRotation());\n  x114_particleGen->SetGlobalTranslation(GetTranslation());\n  x114_particleGen->SetParticleEmission(true);\n  mgr.GetPlayerState()->GetStaticInterference().AddSource(GetUniqueId(), x100_interferenceMag, xf8_interferenceDur);\n}\n\nvoid CScriptEMPulse::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) {\n  CActor::AddToRenderer(frustum, mgr);\n  if (GetActive()) {\n    g_Renderer->AddParticleGen(*x114_particleGen);\n  }\n}\n\nvoid CScriptEMPulse::CalculateRenderBounds() { x9c_renderBounds = CalculateBoundingBox(); }\n\nstd::optional<zeus::CAABox> CScriptEMPulse::GetTouchBounds() const { return {CalculateBoundingBox()}; }\n\nvoid CScriptEMPulse::Touch(CActor& act, CStateManager& mgr) {\n  if (!GetActive()) {\n    return;\n  }\n\n  if (const TCastToPtr<CPlayer> pl = act) {\n    const zeus::CVector3f posDiff = GetTranslation() - pl->GetTranslation();\n    const float diffMagnitude = posDiff.magnitude();\n    if (diffMagnitude < xec_finalRadius) {\n      const float multi = 1.f - (diffMagnitude / xec_finalRadius);\n      const float dur = (multi * (xfc_ - xf8_interferenceDur)) + xf8_interferenceDur;\n      const float mag = (multi * (x104_ - x100_interferenceMag)) + x100_interferenceMag;\n\n      if (dur > pl->GetStaticTimer()) {\n        pl->SetHudDisable(dur, 0.5f, 2.5f);\n        pl->TryToBreakOrbit(mgr.GetPlayer().GetOrbitTargetId(),\n                                     CPlayer::EPlayerOrbitRequest::ActivateOrbitSource, mgr);\n      }\n      mgr.GetPlayerState()->GetStaticInterference().AddSource(GetUniqueId(), mag, dur);\n    }\n  }\n}\n\nzeus::CAABox CScriptEMPulse::CalculateBoundingBox() const {\n  return zeus::CAABox(GetTranslation() - xf0_currentRadius, GetTranslation() + xf0_currentRadius);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptEMPulse.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include \"Runtime/World/CActor.hpp\"\n\nnamespace metaforce {\nclass CElementGen;\nclass CGenDescription;\n\nclass CScriptEMPulse : public CActor {\n  float xe8_duration;\n  float xec_finalRadius;\n  float xf0_currentRadius;\n  float xf4_initialRadius;\n  float xf8_interferenceDur;\n  float xfc_;\n  float x100_interferenceMag;\n  float x104_;\n  TCachedToken<CGenDescription> x108_particleDesc;\n  std::unique_ptr<CElementGen> x114_particleGen;\n  zeus::CAABox CalculateBoundingBox() const;\n\npublic:\n  DEFINE_ENTITY\n  CScriptEMPulse(TUniqueId, std::string_view, const CEntityInfo&, const zeus::CTransform&, bool, float, float, float,\n                 float, float, float, float, CAssetId);\n\n  void Accept(IVisitor&) override;\n  void Think(float, CStateManager&) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void AddToRenderer(const zeus::CFrustum&, CStateManager&) override;\n  void CalculateRenderBounds() override;\n  std::optional<zeus::CAABox> GetTouchBounds() const override;\n  void Touch(CActor&, CStateManager&) override;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptEffect.cpp",
    "content": "#include \"Runtime/World/CScriptEffect.hpp\"\n\n#include \"Runtime/CPlayerState.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Camera/CGameCamera.hpp\"\n#include \"Runtime/Character/CModelData.hpp\"\n#include \"Runtime/Collision/CMaterialList.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/Particle/CParticleElectric.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/World/CGameLight.hpp\"\n#include \"Runtime/World/CScriptTrigger.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nu32 CScriptEffect::g_NumParticlesUpdating = 0;\nu32 CScriptEffect::g_NumParticlesRendered = 0;\n\nCScriptEffect::CScriptEffect(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                             const zeus::CVector3f& scale, CAssetId partId, CAssetId elscId, bool hotInThermal,\n                             bool noTimerUnlessAreaOccluded, bool rebuildSystemsOnActivate, bool active,\n                             bool useRateInverseCamDist, float rateInverseCamDist, float rateInverseCamDistRate,\n                             float duration, float durationResetWhileVisible, bool useRateCamDistRange,\n                             float rateCamDistRangeMin, float rateCamDistRangeMax, float rateCamDistRangeFarRate,\n                             bool combatVisorVisible, bool thermalVisorVisible, bool xrayVisorVisible,\n                             const CLightParameters& lParms, bool dieWhenSystemsDone)\n: CActor(uid, active, name, info, xf, CModelData::CModelDataNull(), CMaterialList(),\n         CActorParameters::None().HotInThermal(hotInThermal), kInvalidUniqueId)\n, x10c_partId(partId)\n, x110_24_enable(active)\n, x110_25_noTimerUnlessAreaOccluded(noTimerUnlessAreaOccluded)\n, x110_26_rebuildSystemsOnActivate(rebuildSystemsOnActivate)\n, x110_27_useRateInverseCamDist(useRateInverseCamDist)\n, x110_28_combatVisorVisible(combatVisorVisible)\n, x110_29_thermalVisorVisible(thermalVisorVisible)\n, x110_30_xrayVisorVisible(xrayVisorVisible)\n, x110_31_anyVisorVisible(xrayVisorVisible && thermalVisorVisible && combatVisorVisible)\n, x111_24_useRateCamDistRange(useRateCamDistRange)\n, x111_25_dieWhenSystemsDone(dieWhenSystemsDone)\n, x114_rateInverseCamDist(rateInverseCamDist)\n, x118_rateInverseCamDistSq(rateInverseCamDist * rateInverseCamDist)\n, x11c_rateInverseCamDistRate(rateInverseCamDistRate)\n, x120_rateCamDistRangeMin(rateCamDistRangeMin)\n, x124_rateCamDistRangeMax(rateCamDistRangeMax)\n, x128_rateCamDistRangeFarRate(rateCamDistRangeFarRate)\n, x12c_remTime(duration)\n, x130_duration(duration)\n, x134_durationResetWhileVisible(durationResetWhileVisible)\n, x138_actorLights(lParms.MakeActorLights()) {\n  if (partId.IsValid()) {\n    xf8_particleSystemToken = g_SimplePool->GetObj({FOURCC('PART'), partId});\n    x104_particleSystem = std::make_unique<CElementGen>(xf8_particleSystemToken);\n    zeus::CTransform newXf = xf;\n    newXf.origin = zeus::skZero3f;\n    x104_particleSystem->SetOrientation(newXf);\n    x104_particleSystem->SetGlobalTranslation(xf.origin);\n    x104_particleSystem->SetGlobalScale(scale);\n    x104_particleSystem->SetParticleEmission(active);\n    x104_particleSystem->SetModulationColor(lParms.GetNoLightsAmbient());\n    x104_particleSystem->SetModelsUseLights(x138_actorLights != nullptr);\n  }\n\n  if (elscId.IsValid()) {\n    xe8_electricToken = g_SimplePool->GetObj({FOURCC('ELSC'), elscId});\n    xf4_electric = std::make_unique<CParticleElectric>(xe8_electricToken);\n    zeus::CTransform newXf = xf;\n    newXf.origin = zeus::skZero3f;\n    xf4_electric->SetOrientation(newXf);\n    xf4_electric->SetGlobalTranslation(xf.origin);\n    xf4_electric->SetGlobalScale(scale);\n    xf4_electric->SetParticleEmission(active);\n    xf4_electric->SetModulationColor(lParms.GetNoLightsAmbient());\n  }\n  xe7_29_drawEnabled = true;\n}\n\nvoid CScriptEffect::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptEffect::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  bool oldActive = GetActive();\n\n  switch (msg) {\n  case EScriptObjectMessage::Activate:\n    if (x110_26_rebuildSystemsOnActivate) {\n      if (x104_particleSystem) {\n        const zeus::CVector3f scale = x104_particleSystem->GetGlobalScale();\n        const zeus::CColor color = x104_particleSystem->GetModulationColor();\n        x104_particleSystem = std::make_unique<CElementGen>(xf8_particleSystemToken);\n        zeus::CTransform newXf = GetTransform();\n        newXf.origin = zeus::skZero3f;\n        x104_particleSystem->SetOrientation(newXf);\n        x104_particleSystem->SetGlobalTranslation(GetTranslation());\n        x104_particleSystem->SetGlobalScale(scale);\n        x104_particleSystem->SetModulationColor(color);\n        x104_particleSystem->SetModelsUseLights(x138_actorLights != nullptr);\n      }\n\n      if (xf4_electric) {\n        const zeus::CVector3f scale = xf4_electric->GetGlobalScale();\n        const zeus::CColor color = xf4_electric->GetModulationColor();\n        xf4_electric = std::make_unique<CParticleElectric>(xe8_electricToken);\n        zeus::CTransform newXf = GetTransform();\n        newXf.origin = zeus::skZero3f;\n        xf4_electric->SetOrientation(newXf);\n        xf4_electric->SetGlobalTranslation(GetTranslation());\n        xf4_electric->SetGlobalScale(scale);\n        xf4_electric->SetModulationColor(color);\n      }\n    }\n    break;\n  case EScriptObjectMessage::Registered:\n    if (x104_particleSystem && x104_particleSystem->SystemHasLight()) {\n      x108_lightId = mgr.AllocateUniqueId();\n      mgr.AddObject(new CGameLight(x108_lightId, GetAreaIdAlways(), GetActive(),\n                                   std::string(\"EffectPLight_\") + GetName().data(), x34_transform, GetUniqueId(),\n                                   x104_particleSystem->GetLight(), x10c_partId.Value(), 1, 0.f));\n    }\n    break;\n  case EScriptObjectMessage::Deleted:\n    if (x108_lightId != kInvalidUniqueId) {\n      mgr.FreeScriptObject(x108_lightId);\n      x108_lightId = kInvalidUniqueId;\n    }\n    break;\n  case EScriptObjectMessage::InitializedInArea:\n    for (const SConnection& conn : x20_conns) {\n      if ((conn.x0_state == EScriptObjectState::Modify && conn.x4_msg == EScriptObjectMessage::Follow) ||\n          (conn.x0_state == EScriptObjectState::InheritBounds && conn.x4_msg == EScriptObjectMessage::Activate)) {\n        const auto search = mgr.GetIdListForScript(conn.x8_objId);\n        for (auto it = search.first; it != search.second; ++it) {\n          if (TCastToConstPtr<CScriptTrigger>(mgr.GetObjectById(it->second))) {\n            x13c_triggerId = it->second;\n          }\n        }\n      }\n    }\n    break;\n  default:\n    break;\n  }\n\n  CActor::AcceptScriptMsg(msg, uid, mgr);\n\n  const TCastToPtr<CActor> light = mgr.ObjectById(x108_lightId);\n  mgr.SendScriptMsg(light, uid, msg);\n\n  if (oldActive != GetActive()) {\n    if (GetActive()) {\n      std::vector<TUniqueId> playIds;\n      for (const SConnection& conn : x20_conns) {\n        if (conn.x0_state != EScriptObjectState::Play || conn.x4_msg != EScriptObjectMessage::Activate) {\n          continue;\n        }\n\n        const TUniqueId scriptId = mgr.GetIdForScript(conn.x8_objId);\n        if (scriptId != kInvalidUniqueId) {\n          playIds.push_back(scriptId);\n        }\n      }\n\n      if (!playIds.empty()) {\n        const TCastToConstPtr<CActor> otherAct =\n            mgr.GetObjectById(playIds[u32(0.99f * playIds.size() * mgr.GetActiveRandom()->Float())]);\n        if (otherAct) {\n          SetTransform(otherAct->GetTransform());\n          if (light) {\n            light->SetTransform(otherAct->GetTransform());\n          }\n        }\n      }\n    }\n\n    x110_24_enable = true;\n    if (x104_particleSystem) {\n      x104_particleSystem->SetParticleEmission(GetActive());\n    }\n    if (xf4_electric) {\n      xf4_electric->SetParticleEmission(GetActive());\n    }\n\n    if (GetActive()) {\n      x12c_remTime = zeus::max(x12c_remTime, x130_duration);\n    }\n  }\n}\n\nvoid CScriptEffect::PreRender(CStateManager& mgr, const zeus::CFrustum&) {\n  if (x110_27_useRateInverseCamDist || x111_24_useRateCamDistRange) {\n    float genRate = 1.f;\n    const CGameCamera* cam = mgr.GetCameraManager()->GetCurrentCamera(mgr);\n    const float camMagSq = (cam->GetTranslation() - GetTranslation()).magSquared();\n\n    float camMag = 0.f;\n    if (camMagSq > 0.001f) {\n      camMag = std::sqrt(camMagSq);\n    }\n    if (x110_27_useRateInverseCamDist && camMagSq < x118_rateInverseCamDistSq) {\n      genRate = (1.f - x11c_rateInverseCamDistRate) * (camMag / x114_rateInverseCamDist) + x11c_rateInverseCamDistRate;\n    }\n    if (x111_24_useRateCamDistRange) {\n      const float t = zeus::min(1.f, zeus::max(0.f, camMag - x120_rateCamDistRangeMin) /\n                                         (x124_rateCamDistRangeMax - x120_rateCamDistRangeMin));\n      genRate = (1.f - t) * genRate + t * x128_rateCamDistRangeFarRate;\n    }\n\n    x104_particleSystem->SetGeneratorRate(genRate);\n  }\n\n  if (!mgr.GetObjectById(x13c_triggerId)) {\n    x13c_triggerId = kInvalidUniqueId;\n  }\n}\n\nvoid CScriptEffect::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) {\n  if (!x111_26_canRender) {\n    x12c_remTime = zeus::max(x12c_remTime, x134_durationResetWhileVisible);\n    return;\n  }\n\n  if (!frustum.aabbFrustumTest(x9c_renderBounds)) {\n    return;\n  }\n  x12c_remTime = zeus::max(x12c_remTime, x134_durationResetWhileVisible);\n\n  if (x110_31_anyVisorVisible) {\n    bool visible = false;\n    const CPlayerState::EPlayerVisor visor = mgr.GetPlayerState()->GetActiveVisor(mgr);\n    if (visor == CPlayerState::EPlayerVisor::Combat || visor == CPlayerState::EPlayerVisor::Scan) {\n      visible = x110_28_combatVisorVisible;\n    } else if (visor == CPlayerState::EPlayerVisor::XRay) {\n      visible = x110_30_xrayVisorVisible;\n    } else if (visor == CPlayerState::EPlayerVisor::Thermal) {\n      visible = x110_29_thermalVisorVisible;\n    }\n\n    if (visible && x138_actorLights) {\n      const CGameArea* area = mgr.GetWorld()->GetAreaAlways(GetAreaIdAlways());\n      x138_actorLights->BuildAreaLightList(mgr, *area,\n                                           zeus::CAABox{x9c_renderBounds.center(), x9c_renderBounds.center()});\n      x138_actorLights->BuildDynamicLightList(mgr, x9c_renderBounds);\n    }\n    EnsureRendered(mgr);\n  }\n}\n\nvoid CScriptEffect::Render(CStateManager& mgr) {\n  if (x138_actorLights) {\n    x138_actorLights->ActivateLights();\n  }\n  if (x104_particleSystem && x104_particleSystem->GetParticleCountAll() > 0) {\n    g_NumParticlesRendered += x104_particleSystem->GetParticleCountAll();\n    x104_particleSystem->Render();\n  }\n  if (xf4_electric && xf4_electric->GetParticleCount() > 0) {\n    g_NumParticlesRendered += xf4_electric->GetParticleCount();\n    xf4_electric->Render();\n  }\n}\n\nvoid CScriptEffect::Think(float dt, CStateManager& mgr) {\n  if (xe4_28_transformDirty) {\n    if (x104_particleSystem) {\n      zeus::CTransform newXf = x34_transform;\n      newXf.origin = zeus::skZero3f;\n      x104_particleSystem->SetOrientation(newXf);\n      x104_particleSystem->SetGlobalTranslation(x34_transform.origin);\n    }\n    if (xf4_electric) {\n      zeus::CTransform newXf = x34_transform;\n      newXf.origin = zeus::skZero3f;\n      xf4_electric->SetOrientation(newXf);\n      xf4_electric->SetGlobalTranslation(x34_transform.origin);\n    }\n\n    if (const TCastToPtr<CActor> act = mgr.ObjectById(x108_lightId)) {\n      act->SetTransform(GetTransform());\n    }\n\n    xe4_28_transformDirty = false;\n  }\n\n  if (x110_25_noTimerUnlessAreaOccluded) {\n    const CGameArea* area = mgr.GetWorld()->GetAreaAlways(GetAreaIdAlways());\n    const auto visible = area->IsPostConstructed() ? area->GetOcclusionState() : CGameArea::EOcclusionState::Occluded;\n\n    if (visible == CGameArea::EOcclusionState::Occluded && x12c_remTime <= 0.f) {\n      return;\n    }\n  } else if (x12c_remTime <= 0.f) {\n    return;\n  }\n\n  x12c_remTime -= dt;\n\n  if (x110_24_enable) {\n    if (x104_particleSystem) {\n      x104_particleSystem->Update(dt);\n      g_NumParticlesUpdating += x104_particleSystem->GetParticleCountAll();\n    }\n\n    if (xf4_electric) {\n      xf4_electric->Update(dt);\n      g_NumParticlesUpdating += xf4_electric->GetParticleCount();\n    }\n\n    if (x108_lightId != kInvalidUniqueId) {\n      if (const TCastToPtr<CGameLight> light = mgr.ObjectById(x108_lightId)) {\n        if (x30_24_active) {\n          light->SetLight(x104_particleSystem->GetLight());\n        }\n      }\n    }\n\n    if (x111_25_dieWhenSystemsDone) {\n      x140_destroyDelayTimer += dt;\n      if (x140_destroyDelayTimer > 15.f || AreBothSystemsDeleteable()) {\n        mgr.FreeScriptObject(GetUniqueId());\n        return;\n      }\n    }\n  }\n\n  if (x104_particleSystem) {\n    if (xb4_drawFlags.x0_blendMode != 0) {\n      x104_particleSystem->SetModulationColor(xb4_drawFlags.x4_color);\n    } else {\n      x104_particleSystem->SetModulationColor(zeus::skWhite);\n    }\n  }\n}\n\nvoid CScriptEffect::CalculateRenderBounds() {\n  std::optional<zeus::CAABox> particleBounds;\n  if (x104_particleSystem) {\n    particleBounds = x104_particleSystem->GetBounds();\n  }\n\n  std::optional<zeus::CAABox> electricBounds;\n  if (xf4_electric) {\n    electricBounds = xf4_electric->GetBounds();\n  }\n\n  if (particleBounds || electricBounds) {\n    zeus::CAABox renderBounds;\n    if (particleBounds) {\n      renderBounds.accumulateBounds(*particleBounds);\n    }\n    if (electricBounds) {\n      renderBounds.accumulateBounds(*electricBounds);\n    }\n    x9c_renderBounds = renderBounds;\n    x111_26_canRender = true;\n  } else {\n    x9c_renderBounds = {GetTranslation(), GetTranslation()};\n    x111_26_canRender = false;\n  }\n}\n\nzeus::CAABox CScriptEffect::GetSortingBounds(const CStateManager& mgr) const {\n  if (x13c_triggerId == kInvalidUniqueId) {\n    return x9c_renderBounds;\n  }\n\n  return static_cast<const CScriptTrigger*>(mgr.GetObjectById(x13c_triggerId))->GetTriggerBoundsWR();\n}\n\nbool CScriptEffect::AreBothSystemsDeleteable() const {\n  if (x104_particleSystem && !x104_particleSystem->IsSystemDeletable()) {\n    return false;\n  }\n\n  if (xf4_electric && !xf4_electric->IsSystemDeletable()) {\n    return false;\n  }\n\n  return true;\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptEffect.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <string_view>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/World/CActor.hpp\"\n\nnamespace metaforce {\nclass CElementGen;\nclass CParticleElectric;\n\nclass CScriptEffect : public CActor {\n  static u32 g_NumParticlesUpdating;\n  static u32 g_NumParticlesRendered;\n  TLockedToken<CElectricDescription> xe8_electricToken;\n  std::unique_ptr<CParticleElectric> xf4_electric;\n  TLockedToken<CGenDescription> xf8_particleSystemToken;\n  std::unique_ptr<CElementGen> x104_particleSystem;\n  TUniqueId x108_lightId = kInvalidUniqueId;\n  CAssetId x10c_partId;\n  bool x110_24_enable : 1;\n  bool x110_25_noTimerUnlessAreaOccluded : 1;\n  bool x110_26_rebuildSystemsOnActivate : 1;\n  bool x110_27_useRateInverseCamDist : 1;\n  bool x110_28_combatVisorVisible : 1;\n  bool x110_29_thermalVisorVisible : 1;\n  bool x110_30_xrayVisorVisible : 1;\n  bool x110_31_anyVisorVisible : 1;\n  bool x111_24_useRateCamDistRange : 1;\n  bool x111_25_dieWhenSystemsDone : 1;\n  bool x111_26_canRender : 1 = false;\n  float x114_rateInverseCamDist;\n  float x118_rateInverseCamDistSq;\n  float x11c_rateInverseCamDistRate;\n  float x120_rateCamDistRangeMin;\n  float x124_rateCamDistRangeMax;\n  float x128_rateCamDistRangeFarRate;\n  float x12c_remTime;\n  float x130_duration;\n  float x134_durationResetWhileVisible;\n  std::unique_ptr<CActorLights> x138_actorLights;\n  TUniqueId x13c_triggerId = kInvalidUniqueId;\n  float x140_destroyDelayTimer = 0.f;\n\npublic:\n  DEFINE_ENTITY\n  CScriptEffect(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                const zeus::CVector3f& scale, CAssetId partId, CAssetId elscId, bool hotInThermal,\n                bool noTimerUnlessAreaOccluded, bool rebuildSystemsOnActivate, bool active, bool useRateInverseCamDist,\n                float rateInverseCamDist, float rateInverseCamDistRate, float duration, float durationResetWhileVisible,\n                bool useRateCamDistRange, float rateCamDistRangeMin, float rateCamDistRangeMax,\n                float rateCamDistRangeFarRate, bool combatVisorVisible, bool thermalVisorVisible, bool xrayVisorVisible,\n                const CLightParameters& lParms, bool dieWhenSystemsDone);\n\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void PreRender(CStateManager&, const zeus::CFrustum&) override;\n  void AddToRenderer(const zeus::CFrustum&, CStateManager&) override;\n  void Render(CStateManager&) override;\n  void Think(float, CStateManager&) override;\n  bool CanRenderUnsorted(const CStateManager&) const override { return false; }\n  void SetActive(bool active) override {\n    CActor::SetActive(active);\n    xe7_29_drawEnabled = true;\n  }\n  void CalculateRenderBounds() override;\n  zeus::CAABox GetSortingBounds(const CStateManager&) const override;\n  bool AreBothSystemsDeleteable() const;\n  static void ResetParticleCounts() {\n    g_NumParticlesUpdating = 0;\n    g_NumParticlesRendered = 0;\n  }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptGenerator.cpp",
    "content": "#include \"Runtime/World/CScriptGenerator.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CWallCrawlerSwarm.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCScriptGenerator::CScriptGenerator(TUniqueId uid, std::string_view name, const CEntityInfo& info, u32 spawnCount,\n                                   bool noReuseFollowers, const zeus::CVector3f& vec1, bool noInheritXf, bool active,\n                                   float minScale, float maxScale)\n: CEntity(uid, info, active, name)\n, x34_spawnCount(spawnCount)\n, x38_24_noReuseFollowers(noReuseFollowers)\n, x38_25_noInheritTransform(noInheritXf)\n, x3c_offset(vec1)\n, x48_minScale(minScale)\n, x4c_maxScale(maxScale) {}\n\nvoid CScriptGenerator::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptGenerator::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& stateMgr) {\n  switch (msg) {\n  case EScriptObjectMessage::SetToZero: {\n    if (!GetActive()) {\n      break;\n    }\n\n    std::vector<TUniqueId> follows;\n    follows.reserve(x20_conns.size());\n    for (const SConnection& conn : x20_conns) {\n      if (conn.x0_state != EScriptObjectState::Zero || conn.x4_msg != EScriptObjectMessage::Follow) {\n        continue;\n      }\n\n      const TUniqueId uid = stateMgr.GetIdForScript(conn.x8_objId);\n      if (uid != kInvalidUniqueId) {\n        const CEntity* ent = stateMgr.GetObjectById(uid);\n        if (ent && ent->GetActive()) {\n          follows.push_back(uid);\n        }\n      }\n    }\n\n    if (follows.empty()) {\n      follows.push_back(sender);\n    }\n\n    std::vector<std::pair<TUniqueId, TEditorId>> activates;\n    activates.reserve(x20_conns.size());\n\n    for (const SConnection& conn : x20_conns) {\n      if (conn.x0_state != EScriptObjectState::Zero) {\n        continue;\n      }\n\n      TUniqueId uid = stateMgr.GetIdForScript(conn.x8_objId);\n      if (uid == kInvalidUniqueId) {\n        continue;\n      }\n\n      if (conn.x4_msg == EScriptObjectMessage::Activate) {\n        if (!stateMgr.GetObjectById(uid)) {\n          continue;\n        }\n        activates.emplace_back(uid, conn.x8_objId);\n      } else {\n        stateMgr.SendScriptMsgAlways(uid, GetUniqueId(), conn.x4_msg);\n      }\n    }\n\n    if (activates.empty()) {\n      break;\n    }\n\n    for (u32 i = 0; i < x34_spawnCount; ++i) {\n      if (activates.size() == 0 || follows.size() == 0) {\n        break;\n      }\n\n      u32 activatesRand = 0.99f * (stateMgr.GetActiveRandom()->Float() * activates.size());\n      const u32 followsRand = 0.99f * (stateMgr.GetActiveRandom()->Float() * follows.size());\n\n      for (u32 j = 0; j < activates.size(); ++j) {\n        if (TCastToConstPtr<CScriptSound>(stateMgr.GetObjectById(activates[j].first))) {\n          activatesRand = j;\n          break;\n        }\n      }\n\n      const std::pair<TUniqueId, TEditorId> idPair = activates[activatesRand];\n      CEntity* activate = stateMgr.ObjectById(idPair.first);\n      CEntity* follow = stateMgr.ObjectById(follows[followsRand]);\n\n      if (!activate || !follow) {\n        break;\n      }\n\n      const bool oldGeneratingObject = stateMgr.GetIsGeneratingObject();\n      stateMgr.SetIsGeneratingObject(true);\n      const std::pair<TEditorId, TUniqueId> objId = stateMgr.GenerateObject(idPair.second);\n      stateMgr.SetIsGeneratingObject(oldGeneratingObject);\n\n      if (objId.second != kInvalidUniqueId) {\n        if (CEntity* genObj = stateMgr.ObjectById(objId.second)) {\n          const TCastToPtr<CActor> activateActor(genObj);\n          const TCastToConstPtr<CActor> followActor(follow);\n          const TCastToConstPtr<CWallCrawlerSwarm> wallCrawlerSwarm(follow);\n\n          if (activateActor && wallCrawlerSwarm) {\n            if (!x38_25_noInheritTransform) {\n              activateActor->SetTransform(wallCrawlerSwarm->GetTransform());\n            }\n            activateActor->SetTranslation(wallCrawlerSwarm->GetLastKilledOffset() + x3c_offset);\n          } else if (activateActor && followActor) {\n            if (!x38_25_noInheritTransform) {\n              activateActor->SetTransform(followActor->GetTransform());\n            }\n            activateActor->SetTranslation(followActor->GetTranslation() + x3c_offset);\n          }\n\n          const float rnd = stateMgr.GetActiveRandom()->Range(x48_minScale, x4c_maxScale);\n          CModelData* mData = activateActor->GetModelData();\n          if (mData && !mData->IsNull()) {\n            mData->SetScale(rnd * mData->GetScale());\n          }\n\n          stateMgr.SendScriptMsg(genObj, GetUniqueId(), EScriptObjectMessage::Activate);\n        }\n      }\n\n      activates.erase(std::find(activates.begin(), activates.end(), idPair));\n      if (x38_24_noReuseFollowers) {\n        follows.erase(std::find(follows.begin(), follows.end(), follows[followsRand]));\n      }\n    }\n    break;\n  }\n  default:\n    break;\n  }\n\n  CEntity::AcceptScriptMsg(msg, sender, stateMgr);\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptGenerator.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/World/CEntity.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\n\nclass CScriptGenerator : public CEntity {\n  u32 x34_spawnCount;\n  bool x38_24_noReuseFollowers : 1;\n  bool x38_25_noInheritTransform : 1;\n  zeus::CVector3f x3c_offset;\n  float x48_minScale;\n  float x4c_maxScale;\n\npublic:\n  DEFINE_ENTITY\n  CScriptGenerator(TUniqueId uid, std::string_view name, const CEntityInfo& info, u32 spawnCount, bool noReuseFollowers,\n                   const zeus::CVector3f& vec1, bool noInheritXf, bool active, float minScale, float maxScale);\n\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) override;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptGrapplePoint.cpp",
    "content": "#include \"Runtime/World/CScriptGrapplePoint.hpp\"\n\n#include \"Runtime/Character/CModelData.hpp\"\n#include \"Runtime/Collision/CMaterialList.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nCScriptGrapplePoint::CScriptGrapplePoint(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                                         const zeus::CTransform& transform, bool active,\n                                         const CGrappleParameters& params)\n: CActor(uid, active, name, info, transform, CModelData::CModelDataNull(), CMaterialList(EMaterialTypes::Orbit),\n         CActorParameters::None(), kInvalidUniqueId)\n, xe8_touchBounds(x34_transform.origin - 0.5f, x34_transform.origin + 0.5f)\n, x100_parameters(params) {}\n\nvoid CScriptGrapplePoint::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptGrapplePoint::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) {\n  switch (msg) {\n  case EScriptObjectMessage::Activate:\n    if (!GetActive()) {\n      AddMaterial(EMaterialTypes::Orbit, mgr);\n      SetActive(true);\n    }\n    break;\n  case EScriptObjectMessage::Deactivate:\n    if (GetActive()) {\n      RemoveMaterial(EMaterialTypes::Orbit, mgr);\n      SetActive(false);\n    }\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CScriptGrapplePoint::Think(float, CStateManager&) {\n  // Empty\n}\n\nvoid CScriptGrapplePoint::Render(CStateManager&) {\n  // Empty\n}\n\nstd::optional<zeus::CAABox> CScriptGrapplePoint::GetTouchBounds() const { return {xe8_touchBounds}; }\n\nvoid CScriptGrapplePoint::AddToRenderer(const zeus::CFrustum&, CStateManager& mgr) { CActor::EnsureRendered(mgr); }\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptGrapplePoint.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/World/CActor.hpp\"\n#include \"Runtime/World/CGrappleParameters.hpp\"\n\n#include <zeus/CAABox.hpp>\n\nnamespace metaforce {\nclass CScriptGrapplePoint : public CActor {\n  zeus::CAABox xe8_touchBounds;\n  CGrappleParameters x100_parameters;\n\npublic:\n  DEFINE_ENTITY\n  CScriptGrapplePoint(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& transform,\n                      bool active, const CGrappleParameters& params);\n\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void Think(float, CStateManager&) override;\n  void Render(CStateManager&) override;\n  std::optional<zeus::CAABox> GetTouchBounds() const override;\n  void AddToRenderer(const zeus::CFrustum&, CStateManager&) override;\n  const CGrappleParameters& GetGrappleParameters() const { return x100_parameters; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptGunTurret.cpp",
    "content": "#include \"Runtime/World/CScriptGunTurret.hpp\"\n\n#include <array>\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Character/CPASAnimParmData.hpp\"\n#include \"Runtime/Collision/CCollisionActor.hpp\"\n#include \"Runtime/Collision/CCollisionActorManager.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/Particle/CGenDescription.hpp\"\n#include \"Runtime/Weapon/CEnergyProjectile.hpp\"\n#include \"Runtime/Weapon/CGameProjectile.hpp\"\n#include \"Runtime/World/CAiFuncMap.hpp\"\n#include \"Runtime/World/CGameLight.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nnamespace {\nconstexpr CMaterialList skGunMaterialList = {EMaterialTypes::Solid, EMaterialTypes::Character, EMaterialTypes::Orbit,\n                                             EMaterialTypes::Target};\nconstexpr CMaterialList skTurretMaterialList = {EMaterialTypes::Character};\n\nconstexpr std::array<SBurst, 6> skBurst2InfoTemplate{{\n    {3, {1, 2, -1, -1, 0, 0, 0, 0}, 0.150000, 0.050000},\n    {3, {7, 6, -1, -1, 0, 0, 0, 0}, 0.150000, 0.050000},\n    {4, {3, 5, -1, -1, 0, 0, 0, 0}, 0.150000, 0.050000},\n    {60, {16, 4, -1, -1, 0, 0, 0, 0}, 0.150000, 0.050000},\n    {30, {4, 4, -1, -1, 0, 0, 0, 0}, 0.150000, 0.050000},\n    {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0.000000, 0.000000},\n}};\n\nconstexpr std::array<SBurst, 6> skBurst3InfoTemplate{{\n    {30, {4, 5, 4, -1, 0, 0, 0, 0}, 0.150000, 0.050000},\n    {30, {2, 3, 4, -1, 0, 0, 0, 0}, 0.150000, 0.050000},\n    {30, {3, 4, 5, -1, 0, 0, 0, 0}, 0.150000, 0.050000},\n    {5, {16, 1, 2, -1, 0, 0, 0, 0}, 0.150000, 0.050000},\n    {5, {8, 7, 6, -1, 0, 0, 0, 0}, 0.150000, 0.050000},\n    {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0.000000, 0.000000},\n}};\n\nconstexpr std::array<SBurst, 8> skBurst4InfoTemplate{{\n    {5, {16, 1, 2, 3, 0, 0, 0, 0}, 0.150000, 0.050000},\n    {5, {9, 8, 7, 6, 0, 0, 0, 0}, 0.150000, 0.050000},\n    {15, {2, 3, 4, 5, 0, 0, 0, 0}, 0.150000, 0.050000},\n    {15, {5, 4, 3, 2, 0, 0, 0, 0}, 0.150000, 0.050000},\n    {15, {10, 11, 4, 13, 0, 0, 0, 0}, 0.150000, 0.050000},\n    {15, {14, 13, 4, 11, 0, 0, 0, 0}, 0.150000, 0.050000},\n    {30, {2, 4, 4, 6, 0, 0, 0, 0}, 0.150000, 0.050000},\n    {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0.000000, 0.000000},\n}};\n\nconstexpr std::array<SBurst, 6> skOOVBurst2InfoTemplate{{\n    {20, {16, 15, -1, -1, 0, 0, 0, 0}, 0.150000, 0.050000},\n    {20, {8, 9, -1, -1, 0, 0, 0, 0}, 0.150000, 0.050000},\n    {20, {13, 11, -1, -1, 0, 0, 0, 0}, 0.150000, 0.050000},\n    {20, {2, 6, -1, -1, 0, 0, 0, 0}, 0.150000, 0.050000},\n    {20, {3, 4, -1, -1, 0, 0, 0, 0}, 0.150000, 0.050000},\n    {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0.000000, 0.000000},\n}};\n\nconstexpr std::array<SBurst, 6> skOOVBurst3InfoTemplate{{\n    {10, {14, 4, 10, -1, 0, 0, 0, 0}, 0.150000, 0.050000},\n    {10, {15, 13, 4, -1, 0, 0, 0, 0}, 0.150000, 0.050000},\n    {10, {9, 11, 4, -1, 0, 0, 0, 0}, 0.150000, 0.050000},\n    {35, {15, 13, 11, -1, 0, 0, 0, 0}, 0.150000, 0.050000},\n    {35, {9, 11, 13, -1, 0, 0, 0, 0}, 0.150000, 0.050000},\n    {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0.000000, 0.000000},\n}};\n\nconstexpr std::array<SBurst, 7> skOOVBurst4InfoTemplate{{\n    {10, {14, 13, 4, 11, 0, 0, 0, 0}, 0.150000, 0.050000},\n    {30, {1, 15, 13, 11, 0, 0, 0, 0}, 0.150000, 0.050000},\n    {20, {16, 15, 14, 13, 0, 0, 0, 0}, 0.150000, 0.050000},\n    {10, {8, 9, 11, 4, 0, 0, 0, 0}, 0.150000, 0.050000},\n    {10, {1, 15, 13, 4, 0, 0, 0, 0}, 0.150000, 0.050000},\n    {20, {8, 9, 10, 11, 0, 0, 0, 0}, 0.150000, 0.050000},\n    {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0.000000, 0.000000},\n}};\n\nconstexpr std::array<const SBurst*, 7> skBursts{\n    skBurst2InfoTemplate.data(),\n    skBurst3InfoTemplate.data(),\n    skBurst4InfoTemplate.data(),\n    skOOVBurst2InfoTemplate.data(),\n    skOOVBurst3InfoTemplate.data(),\n    skOOVBurst4InfoTemplate.data(),\n    nullptr,\n};\n\nconstexpr std::array StateNames{\n    \"Destroyed\", \"Deactive\", \"DeactiveFromReady\", \"Deactivating\", \"DeactivatingFromReady\", \"Inactive\", \"Ready\",\n    \"PanningA\",  \"PanningB\", \"Targeting\",         \"Firing\",       \"ExitTargeting\",         \"Frenzy\",\n};\n\nconstexpr std::array<u32, 13> skStateToLocoTypeLookup{\n    5, 7, 9, 0, 1, 0, 1, 2, 3, 1, 1, 1, 1,\n};\n} // Anonymous namespace\n\nCScriptGunTurretData::CScriptGunTurretData(CInputStream& in, s32 propCount)\n: x0_intoDeactivateDelay(in.ReadFloat())\n, x4_intoActivateDelay(in.ReadFloat())\n, x8_reloadTime(in.ReadFloat())\n, xc_reloadTimeVariance(in.ReadFloat())\n, x10_panStartTime(in.ReadFloat())\n, x14_panHoldTime(in.ReadFloat())\n, x1c_leftMaxAngle(zeus::degToRad(in.ReadFloat()))\n, x20_rightMaxAngle(zeus::degToRad(in.ReadFloat()))\n, x24_downMaxAngle(zeus::degToRad(in.ReadFloat()))\n, x28_turnSpeed(zeus::degToRad(in.ReadFloat()))\n, x2c_detectionRange(in.ReadFloat())\n, x30_detectionZRange(in.ReadFloat())\n, x34_freezeDuration(in.ReadFloat())\n, x38_freezeVariance(in.ReadFloat())\n, x3c_freezeTimeout(propCount >= 48 ? in.ReadBool() : false)\n, x40_projectileRes(in)\n, x44_projectileDamage(in)\n, x60_idleLightRes(in.Get<CAssetId>())\n, x64_deactivateLightRes(in.Get<CAssetId>())\n, x68_targettingLightRes(in.Get<CAssetId>())\n, x6c_frozenEffectRes(in.Get<CAssetId>())\n, x70_chargingEffectRes(in.Get<CAssetId>())\n, x74_panningEffectRes(in.Get<CAssetId>())\n, x78_visorEffectRes(propCount >= 44 ? in.Get<CAssetId>() : CAssetId())\n, x7c_trackingSoundId(CSfxManager::TranslateSFXID(u16(in.ReadLong())))\n, x7e_lockOnSoundId(CSfxManager::TranslateSFXID(u16(in.ReadLong())))\n, x80_unfreezeSoundId(CSfxManager::TranslateSFXID(u16(in.ReadLong())))\n, x82_stopClankSoundId(CSfxManager::TranslateSFXID(u16(in.ReadLong())))\n, x84_chargingSoundId(CSfxManager::TranslateSFXID(u16(in.ReadLong())))\n, x86_visorSoundId(propCount >= 45 ? CSfxManager::TranslateSFXID(u16(in.ReadLong())) : u16(0xFFFF))\n, x88_extensionModelResId(in.Get<CAssetId>())\n, x8c_extensionDropDownDist(in.ReadFloat())\n, x90_numInitialShots(in.ReadLong())\n, x94_initialShotTableIndex(in.ReadLong())\n, x98_numSubsequentShots(in.ReadLong())\n, x9c_frenzyDuration(propCount >= 47 ? in.ReadFloat() : 3.f)\n, xa0_scriptedStartOnly(propCount >= 46 ? in.ReadBool() : false) {}\n\nCScriptGunTurret::CScriptGunTurret(TUniqueId uid, std::string_view name, ETurretComponent comp, const CEntityInfo& info,\n                                   const zeus::CTransform& xf, CModelData&& mData, const zeus::CAABox& aabb,\n                                   const CHealthInfo& hInfo, const CDamageVulnerability& dVuln,\n                                   const CActorParameters& aParms, const CScriptGunTurretData& turretData)\n: CPhysicsActor(uid, true, name, info, xf, std::move(mData),\n                comp == ETurretComponent::Base ? skTurretMaterialList : skGunMaterialList, aabb, SMoverData(1000.f),\n                aParms, 0.3f, 0.1f)\n, x258_type(comp)\n, x264_healthInfo(hInfo)\n, x26c_damageVuln(dVuln)\n, x2d4_data(turretData)\n, x37c_projectileInfo(turretData.GetProjectileRes(), turretData.GetProjectileDamage())\n, x3a4_burstFire(skBursts.data(), 1)\n, x410_idleLightDesc(g_SimplePool->GetObj({SBIG('PART'), turretData.GetIdleLightRes()}))\n, x41c_deactivateLightDesc(g_SimplePool->GetObj({SBIG('PART'), turretData.GetDeactivateLightRes()}))\n, x428_targettingLightDesc(g_SimplePool->GetObj({SBIG('PART'), turretData.GetTargettingLightRes()}))\n, x434_frozenEffectDesc(g_SimplePool->GetObj({SBIG('PART'), turretData.GetFrozenEffectRes()}))\n, x440_chargingEffectDesc(g_SimplePool->GetObj({SBIG('PART'), turretData.GetChargingEffectRes()}))\n, x44c_panningEffectDesc(g_SimplePool->GetObj({SBIG('PART'), turretData.GetPanningEffectRes()})) {\n  if (turretData.GetVisorEffectRes().IsValid()) {\n    x458_visorEffectDesc = g_SimplePool->GetObj({SBIG('PART'), turretData.GetVisorEffectRes()});\n  }\n  x468_idleLight = std::make_unique<CElementGen>(x410_idleLightDesc);\n  x470_deactivateLight = std::make_unique<CElementGen>(x41c_deactivateLightDesc);\n  x478_targettingLight = std::make_unique<CElementGen>(x428_targettingLightDesc);\n  x480_frozenEffect = std::make_unique<CElementGen>(x434_frozenEffectDesc);\n  x488_chargingEffect = std::make_unique<CElementGen>(x440_chargingEffectDesc);\n  x490_panningEffect = std::make_unique<CElementGen>(x44c_panningEffectDesc);\n  x4fc_extensionOffset = xf.origin;\n  x514_lastFrontVector = xf.frontVector();\n  x544_originalFrontVec = xf.frontVector();\n  x550_originalRightVec = xf.rightVector();\n\n  if (comp == ETurretComponent::Base && HasModelData() && GetModelData()->HasAnimData()) {\n    GetModelData()->EnableLooping(true);\n  }\n\n  x37c_projectileInfo.Token().Lock();\n}\n\nCScriptGunTurret::~CScriptGunTurret() = default;\n\nvoid CScriptGunTurret::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptGunTurret::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  CActor::AcceptScriptMsg(msg, uid, mgr);\n\n  switch (msg) {\n  case EScriptObjectMessage::Activate:\n    if (x49c_collisionManager) {\n      x49c_collisionManager->SetActive(mgr, true);\n    }\n    break;\n  case EScriptObjectMessage::Deactivate:\n    if (x49c_collisionManager) {\n      x49c_collisionManager->SetActive(mgr, false);\n    }\n    break;\n  case EScriptObjectMessage::Registered:\n    if (x258_type == ETurretComponent::Gun) {\n      if (x478_targettingLight->SystemHasLight()) {\n        x498_lightId = mgr.AllocateUniqueId();\n        mgr.AddObject(new CGameLight(x498_lightId, GetAreaIdAlways(), GetActive(),\n                                     std::string(\"ParticleLight_\").append(GetName()), GetTransform(), GetUniqueId(),\n                                     x478_targettingLight->GetLight(), 0, 1, 0.f));\n      }\n      SetupCollisionManager(mgr);\n    } else if (x258_type == ETurretComponent::Base) {\n      const zeus::CVector3f scale = GetModelData()->GetScale();\n      if (x2d4_data.GetExtensionModelResId().IsValid()) {\n        CModelData mData(CStaticRes(x2d4_data.GetExtensionModelResId(), scale));\n        x4a4_extensionModel.emplace(std::move(mData));\n        x4f4_extensionRange = x4a4_extensionModel->GetBounds().max.z() - x4a4_extensionModel->GetBounds().min.z();\n      }\n      SetTurretState(ETurretState::Inactive, mgr);\n    }\n    break;\n  case EScriptObjectMessage::Deleted: {\n    if (x258_type == ETurretComponent::Gun) {\n      if (x498_lightId != kInvalidUniqueId) {\n        mgr.FreeScriptObject(x498_lightId);\n      }\n    }\n    if (x50c_targetingEmitter) {\n      CSfxManager::RemoveEmitter(x50c_targetingEmitter);\n    }\n\n    if (x49c_collisionManager) {\n      x49c_collisionManager->Destroy(mgr);\n    }\n    break;\n  }\n  case EScriptObjectMessage::Start:\n    if (x258_type == ETurretComponent::Base && x520_state == ETurretState::Inactive) {\n      x560_29_scriptedStart = true;\n    }\n    break;\n  case EScriptObjectMessage::Stop:\n    if (x258_type == ETurretComponent::Base && x520_state != ETurretState::Deactive &&\n        x520_state != ETurretState::DeactiveFromReady && x520_state != ETurretState::Deactivating) {\n      SetTurretState((x560_28_hasBeenActivated ? ETurretState::DeactivatingFromReady : ETurretState::Deactivating),\n                     mgr);\n    }\n    break;\n  case EScriptObjectMessage::Action: {\n    if (x258_type == ETurretComponent::Gun) {\n      LaunchProjectile(mgr);\n    } else if (x258_type == ETurretComponent::Base) {\n      PlayAdditiveFlinchAnimation(mgr);\n    }\n    break;\n  }\n  case EScriptObjectMessage::SetToMax: {\n    x560_25_frozen = false;\n    SetMuted(false);\n    break;\n  }\n  case EScriptObjectMessage::SetToZero: {\n    x560_25_frozen = true;\n    SetMuted(true);\n    break;\n  }\n  case EScriptObjectMessage::InitializedInArea: {\n    if (x258_type == ETurretComponent::Base) {\n      for (const SConnection& conn : x20_conns) {\n        if (conn.x0_state != EScriptObjectState::Play || conn.x4_msg != EScriptObjectMessage::Activate) {\n          continue;\n        }\n\n        if (TCastToConstPtr<CScriptGunTurret> gun = mgr.GetObjectById(mgr.GetIdForScript(conn.x8_objId))) {\n          x25c_gunId = mgr.GetIdForScript(conn.x8_objId);\n          x260_lastGunHP = gun->GetHealthInfo(mgr)->GetHP();\n          return;\n        }\n      }\n    }\n    break;\n  }\n  case EScriptObjectMessage::Damage: {\n    if (x258_type == ETurretComponent::Gun && GetHealthInfo(mgr)->GetHP() <= 0.f) {\n      if (const TCastToConstPtr<CGameProjectile> proj = mgr.GetObjectById(uid)) {\n        if ((proj->GetAttribField() & EProjectileAttrib::Wave) == EProjectileAttrib::Wave) {\n          x520_state = ETurretState::Frenzy;\n          RemoveMaterial(EMaterialTypes::Target, EMaterialTypes::Orbit, mgr);\n          mgr.GetPlayer().TryToBreakOrbit(GetUniqueId(), CPlayer::EPlayerOrbitRequest::ActivateOrbitSource,\n                                                   mgr);\n          x53c_freezeRemTime = 0.f;\n        }\n      }\n    }\n    break;\n  }\n  default:\n    break;\n  }\n}\n\nvoid CScriptGunTurret::Think(float dt, CStateManager& mgr) {\n  if (!GetActive()) {\n    return;\n  }\n\n  if (x258_type == ETurretComponent::Base) {\n    if (!x560_25_frozen) {\n      ProcessGunStateMachine(dt, mgr);\n      UpdateTurretAnimation();\n      UpdateGunOrientation(dt, mgr);\n      const zeus::CVector3f vec = UpdateExtensionModelState(dt);\n      const SAdvancementDeltas advancementDeltas = UpdateAnimation(dt, mgr, true);\n      SetTranslation(vec + advancementDeltas.x0_posDelta + GetTranslation());\n      RotateToOR(advancementDeltas.xc_rotDelta, dt);\n    } else {\n      Stop();\n    }\n\n    UpdateTargettingSound(dt);\n  } else if (x258_type == ETurretComponent::Gun) {\n    UpdateGunParticles(dt, mgr);\n    const SAdvancementDeltas deltas = UpdateAnimation(dt, mgr, true);\n    MoveToOR(deltas.x0_posDelta, dt);\n    RotateToOR(deltas.xc_rotDelta, dt);\n    UpdateGunCollisionManager(dt, mgr);\n    UpdateFrozenState(dt, mgr);\n  }\n  UpdateHealthInfo(mgr);\n}\n\nvoid CScriptGunTurret::Touch(CActor& act, CStateManager& mgr) {\n  if (x258_type != ETurretComponent::Gun) {\n    return;\n  }\n\n  if (const TCastToConstPtr<CGameProjectile> proj = act) {\n    const CPlayer& player = mgr.GetPlayer();\n    if (proj->GetOwnerId() == player.GetUniqueId()) {\n      const CDamageVulnerability* dVuln = GetDamageVulnerability();\n      if (!x560_24_dead && x520_state != ETurretState::Frenzy &&\n          (proj->GetAttribField() & EProjectileAttrib::Ice) == EProjectileAttrib::Ice &&\n          dVuln->WeaponHits(CWeaponMode::Ice(), false)) {\n        x560_25_frozen = true;\n        SendScriptMsgs(EScriptObjectState::Zero, mgr, EScriptObjectMessage::None);\n        x53c_freezeRemTime =\n            mgr.GetActiveRandom()->Float() * x2d4_data.GetFreezeVariance() + x2d4_data.GetFreezeDuration();\n        SetMuted(true);\n      }\n      SendScriptMsgs(EScriptObjectState::Damage, mgr, EScriptObjectMessage::None);\n    }\n  }\n}\n\nstd::optional<zeus::CAABox> CScriptGunTurret::GetTouchBounds() const {\n  if (GetActive() && GetMaterialList().HasMaterial(EMaterialTypes::Solid)) {\n    return GetBoundingBox();\n  }\n  return std::nullopt;\n}\n\nzeus::CVector3f CScriptGunTurret::GetOrbitPosition(const CStateManager& mgr) const { return GetAimPosition(mgr, 0.f); }\n\nzeus::CVector3f CScriptGunTurret::GetAimPosition(const CStateManager&, float) const {\n  if (x258_type == ETurretComponent::Base) {\n    return GetTranslation() + x34_transform.rotate(GetLocatorTransform(\"Gun_SDK\"sv).origin);\n  }\n\n  return GetTranslation();\n}\n\nvoid CScriptGunTurret::SetupCollisionManager(CStateManager& mgr) {\n  std::vector<CJointCollisionDescription> jointDescs;\n  jointDescs.reserve(2);\n  const CAnimData* animData = GetModelData()->GetAnimationData();\n  x508_gunSDKSeg = animData->GetLocatorSegId(\"Gun_SDK\"sv);\n  const CSegId blastLCTR = animData->GetLocatorSegId(\"Blast_LCTR\"sv);\n  jointDescs.push_back(CJointCollisionDescription::SphereSubdivideCollision(\n      x508_gunSDKSeg, blastLCTR, 0.6f, 1.f, CJointCollisionDescription::EOrientationType::One, \"Gun_SDK\"sv, 1000.f));\n  jointDescs.push_back(CJointCollisionDescription::SphereCollision(blastLCTR, 0.3f, \"Blast_LCTR\"sv, 1000.f));\n\n  x49c_collisionManager =\n      std::make_unique<CCollisionActorManager>(mgr, GetUniqueId(), GetAreaIdAlways(), jointDescs, true);\n  x49c_collisionManager->SetActive(mgr, GetActive());\n\n  for (int i = 0; i < x49c_collisionManager->GetNumCollisionActors(); ++i) {\n    const auto& desc = x49c_collisionManager->GetCollisionDescFromIndex(i);\n    if (const TCastToPtr<CCollisionActor> cAct = mgr.ObjectById(desc.GetCollisionActorId())) {\n      cAct->AddMaterial(EMaterialTypes::ProjectilePassthrough, mgr);\n      cAct->SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(\n          {EMaterialTypes::Player},\n          {EMaterialTypes::Character, EMaterialTypes::NoStaticCollision, EMaterialTypes::NoPlatformCollision}));\n      if (desc.GetName().find(\"Blast_LCTR\"sv) != std::string_view::npos) {\n        x4a0_collisionActor = desc.GetCollisionActorId();\n      }\n    }\n  }\n}\n\nvoid CScriptGunTurret::SetTurretState(ETurretState state, CStateManager& mgr) {\n  if (state < ETurretState::Destroyed || state > ETurretState::Frenzy) {\n    return;\n  }\n\n  if (x520_state != ETurretState::Invalid) {\n    ProcessCurrentState(EStateMsg::Deactivate, mgr, 0.f);\n  }\n\n  if (state != ETurretState::Invalid && x520_state != state) {\n#ifndef NDEBUG\n    spdlog::debug(\"{} {} {} - {}\\n\", GetUniqueId(), GetEditorId(), GetName(), StateNames[size_t(state)]);\n#endif\n  }\n\n  x520_state = state;\n  x524_curStateTime = 0.f;\n  ProcessCurrentState(EStateMsg::Activate, mgr, 0.f);\n}\n\nvoid CScriptGunTurret::LaunchProjectile(CStateManager& mgr) {\n  if (!x37c_projectileInfo.Token().IsLoaded() || !mgr.CanCreateProjectile(GetUniqueId(), EWeaponType::AI, 8)) {\n    return;\n  }\n\n  const zeus::CTransform xf = GetLocatorTransform(\"Blast_LCTR\"sv);\n  const zeus::CVector3f projPt = GetTranslation() + GetTransform().rotate(xf.origin);\n  zeus::CVector3f lookPt = x404_targetPosition;\n  const zeus::CVector3f aimDelta = x404_targetPosition - projPt;\n  if (zeus::CVector3f::getAngleDiff(GetTransform().frontVector(), aimDelta) > zeus::degToRad(20.f)) {\n    if (aimDelta.canBeNormalized()) {\n      const zeus::CVector3f lookDir =\n          zeus::CVector3f::slerp(GetTransform().frontVector(), aimDelta.normalized(), zeus::degToRad(20.f));\n      lookPt = lookDir * aimDelta.magnitude() + projPt;\n    } else {\n      lookPt = projPt + GetTransform().frontVector();\n    }\n  } else if (!aimDelta.canBeNormalized()) {\n    lookPt = projPt + GetTransform().frontVector();\n  }\n  const zeus::CTransform useXf = zeus::lookAt(projPt, lookPt);\n  auto* proj = new CEnergyProjectile(true, x37c_projectileInfo.Token(), EWeaponType::AI, useXf,\n                                     EMaterialTypes::Character, x37c_projectileInfo.GetDamage(), mgr.AllocateUniqueId(),\n                                     GetAreaIdAlways(), GetUniqueId(), kInvalidUniqueId, EProjectileAttrib::None, false,\n                                     zeus::skOne3f, x458_visorEffectDesc, x2d4_data.GetVisorSoundId(), false);\n  mgr.AddObject(proj);\n  const auto pair = x64_modelData->GetAnimationData()->GetCharacterInfo().GetPASDatabase().FindBestAnimation(\n      CPASAnimParmData(pas::EAnimationState::ProjectileAttack, CPASAnimParm::FromEnum(1),\n                       CPASAnimParm::FromReal32(90.f),\n                       CPASAnimParm::FromEnum(skStateToLocoTypeLookup[size_t(x520_state)])),\n      -1);\n  if (pair.first > 0.f) {\n    x64_modelData->EnableLooping(false);\n    x64_modelData->GetAnimationData()->SetAnimation(CAnimPlaybackParms(pair.second, -1, 1.f, true), false);\n  }\n}\n\nvoid CScriptGunTurret::PlayAdditiveFlinchAnimation(CStateManager& mgr) {\n  const auto pair = GetModelData()->GetAnimationData()->GetCharacterInfo().GetPASDatabase().FindBestAnimation(\n      CPASAnimParmData(pas::EAnimationState::AdditiveFlinch), *mgr.GetActiveRandom(), -1);\n  if (pair.first > 0.f) {\n    GetModelData()->GetAnimationData()->AddAdditiveAnimation(pair.second, 1.f, false, true);\n  }\n}\n\nvoid CScriptGunTurret::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) {\n  CActor::AddToRenderer(frustum, mgr);\n\n  if (x258_type != ETurretComponent::Gun) {\n    return;\n  }\n\n  if (!x560_25_frozen) {\n    switch (x520_state) {\n    case ETurretState::Deactive:\n    case ETurretState::DeactiveFromReady:\n    case ETurretState::Deactivating:\n    case ETurretState::DeactivatingFromReady:\n      g_Renderer->AddParticleGen(*x470_deactivateLight);\n      break;\n    case ETurretState::Inactive:\n      g_Renderer->AddParticleGen(*x468_idleLight);\n      break;\n    case ETurretState::PanningA:\n    case ETurretState::PanningB:\n      g_Renderer->AddParticleGen(*x490_panningEffect);\n      break;\n    case ETurretState::Ready:\n    case ETurretState::Targeting:\n    case ETurretState::Firing:\n    case ETurretState::ExitTargeting:\n    case ETurretState::Frenzy:\n      g_Renderer->AddParticleGen(*x478_targettingLight);\n      if (x520_state == ETurretState::Firing || x520_state == ETurretState::Frenzy) {\n        g_Renderer->AddParticleGen(*x488_chargingEffect);\n      }\n      break;\n    default:\n      break;\n    }\n  } else {\n    g_Renderer->AddParticleGen(*x480_frozenEffect);\n  }\n}\n\nvoid CScriptGunTurret::Render(CStateManager& mgr) {\n  CPhysicsActor::Render(mgr);\n\n  if (x258_type == ETurretComponent::Gun) {\n    if (!x560_25_frozen) {\n      switch (x520_state) {\n      case ETurretState::Deactive:\n      case ETurretState::DeactiveFromReady:\n      case ETurretState::Deactivating:\n      case ETurretState::DeactivatingFromReady:\n        x470_deactivateLight->Render();\n        break;\n      case ETurretState::Inactive:\n        x468_idleLight->Render();\n        break;\n      case ETurretState::PanningA:\n      case ETurretState::PanningB:\n        x490_panningEffect->Render();\n        break;\n      case ETurretState::Ready:\n      case ETurretState::Targeting:\n      case ETurretState::Firing:\n      case ETurretState::ExitTargeting:\n      case ETurretState::Frenzy:\n        x478_targettingLight->Render();\n        if (x520_state == ETurretState::Firing) {\n          x488_chargingEffect->Render();\n        }\n        break;\n      default:\n        break;\n      }\n    } else {\n      x480_frozenEffect->Render();\n    }\n  } else if (x258_type == ETurretComponent::Base) {\n    if (x4a4_extensionModel && x4f8_extensionT > 0.f) {\n      zeus::CTransform xf = GetTransform();\n      xf.origin = x4fc_extensionOffset + (x4f4_extensionRange * 0.5f * zeus::skDown);\n      CModelFlags flags{0, 0, 3, zeus::skWhite};\n      x4a4_extensionModel->Render(mgr, xf, x90_actorLights.get(), flags);\n    }\n  }\n}\n\nvoid CScriptGunTurret::UpdateGunCollisionManager(float dt, CStateManager& mgr) {\n  if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(x4a0_collisionActor)) {\n    colAct->SetActive(mgr.GetPlayer().GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Morphed);\n  }\n\n  x49c_collisionManager->Update(dt, mgr, CCollisionActorManager::EUpdateOptions::ObjectSpace);\n}\n\nvoid CScriptGunTurret::UpdateFrozenState(float dt, CStateManager& mgr) {\n  if (x560_25_frozen) {\n    if (x53c_freezeRemTime <= 0.f) {\n      x560_25_frozen = false;\n      SendScriptMsgs(EScriptObjectState::UnFrozen, mgr, EScriptObjectMessage::None);\n      CSfxManager::AddEmitter(x2d4_data.GetUnFreezeSoundId(), GetTranslation(), zeus::skUp, false, false, 0x7f,\n                              GetAreaIdAlways());\n      SetMuted(false);\n    } else if (x2d4_data.GetFreezeTimeout()) {\n      x53c_freezeRemTime -= dt;\n    }\n  } else {\n    x53c_freezeRemTime = 0.f;\n  }\n}\n\nvoid CScriptGunTurret::UpdateGunParticles(float dt, CStateManager& mgr) {\n  CGameLight* light = nullptr;\n  if (x498_lightId != kInvalidUniqueId) {\n    light = TCastToPtr<CGameLight>(mgr.ObjectById(x498_lightId));\n  }\n\n  if (!x560_25_frozen) {\n    const zeus::CTransform lightXf = GetLocatorTransform(\"light_LCTR\"sv);\n    zeus::CVector3f pos = x34_transform.rotate(lightXf.origin);\n    pos += GetTranslation();\n    if (light) {\n      light->SetActive(true);\n    }\n\n    switch (x520_state) {\n    case ETurretState::Deactive:\n    case ETurretState::DeactiveFromReady:\n    case ETurretState::Deactivating:\n    case ETurretState::DeactivatingFromReady:\n      x468_idleLight->SetParticleEmission(false);\n      x470_deactivateLight->SetParticleEmission(true);\n      x478_targettingLight->SetParticleEmission(false);\n      x480_frozenEffect->SetParticleEmission(false);\n      x488_chargingEffect->SetParticleEmission(false);\n      x490_panningEffect->SetParticleEmission(false);\n      x470_deactivateLight->SetOrientation(GetTransform().getRotation());\n      x470_deactivateLight->SetGlobalTranslation(pos);\n      x470_deactivateLight->SetGlobalScale(GetModelData()->GetScale());\n      x470_deactivateLight->Update(dt);\n      if (light) {\n        if (x470_deactivateLight->SystemHasLight()) {\n          light->SetLight(x470_deactivateLight->GetLight());\n        } else {\n          light->SetActive(false);\n        }\n      }\n      break;\n    case ETurretState::Inactive:\n      x468_idleLight->SetParticleEmission(true);\n      x470_deactivateLight->SetParticleEmission(false);\n      x478_targettingLight->SetParticleEmission(false);\n      x480_frozenEffect->SetParticleEmission(false);\n      x488_chargingEffect->SetParticleEmission(false);\n      x490_panningEffect->SetParticleEmission(false);\n      x468_idleLight->SetOrientation(GetTransform().getRotation());\n      x468_idleLight->SetGlobalTranslation(pos);\n      x468_idleLight->SetGlobalScale(GetModelData()->GetScale());\n      x468_idleLight->Update(dt);\n      if (light) {\n        light->SetActive(false);\n      }\n      break;\n    case ETurretState::PanningA:\n    case ETurretState::PanningB:\n      x468_idleLight->SetParticleEmission(false);\n      x470_deactivateLight->SetParticleEmission(false);\n      x478_targettingLight->SetParticleEmission(false);\n      x480_frozenEffect->SetParticleEmission(false);\n      x488_chargingEffect->SetParticleEmission(false);\n      x490_panningEffect->SetParticleEmission(true);\n      x490_panningEffect->SetOrientation(GetTransform().getRotation());\n      x490_panningEffect->SetGlobalTranslation(GetTranslation());\n      x490_panningEffect->SetGlobalScale(GetModelData()->GetScale());\n      x490_panningEffect->Update(dt);\n      if (light) {\n        light->SetActive(false);\n      }\n      break;\n    case ETurretState::Targeting:\n    case ETurretState::Firing:\n    case ETurretState::ExitTargeting:\n    case ETurretState::Frenzy: {\n      const bool doEmission = x520_state == ETurretState::Firing || x520_state == ETurretState::Frenzy;\n      x468_idleLight->SetParticleEmission(false);\n      x470_deactivateLight->SetParticleEmission(false);\n      x478_targettingLight->SetParticleEmission(true);\n      x480_frozenEffect->SetParticleEmission(false);\n      x488_chargingEffect->SetParticleEmission(doEmission);\n      x478_targettingLight->SetOrientation(GetTransform().getRotation());\n      x478_targettingLight->SetGlobalTranslation(pos);\n      x478_targettingLight->SetGlobalScale(GetModelData()->GetScale());\n      x478_targettingLight->Update(dt);\n      if (x478_targettingLight->SystemHasLight()) {\n        light->SetLight(x478_targettingLight->GetLight());\n      } else {\n        light->SetActive(false);\n      }\n\n      if (doEmission) {\n        const zeus::CTransform blastXf = GetLocatorTransform(\"Blast_LCTR\"sv);\n        zeus::CVector3f blastPos = GetTransform().rotate(blastXf.origin);\n        blastPos += GetTranslation();\n        x488_chargingEffect->SetOrientation(GetTransform().getRotation());\n        x488_chargingEffect->SetGlobalTranslation(blastPos);\n        x488_chargingEffect->SetGlobalScale(GetModelData()->GetScale());\n        x488_chargingEffect->Update(dt);\n      }\n      break;\n    }\n    default:\n      x468_idleLight->SetParticleEmission(false);\n      x470_deactivateLight->SetParticleEmission(false);\n      x478_targettingLight->SetParticleEmission(false);\n      x480_frozenEffect->SetParticleEmission(false);\n      x488_chargingEffect->SetParticleEmission(false);\n      x490_panningEffect->SetParticleEmission(false);\n      x490_panningEffect->Update(dt);\n      if (light) {\n        light->SetActive(false);\n      }\n      break;\n    }\n  } else {\n    x468_idleLight->SetParticleEmission(false);\n    x470_deactivateLight->SetParticleEmission(false);\n    x478_targettingLight->SetParticleEmission(false);\n    x480_frozenEffect->SetParticleEmission(true);\n    x488_chargingEffect->SetParticleEmission(false);\n    x490_panningEffect->SetParticleEmission(false);\n    x480_frozenEffect->SetOrientation(GetTransform().getRotation());\n    x480_frozenEffect->SetGlobalTranslation(GetTranslation());\n    x480_frozenEffect->SetGlobalScale(GetModelData()->GetScale());\n    x480_frozenEffect->Update(dt);\n    if (light) {\n      light->SetActive(false);\n    }\n  }\n}\n\nvoid CScriptGunTurret::ProcessGunStateMachine(float dt, CStateManager& mgr) {\n  ProcessCurrentState(EStateMsg::Update, mgr, dt);\n  x524_curStateTime += dt;\n  PlayAdditiveChargingAnimation(mgr);\n\n  if (x25c_gunId == kInvalidUniqueId) {\n    return;\n  }\n\n  if (const TCastToPtr<CScriptGunTurret> gunTurret = mgr.ObjectById(x25c_gunId)) {\n    if (gunTurret->x520_state != ETurretState::Frenzy) {\n      gunTurret->x520_state = x520_state;\n    } else if (x520_state != ETurretState::Frenzy) {\n      SetTurretState(ETurretState::Frenzy, mgr);\n      gunTurret->RemoveMaterial(EMaterialTypes::Target, EMaterialTypes::Orbit, mgr);\n      mgr.GetPlayer().TryToBreakOrbit(GetUniqueId(), CPlayer::EPlayerOrbitRequest::ActivateOrbitSource, mgr);\n    }\n  }\n}\n\nvoid CScriptGunTurret::UpdateTurretAnimation() {\n  if (!HasModelData() || !GetModelData()->HasAnimData()) {\n    return;\n  }\n\n  if (x520_state > ETurretState::Frenzy) {\n    return;\n  }\n\n  const auto parmData = CPASAnimParmData(pas::EAnimationState::Locomotion, CPASAnimParm::FromEnum(0),\n                                         CPASAnimParm::FromEnum(skStateToLocoTypeLookup[size_t(x520_state)]));\n  const auto pair =\n      GetModelData()->GetAnimationData()->GetCharacterInfo().GetPASDatabase().FindBestAnimation(parmData, -1);\n\n  if (pair.first > 0.f && pair.second != x540_turretAnim) {\n    GetModelData()->GetAnimationData()->SetAnimation(CAnimPlaybackParms(pair.second, -1, 1.f, true), false);\n    GetModelData()->GetAnimationData()->EnableLooping(true);\n    x540_turretAnim = pair.second;\n  }\n}\n\nvoid CScriptGunTurret::ProcessCurrentState(EStateMsg msg, CStateManager& mgr, float dt) {\n  switch (x520_state) {\n  case ETurretState::Deactivating:\n  case ETurretState::DeactivatingFromReady:\n    ProcessDeactivatingState(msg, mgr);\n    break;\n  case ETurretState::Inactive:\n    ProcessInactiveState(msg, mgr, dt);\n    break;\n  case ETurretState::Ready:\n    ProcessReadyState(msg, mgr, dt);\n    break;\n  case ETurretState::PanningA:\n  case ETurretState::PanningB:\n    ProcessPanningState(msg, mgr, dt);\n    break;\n  case ETurretState::Targeting:\n  case ETurretState::Firing:\n    ProcessTargettingState(msg, mgr, dt);\n    break;\n  case ETurretState::ExitTargeting:\n    ProcessExitTargettingState(msg, mgr);\n    break;\n  case ETurretState::Frenzy:\n    ProcessFrenzyState(msg, mgr, dt);\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CScriptGunTurret::ProcessDeactivatingState(EStateMsg msg, CStateManager& mgr) {\n  if (msg == EStateMsg::Update && x524_curStateTime >= x2d4_data.GetIntoDeactivateDelay()) {\n    SetTurretState(x560_28_hasBeenActivated ? ETurretState::DeactiveFromReady : ETurretState::Deactive, mgr);\n  }\n}\n\nvoid CScriptGunTurret::ProcessInactiveState(EStateMsg msg, CStateManager& mgr, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x528_curInactiveTime = 0.f;\n    x560_27_burstSet = false;\n    if (const TCastToPtr<CScriptGunTurret> gunTurret = mgr.ObjectById(x25c_gunId)) {\n      x260_lastGunHP = gunTurret->HealthInfo(mgr)->GetHP();\n    }\n  } else if (msg == EStateMsg::Update) {\n    bool forceActivate = false;\n    if (x25c_gunId != kInvalidUniqueId)\n      if (const TCastToPtr<CScriptGunTurret> gun = mgr.ObjectById(x25c_gunId)) {\n        forceActivate = gun->HealthInfo(mgr)->GetHP() < x260_lastGunHP;\n      }\n    if (x2d4_data.GetScriptedStartOnly() ? (forceActivate || x560_29_scriptedStart)\n                                         : (forceActivate || x560_29_scriptedStart || InDetectionRange(mgr))) {\n      x528_curInactiveTime += dt;\n      if (forceActivate || x528_curInactiveTime >= x2d4_data.GetIntoActivateDelay())\n        SetTurretState(ETurretState::Ready, mgr);\n    } else {\n      x468_idleLight->SetParticleEmission(true);\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x560_28_hasBeenActivated = true;\n    x468_idleLight->SetParticleEmission(false);\n\n    if (const TCastToConstPtr<CScriptGunTurret> gunTurret = mgr.ObjectById(x25c_gunId)) {\n      x260_lastGunHP = gunTurret->GetHealthInfo(mgr)->GetHP();\n    }\n  }\n}\n\nvoid CScriptGunTurret::ProcessReadyState(EStateMsg msg, CStateManager& mgr, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x52c_curActiveTime = 0.f;\n  } else if (msg == EStateMsg::Update) {\n    x52c_curActiveTime += dt;\n    if (x52c_curActiveTime < x2d4_data.GetPanStartTime()) {\n      return;\n    }\n\n    if (IsPlayerInFiringRange(mgr) && InDetectionRange(mgr)) {\n      SetTurretState(ETurretState::Targeting, mgr);\n      CSfxManager::AddEmitter(x2d4_data.GetLockOnSoundId(), GetTranslation(), zeus::skUp, false, false, 0x7f,\n                              GetAreaIdAlways());\n    } else {\n      SetTurretState(ETurretState::PanningA, mgr);\n      x530_curPanTime = 0.f;\n    }\n  }\n}\n\nvoid CScriptGunTurret::ProcessPanningState(EStateMsg msg, CStateManager& mgr, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x52c_curActiveTime = 0.f;\n  } else if (msg == EStateMsg::Update) {\n    if (IsPlayerInFiringRange(mgr) && InDetectionRange(mgr)) {\n      SetTurretState(ETurretState::Targeting, mgr);\n      CSfxManager::AddEmitter(x2d4_data.GetLockOnSoundId(), GetTranslation(), zeus::skUp, false, false, 0x7f,\n                              GetAreaIdAlways());\n    } else {\n      x52c_curActiveTime += dt;\n      x530_curPanTime += dt;\n      if (x530_curPanTime >= x2d4_data.GetTotalPanSearchTime() && !x4a4_extensionModel &&\n          !x2d4_data.GetScriptedStartOnly()) {\n        SetTurretState(ETurretState::Inactive, mgr);\n      } else if (x52c_curActiveTime >= x2d4_data.GetPanHoldTime()) {\n        SetTurretState(x520_state != ETurretState::PanningA ? ETurretState::PanningA : ETurretState::PanningB, mgr);\n      }\n    }\n  }\n}\n\nvoid CScriptGunTurret::ProcessTargettingState(EStateMsg msg, CStateManager& mgr, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x52c_curActiveTime = 0.f;\n  } else if (msg == EStateMsg::Update) {\n    if (x560_26_firedWithSetBurst || InDetectionRange(mgr)) {\n      UpdateTargettingMode(dt, mgr);\n      if (x25c_gunId != kInvalidUniqueId) {\n        if (TCastToPtr<CScriptGunTurret> gun = mgr.ObjectById(x25c_gunId)) {\n          zeus::CVector3f vec = x404_targetPosition;\n          if (IsPlayerInFiringRange(mgr)) {\n            const zeus::CTransform blastXf = gun->GetLocatorTransform(\"Blast_LCTR\"sv);\n            const zeus::CVector3f rotatedBlastVec = GetTransform().rotate(blastXf.origin) + GetTranslation();\n            x404_targetPosition = mgr.GetPlayer().GetAimPosition(mgr, 0.f);\n            vec = x37c_projectileInfo.PredictInterceptPos(rotatedBlastVec, mgr.GetPlayer().GetAimPosition(mgr, dt),\n                                                          mgr.GetPlayer(), false, dt);\n          }\n\n          zeus::CVector3f compensated = x3a4_burstFire.GetDistanceCompensatedError(\n              (x404_targetPosition - gun->GetTranslation()).magnitude(), 20.f);\n\n          compensated = gun->GetTransform().rotate(compensated);\n\n          gun->x404_targetPosition = x404_targetPosition + (vec - x404_targetPosition) + compensated;\n        }\n      }\n\n      zeus::CVector3f diffVec = x404_targetPosition - GetTranslation();\n      diffVec.z() = 0.f;\n      if (diffVec.canBeNormalized()) {\n        const zeus::CVector3f normDiff = diffVec.normalized();\n        const float angDif = zeus::CVector3f::getAngleDiff(normDiff, GetTransform().frontVector());\n        zeus::CQuaternion quat = zeus::CQuaternion::lookAt(GetTransform().frontVector(), normDiff,\n                                                           std::min(angDif, (dt * x2d4_data.GetTurnSpeed())));\n\n        quat.setImaginary(GetTransform().transposeRotate(quat.getImaginary()));\n        RotateInOneFrameOR(quat, dt);\n      }\n\n      if (ShouldFire(mgr)) {\n        SendScriptMsgs(EScriptObjectState::Attack, mgr, EScriptObjectMessage::None);\n        x560_26_firedWithSetBurst = true;\n      }\n\n      x52c_curActiveTime = 0.f;\n    } else {\n      x52c_curActiveTime += dt;\n      if (x52c_curActiveTime >= 10.f) {\n        SetTurretState(ETurretState::ExitTargeting, mgr);\n      }\n    }\n  } else if (msg == EStateMsg::Deactivate) {\n    x560_30_needsStopClankSound = true;\n  }\n}\n\nvoid CScriptGunTurret::ProcessExitTargettingState(EStateMsg msg, CStateManager& mgr) {\n  if (msg != EStateMsg::Update || x25c_gunId == kInvalidUniqueId)\n    return;\n\n  if (TCastToPtr<CScriptGunTurret> gun = mgr.ObjectById(x25c_gunId)) {\n    // zeus::CTransform gunXf = GetTransform() * GetLocatorTransform(\"Gun_SDK\"sv);\n\n    if (zeus::CVector3f::getAngleDiff(gun->GetTransform().frontVector(), x544_originalFrontVec) < zeus::degToRad(0.9f))\n      SetTurretState(ETurretState::Ready, mgr);\n  }\n}\n\nvoid CScriptGunTurret::ProcessFrenzyState(EStateMsg msg, CStateManager& mgr, float dt) {\n  if (msg == EStateMsg::Activate) {\n    x560_31_frenzyReverse = mgr.GetActiveRandom()->Float() < 0.f;\n    x534_fireCycleRemTime = 0.15f;\n    RemoveMaterial(EMaterialTypes::Target, EMaterialTypes::Orbit, mgr);\n    mgr.GetPlayer().TryToBreakOrbit(GetUniqueId(), CPlayer::EPlayerOrbitRequest::ActivateOrbitSource, mgr);\n  } else if (msg == EStateMsg::Update) {\n    if (x524_curStateTime >= x2d4_data.GetFrenzyDuration()) {\n      SetTurretState(ETurretState::Destroyed, mgr);\n      if (const TCastToPtr<CScriptGunTurret> gun = mgr.ObjectById(x25c_gunId)) {\n        gun->x520_state = ETurretState::Destroyed;\n      }\n      return;\n    }\n\n    const zeus::CVector3f frontVec = GetTransform().frontVector();\n    if (x560_31_frenzyReverse && x550_originalRightVec.magSquared() < 0.f &&\n        zeus::CVector3f::getAngleDiff(x544_originalFrontVec, frontVec) >= zeus::degToRad(45.f)) {\n      x560_31_frenzyReverse = false;\n    } else if (!x560_31_frenzyReverse && x550_originalRightVec.magSquared() < 0.f &&\n               zeus::CVector3f::getAngleDiff(x544_originalFrontVec, frontVec) >= zeus::degToRad(45.f)) {\n      x560_31_frenzyReverse = true;\n    }\n\n    if (const TCastToConstPtr<CScriptGunTurret> gun = mgr.ObjectById(x25c_gunId)) {\n      x534_fireCycleRemTime -= dt;\n      if (x534_fireCycleRemTime >= 0.f) {\n        return;\n      }\n\n      x404_targetPosition = gun->GetTranslation() + (100.f * gun->GetTransform().frontVector());\n      SendScriptMsgs(EScriptObjectState::Attack, mgr, EScriptObjectMessage::None);\n      x534_fireCycleRemTime = 0.15f;\n    }\n  }\n}\n\nbool CScriptGunTurret::IsPlayerInFiringRange(CStateManager& mgr) const {\n  const zeus::CVector3f posDif = mgr.GetPlayer().GetTranslation() - GetTranslation();\n  const zeus::CVector3f someVec(posDif.x(), posDif.y(), 0.f);\n  if (x550_originalRightVec.dot(posDif) >= 0.f) {\n    return zeus::CVector3f::getAngleDiff(x544_originalFrontVec, someVec) <= x2d4_data.GetRightMaxAngle();\n  }\n\n  if (zeus::CVector3f::getAngleDiff(x544_originalFrontVec, someVec) <= x2d4_data.GetLeftMaxAngle()) {\n    return true;\n  }\n\n  const float biasedAngle = zeus::CVector3f::getAngleDiff(posDif, zeus::skUp) - zeus::degToRad(90.f);\n  return biasedAngle >= zeus::degToRad(-20.f) && biasedAngle <= x2d4_data.GetDownMaxAngle();\n}\n\nbool CScriptGunTurret::LineOfSightTest(CStateManager& mgr) const {\n  if (x25c_gunId == kInvalidUniqueId) {\n    return false;\n  }\n\n  if (const TCastToConstPtr<CScriptGunTurret> gun = mgr.ObjectById(x25c_gunId)) {\n    if (x560_27_burstSet || (x520_state == ETurretState::Inactive && x4a4_extensionModel)) {\n      return true;\n    }\n\n    const zeus::CTransform xf = GetLocatorTransform(\"Blast_LCTR\"sv);\n    const zeus::CVector3f muzzlePos = gun->GetTransform().rotate(xf.origin) + gun->GetTranslation();\n    zeus::CVector3f dir = mgr.GetPlayer().GetAimPosition(mgr, 0.f) - muzzlePos;\n    const float mag = dir.magnitude();\n    dir = dir / mag;\n    EntityList nearList;\n    constexpr auto filter = CMaterialFilter::MakeIncludeExclude(\n        {EMaterialTypes::Solid}, {EMaterialTypes::Player, EMaterialTypes::CollisionActor});\n    mgr.BuildNearList(nearList, muzzlePos, dir, mag, filter, gun.GetPtr());\n    TUniqueId id = kInvalidUniqueId;\n    return mgr.RayWorldIntersection(id, muzzlePos, dir, mag, filter, nearList).IsInvalid();\n  }\n  return false;\n}\n\nbool CScriptGunTurret::InDetectionRange(CStateManager& mgr) const {\n  const zeus::CVector3f delta = mgr.GetPlayer().GetTranslation() - GetTranslation();\n\n  if (delta.dot(zeus::skDown) < 0.f &&\n      zeus::CVector3f::getAngleDiff(GetTransform().frontVector(), delta) > zeus::degToRad(20.f)) {\n    return false;\n  }\n\n  if (delta.magSquared() > x2d4_data.GetDetectionRange() * x2d4_data.GetDetectionRange()) {\n    return false;\n  }\n\n  if (x2d4_data.GetDetectionZRange() != 0.f && std::fabs(delta.z()) >= x2d4_data.GetDetectionZRange()) {\n    return false;\n  }\n\n  return LineOfSightTest(mgr);\n}\n\nzeus::CVector3f CScriptGunTurret::UpdateExtensionModelState(float dt) {\n  if (!x4a4_extensionModel) {\n    return {};\n  }\n\n  switch (x520_state) {\n  case ETurretState::PanningA:\n  case ETurretState::PanningB:\n  case ETurretState::Targeting:\n  case ETurretState::Firing:\n  case ETurretState::ExitTargeting:\n    x4f8_extensionT = std::min(0.9f, x4f8_extensionT + 1.5f * dt);\n    break;\n  default:\n    x4f8_extensionT = std::max(0.f, x4f8_extensionT - 1.5f * dt);\n    break;\n  case ETurretState::Ready:\n  case ETurretState::Deactivating:\n  case ETurretState::DeactivatingFromReady:\n  case ETurretState::Frenzy:\n    break;\n  }\n  return (x4fc_extensionOffset + (x2d4_data.GetExtensionDropDownDist() * x4f8_extensionT * zeus::skDown)) -\n         GetTranslation();\n}\n\nvoid CScriptGunTurret::UpdateHealthInfo(CStateManager& mgr) {\n  switch (x258_type) {\n  case ETurretComponent::Base:\n    if (x25c_gunId != kInvalidUniqueId) {\n      if (!TCastToConstPtr<CScriptGunTurret>(mgr.ObjectById(x25c_gunId))) {\n        SetTurretState(ETurretState::Destroyed, mgr);\n        x560_25_frozen = false;\n        x25c_gunId = kInvalidUniqueId;\n        if (x50c_targetingEmitter) {\n          CSfxManager::RemoveEmitter(x50c_targetingEmitter);\n          x50c_targetingEmitter.reset();\n        }\n      }\n    } else {\n      SetTurretState(ETurretState::Destroyed, mgr);\n    }\n    break;\n  case ETurretComponent::Gun:\n    if (!x560_24_dead && x520_state != ETurretState::Frenzy && HealthInfo(mgr)->GetHP() <= 0.f) {\n      x560_24_dead = true;\n      SendScriptMsgs(EScriptObjectState::Dead, mgr, EScriptObjectMessage::None);\n      mgr.FreeScriptObject(GetUniqueId());\n    }\n    break;\n  default:\n    break;\n  }\n}\n\nbool CScriptGunTurret::PlayerInsideTurretSphere(CStateManager& mgr) const {\n  if (const TCastToConstPtr<CCollisionActor> cAct = mgr.GetObjectById(x4a0_collisionActor)) {\n    if (cAct->GetActive()) {\n      zeus::CVector3f delta = mgr.GetPlayer().GetAimPosition(mgr, 0.f) - GetTranslation();\n      if (delta.z() < 0.f) {\n        const float rad = cAct->GetSphereRadius() * 2.f + (cAct->GetTranslation() - GetTranslation()).magnitude();\n        return delta.magSquared() < rad * rad;\n      }\n    }\n  }\n  return false;\n}\n\nvoid CScriptGunTurret::UpdateGunOrientation(float dt, CStateManager& mgr) {\n  if (x25c_gunId == kInvalidUniqueId) {\n    return;\n  }\n\n  if (const TCastToPtr<CScriptGunTurret> gun = mgr.ObjectById(x25c_gunId)) {\n    zeus::CTransform xf = GetLocatorTransform(\"Gun_SDK\"sv);\n    xf = GetTransform() * xf;\n\n    switch (x520_state) {\n    case ETurretState::Targeting:\n    case ETurretState::Firing: {\n      const float xyMagSq = xf.frontVector().toVec2f().magSquared();\n      float useYaw = 0.f;\n      if (std::sqrt(xyMagSq) > 0.001f) {\n        useYaw = -std::atan2(xf.frontVector().x(), xf.frontVector().y());\n      }\n\n      const float oldPitch = gun->GetPitch();\n      float usePitch = 0.f;\n      if (!gun->PlayerInsideTurretSphere(mgr)) {\n        zeus::CTransform newXf;\n        if ((x404_targetPosition - xf.origin).canBeNormalized()) {\n          newXf = zeus::lookAt(xf.origin, x404_targetPosition);\n        } else {\n          newXf = GetTransform();\n        }\n\n        const float newPitch = -std::atan2(-newXf.frontVector().z(), newXf.frontVector().toVec2f().magnitude());\n        const float newPitchDelta = newPitch - oldPitch;\n        const float f2 = newPitchDelta > 0.f ? dt * x2d4_data.GetTurnSpeed() : dt * -x2d4_data.GetTurnSpeed();\n        usePitch = std::max(std::fabs(newPitchDelta) <= std::fabs(f2) ? newPitch : oldPitch + f2,\n                            -x2d4_data.GetDownMaxAngle());\n      }\n      zeus::CQuaternion qy, qx, qz;\n      qy.rotateY(0.f);\n      qx.rotateX(usePitch);\n      qz.rotateZ(useYaw);\n      gun->SetTransform((qz * qx * qy).toTransform(xf.origin));\n      break;\n    }\n    case ETurretState::ExitTargeting: {\n      const zeus::CVector3f frontVec = GetTransform().frontVector();\n      const zeus::CVector3f gunFrontVec = gun->GetTransform().frontVector();\n      const float rotAngle = 0.3f * dt * x2d4_data.GetTurnSpeed();\n      zeus::CQuaternion quat = zeus::CQuaternion::lookAt(gunFrontVec, frontVec, rotAngle);\n      quat.setImaginary(gun->GetTransform().transposeRotate(quat.getImaginary()));\n      gun->RotateInOneFrameOR(quat, dt);\n      zeus::CQuaternion quat2 = zeus::CQuaternion::lookAt(frontVec, x544_originalFrontVec, rotAngle);\n      quat2.setImaginary(GetTransform().transposeRotate(quat2.getImaginary()));\n      RotateInOneFrameOR(quat2, dt);\n      break;\n    }\n    case ETurretState::Frenzy: {\n      const float xyMagSq = xf.frontVector().toVec2f().magSquared();\n      float useYaw = 0.f;\n      if (std::sqrt(xyMagSq) > 0.001f) {\n        useYaw = -std::atan2(xf.frontVector().x(), xf.frontVector().y());\n      }\n\n      const float f28 =\n          -0.5f * x2d4_data.GetDownMaxAngle() * (1.f - std::cos(2.f * x524_curStateTime * x2d4_data.GetTurnSpeed()));\n      const float pitch = gun->GetPitch();\n      const float f2 = f28 - pitch;\n      const float f31 = x2d4_data.GetTurnSpeed() * dt;\n      const float f3 = f2 > 0.f ? f31 : -f31;\n      float usePitch = std::fabs(f2) <= std::fabs(f3) ? f28 : pitch + f3;\n      usePitch = std::max(usePitch, -x2d4_data.GetDownMaxAngle());\n      zeus::CQuaternion qy, qx, qz;\n      qy.rotateY(0.f);\n      qx.rotateX(usePitch);\n      qz.rotateZ(useYaw);\n      gun->SetTransform((qz * qx * qy).toTransform(xf.origin));\n      zeus::CQuaternion rot = zeus::CQuaternion::lookAt(\n          GetTransform().frontVector(), x560_31_frenzyReverse ? -x550_originalRightVec : x550_originalRightVec, f31);\n      rot.setImaginary(GetTransform().transposeRotate(rot.getImaginary()));\n      RotateInOneFrameOR(rot, dt);\n      break;\n    }\n    default:\n      gun->SetTransform(xf);\n      break;\n    }\n  }\n}\n\nvoid CScriptGunTurret::UpdateTargettingSound(float dt) {\n  x510_timeSinceLastTargetSfx += dt;\n  const float angleDiff2D =\n      zeus::CVector2f::getAngleDiff(x514_lastFrontVector.toVec2f(), GetTransform().frontVector().toVec2f());\n\n  if (x560_30_needsStopClankSound && angleDiff2D < zeus::degToRad(20.f) &&\n      (x520_state == ETurretState::Targeting || x520_state == ETurretState::Firing)) {\n    if (!x560_25_frozen) {\n      CSfxManager::AddEmitter(x2d4_data.GetStopClankSoundId(), GetTranslation(), zeus::skUp, false, false, 127,\n                              GetAreaIdAlways());\n    }\n    x560_30_needsStopClankSound = false;\n  }\n\n  if (x510_timeSinceLastTargetSfx >= 0.5f && !x560_25_frozen) {\n    if (x520_state == ETurretState::Targeting || x520_state == ETurretState::Firing ||\n        x520_state == ETurretState::Frenzy) {\n      const bool insignificant = IsInsignificantRotation(dt);\n      if (!insignificant && !x50c_targetingEmitter) {\n        x50c_targetingEmitter = CSfxManager::AddEmitter(x2d4_data.GetTrackingSoundId(), GetTranslation(), zeus::skUp,\n                                                        false, true, 127, GetAreaIdAlways());\n      } else if (insignificant && x50c_targetingEmitter) {\n        CSfxManager::RemoveEmitter(x50c_targetingEmitter);\n        x50c_targetingEmitter.reset();\n        x510_timeSinceLastTargetSfx = 0.f;\n      }\n\n      if (x50c_targetingEmitter) {\n        const float bendScale = dt * x2d4_data.GetTurnSpeed();\n        CSfxManager::PitchBend(x50c_targetingEmitter, std::max(0.f, (bendScale > 0.f ? angleDiff2D / bendScale : 0.f)));\n      }\n    }\n  } else if (x560_25_frozen && x50c_targetingEmitter) {\n    CSfxManager::RemoveEmitter(x50c_targetingEmitter);\n    x50c_targetingEmitter.reset();\n  }\n\n  x514_lastFrontVector = GetTransform().frontVector();\n}\n\nvoid CScriptGunTurret::PlayAdditiveChargingAnimation(CStateManager& mgr) {\n  if (x520_state == ETurretState::Firing) {\n    if (x55c_additiveChargeAnim != -1) {\n      return;\n    }\n\n    const auto pair = GetModelData()->GetAnimationData()->GetCharacterInfo().GetPASDatabase().FindBestAnimation(\n        CPASAnimParmData(pas::EAnimationState::AdditiveReaction, CPASAnimParm::FromEnum(2)), *mgr.GetActiveRandom(),\n        -1);\n    if (pair.first > 0.f) {\n      x55c_additiveChargeAnim = pair.second;\n      GetModelData()->GetAnimationData()->AddAdditiveAnimation(pair.second, 1.f, true, false);\n    }\n  } else if (x55c_additiveChargeAnim != -1) {\n    GetModelData()->GetAnimationData()->DelAdditiveAnimation(x55c_additiveChargeAnim);\n    x55c_additiveChargeAnim = -1;\n  }\n}\n\nvoid CScriptGunTurret::UpdateTargettingMode(float dt, CStateManager& mgr) {\n  if (mgr.GetCameraManager()->IsInCinematicCamera()) {\n    x534_fireCycleRemTime =\n        mgr.GetActiveRandom()->Float() * x2d4_data.GetReloadTimeVariance() + x2d4_data.GetReloadTime();\n    x538_halfFireCycleDur = 0.5f * x534_fireCycleRemTime;\n  }\n\n  if (x534_fireCycleRemTime > 0.f) {\n    x534_fireCycleRemTime -= dt;\n    if (x534_fireCycleRemTime < x538_halfFireCycleDur && x520_state != ETurretState::Firing) {\n      CSfxManager::AddEmitter(x2d4_data.GetChargingSoundId(), GetTranslation(), zeus::skUp, false, false, 0x7f,\n                              GetAreaIdAlways());\n      SetTurretState(ETurretState::Firing, mgr);\n      return;\n    }\n  } else {\n    if (x520_state != ETurretState::Targeting) {\n      SetTurretState(ETurretState::Targeting, mgr);\n    }\n\n    if (!x3a4_burstFire.IsBurstSet()) {\n      UpdateBurstType(mgr);\n      x534_fireCycleRemTime =\n          mgr.GetActiveRandom()->Float() * x2d4_data.GetReloadTimeVariance() + x2d4_data.GetReloadTime();\n      x538_halfFireCycleDur = 0.5f * x534_fireCycleRemTime;\n    } else {\n      x3a4_burstFire.Update(mgr, dt);\n    }\n  }\n}\n\nvoid CScriptGunTurret::UpdateBurstType(CStateManager& mgr) {\n  if (x560_27_burstSet) {\n    bool inView = true;\n    if (mgr.GetPlayer().GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Morphed) {\n      const zeus::CVector3f frontVec = GetTransform().frontVector();\n      const zeus::CVector3f plFrontVec = mgr.GetPlayer().GetTransform().frontVector();\n      const float dot = frontVec.dot(plFrontVec);\n\n      if (dot >= 0.f) {\n        inView = false;\n      }\n    }\n\n    u32 r3 = mgr.GetActiveRandom()->Range(0, 3);\n    r3 += 2;\n\n    u32 type = 1;\n    if (r3 <= 2 || x2d4_data.GetNumSubsequentShots() < 3) {\n      type = 0;\n    } else if (r3 >= 5 && x2d4_data.GetNumSubsequentShots() > 3) {\n      type = 2;\n    }\n\n    x3a4_burstFire.SetBurstType(type + (inView ? 0 : 3));\n  } else {\n    const u32 type = x2d4_data.GetNumInitialShots() - 2;\n    x3a4_burstFire.SetBurstType(type);\n    x3a4_burstFire.SetFirstBurstIndex(x2d4_data.GetInitialShotTableIndex());\n  }\n\n  x3a4_burstFire.Start(mgr);\n  x560_26_firedWithSetBurst = false;\n  x560_27_burstSet = true;\n}\n\nbool CScriptGunTurret::ShouldFire(CStateManager& mgr) const {\n  if (x520_state == ETurretState::Targeting && x534_fireCycleRemTime <= 0.f && x3a4_burstFire.ShouldFire()) {\n    return IsPlayerInFiringRange(mgr);\n  }\n\n  return false;\n}\n\nbool CScriptGunTurret::IsInsignificantRotation(float dt) const {\n  return zeus::CVector2f::getAngleDiff(x514_lastFrontVector.toVec2f(), GetTransform().frontVector().toVec2f()) <\n         zeus::degToRad(2.f) * dt;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptGunTurret.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <optional>\n#include <string_view>\n\n#include \"Runtime/Weapon/CBurstFire.hpp\"\n#include \"Runtime/Weapon/CProjectileInfo.hpp\"\n#include \"Runtime/World/CDamageInfo.hpp\"\n#include \"Runtime/World/CDamageVulnerability.hpp\"\n#include \"Runtime/World/CPhysicsActor.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CCollisionActorManager;\nclass CElementGen;\n\nenum class EStateMsg;\n\nclass CScriptGunTurretData {\n  float x0_intoDeactivateDelay;\n  float x4_intoActivateDelay;\n  float x8_reloadTime;\n  float xc_reloadTimeVariance;\n  float x10_panStartTime;\n  float x14_panHoldTime;\n  float x18_totalPanSearchTime = 30.f;\n  float x1c_leftMaxAngle;\n  float x20_rightMaxAngle;\n  float x24_downMaxAngle;\n  float x28_turnSpeed;\n  float x2c_detectionRange;\n  float x30_detectionZRange;\n  float x34_freezeDuration;\n  float x38_freezeVariance;\n  bool x3c_freezeTimeout;\n  CAssetId x40_projectileRes;\n  CDamageInfo x44_projectileDamage;\n  CAssetId x60_idleLightRes;\n  CAssetId x64_deactivateLightRes;\n  CAssetId x68_targettingLightRes;\n  CAssetId x6c_frozenEffectRes;\n  CAssetId x70_chargingEffectRes;\n  CAssetId x74_panningEffectRes;\n  CAssetId x78_visorEffectRes;\n  u16 x7c_trackingSoundId;\n  u16 x7e_lockOnSoundId;\n  u16 x80_unfreezeSoundId;\n  u16 x82_stopClankSoundId;\n  u16 x84_chargingSoundId;\n  u16 x86_visorSoundId;\n  CAssetId x88_extensionModelResId;\n  float x8c_extensionDropDownDist;\n  u32 x90_numInitialShots;\n  u32 x94_initialShotTableIndex;\n  u32 x98_numSubsequentShots;\n  float x9c_frenzyDuration;\n  bool xa0_scriptedStartOnly;\n  static constexpr s32 skMinProperties = 43;\n\npublic:\n  CScriptGunTurretData(CInputStream&, s32);\n  CAssetId GetPanningEffectRes() const { return x74_panningEffectRes; }\n  CAssetId GetChargingEffectRes() const { return x70_chargingEffectRes; }\n  CAssetId GetFrozenEffectRes() const { return x6c_frozenEffectRes; }\n  CAssetId GetTargettingLightRes() const { return x68_targettingLightRes; }\n  CAssetId GetDeactivateLightRes() const { return x64_deactivateLightRes; }\n  CAssetId GetIdleLightRes() const { return x60_idleLightRes; }\n  CAssetId GetVisorEffectRes() const { return x78_visorEffectRes; }\n  const CDamageInfo& GetProjectileDamage() const { return x44_projectileDamage; }\n  CAssetId GetProjectileRes() const { return x40_projectileRes; }\n  u16 GetUnFreezeSoundId() const { return x80_unfreezeSoundId; }\n  float GetIntoDeactivateDelay() const { return x0_intoDeactivateDelay; }\n  CAssetId GetExtensionModelResId() const { return x88_extensionModelResId; }\n  float GetFreezeVariance() const { return x38_freezeVariance; }\n  float GetFreezeDuration() const { return x34_freezeDuration; }\n  bool GetFreezeTimeout() const { return x3c_freezeTimeout; }\n  float GetIntoActivateDelay() const { return x4_intoActivateDelay; }\n  u16 GetLockOnSoundId() const { return x7e_lockOnSoundId; }\n  float GetPanStartTime() const { return x10_panStartTime; }\n  float GetPanHoldTime() const { return x14_panHoldTime; }\n  float GetTotalPanSearchTime() const { return x18_totalPanSearchTime; }\n  float GetTurnSpeed() const { return x28_turnSpeed; }\n  float GetReloadTimeVariance() const { return xc_reloadTimeVariance; }\n  float GetReloadTime() const { return x8_reloadTime; }\n  u16 GetChargingSoundId() const { return x84_chargingSoundId; }\n  float GetDownMaxAngle() const { return x24_downMaxAngle; }\n  float GetExtensionDropDownDist() const { return x8c_extensionDropDownDist; }\n  float GetLeftMaxAngle() const { return x1c_leftMaxAngle; }\n  float GetRightMaxAngle() const { return x20_rightMaxAngle; }\n  float GetDetectionRange() const { return x2c_detectionRange; }\n  float GetDetectionZRange() const { return x30_detectionZRange; }\n  u32 GetNumSubsequentShots() const { return x98_numSubsequentShots; }\n  u32 GetInitialShotTableIndex() const { return x94_initialShotTableIndex; }\n  u32 GetNumInitialShots() const { return x90_numInitialShots; }\n  u16 GetTrackingSoundId() const { return x7c_trackingSoundId; }\n  u16 GetStopClankSoundId() const { return x82_stopClankSoundId; }\n  u16 GetVisorSoundId() const { return x86_visorSoundId; }\n  bool GetScriptedStartOnly() const { return xa0_scriptedStartOnly; }\n  float GetFrenzyDuration() const { return x9c_frenzyDuration; }\n  static s32 GetMinProperties() { return skMinProperties; }\n};\n\nclass CScriptGunTurret : public CPhysicsActor {\npublic:\n  enum class ETurretComponent { Base, Gun };\n  enum class ETurretState {\n    Invalid = -1,\n    Destroyed,\n    Deactive,\n    DeactiveFromReady,\n    Deactivating,\n    DeactivatingFromReady,\n    Inactive,\n    Ready,\n    PanningA,\n    PanningB,\n    Targeting,\n    Firing,\n    ExitTargeting,\n    Frenzy\n  };\n\nprivate:\n  ETurretComponent x258_type;\n  TUniqueId x25c_gunId = kInvalidUniqueId;\n  float x260_lastGunHP = 0.f;\n  CHealthInfo x264_healthInfo;\n  CDamageVulnerability x26c_damageVuln;\n  CScriptGunTurretData x2d4_data;\n  TUniqueId x378_ = kInvalidUniqueId;\n  CProjectileInfo x37c_projectileInfo;\n  CBurstFire x3a4_burstFire;\n  zeus::CVector3f x404_targetPosition;\n  TToken<CGenDescription> x410_idleLightDesc;\n  TToken<CGenDescription> x41c_deactivateLightDesc;\n  TToken<CGenDescription> x428_targettingLightDesc;\n  TToken<CGenDescription> x434_frozenEffectDesc;\n  TToken<CGenDescription> x440_chargingEffectDesc;\n  TToken<CGenDescription> x44c_panningEffectDesc;\n  TLockedToken<CGenDescription> x458_visorEffectDesc;\n  std::unique_ptr<CElementGen> x468_idleLight;\n  std::unique_ptr<CElementGen> x470_deactivateLight;\n  std::unique_ptr<CElementGen> x478_targettingLight;\n  std::unique_ptr<CElementGen> x480_frozenEffect;\n  std::unique_ptr<CElementGen> x488_chargingEffect;\n  std::unique_ptr<CElementGen> x490_panningEffect;\n  TUniqueId x498_lightId = kInvalidUniqueId;\n  std::unique_ptr<CCollisionActorManager> x49c_collisionManager;\n  TUniqueId x4a0_collisionActor = kInvalidUniqueId;\n  std::optional<CModelData> x4a4_extensionModel;\n  float x4f4_extensionRange = 0.f;\n  float x4f8_extensionT = 0.f;\n  zeus::CVector3f x4fc_extensionOffset;\n  u8 x508_gunSDKSeg = 0xFF;\n  CSfxHandle x50c_targetingEmitter;\n  float x510_timeSinceLastTargetSfx = 0.f;\n  zeus::CVector3f x514_lastFrontVector;\n  ETurretState x520_state = ETurretState::Invalid;\n  float x524_curStateTime = 0.f;\n  float x528_curInactiveTime = 0.f;\n  float x52c_curActiveTime = 0.f;\n  float x530_curPanTime = 0.f;\n  float x534_fireCycleRemTime = 0.f;\n  float x538_halfFireCycleDur = 0.f;\n  float x53c_freezeRemTime = 0.f;\n  s32 x540_turretAnim = -1;\n  zeus::CVector3f x544_originalFrontVec;\n  zeus::CVector3f x550_originalRightVec;\n  s32 x55c_additiveChargeAnim = -1;\n  bool x560_24_dead : 1 = false;\n  bool x560_25_frozen : 1 = false;\n  bool x560_26_firedWithSetBurst : 1 = false;\n  bool x560_27_burstSet : 1 = false;\n  bool x560_28_hasBeenActivated : 1 = false;\n  bool x560_29_scriptedStart : 1 = false;\n  bool x560_30_needsStopClankSound : 1 = true;\n  bool x560_31_frenzyReverse : 1 = false;\n\nprivate:\n  void SetupCollisionManager(CStateManager&);\n  void SetTurretState(ETurretState, CStateManager&);\n  void ProcessCurrentState(EStateMsg, CStateManager&, float);\n  void LaunchProjectile(CStateManager&);\n  void PlayAdditiveFlinchAnimation(CStateManager&);\n  void ProcessGunStateMachine(float, CStateManager&);\n  void UpdateTurretAnimation();\n  void UpdateGunCollisionManager(float, CStateManager&);\n  void UpdateFrozenState(float, CStateManager&);\n  void UpdateGunParticles(float, CStateManager&);\n  void ProcessDeactivatingState(EStateMsg, CStateManager&);\n  void ProcessInactiveState(EStateMsg, CStateManager&, float);\n  void ProcessReadyState(EStateMsg, CStateManager&, float);\n  void ProcessPanningState(EStateMsg, CStateManager&, float);\n  void ProcessTargettingState(EStateMsg, CStateManager&, float);\n  void ProcessExitTargettingState(EStateMsg, CStateManager&);\n  void ProcessFrenzyState(EStateMsg, CStateManager&, float);\n  bool IsPlayerInFiringRange(CStateManager&) const;\n  bool LineOfSightTest(CStateManager&) const;\n  bool InDetectionRange(CStateManager&) const;\n  bool PlayerInsideTurretSphere(CStateManager&) const;\n  void UpdateGunOrientation(float, CStateManager&);\n  zeus::CVector3f UpdateExtensionModelState(float);\n  void UpdateHealthInfo(CStateManager&);\n  void UpdateTargettingSound(float);\n  void PlayAdditiveChargingAnimation(CStateManager&);\n  void UpdateTargettingMode(float, CStateManager&);\n  void UpdateBurstType(CStateManager&);\n  bool ShouldFire(CStateManager&) const;\n  bool IsInsignificantRotation(float) const;\n\npublic:\n  DEFINE_ENTITY\n  CScriptGunTurret(TUniqueId uid, std::string_view name, ETurretComponent comp, const CEntityInfo& info,\n                   const zeus::CTransform& xf, CModelData&& mData, const zeus::CAABox& aabb, const CHealthInfo& hInfo,\n                   const CDamageVulnerability& dVuln, const CActorParameters& aParms,\n                   const CScriptGunTurretData& turretData);\n  ~CScriptGunTurret() override;\n\n  void Accept(IVisitor&) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void Think(float, CStateManager&) override;\n  void Touch(CActor&, CStateManager&) override;\n  void AddToRenderer(const zeus::CFrustum&, CStateManager&) override;\n  void Render(CStateManager&) override;\n  std::optional<zeus::CAABox> GetTouchBounds() const override;\n  zeus::CVector3f GetOrbitPosition(const CStateManager&) const override;\n  zeus::CVector3f GetAimPosition(const CStateManager&, float) const override;\n\n  CHealthInfo* HealthInfo(CStateManager&) override { return &x264_healthInfo; }\n  const CDamageVulnerability* GetDamageVulnerability() const override { return &x26c_damageVuln; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptHUDMemo.cpp",
    "content": "#include \"Runtime/World/CScriptHUDMemo.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/GuiSys/CStringTable.hpp\"\n#include \"Runtime/MP1/CSamusHud.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCScriptHUDMemo::CScriptHUDMemo(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                               const CHUDMemoParms& parms, EDisplayType disp, CAssetId msg, bool active)\n: CEntity(uid, info, active, name), x34_parms(parms), x3c_dispType(disp), x40_stringTableId(msg) {\n  if (msg.IsValid()) {\n    x44_stringTable.emplace(g_SimplePool->GetObj({FOURCC('STRG'), msg}));\n  }\n}\n\nvoid CScriptHUDMemo::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptHUDMemo::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  if (msg == EScriptObjectMessage::SetToZero) {\n    if (GetActive()) {\n      if (x3c_dispType == EDisplayType::MessageBox) {\n        mgr.ShowPausedHUDMemo(x40_stringTableId, x34_parms.GetDisplayTime());\n      } else if (x3c_dispType == EDisplayType::StatusMessage) {\n        if (x44_stringTable) {\n          MP1::CSamusHud::DisplayHudMemo((*x44_stringTable)->GetString(0), x34_parms);\n        } else {\n          MP1::CSamusHud::DisplayHudMemo(u\"\", x34_parms);\n        }\n      }\n    }\n  } else if (msg == EScriptObjectMessage::Deactivate && GetActive() && x3c_dispType == EDisplayType::StatusMessage) {\n    MP1::CSamusHud::DisplayHudMemo(u\"\", CHUDMemoParms(0.f, false, true, false));\n  }\n\n  CEntity::AcceptScriptMsg(msg, uid, mgr);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptHUDMemo.hpp",
    "content": "#pragma once\n\n#include <optional>\n#include <string_view>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/GuiSys/CStringTable.hpp\"\n#include \"Runtime/World/CEntity.hpp\"\n#include \"Runtime/World/CHUDMemoParms.hpp\"\n\nnamespace metaforce {\n\nclass CScriptHUDMemo : public CEntity {\npublic:\n  enum class EDisplayType {\n    StatusMessage,\n    MessageBox,\n  };\n  CHUDMemoParms x34_parms;\n  EDisplayType x3c_dispType;\n  CAssetId x40_stringTableId;\n  std::optional<TLockedToken<CStringTable>> x44_stringTable;\n\nprivate:\npublic:\n  DEFINE_ENTITY\n  CScriptHUDMemo(TUniqueId, std::string_view, const CEntityInfo&, const CHUDMemoParms&, CScriptHUDMemo::EDisplayType,\n                 CAssetId, bool);\n\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptMazeNode.cpp",
    "content": "#include \"Runtime/World/CScriptMazeNode.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Character/CModelData.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nstd::array<s32, 300> sMazeSeeds;\n\nstd::array<zeus::CVector3f, skMazeRows * skMazeCols> sDebugCellPos;\n\nCScriptMazeNode::CScriptMazeNode(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                                 const zeus::CTransform& xf, bool active, s32 col, s32 row, s32 side,\n                                 const zeus::CVector3f& actorPos, const zeus::CVector3f& triggerPos,\n                                 const zeus::CVector3f& effectPos)\n: CActor(uid, active, name, info, xf, CModelData::CModelDataNull(), CMaterialList(), CActorParameters::None(),\n         kInvalidUniqueId)\n, xe8_col(col)\n, xec_row(row)\n, xf0_side(static_cast<ESide>(side))\n, x100_actorPos(actorPos)\n, x110_triggerPos(triggerPos)\n, x120_effectPos(effectPos) {}\n\nvoid CScriptMazeNode::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptMazeNode::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  if (GetActive()) {\n    if (msg == EScriptObjectMessage::Action) {\n      if (auto* maze = mgr.GetCurrentMaze()) {\n        bool shouldGenObjs = false;\n        auto& cell = maze->GetCell(xe8_col, xec_row);\n        if (xf0_side == ESide::Top && cell.x0_24_openTop) {\n          if (cell.x0_28_gateTop) {\n            shouldGenObjs = true;\n            x13c_25_hasGate = true;\n          }\n        } else if (xf0_side == ESide::Right && cell.x0_25_openRight) {\n          if (cell.x0_29_gateRight) {\n            shouldGenObjs = true;\n            x13c_25_hasGate = true;\n          }\n        } else {\n          shouldGenObjs = true;\n        }\n        if (shouldGenObjs) {\n          GenerateObjects(mgr);\n        }\n        if (xf0_side == ESide::Right && cell.x1_24_puddle) {\n          x13c_24_hasPuddle = true;\n        }\n        if (x13c_25_hasGate) {\n          const auto origin = GetTranslation();\n          for (const auto& conn : GetConnectionList()) {\n            if (conn.x0_state != EScriptObjectState::Modify || conn.x4_msg != EScriptObjectMessage::Activate) {\n              continue;\n            }\n\n            bool wasGeneratingObject = mgr.GetIsGeneratingObject();\n            mgr.SetIsGeneratingObject(true);\n            const auto genObj = mgr.GenerateObject(conn.x8_objId);\n            mgr.SetIsGeneratingObject(wasGeneratingObject);\n\n            xf4_gateEffectId = genObj.second;\n            if (TCastToPtr<CActor> actor = mgr.ObjectById(genObj.second)) {\n              actor->SetTranslation(origin + x120_effectPos);\n              mgr.SendScriptMsg(actor, GetUniqueId(), EScriptObjectMessage::Activate);\n            }\n            break;\n          }\n        }\n        if (x13c_24_hasPuddle) {\n          size_t count = 0;\n          for (const auto& conn : GetConnectionList()) {\n            if ((conn.x0_state == EScriptObjectState::Closed || conn.x0_state == EScriptObjectState::DeactivateState) &&\n                conn.x4_msg == EScriptObjectMessage::Activate) {\n              count++;\n            }\n          }\n          x12c_puddleObjectIds.reserve(count);\n          for (const auto& conn : GetConnectionList()) {\n            if ((conn.x0_state == EScriptObjectState::Closed || conn.x0_state == EScriptObjectState::DeactivateState) &&\n                conn.x4_msg == EScriptObjectMessage::Activate) {\n              bool wasGeneratingObject = mgr.GetIsGeneratingObject();\n              mgr.SetIsGeneratingObject(true);\n              const auto genObj = mgr.GenerateObject(conn.x8_objId);\n              mgr.SetIsGeneratingObject(wasGeneratingObject);\n\n              x12c_puddleObjectIds.push_back(genObj.second);\n              if (TCastToPtr<CActor> actor = mgr.ObjectById(genObj.second)) {\n                actor->SetTransform(GetTransform());\n                if (conn.x0_state == EScriptObjectState::Closed) {\n                  mgr.SendScriptMsg(actor, GetUniqueId(), EScriptObjectMessage::Activate);\n                }\n              }\n            }\n          }\n        }\n      }\n    } else if (msg == EScriptObjectMessage::SetToZero) {\n      auto* maze = mgr.GetCurrentMaze();\n      if (x13c_24_hasPuddle && maze != nullptr &&\n          std::any_of(x12c_puddleObjectIds.cbegin(), x12c_puddleObjectIds.cend(), [=](auto v) { return v == uid; })) {\n        for (const auto& id : x12c_puddleObjectIds) {\n          if (auto* ent = mgr.ObjectById(id)) {\n            if (!ent->GetActive()) {\n              mgr.SendScriptMsg(ent, GetUniqueId(), EScriptObjectMessage::Activate);\n            } else {\n              mgr.FreeScriptObject(ent->GetUniqueId());\n            }\n          }\n        }\n        for (const auto ent : mgr.GetAllObjectList()) {\n          if (TCastToPtr<CScriptMazeNode> node = ent) {\n            if (node->xe8_col == xe8_col - 1 && node->xec_row == xec_row && node->xf0_side == ESide::Right) {\n              auto& cell = maze->GetCell(xe8_col - 1, xec_row);\n              if (!cell.x0_25_openRight) {\n                cell.x0_25_openRight = true;\n                node->Reset(mgr);\n                node->x13c_25_hasGate = false;\n              }\n            }\n            if (node->xe8_col == xe8_col && node->xec_row == xec_row && node->xf0_side == ESide::Right) {\n              auto& cell = maze->GetCell(xe8_col, xec_row);\n              if (!cell.x0_25_openRight) {\n                cell.x0_25_openRight = true;\n                node->Reset(mgr);\n                node->x13c_25_hasGate = false;\n              }\n            }\n            if (node->xe8_col == xe8_col && node->xec_row == xec_row && node->xf0_side == ESide::Top) {\n              auto& cell = maze->GetCell(xe8_col, xec_row);\n              if (!cell.x0_24_openTop) {\n                cell.x0_24_openTop = true;\n                node->Reset(mgr);\n                node->x13c_25_hasGate = false;\n              }\n            }\n            if (node->xe8_col == xe8_col && node->xec_row == xec_row + 1 && node->xf0_side == ESide::Top) {\n              auto& cell = maze->GetCell(xe8_col, xec_row + 1);\n              if (!cell.x0_24_openTop) {\n                cell.x0_24_openTop = true;\n                node->Reset(mgr);\n                node->x13c_25_hasGate = false;\n              }\n            }\n          }\n        }\n      }\n    } else if (msg == EScriptObjectMessage::Deactivate) {\n      Reset(mgr);\n    } else if (msg == EScriptObjectMessage::InitializedInArea) {\n      if (mgr.GetCurrentMaze() == nullptr) {\n        auto maze = std::make_unique<CMazeState>(skEnterCol, skEnterRow, skTargetCol, skTargetRow);\n        maze->Reset(sMazeSeeds[mgr.GetActiveRandom()->Next() % sMazeSeeds.size()]);\n        maze->Initialize();\n        maze->GenerateObstacles();\n        mgr.SetCurrentMaze(std::move(maze));\n      }\n      if (xf0_side == ESide::Right) {\n        sDebugCellPos[xe8_col + xec_row * skMazeCols] = GetTranslation();\n      } else if (xe8_col == skMazeCols - 1) {\n        // Last column does not have right nodes, but we can infer the position\n        sDebugCellPos[xe8_col + xec_row * skMazeCols] = GetTranslation() - zeus::CVector3f{1.1875f, -0.1215f, 1.2187f};\n      }\n    }\n  }\n  // URDE change: used to be in the above if branch\n  if (msg == EScriptObjectMessage::Deleted) {\n    mgr.ClearCurrentMaze();\n    Reset(mgr);\n  }\n  CActor::AcceptScriptMsg(msg, uid, mgr);\n}\n\nvoid CScriptMazeNode::Think(float dt, CStateManager& mgr) {\n  if (!GetActive() || !x13c_25_hasGate) {\n    return;\n  }\n  xf8_msgTimer -= dt;\n  if (xf8_msgTimer <= 0.f) {\n    xf8_msgTimer = 1.f;\n    if (x13c_26_gateActive) {\n      x13c_26_gateActive = false;\n      SendScriptMsgs(mgr, EScriptObjectMessage::Deactivate);\n    } else {\n      x13c_26_gateActive = true;\n      SendScriptMsgs(mgr, EScriptObjectMessage::Activate);\n    }\n  }\n}\n\nvoid CScriptMazeNode::LoadMazeSeeds() {\n  const SObjectTag* tag = g_ResFactory->GetResourceIdByName(\"DUMB_MazeSeeds\");\n  const u32 resSize = g_ResFactory->ResourceSize(*tag);\n  const std::unique_ptr<u8[]> buf = g_ResFactory->LoadResourceSync(*tag);\n  CMemoryInStream in(buf.get(), resSize);\n  for (auto& seed : sMazeSeeds) {\n    seed = in.ReadInt32();\n  }\n}\n\nvoid CScriptMazeNode::GenerateObjects(CStateManager& mgr) {\n  for (const auto& conn : GetConnectionList()) {\n    if (conn.x0_state != EScriptObjectState::MaxReached || conn.x4_msg != EScriptObjectMessage::Activate) {\n      continue;\n    }\n    const auto* ent = mgr.GetObjectById(mgr.GetIdForScript(conn.x8_objId));\n    TCastToConstPtr<CScriptEffect> scriptEffect{ent};\n    TCastToConstPtr<CScriptActor> scriptActor{ent};\n    TCastToConstPtr<CScriptTrigger> scriptTrigger{ent};\n    if ((scriptEffect || scriptActor || scriptTrigger) && (!scriptEffect || !x13c_25_hasGate)) {\n      bool wasGeneratingObject = mgr.GetIsGeneratingObject();\n      mgr.SetIsGeneratingObject(true);\n      const auto genObj = mgr.GenerateObject(conn.x8_objId);\n      mgr.SetIsGeneratingObject(wasGeneratingObject);\n      if (auto* actor = static_cast<CActor*>(mgr.ObjectById(genObj.second))) {\n        mgr.SendScriptMsg(actor, GetUniqueId(), EScriptObjectMessage::Activate);\n        if (scriptEffect) {\n          actor->SetTranslation(GetTranslation() + x120_effectPos);\n          x11c_effectId = genObj.second;\n        }\n        if (scriptActor) {\n          actor->SetTranslation(GetTranslation() + x100_actorPos);\n          xfc_actorId = genObj.second;\n        }\n        if (scriptTrigger) {\n          actor->SetTranslation(GetTranslation() + x110_triggerPos);\n          x10c_triggerId = genObj.second;\n        }\n      }\n    }\n  }\n}\n\nvoid CScriptMazeNode::Reset(CStateManager& mgr) {\n  mgr.FreeScriptObject(x11c_effectId);\n  mgr.FreeScriptObject(xfc_actorId);\n  mgr.FreeScriptObject(x10c_triggerId);\n  mgr.FreeScriptObject(xf4_gateEffectId);\n  xf4_gateEffectId = kInvalidUniqueId;\n  xfc_actorId = kInvalidUniqueId;\n  x10c_triggerId = kInvalidUniqueId;\n  x11c_effectId = kInvalidUniqueId;\n}\n\nvoid CScriptMazeNode::SendScriptMsgs(CStateManager& mgr, EScriptObjectMessage msg) {\n  mgr.SendScriptMsg(x11c_effectId, GetUniqueId(), msg);\n  mgr.SendScriptMsg(xfc_actorId, GetUniqueId(), msg);\n  mgr.SendScriptMsg(x10c_triggerId, GetUniqueId(), msg);\n  mgr.SendScriptMsg(xf4_gateEffectId, GetUniqueId(), msg);\n}\n\nvoid CMazeState::Reset(s32 seed) {\n  x0_rand.SetSeed(seed);\n  x94_24_initialized = false;\n  x4_cells.fill({});\n\n  std::array<ESide, 4> sides{};\n  s32 cellIdx = 0;\n  for (u32 i = skMazeCols * skMazeRows - 1; i != 0;) {\n    u32 acc = 0;\n    if (cellIdx - skMazeCols > 0 && !GetCell(cellIdx - skMazeCols).IsOpen()) {\n      sides[acc++] = ESide::Top;\n    }\n    if (cellIdx < x4_cells.size() - 2 && (cellIdx + 1) % skMazeCols != 0 && !GetCell(cellIdx + 1).IsOpen()) {\n      sides[acc++] = ESide::Right;\n    }\n    if (cellIdx + skMazeCols < x4_cells.size() && !GetCell(cellIdx + skMazeCols).IsOpen()) {\n      sides[acc++] = ESide::Bottom;\n    }\n    if (cellIdx > 0 && cellIdx % skMazeCols != 0 && !GetCell(cellIdx - 1).IsOpen()) {\n      sides[acc++] = ESide::Left;\n    }\n\n    if (acc == 0) {\n      do {\n        cellIdx++;\n        if (cellIdx > x4_cells.size() - 1) {\n          cellIdx = 0;\n        }\n      } while (!GetCell(cellIdx).IsOpen());\n      continue;\n    }\n\n    i--;\n    ESide side = sides[x0_rand.Next() % acc];\n    if (side == ESide::Bottom) {\n      GetCell(cellIdx).x0_26_openBottom = true;\n      GetCell(cellIdx + skMazeCols).x0_24_openTop = true;\n      cellIdx += skMazeCols;\n    } else if (side == ESide::Top) {\n      GetCell(cellIdx).x0_24_openTop = true;\n      GetCell(cellIdx - skMazeCols).x0_26_openBottom = true;\n      cellIdx -= skMazeCols;\n    } else if (side == ESide::Right) {\n      GetCell(cellIdx).x0_25_openRight = true;\n      GetCell(cellIdx + 1).x0_27_openLeft = true;\n      cellIdx++;\n    } else if (side == ESide::Left) {\n      GetCell(cellIdx).x0_27_openLeft = true;\n      GetCell(cellIdx - 1).x0_25_openRight = true;\n      cellIdx--;\n    }\n  }\n}\n\nvoid CMazeState::Initialize() {\n  std::array<s32, skMazeRows * skMazeCols> path{};\n  path[0] = x84_enterCol + x88_enterRow * skMazeCols;\n  GetCell(path[0]).x1_26_checked = true;\n  s32 pathLength = 1;\n  while (path[0] != x8c_targetCol + x90_targetRow * skMazeCols) {\n    if (GetCell(path[0]).x0_24_openTop) {\n      if (!GetCell(path[0] - skMazeCols).x1_26_checked) {\n        path[pathLength] = path[0] - skMazeCols;\n        pathLength++;\n      }\n    }\n    if (GetCell(path[0]).x0_25_openRight) {\n      if (!GetCell(path[0] + 1).x1_26_checked) {\n        path[pathLength] = path[0] + 1;\n        pathLength++;\n      }\n    }\n    if (GetCell(path[0]).x0_26_openBottom) {\n      if (!GetCell(path[0] + skMazeCols).x1_26_checked) {\n        path[pathLength] = path[0] + skMazeCols;\n        pathLength++;\n      }\n    }\n    if (GetCell(path[0]).x0_27_openLeft) {\n      if (!GetCell(path[0] - 1).x1_26_checked) {\n        path[pathLength] = path[0] - 1;\n        pathLength++;\n      }\n    }\n    if (path[0] == path[pathLength - 1]) {\n      pathLength--;\n    }\n    path[0] = path[pathLength - 1];\n    GetCell(path[0]).x1_26_checked = true;\n  }\n  s32* idx = &path[pathLength];\n  while (pathLength != 0) {\n    pathLength--;\n    idx--;\n    auto& cell = GetCell(*idx);\n    if (cell.x1_26_checked) {\n      cell.x1_25_onPath = true;\n      if (pathLength > 0) {\n        m_path.push_back(*idx);\n      }\n    }\n  }\n  x94_24_initialized = true;\n}\n\nvoid CMazeState::GenerateObstacles() {\n  if (!x94_24_initialized) {\n    Initialize();\n  }\n\n  auto GetRandom = [this](s32 offset) {\n    s32 tmp = x0_rand.Next();\n    return tmp + ((tmp / 5) * -5) + offset;\n  };\n  s32 gate1Idx = GetRandom(9);\n  s32 gate2Idx = GetRandom(21);\n  s32 gate3Idx = GetRandom(33);\n  s32 puddle1Idx = GetRandom(13);\n  s32 puddle2Idx = GetRandom(29);\n\n  ESide side = ESide::Invalid;\n  s32 idx = 0;\n\n  s32 prevCol = x84_enterCol;\n  s32 prevRow = x88_enterRow;\n  s32 col = x84_enterCol;\n  s32 row = x88_enterRow;\n\n  while (col != x8c_targetCol || row != x90_targetRow) {\n    if (idx == gate1Idx || idx == gate2Idx || idx == gate3Idx) {\n      if (side == ESide::Bottom) {\n        GetCell(col, row).x0_28_gateTop = true;\n        GetCell(prevCol, prevRow).x0_30_gateBottom = true;\n      } else if (side == ESide::Top) {\n        GetCell(col, row).x0_30_gateBottom = true;\n        GetCell(prevCol, prevRow).x0_28_gateTop = true;\n      } else if (side == ESide::Right) {\n        GetCell(col, row).x0_31_gateLeft = true;\n        GetCell(prevCol, prevRow).x0_29_gateRight = true;\n      } else if (side == ESide::Left) {\n        GetCell(col, row).x0_29_gateRight = true;\n        GetCell(prevCol, prevRow).x0_31_gateLeft = true;\n      }\n    }\n\n    s32 nextCol = col;\n    s32 nextRow = -1;\n    if (row < 1 || side == ESide::Bottom || !GetCell(col, row).x0_24_openTop || !GetCell(col, row - 1).x1_25_onPath) {\n      if (row < skMazeRows - 1 && side != ESide::Top && GetCell(col, row).x0_26_openBottom &&\n          GetCell(col, row + 1).x1_25_onPath) {\n        side = ESide::Bottom;\n        nextRow = row + 1;\n      } else {\n        nextRow = row;\n        if (col < 1 || side == ESide::Right || !GetCell(col, row).x0_27_openLeft ||\n            !GetCell(col - 1, row).x1_25_onPath) {\n          if (col > skMazeRows || side == ESide::Left || !GetCell(col, row).x0_25_openRight ||\n              !GetCell(col + 1, row).x1_25_onPath) {\n            return;\n          }\n          side = ESide::Right;\n          nextCol = col + 1;\n        } else {\n          side = ESide::Left;\n          nextCol = col - 1;\n        }\n      }\n    } else {\n      side = ESide::Top;\n      nextRow = row - 1;\n    }\n\n    if (idx == puddle1Idx || idx == puddle2Idx) {\n      if (col == 0 || row == 0 || col == skMazeCols - 1 || row == skMazeRows - 1) {\n        if (idx == puddle1Idx) {\n          puddle1Idx++;\n        } else {\n          puddle2Idx++;\n        }\n      } else {\n        auto& cell = GetCell(col, row);\n        cell.x1_24_puddle = true;\n        if (side == ESide::Bottom) {\n          GetCell(nextCol, nextRow).x0_24_openTop = false;\n          cell.x0_26_openBottom = false;\n        } else if (side == ESide::Top) {\n          GetCell(nextCol, nextRow).x0_26_openBottom = false;\n          cell.x0_24_openTop = false;\n        } else if (side == ESide::Right) {\n          GetCell(nextCol, nextRow).x0_27_openLeft = false;\n          cell.x0_25_openRight = false;\n        } else if (side == ESide::Left) {\n          GetCell(nextCol, nextRow).x0_25_openRight = false;\n          cell.x0_27_openLeft = false;\n        }\n      }\n    }\n\n    idx++;\n    prevCol = col;\n    prevRow = row;\n    col = nextCol;\n    row = nextRow;\n  };\n}\n\nvoid CMazeState::DebugRender() {\n  // m_renderer.Reset();\n  // m_renderer.AddVertex(sDebugCellPos[skEnterCol + skEnterRow * skMazeCols], zeus::skBlue, 2.f);\n  for (s32 i = m_path.size() - 1; i >= 0; --i) {\n    s32 idx = m_path[i];\n    zeus::CVector3f pos;\n    if (idx == skMazeCols - 1) {\n      // 8,0 has no node, infer from 8,1\n      pos = sDebugCellPos[idx + skMazeCols] + zeus::CVector3f{4.f, 0.f, 0.f};\n    } else {\n      pos = sDebugCellPos[idx];\n    }\n    // m_renderer.AddVertex(pos, zeus::skBlue, 2.f);\n  }\n  // m_renderer.Render();\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptMazeNode.hpp",
    "content": "#pragma once\n\n#include <array>\n#include <cassert>\n#include <string_view>\n\n#include \"Runtime/CRandom16.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/World/CActor.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nconstexpr s32 skMazeCols = 9;\nconstexpr s32 skMazeRows = 7;\nconstexpr s32 skEnterCol = 4;\nconstexpr s32 skEnterRow = 4;\nconstexpr s32 skTargetCol = 5;\nconstexpr s32 skTargetRow = 3;\n\nenum class ESide {\n  Invalid = -1,\n  Top = 0,\n  Right = 1,\n  Bottom = 2,\n  Left = 3,\n};\n\nstruct SMazeCell {\n  bool x0_24_openTop : 1 = false;\n  bool x0_25_openRight : 1 = false;\n  bool x0_26_openBottom : 1 = false;\n  bool x0_27_openLeft : 1 = false;\n  bool x0_28_gateTop : 1 = false;\n  bool x0_29_gateRight : 1 = false;\n  bool x0_30_gateBottom : 1 = false;\n  bool x0_31_gateLeft : 1 = false;\n  bool x1_24_puddle : 1 = false;\n  bool x1_25_onPath : 1 = false;\n  bool x1_26_checked : 1 = false;\n\n  [[nodiscard]] constexpr bool IsOpen() const {\n    return x0_24_openTop || x0_25_openRight || x0_26_openBottom || x0_27_openLeft;\n  }\n};\n\nclass CMazeState {\n  CRandom16 x0_rand{0};\n  std::array<SMazeCell, skMazeRows * skMazeCols> x4_cells{};\n  s32 x84_enterCol;\n  s32 x88_enterRow;\n  s32 x8c_targetCol;\n  s32 x90_targetRow;\n  bool x94_24_initialized : 1 = false;\n\n  std::vector<s32> m_path;\n\npublic:\n  CMazeState(s32 enterCol, s32 enterRow, s32 targetCol, s32 targetRow)\n  : x84_enterCol(enterCol), x88_enterRow(enterRow), x8c_targetCol(targetCol), x90_targetRow(targetRow) {}\n  void Reset(s32 seed);\n  void Initialize();\n  void GenerateObstacles();\n\n  void DebugRender();\n\n  [[nodiscard]] SMazeCell& GetCell(u32 col, u32 row) {\n#ifndef NDEBUG\n    assert(col < skMazeCols);\n    assert(row < skMazeRows);\n#endif\n    return x4_cells[col + row * skMazeCols];\n  }\n  [[nodiscard]] SMazeCell& GetCell(u32 idx) {\n#ifndef NDEBUG\n    assert(idx < x4_cells.size());\n#endif\n    return x4_cells[idx];\n  }\n};\n\nclass CScriptMazeNode : public CActor {\n  s32 xe8_col;\n  s32 xec_row;\n  ESide xf0_side;\n  TUniqueId xf4_gateEffectId = kInvalidUniqueId;\n  float xf8_msgTimer = 1.f;\n  TUniqueId xfc_actorId = kInvalidUniqueId;\n  zeus::CVector3f x100_actorPos;\n  TUniqueId x10c_triggerId = kInvalidUniqueId;\n  zeus::CVector3f x110_triggerPos;\n  TUniqueId x11c_effectId = kInvalidUniqueId;\n  zeus::CVector3f x120_effectPos;\n  std::vector<TUniqueId> x12c_puddleObjectIds;\n  bool x13c_24_hasPuddle : 1 = false;\n  bool x13c_25_hasGate : 1 = false;\n  bool x13c_26_gateActive : 1 = true;\n\npublic:\n  DEFINE_ENTITY\n  CScriptMazeNode(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                  bool active, s32 col, s32 row, s32 side, const zeus::CVector3f& actorPos,\n                  const zeus::CVector3f& triggerPos, const zeus::CVector3f& effectPos);\n\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) override;\n  void Think(float dt, CStateManager& mgr) override;\n\n  static void LoadMazeSeeds();\n\nprivate:\n  void GenerateObjects(CStateManager& mgr);\n  void Reset(CStateManager& mgr);\n  void SendScriptMsgs(CStateManager& mgr, EScriptObjectMessage msg);\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptMemoryRelay.cpp",
    "content": "#include \"Runtime/World/CScriptMemoryRelay.hpp\"\n\n#include \"Runtime/CScriptMailbox.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCScriptMemoryRelay::CScriptMemoryRelay(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                                       bool defaultActive, bool skipSendActive, bool ignoreMessages)\n: CEntity(uid, info, true, name)\n, x34_24_defaultActive(defaultActive)\n, x34_25_skipSendActive(skipSendActive)\n, x34_26_ignoreMessages(ignoreMessages) {}\n\nvoid CScriptMemoryRelay::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptMemoryRelay::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) {\n  if (x34_26_ignoreMessages) {\n    return;\n  }\n\n  if (msg == EScriptObjectMessage::Deactivate) {\n    stateMgr.GetMailbox()->RemoveMsg(xc_editorId);\n    return;\n  } else if (msg == EScriptObjectMessage::Activate) {\n    stateMgr.GetMailbox()->AddMsg(xc_editorId);\n    if (!x34_25_skipSendActive) {\n      SendScriptMsgs(EScriptObjectState::Active, stateMgr, EScriptObjectMessage::None);\n    }\n\n    return;\n  }\n\n  CEntity::AcceptScriptMsg(msg, objId, stateMgr);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptMemoryRelay.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/World/CEntity.hpp\"\n\nnamespace metaforce {\nclass CScriptMemoryRelay : public CEntity {\n  bool x34_24_defaultActive;\n  bool x34_25_skipSendActive;\n  bool x34_26_ignoreMessages;\n\npublic:\n  DEFINE_ENTITY\n  CScriptMemoryRelay(TUniqueId, std::string_view name, const CEntityInfo&, bool, bool, bool);\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) override;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptMidi.cpp",
    "content": "#include \"Runtime/World/CScriptMidi.hpp\"\n\n#include \"Runtime/CInGameTweakManagerBase.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/MP1/CInGameGuiManager.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCScriptMidi::CScriptMidi(TUniqueId id, const CEntityInfo& info, std::string_view name, bool active, CAssetId csng,\n                         float fadeIn, float fadeOut, s32 volume)\n: CEntity(id, info, active, name), x40_fadeInTime(fadeIn), x44_fadeOutTime(fadeOut), x48_volume(volume) {\n  x34_song = g_SimplePool->GetObj(SObjectTag{FOURCC('CSNG'), csng});\n}\n\nCScriptMidi::~CScriptMidi() {\n  StopInternal(0.f);\n}\n\nvoid CScriptMidi::StopInternal(float fadeTime) {\n  if (!x3c_handle) {\n    return;\n  }\n\n  CMidiManager::Stop(x3c_handle, fadeTime);\n  x3c_handle.reset();\n}\n\nvoid CScriptMidi::Stop(CStateManager& mgr, float fadeTime) {\n  const CWorld* wld = mgr.GetWorld();\n  const CGameArea* area = wld->GetAreaAlways(x4_areaId);\n  const std::string twkName =\n      CInGameTweakManagerBase::GetIdentifierForMidiEvent(wld->IGetWorldAssetId(), area->GetAreaAssetId(), x10_name);\n  if (g_TweakManager->HasTweakValue(twkName)) {\n    const CTweakValue::Audio& audio = g_TweakManager->GetTweakValue(twkName)->GetAudio();\n    fadeTime = audio.GetFadeOut();\n  }\n\n  StopInternal(fadeTime);\n}\n\nvoid CScriptMidi::Play(CStateManager& mgr, float fadeTime) {\n  u32 volume = x48_volume;\n  const CWorld* wld = mgr.GetWorld();\n  const CGameArea* area = wld->GetAreaAlways(x4_areaId);\n  const std::string twkName =\n      CInGameTweakManagerBase::GetIdentifierForMidiEvent(wld->IGetWorldAssetId(), area->GetAreaAssetId(), x10_name);\n  if (g_TweakManager->HasTweakValue(twkName)) {\n    const CTweakValue::Audio& audio = g_TweakManager->GetTweakValue(twkName)->GetAudio();\n    x34_song = g_SimplePool->GetObj(SObjectTag{FOURCC('CSNG'), audio.GetResId()});\n    fadeTime = audio.GetFadeIn();\n    volume = audio.GetVolume() * 127.f;\n  }\n\n  x3c_handle = CMidiManager::Play(*x34_song, fadeTime, false, volume / 127.f);\n}\n\nvoid CScriptMidi::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptMidi::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) {\n  CEntity::AcceptScriptMsg(msg, objId, stateMgr);\n  switch (msg) {\n  case EScriptObjectMessage::Play:\n    if (GetActive()) {\n      Play(stateMgr, x40_fadeInTime);\n    }\n    break;\n  case EScriptObjectMessage::Stop:\n    if (GetActive()) {\n      Stop(stateMgr, x44_fadeOutTime);\n    }\n    break;\n  case EScriptObjectMessage::Deactivate:\n    StopInternal(0.f);\n    break;\n  default:\n    break;\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptMidi.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Audio/CMidiManager.hpp\"\n#include \"Runtime/World/CEntity.hpp\"\n\nnamespace metaforce {\n\nclass CScriptMidi : public CEntity {\n  TToken<CMidiManager::CMidiData> x34_song;\n  CMidiHandle x3c_handle;\n  float x40_fadeInTime;\n  float x44_fadeOutTime;\n  u16 x48_volume;\n\n  void StopInternal(float fadeTime);\n\npublic:\n  DEFINE_ENTITY\n  CScriptMidi(TUniqueId id, const CEntityInfo& info, std::string_view name, bool active, CAssetId csng, float, float,\n              s32);\n  ~CScriptMidi() override;\n\n  void Stop(CStateManager& mgr, float fadeTime);\n  void Play(CStateManager& mgr, float fadeTime);\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptPickup.cpp",
    "content": "#include \"Runtime/World/CScriptPickup.hpp\"\n\n#include \"Runtime/CGameOptions.hpp\"\n#include \"Runtime/CGameState.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Camera/CFirstPersonCamera.hpp\"\n#include \"Runtime/GuiSys/CStringTable.hpp\"\n#include \"Runtime/MP1/CArtifactDoll.hpp\"\n#include \"Runtime/MP1/CSamusHud.hpp\"\n#include \"Runtime/Particle/CGenDescription.hpp\"\n\n#include \"Runtime/World/CExplosion.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nCScriptPickup::CScriptPickup(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                             CModelData&& mData, const CActorParameters& aParams, const zeus::CAABox& aabb,\n                             CPlayerState::EItemType itemType, s32 amount, s32 capacity, CAssetId pickupEffect,\n                             float possibility, float lifeTime, float fadeInTime, float startDelay, bool active)\n: CPhysicsActor(uid, active, name, info, xf, std::move(mData), CMaterialList(), aabb, SMoverData(1.f), aParams, 0.3f,\n                0.1f)\n, x258_itemType(itemType)\n, x25c_amount(amount)\n, x260_capacity(capacity)\n, x264_possibility(possibility)\n, x268_fadeInTime(fadeInTime)\n, x26c_lifeTime(lifeTime)\n, x278_delayTimer(startDelay) {\n  if (pickupEffect.IsValid()) {\n    x27c_pickupParticleDesc = g_SimplePool->GetObj({SBIG('PART'), pickupEffect});\n  }\n\n  if (x64_modelData && x64_modelData->GetAnimationData()) {\n    x64_modelData->GetAnimationData()->SetAnimation(CAnimPlaybackParms(0, -1, 1.f, true), false);\n  }\n\n  if (x278_delayTimer != 0.f) {\n    xb4_drawFlags = CModelFlags(5, 0, 1, zeus::CColor(1.f, 1.f, 1.f, 0.f));\n  }\n}\n\nvoid CScriptPickup::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptPickup::Think(float dt, CStateManager& mgr) {\n  if (!GetActive()) {\n    return;\n  }\n\n  if (x278_delayTimer >= 0.f) {\n    CPhysicsActor::Stop();\n    x278_delayTimer -= dt;\n    return;\n  }\n\n  x270_curTime += dt;\n  if (x28c_25_inTractor && (x26c_lifeTime - x270_curTime) < 2.f) {\n    x270_curTime = zeus::max(x270_curTime - 2.f * dt, x26c_lifeTime - 2.f - FLT_EPSILON);\n  }\n\n  CModelFlags drawFlags{0, 0, 3, zeus::CColor(1.f, 1.f, 1.f, 1.f)};\n\n  if (x268_fadeInTime != 0.f) {\n    if (x270_curTime < x268_fadeInTime) {\n      drawFlags = CModelFlags(5, 0, 1, zeus::CColor(1.f, x270_curTime / x268_fadeInTime));\n    } else {\n      x268_fadeInTime = 0.f;\n    }\n  } else if (x26c_lifeTime != 0.f) {\n    float alpha = 1.f;\n    if (x26c_lifeTime < 2.f) {\n      alpha = 1.f - (x26c_lifeTime / x270_curTime);\n    } else if ((x26c_lifeTime - x270_curTime) < 2.f) {\n      alpha = (x26c_lifeTime - x270_curTime) / 2.f;\n    }\n\n    drawFlags = CModelFlags(5, 0, 1, zeus::CColor(1.f, alpha));\n  }\n\n  xb4_drawFlags = drawFlags;\n\n  if (x64_modelData && x64_modelData->HasAnimData()) {\n    SAdvancementDeltas deltas = UpdateAnimation(dt, mgr, true);\n    MoveToOR(deltas.x0_posDelta, dt);\n    RotateToOR(deltas.xc_rotDelta, dt);\n  }\n\n  if (x28c_25_inTractor) {\n    zeus::CVector3f posDelta = mgr.GetPlayer().GetTranslation() + (2.f * zeus::skUp) - GetTranslation();\n    x274_tractorTime += dt;\n    posDelta = (20.f * (0.5f * zeus::min(2.f, x274_tractorTime))) * posDelta.normalized();\n\n    if (x28c_26_enableTractorTest &&\n        (mgr.GetPlayer().GetPlayerGun()->IsCharging() ? mgr.GetPlayer().GetPlayerGun()->GetChargeBeamFactor() : 0.f) <\n            CPlayerGun::skTractorBeamFactor) {\n      x28c_26_enableTractorTest = false;\n      x28c_25_inTractor = false;\n      posDelta.zeroOut();\n    }\n    SetVelocityWR(posDelta);\n  } else if (x28c_24_generated) {\n    const float chargeFactor =\n        mgr.GetPlayer().GetPlayerGun()->IsCharging() ? mgr.GetPlayer().GetPlayerGun()->GetChargeBeamFactor() : 0.f;\n\n    if (chargeFactor > CPlayerGun::skTractorBeamFactor) {\n      const zeus::CVector3f posDelta =\n          GetTranslation() - mgr.GetCameraManager()->GetFirstPersonCamera()->GetTranslation();\n      const float relFov = zeus::CRelAngle(zeus::degToRad(g_tweakGame->GetFirstPersonFOV())).asRel();\n      if (mgr.GetCameraManager()->GetFirstPersonCamera()->GetTransform().frontVector().dot(posDelta.normalized()) >\n              std::cos(relFov) &&\n          posDelta.magSquared() < (30.f * 30.f)) {\n        x28c_25_inTractor = true;\n        x28c_26_enableTractorTest = true;\n        x274_tractorTime = 0.f;\n      }\n    }\n  }\n\n  if (x26c_lifeTime != 0.f && x270_curTime > x26c_lifeTime) {\n    mgr.FreeScriptObject(GetUniqueId());\n  }\n}\n\nvoid CScriptPickup::Touch(CActor& act, CStateManager& mgr) {\n  if (GetActive() && x278_delayTimer < 0.f && TCastToPtr<CPlayer>(act)) {\n    if (x258_itemType >= CPlayerState::EItemType::Truth && x258_itemType <= CPlayerState::EItemType::Newborn) {\n      const CAssetId id = MP1::CArtifactDoll::GetArtifactHeadScanFromItemType(x258_itemType);\n      if (id.IsValid()) {\n        mgr.GetPlayerState()->SetScanTime(id, 0.5f);\n      }\n    }\n\n    if (x27c_pickupParticleDesc) {\n      if (mgr.GetPlayerState()->GetActiveVisor(mgr) != CPlayerState::EPlayerVisor::Thermal) {\n        mgr.AddObject(new CExplosion(x27c_pickupParticleDesc, mgr.AllocateUniqueId(), true,\n                                     CEntityInfo(GetAreaIdAlways(), CEntity::NullConnectionList, kInvalidEditorId),\n                                     \"Explosion - Pickup Effect\", x34_transform, 0, zeus::skOne3f, zeus::skWhite));\n      }\n    }\n\n    mgr.GetPlayerState()->AddPowerUp(x258_itemType, x260_capacity);\n    mgr.GetPlayerState()->IncrPickup(x258_itemType, x25c_amount);\n    mgr.FreeScriptObject(GetUniqueId());\n    SendScriptMsgs(EScriptObjectState::Arrived, mgr, EScriptObjectMessage::None);\n\n    if (x260_capacity > 0) {\n      const u32 total = mgr.GetPlayerState()->GetPickupTotal();\n      const u32 colRate = mgr.GetPlayerState()->CalculateItemCollectionRate();\n      if (total == colRate) {\n        CPersistentOptions& opts = g_GameState->SystemOptions();\n        mgr.QueueMessage(\n            mgr.GetHUDMessageFrameCount() + 1,\n            g_ResFactory\n                ->GetResourceIdByName(opts.GetAllItemsCollected() ? \"STRG_AllPickupsFound_2\" : \"STRG_AllPickupsFound_1\")\n                ->id,\n            0.f);\n        opts.SetAllItemsCollected(true);\n      }\n    }\n\n    if (x258_itemType == CPlayerState::EItemType::PowerBombs &&\n        g_GameState->SystemOptions().GetShowPowerBombAmmoMessage()) {\n      g_GameState->SystemOptions().IncrementPowerBombAmmoCount();\n      MP1::CSamusHud::DisplayHudMemo(g_MainStringTable->GetString(109), {5.f, true, false, false});\n    }\n  }\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptPickup.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/CPlayerState.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/World/CPhysicsActor.hpp\"\n\nnamespace metaforce {\nclass CScriptPickup : public CPhysicsActor {\n  CPlayerState::EItemType x258_itemType;\n  s32 x25c_amount;\n  s32 x260_capacity;\n  float x264_possibility;\n  float x268_fadeInTime;\n  float x26c_lifeTime;\n  float x270_curTime = 0.f;\n  float x274_tractorTime = 0.f;\n  float x278_delayTimer;\n  TLockedToken<CGenDescription> x27c_pickupParticleDesc;\n\n  bool x28c_24_generated : 1 = false;\n  bool x28c_25_inTractor : 1 = false;\n  bool x28c_26_enableTractorTest : 1 = false;\n\npublic:\n  DEFINE_ENTITY\n  CScriptPickup(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                CModelData&& mData, const CActorParameters& aParams, const zeus::CAABox& aabb,\n                CPlayerState::EItemType itemType, s32 amount, s32 capacity, CAssetId pickupEffect, float possibility,\n                float lifeTime, float fadeInTime, float startDelay, bool active);\n\n  void Accept(IVisitor& visitor) override;\n  void Think(float, CStateManager&) override;\n  void Touch(CActor&, CStateManager&) override;\n  std::optional<zeus::CAABox> GetTouchBounds() const override { return CPhysicsActor::GetBoundingBox(); }\n  float GetPossibility() const { return x264_possibility; }\n  CPlayerState::EItemType GetItem() const { return x258_itemType; }\n  void SetGenerated() { x28c_24_generated = true; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptPickupGenerator.cpp",
    "content": "#include \"Runtime/World/CScriptPickupGenerator.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CScriptPickup.hpp\"\n#include \"Runtime/World/CWallCrawlerSwarm.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCScriptPickupGenerator::CScriptPickupGenerator(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                                               const zeus::CVector3f& pos, float frequency, bool active)\n: CEntity(uid, info, active, name), x34_position(pos), x40_frequency(frequency) {\n  ResetDelayTimer();\n}\n\nvoid CScriptPickupGenerator::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptPickupGenerator::ResetDelayTimer() {\n  if (x40_frequency > 0.f) {\n    x44_delayTimer += 100.f / x40_frequency;\n  } else {\n    x44_delayTimer = FLT_MAX;\n  }\n}\n\nvoid CScriptPickupGenerator::GetGeneratorIds(CStateManager& mgr, TUniqueId sender,\n                                             std::vector<TUniqueId>& idsOut) const {\n  idsOut.reserve(std::max(size_t(1), GetConnectionList().size()));\n  for (const auto& conn : GetConnectionList()) {\n    if (conn.x0_state == EScriptObjectState::Zero && conn.x4_msg == EScriptObjectMessage::Follow) {\n      const TUniqueId id = mgr.GetIdForScript(conn.x8_objId);\n      if (id != kInvalidUniqueId) {\n        if (const CEntity* ent = mgr.GetObjectById(id)) {\n          if (ent->GetActive()) {\n            idsOut.push_back(id);\n          }\n        }\n      }\n    }\n  }\n  if (idsOut.empty()) {\n    idsOut.push_back(sender);\n  }\n}\n\nfloat CScriptPickupGenerator::GetPickupTemplates(CStateManager& mgr,\n                                                 std::vector<std::pair<float, TEditorId>>& idsOut) const {\n  float totalPossibility = 0.f;\n  CPlayerState& pState = *mgr.GetPlayerState();\n  idsOut.reserve(GetConnectionList().size());\n  for (const auto& conn : GetConnectionList()) {\n    if (conn.x0_state == EScriptObjectState::Zero && conn.x4_msg == EScriptObjectMessage::Activate) {\n      const TUniqueId id = mgr.GetIdForScript(conn.x8_objId);\n      if (id != kInvalidUniqueId) {\n        if (const TCastToConstPtr<CScriptPickup> pickup = mgr.GetObjectById(id)) {\n          const CPlayerState::EItemType item = pickup->GetItem();\n          float possibility = pickup->GetPossibility();\n          float multiplier = 1.f;\n          bool doAlways = false;\n          bool doThirtyPerc = false;\n          switch (item) {\n          case CPlayerState::EItemType::Missiles:\n            if (pState.HasPowerUp(CPlayerState::EItemType::Missiles)) {\n              if (pState.GetItemAmount(CPlayerState::EItemType::Missiles) <\n                  pState.GetItemCapacity(CPlayerState::EItemType::Missiles)) {\n                doAlways = true;\n              } else {\n                doThirtyPerc = true;\n              }\n            }\n            break;\n          case CPlayerState::EItemType::PowerBombs:\n            if (pState.HasPowerUp(CPlayerState::EItemType::PowerBombs)) {\n              if (pState.GetItemAmount(CPlayerState::EItemType::PowerBombs) <\n                  pState.GetItemCapacity(CPlayerState::EItemType::PowerBombs)) {\n                doAlways = true;\n                if (pState.GetItemAmount(CPlayerState::EItemType::PowerBombs) < 2 && possibility >= 10.f &&\n                    possibility < 25.f) {\n                  multiplier = 2.f;\n                }\n              } else {\n                doThirtyPerc = true;\n              }\n            }\n            break;\n          case CPlayerState::EItemType::HealthRefill:\n            if (pState.GetHealthInfo().GetHP() < pState.CalculateHealth()) {\n              doAlways = true;\n            } else {\n              doThirtyPerc = true;\n            }\n            break;\n          default:\n            doAlways = true;\n            break;\n          }\n          const bool thirtyPercTest = mgr.GetActiveRandom()->Float() < 0.3f;\n          if ((doAlways || (doThirtyPerc && thirtyPercTest)) && possibility > 0.f) {\n            totalPossibility += possibility * multiplier;\n            idsOut.emplace_back(possibility, conn.x8_objId);\n          }\n        }\n      }\n    }\n  }\n  return totalPossibility;\n}\n\nvoid CScriptPickupGenerator::GeneratePickup(CStateManager& mgr, TEditorId templateId, TUniqueId generatorId) const {\n  CEntity* pickupTempl = mgr.ObjectById(mgr.GetIdForScript(templateId));\n  CEntity* generator = mgr.ObjectById(generatorId);\n\n  if (pickupTempl == nullptr || generator == nullptr) {\n    return;\n  }\n\n  const bool oldGeneratingObject = mgr.GetIsGeneratingObject();\n  mgr.SetIsGeneratingObject(true);\n  const auto p = mgr.GenerateObject(templateId);\n  mgr.SetIsGeneratingObject(oldGeneratingObject);\n\n  if (p.second == kInvalidUniqueId) {\n    return;\n  }\n\n  CEntity* newObj = mgr.ObjectById(p.second);\n  CActor* newAct = TCastToPtr<CActor>(newObj).GetPtr();\n  CScriptPickup* newPickup = TCastToPtr<CScriptPickup>(newObj).GetPtr();\n  const CActor* generatorAct = TCastToConstPtr<CActor>(generator).GetPtr();\n  const CWallCrawlerSwarm* swarmAct = TCastToConstPtr<CWallCrawlerSwarm>(generator).GetPtr();\n\n  if (newAct && swarmAct) {\n    newAct->SetTranslation(swarmAct->GetLastKilledOffset() + x34_position);\n  } else if (newAct && generatorAct) {\n    newAct->SetTranslation(generatorAct->GetTranslation() + x34_position);\n  }\n\n  if (newPickup) {\n    newPickup->SetGenerated();\n  }\n\n  mgr.SendScriptMsg(newObj, GetUniqueId(), EScriptObjectMessage::Activate);\n}\n\nvoid CScriptPickupGenerator::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& stateMgr) {\n  if (msg == EScriptObjectMessage::SetToZero && x30_24_active && x40_frequency != 100.f) {\n    x44_delayTimer -= 1.f;\n    if (x44_delayTimer < 0.000009f) {\n      ResetDelayTimer();\n    } else {\n      std::vector<TUniqueId> generatorIds;\n      GetGeneratorIds(stateMgr, sender, generatorIds);\n      std::vector<std::pair<float, TEditorId>> pickupTemplates;\n      const float totalProb = GetPickupTemplates(stateMgr, pickupTemplates);\n      if (!pickupTemplates.empty()) {\n        const float r = stateMgr.GetActiveRandom()->Range(0.f, totalProb);\n        float f2 = 0.f;\n        size_t count = 0;\n        for (const auto& id : pickupTemplates) {\n          if (r >= f2 && r <= f2 + id.first) {\n            break;\n          }\n          f2 += id.first;\n          ++count;\n        }\n        if (count != pickupTemplates.size()) {\n          const TEditorId templateId = pickupTemplates[count].second;\n          GeneratePickup(stateMgr, templateId,\n                         generatorIds[stateMgr.GetActiveRandom()->Float() * generatorIds.size() * 0.99f]);\n        }\n      }\n    }\n  }\n\n  CEntity::AcceptScriptMsg(msg, sender, stateMgr);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptPickupGenerator.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/World/CEntity.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CScriptPickupGenerator : public CEntity {\n  zeus::CVector3f x34_position;\n  float x40_frequency;\n  float x44_delayTimer = 0.f;\n  void ResetDelayTimer();\n  void GetGeneratorIds(CStateManager& mgr, TUniqueId sender, std::vector<TUniqueId>& idsOut) const;\n  float GetPickupTemplates(CStateManager& mgr, std::vector<std::pair<float, TEditorId>>& idsOut) const;\n  void GeneratePickup(CStateManager& mgr, TEditorId templateId, TUniqueId generatorId) const;\n\npublic:\n  DEFINE_ENTITY\n  CScriptPickupGenerator(TUniqueId, std::string_view, const CEntityInfo&, const zeus::CVector3f&, float, bool);\n\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& stateMgr) override;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptPlatform.cpp",
    "content": "#include \"Runtime/World/CScriptPlatform.hpp\"\n\n#include <algorithm>\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Collision/CCollidableOBBTreeGroup.hpp\"\n#include \"Runtime/Collision/CGameCollision.hpp\"\n#include \"Runtime/Collision/CMaterialList.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CScriptColorModulate.hpp\"\n#include \"Runtime/World/CScriptTrigger.hpp\"\n#include \"Runtime/World/CScriptWaypoint.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nconstexpr auto skPlatformMaterialList =\n    CMaterialList{EMaterialTypes::Solid, EMaterialTypes::Immovable, EMaterialTypes::Platform, EMaterialTypes::Occluder};\n\nCScriptPlatform::CScriptPlatform(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                                 const zeus::CTransform& xf, CModelData&& mData, const CActorParameters& actParms,\n                                 const zeus::CAABox& aabb, float speed, bool detectCollision, float xrayAlpha,\n                                 bool active, const CHealthInfo& hInfo, const CDamageVulnerability& dVuln,\n                                 std::optional<TLockedToken<CCollidableOBBTreeGroupContainer>> dcln, bool rainSplashes,\n                                 u32 maxRainSplashes, u32 rainGenRate)\n: CPhysicsActor(uid, active, name, info, xf, std::move(mData), skPlatformMaterialList, aabb, SMoverData(15000.f),\n                actParms, 0.3f, 0.1f)\n, x25c_currentSpeed(speed)\n, x268_fadeInTime(actParms.GetFadeInTime())\n, x26c_fadeOutTime(actParms.GetFadeOutTime())\n, x28c_initialHealth(hInfo)\n, x294_health(hInfo)\n, x29c_damageVuln(dVuln)\n, x304_treeGroupContainer(std::move(dcln))\n, x348_xrayAlpha(xrayAlpha)\n, x34c_maxRainSplashes(maxRainSplashes)\n, x350_rainGenRate(rainGenRate)\n, x356_26_detectCollision(detectCollision)\n, x356_28_rainSplashes(rainSplashes) {\n  CActor::SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(\n      CMaterialList(EMaterialTypes::Solid),\n      CMaterialList(EMaterialTypes::NoStaticCollision, EMaterialTypes::NoPlatformCollision, EMaterialTypes::Platform)));\n  xf8_24_movable = false;\n  if (HasModelData() && GetModelData()->HasAnimData()) {\n    GetModelData()->GetAnimationData()->EnableLooping(true);\n  }\n  if (x304_treeGroupContainer) {\n    x314_treeGroup = std::make_unique<CCollidableOBBTreeGroup>(x304_treeGroupContainer->GetObj(), x68_material);\n  }\n}\n\nvoid CScriptPlatform::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptPlatform::DragSlave(CStateManager& mgr, rstl::reserved_vector<u16, kMaxEntities>& draggedSet, CActor* actor,\n                                const zeus::CVector3f& delta) {\n  if (std::find(draggedSet.begin(), draggedSet.end(), actor->GetUniqueId().Value()) != draggedSet.end()) {\n    return;\n  }\n\n  draggedSet.push_back(actor->GetUniqueId().Value());\n  zeus::CTransform newXf = actor->GetTransform();\n  newXf.origin += delta;\n  actor->SetTransform(newXf);\n  if (const TCastToPtr<CScriptPlatform> plat = actor) {\n    plat->DragSlaves(mgr, draggedSet, delta);\n  }\n}\n\nvoid CScriptPlatform::DragSlaves(CStateManager& mgr, rstl::reserved_vector<u16, kMaxEntities>& draggedSet,\n                                 const zeus::CVector3f& delta) {\n  for (SRiders& rider : x328_slavesStatic) {\n    if (const TCastToPtr<CActor> act = mgr.ObjectById(rider.x0_uid)) {\n      DragSlave(mgr, draggedSet, act.GetPtr(), delta);\n    }\n  }\n  for (auto it = x338_slavesDynamic.begin(); it != x338_slavesDynamic.end();) {\n    if (const TCastToPtr<CActor> act = mgr.ObjectById(it->x0_uid)) {\n      DragSlave(mgr, draggedSet, act.GetPtr(), delta);\n      ++it;\n    } else {\n      it = x338_slavesDynamic.erase(it);\n    }\n  }\n}\n\nvoid CScriptPlatform::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  switch (msg) {\n  case EScriptObjectMessage::InitializedInArea:\n    BuildSlaveList(mgr);\n    break;\n  case EScriptObjectMessage::AddPlatformRider:\n    AddRider(x318_riders, uid, this, mgr);\n    break;\n  case EScriptObjectMessage::Stop: {\n    x25c_currentSpeed = 0.f;\n    Stop();\n    break;\n  }\n  case EScriptObjectMessage::Next: {\n    x25a_targetWaypoint = GetNext(x258_currentWaypoint, mgr);\n    if (x25a_targetWaypoint == kInvalidUniqueId) {\n      mgr.SendScriptMsg(this, GetUniqueId(), EScriptObjectMessage::Stop);\n    } else if (const TCastToPtr<CScriptWaypoint> wp = mgr.ObjectById(x25a_targetWaypoint)) {\n      x25c_currentSpeed = 0.f;\n      Stop();\n      x270_dragDelta = wp->GetTranslation() - GetTranslation();\n      SetTranslation(wp->GetTranslation());\n\n      x258_currentWaypoint = x25a_targetWaypoint;\n      x25a_targetWaypoint = GetNext(x258_currentWaypoint, mgr);\n      mgr.SendScriptMsg(wp, GetUniqueId(), EScriptObjectMessage::Arrived);\n      if (!x328_slavesStatic.empty() || !x338_slavesDynamic.empty()) {\n        rstl::reserved_vector<u16, kMaxEntities> draggedSet;\n        DragSlaves(mgr, draggedSet, x270_dragDelta);\n      }\n      x270_dragDelta = zeus::skZero3f;\n    }\n    break;\n  }\n  case EScriptObjectMessage::Start: {\n    x25a_targetWaypoint = GetNext(x258_currentWaypoint, mgr);\n    if (x25a_targetWaypoint == kInvalidUniqueId) {\n      mgr.SendScriptMsg(this, GetUniqueId(), EScriptObjectMessage::Stop);\n    } else if (const TCastToConstPtr<CScriptWaypoint> wp = mgr.ObjectById(x25a_targetWaypoint)) {\n      x25c_currentSpeed = wp->GetSpeed();\n    }\n    break;\n  }\n  case EScriptObjectMessage::Reset: {\n    x356_24_dead = false;\n    x294_health = x28c_initialHealth;\n    break;\n  }\n  case EScriptObjectMessage::Increment: {\n    if (!GetActive()) {\n      mgr.SendScriptMsg(this, GetUniqueId(), EScriptObjectMessage::Activate);\n    }\n    CScriptColorModulate::FadeInHelper(mgr, GetUniqueId(), x268_fadeInTime);\n    break;\n  }\n  case EScriptObjectMessage::Decrement:\n    CScriptColorModulate::FadeOutHelper(mgr, GetUniqueId(), x26c_fadeOutTime);\n    break;\n  case EScriptObjectMessage::Deleted:\n    DecayRiders(x318_riders, 1.66666675f, mgr);\n    break;\n  default:\n    break;\n  }\n\n  CPhysicsActor::AcceptScriptMsg(msg, uid, mgr);\n}\n\nvoid CScriptPlatform::DecayRiders(std::vector<SRiders>& riders, float dt, CStateManager& mgr) {\n  for (auto it = riders.begin(); it != riders.end();) {\n    it->x4_decayTimer -= dt;\n    if (it->x4_decayTimer <= 0.f) {\n      mgr.SendScriptMsgAlways(it->x0_uid, kInvalidUniqueId, EScriptObjectMessage::AddPlatformRider);\n      it = riders.erase(it);\n      continue;\n    }\n    ++it;\n  }\n}\n\nvoid CScriptPlatform::MoveRiders(CStateManager& mgr, float dt, bool active, std::vector<SRiders>& riders,\n                                 std::vector<SRiders>& collidedRiders, const zeus::CTransform& oldXf,\n                                 const zeus::CTransform& newXf, const zeus::CVector3f& dragDelta,\n                                 const zeus::CQuaternion& rotDelta) {\n  for (auto it = riders.begin(); it != riders.end();) {\n    if (active) {\n      if (TCastToPtr<CPhysicsActor> act = mgr.ObjectById(it->x0_uid)) {\n        if (act->GetActive()) {\n          zeus::CVector3f delta =\n              newXf.rotate(it->x8_transform.origin) - oldXf.rotate(it->x8_transform.origin) + dragDelta;\n          zeus::CVector3f newPos = act->GetTranslation() + delta;\n          act->MoveCollisionPrimitive(delta);\n          bool collision = CGameCollision::DetectStaticCollisionBoolean(\n              mgr, *act->GetCollisionPrimitive(), act->GetPrimitiveTransform(), act->GetMaterialFilter());\n          act->MoveCollisionPrimitive(zeus::skZero3f);\n          if (collision) {\n            AddRider(collidedRiders, act->GetUniqueId(), act.GetPtr(), mgr);\n            it = riders.erase(it);\n            continue;\n          }\n          act->SetTranslation(newPos);\n          if (const TCastToConstPtr<CPlayer> player = act.GetPtr()) {\n            if (player->GetOrbitState() != CPlayer::EPlayerOrbitState::NoOrbit) {\n              ++it;\n              continue;\n            }\n          }\n          zeus::CTransform xf = (rotDelta * zeus::CQuaternion(act->GetTransform().basis)).toTransform();\n          xf.origin = act->GetTranslation();\n          act->SetTransform(xf);\n        }\n      }\n    }\n    ++it;\n  }\n}\n\nEntityList CScriptPlatform::BuildNearListFromRiders(CStateManager& mgr, const std::vector<SRiders>& movedRiders) {\n  EntityList ret;\n  for (const SRiders& rider : movedRiders) {\n    if (const TCastToConstPtr<CActor> act = mgr.ObjectById(rider.x0_uid)) {\n      ret.push_back(act->GetUniqueId());\n    }\n  }\n  return ret;\n}\n\nvoid CScriptPlatform::PreThink(float dt, CStateManager& mgr) {\n  DecayRiders(x318_riders, dt, mgr);\n  x264_collisionRecoverDelay -= dt;\n  x260_moveDelay -= dt;\n  if (x260_moveDelay <= 0.f) {\n    x270_dragDelta = zeus::skZero3f;\n    zeus::CTransform oldXf = x34_transform;\n    CMotionState mState = GetMotionState();\n    if (GetActive()) {\n      for (SRiders& rider : x318_riders) {\n        if (const TCastToConstPtr<CPhysicsActor> act = mgr.ObjectById(rider.x0_uid)) {\n          rider.x8_transform.origin = x34_transform.transposeRotate(act->GetTranslation() - GetTranslation());\n        }\n      }\n      x27c_rotDelta = Move(dt, mgr);\n    }\n\n    x270_dragDelta = x34_transform.origin - oldXf.origin;\n\n    std::vector<SRiders> collidedRiders;\n    MoveRiders(mgr, dt, GetActive(), x318_riders, collidedRiders, oldXf, x34_transform, x270_dragDelta, x27c_rotDelta);\n    x356_27_squishedRider = false;\n    if (!collidedRiders.empty()) {\n      EntityList nearList = BuildNearListFromRiders(mgr, collidedRiders);\n      if (CGameCollision::DetectDynamicCollisionBoolean(*GetCollisionPrimitive(), GetPrimitiveTransform(), nearList,\n                                                        mgr)) {\n        SetMotionState(mState);\n        Stop();\n        x260_moveDelay = 0.035f;\n        MoveRiders(mgr, dt, GetActive(), x318_riders, collidedRiders, x34_transform, oldXf, -x270_dragDelta,\n                   x27c_rotDelta.inverse());\n        x270_dragDelta = zeus::skZero3f;\n        SendScriptMsgs(EScriptObjectState::Modify, mgr, EScriptObjectMessage::None);\n        x356_27_squishedRider = true;\n      }\n    }\n  }\n}\n\nvoid CScriptPlatform::Think(float dt, CStateManager& mgr) {\n  if (!GetActive()) {\n    return;\n  }\n\n  if (HasModelData() && GetModelData()->HasAnimData()) {\n    if (!x356_25_controlledAnimation) {\n      UpdateAnimation(dt, mgr, true);\n    }\n\n    if (x356_28_rainSplashes && mgr.GetWorld()->GetNeededEnvFx() == EEnvFxType::Rain) {\n      if (HasModelData() && !GetModelData()->IsNull() && mgr.GetEnvFxManager()->IsSplashActive() &&\n          mgr.GetEnvFxManager()->GetRainMagnitude() != 0.f) {\n        mgr.GetActorModelParticles()->AddRainSplashGenerator(*this, mgr, x34c_maxRainSplashes, x350_rainGenRate, 0.f);\n      }\n    }\n  }\n\n  if (!x328_slavesStatic.empty() || !x338_slavesDynamic.empty()) {\n    rstl::reserved_vector<u16, kMaxEntities> draggedSet;\n    DragSlaves(mgr, draggedSet, x270_dragDelta);\n  }\n\n  if (x356_24_dead) {\n    return;\n  }\n\n  if (HealthInfo(mgr)->GetHP() <= 0.f) {\n    x356_24_dead = true;\n    SendScriptMsgs(EScriptObjectState::Dead, mgr, EScriptObjectMessage::None);\n  }\n}\n\nvoid CScriptPlatform::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) {\n  CActor::PreRender(mgr, frustum);\n\n  if (!xe4_30_outOfFrustum && !zeus::close_enough(x348_xrayAlpha, 1.f)) {\n    const CModelFlags flags(5, 0, 3, {1.f, x348_xrayAlpha});\n    if (mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::XRay && !x356_30_disableXrayAlpha) {\n      xb4_drawFlags = flags;\n      x356_29_setXrayDrawFlags = true;\n    } else if (x356_29_setXrayDrawFlags) {\n      x356_29_setXrayDrawFlags = false;\n      if (xb4_drawFlags == flags && !x356_30_disableXrayAlpha) {\n        xb4_drawFlags = CModelFlags(0, 0, 3, zeus::skWhite);\n      }\n    }\n  }\n\n  if (!mgr.GetObjectById(x354_boundsTrigger)) {\n    x354_boundsTrigger = kInvalidUniqueId;\n  }\n}\n\nvoid CScriptPlatform::Render(CStateManager& mgr) {\n  const bool xray = mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::XRay;\n  if (xray && !x356_31_xrayFog) {\n    g_Renderer->SetWorldFog(ERglFogMode::None, 0.f, 1.f, zeus::skBlack);\n  }\n\n  CPhysicsActor::Render(mgr);\n\n  if (xray && !x356_31_xrayFog) {\n    mgr.SetupFogForArea(x4_areaId);\n  }\n}\n\nstd::optional<zeus::CAABox> CScriptPlatform::GetTouchBounds() const {\n  if (x314_treeGroup) {\n    return {x314_treeGroup->CalculateAABox(GetTransform())};\n  }\n\n  return {CPhysicsActor::GetBoundingBox()};\n}\n\nzeus::CTransform CScriptPlatform::GetPrimitiveTransform() const {\n  zeus::CTransform ret = GetTransform();\n  ret.origin += GetPrimitiveOffset();\n  return ret;\n}\n\nconst CCollisionPrimitive* CScriptPlatform::GetCollisionPrimitive() const {\n  if (!x314_treeGroup) {\n    return CPhysicsActor::GetCollisionPrimitive();\n  }\n  return x314_treeGroup.get();\n}\n\nzeus::CVector3f CScriptPlatform::GetOrbitPosition(const CStateManager& mgr) const { return GetAimPosition(mgr, 0.f); }\n\nzeus::CVector3f CScriptPlatform::GetAimPosition(const CStateManager& mgr, float dt) const {\n  if (auto tb = GetTouchBounds()) {\n    return {tb->center()};\n  }\n  return CPhysicsActor::GetAimPosition(mgr, dt);\n}\n\nzeus::CAABox CScriptPlatform::GetSortingBounds(const CStateManager& mgr) const {\n  if (x354_boundsTrigger != kInvalidUniqueId) {\n    if (const TCastToConstPtr<CScriptTrigger> trig = mgr.GetObjectById(x354_boundsTrigger)) {\n      return trig->GetTriggerBoundsWR();\n    }\n  }\n  return CActor::GetSortingBounds(mgr);\n}\n\nbool CScriptPlatform::IsRider(TUniqueId id) const {\n  return std::any_of(x318_riders.cbegin(), x318_riders.cend(), [id](const auto& rider) { return rider.x0_uid == id; });\n}\n\nbool CScriptPlatform::IsSlave(TUniqueId id) const {\n  auto search = std::find_if(x328_slavesStatic.begin(), x328_slavesStatic.end(),\n                             [id](const SRiders& rider) { return rider.x0_uid == id; });\n  if (search != x328_slavesStatic.end()) {\n    return true;\n  }\n  search = std::find_if(x338_slavesDynamic.begin(), x338_slavesDynamic.end(),\n                        [id](const SRiders& rider) { return rider.x0_uid == id; });\n  return search != x338_slavesDynamic.end();\n}\n\nvoid CScriptPlatform::BuildSlaveList(CStateManager& mgr) {\n  x328_slavesStatic.reserve(GetConnectionList().size());\n  for (const SConnection& conn : GetConnectionList()) {\n    if (conn.x0_state == EScriptObjectState::Play && conn.x4_msg == EScriptObjectMessage::Activate) {\n      if (const TCastToPtr<CActor> act = mgr.ObjectById(mgr.GetIdForScript(conn.x8_objId))) {\n        act->AddMaterial(EMaterialTypes::PlatformSlave, mgr);\n        zeus::CTransform xf = act->GetTransform();\n        xf.origin = act->GetTranslation() - GetTranslation();\n        x328_slavesStatic.emplace_back(act->GetUniqueId(), 0.166667f, xf);\n      }\n    } else if (conn.x0_state == EScriptObjectState::InheritBounds && conn.x4_msg == EScriptObjectMessage::Activate) {\n      auto list = mgr.GetIdListForScript(conn.x8_objId);\n      for (auto it = list.first; it != list.second; ++it) {\n        if (TCastToConstPtr<CScriptTrigger>(mgr.GetObjectById(it->second))) {\n          x354_boundsTrigger = it->second;\n        }\n      }\n    }\n  }\n}\n\nvoid CScriptPlatform::AddRider(std::vector<SRiders>& riders, TUniqueId riderId, const CPhysicsActor* ridee,\n                               CStateManager& mgr) {\n  const auto& search =\n      std::find_if(riders.begin(), riders.end(), [riderId](const SRiders& r) { return r.x0_uid == riderId; });\n  if (search == riders.end()) {\n    zeus::CTransform xf;\n    if (const TCastToPtr<CPhysicsActor> act = mgr.ObjectById(riderId)) {\n      xf.origin = ridee->GetTransform().transposeRotate(act->GetTranslation() - ridee->GetTranslation());\n      mgr.SendScriptMsg(act.GetPtr(), ridee->GetUniqueId(), EScriptObjectMessage::AddPlatformRider);\n    }\n    riders.emplace_back(riderId, 0.166667f, xf);\n  } else {\n    search->x4_decayTimer = 0.166667f;\n  }\n}\n\nvoid CScriptPlatform::AddSlave(TUniqueId id, CStateManager& mgr) {\n  const auto& search = std::find_if(x338_slavesDynamic.begin(), x338_slavesDynamic.end(),\n                                    [id](const SRiders& r) { return r.x0_uid == id; });\n  if (search != x338_slavesDynamic.end()) {\n    return;\n  }\n\n  if (const TCastToPtr<CActor> act = mgr.ObjectById(id)) {\n    act->AddMaterial(EMaterialTypes::PlatformSlave, mgr);\n    const zeus::CTransform localXf = x34_transform.inverse() * act->GetTransform();\n    x338_slavesDynamic.emplace_back(id, 0.166667f, localXf);\n  }\n}\n\nTUniqueId CScriptPlatform::GetNext(TUniqueId uid, CStateManager& mgr) {\n  const TCastToConstPtr<CScriptWaypoint> nextWp = mgr.GetObjectById(uid);\n  if (!nextWp) {\n    return GetWaypoint(mgr);\n  }\n\n  const TUniqueId next = nextWp->NextWaypoint(mgr);\n  if (const TCastToConstPtr<CScriptWaypoint> wp = mgr.GetObjectById(next)) {\n    x25c_currentSpeed = wp->GetSpeed();\n  }\n\n  return next;\n}\n\nTUniqueId CScriptPlatform::GetWaypoint(CStateManager& mgr) {\n  for (const SConnection& conn : x20_conns) {\n    if (conn.x4_msg == EScriptObjectMessage::Follow) {\n      return mgr.GetIdForScript(conn.x8_objId);\n    }\n  }\n\n  return kInvalidUniqueId;\n}\n\nvoid CScriptPlatform::SplashThink(const zeus::CAABox&, const CFluidPlane&, float, CStateManager&) const {\n  // Empty\n}\n\nzeus::CQuaternion CScriptPlatform::Move(float dt, CStateManager& mgr) {\n  TUniqueId nextWaypoint = x25a_targetWaypoint;\n  if (x25a_targetWaypoint == kInvalidUniqueId) {\n    nextWaypoint = GetNext(x258_currentWaypoint, mgr);\n  }\n\n  const TCastToConstPtr<CScriptWaypoint> wp = mgr.ObjectById(nextWaypoint);\n  if (x258_currentWaypoint != kInvalidUniqueId && wp && !wp->GetActive()) {\n    nextWaypoint = GetNext(x258_currentWaypoint, mgr);\n    if (nextWaypoint == kInvalidUniqueId) {\n      if (const TCastToConstPtr<CScriptWaypoint> wp2 = mgr.ObjectById(x258_currentWaypoint)) {\n        if (wp2->GetActive()) {\n          nextWaypoint = x258_currentWaypoint;\n        }\n      }\n    }\n  }\n\n  if (nextWaypoint == kInvalidUniqueId) {\n    return zeus::CQuaternion();\n  }\n\n  while (nextWaypoint != kInvalidUniqueId) {\n    if (const TCastToPtr<CScriptWaypoint> wp2 = mgr.ObjectById(nextWaypoint)) {\n      const zeus::CVector3f platToWp = wp2->GetTranslation() - GetTranslation();\n      if (zeus::close_enough(platToWp, zeus::skZero3f)) {\n        x258_currentWaypoint = nextWaypoint;\n        mgr.SendScriptMsg(wp2.GetPtr(), GetUniqueId(), EScriptObjectMessage::Arrived);\n        if (zeus::close_enough(x25c_currentSpeed, 0.f, 0.02)) {\n          nextWaypoint = GetNext(x258_currentWaypoint, mgr);\n          x25c_currentSpeed = 0.f;\n          Stop();\n        } else {\n          nextWaypoint = GetNext(x258_currentWaypoint, mgr);\n        }\n\n        if (nextWaypoint != kInvalidUniqueId) {\n          continue;\n        }\n\n        mgr.SendScriptMsg(this, GetUniqueId(), EScriptObjectMessage::Stop);\n      }\n\n      if (zeus::close_enough(platToWp, zeus::skZero3f)) {\n        x270_dragDelta = wp2->GetTranslation() - GetTranslation();\n        MoveToWR(GetTranslation(), dt);\n      } else if ((platToWp.normalized() * x25c_currentSpeed * dt).magSquared() > platToWp.magSquared()) {\n        x270_dragDelta = wp2->GetTranslation() - GetTranslation();\n        MoveToWR(wp2->GetTranslation(), dt);\n      } else {\n        x270_dragDelta = platToWp.normalized() * x25c_currentSpeed * dt;\n        MoveToWR(GetTranslation() + x270_dragDelta, dt);\n      }\n\n      EntityList nearList;\n      mgr.BuildColliderList(nearList, *this, GetMotionVolume(dt));\n      EntityList nonRiders;\n      for (TUniqueId id : nearList) {\n        if (!IsRider(id) && !IsSlave(id)) {\n          nonRiders.push_back(id);\n        }\n      }\n\n      if (x356_26_detectCollision) {\n        const CMotionState mState = PredictMotion(dt);\n        MoveCollisionPrimitive(mState.x0_translation);\n        const bool collision = CGameCollision::DetectDynamicCollisionBoolean(*GetCollisionPrimitive(),\n                                                                             GetPrimitiveTransform(), nonRiders, mgr);\n        MoveCollisionPrimitive(zeus::skZero3f);\n        if (collision || x356_27_squishedRider) {\n          if (x356_26_detectCollision) {\n            if (x264_collisionRecoverDelay <= 0.f && !x356_27_squishedRider) {\n              x264_collisionRecoverDelay = 0.035f;\n              break;\n            } else {\n              x356_27_squishedRider = false;\n              const TUniqueId prevWaypoint = nextWaypoint;\n              nextWaypoint = GetNext(nextWaypoint, mgr);\n              if (x25a_targetWaypoint == nextWaypoint || x25a_targetWaypoint == prevWaypoint) {\n                x260_moveDelay = 0.035f;\n                break;\n              }\n            }\n          } else {\n            break;\n          }\n        } else {\n          AddMotionState(mState);\n          break;\n        }\n      } else {\n        xf8_24_movable = true;\n        CGameCollision::Move(mgr, *this, dt, &nonRiders);\n        xf8_24_movable = false;\n        break;\n      }\n    } else {\n      nextWaypoint = kInvalidUniqueId;\n      break;\n    }\n  }\n\n  x25a_targetWaypoint = nextWaypoint;\n  return zeus::CQuaternion();\n}\n\nvoid CScriptPlatform::DebugDraw() {\n  // if (!m_boxFilter) {\n  //   m_boxFilter = {CAABoxShader()};\n  // }\n  //\n  // m_boxFilter->setAABB(*GetTouchBounds());\n  // m_boxFilter->draw({1.f, 0.f, 1.f, .5f});\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptPlatform.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <optional>\n#include <string_view>\n#include <vector>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Collision/CCollidableOBBTreeGroup.hpp\"\n#include \"Runtime/World/CDamageVulnerability.hpp\"\n#include \"Runtime/World/CHealthInfo.hpp\"\n#include \"Runtime/World/CPhysicsActor.hpp\"\n\n#include <zeus/CQuaternion.hpp>\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CFluidPlane;\n\nstruct SRiders {\n  TUniqueId x0_uid;\n  float x4_decayTimer;\n  zeus::CTransform x8_transform;\n\n  SRiders(TUniqueId id, float decayTimer, const zeus::CTransform& xf)\n  : x0_uid(id), x4_decayTimer(decayTimer), x8_transform(xf) {}\n};\n\nclass CScriptPlatform : public CPhysicsActor {\n  // u32 x254_;\n  TUniqueId x258_currentWaypoint = kInvalidUniqueId;\n  TUniqueId x25a_targetWaypoint = kInvalidUniqueId;\n  float x25c_currentSpeed;\n  float x260_moveDelay = 0.f;\n  float x264_collisionRecoverDelay = 0.f;\n  float x268_fadeInTime = 0.f;\n  float x26c_fadeOutTime = 0.f;\n  zeus::CVector3f x270_dragDelta;\n  zeus::CQuaternion x27c_rotDelta;\n  CHealthInfo x28c_initialHealth;\n  CHealthInfo x294_health;\n  CDamageVulnerability x29c_damageVuln;\n  std::optional<TLockedToken<CCollidableOBBTreeGroupContainer>> x304_treeGroupContainer;\n  std::unique_ptr<CCollidableOBBTreeGroup> x314_treeGroup;\n  std::vector<SRiders> x318_riders;\n  std::vector<SRiders> x328_slavesStatic;\n  std::vector<SRiders> x338_slavesDynamic;\n  float x348_xrayAlpha;\n  u32 x34c_maxRainSplashes;\n  u32 x350_rainGenRate;\n  TUniqueId x354_boundsTrigger = kInvalidUniqueId;\n  bool x356_24_dead : 1 = false;\n  bool x356_25_controlledAnimation : 1 = false;\n  bool x356_26_detectCollision : 1;\n  bool x356_27_squishedRider : 1 = false;\n  bool x356_28_rainSplashes : 1;\n  bool x356_29_setXrayDrawFlags : 1 = false;\n  bool x356_30_disableXrayAlpha : 1 = false;\n  bool x356_31_xrayFog : 1 = true;\n\n  void DragSlave(CStateManager& mgr, rstl::reserved_vector<u16, kMaxEntities>& draggedSet, CActor* actor,\n                 const zeus::CVector3f& delta);\n  void DragSlaves(CStateManager& mgr, rstl::reserved_vector<u16, kMaxEntities>& draggedSet,\n                  const zeus::CVector3f& delta);\n  static void DecayRiders(std::vector<SRiders>& riders, float dt, CStateManager& mgr);\n  static void MoveRiders(CStateManager& mgr, float dt, bool active, std::vector<SRiders>& riders,\n                         std::vector<SRiders>& collidedRiders, const zeus::CTransform& oldXf,\n                         const zeus::CTransform& newXf, const zeus::CVector3f& dragDelta,\n                         const zeus::CQuaternion& rotDelta);\n  static EntityList BuildNearListFromRiders(CStateManager& mgr, const std::vector<SRiders>& movedRiders);\n\npublic:\n  DEFINE_ENTITY\n  CScriptPlatform(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                  CModelData&& mData, const CActorParameters& actParms, const zeus::CAABox& aabb, float speed,\n                  bool detectCollision, float xrayAlpha, bool active, const CHealthInfo& hInfo,\n                  const CDamageVulnerability& dVuln, std::optional<TLockedToken<CCollidableOBBTreeGroupContainer>> dcln,\n                  bool rainSplashes, u32 maxRainSplashes, u32 rainGenRate);\n\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void PreThink(float, CStateManager&) override;\n  void Think(float, CStateManager&) override;\n  void PreRender(CStateManager&, const zeus::CFrustum&) override;\n  void Render(CStateManager&) override;\n  std::optional<zeus::CAABox> GetTouchBounds() const override;\n  zeus::CTransform GetPrimitiveTransform() const override;\n  const CCollisionPrimitive* GetCollisionPrimitive() const override;\n  zeus::CVector3f GetOrbitPosition(const CStateManager& mgr) const override;\n  zeus::CVector3f GetAimPosition(const CStateManager& mgr, float dt) const override;\n  zeus::CAABox GetSortingBounds(const CStateManager& mgr) const override;\n  bool IsRider(TUniqueId id) const;\n  bool IsSlave(TUniqueId id) const;\n  std::vector<SRiders>& GetStaticSlaves() { return x328_slavesStatic; }\n  const std::vector<SRiders>& GetStaticSlaves() const { return x328_slavesStatic; }\n  std::vector<SRiders>& GetDynamicSlaves() { return x338_slavesDynamic; }\n  const std::vector<SRiders>& GetDynamicSlaves() const { return x338_slavesDynamic; }\n  bool HasComplexCollision() const { return x314_treeGroup.operator bool(); }\n  void BuildSlaveList(CStateManager&);\n  static void AddRider(std::vector<SRiders>& riders, TUniqueId riderId, const CPhysicsActor* ridee, CStateManager& mgr);\n  void AddSlave(TUniqueId, CStateManager&);\n  TUniqueId GetNext(TUniqueId, CStateManager&);\n  TUniqueId GetWaypoint(CStateManager&);\n\n  const CDamageVulnerability* GetDamageVulnerability() const override { return &x29c_damageVuln; }\n  void SetDamageVulnerability(const CDamageVulnerability& vuln) { x29c_damageVuln = vuln; }\n  CHealthInfo* HealthInfo(CStateManager&) override { return &x294_health; }\n  void SetControlledAnimation(bool controlled) { x356_25_controlledAnimation = controlled; }\n  void SetDisableXRayAlpha(bool val) { x356_30_disableXrayAlpha = val; }\n  void SetXRayFog(bool val) { x356_31_xrayFog = val; }\n\n  virtual void SplashThink(const zeus::CAABox&, const CFluidPlane&, float, CStateManager&) const;\n  virtual zeus::CQuaternion Move(float, CStateManager&);\n\n  void DebugDraw();\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptPlayerActor.cpp",
    "content": "#include \"Runtime/World/CScriptPlayerActor.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Camera/CGameCamera.hpp\"\n#include \"Runtime/Character/CAssetFactory.hpp\"\n#include \"Runtime/Character/CCharacterFactory.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/MP1/MP1.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/World/CLightParameters.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nCScriptPlayerActor::CScriptPlayerActor(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                                       const zeus::CTransform& xf, const CAnimRes& animRes, CModelData&& mData,\n                                       const zeus::CAABox& aabox, bool setBoundingBox, const CMaterialList& list,\n                                       float mass, float zMomentum, const CHealthInfo& hInfo,\n                                       const CDamageVulnerability& dVuln, const CActorParameters& aParams, bool loop,\n                                       bool active, u32 flags, CPlayerState::EBeamId beam)\n: CScriptActor(uid, name, info, xf, std::move(mData), aabox, mass, zMomentum, list, hInfo, dVuln, aParams, loop, active,\n               0, 1.f, false, false, false, false)\n, x2e8_suitRes(animRes)\n, x304_beam(beam)\n, x350_flags(flags)\n, x354_24_setBoundingBox(setBoundingBox) {\n  CMaterialList exclude = GetMaterialFilter().GetExcludeList();\n  CMaterialList include = GetMaterialFilter().GetIncludeList();\n  include.Add(EMaterialTypes::Player);\n  SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(include, exclude));\n\n  SetActorLights(aParams.GetLightParameters().MakeActorLights());\n  xe7_29_drawEnabled = true;\n  x2e3_24_isPlayerActor = true;\n\n  _CreateReflectionCube();\n}\n\nu32 CScriptPlayerActor::GetSuitCharIdx(const CStateManager& mgr, CPlayerState::EPlayerSuit suit) const {\n  if (mgr.GetPlayerState()->IsFusionEnabled()) {\n    switch (suit) {\n    case CPlayerState::EPlayerSuit::Power:\n      return 4;\n    case CPlayerState::EPlayerSuit::Varia:\n      return 7;\n    case CPlayerState::EPlayerSuit::Gravity:\n      return 6;\n    case CPlayerState::EPlayerSuit::Phazon:\n      return 8;\n    default:\n      break;\n    }\n  }\n  return u32(suit);\n}\n\nu32 CScriptPlayerActor::GetNextSuitCharIdx(const CStateManager& mgr) const {\n  CPlayerState::EPlayerSuit nextSuit = CPlayerState::EPlayerSuit::Phazon;\n\n  if (x350_flags & 0x2) {\n    switch (x308_suit) {\n    case CPlayerState::EPlayerSuit::Gravity:\n      nextSuit = CPlayerState::EPlayerSuit::Varia;\n      break;\n    case CPlayerState::EPlayerSuit::Phazon:\n      nextSuit = CPlayerState::EPlayerSuit::Gravity;\n      break;\n    case CPlayerState::EPlayerSuit::Varia:\n    default:\n      nextSuit = CPlayerState::EPlayerSuit::Power;\n      break;\n    }\n  } else {\n    switch (x308_suit) {\n    case CPlayerState::EPlayerSuit::Power:\n      nextSuit = CPlayerState::EPlayerSuit::Varia;\n      break;\n    case CPlayerState::EPlayerSuit::Varia:\n      nextSuit = CPlayerState::EPlayerSuit::Gravity;\n      break;\n    case CPlayerState::EPlayerSuit::Gravity:\n      nextSuit = CPlayerState::EPlayerSuit::Phazon;\n      break;\n    default:\n      break;\n    }\n  }\n\n  return GetSuitCharIdx(mgr, nextSuit);\n}\n\nvoid CScriptPlayerActor::LoadSuit(u32 charIdx) {\n  if (charIdx == x310_loadedCharIdx) {\n    return;\n  }\n\n  const TToken<CCharacterFactory> fac = g_CharFactoryBuilder->GetFactory(x2e8_suitRes);\n  const CCharacterInfo& chInfo = fac->GetCharInfo(charIdx);\n  x320_suitModel = g_SimplePool->GetObj({FOURCC('CMDL'), chInfo.GetModelId()});\n  x324_suitSkin = g_SimplePool->GetObj({FOURCC('CSKR'), chInfo.GetSkinRulesId()});\n  x354_28_suitModelLoading = true;\n  x310_loadedCharIdx = charIdx;\n}\n\nvoid CScriptPlayerActor::LoadBeam(CPlayerState::EBeamId beam) {\n  if (beam == x30c_setBeamId) {\n    return;\n  }\n\n  x31c_beamModel = g_SimplePool->GetObj({FOURCC('CMDL'), g_tweakPlayerRes->GetBeamCineModel(beam)});\n  x354_27_beamModelLoading = true;\n  x30c_setBeamId = beam;\n}\n\nvoid CScriptPlayerActor::PumpBeamModel(CStateManager& mgr) {\n  if (!x31c_beamModel || !x31c_beamModel.IsLoaded()) {\n    return;\n  }\n\n  BuildBeamModelData();\n  x314_beamModelData->Touch(mgr, 0);\n  mgr.GetWorld()->CycleLoadPauseState();\n  x31c_beamModel = TLockedToken<CModel>();\n  x354_27_beamModelLoading = false;\n}\n\nvoid CScriptPlayerActor::BuildBeamModelData() {\n  x314_beamModelData = std::make_unique<CModelData>(\n      CStaticRes(g_tweakPlayerRes->GetBeamCineModel(x30c_setBeamId), x2e8_suitRes.GetScale()));\n}\n\nvoid CScriptPlayerActor::PumpSuitModel(CStateManager& mgr) {\n  if (!x320_suitModel || !x320_suitModel.IsLoaded() || !x324_suitSkin || !x324_suitSkin.IsLoaded()) {\n    return;\n  }\n\n  x320_suitModel->Touch(0);\n  mgr.GetWorld()->CycleLoadPauseState();\n\n  bool didSetup = false;\n  if (x354_26_deferOfflineModelData) {\n    didSetup = true;\n    x354_26_deferOfflineModelData = false;\n    SetupOfflineModelData();\n  } else if (x354_25_deferOnlineModelData) {\n    didSetup = true;\n    x354_25_deferOnlineModelData = false;\n    SetupOnlineModelData();\n  }\n\n  if (didSetup) {\n    x354_28_suitModelLoading = false;\n    x320_suitModel = TLockedToken<CModel>();\n    x324_suitSkin = TLockedToken<CSkinRules>();\n  }\n}\n\nvoid CScriptPlayerActor::SetupOfflineModelData() {\n  x2e8_suitRes.SetCharacterNodeId(x310_loadedCharIdx);\n  x318_suitModelData = std::make_unique<CModelData>(x2e8_suitRes);\n  if (!static_cast<MP1::CMain&>(*g_Main).GetScreenFading()) {\n    x328_backupModelData = x64_modelData->GetAnimationData()->GetModelData();\n    x348_deallocateBackupCountdown = 2;\n  }\n  x64_modelData->GetAnimationData()->SubstituteModelData(x318_suitModelData->GetAnimationData()->GetModelData());\n}\n\nvoid CScriptPlayerActor::SetupOnlineModelData() {\n  if (x310_loadedCharIdx == x2e8_suitRes.GetCharacterNodeId() && x64_modelData && x64_modelData->HasAnimData()) {\n    return;\n  }\n\n  x2e8_suitRes.SetCharacterNodeId(x310_loadedCharIdx);\n  SetModelData(std::make_unique<CModelData>(x2e8_suitRes));\n  const CAnimPlaybackParms parms(x2e8_suitRes.GetDefaultAnim(), -1, 1.f, true);\n  x64_modelData->GetAnimationData()->SetAnimation(parms, false);\n  if (x354_24_setBoundingBox) {\n    SetBoundingBox(x64_modelData->GetBounds(GetTransform().getRotation()));\n  }\n}\n\nvoid CScriptPlayerActor::Think(float dt, CStateManager& mgr) {\n  auto& pState = *mgr.GetPlayerState();\n\n  if (x354_31_deferOnlineLoad) {\n    x354_25_deferOnlineModelData = true;\n    x354_31_deferOnlineLoad = false;\n    x308_suit = pState.GetCurrentSuitRaw();\n    LoadSuit(GetSuitCharIdx(mgr, x308_suit));\n  }\n\n  if (x354_30_enableLoading) {\n    if ((x350_flags & 0x1) == 0) {\n      const u32 tmpIdx = GetSuitCharIdx(mgr, pState.GetCurrentSuitRaw());\n      if (tmpIdx != x310_loadedCharIdx) {\n        SetModelData(std::make_unique<CModelData>(CModelData::CModelDataNull()));\n        LoadSuit(tmpIdx);\n        x354_25_deferOnlineModelData = true;\n      }\n    }\n\n    LoadBeam(x304_beam != CPlayerState::EBeamId::Invalid ? x304_beam : pState.GetCurrentBeam());\n\n    if (x354_27_beamModelLoading) {\n      PumpBeamModel(mgr);\n    }\n\n    if (x354_28_suitModelLoading) {\n      PumpSuitModel(mgr);\n    }\n\n    if (!x354_29_loading) {\n      if (x354_28_suitModelLoading || x354_27_beamModelLoading || !x64_modelData || x64_modelData->IsNull() ||\n          !x64_modelData->IsLoaded(0)) {\n        x354_29_loading = true;\n      }\n    }\n\n    if (x354_29_loading && !x354_28_suitModelLoading && !x354_27_beamModelLoading && x64_modelData &&\n        !x64_modelData->IsNull() && x64_modelData->IsLoaded(0)) {\n      if (x355_24_areaTrackingLoad) {\n        CGameArea* area = mgr.GetWorld()->GetArea(x4_areaId);\n        --area->GetPostConstructed()->x113c_playerActorsLoading;\n        x355_24_areaTrackingLoad = false;\n      }\n      x354_29_loading = false;\n      SendScriptMsgs(EScriptObjectState::Arrived, mgr, EScriptObjectMessage::None);\n    }\n  }\n\n  if (x2e8_suitRes.GetCharacterNodeId() == 3) {\n    if (!x338_phazonIndirectTexture) {\n      x338_phazonIndirectTexture = g_SimplePool->GetObj(\"PhazonIndirectTexture\");\n    }\n  } else {\n    if (x338_phazonIndirectTexture) {\n      x338_phazonIndirectTexture = TLockedToken<CTexture>();\n    }\n  }\n\n  if (x338_phazonIndirectTexture) {\n    x34c_phazonOffsetAngle += 0.03f;\n    x34c_phazonOffsetAngle = zeus::CRelAngle(x34c_phazonOffsetAngle).asRel();\n  }\n\n  CScriptActor::Think(dt, mgr);\n}\n\nvoid CScriptPlayerActor::SetupEnvFx(CStateManager& mgr, bool set) {\n  if (set) {\n    if (mgr.GetWorld()->GetNeededEnvFx() == EEnvFxType::Rain) {\n      if (x64_modelData && !x64_modelData->IsNull()) {\n        if (mgr.GetEnvFxManager()->GetRainMagnitude() != 0.f) {\n          mgr.GetActorModelParticles()->AddRainSplashGenerator(*this, mgr, 250, 10, 1.2f);\n        }\n      }\n    }\n  } else {\n    mgr.GetActorModelParticles()->RemoveRainSplashGenerator(*this);\n  }\n}\n\nvoid CScriptPlayerActor::SetIntoStateManager(CStateManager& mgr, bool set) {\n  if (!set && mgr.GetPlayerActorHead() == x8_uid) {\n    mgr.SetPlayerActorHead(x356_nextPlayerActor);\n    x356_nextPlayerActor = kInvalidUniqueId;\n  } else {\n    TUniqueId paId = mgr.GetPlayerActorHead();\n    CScriptPlayerActor* other = nullptr;\n    while (paId != kInvalidUniqueId) {\n      if (paId == x8_uid) {\n        if (!set && other) {\n          other->x356_nextPlayerActor = x356_nextPlayerActor;\n          x356_nextPlayerActor = kInvalidUniqueId;\n        }\n        return;\n      }\n\n      const TCastToPtr<CScriptActor> act = mgr.ObjectById(paId);\n      if (act && act->IsPlayerActor()) {\n        other = static_cast<CScriptPlayerActor*>(act.GetPtr());\n        paId = other->x356_nextPlayerActor;\n      } else {\n        paId = kInvalidUniqueId;\n        x356_nextPlayerActor = kInvalidUniqueId;\n      }\n    }\n\n    if (set) {\n      x356_nextPlayerActor = mgr.GetPlayerActorHead();\n      mgr.SetPlayerActorHead(x8_uid);\n    }\n  }\n}\n\nvoid CScriptPlayerActor::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  switch (msg) {\n  case EScriptObjectMessage::InitializedInArea:\n    x354_31_deferOnlineLoad = true;\n    if (x350_flags & 0x8) {\n      CGameArea* area = mgr.GetWorld()->GetArea(x4_areaId);\n      ++area->GetPostConstructed()->x113c_playerActorsLoading;\n      x355_24_areaTrackingLoad = true;\n    }\n    if (GetActive()) {\n      SetupEnvFx(mgr, true);\n      SetIntoStateManager(mgr, true);\n    }\n    break;\n  case EScriptObjectMessage::Activate:\n    if (!GetActive()) {\n      if ((x350_flags & 0x1) != 0) {\n        LoadSuit(GetNextSuitCharIdx(mgr));\n      }\n      SetIntoStateManager(mgr, true);\n      SetupEnvFx(mgr, true);\n      x354_30_enableLoading = true;\n    }\n    break;\n  case EScriptObjectMessage::Increment:\n    if ((x350_flags & 0x1) != 0) {\n      x354_25_deferOnlineModelData = false;\n      x354_26_deferOfflineModelData = true;\n      mgr.GetPlayer().AsyncLoadSuit(mgr);\n    }\n    break;\n  case EScriptObjectMessage::Deactivate:\n    if (GetActive()) {\n      if ((x350_flags & 0x10) == 0) {\n        SetIntoStateManager(mgr, false);\n      }\n      SetupEnvFx(mgr, false);\n    }\n    if ((x350_flags & 0x4) == 0) {\n      break;\n    }\n    [[fallthrough]];\n  case EScriptObjectMessage::Reset:\n    if (GetActive() || msg == EScriptObjectMessage::Reset) {\n      x30c_setBeamId = CPlayerState::EBeamId::Invalid;\n      x310_loadedCharIdx = -1;\n      x314_beamModelData.reset();\n      x318_suitModelData.reset();\n      x31c_beamModel = TLockedToken<CModel>();\n      x320_suitModel = TLockedToken<CModel>();\n      x324_suitSkin = TLockedToken<CSkinRules>();\n      x328_backupModelData = TLockedToken<CSkinnedModel>();\n      x338_phazonIndirectTexture = TLockedToken<CTexture>();\n      x348_deallocateBackupCountdown = 0;\n      x350_flags &= ~0x1;\n      x354_25_deferOnlineModelData = false;\n      x354_26_deferOfflineModelData = false;\n      x354_27_beamModelLoading = false;\n      x354_28_suitModelLoading = false;\n      x354_30_enableLoading = false;\n      SetModelData(std::make_unique<CModelData>(CModelData::CModelDataNull()));\n      SetActive(false);\n    }\n    break;\n  case EScriptObjectMessage::Deleted:\n    SetIntoStateManager(mgr, false);\n    break;\n  default:\n    break;\n  }\n\n  CScriptActor::AcceptScriptMsg(msg, uid, mgr);\n}\n\nvoid CScriptPlayerActor::SetActive(bool active) {\n  CActor::SetActive(active);\n  xe7_29_drawEnabled = true;\n}\n\nvoid CScriptPlayerActor::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) {\n  if (x328_backupModelData) {\n    if (x348_deallocateBackupCountdown == 0) {\n      x328_backupModelData = TLockedToken<CSkinnedModel>();\n    } else {\n      --x348_deallocateBackupCountdown;\n    }\n  }\n  if (x2e8_suitRes.GetCharacterNodeId() == 3) {\n    g_Renderer->AllocatePhazonSuitMaskTexture();\n  }\n  CScriptActor::PreRender(mgr, frustum);\n}\n\nvoid CScriptPlayerActor::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) {\n  TouchModels_Internal(mgr);\n  if (GetActive()) {\n    CActor::AddToRenderer(frustum, mgr);\n  }\n}\n\nvoid CScriptPlayerActor::Render(CStateManager& mgr) {\n  const bool phazonSuit = x2e8_suitRes.GetCharacterNodeId() == 3;\n  if (phazonSuit) {\n    GXSetDstAlpha(true, 1.f);\n  }\n\n  CPhysicsActor::Render(mgr);\n\n  if (x314_beamModelData && !x314_beamModelData->IsNull() && x64_modelData && !x64_modelData->IsNull()) {\n    const auto modelXf = GetTransform() * x64_modelData->GetScaledLocatorTransform(\"GUN_LCTR\");\n    const CModelFlags flags{5, 0, 3, zeus::CColor{1.f, xb4_drawFlags.x4_color.a()}};\n    x314_beamModelData->Render(mgr, modelXf, x90_actorLights.get(), flags);\n  }\n\n  if (phazonSuit) {\n    // TODO\n    // CCubeRenderer::CopyTex\n    zeus::CVector3f vecFromCam =\n        GetBoundingBox().center() - mgr.GetCameraManager()->GetCurrentCamera(mgr)->GetTranslation();\n    const float radius = zeus::clamp(0.25f, (6.f - vecFromCam.magnitude()) / 6.f, 2.f);\n    const float offsetX = std::sin(x34c_phazonOffsetAngle);\n    const float offsetY = std::sin(x34c_phazonOffsetAngle) * 0.5f;\n    g_Renderer->DrawPhazonSuitIndirectEffect(zeus::CColor(0.1f, 1.f), x338_phazonIndirectTexture, zeus::skWhite, radius,\n                                             0.05f, offsetX, offsetY);\n  }\n}\n\nvoid CScriptPlayerActor::TouchModels_Internal(const CStateManager& mgr) const {\n  if (x64_modelData && !x64_modelData->IsNull()) {\n    x64_modelData->Touch(mgr, 0);\n  }\n\n  if (x318_suitModelData && !x318_suitModelData->IsNull()) {\n    x318_suitModelData->Touch(mgr, 0);\n  }\n\n  if (!x354_27_beamModelLoading) {\n    if (x314_beamModelData && !x314_beamModelData->IsNull()) {\n      x314_beamModelData->Touch(mgr, 0);\n    }\n  }\n}\n\nvoid CScriptPlayerActor::TouchModels(const CStateManager& mgr) const {\n  TouchModels_Internal(mgr);\n  TUniqueId paId = x356_nextPlayerActor;\n  while (paId != kInvalidUniqueId) {\n    const TCastToConstPtr<CScriptActor> act = mgr.GetObjectById(paId);\n    if (act && act->IsPlayerActor()) {\n      const auto* pa = static_cast<const CScriptPlayerActor*>(act.GetPtr());\n      pa->TouchModels_Internal(mgr);\n      paId = pa->x356_nextPlayerActor;\n    } else {\n      paId = kInvalidUniqueId;\n    }\n  }\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptPlayerActor.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <string_view>\n\n#include \"Runtime/CPlayerState.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/World/CScriptActor.hpp\"\n\nnamespace metaforce {\nclass CScriptPlayerActor : public CScriptActor {\n  CAnimRes x2e8_suitRes;\n  CPlayerState::EBeamId x304_beam;\n  CPlayerState::EPlayerSuit x308_suit = CPlayerState::EPlayerSuit::Invalid;\n  CPlayerState::EBeamId x30c_setBeamId = CPlayerState::EBeamId::Invalid;\n  s32 x310_loadedCharIdx = -1;\n  std::unique_ptr<CModelData> x314_beamModelData;\n  std::unique_ptr<CModelData> x318_suitModelData;\n  TLockedToken<CModel> x31c_beamModel;               // Used to be single_ptr\n  TLockedToken<CModel> x320_suitModel;               // Used to be single_ptr\n  TLockedToken<CSkinRules> x324_suitSkin;            // Used to be single_ptr\n  TLockedToken<CSkinnedModel> x328_backupModelData;  // Used to be optional\n  TLockedToken<CTexture> x338_phazonIndirectTexture; // Used to be optional\n  u32 x348_deallocateBackupCountdown = 0;\n  float x34c_phazonOffsetAngle = 0.f;\n  u32 x350_flags; /* 0x1: suit transition, 0x2: previous suit, 0x4: force reset\n                   * 0x8: track in area data, 0x10: keep in state manager */\n  bool x354_24_setBoundingBox : 1;\n  bool x354_25_deferOnlineModelData : 1 = false;\n  bool x354_26_deferOfflineModelData : 1 = false;\n  bool x354_27_beamModelLoading : 1 = false;\n  bool x354_28_suitModelLoading : 1 = false;\n  bool x354_29_loading : 1 = true;\n  bool x354_30_enableLoading : 1 = true;\n  bool x354_31_deferOnlineLoad : 1 = false;\n  bool x355_24_areaTrackingLoad : 1 = false;\n  TUniqueId x356_nextPlayerActor = kInvalidUniqueId;\n\n  u32 GetSuitCharIdx(const CStateManager& mgr, CPlayerState::EPlayerSuit suit) const;\n  u32 GetNextSuitCharIdx(const CStateManager& mgr) const;\n  void LoadSuit(u32 charIdx);\n  void LoadBeam(CPlayerState::EBeamId beam);\n  void PumpBeamModel(CStateManager& mgr);\n  void BuildBeamModelData();\n  void PumpSuitModel(CStateManager& mgr);\n  void SetupOfflineModelData();\n  void SetupOnlineModelData();\n\n  void SetupEnvFx(CStateManager& mgr, bool set);\n  void SetIntoStateManager(CStateManager& mgr, bool set);\n\n  void TouchModels_Internal(const CStateManager& mgr) const;\n\npublic:\n  DEFINE_ENTITY\n  CScriptPlayerActor(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                     const CAnimRes& animRes, CModelData&& mData, const zeus::CAABox& aabox, bool setBoundingBox,\n                     const CMaterialList& list, float mass, float zMomentum, const CHealthInfo& hInfo,\n                     const CDamageVulnerability& dVuln, const CActorParameters& aParams, bool loop, bool active,\n                     u32 flags, CPlayerState::EBeamId beam);\n\n  void Think(float, CStateManager&) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void SetActive(bool active) override;\n  void PreRender(CStateManager&, const zeus::CFrustum&) override;\n  void AddToRenderer(const zeus::CFrustum&, CStateManager&) override;\n  void Render(CStateManager& mgr) override;\n  void TouchModels(const CStateManager& mgr) const;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptPlayerHint.cpp",
    "content": "#include \"Runtime/World/CScriptPlayerHint.hpp\"\n\n#include <algorithm>\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/MP1/World/CMetroidPrimeRelay.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCScriptPlayerHint::CScriptPlayerHint(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                                     const zeus::CTransform& xf, bool active, u32 priority, u32 overrideFlags)\n: CActor(uid, active, name, info, xf, CModelData::CModelDataNull(), {EMaterialTypes::NoStepLogic},\n         CActorParameters::None(), kInvalidUniqueId)\n, x100_priority(priority)\n, x104_overrideFlags(overrideFlags) {}\n\nvoid CScriptPlayerHint::Accept(IVisitor& visit) { visit.Visit(this); }\n\nvoid CScriptPlayerHint::AddToObjectList(TUniqueId uid) {\n  const bool inList =\n      std::any_of(xe8_objectList.cbegin(), xe8_objectList.cend(), [uid](const auto id) { return id == uid; });\n  if (inList) {\n    return;\n  }\n\n  if (xe8_objectList.size() == xe8_objectList.capacity()) {\n    return;\n  }\n\n  xe8_objectList.push_back(uid);\n}\n\nvoid CScriptPlayerHint::RemoveFromObjectList(TUniqueId uid) {\n  const auto iter =\n      std::find_if(xe8_objectList.cbegin(), xe8_objectList.cend(), [uid](const auto id) { return id == uid; });\n\n  if (iter == xe8_objectList.cend()) {\n    return;\n  }\n\n  xe8_objectList.erase(iter);\n}\n\nvoid CScriptPlayerHint::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) {\n  switch (msg) {\n  case EScriptObjectMessage::Deactivate:\n  case EScriptObjectMessage::Deleted:\n    RemoveFromObjectList(sender);\n    mgr.GetPlayer().AddToPlayerHintRemoveList(GetUniqueId(), mgr);\n    xfc_deactivated = true;\n    break;\n  case EScriptObjectMessage::Increment:\n    x108_mpId = kInvalidUniqueId;\n    if ((x104_overrideFlags & 0x4000) != 0) {\n      for (const SConnection& conn : x20_conns) {\n        if (conn.x0_state != EScriptObjectState::Play) {\n          continue;\n        }\n        x108_mpId = mgr.GetIdForScript(conn.x8_objId);\n        if (TCastToConstPtr<MP1::CMetroidPrimeRelay> mpRelay = mgr.GetObjectById(x108_mpId)) {\n          x108_mpId = mpRelay->GetMetroidPrimeExoId();\n          break;\n        }\n      }\n    }\n    break;\n  default:\n    break;\n  }\n\n  if (x30_24_active) {\n    switch (msg) {\n    case EScriptObjectMessage::Increment:\n      AddToObjectList(sender);\n      mgr.GetPlayer().AddToPlayerHintAddList(GetUniqueId(), mgr);\n      xfc_deactivated = false;\n      break;\n    case EScriptObjectMessage::Decrement:\n      RemoveFromObjectList(sender);\n      mgr.GetPlayer().AddToPlayerHintRemoveList(GetUniqueId(), mgr);\n      break;\n    default:\n      break;\n    }\n  }\n\n  CActor::AcceptScriptMsg(msg, sender, mgr);\n}\n\n} // namespace metaforce"
  },
  {
    "path": "Runtime/World/CScriptPlayerHint.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/World/CActor.hpp\"\n\nnamespace metaforce {\n\nclass CScriptPlayerHint : public CActor {\n  rstl::reserved_vector<TUniqueId, 8> xe8_objectList;\n  bool xfc_deactivated = false;\n  u32 x100_priority;\n  u32 x104_overrideFlags;\n  TUniqueId x108_mpId = kInvalidUniqueId;\n  void AddToObjectList(TUniqueId uid);\n  void RemoveFromObjectList(TUniqueId uid);\n\npublic:\n  DEFINE_ENTITY\n  CScriptPlayerHint(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                    bool active, u32 priority, u32 overrideFlags);\n  void Accept(IVisitor& visit) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  u32 GetPriority() const { return x100_priority; }\n  u32 GetOverrideFlags() const { return x104_overrideFlags; }\n  TUniqueId GetActorId() const { return x108_mpId; }\n  void ClearObjectList() { xe8_objectList.clear(); }\n  void SetDeactivated() { xfc_deactivated = true; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptPlayerStateChange.cpp",
    "content": "#include \"Runtime/World/CScriptPlayerStateChange.hpp\"\n\n#include \"Runtime/CPlayerState.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Input/ControlMapper.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nCScriptPlayerStateChange::CScriptPlayerStateChange(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                                                   bool active, u32 itemType, u32 itemCount, u32 itemCapacity,\n                                                   EControl control, EControlCommandOption controlCmdOpt)\n: CEntity(uid, info, active, name)\n, x34_itemType(itemType)\n, x38_itemCount(itemCount)\n, x3c_itemCapacity(itemCapacity)\n, x40_ctrl(control)\n, x44_ctrlCmdOpt(controlCmdOpt) {}\n\nvoid CScriptPlayerStateChange::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptPlayerStateChange::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) {\n  if (GetActive() && msg == EScriptObjectMessage::SetToZero) {\n    stateMgr.GetPlayerState()->AddPowerUp(CPlayerState::EItemType(x34_itemType), x3c_itemCapacity);\n    stateMgr.GetPlayerState()->IncrPickup(CPlayerState::EItemType(x34_itemType), x38_itemCount);\n\n    if (x44_ctrlCmdOpt == EControlCommandOption::Filtered && x40_ctrl == EControl::Filtered) {\n      bool filtered = x44_ctrlCmdOpt != EControlCommandOption::Unfiltered;\n      ControlMapper::SetCommandFiltered(ControlMapper::ECommands::OrbitClose, filtered);\n      ControlMapper::SetCommandFiltered(ControlMapper::ECommands::OrbitConfirm, filtered);\n      ControlMapper::SetCommandFiltered(ControlMapper::ECommands::OrbitDown, filtered);\n      ControlMapper::SetCommandFiltered(ControlMapper::ECommands::OrbitFar, filtered);\n      ControlMapper::SetCommandFiltered(ControlMapper::ECommands::OrbitLeft, filtered);\n      ControlMapper::SetCommandFiltered(ControlMapper::ECommands::OrbitObject, filtered);\n      ControlMapper::SetCommandFiltered(ControlMapper::ECommands::OrbitRight, filtered);\n      ControlMapper::SetCommandFiltered(ControlMapper::ECommands::OrbitSelect, filtered);\n      ControlMapper::SetCommandFiltered(ControlMapper::ECommands::OrbitUp, filtered);\n    }\n  }\n\n  CEntity::AcceptScriptMsg(msg, objId, stateMgr);\n}\n} // namespace metaforce"
  },
  {
    "path": "Runtime/World/CScriptPlayerStateChange.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/World/CEntity.hpp\"\n\nnamespace metaforce {\nclass CScriptPlayerStateChange : public CEntity {\npublic:\n  enum class EControl { Unfiltered, Filtered };\n  enum class EControlCommandOption { Unfiltered, Filtered };\n\nprivate:\n  u32 x34_itemType;\n  u32 x38_itemCount;\n  u32 x3c_itemCapacity;\n  EControl x40_ctrl;\n  EControlCommandOption x44_ctrlCmdOpt;\n\npublic:\n  DEFINE_ENTITY\n  CScriptPlayerStateChange(TUniqueId, std::string_view, const CEntityInfo&, bool, u32, u32, u32, EControl,\n                           EControlCommandOption);\n  void Accept(IVisitor& visit) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptPointOfInterest.cpp",
    "content": "#include \"Runtime/World/CScriptPointOfInterest.hpp\"\n\n#include \"Runtime/CPlayerState.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCScriptPointOfInterest::CScriptPointOfInterest(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                                               const zeus::CTransform& xf, bool active,\n                                               const CScannableParameters& parms, float f1)\n: CActor(uid, active, name, info, xf, CModelData::CModelDataNull(), CMaterialList(u64(EMaterialTypes::Orbit)),\n         CActorParameters::None().Scannable(parms), kInvalidUniqueId)\n, xe8_pointSize(f1) {}\n\nvoid CScriptPointOfInterest::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptPointOfInterest::Think(float dt, CStateManager& mgr) {\n  xe7_31_targetable = mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::Scan;\n  CEntity::Think(dt, mgr);\n}\n\nvoid CScriptPointOfInterest::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  CActor::AcceptScriptMsg(msg, uid, mgr);\n}\n\nvoid CScriptPointOfInterest::AddToRenderer(const zeus::CFrustum&, CStateManager&) {}\n\nvoid CScriptPointOfInterest::Render(CStateManager&) {}\n\nvoid CScriptPointOfInterest::CalculateRenderBounds() {\n  if (xe8_pointSize == 0.f) {\n    CActor::CalculateRenderBounds();\n  } else {\n    x9c_renderBounds = zeus::CAABox(x34_transform.origin - xe8_pointSize, x34_transform.origin + xe8_pointSize);\n  }\n}\n\nstd::optional<zeus::CAABox> CScriptPointOfInterest::GetTouchBounds() const {\n  return {zeus::CAABox{x34_transform.origin, x34_transform.origin}};\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptPointOfInterest.hpp",
    "content": "#pragma once\n\n#include <string_view>\n#include \"Runtime/World/CActor.hpp\"\n\nnamespace metaforce {\nclass CScannableParameters;\n\nclass CScriptPointOfInterest : public CActor {\nprivate:\n  float xe8_pointSize;\n\npublic:\n  DEFINE_ENTITY\n  CScriptPointOfInterest(TUniqueId, std::string_view, const CEntityInfo&, const zeus::CTransform&, bool,\n                         const CScannableParameters&, float);\n\n  void Accept(IVisitor& visitor) override;\n  void Think(float, CStateManager&) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void AddToRenderer(const zeus::CFrustum&, CStateManager&) override;\n  void Render(CStateManager&) override;\n  void CalculateRenderBounds() override;\n  std::optional<zeus::CAABox> GetTouchBounds() const override;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptRandomRelay.cpp",
    "content": "#include \"Runtime/World/CScriptRandomRelay.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nCScriptRandomRelay::CScriptRandomRelay(TUniqueId uid, std::string_view name, const CEntityInfo& info, s32 sendSetSize,\n                                       s32 sendSetVariance, bool percentSize, bool active)\n: CEntity(uid, info, active, name)\n, x34_sendSetSize((percentSize && sendSetSize > 100) ? 100 : sendSetSize)\n, x38_sendSetVariance(sendSetVariance)\n, x3c_percentSize(percentSize) {}\n\nvoid CScriptRandomRelay::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptRandomRelay::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) {\n  CEntity::AcceptScriptMsg(msg, objId, stateMgr);\n  if (msg == EScriptObjectMessage::SetToZero) {\n    if (!x30_24_active) {\n      return;\n    }\n    SendLocalScriptMsgs(EScriptObjectState::Zero, stateMgr);\n  }\n}\n\nvoid CScriptRandomRelay::SendLocalScriptMsgs(EScriptObjectState state, CStateManager& stateMgr) {\n  if (state != EScriptObjectState::Zero) {\n    SendScriptMsgs(state, stateMgr, EScriptObjectMessage::None);\n    return;\n  }\n\n  std::vector<std::pair<CEntity*, EScriptObjectMessage>> objs;\n  objs.reserve(10);\n  for (const SConnection& conn : x20_conns) {\n    const auto list = stateMgr.GetIdListForScript(conn.x8_objId);\n    for (auto it = list.first; it != list.second; ++it) {\n      CEntity* ent = stateMgr.ObjectById(it->second);\n      if (ent && ent->GetActive()) {\n        objs.emplace_back(ent, conn.x4_msg);\n      }\n    }\n  }\n\n  s32 targetSetSize = x34_sendSetSize;\n  if (x3c_percentSize) {\n    targetSetSize = s32(0.5f + (float(x34_sendSetSize * objs.size()) / 100.f));\n  }\n  targetSetSize += s32(float(x38_sendSetVariance) * 2.f * stateMgr.GetActiveRandom()->Float()) - x38_sendSetVariance;\n  targetSetSize = zeus::clamp(0, targetSetSize, 64);\n\n  while (objs.size() > targetSetSize) {\n    const s32 randomRemoveIdx = s32(stateMgr.GetActiveRandom()->Float() * float(objs.size()) * 0.99f);\n    auto it = objs.begin();\n    for (s32 i = 0; i < randomRemoveIdx; ++i) {\n      ++it;\n      if (it == objs.end()) {\n        break;\n      }\n    }\n    if (it != objs.end()) {\n      objs.erase(it);\n    }\n  }\n\n  for (const auto& o : objs) {\n    stateMgr.SendScriptMsg(o.first, GetUniqueId(), o.second);\n  }\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptRandomRelay.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/World/CEntity.hpp\"\n\nnamespace metaforce {\nclass CScriptRandomRelay : public CEntity {\n  s32 x34_sendSetSize;\n  s32 x38_sendSetVariance;\n  bool x3c_percentSize;\n\npublic:\n  DEFINE_ENTITY\n  CScriptRandomRelay(TUniqueId uid, std::string_view name, const CEntityInfo& info, s32 sendSetSize,\n                     s32 sendSetVariance, bool percentSize, bool active);\n\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) override;\n  void SendLocalScriptMsgs(EScriptObjectState state, CStateManager& stateMgr);\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptRelay.cpp",
    "content": "#include \"Runtime/World/CScriptRelay.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCScriptRelay::CScriptRelay(TUniqueId uid, std::string_view name, const CEntityInfo& info, bool active)\n: CEntity(uid, info, active, name) {}\n\nvoid CScriptRelay::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptRelay::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) {\n  CEntity::AcceptScriptMsg(msg, objId, stateMgr);\n  if (msg == EScriptObjectMessage::Deleted) {\n    UpdateObjectRef(stateMgr);\n  } else if (msg == EScriptObjectMessage::SetToZero) {\n    if (!x30_24_active) {\n      return;\n    }\n\n    x38_sendCount++;\n    TUniqueId tmp = stateMgr.GetLastRelayId();\n    while (tmp != GetUniqueId() && tmp != kInvalidUniqueId) {\n      const CScriptRelay* obj = static_cast<const CScriptRelay*>(stateMgr.GetObjectById(tmp));\n      if (!obj) {\n        tmp = kInvalidUniqueId;\n        break;\n      }\n\n      tmp = obj->x34_nextRelay;\n    }\n\n    if (tmp == kInvalidUniqueId) {\n      x34_nextRelay = stateMgr.GetLastRelayId();\n      stateMgr.SetLastRelayId(GetUniqueId());\n    }\n  }\n}\n\nvoid CScriptRelay::Think(float, CStateManager& stateMgr) {\n  if (x38_sendCount == 0) {\n    return;\n  }\n\n  while (x38_sendCount != 0) {\n    x38_sendCount--;\n    SendScriptMsgs(EScriptObjectState::Zero, stateMgr, EScriptObjectMessage::None);\n  }\n  UpdateObjectRef(stateMgr);\n}\n\nvoid CScriptRelay::UpdateObjectRef(CStateManager& stateMgr) {\n  TUniqueId* tmp = stateMgr.GetLastRelayIdPtr();\n  while (tmp != nullptr && *tmp != kInvalidUniqueId) {\n    if (*tmp == GetUniqueId()) {\n      *tmp = x34_nextRelay;\n      return;\n    }\n    auto* obj = static_cast<CScriptRelay*>(stateMgr.ObjectById(*tmp));\n    if (obj == nullptr) {\n      return;\n    }\n    tmp = &obj->x34_nextRelay;\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptRelay.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/World/CEntity.hpp\"\n\nnamespace metaforce {\nclass CScriptRelay : public CEntity {\n  TUniqueId x34_nextRelay = kInvalidUniqueId;\n  u32 x38_sendCount = 0;\n\npublic:\n  DEFINE_ENTITY\n  CScriptRelay(TUniqueId, std::string_view, const CEntityInfo&, bool);\n\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) override;\n  void Think(float, CStateManager& stateMgr) override;\n  void UpdateObjectRef(CStateManager& stateMgr);\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptRipple.cpp",
    "content": "#include \"Runtime/World/CScriptRipple.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CScriptWater.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCScriptRipple::CScriptRipple(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CVector3f& vec,\n                             bool active, float f1)\n: CEntity(uid, info, active, name), x34_magnitude(f1 >= 0.f ? f1 : 0.5f), x38_center(vec) {}\n\nvoid CScriptRipple::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptRipple::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  if (msg != EScriptObjectMessage::Play) {\n    CEntity::AcceptScriptMsg(msg, uid, mgr);\n    return;\n  }\n\n  if (!GetActive()) {\n    return;\n  }\n\n  for (const SConnection& conn : x20_conns) {\n    if (conn.x0_state != EScriptObjectState::Active || conn.x4_msg != EScriptObjectMessage::Next) {\n      continue;\n    }\n\n    const auto search = mgr.GetIdListForScript(conn.x8_objId);\n    for (auto it = search.first; it != search.second; ++it) {\n      if (const TCastToPtr<CScriptWater> water = mgr.ObjectById(it->second)) {\n        water->GetFluidPlane().AddRipple(x34_magnitude, GetUniqueId(), x38_center, *water, mgr);\n      }\n    }\n  }\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptRipple.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/World/CEntity.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CScriptRipple : public CEntity {\n  float x34_magnitude;\n  zeus::CVector3f x38_center;\n\npublic:\n  DEFINE_ENTITY\n  CScriptRipple(TUniqueId, std::string_view, const CEntityInfo&, const zeus::CVector3f&, bool, float);\n\n  void Accept(IVisitor&) override;\n  void Think(float, CStateManager&) override {}\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptRoomAcoustics.cpp",
    "content": "#include \"Runtime/World/CScriptRoomAcoustics.hpp\"\n\n#include \"Runtime/Audio/CSfxManager.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nstatic TAreaId s_ActiveAcousticsAreaId = kInvalidAreaId;\n\nCScriptRoomAcoustics::CScriptRoomAcoustics(TUniqueId uid, std::string_view name, const CEntityInfo& info, bool active,\n                                           u32 volScale, bool revHi, bool revHiDis, float revHiColoration,\n                                           float revHiMix, float revHiTime, float revHiDamping, float revHiPreDelay,\n                                           float revHiCrosstalk, bool chorus, float baseDelay, float variation,\n                                           float period, bool revStd, bool revStdDis, float revStdColoration,\n                                           float revStdMix, float revStdTime, float revStdDamping, float revStdPreDelay,\n                                           bool delay, u32 delayL, u32 delayR, u32 delayS, u32 feedbackL, u32 feedbackR,\n                                           u32 feedbackS, u32 outputL, u32 outputR, u32 outputS)\n: CEntity(uid, info, active, name)\n, x34_volumeScale(volScale)\n//, x38_revHi(revHi)\n//, x39_revHiDis(revHiDis)\n//, x3c_revHiInfo(revHiColoration, revHiMix, revHiTime, revHiDamping, revHiPreDelay, revHiCrosstalk)\n//, x54_chorus(chorus)\n//, x58_chorusInfo(baseDelay, variation, period)\n//, x64_revStd(revStd)\n//, x65_revStdDis(revStdDis)\n//, x68_revStdInfo(revStdColoration, revStdMix, revStdTime, revStdDamping, revStdPreDelay)\n//, x7c_delay(delay)\n//, x80_delayInfo(delayL, delayR, delayS, feedbackL, feedbackR, feedbackS, outputL, outputR, outputS)\n     {}\n\nvoid CScriptRoomAcoustics::DisableAuxCallbacks() {\n  CSfxManager::DisableAuxProcessing();\n  s_ActiveAcousticsAreaId = kInvalidAreaId;\n  CAudioSys::SetVolumeScale(CAudioSys::GetDefaultVolumeScale());\n}\n\nvoid CScriptRoomAcoustics::EnableAuxCallbacks() {\n  if (!x30_24_active) {\n    return;\n  }\n\n  bool applied = true;\n//  if (x38_revHi) {\n//    CSfxManager::PrepareReverbHiCallback(x3c_revHiInfo);\n//  } else if (x54_chorus) {\n//    CSfxManager::PrepareChorusCallback(x58_chorusInfo);\n//  } else if (x64_revStd) {\n//    CSfxManager::PrepareReverbStdCallback(x68_revStdInfo);\n//  } else if (x7c_delay) {\n//    CSfxManager::PrepareDelayCallback(x80_delayInfo);\n//  } else {\n//    applied = false;\n//  }\n\n  if (applied) {\n    CAudioSys::SetVolumeScale(x34_volumeScale);\n  }\n  s_ActiveAcousticsAreaId = x4_areaId;\n}\n\nvoid CScriptRoomAcoustics::Think(float dt, CStateManager& stateMgr) { /* Intentionally empty */\n}\n\nvoid CScriptRoomAcoustics::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) {\n  CEntity::AcceptScriptMsg(msg, objId, stateMgr);\n\n  switch (msg) {\n  case EScriptObjectMessage::Activate:\n    EnableAuxCallbacks();\n    break;\n  case EScriptObjectMessage::Deactivate:\n    if (s_ActiveAcousticsAreaId == x4_areaId) {\n      s_ActiveAcousticsAreaId = kInvalidAreaId;\n      CSfxManager::DisableAuxProcessing();\n      CAudioSys::SetVolumeScale(CAudioSys::GetDefaultVolumeScale());\n    }\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CScriptRoomAcoustics::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptRoomAcoustics.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/World/CEntity.hpp\"\n\n//#include <amuse/EffectChorus.hpp>\n//#include <amuse/EffectDelay.hpp>\n//#include <amuse/EffectReverb.hpp>\n\nnamespace metaforce {\n\nclass CScriptRoomAcoustics : public CEntity {\n  u32 x34_volumeScale;\n\n//  bool x38_revHi, x39_revHiDis;\n//  amuse::EffectReverbHiInfo x3c_revHiInfo;\n//\n//  bool x54_chorus;\n//  amuse::EffectChorusInfo x58_chorusInfo;\n//\n//  bool x64_revStd, x65_revStdDis;\n//  amuse::EffectReverbStdInfo x68_revStdInfo;\n//\n//  bool x7c_delay;\n//  amuse::EffectDelayInfo x80_delayInfo;\n\npublic:\n  DEFINE_ENTITY\n  CScriptRoomAcoustics(TUniqueId uid, std::string_view name, const CEntityInfo& info, bool active, u32 volScale,\n                       bool revHi, bool revHiDis, float revHiColoration, float revHiMix, float revHiTime,\n                       float revHiDamping, float revHiPreDelay, float revHiCrosstalk, bool chorus, float baseDelay,\n                       float variation, float period, bool revStd, bool revStdDis, float revStdColoration,\n                       float revStdMix, float revStdTime, float revStdDamping, float revStdPreDelay, bool delay,\n                       u32 delayL, u32 delayR, u32 delayS, u32 feedbackL, u32 feedbackR, u32 feedbackS, u32 outputL,\n                       u32 outputR, u32 outputS);\n  void Think(float dt, CStateManager& stateMgr) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) override;\n  void Accept(IVisitor& visitor) override;\n  void EnableAuxCallbacks();\n\n  static void DisableAuxCallbacks();\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptShadowProjector.cpp",
    "content": "#include \"Runtime/World/CScriptShadowProjector.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/World/CProjectedShadow.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCScriptShadowProjector::CScriptShadowProjector(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                                               const zeus::CTransform& xf, bool active, const zeus::CVector3f& offset,\n                                               bool persistent, float scale, float f2, float opacity, float opacityQ,\n                                               s32 textureSize)\n: CActor(uid, active, name, info, xf, CModelData::CModelDataNull(), CMaterialList(), CActorParameters::None(),\n         kInvalidUniqueId)\n, xe8_scale(scale)\n, xec_offset(offset)\n, xf8_zOffsetAdjust(f2)\n, xfc_opacity(opacity)\n, x100_opacityRecip(opacity < 0.00001 ? 1.f : opacityQ / opacity)\n, x10c_textureSize(textureSize)\n, x110_24_persistent(persistent) {}\n\nvoid CScriptShadowProjector::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptShadowProjector::Think(float dt, CStateManager& mgr) {\n  if (!GetActive() || !x110_25_shadowInvalidated) {\n    return;\n  }\n\n  xfc_opacity = -(x100_opacityRecip * dt - xfc_opacity);\n  if (xfc_opacity > 0.f) {\n    return;\n  }\n  xfc_opacity = 0.f;\n  x108_projectedShadow.reset();\n\n  x110_25_shadowInvalidated = false;\n  SendScriptMsgs(EScriptObjectState::Zero, mgr, EScriptObjectMessage::None);\n}\n\nvoid CScriptShadowProjector::CreateProjectedShadow() {\n  if (!GetActive() || x104_target == kInvalidUniqueId || xfc_opacity <= 0.f) {\n    x108_projectedShadow.reset();\n  } else {\n    x108_projectedShadow.reset(new CProjectedShadow(x10c_textureSize, x10c_textureSize, x110_24_persistent));\n  }\n}\n\nvoid CScriptShadowProjector::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  CActor::AcceptScriptMsg(msg, uid, mgr);\n\n  switch (msg) {\n  case EScriptObjectMessage::InitializedInArea:\n    for (const SConnection& conn : x20_conns) {\n      if (conn.x0_state != EScriptObjectState::Play) {\n        continue;\n      }\n      const CActor* act = TCastToConstPtr<CActor>(mgr.GetObjectById(mgr.GetIdForScript(conn.x8_objId)));\n      if (act) {\n        const CModelData* mData = act->GetModelData();\n        if (mData && (mData->GetAnimationData() || mData->HasNormalModel())) {\n          x104_target = act->GetUniqueId();\n          break;\n        }\n      }\n    }\n    if (x104_target == kInvalidUniqueId) {\n      mgr.FreeScriptObject(GetUniqueId());\n      break;\n    }\n    [[fallthrough]];\n  case EScriptObjectMessage::Deactivate:\n  case EScriptObjectMessage::Activate:\n    CreateProjectedShadow();\n    break;\n\n  case EScriptObjectMessage::Decrement:\n    if (!GetActive()) {\n      return;\n    }\n\n    if (xfc_opacity > 0.f) {\n      x110_25_shadowInvalidated = true;\n    }\n    break;\n\n  default:\n    break;\n  }\n}\n\nvoid CScriptShadowProjector::PreRender(CStateManager&, const zeus::CFrustum&) {}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptShadowProjector.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <string_view>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/World/CActor.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CProjectedShadow;\n\nclass CScriptShadowProjector : public CActor {\n  float xe8_scale;\n  zeus::CVector3f xec_offset;\n  float xf8_zOffsetAdjust;\n  float xfc_opacity;\n  float x100_opacityRecip;\n  TUniqueId x104_target;\n  std::unique_ptr<CProjectedShadow> x108_projectedShadow;\n  u32 x10c_textureSize;\n  bool x110_24_persistent : 1;\n  bool x110_25_shadowInvalidated : 1 = false;\n\npublic:\n  DEFINE_ENTITY\n  CScriptShadowProjector(TUniqueId, std::string_view, const CEntityInfo&, const zeus::CTransform&, bool,\n                         const zeus::CVector3f&, bool, float, float, float, float, s32);\n\n  void Accept(IVisitor& visitor) override;\n  void Think(float, CStateManager&) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void PreRender(CStateManager&, const zeus::CFrustum&) override;\n  void AddToRenderer(const zeus::CFrustum&, CStateManager&) override {}\n  void CreateProjectedShadow();\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptSound.cpp",
    "content": "#include \"Runtime/World/CScriptSound.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Character/CModelData.hpp\"\n#include \"Runtime/Collision/CMaterialList.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nbool CScriptSound::sFirstInFrame = false;\n\nCScriptSound::CScriptSound(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                           u16 soundId, bool active, float maxDist, float distComp, float startDelay, u32 minVol,\n                           u32 vol, u32 w3, u32 prio, u32 pan, u32 w6, bool looped, bool nonEmitter, bool autoStart,\n                           bool occlusionTest, bool acoustics, bool worldSfx, bool allowDuplicates, s32 pitch)\n: CActor(uid, active, name, info, xf, CModelData::CModelDataNull(), CMaterialList(EMaterialTypes::NoStepLogic),\n         CActorParameters::None(), kInvalidUniqueId)\n, xfc_startDelay(startDelay)\n, x100_soundId(CSfxManager::TranslateSFXID(soundId))\n, x104_maxDist(maxDist)\n, x108_distComp(distComp)\n, x10c_minVol(minVol / 127.f)\n, x10e_vol(vol / 127.f)\n, x110_(w3)\n, x112_prio(s16(prio))\n//, x114_pan(amuse::convertMusyXPanToAmusePan(pan))\n, x116_(w6)\n, x118_pitch(pitch / 8192.f)\n, x11c_25_looped(looped)\n, x11c_26_nonEmitter(nonEmitter)\n, x11c_27_autoStart(autoStart)\n, x11c_28_occlusionTest(occlusionTest)\n, x11c_29_acoustics(acoustics)\n, x11c_30_worldSfx(worldSfx)\n, x11d_24_allowDuplicates(allowDuplicates) {\n  if (x11c_30_worldSfx && (!x11c_26_nonEmitter || !x11c_25_looped)) {\n    x11c_30_worldSfx = false;\n  }\n}\n\nvoid CScriptSound::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptSound::PreThink(float dt, CStateManager& mgr) {\n  CEntity::PreThink(dt, mgr);\n  sFirstInFrame = true;\n  x11d_25_processedThisFrame = false;\n}\n\nconstexpr CMaterialFilter kSolidFilter =\n    CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {EMaterialTypes::ProjectilePassthrough});\n\nfloat CScriptSound::GetOccludedVolumeAmount(const zeus::CVector3f& pos, const CStateManager& mgr) {\n  const zeus::CTransform camXf = mgr.GetCameraManager()->GetCurrentCameraTransform(mgr);\n  const zeus::CVector3f soundToCam = camXf.origin - pos;\n  const float soundToCamMag = soundToCam.magnitude();\n  const zeus::CVector3f soundToCamNorm = soundToCam * (1.f / soundToCamMag);\n  const zeus::CVector3f thirdEdge = zeus::skUp - soundToCamNorm * soundToCamNorm.dot(zeus::skUp);\n  const zeus::CVector3f cross = soundToCamNorm.cross(thirdEdge);\n  static float kInfluenceAmount = 3.f / soundToCamMag;\n  static float kInfluenceIncrement = kInfluenceAmount;\n\n  int totalCount = 0;\n  int invalCount = 0;\n  float f17 = -kInfluenceAmount;\n  while (f17 <= kInfluenceAmount) {\n    const zeus::CVector3f angledDir = thirdEdge * f17 + soundToCamNorm;\n    float f16 = -kInfluenceAmount;\n    while (f16 <= kInfluenceAmount) {\n      if (mgr.RayStaticIntersection(pos, (cross * f16 + angledDir).normalized(), soundToCamMag, kSolidFilter)\n              .IsInvalid()) {\n        ++invalCount;\n      }\n      ++totalCount;\n      f16 += kInfluenceIncrement;\n    }\n    f17 += kInfluenceIncrement;\n  }\n\n  return invalCount / float(totalCount) * 0.42f + 0.58f;\n}\n\nvoid CScriptSound::Think(float dt, CStateManager& mgr) {\n  if (x11c_31_selfFree && (!GetActive() || x11c_25_looped || !x11c_27_autoStart)) {\n    mgr.FreeScriptObject(GetUniqueId());\n  } else if (GetActive()) {\n    if (!x11c_25_looped && x11c_27_autoStart && !x11c_24_playRequested && xec_sfxHandle &&\n        !CSfxManager::IsPlaying(xec_sfxHandle)) {\n      mgr.FreeScriptObject(GetUniqueId());\n    }\n\n    if (!x11c_26_nonEmitter && xec_sfxHandle) {\n      if (xf8_updateTimer <= 0.f) {\n        xf8_updateTimer = 0.25f;\n        CSfxManager::UpdateEmitter(xec_sfxHandle, GetTranslation(), zeus::skZero3f, xf2_maxVolUpd);\n      } else {\n        xf8_updateTimer -= dt;\n      }\n    }\n\n    if (xec_sfxHandle && !x11c_26_nonEmitter && x11c_28_occlusionTest) {\n      if (xe8_occUpdateTimer <= 0.f && sFirstInFrame) {\n        sFirstInFrame = false;\n        const float occVol = GetOccludedVolumeAmount(GetTranslation(), mgr);\n        const float newMaxVol = std::max(occVol * x10e_vol, x10c_minVol);\n        if (newMaxVol != xf0_maxVol) {\n          xf0_maxVol = newMaxVol;\n          const float delta = xf0_maxVol - xf2_maxVolUpd;\n          xf4_maxVolUpdDelta = delta / 10.5f;\n          if (xf4_maxVolUpdDelta == 0.f) {\n            if (xf2_maxVolUpd < xf0_maxVol) {\n              xf4_maxVolUpdDelta = 1.f / 127.f;\n            } else {\n              xf4_maxVolUpdDelta = -1.f / 127.f;\n            }\n          }\n        }\n        xe8_occUpdateTimer = 0.5f;\n      } else {\n        xe8_occUpdateTimer -= dt;\n      }\n\n      if (xf2_maxVolUpd != xf0_maxVol) {\n        xf2_maxVolUpd += xf4_maxVolUpdDelta;\n        if (xf4_maxVolUpdDelta > 0.f && xf2_maxVolUpd > xf0_maxVol) {\n          xf2_maxVolUpd = xf0_maxVol;\n        }\n        if (xf4_maxVolUpdDelta < 0.f && xf2_maxVolUpd < xf0_maxVol) {\n          xf2_maxVolUpd = xf0_maxVol;\n        }\n        CSfxManager::UpdateEmitter(xec_sfxHandle, GetTranslation(), zeus::skZero3f, xf2_maxVolUpd);\n      }\n    }\n\n    if (x11c_24_playRequested) {\n      xfc_startDelay -= dt;\n      if (xfc_startDelay <= 0.f) {\n        x11c_24_playRequested = false;\n        PlaySound(mgr);\n      }\n    }\n\n    if (x118_pitch != 0.f && xec_sfxHandle) {\n      CSfxManager::PitchBend(xec_sfxHandle, x118_pitch);\n    }\n  }\n}\n\nvoid CScriptSound::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  CActor::AcceptScriptMsg(msg, uid, mgr);\n\n  switch (msg) {\n  case EScriptObjectMessage::Registered: {\n    if (GetActive() && x11c_27_autoStart) {\n      x11c_24_playRequested = true;\n    }\n    x11c_31_selfFree = mgr.GetIsGeneratingObject();\n  } break;\n  case EScriptObjectMessage::Play: {\n    if (GetActive()) {\n      PlaySound(mgr);\n    }\n  } break;\n  case EScriptObjectMessage::Stop: {\n    if (GetActive()) {\n      StopSound(mgr);\n    }\n  } break;\n  case EScriptObjectMessage::Deactivate: {\n    StopSound(mgr);\n  } break;\n  case EScriptObjectMessage::Activate: {\n    if (GetActive()) {\n      x11c_24_playRequested = true;\n    }\n  } break;\n  case EScriptObjectMessage::Deleted: {\n    if (!x11c_30_worldSfx) {\n      StopSound(mgr);\n    }\n  } break;\n  default:\n    break;\n  }\n}\n\nvoid CScriptSound::PlaySound(CStateManager& mgr) {\n  if ((!x11d_24_allowDuplicates && xec_sfxHandle && !xec_sfxHandle->IsClosed()) || x11d_25_processedThisFrame) {\n    return;\n  }\n\n  x11d_25_processedThisFrame = true;\n  if (x11c_26_nonEmitter) {\n    if (!x11c_30_worldSfx || !mgr.GetWorld()->HasGlobalSound(x100_soundId)) {\n      xec_sfxHandle = CSfxManager::SfxStart(x100_soundId, x10e_vol, x114_pan, x11c_29_acoustics, x112_prio,\n                                            x11c_25_looped, x11c_30_worldSfx ? kInvalidAreaId : GetAreaIdAlways());\n      if (x11c_30_worldSfx) {\n        mgr.GetWorld()->AddGlobalSound(xec_sfxHandle);\n      }\n    }\n  } else {\n    const float occVol = x11c_28_occlusionTest ? GetOccludedVolumeAmount(GetTranslation(), mgr) : 1.f;\n    xf0_maxVol = xf2_maxVolUpd = x10e_vol * occVol;\n    CAudioSys::C3DEmitterParmData data = {};\n    data.x0_pos = GetTranslation();\n    data.x18_maxDist = x104_maxDist;\n    data.x1c_distComp = x108_distComp;\n    data.x20_flags = 1; // Continuous parameter update\n    data.x24_sfxId = x100_soundId;\n    data.x26_maxVol = xf0_maxVol;\n    data.x27_minVol = x10c_minVol;\n    data.x29_prio = 0x7f;\n    xec_sfxHandle = CSfxManager::AddEmitter(data, x11c_29_acoustics, x112_prio, x11c_25_looped, GetAreaIdAlways());\n  }\n}\n\nvoid CScriptSound::StopSound(CStateManager& mgr) {\n  x11c_24_playRequested = false;\n  if (x11c_30_worldSfx && x11c_26_nonEmitter) {\n    mgr.GetWorld()->StopGlobalSound(x100_soundId);\n    xec_sfxHandle.reset();\n  } else if (xec_sfxHandle) {\n    CSfxManager::RemoveEmitter(xec_sfxHandle);\n    xec_sfxHandle.reset();\n  }\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptSound.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/Audio/CSfxManager.hpp\"\n#include \"Runtime/World/CActor.hpp\"\n\nnamespace metaforce {\n\nclass CScriptSound : public CActor {\n  static bool sFirstInFrame;\n\n  float xe8_occUpdateTimer = 0.f;\n  CSfxHandle xec_sfxHandle;\n  float xf0_maxVol = 0.f;\n  float xf2_maxVolUpd = 0.f;\n  float xf4_maxVolUpdDelta = 0.f;\n  float xf8_updateTimer = 0.f;\n  float xfc_startDelay;\n  u16 x100_soundId;\n  float x104_maxDist;\n  float x108_distComp;\n  float x10c_minVol;\n  float x10e_vol;\n  s16 x110_;\n  s16 x112_prio;\n  float x114_pan;\n  short x116_;\n  float x118_pitch;\n  bool x11c_24_playRequested : 1 = false;\n  bool x11c_25_looped : 1;\n  bool x11c_26_nonEmitter : 1;\n  bool x11c_27_autoStart : 1;\n  bool x11c_28_occlusionTest : 1;\n  bool x11c_29_acoustics : 1;\n  bool x11c_30_worldSfx : 1;\n  bool x11c_31_selfFree : 1 = false;\n  bool x11d_24_allowDuplicates : 1;\n  bool x11d_25_processedThisFrame : 1 = false;\n\n  static float GetOccludedVolumeAmount(const zeus::CVector3f& pos, const CStateManager& mgr);\n\npublic:\n  DEFINE_ENTITY\n  CScriptSound(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf, u16 soundId,\n               bool active, float maxDist, float distComp, float startDelay, u32 minVol, u32 vol, u32 w3, u32 prio,\n               u32 pan, u32 w6, bool looped, bool nonEmitter, bool autoStart, bool occlusionTest, bool acoustics,\n               bool worldSfx, bool allowDuplicates, s32 pitch);\n\n  void Accept(IVisitor& visitor) override;\n  void AddToRenderer(const zeus::CFrustum&, CStateManager&) override {}\n  void PreThink(float, CStateManager&) override;\n  void Think(float, CStateManager&) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void PlaySound(CStateManager&);\n  void StopSound(CStateManager&);\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptSpawnPoint.cpp",
    "content": "#include \"Runtime/World/CScriptSpawnPoint.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCScriptSpawnPoint::CScriptSpawnPoint(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                                     const zeus::CTransform& xf,\n                                     const rstl::reserved_vector<u32, int(CPlayerState::EItemType::Max)>& itemCounts,\n                                     bool defaultSpawn, bool active, bool morphed)\n: CEntity(uid, info, active, name)\n, x34_xf(xf)\n, x64_itemCounts(itemCounts)\n, x10c_24_firstSpawn(defaultSpawn)\n, x10c_25_morphed(morphed) {}\n\nvoid CScriptSpawnPoint::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptSpawnPoint::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) {\n  CEntity::AcceptScriptMsg(msg, objId, stateMgr);\n\n  if (msg == EScriptObjectMessage::SetToZero || msg == EScriptObjectMessage::Reset) {\n    if (msg == EScriptObjectMessage::Reset) {\n      using EPlayerItemType = CPlayerState::EItemType;\n      const std::shared_ptr<CPlayerState>& plState = stateMgr.GetPlayerState();\n      for (u32 i = 0; i < u32(EPlayerItemType::Max); ++i) {\n        plState->ReInitializePowerUp(EPlayerItemType(i), GetPowerup(EPlayerItemType(i)));\n        plState->ResetAndIncrPickUp(EPlayerItemType(i), GetPowerup(EPlayerItemType(i)));\n      }\n    }\n\n    if (GetActive()) {\n      CPlayer* player = stateMgr.Player();\n\n      if (x4_areaId != stateMgr.GetNextAreaId()) {\n        CGameArea* area = stateMgr.GetWorld()->GetArea(x4_areaId);\n        if (area->IsPostConstructed() && area->GetOcclusionState() == CGameArea::EOcclusionState::Occluded) {\n          /* while (!area->TryTakingOutOfARAM()) {} */\n          CWorld::PropogateAreaChain(CGameArea::EOcclusionState::Visible, area, stateMgr.GetWorld());\n        }\n\n        stateMgr.SetCurrentAreaId(x4_areaId);\n        stateMgr.SetActorAreaId(*stateMgr.Player(), x4_areaId);\n        player->Teleport(GetTransform(), stateMgr, true);\n        player->SetSpawnedMorphBallState(CPlayer::EPlayerMorphBallState(x10c_25_morphed), stateMgr);\n\n        if (area->IsPostConstructed() && area->GetOcclusionState() == CGameArea::EOcclusionState::Visible) {\n          CWorld::PropogateAreaChain(CGameArea::EOcclusionState::Occluded,\n                                     stateMgr.GetWorld()->GetArea(stateMgr.GetNextAreaId()), stateMgr.GetWorld());\n        }\n      } else {\n        player->Teleport(GetTransform(), stateMgr, true);\n        player->SetSpawnedMorphBallState(CPlayer::EPlayerMorphBallState(x10c_25_morphed), stateMgr);\n      }\n    }\n\n    CEntity::SendScriptMsgs(EScriptObjectState::Zero, stateMgr, EScriptObjectMessage::None);\n  }\n}\n\nu32 CScriptSpawnPoint::GetPowerup(CPlayerState::EItemType item) const {\n  const auto idx = static_cast<size_t>(item);\n  if (item >= CPlayerState::EItemType::Max) {\n    return x64_itemCounts.front();\n  }\n  return x64_itemCounts[idx];\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptSpawnPoint.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/CPlayerState.hpp\"\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/World/CEntity.hpp\"\n\n#include <zeus/CTransform.hpp>\n\nnamespace metaforce {\n\nclass CScriptSpawnPoint : public CEntity {\n  zeus::CTransform x34_xf;\n  rstl::reserved_vector<u32, int(CPlayerState::EItemType::Max)> x64_itemCounts;\n  bool x10c_24_firstSpawn : 1;\n  bool x10c_25_morphed : 1;\n\npublic:\n  DEFINE_ENTITY\n  CScriptSpawnPoint(TUniqueId, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                    const rstl::reserved_vector<u32, int(CPlayerState::EItemType::Max)>& itemCounts, bool, bool, bool);\n\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) override;\n  bool FirstSpawn() const { return x10c_24_firstSpawn; }\n  const zeus::CTransform& GetTransform() const { return x34_xf; }\n  u32 GetPowerup(CPlayerState::EItemType item) const;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptSpecialFunction.cpp",
    "content": "#include \"Runtime/World/CScriptSpecialFunction.hpp\"\n\n#include <array>\n\n#include \"Runtime/CGameState.hpp\"\n#include \"Runtime/CMemoryCardSys.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/IMain.hpp\"\n#include \"Runtime/Audio/CSfxManager.hpp\"\n#include \"Runtime/Camera/CCameraManager.hpp\"\n#include \"Runtime/Character/CModelData.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Graphics/CTexture.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CScriptPlatform.hpp\"\n#include \"Runtime/Weapon/CEnergyProjectile.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\n#include \"ConsoleVariables/CVarManager.hpp\"\n\nnamespace metaforce {\n\nCScriptSpecialFunction::CScriptSpecialFunction(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                                               const zeus::CTransform& xf, ESpecialFunction func,\n                                               std::string_view lcName, float f1, float f2, float f3, float f4,\n                                               const zeus::CVector3f& vec, const zeus::CColor& col, bool active,\n                                               const CDamageInfo& dInfo, s32 aId1, s32 aId2,\n                                               CPlayerState::EItemType itemType, s16 sId1, s16 sId2, s16 sId3)\n: CActor(uid, active, name, info, xf, CModelData::CModelDataNull(), CMaterialList(), CActorParameters::None(),\n         kInvalidUniqueId)\n, xe8_function(func)\n, xec_locatorName(lcName)\n, xfc_float1(f1)\n, x100_float2(f2)\n, x104_float3(f3)\n, x108_float4(f4)\n, x10c_vector3f(vec)\n, x118_color(col)\n, x11c_damageInfo(dInfo)\n, x170_sfx1(CSfxManager::TranslateSFXID(sId1))\n, x172_sfx2(CSfxManager::TranslateSFXID(sId2))\n, x174_sfx3(CSfxManager::TranslateSFXID(sId3))\n, x184_(0.f)\n, x1bc_areaSaveId(aId1)\n, x1c0_layerIdx(aId2)\n, x1c4_item(itemType) {\n  if (xe8_function == ESpecialFunction::HUDTarget) {\n    x1c8_touchBounds = {-1.f, 1.f};\n  }\n  // Set this as internal archivable so it can be serialized if the player sets it in the configuration\n  m_cvRef.emplace(&m_canSkipCutscenes,\n                  CVarManager::instance()->findOrMakeCVar(\n                      \"enableCutsceneSkip\"sv, \"Enable skipping of all cutscenes\", false,\n                      CVar::EFlags::Cheat | CVar::EFlags::Game | CVar::EFlags::InternalArchivable));\n}\n\nvoid CScriptSpecialFunction::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptSpecialFunction::Think(float dt, CStateManager& mgr) {\n  if (!GetActive()) {\n    return;\n  }\n\n  switch (xe8_function) {\n  case ESpecialFunction::PlayerFollowLocator:\n    ThinkPlayerFollowLocator(dt, mgr);\n    break;\n  case ESpecialFunction::SpinnerController:\n    ThinkSpinnerController(dt, mgr, ESpinnerControllerMode::Zero);\n    break;\n  case ESpecialFunction::ShotSpinnerController:\n    ThinkSpinnerController(dt, mgr, ESpinnerControllerMode::One);\n    break;\n  case ESpecialFunction::ObjectFollowLocator:\n    ThinkObjectFollowLocator(dt, mgr);\n    break;\n  case ESpecialFunction::ObjectFollowObject:\n    ThinkObjectFollowObject(dt, mgr);\n    break;\n  case ESpecialFunction::ChaffTarget:\n    ThinkChaffTarget(dt, mgr);\n    break;\n  case ESpecialFunction::ViewFrustumTester: {\n    if (x1e4_28_frustumEntered) {\n      x1e4_28_frustumEntered = false;\n      SendScriptMsgs(EScriptObjectState::Entered, mgr, EScriptObjectMessage::None);\n    }\n    if (x1e4_29_frustumExited) {\n      x1e4_29_frustumExited = false;\n      SendScriptMsgs(EScriptObjectState::Exited, mgr, EScriptObjectMessage::None);\n    }\n    break;\n  }\n  case ESpecialFunction::SaveStation:\n    ThinkSaveStation(dt, mgr);\n    break;\n  case ESpecialFunction::IntroBossRingController:\n    ThinkIntroBossRingController(dt, mgr);\n    break;\n  case ESpecialFunction::RainSimulator:\n    ThinkRainSimulator(dt, mgr);\n    break;\n  case ESpecialFunction::AreaDamage:\n    ThinkAreaDamage(dt, mgr);\n    break;\n  case ESpecialFunction::ScaleActor:\n    ThinkActorScale(dt, mgr);\n    break;\n  case ESpecialFunction::PlayerInAreaRelay:\n    ThinkPlayerInArea(dt, mgr);\n    break;\n  case ESpecialFunction::Billboard: {\n    if (x1e8_ && x1e5_26_displayBillboard) {\n      SendScriptMsgs(EScriptObjectState::MaxReached, mgr, EScriptObjectMessage::None);\n      x1e5_26_displayBillboard = false;\n    }\n    break;\n  }\n  default:\n    break;\n  }\n}\n\nconstexpr std::array fxTranslation{\n    ERumbleFxId::Twenty,    ERumbleFxId::One,         ERumbleFxId::TwentyOne,\n    ERumbleFxId::TwentyTwo, ERumbleFxId::TwentyThree, ERumbleFxId::Zero,\n};\n\nvoid CScriptSpecialFunction::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  if (GetActive() && msg == EScriptObjectMessage::Deactivate && xe8_function == ESpecialFunction::Billboard) {\n    mgr.SetPendingOnScreenTex(CAssetId(), zeus::CVector2i(), zeus::CVector2i());\n    if (x1e8_) {\n      x1e8_ = TLockedToken<CTexture>();\n    }\n    x1e5_26_displayBillboard = false;\n  }\n  CActor::AcceptScriptMsg(msg, uid, mgr);\n\n  if (xe8_function == ESpecialFunction::ChaffTarget && msg == EScriptObjectMessage::InitializedInArea) {\n    AddMaterial(EMaterialTypes::Target, mgr);\n  }\n\n  if (GetActive()) {\n    switch (xe8_function) {\n    case ESpecialFunction::HUDFadeIn: {\n      if (msg == EScriptObjectMessage::Action) {\n        mgr.Player()->SetHudDisable(xfc_float1, 0.f, 0.5f);\n      }\n      break;\n    }\n    case ESpecialFunction::EscapeSequence: {\n      if (msg == EScriptObjectMessage::Action && xfc_float1 >= 0.f) {\n        mgr.ResetEscapeSequenceTimer(xfc_float1);\n      }\n      break;\n    }\n    case ESpecialFunction::SpinnerController: {\n      switch (msg) {\n      case EScriptObjectMessage::Stop: {\n        x1e4_25_spinnerCanMove = false;\n        break;\n      }\n      case EScriptObjectMessage::Play: {\n        x1e4_25_spinnerCanMove = true;\n        mgr.Player()->SetAngularVelocityWR(zeus::CAxisAngle());\n        break;\n      }\n      case EScriptObjectMessage::Deactivate:\n        DeleteEmitter(x178_sfxHandle);\n        break;\n      default:\n        break;\n      }\n      break;\n    }\n    case ESpecialFunction::ShotSpinnerController: {\n      switch (msg) {\n      case EScriptObjectMessage::Increment: {\n        x16c_ = zeus::clamp(0.f, x16c_ + 1.f, 1.f);\n        SendScriptMsgs(EScriptObjectState::Play, mgr, EScriptObjectMessage::None);\n        break;\n      }\n      case EScriptObjectMessage::SetToMax: {\n        x16c_ = x104_float3;\n        SendScriptMsgs(EScriptObjectState::Play, mgr, EScriptObjectMessage::None);\n        break;\n      }\n      case EScriptObjectMessage::SetToZero: {\n        x16c_ = -0.5f * x104_float3;\n        break;\n      }\n      default:\n        break;\n      }\n      break;\n    }\n    case ESpecialFunction::MapStation: {\n      if (msg == EScriptObjectMessage::Action) {\n        mgr.MapWorldInfo()->SetMapStationUsed(true);\n        mgr.GetWorld()->GetMapWorld()->RecalculateWorldSphere(*mgr.MapWorldInfo(), *mgr.GetWorld());\n      }\n      break;\n    }\n    case ESpecialFunction::MissileStation: {\n      if (msg == EScriptObjectMessage::Action) {\n        CPlayerState& pState = *mgr.GetPlayerState();\n        pState.ResetAndIncrPickUp(CPlayerState::EItemType::Missiles,\n                                  pState.GetItemCapacity(CPlayerState::EItemType::Missiles));\n      }\n      break;\n    }\n    case ESpecialFunction::PowerBombStation: {\n      if (msg == EScriptObjectMessage::Action) {\n        CPlayerState& pState = *mgr.GetPlayerState();\n        pState.ResetAndIncrPickUp(CPlayerState::EItemType::PowerBombs,\n                                  pState.GetItemCapacity(CPlayerState::EItemType::PowerBombs));\n      }\n      break;\n    }\n    case ESpecialFunction::SaveStation: {\n      if (msg == EScriptObjectMessage::Action) {\n        g_GameState->GetPlayerState()->IncrPickup(CPlayerState::EItemType::EnergyTanks, 1);\n        if (g_GameState->GetCardSerial() == 0) {\n          SendScriptMsgs(EScriptObjectState::Closed, mgr, EScriptObjectMessage::None);\n        } else {\n          mgr.DeferStateTransition(EStateManagerTransition::SaveGame);\n          x1e5_24_doSave = true;\n        }\n      }\n      break;\n    }\n    case ESpecialFunction::IntroBossRingController: {\n      if (x1a8_ringState != ERingState::Breakup) {\n        switch (msg) {\n        case EScriptObjectMessage::Play: {\n          if (x1a8_ringState != ERingState::Scramble) {\n            RingScramble(mgr);\n          }\n\n          for (SRingController& cont : x198_ringControllers) {\n            if (const TCastToPtr<CActor> act = mgr.ObjectById(cont.x0_id)) {\n              cont.xc_ = act->GetTransform().frontVector();\n            } else {\n              cont.xc_ = zeus::skForward;\n            }\n          }\n\n          x1a8_ringState = ERingState::Breakup;\n          break;\n        }\n        case EScriptObjectMessage::SetToZero: {\n          x1a8_ringState = ERingState::Rotate;\n          x1ac_ringRotateTarget = GetTranslation() - mgr.GetPlayer().GetTranslation();\n          x1ac_ringRotateTarget.z() = 0.f;\n          x1ac_ringRotateTarget.normalize();\n          break;\n        }\n        case EScriptObjectMessage::Action: {\n          RingScramble(mgr);\n          break;\n        }\n        case EScriptObjectMessage::InitializedInArea: {\n          x198_ringControllers.reserve(3);\n          for (const SConnection& conn : x20_conns) {\n            if (conn.x0_state != EScriptObjectState::Play || conn.x4_msg != EScriptObjectMessage::Activate) {\n              continue;\n            }\n\n            auto search = mgr.GetIdListForScript(conn.x8_objId);\n            for (auto it = search.first; it != search.second; ++it) {\n              if (const TCastToPtr<CActor> act = mgr.ObjectById(it->second)) {\n                x198_ringControllers.emplace_back(it->second, 0.f, false);\n                act->RemoveMaterial(EMaterialTypes::Occluder, mgr);\n              }\n            }\n          }\n\n          std::sort(x198_ringControllers.begin(), x198_ringControllers.end(),\n                    [&mgr](const SRingController& a, const SRingController& b) {\n                      const TCastToConstPtr<CActor> actA(mgr.GetObjectById(a.x0_id));\n                      const TCastToConstPtr<CActor> actB(mgr.GetObjectById(b.x0_id));\n                      if (actA && actB) {\n                        return actA->GetTranslation().z() < actB->GetTranslation().z();\n                      }\n                      return false;\n                    });\n\n          for (auto& rc : x198_ringControllers) {\n            rc.x4_rotateSpeed = (x1b8_ringReverse ? 1.f : -1.f) * xfc_float1;\n            rc.x8_reachedTarget = false;\n          }\n          break;\n        }\n        default:\n          break;\n        }\n      }\n      break;\n    }\n    case ESpecialFunction::RadialDamage: {\n      if (msg == EScriptObjectMessage::Action) {\n        CDamageInfo dInfo = x11c_damageInfo;\n        dInfo.SetRadius(xfc_float1);\n        mgr.ApplyDamageToWorld(GetUniqueId(), *this, GetTranslation(), dInfo,\n                               CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {0ull}));\n      }\n      break;\n    }\n    case ESpecialFunction::BossEnergyBar: {\n      if (msg == EScriptObjectMessage::Increment) {\n        mgr.SetBossParams(uid, xfc_float1, u32(x100_float2) + 86);\n      } else if (msg == EScriptObjectMessage::Decrement) {\n        mgr.SetBossParams(kInvalidUniqueId, 0.f, 0);\n      }\n      break;\n    }\n    case ESpecialFunction::EndGame: {\n      if (msg == EScriptObjectMessage::Action) {\n        switch (ClassifyEnding(mgr)) {\n        case 0:\n          g_Main->SetFlowState(EClientFlowStates::WinBad);\n          break;\n        case 1:\n          g_Main->SetFlowState(EClientFlowStates::WinGood);\n          break;\n        case 2:\n          g_Main->SetFlowState(EClientFlowStates::WinBest);\n          break;\n        }\n        mgr.SetShouldQuitGame(true);\n      }\n      break;\n    }\n    case ESpecialFunction::CinematicSkip: {\n      if (msg == EScriptObjectMessage::Increment) {\n        if (ShouldSkipCinematic(mgr)) {\n          mgr.SetSkipCinematicSpecialFunction(GetUniqueId());\n        }\n      } else if (msg == EScriptObjectMessage::Decrement) {\n        mgr.SetSkipCinematicSpecialFunction(kInvalidUniqueId);\n        g_GameState->SystemOptions().SetCinematicState(mgr.GetWorld()->GetWorldAssetId(), GetEditorId(), true);\n      }\n      break;\n    }\n    case ESpecialFunction::ScriptLayerController: {\n      if (msg == EScriptObjectMessage::Decrement || msg == EScriptObjectMessage::Increment) {\n        if (x1bc_areaSaveId != -1 && x1c0_layerIdx != -1) {\n          TAreaId aId = mgr.GetWorld()->GetAreaIdForSaveId(x1bc_areaSaveId);\n          std::shared_ptr<CScriptLayerManager> worldLayerState;\n          if (aId != kInvalidAreaId) {\n            worldLayerState = mgr.WorldLayerState();\n          } else {\n            const std::pair<CAssetId, TAreaId> worldAreaPair =\n                g_MemoryCardSys->GetAreaAndWorldIdForSaveId(x1bc_areaSaveId);\n            if (worldAreaPair.first.IsValid()) {\n              worldLayerState = g_GameState->StateForWorld(worldAreaPair.first).GetLayerState();\n              aId = worldAreaPair.second;\n            }\n          }\n\n          if (aId != kInvalidAreaId) {\n            worldLayerState->SetLayerActive(aId, x1c0_layerIdx, msg == EScriptObjectMessage::Increment);\n          }\n        }\n      }\n      break;\n    }\n    /*\n    For some bizarre reason ScriptLayerController drops into EnvFxDensityController\n    [[fallthrough]];\n    We won't do that though\n    */\n    case ESpecialFunction::EnvFxDensityController: {\n      if (msg == EScriptObjectMessage::Action) {\n        mgr.GetEnvFxManager()->SetFxDensity(s32(x100_float2), xfc_float1);\n      }\n      break;\n    }\n    case ESpecialFunction::RumbleEffect: {\n      if (msg != EScriptObjectMessage::Action) {\n        break;\n      }\n      int rumbFxIdx = int(x100_float2);\n      if (rumbFxIdx < 0 || rumbFxIdx >= fxTranslation.size()) {\n        break;\n      }\n      ERumbleFxId rumbFx = fxTranslation[rumbFxIdx];\n      u32 param3 = x104_float3;\n      if ((param3 & 1) != 0) {\n        mgr.GetRumbleManager().Rumble(mgr, rumbFx, 1.f, ERumblePriority::One);\n      } else {\n        zeus::CVector3f pos = GetTranslation();\n        if ((param3 & 2) != 0) {\n          if (const CActor* act = TCastToConstPtr<CActor>(mgr.GetObjectById(uid))) {\n            pos = act->GetTranslation();\n          }\n        }\n        mgr.GetRumbleManager().Rumble(mgr, pos, rumbFx, xfc_float1, ERumblePriority::One);\n      }\n      break;\n    }\n    case ESpecialFunction::InventoryActivator: {\n      if (msg == EScriptObjectMessage::Action && mgr.GetPlayerState()->HasPowerUp(x1c4_item)) {\n        SendScriptMsgs(EScriptObjectState::Zero, mgr, EScriptObjectMessage::None);\n      }\n      break;\n    }\n    case ESpecialFunction::FusionRelay: {\n      if (msg == EScriptObjectMessage::Action && mgr.GetPlayerState()->IsFusionEnabled()) {\n        SendScriptMsgs(EScriptObjectState::Zero, mgr, EScriptObjectMessage::None);\n      }\n      break;\n    }\n    case ESpecialFunction::AreaDamage: {\n      if ((msg == EScriptObjectMessage::Deleted || msg == EScriptObjectMessage::Deactivate) && x1e4_31_inAreaDamage) {\n        x1e4_31_inAreaDamage = false;\n        mgr.GetPlayer().DecrementEnvironmentDamage();\n        mgr.SetIsFullThreat(false);\n      }\n      break;\n    }\n    case ESpecialFunction::DropBomb: {\n      if (msg == EScriptObjectMessage::Action) {\n        if (xfc_float1 >= 1.f) {\n          mgr.GetPlayer().GetPlayerGun()->DropBomb(CPlayerGun::EBWeapon::PowerBomb, mgr);\n        } else {\n          mgr.GetPlayer().GetPlayerGun()->DropBomb(CPlayerGun::EBWeapon::Bomb, mgr);\n        }\n      }\n      break;\n    }\n    case ESpecialFunction::RedundantHintSystem: {\n      CHintOptions& hintOptions = g_GameState->HintOptions();\n      if (msg == EScriptObjectMessage::Action) {\n        hintOptions.ActivateContinueDelayHintTimer(xec_locatorName);\n      } else if (msg == EScriptObjectMessage::Increment) {\n        hintOptions.ActivateImmediateHintTimer(xec_locatorName);\n      } else if (msg == EScriptObjectMessage::Decrement) {\n        hintOptions.DelayHint(xec_locatorName);\n      }\n      break;\n    }\n    case ESpecialFunction::Billboard: {\n      if (msg == EScriptObjectMessage::Increment) {\n        const SObjectTag* objectTag = g_ResFactory->GetResourceIdByName(xec_locatorName);\n        const CAssetId assetId = objectTag ? objectTag->id : CAssetId();\n\n        mgr.SetPendingOnScreenTex(assetId, {int(xfc_float1), int(x100_float2)}, {int(x104_float3), int(x108_float4)});\n        if (objectTag) {\n          x1e8_ = g_SimplePool->GetObj(*objectTag);\n          x1e5_26_displayBillboard = true;\n        }\n      } else if (msg == EScriptObjectMessage::Decrement) {\n        mgr.SetPendingOnScreenTex({}, {int(xfc_float1), int(x100_float2)}, {int(x104_float3), int(x108_float4)});\n        if (x1e8_)\n          x1e8_ = TLockedToken<CTexture>();\n        x1e5_26_displayBillboard = false;\n      }\n      break;\n    }\n    case ESpecialFunction::PlayerInAreaRelay: {\n      if ((msg == EScriptObjectMessage::Action || msg == EScriptObjectMessage::SetToZero) &&\n          GetAreaIdAlways() == mgr.GetPlayer().GetAreaIdAlways()) {\n        SendScriptMsgs(EScriptObjectState::Zero, mgr, EScriptObjectMessage::None);\n      }\n      break;\n    }\n    case ESpecialFunction::HUDTarget: {\n      if (msg == EScriptObjectMessage::Increment) {\n        AddMaterial(EMaterialTypes::Target, EMaterialTypes::RadarObject, mgr);\n      } else if (msg == EScriptObjectMessage::Decrement) {\n        RemoveMaterial(EMaterialTypes::Target, EMaterialTypes::RadarObject, mgr);\n      }\n      break;\n    }\n    case ESpecialFunction::FogFader: {\n      if (msg == EScriptObjectMessage::Increment) {\n        mgr.GetCameraManager()->SetFogDensity(x100_float2, xfc_float1);\n      } else if (msg == EScriptObjectMessage::Decrement) {\n        mgr.GetCameraManager()->SetFogDensity(x100_float2, 1.f);\n      }\n      break;\n    }\n    case ESpecialFunction::EnterLogbook: {\n      if (msg == EScriptObjectMessage::Action) {\n        mgr.DeferStateTransition(EStateManagerTransition::LogBook);\n      }\n      break;\n    }\n    case ESpecialFunction::Ending: {\n      if (msg == EScriptObjectMessage::Action && ClassifyEnding(mgr) == u32(xfc_float1)) {\n        SendScriptMsgs(EScriptObjectState::Zero, mgr, EScriptObjectMessage::None);\n      }\n      break;\n    }\n    default:\n      break;\n    }\n  }\n}\n\nvoid CScriptSpecialFunction::PreRender(CStateManager&, const zeus::CFrustum& frustum) {\n  if (xe8_function != ESpecialFunction::FogVolume && xe8_function != ESpecialFunction::ViewFrustumTester) {\n    return;\n  }\n  if (!GetActive()) {\n    return;\n  }\n\n  bool val;\n  if (xe8_function == ESpecialFunction::FogVolume) {\n    val = frustum.aabbFrustumTest(zeus::CAABox(GetTranslation() - x10c_vector3f, GetTranslation() + x10c_vector3f));\n  } else {\n    val = frustum.pointFrustumTest(GetTranslation());\n  }\n\n  if (x1e4_30_ == val) {\n    return;\n  }\n  if (val) {\n    x1e4_28_frustumEntered = true;\n  } else {\n    x1e4_29_frustumExited = true;\n  }\n  x1e4_30_ = val;\n}\n\nvoid CScriptSpecialFunction::AddToRenderer(const zeus::CFrustum&, CStateManager& mgr) {\n  if (!GetActive()) {\n    return;\n  }\n\n  if (xe8_function == ESpecialFunction::FogVolume && x1e4_30_) {\n    EnsureRendered(mgr);\n  }\n}\n\nvoid CScriptSpecialFunction::Render(CStateManager& mgr) {\n  if (xe8_function == ESpecialFunction::FogVolume) {\n    const float z = mgr.IntegrateVisorFog(xfc_float1 * std::sin(CGraphics::GetSecondsMod900() * x100_float2));\n    if (z > 0.f) {\n      zeus::CVector3f max = GetTranslation() + x10c_vector3f;\n      max.z() += z;\n      const zeus::CAABox box(GetTranslation() - x10c_vector3f, max);\n      const zeus::CTransform modelMtx =\n          zeus::CTransform::Translate(box.center()) * zeus::CTransform::Scale(box.extents());\n      g_Renderer->SetModelMatrix(modelMtx);\n      g_Renderer->RenderFogVolume(x118_color, zeus::CAABox(-1.f, 1.f), nullptr, nullptr);\n    }\n  } else {\n    CActor::Render(mgr);\n  }\n}\n\nvoid CScriptSpecialFunction::SkipCinematic(CStateManager& stateMgr) {\n  SendScriptMsgs(EScriptObjectState::Zero, stateMgr, EScriptObjectMessage::None);\n  stateMgr.SetSkipCinematicSpecialFunction(kInvalidUniqueId);\n}\n\nvoid CScriptSpecialFunction::RingScramble(CStateManager& mgr) {\n  SendScriptMsgs(EScriptObjectState::Zero, mgr, EScriptObjectMessage::None);\n  x1a8_ringState = ERingState::Scramble;\n  x1b8_ringReverse = !x1b8_ringReverse;\n  float dir = (x1b8_ringReverse ? 1.f : -1.f);\n  for (auto& rc : x198_ringControllers) {\n    rc.x4_rotateSpeed = dir * mgr.GetActiveRandom()->Range(x100_float2, x104_float3);\n    dir = -dir;\n    rc.x8_reachedTarget = false;\n  }\n}\n\nvoid CScriptSpecialFunction::ThinkIntroBossRingController(float dt, CStateManager& mgr) {\n  if (x1a8_ringState != ERingState::Breakup) {\n    for (const auto& rc : x198_ringControllers) {\n      if (const TCastToPtr<CActor> act = mgr.ObjectById(rc.x0_id)) {\n        zeus::CTransform newXf = act->GetTransform();\n        newXf.rotateLocalZ(zeus::degToRad(rc.x4_rotateSpeed * dt));\n        act->SetTransform(newXf);\n      }\n    }\n  }\n  switch (x1a8_ringState) {\n  case ERingState::Breakup: {\n    float minMag = 0.f;\n    for (const auto& rc : x198_ringControllers) {\n      if (const TCastToPtr<CActor> act = mgr.ObjectById(rc.x0_id)) {\n        act->SetTranslation(act->GetTransform().basis[1] * 50.f * dt + act->GetTranslation());\n        minMag = std::min(act->GetTranslation().magnitude(), minMag);\n      }\n    }\n    CalculateRenderBounds();\n    if (minMag != 0.f) {\n      /* Never actually happens */\n      for (const auto& rc : x198_ringControllers) {\n        if (CEntity* ent = mgr.ObjectById(rc.x0_id)) {\n          ent->SetActive(false);\n        }\n      }\n      SetActive(false);\n    }\n    break;\n  }\n  case ERingState::Rotate: {\n    x1ac_ringRotateTarget =\n        zeus::CQuaternion::fromAxisAngle(zeus::skUp, zeus::degToRad(xfc_float1 * (x1b8_ringReverse ? 1.f : -1.f) * dt))\n            .transform(x1ac_ringRotateTarget);\n    bool allReachedTarget = true;\n    for (auto& rc : x198_ringControllers) {\n      if (const TCastToPtr<CActor> act = mgr.ObjectById(rc.x0_id)) {\n        zeus::CVector3f lookDirFlat = act->GetTransform().basis[1];\n        lookDirFlat.z() = 0.f;\n        lookDirFlat.normalize();\n        if (std::acos(zeus::clamp(-1.f, lookDirFlat.dot(x1ac_ringRotateTarget), 1.f)) <=\n            zeus::degToRad((xfc_float1 + std::fabs(rc.x4_rotateSpeed)) / 30.f)) {\n          zeus::CTransform newXf = zeus::lookAt(zeus::skZero3f, x1ac_ringRotateTarget);\n          newXf.origin = act->GetTranslation();\n          act->SetTransform(newXf);\n          rc.x4_rotateSpeed = (x1b8_ringReverse ? 1.f : -1.f) * xfc_float1;\n          rc.x8_reachedTarget = true;\n        } else {\n          allReachedTarget = false;\n          break;\n        }\n      }\n    }\n    if (allReachedTarget) {\n      SendScriptMsgs(EScriptObjectState::MaxReached, mgr, EScriptObjectMessage::None);\n      x1a8_ringState = ERingState::Stopped;\n      for (auto& rc : x198_ringControllers) {\n        rc.x8_reachedTarget = false;\n      }\n    }\n    break;\n  }\n  default:\n    break;\n  }\n}\n\nvoid CScriptSpecialFunction::ThinkPlayerFollowLocator(float, CStateManager& mgr) {\n  for (const SConnection& conn : GetConnectionList()) {\n    if (conn.x0_state != EScriptObjectState::Play || conn.x4_msg != EScriptObjectMessage::Activate) {\n      continue;\n    }\n\n    const auto search = mgr.GetIdListForScript(conn.x8_objId);\n    for (auto it = search.first; it != search.second; ++it) {\n      if (const TCastToConstPtr<CActor> act = mgr.GetObjectById(it->second)) {\n        if (!act->GetModelData() || !act->GetModelData()->HasAnimData()) {\n          continue;\n        }\n\n        const zeus::CTransform xf = act->GetTransform() * act->GetLocatorTransform(xec_locatorName);\n        CPlayer& pl = mgr.GetPlayer();\n        pl.SetTransform(xf);\n        pl.SetVelocityWR({});\n        pl.SetAngularVelocityWR({});\n        pl.ClearForcesAndTorques();\n        return;\n      }\n    }\n  }\n}\n\nvoid CScriptSpecialFunction::ThinkSpinnerController(float dt, CStateManager& mgr, ESpinnerControllerMode mode) {\n  const bool allowWrap = xec_locatorName.find(\"AllowWrap\") != std::string::npos;\n  const bool noBackward = xec_locatorName.find(\"NoBackward\") != std::string::npos;\n  const float pointOneByDt = 0.1f * dt;\n  const float twoByDt = 2.f * dt;\n\n  for (const SConnection& conn : x20_conns) {\n    if (conn.x0_state != EScriptObjectState::Play || conn.x4_msg != EScriptObjectMessage::Activate) {\n      continue;\n    }\n\n    const auto search = mgr.GetIdListForScript(conn.x8_objId);\n    for (auto it = search.first; it != search.second; ++it) {\n      if (const TCastToPtr<CScriptPlatform> plat = mgr.ObjectById((*it).second)) {\n        if (plat->HasModelData() && plat->GetModelData()->HasAnimData()) {\n          plat->SetControlledAnimation(true);\n          if (!x1e4_24_spinnerInitializedXf) {\n            x13c_spinnerInitialXf = plat->GetTransform();\n            x1e4_24_spinnerInitializedXf = true;\n          }\n\n          float f28 = x138_;\n          const float f29 = pointOneByDt * x100_float2;\n\n          if (mode == ESpinnerControllerMode::Zero) {\n            if (x1e4_25_spinnerCanMove) {\n              const CPlayer& pl = mgr.GetPlayer();\n              const zeus::CVector3f angVel = pl.GetAngularVelocityOR().getVector();\n              float mag = 0.f;\n              if (angVel.canBeNormalized()) {\n                mag = angVel.magnitude();\n              }\n\n              const float spinImpulse =\n                  (pl.GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed ? 0.025f * mag : 0.f);\n              if (spinImpulse >= x180_) {\n                SendScriptMsgs(EScriptObjectState::Play, mgr, EScriptObjectMessage::None);\n              }\n\n              x180_ = spinImpulse;\n              x138_ += 0.01f * spinImpulse * xfc_float1;\n\n              if (!noBackward) {\n                x138_ -= f29;\n              }\n            } else if (!noBackward) {\n              x138_ = f28 - twoByDt;\n            }\n          } else if (mode == ESpinnerControllerMode::One) {\n            x138_ = (0.01f * x16c_) * xfc_float1 + f28;\n\n            if (!noBackward) {\n              x138_ -= f29;\n\n              if (std::fabs(x16c_) < dt) {\n                x16c_ = 0.f;\n              } else {\n                x16c_ -= (dt * (x16c_ <= 0.f ? -1.f : 1.f));\n              }\n            }\n          }\n\n          if (allowWrap) {\n            x138_ = std::fmod(x138_, 1.f);\n            if (x138_ < 0.f) {\n              x138_ += 1.f;\n            }\n          } else {\n            x138_ = zeus::clamp(0.f, x138_, 1.f);\n          }\n\n          bool r23 = true;\n          f28 = x138_ - f28; // always 0?\n          if (zeus::close_enough(x138_, 1.f, FLT_EPSILON)) {\n            if (!x1e4_27_sfx3Played) {\n              if (x174_sfx3 != 0xFFFF) {\n                CSfxManager::AddEmitter(x174_sfx3, GetTranslation(), {}, true, false, 0x7F, kInvalidAreaId);\n              }\n\n              x1e4_27_sfx3Played = true;\n            }\n\n            SendScriptMsgs(EScriptObjectState::MaxReached, mgr, EScriptObjectMessage::None);\n            r23 = false;\n          } else {\n            x1e4_27_sfx3Played = false;\n          }\n\n          if (zeus::close_enough(x138_, 0.f, FLT_EPSILON)) {\n            if (!x1e4_26_sfx2Played) {\n              if (x172_sfx2 != 0xFFFF) {\n                CSfxManager::AddEmitter(x172_sfx2, GetTranslation(), {}, true, false, 0x7F, kInvalidAreaId);\n              }\n\n              x1e4_26_sfx2Played = true;\n            }\n\n            SendScriptMsgs(EScriptObjectState::Zero, mgr, EScriptObjectMessage::None);\n            r23 = false;\n          } else {\n            x1e4_26_sfx2Played = false;\n          }\n\n          if (r23) {\n            if (x170_sfx1 != 0xFFFF) {\n              x184_.AddValue(0.f <= f28 ? 100 : 0x7f);\n              const std::optional<float> avg = x184_.GetAverage();\n              AddOrUpdateEmitter(0.f <= f28 ? x108_float4 : 1.f, x178_sfxHandle, x170_sfx1, GetTranslation(),\n                                 avg.value());\n            }\n          } else {\n            DeleteEmitter(x178_sfxHandle);\n          }\n\n          CAnimData* animData = plat->GetModelData()->GetAnimationData();\n          const float dur = animData->GetAnimationDuration(animData->GetDefaultAnimation()) * x138_;\n          animData->SetPhase(0.f);\n          animData->SetPlaybackRate(1.f);\n          const SAdvancementDeltas& deltas = plat->UpdateAnimation(dur, mgr, true);\n          plat->SetTransform(x13c_spinnerInitialXf * deltas.xc_rotDelta.toTransform(deltas.x0_posDelta));\n        }\n      }\n    }\n  }\n}\n\nvoid CScriptSpecialFunction::ThinkObjectFollowLocator(float, CStateManager& mgr) {\n  TUniqueId followerAct = kInvalidUniqueId;\n  TUniqueId followedAct = kInvalidUniqueId;\n  for (const SConnection& conn : x20_conns) {\n    if (conn.x0_state != EScriptObjectState::Play ||\n        (conn.x4_msg != EScriptObjectMessage::Activate && conn.x4_msg != EScriptObjectMessage::Deactivate)) {\n      continue;\n    }\n\n    const auto search = mgr.GetIdListForScript(conn.x8_objId);\n    for (auto it = search.first; it != search.second; ++it) {\n      if (const TCastToConstPtr<CActor> act = mgr.GetObjectById(it->second)) {\n        if (conn.x4_msg == EScriptObjectMessage::Activate &&\n            (act->HasModelData() && act->GetModelData()->HasAnimData()) && act->GetActive()) {\n          followedAct = it->second;\n        } else if (conn.x4_msg == EScriptObjectMessage::Deactivate) {\n          followerAct = it->second;\n        }\n      }\n    }\n  }\n\n  if (followerAct == kInvalidUniqueId || followedAct == kInvalidUniqueId) {\n    return;\n  }\n\n  const TCastToConstPtr<CActor> fromAct = mgr.GetObjectById(followedAct);\n  const TCastToPtr<CActor> toAct = mgr.ObjectById(followerAct);\n  toAct->SetTransform(fromAct->GetTransform() * fromAct->GetScaledLocatorTransform(xec_locatorName));\n}\n\nvoid CScriptSpecialFunction::ThinkObjectFollowObject(float, CStateManager& mgr) {\n  TUniqueId followerAct = kInvalidUniqueId;\n  TUniqueId followedAct = kInvalidUniqueId;\n  for (const SConnection& conn : x20_conns) {\n    if (conn.x0_state != EScriptObjectState::Play ||\n        (conn.x4_msg != EScriptObjectMessage::Activate && conn.x4_msg != EScriptObjectMessage::Deactivate)) {\n      continue;\n    }\n\n    const auto search = mgr.GetIdListForScript(conn.x8_objId);\n    for (auto it = search.first; it != search.second; ++it) {\n      if (const TCastToConstPtr<CActor> act = mgr.GetObjectById(it->second)) {\n        if (conn.x4_msg == EScriptObjectMessage::Activate && act->GetActive()) {\n          followedAct = it->second;\n        } else if (conn.x4_msg == EScriptObjectMessage::Deactivate) {\n          followerAct = it->second;\n        }\n      }\n    }\n  }\n\n  const TCastToConstPtr<CActor> followed = mgr.GetObjectById(followedAct);\n  const TCastToPtr<CActor> follower = mgr.ObjectById(followerAct);\n  if (followed && follower) {\n    follower->SetTransform(followed->GetTransform());\n  }\n}\n\nvoid CScriptSpecialFunction::ThinkChaffTarget(float dt, CStateManager& mgr) {\n  const zeus::CAABox box(GetTranslation() - 5.f, GetTranslation() + 5.f);\n  EntityList nearList;\n  mgr.BuildNearList(nearList, box, CMaterialFilter::MakeInclude({EMaterialTypes::Projectile}), nullptr);\n\n  for (const auto& uid : nearList) {\n    if (const TCastToPtr<CEnergyProjectile> proj = mgr.ObjectById(uid)) {\n      if (proj->GetHomingTargetId() == GetUniqueId()) {\n        proj->Set3d0_26(true);\n        if (mgr.GetPlayer().GetAreaIdAlways() == GetAreaIdAlways()) {\n          mgr.GetPlayer().SetHudDisable(x100_float2, 0.5f, 2.5f);\n          x194_ = xfc_float1;\n\n          auto& filter = mgr.GetCameraFilterPass(7);\n          filter.SetFilter(EFilterType::Blend, EFilterShape::Fullscreen, 0.f, zeus::skWhite, CAssetId());\n          filter.DisableFilter(0.1f);\n        }\n      }\n    }\n  }\n\n  x194_ = zeus::max(0.f, x194_ - dt);\n  if (x194_ != 0.f && mgr.GetPlayer().GetAreaIdAlways() == GetAreaIdAlways()) {\n    float intfMag = x104_float3 * (0.5f + ((0.5f + x194_) / xfc_float1));\n    if (x194_ < 1.f) {\n      intfMag *= x194_;\n    }\n\n    mgr.GetPlayerState()->GetStaticInterference().AddSource(GetUniqueId(), intfMag, .5f);\n\n    if (mgr.GetPlayerState()->GetCurrentVisor() != CPlayerState::EPlayerVisor::Thermal) {\n      mgr.GetPlayer().AddOrbitDisableSource(mgr, GetUniqueId());\n    } else {\n      mgr.GetPlayer().RemoveOrbitDisableSource(GetUniqueId());\n    }\n  }\n}\n\nvoid CScriptSpecialFunction::ThinkActorScale(float dt, CStateManager& mgr) {\n  const float deltaScale = dt * xfc_float1;\n\n  for (const SConnection& conn : x20_conns) {\n    if (conn.x0_state != EScriptObjectState::Play || conn.x4_msg != EScriptObjectMessage::Activate) {\n      continue;\n    }\n\n    if (const TCastToPtr<CActor> act = mgr.ObjectById(mgr.GetIdForScript(conn.x8_objId))) {\n      CModelData* mData = act->GetModelData();\n      if (mData && (mData->HasAnimData() || mData->HasNormalModel())) {\n        zeus::CVector3f scale = mData->GetScale();\n\n        if (deltaScale > 0.f) {\n          scale = zeus::min(zeus::CVector3f(deltaScale) + scale, zeus::CVector3f(x100_float2));\n        } else {\n          scale = zeus::max(zeus::CVector3f(deltaScale) + scale, zeus::CVector3f(x100_float2));\n        }\n\n        mData->SetScale(scale);\n      }\n    }\n  }\n}\n\nvoid CScriptSpecialFunction::ThinkSaveStation(float, CStateManager& mgr) {\n  if (!x1e5_24_doSave || mgr.GetDeferredStateTransition() == EStateManagerTransition::SaveGame) {\n    return;\n  }\n\n  x1e5_24_doSave = false;\n  if (mgr.GetInSaveUI()) {\n    SendScriptMsgs(EScriptObjectState::MaxReached, mgr, EScriptObjectMessage::None);\n  } else {\n    SendScriptMsgs(EScriptObjectState::Zero, mgr, EScriptObjectMessage::None);\n  }\n}\n\nvoid CScriptSpecialFunction::ThinkRainSimulator(float, CStateManager& mgr) {\n  if ((static_cast<float>(mgr.GetInputFrameIdx() % 3600)) / 3600.f < 0.5f) {\n    SendScriptMsgs(EScriptObjectState::MaxReached, mgr, EScriptObjectMessage::None);\n  } else {\n    SendScriptMsgs(EScriptObjectState::Zero, mgr, EScriptObjectMessage::None);\n  }\n}\n\nvoid CScriptSpecialFunction::ThinkAreaDamage(float dt, CStateManager& mgr) {\n  const auto& playerState = mgr.GetPlayerState();\n  CPlayer& player = mgr.GetPlayer();\n\n  /* The following check is a URDE addition */\n  if (!playerState->CanTakeDamage()) {\n    /* Make sure we're not currently set to take damage, if so reset our state to be as if we're not */\n    if (x1e4_31_inAreaDamage) {\n      x1e4_31_inAreaDamage = false;\n      player.DecrementEnvironmentDamage();\n      SendScriptMsgs(EScriptObjectState::Exited, mgr, EScriptObjectMessage::None);\n      mgr.SetIsFullThreat(false);\n    }\n    return;\n  }\n  /* End URDE Addition */\n\n  if (!x1e4_31_inAreaDamage) {\n    if (mgr.GetPlayer().GetAreaIdAlways() != GetAreaIdAlways() ||\n        playerState->GetCurrentSuitRaw() != CPlayerState::EPlayerSuit::Power) {\n      return;\n    }\n    x1e4_31_inAreaDamage = true;\n    player.IncrementEnvironmentDamage();\n    SendScriptMsgs(EScriptObjectState::Entered, mgr, EScriptObjectMessage::None);\n    mgr.SetIsFullThreat(true);\n  } else if (mgr.GetPlayer().GetAreaIdAlways() != GetAreaIdAlways() ||\n             playerState->GetCurrentSuitRaw() != CPlayerState::EPlayerSuit::Power) {\n    x1e4_31_inAreaDamage = false;\n    player.DecrementEnvironmentDamage();\n    SendScriptMsgs(EScriptObjectState::Exited, mgr, EScriptObjectMessage::None);\n    mgr.SetIsFullThreat(false);\n    return;\n  }\n\n  CDamageInfo dInfo(CWeaponMode(EWeaponType::Heat), xfc_float1 * dt, 0.f, 0.f);\n  dInfo.SetNoImmunity(true);\n  mgr.ApplyDamage(GetUniqueId(), player.GetUniqueId(), GetUniqueId(), dInfo,\n                  CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), {});\n}\n\nvoid CScriptSpecialFunction::ThinkPlayerInArea(float dt, CStateManager& mgr) {\n  if (mgr.GetPlayer().GetAreaIdAlways() == GetAreaIdAlways()) {\n    if (x1e5_25_playerInArea) {\n      return;\n    }\n\n    x1e5_25_playerInArea = true;\n    SendScriptMsgs(EScriptObjectState::Entered, mgr, EScriptObjectMessage::None);\n  } else if (x1e5_25_playerInArea) {\n    x1e5_25_playerInArea = false;\n    SendScriptMsgs(EScriptObjectState::Exited, mgr, EScriptObjectMessage::None);\n  }\n}\n\nbool CScriptSpecialFunction::ShouldSkipCinematic(CStateManager& stateMgr) const {\n  if (m_canSkipCutscenes) {\n    return true;\n  }\n  return g_GameState->SystemOptions().GetCinematicState(stateMgr.GetWorld()->IGetWorldAssetId(), GetEditorId());\n}\n\nvoid CScriptSpecialFunction::DeleteEmitter(CSfxHandle& handle) {\n  if (!handle) {\n    return;\n  }\n\n  CSfxManager::RemoveEmitter(handle);\n  handle = CSfxHandle();\n}\n\nu32 CScriptSpecialFunction::ClassifyEnding(const CStateManager& mgr) const {\n  const int rate = (mgr.GetPlayerState()->CalculateItemCollectionRate() * 100) / mgr.GetPlayerState()->GetPickupTotal();\n  int result;\n  if (rate < 75) {\n    result = 0;\n  } else {\n    result = 2;\n    if (rate < 100) {\n      result = 1;\n    }\n  }\n  return result;\n}\n\nvoid CScriptSpecialFunction::AddOrUpdateEmitter(float pitch, CSfxHandle& handle, u16 id, const zeus::CVector3f& pos,\n                                                float vol) {\n  if (!handle) {\n    handle = CSfxManager::AddEmitter(id, pos, zeus::skZero3f, vol, true, true, 0x7f, kInvalidAreaId);\n  } else {\n    CSfxManager::UpdateEmitter(handle, pos, zeus::skZero3f, vol);\n    CSfxManager::PitchBend(handle, 8192.f * pitch + 8192.f);\n  }\n}\n\nCScriptSpecialFunction::SRingController::SRingController(TUniqueId uid, float rotateSpeed, bool reachedTarget)\n: x0_id(uid), x4_rotateSpeed(rotateSpeed), x8_reachedTarget(reachedTarget) {}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptSpecialFunction.hpp",
    "content": "#pragma once\n\n#include <optional>\n#include <string>\n#include <vector>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/World/CActor.hpp\"\n#include \"Runtime/World/CDamageInfo.hpp\"\n\n#include \"Runtime/ConsoleVariables/CVar.hpp\"\n\n#include <zeus/CColor.hpp>\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CScriptSpecialFunction : public CActor {\npublic:\n  enum class ESpecialFunction {\n    What = 0,\n    PlayerFollowLocator = 1,\n    SpinnerController = 2,\n    ObjectFollowLocator = 3,\n    ChaffTarget = 4,\n    InventoryActivator = 5,\n    MapStation = 6,\n    SaveStation = 7,\n    IntroBossRingController = 8,\n    ViewFrustumTester = 9,\n    ShotSpinnerController = 10,\n    EscapeSequence = 11,\n    BossEnergyBar = 12,\n    EndGame = 13,\n    HUDFadeIn = 14,\n    CinematicSkip = 15,\n    ScriptLayerController = 16,\n    RainSimulator = 17,\n    AreaDamage = 18,\n    ObjectFollowObject = 19,\n    RedundantHintSystem = 20,\n    DropBomb = 21,\n    ScaleActor = 22,\n    MissileStation = 23,\n    Billboard = 24,\n    PlayerInAreaRelay = 25,\n    HUDTarget = 26,\n    FogFader = 27,\n    EnterLogbook = 28,\n    PowerBombStation = 29,\n    Ending = 30,\n    FusionRelay = 31,\n    WeaponSwitch = 32,\n    FogVolume = 47,\n    RadialDamage = 48,\n    EnvFxDensityController = 49,\n    RumbleEffect = 50\n  };\n\n  enum class ESpinnerControllerMode {\n    Zero,\n    One,\n  };\n\n  enum class ERingState { Scramble, Rotate, Stopped, Breakup };\n\n  struct SRingController {\n    TUniqueId x0_id;\n    float x4_rotateSpeed;\n    bool x8_reachedTarget;\n    zeus::CVector3f xc_;\n    SRingController(TUniqueId uid, float rotateSpeed, bool reachedTarget);\n  };\n\nprivate:\n  ESpecialFunction xe8_function;\n  std::string xec_locatorName;\n  float xfc_float1;\n  float x100_float2;\n  float x104_float3;\n  float x108_float4;\n  zeus::CVector3f x10c_vector3f;\n  zeus::CColor x118_color;\n  CDamageInfo x11c_damageInfo;\n  float x138_ = 0.f;\n  zeus::CTransform x13c_spinnerInitialXf = zeus::CTransform();\n  float x16c_ = 0.f;\n  u16 x170_sfx1;\n  u16 x172_sfx2;\n  u16 x174_sfx3;\n  CSfxHandle x178_sfxHandle;\n  u32 x17c_;\n  float x180_ = 0.f;\n  TReservedAverage<float, 6> x184_;\n  float x194_ = 0.f;\n  std::vector<SRingController> x198_ringControllers;\n  ERingState x1a8_ringState = ERingState::Stopped;\n  zeus::CVector3f x1ac_ringRotateTarget = zeus::skZero3f;\n  bool x1b8_ringReverse = true;\n  s32 x1bc_areaSaveId;\n  s32 x1c0_layerIdx;\n  CPlayerState::EItemType x1c4_item;\n  std::optional<zeus::CAABox> x1c8_touchBounds;\n  bool x1e4_24_spinnerInitializedXf : 1 = false;\n  bool x1e4_25_spinnerCanMove : 1 = false;\n  bool x1e4_26_sfx2Played : 1 = true;\n  bool x1e4_27_sfx3Played : 1 = false;\n  bool x1e4_28_frustumEntered : 1 = false;\n  bool x1e4_29_frustumExited : 1 = false;\n  bool x1e4_30_ : 1 = false;\n  bool x1e4_31_inAreaDamage : 1 = false;\n  bool x1e5_24_doSave : 1 = false;\n  bool x1e5_25_playerInArea : 1 = false;\n  bool x1e5_26_displayBillboard : 1 = false;\n  TLockedToken<CTexture> x1e8_; // Used to be optional\n  std::optional<CVarValueReference<bool>> m_cvRef;\n  bool m_canSkipCutscenes = false;\n\npublic:\n  DEFINE_ENTITY\n  CScriptSpecialFunction(TUniqueId, std::string_view, const CEntityInfo&, const zeus::CTransform&, ESpecialFunction,\n                         std::string_view, float, float, float, float, const zeus::CVector3f&, const zeus::CColor&,\n                         bool, const CDamageInfo&, s32, s32, CPlayerState::EItemType, s16, s16, s16);\n\n  void Accept(IVisitor& visitor) override;\n  void Think(float, CStateManager&) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void PreRender(CStateManager&, const zeus::CFrustum&) override;\n  void AddToRenderer(const zeus::CFrustum&, CStateManager&) override;\n  void Render(CStateManager&) override;\n  std::optional<zeus::CAABox> GetTouchBounds() const override { return x1c8_touchBounds; }\n\n  void SkipCinematic(CStateManager&);\n  void RingScramble(CStateManager&);\n  void ThinkIntroBossRingController(float, CStateManager&);\n  void ThinkPlayerFollowLocator(float, CStateManager&);\n  void ThinkSpinnerController(float, CStateManager&, ESpinnerControllerMode);\n  void ThinkObjectFollowLocator(float, CStateManager&);\n  void ThinkObjectFollowObject(float, CStateManager&);\n  void ThinkChaffTarget(float, CStateManager&);\n  void ThinkActorScale(float, CStateManager&);\n  void ThinkSaveStation(float, CStateManager&);\n  void ThinkRainSimulator(float, CStateManager&);\n  void ThinkAreaDamage(float, CStateManager&);\n  void ThinkPlayerInArea(float, CStateManager&);\n\n  bool ShouldSkipCinematic(CStateManager& stateMgr) const;\n\n  void DeleteEmitter(CSfxHandle& handle);\n  u32 ClassifyEnding(const CStateManager& mgr) const;\n  void AddOrUpdateEmitter(float pitch, CSfxHandle& handle, u16 id, const zeus::CVector3f& pos, float vol);\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptSpiderBallAttractionSurface.cpp",
    "content": "#include \"Runtime/World/CScriptSpiderBallAttractionSurface.hpp\"\n\n#include \"Runtime/World/CActorParameters.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCScriptSpiderBallAttractionSurface::CScriptSpiderBallAttractionSurface(TUniqueId uid, std::string_view name,\n                                                                       const CEntityInfo& info,\n                                                                       const zeus::CTransform& xf,\n                                                                       const zeus::CVector3f& scale, bool active)\n: CActor(uid, active, name, info, xf, CModelData::CModelDataNull(), {EMaterialTypes::NoStepLogic},\n         CActorParameters::None(), kInvalidUniqueId)\n, xe8_scale(scale)\n, xf4_aabb(zeus::CAABox(scale * -0.5f, scale * 0.5f).getTransformedAABox(xf.getRotation())) {}\n\nvoid CScriptSpiderBallAttractionSurface::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptSpiderBallAttractionSurface::Think(float dt, CStateManager& mgr) {\n  // Empty\n}\n\nvoid CScriptSpiderBallAttractionSurface::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender,\n                                                         CStateManager& mgr) {\n  CActor::AcceptScriptMsg(msg, sender, mgr);\n}\n\nstd::optional<zeus::CAABox> CScriptSpiderBallAttractionSurface::GetTouchBounds() const {\n  if (GetActive()) {\n    return zeus::CAABox(xf4_aabb.min + GetTranslation(), xf4_aabb.max + GetTranslation());\n  }\n  return std::nullopt;\n}\n\nvoid CScriptSpiderBallAttractionSurface::Touch(CActor& actor, CStateManager& mgr) {\n  // Empty\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptSpiderBallAttractionSurface.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/World/CActor.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\n\nclass CScriptSpiderBallAttractionSurface : public CActor {\n  zeus::CVector3f xe8_scale;\n  zeus::CAABox xf4_aabb;\n\npublic:\n  DEFINE_ENTITY\n  CScriptSpiderBallAttractionSurface(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                                     const zeus::CTransform& xf, const zeus::CVector3f& scale, bool active);\n  void Accept(IVisitor& visitor) override;\n  void Think(float dt, CStateManager& mgr) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) override;\n  std::optional<zeus::CAABox> GetTouchBounds() const override;\n  void Touch(CActor& actor, CStateManager& mgr) override;\n  const zeus::CVector3f& GetScale() const { return xe8_scale; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptSpiderBallWaypoint.cpp",
    "content": "#include \"Runtime/World/CScriptSpiderBallWaypoint.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nCScriptSpiderBallWaypoint::CScriptSpiderBallWaypoint(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                                                     const zeus::CTransform& xf, bool active, u32 w1)\n: CActor(uid, active, name, info, xf, CModelData::CModelDataNull(), CMaterialList(EMaterialTypes::NoStepLogic),\n         CActorParameters::None(), kInvalidUniqueId)\n, xe8_(w1) {}\n\nvoid CScriptSpiderBallWaypoint::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptSpiderBallWaypoint::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  CActor::AcceptScriptMsg(msg, uid, mgr);\n\n  if (msg == EScriptObjectMessage::InitializedInArea) {\n    BuildWaypointListAndBounds(mgr);\n  } else if (msg == EScriptObjectMessage::Arrived) {\n    SendScriptMsgs(EScriptObjectState::Arrived, mgr, EScriptObjectMessage::None);\n  }\n}\n\nvoid CScriptSpiderBallWaypoint::AccumulateBounds(const zeus::CVector3f& v) {\n  if (!xfc_aabox) {\n    xfc_aabox.emplace(v, v);\n  }\n  xfc_aabox->accumulateBounds(v);\n}\n\nvoid CScriptSpiderBallWaypoint::BuildWaypointListAndBounds(CStateManager& mgr) {\n  u32 validConnections = 0;\n  for (const SConnection& conn : x20_conns) {\n    if (conn.x0_state == EScriptObjectState::Arrived && conn.x4_msg == EScriptObjectMessage::Next) {\n      const TUniqueId uid = mgr.GetIdForScript(conn.x8_objId);\n      if (uid != kInvalidUniqueId) {\n        static_cast<CScriptSpiderBallWaypoint*>(mgr.ObjectById(uid))->AddPreviousWaypoint(GetUniqueId());\n        ++validConnections;\n      }\n    }\n  }\n\n  if (validConnections == 0) {\n    AccumulateBounds(x34_transform.origin);\n  } else {\n    CScriptSpiderBallWaypoint* curWaypoint = this;\n    TUniqueId uid = curWaypoint->NextWaypoint(mgr, ECheckActiveWaypoint::SkipCheck);\n    while (uid != kInvalidUniqueId) {\n      curWaypoint = static_cast<CScriptSpiderBallWaypoint*>(mgr.ObjectById(uid));\n      uid = curWaypoint->NextWaypoint(mgr, ECheckActiveWaypoint::SkipCheck);\n    }\n\n    curWaypoint->AccumulateBounds(x34_transform.origin);\n  }\n}\n\nvoid CScriptSpiderBallWaypoint::AddPreviousWaypoint(TUniqueId uid) { xec_waypoints.push_back(uid); }\n\nTUniqueId CScriptSpiderBallWaypoint::PreviousWaypoint(const CStateManager& mgr,\n                                                      ECheckActiveWaypoint checkActive) const {\n  for (const auto& id : xec_waypoints) {\n    if (const CEntity* ent = mgr.GetObjectById(id)) {\n      if (checkActive == ECheckActiveWaypoint::SkipCheck) {\n        return id;\n      }\n      if (ent->GetActive()) {\n        return id;\n      }\n    }\n  }\n\n  return kInvalidUniqueId;\n}\n\nTUniqueId CScriptSpiderBallWaypoint::NextWaypoint(const CStateManager& mgr, ECheckActiveWaypoint checkActive) const {\n  for (const SConnection& conn : x20_conns) {\n    if (conn.x0_state != EScriptObjectState::Arrived || conn.x4_msg != EScriptObjectMessage::Next) {\n      continue;\n    }\n    const TUniqueId uid = mgr.GetIdForScript(conn.x8_objId);\n    if (const CEntity* ent = mgr.GetObjectById(uid)) {\n      if (checkActive == ECheckActiveWaypoint::SkipCheck) {\n        return ent->GetUniqueId();\n      }\n      if (ent->GetActive()) {\n        return ent->GetUniqueId();\n      }\n    }\n  }\n\n  return kInvalidUniqueId;\n}\n\nvoid CScriptSpiderBallWaypoint::GetClosestPointAlongWaypoints(CStateManager& mgr, const zeus::CVector3f& ballPos,\n                                                              float maxPointToBallDist,\n                                                              const CScriptSpiderBallWaypoint*& closestWaypoint,\n                                                              zeus::CVector3f& closestPoint,\n                                                              zeus::CVector3f& deltaBetweenPoints,\n                                                              float deltaBetweenInterpDist,\n                                                              zeus::CVector3f& interpDeltaBetweenPoints) const {\n  const auto* wp = this;\n  while (wp->PreviousWaypoint(mgr, ECheckActiveWaypoint::SkipCheck) != kInvalidUniqueId) {\n    wp = static_cast<const CScriptSpiderBallWaypoint*>(\n        mgr.GetObjectById(wp->PreviousWaypoint(mgr, ECheckActiveWaypoint::SkipCheck)));\n  }\n\n  float minPointToBallDistSq = maxPointToBallDist * maxPointToBallDist;\n  const float deltaBetweenInterpDistSq = deltaBetweenInterpDist * deltaBetweenInterpDist;\n  zeus::CVector3f lastPoint = wp->GetTranslation();\n  zeus::CVector3f lastDelta;\n  bool computeDelta = wp->GetActive();\n\n  while (true) {\n    if (wp->NextWaypoint(mgr, ECheckActiveWaypoint::Check) != kInvalidUniqueId) {\n      if (computeDelta) {\n        const auto* prevWp = wp;\n        wp = static_cast<const CScriptSpiderBallWaypoint*>(\n            mgr.GetObjectById(wp->NextWaypoint(mgr, ECheckActiveWaypoint::Check)));\n\n        const zeus::CVector3f thisDelta = wp->GetTranslation() - lastPoint;\n        const zeus::CVector3f lastPointToBall = ballPos - lastPoint;\n        if (prevWp->PreviousWaypoint(mgr, ECheckActiveWaypoint::Check) == kInvalidUniqueId) {\n          lastDelta = thisDelta;\n        }\n\n        const float pointToBallDistSq = lastPointToBall.magSquared();\n        if (pointToBallDistSq < minPointToBallDistSq) {\n          minPointToBallDistSq = pointToBallDistSq;\n          closestPoint = lastPoint;\n          deltaBetweenPoints = thisDelta;\n          interpDeltaBetweenPoints = (thisDelta.normalized() + lastDelta.normalized()) * 0.5f;\n          closestWaypoint = wp;\n        }\n\n        const float projectedT = lastPointToBall.dot(thisDelta);\n        if (projectedT >= 0.f) {\n          const float normT = projectedT / thisDelta.magSquared();\n          if (normT < 1.f) {\n            const zeus::CVector3f projectedPoint = zeus::CVector3f::lerp(lastPoint, wp->GetTranslation(), normT);\n            const float projToBallDistSq = (ballPos - projectedPoint).magSquared();\n            if (projToBallDistSq < minPointToBallDistSq) {\n              minPointToBallDistSq = projToBallDistSq;\n              closestPoint = projectedPoint;\n              deltaBetweenPoints = thisDelta;\n              interpDeltaBetweenPoints = deltaBetweenPoints;\n              closestWaypoint = wp;\n              float lastToProjDist = (lastPoint - projectedPoint).magnitude();\n              if (lastToProjDist < deltaBetweenInterpDistSq) {\n                interpDeltaBetweenPoints =\n                    zeus::CVector3f::lerp(0.5f * (thisDelta.normalized() + lastDelta.normalized()),\n                                          thisDelta.normalized(), lastToProjDist / deltaBetweenInterpDist);\n              } else if (wp->NextWaypoint(mgr, ECheckActiveWaypoint::Check) != kInvalidUniqueId) {\n                lastToProjDist = (projectedPoint - wp->GetTranslation()).magnitude();\n                if (lastToProjDist < deltaBetweenInterpDist) {\n                  const float t = lastToProjDist / deltaBetweenInterpDist;\n                  interpDeltaBetweenPoints =\n                      zeus::CVector3f::lerp(((static_cast<const CScriptSpiderBallWaypoint*>(\n                                                  mgr.GetObjectById(wp->NextWaypoint(mgr, ECheckActiveWaypoint::Check)))\n                                                  ->GetTranslation() -\n                                              wp->GetTranslation())\n                                                 .normalized() +\n                                             thisDelta.normalized()) *\n                                                0.5f,\n                                            thisDelta.normalized(), t);\n                }\n              }\n            }\n          }\n        }\n        lastDelta = thisDelta;\n        lastPoint = wp->GetTranslation();\n        computeDelta = true;\n      } else {\n        wp = static_cast<const CScriptSpiderBallWaypoint*>(\n            mgr.GetObjectById(wp->NextWaypoint(mgr, ECheckActiveWaypoint::Check)));\n        lastPoint = wp->GetTranslation();\n        computeDelta = true;\n      }\n    } else {\n      if (wp->NextWaypoint(mgr, ECheckActiveWaypoint::SkipCheck) != kInvalidUniqueId) {\n        wp = static_cast<const CScriptSpiderBallWaypoint*>(\n            mgr.GetObjectById(wp->NextWaypoint(mgr, ECheckActiveWaypoint::SkipCheck)));\n        computeDelta = false;\n      } else {\n        break;\n      }\n    }\n  }\n\n  if ((ballPos - lastPoint).magSquared() >= minPointToBallDistSq) {\n    return;\n  }\n\n  closestPoint = lastPoint;\n  if (wp->PreviousWaypoint(mgr, ECheckActiveWaypoint::Check) != kInvalidUniqueId) {\n    wp = static_cast<const CScriptSpiderBallWaypoint*>(\n        mgr.GetObjectById(wp->PreviousWaypoint(mgr, ECheckActiveWaypoint::Check)));\n    deltaBetweenPoints = lastPoint - wp->GetTranslation();\n    interpDeltaBetweenPoints = deltaBetweenPoints;\n  }\n  closestWaypoint = wp;\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptSpiderBallWaypoint.hpp",
    "content": "#pragma once\n\n#include <optional>\n#include <vector>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/World/CActor.hpp\"\n\n#include <zeus/CAABox.hpp>\n\nnamespace metaforce {\nclass CScriptSpiderBallWaypoint : public CActor {\n  enum class ECheckActiveWaypoint { Check, SkipCheck };\n  u32 xe8_;\n  std::vector<TUniqueId> xec_waypoints;\n  std::optional<zeus::CAABox> xfc_aabox;\n\npublic:\n  DEFINE_ENTITY\n  CScriptSpiderBallWaypoint(TUniqueId, std::string_view, const CEntityInfo&, const zeus::CTransform&, bool, u32);\n  void Accept(IVisitor&) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void Render(CStateManager& mgr) override { CActor::Render(mgr); }\n  void AddToRenderer(const zeus::CFrustum&, CStateManager&) override {}\n  std::optional<zeus::CAABox> GetTouchBounds() const override { return xfc_aabox; }\n  void AccumulateBounds(const zeus::CVector3f& v);\n  void BuildWaypointListAndBounds(CStateManager& mgr);\n  void AddPreviousWaypoint(TUniqueId uid);\n  TUniqueId PreviousWaypoint(const CStateManager& mgr, ECheckActiveWaypoint checkActive) const;\n  TUniqueId NextWaypoint(const CStateManager& mgr, ECheckActiveWaypoint checkActive) const;\n  void GetClosestPointAlongWaypoints(CStateManager& mgr, const zeus::CVector3f& ballPos, float maxPointToBallDist,\n                                     const CScriptSpiderBallWaypoint*& closestWaypoint, zeus::CVector3f& closestPoint,\n                                     zeus::CVector3f& deltaBetweenPoints, float deltaBetweenInterpDist,\n                                     zeus::CVector3f& interpDeltaBetweenPoints) const;\n  void ClearWaypoints() {\n    xfc_aabox.reset();\n    xec_waypoints.clear();\n  }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptSpindleCamera.cpp",
    "content": "#include \"Runtime/World/CScriptSpindleCamera.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Camera/CBallCamera.hpp\"\n#include \"Runtime/Camera/CCameraManager.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CScriptCameraHint.hpp\"\n#include \"Runtime/World/ScriptLoader.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nSSpindleProperty::SSpindleProperty(CInputStream& in) {\n  x4_input = ESpindleInput(in.ReadLong());\n  x0_flags = ScriptLoader::LoadParameterFlags(in);\n  x8_lowOut = in.ReadFloat();\n  xc_highOut = in.ReadFloat();\n  x10_lowIn = in.ReadFloat();\n  x14_highIn = in.ReadFloat();\n  switch (x4_input) {\n  case ESpindleInput::HintBallAngle:\n  case ESpindleInput::HintBallRightAngle:\n  case ESpindleInput::HintBallLeftAngle:\n    x10_lowIn = zeus::degToRad(x10_lowIn);\n    x14_highIn = zeus::degToRad(x14_highIn);\n    break;\n  default:\n    break;\n  }\n}\n\nCScriptSpindleCamera::CScriptSpindleCamera(\n    TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf, bool active, u32 flags,\n    float hintToCamDistMin, float hintToCamDistMax, float hintToCamVOffMin, float hintToCamVOffMax,\n    const SSpindleProperty& targetHintToCamDeltaAngleVel, const SSpindleProperty& deltaAngleScaleWithCamDist,\n    const SSpindleProperty& hintToCamDist, const SSpindleProperty& distOffsetFromBallDist,\n    const SSpindleProperty& hintBallToCamAzimuth, const SSpindleProperty& unused,\n    const SSpindleProperty& maxHintBallToCamAzimuth, const SSpindleProperty& camLookRelAzimuth,\n    const SSpindleProperty& lookPosZOffset, const SSpindleProperty& camPosZOffset,\n    const SSpindleProperty& clampedAzimuthFromHintDir, const SSpindleProperty& dampingAzimuthSpeed,\n    const SSpindleProperty& targetHintToCamDeltaAngleVelRange, const SSpindleProperty& deleteHintBallDist,\n    const SSpindleProperty& recoverClampedAzimuthFromHintDir)\n: CGameCamera(uid, active, name, info, xf, CCameraManager::ThirdPersonFOV(), CCameraManager::NearPlane(),\n              CCameraManager::FarPlane(), CCameraManager::Aspect(), kInvalidUniqueId, false, 0)\n, x188_flags(flags)\n, x1b0_hintToCamDistMin(hintToCamDistMin)\n, x1b4_hintToCamDistMax(hintToCamDistMax)\n, x1b8_hintToCamVOffMin(hintToCamVOffMin)\n, x1bc_hintToCamVOffMax(hintToCamVOffMax)\n, x1c0_targetHintToCamDeltaAngleVel(targetHintToCamDeltaAngleVel)\n, x1d8_deltaAngleScaleWithCamDist(deltaAngleScaleWithCamDist)\n, x1f0_hintToCamDist(hintToCamDist)\n, x208_distOffsetFromBallDist(distOffsetFromBallDist)\n, x220_hintBallToCamAzimuth(hintBallToCamAzimuth)\n, x238_unused(unused)\n, x250_maxHintBallToCamAzimuth(maxHintBallToCamAzimuth)\n, x268_camLookRelAzimuth(camLookRelAzimuth)\n, x280_lookPosZOffset(lookPosZOffset)\n, x298_camPosZOffset(camPosZOffset)\n, x2b0_clampedAzimuthFromHintDir(clampedAzimuthFromHintDir)\n, x2c8_dampingAzimuthSpeed(dampingAzimuthSpeed)\n, x2e0_targetHintToCamDeltaAngleVelRange(targetHintToCamDeltaAngleVelRange)\n, x2f8_deleteHintBallDist(deleteHintBallDist)\n, x310_recoverClampedAzimuthFromHintDir(recoverClampedAzimuthFromHintDir)\n, x330_lookDir(xf.basis[1]) {}\n\nvoid CScriptSpindleCamera::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptSpindleCamera::ProcessInput(const CFinalInput& input, CStateManager& mgr) {\n  // Empty\n}\n\nvoid CScriptSpindleCamera::Reset(const zeus::CTransform& xf, CStateManager& mgr) {\n  const CScriptCameraHint* hint = mgr.GetCameraManager()->GetCameraHint(mgr);\n  if (!GetActive() || hint == nullptr) {\n    return;\n  }\n\n  x33c_24_inResetThink = true;\n  mgr.GetCameraManager()->GetBallCamera()->UpdateLookAtPosition(0.01f, mgr);\n  Think(0.01f, mgr);\n  x33c_24_inResetThink = false;\n}\n\nvoid CScriptSpindleCamera::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) {\n  CGameCamera::AcceptScriptMsg(msg, sender, mgr);\n}\n\nfloat SSpindleProperty::GetValue(float inVar) const {\n  if (x4_input == ESpindleInput::Constant) {\n    return x8_lowOut;\n  }\n\n  const float reflectRange = x14_highIn - x10_lowIn;\n  if (zeus::close_enough(reflectRange, 0.f)) {\n    return x8_lowOut;\n  }\n\n  float reflectedVar = inVar;\n  if (x0_flags & 0x1 && inVar > x14_highIn) {\n    reflectedVar = x14_highIn - (inVar - x14_highIn);\n  }\n  if (x0_flags & 0x2 && inVar < x10_lowIn) {\n    reflectedVar = x10_lowIn + (x10_lowIn - inVar);\n  }\n\n  const float outRange = xc_highOut - x8_lowOut;\n  const float res = (reflectedVar - x10_lowIn) * outRange / reflectRange + x8_lowOut;\n  if (x8_lowOut < xc_highOut) {\n    return zeus::clamp(x8_lowOut, res, xc_highOut);\n  } else {\n    return zeus::clamp(xc_highOut, res, x8_lowOut);\n  }\n}\n\nvoid CScriptSpindleCamera::Think(float dt, CStateManager& mgr) {\n  const CScriptCameraHint* hint = mgr.GetCameraManager()->GetCameraHint(mgr);\n  if (!GetActive() || hint == nullptr) {\n    return;\n  }\n\n  zeus::CVector3f hintPos = hint->GetTranslation();\n  zeus::CVector2f lookAheadPos = mgr.GetCameraManager()->GetBallCamera()->GetLookPosAhead().toVec2f();\n  zeus::CVector3f hintToCamDir = GetTranslation() - hintPos;\n  hintToCamDir.z() = 0.f;\n  const zeus::CVector3f ballPos = mgr.GetPlayer().GetBallPosition();\n  zeus::CVector3f hintToBallDir = ballPos - hintPos;\n  const float hintToBallVOff = hintToBallDir.z();\n  hintToBallDir.z() = 0.f;\n\n  zeus::CVector3f hintDir = hint->GetTransform().basis[1];\n  hintDir.z() = 0.f;\n  if (hintDir.canBeNormalized()) {\n    hintDir.normalize();\n  } else {\n    hintDir = zeus::skForward;\n  }\n\n  float hintToBallDist = 0.f;\n  if (hintToBallDir.canBeNormalized()) {\n    hintToBallDist = hintToBallDir.magnitude();\n    hintToBallDir.normalize();\n  } else {\n    hintToBallDir = hintDir;\n  }\n\n  x18c_inVars.clear();\n  x18c_inVars.push_back(0.f);                       // Zero\n  x18c_inVars.push_back(hintToBallDist);            // HintToBallDist\n  x18c_inVars.push_back(std::fabs(hintToBallVOff)); // HintToBallVOff\n  const float hintBallAngle = std::fabs(std::acos(zeus::clamp(-1.f, hintToBallDir.dot(hintDir), 1.f)));\n  x18c_inVars.push_back(hintBallAngle); // HintBallAngle\n  const float hintBallCross = hintToBallDir.toVec2f().cross(hintDir.toVec2f());\n  if (hintBallCross >= 0.f) {\n    x18c_inVars.push_back(hintBallAngle);               // HintBallRightAngle\n    x18c_inVars.push_back(2.f * M_PIF - hintBallAngle); // HintBallLeftAngle\n  } else {\n    x18c_inVars.push_back(2.f * M_PIF - hintBallAngle); // HintBallRightAngle\n    x18c_inVars.push_back(hintBallAngle);               // HintBallLeftAngle\n  }\n  zeus::CVector3f hintDelta = hint->GetTranslation() - hint->GetOriginalTransform().origin;\n  const float hintDeltaVOff = std::fabs(hintDelta.z());\n  hintDelta.z() = 0.f;\n  x18c_inVars.push_back(hintDelta.canBeNormalized() ? hintDelta.magnitude() : 0.f); // HintDeltaDist\n  x18c_inVars.push_back(hintDeltaVOff);                                             // HintDeltaVOff\n\n  if ((x188_flags & 0x2000) && hintToBallDist > x2f8_deleteHintBallDist.GetValue(GetInVar(x2f8_deleteHintBallDist))) {\n\n    if (hint->GetDelegatedCamera() == GetUniqueId()) {\n      mgr.GetCameraManager()->DeleteCameraHint(hint->GetUniqueId(), mgr);\n    }\n  } else {\n    if ((x188_flags & 0x800) == 0) {\n      hintToBallDir = hintDir;\n    }\n    if ((x188_flags & 0x20) != 0) {\n      if (!x32c_outsideClampedAzimuth) {\n        if (hintBallAngle > x2b0_clampedAzimuthFromHintDir.GetValue(GetInVar(x2b0_clampedAzimuthFromHintDir))) {\n          x330_lookDir = hintToBallDir;\n          x32c_outsideClampedAzimuth = true;\n        }\n      } else {\n        const float hintCamCross = hintToCamDir.toVec2f().cross(hintDir.toVec2f());\n        if ((hintBallAngle <\n                 x310_recoverClampedAzimuthFromHintDir.GetValue(GetInVar(x310_recoverClampedAzimuthFromHintDir)) &&\n             hintBallCross * hintCamCross < 0.f) ||\n            hintBallAngle <= x2b0_clampedAzimuthFromHintDir.GetValue(GetInVar(x2b0_clampedAzimuthFromHintDir))) {\n          x32c_outsideClampedAzimuth = false;\n        } else {\n          hintToBallDir = x330_lookDir;\n        }\n      }\n    }\n\n    float newHintToCamDist = x1f0_hintToCamDist.GetValue(GetInVar(x1f0_hintToCamDist));\n    if ((x188_flags & 0x40) != 0) {\n      newHintToCamDist = hintToBallDist + x208_distOffsetFromBallDist.GetValue(GetInVar(x208_distOffsetFromBallDist));\n    }\n    newHintToCamDist = zeus::clamp(x1b0_hintToCamDistMin, newHintToCamDist, x1b4_hintToCamDistMax);\n\n    zeus::CVector3f newCamPos = GetTranslation();\n    float hintToCamDist = hintToCamDir.magnitude();\n    if (hintToCamDir.canBeNormalized()) {\n      hintToCamDir.normalize();\n    } else {\n      hintToCamDir = hintDir;\n      hintToCamDist = x1f0_hintToCamDist.GetValue(GetInVar(x1f0_hintToCamDist));\n    }\n\n    float hintBallToCamTargetAzimuth = x220_hintBallToCamAzimuth.GetValue(GetInVar(x220_hintBallToCamAzimuth));\n    if ((x188_flags & 0x4000) == 0 && hintToCamDir.cross(hintToBallDir).z() >= 0.f) {\n      hintBallToCamTargetAzimuth = -hintBallToCamTargetAzimuth;\n    }\n\n    zeus::CQuaternion hintBallToCamTargetAzimuthQuat;\n    hintBallToCamTargetAzimuthQuat.rotateZ(hintBallToCamTargetAzimuth);\n    const zeus::CVector3f targetHintToCam = hintBallToCamTargetAzimuthQuat.transform(hintToBallDir);\n    zeus::CVector3f newHintToCamDir = hintToCamDir;\n    const float hintToCamDeltaAngleRange =\n        std::fabs(std::acos(zeus::clamp(-1.f, hintToCamDir.dot(targetHintToCam), 1.f)));\n    const float hintToCamDeltaAngleSpeedFactor = zeus::clamp(\n        -1.f, hintToCamDeltaAngleRange / x2c8_dampingAzimuthSpeed.GetValue(GetInVar(x2c8_dampingAzimuthSpeed)), 1.f);\n    float targetHintToCamDeltaAngleVel =\n        x1c0_targetHintToCamDeltaAngleVel.GetValue(GetInVar(x1c0_targetHintToCamDeltaAngleVel));\n    if ((x188_flags & 0x100) != 0) {\n      targetHintToCamDeltaAngleVel = zeus::clamp(\n          -targetHintToCamDeltaAngleVel,\n          x1d8_deltaAngleScaleWithCamDist.GetValue(GetInVar(x1d8_deltaAngleScaleWithCamDist)) / hintToCamDist,\n          targetHintToCamDeltaAngleVel);\n    }\n    if ((hintToBallDir.cross(hintToCamDir).z() >= 0.f && targetHintToCam.cross(hintToCamDir).z() < 0.f) ||\n        (hintToBallDir.cross(hintToCamDir).z() < 0.f && targetHintToCam.cross(hintToCamDir).z() >= 0.f)) {\n      const float targetHintToCamDeltaAngleVelRange =\n          x2e0_targetHintToCamDeltaAngleVelRange.GetValue(GetInVar(x2e0_targetHintToCamDeltaAngleVelRange));\n      targetHintToCamDeltaAngleVel = zeus::clamp(-targetHintToCamDeltaAngleVelRange, targetHintToCamDeltaAngleVel,\n                                                 targetHintToCamDeltaAngleVelRange);\n    }\n\n    zeus::CVector3f camToBall = ballPos - GetTranslation();\n    camToBall.z() = 0.f;\n    float targetHintToCamDeltaAngle = targetHintToCamDeltaAngleVel * dt * hintToCamDeltaAngleSpeedFactor;\n    float camToBallDist = 0.f;\n    if (camToBall.canBeNormalized()) {\n      camToBallDist = camToBall.magnitude();\n    }\n    targetHintToCamDeltaAngle *= (1.f - zeus::clamp(0.f, (camToBallDist - 2.f) * 0.5f, 1.f)) * 10.f + 1.f;\n    targetHintToCamDeltaAngle =\n        zeus::clamp(-hintToCamDeltaAngleRange, targetHintToCamDeltaAngle, hintToCamDeltaAngleRange);\n    if (std::fabs(zeus::clamp(-1.f, hintToCamDir.dot(targetHintToCam), 1.f)) < 0.99999f) {\n      newHintToCamDir =\n          zeus::CQuaternion::lookAt(hintToCamDir, targetHintToCam, targetHintToCamDeltaAngle).transform(hintToCamDir);\n    }\n    const float hintBallToCamAzimuth = std::acos(zeus::clamp(-1.f, hintToBallDir.dot(newHintToCamDir), 1.f));\n    if ((x188_flags & 0x10) != 0) {\n      if (std::fabs(hintBallToCamAzimuth) < x220_hintBallToCamAzimuth.GetValue(GetInVar(x220_hintBallToCamAzimuth)) ||\n          (x188_flags & 0x8) != 0 || x33c_24_inResetThink) {\n        newHintToCamDir = targetHintToCam;\n      }\n    }\n\n    const float maxHintBallToCamAzimuth = x250_maxHintBallToCamAzimuth.GetValue(GetInVar(x250_maxHintBallToCamAzimuth));\n    if (std::fabs(hintBallToCamAzimuth) > maxHintBallToCamAzimuth) {\n      x328_maxAzimuthInterpTimer += dt;\n      if (x328_maxAzimuthInterpTimer < 3.f) {\n        const float ballToCamAzimuthInterp = zeus::clamp(-1.f, x328_maxAzimuthInterpTimer / 3.f, 1.f);\n        float hintBallToCamAzimuthDelta = std::fabs(maxHintBallToCamAzimuth - hintBallToCamAzimuth);\n        if (hintToBallDir.cross(newHintToCamDir).z() > 0.f) {\n          hintBallToCamAzimuthDelta = -hintBallToCamAzimuthDelta;\n        }\n        zeus::CQuaternion hintBallToCamAzimuthQuat;\n        hintBallToCamAzimuthQuat.rotateZ(hintBallToCamAzimuthDelta * ballToCamAzimuthInterp);\n        newHintToCamDir = hintBallToCamAzimuthQuat.transform(newHintToCamDir);\n      } else {\n        zeus::CQuaternion hintBallToCamAzimuthQuat;\n        if (hintBallToCamTargetAzimuth > 0.f) {\n          hintBallToCamAzimuthQuat.rotateZ(maxHintBallToCamAzimuth);\n        } else {\n          hintBallToCamAzimuthQuat.rotateZ(-maxHintBallToCamAzimuth);\n        }\n        newHintToCamDir = hintBallToCamAzimuthQuat.transform(hintToBallDir);\n      }\n    } else {\n      x328_maxAzimuthInterpTimer = 0.f;\n    }\n\n    if ((x188_flags & 0x20) != 0) {\n      zeus::CVector3f hintDir2 = hint->GetTransform().basis[1];\n      hintDir2.z() = 0.f;\n      if (hintDir2.canBeNormalized()) {\n        hintDir2.normalize();\n        float hintCamAzimuth = std::fabs(std::acos(zeus::clamp(-1.f, hintDir2.dot(newHintToCamDir), 1.f)));\n        const float hintCamAzimuthRange =\n            x2b0_clampedAzimuthFromHintDir.GetValue(GetInVar(x2b0_clampedAzimuthFromHintDir));\n        hintCamAzimuth = zeus::clamp(-hintCamAzimuthRange, hintCamAzimuth, hintCamAzimuthRange);\n        if (hintDir2.cross(newHintToCamDir).z() < 0.f) {\n          hintCamAzimuth = -hintCamAzimuth;\n        }\n        zeus::CQuaternion hintCamAzimuthQuat;\n        hintCamAzimuthQuat.rotateZ(hintCamAzimuth);\n        newHintToCamDir = hintCamAzimuthQuat.transform(hintDir2);\n      }\n    }\n\n    newCamPos = hintPos + newHintToCamDir * newHintToCamDist;\n    if ((x188_flags & 0x80) != 0) {\n      newCamPos.z() = ballPos.z() + x298_camPosZOffset.GetValue(GetInVar(x298_camPosZOffset));\n    } else {\n      newCamPos.z() = hintPos.z() + x298_camPosZOffset.GetValue(GetInVar(x298_camPosZOffset));\n    }\n    newCamPos.z() =\n        zeus::clamp(x1b8_hintToCamVOffMin, newCamPos.z() - hintPos.z(), x1bc_hintToCamVOffMax) + hintPos.z();\n\n    float lookPosZ;\n    if ((x188_flags & 0x200) != 0) {\n      lookPosZ = ballPos.z() + x280_lookPosZOffset.GetValue(GetInVar(x280_lookPosZOffset));\n    } else {\n      lookPosZ = hintPos.z() + x280_lookPosZOffset.GetValue(GetInVar(x280_lookPosZOffset));\n    }\n\n    zeus::CVector3f newLookDelta(lookAheadPos - newCamPos.toVec2f(), lookPosZ - newCamPos.z());\n    zeus::CVector3f newLookDirFlat = newLookDelta;\n    newLookDirFlat.z() = 0.f;\n    if (newLookDirFlat.canBeNormalized()) {\n      const float newLookDistFlat = newLookDirFlat.magnitude();\n      newLookDirFlat.normalize();\n      float camLookRelAzimuth = -x268_camLookRelAzimuth.GetValue(GetInVar(x268_camLookRelAzimuth));\n      zeus::CVector3f newHintToCamDirFlat = newCamPos - hintPos;\n      newHintToCamDirFlat.z() = 0.f;\n      if (newHintToCamDirFlat.canBeNormalized()) {\n        newHintToCamDirFlat.normalize();\n      } else {\n        newHintToCamDirFlat = zeus::skForward;\n      }\n      if (newHintToCamDirFlat.cross(hintToBallDir).z() >= 0.f) {\n        camLookRelAzimuth = -camLookRelAzimuth;\n      }\n      if ((x188_flags & 0x1000) != 0) {\n        camLookRelAzimuth *= zeus::clamp(\n            -1.f,\n            std::acos(std::fabs(zeus::clamp(-1.f, hintToBallDir.dot(newHintToCamDirFlat), 1.f))) / zeus::degToRad(10.f),\n            1.f);\n      }\n      zeus::CQuaternion azimuthQuat;\n      azimuthQuat.rotateZ(camLookRelAzimuth);\n      lookAheadPos = azimuthQuat.transform(newLookDirFlat).toVec2f() * std::cos(camLookRelAzimuth) * newLookDistFlat +\n                     newCamPos.toVec2f();\n    }\n    newLookDelta = zeus::CVector3f(lookAheadPos, lookPosZ) - newCamPos;\n    if ((x188_flags & 0x1) != 0) {\n      newLookDelta = zeus::CVector3f(hintPos.toVec2f() - newCamPos.toVec2f(), newLookDelta.z());\n    }\n    if ((x188_flags & 0x2) != 0) {\n      newLookDelta = lookAheadPos - hintPos.toVec2f();\n    }\n    if (newLookDelta.canBeNormalized()) {\n      SetTransform(zeus::lookAt(newCamPos, newCamPos + newLookDelta.normalized()));\n    }\n  }\n}\n\nvoid CScriptSpindleCamera::Render(CStateManager&) {\n  // Empty\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptSpindleCamera.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Camera/CGameCamera.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\n\nenum class ESpindleInput {\n  Constant,\n  HintToBallDist,\n  HintToBallVOff,\n  HintBallAngle,\n  HintBallRightAngle,\n  HintBallLeftAngle,\n  HintDeltaDist,\n  HintDeltaVOff\n};\n\nstruct SSpindleProperty {\n  u32 x0_flags;\n  ESpindleInput x4_input;\n  float x8_lowOut;\n  float xc_highOut;\n  float x10_lowIn;\n  float x14_highIn;\n\n  explicit SSpindleProperty(CInputStream& in);\n  void FixupAngles() {\n    x8_lowOut = zeus::degToRad(x8_lowOut);\n    xc_highOut = zeus::degToRad(xc_highOut);\n  }\n  float GetValue(float inVar) const;\n};\n\nclass CScriptSpindleCamera : public CGameCamera {\n  /*\n   * 0x1: Look toward hint\n   * 0x2: Flat look delta\n   * 0x8: force minimum-clamp ball-to-cam azimuth\n   * 0x10: minimum-clamp ball-to-cam azimuth\n   * 0x20: Enable clampedAzimuthFromHintDir\n   * 0x40: Enable distOffsetFromBallDist\n   * 0x80: Use ball pos for cam pos Z (vs. hint pos)\n   * 0x100: Enable deltaAngleScaleWithCamDist\n   * 0x200: Use ball pos for look pos Z (vs. hint pos)\n   * 0x400: unused\n   * 0x800: Variable hint-to-ball direction\n   * 0x1000: Damp look azimuth with hint ball-to-cam azimuth < 10-degrees\n   * 0x2000: Enable deleteHintBallDist\n   * 0x4000: Ignore ball-to-cam azimuth sign\n   */\n  u32 x188_flags;\n  rstl::reserved_vector<float, 8> x18c_inVars;\n  float x1b0_hintToCamDistMin;\n  float x1b4_hintToCamDistMax;\n  float x1b8_hintToCamVOffMin;\n  float x1bc_hintToCamVOffMax;\n  SSpindleProperty x1c0_targetHintToCamDeltaAngleVel;\n  SSpindleProperty x1d8_deltaAngleScaleWithCamDist;\n  SSpindleProperty x1f0_hintToCamDist;\n  SSpindleProperty x208_distOffsetFromBallDist;\n  SSpindleProperty x220_hintBallToCamAzimuth;\n  SSpindleProperty x238_unused;\n  SSpindleProperty x250_maxHintBallToCamAzimuth;\n  SSpindleProperty x268_camLookRelAzimuth;\n  SSpindleProperty x280_lookPosZOffset;\n  SSpindleProperty x298_camPosZOffset;\n  SSpindleProperty x2b0_clampedAzimuthFromHintDir;\n  SSpindleProperty x2c8_dampingAzimuthSpeed;\n  SSpindleProperty x2e0_targetHintToCamDeltaAngleVelRange;\n  SSpindleProperty x2f8_deleteHintBallDist;\n  SSpindleProperty x310_recoverClampedAzimuthFromHintDir;\n  float x328_maxAzimuthInterpTimer = 0.f;\n  bool x32c_outsideClampedAzimuth = false;\n  zeus::CVector3f x330_lookDir;\n  bool x33c_24_inResetThink = false;\n\n  float GetInVar(const SSpindleProperty& seg) const { return x18c_inVars[int(seg.x4_input)]; }\n\npublic:\n  DEFINE_ENTITY\n  CScriptSpindleCamera(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                       bool active, u32 flags, float hintToCamDistMin, float hintToCamDistMax, float hintToCamVOffMin,\n                       float hintToCamVOffMax, const SSpindleProperty& targetHintToCamDeltaAngleVel,\n                       const SSpindleProperty& deltaAngleScaleWithCamDist, const SSpindleProperty& hintToCamDist,\n                       const SSpindleProperty& distOffsetFromBallDist, const SSpindleProperty& hintBallToCamAzimuth,\n                       const SSpindleProperty& unused, const SSpindleProperty& maxHintBallToCamAzimuth,\n                       const SSpindleProperty& camLookRelAzimuth, const SSpindleProperty& lookPosZOffset,\n                       const SSpindleProperty& camPosZOffset, const SSpindleProperty& clampedAzimuthFromHintDir,\n                       const SSpindleProperty& dampingAzimuthSpeed,\n                       const SSpindleProperty& targetHintToCamDeltaAngleVelRange,\n                       const SSpindleProperty& deleteHintBallDist,\n                       const SSpindleProperty& recoverClampedAzimuthFromHintDir);\n\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void Think(float, CStateManager&) override;\n  void Render(CStateManager&) override;\n  void Reset(const zeus::CTransform& xf, CStateManager& mgr) override;\n  void ProcessInput(const CFinalInput& input, CStateManager& mgr) override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptSteam.cpp",
    "content": "#include \"Runtime/World/CScriptSteam.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCScriptSteam::CScriptSteam(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CVector3f& pos,\n                           const zeus::CAABox& aabb, const CDamageInfo& dInfo, const zeus::CVector3f& orientedForce,\n                           ETriggerFlags flags, bool active, CAssetId texture, float f1, float f2, float f3, float f4,\n                           bool b1)\n: CScriptTrigger(uid, name, info, pos, aabb, dInfo, orientedForce, flags, active, false, false)\n, x150_(b1)\n, x154_texture(texture)\n, x158_(f1)\n, x15c_alphaInDur(f2 / f1)\n, x160_alphaOutDur(f3 / f1) {\n  float r3 = std::min(aabb.max.x(), std::min(aabb.max.y(), aabb.max.z()));\n  x164_ = zeus::close_enough(f4, 0.f) ? r3 : std::min(f4, r3);\n  x168_ = 1.f / x164_;\n}\n\nvoid CScriptSteam::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptSteam::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  if (msg == EScriptObjectMessage::Deactivate) {\n    mgr.Player()->SetVisorSteam(0.f, x158_, x160_alphaOutDur, CAssetId(), !x150_);\n  }\n\n  CScriptTrigger::AcceptScriptMsg(msg, uid, mgr);\n}\n\nvoid CScriptSteam::Think(float dt, CStateManager& mgr) {\n  if (!GetActive()) {\n    return;\n  }\n\n  CScriptTrigger::Think(dt, mgr);\n\n  if (x148_28_playerTriggerProc && mgr.GetCameraManager()->GetFluidCounter() == 0) {\n    const zeus::CVector3f eyePos = mgr.GetPlayer().GetEyePosition();\n    const zeus::CVector3f posDiff = (GetTranslation() - eyePos);\n    const float mag = posDiff.magnitude();\n    const float distance = (mag >= x164_ ? 0.f : std::cos((1.5707964f * mag) * x168_) * x158_);\n    mgr.Player()->SetVisorSteam(distance, x15c_alphaInDur, x160_alphaOutDur, x154_texture, !x150_);\n    if (x150_) {\n      mgr.GetEnvFxManager()->SetSplashRate(2.f * distance);\n    }\n  } else {\n    mgr.Player()->SetVisorSteam(0.f, x15c_alphaInDur, x160_alphaOutDur, CAssetId(), !x150_);\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptSteam.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/World/CScriptTrigger.hpp\"\n\nnamespace metaforce {\n\nclass CScriptSteam : public CScriptTrigger {\n  bool x150_;\n  CAssetId x154_texture;\n  float x158_;\n  float x15c_alphaInDur;\n  float x160_alphaOutDur;\n  float x164_ = 0.f;\n  float x168_ = 0.f;\n\npublic:\n  DEFINE_ENTITY\n  CScriptSteam(TUniqueId, std::string_view name, const CEntityInfo& info, const zeus::CVector3f& pos,\n               const zeus::CAABox&, const CDamageInfo& dInfo, const zeus::CVector3f& orientedForce, ETriggerFlags flags,\n               bool active, CAssetId, float, float, float, float, bool);\n\n  void Accept(IVisitor&) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void Think(float, CStateManager&) override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptStreamedMusic.cpp",
    "content": "#include \"Runtime/World/CScriptStreamedMusic.hpp\"\n\n#include \"Runtime/CInGameTweakManagerBase.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/CStringExtras.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Audio/CStreamAudioManager.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nbool CScriptStreamedMusic::IsDSPFile(std::string_view fileName) {\n  if (CStringExtras::CompareCaseInsensitive(fileName, \"sw\")) {\n    return true;\n  }\n  return CStringExtras::IndexOfSubstring(fileName, \".dsp\") != -1;\n}\n\nvoid CScriptStreamedMusic::StopStream(CStateManager& mgr) { CStreamAudioManager::Stop(!x46_loop, x34_fileName); }\n\nvoid CScriptStreamedMusic::StartStream(CStateManager& mgr) {\n  CStreamAudioManager::Start(!x46_loop, x34_fileName, x50_volume / 127.f, x47_music, x48_fadeIn, x4c_fadeOut);\n}\n\nvoid CScriptStreamedMusic::TweakOverride(CStateManager& mgr) {\n  const CWorld* wld = mgr.GetWorld();\n  const CGameArea* area = wld->GetAreaAlways(x4_areaId);\n  std::string twkName = fmt::format(\"Area {} MusicObject: {}\", area->GetAreaAssetId(), x10_name);\n  if (g_TweakManager->HasTweakValue(twkName)) {\n    const CTweakValue::Audio& audio = g_TweakManager->GetTweakValue(twkName)->GetAudio();\n    x34_fileName = audio.GetFileName();\n    x45_fileIsDsp = IsDSPFile(x34_fileName);\n    x48_fadeIn = audio.GetFadeIn();\n    x4c_fadeOut = audio.GetFadeOut();\n    x50_volume = audio.GetVolume() * 127.f;\n  }\n}\n\nCScriptStreamedMusic::CScriptStreamedMusic(TUniqueId id, const CEntityInfo& info, std::string_view name, bool active,\n                                           std::string_view fileName, bool noStopOnDeactivate, float fadeIn,\n                                           float fadeOut, u32 volume, bool loop, bool music)\n: CEntity(id, info, active, name)\n, x34_fileName(fileName)\n, x44_noStopOnDeactivate(noStopOnDeactivate)\n, x45_fileIsDsp(IsDSPFile(fileName))\n, x46_loop(loop)\n, x47_music(music)\n, x48_fadeIn(fadeIn)\n, x4c_fadeOut(fadeOut)\n, x50_volume(volume) {}\n\nvoid CScriptStreamedMusic::Stop(CStateManager& mgr) {\n  if (!x45_fileIsDsp) {\n    return;\n  }\n\n  StopStream(mgr);\n}\n\nvoid CScriptStreamedMusic::Play(CStateManager& mgr) {\n  TweakOverride(mgr);\n  if (x45_fileIsDsp) {\n    StartStream(mgr);\n  }\n}\n\nvoid CScriptStreamedMusic::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptStreamedMusic::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) {\n  CEntity::AcceptScriptMsg(msg, objId, stateMgr);\n  switch (msg) {\n  case EScriptObjectMessage::Play:\n    if (x30_24_active) {\n      Play(stateMgr);\n    }\n    break;\n  case EScriptObjectMessage::Stop:\n    if (x30_24_active) {\n      Stop(stateMgr);\n    }\n    break;\n  case EScriptObjectMessage::Increment:\n    if (x45_fileIsDsp) {\n      CStreamAudioManager::FadeBackIn(!x46_loop, x48_fadeIn);\n    }\n    break;\n  case EScriptObjectMessage::Decrement:\n    if (x45_fileIsDsp) {\n      CStreamAudioManager::TemporaryFadeOut(!x46_loop, x4c_fadeOut);\n    }\n    break;\n  default:\n    break;\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptStreamedMusic.hpp",
    "content": "#pragma once\n\n#include <string>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/World/CEntity.hpp\"\n\nnamespace metaforce {\nclass CStateManager;\n\nclass CScriptStreamedMusic : public CEntity {\n  std::string x34_fileName;\n  bool x44_noStopOnDeactivate;\n  bool x45_fileIsDsp; // As opposed to .adp for DTK streaming\n  bool x46_loop;\n  bool x47_music;\n  float x48_fadeIn;\n  float x4c_fadeOut;\n  u32 x50_volume;\n  static bool IsDSPFile(std::string_view fileName);\n  void StopStream(CStateManager& mgr);\n  void StartStream(CStateManager& mgr);\n  void TweakOverride(CStateManager& mgr);\n\npublic:\n  DEFINE_ENTITY\n  CScriptStreamedMusic(TUniqueId id, const CEntityInfo& info, std::string_view name, bool active,\n                       std::string_view fileName, bool noStopOnDeactivate, float fadeIn, float fadeOut, u32 volume,\n                       bool loop, bool music);\n\n  void Stop(CStateManager& mgr);\n  void Play(CStateManager& mgr);\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptSwitch.cpp",
    "content": "#include \"Runtime/World/CScriptSwitch.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nCScriptSwitch::CScriptSwitch(TUniqueId uid, std::string_view name, const CEntityInfo& info, bool active, bool opened,\n                             bool closeOnOpened)\n: CEntity(uid, info, active, name), x34_opened(opened), x35_closeOnOpened(closeOnOpened) {}\n\nvoid CScriptSwitch::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptSwitch::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& mgr) {\n  if (GetActive()) {\n    if (msg == EScriptObjectMessage::Open) {\n      x34_opened = true;\n    } else if (msg == EScriptObjectMessage::Close) {\n      x34_opened = false;\n    } else if (msg == EScriptObjectMessage::SetToZero) {\n      if (x34_opened) {\n        SendScriptMsgs(EScriptObjectState::Open, mgr, EScriptObjectMessage::None);\n        if (x35_closeOnOpened)\n          x34_opened = false;\n      } else {\n        SendScriptMsgs(EScriptObjectState::Closed, mgr, EScriptObjectMessage::None);\n      }\n    }\n  }\n\n  CEntity::AcceptScriptMsg(msg, objId, mgr);\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptSwitch.hpp",
    "content": "#pragma once\n\n#include <string_view>\n#include \"Runtime/World/CEntity.hpp\"\n\nnamespace metaforce {\nclass CScriptSwitch : public CEntity {\n  bool x34_opened;\n  bool x35_closeOnOpened;\n\npublic:\n  DEFINE_ENTITY\n  CScriptSwitch(TUniqueId, std::string_view, const CEntityInfo&, bool, bool, bool);\n\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) override;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptTargetingPoint.cpp",
    "content": "#include \"Runtime/World/CScriptTargetingPoint.hpp\"\n\n#include \"Runtime/World/CActorParameters.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCScriptTargetingPoint::CScriptTargetingPoint(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                                             const zeus::CTransform& xf, bool active)\n: CActor(uid, active, name, info, xf, CModelData::CModelDataNull(), CMaterialList(EMaterialTypes::NoStepLogic),\n         CActorParameters::None(), kInvalidUniqueId) {}\n\nvoid CScriptTargetingPoint::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptTargetingPoint::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  CActor::AcceptScriptMsg(msg, uid, mgr);\n\n  if (msg == EScriptObjectMessage::Deactivate || msg == EScriptObjectMessage::Activate) {\n    CEntity::SendScriptMsgs(EScriptObjectState::Attack, mgr, EScriptObjectMessage::None);\n  }\n}\n\nvoid CScriptTargetingPoint::Think(float dt, CStateManager&) {\n  if (xec_time <= 0.f) {\n    return;\n  }\n\n  xec_time -= dt;\n}\n\nbool CScriptTargetingPoint::GetLocked() const { return !x20_conns.empty(); }\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptTargetingPoint.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/World/CActor.hpp\"\n\nnamespace metaforce {\nclass CScriptTargetingPoint : public CActor {\nprivate:\n  bool xe8_e4_ : 1 = false;\n  TUniqueId xea_;\n  float xec_time = 0.f;\n\npublic:\n  DEFINE_ENTITY\n  CScriptTargetingPoint(TUniqueId, std::string_view, const CEntityInfo&, const zeus::CTransform&, bool);\n\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void Think(float, CStateManager&) override;\n  void Render(CStateManager&) override {}\n\n  bool GetLocked() const;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptTimer.cpp",
    "content": "#include \"Runtime/World/CScriptTimer.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCScriptTimer::CScriptTimer(TUniqueId uid, std::string_view name, const CEntityInfo& info, float startTime,\n                           float maxRandDelay, bool loop, bool autoStart, bool active)\n: CEntity(uid, info, active, name)\n, x34_time(startTime)\n, x38_startTime(startTime)\n, x3c_maxRandDelay(maxRandDelay)\n, x40_loop(loop)\n, x41_autoStart(autoStart)\n, x42_isTiming(autoStart) {}\n\nvoid CScriptTimer::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptTimer::Think(float dt, CStateManager& mgr) {\n  if (GetActive() && IsTiming()) {\n    ApplyTime(dt, mgr);\n  }\n}\n\nvoid CScriptTimer::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) {\n  if (GetActive()) {\n    if (msg == EScriptObjectMessage::Start) {\n      StartTiming(true);\n    } else if (msg == EScriptObjectMessage::Stop) {\n      StartTiming(false);\n    } else if (msg == EScriptObjectMessage::ResetAndStart) {\n      Reset(stateMgr);\n      StartTiming(true);\n    } else if (msg == EScriptObjectMessage::Reset) {\n      Reset(stateMgr);\n      if (x41_autoStart) {\n        StartTiming(true);\n      }\n    } else if (msg == EScriptObjectMessage::StopAndReset) {\n      Reset(stateMgr);\n      StartTiming(false);\n    }\n  }\n  CEntity::AcceptScriptMsg(msg, objId, stateMgr);\n}\n\nbool CScriptTimer::IsTiming() const { return x42_isTiming; }\n\nvoid CScriptTimer::StartTiming(bool isTiming) { x42_isTiming = isTiming; }\n\nvoid CScriptTimer::Reset(CStateManager& mgr) {\n  const float rDt = mgr.GetActiveRandom()->Float();\n  x34_time = (x3c_maxRandDelay * rDt) + x38_startTime;\n}\n\nvoid CScriptTimer::ApplyTime(float dt, CStateManager& mgr) {\n  if (x34_time <= 0.f || !GetActive()) {\n    return;\n  }\n\n  x34_time -= dt;\n  if (x34_time > 0.f) {\n    return;\n  }\n\n  SendScriptMsgs(EScriptObjectState::Zero, mgr, EScriptObjectMessage::None);\n\n  x42_isTiming = false;\n  if (!x40_loop) {\n    return;\n  }\n\n  Reset(mgr);\n  if (!x41_autoStart) {\n    return;\n  }\n\n  x42_isTiming = true;\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptTimer.hpp",
    "content": "#pragma once\n\n#include <string_view>\n#include \"Runtime/World/CEntity.hpp\"\n\nnamespace metaforce {\n\nclass CScriptTimer : public CEntity {\n  float x34_time;\n  float x38_startTime;\n  float x3c_maxRandDelay;\n  bool x40_loop;\n  bool x41_autoStart;\n  bool x42_isTiming;\n\npublic:\n  DEFINE_ENTITY\n  CScriptTimer(TUniqueId, std::string_view name, const CEntityInfo& info, float, float, bool, bool, bool);\n\n  void Accept(IVisitor& visitor) override;\n  void Think(float, CStateManager&) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) override;\n  bool IsTiming() const;\n  void StartTiming(bool isTiming);\n  void Reset(CStateManager&);\n  void ApplyTime(float, CStateManager&);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptTrigger.cpp",
    "content": "#include \"Runtime/World/CScriptTrigger.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Camera/CGameCamera.hpp\"\n#include \"Runtime/Weapon/CGameProjectile.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/Logging.hpp\"\n#include \"Runtime/Formatting.hpp\"\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nCScriptTrigger::CScriptTrigger(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                               const zeus::CVector3f& pos, const zeus::CAABox& bounds, const CDamageInfo& dInfo,\n                               const zeus::CVector3f& forceField, ETriggerFlags triggerFlags, bool active,\n                               bool deactivateOnEntered, bool deactivateOnExited)\n: CActor(uid, active, name, info, zeus::CTransform::Translate(pos), CModelData::CModelDataNull(),\n         CMaterialList(EMaterialTypes::Trigger), CActorParameters::None(), kInvalidUniqueId)\n, x100_damageInfo(dInfo)\n, x11c_forceField(forceField)\n, x128_forceMagnitude(forceField.magnitude())\n, x12c_flags(triggerFlags)\n, x130_bounds(bounds)\n, x148_26_deactivateOnEntered(deactivateOnEntered)\n, x148_27_deactivateOnExited(deactivateOnExited) {\n  SetCallTouch(false);\n#ifndef NDEBUG\n  // HACK: For some reason MetroidPrime's lair doesn't enable this trigger until after the cutscene, activate it in\n  // debug build\n  if (GetEditorId() == 0x000B01DB && !GetActive()) {\n    spdlog::warn(\"BUG THIS!: Overriding active for trigger {} in area {}\", GetEditorId(), GetAreaIdAlways());\n    SetActive(true);\n  }\n#endif\n}\n\nvoid CScriptTrigger::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptTrigger::Think(float dt, CStateManager& mgr) {\n  if (GetActive()) {\n    UpdateInhabitants(dt, mgr);\n  }\n}\n\nvoid CScriptTrigger::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  if (GetActive() && (msg == EScriptObjectMessage::Deactivate || msg == EScriptObjectMessage::Deleted)) {\n    if (msg == EScriptObjectMessage::Deactivate) {\n      xe8_inhabitants.clear();\n      x148_25_camSubmerged = false;\n    }\n\n    if (x148_28_playerTriggerProc) {\n      x148_28_playerTriggerProc = false;\n      if (x148_29_didPhazonDamage) {\n        mgr.Player()->DecrementEnvironmentDamage();\n        x148_29_didPhazonDamage = false;\n      }\n\n      if (x8_uid == mgr.GetLastTriggerId()) {\n        mgr.SetLastTriggerId(kInvalidUniqueId);\n      }\n    }\n  }\n\n  CActor::AcceptScriptMsg(msg, uid, mgr);\n}\n\nCScriptTrigger::CObjectTracker* CScriptTrigger::FindObject(TUniqueId id) {\n  auto& inhabitants = GetInhabitants();\n  const auto iter = std::find(inhabitants.begin(), inhabitants.end(), CObjectTracker{id});\n\n  if (iter == inhabitants.end()) {\n    return nullptr;\n  }\n\n  return &*iter;\n}\n\nvoid CScriptTrigger::UpdateInhabitants(float dt, CStateManager& mgr) {\n  bool sendInside = false;\n  bool sendExited = false;\n  std::list<CObjectTracker>::iterator nextIt;\n  for (auto it = xe8_inhabitants.begin(); it != xe8_inhabitants.end(); it = nextIt) {\n    nextIt = it;\n    ++nextIt;\n    if (const TCastToPtr<CActor> act = mgr.ObjectById(it->GetObjectId())) {\n      bool playerValid = true;\n      if (it->GetObjectId() == mgr.GetPlayer().GetUniqueId()) {\n        if (False(x12c_flags & ETriggerFlags::DetectPlayer) &&\n            ((mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed &&\n              True(x12c_flags & ETriggerFlags::DetectUnmorphedPlayer)) ||\n             (mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Unmorphed &&\n              True(x12c_flags & ETriggerFlags::DetectMorphedPlayer)))) {\n          playerValid = false;\n        }\n        if (!playerValid) {\n          xe8_inhabitants.erase(it);\n          sendExited = true;\n          if (x148_28_playerTriggerProc) {\n            x148_28_playerTriggerProc = false;\n            if (x148_29_didPhazonDamage) {\n              mgr.GetPlayer().DecrementEnvironmentDamage();\n              x148_29_didPhazonDamage = false;\n            }\n\n            if (mgr.GetLastTriggerId() == GetUniqueId()) {\n              mgr.SetLastTriggerId(kInvalidUniqueId);\n            }\n          }\n\n          InhabitantExited(*act, mgr);\n          continue;\n        }\n      }\n\n      const auto touchBounds = GetTouchBounds();\n      const auto actTouchBounds = act->GetTouchBounds();\n      if (touchBounds && actTouchBounds && touchBounds->intersects(*actTouchBounds)) {\n        sendInside = true;\n        InhabitantIdle(*act, mgr);\n        if (act->HealthInfo(mgr) && x100_damageInfo.GetDamage() > 0.f) {\n          mgr.ApplyDamage(GetUniqueId(), act->GetUniqueId(), GetUniqueId(), {x100_damageInfo, dt},\n                          CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), zeus::skZero3f);\n        }\n\n        if (x128_forceMagnitude > 0.f) {\n          if (TCastToPtr<CPhysicsActor> pact = act.GetPtr()) {\n            float forceMult = 1.f;\n            if (True(x12c_flags & ETriggerFlags::UseBooleanIntersection)) {\n              forceMult = touchBounds->booleanIntersection(*actTouchBounds).volume() / actTouchBounds->volume();\n            }\n\n            const zeus::CVector3f force = forceMult * x11c_forceField;\n            if (True(x12c_flags & ETriggerFlags::UseCollisionImpulses)) {\n              pact->ApplyImpulseWR(force, zeus::CAxisAngle());\n              pact->UseCollisionImpulses();\n            } else {\n              pact->ApplyForceWR(force, zeus::CAxisAngle());\n            }\n          }\n        }\n      } else {\n        const TUniqueId tmpId = it->GetObjectId();\n        xe8_inhabitants.erase(it);\n        sendExited = true;\n        if (mgr.GetPlayer().GetUniqueId() == tmpId && x148_28_playerTriggerProc) {\n          x148_28_playerTriggerProc = false;\n          if (x148_29_didPhazonDamage) {\n            mgr.Player()->DecrementEnvironmentDamage();\n            x148_29_didPhazonDamage = false;\n          }\n\n          if (mgr.GetLastTriggerId() == GetUniqueId())\n            mgr.SetLastTriggerId(kInvalidUniqueId);\n        }\n\n        InhabitantExited(*act, mgr);\n      }\n    } else {\n      const TUniqueId tmpId = it->GetObjectId();\n      xe8_inhabitants.erase(it);\n      if (mgr.GetPlayer().GetUniqueId() == tmpId && x148_28_playerTriggerProc) {\n        x148_28_playerTriggerProc = false;\n        if (x148_29_didPhazonDamage) {\n          mgr.Player()->DecrementEnvironmentDamage();\n          x148_29_didPhazonDamage = false;\n        }\n\n        if (mgr.GetLastTriggerId() == GetUniqueId()) {\n          mgr.SetLastTriggerId(kInvalidUniqueId);\n        }\n      }\n    }\n  }\n\n  if (True(x12c_flags & ETriggerFlags::DetectCamera) || x148_24_detectCamera) {\n    CGameCamera* cam = mgr.GetCameraManager()->GetCurrentCamera(mgr);\n    const bool camInTrigger = GetTriggerBoundsWR().pointInside(cam->GetTranslation());\n    if (x148_25_camSubmerged) {\n      if (!camInTrigger) {\n        x148_25_camSubmerged = false;\n        if (True(x12c_flags & ETriggerFlags::DetectCamera)) {\n          sendExited = true;\n          InhabitantExited(*cam, mgr);\n        }\n      } else {\n        if (True(x12c_flags & ETriggerFlags::DetectCamera)) {\n          InhabitantIdle(*cam, mgr);\n          sendInside = true;\n        }\n      }\n    } else {\n      if (camInTrigger) {\n        x148_25_camSubmerged = true;\n        if (True(x12c_flags & ETriggerFlags::DetectCamera)) {\n          InhabitantAdded(*cam, mgr);\n          SendScriptMsgs(EScriptObjectState::Entered, mgr, EScriptObjectMessage::None);\n        }\n      }\n    }\n  }\n\n  if (sendInside) {\n    SendScriptMsgs(EScriptObjectState::Inside, mgr, EScriptObjectMessage::None);\n  }\n\n  if (sendExited) {\n    SendScriptMsgs(EScriptObjectState::Exited, mgr, EScriptObjectMessage::None);\n    if (x148_27_deactivateOnExited) {\n      mgr.SendScriptMsg(GetUniqueId(), mgr.GetEditorIdForUniqueId(GetUniqueId()), EScriptObjectMessage::Deactivate,\n                        EScriptObjectState::Exited);\n    }\n  }\n}\n\nstd::list<CScriptTrigger::CObjectTracker>& CScriptTrigger::GetInhabitants() { return xe8_inhabitants; }\n\nstd::optional<zeus::CAABox> CScriptTrigger::GetTouchBounds() const {\n  if (x30_24_active) {\n    return GetTriggerBoundsWR();\n  }\n  return std::nullopt;\n}\nconstexpr auto sktonOHurtWeaponMode = CWeaponMode(EWeaponType::Power, false, false, true);\n\nvoid CScriptTrigger::Touch(CActor& act, CStateManager& mgr) {\n  if (!act.GetActive() || act.GetMaterialList().HasMaterial(EMaterialTypes::Trigger)) {\n    return;\n  }\n\n  if (FindObject(act.GetUniqueId()) == nullptr) {\n    auto testFlags = ETriggerFlags::None;\n    const TCastToPtr<CPlayer> pl(act);\n    if (pl) {\n      if (x128_forceMagnitude > 0.f && True(x12c_flags & ETriggerFlags::DetectPlayer)) {\n        if (mgr.GetLastTriggerId() != kInvalidUniqueId) {\n          return;\n        }\n        mgr.SetLastTriggerId(x8_uid);\n      }\n\n      testFlags |= ETriggerFlags::DetectPlayer;\n      if (pl->GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Unmorphed) {\n        testFlags |= ETriggerFlags::DetectUnmorphedPlayer;\n      } else if (pl->GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed) {\n        testFlags |= ETriggerFlags::DetectMorphedPlayer;\n      }\n    } else if (TCastToPtr<CPatterned>(act)) {\n      testFlags |= ETriggerFlags::DetectAI;\n    } else if (TCastToPtr<CGameProjectile>(act)) {\n      testFlags |= ETriggerFlags::DetectProjectiles1 | ETriggerFlags::DetectProjectiles2 |\n                   ETriggerFlags::DetectProjectiles3 | ETriggerFlags::DetectProjectiles4 |\n                   ETriggerFlags::DetectProjectiles5 | ETriggerFlags::DetectProjectiles6 |\n                   ETriggerFlags::DetectProjectiles7;\n    } else if (const TCastToConstPtr<CWeapon> weap = act) {\n      if ((weap->GetAttribField() & EProjectileAttrib::Bombs) == EProjectileAttrib::Bombs) {\n        testFlags |= ETriggerFlags::DetectBombs;\n      } else if ((weap->GetAttribField() & EProjectileAttrib::PowerBombs) == EProjectileAttrib::PowerBombs) {\n        testFlags |= ETriggerFlags::DetectPowerBombs;\n      }\n    }\n\n    if (True(testFlags & x12c_flags)) {\n      xe8_inhabitants.emplace_back(act.GetUniqueId());\n      InhabitantAdded(act, mgr);\n\n      if (pl) {\n        if (!x148_28_playerTriggerProc) {\n          x148_28_playerTriggerProc = true;\n          if (x148_29_didPhazonDamage) {\n            mgr.Player()->DecrementEnvironmentDamage();\n            x148_29_didPhazonDamage = false;\n          } else if (x100_damageInfo.GetDamage() > 0.f) {\n            const CDamageVulnerability* dVuln = mgr.Player()->GetDamageVulnerability();\n            if (dVuln->WeaponHurts(x100_damageInfo.GetWeaponMode(), false) &&\n                x100_damageInfo.GetWeaponMode().GetType() == EWeaponType::Phazon &&\n                !mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::PhazonSuit)) {\n              pl->IncrementEnvironmentDamage();\n              x148_29_didPhazonDamage = true;\n            }\n          }\n        }\n      }\n\n      SendScriptMsgs(EScriptObjectState::Entered, mgr, EScriptObjectMessage::None);\n\n      if (x148_26_deactivateOnEntered) {\n        mgr.SendScriptMsg(x8_uid, mgr.GetEditorIdForUniqueId(x8_uid), EScriptObjectMessage::Deactivate,\n                          EScriptObjectState::Entered);\n        if (act.HealthInfo(mgr) && x100_damageInfo.GetDamage() > 0.f) {\n          mgr.ApplyDamage(x8_uid, act.GetUniqueId(), x8_uid, x100_damageInfo,\n                          CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {0ull}), zeus::skZero3f);\n        }\n      }\n\n      if (True(x12c_flags & ETriggerFlags::KillOnEnter) && act.HealthInfo(mgr)) {\n        CHealthInfo* hInfo = act.HealthInfo(mgr);\n        mgr.ApplyDamage(x8_uid, act.GetUniqueId(), x8_uid, {sktonOHurtWeaponMode, 10.f * hInfo->GetHP(), 0.f, 0.f},\n                        CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {0ull}), zeus::skZero3f);\n      }\n    } else {\n      InhabitantRejected(act, mgr);\n    }\n  }\n}\n\nzeus::CAABox CScriptTrigger::GetTriggerBoundsWR() const {\n  return {x130_bounds.min + x34_transform.origin, x130_bounds.max + x34_transform.origin};\n}\n\nvoid CScriptTrigger::DebugDraw() {\n  if (m_debugSelected || m_debugHovered) {\n    // m_debugBox.setAABB(GetTriggerBoundsWR());\n    // m_debugBox.draw(m_debugAddColor);\n  }\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptTrigger.hpp",
    "content": "#pragma once\n\n#include <list>\n#include <string_view>\n\n#include \"Runtime/World/CActor.hpp\"\n#include \"Runtime/World/CDamageInfo.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\n// TODO - Phil: Figure out what each of the DetectProjectiles actually mean\nenum class ETriggerFlags : u32 {\n  None = 0,\n  DetectPlayer = (1 << 0),\n  DetectAI = (1 << 1),\n  DetectProjectiles1 = (1 << 2),\n  DetectProjectiles2 = (1 << 3),\n  DetectProjectiles3 = (1 << 4),\n  DetectProjectiles4 = (1 << 5),\n  DetectBombs = (1 << 6),\n  DetectPowerBombs = (1 << 7),\n  DetectProjectiles5 = (1 << 8),\n  DetectProjectiles6 = (1 << 9),\n  DetectProjectiles7 = (1 << 10),\n  KillOnEnter = (1 << 11),\n  DetectMorphedPlayer = (1 << 12),\n  UseCollisionImpulses = (1 << 13),\n  DetectCamera = (1 << 14),\n  UseBooleanIntersection = (1 << 15),\n  DetectUnmorphedPlayer = (1 << 16),\n  BlockEnvironmentalEffects = (1 << 17)\n};\nENABLE_BITWISE_ENUM(ETriggerFlags)\n\nclass CScriptTrigger : public CActor {\npublic:\n  class CObjectTracker {\n    TUniqueId x0_id;\n\n  public:\n    explicit CObjectTracker(TUniqueId id) : x0_id(id) {}\n\n    TUniqueId GetObjectId() const { return x0_id; }\n    bool operator==(const CObjectTracker& other) const { return x0_id == other.x0_id; }\n  };\n\nprotected:\n  std::list<CObjectTracker> xe8_inhabitants;\n  CDamageInfo x100_damageInfo;\n  zeus::CVector3f x11c_forceField;\n  float x128_forceMagnitude;\n  ETriggerFlags x12c_flags;\n  zeus::CAABox x130_bounds;\n  bool x148_24_detectCamera : 1 = false;\n  bool x148_25_camSubmerged : 1 = false;\n  bool x148_26_deactivateOnEntered : 1;\n  bool x148_27_deactivateOnExited : 1;\n  bool x148_28_playerTriggerProc : 1 = false;\n  bool x148_29_didPhazonDamage : 1 = false;\n\npublic:\n  DEFINE_ENTITY\n  CScriptTrigger(TUniqueId, std::string_view name, const CEntityInfo& info, const zeus::CVector3f& pos,\n                 const zeus::CAABox&, const CDamageInfo& dInfo, const zeus::CVector3f& orientedForce,\n                 ETriggerFlags triggerFlags, bool, bool, bool);\n\n  void Accept(IVisitor& visitor) override;\n  void Think(float, CStateManager&) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  virtual void InhabitantRejected(CActor&, CStateManager&) {}\n  virtual void InhabitantExited(CActor&, CStateManager&) {}\n  virtual void InhabitantIdle(CActor&, CStateManager&) {}\n  virtual void InhabitantAdded(CActor&, CStateManager&) {}\n  CObjectTracker* FindObject(TUniqueId);\n  void UpdateInhabitants(float, CStateManager&);\n  std::list<CObjectTracker>& GetInhabitants();\n  std::optional<zeus::CAABox> GetTouchBounds() const override;\n  void Touch(CActor&, CStateManager&) override;\n  const zeus::CAABox& GetTriggerBoundsOR() const { return x130_bounds; }\n  zeus::CAABox GetTriggerBoundsWR() const;\n  const CDamageInfo& GetDamageInfo() const { return x100_damageInfo; }\n  ETriggerFlags GetTriggerFlags() const { return x12c_flags; }\n  float GetForceMagnitude() const { return x128_forceMagnitude; }\n  const zeus::CVector3f& GetForceVector() const { return x11c_forceField; }\n  void SetForceVector(const zeus::CVector3f& force) {\n    x11c_forceField = force;\n    x128_forceMagnitude = x11c_forceField.magnitude();\n  }\n  bool IsPlayerTriggerProc() const { return x148_28_playerTriggerProc; }\n  void DebugDraw();\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptVisorFlare.cpp",
    "content": "#include \"Runtime/World/CScriptVisorFlare.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Particle/CGenDescription.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCScriptVisorFlare::CScriptVisorFlare(TUniqueId uid, std::string_view name, const CEntityInfo& info, bool active,\n                                     const zeus::CVector3f& pos, CVisorFlare::EBlendMode blendMode, bool b1, float f1,\n                                     float f2, float f3, u32 w1, u32 w2, std::vector<CVisorFlare::CFlareDef> flares)\n: CActor(uid, active, name, info, zeus::CTransform::Translate(pos), CModelData::CModelDataNull(),\n         CMaterialList(EMaterialTypes::NoStepLogic), CActorParameters::None(), kInvalidUniqueId)\n, xe8_flare(blendMode, b1, f1, f2, f3, w1, w2, std::move(flares))\n, x11c_notInRenderLast(true) {\n  xe6_27_thermalVisorFlags = 2;\n}\n\nvoid CScriptVisorFlare::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptVisorFlare::Think(float dt, CStateManager& stateMgr) {\n  if (GetActive()) {\n    xe8_flare.Update(dt, GetTranslation(), this, stateMgr);\n  }\n}\n\nvoid CScriptVisorFlare::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) {\n  CActor::AcceptScriptMsg(msg, objId, stateMgr);\n}\n\nvoid CScriptVisorFlare::PreRender(CStateManager& stateMgr, const zeus::CFrustum&) {\n  x11c_notInRenderLast = !stateMgr.RenderLast(x8_uid);\n}\n\nvoid CScriptVisorFlare::AddToRenderer(const zeus::CFrustum&, CStateManager& stateMgr) {\n  if (x11c_notInRenderLast) {\n    EnsureRendered(stateMgr, stateMgr.GetPlayer().GetTranslation(), GetSortingBounds(stateMgr));\n  }\n}\n\nvoid CScriptVisorFlare::Render(CStateManager& stateMgr) { xe8_flare.Render(GetTranslation(), stateMgr); }\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptVisorFlare.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/World/CActor.hpp\"\n#include \"Runtime/World/CVisorFlare.hpp\"\n\nnamespace metaforce {\n\nclass CScriptVisorFlare : public CActor {\n  CVisorFlare xe8_flare;\n  bool x11c_notInRenderLast = true;\n\npublic:\n  DEFINE_ENTITY\n  CScriptVisorFlare(TUniqueId uid, std::string_view name, const CEntityInfo& info, bool active,\n                    const zeus::CVector3f& pos, CVisorFlare::EBlendMode blendMode, bool, float, float, float, u32, u32,\n                    std::vector<CVisorFlare::CFlareDef> flares);\n\n  void Accept(IVisitor& visitor) override;\n  void Think(float, CStateManager& stateMgr) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) override;\n  void PreRender(CStateManager&, const zeus::CFrustum&) override;\n  void AddToRenderer(const zeus::CFrustum&, CStateManager&) override;\n  void Render(CStateManager&) override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptVisorGoo.cpp",
    "content": "#include \"Runtime/World/CScriptVisorGoo.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/World/CHUDBillboardEffect.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCScriptVisorGoo::CScriptVisorGoo(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                                 const zeus::CTransform& xf, CAssetId particle, CAssetId electric, float minDist,\n                                 float maxDist, float nearProb, float farProb, const zeus::CColor& color, int sfx,\n                                 bool forceShow, bool active)\n: CActor(uid, active, name, info, xf, CModelData::CModelDataNull(), {}, CActorParameters::None(), kInvalidUniqueId)\n, xe8_particleDesc(CToken(TObjOwnerDerivedFromIObj<CGenDescription>::GetNewDerivedObject({})))\n, xf0_electricDesc(CToken(TObjOwnerDerivedFromIObj<CElectricDescription>::GetNewDerivedObject({})))\n, xf8_sfx(CSfxManager::TranslateSFXID(sfx))\n, xfc_particleId(particle)\n, x100_electricId(electric)\n, x104_minDist(minDist)\n, x108_maxDist(std::max(maxDist, minDist + 0.01f))\n, x10c_nearProb(nearProb)\n, x110_farProb(farProb)\n, x114_color(color) {\n  x118_24_angleTest = !forceShow;\n  if (particle.IsValid()) {\n    xe8_particleDesc = g_SimplePool->GetObj(SObjectTag{FOURCC('PART'), particle});\n  }\n  if (electric.IsValid()) {\n    xf0_electricDesc = g_SimplePool->GetObj(SObjectTag{FOURCC('ELSC'), electric});\n  }\n}\n\nvoid CScriptVisorGoo::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptVisorGoo::Think(float, CStateManager& mgr) {\n  if (GetActive()) {\n    bool loaded = false;\n    if (xfc_particleId.IsValid()) {\n      if (xe8_particleDesc.IsLoaded()) {\n        if (x100_electricId.IsValid()) {\n          loaded = xf0_electricDesc.IsLoaded();\n        } else {\n          loaded = true;\n        }\n      }\n    } else {\n      loaded = xf0_electricDesc.IsLoaded();\n    }\n\n    if (loaded) {\n      bool showGoo = false;\n      if (mgr.GetPlayer().GetCameraState() == CPlayer::EPlayerCameraState::FirstPerson) {\n        const zeus::CVector3f eyeToGoo = GetTranslation() - mgr.GetPlayer().GetEyePosition();\n        const float eyeToGooDist = eyeToGoo.magnitude();\n        if (eyeToGooDist >= x104_minDist && eyeToGooDist <= x108_maxDist) {\n          if (x118_24_angleTest) {\n            const float angle = zeus::radToDeg(\n                std::acos(mgr.GetCameraManager()->GetCurrentCameraTransform(mgr).basis[1].normalized().dot(\n                    eyeToGoo.normalized())));\n            float angleThresh = 45.f;\n            if (eyeToGooDist < 4.f) {\n              angleThresh *= 4.f / eyeToGooDist;\n              angleThresh = std::min(90.f, angleThresh);\n            }\n            if (angle <= angleThresh) {\n              showGoo = true;\n            }\n          } else {\n            showGoo = true;\n          }\n          if (showGoo) {\n            const float t = (x108_maxDist - eyeToGooDist) / (x108_maxDist - x104_minDist);\n            if (mgr.GetActiveRandom()->Float() * 100.f <= (1.f - t) * x110_farProb + t * x10c_nearProb) {\n              mgr.AddObject(new CHUDBillboardEffect(\n                  xfc_particleId.IsValid() ? std::make_optional(xe8_particleDesc) : std::nullopt,\n                  x100_electricId.IsValid() ? std::make_optional(xf0_electricDesc) : std::nullopt,\n                  mgr.AllocateUniqueId(), true, \"VisorGoo\", CHUDBillboardEffect::GetNearClipDistance(mgr),\n                  CHUDBillboardEffect::GetScaleForPOV(mgr), x114_color, zeus::skOne3f, zeus::skZero3f));\n              CSfxManager::SfxStart(xf8_sfx, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n            }\n          }\n        }\n      }\n      mgr.FreeScriptObject(GetUniqueId());\n    }\n  }\n}\n\nvoid CScriptVisorGoo::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& mgr) {\n  switch (msg) {\n  case EScriptObjectMessage::Activate:\n    if (xfc_particleId.IsValid()) {\n      xe8_particleDesc.Lock();\n    }\n    if (x100_electricId.IsValid()) {\n      xf0_electricDesc.Lock();\n    }\n    break;\n  default:\n    break;\n  }\n  CActor::AcceptScriptMsg(msg, objId, mgr);\n}\n\nvoid CScriptVisorGoo::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) {\n  // Empty\n}\n\nvoid CScriptVisorGoo::Render(CStateManager& mgr) {\n  // Empty\n}\n\nstd::optional<zeus::CAABox> CScriptVisorGoo::GetTouchBounds() const { return std::nullopt; }\n\nvoid CScriptVisorGoo::Touch(CActor& other, CStateManager& mgr) {\n  // Empty\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptVisorGoo.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/World/CActor.hpp\"\n\n#include <zeus/CColor.hpp>\n\nnamespace metaforce {\n\nclass CScriptVisorGoo : public CActor {\n  TToken<CGenDescription> xe8_particleDesc;\n  TToken<CElectricDescription> xf0_electricDesc;\n  u16 xf8_sfx;\n  CAssetId xfc_particleId;\n  CAssetId x100_electricId;\n  float x104_minDist;\n  float x108_maxDist;\n  float x10c_nearProb;\n  float x110_farProb;\n  zeus::CColor x114_color;\n  bool x118_24_angleTest : 1;\n\npublic:\n  DEFINE_ENTITY\n  CScriptVisorGoo(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                  CAssetId particle, CAssetId electric, float minDist, float maxDist, float nearProb, float farProb,\n                  const zeus::CColor& color, int sfx, bool forceShow, bool active);\n\n  void Accept(IVisitor& visitor) override;\n  void Think(float, CStateManager& stateMgr) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) override;\n  void AddToRenderer(const zeus::CFrustum&, CStateManager&) override;\n  void Render(CStateManager&) override;\n  std::optional<zeus::CAABox> GetTouchBounds() const override;\n  void Touch(CActor&, CStateManager&) override;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptWater.cpp",
    "content": "#include \"Runtime/World/CScriptWater.hpp\"\n\n#include <array>\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Camera/CGameCamera.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/World/CFluidPlaneGPU.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nconstexpr std::array kSplashScales{\n    1.0f, 3.0f, 0.709f, 1.19f, 0.709f, 1.f,\n};\n\nCScriptWater::CScriptWater(\n    CStateManager& mgr, TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CVector3f& pos,\n    const zeus::CAABox& box, const metaforce::CDamageInfo& dInfo, zeus::CVector3f& orientedForce,\n    ETriggerFlags triggerFlags, bool thermalCold, bool allowRender, CAssetId patternMap1, CAssetId patternMap2,\n    CAssetId colorMap, CAssetId bumpMap, CAssetId envMap, CAssetId envBumpMap, CAssetId unusedMap,\n    const zeus::CVector3f& bumpLightDir, float bumpScale, float morphInTime, float morphOutTime, bool active,\n    EFluidType fluidType, bool b4, float alpha, const CFluidUVMotion& uvMot, float turbSpeed, float turbDistance,\n    float turbFreqMax, float turbFreqMin, float turbPhaseMax, float turbPhaseMin, float turbAmplitudeMax,\n    float turbAmplitudeMin, const zeus::CColor& splashColor, const zeus::CColor& insideFogColor,\n    CAssetId splashParticle1, CAssetId splashParticle2, CAssetId splashParticle3, CAssetId visorRunoffParticle,\n    CAssetId unmorphVisorRunoffparticle, s32 visorRunoffSfx, s32 unmorphVisorRunoffSfx, s32 splashSfx1, s32 splashSfx2,\n    s32 splashSfx3, float tileSize, u32 tileSubdivisions, float specularMin, float specularMax, float reflectionSize,\n    float rippleIntensity, float reflectionBlend, float fogBias, float fogMagnitude, float fogSpeed,\n    const zeus::CColor& fogColor, CAssetId lightmapId, float unitsPerLightmapTexel, float alphaInTime,\n    float alphaOutTime, u32, u32, bool, s32, s32, std::unique_ptr<u32[]>&& u32Arr)\n: CScriptTrigger(uid, name, info, pos, box, dInfo, orientedForce, triggerFlags, active, false, false)\n, x1b8_positionMorphed(pos)\n, x1c4_extentMorphed(box.max - box.min)\n, x1d0_morphInTime(morphInTime)\n, x1d4_positionOrig(pos)\n, x1e0_extentOrig(box.max - box.min)\n, x1ec_damageOrig(dInfo.GetDamage())\n, x1f0_damageMorphed(dInfo.GetDamage())\n, x1f4_morphOutTime(morphOutTime)\n, x214_fogBias(fogBias)\n, x218_fogMagnitude(fogMagnitude)\n, x21c_origFogBias(fogBias)\n, x220_origFogMagnitude(fogMagnitude)\n, x224_fogSpeed(fogSpeed)\n, x228_fogColor(fogColor)\n, x22c_splashParticle1Id(splashParticle1)\n, x230_splashParticle2Id(splashParticle2)\n, x234_splashParticle3Id(splashParticle3)\n, x238_visorRunoffParticleId(visorRunoffParticle)\n, x24c_unmorphVisorRunoffParticleId(unmorphVisorRunoffparticle)\n, x260_visorRunoffSfx(CSfxManager::TranslateSFXID(visorRunoffSfx))\n, x262_unmorphVisorRunoffSfx(CSfxManager::TranslateSFXID(unmorphVisorRunoffSfx))\n, x2a4_splashColor(splashColor)\n, x2a8_insideFogColor(insideFogColor)\n, x2ac_alphaInTime(alphaInTime)\n, x2b0_alphaOutTime(alphaOutTime)\n, x2b4_alphaInRecip((alphaInTime != 0.f) ? 1.f / alphaInTime : 0.f)\n, x2b8_alphaOutRecip((alphaOutTime != 0.f) ? 1.f / alphaOutTime : 0.f)\n, x2bc_alpha(alpha)\n, x2c0_tileSize(tileSize)\n, x2e8_24_b4(b4)\n, x2e8_27_allowRender(allowRender) {\n  const zeus::CAABox triggerAABB = GetTriggerBoundsWR();\n  x2c4_gridDimX = u32((x2c0_tileSize + triggerAABB.max.x() - triggerAABB.min.x() - 0.01f) / x2c0_tileSize);\n  x2c8_gridDimY = u32((x2c0_tileSize + triggerAABB.max.y() - triggerAABB.min.y() - 0.01f) / x2c0_tileSize);\n  x2cc_gridCellCount = (x2c4_gridDimX + 1) * (x2c8_gridDimY + 1);\n\n  uint32_t maxPatchSize;\n//  if (CGraphics::g_BooFactory->isTessellationSupported(maxPatchSize)) {\n//    x1b4_fluidPlane = std::make_unique<CFluidPlaneGPU>(\n//        patternMap1, patternMap2, colorMap, bumpMap, envMap, envBumpMap, lightmapId, unitsPerLightmapTexel, tileSize,\n//        tileSubdivisions * 2, fluidType, x2bc_alpha, bumpLightDir, bumpScale, uvMot, turbSpeed, turbDistance,\n//        turbFreqMax, turbFreqMin, turbPhaseMax, turbPhaseMin, turbAmplitudeMax, turbAmplitudeMin, specularMin,\n//        specularMax, reflectionBlend, reflectionSize, rippleIntensity,\n//        x2cc_gridCellCount * ((std::max(u32(2), tileSubdivisions * 2) * 4 + 2) * 4));\n//  } else {\n    x1b4_fluidPlane = std::make_unique<CFluidPlaneCPU>(\n        patternMap1, patternMap2, colorMap, bumpMap, envMap, envBumpMap, lightmapId, unitsPerLightmapTexel, tileSize,\n        tileSubdivisions, fluidType, x2bc_alpha, bumpLightDir, bumpScale, uvMot, turbSpeed, turbDistance, turbFreqMax,\n        turbFreqMin, turbPhaseMax, turbPhaseMin, turbAmplitudeMax, turbAmplitudeMin, specularMin, specularMax,\n        reflectionBlend, reflectionSize, rippleIntensity,\n        x2cc_gridCellCount * ((std::max(u32(2), tileSubdivisions) * 4 + 2) * 4));\n//  }\n  u32Arr.reset();\n  x264_splashEffects.resize(3);\n  if (x22c_splashParticle1Id.IsValid()) {\n    x264_splashEffects[0].emplace(g_SimplePool->GetObj({FOURCC('PART'), x22c_splashParticle1Id}));\n  }\n  if (x230_splashParticle2Id.IsValid()) {\n    x264_splashEffects[1].emplace(g_SimplePool->GetObj({FOURCC('PART'), x230_splashParticle2Id}));\n  }\n  if (x234_splashParticle3Id.IsValid()) {\n    x264_splashEffects[2].emplace(g_SimplePool->GetObj({FOURCC('PART'), x234_splashParticle3Id}));\n  }\n  if (x238_visorRunoffParticleId.IsValid()) {\n    x23c_visorRunoffEffect.emplace(g_SimplePool->GetObj({FOURCC('PART'), x238_visorRunoffParticleId}));\n  }\n  if (x24c_unmorphVisorRunoffParticleId.IsValid()) {\n    x250_unmorphVisorRunoffEffect.emplace(g_SimplePool->GetObj({FOURCC('PART'), x24c_unmorphVisorRunoffParticleId}));\n  }\n  x298_splashSounds.push_back(CSfxManager::TranslateSFXID(splashSfx1));\n  x298_splashSounds.push_back(CSfxManager::TranslateSFXID(splashSfx2));\n  x298_splashSounds.push_back(CSfxManager::TranslateSFXID(splashSfx3));\n  SetCalculateLighting(true);\n  if (lightmapId.IsValid()) {\n    x90_actorLights->DisableAreaLights();\n  }\n  x90_actorLights->SetMaxDynamicLights(4);\n  x90_actorLights->SetCastShadows(false);\n  x90_actorLights->SetAmbienceGenerated(false);\n  x90_actorLights->SetFindNearestDynamicLights(true);\n  x148_24_detectCamera = true;\n  CScriptWater::CalculateRenderBounds();\n  xe6_27_thermalVisorFlags = u8(thermalCold ? 2 : 1);\n  if (!x30_24_active) {\n    x2bc_alpha = 0.f;\n    x214_fogBias = 0.f;\n    x218_fogMagnitude = 0.f;\n  }\n  SetupGrid(true);\n}\n\nvoid CScriptWater::SetupGrid(bool recomputeClipping) {\n  const zeus::CAABox triggerAABB = GetTriggerBoundsWR();\n  const auto dimX = u32((triggerAABB.max.x() - triggerAABB.min.x() + x2c0_tileSize) / x2c0_tileSize);\n  const auto dimY = u32((triggerAABB.max.y() - triggerAABB.min.y() + x2c0_tileSize) / x2c0_tileSize);\n  x2e4_computedGridCellCount = x2cc_gridCellCount = (dimX + 1) * (dimY + 1);\n  x2dc_vertIntersects.reset();\n  if (!x2d8_tileIntersects || dimX != x2c4_gridDimX || dimY != x2c8_gridDimY) {\n    x2d8_tileIntersects.reset(new bool[x2cc_gridCellCount]);\n  }\n  x2c4_gridDimX = dimX;\n  x2c8_gridDimY = dimY;\n  for (int i = 0; i < x2c8_gridDimY; ++i) {\n    for (int j = 0; j < x2c4_gridDimX; ++j) {\n      x2d8_tileIntersects[i * x2c4_gridDimX + j] = true;\n    }\n  }\n  if (!x2e0_patchIntersects || x2d0_patchDimX != 0 || x2d4_patchDimY != 0) {\n    x2e0_patchIntersects.reset(new u8[32]);\n  }\n  for (int i = 0; i < 32; ++i) {\n    x2e0_patchIntersects[i] = 1;\n  }\n  x2d4_patchDimY = 0;\n  x2d0_patchDimX = 0;\n  x2e8_28_recomputeClipping = recomputeClipping;\n}\n\nconstexpr CMaterialFilter SolidFilter = CMaterialFilter::MakeInclude({EMaterialTypes::Solid});\n\nvoid CScriptWater::SetupGridClipping(CStateManager& mgr, int computeVerts) {\n  if (x2e8_28_recomputeClipping) {\n    x2e4_computedGridCellCount = 0;\n    x2dc_vertIntersects.reset();\n    x2e8_28_recomputeClipping = false;\n  }\n\n  if (x2e4_computedGridCellCount >= x2cc_gridCellCount) {\n    return;\n  }\n\n  if (!x2dc_vertIntersects) {\n    x2dc_vertIntersects.reset(new bool[(x2c4_gridDimX + 1) * (x2c8_gridDimY + 1)]);\n  }\n\n  const zeus::CAABox triggerBounds = GetTriggerBoundsWR();\n  zeus::CVector3f basePos = triggerBounds.min;\n  basePos.z() = triggerBounds.max.z() + 0.8f;\n  auto gridDiv = std::div(x2e4_computedGridCellCount, x2c4_gridDimX + 1);\n  float yOffset = x2c0_tileSize * gridDiv.quot;\n  float xOffset = x2c0_tileSize * gridDiv.rem;\n  float mag = std::min(120.f, 2.f * (x130_bounds.max.z() - x130_bounds.min.z()) + 0.8f);\n  for (int i = x2e4_computedGridCellCount; i < std::min(x2e4_computedGridCellCount + computeVerts, x2cc_gridCellCount);\n       ++i) {\n    zeus::CVector3f pos = basePos;\n    pos.x() += xOffset;\n    pos.y() += yOffset;\n    x2dc_vertIntersects[i] = mgr.RayStaticIntersection(pos, zeus::skDown, mag, SolidFilter).IsValid();\n    gridDiv.rem += 1;\n    xOffset += x2c0_tileSize;\n    if (gridDiv.rem > x2c4_gridDimX) {\n      yOffset += x2c0_tileSize;\n      xOffset = 0.f;\n      gridDiv.rem = 0;\n    }\n  }\n  x2e4_computedGridCellCount += computeVerts;\n  if (x2e4_computedGridCellCount < x2cc_gridCellCount) {\n    return;\n  }\n\n  x2e4_computedGridCellCount = x2cc_gridCellCount;\n  x2d8_tileIntersects.reset(new bool[x2cc_gridCellCount]);\n\n  for (int i = 0; i < x2c8_gridDimY; ++i) {\n    const int rowBase = x2c4_gridDimX * i;\n    const int nextRowBase = (x2c4_gridDimX + 1) * i;\n    for (int j = 0; j < x2c4_gridDimX; ++j) {\n      x2d8_tileIntersects[rowBase + j] = x2dc_vertIntersects[nextRowBase + j] ||\n                                         x2dc_vertIntersects[nextRowBase + j + 1] ||\n                                         x2dc_vertIntersects[nextRowBase + j + x2c4_gridDimX + 1] ||\n                                         x2dc_vertIntersects[nextRowBase + j + x2c4_gridDimX + 2];\n    }\n  }\n\n  const int tilesPerPatch = std::min(42 / x1b4_fluidPlane->GetTileSubdivisions(), 7);\n  x2d0_patchDimX = (tilesPerPatch + x2c4_gridDimX - 1) / tilesPerPatch;\n  x2d4_patchDimY = (tilesPerPatch + x2c8_gridDimY - 1) / tilesPerPatch;\n  x2e0_patchIntersects.reset(new u8[x2d0_patchDimX * x2d4_patchDimY]);\n\n  int curTileY = 0;\n  int nextTileY;\n  for (int i = 0; i < x2d4_patchDimY; ++i, curTileY = nextTileY) {\n    nextTileY = curTileY + tilesPerPatch;\n    int curTileX = 0;\n    const int rowBase = x2d0_patchDimX * i;\n    for (int j = 0; j < x2d0_patchDimX; ++j) {\n      int nextTileX = curTileX + tilesPerPatch;\n      bool allClear = true;\n      bool allIntersections = true;\n      for (int k = curTileY; k < std::min(nextTileY, x2c8_gridDimY); ++k) {\n        if (!allClear && !allIntersections) {\n          break;\n        }\n        for (int l = curTileX; l < std::min(nextTileX, x2c4_gridDimX); ++l) {\n          if (x2d8_tileIntersects[k * x2c4_gridDimX + l]) {\n            allClear = false;\n            if (!allIntersections) {\n              break;\n            }\n          } else {\n            allIntersections = false;\n            if (!allClear) {\n              break;\n            }\n          }\n        }\n      }\n\n      u8 flag = 2;\n      if (allIntersections) {\n        flag = 1;\n      } else if (allClear) {\n        flag = 0;\n      }\n\n      x2e0_patchIntersects[rowBase + j] = flag;\n      curTileX += tilesPerPatch;\n    }\n  }\n\n  x2dc_vertIntersects.reset();\n}\n\nvoid CScriptWater::UpdateSplashInhabitants(CStateManager& mgr) {\n  for (auto it = x1fc_waterInhabitants.begin(); it != x1fc_waterInhabitants.end();) {\n    auto& inhab = *it;\n    const TCastToPtr<CActor> act = mgr.ObjectById(inhab.first);\n    bool intersects = false;\n    if (act) {\n      if (const auto tb = act->GetTouchBounds()) {\n        const zeus::CAABox thisTb = GetTriggerBoundsWR();\n        if (tb->min.z() <= thisTb.max.z() && tb->max.z() >= thisTb.max.z()) {\n          intersects = true;\n        }\n      }\n    }\n\n    if (act && inhab.second) {\n      if (intersects) {\n        act->FluidFXThink(EFluidState::InFluid, *this, mgr);\n      }\n      mgr.SendScriptMsg(act.GetPtr(), GetUniqueId(), EScriptObjectMessage::UpdateSplashInhabitant);\n      inhab.second = false;\n    } else {\n      it = x1fc_waterInhabitants.erase(it);\n      if (act) {\n        if (intersects) {\n          act->FluidFXThink(EFluidState::LeftFluid, *this, mgr);\n        }\n        mgr.SendScriptMsg(act.GetPtr(), GetUniqueId(), EScriptObjectMessage::RemoveSplashInhabitant);\n      }\n      continue;\n    }\n    ++it;\n  }\n}\n\nvoid CScriptWater::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptWater::Think(float dt, CStateManager& mgr) {\n  if (!x30_24_active) {\n    return;\n  }\n\n  const bool oldCamSubmerged = x148_25_camSubmerged;\n  CScriptTrigger::Think(dt, mgr);\n\n  CGameCamera* curCam = mgr.GetCameraManager()->GetCurrentCamera(mgr);\n  if (x148_25_camSubmerged && !oldCamSubmerged) {\n    mgr.SendScriptMsg(curCam, x8_uid, EScriptObjectMessage::AddSplashInhabitant);\n  } else if (!x148_25_camSubmerged && oldCamSubmerged) {\n    mgr.SendScriptMsg(curCam, x8_uid, EScriptObjectMessage::RemoveSplashInhabitant);\n  }\n\n  UpdateSplashInhabitants(mgr);\n\n  if (x2e8_30_alphaOut) {\n    x2bc_alpha -= x2b8_alphaOutRecip * dt * x1b4_fluidPlane->GetAlpha();\n    x214_fogBias -= x2b8_alphaOutRecip * dt * x21c_origFogBias;\n    x218_fogMagnitude -= x2b8_alphaOutRecip * dt * x220_origFogMagnitude;\n    if (x2bc_alpha <= 0.f) {\n      x218_fogMagnitude = 0.f;\n      x214_fogBias = 0.f;\n      x2bc_alpha = 0.f;\n      x2e8_30_alphaOut = false;\n    }\n  } else if (x2e8_29_alphaIn) {\n    x2bc_alpha += x2b4_alphaInRecip * dt * x1b4_fluidPlane->GetAlpha();\n    x214_fogBias -= x2b4_alphaInRecip * dt * x21c_origFogBias;\n    x218_fogMagnitude -= x2b4_alphaInRecip * dt * x220_origFogMagnitude;\n    if (x2bc_alpha > x1b4_fluidPlane->GetAlpha()) {\n      x2bc_alpha = x1b4_fluidPlane->GetAlpha();\n      x214_fogBias = x21c_origFogBias;\n      x218_fogMagnitude = x220_origFogMagnitude;\n      x2e8_29_alphaIn = false;\n    }\n  }\n\n  if (x2e8_26_morphing) {\n    bool stillMorphing = true;\n    if (x2e8_25_morphIn) {\n      x1f8_morphFactor += dt / x1d0_morphInTime;\n      if (x1f8_morphFactor > 1.f) {\n        x1f8_morphFactor = 1.f;\n        stillMorphing = false;\n      }\n    } else {\n      x1f8_morphFactor -= dt / x1f4_morphOutTime;\n      if (x1f8_morphFactor < 0.f) {\n        x1f8_morphFactor = 0.f;\n        stillMorphing = false;\n      }\n    }\n\n    SetTranslation(zeus::CVector3f::lerp(x1d4_positionOrig, x1b8_positionMorphed, x1f8_morphFactor));\n    const zeus::CVector3f lerpExtent = zeus::CVector3f::lerp(x1e0_extentOrig, x1c4_extentMorphed, x1f8_morphFactor);\n    x130_bounds = zeus::CAABox(lerpExtent * -0.5f, lerpExtent * 0.5f);\n    CalculateRenderBounds();\n\n    if (!stillMorphing) {\n      SetMorphing(false);\n    } else {\n      SetupGrid(false);\n    }\n  }\n\n  SetupGridClipping(mgr, 4);\n}\n\nvoid CScriptWater::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId other, CStateManager& mgr) {\n  switch (msg) {\n  case EScriptObjectMessage::Next:\n    if (!x30_24_active) {\n      break;\n    }\n    x2e8_25_morphIn = !x2e8_25_morphIn;\n    if (x2e8_25_morphIn) {\n      for (const SConnection& conn : x20_conns) {\n        if (conn.x0_state != EScriptObjectState::Play || conn.x4_msg != EScriptObjectMessage::Activate) {\n          continue;\n        }\n        const auto list = mgr.GetIdListForScript(conn.x8_objId);\n        if (list.first == mgr.GetIdListEnd()) {\n          continue;\n        }\n        if (const TCastToConstPtr<CScriptTrigger> trig = mgr.GetObjectById(list.first->second)) {\n          x1b8_positionMorphed = trig->GetTranslation();\n          x1c4_extentMorphed = trig->GetTriggerBoundsOR().max - trig->GetTriggerBoundsOR().min;\n          x1f0_damageMorphed = trig->GetDamageInfo().GetDamage();\n          x1d4_positionOrig = GetTranslation();\n          x1e0_extentOrig = x130_bounds.max - x130_bounds.min;\n          x1ec_damageOrig = x100_damageInfo.GetDamage();\n          break;\n        }\n      }\n    }\n    SetMorphing(true);\n    break;\n  case EScriptObjectMessage::Activate:\n    x2e8_30_alphaOut = false;\n    if (std::fabs(x2ac_alphaInTime) < 0.00001f) {\n      x2bc_alpha = x1b4_fluidPlane->GetAlpha();\n      x214_fogBias = x21c_origFogBias;\n      x218_fogMagnitude = x220_origFogMagnitude;\n    } else {\n      x2e8_29_alphaIn = true;\n    }\n    break;\n  case EScriptObjectMessage::Action:\n    x2e8_29_alphaIn = false;\n    if (std::fabs(x2b0_alphaOutTime) < 0.00001f) {\n      x2bc_alpha = 0.f;\n      x214_fogBias = 0.f;\n      x218_fogMagnitude = 0.f;\n    } else {\n      x2e8_30_alphaOut = true;\n    }\n    break;\n  default:\n    break;\n  }\n\n  CScriptTrigger::AcceptScriptMsg(msg, other, mgr);\n}\n\nvoid CScriptWater::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) {\n  if (!x2e8_27_allowRender) {\n    xe4_30_outOfFrustum = true;\n    return;\n  }\n\n  const zeus::CAABox aabb = GetSortingBounds(mgr);\n  xe4_30_outOfFrustum = !frustum.aabbFrustumTest(aabb);\n  if (xe4_30_outOfFrustum) {\n    return;\n  }\n\n  if (x4_areaId != kInvalidAreaId) {\n    if (x90_actorLights->GetMaxAreaLights() && (xe4_29_actorLightsDirty || x90_actorLights->GetIsDirty())) {\n      const CGameArea* area = mgr.GetWorld()->GetAreaAlways(x4_areaId);\n      if (area->IsPostConstructed()) {\n        x90_actorLights->BuildAreaLightList(mgr, *area, GetTriggerBoundsWR());\n        xe4_29_actorLightsDirty = false;\n      }\n    }\n    x90_actorLights->BuildDynamicLightList(mgr, GetTriggerBoundsWR());\n  }\n  x150_frustum = frustum;\n}\n\nvoid CScriptWater::AddToRenderer(const zeus::CFrustum& /*frustum*/, CStateManager& mgr) {\n  if (xe4_30_outOfFrustum) {\n    return;\n  }\n\n  const zeus::CPlane plane(zeus::skUp, x34_transform.origin.z() + x130_bounds.max.z());\n  const zeus::CAABox renderBounds = GetSortingBounds(mgr);\n  mgr.AddDrawableActorPlane(*this, plane, renderBounds);\n}\n\nvoid CScriptWater::Render(CStateManager& mgr) {\n  if (x30_24_active && !xe4_30_outOfFrustum) {\n    const float zOffset = 0.5f * (x9c_renderBounds.max.z() + x9c_renderBounds.min.z()) - x34_transform.origin.z();\n    const zeus::CAABox aabb = x9c_renderBounds.getTransformedAABox(zeus::CTransform::Translate(\n        -x34_transform.origin.x(), -x34_transform.origin.y(), -x34_transform.origin.z() - zOffset));\n    zeus::CTransform xf = x34_transform;\n    xf.origin.z() += zOffset;\n    const zeus::CVector3f areaCenter = mgr.GetWorld()->GetAreaAlways(mgr.GetNextAreaId())->GetAABB().center();\n    const std::optional<CRippleManager> rippleMan(mgr.GetFluidPlaneManager()->GetRippleManager());\n    x1b4_fluidPlane->Render(mgr, x2bc_alpha, aabb, xf, mgr.GetWorld()->GetAreaAlways(x4_areaId)->GetTransform(), false,\n                            x150_frustum, rippleMan, x8_uid, x2d8_tileIntersects.get(), x2c4_gridDimX, x2c8_gridDimY,\n                            areaCenter);\n    if (x214_fogBias != 0.f) {\n      if (mgr.GetPlayerState()->CanVisorSeeFog(mgr)) {\n        const float fogLevel = mgr.IntegrateVisorFog(\n            x218_fogMagnitude * std::sin(x224_fogSpeed * CGraphics::GetSecondsMod900()) + x214_fogBias);\n        if (fogLevel > 0.f) {\n          zeus::CAABox fogBox = GetTriggerBoundsWR();\n          fogBox.min.z() = float(fogBox.max.z());\n          fogBox.max.z() += fogLevel;\n          const zeus::CTransform modelXf =\n              zeus::CTransform::Translate(fogBox.center()) * zeus::CTransform::Scale((fogBox.max - fogBox.min) * 0.5f);\n          const zeus::CAABox renderAABB(zeus::skNegOne3f, zeus::skOne3f);\n          CGraphics::SetModelMatrix(modelXf);\n          g_Renderer->SetAmbientColor(zeus::skWhite);\n          g_Renderer->RenderFogVolume(x228_fogColor, renderAABB, nullptr, nullptr);\n        }\n      }\n    }\n    CGraphics::DisableAllLights();\n  }\n  CActor::Render(mgr);\n}\n\nvoid CScriptWater::Touch(CActor& otherAct, CStateManager& mgr) {\n  if (!x30_24_active) {\n    return;\n  }\n\n  CScriptTrigger::Touch(otherAct, mgr);\n  if (otherAct.GetMaterialList().HasMaterial(EMaterialTypes::Trigger)) {\n    return;\n  }\n\n  for (auto& inhab : x1fc_waterInhabitants)\n    if (inhab.first == otherAct.GetUniqueId()) {\n      inhab.second = true;\n      return;\n    }\n\n  const auto touchBounds = otherAct.GetTouchBounds();\n  if (!touchBounds) {\n    return;\n  }\n\n  x1fc_waterInhabitants.emplace_back(otherAct.GetUniqueId(), true);\n  const float triggerMaxZ = GetTriggerBoundsWR().max.z();\n  if (touchBounds->min.z() <= triggerMaxZ && touchBounds->max.z() >= triggerMaxZ) {\n    otherAct.FluidFXThink(EFluidState::EnteredFluid, *this, mgr);\n  }\n\n  mgr.SendScriptMsg(&otherAct, x8_uid, EScriptObjectMessage::AddSplashInhabitant);\n}\n\nvoid CScriptWater::CalculateRenderBounds() {\n  zeus::CVector3f aabbMin = x130_bounds.min;\n  aabbMin.z() = x130_bounds.max.z() - 1.f;\n  zeus::CVector3f aabbMax = x130_bounds.max;\n  aabbMax.z() += 1.f;\n  const zeus::CVector3f transAABBMin = aabbMin + GetTranslation();\n  const zeus::CVector3f transAABBMax = aabbMax + GetTranslation();\n  x9c_renderBounds = zeus::CAABox(transAABBMin, transAABBMax);\n}\n\nzeus::CAABox CScriptWater::GetSortingBounds(const CStateManager& mgr) const {\n  zeus::CVector3f max = x9c_renderBounds.max;\n  max.z() = std::max(float(max.z()), x9c_renderBounds.max.z() - 1.f + x214_fogBias + x218_fogMagnitude);\n  return {x9c_renderBounds.min, max};\n}\n\nEWeaponCollisionResponseTypes CScriptWater::GetCollisionResponseType(const zeus::CVector3f&, const zeus::CVector3f&,\n                                                                     const CWeaponMode&, EProjectileAttrib) const {\n  return EWeaponCollisionResponseTypes::Water;\n}\n\nu16 CScriptWater::GetSplashSound(float mag) const { return x298_splashSounds[GetSplashIndex(mag)]; }\n\nconst std::optional<TLockedToken<CGenDescription>>& CScriptWater::GetSplashEffect(float mag) const {\n  return x264_splashEffects[GetSplashIndex(mag)];\n}\n\nfloat CScriptWater::GetSplashEffectScale(float dt) const {\n  if (std::fabs(dt - 1.f) < 0.00001f) {\n    return kSplashScales[5];\n  }\n\n  const u32 idx = GetSplashIndex(dt);\n  const float s = dt - std::floor(dt * 3.f);\n  return ((1.f - s) * (s * kSplashScales[idx * 2])) + kSplashScales[idx];\n}\n\nu32 CScriptWater::GetSplashIndex(float mag) const {\n  const auto idx = u32(mag * 3.f);\n  return (idx < 3 ? idx : idx - 1);\n}\n\nvoid CScriptWater::SetMorphing(bool m) {\n  if (m == x2e8_26_morphing) {\n    return;\n  }\n\n  x2e8_26_morphing = m;\n  SetupGrid(!m);\n}\n\nconst CScriptWater* CScriptWater::GetNextConnectedWater(const CStateManager& mgr) const {\n  for (const SConnection& conn : x20_conns) {\n    if (conn.x0_state != EScriptObjectState::Play || conn.x4_msg != EScriptObjectMessage::Activate) {\n      continue;\n    }\n    const auto its = mgr.GetIdListForScript(conn.x8_objId);\n    if (its.first != mgr.GetIdListEnd()) {\n      if (const TCastToConstPtr<CScriptWater> water = mgr.GetObjectById(its.first->second)) {\n        return water.GetPtr();\n      }\n    }\n  }\n  return nullptr;\n}\n\nbool CScriptWater::CanRippleAtPoint(const zeus::CVector3f& point) const {\n  if (!x2d8_tileIntersects) {\n    return true;\n  }\n\n  const auto xTile = int((point.x() - GetTriggerBoundsWR().min.x()) / x2c0_tileSize);\n  if (xTile < 0 || xTile >= x2c4_gridDimX) {\n    return false;\n  }\n\n  const auto yTile = int((point.y() - GetTriggerBoundsWR().min.y()) / x2c0_tileSize);\n  if (yTile < 0 || yTile >= x2c8_gridDimY) {\n    return false;\n  }\n\n  return x2d8_tileIntersects[yTile * x2c4_gridDimX + xTile];\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptWater.hpp",
    "content": "#pragma once\n\n#include <list>\n#include <memory>\n#include <optional>\n#include <utility>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/World/CFluidPlaneCPU.hpp\"\n#include \"Runtime/World/CScriptTrigger.hpp\"\n\n#include <zeus/CColor.hpp>\n#include <zeus/CFrustum.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\n\nclass CDamageInfo;\nclass CFluidUVMotion;\n\nclass CScriptWater : public CScriptTrigger {\n  zeus::CFrustum x150_frustum;\n  std::unique_ptr<CFluidPlaneCPU> x1b4_fluidPlane;\n  zeus::CVector3f x1b8_positionMorphed;\n  zeus::CVector3f x1c4_extentMorphed;\n  float x1d0_morphInTime;\n  zeus::CVector3f x1d4_positionOrig;\n  zeus::CVector3f x1e0_extentOrig;\n  float x1ec_damageOrig;\n  float x1f0_damageMorphed;\n  float x1f4_morphOutTime;\n  float x1f8_morphFactor = 0.f;\n  std::list<std::pair<TUniqueId, bool>> x1fc_waterInhabitants;\n  float x214_fogBias;\n  float x218_fogMagnitude;\n  float x21c_origFogBias;\n  float x220_origFogMagnitude;\n  float x224_fogSpeed;\n  zeus::CColor x228_fogColor;\n  CAssetId x22c_splashParticle1Id;\n  CAssetId x230_splashParticle2Id;\n  CAssetId x234_splashParticle3Id;\n  CAssetId x238_visorRunoffParticleId;\n  std::optional<TLockedToken<CGenDescription>> x23c_visorRunoffEffect;\n  CAssetId x24c_unmorphVisorRunoffParticleId;\n  std::optional<TLockedToken<CGenDescription>> x250_unmorphVisorRunoffEffect;\n  u16 x260_visorRunoffSfx;\n  u16 x262_unmorphVisorRunoffSfx;\n  rstl::reserved_vector<std::optional<TLockedToken<CGenDescription>>, 3> x264_splashEffects;\n  rstl::reserved_vector<u16, 3> x298_splashSounds;\n  zeus::CColor x2a4_splashColor;\n  zeus::CColor x2a8_insideFogColor;\n  float x2ac_alphaInTime;\n  float x2b0_alphaOutTime;\n  float x2b4_alphaInRecip;\n  float x2b8_alphaOutRecip;\n  float x2bc_alpha;\n  float x2c0_tileSize;\n  int x2c4_gridDimX = 0;\n  int x2c8_gridDimY = 0;\n  int x2cc_gridCellCount = 0;\n  int x2d0_patchDimX = 0;\n  int x2d4_patchDimY = 0;\n  std::unique_ptr<bool[]> x2d8_tileIntersects;\n  std::unique_ptr<bool[]> x2dc_vertIntersects;\n  std::unique_ptr<u8[]> x2e0_patchIntersects; // 0: all clear, 1: all intersect, 2: partial intersect\n  int x2e4_computedGridCellCount = 0;\n  bool x2e8_24_b4 : 1;\n  bool x2e8_25_morphIn : 1 = false;\n  bool x2e8_26_morphing : 1 = false;\n  bool x2e8_27_allowRender : 1;\n  bool x2e8_28_recomputeClipping : 1 = true;\n  bool x2e8_29_alphaIn : 1 = false;\n  bool x2e8_30_alphaOut : 1 = false;\n\n  void SetupGrid(bool recomputeClipping);\n  void SetupGridClipping(CStateManager& mgr, int computeVerts);\n  void UpdateSplashInhabitants(CStateManager& mgr);\n\npublic:\n  DEFINE_ENTITY\n  CScriptWater(CStateManager& mgr, TUniqueId uid, std::string_view name, const CEntityInfo& info,\n               const zeus::CVector3f& pos, const zeus::CAABox& box, const metaforce::CDamageInfo& dInfo,\n               zeus::CVector3f& orientedForce, ETriggerFlags triggerFlags, bool thermalCold, bool allowRender,\n               CAssetId patternMap1, CAssetId patternMap2, CAssetId colorMap, CAssetId bumpMap, CAssetId envMap,\n               CAssetId envBumpMap, CAssetId unusedMap, const zeus::CVector3f& bumpLightDir, float bumpScale,\n               float morphInTime, float morphOutTime, bool active, EFluidType fluidType, bool b4, float alpha,\n               const CFluidUVMotion& uvMot, float turbSpeed, float turbDistance, float turbFreqMax, float turbFreqMin,\n               float turbPhaseMax, float turbPhaseMin, float turbAmplitudeMax, float turbAmplitudeMin,\n               const zeus::CColor& splashColor, const zeus::CColor& insideFogColor, CAssetId splashParticle1,\n               CAssetId splashParticle2, CAssetId splashParticle3, CAssetId visorRunoffParticle,\n               CAssetId unmorphVisorRunoffparticle, s32 visorRunoffSfx, s32 unmorphVisorRunoffSfx, s32 splashSfx1,\n               s32 splashSfx2, s32 splashSfx3, float tileSize, u32 tileSubdivisions, float specularMin,\n               float specularMax, float reflectionSize, float rippleIntensity, float reflectionBlend, float fogBias,\n               float fogMagnitude, float fogSpeed, const zeus::CColor& fogColor, CAssetId lightmapId,\n               float unitsPerLightmapTexel, float alphaInTime, float alphaOutTime, u32, u32, bool, s32, s32,\n               std::unique_ptr<u32[]>&& u32Arr);\n\n  void Accept(IVisitor& visitor) override;\n  void Think(float, CStateManager&) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void PreRender(CStateManager&, const zeus::CFrustum&) override;\n  void AddToRenderer(const zeus::CFrustum&, CStateManager&) override;\n  void Render(CStateManager&) override;\n  void Touch(CActor&, CStateManager&) override;\n  void CalculateRenderBounds() override;\n  zeus::CAABox GetSortingBounds(const CStateManager&) const override;\n  EWeaponCollisionResponseTypes GetCollisionResponseType(const zeus::CVector3f&, const zeus::CVector3f&,\n                                                         const CWeaponMode&, EProjectileAttrib) const override;\n\n  u16 GetSplashSound(float) const;\n  const std::optional<TLockedToken<CGenDescription>>& GetSplashEffect(float) const;\n  float GetSplashEffectScale(float) const;\n  u32 GetSplashIndex(float) const;\n  CFluidPlaneCPU& FluidPlane() { return *x1b4_fluidPlane; }\n  zeus::CPlane GetWRSurfacePlane() const;\n  float GetSurfaceZ() const;\n  bool IsMorphing() const { return x2e8_26_morphing; }\n  void SetMorphing(bool);\n  float GetMorphFactor() const { return x1f8_morphFactor; }\n  zeus::CColor GetSplashColor() const { return x2a4_splashColor; }\n  void SetFrustumPlanes(const zeus::CFrustum& frustum) { x150_frustum = frustum; }\n  const zeus::CFrustum& GetFrustumPlanes() const { return x150_frustum; }\n  CFluidPlaneCPU& GetFluidPlane() const { return *x1b4_fluidPlane; }\n  const std::optional<TLockedToken<CGenDescription>>& GetVisorRunoffEffect() const { return x23c_visorRunoffEffect; }\n  u16 GetVisorRunoffSfx() const { return x260_visorRunoffSfx; }\n  const std::optional<TLockedToken<CGenDescription>>& GetUnmorphVisorRunoffEffect() const {\n    return x250_unmorphVisorRunoffEffect;\n  }\n  u16 GetUnmorphVisorRunoffSfx() const { return x262_unmorphVisorRunoffSfx; }\n  const CScriptWater* GetNextConnectedWater(const CStateManager& mgr) const;\n  u8 GetPatchRenderFlags(int x, int y) const { return x2e0_patchIntersects[y * x2d0_patchDimX + x]; }\n  int GetPatchDimensionX() const { return x2d0_patchDimX; }\n  int GetPatchDimensionY() const { return x2d4_patchDimY; }\n  bool CanRippleAtPoint(const zeus::CVector3f& point) const;\n  const zeus::CColor& GetInsideFogColor() const { return x2a8_insideFogColor; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptWaypoint.cpp",
    "content": "#include \"Runtime/World/CScriptWaypoint.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nCScriptWaypoint::CScriptWaypoint(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                                 const zeus::CTransform& xf, bool active, float speed, float pause,\n                                 u32 patternTranslate, u32 patternOrient, u32 patternFit, u32 behaviour,\n                                 u32 behaviourOrient, u32 behaviourModifiers, u32 animation)\n: CActor(uid, active, name, info, xf, CModelData(), CMaterialList(), CActorParameters::None(), kInvalidUniqueId)\n, xe8_speed(speed)\n, xec_animation(animation)\n, xf0_pause(pause)\n, xf4_patternTranslate(patternTranslate)\n, xf5_patternOrient(patternOrient)\n, xf6_patternFit(patternFit)\n, xf7_behaviour(behaviour)\n, xf8_behaviourOrient(behaviourOrient)\n, xfa_behaviourModifiers(behaviourModifiers) {\n  SetUseInSortedLists(false);\n  SetCallTouch(false);\n}\n\nvoid CScriptWaypoint::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptWaypoint::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) {\n  CActor::AcceptScriptMsg(msg, sender, mgr);\n  if (GetActive() && msg == EScriptObjectMessage::Arrived)\n    SendScriptMsgs(EScriptObjectState::Arrived, mgr, EScriptObjectMessage::None);\n}\n\nvoid CScriptWaypoint::AddToRenderer(const zeus::CFrustum&, CStateManager&) {\n  // Empty\n}\n\nTUniqueId CScriptWaypoint::FollowWaypoint(CStateManager& mgr) const {\n  for (const SConnection& conn : x20_conns) {\n    if (conn.x0_state == EScriptObjectState::Arrived && conn.x4_msg == EScriptObjectMessage::Follow) {\n      return mgr.GetIdForScript(conn.x8_objId);\n    }\n  }\n  return kInvalidUniqueId;\n}\n\nTUniqueId CScriptWaypoint::NextWaypoint(CStateManager& mgr) const {\n  rstl::reserved_vector<TUniqueId, 10> ids;\n  for (const SConnection& conn : x20_conns) {\n    if (conn.x0_state == EScriptObjectState::Arrived && conn.x4_msg == EScriptObjectMessage::Next) {\n      const TUniqueId id = mgr.GetIdForScript(conn.x8_objId);\n      if (id != kInvalidUniqueId) {\n        if (const TCastToConstPtr<CScriptWaypoint> wp = mgr.GetObjectById(id)) {\n          if (wp->GetActive()) {\n            ids.push_back(wp->GetUniqueId());\n          }\n        }\n      }\n    }\n  }\n\n  if (ids.empty()) {\n    return kInvalidUniqueId;\n  }\n\n  return ids[int(mgr.GetActiveRandom()->Float() * ids.size() * 0.99f)];\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptWaypoint.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/World/CActor.hpp\"\n\nnamespace metaforce {\n\nclass CScriptWaypoint : public CActor {\n  float xe8_speed;\n  u32 xec_animation;\n  float xf0_pause;\n  u8 xf4_patternTranslate;\n  u8 xf5_patternOrient;\n  u8 xf6_patternFit;\n  u8 xf7_behaviour;\n  u8 xf8_behaviourOrient;\n  u16 xfa_behaviourModifiers;\n\npublic:\n  DEFINE_ENTITY\n  CScriptWaypoint(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,\n                  bool active, float speed, float pause, u32 patternTranslate, u32 patternOrient, u32 patternFit,\n                  u32 behaviour, u32 behaviourOrient, u32 behaviourModifiers, u32 animation);\n\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) override;\n  void AddToRenderer(const zeus::CFrustum&, CStateManager&) override;\n  TUniqueId FollowWaypoint(CStateManager& mgr) const;\n  TUniqueId NextWaypoint(CStateManager& mgr) const;\n  float GetSpeed() const { return xe8_speed; }\n  u32 GetAnimation() const { return xec_animation; }\n  float GetPause() const { return xf0_pause; }\n  u8 GetPatternTranslate() const { return xf4_patternTranslate; }\n  u8 GetPatternOrient() const { return xf5_patternOrient; }\n  u8 GetPatternFit() const { return xf6_patternFit; }\n  u8 GetBehaviour() const { return xf7_behaviour; }\n  u8 GetBehaviourOrient() const { return xf8_behaviourOrient; }\n  u16 GetBehaviourModifiers() const { return xfa_behaviourModifiers; }\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptWorldTeleporter.cpp",
    "content": "#include \"Runtime/World/CScriptWorldTeleporter.hpp\"\n\n#include \"Runtime/CGameState.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/IMain.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n#include \"Runtime/World/CWorldTransManager.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nCScriptWorldTeleporter::CScriptWorldTeleporter(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                                               bool active, CAssetId worldId, CAssetId areaId)\n: CEntity(uid, info, active, name), x34_worldId(worldId), x38_areaId(areaId) {}\n\nCScriptWorldTeleporter::CScriptWorldTeleporter(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                                               bool active, CAssetId worldId, CAssetId areaId, CAssetId playerAncs,\n                                               u32 charIdx, u32 defaultAnim, const zeus::CVector3f& playerScale,\n                                               CAssetId platformModel, const zeus::CVector3f& platformScale,\n                                               CAssetId backgroundModel, const zeus::CVector3f& backgroundScale,\n                                               bool upElevator, u16 soundId, u8 volume, u8 panning)\n: CEntity(uid, info, active, name)\n, x34_worldId(worldId)\n, x38_areaId(areaId)\n, x3c_type(ETeleporterType::Elevator)\n, x40_24_upElevator(upElevator)\n, x50_playerAnim(playerAncs, charIdx, defaultAnim)\n, x5c_playerScale(playerScale)\n, x68_platformModel(platformModel)\n, x6c_platformScale(platformScale)\n, x78_backgroundModel(backgroundModel)\n, x7c_backgroundScale(backgroundScale)\n, x88_soundId(CSfxManager::TranslateSFXID(soundId))\n, x8a_volume(volume)\n, x8b_panning(panning) {}\n\nCScriptWorldTeleporter::CScriptWorldTeleporter(TUniqueId uid, std::string_view name, const CEntityInfo& info,\n                                               bool active, CAssetId worldId, CAssetId areaId, u16 soundId, u8 volume,\n                                               u8 panning, CAssetId fontId, CAssetId stringId, bool fadeWhite,\n                                               float charFadeIn, float charsPerSecond, float showDelay)\n: CEntity(uid, info, active, name)\n, x34_worldId(worldId)\n, x38_areaId(areaId)\n, x3c_type(ETeleporterType::Text)\n, x40_27_fadeWhite(fadeWhite)\n, x44_charFadeIn(charFadeIn)\n, x48_charsPerSecond(charsPerSecond)\n, x4c_showDelay(showDelay)\n, x88_soundId(CSfxManager::TranslateSFXID(soundId))\n, x8a_volume(volume)\n, x8b_panning(panning)\n, x8c_fontId(fontId)\n, x90_stringId(stringId) {}\n\nvoid CScriptWorldTeleporter::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CScriptWorldTeleporter::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {\n  if (GetActive()) {\n    const std::shared_ptr<CWorldTransManager>& transMgr = mgr.WorldTransManager();\n    switch (msg) {\n    case EScriptObjectMessage::Stop:\n      x40_25_inTransition = false;\n      transMgr->DisableTransition();\n      transMgr->SfxStop();\n      break;\n    case EScriptObjectMessage::Play:\n      StartTransition(mgr);\n      transMgr->SetSfx(x88_soundId, x8a_volume, x8b_panning);\n      transMgr->SfxStart();\n      break;\n    case EScriptObjectMessage::SetToZero: {\n      const auto& world = mgr.GetWorld();\n      world->SetLoadPauseState(true);\n      CAssetId currentWorld = g_GameState->CurrentWorldAssetId();\n      g_GameState->SetCurrentWorldId(x34_worldId);\n\n      if (g_ResFactory->GetResourceTypeById(x34_worldId) == SBIG('MLVL')) {\n        StartTransition(mgr);\n        g_GameState->SetCurrentWorldId(x34_worldId);\n        g_GameState->CurrentWorldState().SetDesiredAreaAssetId(x38_areaId);\n        g_Main->SetFlowState(EClientFlowStates::None);\n        mgr.SetShouldQuitGame(true);\n      } else {\n        x40_25_inTransition = false;\n        transMgr->DisableTransition();\n        g_GameState->SetCurrentWorldId(currentWorld);\n      }\n      break;\n    }\n    default:\n      break;\n    }\n  }\n  CEntity::AcceptScriptMsg(msg, uid, mgr);\n}\n\nvoid CScriptWorldTeleporter::StartTransition(CStateManager& mgr) {\n  if (x40_25_inTransition) {\n    return;\n  }\n\n  const auto& transMgr = mgr.WorldTransManager();\n  switch (x3c_type) {\n  case ETeleporterType::NoTransition:\n    transMgr->DisableTransition();\n    break;\n  case ETeleporterType::Elevator:\n    if (x50_playerAnim.GetACSFile().IsValid() && x50_playerAnim.GetCharacter() != u32(-1)) {\n      transMgr->EnableTransition(CAnimRes(x50_playerAnim.GetACSFile(), x50_playerAnim.GetCharacter(), x5c_playerScale,\n                                          x50_playerAnim.GetInitialAnimation(), true),\n                                 x68_platformModel, x6c_platformScale, x78_backgroundModel, x7c_backgroundScale,\n                                 x40_24_upElevator);\n      x40_25_inTransition = true;\n    }\n    break;\n  case ETeleporterType::Text:\n    transMgr->EnableTransition(x8c_fontId, x90_stringId, 0, x40_27_fadeWhite, x44_charFadeIn, x48_charsPerSecond,\n                               x4c_showDelay);\n    x40_25_inTransition = true;\n    break;\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CScriptWorldTeleporter.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/World/CAnimationParameters.hpp\"\n#include \"Runtime/World/CEntity.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\n\nclass CScriptWorldTeleporter : public CEntity {\n  enum class ETeleporterType { NoTransition, Elevator, Text };\n\n  CAssetId x34_worldId;\n  CAssetId x38_areaId;\n  ETeleporterType x3c_type = ETeleporterType::NoTransition;\n  bool x40_24_upElevator : 1 = false;\n  bool x40_25_inTransition : 1 = false;\n  bool x40_27_fadeWhite : 1 = false;\n  float x44_charFadeIn = 0.1f;\n  float x48_charsPerSecond = 8.0f;\n  float x4c_showDelay = 0.0f;\n  CAnimationParameters x50_playerAnim;\n  zeus::CVector3f x5c_playerScale;\n  CAssetId x68_platformModel;\n  zeus::CVector3f x6c_platformScale;\n  CAssetId x78_backgroundModel;\n  zeus::CVector3f x7c_backgroundScale;\n  u16 x88_soundId = -1;\n  u8 x8a_volume = 0;\n  u8 x8b_panning = 0;\n  CAssetId x8c_fontId;\n  CAssetId x90_stringId;\n\npublic:\n  DEFINE_ENTITY\n  CScriptWorldTeleporter(TUniqueId, std::string_view, const CEntityInfo&, bool, CAssetId, CAssetId);\n  CScriptWorldTeleporter(TUniqueId, std::string_view, const CEntityInfo&, bool, CAssetId, CAssetId, u16, u8, u8,\n                         CAssetId, CAssetId, bool, float, float, float);\n  CScriptWorldTeleporter(TUniqueId, std::string_view, const CEntityInfo&, bool, CAssetId, CAssetId, CAssetId, u32, u32,\n                         const zeus::CVector3f&, CAssetId, const zeus::CVector3f&, CAssetId, const zeus::CVector3f&,\n                         bool, u16, u8, u8);\n\n  void Accept(IVisitor&) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) override;\n  void StartTransition(CStateManager&);\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CSnakeWeedSwarm.cpp",
    "content": "#include \"Runtime/World/CSnakeWeedSwarm.hpp\"\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Graphics/CSkinnedModel.hpp\"\n#include \"Runtime/Graphics/CVertexMorphEffect.hpp\"\n#include \"Runtime/Weapon/CGameProjectile.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/World/CAnimationParameters.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nstatic constexpr CMaterialFilter skMaterialFilter = CMaterialFilter::MakeInclude({EMaterialTypes::Solid});\n\nCSnakeWeedSwarm::CSnakeWeedSwarm(TUniqueId uid, bool active, std::string_view name, const CEntityInfo& info,\n                                 const zeus::CVector3f& pos, const zeus::CVector3f& scale, const CAnimRes& animRes,\n                                 const CActorParameters& actParms, float spacing, float height, float f3,\n                                 float weaponDamageRadius, float maxPlayerDistance, float loweredTime,\n                                 float loweredTimeVariation, float maxZOffset, float speed, float speedVariation,\n                                 float f11, float scaleMin, float scaleMax, float distanceBelowGround,\n                                 const CDamageInfo& dInfo, float /*f15*/, u32 sfxId1, u32 sfxId2, u32 sfxId3,\n                                 CAssetId particleGenDesc1, u32 w5, CAssetId particleGenDesc2, float f16)\n: CActor(uid, active, name, info, zeus::CTransform::Translate(pos), CModelData::CModelDataNull(),\n         CMaterialList(EMaterialTypes::Trigger, EMaterialTypes::NonSolidDamageable), actParms, kInvalidUniqueId)\n, xe8_scale(scale)\n, xf4_boidSpacing(spacing)\n, xf8_height(height)\n, xfc_(f3)\n, x100_weaponDamageRadius(weaponDamageRadius)\n, x104_maxPlayerDistance(maxPlayerDistance)\n, x108_loweredTime(loweredTime)\n, x10c_loweredTimeVariation(loweredTimeVariation)\n, x110_maxZOffset(maxZOffset)\n, x114_speed(speed)\n, x118_speedVariation(speedVariation)\n, x11c_(std::cos(zeus::degToRad(f11)))\n, x120_scaleMin(scaleMin)\n, x124_scaleMax(scaleMax)\n, x128_distanceBelowGround(distanceBelowGround)\n, x15c_damageInfo(dInfo)\n, x1c8_boidPositions(std::make_unique<std::vector<zeus::CVector3f>>())\n, x1cc_boidPlacement(std::make_unique<std::vector<EBoidPlacement>>())\n, x1d0_sfx1(CSfxManager::TranslateSFXID(sfxId1))\n, x1d2_sfx2(CSfxManager::TranslateSFXID(sfxId2))\n, x1d4_sfx3(CSfxManager::TranslateSFXID(sfxId3))\n, x1fc_(w5)\n, x200_(f16) {\n  SetActorLights(actParms.GetLightParameters().MakeActorLights());\n  x1b0_modelData.emplace_back(std::make_unique<CModelData>(animRes));\n  x1b0_modelData.emplace_back(std::make_unique<CModelData>(animRes));\n  x1b0_modelData.emplace_back(std::make_unique<CModelData>(animRes));\n  x1b0_modelData.emplace_back(std::make_unique<CModelData>(animRes));\n  if (actParms.GetXRayAssets().first.IsValid()) {\n    for (const auto& modelData : x1b0_modelData) {\n      modelData->SetXRayModel(actParms.GetXRayAssets());\n    }\n    x140_25_modelAssetDirty = true;\n  }\n  if (actParms.GetThermalAssets().first.IsValid()) {\n    for (const auto& modelData : x1b0_modelData) {\n      modelData->SetInfraModel(actParms.GetThermalAssets());\n    }\n    x140_25_modelAssetDirty = true;\n  }\n  if (particleGenDesc1.IsValid()) {\n    x1dc_particleGenDesc = g_SimplePool->GetObj({FOURCC('PART'), particleGenDesc1});\n    x1ec_particleGen1 = std::make_unique<CElementGen>(x1dc_particleGenDesc);\n  }\n  if (particleGenDesc2.IsValid()) {\n    x1dc_particleGenDesc = g_SimplePool->GetObj({FOURCC('PART'), particleGenDesc2});\n    x1f4_particleGen2 = std::make_unique<CElementGen>(x1dc_particleGenDesc);\n  }\n}\n\nvoid CSnakeWeedSwarm::Accept(metaforce::IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CSnakeWeedSwarm::AllocateSkinnedModels(CStateManager& mgr, CModelData::EWhichModel which) {\n  x178_workspaces.clear();\n  for (int i = 0; i < x1b0_modelData.size(); ++i) {\n    auto& modelData = *x1b0_modelData[i];\n    x178_workspaces.emplace_back(modelData.PickAnimatedModel(which).CloneWorkspace());\n    modelData.EnableLooping(true);\n    const float dt = modelData.GetAnimationData()->GetAnimTimeRemaining(\"Whole Body\"sv) * (i * 0.25f);\n    modelData.AdvanceAnimation(dt, mgr, x4_areaId, true);\n  }\n  x1c4_which = which;\n}\n\nvoid CSnakeWeedSwarm::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId id, CStateManager& mgr) {\n  CActor::AcceptScriptMsg(msg, id, mgr);\n  if (msg == EScriptObjectMessage::Deleted) {\n    if (x1d8_sfxHandle) {\n      CSfxManager::RemoveEmitter(x1d8_sfxHandle);\n      x1d8_sfxHandle.reset();\n    }\n  } else if (msg == EScriptObjectMessage::InitializedInArea) {\n    AllocateSkinnedModels(mgr, CModelData::EWhichModel::Normal);\n    SetCalculateLighting(true);\n    x90_actorLights->SetCastShadows(true);\n  }\n}\n\nvoid CSnakeWeedSwarm::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) {\n  if (!frustum.aabbFrustumTest(x144_touchBounds)) {\n    xe4_30_outOfFrustum = true;\n    return;\n  }\n\n  xe4_30_outOfFrustum = false;\n  for (const auto& modelData : x1b0_modelData) {\n    modelData->GetAnimationData()->PreRender();\n  }\n  if (x90_actorLights && x140_24_hasGround) {\n    bool buildLights = false;\n    if (xe4_29_actorLightsDirty) {\n      buildLights = true;\n      xe4_29_actorLightsDirty = false;\n    } else if (x90_actorLights->GetIsDirty()) {\n      buildLights = true;\n    }\n    if (xe4_31_calculateLighting && x90_actorLights) {\n      auto visor = mgr.GetPlayerState()->GetActiveVisor(mgr);\n      if (visor == CPlayerState::EPlayerVisor::Thermal) {\n        x90_actorLights->BuildConstantAmbientLighting(zeus::skWhite);\n      } else if (buildLights && x4_areaId != kInvalidAreaId) {\n        CGameArea* area = mgr.GetWorld()->GetArea(x4_areaId);\n        if (area != nullptr) {\n          x90_actorLights->BuildAreaLightList(mgr, *area, x144_touchBounds);\n        }\n      }\n      x90_actorLights->BuildDynamicLightList(mgr, x144_touchBounds);\n    }\n  }\n}\n\nvoid CSnakeWeedSwarm::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) {\n  if (xe4_30_outOfFrustum) {\n    return;\n  }\n\n  if (x1ec_particleGen1) {\n    g_Renderer->AddParticleGen(*x1ec_particleGen1);\n  }\n  if (x1f4_particleGen2) {\n    g_Renderer->AddParticleGen(*x1f4_particleGen2);\n  }\n\n  if (x90_actorLights) {\n    for (const auto& modelData : x1b0_modelData) {\n      x90_actorLights->ActivateLights();\n    }\n  } else {\n    CGraphics::DisableAllLights();\n    CGraphics::SetAmbientColor(zeus::skWhite);\n  }\n\n  u32 posesToBuild = -1;\n  for (u32 i = 0; i < x134_boids.size(); ++i) {\n    RenderBoid(i, x134_boids[i], posesToBuild);\n  }\n  CGraphics::DisableAllLights();\n}\n\nvoid CSnakeWeedSwarm::Touch(CActor& actor, CStateManager& mgr) {\n  if (TCastToPtr<CGameProjectile> proj = actor) {\n    if (proj->GetDamageInfo().GetWeaponMode().GetType() != EWeaponType::AI) {\n      HandleRadiusDamage(x100_weaponDamageRadius, mgr, proj->GetTransform().origin);\n    }\n  }\n  if (TCastToPtr<CPlayer> player = actor) {\n    for (const auto& boid : x134_boids) {\n      float m = (boid.GetPosition() - player->GetTransform().origin).magnitude();\n      if (m < x104_maxPlayerDistance && boid.GetState() == EBoidState::Raised) {\n        mgr.SendScriptMsg(player, kInvalidUniqueId, EScriptObjectMessage::InSnakeWeed);\n        x140_26_playerTouching = true;\n      }\n    }\n  }\n}\n\nvoid CSnakeWeedSwarm::HandleRadiusDamage(float radius, CStateManager& mgr, const zeus::CVector3f& pos) {\n  float radiusSquared = radius * radius;\n  for (auto& boid : x134_boids) {\n    const auto& boidPosition = boid.GetPosition();\n    if ((boidPosition - pos).magSquared() < radiusSquared &&\n        (boid.GetState() == EBoidState::Raised || boid.GetState() == EBoidState::Raising)) {\n      boid.SetState(EBoidState::Lowering);\n      boid.SetSpeed(x118_speedVariation * mgr.GetActiveRandom()->Float() + x114_speed);\n      CSfxManager::AddEmitter(x1d2_sfx2, boidPosition, zeus::skZero3f, true, false, 0x7f, x4_areaId);\n      EmitParticles1(boidPosition);\n    }\n  }\n}\n\nvoid CSnakeWeedSwarm::Think(float dt, CStateManager& mgr) {\n  CEntity::Think(dt, mgr);\n  if (!x30_24_active)\n    return;\n\n  if (x1ec_particleGen1)\n    x1ec_particleGen1->Update(dt);\n  if (x1f4_particleGen2)\n    x1f4_particleGen2->Update(dt);\n\n  x204_particleTimer -= dt;\n  bool emitParticle = false;\n  if (x204_particleTimer < 0.f) {\n    x204_particleTimer = 0.f;\n    emitParticle = true;\n  }\n\n  if (!x140_24_hasGround)\n    FindGround(mgr);\n  if (x140_24_hasGround && x1c8_boidPositions && !x1c8_boidPositions->empty()) {\n    int n = (u64)(dt * x1cc_boidPlacement->size()) >> 0x20;\n    CreateBoids(mgr, n + 1);\n  }\n\n  CModelData::EWhichModel model = CModelData::GetRenderingModel(mgr);\n  if (x140_25_modelAssetDirty && x1c4_which != model)\n    AllocateSkinnedModels(mgr, model);\n\n  for (const auto& modelData : x1b0_modelData) {\n    modelData->GetAnimationData()->SetPlaybackRate(1.f);\n    modelData->AdvanceAnimation(dt, mgr, x4_areaId, true);\n  }\n\n  int raisedBoids = 0;\n  for (auto& boid : x134_boids) {\n    const zeus::CVector3f& pos = boid.GetPosition();\n    const EBoidState state = boid.GetState();\n    if (state == EBoidState::Raised) {\n      raisedBoids++;\n      if (x1f4_particleGen2 && emitParticle) {\n        EmitParticles2(pos);\n      }\n    } else if (state == EBoidState::Raising) {\n      boid.SetZOffset(boid.GetZOffset() - dt * boid.GetSpeed());\n      if (boid.GetZOffset() <= 0.f) {\n        boid.SetZOffset(0.f);\n        boid.SetState(EBoidState::Raised);\n      }\n    } else if (state == EBoidState::Lowered) {\n      boid.SetLoweredTimer(boid.GetLoweredTimer() - dt);\n      if (boid.GetLoweredTimer() <= 0.f) {\n        boid.SetState(EBoidState::Raising);\n        CSfxManager::AddEmitter(x1d4_sfx3, pos, zeus::skZero3f, true, false, 0x7f, x4_areaId);\n        EmitParticles1(pos);\n      }\n    } else if (state == EBoidState::Lowering) {\n      boid.SetZOffset(boid.GetZOffset() + dt * boid.GetSpeed());\n      const float max = x110_maxZOffset * boid.GetScale();\n      if (boid.GetZOffset() >= max) {\n        boid.SetZOffset(max);\n        boid.SetLoweredTimer(x10c_loweredTimeVariation * mgr.GetActiveRandom()->Float() + x108_loweredTime);\n        boid.SetState(EBoidState::Lowered);\n      }\n    }\n  }\n  if (raisedBoids == 0) {\n    if (x1d8_sfxHandle) {\n      CSfxManager::RemoveEmitter(x1d8_sfxHandle);\n      x1d8_sfxHandle.reset();\n    }\n  } else if (!x1d8_sfxHandle) {\n    x1d8_sfxHandle =\n        CSfxManager::AddEmitter(x1d0_sfx1, GetTransform().origin, zeus::skZero3f, true, true, 0x7f, x4_areaId);\n  }\n\n  if (x140_26_playerTouching) {\n    mgr.ApplyDamage(x8_uid, mgr.GetPlayer().GetUniqueId(), x8_uid, {x15c_damageInfo, dt}, skMaterialFilter,\n                    zeus::skZero3f);\n  }\n  x140_26_playerTouching = false;\n}\n\nzeus::CAABox CSnakeWeedSwarm::GetBoidBox() const {\n  const auto scale = xe8_scale * 0.75f;\n  return {GetTransform().origin - scale, GetTransform().origin + scale};\n}\n\nvoid CSnakeWeedSwarm::FindGround(const CStateManager& mgr) {\n  const auto box = GetBoidBox();\n  const auto result =\n      mgr.RayStaticIntersection(box.center(), zeus::skDown, box.max.z() - box.min.z(), skMaterialFilter);\n  if (result.IsValid()) {\n    int ct = GetNumBoidsX() * GetNumBoidsY();\n    x134_boids.reserve(ct);\n    x1c8_boidPositions->reserve(ct);\n    x1c8_boidPositions->push_back(result.GetPoint());\n    x1cc_boidPlacement->resize(ct, EBoidPlacement::None);\n    x140_24_hasGround = true;\n  }\n}\n\nint CSnakeWeedSwarm::GetNumBoidsY() const {\n  const auto box = GetBoidBox();\n  return static_cast<int>((box.max.y() - box.min.y()) / xf4_boidSpacing) + 1;\n}\n\nint CSnakeWeedSwarm::GetNumBoidsX() const {\n  const auto box = GetBoidBox();\n  return static_cast<int>((box.max.x() - box.min.x()) / xf4_boidSpacing) + 1;\n}\n\nvoid CSnakeWeedSwarm::CreateBoids(CStateManager& mgr, int num) {\n  auto width = GetNumBoidsX();\n  for (int i = 0; i < num; ++i) {\n    if (x1c8_boidPositions->empty())\n      break;\n    const auto pos = x1c8_boidPositions->back();\n    x1c8_boidPositions->pop_back();\n    const auto& idx = GetBoidIndex(pos);\n    if (CreateBoid(pos, mgr)) {\n      x1cc_boidPlacement->at(idx.x + width * idx.y) = EBoidPlacement::Placed;\n      AddBoidPosition({pos.x(), pos.y() - xf4_boidSpacing, pos.z()});\n      AddBoidPosition({pos.x(), xf4_boidSpacing + pos.y(), pos.z()});\n      AddBoidPosition({pos.x() - xf4_boidSpacing, pos.y(), pos.z()});\n      AddBoidPosition({xf4_boidSpacing + pos.x(), pos.y(), pos.z()});\n    } else {\n      x1cc_boidPlacement->at(idx.x + width * idx.y) = EBoidPlacement::Invalid;\n    }\n  }\n  CalculateTouchBounds();\n  if (x1c8_boidPositions->empty()) {\n    x1c8_boidPositions = nullptr;\n    x1cc_boidPlacement = nullptr;\n  }\n}\n\nzeus::CVector2i CSnakeWeedSwarm::GetBoidIndex(const zeus::CVector3f& pos) const {\n  const auto box = GetBoidBox();\n  return {static_cast<int>((pos.x() - box.min.x()) / xf4_boidSpacing),\n          static_cast<int>((pos.y() - box.min.y()) / xf4_boidSpacing)};\n}\n\nbool CSnakeWeedSwarm::CreateBoid(const zeus::CVector3f& vec, CStateManager& mgr) {\n  const auto pos = vec + zeus::CVector3f(GetBoidOffsetX(vec), GetBoidOffsetY(vec), xf8_height);\n  const auto result = mgr.RayStaticIntersection(pos, zeus::skDown, 2.f * xf8_height, skMaterialFilter);\n  if (result.IsValid() && result.GetPlane().normal().dot(zeus::skUp) > x11c_) {\n    const auto boidPosition = result.GetPoint() - zeus::CVector3f(0.f, 0.f, x128_distanceBelowGround);\n    x134_boids.emplace_back(boidPosition, x110_maxZOffset, x114_speed + x118_speedVariation,\n                            (x124_scaleMax - x120_scaleMin) * mgr.GetActiveRandom()->Float() + x120_scaleMin);\n    return true;\n  }\n  return false;\n}\n\nfloat CSnakeWeedSwarm::GetBoidOffsetY(const zeus::CVector3f& pos) const {\n  const float f = 2.4729404f * pos.y() + 0.3478602f * pos.x() * pos.x();\n  return xfc_ * (2.f * std::abs(f - std::trunc(f)) - 1.f);\n}\n\nfloat CSnakeWeedSwarm::GetBoidOffsetX(const zeus::CVector3f& pos) const {\n  const float f = 8.21395f * pos.x() + 0.112869f * pos.y() * pos.y();\n  return xfc_ * (2.f * std::abs(f - std::trunc(f)) - 1.f);\n}\n\nvoid CSnakeWeedSwarm::AddBoidPosition(const zeus::CVector3f& pos) {\n  int x = GetNumBoidsX();\n  int y = GetNumBoidsY();\n  const auto& v = GetBoidIndex(pos);\n  if (-1 < v.x && v.x < x && -1 < v.y && v.y < y) {\n    int idx = v.x + x * v.y;\n    auto& placement = x1cc_boidPlacement->at(idx);\n    if (placement == EBoidPlacement::None) {\n      placement = EBoidPlacement::Ready;\n      x1c8_boidPositions->push_back(pos);\n    }\n  }\n}\n\nvoid CSnakeWeedSwarm::CalculateTouchBounds() {\n  if (x134_boids.empty()) {\n    x144_touchBounds = {x34_transform.origin, x34_transform.origin};\n  } else {\n    x144_touchBounds = zeus::skInvertedBox;\n    for (const auto& boid : x134_boids) {\n      x144_touchBounds.accumulateBounds(boid.GetPosition() - x100_weaponDamageRadius);\n      x144_touchBounds.accumulateBounds(boid.GetPosition() + x100_weaponDamageRadius);\n    }\n  }\n  xe4_27_notInSortedLists = true;\n}\n\nvoid CSnakeWeedSwarm::EmitParticles1(const zeus::CVector3f& pos) {\n  if (!x1ec_particleGen1)\n    return;\n\n  auto& particleGen = *x1ec_particleGen1;\n  particleGen.SetParticleEmission(true);\n  particleGen.SetTranslation(pos);\n  particleGen.ForceParticleCreation(x1fc_);\n  particleGen.SetParticleEmission(false);\n}\n\nvoid CSnakeWeedSwarm::EmitParticles2(const zeus::CVector3f& pos) {\n  if (!x1f4_particleGen2)\n    return;\n\n  auto& particleGen = *x1f4_particleGen2;\n  particleGen.SetParticleEmission(true);\n  particleGen.SetTranslation(pos);\n  particleGen.ForceParticleCreation(1);\n  particleGen.SetParticleEmission(false);\n}\n\nvoid CSnakeWeedSwarm::RenderBoid(u32 idx, const CBoid& boid, u32& posesToBuild) const {\n  const u32 modelIdx = idx & 3;\n  auto& modelData = *x1b0_modelData[modelIdx];\n  auto& model = modelData.PickAnimatedModel(x1c4_which);\n  auto& animData = *modelData.GetAnimationData();\n  auto& workspace = x178_workspaces[modelIdx];\n  if (posesToBuild & 1 << modelIdx) {\n    posesToBuild &= ~(1 << modelIdx);\n    animData.BuildPose();\n    model.Calculate(animData.GetPose(), nullptr, {}, &workspace);\n  }\n  CGraphics::SetModelMatrix(\n      zeus::CTransform::Translate(boid.GetPosition() - zeus::CVector3f(0.f, 0.f, boid.GetZOffset())) *\n      zeus::CTransform::Scale(boid.GetScale()));\n  constexpr CModelFlags useFlags{0, 0, 3, zeus::skWhite};\n  model.Draw(workspace.m_vertexWorkspace, workspace.m_normalWorkspace, useFlags);\n}\n\nvoid CSnakeWeedSwarm::ApplyRadiusDamage(const zeus::CVector3f& pos, const CDamageInfo& info, CStateManager& mgr) {\n  auto type = info.GetWeaponMode().GetType();\n  if (type == EWeaponType::Bomb || type == EWeaponType::PowerBomb)\n    HandleRadiusDamage(info.GetRadius(), mgr, pos);\n}\n\nstd::optional<zeus::CAABox> CSnakeWeedSwarm::GetTouchBounds() const {\n  if (x140_24_hasGround)\n    return x144_touchBounds;\n  return std::nullopt;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CSnakeWeedSwarm.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Collision/CCollisionSurface.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/World/CActor.hpp\"\n#include \"Runtime/World/CDamageInfo.hpp\"\n\nnamespace metaforce {\nclass CAnimationParameters;\n\nclass CSnakeWeedSwarm : public CActor {\npublic:\n  enum class EBoidState : u32 {\n    Raised = 0,\n    Raising = 1,\n    Lowered = 2,\n    Lowering = 3,\n  };\n\n  enum class EBoidPlacement : u32 {\n    None = 0,\n    Ready = 1,\n    Invalid = 2,\n    Placed = 3,\n  };\n\n  class CBoid {\n    zeus::CVector3f x0_pos;\n    EBoidState xc_state;\n    float x10_loweredTimer = 0.f;\n    float x14_zOffset;\n    float x18_speed;\n    // x1c unused\n    float x20_scale;\n\n  public:\n    constexpr CBoid(const zeus::CVector3f& pos, float zOffset, float speed, float scale)\n    : x0_pos(pos), xc_state(EBoidState::Raising), x14_zOffset(zOffset), x18_speed(speed), x20_scale(scale) {}\n\n    constexpr const zeus::CVector3f& GetPosition() const { return x0_pos; }\n    constexpr EBoidState GetState() const { return xc_state; }\n    constexpr float GetLoweredTimer() const { return x10_loweredTimer; }\n    constexpr float GetZOffset() const { return x14_zOffset; }\n    constexpr float GetSpeed() const { return x18_speed; }\n    constexpr float GetScale() const { return x20_scale; }\n    constexpr void SetState(EBoidState v) { xc_state = v; }\n    constexpr void SetLoweredTimer(float v) { x10_loweredTimer = v; }\n    constexpr void SetZOffset(float v) { x14_zOffset = v; }\n    constexpr void SetSpeed(float v) { x18_speed = v; }\n  };\n\nprivate:\n  zeus::CVector3f xe8_scale;\n  float xf4_boidSpacing;\n  float xf8_height;\n  float xfc_;\n  float x100_weaponDamageRadius;\n  float x104_maxPlayerDistance;\n  float x108_loweredTime;\n  float x10c_loweredTimeVariation;\n  float x110_maxZOffset;\n  float x114_speed;\n  float x118_speedVariation;\n  float x11c_;\n  float x120_scaleMin;\n  float x124_scaleMax;\n  float x128_distanceBelowGround;\n  // u32 x12c_ = 0;\n  std::vector<CBoid> x134_boids;\n  bool x140_24_hasGround : 1 = false;\n  bool x140_25_modelAssetDirty : 1 = false;\n  bool x140_26_playerTouching : 1 = false;\n  zeus::CAABox x144_touchBounds = zeus::skInvertedBox;\n  CDamageInfo x15c_damageInfo;\n  mutable rstl::reserved_vector<SSkinningWorkspace, 4> x178_workspaces;\n  // Originally:\n  // rstl::reserved_vector<rstl::auto_ptr<float[]>, 4> x178_posWorkspaces;\n  // rstl::reserved_vector<float[], 4> x19c_nrmWorkspaces;\n  rstl::reserved_vector<std::unique_ptr<CModelData>, 4> x1b0_modelData;\n  CModelData::EWhichModel x1c4_which;\n  std::unique_ptr<std::vector<zeus::CVector3f>> x1c8_boidPositions;\n  std::unique_ptr<std::vector<EBoidPlacement>> x1cc_boidPlacement;\n  u16 x1d0_sfx1;\n  u16 x1d2_sfx2;\n  u16 x1d4_sfx3;\n  CSfxHandle x1d8_sfxHandle;\n  TLockedToken<CGenDescription> x1dc_particleGenDesc;\n  // TLockedToken<CGenDescription> x1e4_; both assign to x1dc_\n  std::unique_ptr<CElementGen> x1ec_particleGen1;\n  std::unique_ptr<CElementGen> x1f4_particleGen2;\n  u32 x1fc_;\n  float x200_; // unused?\n  float x204_particleTimer = 0.f;\n\npublic:\n  DEFINE_ENTITY\n  CSnakeWeedSwarm(TUniqueId uid, bool active, std::string_view name, const CEntityInfo& info,\n                  const zeus::CVector3f& pos, const zeus::CVector3f& scale, const CAnimRes& animRes,\n                  const CActorParameters& actParms, float spacing, float height, float f3, float weaponDamageRadius,\n                  float maxPlayerDistance, float loweredTime, float loweredTimeVariation, float maxZOffset, float speed,\n                  float speedVariation, float f11, float scaleMin, float scaleMax, float distanceBelowGround,\n                  const CDamageInfo& dInfo, float /*f15*/, u32 sfxId1, u32 sfxId2, u32 sfxId3,\n                  CAssetId particleGenDesc1, u32 w5, CAssetId particleGenDesc2, float f16);\n\n  void Accept(IVisitor&) override;\n  void ApplyRadiusDamage(const zeus::CVector3f& pos, const CDamageInfo& info, CStateManager& stateMgr);\n  std::optional<zeus::CAABox> GetTouchBounds() const override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void PreRender(CStateManager&, const zeus::CFrustum&) override;\n  void AddToRenderer(const zeus::CFrustum&, CStateManager&) override;\n  void Touch(CActor&, CStateManager&) override;\n  void Think(float, CStateManager&) override;\n  void HandleRadiusDamage(float radius, CStateManager& mgr, const zeus::CVector3f& pos);\n  float GetWeaponDamageRadius() const { return x100_weaponDamageRadius; }\n\nprivate:\n  void AllocateSkinnedModels(CStateManager& mgr, CModelData::EWhichModel which);\n  void FindGround(const CStateManager& mgr);\n  zeus::CAABox GetBoidBox() const;\n  int GetNumBoidsY() const;\n  int GetNumBoidsX() const;\n  void CreateBoids(CStateManager& mgr, int num);\n  zeus::CVector2i GetBoidIndex(const zeus::CVector3f& pos) const;\n  bool CreateBoid(const zeus::CVector3f& vec, CStateManager& mgr);\n  float GetBoidOffsetY(const zeus::CVector3f& pos) const;\n  float GetBoidOffsetX(const zeus::CVector3f& pos) const;\n  void AddBoidPosition(const zeus::CVector3f& pos);\n  void CalculateTouchBounds();\n  void EmitParticles1(const zeus::CVector3f& pos);\n  void EmitParticles2(const zeus::CVector3f& pos);\n  void RenderBoid(u32 idx, const CBoid& boid, u32& posesToBuild) const;\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CStateMachine.cpp",
    "content": "#include \"Runtime/World/CStateMachine.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CAi.hpp\"\n#include \"Runtime/Logging.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\n#include \"ConsoleVariables/CVarManager.hpp\"\n\nnamespace metaforce {\nCStateMachine::CStateMachine(CInputStream& in) {\n  CAiTrigger* lastTrig = nullptr;\n  u32 stateCount = in.ReadLong();\n\n  x0_states.reserve(stateCount);\n\n  for (u32 i = 0; i < stateCount; ++i) {\n    std::string name;\n    while (name.size() < 31) {\n      const auto chr = in.ReadChar();\n      if (chr == '\\0') {\n        break;\n      }\n      name += chr;\n    }\n    CAiStateFunc func = CAi::GetStateFunc(name);\n    x0_states.emplace_back(func, name.c_str());\n  }\n\n  x10_triggers.reserve(in.ReadLong());\n\n  for (u32 i = 0; i < stateCount; ++i) {\n    x0_states[i].SetNumTriggers(in.ReadLong());\n    if (x0_states[i].GetNumTriggers() == 0)\n      continue;\n    CAiTrigger* firstTrig = x10_triggers.data() + x10_triggers.size();\n    x0_states[i].SetTriggers(firstTrig);\n    x10_triggers.resize(x10_triggers.size() + x0_states[i].GetNumTriggers());\n\n    for (s32 j = 0; j < x0_states[i].GetNumTriggers(); ++j) {\n      const u32 triggerCount = in.ReadLong();\n      const u32 lastTriggerIdx = triggerCount - 1;\n      for (u32 k = 0; k < triggerCount; ++k) {\n        std::string name;\n        while (name.size() < 31) {\n          const auto chr = in.ReadChar();\n          name += chr;\n          if (chr == '\\0') {\n            break;\n          }\n        }\n\n        const bool isNot = name.front() == '!';\n        const CAiTriggerFunc func = CAi::GetTriggerFunc(isNot ? name.c_str() + 1 : name.c_str());\n        const float arg = in.ReadFloat();\n        CAiTrigger* newTrig;\n        if (k < lastTriggerIdx) {\n          newTrig = &x10_triggers.emplace_back();\n        } else {\n          newTrig = &firstTrig[j];\n        }\n        if (k == 0)\n          newTrig->Setup(func, isNot, arg, &x0_states[in.ReadLong()]);\n        else\n          newTrig->Setup(func, isNot, arg, lastTrig);\n        lastTrig = newTrig;\n      }\n    }\n  }\n}\n\ns32 CStateMachine::GetStateIndex(std::string_view state) const {\n  auto it = std::find_if(x0_states.begin(), x0_states.end(),\n                         [state](const CAiState& st) { return (strncmp(st.GetName(), state.data(), 31) == 0); });\n  if (it == x0_states.end())\n    return 0;\n\n  return it - x0_states.begin();\n}\n\nvoid CStateMachineState::Update(CStateManager& mgr, CAi& ai, float delta) {\n  if (x4_state) {\n    x8_time += delta;\n    x4_state->CallFunc(mgr, ai, EStateMsg::Update, delta);\n    for (int i = 0; i < x4_state->GetNumTriggers(); ++i) {\n      CAiTrigger* trig = x4_state->GetTrig(i);\n      CAiState* state = nullptr;\n      bool andPassed = true;\n      while (andPassed && trig) {\n        andPassed = false;\n        if (trig->CallFunc(mgr, ai)) {\n          andPassed = true;\n          state = trig->GetState();\n          trig = trig->GetAnd();\n        }\n      }\n      if (andPassed && state != nullptr) {\n        x4_state->CallFunc(mgr, ai, EStateMsg::Deactivate, 0.f);\n        x4_state = state;\n        spdlog::info(\"{} {} {} - {} {}\", ai.GetUniqueId(), ai.GetEditorId(), ai.GetName(), state->xc_name,\n                     int(state - x0_machine->GetStateVector().data()));\n        x8_time = 0.f;\n        x18_24_codeTrigger = false;\n        xc_random = mgr.GetActiveRandom()->Float();\n        x4_state->CallFunc(mgr, ai, EStateMsg::Activate, delta);\n        return;\n      }\n    }\n  }\n}\n\nvoid CStateMachineState::SetState(CStateManager& mgr, CAi& ai, s32 idx) {\n  CAiState* state = const_cast<CAiState*>(&x0_machine->GetStateVector()[idx]);\n  if (x4_state != state) {\n    if (x4_state)\n      x4_state->CallFunc(mgr, ai, EStateMsg::Deactivate, 0.f);\n    x4_state = state;\n    x8_time = 0.f;\n    xc_random = mgr.GetActiveRandom()->Float();\n    x18_24_codeTrigger = false;\n    x4_state->CallFunc(mgr, ai, EStateMsg::Activate, 0.f);\n  }\n}\n\nvoid CStateMachineState::SetState(CStateManager& mgr, CAi& ai, const CStateMachine* machine, std::string_view state) {\n  if (!machine)\n    return;\n\n  if (!x0_machine)\n    Setup(machine);\n\n  s32 idx = machine->GetStateIndex(state);\n  SetState(mgr, ai, idx);\n}\n\nconst std::vector<CAiState>* CStateMachineState::GetStateVector() const {\n  if (!x0_machine)\n    return nullptr;\n\n  return &x0_machine->GetStateVector();\n}\n\nvoid CStateMachineState::Setup(const CStateMachine* machine) {\n  x0_machine = machine;\n  x4_state = nullptr;\n  x8_time = 0.f;\n  xc_random = 0.f;\n  x10_delay = 0.f;\n}\n\nCFactoryFnReturn FAiFiniteStateMachineFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& vparms,\n                                              CObjectReference*) {\n  return TToken<CStateMachine>::GetIObjObjectFor(std::make_unique<CStateMachine>(in));\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CStateMachine.hpp",
    "content": "#pragma once\n\n#include <cstring>\n#include <vector>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/GCNTypes.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n#include \"Runtime/IObj.hpp\"\n#include \"Runtime/IObjFactory.hpp\"\n#include \"Runtime/World/CAiFuncMap.hpp\"\n\nnamespace metaforce {\nclass CAiState;\nclass CStateManager;\n\nclass CAiTrigger {\n  CAiTriggerFunc x0_func;\n  float xc_arg = 0.f;\n  CAiTrigger* x10_andTrig = nullptr;\n  CAiState* x14_state = nullptr;\n  bool x18_lNot = false;\n\npublic:\n  CAiTrigger() = default;\n  CAiTrigger* GetAnd() const { return x10_andTrig; }\n  CAiState* GetState() const { return x14_state; }\n  bool CallFunc(CStateManager& mgr, CAi& ai) const {\n    bool ret = true;\n    if (x0_func) {\n      ret = (ai.*x0_func)(mgr, xc_arg);\n      if (x18_lNot) {\n        ret = !ret;\n      }\n    }\n    return ret;\n  }\n\n  void Setup(CAiTriggerFunc func, bool lnot, float arg, CAiTrigger* andTrig) {\n    x0_func = func;\n    x18_lNot = lnot;\n    xc_arg = arg;\n    x10_andTrig = andTrig;\n  }\n  void Setup(CAiTriggerFunc func, bool lnot, float arg, CAiState* state) {\n    x0_func = func;\n    x18_lNot = lnot;\n    xc_arg = arg;\n    x14_state = state;\n  }\n};\n\nclass CAiState {\n  friend class CStateMachineState;\n  CAiStateFunc x0_func;\n  char xc_name[32] = {};\n  u32 x2c_numTriggers = 0;\n  CAiTrigger* x30_firstTrigger = nullptr;\n\npublic:\n  CAiState(CAiStateFunc func, const char* name) {\n    x0_func = func;\n    strncpy(xc_name, name, 31);\n  }\n\n  s32 GetNumTriggers() const { return x2c_numTriggers; }\n  CAiTrigger* GetTrig(s32 i) const { return &x30_firstTrigger[i]; }\n  const char* GetName() const { return xc_name; }\n  void SetTriggers(CAiTrigger* triggers) { x30_firstTrigger = triggers; }\n  void SetNumTriggers(s32 numTriggers) { x2c_numTriggers = numTriggers; }\n  void CallFunc(CStateManager& mgr, CAi& ai, EStateMsg msg, float delta) const {\n    if (x0_func)\n      (ai.*x0_func)(mgr, msg, delta);\n  }\n};\n\nclass CStateMachine {\n  std::vector<CAiState> x0_states;\n  std::vector<CAiTrigger> x10_triggers;\n\npublic:\n  explicit CStateMachine(CInputStream& in);\n\n  s32 GetStateIndex(std::string_view state) const;\n  const std::vector<CAiState>& GetStateVector() const { return x0_states; }\n};\n\nclass CStateMachineState {\n  friend class CPatterned;\n  const CStateMachine* x0_machine = nullptr;\n  CAiState* x4_state = nullptr;\n  float x8_time = 0.f;\n  float xc_random = 0.f;\n  float x10_delay = 0.f;\n  float x14_ = 0.f;\n  bool x18_24_codeTrigger : 1 = false;\n\npublic:\n  CStateMachineState() = default;\n\n  CAiState* GetActorState() const { return x4_state; }\n\n  void Update(CStateManager& mgr, CAi& ai, float delta);\n  void SetState(CStateManager&, CAi&, s32);\n  void SetState(CStateManager&, CAi&, const CStateMachine*, std::string_view);\n  const std::vector<CAiState>* GetStateVector() const;\n  void Setup(const CStateMachine* machine);\n  void SetDelay(float delay) { x10_delay = delay; }\n  float GetTime() const { return x8_time; }\n  float GetRandom() const { return xc_random; }\n  float GetDelay() const { return x10_delay; }\n  void SetCodeTrigger() { x18_24_codeTrigger = true; }\n\n  const char* GetName() const {\n    if (x4_state)\n      return x4_state->GetName();\n    return nullptr;\n  }\n};\n\nCFactoryFnReturn FAiFiniteStateMachineFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& vparms,\n                                              CObjectReference*);\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CTeamAiMgr.cpp",
    "content": "#include \"Runtime/World/CTeamAiMgr.hpp\"\n\n#include <algorithm>\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nstruct TeamAiRoleSorter {\n  enum class Type {\n    OwnerID,\n    Distance,\n    TeamAIRole,\n  };\n\n  zeus::CVector3f x0_pos;\n  Type xc_type;\n\n  bool operator()(const CTeamAiRole& a, const CTeamAiRole& b) const {\n    const float aDist = (x0_pos - a.GetTeamPosition()).magSquared();\n    const float bDist = (x0_pos - b.GetTeamPosition()).magSquared();\n    switch (xc_type) {\n    case Type::OwnerID:\n      return a.GetOwnerId() < b.GetOwnerId();\n    case Type::Distance:\n      return aDist < bDist;\n    default:\n      if (a.GetTeamAiRole() == b.GetTeamAiRole())\n        return aDist < bDist;\n      else\n        return a.GetTeamAiRole() < b.GetTeamAiRole();\n    }\n  }\n  TeamAiRoleSorter(const zeus::CVector3f& pos, Type type) : x0_pos(pos), xc_type(type) {}\n};\n\nCTeamAiData::CTeamAiData(CInputStream& in, s32 propCount)\n: x0_aiCount(in.ReadLong())\n, x4_meleeCount(in.ReadLong())\n, x8_rangedCount(in.ReadLong())\n, xc_unknownCount(in.ReadLong())\n, x10_maxMeleeAttackerCount(in.ReadLong())\n, x14_maxRangedAttackerCount(in.ReadLong())\n, x18_positionMode(in.ReadLong())\n, x1c_meleeTimeInterval(propCount > 8 ? in.ReadFloat() : 0.f)\n, x20_rangedTimeInterval(propCount > 8 ? in.ReadFloat() : 0.f) {}\n\nCTeamAiMgr::CTeamAiMgr(TUniqueId uid, std::string_view name, const CEntityInfo& info, const CTeamAiData& data)\n: CEntity(uid, info, true, name), x34_data(data) {\n  if (x34_data.x0_aiCount)\n    x58_roles.reserve(x34_data.x0_aiCount);\n  if (x34_data.x4_meleeCount)\n    x68_meleeAttackers.reserve(x34_data.x4_meleeCount);\n  if (x34_data.x8_rangedCount)\n    x78_rangedAttackers.reserve(x34_data.x8_rangedCount);\n}\n\nvoid CTeamAiMgr::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CTeamAiMgr::UpdateTeamCaptain() {\n  int maxPriority = INT_MIN;\n  x8c_teamCaptainId = kInvalidUniqueId;\n  for (const auto& role : x58_roles) {\n    if (role.x18_captainPriority > maxPriority) {\n      maxPriority = role.x18_captainPriority;\n      x8c_teamCaptainId = role.GetOwnerId();\n    }\n  }\n}\n\nbool CTeamAiMgr::ShouldUpdateRoles(float dt) {\n  if (x58_roles.empty()) {\n    return false;\n  }\n\n  x88_timeDirty += dt;\n  if (x88_timeDirty >= 1.5f) {\n    return true;\n  }\n\n  return std::any_of(x58_roles.cbegin(), x58_roles.cend(), [](const auto& role) {\n    return role.GetTeamAiRole() <= CTeamAiRole::ETeamAiRole::Initial ||\n           role.GetTeamAiRole() > CTeamAiRole::ETeamAiRole::Unassigned;\n  });\n}\n\nvoid CTeamAiMgr::ResetRoles(CStateManager& mgr) {\n  for (auto& role : x58_roles) {\n    role.x10_curRole = CTeamAiRole::ETeamAiRole::Initial;\n    role.x14_roleIndex = 0;\n    if (const CAi* ai = static_cast<const CAi*>(mgr.GetObjectById(role.GetOwnerId())))\n      role.x1c_position = ai->GetTranslation();\n  }\n}\n\nvoid CTeamAiMgr::SpacingSort(CStateManager& mgr, const zeus::CVector3f& pos) {\n  const TeamAiRoleSorter sorter(pos, TeamAiRoleSorter::Type::TeamAIRole);\n  std::sort(x58_roles.begin(), x58_roles.end(), sorter);\n  float tierStagger = 4.5f;\n  for (const auto& role : x58_roles) {\n    if (const TCastToConstPtr<CPatterned> ai = mgr.ObjectById(role.GetOwnerId())) {\n      const float length = (ai->GetBaseBoundingBox().max.y() - ai->GetBaseBoundingBox().min.y()) * 1.5f;\n      if (length > tierStagger) {\n        tierStagger = length;\n      }\n    }\n  }\n  float curTierDist = tierStagger;\n  int tierTeamSize = 0;\n  int maxTierTeamSize = 3;\n  for (auto& role : x58_roles) {\n    if (const TCastToConstPtr<CPatterned> ai = mgr.ObjectById(role.GetOwnerId())) {\n      zeus::CVector3f delta = ai->GetTranslation() - pos;\n      zeus::CVector3f newPos;\n      if (delta.canBeNormalized()) {\n        newPos = pos + delta.normalized() * curTierDist;\n      } else {\n        newPos = pos + ai->GetTransform().basis[1] * curTierDist;\n      }\n      role.x1c_position = newPos;\n      role.x1c_position.z() = ai->GetTranslation().z();\n      tierTeamSize += 1;\n      if (tierTeamSize > maxTierTeamSize) {\n        curTierDist += tierStagger;\n        tierTeamSize = 0;\n        maxTierTeamSize += 1;\n      }\n    }\n  }\n  const TeamAiRoleSorter sorter2(pos, TeamAiRoleSorter::Type::OwnerID);\n  std::sort(x58_roles.begin(), x58_roles.end(), sorter2);\n}\n\nvoid CTeamAiMgr::PositionTeam(CStateManager& mgr) {\n  zeus::CVector3f aimPos = mgr.GetPlayer().GetAimPosition(mgr, 0.f);\n  switch (x34_data.x18_positionMode) {\n  case 1:\n    SpacingSort(mgr, aimPos);\n    break;\n  default:\n    for (auto& role : x58_roles) {\n      if (const TCastToConstPtr<CPatterned> ai = mgr.ObjectById(role.GetOwnerId())) {\n        role.x1c_position = ai->GetOrigin(mgr, role, aimPos);\n      }\n    }\n    break;\n  }\n}\n\nvoid CTeamAiMgr::AssignRoles(CTeamAiRole::ETeamAiRole assRole, s32 count) {\n  if (count == 0) {\n    return;\n  }\n\n  s32 lastIdx = 0;\n  for (auto& role : x58_roles) {\n    if (role.GetTeamAiRole() == CTeamAiRole::ETeamAiRole::Initial) {\n      if (role.x4_roleA == assRole || role.x8_roleB == assRole || role.xc_roleC == assRole) {\n        role.x10_curRole = assRole;\n        role.x14_roleIndex = lastIdx++;\n        if (lastIdx == count) {\n          return;\n        }\n      }\n    }\n  }\n}\n\nvoid CTeamAiMgr::UpdateRoles(CStateManager& mgr) {\n  ResetRoles(mgr);\n\n  const zeus::CVector3f aimPos = mgr.GetPlayer().GetAimPosition(mgr, 0.f);\n  const TeamAiRoleSorter sorter(aimPos, TeamAiRoleSorter::Type::Distance);\n  std::sort(x58_roles.begin(), x58_roles.end(), sorter);\n\n  AssignRoles(CTeamAiRole::ETeamAiRole::Melee, x34_data.x4_meleeCount);\n  AssignRoles(CTeamAiRole::ETeamAiRole::Ranged, x34_data.x8_rangedCount);\n  AssignRoles(CTeamAiRole::ETeamAiRole::Unknown, x34_data.xc_unknownCount);\n\n  for (auto& role : x58_roles) {\n    if (role.GetTeamAiRole() <= CTeamAiRole::ETeamAiRole::Initial ||\n        role.GetTeamAiRole() > CTeamAiRole::ETeamAiRole::Unassigned) {\n      role.SetTeamAiRole(CTeamAiRole::ETeamAiRole::Unassigned);\n    }\n  }\n\n  const TeamAiRoleSorter sorter2(aimPos, TeamAiRoleSorter::Type::OwnerID);\n  std::sort(x58_roles.begin(), x58_roles.end(), sorter2);\n  x88_timeDirty = 0.f;\n}\n\nvoid CTeamAiMgr::Think(float dt, CStateManager& mgr) {\n  CEntity::Think(dt, mgr);\n\n  if (ShouldUpdateRoles(dt)) {\n    UpdateRoles(mgr);\n  }\n\n  PositionTeam(mgr);\n  x90_timeSinceMelee += dt;\n  x94_timeSinceRanged += dt;\n}\n\nvoid CTeamAiMgr::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& mgr) {\n  CEntity::AcceptScriptMsg(msg, objId, mgr);\n}\n\nCTeamAiRole* CTeamAiMgr::GetTeamAiRole(TUniqueId aiId) {\n  auto search =\n      rstl::binary_find(x58_roles.begin(), x58_roles.end(), aiId, [](const auto& obj) { return obj.GetOwnerId(); });\n  return search != x58_roles.end() ? &*search : nullptr;\n}\n\nbool CTeamAiMgr::IsPartOfTeam(TUniqueId aiId) const {\n  auto search =\n      rstl::binary_find(x58_roles.begin(), x58_roles.end(), aiId, [](const auto& obj) { return obj.GetOwnerId(); });\n  return search != x58_roles.end();\n}\n\nbool CTeamAiMgr::HasTeamAiRole(TUniqueId aiId) const {\n  auto search =\n      rstl::binary_find(x58_roles.begin(), x58_roles.end(), aiId, [](const auto& obj) { return obj.GetOwnerId(); });\n  return (search != x58_roles.end() && search->GetTeamAiRole() > CTeamAiRole::ETeamAiRole::Initial &&\n          search->GetTeamAiRole() <= CTeamAiRole::ETeamAiRole::Unassigned);\n}\n\nbool CTeamAiMgr::IsMeleeAttacker(TUniqueId aiId) const {\n  auto search = rstl::binary_find(x68_meleeAttackers.begin(), x68_meleeAttackers.end(), aiId);\n  return search != x68_meleeAttackers.end();\n}\n\nbool CTeamAiMgr::CanAcceptMeleeAttacker(TUniqueId aiId) const {\n  if (x90_timeSinceMelee >= x34_data.x1c_meleeTimeInterval &&\n      x68_meleeAttackers.size() < x34_data.x10_maxMeleeAttackerCount)\n    return true;\n  return IsMeleeAttacker(aiId);\n}\n\nbool CTeamAiMgr::AddMeleeAttacker(TUniqueId aiId) {\n  if (x90_timeSinceMelee >= x34_data.x1c_meleeTimeInterval &&\n      x68_meleeAttackers.size() < x34_data.x10_maxMeleeAttackerCount && HasTeamAiRole(aiId)) {\n    auto search = rstl::binary_find(x68_meleeAttackers.begin(), x68_meleeAttackers.end(), aiId);\n    if (search == x68_meleeAttackers.end()) {\n      x68_meleeAttackers.insert(std::lower_bound(x68_meleeAttackers.begin(), x68_meleeAttackers.end(), aiId), aiId);\n      x90_timeSinceMelee = 0.f;\n    }\n    return true;\n  }\n  return false;\n}\n\nvoid CTeamAiMgr::RemoveMeleeAttacker(TUniqueId aiId) {\n  auto search = rstl::binary_find(x68_meleeAttackers.begin(), x68_meleeAttackers.end(), aiId);\n  if (search != x68_meleeAttackers.end())\n    x68_meleeAttackers.erase(search);\n}\n\nbool CTeamAiMgr::IsRangedAttacker(TUniqueId aiId) const {\n  auto search = rstl::binary_find(x78_rangedAttackers.begin(), x78_rangedAttackers.end(), aiId);\n  return search != x78_rangedAttackers.end();\n}\n\nbool CTeamAiMgr::CanAcceptRangedAttacker(TUniqueId aiId) const {\n  if (x94_timeSinceRanged >= x34_data.x20_rangedTimeInterval &&\n      x78_rangedAttackers.size() < x34_data.x14_maxRangedAttackerCount)\n    return true;\n  return IsRangedAttacker(aiId);\n}\n\nbool CTeamAiMgr::AddRangedAttacker(TUniqueId aiId) {\n  if (x94_timeSinceRanged >= x34_data.x20_rangedTimeInterval &&\n      x78_rangedAttackers.size() < x34_data.x14_maxRangedAttackerCount && HasTeamAiRole(aiId)) {\n    auto search = rstl::binary_find(x78_rangedAttackers.begin(), x78_rangedAttackers.end(), aiId);\n    if (search == x78_rangedAttackers.end()) {\n      x78_rangedAttackers.insert(std::lower_bound(x78_rangedAttackers.begin(), x78_rangedAttackers.end(), aiId), aiId);\n      x94_timeSinceRanged = 0.f;\n    }\n    return true;\n  }\n  return false;\n}\n\nvoid CTeamAiMgr::RemoveRangedAttacker(TUniqueId aiId) {\n  auto search = rstl::binary_find(x78_rangedAttackers.begin(), x78_rangedAttackers.end(), aiId);\n  if (search != x78_rangedAttackers.end())\n    x78_rangedAttackers.erase(search);\n}\n\nbool CTeamAiMgr::AssignTeamAiRole(const CAi& ai, CTeamAiRole::ETeamAiRole roleA, CTeamAiRole::ETeamAiRole roleB,\n                                  CTeamAiRole::ETeamAiRole roleC) {\n  CTeamAiRole newRole(ai.GetUniqueId(), roleA, roleB, roleC);\n  auto search = rstl::binary_find(x58_roles.begin(), x58_roles.end(), newRole);\n  if (search == x58_roles.end()) {\n    if (x58_roles.size() >= x58_roles.capacity())\n      return false;\n    x58_roles.insert(std::lower_bound(x58_roles.begin(), x58_roles.end(), newRole), newRole);\n  } else {\n    *search = newRole;\n  }\n  UpdateTeamCaptain();\n  return true;\n}\n\nvoid CTeamAiMgr::RemoveTeamAiRole(TUniqueId aiId) {\n  if (IsMeleeAttacker(aiId))\n    RemoveMeleeAttacker(aiId);\n  if (IsRangedAttacker(aiId))\n    RemoveRangedAttacker(aiId);\n  auto search =\n      rstl::binary_find(x58_roles.begin(), x58_roles.end(), aiId, [](const auto& obj) { return obj.GetOwnerId(); });\n  x58_roles.erase(search);\n  UpdateTeamCaptain();\n}\n\nvoid CTeamAiMgr::ClearTeamAiRole(TUniqueId aiId) {\n  auto search =\n      rstl::binary_find(x58_roles.begin(), x58_roles.end(), aiId, [](const auto& obj) { return obj.GetOwnerId(); });\n  if (search != x58_roles.end())\n    search->x10_curRole = CTeamAiRole::ETeamAiRole::Initial;\n}\n\ns32 CTeamAiMgr::GetNumAssignedOfRole(CTeamAiRole::ETeamAiRole testRole) const {\n  return static_cast<s32>(std::count_if(x58_roles.cbegin(), x58_roles.cend(),\n                                        [testRole](const auto& role) { return role.GetTeamAiRole() == testRole; }));\n}\n\ns32 CTeamAiMgr::GetNumAssignedAiRoles() const {\n  return static_cast<s32>(std::count_if(x58_roles.cbegin(), x58_roles.cend(), [](const auto& role) {\n    const auto aiRole = role.GetTeamAiRole();\n    return aiRole > CTeamAiRole::ETeamAiRole::Initial && aiRole <= CTeamAiRole::ETeamAiRole::Unassigned;\n  }));\n}\n\nCTeamAiRole* CTeamAiMgr::GetTeamAiRole(CStateManager& mgr, TUniqueId mgrId, TUniqueId aiId) {\n  if (TCastToPtr<CTeamAiMgr> aimgr = mgr.ObjectById(mgrId))\n    return aimgr->GetTeamAiRole(aiId);\n  return nullptr;\n}\n\nvoid CTeamAiMgr::ResetTeamAiRole(EAttackType type, CStateManager& mgr, TUniqueId mgrId, TUniqueId aiId,\n                                 bool clearRole) {\n  if (TCastToPtr<CTeamAiMgr> tmgr = mgr.ObjectById(mgrId)) {\n    if (tmgr->HasTeamAiRole(aiId)) {\n      if (type == EAttackType::Melee) {\n        if (tmgr->IsMeleeAttacker(aiId))\n          tmgr->RemoveMeleeAttacker(aiId);\n      } else if (type == EAttackType::Ranged) {\n        if (tmgr->IsRangedAttacker(aiId))\n          tmgr->RemoveRangedAttacker(aiId);\n      }\n      if (clearRole)\n        tmgr->ClearTeamAiRole(aiId);\n    }\n  }\n}\n\nbool CTeamAiMgr::CanAcceptAttacker(EAttackType type, CStateManager& mgr, TUniqueId mgrId, TUniqueId aiId) {\n  if (TCastToPtr<CTeamAiMgr> tmgr = mgr.ObjectById(mgrId)) {\n    if (tmgr->HasTeamAiRole(aiId)) {\n      if (type == EAttackType::Melee)\n        return tmgr->CanAcceptMeleeAttacker(aiId);\n      else if (type == EAttackType::Ranged)\n        return tmgr->CanAcceptRangedAttacker(aiId);\n    }\n  }\n  return false;\n}\n\nbool CTeamAiMgr::AddAttacker(EAttackType type, CStateManager& mgr, TUniqueId mgrId, TUniqueId aiId) {\n  if (TCastToPtr<CTeamAiMgr> tmgr = mgr.ObjectById(mgrId)) {\n    if (tmgr->HasTeamAiRole(aiId)) {\n      if (type == EAttackType::Melee)\n        return tmgr->AddMeleeAttacker(aiId);\n      else if (type == EAttackType::Ranged)\n        return tmgr->AddRangedAttacker(aiId);\n    }\n  }\n  return false;\n}\n\nTUniqueId CTeamAiMgr::GetTeamAiMgr(const CAi& ai, const CStateManager& mgr) {\n  for (const auto& conn : ai.GetConnectionList()) {\n    if (conn.x0_state == EScriptObjectState::Active && conn.x4_msg == EScriptObjectMessage::Play) {\n      const TUniqueId id = mgr.GetIdForScript(conn.x8_objId);\n      if (const TCastToConstPtr<CTeamAiMgr> aimgr = mgr.GetObjectById(id)) {\n        return aimgr->GetUniqueId();\n      }\n    }\n  }\n  return kInvalidUniqueId;\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CTeamAiMgr.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/World/CEntity.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CStateManager;\nclass CAi;\n\nclass CTeamAiRole {\n  friend class CTeamAiMgr;\n\npublic:\n  enum class ETeamAiRole { Invalid = -1, Initial, Melee, Ranged, Unknown, Unassigned };\n\nprivate:\n  TUniqueId x0_ownerId;\n  ETeamAiRole x4_roleA = ETeamAiRole::Invalid;\n  ETeamAiRole x8_roleB = ETeamAiRole::Invalid;\n  ETeamAiRole xc_roleC = ETeamAiRole::Invalid;\n  ETeamAiRole x10_curRole = ETeamAiRole::Invalid;\n  s32 x14_roleIndex = -1;\n  s32 x18_captainPriority = 0;\n  zeus::CVector3f x1c_position;\n\npublic:\n  CTeamAiRole(TUniqueId ownerId, ETeamAiRole a = ETeamAiRole::Invalid, ETeamAiRole b = ETeamAiRole::Invalid,\n              ETeamAiRole c = ETeamAiRole::Invalid)\n  : x0_ownerId(ownerId), x4_roleA(a), x8_roleB(b), xc_roleC(c) {}\n  TUniqueId GetOwnerId() const { return x0_ownerId; }\n  bool HasTeamAiRole() const { return false; }\n  ETeamAiRole GetTeamAiRole() const { return x10_curRole; }\n  void SetTeamAiRole(ETeamAiRole role) { x10_curRole = role; }\n  s32 GetRoleIndex() const { return x14_roleIndex; }\n  void SetRoleIndex(s32 idx) { x14_roleIndex = idx; }\n  const zeus::CVector3f& GetTeamPosition() const { return x1c_position; }\n  void SetTeamPosition(const zeus::CVector3f& pos) { x1c_position = pos; }\n  bool operator<(const CTeamAiRole& other) const { return x0_ownerId < other.x0_ownerId; }\n};\n\nclass CTeamAiData {\n  friend class CTeamAiMgr;\n  u32 x0_aiCount;\n  u32 x4_meleeCount;\n  u32 x8_rangedCount;\n  u32 xc_unknownCount;\n  u32 x10_maxMeleeAttackerCount;\n  u32 x14_maxRangedAttackerCount;\n  u32 x18_positionMode;\n  float x1c_meleeTimeInterval;\n  float x20_rangedTimeInterval;\n\npublic:\n  CTeamAiData(CInputStream& in, s32 propCount);\n};\n\nclass CTeamAiMgr : public CEntity {\npublic:\n  enum class EAttackType { Melee, Ranged };\n\nprivate:\n  CTeamAiData x34_data;\n  std::vector<CTeamAiRole> x58_roles;\n  std::vector<TUniqueId> x68_meleeAttackers;\n  std::vector<TUniqueId> x78_rangedAttackers;\n  float x88_timeDirty = 0.0f;\n  TUniqueId x8c_teamCaptainId = kInvalidUniqueId;\n  float x90_timeSinceMelee = 0.0f;\n  float x94_timeSinceRanged = 0.0f;\n\n  void UpdateTeamCaptain();\n  bool ShouldUpdateRoles(float dt);\n  void ResetRoles(CStateManager& mgr);\n  void AssignRoles(CTeamAiRole::ETeamAiRole role, s32 count);\n  void UpdateRoles(CStateManager& mgr);\n  void SpacingSort(CStateManager& mgr, const zeus::CVector3f& pos);\n  void PositionTeam(CStateManager& mgr);\n\npublic:\n  DEFINE_ENTITY\n  CTeamAiMgr(TUniqueId uid, std::string_view name, const CEntityInfo& info, const CTeamAiData& data);\n\n  void Accept(IVisitor&) override;\n  void Think(float dt, CStateManager& mgr) override;\n  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& mgr) override;\n  CTeamAiRole* GetTeamAiRole(TUniqueId aiId);\n  bool IsPartOfTeam(TUniqueId aiId) const;\n  bool HasTeamAiRole(TUniqueId aiId) const;\n  bool AssignTeamAiRole(const CAi& ai, CTeamAiRole::ETeamAiRole roleA, CTeamAiRole::ETeamAiRole roleB,\n                        CTeamAiRole::ETeamAiRole roleC);\n  void RemoveTeamAiRole(TUniqueId aiId);\n  void ClearTeamAiRole(TUniqueId aiId);\n  s32 GetNumAssignedOfRole(CTeamAiRole::ETeamAiRole role) const;\n  s32 GetNumAssignedAiRoles() const;\n\n  bool IsMeleeAttacker(TUniqueId aiId) const;\n  bool CanAcceptMeleeAttacker(TUniqueId aiId) const;\n  bool AddMeleeAttacker(TUniqueId aiId);\n  void RemoveMeleeAttacker(TUniqueId aiId);\n  bool IsRangedAttacker(TUniqueId aiId) const;\n  bool CanAcceptRangedAttacker(TUniqueId aiId) const;\n  bool AddRangedAttacker(TUniqueId aiId);\n  void RemoveRangedAttacker(TUniqueId aiId);\n\n  bool HasMeleeAttackers() const { return !x68_meleeAttackers.empty(); }\n  const std::vector<TUniqueId>& GetMeleeAttackers() const { return x68_meleeAttackers; }\n  bool HasRangedAttackers() const { return !x78_rangedAttackers.empty(); }\n  const std::vector<TUniqueId>& GetRangedAttackers() const { return x78_rangedAttackers; }\n  s32 GetNumRoles() const { return x58_roles.size(); }\n  const std::vector<CTeamAiRole>& GetRoles() const { return x58_roles; }\n  s32 GetMaxMeleeAttackerCount() const { return x34_data.x10_maxMeleeAttackerCount; }\n  s32 GetMaxRangedAttackerCount() const { return x34_data.x14_maxRangedAttackerCount; }\n\n  static CTeamAiRole* GetTeamAiRole(CStateManager& mgr, TUniqueId mgrId, TUniqueId aiId);\n  static void ResetTeamAiRole(EAttackType type, CStateManager& mgr, TUniqueId mgrId, TUniqueId aiId, bool clearRole);\n  static bool CanAcceptAttacker(EAttackType type, CStateManager& mgr, TUniqueId mgrId, TUniqueId aiId);\n  static bool AddAttacker(EAttackType type, CStateManager& mgr, TUniqueId mgrId, TUniqueId aiId);\n\n  static TUniqueId GetTeamAiMgr(const CAi& ai, const CStateManager& mgr);\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CTeamAiTypes.hpp",
    "content": "#pragma once\n\nnamespace metaforce {\n\nclass CTeamAiTypes {};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CVisorFlare.cpp",
    "content": "#include \"Runtime/World/CVisorFlare.hpp\"\n\n#include \"Runtime/Camera/CCameraManager.hpp\"\n#include \"Runtime/Camera/CGameCamera.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Graphics/CGX.hpp\"\n\nnamespace metaforce {\n\nstd::optional<CVisorFlare::CFlareDef> CVisorFlare::LoadFlareDef(CInputStream& in) {\n  u32 propCount = in.ReadLong();\n  if (propCount != 4)\n    return std::nullopt;\n\n  CAssetId txtrId = in.Get<CAssetId>();\n  float f1 = in.ReadFloat();\n  float f2 = in.ReadFloat();\n  zeus::CColor color = in.Get<zeus::CColor>();\n  if (!txtrId.IsValid())\n    return std::nullopt;\n\n  TToken<CTexture> tex = g_SimplePool->GetObj(SObjectTag{FOURCC('TXTR'), txtrId});\n\n  return CFlareDef(tex, f1, f2, color);\n}\n\nCVisorFlare::CVisorFlare(EBlendMode blendMode, bool b1, float f1, float f2, float f3, u32 w1, u32 w2,\n                         std::vector<CFlareDef> flares)\n: x0_blendMode(blendMode)\n, x4_flareDefs(std::move(flares))\n, x14_b1(b1)\n, x18_f1(std::max(f1, 1.0E-4f))\n, x1c_f2(f2)\n, x20_f3(f3)\n, x2c_w1(w1)\n, x30_w2(w2) {}\n\nvoid CVisorFlare::Update(float dt, const zeus::CVector3f& pos, const CActor* act, CStateManager& mgr) {\n  const CPlayerState::EPlayerVisor visor = mgr.GetPlayerState()->GetCurrentVisor();\n\n  if ((visor == CPlayerState::EPlayerVisor::Combat || (x2c_w1 != 1 && visor == CPlayerState::EPlayerVisor::Thermal)) &&\n      mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Unmorphed) {\n\n    zeus::CVector3f camPos = mgr.GetCameraManager()->GetCurrentCamera(mgr)->GetTranslation();\n    zeus::CVector3f camDiff = pos - camPos;\n    const float mag = camDiff.magnitude();\n    camDiff = camDiff * (1.f / mag);\n    EntityList nearVec;\n    mgr.BuildNearList(nearVec, camPos, camDiff, mag,\n                      CMaterialFilter::MakeInclude({EMaterialTypes::Occluder}), act);\n    TUniqueId id;\n    CRayCastResult result = mgr.RayWorldIntersection(\n        id, camPos, camDiff, mag,\n        CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {EMaterialTypes::SeeThrough}), nearVec);\n\n    if (result.IsInvalid()) {\n      x28_ -= dt;\n    } else {\n      x28_ += dt;\n    }\n    x28_ = zeus::clamp(0.f, x28_, x18_f1);\n    x24_ = 1.f - (x28_ / x18_f1);\n\n    const auto* curCam = mgr.GetCameraManager()->GetCurrentCamera(mgr);\n    const auto dir = (pos - curCam->GetTranslation()).normalized();\n    float dot = dir.dot(curCam->GetTransform().frontVector());\n    x24_ *= std::max(0.f, 1.f - (x1c_f2 * 4.f * (1.f - dot)));\n\n    if (x2c_w1 == 2) {\n      mgr.SetThermalColdScale2(mgr.GetThermalColdScale2() + x24_);\n    }\n  }\n}\n\nvoid CVisorFlare::Render(const zeus::CVector3f& pos, const CStateManager& mgr) const {\n  if (zeus::close_enough(x28_, x18_f1, 1.0E-5f) ||\n      mgr.GetPlayer().GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Unmorphed) {\n    return;\n  }\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CVisorFlare::Render\", zeus::skGrey);\n\n  switch (mgr.GetPlayerState()->GetActiveVisor(mgr)) {\n  case CPlayerState::EPlayerVisor::Combat:\n    if (x30_w2 != 0)\n      return;\n    break;\n  case CPlayerState::EPlayerVisor::Thermal:\n    if (x2c_w1 != 0)\n      return;\n    break;\n  default:\n    return;\n  }\n\n\n  CGraphics::DisableAllLights();\n  g_Renderer->SetDepthReadWrite(false, false);\n  const CGameCamera* cam = mgr.GetCameraManager()->GetCurrentCamera(mgr);\n  zeus::CVector3f camPos = cam->GetTranslation();\n  zeus::CVector3f camFront = cam->GetTransform().frontVector();\n  const auto invPos = CGraphics::mViewMatrix.inverse() * pos;\n  const auto invPos2 = CGraphics::mViewMatrix * zeus::CVector3f{-invPos.x(), invPos.y(), -invPos.z()};\n  if (!zeus::close_enough(x24_, 0.f, 1.0E-5f)) {\n    float acos = 0.f;\n    if (!zeus::close_enough(x20_f3, 0.f, 1.0E-5f)) {\n      zeus::CVector3f camDist{pos.x() - camPos.x(), pos.y() - camPos.y(), 0.f};\n      camDist.normalize();\n      zeus::CVector3f camDir{camFront.x(), camFront.y(), 0.f};\n      camDir.normalize();\n      acos = std::acos(camDist.dot(camDir));\n      if (camDist.x() * camDir.y() - camDir.x() * camDist.y() < 0.f) {\n        acos = -acos;\n      }\n      acos = x20_f3 * acos;\n    }\n    SetupRenderState(mgr);\n    for (const auto& item : x4_flareDefs) {\n      const auto origin = pos * (1.f - item.GetPosition()) + invPos2 * item.GetPosition();\n      g_Renderer->SetModelMatrix(zeus::lookAt(origin, camPos));\n      float scale = 0.5f * x24_ * item.GetScale();\n      if (x14_b1) {\n        auto dist = origin - camPos;\n        if (dist.canBeNormalized()) {\n          scale *= dist.magnitude();\n        }\n      }\n      if (item.GetTexture()) {\n        item.GetTexture()->Load(GX_TEXMAP0, EClampMode::Repeat);\n        float f1;\n        if (zeus::close_enough(acos, 0.f)) {\n          f1 = 0.f;\n        } else {\n          f1 = scale * std::sin(acos);\n          scale *= std::cos(acos);\n        }\n        if (mgr.GetThermalDrawFlag() == EThermalDrawFlag::Hot) {\n          DrawDirect(item.GetColor(), f1, scale);\n        } else {\n          DrawStreamed(item.GetColor(), f1, scale);\n        }\n      }\n    }\n    ResetTevSwapMode(mgr);\n  }\n}\n\nvoid CVisorFlare::SetupRenderState(const CStateManager& mgr) const {\n  if (mgr.GetThermalDrawFlag() == EThermalDrawFlag::Hot) {\n    CGX::SetBlendMode(GX_BM_BLEND, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR);\n    CGX::SetStandardTevColorAlphaOp(GX_TEVSTAGE0);\n    CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL);\n    CGX::SetAlphaCompare(GX_ALWAYS, 0, GX_AOP_OR, GX_ALWAYS, 0);\n    GXSetTevSwapMode(GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP1);\n    CGX::SetTevKColorSel(GX_TEVSTAGE0, GX_TEV_KCSEL_K0_A);\n    CGX::SetTevKAlphaSel(GX_TEVSTAGE0, GX_TEV_KASEL_K0_A);\n    CGX::SetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_TEXC, GX_CC_KONST, GX_CC_ZERO);\n    CGX::SetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_TEXA, GX_CA_KONST, GX_CA_ZERO);\n    CGX::SetNumTexGens(1);\n    CGX::SetNumChans(0);\n    CGX::SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY, false, GX_PTIDENTITY);\n    if (x0_blendMode == EBlendMode::Blend) {\n      CGX::SetStandardTevColorAlphaOp(GX_TEVSTAGE1);\n      CGX::SetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL);\n      CGX::SetTevColorIn(GX_TEVSTAGE1, GX_CC_ZERO, GX_CC_TEXA, GX_CC_CPREV, GX_CC_ZERO);\n      CGX::SetTevAlphaIn(GX_TEVSTAGE1, GX_CA_ZERO, GX_CA_TEXA, GX_CA_APREV, GX_CA_ZERO);\n      CGX::SetNumTevStages(2);\n    } else if (x0_blendMode == EBlendMode::Additive) {\n      CGX::SetNumTevStages(1);\n    }\n    constexpr std::array vtxDescList{\n        GXVtxDescList{GX_VA_POS, GX_DIRECT},\n        GXVtxDescList{GX_VA_TEX0, GX_DIRECT},\n        GXVtxDescList{GX_VA_NULL, GX_NONE},\n    };\n    CGX::SetVtxDescv(vtxDescList.data());\n  } else {\n    if (x0_blendMode == EBlendMode::Blend) {\n      g_Renderer->SetBlendMode_AlphaBlended();\n    } else if (x0_blendMode == EBlendMode::Additive) {\n      g_Renderer->SetBlendMode_AdditiveAlpha();\n    }\n    CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvModulate);\n    CGraphics::SetTevOp(ERglTevStage::Stage1, CTevCombiners::kEnvPassthru);\n  }\n}\n\nvoid CVisorFlare::ResetTevSwapMode(const CStateManager& mgr) const {\n  if (mgr.GetThermalDrawFlag() == EThermalDrawFlag::Hot) {\n    GXSetTevSwapMode(GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP0);\n  }\n}\n\nvoid CVisorFlare::DrawDirect(const zeus::CColor& color, float f1, float f2) const {\n  zeus::CColor kcolor = color;\n  kcolor.a() *= x24_;\n  CGX::SetTevKColor(GX_KCOLOR0, kcolor);\n  CGX::Begin(GX_TRIANGLESTRIP, GX_VTXFMT0, 4);\n  GXPosition3f32(f1 - f2, 0.f, f2 + f1);\n  GXTexCoord2f32(0.f, 1.f);\n  GXPosition3f32(f2 + f1, 0.f, f2 - f1);\n  GXTexCoord2f32(1.f, 1.f);\n  GXPosition3f32(-(f2 - f1), 0.f, -(f2 - f1));\n  GXTexCoord2f32(0.f, 0.f);\n  GXPosition3f32(-f1 + f2, 0.f, -f2 - f1);\n  GXTexCoord2f32(1.f, 0.f);\n  CGX::End();\n}\n\nvoid CVisorFlare::DrawStreamed(const zeus::CColor& color, float f1, float f2) const {\n  CGraphics::StreamBegin(ERglPrimitive::TriangleStrip);\n  zeus::CColor kcolor = color;\n  kcolor.a() *= x24_;\n  CGraphics::StreamColor(kcolor);\n  CGraphics::StreamTexcoord(0.f, 1.f);\n  CGraphics::StreamVertex(f1 - f2, 0.f, f2 + f1);\n  CGraphics::StreamTexcoord(1.f, 1.f);\n  CGraphics::StreamVertex(f1 + f2, 0.f, f2 - f1);\n  CGraphics::StreamTexcoord(0.f, 0.f);\n  CGraphics::StreamVertex(-(f1 + f2), 0.f, -(f2 - f1));\n  CGraphics::StreamTexcoord(1.f, 0.f);\n  CGraphics::StreamVertex(-f1 + f2, 0.f, -f2 - f1);\n  CGraphics::StreamEnd();\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CVisorFlare.hpp",
    "content": "#pragma once\n\n#include <optional>\n#include <vector>\n\n#include \"Runtime/CToken.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n\n#include <zeus/CColor.hpp>\n\nnamespace metaforce {\nclass CActor;\nclass CStateManager;\nclass CTexture;\n\nclass CVisorFlare {\npublic:\n  enum class EBlendMode {\n    Additive = 0,\n    Blend = 1,\n  };\n  class CFlareDef {\n    mutable TToken<CTexture> x0_tex;\n    float x8_pos;\n    float xc_scale;\n    zeus::CColor x10_color;\n\n  public:\n    CFlareDef() = default;\n    CFlareDef(const CFlareDef&) = default;\n    CFlareDef(const TToken<CTexture>& tex, float pos, float scale, const zeus::CColor& color)\n    : x0_tex(tex), x8_pos(pos), xc_scale(scale), x10_color(color) {\n      x0_tex.Lock();\n    }\n\n    TToken<CTexture>& GetTexture() const { return x0_tex; }\n    zeus::CColor GetColor() const { return x10_color; }\n    float GetPosition() const { return x8_pos; }\n    float GetScale() const { return xc_scale; }\n  };\n\nprivate:\n  EBlendMode x0_blendMode;\n  std::vector<CFlareDef> x4_flareDefs;\n  bool x14_b1;\n  float x18_f1;\n  float x1c_f2;\n  float x20_f3;\n  float x24_ = 0.f;\n  float x28_ = 0.f;\n  u32 x2c_w1;\n  u32 x30_w2;\n\n  void SetupRenderState(const CStateManager& mgr) const;\n  void ResetTevSwapMode(const CStateManager& mgr) const;\n  void DrawDirect(const zeus::CColor& color, float f1, float f2) const;\n  void DrawStreamed(const zeus::CColor& color, float f1, float f2) const;\n\npublic:\n  CVisorFlare(EBlendMode blendMode, bool, float, float, float, u32, u32, std::vector<CFlareDef> flares);\n  void Update(float dt, const zeus::CVector3f& pos, const CActor* act, CStateManager& mgr);\n  void Render(const zeus::CVector3f& pos, const CStateManager& mgr) const;\n  static std::optional<CFlareDef> LoadFlareDef(CInputStream& in);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CVisorParameters.hpp",
    "content": "#pragma once\n\n#include \"Runtime/GCNTypes.hpp\"\n\nnamespace metaforce {\n\nclass CVisorParameters {\npublic:\n  u8 x0_mask : 4 = 0xf;\n  bool x0_4_b1 : 1 = false;\n  bool x0_5_scanPassthrough : 1 = false;\n  CVisorParameters() = default;\n  CVisorParameters(u8 mask, bool b1, bool scanPassthrough)\n  : x0_mask(mask), x0_4_b1(b1), x0_5_scanPassthrough(scanPassthrough) {}\n  u8 GetMask() const { return x0_mask; }\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CWallCrawlerSwarm.cpp",
    "content": "#include \"Runtime/World/CWallCrawlerSwarm.hpp\"\n\n#include <array>\n\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Camera/CFirstPersonCamera.hpp\"\n#include \"Runtime/Character/CSteeringBehaviors.hpp\"\n#include \"Runtime/Collision/CGameCollision.hpp\"\n#include \"Runtime/Collision/CMaterialList.hpp\"\n#include \"Runtime/Collision/CMetroidAreaCollider.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Graphics/CGX.hpp\"\n#include \"Runtime/Graphics/CSkinnedModel.hpp\"\n#include \"Runtime/Graphics/CVertexMorphEffect.hpp\"\n#include \"Runtime/Weapon/CGameProjectile.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/World/CMarkerGrid.hpp\"\n#include \"Runtime/World/CPhysicsActor.hpp\"\n#include \"Runtime/World/CPlayer.hpp\"\n#include \"Runtime/World/CScriptDoor.hpp\"\n#include \"Runtime/World/CScriptWaypoint.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\n\nstatic CMaterialList MakeMaterialList() {\n  return CMaterialList(EMaterialTypes::Scannable, EMaterialTypes::Trigger, EMaterialTypes::NonSolidDamageable,\n                       EMaterialTypes::RadarObject);\n}\n\nCWallCrawlerSwarm::CWallCrawlerSwarm(\n    TUniqueId uid, bool active, std::string_view name, const CEntityInfo& info,\n    const zeus::CVector3f& boundingBoxExtent, const zeus::CTransform& xf, EFlavor flavor, const CAnimRes& animRes,\n    s32 launchAnim, s32 attractAnim, CAssetId part1, CAssetId part2, CAssetId part3, CAssetId part4,\n    const CDamageInfo& crabDamage, const CDamageInfo& scarabExplodeDamage, float crabDamageCooldown, float boidRadius,\n    float touchRadius, float playerTouchRadius, u32 numBoids, u32 maxCreatedBoids, float animPlaybackSpeed,\n    float separationRadius, float cohesionMagnitude, float alignmentWeight, float separationMagnitude,\n    float moveToWaypointWeight, float attractionMagnitude, float attractionRadius, float boidGenRate, u32 maxLaunches,\n    float scarabBoxMargin, float scarabScatterXYVelocity, float scarabTimeToExplode, const CHealthInfo& hInfo,\n    const CDamageVulnerability& dVuln, s32 launchSfx, s32 scatterSfx, const CActorParameters& aParams)\n: CActor(uid, active, name, info, xf, CModelData::CModelDataNull(), MakeMaterialList(), aParams, kInvalidUniqueId)\n, x118_boundingBoxExtent(boundingBoxExtent)\n, x13c_separationRadius(separationRadius)\n, x140_cohesionMagnitude(cohesionMagnitude)\n, x144_alignmentWeight(alignmentWeight)\n, x148_separationMagnitude(separationMagnitude)\n, x14c_moveToWaypointWeight(moveToWaypointWeight)\n, x150_attractionMagnitude(attractionMagnitude)\n, x154_attractionRadius(attractionRadius)\n, x158_scarabScatterXYVelocity(scarabScatterXYVelocity)\n, x15c_scarabTimeToExplode(scarabTimeToExplode)\n, x160_animPlaybackSpeed(animPlaybackSpeed)\n, x364_boidGenRate(boidGenRate)\n, x370_crabDamageCooldown(crabDamageCooldown)\n, x374_boidRadius(boidRadius)\n, x378_touchRadius(touchRadius)\n, x37c_scarabBoxMargin(scarabBoxMargin)\n, x380_playerTouchRadius(playerTouchRadius)\n, x384_crabDamage(crabDamage)\n, x3a0_scarabExplodeDamage(scarabExplodeDamage)\n, x3bc_hInfo(hInfo)\n, x3c4_dVuln(dVuln)\n, x548_numBoids(numBoids)\n, x54c_maxCreatedBoids(maxCreatedBoids)\n, x554_maxLaunches(maxLaunches)\n, x558_flavor(flavor) {\n  x168_partitionedBoidLists.resize(125);\n  x55c_launchSfx = CSfxManager::TranslateSFXID(launchSfx != -1 ? u16(launchSfx) : u16(0xffff));\n  x55e_scatterSfx = CSfxManager::TranslateSFXID(scatterSfx != -1 ? u16(scatterSfx) : u16(0xffff));\n  CAnimRes attractAnimRes(animRes);\n  attractAnimRes.SetCanLoop(true);\n  attractAnimRes.SetDefaultAnim(attractAnim != -1 ? attractAnim : 0);\n  CAnimRes launchAnimRes(animRes);\n  launchAnimRes.SetCanLoop(true);\n  launchAnimRes.SetDefaultAnim(launchAnim != -1 ? launchAnim : 0);\n  x4b0_modelDatas.emplace_back(std::make_unique<CModelData>(animRes));\n  x4b0_modelDatas.emplace_back(std::make_unique<CModelData>(animRes));\n  x4b0_modelDatas.emplace_back(std::make_unique<CModelData>(animRes));\n  x4b0_modelDatas.emplace_back(std::make_unique<CModelData>(animRes));\n  x4b0_modelDatas.emplace_back(std::make_unique<CModelData>(attractAnimRes));\n  x4b0_modelDatas.emplace_back(std::make_unique<CModelData>(attractAnimRes));\n  x4b0_modelDatas.emplace_back(std::make_unique<CModelData>(attractAnimRes));\n  x4b0_modelDatas.emplace_back(std::make_unique<CModelData>(attractAnimRes));\n  x4b0_modelDatas.emplace_back(std::make_unique<CModelData>(launchAnimRes));\n  x4b0_modelDatas.emplace_back(std::make_unique<CModelData>(animRes));\n  if (aParams.GetXRayAssets().first.IsValid()) {\n    for (size_t i = 0; i < 9; ++i) {\n      x4b0_modelDatas[i]->SetXRayModel(aParams.GetXRayAssets());\n    }\n    x560_26_modelAssetDirty = true;\n  }\n  if (aParams.GetThermalAssets().first.IsValid()) {\n    for (size_t i = 0; i < 9; ++i) {\n      x4b0_modelDatas[i]->SetInfraModel(aParams.GetThermalAssets());\n    }\n    x560_26_modelAssetDirty = true;\n  }\n  if (part1.IsValid()) {\n    x4f0_particleDescs.push_back(g_SimplePool->GetObj({FOURCC('PART'), part1}));\n  }\n  if (part2.IsValid()) {\n    x4f0_particleDescs.push_back(g_SimplePool->GetObj({FOURCC('PART'), part2}));\n  }\n  if (part3.IsValid()) {\n    x4f0_particleDescs.push_back(g_SimplePool->GetObj({FOURCC('PART'), part3}));\n  }\n  if (part4.IsValid()) {\n    x4f0_particleDescs.push_back(g_SimplePool->GetObj({FOURCC('PART'), part4}));\n  }\n  for (const auto& t : x4f0_particleDescs) {\n    x524_particleGens.emplace_back(new CElementGen(t));\n    x524_particleGens.back()->SetParticleEmission(false);\n  }\n}\n\nvoid CWallCrawlerSwarm::Accept(IVisitor& visitor) { visitor.Visit(this); }\n\nvoid CWallCrawlerSwarm::AllocateSkinnedModels(CStateManager& mgr, CModelData::EWhichModel which) {\n  x430_workspaces.clear();\n  for (size_t i = 0; i < 9; ++i) {\n    x430_workspaces.emplace_back(x4b0_modelDatas[i]->PickAnimatedModel(which).CloneWorkspace());\n    x4b0_modelDatas[i]->EnableLooping(true);\n    x4b0_modelDatas[i]->AdvanceAnimation(x4b0_modelDatas[i]->GetAnimationData()->GetAnimTimeRemaining(\"Whole Body\"sv) *\n                                             (float(i) * 0.0625f),\n                                         mgr, x4_areaId, true);\n  }\n  x430_workspaces.emplace_back(x4b0_modelDatas.back()->PickAnimatedModel(which).CloneWorkspace());\n  x4dc_whichModel = which;\n}\n\nvoid CWallCrawlerSwarm::AddDoorRepulsors(CStateManager& mgr) {\n  size_t doorCount = 0;\n  for (const CEntity* ent : mgr.GetPhysicsActorObjectList()) {\n    if (const TCastToConstPtr<CScriptDoor> door = ent) {\n      if (door->GetAreaIdAlways() == x4_areaId) {\n        ++doorCount;\n      }\n    }\n  }\n\n  x4e0_doorRepulsors.reserve(doorCount);\n\n  for (const CEntity* ent : mgr.GetPhysicsActorObjectList()) {\n    if (const TCastToConstPtr<CScriptDoor> door = ent) {\n      if (door->GetAreaIdAlways() == x4_areaId) {\n        if (const auto tb = door->GetTouchBounds()) {\n          x4e0_doorRepulsors.emplace_back(tb->center(), (tb->min - tb->max).magnitude() * 0.75f);\n        }\n      }\n    }\n  }\n}\n\nvoid CWallCrawlerSwarm::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) {\n  CActor::AcceptScriptMsg(msg, sender, mgr);\n  switch (msg) {\n  case EScriptObjectMessage::Registered:\n    x108_boids.reserve(size_t(x548_numBoids));\n    for (int i = 0; i < x548_numBoids; ++i) {\n      x108_boids.emplace_back(zeus::CTransform(), i);\n    }\n    AllocateSkinnedModels(mgr, CModelData::EWhichModel::Normal);\n    AddDoorRepulsors(mgr);\n    CreateShadow(false);\n    break;\n  default:\n    break;\n  }\n}\n\nvoid CWallCrawlerSwarm::UpdateParticles(float dt) {\n  for (auto& p : x524_particleGens) {\n    p->Update(dt);\n  }\n}\n\nint CWallCrawlerSwarm::SelectLockOnIdx(CStateManager& mgr) const {\n  const zeus::CTransform fpCamXf = mgr.GetCameraManager()->GetFirstPersonCamera()->GetTransform();\n  if (x42c_lockOnIdx != -1) {\n    const CBoid& b = x108_boids[x42c_lockOnIdx];\n    if (b.GetActive()) {\n      zeus::CVector3f dir = b.GetTranslation() - fpCamXf.origin;\n      const float mag = dir.magnitude();\n      dir = dir / mag;\n      if (fpCamXf.basis[1].dot(dir) > 0.92388f) {\n        if (mgr.RayStaticIntersection(fpCamXf.origin, dir, mag, CMaterialFilter::MakeInclude(EMaterialTypes::Solid))\n                .IsInvalid()) {\n          return x42c_lockOnIdx;\n        }\n      }\n    }\n    return -1;\n  }\n\n  int ret = -1;\n  const float omtd = mgr.GetPlayer().GetOrbitMaxTargetDistance(mgr);\n  const float omtdSq = omtd * omtd;\n  float maxDot = 0.5f;\n  for (size_t i = 0; i < x108_boids.size(); ++i) {\n    const CBoid& b = x108_boids[i];\n    if (b.GetActive()) {\n      zeus::CVector3f delta = b.GetTranslation() - fpCamXf.origin;\n      if (delta.magSquared() > omtdSq) {\n        continue;\n      }\n      if (delta.canBeNormalized()) {\n        const float thisDot = fpCamXf.basis[1].dot(delta.normalized());\n        if (thisDot > maxDot) {\n          ret = static_cast<int>(i);\n          maxDot = thisDot;\n        }\n      }\n    }\n  }\n  return ret;\n}\n\nzeus::CAABox CWallCrawlerSwarm::GetBoundingBox() const {\n  const zeus::CVector3f he = x118_boundingBoxExtent * 0.75f;\n  return zeus::CAABox(-he, he).getTransformedAABox(x34_transform);\n}\n\nTUniqueId CWallCrawlerSwarm::GetWaypointForState(EScriptObjectState state, CStateManager& mgr) const {\n  for (const auto& c : GetConnectionList()) {\n    if (c.x0_state == state && c.x4_msg == EScriptObjectMessage::Follow) {\n      return mgr.GetIdForScript(c.x8_objId);\n    }\n  }\n  return kInvalidUniqueId;\n}\n\nbool CWallCrawlerSwarm::PointOnSurface(const CCollisionSurface& surf, const zeus::CVector3f& pos,\n                                       const zeus::CPlane& plane) const {\n  const zeus::CVector3f projPt = ProjectPointToPlane(pos, surf.GetVert(0), plane.normal());\n  for (int i = 0; i < 3; ++i) {\n    if (plane.normal().dot((projPt - surf.GetVert(i)).cross(surf.GetVert((i + 2) % 3) - surf.GetVert(i))) < 0.f) {\n      return false;\n    }\n  }\n  return true;\n}\n\nbool CWallCrawlerSwarm::FindBestSurface(const CAreaCollisionCache& ccache, const zeus::CVector3f& pos, float radius,\n                                        CCollisionSurface& out) const {\n  bool ret = false;\n  const float radiusSq = radius * radius;\n  zeus::CSphere sphere(pos, radius);\n  for (const auto& c : ccache) {\n    for (const auto& n : c) {\n      if (CCollidableSphere::Sphere_AABox_Bool(sphere, n.GetBoundingBox())) {\n        const auto triList = n.GetTriangleArray();\n        for (int i = 0; i < triList.GetSize(); ++i) {\n          CCollisionSurface surf = n.GetOwner().GetMasterListTriangle(triList.GetAt(i));\n          const zeus::CPlane plane = surf.GetPlane();\n          const float distSq = std::fabs(plane.pointToPlaneDist(pos));\n          if (distSq < radiusSq && PointOnSurface(surf, pos, plane)) {\n            float dist = 0.f;\n            if (distSq != 0.f) {\n              dist = std::sqrt(distSq);\n            }\n            sphere.radius = dist;\n            out = surf;\n            ret = true;\n          }\n        }\n      }\n    }\n  }\n  return ret;\n}\n\nCCollisionSurface CWallCrawlerSwarm::FindBestCollisionInBox(CStateManager& mgr, const zeus::CVector3f& wpPos) const {\n  CCollisionSurface ret(zeus::skRight, zeus::skForward, zeus::skUp, 0xffffffff);\n  const zeus::CVector3f aabbExtents = GetBoundingBox().extents();\n  float f25 = 0.1f;\n  while (f25 < 1.f) {\n    const zeus::CVector3f scaledExtents = aabbExtents * f25;\n    CAreaCollisionCache ccache(zeus::CAABox(wpPos - scaledExtents, wpPos + scaledExtents));\n    CGameCollision::BuildAreaCollisionCache(mgr, ccache);\n    if (FindBestSurface(ccache, wpPos, 2.f * scaledExtents.magnitude(), ret)) {\n      return ret;\n    }\n    f25 += 0.1f;\n  }\n  return ret;\n}\n\nstatic zeus::CTransform LookAt(const zeus::CUnitVector3f& a, const zeus::CUnitVector3f& b, const zeus::CRelAngle& ang) {\n  const float dot = a.dot(b);\n  if (zeus::close_enough(dot, 1.f)) {\n    return zeus::CTransform();\n  }\n  if (dot > -0.99981f) {\n    return zeus::CQuaternion::clampedRotateTo(a, b, ang).toTransform();\n  }\n  if (a != zeus::skRight && b != zeus::skRight) {\n    return zeus::CQuaternion::fromAxisAngle(a.cross(zeus::skRight), ang).toTransform();\n  }\n  return zeus::CQuaternion::fromAxisAngle(a.cross(zeus::skUp), ang).toTransform();\n}\n\nvoid CWallCrawlerSwarm::CreateBoid(CStateManager& mgr, int idx) {\n  // zeus::CAABox aabb = GetBoundingBox();\n  const TUniqueId wpId = GetWaypointForState(EScriptObjectState::Patrol, mgr);\n  if (TCastToConstPtr<CScriptWaypoint> wp = mgr.GetObjectById(wpId)) {\n    const CCollisionSurface surf = FindBestCollisionInBox(mgr, wp->GetTranslation());\n    x108_boids[idx].Transform() =\n        zeus::CTransform::Translate(ProjectPointToPlane(wp->GetTranslation(), surf.GetVert(0), surf.GetNormal()) +\n                                    surf.GetNormal() * x374_boidRadius);\n    if (zeus::close_enough(zeus::skUp.dot(surf.GetNormal()), -1.f)) {\n      x108_boids[idx].Transform().setRotation(\n          zeus::CTransform(zeus::skRight, zeus::skBack, zeus::skDown, zeus::skZero3f));\n    } else {\n      x108_boids[idx].Transform().setRotation(LookAt(zeus::skUp, surf.GetNormal(), M_PIF));\n    }\n    x108_boids[idx].x80_24_active = true;\n    x108_boids[idx].x30_velocity = zeus::skZero3f;\n    x108_boids[idx].x3c_targetWaypoint = wpId;\n    x108_boids[idx].x7c_framesNotOnSurface = 0;\n    x108_boids[idx].x48_timeToDie = 0.f;\n    x108_boids[idx].x80_27_scarabExplodeTimerEnabled = false;\n    x108_boids[idx].x78_health = x3bc_hInfo.GetHP();\n  }\n}\n\nvoid CWallCrawlerSwarm::ExplodeBoid(CBoid& boid, CStateManager& mgr) {\n  KillBoid(boid, mgr, 0.f, 1.f);\n  mgr.ApplyDamageToWorld(GetUniqueId(), *this, boid.GetTranslation(), x3a0_scarabExplodeDamage,\n                         CMaterialFilter::MakeInclude({EMaterialTypes::Player}));\n}\n\nvoid CWallCrawlerSwarm::SetExplodeTimers(const zeus::CVector3f& pos, float radius, float minTime, float maxTime) {\n  const float radiusSq = radius * radius;\n  const float range = maxTime - minTime;\n\n  for (auto& b : x108_boids) {\n    if (b.GetActive() && b.x48_timeToDie <= 0.f) {\n      const float dist = (b.GetTranslation() - pos).magSquared();\n      if (dist < radiusSq) {\n        const float fac = dist / radiusSq * range + minTime;\n        if (b.x4c_timeToExplode > fac || b.x4c_timeToExplode == 0.f) {\n          b.x4c_timeToExplode = fac;\n        }\n      }\n    }\n  }\n}\n\nCWallCrawlerSwarm::CBoid* CWallCrawlerSwarm::GetListAt(const zeus::CVector3f& pos) {\n  const zeus::CAABox aabb = GetBoundingBox();\n  const zeus::CVector3f ints = (pos - aabb.min) / ((aabb.max - aabb.min) / 5.f);\n  const int idx = int(ints.x()) + int(ints.y()) * 5 + int(ints.z()) * 25;\n  if (idx < 0 || idx >= 125) {\n    return x360_outlierBoidList;\n  }\n  return x168_partitionedBoidLists[idx];\n}\n\nvoid CWallCrawlerSwarm::BuildBoidNearList(const CBoid& boid, float radius,\n                                          rstl::reserved_vector<CBoid*, 50>& nearList) {\n  CBoid* b = GetListAt(boid.GetTranslation());\n  while (b && nearList.size() < 50) {\n    const float distSq = (b->GetTranslation() - boid.GetTranslation()).magSquared();\n    if (distSq != 0.f && distSq < radius) {\n      nearList.push_back(b);\n    }\n    b = b->x44_next;\n  }\n}\n\nvoid CWallCrawlerSwarm::ApplySeparation(const CBoid& boid, const rstl::reserved_vector<CBoid*, 50>& nearList,\n                                        zeus::CVector3f& aheadVec) const {\n  if (nearList.empty()) {\n    return;\n  }\n\n  float minDist = FLT_MAX;\n  zeus::CVector3f pos;\n  for (const CBoid* b : nearList) {\n    const float dist = (boid.GetTranslation() - b->GetTranslation()).magSquared();\n    if (dist != 0.f && dist < minDist) {\n      minDist = dist;\n      pos = b->GetTranslation();\n    }\n  }\n\n  ApplySeparation(boid, pos, x13c_separationRadius, x148_separationMagnitude, aheadVec);\n}\n\nvoid CWallCrawlerSwarm::ApplySeparation(const CBoid& boid, const zeus::CVector3f& separateFrom, float separationRadius,\n                                        float separationMagnitude, zeus::CVector3f& aheadVec) const {\n  const zeus::CVector3f delta = boid.GetTranslation() - separateFrom;\n  if (!delta.canBeNormalized()) {\n    return;\n  }\n\n  const float deltaDistSq = delta.magSquared();\n  const float capDeltaDistSq = separationRadius * separationRadius;\n  if (deltaDistSq < capDeltaDistSq) {\n    aheadVec += (1.f - deltaDistSq / capDeltaDistSq) * delta.normalized() * separationMagnitude;\n  }\n}\n\nvoid CWallCrawlerSwarm::ScatterScarabBoid(CBoid& boid, CStateManager& mgr) const {\n  const zeus::CVector3f oldDir = boid.GetTransform().basis[1];\n  boid.Transform().setRotation(zeus::CTransform());\n  boid.Transform() = LookAt(boid.GetTransform().basis[1], oldDir, M_PIF).multiplyIgnoreTranslation(boid.GetTransform());\n  boid.x30_velocity = zeus::skZero3f;\n  const float angle = mgr.GetActiveRandom()->Float() * (2.f * M_PIF);\n  const float mag = mgr.GetActiveRandom()->Float() * x158_scarabScatterXYVelocity;\n  boid.x30_velocity.x() = mag * std::cos(angle);\n  boid.x30_velocity.y() = mag * std::sin(angle);\n  boid.x80_26_launched = true;\n  boid.x7c_remainingLaunchNotOnSurfaceFrames = 5;\n  CSfxManager::AddEmitter(x55c_launchSfx, boid.GetTranslation(), zeus::skZero3f, true, false, 0x7f, x4_areaId);\n}\n\nvoid CWallCrawlerSwarm::MoveToWayPoint(CBoid& boid, CStateManager& mgr, zeus::CVector3f& aheadVec) const {\n  if (const TCastToConstPtr<CScriptWaypoint> wp = mgr.ObjectById(boid.x3c_targetWaypoint)) {\n    const CScriptWaypoint* useWp = wp.GetPtr();\n    if ((useWp->GetTranslation() - boid.GetTranslation()).magSquared() <\n        x164_waypointGoalRadius * x164_waypointGoalRadius) {\n      boid.x3c_targetWaypoint = useWp->NextWaypoint(mgr);\n      if (boid.x3c_targetWaypoint == kInvalidUniqueId) {\n        if (x558_flavor == EFlavor::Scarab) {\n          ScatterScarabBoid(boid, mgr);\n        } else {\n          boid.x80_24_active = false;\n          return;\n        }\n      } else {\n        useWp = TCastToConstPtr<CScriptWaypoint>(mgr.ObjectById(boid.x3c_targetWaypoint)).GetPtr();\n      }\n    }\n    aheadVec += (useWp->GetTranslation() - boid.GetTranslation()).normalized() * x14c_moveToWaypointWeight;\n  }\n}\n\nvoid CWallCrawlerSwarm::ApplyCohesion(const CBoid& boid, const rstl::reserved_vector<CBoid*, 50>& nearList,\n                                      zeus::CVector3f& aheadVec) const {\n  if (nearList.empty()) {\n    return;\n  }\n\n  zeus::CVector3f avg;\n  for (const CBoid* b : nearList) {\n    avg += b->GetTranslation();\n  }\n\n  avg = avg / float(nearList.size());\n  ApplyCohesion(boid, avg, x13c_separationRadius, x140_cohesionMagnitude, aheadVec);\n}\n\nvoid CWallCrawlerSwarm::ApplyCohesion(const CBoid& boid, const zeus::CVector3f& cohesionFrom, float cohesionRadius,\n                                      float cohesionMagnitude, zeus::CVector3f& aheadVec) const {\n  const zeus::CVector3f delta = cohesionFrom - boid.GetTranslation();\n  if (!delta.canBeNormalized()) {\n    return;\n  }\n\n  const float distSq = delta.magSquared();\n  const float capDistSq = cohesionRadius * cohesionRadius;\n  aheadVec += ((distSq > capDistSq) ? 1.f : distSq / capDistSq) * delta.normalized() * cohesionMagnitude;\n}\n\nvoid CWallCrawlerSwarm::ApplyAlignment(const CBoid& boid, const rstl::reserved_vector<CBoid*, 50>& nearList,\n                                       zeus::CVector3f& aheadVec) const {\n  if (nearList.empty()) {\n    return;\n  }\n\n  zeus::CVector3f avg;\n  for (const CBoid* b : nearList) {\n    avg += b->GetTransform().basis[1];\n  }\n\n  avg = avg / float(nearList.size());\n  aheadVec += zeus::CVector3f::getAngleDiff(boid.GetTransform().basis[1], avg) / M_PIF * (avg * x144_alignmentWeight);\n}\n\nvoid CWallCrawlerSwarm::ApplyAttraction(const CBoid& boid, const zeus::CVector3f& attractTo, float attractionRadius,\n                                        float attractionMagnitude, zeus::CVector3f& aheadVec) const {\n  const zeus::CVector3f delta = attractTo - boid.GetTranslation();\n  if (!delta.canBeNormalized()) {\n    return;\n  }\n\n  const float distSq = delta.magSquared();\n  const float capDistSq = attractionRadius * attractionRadius;\n  aheadVec += ((distSq > capDistSq) ? 0.f : (1.f - distSq / capDistSq)) * delta.normalized() * attractionMagnitude;\n}\n\nvoid CWallCrawlerSwarm::UpdateBoid(const CAreaCollisionCache& ccache, CStateManager& mgr, float dt, CBoid& boid) {\n  if (boid.x80_27_scarabExplodeTimerEnabled) {\n    if (x558_flavor == EFlavor::Scarab && boid.x4c_timeToExplode > 0.f) {\n      boid.x4c_timeToExplode -= 2.f * dt;\n      if (boid.x4c_timeToExplode <= 0.f) {\n        ExplodeBoid(boid, mgr);\n      }\n    }\n  } else if (boid.x80_26_launched) {\n    const float radius = 2.f * x374_boidRadius;\n    const float boidMag = boid.x30_velocity.magnitude();\n    float f20 = boidMag * dt;\n    const zeus::CVector3f f25 = (-boid.x30_velocity / boidMag) * x374_boidRadius;\n    zeus::CVector3f f28 = boid.GetTranslation();\n    bool found = false;\n    while (f20 >= 0.f && !found) {\n      CCollisionSurface surf(zeus::skRight, zeus::skForward, zeus::skUp, 0xffffffff);\n      if (FindBestSurface(ccache, boid.x30_velocity * dt * 1.5f + f28, radius, surf) &&\n          boid.x7c_remainingLaunchNotOnSurfaceFrames == 0) {\n        if (x558_flavor != EFlavor::Scarab) {\n          boid.Transform() = LookAt(boid.GetTransform().basis[2], surf.GetNormal(), M_PIF)\n                                 .multiplyIgnoreTranslation(boid.GetTransform());\n        }\n        const auto plane = surf.GetPlane();\n        boid.Translation() +=\n            -(plane.pointToPlaneDist(boid.GetTranslation()) - x374_boidRadius - 0.01f) * plane.normal();\n        boid.x7c_framesNotOnSurface = 0;\n        boid.x80_26_launched = false;\n        if (x558_flavor == EFlavor::Scarab) {\n          boid.x80_27_scarabExplodeTimerEnabled = true;\n          boid.x4c_timeToExplode = x15c_scarabTimeToExplode;\n          CSfxManager::AddEmitter(x55e_scatterSfx, boid.GetTranslation(), zeus::skZero3f, true, false, 0x7f, x4_areaId);\n        }\n        found = true;\n      }\n      f20 -= x374_boidRadius;\n      f28 += f25;\n    }\n    if (!found) {\n      boid.x30_velocity += zeus::CVector3f(0.f, 0.f,\n                                           -(x558_flavor == EFlavor::Scarab ? 3.f * CPhysicsActor::GravityConstant()\n                                                                            : CPhysicsActor::GravityConstant())) *\n                           dt;\n      if (boid.x7c_remainingLaunchNotOnSurfaceFrames) {\n        boid.x7c_remainingLaunchNotOnSurfaceFrames -= 1;\n      }\n    }\n  } else if (boid.x7c_framesNotOnSurface >= 30) {\n    boid.x80_24_active = false;\n  } else {\n    const float radius = 2.f * x374_boidRadius;\n    bool found = false;\n    CCollisionSurface surf(zeus::skRight, zeus::skForward, zeus::skUp, 0xffffffff);\n    if (FindBestSurface(ccache, boid.GetTranslation() + boid.x30_velocity * dt * 1.5f, radius, surf)) {\n      boid.x50_surface = surf;\n      boid.Transform() = LookAt(boid.GetTransform().basis[2], surf.GetNormal(), zeus::degToRad(180.f * dt))\n                             .multiplyIgnoreTranslation(boid.GetTransform());\n      const auto plane = surf.GetPlane();\n      const float dist = plane.pointToPlaneDist(boid.GetTranslation());\n      if (dist <= 1.5f * x374_boidRadius) {\n        boid.Translation() += -(dist - x374_boidRadius - 0.01f) * plane.normal();\n        boid.x7c_framesNotOnSurface = 0;\n        found = true;\n      }\n    }\n    if (!found) {\n      boid.Transform() = LookAt(boid.GetTransform().basis[2], boid.GetTransform().basis[1],\n                                boid.x30_velocity.magnitude() / x374_boidRadius * dt)\n                             .multiplyIgnoreTranslation(boid.GetTransform());\n      boid.x7c_framesNotOnSurface += 1;\n    }\n    rstl::reserved_vector<CBoid*, 50> nearList;\n    BuildBoidNearList(boid, x13c_separationRadius, nearList);\n    zeus::CVector3f aheadVec = boid.GetTransform().basis[1] * 0.3f;\n    for (int r26 = 0; r26 < 8; ++r26) {\n      switch (r26) {\n      case 0:\n        for (const auto& rep : x4e0_doorRepulsors) {\n          if ((rep.x0_center - boid.GetTranslation()).magSquared() < rep.xc_mag * rep.xc_mag) {\n            ApplySeparation(boid, rep.x0_center, rep.xc_mag, 4.5f, aheadVec);\n          }\n        }\n        break;\n      case 4:\n        ApplySeparation(boid, nearList, aheadVec);\n        break;\n      case 5:\n        MoveToWayPoint(boid, mgr, aheadVec);\n        break;\n      case 6:\n        ApplyCohesion(boid, nearList, aheadVec);\n        break;\n      case 7:\n        ApplyAlignment(boid, nearList, aheadVec);\n        break;\n      case 3:\n        ApplyAttraction(boid, mgr.GetPlayer().GetTranslation(), x154_attractionRadius, x150_attractionMagnitude,\n                        aheadVec);\n        break;\n      default:\n        break;\n      }\n      if (aheadVec.magSquared() >= 9.f) {\n        break;\n      }\n    }\n    boid.Transform() = LookAt(boid.GetTransform().basis[1],\n                              ProjectVectorToPlane(aheadVec, boid.GetTransform().basis[2]).normalized(), M_PIF * dt)\n                           .multiplyIgnoreTranslation(boid.GetTransform());\n  }\n}\n\nvoid CWallCrawlerSwarm::LaunchBoid(CBoid& boid, const zeus::CVector3f& dir) {\n  const zeus::CVector3f pos = boid.GetTranslation();\n  static float skAttackTime = std::sqrt(2.5f / CPhysicsActor::GravityConstant()) * 2.f;\n  static float skAttackVelocity = 15.f / skAttackTime;\n  zeus::CVector3f deltaFlat = dir - pos;\n  const float deltaZ = deltaFlat.z();\n  deltaFlat.z() = 0.f;\n  const float deltaMag = deltaFlat.magnitude();\n  boid.Transform().setRotation(zeus::CTransform());\n  boid.Transform() = LookAt(boid.GetTransform().basis[1], deltaFlat.normalized(), M_PIF)\n                         .multiplyIgnoreTranslation(boid.GetTransform());\n  zeus::CVector3f vec(skAttackVelocity * boid.GetTransform().basis[1].toVec2f(), 0.5f * skAttackVelocity);\n  if (deltaMag > FLT_EPSILON) {\n    deltaFlat = deltaFlat / deltaMag;\n    const float dot = deltaFlat.dot(vec);\n    if (dot > FLT_EPSILON) {\n      const bool r29 = deltaZ < 0.f;\n      float _12c, _130;\n      float f25 = 0.f;\n      if (CSteeringBehaviors::SolveQuadratic(-CPhysicsActor::GravityConstant(), vec.z(), -deltaZ, _12c, _130)) {\n        f25 = r29 ? _130 : _12c;\n      }\n      if (!r29) {\n        f25 += deltaMag / dot;\n      }\n      if (f25 < 10.f) {\n        vec.x() = deltaMag / f25 * deltaFlat.x() * 0.6f;\n        vec.y() = deltaMag / f25 * deltaFlat.y() * 0.6f;\n        vec.z() = deltaZ / f25 - 0.5f * -CPhysicsActor::GravityConstant() * f25;\n      }\n    }\n  }\n  boid.x30_velocity = vec;\n  boid.x80_26_launched = true;\n  boid.x7c_remainingLaunchNotOnSurfaceFrames = 1;\n  CSfxManager::AddEmitter(x55c_launchSfx, pos, zeus::skZero3f, true, false, 0x7f, x4_areaId);\n}\n\nvoid CWallCrawlerSwarm::AddParticle(const zeus::CTransform& xf) {\n  static constexpr std::array particleCounts{8, 2, 0, 0};\n\n  size_t i = 0;\n  for (auto& p : x524_particleGens) {\n    p->SetParticleEmission(true);\n    p->SetTranslation(xf.origin);\n    p->ForceParticleCreation(particleCounts[i]);\n    p->SetParticleEmission(false);\n    ++i;\n  }\n}\n\nvoid CWallCrawlerSwarm::KillBoid(CBoid& boid, CStateManager& mgr, float deathRattleChance, float deadChance) {\n  x130_lastKilledOffset = boid.GetTranslation();\n  AddParticle(boid.GetTransform());\n  boid.x80_24_active = false;\n\n  const float sendDeadRoll = mgr.GetActiveRandom()->Float();\n  const float sendDeathRattleRoll = mgr.GetActiveRandom()->Float();\n  if (sendDeathRattleRoll < deathRattleChance) {\n    SendScriptMsgs(EScriptObjectState::DeathRattle, mgr, EScriptObjectMessage::None);\n  }\n  if (sendDeadRoll < deadChance) {\n    SendScriptMsgs(EScriptObjectState::Dead, mgr, EScriptObjectMessage::None);\n  }\n}\n\nvoid CWallCrawlerSwarm::UpdatePartition() {\n  x168_partitionedBoidLists.clear();\n  x168_partitionedBoidLists.resize(125);\n  x360_outlierBoidList = nullptr;\n\n  const zeus::CAABox aabb = GetBoundingBox();\n  const zeus::CVector3f vec = (aabb.max - aabb.min) / 5.f;\n  for (auto& b : x108_boids) {\n    if (b.GetActive()) {\n      const zeus::CVector3f divVec = (b.GetTranslation() - aabb.min) / vec;\n      const int xIdx = int(divVec.x());\n      const int yIdx = int(divVec.y());\n      const int zIdx = int(divVec.z());\n      const int idx = xIdx + yIdx * 5 + zIdx * 25;\n      if (idx < 0 || idx >= 125 || xIdx < 0 || xIdx >= 5 || yIdx < 0 || yIdx >= 5 || zIdx < 0 || zIdx >= 5) {\n        b.x44_next = x360_outlierBoidList;\n        x360_outlierBoidList = &b;\n      } else {\n        b.x44_next = x168_partitionedBoidLists[idx];\n        x168_partitionedBoidLists[idx] = &b;\n      }\n    }\n  }\n}\n\nzeus::CVector3f CWallCrawlerSwarm::FindClosestCell(const zeus::CVector3f& pos) const {\n  float minDist = FLT_MAX;\n  zeus::CVector3f ret;\n  for (int r28 = 0; r28 < 5; ++r28) {\n    for (int r29 = 0; r29 < 5; ++r29) {\n      for (int r25 = 0; r25 < 5; ++r25) {\n        const zeus::CAABox aabb = BoxForPosition(r28, r29, r25, 0.1f);\n        const float dist = (aabb.center() - pos).magSquared();\n        if (dist < minDist) {\n          minDist = dist;\n          ret = aabb.center();\n        }\n      }\n    }\n  }\n  return ret;\n}\n\nvoid CWallCrawlerSwarm::UpdateEffects(CStateManager& mgr, CAnimData& aData, int vol) {\n  if (aData.GetPassedSoundPOICount() == 0 || CAnimData::g_SoundPOINodes.empty()) {\n    return;\n  }\n\n  for (size_t i = 0; i < aData.GetPassedSoundPOICount(); ++i) {\n    const CSoundPOINode& n = CAnimData::g_SoundPOINodes[i];\n    if (n.GetPoiType() != EPOIType::Sound ||\n        (n.GetCharacterIndex() != -1 && n.GetCharacterIndex() != aData.GetCharacterIndex())) {\n      continue;\n    }\n\n    const u16 sfx = CSfxManager::TranslateSFXID(u16(n.GetSfxId() & 0xffff));\n    const bool loop = bool(n.GetSfxId() >> 31);\n    if (loop) {\n      continue;\n    }\n\n    CAudioSys::C3DEmitterParmData parmData;\n    parmData.x0_pos = FindClosestCell(mgr.GetPlayer().GetTranslation());\n    static float maxDist = n.GetMaxDist();\n    static float falloff = n.GetFalloff();\n    parmData.x18_maxDist = maxDist;\n    parmData.x1c_distComp = falloff;\n    parmData.x20_flags = 0x1;\n    parmData.x24_sfxId = sfx;\n    parmData.x26_maxVol = zeus::clamp(0, vol, 127) / 127.f;\n    parmData.x27_minVol = 20.f / 127.f;\n    parmData.x28_important = false;\n    parmData.x29_prio = 0x7f;\n    CSfxManager::AddEmitter(parmData, true, 0x7f, false, x4_areaId);\n  }\n}\n\nzeus::CAABox CWallCrawlerSwarm::BoxForPosition(int x, int y, int z, float f) const {\n  const zeus::CAABox aabb = GetBoundingBox();\n  const zeus::CVector3f vec = (aabb.max - aabb.min) / 5.f;\n  return zeus::CAABox(zeus::CVector3f(x, y, z) * vec + aabb.min - f,\n                      zeus::CVector3f(x + 1, y + 1, z + 1) * vec + aabb.min + f);\n}\n\nvoid CWallCrawlerSwarm::Think(float dt, CStateManager& mgr) {\n  if (!GetActive()) {\n    return;\n  }\n\n  if (x560_26_modelAssetDirty && CModelData::GetRenderingModel(mgr) != x4dc_whichModel) {\n    const auto which = CModelData::GetRenderingModel(mgr);\n    if (which != x4dc_whichModel) {\n      AllocateSkinnedModels(mgr, which);\n    }\n  }\n\n  xe4_27_notInSortedLists = true;\n  x368_boidGenCooldownTimer -= dt;\n  x36c_crabDamageCooldownTimer -= dt;\n  ++x100_thinkCounter;\n  const CGameArea* area = mgr.GetWorld()->GetAreaAlways(x4_areaId);\n  const auto occState = area->IsPostConstructed() ? area->GetOcclusionState() : CGameArea::EOcclusionState::Occluded;\n  if (occState != CGameArea::EOcclusionState::Visible) {\n    if (x104_occludedTimer > 0.f) {\n      x104_occludedTimer -= dt;\n    }\n    if (x104_occludedTimer <= 0.f) {\n      return;\n    }\n    if (x100_thinkCounter & 0x2) {\n      return;\n    }\n  } else {\n    x104_occludedTimer = 7.f;\n  }\n\n  UpdateParticles(dt);\n  x42c_lockOnIdx = SelectLockOnIdx(mgr);\n  xe7_31_targetable = x42c_lockOnIdx != -1;\n\n  if (x42c_lockOnIdx == -1) {\n    RemoveMaterial(EMaterialTypes::Target, EMaterialTypes::Orbit, mgr);\n  } else {\n    AddMaterial(EMaterialTypes::Target, EMaterialTypes::Orbit, mgr);\n  }\n\n  while ((x54c_maxCreatedBoids == 0 || x550_createdBoids < x54c_maxCreatedBoids) && x368_boidGenCooldownTimer <= 0.f) {\n    int idx = 0;\n    bool madeBoid = false;\n    for (const auto& b : x108_boids) {\n      if (!b.GetActive()) {\n        CreateBoid(mgr, idx);\n        x550_createdBoids += 1;\n        x368_boidGenCooldownTimer += 1.f / x364_boidGenRate;\n        madeBoid = true;\n        break;\n      }\n      ++idx;\n    }\n    if (!madeBoid) {\n      x368_boidGenCooldownTimer += 1.f / x364_boidGenRate;\n      break;\n    }\n  }\n  UpdatePartition();\n  xe8_aabox = GetBoundingBox();\n\n  int r21 = 0;\n  for (int r26 = 0; r26 < 5; ++r26) {\n    for (int r27 = 0; r27 < 5; ++r27) {\n      for (int r20 = 0; r20 < 5; ++r20) {\n        const int idx = r20 * 25 + r27 * 5 + r26;\n        if (CBoid* boid = x168_partitionedBoidLists[idx]) {\n          const zeus::CAABox aabb = BoxForPosition(r26, r27, r20, x374_boidRadius + 0.5f);\n          CAreaCollisionCache ccache(aabb);\n          CGameCollision::BuildAreaCollisionCache(mgr, ccache);\n          while (boid != nullptr) {\n            r21 += 1;\n            if (boid->GetActive()) {\n              if (x558_flavor == EFlavor::Scarab) {\n                xe8_aabox.accumulateBounds(boid->GetTranslation() + x37c_scarabBoxMargin);\n                xe8_aabox.accumulateBounds(boid->GetTranslation() - x37c_scarabBoxMargin);\n              } else {\n                xe8_aabox.accumulateBounds(boid->GetTranslation());\n              }\n            }\n            if (((x100_thinkCounter & 0x1) == (r21 & 0x1) && boid->GetActive() && boid->x48_timeToDie < 0.1f) ||\n                boid->x80_26_launched) {\n              UpdateBoid(ccache, mgr, dt, *boid);\n            }\n            boid = boid->x44_next;\n          }\n        }\n      }\n    }\n  }\n\n  for (CBoid* boid = x360_outlierBoidList; boid; boid = boid->x44_next) {\n    r21 += 1;\n    if (boid->GetActive()) {\n      xe8_aabox.accumulateBounds(boid->GetTranslation());\n    }\n    if (((x100_thinkCounter & 0x1) == (r21 & 0x1) && boid->GetActive() && boid->x48_timeToDie < 0.1f) ||\n        boid->x80_26_launched) {\n      const float margin = 1.5f + x374_boidRadius + 0.5f;\n      const zeus::CAABox aabb(boid->GetTranslation() - margin, boid->GetTranslation() + margin);\n      CAreaCollisionCache ccache(aabb);\n      CGameCollision::BuildAreaCollisionCache(mgr, ccache);\n      UpdateBoid(ccache, mgr, dt, *boid);\n    }\n  }\n\n  x4b0_modelDatas[8]->GetAnimationData()->SetPlaybackRate(x160_animPlaybackSpeed);\n  x4b0_modelDatas[8]->AdvanceAnimation(dt, mgr, x4_areaId, true);\n\n  SAdvancementDeltas deltas1, deltas2;\n\n  int r9 = 0;\n  int r3 = 0;\n  int r8 = 0;\n  std::array<bool, 4> _38F8{};\n  std::array<bool, 4> _38F4{};\n  for (const auto& b : x108_boids) {\n    if (b.GetActive() && !b.x80_26_launched) {\n      if (b.x80_27_scarabExplodeTimerEnabled || b.x80_28_nearPlayer) {\n        _38F8[r9 & 0x3] = true;\n        ++r3;\n      } else {\n        _38F4[r9 & 0x3] = true;\n        ++r8;\n      }\n    }\n    ++r9;\n  }\n\n  for (size_t i = 0; i < _38F4.size(); ++i) {\n    x4b0_modelDatas[i]->GetAnimationData()->SetPlaybackRate(x160_animPlaybackSpeed);\n    deltas1 = x4b0_modelDatas[i]->AdvanceAnimation(dt, mgr, x4_areaId, true);\n    x4b0_modelDatas[i + 4]->GetAnimationData()->SetPlaybackRate(x160_animPlaybackSpeed);\n    deltas2 = x4b0_modelDatas[i + 4]->AdvanceAnimation(dt, mgr, x4_areaId, true);\n    if (x4b0_modelDatas[i]->HasAnimData() && _38F4[i]) {\n      UpdateEffects(mgr, *x4b0_modelDatas[i]->GetAnimationData(), r8 * 44 / x548_numBoids + 0x53);\n    }\n    if (x4b0_modelDatas[i + 4]->HasAnimData() && _38F8[i]) {\n      UpdateEffects(mgr, *x4b0_modelDatas[i + 4]->GetAnimationData(), r3 * 44 / x548_numBoids + 0x53);\n    }\n    for (size_t r20 = i; r20 < x108_boids.size(); r20 += 4) {\n      CBoid& b = x108_boids[r20];\n      if (b.GetActive()) {\n        if (b.x80_26_launched) {\n          b.Translation() += b.x30_velocity * dt;\n        } else if (b.x48_timeToDie > 0.f) {\n          b.x48_timeToDie -= dt;\n          if (b.x48_timeToDie < 0.7f * mgr.GetActiveRandom()->Float()) {\n            KillBoid(b, mgr, 1.f, 0.05f);\n          }\n        } else if (b.x80_27_scarabExplodeTimerEnabled || b.x80_28_nearPlayer) {\n          b.x30_velocity = b.GetTransform().rotate(deltas2.x0_posDelta) * 1.5f / dt;\n          b.Translation() += b.x30_velocity * dt;\n        } else {\n          b.x30_velocity = b.GetTransform().rotate(deltas1.x0_posDelta) * 1.5f / dt;\n          b.Translation() += b.x30_velocity * dt;\n        }\n      }\n    }\n  }\n\n  if (x558_flavor == EFlavor::Crab) {\n    const zeus::CVector3f playerPos = mgr.GetPlayer().GetTranslation();\n    for (auto& b : x108_boids) {\n      if (b.GetActive() && zeus::close_enough(b.x48_timeToDie, 0.f) && !b.x80_26_launched) {\n        b.x80_28_nearPlayer = (playerPos - b.GetTranslation()).magnitude() < x154_attractionRadius;\n      }\n    }\n  }\n\n  if (x558_flavor == EFlavor::Parasite && x554_maxLaunches > 0) {\n    const zeus::CVector3f _383c = mgr.GetPlayer().GetTranslation() + zeus::skUp;\n    static constexpr auto filter = CMaterialFilter::MakeInclude(EMaterialTypes::Solid);\n    int numLaunched = 0;\n    for (const auto& b : x108_boids) {\n      if (b.GetActive() && b.x80_26_launched) {\n        ++numLaunched;\n      }\n    }\n\n    for (auto it = x108_boids.begin(); it != x108_boids.end() && numLaunched < x554_maxLaunches; ++it) {\n      CBoid& b = *it;\n      if (b.GetActive() && zeus::close_enough(b.x48_timeToDie, 0.f) && !b.x80_26_launched &&\n          (b.GetTranslation() - _383c).magSquared() < 18.f * 18.f && mgr.GetActiveRandom()->Float() <= 0.02f) {\n        zeus::CVector3f dir = _383c - b.GetTranslation();\n        const float mag = dir.magnitude();\n        dir = dir / mag;\n        if (mgr.RayStaticIntersection(b.GetTranslation(), dir, mag, filter).IsInvalid()) {\n          LaunchBoid(b, _383c);\n          ++numLaunched;\n        }\n      }\n    }\n  }\n}\n\nvoid CWallCrawlerSwarm::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) {\n  for (size_t i = 0; i < 5; ++i) {\n    x4b0_modelDatas[i]->GetAnimationData()->PreRender();\n  }\n  bool activeBoid = false;\n  for (auto& b : x108_boids) {\n    if (b.GetActive()) {\n      b.x80_25_inFrustum = frustum.sphereFrustumTest(zeus::CSphere(b.GetTranslation(), 2.f * x374_boidRadius));\n      activeBoid = true;\n    } else {\n      b.x80_25_inFrustum = false;\n    }\n  }\n  xe4_30_outOfFrustum = !activeBoid;\n}\n\nvoid CWallCrawlerSwarm::RenderParticles() const {\n  for (const auto& p : x524_particleGens) {\n    g_Renderer->AddParticleGen(*p);\n  }\n}\n\nvoid CWallCrawlerSwarm::AddToRenderer(const zeus::CFrustum&, CStateManager& mgr) {\n  if (!GetActive()) {\n    return;\n  }\n\n  RenderParticles();\n\n  if (xe4_30_outOfFrustum) {\n    return;\n  }\n\n  if (CanRenderUnsorted(mgr)) {\n    Render(mgr);\n  } else {\n    EnsureRendered(mgr);\n  }\n}\n\nzeus::CColor CWallCrawlerSwarm::SoftwareLight(const CStateManager& mgr, const zeus::CAABox& aabb) const {\n  CActorLights lights(8, zeus::skZero3f, 4, 4, false, false, false, 0.1f);\n  lights.SetDirty();\n  lights.SetCastShadows(false);\n  lights.SetFindShadowLight(false);\n  lights.BuildAreaLightList(mgr, *mgr.GetWorld()->GetAreaAlways(x4_areaId), aabb);\n  lights.BuildDynamicLightList(mgr, aabb);\n  zeus::CColor ret = lights.GetAmbientColor();\n  ret.a() = 1.f;\n  const zeus::CVector3f center = aabb.center();\n  const u32 lightCount = lights.GetActiveLightCount();\n  for (u32 i = 0; i < lightCount; ++i) {\n    const CLight& light = lights.GetLight(i);\n    const float dist = (light.GetPosition() - center).magnitude();\n    const float att = 1.f / (dist * dist * light.GetAttenuationQuadratic() + dist * light.GetAttenuationLinear() +\n                             light.GetAttenuationConstant());\n    ret += zeus::CColor::lerp(zeus::skBlack, light.GetColor(), 0.8f * std::min(att, 1.f));\n  }\n  return ret;\n}\n\nvoid CWallCrawlerSwarm::HardwareLight(const CStateManager& mgr, const zeus::CAABox& aabb) const {\n  CActorLights lights(8, zeus::skZero3f, 4, 4, false, false, false, 0.1f);\n  lights.SetDirty();\n  lights.SetCastShadows(false);\n  lights.SetFindShadowLight(false);\n  lights.BuildAreaLightList(mgr, *mgr.GetWorld()->GetAreaAlways(x4_areaId), aabb);\n  lights.BuildDynamicLightList(mgr, aabb);\n  lights.ActivateLights();\n}\n\nvoid CWallCrawlerSwarm::RenderBoid(const CBoid* boid, u32& drawMask, bool thermalHot, const CModelFlags& flags) const {\n  u32 modelIndex = 0x0;\n  if (boid->x80_26_launched) {\n    modelIndex = 0x8;\n  } else if (boid->x48_timeToDie > 0.f) {\n    modelIndex = 0x9;\n  } else if (boid->x80_27_scarabExplodeTimerEnabled || boid->x80_28_nearPlayer) {\n    modelIndex += 0x4;\n  }\n\n  CModelData& mData = *x4b0_modelDatas[modelIndex];\n  CSkinnedModel& model = mData.PickAnimatedModel(x4dc_whichModel);\n\n  const u32 thisDrawMask = 1u << modelIndex;\n  if ((drawMask & thisDrawMask) != 0u) {\n    drawMask &= ~thisDrawMask;\n    mData.GetAnimationData()->BuildPose();\n    model.Calculate(mData.GetAnimationData()->GetPose(), nullptr, {}, &x430_workspaces[modelIndex]);\n  }\n\n  g_Renderer->SetAmbientColor(boid->x40_ambientLighting);\n  g_Renderer->SetModelMatrix(boid->GetTransform());\n\n  const auto& positions = x430_workspaces[modelIndex].m_vertexWorkspace;\n  const auto& normals = x430_workspaces[modelIndex].m_normalWorkspace;\n  if (boid->x48_timeToDie > 0.f && !thermalHot) {\n    constexpr CModelFlags useFlags(0, 0, 3, zeus::skWhite);\n    model.Draw(positions, normals, useFlags);\n    if (auto iceModel = mData.GetAnimationData()->GetIceModel()) {\n      g_Renderer->SetAmbientColor(zeus::skWhite);\n      const float alpha = 1.f - boid->x48_timeToDie;\n      const zeus::CColor color(1.f, alpha > 0.f ? boid->x48_timeToDie : 1.f);\n      const CModelFlags iceFlags(5, 0, 3, color);\n      mData.GetAnimationData()->Render(*iceModel, iceFlags, nullptr, {});\n    }\n  } else if (thermalHot) {\n    constexpr CModelFlags thermFlags(5, 0, 3, zeus::skWhite);\n    CModelData::ThermalDraw(model, positions, normals, zeus::skWhite, zeus::CColor(0.f, 0.25f), thermFlags);\n  } else {\n    model.Draw(positions, normals, flags);\n  }\n}\n\nvoid CWallCrawlerSwarm::Render(CStateManager& mgr) {\n  SCOPED_GRAPHICS_DEBUG_GROUP(fmt::format(\"CWallCrawlerSwarm::Render {} {} {}\", x8_uid, xc_editorId, x10_name).c_str(),\n                              zeus::skOrange);\n  u32 drawMask = 0xffffffff;\n  const bool r24 = x560_24_enableLighting;\n  const bool r23 = x560_25_useSoftwareLight;\n  if (!r24) {\n    CGraphics::DisableAllLights();\n    g_Renderer->SetAmbientColor(zeus::CColor{0.5f, 1.f});\n  }\n\n  const bool thermalHot = mgr.GetThermalDrawFlag() == EThermalDrawFlag::Hot;\n  CModelFlags flags(0, 0, 3, zeus::skWhite);\n  if (mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::XRay) {\n    flags = CModelFlags(5, 0, 3, zeus::CColor(1.f, 0.3f));\n  }\n  CGX::SetChanCtrl(CGX::EChannelId::Channel0, r24 && !r23, GX_SRC_REG, GX_SRC_REG, CGraphics::mLightActive,\n                   CGraphics::mLightActive.any() ? GX_DF_CLAMP : GX_DF_NONE,\n                   CGraphics::mLightActive.any() ? GX_AF_SPOT : GX_AF_NONE);\n\n  for (int r27 = 0; r27 < 5; ++r27) {\n    for (int r28 = 0; r28 < 5; ++r28) {\n      for (int r21 = 0; r21 < 5; ++r21) {\n        const int idx = r21 * 25 + r28 * 5 + r27;\n        if (CBoid* b = x168_partitionedBoidLists[idx]) {\n          if (r24) {\n            const zeus::CAABox aabb = BoxForPosition(r27, r28, r21, 0.f);\n            if (r23) {\n              if ((idx & 0x3) == (x100_thinkCounter & 0x3)) {\n                const zeus::CColor color = SoftwareLight(mgr, aabb);\n                for (CBoid* b2 = b; b2; b2 = b2->x44_next) {\n                  if (b2->GetActive()) {\n                    b2->x40_ambientLighting = zeus::CColor::lerp(b2->x40_ambientLighting, color, 0.3f);\n                  }\n                }\n              }\n            } else {\n              HardwareLight(mgr, aabb);\n            }\n          }\n          for (CBoid* b2 = b; b2; b2 = b2->x44_next) {\n            if (b2->x80_25_inFrustum && b2->GetActive()) {\n              RenderBoid(b2, drawMask, thermalHot, flags);\n            }\n          }\n        }\n      }\n    }\n  }\n\n  CBoid* b = x360_outlierBoidList;\n  for (int i = 1; b; ++i, b = b->x44_next) {\n    if (b->x80_25_inFrustum && b->GetActive()) {\n      if (r24) {\n        const zeus::CAABox aabb(b->GetTranslation() - x374_boidRadius, b->GetTranslation() + x374_boidRadius);\n        if (r23) {\n          if ((i & 0x3) == (x100_thinkCounter & 0x3)) {\n            zeus::CColor color = SoftwareLight(mgr, aabb);\n            if (b->GetActive()) {\n              b->x40_ambientLighting = zeus::CColor::lerp(b->x40_ambientLighting, color, 0.3f);\n            }\n          }\n        } else {\n          HardwareLight(mgr, aabb);\n        }\n      }\n      RenderBoid(b, drawMask, thermalHot, flags);\n    }\n  }\n  CGraphics::DisableAllLights();\n  DrawTouchBounds();\n}\n\nbool CWallCrawlerSwarm::CanRenderUnsorted(const CStateManager&) const { return true; }\n\nvoid CWallCrawlerSwarm::CalculateRenderBounds() { x9c_renderBounds = GetBoundingBox(); }\n\nstd::optional<zeus::CAABox> CWallCrawlerSwarm::GetTouchBounds() const { return {xe8_aabox}; }\n\nvoid CWallCrawlerSwarm::Touch(CActor& other, CStateManager& mgr) {\n  CActor::Touch(other, mgr);\n  if (TCastToConstPtr<CGameProjectile> proj = other) {\n    if (x3c4_dVuln.WeaponHurts(proj->GetDamageInfo().GetWeaponMode(), false)) {\n      if (auto projTb = proj->GetTouchBounds()) {\n        const float f0 = 0.1f + x378_touchRadius;\n        const float f30 = f0 * f0;\n        for (auto& b : x108_boids) {\n          if (b.GetActive()) {\n            const zeus::CAABox aabb(b.GetTranslation() - f30, b.GetTranslation() + f30);\n            if (aabb.intersects(*projTb)) {\n              b.x78_health -= proj->GetDamageInfo().GetDamage(x3c4_dVuln);\n              if (b.x78_health <= 0.f) {\n                KillBoid(b, mgr, 1.f, 0.1f);\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n\n  if (TCastToConstPtr<CPlayer> player = other) {\n    const float radius = zeus::close_enough(x380_playerTouchRadius, 0.f) ? x378_touchRadius : x380_playerTouchRadius;\n    if (auto playerTb = player->GetTouchBounds()) {\n      for (auto& b : x108_boids) {\n        if (b.GetActive() && b.x48_timeToDie <= 0.f) {\n          if (x558_flavor == EFlavor::Scarab && b.x80_27_scarabExplodeTimerEnabled) {\n            const zeus::CAABox aabb(b.GetTranslation() - x37c_scarabBoxMargin,\n                                    b.GetTranslation() + x37c_scarabBoxMargin);\n            if (playerTb->intersects(aabb)) {\n              ExplodeBoid(b, mgr);\n              SetExplodeTimers(b.GetTranslation(), 0.5f, 0.5f, 2.5f);\n            }\n          }\n          const zeus::CAABox aabb(b.GetTranslation() - radius, b.GetTranslation() + radius);\n          if (playerTb->intersects(aabb)) {\n            if (b.GetActive() && x558_flavor == EFlavor::Parasite) {\n              constexpr CDamageInfo dInfo(CWeaponMode(EWeaponType::AI), 2.0e-05f, 0.f, 0.f);\n              mgr.ApplyDamage(GetUniqueId(), player->GetUniqueId(), GetUniqueId(), dInfo,\n                              CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), zeus::skZero3f);\n              KillBoid(b, mgr, 0.f, 1.f);\n            } else if (x558_flavor == EFlavor::Scarab) {\n              ExplodeBoid(b, mgr);\n            } else if (x36c_crabDamageCooldownTimer <= 0.f) {\n              mgr.ApplyDamage(GetUniqueId(), player->GetUniqueId(), GetUniqueId(), x384_crabDamage,\n                              CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), zeus::skZero3f);\n              x36c_crabDamageCooldownTimer = x370_crabDamageCooldown;\n              break;\n            }\n          }\n        }\n      }\n    }\n  }\n}\n\nzeus::CVector3f CWallCrawlerSwarm::GetOrbitPosition(const CStateManager&) const {\n  if (x42c_lockOnIdx == -1) {\n    return x124_lastOrbitPosition;\n  }\n\n  x124_lastOrbitPosition = x108_boids[x42c_lockOnIdx].GetTranslation();\n  return x124_lastOrbitPosition;\n}\n\nzeus::CVector3f CWallCrawlerSwarm::GetAimPosition(const CStateManager&, float dt) const {\n  if (x42c_lockOnIdx == -1) {\n    return x124_lastOrbitPosition;\n  }\n\n  return x108_boids[x42c_lockOnIdx].x30_velocity * dt + x124_lastOrbitPosition;\n}\nvoid CWallCrawlerSwarm::ApplyRadiusDamage(const zeus::CVector3f& pos, const CDamageInfo& info,\n                                          CStateManager& stateMgr) {\n  for (CBoid& boid : x108_boids) {\n    if (boid.GetActive() && (boid.GetTranslation() - pos).magSquared() < info.GetRadius() * info.GetRadius()) {\n      boid.x78_health -= info.GetRadiusDamage();\n      if (boid.x78_health <= 0.f) {\n        KillBoid(boid, stateMgr, 1.f, 0.1f);\n      }\n    }\n  }\n}\n\nvoid CWallCrawlerSwarm::FreezeCollision(CMarkerGrid const& grid) {\n  for (CBoid& boid : x108_boids) {\n    if (!boid.x80_24_active) {\n      continue;\n    }\n    const float rad_sq_1 = x378_touchRadius * x378_touchRadius + 0.3f;\n    const float rad_sq_2 = x378_touchRadius * x378_touchRadius + 0.5f;\n    const zeus::CVector3f half_bounds(rad_sq_1, rad_sq_1, rad_sq_2);\n    zeus::CAABox coll_bounds(boid.x0_xf.origin - half_bounds, boid.x0_xf.origin + half_bounds);\n    if (grid.AABoxTouchesData(coll_bounds, 1)) {\n      boid.x48_timeToDie = 1.f;\n    }\n  }\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CWallCrawlerSwarm.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <string_view>\n#include <vector>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Collision/CCollisionSurface.hpp\"\n#include \"Runtime/Particle/CElementGen.hpp\"\n#include \"Runtime/World/CActor.hpp\"\n#include \"Runtime/World/CDamageInfo.hpp\"\n#include \"Runtime/World/CDamageVulnerability.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CColor.hpp>\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CAreaCollisionCache;\nclass CMarkerGrid;\nclass CWallCrawlerSwarm : public CActor {\npublic:\n  enum class EFlavor { Parasite, Scarab, Crab };\n  class CBoid {\n    friend class CWallCrawlerSwarm;\n    zeus::CTransform x0_xf;\n    zeus::CVector3f x30_velocity;\n    TUniqueId x3c_targetWaypoint = kInvalidUniqueId;\n    zeus::CColor x40_ambientLighting = zeus::CColor(0.3f, 0.3f, 0.3f, 1.f);\n    CBoid* x44_next = nullptr;\n    float x48_timeToDie = 0.f;\n    float x4c_timeToExplode = 0.f;\n    CCollisionSurface x50_surface = CCollisionSurface(zeus::CVector3f(0.f, 0.f, 1.f), zeus::CVector3f(0.f, 1.f, 0.f),\n                                                      zeus::CVector3f(1.f, 0.f, 0.f), 0xffffffff);\n    float x78_health = 0.f;\n    int x7c_framesNotOnSurface : 8 = 0;\n    int x7c_idx : 10;\n    int x7c_remainingLaunchNotOnSurfaceFrames : 8 = 0;\n    bool x80_24_active : 1 = false;\n    bool x80_25_inFrustum : 1 = false;\n    bool x80_26_launched : 1 = false;\n    bool x80_27_scarabExplodeTimerEnabled : 1 = false;\n    bool x80_28_nearPlayer : 1 = false;\n\n  public:\n    CBoid(const zeus::CTransform& xf, int idx) : x0_xf(xf), x7c_idx(idx) {}\n\n    zeus::CTransform& Transform() { return x0_xf; }\n    zeus::CVector3f& Translation() { return x0_xf.origin; }\n    const zeus::CTransform& GetTransform() const { return x0_xf; }\n    const zeus::CVector3f& GetTranslation() const { return x0_xf.origin; }\n    bool GetActive() const { return x80_24_active; }\n  };\n  class CRepulsor {\n    friend class CWallCrawlerSwarm;\n    zeus::CVector3f x0_center;\n    float xc_mag;\n\n  public:\n    CRepulsor(const zeus::CVector3f& center, float mag) : x0_center(center), xc_mag(mag) {}\n  };\n\nprivate:\n  zeus::CAABox xe8_aabox = zeus::skNullBox;\n  s32 x100_thinkCounter = 0;\n  float x104_occludedTimer = 5.f;\n  std::vector<CBoid> x108_boids;\n  zeus::CVector3f x118_boundingBoxExtent;\n  mutable zeus::CVector3f x124_lastOrbitPosition;\n  zeus::CVector3f x130_lastKilledOffset;\n  float x13c_separationRadius;\n  float x140_cohesionMagnitude;\n  float x144_alignmentWeight;\n  float x148_separationMagnitude;\n  float x14c_moveToWaypointWeight;\n  float x150_attractionMagnitude;\n  float x154_attractionRadius;\n  float x158_scarabScatterXYVelocity;\n  float x15c_scarabTimeToExplode;\n  float x160_animPlaybackSpeed;\n  float x164_waypointGoalRadius = 3.f;\n  rstl::reserved_vector<CBoid*, 125> x168_partitionedBoidLists;\n  CBoid* x360_outlierBoidList = nullptr;\n  float x364_boidGenRate;\n  float x368_boidGenCooldownTimer = 0.f;\n  float x36c_crabDamageCooldownTimer = 0.f;\n  float x370_crabDamageCooldown;\n  float x374_boidRadius;\n  float x378_touchRadius;\n  float x37c_scarabBoxMargin;\n  float x380_playerTouchRadius;\n  CDamageInfo x384_crabDamage;\n  CDamageInfo x3a0_scarabExplodeDamage;\n  CHealthInfo x3bc_hInfo;\n  CDamageVulnerability x3c4_dVuln;\n  s32 x42c_lockOnIdx = -1;\n  mutable rstl::reserved_vector<SSkinningWorkspace, 10> x430_workspaces;\n  // Originally:\n  // rstl::reserved_vector<rstl::auto_ptr<float[]>, 10> x430_posWorkspaces;\n  // rstl::reserved_vector<float[], 10> x484_nrmWorkspaces;\n  rstl::reserved_vector<std::shared_ptr<CModelData>, 10> x4b0_modelDatas;\n  CModelData::EWhichModel x4dc_whichModel = CModelData::EWhichModel::Normal;\n  std::vector<CRepulsor> x4e0_doorRepulsors;\n  rstl::reserved_vector<TLockedToken<CGenDescription>, 4> x4f0_particleDescs;\n  rstl::reserved_vector<std::unique_ptr<CElementGen>, 4> x524_particleGens;\n  s32 x548_numBoids;\n  s32 x54c_maxCreatedBoids;\n  u32 x550_createdBoids = 0;\n  s32 x554_maxLaunches;\n  EFlavor x558_flavor;\n  u16 x55c_launchSfx;\n  u16 x55e_scatterSfx;\n  bool x560_24_enableLighting : 1 = true;\n  bool x560_25_useSoftwareLight : 1 = true;\n  bool x560_26_modelAssetDirty : 1 = false;\n\n  void AllocateSkinnedModels(CStateManager& mgr, CModelData::EWhichModel which);\n  void AddDoorRepulsors(CStateManager& mgr);\n  void UpdateParticles(float dt);\n  int SelectLockOnIdx(CStateManager& mgr) const;\n  zeus::CAABox GetBoundingBox() const;\n  TUniqueId GetWaypointForState(EScriptObjectState state, CStateManager& mgr) const;\n  static zeus::CVector3f ProjectVectorToPlane(const zeus::CVector3f& pt, const zeus::CVector3f& plane) {\n    return pt - plane * pt.dot(plane);\n  }\n  static zeus::CVector3f ProjectPointToPlane(const zeus::CVector3f& p0, const zeus::CVector3f& p1,\n                                             const zeus::CVector3f& plane) {\n    return p0 - (p0 - p1).dot(plane) * plane;\n  }\n  bool PointOnSurface(const CCollisionSurface& surf, const zeus::CVector3f& pos, const zeus::CPlane& plane) const;\n  bool FindBestSurface(const CAreaCollisionCache& ccache, const zeus::CVector3f& pos, float radius,\n                       CCollisionSurface& out) const;\n  CCollisionSurface FindBestCollisionInBox(CStateManager& mgr, const zeus::CVector3f& wpPos) const;\n  void CreateBoid(CStateManager& mgr, int idx);\n  void ExplodeBoid(CBoid& boid, CStateManager& mgr);\n  void SetExplodeTimers(const zeus::CVector3f& pos, float radius, float minTime, float maxTime);\n  CBoid* GetListAt(const zeus::CVector3f& pos);\n  void BuildBoidNearList(const CBoid& boid, float radius, rstl::reserved_vector<CBoid*, 50>& nearList);\n  void ApplySeparation(const CBoid& boid, const rstl::reserved_vector<CBoid*, 50>& nearList,\n                       zeus::CVector3f& aheadVec) const;\n  void ApplySeparation(const CBoid& boid, const zeus::CVector3f& separateFrom, float separationRadius,\n                       float separationMagnitude, zeus::CVector3f& aheadVec) const;\n  void ScatterScarabBoid(CBoid& boid, CStateManager& mgr) const;\n  void MoveToWayPoint(CBoid& boid, CStateManager& mgr, zeus::CVector3f& aheadVec) const;\n  void ApplyCohesion(const CBoid& boid, const rstl::reserved_vector<CBoid*, 50>& nearList,\n                     zeus::CVector3f& aheadVec) const;\n  void ApplyCohesion(const CBoid& boid, const zeus::CVector3f& cohesionFrom, float cohesionRadius,\n                     float cohesionMagnitude, zeus::CVector3f& aheadVec) const;\n  void ApplyAlignment(const CBoid& boid, const rstl::reserved_vector<CBoid*, 50>& nearList,\n                      zeus::CVector3f& aheadVec) const;\n  void ApplyAttraction(const CBoid& boid, const zeus::CVector3f& attractTo, float attractionRadius,\n                       float attractionMagnitude, zeus::CVector3f& aheadVec) const;\n  void UpdateBoid(const CAreaCollisionCache& ccache, CStateManager& mgr, float dt, CBoid& boid);\n  void LaunchBoid(CBoid& boid, const zeus::CVector3f& dir);\n  void AddParticle(const zeus::CTransform& xf);\n  void KillBoid(CBoid& boid, CStateManager& mgr, float deathRattleChance, float deadChance);\n  void UpdatePartition();\n  zeus::CVector3f FindClosestCell(const zeus::CVector3f& pos) const;\n  void UpdateEffects(CStateManager& mgr, CAnimData& aData, int vol);\n  zeus::CAABox BoxForPosition(int x, int y, int z, float f) const;\n  void RenderParticles() const;\n  zeus::CColor SoftwareLight(const CStateManager& mgr, const zeus::CAABox& aabb) const;\n  void HardwareLight(const CStateManager& mgr, const zeus::CAABox& aabb) const;\n  void RenderBoid(const CBoid* boid, u32& drawMask, bool thermalHot, const CModelFlags& flags) const;\n\npublic:\n  DEFINE_ENTITY\n  CWallCrawlerSwarm(TUniqueId uid, bool active, std::string_view name, const CEntityInfo& info,\n                    const zeus::CVector3f& boundingBoxExtent, const zeus::CTransform& xf, EFlavor flavor,\n                    const CAnimRes& animRes, s32 launchAnim, s32 attractAnim, CAssetId part1, CAssetId part2,\n                    CAssetId part3, CAssetId part4, const CDamageInfo& crabDamage,\n                    const CDamageInfo& scarabExplodeDamage, float crabDamageCooldown, float boidRadius,\n                    float touchRadius, float playerTouchRadius, u32 numBoids, u32 maxCreatedBoids,\n                    float animPlaybackSpeed, float separationRadius, float cohesionMagnitude, float alignmentWeight,\n                    float separationMagnitude, float moveToWaypointWeight, float attractionMagnitude,\n                    float attractionRadius, float boidGenRate, u32 maxLaunches, float scarabBoxMargin,\n                    float scarabScatterXYVelocity, float scarabTimeToExplode, const CHealthInfo& hInfo,\n                    const CDamageVulnerability& dVuln, s32 launchSfx, s32 scatterSfx, const CActorParameters& aParams);\n\n  void Accept(IVisitor& visitor) override;\n  void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override;\n  void Think(float, CStateManager&) override;\n  void PreRender(CStateManager&, const zeus::CFrustum&) override;\n  void AddToRenderer(const zeus::CFrustum&, CStateManager&) override;\n  void Render(CStateManager&) override;\n  bool CanRenderUnsorted(const CStateManager&) const override;\n  void CalculateRenderBounds() override;\n  std::optional<zeus::CAABox> GetTouchBounds() const override;\n  void Touch(CActor& other, CStateManager&) override;\n  zeus::CVector3f GetOrbitPosition(const CStateManager&) const override;\n  zeus::CVector3f GetAimPosition(const CStateManager&, float) const override;\n  const zeus::CVector3f& GetLastKilledOffset() const { return x130_lastKilledOffset; }\n  void ApplyRadiusDamage(const zeus::CVector3f& pos, const CDamageInfo& info, CStateManager& stateMgr);\n  const std::vector<CBoid>& GetBoids() const { return x108_boids; }\n  int GetCurrentLockOnId() const { return x42c_lockOnIdx; }\n  bool GetLockOnLocationValid(int id) const { return id >= 0 && id < x108_boids.size() && x108_boids[id].GetActive(); }\n  const zeus::CVector3f& GetLockOnLocation(int id) const { return x108_boids[id].GetTranslation(); }\n  void FreezeCollision(const CMarkerGrid& grid);\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CWallWalker.cpp",
    "content": "#include \"Runtime/World/CWallWalker.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Collision/CGameCollision.hpp\"\n#include \"Runtime/Collision/CMetroidAreaCollider.hpp\"\n#include \"Runtime/World/CPatternedInfo.hpp\"\n#include \"Runtime/World/CScriptWaypoint.hpp\"\n\n#include \"TCastTo.hpp\" // Generated file, do not modify include path\n\nnamespace metaforce {\nCWallWalker::CWallWalker(ECharacter chr, TUniqueId uid, std::string_view name, EFlavorType flavType,\n                         const CEntityInfo& eInfo, const zeus::CTransform& xf, CModelData&& mData,\n                         const CPatternedInfo& pInfo, EMovementType mType, EColliderType colType, EBodyType bType,\n                         const CActorParameters& aParms, float collisionCloseMargin, float alignAngVel,\n                         EKnockBackVariant kbVariant, float advanceWpRadius, EWalkerType wType,\n                         float playerObstructionMinDist, bool disableMove)\n: CPatterned(chr, uid, name, flavType, eInfo, xf, std::move(mData), pInfo, mType, colType, bType, aParms, kbVariant)\n, x590_colSphere(zeus::CSphere(zeus::skZero3f, pInfo.GetHalfExtent()), x68_material)\n, x5b0_collisionCloseMargin(collisionCloseMargin)\n, x5b4_alignAngVel(alignAngVel)\n, x5c0_advanceWpRadius(advanceWpRadius)\n, x5c4_playerObstructionMinDist(playerObstructionMinDist)\n, x5cc_bendingHackAnim(\n      GetModelData()->GetAnimationData()->GetCharacterInfo().GetAnimationIndex(\"BendingAnimationHack\"sv))\n, x5d0_walkerType(wType)\n, x5d6_27_disableMove(disableMove) {}\n\nvoid CWallWalker::OrientToSurfaceNormal(const zeus::CVector3f& normal, float clampAngle) {\n  float dot = x34_transform.basis[2].dot(normal);\n  if (zeus::close_enough(dot, 1.f) || dot < -0.999f)\n    return;\n  zeus::CQuaternion q = zeus::CQuaternion::clampedRotateTo(x34_transform.basis[2], normal, zeus::degToRad(clampAngle));\n  q.setImaginary(x34_transform.transposeRotate(q.getImaginary()));\n  SetTransform((zeus::CQuaternion(x34_transform.basis) * q).normalized().toTransform(GetTranslation()));\n}\n\nbool CWallWalker::PointOnSurface(const CCollisionSurface& surf, const zeus::CVector3f& point) {\n  zeus::CVector3f normal = surf.GetNormal();\n  zeus::CVector3f projPt = ProjectPointToPlane(point, surf.GetVert(0), normal);\n  for (int i = 0; i < 3; ++i) {\n    zeus::CVector3f projDelta = projPt - surf.GetVert(i);\n    zeus::CVector3f edge = surf.GetVert((i + 2) % 3) - surf.GetVert(i);\n    if (projDelta.cross(edge).dot(normal) < 0.f)\n      return false;\n  }\n  return true;\n}\n\nvoid CWallWalker::AlignToFloor(CStateManager& mgr, float radius, const zeus::CVector3f& newPos, float dt) {\n  bool hasSurface = false;\n  float margin = radius + x5b0_collisionCloseMargin;\n  zeus::CAABox aabb(newPos - margin, newPos + margin);\n  CAreaCollisionCache ccache(aabb);\n  CGameCollision::BuildAreaCollisionCache(mgr, ccache);\n  if (x5d6_25_hasAlignSurface) {\n    x5d6_25_hasAlignSurface = PointOnSurface(x568_alignNormal, newPos);\n  }\n  if (!x5d6_25_hasAlignSurface || !(x5d4_thinkCounter & 0x3)) {\n    for (const auto& leaf : ccache) {\n      for (const auto& node : leaf) {\n        CAreaOctTree::TriListReference triArr = node.GetTriangleArray();\n        for (u16 i = 0; i < triArr.GetSize(); ++i) {\n          u16 triIdx = triArr.GetAt(i);\n          CCollisionSurface surf = leaf.GetOctTree().GetMasterListTriangle(triIdx);\n          float dist = std::fabs(surf.GetPlane().pointToPlaneDist(newPos));\n          if (dist < margin && PointOnSurface(surf, newPos)) {\n            margin = dist;\n            x568_alignNormal = surf;\n            hasSurface = true;\n          }\n        }\n      }\n    }\n    x5d6_25_hasAlignSurface = hasSurface;\n  }\n  if (x5d6_25_hasAlignSurface) {\n    OrientToSurfaceNormal(x568_alignNormal.GetNormal(), x5b4_alignAngVel * dt);\n    x5b8_tumbleAngle = 0.f;\n    x5d6_28_addBendingWeight = false;\n  } else {\n    float angDelta = zeus::radToDeg(x138_velocity.magnitude()) / x590_colSphere.GetSphere().radius * dt;\n    OrientToSurfaceNormal(x34_transform.basis[1], angDelta);\n    if (x450_bodyController->HasBodyState(pas::EAnimationState::Step)) {\n      x450_bodyController->GetCommandMgr().DeliverCmd(CBCStepCmd(pas::EStepDirection::Down, pas::EStepType::Normal));\n    } else {\n      x5d6_28_addBendingWeight = true;\n    }\n    x5b8_tumbleAngle += angDelta;\n  }\n}\n\nvoid CWallWalker::GotoNextWaypoint(CStateManager& mgr) {\n  if (TCastToPtr<CScriptWaypoint> wp = mgr.ObjectById(x2dc_destObj)) {\n    zeus::CVector3f wpPos = wp->GetTranslation();\n    if ((wpPos - GetTranslation()).magSquared() < x5c0_advanceWpRadius * x5c0_advanceWpRadius) {\n      x2dc_destObj = wp->NextWaypoint(mgr);\n      if (!zeus::close_enough(wp->GetPause(), 0.f)) {\n        x5bc_patrolPauseRemTime = wp->GetPause();\n        if (x5d0_walkerType == EWalkerType::Parasite)\n          x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n      }\n      mgr.SendScriptMsg(wp, GetUniqueId(), EScriptObjectMessage::Arrived);\n    }\n    SetDestPos(wpPos);\n  }\n}\n\nvoid CWallWalker::PreThink(float dt, CStateManager& mgr) {\n  CPatterned::PreThink(dt, mgr);\n  if (!GetActive() || x5d6_26_playerObstructed || x5bc_patrolPauseRemTime > 0.f || x5d6_27_disableMove ||\n      x450_bodyController->IsFrozen() || !x5d6_24_alignToFloor) {\n    return;\n  }\n\n  // In GM8Ev0, the game binary also constructs two unused quaternions here.\n\n  AddMotionState(PredictMotion(dt));\n  ClearForcesAndTorques();\n  if (x5d6_25_hasAlignSurface) {\n    zeus::CPlane plane = x568_alignNormal.GetPlane();\n    const float futureDt = (10.f * dt);\n    SetTranslation(zeus::CVector3f::lerp(\n        GetTranslation(),\n        GetTranslation() -\n            (plane.pointToPlaneDist(GetTranslation()) - x590_colSphere.GetSphere().radius - 0.01f) * plane.normal(),\n        futureDt));\n  }\n  MoveCollisionPrimitive(zeus::skZero3f);\n}\n\nvoid CWallWalker::Think(float dt, CStateManager& mgr) {\n  if (!x450_bodyController->GetActive())\n    x450_bodyController->Activate(mgr);\n  CPatterned::Think(dt, mgr);\n\n  if (x5cc_bendingHackAnim == -1)\n    return;\n\n  if (x5d6_28_addBendingWeight) {\n    if (x5c8_bendingHackWeight < 1.f) {\n      x5c8_bendingHackWeight += (dt * x138_velocity.magnitude()) / 0.6f;\n      if (x5c8_bendingHackWeight >= 1.f)\n        x5c8_bendingHackWeight = 1.f;\n    }\n  } else if (x5c8_bendingHackWeight > 0.f) {\n    x5c8_bendingHackWeight -= (dt * x138_velocity.magnitude()) / 1.5f;\n    if (x5c8_bendingHackWeight < 0.f)\n      x5c8_bendingHackWeight = 0.f;\n  }\n\n  if (x5c8_bendingHackWeight <= 0.f && !x5d6_29_applyBendingHack)\n    return;\n\n  if (x5c8_bendingHackWeight > 0.0001f) {\n    GetModelData()->GetAnimationData()->AddAdditiveAnimation(x5cc_bendingHackAnim, x5c8_bendingHackWeight, true, false);\n    x5d6_29_applyBendingHack = true;\n  } else {\n    GetModelData()->GetAnimationData()->DelAdditiveAnimation(x5cc_bendingHackAnim);\n    x5d6_29_applyBendingHack = false;\n  }\n}\n\nvoid CWallWalker::Render(CStateManager& mgr) { CPatterned::Render(mgr); }\n\nvoid CWallWalker::UpdateWPDestination(CStateManager& mgr) {\n  if (TCastToPtr<CScriptWaypoint> wp = mgr.ObjectById(x2dc_destObj)) {\n    zeus::CVector3f wpPos = wp->GetTranslation();\n    if ((wpPos - GetTranslation()).magSquared() < x5c0_advanceWpRadius * x5c0_advanceWpRadius) {\n      x2dc_destObj = wp->NextWaypoint(mgr);\n      if (std::fabs(wp->GetPause()) > 0.00001f) {\n        x5bc_patrolPauseRemTime = wp->GetPause();\n        if (x5d0_walkerType == EWalkerType::Parasite)\n          x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);\n        mgr.SendScriptMsg(wp, GetUniqueId(), EScriptObjectMessage::Arrived);\n      }\n    }\n\n    SetDestPos(wpPos);\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CWallWalker.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Collision/CCollidableSphere.hpp\"\n#include \"Runtime/Collision/CCollisionSurface.hpp\"\n#include \"Runtime/World/CPatterned.hpp\"\n\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CWallWalker : public CPatterned {\npublic:\n  DEFINE_ENTITY\n  enum class EWalkerType { Parasite = 0, Oculus = 1, Geemer = 2, IceZoomer = 3, Seedling = 4 };\n\nprotected:\n  CCollisionSurface x568_alignNormal{zeus::CVector3f(), zeus::skForward, zeus::skRight, UINT32_MAX};\n  CCollidableSphere x590_colSphere;\n  float x5b0_collisionCloseMargin;\n  float x5b4_alignAngVel;\n  float x5b8_tumbleAngle = 0.f;\n  float x5bc_patrolPauseRemTime = 0.f;\n  float x5c0_advanceWpRadius;\n  float x5c4_playerObstructionMinDist;\n  float x5c8_bendingHackWeight = 0.f;\n  s32 x5cc_bendingHackAnim;\n  EWalkerType x5d0_walkerType;\n  s16 x5d4_thinkCounter = 0;\n  bool x5d6_24_alignToFloor : 1 = false;\n  bool x5d6_25_hasAlignSurface : 1 = false;\n  bool x5d6_26_playerObstructed : 1 = false;\n  bool x5d6_27_disableMove : 1;\n  bool x5d6_28_addBendingWeight : 1 = true;\n  bool x5d6_29_applyBendingHack : 1 = false;\n  static zeus::CVector3f ProjectVectorToPlane(const zeus::CVector3f& pt, const zeus::CVector3f& plane) {\n    return pt - plane * pt.dot(plane);\n  }\n  static zeus::CVector3f ProjectPointToPlane(const zeus::CVector3f& p0, const zeus::CVector3f& p1,\n                                             const zeus::CVector3f& plane) {\n    return p0 - (p0 - p1).dot(plane) * plane;\n  }\n  void OrientToSurfaceNormal(const zeus::CVector3f& normal, float clampAngle);\n  static bool PointOnSurface(const CCollisionSurface& surf, const zeus::CVector3f& point);\n  void AlignToFloor(CStateManager&, float, const zeus::CVector3f&, float);\n  void GotoNextWaypoint(CStateManager& mgr);\n\npublic:\n  CWallWalker(ECharacter chr, TUniqueId uid, std::string_view name, EFlavorType flavType, const CEntityInfo& eInfo,\n              const zeus::CTransform& xf, CModelData&& mData, const CPatternedInfo& pInfo, EMovementType mType,\n              EColliderType colType, EBodyType bType, const CActorParameters& aParms, float collisionCloseMargin,\n              float alignAngVel, EKnockBackVariant kbVariant, float advanceWpRadius, EWalkerType wType,\n              float playerObstructionMinDist, bool disableMove);\n\n  void PreThink(float, CStateManager&) override;\n  void Think(float, CStateManager&) override;\n  void Render(CStateManager&) override;\n  const CCollisionPrimitive* GetCollisionPrimitive() const override { return &x590_colSphere; }\n  void UpdateWPDestination(CStateManager&);\n};\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CWorld.cpp",
    "content": "#include \"Runtime/World/CWorld.hpp\"\n\n#include <algorithm>\n#include <iterator>\n\n#include \"Runtime/CGameState.hpp\"\n#include \"Runtime/CInGameTweakManagerBase.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/IMain.hpp\"\n#include \"Runtime/Audio/CAudioGroupSet.hpp\"\n#include \"Runtime/Audio/CStreamAudioManager.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/World/CScriptAreaAttributes.hpp\"\n#include \"Runtime/World/CScriptRoomAcoustics.hpp\"\n\nnamespace metaforce {\n\nCWorld::CSoundGroupData::CSoundGroupData(int grpId, CAssetId agsc) : x0_groupId(grpId), x4_agscId(agsc) {\n  x1c_groupData = g_SimplePool->GetObj(SObjectTag{FOURCC('AGSC'), agsc});\n}\n\nCDummyWorld::CDummyWorld(CAssetId mlvlId, bool loadMap) : x4_loadMap(loadMap), xc_mlvlId(mlvlId) {\n  SObjectTag tag{FOURCC('MLVL'), mlvlId};\n  x38_bufSz = g_ResFactory->ResourceSize(tag);\n  x34_loadBuf.reset(new u8[x38_bufSz]);\n  x30_loadToken = g_ResFactory->LoadResourceAsync(tag, x34_loadBuf.get());\n}\n\nCDummyWorld::~CDummyWorld() {\n  if (x30_loadToken)\n    x30_loadToken->PostCancelRequest();\n}\n\nCAssetId CDummyWorld::IGetWorldAssetId() const { return xc_mlvlId; }\n\nCAssetId CDummyWorld::IGetStringTableAssetId() const { return x10_strgId; }\n\nCAssetId CDummyWorld::IGetSaveWorldAssetId() const { return x14_savwId; }\n\nconst CMapWorld* CDummyWorld::IGetMapWorld() const { return x2c_mapWorld.GetObj(); }\n\nCMapWorld* CDummyWorld::IGetMapWorld() { return x2c_mapWorld.GetObj(); }\n\nconst IGameArea* CDummyWorld::IGetAreaAlways(TAreaId id) const { return &x18_areas.at(id); }\n\nTAreaId CDummyWorld::IGetCurrentAreaId() const { return x3c_curAreaId; }\n\nTAreaId CDummyWorld::IGetAreaId(CAssetId id) const {\n  if (!id.IsValid()) {\n    return kInvalidAreaId;\n  }\n\n  const auto iter =\n      std::find_if(x18_areas.cbegin(), x18_areas.cend(), [id](const auto& area) { return area.xc_mrea == id; });\n  if (iter == x18_areas.cend()) {\n    return kInvalidAreaId;\n  }\n\n  return TAreaId(std::distance(x18_areas.cbegin(), iter));\n}\n\nCWorld::CRelay::CRelay(CInputStream& in) {\n  x0_relay = in.ReadLong();\n  x4_target = in.ReadLong();\n  x8_msg = in.ReadShort();\n  xa_active = in.ReadBool();\n}\n\nstd::vector<CWorld::CRelay> CWorld::CRelay::ReadMemoryRelays(CInputStream& r) {\n  std::vector<CWorld::CRelay> ret;\n  u32 count = r.ReadLong();\n  ret.reserve(count);\n  for (u32 i = 0; i < count; ++i)\n    ret.emplace_back(r);\n  return ret;\n}\n\nstd::optional<CWorldLayers> CWorldLayers::ReadWorldLayers(CInputStream& r, int version, CAssetId mlvlId) {\n  if (version <= 14) {\n    return std::nullopt;\n  }\n\n  CWorldLayers ret;\n\n  u32 areaCount = r.ReadLong();\n  ret.m_areas.reserve(areaCount);\n  for (u32 i = 0; i < areaCount; ++i) {\n    auto& area = ret.m_areas.emplace_back();\n    area.m_layerCount = r.ReadLong();\n    area.m_layerBits = r.ReadLongLong();\n  }\n\n  const u32 nameCount = r.ReadLong();\n  ret.m_names.reserve(nameCount);\n  for (u32 i = 0; i < nameCount; ++i) {\n    auto name = r.Get<std::string>();\n    ret.m_names.emplace_back(name);\n  }\n\n  areaCount = r.ReadLong();\n  for (u32 i = 0; i < areaCount; ++i) {\n    ret.m_areas[i].m_startNameIdx = r.ReadLong();\n  }\n\n  CWorldState& wldState = g_GameState->StateForWorld(mlvlId);\n  wldState.GetLayerState()->InitializeWorldLayers(ret.m_areas);\n\n  return ret;\n}\n\nbool CDummyWorld::ICheckWorldComplete() {\n  switch (x8_phase) {\n  case Phase::Loading: {\n    if (!x30_loadToken->IsComplete())\n      return false;\n    CMemoryInStream r(x34_loadBuf.get(), x38_bufSz, CMemoryInStream::EOwnerShip::NotOwned);\n    r.ReadLong();\n    int version = r.ReadLong();\n    x10_strgId = r.Get<CAssetId>();\n\n    if (version >= 15)\n      x14_savwId = r.Get<CAssetId>();\n    if (version >= 12)\n      r.ReadLong();\n    if (version >= 17)\n      CWorld::CRelay::ReadMemoryRelays(r);\n\n    u32 areaCount = r.ReadLong();\n    r.ReadLong();\n\n    x18_areas.reserve(areaCount);\n    for (u32 i = 0; i < areaCount; ++i)\n      x18_areas.emplace_back(r, i, version);\n\n    x28_mapWorldId = r.Get<CAssetId>();\n    if (x4_loadMap)\n      x2c_mapWorld = g_SimplePool->GetObj(SObjectTag{FOURCC('MAPW'), x28_mapWorldId});\n\n    r.ReadInt8();\n    r.ReadLong();\n\n    if (version > 10) {\n      u32 audioGroupCount = r.ReadLong();\n      for (u32 i = 0; i < audioGroupCount; ++i) {\n        r.ReadLong();\n        r.ReadLong();\n      }\n    }\n\n    if (version > 12)\n      r.Get<std::string>();\n\n    m_worldLayers = CWorldLayers::ReadWorldLayers(r, version, xc_mlvlId);\n\n    x30_loadToken.reset();\n    x34_loadBuf.reset();\n    x38_bufSz = 0;\n\n    if (x4_loadMap)\n      x8_phase = Phase::LoadingMap;\n    else {\n      x8_phase = Phase::Done;\n      return false;\n    }\n    [[fallthrough]];\n  }\n  case Phase::LoadingMap: {\n    if (!x2c_mapWorld.IsLoaded() || !x2c_mapWorld.GetObj())\n      return false;\n\n    x2c_mapWorld->SetWhichMapAreasLoaded(*this, 0, 9999);\n    x8_phase = Phase::LoadingMapAreas;\n    [[fallthrough]];\n  }\n  case Phase::LoadingMapAreas: {\n    if (x2c_mapWorld->IsMapAreasStreaming())\n      return false;\n\n    x8_phase = Phase::Done;\n    [[fallthrough]];\n  }\n  case Phase::Done:\n    return true;\n  default:\n    return false;\n  }\n}\n\nstd::string CDummyWorld::IGetDefaultAudioTrack() const { return {}; }\n\nint CDummyWorld::IGetAreaCount() const { return x18_areas.size(); }\n\nconst std::optional<CWorldLayers>& CDummyWorld::GetWorldLayers() const { return m_worldLayers; }\n\nCWorld::CWorld(IObjectStore& objStore, IFactory& resFactory, CAssetId mlvlId)\n: x8_mlvlId(mlvlId), x60_objectStore(objStore), x64_resFactory(resFactory) {\n  SObjectTag tag{FOURCC('MLVL'), mlvlId};\n  x44_bufSz = resFactory.ResourceSize(tag);\n  x40_loadBuf.reset(new u8[x44_bufSz]);\n  x3c_loadToken = resFactory.LoadResourceAsync(tag, x40_loadBuf.get());\n}\n\nCWorld::~CWorld() {\n  StopSounds();\n  if (g_GameState->GetWorldTransitionManager()->IsTransitionEnabled() &&\n      g_Main->GetFlowState() == EClientFlowStates::None)\n    CStreamAudioManager::StopOneShot();\n  else\n    CStreamAudioManager::StopAll();\n  UnloadSoundGroups();\n  CScriptRoomAcoustics::DisableAuxCallbacks();\n}\n\nCAssetId CWorld::IGetWorldAssetId() const { return GetWorldAssetId(); }\n\nCAssetId CWorld::IGetStringTableAssetId() const { return xc_strgId; }\n\nCAssetId CWorld::IGetSaveWorldAssetId() const { return x10_savwId; }\n\nconst CMapWorld* CWorld::IGetMapWorld() const { return const_cast<CWorld*>(this)->GetMapWorld(); }\n\nCMapWorld* CWorld::IGetMapWorld() { return const_cast<CMapWorld*>(GetMapWorld()); }\n\nconst CGameArea* CWorld::GetAreaAlways(TAreaId id) const { return x18_areas.at(id).get(); }\n\nCGameArea* CWorld::GetArea(TAreaId id) { return const_cast<CGameArea*>(GetAreaAlways(id)); }\n\nconst IGameArea* CWorld::IGetAreaAlways(TAreaId id) const { return GetAreaAlways(id); }\n\nTAreaId CWorld::IGetCurrentAreaId() const { return x68_curAreaId; }\n\nTAreaId CWorld::IGetAreaId(CAssetId id) const {\n  if (!id.IsValid()) {\n    return kInvalidAreaId;\n  }\n\n  const auto iter =\n      std::find_if(x18_areas.cbegin(), x18_areas.cend(), [id](const auto& area) { return area->x84_mrea == id; });\n  if (iter == x18_areas.cend()) {\n    return kInvalidAreaId;\n  }\n\n  return TAreaId(std::distance(x18_areas.cbegin(), iter));\n}\n\nvoid CWorld::MoveToChain(CGameArea* area, EChain chain) {\n  if (area->x138_curChain == chain) {\n    return;\n  }\n\n  if (area->x138_curChain != EChain::Invalid) {\n    if (x4c_chainHeads[size_t(area->x138_curChain)] == area) {\n      x4c_chainHeads[size_t(area->x138_curChain)] = area->x130_next;\n    }\n  }\n\n  area->SetChain(x4c_chainHeads[size_t(chain)], chain);\n  x4c_chainHeads[size_t(chain)] = area;\n}\n\nvoid CWorld::MoveAreaToAliveChain(TAreaId aid) { MoveToChain(x18_areas[aid].get(), EChain::Alive); }\n\nvoid CWorld::LoadSoundGroup(int groupId, CAssetId agscId, CSoundGroupData& data) {\n  if (!CAudioSys::SysLoadGroupSet(g_SimplePool, agscId)) {\n    auto name = CAudioSys::SysGetGroupSetName(agscId);\n    CAudioSys::SysAddGroupIntoAmuse(name);\n    data.xc_name = name;\n    ++x6c_loadedAudioGrpCount;\n  }\n}\n\nvoid CWorld::LoadSoundGroups() {}\n\nvoid CWorld::UnloadSoundGroups() {\n  for (CSoundGroupData& data : x74_soundGroupData) {\n    CAudioSys::SysRemoveGroupFromAmuse(data.xc_name);\n    CAudioSys::SysUnloadAudioGroupSet(data.xc_name);\n  }\n}\n\nvoid CWorld::StopSounds() {\n  for (CSfxHandle& hnd : xc8_globalSfxHandles)\n    CSfxManager::RemoveEmitter(hnd);\n  xc8_globalSfxHandles.clear();\n}\n\nbool CWorld::CheckWorldComplete(CStateManager* mgr, TAreaId id, CAssetId mreaId) {\n  if (mreaId.IsValid()) {\n    x68_curAreaId = 0;\n    TAreaId aid = 0;\n    for (const std::unique_ptr<CGameArea>& area : x18_areas) {\n      if (area->x84_mrea == mreaId) {\n        x68_curAreaId = aid;\n        break;\n      }\n      ++aid;\n    }\n  } else\n    x68_curAreaId = id;\n\n  switch (x4_phase) {\n  case Phase::Loading: {\n    if (!x3c_loadToken->IsComplete())\n      return false;\n    CMemoryInStream r(x40_loadBuf.get(), x44_bufSz, CMemoryInStream::EOwnerShip::NotOwned);\n    r.ReadLong();\n    int version = r.ReadLong();\n    xc_strgId = r.Get<CAssetId>();\n\n    if (version >= 15)\n      x10_savwId = r.Get<CAssetId>();\n    if (version >= 12) {\n      CAssetId skyboxId = r.Get<CAssetId>();\n      if (skyboxId.IsValid() && mgr)\n        x94_skyboxWorld = g_SimplePool->GetObj(SObjectTag{FOURCC('CMDL'), skyboxId});\n    }\n    if (version >= 17)\n      x2c_relays = CWorld::CRelay::ReadMemoryRelays(r);\n\n    u32 areaCount = r.ReadLong();\n    r.ReadLong();\n\n    x18_areas.reserve(areaCount);\n    for (u32 i = 0; i < areaCount; ++i) {\n      x18_areas.push_back(std::make_unique<CGameArea>(r, i, version));\n    }\n\n    if (x48_chainCount < x4c_chainHeads.size()) {\n      for (size_t i = x48_chainCount; i < x4c_chainHeads.size(); ++i) {\n        x4c_chainHeads[i] = nullptr;\n      }\n      x48_chainCount = x4c_chainHeads.size();\n    }\n\n    for (std::unique_ptr<CGameArea>& area : x18_areas)\n      MoveToChain(area.get(), EChain::Deallocated);\n\n    x24_mapwId = r.Get<CAssetId>();\n    x28_mapWorld = g_SimplePool->GetObj(SObjectTag{FOURCC('MAPW'), x24_mapwId});\n\n    if (mgr) {\n      std::vector<TEditorId> ids;\n      mgr->LoadScriptObjects(kInvalidAreaId, r, ids);\n      mgr->InitScriptObjects(ids);\n    }\n\n    if (version > 10) {\n      u32 audioGroupCount = r.ReadLong();\n      x74_soundGroupData.reserve(audioGroupCount);\n      for (u32 i = 0; i < audioGroupCount; ++i) {\n        int grpId = r.ReadLong();\n        CAssetId agscId = r.Get<CAssetId>();\n        x74_soundGroupData.emplace_back(grpId, agscId);\n      }\n    }\n\n    if (version > 12) {\n      x84_defAudioTrack = r.Get<std::string>();\n      std::string trackKey = fmt::format(\"WorldDefault: {}\", x8_mlvlId);\n      if (g_TweakManager->HasTweakValue(trackKey))\n        x84_defAudioTrack = g_TweakManager->GetTweakValue(trackKey)->GetAudio().GetFileName();\n    }\n\n    m_worldLayers = CWorldLayers::ReadWorldLayers(r, version, x8_mlvlId);\n\n    x3c_loadToken.reset();\n    x40_loadBuf.reset();\n    x44_bufSz = 0;\n    x4_phase = Phase::LoadingMap;\n    [[fallthrough]];\n  }\n  case Phase::LoadingMap: {\n    if (!x28_mapWorld.IsLoaded() || !x28_mapWorld.GetObj())\n      return false;\n\n    if (x68_curAreaId == kInvalidAreaId)\n      x28_mapWorld->SetWhichMapAreasLoaded(*this, 0, 9999);\n    else\n      x28_mapWorld->SetWhichMapAreasLoaded(*this, x68_curAreaId, 3);\n\n    x4_phase = Phase::LoadingMapAreas;\n    [[fallthrough]];\n  }\n  case Phase::LoadingMapAreas: {\n    if (x28_mapWorld->IsMapAreasStreaming())\n      return false;\n\n    x4_phase = Phase::LoadingSkyBox;\n    [[fallthrough]];\n  }\n  case Phase::LoadingSkyBox: {\n    x70_26_skyboxActive = true;\n    x70_27_skyboxVisible = false;\n\n    if (x94_skyboxWorld) {\n      CModel* skybox = x94_skyboxWorld->GetObj();\n      if (!skybox)\n        return false;\n\n      skybox->Touch(0);\n      if (!skybox->IsLoaded(0))\n        return false;\n\n      xa4_skyboxWorldLoaded = x94_skyboxWorld;\n    }\n\n    for (CSoundGroupData& group : x74_soundGroupData)\n      group.x1c_groupData.Lock();\n\n    x4_phase = Phase::LoadingSoundGroups;\n    [[fallthrough]];\n  }\n  case Phase::LoadingSoundGroups: {\n    bool allLoaded = true;\n    for (CSoundGroupData& group : x74_soundGroupData) {\n      bool loaded = group.x1c_groupData.IsLoaded();\n      allLoaded &= loaded;\n      if (loaded) {\n        CAudioGroupSet* groupData = group.x1c_groupData.GetObj();\n        if (groupData)\n          LoadSoundGroup(group.x0_groupId, group.x4_agscId, group);\n      }\n    }\n    if (!allLoaded)\n      return false;\n\n    LoadSoundGroups();\n    x4_phase = Phase::Done;\n    [[fallthrough]];\n  }\n  case Phase::Done:\n    return true;\n  default:\n    break;\n  }\n\n  return false;\n}\n\nbool CWorld::ScheduleAreaToLoad(CGameArea* area, CStateManager& mgr) {\n  if (!area->IsPostConstructed()) {\n    MoveToChain(area, EChain::Loading);\n    return true;\n  } else {\n    if (area->x138_curChain != EChain::Alive) {\n      if (area->x138_curChain != EChain::AliveJudgement) {\n        x70_24_currentAreaNeedsAllocation = true;\n      }\n      MoveToChain(area, EChain::Alive);\n    }\n    return false;\n  }\n}\n\nvoid CWorld::TravelToArea(TAreaId aid, CStateManager& mgr, bool skipLoadOther) {\n  if (aid < 0 || aid >= x18_areas.size())\n    return;\n  x70_24_currentAreaNeedsAllocation = false;\n  x68_curAreaId = aid;\n  CGameArea* toDeallocateAreas = x4c_chainHeads[0];\n  while (toDeallocateAreas) {\n    if (toDeallocateAreas->Invalidate(&mgr)) {\n      MoveToChain(toDeallocateAreas, EChain::Deallocated);\n      break;\n    }\n    toDeallocateAreas = toDeallocateAreas->x130_next;\n  }\n\n  CGameArea* aliveAreas = x4c_chainHeads[3];\n  while (aliveAreas) {\n    CGameArea* aliveArea = aliveAreas;\n    aliveAreas = aliveAreas->x130_next;\n    MoveToChain(aliveArea, EChain::AliveJudgement);\n  }\n  CGameArea* loadingAreas = x4c_chainHeads[2];\n  while (loadingAreas) {\n    CGameArea* loadingArea = loadingAreas;\n    loadingAreas = loadingAreas->x130_next;\n    MoveToChain(loadingArea, EChain::ToDeallocate);\n  }\n\n  CGameArea* area = x18_areas[aid].get();\n  if (area->x138_curChain != EChain::AliveJudgement)\n    x70_24_currentAreaNeedsAllocation = true;\n  area->Validate(mgr);\n  MoveToChain(area, EChain::Alive);\n  area->SetOcclusionState(CGameArea::EOcclusionState::Visible);\n\n  CGameArea* otherLoadArea = nullptr;\n  if (!skipLoadOther) {\n    bool otherLoading = false;\n    for (CGameArea::Dock& dock : area->xcc_docks) {\n      u32 dockRefCount = dock.GetDockRefs().size();\n      for (u32 i = 0; i < dockRefCount; ++i) {\n        if (!dock.ShouldLoadOtherArea(i))\n          continue;\n        TAreaId connArea = dock.GetConnectedAreaId(i);\n        CGameArea* cArea = x18_areas[connArea].get();\n        if (!cArea->xf0_25_active)\n          continue;\n        if (!otherLoading) {\n          otherLoading = ScheduleAreaToLoad(cArea, mgr);\n          if (!otherLoading)\n            continue;\n          otherLoadArea = cArea;\n        } else\n          ScheduleAreaToLoad(cArea, mgr);\n      }\n    }\n  }\n  CGameArea* judgementAreas = x4c_chainHeads[4];\n  while (judgementAreas) {\n    CGameArea* judgementArea = judgementAreas;\n    judgementAreas = judgementArea->x130_next;\n    MoveToChain(judgementArea, EChain::ToDeallocate);\n  }\n\n  size_t toStreamCount = 0;\n  toDeallocateAreas = x4c_chainHeads[0];\n  while (toDeallocateAreas) {\n    toDeallocateAreas->RemoveStaticGeometry();\n    toDeallocateAreas = toDeallocateAreas->x130_next;\n    ++toStreamCount;\n  }\n\n  if (!toStreamCount && otherLoadArea && !x70_25_loadPaused)\n    otherLoadArea->StartStreamIn(mgr);\n\n  x28_mapWorld->SetWhichMapAreasLoaded(*this, aid, 3);\n}\n\nvoid CWorld::SetLoadPauseState(bool paused) {\n  for (auto it = GetChainHead(EChain::Loading); it != AliveAreasEnd(); ++it)\n    it->SetLoadPauseState(paused);\n  x70_25_loadPaused = paused;\n}\n\nvoid CWorld::CycleLoadPauseState() {\n  if (!x70_25_loadPaused) {\n    SetLoadPauseState(true);\n    SetLoadPauseState(false);\n  }\n}\n\nbool CWorld::ICheckWorldComplete() { return CheckWorldComplete(nullptr, kInvalidAreaId, {}); }\n\nstd::string CWorld::IGetDefaultAudioTrack() const { return x84_defAudioTrack; }\n\nint CWorld::IGetAreaCount() const { return x18_areas.size(); }\n\nbool CWorld::DoesAreaExist(TAreaId area) const { return (area >= 0 && area < x18_areas.size()); }\n\nvoid CWorld::PropogateAreaChain(CGameArea::EOcclusionState occlusionState, CGameArea* area, CWorld* world) {\n  if (!area->GetPostConstructed() || occlusionState == area->GetOcclusionState())\n    return;\n\n  if (occlusionState == CGameArea::EOcclusionState::Visible)\n    area->SetOcclusionState(CGameArea::EOcclusionState::Visible);\n\n  for (CGameArea& areaItr : *world) {\n    if (&areaItr == area)\n      continue;\n    if (areaItr.IsPostConstructed() && areaItr.GetOcclusionState() == CGameArea::EOcclusionState::Visible)\n      areaItr.OtherAreaOcclusionChanged();\n  }\n\n  for (CGameArea& areaItr : *world) {\n    if (&areaItr == area)\n      continue;\n    if (areaItr.IsPostConstructed() && areaItr.GetOcclusionState() == CGameArea::EOcclusionState::Occluded)\n      areaItr.OtherAreaOcclusionChanged();\n  }\n\n  if (occlusionState == CGameArea::EOcclusionState::Occluded)\n    area->SetOcclusionState(CGameArea::EOcclusionState::Occluded);\n}\n\nvoid CWorld::Update(float dt) {\n  xc4_neededFx = EEnvFxType::None;\n  CAssetId overrideSkyId;\n  bool needsSky = false;\n  bool skyVisible = false;\n\n  u32 areaCount = 0;\n\n  for (auto head = GetChainHead(EChain::Alive); head != AliveAreasEnd(); ++head, ++areaCount) {\n    head->AliveUpdate(dt);\n\n    if (head->DoesAreaNeedSkyNow()) {\n      const CScriptAreaAttributes* attrs = head->GetPostConstructed()->x10d8_areaAttributes;\n\n      if (attrs && attrs->GetSkyModel().IsValid())\n        overrideSkyId = attrs->GetSkyModel();\n\n      needsSky = true;\n      CGameArea::EOcclusionState occlusionState =\n          (head->IsPostConstructed() ? head->GetPostConstructed()->x10dc_occlusionState\n                                     : CGameArea::EOcclusionState::Occluded);\n      if (occlusionState == CGameArea::EOcclusionState::Visible)\n        skyVisible = true;\n    }\n\n    EEnvFxType envFxType = head->DoesAreaNeedEnvFx();\n    if (envFxType != EEnvFxType::None)\n      xc4_neededFx = envFxType;\n  }\n\n  if (areaCount == 0)\n    return;\n\n  if (overrideSkyId.IsValid() && needsSky) {\n    x70_26_skyboxActive = true;\n    x70_27_skyboxVisible = skyVisible;\n    xb4_skyboxOverride = g_SimplePool->GetObj({SBIG('CMDL'), overrideSkyId});\n    xa4_skyboxWorldLoaded.reset();\n    if (x94_skyboxWorld)\n      x94_skyboxWorld->Unlock();\n  } else {\n    xb4_skyboxOverride.reset();\n    if (!x94_skyboxWorld) {\n      x70_26_skyboxActive = false;\n      x70_27_skyboxVisible = false;\n    } else if (!needsSky) {\n      xa4_skyboxWorldLoaded.reset();\n      x94_skyboxWorld->Unlock();\n      x70_26_skyboxActive = false;\n      x70_27_skyboxVisible = false;\n    } else {\n      if (!xa4_skyboxWorldLoaded) {\n        x94_skyboxWorld->Lock();\n        if (x94_skyboxWorld->IsLoaded()) {\n          x94_skyboxWorld.value()->Touch(0);\n          if (x94_skyboxWorld.value()->IsLoaded(0))\n            xa4_skyboxWorldLoaded = x94_skyboxWorld;\n        }\n      }\n      x70_26_skyboxActive = true;\n      x70_27_skyboxVisible = skyVisible;\n    }\n  }\n}\n\nvoid CWorld::PreRender() {\n  for (auto head = GetChainHead(EChain::Alive); head != AliveAreasEnd(); ++head) {\n    head->PreRender();\n  }\n}\n\nvoid CWorld::TouchSky() {\n  if (xa4_skyboxWorldLoaded && xa4_skyboxWorldLoaded->IsLoaded())\n    xa4_skyboxWorldLoaded.value()->Touch(0);\n  if (xb4_skyboxOverride && xb4_skyboxOverride->IsLoaded())\n    xb4_skyboxOverride.value()->Touch(0);\n}\n\nvoid CWorld::DrawSky(const zeus::CTransform& xf) {\n  CModel* model;\n  if (xa4_skyboxWorldLoaded)\n    model = xa4_skyboxWorldLoaded->GetObj();\n  else if (xb4_skyboxOverride)\n    model = xb4_skyboxOverride->GetObj();\n  else\n    return;\n\n  if (!x70_27_skyboxVisible)\n    return;\n\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CWorld::DrawSky\", zeus::skCyan);\n\n  CGraphics::DisableAllLights();\n  CGraphics::SetModelMatrix(xf);\n  g_Renderer->SetAmbientColor(zeus::skWhite);\n  CGraphics::SetDepthRange(DEPTH_SKY, DEPTH_FAR);\n\n  CModelFlags flags(0, 0, 1, zeus::skWhite);\n  model->Draw(flags);\n\n  CGraphics::SetDepthRange(DEPTH_WORLD, DEPTH_FAR);\n}\n\nvoid CWorld::StopGlobalSound(u16 id) {\n  auto search = std::find_if(xc8_globalSfxHandles.begin(), xc8_globalSfxHandles.end(),\n                             [id](CSfxHandle& hnd) { return hnd->GetSfxId() == id; });\n  if (search != xc8_globalSfxHandles.end()) {\n    CSfxManager::RemoveEmitter(*search);\n    xc8_globalSfxHandles.erase(search);\n  }\n}\n\nbool CWorld::HasGlobalSound(u16 id) const {\n  auto search = std::find_if(xc8_globalSfxHandles.begin(), xc8_globalSfxHandles.end(),\n                             [id](const CSfxHandle& hnd) { return hnd->GetSfxId() == id; });\n  return search != xc8_globalSfxHandles.end();\n}\n\nvoid CWorld::AddGlobalSound(const CSfxHandle& hnd) {\n  if (xc8_globalSfxHandles.size() >= xc8_globalSfxHandles.capacity())\n    return;\n  xc8_globalSfxHandles.push_back(hnd);\n}\n\nbool CWorld::AreSkyNeedsMet() {\n  if (!x70_26_skyboxActive)\n    return true;\n  if (xb4_skyboxOverride && xb4_skyboxOverride.value()->IsLoaded(0))\n    return true;\n  if (xa4_skyboxWorldLoaded && xa4_skyboxWorldLoaded.value()->IsLoaded(0))\n    return true;\n  return x94_skyboxWorld && x94_skyboxWorld.value()->IsLoaded(0);\n}\n\nTAreaId CWorld::GetAreaIdForSaveId(s32 saveId) const {\n  if (saveId == -1) {\n    return kInvalidAreaId;\n  }\n\n  if (x18_areas.empty()) {\n    return kInvalidAreaId;\n  }\n\n  const auto iter = std::find_if(x18_areas.cbegin(), x18_areas.cend(),\n                                 [saveId](const auto& area) { return area->x88_areaId == saveId; });\n  if (iter == x18_areas.cend()) {\n    return kInvalidAreaId;\n  }\n\n  return TAreaId(std::distance(x18_areas.cbegin(), iter));\n}\n\nconst std::optional<CWorldLayers>& CWorld::GetWorldLayers() const { return m_worldLayers; }\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CWorld.hpp",
    "content": "#pragma once\n\n#include <array>\n#include <memory>\n#include <string>\n#include <vector>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n#include \"Runtime/Audio/CAudioGroupSet.hpp\"\n#include \"Runtime/Audio/CSfxManager.hpp\"\n#include \"Runtime/AutoMapper/CMapWorld.hpp\"\n#include \"Runtime/Graphics/CModel.hpp\"\n#include \"Runtime/World/CEnvFxManager.hpp\"\n#include \"Runtime/World/CGameArea.hpp\"\n#include \"Runtime/World/ScriptObjectSupport.hpp\"\n\nnamespace metaforce {\nclass CGameArea;\nclass CResFactory;\nclass IGameArea;\nclass IObjectStore;\n\nstruct CWorldLayers;\n\nclass IWorld {\npublic:\n  virtual ~IWorld() = default;\n  virtual CAssetId IGetWorldAssetId() const = 0;\n  virtual CAssetId IGetStringTableAssetId() const = 0;\n  virtual CAssetId IGetSaveWorldAssetId() const = 0;\n  virtual const CMapWorld* IGetMapWorld() const = 0;\n  virtual CMapWorld* IGetMapWorld() = 0;\n  virtual const IGameArea* IGetAreaAlways(TAreaId id) const = 0;\n  virtual TAreaId IGetCurrentAreaId() const = 0;\n  virtual TAreaId IGetAreaId(CAssetId id) const = 0;\n  virtual bool ICheckWorldComplete() = 0;\n  virtual std::string IGetDefaultAudioTrack() const = 0;\n  virtual int IGetAreaCount() const = 0;\n  // Metaforce addition\n  virtual const std::optional<CWorldLayers>& GetWorldLayers() const = 0;\n};\n\nstruct CWorldLayers {\n  struct Area {\n    u32 m_startNameIdx;\n    u32 m_layerCount;\n    u64 m_layerBits;\n  };\n  std::vector<Area> m_areas;\n  std::vector<std::string> m_names;\n  static std::optional<CWorldLayers> ReadWorldLayers(CInputStream& r, int version, CAssetId mlvlId);\n};\n\nclass CDummyWorld : public IWorld {\n  enum class Phase {\n    Loading,\n    LoadingMap,\n    LoadingMapAreas,\n    Done,\n  };\n\n  bool x4_loadMap;\n  Phase x8_phase = Phase::Loading;\n  CAssetId xc_mlvlId;\n  CAssetId x10_strgId;\n  CAssetId x14_savwId;\n  std::vector<CDummyGameArea> x18_areas;\n  CAssetId x28_mapWorldId;\n  TLockedToken<CMapWorld> x2c_mapWorld;\n  std::shared_ptr<IDvdRequest> x30_loadToken;\n  std::unique_ptr<uint8_t[]> x34_loadBuf;\n  u32 x38_bufSz;\n  TAreaId x3c_curAreaId = kInvalidAreaId;\n\n  // Metaforce addition\n  std::optional<CWorldLayers> m_worldLayers;\n\npublic:\n  CDummyWorld(CAssetId mlvlId, bool loadMap);\n  ~CDummyWorld() override;\n  CAssetId IGetWorldAssetId() const override;\n  CAssetId IGetStringTableAssetId() const override;\n  CAssetId IGetSaveWorldAssetId() const override;\n  const CMapWorld* IGetMapWorld() const override;\n  CMapWorld* IGetMapWorld() override;\n  const IGameArea* IGetAreaAlways(TAreaId id) const override;\n  TAreaId IGetCurrentAreaId() const override;\n  TAreaId IGetAreaId(CAssetId id) const override;\n  bool ICheckWorldComplete() override;\n  std::string IGetDefaultAudioTrack() const override;\n  int IGetAreaCount() const override;\n  const std::optional<CWorldLayers>& GetWorldLayers() const override;\n};\n\nclass CWorld : public IWorld {\n  friend class CStateManager;\n\npublic:\n  class CRelay {\n    TEditorId x0_relay = kInvalidEditorId;\n    TEditorId x4_target = kInvalidEditorId;\n    s16 x8_msg = -1;\n    bool xa_active = false;\n\n  public:\n    CRelay() = default;\n    explicit CRelay(CInputStream& in);\n\n    TEditorId GetRelayId() const { return x0_relay; }\n    TEditorId GetTargetId() const { return x4_target; }\n    s16 GetMessage() const { return x8_msg; }\n    bool GetActive() const { return xa_active; }\n\n    static std::vector<CWorld::CRelay> ReadMemoryRelays(CInputStream& r);\n  };\n\n  struct CSoundGroupData {\n    int x0_groupId;\n    CAssetId x4_agscId;\n    std::string xc_name;\n    TCachedToken<CAudioGroupSet> x1c_groupData;\n\n  public:\n    CSoundGroupData(int grpId, CAssetId agsc);\n  };\n\nprivate:\n  enum class Phase {\n    Loading,\n    LoadingMap,\n    LoadingMapAreas,\n    LoadingSkyBox,\n    LoadingSoundGroups,\n    Done,\n  };\n\n  Phase x4_phase = Phase::Loading;\n  CAssetId x8_mlvlId;\n  CAssetId xc_strgId;\n  CAssetId x10_savwId;\n  std::vector<std::unique_ptr<CGameArea>> x18_areas;\n  CAssetId x24_mapwId;\n  TLockedToken<CMapWorld> x28_mapWorld;\n  std::vector<CRelay> x2c_relays;\n  std::shared_ptr<IDvdRequest> x3c_loadToken;\n  std::unique_ptr<uint8_t[]> x40_loadBuf;\n  u32 x44_bufSz;\n  u32 x48_chainCount = 0;\n  std::array<CGameArea*, 5> x4c_chainHeads{};\n\n  IObjectStore& x60_objectStore;\n  IFactory& x64_resFactory;\n  TAreaId x68_curAreaId = kInvalidAreaId;\n  u32 x6c_loadedAudioGrpCount = 0;\n  bool x70_24_currentAreaNeedsAllocation : 1 = true;\n  bool x70_25_loadPaused : 1 = false;\n  bool x70_26_skyboxActive : 1 = false;\n  bool x70_27_skyboxVisible : 1 = false;\n  std::vector<CSoundGroupData> x74_soundGroupData;\n  std::string x84_defAudioTrack;\n  std::optional<TLockedToken<CModel>> x94_skyboxWorld;\n  std::optional<TLockedToken<CModel>> xa4_skyboxWorldLoaded;\n  std::optional<TLockedToken<CModel>> xb4_skyboxOverride;\n  EEnvFxType xc4_neededFx = EEnvFxType::None;\n  rstl::reserved_vector<CSfxHandle, 10> xc8_globalSfxHandles;\n\n  // Metaforce addition\n  std::optional<CWorldLayers> m_worldLayers;\n\n  void LoadSoundGroup(int groupId, CAssetId agscId, CSoundGroupData& data);\n  void LoadSoundGroups();\n  void UnloadSoundGroups();\n  void StopSounds();\n\npublic:\n  void MoveToChain(CGameArea* area, EChain chain);\n  void MoveAreaToAliveChain(TAreaId aid);\n  bool CheckWorldComplete(CStateManager* mgr, TAreaId id, CAssetId mreaId);\n\n  [[nodiscard]] auto GetChainHead(EChain chain) { return CGameArea::CChainIterator{x4c_chainHeads[size_t(chain)]}; }\n  [[nodiscard]] auto GetChainHead(EChain chain) const {\n    return CGameArea::CConstChainIterator{x4c_chainHeads[size_t(chain)]};\n  }\n  [[nodiscard]] auto begin() { return GetChainHead(EChain::Alive); }\n  [[nodiscard]] auto end() { return AliveAreasEnd(); }\n  [[nodiscard]] auto begin() const { return GetChainHead(EChain::Alive); }\n  [[nodiscard]] auto end() const { return GetAliveAreasEnd(); }\n\n  bool ScheduleAreaToLoad(CGameArea* area, CStateManager& mgr);\n  void TravelToArea(TAreaId aid, CStateManager& mgr, bool skipLoadOther);\n  void SetLoadPauseState(bool paused);\n  void CycleLoadPauseState();\n\n  CWorld(IObjectStore& objStore, IFactory& resFactory, CAssetId mlvlId);\n  ~CWorld() override;\n  bool DoesAreaExist(TAreaId area) const;\n  const std::vector<std::unique_ptr<CGameArea>>& GetGameAreas() const { return x18_areas; }\n\n  CMapWorld* GetMapWorld() { return x28_mapWorld.GetObj(); }\n  const CMapWorld* GetMapWorld() const { return x28_mapWorld.GetObj(); }\n\n  u32 GetRelayCount() const { return x2c_relays.size(); }\n  CRelay GetRelay(u32 idx) const { return x2c_relays[idx]; }\n\n  CAssetId IGetWorldAssetId() const override;\n  CAssetId IGetStringTableAssetId() const override;\n  CAssetId IGetSaveWorldAssetId() const override;\n  const CMapWorld* IGetMapWorld() const override;\n  CMapWorld* IGetMapWorld() override;\n  const CGameArea* GetAreaAlways(TAreaId id) const;\n  CGameArea* GetArea(TAreaId);\n  s32 GetNumAreas() const { return x18_areas.size(); }\n  const IGameArea* IGetAreaAlways(TAreaId id) const override;\n  TAreaId IGetCurrentAreaId() const override;\n  TAreaId GetCurrentAreaId() const { return x68_curAreaId; }\n  TAreaId IGetAreaId(CAssetId id) const override;\n  bool ICheckWorldComplete() override;\n  std::string IGetDefaultAudioTrack() const override;\n  int IGetAreaCount() const override;\n  const std::optional<CWorldLayers>& GetWorldLayers() const override;\n\n  static void PropogateAreaChain(CGameArea::EOcclusionState occlusionState, CGameArea* area, CWorld* world);\n  static constexpr CGameArea::CConstChainIterator GetAliveAreasEnd() { return CGameArea::CConstChainIterator{}; }\n  static constexpr CGameArea::CChainIterator AliveAreasEnd() { return CGameArea::CChainIterator{}; }\n\n  void Update(float dt);\n  void PreRender();\n  void TouchSky();\n  void DrawSky(const zeus::CTransform& xf);\n  void StopGlobalSound(u16 id);\n  bool HasGlobalSound(u16 id) const;\n  void AddGlobalSound(const CSfxHandle& hnd);\n  EEnvFxType GetNeededEnvFx() const { return xc4_neededFx; }\n  CAssetId GetWorldAssetId() const { return x8_mlvlId; }\n  bool AreSkyNeedsMet();\n  TAreaId GetAreaIdForSaveId(s32 saveId) const;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CWorldLight.cpp",
    "content": "#include \"Runtime/World/CWorldLight.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n\n#include <cfloat>\n\nnamespace metaforce {\nCWorldLight::CWorldLight(CInputStream& in)\n: x0_type(EWorldLightType(in.ReadLong()))\n, x4_color(in.Get<zeus::CVector3f>())\n, x10_position(in.Get<zeus::CVector3f>())\n, x1c_direction(in.Get<zeus::CVector3f>())\n, x28_q(in.ReadFloat())\n, x2c_cutoffAngle(in.ReadFloat())\n, x30_(in.ReadFloat())\n, x34_castShadows(in.ReadBool())\n, x38_(in.ReadFloat())\n, x3c_falloff(EFalloffType(in.ReadLong()))\n, x40_(in.ReadFloat()) {}\n\nstd::tuple<float, float, float> CalculateLightFalloff(EFalloffType falloff, float q) {\n  float constant = 0.f;\n  float linear = 0.f;\n  float quadratic = 0.f;\n\n  if (falloff == EFalloffType::Constant)\n    constant = 2.f / q;\n  else if (falloff == EFalloffType::Linear)\n    linear = 250.f / q;\n  else if (falloff == EFalloffType::Quadratic)\n    quadratic = 25000.f / q;\n\n  return {constant, linear, quadratic};\n}\n\nCLight CWorldLight::GetAsCGraphicsLight() const {\n  zeus::CVector3f float_color = x4_color;\n  float q = x28_q;\n  if (q < FLT_EPSILON)\n    q = 0.000001f;\n\n  if (x0_type == EWorldLightType::LocalAmbient) {\n    float_color *= zeus::CVector3f(q);\n    if (float_color.x() >= 1.f)\n      float_color.x() = 1.f;\n\n    if (float_color.y() >= 1.f)\n      float_color.y() = 1.f;\n\n    if (float_color.z() >= 1.f)\n      float_color.z() = 1.f;\n\n    return CLight::BuildLocalAmbient(x10_position,\n                                     zeus::CColor(float_color.x(), float_color.y(), float_color.z(), 1.f));\n  } else if (x0_type == EWorldLightType::Directional) {\n    return CLight::BuildDirectional(x1c_direction, zeus::CColor{x4_color.x(), x4_color.y(), x4_color.z(), 1.f});\n  } else if (x0_type == EWorldLightType::Spot) {\n    CLight light =\n        CLight::BuildSpot(x10_position, x1c_direction.normalized(),\n                          zeus::CColor{x4_color.x(), x4_color.y(), x4_color.z(), 1.f}, x2c_cutoffAngle * .5f);\n\n    const auto [constant, linear, quadratic] = CalculateLightFalloff(x3c_falloff, x28_q);\n    light.SetAttenuation(constant, linear, quadratic);\n    return light;\n  }\n\n  const auto [distC, distL, distQ] = CalculateLightFalloff(x3c_falloff, x28_q);\n  return CLight::BuildCustom(x10_position, zeus::CVector3f{1.f, 0.f, 0.f},\n                             zeus::CColor{x4_color.x(), x4_color.y(), x4_color.z(), 1.f}, distC, distL, distQ, 1.f, 0.f,\n                             0.f);\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CWorldLight.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Graphics/CLight.hpp\"\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CWorldLight {\npublic:\n  enum class EWorldLightType {\n    LocalAmbient,\n    Directional,\n    Custom,\n    Spot,\n    Spot2,\n    LocalAmbient2,\n  };\n\nprivate:\n  EWorldLightType x0_type = EWorldLightType::Spot2;\n  zeus::CVector3f x4_color;\n  zeus::CVector3f x10_position;\n  zeus::CVector3f x1c_direction;\n  float x28_q = 0.f;\n  float x2c_cutoffAngle = 0.f;\n  float x30_ = 0.f;\n  bool x34_castShadows = false;\n  float x38_ = 0.f;\n  EFalloffType x3c_falloff = EFalloffType::Linear;\n  float x40_ = 0.f;\n\npublic:\n  explicit CWorldLight(CInputStream& in);\n\n  CWorldLight(const CWorldLight&) = default;\n  CWorldLight& operator=(const CWorldLight&) = default;\n\n  CWorldLight(CWorldLight&&) = default;\n  CWorldLight& operator=(CWorldLight&&) = default;\n\n  EWorldLightType GetLightType() const { return x0_type; }\n  const zeus::CVector3f& GetDirection() const { return x1c_direction; }\n  const zeus::CVector3f& GetPosition() const { return x10_position; }\n  bool DoesCastShadows() const { return x34_castShadows; }\n\n  CLight GetAsCGraphicsLight() const;\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CWorldShadow.cpp",
    "content": "#include \"Runtime/World/CWorldShadow.hpp\"\n\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n\nnamespace metaforce {\n\nCWorldShadow::CWorldShadow(u32 w, u32 h, bool rgba8)\n: x0_texture(\n      std::make_unique<CTexture>(rgba8 ? ETexelFormat::RGBA8 : ETexelFormat::RGB565, w, h, 1, \"World Shadow\"sv)) {}\n\nvoid CWorldShadow::EnableModelProjectedShadow(const zeus::CTransform& pos, s32 lightIdx, float f1) {\n  zeus::CTransform texTransform = zeus::lookAt(zeus::skZero3f, x74_lightPos - x68_objPos);\n  zeus::CTransform posXf = pos;\n  posXf.origin = zeus::skZero3f;\n  texTransform = posXf.inverse() * texTransform;\n  texTransform = (texTransform * zeus::CTransform::Scale(float(M_SQRT2) * x64_objHalfExtent * f1)).inverse();\n  texTransform = zeus::CTransform::Translate(0.5f, 0.f, 0.5f) * texTransform;\n  GX::LightMask lightMask;\n  lightMask.set(lightIdx);\n  CCubeModel::EnableShadowMaps(*x0_texture, texTransform, lightMask, lightMask);\n}\n\nvoid CWorldShadow::DisableModelProjectedShadow() { CCubeModel::DisableShadowMaps(); }\n\nvoid CWorldShadow::BuildLightShadowTexture(const CStateManager& mgr, TAreaId aid, s32 lightIdx,\n                                           const zeus::CAABox& aabb, bool motionBlur, bool lighten) {\n  if (x80_aid != aid || x84_lightIdx != lightIdx) {\n    x88_blurReset = true;\n    x80_aid = aid;\n    x84_lightIdx = lightIdx;\n  }\n\n  if (aid != kInvalidAreaId) {\n    const CGameArea* area = mgr.GetWorld()->GetAreaAlways(aid);\n    if (area->IsPostConstructed()) {\n      const CWorldLight& light = area->GetPostConstructed()->x60_lightsA[lightIdx];\n      zeus::CVector3f centerPoint = aabb.center();\n      if (const CPVSAreaSet* pvs = area->GetAreaVisSet()) {\n        CPVSVisSet lightSet = pvs->Get1stLightSet(lightIdx);\n        g_Renderer->EnablePVS(lightSet, aid);\n      } else {\n        CPVSVisSet visSet;\n        visSet.Reset(EPVSVisSetState::OutOfBounds);\n        g_Renderer->EnablePVS(visSet, aid);\n      }\n      zeus::CVector3f lightToPoint = centerPoint - light.GetPosition();\n      x64_objHalfExtent = (aabb.max - centerPoint).magnitude();\n      float distance = lightToPoint.magnitude();\n      float fov = zeus::radToDeg(std::atan2(x64_objHalfExtent, distance)) * 2.f;\n      if (fov >= 0.00001f) {\n        lightToPoint.normalize();\n        x4_view = zeus::lookAt(light.GetPosition(), centerPoint, zeus::skDown);\n        x68_objPos = centerPoint;\n        x74_lightPos = light.GetPosition();\n        CGraphics::SetViewPointMatrix(x4_view);\n        zeus::CFrustum frustum;\n        frustum.updatePlanes(x4_view, zeus::SProjPersp(zeus::degToRad(fov), 1.f, 0.1f, distance + x64_objHalfExtent));\n        g_Renderer->SetClippingPlanes(frustum);\n        g_Renderer->SetPerspective(fov, x0_texture->GetWidth(), x0_texture->GetHeight(), 0.1f, 1000.f);\n        float backupDepthNear = CGraphics::mDepthNear;\n        float backupDepthFar = CGraphics::mDepthFar;\n        CGraphics::SetDepthRange(DEPTH_NEAR, DEPTH_FAR);\n        CViewport backupVp = CGraphics::mViewport;\n        g_Renderer->SetViewport(0, 0, x0_texture->GetWidth() * 2, x0_texture->GetHeight() * 2);\n\n        float extent = float(M_SQRT2) * x64_objHalfExtent;\n        x34_model = zeus::lookAt(centerPoint - zeus::CVector3f(0.f, 0.f, 0.1f), light.GetPosition());\n        g_Renderer->SetModelMatrix(x34_model);\n\n        g_Renderer->PrimColor(zeus::skWhite);\n        CGraphics::SetAlphaCompare(ERglAlphaFunc::Always, 0, ERglAlphaOp::And, ERglAlphaFunc::Always, 0);\n        CGraphics::SetDepthWriteMode(true, ERglEnum::LEqual, true);\n        CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::InvSrcAlpha,\n                                ERglLogicOp::Clear);\n        CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvPassthru);\n        CGraphics::SetTevOp(ERglTevStage::Stage1, CTevCombiners::kEnvPassthru);\n\n        g_Renderer->BeginTriangleStrip(4);\n        g_Renderer->PrimVertex({-extent, 0.f, extent});\n        g_Renderer->PrimVertex({extent, 0.f, extent});\n        g_Renderer->PrimVertex({-extent, 0.f, -extent});\n        g_Renderer->PrimVertex({extent, 0.f, -extent});\n        g_Renderer->EndPrimitive();\n\n        CGraphics::SetModelMatrix(zeus::CTransform());\n        CCubeModel::SetRenderModelBlack(true);\n        CCubeModel::SetDrawingOccluders(true);\n        g_Renderer->PrepareDynamicLights({});\n        g_Renderer->DrawUnsortedGeometry(aid, 0, 0);\n        CCubeModel::SetRenderModelBlack(false);\n        CCubeModel::SetDrawingOccluders(false);\n\n        if (lighten) {\n          CGraphics::SetModelMatrix(x34_model);\n          CGraphics::SetAlphaCompare(ERglAlphaFunc::Always, 0, ERglAlphaOp::And, ERglAlphaFunc::Always, 0);\n          CGraphics::SetDepthWriteMode(false, ERglEnum::LEqual, false);\n          CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::InvSrcAlpha,\n                                  ERglLogicOp::Clear);\n          CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvPassthru);\n          CGraphics::SetTevOp(ERglTevStage::Stage1, CTevCombiners::kEnvPassthru);\n          CGraphics::StreamBegin(ERglPrimitive::TriangleStrip);\n          CGraphics::StreamColor(1.f, 1.f, 1.f, 0.25f);\n          CGraphics::StreamVertex(-extent, 0.f, extent);\n          CGraphics::StreamVertex(extent, 0.f, extent);\n          CGraphics::StreamVertex(-extent, 0.f, -extent);\n          CGraphics::StreamVertex(extent, 0.f, -extent);\n          CGraphics::StreamEnd();\n          CGraphics::SetDepthWriteMode(true, ERglEnum::LEqual, true);\n        }\n\n        if (motionBlur && !x88_blurReset) {\n          CGraphics::SetDepthWriteMode(false, ERglEnum::LEqual, false);\n          CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::InvSrcAlpha,\n                                  ERglLogicOp::Clear);\n          CGraphics::SetAlphaCompare(ERglAlphaFunc::Always, 0, ERglAlphaOp::And, ERglAlphaFunc::Always, 0);\n          CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvModulate);\n          CGraphics::SetTevOp(ERglTevStage::Stage1, CTevCombiners::kEnvPassthru);\n          CGraphics::Render2D(*x0_texture, 0, x0_texture->GetWidth() * 2, x0_texture->GetHeight() * 2,\n                              x0_texture->GetWidth() * -2, zeus::CColor{1.f, 0.85f}, false);\n          CGraphics::SetDepthWriteMode(true, ERglEnum::LEqual, true);\n        }\n\n        x88_blurReset = false;\n\n        GXSetTexCopySrc(0, CGraphics::mRenderModeObj.efbHeight - x0_texture->GetHeight() * 2,\n                        x0_texture->GetWidth() * 2, x0_texture->GetHeight() * 2);\n        GXTexFmt fmt = GX_TF_RGBA8;\n        if (x0_texture->GetTexelFormat() == ETexelFormat::RGB565) {\n          fmt = GX_TF_RGB565;\n        }\n        GXSetTexCopyDst(x0_texture->GetWidth(), x0_texture->GetHeight(), fmt, true);\n        void* dest = x0_texture->Lock();\n        GXCopyTex(dest, true);\n        x0_texture->UnLock();\n\n        g_Renderer->SetViewport(backupVp.mLeft, backupVp.mTop, backupVp.mWidth, backupVp.mHeight);\n        CGraphics::SetDepthRange(backupDepthNear, backupDepthFar);\n      }\n    }\n  }\n}\n\nvoid CWorldShadow::ResetBlur() { x88_blurReset = true; }\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CWorldShadow.hpp",
    "content": "#pragma once\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Graphics/CTexture.hpp\"\n\n#include <zeus/CAABox.hpp>\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CStateManager;\n\nclass CWorldShadow {\n  std::unique_ptr<CTexture> x0_texture;\n  zeus::CTransform x4_view;\n  zeus::CTransform x34_model;\n  float x64_objHalfExtent = 1.f;\n  zeus::CVector3f x68_objPos = {0.f, 1.f, 0.f};\n  zeus::CVector3f x74_lightPos;\n  TAreaId x80_aid = kInvalidAreaId;\n  s32 x84_lightIdx = -1;\n  bool x88_blurReset = true;\n\npublic:\n  CWorldShadow(u32 w, u32 h, bool rgba8);\n  void EnableModelProjectedShadow(const zeus::CTransform& pos, s32 lightIdx, float f1);\n  void DisableModelProjectedShadow();\n  void BuildLightShadowTexture(const CStateManager& mgr, TAreaId aid, s32 lightIdx, const zeus::CAABox& aabb,\n                               bool motionBlur, bool lighten);\n  void ResetBlur();\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CWorldTransManager.cpp",
    "content": "#include \"Runtime/World/CWorldTransManager.hpp\"\n\n#include \"Runtime/CGameState.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/Audio/CSfxManager.hpp\"\n#include \"Runtime/Camera/CCameraManager.hpp\"\n#include \"Runtime/Character/CActorLights.hpp\"\n#include \"Runtime/Character/CAnimPlaybackParms.hpp\"\n#include \"Runtime/Character/CAssetFactory.hpp\"\n#include \"Runtime/Character/CCharacterFactory.hpp\"\n#include \"Runtime/Character/CSkinRules.hpp\"\n#include \"Runtime/Character/IAnimReader.hpp\"\n#include \"Runtime/Graphics/CCubeRenderer.hpp\"\n#include \"Runtime/Graphics/CModel.hpp\"\n#include \"Runtime/GuiSys/CGuiTextSupport.hpp\"\n#include \"Runtime/GuiSys/CStringTable.hpp\"\n\n#include \"ConsoleVariables/CVarManager.hpp\"\n\nnamespace metaforce {\n\nint CWorldTransManager::GetSuitCharIdx() {\n  CPlayerState& state = *g_GameState->GetPlayerState();\n  if (state.IsFusionEnabled()) {\n    switch (state.x20_currentSuit) {\n    case CPlayerState::EPlayerSuit::Power:\n      return 4;\n    case CPlayerState::EPlayerSuit::Gravity:\n      return 6;\n    case CPlayerState::EPlayerSuit::Varia:\n      return 7;\n    case CPlayerState::EPlayerSuit::Phazon:\n      return 8;\n    default:\n      break;\n    }\n  }\n  return int(state.x20_currentSuit);\n}\n\nCWorldTransManager::SModelDatas::SModelDatas(const CAnimRes& samusRes) : x0_samusRes(samusRes) {\n  x1a0_lights.reserve(8);\n}\n\nvoid CWorldTransManager::UpdateLights(float dt) {\n  if (!x4_modelData) {\n    return;\n  }\n\n  x4_modelData->x1a0_lights.clear();\n  constexpr zeus::CVector3f lightPos(0.f, 10.f, 0.f);\n  CLight spot = CLight::BuildSpot(lightPos, zeus::skBack, zeus::skWhite, 90.f);\n  spot.SetAttenuation(1.f, 0.f, 0.f);\n\n  CLight s1 = spot;\n  s1.SetPosition(lightPos + zeus::CVector3f{0.f, 0.f, 2.f * x18_bgOffset - x1c_bgHeight});\n\n  float z = 1.f;\n  float delta = x1c_bgHeight - x18_bgOffset;\n  if (!x44_26_goingUp && delta < 2.f)\n    z = delta * 0.5f;\n  else if (x44_26_goingUp && x18_bgOffset < 2.f)\n    z = x18_bgOffset * 0.5f;\n\n  if (z < 1.f) {\n    CLight s2 = spot;\n    float pos = x44_26_goingUp ? x1c_bgHeight : -x1c_bgHeight;\n    s2.SetPosition(lightPos + zeus::CVector3f{0.f, 0.f, pos});\n    s2.SetColor(zeus::CColor::lerp(zeus::skBlack, zeus::skWhite, 1.f - z));\n    x4_modelData->x1a0_lights.push_back(std::move(s2));\n    s1.SetColor(zeus::CColor::lerp(zeus::skBlack, zeus::skWhite, z));\n  }\n\n  x4_modelData->x1a0_lights.push_back(std::move(s1));\n}\n\nvoid CWorldTransManager::UpdateDisabled(float) {\n  if (x0_curTime <= 2.f)\n    return;\n  x44_24_transFinished = true;\n}\n\nvoid CWorldTransManager::UpdateEnabled(float dt) {\n  if (x4_modelData && !x4_modelData->x1c_samusModelData.IsNull()) {\n    if (x44_25_stopSoon && !x4_modelData->x1dc_dissolveStarted && x0_curTime > 2.f) {\n      x4_modelData->x1dc_dissolveStarted = true;\n      x4_modelData->x1d0_dissolveStartTime = x0_curTime;\n      x4_modelData->x1d4_dissolveEndTime = 4.f + x0_curTime - 2.f;\n      x4_modelData->x1d8_transCompleteTime = 5.f + x0_curTime - 2.f;\n    }\n\n    if (x0_curTime > x4_modelData->x1d8_transCompleteTime && x4_modelData->x1dc_dissolveStarted)\n      x44_24_transFinished = true;\n\n    x4_modelData->x1c_samusModelData.AdvanceAnimationIgnoreParticles(dt, x20_random, true);\n    x4_modelData->x170_gunXf = x4_modelData->x1c_samusModelData.GetScaledLocatorTransform(\"GUN_LCTR\");\n\n    x4_modelData->x1c4_randTimeout -= dt;\n\n    if (x4_modelData->x1c4_randTimeout <= 0.f) {\n      x4_modelData->x1c4_randTimeout = x20_random.Range(0.016666668f, 0.1f);\n      zeus::CVector2f randVec(x20_random.Range(-0.025f, 0.025f), x20_random.Range(-0.075f, 0.075f));\n      x4_modelData->x1bc_shakeDelta = (randVec - x4_modelData->x1b4_shakeResult) / x4_modelData->x1c4_randTimeout;\n      x4_modelData->x1cc_blurDelta =\n          (x20_random.Range(-2.f, 4.f) - x4_modelData->x1c8_blurResult) / x4_modelData->x1c4_randTimeout;\n    }\n\n    x4_modelData->x1b4_shakeResult += x4_modelData->x1bc_shakeDelta * dt;\n    x4_modelData->x1c8_blurResult += dt * x4_modelData->x1cc_blurDelta;\n  }\n\n  float delta = dt * 50.f;\n  if (x44_26_goingUp)\n    delta = -delta;\n\n  x18_bgOffset += delta;\n  if (x18_bgOffset > x1c_bgHeight)\n    x18_bgOffset -= x1c_bgHeight;\n  if (x18_bgOffset < 0.f)\n    x18_bgOffset += x1c_bgHeight;\n\n  UpdateLights(dt);\n}\n\nvoid CWorldTransManager::UpdateText(float dt) {\n  if (x44_28_textDirty) {\n    if (xc_strTable.IsLoaded()) {\n      x8_textData->SetText(xc_strTable->GetString(x40_strIdx));\n      x3c_sfxInterval = 0.f;\n      x44_28_textDirty = false;\n    } else if (x0_curTime >= x38_textStartTime) {\n      x38_textStartTime += dt;\n    }\n  }\n\n  if (x0_curTime >= x38_textStartTime) {\n    x8_textData->Update(dt);\n\n    float nextSfxInterval = x3c_sfxInterval + g_tweakGui->GetWorldTransManagerCharsPerSfx();\n    float printed = x8_textData->GetNumCharsPrinted();\n    if (printed >= nextSfxInterval) {\n      x3c_sfxInterval = nextSfxInterval;\n      CSfxManager::SfxStart(SFXsfx059E, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);\n    }\n  }\n\n  if (x44_25_stopSoon) {\n    if (x8_textData->GetTotalAnimationTime() + 1.f < x8_textData->GetCurTime()) {\n      /* Done typing + 1 sec */\n      if (x0_curTime - x34_stopTime > 1.f)\n        x44_24_transFinished = true;\n    } else {\n      x34_stopTime = x0_curTime;\n    }\n  }\n}\n\nvoid CWorldTransManager::Update(float dt) {\n  x0_curTime += dt;\n  switch (x30_type) {\n  case ETransType::Disabled:\n    UpdateDisabled(dt);\n    break;\n  case ETransType::Enabled:\n    UpdateEnabled(dt);\n    break;\n  case ETransType::Text:\n    UpdateText(dt);\n    break;\n  }\n}\n\nvoid CWorldTransManager::DrawPlatformModels(CActorLights* lights) {\n  CModelFlags flags = {};\n  // TODO flags.m_extendedShader = EExtendedShader::Lighting;\n\n  if (!x4_modelData->x100_bgModelData[0].IsNull()) {\n    zeus::CTransform xf0 = zeus::CTransform::Translate(0.f, 0.f, -(2.f * x1c_bgHeight - x18_bgOffset));\n    x4_modelData->x100_bgModelData[0].Render(CModelData::EWhichModel::Normal, xf0, lights, flags);\n  }\n  if (!x4_modelData->x100_bgModelData[1].IsNull()) {\n    zeus::CTransform xf1 = zeus::CTransform::Translate(0.f, 0.f, x18_bgOffset - x1c_bgHeight);\n    x4_modelData->x100_bgModelData[1].Render(CModelData::EWhichModel::Normal, xf1, lights, flags);\n  }\n  if (!x4_modelData->x100_bgModelData[2].IsNull()) {\n    zeus::CTransform xf2 = zeus::CTransform::Translate(0.f, 0.f, x18_bgOffset);\n    x4_modelData->x100_bgModelData[2].Render(CModelData::EWhichModel::Normal, xf2, lights, flags);\n  }\n\n  if (!x4_modelData->xb4_platformModelData.IsNull()) {\n    x4_modelData->xb4_platformModelData.Render(CModelData::EWhichModel::Normal, zeus::CTransform(), lights, flags);\n  }\n}\n\nvoid CWorldTransManager::DrawAllModels(CActorLights* lights) {\n  // TODO this needs reimpl\n  DrawPlatformModels(lights);\n\n  if (!x4_modelData->x1c_samusModelData.IsNull()) {\n    CModelFlags flags = {};\n    flags.x2_flags |= CModelFlagBits::DepthTest;\n    flags.x2_flags |= CModelFlagBits::DepthUpdate;\n    // TODO flags.m_extendedShader = EExtendedShader::LightingCubeReflection;\n\n    x4_modelData->x1c_samusModelData.GetAnimationData()->PreRender();\n    x4_modelData->x1c_samusModelData.Render(CModelData::EWhichModel::Normal, zeus::CTransform(), lights, flags);\n\n    if (!x4_modelData->x68_beamModelData.IsNull()) {\n      x4_modelData->x68_beamModelData.Render(CModelData::EWhichModel::Normal, x4_modelData->x170_gunXf, lights, flags);\n    }\n  }\n}\n\nvoid CWorldTransManager::DrawFirstPass(CActorLights* lights) {\n  zeus::CTransform translateXf = zeus::CTransform::Translate(\n      x4_modelData->x1b4_shakeResult.x(), -3.5f * (1.f - zeus::clamp(0.f, x0_curTime / 10.f, 1.f)) - 3.5f,\n      x4_modelData->x1b4_shakeResult.y() + 2.f);\n  zeus::CTransform rotateXf =\n      zeus::CTransform::RotateZ(zeus::degToRad(zeus::clamp(0.f, x0_curTime / 25.f, 100.f) * 360.f + 180.f - 90.f));\n  CGraphics::SetViewPointMatrix(rotateXf * translateXf);\n  DrawAllModels(lights);\n  if (x4_modelData->x1c8_blurResult > 0.f) {\n    const auto backupProjectionState = CGraphics::GetProjectionState();\n    CCameraBlurPass blurPass;\n    blurPass.SetBlur(EBlurType::LoBlur, x4_modelData->x1c8_blurResult, 0.f, false);\n    blurPass.Draw();\n    CGraphics::SetProjectionState(backupProjectionState);\n  }\n}\n\nvoid CWorldTransManager::DrawSecondPass(CActorLights* lights) {\n  const zeus::CVector3f& samusScale = x4_modelData->x0_samusRes.GetScale();\n  zeus::CTransform translateXf =\n      zeus::CTransform::Translate(-0.1f * samusScale.x(), -0.5f * samusScale.y(), 1.5f * samusScale.z());\n  zeus::CTransform rotateXf = zeus::CTransform::RotateZ(zeus::degToRad(\n      48.f * zeus::clamp(0.f, (x0_curTime - x4_modelData->x1d0_dissolveStartTime + 2.f) / 5.f, 1.f) + 180.f - 24.f));\n  CGraphics::SetViewPointMatrix(rotateXf * translateXf);\n  DrawAllModels(lights);\n}\n\nvoid CWorldTransManager::DrawEnabled() {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CWorldTransManager::DrawEnabled\", zeus::skPurple);\n  CActorLights lights(0, zeus::skZero3f, 4, 4, 0, 0, 0, 0.1f);\n  lights.BuildFakeLightList(x4_modelData->x1a0_lights, zeus::CColor{0.1f, 0.1f, 0.1f, 1.0f});\n\n  float wsAspect = 1.7777f; // TODO\n\n  g_Renderer->SetPerspective(CCameraManager::FirstPersonFOV(), wsAspect, CCameraManager::NearPlane(),\n                             CCameraManager::FarPlane());\n  g_Renderer->x318_26_requestRGBA6 = true;\n\n  if (x0_curTime <= x4_modelData->x1d0_dissolveStartTime)\n    DrawFirstPass(&lights);\n  else if (x0_curTime >= x4_modelData->x1d4_dissolveEndTime)\n    DrawSecondPass(&lights);\n  else {\n    float t = zeus::clamp(0.f, (x0_curTime - x4_modelData->x1d0_dissolveStartTime) / 2.f, 1.f);\n    DrawFirstPass(&lights);\n    // SClipScreenRect rect(CGraphics::g_Viewport);\n//    CGraphics::ResolveSpareTexture(rect);\n//    CGraphics::g_BooMainCommandQueue->clearTarget(true, true);\n//    DrawSecondPass(&lights);\n//    m_dissolve.drawCropped(zeus::CColor{1.f, 1.f, 1.f, 1.f - t}, 1.f);\n  }\n\n  CCameraFilterPass::DrawFilter(EFilterType::Multiply, EFilterShape::CinemaBars, zeus::skBlack, nullptr, 1.f);\n\n  float ftbT = 0.f;\n  if (x0_curTime < 0.25f)\n    ftbT = 1.f - x0_curTime / 0.25f;\n  else if (x0_curTime > x4_modelData->x1d8_transCompleteTime)\n    ftbT = 1.f;\n  else if (x0_curTime > x4_modelData->x1d8_transCompleteTime - 0.25f)\n    ftbT = 1.f - (x4_modelData->x1d8_transCompleteTime - x0_curTime) / 0.25f;\n  if (ftbT > 0.f)\n    CCameraFilterPass::DrawFilter(EFilterType::Blend, EFilterShape::Fullscreen, zeus::CColor{0.f, ftbT}, nullptr, 1.f);\n}\n\nvoid CWorldTransManager::DrawDisabled() {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CWorldTransManager::DrawDisabled\", zeus::skPurple);\n  CCameraFilterPass::DrawFilter(EFilterType::Blend, EFilterShape::Fullscreen, zeus::CColor{0.f, 0.01f}, nullptr, 1.f);\n}\n\nvoid CWorldTransManager::DrawText() {\n  SCOPED_GRAPHICS_DEBUG_GROUP(\"CWorldTransManager::DrawText\", zeus::skPurple);\n  float width = 448.f * CGraphics::GetViewportAspect();\n  CGraphics::SetOrtho(0.f, width, 448.f, 0.f, -4096.f, 4096.f);\n  CGraphics::SetViewPointMatrix(zeus::CTransform());\n  CGraphics::SetModelMatrix(zeus::CTransform::Translate((width - 640.f) / 2.f, 0.f, 448.f));\n  // g_Renderer->SetViewportOrtho(false, -4096.f, 4096.f);\n  // g_Renderer->SetModelMatrix(zeus::CTransform::Translate(0.f, 0.f, 0.f));\n  CGraphics::SetCullMode(ERglCullMode::None);\n  g_Renderer->SetDepthReadWrite(false, false);\n  g_Renderer->SetBlendMode_AdditiveAlpha();\n  x8_textData->Render();\n\n  float filterAlpha = 0.f;\n  if (x0_curTime < 1.f)\n    filterAlpha = 1.f - x0_curTime;\n  else if (x44_25_stopSoon)\n    filterAlpha = std::min(1.f, x0_curTime - x34_stopTime);\n\n  if (filterAlpha > 0.f) {\n    zeus::CColor filterColor = x44_27_fadeWhite ? zeus::skWhite : zeus::skBlack;\n    filterColor.a() = filterAlpha;\n    CCameraFilterPass::DrawFilter(EFilterType::Blend, EFilterShape::Fullscreen, filterColor, nullptr, 1.f);\n  }\n\n  CGraphics::SetIsBeginSceneClearFb(true);\n}\n\nvoid CWorldTransManager::Draw() {\n  if (x30_type == ETransType::Disabled)\n    DrawDisabled();\n  else if (x30_type == ETransType::Enabled)\n    DrawEnabled();\n  else if (x30_type == ETransType::Text)\n    DrawText();\n}\n\nvoid CWorldTransManager::TouchModels() {\n  if (!x4_modelData)\n    return;\n\n  if (x4_modelData->x68_beamModelData.IsNull() && x4_modelData->x14c_beamModel.IsLoaded() &&\n      x4_modelData->x14c_beamModel.GetObj()) {\n    x4_modelData->x68_beamModelData = CModelData{\n        CStaticRes(x4_modelData->x14c_beamModel.GetObjectTag()->id, x4_modelData->x0_samusRes.GetScale())}; // , 2\n  }\n\n  if (x4_modelData->x1c_samusModelData.IsNull() && x4_modelData->x158_suitModel.IsLoaded() &&\n      x4_modelData->x158_suitModel.GetObj() && x4_modelData->x164_suitSkin.IsLoaded() &&\n      x4_modelData->x164_suitSkin.GetObj()) {\n    CAnimRes animRes(x4_modelData->x0_samusRes.GetId(), GetSuitCharIdx(), x4_modelData->x0_samusRes.GetScale(),\n                     x4_modelData->x0_samusRes.GetDefaultAnim(), true);\n    x4_modelData->x1c_samusModelData = CModelData{animRes}; // , 2\n\n    CAnimPlaybackParms aData(animRes.GetDefaultAnim(), -1, 1.f, true);\n    x4_modelData->x1c_samusModelData.GetAnimationData()->SetAnimation(aData, false);\n  }\n\n  if (!x4_modelData->x1c_samusModelData.IsNull())\n    x4_modelData->x1c_samusModelData.Touch(CModelData::EWhichModel::Normal, 0);\n\n  if (!x4_modelData->xb4_platformModelData.IsNull())\n    x4_modelData->xb4_platformModelData.Touch(CModelData::EWhichModel::Normal, 0);\n\n  if (!x4_modelData->x100_bgModelData[0].IsNull())\n    x4_modelData->x100_bgModelData[0].Touch(CModelData::EWhichModel::Normal, 0);\n  if (!x4_modelData->x100_bgModelData[1].IsNull())\n    x4_modelData->x100_bgModelData[1].Touch(CModelData::EWhichModel::Normal, 0);\n  if (!x4_modelData->x100_bgModelData[2].IsNull())\n    x4_modelData->x100_bgModelData[2].Touch(CModelData::EWhichModel::Normal, 0);\n\n  if (!x4_modelData->x68_beamModelData.IsNull())\n    x4_modelData->x68_beamModelData.Touch(CModelData::EWhichModel::Normal, 0);\n}\n\nvoid CWorldTransManager::EnableTransition(const CAnimRes& samusRes, CAssetId platRes, const zeus::CVector3f& platScale,\n                                          CAssetId bgRes, const zeus::CVector3f& bgScale, bool goingUp) {\n  x44_25_stopSoon = false;\n  x44_26_goingUp = goingUp;\n  x30_type = ETransType::Enabled;\n  x4_modelData = std::make_unique<SModelDatas>(samusRes);\n\n  x8_textData.reset();\n  x20_random.SetSeed(99);\n\n  CAssetId beamModelId = g_tweakPlayerRes->GetBeamCineModel(g_GameState->GetPlayerState()->GetCurrentBeam());\n\n  x4_modelData->x14c_beamModel = g_SimplePool->GetObj(SObjectTag{FOURCC('CMDL'), beamModelId});\n\n  TToken<CCharacterFactory> fac = g_CharFactoryBuilder->GetFactory(samusRes);\n  const CCharacterInfo& info = fac.GetObj()->GetCharInfo(GetSuitCharIdx());\n  x4_modelData->x158_suitModel = g_SimplePool->GetObj(SObjectTag{FOURCC('CMDL'), info.GetModelId()});\n  x4_modelData->x164_suitSkin = g_SimplePool->GetObj(SObjectTag{FOURCC('CSKR'), info.GetSkinRulesId()});\n\n  if (platRes.IsValid()) {\n    x4_modelData->xb4_platformModelData = CModelData{CStaticRes(platRes, platScale)}; // , 2\n    x4_modelData->xb4_platformModelData.Touch(CModelData::EWhichModel::Normal, 0);\n  }\n\n  if (bgRes.IsValid()) {\n    const CStaticRes bg(bgRes, bgScale);\n    x4_modelData->x100_bgModelData[0] = CModelData{bg};\n    x4_modelData->x100_bgModelData[0].Touch(CModelData::EWhichModel::Normal, 0);\n    x4_modelData->x100_bgModelData[1] = CModelData{bg};\n    x4_modelData->x100_bgModelData[1].Touch(CModelData::EWhichModel::Normal, 0);\n    x4_modelData->x100_bgModelData[2] = CModelData{bg};\n    x4_modelData->x100_bgModelData[2].Touch(CModelData::EWhichModel::Normal, 0);\n    const zeus::CAABox bounds = x4_modelData->x100_bgModelData[0].GetBounds();\n    x1c_bgHeight = (bounds.max.z() - bounds.min.z()) * bgScale.z();\n  } else {\n    x1c_bgHeight = 0.f;\n  }\n\n  StartTransition();\n  TouchModels();\n}\n\nvoid CWorldTransManager::EnableTransition(CAssetId fontId, CAssetId stringId, u32 strIdx, bool fadeWhite,\n                                          float chFadeTime, float chFadeRate, float textStartTime) {\n  x40_strIdx = strIdx;\n  x38_textStartTime = textStartTime;\n  x44_25_stopSoon = false;\n  x30_type = ETransType::Text;\n\n  x4_modelData.reset();\n  x44_27_fadeWhite = fadeWhite;\n\n  const CGuiTextProperties props(false, true, EJustification::Center, EVerticalJustification::Center);\n  x8_textData = std::make_unique<CGuiTextSupport>(fontId, props, zeus::skWhite, zeus::skBlack, zeus::skWhite, 640, 448,\n                                                  g_SimplePool, CGuiWidget::EGuiModelDrawFlags::Additive);\n  x8_textData->SetTypeWriteEffectOptions(true, chFadeTime, chFadeRate);\n  xc_strTable = g_SimplePool->GetObj(SObjectTag{FOURCC('STRG'), stringId});\n  x8_textData->SetText(u\"\");\n  StartTransition();\n}\n\nvoid CWorldTransManager::StartTextFadeOut() {\n  if (!x44_25_stopSoon)\n    x34_stopTime = x0_curTime;\n  x44_25_stopSoon = true;\n}\n\nvoid CWorldTransManager::DisableTransition() {\n  x30_type = ETransType::Disabled;\n  x4_modelData.reset();\n  x8_textData.reset();\n  x44_26_goingUp = false;\n}\n\nvoid CWorldTransManager::StartTransition() {\n  x0_curTime = 0.f;\n  x18_bgOffset = 0.f;\n  x44_24_transFinished = false;\n  x44_28_textDirty = true;\n}\n\nvoid CWorldTransManager::EndTransition() { DisableTransition(); }\n\nbool CWorldTransManager::WaitForModelsAndTextures() {\n  std::vector<SObjectTag> tags = g_SimplePool->GetReferencedTags();\n  for (const SObjectTag& tag : tags) {\n    if (tag.type == FOURCC('TXTR') || tag.type == FOURCC('CMDL'))\n      g_SimplePool->GetObj(tag).GetObj();\n  }\n  return false;\n}\n\nvoid CWorldTransManager::SfxStop() {\n  if (x28_sfxHandle) {\n    CSfxManager::SfxStop(x28_sfxHandle);\n    x28_sfxHandle.reset();\n  }\n}\n\nvoid CWorldTransManager::SfxStart() {\n  if (!x28_sfxHandle && x24_sfx != 0xFFFF)\n    x28_sfxHandle = CSfxManager::SfxStart(x24_sfx, x2c_volume, x2d_panning, false, 127, true, -1);\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/CWorldTransManager.hpp",
    "content": "#pragma once\n\n#include <array>\n#include <memory>\n#include <vector>\n\n#include \"Runtime/CRandom16.hpp\"\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/Audio/CSfxManager.hpp\"\n#include \"Runtime/Camera/CCameraFilter.hpp\"\n#include \"Runtime/Character/CModelData.hpp\"\n#include \"Runtime/Graphics/CLight.hpp\"\n#include \"Runtime/GuiSys/CGuiTextSupport.hpp\"\n#include \"Runtime/GuiSys/CStringTable.hpp\"\n\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector2f.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CSimplePool;\n\nclass CWorldTransManager {\npublic:\n  enum class ETransType { Disabled, Enabled, Text };\n\n  struct SModelDatas {\n    CAnimRes x0_samusRes;\n    CModelData x1c_samusModelData;\n    CModelData x68_beamModelData;\n    CModelData xb4_platformModelData;\n    std::array<CModelData, 3> x100_bgModelData;\n    TLockedToken<CModel> x14c_beamModel;\n    TLockedToken<CModel> x158_suitModel;\n    TLockedToken<CSkinRules> x164_suitSkin;\n    zeus::CTransform x170_gunXf;\n    std::vector<CLight> x1a0_lights;\n    // std::unique_ptr<u8> x1b0_dissolveTextureBuffer;\n    zeus::CVector2f x1b4_shakeResult;\n    zeus::CVector2f x1bc_shakeDelta;\n    float x1c4_randTimeout = 0.f;\n    float x1c8_blurResult = 0.f;\n    float x1cc_blurDelta = 0.f;\n    float x1d0_dissolveStartTime = 99999.f;\n    float x1d4_dissolveEndTime = 99999.f;\n    float x1d8_transCompleteTime = 99999.f;\n    bool x1dc_dissolveStarted = false;\n\n    explicit SModelDatas(const CAnimRes& samusRes);\n  };\n\nprivate:\n  float x0_curTime = 0.f;\n  std::unique_ptr<SModelDatas> x4_modelData;\n  std::unique_ptr<CGuiTextSupport> x8_textData;\n  TLockedToken<CStringTable> xc_strTable;\n  u8 x14_ = 0;\n  float x18_bgOffset = 0.0f;\n  float x1c_bgHeight = 0.0f;\n  CRandom16 x20_random = CRandom16(99);\n  u16 x24_sfx = 1189;\n  CSfxHandle x28_sfxHandle;\n  u8 x2c_volume = 127;\n  u8 x2d_panning = 64;\n  ETransType x30_type = ETransType::Disabled;\n  float x34_stopTime = 0.0f;\n  float x38_textStartTime = 0.f;\n  float x3c_sfxInterval = 0.0f;\n  u32 x40_strIdx = 0;\n  bool x44_24_transFinished : 1 = true;\n  bool x44_25_stopSoon : 1 = false;\n  bool x44_26_goingUp : 1 = false;\n  bool x44_27_fadeWhite : 1 = false;\n  bool x44_28_textDirty : 1 = false;\n\n  static int GetSuitCharIdx();\n  void DrawFirstPass(CActorLights* lights);\n  void DrawSecondPass(CActorLights* lights);\n  void DrawPlatformModels(CActorLights* lights);\n  void DrawAllModels(CActorLights* lights);\n  void UpdateLights(float dt);\n  void UpdateEnabled(float);\n  void UpdateDisabled(float);\n  void UpdateText(float);\n  void DrawEnabled();\n  void DrawDisabled();\n  void DrawText();\n\npublic:\n  CWorldTransManager() = default;\n\n  void Update(float);\n  void Draw();\n\n  void EnableTransition(const CAnimRes& samusRes, CAssetId platRes, const zeus::CVector3f& platScale, CAssetId bgRes,\n                        const zeus::CVector3f& bgScale, bool goingUp);\n  void EnableTransition(CAssetId fontId, CAssetId stringId, u32 strIdx, bool fadeWhite, float chFadeTime,\n                        float chFadeRate, float textStartTime);\n\n  void StartTransition();\n  void EndTransition();\n  bool IsTransitionFinished() const { return x44_24_transFinished; }\n  void PleaseStopSoon() { x44_25_stopSoon = true; }\n  void StartTextFadeOut();\n  bool IsTransitionEnabled() const { return x30_type != ETransType::Disabled; }\n  void DisableTransition();\n  void TouchModels();\n  ETransType GetTransType() const { return x30_type; }\n  void SetSfx(u16 sfx, u8 volume, u8 panning) {\n    x24_sfx = sfx;\n    x2c_volume = volume;\n    x2d_panning = panning;\n  }\n  void SfxStart();\n  void SfxStop();\n\n  static bool WaitForModelsAndTextures();\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/IGameArea.cpp",
    "content": "#include \"Runtime/World/IGameArea.hpp\"\n#include \"Runtime/Streams/IOStreams.hpp\"\n\nnamespace metaforce {\nIGameArea::Dock::Dock(metaforce::CInputStream& in, const zeus::CTransform& xf) {\n  u32 refCount = in.ReadLong();\n  x4_dockReferences.reserve(refCount);\n  for (u32 i = 0; i < refCount; i++) {\n    SDockReference ref;\n    ref.x0_area = in.ReadLong();\n    ref.x4_dock = in.ReadLong();\n    x4_dockReferences.push_back(ref);\n  }\n\n  u32 vertCount = in.ReadLong();\n\n  for (u32 i = 0; i < vertCount; i++) {\n    zeus::CVector3f vert = in.Get<zeus::CVector3f>();\n    x14_planeVertices.push_back(xf * vert);\n  }\n}\n\nTAreaId IGameArea::Dock::GetConnectedAreaId(s32 other) const {\n  if (x4_dockReferences.empty())\n    return kInvalidAreaId;\n\n  return x4_dockReferences[other].x0_area;\n}\n\ns16 IGameArea::Dock::GetOtherDockNumber(s32 other) const {\n  if (u32(other) >= x4_dockReferences.size() || other < 0)\n    return -1;\n\n  return x4_dockReferences[other].x4_dock;\n}\n\nbool IGameArea::Dock::GetShouldLoadOther(s32 other) const {\n  if (other >= x4_dockReferences.size())\n    return false;\n\n  return x4_dockReferences[other].x6_loadOther;\n}\n\nvoid IGameArea::Dock::SetShouldLoadOther(s32 other, bool should) {\n  if (other >= x4_dockReferences.size())\n    return;\n\n  x4_dockReferences[other].x6_loadOther = should;\n}\n\nbool IGameArea::Dock::ShouldLoadOtherArea(s32 other) const {\n  if (x4_dockReferences.size() == 0)\n    return false;\n\n  return x4_dockReferences[other].x6_loadOther;\n}\n\nzeus::CVector3f IGameArea::Dock::GetPoint(s32 idx) const {\n  if (idx >= x14_planeVertices.size() || idx < 0)\n    return zeus::CVector3f();\n\n  return x14_planeVertices[idx];\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/IGameArea.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <utility>\n#include <vector>\n\n#include \"Runtime/RetroTypes.hpp\"\n#include \"Runtime/rstl.hpp\"\n\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CEntity;\n\nclass IGameArea {\npublic:\n  class Dock {\n  public:\n    struct SDockReference {\n      u32 x0_area = 0;\n      s16 x4_dock = 0;\n      bool x6_loadOther = false;\n      SDockReference() = default;\n    };\n\n  private:\n    s32 x0_referenceCount = 0;\n    std::vector<SDockReference> x4_dockReferences;\n    rstl::reserved_vector<zeus::CVector3f, 4> x14_planeVertices;\n    bool x48_isReferenced = false;\n\n  public:\n    const rstl::reserved_vector<zeus::CVector3f, 4>& GetPlaneVertices() const { return x14_planeVertices; }\n    s32 GetReferenceCount() const { return x0_referenceCount; }\n    const std::vector<SDockReference>& GetDockRefs() const { return x4_dockReferences; }\n    Dock(CInputStream& in, const zeus::CTransform& xf);\n    TAreaId GetConnectedAreaId(s32 other) const;\n    s16 GetOtherDockNumber(s32 other) const;\n    bool GetShouldLoadOther(s32 other) const;\n    void SetShouldLoadOther(s32 other, bool should);\n    bool ShouldLoadOtherArea(s32 other) const;\n    zeus::CVector3f GetPoint(s32 idx) const;\n    bool IsReferenced() const { return x48_isReferenced; }\n    void SetReferenceCount(s32 v) {\n      x0_referenceCount = v;\n      x48_isReferenced = true;\n    }\n  };\n\n  virtual std::pair<std::unique_ptr<u8[]>, s32> IGetScriptingMemoryAlways() const = 0;\n  virtual s32 IGetAreaSaveId() const = 0;\n  virtual CAssetId IGetAreaAssetId() const = 0;\n  virtual bool IIsActive() const = 0;\n  virtual TAreaId IGetAttachedAreaId(int) const = 0;\n  virtual u32 IGetNumAttachedAreas() const = 0;\n  virtual CAssetId IGetStringTableAssetId() const = 0;\n  virtual const zeus::CTransform& IGetTM() const = 0;\n};\n\nenum class EChain {\n  Invalid = -1,\n  ToDeallocate,\n  Deallocated,\n  Loading,\n  Alive,\n  AliveJudgement,\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/ScriptLoader.cpp",
    "content": "#include \"Runtime/World/ScriptLoader.hpp\"\n\n#include \"Runtime/Camera/CCinematicCamera.hpp\"\n#include \"Runtime/Camera/CPathCamera.hpp\"\n#include \"Runtime/Collision/CCollidableOBBTreeGroup.hpp\"\n#include \"Runtime/CSimplePool.hpp\"\n#include \"Runtime/CStateManager.hpp\"\n#include \"Runtime/GameGlobalObjects.hpp\"\n#include \"Runtime/MP1/World/CScriptContraption.hpp\"\n#include \"Runtime/MP1/World/CAtomicAlpha.hpp\"\n#include \"Runtime/MP1/World/CAtomicBeta.hpp\"\n#include \"Runtime/MP1/World/CBabygoth.hpp\"\n#include \"Runtime/MP1/World/CBeetle.hpp\"\n#include \"Runtime/MP1/World/CBloodFlower.hpp\"\n#include \"Runtime/MP1/World/CBurrower.hpp\"\n#include \"Runtime/MP1/World/CChozoGhost.hpp\"\n#include \"Runtime/MP1/World/CElitePirate.hpp\"\n#include \"Runtime/MP1/World/CEnergyBall.hpp\"\n#include \"Runtime/MP1/World/CEyeball.hpp\"\n#include \"Runtime/MP1/World/CFireFlea.hpp\"\n#include \"Runtime/MP1/World/CFlaahgra.hpp\"\n#include \"Runtime/MP1/World/CFlaahgraTentacle.hpp\"\n#include \"Runtime/MP1/World/CFlickerBat.hpp\"\n#include \"Runtime/MP1/World/CFlyingPirate.hpp\"\n#include \"Runtime/MP1/World/CIceSheegoth.hpp\"\n#include \"Runtime/MP1/World/CJellyZap.hpp\"\n#include \"Runtime/MP1/World/CMagdolite.hpp\"\n#include \"Runtime/MP1/World/CMetaree.hpp\"\n#include \"Runtime/MP1/World/CDrone.hpp\"\n#include \"Runtime/MP1/World/CMetroid.hpp\"\n#include \"Runtime/MP1/World/CMetroidBeta.hpp\"\n#include \"Runtime/MP1/World/CMetroidPrimeStage2.hpp\"\n#include \"Runtime/MP1/World/CMetroidPrimeRelay.hpp\"\n#include \"Runtime/MP1/World/CNewIntroBoss.hpp\"\n#include \"Runtime/MP1/World/COmegaPirate.hpp\"\n#include \"Runtime/MP1/World/CParasite.hpp\"\n#include \"Runtime/MP1/World/CPhazonHealingNodule.hpp\"\n#include \"Runtime/MP1/World/CPhazonPool.hpp\"\n#include \"Runtime/MP1/World/CPuddleSpore.hpp\"\n#include \"Runtime/MP1/World/CPuddleToadGamma.hpp\"\n#include \"Runtime/MP1/World/CPuffer.hpp\"\n#include \"Runtime/MP1/World/CRidley.hpp\"\n#include \"Runtime/MP1/World/CRipper.hpp\"\n#include \"Runtime/MP1/World/CSeedling.hpp\"\n#include \"Runtime/MP1/World/CSpacePirate.hpp\"\n#include \"Runtime/MP1/World/CSpankWeed.hpp\"\n#include \"Runtime/MP1/World/CThardus.hpp\"\n#include \"Runtime/MP1/World/CThardusRockProjectile.hpp\"\n#include \"Runtime/MP1/World/CTryclops.hpp\"\n#include \"Runtime/MP1/World/CWarWasp.hpp\"\n#include \"Runtime/Particle/CWeaponDescription.hpp\"\n#include \"Runtime/World/CActorParameters.hpp\"\n#include \"Runtime/World/CAmbientAI.hpp\"\n#include \"Runtime/World/CAnimationParameters.hpp\"\n#include \"Runtime/World/CDamageInfo.hpp\"\n#include \"Runtime/World/CFishCloud.hpp\"\n#include \"Runtime/World/CFishCloudModifier.hpp\"\n#include \"Runtime/World/CFluidUVMotion.hpp\"\n#include \"Runtime/World/CGrappleParameters.hpp\"\n#include \"Runtime/World/CPatternedInfo.hpp\"\n#include \"Runtime/World/CRepulsor.hpp\"\n#include \"Runtime/World/CScriptActor.hpp\"\n#include \"Runtime/World/CScriptActorKeyframe.hpp\"\n#include \"Runtime/World/CScriptActorRotate.hpp\"\n#include \"Runtime/World/CScriptAiJumpPoint.hpp\"\n#include \"Runtime/World/CScriptAreaAttributes.hpp\"\n#include \"Runtime/World/CScriptBallTrigger.hpp\"\n#include \"Runtime/World/CScriptBeam.hpp\"\n#include \"Runtime/World/CScriptCameraBlurKeyframe.hpp\"\n#include \"Runtime/World/CScriptCameraFilterKeyframe.hpp\"\n#include \"Runtime/World/CScriptCameraHint.hpp\"\n#include \"Runtime/World/CScriptCameraHintTrigger.hpp\"\n#include \"Runtime/World/CScriptCameraPitchVolume.hpp\"\n#include \"Runtime/World/CScriptCameraShaker.hpp\"\n#include \"Runtime/World/CScriptCameraWaypoint.hpp\"\n#include \"Runtime/World/CScriptColorModulate.hpp\"\n#include \"Runtime/World/CScriptControllerAction.hpp\"\n#include \"Runtime/World/CScriptCounter.hpp\"\n#include \"Runtime/World/CScriptCoverPoint.hpp\"\n#include \"Runtime/World/CScriptDamageableTrigger.hpp\"\n#include \"Runtime/World/CScriptDebris.hpp\"\n#include \"Runtime/World/CScriptDebugCameraWaypoint.hpp\"\n#include \"Runtime/World/CScriptDistanceFog.hpp\"\n#include \"Runtime/World/CScriptDock.hpp\"\n#include \"Runtime/World/CScriptDockAreaChange.hpp\"\n#include \"Runtime/World/CScriptDoor.hpp\"\n#include \"Runtime/World/CScriptEffect.hpp\"\n#include \"Runtime/World/CScriptEMPulse.hpp\"\n#include \"Runtime/World/CScriptGenerator.hpp\"\n#include \"Runtime/World/CScriptGrapplePoint.hpp\"\n#include \"Runtime/World/CScriptGunTurret.hpp\"\n#include \"Runtime/World/CScriptHUDMemo.hpp\"\n#include \"Runtime/World/CScriptMazeNode.hpp\"\n#include \"Runtime/World/CScriptMemoryRelay.hpp\"\n#include \"Runtime/World/CScriptMidi.hpp\"\n#include \"Runtime/World/CScriptPickup.hpp\"\n#include \"Runtime/World/CScriptPickupGenerator.hpp\"\n#include \"Runtime/World/CScriptPlatform.hpp\"\n#include \"Runtime/World/CScriptPlayerActor.hpp\"\n#include \"Runtime/World/CScriptPlayerHint.hpp\"\n#include \"Runtime/World/CScriptPlayerStateChange.hpp\"\n#include \"Runtime/World/CScriptPointOfInterest.hpp\"\n#include \"Runtime/World/CScriptRandomRelay.hpp\"\n#include \"Runtime/World/CScriptRelay.hpp\"\n#include \"Runtime/World/CScriptRipple.hpp\"\n#include \"Runtime/World/CScriptRoomAcoustics.hpp\"\n#include \"Runtime/World/CScriptShadowProjector.hpp\"\n#include \"Runtime/World/CScriptSound.hpp\"\n#include \"Runtime/World/CScriptSpawnPoint.hpp\"\n#include \"Runtime/World/CScriptSpecialFunction.hpp\"\n#include \"Runtime/World/CScriptSpiderBallAttractionSurface.hpp\"\n#include \"Runtime/World/CScriptSpiderBallWaypoint.hpp\"\n#include \"Runtime/World/CScriptSpindleCamera.hpp\"\n#include \"Runtime/World/CScriptSteam.hpp\"\n#include \"Runtime/World/CScriptStreamedMusic.hpp\"\n#include \"Runtime/World/CScriptSwitch.hpp\"\n#include \"Runtime/World/CScriptTargetingPoint.hpp\"\n#include \"Runtime/World/CScriptTimer.hpp\"\n#include \"Runtime/World/CScriptVisorFlare.hpp\"\n#include \"Runtime/World/CScriptVisorGoo.hpp\"\n#include \"Runtime/World/CScriptWater.hpp\"\n#include \"Runtime/World/CScriptWaypoint.hpp\"\n#include \"Runtime/World/CScriptWorldTeleporter.hpp\"\n#include \"Runtime/World/CSnakeWeedSwarm.hpp\"\n#include \"Runtime/World/CTeamAiMgr.hpp\"\n#include \"Runtime/World/CWallCrawlerSwarm.hpp\"\n#include \"Runtime/World/CWorld.hpp\"\n#include \"Runtime/Logging.hpp\"\n#include \"Runtime/Formatting.hpp\"\n\nnamespace metaforce {\nconstexpr SObjectTag MorphballDoorANCS = {FOURCC('ANCS'), 0x1F9DA858u};\n\nconstexpr int skElitePiratePropCount = 41;\n\nstatic bool EnsurePropertyCount(int count, int expected, const char* structName) {\n  if (count < expected) {\n    spdlog::warn(\"Insufficient number of props ({}/{}) for {} entity\", count, expected, structName);\n    return false;\n  }\n  return true;\n}\n\nstruct SActorHead {\n  std::string x0_name;\n  zeus::CTransform x10_transform;\n};\n\nstruct SScaledActorHead : SActorHead {\n  zeus::CVector3f x40_scale;\n\n  SScaledActorHead(SActorHead&& head) : SActorHead(std::move(head)) {}\n};\n\nstatic zeus::CTransform LoadEditorTransform(CInputStream& in) {\n  zeus::CVector3f position = in.Get<zeus::CVector3f>();\n  zeus::CVector3f orientation = in.Get<zeus::CVector3f>();\n  return ScriptLoader::ConvertEditorEulerToTransform4f(orientation, position);\n}\n\nstatic zeus::CTransform LoadEditorTransformPivotOnly(CInputStream& in) {\n  zeus::CVector3f position = in.Get<zeus::CVector3f>();\n  zeus::CVector3f orientation = in.Get<zeus::CVector3f>();\n  orientation.x() = 0.f;\n  orientation.y() = 0.f;\n  return ScriptLoader::ConvertEditorEulerToTransform4f(orientation, position);\n}\n\nstatic SActorHead LoadActorHead(CInputStream& in, CStateManager& stateMgr) {\n  SActorHead ret;\n  ret.x0_name = stateMgr.HashInstanceName(in);\n  ret.x10_transform = LoadEditorTransform(in);\n  return ret;\n}\n\nstatic SScaledActorHead LoadScaledActorHead(CInputStream& in, CStateManager& stateMgr) {\n  SScaledActorHead ret = LoadActorHead(in, stateMgr);\n  ret.x40_scale = in.Get<zeus::CVector3f>();\n  return ret;\n}\n\nstatic zeus::CAABox GetCollisionBox(CStateManager& stateMgr, TAreaId id, const zeus::CVector3f& extent,\n                                    const zeus::CVector3f& offset) {\n  zeus::CAABox box(-extent * 0.5f + offset, extent * 0.5f + offset);\n  const zeus::CTransform& rot = stateMgr.GetWorld()->GetGameAreas()[id]->GetTransform().getRotation();\n  return box.getTransformedAABox(rot);\n}\n\nu32 ScriptLoader::LoadParameterFlags(CInputStream& in) {\n  u32 count = in.ReadLong();\n  u32 ret = 0;\n  for (u32 i = 0; i < count; ++i)\n    if (in.ReadBool())\n      ret |= 1 << i;\n  return ret;\n}\n\nCGrappleParameters ScriptLoader::LoadGrappleParameters(CInputStream& in) {\n  float a = in.ReadFloat();\n  float b = in.ReadFloat();\n  float c = in.ReadFloat();\n  float d = in.ReadFloat();\n  float e = in.ReadFloat();\n  float f = in.ReadFloat();\n  float g = in.ReadFloat();\n  float h = in.ReadFloat();\n  float i = in.ReadFloat();\n  float j = in.ReadFloat();\n  float k = in.ReadFloat();\n  bool l = in.ReadBool();\n  return CGrappleParameters(a, b, c, d, e, f, g, h, i, j, k, l);\n}\n\nCActorParameters ScriptLoader::LoadActorParameters(CInputStream& in) {\n  u32 propCount = in.ReadLong();\n  if (propCount >= 5 && propCount <= 0xe) {\n    CLightParameters lParms = ScriptLoader::LoadLightParameters(in);\n\n    CScannableParameters sParams;\n    if (propCount > 5)\n      sParams = LoadScannableParameters(in);\n\n    CAssetId xrayModel = in.Get<CAssetId>();\n    CAssetId xraySkin = in.Get<CAssetId>();\n    CAssetId infraModel = in.Get<CAssetId>();\n    CAssetId infraSkin = in.Get<CAssetId>();\n\n    bool globalTimeProvider = true;\n    if (propCount > 7)\n      globalTimeProvider = in.ReadBool();\n\n    float fadeInTime = 1.f;\n    if (propCount > 8)\n      fadeInTime = in.ReadFloat();\n\n    float fadeOutTime = 1.f;\n    if (propCount > 9)\n      fadeOutTime = in.ReadFloat();\n\n    CVisorParameters vParms;\n    if (propCount > 6)\n      vParms = LoadVisorParameters(in);\n\n    bool thermalHeat = false;\n    if (propCount > 10)\n      thermalHeat = in.ReadBool();\n\n    bool renderUnsorted = false;\n    if (propCount > 11)\n      renderUnsorted = in.ReadBool();\n\n    bool noSortThermal = false;\n    if (propCount > 12)\n      noSortThermal = in.ReadBool();\n\n    float thermalMag = 1.f;\n    if (propCount > 13)\n      thermalMag = in.ReadFloat();\n\n    std::pair<CAssetId, CAssetId> xray = {};\n    if (g_ResFactory->GetResourceTypeById(xrayModel).IsValid())\n      xray = {xrayModel, xraySkin};\n\n    std::pair<CAssetId, CAssetId> infra = {};\n    if (g_ResFactory->GetResourceTypeById(infraModel).IsValid())\n      infra = {infraModel, infraSkin};\n\n    return CActorParameters(lParms, sParams, xray, infra, vParms, globalTimeProvider, thermalHeat, renderUnsorted,\n                            noSortThermal, fadeInTime, fadeOutTime, thermalMag);\n  }\n  return CActorParameters::None();\n}\n\nCVisorParameters ScriptLoader::LoadVisorParameters(CInputStream& in) {\n  u32 propCount = in.ReadLong();\n  if (propCount >= 1 && propCount <= 3) {\n    bool b1 = in.ReadBool();\n    bool scanPassthrough = false;\n    u8 mask = 0xf;\n    if (propCount > 2)\n      scanPassthrough = in.ReadBool();\n    if (propCount >= 2)\n      mask = u8(in.ReadLong());\n    return CVisorParameters(mask, b1, scanPassthrough);\n  }\n  return CVisorParameters();\n}\n\nCScannableParameters ScriptLoader::LoadScannableParameters(CInputStream& in) {\n  u32 propCount = in.ReadLong();\n  if (propCount == 1)\n    return CScannableParameters(in.Get<CAssetId>());\n  return CScannableParameters();\n}\n\nCLightParameters ScriptLoader::LoadLightParameters(CInputStream& in) {\n  u32 propCount = in.ReadLong();\n  if (propCount == 14) {\n    bool a = in.ReadBool();\n    float b = in.ReadFloat();\n    CLightParameters::EShadowTesselation shadowTess = CLightParameters::EShadowTesselation(in.ReadLong());\n    float d = in.ReadFloat();\n    float e = in.ReadFloat();\n\n    zeus::CColor noLightsAmbient = in.Get<zeus::CColor>();\n\n    bool makeLights = in.ReadBool();\n    CLightParameters::EWorldLightingOptions lightOpts = CLightParameters::EWorldLightingOptions(in.ReadLong());\n    CLightParameters::ELightRecalculationOptions recalcOpts =\n        CLightParameters::ELightRecalculationOptions(in.ReadLong());\n\n    zeus::CVector3f actorPosBias = in.Get<zeus::CVector3f>();\n\n    s32 maxDynamicLights = -1;\n    s32 maxAreaLights = -1;\n    if (propCount >= 12) {\n      maxDynamicLights = in.ReadLong();\n      maxAreaLights = in.ReadLong();\n    }\n\n    bool ambientChannelOverflow = false;\n    if (propCount >= 13)\n      ambientChannelOverflow = in.ReadBool();\n\n    s32 layerIdx = 0;\n    if (propCount >= 14)\n      layerIdx = in.ReadLong();\n\n    return CLightParameters(a, b, shadowTess, d, e, noLightsAmbient, makeLights, lightOpts, recalcOpts, actorPosBias,\n                            maxDynamicLights, maxAreaLights, ambientChannelOverflow, layerIdx);\n  }\n  return CLightParameters::None();\n}\n\nCAnimationParameters ScriptLoader::LoadAnimationParameters(CInputStream& in) {\n  CAssetId ancs = in.Get<CAssetId>();\n  s32 charIdx = in.ReadLong();\n  u32 defaultAnim = in.ReadLong();\n  return CAnimationParameters(ancs, charIdx, defaultAnim);\n}\n\nCFluidUVMotion ScriptLoader::LoadFluidUVMotion(CInputStream& in) {\n  auto motion = CFluidUVMotion::EFluidUVMotion(in.ReadLong());\n  float a = in.ReadFloat();\n  float b = in.ReadFloat();\n  b = zeus::degToRad(b) - M_PIF;\n  float c = in.ReadFloat();\n  float d = in.ReadFloat();\n  CFluidUVMotion::SFluidLayerMotion pattern1Layer(motion, a, b, c, d);\n\n  motion = CFluidUVMotion::EFluidUVMotion(in.ReadLong());\n  a = in.ReadFloat();\n  b = in.ReadFloat();\n  b = zeus::degToRad(b) - M_PIF;\n  c = in.ReadFloat();\n  d = in.ReadFloat();\n  CFluidUVMotion::SFluidLayerMotion pattern2Layer(motion, a, b, c, d);\n\n  motion = CFluidUVMotion::EFluidUVMotion(in.ReadLong());\n  a = in.ReadFloat();\n  b = in.ReadFloat();\n  b = zeus::degToRad(b) - M_PIF;\n  c = in.ReadFloat();\n  d = in.ReadFloat();\n  CFluidUVMotion::SFluidLayerMotion colorLayer(motion, a, b, c, d);\n\n  a = in.ReadFloat();\n  b = in.ReadFloat();\n\n  b = zeus::degToRad(b) - M_PIF;\n\n  return CFluidUVMotion(a, b, colorLayer, pattern1Layer, pattern2Layer);\n}\n\nzeus::CTransform ScriptLoader::ConvertEditorEulerToTransform4f(const zeus::CVector3f& orientation,\n                                                               const zeus::CVector3f& position) {\n  zeus::simd_floats f(orientation.mSimd);\n  return zeus::CTransform::RotateZ(zeus::degToRad(f[2])) * zeus::CTransform::RotateY(zeus::degToRad(f[1])) *\n             zeus::CTransform::RotateX(zeus::degToRad(f[0])) +\n         position;\n}\n\nCEntity* ScriptLoader::LoadActor(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 24, \"Actor\"))\n    return nullptr;\n\n  SScaledActorHead head = LoadScaledActorHead(in, mgr);\n\n  zeus::CVector3f collisionExtent = in.Get<zeus::CVector3f>();\n\n  zeus::CVector3f centroid = in.Get<zeus::CVector3f>();\n\n  float mass = in.ReadFloat();\n  float zMomentum = in.ReadFloat();\n\n  CHealthInfo hInfo(in);\n\n  CDamageVulnerability dVuln(in);\n\n  CAssetId staticId = in.Get<CAssetId>();\n  CAnimationParameters aParms = LoadAnimationParameters(in);\n\n  CActorParameters actParms = LoadActorParameters(in);\n\n  bool looping = in.ReadBool();\n  bool immovable = in.ReadBool();\n  bool solid = in.ReadBool();\n  bool cameraPassthrough = in.ReadBool();\n  bool active = in.ReadBool();\n  u32 shaderIdx = in.ReadLong();\n  float xrayAlpha = in.ReadFloat();\n  bool noThermalHotZ = in.ReadBool();\n  bool castsShadow = in.ReadBool();\n  bool scaleAdvancementDelta = in.ReadBool();\n  bool materialFlag54 = in.ReadBool();\n\n  FourCC animType = g_ResFactory->GetResourceTypeById(aParms.GetACSFile());\n  if (!g_ResFactory->GetResourceTypeById(staticId).IsValid() && !animType.IsValid())\n    return nullptr;\n\n  zeus::CAABox aabb = GetCollisionBox(mgr, info.GetAreaId(), collisionExtent, centroid);\n\n  CMaterialList list;\n  if (immovable) // Bool 2\n    list.Add(EMaterialTypes::Immovable);\n\n  if (solid) // Bool 3\n    list.Add(EMaterialTypes::Solid);\n\n  if (cameraPassthrough) // Bool 4\n    list.Add(EMaterialTypes::CameraPassthrough);\n\n  CModelData data;\n  if (animType == SBIG('ANCS')) {\n    data = CModelData{\n        CAnimRes(aParms.GetACSFile(), aParms.GetCharacter(), head.x40_scale, aParms.GetInitialAnimation(), false)};\n  } else {\n    data = CModelData{CStaticRes(staticId, head.x40_scale)};\n  }\n\n  if ((collisionExtent.x() < 0.f || collisionExtent.y() < 0.f || collisionExtent.z() < 0.f) || collisionExtent.isZero())\n    aabb = data.GetBounds(head.x10_transform.getRotation());\n\n  return new CScriptActor(mgr.AllocateUniqueId(), head.x0_name, info, head.x10_transform, std::move(data), aabb, mass,\n                          zMomentum, list, hInfo, dVuln, actParms, looping, active, shaderIdx, xrayAlpha, noThermalHotZ,\n                          castsShadow, scaleAdvancementDelta, materialFlag54);\n}\n\nCEntity* ScriptLoader::LoadWaypoint(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 13, \"Waypoint\"))\n    return nullptr;\n\n  SActorHead head = LoadActorHead(in, mgr);\n\n  bool active = in.ReadBool();\n  float speed = in.ReadFloat();\n  float pause = in.ReadFloat();\n  u32 patternTranslate = in.ReadLong();\n  u32 patternOrient = in.ReadLong();\n  u32 patternFit = in.ReadLong();\n  u32 behaviour = in.ReadLong();\n  u32 behaviourOrient = in.ReadLong();\n  u32 behaviourModifiers = in.ReadLong();\n  u32 animation = in.ReadLong();\n\n  return new CScriptWaypoint(mgr.AllocateUniqueId(), head.x0_name, info, head.x10_transform, active, speed, pause,\n                             patternTranslate, patternOrient, patternFit, behaviour, behaviourOrient,\n                             behaviourModifiers, animation);\n}\n\nCEntity* ScriptLoader::LoadDoor(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 13, \"Door\") || propCount > 14)\n    return nullptr;\n\n  SScaledActorHead head = LoadScaledActorHead(in, mgr);\n  CAnimationParameters aParms = LoadAnimationParameters(in);\n  CActorParameters actParms = LoadActorParameters(in);\n\n  zeus::CVector3f orbitPos = in.Get<zeus::CVector3f>();\n  zeus::CVector3f collisionExtent = in.Get<zeus::CVector3f>();\n  zeus::CVector3f offset = in.Get<zeus::CVector3f>();\n\n  bool active = in.ReadBool();\n  bool open = in.ReadBool();\n  bool projectilesCollide = in.ReadBool();\n  float animationLength = in.ReadFloat();\n\n  zeus::CAABox aabb = GetCollisionBox(mgr, info.GetAreaId(), collisionExtent, offset);\n\n  if (!g_ResFactory->GetResourceTypeById(aParms.GetACSFile()).IsValid())\n    return nullptr;\n\n  CModelData mData{CAnimRes(aParms.GetACSFile(), aParms.GetCharacter(), head.x40_scale, 0, false)};\n  if (collisionExtent.isZero())\n    aabb = mData.GetBounds(head.x10_transform.getRotation());\n\n  bool isMorphballDoor = false;\n  if (propCount == 13) {\n    if (aParms.GetACSFile() == MorphballDoorANCS.id)\n      isMorphballDoor = true;\n  } else if (propCount == 14)\n    isMorphballDoor = in.ReadBool();\n\n  return new CScriptDoor(mgr.AllocateUniqueId(), head.x0_name, info, head.x10_transform, std::move(mData), actParms,\n                         orbitPos, aabb, active, open, projectilesCollide, animationLength, isMorphballDoor);\n}\n\nCEntity* ScriptLoader::LoadTrigger(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 9, \"Trigger\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n\n  zeus::CVector3f position = in.Get<zeus::CVector3f>();\n\n  zeus::CVector3f extent = in.Get<zeus::CVector3f>();\n\n  CDamageInfo dInfo(in);\n\n  zeus::CVector3f forceVec = in.Get<zeus::CVector3f>();\n\n  ETriggerFlags flags = ETriggerFlags(in.ReadLong());\n  bool active = in.ReadBool();\n  bool b2 = in.ReadBool();\n  bool b3 = in.ReadBool();\n\n  zeus::CAABox box(-extent * 0.5f, extent * 0.5f);\n\n  const zeus::CTransform& areaXf = mgr.GetWorld()->GetGameAreas()[info.GetAreaId()]->GetTransform();\n  zeus::CVector3f orientedForce = areaXf.basis * forceVec;\n\n  return new CScriptTrigger(mgr.AllocateUniqueId(), name, info, position, box, dInfo, orientedForce, flags, active, b2,\n                            b3);\n}\n\nCEntity* ScriptLoader::LoadTimer(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 6, \"Timer\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n\n  float f1 = in.ReadFloat();\n  float f2 = in.ReadFloat();\n  bool b1 = in.ReadBool();\n  bool b2 = in.ReadBool();\n  bool b3 = in.ReadBool();\n\n  return new CScriptTimer(mgr.AllocateUniqueId(), name, info, f1, f2, b1, b2, b3);\n}\n\nCEntity* ScriptLoader::LoadCounter(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 5, \"Counter\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n\n  u32 initial = in.ReadLong();\n  u32 max = in.ReadLong();\n  bool autoReset = in.ReadBool();\n  bool active = in.ReadBool();\n\n  return new CScriptCounter(mgr.AllocateUniqueId(), name, info, initial, max, autoReset, active);\n}\n\nCEntity* ScriptLoader::LoadEffect(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 24, \"Effect\"))\n    return nullptr;\n\n  SScaledActorHead head = LoadScaledActorHead(in, mgr);\n\n  CAssetId partId(in);\n  CAssetId elscId(in);\n  bool hotInThermal = in.ReadBool();\n  bool noTimerUnlessAreaOccluded = in.ReadBool();\n  bool rebuildSystemsOnActivate = in.ReadBool();\n  bool active = in.ReadBool();\n\n  if (!partId.IsValid() && !elscId.IsValid())\n    return nullptr;\n\n  if (!g_ResFactory->GetResourceTypeById(partId).IsValid() && !g_ResFactory->GetResourceTypeById(elscId).IsValid())\n    return nullptr;\n\n  bool useRateInverseCamDist = in.ReadBool();\n  float rateInverseCamDist = in.ReadFloat();\n  float rateInverseCamDistRate = in.ReadFloat();\n  float duration = in.ReadFloat();\n  float durationResetWhileVisible = in.ReadFloat();\n  bool useRateCamDistRange = in.ReadBool();\n  float rateCamDistRangeMin = in.ReadFloat();\n  float rateCamDistRangeMax = in.ReadFloat();\n  float rateCamDistRangeFarRate = in.ReadFloat();\n  bool combatVisorVisible = in.ReadBool();\n  bool thermalVisorVisible = in.ReadBool();\n  bool xrayVisorVisible = in.ReadBool();\n  bool dieWhenSystemsDone = in.ReadBool();\n\n  CLightParameters lParms = LoadLightParameters(in);\n\n  return new CScriptEffect(mgr.AllocateUniqueId(), head.x0_name, info, head.x10_transform, head.x40_scale, partId,\n                           elscId, hotInThermal, noTimerUnlessAreaOccluded, rebuildSystemsOnActivate, active,\n                           useRateInverseCamDist, rateInverseCamDist, rateInverseCamDistRate, duration,\n                           durationResetWhileVisible, useRateCamDistRange, rateCamDistRangeMin, rateCamDistRangeMax,\n                           rateCamDistRangeFarRate, combatVisorVisible, thermalVisorVisible, xrayVisorVisible, lParms,\n                           dieWhenSystemsDone);\n}\n\nCEntity* ScriptLoader::LoadPlatform(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 19, \"Platform\"))\n    return nullptr;\n\n  SScaledActorHead head = LoadScaledActorHead(in, mgr);\n\n  zeus::CVector3f extent = in.Get<zeus::CVector3f>();\n\n  zeus::CVector3f centroid = in.Get<zeus::CVector3f>();\n\n  CAssetId staticId = in.Get<CAssetId>();\n  CAnimationParameters aParms = LoadAnimationParameters(in);\n\n  CActorParameters actParms = LoadActorParameters(in);\n\n  float speed = in.ReadFloat();\n  bool active = in.ReadBool();\n  CAssetId dclnId = in.Get<CAssetId>();\n\n  CHealthInfo hInfo(in);\n\n  CDamageVulnerability dInfo(in);\n\n  bool detectCollision = in.ReadBool();\n  float xrayAlpha = in.ReadFloat();\n  bool rainSplashes = in.ReadBool();\n  u32 maxRainSplashes = in.ReadLong();\n  u32 rainGenRate = in.ReadLong();\n\n  FourCC animType = g_ResFactory->GetResourceTypeById(aParms.GetACSFile());\n  if (!g_ResFactory->GetResourceTypeById(staticId).IsValid() && !animType.IsValid())\n    return nullptr;\n\n  zeus::CAABox aabb = GetCollisionBox(mgr, info.GetAreaId(), extent, centroid);\n\n  FourCC dclnType = g_ResFactory->GetResourceTypeById(dclnId);\n  std::optional<TLockedToken<CCollidableOBBTreeGroupContainer>> dclnToken;\n  if (dclnType.IsValid()) {\n    dclnToken.emplace(g_SimplePool->GetObj({SBIG('DCLN'), dclnId}));\n    dclnToken->GetObj();\n  }\n\n  CModelData data;\n  if (animType == SBIG('ANCS')) {\n    data = CModelData{\n        CAnimRes(aParms.GetACSFile(), aParms.GetCharacter(), head.x40_scale, aParms.GetInitialAnimation(), true)};\n  } else {\n    data = CModelData{CStaticRes(staticId, head.x40_scale)};\n  }\n\n  if (extent.isZero())\n    aabb = data.GetBounds(head.x10_transform.getRotation());\n\n  return new CScriptPlatform(mgr.AllocateUniqueId(), head.x0_name, info, head.x10_transform, std::move(data), actParms,\n                             aabb, speed, detectCollision, xrayAlpha, active, hInfo, dInfo, dclnToken, rainSplashes,\n                             maxRainSplashes, rainGenRate);\n}\n\nCEntity* ScriptLoader::LoadSound(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 20, \"Sound\"))\n    return nullptr;\n\n  SActorHead head = LoadActorHead(in, mgr);\n  s32 soundId = in.ReadInt32();\n  bool active = in.ReadBool();\n  float maxDist = in.ReadFloat();\n  float distComp = in.ReadFloat();\n  float startDelay = in.ReadFloat();\n  u32 minVol = in.ReadLong();\n  u32 vol = in.ReadLong();\n  u32 prio = in.ReadLong();\n  u32 pan = in.ReadLong();\n  bool loop = in.ReadBool();\n  bool nonEmitter = in.ReadBool();\n  bool autoStart = in.ReadBool();\n  bool occlusionTest = in.ReadBool();\n  bool acoustics = in.ReadBool();\n  bool worldSfx = in.ReadBool();\n  bool allowDuplicates = in.ReadBool();\n  s32 pitch = in.ReadInt32();\n\n  if (soundId < 0) {\n    spdlog::warn(\"Invalid sound ID specified in Sound {} ({}), dropping...\", head.x0_name, info.GetEditorId());\n    return nullptr;\n  }\n\n  return new CScriptSound(mgr.AllocateUniqueId(), head.x0_name, info, head.x10_transform, u16(soundId), active, maxDist,\n                          distComp, startDelay, minVol, vol, 0, prio, pan, 0, loop, nonEmitter, autoStart,\n                          occlusionTest, acoustics, worldSfx, allowDuplicates, pitch);\n}\n\nCEntity* ScriptLoader::LoadGenerator(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 8, \"Generator\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n\n  u32 spawnCount = in.ReadLong();\n  bool noReuseFollowers = in.ReadBool();\n  bool noInheritXf = in.ReadBool();\n\n  zeus::CVector3f offset = in.Get<zeus::CVector3f>();\n\n  bool active = in.ReadBool();\n  float minScale = in.ReadFloat();\n  float maxScale = in.ReadFloat();\n\n  return new CScriptGenerator(mgr.AllocateUniqueId(), name, info, spawnCount, noReuseFollowers, offset, noInheritXf,\n                              active, minScale, maxScale);\n}\n\nCEntity* ScriptLoader::LoadDock(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 7, \"Dock\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  bool active = in.ReadBool();\n  zeus::CVector3f position = in.Get<zeus::CVector3f>();\n  zeus::CVector3f scale = in.Get<zeus::CVector3f>();\n  u32 dock = in.ReadLong();\n  TAreaId area = in.ReadLong();\n  bool loadConnected = in.ReadBool();\n  return new CScriptDock(mgr.AllocateUniqueId(), name, info, position, scale, dock, area, active, 0, loadConnected);\n}\n\nCEntity* ScriptLoader::LoadCamera(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 14, \"Camera\"))\n    return nullptr;\n\n  SActorHead head = LoadActorHead(in, mgr);\n\n  bool active = in.ReadBool();\n  float shotDuration = in.ReadFloat();\n  bool lookAtPlayer = in.ReadBool();\n  bool outOfPlayerEye = in.ReadBool();\n  bool intoPlayerEye = in.ReadBool();\n  bool drawPlayer = in.ReadBool();\n  bool disableInput = in.ReadBool();\n  bool b7 = in.ReadBool();\n  bool finishCineSkip = in.ReadBool();\n  float fov = in.ReadFloat();\n  bool checkFailsafe = in.ReadBool();\n\n  bool disableOutOfInto = false;\n  if (propCount > 14)\n    disableOutOfInto = in.ReadBool();\n\n  s32 flags = s32(lookAtPlayer) | s32(outOfPlayerEye) << 1 | s32(intoPlayerEye) << 2 | s32(b7) << 3 |\n              s32(finishCineSkip) << 4 | s32(disableInput) << 5 | s32(drawPlayer) << 6 | s32(checkFailsafe) << 7 |\n              s32(disableOutOfInto) << 9;\n\n  return new CCinematicCamera(mgr.AllocateUniqueId(), head.x0_name, info, head.x10_transform, active, shotDuration,\n                              fov / CCameraManager::Aspect(), CCameraManager::NearPlane(), CCameraManager::FarPlane(),\n                              CCameraManager::Aspect(), flags);\n}\n\nCEntity* ScriptLoader::LoadCameraWaypoint(CStateManager& mgr, CInputStream& in, int propCount,\n                                          const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 6, \"CameraWaypoint\"))\n    return nullptr;\n\n  SActorHead head = LoadActorHead(in, mgr);\n\n  bool active = in.ReadBool();\n  float f1 = in.ReadFloat();\n  u32 w1 = in.ReadLong();\n\n  return new CScriptCameraWaypoint(mgr.AllocateUniqueId(), head.x0_name, info, head.x10_transform, active, f1, w1);\n}\n\nCEntity* ScriptLoader::LoadNewIntroBoss(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 13, \"NewIntroBoss\"))\n    return nullptr;\n\n  SScaledActorHead head = LoadScaledActorHead(in, mgr);\n\n  std::pair<bool, u32> pcount = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!pcount.first)\n    return nullptr;\n\n  CPatternedInfo pInfo(in, pcount.second);\n\n  CActorParameters actParms = LoadActorParameters(in);\n\n  float minTurnAngle = in.ReadFloat();\n  CAssetId projectile(in);\n\n  CDamageInfo dInfo(in);\n\n  CAssetId beamContactFxId(in);\n  CAssetId beamPulseFxId(in);\n  CAssetId beamTextureId(in);\n  CAssetId beamGlowTextureId(in);\n\n  const CAnimationParameters& aParms = pInfo.GetAnimationParameters();\n  FourCC animType = g_ResFactory->GetResourceTypeById(aParms.GetACSFile());\n  if (animType != SBIG('ANCS'))\n    return nullptr;\n\n  CAnimRes res(aParms.GetACSFile(), aParms.GetCharacter(), head.x40_scale, aParms.GetInitialAnimation(), true);\n\n  return new MP1::CNewIntroBoss(mgr.AllocateUniqueId(), head.x0_name, info, head.x10_transform, CModelData{res}, pInfo,\n                                actParms, minTurnAngle, projectile, dInfo, beamContactFxId, beamPulseFxId,\n                                beamTextureId, beamGlowTextureId);\n}\n\nCEntity* ScriptLoader::LoadSpawnPoint(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 35, \"SpawnPoint\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n\n  zeus::CVector3f position = in.Get<zeus::CVector3f>();\n\n  zeus::CVector3f rotation = in.Get<zeus::CVector3f>();\n\n  rstl::reserved_vector<u32, int(CPlayerState::EItemType::Max)> itemCounts;\n  itemCounts.resize(size_t(CPlayerState::EItemType::Max), 0);\n  for (int i = 0; i < propCount - 6; ++i)\n    itemCounts[i] = in.ReadLong();\n\n  bool defaultSpawn = in.ReadBool();\n  bool active = in.ReadBool();\n  bool morphed = false;\n  if (propCount > 34)\n    morphed = in.ReadBool();\n\n  return new CScriptSpawnPoint(mgr.AllocateUniqueId(), name, info, ConvertEditorEulerToTransform4f(rotation, position),\n                               itemCounts, defaultSpawn, active, morphed);\n}\n\nCEntity* ScriptLoader::LoadCameraHint(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (propCount > 25) {\n    spdlog::warn(\"Too many props ({} > 25) for CameraHint entity\", propCount);\n    return nullptr;\n  }\n\n  SActorHead head = LoadActorHead(in, mgr);\n\n  bool active = in.ReadBool();\n  u32 prio = in.ReadLong();\n  auto behaviour = CBallCamera::EBallCameraBehaviour(in.ReadLong());\n  u32 overrideFlags = LoadParameterFlags(in);\n  overrideFlags |= in.ReadBool() << 22;\n  float minDist = in.ReadFloat();\n  overrideFlags |= in.ReadBool() << 23;\n  float maxDist = in.ReadFloat();\n  overrideFlags |= in.ReadBool() << 24;\n  float backwardsDist = in.ReadFloat();\n  overrideFlags |= in.ReadBool() << 25;\n  zeus::CVector3f lookAtOffset = in.Get<zeus::CVector3f>();\n  overrideFlags |= in.ReadBool() << 26;\n  zeus::CVector3f chaseLookAtOffset = in.Get<zeus::CVector3f>();\n  zeus::CVector3f ballToCam = in.Get<zeus::CVector3f>();\n  overrideFlags |= in.ReadBool() << 27;\n  float fov = in.ReadFloat();\n  overrideFlags |= in.ReadBool() << 28;\n  float attitudeRange = zeus::degToRad(in.ReadFloat());\n  overrideFlags |= in.ReadBool() << 29;\n  float azimuthRange = zeus::degToRad(in.ReadFloat());\n  overrideFlags |= in.ReadBool() << 30;\n  float anglePerSecond = zeus::degToRad(in.ReadFloat());\n  float clampVelRange = in.ReadFloat();\n  float clampRotRange = zeus::degToRad(in.ReadFloat());\n  overrideFlags |= in.ReadBool() << 31;\n  float elevation = in.ReadFloat();\n  float clampVelTime = in.ReadFloat();\n  float interpolateTime = in.ReadFloat();\n  float controlInterpDur = in.ReadFloat();\n\n  return new CScriptCameraHint(mgr.AllocateUniqueId(), head.x0_name, info, head.x10_transform, active, prio, behaviour,\n                               overrideFlags, minDist, maxDist, backwardsDist, lookAtOffset, chaseLookAtOffset,\n                               ballToCam, fov, attitudeRange, azimuthRange, anglePerSecond, clampVelRange,\n                               clampRotRange, elevation, interpolateTime, clampVelTime, controlInterpDur);\n}\n\nCEntity* ScriptLoader::LoadPickup(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 18, \"Pickup\"))\n    return nullptr;\n\n  SScaledActorHead head = LoadScaledActorHead(in, mgr);\n  zeus::CVector3f extent =  in.Get<zeus::CVector3f>();\n  zeus::CVector3f offset =  in.Get<zeus::CVector3f>();\n  CPlayerState::EItemType itemType = CPlayerState::EItemType(in.ReadLong());\n  u32 capacity = in.ReadLong();\n  u32 amount = in.ReadLong();\n  float possibility = in.ReadFloat();\n  float lifeTime = in.ReadFloat();\n  float fadeInTime = in.ReadFloat();\n  CAssetId staticModel = in.Get<CAssetId>();\n  CAnimationParameters animParms = LoadAnimationParameters(in);\n  CActorParameters actorParms = LoadActorParameters(in);\n  bool active = in.ReadBool();\n  float startDelay = in.ReadFloat();\n  CAssetId pickupEffect(in);\n\n  FourCC acsType = g_ResFactory->GetResourceTypeById(animParms.GetACSFile());\n  if (g_ResFactory->GetResourceTypeById(staticModel) == 0 && acsType == 0)\n    return nullptr;\n\n  zeus::CAABox aabb = GetCollisionBox(mgr, info.GetAreaId(), extent, offset);\n\n  CModelData data;\n\n  if (acsType == SBIG('ANCS')) {\n    data = CModelData{CAnimRes(animParms.GetACSFile(), animParms.GetCharacter(), head.x40_scale,\n                               animParms.GetInitialAnimation(), true)};\n  } else {\n    data = CModelData{CStaticRes(staticModel, head.x40_scale)};\n  }\n\n  if (extent.isZero()) {\n    aabb = data.GetBounds(head.x10_transform.getRotation());\n  }\n\n  return new CScriptPickup(mgr.AllocateUniqueId(), head.x0_name, info, head.x10_transform, std::move(data), actorParms,\n                           aabb, itemType, amount, capacity, pickupEffect, possibility, lifeTime, fadeInTime,\n                           startDelay, active);\n}\n\nCEntity* ScriptLoader::LoadMemoryRelay(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 3, \"MemoryRelay\") || propCount > 4)\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  bool b1 = in.ReadBool();\n  bool b2 = in.ReadBool();\n  bool b3 = false;\n  if (propCount > 3)\n    b3 = in.ReadBool();\n\n  return new CScriptMemoryRelay(mgr.AllocateUniqueId(), name, info, b1, b2, b3);\n}\n\nCEntity* ScriptLoader::LoadRandomRelay(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 5, \"RandomRelay\"))\n    return nullptr;\n  std::string name = mgr.HashInstanceName(in);\n  u32 sendSetSize = in.ReadLong();\n  u32 sendSetVariance = in.ReadLong();\n  bool percentSize = in.ReadBool();\n  bool active = in.ReadBool();\n\n  return new CScriptRandomRelay(mgr.AllocateUniqueId(), name, info, sendSetSize, sendSetVariance, percentSize, active);\n}\n\nCEntity* ScriptLoader::LoadRelay(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 2, \"Relay\") || propCount > 3)\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  if (propCount >= 3)\n    in.ReadLong();\n  bool b1 = in.ReadBool();\n\n  return new CScriptRelay(mgr.AllocateUniqueId(), name, info, b1);\n}\n\nCEntity* ScriptLoader::LoadBeetle(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 16, \"Beetle\"))\n    return nullptr;\n  std::string name = mgr.HashInstanceName(in);\n  CPatterned::EFlavorType flavor = CPatterned::EFlavorType(in.ReadLong());\n  zeus::CTransform xfrm = LoadEditorTransform(in);\n  zeus::CVector3f scale =  in.Get<zeus::CVector3f>();\n  std::pair<bool, u32> pcount = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!pcount.first)\n    return nullptr;\n\n  CPatternedInfo pInfo(in, pcount.second);\n  CActorParameters aParams = LoadActorParameters(in);\n  CDamageInfo touchDamage(in);\n  zeus::CVector3f tailAimReference =  in.Get<zeus::CVector3f>();\n  float unused = in.ReadFloat();\n  CDamageVulnerability tailVuln(in);\n  CDamageVulnerability platingVuln(in);\n  CAssetId tailModel = in.Get<CAssetId>();\n  MP1::CBeetle::EEntranceType entranceType = MP1::CBeetle::EEntranceType(in.ReadLong());\n  float initialAttackDelay = in.ReadFloat();\n  float retreatTime = in.ReadFloat();\n\n  FourCC animType = g_ResFactory->GetResourceTypeById(pInfo.GetAnimationParameters().GetACSFile());\n  if (animType != SBIG('ANCS'))\n    return nullptr;\n\n  std::optional<CStaticRes> tailRes;\n  if (flavor == CPatterned::EFlavorType::One)\n    tailRes.emplace(CStaticRes(tailModel, scale));\n\n  const CAnimationParameters& animParams = pInfo.GetAnimationParameters();\n  CAnimRes animRes(animParams.GetACSFile(), animParams.GetCharacter(), scale, animParams.GetInitialAnimation(), true);\n\n  return new MP1::CBeetle(mgr.AllocateUniqueId(), name, info, xfrm, CModelData{animRes}, pInfo, flavor, entranceType,\n                          touchDamage, platingVuln, tailAimReference, initialAttackDelay, retreatTime, unused, tailVuln,\n                          aParams, tailRes);\n}\n\nCEntity* ScriptLoader::LoadHUDMemo(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (propCount != 5 && !EnsurePropertyCount(propCount, 6, \"HUDMemo\")) {\n    return nullptr;\n  }\n\n  const std::string name = mgr.HashInstanceName(in);\n  const CHUDMemoParms hParms(in);\n  auto displayType = CScriptHUDMemo::EDisplayType::MessageBox;\n  if (propCount == 6) {\n    displayType = CScriptHUDMemo::EDisplayType(in.ReadLong());\n  }\n  const CAssetId message = in.Get<CAssetId>();\n  const bool active = in.ReadBool();\n\n  return new CScriptHUDMemo(mgr.AllocateUniqueId(), name, info, hParms, displayType, message, active);\n}\n\nCEntity* ScriptLoader::LoadCameraFilterKeyframe(CStateManager& mgr, CInputStream& in, int propCount,\n                                                const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 10, \"CameraFilterKeyframe\"))\n    return nullptr;\n  std::string name = mgr.HashInstanceName(in);\n  bool active = in.ReadBool();\n  EFilterType ftype = EFilterType(in.ReadLong());\n  EFilterShape shape = EFilterShape(in.ReadLong());\n  u32 filterIdx = in.ReadLong();\n  u32 unk = in.ReadLong();\n  zeus::CColor color = in.Get<zeus::CColor>();\n  float timeIn = in.ReadFloat();\n  float timeOut = in.ReadFloat();\n  CAssetId txtr(in);\n\n  return new CScriptCameraFilterKeyframe(mgr.AllocateUniqueId(), name, info, ftype, shape, filterIdx, unk, color,\n                                         timeIn, timeOut, txtr, active);\n}\n\nCEntity* ScriptLoader::LoadCameraBlurKeyframe(CStateManager& mgr, CInputStream& in, int propCount,\n                                              const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 7, \"CameraBlurKeyframe\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  bool active = in.ReadBool();\n  EBlurType type = EBlurType(in.ReadLong());\n  float amount = in.ReadFloat();\n  u32 unk = in.ReadLong();\n  float timeIn = in.ReadFloat();\n  float timeOut = in.ReadFloat();\n\n  return new CScriptCameraBlurKeyframe(mgr.AllocateUniqueId(), name, info, type, amount, unk, timeIn, timeOut, active);\n}\n\nu32 ClassifyVector(const zeus::CVector3f& dir) {\n  zeus::CVector3f absDir(std::fabs(dir.x()), std::fabs(dir.y()), std::fabs(dir.z()));\n  u32 max = (absDir.x() > absDir.y() ? 0 : 1);\n  max = (absDir[max] > absDir.z() ? max : 2);\n\n  bool positive = (absDir[max] == dir[max]);\n  if (max == 0)\n    return (positive ? 0x08 : 0x04);\n  else if (max == 1)\n    return (positive ? 0x01 : 0x02);\n  else if (max == 2)\n    return (positive ? 0x10 : 0x20);\n\n  return 0;\n}\n\nu32 TransformDamagableTriggerFlags(CStateManager& mgr, TAreaId aId, u32 flags) {\n  CGameArea* area = mgr.GetWorld()->GetGameAreas().at(u32(aId)).get();\n  zeus::CTransform rotation = area->GetTransform().getRotation();\n\n  u32 ret = 0;\n  if (flags & 0x01)\n    ret |= ClassifyVector(rotation * zeus::skForward);\n  if (flags & 0x02)\n    ret |= ClassifyVector(rotation * zeus::skBack);\n  if (flags & 0x04)\n    ret |= ClassifyVector(rotation * zeus::skLeft);\n  if (flags & 0x08)\n    ret |= ClassifyVector(rotation * zeus::skRight);\n  if (flags & 0x10)\n    ret |= ClassifyVector(rotation * zeus::skUp);\n  if (flags & 0x20)\n    ret |= ClassifyVector(rotation * zeus::skDown);\n  return ret;\n}\n\nCEntity* ScriptLoader::LoadDamageableTrigger(CStateManager& mgr, CInputStream& in, int propCount,\n                                             const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 12, \"DamageableTrigger\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  zeus::CVector3f position( in.Get<zeus::CVector3f>());\n  zeus::CVector3f volume( in.Get<zeus::CVector3f>());\n\n  CHealthInfo hInfo(in);\n  CDamageVulnerability dVuln(in);\n  u32 triggerFlags = in.ReadLong();\n  triggerFlags = TransformDamagableTriggerFlags(mgr, info.GetAreaId(), triggerFlags);\n  CAssetId patternTex1 = in.Get<CAssetId>();\n  CAssetId patternTex2 = in.Get<CAssetId>();\n  CAssetId colorTex = in.Get<CAssetId>();\n  CScriptDamageableTrigger::ECanOrbit canOrbit = CScriptDamageableTrigger::ECanOrbit(in.ReadBool());\n  bool active = in.ReadBool();\n  CVisorParameters vParms = LoadVisorParameters(in);\n  return new CScriptDamageableTrigger(mgr.AllocateUniqueId(), name, info, position, volume, hInfo, dVuln, triggerFlags,\n                                      patternTex1, patternTex2, colorTex, canOrbit, active, vParms);\n}\n\nCEntity* ScriptLoader::LoadDebris(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 18, \"Debris\"))\n    return nullptr;\n\n  const SScaledActorHead head = LoadScaledActorHead(in, mgr);\n  const float zImpulse = in.ReadFloat();\n  const zeus::CVector3f velocity =  in.Get<zeus::CVector3f>();\n  zeus::CColor endsColor = in.Get<zeus::CColor>();\n  const float mass = in.ReadFloat();\n  const float restitution = in.ReadFloat();\n  const float duration = in.ReadFloat();\n  const auto scaleType = CScriptDebris::EScaleType(in.ReadLong());\n  const bool randomAngImpulse = in.ReadBool();\n  const CAssetId model = in.Get<CAssetId>();\n  const CActorParameters aParams = LoadActorParameters(in);\n  const CAssetId particleId = in.Get<CAssetId>();\n  const zeus::CVector3f particleScale =  in.Get<zeus::CVector3f>();\n  const bool b1 = in.ReadBool();\n  const bool active = in.ReadBool();\n\n  if (!g_ResFactory->GetResourceTypeById(model).IsValid()) {\n    return nullptr;\n  }\n\n  return new CScriptDebris(mgr.AllocateUniqueId(), head.x0_name, info, head.x10_transform,\n                           CModelData{CStaticRes(model, head.x40_scale)}, aParams, particleId, particleScale, zImpulse,\n                           velocity, endsColor, mass, restitution, duration, scaleType, b1, randomAngImpulse, active);\n}\n\nCEntity* ScriptLoader::LoadCameraShaker(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 9, \"CameraShaker\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  CCameraShakeData shakeData = CCameraShakeData::LoadCameraShakeData(in);\n  bool active = in.ReadBool();\n\n  return new CScriptCameraShaker(mgr.AllocateUniqueId(), name, info, active, shakeData);\n}\n\nCEntity* ScriptLoader::LoadActorKeyframe(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 7, \"ActorKeyframe\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  s32 animId = in.ReadInt32();\n  bool looping = in.ReadBool();\n  float lifetime = in.ReadFloat();\n  bool active = in.ReadBool();\n  u32 fadeOut = in.ReadLong();\n  float totalPlayback = in.ReadFloat();\n\n  if (animId == -1)\n    return nullptr;\n\n  return new CScriptActorKeyframe(mgr.AllocateUniqueId(), name, info, animId, looping, lifetime, false, fadeOut, active,\n                                  totalPlayback);\n}\n\nCEntity* ScriptLoader::LoadWater(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 63, \"Water\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  zeus::CVector3f position = in.Get<zeus::CVector3f>();\n  zeus::CVector3f extent = in.Get<zeus::CVector3f>();\n  CDamageInfo dInfo(in);\n  zeus::CVector3f orientedForce = in.Get<zeus::CVector3f>();\n  ETriggerFlags triggerFlags = ETriggerFlags(in.ReadLong()) | ETriggerFlags::DetectProjectiles1 |\n                               ETriggerFlags::DetectProjectiles2 | ETriggerFlags::DetectProjectiles3 |\n                               ETriggerFlags::DetectProjectiles4 | ETriggerFlags::DetectBombs |\n                               ETriggerFlags::DetectPowerBombs | ETriggerFlags::DetectProjectiles5 |\n                               ETriggerFlags::DetectProjectiles6 | ETriggerFlags::DetectProjectiles7;\n  bool thermalCold = in.ReadBool();\n  bool displaySurface = in.ReadBool();\n  CAssetId patternMap1 = in.Get<CAssetId>();\n  CAssetId patternMap2 = in.Get<CAssetId>();\n  CAssetId colorMap = in.Get<CAssetId>();\n  CAssetId bumpMap = in.Get<CAssetId>();\n  CAssetId _envMap = in.Get<CAssetId>();\n  CAssetId _envBumpMap = in.Get<CAssetId>();\n  zeus::CVector3f _bumpLightDir  =in.Get<zeus::CVector3f>();\n\n  zeus::CVector3f bumpLightDir = _bumpLightDir;\n  if (!bumpLightDir.canBeNormalized())\n    bumpLightDir.assign(0.f, 0.f, -1.f);\n\n  float bumpScale = 1.f / in.ReadFloat();\n  float morphInTime = in.ReadFloat();\n  float morphOutTime = in.ReadFloat();\n  bool active = in.ReadBool();\n  auto fluidType = EFluidType(in.ReadLong());\n  bool b4 = in.ReadBool();\n  float alpha = in.ReadFloat();\n  CFluidUVMotion uvMotion = LoadFluidUVMotion(in);\n\n  float turbSpeed = in.ReadFloat();\n  float turbDistance = in.ReadFloat();\n  float turbFreqMax = in.ReadFloat();\n  float turbFreqMin = in.ReadFloat();\n  float turbPhaseMax = zeus::degToRad(in.ReadFloat());\n  float turbPhaseMin = zeus::degToRad(in.ReadFloat());\n  float turbAmplitudeMax = in.ReadFloat();\n  float turbAmplitudeMin = in.ReadFloat();\n  zeus::CColor splashColor = in.Get<zeus::CColor>();\n  zeus::CColor insideFogColor = in.Get<zeus::CColor>();\n  CAssetId splashParticle1 = in.Get<CAssetId>();\n  CAssetId splashParticle2 = in.Get<CAssetId>();\n  CAssetId splashParticle3 = in.Get<CAssetId>();\n  CAssetId visorRunoffParticle = in.Get<CAssetId>();\n  CAssetId unmorphVisorRunoffParticle = in.Get<CAssetId>();\n  u32 visorRunoffSfx = in.ReadLong();\n  u32 unmorphVisorRunoffSfx = in.ReadLong();\n  u32 splashSfx1 = in.ReadLong();\n  u32 splashSfx2 = in.ReadLong();\n  u32 splashSfx3 = in.ReadLong();\n  float tileSize = in.ReadFloat();\n  u32 tileSubdivisions = in.ReadLong();\n  float specularMin = in.ReadFloat();\n  float specularMax = in.ReadFloat();\n  float reflectionSize = in.ReadFloat();\n  float rippleIntensity = in.ReadFloat();\n  float reflectionBlend = in.ReadFloat();\n  float fogBias = in.ReadFloat();\n  float fogMagnitude = in.ReadFloat();\n  float fogSpeed = in.ReadFloat();\n  zeus::CColor fogColor = in.Get<zeus::CColor>();\n  CAssetId lightmap = in.Get<CAssetId>();\n  float unitsPerLightmapTexel = in.ReadFloat();\n  float alphaInTime = in.ReadFloat();\n  float alphaOutTime = in.ReadFloat();\n  u32 w21 = in.ReadLong();\n  u32 w22 = in.ReadLong();\n  bool b5 = in.ReadBool();\n\n  std::unique_ptr<u32[]> bitset;\n  u32 bitVal0 = 0;\n  u32 bitVal1 = 0;\n\n  if (b5) {\n    bitVal0 = in.ReadShort();\n    bitVal1 = in.ReadShort();\n    u32 len = ((bitVal0 * bitVal1) + 31) / 31;\n    bitset.reset(new u32[len]);\n    in.Get(reinterpret_cast<u8*>(bitset.get()), len * 4);\n  }\n\n  zeus::CAABox box(-extent * 0.5f, extent * 0.5f);\n\n  CAssetId envBumpMap;\n  if (!bumpMap.IsValid())\n    envBumpMap = _envBumpMap;\n\n  CAssetId envMap;\n  if (!bumpMap.IsValid())\n    envMap = _envMap;\n\n  return new CScriptWater(mgr, mgr.AllocateUniqueId(), name, info, position, box, dInfo, orientedForce, triggerFlags,\n                          thermalCold, displaySurface, patternMap1, patternMap2, colorMap, bumpMap, envMap, envBumpMap,\n                          {}, bumpLightDir, bumpScale, morphInTime, morphOutTime, active, fluidType, b4, alpha,\n                          uvMotion, turbSpeed, turbDistance, turbFreqMax, turbFreqMin, turbPhaseMax, turbPhaseMin,\n                          turbAmplitudeMax, turbAmplitudeMin, splashColor, insideFogColor, splashParticle1,\n                          splashParticle2, splashParticle3, visorRunoffParticle, unmorphVisorRunoffParticle,\n                          visorRunoffSfx, unmorphVisorRunoffSfx, splashSfx1, splashSfx2, splashSfx3, tileSize,\n                          tileSubdivisions, specularMin, specularMax, reflectionSize, rippleIntensity, reflectionBlend,\n                          fogBias, fogMagnitude, fogSpeed, fogColor, lightmap, unitsPerLightmapTexel, alphaInTime,\n                          alphaOutTime, w21, w22, b5, bitVal0, bitVal1, std::move(bitset));\n}\n\nCEntity* ScriptLoader::LoadWarWasp(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 13, \"WarWasp\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  CPatterned::EFlavorType flavor = CPatterned::EFlavorType(in.ReadLong());\n  zeus::CTransform xf = LoadEditorTransformPivotOnly(in);\n  zeus::CVector3f scale = in.Get<zeus::CVector3f>();\n\n  std::pair<bool, u32> verifyPair = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!verifyPair.first)\n    return nullptr;\n\n  CPatternedInfo pInfo(in, verifyPair.second);\n  CActorParameters actorParms = LoadActorParameters(in);\n  CPatterned::EColliderType collider = CPatterned::EColliderType(in.ReadBool());\n  CDamageInfo damageInfo1(in);\n  CAssetId projectileWeapon = in.Get<CAssetId>();\n  CDamageInfo projectileDamage(in);\n  CAssetId projectileVisorParticle = in.Get<CAssetId>();\n  u32 projectileVisorSfx = in.ReadLong();\n\n  const CAnimationParameters& aParms = pInfo.GetAnimationParameters();\n  FourCC animType = g_ResFactory->GetResourceTypeById(aParms.GetACSFile());\n  if (animType != SBIG('ANCS'))\n    return nullptr;\n\n  CAnimRes res(aParms.GetACSFile(), aParms.GetCharacter(), scale, aParms.GetInitialAnimation(), true);\n  CModelData mData(res);\n  return new MP1::CWarWasp(mgr.AllocateUniqueId(), name, info, xf, std::move(mData), pInfo, flavor, collider,\n                           damageInfo1, actorParms, projectileWeapon, projectileDamage, projectileVisorParticle,\n                           projectileVisorSfx);\n}\n\nCEntity* ScriptLoader::LoadSpacePirate(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 34, \"SpacePirate\"))\n    return nullptr;\n  SScaledActorHead head = LoadScaledActorHead(in, mgr);\n  std::pair<bool, u32> verifyPair = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!verifyPair.first)\n    return nullptr;\n\n  CPatternedInfo pInfo(in, verifyPair.second);\n  CActorParameters aParams = LoadActorParameters(in);\n  CAnimationParameters& animParms = pInfo.GetAnimationParameters();\n\n  if (g_ResFactory->GetResourceTypeById(animParms.GetACSFile()) != SBIG('ANCS'))\n    return nullptr;\n\n  if (animParms.GetCharacter() == 0) {\n    spdlog::warn(\"SpacePirate <{}> has AnimationInformation property with invalid character selected\", head.x0_name);\n    animParms.SetCharacter(2);\n  }\n\n  CModelData mData(CAnimRes(animParms.GetACSFile(), animParms.GetCharacter(), head.x40_scale,\n                            animParms.GetInitialAnimation(), true));\n\n  return new MP1::CSpacePirate(mgr.AllocateUniqueId(), head.x0_name, info, head.x10_transform, std::move(mData),\n                               aParams, pInfo, in, propCount);\n}\n\nCEntity* ScriptLoader::LoadFlyingPirate(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 35, \"FlyingPirate\"))\n    return nullptr;\n\n  SScaledActorHead actHead = LoadScaledActorHead(in, mgr);\n  auto pair = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!pair.first)\n    return nullptr;\n\n  CPatternedInfo pInfo(in, pair.second);\n  CActorParameters actParms = LoadActorParameters(in);\n\n  if (g_ResFactory->GetResourceTypeById(pInfo.GetAnimationParameters().GetACSFile()) != SBIG('ANCS'))\n    return nullptr;\n\n  const CAnimationParameters& animParms = pInfo.GetAnimationParameters();\n  CModelData mData(CAnimRes(animParms.GetACSFile(), animParms.GetCharacter(), actHead.x40_scale,\n                            animParms.GetInitialAnimation(), true));\n\n  return new MP1::CFlyingPirate(mgr.AllocateUniqueId(), actHead.x0_name, info, actHead.x10_transform, std::move(mData),\n                                actParms, pInfo, in, propCount);\n}\n\nCEntity* ScriptLoader::LoadElitePirate(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, skElitePiratePropCount, \"ElitePirate\"))\n    return nullptr;\n\n  SScaledActorHead actHead = LoadScaledActorHead(in, mgr);\n  auto pair = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!pair.first)\n    return nullptr;\n\n  CPatternedInfo pInfo(in, pair.second);\n  CActorParameters actParms = LoadActorParameters(in);\n  MP1::CElitePirateData elitePirateData(in, propCount);\n\n  if (!pInfo.GetAnimationParameters().GetACSFile().IsValid())\n    return nullptr;\n\n  CModelData mData(CAnimRes(pInfo.GetAnimationParameters().GetACSFile(), pInfo.GetAnimationParameters().GetCharacter(),\n                            actHead.x40_scale, pInfo.GetAnimationParameters().GetInitialAnimation(), true));\n\n  return new MP1::CElitePirate(mgr.AllocateUniqueId(), actHead.x0_name, info, actHead.x10_transform, std::move(mData),\n                               pInfo, actParms, elitePirateData);\n}\n\nCEntity* ScriptLoader::LoadMetroidBeta(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 23, \"MetroidBeta\"))\n    return nullptr;\n\n#if 0\n  std::string name = mgr.HashInstanceName(in);\n  zeus::CTransform xf = LoadEditorTransform(in);\n  zeus::CVector3f scale =  in.Get<zeus::CVector3f>();\n  auto pair = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!pair.first)\n    return nullptr;\n\n  CPatternedInfo pInfo(in, pair.second);\n  CActorParameters actParms = LoadActorParameters(in);\n  MP1::CMetroidBetaData metData(in);\n  if (!pInfo.GetAnimationParameters().GetACSFile().IsValid())\n    return nullptr;\n\n  const CAnimationParameters& animParms = pInfo.GetAnimationParameters();\n  CModelData mData(\n      CAnimRes(animParms.GetACSFile(), animParms.GetCharacter(), scale, animParms.GetInitialAnimation(), true));\n\n  return new MP1::CMetroidBeta(mgr.AllocateUniqueId(), name, info, xf, std::move(mData), pInfo, actParms, metData);\n#else\n  return nullptr;\n#endif\n}\n\nCEntity* ScriptLoader::LoadChozoGhost(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 31, \"ChozoGhost\"))\n    return nullptr;\n\n  SScaledActorHead actorHead = LoadScaledActorHead(in, mgr);\n\n  auto pair = CPatternedInfo::HasCorrectParameterCount(in);\n\n  if (!pair.first)\n    return nullptr;\n\n  CPatternedInfo pInfo(in, pair.second);\n  CActorParameters actParms = LoadActorParameters(in);\n  float f1 = in.ReadFloat();\n  float f2 = in.ReadFloat();\n  float f3 = in.ReadFloat();\n  float f4 = in.ReadFloat();\n  CAssetId wpsc1(in);\n  CDamageInfo dInfo1(in);\n  CAssetId wpsc2(in);\n  CDamageInfo dInfo2(in);\n  MP1::CChozoGhost::CBehaveChance behaveChance1(in);\n  MP1::CChozoGhost::CBehaveChance behaveChance2(in);\n  MP1::CChozoGhost::CBehaveChance behaveChance3(in);\n  s16 sId1 = CSfxManager::TranslateSFXID(in.ReadLong());\n  float f5 = in.ReadFloat();\n  s16 sId2 = CSfxManager::TranslateSFXID(in.ReadLong());\n  s16 sId3 = CSfxManager::TranslateSFXID(in.ReadLong());\n  u32 w1 = in.ReadLong();\n  float f6 = in.ReadFloat();\n  u32 w2 = in.ReadLong();\n  float f7 = in.ReadFloat();\n  CAssetId partId(in);\n  s16 sId4 = CSfxManager::TranslateSFXID(in.ReadLong());\n  float f8 = in.ReadFloat();\n  float f9 = in.ReadFloat();\n  u32 w3 = in.ReadLong();\n  u32 w4 = in.ReadLong();\n\n  const CAnimationParameters& animParms = pInfo.GetAnimationParameters();\n  if (g_ResFactory->GetResourceTypeById(animParms.GetACSFile()) != SBIG('ANCS'))\n    return nullptr;\n\n  CModelData mData(CAnimRes(animParms.GetACSFile(), 0, actorHead.x40_scale, animParms.GetInitialAnimation(), true));\n\n  return new MP1::CChozoGhost(mgr.AllocateUniqueId(), actorHead.x0_name, info, actorHead.x10_transform,\n                              std::move(mData), actParms, pInfo, f1, f2, f3, f4, wpsc1, dInfo1, wpsc2, dInfo2,\n                              behaveChance1, behaveChance2, behaveChance3, sId1, f5, sId2, sId3, w1, f6, w2, f7, partId,\n                              sId4, f8, f9, w3, w4);\n}\n\nCEntity* ScriptLoader::LoadCoverPoint(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 9, \"CoverPoint\"))\n    return nullptr;\n\n  SActorHead head = LoadActorHead(in, mgr);\n  bool active = in.ReadBool();\n  u32 flags = in.ReadLong();\n  bool crouch = in.ReadBool();\n  float horizontalAngle = in.ReadFloat();\n  float verticalAngle = in.ReadFloat();\n  float coverTime = in.ReadFloat();\n\n  return new CScriptCoverPoint(mgr.AllocateUniqueId(), head.x0_name, info, head.x10_transform, active, flags, crouch,\n                               horizontalAngle, verticalAngle, coverTime);\n}\n\nCEntity* ScriptLoader::LoadSpiderBallWaypoint(CStateManager& mgr, CInputStream& in, int propCount,\n                                              const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 5, \"SpiderBallWaypoint\"))\n    return nullptr;\n\n  SActorHead actHead = LoadActorHead(in, mgr);\n  bool active = in.ReadBool();\n  u32 w1 = in.ReadLong();\n\n  return new CScriptSpiderBallWaypoint(mgr.AllocateUniqueId(), actHead.x0_name, info, actHead.x10_transform, active,\n                                       w1);\n}\n\nCEntity* ScriptLoader::LoadBloodFlower(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 18, \"BloodFlower\"))\n    return nullptr;\n\n  SScaledActorHead actHead = LoadScaledActorHead(in, mgr);\n  auto pair = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!pair.first)\n    return nullptr;\n\n  CPatternedInfo pInfo(in, pair.second);\n  CActorParameters actParms = LoadActorParameters(in);\n  CAssetId partId1(in);\n  CAssetId wpsc1(in);\n  CAssetId wpsc2(in);\n  CDamageInfo dInfo1(in);\n  CDamageInfo dInfo2(in);\n  CDamageInfo dInfo3(in);\n  CAssetId partId2(in);\n  CAssetId partId3(in);\n  CAssetId partId4(in);\n  float f1 = in.ReadFloat();\n  CAssetId partId5(in);\n  u32 soundId = in.ReadLong();\n\n  if (g_ResFactory->GetResourceTypeById(pInfo.GetAnimationParameters().GetACSFile()) != SBIG('ANCS'))\n    return nullptr;\n\n  CModelData mData(CAnimRes(pInfo.GetAnimationParameters().GetACSFile(), pInfo.GetAnimationParameters().GetCharacter(),\n                            actHead.x40_scale, pInfo.GetAnimationParameters().GetInitialAnimation(), true));\n\n  return new MP1::CBloodFlower(mgr.AllocateUniqueId(), actHead.x0_name, info, actHead.x10_transform, std::move(mData),\n                               pInfo, partId1, wpsc1, actParms, wpsc2, dInfo1, dInfo2, dInfo3, partId2, partId3,\n                               partId4, f1, partId5, soundId);\n}\n\nCEntity* ScriptLoader::LoadFlickerBat(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 10, \"FlickerBat\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  CPatterned::EFlavorType flavor = CPatterned::EFlavorType(in.ReadLong());\n  zeus::CTransform xf = LoadEditorTransform(in);\n  zeus::CVector3f scale =  in.Get<zeus::CVector3f>();\n  auto pair = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!pair.first)\n    return nullptr;\n\n  CPatternedInfo pInfo(in, pair.second);\n  CActorParameters actParms = LoadActorParameters(in);\n  bool collider = in.ReadBool();\n  bool excludePlayer = in.ReadBool();\n  bool enableLineOfSight = in.ReadBool();\n\n  if (g_ResFactory->GetResourceTypeById(pInfo.GetAnimationParameters().GetACSFile()) != SBIG('ANCS'))\n    return nullptr;\n\n  CModelData mData(CAnimRes(pInfo.GetAnimationParameters().GetACSFile(), pInfo.GetAnimationParameters().GetCharacter(),\n                            scale, pInfo.GetAnimationParameters().GetInitialAnimation(), true));\n  return new MP1::CFlickerBat(mgr.AllocateUniqueId(), name, flavor, info, xf, std::move(mData), pInfo,\n                              CPatterned::EColliderType(collider), excludePlayer, actParms, enableLineOfSight);\n}\n\nCEntity* ScriptLoader::LoadPathCamera(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (propCount > 15) {\n    spdlog::warn(\"Too many props ({} > 15) for PathCamera entity\", propCount);\n    return nullptr;\n  }\n\n  SActorHead aHead = LoadActorHead(in, mgr);\n  bool active = in.ReadBool();\n  u32 flags = LoadParameterFlags(in);\n  float lengthExtent = in.ReadFloat();\n  float filterMag = in.ReadFloat();\n  float filterProportion = in.ReadFloat();\n  CPathCamera::EInitialSplinePosition initPos = CPathCamera::EInitialSplinePosition(in.ReadLong());\n  float minEaseDist = in.ReadFloat();\n  float maxEaseDist = in.ReadFloat();\n\n  return new CPathCamera(mgr.AllocateUniqueId(), aHead.x0_name, info, aHead.x10_transform, active, lengthExtent,\n                         filterMag, filterProportion, minEaseDist, maxEaseDist, flags, initPos);\n}\n\nCEntity* ScriptLoader::LoadGrapplePoint(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 5, \"GrapplePoint\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  zeus::CTransform grappleXf = LoadEditorTransform(in);\n  bool active = in.ReadBool();\n  CGrappleParameters parameters = LoadGrappleParameters(in);\n  return new CScriptGrapplePoint(mgr.AllocateUniqueId(), name, info, grappleXf, active, parameters);\n}\n\nCEntity* ScriptLoader::LoadPuddleSpore(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 16, \"PuddleSpore\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  CPatterned::EFlavorType flavor = CPatterned::EFlavorType(in.ReadLong());\n  zeus::CTransform xf = LoadEditorTransform(in);\n  zeus::CVector3f scale =  in.Get<zeus::CVector3f>();\n\n  auto pair = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!pair.first)\n    return nullptr;\n\n  CPatternedInfo pInfo(in, pair.second);\n  CActorParameters actParms = LoadActorParameters(in);\n  bool b1 = in.ReadBool();\n  CAssetId w1(in);\n  float f1 = in.ReadFloat();\n  float f2 = in.ReadFloat();\n  float f3 = in.ReadFloat();\n  float f4 = in.ReadFloat();\n  float f5 = in.ReadFloat();\n  CAssetId w2(in);\n  CDamageInfo dInfo(in);\n\n  const CAnimationParameters& animParms = pInfo.GetAnimationParameters();\n  if (g_ResFactory->GetResourceTypeById(animParms.GetACSFile()) != SBIG('ANCS'))\n    return nullptr;\n\n  CModelData mData(\n      CAnimRes(animParms.GetACSFile(), animParms.GetCharacter(), scale, animParms.GetInitialAnimation(), true));\n  return new MP1::CPuddleSpore(mgr.AllocateUniqueId(), name, flavor, info, xf, std::move(mData), pInfo,\n                               CPatterned::EColliderType(b1), w1, f1, f2, f3, f4, f5, actParms, w2, dInfo);\n}\n\nCEntity* ScriptLoader::LoadDebugCameraWaypoint(CStateManager& mgr, CInputStream& in, int propCount,\n                                               const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 4, \"DebugCameraWaypoint\"))\n    return nullptr;\n\n  SActorHead actHead = LoadActorHead(in, mgr);\n  u32 w1 = in.ReadLong();\n  return new CScriptDebugCameraWaypoint(mgr.AllocateUniqueId(), actHead.x0_name, info, actHead.x10_transform, w1);\n}\n\nCEntity* ScriptLoader::LoadSpiderBallAttractionSurface(CStateManager& mgr, CInputStream& in, int propCount,\n                                                       const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 5, \"SpiderBallAttractionSurface\"))\n    return nullptr;\n  SScaledActorHead aHead = LoadScaledActorHead(in, mgr);\n  bool active = in.ReadBool();\n  return new CScriptSpiderBallAttractionSurface(mgr.AllocateUniqueId(), aHead.x0_name, info, aHead.x10_transform,\n                                                aHead.x40_scale, active);\n}\n\nCEntity* ScriptLoader::LoadPuddleToadGamma(CStateManager& mgr, CInputStream& in, int propCount,\n                                           const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 17, \"PuddleToadGamma\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  CPatterned::EFlavorType flavor = CPatterned::EFlavorType(in.ReadLong());\n  zeus::CTransform xf = LoadEditorTransform(in);\n  zeus::CVector3f scale =  in.Get<zeus::CVector3f>();\n  auto pair = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!pair.first)\n    return nullptr;\n\n  CPatternedInfo pInfo(in, pair.second);\n  CActorParameters actParms = LoadActorParameters(in);\n  float suckForceMultiplier = in.ReadFloat();\n  float suckAngle = in.ReadFloat();\n  float playerSuckRange = in.ReadFloat();\n  zeus::CVector3f localShootDir =  in.Get<zeus::CVector3f>();\n  float playerShootSpeed = in.ReadFloat();\n  float shouldAttackWaitTime = in.ReadFloat();\n  float spotPlayerWaitTime = in.ReadFloat();\n  CDamageInfo playerShootDamage(in);\n  CDamageInfo dInfo2(in);\n  CAssetId collisionData(in);\n  const CAnimationParameters& animParms = pInfo.GetAnimationParameters();\n  CModelData mData(\n      CAnimRes(animParms.GetACSFile(), animParms.GetCharacter(), scale, animParms.GetInitialAnimation(), true));\n  return new MP1::CPuddleToadGamma(mgr.AllocateUniqueId(), name, flavor, info, xf, std::move(mData), pInfo, actParms,\n                                   suckForceMultiplier, suckAngle, playerSuckRange, localShootDir, playerShootSpeed,\n                                   shouldAttackWaitTime, spotPlayerWaitTime, playerShootDamage, dInfo2, collisionData);\n}\n\nCEntity* ScriptLoader::LoadDistanceFog(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 8, \"DistanceFog\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  u32 mode = in.ReadLong();\n  zeus::CColor color = in.Get<zeus::CColor>();\n  zeus::CVector2f range = in.Get<zeus::CVector2f>();\n  float colorDelta = in.ReadFloat();\n  zeus::CVector2f rangeDelta = in.Get<zeus::CVector2f>();\n  bool expl = in.ReadBool();\n  bool active = in.ReadBool();\n  ERglFogMode realMode = ERglFogMode::None;\n\n  if (mode == 0)\n    realMode = ERglFogMode::None;\n  else if (mode == 1)\n    realMode = ERglFogMode::PerspLin;\n  else if (mode == 2)\n    realMode = ERglFogMode::PerspExp;\n  else if (mode == 3)\n    realMode = ERglFogMode::PerspExp2;\n  else if (mode == 4)\n    realMode = ERglFogMode::PerspRevExp;\n  else if (mode == 5)\n    realMode = ERglFogMode::PerspRevExp2;\n\n  return new CScriptDistanceFog(mgr.AllocateUniqueId(), name, info, realMode, color, range, colorDelta, rangeDelta,\n                                expl, active, 0.f, 0.f, 0.f, 0.f);\n}\n\nCEntity* ScriptLoader::LoadFireFlea(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 9, \"FireFlea\"))\n    return nullptr;\n\n  SScaledActorHead acthead = LoadScaledActorHead(in, mgr);\n  auto pair = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!pair.first)\n    return nullptr;\n\n  CPatternedInfo pInfo(in, pair.second);\n  CActorParameters actParms = LoadActorParameters(in);\n  in.ReadBool();\n  in.ReadBool();\n  float f1 = in.ReadFloat();\n\n  if (!pInfo.GetAnimationParameters().GetACSFile().IsValid())\n    return nullptr;\n\n  CModelData mData(CAnimRes(pInfo.GetAnimationParameters().GetACSFile(), pInfo.GetAnimationParameters().GetCharacter(),\n                            acthead.x40_scale, pInfo.GetAnimationParameters().GetInitialAnimation(), true));\n\n  return new MP1::CFireFlea(mgr.AllocateUniqueId(), acthead.x0_name, info, acthead.x10_transform, std::move(mData),\n                            actParms, pInfo, f1);\n}\n\nCEntity* ScriptLoader::LoadMetaree(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 12, \"Metaree\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  zeus::CTransform xf = LoadEditorTransform(in);\n  zeus::CVector3f scale =  in.Get<zeus::CVector3f>();\n\n  auto pair = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!pair.first)\n    return nullptr;\n\n  CPatternedInfo pInfo(in, pair.second);\n  CActorParameters aParms = LoadActorParameters(in);\n  CDamageInfo dInfo(in);\n  float f1 = in.ReadFloat();\n  zeus::CVector3f vec =  in.Get<zeus::CVector3f>();\n  float f2 = in.ReadFloat();\n  float f3 = in.ReadFloat();\n\n  if (g_ResFactory->GetResourceTypeById(pInfo.GetAnimationParameters().GetACSFile()) != SBIG('ANCS'))\n    return nullptr;\n\n  float f4 = in.ReadFloat();\n\n  CModelData mData(CAnimRes(pInfo.GetAnimationParameters().GetACSFile(), pInfo.GetAnimationParameters().GetCharacter(),\n                            scale, pInfo.GetAnimationParameters().GetInitialAnimation(), true));\n  return new MP1::CMetaree(mgr.AllocateUniqueId(), name, CPatterned::EFlavorType::Zero, info, xf, std::move(mData),\n                           pInfo, dInfo, f1, vec, f2, EBodyType::Invalid, f3, f4, aParms);\n}\n\nCEntity* ScriptLoader::LoadDockAreaChange(CStateManager& mgr, CInputStream& in, int propCount,\n                                          const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 3, \"DockAreaChange\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  s32 w1 = in.ReadInt32();\n  bool active = in.ReadBool();\n\n  return new CScriptDockAreaChange(mgr.AllocateUniqueId(), name, info, w1, active);\n}\n\nCEntity* ScriptLoader::LoadActorRotate(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 6, \"ActorRotate\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  zeus::CVector3f rotation =  in.Get<zeus::CVector3f>();\n  float scale = in.ReadFloat();\n  bool updateActors = in.ReadBool();\n  bool updateOnCreation = in.ReadBool();\n  bool active = in.ReadBool();\n\n  return new CScriptActorRotate(mgr.AllocateUniqueId(), name, info, rotation, scale, updateActors, updateOnCreation,\n                                active);\n}\n\nCEntity* ScriptLoader::LoadSpecialFunction(CStateManager& mgr, CInputStream& in, int propCount,\n                                           const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 15, \"SpecialFunction\"))\n    return nullptr;\n\n  SActorHead head = LoadActorHead(in, mgr);\n  CScriptSpecialFunction::ESpecialFunction specialFunction =\n      CScriptSpecialFunction::ESpecialFunction(in.ReadLong());\n  std::string str = in.Get<std::string>();\n  float f1 = in.ReadFloat();\n  float f2 = in.ReadFloat();\n  float f3 = in.ReadFloat();\n  u32 w2 = in.ReadLong();\n  u32 w3 = in.ReadLong();\n  CPlayerState::EItemType w4 = CPlayerState::EItemType(in.ReadLong());\n  bool active1 = in.ReadBool();\n  float f4 = in.ReadFloat();\n  s16 w5 = in.ReadLong() & 0xFFFF;\n  s16 w6 = in.ReadLong() & 0xFFFF;\n  s16 w7 = in.ReadLong() & 0xFFFF;\n  if (specialFunction == CScriptSpecialFunction::ESpecialFunction::FogVolume ||\n      specialFunction == CScriptSpecialFunction::ESpecialFunction::RadialDamage)\n    return nullptr;\n\n  return new CScriptSpecialFunction(mgr.AllocateUniqueId(), head.x0_name, info, head.x10_transform, specialFunction,\n                                    str, f1, f2, f3, f4, zeus::skZero3f, zeus::skBlack, active1, CDamageInfo(), w2, w3,\n                                    w4, w5, w6, w7);\n}\n\nCEntity* ScriptLoader::LoadSpankWeed(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 11, \"SpankWeed\"))\n    return nullptr;\n  SScaledActorHead aHead = LoadScaledActorHead(in, mgr);\n  auto pair = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!pair.first)\n    return nullptr;\n\n  CPatternedInfo pInfo(in, pair.second);\n  CActorParameters actParms = LoadActorParameters(in);\n  in.ReadBool();\n  float maxDetectionRange = in.ReadFloat();\n  float maxHearingRange = in.ReadFloat();\n  float maxSightRange = in.ReadFloat();\n  float hideTime = in.ReadFloat();\n\n  const CAnimationParameters& animParms = pInfo.GetAnimationParameters();\n  if (!animParms.GetACSFile().IsValid())\n    return nullptr;\n\n  CModelData mData(CAnimRes(animParms.GetACSFile(), animParms.GetCharacter(), aHead.x40_scale,\n                            animParms.GetInitialAnimation(), true));\n  return new MP1::CSpankWeed(mgr.AllocateUniqueId(), aHead.x0_name, info, aHead.x10_transform, std::move(mData),\n                             actParms, pInfo, maxDetectionRange, maxHearingRange, maxSightRange, hideTime);\n}\n\nCEntity* ScriptLoader::LoadParasite(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 25, \"Parasite\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  CPatterned::EFlavorType flavor = CPatterned::EFlavorType(in.ReadLong());\n  zeus::CTransform xf = LoadEditorTransform(in);\n  zeus::CVector3f scale =  in.Get<zeus::CVector3f>();\n\n  std::pair<bool, u32> pcount = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!pcount.first)\n    return nullptr;\n\n  CPatternedInfo pInfo(in, pcount.second);\n  CActorParameters aParms = LoadActorParameters(in);\n  float maxTelegraphReactDist = in.ReadFloat();\n  float advanceWpRadius = in.ReadFloat();\n  float f3 = in.ReadFloat();\n  float alignAngVel = in.ReadFloat();\n  float f5 = in.ReadFloat();\n  float stuckTimeThreshold = in.ReadFloat();\n  float collisionCloseMargin = in.ReadFloat();\n  float parasiteSearchRadius = in.ReadFloat();\n  float parasiteSeparationDist = in.ReadFloat();\n  float parasiteSeparationWeight = in.ReadFloat();\n  float parasiteAlignmentWeight = in.ReadFloat();\n  float parasiteCohesionWeight = in.ReadFloat();\n  float destinationSeekWeight = in.ReadFloat();\n  float forwardMoveWeight = in.ReadFloat();\n  float playerSeparationDist = in.ReadFloat();\n  float playerSeparationWeight = in.ReadFloat();\n  float playerObstructionMinDist = in.ReadFloat();\n  bool disableMove = in.ReadBool();\n\n  if (g_ResFactory->GetResourceTypeById(pInfo.GetAnimationParameters().GetACSFile()) != SBIG('ANCS'))\n    return nullptr;\n  const CAnimationParameters& animParms = pInfo.GetAnimationParameters();\n  CModelData mData(\n      CAnimRes(animParms.GetACSFile(), animParms.GetCharacter(), scale, animParms.GetInitialAnimation(), true));\n  return new MP1::CParasite(mgr.AllocateUniqueId(), name, flavor, info, xf, std::move(mData), pInfo,\n                            EBodyType::WallWalker, maxTelegraphReactDist, advanceWpRadius, f3, alignAngVel, f5,\n                            stuckTimeThreshold, collisionCloseMargin, parasiteSearchRadius, parasiteSeparationDist,\n                            parasiteSeparationWeight, parasiteAlignmentWeight, parasiteCohesionWeight,\n                            destinationSeekWeight, forwardMoveWeight, playerSeparationDist, playerSeparationWeight,\n                            playerObstructionMinDist, 0.f, disableMove, CWallWalker::EWalkerType::Parasite,\n                            CDamageVulnerability::NormalVulnerabilty(), CDamageInfo(), -1, -1, -1, {}, {}, 0.f, aParms);\n}\n\nCEntity* ScriptLoader::LoadPlayerHint(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 6, \"PlayerHint\"))\n    return nullptr;\n\n  SActorHead aHead = LoadActorHead(in, mgr);\n  bool active = in.ReadBool();\n  u32 overrideFlags = LoadParameterFlags(in);\n  u32 priority = in.ReadLong();\n\n  return new CScriptPlayerHint(mgr.AllocateUniqueId(), aHead.x0_name, info, aHead.x10_transform, active, priority,\n                               overrideFlags);\n}\n\nCEntity* ScriptLoader::LoadRipper(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 8, \"Ripper\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  CPatterned::EFlavorType type = CPatterned::EFlavorType(in.ReadLong());\n  zeus::CTransform xf = LoadEditorTransform(in);\n  zeus::CVector3f scale =  in.Get<zeus::CVector3f>();\n  auto pair = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!pair.first)\n    return nullptr;\n  CPatternedInfo pInfo(in, pair.second);\n  CActorParameters actParms = LoadActorParameters(in);\n  CGrappleParameters grappleParms = LoadGrappleParameters(in);\n\n  const CAnimationParameters& animParms = pInfo.GetAnimationParameters();\n\n  if (!animParms.GetACSFile().IsValid())\n    return nullptr;\n\n  CModelData mData(\n      CAnimRes(animParms.GetACSFile(), animParms.GetCharacter(), scale, animParms.GetInitialAnimation(), true));\n  return new MP1::CRipper(mgr.AllocateUniqueId(), name, type, info, xf, std::move(mData), pInfo, actParms,\n                          grappleParms);\n}\n\nCEntity* ScriptLoader::LoadPickupGenerator(CStateManager& mgr, CInputStream& in, int propCount,\n                                           const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 4, \"PickupGenerator\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  zeus::CVector3f pos =  in.Get<zeus::CVector3f>();\n  bool active = in.ReadBool();\n  float f1 = in.ReadFloat();\n  return new CScriptPickupGenerator(mgr.AllocateUniqueId(), name, info, pos, f1, active);\n}\n\nCEntity* ScriptLoader::LoadAIKeyframe(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (CScriptActorKeyframe* kf = static_cast<CScriptActorKeyframe*>(LoadActorKeyframe(mgr, in, propCount, info))) {\n    kf->SetIsPassive(true);\n    return kf;\n  }\n  return nullptr;\n}\n\nCEntity* ScriptLoader::LoadPointOfInterest(CStateManager& mgr, CInputStream& in, int propCount,\n                                           const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 6, \"PointOfInterest\"))\n    return nullptr;\n\n  SActorHead aHead = LoadActorHead(in, mgr);\n  bool active = in.ReadBool();\n  CScannableParameters sParms = LoadScannableParameters(in);\n  float pointSize = in.ReadFloat();\n\n  return new CScriptPointOfInterest(mgr.AllocateUniqueId(), aHead.x0_name, info, aHead.x10_transform, active, sParms,\n                                    pointSize);\n}\n\nstd::optional<CVisorFlare::CFlareDef> LoadFlareDef(CInputStream& in) {\n  if (in.ReadLong() == 4) {\n    CAssetId textureId(in);\n    float f1 = in.ReadFloat();\n    float f2 = in.ReadFloat();\n    zeus::CColor color = in.Get<zeus::CColor>();\n    if (textureId.IsValid()) {\n      return {CVisorFlare::CFlareDef(g_SimplePool->GetObj({SBIG('TXTR'), textureId}), f1, f2, color)};\n    }\n  }\n\n  return {};\n}\n\nCEntity* ScriptLoader::LoadDrone(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (propCount != 45 && EnsurePropertyCount(propCount, 45, \"Drone\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  CPatterned::EFlavorType flavor = CPatterned::EFlavorType(in.ReadLong());\n  zeus::CTransform xf = LoadEditorTransform(in);\n  zeus::CVector3f scale =  in.Get<zeus::CVector3f>();\n  float f1 = in.ReadFloat();\n  const auto [patternedValid, patternedPropCount] = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!patternedValid)\n    return nullptr;\n  CPatternedInfo pInfo(in, patternedPropCount);\n  CActorParameters actParms = LoadActorParameters(in);\n  CDamageInfo dInfo1(in);\n  u32 w1 = in.ReadLong();\n  CDamageInfo dInfo2(in);\n  CAssetId aId1(in);\n  in.ReadLong();\n  CAssetId aId2(in);\n  std::optional<CVisorFlare::CFlareDef> def1 = LoadFlareDef(in);\n  std::optional<CVisorFlare::CFlareDef> def2 = LoadFlareDef(in);\n  std::optional<CVisorFlare::CFlareDef> def3 = LoadFlareDef(in);\n  std::optional<CVisorFlare::CFlareDef> def4 = LoadFlareDef(in);\n  std::optional<CVisorFlare::CFlareDef> def5 = LoadFlareDef(in);\n  std::vector<CVisorFlare::CFlareDef> flares(5);\n  if (def1)\n    flares.push_back(*def1);\n  if (def2)\n    flares.push_back(*def2);\n  if (def3)\n    flares.push_back(*def3);\n  if (def4)\n    flares.push_back(*def4);\n  if (def4)\n    flares.push_back(*def4);\n\n  const auto& animParms = pInfo.GetAnimationParameters();\n  if (g_ResFactory->GetResourceTypeById(animParms.GetACSFile()) != SBIG('ANCS'))\n    return nullptr;\n\n  float f2 = in.ReadFloat();\n  float f3 = in.ReadFloat();\n  float f4 = in.ReadFloat();\n  float f5 = in.ReadFloat();\n  float f6 = in.ReadFloat();\n  float f7 = in.ReadFloat();\n  float f8 = in.ReadFloat();\n  float f9 = in.ReadFloat();\n  float f10 = in.ReadFloat();\n  float f11 = in.ReadFloat();\n  float f12 = in.ReadFloat();\n  float f13 = in.ReadFloat();\n  float f14 = in.ReadFloat();\n  float f15 = in.ReadFloat();\n  float f16 = in.ReadFloat();\n  float f17 = in.ReadFloat();\n  float f18 = in.ReadFloat();\n  float f19 = in.ReadFloat();\n  float f20 = in.ReadFloat();\n  CAssetId crscId(in);\n  float f21 = in.ReadFloat();\n  float f22 = in.ReadFloat();\n  float f23 = in.ReadFloat();\n  float f24 = in.ReadFloat();\n  s32 soundId = in.ReadLong();\n  bool b1 = in.ReadBool();\n  CModelData mData(\n      CAnimRes(animParms.GetACSFile(), animParms.GetCharacter(), scale, animParms.GetInitialAnimation(), true));\n  return new MP1::CDrone(mgr.AllocateUniqueId(), name, flavor, info, xf, f1, std::move(mData), pInfo, actParms,\n                         CPatterned::EMovementType::Flyer, CPatterned::EColliderType::One, EBodyType::Pitchable, dInfo1,\n                         aId1, dInfo2, aId2, flares, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, f16,\n                         f17, f18, f19, f20, crscId, f21, f22, f23, f24, soundId, b1);\n}\n\nCEntity* ScriptLoader::LoadMetroid(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, MP1::CMetroidData::GetNumProperties(), \"Metroid\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  CPatterned::EFlavorType flavor = CPatterned::EFlavorType(in.ReadLong());\n  zeus::CTransform xf = LoadEditorTransform(in);\n  zeus::CVector3f scale =  in.Get<zeus::CVector3f>();\n  auto pair = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!pair.first)\n    return nullptr;\n\n  CPatternedInfo pInfo(in, pair.second);\n  CActorParameters actParms = LoadActorParameters(in);\n  MP1::CMetroidData metData(in);\n  const CAnimationParameters& animParms = pInfo.GetAnimationParameters();\n  if (!animParms.GetACSFile().IsValid() || flavor == CPatterned::EFlavorType::One)\n    return nullptr;\n\n  CModelData mData(\n      CAnimRes(animParms.GetACSFile(), animParms.GetCharacter(), scale, animParms.GetInitialAnimation(), true));\n  return new MP1::CMetroid(mgr.AllocateUniqueId(), name, flavor, info, xf, std::move(mData), pInfo, actParms, metData,\n                           kInvalidUniqueId);\n}\n\nCEntity* ScriptLoader::LoadDebrisExtended(CStateManager& mgr, CInputStream& in, int propCount,\n                                          const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 39, \"DebrisExtended\"))\n    return nullptr;\n\n  SScaledActorHead aHead = LoadScaledActorHead(in, mgr);\n\n  float linConeAngle = in.ReadFloat();\n  float linMinMag = in.ReadFloat();\n  float linMaxMag = in.ReadFloat();\n  float angMinMag = in.ReadFloat();\n  float angMaxMag = in.ReadFloat();\n  float minDuration = in.ReadFloat();\n  float maxDuration = in.ReadFloat();\n  float colorInT = in.ReadFloat();\n  float colorOutT = in.ReadFloat();\n\n  zeus::CColor color = in.Get<zeus::CColor>();\n  zeus::CColor endsColor = in.Get<zeus::CColor>();\n\n  float scaleOutT = in.ReadFloat();\n\n  zeus::CVector3f endScale =  in.Get<zeus::CVector3f>();\n\n  float restitution = in.ReadFloat();\n  float downwardSpeed = in.ReadFloat();\n\n  zeus::CVector3f localOffset =  in.Get<zeus::CVector3f>();\n\n  CAssetId model = in.Get<CAssetId>();\n\n  CActorParameters aParam = LoadActorParameters(in);\n\n  CAssetId particle1 = in.Get<CAssetId>();\n  zeus::CVector3f particle1Scale =  in.Get<zeus::CVector3f>();\n  bool particle1GlobalTranslation = in.ReadBool();\n  bool deferDeleteTillParticle1Done = in.ReadBool();\n  CScriptDebris::EOrientationType particle1Or = CScriptDebris::EOrientationType(in.ReadLong());\n\n  CAssetId particle2 = in.Get<CAssetId>();\n  zeus::CVector3f particle2Scale =  in.Get<zeus::CVector3f>();\n  bool particle2GlobalTranslation = in.ReadBool();\n  bool deferDeleteTillParticle2Done = in.ReadBool();\n  CScriptDebris::EOrientationType particle2Or = CScriptDebris::EOrientationType(in.ReadLong());\n\n  CAssetId particle3 = in.Get<CAssetId>();\n  /*zeus::CVector3f particle3Scale =*/(void) in.Get<zeus::CVector3f>(); /* Not actually used, go figure */\n  CScriptDebris::EOrientationType particle3Or = CScriptDebris::EOrientationType(in.ReadLong());\n\n  bool solid = in.ReadBool();\n  bool dieOnProjectile = in.ReadBool();\n  bool noBounce = in.ReadBool();\n  bool active = in.ReadBool();\n\n  CModelData modelData;\n  if (g_ResFactory->GetResourceTypeById(model).IsValid())\n    modelData = CModelData(CStaticRes(model, aHead.x40_scale));\n\n  return new CScriptDebris(mgr.AllocateUniqueId(), aHead.x0_name, info, aHead.x10_transform, std::move(modelData),\n                           aParam, linConeAngle, linMinMag, linMaxMag, angMinMag, angMaxMag, minDuration, maxDuration,\n                           colorInT, colorOutT, color, endsColor, scaleOutT, aHead.x40_scale, endScale, restitution,\n                           downwardSpeed, localOffset, particle1, particle1Scale, particle1GlobalTranslation,\n                           deferDeleteTillParticle1Done, particle1Or, particle2, particle2Scale,\n                           particle2GlobalTranslation, deferDeleteTillParticle2Done, particle2Or, particle3,\n                           particle2Scale, particle3Or, solid, dieOnProjectile, noBounce, active);\n}\n\nCEntity* ScriptLoader::LoadSteam(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 11, \"Steam\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n\n  zeus::CVector3f v1 =  in.Get<zeus::CVector3f>();\n  zeus::CVector3f v2 =  in.Get<zeus::CVector3f>();\n\n  CDamageInfo dInfo(in);\n\n  zeus::CVector3f v3 =  in.Get<zeus::CVector3f>();\n\n  ETriggerFlags w1 = ETriggerFlags(in.ReadLong());\n  bool b1 = in.ReadBool();\n  u32 w2 = in.ReadLong();\n  float f1 = in.ReadFloat();\n  float f2 = in.ReadFloat();\n  float f3 = in.ReadFloat();\n  float f4 = in.ReadFloat();\n  bool b2 = in.ReadBool();\n\n  zeus::CAABox aabb(-v2 * 0.5f, v2 * 0.5f);\n\n  return new CScriptSteam(mgr.AllocateUniqueId(), name, info, v1, aabb, dInfo, v3, w1, b1, w2, f1, f2, f3, f4, b2);\n}\n\nCEntity* ScriptLoader::LoadRipple(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 4, \"Ripple\"))\n    return nullptr;\n  std::string name = mgr.HashInstanceName(in);\n  zeus::CVector3f center =  in.Get<zeus::CVector3f>();\n  bool active = in.ReadBool();\n  float mag = in.ReadFloat();\n  return new CScriptRipple(mgr.AllocateUniqueId(), name, info, center, active, mag);\n}\n\nCEntity* ScriptLoader::LoadBallTrigger(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 9, \"BallTrigger\"))\n    return nullptr;\n\n  std::string name = in.Get<std::string>();\n  zeus::CVector3f pos =  in.Get<zeus::CVector3f>();\n  zeus::CVector3f scale =  in.Get<zeus::CVector3f>();\n\n  bool b1 = in.ReadBool();\n  float f1 = in.ReadFloat();\n  float f2 = in.ReadFloat();\n  float f3 = in.ReadFloat();\n  zeus::CVector3f vec =  in.Get<zeus::CVector3f>();\n  bool b2 = in.ReadBool();\n  return new CScriptBallTrigger(mgr.AllocateUniqueId(), name, info, pos, scale, b1, f1, f2, f3, vec, b2);\n}\n\nCEntity* ScriptLoader::LoadTargetingPoint(CStateManager& mgr, CInputStream& in, int propCount,\n                                          const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 4, \"TargetingPoint\"))\n    return nullptr;\n\n  SActorHead aHead = LoadActorHead(in, mgr);\n  bool active = in.ReadBool();\n\n  return new CScriptTargetingPoint(mgr.AllocateUniqueId(), aHead.x0_name, info, aHead.x10_transform, active);\n}\n\nCEntity* ScriptLoader::LoadEMPulse(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 12, \"EMPulse\"))\n    return nullptr;\n\n  SActorHead actorHead = LoadActorHead(in, mgr);\n  bool active = in.ReadBool();\n  float f1 = in.ReadFloat();\n  float f2 = in.ReadFloat();\n  float f3 = in.ReadFloat();\n  float f4 = in.ReadFloat();\n  float f5 = in.ReadFloat();\n  float f6 = in.ReadFloat();\n  float f7 = in.ReadFloat();\n  CAssetId particleId(in);\n\n  return new CScriptEMPulse(mgr.AllocateUniqueId(), actorHead.x0_name, info, actorHead.x10_transform, active, f1, f2,\n                            f3, f4, f5, f6, f7, particleId);\n}\n\nCEntity* ScriptLoader::LoadIceSheegoth(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 35, \"IceSheegoth\"))\n    return nullptr;\n\n  SScaledActorHead head = LoadScaledActorHead(in, mgr);\n  const auto [hasProperCount, patternedParmCount] = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!hasProperCount)\n    return nullptr;\n  CPatternedInfo pInfo(in, patternedParmCount);\n  CActorParameters actParms = LoadActorParameters(in);\n  MP1::CIceSheegothData sheegothData(in, propCount);\n\n  const CAnimationParameters& animParms = pInfo.GetAnimationParameters();\n  if (!animParms.GetACSFile().IsValid())\n    return nullptr;\n\n  CModelData mData(CAnimRes(animParms.GetACSFile(), animParms.GetCharacter(), head.x40_scale,\n                            animParms.GetInitialAnimation(), true));\n  return new MP1::CIceSheegoth(mgr.AllocateUniqueId(), head.x0_name, info, head.x10_transform, std::move(mData), pInfo,\n                               actParms, sheegothData);\n}\n\nCEntity* ScriptLoader::LoadPlayerActor(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 19, \"PlayerActor\"))\n    return nullptr;\n\n  SScaledActorHead aHead = LoadScaledActorHead(in, mgr);\n  zeus::CVector3f extents =  in.Get<zeus::CVector3f>();\n  zeus::CVector3f offset =  in.Get<zeus::CVector3f>();\n  float mass = in.ReadFloat();\n  float zMomentum = in.ReadFloat();\n  CHealthInfo hInfo(in);\n  CDamageVulnerability dVuln(in);\n  in.ReadLong();\n  CAnimationParameters animParms = LoadAnimationParameters(in);\n  CActorParameters actParms = LoadActorParameters(in);\n  bool loop = in.ReadBool();\n  bool snow = in.ReadBool();\n  bool solid = in.ReadBool();\n  bool active = in.ReadBool();\n  u32 flags = LoadParameterFlags(in);\n  CPlayerState::EBeamId beam = CPlayerState::EBeamId(in.ReadLong() - 1);\n\n  FourCC fcc = g_ResFactory->GetResourceTypeById(animParms.GetACSFile());\n  if (!fcc.IsValid() || fcc != SBIG('ANCS'))\n    return nullptr;\n\n  zeus::CAABox aabox = GetCollisionBox(mgr, info.GetAreaId(), extents, offset);\n  CMaterialList list;\n  if (snow)\n    list.Add(EMaterialTypes::Snow);\n\n  if (solid)\n    list.Add(EMaterialTypes::Solid);\n\n  if ((extents.x() < 0.f || extents.y() < 0.f || extents.z() < 0.f) || extents.isZero())\n    aabox = zeus::CAABox(-.5f, 0.5f);\n\n  return new CScriptPlayerActor(mgr.AllocateUniqueId(), aHead.x0_name, info, aHead.x10_transform,\n                                CAnimRes(animParms.GetACSFile(), animParms.GetCharacter(), aHead.x40_scale,\n                                         animParms.GetInitialAnimation(), loop),\n                                CModelData::CModelDataNull(), aabox, true, list, mass, zMomentum, hInfo, dVuln,\n                                actParms, loop, active, flags, beam);\n}\n\nCEntity* ScriptLoader::LoadFlaahgra(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, MP1::CFlaahgraData::GetNumProperties(), \"Flaahgra\"))\n    return nullptr;\n\n  SScaledActorHead actHead = LoadScaledActorHead(in, mgr);\n\n  auto pair = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!pair.first)\n    return nullptr;\n  CPatternedInfo pInfo(in, pair.second);\n  CActorParameters actParms = LoadActorParameters(in);\n  MP1::CFlaahgraData flaahgraData(in);\n\n  CAnimRes animRes(pInfo.GetAnimationParameters().GetACSFile(), pInfo.GetAnimationParameters().GetCharacter(),\n                   actHead.x40_scale, pInfo.GetAnimationParameters().GetInitialAnimation(), true);\n\n  if (!pInfo.GetAnimationParameters().GetACSFile().IsValid()) {\n    animRes = CAnimRes(flaahgraData.GetAnimationParameters().GetACSFile(),\n                       flaahgraData.GetAnimationParameters().GetCharacter(), actHead.x40_scale,\n                       flaahgraData.GetAnimationParameters().GetInitialAnimation(), true);\n  }\n\n  if (!animRes.GetId().IsValid())\n    return nullptr;\n\n  return new MP1::CFlaahgra(mgr.AllocateUniqueId(), actHead.x0_name, info, actHead.x10_transform, animRes, pInfo,\n                            actParms, flaahgraData);\n}\n\nCEntity* ScriptLoader::LoadAreaAttributes(CStateManager& mgr, CInputStream& in, int propCount,\n                                          const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 9, \"AreaAttributes\"))\n    return nullptr;\n\n  bool load = in.ReadLong() != 0;\n  if (!load)\n    return nullptr;\n\n  bool showSkybox = in.ReadBool();\n  EEnvFxType fxType = EEnvFxType(in.ReadLong());\n  float envFxDensity = in.ReadFloat();\n  float thermalHeat = in.ReadFloat();\n  float xrayFogDistance = in.ReadFloat();\n  float worldLightingLevel = in.ReadFloat();\n  CAssetId skybox = in.Get<CAssetId>();\n  EPhazonType phazonType = EPhazonType(in.ReadLong());\n\n  return new CScriptAreaAttributes(mgr.AllocateUniqueId(), info, showSkybox, fxType, envFxDensity, thermalHeat,\n                                   xrayFogDistance, worldLightingLevel, skybox, phazonType);\n}\n\nCEntity* ScriptLoader::LoadFishCloud(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 36, \"FishCloud\"))\n    return nullptr;\n  SScaledActorHead aHead = LoadScaledActorHead(in, mgr);\n  bool active = in.ReadBool();\n  CAssetId model(in);\n  CAnimationParameters animParms(in);\n  u32 numBoids = u32(in.ReadFloat());\n  float speed = in.ReadFloat();\n  float separationRadius = in.ReadFloat();\n  float cohesionMagnitude = in.ReadFloat();\n  float alignmentWeight = in.ReadFloat();\n  float separationMagnitude = in.ReadFloat();\n  float weaponRepelMagnitude = in.ReadFloat();\n  float playerRepelMagnitude = in.ReadFloat();\n  float containmentMagnitude = in.ReadFloat();\n  float scatterVel = in.ReadFloat();\n  float maxScatterAngle = in.ReadFloat();\n  float weaponRepelDampingSpeed = in.ReadFloat();\n  float playerRepelDampingSpeed = in.ReadFloat();\n  float containmentRadius = in.ReadFloat();\n  u32 updateShift = in.ReadLong();\n\n  if (!g_ResFactory->GetResourceTypeById(model).IsValid())\n    return nullptr;\n\n  zeus::CColor color = in.Get<zeus::CColor>();\n  bool killable = in.ReadBool();\n  float weaponKillRadius = in.ReadFloat();\n  CAssetId part1 = in.Get<CAssetId>();\n  u32 partCount1 = in.ReadLong();\n  CAssetId part2 = in.Get<CAssetId>();\n  u32 partCount2 = in.ReadLong();\n  CAssetId part3 = in.Get<CAssetId>();\n  u32 partCount3 = in.ReadLong();\n  CAssetId part4 = in.Get<CAssetId>();\n  u32 partCount4 = in.ReadLong();\n  u32 deathSfx = in.ReadLong();\n  bool repelFromThreats = in.ReadBool();\n  bool hotInThermal = in.ReadBool();\n\n  CModelData mData(CStaticRes(model, zeus::skOne3f));\n  CAnimRes animRes(animParms.GetACSFile(), animParms.GetCharacter(), zeus::skOne3f, animParms.GetInitialAnimation(),\n                   true);\n  return new CFishCloud(mgr.AllocateUniqueId(), active, aHead.x0_name, info, aHead.x40_scale, aHead.x10_transform,\n                        std::move(mData), animRes, numBoids, speed, separationRadius, cohesionMagnitude,\n                        alignmentWeight, separationMagnitude, weaponRepelMagnitude, playerRepelMagnitude,\n                        containmentMagnitude, scatterVel, maxScatterAngle, weaponRepelDampingSpeed,\n                        playerRepelDampingSpeed, containmentRadius, updateShift, color, killable, weaponKillRadius,\n                        part1, partCount1, part2, partCount2, part3, partCount3, part4, partCount4, deathSfx,\n                        repelFromThreats, hotInThermal);\n}\n\nCEntity* ScriptLoader::LoadFishCloudModifier(CStateManager& mgr, CInputStream& in, int propCount,\n                                             const CEntityInfo& info) {\n  if (propCount < 6 || !EnsurePropertyCount(propCount, 7, \"FishCloudModifier\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  zeus::CVector3f pos =  in.Get<zeus::CVector3f>();\n  bool active = in.ReadBool();\n  bool repulsor = in.ReadBool();\n  bool swirl = propCount > 6 ? in.ReadBool() : false;\n  float radius = in.ReadFloat();\n  float priority = in.ReadFloat();\n\n  return new CFishCloudModifier(mgr.AllocateUniqueId(), active, name, info, pos, repulsor, swirl, radius, priority);\n}\n\nCEntity* ScriptLoader::LoadVisorFlare(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 14, \"VisorFlare\")) {\n    return nullptr;\n  }\n\n  const std::string name = mgr.HashInstanceName(in);\n  const zeus::CVector3f pos =  in.Get<zeus::CVector3f>();\n  const bool b1 = in.ReadBool();\n  const auto w1 = CVisorFlare::EBlendMode(in.ReadLong());\n  const bool b2 = in.ReadBool();\n  const float f1 = in.ReadFloat();\n  const float f2 = in.ReadFloat();\n  const float f3 = in.ReadFloat();\n  const u32 w2 = in.ReadLong();\n\n  std::vector<CVisorFlare::CFlareDef> flares;\n  flares.reserve(5);\n  for (size_t i = 0; i < flares.capacity(); ++i) {\n    if (auto flare = CVisorFlare::LoadFlareDef(in)) {\n      flares.push_back(*flare);\n    }\n  }\n\n  return new CScriptVisorFlare(mgr.AllocateUniqueId(), name, info, b1, pos, w1, b2, f1, f2, f3, 2, w2,\n                               std::move(flares));\n}\n\nCEntity* ScriptLoader::LoadWorldTeleporter(CStateManager& mgr, CInputStream& in, int propCount,\n                                           const CEntityInfo& info) {\n  if (propCount < 4 || propCount > 26) {\n    spdlog::warn(\"Incorrect number of props for WorldTeleporter\");\n    return nullptr;\n  }\n\n  std::string name = mgr.HashInstanceName(in);\n  bool active = in.ReadBool();\n  CAssetId worldId = in.Get<CAssetId>();\n  CAssetId areaId = in.Get<CAssetId>();\n\n  if (propCount == 4)\n    return new CScriptWorldTeleporter(mgr.AllocateUniqueId(), name, info, active, worldId, areaId);\n\n  CAnimationParameters animParms = LoadAnimationParameters(in);\n  zeus::CVector3f playerScale =  in.Get<zeus::CVector3f>();\n  CAssetId platformModel = in.Get<CAssetId>();\n  zeus::CVector3f platformScale =  in.Get<zeus::CVector3f>();\n  CAssetId backgroundModel = in.Get<CAssetId>();\n  zeus::CVector3f backgroundScale =  in.Get<zeus::CVector3f>();\n  bool upElevator = in.ReadBool();\n\n  s16 elevatorSound = (propCount < 12 ? s16(-1) : s16(in.ReadLong()));\n  u8 volume = (propCount < 13 ? u8(127) : u8(in.ReadLong()));\n  u8 panning = (propCount < 14 ? u8(64) : u8(in.ReadLong()));\n  bool showText = (propCount < 15 ? false : in.ReadBool());\n  CAssetId fontId = (propCount < 16 ? CAssetId() : in.Get<CAssetId>());\n  CAssetId stringId = (propCount < 17 ? CAssetId() : in.Get<CAssetId>());\n  bool fadeWhite = (propCount < 18 ? false : in.ReadBool());\n  float charFadeInTime = (propCount < 19 ? 0.1f : in.ReadFloat());\n  float charsPerSecond = (propCount < 20 ? 16.f : in.ReadFloat());\n  float showDelay = (propCount < 21 ? 0.f : in.ReadFloat());\n  std::string str1 = (propCount < 22 ? \"\" : in.Get<std::string>());\n  std::string str2 = (propCount < 23 ? \"\" : in.Get<std::string>());\n  /*float f1 =*/(propCount < 24 ? 0.f : in.ReadFloat());\n  /*float f2 =*/(propCount < 25 ? 0.f : in.ReadFloat());\n  /*float f3 =*/(propCount < 26 ? 0.f : in.ReadFloat());\n\n  if (showText)\n    return new CScriptWorldTeleporter(mgr.AllocateUniqueId(), name, info, active, worldId, areaId, elevatorSound,\n                                      volume, panning, fontId, stringId, fadeWhite, charFadeInTime, charsPerSecond,\n                                      showDelay);\n\n  return new CScriptWorldTeleporter(mgr.AllocateUniqueId(), name, info, active, worldId, areaId, animParms.GetACSFile(),\n                                    animParms.GetCharacter(), animParms.GetInitialAnimation(), playerScale,\n                                    platformModel, platformScale, backgroundModel, backgroundScale, upElevator,\n                                    elevatorSound, volume, panning);\n}\n\nCEntity* ScriptLoader::LoadVisorGoo(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 11, \"VisorGoo\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  zeus::CVector3f position = in.Get<zeus::CVector3f>();\n  zeus::CTransform xf = zeus::CTransform::Translate(position);\n  CAssetId particle(in);\n  CAssetId electric(in);\n  float minDist = in.ReadFloat();\n  float maxDist = in.ReadFloat();\n  float nearProb = in.ReadFloat();\n  float farProb = in.ReadFloat();\n  zeus::CColor color = in.Get<zeus::CColor>();\n  s32 sfx = in.ReadInt32();\n  bool forceShow = in.ReadBool();\n\n  if (!particle.IsValid() && !electric.IsValid())\n    return nullptr;\n  return new CScriptVisorGoo(mgr.AllocateUniqueId(), name, info, xf, particle, electric, minDist, maxDist, nearProb,\n                             farProb, color, sfx, forceShow, false);\n}\n\nCEntity* ScriptLoader::LoadJellyZap(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 20, \"JellyZap\")) {\n    return nullptr;\n  }\n\n  SScaledActorHead aHead = LoadScaledActorHead(in, mgr);\n  auto pair = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!pair.first) {\n    return nullptr;\n  }\n\n  CPatternedInfo pInfo(in, pair.second);\n  CActorParameters actParms = LoadActorParameters(in);\n  CDamageInfo dInfo(in);\n  float attackRadius = in.ReadFloat();\n  float f2 = in.ReadFloat();\n  float f3 = in.ReadFloat();\n  float f4 = in.ReadFloat();\n  float f5 = in.ReadFloat();\n  float f6 = in.ReadFloat();\n  float f7 = in.ReadFloat();\n  float f8 = in.ReadFloat();\n  float priority = in.ReadFloat();\n  float repulseRadius = in.ReadFloat();\n  float attractRadius = in.ReadFloat();\n  float f12 = in.ReadFloat();\n  bool b1 = in.ReadBool();\n\n  const CAnimationParameters& animParms = pInfo.GetAnimationParameters();\n  if (g_ResFactory->GetResourceTypeById(animParms.GetACSFile()) != SBIG('ANCS')) {\n    return nullptr;\n  }\n\n  CModelData mData(CAnimRes(animParms.GetACSFile(), animParms.GetCharacter(), aHead.x40_scale,\n                            animParms.GetInitialAnimation(), true));\n  return new MP1::CJellyZap(mgr.AllocateUniqueId(), aHead.x0_name, info, aHead.x10_transform, std::move(mData), dInfo,\n                            b1, attackRadius, f2, f3, f4, f5, f6, f7, f8, priority, repulseRadius, attractRadius, f12,\n                            pInfo, actParms);\n}\n\nCEntity* ScriptLoader::LoadControllerAction(CStateManager& mgr, CInputStream& in, int propCount,\n                                            const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 4, \"ControllerAction\") || propCount > 6)\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  bool active = in.ReadBool();\n  ControlMapper::ECommands w1 = ControlMapper::ECommands(in.ReadLong());\n  bool b1 = false;\n  u32 w2 = 0;\n  if (propCount == 6) {\n    b1 = in.ReadBool();\n    w2 = in.ReadLong();\n  }\n  bool b2 = in.ReadBool();\n\n  return new CScriptControllerAction(mgr.AllocateUniqueId(), name, info, active, w1, b1, w2, b2);\n}\n\nCEntity* ScriptLoader::LoadSwitch(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 4, \"Switch\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  bool active = in.ReadBool();\n  bool b2 = in.ReadBool();\n  bool b3 = in.ReadBool();\n\n  return new CScriptSwitch(mgr.AllocateUniqueId(), name, info, active, b2, b3);\n}\n\nCEntity* ScriptLoader::LoadPlayerStateChange(CStateManager& mgr, CInputStream& in, int propCount,\n                                             const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 7, \"PlayerStateChange\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  bool active = in.ReadBool();\n  s32 itemType = in.ReadLong();\n  s32 itemCount = in.ReadInt32();\n  s32 itemCapacity = in.ReadInt32();\n  CScriptPlayerStateChange::EControl ctrl = CScriptPlayerStateChange::EControl(in.ReadLong());\n  CScriptPlayerStateChange::EControlCommandOption ctrlCmdOpt =\n      CScriptPlayerStateChange::EControlCommandOption(in.ReadLong());\n  return new CScriptPlayerStateChange(mgr.AllocateUniqueId(), name, info, active, itemType, itemCount, itemCapacity,\n                                      ctrl, ctrlCmdOpt);\n}\n\nCEntity* ScriptLoader::LoadThardus(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n\n  if (!EnsurePropertyCount(propCount, 43, \"Thardus\")) {\n    return nullptr;\n  }\n\n  SScaledActorHead actHead = LoadScaledActorHead(in, mgr);\n  const auto [isValid, patternedCount] = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!isValid)\n    return nullptr;\n\n  CPatternedInfo pInfo(in, patternedCount);\n  CActorParameters actParms = LoadActorParameters(in);\n  in.ReadBool();\n  in.ReadBool();\n  const CAnimationParameters& animParms = pInfo.GetAnimationParameters();\n  if (!animParms.GetACSFile().IsValid())\n    return nullptr;\n  CStaticRes staticRes[2][7];\n  staticRes[0][6] = CStaticRes(CAssetId(in), zeus::skOne3f);\n  staticRes[0][5] = CStaticRes(CAssetId(in), zeus::skOne3f);\n  staticRes[0][4] = CStaticRes(CAssetId(in), zeus::skOne3f);\n  staticRes[0][3] = CStaticRes(CAssetId(in), zeus::skOne3f);\n  staticRes[0][2] = CStaticRes(CAssetId(in), zeus::skOne3f);\n  staticRes[0][1] = CStaticRes(CAssetId(in), zeus::skOne3f);\n  staticRes[0][0] = CStaticRes(CAssetId(in), zeus::skOne3f);\n  staticRes[1][6] = CStaticRes(CAssetId(in), zeus::skOne3f);\n  staticRes[1][5] = CStaticRes(CAssetId(in), zeus::skOne3f);\n  staticRes[1][4] = CStaticRes(CAssetId(in), zeus::skOne3f);\n  staticRes[1][3] = CStaticRes(CAssetId(in), zeus::skOne3f);\n  staticRes[1][2] = CStaticRes(CAssetId(in), zeus::skOne3f);\n  staticRes[1][1] = CStaticRes(CAssetId(in), zeus::skOne3f);\n  staticRes[1][0] = CStaticRes(CAssetId(in), zeus::skOne3f);\n  CAssetId particle1(in);\n  CAssetId particle2(in);\n  CAssetId particle3(in);\n  CAssetId stateMachine(in);\n  CAssetId particle4(in);\n  CAssetId particle5(in);\n  CAssetId particle6(in);\n  CAssetId particle7(in);\n  CAssetId particle8(in);\n  CAssetId particle9(in);\n  float f1 = in.ReadFloat();\n  float f2 = in.ReadFloat();\n  float f3 = in.ReadFloat();\n  float f4 = in.ReadFloat();\n  float f5 = in.ReadFloat();\n  float f6 = in.ReadFloat();\n  CAssetId texture(in);\n  int sfxID1 = in.ReadLong();\n  CAssetId particle10 = (propCount < 44) ? CAssetId() : CAssetId(in);\n  int sfxID2 = in.ReadLong();\n  int sfxID3 = in.ReadLong();\n  int sfxID4 = in.ReadLong();\n  std::vector<CStaticRes> mData1(std::rbegin(staticRes[0]), std::rend(staticRes[0]));\n  std::vector<CStaticRes> mData2(std::rbegin(staticRes[1]), std::rend(staticRes[1]));\n\n  CModelData mData(CAnimRes(animParms.GetACSFile(), 0, actHead.x40_scale, animParms.GetInitialAnimation(), true));\n  return new MP1::CThardus(mgr.AllocateUniqueId(), actHead.x0_name, info, actHead.x10_transform, std::move(mData),\n                           actParms, pInfo, std::move(mData1), std::move(mData2), particle1, particle2, particle3, f1,\n                           f2, f3, f4, f5, f6, stateMachine, particle4, particle5, particle6, particle7, particle8,\n                           particle9, texture, sfxID1, particle10, sfxID2, sfxID3, sfxID4);\n}\n\nCEntity* ScriptLoader::LoadWallCrawlerSwarm(CStateManager& mgr, CInputStream& in, int propCount,\n                                            const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 39, \"WallCrawlerSwarm\"))\n    return nullptr;\n\n  SScaledActorHead aHead = LoadScaledActorHead(in, mgr);\n  bool active = in.ReadBool();\n  CActorParameters aParams = LoadActorParameters(in);\n  CWallCrawlerSwarm::EFlavor flavor = CWallCrawlerSwarm::EFlavor(in.ReadLong());\n  u32 actor = in.ReadLong();\n  u32 charIdx = in.ReadLong();\n  u32 defaultAnim = in.ReadLong();\n  u32 launchAnim = in.ReadLong();\n  u32 attractAnim = in.ReadLong();\n  u32 part1 = in.ReadLong();\n  u32 part2 = in.ReadLong();\n  u32 part3 = in.ReadLong();\n  u32 part4 = in.ReadLong();\n  CDamageInfo crabDamage(in);\n  float crabDamageCooldown = in.ReadFloat();\n  CDamageInfo scarabExplodeDamage(in);\n  float boidRadius = in.ReadFloat();\n  float touchRadius = in.ReadFloat();\n  float playerTouchRadius = in.ReadFloat();\n  float animPlaybackSpeed = in.ReadFloat();\n  u32 numBoids = in.ReadLong();\n  u32 maxCreatedBoids = in.ReadLong();\n  float separationRadius = in.ReadFloat();\n  float cohesionMagnitude = in.ReadFloat();\n  float alignmentWeight = in.ReadFloat();\n  float separationMagnitude = in.ReadFloat();\n  float moveToWaypointWeight = in.ReadFloat();\n  float attractionMagnitude = in.ReadFloat();\n  float attractionRadius = in.ReadFloat();\n  float boidGenRate = in.ReadFloat();\n  u32 maxLaunches = in.ReadLong();\n  float scarabBoxMargin = in.ReadFloat();\n  float scarabScatterXYVelocity = in.ReadFloat();\n  float scarabTimeToExplode = in.ReadFloat();\n  CHealthInfo hInfo(in);\n  CDamageVulnerability dVulns(in);\n  u32 launchSfx = in.ReadLong();\n  u32 scatterSfx = in.ReadLong();\n\n  return new CWallCrawlerSwarm(\n      mgr.AllocateUniqueId(), active, aHead.x0_name, info, aHead.x40_scale, aHead.x10_transform, flavor,\n      CAnimRes(actor, charIdx, zeus::CVector3f(1.5f), defaultAnim, true), launchAnim, attractAnim, part1, part2, part3,\n      part4, crabDamage, scarabExplodeDamage, crabDamageCooldown, boidRadius, touchRadius, playerTouchRadius, numBoids,\n      maxCreatedBoids, animPlaybackSpeed, separationRadius, cohesionMagnitude, alignmentWeight, separationMagnitude,\n      moveToWaypointWeight, attractionMagnitude, attractionRadius, boidGenRate, maxLaunches, scarabBoxMargin,\n      scarabScatterXYVelocity, scarabTimeToExplode, hInfo, dVulns, launchSfx, scatterSfx, aParams);\n}\n\nCEntity* ScriptLoader::LoadAiJumpPoint(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 5, \"AiJumpPoint\"))\n    return nullptr;\n\n  SActorHead aHead = LoadActorHead(in, mgr);\n  bool active = in.ReadBool();\n  float apex = in.ReadFloat();\n\n  return new CScriptAiJumpPoint(mgr.AllocateUniqueId(), aHead.x0_name, info, aHead.x10_transform, active, apex);\n}\n\nCEntity* ScriptLoader::LoadFlaahgraTentacle(CStateManager& mgr, CInputStream& in, int propCount,\n                                            const CEntityInfo& info) {\n\n  if (!EnsurePropertyCount(propCount, 6, \"FlaahgraTentacle\"))\n    return nullptr;\n\n  SScaledActorHead actHead = LoadScaledActorHead(in, mgr);\n  auto pair = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!pair.first)\n    return nullptr;\n  CPatternedInfo pInfo(in, pair.second);\n  CActorParameters actParms = LoadActorParameters(in);\n\n  if (!pInfo.GetAnimationParameters().GetACSFile().IsValid())\n    return nullptr;\n\n  const CAnimationParameters& animParms = pInfo.GetAnimationParameters();\n  CModelData mData(CAnimRes(animParms.GetACSFile(), animParms.GetCharacter(), actHead.x40_scale,\n                            animParms.GetInitialAnimation(), true));\n\n  return new MP1::CFlaahgraTentacle(mgr.AllocateUniqueId(), actHead.x0_name, info, actHead.x10_transform,\n                                    std::move(mData), pInfo, actParms);\n}\n\nCEntity* ScriptLoader::LoadRoomAcoustics(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 32, \"RoomAcoustics\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  bool a = in.ReadBool();\n  u32 b = in.ReadLong();\n  bool c = in.ReadBool();\n  bool d = in.ReadBool();\n  float e = in.ReadFloat();\n  float f = in.ReadFloat();\n  float g = in.ReadFloat();\n  float h = in.ReadFloat();\n  float i = in.ReadFloat();\n  float j = in.ReadFloat();\n  bool k = in.ReadBool();\n  float l = in.ReadFloat();\n  float m = in.ReadFloat();\n  float n = in.ReadFloat();\n  bool o = in.ReadBool();\n  bool p = in.ReadBool();\n  float q = in.ReadFloat();\n  float r = in.ReadFloat();\n  float s = in.ReadFloat();\n  float t = in.ReadFloat();\n  float u = in.ReadFloat();\n  bool v = in.ReadBool();\n  u32 w = in.ReadLong();\n  u32 x = in.ReadLong();\n  u32 y = in.ReadLong();\n  u32 z = in.ReadLong();\n  u32 _a = in.ReadLong();\n  u32 _b = in.ReadLong();\n  u32 _c = in.ReadLong();\n  u32 _d = in.ReadLong();\n  u32 _e = in.ReadLong();\n\n  return new CScriptRoomAcoustics(mgr.AllocateUniqueId(), name, info, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q,\n                                  r, s, t, u, v, w, x, y, z, _a, _b, _c, _d, _e);\n}\n\nCEntity* ScriptLoader::LoadColorModulate(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 12, \"ColorModulate\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  zeus::CColor colorA = in.Get<zeus::CColor>();\n  zeus::CColor colorB = in.Get<zeus::CColor>();\n  CScriptColorModulate::EBlendMode blendMode = CScriptColorModulate::EBlendMode(in.ReadLong());\n  float timeA2B = in.ReadFloat();\n  float timeB2A = in.ReadFloat();\n  bool doReverse = in.ReadBool();\n  bool resetTargetWhenDone = in.ReadBool();\n  bool depthCompare = in.ReadBool();\n  bool depthUpdate = in.ReadBool();\n  bool depthBackwards = in.ReadBool();\n  bool active = in.ReadBool();\n  return new CScriptColorModulate(mgr.AllocateUniqueId(), name, info, colorA, colorB, blendMode, timeA2B, timeB2A,\n                                  doReverse, resetTargetWhenDone, depthCompare, depthUpdate, depthBackwards, active);\n}\n\nCEntity* ScriptLoader::LoadThardusRockProjectile(CStateManager& mgr, CInputStream& in, int propCount,\n                                                 const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 3, \"ThardusRockProjectile\"))\n    return nullptr;\n\n  SScaledActorHead actorHead = LoadScaledActorHead(in, mgr);\n  auto [pInfoValid, pInfoCount] = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!pInfoValid)\n    return nullptr;\n\n  CPatternedInfo pInfo(in, pInfoCount);\n  CActorParameters actParms = LoadActorParameters(in);\n  in.ReadBool();\n  in.ReadBool();\n  float f1 = in.ReadFloat();\n  CAssetId modelId(in);\n  CAssetId stateMachine(in);\n  if (!pInfo.GetAnimationParameters().GetACSFile().IsValid())\n    return nullptr;\n\n  std::vector<CStaticRes> mDataVec;\n  CModelData mData(CAnimRes(pInfo.GetAnimationParameters().GetACSFile(), 0, actorHead.x40_scale,\n                            pInfo.GetAnimationParameters().GetInitialAnimation(), true));\n  mDataVec.reserve(3);\n  mDataVec.emplace_back(modelId, zeus::skOne3f);\n  return new MP1::CThardusRockProjectile(mgr.AllocateUniqueId(), actorHead.x0_name, info, actorHead.x10_transform,\n                                         std::move(mData), actParms, pInfo, std::move(mDataVec), stateMachine, f1);\n}\n\nCEntity* ScriptLoader::LoadMidi(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 6, \"Midi\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  bool active = in.ReadBool();\n  u32 csng = in.ReadLong();\n  float fadeIn = in.ReadFloat();\n  float fadeOut = in.ReadFloat();\n  u32 vol = in.ReadLong();\n  return new CScriptMidi(mgr.AllocateUniqueId(), info, name, active, csng, fadeIn, fadeOut, vol);\n}\n\nCEntity* ScriptLoader::LoadStreamedAudio(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 9, \"StreamedAudio\"))\n    return nullptr;\n\n  const std::string name = mgr.HashInstanceName(in);\n  bool active = in.ReadBool();\n  std::string fileName = in.Get<std::string>();\n  bool noStopOnDeactivate = in.ReadBool();\n  float fadeIn = in.ReadFloat();\n  float fadeOut = in.ReadFloat();\n  u32 volume = in.ReadLong();\n  u32 oneShot = in.ReadLong();\n  bool music = in.ReadBool();\n\n  return new CScriptStreamedMusic(mgr.AllocateUniqueId(), info, name, active, fileName, noStopOnDeactivate, fadeIn,\n                                  fadeOut, volume, !oneShot, music);\n}\n\nCEntity* ScriptLoader::LoadRepulsor(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 4, \"Repulsor\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  zeus::CVector3f center = in.Get<zeus::CVector3f>();\n  bool active = in.ReadBool();\n  float radius = in.ReadFloat();\n\n  return new CRepulsor(mgr.AllocateUniqueId(), active, name, info, center, radius);\n}\n\nCEntity* ScriptLoader::LoadGunTurret(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, CScriptGunTurretData::GetMinProperties(), \"GunTurret\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  CScriptGunTurret::ETurretComponent component = CScriptGunTurret::ETurretComponent(in.ReadLong());\n  zeus::CTransform xf = LoadEditorTransform(in);\n  zeus::CVector3f scale =  in.Get<zeus::CVector3f>();\n  zeus::CVector3f collisionExtent =  in.Get<zeus::CVector3f>();\n  zeus::CVector3f collisionOffset =  in.Get<zeus::CVector3f>();\n  CAnimationParameters animParms = LoadAnimationParameters(in);\n  CActorParameters actParms = LoadActorParameters(in);\n  CHealthInfo hInfo(in);\n  CDamageVulnerability dVuln(in);\n  CScriptGunTurretData turretData(in, propCount);\n\n  if (!g_ResFactory->GetResourceTypeById(animParms.GetACSFile()).IsValid())\n    return nullptr;\n\n  CModelData mData(\n      CAnimRes(animParms.GetACSFile(), animParms.GetCharacter(), scale, animParms.GetInitialAnimation(), true));\n  zeus::CAABox aabb = GetCollisionBox(mgr, info.GetAreaId(), collisionExtent, collisionOffset);\n\n  if ((collisionExtent.x() < 0.f || collisionExtent.y() < 0.f || collisionExtent.z() < 0.f) || collisionExtent.isZero())\n    aabb = mData.GetBounds(xf.getRotation());\n\n  return new CScriptGunTurret(mgr.AllocateUniqueId(), name, component, info, xf, std::move(mData), aabb, hInfo, dVuln,\n                              actParms, turretData);\n}\n\nCEntity* ScriptLoader::LoadFogVolume(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 7, \"FogVolume\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  zeus::CVector3f center =  in.Get<zeus::CVector3f>();\n  zeus::CVector3f volume =  in.Get<zeus::CVector3f>();\n  float flickerSpeed = in.ReadFloat();\n  float f2 = in.ReadFloat();\n  zeus::CColor fogColor = in.Get<zeus::CColor>();\n  bool active = in.ReadBool();\n\n  volume.x() = std::fabs(volume.x());\n  volume.y() = std::fabs(volume.y());\n  volume.z() = std::fabs(volume.z());\n\n  return new CScriptSpecialFunction(mgr.AllocateUniqueId(), name, info, ConvertEditorEulerToTransform4f(center, {}),\n                                    CScriptSpecialFunction::ESpecialFunction::FogVolume, \"\", flickerSpeed, f2, 0.f, 0.f,\n                                    volume, fogColor, active, CDamageInfo(), -1, -1, CPlayerState::EItemType::Invalid,\n                                    -1, -1, -1);\n}\n\nCEntity* ScriptLoader::LoadBabygoth(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 33, \"Babygoth\"))\n    return nullptr;\n\n  SScaledActorHead actHead = LoadScaledActorHead(in, mgr);\n  auto pair = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!pair.first)\n    return nullptr;\n\n  CPatternedInfo pInfo(in, pair.second);\n  CActorParameters actParms = LoadActorParameters(in);\n  MP1::CBabygothData babyData(in);\n\n  if (!pInfo.GetAnimationParameters().GetACSFile().IsValid())\n    return nullptr;\n\n  CModelData mData(CAnimRes(pInfo.GetAnimationParameters().GetACSFile(), pInfo.GetAnimationParameters().GetCharacter(),\n                            actHead.x40_scale, pInfo.GetAnimationParameters().GetInitialAnimation(), true));\n  return new MP1::CBabygoth(mgr.AllocateUniqueId(), actHead.x0_name, info, actHead.x10_transform, std::move(mData),\n                            pInfo, actParms, babyData);\n}\n\nCEntity* ScriptLoader::LoadEyeball(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 21, \"Eyeball\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  CPatterned::EFlavorType flavor = CPatterned::EFlavorType(in.ReadLong());\n  zeus::CTransform xf = LoadEditorTransform(in);\n  zeus::CVector3f scale =  in.Get<zeus::CVector3f>();\n\n  auto pair = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!pair.first)\n    return nullptr;\n\n  CPatternedInfo pInfo(in, pair.second);\n  CActorParameters actParms = LoadActorParameters(in);\n  float attackDelay = in.ReadFloat();\n  float attackStartTime = in.ReadFloat();\n  CAssetId wpsc(in);\n  CDamageInfo dInfo(in);\n  CAssetId beamContactFxId(in);\n  CAssetId beamPulseFxId(in);\n  CAssetId beamTextureId(in);\n  CAssetId beamGlowTextureId(in);\n  u32 anim0 = in.ReadLong();\n  u32 anim1 = in.ReadLong();\n  u32 anim2 = in.ReadLong();\n  u32 anim3 = in.ReadLong();\n  u32 beamSfx = in.ReadLong();\n\n  if (g_ResFactory->GetResourceTypeById(pInfo.GetAnimationParameters().GetACSFile()) != SBIG('ANCS'))\n    return nullptr;\n\n  bool attackDisabled = in.ReadBool();\n\n  CModelData mData(CAnimRes(pInfo.GetAnimationParameters().GetACSFile(), pInfo.GetAnimationParameters().GetCharacter(),\n                            scale, pInfo.GetAnimationParameters().GetInitialAnimation(), true));\n\n  return new MP1::CEyeball(mgr.AllocateUniqueId(), name, flavor, info, xf, std::move(mData), pInfo, attackDelay,\n                           attackStartTime, wpsc, dInfo, beamContactFxId, beamPulseFxId, beamTextureId,\n                           beamGlowTextureId, anim0, anim1, anim2, anim3, beamSfx, attackDisabled, actParms);\n}\n\nCEntity* ScriptLoader::LoadRadialDamage(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 5, \"RadialDamage\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  zeus::CVector3f center =  in.Get<zeus::CVector3f>();\n  bool active = in.ReadBool();\n  CDamageInfo dInfo(in);\n  float radius = in.ReadFloat();\n  zeus::CTransform xf = ConvertEditorEulerToTransform4f(zeus::skZero3f, center);\n\n  return new CScriptSpecialFunction(\n      mgr.AllocateUniqueId(), name, info, xf, CScriptSpecialFunction::ESpecialFunction::RadialDamage, \"\", radius, 0.f,\n      0.f, 0.f, zeus::skZero3f, zeus::skBlack, active, dInfo, -1, -1, CPlayerState::EItemType::Invalid, -1, -1, -1);\n}\n\nCEntity* ScriptLoader::LoadCameraPitchVolume(CStateManager& mgr, CInputStream& in, int propCount,\n                                             const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 8, \"CameraPitchVolume\"))\n    return nullptr;\n\n  SScaledActorHead aHead = LoadScaledActorHead(in, mgr);\n  bool active = in.ReadBool();\n  zeus::CRelAngle upPitch = zeus::CRelAngle::FromDegrees(in.ReadFloat());\n  zeus::CRelAngle downPitch = zeus::CRelAngle::FromDegrees(in.ReadFloat());\n  float scale = in.ReadFloat();\n\n  return new CScriptCameraPitchVolume(mgr.AllocateUniqueId(), active, aHead.x0_name, info, aHead.x40_scale,\n                                      aHead.x10_transform, upPitch, downPitch, scale);\n}\n\nCEntity* ScriptLoader::LoadEnvFxDensityController(CStateManager& mgr, CInputStream& in, int propCount,\n                                                  const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 4, \"EnvFxDensityController\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  bool active = in.ReadBool();\n  float density = in.ReadFloat();\n  u32 maxDensityDeltaSpeed = in.ReadLong();\n\n  return new CScriptSpecialFunction(mgr.AllocateUniqueId(), name, info, zeus::CTransform(),\n                                    CScriptSpecialFunction::ESpecialFunction::EnvFxDensityController, \"\", density,\n                                    maxDensityDeltaSpeed, 0.f, 0.f, zeus::skZero3f, zeus::skBlack, active,\n                                    CDamageInfo(), -1, -1, CPlayerState::EItemType::Invalid, -1, -1, -1);\n}\n\nCEntity* ScriptLoader::LoadMagdolite(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 22, \"Magdolite\"))\n    return nullptr;\n\n  SScaledActorHead actorHead = LoadScaledActorHead(in, mgr);\n\n  auto pair = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!pair.first) {\n    return nullptr;\n  }\n\n  CPatternedInfo pInfo(in, pair.second);\n  CActorParameters actorParameters = LoadActorParameters(in);\n\n  if (!pInfo.GetAnimationParameters().GetACSFile().IsValid()) {\n    return nullptr;\n  }\n\n  float f1 = in.ReadFloat();\n  float f2 = in.ReadFloat();\n  CDamageInfo damageInfo1(in);\n  CDamageInfo damageInfo2(in);\n  CDamageVulnerability damageVulnerability1(in);\n  CDamageVulnerability damageVulnerability2(in);\n  CAssetId modelId(in);\n  CAssetId skinId(in);\n  float f3 = in.ReadFloat();\n  float f4 = in.ReadFloat();\n  float f5 = in.ReadFloat();\n  float f6 = in.ReadFloat();\n  CFlameInfo flameInfo(in);\n  float f7 = in.ReadFloat();\n  float f8 = in.ReadFloat();\n  float f9 = in.ReadFloat();\n\n  CModelData modelData(CAnimRes(pInfo.GetAnimationParameters().GetACSFile(),\n                                pInfo.GetAnimationParameters().GetCharacter(), actorHead.x40_scale,\n                                pInfo.GetAnimationParameters().GetInitialAnimation(), true));\n\n  return new MP1::CMagdolite(mgr.AllocateUniqueId(), actorHead.x0_name, info, actorHead.x10_transform,\n                             std::move(modelData), pInfo, actorParameters, f1, f2, damageInfo1, damageInfo2,\n                             damageVulnerability1, damageVulnerability2, modelId, skinId, f6, f3, f4, f5, flameInfo, f7,\n                             f8, f9);\n}\n\nCEntity* ScriptLoader::LoadTeamAIMgr(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 8, \"TeamAiMgr\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  CTeamAiData data(in, propCount);\n  return new CTeamAiMgr(mgr.AllocateUniqueId(), name, info, data);\n}\n\nCEntity* ScriptLoader::LoadSnakeWeedSwarm(CStateManager& mgr, CInputStream& in, int propCount,\n                                          const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 25, \"SnakeWeedSwarm\") || propCount > 29)\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  zeus::CVector3f pos =  in.Get<zeus::CVector3f>();\n  zeus::CVector3f scale =  in.Get<zeus::CVector3f>();\n  bool active = in.ReadBool();\n  CAnimationParameters animParms = LoadAnimationParameters(in);\n  CActorParameters actParms = LoadActorParameters(in);\n  float spacing = in.ReadFloat();\n  float height = in.ReadFloat();\n  float f3 = in.ReadFloat();\n  float weaponDamageRadius = in.ReadFloat();\n  float maxPlayerDistance = in.ReadFloat();\n  float loweredTime = in.ReadFloat();\n  float loweredTimeVariation = in.ReadFloat();\n  float maxZOffset = in.ReadFloat();\n  float speed = in.ReadFloat();\n  float speedVariation = in.ReadFloat();\n  float f11 = in.ReadFloat();\n  float scaleMin = in.ReadFloat();\n  float scaleMax = in.ReadFloat();\n  float distanceBelowGround = in.ReadFloat();\n  CDamageInfo dInfo(in);\n  float unused = in.ReadFloat();\n  u32 sfxId1 = in.ReadLong();\n  u32 sfxId2 = in.ReadLong();\n  u32 sfxId3 = in.ReadLong();\n  CAssetId particleGenDesc1 = (propCount < 29 ? CAssetId() : CAssetId(in));\n  u32 w5 = (propCount < 29 ? 0 : in.ReadLong());\n  CAssetId particleGenDesc2 = (propCount < 29 ? CAssetId() : CAssetId(in));\n  float f16 = (propCount < 29 ? 0.f : in.ReadFloat());\n\n  CAnimRes animRes(animParms.GetACSFile(), animParms.GetCharacter(), zeus::skOne3f, animParms.GetInitialAnimation(),\n                   true);\n  return new CSnakeWeedSwarm(mgr.AllocateUniqueId(), active, name, info, pos, scale, animRes, actParms, spacing, height,\n                             f3, weaponDamageRadius, maxPlayerDistance, loweredTime, loweredTimeVariation, maxZOffset,\n                             speed, speedVariation, f11, scaleMin, scaleMax, distanceBelowGround, dInfo, unused, sfxId1,\n                             sfxId2, sfxId3, particleGenDesc1, w5, particleGenDesc2, f16);\n}\n\nCEntity* ScriptLoader::LoadActorContraption(CStateManager& mgr, CInputStream& in, int propCount,\n                                            const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 15, \"ActorContraption\"))\n    return nullptr;\n\n  SScaledActorHead head = LoadScaledActorHead(in, mgr);\n  zeus::CVector3f collisionExtent =  in.Get<zeus::CVector3f>();\n  zeus::CVector3f collisionOrigin =  in.Get<zeus::CVector3f>();\n  float mass = in.ReadFloat();\n  float zMomentum = in.ReadFloat();\n  CHealthInfo hInfo(in);\n  CDamageVulnerability dVuln(in);\n  CAnimationParameters animParams(in);\n  CActorParameters actParams = LoadActorParameters(in);\n  CAssetId flameFxId = in.Get<CAssetId>();\n  CDamageInfo dInfo(in);\n  bool active = in.ReadBool();\n\n  if (!g_ResFactory->GetResourceTypeById(animParams.GetACSFile()).IsValid())\n    return nullptr;\n\n  zeus::CAABox aabb = GetCollisionBox(mgr, info.GetAreaId(), collisionExtent, collisionOrigin);\n  CMaterialList list;\n  list.Add(EMaterialTypes::Immovable);\n  list.Add(EMaterialTypes::Solid);\n\n  CModelData data(CAnimRes(animParams.GetACSFile(), animParams.GetCharacter(), head.x40_scale,\n                           animParams.GetInitialAnimation(), true));\n\n  if ((collisionExtent.x() < 0.f || collisionExtent.y() < 0.f || collisionExtent.z() < 0.f) || collisionExtent.isZero())\n    aabb = data.GetBounds(head.x10_transform.getRotation());\n\n  return new MP1::CScriptContraption(mgr.AllocateUniqueId(), head.x0_name, info, head.x10_transform, std::move(data),\n                                    aabb, list, mass, zMomentum, hInfo, dVuln, actParams, flameFxId, dInfo, active);\n}\n\nCEntity* ScriptLoader::LoadOculus(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 15, \"Oculus\"))\n    return nullptr;\n\n  SScaledActorHead aHead = LoadScaledActorHead(in, mgr);\n  auto pair = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!pair.first)\n    return nullptr;\n  CPatternedInfo pInfo(in, pair.second);\n  CActorParameters actParms = LoadActorParameters(in);\n  if (!pInfo.GetAnimationParameters().GetACSFile().IsValid())\n    return nullptr;\n\n  float f1 = in.ReadFloat();\n  float f2 = in.ReadFloat();\n  float f3 = in.ReadFloat();\n  float f4 = in.ReadFloat();\n  float f5 = in.ReadFloat();\n  float f6 = in.ReadFloat();\n  CDamageVulnerability dVuln(in);\n  float f7 = in.ReadFloat();\n  CDamageInfo dInfo(in);\n  const CAnimationParameters animParms = pInfo.GetAnimationParameters();\n  CModelData mData(CAnimRes(animParms.GetACSFile(), animParms.GetCharacter(), aHead.x40_scale,\n                            animParms.GetInitialAnimation(), true));\n\n  return new MP1::CParasite(\n      mgr.AllocateUniqueId(), aHead.x0_name, CPatterned::EFlavorType::Zero, info, aHead.x10_transform, std::move(mData),\n      pInfo, EBodyType::WallWalker, 0.f, f1, f2, f3, f4, 0.2f, 0.4f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f, f7, 0.f, 0.f, f5, f6,\n      false, CWallWalker::EWalkerType::Oculus, dVuln, dInfo, -1, -1, -1, CAssetId(), CAssetId(), 0.f, actParms);\n}\n\nCEntity* ScriptLoader::LoadGeemer(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 16, \"Geemer\"))\n    return nullptr;\n  SScaledActorHead actHead = LoadScaledActorHead(in, mgr);\n\n  auto pair = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!pair.first)\n    return nullptr;\n\n  CPatternedInfo pInfo(in, pair.second);\n  CActorParameters actParms = LoadActorParameters(in);\n\n  if (pInfo.GetAnimationParameters().GetACSFile() == CAssetId())\n    return nullptr;\n\n  float advanceWpRadius = in.ReadFloat();\n  float f2 = in.ReadFloat();\n  float alignAngVel = in.ReadFloat();\n  float f4 = in.ReadFloat();\n  float playerObstructionMinDist = in.ReadFloat();\n  float haltDelay = in.ReadFloat();\n  float forwardMoveWeight = in.ReadFloat();\n  u16 haltSfx = in.ReadLong() & 0xFFFF;\n  u16 getUpSfx = in.ReadLong() & 0xFFFF;\n  u16 crouchSfx = in.ReadLong() & 0xFFFF;\n\n  CModelData mData(CAnimRes(pInfo.GetAnimationParameters().GetACSFile(), pInfo.GetAnimationParameters().GetCharacter(),\n                            actHead.x40_scale, pInfo.GetAnimationParameters().GetInitialAnimation(), true));\n\n  return new MP1::CParasite(mgr.AllocateUniqueId(), actHead.x0_name, CPatterned::EFlavorType::Zero, info,\n                            actHead.x10_transform, std::move(mData), pInfo, EBodyType::WallWalker, 0.f, advanceWpRadius,\n                            f2, alignAngVel, f4, 0.2f, 0.4f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f, forwardMoveWeight, 0.f, 0.f,\n                            playerObstructionMinDist, haltDelay, false, CWallWalker::EWalkerType::Geemer,\n                            CDamageVulnerability::NormalVulnerabilty(), CDamageInfo(), haltSfx, getUpSfx, crouchSfx, {},\n                            {}, 0.f, actParms);\n}\n\nCEntity* ScriptLoader::LoadSpindleCamera(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 24, \"SpindleCamera\"))\n    return nullptr;\n\n  SActorHead aHead = LoadActorHead(in, mgr);\n  bool active = in.ReadBool();\n  u32 flags = LoadParameterFlags(in);\n  float hintToCamDistMin = in.ReadFloat();\n  float hintToCamDistMax = in.ReadFloat();\n  float hintToCamVOffMin = in.ReadFloat();\n  float hintToCamVOffMax = in.ReadFloat();\n\n  SSpindleProperty targetHintToCamDeltaAngleVel(in);\n  targetHintToCamDeltaAngleVel.FixupAngles();\n  SSpindleProperty deltaAngleScaleWithCamDist(in);\n  SSpindleProperty hintToCamDist(in);\n  SSpindleProperty distOffsetFromBallDist(in);\n  SSpindleProperty hintBallToCamAzimuth(in);\n  hintBallToCamAzimuth.FixupAngles();\n  SSpindleProperty unused(in);\n  unused.FixupAngles();\n  SSpindleProperty maxHintBallToCamAzimuth(in);\n  maxHintBallToCamAzimuth.FixupAngles();\n  SSpindleProperty camLookRelAzimuth(in);\n  camLookRelAzimuth.FixupAngles();\n  SSpindleProperty lookPosZOffset(in);\n  SSpindleProperty camPosZOffset(in);\n  SSpindleProperty clampedAzimuthFromHintDir(in);\n  clampedAzimuthFromHintDir.FixupAngles();\n  SSpindleProperty dampingAzimuthSpeed(in);\n  dampingAzimuthSpeed.FixupAngles();\n  SSpindleProperty targetHintToCamDeltaAngleVelRange(in);\n  targetHintToCamDeltaAngleVelRange.FixupAngles();\n  SSpindleProperty deleteHintBallDist(in);\n  SSpindleProperty recoverClampedAzimuthFromHintDir(in);\n  recoverClampedAzimuthFromHintDir.FixupAngles();\n\n  return new CScriptSpindleCamera(\n      mgr.AllocateUniqueId(), aHead.x0_name, info, aHead.x10_transform, active, flags, hintToCamDistMin,\n      hintToCamDistMax, hintToCamVOffMin, hintToCamVOffMax, targetHintToCamDeltaAngleVel, deltaAngleScaleWithCamDist,\n      hintToCamDist, distOffsetFromBallDist, hintBallToCamAzimuth, unused, maxHintBallToCamAzimuth, camLookRelAzimuth,\n      lookPosZOffset, camPosZOffset, clampedAzimuthFromHintDir, dampingAzimuthSpeed, targetHintToCamDeltaAngleVelRange,\n      deleteHintBallDist, recoverClampedAzimuthFromHintDir);\n}\n\nCEntity* ScriptLoader::LoadAtomicAlpha(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 14, \"AtomicAlpha\"))\n    return nullptr;\n\n  SScaledActorHead actHead = LoadScaledActorHead(in, mgr);\n  auto pair = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!pair.first)\n    return nullptr;\n\n  CPatternedInfo pInfo(in, pair.second);\n  CActorParameters actParms = LoadActorParameters(in);\n\n  CAssetId wpsc(in);\n  CAssetId model(in);\n  CDamageInfo dInfo(in);\n  float f1 = in.ReadFloat();\n  float f2 = in.ReadFloat();\n  float f3 = in.ReadFloat();\n  bool b1 = in.ReadBool();\n  bool b2 = in.ReadBool();\n\n  CModelData mData(CAnimRes(pInfo.GetAnimationParameters().GetACSFile(), pInfo.GetAnimationParameters().GetCharacter(),\n                            actHead.x40_scale, pInfo.GetAnimationParameters().GetInitialAnimation(), true));\n\n  return new MP1::CAtomicAlpha(mgr.AllocateUniqueId(), actHead.x0_name, info, actHead.x10_transform, std::move(mData),\n                               actParms, pInfo, wpsc, dInfo, f1, f2, f3, model, b1, b2);\n}\n\nCEntity* ScriptLoader::LoadCameraHintTrigger(CStateManager& mgr, CInputStream& in, int propCount,\n                                             const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 7, \"CameraHintTrigger\"))\n    return nullptr;\n\n  SActorHead aHead = LoadActorHead(in, mgr);\n  zeus::CVector3f scale = 0.5f *  in.Get<zeus::CVector3f>();\n  bool active = in.ReadBool();\n  bool deactivateOnEnter = in.ReadBool();\n  bool deactivateOnExit = in.ReadBool();\n\n  zeus::CTransform xfRot = aHead.x10_transform.getRotation();\n  if (xfRot == zeus::CTransform())\n    return new CScriptTrigger(mgr.AllocateUniqueId(), aHead.x0_name, info, aHead.x10_transform.origin,\n                              zeus::CAABox(-scale, scale), CDamageInfo(), zeus::skZero3f, ETriggerFlags::DetectPlayer,\n                              active, deactivateOnEnter, deactivateOnExit);\n\n  return new CScriptCameraHintTrigger(mgr.AllocateUniqueId(), active, aHead.x0_name, info, scale, aHead.x10_transform,\n                                      deactivateOnEnter, deactivateOnExit);\n}\n\nCEntity* ScriptLoader::LoadRumbleEffect(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 6, \"RumbleEffect\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  zeus::CVector3f position = in.Get<zeus::CVector3f>();\n  bool active = in.ReadBool();\n  float f1 = in.ReadFloat();\n  u32 w1 = in.ReadLong();\n  u32 pFlags = LoadParameterFlags(in);\n\n  return new CScriptSpecialFunction(\n      mgr.AllocateUniqueId(), name, info, ConvertEditorEulerToTransform4f(zeus::skZero3f, position),\n      CScriptSpecialFunction::ESpecialFunction::RumbleEffect, \"\", f1, w1, pFlags, 0.f, zeus::skZero3f, zeus::skBlack,\n      active, {}, {}, {}, CPlayerState::EItemType::Invalid, -1, -1, -1);\n}\n\nCEntity* ScriptLoader::LoadAmbientAI(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 16, \"AmbientAI\"))\n    return nullptr;\n\n  SScaledActorHead head = LoadScaledActorHead(in, mgr);\n  zeus::CVector3f collisionExtent =  in.Get<zeus::CVector3f>();\n  zeus::CVector3f collisionOffset =  in.Get<zeus::CVector3f>();\n  float mass = in.ReadFloat();\n  CHealthInfo hInfo(in);\n  CDamageVulnerability dVuln(in);\n  CAnimationParameters animParms = LoadAnimationParameters(in);\n  CActorParameters actParms = LoadActorParameters(in);\n  float alertRange = in.ReadFloat();\n  float impactRange = in.ReadFloat();\n  s32 alertAnim = in.ReadInt32();\n  s32 impactAnim = in.ReadInt32();\n  bool active = in.ReadBool();\n\n  if (!g_ResFactory->GetResourceTypeById(animParms.GetACSFile()).IsValid())\n    return nullptr;\n\n  zeus::CAABox aabox = GetCollisionBox(mgr, info.GetAreaId(), collisionExtent, collisionOffset);\n\n  CMaterialList matList(EMaterialTypes::Immovable, EMaterialTypes::NonSolidDamageable);\n\n  CModelData mData(CAnimRes(animParms.GetACSFile(), animParms.GetCharacter(), head.x40_scale,\n                            animParms.GetInitialAnimation(), true));\n  if ((collisionExtent.x() < 0.f || collisionExtent.y() < 0.f || collisionExtent.z() < 0.f) || collisionExtent.isZero())\n    aabox = mData.GetBounds(head.x10_transform.getRotation());\n\n  return new CAmbientAI(mgr.AllocateUniqueId(), head.x0_name, info, head.x10_transform, std::move(mData), aabox,\n                        matList, mass, hInfo, dVuln, actParms, alertRange, impactRange, alertAnim, impactAnim, active);\n}\n\nCEntity* ScriptLoader::LoadAtomicBeta(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 21, \"AtomicBeta\"))\n    return nullptr;\n  SScaledActorHead aHead = LoadScaledActorHead(in, mgr);\n  auto pair = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!pair.first)\n    return nullptr;\n  CPatternedInfo pInfo(in, pair.second);\n  CActorParameters actParms = LoadActorParameters(in);\n  CAssetId electricId(in);\n  CAssetId weaponId(in);\n  CDamageInfo dInfo(in);\n  CAssetId particleId(in);\n  float f1 = in.ReadFloat();\n  float f2 = in.ReadFloat();\n  float f3 = in.ReadFloat();\n  CDamageVulnerability dVuln(in);\n  float f4 = in.ReadFloat();\n  float f5 = in.ReadFloat();\n  float f6 = in.ReadFloat();\n  s16 sId1 = s16(in.ReadInt32() & 0xFFFF);\n  s16 sId2 = s16(in.ReadInt32() & 0xFFFF);\n  s16 sId3 = s16(in.ReadInt32() & 0xFFFF);\n  float f7 = in.ReadFloat();\n  const CAnimationParameters& animParms = pInfo.GetAnimationParameters();\n  CModelData mData(CAnimRes(animParms.GetACSFile(), animParms.GetCharacter(), aHead.x40_scale,\n                            animParms.GetInitialAnimation(), true));\n  return new MP1::CAtomicBeta(mgr.AllocateUniqueId(), aHead.x0_name, info, aHead.x10_transform, std::move(mData),\n                              actParms, pInfo, electricId, weaponId, dInfo, particleId, f1, f2, f7, dVuln, f3, f4, f5,\n                              sId1, sId2, sId3, f6);\n}\n\nCEntity* ScriptLoader::LoadIceZoomer(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 16, \"IceZoomer\"))\n    return nullptr;\n  SScaledActorHead actHead = LoadScaledActorHead(in, mgr);\n  auto pair = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!pair.first)\n    return nullptr;\n\n  CPatternedInfo pInfo(in, pair.second);\n  CActorParameters actParms = LoadActorParameters(in);\n  const CAnimationParameters& animParms = pInfo.GetAnimationParameters();\n  if (!animParms.GetACSFile().IsValid())\n    return nullptr;\n\n  float advanceWpRadius = in.ReadFloat();\n  float f2 = in.ReadFloat();\n  float alignAngleVel = in.ReadFloat();\n  float f4 = in.ReadFloat();\n  float playerObstructionMinDist = in.ReadFloat();\n  float moveFowardWeight = in.ReadFloat();\n  CAssetId modelRes(in.Get<CAssetId>());\n  CAssetId skinRes(in.Get<CAssetId>());\n  CDamageVulnerability dVuln(in);\n  float iceZoomerJointHP = in.ReadFloat();\n\n  CModelData mData(CAnimRes(animParms.GetACSFile(), animParms.GetCharacter(), actHead.x40_scale,\n                            animParms.GetInitialAnimation(), true));\n  return new MP1::CParasite(mgr.AllocateUniqueId(), actHead.x0_name, CPatterned::EFlavorType::Zero, info,\n                            actHead.x10_transform, std::move(mData), pInfo, EBodyType::WallWalker, 0.f, advanceWpRadius,\n                            f2, alignAngleVel, f4, 0.2f, 0.4f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f, moveFowardWeight, 0.f, 0.f,\n                            playerObstructionMinDist, 0.f, false, CWallWalker::EWalkerType::IceZoomer, dVuln,\n                            CDamageInfo(), 0xFFFF, 0xFFFF, 0xFFFF, modelRes, skinRes, iceZoomerJointHP, actParms);\n}\n\nCEntity* ScriptLoader::LoadPuffer(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 16, \"Puffer\"))\n    return nullptr;\n\n  SScaledActorHead aHead = LoadScaledActorHead(in, mgr);\n  auto pair = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!pair.first)\n    return nullptr;\n\n  CPatternedInfo pInfo(in, pair.second);\n  if (!pInfo.GetAnimationParameters().GetACSFile().IsValid())\n    return nullptr;\n\n  CActorParameters actorParameters = LoadActorParameters(in);\n  float hoverSpeed = in.ReadFloat();\n  CAssetId cloudEffect(in);\n  CDamageInfo cloudDamage(in);\n  CAssetId cloudSteam(in);\n  float f2 = in.ReadFloat();\n  bool b1 = in.ReadBool();\n  bool b2 = in.ReadBool();\n  bool b3 = in.ReadBool();\n  CDamageInfo explosionDamage(in);\n  s16 sfxId = in.ReadShort();\n\n  CModelData mData(CAnimRes(pInfo.GetAnimationParameters().GetACSFile(), pInfo.GetAnimationParameters().GetCharacter(),\n                            aHead.x40_scale, pInfo.GetAnimationParameters().GetInitialAnimation(), true));\n  return new MP1::CPuffer(mgr.AllocateUniqueId(), aHead.x0_name, info, aHead.x10_transform, std::move(mData),\n                          actorParameters, pInfo, hoverSpeed, cloudEffect, cloudDamage, cloudSteam, f2, b1, b2, b3,\n                          explosionDamage, sfxId);\n}\n\nCEntity* ScriptLoader::LoadTryclops(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 10, \"Tryclops\"))\n    return nullptr;\n\n  SScaledActorHead actorHead = LoadScaledActorHead(in, mgr);\n  auto pair = CPatternedInfo::HasCorrectParameterCount(in);\n\n  if (!pair.first)\n    return nullptr;\n\n  CPatternedInfo pInfo(in, pair.second);\n\n  if (!pInfo.GetAnimationParameters().GetACSFile().IsValid())\n    return nullptr;\n\n  CActorParameters actorParameters = LoadActorParameters(in);\n  float f1 = in.ReadFloat();\n  float f2 = in.ReadFloat();\n  float f3 = in.ReadFloat();\n  float f4 = in.ReadFloat();\n\n  CModelData mData(CAnimRes(pInfo.GetAnimationParameters().GetACSFile(), pInfo.GetAnimationParameters().GetCharacter(),\n                            actorHead.x40_scale, pInfo.GetAnimationParameters().GetInitialAnimation(), true));\n\n  return new MP1::CTryclops(mgr.AllocateUniqueId(), actorHead.x0_name, info, actorHead.x10_transform, std::move(mData),\n                            pInfo, actorParameters, f1, f2, f3, f4);\n}\n\nCEntity* ScriptLoader::LoadRidley(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 47, \"Ridley\"))\n    return nullptr;\n\n  SScaledActorHead aHead = LoadScaledActorHead(in, mgr);\n  auto pair = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!pair.first)\n    return nullptr;\n\n  CPatternedInfo pInfo(in, pair.second);\n  CActorParameters actParms = LoadActorParameters(in);\n  if (!pInfo.GetAnimationParameters().GetACSFile().IsValid())\n    return nullptr;\n\n  CModelData mData(CAnimRes(pInfo.GetAnimationParameters().GetACSFile(), pInfo.GetAnimationParameters().GetCharacter(),\n                            aHead.x40_scale, pInfo.GetAnimationParameters().GetInitialAnimation(), true));\n  return new MP1::CRidley(mgr.AllocateUniqueId(), aHead.x0_name, info, aHead.x10_transform, std::move(mData), pInfo,\n                          actParms, in, propCount);\n}\n\nCEntity* ScriptLoader::LoadSeedling(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 14, \"Seedling\"))\n    return nullptr;\n  SScaledActorHead aHead = LoadScaledActorHead(in, mgr);\n\n  auto pair = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!pair.first)\n    return nullptr;\n\n  CPatternedInfo pInfo(in, pair.second);\n\n  if (!pInfo.GetAnimationParameters().GetACSFile().IsValid())\n    return nullptr;\n\n  CActorParameters actParms = LoadActorParameters(in);\n  CAssetId needleId(in);\n  CAssetId weaponId(in);\n  CDamageInfo dInfo1(in);\n  CDamageInfo dInfo2(in);\n  float f1 = in.ReadFloat();\n  float f2 = in.ReadFloat();\n  float f3 = in.ReadFloat();\n  float f4 = in.ReadFloat();\n\n  CModelData mData(CAnimRes(pInfo.GetAnimationParameters().GetACSFile(), pInfo.GetAnimationParameters().GetCharacter(),\n                            aHead.x40_scale, pInfo.GetAnimationParameters().GetInitialAnimation(), true));\n  return new MP1::CSeedling(mgr.AllocateUniqueId(), aHead.x0_name, info, aHead.x10_transform, std::move(mData), pInfo,\n                            actParms, needleId, weaponId, dInfo1, dInfo2, f1, f2, f3, f4);\n}\n\nCEntity* ScriptLoader::LoadThermalHeatFader(CStateManager& mgr, CInputStream& in, int propCount,\n                                            const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 4, \"ThermalHeatFader\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  bool active = in.ReadBool();\n  float fadedLevel = in.ReadFloat();\n  float initialLevel = in.ReadFloat();\n  return new CScriptDistanceFog(mgr.AllocateUniqueId(), name, info, ERglFogMode::None, zeus::skBlack, zeus::CVector2f(),\n                                0.f, zeus::CVector2f(), false, active, fadedLevel, initialLevel, 0.f, 0.f);\n}\n\nCEntity* ScriptLoader::LoadBurrower(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 13, \"Burrower\"))\n    return nullptr;\n\n  SScaledActorHead aHead = LoadScaledActorHead(in, mgr);\n  auto pair = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!pair.first)\n    return nullptr;\n\n  CPatternedInfo pInfo(in, pair.second);\n\n  if (!pInfo.GetAnimationParameters().GetACSFile().IsValid())\n    return nullptr;\n\n  CActorParameters actParms = LoadActorParameters(in);\n  CAssetId w1(in);\n  CAssetId w2(in);\n  CAssetId w3(in);\n  CDamageInfo dInfo(in);\n\n  CAssetId w4(in);\n  u32 w5 = in.ReadLong();\n  CAssetId w6(in);\n\n  CModelData mData(CAnimRes(pInfo.GetAnimationParameters().GetACSFile(), pInfo.GetAnimationParameters().GetCharacter(),\n                            aHead.x40_scale, pInfo.GetAnimationParameters().GetInitialAnimation(), true));\n\n  return new MP1::CBurrower(mgr.AllocateUniqueId(), aHead.x0_name, info, aHead.x10_transform, std::move(mData), pInfo,\n                            actParms, w1, w2, w3, dInfo, w4, w5, w6);\n}\n\nCEntity* ScriptLoader::LoadBeam(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 7, \"Beam\"))\n    return nullptr;\n\n  SActorHead aHead = LoadActorHead(in, mgr);\n  bool active = in.ReadBool();\n  u32 weaponDescId = in.ReadLong();\n  if (!g_ResFactory->GetResourceTypeById(weaponDescId).IsValid())\n    return nullptr;\n\n  CBeamInfo beamInfo(in);\n  CDamageInfo dInfo(in);\n  TToken<CWeaponDescription> weaponDesc = g_SimplePool->GetObj({SBIG('WPSC'), weaponDescId});\n\n  return new CScriptBeam(mgr.AllocateUniqueId(), aHead.x0_name, info, aHead.x10_transform, active, weaponDesc, beamInfo,\n                         dInfo);\n}\n\nCEntity* ScriptLoader::LoadWorldLightFader(CStateManager& mgr, CInputStream& in, int propCount,\n                                           const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 4, \"WorldLightFader\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  bool active = in.ReadBool();\n  float f1 = in.ReadFloat();\n  float f2 = in.ReadFloat();\n\n  return new CScriptDistanceFog(mgr.AllocateUniqueId(), name, info, ERglFogMode::None, zeus::skBlack, zeus::skZero2f,\n                                0.f, zeus::skZero2f, false, active, 0.f, 0.f, f1, f2);\n}\n\nCEntity* ScriptLoader::LoadMetroidPrimeEssence(CStateManager& mgr, CInputStream& in, int propCount,\n                                               const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 11, \"MetroidPrimeEssence\")) {\n    return nullptr;\n  }\n\n  SScaledActorHead aHead = LoadScaledActorHead(in, mgr);\n  auto [valid, pInfoPropCount] = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!valid) {\n    return nullptr;\n  }\n  CPatternedInfo pInfo{in, pInfoPropCount};\n  CActorParameters actParms = LoadActorParameters(in);\n  CAssetId particle1{in};\n  CDamageInfo dInfo{in};\n  CAssetId electric{in};\n  u32 w3 = in.ReadLong();\n  CAssetId particle2{in};\n\n  const CAnimationParameters& animParms = pInfo.GetAnimationParameters();\n  CModelData mData{CAnimRes(animParms.GetACSFile(), animParms.GetCharacter(), aHead.x40_scale,\n                            animParms.GetInitialAnimation(), true)};\n  return new MP1::CMetroidPrimeStage2(mgr.AllocateUniqueId(), aHead.x0_name, info, aHead.x10_transform,\n                                       std::move(mData), pInfo, actParms, particle1, dInfo, aHead.x40_scale.y(),\n                                       electric, w3, particle2);\n};\n\nCEntity* ScriptLoader::LoadMetroidPrimeStage1(CStateManager& mgr, CInputStream& in, int propCount,\n                                              const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 22, \"MetroidPrimeStage1\"))\n    return nullptr;\n  u32 version = in.ReadLong();\n  if (version != 3)\n    return nullptr;\n\n  SScaledActorHead aHead = LoadScaledActorHead(in, mgr);\n  bool active = in.ReadBool();\n  float f1 = in.ReadFloat();\n  float f2 = in.ReadFloat();\n  float f3 = in.ReadFloat();\n  u32 w1 = in.ReadLong();\n  bool b1 = in.ReadBool();\n  u32 w2 = in.ReadLong();\n  CHealthInfo hInfo1(in);\n  CHealthInfo hInfo2(in);\n  u32 w3 = in.ReadLong();\n  rstl::reserved_vector<MP1::CMetroidPrimeAttackWeights, 4> roomParms;\n  for (int i = 0; i < 4; ++i)\n    roomParms.emplace_back(in);\n  u32 w4 = in.ReadLong();\n  u32 w5 = in.ReadLong();\n  MP1::CMetroidPrimeData primeParms(in);\n\n  return new MP1::CMetroidPrimeRelay(mgr.AllocateUniqueId(), aHead.x0_name, info, active, aHead.x10_transform,\n                                     aHead.x40_scale, std::move(primeParms), f1, f2, f3, w1, b1, w2, hInfo1, hInfo2, w3,\n                                     w4, w5, std::move(roomParms));\n}\n\nCEntity* ScriptLoader::LoadMazeNode(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 10, \"MazeNode\"))\n    return nullptr;\n\n  SActorHead aHead = LoadActorHead(in, mgr);\n  bool active = in.ReadBool();\n  u32 col = in.ReadLong();\n  u32 row = in.ReadLong();\n  u32 side = in.ReadLong();\n  zeus::CVector3f actorPos =  in.Get<zeus::CVector3f>();\n  zeus::CVector3f triggerPos =  in.Get<zeus::CVector3f>();\n  zeus::CVector3f effectPos =  in.Get<zeus::CVector3f>();\n\n  return new CScriptMazeNode(mgr.AllocateUniqueId(), aHead.x0_name, info, aHead.x10_transform, active, col, row, side,\n                             actorPos, triggerPos, effectPos);\n}\n\nCEntity* ScriptLoader::LoadOmegaPirate(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, skElitePiratePropCount + 1, \"OmegaPirate\")) {\n    return nullptr;\n  }\n\n  SScaledActorHead actHead = LoadScaledActorHead(in, mgr);\n  auto pair = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!pair.first) {\n    return nullptr;\n  }\n\n  CPatternedInfo pInfo(in, pair.second);\n  CActorParameters actParms = LoadActorParameters(in);\n  MP1::CElitePirateData elitePirateData(in, propCount);\n\n  if (!pInfo.GetAnimationParameters().GetACSFile().IsValid()) {\n    return nullptr;\n  }\n\n  const CAssetId skeletonModelId{in};\n  const CAssetId skeletonSkinRulesId{in};\n  const CAssetId skeletonLayoutInfoId{in};\n  CModelData mData(CAnimRes(pInfo.GetAnimationParameters().GetACSFile(), pInfo.GetAnimationParameters().GetCharacter(),\n                            actHead.x40_scale, pInfo.GetAnimationParameters().GetInitialAnimation(), true));\n  return new MP1::COmegaPirate(mgr.AllocateUniqueId(), actHead.x0_name, info, actHead.x10_transform, std::move(mData),\n                               pInfo, actParms, elitePirateData, skeletonModelId, skeletonSkinRulesId,\n                               skeletonLayoutInfoId);\n}\n\nCEntity* ScriptLoader::LoadPhazonPool(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 18, \"PhazonPool\")) {\n    return nullptr;\n  }\n\n  SScaledActorHead actHead = LoadScaledActorHead(in, mgr);\n  bool active = in.ReadBool();\n  CAssetId w1{in};\n  CAssetId w2{in};\n  CAssetId w3{in};\n  CAssetId w4{in};\n  u32 u1 = in.ReadLong();\n  CDamageInfo dInfo{in};\n  zeus::CVector3f orientedForce = in.Get<zeus::CVector3f>();\n  ETriggerFlags triggerFlags = static_cast<ETriggerFlags>(in.ReadLong());\n  float f1 = in.ReadFloat();\n  float f2 = in.ReadFloat();\n  float f3 = in.ReadFloat();\n  bool b2 = in.ReadBool();\n  float f4 = in.ReadFloat();\n\n  return new MP1::CPhazonPool(mgr.AllocateUniqueId(), actHead.x0_name, info,\n                              zeus::CTransform::Translate(actHead.x10_transform.origin), actHead.x40_scale, active, w1,\n                              w2, w3, w4, u1, dInfo, orientedForce, triggerFlags, b2, f1, f2, f3, f4);\n}\n\nCEntity* ScriptLoader::LoadPhazonHealingNodule(CStateManager& mgr, CInputStream& in, int propCount,\n                                               const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 9, \"PhazonHealingNodule\")) {\n    return nullptr;\n  }\n\n  SScaledActorHead actHead = LoadScaledActorHead(in, mgr);\n  auto pair = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!pair.first) {\n    return nullptr;\n  }\n\n  CPatternedInfo pInfo(in, pair.second);\n  CActorParameters actParms = LoadActorParameters(in);\n\n  in.ReadBool();\n  CAssetId w1{in};\n  std::string w2 = in.Get<std::string>();\n\n  if (!pInfo.GetAnimationParameters().GetACSFile().IsValid()) {\n    return nullptr;\n  }\n\n  CModelData mData(CAnimRes(pInfo.GetAnimationParameters().GetACSFile(), pInfo.GetAnimationParameters().GetCharacter(),\n                            actHead.x40_scale, pInfo.GetAnimationParameters().GetInitialAnimation(), true));\n\n  return new MP1::CPhazonHealingNodule(mgr.AllocateUniqueId(), actHead.x0_name, info, actHead.x10_transform,\n                                       std::move(mData), actParms, pInfo, w1, std::move(w2));\n}\n\nCEntity* ScriptLoader::LoadNewCameraShaker(CStateManager& mgr, CInputStream& in, int propCount,\n                                           const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 8, \"NewCameraShaker\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  zeus::CVector3f sfxPos =  in.Get<zeus::CVector3f>();\n  bool active = in.ReadBool();\n  u32 flags = LoadParameterFlags(in);\n  float duration = in.ReadFloat();\n  float sfxDist = in.ReadFloat();\n  CCameraShakerComponent shakerX = CCameraShakerComponent::LoadNewCameraShakerComponent(in);\n  CCameraShakerComponent shakerY = CCameraShakerComponent::LoadNewCameraShakerComponent(in);\n  CCameraShakerComponent shakerZ = CCameraShakerComponent::LoadNewCameraShakerComponent(in);\n\n  CCameraShakeData shakeData(duration, sfxDist, flags, sfxPos, shakerX, shakerY, shakerZ);\n\n  return new CScriptCameraShaker(mgr.AllocateUniqueId(), name, info, active, shakeData);\n}\n\nCEntity* ScriptLoader::LoadShadowProjector(CStateManager& mgr, CInputStream& in, int propCount,\n                                           const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 10, \"ShadowProjector\"))\n    return nullptr;\n\n  std::string name = mgr.HashInstanceName(in);\n  zeus::CVector3f position( in.Get<zeus::CVector3f>());\n  bool b1 = in.ReadBool();\n  float f1 = in.ReadFloat();\n  zeus::CVector3f vec2( in.Get<zeus::CVector3f>());\n  float f2 = in.ReadFloat();\n  float f3 = in.ReadFloat();\n  float f4 = in.ReadFloat();\n  bool b2 = in.ReadBool();\n  u32 w1 = in.ReadLong();\n  return new CScriptShadowProjector(mgr.AllocateUniqueId(), name, info, zeus::CTransform::Translate(position), b1, vec2,\n                                    b2, f1, f2, f3, f4, w1);\n}\n\nCEntity* ScriptLoader::LoadEnergyBall(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {\n  if (!EnsurePropertyCount(propCount, 16, \"EnergyBall\"))\n    return nullptr;\n\n  SScaledActorHead actorHead = LoadScaledActorHead(in, mgr);\n  const auto pair = CPatternedInfo::HasCorrectParameterCount(in);\n  if (!pair.first)\n    return nullptr;\n\n  CPatternedInfo pInfo(in, pair.second);\n  const CAnimationParameters& animParms = pInfo.GetAnimationParameters();\n  if (!animParms.GetACSFile().IsValid())\n    return nullptr;\n\n  CActorParameters actParms = LoadActorParameters(in);\n  u32 w1 = in.ReadLong();\n  float f1 = in.ReadFloat();\n  CDamageInfo dInfo1(in);\n  float f2 = in.ReadFloat();\n  CAssetId a1(in);\n  s16 sfxId1 = CSfxManager::TranslateSFXID(in.ReadLong());\n  CAssetId a2(in);\n  CAssetId a3(in);\n  s16 sfxId2 = CSfxManager::TranslateSFXID(in.ReadLong());\n  float f3 = in.ReadFloat();\n  float f4 = in.ReadFloat();\n  CAssetId a4(in);\n\n  CDamageInfo dInfo2 = propCount >= 19 ? CDamageInfo(in) : CDamageInfo();\n  float f5 = propCount >= 20 ? in.ReadFloat() : 3.0f;\n\n  CModelData mData(CAnimRes(animParms.GetACSFile(), animParms.GetCharacter(), actorHead.x40_scale,\n                            animParms.GetInitialAnimation(), true));\n  return new MP1::CEnergyBall(mgr.AllocateUniqueId(), actorHead.x0_name, info, actorHead.x10_transform,\n                              std::move(mData), actParms, pInfo, w1, f1, dInfo1, f2, a1, sfxId1, a2, a3, sfxId2, f3, f4,\n                              a4, dInfo2, f5);\n}\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/ScriptLoader.hpp",
    "content": "#pragma once\n\n#include \"Runtime/Streams/IOStreams.hpp\"\n\n#include <zeus/CTransform.hpp>\n#include <zeus/CVector3f.hpp>\n\nnamespace metaforce {\nclass CActorParameters;\nclass CAnimationParameters;\nclass CCameraShakeData;\nclass CEntity;\nclass CEntityInfo;\nclass CFluidUVMotion;\nclass CGrappleParameters;\nclass CLightParameters;\nclass CScannableParameters;\nclass CStateManager;\nclass CVisorParameters;\n\nusing FScriptLoader = CEntity* (*)(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n\nclass ScriptLoader {\npublic:\n  static u32 LoadParameterFlags(CInputStream& in);\n  static CGrappleParameters LoadGrappleParameters(CInputStream& in);\n  static CActorParameters LoadActorParameters(CInputStream& in);\n  static CVisorParameters LoadVisorParameters(CInputStream& in);\n  static CScannableParameters LoadScannableParameters(CInputStream& in);\n  static CLightParameters LoadLightParameters(CInputStream& in);\n  static CAnimationParameters LoadAnimationParameters(CInputStream& in);\n  static CFluidUVMotion LoadFluidUVMotion(CInputStream& in);\n  static zeus::CTransform ConvertEditorEulerToTransform4f(const zeus::CVector3f& orientation,\n                                                          const zeus::CVector3f& position);\n\n  static CEntity* LoadActor(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadWaypoint(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadDoor(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadTrigger(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadTimer(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadCounter(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadEffect(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadPlatform(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadSound(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadGenerator(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadDock(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadCamera(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadCameraWaypoint(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadNewIntroBoss(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadSpawnPoint(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadCameraHint(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadPickup(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadMemoryRelay(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadRandomRelay(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadRelay(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadBeetle(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadHUDMemo(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadCameraFilterKeyframe(CStateManager& mgr, CInputStream& in, int propCount,\n                                           const CEntityInfo& info);\n  static CEntity* LoadCameraBlurKeyframe(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadDamageableTrigger(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadDebris(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadCameraShaker(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadActorKeyframe(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadWater(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadWarWasp(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadSpacePirate(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadFlyingPirate(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadElitePirate(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadMetroidBeta(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadChozoGhost(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadCoverPoint(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadSpiderBallWaypoint(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadBloodFlower(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadFlickerBat(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadPathCamera(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadGrapplePoint(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadPuddleSpore(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadDebugCameraWaypoint(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadSpiderBallAttractionSurface(CStateManager& mgr, CInputStream& in, int propCount,\n                                                  const CEntityInfo& info);\n  static CEntity* LoadPuddleToadGamma(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadDistanceFog(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadFireFlea(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadMetaree(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadDockAreaChange(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadActorRotate(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadSpecialFunction(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadSpankWeed(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadParasite(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadPlayerHint(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadRipper(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadPickupGenerator(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadAIKeyframe(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadPointOfInterest(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadDrone(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadMetroid(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadDebrisExtended(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadSteam(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadRipple(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadBallTrigger(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadTargetingPoint(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadEMPulse(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadIceSheegoth(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadPlayerActor(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadFlaahgra(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadAreaAttributes(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadFishCloud(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadFishCloudModifier(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadVisorFlare(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadWorldTeleporter(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadVisorGoo(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadJellyZap(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadControllerAction(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadSwitch(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadPlayerStateChange(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadThardus(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadWallCrawlerSwarm(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadAiJumpPoint(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadFlaahgraTentacle(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadRoomAcoustics(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadColorModulate(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadThardusRockProjectile(CStateManager& mgr, CInputStream& in, int propCount,\n                                            const CEntityInfo& info);\n  static CEntity* LoadMidi(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadStreamedAudio(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadRepulsor(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadGunTurret(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadFogVolume(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadBabygoth(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadEyeball(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadRadialDamage(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadCameraPitchVolume(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadEnvFxDensityController(CStateManager& mgr, CInputStream& in, int propCount,\n                                             const CEntityInfo& info);\n  static CEntity* LoadMagdolite(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadTeamAIMgr(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadSnakeWeedSwarm(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadActorContraption(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadOculus(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadGeemer(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadSpindleCamera(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadAtomicAlpha(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadCameraHintTrigger(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadRumbleEffect(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadAmbientAI(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadAtomicBeta(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadIceZoomer(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadPuffer(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadTryclops(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadRidley(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadSeedling(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadThermalHeatFader(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadBurrower(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadBeam(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadWorldLightFader(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadMetroidPrimeEssence(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadMetroidPrimeStage1(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadMazeNode(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadOmegaPirate(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadPhazonPool(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadPhazonHealingNodule(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadNewCameraShaker(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadShadowProjector(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n  static CEntity* LoadEnergyBall(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info);\n};\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/ScriptObjectSupport.cpp",
    "content": "#include \"Runtime/World/ScriptObjectSupport.hpp\"\n\nusing namespace std::literals;\n\nnamespace metaforce {\n\nstd::string_view ScriptObjectTypeToStr(EScriptObjectType type) {\n  switch (type) {\n  case EScriptObjectType::Actor:\n    return \"Actor\"sv;\n  case EScriptObjectType::Waypoint:\n    return \"Waypoint\"sv;\n  case EScriptObjectType::Door:\n    return \"Door\"sv;\n  case EScriptObjectType::Trigger:\n    return \"Trigger\"sv;\n  case EScriptObjectType::Timer:\n    return \"Timer\"sv;\n  case EScriptObjectType::Counter:\n    return \"Counter\"sv;\n  case EScriptObjectType::Effect:\n    return \"Effect\"sv;\n  case EScriptObjectType::Platform:\n    return \"Platform\"sv;\n  case EScriptObjectType::Sound:\n    return \"Sound\"sv;\n  case EScriptObjectType::Generator:\n    return \"Generator\"sv;\n  case EScriptObjectType::Dock:\n    return \"Dock\"sv;\n  case EScriptObjectType::Camera:\n    return \"Camera\"sv;\n  case EScriptObjectType::CameraWaypoint:\n    return \"CameraWaypoint\"sv;\n  case EScriptObjectType::NewIntroBoss:\n    return \"NewIntroBoss\"sv;\n  case EScriptObjectType::SpawnPoint:\n    return \"SpawnPoint\"sv;\n  case EScriptObjectType::CameraHint:\n    return \"CameraHint\"sv;\n  case EScriptObjectType::Pickup:\n    return \"Pickup\"sv;\n  case EScriptObjectType::MemoryRelay:\n    return \"MemoryRelay\"sv;\n  case EScriptObjectType::RandomRelay:\n    return \"RandomRelay\"sv;\n  case EScriptObjectType::Relay:\n    return \"Relay\"sv;\n  case EScriptObjectType::Beetle:\n    return \"Beetle\"sv;\n  case EScriptObjectType::HUDMemo:\n    return \"HUDMemo\"sv;\n  case EScriptObjectType::CameraFilterKeyframe:\n    return \"CameraFilterKeyframe\"sv;\n  case EScriptObjectType::CameraBlurKeyframe:\n    return \"CameraBlurKeyframe\"sv;\n  case EScriptObjectType::DamageableTrigger:\n    return \"DamageableTrigger\"sv;\n  case EScriptObjectType::Debris:\n    return \"Debris\"sv;\n  case EScriptObjectType::CameraShaker:\n    return \"CameraShaker\"sv;\n  case EScriptObjectType::ActorKeyframe:\n    return \"ActorKeyframe\"sv;\n  case EScriptObjectType::Water:\n    return \"Water\"sv;\n  case EScriptObjectType::Warwasp:\n    return \"Warwasp\"sv;\n  case EScriptObjectType::SpacePirate:\n    return \"SpacePirate\"sv;\n  case EScriptObjectType::FlyingPirate:\n    return \"FlyingPirate\"sv;\n  case EScriptObjectType::ElitePirate:\n    return \"ElitePirate\"sv;\n  case EScriptObjectType::MetroidBeta:\n    return \"MetroidBeta\"sv;\n  case EScriptObjectType::ChozoGhost:\n    return \"ChozoGhost\"sv;\n  case EScriptObjectType::CoverPoint:\n    return \"CoverPoint\"sv;\n  case EScriptObjectType::SpiderBallWaypoint:\n    return \"SpiderBallWaypoint\"sv;\n  case EScriptObjectType::BloodFlower:\n    return \"BloodFlower\"sv;\n  case EScriptObjectType::FlickerBat:\n    return \"FlickerBat\"sv;\n  case EScriptObjectType::PathCamera:\n    return \"PathCamera\"sv;\n  case EScriptObjectType::GrapplePoint:\n    return \"GrapplePoint\"sv;\n  case EScriptObjectType::PuddleSpore:\n    return \"PuddleSpore\"sv;\n  case EScriptObjectType::DebugCameraWaypoint:\n    return \"DebugCameraWaypoint\"sv;\n  case EScriptObjectType::SpiderBallAttractionSurface:\n    return \"SpiderBallAttractionSurface\"sv;\n  case EScriptObjectType::PuddleToadGamma:\n    return \"PuddleToadGamma\"sv;\n  case EScriptObjectType::DistanceFog:\n    return \"DistanceFog\"sv;\n  case EScriptObjectType::FireFlea:\n    return \"FireFlea\"sv;\n  case EScriptObjectType::Metaree:\n    return \"Metaree\"sv;\n  case EScriptObjectType::DockAreaChange:\n    return \"DockAreaChange\"sv;\n  case EScriptObjectType::ActorRotate:\n    return \"ActorRotate\"sv;\n  case EScriptObjectType::SpecialFunction:\n    return \"SpecialFunction\"sv;\n  case EScriptObjectType::SpankWeed:\n    return \"SpankWeed\"sv;\n  case EScriptObjectType::Parasite:\n    return \"Parasite\"sv;\n  case EScriptObjectType::PlayerHint:\n    return \"PlayerHint\"sv;\n  case EScriptObjectType::Ripper:\n    return \"Ripper\"sv;\n  case EScriptObjectType::PickupGenerator:\n    return \"PickupGenerator\"sv;\n  case EScriptObjectType::AIKeyframe:\n    return \"AIKeyframe\"sv;\n  case EScriptObjectType::PointOfInterest:\n    return \"PointOfInterest\"sv;\n  case EScriptObjectType::Drone:\n    return \"Drone\"sv;\n  case EScriptObjectType::Metroid:\n    return \"Metroid\"sv;\n  case EScriptObjectType::DebrisExtended:\n    return \"DebrisExtended\"sv;\n  case EScriptObjectType::Steam:\n    return \"Steam\"sv;\n  case EScriptObjectType::Ripple:\n    return \"Ripple\"sv;\n  case EScriptObjectType::BallTrigger:\n    return \"BallTrigger\"sv;\n  case EScriptObjectType::TargetingPoint:\n    return \"TargetingPoint\"sv;\n  case EScriptObjectType::EMPulse:\n    return \"EMPulse\"sv;\n  case EScriptObjectType::IceSheegoth:\n    return \"IceSheegoth\"sv;\n  case EScriptObjectType::PlayerActor:\n    return \"PlayerActor\"sv;\n  case EScriptObjectType::Flaahgra:\n    return \"Flaahgra\"sv;\n  case EScriptObjectType::AreaAttributes:\n    return \"AreaAttributes\"sv;\n  case EScriptObjectType::FishCloud:\n    return \"FishCloud\"sv;\n  case EScriptObjectType::FishCloudModifier:\n    return \"FishCloudModifier\"sv;\n  case EScriptObjectType::VisorFlare:\n    return \"VisorFlare\"sv;\n  case EScriptObjectType::WorldTeleporter:\n    return \"WorldTeleporter\"sv;\n  case EScriptObjectType::VisorGoo:\n    return \"VisorGoo\"sv;\n  case EScriptObjectType::JellyZap:\n    return \"JellyZap\"sv;\n  case EScriptObjectType::ControllerAction:\n    return \"ControllerAction\"sv;\n  case EScriptObjectType::Switch:\n    return \"Switch\"sv;\n  case EScriptObjectType::PlayerStateChange:\n    return \"PlayerStateChange\"sv;\n  case EScriptObjectType::Thardus:\n    return \"Thardus\"sv;\n  case EScriptObjectType::WallCrawlerSwarm:\n    return \"WallCrawlerSwarm\"sv;\n  case EScriptObjectType::AIJumpPoint:\n    return \"AIJumpPoint\"sv;\n  case EScriptObjectType::FlaahgraTentacle:\n    return \"FlaahgraTentacle\"sv;\n  case EScriptObjectType::RoomAcoustics:\n    return \"RoomAcoustics\"sv;\n  case EScriptObjectType::ColorModulate:\n    return \"ColorModulate\"sv;\n  case EScriptObjectType::ThardusRockProjectile:\n    return \"ThardusRockProjectile\"sv;\n  case EScriptObjectType::Midi:\n    return \"Midi\"sv;\n  case EScriptObjectType::StreamedAudio:\n    return \"StreamedAudio\"sv;\n  case EScriptObjectType::WorldTeleporterToo:\n    return \"WorldTeleporterToo\"sv;\n  case EScriptObjectType::Repulsor:\n    return \"Repulsor\"sv;\n  case EScriptObjectType::GunTurret:\n    return \"GunTurret\"sv;\n  case EScriptObjectType::FogVolume:\n    return \"FogVolume\"sv;\n  case EScriptObjectType::Babygoth:\n    return \"Babygoth\"sv;\n  case EScriptObjectType::Eyeball:\n    return \"Eyeball\"sv;\n  case EScriptObjectType::RadialDamage:\n    return \"RadialDamage\"sv;\n  case EScriptObjectType::CameraPitchVolume:\n    return \"CameraPitchVolume\"sv;\n  case EScriptObjectType::EnvFxDensityController:\n    return \"EnvFxDensityController\"sv;\n  case EScriptObjectType::Magdolite:\n    return \"Magdolite\"sv;\n  case EScriptObjectType::TeamAIMgr:\n    return \"TeamAIMgr\"sv;\n  case EScriptObjectType::SnakeWeedSwarm:\n    return \"SnakeWeedSwarm\"sv;\n  case EScriptObjectType::ActorContraption:\n    return \"ActorContraption\"sv;\n  case EScriptObjectType::Oculus:\n    return \"Oculus\"sv;\n  case EScriptObjectType::Geemer:\n    return \"Geemer\"sv;\n  case EScriptObjectType::SpindleCamera:\n    return \"SpindleCamera\"sv;\n  case EScriptObjectType::AtomicAlpha:\n    return \"AtomicAlpha\"sv;\n  case EScriptObjectType::CameraHintTrigger:\n    return \"CameraHintTrigger\"sv;\n  case EScriptObjectType::RumbleEffect:\n    return \"RumbleEffect\"sv;\n  case EScriptObjectType::AmbientAI:\n    return \"AmbientAI\"sv;\n  case EScriptObjectType::AtomicBeta:\n    return \"AtomicBeta\"sv;\n  case EScriptObjectType::IceZoomer:\n    return \"IceZoomer\"sv;\n  case EScriptObjectType::Puffer:\n    return \"Puffer\"sv;\n  case EScriptObjectType::Tryclops:\n    return \"Tryclops\"sv;\n  case EScriptObjectType::Ridley:\n    return \"Ridley\"sv;\n  case EScriptObjectType::Seedling:\n    return \"Seedling\"sv;\n  case EScriptObjectType::ThermalHeatFader:\n    return \"ThermalHeatFader\"sv;\n  case EScriptObjectType::Burrower:\n    return \"Burrower\"sv;\n  case EScriptObjectType::ScriptBeam:\n    return \"ScriptBeam\"sv;\n  case EScriptObjectType::WorldLightFader:\n    return \"WorldLightFader\"sv;\n  case EScriptObjectType::MetroidPrimeStage2:\n    return \"MetroidPrimeStage2\"sv;\n  case EScriptObjectType::MetroidPrimeStage1:\n    return \"MetroidPrimeStage1\"sv;\n  case EScriptObjectType::MazeNode:\n    return \"MazeNode\"sv;\n  case EScriptObjectType::OmegaPirate:\n    return \"OmegaPirate\"sv;\n  case EScriptObjectType::PhazonPool:\n    return \"PhazonPool\"sv;\n  case EScriptObjectType::PhazonHealingNodule:\n    return \"PhazonHealingNodule\"sv;\n  case EScriptObjectType::NewCameraShaker:\n    return \"NewCameraShaker\"sv;\n  case EScriptObjectType::ShadowProjector:\n    return \"ShadowProjector\"sv;\n  case EScriptObjectType::EnergyBall:\n    return \"EnergyBall\"sv;\n  default:\n    return \"[unknown]\"sv;\n  }\n}\n\nstd::string_view ScriptObjectStateToStr(EScriptObjectState state) {\n  switch (state) {\n  case EScriptObjectState::Any:\n    return \"Any\"sv;\n  case EScriptObjectState::Active:\n    return \"Active\"sv;\n  case EScriptObjectState::Arrived:\n    return \"Arrived\"sv;\n  case EScriptObjectState::Closed:\n    return \"Closed\"sv;\n  case EScriptObjectState::Entered:\n    return \"Entered\"sv;\n  case EScriptObjectState::Exited:\n    return \"Exited\"sv;\n  case EScriptObjectState::Inactive:\n    return \"Inactive\"sv;\n  case EScriptObjectState::Inside:\n    return \"Inside\"sv;\n  case EScriptObjectState::MaxReached:\n    return \"MaxReached\"sv;\n  case EScriptObjectState::Open:\n    return \"Open\"sv;\n  case EScriptObjectState::Zero:\n    return \"Zero\"sv;\n  case EScriptObjectState::Attack:\n    return \"Attack\"sv;\n  case EScriptObjectState::CloseIn:\n    return \"CloseIn\"sv;\n  case EScriptObjectState::Retreat:\n    return \"Retreat\"sv;\n  case EScriptObjectState::Patrol:\n    return \"Patrol\"sv;\n  case EScriptObjectState::Dead:\n    return \"Dead\"sv;\n  case EScriptObjectState::CameraPath:\n    return \"CameraPath\"sv;\n  case EScriptObjectState::CameraTarget:\n    return \"CameraTarget\"sv;\n  case EScriptObjectState::DeactivateState:\n    return \"DeactivateState\"sv;\n  case EScriptObjectState::Play:\n    return \"Play\"sv;\n  case EScriptObjectState::MassiveDeath:\n    return \"MassiveDeath\"sv;\n  case EScriptObjectState::DeathRattle:\n    return \"DeathRattle\"sv;\n  case EScriptObjectState::AboutToMassivelyDie:\n    return \"AboutToMassivelyDie\"sv;\n  case EScriptObjectState::Damage:\n    return \"Damage\"sv;\n  case EScriptObjectState::InvulnDamage:\n    return \"InvulnDamage\"sv;\n  case EScriptObjectState::MassiveFrozenDeath:\n    return \"MassiveFrozenDeath\"sv;\n  case EScriptObjectState::Modify:\n    return \"Modify\"sv;\n  case EScriptObjectState::ScanStart:\n    return \"ScanStart\"sv;\n  case EScriptObjectState::ScanProcessing:\n    return \"ScanProcessing\"sv;\n  case EScriptObjectState::ScanDone:\n    return \"ScanDone\"sv;\n  case EScriptObjectState::UnFrozen:\n    return \"UnFrozen\"sv;\n  case EScriptObjectState::Default:\n    return \"Default\"sv;\n  case EScriptObjectState::ReflectedDamage:\n    return \"ReflectedDamage\"sv;\n  case EScriptObjectState::InheritBounds:\n    return \"InheritBounds\"sv;\n  default:\n    return \"[unknown]\"sv;\n  }\n}\n\nstd::string_view ScriptObjectMessageToStr(EScriptObjectMessage message) {\n  switch (message) {\n  case EScriptObjectMessage::None:\n    return \"None\"sv;\n  case EScriptObjectMessage::UNKM0:\n    return \"UNKM0\"sv;\n  case EScriptObjectMessage::Activate:\n    return \"Activate\"sv;\n  case EScriptObjectMessage::Arrived:\n    return \"Arrived\"sv;\n  case EScriptObjectMessage::Close:\n    return \"Close\"sv;\n  case EScriptObjectMessage::Deactivate:\n    return \"Deactivate\"sv;\n  case EScriptObjectMessage::Decrement:\n    return \"Decrement\"sv;\n  case EScriptObjectMessage::Follow:\n    return \"Follow\"sv;\n  case EScriptObjectMessage::Increment:\n    return \"Increment\"sv;\n  case EScriptObjectMessage::Next:\n    return \"Next\"sv;\n  case EScriptObjectMessage::Open:\n    return \"Open\"sv;\n  case EScriptObjectMessage::Reset:\n    return \"Reset\"sv;\n  case EScriptObjectMessage::ResetAndStart:\n    return \"ResetAndStart\"sv;\n  case EScriptObjectMessage::SetToMax:\n    return \"SetToMax\"sv;\n  case EScriptObjectMessage::SetToZero:\n    return \"SetToZero\"sv;\n  case EScriptObjectMessage::Start:\n    return \"Start\"sv;\n  case EScriptObjectMessage::Stop:\n    return \"Stop\"sv;\n  case EScriptObjectMessage::StopAndReset:\n    return \"StopAndReset\"sv;\n  case EScriptObjectMessage::ToggleActive:\n    return \"ToggleActive\"sv;\n  case EScriptObjectMessage::UNKM18:\n    return \"UNKM18\"sv;\n  case EScriptObjectMessage::Action:\n    return \"Action\"sv;\n  case EScriptObjectMessage::Play:\n    return \"Play\"sv;\n  case EScriptObjectMessage::Alert:\n    return \"Alert\"sv;\n  case EScriptObjectMessage::InternalMessage00:\n    return \"InternalMessage00\"sv;\n  case EScriptObjectMessage::OnFloor:\n    return \"OnFloor\"sv;\n  case EScriptObjectMessage::InternalMessage02:\n    return \"InternalMessage02\"sv;\n  case EScriptObjectMessage::InternalMessage03:\n    return \"InternalMessage03\"sv;\n  case EScriptObjectMessage::Falling:\n    return \"Falling\"sv;\n  case EScriptObjectMessage::OnIceSurface:\n    return \"OnIceSurface\"sv;\n  case EScriptObjectMessage::OnMudSlowSurface:\n    return \"OnMudSlowSurface\"sv;\n  case EScriptObjectMessage::OnNormalSurface:\n    return \"OnNormalSurface\"sv;\n  case EScriptObjectMessage::Touched:\n    return \"Touched\"sv;\n  case EScriptObjectMessage::AddPlatformRider:\n    return \"AddPlatformRider\"sv;\n  case EScriptObjectMessage::LandOnNotFloor:\n    return \"LandOnNotFloor\"sv;\n  case EScriptObjectMessage::Registered:\n    return \"Registered\"sv;\n  case EScriptObjectMessage::Deleted:\n    return \"Deleted\"sv;\n  case EScriptObjectMessage::InitializedInArea:\n    return \"InitializedInArea\"sv;\n  case EScriptObjectMessage::WorldInitialized:\n    return \"WorldInitialized\"sv;\n  case EScriptObjectMessage::AddSplashInhabitant:\n    return \"AddSplashInhabitant\"sv;\n  case EScriptObjectMessage::UpdateSplashInhabitant:\n    return \"UpdateSplashInhabitant\"sv;\n  case EScriptObjectMessage::RemoveSplashInhabitant:\n    return \"RemoveSplashInhabitant\"sv;\n  case EScriptObjectMessage::Jumped:\n    return \"Jumped\"sv;\n  case EScriptObjectMessage::Damage:\n    return \"Damage\"sv;\n  case EScriptObjectMessage::InvulnDamage:\n    return \"InvulnDamage\"sv;\n  case EScriptObjectMessage::ProjectileCollide:\n    return \"ProjectileCollide\"sv;\n  case EScriptObjectMessage::InSnakeWeed:\n    return \"InSnakeWeed\"sv;\n  case EScriptObjectMessage::AddPhazonPoolInhabitant:\n    return \"AddPhazonPoolInhabitant\"sv;\n  case EScriptObjectMessage::UpdatePhazonPoolInhabitant:\n    return \"UpdatePhazonPoolInhabitant\"sv;\n  case EScriptObjectMessage::RemovePhazonPoolInhabitant:\n    return \"RemovePhazonPoolInhabitant\"sv;\n  case EScriptObjectMessage::SuspendedMove:\n    return \"SuspendedMove\"sv;\n  default:\n    return \"[unknown]\"sv;\n  }\n}\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/World/ScriptObjectSupport.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\nnamespace metaforce {\n\nenum class EScriptObjectType {\n  Actor = 0x00,\n  Waypoint = 0x02,\n  Door = 0x03,\n  Trigger = 0x04,\n  Timer = 0x05,\n  Counter = 0x06,\n  Effect = 0x07,\n  Platform = 0x08,\n  Sound = 0x09,\n  Generator = 0x0A,\n  Dock = 0x0B,\n  Camera = 0x0C,\n  CameraWaypoint = 0x0D,\n  NewIntroBoss = 0x0E,\n  SpawnPoint = 0x0F,\n  CameraHint = 0x10,\n  Pickup = 0x11,\n  MemoryRelay = 0x13,\n  RandomRelay = 0x14,\n  Relay = 0x15,\n  Beetle = 0x16,\n  HUDMemo = 0x17,\n  CameraFilterKeyframe = 0x18,\n  CameraBlurKeyframe = 0x19,\n  DamageableTrigger = 0x1A,\n  Debris = 0x1B,\n  CameraShaker = 0x1C,\n  ActorKeyframe = 0x1D,\n  Water = 0x20,\n  Warwasp = 0x21,\n  SpacePirate = 0x24,\n  FlyingPirate = 0x25,\n  ElitePirate = 0x26,\n  MetroidBeta = 0x27,\n  ChozoGhost = 0x28,\n  CoverPoint = 0x2A,\n  SpiderBallWaypoint = 0x2C,\n  BloodFlower = 0x2D,\n  FlickerBat = 0x2E,\n  PathCamera = 0x2F,\n  GrapplePoint = 0x30,\n  PuddleSpore = 0x31,\n  DebugCameraWaypoint = 0x32,\n  SpiderBallAttractionSurface = 0x33,\n  PuddleToadGamma = 0x34,\n  DistanceFog = 0x35,\n  FireFlea = 0x36,\n  Metaree = 0x37,\n  DockAreaChange = 0x38,\n  ActorRotate = 0x39,\n  SpecialFunction = 0x3A,\n  SpankWeed = 0x3B,\n  Parasite = 0x3D,\n  PlayerHint = 0x3E,\n  Ripper = 0x3F,\n  PickupGenerator = 0x40,\n  AIKeyframe = 0x41,\n  PointOfInterest = 0x42,\n  Drone = 0x43,\n  Metroid = 0x44,\n  DebrisExtended = 0x45,\n  Steam = 0x46,\n  Ripple = 0x47,\n  BallTrigger = 0x48,\n  TargetingPoint = 0x49,\n  EMPulse = 0x4A,\n  IceSheegoth = 0x4B,\n  PlayerActor = 0x4C,\n  Flaahgra = 0x4D,\n  AreaAttributes = 0x4E,\n  FishCloud = 0x4F,\n  FishCloudModifier = 0x50,\n  VisorFlare = 0x51,\n  WorldTeleporter = 0x52,\n  VisorGoo = 0x53,\n  JellyZap = 0x54,\n  ControllerAction = 0x55,\n  Switch = 0x56,\n  PlayerStateChange = 0x57,\n  Thardus = 0x58,\n  WallCrawlerSwarm = 0x5A,\n  AIJumpPoint = 0x5B,\n  FlaahgraTentacle = 0x5C,\n  RoomAcoustics = 0x5D,\n  ColorModulate = 0x5E,\n  ThardusRockProjectile = 0x5F,\n  Midi = 0x60,\n  StreamedAudio = 0x61,\n  WorldTeleporterToo = 0x62,\n  Repulsor = 0x63,\n  GunTurret = 0x64,\n  FogVolume = 0x65,\n  Babygoth = 0x66,\n  Eyeball = 0x67,\n  RadialDamage = 0x68,\n  CameraPitchVolume = 0x69,\n  EnvFxDensityController = 0x6A,\n  Magdolite = 0x6B,\n  TeamAIMgr = 0x6C,\n  SnakeWeedSwarm = 0x6D,\n  ActorContraption = 0x6E,\n  Oculus = 0x6F,\n  Geemer = 0x70,\n  SpindleCamera = 0x71,\n  AtomicAlpha = 0x72,\n  CameraHintTrigger = 0x73,\n  RumbleEffect = 0x74,\n  AmbientAI = 0x75,\n  AtomicBeta = 0x77,\n  IceZoomer = 0x78,\n  Puffer = 0x79,\n  Tryclops = 0x7A,\n  Ridley = 0x7B,\n  Seedling = 0x7C,\n  ThermalHeatFader = 0x7D,\n  Burrower = 0x7F,\n  ScriptBeam = 0x81,\n  WorldLightFader = 0x82,\n  MetroidPrimeStage2 = 0x83,\n  MetroidPrimeStage1 = 0x84,\n  MazeNode = 0x85,\n  OmegaPirate = 0x86,\n  PhazonPool = 0x87,\n  PhazonHealingNodule = 0x88,\n  NewCameraShaker = 0x89,\n  ShadowProjector = 0x8A,\n  EnergyBall = 0x8B,\n  ScriptObjectTypeMAX\n};\n\nenum class EScriptObjectState {\n  Any = -1,\n  Active = 0,\n  Arrived = 1,\n  Closed = 2,\n  Entered = 3,\n  Exited = 4,\n  Inactive = 5,\n  Inside = 6,\n  MaxReached = 7,\n  Open = 8,\n  Zero = 9,\n  Attack = 10,\n  CloseIn = 11,\n  Retreat = 12,\n  Patrol = 13,\n  Dead = 14,\n  CameraPath = 15,\n  CameraTarget = 16,\n  DeactivateState = 17,\n  Play = 18,\n  MassiveDeath = 19,\n  DeathRattle = 20,\n  AboutToMassivelyDie = 21,\n  Damage = 22,\n  InvulnDamage = 23,\n  MassiveFrozenDeath = 24,\n  Modify = 25,\n  ScanStart = 26,\n  ScanProcessing = 27,\n  ScanDone = 28,\n  UnFrozen = 29,\n  Default = 30,\n  ReflectedDamage = 31,\n  InheritBounds = 32\n};\n\nenum class EScriptObjectMessage {\n  None = -1,\n  UNKM0 = 0,\n  Activate = 1,\n  Arrived = 2,\n  Close = 3,\n  Deactivate = 4,\n  Decrement = 5,\n  Follow = 6,\n  Increment = 7,\n  Next = 8,\n  Open = 9,\n  Reset = 10,\n  ResetAndStart = 11,\n  SetToMax = 12,\n  SetToZero = 13,\n  Start = 14,\n  Stop = 15,\n  StopAndReset = 16,\n  ToggleActive = 17,\n  UNKM18 = 18,\n  Action = 19,\n  Play = 20,\n  Alert = 21,\n  InternalMessage00 = 22,\n  OnFloor = 23,\n  InternalMessage02 = 24,\n  InternalMessage03 = 25,\n  Falling = 26,\n  OnIceSurface = 27,\n  OnMudSlowSurface = 28,\n  OnNormalSurface = 29,\n  Touched = 30,\n  AddPlatformRider = 31,\n  LandOnNotFloor = 32,\n  Registered = 33,\n  Deleted = 34,\n  InitializedInArea = 35,\n  WorldInitialized = 36,\n  AddSplashInhabitant = 37,\n  UpdateSplashInhabitant = 38,\n  RemoveSplashInhabitant = 39,\n  Jumped = 40,\n  Damage = 41,\n  InvulnDamage = 42,\n  ProjectileCollide = 43,\n  InSnakeWeed = 44,\n  AddPhazonPoolInhabitant = 45,\n  UpdatePhazonPoolInhabitant = 46,\n  RemovePhazonPoolInhabitant = 47,\n  SuspendedMove = 48\n};\n\nstd::string_view ScriptObjectTypeToStr(EScriptObjectType type);\nstd::string_view ScriptObjectStateToStr(EScriptObjectState state);\nstd::string_view ScriptObjectMessageToStr(EScriptObjectMessage message);\n\n} // namespace metaforce\n"
  },
  {
    "path": "Runtime/platforms/freedesktop/metaforce.desktop",
    "content": "[Desktop Entry]\nName=Metaforce\nGenericName=Game Engine Replacement\nComment=Engine reimplementation for Metroid Prime 1\nExec=metaforce\nIcon=metaforce\nTerminal=false\nType=Application\nCategories=Graphics;3DGraphics;Game\n"
  },
  {
    "path": "Runtime/platforms/freedesktop/mkwmicon.c",
    "content": "/* clang -o mkwmicon -lpng mkwmicon.c */\n\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#include <stdint.h>\n#include <png.h>\n\nstatic int CountBits(uint32_t n) {\n  int ret = 0;\n  for (int i = 0; i < 32; ++i)\n    if (((n >> i) & 1) != 0)\n      ++ret;\n  return ret;\n}\n\nstatic const int DIMS[] = {16, 32, 48, 64, 128, 0};\n\nint main(int argc, char* argv[]) {\n  if (argc < 2) {\n    fprintf(stderr, \"Usage: makewmicon <out.bin>\\n\");\n    return 1;\n  }\n\n  FILE* ofp = fopen(argv[1], \"wb\");\n  if (!ofp) {\n    fprintf(stderr, \"'%s' is not able to be opened for writing as a regular file\\n\", argv[1]);\n    return 1;\n  }\n\n  png_bytep row = malloc(4 * 128);\n\n  char command[2048];\n\n  for (const int* d = DIMS; *d != 0; ++d) {\n    printf(\"Rendering main icon @%dx%d\\n\", *d, *d);\n    fflush(stdout);\n\n    snprintf(command, 2048, \"%dx%d/apps/metaforce.png\", *d, *d);\n    FILE* fp = fopen(command, \"rb\");\n    if (!fp) {\n      fprintf(stderr, \"unable to open '%s' for reading\\n\", command);\n      fclose(ofp);\n      return 1;\n    }\n\n    char header[8];\n    fread(header, 1, 8, fp);\n    if (png_sig_cmp((png_const_bytep)header, 0, 8)) {\n      fprintf(stderr, \"invalid PNG signature in '%s'\\n\", command);\n      fclose(fp);\n      fclose(ofp);\n      return 1;\n    }\n\n    png_structp pngRead = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);\n    if (!pngRead) {\n      fprintf(stderr, \"unable to initialize libpng\\n\");\n      fclose(fp);\n      fclose(ofp);\n      return 1;\n    }\n    png_infop info = png_create_info_struct(pngRead);\n    if (!info) {\n      fprintf(stderr, \"unable to initialize libpng info\\n\");\n      fclose(fp);\n      fclose(ofp);\n      return 1;\n    }\n\n    if (setjmp(png_jmpbuf(pngRead))) {\n      fprintf(stderr, \"unable to initialize libpng I/O for '%s'\\n\", command);\n      fclose(fp);\n      fclose(ofp);\n      return 1;\n    }\n\n    png_init_io(pngRead, fp);\n    png_set_sig_bytes(pngRead, 8);\n\n    png_read_info(pngRead, info);\n\n    png_uint_32 width = png_get_image_width(pngRead, info);\n    png_uint_32 height = png_get_image_height(pngRead, info);\n    png_byte colorType = png_get_color_type(pngRead, info);\n    png_byte bitDepth = png_get_bit_depth(pngRead, info);\n\n    if (colorType != PNG_COLOR_TYPE_RGB_ALPHA) {\n      fprintf(stderr, \"'%s' is not in RGBA color mode\\n\", command);\n      fclose(fp);\n      fclose(ofp);\n      return 1;\n    }\n\n    if (bitDepth != 8) {\n      fprintf(stderr, \"'%s' is not 8 bits-per-channel\\n\", command);\n      fclose(fp);\n      fclose(ofp);\n      return 1;\n    }\n\n    if (setjmp(png_jmpbuf(pngRead))) {\n      fprintf(stderr, \"unable to read image in '%s'\\n\", command);\n      fclose(fp);\n      fclose(ofp);\n      return 1;\n    }\n\n    unsigned long lWidth = width;\n    unsigned long lHeight = height;\n    fwrite(&lWidth, 1, sizeof(lWidth), ofp);\n    fwrite(&lHeight, 1, sizeof(lHeight), ofp);\n    for (png_uint_32 r = 0; r < height; ++r) {\n      png_read_row(pngRead, row, NULL);\n      for (int i = 0; i < width; ++i) {\n        unsigned long px;\n        px = row[i * 4 + 2];\n        px |= row[i * 4 + 1] << 8;\n        px |= row[i * 4] << 16;\n        px |= row[i * 4 + 3] << 24;\n        fwrite(&px, 1, sizeof(unsigned long), ofp);\n      }\n    }\n\n    png_destroy_read_struct(&pngRead, &info, NULL);\n    fclose(fp);\n  }\n\n  free(row);\n  fclose(ofp);\n  return 0;\n}\n"
  },
  {
    "path": "Runtime/platforms/ios/Info.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    <key>CFBundleDevelopmentRegion</key>\n    <string>en-US</string>\n    <key>CFBundleInfoDictionaryVersion</key>\n    <string>6.0</string>\n    <key>CFBundlePackageType</key>\n    <string>APPL</string>\n    <key>CFBundleSignature</key>\n    <string>????</string>\n    <key>CFBundleExecutable</key>\n    <string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>\n    <key>CFBundleIdentifier</key>\n    <string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>\n    <key>CFBundleName</key>\n    <string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>\n    <key>CFBundleDisplayName</key>\n    <string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>\n    <key>CFBundleVersion</key>\n    <string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>\n    <key>CFBundleShortVersionString</key>\n    <string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>\n    <key>CFBundleSupportedPlatforms</key>\n    <array>\n      <string>iPhoneOS</string>\n    </array>\n    <key>CFBundleIcons</key>\n    <dict>\n      <key>CFBundlePrimaryIcon</key>\n      <dict>\n        <key>CFBundleIconFiles</key>\n        <array>\n          <string>AppIcon60x60</string>\n        </array>\n        <key>CFBundleIconName</key>\n        <string>AppIcon</string>\n      </dict>\n    </dict>\n    <key>CFBundleIcons~ipad</key>\n    <dict>\n      <key>CFBundlePrimaryIcon</key>\n      <dict>\n        <key>CFBundleIconFiles</key>\n        <array>\n          <string>AppIcon60x60</string>\n          <string>AppIcon76x76</string>\n        </array>\n        <key>CFBundleIconName</key>\n        <string>AppIcon</string>\n      </dict>\n    </dict>\n    <key>MinimumOSVersion</key>\n    <string>${DEPLOYMENT_TARGET}</string>\n    <key>LSRequiresIPhoneOS</key>\n    <true/>\n    <key>LSApplicationCategoryType</key>\n    <string>public.app-category.adventure-games</string>\n    <key>UILaunchStoryboardName</key>\n    <string>LaunchScreen</string>\n    <key>UIRequiresFullScreen</key>\n    <true/>\n    <key>UIStatusBarHidden</key>\n    <true/>\n    <key>UIStatusBarStyle</key>\n    <string>UIStatusBarStyleDarkContent</string>\n    <key>UISupportedInterfaceOrientations</key>\n    <array>\n        <string>UIInterfaceOrientationLandscapeLeft</string>\n        <string>UIInterfaceOrientationLandscapeRight</string>\n    </array>\n    <key>UIDeviceFamily</key>\n    <array>\n      <integer>1</integer>\n      <integer>2</integer>\n    </array>\n</dict>\n</plist>\n"
  },
  {
    "path": "Runtime/platforms/macos/Info.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>CFBundleDevelopmentRegion</key>\n\t<string>en-US</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundlePackageType</key>\n\t<string>APPL</string>\n\t<key>CFBundleSignature</key>\n\t<string>????</string>\n\t<key>CFBundleExecutable</key>\n\t<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>\n\t<key>CFBundleIconFile</key>\n\t<string>mainicon.icns</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>\n\t<key>CFBundleName</key>\n\t<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>\n\t<key>CFBundleDisplayName</key>\n\t<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>\n\t<key>CFBundleVersion</key>\n\t<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>\n\t<key>NSHighResolutionCapable</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "Runtime/platforms/tvos/Info.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    <key>CFBundleDevelopmentRegion</key>\n    <string>en-US</string>\n    <key>CFBundleInfoDictionaryVersion</key>\n    <string>6.0</string>\n    <key>CFBundlePackageType</key>\n    <string>APPL</string>\n    <key>CFBundleSignature</key>\n    <string>????</string>\n    <key>CFBundleExecutable</key>\n    <string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>\n    <key>CFBundleIdentifier</key>\n    <string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>\n    <key>CFBundleName</key>\n    <string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>\n    <key>CFBundleDisplayName</key>\n    <string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>\n    <key>CFBundleVersion</key>\n    <string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>\n    <key>CFBundleShortVersionString</key>\n    <string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>\n    <key>CFBundleSupportedPlatforms</key>\n    <array>\n      <string>AppleTVOS</string>\n    </array>\n    <key>CFBundleIcons</key>\n    <dict>\n      <key>CFBundlePrimaryIcon</key>\n      <string>App Icon</string>\n    </dict>\n    <key>MinimumOSVersion</key>\n    <string>${DEPLOYMENT_TARGET}</string>\n    <key>LSRequiresIPhoneOS</key>\n    <true/>\n    <key>LSApplicationCategoryType</key>\n    <string>public.app-category.adventure-games</string>\n    <key>NSHumanReadableCopyright</key>\n    <string>${MACOSX_BUNDLE_COPYRIGHT}</string>\n    <key>NSHighResolutionCapable</key>\n    <true/>\n    <key>UILaunchStoryboardName</key>\n    <string>LaunchScreen</string>\n    <key>UIUserInterfaceStyle</key>\n    <string>Automatic</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "Runtime/platforms/win/Package.appxmanifest",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Package xmlns=\"http://schemas.microsoft.com/appx/manifest/foundation/windows10\" xmlns:uap=\"http://schemas.microsoft.com/appx/manifest/uap/windows10\" xmlns:uap4=\"http://schemas.microsoft.com/appx/manifest/uap/windows10/4\" xmlns:pm=\"http://schemas.microsoft.com/appx/2014/phone/manifest\" IgnorableNamespaces=\"uap uap4 pm\">\n  <Identity Name=\"AxioDL.Metaforce\" Version=\"1.0.0.0\" Publisher=\"CN=AxioDL\" />\n  <pm:PhoneIdentity PhoneProductId=\"97E278C0-4527-453D-AE03-375B1FAA0B19\" PhonePublisherId=\"00000000-0000-0000-0000-000000000000\" />\n  <Properties>\n    <DisplayName>Metaforce</DisplayName>\n    <PublisherDisplayName>AxioDL</PublisherDisplayName>\n    <Logo>Assets\\metaforce.png</Logo>\n  </Properties>\n  <Dependencies>\n    <TargetDeviceFamily Name=\"Windows.Universal\" MinVersion=\"10.0.14393.0\" MaxVersionTested=\"10.0.16299.0\" />\n  </Dependencies>\n  <Resources>\n    <Resource Language=\"en-us\" />\n  </Resources>\n  <Applications>\n    <Application Id=\"URDE\" Executable=\"$targetnametoken$.exe\" EntryPoint=\"URDE.App\">\n      <uap:VisualElements DisplayName=\"Metaforce\" Description=\"Game engine recreation for Metroid Prime\" Square150x150Logo=\"Assets\\Square150x150Logo.png\" Square44x44Logo=\"Assets\\Square44x44Logo.png\" BackgroundColor=\"#333333\">\n        <uap:DefaultTile Square310x310Logo=\"Assets\\LargeTile.png\" Wide310x150Logo=\"Assets\\WideTile.png\" Square71x71Logo=\"Assets\\SmallTile.png\" ShortName=\"URDE\">\n          <uap:ShowNameOnTiles>\n            <uap:ShowOn Tile=\"square150x150Logo\" />\n            <uap:ShowOn Tile=\"wide310x150Logo\" />\n            <uap:ShowOn Tile=\"square310x310Logo\" />\n          </uap:ShowNameOnTiles>\n        </uap:DefaultTile>\n        <uap:SplashScreen BackgroundColor=\"#333333\" Image=\"Assets\\SplashScreen.png\" />\n      </uap:VisualElements>\n    </Application>\n  </Applications>\n</Package>"
  },
  {
    "path": "Runtime/platforms/win/metaforce.manifest",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<assembly manifestVersion=\"1.0\" xmlns=\"urn:schemas-microsoft-com:asm.v1\" xmlns:asmv3=\"urn:schemas-microsoft-com:asm.v3\">\n    <description>URDE</description>\n    <compatibility xmlns=\"urn:schemas-microsoft-com:compatibility.v1\">\n        <application>\n            <!-- Windows 10 -->\n            <supportedOS Id=\"{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}\"/>\n            <!-- Windows 8.1 -->\n            <supportedOS Id=\"{1f676c76-80e1-4239-95bb-83d0f6d0da78}\"/>\n            <!-- Windows Vista -->\n            <supportedOS Id=\"{e2011457-1546-43c5-a5fe-008deee3d3f0}\"/>\n            <!-- Windows 7 -->\n            <supportedOS Id=\"{35138b9a-5d96-4fbd-8e2d-a2440225f93a}\"/>\n            <!-- Windows 8 -->\n            <supportedOS Id=\"{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}\"/>\n        </application>\n    </compatibility>\n    <asmv3:application>\n        <asmv3:windowsSettings xmlns=\"http://schemas.microsoft.com/SMI/2005/WindowsSettings\">\n            <dpiAware>true/PM</dpiAware>\n        </asmv3:windowsSettings>\n    </asmv3:application>\n</assembly>\n"
  },
  {
    "path": "Runtime/platforms/win/metaforce.rc.in",
    "content": "#include \"winver.h\"\n#define IDI_ICON1 101\n\nIDI_ICON1 ICON DISCARDABLE \"mainicon.ico\"\n\n    VS_VERSION_INFO VERSIONINFO FILEVERSION 1,\n    0, 0, 0 PRODUCTVERSION 1, 0, 0,\n    0 FILEFLAGS 0x0L FILEFLAGSMASK 0x3fL FILEOS 0x00040004L FILETYPE 0x1L FILESUBTYPE 0x0L BEGIN BLOCK\n    \"StringFileInfo\" BEGIN BLOCK \"000004b0\" BEGIN VALUE \"CompanyName\",\n    \"AxioDL\" VALUE \"FileDescription\", \"Metaforce\" VALUE \"FileVersion\", \"@METAFORCE_WC_DESCRIBE@\" VALUE \"LegalCopyright\",\n    \"Copyright (C) 2015-@CURRENT_YEAR@ AxioDL Team\" VALUE \"InternalName\", \"metaforce\" VALUE \"OriginalFilename\",\n    \"metaforce.exe\" VALUE \"ProductName\", \"Metaforce\" VALUE \"ProductVersion\",\n    \"@METAFORCE_WC_DESCRIBE@\" END END BLOCK \"VarFileInfo\" BEGIN VALUE \"Translation\", 0x0, 1200 END END\n"
  },
  {
    "path": "Runtime/rstl.hpp",
    "content": "#pragma once\n\n#include <algorithm>\n#include <cstdlib>\n#include <optional>\n#include <type_traits>\n#include <vector>\n#include <iterator>\n\n#ifndef NDEBUG\n#include \"Runtime/Logging.hpp\"\n#endif\n\nnamespace rstl {\n\n/**\n * @brief Base vector backed by statically-allocated array\n */\ntemplate <class T>\nclass _reserved_vector_base {\npublic:\n  class const_iterator {\n    friend class _reserved_vector_base;\n\n  protected:\n    const T* m_val;\n\n  public:\n    const_iterator() : m_val(nullptr) {}\n    explicit const_iterator(const T* val) : m_val(val) {}\n    using value_type = T;\n    using element_type = T;\n    using difference_type = std::ptrdiff_t;\n    using pointer = T*;\n    using reference = T&;\n#ifdef __cpp_lib_concepts\n    using iterator_category = std::contiguous_iterator_tag;\n#else\n    using iterator_category = std::random_access_iterator_tag;\n#endif\n\n    const T& operator*() const { return *m_val; }\n    const T* operator->() const { return m_val; }\n    const_iterator& operator++() {\n      ++m_val;\n      return *this;\n    }\n    const_iterator& operator--() {\n      --m_val;\n      return *this;\n    }\n    const_iterator operator++(int) {\n      auto ret = *this;\n      ++m_val;\n      return ret;\n    }\n    const_iterator operator--(int) {\n      auto ret = *this;\n      --m_val;\n      return ret;\n    }\n    bool operator!=(const const_iterator& other) const { return m_val != other.m_val; }\n    bool operator==(const const_iterator& other) const { return m_val == other.m_val; }\n    const_iterator operator+(std::ptrdiff_t i) const { return const_iterator(m_val + i); }\n    const_iterator operator-(std::ptrdiff_t i) const { return const_iterator(m_val - i); }\n    const_iterator& operator+=(std::ptrdiff_t i) {\n      m_val += i;\n      return *this;\n    }\n    const_iterator& operator-=(std::ptrdiff_t i) {\n      m_val -= i;\n      return *this;\n    }\n    std::ptrdiff_t operator-(const const_iterator& it) const { return m_val - it.m_val; }\n    bool operator>(const const_iterator& it) const { return m_val > it.m_val; }\n    bool operator<(const const_iterator& it) const { return m_val < it.m_val; }\n    bool operator>=(const const_iterator& it) const { return m_val >= it.m_val; }\n    bool operator<=(const const_iterator& it) const { return m_val <= it.m_val; }\n    const T& operator[](std::ptrdiff_t i) const { return m_val[i]; }\n  };\n\n  class iterator : public const_iterator {\n    friend class _reserved_vector_base;\n\n  public:\n    iterator() : const_iterator() {}\n    explicit iterator(T* val) : const_iterator(val) {}\n    T& operator*() const { return *const_cast<T*>(const_iterator::m_val); }\n    T* operator->() const { return const_cast<T*>(const_iterator::m_val); }\n    iterator& operator++() {\n      ++const_iterator::m_val;\n      return *this;\n    }\n    iterator& operator--() {\n      --const_iterator::m_val;\n      return *this;\n    }\n    iterator operator++(int) {\n      auto ret = *this;\n      ++const_iterator::m_val;\n      return ret;\n    }\n    iterator operator--(int) {\n      auto ret = *this;\n      --const_iterator::m_val;\n      return ret;\n    }\n    iterator operator+(std::ptrdiff_t i) const { return iterator(const_cast<T*>(const_iterator::m_val) + i); }\n    iterator operator-(std::ptrdiff_t i) const { return iterator(const_cast<T*>(const_iterator::m_val) - i); }\n    iterator& operator+=(std::ptrdiff_t i) {\n      const_iterator::m_val += i;\n      return *this;\n    }\n    iterator& operator-=(std::ptrdiff_t i) {\n      const_iterator::m_val -= i;\n      return *this;\n    }\n    std::ptrdiff_t operator-(const iterator& it) const { return const_iterator::m_val - it.m_val; }\n    bool operator>(const iterator& it) const { return const_iterator::m_val > it.m_val; }\n    bool operator<(const iterator& it) const { return const_iterator::m_val < it.m_val; }\n    bool operator>=(const iterator& it) const { return const_iterator::m_val >= it.m_val; }\n    bool operator<=(const iterator& it) const { return const_iterator::m_val <= it.m_val; }\n    T& operator[](std::ptrdiff_t i) const { return const_cast<T*>(const_iterator::m_val)[i]; }\n  };\n\n  using reverse_iterator = decltype(std::make_reverse_iterator(iterator{}));\n  using const_reverse_iterator = decltype(std::make_reverse_iterator(const_iterator{}));\n\nprotected:\n  static iterator _const_cast_iterator(const const_iterator& it) { return iterator(const_cast<T*>(it.m_val)); }\n};\n\n/**\n * @brief Vector backed by statically-allocated array with uninitialized storage\n */\ntemplate <class T, size_t N>\nclass reserved_vector : public _reserved_vector_base<T> {\n  using base = _reserved_vector_base<T>;\n\npublic:\n  using value_type = T;\n\n  using pointer = value_type*;\n  using const_pointer = const value_type*;\n\n  using reference = value_type&;\n  using const_reference = const value_type&;\n\n  using difference_type = std::ptrdiff_t;\n  using size_type = std::size_t;\n\n  using iterator = typename base::iterator;\n  using const_iterator = typename base::const_iterator;\n  using reverse_iterator = typename base::reverse_iterator;\n  using const_reverse_iterator = typename base::const_reverse_iterator;\n\nprivate:\n  union alignas(T) storage_t {\n    struct {\n    } _dummy;\n    T _value;\n    storage_t() : _dummy() {}\n    ~storage_t() {}\n  };\n  size_t x0_size;\n  storage_t x4_data[N];\n  T& _value(std::ptrdiff_t idx) { return x4_data[idx]._value; }\n  const T& _value(std::ptrdiff_t idx) const { return x4_data[idx]._value; }\n  template <typename Tp>\n  static void\n  destroy(Tp& t, std::enable_if_t<std::is_destructible_v<Tp> && !std::is_trivially_destructible_v<Tp>>* = nullptr) {\n    t.Tp::~Tp();\n  }\n  template <typename Tp>\n  static void\n  destroy(Tp& t, std::enable_if_t<!std::is_destructible_v<Tp> || std::is_trivially_destructible_v<Tp>>* = nullptr) {}\n\npublic:\n  constexpr reserved_vector() noexcept(std::is_nothrow_constructible_v<T>) : x0_size(0) {}\n\n  template <size_t LN>\n  constexpr reserved_vector(const T (&l)[LN]) noexcept(std::is_nothrow_copy_constructible_v<T>) : x0_size(LN) {\n    static_assert(LN <= N, \"initializer array too large for reserved_vector\");\n    for (size_t i = 0; i < LN; ++i) {\n      ::new (static_cast<void*>(std::addressof(_value(i)))) T(l[i]);\n    }\n  }\n\n  reserved_vector(const reserved_vector& other) noexcept(std::is_nothrow_copy_constructible_v<T>)\n  : x0_size(other.x0_size) {\n    for (size_t i = 0; i < x0_size; ++i) {\n      ::new (static_cast<void*>(std::addressof(_value(i)))) T(other._value(i));\n    }\n  }\n\n  reserved_vector& operator=(const reserved_vector& other) noexcept(std::is_nothrow_copy_assignable_v<T>) {\n    size_t i = 0;\n    if (other.x0_size > x0_size) {\n      for (; i < x0_size; ++i) {\n        _value(i) = other._value(i);\n      }\n      for (; i < other.x0_size; ++i) {\n        ::new (static_cast<void*>(std::addressof(_value(i)))) T(other._value(i));\n      }\n    } else if (other.x0_size < x0_size) {\n      for (; i < other.x0_size; ++i) {\n        _value(i) = other._value(i);\n      }\n\n      if constexpr (std::is_destructible_v<T> && !std::is_trivially_destructible_v<T>) {\n        for (; i < x0_size; ++i) {\n          destroy(_value(i));\n        }\n      }\n    } else {\n      for (; i < other.x0_size; ++i) {\n        _value(i) = other._value(i);\n      }\n    }\n\n    x0_size = other.x0_size;\n    return *this;\n  }\n\n  reserved_vector(reserved_vector&& other) noexcept(std::is_nothrow_move_constructible_v<T>) : x0_size(other.x0_size) {\n    for (size_t i = 0; i < x0_size; ++i) {\n      ::new (static_cast<void*>(std::addressof(_value(i)))) T(std::forward<T>(other._value(i)));\n    }\n  }\n\n  reserved_vector& operator=(reserved_vector&& other) noexcept(\n      std::is_nothrow_move_assignable_v<T>&& std::is_nothrow_move_constructible_v<T>) {\n    size_t i = 0;\n    if (other.x0_size > x0_size) {\n      for (; i < x0_size; ++i) {\n        _value(i) = std::forward<T>(other._value(i));\n      }\n      for (; i < other.x0_size; ++i) {\n        ::new (static_cast<void*>(std::addressof(_value(i)))) T(std::forward<T>(other._value(i)));\n      }\n    } else if (other.x0_size < x0_size) {\n      for (; i < other.x0_size; ++i) {\n        _value(i) = std::forward<T>(other._value(i));\n      }\n\n      if constexpr (std::is_destructible_v<T> && !std::is_trivially_destructible_v<T>) {\n        for (; i < x0_size; ++i) {\n          destroy(_value(i));\n        }\n      }\n    } else {\n      for (; i < other.x0_size; ++i) {\n        _value(i) = std::forward<T>(other._value(i));\n      }\n    }\n\n    x0_size = other.x0_size;\n    return *this;\n  }\n\n  ~reserved_vector() {\n    if constexpr (std::is_destructible_v<T> && !std::is_trivially_destructible_v<T>) {\n      for (size_t i = 0; i < x0_size; ++i) {\n        destroy(_value(i));\n      }\n    }\n  }\n\n  void push_back(const T& d) {\n#ifndef NDEBUG\n    if (x0_size == N) {\n      spdlog::fatal(\"push_back() called on full rstl::reserved_vector.\");\n    }\n#endif\n\n    ::new (static_cast<void*>(std::addressof(_value(x0_size)))) T(d);\n    ++x0_size;\n  }\n\n  void push_back(T&& d) {\n#ifndef NDEBUG\n    if (x0_size == N) {\n      spdlog::fatal(\"push_back() called on full rstl::reserved_vector.\");\n    }\n#endif\n\n    ::new (static_cast<void*>(std::addressof(_value(x0_size)))) T(std::forward<T>(d));\n    ++x0_size;\n  }\n\n  template <class... _Args>\n  T& emplace_back(_Args&&... args) {\n#ifndef NDEBUG\n    if (x0_size == N) {\n      spdlog::fatal(\"emplace_back() called on full rstl::reserved_vector.\");\n    }\n#endif\n\n    T& element = _value(x0_size);\n    ::new (static_cast<void*>(std::addressof(element))) T(std::forward<_Args>(args)...);\n\n    ++x0_size;\n    return element;\n  }\n\n  void pop_back() {\n#ifndef NDEBUG\n    if (x0_size == 0) {\n      spdlog::fatal(\"pop_back() called on empty rstl::reserved_vector.\");\n    }\n#endif\n\n    --x0_size;\n    destroy(_value(x0_size));\n  }\n\n  iterator insert(const_iterator pos, const T& value) {\n#ifndef NDEBUG\n    if (x0_size == N) {\n      spdlog::fatal(\"insert() called on full rstl::reserved_vector.\");\n    }\n#endif\n\n    auto target_it = base::_const_cast_iterator(pos);\n    if (pos == cend()) {\n      ::new (static_cast<void*>(std::addressof(_value(x0_size)))) T(value);\n    } else {\n      ::new (static_cast<void*>(std::addressof(_value(x0_size)))) T(std::forward<T>(_value(x0_size - 1)));\n      for (auto it = end() - 1; it != target_it; --it) {\n        *it = std::forward<T>(*(it - 1));\n      }\n      *target_it = value;\n    }\n    ++x0_size;\n    return target_it;\n  }\n\n  iterator insert(const_iterator pos, T&& value) {\n#ifndef NDEBUG\n    if (x0_size == N)\n      spdlog::fatal(\"insert() called on full rstl::reserved_vector.\");\n#endif\n    auto target_it = base::_const_cast_iterator(pos);\n    if (pos == cend()) {\n      ::new (static_cast<void*>(std::addressof(_value(x0_size)))) T(std::forward<T>(value));\n    } else {\n      ::new (static_cast<void*>(std::addressof(_value(x0_size)))) T(std::forward<T>(_value(x0_size - 1)));\n      for (auto it = end() - 1; it != target_it; --it) {\n        *it = std::forward<T>(*(it - 1));\n      }\n      *target_it = std::forward<T>(value);\n    }\n    ++x0_size;\n    return target_it;\n  }\n\n  void resize(size_t size) {\n#ifndef NDEBUG\n    if (size > N) {\n      spdlog::fatal(\"resize() call overflows rstl::reserved_vector.\");\n    }\n#endif\n\n    if (size > x0_size) {\n      for (size_t i = x0_size; i < size; ++i) {\n        ::new (static_cast<void*>(std::addressof(_value(i)))) T();\n      }\n      x0_size = size;\n    } else if (size < x0_size) {\n      if constexpr (std::is_destructible_v<T> && !std::is_trivially_destructible_v<T>) {\n        for (size_t i = size; i < x0_size; ++i) {\n          destroy(_value(i));\n        }\n      }\n      x0_size = size;\n    }\n  }\n\n  void resize(size_t size, const T& value) {\n#ifndef NDEBUG\n    if (size > N) {\n      spdlog::fatal(\"resize() call overflows rstl::reserved_vector.\");\n    }\n#endif\n\n    if (size > x0_size) {\n      for (size_t i = x0_size; i < size; ++i) {\n        ::new (static_cast<void*>(std::addressof(_value(i)))) T(value);\n      }\n      x0_size = size;\n    } else if (size < x0_size) {\n      if constexpr (std::is_destructible_v<T> && !std::is_trivially_destructible_v<T>) {\n        for (size_t i = size; i < x0_size; ++i) {\n          destroy(_value(i));\n        }\n      }\n      x0_size = size;\n    }\n  }\n\n  iterator erase(const_iterator pos) {\n#ifndef NDEBUG\n    if (x0_size == 0) {\n      spdlog::fatal(\"erase() called on empty rstl::reserved_vector.\");\n    }\n#endif\n\n    for (auto it = base::_const_cast_iterator(pos) + 1; it != end(); ++it) {\n      *(it - 1) = std::forward<T>(*it);\n    }\n    --x0_size;\n    destroy(_value(x0_size));\n    return base::_const_cast_iterator(pos);\n  }\n\n  void pop_front() {\n    if (x0_size != 0) {\n      erase(begin());\n    }\n  }\n\n  void clear() {\n    if constexpr (std::is_destructible_v<T> && !std::is_trivially_destructible_v<T>) {\n      for (auto it = begin(); it != end(); ++it) {\n        destroy(*it);\n      }\n    }\n    x0_size = 0;\n  }\n\n  [[nodiscard]] size_t size() const noexcept { return x0_size; }\n  [[nodiscard]] bool empty() const noexcept { return x0_size == 0; }\n  [[nodiscard]] constexpr size_t capacity() const noexcept { return N; }\n  [[nodiscard]] const T* data() const noexcept { return std::addressof(_value(0)); }\n  [[nodiscard]] T* data() noexcept { return std::addressof(_value(0)); }\n\n  [[nodiscard]] T& back() { return _value(x0_size - 1); }\n  [[nodiscard]] T& front() { return _value(0); }\n  [[nodiscard]] const T& back() const { return _value(x0_size - 1); }\n  [[nodiscard]] const T& front() const { return _value(0); }\n\n  [[nodiscard]] const_iterator begin() const noexcept { return const_iterator(std::addressof(_value(0))); }\n  [[nodiscard]] const_iterator end() const noexcept { return const_iterator(std::addressof(_value(x0_size))); }\n  [[nodiscard]] iterator begin() noexcept { return iterator(std::addressof(_value(0))); }\n  [[nodiscard]] iterator end() noexcept { return iterator(std::addressof(_value(x0_size))); }\n  [[nodiscard]] const_iterator cbegin() const noexcept { return begin(); }\n  [[nodiscard]] const_iterator cend() const noexcept { return end(); }\n\n  [[nodiscard]] const_reverse_iterator rbegin() const noexcept { return std::make_reverse_iterator(end()); }\n  [[nodiscard]] const_reverse_iterator rend() const noexcept { return std::make_reverse_iterator(begin()); }\n  [[nodiscard]] reverse_iterator rbegin() noexcept { return std::make_reverse_iterator(end()); }\n  [[nodiscard]] reverse_iterator rend() noexcept { return std::make_reverse_iterator(begin()); }\n  [[nodiscard]] const_reverse_iterator crbegin() const noexcept { return rbegin(); }\n  [[nodiscard]] const_reverse_iterator crend() const noexcept { return rend(); }\n\n  [[nodiscard]] T& operator[](size_t idx) {\n#ifndef NDEBUG\n    if (idx >= x0_size) {\n      spdlog::fatal(\"out of bounds access on reserved_vector.\");\n    }\n#endif\n    return _value(idx);\n  }\n  [[nodiscard]] const T& operator[](size_t idx) const {\n#ifndef NDEBUG\n    if (idx >= x0_size) {\n      spdlog::fatal(\"out of bounds access on reserved_vector.\");\n    }\n#endif\n    return _value(idx);\n  }\n};\n\n/**\n * @brief Vector-style view backed by externally-allocated storage\n */\ntemplate <class T>\nclass prereserved_vector : public _reserved_vector_base<T> {\n  size_t x0_size;\n  T* x4_data;\n  T& _value(std::ptrdiff_t idx) { return x4_data[idx]; }\n  const T& _value(std::ptrdiff_t idx) const { return x4_data[idx]; }\n\npublic:\n  using base = _reserved_vector_base<T>;\n  using iterator = typename base::iterator;\n  using const_iterator = typename base::const_iterator;\n  using reverse_iterator = typename base::reverse_iterator;\n  using const_reverse_iterator = typename base::const_reverse_iterator;\n  prereserved_vector() : x0_size(0), x4_data(nullptr) {}\n  prereserved_vector(size_t size, T* data) : x0_size(size), x4_data(data) {}\n\n  void set_size(size_t n) { x0_size = n; }\n  void set_data(T* data) { x4_data = data; }\n\n  [[nodiscard]] size_t size() const noexcept { return x0_size; }\n  [[nodiscard]] bool empty() const noexcept { return x0_size == 0; }\n  [[nodiscard]] const T* data() const noexcept { return x4_data; }\n  [[nodiscard]] T* data() noexcept { return x4_data; }\n\n  [[nodiscard]] T& back() { return _value(x0_size - 1); }\n  [[nodiscard]] T& front() { return _value(0); }\n  [[nodiscard]] const T& back() const { return _value(x0_size - 1); }\n  [[nodiscard]] const T& front() const { return _value(0); }\n\n  [[nodiscard]] const_iterator begin() const noexcept { return const_iterator(std::addressof(_value(0))); }\n  [[nodiscard]] const_iterator end() const noexcept { return const_iterator(std::addressof(_value(x0_size))); }\n  [[nodiscard]] iterator begin() noexcept { return iterator(std::addressof(_value(0))); }\n  [[nodiscard]] iterator end() noexcept { return iterator(std::addressof(_value(x0_size))); }\n  [[nodiscard]] const_iterator cbegin() const noexcept { return begin(); }\n  [[nodiscard]] const_iterator cend() const noexcept { return end(); }\n\n  [[nodiscard]] const_reverse_iterator rbegin() const noexcept { return std::make_reverse_iterator(end()); }\n  [[nodiscard]] const_reverse_iterator rend() const noexcept { return std::make_reverse_iterator(begin()); }\n  [[nodiscard]] reverse_iterator rbegin() noexcept { return std::make_reverse_iterator(end()); }\n  [[nodiscard]] reverse_iterator rend() noexcept { return std::make_reverse_iterator(begin()); }\n  [[nodiscard]] const_reverse_iterator crbegin() const noexcept { return rbegin(); }\n  [[nodiscard]] const_reverse_iterator crend() const noexcept { return rend(); }\n\n  [[nodiscard]] T& operator[](size_t idx) { return _value(idx); }\n  [[nodiscard]] const T& operator[](size_t idx) const { return _value(idx); }\n};\n\ntemplate <class ForwardIt, class T>\nForwardIt binary_find(ForwardIt first, ForwardIt last, const T& value) {\n  first = std::lower_bound(first, last, value);\n  return (!(first == last) && !(value < *first)) ? first : last;\n}\n\ntemplate <class ForwardIt, class T, class GetKey>\nForwardIt binary_find(ForwardIt first, ForwardIt last, const T& value, GetKey getkey) {\n  auto comp = [&](const auto& left, const T& right) { return getkey(left) < right; };\n  first = std::lower_bound(first, last, value, comp);\n  return (!(first == last) && !(value < getkey(*first))) ? first : last;\n}\n\n#if 0\ntemplate <typename _CharTp>\nclass basic_string\n{\n    struct COWData\n    {\n        uint32_t x0_capacity;\n        uint32_t x4_refCount;\n        _CharTp x8_data[];\n    };\n\n    const _CharTp* x0_ptr;\n    COWData* x4_cow;\n    uint32_t x8_size;\n\n    void internal_allocate(int size)\n    {\n        x4_cow = reinterpret_cast<COWData*>(new uint8_t[size * sizeof(_CharTp) + 8]);\n        x0_ptr = x4_cow->x8_data;\n        x4_cow->x0_capacity = uint32_t(size);\n        x4_cow->x4_refCount = 1;\n    }\n\n    static const _CharTp _EmptyString;\n\npublic:\n    struct literal_t {};\n\n    basic_string(literal_t, const _CharTp* data)\n    {\n        x0_ptr = data;\n        x4_cow = nullptr;\n\n        const _CharTp* it = data;\n        while (*it)\n            ++it;\n\n        x8_size = uint32_t((it - data) / sizeof(_CharTp));\n    }\n\n    basic_string(const basic_string& str)\n    {\n        x0_ptr = str.x0_ptr;\n        x4_cow = str.x4_cow;\n        x8_size = str.x8_size;\n        if (x4_cow)\n            ++x4_cow->x4_refCount;\n    }\n\n    basic_string(const _CharTp* data, int size)\n    {\n        if (size <= 0 && !data)\n        {\n            x0_ptr = &_EmptyString;\n            x4_cow = nullptr;\n            x8_size = 0;\n            return;\n        }\n\n        const _CharTp* it = data;\n        uint32_t len = 0;\n        while (*it)\n        {\n            if (size != -1 && len >= size)\n                break;\n            ++it;\n            ++len;\n        }\n\n        internal_allocate(len + 1);\n        x8_size = len;\n        for (int i = 0; i < len; ++i)\n            x4_cow->x8_data[i] = data[i];\n        x4_cow->x8_data[len] = 0;\n    }\n\n    ~basic_string()\n    {\n        if (x4_cow && --x4_cow->x4_refCount == 0)\n            delete[] x4_cow;\n    }\n};\n\ntemplate <>\nconst char basic_string<char>::_EmptyString = 0;\ntemplate <>\nconst wchar_t basic_string<wchar_t>::_EmptyString = 0;\n\ntypedef basic_string<wchar_t> wstring;\ntypedef basic_string<char> string;\n\nwstring wstring_l(const wchar_t* data)\n{\n    return wstring(wstring::literal_t(), data);\n}\n\nstring string_l(const char* data)\n{\n    return string(string::literal_t(), data);\n}\n#endif\n\n} // namespace rstl\n"
  },
  {
    "path": "android/.gitignore",
    "content": ".gradle/\nbuild/\napp/build/\nlocal.properties\napp/src/main/jniLibs/*/*.so\n"
  },
  {
    "path": "android/README.md",
    "content": "# Android Shell\n\nThis directory contains a minimal SDLActivity-based Android app wrapper for Metaforce.\n\n## Prerequisites\n\n- Android SDK installed (`ANDROID_HOME`)\n- Android NDK version used by CMake presets (`ANDROID_NDK_VERSION`)\n- JDK 17+\n\nExample:\n\n```bash\nexport ANDROID_HOME=\"$HOME/Android/Sdk\"\nexport ANDROID_NDK_VERSION=\"29.0.14206865\"\nexport JAVA_HOME=\"/usr/lib/jvm/java-17-openjdk\"\n```\n\n## Build Native Libraries\n\n```bash\ncmake --preset android-arm64\ncmake --build --preset android-arm64\n\ncmake --preset android-x86_64\ncmake --build --preset android-x86_64\n```\n\nThese builds produce:\n\n- `build/android-arm64/Binaries/libmain.so`\n- `build/android-x86_64/Binaries/libmain.so`\n\n## Stage Libraries Into APK Project\n\n```bash\n./android/scripts/stage-jni-libs.sh\n```\n\nThis copies:\n\n- `libmain.so` -> `android/app/src/main/jniLibs/arm64-v8a/`\n- `libmain.so` -> `android/app/src/main/jniLibs/x86_64/`\n\n## Refresh SDL Java Shim (Optional)\n\nIf you update SDL and want to refresh the embedded Java shim files:\n\n```bash\n./android/scripts/sync-sdl-java.sh\n```\n\n## Build APK\n\n```bash\ncd android\n./gradlew :app:assembleDebug\n```\n\nOutput APK:\n\n- `android/app/build/outputs/apk/debug/app-debug.apk`\n\n## Launch With Runtime Args (adb)\n\nYou can pass command-line args through the activity intent:\n\n```bash\nadb shell am start -n com.axiodl.metaforce/.MetaforceActivity \\\n  --es metaforce_args \"'/sdcard/Download/Metroid Prime (USA).iso' --warp 2 17\"\n```\n\nSupported extras:\n\n- `metaforce_args`: single shell-like argument string\n- `metaforce_argv`: string-array argv\n- `metaforce_disc`: compatibility shortcut (single ISO path)\n"
  },
  {
    "path": "android/app/build.gradle",
    "content": "plugins {\n    id 'com.android.application'\n}\n\nandroid {\n    namespace 'com.axiodl.metaforce'\n    compileSdk 35\n\n    defaultConfig {\n        applicationId 'com.axiodl.metaforce'\n        minSdk 24\n        targetSdk 35\n        versionCode 1\n        versionName '0.1.0'\n    }\n\n    buildTypes {\n        debug {\n            minifyEnabled false\n        }\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n    }\n\n    sourceSets {\n        main {\n            jniLibs.srcDirs = ['src/main/jniLibs']\n            assets.srcDirs = ['../../assets']\n        }\n    }\n\n    splits {\n        abi {\n            enable true\n            reset()\n            include 'arm64-v8a', 'x86_64'\n            universalApk false\n        }\n    }\n\n    lint {\n        abortOnError false\n    }\n}\n\ndependencies {\n    implementation fileTree(dir: 'libs', include: ['*.jar'])\n}\n"
  },
  {
    "path": "android/app/proguard-rules.pro",
    "content": "# Keep SDL activity and related JNI bridge methods.\n-keep class org.libsdl.app.** { *; }\n"
  },
  {
    "path": "android/app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:installLocation=\"auto\">\n\n    <uses-feature android:glEsVersion=\"0x00020000\" />\n    <uses-feature android:name=\"android.hardware.touchscreen\" android:required=\"false\" />\n    <uses-feature android:name=\"android.hardware.bluetooth\" android:required=\"false\" />\n    <uses-feature android:name=\"android.hardware.gamepad\" android:required=\"false\" />\n    <uses-feature android:name=\"android.hardware.usb.host\" android:required=\"false\" />\n    <uses-feature android:name=\"android.hardware.type.pc\" android:required=\"false\" />\n\n    <uses-permission android:name=\"android.permission.VIBRATE\" />\n\n    <application\n        android:allowBackup=\"true\"\n        android:hardwareAccelerated=\"true\"\n        android:icon=\"@android:drawable/sym_def_app_icon\"\n        android:label=\"@string/app_name\"\n        android:theme=\"@android:style/Theme.NoTitleBar.Fullscreen\"\n        android:enableOnBackInvokedCallback=\"false\">\n\n        <activity\n            android:name=\"com.axiodl.metaforce.MetaforceActivity\"\n            android:alwaysRetainTaskState=\"true\"\n            android:configChanges=\"layoutDirection|locale|grammaticalGender|fontScale|fontWeightAdjustment|orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation\"\n            android:exported=\"true\"\n            android:label=\"@string/app_name\"\n            android:launchMode=\"singleInstance\"\n            android:preferMinimalPostProcessing=\"true\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n            <intent-filter>\n                <action android:name=\"android.hardware.usb.action.USB_DEVICE_ATTACHED\" />\n            </intent-filter>\n        </activity>\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "android/app/src/main/java/com/axiodl/metaforce/MetaforceActivity.java",
    "content": "package com.axiodl.metaforce;\n\nimport android.content.Intent;\n\nimport org.libsdl.app.SDLActivity;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class MetaforceActivity extends SDLActivity {\n    private static String[] splitArgs(String raw) {\n        List<String> out = new ArrayList<>();\n        StringBuilder current = new StringBuilder();\n        boolean inSingle = false;\n        boolean inDouble = false;\n        boolean escaped = false;\n\n        for (int i = 0; i < raw.length(); ++i) {\n            char c = raw.charAt(i);\n            if (escaped) {\n                current.append(c);\n                escaped = false;\n                continue;\n            }\n            if (c == '\\\\' && !inSingle) {\n                escaped = true;\n                continue;\n            }\n            if (c == '\"' && !inSingle) {\n                inDouble = !inDouble;\n                continue;\n            }\n            if (c == '\\'' && !inDouble) {\n                inSingle = !inSingle;\n                continue;\n            }\n            if (!inSingle && !inDouble && Character.isWhitespace(c)) {\n                if (current.length() > 0) {\n                    out.add(current.toString());\n                    current.setLength(0);\n                }\n                continue;\n            }\n            current.append(c);\n        }\n\n        if (escaped) {\n            current.append('\\\\');\n        }\n        if (current.length() > 0) {\n            out.add(current.toString());\n        }\n        return out.toArray(new String[0]);\n    }\n\n    @Override\n    protected String[] getLibraries() {\n        // SDL3 is statically linked into libmain.so in this build.\n        return new String[] {\n            \"main\"\n        };\n    }\n\n    @Override\n    protected String[] getArguments() {\n        Intent intent = getIntent();\n        if (intent != null) {\n            String[] argv = intent.getStringArrayExtra(\"metaforce_argv\");\n            if (argv != null && argv.length > 0) {\n                return argv;\n            }\n\n            String rawArgs = intent.getStringExtra(\"metaforce_args\");\n            if (rawArgs != null) {\n                String trimmed = rawArgs.trim();\n                if (!trimmed.isEmpty()) {\n                    return splitArgs(trimmed);\n                }\n            }\n\n            String discPath = intent.getStringExtra(\"metaforce_disc\");\n            if (discPath != null && !discPath.isEmpty()) {\n                return new String[] { discPath };\n            }\n        }\n        return new String[0];\n    }\n}\n"
  },
  {
    "path": "android/app/src/main/java/org/libsdl/app/HIDDevice.java",
    "content": "package org.libsdl.app;\n\nimport android.hardware.usb.UsbDevice;\n\ninterface HIDDevice\n{\n    public int getId();\n    public int getVendorId();\n    public int getProductId();\n    public String getSerialNumber();\n    public int getVersion();\n    public String getManufacturerName();\n    public String getProductName();\n    public UsbDevice getDevice();\n    public boolean open();\n    public int writeReport(byte[] report, boolean feature);\n    public boolean readReport(byte[] report, boolean feature);\n    public void setFrozen(boolean frozen);\n    public void close();\n    public void shutdown();\n}\n"
  },
  {
    "path": "android/app/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java",
    "content": "package org.libsdl.app;\n\nimport android.content.Context;\nimport android.bluetooth.BluetoothDevice;\nimport android.bluetooth.BluetoothGatt;\nimport android.bluetooth.BluetoothGattCallback;\nimport android.bluetooth.BluetoothGattCharacteristic;\nimport android.bluetooth.BluetoothGattDescriptor;\nimport android.bluetooth.BluetoothManager;\nimport android.bluetooth.BluetoothProfile;\nimport android.bluetooth.BluetoothGattService;\nimport android.hardware.usb.UsbDevice;\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.util.Log;\nimport android.os.*;\n\n//import com.android.internal.util.HexDump;\n\nimport java.lang.Runnable;\nimport java.util.Arrays;\nimport java.util.LinkedList;\nimport java.util.UUID;\n\nclass HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDevice {\n\n    private static final String TAG = \"hidapi\";\n    private HIDDeviceManager mManager;\n    private BluetoothDevice mDevice;\n    private int mDeviceId;\n    private BluetoothGatt mGatt;\n    private boolean mIsRegistered = false;\n    private boolean mIsConnected = false;\n    private boolean mIsChromebook = false;\n    private boolean mIsReconnecting = false;\n    private boolean mFrozen = false;\n    private LinkedList<GattOperation> mOperations;\n    GattOperation mCurrentOperation = null;\n    private Handler mHandler;\n\n    private static final int TRANSPORT_AUTO = 0;\n    private static final int TRANSPORT_BREDR = 1;\n    private static final int TRANSPORT_LE = 2;\n\n    private static final int CHROMEBOOK_CONNECTION_CHECK_INTERVAL = 10000;\n\n    static final UUID steamControllerService = UUID.fromString(\"100F6C32-1735-4313-B402-38567131E5F3\");\n    static final UUID inputCharacteristic = UUID.fromString(\"100F6C33-1735-4313-B402-38567131E5F3\");\n    static final UUID reportCharacteristic = UUID.fromString(\"100F6C34-1735-4313-B402-38567131E5F3\");\n    static private final byte[] enterValveMode = new byte[] { (byte)0xC0, (byte)0x87, 0x03, 0x08, 0x07, 0x00 };\n\n    static class GattOperation {\n        private enum Operation {\n            CHR_READ,\n            CHR_WRITE,\n            ENABLE_NOTIFICATION\n        }\n\n        Operation mOp;\n        UUID mUuid;\n        byte[] mValue;\n        BluetoothGatt mGatt;\n        boolean mResult = true;\n\n        private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid) {\n            mGatt = gatt;\n            mOp = operation;\n            mUuid = uuid;\n        }\n\n        private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid, byte[] value) {\n            mGatt = gatt;\n            mOp = operation;\n            mUuid = uuid;\n            mValue = value;\n        }\n\n        public void run() {\n            // This is executed in main thread\n            BluetoothGattCharacteristic chr;\n\n            switch (mOp) {\n                case CHR_READ:\n                    chr = getCharacteristic(mUuid);\n                    //Log.v(TAG, \"Reading characteristic \" + chr.getUuid());\n                    if (!mGatt.readCharacteristic(chr)) {\n                        Log.e(TAG, \"Unable to read characteristic \" + mUuid.toString());\n                        mResult = false;\n                        break;\n                    }\n                    mResult = true;\n                    break;\n                case CHR_WRITE:\n                    chr = getCharacteristic(mUuid);\n                    //Log.v(TAG, \"Writing characteristic \" + chr.getUuid() + \" value=\" + HexDump.toHexString(value));\n                    chr.setValue(mValue);\n                    if (!mGatt.writeCharacteristic(chr)) {\n                        Log.e(TAG, \"Unable to write characteristic \" + mUuid.toString());\n                        mResult = false;\n                        break;\n                    }\n                    mResult = true;\n                    break;\n                case ENABLE_NOTIFICATION:\n                    chr = getCharacteristic(mUuid);\n                    //Log.v(TAG, \"Writing descriptor of \" + chr.getUuid());\n                    if (chr != null) {\n                        BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString(\"00002902-0000-1000-8000-00805f9b34fb\"));\n                        if (cccd != null) {\n                            int properties = chr.getProperties();\n                            byte[] value;\n                            if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == BluetoothGattCharacteristic.PROPERTY_NOTIFY) {\n                                value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;\n                            } else if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) == BluetoothGattCharacteristic.PROPERTY_INDICATE) {\n                                value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE;\n                            } else {\n                                Log.e(TAG, \"Unable to start notifications on input characteristic\");\n                                mResult = false;\n                                return;\n                            }\n\n                            mGatt.setCharacteristicNotification(chr, true);\n                            cccd.setValue(value);\n                            if (!mGatt.writeDescriptor(cccd)) {\n                                Log.e(TAG, \"Unable to write descriptor \" + mUuid.toString());\n                                mResult = false;\n                                return;\n                            }\n                            mResult = true;\n                        }\n                    }\n            }\n        }\n\n        public boolean finish() {\n            return mResult;\n        }\n\n        private BluetoothGattCharacteristic getCharacteristic(UUID uuid) {\n            BluetoothGattService valveService = mGatt.getService(steamControllerService);\n            if (valveService == null)\n                return null;\n            return valveService.getCharacteristic(uuid);\n        }\n\n        static public GattOperation readCharacteristic(BluetoothGatt gatt, UUID uuid) {\n            return new GattOperation(gatt, Operation.CHR_READ, uuid);\n        }\n\n        static public GattOperation writeCharacteristic(BluetoothGatt gatt, UUID uuid, byte[] value) {\n            return new GattOperation(gatt, Operation.CHR_WRITE, uuid, value);\n        }\n\n        static public GattOperation enableNotification(BluetoothGatt gatt, UUID uuid) {\n            return new GattOperation(gatt, Operation.ENABLE_NOTIFICATION, uuid);\n        }\n    }\n\n    HIDDeviceBLESteamController(HIDDeviceManager manager, BluetoothDevice device) {\n        mManager = manager;\n        mDevice = device;\n        mDeviceId = mManager.getDeviceIDForIdentifier(getIdentifier());\n        mIsRegistered = false;\n        mIsChromebook = SDLActivity.isChromebook();\n        mOperations = new LinkedList<GattOperation>();\n        mHandler = new Handler(Looper.getMainLooper());\n\n        mGatt = connectGatt();\n        // final HIDDeviceBLESteamController finalThis = this;\n        // mHandler.postDelayed(new Runnable() {\n        //     @Override\n        //     void run() {\n        //         finalThis.checkConnectionForChromebookIssue();\n        //     }\n        // }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL);\n    }\n\n    String getIdentifier() {\n        return String.format(\"SteamController.%s\", mDevice.getAddress());\n    }\n\n    BluetoothGatt getGatt() {\n        return mGatt;\n    }\n\n    // Because on Chromebooks we show up as a dual-mode device, it will attempt to connect TRANSPORT_AUTO, which will use TRANSPORT_BREDR instead\n    // of TRANSPORT_LE.  Let's force ourselves to connect low energy.\n    private BluetoothGatt connectGatt(boolean managed) {\n        if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) {\n            try {\n                return mDevice.connectGatt(mManager.getContext(), managed, this, TRANSPORT_LE);\n            } catch (Exception e) {\n                return mDevice.connectGatt(mManager.getContext(), managed, this);\n            }\n        } else {\n            return mDevice.connectGatt(mManager.getContext(), managed, this);\n        }\n    }\n\n    private BluetoothGatt connectGatt() {\n        return connectGatt(false);\n    }\n\n    protected int getConnectionState() {\n\n        Context context = mManager.getContext();\n        if (context == null) {\n            // We are lacking any context to get our Bluetooth information.  We'll just assume disconnected.\n            return BluetoothProfile.STATE_DISCONNECTED;\n        }\n\n        BluetoothManager btManager = (BluetoothManager)context.getSystemService(Context.BLUETOOTH_SERVICE);\n        if (btManager == null) {\n            // This device doesn't support Bluetooth.  We should never be here, because how did\n            // we instantiate a device to start with?\n            return BluetoothProfile.STATE_DISCONNECTED;\n        }\n\n        return btManager.getConnectionState(mDevice, BluetoothProfile.GATT);\n    }\n\n    void reconnect() {\n\n        if (getConnectionState() != BluetoothProfile.STATE_CONNECTED) {\n            mGatt.disconnect();\n            mGatt = connectGatt();\n        }\n\n    }\n\n    protected void checkConnectionForChromebookIssue() {\n        if (!mIsChromebook) {\n            // We only do this on Chromebooks, because otherwise it's really annoying to just attempt\n            // over and over.\n            return;\n        }\n\n        int connectionState = getConnectionState();\n\n        switch (connectionState) {\n            case BluetoothProfile.STATE_CONNECTED:\n                if (!mIsConnected) {\n                    // We are in the Bad Chromebook Place.  We can force a disconnect\n                    // to try to recover.\n                    Log.v(TAG, \"Chromebook: We are in a very bad state; the controller shows as connected in the underlying Bluetooth layer, but we never received a callback.  Forcing a reconnect.\");\n                    mIsReconnecting = true;\n                    mGatt.disconnect();\n                    mGatt = connectGatt(false);\n                    break;\n                }\n                else if (!isRegistered()) {\n                    if (mGatt.getServices().size() > 0) {\n                        Log.v(TAG, \"Chromebook: We are connected to a controller, but never got our registration.  Trying to recover.\");\n                        probeService(this);\n                    }\n                    else {\n                        Log.v(TAG, \"Chromebook: We are connected to a controller, but never discovered services.  Trying to recover.\");\n                        mIsReconnecting = true;\n                        mGatt.disconnect();\n                        mGatt = connectGatt(false);\n                        break;\n                    }\n                }\n                else {\n                    Log.v(TAG, \"Chromebook: We are connected, and registered.  Everything's good!\");\n                    return;\n                }\n                break;\n\n            case BluetoothProfile.STATE_DISCONNECTED:\n                Log.v(TAG, \"Chromebook: We have either been disconnected, or the Chromebook BtGatt.ContextMap bug has bitten us.  Attempting a disconnect/reconnect, but we may not be able to recover.\");\n\n                mIsReconnecting = true;\n                mGatt.disconnect();\n                mGatt = connectGatt(false);\n                break;\n\n            case BluetoothProfile.STATE_CONNECTING:\n                Log.v(TAG, \"Chromebook: We're still trying to connect.  Waiting a bit longer.\");\n                break;\n        }\n\n        final HIDDeviceBLESteamController finalThis = this;\n        mHandler.postDelayed(new Runnable() {\n            @Override\n            public void run() {\n                finalThis.checkConnectionForChromebookIssue();\n            }\n        }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL);\n    }\n\n    private boolean isRegistered() {\n        return mIsRegistered;\n    }\n\n    private void setRegistered() {\n        mIsRegistered = true;\n    }\n\n    private boolean probeService(HIDDeviceBLESteamController controller) {\n\n        if (isRegistered()) {\n            return true;\n        }\n\n        if (!mIsConnected) {\n            return false;\n        }\n\n        Log.v(TAG, \"probeService controller=\" + controller);\n\n        for (BluetoothGattService service : mGatt.getServices()) {\n            if (service.getUuid().equals(steamControllerService)) {\n                Log.v(TAG, \"Found Valve steam controller service \" + service.getUuid());\n\n                for (BluetoothGattCharacteristic chr : service.getCharacteristics()) {\n                    if (chr.getUuid().equals(inputCharacteristic)) {\n                        Log.v(TAG, \"Found input characteristic\");\n                        // Start notifications\n                        BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString(\"00002902-0000-1000-8000-00805f9b34fb\"));\n                        if (cccd != null) {\n                            enableNotification(chr.getUuid());\n                        }\n                    }\n                }\n                return true;\n            }\n        }\n\n        if ((mGatt.getServices().size() == 0) && mIsChromebook && !mIsReconnecting) {\n            Log.e(TAG, \"Chromebook: Discovered services were empty; this almost certainly means the BtGatt.ContextMap bug has bitten us.\");\n            mIsConnected = false;\n            mIsReconnecting = true;\n            mGatt.disconnect();\n            mGatt = connectGatt(false);\n        }\n\n        return false;\n    }\n\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n\n    private void finishCurrentGattOperation() {\n        GattOperation op = null;\n        synchronized (mOperations) {\n            if (mCurrentOperation != null) {\n                op = mCurrentOperation;\n                mCurrentOperation = null;\n            }\n        }\n        if (op != null) {\n            boolean result = op.finish(); // TODO: Maybe in main thread as well?\n\n            // Our operation failed, let's add it back to the beginning of our queue.\n            if (!result) {\n                mOperations.addFirst(op);\n            }\n        }\n        executeNextGattOperation();\n    }\n\n    private void executeNextGattOperation() {\n        synchronized (mOperations) {\n            if (mCurrentOperation != null)\n                return;\n\n            if (mOperations.isEmpty())\n                return;\n\n            mCurrentOperation = mOperations.removeFirst();\n        }\n\n        // Run in main thread\n        mHandler.post(new Runnable() {\n            @Override\n            public void run() {\n                synchronized (mOperations) {\n                    if (mCurrentOperation == null) {\n                        Log.e(TAG, \"Current operation null in executor?\");\n                        return;\n                    }\n\n                    mCurrentOperation.run();\n                    // now wait for the GATT callback and when it comes, finish this operation\n                }\n            }\n        });\n    }\n\n    private void queueGattOperation(GattOperation op) {\n        synchronized (mOperations) {\n            mOperations.add(op);\n        }\n        executeNextGattOperation();\n    }\n\n    private void enableNotification(UUID chrUuid) {\n        GattOperation op = HIDDeviceBLESteamController.GattOperation.enableNotification(mGatt, chrUuid);\n        queueGattOperation(op);\n    }\n\n    void writeCharacteristic(UUID uuid, byte[] value) {\n        GattOperation op = HIDDeviceBLESteamController.GattOperation.writeCharacteristic(mGatt, uuid, value);\n        queueGattOperation(op);\n    }\n\n    void readCharacteristic(UUID uuid) {\n        GattOperation op = HIDDeviceBLESteamController.GattOperation.readCharacteristic(mGatt, uuid);\n        queueGattOperation(op);\n    }\n\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n    //////////////  BluetoothGattCallback overridden methods\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n\n    @Override\n    public void onConnectionStateChange(BluetoothGatt g, int status, int newState) {\n        //Log.v(TAG, \"onConnectionStateChange status=\" + status + \" newState=\" + newState);\n        mIsReconnecting = false;\n        if (newState == 2) {\n            mIsConnected = true;\n            // Run directly, without GattOperation\n            if (!isRegistered()) {\n                mHandler.post(new Runnable() {\n                    @Override\n                    public void run() {\n                        mGatt.discoverServices();\n                    }\n                });\n            }\n        }\n        else if (newState == 0) {\n            mIsConnected = false;\n        }\n\n        // Disconnection is handled in SteamLink using the ACTION_ACL_DISCONNECTED Intent.\n    }\n\n    @Override\n    public void onServicesDiscovered(BluetoothGatt gatt, int status) {\n        //Log.v(TAG, \"onServicesDiscovered status=\" + status);\n        if (status == 0) {\n            if (gatt.getServices().size() == 0) {\n                Log.v(TAG, \"onServicesDiscovered returned zero services; something has gone horribly wrong down in Android's Bluetooth stack.\");\n                mIsReconnecting = true;\n                mIsConnected = false;\n                gatt.disconnect();\n                mGatt = connectGatt(false);\n            }\n            else {\n                probeService(this);\n            }\n        }\n    }\n\n    @Override\n    public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {\n        //Log.v(TAG, \"onCharacteristicRead status=\" + status + \" uuid=\" + characteristic.getUuid());\n\n        if (characteristic.getUuid().equals(reportCharacteristic) && !mFrozen) {\n            mManager.HIDDeviceReportResponse(getId(), characteristic.getValue());\n        }\n\n        finishCurrentGattOperation();\n    }\n\n    @Override\n    public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {\n        //Log.v(TAG, \"onCharacteristicWrite status=\" + status + \" uuid=\" + characteristic.getUuid());\n\n        if (characteristic.getUuid().equals(reportCharacteristic)) {\n            // Only register controller with the native side once it has been fully configured\n            if (!isRegistered()) {\n                Log.v(TAG, \"Registering Steam Controller with ID: \" + getId());\n                mManager.HIDDeviceConnected(getId(), getIdentifier(), getVendorId(), getProductId(), getSerialNumber(), getVersion(), getManufacturerName(), getProductName(), 0, 0, 0, 0, true);\n                setRegistered();\n            }\n        }\n\n        finishCurrentGattOperation();\n    }\n\n    @Override\n    public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {\n    // Enable this for verbose logging of controller input reports\n        //Log.v(TAG, \"onCharacteristicChanged uuid=\" + characteristic.getUuid() + \" data=\" + HexDump.dumpHexString(characteristic.getValue()));\n\n        if (characteristic.getUuid().equals(inputCharacteristic) && !mFrozen) {\n            mManager.HIDDeviceInputReport(getId(), characteristic.getValue());\n        }\n    }\n\n    @Override\n    public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {\n        //Log.v(TAG, \"onDescriptorRead status=\" + status);\n    }\n\n    @Override\n    public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {\n        BluetoothGattCharacteristic chr = descriptor.getCharacteristic();\n        //Log.v(TAG, \"onDescriptorWrite status=\" + status + \" uuid=\" + chr.getUuid() + \" descriptor=\" + descriptor.getUuid());\n\n        if (chr.getUuid().equals(inputCharacteristic)) {\n            boolean hasWrittenInputDescriptor = true;\n            BluetoothGattCharacteristic reportChr = chr.getService().getCharacteristic(reportCharacteristic);\n            if (reportChr != null) {\n                Log.v(TAG, \"Writing report characteristic to enter valve mode\");\n                reportChr.setValue(enterValveMode);\n                gatt.writeCharacteristic(reportChr);\n            }\n        }\n\n        finishCurrentGattOperation();\n    }\n\n    @Override\n    public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {\n        //Log.v(TAG, \"onReliableWriteCompleted status=\" + status);\n    }\n\n    @Override\n    public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {\n        //Log.v(TAG, \"onReadRemoteRssi status=\" + status);\n    }\n\n    @Override\n    public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {\n        //Log.v(TAG, \"onMtuChanged status=\" + status);\n    }\n\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n    //////// Public API\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n\n    @Override\n    public int getId() {\n        return mDeviceId;\n    }\n\n    @Override\n    public int getVendorId() {\n        // Valve Corporation\n        final int VALVE_USB_VID = 0x28DE;\n        return VALVE_USB_VID;\n    }\n\n    @Override\n    public int getProductId() {\n        // We don't have an easy way to query from the Bluetooth device, but we know what it is\n        final int D0G_BLE2_PID = 0x1106;\n        return D0G_BLE2_PID;\n    }\n\n    @Override\n    public String getSerialNumber() {\n        // This will be read later via feature report by Steam\n        return \"12345\";\n    }\n\n    @Override\n    public int getVersion() {\n        return 0;\n    }\n\n    @Override\n    public String getManufacturerName() {\n        return \"Valve Corporation\";\n    }\n\n    @Override\n    public String getProductName() {\n        return \"Steam Controller\";\n    }\n\n    @Override\n    public UsbDevice getDevice() {\n        return null;\n    }\n\n    @Override\n    public boolean open() {\n        return true;\n    }\n\n    @Override\n    public int writeReport(byte[] report, boolean feature) {\n        if (!isRegistered()) {\n            Log.e(TAG, \"Attempted writeReport before Steam Controller is registered!\");\n            if (mIsConnected) {\n                probeService(this);\n            }\n            return -1;\n        }\n\n        if (feature) {\n            // We need to skip the first byte, as that doesn't go over the air\n            byte[] actual_report = Arrays.copyOfRange(report, 1, report.length - 1);\n            //Log.v(TAG, \"writeFeatureReport \" + HexDump.dumpHexString(actual_report));\n            writeCharacteristic(reportCharacteristic, actual_report);\n            return report.length;\n        } else {\n            //Log.v(TAG, \"writeOutputReport \" + HexDump.dumpHexString(report));\n            writeCharacteristic(reportCharacteristic, report);\n            return report.length;\n        }\n    }\n\n    @Override\n    public boolean readReport(byte[] report, boolean feature) {\n        if (!isRegistered()) {\n            Log.e(TAG, \"Attempted readReport before Steam Controller is registered!\");\n            if (mIsConnected) {\n                probeService(this);\n            }\n            return false;\n        }\n\n        if (feature) {\n            readCharacteristic(reportCharacteristic);\n            return true;\n        } else {\n            // Not implemented\n            return false;\n        }\n    }\n\n    @Override\n    public void close() {\n    }\n\n    @Override\n    public void setFrozen(boolean frozen) {\n        mFrozen = frozen;\n    }\n\n    @Override\n    public void shutdown() {\n        close();\n\n        BluetoothGatt g = mGatt;\n        if (g != null) {\n            g.disconnect();\n            g.close();\n            mGatt = null;\n        }\n        mManager = null;\n        mIsRegistered = false;\n        mIsConnected = false;\n        mOperations.clear();\n    }\n\n}\n\n"
  },
  {
    "path": "android/app/src/main/java/org/libsdl/app/HIDDeviceManager.java",
    "content": "package org.libsdl.app;\n\nimport android.app.Activity;\nimport android.app.AlertDialog;\nimport android.app.PendingIntent;\nimport android.bluetooth.BluetoothAdapter;\nimport android.bluetooth.BluetoothDevice;\nimport android.bluetooth.BluetoothManager;\nimport android.bluetooth.BluetoothProfile;\nimport android.os.Build;\nimport android.util.Log;\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.DialogInterface;\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.content.SharedPreferences;\nimport android.content.pm.PackageManager;\nimport android.hardware.usb.*;\nimport android.os.Handler;\nimport android.os.Looper;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.List;\n\npublic class HIDDeviceManager {\n    private static final String TAG = \"hidapi\";\n    private static final String ACTION_USB_PERMISSION = \"org.libsdl.app.USB_PERMISSION\";\n\n    private static HIDDeviceManager sManager;\n    private static int sManagerRefCount = 0;\n\n    static public HIDDeviceManager acquire(Context context) {\n        if (sManagerRefCount == 0) {\n            sManager = new HIDDeviceManager(context);\n        }\n        ++sManagerRefCount;\n        return sManager;\n    }\n\n    static public void release(HIDDeviceManager manager) {\n        if (manager == sManager) {\n            --sManagerRefCount;\n            if (sManagerRefCount == 0) {\n                sManager.close();\n                sManager = null;\n            }\n        }\n    }\n\n    private Context mContext;\n    private HashMap<Integer, HIDDevice> mDevicesById = new HashMap<Integer, HIDDevice>();\n    private HashMap<BluetoothDevice, HIDDeviceBLESteamController> mBluetoothDevices = new HashMap<BluetoothDevice, HIDDeviceBLESteamController>();\n    private int mNextDeviceId = 0;\n    private SharedPreferences mSharedPreferences = null;\n    private boolean mIsChromebook = false;\n    private UsbManager mUsbManager;\n    private Handler mHandler;\n    private BluetoothManager mBluetoothManager;\n    private List<BluetoothDevice> mLastBluetoothDevices;\n\n    private final BroadcastReceiver mUsbBroadcast = new BroadcastReceiver() {\n        @Override\n        public void onReceive(Context context, Intent intent) {\n            String action = intent.getAction();\n            if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {\n                UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);\n                handleUsbDeviceAttached(usbDevice);\n            } else if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {\n                UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);\n                handleUsbDeviceDetached(usbDevice);\n            } else if (action.equals(HIDDeviceManager.ACTION_USB_PERMISSION)) {\n                UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);\n                handleUsbDevicePermission(usbDevice, intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false));\n            }\n        }\n    };\n\n    private final BroadcastReceiver mBluetoothBroadcast = new BroadcastReceiver() {\n        @Override\n        public void onReceive(Context context, Intent intent) {\n            String action = intent.getAction();\n            // Bluetooth device was connected. If it was a Steam Controller, handle it\n            if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {\n                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);\n                Log.d(TAG, \"Bluetooth device connected: \" + device);\n\n                if (isSteamController(device)) {\n                    connectBluetoothDevice(device);\n                }\n            }\n\n            // Bluetooth device was disconnected, remove from controller manager (if any)\n            if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {\n                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);\n                Log.d(TAG, \"Bluetooth device disconnected: \" + device);\n\n                disconnectBluetoothDevice(device);\n            }\n        }\n    };\n\n    private HIDDeviceManager(final Context context) {\n        mContext = context;\n\n        HIDDeviceRegisterCallback();\n\n        mSharedPreferences = mContext.getSharedPreferences(\"hidapi\", Context.MODE_PRIVATE);\n        mIsChromebook = SDLActivity.isChromebook();\n\n//        if (shouldClear) {\n//            SharedPreferences.Editor spedit = mSharedPreferences.edit();\n//            spedit.clear();\n//            spedit.apply();\n//        }\n//        else\n        {\n            mNextDeviceId = mSharedPreferences.getInt(\"next_device_id\", 0);\n        }\n    }\n\n    Context getContext() {\n        return mContext;\n    }\n\n    int getDeviceIDForIdentifier(String identifier) {\n        SharedPreferences.Editor spedit = mSharedPreferences.edit();\n\n        int result = mSharedPreferences.getInt(identifier, 0);\n        if (result == 0) {\n            result = mNextDeviceId++;\n            spedit.putInt(\"next_device_id\", mNextDeviceId);\n        }\n\n        spedit.putInt(identifier, result);\n        spedit.apply();\n        return result;\n    }\n\n    private void initializeUSB() {\n        mUsbManager = (UsbManager)mContext.getSystemService(Context.USB_SERVICE);\n        if (mUsbManager == null) {\n            return;\n        }\n\n        /*\n        // Logging\n        for (UsbDevice device : mUsbManager.getDeviceList().values()) {\n            Log.i(TAG,\"Path: \" + device.getDeviceName());\n            Log.i(TAG,\"Manufacturer: \" + device.getManufacturerName());\n            Log.i(TAG,\"Product: \" + device.getProductName());\n            Log.i(TAG,\"ID: \" + device.getDeviceId());\n            Log.i(TAG,\"Class: \" + device.getDeviceClass());\n            Log.i(TAG,\"Protocol: \" + device.getDeviceProtocol());\n            Log.i(TAG,\"Vendor ID \" + device.getVendorId());\n            Log.i(TAG,\"Product ID: \" + device.getProductId());\n            Log.i(TAG,\"Interface count: \" + device.getInterfaceCount());\n            Log.i(TAG,\"---------------------------------------\");\n\n            // Get interface details\n            for (int index = 0; index < device.getInterfaceCount(); index++) {\n                UsbInterface mUsbInterface = device.getInterface(index);\n                Log.i(TAG,\"  *****     *****\");\n                Log.i(TAG,\"  Interface index: \" + index);\n                Log.i(TAG,\"  Interface ID: \" + mUsbInterface.getId());\n                Log.i(TAG,\"  Interface class: \" + mUsbInterface.getInterfaceClass());\n                Log.i(TAG,\"  Interface subclass: \" + mUsbInterface.getInterfaceSubclass());\n                Log.i(TAG,\"  Interface protocol: \" + mUsbInterface.getInterfaceProtocol());\n                Log.i(TAG,\"  Endpoint count: \" + mUsbInterface.getEndpointCount());\n\n                // Get endpoint details\n                for (int epi = 0; epi < mUsbInterface.getEndpointCount(); epi++)\n                {\n                    UsbEndpoint mEndpoint = mUsbInterface.getEndpoint(epi);\n                    Log.i(TAG,\"    ++++   ++++   ++++\");\n                    Log.i(TAG,\"    Endpoint index: \" + epi);\n                    Log.i(TAG,\"    Attributes: \" + mEndpoint.getAttributes());\n                    Log.i(TAG,\"    Direction: \" + mEndpoint.getDirection());\n                    Log.i(TAG,\"    Number: \" + mEndpoint.getEndpointNumber());\n                    Log.i(TAG,\"    Interval: \" + mEndpoint.getInterval());\n                    Log.i(TAG,\"    Packet size: \" + mEndpoint.getMaxPacketSize());\n                    Log.i(TAG,\"    Type: \" + mEndpoint.getType());\n                }\n            }\n        }\n        Log.i(TAG,\" No more devices connected.\");\n        */\n\n        // Register for USB broadcasts and permission completions\n        IntentFilter filter = new IntentFilter();\n        filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);\n        filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);\n        filter.addAction(HIDDeviceManager.ACTION_USB_PERMISSION);\n        if (Build.VERSION.SDK_INT >= 33) { /* Android 13.0 (TIRAMISU) */\n            mContext.registerReceiver(mUsbBroadcast, filter, Context.RECEIVER_EXPORTED);\n        } else {\n            mContext.registerReceiver(mUsbBroadcast, filter);\n        }\n\n        for (UsbDevice usbDevice : mUsbManager.getDeviceList().values()) {\n            handleUsbDeviceAttached(usbDevice);\n        }\n    }\n\n    UsbManager getUSBManager() {\n        return mUsbManager;\n    }\n\n    private void shutdownUSB() {\n        try {\n            mContext.unregisterReceiver(mUsbBroadcast);\n        } catch (Exception e) {\n            // We may not have registered, that's okay\n        }\n    }\n\n    private boolean isHIDDeviceInterface(UsbDevice usbDevice, UsbInterface usbInterface) {\n        if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_HID) {\n            return true;\n        }\n        if (isXbox360Controller(usbDevice, usbInterface) || isXboxOneController(usbDevice, usbInterface)) {\n            return true;\n        }\n        return false;\n    }\n\n    private boolean isXbox360Controller(UsbDevice usbDevice, UsbInterface usbInterface) {\n        final int XB360_IFACE_SUBCLASS = 93;\n        final int XB360_IFACE_PROTOCOL = 1; // Wired\n        final int XB360W_IFACE_PROTOCOL = 129; // Wireless\n        final int[] SUPPORTED_VENDORS = {\n            0x0079, // GPD Win 2\n            0x044f, // Thrustmaster\n            0x045e, // Microsoft\n            0x046d, // Logitech\n            0x056e, // Elecom\n            0x06a3, // Saitek\n            0x0738, // Mad Catz\n            0x07ff, // Mad Catz\n            0x0e6f, // PDP\n            0x0f0d, // Hori\n            0x1038, // SteelSeries\n            0x11c9, // Nacon\n            0x12ab, // Unknown\n            0x1430, // RedOctane\n            0x146b, // BigBen\n            0x1532, // Razer Sabertooth\n            0x15e4, // Numark\n            0x162e, // Joytech\n            0x1689, // Razer Onza\n            0x1949, // Lab126, Inc.\n            0x1bad, // Harmonix\n            0x20d6, // PowerA\n            0x24c6, // PowerA\n            0x2c22, // Qanba\n            0x2dc8, // 8BitDo\n            0x9886, // ASTRO Gaming\n        };\n\n        if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&\n            usbInterface.getInterfaceSubclass() == XB360_IFACE_SUBCLASS &&\n            (usbInterface.getInterfaceProtocol() == XB360_IFACE_PROTOCOL ||\n             usbInterface.getInterfaceProtocol() == XB360W_IFACE_PROTOCOL)) {\n            int vendor_id = usbDevice.getVendorId();\n            for (int supportedVid : SUPPORTED_VENDORS) {\n                if (vendor_id == supportedVid) {\n                    return true;\n                }\n            }\n        }\n        return false;\n    }\n\n    private boolean isXboxOneController(UsbDevice usbDevice, UsbInterface usbInterface) {\n        final int XB1_IFACE_SUBCLASS = 71;\n        final int XB1_IFACE_PROTOCOL = 208;\n        final int[] SUPPORTED_VENDORS = {\n            0x03f0, // HP\n            0x044f, // Thrustmaster\n            0x045e, // Microsoft\n            0x0738, // Mad Catz\n            0x0b05, // ASUS\n            0x0e6f, // PDP\n            0x0f0d, // Hori\n            0x10f5, // Turtle Beach\n            0x1532, // Razer Wildcat\n            0x20d6, // PowerA\n            0x24c6, // PowerA\n            0x294b, // Snakebyte\n            0x2dc8, // 8BitDo\n            0x2e24, // Hyperkin\n            0x2e95, // SCUF\n            0x3285, // Nacon\n            0x3537, // GameSir\n            0x366c, // ByoWave\n        };\n\n        if (usbInterface.getId() == 0 &&\n            usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&\n            usbInterface.getInterfaceSubclass() == XB1_IFACE_SUBCLASS &&\n            usbInterface.getInterfaceProtocol() == XB1_IFACE_PROTOCOL) {\n            int vendor_id = usbDevice.getVendorId();\n            for (int supportedVid : SUPPORTED_VENDORS) {\n                if (vendor_id == supportedVid) {\n                    return true;\n                }\n            }\n        }\n        return false;\n    }\n\n    private void handleUsbDeviceAttached(UsbDevice usbDevice) {\n        connectHIDDeviceUSB(usbDevice);\n    }\n\n    private void handleUsbDeviceDetached(UsbDevice usbDevice) {\n        List<Integer> devices = new ArrayList<Integer>();\n        for (HIDDevice device : mDevicesById.values()) {\n            if (usbDevice.equals(device.getDevice())) {\n                devices.add(device.getId());\n            }\n        }\n        for (int id : devices) {\n            HIDDevice device = mDevicesById.get(id);\n            mDevicesById.remove(id);\n            device.shutdown();\n            HIDDeviceDisconnected(id);\n        }\n    }\n\n    private void handleUsbDevicePermission(UsbDevice usbDevice, boolean permission_granted) {\n        for (HIDDevice device : mDevicesById.values()) {\n            if (usbDevice.equals(device.getDevice())) {\n                boolean opened = false;\n                if (permission_granted) {\n                    opened = device.open();\n                }\n                HIDDeviceOpenResult(device.getId(), opened);\n            }\n        }\n    }\n\n    private void connectHIDDeviceUSB(UsbDevice usbDevice) {\n        synchronized (this) {\n            int interface_mask = 0;\n            for (int interface_index = 0; interface_index < usbDevice.getInterfaceCount(); interface_index++) {\n                UsbInterface usbInterface = usbDevice.getInterface(interface_index);\n                if (isHIDDeviceInterface(usbDevice, usbInterface)) {\n                    // Check to see if we've already added this interface\n                    // This happens with the Xbox Series X controller which has a duplicate interface 0, which is inactive\n                    int interface_id = usbInterface.getId();\n                    if ((interface_mask & (1 << interface_id)) != 0) {\n                        continue;\n                    }\n                    interface_mask |= (1 << interface_id);\n\n                    HIDDeviceUSB device = new HIDDeviceUSB(this, usbDevice, interface_index);\n                    int id = device.getId();\n                    mDevicesById.put(id, device);\n                    HIDDeviceConnected(id, device.getIdentifier(), device.getVendorId(), device.getProductId(), device.getSerialNumber(), device.getVersion(), device.getManufacturerName(), device.getProductName(), usbInterface.getId(), usbInterface.getInterfaceClass(), usbInterface.getInterfaceSubclass(), usbInterface.getInterfaceProtocol(), false);\n                }\n            }\n        }\n    }\n\n    private void initializeBluetooth() {\n        Log.d(TAG, \"Initializing Bluetooth\");\n\n        if (Build.VERSION.SDK_INT >= 31 /* Android 12  */ &&\n            mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH_CONNECT, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) {\n            Log.d(TAG, \"Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH_CONNECT\");\n            return;\n        }\n\n        if (Build.VERSION.SDK_INT <= 30 /* Android 11.0 (R) */ &&\n            mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) {\n            Log.d(TAG, \"Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH\");\n            return;\n        }\n\n        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {\n            Log.d(TAG, \"Couldn't initialize Bluetooth, this version of Android does not support Bluetooth LE\");\n            return;\n        }\n\n        // Find bonded bluetooth controllers and create SteamControllers for them\n        mBluetoothManager = (BluetoothManager)mContext.getSystemService(Context.BLUETOOTH_SERVICE);\n        if (mBluetoothManager == null) {\n            // This device doesn't support Bluetooth.\n            return;\n        }\n\n        BluetoothAdapter btAdapter = mBluetoothManager.getAdapter();\n        if (btAdapter == null) {\n            // This device has Bluetooth support in the codebase, but has no available adapters.\n            return;\n        }\n\n        // Get our bonded devices.\n        for (BluetoothDevice device : btAdapter.getBondedDevices()) {\n\n            Log.d(TAG, \"Bluetooth device available: \" + device);\n            if (isSteamController(device)) {\n                connectBluetoothDevice(device);\n            }\n\n        }\n\n        // NOTE: These don't work on Chromebooks, to my undying dismay.\n        IntentFilter filter = new IntentFilter();\n        filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);\n        filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);\n        if (Build.VERSION.SDK_INT >= 33) { /* Android 13.0 (TIRAMISU) */\n            mContext.registerReceiver(mBluetoothBroadcast, filter, Context.RECEIVER_EXPORTED);\n        } else {\n            mContext.registerReceiver(mBluetoothBroadcast, filter);\n        }\n\n        if (mIsChromebook) {\n            mHandler = new Handler(Looper.getMainLooper());\n            mLastBluetoothDevices = new ArrayList<BluetoothDevice>();\n\n            // final HIDDeviceManager finalThis = this;\n            // mHandler.postDelayed(new Runnable() {\n            //     @Override\n            //     public void run() {\n            //         finalThis.chromebookConnectionHandler();\n            //     }\n            // }, 5000);\n        }\n    }\n\n    private void shutdownBluetooth() {\n        try {\n            mContext.unregisterReceiver(mBluetoothBroadcast);\n        } catch (Exception e) {\n            // We may not have registered, that's okay\n        }\n    }\n\n    // Chromebooks do not pass along ACTION_ACL_CONNECTED / ACTION_ACL_DISCONNECTED properly.\n    // This function provides a sort of dummy version of that, watching for changes in the\n    // connected devices and attempting to add controllers as things change.\n    void chromebookConnectionHandler() {\n        if (!mIsChromebook) {\n            return;\n        }\n\n        ArrayList<BluetoothDevice> disconnected = new ArrayList<BluetoothDevice>();\n        ArrayList<BluetoothDevice> connected = new ArrayList<BluetoothDevice>();\n\n        List<BluetoothDevice> currentConnected = mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT);\n\n        for (BluetoothDevice bluetoothDevice : currentConnected) {\n            if (!mLastBluetoothDevices.contains(bluetoothDevice)) {\n                connected.add(bluetoothDevice);\n            }\n        }\n        for (BluetoothDevice bluetoothDevice : mLastBluetoothDevices) {\n            if (!currentConnected.contains(bluetoothDevice)) {\n                disconnected.add(bluetoothDevice);\n            }\n        }\n\n        mLastBluetoothDevices = currentConnected;\n\n        for (BluetoothDevice bluetoothDevice : disconnected) {\n            disconnectBluetoothDevice(bluetoothDevice);\n        }\n        for (BluetoothDevice bluetoothDevice : connected) {\n            connectBluetoothDevice(bluetoothDevice);\n        }\n\n        final HIDDeviceManager finalThis = this;\n        mHandler.postDelayed(new Runnable() {\n            @Override\n            public void run() {\n                finalThis.chromebookConnectionHandler();\n            }\n        }, 10000);\n    }\n\n    boolean connectBluetoothDevice(BluetoothDevice bluetoothDevice) {\n        Log.v(TAG, \"connectBluetoothDevice device=\" + bluetoothDevice);\n        synchronized (this) {\n            if (mBluetoothDevices.containsKey(bluetoothDevice)) {\n                Log.v(TAG, \"Steam controller with address \" + bluetoothDevice + \" already exists, attempting reconnect\");\n\n                HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice);\n                device.reconnect();\n\n                return false;\n            }\n            HIDDeviceBLESteamController device = new HIDDeviceBLESteamController(this, bluetoothDevice);\n            int id = device.getId();\n            mBluetoothDevices.put(bluetoothDevice, device);\n            mDevicesById.put(id, device);\n\n            // The Steam Controller will mark itself connected once initialization is complete\n        }\n        return true;\n    }\n\n    void disconnectBluetoothDevice(BluetoothDevice bluetoothDevice) {\n        synchronized (this) {\n            HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice);\n            if (device == null)\n                return;\n\n            int id = device.getId();\n            mBluetoothDevices.remove(bluetoothDevice);\n            mDevicesById.remove(id);\n            device.shutdown();\n            HIDDeviceDisconnected(id);\n        }\n    }\n\n    boolean isSteamController(BluetoothDevice bluetoothDevice) {\n        // Sanity check.  If you pass in a null device, by definition it is never a Steam Controller.\n        if (bluetoothDevice == null) {\n            return false;\n        }\n\n        // If the device has no local name, we really don't want to try an equality check against it.\n        if (bluetoothDevice.getName() == null) {\n            return false;\n        }\n\n        return bluetoothDevice.getName().equals(\"SteamController\") && ((bluetoothDevice.getType() & BluetoothDevice.DEVICE_TYPE_LE) != 0);\n    }\n\n    private void close() {\n        shutdownUSB();\n        shutdownBluetooth();\n        synchronized (this) {\n            for (HIDDevice device : mDevicesById.values()) {\n                device.shutdown();\n            }\n            mDevicesById.clear();\n            mBluetoothDevices.clear();\n            HIDDeviceReleaseCallback();\n        }\n    }\n\n    public void setFrozen(boolean frozen) {\n        synchronized (this) {\n            for (HIDDevice device : mDevicesById.values()) {\n                device.setFrozen(frozen);\n            }\n        }\n    }\n\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n\n    private HIDDevice getDevice(int id) {\n        synchronized (this) {\n            HIDDevice result = mDevicesById.get(id);\n            if (result == null) {\n                Log.v(TAG, \"No device for id: \" + id);\n                Log.v(TAG, \"Available devices: \" + mDevicesById.keySet());\n            }\n            return result;\n        }\n    }\n\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n    ////////// JNI interface functions\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n\n    boolean initialize(boolean usb, boolean bluetooth) {\n        Log.v(TAG, \"initialize(\" + usb + \", \" + bluetooth + \")\");\n\n        if (usb) {\n            initializeUSB();\n        }\n        if (bluetooth) {\n            initializeBluetooth();\n        }\n        return true;\n    }\n\n    boolean openDevice(int deviceID) {\n        Log.v(TAG, \"openDevice deviceID=\" + deviceID);\n        HIDDevice device = getDevice(deviceID);\n        if (device == null) {\n            HIDDeviceDisconnected(deviceID);\n            return false;\n        }\n\n        // Look to see if this is a USB device and we have permission to access it\n        UsbDevice usbDevice = device.getDevice();\n        if (usbDevice != null && !mUsbManager.hasPermission(usbDevice)) {\n            HIDDeviceOpenPending(deviceID);\n            try {\n                final int FLAG_MUTABLE = 0x02000000; // PendingIntent.FLAG_MUTABLE, but don't require SDK 31\n                int flags;\n                if (Build.VERSION.SDK_INT >= 31 /* Android 12.0 (S) */) {\n                    flags = FLAG_MUTABLE;\n                } else {\n                    flags = 0;\n                }\n\n                Intent intent = new Intent(HIDDeviceManager.ACTION_USB_PERMISSION);\n                intent.setPackage(mContext.getPackageName());\n                mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, intent, flags));\n            } catch (Exception e) {\n                Log.v(TAG, \"Couldn't request permission for USB device \" + usbDevice);\n                HIDDeviceOpenResult(deviceID, false);\n            }\n            return false;\n        }\n\n        try {\n            return device.open();\n        } catch (Exception e) {\n            Log.e(TAG, \"Got exception: \" + Log.getStackTraceString(e));\n        }\n        return false;\n    }\n\n    int writeReport(int deviceID, byte[] report, boolean feature) {\n        try {\n            //Log.v(TAG, \"writeReport deviceID=\" + deviceID + \" length=\" + report.length);\n            HIDDevice device;\n            device = getDevice(deviceID);\n            if (device == null) {\n                HIDDeviceDisconnected(deviceID);\n                return -1;\n            }\n\n            return device.writeReport(report, feature);\n        } catch (Exception e) {\n            Log.e(TAG, \"Got exception: \" + Log.getStackTraceString(e));\n        }\n        return -1;\n    }\n\n    boolean readReport(int deviceID, byte[] report, boolean feature) {\n        try {\n            //Log.v(TAG, \"readReport deviceID=\" + deviceID);\n            HIDDevice device;\n            device = getDevice(deviceID);\n            if (device == null) {\n                HIDDeviceDisconnected(deviceID);\n                return false;\n            }\n\n            return device.readReport(report, feature);\n        } catch (Exception e) {\n            Log.e(TAG, \"Got exception: \" + Log.getStackTraceString(e));\n        }\n        return false;\n    }\n\n    void closeDevice(int deviceID) {\n        try {\n            Log.v(TAG, \"closeDevice deviceID=\" + deviceID);\n            HIDDevice device;\n            device = getDevice(deviceID);\n            if (device == null) {\n                HIDDeviceDisconnected(deviceID);\n                return;\n            }\n\n            device.close();\n        } catch (Exception e) {\n            Log.e(TAG, \"Got exception: \" + Log.getStackTraceString(e));\n        }\n    }\n\n\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n    /////////////// Native methods\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n\n    private native void HIDDeviceRegisterCallback();\n    private native void HIDDeviceReleaseCallback();\n\n    native void HIDDeviceConnected(int deviceID, String identifier, int vendorId, int productId, String serial_number, int release_number, String manufacturer_string, String product_string, int interface_number, int interface_class, int interface_subclass, int interface_protocol, boolean bBluetooth);\n    native void HIDDeviceOpenPending(int deviceID);\n    native void HIDDeviceOpenResult(int deviceID, boolean opened);\n    native void HIDDeviceDisconnected(int deviceID);\n\n    native void HIDDeviceInputReport(int deviceID, byte[] report);\n    native void HIDDeviceReportResponse(int deviceID, byte[] report);\n}\n"
  },
  {
    "path": "android/app/src/main/java/org/libsdl/app/HIDDeviceUSB.java",
    "content": "package org.libsdl.app;\n\nimport android.hardware.usb.*;\nimport android.os.Build;\nimport android.util.Log;\nimport java.util.Arrays;\nimport java.util.Locale;\n\nclass HIDDeviceUSB implements HIDDevice {\n\n    private static final String TAG = \"hidapi\";\n\n    protected HIDDeviceManager mManager;\n    protected UsbDevice mDevice;\n    protected int mInterfaceIndex;\n    protected int mInterface;\n    protected int mDeviceId;\n    protected UsbDeviceConnection mConnection;\n    protected UsbEndpoint mInputEndpoint;\n    protected UsbEndpoint mOutputEndpoint;\n    protected InputThread mInputThread;\n    protected boolean mRunning;\n    protected boolean mFrozen;\n\n    public HIDDeviceUSB(HIDDeviceManager manager, UsbDevice usbDevice, int interface_index) {\n        mManager = manager;\n        mDevice = usbDevice;\n        mInterfaceIndex = interface_index;\n        mInterface = mDevice.getInterface(mInterfaceIndex).getId();\n        mDeviceId = manager.getDeviceIDForIdentifier(getIdentifier());\n        mRunning = false;\n    }\n\n    String getIdentifier() {\n        return String.format(Locale.ENGLISH, \"%s/%x/%x/%d\", mDevice.getDeviceName(), mDevice.getVendorId(), mDevice.getProductId(), mInterfaceIndex);\n    }\n\n    @Override\n    public int getId() {\n        return mDeviceId;\n    }\n\n    @Override\n    public int getVendorId() {\n        return mDevice.getVendorId();\n    }\n\n    @Override\n    public int getProductId() {\n        return mDevice.getProductId();\n    }\n\n    @Override\n    public String getSerialNumber() {\n        String result = null;\n        try {\n            result = mDevice.getSerialNumber();\n        }\n        catch (SecurityException exception) {\n            //Log.w(TAG, \"App permissions mean we cannot get serial number for device \" + getDeviceName() + \" message: \" + exception.getMessage());\n        }\n        if (result == null) {\n            result = \"\";\n        }\n        return result;\n    }\n\n    @Override\n    public int getVersion() {\n        return 0;\n    }\n\n    @Override\n    public String getManufacturerName() {\n        String result;\n        result = mDevice.getManufacturerName();\n        if (result == null) {\n            result = String.format(\"%x\", getVendorId());\n        }\n        return result;\n    }\n\n    @Override\n    public String getProductName() {\n        String result;\n        result = mDevice.getProductName();\n        if (result == null) {\n            result = String.format(\"%x\", getProductId());\n        }\n        return result;\n    }\n\n    @Override\n    public UsbDevice getDevice() {\n        return mDevice;\n    }\n\n    String getDeviceName() {\n        return getManufacturerName() + \" \" + getProductName() + \"(0x\" + String.format(\"%x\", getVendorId()) + \"/0x\" + String.format(\"%x\", getProductId()) + \")\";\n    }\n\n    @Override\n    public boolean open() {\n        mConnection = mManager.getUSBManager().openDevice(mDevice);\n        if (mConnection == null) {\n            Log.w(TAG, \"Unable to open USB device \" + getDeviceName());\n            return false;\n        }\n\n        // Force claim our interface\n        UsbInterface iface = mDevice.getInterface(mInterfaceIndex);\n        if (!mConnection.claimInterface(iface, true)) {\n            Log.w(TAG, \"Failed to claim interfaces on USB device \" + getDeviceName());\n            close();\n            return false;\n        }\n\n        // Find the endpoints\n        for (int j = 0; j < iface.getEndpointCount(); j++) {\n            UsbEndpoint endpt = iface.getEndpoint(j);\n            switch (endpt.getDirection()) {\n            case UsbConstants.USB_DIR_IN:\n                if (mInputEndpoint == null) {\n                    mInputEndpoint = endpt;\n                }\n                break;\n            case UsbConstants.USB_DIR_OUT:\n                if (mOutputEndpoint == null) {\n                    mOutputEndpoint = endpt;\n                }\n                break;\n            }\n        }\n\n        // Make sure the required endpoints were present\n        if (mInputEndpoint == null || mOutputEndpoint == null) {\n            Log.w(TAG, \"Missing required endpoint on USB device \" + getDeviceName());\n            close();\n            return false;\n        }\n\n        // Start listening for input\n        mRunning = true;\n        mInputThread = new InputThread();\n        mInputThread.start();\n\n        return true;\n    }\n\n    @Override\n    public int writeReport(byte[] report, boolean feature) {\n        if (mConnection == null) {\n            Log.w(TAG, \"writeReport() called with no device connection\");\n            return -1;\n        }\n\n        if (feature) {\n            int res = -1;\n            int offset = 0;\n            int length = report.length;\n            boolean skipped_report_id = false;\n            byte report_number = report[0];\n\n            if (report_number == 0x0) {\n                ++offset;\n                --length;\n                skipped_report_id = true;\n            }\n\n            res = mConnection.controlTransfer(\n                UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_OUT,\n                0x09/*HID set_report*/,\n                (3/*HID feature*/ << 8) | report_number,\n                mInterface,\n                report, offset, length,\n                1000/*timeout millis*/);\n\n            if (res < 0) {\n                Log.w(TAG, \"writeFeatureReport() returned \" + res + \" on device \" + getDeviceName());\n                return -1;\n            }\n\n            if (skipped_report_id) {\n                ++length;\n            }\n            return length;\n        } else {\n            int res = mConnection.bulkTransfer(mOutputEndpoint, report, report.length, 1000);\n            if (res != report.length) {\n                Log.w(TAG, \"writeOutputReport() returned \" + res + \" on device \" + getDeviceName());\n            }\n            return res;\n        }\n    }\n\n    @Override\n    public boolean readReport(byte[] report, boolean feature) {\n        int res = -1;\n        int offset = 0;\n        int length = report.length;\n        boolean skipped_report_id = false;\n        byte report_number = report[0];\n\n        if (mConnection == null) {\n            Log.w(TAG, \"readReport() called with no device connection\");\n            return false;\n        }\n\n        if (report_number == 0x0) {\n            /* Offset the return buffer by 1, so that the report ID\n               will remain in byte 0. */\n            ++offset;\n            --length;\n            skipped_report_id = true;\n        }\n\n        res = mConnection.controlTransfer(\n            UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_IN,\n            0x01/*HID get_report*/,\n            ((feature ? 3/*HID feature*/ : 1/*HID Input*/) << 8) | report_number,\n            mInterface,\n            report, offset, length,\n            1000/*timeout millis*/);\n\n        if (res < 0) {\n            Log.w(TAG, \"getFeatureReport() returned \" + res + \" on device \" + getDeviceName());\n            return false;\n        }\n\n        if (skipped_report_id) {\n            ++res;\n            ++length;\n        }\n\n        byte[] data;\n        if (res == length) {\n            data = report;\n        } else {\n            data = Arrays.copyOfRange(report, 0, res);\n        }\n        mManager.HIDDeviceReportResponse(mDeviceId, data);\n\n        return true;\n    }\n\n    @Override\n    public void close() {\n        mRunning = false;\n        if (mInputThread != null) {\n            while (mInputThread.isAlive()) {\n                mInputThread.interrupt();\n                try {\n                    mInputThread.join();\n                } catch (InterruptedException e) {\n                    // Keep trying until we're done\n                }\n            }\n            mInputThread = null;\n        }\n        if (mConnection != null) {\n            UsbInterface iface = mDevice.getInterface(mInterfaceIndex);\n            mConnection.releaseInterface(iface);\n            mConnection.close();\n            mConnection = null;\n        }\n    }\n\n    @Override\n    public void shutdown() {\n        close();\n        mManager = null;\n    }\n\n    @Override\n    public void setFrozen(boolean frozen) {\n        mFrozen = frozen;\n    }\n\n    protected class InputThread extends Thread {\n        @Override\n        public void run() {\n            int packetSize = mInputEndpoint.getMaxPacketSize();\n            byte[] packet = new byte[packetSize];\n            while (mRunning) {\n                int r;\n                try\n                {\n                    r = mConnection.bulkTransfer(mInputEndpoint, packet, packetSize, 1000);\n                }\n                catch (Exception e)\n                {\n                    Log.v(TAG, \"Exception in UsbDeviceConnection bulktransfer: \" + e);\n                    break;\n                }\n                if (r < 0) {\n                    // Could be a timeout or an I/O error\n                }\n                if (r > 0) {\n                    byte[] data;\n                    if (r == packetSize) {\n                        data = packet;\n                    } else {\n                        data = Arrays.copyOfRange(packet, 0, r);\n                    }\n\n                    if (!mFrozen) {\n                        mManager.HIDDeviceInputReport(mDeviceId, data);\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "android/app/src/main/java/org/libsdl/app/SDL.java",
    "content": "package org.libsdl.app;\n\nimport android.app.Activity;\nimport android.content.Context;\n\nimport java.lang.reflect.Method;\n\n/**\n    SDL library initialization\n*/\npublic class SDL {\n\n    // This function should be called first and sets up the native code\n    // so it can call into the Java classes\n    static public void setupJNI() {\n        SDLActivity.nativeSetupJNI();\n        SDLAudioManager.nativeSetupJNI();\n        SDLControllerManager.nativeSetupJNI();\n    }\n\n    // This function should be called each time the activity is started\n    static public void initialize() {\n        setContext(null);\n\n        SDLActivity.initialize();\n        SDLAudioManager.initialize();\n        SDLControllerManager.initialize();\n    }\n\n    // This function stores the current activity (SDL or not)\n    static public void setContext(Activity context) {\n        SDLAudioManager.setContext(context);\n        mContext = context;\n    }\n\n    static public Activity getContext() {\n        return mContext;\n    }\n\n    static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, SecurityException, NullPointerException {\n        loadLibrary(libraryName, mContext);\n    }\n\n    static void loadLibrary(String libraryName, Context context) throws UnsatisfiedLinkError, SecurityException, NullPointerException {\n\n        if (libraryName == null) {\n            throw new NullPointerException(\"No library name provided.\");\n        }\n\n        try {\n            // Let's see if we have ReLinker available in the project.  This is necessary for\n            // some projects that have huge numbers of local libraries bundled, and thus may\n            // trip a bug in Android's native library loader which ReLinker works around.  (If\n            // loadLibrary works properly, ReLinker will simply use the normal Android method\n            // internally.)\n            //\n            // To use ReLinker, just add it as a dependency.  For more information, see\n            // https://github.com/KeepSafe/ReLinker for ReLinker's repository.\n            //\n            Class<?> relinkClass = context.getClassLoader().loadClass(\"com.getkeepsafe.relinker.ReLinker\");\n            Class<?> relinkListenerClass = context.getClassLoader().loadClass(\"com.getkeepsafe.relinker.ReLinker$LoadListener\");\n            Class<?> contextClass = context.getClassLoader().loadClass(\"android.content.Context\");\n            Class<?> stringClass = context.getClassLoader().loadClass(\"java.lang.String\");\n\n            // Get a 'force' instance of the ReLinker, so we can ensure libraries are reinstalled if\n            // they've changed during updates.\n            Method forceMethod = relinkClass.getDeclaredMethod(\"force\");\n            Object relinkInstance = forceMethod.invoke(null);\n            Class<?> relinkInstanceClass = relinkInstance.getClass();\n\n            // Actually load the library!\n            Method loadMethod = relinkInstanceClass.getDeclaredMethod(\"loadLibrary\", contextClass, stringClass, stringClass, relinkListenerClass);\n            loadMethod.invoke(relinkInstance, context, libraryName, null, null);\n        }\n        catch (final Throwable e) {\n            // Fall back\n            try {\n                System.loadLibrary(libraryName);\n            }\n            catch (final UnsatisfiedLinkError ule) {\n                throw ule;\n            }\n            catch (final SecurityException se) {\n                throw se;\n            }\n        }\n    }\n\n    protected static Activity mContext;\n}\n"
  },
  {
    "path": "android/app/src/main/java/org/libsdl/app/SDLActivity.java",
    "content": "package org.libsdl.app;\n\nimport android.app.Activity;\nimport android.app.AlertDialog;\nimport android.app.Dialog;\nimport android.app.UiModeManager;\nimport android.content.ActivityNotFoundException;\nimport android.content.ClipboardManager;\nimport android.content.ClipData;\nimport android.content.Context;\nimport android.content.DialogInterface;\nimport android.content.Intent;\nimport android.content.UriPermission;\nimport android.content.pm.ActivityInfo;\nimport android.content.pm.ApplicationInfo;\nimport android.content.pm.PackageManager;\nimport android.content.res.Configuration;\nimport android.graphics.Bitmap;\nimport android.graphics.Color;\nimport android.graphics.PorterDuff;\nimport android.graphics.drawable.Drawable;\nimport android.hardware.Sensor;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.os.LocaleList;\nimport android.os.Message;\nimport android.os.ParcelFileDescriptor;\nimport android.util.DisplayMetrics;\nimport android.util.Log;\nimport android.util.SparseArray;\nimport android.view.Display;\nimport android.view.Gravity;\nimport android.view.InputDevice;\nimport android.view.KeyEvent;\nimport android.view.PointerIcon;\nimport android.view.Surface;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.Window;\nimport android.view.WindowManager;\nimport android.view.inputmethod.InputConnection;\nimport android.view.inputmethod.InputMethodManager;\nimport android.webkit.MimeTypeMap;\nimport android.widget.Button;\nimport android.widget.LinearLayout;\nimport android.widget.RelativeLayout;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport java.io.FileNotFoundException;\nimport java.util.ArrayList;\nimport java.util.Hashtable;\nimport java.util.Locale;\n\n\n/**\n    SDL Activity\n*/\npublic class SDLActivity extends Activity implements View.OnSystemUiVisibilityChangeListener {\n    private static final String TAG = \"SDL\";\n    private static final int SDL_MAJOR_VERSION = 3;\n    private static final int SDL_MINOR_VERSION = 4;\n    private static final int SDL_MICRO_VERSION = 0;\n/*\n    // Display InputType.SOURCE/CLASS of events and devices\n    //\n    // SDLActivity.debugSource(device.getSources(), \"device[\" + device.getName() + \"]\");\n    // SDLActivity.debugSource(event.getSource(), \"event\");\n    public static void debugSource(int sources, String prefix) {\n        int s = sources;\n        int s_copy = sources;\n        String cls = \"\";\n        String src = \"\";\n        int tst = 0;\n        int FLAG_TAINTED = 0x80000000;\n\n        if ((s & InputDevice.SOURCE_CLASS_BUTTON) != 0)     cls += \" BUTTON\";\n        if ((s & InputDevice.SOURCE_CLASS_JOYSTICK) != 0)   cls += \" JOYSTICK\";\n        if ((s & InputDevice.SOURCE_CLASS_POINTER) != 0)    cls += \" POINTER\";\n        if ((s & InputDevice.SOURCE_CLASS_POSITION) != 0)   cls += \" POSITION\";\n        if ((s & InputDevice.SOURCE_CLASS_TRACKBALL) != 0)  cls += \" TRACKBALL\";\n\n\n        int s2 = s_copy & ~InputDevice.SOURCE_ANY; // keep class bits\n        s2 &= ~(  InputDevice.SOURCE_CLASS_BUTTON\n                | InputDevice.SOURCE_CLASS_JOYSTICK\n                | InputDevice.SOURCE_CLASS_POINTER\n                | InputDevice.SOURCE_CLASS_POSITION\n                | InputDevice.SOURCE_CLASS_TRACKBALL);\n\n        if (s2 != 0) cls += \"Some_Unknown\";\n\n        s2 = s_copy & InputDevice.SOURCE_ANY; // keep source only, no class;\n\n        if (Build.VERSION.SDK_INT >= 23) {\n            tst = InputDevice.SOURCE_BLUETOOTH_STYLUS;\n            if ((s & tst) == tst) src += \" BLUETOOTH_STYLUS\";\n            s2 &= ~tst;\n        }\n\n        tst = InputDevice.SOURCE_DPAD;\n        if ((s & tst) == tst) src += \" DPAD\";\n        s2 &= ~tst;\n\n        tst = InputDevice.SOURCE_GAMEPAD;\n        if ((s & tst) == tst) src += \" GAMEPAD\";\n        s2 &= ~tst;\n\n        tst = InputDevice.SOURCE_HDMI;\n        if ((s & tst) == tst) src += \" HDMI\";\n        s2 &= ~tst;\n\n        tst = InputDevice.SOURCE_JOYSTICK;\n        if ((s & tst) == tst) src += \" JOYSTICK\";\n        s2 &= ~tst;\n\n        tst = InputDevice.SOURCE_KEYBOARD;\n        if ((s & tst) == tst) src += \" KEYBOARD\";\n        s2 &= ~tst;\n\n        tst = InputDevice.SOURCE_MOUSE;\n        if ((s & tst) == tst) src += \" MOUSE\";\n        s2 &= ~tst;\n\n        if (Build.VERSION.SDK_INT >= 26) {\n            tst = InputDevice.SOURCE_MOUSE_RELATIVE;\n            if ((s & tst) == tst) src += \" MOUSE_RELATIVE\";\n            s2 &= ~tst;\n\n            tst = InputDevice.SOURCE_ROTARY_ENCODER;\n            if ((s & tst) == tst) src += \" ROTARY_ENCODER\";\n            s2 &= ~tst;\n        }\n        tst = InputDevice.SOURCE_STYLUS;\n        if ((s & tst) == tst) src += \" STYLUS\";\n        s2 &= ~tst;\n\n        tst = InputDevice.SOURCE_TOUCHPAD;\n        if ((s & tst) == tst) src += \" TOUCHPAD\";\n        s2 &= ~tst;\n\n        tst = InputDevice.SOURCE_TOUCHSCREEN;\n        if ((s & tst) == tst) src += \" TOUCHSCREEN\";\n        s2 &= ~tst;\n\n        tst = InputDevice.SOURCE_TOUCH_NAVIGATION;\n        if ((s & tst) == tst) src += \" TOUCH_NAVIGATION\";\n        s2 &= ~tst;\n\n        tst = InputDevice.SOURCE_TRACKBALL;\n        if ((s & tst) == tst) src += \" TRACKBALL\";\n        s2 &= ~tst;\n\n        tst = InputDevice.SOURCE_ANY;\n        if ((s & tst) == tst) src += \" ANY\";\n        s2 &= ~tst;\n\n        if (s == FLAG_TAINTED) src += \" FLAG_TAINTED\";\n        s2 &= ~FLAG_TAINTED;\n\n        if (s2 != 0) src += \" Some_Unknown\";\n\n        Log.v(TAG, prefix + \"int=\" + s_copy + \" CLASS={\" + cls + \" } source(s):\" + src);\n    }\n*/\n\n    public static boolean mIsResumedCalled, mHasFocus;\n    public static final boolean mHasMultiWindow = (Build.VERSION.SDK_INT >= 24  /* Android 7.0 (N) */);\n\n    // Cursor types\n    // private static final int SDL_SYSTEM_CURSOR_NONE = -1;\n    private static final int SDL_SYSTEM_CURSOR_ARROW = 0;\n    private static final int SDL_SYSTEM_CURSOR_IBEAM = 1;\n    private static final int SDL_SYSTEM_CURSOR_WAIT = 2;\n    private static final int SDL_SYSTEM_CURSOR_CROSSHAIR = 3;\n    private static final int SDL_SYSTEM_CURSOR_WAITARROW = 4;\n    private static final int SDL_SYSTEM_CURSOR_SIZENWSE = 5;\n    private static final int SDL_SYSTEM_CURSOR_SIZENESW = 6;\n    private static final int SDL_SYSTEM_CURSOR_SIZEWE = 7;\n    private static final int SDL_SYSTEM_CURSOR_SIZENS = 8;\n    private static final int SDL_SYSTEM_CURSOR_SIZEALL = 9;\n    private static final int SDL_SYSTEM_CURSOR_NO = 10;\n    private static final int SDL_SYSTEM_CURSOR_HAND = 11;\n    private static final int SDL_SYSTEM_CURSOR_WINDOW_TOPLEFT = 12;\n    private static final int SDL_SYSTEM_CURSOR_WINDOW_TOP = 13;\n    private static final int SDL_SYSTEM_CURSOR_WINDOW_TOPRIGHT = 14;\n    private static final int SDL_SYSTEM_CURSOR_WINDOW_RIGHT = 15;\n    private static final int SDL_SYSTEM_CURSOR_WINDOW_BOTTOMRIGHT = 16;\n    private static final int SDL_SYSTEM_CURSOR_WINDOW_BOTTOM = 17;\n    private static final int SDL_SYSTEM_CURSOR_WINDOW_BOTTOMLEFT = 18;\n    private static final int SDL_SYSTEM_CURSOR_WINDOW_LEFT = 19;\n\n    protected static final int SDL_ORIENTATION_UNKNOWN = 0;\n    protected static final int SDL_ORIENTATION_LANDSCAPE = 1;\n    protected static final int SDL_ORIENTATION_LANDSCAPE_FLIPPED = 2;\n    protected static final int SDL_ORIENTATION_PORTRAIT = 3;\n    protected static final int SDL_ORIENTATION_PORTRAIT_FLIPPED = 4;\n\n    protected static int mCurrentRotation;\n    protected static Locale mCurrentLocale;\n\n    // Handle the state of the native layer\n    public enum NativeState {\n           INIT, RESUMED, PAUSED\n    }\n\n    public static NativeState mNextNativeState;\n    public static NativeState mCurrentNativeState;\n\n    /** If shared libraries (e.g. SDL or the native application) could not be loaded. */\n    public static boolean mBrokenLibraries = true;\n\n    // Main components\n    protected static SDLActivity mSingleton;\n    protected static SDLSurface mSurface;\n    protected static SDLDummyEdit mTextEdit;\n    protected static ViewGroup mLayout;\n    protected static SDLClipboardHandler mClipboardHandler;\n    protected static Hashtable<Integer, PointerIcon> mCursors;\n    protected static int mLastCursorID;\n    protected static SDLGenericMotionListener_API14 mMotionListener;\n    protected static HIDDeviceManager mHIDDeviceManager;\n\n    // This is what SDL runs in. It invokes SDL_main(), eventually\n    protected static Thread mSDLThread;\n    protected static boolean mSDLMainFinished = false;\n    protected static boolean mActivityCreated = false;\n    private static SDLFileDialogState mFileDialogState = null;\n    protected static boolean mDispatchingKeyEvent = false;\n\n    public static SDLGenericMotionListener_API14 getMotionListener() {\n        if (mMotionListener == null) {\n            if (Build.VERSION.SDK_INT >= 29 /* Android 10 (Q) */) {\n                mMotionListener = new SDLGenericMotionListener_API29();\n            } else if (Build.VERSION.SDK_INT >= 26 /* Android 8.0 (O) */) {\n                mMotionListener = new SDLGenericMotionListener_API26();\n            } else if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n                mMotionListener = new SDLGenericMotionListener_API24();\n            } else {\n                mMotionListener = new SDLGenericMotionListener_API14();\n            }\n        }\n\n        return mMotionListener;\n    }\n\n    /**\n     * The application entry point, called on a dedicated thread (SDLThread).\n     * The default implementation uses the getMainSharedObject() and getMainFunction() methods\n     * to invoke native code from the specified shared library.\n     * It can be overridden by derived classes.\n     */\n    protected void main() {\n        String library = SDLActivity.mSingleton.getMainSharedObject();\n        String function = SDLActivity.mSingleton.getMainFunction();\n        String[] arguments = SDLActivity.mSingleton.getArguments();\n\n        Log.v(\"SDL\", \"Running main function \" + function + \" from library \" + library);\n        SDLActivity.nativeRunMain(library, function, arguments);\n        Log.v(\"SDL\", \"Finished main function\");\n    }\n\n    /**\n     * This method returns the name of the shared object with the application entry point\n     * It can be overridden by derived classes.\n     */\n    protected String getMainSharedObject() {\n        String library;\n        String[] libraries = SDLActivity.mSingleton.getLibraries();\n        if (libraries.length > 0) {\n            library = \"lib\" + libraries[libraries.length - 1] + \".so\";\n        } else {\n            library = \"libmain.so\";\n        }\n        return getContext().getApplicationInfo().nativeLibraryDir + \"/\" + library;\n    }\n\n    /**\n     * This method returns the name of the application entry point\n     * It can be overridden by derived classes.\n     */\n    protected String getMainFunction() {\n        return \"SDL_main\";\n    }\n\n    /**\n     * This method is called by SDL before loading the native shared libraries.\n     * It can be overridden to provide names of shared libraries to be loaded.\n     * The default implementation returns the defaults. It never returns null.\n     * An array returned by a new implementation must at least contain \"SDL3\".\n     * Also keep in mind that the order the libraries are loaded may matter.\n     * @return names of shared libraries to be loaded (e.g. \"SDL3\", \"main\").\n     */\n    protected String[] getLibraries() {\n        return new String[] {\n            \"SDL3\",\n            // \"SDL3_image\",\n            // \"SDL3_mixer\",\n            // \"SDL3_net\",\n            // \"SDL3_ttf\",\n            \"main\"\n        };\n    }\n\n    // Load the .so\n    public void loadLibraries() {\n       for (String lib : getLibraries()) {\n          SDL.loadLibrary(lib, this);\n       }\n    }\n\n    /**\n     * This method is called by SDL before starting the native application thread.\n     * It can be overridden to provide the arguments after the application name.\n     * The default implementation returns an empty array. It never returns null.\n     * @return arguments for the native application.\n     */\n    protected String[] getArguments() {\n        return new String[0];\n    }\n\n    public static void initialize() {\n        // The static nature of the singleton and Android quirkyness force us to initialize everything here\n        // Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values\n        mSingleton = null;\n        mSurface = null;\n        mTextEdit = null;\n        mLayout = null;\n        mClipboardHandler = null;\n        mCursors = new Hashtable<Integer, PointerIcon>();\n        mLastCursorID = 0;\n        mSDLThread = null;\n        mIsResumedCalled = false;\n        mHasFocus = true;\n        mNextNativeState = NativeState.INIT;\n        mCurrentNativeState = NativeState.INIT;\n    }\n\n    protected SDLSurface createSDLSurface(Context context) {\n        return new SDLSurface(context);\n    }\n\n    // Setup\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        Log.v(TAG, \"Manufacturer: \" + Build.MANUFACTURER);\n        Log.v(TAG, \"Device: \" + Build.DEVICE);\n        Log.v(TAG, \"Model: \" + Build.MODEL);\n        Log.v(TAG, \"onCreate()\");\n        super.onCreate(savedInstanceState);\n\n\n        /* Control activity re-creation */\n        if (mSDLMainFinished || mActivityCreated) {\n              boolean allow_recreate = SDLActivity.nativeAllowRecreateActivity();\n              if (mSDLMainFinished) {\n                  Log.v(TAG, \"SDL main() finished\");\n              }\n              if (allow_recreate) {\n                  Log.v(TAG, \"activity re-created\");\n              } else {\n                  Log.v(TAG, \"activity finished\");\n                  System.exit(0);\n                  return;\n              }\n        }\n\n        mActivityCreated = true;\n\n        try {\n            Thread.currentThread().setName(\"SDLActivity\");\n        } catch (Exception e) {\n            Log.v(TAG, \"modify thread properties failed \" + e.toString());\n        }\n\n        // Load shared libraries\n        String errorMsgBrokenLib = \"\";\n        try {\n            loadLibraries();\n            mBrokenLibraries = false; /* success */\n        } catch(UnsatisfiedLinkError e) {\n            System.err.println(e.getMessage());\n            mBrokenLibraries = true;\n            errorMsgBrokenLib = e.getMessage();\n        } catch(Exception e) {\n            System.err.println(e.getMessage());\n            mBrokenLibraries = true;\n            errorMsgBrokenLib = e.getMessage();\n        }\n\n        if (!mBrokenLibraries) {\n            String expected_version = String.valueOf(SDL_MAJOR_VERSION) + \".\" +\n                                      String.valueOf(SDL_MINOR_VERSION) + \".\" +\n                                      String.valueOf(SDL_MICRO_VERSION);\n            String version = nativeGetVersion();\n            if (!version.equals(expected_version)) {\n                mBrokenLibraries = true;\n                errorMsgBrokenLib = \"SDL C/Java version mismatch (expected \" + expected_version + \", got \" + version + \")\";\n            }\n        }\n\n        if (mBrokenLibraries) {\n            mSingleton = this;\n            AlertDialog.Builder dlgAlert  = new AlertDialog.Builder(this);\n            dlgAlert.setMessage(\"An error occurred while trying to start the application. Please try again and/or reinstall.\"\n                  + System.getProperty(\"line.separator\")\n                  + System.getProperty(\"line.separator\")\n                  + \"Error: \" + errorMsgBrokenLib);\n            dlgAlert.setTitle(\"SDL Error\");\n            dlgAlert.setPositiveButton(\"Exit\",\n                new DialogInterface.OnClickListener() {\n                    @Override\n                    public void onClick(DialogInterface dialog,int id) {\n                        // if this button is clicked, close current activity\n                        SDLActivity.mSingleton.finish();\n                    }\n                });\n           dlgAlert.setCancelable(false);\n           dlgAlert.create().show();\n\n           return;\n        }\n\n\n        /* Control activity re-creation */\n        /* Robustness: check that the native code is run for the first time.\n         * (Maybe Activity was reset, but not the native code.) */\n        {\n            int run_count = SDLActivity.nativeCheckSDLThreadCounter(); /* get and increment a native counter */\n            if (run_count != 0) {\n                boolean allow_recreate = SDLActivity.nativeAllowRecreateActivity();\n                if (allow_recreate) {\n                    Log.v(TAG, \"activity re-created // run_count: \" + run_count);\n                } else {\n                    Log.v(TAG, \"activity finished // run_count: \" + run_count);\n                    System.exit(0);\n                    return;\n                }\n            }\n        }\n\n        // Set up JNI\n        SDL.setupJNI();\n\n        // Initialize state\n        SDL.initialize();\n\n        // So we can call stuff from static callbacks\n        mSingleton = this;\n        SDL.setContext(this);\n\n        mClipboardHandler = new SDLClipboardHandler();\n\n        mHIDDeviceManager = HIDDeviceManager.acquire(this);\n\n        // Set up the surface\n        mSurface = createSDLSurface(this);\n\n        mLayout = new RelativeLayout(this);\n        mLayout.addView(mSurface);\n\n        // Get our current screen orientation and pass it down.\n        SDLActivity.nativeSetNaturalOrientation(SDLActivity.getNaturalOrientation());\n        mCurrentRotation = SDLActivity.getCurrentRotation();\n        SDLActivity.onNativeRotationChanged(mCurrentRotation);\n\n        try {\n            if (Build.VERSION.SDK_INT < 24 /* Android 7.0 (N) */) {\n                mCurrentLocale = getContext().getResources().getConfiguration().locale;\n            } else {\n                mCurrentLocale = getContext().getResources().getConfiguration().getLocales().get(0);\n            }\n        } catch(Exception ignored) {\n        }\n\n        switch (getContext().getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) {\n        case Configuration.UI_MODE_NIGHT_NO:\n            SDLActivity.onNativeDarkModeChanged(false);\n            break;\n        case Configuration.UI_MODE_NIGHT_YES:\n            SDLActivity.onNativeDarkModeChanged(true);\n            break;\n        }\n\n        setContentView(mLayout);\n\n        setWindowStyle(false);\n\n        getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(this);\n\n        // Get filename from \"Open with\" of another application\n        Intent intent = getIntent();\n        if (intent != null && intent.getData() != null) {\n            String filename = intent.getData().getPath();\n            if (filename != null) {\n                Log.v(TAG, \"Got filename: \" + filename);\n                SDLActivity.onNativeDropFile(filename);\n            }\n        }\n    }\n\n    protected void pauseNativeThread() {\n        mNextNativeState = NativeState.PAUSED;\n        mIsResumedCalled = false;\n\n        if (SDLActivity.mBrokenLibraries) {\n            return;\n        }\n\n        SDLActivity.handleNativeState();\n    }\n\n    protected void resumeNativeThread() {\n        mNextNativeState = NativeState.RESUMED;\n        mIsResumedCalled = true;\n\n        if (SDLActivity.mBrokenLibraries) {\n           return;\n        }\n\n        SDLActivity.handleNativeState();\n    }\n\n    // Events\n    @Override\n    protected void onPause() {\n        Log.v(TAG, \"onPause()\");\n        super.onPause();\n\n        if (mHIDDeviceManager != null) {\n            mHIDDeviceManager.setFrozen(true);\n        }\n        if (!mHasMultiWindow) {\n            pauseNativeThread();\n        }\n    }\n\n    @Override\n    protected void onResume() {\n        Log.v(TAG, \"onResume()\");\n        super.onResume();\n\n        if (mHIDDeviceManager != null) {\n            mHIDDeviceManager.setFrozen(false);\n        }\n        if (!mHasMultiWindow) {\n            resumeNativeThread();\n        }\n    }\n\n    @Override\n    protected void onStop() {\n        Log.v(TAG, \"onStop()\");\n        super.onStop();\n        if (mHasMultiWindow) {\n            pauseNativeThread();\n        }\n    }\n\n    @Override\n    protected void onStart() {\n        Log.v(TAG, \"onStart()\");\n        super.onStart();\n        if (mHasMultiWindow) {\n            resumeNativeThread();\n        }\n    }\n\n    public static int getNaturalOrientation() {\n        int result = SDL_ORIENTATION_UNKNOWN;\n\n        Activity activity = (Activity)getContext();\n        if (activity != null) {\n            Configuration config = activity.getResources().getConfiguration();\n            Display display = activity.getWindowManager().getDefaultDisplay();\n            int rotation = display.getRotation();\n            if (((rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) &&\n                    config.orientation == Configuration.ORIENTATION_LANDSCAPE) ||\n                ((rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) &&\n                    config.orientation == Configuration.ORIENTATION_PORTRAIT)) {\n                result = SDL_ORIENTATION_LANDSCAPE;\n            } else {\n                result = SDL_ORIENTATION_PORTRAIT;\n            }\n        }\n        return result;\n    }\n\n    public static int getCurrentRotation() {\n        int result = 0;\n\n        Activity activity = (Activity)getContext();\n        if (activity != null) {\n            Display display = activity.getWindowManager().getDefaultDisplay();\n            switch (display.getRotation()) {\n                case Surface.ROTATION_0:\n                    result = 0;\n                    break;\n                case Surface.ROTATION_90:\n                    result = 90;\n                    break;\n                case Surface.ROTATION_180:\n                    result = 180;\n                    break;\n                case Surface.ROTATION_270:\n                    result = 270;\n                    break;\n            }\n        }\n        return result;\n    }\n\n    @Override\n    public void onWindowFocusChanged(boolean hasFocus) {\n        super.onWindowFocusChanged(hasFocus);\n        Log.v(TAG, \"onWindowFocusChanged(): \" + hasFocus);\n\n        if (SDLActivity.mBrokenLibraries) {\n           return;\n        }\n\n        mHasFocus = hasFocus;\n        if (hasFocus) {\n           mNextNativeState = NativeState.RESUMED;\n           SDLActivity.getMotionListener().reclaimRelativeMouseModeIfNeeded();\n\n           SDLActivity.handleNativeState();\n           nativeFocusChanged(true);\n\n        } else {\n           nativeFocusChanged(false);\n           if (!mHasMultiWindow) {\n               mNextNativeState = NativeState.PAUSED;\n               SDLActivity.handleNativeState();\n           }\n        }\n    }\n\n    @Override\n    public void onTrimMemory(int level) {\n        Log.v(TAG, \"onTrimMemory()\");\n        super.onTrimMemory(level);\n\n        if (SDLActivity.mBrokenLibraries) {\n           return;\n        }\n\n        SDLActivity.nativeLowMemory();\n    }\n\n    @Override\n    public void onConfigurationChanged(Configuration newConfig) {\n        Log.v(TAG, \"onConfigurationChanged()\");\n        super.onConfigurationChanged(newConfig);\n\n        if (SDLActivity.mBrokenLibraries) {\n           return;\n        }\n\n        if (mCurrentLocale == null || !mCurrentLocale.equals(newConfig.locale)) {\n            mCurrentLocale = newConfig.locale;\n            SDLActivity.onNativeLocaleChanged();\n        }\n\n        switch (newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) {\n        case Configuration.UI_MODE_NIGHT_NO:\n            SDLActivity.onNativeDarkModeChanged(false);\n            break;\n        case Configuration.UI_MODE_NIGHT_YES:\n            SDLActivity.onNativeDarkModeChanged(true);\n            break;\n        }\n    }\n\n    @Override\n    protected void onDestroy() {\n        Log.v(TAG, \"onDestroy()\");\n\n        if (mHIDDeviceManager != null) {\n            HIDDeviceManager.release(mHIDDeviceManager);\n            mHIDDeviceManager = null;\n        }\n\n        SDLAudioManager.release(this);\n\n        if (SDLActivity.mBrokenLibraries) {\n           super.onDestroy();\n           return;\n        }\n\n        if (SDLActivity.mSDLThread != null) {\n\n            // Send Quit event to \"SDLThread\" thread\n            SDLActivity.nativeSendQuit();\n\n            // Wait for \"SDLThread\" thread to end\n            try {\n                // Use a timeout because:\n                // C SDLmain() thread might have started (mSDLThread.start() called)\n                // while the SDL_Init() might not have been called yet,\n                // and so the previous QUIT event will be discarded by SDL_Init() and app is running, not exiting.\n                SDLActivity.mSDLThread.join(1000);\n            } catch(Exception e) {\n                Log.v(TAG, \"Problem stopping SDLThread: \" + e);\n            }\n        }\n\n        SDLActivity.nativeQuit();\n\n        super.onDestroy();\n    }\n\n    @Override\n    public void onBackPressed() {\n        // Check if we want to block the back button in case of mouse right click.\n        //\n        // If we do, the normal hardware back button will no longer work and people have to use home,\n        // but the mouse right click will work.\n        //\n        boolean trapBack = SDLActivity.nativeGetHintBoolean(\"SDL_ANDROID_TRAP_BACK_BUTTON\", false);\n        if (trapBack) {\n            // Exit and let the mouse handler handle this button (if appropriate)\n            return;\n        }\n\n        // Default system back button behavior.\n        if (!isFinishing()) {\n            super.onBackPressed();\n        }\n    }\n\n    @Override\n    protected void onActivityResult(int requestCode, int resultCode, Intent data) {\n        super.onActivityResult(requestCode, resultCode, data);\n\n        if (mFileDialogState != null && mFileDialogState.requestCode == requestCode) {\n            /* This is our file dialog */\n            String[] filelist = null;\n            final int rawFlags = data != null ? data.getFlags() : 0;\n            final Uri dataUri = data != null ? data.getData() : null;\n            Log.i(TAG, \"File dialog result requestCode=\" + requestCode +\n                \" resultCode=\" + resultCode +\n                \" flags=0x\" + Integer.toHexString(rawFlags) +\n                \" dataUri=\" + dataUri);\n\n            if (data != null) {\n                final int grantedFlags = data.getFlags()\n                    & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);\n                final int requestedFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION\n                    | (mFileDialogState.forWrite ? Intent.FLAG_GRANT_WRITE_URI_PERMISSION : 0);\n                final int takeFlags = grantedFlags != 0 ? grantedFlags : requestedFlags;\n                Uri singleFileUri = data.getData();\n\n                if (singleFileUri == null) {\n                    /* Use Intent.getClipData to get multiple choices */\n                    ClipData clipData = data.getClipData();\n                    assert clipData != null;\n\n                    filelist = new String[clipData.getItemCount()];\n\n                    for (int i = 0; i < filelist.length; i++) {\n                        Uri itemUri = clipData.getItemAt(i).getUri();\n                        if (itemUri != null && takeFlags != 0) {\n                            try {\n                                Log.i(TAG, \"Persisting URI permission uri=\" + itemUri +\n                                    \" flags=0x\" + Integer.toHexString(takeFlags));\n                                getContentResolver().takePersistableUriPermission(itemUri, takeFlags);\n                            } catch (SecurityException e) {\n                                Log.w(TAG, \"Failed to persist URI permission for \" + itemUri + \": \" + e.getMessage());\n                            }\n                        }\n                        String uri = itemUri != null ? itemUri.toString() : \"\";\n                        filelist[i] = uri;\n                    }\n                } else {\n                    /* Only one file is selected. */\n                    if (takeFlags != 0) {\n                        try {\n                            Log.i(TAG, \"Persisting URI permission uri=\" + singleFileUri +\n                                \" flags=0x\" + Integer.toHexString(takeFlags));\n                            getContentResolver().takePersistableUriPermission(singleFileUri, takeFlags);\n                        } catch (SecurityException e) {\n                            Log.w(TAG, \"Failed to persist URI permission for \" + singleFileUri + \": \" + e.getMessage());\n                        }\n                    }\n                    filelist = new String[]{singleFileUri.toString()};\n                }\n            } else {\n                /* User cancelled the request. */\n                Log.i(TAG, \"File dialog returned null intent data (cancelled or provider failure)\");\n                filelist = new String[0];\n            }\n\n            if (filelist.length > 0) {\n                Log.i(TAG, \"File dialog selected first URI=\" + filelist[0] + \" count=\" + filelist.length);\n            } else {\n                Log.i(TAG, \"File dialog selected no files\");\n            }\n            for (UriPermission permission : getContentResolver().getPersistedUriPermissions()) {\n                Log.i(TAG, \"Persisted URI permission uri=\" + permission.getUri() +\n                    \" read=\" + permission.isReadPermission() +\n                    \" write=\" + permission.isWritePermission());\n            }\n\n            // TODO: Detect the file MIME type and pass the filter value accordingly.\n            SDLActivity.onNativeFileDialog(requestCode, filelist, -1);\n            mFileDialogState = null;\n        }\n    }\n\n    // Called by JNI from SDL.\n    public static void manualBackButton() {\n        mSingleton.pressBackButton();\n    }\n\n    // Used to get us onto the activity's main thread\n    public void pressBackButton() {\n        runOnUiThread(new Runnable() {\n            @Override\n            public void run() {\n                if (!SDLActivity.this.isFinishing()) {\n                    SDLActivity.this.superOnBackPressed();\n                }\n            }\n        });\n    }\n\n    // Used to access the system back behavior.\n    public void superOnBackPressed() {\n        super.onBackPressed();\n    }\n\n    @Override\n    public boolean dispatchKeyEvent(KeyEvent event) {\n\n        if (SDLActivity.mBrokenLibraries) {\n           return false;\n        }\n\n        int keyCode = event.getKeyCode();\n        // Ignore certain special keys so they're handled by Android\n        if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ||\n            keyCode == KeyEvent.KEYCODE_VOLUME_UP ||\n            keyCode == KeyEvent.KEYCODE_CAMERA ||\n            keyCode == KeyEvent.KEYCODE_ZOOM_IN || /* API 11 */\n            keyCode == KeyEvent.KEYCODE_ZOOM_OUT /* API 11 */\n            ) {\n            return false;\n        }\n        mDispatchingKeyEvent = true;\n        boolean result = super.dispatchKeyEvent(event);\n        mDispatchingKeyEvent = false;\n        return result;\n    }\n\n    public static boolean dispatchingKeyEvent() {\n        return mDispatchingKeyEvent;\n    }\n\n    /* Transition to next state */\n    public static void handleNativeState() {\n\n        if (mNextNativeState == mCurrentNativeState) {\n            // Already in same state, discard.\n            return;\n        }\n\n        // Try a transition to init state\n        if (mNextNativeState == NativeState.INIT) {\n\n            mCurrentNativeState = mNextNativeState;\n            return;\n        }\n\n        // Try a transition to paused state\n        if (mNextNativeState == NativeState.PAUSED) {\n            if (mSDLThread != null) {\n                nativePause();\n            }\n            if (mSurface != null) {\n                mSurface.handlePause();\n            }\n            mCurrentNativeState = mNextNativeState;\n            return;\n        }\n\n        // Try a transition to resumed state\n        if (mNextNativeState == NativeState.RESUMED) {\n            if (mSurface.mIsSurfaceReady && (mHasFocus || mHasMultiWindow) && mIsResumedCalled) {\n                if (mSDLThread == null) {\n                    // This is the entry point to the C app.\n                    // Start up the C app thread and enable sensor input for the first time\n                    // FIXME: Why aren't we enabling sensor input at start?\n\n                    mSDLThread = new Thread(new SDLMain(), \"SDLThread\");\n                    mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, true);\n                    mSDLThread.start();\n\n                    // No nativeResume(), don't signal Android_ResumeSem\n                } else {\n                    nativeResume();\n                }\n                mSurface.handleResume();\n\n                mCurrentNativeState = mNextNativeState;\n            }\n        }\n    }\n\n    // Messages from the SDLMain thread\n    protected static final int COMMAND_CHANGE_TITLE = 1;\n    protected static final int COMMAND_CHANGE_WINDOW_STYLE = 2;\n    protected static final int COMMAND_TEXTEDIT_HIDE = 3;\n    protected static final int COMMAND_SET_KEEP_SCREEN_ON = 5;\n    protected static final int COMMAND_USER = 0x8000;\n\n    protected static boolean mFullscreenModeActive;\n\n    /**\n     * This method is called by SDL if SDL did not handle a message itself.\n     * This happens if a received message contains an unsupported command.\n     * Method can be overwritten to handle Messages in a different class.\n     * @param command the command of the message.\n     * @param param the parameter of the message. May be null.\n     * @return if the message was handled in overridden method.\n     */\n    protected boolean onUnhandledMessage(int command, Object param) {\n        return false;\n    }\n\n    /**\n     * A Handler class for Messages from native SDL applications.\n     * It uses current Activities as target (e.g. for the title).\n     * static to prevent implicit references to enclosing object.\n     */\n    protected static class SDLCommandHandler extends Handler {\n        @Override\n        public void handleMessage(Message msg) {\n            Context context = getContext();\n            if (context == null) {\n                Log.e(TAG, \"error handling message, getContext() returned null\");\n                return;\n            }\n            switch (msg.arg1) {\n            case COMMAND_CHANGE_TITLE:\n                if (context instanceof Activity) {\n                    ((Activity) context).setTitle((String)msg.obj);\n                } else {\n                    Log.e(TAG, \"error handling message, getContext() returned no Activity\");\n                }\n                break;\n            case COMMAND_CHANGE_WINDOW_STYLE:\n                if (context instanceof Activity) {\n                    Window window = ((Activity) context).getWindow();\n                    if (window != null) {\n                        if ((msg.obj instanceof Integer) && ((Integer) msg.obj != 0)) {\n                            int flags = View.SYSTEM_UI_FLAG_FULLSCREEN |\n                                    View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |\n                                    View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY |\n                                    View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |\n                                    View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |\n                                    View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.INVISIBLE;\n                            window.getDecorView().setSystemUiVisibility(flags);\n                            window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);\n                            window.clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);\n                            SDLActivity.mFullscreenModeActive = true;\n                        } else {\n                            int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_VISIBLE;\n                            window.getDecorView().setSystemUiVisibility(flags);\n                            window.addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);\n                            window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);\n                            SDLActivity.mFullscreenModeActive = false;\n                        }\n                        if (Build.VERSION.SDK_INT >= 30 /* Android 11 (R) */) {\n                            window.getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;\n                        }\n                        if (Build.VERSION.SDK_INT >= 30 /* Android 11 (R) */ &&\n                            Build.VERSION.SDK_INT < 35 /* Android 15 */) {\n                            SDLActivity.onNativeInsetsChanged(0, 0, 0, 0);\n                        }\n                    }\n                } else {\n                    Log.e(TAG, \"error handling message, getContext() returned no Activity\");\n                }\n                break;\n            case COMMAND_TEXTEDIT_HIDE:\n                if (mTextEdit != null) {\n                    // Note: On some devices setting view to GONE creates a flicker in landscape.\n                    // Setting the View's sizes to 0 is similar to GONE but without the flicker.\n                    // The sizes will be set to useful values when the keyboard is shown again.\n                    mTextEdit.setLayoutParams(new RelativeLayout.LayoutParams(0, 0));\n\n                    InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);\n                    imm.hideSoftInputFromWindow(mTextEdit.getWindowToken(), 0);\n\n                    onNativeScreenKeyboardHidden();\n\n                    mSurface.requestFocus();\n                }\n                break;\n            case COMMAND_SET_KEEP_SCREEN_ON:\n            {\n                if (context instanceof Activity) {\n                    Window window = ((Activity) context).getWindow();\n                    if (window != null) {\n                        if ((msg.obj instanceof Integer) && ((Integer) msg.obj != 0)) {\n                            window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);\n                        } else {\n                            window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);\n                        }\n                    }\n                }\n                break;\n            }\n            default:\n                if ((context instanceof SDLActivity) && !((SDLActivity) context).onUnhandledMessage(msg.arg1, msg.obj)) {\n                    Log.e(TAG, \"error handling message, command is \" + msg.arg1);\n                }\n            }\n        }\n    }\n\n    // Handler for the messages\n    Handler commandHandler = new SDLCommandHandler();\n\n    // Send a message from the SDLMain thread\n    protected boolean sendCommand(int command, Object data) {\n        Message msg = commandHandler.obtainMessage();\n        msg.arg1 = command;\n        msg.obj = data;\n        boolean result = commandHandler.sendMessage(msg);\n\n        if (command == COMMAND_CHANGE_WINDOW_STYLE) {\n            // Ensure we don't return until the resize has actually happened,\n            // or 500ms have passed.\n\n            boolean bShouldWait = false;\n\n            if (data instanceof Integer) {\n                // Let's figure out if we're already laid out fullscreen or not.\n                Display display = ((WindowManager) getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();\n                DisplayMetrics realMetrics = new DisplayMetrics();\n                display.getRealMetrics(realMetrics);\n\n                boolean bFullscreenLayout = ((realMetrics.widthPixels == mSurface.getWidth()) &&\n                        (realMetrics.heightPixels == mSurface.getHeight()));\n\n                if ((Integer) data == 1) {\n                    // If we aren't laid out fullscreen or actively in fullscreen mode already, we're going\n                    // to change size and should wait for surfaceChanged() before we return, so the size\n                    // is right back in native code.  If we're already laid out fullscreen, though, we're\n                    // not going to change size even if we change decor modes, so we shouldn't wait for\n                    // surfaceChanged() -- which may not even happen -- and should return immediately.\n                    bShouldWait = !bFullscreenLayout;\n                } else {\n                    // If we're laid out fullscreen (even if the status bar and nav bar are present),\n                    // or are actively in fullscreen, we're going to change size and should wait for\n                    // surfaceChanged before we return, so the size is right back in native code.\n                    bShouldWait = bFullscreenLayout;\n                }\n            }\n\n            if (bShouldWait && (getContext() != null)) {\n                // We'll wait for the surfaceChanged() method, which will notify us\n                // when called.  That way, we know our current size is really the\n                // size we need, instead of grabbing a size that's still got\n                // the navigation and/or status bars before they're hidden.\n                //\n                // We'll wait for up to half a second, because some devices\n                // take a surprisingly long time for the surface resize, but\n                // then we'll just give up and return.\n                //\n                synchronized (getContext()) {\n                    try {\n                        getContext().wait(500);\n                    } catch (InterruptedException ie) {\n                        ie.printStackTrace();\n                    }\n                }\n            }\n        }\n\n        return result;\n    }\n\n    // C functions we call\n    public static native String nativeGetVersion();\n    public static native void nativeSetupJNI();\n    public static native void nativeInitMainThread();\n    public static native void nativeCleanupMainThread();\n    public static native int nativeRunMain(String library, String function, Object arguments);\n    public static native void nativeLowMemory();\n    public static native void nativeSendQuit();\n    public static native void nativeQuit();\n    public static native void nativePause();\n    public static native void nativeResume();\n    public static native void nativeFocusChanged(boolean hasFocus);\n    public static native void onNativeDropFile(String filename);\n    public static native void nativeSetScreenResolution(int surfaceWidth, int surfaceHeight, int deviceWidth, int deviceHeight, float density, float rate);\n    public static native void onNativeResize();\n    public static native void onNativeKeyDown(int keycode);\n    public static native void onNativeKeyUp(int keycode);\n    public static native boolean onNativeSoftReturnKey();\n    public static native void onNativeKeyboardFocusLost();\n    public static native void onNativeMouse(int button, int action, float x, float y, boolean relative);\n    public static native void onNativeTouch(int touchDevId, int pointerFingerId,\n                                            int action, float x,\n                                            float y, float p);\n    public static native void onNativePen(int penId, int device_type, int button, int action, float x, float y, float p);\n    public static native void onNativeAccel(float x, float y, float z);\n    public static native void onNativeClipboardChanged();\n    public static native void onNativeSurfaceCreated();\n    public static native void onNativeSurfaceChanged();\n    public static native void onNativeSurfaceDestroyed();\n    public static native void onNativeScreenKeyboardShown();\n    public static native void onNativeScreenKeyboardHidden();\n    public static native String nativeGetHint(String name);\n    public static native boolean nativeGetHintBoolean(String name, boolean default_value);\n    public static native void nativeSetenv(String name, String value);\n    public static native void nativeSetNaturalOrientation(int orientation);\n    public static native void onNativeRotationChanged(int rotation);\n    public static native void onNativeInsetsChanged(int left, int right, int top, int bottom);\n    public static native void nativeAddTouch(int touchId, String name);\n    public static native void nativePermissionResult(int requestCode, boolean result);\n    public static native void onNativeLocaleChanged();\n    public static native void onNativeDarkModeChanged(boolean enabled);\n    public static native boolean nativeAllowRecreateActivity();\n    public static native int nativeCheckSDLThreadCounter();\n    public static native void onNativeFileDialog(int requestCode, String[] filelist, int filter);\n    public static native void onNativePinchStart();\n    public static native void onNativePinchUpdate(float scale);\n    public static native void onNativePinchEnd();\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean setActivityTitle(String title) {\n        // Called from SDLMain() thread and can't directly affect the view\n        return mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void setWindowStyle(boolean fullscreen) {\n        // Called from SDLMain() thread and can't directly affect the view\n        mSingleton.sendCommand(COMMAND_CHANGE_WINDOW_STYLE, fullscreen ? 1 : 0);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     * This is a static method for JNI convenience, it calls a non-static method\n     * so that is can be overridden\n     */\n    public static void setOrientation(int w, int h, boolean resizable, String hint)\n    {\n        if (mSingleton != null) {\n            mSingleton.setOrientationBis(w, h, resizable, hint);\n        }\n    }\n\n    /**\n     * This can be overridden\n     */\n    public void setOrientationBis(int w, int h, boolean resizable, String hint)\n    {\n        int orientation_landscape = -1;\n        int orientation_portrait = -1;\n\n        if (w <= 1 || h <= 1) {\n            // Invalid width/height, ignore this request\n            return;\n        }\n\n        /* If set, hint \"explicitly controls which UI orientations are allowed\". */\n        if (hint.contains(\"LandscapeRight\") && hint.contains(\"LandscapeLeft\")) {\n            orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE;\n        } else if (hint.contains(\"LandscapeLeft\")) {\n            orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;\n        } else if (hint.contains(\"LandscapeRight\")) {\n            orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;\n        }\n\n        /* exact match to 'Portrait' to distinguish with PortraitUpsideDown */\n        boolean contains_Portrait = hint.contains(\"Portrait \") || hint.endsWith(\"Portrait\");\n\n        if (contains_Portrait && hint.contains(\"PortraitUpsideDown\")) {\n            orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT;\n        } else if (contains_Portrait) {\n            orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;\n        } else if (hint.contains(\"PortraitUpsideDown\")) {\n            orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;\n        }\n\n        boolean is_landscape_allowed = (orientation_landscape != -1);\n        boolean is_portrait_allowed = (orientation_portrait != -1);\n        int req; /* Requested orientation */\n\n        /* No valid hint, nothing is explicitly allowed */\n        if (!is_portrait_allowed && !is_landscape_allowed) {\n            if (resizable) {\n                /* All orientations are allowed, respecting user orientation lock setting */\n                req = ActivityInfo.SCREEN_ORIENTATION_FULL_USER;\n            } else {\n                /* Fixed window and nothing specified. Get orientation from w/h of created window */\n                req = (w > h ? ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE : ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);\n            }\n        } else {\n            /* At least one orientation is allowed */\n            if (resizable) {\n                if (is_portrait_allowed && is_landscape_allowed) {\n                    /* hint allows both landscape and portrait, promote to full user */\n                    req = ActivityInfo.SCREEN_ORIENTATION_FULL_USER;\n                } else {\n                    /* Use the only one allowed \"orientation\" */\n                    req = (is_landscape_allowed ? orientation_landscape : orientation_portrait);\n                }\n            } else {\n                /* Fixed window and both orientations are allowed. Choose one. */\n                if (is_portrait_allowed && is_landscape_allowed) {\n                    req = (w > h ? orientation_landscape : orientation_portrait);\n                } else {\n                    /* Use the only one allowed \"orientation\" */\n                    req = (is_landscape_allowed ? orientation_landscape : orientation_portrait);\n                }\n            }\n        }\n\n        Log.v(TAG, \"setOrientation() requestedOrientation=\" + req + \" width=\" + w +\" height=\"+ h +\" resizable=\" + resizable + \" hint=\" + hint);\n        mSingleton.setRequestedOrientation(req);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void minimizeWindow() {\n\n        if (mSingleton == null) {\n            return;\n        }\n\n        Intent startMain = new Intent(Intent.ACTION_MAIN);\n        startMain.addCategory(Intent.CATEGORY_HOME);\n        startMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n        mSingleton.startActivity(startMain);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean shouldMinimizeOnFocusLoss() {\n        return false;\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean supportsRelativeMouse()\n    {\n        // DeX mode in Samsung Experience 9.0 and earlier doesn't support relative mice properly under\n        // Android 7 APIs, and simply returns no data under Android 8 APIs.\n        //\n        // This is fixed in Samsung Experience 9.5, which corresponds to Android 8.1.0, and\n        // thus SDK version 27.  If we are in DeX mode and not API 27 or higher, as a result,\n        // we should stick to relative mode.\n        //\n        if (Build.VERSION.SDK_INT < 27 /* Android 8.1 (O_MR1) */ && isDeXMode()) {\n            return false;\n        }\n\n        return SDLActivity.getMotionListener().supportsRelativeMouse();\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean setRelativeMouseEnabled(boolean enabled)\n    {\n        if (enabled && !supportsRelativeMouse()) {\n            return false;\n        }\n\n        return SDLActivity.getMotionListener().setRelativeMouseEnabled(enabled);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean sendMessage(int command, int param) {\n        if (mSingleton == null) {\n            return false;\n        }\n        return mSingleton.sendCommand(command, param);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static Activity getContext() {\n        return SDL.getContext();\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean isAndroidTV() {\n        UiModeManager uiModeManager = (UiModeManager) getContext().getSystemService(UI_MODE_SERVICE);\n        if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) {\n            return true;\n        }\n        if (Build.MANUFACTURER.equals(\"MINIX\") && Build.MODEL.equals(\"NEO-U1\")) {\n            return true;\n        }\n        if (Build.MANUFACTURER.equals(\"Amlogic\") && Build.MODEL.equals(\"X96-W\")) {\n            return true;\n        }\n        if (Build.MANUFACTURER.equals(\"Amlogic\") && Build.MODEL.startsWith(\"TV\")) {\n            return true;\n        }\n        return false;\n    }\n\n    public static boolean isVRHeadset() {\n        if (Build.MANUFACTURER.equals(\"Oculus\") && Build.MODEL.startsWith(\"Quest\")) {\n            return true;\n        }\n        if (Build.MANUFACTURER.equals(\"Pico\")) {\n            return true;\n        }\n        return false;\n    }\n\n    public static double getDiagonal()\n    {\n        DisplayMetrics metrics = new DisplayMetrics();\n        Activity activity = (Activity)getContext();\n        if (activity == null) {\n            return 0.0;\n        }\n        activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);\n\n        double dWidthInches = metrics.widthPixels / (double)metrics.xdpi;\n        double dHeightInches = metrics.heightPixels / (double)metrics.ydpi;\n\n        return Math.sqrt((dWidthInches * dWidthInches) + (dHeightInches * dHeightInches));\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean isTablet() {\n        // If our diagonal size is seven inches or greater, we consider ourselves a tablet.\n        return (getDiagonal() >= 7.0);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean isChromebook() {\n        // https://stackoverflow.com/questions/39784415/how-to-detect-programmatically-if-android-app-is-running-in-chrome-book-or-in\n        if (getContext() != null) {\n            if (getContext().getPackageManager().hasSystemFeature(\"org.chromium.arc\")\n                || getContext().getPackageManager().hasSystemFeature(\"org.chromium.arc.device_management\")) {\n                return true;\n            }\n        }\n\n        // Running on AVD emulator\n        boolean isChromebookEmulator = (Build.MODEL != null && Build.MODEL.startsWith(\"sdk_gpc_\"));\n        return isChromebookEmulator;\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean isDeXMode() {\n        if (Build.VERSION.SDK_INT < 24 /* Android 7.0 (N) */) {\n            return false;\n        }\n        try {\n            final Configuration config = getContext().getResources().getConfiguration();\n            final Class<?> configClass = config.getClass();\n            return configClass.getField(\"SEM_DESKTOP_MODE_ENABLED\").getInt(configClass)\n                    == configClass.getField(\"semDesktopModeEnabled\").getInt(config);\n        } catch(Exception ignored) {\n            return false;\n        }\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean getManifestEnvironmentVariables() {\n        try {\n            if (getContext() == null) {\n                return false;\n            }\n\n            ApplicationInfo applicationInfo = getContext().getPackageManager().getApplicationInfo(getContext().getPackageName(), PackageManager.GET_META_DATA);\n            Bundle bundle = applicationInfo.metaData;\n            if (bundle == null) {\n                return false;\n            }\n            String prefix = \"SDL_ENV.\";\n            final int trimLength = prefix.length();\n            for (String key : bundle.keySet()) {\n                if (key.startsWith(prefix)) {\n                    String name = key.substring(trimLength);\n                    String value = bundle.get(key).toString();\n                    nativeSetenv(name, value);\n                }\n            }\n            /* environment variables set! */\n            return true;\n        } catch (Exception e) {\n           Log.v(TAG, \"exception \" + e.toString());\n        }\n        return false;\n    }\n\n    // This method is called by SDLControllerManager's API 26 Generic Motion Handler.\n    public static View getContentView() {\n        return mLayout;\n    }\n\n    static class ShowTextInputTask implements Runnable {\n        /*\n         * This is used to regulate the pan&scan method to have some offset from\n         * the bottom edge of the input region and the top edge of an input\n         * method (soft keyboard)\n         */\n        static final int HEIGHT_PADDING = 15;\n\n        public int input_type;\n        public int x, y, w, h;\n\n        public ShowTextInputTask(int input_type, int x, int y, int w, int h) {\n            this.input_type = input_type;\n            this.x = x;\n            this.y = y;\n            this.w = w;\n            this.h = h;\n\n            /* Minimum size of 1 pixel, so it takes focus. */\n            if (this.w <= 0) {\n                this.w = 1;\n            }\n            if (this.h + HEIGHT_PADDING <= 0) {\n                this.h = 1 - HEIGHT_PADDING;\n            }\n        }\n\n        @Override\n        public void run() {\n            RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(w, h + HEIGHT_PADDING);\n            params.leftMargin = x;\n            params.topMargin = y;\n\n            if (mTextEdit == null) {\n                mTextEdit = new SDLDummyEdit(getContext());\n\n                mLayout.addView(mTextEdit, params);\n            } else {\n                mTextEdit.setLayoutParams(params);\n            }\n            mTextEdit.setInputType(input_type);\n\n            mTextEdit.setVisibility(View.VISIBLE);\n            mTextEdit.requestFocus();\n\n            InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);\n            imm.showSoftInput(mTextEdit, 0);\n\n            if (imm.isAcceptingText()) {\n                onNativeScreenKeyboardShown();\n            }\n        }\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean showTextInput(int input_type, int x, int y, int w, int h) {\n        // Transfer the task to the main thread as a Runnable\n        return mSingleton.commandHandler.post(new ShowTextInputTask(input_type, x, y, w, h));\n    }\n\n    public static boolean isTextInputEvent(KeyEvent event) {\n\n        // Key pressed with Ctrl should be sent as SDL_KEYDOWN/SDL_KEYUP and not SDL_TEXTINPUT\n        if (event.isCtrlPressed()) {\n            return false;\n        }\n\n        return event.isPrintingKey() || event.getKeyCode() == KeyEvent.KEYCODE_SPACE;\n    }\n\n    public static boolean handleKeyEvent(View v, int keyCode, KeyEvent event, InputConnection ic) {\n        int deviceId = event.getDeviceId();\n        int source = event.getSource();\n\n        if (source == InputDevice.SOURCE_UNKNOWN) {\n            InputDevice device = InputDevice.getDevice(deviceId);\n            if (device != null) {\n                source = device.getSources();\n            }\n        }\n\n//        if (event.getAction() == KeyEvent.ACTION_DOWN) {\n//            Log.v(\"SDL\", \"key down: \" + keyCode + \", deviceId = \" + deviceId + \", source = \" + source);\n//        } else if (event.getAction() == KeyEvent.ACTION_UP) {\n//            Log.v(\"SDL\", \"key up: \" + keyCode + \", deviceId = \" + deviceId + \", source = \" + source);\n//        }\n\n        // Dispatch the different events depending on where they come from\n        // Some SOURCE_JOYSTICK, SOURCE_DPAD or SOURCE_GAMEPAD are also SOURCE_KEYBOARD\n        // So, we try to process them as JOYSTICK/DPAD/GAMEPAD events first, if that fails we try them as KEYBOARD\n        //\n        // Furthermore, it's possible a game controller has SOURCE_KEYBOARD and\n        // SOURCE_JOYSTICK, while its key events arrive from the keyboard source\n        // So, retrieve the device itself and check all of its sources\n        if (SDLControllerManager.isDeviceSDLJoystick(deviceId)) {\n            // Note that we process events with specific key codes here\n            if (event.getAction() == KeyEvent.ACTION_DOWN) {\n                if (SDLControllerManager.onNativePadDown(deviceId, keyCode)) {\n                    return true;\n                }\n            } else if (event.getAction() == KeyEvent.ACTION_UP) {\n                if (SDLControllerManager.onNativePadUp(deviceId, keyCode)) {\n                    return true;\n                }\n            }\n        }\n\n        if ((source & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) {\n            if (SDLActivity.isVRHeadset()) {\n                // The Oculus Quest controller back button comes in as source mouse, so accept that\n            } else {\n                // on some devices key events are sent for mouse BUTTON_BACK/FORWARD presses\n                // they are ignored here because sending them as mouse input to SDL is messy\n                if ((keyCode == KeyEvent.KEYCODE_BACK) || (keyCode == KeyEvent.KEYCODE_FORWARD)) {\n                    switch (event.getAction()) {\n                    case KeyEvent.ACTION_DOWN:\n                    case KeyEvent.ACTION_UP:\n                        // mark the event as handled or it will be handled by system\n                        // handling KEYCODE_BACK by system will call onBackPressed()\n                        return true;\n                    }\n                }\n            }\n        }\n\n        if (event.getAction() == KeyEvent.ACTION_DOWN) {\n            onNativeKeyDown(keyCode);\n\n            if (isTextInputEvent(event)) {\n                if (ic != null) {\n                    ic.commitText(String.valueOf((char) event.getUnicodeChar()), 1);\n                } else {\n                    SDLInputConnection.nativeCommitText(String.valueOf((char) event.getUnicodeChar()), 1);\n                }\n            }\n            return true;\n        } else if (event.getAction() == KeyEvent.ACTION_UP) {\n            onNativeKeyUp(keyCode);\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static Surface getNativeSurface() {\n        if (SDLActivity.mSurface == null) {\n            return null;\n        }\n        return SDLActivity.mSurface.getNativeSurface();\n    }\n\n    // Input\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void initTouch() {\n        int[] ids = InputDevice.getDeviceIds();\n\n        for (int id : ids) {\n            InputDevice device = InputDevice.getDevice(id);\n            /* Allow SOURCE_TOUCHSCREEN and also Virtual InputDevices because they can send TOUCHSCREEN events */\n            if (device != null && ((device.getSources() & InputDevice.SOURCE_TOUCHSCREEN) == InputDevice.SOURCE_TOUCHSCREEN\n                    || device.isVirtual())) {\n\n                nativeAddTouch(device.getId(), device.getName());\n            }\n        }\n    }\n\n    // Messagebox\n\n    /** Result of current messagebox. Also used for blocking the calling thread. */\n    protected final int[] messageboxSelection = new int[1];\n\n    /**\n     * This method is called by SDL using JNI.\n     * Shows the messagebox from UI thread and block calling thread.\n     * buttonFlags, buttonIds and buttonTexts must have same length.\n     * @param buttonFlags array containing flags for every button.\n     * @param buttonIds array containing id for every button.\n     * @param buttonTexts array containing text for every button.\n     * @param colors null for default or array of length 5 containing colors.\n     * @return button id or -1.\n     */\n    public int messageboxShowMessageBox(\n            final int flags,\n            final String title,\n            final String message,\n            final int[] buttonFlags,\n            final int[] buttonIds,\n            final String[] buttonTexts,\n            final int[] colors) {\n\n        messageboxSelection[0] = -1;\n\n        // sanity checks\n\n        if ((buttonFlags.length != buttonIds.length) && (buttonIds.length != buttonTexts.length)) {\n            return -1; // implementation broken\n        }\n\n        // collect arguments for Dialog\n\n        final Bundle args = new Bundle();\n        args.putInt(\"flags\", flags);\n        args.putString(\"title\", title);\n        args.putString(\"message\", message);\n        args.putIntArray(\"buttonFlags\", buttonFlags);\n        args.putIntArray(\"buttonIds\", buttonIds);\n        args.putStringArray(\"buttonTexts\", buttonTexts);\n        args.putIntArray(\"colors\", colors);\n\n        // trigger Dialog creation on UI thread\n\n        runOnUiThread(new Runnable() {\n            @Override\n            public void run() {\n                messageboxCreateAndShow(args);\n            }\n        });\n\n        // block the calling thread\n\n        synchronized (messageboxSelection) {\n            try {\n                messageboxSelection.wait();\n            } catch (InterruptedException ex) {\n                ex.printStackTrace();\n                return -1;\n            }\n        }\n\n        // return selected value\n\n        return messageboxSelection[0];\n    }\n\n    protected void messageboxCreateAndShow(Bundle args) {\n\n        // TODO set values from \"flags\" to messagebox dialog\n\n        // get colors\n\n        int[] colors = args.getIntArray(\"colors\");\n        int backgroundColor;\n        int textColor;\n        int buttonBorderColor;\n        int buttonBackgroundColor;\n        int buttonSelectedColor;\n        if (colors != null) {\n            int i = -1;\n            backgroundColor = colors[++i];\n            textColor = colors[++i];\n            buttonBorderColor = colors[++i];\n            buttonBackgroundColor = colors[++i];\n            buttonSelectedColor = colors[++i];\n        } else {\n            backgroundColor = Color.TRANSPARENT;\n            textColor = Color.TRANSPARENT;\n            buttonBorderColor = Color.TRANSPARENT;\n            buttonBackgroundColor = Color.TRANSPARENT;\n            buttonSelectedColor = Color.TRANSPARENT;\n        }\n\n        // create dialog with title and a listener to wake up calling thread\n\n        final AlertDialog dialog = new AlertDialog.Builder(this).create();\n        dialog.setTitle(args.getString(\"title\"));\n        dialog.setCancelable(false);\n        dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {\n            @Override\n            public void onDismiss(DialogInterface unused) {\n                synchronized (messageboxSelection) {\n                    messageboxSelection.notify();\n                }\n            }\n        });\n\n        // create text\n\n        TextView message = new TextView(this);\n        message.setGravity(Gravity.CENTER);\n        message.setText(args.getString(\"message\"));\n        if (textColor != Color.TRANSPARENT) {\n            message.setTextColor(textColor);\n        }\n\n        // create buttons\n\n        int[] buttonFlags = args.getIntArray(\"buttonFlags\");\n        int[] buttonIds = args.getIntArray(\"buttonIds\");\n        String[] buttonTexts = args.getStringArray(\"buttonTexts\");\n\n        final SparseArray<Button> mapping = new SparseArray<Button>();\n\n        LinearLayout buttons = new LinearLayout(this);\n        buttons.setOrientation(LinearLayout.HORIZONTAL);\n        buttons.setGravity(Gravity.CENTER);\n        for (int i = 0; i < buttonTexts.length; ++i) {\n            Button button = new Button(this);\n            final int id = buttonIds[i];\n            button.setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View v) {\n                    messageboxSelection[0] = id;\n                    dialog.dismiss();\n                }\n            });\n            if (buttonFlags[i] != 0) {\n                // see SDL_messagebox.h\n                if ((buttonFlags[i] & 0x00000001) != 0) {\n                    mapping.put(KeyEvent.KEYCODE_ENTER, button);\n                }\n                if ((buttonFlags[i] & 0x00000002) != 0) {\n                    mapping.put(KeyEvent.KEYCODE_ESCAPE, button); /* API 11 */\n                }\n            }\n            button.setText(buttonTexts[i]);\n            if (textColor != Color.TRANSPARENT) {\n                button.setTextColor(textColor);\n            }\n            if (buttonBorderColor != Color.TRANSPARENT) {\n                // TODO set color for border of messagebox button\n            }\n            if (buttonBackgroundColor != Color.TRANSPARENT) {\n                Drawable drawable = button.getBackground();\n                if (drawable == null) {\n                    // setting the color this way removes the style\n                    button.setBackgroundColor(buttonBackgroundColor);\n                } else {\n                    // setting the color this way keeps the style (gradient, padding, etc.)\n                    drawable.setColorFilter(buttonBackgroundColor, PorterDuff.Mode.MULTIPLY);\n                }\n            }\n            if (buttonSelectedColor != Color.TRANSPARENT) {\n                // TODO set color for selected messagebox button\n            }\n            buttons.addView(button);\n        }\n\n        // create content\n\n        LinearLayout content = new LinearLayout(this);\n        content.setOrientation(LinearLayout.VERTICAL);\n        content.addView(message);\n        content.addView(buttons);\n        if (backgroundColor != Color.TRANSPARENT) {\n            content.setBackgroundColor(backgroundColor);\n        }\n\n        // add content to dialog and return\n\n        dialog.setView(content);\n        dialog.setOnKeyListener(new Dialog.OnKeyListener() {\n            @Override\n            public boolean onKey(DialogInterface d, int keyCode, KeyEvent event) {\n                Button button = mapping.get(keyCode);\n                if (button != null) {\n                    if (event.getAction() == KeyEvent.ACTION_UP) {\n                        button.performClick();\n                    }\n                    return true; // also for ignored actions\n                }\n                return false;\n            }\n        });\n\n        dialog.show();\n    }\n\n    private final Runnable rehideSystemUi = new Runnable() {\n        @Override\n        public void run() {\n            int flags = View.SYSTEM_UI_FLAG_FULLSCREEN |\n                    View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |\n                    View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY |\n                    View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |\n                    View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |\n                    View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.INVISIBLE;\n\n            SDLActivity.this.getWindow().getDecorView().setSystemUiVisibility(flags);\n        }\n    };\n\n    public void onSystemUiVisibilityChange(int visibility) {\n        if (SDLActivity.mFullscreenModeActive && ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0 || (visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0)) {\n\n            Handler handler = getWindow().getDecorView().getHandler();\n            if (handler != null) {\n                handler.removeCallbacks(rehideSystemUi); // Prevent a hide loop.\n                handler.postDelayed(rehideSystemUi, 2000);\n            }\n\n        }\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean clipboardHasText() {\n        return mClipboardHandler.clipboardHasText();\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static String clipboardGetText() {\n        return mClipboardHandler.clipboardGetText();\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void clipboardSetText(String string) {\n        mClipboardHandler.clipboardSetText(string);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static int createCustomCursor(int[] colors, int width, int height, int hotSpotX, int hotSpotY) {\n        Bitmap bitmap = Bitmap.createBitmap(colors, width, height, Bitmap.Config.ARGB_8888);\n        ++mLastCursorID;\n\n        if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n            try {\n                mCursors.put(mLastCursorID, PointerIcon.create(bitmap, hotSpotX, hotSpotY));\n            } catch (Exception e) {\n                return 0;\n            }\n        } else {\n            return 0;\n        }\n        return mLastCursorID;\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void destroyCustomCursor(int cursorID) {\n        if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n            try {\n                mCursors.remove(cursorID);\n            } catch (Exception e) {\n            }\n        }\n        return;\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean setCustomCursor(int cursorID) {\n\n        if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n            try {\n                mSurface.setPointerIcon(mCursors.get(cursorID));\n            } catch (Exception e) {\n                return false;\n            }\n        } else {\n            return false;\n        }\n        return true;\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean setSystemCursor(int cursorID) {\n        int cursor_type = 0; //PointerIcon.TYPE_NULL;\n        switch (cursorID) {\n        case SDL_SYSTEM_CURSOR_ARROW:\n            cursor_type = 1000; //PointerIcon.TYPE_ARROW;\n            break;\n        case SDL_SYSTEM_CURSOR_IBEAM:\n            cursor_type = 1008; //PointerIcon.TYPE_TEXT;\n            break;\n        case SDL_SYSTEM_CURSOR_WAIT:\n            cursor_type = 1004; //PointerIcon.TYPE_WAIT;\n            break;\n        case SDL_SYSTEM_CURSOR_CROSSHAIR:\n            cursor_type = 1007; //PointerIcon.TYPE_CROSSHAIR;\n            break;\n        case SDL_SYSTEM_CURSOR_WAITARROW:\n            cursor_type = 1004; //PointerIcon.TYPE_WAIT;\n            break;\n        case SDL_SYSTEM_CURSOR_SIZENWSE:\n            cursor_type = 1017; //PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW;\n            break;\n        case SDL_SYSTEM_CURSOR_SIZENESW:\n            cursor_type = 1016; //PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW;\n            break;\n        case SDL_SYSTEM_CURSOR_SIZEWE:\n            cursor_type = 1014; //PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;\n            break;\n        case SDL_SYSTEM_CURSOR_SIZENS:\n            cursor_type = 1015; //PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;\n            break;\n        case SDL_SYSTEM_CURSOR_SIZEALL:\n            cursor_type = 1020; //PointerIcon.TYPE_GRAB;\n            break;\n        case SDL_SYSTEM_CURSOR_NO:\n            cursor_type = 1012; //PointerIcon.TYPE_NO_DROP;\n            break;\n        case SDL_SYSTEM_CURSOR_HAND:\n            cursor_type = 1002; //PointerIcon.TYPE_HAND;\n            break;\n        case SDL_SYSTEM_CURSOR_WINDOW_TOPLEFT:\n            cursor_type = 1017; //PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW;\n            break;\n        case SDL_SYSTEM_CURSOR_WINDOW_TOP:\n            cursor_type = 1015; //PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;\n            break;\n        case SDL_SYSTEM_CURSOR_WINDOW_TOPRIGHT:\n            cursor_type = 1016; //PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW;\n            break;\n        case SDL_SYSTEM_CURSOR_WINDOW_RIGHT:\n            cursor_type = 1014; //PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;\n            break;\n        case SDL_SYSTEM_CURSOR_WINDOW_BOTTOMRIGHT:\n            cursor_type = 1017; //PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW;\n            break;\n        case SDL_SYSTEM_CURSOR_WINDOW_BOTTOM:\n            cursor_type = 1015; //PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;\n            break;\n        case SDL_SYSTEM_CURSOR_WINDOW_BOTTOMLEFT:\n            cursor_type = 1016; //PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW;\n            break;\n        case SDL_SYSTEM_CURSOR_WINDOW_LEFT:\n            cursor_type = 1014; //PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;\n            break;\n        }\n        if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n            try {\n                mSurface.setPointerIcon(PointerIcon.getSystemIcon(getContext(), cursor_type));\n            } catch (Exception e) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void requestPermission(String permission, int requestCode) {\n        if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {\n            nativePermissionResult(requestCode, true);\n            return;\n        }\n\n        Activity activity = (Activity)getContext();\n        if (activity.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {\n            activity.requestPermissions(new String[]{permission}, requestCode);\n        } else {\n            nativePermissionResult(requestCode, true);\n        }\n    }\n\n    @Override\n    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {\n        boolean result = (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED);\n        nativePermissionResult(requestCode, result);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean openURL(String url)\n    {\n        try {\n            Intent i = new Intent(Intent.ACTION_VIEW);\n            i.setData(Uri.parse(url));\n\n            int flags = Intent.FLAG_ACTIVITY_NO_HISTORY \n                      | Intent.FLAG_ACTIVITY_MULTIPLE_TASK\n                      | Intent.FLAG_ACTIVITY_NEW_DOCUMENT;\n            i.addFlags(flags);\n\n            mSingleton.startActivity(i);\n        } catch (Exception ex) {\n            return false;\n        }\n        return true;\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean showToast(String message, int duration, int gravity, int xOffset, int yOffset)\n    {\n        if(null == mSingleton) {\n            return false;\n        }\n\n        try\n        {\n            class OneShotTask implements Runnable {\n                private final String mMessage;\n                private final int mDuration;\n                private final int mGravity;\n                private final int mXOffset;\n                private final int mYOffset;\n\n                OneShotTask(String message, int duration, int gravity, int xOffset, int yOffset) {\n                    mMessage  = message;\n                    mDuration = duration;\n                    mGravity  = gravity;\n                    mXOffset  = xOffset;\n                    mYOffset  = yOffset;\n                }\n\n                public void run() {\n                    try\n                    {\n                        Toast toast = Toast.makeText(mSingleton, mMessage, mDuration);\n                        if (mGravity >= 0) {\n                            toast.setGravity(mGravity, mXOffset, mYOffset);\n                        }\n                        toast.show();\n                    } catch(Exception ex) {\n                        Log.e(TAG, ex.getMessage());\n                    }\n                }\n            }\n            mSingleton.runOnUiThread(new OneShotTask(message, duration, gravity, xOffset, yOffset));\n        } catch(Exception ex) {\n            return false;\n        }\n        return true;\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static int openFileDescriptor(String uri, String mode) {\n        if (mSingleton == null) {\n            return -1;\n        }\n\n        try {\n            Uri parsedUri = Uri.parse(uri);\n            ParcelFileDescriptor pfd = mSingleton.getContentResolver().openFileDescriptor(parsedUri, mode);\n            if (pfd == null) {\n                Log.w(TAG, \"openFileDescriptor returned null for uri=\" + uri + \" mode=\" + mode);\n                return -1;\n            }\n            long statSize = pfd.getStatSize();\n            int fd = pfd.detachFd();\n            Log.i(TAG, \"openFileDescriptor success uri=\" + uri + \" mode=\" + mode + \" fd=\" + fd + \" statSize=\" + statSize);\n            return fd;\n        } catch (FileNotFoundException | SecurityException | IllegalArgumentException e) {\n            Log.w(TAG, \"openFileDescriptor failed for uri=\" + uri + \" mode=\" + mode + \": \" + e.getMessage());\n            return -1;\n        } catch (Exception e) {\n            Log.e(TAG, \"openFileDescriptor unexpected failure\", e);\n            return -1;\n        }\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean showFileDialog(String[] filters, boolean allowMultiple, boolean forWrite, int requestCode) {\n        if (mSingleton == null) {\n            return false;\n        }\n\n        if (forWrite) {\n            allowMultiple = false;\n        }\n\n        /* Convert string list of extensions to their respective MIME types */\n        ArrayList<String> mimes = new ArrayList<>();\n        MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();\n        if (filters != null) {\n            for (String pattern : filters) {\n                String[] extensions = pattern.split(\";\");\n\n                if (extensions.length == 1 && extensions[0].equals(\"*\")) {\n                    /* Handle \"*\" special case */\n                    mimes.add(\"*/*\");\n                } else {\n                    for (String ext : extensions) {\n                        String mime = mimeTypeMap.getMimeTypeFromExtension(ext);\n                        if (mime != null) {\n                            mimes.add(mime);\n                        }\n                    }\n                }\n            }\n        }\n\n        /* Display the file dialog */\n        Intent intent = new Intent(forWrite ? Intent.ACTION_CREATE_DOCUMENT : Intent.ACTION_OPEN_DOCUMENT);\n        intent.addCategory(Intent.CATEGORY_OPENABLE);\n        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);\n        if (forWrite) {\n            intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);\n        }\n        intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);\n        intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, allowMultiple);\n        switch (mimes.size()) {\n            case 0:\n                intent.setType(\"*/*\");\n                break;\n            case 1:\n                intent.setType(mimes.get(0));\n                break;\n            default:\n                intent.setType(\"*/*\");\n                intent.putExtra(Intent.EXTRA_MIME_TYPES, mimes.toArray(new String[]{}));\n        }\n\n        try {\n            mSingleton.startActivityForResult(intent, requestCode);\n        } catch (ActivityNotFoundException e) {\n            Log.e(TAG, \"Unable to open file dialog.\", e);\n            return false;\n        }\n\n        /* Save current dialog state */\n        mFileDialogState = new SDLFileDialogState();\n        mFileDialogState.requestCode = requestCode;\n        mFileDialogState.multipleChoice = allowMultiple;\n        mFileDialogState.forWrite = forWrite;\n        return true;\n    }\n\n    /* Internal class used to track active open file dialog */\n    static class SDLFileDialogState {\n        int requestCode;\n        boolean multipleChoice;\n        boolean forWrite;\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static String getPreferredLocales() {\n        String result = \"\";\n        if (Build.VERSION.SDK_INT >= 24 /* Android 7 (N) */) {\n            LocaleList locales = LocaleList.getAdjustedDefault();\n            for (int i = 0; i < locales.size(); i++) {\n                if (i != 0) result += \",\";\n                result += formatLocale(locales.get(i));\n            }\n        } else if (mCurrentLocale != null) {\n            result = formatLocale(mCurrentLocale);\n        }\n        return result;\n    }\n\n    public static String formatLocale(Locale locale) {\n        String result = \"\";\n        String lang = \"\";\n        if (locale.getLanguage() == \"in\") {\n            // Indonesian is \"id\" according to ISO 639.2, but on Android is \"in\" because of Java backwards compatibility\n            lang = \"id\";\n        } else if (locale.getLanguage() == \"\") {\n            // Make sure language is never empty\n            lang = \"und\";\n        } else {\n            lang = locale.getLanguage();\n        }\n\n        if (locale.getCountry() == \"\") {\n            result = lang;\n        } else {\n            result = lang + \"_\" + locale.getCountry();\n        }\n        return result;\n    }\n}\n\n/**\n    Simple runnable to start the SDL application\n*/\nclass SDLMain implements Runnable {\n    @Override\n    public void run() {\n        // Runs SDLActivity.main()\n\n        try {\n            android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_DISPLAY);\n        } catch (Exception e) {\n            Log.v(\"SDL\", \"modify thread properties failed \" + e.toString());\n        }\n\n        SDLActivity.nativeInitMainThread();\n        SDLActivity.mSingleton.main();\n        SDLActivity.nativeCleanupMainThread();\n\n        if (SDLActivity.mSingleton != null && !SDLActivity.mSingleton.isFinishing()) {\n            // Let's finish the Activity\n            SDLActivity.mSDLThread = null;\n            SDLActivity.mSDLMainFinished = true;\n            SDLActivity.mSingleton.finish();\n        }  // else: Activity is already being destroyed\n\n    }\n}\n\nclass SDLClipboardHandler implements\n    ClipboardManager.OnPrimaryClipChangedListener {\n\n    protected ClipboardManager mClipMgr;\n\n    SDLClipboardHandler() {\n       mClipMgr = (ClipboardManager) SDL.getContext().getSystemService(Context.CLIPBOARD_SERVICE);\n       mClipMgr.addPrimaryClipChangedListener(this);\n    }\n\n    public boolean clipboardHasText() {\n        if (Build.VERSION.SDK_INT >= 28 /* Android 9 (P) */) {\n            return mClipMgr.hasPrimaryClip();\n        } else {\n            return mClipMgr.hasText();\n        }\n    }\n\n    public String clipboardGetText() {\n        ClipData clip = mClipMgr.getPrimaryClip();\n        if (clip != null) {\n            ClipData.Item item = clip.getItemAt(0);\n            if (item != null) {\n                CharSequence text = item.getText();\n                if (text != null) {\n                    return text.toString();\n                }\n            }\n        }\n        return null;\n    }\n\n    public void clipboardSetText(String string) {\n        mClipMgr.removePrimaryClipChangedListener(this);\n        if (string.isEmpty()) {\n            if (Build.VERSION.SDK_INT >= 28 /* Android 9 (P) */) {\n                mClipMgr.clearPrimaryClip();\n            } else {\n                ClipData clip = ClipData.newPlainText(null, \"\");\n                mClipMgr.setPrimaryClip(clip);\n            }\n        } else {\n            ClipData clip = ClipData.newPlainText(null, string);\n            mClipMgr.setPrimaryClip(clip);\n        }\n        mClipMgr.addPrimaryClipChangedListener(this);\n    }\n\n    @Override\n    public void onPrimaryClipChanged() {\n        SDLActivity.onNativeClipboardChanged();\n    }\n}\n"
  },
  {
    "path": "android/app/src/main/java/org/libsdl/app/SDLAudioManager.java",
    "content": "package org.libsdl.app;\n\nimport android.content.Context;\nimport android.media.AudioDeviceCallback;\nimport android.media.AudioDeviceInfo;\nimport android.media.AudioManager;\nimport android.os.Build;\nimport android.util.Log;\n\nimport java.util.Arrays;\nimport java.util.ArrayList;\n\nclass SDLAudioManager {\n    protected static final String TAG = \"SDLAudio\";\n\n    protected static Context mContext;\n\n    private static AudioDeviceCallback mAudioDeviceCallback;\n\n    static void initialize() {\n        mAudioDeviceCallback = null;\n\n        if(Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */)\n        {\n            mAudioDeviceCallback = new AudioDeviceCallback() {\n                @Override\n                public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {\n                    for (AudioDeviceInfo deviceInfo : addedDevices) {\n                        nativeAddAudioDevice(deviceInfo.isSink(), deviceInfo.getProductName().toString(), deviceInfo.getId());\n                    }\n                }\n\n                @Override\n                public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {\n                    for (AudioDeviceInfo deviceInfo : removedDevices) {\n                        nativeRemoveAudioDevice(deviceInfo.isSink(), deviceInfo.getId());\n                    }\n                }\n            };\n        }\n    }\n\n    static void setContext(Context context) {\n        mContext = context;\n    }\n\n    static void release(Context context) {\n        // no-op atm\n    }\n\n    // Audio\n\n    private static AudioDeviceInfo getInputAudioDeviceInfo(int deviceId) {\n        if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n            AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);\n            for (AudioDeviceInfo deviceInfo : audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)) {\n                if (deviceInfo.getId() == deviceId) {\n                    return deviceInfo;\n                }\n            }\n        }\n        return null;\n    }\n\n    private static AudioDeviceInfo getPlaybackAudioDeviceInfo(int deviceId) {\n        if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n            AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);\n            for (AudioDeviceInfo deviceInfo : audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)) {\n                if (deviceInfo.getId() == deviceId) {\n                    return deviceInfo;\n                }\n            }\n        }\n        return null;\n    }\n\n    static void registerAudioDeviceCallback() {\n        if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n            AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);\n            // get an initial list now, before hotplug callbacks fire.\n            for (AudioDeviceInfo dev : audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)) {\n                if (dev.getType() == AudioDeviceInfo.TYPE_TELEPHONY) {\n                    continue;  // Device cannot be opened\n                }\n                nativeAddAudioDevice(dev.isSink(), dev.getProductName().toString(), dev.getId());\n            }\n            for (AudioDeviceInfo dev : audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)) {\n                nativeAddAudioDevice(dev.isSink(), dev.getProductName().toString(), dev.getId());\n            }\n            audioManager.registerAudioDeviceCallback(mAudioDeviceCallback, null);\n        }\n    }\n\n    static void unregisterAudioDeviceCallback() {\n        if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n            AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);\n            audioManager.unregisterAudioDeviceCallback(mAudioDeviceCallback);\n        }\n    }\n\n    /** This method is called by SDL using JNI. */\n    static void audioSetThreadPriority(boolean recording, int device_id) {\n        try {\n\n            /* Set thread name */\n            if (recording) {\n                Thread.currentThread().setName(\"SDLAudioC\" + device_id);\n            } else {\n                Thread.currentThread().setName(\"SDLAudioP\" + device_id);\n            }\n\n            /* Set thread priority */\n            android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO);\n\n        } catch (Exception e) {\n            Log.v(TAG, \"modify thread properties failed \" + e.toString());\n        }\n    }\n\n    static native void nativeSetupJNI();\n\n    static native void nativeRemoveAudioDevice(boolean recording, int deviceId);\n\n    static native void nativeAddAudioDevice(boolean recording, String name, int deviceId);\n\n}\n"
  },
  {
    "path": "android/app/src/main/java/org/libsdl/app/SDLControllerManager.java",
    "content": "package org.libsdl.app;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.List;\n\nimport android.content.Context;\nimport android.hardware.lights.Light;\nimport android.hardware.lights.LightsRequest;\nimport android.hardware.lights.LightsManager;\nimport android.hardware.lights.LightState;\nimport android.graphics.Color;\nimport android.os.Build;\nimport android.os.VibrationEffect;\nimport android.os.Vibrator;\nimport android.os.VibratorManager;\nimport android.util.Log;\nimport android.view.InputDevice;\nimport android.view.KeyEvent;\nimport android.view.MotionEvent;\nimport android.view.View;\n\n\npublic class SDLControllerManager\n{\n\n    static native void nativeSetupJNI();\n\n    static native void nativeAddJoystick(int device_id, String name, String desc,\n                                                int vendor_id, int product_id,\n                                                int button_mask,\n                                                int naxes, int axis_mask, int nhats, boolean can_rumble, boolean has_rgb_led);\n    static native void nativeRemoveJoystick(int device_id);\n    static native void nativeAddHaptic(int device_id, String name);\n    static native void nativeRemoveHaptic(int device_id);\n    static public native boolean onNativePadDown(int device_id, int keycode);\n    static public native boolean onNativePadUp(int device_id, int keycode);\n    static native void onNativeJoy(int device_id, int axis,\n                                          float value);\n    static native void onNativeHat(int device_id, int hat_id,\n                                          int x, int y);\n\n    protected static SDLJoystickHandler mJoystickHandler;\n    protected static SDLHapticHandler mHapticHandler;\n\n    private static final String TAG = \"SDLControllerManager\";\n\n    static void initialize() {\n        if (mJoystickHandler == null) {\n            mJoystickHandler = new SDLJoystickHandler();\n        }\n\n        if (mHapticHandler == null) {\n            if (Build.VERSION.SDK_INT >= 31 /* Android 12.0 (S) */) {\n                mHapticHandler = new SDLHapticHandler_API31();\n            } else if (Build.VERSION.SDK_INT >= 26 /* Android 8.0 (O) */) {\n                mHapticHandler = new SDLHapticHandler_API26();\n            } else {\n                mHapticHandler = new SDLHapticHandler();\n            }\n        }\n    }\n\n    // Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance\n    static public boolean handleJoystickMotionEvent(MotionEvent event) {\n        return mJoystickHandler.handleMotionEvent(event);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    static void pollInputDevices() {\n        mJoystickHandler.pollInputDevices();\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    static void joystickSetLED(int device_id, int red, int green, int blue) {\n        mJoystickHandler.setLED(device_id, red, green, blue);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    static void pollHapticDevices() {\n        mHapticHandler.pollHapticDevices();\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    static void hapticRun(int device_id, float intensity, int length) {\n        mHapticHandler.run(device_id, intensity, length);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    static void hapticRumble(int device_id, float low_frequency_intensity, float high_frequency_intensity, int length) {\n        mHapticHandler.rumble(device_id, low_frequency_intensity, high_frequency_intensity, length);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    static void hapticStop(int device_id)\n    {\n        mHapticHandler.stop(device_id);\n    }\n\n    // Check if a given device is considered a possible SDL joystick\n    static public boolean isDeviceSDLJoystick(int deviceId) {\n        InputDevice device = InputDevice.getDevice(deviceId);\n        // We cannot use InputDevice.isVirtual before API 16, so let's accept\n        // only nonnegative device ids (VIRTUAL_KEYBOARD equals -1)\n        if ((device == null) || (deviceId < 0)) {\n            return false;\n        }\n        int sources = device.getSources();\n\n        /* This is called for every button press, so let's not spam the logs */\n        /*\n        if ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {\n            Log.v(TAG, \"Input device \" + device.getName() + \" has class joystick.\");\n        }\n        if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) {\n            Log.v(TAG, \"Input device \" + device.getName() + \" is a dpad.\");\n        }\n        if ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) {\n            Log.v(TAG, \"Input device \" + device.getName() + \" is a gamepad.\");\n        }\n        */\n\n        return ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0 ||\n                ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) ||\n                ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD)\n        );\n    }\n\n}\n\n\n/* Actual joystick functionality available for API >= 19 devices */\nclass SDLJoystickHandler {\n\n    static class SDLJoystick {\n        int device_id;\n        String name;\n        String desc;\n        ArrayList<InputDevice.MotionRange> axes;\n        ArrayList<InputDevice.MotionRange> hats;\n        ArrayList<Light> lights;\n        LightsManager.LightsSession lightsSession;\n    }\n    static class RangeComparator implements Comparator<InputDevice.MotionRange> {\n        @Override\n        public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) {\n            // Some controllers, like the Moga Pro 2, return AXIS_GAS (22) for right trigger and AXIS_BRAKE (23) for left trigger - swap them so they're sorted in the right order for SDL\n            int arg0Axis = arg0.getAxis();\n            int arg1Axis = arg1.getAxis();\n            if (arg0Axis == MotionEvent.AXIS_GAS) {\n                arg0Axis = MotionEvent.AXIS_BRAKE;\n            } else if (arg0Axis == MotionEvent.AXIS_BRAKE) {\n                arg0Axis = MotionEvent.AXIS_GAS;\n            }\n            if (arg1Axis == MotionEvent.AXIS_GAS) {\n                arg1Axis = MotionEvent.AXIS_BRAKE;\n            } else if (arg1Axis == MotionEvent.AXIS_BRAKE) {\n                arg1Axis = MotionEvent.AXIS_GAS;\n            }\n\n            // Make sure the AXIS_Z is sorted between AXIS_RY and AXIS_RZ.\n            // This is because the usual pairing are:\n            // - AXIS_X + AXIS_Y (left stick).\n            // - AXIS_RX, AXIS_RY (sometimes the right stick, sometimes triggers).\n            // - AXIS_Z, AXIS_RZ (sometimes the right stick, sometimes triggers).\n            // This sorts the axes in the above order, which tends to be correct\n            // for Xbox-ish game pads that have the right stick on RX/RY and the\n            // triggers on Z/RZ.\n            //\n            // Gamepads that don't have AXIS_Z/AXIS_RZ but use\n            // AXIS_LTRIGGER/AXIS_RTRIGGER are unaffected by this.\n            //\n            // References:\n            // - https://developer.android.com/develop/ui/views/touch-and-input/game-controllers/controller-input\n            // - https://www.kernel.org/doc/html/latest/input/gamepad.html\n            if (arg0Axis == MotionEvent.AXIS_Z) {\n                arg0Axis = MotionEvent.AXIS_RZ - 1;\n            } else if (arg0Axis > MotionEvent.AXIS_Z && arg0Axis < MotionEvent.AXIS_RZ) {\n                --arg0Axis;\n            }\n            if (arg1Axis == MotionEvent.AXIS_Z) {\n                arg1Axis = MotionEvent.AXIS_RZ - 1;\n            } else if (arg1Axis > MotionEvent.AXIS_Z && arg1Axis < MotionEvent.AXIS_RZ) {\n                --arg1Axis;\n            }\n\n            return arg0Axis - arg1Axis;\n        }\n    }\n\n    private final ArrayList<SDLJoystick> mJoysticks;\n\n    SDLJoystickHandler() {\n\n        mJoysticks = new ArrayList<SDLJoystick>();\n    }\n\n    /**\n     * Handles adding and removing of input devices.\n     */\n    void pollInputDevices() {\n        int[] deviceIds = InputDevice.getDeviceIds();\n\n        for (int device_id : deviceIds) {\n            if (SDLControllerManager.isDeviceSDLJoystick(device_id)) {\n                SDLJoystick joystick = getJoystick(device_id);\n                if (joystick == null) {\n                    InputDevice joystickDevice = InputDevice.getDevice(device_id);\n                    joystick = new SDLJoystick();\n                    joystick.device_id = device_id;\n                    joystick.name = joystickDevice.getName();\n                    joystick.desc = getJoystickDescriptor(joystickDevice);\n                    joystick.axes = new ArrayList<InputDevice.MotionRange>();\n                    joystick.hats = new ArrayList<InputDevice.MotionRange>();\n                    joystick.lights = new ArrayList<Light>();\n\n                    List<InputDevice.MotionRange> ranges = joystickDevice.getMotionRanges();\n                    Collections.sort(ranges, new RangeComparator());\n                    for (InputDevice.MotionRange range : ranges) {\n                        if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {\n                            if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) {\n                                joystick.hats.add(range);\n                            } else {\n                                joystick.axes.add(range);\n                            }\n                        }\n                    }\n\n                    boolean can_rumble = false;\n                    boolean has_rgb_led = false;\n                    if (Build.VERSION.SDK_INT >= 31 /* Android 12.0 (S) */) {\n                        VibratorManager vibratorManager = joystickDevice.getVibratorManager();\n                        int[] vibrators = vibratorManager.getVibratorIds();\n                        if (vibrators.length > 0) {\n                            can_rumble = true;\n                        }\n                        LightsManager lightsManager = joystickDevice.getLightsManager();\n                        List<Light> lights = lightsManager.getLights();\n                        for (Light light : lights) {\n                            if (light.hasRgbControl()) {\n                                joystick.lights.add(light);\n                            }\n                        }\n                        if (!joystick.lights.isEmpty()) {\n                            joystick.lightsSession = lightsManager.openSession();\n                            has_rgb_led = true;\n                        }\n                    }\n\n                    mJoysticks.add(joystick);\n                    SDLControllerManager.nativeAddJoystick(joystick.device_id, joystick.name, joystick.desc,\n                            getVendorId(joystickDevice), getProductId(joystickDevice),\n                            getButtonMask(joystickDevice), joystick.axes.size(), getAxisMask(joystick.axes), joystick.hats.size()/2, can_rumble, has_rgb_led);\n                }\n            }\n        }\n\n        /* Check removed devices */\n        ArrayList<Integer> removedDevices = null;\n        for (SDLJoystick joystick : mJoysticks) {\n            int device_id = joystick.device_id;\n            int i;\n            for (i = 0; i < deviceIds.length; i++) {\n                if (device_id == deviceIds[i]) break;\n            }\n            if (i == deviceIds.length) {\n                if (removedDevices == null) {\n                    removedDevices = new ArrayList<Integer>();\n                }\n                removedDevices.add(device_id);\n            }\n        }\n\n        if (removedDevices != null) {\n            for (int device_id : removedDevices) {\n                SDLControllerManager.nativeRemoveJoystick(device_id);\n                for (int i = 0; i < mJoysticks.size(); i++) {\n                    if (mJoysticks.get(i).device_id == device_id) {\n                        if (Build.VERSION.SDK_INT >= 31 /* Android 12.0 (S) */) {\n                            if (mJoysticks.get(i).lightsSession != null) {\n                                try {\n                                    mJoysticks.get(i).lightsSession.close();\n                                } catch (Exception e) {\n                                    // Session may already be unregistered when device disconnects\n                                }\n                                mJoysticks.get(i).lightsSession = null;\n                            }\n                        }\n                        mJoysticks.remove(i);\n                        break;\n                    }\n                }\n            }\n        }\n    }\n\n    protected SDLJoystick getJoystick(int device_id) {\n        for (SDLJoystick joystick : mJoysticks) {\n            if (joystick.device_id == device_id) {\n                return joystick;\n            }\n        }\n        return null;\n    }\n\n    /**\n     * Handles given MotionEvent.\n     * @param event the event to be handled.\n     * @return if given event was processed.\n     */\n    boolean handleMotionEvent(MotionEvent event) {\n        int actionPointerIndex = event.getActionIndex();\n        int action = event.getActionMasked();\n        if (action == MotionEvent.ACTION_MOVE) {\n            SDLJoystick joystick = getJoystick(event.getDeviceId());\n            if (joystick != null) {\n                for (int i = 0; i < joystick.axes.size(); i++) {\n                    InputDevice.MotionRange range = joystick.axes.get(i);\n                    /* Normalize the value to -1...1 */\n                    float value = (event.getAxisValue(range.getAxis(), actionPointerIndex) - range.getMin()) / range.getRange() * 2.0f - 1.0f;\n                    SDLControllerManager.onNativeJoy(joystick.device_id, i, value);\n                }\n                for (int i = 0; i < joystick.hats.size() / 2; i++) {\n                    int hatX = Math.round(event.getAxisValue(joystick.hats.get(2 * i).getAxis(), actionPointerIndex));\n                    int hatY = Math.round(event.getAxisValue(joystick.hats.get(2 * i + 1).getAxis(), actionPointerIndex));\n                    SDLControllerManager.onNativeHat(joystick.device_id, i, hatX, hatY);\n                }\n            }\n        }\n        return true;\n    }\n\n    String getJoystickDescriptor(InputDevice joystickDevice) {\n        String desc = joystickDevice.getDescriptor();\n\n        if (desc != null && !desc.isEmpty()) {\n            return desc;\n        }\n\n        return joystickDevice.getName();\n    }\n\n    int getProductId(InputDevice joystickDevice) {\n        return joystickDevice.getProductId();\n    }\n\n    int getVendorId(InputDevice joystickDevice) {\n        return joystickDevice.getVendorId();\n    }\n\n    int getAxisMask(List<InputDevice.MotionRange> ranges) {\n        // For compatibility, keep computing the axis mask like before,\n        // only really distinguishing 2, 4 and 6 axes.\n        int axis_mask = 0;\n        if (ranges.size() >= 2) {\n            // ((1 << SDL_GAMEPAD_AXIS_LEFTX) | (1 << SDL_GAMEPAD_AXIS_LEFTY))\n            axis_mask |= 0x0003;\n        }\n        if (ranges.size() >= 4) {\n            // ((1 << SDL_GAMEPAD_AXIS_RIGHTX) | (1 << SDL_GAMEPAD_AXIS_RIGHTY))\n            axis_mask |= 0x000c;\n        }\n        if (ranges.size() >= 6) {\n            // ((1 << SDL_GAMEPAD_AXIS_LEFT_TRIGGER) | (1 << SDL_GAMEPAD_AXIS_RIGHT_TRIGGER))\n            axis_mask |= 0x0030;\n        }\n        // Also add an indicator bit for whether the sorting order has changed.\n        // This serves to disable outdated gamecontrollerdb.txt mappings.\n        boolean have_z = false;\n        boolean have_past_z_before_rz = false;\n        for (InputDevice.MotionRange range : ranges) {\n            int axis = range.getAxis();\n            if (axis == MotionEvent.AXIS_Z) {\n                have_z = true;\n            } else if (axis > MotionEvent.AXIS_Z && axis < MotionEvent.AXIS_RZ) {\n                have_past_z_before_rz = true;\n            }\n        }\n        if (have_z && have_past_z_before_rz) {\n            // If both these exist, the compare() function changed sorting order.\n            // Set a bit to indicate this fact.\n            axis_mask |= 0x8000;\n        }\n        return axis_mask;\n    }\n\n    int getButtonMask(InputDevice joystickDevice) {\n        int button_mask = 0;\n        int[] keys = new int[] {\n            KeyEvent.KEYCODE_BUTTON_A,\n            KeyEvent.KEYCODE_BUTTON_B,\n            KeyEvent.KEYCODE_BUTTON_X,\n            KeyEvent.KEYCODE_BUTTON_Y,\n            KeyEvent.KEYCODE_BACK,\n            KeyEvent.KEYCODE_MENU,\n            KeyEvent.KEYCODE_BUTTON_MODE,\n            KeyEvent.KEYCODE_BUTTON_START,\n            KeyEvent.KEYCODE_BUTTON_THUMBL,\n            KeyEvent.KEYCODE_BUTTON_THUMBR,\n            KeyEvent.KEYCODE_BUTTON_L1,\n            KeyEvent.KEYCODE_BUTTON_R1,\n            KeyEvent.KEYCODE_DPAD_UP,\n            KeyEvent.KEYCODE_DPAD_DOWN,\n            KeyEvent.KEYCODE_DPAD_LEFT,\n            KeyEvent.KEYCODE_DPAD_RIGHT,\n            KeyEvent.KEYCODE_BUTTON_SELECT,\n            KeyEvent.KEYCODE_DPAD_CENTER,\n\n            // These don't map into any SDL controller buttons directly\n            KeyEvent.KEYCODE_BUTTON_L2,\n            KeyEvent.KEYCODE_BUTTON_R2,\n            KeyEvent.KEYCODE_BUTTON_C,\n            KeyEvent.KEYCODE_BUTTON_Z,\n            KeyEvent.KEYCODE_BUTTON_1,\n            KeyEvent.KEYCODE_BUTTON_2,\n            KeyEvent.KEYCODE_BUTTON_3,\n            KeyEvent.KEYCODE_BUTTON_4,\n            KeyEvent.KEYCODE_BUTTON_5,\n            KeyEvent.KEYCODE_BUTTON_6,\n            KeyEvent.KEYCODE_BUTTON_7,\n            KeyEvent.KEYCODE_BUTTON_8,\n            KeyEvent.KEYCODE_BUTTON_9,\n            KeyEvent.KEYCODE_BUTTON_10,\n            KeyEvent.KEYCODE_BUTTON_11,\n            KeyEvent.KEYCODE_BUTTON_12,\n            KeyEvent.KEYCODE_BUTTON_13,\n            KeyEvent.KEYCODE_BUTTON_14,\n            KeyEvent.KEYCODE_BUTTON_15,\n            KeyEvent.KEYCODE_BUTTON_16,\n        };\n        int[] masks = new int[] {\n            (1 << 0),   // A -> A\n            (1 << 1),   // B -> B\n            (1 << 2),   // X -> X\n            (1 << 3),   // Y -> Y\n            (1 << 4),   // BACK -> BACK\n            (1 << 6),   // MENU -> START\n            (1 << 5),   // MODE -> GUIDE\n            (1 << 6),   // START -> START\n            (1 << 7),   // THUMBL -> LEFTSTICK\n            (1 << 8),   // THUMBR -> RIGHTSTICK\n            (1 << 9),   // L1 -> LEFTSHOULDER\n            (1 << 10),  // R1 -> RIGHTSHOULDER\n            (1 << 11),  // DPAD_UP -> DPAD_UP\n            (1 << 12),  // DPAD_DOWN -> DPAD_DOWN\n            (1 << 13),  // DPAD_LEFT -> DPAD_LEFT\n            (1 << 14),  // DPAD_RIGHT -> DPAD_RIGHT\n            (1 << 4),   // SELECT -> BACK\n            (1 << 0),   // DPAD_CENTER -> A\n            (1 << 15),  // L2 -> ??\n            (1 << 16),  // R2 -> ??\n            (1 << 17),  // C -> ??\n            (1 << 18),  // Z -> ??\n            (1 << 20),  // 1 -> ??\n            (1 << 21),  // 2 -> ??\n            (1 << 22),  // 3 -> ??\n            (1 << 23),  // 4 -> ??\n            (1 << 24),  // 5 -> ??\n            (1 << 25),  // 6 -> ??\n            (1 << 26),  // 7 -> ??\n            (1 << 27),  // 8 -> ??\n            (1 << 28),  // 9 -> ??\n            (1 << 29),  // 10 -> ??\n            (1 << 30),  // 11 -> ??\n            (1 << 31),  // 12 -> ??\n            // We're out of room...\n            0xFFFFFFFF,  // 13 -> ??\n            0xFFFFFFFF,  // 14 -> ??\n            0xFFFFFFFF,  // 15 -> ??\n            0xFFFFFFFF,  // 16 -> ??\n        };\n        boolean[] has_keys = joystickDevice.hasKeys(keys);\n        for (int i = 0; i < keys.length; ++i) {\n            if (has_keys[i]) {\n                button_mask |= masks[i];\n            }\n        }\n        return button_mask;\n    }\n\n    void setLED(int device_id, int red, int green, int blue) {\n        if (Build.VERSION.SDK_INT < 31 /* Android 12.0 (S) */) {\n            return;\n        }\n        SDLJoystick joystick = getJoystick(device_id);\n        if (joystick == null || joystick.lights.isEmpty()) {\n            return;\n        }\n        LightsRequest.Builder lightsRequest = new LightsRequest.Builder();\n        LightState lightState = new LightState.Builder().setColor(Color.rgb(red, green, blue)).build();\n        for (Light light : joystick.lights) {\n            if (light.hasRgbControl()) {\n                lightsRequest.addLight(light, lightState);\n            }\n        }\n        joystick.lightsSession.requestLights(lightsRequest.build());\n    }\n}\n\nclass SDLHapticHandler_API31 extends SDLHapticHandler {\n    @Override\n    void run(int device_id, float intensity, int length) {\n        SDLHaptic haptic = getHaptic(device_id);\n        if (haptic != null) {\n            vibrate(haptic.vib, intensity, length);\n        }\n    }\n\n    @Override\n    void rumble(int device_id, float low_frequency_intensity, float high_frequency_intensity, int length) {\n        InputDevice device = InputDevice.getDevice(device_id);\n        if (device == null) {\n            return;\n        }\n\n        if (Build.VERSION.SDK_INT < 31 /* Android 12.0 (S) */) {\n            /* Silence 'lint' warning */\n            return;\n        }\n\n        VibratorManager manager = device.getVibratorManager();\n        int[] vibrators = manager.getVibratorIds();\n        if (vibrators.length >= 2) {\n            vibrate(manager.getVibrator(vibrators[0]), low_frequency_intensity, length);\n            vibrate(manager.getVibrator(vibrators[1]), high_frequency_intensity, length);\n        } else if (vibrators.length == 1) {\n            float intensity = (low_frequency_intensity * 0.6f) + (high_frequency_intensity * 0.4f);\n            vibrate(manager.getVibrator(vibrators[0]), intensity, length);\n        }\n    }\n\n    private void vibrate(Vibrator vibrator, float intensity, int length) {\n\n        if (Build.VERSION.SDK_INT < 31 /* Android 12.0 (S) */) {\n            /* Silence 'lint' warning */\n            return;\n        }\n\n        if (intensity == 0.0f) {\n            vibrator.cancel();\n            return;\n        }\n\n        int value = Math.round(intensity * 255);\n        if (value > 255) {\n            value = 255;\n        }\n        if (value < 1) {\n            vibrator.cancel();\n            return;\n        }\n        try {\n            vibrator.vibrate(VibrationEffect.createOneShot(length, value));\n        }\n        catch (Exception e) {\n            // Fall back to the generic method, which uses DEFAULT_AMPLITUDE, but works even if\n            // something went horribly wrong with the Android 8.0 APIs.\n            vibrator.vibrate(length);\n        }\n    }\n}\n\nclass SDLHapticHandler_API26 extends SDLHapticHandler {\n    @Override\n    void run(int device_id, float intensity, int length) {\n\n        if (Build.VERSION.SDK_INT < 26 /* Android 8.0 (O) */) {\n            /* Silence 'lint' warning */\n            return;\n        }\n\n        SDLHaptic haptic = getHaptic(device_id);\n        if (haptic != null) {\n            if (intensity == 0.0f) {\n                stop(device_id);\n                return;\n            }\n\n            int vibeValue = Math.round(intensity * 255);\n\n            if (vibeValue > 255) {\n                vibeValue = 255;\n            }\n            if (vibeValue < 1) {\n                stop(device_id);\n                return;\n            }\n            try {\n                haptic.vib.vibrate(VibrationEffect.createOneShot(length, vibeValue));\n            }\n            catch (Exception e) {\n                // Fall back to the generic method, which uses DEFAULT_AMPLITUDE, but works even if\n                // something went horribly wrong with the Android 8.0 APIs.\n                haptic.vib.vibrate(length);\n            }\n        }\n    }\n}\n\nclass SDLHapticHandler {\n\n    static class SDLHaptic {\n        int device_id;\n        String name;\n        Vibrator vib;\n    }\n\n    private final ArrayList<SDLHaptic> mHaptics;\n\n    SDLHapticHandler() {\n        mHaptics = new ArrayList<SDLHaptic>();\n    }\n\n    void run(int device_id, float intensity, int length) {\n        SDLHaptic haptic = getHaptic(device_id);\n        if (haptic != null) {\n            haptic.vib.vibrate(length);\n        }\n    }\n\n    void rumble(int device_id, float low_frequency_intensity, float high_frequency_intensity, int length) {\n        // Not supported in older APIs\n    }\n\n    void stop(int device_id) {\n        SDLHaptic haptic = getHaptic(device_id);\n        if (haptic != null) {\n            haptic.vib.cancel();\n        }\n    }\n\n    void pollHapticDevices() {\n\n        final int deviceId_VIBRATOR_SERVICE = 999999;\n        boolean hasVibratorService = false;\n\n        /* Check VIBRATOR_SERVICE */\n        Vibrator vib = (Vibrator) SDL.getContext().getSystemService(Context.VIBRATOR_SERVICE);\n        if (vib != null) {\n            hasVibratorService = vib.hasVibrator();\n\n            if (hasVibratorService) {\n                SDLHaptic haptic = getHaptic(deviceId_VIBRATOR_SERVICE);\n                if (haptic == null) {\n                    haptic = new SDLHaptic();\n                    haptic.device_id = deviceId_VIBRATOR_SERVICE;\n                    haptic.name = \"VIBRATOR_SERVICE\";\n                    haptic.vib = vib;\n                    mHaptics.add(haptic);\n                    SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);\n                }\n            }\n        }\n\n        /* Check removed devices */\n        ArrayList<Integer> removedDevices = null;\n        for (SDLHaptic haptic : mHaptics) {\n            int device_id = haptic.device_id;\n            if (device_id != deviceId_VIBRATOR_SERVICE || !hasVibratorService) {\n                if (removedDevices == null) {\n                    removedDevices = new ArrayList<Integer>();\n                }\n                removedDevices.add(device_id);\n            }  // else: don't remove the vibrator if it is still present\n        }\n\n        if (removedDevices != null) {\n            for (int device_id : removedDevices) {\n                SDLControllerManager.nativeRemoveHaptic(device_id);\n                for (int i = 0; i < mHaptics.size(); i++) {\n                    if (mHaptics.get(i).device_id == device_id) {\n                        mHaptics.remove(i);\n                        break;\n                    }\n                }\n            }\n        }\n    }\n\n    protected SDLHaptic getHaptic(int device_id) {\n        for (SDLHaptic haptic : mHaptics) {\n            if (haptic.device_id == device_id) {\n                return haptic;\n            }\n        }\n        return null;\n    }\n}\n\nclass SDLGenericMotionListener_API14 implements View.OnGenericMotionListener {\n    protected static final int SDL_PEN_DEVICE_TYPE_UNKNOWN = 0;\n    protected static final int SDL_PEN_DEVICE_TYPE_DIRECT = 1;\n    protected static final int SDL_PEN_DEVICE_TYPE_INDIRECT = 2;\n\n    // Generic Motion (mouse hover, joystick...) events go here\n    @Override\n    public boolean onGenericMotion(View v, MotionEvent event) {\n        if (event.getSource() == InputDevice.SOURCE_JOYSTICK)\n            return SDLControllerManager.handleJoystickMotionEvent(event);\n\n        float x, y;\n        int action = event.getActionMasked();\n        int pointerCount = event.getPointerCount();\n        boolean consumed = false;\n\n        for (int i = 0; i < pointerCount; i++) {\n            int toolType = event.getToolType(i);\n\n            if (toolType == MotionEvent.TOOL_TYPE_MOUSE) {\n                switch (action) {\n                    case MotionEvent.ACTION_SCROLL:\n                        x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, i);\n                        y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, i);\n                        SDLActivity.onNativeMouse(0, action, x, y, false);\n                        consumed = true;\n                        break;\n\n                    case MotionEvent.ACTION_HOVER_MOVE:\n                        x = getEventX(event, i);\n                        y = getEventY(event, i);\n\n                        SDLActivity.onNativeMouse(0, action, x, y, checkRelativeEvent(event));\n                        consumed = true;\n                        break;\n\n                    default:\n                        break;\n                }\n            } else if (toolType == MotionEvent.TOOL_TYPE_STYLUS || toolType == MotionEvent.TOOL_TYPE_ERASER) {\n                switch (action) {\n                    case MotionEvent.ACTION_HOVER_ENTER:\n                    case MotionEvent.ACTION_HOVER_MOVE:\n                    case MotionEvent.ACTION_HOVER_EXIT:\n                        x = event.getX(i);\n                        y = event.getY(i);\n                        float p = event.getPressure(i);\n                        if (p > 1.0f) {\n                            // may be larger than 1.0f on some devices\n                            // see the documentation of getPressure(i)\n                            p = 1.0f;\n                        }\n\n                        // BUTTON_STYLUS_PRIMARY is 2^5, so shift by 4, and apply SDL_PEN_INPUT_DOWN/SDL_PEN_INPUT_ERASER_TIP\n                        int buttons = (event.getButtonState() >> 4) | (1 << (toolType == MotionEvent.TOOL_TYPE_STYLUS ? 0 : 30));\n\n                        SDLActivity.onNativePen(event.getPointerId(i), getPenDeviceType(event.getDevice()), buttons, action, x, y, p);\n                        consumed = true;\n                        break;\n                }\n            }\n        }\n\n        return consumed;\n    }\n\n    boolean supportsRelativeMouse() {\n        return false;\n    }\n\n    boolean inRelativeMode() {\n        return false;\n    }\n\n    boolean setRelativeMouseEnabled(boolean enabled) {\n        return false;\n    }\n\n    void reclaimRelativeMouseModeIfNeeded() {\n\n    }\n\n    boolean checkRelativeEvent(MotionEvent event) {\n        return inRelativeMode();\n    }\n\n    float getEventX(MotionEvent event, int pointerIndex) {\n        return event.getX(pointerIndex);\n    }\n\n    float getEventY(MotionEvent event, int pointerIndex) {\n        return event.getY(pointerIndex);\n    }\n\n    int getPenDeviceType(InputDevice penDevice) {\n        return SDL_PEN_DEVICE_TYPE_UNKNOWN;\n    }\n}\n\nclass SDLGenericMotionListener_API24 extends SDLGenericMotionListener_API14 {\n    // Generic Motion (mouse hover, joystick...) events go here\n\n    private boolean mRelativeModeEnabled;\n\n    @Override\n    boolean supportsRelativeMouse() {\n        return true;\n    }\n\n    @Override\n    boolean inRelativeMode() {\n        return mRelativeModeEnabled;\n    }\n\n    @Override\n    boolean setRelativeMouseEnabled(boolean enabled) {\n        mRelativeModeEnabled = enabled;\n        return true;\n    }\n\n    @Override\n    float getEventX(MotionEvent event, int pointerIndex) {\n        if (Build.VERSION.SDK_INT < 24 /* Android 7.0 (N) */) {\n            /* Silence 'lint' warning */\n            return 0;\n        }\n\n        if (mRelativeModeEnabled && event.getToolType(pointerIndex) == MotionEvent.TOOL_TYPE_MOUSE) {\n            return event.getAxisValue(MotionEvent.AXIS_RELATIVE_X, pointerIndex);\n        } else {\n            return event.getX(pointerIndex);\n        }\n    }\n\n    @Override\n    float getEventY(MotionEvent event, int pointerIndex) {\n        if (Build.VERSION.SDK_INT < 24 /* Android 7.0 (N) */) {\n            /* Silence 'lint' warning */\n            return 0;\n        }\n\n        if (mRelativeModeEnabled && event.getToolType(pointerIndex) == MotionEvent.TOOL_TYPE_MOUSE) {\n            return event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y, pointerIndex);\n        } else {\n            return event.getY(pointerIndex);\n        }\n    }\n}\n\nclass SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 {\n    // Generic Motion (mouse hover, joystick...) events go here\n    private boolean mRelativeModeEnabled;\n\n    @Override\n    boolean supportsRelativeMouse() {\n        return (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */);\n    }\n\n    @Override\n    boolean inRelativeMode() {\n        return mRelativeModeEnabled;\n    }\n\n    @Override\n    boolean setRelativeMouseEnabled(boolean enabled) {\n\n        if (Build.VERSION.SDK_INT < 26 /* Android 8.0 (O) */) {\n            /* Silence 'lint' warning */\n            return false;\n        }\n\n        if (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */) {\n            if (enabled) {\n                SDLActivity.getContentView().requestPointerCapture();\n            } else {\n                SDLActivity.getContentView().releasePointerCapture();\n            }\n            mRelativeModeEnabled = enabled;\n            return true;\n        } else {\n            return false;\n        }\n    }\n\n    @Override\n    void reclaimRelativeMouseModeIfNeeded() {\n\n        if (Build.VERSION.SDK_INT < 26 /* Android 8.0 (O) */) {\n            /* Silence 'lint' warning */\n            return;\n        }\n\n        if (mRelativeModeEnabled && !SDLActivity.isDeXMode()) {\n            SDLActivity.getContentView().requestPointerCapture();\n        }\n    }\n\n    @Override\n    boolean checkRelativeEvent(MotionEvent event) {\n        if (Build.VERSION.SDK_INT < 26 /* Android 8.0 (O) */) {\n            /* Silence 'lint' warning */\n            return false;\n        }\n        return event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE;\n    }\n\n    @Override\n    float getEventX(MotionEvent event, int pointerIndex) {\n        // Relative mouse in capture mode will only have relative for X/Y\n        return event.getX(pointerIndex);\n    }\n\n    @Override\n    float getEventY(MotionEvent event, int pointerIndex) {\n        // Relative mouse in capture mode will only have relative for X/Y\n        return event.getY(pointerIndex);\n    }\n}\n\nclass SDLGenericMotionListener_API29 extends SDLGenericMotionListener_API26 {\n    @Override\n    int getPenDeviceType(InputDevice penDevice)\n    {\n        if (penDevice == null) {\n            return SDL_PEN_DEVICE_TYPE_UNKNOWN;\n        }\n\n        return penDevice.isExternal() ? SDL_PEN_DEVICE_TYPE_INDIRECT : SDL_PEN_DEVICE_TYPE_DIRECT;\n    }\n}\n"
  },
  {
    "path": "android/app/src/main/java/org/libsdl/app/SDLDummyEdit.java",
    "content": "package org.libsdl.app;\n\nimport android.content.*;\nimport android.text.InputType;\nimport android.view.*;\nimport android.view.inputmethod.EditorInfo;\nimport android.view.inputmethod.InputConnection;\n\n/* This is a fake invisible editor view that receives the input and defines the\n * pan&scan region\n */\npublic class SDLDummyEdit extends View implements View.OnKeyListener\n{\n    InputConnection ic;\n    int input_type;\n\n    SDLDummyEdit(Context context) {\n        super(context);\n        setFocusableInTouchMode(true);\n        setFocusable(true);\n        setOnKeyListener(this);\n    }\n\n    void setInputType(int input_type) {\n        this.input_type = input_type;\n    }\n\n    @Override\n    public boolean onCheckIsTextEditor() {\n        return true;\n    }\n\n    @Override\n    public boolean onKey(View v, int keyCode, KeyEvent event) {\n        return SDLActivity.handleKeyEvent(v, keyCode, event, ic);\n    }\n\n    //\n    @Override\n    public boolean onKeyPreIme (int keyCode, KeyEvent event) {\n        // As seen on StackOverflow: http://stackoverflow.com/questions/7634346/keyboard-hide-event\n        // FIXME: Discussion at http://bugzilla.libsdl.org/show_bug.cgi?id=1639\n        // FIXME: This is not a 100% effective solution to the problem of detecting if the keyboard is showing or not\n        // FIXME: A more effective solution would be to assume our Layout to be RelativeLayout or LinearLayout\n        // FIXME: And determine the keyboard presence doing this: http://stackoverflow.com/questions/2150078/how-to-check-visibility-of-software-keyboard-in-android\n        // FIXME: An even more effective way would be if Android provided this out of the box, but where would the fun be in that :)\n        if (event.getAction()==KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {\n            if (SDLActivity.mTextEdit != null && SDLActivity.mTextEdit.getVisibility() == View.VISIBLE) {\n                SDLActivity.onNativeKeyboardFocusLost();\n            }\n        }\n        return super.onKeyPreIme(keyCode, event);\n    }\n\n    @Override\n    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {\n        ic = new SDLInputConnection(this, true);\n\n        outAttrs.inputType = input_type;\n        outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI |\n                              EditorInfo.IME_FLAG_NO_FULLSCREEN /* API 11 */;\n\n        return ic;\n    }\n}\n\n"
  },
  {
    "path": "android/app/src/main/java/org/libsdl/app/SDLInputConnection.java",
    "content": "package org.libsdl.app;\n\nimport android.content.*;\nimport android.os.Build;\nimport android.text.Editable;\nimport android.view.*;\nimport android.view.inputmethod.BaseInputConnection;\nimport android.widget.EditText;\n\nclass SDLInputConnection extends BaseInputConnection\n{\n    protected EditText mEditText;\n    protected String mCommittedText = \"\";\n\n    SDLInputConnection(View targetView, boolean fullEditor) {\n        super(targetView, fullEditor);\n        mEditText = new EditText(SDL.getContext());\n    }\n\n    @Override\n    public Editable getEditable() {\n        return mEditText.getEditableText();\n    }\n\n    @Override\n    public boolean sendKeyEvent(KeyEvent event) {\n        /*\n         * This used to handle the keycodes from soft keyboard (and IME-translated input from hardkeyboard)\n         * However, as of Ice Cream Sandwich and later, almost all soft keyboard doesn't generate key presses\n         * and so we need to generate them ourselves in commitText.  To avoid duplicates on the handful of keys\n         * that still do, we empty this out.\n         */\n\n        /*\n         * Return DOES still generate a key event, however.  So rather than using it as the 'click a button' key\n         * as we do with physical keyboards, let's just use it to hide the keyboard.\n         */\n\n        if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) {\n            if (SDLActivity.onNativeSoftReturnKey()) {\n                return true;\n            }\n        }\n\n        return super.sendKeyEvent(event);\n    }\n\n    @Override\n    public boolean commitText(CharSequence text, int newCursorPosition) {\n        if (!super.commitText(text, newCursorPosition)) {\n            return false;\n        }\n        updateText();\n        return true;\n    }\n\n    @Override\n    public boolean setComposingText(CharSequence text, int newCursorPosition) {\n        if (!super.setComposingText(text, newCursorPosition)) {\n            return false;\n        }\n        updateText();\n        return true;\n    }\n\n    @Override\n    public boolean deleteSurroundingText(int beforeLength, int afterLength) {\n        if (Build.VERSION.SDK_INT <= 29 /* Android 10.0 (Q) */) {\n            // Workaround to capture backspace key. Ref: http://stackoverflow.com/questions>/14560344/android-backspace-in-webview-baseinputconnection\n            // and https://bugzilla.libsdl.org/show_bug.cgi?id=2265\n            if (beforeLength > 0 && afterLength == 0) {\n                // backspace(s)\n                while (beforeLength-- > 0) {\n                    nativeGenerateScancodeForUnichar('\\b');\n                }\n                return true;\n           }\n        }\n\n        if (!super.deleteSurroundingText(beforeLength, afterLength)) {\n            return false;\n        }\n        updateText();\n        return true;\n    }\n\n    protected void updateText() {\n        final Editable content = getEditable();\n        if (content == null) {\n            return;\n        }\n\n        String text = content.toString();\n        int compareLength = Math.min(text.length(), mCommittedText.length());\n        int matchLength, offset;\n\n        /* Backspace over characters that are no longer in the string */\n        for (matchLength = 0; matchLength < compareLength; ) {\n            int codePoint = mCommittedText.codePointAt(matchLength);\n            if (codePoint != text.codePointAt(matchLength)) {\n                break;\n            }\n            matchLength += Character.charCount(codePoint);\n        }\n        /* FIXME: This doesn't handle graphemes, like '🌬️' */\n        for (offset = matchLength; offset < mCommittedText.length(); ) {\n            int codePoint = mCommittedText.codePointAt(offset);\n            nativeGenerateScancodeForUnichar('\\b');\n            offset += Character.charCount(codePoint);\n        }\n\n        if (matchLength < text.length()) {\n            String pendingText = text.subSequence(matchLength, text.length()).toString();\n            if (!SDLActivity.dispatchingKeyEvent()) {\n                for (offset = 0; offset < pendingText.length(); ) {\n                    int codePoint = pendingText.codePointAt(offset);\n                    if (codePoint == '\\n') {\n                        if (SDLActivity.onNativeSoftReturnKey()) {\n                            return;\n                        }\n                    }\n                    /* Higher code points don't generate simulated scancodes */\n                    if (codePoint > 0 && codePoint < 128) {\n                        nativeGenerateScancodeForUnichar((char)codePoint);\n                    }\n                    offset += Character.charCount(codePoint);\n                }\n            }\n            SDLInputConnection.nativeCommitText(pendingText, 0);\n        }\n        mCommittedText = text;\n    }\n\n    public static native void nativeCommitText(String text, int newCursorPosition);\n\n    public static native void nativeGenerateScancodeForUnichar(char c);\n}\n\n"
  },
  {
    "path": "android/app/src/main/java/org/libsdl/app/SDLSurface.java",
    "content": "package org.libsdl.app;\n\n\nimport android.content.Context;\nimport android.content.pm.ActivityInfo;\nimport android.graphics.Insets;\nimport android.hardware.Sensor;\nimport android.hardware.SensorEvent;\nimport android.hardware.SensorEventListener;\nimport android.hardware.SensorManager;\nimport android.os.Build;\nimport android.util.DisplayMetrics;\nimport android.util.Log;\nimport android.view.Display;\nimport android.view.InputDevice;\nimport android.view.KeyEvent;\nimport android.view.MotionEvent;\nimport android.view.PointerIcon;\nimport android.view.Surface;\nimport android.view.SurfaceHolder;\nimport android.view.SurfaceView;\nimport android.view.View;\nimport android.view.WindowInsets;\nimport android.view.WindowManager;\n\nimport android.view.ScaleGestureDetector;\n\n/**\n    SDLSurface. This is what we draw on, so we need to know when it's created\n    in order to do anything useful.\n\n    Because of this, that's where we set up the SDL thread\n*/\npublic class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,\n    View.OnApplyWindowInsetsListener, View.OnKeyListener, View.OnTouchListener,\n    SensorEventListener, ScaleGestureDetector.OnScaleGestureListener {\n\n    // Sensors\n    protected SensorManager mSensorManager;\n    protected Display mDisplay;\n\n    // Keep track of the surface size to normalize touch events\n    protected float mWidth, mHeight;\n\n    // Is SurfaceView ready for rendering\n    protected boolean mIsSurfaceReady;\n\n    // Pinch events\n    private final ScaleGestureDetector scaleGestureDetector;\n\n    // Startup\n    protected SDLSurface(Context context) {\n        super(context);\n        getHolder().addCallback(this);\n\n        scaleGestureDetector = new ScaleGestureDetector(context, this);\n\n        setFocusable(true);\n        setFocusableInTouchMode(true);\n        requestFocus();\n        setOnApplyWindowInsetsListener(this);\n        setOnKeyListener(this);\n        setOnTouchListener(this);\n\n        mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();\n        mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);\n\n        setOnGenericMotionListener(SDLActivity.getMotionListener());\n\n        // Some arbitrary defaults to avoid a potential division by zero\n        mWidth = 1.0f;\n        mHeight = 1.0f;\n\n        mIsSurfaceReady = false;\n    }\n\n    protected void handlePause() {\n        enableSensor(Sensor.TYPE_ACCELEROMETER, false);\n    }\n\n    protected void handleResume() {\n        setFocusable(true);\n        setFocusableInTouchMode(true);\n        requestFocus();\n        setOnApplyWindowInsetsListener(this);\n        setOnKeyListener(this);\n        setOnTouchListener(this);\n        enableSensor(Sensor.TYPE_ACCELEROMETER, true);\n    }\n\n    protected Surface getNativeSurface() {\n        return getHolder().getSurface();\n    }\n\n    // Called when we have a valid drawing surface\n    @Override\n    public void surfaceCreated(SurfaceHolder holder) {\n        Log.v(\"SDL\", \"surfaceCreated()\");\n        SDLActivity.onNativeSurfaceCreated();\n    }\n\n    // Called when we lose the surface\n    @Override\n    public void surfaceDestroyed(SurfaceHolder holder) {\n        Log.v(\"SDL\", \"surfaceDestroyed()\");\n\n        // Transition to pause, if needed\n        SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED;\n        SDLActivity.handleNativeState();\n\n        mIsSurfaceReady = false;\n        SDLActivity.onNativeSurfaceDestroyed();\n    }\n\n    // Called when the surface is resized\n    @Override\n    public void surfaceChanged(SurfaceHolder holder,\n                               int format, int width, int height) {\n        Log.v(\"SDL\", \"surfaceChanged()\");\n\n        if (SDLActivity.mSingleton == null) {\n            return;\n        }\n\n        mWidth = width;\n        mHeight = height;\n        int nDeviceWidth = width;\n        int nDeviceHeight = height;\n        float density = 1.0f;\n        try\n        {\n            DisplayMetrics realMetrics = new DisplayMetrics();\n            mDisplay.getRealMetrics( realMetrics );\n            nDeviceWidth = realMetrics.widthPixels;\n            nDeviceHeight = realMetrics.heightPixels;\n            // Use densityDpi instead of density to more closely match what the UI scale is\n            density = (float)realMetrics.densityDpi / 160.0f;\n        } catch(Exception ignored) {\n        }\n\n        synchronized(SDLActivity.getContext()) {\n            // In case we're waiting on a size change after going fullscreen, send a notification.\n            SDLActivity.getContext().notifyAll();\n        }\n\n        Log.v(\"SDL\", \"Window size: \" + width + \"x\" + height);\n        Log.v(\"SDL\", \"Device size: \" + nDeviceWidth + \"x\" + nDeviceHeight);\n        SDLActivity.nativeSetScreenResolution(width, height, nDeviceWidth, nDeviceHeight, density, mDisplay.getRefreshRate());\n        SDLActivity.onNativeResize();\n\n        // Prevent a screen distortion glitch,\n        // for instance when the device is in Landscape and a Portrait App is resumed.\n        boolean skip = false;\n        int requestedOrientation = SDLActivity.mSingleton.getRequestedOrientation();\n\n        if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) {\n            if (mWidth > mHeight) {\n               skip = true;\n            }\n        } else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) {\n            if (mWidth < mHeight) {\n               skip = true;\n            }\n        }\n\n        // Special Patch for Square Resolution: Black Berry Passport\n        if (skip) {\n           double min = Math.min(mWidth, mHeight);\n           double max = Math.max(mWidth, mHeight);\n\n           if (max / min < 1.20) {\n              Log.v(\"SDL\", \"Don't skip on such aspect-ratio. Could be a square resolution.\");\n              skip = false;\n           }\n        }\n\n        // Don't skip if we might be multi-window or have popup dialogs\n        if (skip) {\n            if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n                skip = false;\n            }\n        }\n\n        if (skip) {\n           Log.v(\"SDL\", \"Skip .. Surface is not ready.\");\n           mIsSurfaceReady = false;\n           return;\n        }\n\n        /* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */\n        SDLActivity.onNativeSurfaceChanged();\n\n        /* Surface is ready */\n        mIsSurfaceReady = true;\n\n        SDLActivity.mNextNativeState = SDLActivity.NativeState.RESUMED;\n        SDLActivity.handleNativeState();\n    }\n\n    // Window inset\n    @Override\n    public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {\n        if (Build.VERSION.SDK_INT >= 30 /* Android 11 (R) */) {\n            Insets combined = insets.getInsets(WindowInsets.Type.systemBars() |\n                                               WindowInsets.Type.systemGestures() |\n                                               WindowInsets.Type.mandatorySystemGestures() |\n                                               WindowInsets.Type.tappableElement() |\n                                               WindowInsets.Type.displayCutout());\n\n            SDLActivity.onNativeInsetsChanged(combined.left, combined.right, combined.top, combined.bottom);\n        }\n\n        // Pass these to any child views in case they need them\n        return insets;\n    }\n\n    // Key events\n    @Override\n    public boolean onKey(View v, int keyCode, KeyEvent event) {\n        return SDLActivity.handleKeyEvent(v, keyCode, event, null);\n    }\n\n    private float getNormalizedX(float x)\n    {\n        if (mWidth <= 1) {\n            return 0.5f;\n        } else {\n            return (x / (mWidth - 1));\n        }\n    }\n\n    private float getNormalizedY(float y)\n    {\n        if (mHeight <= 1) {\n            return 0.5f;\n        } else {\n            return (y / (mHeight - 1));\n        }\n    }\n\n    // Touch events\n    @Override\n    public boolean onTouch(View v, MotionEvent event) {\n        /* Ref: http://developer.android.com/training/gestures/multi.html */\n        int touchDevId = event.getDeviceId();\n        final int pointerCount = event.getPointerCount();\n        int action = event.getActionMasked();\n        int pointerId;\n        int i = 0;\n        float x,y,p;\n\n        if (action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_POINTER_DOWN)\n            i = event.getActionIndex();\n\n        do {\n            int toolType = event.getToolType(i);\n\n            if (toolType == MotionEvent.TOOL_TYPE_MOUSE) {\n                int buttonState = event.getButtonState();\n                boolean relative = false;\n\n                // We need to check if we're in relative mouse mode and get the axis offset rather than the x/y values\n                // if we are. We'll leverage our existing mouse motion listener\n                SDLGenericMotionListener_API14 motionListener = SDLActivity.getMotionListener();\n                x = motionListener.getEventX(event, i);\n                y = motionListener.getEventY(event, i);\n                relative = motionListener.inRelativeMode();\n\n                SDLActivity.onNativeMouse(buttonState, action, x, y, relative);\n            } else if (toolType == MotionEvent.TOOL_TYPE_STYLUS || toolType == MotionEvent.TOOL_TYPE_ERASER) {\n                pointerId = event.getPointerId(i);\n                x = event.getX(i);\n                y = event.getY(i);\n                p = event.getPressure(i);\n                if (p > 1.0f) {\n                    // may be larger than 1.0f on some devices\n                    // see the documentation of getPressure(i)\n                    p = 1.0f;\n                }\n\n                // BUTTON_STYLUS_PRIMARY is 2^5, so shift by 4, and apply SDL_PEN_INPUT_DOWN/SDL_PEN_INPUT_ERASER_TIP\n                int buttonState = (event.getButtonState() >> 4) | (1 << (toolType == MotionEvent.TOOL_TYPE_STYLUS ? 0 : 30));\n\n                SDLActivity.onNativePen(pointerId, SDLActivity.getMotionListener().getPenDeviceType(event.getDevice()), buttonState, action, x, y, p);\n            } else { // MotionEvent.TOOL_TYPE_FINGER or MotionEvent.TOOL_TYPE_UNKNOWN\n                pointerId = event.getPointerId(i);\n                x = getNormalizedX(event.getX(i));\n                y = getNormalizedY(event.getY(i));\n                p = event.getPressure(i);\n                if (p > 1.0f) {\n                    // may be larger than 1.0f on some devices\n                    // see the documentation of getPressure(i)\n                    p = 1.0f;\n                }\n\n                SDLActivity.onNativeTouch(touchDevId, pointerId, action, x, y, p);\n            }\n\n            // Non-primary up/down\n            if (action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_POINTER_DOWN)\n                break;\n        } while (++i < pointerCount);\n\n        scaleGestureDetector.onTouchEvent(event);\n\n        return true;\n    }\n\n    // Sensor events\n    protected void enableSensor(int sensortype, boolean enabled) {\n        // TODO: This uses getDefaultSensor - what if we have >1 accels?\n        if (enabled) {\n            mSensorManager.registerListener(this,\n                            mSensorManager.getDefaultSensor(sensortype),\n                            SensorManager.SENSOR_DELAY_GAME, null);\n        } else {\n            mSensorManager.unregisterListener(this,\n                            mSensorManager.getDefaultSensor(sensortype));\n        }\n    }\n\n    @Override\n    public void onAccuracyChanged(Sensor sensor, int accuracy) {\n        // TODO\n    }\n\n    @Override\n    public void onSensorChanged(SensorEvent event) {\n        if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {\n\n            // Since we may have an orientation set, we won't receive onConfigurationChanged events.\n            // We thus should check here.\n            int newRotation;\n\n            float x, y;\n            switch (mDisplay.getRotation()) {\n                case Surface.ROTATION_0:\n                default:\n                    x = event.values[0];\n                    y = event.values[1];\n                    newRotation = 0;\n                    break;\n                case Surface.ROTATION_90:\n                    x = -event.values[1];\n                    y = event.values[0];\n                    newRotation = 90;\n                    break;\n                case Surface.ROTATION_180:\n                    x = -event.values[0];\n                    y = -event.values[1];\n                    newRotation = 180;\n                    break;\n                case Surface.ROTATION_270:\n                    x = event.values[1];\n                    y = -event.values[0];\n                    newRotation = 270;\n                    break;\n            }\n\n            if (newRotation != SDLActivity.mCurrentRotation) {\n                SDLActivity.mCurrentRotation = newRotation;\n                SDLActivity.onNativeRotationChanged(newRotation);\n            }\n\n            SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH,\n                                      y / SensorManager.GRAVITY_EARTH,\n                                      event.values[2] / SensorManager.GRAVITY_EARTH);\n\n\n        }\n    }\n\n    // Prevent android internal NullPointerException (https://github.com/libsdl-org/SDL/issues/13306)\n    @Override\n    public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {\n        try {\n            return super.onResolvePointerIcon(event, pointerIndex);\n        } catch (NullPointerException e) {\n            return null;\n        }\n    }\n\n    // Captured pointer events for API 26.\n    @Override\n    public boolean onCapturedPointerEvent(MotionEvent event)\n    {\n        int action = event.getActionMasked();\n        int pointerCount = event.getPointerCount();\n\n        for (int i = 0; i < pointerCount; i++) {\n            float x, y;\n            switch (action) {\n                case MotionEvent.ACTION_SCROLL:\n                    x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, i);\n                    y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, i);\n                    SDLActivity.onNativeMouse(0, action, x, y, false);\n                    return true;\n\n                case MotionEvent.ACTION_HOVER_MOVE:\n                case MotionEvent.ACTION_MOVE:\n                    x = event.getX(i);\n                    y = event.getY(i);\n                    SDLActivity.onNativeMouse(0, action, x, y, true);\n                    return true;\n\n                case MotionEvent.ACTION_BUTTON_PRESS:\n                case MotionEvent.ACTION_BUTTON_RELEASE:\n\n                    // Change our action value to what SDL's code expects.\n                    if (action == MotionEvent.ACTION_BUTTON_PRESS) {\n                        action = MotionEvent.ACTION_DOWN;\n                    } else { /* MotionEvent.ACTION_BUTTON_RELEASE */\n                        action = MotionEvent.ACTION_UP;\n                    }\n\n                    x = event.getX(i);\n                    y = event.getY(i);\n                    int button = event.getButtonState();\n\n                    SDLActivity.onNativeMouse(button, action, x, y, true);\n                    return true;\n            }\n        }\n\n        return false;\n    }\n\n    @Override\n    public boolean onScale(ScaleGestureDetector detector) {\n        float scale = detector.getScaleFactor();\n        SDLActivity.onNativePinchUpdate(scale);\n        return true;\n    }\n\n    @Override\n    public boolean onScaleBegin(ScaleGestureDetector detector) {\n        SDLActivity.onNativePinchStart();\n        return true;\n    }\n\n    @Override\n    public void onScaleEnd(ScaleGestureDetector detector) {\n        SDLActivity.onNativePinchEnd();\n    }\n\n}\n"
  },
  {
    "path": "android/app/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"app_name\">Metaforce</string>\n</resources>\n"
  },
  {
    "path": "android/build.gradle",
    "content": "plugins {\n    id 'com.android.application' version '8.7.3' apply false\n}\n"
  },
  {
    "path": "android/gradle/wrapper/gradle-wrapper.properties",
    "content": "#Thu Nov 11 18:20:34 PST 2021\ndistributionBase=GRADLE_USER_HOME\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.12-bin.zip\ndistributionPath=wrapper/dists\nzipStorePath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\n"
  },
  {
    "path": "android/gradle.properties",
    "content": "org.gradle.jvmargs=-Xmx4g -Dfile.encoding=UTF-8\nandroid.useAndroidX=true\n"
  },
  {
    "path": "android/gradlew",
    "content": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn ( ) {\n    echo \"$*\"\n}\n\ndie ( ) {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\nesac\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|grep -E -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|grep -E -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules\nfunction splitJvmOpts() {\n    JVM_OPTS=(\"$@\")\n}\neval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\nJVM_OPTS[${#JVM_OPTS[*]}]=\"-Dorg.gradle.appname=$APP_BASE_NAME\"\n\nexec \"$JAVACMD\" \"${JVM_OPTS[@]}\" -classpath \"$CLASSPATH\" org.gradle.wrapper.GradleWrapperMain \"$@\"\n"
  },
  {
    "path": "android/gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto init\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto init\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:init\n@rem Get command-line arguments, handling Windowz variants\n\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\nif \"%@eval[2+2]\" == \"4\" goto 4NT_args\n\n:win9xME_args\n@rem Slurp the command line arguments.\nset CMD_LINE_ARGS=\nset _SKIP=2\n\n:win9xME_args_slurp\nif \"x%~1\" == \"x\" goto execute\n\nset CMD_LINE_ARGS=%*\ngoto execute\n\n:4NT_args\n@rem Get arguments from the 4NT Shell from JP Software\nset CMD_LINE_ARGS=%$\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "android/scripts/stage-jni-libs.sh",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\n\nROOT_DIR=\"$(cd \"$(dirname \"$0\")/../..\" && pwd)\"\nAPP_DIR=\"$ROOT_DIR/android/app/src/main/jniLibs\"\nANDROID_HOME_DIR=\"${ANDROID_HOME:-$HOME/Android/Sdk}\"\nANDROID_NDK_VER=\"${ANDROID_NDK_VERSION:-}\"\nANDROID_STAGE_ABIS=\"${ANDROID_STAGE_ABIS:-arm64-v8a x86_64}\"\nANDROID_STAGE_STRIP=\"${ANDROID_STAGE_STRIP:-1}\"\nSTRIP_TOOL=\"\"\n\nif [[ -z \"$ANDROID_NDK_VER\" ]] && [[ -d \"$ANDROID_HOME_DIR/ndk\" ]]; then\n  ANDROID_NDK_VER=\"$(ls -1 \"$ANDROID_HOME_DIR/ndk\" | sort -V | tail -n 1)\"\nfi\n\nif [[ -n \"$ANDROID_NDK_VER\" ]]; then\n  TOOLCHAIN_BIN=\"$ANDROID_HOME_DIR/ndk/$ANDROID_NDK_VER/toolchains/llvm/prebuilt/linux-x86_64/bin\"\n  if [[ -x \"$TOOLCHAIN_BIN/llvm-strip\" ]]; then\n    STRIP_TOOL=\"$TOOLCHAIN_BIN/llvm-strip\"\n  fi\nfi\n\ncopy_lib() {\n  local abi=\"$1\"\n  local src=\"$2\"\n  local dst_dir=\"$APP_DIR/$abi\"\n  local dst=\"$dst_dir/libmain.so\"\n  mkdir -p \"$dst_dir\"\n  cp -f \"$src\" \"$dst\"\n  if [[ \"$ANDROID_STAGE_STRIP\" != \"0\" ]] && [[ -n \"$STRIP_TOOL\" ]]; then\n    \"$STRIP_TOOL\" --strip-debug \"$dst\"\n    echo \"Staged and stripped $src -> $dst\"\n  else\n    echo \"Staged $src -> $dst (strip disabled or strip tool unavailable)\"\n  fi\n}\n\ndeclare -A ABI_TO_LIB=(\n  [\"arm64-v8a\"]=\"$ROOT_DIR/build/android-arm64/Binaries/libmain.so\"\n  [\"x86_64\"]=\"$ROOT_DIR/build/android-x86_64/Binaries/libmain.so\"\n)\n\n# Drop any previously staged ABI directories to avoid stale APK contents.\nrm -rf \"$APP_DIR/x86\" \"$APP_DIR/arm64-v8a\" \"$APP_DIR/x86_64\"\n\nfor abi in $ANDROID_STAGE_ABIS; do\n  src=\"${ABI_TO_LIB[$abi]:-}\"\n  if [[ -z \"$src\" ]]; then\n    echo \"Unsupported ABI '$abi'. Supported ABIs: arm64-v8a x86_64\" >&2\n    exit 1\n  fi\n  copy_lib \"$abi\" \"$src\"\ndone\n"
  },
  {
    "path": "android/scripts/sync-sdl-java.sh",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\n\nROOT_DIR=\"$(cd \"$(dirname \"$0\")/../..\" && pwd)\"\nSRC_DEFAULT=\"$ROOT_DIR/build/android-arm64/_deps/sdl-src/android-project/app/src/main/java/org/libsdl/app\"\nSRC_DIR=\"${1:-$SRC_DEFAULT}\"\nDST_DIR=\"$ROOT_DIR/android/app/src/main/java/org/libsdl/app\"\n\nif [[ ! -d \"$SRC_DIR\" ]]; then\n  echo \"SDL Java source directory not found: $SRC_DIR\" >&2\n  exit 1\nfi\n\nmkdir -p \"$DST_DIR\"\ncp -f \"$SRC_DIR\"/*.java \"$DST_DIR\"/\necho \"Synced SDL Java sources from $SRC_DIR to $DST_DIR\"\n"
  },
  {
    "path": "android/settings.gradle",
    "content": "pluginManagement {\n    repositories {\n        google()\n        mavenCentral()\n        gradlePluginPortal()\n    }\n}\n\ndependencyResolutionManagement {\n    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)\n    repositories {\n        google()\n        mavenCentral()\n    }\n}\n\nrootProject.name = \"metaforce-android\"\ninclude ':app'\n"
  },
  {
    "path": "bintoc/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.15 FATAL_ERROR)\ncmake_policy(VERSION 3.15...3.20)\nproject(bintoc LANGUAGES C)\n\nadd_executable(bintoc bintoc.c)\n\nfind_package(ZLIB REQUIRED)\ntarget_link_libraries(bintoc PRIVATE ZLIB::ZLIB)\n\ninstall(TARGETS bintoc DESTINATION bin)\n"
  },
  {
    "path": "bintoc/bintoc.c",
    "content": "#include <stdio.h>\n#include <stdint.h>\n#include <string.h>\n#include <stdbool.h>\n#include <zlib.h>\n\n#define CHUNK 16384\n#define LINE_BREAK 32\nstatic uint8_t buf[CHUNK];\nstatic uint8_t zbuf[CHUNK];\n\nvoid print_usage() { fprintf(stderr, \"Usage: bintoc [--compress] <in> <out> <symbol>\\n\"); }\n\nint main(int argc, char** argv) {\n  if (argc < 4) {\n    print_usage();\n    return 1;\n  }\n  char* input = argv[1];\n  char* output = argv[2];\n  char* symbol = argv[3];\n  bool compress = false;\n  if (strcmp(input, \"--compress\") == 0) {\n    if (argc < 5) {\n      print_usage();\n      return 1;\n    }\n    input = argv[2];\n    output = argv[3];\n    symbol = argv[4];\n    compress = true;\n  }\n  FILE* fin = fopen(input, \"rb\");\n  if (!fin) {\n    fprintf(stderr, \"Unable to open %s for reading\\n\", input);\n    return 1;\n  }\n  FILE* fout = fopen(output, \"wb\");\n  if (!fout) {\n    fprintf(stderr, \"Unable to open %s for writing\\n\", output);\n    return 1;\n  }\n  fprintf(fout, \"#include <cstdint>\\n#include <cstddef>\\n\");\n  fprintf(fout, \"extern \\\"C\\\" const uint8_t %s[] =\\n{\\n    \", symbol);\n  size_t totalSz = 0;\n  size_t readSz;\n  if (compress) {\n    size_t compressedSz = 0;\n    z_stream strm = {.zalloc = Z_NULL, .zfree = Z_NULL, .opaque = Z_NULL};\n    int ret = deflateInit(&strm, Z_BEST_COMPRESSION);\n    if (ret != Z_OK) {\n      fprintf(stderr, \"zlib initialization failed %d\\n\", ret);\n      return 1;\n    }\n    while ((strm.avail_in = fread(buf, 1, sizeof(buf), fin))) {\n      totalSz += strm.avail_in;\n      strm.next_in = buf;\n      int eof = feof(fin);\n      do {\n        strm.next_out = zbuf;\n        strm.avail_out = sizeof(zbuf);\n        ret = deflate(&strm, eof ? Z_FINISH : Z_NO_FLUSH);\n        if (ret == Z_STREAM_ERROR) {\n          fprintf(stderr, \"zlib compression failed %d\\n\", ret);\n          return 1;\n        }\n        size_t sz = sizeof(zbuf) - strm.avail_out;\n        if (sz > 0) {\n          for (int b = 0; b < sz; ++b) {\n            fprintf(fout, \"0x%02X, \", zbuf[b]);\n            if ((compressedSz + b + 1) % LINE_BREAK == 0)\n              fprintf(fout, \"\\n    \");\n          }\n          compressedSz += sz;\n        }\n      } while (strm.avail_out == 0 || (eof && (ret == Z_OK || ret == Z_BUF_ERROR)));\n    }\n    deflateEnd(&strm);\n    fprintf(fout, \"0x00};\\nextern \\\"C\\\" const size_t %s_SZ = %zu;\\n\", symbol, compressedSz);\n    fprintf(fout, \"extern \\\"C\\\" const size_t %s_DECOMPRESSED_SZ = %zu;\\n\", symbol, totalSz);\n  } else {\n    while ((readSz = fread(buf, 1, sizeof(buf), fin))) {\n      for (int b = 0; b < readSz; ++b) {\n        fprintf(fout, \"0x%02X, \", buf[b]);\n        if ((totalSz + b + 1) % LINE_BREAK == 0)\n          fprintf(fout, \"\\n    \");\n      }\n      totalSz += readSz;\n    }\n    fprintf(fout, \"0x0};\\nextern \\\"C\\\" const size_t %s_SZ = %zu;\\n\", symbol, totalSz);\n  }\n  fclose(fin);\n  fclose(fout);\n  return 0;\n}\n"
  },
  {
    "path": "bintoc/bintocHelpers.cmake",
    "content": "function(bintoc out in sym)\n  if(IS_ABSOLUTE ${out})\n    set(theOut ${out})\n  else()\n    set(theOut ${CMAKE_CURRENT_BINARY_DIR}/${out})\n  endif()\n  if(IS_ABSOLUTE ${in})\n    set(theIn ${in})\n  else()\n    set(theIn ${CMAKE_CURRENT_SOURCE_DIR}/${in})\n  endif()\n  get_filename_component(outDir ${theOut} DIRECTORY)\n  file(MAKE_DIRECTORY ${outDir})\n  ExternalProject_Get_Property(bintoc INSTALL_DIR)\n  add_custom_command(OUTPUT ${theOut}\n                     COMMAND \"${INSTALL_DIR}/bin/bintoc\" ARGS ${theIn} ${theOut} ${sym}\n                     DEPENDS ${theIn} bintoc)\nendfunction()\n\nfunction(bintoc_compress out in sym)\n  if(IS_ABSOLUTE ${out})\n    set(theOut ${out})\n  else()\n    set(theOut ${CMAKE_CURRENT_BINARY_DIR}/${out})\n  endif()\n  if(IS_ABSOLUTE ${in})\n    set(theIn ${in})\n  else()\n    set(theIn ${CMAKE_CURRENT_SOURCE_DIR}/${in})\n  endif()\n  get_filename_component(outDir ${theOut} DIRECTORY)\n  file(MAKE_DIRECTORY ${outDir})\n  ExternalProject_Get_Property(bintoc INSTALL_DIR)\n  add_custom_command(OUTPUT ${theOut}\n                     COMMAND \"${INSTALL_DIR}/bin/bintoc\" ARGS --compress ${theIn} ${theOut} ${sym}\n                     DEPENDS ${theIn} bintoc)\nendfunction()\n"
  },
  {
    "path": "ci/build-appimage.sh",
    "content": "#!/bin/bash -ex\nshopt -s extglob\n\n# Get linuxdeploy\ncd \"$RUNNER_WORKSPACE\"\ncurl -OL https://github.com/encounter/linuxdeploy/releases/download/continuous/linuxdeploy-$(uname -m).AppImage\nchmod +x linuxdeploy-$(uname -m).AppImage\n\n# Build AppImage\ncd \"$GITHUB_WORKSPACE\"\nmkdir -p build/appdir/usr/{bin,share/{applications,icons/hicolor}}\ncp build/install/!(*.*) build/appdir/usr/bin\ncp -r Runtime/platforms/freedesktop/{16x16,32x32,48x48,64x64,128x128,256x256,512x512,1024x1024} build/appdir/usr/share/icons/hicolor\ncp Runtime/platforms/freedesktop/metaforce.desktop build/appdir/usr/share/applications\n\ncd build/install\nVERSION=\"$METAFORCE_VERSION\" NO_STRIP=1 \"$RUNNER_WORKSPACE\"/linuxdeploy-$(uname -m).AppImage \\\n  --appdir \"$GITHUB_WORKSPACE\"/build/appdir --output appimage\n"
  },
  {
    "path": "ci/build-dmg.sh",
    "content": "#!/bin/bash -ex\ncd build/install\n#for i in Metaforce crashpad_handler; do\n#  if [ -x \"Metaforce.app/Contents/MacOS/$i\" ]; then\n#    codesign --timestamp --options runtime -s \"$CODESIGN_IDENT\" \"Metaforce.app/Contents/MacOS/$i\"\n#  fi\n#done\ncreate-dmg Metaforce.app . #--identity=\"$CODESIGN_IDENT\" .\n#xcrun altool -t osx -f *.dmg --primary-bundle-id com.axiodl.Metaforce \\\n#  --notarize-app -u \"$ASC_USERNAME\" -p \"$ASC_PASSWORD\" --team-id \"$ASC_TEAM_ID\"\n"
  },
  {
    "path": "ci/build-ipa.sh",
    "content": "#!/bin/bash -ex\ncd build/install\nrm -fr Payload\nmkdir Payload\ncp -r Metaforce.app Payload\nzip -r Metaforce.zip Payload\nmv Metaforce.zip Metaforce.ipa\n"
  },
  {
    "path": "ci/upload-debug-linux.sh",
    "content": "#!/bin/bash -ex\ncd build/install\nsentry-cli upload-dif --org axiodl --project metaforce metaforce{,.dbg} --include-sources\n"
  },
  {
    "path": "ci/upload-debug-macos.sh",
    "content": "#!/bin/bash -ex\ncd build/install\nsentry-cli upload-dif --org axiodl --project metaforce metaforce.app/Contents/MacOS/metaforce metaforce.dSYM --include-sources\n"
  },
  {
    "path": "extern/CMakeLists.txt",
    "content": "# Enable MoltenVK\n#if (CMAKE_SYSTEM_NAME STREQUAL Darwin)\n#  set(DAWN_ENABLE_VULKAN ON CACHE BOOL \"Enable compilation of the Vulkan backend\" FORCE)\n#endif()\nif (CMAKE_SYSTEM_NAME STREQUAL Linux)\n  set(DAWN_USE_WAYLAND ON CACHE BOOL \"Enable support for Wayland surface\" FORCE)\nendif ()\nadd_subdirectory(aurora)\n\nif (NOT TARGET spdlog)\n  set(SPDLOG_NO_EXCEPTIONS ON CACHE BOOL \"Compile with -fno-exceptions. Call abort() on any spdlog exceptions\" FORCE)\n  set(SPDLOG_FMT_EXTERNAL ON CACHE BOOL \"Use external fmt library instead of bundled\" FORCE)\n  add_subdirectory(spdlog EXCLUDE_FROM_ALL)\nendif ()\n\nif (WIN32 AND NOT TARGET nowide)\n  include(FetchContent)\n  FetchContent_Declare(\n    nowide\n    URL https://github.com/boostorg/nowide/releases/download/v11.3.0/nowide_standalone_v11.3.0.tar.gz\n    URL_HASH SHA256=153ac93173c8de9c08e7701e471fa750f84c27e51fe329570c5aa06016591f8c\n    DOWNLOAD_EXTRACT_TIMESTAMP TRUE\n    EXCLUDE_FROM_ALL\n  )\n  FetchContent_MakeAvailable(nowide)\nendif ()\n\nadd_subdirectory(nod EXCLUDE_FROM_ALL)\nadd_subdirectory(jbus EXCLUDE_FROM_ALL)\nadd_subdirectory(kabufuda EXCLUDE_FROM_ALL)\n\n#option(OPTICK_ENABLED \"Enable profiling with Optick\" OFF)\n#set(OPTICK_USE_VULKAN ${DAWN_ENABLE_VULKAN} CACHE BOOL \"Built-in support for Vulkan\" FORCE)\n#set(OPTICK_INSTALL_TARGETS OFF CACHE BOOL \"Should optick be installed? Set to OFF if you use add_subdirectory to include Optick.\" FORCE)\n#add_subdirectory(optick)\n#if (NOT MSVC)\n#  target_compile_options(OptickCore PRIVATE -Wno-implicit-fallthrough)\n#endif ()\n\nadd_subdirectory(libjpeg-turbo EXCLUDE_FROM_ALL)\nadd_subdirectory(zeus EXCLUDE_FROM_ALL)\nadd_subdirectory(musyx EXCLUDE_FROM_ALL)\n"
  },
  {
    "path": "gbalink/CMakeLists.txt",
    "content": "add_executable(gbalink main.cpp)\ntarget_link_libraries(gbalink jbus)\ntarget_include_directories(gbalink PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ../Runtime ${ATHENA_INCLUDE_DIR})\n"
  },
  {
    "path": "gbalink/main.cpp",
    "content": "#include <cstdio>\n#include <cstring>\n#include <memory>\n#include <optional>\n\n#include \"GCNTypes.hpp\"\n\n#include <jbus/Endpoint.hpp>\n#include <jbus/Listener.hpp>\n\n#undef min\n#undef max\n\nclass CGBASupport {\npublic:\n  enum class EPhase {\n    LoadClientPad,\n    Standby,\n    StartProbeTimeout,\n    PollProbe,\n    StartJoyBusBoot,\n    PollJoyBusBoot,\n    DataTransfer,\n    Complete,\n    Failed\n  };\n\nprivate:\n  std::unique_ptr<jbus::Endpoint> m_endpoint;\n  u32 x28_fileSize;\n  std::unique_ptr<u8[]> x2c_buffer;\n  EPhase x34_phase = EPhase::LoadClientPad;\n  float x38_timeout = 0.f;\n  u8 x3c_status = 0;\n  u32 x40_siChan = -1;\n  bool x44_fusionLinked = false;\n  bool x45_fusionBeat = false;\n  static CGBASupport* SharedInstance;\n\n  static u8 CalculateFusionJBusChecksum(const u8* data, size_t len);\n\npublic:\n  CGBASupport(const char* clientPadPath, std::unique_ptr<jbus::Endpoint>&& ep);\n  ~CGBASupport();\n  bool PollResponse();\n  void Update(float dt);\n  bool IsReady();\n  void InitializeSupport();\n  void StartLink();\n  EPhase GetPhase() const { return x34_phase; }\n  bool IsFusionLinked() const { return x44_fusionLinked; }\n  bool IsFusionBeat() const { return x45_fusionBeat; }\n};\n\nCGBASupport* CGBASupport::SharedInstance;\n\nCGBASupport::CGBASupport(const char* clientPadPath, std::unique_ptr<jbus::Endpoint>&& ep) : m_endpoint(std::move(ep)) {\n  FILE* fp = fopen(clientPadPath, \"rb\");\n  if (!fp) {\n    fprintf(stderr, \"No file at %s\\n\", clientPadPath);\n    exit(1);\n  }\n  fseek(fp, 0, SEEK_END);\n  x28_fileSize = ftell(fp);\n  fseek(fp, 0, SEEK_SET);\n  x2c_buffer.reset(new u8[x28_fileSize]);\n  fread(x2c_buffer.get(), 1, x28_fileSize, fp);\n  fclose(fp);\n  SharedInstance = this;\n}\n\nCGBASupport::~CGBASupport() { SharedInstance = nullptr; }\n\nu8 CGBASupport::CalculateFusionJBusChecksum(const u8* data, size_t len) {\n  u32 sum = -1;\n  for (size_t i = 0; i < len; ++i) {\n    u8 ch = *data++;\n    sum ^= ch;\n    for (int j = 0; j < 8; ++j) {\n      if ((sum & 1)) {\n        sum >>= 1;\n        sum ^= 0xb010;\n      } else\n        sum >>= 1;\n    }\n  }\n  return sum;\n}\n\nbool CGBASupport::PollResponse() {\n  u8 status;\n  if (m_endpoint->GBAReset(&status) == jbus::GBA_NOT_READY)\n    if (m_endpoint->GBAReset(&status) == jbus::GBA_NOT_READY)\n      return false;\n\n  if (m_endpoint->GBAGetStatus(&status) == jbus::GBA_NOT_READY)\n    return false;\n  if (status != (jbus::GBA_JSTAT_PSF1 | jbus::GBA_JSTAT_SEND))\n    return false;\n\n  jbus::ReadWriteBuffer bytes;\n  if (m_endpoint->GBARead(bytes, &status) == jbus::GBA_NOT_READY) {\n    return false;\n  }\n\n  u32 bytesU32;\n  std::memcpy(&bytesU32, bytes.data(), sizeof(bytes));\n  if (bytesU32 != SBIG('AMTE')) {\n    return false;\n  }\n\n  if (m_endpoint->GBAGetStatus(&status) == jbus::GBA_NOT_READY) {\n    return false;\n  }\n  if (status != jbus::GBA_JSTAT_PSF1) {\n    return false;\n  }\n\n  if (m_endpoint->GBAWrite({'A', 'M', 'T', 'E'}, &status) == jbus::GBA_NOT_READY) {\n    return false;\n  }\n\n  if (m_endpoint->GBAGetStatus(&status) == jbus::GBA_NOT_READY) {\n    return false;\n  }\n  if ((status & jbus::GBA_JSTAT_FLAGS_MASK) != jbus::GBA_JSTAT_FLAGS_MASK) {\n    return false;\n  }\n\n  u64 profStart = jbus::GetGCTicks();\n  const u64 timeToSpin = jbus::GetGCTicksPerSec() / 8000;\n  for (;;) {\n    u64 curTime = jbus::GetGCTicks();\n    if (curTime - profStart > timeToSpin)\n      return true;\n\n    if (m_endpoint->GBAGetStatus(&status) == jbus::GBA_NOT_READY)\n      continue;\n    if (!(status & jbus::GBA_JSTAT_SEND))\n      continue;\n\n    if (m_endpoint->GBAGetStatus(&status) == jbus::GBA_NOT_READY)\n      continue;\n    if (status != (jbus::GBA_JSTAT_FLAGS_MASK | jbus::GBA_JSTAT_SEND))\n      continue;\n    break;\n  }\n\n  if (m_endpoint->GBARead(bytes, &status) != jbus::GBA_READY)\n    return false;\n\n  if (bytes[3] != CalculateFusionJBusChecksum(bytes.data(), 3))\n    return false;\n\n  x44_fusionLinked = (bytes[2] & 0x2) == 0;\n  if (x44_fusionLinked && (bytes[2] & 0x1) != 0)\n    x45_fusionBeat = true;\n\n  return true;\n}\n\nstatic void JoyBootDone(jbus::ThreadLocalEndpoint& endpoint, jbus::EJoyReturn status) {}\n\nvoid CGBASupport::Update(float dt) {\n  switch (x34_phase) {\n  case EPhase::LoadClientPad:\n    IsReady();\n    break;\n\n  case EPhase::StartProbeTimeout:\n    x38_timeout = 4.f;\n    x34_phase = EPhase::PollProbe;\n    [[fallthrough]];\n\n  case EPhase::PollProbe:\n    /* SIProbe poll normally occurs here with 4 second timeout */\n    x40_siChan = m_endpoint->getChan();\n    x34_phase = EPhase::StartJoyBusBoot;\n    [[fallthrough]];\n\n  case EPhase::StartJoyBusBoot:\n    x34_phase = EPhase::PollJoyBusBoot;\n    if (m_endpoint->GBAJoyBootAsync(x40_siChan * 2, 2, x2c_buffer.get(), x28_fileSize, &x3c_status,\n                                    std::bind(JoyBootDone, std::placeholders::_1, std::placeholders::_2)) !=\n        jbus::GBA_READY)\n      x34_phase = EPhase::Failed;\n    break;\n\n  case EPhase::PollJoyBusBoot:\n    u8 percent;\n    if (m_endpoint->GBAGetProcessStatus(percent) == jbus::GBA_BUSY)\n      break;\n    if (m_endpoint->GBAGetStatus(&x3c_status) == jbus::GBA_NOT_READY) {\n      x34_phase = EPhase::Failed;\n      break;\n    }\n    x38_timeout = 4.f;\n    x34_phase = EPhase::DataTransfer;\n    break;\n\n  case EPhase::DataTransfer:\n    if (PollResponse()) {\n      x34_phase = EPhase::Complete;\n      break;\n    }\n    x38_timeout = std::max(0.f, x38_timeout - dt);\n    if (x38_timeout == 0.f)\n      x34_phase = EPhase::Failed;\n    break;\n\n  default:\n    break;\n  }\n}\n\nbool CGBASupport::IsReady() {\n  if (x34_phase != EPhase::LoadClientPad)\n    return true;\n\n  x34_phase = EPhase::Standby;\n  reinterpret_cast<u32&>(x2c_buffer[0xc8]) = u32(jbus::GetGCTicks());\n  x2c_buffer[0xaf] = 'E';\n  x2c_buffer[0xbd] = 0xc9;\n  return true;\n}\n\nvoid CGBASupport::InitializeSupport() {\n  x34_phase = EPhase::Standby;\n  x38_timeout = 0.f;\n  x3c_status = false;\n  x40_siChan = -1;\n  x44_fusionLinked = false;\n  x45_fusionBeat = false;\n}\n\nvoid CGBASupport::StartLink() {\n  x34_phase = EPhase::StartProbeTimeout;\n  x40_siChan = -1;\n}\n\nint main(int argc, char** argv) {\n  jbus::Initialize();\n  printf(\"Listening for client\\n\");\n  jbus::Listener listener;\n  listener.start();\n  std::unique_ptr<jbus::Endpoint> endpoint;\n  while (true) {\n    s64 frameStart = jbus::GetGCTicks();\n    endpoint = listener.accept();\n    if (endpoint)\n      break;\n    s64 frameEnd = jbus::GetGCTicks();\n    s64 waitTicks = jbus::GetGCTicksPerSec() / 60;\n    if (waitTicks > 0)\n      jbus::WaitGCTicks(waitTicks);\n  }\n\n  CGBASupport gba(\"client_pad.bin\", std::move(endpoint));\n  gba.Update(0.f);\n  gba.InitializeSupport();\n  gba.StartLink();\n\n  printf(\"Waiting 5 sec\\n\");\n  jbus::WaitGCTicks(jbus::GetGCTicksPerSec() * 5);\n\n  printf(\"Connecting\\n\");\n  while (gba.GetPhase() < CGBASupport::EPhase::Complete) {\n    gba.Update(1.f / 60.f);\n    s64 waitTicks = jbus::GetGCTicksPerSec() / 60;\n    if (waitTicks > 0)\n      jbus::WaitGCTicks(waitTicks);\n  }\n\n  CGBASupport::EPhase finalPhase = gba.GetPhase();\n  printf(\"%s Linked: %d Beat: %d\\n\", finalPhase == CGBASupport::EPhase::Complete ? \"Complete\" : \"Failed\",\n         gba.IsFusionLinked(), gba.IsFusionBeat());\n\n  return 0;\n}\n"
  },
  {
    "path": "imgui/CMakeLists.txt",
    "content": "add_library(imgui_support\n    ImGuiEngine.cpp\n    ImGuiEngine.hpp\n    NotoMono.cpp\n    MetaforceIcon.cpp\n    )\ntarget_link_libraries(imgui_support PUBLIC aurora::core imgui ZLIB::ZLIB)\ntarget_include_directories(imgui_support PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})\ntarget_include_directories(imgui_support PRIVATE ${CMAKE_SOURCE_DIR})\n\nbintoc_compress(NotoMono.cpp NotoMono-Regular.ttf NOTO_MONO_FONT)\nbintoc(MetaforceIcon.cpp ../Runtime/platforms/freedesktop/256x256/apps/metaforce.png METAFORCE_ICON)\n"
  },
  {
    "path": "imgui/ImGuiEngine.cpp",
    "content": "#include \"ImGuiEngine.hpp\"\n\n#include <aurora/imgui.h>\n#include <cmath>\n\n#include \"Runtime/Streams/CMemoryInStream.hpp\"\n#include \"Runtime/Streams/CZipInputStream.hpp\"\n\n#define STBI_NO_STDIO\n#define STB_IMAGE_STATIC\n#define STB_IMAGE_IMPLEMENTATION\n#define STBI_ONLY_PNG\n#include \"stb_image.h\"\n\n#ifdef IMGUI_ENABLE_FREETYPE\n#include \"misc/freetype/imgui_freetype.h\"\n#endif\n\nextern \"C\" const uint8_t NOTO_MONO_FONT[];\nextern \"C\" const size_t NOTO_MONO_FONT_SZ;\nextern \"C\" const size_t NOTO_MONO_FONT_DECOMPRESSED_SZ;\nextern \"C\" const uint8_t METAFORCE_ICON[];\nextern \"C\" const size_t METAFORCE_ICON_SZ;\n\nnamespace metaforce {\nImFont* ImGuiEngine::fontNormal;\nImFont* ImGuiEngine::fontLarge;\nImTextureID ImGuiEngine::metaforceIcon;\n\nvoid ImGuiEngine_Initialize(float scale) {\n  ImGui::GetCurrentContext();\n  ImGuiIO& io = ImGui::GetIO();\n  io.Fonts->Clear();\n  io.FontGlobalScale = scale > 0.0f ? 1.0f / scale : 1.0f;\n\n  auto* fontData = ImGui::MemAlloc(NOTO_MONO_FONT_DECOMPRESSED_SZ);\n  {\n    auto stream = std::make_unique<CMemoryInStream>(static_cast<const u8*>(NOTO_MONO_FONT), NOTO_MONO_FONT_SZ,\n                                                    CMemoryInStream::EOwnerShip::NotOwned);\n    CZipInputStream zipInputStream{std::move(stream)};\n    zipInputStream.Get(static_cast<uint8_t*>(fontData), NOTO_MONO_FONT_DECOMPRESSED_SZ);\n  }\n\n  ImFontConfig fontConfig{};\n  fontConfig.FontData = fontData;\n  fontConfig.FontDataSize = int(NOTO_MONO_FONT_DECOMPRESSED_SZ);\n  fontConfig.SizePixels = std::floor(15.f * scale);\n  snprintf(static_cast<char*>(fontConfig.Name), sizeof(fontConfig.Name), \"Noto Mono Regular, %dpx\",\n           static_cast<int>(fontConfig.SizePixels));\n  ImGuiEngine::fontNormal = io.Fonts->AddFont(&fontConfig);\n\n  fontConfig.FontDataOwnedByAtlas = false; // first one took ownership\n  fontConfig.SizePixels = std::floor(26.f * scale);\n#ifdef IMGUI_ENABLE_FREETYPE\n  fontConfig.FontBuilderFlags |= ImGuiFreeTypeBuilderFlags_Bold;\n  snprintf(static_cast<char*>(fontConfig.Name), sizeof(fontConfig.Name), \"Noto Mono Bold, %dpx\",\n           static_cast<int>(fontConfig.SizePixels));\n#else\n  snprintf(static_cast<char*>(fontConfig.Name), sizeof(fontConfig.Name), \"Noto Mono Regular, %dpx\",\n           static_cast<int>(fontConfig.SizePixels));\n#endif\n  ImGuiEngine::fontLarge = io.Fonts->AddFont(&fontConfig);\n\n  auto& style = ImGui::GetStyle();\n  style = {}; // Reset sizes\n  style.WindowPadding = ImVec2(15, 15);\n  style.WindowRounding = 5.0f;\n  style.FrameBorderSize = 1.f;\n  style.FramePadding = ImVec2(5, 5);\n  style.FrameRounding = 4.0f;\n  style.ItemSpacing = ImVec2(12, 8);\n  style.ItemInnerSpacing = ImVec2(8, 6);\n  style.IndentSpacing = 25.0f;\n  style.ScrollbarSize = 15.0f;\n  style.ScrollbarRounding = 9.0f;\n  style.GrabMinSize = 5.0f;\n  style.GrabRounding = 3.0f;\n  style.PopupBorderSize = 1.f;\n  style.PopupRounding = 7.0;\n  style.TabBorderSize = 1.f;\n  style.TabRounding = 3.f;\n\n  auto* colors = style.Colors;\n  colors[ImGuiCol_Text] = ImVec4(0.95f, 0.96f, 0.98f, 1.00f);\n  colors[ImGuiCol_TextDisabled] = ImVec4(0.36f, 0.42f, 0.47f, 1.00f);\n  colors[ImGuiCol_WindowBg] = ImVec4(0.11f, 0.15f, 0.17f, 1.00f);\n  colors[ImGuiCol_ChildBg] = ImVec4(0.15f, 0.18f, 0.22f, 1.00f);\n  colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.94f);\n  colors[ImGuiCol_Border] = ImVec4(0.08f, 0.10f, 0.12f, 1.00f);\n  colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);\n  colors[ImGuiCol_FrameBg] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f);\n  colors[ImGuiCol_FrameBgHovered] = ImVec4(0.12f, 0.20f, 0.28f, 1.00f);\n  colors[ImGuiCol_FrameBgActive] = ImVec4(0.09f, 0.12f, 0.14f, 1.00f);\n  colors[ImGuiCol_TitleBg] = ImVec4(0.09f, 0.12f, 0.14f, 0.65f);\n  colors[ImGuiCol_TitleBgActive] = ImVec4(0.08f, 0.10f, 0.12f, 1.00f);\n  colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 0.51f);\n  colors[ImGuiCol_MenuBarBg] = ImVec4(0.15f, 0.18f, 0.22f, 1.00f);\n  colors[ImGuiCol_ScrollbarBg] = ImVec4(0.02f, 0.02f, 0.02f, 0.39f);\n  colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f);\n  colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.18f, 0.22f, 0.25f, 1.00f);\n  colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.09f, 0.21f, 0.31f, 1.00f);\n  colors[ImGuiCol_CheckMark] = ImVec4(0.28f, 0.56f, 1.00f, 1.00f);\n  colors[ImGuiCol_SliderGrab] = ImVec4(0.28f, 0.56f, 1.00f, 1.00f);\n  colors[ImGuiCol_SliderGrabActive] = ImVec4(0.37f, 0.61f, 1.00f, 1.00f);\n  colors[ImGuiCol_Button] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f);\n  colors[ImGuiCol_ButtonHovered] = ImVec4(0.28f, 0.56f, 1.00f, 1.00f);\n  colors[ImGuiCol_ButtonActive] = ImVec4(0.06f, 0.53f, 0.98f, 1.00f);\n  colors[ImGuiCol_Header] = ImVec4(0.20f, 0.25f, 0.29f, 0.55f);\n  colors[ImGuiCol_HeaderHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.80f);\n  colors[ImGuiCol_HeaderActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);\n  colors[ImGuiCol_Separator] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f);\n  colors[ImGuiCol_SeparatorHovered] = ImVec4(0.10f, 0.40f, 0.75f, 0.78f);\n  colors[ImGuiCol_SeparatorActive] = ImVec4(0.10f, 0.40f, 0.75f, 1.00f);\n  colors[ImGuiCol_ResizeGrip] = ImVec4(0.26f, 0.59f, 0.98f, 0.25f);\n  colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f);\n  colors[ImGuiCol_ResizeGripActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f);\n  colors[ImGuiCol_Tab] = ImVec4(0.11f, 0.15f, 0.17f, 1.00f);\n  colors[ImGuiCol_TabHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.80f);\n  colors[ImGuiCol_TabActive] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f);\n  colors[ImGuiCol_TabUnfocused] = ImVec4(0.11f, 0.15f, 0.17f, 1.00f);\n  colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.11f, 0.15f, 0.17f, 1.00f);\n  colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f);\n  colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f);\n  colors[ImGuiCol_PlotHistogram] = ImVec4(0.06f, 0.53f, 0.98f, 1.00f);\n  colors[ImGuiCol_PlotHistogramHovered] = ImVec4(0.28f, 0.56f, 1.00f, 1.00f);\n  colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f);\n  colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f);\n  colors[ImGuiCol_NavHighlight] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);\n  colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);\n  colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f);\n  colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f);\n}\n\nIcon GetIcon() {\n  int iconWidth = 0;\n  int iconHeight = 0;\n  auto* data =\n      stbi_load_from_memory(METAFORCE_ICON, static_cast<int>(METAFORCE_ICON_SZ), &iconWidth, &iconHeight, nullptr, 4);\n  size_t size = static_cast<size_t>(iconWidth) * static_cast<size_t>(iconHeight) * 4;\n  auto ptr = std::make_unique<u8[]>(size);\n  memcpy(ptr.get(), data, size);\n  stbi_image_free(data);\n  return Icon{\n      std::move(ptr),\n      size,\n      static_cast<uint32_t>(iconWidth),\n      static_cast<uint32_t>(iconHeight),\n  };\n}\n\nvoid ImGuiEngine_AddTextures() {\n  auto icon = GetIcon();\n  ImGuiEngine::metaforceIcon = aurora_imgui_add_texture(icon.width, icon.height, icon.data.get());\n}\n} // namespace metaforce\n"
  },
  {
    "path": "imgui/ImGuiEngine.hpp",
    "content": "#pragma once\n\n#include \"imgui.h\"\n#include \"misc/cpp/imgui_stdlib.h\"\n#include <memory>\n\nnamespace metaforce {\nclass ImGuiEngine {\npublic:\n  static ImFont* fontNormal;\n  static ImFont* fontLarge;\n  static ImTextureID metaforceIcon;\n};\n\nvoid ImGuiEngine_Initialize(float scale);\nvoid ImGuiEngine_AddTextures();\n\nstruct Icon {\n  std::unique_ptr<uint8_t[]> data;\n  size_t size;\n  uint32_t width;\n  uint32_t height;\n};\nIcon GetIcon();\n} // namespace metaforce\n"
  },
  {
    "path": "imgui/magic_enum.hpp",
    "content": "//  __  __             _        ______                          _____\n// |  \\/  |           (_)      |  ____|                        / ____|_     _\n// | \\  / | __ _  __ _ _  ___  | |__   _ __  _   _ _ __ ___   | |   _| |_ _| |_\n// | |\\/| |/ _` |/ _` | |/ __| |  __| | '_ \\| | | | '_ ` _ \\  | |  |_   _|_   _|\n// | |  | | (_| | (_| | | (__  | |____| | | | |_| | | | | | | | |____|_|   |_|\n// |_|  |_|\\__,_|\\__, |_|\\___| |______|_| |_|\\__,_|_| |_| |_|  \\_____|\n//                __/ | https://github.com/Neargye/magic_enum\n//               |___/  version 0.7.2\n//\n// Licensed under the MIT License <http://opensource.org/licenses/MIT>.\n// SPDX-License-Identifier: MIT\n// Copyright (c) 2019 - 2021 Daniil Goncharov <neargye@gmail.com>.\n//\n// Permission is hereby  granted, free of charge, to any  person obtaining a copy\n// of this software and associated  documentation files (the \"Software\"), to deal\n// in the Software  without restriction, including without  limitation the rights\n// to  use, copy,  modify, merge,  publish, distribute,  sublicense, and/or  sell\n// copies  of  the Software,  and  to  permit persons  to  whom  the Software  is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE  IS PROVIDED \"AS  IS\", WITHOUT WARRANTY  OF ANY KIND,  EXPRESS OR\n// IMPLIED,  INCLUDING BUT  NOT  LIMITED TO  THE  WARRANTIES OF  MERCHANTABILITY,\n// FITNESS FOR  A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT  SHALL THE\n// AUTHORS  OR COPYRIGHT  HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER\n// LIABILITY, WHETHER IN AN ACTION OF  CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n#ifndef NEARGYE_MAGIC_ENUM_HPP\n#define NEARGYE_MAGIC_ENUM_HPP\n\n#define MAGIC_ENUM_VERSION_MAJOR 0\n#define MAGIC_ENUM_VERSION_MINOR 7\n#define MAGIC_ENUM_VERSION_PATCH 2\n\n#include <array>\n#include <cassert>\n#include <cstdint>\n#include <cstddef>\n#include <iosfwd>\n#include <limits>\n#include <type_traits>\n#include <utility>\n\n#if !defined(MAGIC_ENUM_USING_ALIAS_OPTIONAL)\n#include <optional>\n#endif\n#if !defined(MAGIC_ENUM_USING_ALIAS_STRING)\n#include <string>\n#endif\n#if !defined(MAGIC_ENUM_USING_ALIAS_STRING_VIEW)\n#include <string_view>\n#endif\n\n#if defined(__clang__)\n#  pragma clang diagnostic push\n#elif defined(__GNUC__)\n#  pragma GCC diagnostic push\n#  pragma GCC diagnostic ignored \"-Wmaybe-uninitialized\" // May be used uninitialized 'return {};'.\n#elif defined(_MSC_VER)\n#  pragma warning(push)\n#  pragma warning(disable : 26495) // Variable 'static_string<N>::chars_' is uninitialized.\n#  pragma warning(disable : 28020) // Arithmetic overflow: Using operator '-' on a 4 byte value and then casting the result to a 8 byte value.\n#  pragma warning(disable : 26451) // The expression '0<=_Param_(1)&&_Param_(1)<=1-1' is not true at this call.\n#endif\n\n// Checks magic_enum compiler compatibility.\n#if defined(__clang__) && __clang_major__ >= 5 || defined(__GNUC__) && __GNUC__ >= 9 || defined(_MSC_VER) && _MSC_VER >= 1910\n#  undef  MAGIC_ENUM_SUPPORTED\n#  define MAGIC_ENUM_SUPPORTED 1\n#endif\n\n// Checks magic_enum compiler aliases compatibility.\n#if defined(__clang__) && __clang_major__ >= 5 || defined(__GNUC__) && __GNUC__ >= 9 || defined(_MSC_VER) && _MSC_VER >= 1920\n#  undef  MAGIC_ENUM_SUPPORTED_ALIASES\n#  define MAGIC_ENUM_SUPPORTED_ALIASES 1\n#endif\n\n// Enum value must be greater or equals than MAGIC_ENUM_RANGE_MIN. By default MAGIC_ENUM_RANGE_MIN = -128.\n// If need another min range for all enum types by default, redefine the macro MAGIC_ENUM_RANGE_MIN.\n#if !defined(MAGIC_ENUM_RANGE_MIN)\n#  define MAGIC_ENUM_RANGE_MIN -128\n#endif\n\n// Enum value must be less or equals than MAGIC_ENUM_RANGE_MAX. By default MAGIC_ENUM_RANGE_MAX = 128.\n// If need another max range for all enum types by default, redefine the macro MAGIC_ENUM_RANGE_MAX.\n#if !defined(MAGIC_ENUM_RANGE_MAX)\n#  define MAGIC_ENUM_RANGE_MAX 128\n#endif\n\nnamespace magic_enum {\n\n// If need another optional type, define the macro MAGIC_ENUM_USING_ALIAS_OPTIONAL.\n#if defined(MAGIC_ENUM_USING_ALIAS_OPTIONAL)\nMAGIC_ENUM_USING_ALIAS_OPTIONAL\n#else\nusing std::optional;\n#endif\n\n// If need another string_view type, define the macro MAGIC_ENUM_USING_ALIAS_STRING_VIEW.\n#if defined(MAGIC_ENUM_USING_ALIAS_STRING_VIEW)\nMAGIC_ENUM_USING_ALIAS_STRING_VIEW\n#else\nusing std::string_view;\n#endif\n\n// If need another string type, define the macro MAGIC_ENUM_USING_ALIAS_STRING.\n#if defined(MAGIC_ENUM_USING_ALIAS_STRING)\nMAGIC_ENUM_USING_ALIAS_STRING\n#else\nusing std::string;\n#endif\n\nnamespace customize {\n\n// Enum value must be in range [MAGIC_ENUM_RANGE_MIN, MAGIC_ENUM_RANGE_MAX]. By default MAGIC_ENUM_RANGE_MIN = -128, MAGIC_ENUM_RANGE_MAX = 128.\n// If need another range for all enum types by default, redefine the macro MAGIC_ENUM_RANGE_MIN and MAGIC_ENUM_RANGE_MAX.\n// If need another range for specific enum type, add specialization enum_range for necessary enum type.\ntemplate <typename E>\nstruct enum_range {\n  static_assert(std::is_enum_v<E>, \"magic_enum::customize::enum_range requires enum type.\");\n  inline static constexpr int min = MAGIC_ENUM_RANGE_MIN;\n  inline static constexpr int max = MAGIC_ENUM_RANGE_MAX;\n  static_assert(max > min, \"magic_enum::customize::enum_range requires max > min.\");\n};\n\nstatic_assert(MAGIC_ENUM_RANGE_MIN <= 0, \"MAGIC_ENUM_RANGE_MIN must be less or equals than 0.\");\nstatic_assert(MAGIC_ENUM_RANGE_MIN > (std::numeric_limits<std::int16_t>::min)(), \"MAGIC_ENUM_RANGE_MIN must be greater than INT16_MIN.\");\n\nstatic_assert(MAGIC_ENUM_RANGE_MAX > 0, \"MAGIC_ENUM_RANGE_MAX must be greater than 0.\");\nstatic_assert(MAGIC_ENUM_RANGE_MAX < (std::numeric_limits<std::int16_t>::max)(), \"MAGIC_ENUM_RANGE_MAX must be less than INT16_MAX.\");\n\nstatic_assert(MAGIC_ENUM_RANGE_MAX > MAGIC_ENUM_RANGE_MIN, \"MAGIC_ENUM_RANGE_MAX must be greater than MAGIC_ENUM_RANGE_MIN.\");\n\n// If need custom names for enum, add specialization enum_name for necessary enum type.\ntemplate <typename E>\nconstexpr string_view enum_name(E) noexcept {\n  static_assert(std::is_enum_v<E>, \"magic_enum::customize::enum_name requires enum type.\");\n\n  return {};\n}\n\n} // namespace magic_enum::customize\n\nnamespace detail {\n\ntemplate <typename T>\nstruct supported\n#if defined(MAGIC_ENUM_SUPPORTED) && MAGIC_ENUM_SUPPORTED || defined(MAGIC_ENUM_NO_CHECK_SUPPORT)\n    : std::true_type {};\n#else\n    : std::false_type {};\n#endif\n\nstruct char_equal_to {\n  constexpr bool operator()(char lhs, char rhs) const noexcept {\n    return lhs == rhs;\n  }\n};\n\ntemplate <std::size_t N>\nclass static_string {\n public:\n  constexpr explicit static_string(string_view str) noexcept : static_string{str, std::make_index_sequence<N>{}} {\n    assert(str.size() == N);\n  }\n\n  constexpr const char* data() const noexcept { return chars_; }\n\n  constexpr std::size_t size() const noexcept { return N; }\n\n  constexpr operator string_view() const noexcept { return {data(), size()}; }\n\n private:\n  template <std::size_t... I>\n  constexpr static_string(string_view str, std::index_sequence<I...>) noexcept : chars_{str[I]..., '\\0'} {}\n\n  char chars_[N + 1];\n};\n\ntemplate <>\nclass static_string<0> {\n public:\n  constexpr explicit static_string(string_view) noexcept {}\n\n  constexpr const char* data() const noexcept { return nullptr; }\n\n  constexpr std::size_t size() const noexcept { return 0; }\n\n  constexpr operator string_view() const noexcept { return {}; }\n};\n\nconstexpr string_view pretty_name(string_view name) noexcept {\n  for (std::size_t i = name.size(); i > 0; --i) {\n    if (!((name[i - 1] >= '0' && name[i - 1] <= '9') ||\n          (name[i - 1] >= 'a' && name[i - 1] <= 'z') ||\n          (name[i - 1] >= 'A' && name[i - 1] <= 'Z') ||\n          (name[i - 1] == '_'))) {\n      name.remove_prefix(i);\n      break;\n    }\n  }\n\n  if (name.size() > 0 && ((name.front() >= 'a' && name.front() <= 'z') ||\n                          (name.front() >= 'A' && name.front() <= 'Z') ||\n                          (name.front() == '_'))) {\n    return name;\n  }\n\n  return {}; // Invalid name.\n}\n\nconstexpr std::size_t find(string_view str, char c) noexcept {\n#if defined(__clang__) && __clang_major__ < 9 && defined(__GLIBCXX__) || defined(_MSC_VER) && _MSC_VER < 1920 && !defined(__clang__)\n// https://stackoverflow.com/questions/56484834/constexpr-stdstring-viewfind-last-of-doesnt-work-on-clang-8-with-libstdc\n// https://developercommunity.visualstudio.com/content/problem/360432/vs20178-regression-c-failed-in-test.html\n  constexpr bool workaround = true;\n#else\n  constexpr bool workaround = false;\n#endif\n  if constexpr (workaround) {\n    for (std::size_t i = 0; i < str.size(); ++i) {\n      if (str[i] == c) {\n        return i;\n      }\n    }\n\n    return string_view::npos;\n  } else {\n    return str.find_first_of(c);\n  }\n}\n\ntemplate <typename T, std::size_t N, std::size_t... I>\nconstexpr std::array<std::remove_cv_t<T>, N> to_array(T (&a)[N], std::index_sequence<I...>) {\n  return {{a[I]...}};\n}\n\ntemplate <typename BinaryPredicate>\nconstexpr bool cmp_equal(string_view lhs, string_view rhs, BinaryPredicate&& p) noexcept(std::is_nothrow_invocable_r_v<bool, BinaryPredicate, char, char>) {\n#if defined(_MSC_VER) && _MSC_VER < 1920 && !defined(__clang__)\n  // https://developercommunity.visualstudio.com/content/problem/360432/vs20178-regression-c-failed-in-test.html\n  // https://developercommunity.visualstudio.com/content/problem/232218/c-constexpr-string-view.html\n  constexpr bool workaround = true;\n#else\n  constexpr bool workaround = false;\n#endif\n  constexpr bool default_predicate = std::is_same_v<std::decay_t<BinaryPredicate>, char_equal_to>;\n\n  if constexpr (default_predicate && !workaround) {\n    static_cast<void>(p);\n    return lhs == rhs;\n  } else {\n    if (lhs.size() != rhs.size()) {\n      return false;\n    }\n\n    const auto size = lhs.size();\n    for (std::size_t i = 0; i < size; ++i) {\n      if (!p(lhs[i], rhs[i])) {\n        return false;\n      }\n    }\n\n    return true;\n  }\n}\n\ntemplate <typename L, typename R>\nconstexpr bool cmp_less(L lhs, R rhs) noexcept {\n  static_assert(std::is_integral_v<L> && std::is_integral_v<R>, \"magic_enum::detail::cmp_less requires integral type.\");\n\n  if constexpr (std::is_signed_v<L> == std::is_signed_v<R>) {\n    // If same signedness (both signed or both unsigned).\n    return lhs < rhs;\n  } else if constexpr (std::is_signed_v<R>) {\n    // If 'right' is negative, then result is 'false', otherwise cast & compare.\n    return rhs > 0 && lhs < static_cast<std::make_unsigned_t<R>>(rhs);\n  } else {\n    // If 'left' is negative, then result is 'true', otherwise cast & compare.\n    return lhs < 0 || static_cast<std::make_unsigned_t<L>>(lhs) < rhs;\n  }\n}\n\ntemplate <typename I>\nconstexpr I log2(I value) noexcept {\n  static_assert(std::is_integral_v<I>, \"magic_enum::detail::log2 requires integral type.\");\n\n  auto ret = I{0};\n  for (; value > I{1}; value >>= I{1}, ++ret) {}\n\n  return ret;\n}\n\ntemplate <typename I>\nconstexpr bool is_pow2(I x) noexcept {\n  static_assert(std::is_integral_v<I>, \"magic_enum::detail::is_pow2 requires integral type.\");\n\n  return x != 0 && (x & (x - 1)) == 0;\n}\n\ntemplate <typename T>\ninline constexpr bool is_enum_v = std::is_enum_v<T> && std::is_same_v<T, std::decay_t<T>>;\n\ntemplate <typename E>\nconstexpr auto n() noexcept {\n  static_assert(is_enum_v<E>, \"magic_enum::detail::n requires enum type.\");\n#if defined(MAGIC_ENUM_SUPPORTED) && MAGIC_ENUM_SUPPORTED\n#  if defined(__clang__)\n  constexpr string_view name{__PRETTY_FUNCTION__ + 34, sizeof(__PRETTY_FUNCTION__) - 36};\n#  elif defined(__GNUC__)\n  constexpr string_view name{__PRETTY_FUNCTION__ + 49, sizeof(__PRETTY_FUNCTION__) - 51};\n#  elif defined(_MSC_VER)\n  constexpr string_view name{__FUNCSIG__ + 40, sizeof(__FUNCSIG__) - 57};\n#  endif\n  return static_string<name.size()>{name};\n#else\n  return string_view{}; // Unsupported compiler.\n#endif\n}\n\ntemplate <typename E>\ninline constexpr auto type_name_v = n<E>();\n\ntemplate <typename E, E V>\nconstexpr auto n() noexcept {\n  static_assert(is_enum_v<E>, \"magic_enum::detail::n requires enum type.\");\n  constexpr auto custom_name = customize::enum_name<E>(V);\n\n  if constexpr (custom_name.empty()) {\n    static_cast<void>(custom_name);\n#if defined(MAGIC_ENUM_SUPPORTED) && MAGIC_ENUM_SUPPORTED\n#  if defined(__clang__) || defined(__GNUC__)\n    constexpr auto name = pretty_name({__PRETTY_FUNCTION__, sizeof(__PRETTY_FUNCTION__) - 2});\n#  elif defined(_MSC_VER)\n    constexpr auto name = pretty_name({__FUNCSIG__, sizeof(__FUNCSIG__) - 17});\n#  endif\n    return static_string<name.size()>{name};\n#else\n    return string_view{}; // Unsupported compiler.\n#endif\n  } else {\n    return static_string<custom_name.size()>{custom_name};\n  }\n}\n\ntemplate <typename E, E V>\ninline constexpr auto enum_name_v = n<E, V>();\n\ntemplate <typename E, auto V>\nconstexpr bool is_valid() noexcept {\n  static_assert(is_enum_v<E>, \"magic_enum::detail::is_valid requires enum type.\");\n\n  return n<E, static_cast<E>(V)>().size() != 0;\n}\n\ntemplate <typename E, int O, bool IsFlags = false, typename U = std::underlying_type_t<E>>\nconstexpr E value(std::size_t i) noexcept {\n  static_assert(is_enum_v<E>, \"magic_enum::detail::value requires enum type.\");\n\n  if constexpr (IsFlags) {\n    return static_cast<E>(U{1} << static_cast<U>(static_cast<int>(i) + O));\n  } else {\n    return static_cast<E>(static_cast<int>(i) + O);\n  }\n}\n\ntemplate <typename E, bool IsFlags, typename U = std::underlying_type_t<E>>\nconstexpr int reflected_min() noexcept {\n  static_assert(is_enum_v<E>, \"magic_enum::detail::reflected_min requires enum type.\");\n\n  if constexpr (IsFlags) {\n    return 0;\n  } else {\n    constexpr auto lhs = customize::enum_range<E>::min;\n    static_assert(lhs > (std::numeric_limits<std::int16_t>::min)(), \"magic_enum::enum_range requires min must be greater than INT16_MIN.\");\n    constexpr auto rhs = (std::numeric_limits<U>::min)();\n\n    if constexpr (cmp_less(lhs, rhs)) {\n      return rhs;\n    } else {\n      static_assert(!is_valid<E, value<E, lhs - 1, IsFlags>(0)>(), \"magic_enum::enum_range detects enum value smaller than min range size.\");\n      return lhs;\n    }\n  }\n}\n\ntemplate <typename E, bool IsFlags, typename U = std::underlying_type_t<E>>\nconstexpr int reflected_max() noexcept {\n  static_assert(is_enum_v<E>, \"magic_enum::detail::reflected_max requires enum type.\");\n\n  if constexpr (IsFlags) {\n    return std::numeric_limits<U>::digits - 1;\n  } else {\n    constexpr auto lhs = customize::enum_range<E>::max;\n    static_assert(lhs < (std::numeric_limits<std::int16_t>::max)(), \"magic_enum::enum_range requires max must be less than INT16_MAX.\");\n    constexpr auto rhs = (std::numeric_limits<U>::max)();\n\n    if constexpr (cmp_less(lhs, rhs)) {\n      static_assert(!is_valid<E, value<E, lhs + 1, IsFlags>(0)>(), \"magic_enum::enum_range detects enum value larger than max range size.\");\n      return lhs;\n    } else {\n      return rhs;\n    }\n  }\n}\n\ntemplate <typename E, bool IsFlags = false>\ninline constexpr auto reflected_min_v = reflected_min<E, IsFlags>();\n\ntemplate <typename E, bool IsFlags = false>\ninline constexpr auto reflected_max_v = reflected_max<E, IsFlags>();\n\ntemplate <std::size_t N>\nconstexpr std::size_t values_count(const bool (&valid)[N]) noexcept {\n  auto count = std::size_t{0};\n  for (std::size_t i = 0; i < N; ++i) {\n    if (valid[i]) {\n      ++count;\n    }\n  }\n\n  return count;\n}\n\ntemplate <typename E, bool IsFlags, int Min, std::size_t... I>\nconstexpr auto values(std::index_sequence<I...>) noexcept {\n  static_assert(is_enum_v<E>, \"magic_enum::detail::values requires enum type.\");\n  constexpr bool valid[sizeof...(I)] = {is_valid<E, value<E, Min, IsFlags>(I)>()...};\n  constexpr std::size_t count = values_count(valid);\n\n  if constexpr (count > 0) {\n    E values[count] = {};\n    for (std::size_t i = 0, v = 0; v < count; ++i) {\n      if (valid[i]) {\n        values[v++] = value<E, Min, IsFlags>(i);\n      }\n    }\n\n    return to_array(values, std::make_index_sequence<count>{});\n  } else {\n    return std::array<E, 0>{};\n  }\n}\n\ntemplate <typename E, bool IsFlags, typename U = std::underlying_type_t<E>>\nconstexpr auto values() noexcept {\n  static_assert(is_enum_v<E>, \"magic_enum::detail::values requires enum type.\");\n  constexpr auto min = reflected_min_v<E, IsFlags>;\n  constexpr auto max = reflected_max_v<E, IsFlags>;\n  constexpr auto range_size = max - min + 1;\n  static_assert(range_size > 0, \"magic_enum::enum_range requires valid size.\");\n  static_assert(range_size < (std::numeric_limits<std::uint16_t>::max)(), \"magic_enum::enum_range requires valid size.\");\n\n  return values<E, IsFlags, reflected_min_v<E, IsFlags>>(std::make_index_sequence<range_size>{});\n}\n\ntemplate <typename E, bool IsFlags = false>\ninline constexpr auto values_v = values<E, IsFlags>();\n\ntemplate <typename E, bool IsFlags = false, typename D = std::decay_t<E>>\nusing values_t = decltype((values_v<D, IsFlags>));\n\ntemplate <typename E, bool IsFlags = false>\ninline constexpr auto count_v = values_v<E, IsFlags>.size();\n\ntemplate <typename E, bool IsFlags = false, typename U = std::underlying_type_t<E>>\ninline constexpr auto min_v = (count_v<E, IsFlags> > 0) ? static_cast<U>(values_v<E, IsFlags>.front()) : U{0};\n\ntemplate <typename E, bool IsFlags = false, typename U = std::underlying_type_t<E>>\ninline constexpr auto max_v = (count_v<E, IsFlags> > 0) ? static_cast<U>(values_v<E, IsFlags>.back()) : U{0};\n\ntemplate <typename E, bool IsFlags, typename U = std::underlying_type_t<E>>\nconstexpr std::size_t range_size() noexcept {\n  static_assert(is_enum_v<E>, \"magic_enum::detail::range_size requires enum type.\");\n  constexpr auto max = IsFlags ? log2(max_v<E, IsFlags>) : max_v<E, IsFlags>;\n  constexpr auto min = IsFlags ? log2(min_v<E, IsFlags>) : min_v<E, IsFlags>;\n  constexpr auto range_size = max - min + U{1};\n  static_assert(range_size > 0, \"magic_enum::enum_range requires valid size.\");\n  static_assert(range_size < (std::numeric_limits<std::uint16_t>::max)(), \"magic_enum::enum_range requires valid size.\");\n\n  return static_cast<std::size_t>(range_size);\n}\n\ntemplate <typename E, bool IsFlags = false>\ninline constexpr auto range_size_v = range_size<E, IsFlags>();\n\ntemplate <typename E, bool IsFlags = false>\nusing index_t = std::conditional_t<range_size_v<E, IsFlags> < (std::numeric_limits<std::uint8_t>::max)(), std::uint8_t, std::uint16_t>;\n\ntemplate <typename E, bool IsFlags = false>\ninline constexpr auto invalid_index_v = (std::numeric_limits<index_t<E, IsFlags>>::max)();\n\ntemplate <typename E, bool IsFlags, std::size_t... I>\nconstexpr auto indexes(std::index_sequence<I...>) noexcept {\n  static_assert(is_enum_v<E>, \"magic_enum::detail::indexes requires enum type.\");\n  constexpr auto min = IsFlags ? log2(min_v<E, IsFlags>) : min_v<E, IsFlags>;\n  [[maybe_unused]] auto i = index_t<E, IsFlags>{0};\n\n  return std::array<decltype(i), sizeof...(I)>{{(is_valid<E, value<E, min, IsFlags>(I)>() ? i++ : invalid_index_v<E, IsFlags>)...}};\n}\n\ntemplate <typename E, bool IsFlags = false>\ninline constexpr auto indexes_v = indexes<E, IsFlags>(std::make_index_sequence<range_size_v<E, IsFlags>>{});\n\ntemplate <typename E, bool IsFlags, std::size_t... I>\nconstexpr auto names(std::index_sequence<I...>) noexcept {\n  static_assert(is_enum_v<E>, \"magic_enum::detail::names requires enum type.\");\n\n  return std::array<string_view, sizeof...(I)>{{enum_name_v<E, values_v<E, IsFlags>[I]>...}};\n}\n\ntemplate <typename E, bool IsFlags = false>\ninline constexpr auto names_v = names<E, IsFlags>(std::make_index_sequence<count_v<E, IsFlags>>{});\n\ntemplate <typename E, bool IsFlags = false, typename D = std::decay_t<E>>\nusing names_t = decltype((names_v<D, IsFlags>));\n\ntemplate <typename E, bool IsFlags, std::size_t... I>\nconstexpr auto entries(std::index_sequence<I...>) noexcept {\n  static_assert(is_enum_v<E>, \"magic_enum::detail::entries requires enum type.\");\n\n  return std::array<std::pair<E, string_view>, sizeof...(I)>{{{values_v<E, IsFlags>[I], enum_name_v<E, values_v<E, IsFlags>[I]>}...}};\n}\n\ntemplate <typename E, bool IsFlags = false>\ninline constexpr auto entries_v = entries<E, IsFlags>(std::make_index_sequence<count_v<E, IsFlags>>{});\n\ntemplate <typename E, bool IsFlags = false, typename D = std::decay_t<E>>\nusing entries_t = decltype((entries_v<D, IsFlags>));\n\ntemplate <typename E, bool IsFlags, typename U = std::underlying_type_t<E>>\nconstexpr bool is_sparse() noexcept {\n  static_assert(is_enum_v<E>, \"magic_enum::detail::is_sparse requires enum type.\");\n\n  return range_size_v<E, IsFlags> != count_v<E, IsFlags>;\n}\n\ntemplate <typename E, bool IsFlags = false>\ninline constexpr bool is_sparse_v = is_sparse<E, IsFlags>();\n\ntemplate <typename E, typename U = std::underlying_type_t<E>>\nconstexpr std::size_t undex(U value) noexcept {\n  static_assert(is_enum_v<E>, \"magic_enum::detail::undex requires enum type.\");\n\n  if (const auto i = static_cast<std::size_t>(value - min_v<E>); value >= min_v<E> && value <= max_v<E>) {\n    if constexpr (is_sparse_v<E>) {\n      if (const auto idx = indexes_v<E>[i]; idx != invalid_index_v<E>) {\n        return idx;\n      }\n    } else {\n      return i;\n    }\n  }\n\n  return invalid_index_v<E>; // Value out of range.\n}\n\ntemplate <typename E, typename U = std::underlying_type_t<E>>\nconstexpr std::size_t endex(E value) noexcept {\n  static_assert(is_enum_v<E>, \"magic_enum::detail::endex requires enum type.\");\n\n  return undex<E>(static_cast<U>(value));\n}\n\ntemplate <typename E, typename U = std::underlying_type_t<E>>\nconstexpr U value_ors() noexcept {\n  static_assert(is_enum_v<E>, \"magic_enum::detail::endex requires enum type.\");\n\n  auto value = U{0};\n  for (std::size_t i = 0; i < count_v<E, true>; ++i) {\n    value |= static_cast<U>(values_v<E, true>[i]);\n  }\n\n  return value;\n}\n\ntemplate <bool, typename T, typename R>\nstruct enable_if_enum {};\n\ntemplate <typename T, typename R>\nstruct enable_if_enum<true, T, R> {\n  using type = R;\n  using D = std::decay_t<T>;\n  static_assert(supported<D>::value, \"magic_enum unsupported compiler (https://github.com/Neargye/magic_enum#compiler-compatibility).\");\n};\n\ntemplate <typename T, typename R = void>\nusing enable_if_enum_t = std::enable_if_t<std::is_enum_v<std::decay_t<T>>, R>;\n\ntemplate <typename T, typename Enable = std::enable_if_t<std::is_enum_v<std::decay_t<T>>>>\nusing enum_concept = T;\n\ntemplate <typename T, bool = std::is_enum_v<T>>\nstruct is_scoped_enum : std::false_type {};\n\ntemplate <typename T>\nstruct is_scoped_enum<T, true> : std::bool_constant<!std::is_convertible_v<T, std::underlying_type_t<T>>> {};\n\ntemplate <typename T, bool = std::is_enum_v<T>>\nstruct is_unscoped_enum : std::false_type {};\n\ntemplate <typename T>\nstruct is_unscoped_enum<T, true> : std::bool_constant<std::is_convertible_v<T, std::underlying_type_t<T>>> {};\n\ntemplate <typename T, bool = std::is_enum_v<std::decay_t<T>>>\nstruct underlying_type {};\n\ntemplate <typename T>\nstruct underlying_type<T, true> : std::underlying_type<std::decay_t<T>> {};\n\n} // namespace magic_enum::detail\n\n// Checks is magic_enum supported compiler.\ninline constexpr bool is_magic_enum_supported = detail::supported<void>::value;\n\ntemplate <typename T>\nusing Enum = detail::enum_concept<T>;\n\n// Checks whether T is an Unscoped enumeration type.\n// Provides the member constant value which is equal to true, if T is an [Unscoped enumeration](https://en.cppreference.com/w/cpp/language/enum#Unscoped_enumeration) type. Otherwise, value is equal to false.\ntemplate <typename T>\nstruct is_unscoped_enum : detail::is_unscoped_enum<T> {};\n\ntemplate <typename T>\ninline constexpr bool is_unscoped_enum_v = is_unscoped_enum<T>::value;\n\n// Checks whether T is an Scoped enumeration type.\n// Provides the member constant value which is equal to true, if T is an [Scoped enumeration](https://en.cppreference.com/w/cpp/language/enum#Scoped_enumerations) type. Otherwise, value is equal to false.\ntemplate <typename T>\nstruct is_scoped_enum : detail::is_scoped_enum<T> {};\n\ntemplate <typename T>\ninline constexpr bool is_scoped_enum_v = is_scoped_enum<T>::value;\n\n// If T is a complete enumeration type, provides a member typedef type that names the underlying type of T.\n// Otherwise, if T is not an enumeration type, there is no member type. Otherwise (T is an incomplete enumeration type), the program is ill-formed.\ntemplate <typename T>\nstruct underlying_type : detail::underlying_type<T> {};\n\ntemplate <typename T>\nusing underlying_type_t = typename underlying_type<T>::type;\n\n// Returns type name of enum.\ntemplate <typename E>\n[[nodiscard]] constexpr auto enum_type_name() noexcept -> detail::enable_if_enum_t<E, string_view> {\n  using D = std::decay_t<E>;\n  constexpr string_view name = detail::type_name_v<D>;\n  static_assert(name.size() > 0, \"Enum type does not have a name.\");\n\n  return name;\n}\n\n// Returns number of enum values.\ntemplate <typename E>\n[[nodiscard]] constexpr auto enum_count() noexcept -> detail::enable_if_enum_t<E, std::size_t> {\n  using D = std::decay_t<E>;\n\n  return detail::count_v<D>;\n}\n\n// Returns enum value at specified index.\n// No bounds checking is performed: the behavior is undefined if index >= number of enum values.\ntemplate <typename E>\n[[nodiscard]] constexpr auto enum_value(std::size_t index) noexcept -> detail::enable_if_enum_t<E, std::decay_t<E>> {\n  using D = std::decay_t<E>;\n  static_assert(detail::count_v<D> > 0, \"magic_enum requires enum implementation and valid max and min.\");\n\n  if constexpr (detail::is_sparse_v<D>) {\n    return assert((index < detail::count_v<D>)), detail::values_v<D>[index];\n  } else {\n    return assert((index < detail::count_v<D>)), detail::value<D, detail::min_v<D>>(index);\n  }\n}\n\n// Returns std::array with enum values, sorted by enum value.\ntemplate <typename E>\n[[nodiscard]] constexpr auto enum_values() noexcept -> detail::enable_if_enum_t<E, detail::values_t<E>> {\n  using D = std::decay_t<E>;\n  static_assert(detail::count_v<D> > 0, \"magic_enum requires enum implementation and valid max and min.\");\n\n  return detail::values_v<D>;\n}\n\n// Returns name from static storage enum variable.\n// This version is much lighter on the compile times and is not restricted to the enum_range limitation.\ntemplate <auto V>\n[[nodiscard]] constexpr auto enum_name() noexcept -> detail::enable_if_enum_t<decltype(V), string_view> {\n  using D = std::decay_t<decltype(V)>;\n  constexpr string_view name = detail::enum_name_v<D, V>;\n  static_assert(name.size() > 0, \"Enum value does not have a name.\");\n\n  return name;\n}\n\n// Returns name from enum value.\n// If enum value does not have name or value out of range, returns empty string.\ntemplate <typename E>\n[[nodiscard]] constexpr auto enum_name(E value) noexcept -> detail::enable_if_enum_t<E, string_view> {\n  using D = std::decay_t<E>;\n\n  if (const auto i = detail::endex<D>(value); i != detail::invalid_index_v<D>) {\n    return detail::names_v<D>[i];\n  }\n\n  return {}; // Invalid value or out of range.\n}\n\n// Returns std::array with names, sorted by enum value.\ntemplate <typename E>\n[[nodiscard]] constexpr auto enum_names() noexcept -> detail::enable_if_enum_t<E, detail::names_t<E>> {\n  using D = std::decay_t<E>;\n  static_assert(detail::count_v<D> > 0, \"magic_enum requires enum implementation and valid max and min.\");\n\n  return detail::names_v<D>;\n}\n\n// Returns std::array with pairs (value, name), sorted by enum value.\ntemplate <typename E>\n[[nodiscard]] constexpr auto enum_entries() noexcept -> detail::enable_if_enum_t<E, detail::entries_t<E>> {\n  using D = std::decay_t<E>;\n  static_assert(detail::count_v<D> > 0, \"magic_enum requires enum implementation and valid max and min.\");\n\n  return detail::entries_v<D>;\n}\n\n// Obtains enum value from integer value.\n// Returns optional with enum value.\ntemplate <typename E>\n[[nodiscard]] constexpr auto enum_cast(underlying_type_t<E> value) noexcept -> detail::enable_if_enum_t<E, optional<std::decay_t<E>>> {\n  using D = std::decay_t<E>;\n\n  if (detail::undex<D>(value) != detail::invalid_index_v<D>) {\n    return static_cast<D>(value);\n  }\n\n  return {}; // Invalid value or out of range.\n}\n\n// Obtains enum value from name.\n// Returns optional with enum value.\ntemplate <typename E, typename BinaryPredicate>\n[[nodiscard]] constexpr auto enum_cast(string_view value, BinaryPredicate p) noexcept(std::is_nothrow_invocable_r_v<bool, BinaryPredicate, char, char>) -> detail::enable_if_enum_t<E, optional<std::decay_t<E>>> {\n  static_assert(std::is_invocable_r_v<bool, BinaryPredicate, char, char>, \"magic_enum::enum_cast requires bool(char, char) invocable predicate.\");\n  using D = std::decay_t<E>;\n\n  for (std::size_t i = 0; i < detail::count_v<D>; ++i) {\n    if (detail::cmp_equal(value, detail::names_v<D>[i], p)) {\n      return enum_value<D>(i);\n    }\n  }\n\n  return {}; // Invalid value or out of range.\n}\n\n// Obtains enum value from name.\n// Returns optional with enum value.\ntemplate <typename E>\n[[nodiscard]] constexpr auto enum_cast(string_view value) noexcept -> detail::enable_if_enum_t<E, optional<std::decay_t<E>>> {\n  using D = std::decay_t<E>;\n\n  return enum_cast<D>(value, detail::char_equal_to{});\n}\n\n// Returns integer value from enum value.\ntemplate <typename E>\n[[nodiscard]] constexpr auto enum_integer(E value) noexcept -> detail::enable_if_enum_t<E, underlying_type_t<E>> {\n  return static_cast<underlying_type_t<E>>(value);\n}\n\n// Obtains index in enum values from enum value.\n// Returns optional with index.\ntemplate <typename E>\n[[nodiscard]] constexpr auto enum_index(E value) noexcept -> detail::enable_if_enum_t<E, optional<std::size_t>> {\n  using D = std::decay_t<E>;\n\n  if (const auto i = detail::endex<D>(value); i != detail::invalid_index_v<D>) {\n    return i;\n  }\n\n  return {}; // Invalid value or out of range.\n}\n\n// Checks whether enum contains enumerator with such enum value.\ntemplate <typename E>\n[[nodiscard]] constexpr auto enum_contains(E value) noexcept -> detail::enable_if_enum_t<E, bool> {\n  using D = std::decay_t<E>;\n\n  return detail::endex<D>(value) != detail::invalid_index_v<D>;\n}\n\n// Checks whether enum contains enumerator with such integer value.\ntemplate <typename E>\n[[nodiscard]] constexpr auto enum_contains(underlying_type_t<E> value) noexcept -> detail::enable_if_enum_t<E, bool> {\n  using D = std::decay_t<E>;\n\n  return detail::undex<D>(value) != detail::invalid_index_v<D>;\n}\n\n// Checks whether enum contains enumerator with such name.\ntemplate <typename E, typename BinaryPredicate>\n[[nodiscard]] constexpr auto enum_contains(string_view value, BinaryPredicate p) noexcept(std::is_nothrow_invocable_r_v<bool, BinaryPredicate, char, char>) -> detail::enable_if_enum_t<E, bool> {\n  static_assert(std::is_invocable_r_v<bool, BinaryPredicate, char, char>, \"magic_enum::enum_contains requires bool(char, char) invocable predicate.\");\n  using D = std::decay_t<E>;\n\n  return enum_cast<D>(value, std::move_if_noexcept(p)).has_value();\n}\n\n// Checks whether enum contains enumerator with such name.\ntemplate <typename E>\n[[nodiscard]] constexpr auto enum_contains(string_view value) noexcept -> detail::enable_if_enum_t<E, bool> {\n  using D = std::decay_t<E>;\n\n  return enum_cast<D>(value).has_value();\n}\n\nnamespace ostream_operators {\n\ntemplate <typename Char, typename Traits, typename E, std::enable_if_t<std::is_enum_v<E>, int> = 0>\nstd::basic_ostream<Char, Traits>& operator<<(std::basic_ostream<Char, Traits>& os, E value) {\n  using D = std::decay_t<E>;\n  using U = underlying_type_t<D>;\n#if defined(MAGIC_ENUM_SUPPORTED) && MAGIC_ENUM_SUPPORTED\n  if (const auto name = magic_enum::enum_name<D>(value); !name.empty()) {\n    for (const auto c : name) {\n      os.put(c);\n    }\n    return os;\n  }\n#endif\n  return (os << static_cast<U>(value));\n}\n\ntemplate <typename Char, typename Traits, typename E, std::enable_if_t<std::is_enum_v<E>, int> = 0>\nstd::basic_ostream<Char, Traits>& operator<<(std::basic_ostream<Char, Traits>& os, optional<E> value) {\n  return value.has_value() ? (os << value.value()) : os;\n}\n\n} // namespace magic_enum::ostream_operators\n\nnamespace bitwise_operators {\n\ntemplate <typename E, std::enable_if_t<std::is_enum_v<E>, int> = 0>\nconstexpr E operator~(E rhs) noexcept {\n  return static_cast<E>(~static_cast<underlying_type_t<E>>(rhs));\n}\n\ntemplate <typename E, std::enable_if_t<std::is_enum_v<E>, int> = 0>\nconstexpr E operator|(E lhs, E rhs) noexcept {\n  return static_cast<E>(static_cast<underlying_type_t<E>>(lhs) | static_cast<underlying_type_t<E>>(rhs));\n}\n\ntemplate <typename E, std::enable_if_t<std::is_enum_v<E>, int> = 0>\nconstexpr E operator&(E lhs, E rhs) noexcept {\n  return static_cast<E>(static_cast<underlying_type_t<E>>(lhs) & static_cast<underlying_type_t<E>>(rhs));\n}\n\ntemplate <typename E, std::enable_if_t<std::is_enum_v<E>, int> = 0>\nconstexpr E operator^(E lhs, E rhs) noexcept {\n  return static_cast<E>(static_cast<underlying_type_t<E>>(lhs) ^ static_cast<underlying_type_t<E>>(rhs));\n}\n\ntemplate <typename E, std::enable_if_t<std::is_enum_v<E>, int> = 0>\nconstexpr E& operator|=(E& lhs, E rhs) noexcept {\n  return lhs = (lhs | rhs);\n}\n\ntemplate <typename E, std::enable_if_t<std::is_enum_v<E>, int> = 0>\nconstexpr E& operator&=(E& lhs, E rhs) noexcept {\n  return lhs = (lhs & rhs);\n}\n\ntemplate <typename E, std::enable_if_t<std::is_enum_v<E>, int> = 0>\nconstexpr E& operator^=(E& lhs, E rhs) noexcept {\n  return lhs = (lhs ^ rhs);\n}\n\n} // namespace magic_enum::bitwise_operators\n\nnamespace flags {\n\n// Returns type name of enum.\nusing magic_enum::enum_type_name;\n\n// Returns number of enum-flags values.\ntemplate <typename E>\n[[nodiscard]] constexpr auto enum_count() noexcept -> detail::enable_if_enum_t<E, std::size_t> {\n  using D = std::decay_t<E>;\n\n  return detail::count_v<D, true>;\n}\n\n// Returns enum-flags value at specified index.\n// No bounds checking is performed: the behavior is undefined if index >= number of enum-flags values.\ntemplate <typename E>\n[[nodiscard]] constexpr auto enum_value(std::size_t index) noexcept -> detail::enable_if_enum_t<E, std::decay_t<E>> {\n  using D = std::decay_t<E>;\n  static_assert(detail::count_v<D, true> > 0, \"magic_enum::flags requires enum-flags implementation.\");\n\n  if constexpr (detail::is_sparse_v<D, true>) {\n    return assert((index < detail::count_v<D, true>)), detail::values_v<D, true>[index];\n  } else {\n    constexpr auto min = detail::log2(detail::min_v<D, true>);\n\n    return assert((index < detail::count_v<D, true>)), detail::value<D, min, true>(index);\n  }\n}\n\n// Returns std::array with enum-flags values, sorted by enum-flags value.\ntemplate <typename E>\n[[nodiscard]] constexpr auto enum_values() noexcept -> detail::enable_if_enum_t<E, detail::values_t<E, true>> {\n  using D = std::decay_t<E>;\n  static_assert(detail::count_v<D, true> > 0, \"magic_enum::flags requires enum-flags implementation.\");\n\n  return detail::values_v<D, true>;\n}\n\n// Returns name from enum-flags value.\n// If enum-flags value does not have name or value out of range, returns empty string.\ntemplate <typename E>\n[[nodiscard]] auto enum_name(E value) -> detail::enable_if_enum_t<E, string> {\n  using D = std::decay_t<E>;\n  using U = underlying_type_t<D>;\n\n  string name;\n  auto check_value = U{0};\n  for (std::size_t i = 0; i < detail::count_v<D, true>; ++i) {\n    if (const auto v = static_cast<U>(enum_value<D>(i)); (static_cast<U>(value) & v) != 0) {\n      check_value |= v;\n      const auto n = detail::names_v<D, true>[i];\n      if (!name.empty()) {\n        name.append(1, '|');\n      }\n      name.append(n.data(), n.size());\n    }\n  }\n\n  if (check_value != 0 && check_value == static_cast<U>(value)) {\n    return name;\n  }\n\n  return {}; // Invalid value or out of range.\n}\n\n// Returns std::array with string names, sorted by enum-flags value.\ntemplate <typename E>\n[[nodiscard]] constexpr auto enum_names() noexcept -> detail::enable_if_enum_t<E, detail::names_t<E, true>> {\n  using D = std::decay_t<E>;\n  static_assert(detail::count_v<D, true> > 0, \"magic_enum::flags requires enum-flags implementation.\");\n\n  return detail::names_v<D, true>;\n}\n\n// Returns std::array with pairs (value, name), sorted by enum-flags value.\ntemplate <typename E>\n[[nodiscard]] constexpr auto enum_entries() noexcept -> detail::enable_if_enum_t<E, detail::entries_t<E, true>> {\n  using D = std::decay_t<E>;\n  static_assert(detail::count_v<D, true> > 0, \"magic_enum::flags requires enum-flags implementation.\");\n\n  return detail::entries_v<D, true>;\n}\n\n// Obtains enum-flags value from integer value.\n// Returns optional with enum-flags value.\ntemplate <typename E>\n[[nodiscard]] constexpr auto enum_cast(underlying_type_t<E> value) noexcept -> detail::enable_if_enum_t<E, optional<std::decay_t<E>>> {\n  using D = std::decay_t<E>;\n  using U = underlying_type_t<D>;\n\n  if constexpr (detail::is_sparse_v<D, true>) {\n    auto check_value = U{0};\n    for (std::size_t i = 0; i < detail::count_v<D, true>; ++i) {\n      if (const auto v = static_cast<U>(enum_value<D>(i)); (value & v) != 0) {\n        check_value |= v;\n      }\n    }\n\n    if (check_value != 0 && check_value == value) {\n      return static_cast<D>(value);\n    }\n  } else {\n    constexpr auto min = detail::min_v<D, true>;\n    constexpr auto max = detail::value_ors<D>();\n\n    if (value >= min && value <= max) {\n      return static_cast<D>(value);\n    }\n  }\n\n  return {}; // Invalid value or out of range.\n}\n\n// Obtains enum-flags value from name.\n// Returns optional with enum-flags value.\ntemplate <typename E, typename BinaryPredicate>\n[[nodiscard]] constexpr auto enum_cast(string_view value, BinaryPredicate p) noexcept(std::is_nothrow_invocable_r_v<bool, BinaryPredicate, char, char>) -> detail::enable_if_enum_t<E, optional<std::decay_t<E>>> {\n  static_assert(std::is_invocable_r_v<bool, BinaryPredicate, char, char>, \"magic_enum::flags::enum_cast requires bool(char, char) invocable predicate.\");\n  using D = std::decay_t<E>;\n  using U = underlying_type_t<D>;\n\n  auto result = U{0};\n  while (!value.empty()) {\n    const auto d = detail::find(value, '|');\n    const auto s = (d == string_view::npos) ? value : value.substr(0, d);\n    auto f = U{0};\n    for (std::size_t i = 0; i < detail::count_v<D, true>; ++i) {\n      if (detail::cmp_equal(s, detail::names_v<D, true>[i], p)) {\n        f = static_cast<U>(enum_value<D>(i));\n        result |= f;\n        break;\n      }\n    }\n    if (f == U{0}) {\n      return {}; // Invalid value or out of range.\n    }\n    value.remove_prefix((d == string_view::npos) ? value.size() : d + 1);\n  }\n\n  if (result == U{0}) {\n    return {}; // Invalid value or out of range.\n  } else {\n    return static_cast<D>(result);\n  }\n}\n\n// Obtains enum-flags value from name.\n// Returns optional with enum-flags value.\ntemplate <typename E>\n[[nodiscard]] constexpr auto enum_cast(string_view value) noexcept -> detail::enable_if_enum_t<E, optional<std::decay_t<E>>> {\n  using D = std::decay_t<E>;\n\n  return enum_cast<D>(value, detail::char_equal_to{});\n}\n\n// Returns integer value from enum value.\nusing magic_enum::enum_integer;\n\n// Obtains index in enum-flags values from enum-flags value.\n// Returns optional with index.\ntemplate <typename E>\n[[nodiscard]] constexpr auto enum_index(E value) noexcept -> detail::enable_if_enum_t<E, optional<std::size_t>> {\n  using D = std::decay_t<E>;\n  using U = underlying_type_t<D>;\n\n  if (detail::is_pow2(static_cast<U>(value))) {\n    for (std::size_t i = 0; i < detail::count_v<D, true>; ++i) {\n      if (enum_value<D>(i) == value) {\n        return i;\n      }\n    }\n  }\n\n  return {}; // Invalid value or out of range.\n}\n\n// Checks whether enum-flags contains enumerator with such enum-flags value.\ntemplate <typename E>\n[[nodiscard]] constexpr auto enum_contains(E value) noexcept -> detail::enable_if_enum_t<E, bool> {\n  using D = std::decay_t<E>;\n  using U = underlying_type_t<D>;\n\n  return enum_cast<D>(static_cast<U>(value)).has_value();\n}\n\n// Checks whether enum-flags contains enumerator with such integer value.\ntemplate <typename E>\n[[nodiscard]] constexpr auto enum_contains(underlying_type_t<E> value) noexcept -> detail::enable_if_enum_t<E, bool> {\n  using D = std::decay_t<E>;\n\n  return enum_cast<D>(value).has_value();\n}\n\n// Checks whether enum-flags contains enumerator with such name.\ntemplate <typename E, typename BinaryPredicate>\n[[nodiscard]] constexpr auto enum_contains(string_view value, BinaryPredicate p) noexcept(std::is_nothrow_invocable_r_v<bool, BinaryPredicate, char, char>) -> detail::enable_if_enum_t<E, bool> {\n  static_assert(std::is_invocable_r_v<bool, BinaryPredicate, char, char>, \"magic_enum::flags::enum_contains requires bool(char, char) invocable predicate.\");\n  using D = std::decay_t<E>;\n\n  return enum_cast<D>(value, std::move_if_noexcept(p)).has_value();\n}\n\n// Checks whether enum-flags contains enumerator with such name.\ntemplate <typename E>\n[[nodiscard]] constexpr auto enum_contains(string_view value) noexcept -> detail::enable_if_enum_t<E, bool> {\n  using D = std::decay_t<E>;\n\n  return enum_cast<D>(value).has_value();\n}\n\n} // namespace magic_enum::flags\n\nnamespace flags::ostream_operators {\n\ntemplate <typename Char, typename Traits, typename E, std::enable_if_t<std::is_enum_v<E>, int> = 0>\nstd::basic_ostream<Char, Traits>& operator<<(std::basic_ostream<Char, Traits>& os, E value) {\n  using D = std::decay_t<E>;\n  using U = underlying_type_t<D>;\n#if defined(MAGIC_ENUM_SUPPORTED) && MAGIC_ENUM_SUPPORTED\n  if (const auto name = magic_enum::flags::enum_name<D>(value); !name.empty()) {\n    for (const auto c : name) {\n      os.put(c);\n    }\n    return os;\n  }\n#endif\n  return (os << static_cast<U>(value));\n}\n\ntemplate <typename Char, typename Traits, typename E, std::enable_if_t<std::is_enum_v<E>, int> = 0>\nstd::basic_ostream<Char, Traits>& operator<<(std::basic_ostream<Char, Traits>& os, optional<E> value) {\n  return value.has_value() ? (os << value.value()) : os;\n}\n\n} // namespace magic_enum::flags::ostream_operators\n\nnamespace flags::bitwise_operators {\n\nusing namespace magic_enum::bitwise_operators;\n\n} // namespace magic_enum::flags::bitwise_operators\n\n} // namespace magic_enum\n\n#if defined(__clang__)\n#  pragma clang diagnostic pop\n#elif defined(__GNUC__)\n#  pragma GCC diagnostic pop\n#elif defined(_MSC_VER)\n#  pragma warning(pop)\n#endif\n\n#endif // NEARGYE_MAGIC_ENUM_HPP\n"
  },
  {
    "path": "imgui/stb_image.h",
    "content": "/* stb_image - v2.26 - public domain image loader - http://nothings.org/stb\n                                  no warranty implied; use at your own risk\n\n   Do this:\n      #define STB_IMAGE_IMPLEMENTATION\n   before you include this file in *one* C or C++ file to create the implementation.\n\n   // i.e. it should look like this:\n   #include ...\n   #include ...\n   #include ...\n   #define STB_IMAGE_IMPLEMENTATION\n   #include \"stb_image.h\"\n\n   You can #define STBI_ASSERT(x) before the #include to avoid using assert.h.\n   And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free\n\n\n   QUICK NOTES:\n      Primarily of interest to game developers and other people who can\n          avoid problematic images and only need the trivial interface\n\n      JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib)\n      PNG 1/2/4/8/16-bit-per-channel\n\n      TGA (not sure what subset, if a subset)\n      BMP non-1bpp, non-RLE\n      PSD (composited view only, no extra channels, 8/16 bit-per-channel)\n\n      GIF (*comp always reports as 4-channel)\n      HDR (radiance rgbE format)\n      PIC (Softimage PIC)\n      PNM (PPM and PGM binary only)\n\n      Animated GIF still needs a proper API, but here's one way to do it:\n          http://gist.github.com/urraka/685d9a6340b26b830d49\n\n      - decode from memory or through FILE (define STBI_NO_STDIO to remove code)\n      - decode from arbitrary I/O callbacks\n      - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON)\n\n   Full documentation under \"DOCUMENTATION\" below.\n\n\nLICENSE\n\n  See end of file for license information.\n\nRECENT REVISION HISTORY:\n\n      2.26  (2020-07-13) many minor fixes\n      2.25  (2020-02-02) fix warnings\n      2.24  (2020-02-02) fix warnings; thread-local failure_reason and flip_vertically\n      2.23  (2019-08-11) fix clang static analysis warning\n      2.22  (2019-03-04) gif fixes, fix warnings\n      2.21  (2019-02-25) fix typo in comment\n      2.20  (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs\n      2.19  (2018-02-11) fix warning\n      2.18  (2018-01-30) fix warnings\n      2.17  (2018-01-29) bugfix, 1-bit BMP, 16-bitness query, fix warnings\n      2.16  (2017-07-23) all functions have 16-bit variants; optimizations; bugfixes\n      2.15  (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC\n      2.14  (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs\n      2.13  (2016-12-04) experimental 16-bit API, only for PNG so far; fixes\n      2.12  (2016-04-02) fix typo in 2.11 PSD fix that caused crashes\n      2.11  (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64\n                         RGB-format JPEG; remove white matting in PSD;\n                         allocate large structures on the stack;\n                         correct channel count for PNG & BMP\n      2.10  (2016-01-22) avoid warning introduced in 2.09\n      2.09  (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED\n\n   See end of file for full revision history.\n\n\n ============================    Contributors    =========================\n\n Image formats                          Extensions, features\n    Sean Barrett (jpeg, png, bmp)          Jetro Lauha (stbi_info)\n    Nicolas Schulz (hdr, psd)              Martin \"SpartanJ\" Golini (stbi_info)\n    Jonathan Dummer (tga)                  James \"moose2000\" Brown (iPhone PNG)\n    Jean-Marc Lienher (gif)                Ben \"Disch\" Wenger (io callbacks)\n    Tom Seddon (pic)                       Omar Cornut (1/2/4-bit PNG)\n    Thatcher Ulrich (psd)                  Nicolas Guillemot (vertical flip)\n    Ken Miller (pgm, ppm)                  Richard Mitton (16-bit PSD)\n    github:urraka (animated gif)           Junggon Kim (PNM comments)\n    Christopher Forseth (animated gif)     Daniel Gibson (16-bit TGA)\n                                           socks-the-fox (16-bit PNG)\n                                           Jeremy Sawicki (handle all ImageNet JPGs)\n Optimizations & bugfixes                  Mikhail Morozov (1-bit BMP)\n    Fabian \"ryg\" Giesen                    Anael Seghezzi (is-16-bit query)\n    Arseny Kapoulkine\n    John-Mark Allen\n    Carmelo J Fdez-Aguera\n\n Bug & warning fixes\n    Marc LeBlanc            David Woo          Guillaume George     Martins Mozeiko\n    Christpher Lloyd        Jerry Jansson      Joseph Thomson       Blazej Dariusz Roszkowski\n    Phil Jordan                                Dave Moore           Roy Eltham\n    Hayaki Saito            Nathan Reed        Won Chun\n    Luke Graham             Johan Duparc       Nick Verigakis       the Horde3D community\n    Thomas Ruf              Ronny Chevalier                         github:rlyeh\n    Janez Zemva             John Bartholomew   Michal Cichon        github:romigrou\n    Jonathan Blow           Ken Hamada         Tero Hanninen        github:svdijk\n                            Laurent Gomila     Cort Stratton        github:snagar\n    Aruelien Pocheville     Sergio Gonzalez    Thibault Reuille     github:Zelex\n    Cass Everitt            Ryamond Barbiero                        github:grim210\n    Paul Du Bois            Engin Manap        Aldo Culquicondor    github:sammyhw\n    Philipp Wiesemann       Dale Weiler        Oriol Ferrer Mesia   github:phprus\n    Josh Tobin                                 Matthew Gregan       github:poppolopoppo\n    Julian Raschke          Gregory Mullen     Christian Floisand   github:darealshinji\n    Baldur Karlsson         Kevin Schmidt      JR Smith             github:Michaelangel007\n                            Brad Weinberger    Matvey Cherevko      [reserved]\n    Luca Sas                Alexander Veselov  Zack Middleton       [reserved]\n    Ryan C. Gordon          [reserved]                              [reserved]\n                     DO NOT ADD YOUR NAME HERE\n\n  To add your name to the credits, pick a random blank space in the middle and fill it.\n  80% of merge conflicts on stb PRs are due to people adding their name at the end\n  of the credits.\n*/\n\n#ifndef STBI_INCLUDE_STB_IMAGE_H\n#define STBI_INCLUDE_STB_IMAGE_H\n\n// DOCUMENTATION\n//\n// Limitations:\n//    - no 12-bit-per-channel JPEG\n//    - no JPEGs with arithmetic coding\n//    - GIF always returns *comp=4\n//\n// Basic usage (see HDR discussion below for HDR usage):\n//    int x,y,n;\n//    unsigned char *data = stbi_load(filename, &x, &y, &n, 0);\n//    // ... process data if not NULL ...\n//    // ... x = width, y = height, n = # 8-bit components per pixel ...\n//    // ... replace '0' with '1'..'4' to force that many components per pixel\n//    // ... but 'n' will always be the number that it would have been if you said 0\n//    stbi_image_free(data)\n//\n// Standard parameters:\n//    int *x                 -- outputs image width in pixels\n//    int *y                 -- outputs image height in pixels\n//    int *channels_in_file  -- outputs # of image components in image file\n//    int desired_channels   -- if non-zero, # of image components requested in result\n//\n// The return value from an image loader is an 'unsigned char *' which points\n// to the pixel data, or NULL on an allocation failure or if the image is\n// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels,\n// with each pixel consisting of N interleaved 8-bit components; the first\n// pixel pointed to is top-left-most in the image. There is no padding between\n// image scanlines or between pixels, regardless of format. The number of\n// components N is 'desired_channels' if desired_channels is non-zero, or\n// *channels_in_file otherwise. If desired_channels is non-zero,\n// *channels_in_file has the number of components that _would_ have been\n// output otherwise. E.g. if you set desired_channels to 4, you will always\n// get RGBA output, but you can check *channels_in_file to see if it's trivially\n// opaque because e.g. there were only 3 channels in the source image.\n//\n// An output image with N components has the following components interleaved\n// in this order in each pixel:\n//\n//     N=#comp     components\n//       1           grey\n//       2           grey, alpha\n//       3           red, green, blue\n//       4           red, green, blue, alpha\n//\n// If image loading fails for any reason, the return value will be NULL,\n// and *x, *y, *channels_in_file will be unchanged. The function\n// stbi_failure_reason() can be queried for an extremely brief, end-user\n// unfriendly explanation of why the load failed. Define STBI_NO_FAILURE_STRINGS\n// to avoid compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly\n// more user-friendly ones.\n//\n// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized.\n//\n// ===========================================================================\n//\n// UNICODE:\n//\n//   If compiling for Windows and you wish to use Unicode filenames, compile\n//   with\n//       #define STBI_WINDOWS_UTF8\n//   and pass utf8-encoded filenames. Call stbi_convert_wchar_to_utf8 to convert\n//   Windows wchar_t filenames to utf8.\n//\n// ===========================================================================\n//\n// Philosophy\n//\n// stb libraries are designed with the following priorities:\n//\n//    1. easy to use\n//    2. easy to maintain\n//    3. good performance\n//\n// Sometimes I let \"good performance\" creep up in priority over \"easy to maintain\",\n// and for best performance I may provide less-easy-to-use APIs that give higher\n// performance, in addition to the easy-to-use ones. Nevertheless, it's important\n// to keep in mind that from the standpoint of you, a client of this library,\n// all you care about is #1 and #3, and stb libraries DO NOT emphasize #3 above all.\n//\n// Some secondary priorities arise directly from the first two, some of which\n// provide more explicit reasons why performance can't be emphasized.\n//\n//    - Portable (\"ease of use\")\n//    - Small source code footprint (\"easy to maintain\")\n//    - No dependencies (\"ease of use\")\n//\n// ===========================================================================\n//\n// I/O callbacks\n//\n// I/O callbacks allow you to read from arbitrary sources, like packaged\n// files or some other source. Data read from callbacks are processed\n// through a small internal buffer (currently 128 bytes) to try to reduce\n// overhead.\n//\n// The three functions you must define are \"read\" (reads some bytes of data),\n// \"skip\" (skips some bytes of data), \"eof\" (reports if the stream is at the end).\n//\n// ===========================================================================\n//\n// SIMD support\n//\n// The JPEG decoder will try to automatically use SIMD kernels on x86 when\n// supported by the compiler. For ARM Neon support, you must explicitly\n// request it.\n//\n// (The old do-it-yourself SIMD API is no longer supported in the current\n// code.)\n//\n// On x86, SSE2 will automatically be used when available based on a run-time\n// test; if not, the generic C versions are used as a fall-back. On ARM targets,\n// the typical path is to have separate builds for NEON and non-NEON devices\n// (at least this is true for iOS and Android). Therefore, the NEON support is\n// toggled by a build flag: define STBI_NEON to get NEON loops.\n//\n// If for some reason you do not want to use any of SIMD code, or if\n// you have issues compiling it, you can disable it entirely by\n// defining STBI_NO_SIMD.\n//\n// ===========================================================================\n//\n// HDR image support   (disable by defining STBI_NO_HDR)\n//\n// stb_image supports loading HDR images in general, and currently the Radiance\n// .HDR file format specifically. You can still load any file through the existing\n// interface; if you attempt to load an HDR file, it will be automatically remapped\n// to LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1;\n// both of these constants can be reconfigured through this interface:\n//\n//     stbi_hdr_to_ldr_gamma(2.2f);\n//     stbi_hdr_to_ldr_scale(1.0f);\n//\n// (note, do not use _inverse_ constants; stbi_image will invert them\n// appropriately).\n//\n// Additionally, there is a new, parallel interface for loading files as\n// (linear) floats to preserve the full dynamic range:\n//\n//    float *data = stbi_loadf(filename, &x, &y, &n, 0);\n//\n// If you load LDR images through this interface, those images will\n// be promoted to floating point values, run through the inverse of\n// constants corresponding to the above:\n//\n//     stbi_ldr_to_hdr_scale(1.0f);\n//     stbi_ldr_to_hdr_gamma(2.2f);\n//\n// Finally, given a filename (or an open file or memory block--see header\n// file for details) containing image data, you can query for the \"most\n// appropriate\" interface to use (that is, whether the image is HDR or\n// not), using:\n//\n//     stbi_is_hdr(char *filename);\n//\n// ===========================================================================\n//\n// iPhone PNG support:\n//\n// By default we convert iphone-formatted PNGs back to RGB, even though\n// they are internally encoded differently. You can disable this conversion\n// by calling stbi_convert_iphone_png_to_rgb(0), in which case\n// you will always just get the native iphone \"format\" through (which\n// is BGR stored in RGB).\n//\n// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per\n// pixel to remove any premultiplied alpha *only* if the image file explicitly\n// says there's premultiplied data (currently only happens in iPhone images,\n// and only if iPhone convert-to-rgb processing is on).\n//\n// ===========================================================================\n//\n// ADDITIONAL CONFIGURATION\n//\n//  - You can suppress implementation of any of the decoders to reduce\n//    your code footprint by #defining one or more of the following\n//    symbols before creating the implementation.\n//\n//        STBI_NO_JPEG\n//        STBI_NO_PNG\n//        STBI_NO_BMP\n//        STBI_NO_PSD\n//        STBI_NO_TGA\n//        STBI_NO_GIF\n//        STBI_NO_HDR\n//        STBI_NO_PIC\n//        STBI_NO_PNM   (.ppm and .pgm)\n//\n//  - You can request *only* certain decoders and suppress all other ones\n//    (this will be more forward-compatible, as addition of new decoders\n//    doesn't require you to disable them explicitly):\n//\n//        STBI_ONLY_JPEG\n//        STBI_ONLY_PNG\n//        STBI_ONLY_BMP\n//        STBI_ONLY_PSD\n//        STBI_ONLY_TGA\n//        STBI_ONLY_GIF\n//        STBI_ONLY_HDR\n//        STBI_ONLY_PIC\n//        STBI_ONLY_PNM   (.ppm and .pgm)\n//\n//   - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still\n//     want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB\n//\n//  - If you define STBI_MAX_DIMENSIONS, stb_image will reject images greater\n//    than that size (in either width or height) without further processing.\n//    This is to let programs in the wild set an upper bound to prevent\n//    denial-of-service attacks on untrusted data, as one could generate a\n//    valid image of gigantic dimensions and force stb_image to allocate a\n//    huge block of memory and spend disproportionate time decoding it. By\n//    default this is set to (1 << 24), which is 16777216, but that's still\n//    very big.\n\n#ifndef STBI_NO_STDIO\n#include <stdio.h>\n#endif // STBI_NO_STDIO\n\n#define STBI_VERSION 1\n\nenum\n{\n   STBI_default = 0, // only used for desired_channels\n\n   STBI_grey       = 1,\n   STBI_grey_alpha = 2,\n   STBI_rgb        = 3,\n   STBI_rgb_alpha  = 4\n};\n\n#include <stdlib.h>\ntypedef unsigned char stbi_uc;\ntypedef unsigned short stbi_us;\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#ifndef STBIDEF\n#ifdef STB_IMAGE_STATIC\n#define STBIDEF static\n#else\n#define STBIDEF extern\n#endif\n#endif\n\n//////////////////////////////////////////////////////////////////////////////\n//\n// PRIMARY API - works on images of any type\n//\n\n//\n// load image by filename, open file, or memory buffer\n//\n\ntypedef struct\n{\n   int      (*read)  (void *user,char *data,int size);   // fill 'data' with 'size' bytes.  return number of bytes actually read\n   void     (*skip)  (void *user,int n);                 // skip the next 'n' bytes, or 'unget' the last -n bytes if negative\n   int      (*eof)   (void *user);                       // returns nonzero if we are at end of file/data\n} stbi_io_callbacks;\n\n////////////////////////////////////\n//\n// 8-bits-per-channel interface\n//\n\nSTBIDEF stbi_uc *stbi_load_from_memory   (stbi_uc           const *buffer, int len   , int *x, int *y, int *channels_in_file, int desired_channels);\nSTBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk  , void *user, int *x, int *y, int *channels_in_file, int desired_channels);\n\n#ifndef STBI_NO_STDIO\nSTBIDEF stbi_uc *stbi_load            (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels);\nSTBIDEF stbi_uc *stbi_load_from_file  (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels);\n// for stbi_load_from_file, file pointer is left pointing immediately after image\n#endif\n\n#ifndef STBI_NO_GIF\nSTBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp);\n#endif\n\n#ifdef STBI_WINDOWS_UTF8\nSTBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input);\n#endif\n\n////////////////////////////////////\n//\n// 16-bits-per-channel interface\n//\n\nSTBIDEF stbi_us *stbi_load_16_from_memory   (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels);\nSTBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels);\n\n#ifndef STBI_NO_STDIO\nSTBIDEF stbi_us *stbi_load_16          (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels);\nSTBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels);\n#endif\n\n////////////////////////////////////\n//\n// float-per-channel interface\n//\n#ifndef STBI_NO_LINEAR\n   STBIDEF float *stbi_loadf_from_memory     (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels);\n   STBIDEF float *stbi_loadf_from_callbacks  (stbi_io_callbacks const *clbk, void *user, int *x, int *y,  int *channels_in_file, int desired_channels);\n\n   #ifndef STBI_NO_STDIO\n   STBIDEF float *stbi_loadf            (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels);\n   STBIDEF float *stbi_loadf_from_file  (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels);\n   #endif\n#endif\n\n#ifndef STBI_NO_HDR\n   STBIDEF void   stbi_hdr_to_ldr_gamma(float gamma);\n   STBIDEF void   stbi_hdr_to_ldr_scale(float scale);\n#endif // STBI_NO_HDR\n\n#ifndef STBI_NO_LINEAR\n   STBIDEF void   stbi_ldr_to_hdr_gamma(float gamma);\n   STBIDEF void   stbi_ldr_to_hdr_scale(float scale);\n#endif // STBI_NO_LINEAR\n\n// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR\nSTBIDEF int    stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user);\nSTBIDEF int    stbi_is_hdr_from_memory(stbi_uc const *buffer, int len);\n#ifndef STBI_NO_STDIO\nSTBIDEF int      stbi_is_hdr          (char const *filename);\nSTBIDEF int      stbi_is_hdr_from_file(FILE *f);\n#endif // STBI_NO_STDIO\n\n\n// get a VERY brief reason for failure\n// on most compilers (and ALL modern mainstream compilers) this is threadsafe\nSTBIDEF const char *stbi_failure_reason  (void);\n\n// free the loaded image -- this is just free()\nSTBIDEF void     stbi_image_free      (void *retval_from_stbi_load);\n\n// get image dimensions & components without fully decoding\nSTBIDEF int      stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp);\nSTBIDEF int      stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp);\nSTBIDEF int      stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len);\nSTBIDEF int      stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *clbk, void *user);\n\n#ifndef STBI_NO_STDIO\nSTBIDEF int      stbi_info               (char const *filename,     int *x, int *y, int *comp);\nSTBIDEF int      stbi_info_from_file     (FILE *f,                  int *x, int *y, int *comp);\nSTBIDEF int      stbi_is_16_bit          (char const *filename);\nSTBIDEF int      stbi_is_16_bit_from_file(FILE *f);\n#endif\n\n\n\n// for image formats that explicitly notate that they have premultiplied alpha,\n// we just return the colors as stored in the file. set this flag to force\n// unpremultiplication. results are undefined if the unpremultiply overflow.\nSTBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply);\n\n// indicate whether we should process iphone images back to canonical format,\n// or just pass them through \"as-is\"\nSTBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert);\n\n// flip the image vertically, so the first pixel in the output array is the bottom left\nSTBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip);\n\n// as above, but only applies to images loaded on the thread that calls the function\n// this function is only available if your compiler supports thread-local variables;\n// calling it will fail to link if your compiler doesn't\nSTBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip);\n\n// ZLIB client - used by PNG, available for other purposes\n\nSTBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen);\nSTBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header);\nSTBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen);\nSTBIDEF int   stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen);\n\nSTBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen);\nSTBIDEF int   stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen);\n\n\n#ifdef __cplusplus\n}\n#endif\n\n//\n//\n////   end header file   /////////////////////////////////////////////////////\n#endif // STBI_INCLUDE_STB_IMAGE_H\n\n#ifdef STB_IMAGE_IMPLEMENTATION\n\n#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \\\n  || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \\\n  || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \\\n  || defined(STBI_ONLY_ZLIB)\n   #ifndef STBI_ONLY_JPEG\n   #define STBI_NO_JPEG\n   #endif\n   #ifndef STBI_ONLY_PNG\n   #define STBI_NO_PNG\n   #endif\n   #ifndef STBI_ONLY_BMP\n   #define STBI_NO_BMP\n   #endif\n   #ifndef STBI_ONLY_PSD\n   #define STBI_NO_PSD\n   #endif\n   #ifndef STBI_ONLY_TGA\n   #define STBI_NO_TGA\n   #endif\n   #ifndef STBI_ONLY_GIF\n   #define STBI_NO_GIF\n   #endif\n   #ifndef STBI_ONLY_HDR\n   #define STBI_NO_HDR\n   #endif\n   #ifndef STBI_ONLY_PIC\n   #define STBI_NO_PIC\n   #endif\n   #ifndef STBI_ONLY_PNM\n   #define STBI_NO_PNM\n   #endif\n#endif\n\n#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB)\n#define STBI_NO_ZLIB\n#endif\n\n\n#include <stdarg.h>\n#include <stddef.h> // ptrdiff_t on osx\n#include <stdlib.h>\n#include <string.h>\n#include <limits.h>\n\n#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR)\n#include <math.h>  // ldexp, pow\n#endif\n\n#ifndef STBI_NO_STDIO\n#include <stdio.h>\n#endif\n\n#ifndef STBI_ASSERT\n#include <assert.h>\n#define STBI_ASSERT(x) assert(x)\n#endif\n\n#ifdef __cplusplus\n#define STBI_EXTERN extern \"C\"\n#else\n#define STBI_EXTERN extern\n#endif\n\n\n#ifndef _MSC_VER\n   #ifdef __cplusplus\n   #define stbi_inline inline\n   #else\n   #define stbi_inline\n   #endif\n#else\n   #define stbi_inline __forceinline\n#endif\n\n#ifndef STBI_NO_THREAD_LOCALS\n   #if defined(__cplusplus) &&  __cplusplus >= 201103L\n      #define STBI_THREAD_LOCAL       thread_local\n   #elif defined(__GNUC__) && __GNUC__ < 5\n      #define STBI_THREAD_LOCAL       __thread\n   #elif defined(_MSC_VER)\n      #define STBI_THREAD_LOCAL       __declspec(thread)\n   #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_THREADS__)\n      #define STBI_THREAD_LOCAL       _Thread_local\n   #endif\n\n   #ifndef STBI_THREAD_LOCAL\n      #if defined(__GNUC__)\n        #define STBI_THREAD_LOCAL       __thread\n      #endif\n   #endif\n#endif\n\n#ifdef _MSC_VER\ntypedef unsigned short stbi__uint16;\ntypedef   signed short stbi__int16;\ntypedef unsigned int   stbi__uint32;\ntypedef   signed int   stbi__int32;\n#else\n#include <stdint.h>\ntypedef uint16_t stbi__uint16;\ntypedef int16_t  stbi__int16;\ntypedef uint32_t stbi__uint32;\ntypedef int32_t  stbi__int32;\n#endif\n\n// should produce compiler error if size is wrong\ntypedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1];\n\n#ifdef _MSC_VER\n#define STBI_NOTUSED(v)  (void)(v)\n#else\n#define STBI_NOTUSED(v)  (void)sizeof(v)\n#endif\n\n#ifdef _MSC_VER\n#define STBI_HAS_LROTL\n#endif\n\n#ifdef STBI_HAS_LROTL\n   #define stbi_lrot(x,y)  _lrotl(x,y)\n#else\n   #define stbi_lrot(x,y)  (((x) << (y)) | ((x) >> (32 - (y))))\n#endif\n\n#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED))\n// ok\n#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED)\n// ok\n#else\n#error \"Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED).\"\n#endif\n\n#ifndef STBI_MALLOC\n#define STBI_MALLOC(sz)           malloc(sz)\n#define STBI_REALLOC(p,newsz)     realloc(p,newsz)\n#define STBI_FREE(p)              free(p)\n#endif\n\n#ifndef STBI_REALLOC_SIZED\n#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz)\n#endif\n\n// x86/x64 detection\n#if defined(__x86_64__) || defined(_M_X64)\n#define STBI__X64_TARGET\n#elif defined(__i386) || defined(_M_IX86)\n#define STBI__X86_TARGET\n#endif\n\n#if defined(__GNUC__) && defined(STBI__X86_TARGET) && !defined(__SSE2__) && !defined(STBI_NO_SIMD)\n// gcc doesn't support sse2 intrinsics unless you compile with -msse2,\n// which in turn means it gets to use SSE2 everywhere. This is unfortunate,\n// but previous attempts to provide the SSE2 functions with runtime\n// detection caused numerous issues. The way architecture extensions are\n// exposed in GCC/Clang is, sadly, not really suited for one-file libs.\n// New behavior: if compiled with -msse2, we use SSE2 without any\n// detection; if not, we don't use it at all.\n#define STBI_NO_SIMD\n#endif\n\n#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD)\n// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET\n//\n// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the\n// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant.\n// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not\n// simultaneously enabling \"-mstackrealign\".\n//\n// See https://github.com/nothings/stb/issues/81 for more information.\n//\n// So default to no SSE2 on 32-bit MinGW. If you've read this far and added\n// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2.\n#define STBI_NO_SIMD\n#endif\n\n#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET))\n#define STBI_SSE2\n#include <emmintrin.h>\n\n#ifdef _MSC_VER\n\n#if _MSC_VER >= 1400  // not VC6\n#include <intrin.h> // __cpuid\nstatic int stbi__cpuid3(void)\n{\n   int info[4];\n   __cpuid(info,1);\n   return info[3];\n}\n#else\nstatic int stbi__cpuid3(void)\n{\n   int res;\n   __asm {\n      mov  eax,1\n      cpuid\n      mov  res,edx\n   }\n   return res;\n}\n#endif\n\n#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name\n\n#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2)\nstatic int stbi__sse2_available(void)\n{\n   int info3 = stbi__cpuid3();\n   return ((info3 >> 26) & 1) != 0;\n}\n#endif\n\n#else // assume GCC-style if not VC++\n#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16)))\n\n#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2)\nstatic int stbi__sse2_available(void)\n{\n   // If we're even attempting to compile this on GCC/Clang, that means\n   // -msse2 is on, which means the compiler is allowed to use SSE2\n   // instructions at will, and so are we.\n   return 1;\n}\n#endif\n\n#endif\n#endif\n\n// ARM NEON\n#if defined(STBI_NO_SIMD) && defined(STBI_NEON)\n#undef STBI_NEON\n#endif\n\n#ifdef STBI_NEON\n#include <arm_neon.h>\n// assume GCC or Clang on ARM targets\n#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16)))\n#endif\n\n#ifndef STBI_SIMD_ALIGN\n#define STBI_SIMD_ALIGN(type, name) type name\n#endif\n\n#ifndef STBI_MAX_DIMENSIONS\n#define STBI_MAX_DIMENSIONS (1 << 24)\n#endif\n\n///////////////////////////////////////////////\n//\n//  stbi__context struct and start_xxx functions\n\n// stbi__context structure is our basic context used by all images, so it\n// contains all the IO context, plus some basic image information\ntypedef struct\n{\n   stbi__uint32 img_x, img_y;\n   int img_n, img_out_n;\n\n   stbi_io_callbacks io;\n   void *io_user_data;\n\n   int read_from_callbacks;\n   int buflen;\n   stbi_uc buffer_start[128];\n   int callback_already_read;\n\n   stbi_uc *img_buffer, *img_buffer_end;\n   stbi_uc *img_buffer_original, *img_buffer_original_end;\n} stbi__context;\n\n\nstatic void stbi__refill_buffer(stbi__context *s);\n\n// initialize a memory-decode context\nstatic void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len)\n{\n   s->io.read = NULL;\n   s->read_from_callbacks = 0;\n   s->callback_already_read = 0;\n   s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer;\n   s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len;\n}\n\n// initialize a callback-based context\nstatic void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user)\n{\n   s->io = *c;\n   s->io_user_data = user;\n   s->buflen = sizeof(s->buffer_start);\n   s->read_from_callbacks = 1;\n   s->callback_already_read = 0;\n   s->img_buffer = s->img_buffer_original = s->buffer_start;\n   stbi__refill_buffer(s);\n   s->img_buffer_original_end = s->img_buffer_end;\n}\n\n#ifndef STBI_NO_STDIO\n\nstatic int stbi__stdio_read(void *user, char *data, int size)\n{\n   return (int) fread(data,1,size,(FILE*) user);\n}\n\nstatic void stbi__stdio_skip(void *user, int n)\n{\n   int ch;\n   fseek((FILE*) user, n, SEEK_CUR);\n   ch = fgetc((FILE*) user);  /* have to read a byte to reset feof()'s flag */\n   if (ch != EOF) {\n      ungetc(ch, (FILE *) user);  /* push byte back onto stream if valid. */\n   }\n}\n\nstatic int stbi__stdio_eof(void *user)\n{\n   return feof((FILE*) user) || ferror((FILE *) user);\n}\n\nstatic stbi_io_callbacks stbi__stdio_callbacks =\n{\n   stbi__stdio_read,\n   stbi__stdio_skip,\n   stbi__stdio_eof,\n};\n\nstatic void stbi__start_file(stbi__context *s, FILE *f)\n{\n   stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f);\n}\n\n//static void stop_file(stbi__context *s) { }\n\n#endif // !STBI_NO_STDIO\n\nstatic void stbi__rewind(stbi__context *s)\n{\n   // conceptually rewind SHOULD rewind to the beginning of the stream,\n   // but we just rewind to the beginning of the initial buffer, because\n   // we only use it after doing 'test', which only ever looks at at most 92 bytes\n   s->img_buffer = s->img_buffer_original;\n   s->img_buffer_end = s->img_buffer_original_end;\n}\n\nenum\n{\n   STBI_ORDER_RGB,\n   STBI_ORDER_BGR\n};\n\ntypedef struct\n{\n   int bits_per_channel;\n   int num_channels;\n   int channel_order;\n} stbi__result_info;\n\n#ifndef STBI_NO_JPEG\nstatic int      stbi__jpeg_test(stbi__context *s);\nstatic void    *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);\nstatic int      stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp);\n#endif\n\n#ifndef STBI_NO_PNG\nstatic int      stbi__png_test(stbi__context *s);\nstatic void    *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);\nstatic int      stbi__png_info(stbi__context *s, int *x, int *y, int *comp);\nstatic int      stbi__png_is16(stbi__context *s);\n#endif\n\n#ifndef STBI_NO_BMP\nstatic int      stbi__bmp_test(stbi__context *s);\nstatic void    *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);\nstatic int      stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp);\n#endif\n\n#ifndef STBI_NO_TGA\nstatic int      stbi__tga_test(stbi__context *s);\nstatic void    *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);\nstatic int      stbi__tga_info(stbi__context *s, int *x, int *y, int *comp);\n#endif\n\n#ifndef STBI_NO_PSD\nstatic int      stbi__psd_test(stbi__context *s);\nstatic void    *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc);\nstatic int      stbi__psd_info(stbi__context *s, int *x, int *y, int *comp);\nstatic int      stbi__psd_is16(stbi__context *s);\n#endif\n\n#ifndef STBI_NO_HDR\nstatic int      stbi__hdr_test(stbi__context *s);\nstatic float   *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);\nstatic int      stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp);\n#endif\n\n#ifndef STBI_NO_PIC\nstatic int      stbi__pic_test(stbi__context *s);\nstatic void    *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);\nstatic int      stbi__pic_info(stbi__context *s, int *x, int *y, int *comp);\n#endif\n\n#ifndef STBI_NO_GIF\nstatic int      stbi__gif_test(stbi__context *s);\nstatic void    *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);\nstatic void    *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp);\nstatic int      stbi__gif_info(stbi__context *s, int *x, int *y, int *comp);\n#endif\n\n#ifndef STBI_NO_PNM\nstatic int      stbi__pnm_test(stbi__context *s);\nstatic void    *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);\nstatic int      stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp);\n#endif\n\nstatic\n#ifdef STBI_THREAD_LOCAL\nSTBI_THREAD_LOCAL\n#endif\nconst char *stbi__g_failure_reason;\n\nSTBIDEF const char *stbi_failure_reason(void)\n{\n   return stbi__g_failure_reason;\n}\n\n#ifndef STBI_NO_FAILURE_STRINGS\nstatic int stbi__err(const char *str)\n{\n   stbi__g_failure_reason = str;\n   return 0;\n}\n#endif\n\nstatic void *stbi__malloc(size_t size)\n{\n    return STBI_MALLOC(size);\n}\n\n// stb_image uses ints pervasively, including for offset calculations.\n// therefore the largest decoded image size we can support with the\n// current code, even on 64-bit targets, is INT_MAX. this is not a\n// significant limitation for the intended use case.\n//\n// we do, however, need to make sure our size calculations don't\n// overflow. hence a few helper functions for size calculations that\n// multiply integers together, making sure that they're non-negative\n// and no overflow occurs.\n\n// return 1 if the sum is valid, 0 on overflow.\n// negative terms are considered invalid.\nstatic int stbi__addsizes_valid(int a, int b)\n{\n   if (b < 0) return 0;\n   // now 0 <= b <= INT_MAX, hence also\n   // 0 <= INT_MAX - b <= INTMAX.\n   // And \"a + b <= INT_MAX\" (which might overflow) is the\n   // same as a <= INT_MAX - b (no overflow)\n   return a <= INT_MAX - b;\n}\n\n// returns 1 if the product is valid, 0 on overflow.\n// negative factors are considered invalid.\nstatic int stbi__mul2sizes_valid(int a, int b)\n{\n   if (a < 0 || b < 0) return 0;\n   if (b == 0) return 1; // mul-by-0 is always safe\n   // portable way to check for no overflows in a*b\n   return a <= INT_MAX/b;\n}\n\n#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR)\n// returns 1 if \"a*b + add\" has no negative terms/factors and doesn't overflow\nstatic int stbi__mad2sizes_valid(int a, int b, int add)\n{\n   return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add);\n}\n#endif\n\n// returns 1 if \"a*b*c + add\" has no negative terms/factors and doesn't overflow\nstatic int stbi__mad3sizes_valid(int a, int b, int c, int add)\n{\n   return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) &&\n      stbi__addsizes_valid(a*b*c, add);\n}\n\n// returns 1 if \"a*b*c*d + add\" has no negative terms/factors and doesn't overflow\n#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR)\nstatic int stbi__mad4sizes_valid(int a, int b, int c, int d, int add)\n{\n   return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) &&\n      stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add);\n}\n#endif\n\n#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR)\n// mallocs with size overflow checking\nstatic void *stbi__malloc_mad2(int a, int b, int add)\n{\n   if (!stbi__mad2sizes_valid(a, b, add)) return NULL;\n   return stbi__malloc(a*b + add);\n}\n#endif\n\nstatic void *stbi__malloc_mad3(int a, int b, int c, int add)\n{\n   if (!stbi__mad3sizes_valid(a, b, c, add)) return NULL;\n   return stbi__malloc(a*b*c + add);\n}\n\n#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR)\nstatic void *stbi__malloc_mad4(int a, int b, int c, int d, int add)\n{\n   if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL;\n   return stbi__malloc(a*b*c*d + add);\n}\n#endif\n\n// stbi__err - error\n// stbi__errpf - error returning pointer to float\n// stbi__errpuc - error returning pointer to unsigned char\n\n#ifdef STBI_NO_FAILURE_STRINGS\n   #define stbi__err(x,y)  0\n#elif defined(STBI_FAILURE_USERMSG)\n   #define stbi__err(x,y)  stbi__err(y)\n#else\n   #define stbi__err(x,y)  stbi__err(x)\n#endif\n\n#define stbi__errpf(x,y)   ((float *)(size_t) (stbi__err(x,y)?NULL:NULL))\n#define stbi__errpuc(x,y)  ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL))\n\nSTBIDEF void stbi_image_free(void *retval_from_stbi_load)\n{\n   STBI_FREE(retval_from_stbi_load);\n}\n\n#ifndef STBI_NO_LINEAR\nstatic float   *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp);\n#endif\n\n#ifndef STBI_NO_HDR\nstatic stbi_uc *stbi__hdr_to_ldr(float   *data, int x, int y, int comp);\n#endif\n\nstatic int stbi__vertically_flip_on_load_global = 0;\n\nSTBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip)\n{\n   stbi__vertically_flip_on_load_global = flag_true_if_should_flip;\n}\n\n#ifndef STBI_THREAD_LOCAL\n#define stbi__vertically_flip_on_load  stbi__vertically_flip_on_load_global\n#else\nstatic STBI_THREAD_LOCAL int stbi__vertically_flip_on_load_local, stbi__vertically_flip_on_load_set;\n\nSTBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip)\n{\n   stbi__vertically_flip_on_load_local = flag_true_if_should_flip;\n   stbi__vertically_flip_on_load_set = 1;\n}\n\n#define stbi__vertically_flip_on_load  (stbi__vertically_flip_on_load_set       \\\n                                         ? stbi__vertically_flip_on_load_local  \\\n                                         : stbi__vertically_flip_on_load_global)\n#endif // STBI_THREAD_LOCAL\n\nstatic void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc)\n{\n   memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields\n   ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed\n   ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order\n   ri->num_channels = 0;\n\n   #ifndef STBI_NO_JPEG\n   if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri);\n   #endif\n   #ifndef STBI_NO_PNG\n   if (stbi__png_test(s))  return stbi__png_load(s,x,y,comp,req_comp, ri);\n   #endif\n   #ifndef STBI_NO_BMP\n   if (stbi__bmp_test(s))  return stbi__bmp_load(s,x,y,comp,req_comp, ri);\n   #endif\n   #ifndef STBI_NO_GIF\n   if (stbi__gif_test(s))  return stbi__gif_load(s,x,y,comp,req_comp, ri);\n   #endif\n   #ifndef STBI_NO_PSD\n   if (stbi__psd_test(s))  return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc);\n   #else\n   STBI_NOTUSED(bpc);\n   #endif\n   #ifndef STBI_NO_PIC\n   if (stbi__pic_test(s))  return stbi__pic_load(s,x,y,comp,req_comp, ri);\n   #endif\n   #ifndef STBI_NO_PNM\n   if (stbi__pnm_test(s))  return stbi__pnm_load(s,x,y,comp,req_comp, ri);\n   #endif\n\n   #ifndef STBI_NO_HDR\n   if (stbi__hdr_test(s)) {\n      float *hdr = stbi__hdr_load(s, x,y,comp,req_comp, ri);\n      return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp);\n   }\n   #endif\n\n   #ifndef STBI_NO_TGA\n   // test tga last because it's a crappy test!\n   if (stbi__tga_test(s))\n      return stbi__tga_load(s,x,y,comp,req_comp, ri);\n   #endif\n\n   return stbi__errpuc(\"unknown image type\", \"Image not of any known type, or corrupt\");\n}\n\nstatic stbi_uc *stbi__convert_16_to_8(stbi__uint16 *orig, int w, int h, int channels)\n{\n   int i;\n   int img_len = w * h * channels;\n   stbi_uc *reduced;\n\n   reduced = (stbi_uc *) stbi__malloc(img_len);\n   if (reduced == NULL) return stbi__errpuc(\"outofmem\", \"Out of memory\");\n\n   for (i = 0; i < img_len; ++i)\n      reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling\n\n   STBI_FREE(orig);\n   return reduced;\n}\n\nstatic stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int channels)\n{\n   int i;\n   int img_len = w * h * channels;\n   stbi__uint16 *enlarged;\n\n   enlarged = (stbi__uint16 *) stbi__malloc(img_len*2);\n   if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc(\"outofmem\", \"Out of memory\");\n\n   for (i = 0; i < img_len; ++i)\n      enlarged[i] = (stbi__uint16)((orig[i] << 8) + orig[i]); // replicate to high and low byte, maps 0->0, 255->0xffff\n\n   STBI_FREE(orig);\n   return enlarged;\n}\n\nstatic void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel)\n{\n   int row;\n   size_t bytes_per_row = (size_t)w * bytes_per_pixel;\n   stbi_uc temp[2048];\n   stbi_uc *bytes = (stbi_uc *)image;\n\n   for (row = 0; row < (h>>1); row++) {\n      stbi_uc *row0 = bytes + row*bytes_per_row;\n      stbi_uc *row1 = bytes + (h - row - 1)*bytes_per_row;\n      // swap row0 with row1\n      size_t bytes_left = bytes_per_row;\n      while (bytes_left) {\n         size_t bytes_copy = (bytes_left < sizeof(temp)) ? bytes_left : sizeof(temp);\n         memcpy(temp, row0, bytes_copy);\n         memcpy(row0, row1, bytes_copy);\n         memcpy(row1, temp, bytes_copy);\n         row0 += bytes_copy;\n         row1 += bytes_copy;\n         bytes_left -= bytes_copy;\n      }\n   }\n}\n\n#ifndef STBI_NO_GIF\nstatic void stbi__vertical_flip_slices(void *image, int w, int h, int z, int bytes_per_pixel)\n{\n   int slice;\n   int slice_size = w * h * bytes_per_pixel;\n\n   stbi_uc *bytes = (stbi_uc *)image;\n   for (slice = 0; slice < z; ++slice) {\n      stbi__vertical_flip(bytes, w, h, bytes_per_pixel);\n      bytes += slice_size;\n   }\n}\n#endif\n\nstatic unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp)\n{\n   stbi__result_info ri;\n   void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8);\n\n   if (result == NULL)\n      return NULL;\n\n   // it is the responsibility of the loaders to make sure we get either 8 or 16 bit.\n   STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16);\n\n   if (ri.bits_per_channel != 8) {\n      result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp);\n      ri.bits_per_channel = 8;\n   }\n\n   // @TODO: move stbi__convert_format to here\n\n   if (stbi__vertically_flip_on_load) {\n      int channels = req_comp ? req_comp : *comp;\n      stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi_uc));\n   }\n\n   return (unsigned char *) result;\n}\n\nstatic stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp)\n{\n   stbi__result_info ri;\n   void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16);\n\n   if (result == NULL)\n      return NULL;\n\n   // it is the responsibility of the loaders to make sure we get either 8 or 16 bit.\n   STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16);\n\n   if (ri.bits_per_channel != 16) {\n      result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp);\n      ri.bits_per_channel = 16;\n   }\n\n   // @TODO: move stbi__convert_format16 to here\n   // @TODO: special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision\n\n   if (stbi__vertically_flip_on_load) {\n      int channels = req_comp ? req_comp : *comp;\n      stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi__uint16));\n   }\n\n   return (stbi__uint16 *) result;\n}\n\n#if !defined(STBI_NO_HDR) && !defined(STBI_NO_LINEAR)\nstatic void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp)\n{\n   if (stbi__vertically_flip_on_load && result != NULL) {\n      int channels = req_comp ? req_comp : *comp;\n      stbi__vertical_flip(result, *x, *y, channels * sizeof(float));\n   }\n}\n#endif\n\n#ifndef STBI_NO_STDIO\n\n#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8)\nSTBI_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide);\nSTBI_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default);\n#endif\n\n#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8)\nSTBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input)\n{\n\treturn WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL);\n}\n#endif\n\nstatic FILE *stbi__fopen(char const *filename, char const *mode)\n{\n   FILE *f;\n#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8)\n   wchar_t wMode[64];\n   wchar_t wFilename[1024];\n\tif (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)))\n      return 0;\n\n\tif (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)))\n      return 0;\n\n#if _MSC_VER >= 1400\n\tif (0 != _wfopen_s(&f, wFilename, wMode))\n\t\tf = 0;\n#else\n   f = _wfopen(wFilename, wMode);\n#endif\n\n#elif defined(_MSC_VER) && _MSC_VER >= 1400\n   if (0 != fopen_s(&f, filename, mode))\n      f=0;\n#else\n   f = fopen(filename, mode);\n#endif\n   return f;\n}\n\n\nSTBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp)\n{\n   FILE *f = stbi__fopen(filename, \"rb\");\n   unsigned char *result;\n   if (!f) return stbi__errpuc(\"can't fopen\", \"Unable to open file\");\n   result = stbi_load_from_file(f,x,y,comp,req_comp);\n   fclose(f);\n   return result;\n}\n\nSTBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp)\n{\n   unsigned char *result;\n   stbi__context s;\n   stbi__start_file(&s,f);\n   result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp);\n   if (result) {\n      // need to 'unget' all the characters in the IO buffer\n      fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR);\n   }\n   return result;\n}\n\nSTBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, int req_comp)\n{\n   stbi__uint16 *result;\n   stbi__context s;\n   stbi__start_file(&s,f);\n   result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp);\n   if (result) {\n      // need to 'unget' all the characters in the IO buffer\n      fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR);\n   }\n   return result;\n}\n\nSTBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *comp, int req_comp)\n{\n   FILE *f = stbi__fopen(filename, \"rb\");\n   stbi__uint16 *result;\n   if (!f) return (stbi_us *) stbi__errpuc(\"can't fopen\", \"Unable to open file\");\n   result = stbi_load_from_file_16(f,x,y,comp,req_comp);\n   fclose(f);\n   return result;\n}\n\n\n#endif //!STBI_NO_STDIO\n\nSTBIDEF stbi_us *stbi_load_16_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels)\n{\n   stbi__context s;\n   stbi__start_mem(&s,buffer,len);\n   return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels);\n}\n\nSTBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels)\n{\n   stbi__context s;\n   stbi__start_callbacks(&s, (stbi_io_callbacks *)clbk, user);\n   return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels);\n}\n\nSTBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp)\n{\n   stbi__context s;\n   stbi__start_mem(&s,buffer,len);\n   return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp);\n}\n\nSTBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp)\n{\n   stbi__context s;\n   stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user);\n   return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp);\n}\n\n#ifndef STBI_NO_GIF\nSTBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp)\n{\n   unsigned char *result;\n   stbi__context s;\n   stbi__start_mem(&s,buffer,len);\n\n   result = (unsigned char*) stbi__load_gif_main(&s, delays, x, y, z, comp, req_comp);\n   if (stbi__vertically_flip_on_load) {\n      stbi__vertical_flip_slices( result, *x, *y, *z, *comp );\n   }\n\n   return result;\n}\n#endif\n\n#ifndef STBI_NO_LINEAR\nstatic float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp)\n{\n   unsigned char *data;\n   #ifndef STBI_NO_HDR\n   if (stbi__hdr_test(s)) {\n      stbi__result_info ri;\n      float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp, &ri);\n      if (hdr_data)\n         stbi__float_postprocess(hdr_data,x,y,comp,req_comp);\n      return hdr_data;\n   }\n   #endif\n   data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp);\n   if (data)\n      return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp);\n   return stbi__errpf(\"unknown image type\", \"Image not of any known type, or corrupt\");\n}\n\nSTBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp)\n{\n   stbi__context s;\n   stbi__start_mem(&s,buffer,len);\n   return stbi__loadf_main(&s,x,y,comp,req_comp);\n}\n\nSTBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp)\n{\n   stbi__context s;\n   stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user);\n   return stbi__loadf_main(&s,x,y,comp,req_comp);\n}\n\n#ifndef STBI_NO_STDIO\nSTBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp)\n{\n   float *result;\n   FILE *f = stbi__fopen(filename, \"rb\");\n   if (!f) return stbi__errpf(\"can't fopen\", \"Unable to open file\");\n   result = stbi_loadf_from_file(f,x,y,comp,req_comp);\n   fclose(f);\n   return result;\n}\n\nSTBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp)\n{\n   stbi__context s;\n   stbi__start_file(&s,f);\n   return stbi__loadf_main(&s,x,y,comp,req_comp);\n}\n#endif // !STBI_NO_STDIO\n\n#endif // !STBI_NO_LINEAR\n\n// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is\n// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always\n// reports false!\n\nSTBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len)\n{\n   #ifndef STBI_NO_HDR\n   stbi__context s;\n   stbi__start_mem(&s,buffer,len);\n   return stbi__hdr_test(&s);\n   #else\n   STBI_NOTUSED(buffer);\n   STBI_NOTUSED(len);\n   return 0;\n   #endif\n}\n\n#ifndef STBI_NO_STDIO\nSTBIDEF int      stbi_is_hdr          (char const *filename)\n{\n   FILE *f = stbi__fopen(filename, \"rb\");\n   int result=0;\n   if (f) {\n      result = stbi_is_hdr_from_file(f);\n      fclose(f);\n   }\n   return result;\n}\n\nSTBIDEF int stbi_is_hdr_from_file(FILE *f)\n{\n   #ifndef STBI_NO_HDR\n   long pos = ftell(f);\n   int res;\n   stbi__context s;\n   stbi__start_file(&s,f);\n   res = stbi__hdr_test(&s);\n   fseek(f, pos, SEEK_SET);\n   return res;\n   #else\n   STBI_NOTUSED(f);\n   return 0;\n   #endif\n}\n#endif // !STBI_NO_STDIO\n\nSTBIDEF int      stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user)\n{\n   #ifndef STBI_NO_HDR\n   stbi__context s;\n   stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user);\n   return stbi__hdr_test(&s);\n   #else\n   STBI_NOTUSED(clbk);\n   STBI_NOTUSED(user);\n   return 0;\n   #endif\n}\n\n#ifndef STBI_NO_LINEAR\nstatic float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f;\n\nSTBIDEF void   stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; }\nSTBIDEF void   stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; }\n#endif\n\nstatic float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f;\n\nSTBIDEF void   stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; }\nSTBIDEF void   stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; }\n\n\n//////////////////////////////////////////////////////////////////////////////\n//\n// Common code used by all image loaders\n//\n\nenum\n{\n   STBI__SCAN_load=0,\n   STBI__SCAN_type,\n   STBI__SCAN_header\n};\n\nstatic void stbi__refill_buffer(stbi__context *s)\n{\n   int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen);\n   s->callback_already_read += (int) (s->img_buffer - s->img_buffer_original);\n   if (n == 0) {\n      // at end of file, treat same as if from memory, but need to handle case\n      // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file\n      s->read_from_callbacks = 0;\n      s->img_buffer = s->buffer_start;\n      s->img_buffer_end = s->buffer_start+1;\n      *s->img_buffer = 0;\n   } else {\n      s->img_buffer = s->buffer_start;\n      s->img_buffer_end = s->buffer_start + n;\n   }\n}\n\nstbi_inline static stbi_uc stbi__get8(stbi__context *s)\n{\n   if (s->img_buffer < s->img_buffer_end)\n      return *s->img_buffer++;\n   if (s->read_from_callbacks) {\n      stbi__refill_buffer(s);\n      return *s->img_buffer++;\n   }\n   return 0;\n}\n\n#if defined(STBI_NO_JPEG) && defined(STBI_NO_HDR) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM)\n// nothing\n#else\nstbi_inline static int stbi__at_eof(stbi__context *s)\n{\n   if (s->io.read) {\n      if (!(s->io.eof)(s->io_user_data)) return 0;\n      // if feof() is true, check if buffer = end\n      // special case: we've only got the special 0 character at the end\n      if (s->read_from_callbacks == 0) return 1;\n   }\n\n   return s->img_buffer >= s->img_buffer_end;\n}\n#endif\n\n#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC)\n// nothing\n#else\nstatic void stbi__skip(stbi__context *s, int n)\n{\n   if (n == 0) return;  // already there!\n   if (n < 0) {\n      s->img_buffer = s->img_buffer_end;\n      return;\n   }\n   if (s->io.read) {\n      int blen = (int) (s->img_buffer_end - s->img_buffer);\n      if (blen < n) {\n         s->img_buffer = s->img_buffer_end;\n         (s->io.skip)(s->io_user_data, n - blen);\n         return;\n      }\n   }\n   s->img_buffer += n;\n}\n#endif\n\n#if defined(STBI_NO_PNG) && defined(STBI_NO_TGA) && defined(STBI_NO_HDR) && defined(STBI_NO_PNM)\n// nothing\n#else\nstatic int stbi__getn(stbi__context *s, stbi_uc *buffer, int n)\n{\n   if (s->io.read) {\n      int blen = (int) (s->img_buffer_end - s->img_buffer);\n      if (blen < n) {\n         int res, count;\n\n         memcpy(buffer, s->img_buffer, blen);\n\n         count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen);\n         res = (count == (n-blen));\n         s->img_buffer = s->img_buffer_end;\n         return res;\n      }\n   }\n\n   if (s->img_buffer+n <= s->img_buffer_end) {\n      memcpy(buffer, s->img_buffer, n);\n      s->img_buffer += n;\n      return 1;\n   } else\n      return 0;\n}\n#endif\n\n#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC)\n// nothing\n#else\nstatic int stbi__get16be(stbi__context *s)\n{\n   int z = stbi__get8(s);\n   return (z << 8) + stbi__get8(s);\n}\n#endif\n\n#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC)\n// nothing\n#else\nstatic stbi__uint32 stbi__get32be(stbi__context *s)\n{\n   stbi__uint32 z = stbi__get16be(s);\n   return (z << 16) + stbi__get16be(s);\n}\n#endif\n\n#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF)\n// nothing\n#else\nstatic int stbi__get16le(stbi__context *s)\n{\n   int z = stbi__get8(s);\n   return z + (stbi__get8(s) << 8);\n}\n#endif\n\n#ifndef STBI_NO_BMP\nstatic stbi__uint32 stbi__get32le(stbi__context *s)\n{\n   stbi__uint32 z = stbi__get16le(s);\n   return z + (stbi__get16le(s) << 16);\n}\n#endif\n\n#define STBI__BYTECAST(x)  ((stbi_uc) ((x) & 255))  // truncate int to byte without warnings\n\n#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM)\n// nothing\n#else\n//////////////////////////////////////////////////////////////////////////////\n//\n//  generic converter from built-in img_n to req_comp\n//    individual types do this automatically as much as possible (e.g. jpeg\n//    does all cases internally since it needs to colorspace convert anyway,\n//    and it never has alpha, so very few cases ). png can automatically\n//    interleave an alpha=255 channel, but falls back to this for other cases\n//\n//  assume data buffer is malloced, so malloc a new one and free that one\n//  only failure mode is malloc failing\n\nstatic stbi_uc stbi__compute_y(int r, int g, int b)\n{\n   return (stbi_uc) (((r*77) + (g*150) +  (29*b)) >> 8);\n}\n#endif\n\n#if defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM)\n// nothing\n#else\nstatic unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y)\n{\n   int i,j;\n   unsigned char *good;\n\n   if (req_comp == img_n) return data;\n   STBI_ASSERT(req_comp >= 1 && req_comp <= 4);\n\n   good = (unsigned char *) stbi__malloc_mad3(req_comp, x, y, 0);\n   if (good == NULL) {\n      STBI_FREE(data);\n      return stbi__errpuc(\"outofmem\", \"Out of memory\");\n   }\n\n   for (j=0; j < (int) y; ++j) {\n      unsigned char *src  = data + j * x * img_n   ;\n      unsigned char *dest = good + j * x * req_comp;\n\n      #define STBI__COMBO(a,b)  ((a)*8+(b))\n      #define STBI__CASE(a,b)   case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b)\n      // convert source image with img_n components to one with req_comp components;\n      // avoid switch per pixel, so use switch per scanline and massive macros\n      switch (STBI__COMBO(img_n, req_comp)) {\n         STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=255;                                     } break;\n         STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0];                                  } break;\n         STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=255;                     } break;\n         STBI__CASE(2,1) { dest[0]=src[0];                                                  } break;\n         STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0];                                  } break;\n         STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1];                  } break;\n         STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=255;        } break;\n         STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]);                   } break;\n         STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = 255;    } break;\n         STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]);                   } break;\n         STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = src[3]; } break;\n         STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];                    } break;\n         default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return stbi__errpuc(\"unsupported\", \"Unsupported format conversion\");\n      }\n      #undef STBI__CASE\n   }\n\n   STBI_FREE(data);\n   return good;\n}\n#endif\n\n#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD)\n// nothing\n#else\nstatic stbi__uint16 stbi__compute_y_16(int r, int g, int b)\n{\n   return (stbi__uint16) (((r*77) + (g*150) +  (29*b)) >> 8);\n}\n#endif\n\n#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD)\n// nothing\n#else\nstatic stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y)\n{\n   int i,j;\n   stbi__uint16 *good;\n\n   if (req_comp == img_n) return data;\n   STBI_ASSERT(req_comp >= 1 && req_comp <= 4);\n\n   good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2);\n   if (good == NULL) {\n      STBI_FREE(data);\n      return (stbi__uint16 *) stbi__errpuc(\"outofmem\", \"Out of memory\");\n   }\n\n   for (j=0; j < (int) y; ++j) {\n      stbi__uint16 *src  = data + j * x * img_n   ;\n      stbi__uint16 *dest = good + j * x * req_comp;\n\n      #define STBI__COMBO(a,b)  ((a)*8+(b))\n      #define STBI__CASE(a,b)   case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b)\n      // convert source image with img_n components to one with req_comp components;\n      // avoid switch per pixel, so use switch per scanline and massive macros\n      switch (STBI__COMBO(img_n, req_comp)) {\n         STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=0xffff;                                     } break;\n         STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0];                                     } break;\n         STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=0xffff;                     } break;\n         STBI__CASE(2,1) { dest[0]=src[0];                                                     } break;\n         STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0];                                     } break;\n         STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1];                     } break;\n         STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=0xffff;        } break;\n         STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]);                   } break;\n         STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = 0xffff; } break;\n         STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]);                   } break;\n         STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = src[3]; } break;\n         STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];                       } break;\n         default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return (stbi__uint16*) stbi__errpuc(\"unsupported\", \"Unsupported format conversion\");\n      }\n      #undef STBI__CASE\n   }\n\n   STBI_FREE(data);\n   return good;\n}\n#endif\n\n#ifndef STBI_NO_LINEAR\nstatic float   *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp)\n{\n   int i,k,n;\n   float *output;\n   if (!data) return NULL;\n   output = (float *) stbi__malloc_mad4(x, y, comp, sizeof(float), 0);\n   if (output == NULL) { STBI_FREE(data); return stbi__errpf(\"outofmem\", \"Out of memory\"); }\n   // compute number of non-alpha components\n   if (comp & 1) n = comp; else n = comp-1;\n   for (i=0; i < x*y; ++i) {\n      for (k=0; k < n; ++k) {\n         output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale);\n      }\n   }\n   if (n < comp) {\n      for (i=0; i < x*y; ++i) {\n         output[i*comp + n] = data[i*comp + n]/255.0f;\n      }\n   }\n   STBI_FREE(data);\n   return output;\n}\n#endif\n\n#ifndef STBI_NO_HDR\n#define stbi__float2int(x)   ((int) (x))\nstatic stbi_uc *stbi__hdr_to_ldr(float   *data, int x, int y, int comp)\n{\n   int i,k,n;\n   stbi_uc *output;\n   if (!data) return NULL;\n   output = (stbi_uc *) stbi__malloc_mad3(x, y, comp, 0);\n   if (output == NULL) { STBI_FREE(data); return stbi__errpuc(\"outofmem\", \"Out of memory\"); }\n   // compute number of non-alpha components\n   if (comp & 1) n = comp; else n = comp-1;\n   for (i=0; i < x*y; ++i) {\n      for (k=0; k < n; ++k) {\n         float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f;\n         if (z < 0) z = 0;\n         if (z > 255) z = 255;\n         output[i*comp + k] = (stbi_uc) stbi__float2int(z);\n      }\n      if (k < comp) {\n         float z = data[i*comp+k] * 255 + 0.5f;\n         if (z < 0) z = 0;\n         if (z > 255) z = 255;\n         output[i*comp + k] = (stbi_uc) stbi__float2int(z);\n      }\n   }\n   STBI_FREE(data);\n   return output;\n}\n#endif\n\n//////////////////////////////////////////////////////////////////////////////\n//\n//  \"baseline\" JPEG/JFIF decoder\n//\n//    simple implementation\n//      - doesn't support delayed output of y-dimension\n//      - simple interface (only one output format: 8-bit interleaved RGB)\n//      - doesn't try to recover corrupt jpegs\n//      - doesn't allow partial loading, loading multiple at once\n//      - still fast on x86 (copying globals into locals doesn't help x86)\n//      - allocates lots of intermediate memory (full size of all components)\n//        - non-interleaved case requires this anyway\n//        - allows good upsampling (see next)\n//    high-quality\n//      - upsampled channels are bilinearly interpolated, even across blocks\n//      - quality integer IDCT derived from IJG's 'slow'\n//    performance\n//      - fast huffman; reasonable integer IDCT\n//      - some SIMD kernels for common paths on targets with SSE2/NEON\n//      - uses a lot of intermediate memory, could cache poorly\n\n#ifndef STBI_NO_JPEG\n\n// huffman decoding acceleration\n#define FAST_BITS   9  // larger handles more cases; smaller stomps less cache\n\ntypedef struct\n{\n   stbi_uc  fast[1 << FAST_BITS];\n   // weirdly, repacking this into AoS is a 10% speed loss, instead of a win\n   stbi__uint16 code[256];\n   stbi_uc  values[256];\n   stbi_uc  size[257];\n   unsigned int maxcode[18];\n   int    delta[17];   // old 'firstsymbol' - old 'firstcode'\n} stbi__huffman;\n\ntypedef struct\n{\n   stbi__context *s;\n   stbi__huffman huff_dc[4];\n   stbi__huffman huff_ac[4];\n   stbi__uint16 dequant[4][64];\n   stbi__int16 fast_ac[4][1 << FAST_BITS];\n\n// sizes for components, interleaved MCUs\n   int img_h_max, img_v_max;\n   int img_mcu_x, img_mcu_y;\n   int img_mcu_w, img_mcu_h;\n\n// definition of jpeg image component\n   struct\n   {\n      int id;\n      int h,v;\n      int tq;\n      int hd,ha;\n      int dc_pred;\n\n      int x,y,w2,h2;\n      stbi_uc *data;\n      void *raw_data, *raw_coeff;\n      stbi_uc *linebuf;\n      short   *coeff;   // progressive only\n      int      coeff_w, coeff_h; // number of 8x8 coefficient blocks\n   } img_comp[4];\n\n   stbi__uint32   code_buffer; // jpeg entropy-coded buffer\n   int            code_bits;   // number of valid bits\n   unsigned char  marker;      // marker seen while filling entropy buffer\n   int            nomore;      // flag if we saw a marker so must stop\n\n   int            progressive;\n   int            spec_start;\n   int            spec_end;\n   int            succ_high;\n   int            succ_low;\n   int            eob_run;\n   int            jfif;\n   int            app14_color_transform; // Adobe APP14 tag\n   int            rgb;\n\n   int scan_n, order[4];\n   int restart_interval, todo;\n\n// kernels\n   void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]);\n   void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step);\n   stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs);\n} stbi__jpeg;\n\nstatic int stbi__build_huffman(stbi__huffman *h, int *count)\n{\n   int i,j,k=0;\n   unsigned int code;\n   // build size list for each symbol (from JPEG spec)\n   for (i=0; i < 16; ++i)\n      for (j=0; j < count[i]; ++j)\n         h->size[k++] = (stbi_uc) (i+1);\n   h->size[k] = 0;\n\n   // compute actual symbols (from jpeg spec)\n   code = 0;\n   k = 0;\n   for(j=1; j <= 16; ++j) {\n      // compute delta to add to code to compute symbol id\n      h->delta[j] = k - code;\n      if (h->size[k] == j) {\n         while (h->size[k] == j)\n            h->code[k++] = (stbi__uint16) (code++);\n         if (code-1 >= (1u << j)) return stbi__err(\"bad code lengths\",\"Corrupt JPEG\");\n      }\n      // compute largest code + 1 for this size, preshifted as needed later\n      h->maxcode[j] = code << (16-j);\n      code <<= 1;\n   }\n   h->maxcode[j] = 0xffffffff;\n\n   // build non-spec acceleration table; 255 is flag for not-accelerated\n   memset(h->fast, 255, 1 << FAST_BITS);\n   for (i=0; i < k; ++i) {\n      int s = h->size[i];\n      if (s <= FAST_BITS) {\n         int c = h->code[i] << (FAST_BITS-s);\n         int m = 1 << (FAST_BITS-s);\n         for (j=0; j < m; ++j) {\n            h->fast[c+j] = (stbi_uc) i;\n         }\n      }\n   }\n   return 1;\n}\n\n// build a table that decodes both magnitude and value of small ACs in\n// one go.\nstatic void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h)\n{\n   int i;\n   for (i=0; i < (1 << FAST_BITS); ++i) {\n      stbi_uc fast = h->fast[i];\n      fast_ac[i] = 0;\n      if (fast < 255) {\n         int rs = h->values[fast];\n         int run = (rs >> 4) & 15;\n         int magbits = rs & 15;\n         int len = h->size[fast];\n\n         if (magbits && len + magbits <= FAST_BITS) {\n            // magnitude code followed by receive_extend code\n            int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits);\n            int m = 1 << (magbits - 1);\n            if (k < m) k += (~0U << magbits) + 1;\n            // if the result is small enough, we can fit it in fast_ac table\n            if (k >= -128 && k <= 127)\n               fast_ac[i] = (stbi__int16) ((k * 256) + (run * 16) + (len + magbits));\n         }\n      }\n   }\n}\n\nstatic void stbi__grow_buffer_unsafe(stbi__jpeg *j)\n{\n   do {\n      unsigned int b = j->nomore ? 0 : stbi__get8(j->s);\n      if (b == 0xff) {\n         int c = stbi__get8(j->s);\n         while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes\n         if (c != 0) {\n            j->marker = (unsigned char) c;\n            j->nomore = 1;\n            return;\n         }\n      }\n      j->code_buffer |= b << (24 - j->code_bits);\n      j->code_bits += 8;\n   } while (j->code_bits <= 24);\n}\n\n// (1 << n) - 1\nstatic const stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535};\n\n// decode a jpeg huffman value from the bitstream\nstbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h)\n{\n   unsigned int temp;\n   int c,k;\n\n   if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);\n\n   // look at the top FAST_BITS and determine what symbol ID it is,\n   // if the code is <= FAST_BITS\n   c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1);\n   k = h->fast[c];\n   if (k < 255) {\n      int s = h->size[k];\n      if (s > j->code_bits)\n         return -1;\n      j->code_buffer <<= s;\n      j->code_bits -= s;\n      return h->values[k];\n   }\n\n   // naive test is to shift the code_buffer down so k bits are\n   // valid, then test against maxcode. To speed this up, we've\n   // preshifted maxcode left so that it has (16-k) 0s at the\n   // end; in other words, regardless of the number of bits, it\n   // wants to be compared against something shifted to have 16;\n   // that way we don't need to shift inside the loop.\n   temp = j->code_buffer >> 16;\n   for (k=FAST_BITS+1 ; ; ++k)\n      if (temp < h->maxcode[k])\n         break;\n   if (k == 17) {\n      // error! code not found\n      j->code_bits -= 16;\n      return -1;\n   }\n\n   if (k > j->code_bits)\n      return -1;\n\n   // convert the huffman code to the symbol id\n   c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k];\n   STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]);\n\n   // convert the id to a symbol\n   j->code_bits -= k;\n   j->code_buffer <<= k;\n   return h->values[c];\n}\n\n// bias[n] = (-1<<n) + 1\nstatic const int stbi__jbias[16] = {0,-1,-3,-7,-15,-31,-63,-127,-255,-511,-1023,-2047,-4095,-8191,-16383,-32767};\n\n// combined JPEG 'receive' and JPEG 'extend', since baseline\n// always extends everything it receives.\nstbi_inline static int stbi__extend_receive(stbi__jpeg *j, int n)\n{\n   unsigned int k;\n   int sgn;\n   if (j->code_bits < n) stbi__grow_buffer_unsafe(j);\n\n   sgn = (stbi__int32)j->code_buffer >> 31; // sign bit is always in MSB\n   k = stbi_lrot(j->code_buffer, n);\n   if (n < 0 || n >= (int) (sizeof(stbi__bmask)/sizeof(*stbi__bmask))) return 0;\n   j->code_buffer = k & ~stbi__bmask[n];\n   k &= stbi__bmask[n];\n   j->code_bits -= n;\n   return k + (stbi__jbias[n] & ~sgn);\n}\n\n// get some unsigned bits\nstbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n)\n{\n   unsigned int k;\n   if (j->code_bits < n) stbi__grow_buffer_unsafe(j);\n   k = stbi_lrot(j->code_buffer, n);\n   j->code_buffer = k & ~stbi__bmask[n];\n   k &= stbi__bmask[n];\n   j->code_bits -= n;\n   return k;\n}\n\nstbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j)\n{\n   unsigned int k;\n   if (j->code_bits < 1) stbi__grow_buffer_unsafe(j);\n   k = j->code_buffer;\n   j->code_buffer <<= 1;\n   --j->code_bits;\n   return k & 0x80000000;\n}\n\n// given a value that's at position X in the zigzag stream,\n// where does it appear in the 8x8 matrix coded as row-major?\nstatic const stbi_uc stbi__jpeg_dezigzag[64+15] =\n{\n    0,  1,  8, 16,  9,  2,  3, 10,\n   17, 24, 32, 25, 18, 11,  4,  5,\n   12, 19, 26, 33, 40, 48, 41, 34,\n   27, 20, 13,  6,  7, 14, 21, 28,\n   35, 42, 49, 56, 57, 50, 43, 36,\n   29, 22, 15, 23, 30, 37, 44, 51,\n   58, 59, 52, 45, 38, 31, 39, 46,\n   53, 60, 61, 54, 47, 55, 62, 63,\n   // let corrupt input sample past end\n   63, 63, 63, 63, 63, 63, 63, 63,\n   63, 63, 63, 63, 63, 63, 63\n};\n\n// decode one 64-entry block--\nstatic int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi__uint16 *dequant)\n{\n   int diff,dc,k;\n   int t;\n\n   if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);\n   t = stbi__jpeg_huff_decode(j, hdc);\n   if (t < 0) return stbi__err(\"bad huffman code\",\"Corrupt JPEG\");\n\n   // 0 all the ac values now so we can do it 32-bits at a time\n   memset(data,0,64*sizeof(data[0]));\n\n   diff = t ? stbi__extend_receive(j, t) : 0;\n   dc = j->img_comp[b].dc_pred + diff;\n   j->img_comp[b].dc_pred = dc;\n   data[0] = (short) (dc * dequant[0]);\n\n   // decode AC components, see JPEG spec\n   k = 1;\n   do {\n      unsigned int zig;\n      int c,r,s;\n      if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);\n      c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1);\n      r = fac[c];\n      if (r) { // fast-AC path\n         k += (r >> 4) & 15; // run\n         s = r & 15; // combined length\n         j->code_buffer <<= s;\n         j->code_bits -= s;\n         // decode into unzigzag'd location\n         zig = stbi__jpeg_dezigzag[k++];\n         data[zig] = (short) ((r >> 8) * dequant[zig]);\n      } else {\n         int rs = stbi__jpeg_huff_decode(j, hac);\n         if (rs < 0) return stbi__err(\"bad huffman code\",\"Corrupt JPEG\");\n         s = rs & 15;\n         r = rs >> 4;\n         if (s == 0) {\n            if (rs != 0xf0) break; // end block\n            k += 16;\n         } else {\n            k += r;\n            // decode into unzigzag'd location\n            zig = stbi__jpeg_dezigzag[k++];\n            data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]);\n         }\n      }\n   } while (k < 64);\n   return 1;\n}\n\nstatic int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b)\n{\n   int diff,dc;\n   int t;\n   if (j->spec_end != 0) return stbi__err(\"can't merge dc and ac\", \"Corrupt JPEG\");\n\n   if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);\n\n   if (j->succ_high == 0) {\n      // first scan for DC coefficient, must be first\n      memset(data,0,64*sizeof(data[0])); // 0 all the ac values now\n      t = stbi__jpeg_huff_decode(j, hdc);\n      if (t == -1) return stbi__err(\"can't merge dc and ac\", \"Corrupt JPEG\");\n      diff = t ? stbi__extend_receive(j, t) : 0;\n\n      dc = j->img_comp[b].dc_pred + diff;\n      j->img_comp[b].dc_pred = dc;\n      data[0] = (short) (dc << j->succ_low);\n   } else {\n      // refinement scan for DC coefficient\n      if (stbi__jpeg_get_bit(j))\n         data[0] += (short) (1 << j->succ_low);\n   }\n   return 1;\n}\n\n// @OPTIMIZE: store non-zigzagged during the decode passes,\n// and only de-zigzag when dequantizing\nstatic int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac)\n{\n   int k;\n   if (j->spec_start == 0) return stbi__err(\"can't merge dc and ac\", \"Corrupt JPEG\");\n\n   if (j->succ_high == 0) {\n      int shift = j->succ_low;\n\n      if (j->eob_run) {\n         --j->eob_run;\n         return 1;\n      }\n\n      k = j->spec_start;\n      do {\n         unsigned int zig;\n         int c,r,s;\n         if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);\n         c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1);\n         r = fac[c];\n         if (r) { // fast-AC path\n            k += (r >> 4) & 15; // run\n            s = r & 15; // combined length\n            j->code_buffer <<= s;\n            j->code_bits -= s;\n            zig = stbi__jpeg_dezigzag[k++];\n            data[zig] = (short) ((r >> 8) << shift);\n         } else {\n            int rs = stbi__jpeg_huff_decode(j, hac);\n            if (rs < 0) return stbi__err(\"bad huffman code\",\"Corrupt JPEG\");\n            s = rs & 15;\n            r = rs >> 4;\n            if (s == 0) {\n               if (r < 15) {\n                  j->eob_run = (1 << r);\n                  if (r)\n                     j->eob_run += stbi__jpeg_get_bits(j, r);\n                  --j->eob_run;\n                  break;\n               }\n               k += 16;\n            } else {\n               k += r;\n               zig = stbi__jpeg_dezigzag[k++];\n               data[zig] = (short) (stbi__extend_receive(j,s) << shift);\n            }\n         }\n      } while (k <= j->spec_end);\n   } else {\n      // refinement scan for these AC coefficients\n\n      short bit = (short) (1 << j->succ_low);\n\n      if (j->eob_run) {\n         --j->eob_run;\n         for (k = j->spec_start; k <= j->spec_end; ++k) {\n            short *p = &data[stbi__jpeg_dezigzag[k]];\n            if (*p != 0)\n               if (stbi__jpeg_get_bit(j))\n                  if ((*p & bit)==0) {\n                     if (*p > 0)\n                        *p += bit;\n                     else\n                        *p -= bit;\n                  }\n         }\n      } else {\n         k = j->spec_start;\n         do {\n            int r,s;\n            int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh\n            if (rs < 0) return stbi__err(\"bad huffman code\",\"Corrupt JPEG\");\n            s = rs & 15;\n            r = rs >> 4;\n            if (s == 0) {\n               if (r < 15) {\n                  j->eob_run = (1 << r) - 1;\n                  if (r)\n                     j->eob_run += stbi__jpeg_get_bits(j, r);\n                  r = 64; // force end of block\n               } else {\n                  // r=15 s=0 should write 16 0s, so we just do\n                  // a run of 15 0s and then write s (which is 0),\n                  // so we don't have to do anything special here\n               }\n            } else {\n               if (s != 1) return stbi__err(\"bad huffman code\", \"Corrupt JPEG\");\n               // sign bit\n               if (stbi__jpeg_get_bit(j))\n                  s = bit;\n               else\n                  s = -bit;\n            }\n\n            // advance by r\n            while (k <= j->spec_end) {\n               short *p = &data[stbi__jpeg_dezigzag[k++]];\n               if (*p != 0) {\n                  if (stbi__jpeg_get_bit(j))\n                     if ((*p & bit)==0) {\n                        if (*p > 0)\n                           *p += bit;\n                        else\n                           *p -= bit;\n                     }\n               } else {\n                  if (r == 0) {\n                     *p = (short) s;\n                     break;\n                  }\n                  --r;\n               }\n            }\n         } while (k <= j->spec_end);\n      }\n   }\n   return 1;\n}\n\n// take a -128..127 value and stbi__clamp it and convert to 0..255\nstbi_inline static stbi_uc stbi__clamp(int x)\n{\n   // trick to use a single test to catch both cases\n   if ((unsigned int) x > 255) {\n      if (x < 0) return 0;\n      if (x > 255) return 255;\n   }\n   return (stbi_uc) x;\n}\n\n#define stbi__f2f(x)  ((int) (((x) * 4096 + 0.5)))\n#define stbi__fsh(x)  ((x) * 4096)\n\n// derived from jidctint -- DCT_ISLOW\n#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \\\n   int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \\\n   p2 = s2;                                    \\\n   p3 = s6;                                    \\\n   p1 = (p2+p3) * stbi__f2f(0.5411961f);       \\\n   t2 = p1 + p3*stbi__f2f(-1.847759065f);      \\\n   t3 = p1 + p2*stbi__f2f( 0.765366865f);      \\\n   p2 = s0;                                    \\\n   p3 = s4;                                    \\\n   t0 = stbi__fsh(p2+p3);                      \\\n   t1 = stbi__fsh(p2-p3);                      \\\n   x0 = t0+t3;                                 \\\n   x3 = t0-t3;                                 \\\n   x1 = t1+t2;                                 \\\n   x2 = t1-t2;                                 \\\n   t0 = s7;                                    \\\n   t1 = s5;                                    \\\n   t2 = s3;                                    \\\n   t3 = s1;                                    \\\n   p3 = t0+t2;                                 \\\n   p4 = t1+t3;                                 \\\n   p1 = t0+t3;                                 \\\n   p2 = t1+t2;                                 \\\n   p5 = (p3+p4)*stbi__f2f( 1.175875602f);      \\\n   t0 = t0*stbi__f2f( 0.298631336f);           \\\n   t1 = t1*stbi__f2f( 2.053119869f);           \\\n   t2 = t2*stbi__f2f( 3.072711026f);           \\\n   t3 = t3*stbi__f2f( 1.501321110f);           \\\n   p1 = p5 + p1*stbi__f2f(-0.899976223f);      \\\n   p2 = p5 + p2*stbi__f2f(-2.562915447f);      \\\n   p3 = p3*stbi__f2f(-1.961570560f);           \\\n   p4 = p4*stbi__f2f(-0.390180644f);           \\\n   t3 += p1+p4;                                \\\n   t2 += p2+p3;                                \\\n   t1 += p2+p4;                                \\\n   t0 += p1+p3;\n\nstatic void stbi__idct_block(stbi_uc *out, int out_stride, short data[64])\n{\n   int i,val[64],*v=val;\n   stbi_uc *o;\n   short *d = data;\n\n   // columns\n   for (i=0; i < 8; ++i,++d, ++v) {\n      // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing\n      if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0\n           && d[40]==0 && d[48]==0 && d[56]==0) {\n         //    no shortcut                 0     seconds\n         //    (1|2|3|4|5|6|7)==0          0     seconds\n         //    all separate               -0.047 seconds\n         //    1 && 2|3 && 4|5 && 6|7:    -0.047 seconds\n         int dcterm = d[0]*4;\n         v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm;\n      } else {\n         STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56])\n         // constants scaled things up by 1<<12; let's bring them back\n         // down, but keep 2 extra bits of precision\n         x0 += 512; x1 += 512; x2 += 512; x3 += 512;\n         v[ 0] = (x0+t3) >> 10;\n         v[56] = (x0-t3) >> 10;\n         v[ 8] = (x1+t2) >> 10;\n         v[48] = (x1-t2) >> 10;\n         v[16] = (x2+t1) >> 10;\n         v[40] = (x2-t1) >> 10;\n         v[24] = (x3+t0) >> 10;\n         v[32] = (x3-t0) >> 10;\n      }\n   }\n\n   for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) {\n      // no fast case since the first 1D IDCT spread components out\n      STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7])\n      // constants scaled things up by 1<<12, plus we had 1<<2 from first\n      // loop, plus horizontal and vertical each scale by sqrt(8) so together\n      // we've got an extra 1<<3, so 1<<17 total we need to remove.\n      // so we want to round that, which means adding 0.5 * 1<<17,\n      // aka 65536. Also, we'll end up with -128 to 127 that we want\n      // to encode as 0..255 by adding 128, so we'll add that before the shift\n      x0 += 65536 + (128<<17);\n      x1 += 65536 + (128<<17);\n      x2 += 65536 + (128<<17);\n      x3 += 65536 + (128<<17);\n      // tried computing the shifts into temps, or'ing the temps to see\n      // if any were out of range, but that was slower\n      o[0] = stbi__clamp((x0+t3) >> 17);\n      o[7] = stbi__clamp((x0-t3) >> 17);\n      o[1] = stbi__clamp((x1+t2) >> 17);\n      o[6] = stbi__clamp((x1-t2) >> 17);\n      o[2] = stbi__clamp((x2+t1) >> 17);\n      o[5] = stbi__clamp((x2-t1) >> 17);\n      o[3] = stbi__clamp((x3+t0) >> 17);\n      o[4] = stbi__clamp((x3-t0) >> 17);\n   }\n}\n\n#ifdef STBI_SSE2\n// sse2 integer IDCT. not the fastest possible implementation but it\n// produces bit-identical results to the generic C version so it's\n// fully \"transparent\".\nstatic void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64])\n{\n   // This is constructed to match our regular (generic) integer IDCT exactly.\n   __m128i row0, row1, row2, row3, row4, row5, row6, row7;\n   __m128i tmp;\n\n   // dot product constant: even elems=x, odd elems=y\n   #define dct_const(x,y)  _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y))\n\n   // out(0) = c0[even]*x + c0[odd]*y   (c0, x, y 16-bit, out 32-bit)\n   // out(1) = c1[even]*x + c1[odd]*y\n   #define dct_rot(out0,out1, x,y,c0,c1) \\\n      __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \\\n      __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \\\n      __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \\\n      __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \\\n      __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \\\n      __m128i out1##_h = _mm_madd_epi16(c0##hi, c1)\n\n   // out = in << 12  (in 16-bit, out 32-bit)\n   #define dct_widen(out, in) \\\n      __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \\\n      __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4)\n\n   // wide add\n   #define dct_wadd(out, a, b) \\\n      __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \\\n      __m128i out##_h = _mm_add_epi32(a##_h, b##_h)\n\n   // wide sub\n   #define dct_wsub(out, a, b) \\\n      __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \\\n      __m128i out##_h = _mm_sub_epi32(a##_h, b##_h)\n\n   // butterfly a/b, add bias, then shift by \"s\" and pack\n   #define dct_bfly32o(out0, out1, a,b,bias,s) \\\n      { \\\n         __m128i abiased_l = _mm_add_epi32(a##_l, bias); \\\n         __m128i abiased_h = _mm_add_epi32(a##_h, bias); \\\n         dct_wadd(sum, abiased, b); \\\n         dct_wsub(dif, abiased, b); \\\n         out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \\\n         out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \\\n      }\n\n   // 8-bit interleave step (for transposes)\n   #define dct_interleave8(a, b) \\\n      tmp = a; \\\n      a = _mm_unpacklo_epi8(a, b); \\\n      b = _mm_unpackhi_epi8(tmp, b)\n\n   // 16-bit interleave step (for transposes)\n   #define dct_interleave16(a, b) \\\n      tmp = a; \\\n      a = _mm_unpacklo_epi16(a, b); \\\n      b = _mm_unpackhi_epi16(tmp, b)\n\n   #define dct_pass(bias,shift) \\\n      { \\\n         /* even part */ \\\n         dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \\\n         __m128i sum04 = _mm_add_epi16(row0, row4); \\\n         __m128i dif04 = _mm_sub_epi16(row0, row4); \\\n         dct_widen(t0e, sum04); \\\n         dct_widen(t1e, dif04); \\\n         dct_wadd(x0, t0e, t3e); \\\n         dct_wsub(x3, t0e, t3e); \\\n         dct_wadd(x1, t1e, t2e); \\\n         dct_wsub(x2, t1e, t2e); \\\n         /* odd part */ \\\n         dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \\\n         dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \\\n         __m128i sum17 = _mm_add_epi16(row1, row7); \\\n         __m128i sum35 = _mm_add_epi16(row3, row5); \\\n         dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \\\n         dct_wadd(x4, y0o, y4o); \\\n         dct_wadd(x5, y1o, y5o); \\\n         dct_wadd(x6, y2o, y5o); \\\n         dct_wadd(x7, y3o, y4o); \\\n         dct_bfly32o(row0,row7, x0,x7,bias,shift); \\\n         dct_bfly32o(row1,row6, x1,x6,bias,shift); \\\n         dct_bfly32o(row2,row5, x2,x5,bias,shift); \\\n         dct_bfly32o(row3,row4, x3,x4,bias,shift); \\\n      }\n\n   __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f));\n   __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f));\n   __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f));\n   __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f));\n   __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f));\n   __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f));\n   __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f));\n   __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f));\n\n   // rounding biases in column/row passes, see stbi__idct_block for explanation.\n   __m128i bias_0 = _mm_set1_epi32(512);\n   __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17));\n\n   // load\n   row0 = _mm_load_si128((const __m128i *) (data + 0*8));\n   row1 = _mm_load_si128((const __m128i *) (data + 1*8));\n   row2 = _mm_load_si128((const __m128i *) (data + 2*8));\n   row3 = _mm_load_si128((const __m128i *) (data + 3*8));\n   row4 = _mm_load_si128((const __m128i *) (data + 4*8));\n   row5 = _mm_load_si128((const __m128i *) (data + 5*8));\n   row6 = _mm_load_si128((const __m128i *) (data + 6*8));\n   row7 = _mm_load_si128((const __m128i *) (data + 7*8));\n\n   // column pass\n   dct_pass(bias_0, 10);\n\n   {\n      // 16bit 8x8 transpose pass 1\n      dct_interleave16(row0, row4);\n      dct_interleave16(row1, row5);\n      dct_interleave16(row2, row6);\n      dct_interleave16(row3, row7);\n\n      // transpose pass 2\n      dct_interleave16(row0, row2);\n      dct_interleave16(row1, row3);\n      dct_interleave16(row4, row6);\n      dct_interleave16(row5, row7);\n\n      // transpose pass 3\n      dct_interleave16(row0, row1);\n      dct_interleave16(row2, row3);\n      dct_interleave16(row4, row5);\n      dct_interleave16(row6, row7);\n   }\n\n   // row pass\n   dct_pass(bias_1, 17);\n\n   {\n      // pack\n      __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7\n      __m128i p1 = _mm_packus_epi16(row2, row3);\n      __m128i p2 = _mm_packus_epi16(row4, row5);\n      __m128i p3 = _mm_packus_epi16(row6, row7);\n\n      // 8bit 8x8 transpose pass 1\n      dct_interleave8(p0, p2); // a0e0a1e1...\n      dct_interleave8(p1, p3); // c0g0c1g1...\n\n      // transpose pass 2\n      dct_interleave8(p0, p1); // a0c0e0g0...\n      dct_interleave8(p2, p3); // b0d0f0h0...\n\n      // transpose pass 3\n      dct_interleave8(p0, p2); // a0b0c0d0...\n      dct_interleave8(p1, p3); // a4b4c4d4...\n\n      // store\n      _mm_storel_epi64((__m128i *) out, p0); out += out_stride;\n      _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride;\n      _mm_storel_epi64((__m128i *) out, p2); out += out_stride;\n      _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride;\n      _mm_storel_epi64((__m128i *) out, p1); out += out_stride;\n      _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride;\n      _mm_storel_epi64((__m128i *) out, p3); out += out_stride;\n      _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e));\n   }\n\n#undef dct_const\n#undef dct_rot\n#undef dct_widen\n#undef dct_wadd\n#undef dct_wsub\n#undef dct_bfly32o\n#undef dct_interleave8\n#undef dct_interleave16\n#undef dct_pass\n}\n\n#endif // STBI_SSE2\n\n#ifdef STBI_NEON\n\n// NEON integer IDCT. should produce bit-identical\n// results to the generic C version.\nstatic void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64])\n{\n   int16x8_t row0, row1, row2, row3, row4, row5, row6, row7;\n\n   int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f));\n   int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f));\n   int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f));\n   int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f));\n   int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f));\n   int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f));\n   int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f));\n   int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f));\n   int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f));\n   int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f));\n   int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f));\n   int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f));\n\n#define dct_long_mul(out, inq, coeff) \\\n   int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \\\n   int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff)\n\n#define dct_long_mac(out, acc, inq, coeff) \\\n   int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \\\n   int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff)\n\n#define dct_widen(out, inq) \\\n   int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \\\n   int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12)\n\n// wide add\n#define dct_wadd(out, a, b) \\\n   int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \\\n   int32x4_t out##_h = vaddq_s32(a##_h, b##_h)\n\n// wide sub\n#define dct_wsub(out, a, b) \\\n   int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \\\n   int32x4_t out##_h = vsubq_s32(a##_h, b##_h)\n\n// butterfly a/b, then shift using \"shiftop\" by \"s\" and pack\n#define dct_bfly32o(out0,out1, a,b,shiftop,s) \\\n   { \\\n      dct_wadd(sum, a, b); \\\n      dct_wsub(dif, a, b); \\\n      out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \\\n      out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \\\n   }\n\n#define dct_pass(shiftop, shift) \\\n   { \\\n      /* even part */ \\\n      int16x8_t sum26 = vaddq_s16(row2, row6); \\\n      dct_long_mul(p1e, sum26, rot0_0); \\\n      dct_long_mac(t2e, p1e, row6, rot0_1); \\\n      dct_long_mac(t3e, p1e, row2, rot0_2); \\\n      int16x8_t sum04 = vaddq_s16(row0, row4); \\\n      int16x8_t dif04 = vsubq_s16(row0, row4); \\\n      dct_widen(t0e, sum04); \\\n      dct_widen(t1e, dif04); \\\n      dct_wadd(x0, t0e, t3e); \\\n      dct_wsub(x3, t0e, t3e); \\\n      dct_wadd(x1, t1e, t2e); \\\n      dct_wsub(x2, t1e, t2e); \\\n      /* odd part */ \\\n      int16x8_t sum15 = vaddq_s16(row1, row5); \\\n      int16x8_t sum17 = vaddq_s16(row1, row7); \\\n      int16x8_t sum35 = vaddq_s16(row3, row5); \\\n      int16x8_t sum37 = vaddq_s16(row3, row7); \\\n      int16x8_t sumodd = vaddq_s16(sum17, sum35); \\\n      dct_long_mul(p5o, sumodd, rot1_0); \\\n      dct_long_mac(p1o, p5o, sum17, rot1_1); \\\n      dct_long_mac(p2o, p5o, sum35, rot1_2); \\\n      dct_long_mul(p3o, sum37, rot2_0); \\\n      dct_long_mul(p4o, sum15, rot2_1); \\\n      dct_wadd(sump13o, p1o, p3o); \\\n      dct_wadd(sump24o, p2o, p4o); \\\n      dct_wadd(sump23o, p2o, p3o); \\\n      dct_wadd(sump14o, p1o, p4o); \\\n      dct_long_mac(x4, sump13o, row7, rot3_0); \\\n      dct_long_mac(x5, sump24o, row5, rot3_1); \\\n      dct_long_mac(x6, sump23o, row3, rot3_2); \\\n      dct_long_mac(x7, sump14o, row1, rot3_3); \\\n      dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \\\n      dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \\\n      dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \\\n      dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \\\n   }\n\n   // load\n   row0 = vld1q_s16(data + 0*8);\n   row1 = vld1q_s16(data + 1*8);\n   row2 = vld1q_s16(data + 2*8);\n   row3 = vld1q_s16(data + 3*8);\n   row4 = vld1q_s16(data + 4*8);\n   row5 = vld1q_s16(data + 5*8);\n   row6 = vld1q_s16(data + 6*8);\n   row7 = vld1q_s16(data + 7*8);\n\n   // add DC bias\n   row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0));\n\n   // column pass\n   dct_pass(vrshrn_n_s32, 10);\n\n   // 16bit 8x8 transpose\n   {\n// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively.\n// whether compilers actually get this is another story, sadly.\n#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; }\n#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); }\n#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); }\n\n      // pass 1\n      dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6\n      dct_trn16(row2, row3);\n      dct_trn16(row4, row5);\n      dct_trn16(row6, row7);\n\n      // pass 2\n      dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4\n      dct_trn32(row1, row3);\n      dct_trn32(row4, row6);\n      dct_trn32(row5, row7);\n\n      // pass 3\n      dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0\n      dct_trn64(row1, row5);\n      dct_trn64(row2, row6);\n      dct_trn64(row3, row7);\n\n#undef dct_trn16\n#undef dct_trn32\n#undef dct_trn64\n   }\n\n   // row pass\n   // vrshrn_n_s32 only supports shifts up to 16, we need\n   // 17. so do a non-rounding shift of 16 first then follow\n   // up with a rounding shift by 1.\n   dct_pass(vshrn_n_s32, 16);\n\n   {\n      // pack and round\n      uint8x8_t p0 = vqrshrun_n_s16(row0, 1);\n      uint8x8_t p1 = vqrshrun_n_s16(row1, 1);\n      uint8x8_t p2 = vqrshrun_n_s16(row2, 1);\n      uint8x8_t p3 = vqrshrun_n_s16(row3, 1);\n      uint8x8_t p4 = vqrshrun_n_s16(row4, 1);\n      uint8x8_t p5 = vqrshrun_n_s16(row5, 1);\n      uint8x8_t p6 = vqrshrun_n_s16(row6, 1);\n      uint8x8_t p7 = vqrshrun_n_s16(row7, 1);\n\n      // again, these can translate into one instruction, but often don't.\n#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; }\n#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); }\n#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); }\n\n      // sadly can't use interleaved stores here since we only write\n      // 8 bytes to each scan line!\n\n      // 8x8 8-bit transpose pass 1\n      dct_trn8_8(p0, p1);\n      dct_trn8_8(p2, p3);\n      dct_trn8_8(p4, p5);\n      dct_trn8_8(p6, p7);\n\n      // pass 2\n      dct_trn8_16(p0, p2);\n      dct_trn8_16(p1, p3);\n      dct_trn8_16(p4, p6);\n      dct_trn8_16(p5, p7);\n\n      // pass 3\n      dct_trn8_32(p0, p4);\n      dct_trn8_32(p1, p5);\n      dct_trn8_32(p2, p6);\n      dct_trn8_32(p3, p7);\n\n      // store\n      vst1_u8(out, p0); out += out_stride;\n      vst1_u8(out, p1); out += out_stride;\n      vst1_u8(out, p2); out += out_stride;\n      vst1_u8(out, p3); out += out_stride;\n      vst1_u8(out, p4); out += out_stride;\n      vst1_u8(out, p5); out += out_stride;\n      vst1_u8(out, p6); out += out_stride;\n      vst1_u8(out, p7);\n\n#undef dct_trn8_8\n#undef dct_trn8_16\n#undef dct_trn8_32\n   }\n\n#undef dct_long_mul\n#undef dct_long_mac\n#undef dct_widen\n#undef dct_wadd\n#undef dct_wsub\n#undef dct_bfly32o\n#undef dct_pass\n}\n\n#endif // STBI_NEON\n\n#define STBI__MARKER_none  0xff\n// if there's a pending marker from the entropy stream, return that\n// otherwise, fetch from the stream and get a marker. if there's no\n// marker, return 0xff, which is never a valid marker value\nstatic stbi_uc stbi__get_marker(stbi__jpeg *j)\n{\n   stbi_uc x;\n   if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; }\n   x = stbi__get8(j->s);\n   if (x != 0xff) return STBI__MARKER_none;\n   while (x == 0xff)\n      x = stbi__get8(j->s); // consume repeated 0xff fill bytes\n   return x;\n}\n\n// in each scan, we'll have scan_n components, and the order\n// of the components is specified by order[]\n#define STBI__RESTART(x)     ((x) >= 0xd0 && (x) <= 0xd7)\n\n// after a restart interval, stbi__jpeg_reset the entropy decoder and\n// the dc prediction\nstatic void stbi__jpeg_reset(stbi__jpeg *j)\n{\n   j->code_bits = 0;\n   j->code_buffer = 0;\n   j->nomore = 0;\n   j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = j->img_comp[3].dc_pred = 0;\n   j->marker = STBI__MARKER_none;\n   j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff;\n   j->eob_run = 0;\n   // no more than 1<<31 MCUs if no restart_interal? that's plenty safe,\n   // since we don't even allow 1<<30 pixels\n}\n\nstatic int stbi__parse_entropy_coded_data(stbi__jpeg *z)\n{\n   stbi__jpeg_reset(z);\n   if (!z->progressive) {\n      if (z->scan_n == 1) {\n         int i,j;\n         STBI_SIMD_ALIGN(short, data[64]);\n         int n = z->order[0];\n         // non-interleaved data, we just need to process one block at a time,\n         // in trivial scanline order\n         // number of blocks to do just depends on how many actual \"pixels\" this\n         // component has, independent of interleaved MCU blocking and such\n         int w = (z->img_comp[n].x+7) >> 3;\n         int h = (z->img_comp[n].y+7) >> 3;\n         for (j=0; j < h; ++j) {\n            for (i=0; i < w; ++i) {\n               int ha = z->img_comp[n].ha;\n               if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0;\n               z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data);\n               // every data block is an MCU, so countdown the restart interval\n               if (--z->todo <= 0) {\n                  if (z->code_bits < 24) stbi__grow_buffer_unsafe(z);\n                  // if it's NOT a restart, then just bail, so we get corrupt data\n                  // rather than no data\n                  if (!STBI__RESTART(z->marker)) return 1;\n                  stbi__jpeg_reset(z);\n               }\n            }\n         }\n         return 1;\n      } else { // interleaved\n         int i,j,k,x,y;\n         STBI_SIMD_ALIGN(short, data[64]);\n         for (j=0; j < z->img_mcu_y; ++j) {\n            for (i=0; i < z->img_mcu_x; ++i) {\n               // scan an interleaved mcu... process scan_n components in order\n               for (k=0; k < z->scan_n; ++k) {\n                  int n = z->order[k];\n                  // scan out an mcu's worth of this component; that's just determined\n                  // by the basic H and V specified for the component\n                  for (y=0; y < z->img_comp[n].v; ++y) {\n                     for (x=0; x < z->img_comp[n].h; ++x) {\n                        int x2 = (i*z->img_comp[n].h + x)*8;\n                        int y2 = (j*z->img_comp[n].v + y)*8;\n                        int ha = z->img_comp[n].ha;\n                        if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0;\n                        z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data);\n                     }\n                  }\n               }\n               // after all interleaved components, that's an interleaved MCU,\n               // so now count down the restart interval\n               if (--z->todo <= 0) {\n                  if (z->code_bits < 24) stbi__grow_buffer_unsafe(z);\n                  if (!STBI__RESTART(z->marker)) return 1;\n                  stbi__jpeg_reset(z);\n               }\n            }\n         }\n         return 1;\n      }\n   } else {\n      if (z->scan_n == 1) {\n         int i,j;\n         int n = z->order[0];\n         // non-interleaved data, we just need to process one block at a time,\n         // in trivial scanline order\n         // number of blocks to do just depends on how many actual \"pixels\" this\n         // component has, independent of interleaved MCU blocking and such\n         int w = (z->img_comp[n].x+7) >> 3;\n         int h = (z->img_comp[n].y+7) >> 3;\n         for (j=0; j < h; ++j) {\n            for (i=0; i < w; ++i) {\n               short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w);\n               if (z->spec_start == 0) {\n                  if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n))\n                     return 0;\n               } else {\n                  int ha = z->img_comp[n].ha;\n                  if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha]))\n                     return 0;\n               }\n               // every data block is an MCU, so countdown the restart interval\n               if (--z->todo <= 0) {\n                  if (z->code_bits < 24) stbi__grow_buffer_unsafe(z);\n                  if (!STBI__RESTART(z->marker)) return 1;\n                  stbi__jpeg_reset(z);\n               }\n            }\n         }\n         return 1;\n      } else { // interleaved\n         int i,j,k,x,y;\n         for (j=0; j < z->img_mcu_y; ++j) {\n            for (i=0; i < z->img_mcu_x; ++i) {\n               // scan an interleaved mcu... process scan_n components in order\n               for (k=0; k < z->scan_n; ++k) {\n                  int n = z->order[k];\n                  // scan out an mcu's worth of this component; that's just determined\n                  // by the basic H and V specified for the component\n                  for (y=0; y < z->img_comp[n].v; ++y) {\n                     for (x=0; x < z->img_comp[n].h; ++x) {\n                        int x2 = (i*z->img_comp[n].h + x);\n                        int y2 = (j*z->img_comp[n].v + y);\n                        short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w);\n                        if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n))\n                           return 0;\n                     }\n                  }\n               }\n               // after all interleaved components, that's an interleaved MCU,\n               // so now count down the restart interval\n               if (--z->todo <= 0) {\n                  if (z->code_bits < 24) stbi__grow_buffer_unsafe(z);\n                  if (!STBI__RESTART(z->marker)) return 1;\n                  stbi__jpeg_reset(z);\n               }\n            }\n         }\n         return 1;\n      }\n   }\n}\n\nstatic void stbi__jpeg_dequantize(short *data, stbi__uint16 *dequant)\n{\n   int i;\n   for (i=0; i < 64; ++i)\n      data[i] *= dequant[i];\n}\n\nstatic void stbi__jpeg_finish(stbi__jpeg *z)\n{\n   if (z->progressive) {\n      // dequantize and idct the data\n      int i,j,n;\n      for (n=0; n < z->s->img_n; ++n) {\n         int w = (z->img_comp[n].x+7) >> 3;\n         int h = (z->img_comp[n].y+7) >> 3;\n         for (j=0; j < h; ++j) {\n            for (i=0; i < w; ++i) {\n               short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w);\n               stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]);\n               z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data);\n            }\n         }\n      }\n   }\n}\n\nstatic int stbi__process_marker(stbi__jpeg *z, int m)\n{\n   int L;\n   switch (m) {\n      case STBI__MARKER_none: // no marker found\n         return stbi__err(\"expected marker\",\"Corrupt JPEG\");\n\n      case 0xDD: // DRI - specify restart interval\n         if (stbi__get16be(z->s) != 4) return stbi__err(\"bad DRI len\",\"Corrupt JPEG\");\n         z->restart_interval = stbi__get16be(z->s);\n         return 1;\n\n      case 0xDB: // DQT - define quantization table\n         L = stbi__get16be(z->s)-2;\n         while (L > 0) {\n            int q = stbi__get8(z->s);\n            int p = q >> 4, sixteen = (p != 0);\n            int t = q & 15,i;\n            if (p != 0 && p != 1) return stbi__err(\"bad DQT type\",\"Corrupt JPEG\");\n            if (t > 3) return stbi__err(\"bad DQT table\",\"Corrupt JPEG\");\n\n            for (i=0; i < 64; ++i)\n               z->dequant[t][stbi__jpeg_dezigzag[i]] = (stbi__uint16)(sixteen ? stbi__get16be(z->s) : stbi__get8(z->s));\n            L -= (sixteen ? 129 : 65);\n         }\n         return L==0;\n\n      case 0xC4: // DHT - define huffman table\n         L = stbi__get16be(z->s)-2;\n         while (L > 0) {\n            stbi_uc *v;\n            int sizes[16],i,n=0;\n            int q = stbi__get8(z->s);\n            int tc = q >> 4;\n            int th = q & 15;\n            if (tc > 1 || th > 3) return stbi__err(\"bad DHT header\",\"Corrupt JPEG\");\n            for (i=0; i < 16; ++i) {\n               sizes[i] = stbi__get8(z->s);\n               n += sizes[i];\n            }\n            L -= 17;\n            if (tc == 0) {\n               if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0;\n               v = z->huff_dc[th].values;\n            } else {\n               if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0;\n               v = z->huff_ac[th].values;\n            }\n            for (i=0; i < n; ++i)\n               v[i] = stbi__get8(z->s);\n            if (tc != 0)\n               stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th);\n            L -= n;\n         }\n         return L==0;\n   }\n\n   // check for comment block or APP blocks\n   if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) {\n      L = stbi__get16be(z->s);\n      if (L < 2) {\n         if (m == 0xFE)\n            return stbi__err(\"bad COM len\",\"Corrupt JPEG\");\n         else\n            return stbi__err(\"bad APP len\",\"Corrupt JPEG\");\n      }\n      L -= 2;\n\n      if (m == 0xE0 && L >= 5) { // JFIF APP0 segment\n         static const unsigned char tag[5] = {'J','F','I','F','\\0'};\n         int ok = 1;\n         int i;\n         for (i=0; i < 5; ++i)\n            if (stbi__get8(z->s) != tag[i])\n               ok = 0;\n         L -= 5;\n         if (ok)\n            z->jfif = 1;\n      } else if (m == 0xEE && L >= 12) { // Adobe APP14 segment\n         static const unsigned char tag[6] = {'A','d','o','b','e','\\0'};\n         int ok = 1;\n         int i;\n         for (i=0; i < 6; ++i)\n            if (stbi__get8(z->s) != tag[i])\n               ok = 0;\n         L -= 6;\n         if (ok) {\n            stbi__get8(z->s); // version\n            stbi__get16be(z->s); // flags0\n            stbi__get16be(z->s); // flags1\n            z->app14_color_transform = stbi__get8(z->s); // color transform\n            L -= 6;\n         }\n      }\n\n      stbi__skip(z->s, L);\n      return 1;\n   }\n\n   return stbi__err(\"unknown marker\",\"Corrupt JPEG\");\n}\n\n// after we see SOS\nstatic int stbi__process_scan_header(stbi__jpeg *z)\n{\n   int i;\n   int Ls = stbi__get16be(z->s);\n   z->scan_n = stbi__get8(z->s);\n   if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err(\"bad SOS component count\",\"Corrupt JPEG\");\n   if (Ls != 6+2*z->scan_n) return stbi__err(\"bad SOS len\",\"Corrupt JPEG\");\n   for (i=0; i < z->scan_n; ++i) {\n      int id = stbi__get8(z->s), which;\n      int q = stbi__get8(z->s);\n      for (which = 0; which < z->s->img_n; ++which)\n         if (z->img_comp[which].id == id)\n            break;\n      if (which == z->s->img_n) return 0; // no match\n      z->img_comp[which].hd = q >> 4;   if (z->img_comp[which].hd > 3) return stbi__err(\"bad DC huff\",\"Corrupt JPEG\");\n      z->img_comp[which].ha = q & 15;   if (z->img_comp[which].ha > 3) return stbi__err(\"bad AC huff\",\"Corrupt JPEG\");\n      z->order[i] = which;\n   }\n\n   {\n      int aa;\n      z->spec_start = stbi__get8(z->s);\n      z->spec_end   = stbi__get8(z->s); // should be 63, but might be 0\n      aa = stbi__get8(z->s);\n      z->succ_high = (aa >> 4);\n      z->succ_low  = (aa & 15);\n      if (z->progressive) {\n         if (z->spec_start > 63 || z->spec_end > 63  || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13)\n            return stbi__err(\"bad SOS\", \"Corrupt JPEG\");\n      } else {\n         if (z->spec_start != 0) return stbi__err(\"bad SOS\",\"Corrupt JPEG\");\n         if (z->succ_high != 0 || z->succ_low != 0) return stbi__err(\"bad SOS\",\"Corrupt JPEG\");\n         z->spec_end = 63;\n      }\n   }\n\n   return 1;\n}\n\nstatic int stbi__free_jpeg_components(stbi__jpeg *z, int ncomp, int why)\n{\n   int i;\n   for (i=0; i < ncomp; ++i) {\n      if (z->img_comp[i].raw_data) {\n         STBI_FREE(z->img_comp[i].raw_data);\n         z->img_comp[i].raw_data = NULL;\n         z->img_comp[i].data = NULL;\n      }\n      if (z->img_comp[i].raw_coeff) {\n         STBI_FREE(z->img_comp[i].raw_coeff);\n         z->img_comp[i].raw_coeff = 0;\n         z->img_comp[i].coeff = 0;\n      }\n      if (z->img_comp[i].linebuf) {\n         STBI_FREE(z->img_comp[i].linebuf);\n         z->img_comp[i].linebuf = NULL;\n      }\n   }\n   return why;\n}\n\nstatic int stbi__process_frame_header(stbi__jpeg *z, int scan)\n{\n   stbi__context *s = z->s;\n   int Lf,p,i,q, h_max=1,v_max=1,c;\n   Lf = stbi__get16be(s);         if (Lf < 11) return stbi__err(\"bad SOF len\",\"Corrupt JPEG\"); // JPEG\n   p  = stbi__get8(s);            if (p != 8) return stbi__err(\"only 8-bit\",\"JPEG format not supported: 8-bit only\"); // JPEG baseline\n   s->img_y = stbi__get16be(s);   if (s->img_y == 0) return stbi__err(\"no header height\", \"JPEG format not supported: delayed height\"); // Legal, but we don't handle it--but neither does IJG\n   s->img_x = stbi__get16be(s);   if (s->img_x == 0) return stbi__err(\"0 width\",\"Corrupt JPEG\"); // JPEG requires\n   if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err(\"too large\",\"Very large image (corrupt?)\");\n   if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err(\"too large\",\"Very large image (corrupt?)\");\n   c = stbi__get8(s);\n   if (c != 3 && c != 1 && c != 4) return stbi__err(\"bad component count\",\"Corrupt JPEG\");\n   s->img_n = c;\n   for (i=0; i < c; ++i) {\n      z->img_comp[i].data = NULL;\n      z->img_comp[i].linebuf = NULL;\n   }\n\n   if (Lf != 8+3*s->img_n) return stbi__err(\"bad SOF len\",\"Corrupt JPEG\");\n\n   z->rgb = 0;\n   for (i=0; i < s->img_n; ++i) {\n      static const unsigned char rgb[3] = { 'R', 'G', 'B' };\n      z->img_comp[i].id = stbi__get8(s);\n      if (s->img_n == 3 && z->img_comp[i].id == rgb[i])\n         ++z->rgb;\n      q = stbi__get8(s);\n      z->img_comp[i].h = (q >> 4);  if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err(\"bad H\",\"Corrupt JPEG\");\n      z->img_comp[i].v = q & 15;    if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err(\"bad V\",\"Corrupt JPEG\");\n      z->img_comp[i].tq = stbi__get8(s);  if (z->img_comp[i].tq > 3) return stbi__err(\"bad TQ\",\"Corrupt JPEG\");\n   }\n\n   if (scan != STBI__SCAN_load) return 1;\n\n   if (!stbi__mad3sizes_valid(s->img_x, s->img_y, s->img_n, 0)) return stbi__err(\"too large\", \"Image too large to decode\");\n\n   for (i=0; i < s->img_n; ++i) {\n      if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h;\n      if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v;\n   }\n\n   // compute interleaved mcu info\n   z->img_h_max = h_max;\n   z->img_v_max = v_max;\n   z->img_mcu_w = h_max * 8;\n   z->img_mcu_h = v_max * 8;\n   // these sizes can't be more than 17 bits\n   z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w;\n   z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h;\n\n   for (i=0; i < s->img_n; ++i) {\n      // number of effective pixels (e.g. for non-interleaved MCU)\n      z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max;\n      z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max;\n      // to simplify generation, we'll allocate enough memory to decode\n      // the bogus oversized data from using interleaved MCUs and their\n      // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't\n      // discard the extra data until colorspace conversion\n      //\n      // img_mcu_x, img_mcu_y: <=17 bits; comp[i].h and .v are <=4 (checked earlier)\n      // so these muls can't overflow with 32-bit ints (which we require)\n      z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8;\n      z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8;\n      z->img_comp[i].coeff = 0;\n      z->img_comp[i].raw_coeff = 0;\n      z->img_comp[i].linebuf = NULL;\n      z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15);\n      if (z->img_comp[i].raw_data == NULL)\n         return stbi__free_jpeg_components(z, i+1, stbi__err(\"outofmem\", \"Out of memory\"));\n      // align blocks for idct using mmx/sse\n      z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15);\n      if (z->progressive) {\n         // w2, h2 are multiples of 8 (see above)\n         z->img_comp[i].coeff_w = z->img_comp[i].w2 / 8;\n         z->img_comp[i].coeff_h = z->img_comp[i].h2 / 8;\n         z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15);\n         if (z->img_comp[i].raw_coeff == NULL)\n            return stbi__free_jpeg_components(z, i+1, stbi__err(\"outofmem\", \"Out of memory\"));\n         z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15);\n      }\n   }\n\n   return 1;\n}\n\n// use comparisons since in some cases we handle more than one case (e.g. SOF)\n#define stbi__DNL(x)         ((x) == 0xdc)\n#define stbi__SOI(x)         ((x) == 0xd8)\n#define stbi__EOI(x)         ((x) == 0xd9)\n#define stbi__SOF(x)         ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2)\n#define stbi__SOS(x)         ((x) == 0xda)\n\n#define stbi__SOF_progressive(x)   ((x) == 0xc2)\n\nstatic int stbi__decode_jpeg_header(stbi__jpeg *z, int scan)\n{\n   int m;\n   z->jfif = 0;\n   z->app14_color_transform = -1; // valid values are 0,1,2\n   z->marker = STBI__MARKER_none; // initialize cached marker to empty\n   m = stbi__get_marker(z);\n   if (!stbi__SOI(m)) return stbi__err(\"no SOI\",\"Corrupt JPEG\");\n   if (scan == STBI__SCAN_type) return 1;\n   m = stbi__get_marker(z);\n   while (!stbi__SOF(m)) {\n      if (!stbi__process_marker(z,m)) return 0;\n      m = stbi__get_marker(z);\n      while (m == STBI__MARKER_none) {\n         // some files have extra padding after their blocks, so ok, we'll scan\n         if (stbi__at_eof(z->s)) return stbi__err(\"no SOF\", \"Corrupt JPEG\");\n         m = stbi__get_marker(z);\n      }\n   }\n   z->progressive = stbi__SOF_progressive(m);\n   if (!stbi__process_frame_header(z, scan)) return 0;\n   return 1;\n}\n\n// decode image to YCbCr format\nstatic int stbi__decode_jpeg_image(stbi__jpeg *j)\n{\n   int m;\n   for (m = 0; m < 4; m++) {\n      j->img_comp[m].raw_data = NULL;\n      j->img_comp[m].raw_coeff = NULL;\n   }\n   j->restart_interval = 0;\n   if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0;\n   m = stbi__get_marker(j);\n   while (!stbi__EOI(m)) {\n      if (stbi__SOS(m)) {\n         if (!stbi__process_scan_header(j)) return 0;\n         if (!stbi__parse_entropy_coded_data(j)) return 0;\n         if (j->marker == STBI__MARKER_none ) {\n            // handle 0s at the end of image data from IP Kamera 9060\n            while (!stbi__at_eof(j->s)) {\n               int x = stbi__get8(j->s);\n               if (x == 255) {\n                  j->marker = stbi__get8(j->s);\n                  break;\n               }\n            }\n            // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0\n         }\n      } else if (stbi__DNL(m)) {\n         int Ld = stbi__get16be(j->s);\n         stbi__uint32 NL = stbi__get16be(j->s);\n         if (Ld != 4) return stbi__err(\"bad DNL len\", \"Corrupt JPEG\");\n         if (NL != j->s->img_y) return stbi__err(\"bad DNL height\", \"Corrupt JPEG\");\n      } else {\n         if (!stbi__process_marker(j, m)) return 0;\n      }\n      m = stbi__get_marker(j);\n   }\n   if (j->progressive)\n      stbi__jpeg_finish(j);\n   return 1;\n}\n\n// static jfif-centered resampling (across block boundaries)\n\ntypedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1,\n                                    int w, int hs);\n\n#define stbi__div4(x) ((stbi_uc) ((x) >> 2))\n\nstatic stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)\n{\n   STBI_NOTUSED(out);\n   STBI_NOTUSED(in_far);\n   STBI_NOTUSED(w);\n   STBI_NOTUSED(hs);\n   return in_near;\n}\n\nstatic stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)\n{\n   // need to generate two samples vertically for every one in input\n   int i;\n   STBI_NOTUSED(hs);\n   for (i=0; i < w; ++i)\n      out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2);\n   return out;\n}\n\nstatic stbi_uc*  stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)\n{\n   // need to generate two samples horizontally for every one in input\n   int i;\n   stbi_uc *input = in_near;\n\n   if (w == 1) {\n      // if only one sample, can't do any interpolation\n      out[0] = out[1] = input[0];\n      return out;\n   }\n\n   out[0] = input[0];\n   out[1] = stbi__div4(input[0]*3 + input[1] + 2);\n   for (i=1; i < w-1; ++i) {\n      int n = 3*input[i]+2;\n      out[i*2+0] = stbi__div4(n+input[i-1]);\n      out[i*2+1] = stbi__div4(n+input[i+1]);\n   }\n   out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2);\n   out[i*2+1] = input[w-1];\n\n   STBI_NOTUSED(in_far);\n   STBI_NOTUSED(hs);\n\n   return out;\n}\n\n#define stbi__div16(x) ((stbi_uc) ((x) >> 4))\n\nstatic stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)\n{\n   // need to generate 2x2 samples for every one in input\n   int i,t0,t1;\n   if (w == 1) {\n      out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2);\n      return out;\n   }\n\n   t1 = 3*in_near[0] + in_far[0];\n   out[0] = stbi__div4(t1+2);\n   for (i=1; i < w; ++i) {\n      t0 = t1;\n      t1 = 3*in_near[i]+in_far[i];\n      out[i*2-1] = stbi__div16(3*t0 + t1 + 8);\n      out[i*2  ] = stbi__div16(3*t1 + t0 + 8);\n   }\n   out[w*2-1] = stbi__div4(t1+2);\n\n   STBI_NOTUSED(hs);\n\n   return out;\n}\n\n#if defined(STBI_SSE2) || defined(STBI_NEON)\nstatic stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)\n{\n   // need to generate 2x2 samples for every one in input\n   int i=0,t0,t1;\n\n   if (w == 1) {\n      out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2);\n      return out;\n   }\n\n   t1 = 3*in_near[0] + in_far[0];\n   // process groups of 8 pixels for as long as we can.\n   // note we can't handle the last pixel in a row in this loop\n   // because we need to handle the filter boundary conditions.\n   for (; i < ((w-1) & ~7); i += 8) {\n#if defined(STBI_SSE2)\n      // load and perform the vertical filtering pass\n      // this uses 3*x + y = 4*x + (y - x)\n      __m128i zero  = _mm_setzero_si128();\n      __m128i farb  = _mm_loadl_epi64((__m128i *) (in_far + i));\n      __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i));\n      __m128i farw  = _mm_unpacklo_epi8(farb, zero);\n      __m128i nearw = _mm_unpacklo_epi8(nearb, zero);\n      __m128i diff  = _mm_sub_epi16(farw, nearw);\n      __m128i nears = _mm_slli_epi16(nearw, 2);\n      __m128i curr  = _mm_add_epi16(nears, diff); // current row\n\n      // horizontal filter works the same based on shifted vers of current\n      // row. \"prev\" is current row shifted right by 1 pixel; we need to\n      // insert the previous pixel value (from t1).\n      // \"next\" is current row shifted left by 1 pixel, with first pixel\n      // of next block of 8 pixels added in.\n      __m128i prv0 = _mm_slli_si128(curr, 2);\n      __m128i nxt0 = _mm_srli_si128(curr, 2);\n      __m128i prev = _mm_insert_epi16(prv0, t1, 0);\n      __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7);\n\n      // horizontal filter, polyphase implementation since it's convenient:\n      // even pixels = 3*cur + prev = cur*4 + (prev - cur)\n      // odd  pixels = 3*cur + next = cur*4 + (next - cur)\n      // note the shared term.\n      __m128i bias  = _mm_set1_epi16(8);\n      __m128i curs = _mm_slli_epi16(curr, 2);\n      __m128i prvd = _mm_sub_epi16(prev, curr);\n      __m128i nxtd = _mm_sub_epi16(next, curr);\n      __m128i curb = _mm_add_epi16(curs, bias);\n      __m128i even = _mm_add_epi16(prvd, curb);\n      __m128i odd  = _mm_add_epi16(nxtd, curb);\n\n      // interleave even and odd pixels, then undo scaling.\n      __m128i int0 = _mm_unpacklo_epi16(even, odd);\n      __m128i int1 = _mm_unpackhi_epi16(even, odd);\n      __m128i de0  = _mm_srli_epi16(int0, 4);\n      __m128i de1  = _mm_srli_epi16(int1, 4);\n\n      // pack and write output\n      __m128i outv = _mm_packus_epi16(de0, de1);\n      _mm_storeu_si128((__m128i *) (out + i*2), outv);\n#elif defined(STBI_NEON)\n      // load and perform the vertical filtering pass\n      // this uses 3*x + y = 4*x + (y - x)\n      uint8x8_t farb  = vld1_u8(in_far + i);\n      uint8x8_t nearb = vld1_u8(in_near + i);\n      int16x8_t diff  = vreinterpretq_s16_u16(vsubl_u8(farb, nearb));\n      int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2));\n      int16x8_t curr  = vaddq_s16(nears, diff); // current row\n\n      // horizontal filter works the same based on shifted vers of current\n      // row. \"prev\" is current row shifted right by 1 pixel; we need to\n      // insert the previous pixel value (from t1).\n      // \"next\" is current row shifted left by 1 pixel, with first pixel\n      // of next block of 8 pixels added in.\n      int16x8_t prv0 = vextq_s16(curr, curr, 7);\n      int16x8_t nxt0 = vextq_s16(curr, curr, 1);\n      int16x8_t prev = vsetq_lane_s16(t1, prv0, 0);\n      int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7);\n\n      // horizontal filter, polyphase implementation since it's convenient:\n      // even pixels = 3*cur + prev = cur*4 + (prev - cur)\n      // odd  pixels = 3*cur + next = cur*4 + (next - cur)\n      // note the shared term.\n      int16x8_t curs = vshlq_n_s16(curr, 2);\n      int16x8_t prvd = vsubq_s16(prev, curr);\n      int16x8_t nxtd = vsubq_s16(next, curr);\n      int16x8_t even = vaddq_s16(curs, prvd);\n      int16x8_t odd  = vaddq_s16(curs, nxtd);\n\n      // undo scaling and round, then store with even/odd phases interleaved\n      uint8x8x2_t o;\n      o.val[0] = vqrshrun_n_s16(even, 4);\n      o.val[1] = vqrshrun_n_s16(odd,  4);\n      vst2_u8(out + i*2, o);\n#endif\n\n      // \"previous\" value for next iter\n      t1 = 3*in_near[i+7] + in_far[i+7];\n   }\n\n   t0 = t1;\n   t1 = 3*in_near[i] + in_far[i];\n   out[i*2] = stbi__div16(3*t1 + t0 + 8);\n\n   for (++i; i < w; ++i) {\n      t0 = t1;\n      t1 = 3*in_near[i]+in_far[i];\n      out[i*2-1] = stbi__div16(3*t0 + t1 + 8);\n      out[i*2  ] = stbi__div16(3*t1 + t0 + 8);\n   }\n   out[w*2-1] = stbi__div4(t1+2);\n\n   STBI_NOTUSED(hs);\n\n   return out;\n}\n#endif\n\nstatic stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)\n{\n   // resample with nearest-neighbor\n   int i,j;\n   STBI_NOTUSED(in_far);\n   for (i=0; i < w; ++i)\n      for (j=0; j < hs; ++j)\n         out[i*hs+j] = in_near[i];\n   return out;\n}\n\n// this is a reduced-precision calculation of YCbCr-to-RGB introduced\n// to make sure the code produces the same results in both SIMD and scalar\n#define stbi__float2fixed(x)  (((int) ((x) * 4096.0f + 0.5f)) << 8)\nstatic void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step)\n{\n   int i;\n   for (i=0; i < count; ++i) {\n      int y_fixed = (y[i] << 20) + (1<<19); // rounding\n      int r,g,b;\n      int cr = pcr[i] - 128;\n      int cb = pcb[i] - 128;\n      r = y_fixed +  cr* stbi__float2fixed(1.40200f);\n      g = y_fixed + (cr*-stbi__float2fixed(0.71414f)) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000);\n      b = y_fixed                                     +   cb* stbi__float2fixed(1.77200f);\n      r >>= 20;\n      g >>= 20;\n      b >>= 20;\n      if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; }\n      if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; }\n      if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; }\n      out[0] = (stbi_uc)r;\n      out[1] = (stbi_uc)g;\n      out[2] = (stbi_uc)b;\n      out[3] = 255;\n      out += step;\n   }\n}\n\n#if defined(STBI_SSE2) || defined(STBI_NEON)\nstatic void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step)\n{\n   int i = 0;\n\n#ifdef STBI_SSE2\n   // step == 3 is pretty ugly on the final interleave, and i'm not convinced\n   // it's useful in practice (you wouldn't use it for textures, for example).\n   // so just accelerate step == 4 case.\n   if (step == 4) {\n      // this is a fairly straightforward implementation and not super-optimized.\n      __m128i signflip  = _mm_set1_epi8(-0x80);\n      __m128i cr_const0 = _mm_set1_epi16(   (short) ( 1.40200f*4096.0f+0.5f));\n      __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f));\n      __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f));\n      __m128i cb_const1 = _mm_set1_epi16(   (short) ( 1.77200f*4096.0f+0.5f));\n      __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128);\n      __m128i xw = _mm_set1_epi16(255); // alpha channel\n\n      for (; i+7 < count; i += 8) {\n         // load\n         __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i));\n         __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i));\n         __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i));\n         __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128\n         __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128\n\n         // unpack to short (and left-shift cr, cb by 8)\n         __m128i yw  = _mm_unpacklo_epi8(y_bias, y_bytes);\n         __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased);\n         __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased);\n\n         // color transform\n         __m128i yws = _mm_srli_epi16(yw, 4);\n         __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw);\n         __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw);\n         __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1);\n         __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1);\n         __m128i rws = _mm_add_epi16(cr0, yws);\n         __m128i gwt = _mm_add_epi16(cb0, yws);\n         __m128i bws = _mm_add_epi16(yws, cb1);\n         __m128i gws = _mm_add_epi16(gwt, cr1);\n\n         // descale\n         __m128i rw = _mm_srai_epi16(rws, 4);\n         __m128i bw = _mm_srai_epi16(bws, 4);\n         __m128i gw = _mm_srai_epi16(gws, 4);\n\n         // back to byte, set up for transpose\n         __m128i brb = _mm_packus_epi16(rw, bw);\n         __m128i gxb = _mm_packus_epi16(gw, xw);\n\n         // transpose to interleave channels\n         __m128i t0 = _mm_unpacklo_epi8(brb, gxb);\n         __m128i t1 = _mm_unpackhi_epi8(brb, gxb);\n         __m128i o0 = _mm_unpacklo_epi16(t0, t1);\n         __m128i o1 = _mm_unpackhi_epi16(t0, t1);\n\n         // store\n         _mm_storeu_si128((__m128i *) (out + 0), o0);\n         _mm_storeu_si128((__m128i *) (out + 16), o1);\n         out += 32;\n      }\n   }\n#endif\n\n#ifdef STBI_NEON\n   // in this version, step=3 support would be easy to add. but is there demand?\n   if (step == 4) {\n      // this is a fairly straightforward implementation and not super-optimized.\n      uint8x8_t signflip = vdup_n_u8(0x80);\n      int16x8_t cr_const0 = vdupq_n_s16(   (short) ( 1.40200f*4096.0f+0.5f));\n      int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f));\n      int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f));\n      int16x8_t cb_const1 = vdupq_n_s16(   (short) ( 1.77200f*4096.0f+0.5f));\n\n      for (; i+7 < count; i += 8) {\n         // load\n         uint8x8_t y_bytes  = vld1_u8(y + i);\n         uint8x8_t cr_bytes = vld1_u8(pcr + i);\n         uint8x8_t cb_bytes = vld1_u8(pcb + i);\n         int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip));\n         int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip));\n\n         // expand to s16\n         int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4));\n         int16x8_t crw = vshll_n_s8(cr_biased, 7);\n         int16x8_t cbw = vshll_n_s8(cb_biased, 7);\n\n         // color transform\n         int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0);\n         int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0);\n         int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1);\n         int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1);\n         int16x8_t rws = vaddq_s16(yws, cr0);\n         int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1);\n         int16x8_t bws = vaddq_s16(yws, cb1);\n\n         // undo scaling, round, convert to byte\n         uint8x8x4_t o;\n         o.val[0] = vqrshrun_n_s16(rws, 4);\n         o.val[1] = vqrshrun_n_s16(gws, 4);\n         o.val[2] = vqrshrun_n_s16(bws, 4);\n         o.val[3] = vdup_n_u8(255);\n\n         // store, interleaving r/g/b/a\n         vst4_u8(out, o);\n         out += 8*4;\n      }\n   }\n#endif\n\n   for (; i < count; ++i) {\n      int y_fixed = (y[i] << 20) + (1<<19); // rounding\n      int r,g,b;\n      int cr = pcr[i] - 128;\n      int cb = pcb[i] - 128;\n      r = y_fixed + cr* stbi__float2fixed(1.40200f);\n      g = y_fixed + cr*-stbi__float2fixed(0.71414f) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000);\n      b = y_fixed                                   +   cb* stbi__float2fixed(1.77200f);\n      r >>= 20;\n      g >>= 20;\n      b >>= 20;\n      if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; }\n      if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; }\n      if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; }\n      out[0] = (stbi_uc)r;\n      out[1] = (stbi_uc)g;\n      out[2] = (stbi_uc)b;\n      out[3] = 255;\n      out += step;\n   }\n}\n#endif\n\n// set up the kernels\nstatic void stbi__setup_jpeg(stbi__jpeg *j)\n{\n   j->idct_block_kernel = stbi__idct_block;\n   j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row;\n   j->resample_row_hv_2_kernel = stbi__resample_row_hv_2;\n\n#ifdef STBI_SSE2\n   if (stbi__sse2_available()) {\n      j->idct_block_kernel = stbi__idct_simd;\n      j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd;\n      j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd;\n   }\n#endif\n\n#ifdef STBI_NEON\n   j->idct_block_kernel = stbi__idct_simd;\n   j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd;\n   j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd;\n#endif\n}\n\n// clean up the temporary component buffers\nstatic void stbi__cleanup_jpeg(stbi__jpeg *j)\n{\n   stbi__free_jpeg_components(j, j->s->img_n, 0);\n}\n\ntypedef struct\n{\n   resample_row_func resample;\n   stbi_uc *line0,*line1;\n   int hs,vs;   // expansion factor in each axis\n   int w_lores; // horizontal pixels pre-expansion\n   int ystep;   // how far through vertical expansion we are\n   int ypos;    // which pre-expansion row we're on\n} stbi__resample;\n\n// fast 0..255 * 0..255 => 0..255 rounded multiplication\nstatic stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y)\n{\n   unsigned int t = x*y + 128;\n   return (stbi_uc) ((t + (t >>8)) >> 8);\n}\n\nstatic stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp)\n{\n   int n, decode_n, is_rgb;\n   z->s->img_n = 0; // make stbi__cleanup_jpeg safe\n\n   // validate req_comp\n   if (req_comp < 0 || req_comp > 4) return stbi__errpuc(\"bad req_comp\", \"Internal error\");\n\n   // load a jpeg image from whichever source, but leave in YCbCr format\n   if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; }\n\n   // determine actual number of components to generate\n   n = req_comp ? req_comp : z->s->img_n >= 3 ? 3 : 1;\n\n   is_rgb = z->s->img_n == 3 && (z->rgb == 3 || (z->app14_color_transform == 0 && !z->jfif));\n\n   if (z->s->img_n == 3 && n < 3 && !is_rgb)\n      decode_n = 1;\n   else\n      decode_n = z->s->img_n;\n\n   // resample and color-convert\n   {\n      int k;\n      unsigned int i,j;\n      stbi_uc *output;\n      stbi_uc *coutput[4] = { NULL, NULL, NULL, NULL };\n\n      stbi__resample res_comp[4];\n\n      for (k=0; k < decode_n; ++k) {\n         stbi__resample *r = &res_comp[k];\n\n         // allocate line buffer big enough for upsampling off the edges\n         // with upsample factor of 4\n         z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3);\n         if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc(\"outofmem\", \"Out of memory\"); }\n\n         r->hs      = z->img_h_max / z->img_comp[k].h;\n         r->vs      = z->img_v_max / z->img_comp[k].v;\n         r->ystep   = r->vs >> 1;\n         r->w_lores = (z->s->img_x + r->hs-1) / r->hs;\n         r->ypos    = 0;\n         r->line0   = r->line1 = z->img_comp[k].data;\n\n         if      (r->hs == 1 && r->vs == 1) r->resample = resample_row_1;\n         else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2;\n         else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2;\n         else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel;\n         else                               r->resample = stbi__resample_row_generic;\n      }\n\n      // can't error after this so, this is safe\n      output = (stbi_uc *) stbi__malloc_mad3(n, z->s->img_x, z->s->img_y, 1);\n      if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc(\"outofmem\", \"Out of memory\"); }\n\n      // now go ahead and resample\n      for (j=0; j < z->s->img_y; ++j) {\n         stbi_uc *out = output + n * z->s->img_x * j;\n         for (k=0; k < decode_n; ++k) {\n            stbi__resample *r = &res_comp[k];\n            int y_bot = r->ystep >= (r->vs >> 1);\n            coutput[k] = r->resample(z->img_comp[k].linebuf,\n                                     y_bot ? r->line1 : r->line0,\n                                     y_bot ? r->line0 : r->line1,\n                                     r->w_lores, r->hs);\n            if (++r->ystep >= r->vs) {\n               r->ystep = 0;\n               r->line0 = r->line1;\n               if (++r->ypos < z->img_comp[k].y)\n                  r->line1 += z->img_comp[k].w2;\n            }\n         }\n         if (n >= 3) {\n            stbi_uc *y = coutput[0];\n            if (z->s->img_n == 3) {\n               if (is_rgb) {\n                  for (i=0; i < z->s->img_x; ++i) {\n                     out[0] = y[i];\n                     out[1] = coutput[1][i];\n                     out[2] = coutput[2][i];\n                     out[3] = 255;\n                     out += n;\n                  }\n               } else {\n                  z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n);\n               }\n            } else if (z->s->img_n == 4) {\n               if (z->app14_color_transform == 0) { // CMYK\n                  for (i=0; i < z->s->img_x; ++i) {\n                     stbi_uc m = coutput[3][i];\n                     out[0] = stbi__blinn_8x8(coutput[0][i], m);\n                     out[1] = stbi__blinn_8x8(coutput[1][i], m);\n                     out[2] = stbi__blinn_8x8(coutput[2][i], m);\n                     out[3] = 255;\n                     out += n;\n                  }\n               } else if (z->app14_color_transform == 2) { // YCCK\n                  z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n);\n                  for (i=0; i < z->s->img_x; ++i) {\n                     stbi_uc m = coutput[3][i];\n                     out[0] = stbi__blinn_8x8(255 - out[0], m);\n                     out[1] = stbi__blinn_8x8(255 - out[1], m);\n                     out[2] = stbi__blinn_8x8(255 - out[2], m);\n                     out += n;\n                  }\n               } else { // YCbCr + alpha?  Ignore the fourth channel for now\n                  z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n);\n               }\n            } else\n               for (i=0; i < z->s->img_x; ++i) {\n                  out[0] = out[1] = out[2] = y[i];\n                  out[3] = 255; // not used if n==3\n                  out += n;\n               }\n         } else {\n            if (is_rgb) {\n               if (n == 1)\n                  for (i=0; i < z->s->img_x; ++i)\n                     *out++ = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]);\n               else {\n                  for (i=0; i < z->s->img_x; ++i, out += 2) {\n                     out[0] = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]);\n                     out[1] = 255;\n                  }\n               }\n            } else if (z->s->img_n == 4 && z->app14_color_transform == 0) {\n               for (i=0; i < z->s->img_x; ++i) {\n                  stbi_uc m = coutput[3][i];\n                  stbi_uc r = stbi__blinn_8x8(coutput[0][i], m);\n                  stbi_uc g = stbi__blinn_8x8(coutput[1][i], m);\n                  stbi_uc b = stbi__blinn_8x8(coutput[2][i], m);\n                  out[0] = stbi__compute_y(r, g, b);\n                  out[1] = 255;\n                  out += n;\n               }\n            } else if (z->s->img_n == 4 && z->app14_color_transform == 2) {\n               for (i=0; i < z->s->img_x; ++i) {\n                  out[0] = stbi__blinn_8x8(255 - coutput[0][i], coutput[3][i]);\n                  out[1] = 255;\n                  out += n;\n               }\n            } else {\n               stbi_uc *y = coutput[0];\n               if (n == 1)\n                  for (i=0; i < z->s->img_x; ++i) out[i] = y[i];\n               else\n                  for (i=0; i < z->s->img_x; ++i) { *out++ = y[i]; *out++ = 255; }\n            }\n         }\n      }\n      stbi__cleanup_jpeg(z);\n      *out_x = z->s->img_x;\n      *out_y = z->s->img_y;\n      if (comp) *comp = z->s->img_n >= 3 ? 3 : 1; // report original components, not output\n      return output;\n   }\n}\n\nstatic void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)\n{\n   unsigned char* result;\n   stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg));\n   STBI_NOTUSED(ri);\n   j->s = s;\n   stbi__setup_jpeg(j);\n   result = load_jpeg_image(j, x,y,comp,req_comp);\n   STBI_FREE(j);\n   return result;\n}\n\nstatic int stbi__jpeg_test(stbi__context *s)\n{\n   int r;\n   stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg));\n   j->s = s;\n   stbi__setup_jpeg(j);\n   r = stbi__decode_jpeg_header(j, STBI__SCAN_type);\n   stbi__rewind(s);\n   STBI_FREE(j);\n   return r;\n}\n\nstatic int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp)\n{\n   if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) {\n      stbi__rewind( j->s );\n      return 0;\n   }\n   if (x) *x = j->s->img_x;\n   if (y) *y = j->s->img_y;\n   if (comp) *comp = j->s->img_n >= 3 ? 3 : 1;\n   return 1;\n}\n\nstatic int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp)\n{\n   int result;\n   stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg)));\n   j->s = s;\n   result = stbi__jpeg_info_raw(j, x, y, comp);\n   STBI_FREE(j);\n   return result;\n}\n#endif\n\n// public domain zlib decode    v0.2  Sean Barrett 2006-11-18\n//    simple implementation\n//      - all input must be provided in an upfront buffer\n//      - all output is written to a single output buffer (can malloc/realloc)\n//    performance\n//      - fast huffman\n\n#ifndef STBI_NO_ZLIB\n\n// fast-way is faster to check than jpeg huffman, but slow way is slower\n#define STBI__ZFAST_BITS  9 // accelerate all cases in default tables\n#define STBI__ZFAST_MASK  ((1 << STBI__ZFAST_BITS) - 1)\n\n// zlib-style huffman encoding\n// (jpegs packs from left, zlib from right, so can't share code)\ntypedef struct\n{\n   stbi__uint16 fast[1 << STBI__ZFAST_BITS];\n   stbi__uint16 firstcode[16];\n   int maxcode[17];\n   stbi__uint16 firstsymbol[16];\n   stbi_uc  size[288];\n   stbi__uint16 value[288];\n} stbi__zhuffman;\n\nstbi_inline static int stbi__bitreverse16(int n)\n{\n  n = ((n & 0xAAAA) >>  1) | ((n & 0x5555) << 1);\n  n = ((n & 0xCCCC) >>  2) | ((n & 0x3333) << 2);\n  n = ((n & 0xF0F0) >>  4) | ((n & 0x0F0F) << 4);\n  n = ((n & 0xFF00) >>  8) | ((n & 0x00FF) << 8);\n  return n;\n}\n\nstbi_inline static int stbi__bit_reverse(int v, int bits)\n{\n   STBI_ASSERT(bits <= 16);\n   // to bit reverse n bits, reverse 16 and shift\n   // e.g. 11 bits, bit reverse and shift away 5\n   return stbi__bitreverse16(v) >> (16-bits);\n}\n\nstatic int stbi__zbuild_huffman(stbi__zhuffman *z, const stbi_uc *sizelist, int num)\n{\n   int i,k=0;\n   int code, next_code[16], sizes[17];\n\n   // DEFLATE spec for generating codes\n   memset(sizes, 0, sizeof(sizes));\n   memset(z->fast, 0, sizeof(z->fast));\n   for (i=0; i < num; ++i)\n      ++sizes[sizelist[i]];\n   sizes[0] = 0;\n   for (i=1; i < 16; ++i)\n      if (sizes[i] > (1 << i))\n         return stbi__err(\"bad sizes\", \"Corrupt PNG\");\n   code = 0;\n   for (i=1; i < 16; ++i) {\n      next_code[i] = code;\n      z->firstcode[i] = (stbi__uint16) code;\n      z->firstsymbol[i] = (stbi__uint16) k;\n      code = (code + sizes[i]);\n      if (sizes[i])\n         if (code-1 >= (1 << i)) return stbi__err(\"bad codelengths\",\"Corrupt PNG\");\n      z->maxcode[i] = code << (16-i); // preshift for inner loop\n      code <<= 1;\n      k += sizes[i];\n   }\n   z->maxcode[16] = 0x10000; // sentinel\n   for (i=0; i < num; ++i) {\n      int s = sizelist[i];\n      if (s) {\n         int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s];\n         stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i);\n         z->size [c] = (stbi_uc     ) s;\n         z->value[c] = (stbi__uint16) i;\n         if (s <= STBI__ZFAST_BITS) {\n            int j = stbi__bit_reverse(next_code[s],s);\n            while (j < (1 << STBI__ZFAST_BITS)) {\n               z->fast[j] = fastv;\n               j += (1 << s);\n            }\n         }\n         ++next_code[s];\n      }\n   }\n   return 1;\n}\n\n// zlib-from-memory implementation for PNG reading\n//    because PNG allows splitting the zlib stream arbitrarily,\n//    and it's annoying structurally to have PNG call ZLIB call PNG,\n//    we require PNG read all the IDATs and combine them into a single\n//    memory buffer\n\ntypedef struct\n{\n   stbi_uc *zbuffer, *zbuffer_end;\n   int num_bits;\n   stbi__uint32 code_buffer;\n\n   char *zout;\n   char *zout_start;\n   char *zout_end;\n   int   z_expandable;\n\n   stbi__zhuffman z_length, z_distance;\n} stbi__zbuf;\n\nstbi_inline static int stbi__zeof(stbi__zbuf *z)\n{\n   return (z->zbuffer >= z->zbuffer_end);\n}\n\nstbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z)\n{\n   return stbi__zeof(z) ? 0 : *z->zbuffer++;\n}\n\nstatic void stbi__fill_bits(stbi__zbuf *z)\n{\n   do {\n      if (z->code_buffer >= (1U << z->num_bits)) {\n        z->zbuffer = z->zbuffer_end;  /* treat this as EOF so we fail. */\n        return;\n      }\n      z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits;\n      z->num_bits += 8;\n   } while (z->num_bits <= 24);\n}\n\nstbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n)\n{\n   unsigned int k;\n   if (z->num_bits < n) stbi__fill_bits(z);\n   k = z->code_buffer & ((1 << n) - 1);\n   z->code_buffer >>= n;\n   z->num_bits -= n;\n   return k;\n}\n\nstatic int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z)\n{\n   int b,s,k;\n   // not resolved by fast table, so compute it the slow way\n   // use jpeg approach, which requires MSbits at top\n   k = stbi__bit_reverse(a->code_buffer, 16);\n   for (s=STBI__ZFAST_BITS+1; ; ++s)\n      if (k < z->maxcode[s])\n         break;\n   if (s >= 16) return -1; // invalid code!\n   // code size is s, so:\n   b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s];\n   if (b >= sizeof (z->size)) return -1; // some data was corrupt somewhere!\n   if (z->size[b] != s) return -1;  // was originally an assert, but report failure instead.\n   a->code_buffer >>= s;\n   a->num_bits -= s;\n   return z->value[b];\n}\n\nstbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z)\n{\n   int b,s;\n   if (a->num_bits < 16) {\n      if (stbi__zeof(a)) {\n         return -1;   /* report error for unexpected end of data. */\n      }\n      stbi__fill_bits(a);\n   }\n   b = z->fast[a->code_buffer & STBI__ZFAST_MASK];\n   if (b) {\n      s = b >> 9;\n      a->code_buffer >>= s;\n      a->num_bits -= s;\n      return b & 511;\n   }\n   return stbi__zhuffman_decode_slowpath(a, z);\n}\n\nstatic int stbi__zexpand(stbi__zbuf *z, char *zout, int n)  // need to make room for n bytes\n{\n   char *q;\n   unsigned int cur, limit, old_limit;\n   z->zout = zout;\n   if (!z->z_expandable) return stbi__err(\"output buffer limit\",\"Corrupt PNG\");\n   cur   = (unsigned int) (z->zout - z->zout_start);\n   limit = old_limit = (unsigned) (z->zout_end - z->zout_start);\n   if (UINT_MAX - cur < (unsigned) n) return stbi__err(\"outofmem\", \"Out of memory\");\n   while (cur + n > limit) {\n      if(limit > UINT_MAX / 2) return stbi__err(\"outofmem\", \"Out of memory\");\n      limit *= 2;\n   }\n   q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit);\n   STBI_NOTUSED(old_limit);\n   if (q == NULL) return stbi__err(\"outofmem\", \"Out of memory\");\n   z->zout_start = q;\n   z->zout       = q + cur;\n   z->zout_end   = q + limit;\n   return 1;\n}\n\nstatic const int stbi__zlength_base[31] = {\n   3,4,5,6,7,8,9,10,11,13,\n   15,17,19,23,27,31,35,43,51,59,\n   67,83,99,115,131,163,195,227,258,0,0 };\n\nstatic const int stbi__zlength_extra[31]=\n{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 };\n\nstatic const int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,\n257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0};\n\nstatic const int stbi__zdist_extra[32] =\n{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13};\n\nstatic int stbi__parse_huffman_block(stbi__zbuf *a)\n{\n   char *zout = a->zout;\n   for(;;) {\n      int z = stbi__zhuffman_decode(a, &a->z_length);\n      if (z < 256) {\n         if (z < 0) return stbi__err(\"bad huffman code\",\"Corrupt PNG\"); // error in huffman codes\n         if (zout >= a->zout_end) {\n            if (!stbi__zexpand(a, zout, 1)) return 0;\n            zout = a->zout;\n         }\n         *zout++ = (char) z;\n      } else {\n         stbi_uc *p;\n         int len,dist;\n         if (z == 256) {\n            a->zout = zout;\n            return 1;\n         }\n         z -= 257;\n         len = stbi__zlength_base[z];\n         if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]);\n         z = stbi__zhuffman_decode(a, &a->z_distance);\n         if (z < 0) return stbi__err(\"bad huffman code\",\"Corrupt PNG\");\n         dist = stbi__zdist_base[z];\n         if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]);\n         if (zout - a->zout_start < dist) return stbi__err(\"bad dist\",\"Corrupt PNG\");\n         if (zout + len > a->zout_end) {\n            if (!stbi__zexpand(a, zout, len)) return 0;\n            zout = a->zout;\n         }\n         p = (stbi_uc *) (zout - dist);\n         if (dist == 1) { // run of one byte; common in images.\n            stbi_uc v = *p;\n            if (len) { do *zout++ = v; while (--len); }\n         } else {\n            if (len) { do *zout++ = *p++; while (--len); }\n         }\n      }\n   }\n}\n\nstatic int stbi__compute_huffman_codes(stbi__zbuf *a)\n{\n   static const stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 };\n   stbi__zhuffman z_codelength;\n   stbi_uc lencodes[286+32+137];//padding for maximum single op\n   stbi_uc codelength_sizes[19];\n   int i,n;\n\n   int hlit  = stbi__zreceive(a,5) + 257;\n   int hdist = stbi__zreceive(a,5) + 1;\n   int hclen = stbi__zreceive(a,4) + 4;\n   int ntot  = hlit + hdist;\n\n   memset(codelength_sizes, 0, sizeof(codelength_sizes));\n   for (i=0; i < hclen; ++i) {\n      int s = stbi__zreceive(a,3);\n      codelength_sizes[length_dezigzag[i]] = (stbi_uc) s;\n   }\n   if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0;\n\n   n = 0;\n   while (n < ntot) {\n      int c = stbi__zhuffman_decode(a, &z_codelength);\n      if (c < 0 || c >= 19) return stbi__err(\"bad codelengths\", \"Corrupt PNG\");\n      if (c < 16)\n         lencodes[n++] = (stbi_uc) c;\n      else {\n         stbi_uc fill = 0;\n         if (c == 16) {\n            c = stbi__zreceive(a,2)+3;\n            if (n == 0) return stbi__err(\"bad codelengths\", \"Corrupt PNG\");\n            fill = lencodes[n-1];\n         } else if (c == 17) {\n            c = stbi__zreceive(a,3)+3;\n         } else if (c == 18) {\n            c = stbi__zreceive(a,7)+11;\n         } else {\n            return stbi__err(\"bad codelengths\", \"Corrupt PNG\");\n         }\n         if (ntot - n < c) return stbi__err(\"bad codelengths\", \"Corrupt PNG\");\n         memset(lencodes+n, fill, c);\n         n += c;\n      }\n   }\n   if (n != ntot) return stbi__err(\"bad codelengths\",\"Corrupt PNG\");\n   if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0;\n   if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0;\n   return 1;\n}\n\nstatic int stbi__parse_uncompressed_block(stbi__zbuf *a)\n{\n   stbi_uc header[4];\n   int len,nlen,k;\n   if (a->num_bits & 7)\n      stbi__zreceive(a, a->num_bits & 7); // discard\n   // drain the bit-packed data into header\n   k = 0;\n   while (a->num_bits > 0) {\n      header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check\n      a->code_buffer >>= 8;\n      a->num_bits -= 8;\n   }\n   if (a->num_bits < 0) return stbi__err(\"zlib corrupt\",\"Corrupt PNG\");\n   // now fill header the normal way\n   while (k < 4)\n      header[k++] = stbi__zget8(a);\n   len  = header[1] * 256 + header[0];\n   nlen = header[3] * 256 + header[2];\n   if (nlen != (len ^ 0xffff)) return stbi__err(\"zlib corrupt\",\"Corrupt PNG\");\n   if (a->zbuffer + len > a->zbuffer_end) return stbi__err(\"read past buffer\",\"Corrupt PNG\");\n   if (a->zout + len > a->zout_end)\n      if (!stbi__zexpand(a, a->zout, len)) return 0;\n   memcpy(a->zout, a->zbuffer, len);\n   a->zbuffer += len;\n   a->zout += len;\n   return 1;\n}\n\nstatic int stbi__parse_zlib_header(stbi__zbuf *a)\n{\n   int cmf   = stbi__zget8(a);\n   int cm    = cmf & 15;\n   /* int cinfo = cmf >> 4; */\n   int flg   = stbi__zget8(a);\n   if (stbi__zeof(a)) return stbi__err(\"bad zlib header\",\"Corrupt PNG\"); // zlib spec\n   if ((cmf*256+flg) % 31 != 0) return stbi__err(\"bad zlib header\",\"Corrupt PNG\"); // zlib spec\n   if (flg & 32) return stbi__err(\"no preset dict\",\"Corrupt PNG\"); // preset dictionary not allowed in png\n   if (cm != 8) return stbi__err(\"bad compression\",\"Corrupt PNG\"); // DEFLATE required for png\n   // window = 1 << (8 + cinfo)... but who cares, we fully buffer output\n   return 1;\n}\n\nstatic const stbi_uc stbi__zdefault_length[288] =\n{\n   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,8,8,8,\n   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,8,8,8,\n   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,8,8,8,\n   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,8,8,8,\n   8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,\n   9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,\n   9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,\n   9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,\n   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8\n};\nstatic const stbi_uc stbi__zdefault_distance[32] =\n{\n   5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5\n};\n/*\nInit algorithm:\n{\n   int i;   // use <= to match clearly with spec\n   for (i=0; i <= 143; ++i)     stbi__zdefault_length[i]   = 8;\n   for (   ; i <= 255; ++i)     stbi__zdefault_length[i]   = 9;\n   for (   ; i <= 279; ++i)     stbi__zdefault_length[i]   = 7;\n   for (   ; i <= 287; ++i)     stbi__zdefault_length[i]   = 8;\n\n   for (i=0; i <=  31; ++i)     stbi__zdefault_distance[i] = 5;\n}\n*/\n\nstatic int stbi__parse_zlib(stbi__zbuf *a, int parse_header)\n{\n   int final, type;\n   if (parse_header)\n      if (!stbi__parse_zlib_header(a)) return 0;\n   a->num_bits = 0;\n   a->code_buffer = 0;\n   do {\n      final = stbi__zreceive(a,1);\n      type = stbi__zreceive(a,2);\n      if (type == 0) {\n         if (!stbi__parse_uncompressed_block(a)) return 0;\n      } else if (type == 3) {\n         return 0;\n      } else {\n         if (type == 1) {\n            // use fixed code lengths\n            if (!stbi__zbuild_huffman(&a->z_length  , stbi__zdefault_length  , 288)) return 0;\n            if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance,  32)) return 0;\n         } else {\n            if (!stbi__compute_huffman_codes(a)) return 0;\n         }\n         if (!stbi__parse_huffman_block(a)) return 0;\n      }\n   } while (!final);\n   return 1;\n}\n\nstatic int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header)\n{\n   a->zout_start = obuf;\n   a->zout       = obuf;\n   a->zout_end   = obuf + olen;\n   a->z_expandable = exp;\n\n   return stbi__parse_zlib(a, parse_header);\n}\n\nSTBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen)\n{\n   stbi__zbuf a;\n   char *p = (char *) stbi__malloc(initial_size);\n   if (p == NULL) return NULL;\n   a.zbuffer = (stbi_uc *) buffer;\n   a.zbuffer_end = (stbi_uc *) buffer + len;\n   if (stbi__do_zlib(&a, p, initial_size, 1, 1)) {\n      if (outlen) *outlen = (int) (a.zout - a.zout_start);\n      return a.zout_start;\n   } else {\n      STBI_FREE(a.zout_start);\n      return NULL;\n   }\n}\n\nSTBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen)\n{\n   return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen);\n}\n\nSTBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header)\n{\n   stbi__zbuf a;\n   char *p = (char *) stbi__malloc(initial_size);\n   if (p == NULL) return NULL;\n   a.zbuffer = (stbi_uc *) buffer;\n   a.zbuffer_end = (stbi_uc *) buffer + len;\n   if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) {\n      if (outlen) *outlen = (int) (a.zout - a.zout_start);\n      return a.zout_start;\n   } else {\n      STBI_FREE(a.zout_start);\n      return NULL;\n   }\n}\n\nSTBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen)\n{\n   stbi__zbuf a;\n   a.zbuffer = (stbi_uc *) ibuffer;\n   a.zbuffer_end = (stbi_uc *) ibuffer + ilen;\n   if (stbi__do_zlib(&a, obuffer, olen, 0, 1))\n      return (int) (a.zout - a.zout_start);\n   else\n      return -1;\n}\n\nSTBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen)\n{\n   stbi__zbuf a;\n   char *p = (char *) stbi__malloc(16384);\n   if (p == NULL) return NULL;\n   a.zbuffer = (stbi_uc *) buffer;\n   a.zbuffer_end = (stbi_uc *) buffer+len;\n   if (stbi__do_zlib(&a, p, 16384, 1, 0)) {\n      if (outlen) *outlen = (int) (a.zout - a.zout_start);\n      return a.zout_start;\n   } else {\n      STBI_FREE(a.zout_start);\n      return NULL;\n   }\n}\n\nSTBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen)\n{\n   stbi__zbuf a;\n   a.zbuffer = (stbi_uc *) ibuffer;\n   a.zbuffer_end = (stbi_uc *) ibuffer + ilen;\n   if (stbi__do_zlib(&a, obuffer, olen, 0, 0))\n      return (int) (a.zout - a.zout_start);\n   else\n      return -1;\n}\n#endif\n\n// public domain \"baseline\" PNG decoder   v0.10  Sean Barrett 2006-11-18\n//    simple implementation\n//      - only 8-bit samples\n//      - no CRC checking\n//      - allocates lots of intermediate memory\n//        - avoids problem of streaming data between subsystems\n//        - avoids explicit window management\n//    performance\n//      - uses stb_zlib, a PD zlib implementation with fast huffman decoding\n\n#ifndef STBI_NO_PNG\ntypedef struct\n{\n   stbi__uint32 length;\n   stbi__uint32 type;\n} stbi__pngchunk;\n\nstatic stbi__pngchunk stbi__get_chunk_header(stbi__context *s)\n{\n   stbi__pngchunk c;\n   c.length = stbi__get32be(s);\n   c.type   = stbi__get32be(s);\n   return c;\n}\n\nstatic int stbi__check_png_header(stbi__context *s)\n{\n   static const stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 };\n   int i;\n   for (i=0; i < 8; ++i)\n      if (stbi__get8(s) != png_sig[i]) return stbi__err(\"bad png sig\",\"Not a PNG\");\n   return 1;\n}\n\ntypedef struct\n{\n   stbi__context *s;\n   stbi_uc *idata, *expanded, *out;\n   int depth;\n} stbi__png;\n\n\nenum {\n   STBI__F_none=0,\n   STBI__F_sub=1,\n   STBI__F_up=2,\n   STBI__F_avg=3,\n   STBI__F_paeth=4,\n   // synthetic filters used for first scanline to avoid needing a dummy row of 0s\n   STBI__F_avg_first,\n   STBI__F_paeth_first\n};\n\nstatic stbi_uc first_row_filter[5] =\n{\n   STBI__F_none,\n   STBI__F_sub,\n   STBI__F_none,\n   STBI__F_avg_first,\n   STBI__F_paeth_first\n};\n\nstatic int stbi__paeth(int a, int b, int c)\n{\n   int p = a + b - c;\n   int pa = abs(p-a);\n   int pb = abs(p-b);\n   int pc = abs(p-c);\n   if (pa <= pb && pa <= pc) return a;\n   if (pb <= pc) return b;\n   return c;\n}\n\nstatic const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 };\n\n// create the png data from post-deflated data\nstatic int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color)\n{\n   int bytes = (depth == 16? 2 : 1);\n   stbi__context *s = a->s;\n   stbi__uint32 i,j,stride = x*out_n*bytes;\n   stbi__uint32 img_len, img_width_bytes;\n   int k;\n   int img_n = s->img_n; // copy it into a local for later\n\n   int output_bytes = out_n*bytes;\n   int filter_bytes = img_n*bytes;\n   int width = x;\n\n   STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1);\n   a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into\n   if (!a->out) return stbi__err(\"outofmem\", \"Out of memory\");\n\n   if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err(\"too large\", \"Corrupt PNG\");\n   img_width_bytes = (((img_n * x * depth) + 7) >> 3);\n   img_len = (img_width_bytes + 1) * y;\n\n   // we used to check for exact match between raw_len and img_len on non-interlaced PNGs,\n   // but issue #276 reported a PNG in the wild that had extra data at the end (all zeros),\n   // so just check for raw_len < img_len always.\n   if (raw_len < img_len) return stbi__err(\"not enough pixels\",\"Corrupt PNG\");\n\n   for (j=0; j < y; ++j) {\n      stbi_uc *cur = a->out + stride*j;\n      stbi_uc *prior;\n      int filter = *raw++;\n\n      if (filter > 4)\n         return stbi__err(\"invalid filter\",\"Corrupt PNG\");\n\n      if (depth < 8) {\n         if (img_width_bytes > x) return stbi__err(\"invalid width\",\"Corrupt PNG\");\n         cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place\n         filter_bytes = 1;\n         width = img_width_bytes;\n      }\n      prior = cur - stride; // bugfix: need to compute this after 'cur +=' computation above\n\n      // if first row, use special filter that doesn't sample previous row\n      if (j == 0) filter = first_row_filter[filter];\n\n      // handle first byte explicitly\n      for (k=0; k < filter_bytes; ++k) {\n         switch (filter) {\n            case STBI__F_none       : cur[k] = raw[k]; break;\n            case STBI__F_sub        : cur[k] = raw[k]; break;\n            case STBI__F_up         : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break;\n            case STBI__F_avg        : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break;\n            case STBI__F_paeth      : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break;\n            case STBI__F_avg_first  : cur[k] = raw[k]; break;\n            case STBI__F_paeth_first: cur[k] = raw[k]; break;\n         }\n      }\n\n      if (depth == 8) {\n         if (img_n != out_n)\n            cur[img_n] = 255; // first pixel\n         raw += img_n;\n         cur += out_n;\n         prior += out_n;\n      } else if (depth == 16) {\n         if (img_n != out_n) {\n            cur[filter_bytes]   = 255; // first pixel top byte\n            cur[filter_bytes+1] = 255; // first pixel bottom byte\n         }\n         raw += filter_bytes;\n         cur += output_bytes;\n         prior += output_bytes;\n      } else {\n         raw += 1;\n         cur += 1;\n         prior += 1;\n      }\n\n      // this is a little gross, so that we don't switch per-pixel or per-component\n      if (depth < 8 || img_n == out_n) {\n         int nk = (width - 1)*filter_bytes;\n         #define STBI__CASE(f) \\\n             case f:     \\\n                for (k=0; k < nk; ++k)\n         switch (filter) {\n            // \"none\" filter turns into a memcpy here; make that explicit.\n            case STBI__F_none:         memcpy(cur, raw, nk); break;\n            STBI__CASE(STBI__F_sub)          { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break;\n            STBI__CASE(STBI__F_up)           { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break;\n            STBI__CASE(STBI__F_avg)          { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break;\n            STBI__CASE(STBI__F_paeth)        { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break;\n            STBI__CASE(STBI__F_avg_first)    { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break;\n            STBI__CASE(STBI__F_paeth_first)  { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); } break;\n         }\n         #undef STBI__CASE\n         raw += nk;\n      } else {\n         STBI_ASSERT(img_n+1 == out_n);\n         #define STBI__CASE(f) \\\n             case f:     \\\n                for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \\\n                   for (k=0; k < filter_bytes; ++k)\n         switch (filter) {\n            STBI__CASE(STBI__F_none)         { cur[k] = raw[k]; } break;\n            STBI__CASE(STBI__F_sub)          { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break;\n            STBI__CASE(STBI__F_up)           { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break;\n            STBI__CASE(STBI__F_avg)          { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break;\n            STBI__CASE(STBI__F_paeth)        { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break;\n            STBI__CASE(STBI__F_avg_first)    { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break;\n            STBI__CASE(STBI__F_paeth_first)  { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); } break;\n         }\n         #undef STBI__CASE\n\n         // the loop above sets the high byte of the pixels' alpha, but for\n         // 16 bit png files we also need the low byte set. we'll do that here.\n         if (depth == 16) {\n            cur = a->out + stride*j; // start at the beginning of the row again\n            for (i=0; i < x; ++i,cur+=output_bytes) {\n               cur[filter_bytes+1] = 255;\n            }\n         }\n      }\n   }\n\n   // we make a separate pass to expand bits to pixels; for performance,\n   // this could run two scanlines behind the above code, so it won't\n   // intefere with filtering but will still be in the cache.\n   if (depth < 8) {\n      for (j=0; j < y; ++j) {\n         stbi_uc *cur = a->out + stride*j;\n         stbi_uc *in  = a->out + stride*j + x*out_n - img_width_bytes;\n         // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit\n         // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop\n         stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range\n\n         // note that the final byte might overshoot and write more data than desired.\n         // we can allocate enough data that this never writes out of memory, but it\n         // could also overwrite the next scanline. can it overwrite non-empty data\n         // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel.\n         // so we need to explicitly clamp the final ones\n\n         if (depth == 4) {\n            for (k=x*img_n; k >= 2; k-=2, ++in) {\n               *cur++ = scale * ((*in >> 4)       );\n               *cur++ = scale * ((*in     ) & 0x0f);\n            }\n            if (k > 0) *cur++ = scale * ((*in >> 4)       );\n         } else if (depth == 2) {\n            for (k=x*img_n; k >= 4; k-=4, ++in) {\n               *cur++ = scale * ((*in >> 6)       );\n               *cur++ = scale * ((*in >> 4) & 0x03);\n               *cur++ = scale * ((*in >> 2) & 0x03);\n               *cur++ = scale * ((*in     ) & 0x03);\n            }\n            if (k > 0) *cur++ = scale * ((*in >> 6)       );\n            if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03);\n            if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03);\n         } else if (depth == 1) {\n            for (k=x*img_n; k >= 8; k-=8, ++in) {\n               *cur++ = scale * ((*in >> 7)       );\n               *cur++ = scale * ((*in >> 6) & 0x01);\n               *cur++ = scale * ((*in >> 5) & 0x01);\n               *cur++ = scale * ((*in >> 4) & 0x01);\n               *cur++ = scale * ((*in >> 3) & 0x01);\n               *cur++ = scale * ((*in >> 2) & 0x01);\n               *cur++ = scale * ((*in >> 1) & 0x01);\n               *cur++ = scale * ((*in     ) & 0x01);\n            }\n            if (k > 0) *cur++ = scale * ((*in >> 7)       );\n            if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01);\n            if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01);\n            if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01);\n            if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01);\n            if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01);\n            if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01);\n         }\n         if (img_n != out_n) {\n            int q;\n            // insert alpha = 255\n            cur = a->out + stride*j;\n            if (img_n == 1) {\n               for (q=x-1; q >= 0; --q) {\n                  cur[q*2+1] = 255;\n                  cur[q*2+0] = cur[q];\n               }\n            } else {\n               STBI_ASSERT(img_n == 3);\n               for (q=x-1; q >= 0; --q) {\n                  cur[q*4+3] = 255;\n                  cur[q*4+2] = cur[q*3+2];\n                  cur[q*4+1] = cur[q*3+1];\n                  cur[q*4+0] = cur[q*3+0];\n               }\n            }\n         }\n      }\n   } else if (depth == 16) {\n      // force the image data from big-endian to platform-native.\n      // this is done in a separate pass due to the decoding relying\n      // on the data being untouched, but could probably be done\n      // per-line during decode if care is taken.\n      stbi_uc *cur = a->out;\n      stbi__uint16 *cur16 = (stbi__uint16*)cur;\n\n      for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) {\n         *cur16 = (cur[0] << 8) | cur[1];\n      }\n   }\n\n   return 1;\n}\n\nstatic int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced)\n{\n   int bytes = (depth == 16 ? 2 : 1);\n   int out_bytes = out_n * bytes;\n   stbi_uc *final;\n   int p;\n   if (!interlaced)\n      return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color);\n\n   // de-interlacing\n   final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0);\n   for (p=0; p < 7; ++p) {\n      int xorig[] = { 0,4,0,2,0,1,0 };\n      int yorig[] = { 0,0,4,0,2,0,1 };\n      int xspc[]  = { 8,8,4,4,2,2,1 };\n      int yspc[]  = { 8,8,8,4,4,2,2 };\n      int i,j,x,y;\n      // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1\n      x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p];\n      y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p];\n      if (x && y) {\n         stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y;\n         if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) {\n            STBI_FREE(final);\n            return 0;\n         }\n         for (j=0; j < y; ++j) {\n            for (i=0; i < x; ++i) {\n               int out_y = j*yspc[p]+yorig[p];\n               int out_x = i*xspc[p]+xorig[p];\n               memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes,\n                      a->out + (j*x+i)*out_bytes, out_bytes);\n            }\n         }\n         STBI_FREE(a->out);\n         image_data += img_len;\n         image_data_len -= img_len;\n      }\n   }\n   a->out = final;\n\n   return 1;\n}\n\nstatic int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n)\n{\n   stbi__context *s = z->s;\n   stbi__uint32 i, pixel_count = s->img_x * s->img_y;\n   stbi_uc *p = z->out;\n\n   // compute color-based transparency, assuming we've\n   // already got 255 as the alpha value in the output\n   STBI_ASSERT(out_n == 2 || out_n == 4);\n\n   if (out_n == 2) {\n      for (i=0; i < pixel_count; ++i) {\n         p[1] = (p[0] == tc[0] ? 0 : 255);\n         p += 2;\n      }\n   } else {\n      for (i=0; i < pixel_count; ++i) {\n         if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2])\n            p[3] = 0;\n         p += 4;\n      }\n   }\n   return 1;\n}\n\nstatic int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n)\n{\n   stbi__context *s = z->s;\n   stbi__uint32 i, pixel_count = s->img_x * s->img_y;\n   stbi__uint16 *p = (stbi__uint16*) z->out;\n\n   // compute color-based transparency, assuming we've\n   // already got 65535 as the alpha value in the output\n   STBI_ASSERT(out_n == 2 || out_n == 4);\n\n   if (out_n == 2) {\n      for (i = 0; i < pixel_count; ++i) {\n         p[1] = (p[0] == tc[0] ? 0 : 65535);\n         p += 2;\n      }\n   } else {\n      for (i = 0; i < pixel_count; ++i) {\n         if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2])\n            p[3] = 0;\n         p += 4;\n      }\n   }\n   return 1;\n}\n\nstatic int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n)\n{\n   stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y;\n   stbi_uc *p, *temp_out, *orig = a->out;\n\n   p = (stbi_uc *) stbi__malloc_mad2(pixel_count, pal_img_n, 0);\n   if (p == NULL) return stbi__err(\"outofmem\", \"Out of memory\");\n\n   // between here and free(out) below, exitting would leak\n   temp_out = p;\n\n   if (pal_img_n == 3) {\n      for (i=0; i < pixel_count; ++i) {\n         int n = orig[i]*4;\n         p[0] = palette[n  ];\n         p[1] = palette[n+1];\n         p[2] = palette[n+2];\n         p += 3;\n      }\n   } else {\n      for (i=0; i < pixel_count; ++i) {\n         int n = orig[i]*4;\n         p[0] = palette[n  ];\n         p[1] = palette[n+1];\n         p[2] = palette[n+2];\n         p[3] = palette[n+3];\n         p += 4;\n      }\n   }\n   STBI_FREE(a->out);\n   a->out = temp_out;\n\n   STBI_NOTUSED(len);\n\n   return 1;\n}\n\nstatic int stbi__unpremultiply_on_load = 0;\nstatic int stbi__de_iphone_flag = 0;\n\nSTBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply)\n{\n   stbi__unpremultiply_on_load = flag_true_if_should_unpremultiply;\n}\n\nSTBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert)\n{\n   stbi__de_iphone_flag = flag_true_if_should_convert;\n}\n\nstatic void stbi__de_iphone(stbi__png *z)\n{\n   stbi__context *s = z->s;\n   stbi__uint32 i, pixel_count = s->img_x * s->img_y;\n   stbi_uc *p = z->out;\n\n   if (s->img_out_n == 3) {  // convert bgr to rgb\n      for (i=0; i < pixel_count; ++i) {\n         stbi_uc t = p[0];\n         p[0] = p[2];\n         p[2] = t;\n         p += 3;\n      }\n   } else {\n      STBI_ASSERT(s->img_out_n == 4);\n      if (stbi__unpremultiply_on_load) {\n         // convert bgr to rgb and unpremultiply\n         for (i=0; i < pixel_count; ++i) {\n            stbi_uc a = p[3];\n            stbi_uc t = p[0];\n            if (a) {\n               stbi_uc half = a / 2;\n               p[0] = (p[2] * 255 + half) / a;\n               p[1] = (p[1] * 255 + half) / a;\n               p[2] = ( t   * 255 + half) / a;\n            } else {\n               p[0] = p[2];\n               p[2] = t;\n            }\n            p += 4;\n         }\n      } else {\n         // convert bgr to rgb\n         for (i=0; i < pixel_count; ++i) {\n            stbi_uc t = p[0];\n            p[0] = p[2];\n            p[2] = t;\n            p += 4;\n         }\n      }\n   }\n}\n\n#define STBI__PNG_TYPE(a,b,c,d)  (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d))\n\nstatic int stbi__parse_png_file(stbi__png *z, int scan, int req_comp)\n{\n   stbi_uc palette[1024], pal_img_n=0;\n   stbi_uc has_trans=0, tc[3]={0};\n   stbi__uint16 tc16[3];\n   stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0;\n   int first=1,k,interlace=0, color=0, is_iphone=0;\n   stbi__context *s = z->s;\n\n   z->expanded = NULL;\n   z->idata = NULL;\n   z->out = NULL;\n\n   if (!stbi__check_png_header(s)) return 0;\n\n   if (scan == STBI__SCAN_type) return 1;\n\n   for (;;) {\n      stbi__pngchunk c = stbi__get_chunk_header(s);\n      switch (c.type) {\n         case STBI__PNG_TYPE('C','g','B','I'):\n            is_iphone = 1;\n            stbi__skip(s, c.length);\n            break;\n         case STBI__PNG_TYPE('I','H','D','R'): {\n            int comp,filter;\n            if (!first) return stbi__err(\"multiple IHDR\",\"Corrupt PNG\");\n            first = 0;\n            if (c.length != 13) return stbi__err(\"bad IHDR len\",\"Corrupt PNG\");\n            s->img_x = stbi__get32be(s);\n            s->img_y = stbi__get32be(s);\n            if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err(\"too large\",\"Very large image (corrupt?)\");\n            if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err(\"too large\",\"Very large image (corrupt?)\");\n            z->depth = stbi__get8(s);  if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16)  return stbi__err(\"1/2/4/8/16-bit only\",\"PNG not supported: 1/2/4/8/16-bit only\");\n            color = stbi__get8(s);  if (color > 6)         return stbi__err(\"bad ctype\",\"Corrupt PNG\");\n            if (color == 3 && z->depth == 16)                  return stbi__err(\"bad ctype\",\"Corrupt PNG\");\n            if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err(\"bad ctype\",\"Corrupt PNG\");\n            comp  = stbi__get8(s);  if (comp) return stbi__err(\"bad comp method\",\"Corrupt PNG\");\n            filter= stbi__get8(s);  if (filter) return stbi__err(\"bad filter method\",\"Corrupt PNG\");\n            interlace = stbi__get8(s); if (interlace>1) return stbi__err(\"bad interlace method\",\"Corrupt PNG\");\n            if (!s->img_x || !s->img_y) return stbi__err(\"0-pixel image\",\"Corrupt PNG\");\n            if (!pal_img_n) {\n               s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0);\n               if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err(\"too large\", \"Image too large to decode\");\n               if (scan == STBI__SCAN_header) return 1;\n            } else {\n               // if paletted, then pal_n is our final components, and\n               // img_n is # components to decompress/filter.\n               s->img_n = 1;\n               if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err(\"too large\",\"Corrupt PNG\");\n               // if SCAN_header, have to scan to see if we have a tRNS\n            }\n            break;\n         }\n\n         case STBI__PNG_TYPE('P','L','T','E'):  {\n            if (first) return stbi__err(\"first not IHDR\", \"Corrupt PNG\");\n            if (c.length > 256*3) return stbi__err(\"invalid PLTE\",\"Corrupt PNG\");\n            pal_len = c.length / 3;\n            if (pal_len * 3 != c.length) return stbi__err(\"invalid PLTE\",\"Corrupt PNG\");\n            for (i=0; i < pal_len; ++i) {\n               palette[i*4+0] = stbi__get8(s);\n               palette[i*4+1] = stbi__get8(s);\n               palette[i*4+2] = stbi__get8(s);\n               palette[i*4+3] = 255;\n            }\n            break;\n         }\n\n         case STBI__PNG_TYPE('t','R','N','S'): {\n            if (first) return stbi__err(\"first not IHDR\", \"Corrupt PNG\");\n            if (z->idata) return stbi__err(\"tRNS after IDAT\",\"Corrupt PNG\");\n            if (pal_img_n) {\n               if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; }\n               if (pal_len == 0) return stbi__err(\"tRNS before PLTE\",\"Corrupt PNG\");\n               if (c.length > pal_len) return stbi__err(\"bad tRNS len\",\"Corrupt PNG\");\n               pal_img_n = 4;\n               for (i=0; i < c.length; ++i)\n                  palette[i*4+3] = stbi__get8(s);\n            } else {\n               if (!(s->img_n & 1)) return stbi__err(\"tRNS with alpha\",\"Corrupt PNG\");\n               if (c.length != (stbi__uint32) s->img_n*2) return stbi__err(\"bad tRNS len\",\"Corrupt PNG\");\n               has_trans = 1;\n               if (z->depth == 16) {\n                  for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is\n               } else {\n                  for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger\n               }\n            }\n            break;\n         }\n\n         case STBI__PNG_TYPE('I','D','A','T'): {\n            if (first) return stbi__err(\"first not IHDR\", \"Corrupt PNG\");\n            if (pal_img_n && !pal_len) return stbi__err(\"no PLTE\",\"Corrupt PNG\");\n            if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; }\n            if ((int)(ioff + c.length) < (int)ioff) return 0;\n            if (ioff + c.length > idata_limit) {\n               stbi__uint32 idata_limit_old = idata_limit;\n               stbi_uc *p;\n               if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096;\n               while (ioff + c.length > idata_limit)\n                  idata_limit *= 2;\n               STBI_NOTUSED(idata_limit_old);\n               p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err(\"outofmem\", \"Out of memory\");\n               z->idata = p;\n            }\n            if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err(\"outofdata\",\"Corrupt PNG\");\n            ioff += c.length;\n            break;\n         }\n\n         case STBI__PNG_TYPE('I','E','N','D'): {\n            stbi__uint32 raw_len, bpl;\n            if (first) return stbi__err(\"first not IHDR\", \"Corrupt PNG\");\n            if (scan != STBI__SCAN_load) return 1;\n            if (z->idata == NULL) return stbi__err(\"no IDAT\",\"Corrupt PNG\");\n            // initial guess for decoded data size to avoid unnecessary reallocs\n            bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component\n            raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */;\n            z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone);\n            if (z->expanded == NULL) return 0; // zlib should set error\n            STBI_FREE(z->idata); z->idata = NULL;\n            if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans)\n               s->img_out_n = s->img_n+1;\n            else\n               s->img_out_n = s->img_n;\n            if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0;\n            if (has_trans) {\n               if (z->depth == 16) {\n                  if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0;\n               } else {\n                  if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0;\n               }\n            }\n            if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2)\n               stbi__de_iphone(z);\n            if (pal_img_n) {\n               // pal_img_n == 3 or 4\n               s->img_n = pal_img_n; // record the actual colors we had\n               s->img_out_n = pal_img_n;\n               if (req_comp >= 3) s->img_out_n = req_comp;\n               if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n))\n                  return 0;\n            } else if (has_trans) {\n               // non-paletted image with tRNS -> source image has (constant) alpha\n               ++s->img_n;\n            }\n            STBI_FREE(z->expanded); z->expanded = NULL;\n            // end of PNG chunk, read and skip CRC\n            stbi__get32be(s);\n            return 1;\n         }\n\n         default:\n            // if critical, fail\n            if (first) return stbi__err(\"first not IHDR\", \"Corrupt PNG\");\n            if ((c.type & (1 << 29)) == 0) {\n               #ifndef STBI_NO_FAILURE_STRINGS\n               // not threadsafe\n               static char invalid_chunk[] = \"XXXX PNG chunk not known\";\n               invalid_chunk[0] = STBI__BYTECAST(c.type >> 24);\n               invalid_chunk[1] = STBI__BYTECAST(c.type >> 16);\n               invalid_chunk[2] = STBI__BYTECAST(c.type >>  8);\n               invalid_chunk[3] = STBI__BYTECAST(c.type >>  0);\n               #endif\n               return stbi__err(invalid_chunk, \"PNG not supported: unknown PNG chunk type\");\n            }\n            stbi__skip(s, c.length);\n            break;\n      }\n      // end of PNG chunk, read and skip CRC\n      stbi__get32be(s);\n   }\n}\n\nstatic void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, stbi__result_info *ri)\n{\n   void *result=NULL;\n   if (req_comp < 0 || req_comp > 4) return stbi__errpuc(\"bad req_comp\", \"Internal error\");\n   if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) {\n      if (p->depth <= 8)\n         ri->bits_per_channel = 8;\n      else if (p->depth == 16)\n         ri->bits_per_channel = 16;\n      else\n         return stbi__errpuc(\"bad bits_per_channel\", \"PNG not supported: unsupported color depth\");\n      result = p->out;\n      p->out = NULL;\n      if (req_comp && req_comp != p->s->img_out_n) {\n         if (ri->bits_per_channel == 8)\n            result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y);\n         else\n            result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y);\n         p->s->img_out_n = req_comp;\n         if (result == NULL) return result;\n      }\n      *x = p->s->img_x;\n      *y = p->s->img_y;\n      if (n) *n = p->s->img_n;\n   }\n   STBI_FREE(p->out);      p->out      = NULL;\n   STBI_FREE(p->expanded); p->expanded = NULL;\n   STBI_FREE(p->idata);    p->idata    = NULL;\n\n   return result;\n}\n\nstatic void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)\n{\n   stbi__png p;\n   p.s = s;\n   return stbi__do_png(&p, x,y,comp,req_comp, ri);\n}\n\nstatic int stbi__png_test(stbi__context *s)\n{\n   int r;\n   r = stbi__check_png_header(s);\n   stbi__rewind(s);\n   return r;\n}\n\nstatic int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp)\n{\n   if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) {\n      stbi__rewind( p->s );\n      return 0;\n   }\n   if (x) *x = p->s->img_x;\n   if (y) *y = p->s->img_y;\n   if (comp) *comp = p->s->img_n;\n   return 1;\n}\n\nstatic int stbi__png_info(stbi__context *s, int *x, int *y, int *comp)\n{\n   stbi__png p;\n   p.s = s;\n   return stbi__png_info_raw(&p, x, y, comp);\n}\n\nstatic int stbi__png_is16(stbi__context *s)\n{\n   stbi__png p;\n   p.s = s;\n   if (!stbi__png_info_raw(&p, NULL, NULL, NULL))\n\t   return 0;\n   if (p.depth != 16) {\n      stbi__rewind(p.s);\n      return 0;\n   }\n   return 1;\n}\n#endif\n\n// Microsoft/Windows BMP image\n\n#ifndef STBI_NO_BMP\nstatic int stbi__bmp_test_raw(stbi__context *s)\n{\n   int r;\n   int sz;\n   if (stbi__get8(s) != 'B') return 0;\n   if (stbi__get8(s) != 'M') return 0;\n   stbi__get32le(s); // discard filesize\n   stbi__get16le(s); // discard reserved\n   stbi__get16le(s); // discard reserved\n   stbi__get32le(s); // discard data offset\n   sz = stbi__get32le(s);\n   r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124);\n   return r;\n}\n\nstatic int stbi__bmp_test(stbi__context *s)\n{\n   int r = stbi__bmp_test_raw(s);\n   stbi__rewind(s);\n   return r;\n}\n\n\n// returns 0..31 for the highest set bit\nstatic int stbi__high_bit(unsigned int z)\n{\n   int n=0;\n   if (z == 0) return -1;\n   if (z >= 0x10000) { n += 16; z >>= 16; }\n   if (z >= 0x00100) { n +=  8; z >>=  8; }\n   if (z >= 0x00010) { n +=  4; z >>=  4; }\n   if (z >= 0x00004) { n +=  2; z >>=  2; }\n   if (z >= 0x00002) { n +=  1;/* >>=  1;*/ }\n   return n;\n}\n\nstatic int stbi__bitcount(unsigned int a)\n{\n   a = (a & 0x55555555) + ((a >>  1) & 0x55555555); // max 2\n   a = (a & 0x33333333) + ((a >>  2) & 0x33333333); // max 4\n   a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits\n   a = (a + (a >> 8)); // max 16 per 8 bits\n   a = (a + (a >> 16)); // max 32 per 8 bits\n   return a & 0xff;\n}\n\n// extract an arbitrarily-aligned N-bit value (N=bits)\n// from v, and then make it 8-bits long and fractionally\n// extend it to full full range.\nstatic int stbi__shiftsigned(unsigned int v, int shift, int bits)\n{\n   static unsigned int mul_table[9] = {\n      0,\n      0xff/*0b11111111*/, 0x55/*0b01010101*/, 0x49/*0b01001001*/, 0x11/*0b00010001*/,\n      0x21/*0b00100001*/, 0x41/*0b01000001*/, 0x81/*0b10000001*/, 0x01/*0b00000001*/,\n   };\n   static unsigned int shift_table[9] = {\n      0, 0,0,1,0,2,4,6,0,\n   };\n   if (shift < 0)\n      v <<= -shift;\n   else\n      v >>= shift;\n   STBI_ASSERT(v < 256);\n   v >>= (8-bits);\n   STBI_ASSERT(bits >= 0 && bits <= 8);\n   return (int) ((unsigned) v * mul_table[bits]) >> shift_table[bits];\n}\n\ntypedef struct\n{\n   int bpp, offset, hsz;\n   unsigned int mr,mg,mb,ma, all_a;\n   int extra_read;\n} stbi__bmp_data;\n\nstatic void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info)\n{\n   int hsz;\n   if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc(\"not BMP\", \"Corrupt BMP\");\n   stbi__get32le(s); // discard filesize\n   stbi__get16le(s); // discard reserved\n   stbi__get16le(s); // discard reserved\n   info->offset = stbi__get32le(s);\n   info->hsz = hsz = stbi__get32le(s);\n   info->mr = info->mg = info->mb = info->ma = 0;\n   info->extra_read = 14;\n\n   if (info->offset < 0) return stbi__errpuc(\"bad BMP\", \"bad BMP\");\n\n   if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc(\"unknown BMP\", \"BMP type not supported: unknown\");\n   if (hsz == 12) {\n      s->img_x = stbi__get16le(s);\n      s->img_y = stbi__get16le(s);\n   } else {\n      s->img_x = stbi__get32le(s);\n      s->img_y = stbi__get32le(s);\n   }\n   if (stbi__get16le(s) != 1) return stbi__errpuc(\"bad BMP\", \"bad BMP\");\n   info->bpp = stbi__get16le(s);\n   if (hsz != 12) {\n      int compress = stbi__get32le(s);\n      if (compress == 1 || compress == 2) return stbi__errpuc(\"BMP RLE\", \"BMP type not supported: RLE\");\n      stbi__get32le(s); // discard sizeof\n      stbi__get32le(s); // discard hres\n      stbi__get32le(s); // discard vres\n      stbi__get32le(s); // discard colorsused\n      stbi__get32le(s); // discard max important\n      if (hsz == 40 || hsz == 56) {\n         if (hsz == 56) {\n            stbi__get32le(s);\n            stbi__get32le(s);\n            stbi__get32le(s);\n            stbi__get32le(s);\n         }\n         if (info->bpp == 16 || info->bpp == 32) {\n            if (compress == 0) {\n               if (info->bpp == 32) {\n                  info->mr = 0xffu << 16;\n                  info->mg = 0xffu <<  8;\n                  info->mb = 0xffu <<  0;\n                  info->ma = 0xffu << 24;\n                  info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0\n               } else {\n                  info->mr = 31u << 10;\n                  info->mg = 31u <<  5;\n                  info->mb = 31u <<  0;\n               }\n            } else if (compress == 3) {\n               info->mr = stbi__get32le(s);\n               info->mg = stbi__get32le(s);\n               info->mb = stbi__get32le(s);\n               info->extra_read += 12;\n               // not documented, but generated by photoshop and handled by mspaint\n               if (info->mr == info->mg && info->mg == info->mb) {\n                  // ?!?!?\n                  return stbi__errpuc(\"bad BMP\", \"bad BMP\");\n               }\n            } else\n               return stbi__errpuc(\"bad BMP\", \"bad BMP\");\n         }\n      } else {\n         int i;\n         if (hsz != 108 && hsz != 124)\n            return stbi__errpuc(\"bad BMP\", \"bad BMP\");\n         info->mr = stbi__get32le(s);\n         info->mg = stbi__get32le(s);\n         info->mb = stbi__get32le(s);\n         info->ma = stbi__get32le(s);\n         stbi__get32le(s); // discard color space\n         for (i=0; i < 12; ++i)\n            stbi__get32le(s); // discard color space parameters\n         if (hsz == 124) {\n            stbi__get32le(s); // discard rendering intent\n            stbi__get32le(s); // discard offset of profile data\n            stbi__get32le(s); // discard size of profile data\n            stbi__get32le(s); // discard reserved\n         }\n      }\n   }\n   return (void *) 1;\n}\n\n\nstatic void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)\n{\n   stbi_uc *out;\n   unsigned int mr=0,mg=0,mb=0,ma=0, all_a;\n   stbi_uc pal[256][4];\n   int psize=0,i,j,width;\n   int flip_vertically, pad, target;\n   stbi__bmp_data info;\n   STBI_NOTUSED(ri);\n\n   info.all_a = 255;\n   if (stbi__bmp_parse_header(s, &info) == NULL)\n      return NULL; // error code already set\n\n   flip_vertically = ((int) s->img_y) > 0;\n   s->img_y = abs((int) s->img_y);\n\n   if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc(\"too large\",\"Very large image (corrupt?)\");\n   if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc(\"too large\",\"Very large image (corrupt?)\");\n\n   mr = info.mr;\n   mg = info.mg;\n   mb = info.mb;\n   ma = info.ma;\n   all_a = info.all_a;\n\n   if (info.hsz == 12) {\n      if (info.bpp < 24)\n         psize = (info.offset - info.extra_read - 24) / 3;\n   } else {\n      if (info.bpp < 16)\n         psize = (info.offset - info.extra_read - info.hsz) >> 2;\n   }\n   if (psize == 0) {\n      STBI_ASSERT(info.offset == s->callback_already_read + (int) (s->img_buffer - s->img_buffer_original));\n      if (info.offset != s->callback_already_read + (s->img_buffer - s->buffer_start)) {\n        return stbi__errpuc(\"bad offset\", \"Corrupt BMP\");\n      }\n   }\n\n   if (info.bpp == 24 && ma == 0xff000000)\n      s->img_n = 3;\n   else\n      s->img_n = ma ? 4 : 3;\n   if (req_comp && req_comp >= 3) // we can directly decode 3 or 4\n      target = req_comp;\n   else\n      target = s->img_n; // if they want monochrome, we'll post-convert\n\n   // sanity-check size\n   if (!stbi__mad3sizes_valid(target, s->img_x, s->img_y, 0))\n      return stbi__errpuc(\"too large\", \"Corrupt BMP\");\n\n   out = (stbi_uc *) stbi__malloc_mad3(target, s->img_x, s->img_y, 0);\n   if (!out) return stbi__errpuc(\"outofmem\", \"Out of memory\");\n   if (info.bpp < 16) {\n      int z=0;\n      if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc(\"invalid\", \"Corrupt BMP\"); }\n      for (i=0; i < psize; ++i) {\n         pal[i][2] = stbi__get8(s);\n         pal[i][1] = stbi__get8(s);\n         pal[i][0] = stbi__get8(s);\n         if (info.hsz != 12) stbi__get8(s);\n         pal[i][3] = 255;\n      }\n      stbi__skip(s, info.offset - info.extra_read - info.hsz - psize * (info.hsz == 12 ? 3 : 4));\n      if (info.bpp == 1) width = (s->img_x + 7) >> 3;\n      else if (info.bpp == 4) width = (s->img_x + 1) >> 1;\n      else if (info.bpp == 8) width = s->img_x;\n      else { STBI_FREE(out); return stbi__errpuc(\"bad bpp\", \"Corrupt BMP\"); }\n      pad = (-width)&3;\n      if (info.bpp == 1) {\n         for (j=0; j < (int) s->img_y; ++j) {\n            int bit_offset = 7, v = stbi__get8(s);\n            for (i=0; i < (int) s->img_x; ++i) {\n               int color = (v>>bit_offset)&0x1;\n               out[z++] = pal[color][0];\n               out[z++] = pal[color][1];\n               out[z++] = pal[color][2];\n               if (target == 4) out[z++] = 255;\n               if (i+1 == (int) s->img_x) break;\n               if((--bit_offset) < 0) {\n                  bit_offset = 7;\n                  v = stbi__get8(s);\n               }\n            }\n            stbi__skip(s, pad);\n         }\n      } else {\n         for (j=0; j < (int) s->img_y; ++j) {\n            for (i=0; i < (int) s->img_x; i += 2) {\n               int v=stbi__get8(s),v2=0;\n               if (info.bpp == 4) {\n                  v2 = v & 15;\n                  v >>= 4;\n               }\n               out[z++] = pal[v][0];\n               out[z++] = pal[v][1];\n               out[z++] = pal[v][2];\n               if (target == 4) out[z++] = 255;\n               if (i+1 == (int) s->img_x) break;\n               v = (info.bpp == 8) ? stbi__get8(s) : v2;\n               out[z++] = pal[v][0];\n               out[z++] = pal[v][1];\n               out[z++] = pal[v][2];\n               if (target == 4) out[z++] = 255;\n            }\n            stbi__skip(s, pad);\n         }\n      }\n   } else {\n      int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0;\n      int z = 0;\n      int easy=0;\n      stbi__skip(s, info.offset - info.extra_read - info.hsz);\n      if (info.bpp == 24) width = 3 * s->img_x;\n      else if (info.bpp == 16) width = 2*s->img_x;\n      else /* bpp = 32 and pad = 0 */ width=0;\n      pad = (-width) & 3;\n      if (info.bpp == 24) {\n         easy = 1;\n      } else if (info.bpp == 32) {\n         if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000)\n            easy = 2;\n      }\n      if (!easy) {\n         if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc(\"bad masks\", \"Corrupt BMP\"); }\n         // right shift amt to put high bit in position #7\n         rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr);\n         gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg);\n         bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb);\n         ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma);\n         if (rcount > 8 || gcount > 8 || bcount > 8 || acount > 8) { STBI_FREE(out); return stbi__errpuc(\"bad masks\", \"Corrupt BMP\"); }\n      }\n      for (j=0; j < (int) s->img_y; ++j) {\n         if (easy) {\n            for (i=0; i < (int) s->img_x; ++i) {\n               unsigned char a;\n               out[z+2] = stbi__get8(s);\n               out[z+1] = stbi__get8(s);\n               out[z+0] = stbi__get8(s);\n               z += 3;\n               a = (easy == 2 ? stbi__get8(s) : 255);\n               all_a |= a;\n               if (target == 4) out[z++] = a;\n            }\n         } else {\n            int bpp = info.bpp;\n            for (i=0; i < (int) s->img_x; ++i) {\n               stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s));\n               unsigned int a;\n               out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount));\n               out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount));\n               out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount));\n               a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255);\n               all_a |= a;\n               if (target == 4) out[z++] = STBI__BYTECAST(a);\n            }\n         }\n         stbi__skip(s, pad);\n      }\n   }\n\n   // if alpha channel is all 0s, replace with all 255s\n   if (target == 4 && all_a == 0)\n      for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4)\n         out[i] = 255;\n\n   if (flip_vertically) {\n      stbi_uc t;\n      for (j=0; j < (int) s->img_y>>1; ++j) {\n         stbi_uc *p1 = out +      j     *s->img_x*target;\n         stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target;\n         for (i=0; i < (int) s->img_x*target; ++i) {\n            t = p1[i]; p1[i] = p2[i]; p2[i] = t;\n         }\n      }\n   }\n\n   if (req_comp && req_comp != target) {\n      out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y);\n      if (out == NULL) return out; // stbi__convert_format frees input on failure\n   }\n\n   *x = s->img_x;\n   *y = s->img_y;\n   if (comp) *comp = s->img_n;\n   return out;\n}\n#endif\n\n// Targa Truevision - TGA\n// by Jonathan Dummer\n#ifndef STBI_NO_TGA\n// returns STBI_rgb or whatever, 0 on error\nstatic int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16)\n{\n   // only RGB or RGBA (incl. 16bit) or grey allowed\n   if (is_rgb16) *is_rgb16 = 0;\n   switch(bits_per_pixel) {\n      case 8:  return STBI_grey;\n      case 16: if(is_grey) return STBI_grey_alpha;\n               // fallthrough\n      case 15: if(is_rgb16) *is_rgb16 = 1;\n               return STBI_rgb;\n      case 24: // fallthrough\n      case 32: return bits_per_pixel/8;\n      default: return 0;\n   }\n}\n\nstatic int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp)\n{\n    int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp;\n    int sz, tga_colormap_type;\n    stbi__get8(s);                   // discard Offset\n    tga_colormap_type = stbi__get8(s); // colormap type\n    if( tga_colormap_type > 1 ) {\n        stbi__rewind(s);\n        return 0;      // only RGB or indexed allowed\n    }\n    tga_image_type = stbi__get8(s); // image type\n    if ( tga_colormap_type == 1 ) { // colormapped (paletted) image\n        if (tga_image_type != 1 && tga_image_type != 9) {\n            stbi__rewind(s);\n            return 0;\n        }\n        stbi__skip(s,4);       // skip index of first colormap entry and number of entries\n        sz = stbi__get8(s);    //   check bits per palette color entry\n        if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) {\n            stbi__rewind(s);\n            return 0;\n        }\n        stbi__skip(s,4);       // skip image x and y origin\n        tga_colormap_bpp = sz;\n    } else { // \"normal\" image w/o colormap - only RGB or grey allowed, +/- RLE\n        if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) {\n            stbi__rewind(s);\n            return 0; // only RGB or grey allowed, +/- RLE\n        }\n        stbi__skip(s,9); // skip colormap specification and image x/y origin\n        tga_colormap_bpp = 0;\n    }\n    tga_w = stbi__get16le(s);\n    if( tga_w < 1 ) {\n        stbi__rewind(s);\n        return 0;   // test width\n    }\n    tga_h = stbi__get16le(s);\n    if( tga_h < 1 ) {\n        stbi__rewind(s);\n        return 0;   // test height\n    }\n    tga_bits_per_pixel = stbi__get8(s); // bits per pixel\n    stbi__get8(s); // ignore alpha bits\n    if (tga_colormap_bpp != 0) {\n        if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) {\n            // when using a colormap, tga_bits_per_pixel is the size of the indexes\n            // I don't think anything but 8 or 16bit indexes makes sense\n            stbi__rewind(s);\n            return 0;\n        }\n        tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL);\n    } else {\n        tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL);\n    }\n    if(!tga_comp) {\n      stbi__rewind(s);\n      return 0;\n    }\n    if (x) *x = tga_w;\n    if (y) *y = tga_h;\n    if (comp) *comp = tga_comp;\n    return 1;                   // seems to have passed everything\n}\n\nstatic int stbi__tga_test(stbi__context *s)\n{\n   int res = 0;\n   int sz, tga_color_type;\n   stbi__get8(s);      //   discard Offset\n   tga_color_type = stbi__get8(s);   //   color type\n   if ( tga_color_type > 1 ) goto errorEnd;   //   only RGB or indexed allowed\n   sz = stbi__get8(s);   //   image type\n   if ( tga_color_type == 1 ) { // colormapped (paletted) image\n      if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9\n      stbi__skip(s,4);       // skip index of first colormap entry and number of entries\n      sz = stbi__get8(s);    //   check bits per palette color entry\n      if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd;\n      stbi__skip(s,4);       // skip image x and y origin\n   } else { // \"normal\" image w/o colormap\n      if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE\n      stbi__skip(s,9); // skip colormap specification and image x/y origin\n   }\n   if ( stbi__get16le(s) < 1 ) goto errorEnd;      //   test width\n   if ( stbi__get16le(s) < 1 ) goto errorEnd;      //   test height\n   sz = stbi__get8(s);   //   bits per pixel\n   if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index\n   if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd;\n\n   res = 1; // if we got this far, everything's good and we can return 1 instead of 0\n\nerrorEnd:\n   stbi__rewind(s);\n   return res;\n}\n\n// read 16bit value and convert to 24bit RGB\nstatic void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out)\n{\n   stbi__uint16 px = (stbi__uint16)stbi__get16le(s);\n   stbi__uint16 fiveBitMask = 31;\n   // we have 3 channels with 5bits each\n   int r = (px >> 10) & fiveBitMask;\n   int g = (px >> 5) & fiveBitMask;\n   int b = px & fiveBitMask;\n   // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later\n   out[0] = (stbi_uc)((r * 255)/31);\n   out[1] = (stbi_uc)((g * 255)/31);\n   out[2] = (stbi_uc)((b * 255)/31);\n\n   // some people claim that the most significant bit might be used for alpha\n   // (possibly if an alpha-bit is set in the \"image descriptor byte\")\n   // but that only made 16bit test images completely translucent..\n   // so let's treat all 15 and 16bit TGAs as RGB with no alpha.\n}\n\nstatic void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)\n{\n   //   read in the TGA header stuff\n   int tga_offset = stbi__get8(s);\n   int tga_indexed = stbi__get8(s);\n   int tga_image_type = stbi__get8(s);\n   int tga_is_RLE = 0;\n   int tga_palette_start = stbi__get16le(s);\n   int tga_palette_len = stbi__get16le(s);\n   int tga_palette_bits = stbi__get8(s);\n   int tga_x_origin = stbi__get16le(s);\n   int tga_y_origin = stbi__get16le(s);\n   int tga_width = stbi__get16le(s);\n   int tga_height = stbi__get16le(s);\n   int tga_bits_per_pixel = stbi__get8(s);\n   int tga_comp, tga_rgb16=0;\n   int tga_inverted = stbi__get8(s);\n   // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?)\n   //   image data\n   unsigned char *tga_data;\n   unsigned char *tga_palette = NULL;\n   int i, j;\n   unsigned char raw_data[4] = {0};\n   int RLE_count = 0;\n   int RLE_repeating = 0;\n   int read_next_pixel = 1;\n   STBI_NOTUSED(ri);\n   STBI_NOTUSED(tga_x_origin); // @TODO\n   STBI_NOTUSED(tga_y_origin); // @TODO\n\n   if (tga_height > STBI_MAX_DIMENSIONS) return stbi__errpuc(\"too large\",\"Very large image (corrupt?)\");\n   if (tga_width > STBI_MAX_DIMENSIONS) return stbi__errpuc(\"too large\",\"Very large image (corrupt?)\");\n\n   //   do a tiny bit of precessing\n   if ( tga_image_type >= 8 )\n   {\n      tga_image_type -= 8;\n      tga_is_RLE = 1;\n   }\n   tga_inverted = 1 - ((tga_inverted >> 5) & 1);\n\n   //   If I'm paletted, then I'll use the number of bits from the palette\n   if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16);\n   else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16);\n\n   if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency\n      return stbi__errpuc(\"bad format\", \"Can't find out TGA pixelformat\");\n\n   //   tga info\n   *x = tga_width;\n   *y = tga_height;\n   if (comp) *comp = tga_comp;\n\n   if (!stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0))\n      return stbi__errpuc(\"too large\", \"Corrupt TGA\");\n\n   tga_data = (unsigned char*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0);\n   if (!tga_data) return stbi__errpuc(\"outofmem\", \"Out of memory\");\n\n   // skip to the data's starting position (offset usually = 0)\n   stbi__skip(s, tga_offset );\n\n   if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) {\n      for (i=0; i < tga_height; ++i) {\n         int row = tga_inverted ? tga_height -i - 1 : i;\n         stbi_uc *tga_row = tga_data + row*tga_width*tga_comp;\n         stbi__getn(s, tga_row, tga_width * tga_comp);\n      }\n   } else  {\n      //   do I need to load a palette?\n      if ( tga_indexed)\n      {\n         if (tga_palette_len == 0) {  /* you have to have at least one entry! */\n            STBI_FREE(tga_data);\n            return stbi__errpuc(\"bad palette\", \"Corrupt TGA\");\n         }\n\n         //   any data to skip? (offset usually = 0)\n         stbi__skip(s, tga_palette_start );\n         //   load the palette\n         tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0);\n         if (!tga_palette) {\n            STBI_FREE(tga_data);\n            return stbi__errpuc(\"outofmem\", \"Out of memory\");\n         }\n         if (tga_rgb16) {\n            stbi_uc *pal_entry = tga_palette;\n            STBI_ASSERT(tga_comp == STBI_rgb);\n            for (i=0; i < tga_palette_len; ++i) {\n               stbi__tga_read_rgb16(s, pal_entry);\n               pal_entry += tga_comp;\n            }\n         } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) {\n               STBI_FREE(tga_data);\n               STBI_FREE(tga_palette);\n               return stbi__errpuc(\"bad palette\", \"Corrupt TGA\");\n         }\n      }\n      //   load the data\n      for (i=0; i < tga_width * tga_height; ++i)\n      {\n         //   if I'm in RLE mode, do I need to get a RLE stbi__pngchunk?\n         if ( tga_is_RLE )\n         {\n            if ( RLE_count == 0 )\n            {\n               //   yep, get the next byte as a RLE command\n               int RLE_cmd = stbi__get8(s);\n               RLE_count = 1 + (RLE_cmd & 127);\n               RLE_repeating = RLE_cmd >> 7;\n               read_next_pixel = 1;\n            } else if ( !RLE_repeating )\n            {\n               read_next_pixel = 1;\n            }\n         } else\n         {\n            read_next_pixel = 1;\n         }\n         //   OK, if I need to read a pixel, do it now\n         if ( read_next_pixel )\n         {\n            //   load however much data we did have\n            if ( tga_indexed )\n            {\n               // read in index, then perform the lookup\n               int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s);\n               if ( pal_idx >= tga_palette_len ) {\n                  // invalid index\n                  pal_idx = 0;\n               }\n               pal_idx *= tga_comp;\n               for (j = 0; j < tga_comp; ++j) {\n                  raw_data[j] = tga_palette[pal_idx+j];\n               }\n            } else if(tga_rgb16) {\n               STBI_ASSERT(tga_comp == STBI_rgb);\n               stbi__tga_read_rgb16(s, raw_data);\n            } else {\n               //   read in the data raw\n               for (j = 0; j < tga_comp; ++j) {\n                  raw_data[j] = stbi__get8(s);\n               }\n            }\n            //   clear the reading flag for the next pixel\n            read_next_pixel = 0;\n         } // end of reading a pixel\n\n         // copy data\n         for (j = 0; j < tga_comp; ++j)\n           tga_data[i*tga_comp+j] = raw_data[j];\n\n         //   in case we're in RLE mode, keep counting down\n         --RLE_count;\n      }\n      //   do I need to invert the image?\n      if ( tga_inverted )\n      {\n         for (j = 0; j*2 < tga_height; ++j)\n         {\n            int index1 = j * tga_width * tga_comp;\n            int index2 = (tga_height - 1 - j) * tga_width * tga_comp;\n            for (i = tga_width * tga_comp; i > 0; --i)\n            {\n               unsigned char temp = tga_data[index1];\n               tga_data[index1] = tga_data[index2];\n               tga_data[index2] = temp;\n               ++index1;\n               ++index2;\n            }\n         }\n      }\n      //   clear my palette, if I had one\n      if ( tga_palette != NULL )\n      {\n         STBI_FREE( tga_palette );\n      }\n   }\n\n   // swap RGB - if the source data was RGB16, it already is in the right order\n   if (tga_comp >= 3 && !tga_rgb16)\n   {\n      unsigned char* tga_pixel = tga_data;\n      for (i=0; i < tga_width * tga_height; ++i)\n      {\n         unsigned char temp = tga_pixel[0];\n         tga_pixel[0] = tga_pixel[2];\n         tga_pixel[2] = temp;\n         tga_pixel += tga_comp;\n      }\n   }\n\n   // convert to target component count\n   if (req_comp && req_comp != tga_comp)\n      tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height);\n\n   //   the things I do to get rid of an error message, and yet keep\n   //   Microsoft's C compilers happy... [8^(\n   tga_palette_start = tga_palette_len = tga_palette_bits =\n         tga_x_origin = tga_y_origin = 0;\n   STBI_NOTUSED(tga_palette_start);\n   //   OK, done\n   return tga_data;\n}\n#endif\n\n// *************************************************************************************************\n// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB\n\n#ifndef STBI_NO_PSD\nstatic int stbi__psd_test(stbi__context *s)\n{\n   int r = (stbi__get32be(s) == 0x38425053);\n   stbi__rewind(s);\n   return r;\n}\n\nstatic int stbi__psd_decode_rle(stbi__context *s, stbi_uc *p, int pixelCount)\n{\n   int count, nleft, len;\n\n   count = 0;\n   while ((nleft = pixelCount - count) > 0) {\n      len = stbi__get8(s);\n      if (len == 128) {\n         // No-op.\n      } else if (len < 128) {\n         // Copy next len+1 bytes literally.\n         len++;\n         if (len > nleft) return 0; // corrupt data\n         count += len;\n         while (len) {\n            *p = stbi__get8(s);\n            p += 4;\n            len--;\n         }\n      } else if (len > 128) {\n         stbi_uc   val;\n         // Next -len+1 bytes in the dest are replicated from next source byte.\n         // (Interpret len as a negative 8-bit int.)\n         len = 257 - len;\n         if (len > nleft) return 0; // corrupt data\n         val = stbi__get8(s);\n         count += len;\n         while (len) {\n            *p = val;\n            p += 4;\n            len--;\n         }\n      }\n   }\n\n   return 1;\n}\n\nstatic void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc)\n{\n   int pixelCount;\n   int channelCount, compression;\n   int channel, i;\n   int bitdepth;\n   int w,h;\n   stbi_uc *out;\n   STBI_NOTUSED(ri);\n\n   // Check identifier\n   if (stbi__get32be(s) != 0x38425053)   // \"8BPS\"\n      return stbi__errpuc(\"not PSD\", \"Corrupt PSD image\");\n\n   // Check file type version.\n   if (stbi__get16be(s) != 1)\n      return stbi__errpuc(\"wrong version\", \"Unsupported version of PSD image\");\n\n   // Skip 6 reserved bytes.\n   stbi__skip(s, 6 );\n\n   // Read the number of channels (R, G, B, A, etc).\n   channelCount = stbi__get16be(s);\n   if (channelCount < 0 || channelCount > 16)\n      return stbi__errpuc(\"wrong channel count\", \"Unsupported number of channels in PSD image\");\n\n   // Read the rows and columns of the image.\n   h = stbi__get32be(s);\n   w = stbi__get32be(s);\n\n   if (h > STBI_MAX_DIMENSIONS) return stbi__errpuc(\"too large\",\"Very large image (corrupt?)\");\n   if (w > STBI_MAX_DIMENSIONS) return stbi__errpuc(\"too large\",\"Very large image (corrupt?)\");\n\n   // Make sure the depth is 8 bits.\n   bitdepth = stbi__get16be(s);\n   if (bitdepth != 8 && bitdepth != 16)\n      return stbi__errpuc(\"unsupported bit depth\", \"PSD bit depth is not 8 or 16 bit\");\n\n   // Make sure the color mode is RGB.\n   // Valid options are:\n   //   0: Bitmap\n   //   1: Grayscale\n   //   2: Indexed color\n   //   3: RGB color\n   //   4: CMYK color\n   //   7: Multichannel\n   //   8: Duotone\n   //   9: Lab color\n   if (stbi__get16be(s) != 3)\n      return stbi__errpuc(\"wrong color format\", \"PSD is not in RGB color format\");\n\n   // Skip the Mode Data.  (It's the palette for indexed color; other info for other modes.)\n   stbi__skip(s,stbi__get32be(s) );\n\n   // Skip the image resources.  (resolution, pen tool paths, etc)\n   stbi__skip(s, stbi__get32be(s) );\n\n   // Skip the reserved data.\n   stbi__skip(s, stbi__get32be(s) );\n\n   // Find out if the data is compressed.\n   // Known values:\n   //   0: no compression\n   //   1: RLE compressed\n   compression = stbi__get16be(s);\n   if (compression > 1)\n      return stbi__errpuc(\"bad compression\", \"PSD has an unknown compression format\");\n\n   // Check size\n   if (!stbi__mad3sizes_valid(4, w, h, 0))\n      return stbi__errpuc(\"too large\", \"Corrupt PSD\");\n\n   // Create the destination image.\n\n   if (!compression && bitdepth == 16 && bpc == 16) {\n      out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0);\n      ri->bits_per_channel = 16;\n   } else\n      out = (stbi_uc *) stbi__malloc(4 * w*h);\n\n   if (!out) return stbi__errpuc(\"outofmem\", \"Out of memory\");\n   pixelCount = w*h;\n\n   // Initialize the data to zero.\n   //memset( out, 0, pixelCount * 4 );\n\n   // Finally, the image data.\n   if (compression) {\n      // RLE as used by .PSD and .TIFF\n      // Loop until you get the number of unpacked bytes you are expecting:\n      //     Read the next source byte into n.\n      //     If n is between 0 and 127 inclusive, copy the next n+1 bytes literally.\n      //     Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times.\n      //     Else if n is 128, noop.\n      // Endloop\n\n      // The RLE-compressed data is preceded by a 2-byte data count for each row in the data,\n      // which we're going to just skip.\n      stbi__skip(s, h * channelCount * 2 );\n\n      // Read the RLE data by channel.\n      for (channel = 0; channel < 4; channel++) {\n         stbi_uc *p;\n\n         p = out+channel;\n         if (channel >= channelCount) {\n            // Fill this channel with default data.\n            for (i = 0; i < pixelCount; i++, p += 4)\n               *p = (channel == 3 ? 255 : 0);\n         } else {\n            // Read the RLE data.\n            if (!stbi__psd_decode_rle(s, p, pixelCount)) {\n               STBI_FREE(out);\n               return stbi__errpuc(\"corrupt\", \"bad RLE data\");\n            }\n         }\n      }\n\n   } else {\n      // We're at the raw image data.  It's each channel in order (Red, Green, Blue, Alpha, ...)\n      // where each channel consists of an 8-bit (or 16-bit) value for each pixel in the image.\n\n      // Read the data by channel.\n      for (channel = 0; channel < 4; channel++) {\n         if (channel >= channelCount) {\n            // Fill this channel with default data.\n            if (bitdepth == 16 && bpc == 16) {\n               stbi__uint16 *q = ((stbi__uint16 *) out) + channel;\n               stbi__uint16 val = channel == 3 ? 65535 : 0;\n               for (i = 0; i < pixelCount; i++, q += 4)\n                  *q = val;\n            } else {\n               stbi_uc *p = out+channel;\n               stbi_uc val = channel == 3 ? 255 : 0;\n               for (i = 0; i < pixelCount; i++, p += 4)\n                  *p = val;\n            }\n         } else {\n            if (ri->bits_per_channel == 16) {    // output bpc\n               stbi__uint16 *q = ((stbi__uint16 *) out) + channel;\n               for (i = 0; i < pixelCount; i++, q += 4)\n                  *q = (stbi__uint16) stbi__get16be(s);\n            } else {\n               stbi_uc *p = out+channel;\n               if (bitdepth == 16) {  // input bpc\n                  for (i = 0; i < pixelCount; i++, p += 4)\n                     *p = (stbi_uc) (stbi__get16be(s) >> 8);\n               } else {\n                  for (i = 0; i < pixelCount; i++, p += 4)\n                     *p = stbi__get8(s);\n               }\n            }\n         }\n      }\n   }\n\n   // remove weird white matte from PSD\n   if (channelCount >= 4) {\n      if (ri->bits_per_channel == 16) {\n         for (i=0; i < w*h; ++i) {\n            stbi__uint16 *pixel = (stbi__uint16 *) out + 4*i;\n            if (pixel[3] != 0 && pixel[3] != 65535) {\n               float a = pixel[3] / 65535.0f;\n               float ra = 1.0f / a;\n               float inv_a = 65535.0f * (1 - ra);\n               pixel[0] = (stbi__uint16) (pixel[0]*ra + inv_a);\n               pixel[1] = (stbi__uint16) (pixel[1]*ra + inv_a);\n               pixel[2] = (stbi__uint16) (pixel[2]*ra + inv_a);\n            }\n         }\n      } else {\n         for (i=0; i < w*h; ++i) {\n            unsigned char *pixel = out + 4*i;\n            if (pixel[3] != 0 && pixel[3] != 255) {\n               float a = pixel[3] / 255.0f;\n               float ra = 1.0f / a;\n               float inv_a = 255.0f * (1 - ra);\n               pixel[0] = (unsigned char) (pixel[0]*ra + inv_a);\n               pixel[1] = (unsigned char) (pixel[1]*ra + inv_a);\n               pixel[2] = (unsigned char) (pixel[2]*ra + inv_a);\n            }\n         }\n      }\n   }\n\n   // convert to desired output format\n   if (req_comp && req_comp != 4) {\n      if (ri->bits_per_channel == 16)\n         out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, 4, req_comp, w, h);\n      else\n         out = stbi__convert_format(out, 4, req_comp, w, h);\n      if (out == NULL) return out; // stbi__convert_format frees input on failure\n   }\n\n   if (comp) *comp = 4;\n   *y = h;\n   *x = w;\n\n   return out;\n}\n#endif\n\n// *************************************************************************************************\n// Softimage PIC loader\n// by Tom Seddon\n//\n// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format\n// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/\n\n#ifndef STBI_NO_PIC\nstatic int stbi__pic_is4(stbi__context *s,const char *str)\n{\n   int i;\n   for (i=0; i<4; ++i)\n      if (stbi__get8(s) != (stbi_uc)str[i])\n         return 0;\n\n   return 1;\n}\n\nstatic int stbi__pic_test_core(stbi__context *s)\n{\n   int i;\n\n   if (!stbi__pic_is4(s,\"\\x53\\x80\\xF6\\x34\"))\n      return 0;\n\n   for(i=0;i<84;++i)\n      stbi__get8(s);\n\n   if (!stbi__pic_is4(s,\"PICT\"))\n      return 0;\n\n   return 1;\n}\n\ntypedef struct\n{\n   stbi_uc size,type,channel;\n} stbi__pic_packet;\n\nstatic stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest)\n{\n   int mask=0x80, i;\n\n   for (i=0; i<4; ++i, mask>>=1) {\n      if (channel & mask) {\n         if (stbi__at_eof(s)) return stbi__errpuc(\"bad file\",\"PIC file too short\");\n         dest[i]=stbi__get8(s);\n      }\n   }\n\n   return dest;\n}\n\nstatic void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src)\n{\n   int mask=0x80,i;\n\n   for (i=0;i<4; ++i, mask>>=1)\n      if (channel&mask)\n         dest[i]=src[i];\n}\n\nstatic stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result)\n{\n   int act_comp=0,num_packets=0,y,chained;\n   stbi__pic_packet packets[10];\n\n   // this will (should...) cater for even some bizarre stuff like having data\n    // for the same channel in multiple packets.\n   do {\n      stbi__pic_packet *packet;\n\n      if (num_packets==sizeof(packets)/sizeof(packets[0]))\n         return stbi__errpuc(\"bad format\",\"too many packets\");\n\n      packet = &packets[num_packets++];\n\n      chained = stbi__get8(s);\n      packet->size    = stbi__get8(s);\n      packet->type    = stbi__get8(s);\n      packet->channel = stbi__get8(s);\n\n      act_comp |= packet->channel;\n\n      if (stbi__at_eof(s))          return stbi__errpuc(\"bad file\",\"file too short (reading packets)\");\n      if (packet->size != 8)  return stbi__errpuc(\"bad format\",\"packet isn't 8bpp\");\n   } while (chained);\n\n   *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel?\n\n   for(y=0; y<height; ++y) {\n      int packet_idx;\n\n      for(packet_idx=0; packet_idx < num_packets; ++packet_idx) {\n         stbi__pic_packet *packet = &packets[packet_idx];\n         stbi_uc *dest = result+y*width*4;\n\n         switch (packet->type) {\n            default:\n               return stbi__errpuc(\"bad format\",\"packet has bad compression type\");\n\n            case 0: {//uncompressed\n               int x;\n\n               for(x=0;x<width;++x, dest+=4)\n                  if (!stbi__readval(s,packet->channel,dest))\n                     return 0;\n               break;\n            }\n\n            case 1://Pure RLE\n               {\n                  int left=width, i;\n\n                  while (left>0) {\n                     stbi_uc count,value[4];\n\n                     count=stbi__get8(s);\n                     if (stbi__at_eof(s))   return stbi__errpuc(\"bad file\",\"file too short (pure read count)\");\n\n                     if (count > left)\n                        count = (stbi_uc) left;\n\n                     if (!stbi__readval(s,packet->channel,value))  return 0;\n\n                     for(i=0; i<count; ++i,dest+=4)\n                        stbi__copyval(packet->channel,dest,value);\n                     left -= count;\n                  }\n               }\n               break;\n\n            case 2: {//Mixed RLE\n               int left=width;\n               while (left>0) {\n                  int count = stbi__get8(s), i;\n                  if (stbi__at_eof(s))  return stbi__errpuc(\"bad file\",\"file too short (mixed read count)\");\n\n                  if (count >= 128) { // Repeated\n                     stbi_uc value[4];\n\n                     if (count==128)\n                        count = stbi__get16be(s);\n                     else\n                        count -= 127;\n                     if (count > left)\n                        return stbi__errpuc(\"bad file\",\"scanline overrun\");\n\n                     if (!stbi__readval(s,packet->channel,value))\n                        return 0;\n\n                     for(i=0;i<count;++i, dest += 4)\n                        stbi__copyval(packet->channel,dest,value);\n                  } else { // Raw\n                     ++count;\n                     if (count>left) return stbi__errpuc(\"bad file\",\"scanline overrun\");\n\n                     for(i=0;i<count;++i, dest+=4)\n                        if (!stbi__readval(s,packet->channel,dest))\n                           return 0;\n                  }\n                  left-=count;\n               }\n               break;\n            }\n         }\n      }\n   }\n\n   return result;\n}\n\nstatic void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp, stbi__result_info *ri)\n{\n   stbi_uc *result;\n   int i, x,y, internal_comp;\n   STBI_NOTUSED(ri);\n\n   if (!comp) comp = &internal_comp;\n\n   for (i=0; i<92; ++i)\n      stbi__get8(s);\n\n   x = stbi__get16be(s);\n   y = stbi__get16be(s);\n\n   if (y > STBI_MAX_DIMENSIONS) return stbi__errpuc(\"too large\",\"Very large image (corrupt?)\");\n   if (x > STBI_MAX_DIMENSIONS) return stbi__errpuc(\"too large\",\"Very large image (corrupt?)\");\n\n   if (stbi__at_eof(s))  return stbi__errpuc(\"bad file\",\"file too short (pic header)\");\n   if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc(\"too large\", \"PIC image too large to decode\");\n\n   stbi__get32be(s); //skip `ratio'\n   stbi__get16be(s); //skip `fields'\n   stbi__get16be(s); //skip `pad'\n\n   // intermediate buffer is RGBA\n   result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0);\n   memset(result, 0xff, x*y*4);\n\n   if (!stbi__pic_load_core(s,x,y,comp, result)) {\n      STBI_FREE(result);\n      result=0;\n   }\n   *px = x;\n   *py = y;\n   if (req_comp == 0) req_comp = *comp;\n   result=stbi__convert_format(result,4,req_comp,x,y);\n\n   return result;\n}\n\nstatic int stbi__pic_test(stbi__context *s)\n{\n   int r = stbi__pic_test_core(s);\n   stbi__rewind(s);\n   return r;\n}\n#endif\n\n// *************************************************************************************************\n// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb\n\n#ifndef STBI_NO_GIF\ntypedef struct\n{\n   stbi__int16 prefix;\n   stbi_uc first;\n   stbi_uc suffix;\n} stbi__gif_lzw;\n\ntypedef struct\n{\n   int w,h;\n   stbi_uc *out;                 // output buffer (always 4 components)\n   stbi_uc *background;          // The current \"background\" as far as a gif is concerned\n   stbi_uc *history;\n   int flags, bgindex, ratio, transparent, eflags;\n   stbi_uc  pal[256][4];\n   stbi_uc lpal[256][4];\n   stbi__gif_lzw codes[8192];\n   stbi_uc *color_table;\n   int parse, step;\n   int lflags;\n   int start_x, start_y;\n   int max_x, max_y;\n   int cur_x, cur_y;\n   int line_size;\n   int delay;\n} stbi__gif;\n\nstatic int stbi__gif_test_raw(stbi__context *s)\n{\n   int sz;\n   if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0;\n   sz = stbi__get8(s);\n   if (sz != '9' && sz != '7') return 0;\n   if (stbi__get8(s) != 'a') return 0;\n   return 1;\n}\n\nstatic int stbi__gif_test(stbi__context *s)\n{\n   int r = stbi__gif_test_raw(s);\n   stbi__rewind(s);\n   return r;\n}\n\nstatic void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp)\n{\n   int i;\n   for (i=0; i < num_entries; ++i) {\n      pal[i][2] = stbi__get8(s);\n      pal[i][1] = stbi__get8(s);\n      pal[i][0] = stbi__get8(s);\n      pal[i][3] = transp == i ? 0 : 255;\n   }\n}\n\nstatic int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info)\n{\n   stbi_uc version;\n   if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8')\n      return stbi__err(\"not GIF\", \"Corrupt GIF\");\n\n   version = stbi__get8(s);\n   if (version != '7' && version != '9')    return stbi__err(\"not GIF\", \"Corrupt GIF\");\n   if (stbi__get8(s) != 'a')                return stbi__err(\"not GIF\", \"Corrupt GIF\");\n\n   stbi__g_failure_reason = \"\";\n   g->w = stbi__get16le(s);\n   g->h = stbi__get16le(s);\n   g->flags = stbi__get8(s);\n   g->bgindex = stbi__get8(s);\n   g->ratio = stbi__get8(s);\n   g->transparent = -1;\n\n   if (g->w > STBI_MAX_DIMENSIONS) return stbi__err(\"too large\",\"Very large image (corrupt?)\");\n   if (g->h > STBI_MAX_DIMENSIONS) return stbi__err(\"too large\",\"Very large image (corrupt?)\");\n\n   if (comp != 0) *comp = 4;  // can't actually tell whether it's 3 or 4 until we parse the comments\n\n   if (is_info) return 1;\n\n   if (g->flags & 0x80)\n      stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1);\n\n   return 1;\n}\n\nstatic int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp)\n{\n   stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif));\n   if (!stbi__gif_header(s, g, comp, 1)) {\n      STBI_FREE(g);\n      stbi__rewind( s );\n      return 0;\n   }\n   if (x) *x = g->w;\n   if (y) *y = g->h;\n   STBI_FREE(g);\n   return 1;\n}\n\nstatic void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code)\n{\n   stbi_uc *p, *c;\n   int idx;\n\n   // recurse to decode the prefixes, since the linked-list is backwards,\n   // and working backwards through an interleaved image would be nasty\n   if (g->codes[code].prefix >= 0)\n      stbi__out_gif_code(g, g->codes[code].prefix);\n\n   if (g->cur_y >= g->max_y) return;\n\n   idx = g->cur_x + g->cur_y;\n   p = &g->out[idx];\n   g->history[idx / 4] = 1;\n\n   c = &g->color_table[g->codes[code].suffix * 4];\n   if (c[3] > 128) { // don't render transparent pixels;\n      p[0] = c[2];\n      p[1] = c[1];\n      p[2] = c[0];\n      p[3] = c[3];\n   }\n   g->cur_x += 4;\n\n   if (g->cur_x >= g->max_x) {\n      g->cur_x = g->start_x;\n      g->cur_y += g->step;\n\n      while (g->cur_y >= g->max_y && g->parse > 0) {\n         g->step = (1 << g->parse) * g->line_size;\n         g->cur_y = g->start_y + (g->step >> 1);\n         --g->parse;\n      }\n   }\n}\n\nstatic stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g)\n{\n   stbi_uc lzw_cs;\n   stbi__int32 len, init_code;\n   stbi__uint32 first;\n   stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear;\n   stbi__gif_lzw *p;\n\n   lzw_cs = stbi__get8(s);\n   if (lzw_cs > 12) return NULL;\n   clear = 1 << lzw_cs;\n   first = 1;\n   codesize = lzw_cs + 1;\n   codemask = (1 << codesize) - 1;\n   bits = 0;\n   valid_bits = 0;\n   for (init_code = 0; init_code < clear; init_code++) {\n      g->codes[init_code].prefix = -1;\n      g->codes[init_code].first = (stbi_uc) init_code;\n      g->codes[init_code].suffix = (stbi_uc) init_code;\n   }\n\n   // support no starting clear code\n   avail = clear+2;\n   oldcode = -1;\n\n   len = 0;\n   for(;;) {\n      if (valid_bits < codesize) {\n         if (len == 0) {\n            len = stbi__get8(s); // start new block\n            if (len == 0)\n               return g->out;\n         }\n         --len;\n         bits |= (stbi__int32) stbi__get8(s) << valid_bits;\n         valid_bits += 8;\n      } else {\n         stbi__int32 code = bits & codemask;\n         bits >>= codesize;\n         valid_bits -= codesize;\n         // @OPTIMIZE: is there some way we can accelerate the non-clear path?\n         if (code == clear) {  // clear code\n            codesize = lzw_cs + 1;\n            codemask = (1 << codesize) - 1;\n            avail = clear + 2;\n            oldcode = -1;\n            first = 0;\n         } else if (code == clear + 1) { // end of stream code\n            stbi__skip(s, len);\n            while ((len = stbi__get8(s)) > 0)\n               stbi__skip(s,len);\n            return g->out;\n         } else if (code <= avail) {\n            if (first) {\n               return stbi__errpuc(\"no clear code\", \"Corrupt GIF\");\n            }\n\n            if (oldcode >= 0) {\n               p = &g->codes[avail++];\n               if (avail > 8192) {\n                  return stbi__errpuc(\"too many codes\", \"Corrupt GIF\");\n               }\n\n               p->prefix = (stbi__int16) oldcode;\n               p->first = g->codes[oldcode].first;\n               p->suffix = (code == avail) ? p->first : g->codes[code].first;\n            } else if (code == avail)\n               return stbi__errpuc(\"illegal code in raster\", \"Corrupt GIF\");\n\n            stbi__out_gif_code(g, (stbi__uint16) code);\n\n            if ((avail & codemask) == 0 && avail <= 0x0FFF) {\n               codesize++;\n               codemask = (1 << codesize) - 1;\n            }\n\n            oldcode = code;\n         } else {\n            return stbi__errpuc(\"illegal code in raster\", \"Corrupt GIF\");\n         }\n      }\n   }\n}\n\n// this function is designed to support animated gifs, although stb_image doesn't support it\n// two back is the image from two frames ago, used for a very specific disposal format\nstatic stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp, stbi_uc *two_back)\n{\n   int dispose;\n   int first_frame;\n   int pi;\n   int pcount;\n   STBI_NOTUSED(req_comp);\n\n   // on first frame, any non-written pixels get the background colour (non-transparent)\n   first_frame = 0;\n   if (g->out == 0) {\n      if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header\n      if (!stbi__mad3sizes_valid(4, g->w, g->h, 0))\n         return stbi__errpuc(\"too large\", \"GIF image is too large\");\n      pcount = g->w * g->h;\n      g->out = (stbi_uc *) stbi__malloc(4 * pcount);\n      g->background = (stbi_uc *) stbi__malloc(4 * pcount);\n      g->history = (stbi_uc *) stbi__malloc(pcount);\n      if (!g->out || !g->background || !g->history)\n         return stbi__errpuc(\"outofmem\", \"Out of memory\");\n\n      // image is treated as \"transparent\" at the start - ie, nothing overwrites the current background;\n      // background colour is only used for pixels that are not rendered first frame, after that \"background\"\n      // color refers to the color that was there the previous frame.\n      memset(g->out, 0x00, 4 * pcount);\n      memset(g->background, 0x00, 4 * pcount); // state of the background (starts transparent)\n      memset(g->history, 0x00, pcount);        // pixels that were affected previous frame\n      first_frame = 1;\n   } else {\n      // second frame - how do we dispose of the previous one?\n      dispose = (g->eflags & 0x1C) >> 2;\n      pcount = g->w * g->h;\n\n      if ((dispose == 3) && (two_back == 0)) {\n         dispose = 2; // if I don't have an image to revert back to, default to the old background\n      }\n\n      if (dispose == 3) { // use previous graphic\n         for (pi = 0; pi < pcount; ++pi) {\n            if (g->history[pi]) {\n               memcpy( &g->out[pi * 4], &two_back[pi * 4], 4 );\n            }\n         }\n      } else if (dispose == 2) {\n         // restore what was changed last frame to background before that frame;\n         for (pi = 0; pi < pcount; ++pi) {\n            if (g->history[pi]) {\n               memcpy( &g->out[pi * 4], &g->background[pi * 4], 4 );\n            }\n         }\n      } else {\n         // This is a non-disposal case eithe way, so just\n         // leave the pixels as is, and they will become the new background\n         // 1: do not dispose\n         // 0:  not specified.\n      }\n\n      // background is what out is after the undoing of the previou frame;\n      memcpy( g->background, g->out, 4 * g->w * g->h );\n   }\n\n   // clear my history;\n   memset( g->history, 0x00, g->w * g->h );        // pixels that were affected previous frame\n\n   for (;;) {\n      int tag = stbi__get8(s);\n      switch (tag) {\n         case 0x2C: /* Image Descriptor */\n         {\n            stbi__int32 x, y, w, h;\n            stbi_uc *o;\n\n            x = stbi__get16le(s);\n            y = stbi__get16le(s);\n            w = stbi__get16le(s);\n            h = stbi__get16le(s);\n            if (((x + w) > (g->w)) || ((y + h) > (g->h)))\n               return stbi__errpuc(\"bad Image Descriptor\", \"Corrupt GIF\");\n\n            g->line_size = g->w * 4;\n            g->start_x = x * 4;\n            g->start_y = y * g->line_size;\n            g->max_x   = g->start_x + w * 4;\n            g->max_y   = g->start_y + h * g->line_size;\n            g->cur_x   = g->start_x;\n            g->cur_y   = g->start_y;\n\n            // if the width of the specified rectangle is 0, that means\n            // we may not see *any* pixels or the image is malformed;\n            // to make sure this is caught, move the current y down to\n            // max_y (which is what out_gif_code checks).\n            if (w == 0)\n               g->cur_y = g->max_y;\n\n            g->lflags = stbi__get8(s);\n\n            if (g->lflags & 0x40) {\n               g->step = 8 * g->line_size; // first interlaced spacing\n               g->parse = 3;\n            } else {\n               g->step = g->line_size;\n               g->parse = 0;\n            }\n\n            if (g->lflags & 0x80) {\n               stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1);\n               g->color_table = (stbi_uc *) g->lpal;\n            } else if (g->flags & 0x80) {\n               g->color_table = (stbi_uc *) g->pal;\n            } else\n               return stbi__errpuc(\"missing color table\", \"Corrupt GIF\");\n\n            o = stbi__process_gif_raster(s, g);\n            if (!o) return NULL;\n\n            // if this was the first frame,\n            pcount = g->w * g->h;\n            if (first_frame && (g->bgindex > 0)) {\n               // if first frame, any pixel not drawn to gets the background color\n               for (pi = 0; pi < pcount; ++pi) {\n                  if (g->history[pi] == 0) {\n                     g->pal[g->bgindex][3] = 255; // just in case it was made transparent, undo that; It will be reset next frame if need be;\n                     memcpy( &g->out[pi * 4], &g->pal[g->bgindex], 4 );\n                  }\n               }\n            }\n\n            return o;\n         }\n\n         case 0x21: // Comment Extension.\n         {\n            int len;\n            int ext = stbi__get8(s);\n            if (ext == 0xF9) { // Graphic Control Extension.\n               len = stbi__get8(s);\n               if (len == 4) {\n                  g->eflags = stbi__get8(s);\n                  g->delay = 10 * stbi__get16le(s); // delay - 1/100th of a second, saving as 1/1000ths.\n\n                  // unset old transparent\n                  if (g->transparent >= 0) {\n                     g->pal[g->transparent][3] = 255;\n                  }\n                  if (g->eflags & 0x01) {\n                     g->transparent = stbi__get8(s);\n                     if (g->transparent >= 0) {\n                        g->pal[g->transparent][3] = 0;\n                     }\n                  } else {\n                     // don't need transparent\n                     stbi__skip(s, 1);\n                     g->transparent = -1;\n                  }\n               } else {\n                  stbi__skip(s, len);\n                  break;\n               }\n            }\n            while ((len = stbi__get8(s)) != 0) {\n               stbi__skip(s, len);\n            }\n            break;\n         }\n\n         case 0x3B: // gif stream termination code\n            return (stbi_uc *) s; // using '1' causes warning on some compilers\n\n         default:\n            return stbi__errpuc(\"unknown code\", \"Corrupt GIF\");\n      }\n   }\n}\n\nstatic void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp)\n{\n   if (stbi__gif_test(s)) {\n      int layers = 0;\n      stbi_uc *u = 0;\n      stbi_uc *out = 0;\n      stbi_uc *two_back = 0;\n      stbi__gif g;\n      int stride;\n      int out_size = 0;\n      int delays_size = 0;\n      memset(&g, 0, sizeof(g));\n      if (delays) {\n         *delays = 0;\n      }\n\n      do {\n         u = stbi__gif_load_next(s, &g, comp, req_comp, two_back);\n         if (u == (stbi_uc *) s) u = 0;  // end of animated gif marker\n\n         if (u) {\n            *x = g.w;\n            *y = g.h;\n            ++layers;\n            stride = g.w * g.h * 4;\n\n            if (out) {\n               void *tmp = (stbi_uc*) STBI_REALLOC_SIZED( out, out_size, layers * stride );\n               if (NULL == tmp) {\n                  STBI_FREE(g.out);\n                  STBI_FREE(g.history);\n                  STBI_FREE(g.background);\n                  return stbi__errpuc(\"outofmem\", \"Out of memory\");\n               }\n               else {\n                   out = (stbi_uc*) tmp;\n                   out_size = layers * stride;\n               }\n\n               if (delays) {\n                  *delays = (int*) STBI_REALLOC_SIZED( *delays, delays_size, sizeof(int) * layers );\n                  delays_size = layers * sizeof(int);\n               }\n            } else {\n               out = (stbi_uc*)stbi__malloc( layers * stride );\n               out_size = layers * stride;\n               if (delays) {\n                  *delays = (int*) stbi__malloc( layers * sizeof(int) );\n                  delays_size = layers * sizeof(int);\n               }\n            }\n            memcpy( out + ((layers - 1) * stride), u, stride );\n            if (layers >= 2) {\n               two_back = out - 2 * stride;\n            }\n\n            if (delays) {\n               (*delays)[layers - 1U] = g.delay;\n            }\n         }\n      } while (u != 0);\n\n      // free temp buffer;\n      STBI_FREE(g.out);\n      STBI_FREE(g.history);\n      STBI_FREE(g.background);\n\n      // do the final conversion after loading everything;\n      if (req_comp && req_comp != 4)\n         out = stbi__convert_format(out, 4, req_comp, layers * g.w, g.h);\n\n      *z = layers;\n      return out;\n   } else {\n      return stbi__errpuc(\"not GIF\", \"Image was not as a gif type.\");\n   }\n}\n\nstatic void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)\n{\n   stbi_uc *u = 0;\n   stbi__gif g;\n   memset(&g, 0, sizeof(g));\n   STBI_NOTUSED(ri);\n\n   u = stbi__gif_load_next(s, &g, comp, req_comp, 0);\n   if (u == (stbi_uc *) s) u = 0;  // end of animated gif marker\n   if (u) {\n      *x = g.w;\n      *y = g.h;\n\n      // moved conversion to after successful load so that the same\n      // can be done for multiple frames.\n      if (req_comp && req_comp != 4)\n         u = stbi__convert_format(u, 4, req_comp, g.w, g.h);\n   } else if (g.out) {\n      // if there was an error and we allocated an image buffer, free it!\n      STBI_FREE(g.out);\n   }\n\n   // free buffers needed for multiple frame loading;\n   STBI_FREE(g.history);\n   STBI_FREE(g.background);\n\n   return u;\n}\n\nstatic int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp)\n{\n   return stbi__gif_info_raw(s,x,y,comp);\n}\n#endif\n\n// *************************************************************************************************\n// Radiance RGBE HDR loader\n// originally by Nicolas Schulz\n#ifndef STBI_NO_HDR\nstatic int stbi__hdr_test_core(stbi__context *s, const char *signature)\n{\n   int i;\n   for (i=0; signature[i]; ++i)\n      if (stbi__get8(s) != signature[i])\n          return 0;\n   stbi__rewind(s);\n   return 1;\n}\n\nstatic int stbi__hdr_test(stbi__context* s)\n{\n   int r = stbi__hdr_test_core(s, \"#?RADIANCE\\n\");\n   stbi__rewind(s);\n   if(!r) {\n       r = stbi__hdr_test_core(s, \"#?RGBE\\n\");\n       stbi__rewind(s);\n   }\n   return r;\n}\n\n#define STBI__HDR_BUFLEN  1024\nstatic char *stbi__hdr_gettoken(stbi__context *z, char *buffer)\n{\n   int len=0;\n   char c = '\\0';\n\n   c = (char) stbi__get8(z);\n\n   while (!stbi__at_eof(z) && c != '\\n') {\n      buffer[len++] = c;\n      if (len == STBI__HDR_BUFLEN-1) {\n         // flush to end of line\n         while (!stbi__at_eof(z) && stbi__get8(z) != '\\n')\n            ;\n         break;\n      }\n      c = (char) stbi__get8(z);\n   }\n\n   buffer[len] = 0;\n   return buffer;\n}\n\nstatic void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp)\n{\n   if ( input[3] != 0 ) {\n      float f1;\n      // Exponent\n      f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8));\n      if (req_comp <= 2)\n         output[0] = (input[0] + input[1] + input[2]) * f1 / 3;\n      else {\n         output[0] = input[0] * f1;\n         output[1] = input[1] * f1;\n         output[2] = input[2] * f1;\n      }\n      if (req_comp == 2) output[1] = 1;\n      if (req_comp == 4) output[3] = 1;\n   } else {\n      switch (req_comp) {\n         case 4: output[3] = 1; /* fallthrough */\n         case 3: output[0] = output[1] = output[2] = 0;\n                 break;\n         case 2: output[1] = 1; /* fallthrough */\n         case 1: output[0] = 0;\n                 break;\n      }\n   }\n}\n\nstatic float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)\n{\n   char buffer[STBI__HDR_BUFLEN];\n   char *token;\n   int valid = 0;\n   int width, height;\n   stbi_uc *scanline;\n   float *hdr_data;\n   int len;\n   unsigned char count, value;\n   int i, j, k, c1,c2, z;\n   const char *headerToken;\n   STBI_NOTUSED(ri);\n\n   // Check identifier\n   headerToken = stbi__hdr_gettoken(s,buffer);\n   if (strcmp(headerToken, \"#?RADIANCE\") != 0 && strcmp(headerToken, \"#?RGBE\") != 0)\n      return stbi__errpf(\"not HDR\", \"Corrupt HDR image\");\n\n   // Parse header\n   for(;;) {\n      token = stbi__hdr_gettoken(s,buffer);\n      if (token[0] == 0) break;\n      if (strcmp(token, \"FORMAT=32-bit_rle_rgbe\") == 0) valid = 1;\n   }\n\n   if (!valid)    return stbi__errpf(\"unsupported format\", \"Unsupported HDR format\");\n\n   // Parse width and height\n   // can't use sscanf() if we're not using stdio!\n   token = stbi__hdr_gettoken(s,buffer);\n   if (strncmp(token, \"-Y \", 3))  return stbi__errpf(\"unsupported data layout\", \"Unsupported HDR format\");\n   token += 3;\n   height = (int) strtol(token, &token, 10);\n   while (*token == ' ') ++token;\n   if (strncmp(token, \"+X \", 3))  return stbi__errpf(\"unsupported data layout\", \"Unsupported HDR format\");\n   token += 3;\n   width = (int) strtol(token, NULL, 10);\n\n   if (height > STBI_MAX_DIMENSIONS) return stbi__errpf(\"too large\",\"Very large image (corrupt?)\");\n   if (width > STBI_MAX_DIMENSIONS) return stbi__errpf(\"too large\",\"Very large image (corrupt?)\");\n\n   *x = width;\n   *y = height;\n\n   if (comp) *comp = 3;\n   if (req_comp == 0) req_comp = 3;\n\n   if (!stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0))\n      return stbi__errpf(\"too large\", \"HDR image is too large\");\n\n   // Read data\n   hdr_data = (float *) stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0);\n   if (!hdr_data)\n      return stbi__errpf(\"outofmem\", \"Out of memory\");\n\n   // Load image data\n   // image data is stored as some number of sca\n   if ( width < 8 || width >= 32768) {\n      // Read flat data\n      for (j=0; j < height; ++j) {\n         for (i=0; i < width; ++i) {\n            stbi_uc rgbe[4];\n           main_decode_loop:\n            stbi__getn(s, rgbe, 4);\n            stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp);\n         }\n      }\n   } else {\n      // Read RLE-encoded data\n      scanline = NULL;\n\n      for (j = 0; j < height; ++j) {\n         c1 = stbi__get8(s);\n         c2 = stbi__get8(s);\n         len = stbi__get8(s);\n         if (c1 != 2 || c2 != 2 || (len & 0x80)) {\n            // not run-length encoded, so we have to actually use THIS data as a decoded\n            // pixel (note this can't be a valid pixel--one of RGB must be >= 128)\n            stbi_uc rgbe[4];\n            rgbe[0] = (stbi_uc) c1;\n            rgbe[1] = (stbi_uc) c2;\n            rgbe[2] = (stbi_uc) len;\n            rgbe[3] = (stbi_uc) stbi__get8(s);\n            stbi__hdr_convert(hdr_data, rgbe, req_comp);\n            i = 1;\n            j = 0;\n            STBI_FREE(scanline);\n            goto main_decode_loop; // yes, this makes no sense\n         }\n         len <<= 8;\n         len |= stbi__get8(s);\n         if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf(\"invalid decoded scanline length\", \"corrupt HDR\"); }\n         if (scanline == NULL) {\n            scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0);\n            if (!scanline) {\n               STBI_FREE(hdr_data);\n               return stbi__errpf(\"outofmem\", \"Out of memory\");\n            }\n         }\n\n         for (k = 0; k < 4; ++k) {\n            int nleft;\n            i = 0;\n            while ((nleft = width - i) > 0) {\n               count = stbi__get8(s);\n               if (count > 128) {\n                  // Run\n                  value = stbi__get8(s);\n                  count -= 128;\n                  if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf(\"corrupt\", \"bad RLE data in HDR\"); }\n                  for (z = 0; z < count; ++z)\n                     scanline[i++ * 4 + k] = value;\n               } else {\n                  // Dump\n                  if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf(\"corrupt\", \"bad RLE data in HDR\"); }\n                  for (z = 0; z < count; ++z)\n                     scanline[i++ * 4 + k] = stbi__get8(s);\n               }\n            }\n         }\n         for (i=0; i < width; ++i)\n            stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp);\n      }\n      if (scanline)\n         STBI_FREE(scanline);\n   }\n\n   return hdr_data;\n}\n\nstatic int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp)\n{\n   char buffer[STBI__HDR_BUFLEN];\n   char *token;\n   int valid = 0;\n   int dummy;\n\n   if (!x) x = &dummy;\n   if (!y) y = &dummy;\n   if (!comp) comp = &dummy;\n\n   if (stbi__hdr_test(s) == 0) {\n       stbi__rewind( s );\n       return 0;\n   }\n\n   for(;;) {\n      token = stbi__hdr_gettoken(s,buffer);\n      if (token[0] == 0) break;\n      if (strcmp(token, \"FORMAT=32-bit_rle_rgbe\") == 0) valid = 1;\n   }\n\n   if (!valid) {\n       stbi__rewind( s );\n       return 0;\n   }\n   token = stbi__hdr_gettoken(s,buffer);\n   if (strncmp(token, \"-Y \", 3)) {\n       stbi__rewind( s );\n       return 0;\n   }\n   token += 3;\n   *y = (int) strtol(token, &token, 10);\n   while (*token == ' ') ++token;\n   if (strncmp(token, \"+X \", 3)) {\n       stbi__rewind( s );\n       return 0;\n   }\n   token += 3;\n   *x = (int) strtol(token, NULL, 10);\n   *comp = 3;\n   return 1;\n}\n#endif // STBI_NO_HDR\n\n#ifndef STBI_NO_BMP\nstatic int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp)\n{\n   void *p;\n   stbi__bmp_data info;\n\n   info.all_a = 255;\n   p = stbi__bmp_parse_header(s, &info);\n   stbi__rewind( s );\n   if (p == NULL)\n      return 0;\n   if (x) *x = s->img_x;\n   if (y) *y = s->img_y;\n   if (comp) {\n      if (info.bpp == 24 && info.ma == 0xff000000)\n         *comp = 3;\n      else\n         *comp = info.ma ? 4 : 3;\n   }\n   return 1;\n}\n#endif\n\n#ifndef STBI_NO_PSD\nstatic int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp)\n{\n   int channelCount, dummy, depth;\n   if (!x) x = &dummy;\n   if (!y) y = &dummy;\n   if (!comp) comp = &dummy;\n   if (stbi__get32be(s) != 0x38425053) {\n       stbi__rewind( s );\n       return 0;\n   }\n   if (stbi__get16be(s) != 1) {\n       stbi__rewind( s );\n       return 0;\n   }\n   stbi__skip(s, 6);\n   channelCount = stbi__get16be(s);\n   if (channelCount < 0 || channelCount > 16) {\n       stbi__rewind( s );\n       return 0;\n   }\n   *y = stbi__get32be(s);\n   *x = stbi__get32be(s);\n   depth = stbi__get16be(s);\n   if (depth != 8 && depth != 16) {\n       stbi__rewind( s );\n       return 0;\n   }\n   if (stbi__get16be(s) != 3) {\n       stbi__rewind( s );\n       return 0;\n   }\n   *comp = 4;\n   return 1;\n}\n\nstatic int stbi__psd_is16(stbi__context *s)\n{\n   int channelCount, depth;\n   if (stbi__get32be(s) != 0x38425053) {\n       stbi__rewind( s );\n       return 0;\n   }\n   if (stbi__get16be(s) != 1) {\n       stbi__rewind( s );\n       return 0;\n   }\n   stbi__skip(s, 6);\n   channelCount = stbi__get16be(s);\n   if (channelCount < 0 || channelCount > 16) {\n       stbi__rewind( s );\n       return 0;\n   }\n   (void) stbi__get32be(s);\n   (void) stbi__get32be(s);\n   depth = stbi__get16be(s);\n   if (depth != 16) {\n       stbi__rewind( s );\n       return 0;\n   }\n   return 1;\n}\n#endif\n\n#ifndef STBI_NO_PIC\nstatic int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp)\n{\n   int act_comp=0,num_packets=0,chained,dummy;\n   stbi__pic_packet packets[10];\n\n   if (!x) x = &dummy;\n   if (!y) y = &dummy;\n   if (!comp) comp = &dummy;\n\n   if (!stbi__pic_is4(s,\"\\x53\\x80\\xF6\\x34\")) {\n      stbi__rewind(s);\n      return 0;\n   }\n\n   stbi__skip(s, 88);\n\n   *x = stbi__get16be(s);\n   *y = stbi__get16be(s);\n   if (stbi__at_eof(s)) {\n      stbi__rewind( s);\n      return 0;\n   }\n   if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) {\n      stbi__rewind( s );\n      return 0;\n   }\n\n   stbi__skip(s, 8);\n\n   do {\n      stbi__pic_packet *packet;\n\n      if (num_packets==sizeof(packets)/sizeof(packets[0]))\n         return 0;\n\n      packet = &packets[num_packets++];\n      chained = stbi__get8(s);\n      packet->size    = stbi__get8(s);\n      packet->type    = stbi__get8(s);\n      packet->channel = stbi__get8(s);\n      act_comp |= packet->channel;\n\n      if (stbi__at_eof(s)) {\n          stbi__rewind( s );\n          return 0;\n      }\n      if (packet->size != 8) {\n          stbi__rewind( s );\n          return 0;\n      }\n   } while (chained);\n\n   *comp = (act_comp & 0x10 ? 4 : 3);\n\n   return 1;\n}\n#endif\n\n// *************************************************************************************************\n// Portable Gray Map and Portable Pixel Map loader\n// by Ken Miller\n//\n// PGM: http://netpbm.sourceforge.net/doc/pgm.html\n// PPM: http://netpbm.sourceforge.net/doc/ppm.html\n//\n// Known limitations:\n//    Does not support comments in the header section\n//    Does not support ASCII image data (formats P2 and P3)\n//    Does not support 16-bit-per-channel\n\n#ifndef STBI_NO_PNM\n\nstatic int      stbi__pnm_test(stbi__context *s)\n{\n   char p, t;\n   p = (char) stbi__get8(s);\n   t = (char) stbi__get8(s);\n   if (p != 'P' || (t != '5' && t != '6')) {\n       stbi__rewind( s );\n       return 0;\n   }\n   return 1;\n}\n\nstatic void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)\n{\n   stbi_uc *out;\n   STBI_NOTUSED(ri);\n\n   if (!stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n))\n      return 0;\n\n   if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc(\"too large\",\"Very large image (corrupt?)\");\n   if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc(\"too large\",\"Very large image (corrupt?)\");\n\n   *x = s->img_x;\n   *y = s->img_y;\n   if (comp) *comp = s->img_n;\n\n   if (!stbi__mad3sizes_valid(s->img_n, s->img_x, s->img_y, 0))\n      return stbi__errpuc(\"too large\", \"PNM too large\");\n\n   out = (stbi_uc *) stbi__malloc_mad3(s->img_n, s->img_x, s->img_y, 0);\n   if (!out) return stbi__errpuc(\"outofmem\", \"Out of memory\");\n   stbi__getn(s, out, s->img_n * s->img_x * s->img_y);\n\n   if (req_comp && req_comp != s->img_n) {\n      out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y);\n      if (out == NULL) return out; // stbi__convert_format frees input on failure\n   }\n   return out;\n}\n\nstatic int      stbi__pnm_isspace(char c)\n{\n   return c == ' ' || c == '\\t' || c == '\\n' || c == '\\v' || c == '\\f' || c == '\\r';\n}\n\nstatic void     stbi__pnm_skip_whitespace(stbi__context *s, char *c)\n{\n   for (;;) {\n      while (!stbi__at_eof(s) && stbi__pnm_isspace(*c))\n         *c = (char) stbi__get8(s);\n\n      if (stbi__at_eof(s) || *c != '#')\n         break;\n\n      while (!stbi__at_eof(s) && *c != '\\n' && *c != '\\r' )\n         *c = (char) stbi__get8(s);\n   }\n}\n\nstatic int      stbi__pnm_isdigit(char c)\n{\n   return c >= '0' && c <= '9';\n}\n\nstatic int      stbi__pnm_getinteger(stbi__context *s, char *c)\n{\n   int value = 0;\n\n   while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) {\n      value = value*10 + (*c - '0');\n      *c = (char) stbi__get8(s);\n   }\n\n   return value;\n}\n\nstatic int      stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp)\n{\n   int maxv, dummy;\n   char c, p, t;\n\n   if (!x) x = &dummy;\n   if (!y) y = &dummy;\n   if (!comp) comp = &dummy;\n\n   stbi__rewind(s);\n\n   // Get identifier\n   p = (char) stbi__get8(s);\n   t = (char) stbi__get8(s);\n   if (p != 'P' || (t != '5' && t != '6')) {\n       stbi__rewind(s);\n       return 0;\n   }\n\n   *comp = (t == '6') ? 3 : 1;  // '5' is 1-component .pgm; '6' is 3-component .ppm\n\n   c = (char) stbi__get8(s);\n   stbi__pnm_skip_whitespace(s, &c);\n\n   *x = stbi__pnm_getinteger(s, &c); // read width\n   stbi__pnm_skip_whitespace(s, &c);\n\n   *y = stbi__pnm_getinteger(s, &c); // read height\n   stbi__pnm_skip_whitespace(s, &c);\n\n   maxv = stbi__pnm_getinteger(s, &c);  // read max value\n\n   if (maxv > 255)\n      return stbi__err(\"max value > 255\", \"PPM image not 8-bit\");\n   else\n      return 1;\n}\n#endif\n\nstatic int stbi__info_main(stbi__context *s, int *x, int *y, int *comp)\n{\n   #ifndef STBI_NO_JPEG\n   if (stbi__jpeg_info(s, x, y, comp)) return 1;\n   #endif\n\n   #ifndef STBI_NO_PNG\n   if (stbi__png_info(s, x, y, comp))  return 1;\n   #endif\n\n   #ifndef STBI_NO_GIF\n   if (stbi__gif_info(s, x, y, comp))  return 1;\n   #endif\n\n   #ifndef STBI_NO_BMP\n   if (stbi__bmp_info(s, x, y, comp))  return 1;\n   #endif\n\n   #ifndef STBI_NO_PSD\n   if (stbi__psd_info(s, x, y, comp))  return 1;\n   #endif\n\n   #ifndef STBI_NO_PIC\n   if (stbi__pic_info(s, x, y, comp))  return 1;\n   #endif\n\n   #ifndef STBI_NO_PNM\n   if (stbi__pnm_info(s, x, y, comp))  return 1;\n   #endif\n\n   #ifndef STBI_NO_HDR\n   if (stbi__hdr_info(s, x, y, comp))  return 1;\n   #endif\n\n   // test tga last because it's a crappy test!\n   #ifndef STBI_NO_TGA\n   if (stbi__tga_info(s, x, y, comp))\n       return 1;\n   #endif\n   return stbi__err(\"unknown image type\", \"Image not of any known type, or corrupt\");\n}\n\nstatic int stbi__is_16_main(stbi__context *s)\n{\n   #ifndef STBI_NO_PNG\n   if (stbi__png_is16(s))  return 1;\n   #endif\n\n   #ifndef STBI_NO_PSD\n   if (stbi__psd_is16(s))  return 1;\n   #endif\n\n   return 0;\n}\n\n#ifndef STBI_NO_STDIO\nSTBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp)\n{\n    FILE *f = stbi__fopen(filename, \"rb\");\n    int result;\n    if (!f) return stbi__err(\"can't fopen\", \"Unable to open file\");\n    result = stbi_info_from_file(f, x, y, comp);\n    fclose(f);\n    return result;\n}\n\nSTBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp)\n{\n   int r;\n   stbi__context s;\n   long pos = ftell(f);\n   stbi__start_file(&s, f);\n   r = stbi__info_main(&s,x,y,comp);\n   fseek(f,pos,SEEK_SET);\n   return r;\n}\n\nSTBIDEF int stbi_is_16_bit(char const *filename)\n{\n    FILE *f = stbi__fopen(filename, \"rb\");\n    int result;\n    if (!f) return stbi__err(\"can't fopen\", \"Unable to open file\");\n    result = stbi_is_16_bit_from_file(f);\n    fclose(f);\n    return result;\n}\n\nSTBIDEF int stbi_is_16_bit_from_file(FILE *f)\n{\n   int r;\n   stbi__context s;\n   long pos = ftell(f);\n   stbi__start_file(&s, f);\n   r = stbi__is_16_main(&s);\n   fseek(f,pos,SEEK_SET);\n   return r;\n}\n#endif // !STBI_NO_STDIO\n\nSTBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp)\n{\n   stbi__context s;\n   stbi__start_mem(&s,buffer,len);\n   return stbi__info_main(&s,x,y,comp);\n}\n\nSTBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp)\n{\n   stbi__context s;\n   stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user);\n   return stbi__info_main(&s,x,y,comp);\n}\n\nSTBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len)\n{\n   stbi__context s;\n   stbi__start_mem(&s,buffer,len);\n   return stbi__is_16_main(&s);\n}\n\nSTBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *c, void *user)\n{\n   stbi__context s;\n   stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user);\n   return stbi__is_16_main(&s);\n}\n\n#endif // STB_IMAGE_IMPLEMENTATION\n\n/*\n   revision history:\n      2.20  (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs\n      2.19  (2018-02-11) fix warning\n      2.18  (2018-01-30) fix warnings\n      2.17  (2018-01-29) change sbti__shiftsigned to avoid clang -O2 bug\n                         1-bit BMP\n                         *_is_16_bit api\n                         avoid warnings\n      2.16  (2017-07-23) all functions have 16-bit variants;\n                         STBI_NO_STDIO works again;\n                         compilation fixes;\n                         fix rounding in unpremultiply;\n                         optimize vertical flip;\n                         disable raw_len validation;\n                         documentation fixes\n      2.15  (2017-03-18) fix png-1,2,4 bug; now all Imagenet JPGs decode;\n                         warning fixes; disable run-time SSE detection on gcc;\n                         uniform handling of optional \"return\" values;\n                         thread-safe initialization of zlib tables\n      2.14  (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs\n      2.13  (2016-11-29) add 16-bit API, only supported for PNG right now\n      2.12  (2016-04-02) fix typo in 2.11 PSD fix that caused crashes\n      2.11  (2016-04-02) allocate large structures on the stack\n                         remove white matting for transparent PSD\n                         fix reported channel count for PNG & BMP\n                         re-enable SSE2 in non-gcc 64-bit\n                         support RGB-formatted JPEG\n                         read 16-bit PNGs (only as 8-bit)\n      2.10  (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED\n      2.09  (2016-01-16) allow comments in PNM files\n                         16-bit-per-pixel TGA (not bit-per-component)\n                         info() for TGA could break due to .hdr handling\n                         info() for BMP to shares code instead of sloppy parse\n                         can use STBI_REALLOC_SIZED if allocator doesn't support realloc\n                         code cleanup\n      2.08  (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA\n      2.07  (2015-09-13) fix compiler warnings\n                         partial animated GIF support\n                         limited 16-bpc PSD support\n                         #ifdef unused functions\n                         bug with < 92 byte PIC,PNM,HDR,TGA\n      2.06  (2015-04-19) fix bug where PSD returns wrong '*comp' value\n      2.05  (2015-04-19) fix bug in progressive JPEG handling, fix warning\n      2.04  (2015-04-15) try to re-enable SIMD on MinGW 64-bit\n      2.03  (2015-04-12) extra corruption checking (mmozeiko)\n                         stbi_set_flip_vertically_on_load (nguillemot)\n                         fix NEON support; fix mingw support\n      2.02  (2015-01-19) fix incorrect assert, fix warning\n      2.01  (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2\n      2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG\n      2.00  (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg)\n                         progressive JPEG (stb)\n                         PGM/PPM support (Ken Miller)\n                         STBI_MALLOC,STBI_REALLOC,STBI_FREE\n                         GIF bugfix -- seemingly never worked\n                         STBI_NO_*, STBI_ONLY_*\n      1.48  (2014-12-14) fix incorrectly-named assert()\n      1.47  (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb)\n                         optimize PNG (ryg)\n                         fix bug in interlaced PNG with user-specified channel count (stb)\n      1.46  (2014-08-26)\n              fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG\n      1.45  (2014-08-16)\n              fix MSVC-ARM internal compiler error by wrapping malloc\n      1.44  (2014-08-07)\n              various warning fixes from Ronny Chevalier\n      1.43  (2014-07-15)\n              fix MSVC-only compiler problem in code changed in 1.42\n      1.42  (2014-07-09)\n              don't define _CRT_SECURE_NO_WARNINGS (affects user code)\n              fixes to stbi__cleanup_jpeg path\n              added STBI_ASSERT to avoid requiring assert.h\n      1.41  (2014-06-25)\n              fix search&replace from 1.36 that messed up comments/error messages\n      1.40  (2014-06-22)\n              fix gcc struct-initialization warning\n      1.39  (2014-06-15)\n              fix to TGA optimization when req_comp != number of components in TGA;\n              fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite)\n              add support for BMP version 5 (more ignored fields)\n      1.38  (2014-06-06)\n              suppress MSVC warnings on integer casts truncating values\n              fix accidental rename of 'skip' field of I/O\n      1.37  (2014-06-04)\n              remove duplicate typedef\n      1.36  (2014-06-03)\n              convert to header file single-file library\n              if de-iphone isn't set, load iphone images color-swapped instead of returning NULL\n      1.35  (2014-05-27)\n              various warnings\n              fix broken STBI_SIMD path\n              fix bug where stbi_load_from_file no longer left file pointer in correct place\n              fix broken non-easy path for 32-bit BMP (possibly never used)\n              TGA optimization by Arseny Kapoulkine\n      1.34  (unknown)\n              use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case\n      1.33  (2011-07-14)\n              make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements\n      1.32  (2011-07-13)\n              support for \"info\" function for all supported filetypes (SpartanJ)\n      1.31  (2011-06-20)\n              a few more leak fixes, bug in PNG handling (SpartanJ)\n      1.30  (2011-06-11)\n              added ability to load files via callbacks to accomidate custom input streams (Ben Wenger)\n              removed deprecated format-specific test/load functions\n              removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway\n              error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha)\n              fix inefficiency in decoding 32-bit BMP (David Woo)\n      1.29  (2010-08-16)\n              various warning fixes from Aurelien Pocheville\n      1.28  (2010-08-01)\n              fix bug in GIF palette transparency (SpartanJ)\n      1.27  (2010-08-01)\n              cast-to-stbi_uc to fix warnings\n      1.26  (2010-07-24)\n              fix bug in file buffering for PNG reported by SpartanJ\n      1.25  (2010-07-17)\n              refix trans_data warning (Won Chun)\n      1.24  (2010-07-12)\n              perf improvements reading from files on platforms with lock-heavy fgetc()\n              minor perf improvements for jpeg\n              deprecated type-specific functions so we'll get feedback if they're needed\n              attempt to fix trans_data warning (Won Chun)\n      1.23    fixed bug in iPhone support\n      1.22  (2010-07-10)\n              removed image *writing* support\n              stbi_info support from Jetro Lauha\n              GIF support from Jean-Marc Lienher\n              iPhone PNG-extensions from James Brown\n              warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva)\n      1.21    fix use of 'stbi_uc' in header (reported by jon blow)\n      1.20    added support for Softimage PIC, by Tom Seddon\n      1.19    bug in interlaced PNG corruption check (found by ryg)\n      1.18  (2008-08-02)\n              fix a threading bug (local mutable static)\n      1.17    support interlaced PNG\n      1.16    major bugfix - stbi__convert_format converted one too many pixels\n      1.15    initialize some fields for thread safety\n      1.14    fix threadsafe conversion bug\n              header-file-only version (#define STBI_HEADER_FILE_ONLY before including)\n      1.13    threadsafe\n      1.12    const qualifiers in the API\n      1.11    Support installable IDCT, colorspace conversion routines\n      1.10    Fixes for 64-bit (don't use \"unsigned long\")\n              optimized upsampling by Fabian \"ryg\" Giesen\n      1.09    Fix format-conversion for PSD code (bad global variables!)\n      1.08    Thatcher Ulrich's PSD code integrated by Nicolas Schulz\n      1.07    attempt to fix C++ warning/errors again\n      1.06    attempt to fix C++ warning/errors again\n      1.05    fix TGA loading to return correct *comp and use good luminance calc\n      1.04    default float alpha is 1, not 255; use 'void *' for stbi_image_free\n      1.03    bugfixes to STBI_NO_STDIO, STBI_NO_HDR\n      1.02    support for (subset of) HDR files, float interface for preferred access to them\n      1.01    fix bug: possible bug in handling right-side up bmps... not sure\n              fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all\n      1.00    interface to zlib that skips zlib header\n      0.99    correct handling of alpha in palette\n      0.98    TGA loader by lonesock; dynamically add loaders (untested)\n      0.97    jpeg errors on too large a file; also catch another malloc failure\n      0.96    fix detection of invalid v value - particleman@mollyrocket forum\n      0.95    during header scan, seek to markers in case of padding\n      0.94    STBI_NO_STDIO to disable stdio usage; rename all #defines the same\n      0.93    handle jpegtran output; verbose errors\n      0.92    read 4,8,16,24,32-bit BMP files of several formats\n      0.91    output 24-bit Windows 3.0 BMP files\n      0.90    fix a few more warnings; bump version number to approach 1.0\n      0.61    bugfixes due to Marc LeBlanc, Christopher Lloyd\n      0.60    fix compiling as c++\n      0.59    fix warnings: merge Dave Moore's -Wall fixes\n      0.58    fix bug: zlib uncompressed mode len/nlen was wrong endian\n      0.57    fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available\n      0.56    fix bug: zlib uncompressed mode len vs. nlen\n      0.55    fix bug: restart_interval not initialized to 0\n      0.54    allow NULL for 'int *comp'\n      0.53    fix bug in png 3->4; speedup png decoding\n      0.52    png handles req_comp=3,4 directly; minor cleanup; jpeg comments\n      0.51    obey req_comp requests, 1-component jpegs return as 1-component,\n              on 'test' only check type, not whether we support this variant\n      0.50  (2006-11-19)\n              first released version\n*/\n\n\n/*\n------------------------------------------------------------------------------\nThis software is available under 2 licenses -- choose whichever you prefer.\n------------------------------------------------------------------------------\nALTERNATIVE A - MIT License\nCopyright (c) 2017 Sean Barrett\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\nof the Software, and to permit persons to whom the Software is furnished to do\nso, subject to the following conditions:\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n------------------------------------------------------------------------------\nALTERNATIVE B - Public Domain (www.unlicense.org)\nThis is free and unencumbered software released into the public domain.\nAnyone is free to copy, modify, publish, use, compile, sell, or distribute this\nsoftware, either in source code form or as a compiled binary, for any purpose,\ncommercial or non-commercial, and by any means.\nIn jurisdictions that recognize copyright laws, the author or authors of this\nsoftware dedicate any and all copyright interest in the software to the public\ndomain. We make this dedication for the benefit of the public at large and to\nthe detriment of our heirs and successors. We intend this dedication to be an\novert act of relinquishment in perpetuity of all present and future rights to\nthis software under copyright law.\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\nACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n------------------------------------------------------------------------------\n*/\n"
  },
  {
    "path": "ios.toolchain.cmake",
    "content": "# This file is part of the ios-cmake project. It was retrieved from\n# https://github.com/leetal/ios-cmake.git, which is a fork of\n# https://github.com/gerstrong/ios-cmake.git, which is a fork of\n# https://github.com/cristeab/ios-cmake.git, which is a fork of\n# https://code.google.com/p/ios-cmake/. Which in turn is based off of\n# the Platform/Darwin.cmake and Platform/UnixPaths.cmake files which\n# are included with CMake 2.8.4\n#\n# The ios-cmake project is licensed under the new BSD license.\n#\n# Copyright (c) 2014, Bogdan Cristea and LTE Engineering Software,\n# Kitware, Inc., Insight Software Consortium.  All rights reserved.\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# 1. Redistributions of source code must retain the above copyright\n# notice, this list of conditions and the following disclaimer.\n#\n# 2. 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#\n# 3. Neither the name of the copyright holder nor the names of its\n# contributors may be used to endorse or promote products derived from\n# this software without specific prior written permission.\n#\n# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n# \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS\n# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE\n# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN\n# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n# POSSIBILITY OF SUCH DAMAGE.\n#\n# This file is based on the Platform/Darwin.cmake and\n# Platform/UnixPaths.cmake files which are included with CMake 2.8.4\n# It has been altered for iOS development.\n#\n# Updated by Alex Stewart (alexs.mac@gmail.com)\n#\n# *****************************************************************************\n#      Now maintained by Alexander Widerberg (widerbergaren [at] gmail.com)\n#                      under the BSD-3-Clause license\n#                   https://github.com/leetal/ios-cmake\n# *****************************************************************************\n#\n#                           INFORMATION / HELP\n#\n###############################################################################\n#                                  OPTIONS                                    #\n###############################################################################\n#\n# PLATFORM: (default \"OS64\")\n#    OS = Build for iPhoneOS.\n#    OS64 = Build for arm64 iphoneOS.\n#    OS64COMBINED = Build for arm64 x86_64 iphoneOS + iphoneOS Simulator. Combined into FAT STATIC lib (only supported on 3.14+ of CMake with \"-G Xcode\" argument in combination with the \"cmake --install\" CMake build step)\n#    SIMULATOR = Build for x86 i386 iphoneOS Simulator.\n#    SIMULATOR64 = Build for x86_64 iphoneOS Simulator.\n#    SIMULATORARM64 = Build for arm64 iphoneOS Simulator.\n#    SIMULATOR64COMBINED = Build for arm64 x86_64 iphoneOS Simulator. Combined into FAT STATIC lib (supported on 3.14+ of CMakewith \"-G Xcode\" argument ONLY)\n#    TVOS = Build for arm64 tvOS.\n#    TVOSCOMBINED = Build for arm64 x86_64 tvOS + tvOS Simulator. Combined into FAT STATIC lib (only supported on 3.14+ of CMake with \"-G Xcode\" argument in combination with the \"cmake --install\" CMake build step)\n#    SIMULATOR_TVOS = Build for x86_64 tvOS Simulator.\n#    SIMULATORARM64_TVOS = Build for arm64 tvOS Simulator.\n#    VISIONOSCOMBINED = Build for arm64 visionOS + visionOS Simulator. Combined into FAT STATIC lib (only supported on 3.14+ of CMake with \"-G Xcode\" argument in combination with the \"cmake --install\" CMake build step)\n#    VISIONOS = Build for arm64 visionOS.\n#    SIMULATOR_VISIONOS = Build for arm64 visionOS Simulator.\n#    WATCHOS = Build for armv7k arm64_32 for watchOS.\n#    WATCHOSCOMBINED = Build for armv7k arm64_32 x86_64 watchOS + watchOS Simulator. Combined into FAT STATIC lib (only supported on 3.14+ of CMake with \"-G Xcode\" argument in combination with the \"cmake --install\" CMake build step)\n#    SIMULATOR_WATCHOS = Build for x86_64 for watchOS Simulator.\n#    SIMULATORARM64_WATCHOS = Build for arm64 for watchOS Simulator.\n#    SIMULATOR_WATCHOSCOMBINED = Build for arm64 x86_64 for watchOS Simulator. Combined into FAT STATIC lib (supported on 3.14+ of CMakewith \"-G Xcode\" argument ONLY)\n#    MAC = Build for x86_64 macOS.\n#    MAC_ARM64 = Build for Apple Silicon macOS.\n#    MAC_UNIVERSAL = Combined build for x86_64 and Apple Silicon on macOS.\n#    MAC_CATALYST = Build for x86_64 macOS with Catalyst support (iOS toolchain on macOS).\n#                   Note: The build argument \"MACOSX_DEPLOYMENT_TARGET\" can be used to control min-version of macOS\n#    MAC_CATALYST_ARM64 = Build for Apple Silicon macOS with Catalyst support (iOS toolchain on macOS).\n#                         Note: The build argument \"MACOSX_DEPLOYMENT_TARGET\" can be used to control min-version of macOS\n#    MAC_CATALYST_UNIVERSAL = Combined build for x86_64 and Apple Silicon on Catalyst.\n#\n# CMAKE_OSX_SYSROOT: Path to the SDK to use.  By default this is\n#    automatically determined from PLATFORM and xcodebuild, but\n#    can also be manually specified (although this should not be required).\n#\n# CMAKE_DEVELOPER_ROOT: Path to the Developer directory for the platform\n#    being compiled for.  By default, this is automatically determined from\n#    CMAKE_OSX_SYSROOT, but can also be manually specified (although this should\n#    not be required).\n#\n# DEPLOYMENT_TARGET: Minimum SDK version to target. Default 6.0 on watchOS, 13.0 on tvOS+iOS/iPadOS, 11.0 on macOS, 1.0 on visionOS\n#\n# NAMED_LANGUAGE_SUPPORT:\n#    ON (default) = Will require \"enable_language(OBJC) and/or enable_language(OBJCXX)\" for full OBJC|OBJCXX support\n#    OFF = Will embed the OBJC and OBJCXX flags into the CMAKE_C_FLAGS and CMAKE_CXX_FLAGS (legacy behavior, CMake version < 3.16)\n#\n# ENABLE_BITCODE: (ON|OFF) Enables or disables bitcode support. Default OFF\n#\n# ENABLE_ARC: (ON|OFF) Enables or disables ARC support. Default ON (ARC enabled by default)\n#\n# ENABLE_VISIBILITY: (ON|OFF) Enables or disables symbol visibility support. Default OFF (visibility hidden by default)\n#\n# ENABLE_STRICT_TRY_COMPILE: (ON|OFF) Enables or disables strict try_compile() on all Check* directives (will run linker\n#    to actually check if linking is possible). Default OFF (will set CMAKE_TRY_COMPILE_TARGET_TYPE to STATIC_LIBRARY)\n#\n# ARCHS: (armv7 armv7s armv7k arm64 arm64_32 i386 x86_64) If specified, will override the default architectures for the given PLATFORM\n#    OS = armv7 armv7s arm64 (if applicable)\n#    OS64 = arm64 (if applicable)\n#    SIMULATOR = i386\n#    SIMULATOR64 = x86_64\n#    SIMULATORARM64 = arm64\n#    TVOS = arm64\n#    SIMULATOR_TVOS = x86_64 (i386 has since long been deprecated)\n#    SIMULATORARM64_TVOS = arm64\n#    WATCHOS = armv7k arm64_32 (if applicable)\n#    SIMULATOR_WATCHOS = x86_64 (i386 has since long been deprecated)\n#    SIMULATORARM64_WATCHOS = arm64\n#    MAC = x86_64\n#    MAC_ARM64 = arm64\n#    MAC_UNIVERSAL = x86_64 arm64\n#    MAC_CATALYST = x86_64\n#    MAC_CATALYST_ARM64 = arm64\n#    MAC_CATALYST_UNIVERSAL = x86_64 arm64\n#\n# NOTE: When manually specifying ARCHS, put a semi-colon between the entries. E.g., -DARCHS=\"armv7;arm64\"\n#\n###############################################################################\n#                                END OPTIONS                                  #\n###############################################################################\n#\n# This toolchain defines the following properties (available via get_property()) for use externally:\n#\n# PLATFORM: The currently targeted platform.\n# XCODE_VERSION: Version number (not including Build version) of Xcode detected.\n# SDK_VERSION: Version of SDK being used.\n# OSX_ARCHITECTURES: Architectures being compiled for (generated from PLATFORM).\n# APPLE_TARGET_TRIPLE: Used by autoconf build systems. NOTE: If \"ARCHS\" is overridden, this will *NOT* be set!\n#\n# This toolchain defines the following macros for use externally:\n#\n# set_xcode_property (TARGET XCODE_PROPERTY XCODE_VALUE XCODE_VARIANT)\n#   A convenience macro for setting xcode specific properties on targets.\n#   Available variants are: All, Release, RelWithDebInfo, Debug, MinSizeRel\n#   example: set_xcode_property (myioslib IPHONEOS_DEPLOYMENT_TARGET \"3.1\" \"all\").\n#\n# find_host_package (PROGRAM ARGS)\n#   A macro used to find executable programs on the host system, not within the\n#   environment. Thanks to the android-cmake project for providing the\n#   command.\n#\n\ncmake_minimum_required(VERSION 3.8..3.30)\n\n# CMake invokes the toolchain file twice during the first build, but only once during subsequent rebuilds.\n# NOTE: To improve single-library build-times, provide the flag \"OS_SINGLE_BUILD\" as a build argument.\nif(DEFINED OS_SINGLE_BUILD AND DEFINED ENV{_IOS_TOOLCHAIN_HAS_RUN})\n  return()\nendif()\nset(ENV{_IOS_TOOLCHAIN_HAS_RUN} true)\n\n# List of supported platform values\nlist(APPEND _supported_platforms\n        \"OS\" \"OS64\" \"OS64COMBINED\" \"SIMULATOR\" \"SIMULATOR64\" \"SIMULATORARM64\" \"SIMULATOR64COMBINED\"\n        \"TVOS\" \"TVOSCOMBINED\" \"SIMULATOR_TVOS\" \"SIMULATORARM64_TVOS\"\n        \"WATCHOS\" \"WATCHOSCOMBINED\" \"SIMULATOR_WATCHOS\" \"SIMULATORARM64_WATCHOS\" \"SIMULATOR_WATCHOSCOMBINED\"\n        \"MAC\" \"MAC_ARM64\" \"MAC_UNIVERSAL\"\n        \"VISIONOS\" \"SIMULATOR_VISIONOS\" \"VISIONOSCOMBINED\"\n        \"MAC_CATALYST\" \"MAC_CATALYST_ARM64\" \"MAC_CATALYST_UNIVERSAL\")\n\n# Cache what generator is used\nset(USED_CMAKE_GENERATOR \"${CMAKE_GENERATOR}\")\n\n# Check if using a CMake version capable of building combined FAT builds (simulator and target slices combined in one static lib)\nif(${CMAKE_VERSION} VERSION_GREATER_EQUAL \"3.14\")\n  set(MODERN_CMAKE YES)\nendif()\n\n# Get the Xcode version being used.\n# Problem: CMake runs toolchain files multiple times, but can't read cache variables on some runs.\n# Workaround: On the first run (in which cache variables are always accessible), set an intermediary environment variable.\n#\n# NOTE: This pattern is used in many places in this toolchain to speed up checks of all sorts\nif(DEFINED XCODE_VERSION_INT)\n  # Environment variables are always preserved.\n  set(ENV{_XCODE_VERSION_INT} \"${XCODE_VERSION_INT}\")\nelseif(DEFINED ENV{_XCODE_VERSION_INT})\n  set(XCODE_VERSION_INT \"$ENV{_XCODE_VERSION_INT}\")\nelseif(NOT DEFINED XCODE_VERSION_INT)\n  find_program(XCODEBUILD_EXECUTABLE xcodebuild)\n  if(NOT XCODEBUILD_EXECUTABLE)\n    message(FATAL_ERROR \"xcodebuild not found. Please install either the standalone commandline tools or Xcode.\")\n  endif()\n  execute_process(COMMAND ${XCODEBUILD_EXECUTABLE} -version\n          OUTPUT_VARIABLE XCODE_VERSION_INT\n          ERROR_QUIET\n          OUTPUT_STRIP_TRAILING_WHITESPACE)\n  string(REGEX MATCH \"Xcode [0-9\\\\.]+\" XCODE_VERSION_INT \"${XCODE_VERSION_INT}\")\n  string(REGEX REPLACE \"Xcode ([0-9\\\\.]+)\" \"\\\\1\" XCODE_VERSION_INT \"${XCODE_VERSION_INT}\")\n  set(XCODE_VERSION_INT \"${XCODE_VERSION_INT}\" CACHE INTERNAL \"\")\nendif()\n\n# Assuming that xcode 12.0 is installed you most probably have ios sdk 14.0 or later installed (tested on Big Sur)\n# if you don't set a deployment target it will be set the way you only get 64-bit builds\n#if(NOT DEFINED DEPLOYMENT_TARGET AND XCODE_VERSION_INT VERSION_GREATER 12.0)\n# Temporarily fix the arm64 issues in CMake install-combined by excluding arm64 for simulator builds (needed for Apple Silicon...)\n#  set(CMAKE_XCODE_ATTRIBUTE_EXCLUDED_ARCHS[sdk=iphonesimulator*] \"arm64\")\n#endif()\n\n# Check if the platform variable is set\nif(DEFINED PLATFORM)\n  # Environment variables are always preserved.\n  set(ENV{_PLATFORM} \"${PLATFORM}\")\nelseif(DEFINED ENV{_PLATFORM})\n  set(PLATFORM \"$ENV{_PLATFORM}\")\nelseif(NOT DEFINED PLATFORM)\n  message(FATAL_ERROR \"PLATFORM argument not set. Bailing configure since I don't know what target you want to build for!\")\nendif ()\n\nif(PLATFORM MATCHES \".*COMBINED\" AND NOT CMAKE_GENERATOR MATCHES \"Xcode\")\n  message(FATAL_ERROR \"The combined builds support requires Xcode to be used as a generator via '-G Xcode' command-line argument in CMake\")\nendif()\n\n# Safeguard that the platform value is set and is one of the supported values\nlist(FIND _supported_platforms ${PLATFORM} contains_PLATFORM)\nif(\"${contains_PLATFORM}\" EQUAL \"-1\")\n  string(REPLACE \";\"  \"\\n * \" _supported_platforms_formatted \"${_supported_platforms}\")\n  message(FATAL_ERROR \" Invalid PLATFORM specified! Current value: ${PLATFORM}.\\n\"\n          \" Supported PLATFORM values: \\n * ${_supported_platforms_formatted}\")\nendif()\n\n# Check if Apple Silicon is supported\nif(PLATFORM MATCHES \"^(MAC_ARM64)$|^(MAC_CATALYST_ARM64)$|^(MAC_UNIVERSAL)$|^(MAC_CATALYST_UNIVERSAL)$\" AND ${CMAKE_VERSION} VERSION_LESS \"3.19.5\")\n  message(FATAL_ERROR \"Apple Silicon builds requires a minimum of CMake 3.19.5\")\nendif()\n\n# Touch the toolchain variable to suppress the \"unused variable\" warning.\n# This happens if CMake is invoked with the same command line the second time.\nif(CMAKE_TOOLCHAIN_FILE)\nendif()\n\n# Fix for PThread library not in path\nset(CMAKE_THREAD_LIBS_INIT \"-lpthread\")\nset(CMAKE_HAVE_THREADS_LIBRARY 1)\nset(CMAKE_USE_WIN32_THREADS_INIT 0)\nset(CMAKE_USE_PTHREADS_INIT 1)\n\n# Specify named language support defaults.\nif(NOT DEFINED NAMED_LANGUAGE_SUPPORT AND ${CMAKE_VERSION} VERSION_GREATER_EQUAL \"3.16\")\n  set(NAMED_LANGUAGE_SUPPORT ON)\n  message(STATUS \"[DEFAULTS] Using explicit named language support! E.g., enable_language(CXX) is needed in the project files.\")\nelseif(NOT DEFINED NAMED_LANGUAGE_SUPPORT AND ${CMAKE_VERSION} VERSION_LESS \"3.16\")\n  set(NAMED_LANGUAGE_SUPPORT OFF)\n  message(STATUS \"[DEFAULTS] Disabling explicit named language support. Falling back to legacy behavior.\")\nelseif(DEFINED NAMED_LANGUAGE_SUPPORT AND ${CMAKE_VERSION} VERSION_LESS \"3.16\")\n  message(FATAL_ERROR \"CMake named language support for OBJC and OBJCXX was added in CMake 3.16.\")\nendif()\nset(NAMED_LANGUAGE_SUPPORT_INT ${NAMED_LANGUAGE_SUPPORT} CACHE BOOL\n        \"Whether or not to enable explicit named language support\" FORCE)\n\n# Specify the minimum version of the deployment target.\nif(NOT DEFINED DEPLOYMENT_TARGET)\n  if (PLATFORM MATCHES \"WATCHOS\")\n    # Unless specified, SDK version 6.0 is used by default as minimum target version (watchOS).\n    set(DEPLOYMENT_TARGET \"6.0\")\n  elseif(PLATFORM STREQUAL \"MAC\")\n    # Unless specified, SDK version 11.0 (Big Sur) is used by default as the minimum target version (macOS on x86).\n    set(DEPLOYMENT_TARGET \"11.0\")\n  elseif(PLATFORM STREQUAL \"VISIONOS\" OR PLATFORM STREQUAL \"SIMULATOR_VISIONOS\" OR PLATFORM STREQUAL \"VISIONOSCOMBINED\")\n    # Unless specified, SDK version 1.0 is used by default as minimum target version (visionOS).\n    set(DEPLOYMENT_TARGET \"1.0\")\n  elseif(PLATFORM STREQUAL \"MAC_ARM64\")\n    # Unless specified, SDK version 11.0 (Big Sur) is used by default as the minimum target version (macOS on arm).\n    set(DEPLOYMENT_TARGET \"11.0\")\n  elseif(PLATFORM STREQUAL \"MAC_UNIVERSAL\")\n    # Unless specified, SDK version 11.0 (Big Sur) is used by default as minimum target version for universal builds.\n    set(DEPLOYMENT_TARGET \"11.0\")\n  elseif(PLATFORM STREQUAL \"MAC_CATALYST\" OR PLATFORM STREQUAL \"MAC_CATALYST_ARM64\" OR PLATFORM STREQUAL \"MAC_CATALYST_UNIVERSAL\")\n    # Unless specified, SDK version 13.1 is used by default as the minimum target version (mac catalyst minimum requirement).\n    set(DEPLOYMENT_TARGET \"13.1\")\n  else()\n    # Unless specified, SDK version 13.0 is used by default as the minimum target version (iOS, tvOS).\n    set(DEPLOYMENT_TARGET \"13.0\")\n  endif()\n  message(STATUS \"[DEFAULTS] Using the default min-version since DEPLOYMENT_TARGET not provided!\")\nelseif(DEFINED DEPLOYMENT_TARGET AND PLATFORM MATCHES \"^MAC_CATALYST\" AND ${DEPLOYMENT_TARGET} VERSION_LESS \"13.1\")\n  message(FATAL_ERROR \"Mac Catalyst builds requires a minimum deployment target of 13.1!\")\nendif()\n\n# Store the DEPLOYMENT_TARGET in the cache\nset(DEPLOYMENT_TARGET \"${DEPLOYMENT_TARGET}\" CACHE INTERNAL \"\")\n\n# Handle the case where we are targeting iOS and a version above 10.3.4 (32-bit support dropped officially)\nif(PLATFORM STREQUAL \"OS\" AND DEPLOYMENT_TARGET VERSION_GREATER_EQUAL 10.3.4)\n  set(PLATFORM \"OS64\")\n  message(STATUS \"Targeting minimum SDK version ${DEPLOYMENT_TARGET}. Dropping 32-bit support.\")\nelseif(PLATFORM STREQUAL \"SIMULATOR\" AND DEPLOYMENT_TARGET VERSION_GREATER_EQUAL 10.3.4)\n  set(PLATFORM \"SIMULATOR64\")\n  message(STATUS \"Targeting minimum SDK version ${DEPLOYMENT_TARGET}. Dropping 32-bit support.\")\nendif()\n\nset(PLATFORM_INT \"${PLATFORM}\")\n\nif(DEFINED ARCHS)\n  string(REPLACE \";\" \"-\" ARCHS_SPLIT \"${ARCHS}\")\nendif()\n\n# Determine the platform name and architectures for use in xcodebuild commands\n# from the specified PLATFORM_INT name.\nif(PLATFORM_INT STREQUAL \"OS\")\n  set(SDK_NAME iphoneos)\n  if(NOT ARCHS)\n    set(ARCHS armv7 armv7s arm64)\n    set(APPLE_TARGET_TRIPLE_INT arm-apple-ios${DEPLOYMENT_TARGET})\n  else()\n    set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-ios${DEPLOYMENT_TARGET})\n  endif()\nelseif(PLATFORM_INT STREQUAL \"OS64\")\n  set(SDK_NAME iphoneos)\n  if(NOT ARCHS)\n    if (XCODE_VERSION_INT VERSION_GREATER 10.0)\n      set(ARCHS arm64) # FIXME: Add arm64e when Apple has fixed the integration issues with it, libarclite_iphoneos.a is currently missing bitcode markers for example\n    else()\n      set(ARCHS arm64)\n    endif()\n    set(APPLE_TARGET_TRIPLE_INT arm64-apple-ios${DEPLOYMENT_TARGET})\n  else()\n    set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-ios${DEPLOYMENT_TARGET})\n  endif()\nelseif(PLATFORM_INT STREQUAL \"OS64COMBINED\")\n  set(SDK_NAME iphoneos)\n  if(MODERN_CMAKE)\n    if(NOT ARCHS)\n      if (XCODE_VERSION_INT VERSION_GREATER 12.0)\n        set(ARCHS arm64 x86_64)\n        set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=iphoneos*] \"arm64\")\n        set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=iphonesimulator*] \"x86_64 arm64\")\n        set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphoneos*] \"arm64\")\n        set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphonesimulator*] \"x86_64 arm64\")\n      else()\n        set(ARCHS arm64 x86_64)\n        set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=iphoneos*] \"arm64\")\n        set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=iphonesimulator*] \"x86_64\")\n        set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphoneos*] \"arm64\")\n        set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphonesimulator*] \"x86_64\")\n      endif()\n      set(APPLE_TARGET_TRIPLE_INT arm64-x86_64-apple-ios${DEPLOYMENT_TARGET})\n    else()\n      set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-ios${DEPLOYMENT_TARGET})\n    endif()\n  else()\n    message(FATAL_ERROR \"Please make sure that you are running CMake 3.14+ to make the OS64COMBINED setting work\")\n  endif()\nelseif(PLATFORM_INT STREQUAL \"SIMULATOR64COMBINED\")\n  set(SDK_NAME iphonesimulator)\n  if(MODERN_CMAKE)\n    if(NOT ARCHS)\n      if (XCODE_VERSION_INT VERSION_GREATER 12.0)\n        set(ARCHS arm64 x86_64) # FIXME: Add arm64e when Apple have fixed the integration issues with it, libarclite_iphoneos.a is currently missing bitcode markers for example\n        set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=iphoneos*] \"\")\n        set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=iphonesimulator*] \"x86_64 arm64\")\n        set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphoneos*] \"\")\n        set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphonesimulator*] \"x86_64 arm64\")\n      else()\n        set(ARCHS arm64 x86_64)\n        set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=iphoneos*] \"\")\n        set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=iphonesimulator*] \"x86_64\")\n        set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphoneos*] \"\")\n        set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphonesimulator*] \"x86_64\")\n      endif()\n      set(APPLE_TARGET_TRIPLE_INT aarch64-x86_64-apple-ios${DEPLOYMENT_TARGET}-simulator)\n    else()\n      set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-ios${DEPLOYMENT_TARGET}-simulator)\n    endif()\n  else()\n    message(FATAL_ERROR \"Please make sure that you are running CMake 3.14+ to make the SIMULATOR64COMBINED setting work\")\n  endif()\nelseif(PLATFORM_INT STREQUAL \"SIMULATOR\")\n  set(SDK_NAME iphonesimulator)\n  if(NOT ARCHS)\n    set(ARCHS i386)\n    set(APPLE_TARGET_TRIPLE_INT i386-apple-ios${DEPLOYMENT_TARGET}-simulator)\n  else()\n    set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-ios${DEPLOYMENT_TARGET}-simulator)\n  endif()\n  message(DEPRECATION \"SIMULATOR IS DEPRECATED. Consider using SIMULATOR64 instead.\")\nelseif(PLATFORM_INT STREQUAL \"SIMULATOR64\")\n  set(SDK_NAME iphonesimulator)\n  if(NOT ARCHS)\n    set(ARCHS x86_64)\n    set(APPLE_TARGET_TRIPLE_INT x86_64-apple-ios${DEPLOYMENT_TARGET}-simulator)\n  else()\n    set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-ios${DEPLOYMENT_TARGET}-simulator)\n  endif()\nelseif(PLATFORM_INT STREQUAL \"SIMULATORARM64\")\n  set(SDK_NAME iphonesimulator)\n  if(NOT ARCHS)\n    set(ARCHS arm64)\n    set(APPLE_TARGET_TRIPLE_INT arm64-apple-ios${DEPLOYMENT_TARGET}-simulator)\n  else()\n    set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-ios${DEPLOYMENT_TARGET}-simulator)\n  endif()\nelseif(PLATFORM_INT STREQUAL \"TVOS\")\n  set(SDK_NAME appletvos)\n  if(NOT ARCHS)\n    set(ARCHS arm64)\n    set(APPLE_TARGET_TRIPLE_INT arm64-apple-tvos${DEPLOYMENT_TARGET})\n  else()\n    set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-tvos${DEPLOYMENT_TARGET})\n  endif()\nelseif (PLATFORM_INT STREQUAL \"TVOSCOMBINED\")\n  set(SDK_NAME appletvos)\n  if(MODERN_CMAKE)\n    if(NOT ARCHS)\n      set(ARCHS arm64 x86_64)\n      set(APPLE_TARGET_TRIPLE_INT arm64-x86_64-apple-tvos${DEPLOYMENT_TARGET})\n      set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=appletvos*] \"arm64\")\n      set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=appletvsimulator*] \"x86_64 arm64\")\n      set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=appletvos*] \"arm64\")\n      set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=appletvsimulator*] \"x86_64 arm64\")\n    else()\n      set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-tvos${DEPLOYMENT_TARGET})\n    endif()\n  else()\n    message(FATAL_ERROR \"Please make sure that you are running CMake 3.14+ to make the TVOSCOMBINED setting work\")\n  endif()\nelseif(PLATFORM_INT STREQUAL \"SIMULATOR_TVOS\")\n  set(SDK_NAME appletvsimulator)\n  if(NOT ARCHS)\n    set(ARCHS x86_64)\n    set(APPLE_TARGET_TRIPLE_INT x86_64-apple-tvos${DEPLOYMENT_TARGET}-simulator)\n  else()\n    set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-tvos${DEPLOYMENT_TARGET}-simulator)\n  endif()\nelseif(PLATFORM_INT STREQUAL \"SIMULATORARM64_TVOS\")\n  set(SDK_NAME appletvsimulator)\n  if(NOT ARCHS)\n    set(ARCHS arm64)\n    set(APPLE_TARGET_TRIPLE_INT arm64-apple-tvos${DEPLOYMENT_TARGET}-simulator)\n  else()\n    set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-tvos${DEPLOYMENT_TARGET}-simulator)\n  endif()\nelseif(PLATFORM_INT STREQUAL \"WATCHOS\")\n  set(SDK_NAME watchos)\n  if(NOT ARCHS)\n    if (XCODE_VERSION_INT VERSION_GREATER 10.0)\n      set(ARCHS armv7k arm64_32)\n      set(APPLE_TARGET_TRIPLE_INT arm64_32-apple-watchos${DEPLOYMENT_TARGET})\n    else()\n      set(ARCHS armv7k)\n      set(APPLE_TARGET_TRIPLE_INT arm-apple-watchos${DEPLOYMENT_TARGET})\n    endif()\n  else()\n    set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-watchos${DEPLOYMENT_TARGET})\n  endif()\nelseif(PLATFORM_INT STREQUAL \"WATCHOSCOMBINED\")\n  set(SDK_NAME watchos)\n  if(MODERN_CMAKE)\n    if(NOT ARCHS)\n      if (XCODE_VERSION_INT VERSION_GREATER 10.0)\n        set(ARCHS armv7k arm64_32 x86_64)\n        set(APPLE_TARGET_TRIPLE_INT arm64_32-x86_64-apple-watchos${DEPLOYMENT_TARGET})\n        set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=watchos*] \"armv7k arm64_32\")\n        set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=watchsimulator*] \"x86_64\")\n        set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=watchos*] \"armv7k arm64_32\")\n        set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=watchsimulator*] \"x86_64\")\n      else()\n        set(ARCHS armv7k i386)\n        set(APPLE_TARGET_TRIPLE_INT arm-i386-apple-watchos${DEPLOYMENT_TARGET})\n        set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=watchos*] \"armv7k\")\n        set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=watchsimulator*] \"i386\")\n        set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=watchos*] \"armv7k\")\n        set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=watchsimulator*] \"i386\")\n      endif()\n    else()\n      set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-watchos${DEPLOYMENT_TARGET})\n    endif()\n  else()\n    message(FATAL_ERROR \"Please make sure that you are running CMake 3.14+ to make the WATCHOSCOMBINED setting work\")\n  endif()\nelseif(PLATFORM_INT STREQUAL \"SIMULATOR_WATCHOS\")\n  set(SDK_NAME watchsimulator)\n  if(NOT ARCHS)\n    if (XCODE_VERSION_INT VERSION_GREATER 10.0)\n      set(ARCHS x86_64)\n      set(APPLE_TARGET_TRIPLE_INT x86_64-apple-watchos${DEPLOYMENT_TARGET}-simulator)\n    else()\n      set(ARCHS i386)\n      set(APPLE_TARGET_TRIPLE_INT i386-apple-watchos${DEPLOYMENT_TARGET}-simulator)\n    endif()\n  else()\n    set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-watchos${DEPLOYMENT_TARGET}-simulator)\n  endif()\nelseif(PLATFORM_INT STREQUAL \"SIMULATORARM64_WATCHOS\")\n  set(SDK_NAME watchsimulator)\n  if(NOT ARCHS)\n    set(ARCHS arm64)\n    set(APPLE_TARGET_TRIPLE_INT arm64-apple-watchos${DEPLOYMENT_TARGET}-simulator)\n  else()\n    set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-watchos${DEPLOYMENT_TARGET}-simulator)\n  endif()\nelseif(PLATFORM_INT STREQUAL \"SIMULATOR_WATCHOSCOMBINED\")\n  set(SDK_NAME watchsimulator)\n  if(MODERN_CMAKE)\n    if(NOT ARCHS)\n      if (XCODE_VERSION_INT VERSION_GREATER 12.0)\n        set(ARCHS arm64 x86_64)\n        set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=watchos*] \"\")\n        set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=watchsimulator*] \"arm64 x86_64\")\n        set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=watchos*] \"\")\n        set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=watchsimulator*] \"arm64 x86_64\")\n        set(APPLE_TARGET_TRIPLE_INT arm64_x86_64-apple-watchos${DEPLOYMENT_TARGET}-simulator)\n      else()\n        set(ARCHS arm64 i386)\n        set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=watchos*] \"\")\n        set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=watchsimulator*] \"i386\")\n        set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=watchos*] \"\")\n        set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=watchsimulator*] \"i386\")\n        set(APPLE_TARGET_TRIPLE_INT arm64_i386-apple-watchos${DEPLOYMENT_TARGET}-simulator)\n      endif()\n    else()\n      set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-watchos${DEPLOYMENT_TARGET}-simulator)\n    endif()\n  else()\n    message(FATAL_ERROR \"Please make sure that you are running CMake 3.14+ to make the SIMULATOR_WATCHOSCOMBINED setting work\")\n  endif()\nelseif(PLATFORM_INT STREQUAL \"SIMULATOR_VISIONOS\")\n  set(SDK_NAME xrsimulator)\n  if(NOT ARCHS)\n    set(ARCHS arm64)\n    set(APPLE_TARGET_TRIPLE_INT arm64-apple-xros${DEPLOYMENT_TARGET}-simulator)\n  else()\n    set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-xros${DEPLOYMENT_TARGET}-simulator)\n  endif()\nelseif(PLATFORM_INT STREQUAL \"VISIONOS\")\n  set(SDK_NAME xros)\n  if(NOT ARCHS)\n    set(ARCHS arm64)\n    set(APPLE_TARGET_TRIPLE_INT arm64-apple-xros${DEPLOYMENT_TARGET})\n  else()\n    set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-xros${DEPLOYMENT_TARGET})\n  endif()\nelseif(PLATFORM_INT STREQUAL \"VISIONOSCOMBINED\")\n  set(SDK_NAME xros)\n  if(MODERN_CMAKE)\n    if(NOT ARCHS)\n      set(ARCHS arm64)\n      set(APPLE_TARGET_TRIPLE_INT arm64-apple-xros${DEPLOYMENT_TARGET})\n      set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=xros*] \"arm64\")\n      set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=xrsimulator*] \"arm64\")\n    else()\n      set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-xros${DEPLOYMENT_TARGET})\n    endif()\n  else()\n    message(FATAL_ERROR \"Please make sure that you are running CMake 3.14+ to make the VISIONOSCOMBINED setting work\")\n  endif()\nelseif(PLATFORM_INT STREQUAL \"MAC\" OR PLATFORM_INT STREQUAL \"MAC_CATALYST\")\n  set(SDK_NAME macosx)\n  if(NOT ARCHS)\n    set(ARCHS x86_64)\n  endif()\n  string(REPLACE \";\" \"-\" ARCHS_SPLIT \"${ARCHS}\")\n  if(PLATFORM_INT STREQUAL \"MAC\")\n    set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-macosx${DEPLOYMENT_TARGET})\n  elseif(PLATFORM_INT STREQUAL \"MAC_CATALYST\")\n    set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-ios${DEPLOYMENT_TARGET}-macabi)\n  endif()\nelseif(PLATFORM_INT MATCHES \"^(MAC_ARM64)$|^(MAC_CATALYST_ARM64)$\")\n  set(SDK_NAME macosx)\n  if(NOT ARCHS)\n    set(ARCHS arm64)\n  endif()\n  string(REPLACE \";\" \"-\" ARCHS_SPLIT \"${ARCHS}\")\n  if(PLATFORM_INT STREQUAL \"MAC_ARM64\")\n    set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-macosx${DEPLOYMENT_TARGET})\n  elseif(PLATFORM_INT STREQUAL \"MAC_CATALYST_ARM64\")\n    set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-ios${DEPLOYMENT_TARGET}-macabi)\n  endif()\nelseif(PLATFORM_INT STREQUAL \"MAC_UNIVERSAL\")\n  set(SDK_NAME macosx)\n  if(NOT ARCHS)\n    set(ARCHS \"x86_64;arm64\")\n  endif()\n  # For universal builds, don't set target triple - let CMake handle it\n  # string(REPLACE \";\" \"-\" ARCHS_SPLIT \"${ARCHS}\")\n  # set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-macosx${DEPLOYMENT_TARGET})\nelseif(PLATFORM_INT STREQUAL \"MAC_CATALYST_UNIVERSAL\")\n  set(SDK_NAME macosx)\n  if(NOT ARCHS)\n    set(ARCHS \"x86_64;arm64\")\n  endif()\n  string(REPLACE \";\" \"-\" ARCHS_SPLIT \"${ARCHS}\")\n  set(APPLE_TARGET_TRIPLE_INT apple-ios${DEPLOYMENT_TARGET}-macabi)\nelse()\n  message(FATAL_ERROR \"Invalid PLATFORM: ${PLATFORM_INT}\")\nendif()\n\nstring(REPLACE \";\" \" \" ARCHS_SPACED \"${ARCHS}\")\n\nif(MODERN_CMAKE AND PLATFORM_INT MATCHES \".*COMBINED\" AND NOT CMAKE_GENERATOR MATCHES \"Xcode\")\n  message(FATAL_ERROR \"The COMBINED options only work with Xcode generator, -G Xcode\")\nendif()\n\nif(CMAKE_GENERATOR MATCHES \"Xcode\" AND PLATFORM_INT MATCHES \"^MAC_CATALYST\")\n  set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY \"libc++\")\n  set(CMAKE_XCODE_ATTRIBUTE_SUPPORTED_PLATFORMS \"macosx\")\n  set(CMAKE_XCODE_ATTRIBUTE_SUPPORTS_MACCATALYST \"YES\")\n  if(NOT DEFINED MACOSX_DEPLOYMENT_TARGET)\n    set(CMAKE_XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET \"10.15\")\n  else()\n    set(CMAKE_XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET \"${MACOSX_DEPLOYMENT_TARGET}\")\n  endif()\nelseif(CMAKE_GENERATOR MATCHES \"Xcode\")\n  set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY \"libc++\")\n  set(CMAKE_XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET \"${DEPLOYMENT_TARGET}\")\n  if(NOT PLATFORM_INT MATCHES \".*COMBINED\")\n    set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=${SDK_NAME}*] \"${ARCHS_SPACED}\")\n    set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=${SDK_NAME}*] \"${ARCHS_SPACED}\")\n  endif()\nendif()\n\n# If the user did not specify the SDK root to use, then query xcodebuild for it.\nif(DEFINED CMAKE_OSX_SYSROOT_INT)\n  # Environment variables are always preserved.\n  set(ENV{_CMAKE_OSX_SYSROOT_INT} \"${CMAKE_OSX_SYSROOT_INT}\")\nelseif(DEFINED ENV{_CMAKE_OSX_SYSROOT_INT})\n  set(CMAKE_OSX_SYSROOT_INT \"$ENV{_CMAKE_OSX_SYSROOT_INT}\")\nelseif(NOT DEFINED CMAKE_OSX_SYSROOT_INT)\n  execute_process(COMMAND ${XCODEBUILD_EXECUTABLE} -version -sdk ${SDK_NAME} Path\n          OUTPUT_VARIABLE CMAKE_OSX_SYSROOT_INT\n          ERROR_QUIET\n          OUTPUT_STRIP_TRAILING_WHITESPACE)\nendif()\n\nif (NOT DEFINED CMAKE_OSX_SYSROOT_INT AND NOT DEFINED CMAKE_OSX_SYSROOT)\n  message(SEND_ERROR \"Please make sure that Xcode is installed and that the toolchain\"\n          \"is pointing to the correct path. Please run:\"\n          \"sudo xcode-select -s /Applications/Xcode.app/Contents/Developer\"\n          \"and see if that fixes the problem for you.\")\n  message(FATAL_ERROR \"Invalid CMAKE_OSX_SYSROOT: ${CMAKE_OSX_SYSROOT} \"\n          \"does not exist.\")\nelseif(DEFINED CMAKE_OSX_SYSROOT_INT)\n  set(CMAKE_OSX_SYSROOT_INT \"${CMAKE_OSX_SYSROOT_INT}\" CACHE INTERNAL \"\")\n  # Specify the location or name of the platform SDK to be used in CMAKE_OSX_SYSROOT.\n  set(CMAKE_OSX_SYSROOT \"${CMAKE_OSX_SYSROOT_INT}\" CACHE INTERNAL \"\")\nendif()\n\n# Use bitcode or not\nif(NOT DEFINED ENABLE_BITCODE)\n  message(STATUS \"[DEFAULTS] Disabling bitcode support by default. ENABLE_BITCODE not provided for override!\")\n  set(ENABLE_BITCODE OFF)\nendif()\nset(ENABLE_BITCODE_INT ${ENABLE_BITCODE} CACHE BOOL\n        \"Whether or not to enable bitcode\" FORCE)\n# Use ARC or not\nif(NOT DEFINED ENABLE_ARC)\n  # Unless specified, enable ARC support by default\n  set(ENABLE_ARC ON)\n  message(STATUS \"[DEFAULTS] Enabling ARC support by default. ENABLE_ARC not provided!\")\nendif()\nset(ENABLE_ARC_INT ${ENABLE_ARC} CACHE BOOL \"Whether or not to enable ARC\" FORCE)\n# Use hidden visibility or not\nif(NOT DEFINED ENABLE_VISIBILITY)\n  # Unless specified, disable symbols visibility by default\n  set(ENABLE_VISIBILITY OFF)\n  message(STATUS \"[DEFAULTS] Hiding symbols visibility by default. ENABLE_VISIBILITY not provided!\")\nendif()\nset(ENABLE_VISIBILITY_INT ${ENABLE_VISIBILITY} CACHE BOOL \"Whether or not to hide symbols from the dynamic linker (-fvisibility=hidden)\" FORCE)\n# Set strict compiler checks or not\nif(NOT DEFINED ENABLE_STRICT_TRY_COMPILE)\n  # Unless specified, disable strict try_compile()\n  set(ENABLE_STRICT_TRY_COMPILE OFF)\n  message(STATUS \"[DEFAULTS] Using NON-strict compiler checks by default. ENABLE_STRICT_TRY_COMPILE not provided!\")\nendif()\nset(ENABLE_STRICT_TRY_COMPILE_INT ${ENABLE_STRICT_TRY_COMPILE} CACHE BOOL\n        \"Whether or not to use strict compiler checks\" FORCE)\n\n# Get the SDK version information.\nif(DEFINED SDK_VERSION)\n  # Environment variables are always preserved.\n  set(ENV{_SDK_VERSION} \"${SDK_VERSION}\")\nelseif(DEFINED ENV{_SDK_VERSION})\n  set(SDK_VERSION \"$ENV{_SDK_VERSION}\")\nelseif(NOT DEFINED SDK_VERSION)\n  execute_process(COMMAND ${XCODEBUILD_EXECUTABLE} -sdk ${CMAKE_OSX_SYSROOT_INT} -version SDKVersion\n          OUTPUT_VARIABLE SDK_VERSION\n          ERROR_QUIET\n          OUTPUT_STRIP_TRAILING_WHITESPACE)\nendif()\n\n# Find the Developer root for the specific iOS platform being compiled for\n# from CMAKE_OSX_SYSROOT.  Should be ../../ from SDK specified in\n# CMAKE_OSX_SYSROOT. There does not appear to be a direct way to obtain\n# this information from xcrun or xcodebuild.\nif (NOT DEFINED CMAKE_DEVELOPER_ROOT AND NOT CMAKE_GENERATOR MATCHES \"Xcode\")\n  get_filename_component(PLATFORM_SDK_DIR ${CMAKE_OSX_SYSROOT_INT} PATH)\n  get_filename_component(CMAKE_DEVELOPER_ROOT ${PLATFORM_SDK_DIR} PATH)\n  if (NOT EXISTS \"${CMAKE_DEVELOPER_ROOT}\")\n    message(FATAL_ERROR \"Invalid CMAKE_DEVELOPER_ROOT: ${CMAKE_DEVELOPER_ROOT} does not exist.\")\n  endif()\nendif()\n\n# Find the C & C++ compilers for the specified SDK.\nif(DEFINED CMAKE_C_COMPILER)\n  # Environment variables are always preserved.\n  set(ENV{_CMAKE_C_COMPILER} \"${CMAKE_C_COMPILER}\")\nelseif(DEFINED ENV{_CMAKE_C_COMPILER})\n  set(CMAKE_C_COMPILER \"$ENV{_CMAKE_C_COMPILER}\")\n  set(CMAKE_ASM_COMPILER ${CMAKE_C_COMPILER})\nelseif(NOT DEFINED CMAKE_C_COMPILER)\n  execute_process(COMMAND xcrun -sdk ${CMAKE_OSX_SYSROOT_INT} -find clang\n          OUTPUT_VARIABLE CMAKE_C_COMPILER\n          ERROR_QUIET\n          OUTPUT_STRIP_TRAILING_WHITESPACE)\n  set(CMAKE_ASM_COMPILER ${CMAKE_C_COMPILER})\nendif()\nif(DEFINED CMAKE_CXX_COMPILER)\n  # Environment variables are always preserved.\n  set(ENV{_CMAKE_CXX_COMPILER} \"${CMAKE_CXX_COMPILER}\")\nelseif(DEFINED ENV{_CMAKE_CXX_COMPILER})\n  set(CMAKE_CXX_COMPILER \"$ENV{_CMAKE_CXX_COMPILER}\")\nelseif(NOT DEFINED CMAKE_CXX_COMPILER)\n  execute_process(COMMAND xcrun -sdk ${CMAKE_OSX_SYSROOT_INT} -find clang++\n          OUTPUT_VARIABLE CMAKE_CXX_COMPILER\n          ERROR_QUIET\n          OUTPUT_STRIP_TRAILING_WHITESPACE)\nendif()\n# Find (Apple's) libtool.\nif(DEFINED BUILD_LIBTOOL)\n  # Environment variables are always preserved.\n  set(ENV{_BUILD_LIBTOOL} \"${BUILD_LIBTOOL}\")\nelseif(DEFINED ENV{_BUILD_LIBTOOL})\n  set(BUILD_LIBTOOL \"$ENV{_BUILD_LIBTOOL}\")\nelseif(NOT DEFINED BUILD_LIBTOOL)\n  execute_process(COMMAND xcrun -sdk ${CMAKE_OSX_SYSROOT_INT} -find libtool\n          OUTPUT_VARIABLE BUILD_LIBTOOL\n          ERROR_QUIET\n          OUTPUT_STRIP_TRAILING_WHITESPACE)\nendif()\n# Find the toolchain's provided install_name_tool if none is found on the host\nif(DEFINED CMAKE_INSTALL_NAME_TOOL)\n  # Environment variables are always preserved.\n  set(ENV{_CMAKE_INSTALL_NAME_TOOL} \"${CMAKE_INSTALL_NAME_TOOL}\")\nelseif(DEFINED ENV{_CMAKE_INSTALL_NAME_TOOL})\n  set(CMAKE_INSTALL_NAME_TOOL \"$ENV{_CMAKE_INSTALL_NAME_TOOL}\")\nelseif(NOT DEFINED CMAKE_INSTALL_NAME_TOOL)\n  execute_process(COMMAND xcrun -sdk ${CMAKE_OSX_SYSROOT_INT} -find install_name_tool\n          OUTPUT_VARIABLE CMAKE_INSTALL_NAME_TOOL_INT\n          ERROR_QUIET\n          OUTPUT_STRIP_TRAILING_WHITESPACE)\n  set(CMAKE_INSTALL_NAME_TOOL ${CMAKE_INSTALL_NAME_TOOL_INT} CACHE INTERNAL \"\")\nendif()\n\n# Configure libtool to be used instead of ar + ranlib to build static libraries.\n# This is required on Xcode 7+, but should also work on previous versions of\n# Xcode.\nget_property(languages GLOBAL PROPERTY ENABLED_LANGUAGES)\nforeach(lang ${languages})\n  set(CMAKE_${lang}_CREATE_STATIC_LIBRARY \"${BUILD_LIBTOOL} -static -o <TARGET> <LINK_FLAGS> <OBJECTS> \" CACHE INTERNAL \"\")\nendforeach()\n\n# CMake 3.14+ support building for iOS, watchOS, and tvOS out of the box.\nif(MODERN_CMAKE)\n  if(SDK_NAME MATCHES \"iphone\")\n    set(CMAKE_SYSTEM_NAME iOS)\n  elseif(SDK_NAME MATCHES \"xros\")\n      set(CMAKE_SYSTEM_NAME visionOS)\n  elseif(SDK_NAME MATCHES \"xrsimulator\")\n      set(CMAKE_SYSTEM_NAME visionOS)\n  elseif(SDK_NAME MATCHES \"macosx\")\n    set(CMAKE_SYSTEM_NAME Darwin)\n  elseif(SDK_NAME MATCHES \"appletv\")\n    set(CMAKE_SYSTEM_NAME tvOS)\n  elseif(SDK_NAME MATCHES \"watch\")\n    set(CMAKE_SYSTEM_NAME watchOS)\n  endif()\n  # Provide flags for a combined FAT library build on newer CMake versions\n  if(PLATFORM_INT MATCHES \".*COMBINED\")\n    set(CMAKE_IOS_INSTALL_COMBINED YES)\n    if(CMAKE_GENERATOR MATCHES \"Xcode\")\n      # Set the SDKROOT Xcode properties to a Xcode-friendly value (the SDK_NAME, E.g, iphoneos)\n      # This way, Xcode will automatically switch between the simulator and device SDK when building.\n      set(CMAKE_XCODE_ATTRIBUTE_SDKROOT \"${SDK_NAME}\")\n      # Force to not build just one ARCH, but all!\n      set(CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH \"NO\")\n    endif()\n  endif()\nelseif(NOT DEFINED CMAKE_SYSTEM_NAME AND ${CMAKE_VERSION} VERSION_GREATER_EQUAL \"3.10\")\n  # Legacy code path prior to CMake 3.14 or fallback if no CMAKE_SYSTEM_NAME specified\n  set(CMAKE_SYSTEM_NAME iOS)\nelseif(NOT DEFINED CMAKE_SYSTEM_NAME)\n  # Legacy code path before CMake 3.14 or fallback if no CMAKE_SYSTEM_NAME specified\n  set(CMAKE_SYSTEM_NAME Darwin)\nendif()\n# Standard settings.\nset(CMAKE_SYSTEM_VERSION ${SDK_VERSION} CACHE INTERNAL \"\")\nset(UNIX ON CACHE BOOL \"\")\nset(APPLE ON CACHE BOOL \"\")\nif(PLATFORM STREQUAL \"MAC\" OR PLATFORM STREQUAL \"MAC_ARM64\" OR PLATFORM STREQUAL \"MAC_UNIVERSAL\")\n  set(IOS OFF CACHE BOOL \"\")\n  set(MACOS ON CACHE BOOL \"\")\nelseif(PLATFORM STREQUAL \"MAC_CATALYST\" OR PLATFORM STREQUAL \"MAC_CATALYST_ARM64\" OR PLATFORM STREQUAL \"MAC_CATALYST_UNIVERSAL\")\n  set(IOS ON CACHE BOOL \"\")\n  set(MACOS ON CACHE BOOL \"\")\nelseif(PLATFORM STREQUAL \"VISIONOS\" OR PLATFORM STREQUAL \"SIMULATOR_VISIONOS\" OR PLATFORM STREQUAL \"VISIONOSCOMBINED\")\n  set(IOS OFF CACHE BOOL \"\")\n  set(VISIONOS ON CACHE BOOL \"\")\nelse()\n  set(IOS ON CACHE BOOL \"\")\nendif()\n# Set the architectures for which to build.\nset(CMAKE_OSX_ARCHITECTURES ${ARCHS} CACHE INTERNAL \"\")\n# Change the type of target generated for try_compile() so it'll work when cross-compiling, weak compiler checks\nif(NOT ENABLE_STRICT_TRY_COMPILE_INT)\n  set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)\nendif()\n# All iOS/Darwin specific settings - some may be redundant.\nif (NOT DEFINED CMAKE_MACOSX_BUNDLE)\n  set(CMAKE_MACOSX_BUNDLE YES)\nendif()\nset(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED \"NO\")\nset(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED \"NO\")\nset(CMAKE_SHARED_LIBRARY_PREFIX \"lib\")\nset(CMAKE_SHARED_LIBRARY_SUFFIX \".dylib\")\nset(CMAKE_EXTRA_SHARED_LIBRARY_SUFFIXES \".tbd\" \".so\")\nset(CMAKE_SHARED_MODULE_PREFIX \"lib\")\nset(CMAKE_SHARED_MODULE_SUFFIX \".so\")\nset(CMAKE_C_COMPILER_ABI ELF)\nset(CMAKE_CXX_COMPILER_ABI ELF)\nset(CMAKE_C_HAS_ISYSROOT 1)\nset(CMAKE_CXX_HAS_ISYSROOT 1)\nset(CMAKE_MODULE_EXISTS 1)\nset(CMAKE_DL_LIBS \"\")\nset(CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG \"-compatibility_version \")\nset(CMAKE_C_OSX_CURRENT_VERSION_FLAG \"-current_version \")\nset(CMAKE_CXX_OSX_COMPATIBILITY_VERSION_FLAG \"${CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG}\")\nset(CMAKE_CXX_OSX_CURRENT_VERSION_FLAG \"${CMAKE_C_OSX_CURRENT_VERSION_FLAG}\")\n\nif(ARCHS MATCHES \"((^|;|, )(arm64|arm64e|x86_64))+\")\n  set(CMAKE_C_SIZEOF_DATA_PTR 8)\n  set(CMAKE_CXX_SIZEOF_DATA_PTR 8)\n  if(ARCHS MATCHES \"((^|;|, )(arm64|arm64e))+\")\n    set(CMAKE_SYSTEM_PROCESSOR \"aarch64\")\n  else()\n    set(CMAKE_SYSTEM_PROCESSOR \"x86_64\")\n  endif()\nelse()\n  set(CMAKE_C_SIZEOF_DATA_PTR 4)\n  set(CMAKE_CXX_SIZEOF_DATA_PTR 4)\n  set(CMAKE_SYSTEM_PROCESSOR \"arm\")\nendif()\n\n# Note that only Xcode 7+ supports the newer more specific:\n# -m${SDK_NAME}-version-min flags, older versions of Xcode use:\n# -m(ios/ios-simulator)-version-min instead.\nif(${CMAKE_VERSION} VERSION_LESS \"3.11\")\n  if(PLATFORM_INT STREQUAL \"OS\" OR PLATFORM_INT STREQUAL \"OS64\")\n    if(XCODE_VERSION_INT VERSION_LESS 7.0)\n      set(SDK_NAME_VERSION_FLAGS\n              \"-mios-version-min=${DEPLOYMENT_TARGET}\")\n    else()\n      # Xcode 7.0+ uses flags we can build directly from SDK_NAME.\n      set(SDK_NAME_VERSION_FLAGS\n              \"-m${SDK_NAME}-version-min=${DEPLOYMENT_TARGET}\")\n    endif()\n  elseif(PLATFORM_INT STREQUAL \"TVOS\")\n    set(SDK_NAME_VERSION_FLAGS\n            \"-mtvos-version-min=${DEPLOYMENT_TARGET}\")\n  elseif(PLATFORM_INT STREQUAL \"SIMULATOR_TVOS\")\n    set(SDK_NAME_VERSION_FLAGS\n            \"-mtvos-simulator-version-min=${DEPLOYMENT_TARGET}\")\nelseif(PLATFORM_INT STREQUAL \"SIMULATORARM64_TVOS\")\n    set(SDK_NAME_VERSION_FLAGS\n            \"-mtvos-simulator-version-min=${DEPLOYMENT_TARGET}\")\n  elseif(PLATFORM_INT STREQUAL \"WATCHOS\")\n    set(SDK_NAME_VERSION_FLAGS\n            \"-mwatchos-version-min=${DEPLOYMENT_TARGET}\")\n  elseif(PLATFORM_INT STREQUAL \"SIMULATOR_WATCHOS\")\n    set(SDK_NAME_VERSION_FLAGS\n            \"-mwatchos-simulator-version-min=${DEPLOYMENT_TARGET}\")\n  elseif(PLATFORM_INT STREQUAL \"SIMULATORARM64_WATCHOS\")\n    set(SDK_NAME_VERSION_FLAGS\n            \"-mwatchos-simulator-version-min=${DEPLOYMENT_TARGET}\")\n  elseif(PLATFORM_INT STREQUAL \"MAC\")\n    set(SDK_NAME_VERSION_FLAGS\n            \"-mmacosx-version-min=${DEPLOYMENT_TARGET}\")\n  else()\n    # SIMULATOR or SIMULATOR64 both use -mios-simulator-version-min.\n    set(SDK_NAME_VERSION_FLAGS\n            \"-mios-simulator-version-min=${DEPLOYMENT_TARGET}\")\n  endif()\nelseif(NOT PLATFORM_INT MATCHES \"^MAC_CATALYST\")\n  # Newer versions of CMake sets the version min flags correctly, skip this for Mac Catalyst targets\n  set(CMAKE_OSX_DEPLOYMENT_TARGET ${DEPLOYMENT_TARGET} CACHE INTERNAL \"Minimum OS X deployment version\")\nendif()\n\nif(DEFINED APPLE_TARGET_TRIPLE_INT)\n  set(APPLE_TARGET_TRIPLE ${APPLE_TARGET_TRIPLE_INT} CACHE INTERNAL \"\")\n  set(CMAKE_C_COMPILER_TARGET ${APPLE_TARGET_TRIPLE})\n  set(CMAKE_CXX_COMPILER_TARGET ${APPLE_TARGET_TRIPLE})\n  set(CMAKE_ASM_COMPILER_TARGET ${APPLE_TARGET_TRIPLE})\nendif()\n\nif(PLATFORM_INT MATCHES \"^MAC_CATALYST\")\n  set(C_TARGET_FLAGS \"-isystem ${CMAKE_OSX_SYSROOT_INT}/System/iOSSupport/usr/include -iframework ${CMAKE_OSX_SYSROOT_INT}/System/iOSSupport/System/Library/Frameworks\")\nendif()\n\nif(ENABLE_BITCODE_INT)\n  set(BITCODE \"-fembed-bitcode\")\n  set(CMAKE_XCODE_ATTRIBUTE_BITCODE_GENERATION_MODE \"bitcode\")\n  set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE \"YES\")\nelse()\n  set(BITCODE \"\")\n  set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE \"NO\")\nendif()\n\nif(ENABLE_ARC_INT)\n  set(FOBJC_ARC \"-fobjc-arc\")\n  set(CMAKE_XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC \"YES\")\nelse()\n  set(FOBJC_ARC \"-fno-objc-arc\")\n  set(CMAKE_XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC \"NO\")\nendif()\n\nif(NAMED_LANGUAGE_SUPPORT_INT)\n  set(OBJC_VARS \"-fobjc-abi-version=2 -DOBJC_OLD_DISPATCH_PROTOTYPES=0\")\n  set(OBJC_LEGACY_VARS \"\")\nelse()\n  set(OBJC_VARS \"\")\n  set(OBJC_LEGACY_VARS \"-fobjc-abi-version=2 -DOBJC_OLD_DISPATCH_PROTOTYPES=0\")\nendif()\n\nif(NOT ENABLE_VISIBILITY_INT)\n  foreach(lang ${languages})\n    set(CMAKE_${lang}_VISIBILITY_PRESET \"hidden\" CACHE INTERNAL \"\")\n  endforeach()\n  set(CMAKE_XCODE_ATTRIBUTE_GCC_SYMBOLS_PRIVATE_EXTERN \"YES\")\n  set(VISIBILITY \"-fvisibility=hidden -fvisibility-inlines-hidden\")\nelse()\n  foreach(lang ${languages})\n    set(CMAKE_${lang}_VISIBILITY_PRESET \"default\" CACHE INTERNAL \"\")\n  endforeach()\n  set(CMAKE_XCODE_ATTRIBUTE_GCC_SYMBOLS_PRIVATE_EXTERN \"NO\")\n  set(VISIBILITY \"-fvisibility=default\")\nendif()\n\nif(DEFINED APPLE_TARGET_TRIPLE)\n  set(APPLE_TARGET_TRIPLE_FLAG \"-target ${APPLE_TARGET_TRIPLE}\")\nendif()\n\n#Check if Xcode generator is used since that will handle these flags automagically\nif(CMAKE_GENERATOR MATCHES \"Xcode\")\n  message(STATUS \"Not setting any manual command-line buildflags, since Xcode is selected as the generator. Modifying the Xcode build-settings directly instead.\")\nelse()\n  set(CMAKE_C_FLAGS \"${C_TARGET_FLAGS} ${APPLE_TARGET_TRIPLE_FLAG} ${SDK_NAME_VERSION_FLAGS} ${OBJC_LEGACY_VARS} ${BITCODE} ${VISIBILITY} ${CMAKE_C_FLAGS}\" CACHE INTERNAL\n     \"Flags used by the compiler during all C build types.\")\n  set(CMAKE_C_FLAGS_DEBUG \"-O0 -g ${CMAKE_C_FLAGS_DEBUG}\")\n  set(CMAKE_C_FLAGS_MINSIZEREL \"-DNDEBUG -Os ${CMAKE_C_FLAGS_MINSIZEREL}\")\n  set(CMAKE_C_FLAGS_RELWITHDEBINFO \"-DNDEBUG -O2 -g ${CMAKE_C_FLAGS_RELWITHDEBINFO}\")\n  set(CMAKE_C_FLAGS_RELEASE \"-DNDEBUG -O3 ${CMAKE_C_FLAGS_RELEASE}\")\n  set(CMAKE_CXX_FLAGS \"${C_TARGET_FLAGS} ${APPLE_TARGET_TRIPLE_FLAG} ${SDK_NAME_VERSION_FLAGS} ${OBJC_LEGACY_VARS} ${BITCODE} ${VISIBILITY} ${CMAKE_CXX_FLAGS}\" CACHE INTERNAL\n     \"Flags used by the compiler during all CXX build types.\")\n  set(CMAKE_CXX_FLAGS_DEBUG \"-O0 -g ${CMAKE_CXX_FLAGS_DEBUG}\")\n  set(CMAKE_CXX_FLAGS_MINSIZEREL \"-DNDEBUG -Os ${CMAKE_CXX_FLAGS_MINSIZEREL}\")\n  set(CMAKE_CXX_FLAGS_RELWITHDEBINFO \"-DNDEBUG -O2 -g ${CMAKE_CXX_FLAGS_RELWITHDEBINFO}\")\n  set(CMAKE_CXX_FLAGS_RELEASE \"-DNDEBUG -O3 ${CMAKE_CXX_FLAGS_RELEASE}\")\n  if(NAMED_LANGUAGE_SUPPORT_INT)\n    set(CMAKE_OBJC_FLAGS \"${C_TARGET_FLAGS} ${APPLE_TARGET_TRIPLE_FLAG} ${SDK_NAME_VERSION_FLAGS} ${BITCODE} ${VISIBILITY} ${FOBJC_ARC} ${OBJC_VARS} ${CMAKE_OBJC_FLAGS}\" CACHE INTERNAL\n     \"Flags used by the compiler during all OBJC build types.\")\n    set(CMAKE_OBJC_FLAGS_DEBUG \"-O0 -g ${CMAKE_OBJC_FLAGS_DEBUG}\")\n    set(CMAKE_OBJC_FLAGS_MINSIZEREL \"-DNDEBUG -Os ${CMAKE_OBJC_FLAGS_MINSIZEREL}\")\n    set(CMAKE_OBJC_FLAGS_RELWITHDEBINFO \"-DNDEBUG -O2 -g ${CMAKE_OBJC_FLAGS_RELWITHDEBINFO}\")\n    set(CMAKE_OBJC_FLAGS_RELEASE \"-DNDEBUG -O3 ${CMAKE_OBJC_FLAGS_RELEASE}\")\n    set(CMAKE_OBJCXX_FLAGS \"${C_TARGET_FLAGS} ${APPLE_TARGET_TRIPLE_FLAG} ${SDK_NAME_VERSION_FLAGS} ${BITCODE} ${VISIBILITY} ${FOBJC_ARC} ${OBJC_VARS} ${CMAKE_OBJCXX_FLAGS}\" CACHE INTERNAL\n     \"Flags used by the compiler during all OBJCXX build types.\")\n    set(CMAKE_OBJCXX_FLAGS_DEBUG \"-O0 -g ${CMAKE_OBJCXX_FLAGS_DEBUG}\")\n    set(CMAKE_OBJCXX_FLAGS_MINSIZEREL \"-DNDEBUG -Os ${CMAKE_OBJCXX_FLAGS_MINSIZEREL}\")\n    set(CMAKE_OBJCXX_FLAGS_RELWITHDEBINFO \"-DNDEBUG -O2 -g ${CMAKE_OBJCXX_FLAGS_RELWITHDEBINFO}\")\n    set(CMAKE_OBJCXX_FLAGS_RELEASE \"-DNDEBUG -O3 ${CMAKE_OBJCXX_FLAGS_RELEASE}\")\n  endif()\n  set(CMAKE_C_LINK_FLAGS \"${C_TARGET_FLAGS} ${SDK_NAME_VERSION_FLAGS} -Wl,-search_paths_first ${CMAKE_C_LINK_FLAGS}\" CACHE INTERNAL\n     \"Flags used by the compiler for all C link types.\")\n  set(CMAKE_CXX_LINK_FLAGS \"${C_TARGET_FLAGS} ${SDK_NAME_VERSION_FLAGS}  -Wl,-search_paths_first ${CMAKE_CXX_LINK_FLAGS}\" CACHE INTERNAL\n     \"Flags used by the compiler for all CXX link types.\")\n  if(NAMED_LANGUAGE_SUPPORT_INT)\n    set(CMAKE_OBJC_LINK_FLAGS \"${C_TARGET_FLAGS} ${SDK_NAME_VERSION_FLAGS} -Wl,-search_paths_first ${CMAKE_OBJC_LINK_FLAGS}\" CACHE INTERNAL\n     \"Flags used by the compiler for all OBJC link types.\")\n    set(CMAKE_OBJCXX_LINK_FLAGS \"${C_TARGET_FLAGS} ${SDK_NAME_VERSION_FLAGS} -Wl,-search_paths_first ${CMAKE_OBJCXX_LINK_FLAGS}\" CACHE INTERNAL\n     \"Flags used by the compiler for all OBJCXX link types.\")\n  endif()\n  set(CMAKE_ASM_FLAGS \"${CMAKE_C_FLAGS} -x assembler-with-cpp\" CACHE INTERNAL\n     \"Flags used by the compiler for all ASM build types.\")\nendif()\n\n## Print status messages to inform of the current state\nmessage(STATUS \"Configuring ${SDK_NAME} build for platform: ${PLATFORM_INT}, architecture(s): ${ARCHS}\")\nmessage(STATUS \"Using SDK: ${CMAKE_OSX_SYSROOT_INT}\")\nmessage(STATUS \"Using C compiler: ${CMAKE_C_COMPILER}\")\nmessage(STATUS \"Using CXX compiler: ${CMAKE_CXX_COMPILER}\")\nmessage(STATUS \"Using libtool: ${BUILD_LIBTOOL}\")\nmessage(STATUS \"Using install name tool: ${CMAKE_INSTALL_NAME_TOOL}\")\nif(DEFINED APPLE_TARGET_TRIPLE)\n  message(STATUS \"Autoconf target triple: ${APPLE_TARGET_TRIPLE}\")\nendif()\nmessage(STATUS \"Using minimum deployment version: ${DEPLOYMENT_TARGET}\"\n        \" (SDK version: ${SDK_VERSION})\")\nif(MODERN_CMAKE)\n  message(STATUS \"Merging integrated CMake 3.14+ iOS,tvOS,watchOS,macOS toolchain(s) with this toolchain!\")\n  if(PLATFORM_INT MATCHES \".*COMBINED\")\n    message(STATUS \"Will combine built (static) artifacts into FAT lib...\")\n  endif()\nendif()\nif(CMAKE_GENERATOR MATCHES \"Xcode\")\n  message(STATUS \"Using Xcode version: ${XCODE_VERSION_INT}\")\nendif()\nmessage(STATUS \"CMake version: ${CMAKE_VERSION}\")\nif(DEFINED SDK_NAME_VERSION_FLAGS)\n  message(STATUS \"Using version flags: ${SDK_NAME_VERSION_FLAGS}\")\nendif()\nmessage(STATUS \"Using a data_ptr size of: ${CMAKE_CXX_SIZEOF_DATA_PTR}\")\nif(ENABLE_BITCODE_INT)\n  message(STATUS \"Bitcode: Enabled\")\nelse()\n  message(STATUS \"Bitcode: Disabled\")\nendif()\n\nif(ENABLE_ARC_INT)\n  message(STATUS \"ARC: Enabled\")\nelse()\n  message(STATUS \"ARC: Disabled\")\nendif()\n\nif(ENABLE_VISIBILITY_INT)\n  message(STATUS \"Hiding symbols: Disabled\")\nelse()\n  message(STATUS \"Hiding symbols: Enabled\")\nendif()\n\n# Set global properties\nset_property(GLOBAL PROPERTY PLATFORM \"${PLATFORM}\")\nset_property(GLOBAL PROPERTY APPLE_TARGET_TRIPLE \"${APPLE_TARGET_TRIPLE_INT}\")\nset_property(GLOBAL PROPERTY SDK_VERSION \"${SDK_VERSION}\")\nset_property(GLOBAL PROPERTY XCODE_VERSION \"${XCODE_VERSION_INT}\")\nset_property(GLOBAL PROPERTY OSX_ARCHITECTURES \"${CMAKE_OSX_ARCHITECTURES}\")\n\n# Export configurable variables for the try_compile() command.\nset(CMAKE_TRY_COMPILE_PLATFORM_VARIABLES\n        PLATFORM\n        XCODE_VERSION_INT\n        SDK_VERSION\n        NAMED_LANGUAGE_SUPPORT\n        DEPLOYMENT_TARGET\n        CMAKE_DEVELOPER_ROOT\n        CMAKE_OSX_SYSROOT_INT\n        ENABLE_BITCODE\n        ENABLE_ARC\n        CMAKE_ASM_COMPILER\n        CMAKE_C_COMPILER\n        CMAKE_C_COMPILER_TARGET\n        CMAKE_CXX_COMPILER\n        CMAKE_CXX_COMPILER_TARGET\n        BUILD_LIBTOOL\n        CMAKE_INSTALL_NAME_TOOL\n        CMAKE_C_FLAGS\n        CMAKE_C_DEBUG\n        CMAKE_C_MINSIZEREL\n        CMAKE_C_RELWITHDEBINFO\n        CMAKE_C_RELEASE\n        CMAKE_CXX_FLAGS\n        CMAKE_CXX_FLAGS_DEBUG\n        CMAKE_CXX_FLAGS_MINSIZEREL\n        CMAKE_CXX_FLAGS_RELWITHDEBINFO\n        CMAKE_CXX_FLAGS_RELEASE\n        CMAKE_C_LINK_FLAGS\n        CMAKE_CXX_LINK_FLAGS\n        CMAKE_ASM_FLAGS\n)\n\nif(NAMED_LANGUAGE_SUPPORT_INT)\n  list(APPEND CMAKE_TRY_COMPILE_PLATFORM_VARIABLES\n        CMAKE_OBJC_FLAGS\n        CMAKE_OBJC_DEBUG\n        CMAKE_OBJC_MINSIZEREL\n        CMAKE_OBJC_RELWITHDEBINFO\n        CMAKE_OBJC_RELEASE\n        CMAKE_OBJCXX_FLAGS\n        CMAKE_OBJCXX_DEBUG\n        CMAKE_OBJCXX_MINSIZEREL\n        CMAKE_OBJCXX_RELWITHDEBINFO\n        CMAKE_OBJCXX_RELEASE\n        CMAKE_OBJC_LINK_FLAGS\n        CMAKE_OBJCXX_LINK_FLAGS\n  )\nendif()\n\nset(CMAKE_PLATFORM_HAS_INSTALLNAME 1)\nset(CMAKE_SHARED_LINKER_FLAGS \"-rpath @executable_path/Frameworks -rpath @loader_path/Frameworks\")\nset(CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS \"-dynamiclib -Wl,-headerpad_max_install_names\")\nset(CMAKE_SHARED_MODULE_CREATE_C_FLAGS \"-bundle -Wl,-headerpad_max_install_names\")\nset(CMAKE_SHARED_MODULE_LOADER_C_FLAG \"-Wl,-bundle_loader,\")\nset(CMAKE_SHARED_MODULE_LOADER_CXX_FLAG \"-Wl,-bundle_loader,\")\nset(CMAKE_FIND_LIBRARY_SUFFIXES \".tbd\" \".dylib\" \".so\" \".a\")\nset(CMAKE_SHARED_LIBRARY_SONAME_C_FLAG \"-install_name\")\n\n# Set the find root to the SDK developer roots.\n# Note: CMAKE_FIND_ROOT_PATH is only useful when cross-compiling. Thus, do not set on macOS builds.\nif(NOT PLATFORM_INT MATCHES \"^MAC.*$\")\n  list(APPEND CMAKE_FIND_ROOT_PATH \"${CMAKE_OSX_SYSROOT_INT}\" CACHE INTERNAL \"\")\n  set(CMAKE_IGNORE_PATH \"/System/Library/Frameworks;/usr/local/lib;/opt/homebrew\" CACHE INTERNAL \"\")\nendif()\n\n# Default to searching for frameworks first.\nIF(NOT DEFINED CMAKE_FIND_FRAMEWORK)\n  set(CMAKE_FIND_FRAMEWORK FIRST)\nENDIF(NOT DEFINED CMAKE_FIND_FRAMEWORK)\n\n# Set up the default search directories for frameworks.\nif(PLATFORM_INT MATCHES \"^MAC_CATALYST\")\n  set(CMAKE_FRAMEWORK_PATH\n          ${CMAKE_DEVELOPER_ROOT}/Library/PrivateFrameworks\n          ${CMAKE_OSX_SYSROOT_INT}/System/Library/Frameworks\n          ${CMAKE_OSX_SYSROOT_INT}/System/iOSSupport/System/Library/Frameworks\n          ${CMAKE_FRAMEWORK_PATH} CACHE INTERNAL \"\")\nelse()\n  set(CMAKE_FRAMEWORK_PATH\n          ${CMAKE_DEVELOPER_ROOT}/Library/PrivateFrameworks\n          ${CMAKE_OSX_SYSROOT_INT}/System/Library/Frameworks\n          ${CMAKE_FRAMEWORK_PATH} CACHE INTERNAL \"\")\nendif()\n\n# By default, search both the specified iOS SDK and the remainder of the host filesystem.\nif(NOT CMAKE_FIND_ROOT_PATH_MODE_PROGRAM)\n  set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH CACHE INTERNAL \"\")\nendif()\nif(NOT CMAKE_FIND_ROOT_PATH_MODE_LIBRARY)\n  set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH CACHE INTERNAL \"\")\nendif()\nif(NOT CMAKE_FIND_ROOT_PATH_MODE_INCLUDE)\n  set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH CACHE INTERNAL \"\")\nendif()\nif(NOT CMAKE_FIND_ROOT_PATH_MODE_PACKAGE)\n  set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH CACHE INTERNAL \"\")\nendif()\n\n#\n# Some helper-macros below to simplify and beautify the CMakeFile\n#\n\n# This little macro lets you set any Xcode specific property.\nmacro(set_xcode_property TARGET XCODE_PROPERTY XCODE_VALUE XCODE_RELVERSION)\n  set(XCODE_RELVERSION_I \"${XCODE_RELVERSION}\")\n  if(XCODE_RELVERSION_I STREQUAL \"All\")\n    set_property(TARGET ${TARGET} PROPERTY XCODE_ATTRIBUTE_${XCODE_PROPERTY} \"${XCODE_VALUE}\")\n  else()\n    set_property(TARGET ${TARGET} PROPERTY XCODE_ATTRIBUTE_${XCODE_PROPERTY}[variant=${XCODE_RELVERSION_I}] \"${XCODE_VALUE}\")\n  endif()\nendmacro(set_xcode_property)\n\n# This macro lets you find executable programs on the host system.\nmacro(find_host_package)\n  set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)\n  set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER)\n  set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER)\n  set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE NEVER)\n  set(_TOOLCHAIN_IOS ${IOS})\n  set(IOS OFF)\n  find_package(${ARGN})\n  set(IOS ${_TOOLCHAIN_IOS})\n  set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH)\n  set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH)\n  set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH)\n  set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH)\nendmacro(find_host_package)\n"
  },
  {
    "path": "lldb-extras/.lldbinit",
    "content": "command script import ~/metaforce_lldb_tools.py\ntype synthetic add zeus::CMatrix3f --python-class metaforce_lldb_tools.CMatrix3f_Provider\ntype synthetic add zeus::CMatrix4f --python-class metaforce_lldb_tools.CMatrix4f_Provider\n\ntype summary add --summary-string \"(${var.__s_.__storage_[0]}, ${var.__s_.__storage_[1]}, ${var.__s_.__storage_[2]}, ${var.__s_.__storage_[3]})\" zeus::simd<float>\ntype summary add --summary-string \"(${var.__s_.__storage_[0]}, ${var.__s_.__storage_[1]}, ${var.__s_.__storage_[2]}, ${var.__s_.__storage_[3]})\" zeus::simd<double>\n\ntype summary add --summary-string \"(${var.x}, ${var.y})\" zeus::CVector2i\ntype summary add --summary-string \"(${var.mSimd.__s_.__storage_[0]}, ${var.mSimd.__s_.__storage_[1]})\" zeus::CVector2f\ntype summary add --summary-string \"(${var.mSimd.__s_.__storage_[0]}, ${var.mSimd.__s_.__storage_[1]}, ${var.mSimd.__s_.__storage_[2]})\" zeus::CVector3f\ntype summary add --summary-string \"(${var.mSimd.__s_.__storage_[0]}, ${var.mSimd.__s_.__storage_[1]}, ${var.mSimd.__s_.__storage_[2]})\" zeus::CVector3d\ntype summary add --summary-string \"(${var.mSimd.__s_.__storage_[0]}, ${var.mSimd.__s_.__storage_[1]}, ${var.mSimd.__s_.__storage_[2]}, ${var.mSimd.__s_.__storage_[3]})\" zeus::CVector4f\ntype summary add --summary-string \"(${var.mSimd.__s_.__storage_[0]}, ${var.mSimd.__s_.__storage_[1]}, ${var.mSimd.__s_.__storage_[2]}, ${var.mSimd.__s_.__storage_[3]})\" zeus::CColor\ntype summary add --summary-string \"${var.angle}\" zeus::CRelAngle\ntype summary add --summary-string \"(${var.mSimd.__s_.__storage_[0]}, ${var.mSimd.__s_.__storage_[1]}, ${var.mSimd.__s_.__storage_[2]}, ${var.mSimd.__s_.__storage_[3]})\" zeus::CQuaternion\ntype summary add --summary-string \"pos=${var.position} radius=${var.radius}\" zeus::CSphere\ntype summary add --summary-string \"norm=(${var.mSimd.__s_.__storage_[0]}, ${var.mSimd.__s_.__storage_[1]}, ${var.mSimd.__s_.__storage_[2]}) d=${var.mSimd.__s_.__storage_[3]}\" zeus::CPlane\ntype summary add --summary-string \"min=${var.min} max=${var.max}\" zeus::CAABox\ntype summary add --summary-string \"start=${var.origin} dir=${var.dir}\" zeus::CLine\ntype summary add --summary-string \"start=${var.x0_start} dir=${var.xc_dir} end=${var.x18_end}\" zeus::CLineSeg\ntype summary add --summary-string \"pos=${var.position} size=${var.size}\" zeus::CRectangle\ntype summary add --summary-string \"${var.origin}\" zeus::CTransform\n\ntype summary add --summary-string \"${var.id%x} area=${var.id[16-25]}, layer=${var.id[26-31]}, id=${var.id[0-15]}\" metaforce::TEditorId\ntype summary add --summary-string \"${var.id}\" metaforce::TUniqueId\n\ntype summary add --summary-string \"${var.x0_time}\" metaforce::CCharAnimTime\n\ntype summary add --summary-string \"${var.id%x}\" metaforce::CAssetId\ntype summary add --summary-string \"${var.type.fcc} ${var.id.id%x}\" metaforce::SObjectTag\n\n# \\s*(\\S+) \\((\\S+)\\)\n# type summary add --summary-string \"\\${var.x10_name} \\${var.xc_editorId}\" $2::$1\\n\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}, active=${var.x30_24_active}\" metaforce::CEntity\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CActor\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CEffect\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CExplosion\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CHUDBillboardEffect\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CIceImpact\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CFire\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CFishCloud\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CFishCloudModifier\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CGameCamera\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CBallCamera\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CCinematicCamera\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CFirstPersonCamera\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CInterpolationCamera\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CPathCamera\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptSpindleCamera\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CGameLight\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CPhysicsActor\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CAi\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CDestroyableRock\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CPatterned\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::MP1::CAtomicAlpha\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::MP1::CBabygoth\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::MP1::CBeetle\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::MP1::CBloodFlower\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::MP1::CBurrower\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::MP1::CChozoGhost\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::MP1::CElitePirate\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::MP1::CEyeball\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::MP1::CFireFlea\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::MP1::CFlickerBat\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::MP1::CFlyingPirate\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::MP1::CMagdolite\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::MP1::CMetaree\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::MP1::CMetroid\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::MP1::CMetroidBeta\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::MP1::CMetroidPrimeExo\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::MP1::CNewIntroBoss\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::MP1::CPuddleToadGamma\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::MP1::CPuffer\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::MP1::CRidley\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::MP1::CSpacePirate\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CThardusRockProjectile\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::MP1::CTryclops\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CWallWalker\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::MP1::CParasite\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::MP1::CSeedling\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::MP1::CWarWasp\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CAmbientAI\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CCollisionActor\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CPlayer\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptActor\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::MP1::CActorContraption\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptPlayerActor\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptDebris\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptDock\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptDoor\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptGunTurret\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptPickup\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptPlatform\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CRepulsor\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptAiJumpPoint\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptBeam\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptCameraHint\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptCameraHintTrigger\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptCameraPitchVolume\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptCameraWaypoint\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptCoverPoint\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptDamageableTrigger\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptDebugCameraWaypoint\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptEffect\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptEMPulse\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptGrapplePoint\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptMazeNode\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptPlayerHint\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptPointOfInterest\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptShadowProjector\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptSound\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptSpecialFunction\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptSpiderBallAttractionSurface\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptSpiderBallWaypoint\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptTargetingPoint\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptTrigger\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptBallTrigger\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptSteam\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptWater\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptVisorFlare\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptVisorGoo\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptWaypoint\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CSnakeWeedSwarm\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CWallCrawlerSwarm\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CWeapon\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CBomb\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CGameProjectile\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CBeamProjectile\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CPlasmaProjectile\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CEnergyProjectile\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::MP1::CFlaahgraProjectile\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::MP1::CMetroidPrimeProjectile\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CTargetableProjectile\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CFlameThrower\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CNewFlameThrower\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CWaveBuster\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CPowerBomb\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::MP1::CFireFlea::CDeathCameraEffect\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::MP1::CMetroidPrimeRelay\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptActorKeyframe\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptActorRotate\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptAreaAttributes\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptCameraBlurKeyframe\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptCameraFilterKeyframe\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptCameraShaker\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptColorModulate\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptControllerAction\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptCounter\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptDistanceFog\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptDockAreaChange\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptGenerator\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptHUDMemo\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptMemoryRelay\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptMidi\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptPickupGenerator\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptPlayerStateChange\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptRandomRelay\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptRelay\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptRipple\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptRoomAcoustics\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptSpawnPoint\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptStreamedMusic\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptSwitch\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptTimer\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CScriptWorldTeleporter\ntype summary add --summary-string \"${var.x10_name} ${var.xc_editorId}\" metaforce::CTeamAiMgr\n\n"
  },
  {
    "path": "lldb-extras/README.txt",
    "content": "Copy (or append) .lldbinit and metaforce_lldb_tools.py to your home directory to\nenable various Metaforce type summaries in LLDB.\n\n"
  },
  {
    "path": "lldb-extras/metaforce_lldb_tools.py",
    "content": "import lldb\n\nclass CMatrix3f_Provider:\n\n    def __init__(self, valobj, dict):\n        self.valobj = valobj\n\n    def num_children(self):\n        return 3\n\n    def get_child_index(self, name):\n        try:\n            return int(name.lstrip('[').rstrip(']'))\n        except:\n            return -1\n\n    def get_child_at_index(self, index):\n        if index < 0:\n            return None\n        if index >= self.num_children():\n            return None\n        try:\n            m = self.valobj.GetChildMemberWithName('m')\n            return m.GetChildAtIndex(index)\n        except:\n            return None\n\n    def has_children(self):\n        return True\n\nclass CMatrix4f_Provider(CMatrix3f_Provider):\n\n    def num_children(self):\n        return 4\n"
  },
  {
    "path": "normalize_submodules.sh",
    "content": "#! /bin/bash\n\norigin=$(git remote get-url origin)\norigin_base=${origin%/*}\n\nfor sub in \"extern/amuse\" \\\n           \"extern/boo\" \\\n           \"extern/jbus\" \\\n           \"extern/kabufuda\" \\\n           \"extern/xxhash\" \\\n           \"extern/zeus\"; do\n    if [ -d $sub ]; then\n        pushd $sub > /dev/null\n        sub_name=$(basename $sub)\n        popd > /dev/null\n        echo \"Changing url for submodule ${sub} to https://github.com/AxioDL/${sub_name}.git\"\n        git config submodule.$sub.url https://github.com/AxioDL/$sub_name.git\n        git submodule init $sub\n    fi\ndone\n\necho Updating submodules\ngit submodule update --init --recursive\necho Done\n"
  },
  {
    "path": "version.h.in",
    "content": "#ifndef VERSION_H\n#define VERSION_H\n\n#define METAFORCE_WC_DESCRIBE \"@METAFORCE_WC_DESCRIBE@\"\n#define METAFORCE_VERSION_STRING \"@METAFORCE_VERSION_STRING@\"\n\n#define METAFORCE_WC_BRANCH \"@METAFORCE_WC_BRANCH@\"\n#define METAFORCE_WC_REVISION \"@METAFORCE_WC_REVISION@\"\n#define METAFORCE_WC_DATE \"@METAFORCE_WC_DATE@\"\n#define METAFORCE_BUILD_TYPE \"@CMAKE_BUILD_TYPE@\"\n\n#if defined(__x86_64__) || defined(_M_AMD64)\n#define METAFORCE_DLPACKAGE \"metaforce-@METAFORCE_WC_DESCRIBE@-@PLATFORM_NAME@-x86_64-@METAFORCE_VECTOR_ISA@\"\n#elif defined(__i386__) || defined(_M_IX86)\n#define METAFORCE_DLPACKAGE \"metaforce-@METAFORCE_WC_DESCRIBE@-@PLATFORM_NAME@-x86\"\n#elif defined(__aarch64__) || defined(_M_ARM64)\n#define METAFORCE_DLPACKAGE \"metaforce-@METAFORCE_WC_DESCRIBE@-@PLATFORM_NAME@-arm64\"\n#elif defined(EMSCRIPTEN)\n#define METAFORCE_DLPACKAGE \"metaforce-@METAFORCE_WC_DESCRIBE@-@PLATFORM_NAME@-wasm\"\n#endif\n\n#endif\n"
  }
]